@hyperspaceng/neural-coding-agent 0.63.0 → 0.63.2

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.
Files changed (100) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +2 -2
  3. package/dist/core/agent-session.d.ts +9 -6
  4. package/dist/core/agent-session.d.ts.map +1 -1
  5. package/dist/core/agent-session.js +98 -54
  6. package/dist/core/agent-session.js.map +1 -1
  7. package/dist/core/auth-storage.d.ts +3 -1
  8. package/dist/core/auth-storage.d.ts.map +1 -1
  9. package/dist/core/auth-storage.js +5 -2
  10. package/dist/core/auth-storage.js.map +1 -1
  11. package/dist/core/compaction/branch-summarization.d.ts +2 -0
  12. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  13. package/dist/core/compaction/branch-summarization.js +2 -2
  14. package/dist/core/compaction/branch-summarization.js.map +1 -1
  15. package/dist/core/compaction/compaction.d.ts +3 -3
  16. package/dist/core/compaction/compaction.d.ts.map +1 -1
  17. package/dist/core/compaction/compaction.js +27 -26
  18. package/dist/core/compaction/compaction.js.map +1 -1
  19. package/dist/core/export-html/index.d.ts.map +1 -1
  20. package/dist/core/export-html/index.js +5 -4
  21. package/dist/core/export-html/index.js.map +1 -1
  22. package/dist/core/extensions/types.d.ts +7 -1
  23. package/dist/core/extensions/types.d.ts.map +1 -1
  24. package/dist/core/extensions/types.js.map +1 -1
  25. package/dist/core/model-registry.d.ts +18 -2
  26. package/dist/core/model-registry.d.ts.map +1 -1
  27. package/dist/core/model-registry.js +83 -69
  28. package/dist/core/model-registry.js.map +1 -1
  29. package/dist/core/model-resolver.d.ts.map +1 -1
  30. package/dist/core/model-resolver.js +4 -4
  31. package/dist/core/model-resolver.js.map +1 -1
  32. package/dist/core/package-manager.d.ts.map +1 -1
  33. package/dist/core/package-manager.js +88 -24
  34. package/dist/core/package-manager.js.map +1 -1
  35. package/dist/core/resolve-config-value.d.ts +6 -0
  36. package/dist/core/resolve-config-value.d.ts.map +1 -1
  37. package/dist/core/resolve-config-value.js +37 -5
  38. package/dist/core/resolve-config-value.js.map +1 -1
  39. package/dist/core/sdk.d.ts +1 -1
  40. package/dist/core/sdk.d.ts.map +1 -1
  41. package/dist/core/sdk.js +13 -22
  42. package/dist/core/sdk.js.map +1 -1
  43. package/dist/core/settings-manager.d.ts +2 -0
  44. package/dist/core/settings-manager.d.ts.map +1 -1
  45. package/dist/core/settings-manager.js +3 -0
  46. package/dist/core/settings-manager.js.map +1 -1
  47. package/dist/core/timings.d.ts +1 -0
  48. package/dist/core/timings.d.ts.map +1 -1
  49. package/dist/core/timings.js +6 -0
  50. package/dist/core/timings.js.map +1 -1
  51. package/dist/core/tools/edit-diff.d.ts +23 -1
  52. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  53. package/dist/core/tools/edit-diff.js +150 -57
  54. package/dist/core/tools/edit-diff.js.map +1 -1
  55. package/dist/core/tools/edit.d.ts +18 -6
  56. package/dist/core/tools/edit.d.ts.map +1 -1
  57. package/dist/core/tools/edit.js +108 -59
  58. package/dist/core/tools/edit.js.map +1 -1
  59. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  60. package/dist/core/tools/file-mutation-queue.js +4 -4
  61. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  62. package/dist/core/tools/index.d.ts +12 -4
  63. package/dist/core/tools/index.d.ts.map +1 -1
  64. package/dist/main.d.ts.map +1 -1
  65. package/dist/main.js +28 -10
  66. package/dist/main.js.map +1 -1
  67. package/dist/modes/interactive/components/bash-execution.d.ts +0 -1
  68. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/bash-execution.js +18 -5
  70. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  71. package/dist/modes/interactive/components/tool-execution.d.ts +0 -1
  72. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  73. package/dist/modes/interactive/components/tool-execution.js +2 -7
  74. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  75. package/dist/modes/interactive/interactive-mode.d.ts +0 -1
  76. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  77. package/dist/modes/interactive/interactive-mode.js +28 -65
  78. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  79. package/dist/modes/print-mode.d.ts +1 -1
  80. package/dist/modes/print-mode.d.ts.map +1 -1
  81. package/dist/modes/print-mode.js +83 -71
  82. package/dist/modes/print-mode.js.map +1 -1
  83. package/docs/development.md +3 -1
  84. package/docs/extensions.md +13 -2
  85. package/docs/models.md +6 -0
  86. package/docs/rpc.md +11 -2
  87. package/docs/settings.md +12 -0
  88. package/docs/skills.md +3 -2
  89. package/examples/extensions/custom-compaction.ts +17 -4
  90. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  91. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  92. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  93. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  94. package/examples/extensions/handoff.ts +5 -2
  95. package/examples/extensions/qna.ts +5 -2
  96. package/examples/extensions/summarize.ts +15 -4
  97. package/examples/extensions/trigger-compact.ts +11 -1
  98. package/examples/extensions/with-deps/package-lock.json +2 -2
  99. package/examples/extensions/with-deps/package.json +1 -1
  100. package/package.json +5 -4
@@ -24,5 +24,5 @@ export interface PrintModeOptions {
24
24
  * Run in print (single-shot) mode.
25
25
  * Sends prompts to the agent and outputs the result.
26
26
  */
27
- export declare function runPrintMode(session: AgentSession, options: PrintModeOptions): Promise<void>;
27
+ export declare function runPrintMode(session: AgentSession, options: PrintModeOptions): Promise<number>;
28
28
  //# sourceMappingURL=print-mode.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"print-mode.d.ts","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;CAC/B;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyFlG","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `pi -p \"prompt\"` - text output\n * - `pi --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { AssistantMessage, ImageContent } from \"@hyperspaceng/neural-ai\";\nimport type { AgentSession } from \"../core/agent-session.js\";\nimport { flushRawStdout, writeRawStdout } from \"../core/output-guard.js\";\n\n/**\n * Options for print mode.\n */\nexport interface PrintModeOptions {\n\t/** Output mode: \"text\" for final response only, \"json\" for all events */\n\tmode: \"text\" | \"json\";\n\t/** Array of additional prompts to send after initialMessage */\n\tmessages?: string[];\n\t/** First message to send (may contain @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n}\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n */\nexport async function runPrintMode(session: AgentSession, options: PrintModeOptions): Promise<void> {\n\tconst { mode, messages = [], initialMessage, initialImages } = options;\n\tif (mode === \"json\") {\n\t\tconst header = session.sessionManager.getHeader();\n\t\tif (header) {\n\t\t\twriteRawStdout(`${JSON.stringify(header)}\\n`);\n\t\t}\n\t}\n\t// Set up extensions for print mode (no UI)\n\tawait session.bindExtensions({\n\t\tcommandContextActions: {\n\t\t\twaitForIdle: () => session.agent.waitForIdle(),\n\t\t\tnewSession: async (options) => {\n\t\t\t\tconst success = await session.newSession({ parentSession: options?.parentSession });\n\t\t\t\tif (success && options?.setup) {\n\t\t\t\t\tawait options.setup(session.sessionManager);\n\t\t\t\t}\n\t\t\t\treturn { cancelled: !success };\n\t\t\t},\n\t\t\tfork: async (entryId) => {\n\t\t\t\tconst result = await session.fork(entryId);\n\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t},\n\t\t\tnavigateTree: async (targetId, options) => {\n\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\tsummarize: options?.summarize,\n\t\t\t\t\tcustomInstructions: options?.customInstructions,\n\t\t\t\t\treplaceInstructions: options?.replaceInstructions,\n\t\t\t\t\tlabel: options?.label,\n\t\t\t\t});\n\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t},\n\t\t\tswitchSession: async (sessionPath) => {\n\t\t\t\tconst success = await session.switchSession(sessionPath);\n\t\t\t\treturn { cancelled: !success };\n\t\t\t},\n\t\t\treload: async () => {\n\t\t\t\tawait session.reload();\n\t\t\t},\n\t\t},\n\t\tonError: (err) => {\n\t\t\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t},\n\t});\n\n\t// Always subscribe to enable session persistence via _handleAgentEvent\n\tsession.subscribe((event) => {\n\t\t// In JSON mode, output all events\n\t\tif (mode === \"json\") {\n\t\t\twriteRawStdout(`${JSON.stringify(event)}\\n`);\n\t\t}\n\t});\n\n\t// Send initial message with attachments\n\tif (initialMessage) {\n\t\tawait session.prompt(initialMessage, { images: initialImages });\n\t}\n\n\t// Send remaining messages\n\tfor (const message of messages) {\n\t\tawait session.prompt(message);\n\t}\n\n\t// In text mode, output final response\n\tif (mode === \"text\") {\n\t\tconst state = session.state;\n\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\n\t\t\t// Check for error/aborted\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Output text content\n\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Ensure stdout is fully flushed before returning\n\t// This prevents race conditions where the process exits before all output is written\n\tawait flushRawStdout();\n}\n"]}
1
+ {"version":3,"file":"print-mode.d.ts","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAG7D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;CAC/B;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoGpG","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `pi -p \"prompt\"` - text output\n * - `pi --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { AssistantMessage, ImageContent } from \"@hyperspaceng/neural-ai\";\nimport type { AgentSession } from \"../core/agent-session.js\";\nimport { flushRawStdout, writeRawStdout } from \"../core/output-guard.js\";\n\n/**\n * Options for print mode.\n */\nexport interface PrintModeOptions {\n\t/** Output mode: \"text\" for final response only, \"json\" for all events */\n\tmode: \"text\" | \"json\";\n\t/** Array of additional prompts to send after initialMessage */\n\tmessages?: string[];\n\t/** First message to send (may contain @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n}\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n */\nexport async function runPrintMode(session: AgentSession, options: PrintModeOptions): Promise<number> {\n\tconst { mode, messages = [], initialMessage, initialImages } = options;\n\tlet exitCode = 0;\n\n\ttry {\n\t\tif (mode === \"json\") {\n\t\t\tconst header = session.sessionManager.getHeader();\n\t\t\tif (header) {\n\t\t\t\twriteRawStdout(`${JSON.stringify(header)}\\n`);\n\t\t\t}\n\t\t}\n\t\t// Set up extensions for print mode (no UI)\n\t\tawait session.bindExtensions({\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.agent.waitForIdle(),\n\t\t\t\tnewSession: async (options) => {\n\t\t\t\t\tconst success = await session.newSession({ parentSession: options?.parentSession });\n\t\t\t\t\tif (success && options?.setup) {\n\t\t\t\t\t\tawait options.setup(session.sessionManager);\n\t\t\t\t\t}\n\t\t\t\t\treturn { cancelled: !success };\n\t\t\t\t},\n\t\t\t\tfork: async (entryId) => {\n\t\t\t\t\tconst result = await session.fork(entryId);\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, options) => {\n\t\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: options?.summarize,\n\t\t\t\t\t\tcustomInstructions: options?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: options?.replaceInstructions,\n\t\t\t\t\t\tlabel: options?.label,\n\t\t\t\t\t});\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath) => {\n\t\t\t\t\tconst success = await session.switchSession(sessionPath);\n\t\t\t\t\treturn { cancelled: !success };\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait session.reload();\n\t\t\t\t},\n\t\t\t},\n\t\t\tonError: (err) => {\n\t\t\t\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\t// Always subscribe to enable session persistence via _handleAgentEvent\n\t\tsession.subscribe((event) => {\n\t\t\t// In JSON mode, output all events\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(event)}\\n`);\n\t\t\t}\n\t\t});\n\n\t\t// Send initial message with attachments\n\t\tif (initialMessage) {\n\t\t\tawait session.prompt(initialMessage, { images: initialImages });\n\t\t}\n\n\t\t// Send remaining messages\n\t\tfor (const message of messages) {\n\t\t\tawait session.prompt(message);\n\t\t}\n\n\t\t// In text mode, output final response\n\t\tif (mode === \"text\") {\n\t\t\tconst state = session.state;\n\t\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\n\t\t\t\t// Check for error/aborted\n\t\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\t\texitCode = 1;\n\t\t\t\t} else {\n\t\t\t\t\t// Output text content\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn exitCode;\n\t} finally {\n\t\tconst extensionRunner = session.extensionRunner;\n\t\tif (extensionRunner?.hasHandlers(\"session_shutdown\")) {\n\t\t\tawait extensionRunner.emit({ type: \"session_shutdown\" });\n\t\t}\n\n\t\t// Ensure stdout is fully flushed before returning\n\t\t// This prevents race conditions where the process exits before all output is written\n\t\tawait flushRawStdout();\n\t}\n}\n"]}
@@ -12,84 +12,96 @@ import { flushRawStdout, writeRawStdout } from "../core/output-guard.js";
12
12
  */
13
13
  export async function runPrintMode(session, options) {
14
14
  const { mode, messages = [], initialMessage, initialImages } = options;
15
- if (mode === "json") {
16
- const header = session.sessionManager.getHeader();
17
- if (header) {
18
- writeRawStdout(`${JSON.stringify(header)}\n`);
15
+ let exitCode = 0;
16
+ try {
17
+ if (mode === "json") {
18
+ const header = session.sessionManager.getHeader();
19
+ if (header) {
20
+ writeRawStdout(`${JSON.stringify(header)}\n`);
21
+ }
19
22
  }
20
- }
21
- // Set up extensions for print mode (no UI)
22
- await session.bindExtensions({
23
- commandContextActions: {
24
- waitForIdle: () => session.agent.waitForIdle(),
25
- newSession: async (options) => {
26
- const success = await session.newSession({ parentSession: options?.parentSession });
27
- if (success && options?.setup) {
28
- await options.setup(session.sessionManager);
29
- }
30
- return { cancelled: !success };
23
+ // Set up extensions for print mode (no UI)
24
+ await session.bindExtensions({
25
+ commandContextActions: {
26
+ waitForIdle: () => session.agent.waitForIdle(),
27
+ newSession: async (options) => {
28
+ const success = await session.newSession({ parentSession: options?.parentSession });
29
+ if (success && options?.setup) {
30
+ await options.setup(session.sessionManager);
31
+ }
32
+ return { cancelled: !success };
33
+ },
34
+ fork: async (entryId) => {
35
+ const result = await session.fork(entryId);
36
+ return { cancelled: result.cancelled };
37
+ },
38
+ navigateTree: async (targetId, options) => {
39
+ const result = await session.navigateTree(targetId, {
40
+ summarize: options?.summarize,
41
+ customInstructions: options?.customInstructions,
42
+ replaceInstructions: options?.replaceInstructions,
43
+ label: options?.label,
44
+ });
45
+ return { cancelled: result.cancelled };
46
+ },
47
+ switchSession: async (sessionPath) => {
48
+ const success = await session.switchSession(sessionPath);
49
+ return { cancelled: !success };
50
+ },
51
+ reload: async () => {
52
+ await session.reload();
53
+ },
31
54
  },
32
- fork: async (entryId) => {
33
- const result = await session.fork(entryId);
34
- return { cancelled: result.cancelled };
55
+ onError: (err) => {
56
+ console.error(`Extension error (${err.extensionPath}): ${err.error}`);
35
57
  },
36
- navigateTree: async (targetId, options) => {
37
- const result = await session.navigateTree(targetId, {
38
- summarize: options?.summarize,
39
- customInstructions: options?.customInstructions,
40
- replaceInstructions: options?.replaceInstructions,
41
- label: options?.label,
42
- });
43
- return { cancelled: result.cancelled };
44
- },
45
- switchSession: async (sessionPath) => {
46
- const success = await session.switchSession(sessionPath);
47
- return { cancelled: !success };
48
- },
49
- reload: async () => {
50
- await session.reload();
51
- },
52
- },
53
- onError: (err) => {
54
- console.error(`Extension error (${err.extensionPath}): ${err.error}`);
55
- },
56
- });
57
- // Always subscribe to enable session persistence via _handleAgentEvent
58
- session.subscribe((event) => {
59
- // In JSON mode, output all events
60
- if (mode === "json") {
61
- writeRawStdout(`${JSON.stringify(event)}\n`);
62
- }
63
- });
64
- // Send initial message with attachments
65
- if (initialMessage) {
66
- await session.prompt(initialMessage, { images: initialImages });
67
- }
68
- // Send remaining messages
69
- for (const message of messages) {
70
- await session.prompt(message);
71
- }
72
- // In text mode, output final response
73
- if (mode === "text") {
74
- const state = session.state;
75
- const lastMessage = state.messages[state.messages.length - 1];
76
- if (lastMessage?.role === "assistant") {
77
- const assistantMsg = lastMessage;
78
- // Check for error/aborted
79
- if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
80
- console.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);
81
- process.exit(1);
58
+ });
59
+ // Always subscribe to enable session persistence via _handleAgentEvent
60
+ session.subscribe((event) => {
61
+ // In JSON mode, output all events
62
+ if (mode === "json") {
63
+ writeRawStdout(`${JSON.stringify(event)}\n`);
82
64
  }
83
- // Output text content
84
- for (const content of assistantMsg.content) {
85
- if (content.type === "text") {
86
- writeRawStdout(`${content.text}\n`);
65
+ });
66
+ // Send initial message with attachments
67
+ if (initialMessage) {
68
+ await session.prompt(initialMessage, { images: initialImages });
69
+ }
70
+ // Send remaining messages
71
+ for (const message of messages) {
72
+ await session.prompt(message);
73
+ }
74
+ // In text mode, output final response
75
+ if (mode === "text") {
76
+ const state = session.state;
77
+ const lastMessage = state.messages[state.messages.length - 1];
78
+ if (lastMessage?.role === "assistant") {
79
+ const assistantMsg = lastMessage;
80
+ // Check for error/aborted
81
+ if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
82
+ console.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);
83
+ exitCode = 1;
84
+ }
85
+ else {
86
+ // Output text content
87
+ for (const content of assistantMsg.content) {
88
+ if (content.type === "text") {
89
+ writeRawStdout(`${content.text}\n`);
90
+ }
91
+ }
87
92
  }
88
93
  }
89
94
  }
95
+ return exitCode;
96
+ }
97
+ finally {
98
+ const extensionRunner = session.extensionRunner;
99
+ if (extensionRunner?.hasHandlers("session_shutdown")) {
100
+ await extensionRunner.emit({ type: "session_shutdown" });
101
+ }
102
+ // Ensure stdout is fully flushed before returning
103
+ // This prevents race conditions where the process exits before all output is written
104
+ await flushRawStdout();
90
105
  }
91
- // Ensure stdout is fully flushed before returning
92
- // This prevents race conditions where the process exits before all output is written
93
- await flushRawStdout();
94
106
  }
95
107
  //# sourceMappingURL=print-mode.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"print-mode.js","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `pi -p \"prompt\"` - text output\n * - `pi --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { AssistantMessage, ImageContent } from \"@hyperspaceng/neural-ai\";\nimport type { AgentSession } from \"../core/agent-session.js\";\nimport { flushRawStdout, writeRawStdout } from \"../core/output-guard.js\";\n\n/**\n * Options for print mode.\n */\nexport interface PrintModeOptions {\n\t/** Output mode: \"text\" for final response only, \"json\" for all events */\n\tmode: \"text\" | \"json\";\n\t/** Array of additional prompts to send after initialMessage */\n\tmessages?: string[];\n\t/** First message to send (may contain @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n}\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n */\nexport async function runPrintMode(session: AgentSession, options: PrintModeOptions): Promise<void> {\n\tconst { mode, messages = [], initialMessage, initialImages } = options;\n\tif (mode === \"json\") {\n\t\tconst header = session.sessionManager.getHeader();\n\t\tif (header) {\n\t\t\twriteRawStdout(`${JSON.stringify(header)}\\n`);\n\t\t}\n\t}\n\t// Set up extensions for print mode (no UI)\n\tawait session.bindExtensions({\n\t\tcommandContextActions: {\n\t\t\twaitForIdle: () => session.agent.waitForIdle(),\n\t\t\tnewSession: async (options) => {\n\t\t\t\tconst success = await session.newSession({ parentSession: options?.parentSession });\n\t\t\t\tif (success && options?.setup) {\n\t\t\t\t\tawait options.setup(session.sessionManager);\n\t\t\t\t}\n\t\t\t\treturn { cancelled: !success };\n\t\t\t},\n\t\t\tfork: async (entryId) => {\n\t\t\t\tconst result = await session.fork(entryId);\n\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t},\n\t\t\tnavigateTree: async (targetId, options) => {\n\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\tsummarize: options?.summarize,\n\t\t\t\t\tcustomInstructions: options?.customInstructions,\n\t\t\t\t\treplaceInstructions: options?.replaceInstructions,\n\t\t\t\t\tlabel: options?.label,\n\t\t\t\t});\n\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t},\n\t\t\tswitchSession: async (sessionPath) => {\n\t\t\t\tconst success = await session.switchSession(sessionPath);\n\t\t\t\treturn { cancelled: !success };\n\t\t\t},\n\t\t\treload: async () => {\n\t\t\t\tawait session.reload();\n\t\t\t},\n\t\t},\n\t\tonError: (err) => {\n\t\t\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t},\n\t});\n\n\t// Always subscribe to enable session persistence via _handleAgentEvent\n\tsession.subscribe((event) => {\n\t\t// In JSON mode, output all events\n\t\tif (mode === \"json\") {\n\t\t\twriteRawStdout(`${JSON.stringify(event)}\\n`);\n\t\t}\n\t});\n\n\t// Send initial message with attachments\n\tif (initialMessage) {\n\t\tawait session.prompt(initialMessage, { images: initialImages });\n\t}\n\n\t// Send remaining messages\n\tfor (const message of messages) {\n\t\tawait session.prompt(message);\n\t}\n\n\t// In text mode, output final response\n\tif (mode === \"text\") {\n\t\tconst state = session.state;\n\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\n\t\t\t// Check for error/aborted\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Output text content\n\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Ensure stdout is fully flushed before returning\n\t// This prevents race conditions where the process exits before all output is written\n\tawait flushRawStdout();\n}\n"]}
1
+ {"version":3,"file":"print-mode.js","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `pi -p \"prompt\"` - text output\n * - `pi --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { AssistantMessage, ImageContent } from \"@hyperspaceng/neural-ai\";\nimport type { AgentSession } from \"../core/agent-session.js\";\nimport { flushRawStdout, writeRawStdout } from \"../core/output-guard.js\";\n\n/**\n * Options for print mode.\n */\nexport interface PrintModeOptions {\n\t/** Output mode: \"text\" for final response only, \"json\" for all events */\n\tmode: \"text\" | \"json\";\n\t/** Array of additional prompts to send after initialMessage */\n\tmessages?: string[];\n\t/** First message to send (may contain @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n}\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n */\nexport async function runPrintMode(session: AgentSession, options: PrintModeOptions): Promise<number> {\n\tconst { mode, messages = [], initialMessage, initialImages } = options;\n\tlet exitCode = 0;\n\n\ttry {\n\t\tif (mode === \"json\") {\n\t\t\tconst header = session.sessionManager.getHeader();\n\t\t\tif (header) {\n\t\t\t\twriteRawStdout(`${JSON.stringify(header)}\\n`);\n\t\t\t}\n\t\t}\n\t\t// Set up extensions for print mode (no UI)\n\t\tawait session.bindExtensions({\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.agent.waitForIdle(),\n\t\t\t\tnewSession: async (options) => {\n\t\t\t\t\tconst success = await session.newSession({ parentSession: options?.parentSession });\n\t\t\t\t\tif (success && options?.setup) {\n\t\t\t\t\t\tawait options.setup(session.sessionManager);\n\t\t\t\t\t}\n\t\t\t\t\treturn { cancelled: !success };\n\t\t\t\t},\n\t\t\t\tfork: async (entryId) => {\n\t\t\t\t\tconst result = await session.fork(entryId);\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, options) => {\n\t\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: options?.summarize,\n\t\t\t\t\t\tcustomInstructions: options?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: options?.replaceInstructions,\n\t\t\t\t\t\tlabel: options?.label,\n\t\t\t\t\t});\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath) => {\n\t\t\t\t\tconst success = await session.switchSession(sessionPath);\n\t\t\t\t\treturn { cancelled: !success };\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait session.reload();\n\t\t\t\t},\n\t\t\t},\n\t\t\tonError: (err) => {\n\t\t\t\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\t// Always subscribe to enable session persistence via _handleAgentEvent\n\t\tsession.subscribe((event) => {\n\t\t\t// In JSON mode, output all events\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(event)}\\n`);\n\t\t\t}\n\t\t});\n\n\t\t// Send initial message with attachments\n\t\tif (initialMessage) {\n\t\t\tawait session.prompt(initialMessage, { images: initialImages });\n\t\t}\n\n\t\t// Send remaining messages\n\t\tfor (const message of messages) {\n\t\t\tawait session.prompt(message);\n\t\t}\n\n\t\t// In text mode, output final response\n\t\tif (mode === \"text\") {\n\t\t\tconst state = session.state;\n\t\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\n\t\t\t\t// Check for error/aborted\n\t\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\t\texitCode = 1;\n\t\t\t\t} else {\n\t\t\t\t\t// Output text content\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn exitCode;\n\t} finally {\n\t\tconst extensionRunner = session.extensionRunner;\n\t\tif (extensionRunner?.hasHandlers(\"session_shutdown\")) {\n\t\t\tawait extensionRunner.emit({ type: \"session_shutdown\" });\n\t\t}\n\n\t\t// Ensure stdout is fully flushed before returning\n\t\t// This prevents race conditions where the process exits before all output is written\n\t\tawait flushRawStdout();\n\t}\n}\n"]}
@@ -14,9 +14,11 @@ npm run build
14
14
  Run from source:
15
15
 
16
16
  ```bash
17
- ./pi-test.sh
17
+ /path/to/pi-mono/pi-test.sh
18
18
  ```
19
19
 
20
+ The script can be run from any directory. Pi keeps the caller's current working directory.
21
+
20
22
  ## Forking / Rebranding
21
23
 
22
24
  Configure via `package.json`:
@@ -293,10 +293,11 @@ Fired by the `pi` CLI during startup session resolution, before the initial sess
293
293
  This event is:
294
294
  - CLI-only. It is not emitted in SDK mode.
295
295
  - Startup-only. It is not emitted for later interactive `/new` or `/resume` actions.
296
- - Bypassed when `--session-dir` is provided.
296
+ - Lower priority than `--session-dir` and `sessionDir` in `settings.json`.
297
297
  - Special-cased to receive no `ctx` argument.
298
298
 
299
299
  If multiple extensions return `sessionDir`, the last one wins.
300
+ Combined precedence is: `--session-dir` CLI flag, then `sessionDir` in settings, then extension `session_directory` hooks.
300
301
 
301
302
  ```typescript
302
303
  pi.on("session_directory", async (event) => {
@@ -564,17 +565,27 @@ Before `tool_call` runs, pi waits for previously emitted Agent events to finish
564
565
 
565
566
  In the default parallel tool execution mode, sibling tool calls from the same assistant message are preflighted sequentially, then executed concurrently. `tool_call` is not guaranteed to see sibling tool results from that same assistant message in `ctx.sessionManager`.
566
567
 
568
+ `event.input` is mutable. Mutate it in place to patch tool arguments before execution.
569
+
570
+ Behavior guarantees:
571
+ - Mutations to `event.input` affect the actual tool execution
572
+ - Later `tool_call` handlers see mutations made by earlier handlers
573
+ - No re-validation is performed after your mutation
574
+ - Return values from `tool_call` only control blocking via `{ block: true, reason?: string }`
575
+
567
576
  ```typescript
568
577
  import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
569
578
 
570
579
  pi.on("tool_call", async (event, ctx) => {
571
580
  // event.toolName - "bash", "read", "write", "edit", etc.
572
581
  // event.toolCallId
573
- // event.input - tool parameters
582
+ // event.input - tool parameters (mutable)
574
583
 
575
584
  // Built-in tools: no type params needed
576
585
  if (isToolCallEventType("bash", event)) {
577
586
  // event.input is { command: string; timeout?: number }
587
+ event.input.command = `source ~/.profile\n${event.input.command}`;
588
+
578
589
  if (event.input.command.includes("rm -rf")) {
579
590
  return { block: true, reason: "Dangerous command" };
580
591
  }
package/docs/models.md CHANGED
@@ -131,6 +131,12 @@ The `apiKey` and `headers` fields support three formats:
131
131
  "apiKey": "sk-..."
132
132
  ```
133
133
 
134
+ For `models.json`, shell commands are resolved at request time. pi intentionally does not apply built-in TTL, stale reuse, or recovery logic for arbitrary commands. Different commands need different caching and failure strategies, and pi cannot infer the right one.
135
+
136
+ If your command is slow, expensive, rate-limited, or should keep using a previous value on transient failures, wrap it in your own script or command that implements the caching or TTL behavior you want.
137
+
138
+ `/model` availability checks use configured auth presence and do not execute shell commands.
139
+
134
140
  ### Custom Headers
135
141
 
136
142
  ```json
package/docs/rpc.md CHANGED
@@ -494,7 +494,7 @@ Response:
494
494
 
495
495
  #### get_session_stats
496
496
 
497
- Get token usage and cost statistics.
497
+ Get token usage, cost statistics, and current context window usage.
498
498
 
499
499
  ```json
500
500
  {"type": "get_session_stats"}
@@ -521,11 +521,20 @@ Response:
521
521
  "cacheWrite": 5000,
522
522
  "total": 105000
523
523
  },
524
- "cost": 0.45
524
+ "cost": 0.45,
525
+ "contextUsage": {
526
+ "tokens": 60000,
527
+ "contextWindow": 200000,
528
+ "percent": 30
529
+ }
525
530
  }
526
531
  }
527
532
  ```
528
533
 
534
+ `tokens` contains assistant usage totals for the current session state. `contextUsage` contains the actual current context-window estimate used for compaction and footer display.
535
+
536
+ `contextUsage` is omitted when no model or context window is available. `contextUsage.tokens` and `contextUsage.percent` are `null` immediately after compaction until a fresh post-compaction assistant response provides valid usage data.
537
+
529
538
  #### export_html
530
539
 
531
540
  Export session to an HTML file.
package/docs/settings.md CHANGED
@@ -127,6 +127,18 @@ When a provider requests a retry delay longer than `maxDelayMs` (e.g., Google's
127
127
 
128
128
  `npmCommand` is used for all npm package-manager operations, including `npm root -g`, installs, uninstalls, and `npm install` inside git packages. Use argv-style entries exactly as the process should be launched.
129
129
 
130
+ ### Sessions
131
+
132
+ | Setting | Type | Default | Description |
133
+ |---------|------|---------|-------------|
134
+ | `sessionDir` | string | - | Directory where session files are stored. Accepts absolute or relative paths. |
135
+
136
+ ```json
137
+ { "sessionDir": ".pi/sessions" }
138
+ ```
139
+
140
+ When multiple sources specify a session directory, `--session-dir` CLI flag takes precedence, then `sessionDir` in settings.json, then extension hooks.
141
+
130
142
  ### Model Cycling
131
143
 
132
144
  | Setting | Type | Default | Description |
package/docs/skills.md CHANGED
@@ -34,8 +34,9 @@ Pi loads skills from:
34
34
  - CLI: `--skill <path>` (repeatable, additive even with `--no-skills`)
35
35
 
36
36
  Discovery rules:
37
- - Direct `.md` files in the skills directory root
38
- - Recursive `SKILL.md` files under subdirectories
37
+ - In `~/.pi/agent/skills/` and `.pi/skills/`, direct root `.md` files are discovered as individual skills
38
+ - In all skill locations, directories containing `SKILL.md` are discovered recursively
39
+ - In `~/.agents/skills/` and project `.agents/skills/`, root `.md` files are ignored
39
40
 
40
41
  Disable discovery with `--no-skills` (explicit `--skill` paths still load).
41
42
 
@@ -31,9 +31,13 @@ export default function (pi: ExtensionAPI) {
31
31
  return;
32
32
  }
33
33
 
34
- // Resolve API key for the summarization model
35
- const apiKey = await ctx.modelRegistry.getApiKey(model);
36
- if (!apiKey) {
34
+ // Resolve request auth for the summarization model
35
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
36
+ if (!auth.ok) {
37
+ ctx.ui.notify(`Compaction auth failed: ${auth.error}`, "warning");
38
+ return;
39
+ }
40
+ if (!auth.apiKey) {
37
41
  ctx.ui.notify(`No API key for ${model.provider}, using default compaction`, "warning");
38
42
  return;
39
43
  }
@@ -83,7 +87,16 @@ ${conversationText}
83
87
 
84
88
  try {
85
89
  // Pass signal to honor abort requests (e.g., user cancels compaction)
86
- const response = await complete(model, { messages: summaryMessages }, { apiKey, maxTokens: 8192, signal });
90
+ const response = await complete(
91
+ model,
92
+ { messages: summaryMessages },
93
+ {
94
+ apiKey: auth.apiKey,
95
+ headers: auth.headers,
96
+ maxTokens: 8192,
97
+ signal,
98
+ },
99
+ );
87
100
 
88
101
  const summary = response.content
89
102
  .filter((c): c is { type: "text"; text: string } => c.type === "text")
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "1.14.0",
3
+ "version": "1.14.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "1.14.0",
9
+ "version": "1.14.2",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "1.14.0",
4
+ "version": "1.14.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "1.14.0",
4
+ "version": "1.14.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-qwen-cli",
3
3
  "private": true,
4
- "version": "1.13.0",
4
+ "version": "1.13.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -80,7 +80,10 @@ export default function (pi: ExtensionAPI) {
80
80
  loader.onAbort = () => done(null);
81
81
 
82
82
  const doGenerate = async () => {
83
- const apiKey = await ctx.modelRegistry.getApiKey(ctx.model!);
83
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model!);
84
+ if (!auth.ok || !auth.apiKey) {
85
+ throw new Error(auth.ok ? `No API key for ${ctx.model!.provider}` : auth.error);
86
+ }
84
87
 
85
88
  const userMessage: Message = {
86
89
  role: "user",
@@ -96,7 +99,7 @@ export default function (pi: ExtensionAPI) {
96
99
  const response = await complete(
97
100
  ctx.model!,
98
101
  { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
99
- { apiKey, signal: loader.signal },
102
+ { apiKey: auth.apiKey, headers: auth.headers, signal: loader.signal },
100
103
  );
101
104
 
102
105
  if (response.stopReason === "aborted") {
@@ -77,7 +77,10 @@ export default function (pi: ExtensionAPI) {
77
77
 
78
78
  // Do the work
79
79
  const doExtract = async () => {
80
- const apiKey = await ctx.modelRegistry.getApiKey(ctx.model!);
80
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model!);
81
+ if (!auth.ok || !auth.apiKey) {
82
+ throw new Error(auth.ok ? `No API key for ${ctx.model!.provider}` : auth.error);
83
+ }
81
84
  const userMessage: UserMessage = {
82
85
  role: "user",
83
86
  content: [{ type: "text", text: lastAssistantText! }],
@@ -87,7 +90,7 @@ export default function (pi: ExtensionAPI) {
87
90
  const response = await complete(
88
91
  ctx.model!,
89
92
  { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
90
- { apiKey, signal: loader.signal },
93
+ { apiKey: auth.apiKey, headers: auth.headers, signal: loader.signal },
91
94
  );
92
95
 
93
96
  if (response.stopReason === "aborted") {
@@ -165,12 +165,15 @@ export default function (pi: ExtensionAPI) {
165
165
  ctx.ui.notify("Model openai/gpt-5.2 not found", "warning");
166
166
  }
167
167
 
168
- const apiKey = model ? await ctx.modelRegistry.getApiKey(model) : undefined;
169
- if (!apiKey && ctx.hasUI) {
168
+ const auth = model ? await ctx.modelRegistry.getApiKeyAndHeaders(model) : undefined;
169
+ if (auth && !auth.ok && ctx.hasUI) {
170
+ ctx.ui.notify(auth.error, "warning");
171
+ }
172
+ if (auth?.ok && !auth.apiKey && ctx.hasUI) {
170
173
  ctx.ui.notify("No API key for openai/gpt-5.2", "warning");
171
174
  }
172
175
 
173
- if (!model || !apiKey) {
176
+ if (!model || !auth?.ok || !auth.apiKey) {
174
177
  return;
175
178
  }
176
179
 
@@ -182,7 +185,15 @@ export default function (pi: ExtensionAPI) {
182
185
  },
183
186
  ];
184
187
 
185
- const response = await complete(model, { messages: summaryMessages }, { apiKey, reasoningEffort: "high" });
188
+ const response = await complete(
189
+ model,
190
+ { messages: summaryMessages },
191
+ {
192
+ apiKey: auth.apiKey,
193
+ headers: auth.headers,
194
+ reasoningEffort: "high",
195
+ },
196
+ );
186
197
 
187
198
  const summary = response.content
188
199
  .filter((c): c is { type: "text"; text: string } => c.type === "text")
@@ -3,6 +3,8 @@ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-age
3
3
  const COMPACT_THRESHOLD_TOKENS = 100_000;
4
4
 
5
5
  export default function (pi: ExtensionAPI) {
6
+ let previousTokens: number | null | undefined;
7
+
6
8
  const triggerCompaction = (ctx: ExtensionContext, customInstructions?: string) => {
7
9
  if (ctx.hasUI) {
8
10
  ctx.ui.notify("Compaction started", "info");
@@ -24,7 +26,15 @@ export default function (pi: ExtensionAPI) {
24
26
 
25
27
  pi.on("turn_end", (_event, ctx) => {
26
28
  const usage = ctx.getContextUsage();
27
- if (!usage || usage.tokens === null || usage.tokens <= COMPACT_THRESHOLD_TOKENS) {
29
+ const currentTokens = usage?.tokens ?? null;
30
+ if (currentTokens === null) {
31
+ return;
32
+ }
33
+
34
+ const crossedThreshold =
35
+ previousTokens !== undefined && previousTokens !== null && previousTokens <= COMPACT_THRESHOLD_TOKENS;
36
+ previousTokens = currentTokens;
37
+ if (!crossedThreshold || currentTokens <= COMPACT_THRESHOLD_TOKENS) {
28
38
  return;
29
39
  }
30
40
  triggerCompaction(ctx);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "1.27.0",
3
+ "version": "1.27.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "1.27.0",
9
+ "version": "1.27.2",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
3
  "private": true,
4
- "version": "1.27.0",
4
+ "version": "1.27.2",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperspaceng/neural-coding-agent",
3
- "version": "0.63.0",
3
+ "version": "0.63.2",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -40,10 +40,11 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@mariozechner/jiti": "^2.6.2",
43
- "@hyperspaceng/neural-agent-core": "^0.63.0",
44
- "@hyperspaceng/neural-ai": "^0.63.0",
45
- "@hyperspaceng/neural-tui": "^0.63.0",
43
+ "@hyperspaceng/neural-agent-core": "^0.63.2",
44
+ "@hyperspaceng/neural-ai": "^0.63.2",
45
+ "@hyperspaceng/neural-tui": "^0.63.2",
46
46
  "@silvia-odwyer/photon-node": "^0.3.4",
47
+ "ajv": "^8.17.1",
47
48
  "chalk": "^5.5.0",
48
49
  "cli-highlight": "^2.1.11",
49
50
  "diff": "^8.0.2",