@hexclave/shared-backend 1.0.35 → 1.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config-agent.d.ts +22 -1
- package/dist/config-agent.d.ts.map +1 -1
- package/dist/config-agent.js +54 -2
- package/dist/config-agent.js.map +1 -1
- package/dist/config-file.d.ts +15 -0
- package/dist/config-file.d.ts.map +1 -0
- package/dist/config-file.js +90 -0
- package/dist/config-file.js.map +1 -0
- package/dist/config-updater.d.ts +7 -0
- package/dist/config-updater.d.ts.map +1 -0
- package/dist/config-updater.js +330 -0
- package/dist/config-updater.js.map +1 -0
- package/dist/esm/config-agent.d.ts +22 -1
- package/dist/esm/config-agent.d.ts.map +1 -1
- package/dist/esm/config-agent.js +51 -3
- package/dist/esm/config-agent.js.map +1 -1
- package/dist/esm/config-file.d.ts +15 -0
- package/dist/esm/config-file.d.ts.map +1 -0
- package/dist/esm/config-file.js +82 -0
- package/dist/esm/config-file.js.map +1 -0
- package/dist/esm/config-updater.d.ts +7 -0
- package/dist/esm/config-updater.d.ts.map +1 -0
- package/dist/esm/config-updater.js +327 -0
- package/dist/esm/config-updater.js.map +1 -0
- package/dist/esm/index.d.ts +2 -16
- package/dist/esm/index.js +3 -446
- package/dist/index.d.ts +3 -16
- package/dist/index.js +15 -455
- package/package.json +2 -2
- package/dist/esm/index.d.ts.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-updater.js","names":["CONFIG_AGENT_FILE_TOOLS","ClaudeAgentTimeoutError","ClaudeAgentFailureError"],"sources":["../src/config-updater.ts"],"sourcesContent":["import { evalConfigFileContent } from \"@hexclave/shared/dist/config-eval\";\nimport type { Config, ConfigValue, NormalizedConfig } from \"@hexclave/shared/dist/config/format\";\nimport { normalize, override } from \"@hexclave/shared/dist/config/format\";\nimport { existsSync, readFileSync, rmSync, writeFileSync } from \"fs\";\nimport path from \"path\";\nimport { buildCompleteConfigAgentPrompt, buildPartialConfigAgentPrompt, ClaudeAgentFailureError, ClaudeAgentTimeoutError, CONFIG_AGENT_FILE_TOOLS, getToolWriteTargetPath, isPathInsideDir, runHeadlessClaudeAgent } from \"./config-agent\";\nimport { ensureConfigFileExists, readConfigFile } from \"./config-file\";\n\nconst LOG_PREFIX = \"[Hexclave config updater]\";\nconst DEFAULT_AGENT_TIMEOUT_MS = 120_000;\nconst CONFIG_UPDATE_LOG_PATH_LIMIT = 40;\nconst AGENT_OUTPUT_LOG_MAX_LENGTH = 20_000;\n\ntype ConfigFileSnapshot = { path: string, content: string | null };\ntype ConfigChange = { path: string, value: ConfigValue };\n\nfunction formatConfigUpdaterErrorForLog(error: unknown): Record<string, unknown> {\n if (error instanceof Error) {\n return {\n errorName: error.name,\n errorMessage: error.message,\n errorStack: error.stack,\n };\n }\n return {\n errorMessage: String(error),\n };\n}\n\nfunction configUpdatePathDetailsForLog(changes: ConfigChange[]): Record<string, unknown> {\n const paths = changes.map(({ path: configPath }) => configPath).sort();\n return {\n configUpdatePathCount: paths.length,\n configUpdatePaths: paths.slice(0, CONFIG_UPDATE_LOG_PATH_LIMIT),\n configUpdatePathsTruncated: paths.length > CONFIG_UPDATE_LOG_PATH_LIMIT,\n };\n}\n\nfunction appendBoundedAgentOutput(current: string, chunk: string): string {\n const next = `${current}${chunk}`;\n if (next.length <= AGENT_OUTPUT_LOG_MAX_LENGTH) {\n return next;\n }\n return next.slice(next.length - AGENT_OUTPUT_LOG_MAX_LENGTH);\n}\n\nfunction stringifyAgentMessageForLog(message: unknown): string {\n try {\n return `${JSON.stringify(message)}\\n`;\n } catch {\n return `${String(message)}\\n`;\n }\n}\n\nfunction agentOutputDetailsForLog(agentStdout: string, agentStderr: string): Record<string, unknown> {\n return {\n agentStdout,\n agentStdoutTruncated: agentStdout.length >= AGENT_OUTPUT_LOG_MAX_LENGTH,\n agentStderr,\n agentStderrTruncated: agentStderr.length >= AGENT_OUTPUT_LOG_MAX_LENGTH,\n };\n}\n\nexport async function updateConfigObject(configFilePath: string, configUpdate: Config): Promise<void> {\n const startedAtMs = performance.now();\n ensureConfigFileExists(configFilePath);\n\n const changes = flattenConfigUpdate(configUpdate);\n if (changes.length === 0) {\n console.log(`${LOG_PREFIX} Skipping config update because it contains no changes`, {\n configFilePath,\n });\n return;\n }\n const updateLogDetails = {\n configFilePath,\n ...configUpdatePathDetailsForLog(changes),\n };\n console.log(`${LOG_PREFIX} Starting config file update`, updateLogDetails);\n\n const content = readFileSync(configFilePath, \"utf-8\");\n\n // One write path, always: hand the change to the AI agent so it edits the file\n // in place and preserves its authoring (helper wrappers, imports, comments,\n // layout). There is deliberately no deterministic \"fast path\" — re-rendering a\n // config would flatten and destroy hand-authored files. Reads use jiti\n // (see readConfigFile); writes go through the agent.\n console.log(`${LOG_PREFIX} Applying config update with agent-assisted rewrite`, {\n ...updateLogDetails,\n configDirectory: path.dirname(configFilePath),\n });\n const baselineConfig = await tryReadConfigForValidation(configFilePath);\n const { snapshots, seen } = snapshotConfigFiles(configFilePath, content);\n try {\n await runConfigUpdateAgent({\n prompt: buildConfigUpdatePrompt(path.basename(configFilePath), configUpdate, baselineConfig),\n cwd: path.dirname(configFilePath),\n onFileWillChange: (filePath) => captureSnapshotIfAbsent(snapshots, filePath, seen),\n });\n await validateAgentUpdate(configFilePath, baselineConfig, configUpdate);\n } catch (error) {\n console.warn(`${LOG_PREFIX} Config update failed; restoring files from snapshots`, {\n ...updateLogDetails,\n snapshotCount: snapshots.length,\n elapsedMs: Math.round(performance.now() - startedAtMs),\n ...formatConfigUpdaterErrorForLog(error),\n });\n try {\n restoreConfigFiles(snapshots);\n console.warn(`${LOG_PREFIX} Restored files after failed config update`, {\n ...updateLogDetails,\n snapshotCount: snapshots.length,\n });\n } catch (restoreError) {\n console.error(`${LOG_PREFIX} Failed to fully roll back config files after a failed update of ${configFilePath}; some files may be left in a partially-restored state`, {\n configFilePath,\n ...formatConfigUpdaterErrorForLog(restoreError),\n });\n }\n throw error;\n }\n console.log(`${LOG_PREFIX} Finished config update with agent-assisted rewrite`, {\n ...updateLogDetails,\n elapsedMs: Math.round(performance.now() - startedAtMs),\n snapshotCount: snapshots.length,\n });\n}\n\nasync function runConfigUpdateAgent(options: {\n prompt: string,\n cwd: string,\n onFileWillChange?: (filePath: string) => void,\n}): Promise<void> {\n const timeoutMs = parseAgentTimeoutMs();\n const deniedOutOfBoundsWrites = new Set<string>();\n const startedAtMs = performance.now();\n let agentStdout = \"\";\n let agentStderr = \"\";\n console.log(`${LOG_PREFIX} Starting config update agent`, {\n cwd: options.cwd,\n timeoutMs,\n });\n try {\n await runHeadlessClaudeAgent({\n prompt: options.prompt,\n cwd: options.cwd,\n allowedTools: [...CONFIG_AGENT_FILE_TOOLS],\n strictIsolation: true,\n timeoutMs,\n stderr: (data) => {\n agentStderr = appendBoundedAgentOutput(agentStderr, data);\n console.warn(`${LOG_PREFIX} [agent] ${data}`);\n },\n onMessage: (message) => {\n agentStdout = appendBoundedAgentOutput(agentStdout, stringifyAgentMessageForLog(message));\n },\n onPreToolUse: (input) => {\n const target = getToolWriteTargetPath(input.tool_name, input.tool_input, options.cwd);\n if (target == null) return { continue: true };\n if (!isPathInsideDir(options.cwd, target)) {\n deniedOutOfBoundsWrites.add(target);\n return {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: `Refusing to modify ${target}: config updates may only change files inside the config directory.`,\n },\n };\n }\n options.onFileWillChange?.(target);\n return { continue: true };\n },\n });\n } catch (error) {\n if (error instanceof ClaudeAgentTimeoutError) {\n console.warn(`${LOG_PREFIX} Config update agent timed out`, {\n cwd: options.cwd,\n timeoutMs,\n elapsedMs: Math.round(performance.now() - startedAtMs),\n ...formatConfigUpdaterErrorForLog(error),\n ...agentOutputDetailsForLog(agentStdout, agentStderr),\n });\n throw new Error(`Config update agent timed out after ${timeoutMs}ms. It was unable to apply the config changes to the file.`);\n }\n if (error instanceof ClaudeAgentFailureError) {\n console.warn(`${LOG_PREFIX} Config update agent failed`, {\n cwd: options.cwd,\n timeoutMs,\n elapsedMs: Math.round(performance.now() - startedAtMs),\n ...formatConfigUpdaterErrorForLog(error),\n ...agentOutputDetailsForLog(agentStdout, agentStderr),\n });\n throw new Error(`${error.message} It was unable to apply the config changes to the file.`);\n }\n console.warn(`${LOG_PREFIX} Config update agent failed unexpectedly`, {\n cwd: options.cwd,\n timeoutMs,\n elapsedMs: Math.round(performance.now() - startedAtMs),\n ...formatConfigUpdaterErrorForLog(error),\n ...agentOutputDetailsForLog(agentStdout, agentStderr),\n });\n throw error;\n }\n console.log(`${LOG_PREFIX} Finished config update agent`, {\n cwd: options.cwd,\n timeoutMs,\n elapsedMs: Math.round(performance.now() - startedAtMs),\n deniedOutOfBoundsWriteCount: deniedOutOfBoundsWrites.size,\n });\n if (deniedOutOfBoundsWrites.size > 0) {\n console.warn(`${LOG_PREFIX} Config update agent attempted out-of-bounds writes`, {\n cwd: options.cwd,\n deniedOutOfBoundsWriteCount: deniedOutOfBoundsWrites.size,\n deniedOutOfBoundsWrites: [...deniedOutOfBoundsWrites],\n });\n throw new Error(`Config update agent tried to modify ${deniedOutOfBoundsWrites.size} file(s) outside the config directory, which is not allowed: ${[...deniedOutOfBoundsWrites].join(\", \")}. The config was not updated.`);\n }\n}\n\nfunction parseAgentTimeoutMs(): number {\n const raw = process.env.STACK_CONFIG_UPDATE_AGENT_TIMEOUT_MS;\n if (raw == null || raw.trim() === \"\") return DEFAULT_AGENT_TIMEOUT_MS;\n const parsed = Number(raw);\n if (!Number.isFinite(parsed) || parsed <= 0) {\n throw new Error(`Invalid STACK_CONFIG_UPDATE_AGENT_TIMEOUT_MS: ${JSON.stringify(raw)}. Expected a positive number of milliseconds.`);\n }\n return parsed;\n}\n\nfunction captureSnapshotIfAbsent(snapshots: ConfigFileSnapshot[], filePath: string, seen: Set<string>): void {\n const resolved = path.resolve(filePath);\n if (seen.has(resolved)) return;\n seen.add(resolved);\n snapshots.push({ path: resolved, content: existsSync(resolved) ? readFileSync(resolved, \"utf-8\") : null });\n}\n\nfunction snapshotConfigFiles(configFilePath: string, configContent: string): { snapshots: ConfigFileSnapshot[]; seen: Set<string> } {\n const dir = path.dirname(configFilePath);\n const resolvedConfig = path.resolve(configFilePath);\n const snapshots: ConfigFileSnapshot[] = [{ path: resolvedConfig, content: configContent }];\n const seen = new Set<string>([resolvedConfig]);\n for (const specifier of getRelativeImportSpecifiers(configContent)) {\n const resolved = path.resolve(dir, specifier);\n if (!isPathInsideDir(dir, resolved)) continue;\n captureSnapshotIfAbsent(snapshots, resolved, seen);\n }\n return { snapshots, seen };\n}\n\nfunction restoreConfigFiles(snapshots: ConfigFileSnapshot[]): void {\n const failures: string[] = [];\n for (const { path: filePath, content } of snapshots) {\n try {\n if (content === null) {\n if (existsSync(filePath)) rmSync(filePath);\n } else {\n writeFileSync(filePath, content, \"utf-8\");\n }\n } catch (error) {\n failures.push(`${filePath}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n if (failures.length > 0) {\n throw new Error(`Failed to restore ${failures.length} file(s) during rollback: ${failures.join(\"; \")}`);\n }\n}\n\nasync function tryReadConfigForValidation(configFilePath: string): Promise<Config | null> {\n try {\n return (await readConfigFile(configFilePath)).config;\n } catch (error) {\n console.warn(`${LOG_PREFIX} Could not evaluate config for validation baseline; will fall back to a structural check`, {\n configFilePath,\n error: error instanceof Error ? error.message : String(error),\n });\n return null;\n }\n}\n\nasync function validateAgentUpdate(configFilePath: string, baselineConfig: Config | null, configUpdate: Config): Promise<void> {\n if (baselineConfig != null) {\n const target = canonicalizeConfig(override(baselineConfig, configUpdate));\n const result = canonicalizeConfig((await readConfigFile(configFilePath)).config);\n if (!configsEqual(result, target)) {\n throw new Error(`Config update validation failed for ${configFilePath}: the updated file does not evaluate to the expected configuration.`);\n }\n return;\n }\n\n // Structural-only fallback when jiti can't evaluate the config (e.g. import-with\n // assets): we can't verify values, only that the file still exports `config`.\n const content = readFileSync(configFilePath, \"utf-8\");\n if (!configFileExportsConfig(content, configFilePath)) {\n throw new Error(`Config update validation failed for ${configFilePath}: the updated file no longer exports a valid \\`config\\`.`);\n }\n}\n\nfunction configFileExportsConfig(content: string, configFilePath: string): boolean {\n try {\n evalConfigFileContent(content, configFilePath);\n return true;\n } catch {\n // jiti may fail to resolve imports valid in the user's project but absent\n // here (relative assets, workspace packages). For the structural check we\n // only need a runtime `config` binding to still exist; the agent always\n // authors `export const config`.\n return /\\bexport\\s+const\\s+config\\b/.test(content);\n }\n}\n\nfunction getRelativeImportSpecifiers(content: string): string[] {\n const specifiers: string[] = [];\n const importPattern = /\\bimport\\b(?:[^'\"]*?\\bfrom\\s*)?[\"'](\\.{1,2}\\/[^\"']+)[\"']/g;\n let match: RegExpExecArray | null;\n while ((match = importPattern.exec(content)) !== null) {\n specifiers.push(match[1]);\n }\n return specifiers;\n}\n\nfunction flattenConfigUpdate(update: Config): ConfigChange[] {\n const changes: ConfigChange[] = [];\n const walk = (prefix: string, obj: Config): void => {\n for (const [key, value] of Object.entries(obj)) {\n const fullPath = prefix === \"\" ? key : `${prefix}.${key}`;\n if (value === undefined) continue;\n if (value !== null && typeof value === \"object\" && !Array.isArray(value) && Object.keys(value).length > 0) {\n walk(fullPath, value);\n } else {\n changes.push({ path: fullPath, value });\n }\n }\n };\n walk(\"\", update);\n return changes;\n}\n\nfunction buildConfigUpdatePrompt(configFileName: string, configUpdate: Config, baselineConfig: Config | null): string {\n const changes = flattenConfigUpdate(configUpdate);\n const commandPolicy = \"Do not run shell commands and do not create files other than what is required to apply the config changes.\";\n if (baselineConfig != null) {\n return buildCompleteConfigAgentPrompt({\n scope: { mode: \"known-file\", configFileName },\n completeConfig: canonicalizeConfig(override(baselineConfig, configUpdate)),\n commandPolicy,\n });\n }\n return buildPartialConfigAgentPrompt({\n configFileName,\n changes,\n commandPolicy,\n });\n}\n\nfunction canonicalizeConfig(config: Config): NormalizedConfig {\n const droppedKeys: string[] = [];\n const normalized = normalize(config, {\n onDotIntoNonObject: \"ignore\",\n onDotIntoNull: \"empty-object\",\n droppedKeys,\n });\n if (droppedKeys.length > 0) {\n throw new Error(`Config update has conflicting keys that would be dropped during normalization: ${droppedKeys.map((key) => JSON.stringify(key)).join(\", \")}`);\n }\n return normalized;\n}\n\nfunction configsEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return a === b;\n if (Array.isArray(a) || Array.isArray(b)) {\n if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;\n return a.every((value, index) => configsEqual(value, b[index]));\n }\n if (typeof a === \"object\" && typeof b === \"object\") {\n const aEntries = Object.entries(a);\n const bMap = new Map(Object.entries(b));\n if (aEntries.length !== bMap.size) return false;\n return aEntries.every(([key, value]) => bMap.has(key) && configsEqual(value, bMap.get(key)));\n }\n return false;\n}\n"],"mappings":"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"}
|
|
@@ -27,6 +27,27 @@ type RunClaudeAgentOptions = {
|
|
|
27
27
|
type RunClaudeAgentResult = {
|
|
28
28
|
resultText: string;
|
|
29
29
|
};
|
|
30
|
+
declare const CONFIG_AGENT_FILE_TOOLS: readonly ["Read", "Write", "Edit", "Glob", "Grep"];
|
|
31
|
+
declare const CONFIG_AGENT_REPO_TOOLS: readonly ["Read", "Write", "Edit", "Glob", "Grep", "Bash"];
|
|
32
|
+
type ConfigAgentPromptScope = {
|
|
33
|
+
mode: "known-file";
|
|
34
|
+
configFileName: string;
|
|
35
|
+
} | {
|
|
36
|
+
mode: "repo";
|
|
37
|
+
};
|
|
38
|
+
declare function buildCompleteConfigAgentPrompt(options: {
|
|
39
|
+
scope: ConfigAgentPromptScope;
|
|
40
|
+
completeConfig: Record<string, unknown>;
|
|
41
|
+
commandPolicy: string;
|
|
42
|
+
}): string;
|
|
43
|
+
declare function buildPartialConfigAgentPrompt(options: {
|
|
44
|
+
configFileName: string;
|
|
45
|
+
changes: Array<{
|
|
46
|
+
path: string;
|
|
47
|
+
value: unknown;
|
|
48
|
+
}>;
|
|
49
|
+
commandPolicy: string;
|
|
50
|
+
}): string;
|
|
30
51
|
declare class ClaudeAgentTimeoutError extends Error {
|
|
31
52
|
constructor(timeoutMs?: number);
|
|
32
53
|
}
|
|
@@ -38,5 +59,5 @@ declare function runHeadlessClaudeAgent(options: RunClaudeAgentOptions): Promise
|
|
|
38
59
|
declare function getToolWriteTargetPath(toolName: string, toolInput: unknown, cwd: string): string | null;
|
|
39
60
|
declare function isPathInsideDir(dir: string, target: string): boolean;
|
|
40
61
|
//#endregion
|
|
41
|
-
export { ClaudeAgentFailureError, ClaudeAgentPreToolUseInput, ClaudeAgentTimeoutError, ClaudeAgentToolName, RunClaudeAgentOptions, RunClaudeAgentResult, getToolWriteTargetPath, isPathInsideDir, runHeadlessClaudeAgent, stripClaudeCodeEnv };
|
|
62
|
+
export { CONFIG_AGENT_FILE_TOOLS, CONFIG_AGENT_REPO_TOOLS, ClaudeAgentFailureError, ClaudeAgentPreToolUseInput, ClaudeAgentTimeoutError, ClaudeAgentToolName, ConfigAgentPromptScope, RunClaudeAgentOptions, RunClaudeAgentResult, buildCompleteConfigAgentPrompt, buildPartialConfigAgentPrompt, getToolWriteTargetPath, isPathInsideDir, runHeadlessClaudeAgent, stripClaudeCodeEnv };
|
|
42
63
|
//# sourceMappingURL=config-agent.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-agent.d.ts","names":[],"sources":["../../src/config-agent.ts"],"mappings":";KAMY,mBAAA;AAAA,KAEP,qBAAA;EAA0B,QAAA;AAAA;EAC7B,kBAAA;IACE,aAAA;IACA,kBAAA;IACA,wBAAA;EAAA;AAAA;AAAA,KAIQ,0BAAA;EACV,eAAA;EACA,SAAA;EACA,UAAA;AAAA;AAAA,KAGU,qBAAA;EACV,MAAA;EACA,GAAA;EACA,YAAA,EAAc,mBAAA;EACd,SAAA;EACA,eAAA;EACA,MAAA,IAAU,IAAA;EACV,SAAA,IAAa,OAAA;EACb,YAAA,IAAgB,KAAA,EAAO,0BAAA,KAA+B,OAAA,CAAQ,qBAAA,IAAyB,qBAAA;AAAA;AAAA,KAG7E,oBAAA;EACV,UAAA;AAAA;AAAA,cAGW,uBAAA,SAAgC,KAAA;cAC/B,SAAA;AAAA;AAAA,cAMD,uBAAA,SAAgC,KAAA;cAC/B,OAAA;AAAA;AAAA,iBAUE,kBAAA,CAAA,GAAsB,MAAA;AAAA,iBAOhB,sBAAA,CAAuB,OAAA,EAAS,qBAAA,GAAwB,OAAA,CAAQ,oBAAA;AAAA,iBA0EtE,sBAAA,CAAuB,QAAA,UAAkB,SAAA,WAAoB,GAAA;AAAA,iBAO7D,eAAA,CAAgB,GAAA,UAAa,MAAA"}
|
|
1
|
+
{"version":3,"file":"config-agent.d.ts","names":[],"sources":["../../src/config-agent.ts"],"mappings":";KAMY,mBAAA;AAAA,KAEP,qBAAA;EAA0B,QAAA;AAAA;EAC7B,kBAAA;IACE,aAAA;IACA,kBAAA;IACA,wBAAA;EAAA;AAAA;AAAA,KAIQ,0BAAA;EACV,eAAA;EACA,SAAA;EACA,UAAA;AAAA;AAAA,KAGU,qBAAA;EACV,MAAA;EACA,GAAA;EACA,YAAA,EAAc,mBAAA;EACd,SAAA;EACA,eAAA;EACA,MAAA,IAAU,IAAA;EACV,SAAA,IAAa,OAAA;EACb,YAAA,IAAgB,KAAA,EAAO,0BAAA,KAA+B,OAAA,CAAQ,qBAAA,IAAyB,qBAAA;AAAA;AAAA,KAG7E,oBAAA;EACV,UAAA;AAAA;AAAA,cAGW,uBAAA;AAAA,cACA,uBAAA;AAAA,KAYD,sBAAA;EAER,IAAA;EACA,cAAA;AAAA;EAGA,IAAA;AAAA;AAAA,iBA+BY,8BAAA,CAA+B,OAAA;EAC7C,KAAA,EAAO,sBAAA;EACP,cAAA,EAAgB,MAAA;EAChB,aAAA;AAAA;AAAA,iBAYc,6BAAA,CAA8B,OAAA;EAC5C,cAAA;EACA,OAAA,EAAS,KAAA;IAAQ,IAAA;IAAc,KAAA;EAAA;EAC/B,aAAA;AAAA;AAAA,cAeW,uBAAA,SAAgC,KAAA;cAC/B,SAAA;AAAA;AAAA,cAMD,uBAAA,SAAgC,KAAA;cAC/B,OAAA;AAAA;AAAA,iBAUE,kBAAA,CAAA,GAAsB,MAAA;AAAA,iBAOhB,sBAAA,CAAuB,OAAA,EAAS,qBAAA,GAAwB,OAAA,CAAQ,oBAAA;AAAA,iBA0EtE,sBAAA,CAAuB,QAAA,UAAkB,SAAA,WAAoB,GAAA;AAAA,iBAO7D,eAAA,CAAgB,GAAA,UAAa,MAAA"}
|
package/dist/esm/config-agent.js
CHANGED
|
@@ -2,7 +2,55 @@ import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
|
|
4
4
|
//#region src/config-agent.ts
|
|
5
|
-
const ANTHROPIC_PROXY_BASE_URL = process.env.STACK_CLAUDE_PROXY_URL ?? "https://api.hexclave.com/api/
|
|
5
|
+
const ANTHROPIC_PROXY_BASE_URL = process.env.STACK_CLAUDE_PROXY_URL ?? "https://api.hexclave.com/api/latest/integrations/ai-proxy";
|
|
6
|
+
const CONFIG_AGENT_FILE_TOOLS = [
|
|
7
|
+
"Read",
|
|
8
|
+
"Write",
|
|
9
|
+
"Edit",
|
|
10
|
+
"Glob",
|
|
11
|
+
"Grep"
|
|
12
|
+
];
|
|
13
|
+
const CONFIG_AGENT_REPO_TOOLS = [...CONFIG_AGENT_FILE_TOOLS, "Bash"];
|
|
14
|
+
function buildConfigAgentPrompt(options) {
|
|
15
|
+
const targetSection = options.target.mode === "complete" ? `The exported config must end up deep-equal to this JSON value:\n\n${JSON.stringify(options.target.completeConfig, null, 2)}` : `Apply EXACTLY these config changes. Paths use dot notation, so \`a.b.c\` refers to \`config.a.b.c\`:\n\n${options.target.changes.map(({ path, value }) => `- ${JSON.stringify(path)}: set to ${JSON.stringify(value)}`).join("\n")}`;
|
|
16
|
+
return `You are updating a Hexclave / Stack Auth configuration file.
|
|
17
|
+
|
|
18
|
+
${options.scope.mode === "known-file" ? `Config file: ${JSON.stringify(options.scope.configFileName)} in the current working directory.` : "Current working directory: the repository root. Find the Hexclave / Stack Auth config file. It is usually a `*.config.ts` file exporting `config`, often wrapped in `defineHexclaveConfig(...)` or a similar helper."}
|
|
19
|
+
|
|
20
|
+
${targetSection}
|
|
21
|
+
|
|
22
|
+
Rules:
|
|
23
|
+
- Keep the file valid: it must still export \`config\`.
|
|
24
|
+
- Preserve the existing authoring style where possible: imports, comments, helper wrappers, file header comments, and formatting.
|
|
25
|
+
- If the config currently exports the placeholder string "show-onboarding" or is otherwise a stub, replace it with a real typed config object.
|
|
26
|
+
- If a config value is conventionally sourced from an imported external file, you may keep that indirection as long as the evaluated config matches the requested value.
|
|
27
|
+
- Do not touch unrelated files or application code.
|
|
28
|
+
- ${options.commandPolicy}
|
|
29
|
+
- Make the edits, then stop.`;
|
|
30
|
+
}
|
|
31
|
+
function buildCompleteConfigAgentPrompt(options) {
|
|
32
|
+
return buildConfigAgentPrompt({
|
|
33
|
+
scope: options.scope,
|
|
34
|
+
target: {
|
|
35
|
+
mode: "complete",
|
|
36
|
+
completeConfig: options.completeConfig
|
|
37
|
+
},
|
|
38
|
+
commandPolicy: options.commandPolicy
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function buildPartialConfigAgentPrompt(options) {
|
|
42
|
+
return buildConfigAgentPrompt({
|
|
43
|
+
scope: {
|
|
44
|
+
mode: "known-file",
|
|
45
|
+
configFileName: options.configFileName
|
|
46
|
+
},
|
|
47
|
+
target: {
|
|
48
|
+
mode: "partial",
|
|
49
|
+
changes: options.changes
|
|
50
|
+
},
|
|
51
|
+
commandPolicy: options.commandPolicy
|
|
52
|
+
});
|
|
53
|
+
}
|
|
6
54
|
var ClaudeAgentTimeoutError = class extends Error {
|
|
7
55
|
constructor(timeoutMs) {
|
|
8
56
|
super(`Claude agent timed out${timeoutMs == null ? "" : ` after ${timeoutMs}ms`}.`);
|
|
@@ -33,7 +81,7 @@ async function runHeadlessClaudeAgent(options) {
|
|
|
33
81
|
for await (const message of query({
|
|
34
82
|
prompt: options.prompt,
|
|
35
83
|
options: {
|
|
36
|
-
model: "
|
|
84
|
+
model: "anthropic/claude-haiku-4.5",
|
|
37
85
|
...options.strictIsolation === true ? {
|
|
38
86
|
settingSources: [],
|
|
39
87
|
strictMcpConfig: true
|
|
@@ -84,5 +132,5 @@ function isPathInsideDir(dir, target) {
|
|
|
84
132
|
}
|
|
85
133
|
|
|
86
134
|
//#endregion
|
|
87
|
-
export { ClaudeAgentFailureError, ClaudeAgentTimeoutError, getToolWriteTargetPath, isPathInsideDir, runHeadlessClaudeAgent, stripClaudeCodeEnv };
|
|
135
|
+
export { CONFIG_AGENT_FILE_TOOLS, CONFIG_AGENT_REPO_TOOLS, ClaudeAgentFailureError, ClaudeAgentTimeoutError, buildCompleteConfigAgentPrompt, buildPartialConfigAgentPrompt, getToolWriteTargetPath, isPathInsideDir, runHeadlessClaudeAgent, stripClaudeCodeEnv };
|
|
88
136
|
//# sourceMappingURL=config-agent.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-agent.js","names":[],"sources":["../../src/config-agent.ts"],"sourcesContent":["import { query } from \"@anthropic-ai/claude-agent-sdk\";\nimport path from \"path\";\n\nconst DEFAULT_PROXY_URL = \"https://api.hexclave.com/api/v1/integrations/ai-proxy\";\nconst ANTHROPIC_PROXY_BASE_URL: string = process.env.STACK_CLAUDE_PROXY_URL ?? DEFAULT_PROXY_URL;\n\nexport type ClaudeAgentToolName = \"Read\" | \"Write\" | \"Edit\" | \"MultiEdit\" | \"NotebookEdit\" | \"Bash\" | \"Glob\" | \"Grep\";\n\ntype ClaudeAgentHookResult = { continue: true } | {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: string,\n },\n};\n\nexport type ClaudeAgentPreToolUseInput = {\n hook_event_name: \"PreToolUse\",\n tool_name: string,\n tool_input: unknown,\n};\n\nexport type RunClaudeAgentOptions = {\n prompt: string,\n cwd: string,\n allowedTools: ClaudeAgentToolName[],\n timeoutMs?: number,\n strictIsolation?: boolean,\n stderr?: (data: string) => void,\n onMessage?: (message: unknown) => void,\n onPreToolUse?: (input: ClaudeAgentPreToolUseInput) => Promise<ClaudeAgentHookResult> | ClaudeAgentHookResult,\n};\n\nexport type RunClaudeAgentResult = {\n resultText: string,\n};\n\nexport class ClaudeAgentTimeoutError extends Error {\n constructor(timeoutMs?: number) {\n super(`Claude agent timed out${timeoutMs == null ? \"\" : ` after ${timeoutMs}ms`}.`);\n this.name = \"ClaudeAgentTimeoutError\";\n }\n}\n\nexport class ClaudeAgentFailureError extends Error {\n constructor(subtype: string) {\n super(`Claude agent failed (${subtype}).`);\n this.name = \"ClaudeAgentFailureError\";\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nexport function stripClaudeCodeEnv(): Record<string, string | undefined> {\n const env = { ...process.env };\n // CLAUDECODE must be unset for nested agents; ANTHROPIC_API_KEY must be non-empty (proxy ignores it).\n delete env.CLAUDECODE;\n return env;\n}\n\nexport async function runHeadlessClaudeAgent(options: RunClaudeAgentOptions): Promise<RunClaudeAgentResult> {\n const abortController = new AbortController();\n const timeout = options.timeoutMs == null ? null : setTimeout(() => abortController.abort(), options.timeoutMs);\n let sawResult = false;\n let resultText = \"\";\n const onPreToolUse = options.onPreToolUse;\n try {\n for await (const message of query({\n prompt: options.prompt,\n options: {\n model: \"nvidia/nemotron-3-super-120b-a12b:nitro\",\n ...(options.strictIsolation === true ? {\n settingSources: [],\n strictMcpConfig: true,\n } : {}),\n ...(onPreToolUse == null ? {} : {\n hooks: {\n PreToolUse: [{\n hooks: [async (input) => {\n if (input.hook_event_name !== \"PreToolUse\") {\n return { continue: true };\n }\n return await onPreToolUse(input);\n }],\n }],\n },\n }),\n allowedTools: options.allowedTools,\n permissionMode: \"dontAsk\",\n cwd: options.cwd,\n abortController,\n env: {\n ...stripClaudeCodeEnv(),\n ...(options.strictIsolation === true ? { CLAUDE_CODE_DISABLE_AUTO_MEMORY: \"1\" } : {}),\n ANTHROPIC_BASE_URL: ANTHROPIC_PROXY_BASE_URL,\n ANTHROPIC_API_KEY: \"stack-auth-proxy\",\n },\n stderr: options.stderr,\n },\n })) {\n options.onMessage?.(message);\n if (message.type === \"result\") {\n if (\"result\" in message) {\n sawResult = true;\n resultText = message.result;\n } else {\n throw new ClaudeAgentFailureError(message.subtype);\n }\n }\n }\n } catch (error) {\n if (abortController.signal.aborted && isAbortError(error)) {\n throw new ClaudeAgentTimeoutError(options.timeoutMs ?? undefined);\n }\n throw error;\n } finally {\n if (timeout != null) {\n clearTimeout(timeout);\n }\n }\n if (!sawResult) {\n throw new Error(\"Claude agent ended without reporting a result.\");\n }\n return { resultText };\n}\n\n// Only the tools actually in the agent's allowedTools list; MultiEdit and\n// NotebookEdit are intentionally excluded from the agent's tool set.\nconst FILE_MUTATING_TOOLS = new Set([\"Write\", \"Edit\"]);\n\nfunction hasStringFilePath(input: unknown): input is { file_path: string } {\n return typeof input === \"object\" && input !== null && \"file_path\" in input && typeof input.file_path === \"string\";\n}\n\nexport function getToolWriteTargetPath(toolName: string, toolInput: unknown, cwd: string): string | null {\n if (!FILE_MUTATING_TOOLS.has(toolName) || !hasStringFilePath(toolInput)) {\n return null;\n }\n return path.isAbsolute(toolInput.file_path) ? toolInput.file_path : path.resolve(cwd, toolInput.file_path);\n}\n\nexport function isPathInsideDir(dir: string, target: string): boolean {\n const relative = path.relative(path.resolve(dir), path.resolve(target));\n return relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n"],"mappings":";;;;AAIA,MAAM,2BAAmC,QAAQ,IAAI,0BAD3B;AAkC1B,IAAa,0BAAb,cAA6C,MAAM;CACjD,YAAY,WAAoB;AAC9B,QAAM,yBAAyB,aAAa,OAAO,KAAK,UAAU,UAAU,IAAI,GAAG;AACnF,OAAK,OAAO;;;AAIhB,IAAa,0BAAb,cAA6C,MAAM;CACjD,YAAY,SAAiB;AAC3B,QAAM,wBAAwB,QAAQ,IAAI;AAC1C,OAAK,OAAO;;;AAIhB,SAAS,aAAa,OAAyB;AAC7C,QAAO,iBAAiB,SAAS,MAAM,SAAS;;AAGlD,SAAgB,qBAAyD;CACvE,MAAM,MAAM,EAAE,GAAG,QAAQ,KAAK;AAE9B,QAAO,IAAI;AACX,QAAO;;AAGT,eAAsB,uBAAuB,SAA+D;CAC1G,MAAM,kBAAkB,IAAI,iBAAiB;CAC7C,MAAM,UAAU,QAAQ,aAAa,OAAO,OAAO,iBAAiB,gBAAgB,OAAO,EAAE,QAAQ,UAAU;CAC/G,IAAI,YAAY;CAChB,IAAI,aAAa;CACjB,MAAM,eAAe,QAAQ;AAC7B,KAAI;AACF,aAAW,MAAM,WAAW,MAAM;GAChC,QAAQ,QAAQ;GAChB,SAAS;IACP,OAAO;IACP,GAAI,QAAQ,oBAAoB,OAAO;KACrC,gBAAgB,EAAE;KAClB,iBAAiB;KAClB,GAAG,EAAE;IACN,GAAI,gBAAgB,OAAO,EAAE,GAAG,EAC9B,OAAO,EACL,YAAY,CAAC,EACX,OAAO,CAAC,OAAO,UAAU;AACvB,SAAI,MAAM,oBAAoB,aAC5B,QAAO,EAAE,UAAU,MAAM;AAE3B,YAAO,MAAM,aAAa,MAAM;MAChC,EACH,CAAC,EACH,EACF;IACD,cAAc,QAAQ;IACtB,gBAAgB;IAChB,KAAK,QAAQ;IACb;IACA,KAAK;KACH,GAAG,oBAAoB;KACvB,GAAI,QAAQ,oBAAoB,OAAO,EAAE,iCAAiC,KAAK,GAAG,EAAE;KACpF,oBAAoB;KACpB,mBAAmB;KACpB;IACD,QAAQ,QAAQ;IACjB;GACF,CAAC,EAAE;AACF,WAAQ,YAAY,QAAQ;AAC5B,OAAI,QAAQ,SAAS,SACnB,KAAI,YAAY,SAAS;AACvB,gBAAY;AACZ,iBAAa,QAAQ;SAErB,OAAM,IAAI,wBAAwB,QAAQ,QAAQ;;UAIjD,OAAO;AACd,MAAI,gBAAgB,OAAO,WAAW,aAAa,MAAM,CACvD,OAAM,IAAI,wBAAwB,QAAQ,aAAa,OAAU;AAEnE,QAAM;WACE;AACR,MAAI,WAAW,KACb,cAAa,QAAQ;;AAGzB,KAAI,CAAC,UACH,OAAM,IAAI,MAAM,iDAAiD;AAEnE,QAAO,EAAE,YAAY;;AAKvB,MAAM,sBAAsB,IAAI,IAAI,CAAC,SAAS,OAAO,CAAC;AAEtD,SAAS,kBAAkB,OAAgD;AACzE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,eAAe,SAAS,OAAO,MAAM,cAAc;;AAG3G,SAAgB,uBAAuB,UAAkB,WAAoB,KAA4B;AACvG,KAAI,CAAC,oBAAoB,IAAI,SAAS,IAAI,CAAC,kBAAkB,UAAU,CACrE,QAAO;AAET,QAAO,KAAK,WAAW,UAAU,UAAU,GAAG,UAAU,YAAY,KAAK,QAAQ,KAAK,UAAU,UAAU;;AAG5G,SAAgB,gBAAgB,KAAa,QAAyB;CACpE,MAAM,WAAW,KAAK,SAAS,KAAK,QAAQ,IAAI,EAAE,KAAK,QAAQ,OAAO,CAAC;AACvE,QAAO,aAAa,MAAO,CAAC,SAAS,WAAW,KAAK,IAAI,CAAC,KAAK,WAAW,SAAS"}
|
|
1
|
+
{"version":3,"file":"config-agent.js","names":[],"sources":["../../src/config-agent.ts"],"sourcesContent":["import { query } from \"@anthropic-ai/claude-agent-sdk\";\nimport path from \"path\";\n\nconst DEFAULT_PROXY_URL = \"https://api.hexclave.com/api/latest/integrations/ai-proxy\";\nconst ANTHROPIC_PROXY_BASE_URL: string = process.env.STACK_CLAUDE_PROXY_URL ?? DEFAULT_PROXY_URL;\n\nexport type ClaudeAgentToolName = \"Read\" | \"Write\" | \"Edit\" | \"MultiEdit\" | \"NotebookEdit\" | \"Bash\" | \"Glob\" | \"Grep\";\n\ntype ClaudeAgentHookResult = { continue: true } | {\n hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n permissionDecisionReason: string,\n },\n};\n\nexport type ClaudeAgentPreToolUseInput = {\n hook_event_name: \"PreToolUse\",\n tool_name: string,\n tool_input: unknown,\n};\n\nexport type RunClaudeAgentOptions = {\n prompt: string,\n cwd: string,\n allowedTools: ClaudeAgentToolName[],\n timeoutMs?: number,\n strictIsolation?: boolean,\n stderr?: (data: string) => void,\n onMessage?: (message: unknown) => void,\n onPreToolUse?: (input: ClaudeAgentPreToolUseInput) => Promise<ClaudeAgentHookResult> | ClaudeAgentHookResult,\n};\n\nexport type RunClaudeAgentResult = {\n resultText: string,\n};\n\nexport const CONFIG_AGENT_FILE_TOOLS = [\"Read\", \"Write\", \"Edit\", \"Glob\", \"Grep\"] as const;\nexport const CONFIG_AGENT_REPO_TOOLS = [...CONFIG_AGENT_FILE_TOOLS, \"Bash\"] as const;\n\ntype ConfigAgentPromptTarget =\n | {\n mode: \"complete\",\n completeConfig: Record<string, unknown>,\n }\n | {\n mode: \"partial\",\n changes: Array<{ path: string, value: unknown }>,\n };\n\nexport type ConfigAgentPromptScope =\n | {\n mode: \"known-file\",\n configFileName: string,\n }\n | {\n mode: \"repo\",\n };\n\nfunction buildConfigAgentPrompt(options: {\n scope: ConfigAgentPromptScope,\n target: ConfigAgentPromptTarget,\n commandPolicy: string,\n}): string {\n const targetSection = options.target.mode === \"complete\"\n ? `The exported config must end up deep-equal to this JSON value:\\n\\n${JSON.stringify(options.target.completeConfig, null, 2)}`\n : `Apply EXACTLY these config changes. Paths use dot notation, so \\`a.b.c\\` refers to \\`config.a.b.c\\`:\\n\\n${options.target.changes.map(({ path, value }) => `- ${JSON.stringify(path)}: set to ${JSON.stringify(value)}`).join(\"\\n\")}`;\n const scopeSection = options.scope.mode === \"known-file\"\n ? `Config file: ${JSON.stringify(options.scope.configFileName)} in the current working directory.`\n : \"Current working directory: the repository root. Find the Hexclave / Stack Auth config file. It is usually a `*.config.ts` file exporting `config`, often wrapped in `defineHexclaveConfig(...)` or a similar helper.\";\n\n return `You are updating a Hexclave / Stack Auth configuration file.\n\n${scopeSection}\n\n${targetSection}\n\nRules:\n- Keep the file valid: it must still export \\`config\\`.\n- Preserve the existing authoring style where possible: imports, comments, helper wrappers, file header comments, and formatting.\n- If the config currently exports the placeholder string \"show-onboarding\" or is otherwise a stub, replace it with a real typed config object.\n- If a config value is conventionally sourced from an imported external file, you may keep that indirection as long as the evaluated config matches the requested value.\n- Do not touch unrelated files or application code.\n- ${options.commandPolicy}\n- Make the edits, then stop.`;\n}\n\nexport function buildCompleteConfigAgentPrompt(options: {\n scope: ConfigAgentPromptScope,\n completeConfig: Record<string, unknown>,\n commandPolicy: string,\n}): string {\n return buildConfigAgentPrompt({\n scope: options.scope,\n target: {\n mode: \"complete\",\n completeConfig: options.completeConfig,\n },\n commandPolicy: options.commandPolicy,\n });\n}\n\nexport function buildPartialConfigAgentPrompt(options: {\n configFileName: string,\n changes: Array<{ path: string, value: unknown }>,\n commandPolicy: string,\n}): string {\n return buildConfigAgentPrompt({\n scope: {\n mode: \"known-file\",\n configFileName: options.configFileName,\n },\n target: {\n mode: \"partial\",\n changes: options.changes,\n },\n commandPolicy: options.commandPolicy,\n });\n}\n\nexport class ClaudeAgentTimeoutError extends Error {\n constructor(timeoutMs?: number) {\n super(`Claude agent timed out${timeoutMs == null ? \"\" : ` after ${timeoutMs}ms`}.`);\n this.name = \"ClaudeAgentTimeoutError\";\n }\n}\n\nexport class ClaudeAgentFailureError extends Error {\n constructor(subtype: string) {\n super(`Claude agent failed (${subtype}).`);\n this.name = \"ClaudeAgentFailureError\";\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === \"AbortError\";\n}\n\nexport function stripClaudeCodeEnv(): Record<string, string | undefined> {\n const env = { ...process.env };\n // CLAUDECODE must be unset for nested agents; ANTHROPIC_API_KEY must be non-empty (proxy ignores it).\n delete env.CLAUDECODE;\n return env;\n}\n\nexport async function runHeadlessClaudeAgent(options: RunClaudeAgentOptions): Promise<RunClaudeAgentResult> {\n const abortController = new AbortController();\n const timeout = options.timeoutMs == null ? null : setTimeout(() => abortController.abort(), options.timeoutMs);\n let sawResult = false;\n let resultText = \"\";\n const onPreToolUse = options.onPreToolUse;\n try {\n for await (const message of query({\n prompt: options.prompt,\n options: {\n model: \"anthropic/claude-haiku-4.5\",\n ...(options.strictIsolation === true ? {\n settingSources: [],\n strictMcpConfig: true,\n } : {}),\n ...(onPreToolUse == null ? {} : {\n hooks: {\n PreToolUse: [{\n hooks: [async (input) => {\n if (input.hook_event_name !== \"PreToolUse\") {\n return { continue: true };\n }\n return await onPreToolUse(input);\n }],\n }],\n },\n }),\n allowedTools: options.allowedTools,\n permissionMode: \"dontAsk\",\n cwd: options.cwd,\n abortController,\n env: {\n ...stripClaudeCodeEnv(),\n ...(options.strictIsolation === true ? { CLAUDE_CODE_DISABLE_AUTO_MEMORY: \"1\" } : {}),\n ANTHROPIC_BASE_URL: ANTHROPIC_PROXY_BASE_URL,\n ANTHROPIC_API_KEY: \"stack-auth-proxy\",\n },\n stderr: options.stderr,\n },\n })) {\n options.onMessage?.(message);\n if (message.type === \"result\") {\n if (\"result\" in message) {\n sawResult = true;\n resultText = message.result;\n } else {\n throw new ClaudeAgentFailureError(message.subtype);\n }\n }\n }\n } catch (error) {\n if (abortController.signal.aborted && isAbortError(error)) {\n throw new ClaudeAgentTimeoutError(options.timeoutMs ?? undefined);\n }\n throw error;\n } finally {\n if (timeout != null) {\n clearTimeout(timeout);\n }\n }\n if (!sawResult) {\n throw new Error(\"Claude agent ended without reporting a result.\");\n }\n return { resultText };\n}\n\n// Only the tools actually in the agent's allowedTools list; MultiEdit and\n// NotebookEdit are intentionally excluded from the agent's tool set.\nconst FILE_MUTATING_TOOLS = new Set([\"Write\", \"Edit\"]);\n\nfunction hasStringFilePath(input: unknown): input is { file_path: string } {\n return typeof input === \"object\" && input !== null && \"file_path\" in input && typeof input.file_path === \"string\";\n}\n\nexport function getToolWriteTargetPath(toolName: string, toolInput: unknown, cwd: string): string | null {\n if (!FILE_MUTATING_TOOLS.has(toolName) || !hasStringFilePath(toolInput)) {\n return null;\n }\n return path.isAbsolute(toolInput.file_path) ? toolInput.file_path : path.resolve(cwd, toolInput.file_path);\n}\n\nexport function isPathInsideDir(dir: string, target: string): boolean {\n const relative = path.relative(path.resolve(dir), path.resolve(target));\n return relative === \"\" || (!relative.startsWith(\"..\") && !path.isAbsolute(relative));\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Config } from "@hexclave/shared/dist/config/format";
|
|
2
|
+
|
|
3
|
+
//#region src/config-file.d.ts
|
|
4
|
+
declare function sha256String(value: string): string;
|
|
5
|
+
declare function resolveConfigFilePath(inputPath: string): string;
|
|
6
|
+
declare function ensureConfigFileExists(configFilePath: string): void;
|
|
7
|
+
declare function readConfigObject(configFilePath: string): Promise<Config>;
|
|
8
|
+
declare function readConfigFile(configFilePath: string): Promise<{
|
|
9
|
+
config: Config;
|
|
10
|
+
showOnboarding: boolean;
|
|
11
|
+
}>;
|
|
12
|
+
declare function replaceConfigObject(configFilePath: string, config: Config): Promise<void>;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { ensureConfigFileExists, readConfigFile, readConfigObject, replaceConfigObject, resolveConfigFilePath, sha256String };
|
|
15
|
+
//# sourceMappingURL=config-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-file.d.ts","names":[],"sources":["../../src/config-file.ts"],"mappings":";;;iBAUgB,YAAA,CAAa,KAAA;AAAA,iBAIb,qBAAA,CAAsB,SAAA;AAAA,iBAkBtB,sBAAA,CAAuB,cAAA;AAAA,iBAMjB,gBAAA,CAAiB,cAAA,WAAyB,OAAA,CAAQ,MAAA;AAAA,iBAIlD,cAAA,CAAe,cAAA,WAAyB,OAAA;EAAU,MAAA,EAAQ,MAAA;EAAQ,cAAA;AAAA;AAAA,iBAuClE,mBAAA,CAAoB,cAAA,UAAwB,MAAA,EAAQ,MAAA,GAAS,OAAA"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { showOnboardingHexclaveConfigValue } from "@hexclave/shared/dist/config-authoring";
|
|
3
|
+
import { ConfigFileEvalError, detectImportPackageFromDir, evalConfigFileContent } from "@hexclave/shared/dist/config-eval";
|
|
4
|
+
import { renderConfigFileContent } from "@hexclave/shared/dist/config-rendering";
|
|
5
|
+
import { isValidConfig } from "@hexclave/shared/dist/config/format";
|
|
6
|
+
import { captureError } from "@hexclave/shared/dist/utils/errors";
|
|
7
|
+
import { createHash } from "crypto";
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "fs";
|
|
9
|
+
|
|
10
|
+
//#region src/config-file.ts
|
|
11
|
+
function sha256String(value) {
|
|
12
|
+
return createHash("sha256").update(value).digest("hex");
|
|
13
|
+
}
|
|
14
|
+
function resolveConfigFilePath(inputPath) {
|
|
15
|
+
const resolved = path.resolve(inputPath);
|
|
16
|
+
if (/\.(ts|js|mjs|cjs)$/i.test(resolved)) return resolved;
|
|
17
|
+
const hexclaveCandidate = path.join(resolved, "hexclave.config.ts");
|
|
18
|
+
const legacyCandidate = path.join(resolved, "stack.config.ts");
|
|
19
|
+
if (existsSync(hexclaveCandidate)) return hexclaveCandidate;
|
|
20
|
+
if (existsSync(legacyCandidate)) return legacyCandidate;
|
|
21
|
+
return hexclaveCandidate;
|
|
22
|
+
}
|
|
23
|
+
function ensureConfigFileExists(configFilePath) {
|
|
24
|
+
if (existsSync(configFilePath)) return;
|
|
25
|
+
mkdirSync(path.dirname(configFilePath), { recursive: true });
|
|
26
|
+
renderConfigObjectToFile(configFilePath, {});
|
|
27
|
+
}
|
|
28
|
+
async function readConfigObject(configFilePath) {
|
|
29
|
+
return (await readConfigFile(configFilePath)).config;
|
|
30
|
+
}
|
|
31
|
+
async function readConfigFile(configFilePath) {
|
|
32
|
+
ensureConfigFileExists(configFilePath);
|
|
33
|
+
const content = readFileSync(configFilePath, "utf-8");
|
|
34
|
+
if (content.trim() === "") return {
|
|
35
|
+
config: {},
|
|
36
|
+
showOnboarding: false
|
|
37
|
+
};
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = evalConfigFileContent(content, configFilePath);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error instanceof ConfigFileEvalError) throw new Error(`Invalid config in ${configFilePath}.`);
|
|
43
|
+
captureError("shared-backend/readConfigFile", error);
|
|
44
|
+
throw new Error(`Failed to load config file ${configFilePath}.`);
|
|
45
|
+
}
|
|
46
|
+
if (parsed === showOnboardingHexclaveConfigValue) return {
|
|
47
|
+
config: {},
|
|
48
|
+
showOnboarding: true
|
|
49
|
+
};
|
|
50
|
+
if (!isValidConfig(parsed)) throw new Error(`Invalid config in ${configFilePath}.`);
|
|
51
|
+
return {
|
|
52
|
+
config: parsed,
|
|
53
|
+
showOnboarding: false
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function replaceConfigObject(configFilePath, config) {
|
|
57
|
+
renderConfigObjectToFile(configFilePath, config);
|
|
58
|
+
}
|
|
59
|
+
function renderConfigObjectToString(configFilePath, config) {
|
|
60
|
+
return renderConfigFileContent(config, detectImportPackageFromDir(path.dirname(configFilePath)));
|
|
61
|
+
}
|
|
62
|
+
function writeFileAtomic(configFilePath, content) {
|
|
63
|
+
const dir = path.dirname(configFilePath);
|
|
64
|
+
mkdirSync(dir, { recursive: true });
|
|
65
|
+
const tempPath = path.join(dir, `.stack.config.${Math.random().toString(36).slice(2)}.tmp`);
|
|
66
|
+
writeFileSync(tempPath, content, "utf-8");
|
|
67
|
+
try {
|
|
68
|
+
renameSync(tempPath, configFilePath);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
try {
|
|
71
|
+
rmSync(tempPath);
|
|
72
|
+
} catch {}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function renderConfigObjectToFile(configFilePath, config) {
|
|
77
|
+
writeFileAtomic(configFilePath, renderConfigObjectToString(configFilePath, config));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
export { ensureConfigFileExists, readConfigFile, readConfigObject, replaceConfigObject, resolveConfigFilePath, sha256String };
|
|
82
|
+
//# sourceMappingURL=config-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-file.js","names":[],"sources":["../../src/config-file.ts"],"sourcesContent":["import { showOnboardingHexclaveConfigValue } from \"@hexclave/shared/dist/config-authoring\";\nimport { ConfigFileEvalError, detectImportPackageFromDir, evalConfigFileContent } from \"@hexclave/shared/dist/config-eval\";\nimport { renderConfigFileContent } from \"@hexclave/shared/dist/config-rendering\";\nimport type { Config } from \"@hexclave/shared/dist/config/format\";\nimport { isValidConfig } from \"@hexclave/shared/dist/config/format\";\nimport { captureError } from \"@hexclave/shared/dist/utils/errors\";\nimport { createHash } from \"crypto\";\nimport { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"fs\";\nimport path from \"path\";\n\nexport function sha256String(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\");\n}\n\nexport function resolveConfigFilePath(inputPath: string): string {\n const resolved = path.resolve(inputPath);\n const looksLikeConfigFile = /\\.(ts|js|mjs|cjs)$/i.test(resolved);\n if (looksLikeConfigFile) {\n return resolved;\n }\n // Prefer hexclave.config.ts, fall back to stack.config.ts, default to the new name.\n const hexclaveCandidate = path.join(resolved, \"hexclave.config.ts\");\n const legacyCandidate = path.join(resolved, \"stack.config.ts\");\n if (existsSync(hexclaveCandidate)) {\n return hexclaveCandidate;\n }\n if (existsSync(legacyCandidate)) {\n return legacyCandidate;\n }\n return hexclaveCandidate;\n}\n\nexport function ensureConfigFileExists(configFilePath: string): void {\n if (existsSync(configFilePath)) return;\n mkdirSync(path.dirname(configFilePath), { recursive: true });\n renderConfigObjectToFile(configFilePath, {});\n}\n\nexport async function readConfigObject(configFilePath: string): Promise<Config> {\n return (await readConfigFile(configFilePath)).config;\n}\n\nexport async function readConfigFile(configFilePath: string): Promise<{ config: Config, showOnboarding: boolean }> {\n ensureConfigFileExists(configFilePath);\n const content = readFileSync(configFilePath, \"utf-8\");\n if (content.trim() === \"\") {\n return {\n config: {},\n showOnboarding: false,\n };\n }\n\n // ConfigFileEvalError => \"Invalid config\"; any other loader error is captured\n // for diagnostics but not attached as `cause` (the dashboard renders causes\n // recursively and would leak framework internals).\n let parsed: ReturnType<typeof evalConfigFileContent>;\n try {\n parsed = evalConfigFileContent(content, configFilePath);\n } catch (error) {\n if (error instanceof ConfigFileEvalError) {\n throw new Error(`Invalid config in ${configFilePath}.`);\n }\n captureError(\"shared-backend/readConfigFile\", error);\n throw new Error(`Failed to load config file ${configFilePath}.`);\n }\n\n if (parsed === showOnboardingHexclaveConfigValue) {\n return {\n config: {},\n showOnboarding: true,\n };\n }\n if (!isValidConfig(parsed)) {\n throw new Error(`Invalid config in ${configFilePath}.`);\n }\n return {\n config: parsed,\n showOnboarding: false,\n };\n}\n\nexport async function replaceConfigObject(configFilePath: string, config: Config): Promise<void> {\n renderConfigObjectToFile(configFilePath, config);\n}\n\nfunction renderConfigObjectToString(configFilePath: string, config: Config): string {\n const importPackage = detectImportPackageFromDir(path.dirname(configFilePath));\n return renderConfigFileContent(config, importPackage);\n}\n\nfunction writeFileAtomic(configFilePath: string, content: string): void {\n const dir = path.dirname(configFilePath);\n mkdirSync(dir, { recursive: true });\n const tempPath = path.join(dir, `.stack.config.${Math.random().toString(36).slice(2)}.tmp`);\n writeFileSync(tempPath, content, \"utf-8\");\n try {\n renameSync(tempPath, configFilePath);\n } catch (error) {\n try {\n rmSync(tempPath);\n } catch { /* best-effort cleanup */ }\n throw error;\n }\n}\n\nfunction renderConfigObjectToFile(configFilePath: string, config: Config): void {\n writeFileAtomic(configFilePath, renderConfigObjectToString(configFilePath, config));\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAgB,aAAa,OAAuB;AAClD,QAAO,WAAW,SAAS,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;;AAGzD,SAAgB,sBAAsB,WAA2B;CAC/D,MAAM,WAAW,KAAK,QAAQ,UAAU;AAExC,KAD4B,sBAAsB,KAAK,SAAS,CAE9D,QAAO;CAGT,MAAM,oBAAoB,KAAK,KAAK,UAAU,qBAAqB;CACnE,MAAM,kBAAkB,KAAK,KAAK,UAAU,kBAAkB;AAC9D,KAAI,WAAW,kBAAkB,CAC/B,QAAO;AAET,KAAI,WAAW,gBAAgB,CAC7B,QAAO;AAET,QAAO;;AAGT,SAAgB,uBAAuB,gBAA8B;AACnE,KAAI,WAAW,eAAe,CAAE;AAChC,WAAU,KAAK,QAAQ,eAAe,EAAE,EAAE,WAAW,MAAM,CAAC;AAC5D,0BAAyB,gBAAgB,EAAE,CAAC;;AAG9C,eAAsB,iBAAiB,gBAAyC;AAC9E,SAAQ,MAAM,eAAe,eAAe,EAAE;;AAGhD,eAAsB,eAAe,gBAA8E;AACjH,wBAAuB,eAAe;CACtC,MAAM,UAAU,aAAa,gBAAgB,QAAQ;AACrD,KAAI,QAAQ,MAAM,KAAK,GACrB,QAAO;EACL,QAAQ,EAAE;EACV,gBAAgB;EACjB;CAMH,IAAI;AACJ,KAAI;AACF,WAAS,sBAAsB,SAAS,eAAe;UAChD,OAAO;AACd,MAAI,iBAAiB,oBACnB,OAAM,IAAI,MAAM,qBAAqB,eAAe,GAAG;AAEzD,eAAa,iCAAiC,MAAM;AACpD,QAAM,IAAI,MAAM,8BAA8B,eAAe,GAAG;;AAGlE,KAAI,WAAW,kCACb,QAAO;EACL,QAAQ,EAAE;EACV,gBAAgB;EACjB;AAEH,KAAI,CAAC,cAAc,OAAO,CACxB,OAAM,IAAI,MAAM,qBAAqB,eAAe,GAAG;AAEzD,QAAO;EACL,QAAQ;EACR,gBAAgB;EACjB;;AAGH,eAAsB,oBAAoB,gBAAwB,QAA+B;AAC/F,0BAAyB,gBAAgB,OAAO;;AAGlD,SAAS,2BAA2B,gBAAwB,QAAwB;AAElF,QAAO,wBAAwB,QADT,2BAA2B,KAAK,QAAQ,eAAe,CAAC,CACzB;;AAGvD,SAAS,gBAAgB,gBAAwB,SAAuB;CACtE,MAAM,MAAM,KAAK,QAAQ,eAAe;AACxC,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CACnC,MAAM,WAAW,KAAK,KAAK,KAAK,iBAAiB,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM;AAC3F,eAAc,UAAU,SAAS,QAAQ;AACzC,KAAI;AACF,aAAW,UAAU,eAAe;UAC7B,OAAO;AACd,MAAI;AACF,UAAO,SAAS;UACV;AACR,QAAM;;;AAIV,SAAS,yBAAyB,gBAAwB,QAAsB;AAC9E,iBAAgB,gBAAgB,2BAA2B,gBAAgB,OAAO,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Config } from "@hexclave/shared/dist/config/format";
|
|
2
|
+
|
|
3
|
+
//#region src/config-updater.d.ts
|
|
4
|
+
declare function updateConfigObject(configFilePath: string, configUpdate: Config): Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { updateConfigObject };
|
|
7
|
+
//# sourceMappingURL=config-updater.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-updater.d.ts","names":[],"sources":["../../src/config-updater.ts"],"mappings":";;;iBA+DsB,kBAAA,CAAmB,cAAA,UAAwB,YAAA,EAAc,MAAA,GAAS,OAAA"}
|