@hexclave/shared-backend 1.0.36 → 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.
@@ -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"}
@@ -5,7 +5,55 @@ let path = require("path");
5
5
  path = require_chunk.__toESM(path);
6
6
 
7
7
  //#region src/config-agent.ts
8
- const ANTHROPIC_PROXY_BASE_URL = process.env.STACK_CLAUDE_PROXY_URL ?? "https://api.hexclave.com/api/v1/integrations/ai-proxy";
8
+ const ANTHROPIC_PROXY_BASE_URL = process.env.STACK_CLAUDE_PROXY_URL ?? "https://api.hexclave.com/api/latest/integrations/ai-proxy";
9
+ const CONFIG_AGENT_FILE_TOOLS = [
10
+ "Read",
11
+ "Write",
12
+ "Edit",
13
+ "Glob",
14
+ "Grep"
15
+ ];
16
+ const CONFIG_AGENT_REPO_TOOLS = [...CONFIG_AGENT_FILE_TOOLS, "Bash"];
17
+ function buildConfigAgentPrompt(options) {
18
+ 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: path$2, value }) => `- ${JSON.stringify(path$2)}: set to ${JSON.stringify(value)}`).join("\n")}`;
19
+ return `You are updating a Hexclave / Stack Auth configuration file.
20
+
21
+ ${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."}
22
+
23
+ ${targetSection}
24
+
25
+ Rules:
26
+ - Keep the file valid: it must still export \`config\`.
27
+ - Preserve the existing authoring style where possible: imports, comments, helper wrappers, file header comments, and formatting.
28
+ - If the config currently exports the placeholder string "show-onboarding" or is otherwise a stub, replace it with a real typed config object.
29
+ - 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.
30
+ - Do not touch unrelated files or application code.
31
+ - ${options.commandPolicy}
32
+ - Make the edits, then stop.`;
33
+ }
34
+ function buildCompleteConfigAgentPrompt(options) {
35
+ return buildConfigAgentPrompt({
36
+ scope: options.scope,
37
+ target: {
38
+ mode: "complete",
39
+ completeConfig: options.completeConfig
40
+ },
41
+ commandPolicy: options.commandPolicy
42
+ });
43
+ }
44
+ function buildPartialConfigAgentPrompt(options) {
45
+ return buildConfigAgentPrompt({
46
+ scope: {
47
+ mode: "known-file",
48
+ configFileName: options.configFileName
49
+ },
50
+ target: {
51
+ mode: "partial",
52
+ changes: options.changes
53
+ },
54
+ commandPolicy: options.commandPolicy
55
+ });
56
+ }
9
57
  var ClaudeAgentTimeoutError = class extends Error {
10
58
  constructor(timeoutMs) {
11
59
  super(`Claude agent timed out${timeoutMs == null ? "" : ` after ${timeoutMs}ms`}.`);
@@ -36,7 +84,7 @@ async function runHeadlessClaudeAgent(options) {
36
84
  for await (const message of (0, _anthropic_ai_claude_agent_sdk.query)({
37
85
  prompt: options.prompt,
38
86
  options: {
39
- model: "nvidia/nemotron-3-super-120b-a12b:nitro",
87
+ model: "anthropic/claude-haiku-4.5",
40
88
  ...options.strictIsolation === true ? {
41
89
  settingSources: [],
42
90
  strictMcpConfig: true
@@ -87,8 +135,12 @@ function isPathInsideDir(dir, target) {
87
135
  }
88
136
 
89
137
  //#endregion
138
+ exports.CONFIG_AGENT_FILE_TOOLS = CONFIG_AGENT_FILE_TOOLS;
139
+ exports.CONFIG_AGENT_REPO_TOOLS = CONFIG_AGENT_REPO_TOOLS;
90
140
  exports.ClaudeAgentFailureError = ClaudeAgentFailureError;
91
141
  exports.ClaudeAgentTimeoutError = ClaudeAgentTimeoutError;
142
+ exports.buildCompleteConfigAgentPrompt = buildCompleteConfigAgentPrompt;
143
+ exports.buildPartialConfigAgentPrompt = buildPartialConfigAgentPrompt;
92
144
  exports.getToolWriteTargetPath = getToolWriteTargetPath;
93
145
  exports.isPathInsideDir = isPathInsideDir;
94
146
  exports.runHeadlessClaudeAgent = runHeadlessClaudeAgent;
@@ -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,qDAAiB;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,aAAK,WAAW,UAAU,UAAU,GAAG,UAAU,YAAY,aAAK,QAAQ,KAAK,UAAU,UAAU;;AAG5G,SAAgB,gBAAgB,KAAa,QAAyB;CACpE,MAAM,WAAW,aAAK,SAAS,aAAK,QAAQ,IAAI,EAAE,aAAK,QAAQ,OAAO,CAAC;AACvE,QAAO,aAAa,MAAO,CAAC,SAAS,WAAW,KAAK,IAAI,CAAC,aAAK,WAAW,SAAS"}
1
+ {"version":3,"file":"config-agent.js","names":["path"],"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,90 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_chunk = require('./chunk-BE-pF4vm.js');
3
+ let path = require("path");
4
+ path = require_chunk.__toESM(path);
5
+ let _hexclave_shared_dist_config_authoring = require("@hexclave/shared/dist/config-authoring");
6
+ let _hexclave_shared_dist_config_eval = require("@hexclave/shared/dist/config-eval");
7
+ let _hexclave_shared_dist_config_rendering = require("@hexclave/shared/dist/config-rendering");
8
+ let _hexclave_shared_dist_config_format = require("@hexclave/shared/dist/config/format");
9
+ let _hexclave_shared_dist_utils_errors = require("@hexclave/shared/dist/utils/errors");
10
+ let crypto = require("crypto");
11
+ let fs = require("fs");
12
+
13
+ //#region src/config-file.ts
14
+ function sha256String(value) {
15
+ return (0, crypto.createHash)("sha256").update(value).digest("hex");
16
+ }
17
+ function resolveConfigFilePath(inputPath) {
18
+ const resolved = path.default.resolve(inputPath);
19
+ if (/\.(ts|js|mjs|cjs)$/i.test(resolved)) return resolved;
20
+ const hexclaveCandidate = path.default.join(resolved, "hexclave.config.ts");
21
+ const legacyCandidate = path.default.join(resolved, "stack.config.ts");
22
+ if ((0, fs.existsSync)(hexclaveCandidate)) return hexclaveCandidate;
23
+ if ((0, fs.existsSync)(legacyCandidate)) return legacyCandidate;
24
+ return hexclaveCandidate;
25
+ }
26
+ function ensureConfigFileExists(configFilePath) {
27
+ if ((0, fs.existsSync)(configFilePath)) return;
28
+ (0, fs.mkdirSync)(path.default.dirname(configFilePath), { recursive: true });
29
+ renderConfigObjectToFile(configFilePath, {});
30
+ }
31
+ async function readConfigObject(configFilePath) {
32
+ return (await readConfigFile(configFilePath)).config;
33
+ }
34
+ async function readConfigFile(configFilePath) {
35
+ ensureConfigFileExists(configFilePath);
36
+ const content = (0, fs.readFileSync)(configFilePath, "utf-8");
37
+ if (content.trim() === "") return {
38
+ config: {},
39
+ showOnboarding: false
40
+ };
41
+ let parsed;
42
+ try {
43
+ parsed = (0, _hexclave_shared_dist_config_eval.evalConfigFileContent)(content, configFilePath);
44
+ } catch (error) {
45
+ if (error instanceof _hexclave_shared_dist_config_eval.ConfigFileEvalError) throw new Error(`Invalid config in ${configFilePath}.`);
46
+ (0, _hexclave_shared_dist_utils_errors.captureError)("shared-backend/readConfigFile", error);
47
+ throw new Error(`Failed to load config file ${configFilePath}.`);
48
+ }
49
+ if (parsed === _hexclave_shared_dist_config_authoring.showOnboardingHexclaveConfigValue) return {
50
+ config: {},
51
+ showOnboarding: true
52
+ };
53
+ if (!(0, _hexclave_shared_dist_config_format.isValidConfig)(parsed)) throw new Error(`Invalid config in ${configFilePath}.`);
54
+ return {
55
+ config: parsed,
56
+ showOnboarding: false
57
+ };
58
+ }
59
+ async function replaceConfigObject(configFilePath, config) {
60
+ renderConfigObjectToFile(configFilePath, config);
61
+ }
62
+ function renderConfigObjectToString(configFilePath, config) {
63
+ return (0, _hexclave_shared_dist_config_rendering.renderConfigFileContent)(config, (0, _hexclave_shared_dist_config_eval.detectImportPackageFromDir)(path.default.dirname(configFilePath)));
64
+ }
65
+ function writeFileAtomic(configFilePath, content) {
66
+ const dir = path.default.dirname(configFilePath);
67
+ (0, fs.mkdirSync)(dir, { recursive: true });
68
+ const tempPath = path.default.join(dir, `.stack.config.${Math.random().toString(36).slice(2)}.tmp`);
69
+ (0, fs.writeFileSync)(tempPath, content, "utf-8");
70
+ try {
71
+ (0, fs.renameSync)(tempPath, configFilePath);
72
+ } catch (error) {
73
+ try {
74
+ (0, fs.rmSync)(tempPath);
75
+ } catch {}
76
+ throw error;
77
+ }
78
+ }
79
+ function renderConfigObjectToFile(configFilePath, config) {
80
+ writeFileAtomic(configFilePath, renderConfigObjectToString(configFilePath, config));
81
+ }
82
+
83
+ //#endregion
84
+ exports.ensureConfigFileExists = ensureConfigFileExists;
85
+ exports.readConfigFile = readConfigFile;
86
+ exports.readConfigObject = readConfigObject;
87
+ exports.replaceConfigObject = replaceConfigObject;
88
+ exports.resolveConfigFilePath = resolveConfigFilePath;
89
+ exports.sha256String = sha256String;
90
+ //# sourceMappingURL=config-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-file.js","names":["ConfigFileEvalError","showOnboardingHexclaveConfigValue"],"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,+BAAkB,SAAS,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;;AAGzD,SAAgB,sBAAsB,WAA2B;CAC/D,MAAM,WAAW,aAAK,QAAQ,UAAU;AAExC,KAD4B,sBAAsB,KAAK,SAAS,CAE9D,QAAO;CAGT,MAAM,oBAAoB,aAAK,KAAK,UAAU,qBAAqB;CACnE,MAAM,kBAAkB,aAAK,KAAK,UAAU,kBAAkB;AAC9D,wBAAe,kBAAkB,CAC/B,QAAO;AAET,wBAAe,gBAAgB,CAC7B,QAAO;AAET,QAAO;;AAGT,SAAgB,uBAAuB,gBAA8B;AACnE,wBAAe,eAAe,CAAE;AAChC,mBAAU,aAAK,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,+BAAuB,gBAAgB,QAAQ;AACrD,KAAI,QAAQ,MAAM,KAAK,GACrB,QAAO;EACL,QAAQ,EAAE;EACV,gBAAgB;EACjB;CAMH,IAAI;AACJ,KAAI;AACF,wEAA+B,SAAS,eAAe;UAChD,OAAO;AACd,MAAI,iBAAiBA,sDACnB,OAAM,IAAI,MAAM,qBAAqB,eAAe,GAAG;AAEzD,uDAAa,iCAAiC,MAAM;AACpD,QAAM,IAAI,MAAM,8BAA8B,eAAe,GAAG;;AAGlE,KAAI,WAAWC,yEACb,QAAO;EACL,QAAQ,EAAE;EACV,gBAAgB;EACjB;AAEH,KAAI,wDAAe,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,4EAA+B,0EADkB,aAAK,QAAQ,eAAe,CAAC,CACzB;;AAGvD,SAAS,gBAAgB,gBAAwB,SAAuB;CACtE,MAAM,MAAM,aAAK,QAAQ,eAAe;AACxC,mBAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CACnC,MAAM,WAAW,aAAK,KAAK,KAAK,iBAAiB,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,EAAE,CAAC,MAAM;AAC3F,uBAAc,UAAU,SAAS,QAAQ;AACzC,KAAI;AACF,qBAAW,UAAU,eAAe;UAC7B,OAAO;AACd,MAAI;AACF,kBAAO,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"}
@@ -0,0 +1,330 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_chunk = require('./chunk-BE-pF4vm.js');
3
+ let __config_agent_js = require("./config-agent.js");
4
+ let path = require("path");
5
+ path = require_chunk.__toESM(path);
6
+ let _hexclave_shared_dist_config_eval = require("@hexclave/shared/dist/config-eval");
7
+ let _hexclave_shared_dist_config_format = require("@hexclave/shared/dist/config/format");
8
+ let fs = require("fs");
9
+ let __config_file_js = require("./config-file.js");
10
+
11
+ //#region src/config-updater.ts
12
+ const LOG_PREFIX = "[Hexclave config updater]";
13
+ const DEFAULT_AGENT_TIMEOUT_MS = 12e4;
14
+ const CONFIG_UPDATE_LOG_PATH_LIMIT = 40;
15
+ const AGENT_OUTPUT_LOG_MAX_LENGTH = 2e4;
16
+ function formatConfigUpdaterErrorForLog(error) {
17
+ if (error instanceof Error) return {
18
+ errorName: error.name,
19
+ errorMessage: error.message,
20
+ errorStack: error.stack
21
+ };
22
+ return { errorMessage: String(error) };
23
+ }
24
+ function configUpdatePathDetailsForLog(changes) {
25
+ const paths = changes.map(({ path: configPath }) => configPath).sort();
26
+ return {
27
+ configUpdatePathCount: paths.length,
28
+ configUpdatePaths: paths.slice(0, CONFIG_UPDATE_LOG_PATH_LIMIT),
29
+ configUpdatePathsTruncated: paths.length > CONFIG_UPDATE_LOG_PATH_LIMIT
30
+ };
31
+ }
32
+ function appendBoundedAgentOutput(current, chunk) {
33
+ const next = `${current}${chunk}`;
34
+ if (next.length <= AGENT_OUTPUT_LOG_MAX_LENGTH) return next;
35
+ return next.slice(next.length - AGENT_OUTPUT_LOG_MAX_LENGTH);
36
+ }
37
+ function stringifyAgentMessageForLog(message) {
38
+ try {
39
+ return `${JSON.stringify(message)}\n`;
40
+ } catch {
41
+ return `${String(message)}\n`;
42
+ }
43
+ }
44
+ function agentOutputDetailsForLog(agentStdout, agentStderr) {
45
+ return {
46
+ agentStdout,
47
+ agentStdoutTruncated: agentStdout.length >= AGENT_OUTPUT_LOG_MAX_LENGTH,
48
+ agentStderr,
49
+ agentStderrTruncated: agentStderr.length >= AGENT_OUTPUT_LOG_MAX_LENGTH
50
+ };
51
+ }
52
+ async function updateConfigObject(configFilePath, configUpdate) {
53
+ const startedAtMs = performance.now();
54
+ (0, __config_file_js.ensureConfigFileExists)(configFilePath);
55
+ const changes = flattenConfigUpdate(configUpdate);
56
+ if (changes.length === 0) {
57
+ console.log(`${LOG_PREFIX} Skipping config update because it contains no changes`, { configFilePath });
58
+ return;
59
+ }
60
+ const updateLogDetails = {
61
+ configFilePath,
62
+ ...configUpdatePathDetailsForLog(changes)
63
+ };
64
+ console.log(`${LOG_PREFIX} Starting config file update`, updateLogDetails);
65
+ const content = (0, fs.readFileSync)(configFilePath, "utf-8");
66
+ console.log(`${LOG_PREFIX} Applying config update with agent-assisted rewrite`, {
67
+ ...updateLogDetails,
68
+ configDirectory: path.default.dirname(configFilePath)
69
+ });
70
+ const baselineConfig = await tryReadConfigForValidation(configFilePath);
71
+ const { snapshots, seen } = snapshotConfigFiles(configFilePath, content);
72
+ try {
73
+ await runConfigUpdateAgent({
74
+ prompt: buildConfigUpdatePrompt(path.default.basename(configFilePath), configUpdate, baselineConfig),
75
+ cwd: path.default.dirname(configFilePath),
76
+ onFileWillChange: (filePath) => captureSnapshotIfAbsent(snapshots, filePath, seen)
77
+ });
78
+ await validateAgentUpdate(configFilePath, baselineConfig, configUpdate);
79
+ } catch (error) {
80
+ console.warn(`${LOG_PREFIX} Config update failed; restoring files from snapshots`, {
81
+ ...updateLogDetails,
82
+ snapshotCount: snapshots.length,
83
+ elapsedMs: Math.round(performance.now() - startedAtMs),
84
+ ...formatConfigUpdaterErrorForLog(error)
85
+ });
86
+ try {
87
+ restoreConfigFiles(snapshots);
88
+ console.warn(`${LOG_PREFIX} Restored files after failed config update`, {
89
+ ...updateLogDetails,
90
+ snapshotCount: snapshots.length
91
+ });
92
+ } catch (restoreError) {
93
+ 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`, {
94
+ configFilePath,
95
+ ...formatConfigUpdaterErrorForLog(restoreError)
96
+ });
97
+ }
98
+ throw error;
99
+ }
100
+ console.log(`${LOG_PREFIX} Finished config update with agent-assisted rewrite`, {
101
+ ...updateLogDetails,
102
+ elapsedMs: Math.round(performance.now() - startedAtMs),
103
+ snapshotCount: snapshots.length
104
+ });
105
+ }
106
+ async function runConfigUpdateAgent(options) {
107
+ const timeoutMs = parseAgentTimeoutMs();
108
+ const deniedOutOfBoundsWrites = /* @__PURE__ */ new Set();
109
+ const startedAtMs = performance.now();
110
+ let agentStdout = "";
111
+ let agentStderr = "";
112
+ console.log(`${LOG_PREFIX} Starting config update agent`, {
113
+ cwd: options.cwd,
114
+ timeoutMs
115
+ });
116
+ try {
117
+ await (0, __config_agent_js.runHeadlessClaudeAgent)({
118
+ prompt: options.prompt,
119
+ cwd: options.cwd,
120
+ allowedTools: [...__config_agent_js.CONFIG_AGENT_FILE_TOOLS],
121
+ strictIsolation: true,
122
+ timeoutMs,
123
+ stderr: (data) => {
124
+ agentStderr = appendBoundedAgentOutput(agentStderr, data);
125
+ console.warn(`${LOG_PREFIX} [agent] ${data}`);
126
+ },
127
+ onMessage: (message) => {
128
+ agentStdout = appendBoundedAgentOutput(agentStdout, stringifyAgentMessageForLog(message));
129
+ },
130
+ onPreToolUse: (input) => {
131
+ const target = (0, __config_agent_js.getToolWriteTargetPath)(input.tool_name, input.tool_input, options.cwd);
132
+ if (target == null) return { continue: true };
133
+ if (!(0, __config_agent_js.isPathInsideDir)(options.cwd, target)) {
134
+ deniedOutOfBoundsWrites.add(target);
135
+ return { hookSpecificOutput: {
136
+ hookEventName: "PreToolUse",
137
+ permissionDecision: "deny",
138
+ permissionDecisionReason: `Refusing to modify ${target}: config updates may only change files inside the config directory.`
139
+ } };
140
+ }
141
+ options.onFileWillChange?.(target);
142
+ return { continue: true };
143
+ }
144
+ });
145
+ } catch (error) {
146
+ if (error instanceof __config_agent_js.ClaudeAgentTimeoutError) {
147
+ console.warn(`${LOG_PREFIX} Config update agent timed out`, {
148
+ cwd: options.cwd,
149
+ timeoutMs,
150
+ elapsedMs: Math.round(performance.now() - startedAtMs),
151
+ ...formatConfigUpdaterErrorForLog(error),
152
+ ...agentOutputDetailsForLog(agentStdout, agentStderr)
153
+ });
154
+ throw new Error(`Config update agent timed out after ${timeoutMs}ms. It was unable to apply the config changes to the file.`);
155
+ }
156
+ if (error instanceof __config_agent_js.ClaudeAgentFailureError) {
157
+ console.warn(`${LOG_PREFIX} Config update agent failed`, {
158
+ cwd: options.cwd,
159
+ timeoutMs,
160
+ elapsedMs: Math.round(performance.now() - startedAtMs),
161
+ ...formatConfigUpdaterErrorForLog(error),
162
+ ...agentOutputDetailsForLog(agentStdout, agentStderr)
163
+ });
164
+ throw new Error(`${error.message} It was unable to apply the config changes to the file.`);
165
+ }
166
+ console.warn(`${LOG_PREFIX} Config update agent failed unexpectedly`, {
167
+ cwd: options.cwd,
168
+ timeoutMs,
169
+ elapsedMs: Math.round(performance.now() - startedAtMs),
170
+ ...formatConfigUpdaterErrorForLog(error),
171
+ ...agentOutputDetailsForLog(agentStdout, agentStderr)
172
+ });
173
+ throw error;
174
+ }
175
+ console.log(`${LOG_PREFIX} Finished config update agent`, {
176
+ cwd: options.cwd,
177
+ timeoutMs,
178
+ elapsedMs: Math.round(performance.now() - startedAtMs),
179
+ deniedOutOfBoundsWriteCount: deniedOutOfBoundsWrites.size
180
+ });
181
+ if (deniedOutOfBoundsWrites.size > 0) {
182
+ console.warn(`${LOG_PREFIX} Config update agent attempted out-of-bounds writes`, {
183
+ cwd: options.cwd,
184
+ deniedOutOfBoundsWriteCount: deniedOutOfBoundsWrites.size,
185
+ deniedOutOfBoundsWrites: [...deniedOutOfBoundsWrites]
186
+ });
187
+ 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.`);
188
+ }
189
+ }
190
+ function parseAgentTimeoutMs() {
191
+ const raw = process.env.STACK_CONFIG_UPDATE_AGENT_TIMEOUT_MS;
192
+ if (raw == null || raw.trim() === "") return DEFAULT_AGENT_TIMEOUT_MS;
193
+ const parsed = Number(raw);
194
+ if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`Invalid STACK_CONFIG_UPDATE_AGENT_TIMEOUT_MS: ${JSON.stringify(raw)}. Expected a positive number of milliseconds.`);
195
+ return parsed;
196
+ }
197
+ function captureSnapshotIfAbsent(snapshots, filePath, seen) {
198
+ const resolved = path.default.resolve(filePath);
199
+ if (seen.has(resolved)) return;
200
+ seen.add(resolved);
201
+ snapshots.push({
202
+ path: resolved,
203
+ content: (0, fs.existsSync)(resolved) ? (0, fs.readFileSync)(resolved, "utf-8") : null
204
+ });
205
+ }
206
+ function snapshotConfigFiles(configFilePath, configContent) {
207
+ const dir = path.default.dirname(configFilePath);
208
+ const resolvedConfig = path.default.resolve(configFilePath);
209
+ const snapshots = [{
210
+ path: resolvedConfig,
211
+ content: configContent
212
+ }];
213
+ const seen = new Set([resolvedConfig]);
214
+ for (const specifier of getRelativeImportSpecifiers(configContent)) {
215
+ const resolved = path.default.resolve(dir, specifier);
216
+ if (!(0, __config_agent_js.isPathInsideDir)(dir, resolved)) continue;
217
+ captureSnapshotIfAbsent(snapshots, resolved, seen);
218
+ }
219
+ return {
220
+ snapshots,
221
+ seen
222
+ };
223
+ }
224
+ function restoreConfigFiles(snapshots) {
225
+ const failures = [];
226
+ for (const { path: filePath, content } of snapshots) try {
227
+ if (content === null) {
228
+ if ((0, fs.existsSync)(filePath)) (0, fs.rmSync)(filePath);
229
+ } else (0, fs.writeFileSync)(filePath, content, "utf-8");
230
+ } catch (error) {
231
+ failures.push(`${filePath}: ${error instanceof Error ? error.message : String(error)}`);
232
+ }
233
+ if (failures.length > 0) throw new Error(`Failed to restore ${failures.length} file(s) during rollback: ${failures.join("; ")}`);
234
+ }
235
+ async function tryReadConfigForValidation(configFilePath) {
236
+ try {
237
+ return (await (0, __config_file_js.readConfigFile)(configFilePath)).config;
238
+ } catch (error) {
239
+ console.warn(`${LOG_PREFIX} Could not evaluate config for validation baseline; will fall back to a structural check`, {
240
+ configFilePath,
241
+ error: error instanceof Error ? error.message : String(error)
242
+ });
243
+ return null;
244
+ }
245
+ }
246
+ async function validateAgentUpdate(configFilePath, baselineConfig, configUpdate) {
247
+ if (baselineConfig != null) {
248
+ const target = canonicalizeConfig((0, _hexclave_shared_dist_config_format.override)(baselineConfig, configUpdate));
249
+ if (!configsEqual(canonicalizeConfig((await (0, __config_file_js.readConfigFile)(configFilePath)).config), target)) throw new Error(`Config update validation failed for ${configFilePath}: the updated file does not evaluate to the expected configuration.`);
250
+ return;
251
+ }
252
+ if (!configFileExportsConfig((0, fs.readFileSync)(configFilePath, "utf-8"), configFilePath)) throw new Error(`Config update validation failed for ${configFilePath}: the updated file no longer exports a valid \`config\`.`);
253
+ }
254
+ function configFileExportsConfig(content, configFilePath) {
255
+ try {
256
+ (0, _hexclave_shared_dist_config_eval.evalConfigFileContent)(content, configFilePath);
257
+ return true;
258
+ } catch {
259
+ return /\bexport\s+const\s+config\b/.test(content);
260
+ }
261
+ }
262
+ function getRelativeImportSpecifiers(content) {
263
+ const specifiers = [];
264
+ const importPattern = /\bimport\b(?:[^'"]*?\bfrom\s*)?["'](\.{1,2}\/[^"']+)["']/g;
265
+ let match;
266
+ while ((match = importPattern.exec(content)) !== null) specifiers.push(match[1]);
267
+ return specifiers;
268
+ }
269
+ function flattenConfigUpdate(update) {
270
+ const changes = [];
271
+ const walk = (prefix, obj) => {
272
+ for (const [key, value] of Object.entries(obj)) {
273
+ const fullPath = prefix === "" ? key : `${prefix}.${key}`;
274
+ if (value === void 0) continue;
275
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0) walk(fullPath, value);
276
+ else changes.push({
277
+ path: fullPath,
278
+ value
279
+ });
280
+ }
281
+ };
282
+ walk("", update);
283
+ return changes;
284
+ }
285
+ function buildConfigUpdatePrompt(configFileName, configUpdate, baselineConfig) {
286
+ const changes = flattenConfigUpdate(configUpdate);
287
+ const commandPolicy = "Do not run shell commands and do not create files other than what is required to apply the config changes.";
288
+ if (baselineConfig != null) return (0, __config_agent_js.buildCompleteConfigAgentPrompt)({
289
+ scope: {
290
+ mode: "known-file",
291
+ configFileName
292
+ },
293
+ completeConfig: canonicalizeConfig((0, _hexclave_shared_dist_config_format.override)(baselineConfig, configUpdate)),
294
+ commandPolicy
295
+ });
296
+ return (0, __config_agent_js.buildPartialConfigAgentPrompt)({
297
+ configFileName,
298
+ changes,
299
+ commandPolicy
300
+ });
301
+ }
302
+ function canonicalizeConfig(config) {
303
+ const droppedKeys = [];
304
+ const normalized = (0, _hexclave_shared_dist_config_format.normalize)(config, {
305
+ onDotIntoNonObject: "ignore",
306
+ onDotIntoNull: "empty-object",
307
+ droppedKeys
308
+ });
309
+ if (droppedKeys.length > 0) throw new Error(`Config update has conflicting keys that would be dropped during normalization: ${droppedKeys.map((key) => JSON.stringify(key)).join(", ")}`);
310
+ return normalized;
311
+ }
312
+ function configsEqual(a, b) {
313
+ if (a === b) return true;
314
+ if (a === null || b === null) return a === b;
315
+ if (Array.isArray(a) || Array.isArray(b)) {
316
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
317
+ return a.every((value, index) => configsEqual(value, b[index]));
318
+ }
319
+ if (typeof a === "object" && typeof b === "object") {
320
+ const aEntries = Object.entries(a);
321
+ const bMap = new Map(Object.entries(b));
322
+ if (aEntries.length !== bMap.size) return false;
323
+ return aEntries.every(([key, value]) => bMap.has(key) && configsEqual(value, bMap.get(key)));
324
+ }
325
+ return false;
326
+ }
327
+
328
+ //#endregion
329
+ exports.updateConfigObject = updateConfigObject;
330
+ //# sourceMappingURL=config-updater.js.map