chore(node-sdk): audit providers kimi grok qwen letta

Command files use shell-command and managedScriptReference; runtime checks use managed-runtime-status; jsonc-edit.ts and parseCommentedJsonObject replace copied JSONC helpers.

Claude-Session: https://claude.ai/code/session_01Agoc9bRVE9KL7mmWtey9Vq
This commit is contained in:
雷电芽衣 committed 2026-09-02 22:07:30 -04:00
1 parent db4a6ffb62
commit 3b5cbd6e17
24 files changed
+209 -1084

No files matched your search

+24 -5
View File
@@ -1,3 +1,4 @@
import os from 'node:os';
import path from 'node:path';
export const MAX_SECRET_BYTES = 64 * 1024;
@@ -16,12 +17,13 @@ export function hasCode(error: unknown, code: string): boolean {
return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
}
export function pathKey(filePath: string): string {
const resolved = path.resolve(filePath);
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
}
export function samePath(left: string, right: string): boolean {
const normalizedLeft = path.resolve(left);
const normalizedRight = path.resolve(right);
return process.platform === 'win32'
? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
: normalizedLeft === normalizedRight;
return pathKey(left) === pathKey(right);
}
export function sameAgentId(left: unknown, right: string): boolean {
@@ -29,3 +31,20 @@ export function sameAgentId(left: unknown, right: string): boolean {
? left.toLowerCase() === right.toLowerCase()
: left === right);
}
export interface ManagedScriptReference {
readonly agentId: string;
readonly scriptPath: string;
}
// Accepts only ~/.elydora/<agentId>/<scriptName>.
export function managedScriptReference(
scriptPath: string,
scriptName: string,
): ManagedScriptReference | undefined {
if (!path.isAbsolute(scriptPath) || path.basename(scriptPath) !== scriptName) return undefined;
const agentDirectory = path.dirname(scriptPath);
if (!samePath(path.dirname(agentDirectory), path.join(os.homedir(), '.elydora'))) return undefined;
const agentId = path.basename(agentDirectory);
return agentId && agentId !== '.' && agentId !== '..' ? { agentId, scriptPath } : undefined;
}
+7 -28
View File
@@ -1,9 +1,5 @@
import {
applyEdits,
modify,
type FormattingOptions,
type JSONPath,
} from 'jsonc-parser';
import type { JSONPath } from 'jsonc-parser';
import { changeJsonc } from './jsonc-edit.js';
import type { FileSnapshot } from './managed-files.js';
import type { DroidPolicyState } from './droid-policy.js';
import {
@@ -158,23 +154,6 @@ function eventPath(document: DroidDocument, event: string): JSONPath {
return [...document.basePath, event];
}
function formatting(raw: string): FormattingOptions {
const indentation = /\r?\n([ \t]+)\S/.exec(raw)?.[1];
const insertSpaces = !indentation?.includes('\t');
return {
eol: raw.includes('\r\n') ? '\r\n' : '\n',
insertSpaces,
tabSize: insertSpaces ? Math.max(1, indentation?.length ?? 2) : 1,
};
}
function change(raw: string, path: JSONPath, value: unknown, isArrayInsertion = false): string {
return applyEdits(raw, modify(raw, path, value, {
formattingOptions: formatting(raw),
isArrayInsertion,
}));
}
function currentDocument(document: DroidDocument, raw: string): DroidDocument {
return parseDocument({
exists: document.exists,
@@ -194,17 +173,17 @@ function removeManagedEntries(document: DroidDocument, raw: string, agentId?: st
for (const removal of eventRemovals) {
const groupPath = [...eventPath(document, event), removal.groupIndex];
if (removal.removeGroup) {
raw = change(raw, groupPath, undefined);
raw = changeJsonc(raw, groupPath, undefined);
continue;
}
for (const handlerIndex of [...removal.handlerIndexes].sort((left, right) => right - left)) {
raw = change(raw, [...groupPath, 'hooks', handlerIndex], undefined);
raw = changeJsonc(raw, [...groupPath, 'hooks', handlerIndex], undefined);
}
}
if (eventRemovals.length > 0) {
const current = currentDocument(document, raw);
if ((current.hooks[event] ?? []).length === 0) {
raw = change(raw, eventPath(document, event), undefined);
raw = changeJsonc(raw, eventPath(document, event), undefined);
}
}
}
@@ -220,9 +199,9 @@ function appendGroup(
const current = currentDocument(document, raw);
if (Object.prototype.hasOwnProperty.call(current.hooks, event)) {
const groups = current.hooks[event] ?? [];
return change(raw, [...eventPath(document, event), groups.length], group, true);
return changeJsonc(raw, [...eventPath(document, event), groups.length], group, true);
}
return change(raw, eventPath(document, event), [group]);
return changeJsonc(raw, eventPath(document, event), [group]);
}
function hookFileIsEmpty(document: DroidDocument, raw: string): boolean {
+10 -85
View File
@@ -1,14 +1,4 @@
import {
applyEdits,
modify,
parse,
parseTree,
printParseErrorCode,
type FormattingOptions,
type JSONPath,
type Node,
type ParseError,
} from 'jsonc-parser';
import type { JSONPath } from 'jsonc-parser';
import {
MANAGED_EVENTS,
managedGeminiRemovals,
@@ -19,7 +9,8 @@ import {
type GeminiHooks,
type ManagedGeminiEvent,
} from './gemini-contract.js';
import { isObject, type JsonObject } from './strict-json.js';
import { changeJsonc } from './jsonc-edit.js';
import { parseCommentedJsonObject, type JsonObject } from './strict-json.js';
export const GEMINI_OWNED_FILE_MARKER = '// Managed by Elydora';
@@ -46,53 +37,9 @@ interface DocumentOptions {
readonly raw: string;
}
function rejectDuplicateKeys(
node: Node | undefined,
label: string,
location: string[] = [],
): void {
if (!node) return;
if (node.type === 'object') {
const keys = new Set<string>();
for (const property of node.children ?? []) {
const key = String(property.children?.[0]?.value);
const childLocation = [...location, key];
if (keys.has(key)) {
throw new Error(`${label} contains duplicate field "${childLocation.join('.')}"`);
}
keys.add(key);
rejectDuplicateKeys(property.children?.[1], label, childLocation);
}
return;
}
if (node.type === 'array') {
for (const child of node.children ?? []) rejectDuplicateKeys(child, label, location);
}
}
function parseJsoncObject(raw: string, label: string): JsonObject {
const errors: ParseError[] = [];
const value: unknown = parse(raw, errors, {
allowTrailingComma: false,
disallowComments: false,
});
if (errors.length > 0) {
const details = errors
.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`)
.join(', ');
throw new Error(`Failed to parse ${label}: ${details}`);
}
if (!isObject(value)) throw new Error(`${label} must contain a JSON object`);
rejectDuplicateKeys(parseTree(raw, [], {
allowTrailingComma: false,
disallowComments: false,
}), label);
return value;
}
export function parseGeminiDocument(options: DocumentOptions): GeminiDocument {
const label = `Gemini CLI user settings at ${options.filePath}`;
const root = parseJsoncObject(options.raw, label);
const root = parseCommentedJsonObject(options.raw, label);
const hasHooksContainer = Object.hasOwn(root, 'hooks');
return {
...options,
@@ -112,28 +59,6 @@ export function createGeminiDocument(filePath: string): GeminiDocument {
});
}
function formatting(raw: string): FormattingOptions {
const indentation = /\r?\n([ \t]+)\S/.exec(raw)?.[1];
const insertSpaces = !indentation?.includes('\t');
return {
eol: raw.includes('\r\n') ? '\r\n' : '\n',
insertSpaces,
tabSize: insertSpaces ? Math.max(1, indentation?.length ?? 2) : 1,
};
}
function change(
raw: string,
jsonPath: JSONPath,
value: unknown,
isArrayInsertion = false,
): string {
return applyEdits(raw, modify(raw, jsonPath, value, {
formattingOptions: formatting(raw),
isArrayInsertion,
}));
}
function currentDocument(document: GeminiDocument, raw: string): GeminiDocument {
return parseGeminiDocument({
exists: document.exists,
@@ -155,25 +80,25 @@ function removeManagedEntries(
for (const removal of eventRemovals) {
const groupPath: JSONPath = ['hooks', event, removal.groupIndex];
if (removal.removeGroup) {
raw = change(raw, groupPath, undefined);
raw = changeJsonc(raw, groupPath, undefined);
continue;
}
for (const handlerIndex of [...removal.handlerIndexes].sort(
(left, right) => right - left,
)) {
raw = change(raw, [...groupPath, 'hooks', handlerIndex], undefined);
raw = changeJsonc(raw, [...groupPath, 'hooks', handlerIndex], undefined);
}
}
if (eventRemovals.length > 0) {
const current = currentDocument(document, raw);
if ((current.hooks[event] ?? []).length === 0) {
raw = change(raw, ['hooks', event], undefined);
raw = changeJsonc(raw, ['hooks', event], undefined);
}
}
}
const current = currentDocument(document, raw);
if (current.hasHooksContainer && Object.keys(current.hooks).length === 0) {
raw = change(raw, ['hooks'], undefined);
raw = changeJsonc(raw, ['hooks'], undefined);
}
return raw;
}
@@ -186,9 +111,9 @@ function appendGroup(
): string {
const current = currentDocument(document, raw);
if (current.hooks[event]) {
return change(raw, ['hooks', event, current.hooks[event].length], group, true);
return changeJsonc(raw, ['hooks', event, current.hooks[event].length], group, true);
}
return change(raw, ['hooks', event], [group]);
return changeJsonc(raw, ['hooks', event], [group]);
}
export function renderGeminiDocument(
+14 -143
View File
@@ -1,140 +1,21 @@
import os from 'node:os';
import path from 'node:path';
import { managedScriptReference, type ManagedScriptReference } from './common.js';
import {
encodedWindowsCommand,
isNodeExecutable,
parseEncodedWindowsCommand,
parseLegacyWindowsCommand,
parsePosixCommand,
posixSource,
} from './shell-command.js';
interface ParsedArgument {
readonly value: string;
readonly next: number;
}
export interface GrokRuntimeReference {
readonly agentId: string;
readonly scriptPath: string;
}
export function sameGrokPath(left: string, right: string): boolean {
const normalizedLeft = path.resolve(left);
const normalizedRight = path.resolve(right);
return process.platform === 'win32'
? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
: normalizedLeft === normalizedRight;
}
export function sameGrokAgentId(left: string, right: string): boolean {
return process.platform === 'win32'
? left.toLowerCase() === right.toLowerCase()
: left === right;
}
function quotePosix(value: string): string {
return `'${value.replaceAll("'", `'"'"'`)}'`;
}
function quotePowerShell(value: string): string {
return `'${value.replaceAll("'", "''")}'`;
}
function windowsPowerShellPath(): string {
const configuredRoot = process.platform === 'win32' ? process.env.SystemRoot : undefined;
const systemRoot = configuredRoot
&& path.win32.isAbsolute(configuredRoot)
&& !/["%\r\n]/.test(configuredRoot)
? configuredRoot
: 'C:\\Windows';
return path.win32.join(
systemRoot,
'System32',
'WindowsPowerShell',
'v1.0',
'powershell.exe',
);
}
function windowsCommand(scriptPath: string): string {
const source = `& ${quotePowerShell(process.execPath)} ${quotePowerShell(scriptPath)}; exit $LASTEXITCODE`;
const encoded = Buffer.from(source, 'utf16le').toString('base64');
return `"${windowsPowerShellPath()}" -NoLogo -NoProfile -NonInteractive -EncodedCommand ${encoded}`;
}
export type GrokRuntimeReference = ManagedScriptReference;
export function buildGrokCommand(scriptPath: string): string {
if (!path.isAbsolute(process.execPath) || !path.isAbsolute(scriptPath)) {
throw new Error('Grok hook commands require absolute executable and script paths');
}
return process.platform === 'win32'
? windowsCommand(scriptPath)
: `${quotePosix(process.execPath)} ${quotePosix(scriptPath)}`;
}
function readPosixArgument(command: string, start: number): ParsedArgument | undefined {
if (command[start] !== "'") return undefined;
const apostrophe = `'"'"'`;
let value = '';
for (let index = start + 1; index < command.length;) {
if (command.startsWith(apostrophe, index)) {
value += "'";
index += apostrophe.length;
continue;
}
if (command[index] === "'") return { value, next: index + 1 };
value += command[index];
index += 1;
}
return undefined;
}
function parsePosixCommand(command: string): readonly [string, string] | undefined {
const executable = readPosixArgument(command, 0);
if (!executable || command[executable.next] !== ' ') return undefined;
const script = readPosixArgument(command, executable.next + 1);
if (!script || script.next !== command.length) return undefined;
return [executable.value, script.value];
}
function readPowerShellArgument(command: string, start: number): ParsedArgument | undefined {
if (command[start] !== "'") return undefined;
let value = '';
for (let index = start + 1; index < command.length; index += 1) {
if (command[index] !== "'") {
value += command[index];
continue;
}
if (command[index + 1] === "'") {
value += "'";
index += 1;
continue;
}
return { value, next: index + 1 };
}
return undefined;
}
function parsePowerShellSource(source: string): readonly [string, string] | undefined {
if (!source.startsWith('& ')) return undefined;
const executable = readPowerShellArgument(source, 2);
if (!executable || source[executable.next] !== ' ') return undefined;
const script = readPowerShellArgument(source, executable.next + 1);
if (!script || source.slice(script.next) !== '; exit $LASTEXITCODE') return undefined;
return [executable.value, script.value];
}
function parseWindowsCommand(command: string): readonly [string, string] | undefined {
const match = /^"([^"\r\n]+)" -NoLogo -NoProfile -NonInteractive -EncodedCommand ([A-Za-z0-9+/]+={0,2})$/.exec(command);
if (!match
|| !path.win32.isAbsolute(match[1])
|| path.win32.basename(match[1]).toLowerCase() !== 'powershell.exe') return undefined;
const encoded = match[2];
const buffer = Buffer.from(encoded, 'base64');
if (buffer.length % 2 !== 0 || buffer.toString('base64') !== encoded) return undefined;
return parsePowerShellSource(buffer.toString('utf16le'));
}
function parseLegacyWindowsCommand(command: string): readonly [string, string] | undefined {
const match = /^"([^"\r\n]+)" "([^"\r\n]+)"$/.exec(command);
return match ? [match[1], match[2]] : undefined;
}
function isNodeExecutable(filePath: string): boolean {
const basename = path.basename(filePath).toLowerCase();
return basename === 'node' || basename === 'node.exe';
return process.platform === 'win32' ? encodedWindowsCommand(scriptPath) : posixSource(scriptPath);
}
export function grokRuntimeReference(
@@ -142,18 +23,8 @@ export function grokRuntimeReference(
scriptName: string,
): GrokRuntimeReference | undefined {
const parsed = parsePosixCommand(command)
?? parseWindowsCommand(command)
?? parseEncodedWindowsCommand(command)
?? parseLegacyWindowsCommand(command);
if (!parsed
|| !path.isAbsolute(parsed[0])
|| !path.isAbsolute(parsed[1])
|| !isNodeExecutable(parsed[0])
|| path.basename(parsed[1]) !== scriptName) return undefined;
const agentDirectory = path.dirname(parsed[1]);
if (!sameGrokPath(path.dirname(agentDirectory), path.join(os.homedir(), '.elydora'))) {
return undefined;
}
const agentId = path.basename(agentDirectory);
if (!agentId || agentId === '.' || agentId === '..') return undefined;
return { agentId, scriptPath: parsed[1] };
if (!parsed || !path.isAbsolute(parsed[0]) || !isNodeExecutable(parsed[0])) return undefined;
return managedScriptReference(parsed[1], scriptName);
}
+6 -2
View File
@@ -1,15 +1,19 @@
import { sameAgentId } from './common.js';
import {
buildGrokCommand,
grokRuntimeReference,
sameGrokAgentId,
type GrokRuntimeReference,
} from './grok-command.js';
import type { GuardScriptOptions } from './guard-template.js';
import type { HookScriptOptions } from './hook-template.js';
import { isObject, parseStrictJsonObject, type JsonObject } from './strict-json.js';
export const AGENT_KEY = 'grok';
export const GUARD_SCRIPT = 'guard.js';
export const AUDIT_SCRIPT = 'hook.js';
export const HOOK_TIMEOUT_SECONDS = 10;
export const GUARD_OPTIONS: GuardScriptOptions = { denyProtocol: 'grok' };
export const AUDIT_OPTIONS: HookScriptOptions = { nativePayload: true };
const MATCHER_REJECTING_EVENTS = new Set([
'SessionStart',
@@ -172,7 +176,7 @@ function removeFromGroups(
const handlers = group.hooks.filter((handler) => {
const reference = managedReference(handler, scriptName);
return !reference
|| (agentId !== undefined && !sameGrokAgentId(reference.agentId, agentId));
|| (agentId !== undefined && !sameAgentId(reference.agentId, agentId));
});
if (handlers.length > 0) result.push({ hooks: handlers });
}
+7 -9
View File
@@ -2,7 +2,9 @@ import path from 'node:path';
import type { InstallConfig } from './base.js';
import {
AGENT_KEY,
AUDIT_OPTIONS,
AUDIT_SCRIPT,
GUARD_OPTIONS,
GUARD_SCRIPT,
type RenderedGrokDocument,
} from './grok-contract.js';
@@ -24,14 +26,10 @@ const DISPLAY_NAME = 'Grok Build';
const HOOKS_DIRECTORY_LABEL = 'Grok hooks directory';
const HOOKS_LABEL = 'Grok user hooks';
export type GrokRuntimePaths = ManagedRuntimePaths;
export type PreparedGrokInstallation = PreparedManagedInstallation;
export type { RenameFile };
export async function preflightGrokInstallation(
config: InstallConfig,
hooksPath: string,
): Promise<GrokRuntimePaths> {
): Promise<ManagedRuntimePaths> {
return preflightManagedInstallation({
agentKey: AGENT_KEY,
hookLocations: [{ directoryLabel: HOOKS_DIRECTORY_LABEL, filePath: hooksPath }],
@@ -42,7 +40,7 @@ export async function preflightGrokInstallation(
export async function prepareGrokInstallation(
config: InstallConfig,
rendered: RenderedGrokDocument,
): Promise<PreparedGrokInstallation> {
): Promise<PreparedManagedInstallation> {
const hooksSource = rendered.next ?? rendered.document.raw;
if (hooksSource === undefined) throw new Error('Grok hook configuration is missing');
const prepared = await prepareManagedInstallation({
@@ -56,8 +54,8 @@ export async function prepareGrokInstallation(
source: hooksSource,
}],
config,
guardOptions: { denyProtocol: 'grok' },
auditOptions: { nativePayload: true },
guardOptions: GUARD_OPTIONS,
auditOptions: AUDIT_OPTIONS,
}, GUARD_SCRIPT, AUDIT_SCRIPT);
const hooksDirectory = path.dirname(rendered.document.filePath);
return {
@@ -73,7 +71,7 @@ export async function prepareGrokInstallation(
}
export async function commitGrokInstallation(
prepared: PreparedGrokInstallation,
prepared: PreparedManagedInstallation,
renameFile?: RenameFile,
): Promise<void> {
await commitManagedInstallation(prepared, renameFile);
+8 -74
View File
@@ -2,14 +2,15 @@ import os from 'node:os';
import path from 'node:path';
import {
AGENT_KEY,
AUDIT_OPTIONS,
GUARD_OPTIONS,
createGrokDocument,
parseGrokDocument,
type GrokDocument,
type GrokRuntimeContract,
} from './grok-contract.js';
import { sameGrokAgentId, sameGrokPath } from './grok-command.js';
import { inspectPhysicalDirectory, readPhysicalFile } from './managed-files.js';
import { parseStrictJsonObject, type JsonObject } from './strict-json.js';
import { managedRuntimeFilesExist } from './managed-runtime-status.js';
const CONFIG_FILE = 'elydora-audit.json';
@@ -36,82 +37,15 @@ export async function readGrokDocument(): Promise<GrokDocument> {
: createGrokDocument(filePath);
}
function requireNonEmptyString(value: unknown, field: string, configPath: string): string {
if (typeof value !== 'string' || value.trim().length === 0) {
throw new Error(`Elydora runtime config ${field} is invalid: ${configPath}`);
}
return value;
}
function validateRuntimeConfig(
config: JsonObject,
contract: GrokRuntimeContract,
configPath: string,
): void {
const supported = new Set(['org_id', 'agent_id', 'kid', 'base_url', 'token', 'agent_name']);
const extra = Object.keys(config).find((key) => !supported.has(key));
if (extra) throw new Error(`Elydora runtime config has unsupported field "${extra}": ${configPath}`);
requireNonEmptyString(config.org_id, 'org_id', configPath);
requireNonEmptyString(config.kid, 'kid', configPath);
const agentId = requireNonEmptyString(config.agent_id, 'agent_id', configPath);
if (!sameGrokAgentId(agentId, contract.agentId) || config.agent_name !== AGENT_KEY) {
throw new Error(`Elydora runtime identity does not match Grok hooks: ${configPath}`);
}
if (config.token !== undefined) requireNonEmptyString(config.token, 'token', configPath);
const rawBaseUrl = requireNonEmptyString(config.base_url, 'base_url', configPath);
let baseUrl: URL;
try {
baseUrl = new URL(rawBaseUrl);
} catch (error) {
throw new Error(`Elydora runtime config base_url is invalid: ${configPath}`, {
cause: error instanceof Error ? error : new Error(String(error)),
});
}
if (!['http:', 'https:'].includes(baseUrl.protocol)
|| !baseUrl.hostname
|| baseUrl.username
|| baseUrl.password
|| baseUrl.search
|| baseUrl.hash) {
throw new Error(`Elydora runtime config base_url is invalid: ${configPath}`);
}
}
function validatePrivateKey(contents: string, keyPath: string): void {
const bytes = Buffer.from(contents, 'base64url');
if (bytes.length !== 32 || bytes.toString('base64url') !== contents) {
throw new Error(`Elydora private key is invalid: ${keyPath}`);
}
}
async function runtimeContractExists(contract: GrokRuntimeContract): Promise<boolean> {
const runtimeRoot = path.join(os.homedir(), '.elydora');
const agentDirectory = path.dirname(contract.guardPath);
if (!sameGrokPath(path.dirname(agentDirectory), runtimeRoot)
|| !sameGrokPath(contract.auditPath, path.join(agentDirectory, 'hook.js'))) return false;
if (!await inspectPhysicalDirectory(runtimeRoot, 'Elydora runtime directory')) return false;
if (!await inspectPhysicalDirectory(agentDirectory, 'Elydora agent runtime directory')) return false;
const configPath = path.join(agentDirectory, 'config.json');
const keyPath = path.join(agentDirectory, 'private.key');
const [config, key, guard, audit] = await Promise.all([
readPhysicalFile(configPath, 'Elydora runtime config'),
readPhysicalFile(keyPath, 'Elydora private key'),
readPhysicalFile(contract.guardPath, 'Elydora guard runtime'),
readPhysicalFile(contract.auditPath, 'Elydora audit runtime'),
]);
if (!config || !key || !guard || !audit) return false;
const parsed = parseStrictJsonObject(config.contents, `Elydora runtime config at ${configPath}`);
validateRuntimeConfig(parsed, contract, configPath);
validatePrivateKey(key.contents, keyPath);
return guard.contents.length > 0 && audit.contents.length > 0;
}
export async function grokRuntimeFilesExist(
contracts: readonly GrokRuntimeContract[],
): Promise<boolean> {
for (const contract of contracts) {
if (await runtimeContractExists(contract)) return true;
const exists = await managedRuntimeFilesExist(contract, AGENT_KEY, {
guardOptions: GUARD_OPTIONS,
auditOptions: AUDIT_OPTIONS,
});
if (exists) return true;
}
return false;
}
+23
View File
@@ -0,0 +1,23 @@
import { applyEdits, modify, type FormattingOptions, type JSONPath } from 'jsonc-parser';
function formatting(raw: string): FormattingOptions {
const indentation = /\r?\n([ \t]+)\S/.exec(raw)?.[1];
const insertSpaces = !indentation?.includes('\t');
return {
eol: raw.includes('\r\n') ? '\r\n' : '\n',
insertSpaces,
tabSize: insertSpaces ? Math.max(1, indentation?.length ?? 2) : 1,
};
}
export function changeJsonc(
raw: string,
jsonPath: JSONPath,
value: unknown,
isArrayInsertion = false,
): string {
return applyEdits(raw, modify(raw, jsonPath, value, {
formattingOptions: formatting(raw),
isArrayInsertion,
}));
}
+14 -143
View File
@@ -1,140 +1,21 @@
import os from 'node:os';
import path from 'node:path';
import { managedScriptReference, type ManagedScriptReference } from './common.js';
import {
encodedWindowsCommand,
isNodeExecutable,
parseEncodedWindowsCommand,
parseLegacyWindowsCommand,
parsePosixCommand,
posixSource,
} from './shell-command.js';
interface ParsedArgument {
readonly value: string;
readonly next: number;
}
export interface KimiRuntimeReference {
readonly agentId: string;
readonly scriptPath: string;
}
export function sameKimiPath(left: string, right: string): boolean {
const normalizedLeft = path.resolve(left);
const normalizedRight = path.resolve(right);
return process.platform === 'win32'
? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
: normalizedLeft === normalizedRight;
}
export function sameKimiAgentId(left: string, right: string): boolean {
return process.platform === 'win32'
? left.toLowerCase() === right.toLowerCase()
: left === right;
}
function quotePosix(value: string): string {
return `'${value.replaceAll("'", `'"'"'`)}'`;
}
function quotePowerShell(value: string): string {
return `'${value.replaceAll("'", "''")}'`;
}
function windowsPowerShellPath(): string {
const configuredRoot = process.platform === 'win32' ? process.env.SystemRoot : undefined;
const systemRoot = configuredRoot
&& path.win32.isAbsolute(configuredRoot)
&& !/["%\r\n]/.test(configuredRoot)
? configuredRoot
: 'C:\\Windows';
return path.win32.join(
systemRoot,
'System32',
'WindowsPowerShell',
'v1.0',
'powershell.exe',
);
}
function windowsCommand(scriptPath: string): string {
const source = `& ${quotePowerShell(process.execPath)} ${quotePowerShell(scriptPath)}; exit $LASTEXITCODE`;
const encoded = Buffer.from(source, 'utf16le').toString('base64');
return `"${windowsPowerShellPath()}" -NoLogo -NoProfile -NonInteractive -EncodedCommand ${encoded}`;
}
export type KimiRuntimeReference = ManagedScriptReference;
export function buildKimiCommand(scriptPath: string): string {
if (!path.isAbsolute(process.execPath) || !path.isAbsolute(scriptPath)) {
throw new Error('Kimi hook commands require absolute executable and script paths');
}
return process.platform === 'win32'
? windowsCommand(scriptPath)
: `${quotePosix(process.execPath)} ${quotePosix(scriptPath)}`;
}
function readPosixArgument(command: string, start: number): ParsedArgument | undefined {
if (command[start] !== "'") return undefined;
const apostrophe = `'"'"'`;
let value = '';
for (let index = start + 1; index < command.length;) {
if (command.startsWith(apostrophe, index)) {
value += "'";
index += apostrophe.length;
continue;
}
if (command[index] === "'") return { value, next: index + 1 };
value += command[index];
index += 1;
}
return undefined;
}
function parsePosixCommand(command: string): readonly [string, string] | undefined {
const executable = readPosixArgument(command, 0);
if (!executable || command[executable.next] !== ' ') return undefined;
const script = readPosixArgument(command, executable.next + 1);
if (!script || script.next !== command.length) return undefined;
return [executable.value, script.value];
}
function readPowerShellArgument(command: string, start: number): ParsedArgument | undefined {
if (command[start] !== "'") return undefined;
let value = '';
for (let index = start + 1; index < command.length; index += 1) {
if (command[index] !== "'") {
value += command[index];
continue;
}
if (command[index + 1] === "'") {
value += "'";
index += 1;
continue;
}
return { value, next: index + 1 };
}
return undefined;
}
function parsePowerShellSource(source: string): readonly [string, string] | undefined {
if (!source.startsWith('& ')) return undefined;
const executable = readPowerShellArgument(source, 2);
if (!executable || source[executable.next] !== ' ') return undefined;
const script = readPowerShellArgument(source, executable.next + 1);
if (!script || source.slice(script.next) !== '; exit $LASTEXITCODE') return undefined;
return [executable.value, script.value];
}
function parseWindowsCommand(command: string): readonly [string, string] | undefined {
const match = /^"([^"\r\n]+)" -NoLogo -NoProfile -NonInteractive -EncodedCommand ([A-Za-z0-9+/]+={0,2})$/.exec(command);
if (!match
|| !path.win32.isAbsolute(match[1])
|| path.win32.basename(match[1]).toLowerCase() !== 'powershell.exe') return undefined;
const encoded = match[2];
const buffer = Buffer.from(encoded, 'base64');
if (buffer.length % 2 !== 0 || buffer.toString('base64') !== encoded) return undefined;
return parsePowerShellSource(buffer.toString('utf16le'));
}
function parseLegacyWindowsCommand(command: string): readonly [string, string] | undefined {
const match = /^"([^"\r\n]+)" "([^"\r\n]+)"$/.exec(command);
return match ? [match[1], match[2]] : undefined;
}
function isNodeExecutable(filePath: string): boolean {
const basename = path.basename(filePath).toLowerCase();
return basename === 'node' || basename === 'node.exe';
return process.platform === 'win32' ? encodedWindowsCommand(scriptPath) : posixSource(scriptPath);
}
export function kimiRuntimeReference(
@@ -142,18 +23,8 @@ export function kimiRuntimeReference(
scriptName: string,
): KimiRuntimeReference | undefined {
const parsed = parsePosixCommand(command)
?? parseWindowsCommand(command)
?? parseEncodedWindowsCommand(command)
?? parseLegacyWindowsCommand(command);
if (!parsed
|| !path.isAbsolute(parsed[0])
|| !path.isAbsolute(parsed[1])
|| !isNodeExecutable(parsed[0])
|| path.basename(parsed[1]) !== scriptName) return undefined;
const agentDirectory = path.dirname(parsed[1]);
if (!sameKimiPath(path.dirname(agentDirectory), path.join(os.homedir(), '.elydora'))) {
return undefined;
}
const agentId = path.basename(agentDirectory);
if (!agentId || agentId === '.' || agentId === '..') return undefined;
return { agentId, scriptPath: parsed[1] };
if (!parsed || !path.isAbsolute(parsed[0]) || !isNodeExecutable(parsed[0])) return undefined;
return managedScriptReference(parsed[1], scriptName);
}
+5 -12
View File
@@ -1,9 +1,10 @@
import { sameAgentId } from './common.js';
import {
buildKimiCommand,
kimiRuntimeReference,
sameKimiAgentId,
type KimiRuntimeReference,
} from './kimi-command.js';
import { isObject, type JsonObject } from './strict-json.js';
export const AGENT_KEY = 'kimi';
export const GUARD_SCRIPT = 'guard.js';
@@ -34,7 +35,7 @@ export const STABLE_EVENTS = new Set<string>([
]);
export const LEGACY_EVENTS = new Set<string>(SHARED_EVENTS);
export type TomlObject = Record<string, unknown>;
export type TomlObject = JsonObject;
export type ManagedKimiEvent = 'PreToolUse' | 'PostToolUse' | 'PostToolUseFailure';
export interface KimiHook extends TomlObject {
@@ -75,14 +76,6 @@ export interface KimiRuntimeContract {
readonly configPath: string;
}
function isObject(value: unknown): value is TomlObject {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function hasOwn(value: TomlObject, key: string): boolean {
return Object.prototype.hasOwnProperty.call(value, key);
}
function exactManagedKeys(hook: KimiHook): boolean {
return Object.keys(hook).sort().join('|') === 'command|event|timeout';
}
@@ -194,7 +187,7 @@ async function renderHookSource(
if (document.usesHookTables) return patchToml(document.raw ?? '', nextRoot);
let base = document.raw ?? '';
if (hasOwn(document.root, 'hooks')) {
if (Object.hasOwn(document.root, 'hooks')) {
const rootWithoutHooks = { ...document.root };
delete rootWithoutHooks.hooks;
base = patchToml(base, rootWithoutHooks);
@@ -248,7 +241,7 @@ export function removeManagedKimiHooks(
if (!expected) return true;
const managedId = managedAgentId(hook, expected[0], expected[1]);
if (!managedId) return true;
return agentId !== undefined && !sameKimiAgentId(managedId, agentId);
return agentId !== undefined && !sameAgentId(managedId, agentId);
});
}
+3 -7
View File
@@ -23,10 +23,6 @@ import {
const DISPLAY_NAME = 'Kimi';
export type KimiRuntimePaths = ManagedRuntimePaths;
export type PreparedKimiInstallation = PreparedManagedInstallation;
export type { RenameFile };
function hookLocations(documents: readonly KimiConfigDocument[]) {
return documents.map((document) => ({
directoryLabel: document.contract.directoryLabel,
@@ -37,7 +33,7 @@ function hookLocations(documents: readonly KimiConfigDocument[]) {
export async function preflightKimiInstallation(
config: InstallConfig,
documents: readonly KimiConfigDocument[],
): Promise<KimiRuntimePaths> {
): Promise<ManagedRuntimePaths> {
return preflightManagedInstallation({
agentKey: AGENT_KEY,
hookLocations: hookLocations(documents),
@@ -56,7 +52,7 @@ function renderedSource(rendered: RenderedKimiDocument): string {
export async function prepareKimiInstallation(
config: InstallConfig,
rendered: readonly RenderedKimiDocument[],
): Promise<PreparedKimiInstallation> {
): Promise<PreparedManagedInstallation> {
return prepareManagedInstallation({
agentKey: AGENT_KEY,
displayName: DISPLAY_NAME,
@@ -73,7 +69,7 @@ export async function prepareKimiInstallation(
}
export async function commitKimiInstallation(
prepared: PreparedKimiInstallation,
prepared: PreparedManagedInstallation,
renameFile?: RenameFile,
): Promise<void> {
await commitManagedInstallation(prepared, renameFile);
+4 -88
View File
@@ -1,6 +1,7 @@
import fsp from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { asError, errorMessage, hasCode, samePath } from './common.js';
import {
AGENT_KEY,
LEGACY_EVENTS,
@@ -10,23 +11,9 @@ import {
type KimiConfigDocument,
type KimiContract,
type KimiRuntimeContract,
type TomlObject,
} from './kimi-contract.js';
import { sameKimiAgentId, sameKimiPath } from './kimi-command.js';
import { inspectPhysicalDirectory, readPhysicalFile } from './managed-files.js';
import { parseStrictJsonObject } from './strict-json.js';
function asError(error: unknown): Error {
return error instanceof Error ? error : new Error(String(error));
}
function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
function hasCode(error: unknown, code: string): boolean {
return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
}
import { managedRuntimeFilesExist } from './managed-runtime-status.js';
async function pathEntryExists(filePath: string, label: string): Promise<boolean> {
try {
@@ -72,7 +59,7 @@ export async function resolveKimiContracts(): Promise<KimiContract[]> {
const legacyHome = path.join(home, '.kimi');
const stable = stableContract(path.join(stableHome, 'config.toml'));
const legacy = legacyContract(path.join(legacyHome, 'config.toml'));
if (sameKimiPath(stable.configPath, legacy.configPath)) return [stable];
if (samePath(stable.configPath, legacy.configPath)) return [stable];
const stableDetected = explicitHome !== undefined
|| await pathEntryExists(stableHome, 'Kimi Code home');
@@ -96,82 +83,11 @@ export async function readKimiDocuments(): Promise<KimiConfigDocument[]> {
return documents;
}
function requireNonEmptyString(value: unknown, field: string, configPath: string): string {
if (typeof value !== 'string' || value.trim().length === 0) {
throw new Error(`Elydora runtime config ${field} is invalid: ${configPath}`);
}
return value;
}
function validateRuntimeConfig(
config: TomlObject,
contract: KimiRuntimeContract,
configPath: string,
): void {
const supported = new Set(['org_id', 'agent_id', 'kid', 'base_url', 'token', 'agent_name']);
const extra = Object.keys(config).find((key) => !supported.has(key));
if (extra) throw new Error(`Elydora runtime config has unsupported field "${extra}": ${configPath}`);
requireNonEmptyString(config.org_id, 'org_id', configPath);
requireNonEmptyString(config.kid, 'kid', configPath);
const agentId = requireNonEmptyString(config.agent_id, 'agent_id', configPath);
if (!sameKimiAgentId(agentId, contract.agentId) || config.agent_name !== AGENT_KEY) {
throw new Error(`Elydora runtime identity does not match Kimi hooks: ${configPath}`);
}
if (config.token !== undefined) requireNonEmptyString(config.token, 'token', configPath);
const rawBaseUrl = requireNonEmptyString(config.base_url, 'base_url', configPath);
let baseUrl: URL;
try {
baseUrl = new URL(rawBaseUrl);
} catch (error) {
throw new Error(`Elydora runtime config base_url is invalid: ${configPath}`, {
cause: asError(error),
});
}
if (!['http:', 'https:'].includes(baseUrl.protocol)
|| !baseUrl.hostname
|| baseUrl.username
|| baseUrl.password
|| baseUrl.search
|| baseUrl.hash) {
throw new Error(`Elydora runtime config base_url is invalid: ${configPath}`);
}
}
function validatePrivateKey(contents: string, keyPath: string): void {
const bytes = Buffer.from(contents, 'base64url');
if (bytes.length !== 32 || bytes.toString('base64url') !== contents) {
throw new Error(`Elydora private key is invalid: ${keyPath}`);
}
}
async function runtimeContractExists(contract: KimiRuntimeContract): Promise<boolean> {
const runtimeRoot = path.join(os.homedir(), '.elydora');
const agentDirectory = path.dirname(contract.guardPath);
if (!sameKimiPath(path.dirname(agentDirectory), runtimeRoot)
|| !sameKimiPath(contract.auditPath, path.join(agentDirectory, 'hook.js'))) return false;
if (!await inspectPhysicalDirectory(runtimeRoot, 'Elydora runtime directory')) return false;
if (!await inspectPhysicalDirectory(agentDirectory, 'Elydora agent runtime directory')) return false;
const configPath = path.join(agentDirectory, 'config.json');
const keyPath = path.join(agentDirectory, 'private.key');
const [config, key, guard, audit] = await Promise.all([
readPhysicalFile(configPath, 'Elydora runtime config'),
readPhysicalFile(keyPath, 'Elydora private key'),
readPhysicalFile(contract.guardPath, 'Elydora guard runtime'),
readPhysicalFile(contract.auditPath, 'Elydora audit runtime'),
]);
if (!config || !key || !guard || !audit) return false;
const parsed = parseStrictJsonObject(config.contents, `Elydora runtime config at ${configPath}`);
validateRuntimeConfig(parsed, contract, configPath);
validatePrivateKey(key.contents, keyPath);
return guard.contents.length > 0 && audit.contents.length > 0;
}
export async function kimiRuntimeFilesExist(
contracts: readonly KimiRuntimeContract[],
): Promise<boolean> {
for (const contract of contracts) {
if (await runtimeContractExists(contract)) return true;
if (await managedRuntimeFilesExist(contract, AGENT_KEY)) return true;
}
return false;
}
+14 -112
View File
@@ -1,10 +1,12 @@
import os from 'node:os';
import path from 'node:path';
interface ParsedArgument {
readonly value: string;
readonly next: number;
}
import { managedScriptReference } from './common.js';
import {
isNodeExecutable,
parsePosixCommand,
parsePowerShellSource,
posixSource,
powerShellSource,
} from './shell-command.js';
export interface LettaRuntimeReference {
readonly agentId: string;
@@ -12,88 +14,11 @@ export interface LettaRuntimeReference {
readonly scriptPath: string;
}
export function sameLettaPath(left: string, right: string): boolean {
const normalizedLeft = path.resolve(left);
const normalizedRight = path.resolve(right);
return process.platform === 'win32'
? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
: normalizedLeft === normalizedRight;
}
export function sameLettaAgentId(left: string, right: string): boolean {
return process.platform === 'win32'
? left.toLowerCase() === right.toLowerCase()
: left === right;
}
function quotePosix(value: string): string {
return `'${value.replaceAll("'", `'"'"'`)}'`;
}
function quotePowerShell(value: string): string {
return `'${value.replaceAll("'", "''")}'`;
}
export function buildLettaCommand(scriptPath: string): string {
if (!path.isAbsolute(process.execPath) || !path.isAbsolute(scriptPath)) {
throw new Error('Letta Code hook commands require absolute executable and script paths');
}
if (process.platform === 'win32') {
return `& ${quotePowerShell(process.execPath)} ${quotePowerShell(scriptPath)}; exit $LASTEXITCODE`;
}
return `${quotePosix(process.execPath)} ${quotePosix(scriptPath)}`;
}
function readPosixArgument(command: string, start: number): ParsedArgument | undefined {
if (command[start] !== "'") return undefined;
const apostrophe = `'"'"'`;
let value = '';
for (let index = start + 1; index < command.length;) {
if (command.startsWith(apostrophe, index)) {
value += "'";
index += apostrophe.length;
continue;
}
if (command[index] === "'") return { value, next: index + 1 };
value += command[index];
index += 1;
}
return undefined;
}
function parsePosixCommand(command: string): readonly [string, string] | undefined {
const executable = readPosixArgument(command, 0);
if (!executable || command[executable.next] !== ' ') return undefined;
const script = readPosixArgument(command, executable.next + 1);
if (!script || script.next !== command.length) return undefined;
return [executable.value, script.value];
}
function readPowerShellArgument(command: string, start: number): ParsedArgument | undefined {
if (command[start] !== "'") return undefined;
let value = '';
for (let index = start + 1; index < command.length; index += 1) {
if (command[index] !== "'") {
value += command[index];
continue;
}
if (command[index + 1] === "'") {
value += "'";
index += 1;
continue;
}
return { value, next: index + 1 };
}
return undefined;
}
function parsePowerShellCommand(command: string): readonly [string, string] | undefined {
if (!command.startsWith('& ')) return undefined;
const executable = readPowerShellArgument(command, 2);
if (!executable || command[executable.next] !== ' ') return undefined;
const script = readPowerShellArgument(command, executable.next + 1);
if (!script || command.slice(script.next) !== '; exit $LASTEXITCODE') return undefined;
return [executable.value, script.value];
return process.platform === 'win32' ? powerShellSource(scriptPath) : posixSource(scriptPath);
}
function legacyScriptPath(command: string): string | undefined {
@@ -102,39 +27,16 @@ function legacyScriptPath(command: string): string | undefined {
return scriptPath.includes('"') ? undefined : scriptPath;
}
function isNodeExecutable(filePath: string): boolean {
const basename = path.basename(filePath).toLowerCase();
return basename === 'node' || basename === 'node.exe';
}
function runtimeReference(
scriptPath: string,
scriptName: string,
executablePath?: string,
): LettaRuntimeReference | undefined {
if (!path.isAbsolute(scriptPath) || path.basename(scriptPath) !== scriptName) return undefined;
const agentDirectory = path.dirname(scriptPath);
if (!sameLettaPath(path.dirname(agentDirectory), path.join(os.homedir(), '.elydora'))) {
return undefined;
}
const agentId = path.basename(agentDirectory);
if (!agentId || agentId === '.' || agentId === '..') return undefined;
return { agentId, executablePath, scriptPath };
}
export function lettaRuntimeReference(
command: string,
scriptName: string,
): LettaRuntimeReference | undefined {
const parsed = process.platform === 'win32'
? parsePowerShellCommand(command)
? parsePowerShellSource(command)
: parsePosixCommand(command);
if (parsed
&& path.isAbsolute(parsed[0])
&& isNodeExecutable(parsed[0])) {
return runtimeReference(parsed[1], scriptName, parsed[0]);
}
return undefined;
if (!parsed || !path.isAbsolute(parsed[0]) || !isNodeExecutable(parsed[0])) return undefined;
const reference = managedScriptReference(parsed[1], scriptName);
return reference ? { ...reference, executablePath: parsed[0] } : undefined;
}
export function lettaLegacyRuntimeReference(
@@ -142,5 +44,5 @@ export function lettaLegacyRuntimeReference(
scriptName: string,
): LettaRuntimeReference | undefined {
const legacy = legacyScriptPath(command);
return legacy ? runtimeReference(legacy, scriptName) : undefined;
return legacy ? managedScriptReference(legacy, scriptName) : undefined;
}
+8 -34
View File
@@ -1,9 +1,5 @@
import {
applyEdits,
modify,
type FormattingOptions,
type JSONPath,
} from 'jsonc-parser';
import type { JSONPath } from 'jsonc-parser';
import { changeJsonc } from './jsonc-edit.js';
import type { FileSnapshot } from './managed-files.js';
import {
MANAGED_EVENTS,
@@ -73,28 +69,6 @@ export function createLettaDocument(
});
}
function formatting(raw: string): FormattingOptions {
const indentation = /\r?\n([ \t]+)\S/.exec(raw)?.[1];
const insertSpaces = !indentation?.includes('\t');
return {
eol: raw.includes('\r\n') ? '\r\n' : '\n',
insertSpaces,
tabSize: insertSpaces ? Math.max(1, indentation?.length ?? 2) : 1,
};
}
function change(
raw: string,
jsonPath: JSONPath,
value: unknown,
isArrayInsertion = false,
): string {
return applyEdits(raw, modify(raw, jsonPath, value, {
formattingOptions: formatting(raw),
isArrayInsertion,
}));
}
function currentDocument(document: LettaDocument, raw: string): LettaDocument {
return parseLettaDocument({
kind: document.kind,
@@ -124,25 +98,25 @@ function removeManagedEntries(
for (const removal of eventRemovals) {
const groupPath: JSONPath = ['hooks', event, removal.groupIndex];
if (removal.removeGroup) {
raw = change(raw, groupPath, undefined);
raw = changeJsonc(raw, groupPath, undefined);
continue;
}
for (const handlerIndex of [...removal.handlerIndexes].sort(
(left, right) => right - left,
)) {
raw = change(raw, [...groupPath, 'hooks', handlerIndex], undefined);
raw = changeJsonc(raw, [...groupPath, 'hooks', handlerIndex], undefined);
}
}
if (eventRemovals.length > 0) {
const current = currentDocument(document, raw);
if (groupsForEvent(current.hooks, event).length === 0) {
raw = change(raw, ['hooks', event], undefined);
raw = changeJsonc(raw, ['hooks', event], undefined);
}
}
}
const current = currentDocument(document, raw);
if (current.hasHooksContainer && Object.keys(current.hooks).length === 0) {
raw = change(raw, ['hooks'], undefined);
raw = changeJsonc(raw, ['hooks'], undefined);
}
return raw;
}
@@ -156,9 +130,9 @@ function appendGroup(
const current = currentDocument(document, raw);
const groups = groupsForEvent(current.hooks, event);
if (groups.length > 0) {
return change(raw, ['hooks', event, groups.length], group, true);
return changeJsonc(raw, ['hooks', event, groups.length], group, true);
}
return change(raw, ['hooks', event], [group]);
return changeJsonc(raw, ['hooks', event], [group]);
}
export function renderLettaDocument(
+4 -5
View File
@@ -1,9 +1,8 @@
import { sameAgentId, samePath } from './common.js';
import {
buildLettaCommand,
lettaLegacyRuntimeReference,
lettaRuntimeReference,
sameLettaAgentId,
sameLettaPath,
type LettaRuntimeReference,
} from './letta-command.js';
import { isObject, type JsonObject } from './strict-json.js';
@@ -220,7 +219,7 @@ export function managedLettaRemovals(
eventGroups(hooks, event).forEach((group, groupIndex) => {
const handlerIndexes = group.hooks.flatMap((handler, handlerIndex) => {
const reference = managedReference(handler, scriptName);
return reference && (agentId === undefined || sameLettaAgentId(reference.agentId, agentId))
return reference && (agentId === undefined || sameAgentId(reference.agentId, agentId))
? [handlerIndex]
: [];
});
@@ -248,7 +247,7 @@ function referencesForEvent(
const reference = exactCurrentReference(handler, scriptName);
if (!reference
|| !reference.executablePath
|| !sameLettaPath(reference.executablePath, process.execPath)) continue;
|| !samePath(reference.executablePath, process.execPath)) continue;
const key = process.platform === 'win32'
? reference.agentId.toLowerCase()
: reference.agentId;
@@ -270,7 +269,7 @@ export function lettaRuntimeContracts(hooks: LettaHooks): LettaRuntimeContract[]
const post = posts.get(key);
const failure = failures.get(key);
if (guard.length !== 1 || post?.length !== 1 || failure?.length !== 1) continue;
if (!sameLettaPath(post[0].scriptPath, failure[0].scriptPath)) continue;
if (!samePath(post[0].scriptPath, failure[0].scriptPath)) continue;
contracts.push({
agentId: guard[0].agentId,
guardPath: guard[0].scriptPath,
+5 -10
View File
@@ -6,7 +6,7 @@ import {
GUARD_SCRIPT,
LETTA_AUDIT_OPTIONS,
} from './letta-contract.js';
import { sameLettaPath } from './letta-command.js';
import { MAX_SOURCE_BYTES, samePath } from './common.js';
import {
lettaDocumentLabel,
type RenderedLettaDocument,
@@ -31,16 +31,11 @@ import {
const DISPLAY_NAME = 'Letta Code';
const SETTINGS_DIRECTORY_LABEL = 'Letta Code global configuration directory';
const MAX_SOURCE_BYTES = 2 * 1024 * 1024;
export type LettaRuntimePaths = ManagedRuntimePaths;
export type PreparedLettaInstallation = PreparedManagedInstallation;
export type { RenameFile };
export async function preflightLettaInstallation(
config: InstallConfig,
sources: LettaSources,
): Promise<LettaRuntimePaths> {
): Promise<ManagedRuntimePaths> {
requireLettaHooksEnabled(sources);
return preflightManagedInstallation({
agentKey: AGENT_KEY,
@@ -54,7 +49,7 @@ export async function preflightLettaInstallation(
function readOnlyPreconditions(sources: LettaSources, changedPath?: string) {
return sources.preconditions.filter((condition) => (
changedPath === undefined || !sameLettaPath(condition.filePath, changedPath)
changedPath === undefined || !samePath(condition.filePath, changedPath)
));
}
@@ -62,7 +57,7 @@ export async function prepareLettaInstallation(
config: InstallConfig,
sources: LettaSources,
rendered: RenderedLettaDocument,
): Promise<PreparedLettaInstallation> {
): Promise<PreparedManagedInstallation> {
await preflightLettaInstallation(config, sources);
const settingsSource = rendered.next ?? rendered.document.raw;
const prepared = await prepareManagedInstallation({
@@ -89,7 +84,7 @@ export async function prepareLettaInstallation(
}
export async function commitLettaInstallation(
prepared: PreparedLettaInstallation,
prepared: PreparedManagedInstallation,
renameFile?: RenameFile,
): Promise<void> {
await commitManagedInstallation(prepared, renameFile);
+4 -14
View File
@@ -9,14 +9,13 @@ import {
type LettaDocument,
type LettaDocumentKind,
} from './letta-config.js';
import { hasCode, MAX_SOURCE_BYTES, pathKey, samePath } from './common.js';
import {
inspectPhysicalDirectory,
readPhysicalFile,
type FileSnapshot,
} from './managed-files.js';
const MAX_SOURCE_BYTES = 2 * 1024 * 1024;
export interface LettaSourcePrecondition {
readonly filePath: string;
readonly label: string;
@@ -39,15 +38,6 @@ export interface LettaSources {
readonly preconditions: readonly LettaSourcePrecondition[];
}
function comparisonPath(filePath: string): string {
const resolved = path.resolve(filePath);
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
}
function hasCode(error: unknown, code: string): boolean {
return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
}
async function canonicalPath(filePath: string): Promise<string> {
try {
return await fsp.realpath(filePath);
@@ -93,7 +83,7 @@ function deduplicatePreconditions(
): LettaSourcePrecondition[] {
const result = new Map<string, LettaSourcePrecondition>();
for (const value of values) {
const key = comparisonPath(value.filePath);
const key = pathKey(value.filePath);
if (!result.has(key)) result.set(key, value);
}
return [...result.values()];
@@ -123,7 +113,7 @@ export async function readLettaSources(): Promise<LettaSources> {
const projectDirectory = path.join(workspace, '.letta');
await inspectPhysicalDirectory(homeDirectory, 'Letta Code home directory');
await inspectPhysicalDirectory(globalDirectory, 'Letta Code global configuration directory');
if (comparisonPath(projectDirectory) !== comparisonPath(globalDirectory)) {
if (!samePath(projectDirectory, globalDirectory)) {
await inspectPhysicalDirectory(projectDirectory, 'Letta Code project configuration directory');
}
const globalPath = path.join(globalDirectory, 'settings.json');
@@ -133,7 +123,7 @@ export async function readLettaSources(): Promise<LettaSources> {
canonicalPath(workspace),
canonicalPath(homeDirectory),
]);
const projectActive = comparisonPath(canonicalWorkspace) !== comparisonPath(canonicalHome);
const projectActive = !samePath(canonicalWorkspace, canonicalHome);
const global = await readDocument('global', globalPath);
const project = projectActive
? await readDocument('project', projectPath)
+13 -106
View File
@@ -1,103 +1,20 @@
import os from 'node:os';
import path from 'node:path';
import { managedScriptReference, type ManagedScriptReference } from './common.js';
import {
isNodeExecutable,
parsePosixCommand,
parsePowerShellSource,
posixSource,
powerShellSource,
} from './shell-command.js';
interface ParsedArgument {
readonly value: string;
readonly next: number;
}
export interface QwenRuntimeReference {
readonly agentId: string;
readonly scriptPath: string;
}
export function sameQwenPath(left: string, right: string): boolean {
const normalizedLeft = path.resolve(left);
const normalizedRight = path.resolve(right);
return process.platform === 'win32'
? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
: normalizedLeft === normalizedRight;
}
export function sameQwenAgentId(left: string, right: string): boolean {
return process.platform === 'win32'
? left.toLowerCase() === right.toLowerCase()
: left === right;
}
function quotePosix(value: string): string {
return `'${value.replaceAll("'", `'"'"'`)}'`;
}
function quotePowerShell(value: string): string {
return `'${value.replaceAll("'", "''")}'`;
}
export type QwenRuntimeReference = ManagedScriptReference;
export function buildQwenCommand(scriptPath: string): string {
if (!path.isAbsolute(process.execPath) || !path.isAbsolute(scriptPath)) {
throw new Error('Qwen Code hook commands require absolute executable and script paths');
}
if (process.platform === 'win32') {
return `& ${quotePowerShell(process.execPath)} ${quotePowerShell(scriptPath)}; exit $LASTEXITCODE`;
}
return `${quotePosix(process.execPath)} ${quotePosix(scriptPath)}`;
}
function readPosixArgument(command: string, start: number): ParsedArgument | undefined {
if (command[start] !== "'") return undefined;
const apostrophe = `'"'"'`;
let value = '';
for (let index = start + 1; index < command.length;) {
if (command.startsWith(apostrophe, index)) {
value += "'";
index += apostrophe.length;
continue;
}
if (command[index] === "'") return { value, next: index + 1 };
value += command[index];
index += 1;
}
return undefined;
}
function parsePosixCommand(command: string): readonly [string, string] | undefined {
const executable = readPosixArgument(command, 0);
if (!executable || command[executable.next] !== ' ') return undefined;
const script = readPosixArgument(command, executable.next + 1);
if (!script || script.next !== command.length) return undefined;
return [executable.value, script.value];
}
function readPowerShellArgument(command: string, start: number): ParsedArgument | undefined {
if (command[start] !== "'") return undefined;
let value = '';
for (let index = start + 1; index < command.length; index += 1) {
if (command[index] !== "'") {
value += command[index];
continue;
}
if (command[index + 1] === "'") {
value += "'";
index += 1;
continue;
}
return { value, next: index + 1 };
}
return undefined;
}
function parsePowerShellCommand(command: string): readonly [string, string] | undefined {
if (!command.startsWith('& ')) return undefined;
const executable = readPowerShellArgument(command, 2);
if (!executable || command[executable.next] !== ' ') return undefined;
const script = readPowerShellArgument(command, executable.next + 1);
if (!script || command.slice(script.next) !== '; exit $LASTEXITCODE') return undefined;
return [executable.value, script.value];
}
function isNodeExecutable(filePath: string): boolean {
const basename = path.basename(filePath).toLowerCase();
return basename === 'node' || basename === 'node.exe';
return process.platform === 'win32' ? powerShellSource(scriptPath) : posixSource(scriptPath);
}
export function qwenRuntimeReference(
@@ -105,18 +22,8 @@ export function qwenRuntimeReference(
scriptName: string,
): QwenRuntimeReference | undefined {
const parsed = process.platform === 'win32'
? parsePowerShellCommand(command)
? parsePowerShellSource(command)
: parsePosixCommand(command);
if (!parsed
|| !path.isAbsolute(parsed[0])
|| !isNodeExecutable(parsed[0])
|| !path.isAbsolute(parsed[1])
|| path.basename(parsed[1]) !== scriptName) return undefined;
const agentDirectory = path.dirname(parsed[1]);
if (!sameQwenPath(path.dirname(agentDirectory), path.join(os.homedir(), '.elydora'))) {
return undefined;
}
const agentId = path.basename(agentDirectory);
if (!agentId || agentId === '.' || agentId === '..') return undefined;
return { agentId, scriptPath: parsed[1] };
if (!parsed || !path.isAbsolute(parsed[0]) || !isNodeExecutable(parsed[0])) return undefined;
return managedScriptReference(parsed[1], scriptName);
}
+10 -86
View File
@@ -1,25 +1,15 @@
import {
applyEdits,
modify,
parse,
parseTree,
printParseErrorCode,
type FormattingOptions,
type JSONPath,
type Node,
type ParseError,
} from 'jsonc-parser';
import type { JSONPath } from 'jsonc-parser';
import type { FileSnapshot } from './managed-files.js';
import { changeJsonc } from './jsonc-edit.js';
import {
MANAGED_EVENTS,
managedQwenRemovals,
readQwenHooks,
type JsonObject,
type ManagedQwenEvent,
type QwenGroup,
type QwenHooks,
isObject,
} from './qwen-contract.js';
import { isObject, parseCommentedJsonObject, type JsonObject } from './strict-json.js';
export const QWEN_OWNED_FILE_MARKER = '// Managed by Elydora';
@@ -53,50 +43,6 @@ interface DocumentOptions {
readonly snapshot?: FileSnapshot;
}
function rejectDuplicateKeys(
node: Node | undefined,
label: string,
location: string[] = [],
): void {
if (!node) return;
if (node.type === 'object') {
const keys = new Set<string>();
for (const property of node.children ?? []) {
const key = String(property.children?.[0]?.value);
const childLocation = [...location, key];
if (keys.has(key)) {
throw new Error(`${label} contains duplicate field "${childLocation.join('.')}"`);
}
keys.add(key);
rejectDuplicateKeys(property.children?.[1], label, childLocation);
}
return;
}
if (node.type === 'array') {
for (const child of node.children ?? []) rejectDuplicateKeys(child, label, location);
}
}
export function parseQwenJsoncObject(raw: string, label: string): JsonObject {
const errors: ParseError[] = [];
const value: unknown = parse(raw, errors, {
allowTrailingComma: false,
disallowComments: false,
});
if (errors.length > 0) {
const details = errors
.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`)
.join(', ');
throw new Error(`Failed to parse ${label}: ${details}`);
}
if (!isObject(value)) throw new Error(`${label} must contain a JSON object`);
rejectDuplicateKeys(parseTree(raw, [], {
allowTrailingComma: false,
disallowComments: false,
}), label);
return value;
}
export function qwenSourceLabel(kind: QwenDocumentKind): string {
switch (kind) {
case 'system-defaults': return 'Qwen Code system defaults';
@@ -122,7 +68,7 @@ function readFolderTrustEnabled(root: JsonObject, label: string): boolean | unde
export function parseQwenDocument(options: DocumentOptions): QwenDocument {
const label = `${qwenSourceLabel(options.kind)} at ${options.filePath}`;
const root = parseQwenJsoncObject(options.raw, label);
const root = parseCommentedJsonObject(options.raw, label);
if (root.disableAllHooks !== undefined && typeof root.disableAllHooks !== 'boolean') {
throw new Error(`${label} field "disableAllHooks" must be a boolean`);
}
@@ -150,28 +96,6 @@ export function createQwenDocument(
});
}
function formatting(raw: string): FormattingOptions {
const indentation = /\r?\n([ \t]+)\S/.exec(raw)?.[1];
const insertSpaces = !indentation?.includes('\t');
return {
eol: raw.includes('\r\n') ? '\r\n' : '\n',
insertSpaces,
tabSize: insertSpaces ? Math.max(1, indentation?.length ?? 2) : 1,
};
}
function change(
raw: string,
jsonPath: JSONPath,
value: unknown,
isArrayInsertion = false,
): string {
return applyEdits(raw, modify(raw, jsonPath, value, {
formattingOptions: formatting(raw),
isArrayInsertion,
}));
}
function currentDocument(document: QwenDocument, raw: string): QwenDocument {
return parseQwenDocument({
kind: document.kind,
@@ -195,25 +119,25 @@ function removeManagedEntries(
for (const removal of eventRemovals) {
const groupPath: JSONPath = ['hooks', event, removal.groupIndex];
if (removal.removeGroup) {
raw = change(raw, groupPath, undefined);
raw = changeJsonc(raw, groupPath, undefined);
continue;
}
for (const handlerIndex of [...removal.handlerIndexes].sort(
(left, right) => right - left,
)) {
raw = change(raw, [...groupPath, 'hooks', handlerIndex], undefined);
raw = changeJsonc(raw, [...groupPath, 'hooks', handlerIndex], undefined);
}
}
if (eventRemovals.length > 0) {
const current = currentDocument(document, raw);
if ((current.hooks[event] ?? []).length === 0) {
raw = change(raw, ['hooks', event], undefined);
raw = changeJsonc(raw, ['hooks', event], undefined);
}
}
}
const current = currentDocument(document, raw);
if (current.hasHooksContainer && Object.keys(current.hooks).length === 0) {
raw = change(raw, ['hooks'], undefined);
raw = changeJsonc(raw, ['hooks'], undefined);
}
return raw;
}
@@ -226,9 +150,9 @@ function appendGroup(
): string {
const current = currentDocument(document, raw);
if (current.hooks[event]) {
return change(raw, ['hooks', event, current.hooks[event].length], group, true);
return changeJsonc(raw, ['hooks', event, current.hooks[event].length], group, true);
}
return change(raw, ['hooks', event], [group]);
return changeJsonc(raw, ['hooks', event], [group]);
}
export function renderQwenDocument(
+3 -6
View File
@@ -1,8 +1,7 @@
import { sameAgentId, samePath } from './common.js';
import {
buildQwenCommand,
qwenRuntimeReference,
sameQwenAgentId,
sameQwenPath,
type QwenRuntimeReference,
} from './qwen-command.js';
import { isObject, type JsonObject } from './strict-json.js';
@@ -283,7 +282,7 @@ export function managedQwenRemovals(
(hooks[event] ?? []).forEach((group, groupIndex) => {
const handlerIndexes = group.hooks.flatMap((handler, handlerIndex) => {
const reference = managedReference(handler, scriptName, hookName, true);
return reference && (agentId === undefined || sameQwenAgentId(reference.agentId, agentId))
return reference && (agentId === undefined || sameAgentId(reference.agentId, agentId))
? [handlerIndex]
: [];
});
@@ -334,7 +333,7 @@ export function qwenRuntimeContracts(hooks: QwenHooks): QwenRuntimeContract[] {
const post = posts.get(key);
const failure = failures.get(key);
if (guard.length !== 1 || post?.length !== 1 || failure?.length !== 1) continue;
if (!sameQwenPath(post[0].scriptPath, failure[0].scriptPath)) continue;
if (!samePath(post[0].scriptPath, failure[0].scriptPath)) continue;
contracts.push({
agentId: guard[0].agentId,
guardPath: guard[0].scriptPath,
@@ -343,5 +342,3 @@ export function qwenRuntimeContracts(hooks: QwenHooks): QwenRuntimeContract[] {
}
return contracts;
}
export { isObject, type JsonObject };
+5 -10
View File
@@ -5,7 +5,7 @@ import {
AUDIT_SCRIPT,
GUARD_SCRIPT,
} from './qwen-contract.js';
import { sameQwenPath } from './qwen-command.js';
import { MAX_SOURCE_BYTES, samePath } from './common.js';
import {
qwenDocumentLabel,
type RenderedQwenDocument,
@@ -30,16 +30,11 @@ import {
const DISPLAY_NAME = 'Qwen Code';
const SETTINGS_DIRECTORY_LABEL = 'Qwen Code user configuration directory';
const MAX_SOURCE_BYTES = 2 * 1024 * 1024;
export type QwenRuntimePaths = ManagedRuntimePaths;
export type PreparedQwenInstallation = PreparedManagedInstallation;
export type { RenameFile };
export async function preflightQwenInstallation(
config: InstallConfig,
sources: QwenSources,
): Promise<QwenRuntimePaths> {
): Promise<ManagedRuntimePaths> {
requireQwenHooksEnabled(sources);
return preflightManagedInstallation({
agentKey: AGENT_KEY,
@@ -53,7 +48,7 @@ export async function preflightQwenInstallation(
function readOnlyPreconditions(sources: QwenSources, changedPath?: string) {
return sources.preconditions.filter((condition) => (
changedPath === undefined || !sameQwenPath(condition.filePath, changedPath)
changedPath === undefined || !samePath(condition.filePath, changedPath)
));
}
@@ -61,7 +56,7 @@ export async function prepareQwenInstallation(
config: InstallConfig,
sources: QwenSources,
rendered: RenderedQwenDocument,
): Promise<PreparedQwenInstallation> {
): Promise<PreparedManagedInstallation> {
await preflightQwenInstallation(config, sources);
const settingsSource = rendered.next ?? rendered.document.raw;
const prepared = await prepareManagedInstallation({
@@ -88,7 +83,7 @@ export async function prepareQwenInstallation(
}
export async function commitQwenInstallation(
prepared: PreparedQwenInstallation,
prepared: PreparedManagedInstallation,
renameFile?: RenameFile,
): Promise<void> {
await commitManagedInstallation(prepared, renameFile);
+3 -91
View File
@@ -1,99 +1,11 @@
import os from 'node:os';
import path from 'node:path';
import { generateGuardScript } from './guard-template.js';
import { generateHookScript } from './hook-template.js';
import {
AGENT_KEY,
type QwenRuntimeContract,
} from './qwen-contract.js';
import { sameQwenAgentId, sameQwenPath } from './qwen-command.js';
import { inspectPhysicalDirectory, readPhysicalFile } from './managed-files.js';
import { parseStrictJsonObject, type JsonObject } from './strict-json.js';
function requireNonEmptyString(value: unknown, field: string, configPath: string): string {
if (typeof value !== 'string' || value.trim().length === 0) {
throw new Error(`Elydora runtime config ${field} is invalid: ${configPath}`);
}
return value;
}
function validateRuntimeConfig(
config: JsonObject,
contract: QwenRuntimeContract,
configPath: string,
): void {
const supported = new Set(['org_id', 'agent_id', 'kid', 'base_url', 'token', 'agent_name']);
const extra = Object.keys(config).find((key) => !supported.has(key));
if (extra) {
throw new Error(`Elydora runtime config has unsupported field "${extra}": ${configPath}`);
}
requireNonEmptyString(config.org_id, 'org_id', configPath);
requireNonEmptyString(config.kid, 'kid', configPath);
const agentId = requireNonEmptyString(config.agent_id, 'agent_id', configPath);
if (!sameQwenAgentId(agentId, contract.agentId) || config.agent_name !== AGENT_KEY) {
throw new Error(`Elydora runtime identity does not match Qwen Code hooks: ${configPath}`);
}
if (config.token !== undefined) requireNonEmptyString(config.token, 'token', configPath);
const rawBaseUrl = requireNonEmptyString(config.base_url, 'base_url', configPath);
let baseUrl: URL;
try {
baseUrl = new URL(rawBaseUrl);
} catch (error) {
throw new Error(`Elydora runtime config base_url is invalid: ${configPath}`, {
cause: error instanceof Error ? error : new Error(String(error)),
});
}
if (!['http:', 'https:'].includes(baseUrl.protocol)
|| !baseUrl.hostname
|| baseUrl.username
|| baseUrl.password
|| baseUrl.search
|| baseUrl.hash) {
throw new Error(`Elydora runtime config base_url is invalid: ${configPath}`);
}
}
function validatePrivateKey(contents: string, keyPath: string): void {
const bytes = Buffer.from(contents, 'base64url');
if (bytes.length !== 32 || bytes.toString('base64url') !== contents) {
throw new Error(`Elydora private key is invalid: ${keyPath}`);
}
}
async function runtimeContractExists(contract: QwenRuntimeContract): Promise<boolean> {
const runtimeRoot = path.join(os.homedir(), '.elydora');
const agentDirectory = path.dirname(contract.guardPath);
if (!sameQwenPath(path.dirname(agentDirectory), runtimeRoot)
|| !sameQwenPath(contract.auditPath, path.join(agentDirectory, 'hook.js'))) return false;
if (!await inspectPhysicalDirectory(runtimeRoot, 'Elydora runtime directory')) return false;
if (!await inspectPhysicalDirectory(agentDirectory, 'Elydora agent runtime directory')) {
return false;
}
const configPath = path.join(agentDirectory, 'config.json');
const keyPath = path.join(agentDirectory, 'private.key');
const [config, key, guard, audit] = await Promise.all([
readPhysicalFile(configPath, 'Elydora runtime config'),
readPhysicalFile(keyPath, 'Elydora private key'),
readPhysicalFile(contract.guardPath, 'Elydora guard runtime'),
readPhysicalFile(contract.auditPath, 'Elydora audit runtime'),
]);
if (!config || !key || !guard || !audit) return false;
const parsed = parseStrictJsonObject(config.contents, `Elydora runtime config at ${configPath}`);
validateRuntimeConfig(parsed, contract, configPath);
validatePrivateKey(key.contents, keyPath);
return guard.contents === generateGuardScript(AGENT_KEY, contract.agentId)
&& audit.contents === generateHookScript(
AGENT_KEY,
contract.agentId,
{ nativePayload: true },
);
}
import { AGENT_KEY, type QwenRuntimeContract } from './qwen-contract.js';
import { managedRuntimeFilesExist } from './managed-runtime-status.js';
export async function qwenRuntimeFilesExist(
contracts: readonly QwenRuntimeContract[],
): Promise<boolean> {
for (const contract of contracts) {
if (await runtimeContractExists(contract)) return true;
if (await managedRuntimeFilesExist(contract, AGENT_KEY)) return true;
}
return false;
}
+8 -14
View File
@@ -2,18 +2,17 @@ import os from 'node:os';
import path from 'node:path';
import fsp from 'node:fs/promises';
import { parse as parseDotenv } from 'dotenv';
import { MAX_SOURCE_BYTES, pathKey, samePath } from './common.js';
import {
createQwenDocument,
parseQwenDocument,
parseQwenJsoncObject,
qwenDocumentLabel,
qwenSourceLabel,
type QwenDocument,
type QwenDocumentKind,
} from './qwen-config.js';
import { readPhysicalFile, type FileSnapshot } from './managed-files.js';
const MAX_SOURCE_BYTES = 2 * 1024 * 1024;
import { parseCommentedJsonObject } from './strict-json.js';
const HOME_ENV_KEYS = ['QWEN_HOME', 'QWEN_RUNTIME_DIR'] as const;
export interface QwenSourcePrecondition {
@@ -94,7 +93,7 @@ async function resolveQwenRouting(): Promise<RoutingResult> {
const readCandidate = async (filePath: string): Promise<void> => {
const resolved = path.resolve(filePath);
const key = process.platform === 'win32' ? resolved.toLowerCase() : resolved;
const key = pathKey(resolved);
if (visited.has(key)) return;
visited.add(key);
const snapshot = await readPhysicalFile(resolved, 'Qwen Code home environment');
@@ -172,13 +171,8 @@ async function canonicalPath(filePath: string): Promise<string> {
}
}
function comparisonPath(filePath: string): string {
const resolved = path.resolve(filePath);
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
}
function isWithin(child: string, parent: string): boolean {
const relative = path.relative(comparisonPath(parent), comparisonPath(child));
const relative = path.relative(pathKey(parent), pathKey(child));
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
}
@@ -202,7 +196,7 @@ async function workspaceTrust(
original: snapshot,
};
if (!snapshot) return { trusted: true, precondition };
const rules = parseQwenJsoncObject(snapshot.contents, `Qwen Code trusted folders at ${filePath}`);
const rules = parseCommentedJsonObject(snapshot.contents, `Qwen Code trusted folders at ${filePath}`);
for (const [rulePath, level] of Object.entries(rules)) {
if (!['TRUST_FOLDER', 'TRUST_PARENT', 'DO_NOT_TRUST'].includes(String(level))) {
throw new Error(`Qwen Code trusted folders has invalid trust level for "${rulePath}"`);
@@ -217,7 +211,7 @@ async function workspaceTrust(
}
for (const [rulePath, level] of Object.entries(rules)) {
if (level === 'DO_NOT_TRUST'
&& comparisonPath(workspace) === comparisonPath(await canonicalPath(rulePath))) {
&& samePath(workspace, await canonicalPath(rulePath))) {
return { trusted: false, precondition };
}
}
@@ -256,7 +250,7 @@ function deduplicatePreconditions(
): QwenSourcePrecondition[] {
const result = new Map<string, QwenSourcePrecondition>();
for (const value of values) {
const key = comparisonPath(value.filePath);
const key = pathKey(value.filePath);
if (!result.has(key)) result.set(key, value);
}
return [...result.values()];
@@ -276,7 +270,7 @@ export async function readQwenSources(): Promise<QwenSources> {
canonicalPath(process.cwd()),
canonicalPath(os.homedir()),
]);
const workspaceActive = comparisonPath(canonicalWorkspace) !== comparisonPath(canonicalHome);
const workspaceActive = !samePath(canonicalWorkspace, canonicalHome);
const workspace = workspaceActive
? await readDocument('workspace', workspacePath)
: createQwenDocument('workspace', workspacePath);
+7
View File
@@ -66,6 +66,13 @@ export function parseStrictJsonObject(raw: string, label: string): JsonObject {
});
}
export function parseCommentedJsonObject(raw: string, label: string): JsonObject {
return parseJsonObject(raw, label, {
allowTrailingComma: false,
disallowComments: false,
});
}
export function parseStrictJsoncObject(raw: string, label: string): JsonObject {
return parseJsonObject(raw, label, {
allowTrailingComma: true,