@mariozechner/pi-coding-agent 0.49.3 → 0.50.1

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 (207) hide show
  1. package/CHANGELOG.md +110 -1
  2. package/README.md +310 -1230
  3. package/dist/cli/args.d.ts +5 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +57 -23
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/config-selector.d.ts +14 -0
  8. package/dist/cli/config-selector.d.ts.map +1 -0
  9. package/dist/cli/config-selector.js +31 -0
  10. package/dist/cli/config-selector.js.map +1 -0
  11. package/dist/cli/session-picker.d.ts.map +1 -1
  12. package/dist/cli/session-picker.js +1 -1
  13. package/dist/cli/session-picker.js.map +1 -1
  14. package/dist/core/agent-session.d.ts +60 -37
  15. package/dist/core/agent-session.d.ts.map +1 -1
  16. package/dist/core/agent-session.js +272 -69
  17. package/dist/core/agent-session.js.map +1 -1
  18. package/dist/core/auth-storage.d.ts +8 -18
  19. package/dist/core/auth-storage.d.ts.map +1 -1
  20. package/dist/core/auth-storage.js +39 -55
  21. package/dist/core/auth-storage.js.map +1 -1
  22. package/dist/core/bash-executor.d.ts.map +1 -1
  23. package/dist/core/bash-executor.js +2 -1
  24. package/dist/core/bash-executor.js.map +1 -1
  25. package/dist/core/diagnostics.d.ts +15 -0
  26. package/dist/core/diagnostics.d.ts.map +1 -0
  27. package/dist/core/diagnostics.js +2 -0
  28. package/dist/core/diagnostics.js.map +1 -0
  29. package/dist/core/export-html/template.css +9 -0
  30. package/dist/core/export-html/template.js +6 -4
  31. package/dist/core/extensions/index.d.ts +1 -1
  32. package/dist/core/extensions/index.d.ts.map +1 -1
  33. package/dist/core/extensions/index.js.map +1 -1
  34. package/dist/core/extensions/loader.d.ts +1 -1
  35. package/dist/core/extensions/loader.d.ts.map +1 -1
  36. package/dist/core/extensions/loader.js +10 -1
  37. package/dist/core/extensions/loader.js.map +1 -1
  38. package/dist/core/extensions/runner.d.ts +9 -3
  39. package/dist/core/extensions/runner.d.ts.map +1 -1
  40. package/dist/core/extensions/runner.js +39 -12
  41. package/dist/core/extensions/runner.js.map +1 -1
  42. package/dist/core/extensions/types.d.ts +112 -1
  43. package/dist/core/extensions/types.d.ts.map +1 -1
  44. package/dist/core/extensions/types.js.map +1 -1
  45. package/dist/core/footer-data-provider.d.ts +9 -2
  46. package/dist/core/footer-data-provider.d.ts.map +1 -1
  47. package/dist/core/footer-data-provider.js +13 -0
  48. package/dist/core/footer-data-provider.js.map +1 -1
  49. package/dist/core/model-registry.d.ts +42 -2
  50. package/dist/core/model-registry.d.ts.map +1 -1
  51. package/dist/core/model-registry.js +154 -44
  52. package/dist/core/model-registry.js.map +1 -1
  53. package/dist/core/model-resolver.d.ts.map +1 -1
  54. package/dist/core/model-resolver.js +3 -2
  55. package/dist/core/model-resolver.js.map +1 -1
  56. package/dist/core/package-manager.d.ts +130 -0
  57. package/dist/core/package-manager.d.ts.map +1 -0
  58. package/dist/core/package-manager.js +1177 -0
  59. package/dist/core/package-manager.js.map +1 -0
  60. package/dist/core/prompt-templates.d.ts +6 -0
  61. package/dist/core/prompt-templates.d.ts.map +1 -1
  62. package/dist/core/prompt-templates.js +114 -54
  63. package/dist/core/prompt-templates.js.map +1 -1
  64. package/dist/core/resource-loader.d.ts +160 -0
  65. package/dist/core/resource-loader.d.ts.map +1 -0
  66. package/dist/core/resource-loader.js +604 -0
  67. package/dist/core/resource-loader.js.map +1 -0
  68. package/dist/core/sdk.d.ts +14 -105
  69. package/dist/core/sdk.d.ts.map +1 -1
  70. package/dist/core/sdk.js +52 -304
  71. package/dist/core/sdk.js.map +1 -1
  72. package/dist/core/session-manager.d.ts.map +1 -1
  73. package/dist/core/session-manager.js +45 -1
  74. package/dist/core/session-manager.js.map +1 -1
  75. package/dist/core/settings-manager.d.ts +34 -16
  76. package/dist/core/settings-manager.d.ts.map +1 -1
  77. package/dist/core/settings-manager.js +104 -25
  78. package/dist/core/settings-manager.js.map +1 -1
  79. package/dist/core/skills.d.ts +18 -10
  80. package/dist/core/skills.d.ts.map +1 -1
  81. package/dist/core/skills.js +126 -93
  82. package/dist/core/skills.js.map +1 -1
  83. package/dist/core/system-prompt.d.ts +3 -27
  84. package/dist/core/system-prompt.d.ts.map +1 -1
  85. package/dist/core/system-prompt.js +16 -103
  86. package/dist/core/system-prompt.js.map +1 -1
  87. package/dist/core/tools/bash.d.ts.map +1 -1
  88. package/dist/core/tools/bash.js +2 -1
  89. package/dist/core/tools/bash.js.map +1 -1
  90. package/dist/core/tools/read.d.ts.map +1 -1
  91. package/dist/core/tools/read.js +4 -4
  92. package/dist/core/tools/read.js.map +1 -1
  93. package/dist/index.d.ts +12 -7
  94. package/dist/index.d.ts.map +1 -1
  95. package/dist/index.js +8 -6
  96. package/dist/index.js.map +1 -1
  97. package/dist/main.d.ts.map +1 -1
  98. package/dist/main.js +209 -97
  99. package/dist/main.js.map +1 -1
  100. package/dist/modes/interactive/components/bordered-loader.d.ts +5 -1
  101. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  102. package/dist/modes/interactive/components/bordered-loader.js +29 -9
  103. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  104. package/dist/modes/interactive/components/config-selector.d.ts +71 -0
  105. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -0
  106. package/dist/modes/interactive/components/config-selector.js +468 -0
  107. package/dist/modes/interactive/components/config-selector.js.map +1 -0
  108. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  109. package/dist/modes/interactive/components/footer.js +4 -0
  110. package/dist/modes/interactive/components/footer.js.map +1 -1
  111. package/dist/modes/interactive/components/index.d.ts +1 -0
  112. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  113. package/dist/modes/interactive/components/index.js +1 -0
  114. package/dist/modes/interactive/components/index.js.map +1 -1
  115. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  116. package/dist/modes/interactive/components/oauth-selector.js +3 -4
  117. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  118. package/dist/modes/interactive/components/session-selector.d.ts +18 -1
  119. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  120. package/dist/modes/interactive/components/session-selector.js +195 -87
  121. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  122. package/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  123. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -0
  124. package/dist/modes/interactive/components/skill-invocation-message.js +47 -0
  125. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -0
  126. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  127. package/dist/modes/interactive/components/tool-execution.js +5 -5
  128. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  129. package/dist/modes/interactive/interactive-mode.d.ts +42 -2
  130. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  131. package/dist/modes/interactive/interactive-mode.js +538 -204
  132. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  133. package/dist/modes/interactive/theme/dark.json +1 -1
  134. package/dist/modes/interactive/theme/light.json +1 -1
  135. package/dist/modes/interactive/theme/theme-schema.json +8 -1
  136. package/dist/modes/interactive/theme/theme.d.ts +8 -1
  137. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  138. package/dist/modes/interactive/theme/theme.js +72 -25
  139. package/dist/modes/interactive/theme/theme.js.map +1 -1
  140. package/dist/modes/print-mode.d.ts.map +1 -1
  141. package/dist/modes/print-mode.js +7 -74
  142. package/dist/modes/print-mode.js.map +1 -1
  143. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  144. package/dist/modes/rpc/rpc-mode.js +17 -82
  145. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  146. package/dist/utils/git.d.ts +2 -0
  147. package/dist/utils/git.d.ts.map +1 -0
  148. package/dist/utils/git.js +6 -0
  149. package/dist/utils/git.js.map +1 -0
  150. package/dist/utils/shell.d.ts +1 -0
  151. package/dist/utils/shell.d.ts.map +1 -1
  152. package/dist/utils/shell.js +14 -1
  153. package/dist/utils/shell.js.map +1 -1
  154. package/dist/utils/sleep.d.ts +5 -0
  155. package/dist/utils/sleep.d.ts.map +1 -0
  156. package/dist/utils/sleep.js +17 -0
  157. package/dist/utils/sleep.js.map +1 -0
  158. package/docs/compaction.md +23 -21
  159. package/docs/custom-provider.md +538 -0
  160. package/docs/development.md +69 -0
  161. package/docs/extensions.md +182 -118
  162. package/docs/images/doom-extension.png +0 -0
  163. package/docs/images/interactive-mode.png +0 -0
  164. package/docs/images/tree-view.png +0 -0
  165. package/docs/json.md +79 -0
  166. package/docs/keybindings.md +162 -0
  167. package/docs/models.md +193 -0
  168. package/docs/packages.md +168 -0
  169. package/docs/prompt-templates.md +67 -0
  170. package/docs/providers.md +147 -0
  171. package/docs/sdk.md +111 -178
  172. package/docs/session.md +167 -16
  173. package/docs/settings.md +216 -0
  174. package/docs/shell-aliases.md +13 -0
  175. package/docs/skills.md +111 -202
  176. package/docs/terminal-setup.md +65 -0
  177. package/docs/themes.md +295 -0
  178. package/docs/tui.md +36 -5
  179. package/docs/windows.md +17 -0
  180. package/examples/README.md +1 -0
  181. package/examples/extensions/README.md +22 -2
  182. package/examples/extensions/bookmark.ts +50 -0
  183. package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  184. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  185. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  186. package/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
  187. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  188. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  189. package/examples/extensions/doom-overlay/doom/build.sh +1 -1
  190. package/examples/extensions/event-bus.ts +43 -0
  191. package/examples/extensions/message-renderer.ts +59 -0
  192. package/examples/extensions/session-name.ts +27 -0
  193. package/examples/extensions/with-deps/package-lock.json +2 -2
  194. package/examples/extensions/with-deps/package.json +1 -1
  195. package/examples/sdk/02-custom-model.ts +3 -3
  196. package/examples/sdk/03-custom-prompt.ts +20 -9
  197. package/examples/sdk/04-skills.ts +26 -27
  198. package/examples/sdk/06-extensions.ts +15 -6
  199. package/examples/sdk/07-context-files.ts +22 -18
  200. package/examples/sdk/08-prompt-templates.ts +19 -14
  201. package/examples/sdk/09-api-keys-and-oauth.ts +5 -12
  202. package/examples/sdk/10-settings.ts +3 -3
  203. package/examples/sdk/12-full-control.ts +16 -7
  204. package/examples/sdk/README.md +24 -30
  205. package/package.json +4 -4
  206. package/docs/theme.md +0 -617
  207. package/examples/extensions/chalk-logger.ts +0 -26
@@ -13,15 +13,35 @@
13
13
  * Modes use this class and add their own I/O layer on top.
14
14
  */
15
15
  import { readFileSync } from "node:fs";
16
- import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@mariozechner/pi-ai";
17
- import { getAuthPath } from "../config.js";
16
+ import { join } from "node:path";
17
+ import { isContextOverflow, modelsAreEqual, resetApiProviders, supportsXhigh } from "@mariozechner/pi-ai";
18
+ import { getDocsPath } from "../config.js";
18
19
  import { theme } from "../modes/interactive/theme/theme.js";
19
20
  import { stripFrontmatter } from "../utils/frontmatter.js";
21
+ import { sleep } from "../utils/sleep.js";
20
22
  import { executeBash as executeBashCommand, executeBashWithOperations } from "./bash-executor.js";
21
23
  import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
22
24
  import { exportSessionToHtml } from "./export-html/index.js";
23
25
  import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
26
+ import { ExtensionRunner, wrapRegisteredTools, wrapToolsWithExtensions, } from "./extensions/index.js";
24
27
  import { expandPromptTemplate } from "./prompt-templates.js";
28
+ import { buildSystemPrompt } from "./system-prompt.js";
29
+ import { createAllTools } from "./tools/index.js";
30
+ /**
31
+ * Parse a skill block from message text.
32
+ * Returns null if the text doesn't contain a skill block.
33
+ */
34
+ export function parseSkillBlock(text) {
35
+ const match = text.match(/^<skill name="([^"]+)" location="([^"]+)">\n([\s\S]*?)\n<\/skill>(?:\n\n([\s\S]+))?$/);
36
+ if (!match)
37
+ return null;
38
+ return {
39
+ name: match[1],
40
+ location: match[2],
41
+ content: match[3],
42
+ userMessage: match[4]?.trim() || undefined,
43
+ };
44
+ }
25
45
  // ============================================================================
26
46
  // Constants
27
47
  // ============================================================================
@@ -37,7 +57,6 @@ export class AgentSession {
37
57
  sessionManager;
38
58
  settingsManager;
39
59
  _scopedModels;
40
- _promptTemplates;
41
60
  // Event subscription state
42
61
  _unsubscribeAgent;
43
62
  _eventListeners = [];
@@ -63,34 +82,43 @@ export class AgentSession {
63
82
  // Extension system
64
83
  _extensionRunner = undefined;
65
84
  _turnIndex = 0;
66
- _skills;
67
- _skillWarnings;
68
- _skillsSettings;
85
+ _resourceLoader;
86
+ _customTools;
87
+ _baseToolRegistry = new Map();
88
+ _cwd;
89
+ _extensionRunnerRef;
90
+ _initialActiveToolNames;
91
+ _baseToolsOverride;
92
+ _extensionUIContext;
93
+ _extensionCommandContextActions;
94
+ _extensionShutdownHandler;
95
+ _extensionErrorListener;
96
+ _extensionErrorUnsubscriber;
69
97
  // Model registry for API key resolution
70
98
  _modelRegistry;
71
99
  // Tool registry for extension getTools/setTools
72
- _toolRegistry;
73
- // Function to rebuild system prompt when tools change
74
- _rebuildSystemPrompt;
100
+ _toolRegistry = new Map();
75
101
  // Base system prompt (without extension appends) - used to apply fresh appends each turn
76
- _baseSystemPrompt;
102
+ _baseSystemPrompt = "";
77
103
  constructor(config) {
78
104
  this.agent = config.agent;
79
105
  this.sessionManager = config.sessionManager;
80
106
  this.settingsManager = config.settingsManager;
81
107
  this._scopedModels = config.scopedModels ?? [];
82
- this._promptTemplates = config.promptTemplates ?? [];
83
- this._extensionRunner = config.extensionRunner;
84
- this._skills = config.skills ?? [];
85
- this._skillWarnings = config.skillWarnings ?? [];
86
- this._skillsSettings = config.skillsSettings;
108
+ this._resourceLoader = config.resourceLoader;
109
+ this._customTools = config.customTools ?? [];
110
+ this._cwd = config.cwd;
87
111
  this._modelRegistry = config.modelRegistry;
88
- this._toolRegistry = config.toolRegistry ?? new Map();
89
- this._rebuildSystemPrompt = config.rebuildSystemPrompt;
90
- this._baseSystemPrompt = config.agent.state.systemPrompt;
112
+ this._extensionRunnerRef = config.extensionRunnerRef;
113
+ this._initialActiveToolNames = config.initialActiveToolNames;
114
+ this._baseToolsOverride = config.baseToolsOverride;
91
115
  // Always subscribe to agent events for internal handling
92
116
  // (session persistence, extensions, auto-compaction, retry logic)
93
117
  this._unsubscribeAgent = this.agent.subscribe(this._handleAgentEvent);
118
+ this._buildRuntime({
119
+ activeToolNames: this._initialActiveToolNames,
120
+ includeAllExtensionTools: true,
121
+ });
94
122
  }
95
123
  /** Model registry for API key resolution and model discovery */
96
124
  get modelRegistry() {
@@ -334,10 +362,8 @@ export class AgentSession {
334
362
  }
335
363
  this.agent.setTools(tools);
336
364
  // Rebuild base system prompt with new tool set
337
- if (this._rebuildSystemPrompt) {
338
- this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
339
- this.agent.setSystemPrompt(this._baseSystemPrompt);
340
- }
365
+ this._baseSystemPrompt = this._rebuildSystemPrompt(validToolNames);
366
+ this.agent.setSystemPrompt(this._baseSystemPrompt);
341
367
  }
342
368
  /** Whether auto-compaction is currently running */
343
369
  get isCompacting() {
@@ -373,7 +399,23 @@ export class AgentSession {
373
399
  }
374
400
  /** File-based prompt templates */
375
401
  get promptTemplates() {
376
- return this._promptTemplates;
402
+ return this._resourceLoader.getPrompts().prompts;
403
+ }
404
+ _rebuildSystemPrompt(toolNames) {
405
+ const validToolNames = toolNames.filter((name) => this._baseToolRegistry.has(name));
406
+ const loaderSystemPrompt = this._resourceLoader.getSystemPrompt();
407
+ const loaderAppendSystemPrompt = this._resourceLoader.getAppendSystemPrompt();
408
+ const appendSystemPrompt = loaderAppendSystemPrompt.length > 0 ? loaderAppendSystemPrompt.join("\n\n") : undefined;
409
+ const loadedSkills = this._resourceLoader.getSkills().skills;
410
+ const loadedContextFiles = this._resourceLoader.getAgentsFiles().agentsFiles;
411
+ return buildSystemPrompt({
412
+ cwd: this._cwd,
413
+ skills: loadedSkills,
414
+ contextFiles: loadedContextFiles,
415
+ customPrompt: loaderSystemPrompt,
416
+ appendSystemPrompt,
417
+ selectedTools: validToolNames,
418
+ });
377
419
  }
378
420
  // =========================================================================
379
421
  // Prompting
@@ -415,7 +457,7 @@ export class AgentSession {
415
457
  let expandedText = currentText;
416
458
  if (expandPromptTemplates) {
417
459
  expandedText = this._expandSkillCommand(expandedText);
418
- expandedText = expandPromptTemplate(expandedText, [...this._promptTemplates]);
460
+ expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
419
461
  }
420
462
  // If streaming, queue via steer() or followUp() based on option
421
463
  if (this.isStreaming) {
@@ -435,7 +477,7 @@ export class AgentSession {
435
477
  // Validate model
436
478
  if (!this.model) {
437
479
  throw new Error("No model selected.\n\n" +
438
- `Use /login, set an API key environment variable, or create ${getAuthPath()}\n\n` +
480
+ `Use /login or set an API key environment variable. See ${join(getDocsPath(), "authentication.md")}\n\n` +
439
481
  "Then use /model to select a model.");
440
482
  }
441
483
  // Validate API key
@@ -448,7 +490,7 @@ export class AgentSession {
448
490
  `Run '/login ${this.model.provider}' to re-authenticate.`);
449
491
  }
450
492
  throw new Error(`No API key found for ${this.model.provider}.\n\n` +
451
- `Use /login, set an API key environment variable, or create ${getAuthPath()}`);
493
+ `Use /login or set an API key environment variable. See ${join(getDocsPath(), "authentication.md")}`);
452
494
  }
453
495
  // Check if we need to compact before sending (catches aborted responses)
454
496
  const lastAssistant = this._findLastAssistantMessage();
@@ -540,15 +582,14 @@ export class AgentSession {
540
582
  const spaceIndex = text.indexOf(" ");
541
583
  const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
542
584
  const args = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
543
- const skill = this._skills.find((s) => s.name === skillName);
585
+ const skill = this.resourceLoader.getSkills().skills.find((s) => s.name === skillName);
544
586
  if (!skill)
545
587
  return text; // Unknown skill, pass through
546
588
  try {
547
589
  const content = readFileSync(skill.filePath, "utf-8");
548
590
  const body = stripFrontmatter(content).trim();
549
- const header = `Skill location: ${skill.filePath}\nReferences are relative to ${skill.baseDir}.`;
550
- const skillMessage = `${header}\n\n${body}`;
551
- return args ? `${skillMessage}\n\n---\n\nUser: ${args}` : skillMessage;
591
+ const skillBlock = `<skill name="${skill.name}" location="${skill.filePath}">\nReferences are relative to ${skill.baseDir}.\n\n${body}\n</skill>`;
592
+ return args ? `${skillBlock}\n\n${args}` : skillBlock;
552
593
  }
553
594
  catch (err) {
554
595
  // Emit error like extension commands do
@@ -573,7 +614,7 @@ export class AgentSession {
573
614
  }
574
615
  // Expand skill commands and prompt templates
575
616
  let expandedText = this._expandSkillCommand(text);
576
- expandedText = expandPromptTemplate(expandedText, [...this._promptTemplates]);
617
+ expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
577
618
  await this._queueSteer(expandedText);
578
619
  }
579
620
  /**
@@ -589,7 +630,7 @@ export class AgentSession {
589
630
  }
590
631
  // Expand skill commands and prompt templates
591
632
  let expandedText = this._expandSkillCommand(text);
592
- expandedText = expandPromptTemplate(expandedText, [...this._promptTemplates]);
633
+ expandedText = expandPromptTemplate(expandedText, [...this.promptTemplates]);
593
634
  await this._queueFollowUp(expandedText);
594
635
  }
595
636
  /**
@@ -665,6 +706,8 @@ export class AgentSession {
665
706
  else {
666
707
  this.agent.appendMessage(appMessage);
667
708
  this.sessionManager.appendCustomMessageEntry(message.customType, message.content, message.display, message.details);
709
+ this._emit({ type: "message_start", message: appMessage });
710
+ this._emit({ type: "message_end", message: appMessage });
668
711
  }
669
712
  }
670
713
  /**
@@ -729,16 +772,8 @@ export class AgentSession {
729
772
  getFollowUpMessages() {
730
773
  return this._followUpMessages;
731
774
  }
732
- get skillsSettings() {
733
- return this._skillsSettings;
734
- }
735
- /** Skills loaded by SDK (empty if --no-skills or skills: [] was passed) */
736
- get skills() {
737
- return this._skills;
738
- }
739
- /** Skill loading warnings captured by SDK */
740
- get skillWarnings() {
741
- return this._skillWarnings;
775
+ get resourceLoader() {
776
+ return this._resourceLoader;
742
777
  }
743
778
  /**
744
779
  * Abort current operation and wait for agent to become idle.
@@ -752,7 +787,8 @@ export class AgentSession {
752
787
  * Start a new session, optionally with initial messages and parent tracking.
753
788
  * Clears all messages and starts a new session.
754
789
  * Listeners are preserved and will continue receiving events.
755
- * @param options - Optional initial messages and parent session path
790
+ * @param options.parentSession - Optional parent session path for tracking
791
+ * @param options.setup - Optional callback to initialize session (e.g., append messages)
756
792
  * @returns true if completed, false if cancelled by extension
757
793
  */
758
794
  async newSession(options) {
@@ -770,11 +806,18 @@ export class AgentSession {
770
806
  this._disconnectFromAgent();
771
807
  await this.abort();
772
808
  this.agent.reset();
773
- this.sessionManager.newSession(options);
809
+ this.sessionManager.newSession({ parentSession: options?.parentSession });
774
810
  this.agent.sessionId = this.sessionManager.getSessionId();
775
811
  this._steeringMessages = [];
776
812
  this._followUpMessages = [];
777
813
  this._pendingNextTurnMessages = [];
814
+ // Run setup callback if provided (e.g., to append initial messages)
815
+ if (options?.setup) {
816
+ await options.setup(this.sessionManager);
817
+ // Sync agent state with session manager after setup
818
+ const sessionContext = this.sessionManager.buildSessionContext();
819
+ this.agent.replaceMessages(sessionContext.messages);
820
+ }
778
821
  this._reconnectToAgent();
779
822
  // Emit session_switch event with reason "new" to extensions
780
823
  if (this._extensionRunner) {
@@ -879,12 +922,6 @@ export class AgentSession {
879
922
  await this._emitModelSelect(nextModel, currentModel, "cycle");
880
923
  return { model: nextModel, thinkingLevel: this.thinkingLevel, isScoped: false };
881
924
  }
882
- /**
883
- * Get all available models with valid API keys.
884
- */
885
- async getAvailableModels() {
886
- return this._modelRegistry.getAvailable();
887
- }
888
925
  // =========================================================================
889
926
  // Thinking Level Management
890
927
  // =========================================================================
@@ -1255,6 +1292,188 @@ export class AgentSession {
1255
1292
  get autoCompactionEnabled() {
1256
1293
  return this.settingsManager.getCompactionEnabled();
1257
1294
  }
1295
+ async bindExtensions(bindings) {
1296
+ if (bindings.uiContext !== undefined) {
1297
+ this._extensionUIContext = bindings.uiContext;
1298
+ }
1299
+ if (bindings.commandContextActions !== undefined) {
1300
+ this._extensionCommandContextActions = bindings.commandContextActions;
1301
+ }
1302
+ if (bindings.shutdownHandler !== undefined) {
1303
+ this._extensionShutdownHandler = bindings.shutdownHandler;
1304
+ }
1305
+ if (bindings.onError !== undefined) {
1306
+ this._extensionErrorListener = bindings.onError;
1307
+ }
1308
+ if (this._extensionRunner) {
1309
+ this._applyExtensionBindings(this._extensionRunner);
1310
+ await this._extensionRunner.emit({ type: "session_start" });
1311
+ }
1312
+ }
1313
+ _applyExtensionBindings(runner) {
1314
+ runner.setUIContext(this._extensionUIContext);
1315
+ runner.bindCommandContext(this._extensionCommandContextActions);
1316
+ this._extensionErrorUnsubscriber?.();
1317
+ this._extensionErrorUnsubscriber = this._extensionErrorListener
1318
+ ? runner.onError(this._extensionErrorListener)
1319
+ : undefined;
1320
+ }
1321
+ _bindExtensionCore(runner) {
1322
+ runner.bindCore({
1323
+ sendMessage: (message, options) => {
1324
+ this.sendCustomMessage(message, options).catch((err) => {
1325
+ runner.emitError({
1326
+ extensionPath: "<runtime>",
1327
+ event: "send_message",
1328
+ error: err instanceof Error ? err.message : String(err),
1329
+ });
1330
+ });
1331
+ },
1332
+ sendUserMessage: (content, options) => {
1333
+ this.sendUserMessage(content, options).catch((err) => {
1334
+ runner.emitError({
1335
+ extensionPath: "<runtime>",
1336
+ event: "send_user_message",
1337
+ error: err instanceof Error ? err.message : String(err),
1338
+ });
1339
+ });
1340
+ },
1341
+ appendEntry: (customType, data) => {
1342
+ this.sessionManager.appendCustomEntry(customType, data);
1343
+ },
1344
+ setSessionName: (name) => {
1345
+ this.sessionManager.appendSessionInfo(name);
1346
+ },
1347
+ getSessionName: () => {
1348
+ return this.sessionManager.getSessionName();
1349
+ },
1350
+ setLabel: (entryId, label) => {
1351
+ this.sessionManager.appendLabelChange(entryId, label);
1352
+ },
1353
+ getActiveTools: () => this.getActiveToolNames(),
1354
+ getAllTools: () => this.getAllTools(),
1355
+ setActiveTools: (toolNames) => this.setActiveToolsByName(toolNames),
1356
+ setModel: async (model) => {
1357
+ const key = await this.modelRegistry.getApiKey(model);
1358
+ if (!key)
1359
+ return false;
1360
+ await this.setModel(model);
1361
+ return true;
1362
+ },
1363
+ getThinkingLevel: () => this.thinkingLevel,
1364
+ setThinkingLevel: (level) => this.setThinkingLevel(level),
1365
+ }, {
1366
+ getModel: () => this.model,
1367
+ isIdle: () => !this.isStreaming,
1368
+ abort: () => this.abort(),
1369
+ hasPendingMessages: () => this.pendingMessageCount > 0,
1370
+ shutdown: () => {
1371
+ this._extensionShutdownHandler?.();
1372
+ },
1373
+ getContextUsage: () => this.getContextUsage(),
1374
+ compact: (options) => {
1375
+ void (async () => {
1376
+ try {
1377
+ const result = await this.compact(options?.customInstructions);
1378
+ options?.onComplete?.(result);
1379
+ }
1380
+ catch (error) {
1381
+ const err = error instanceof Error ? error : new Error(String(error));
1382
+ options?.onError?.(err);
1383
+ }
1384
+ })();
1385
+ },
1386
+ });
1387
+ }
1388
+ _buildRuntime(options) {
1389
+ const autoResizeImages = this.settingsManager.getImageAutoResize();
1390
+ const shellCommandPrefix = this.settingsManager.getShellCommandPrefix();
1391
+ const baseTools = this._baseToolsOverride
1392
+ ? this._baseToolsOverride
1393
+ : createAllTools(this._cwd, {
1394
+ read: { autoResizeImages },
1395
+ bash: { commandPrefix: shellCommandPrefix },
1396
+ });
1397
+ this._baseToolRegistry = new Map(Object.entries(baseTools).map(([name, tool]) => [name, tool]));
1398
+ const extensionsResult = this._resourceLoader.getExtensions();
1399
+ if (options.flagValues) {
1400
+ for (const [name, value] of options.flagValues) {
1401
+ extensionsResult.runtime.flagValues.set(name, value);
1402
+ }
1403
+ }
1404
+ const hasExtensions = extensionsResult.extensions.length > 0;
1405
+ const hasCustomTools = this._customTools.length > 0;
1406
+ this._extensionRunner =
1407
+ hasExtensions || hasCustomTools
1408
+ ? new ExtensionRunner(extensionsResult.extensions, extensionsResult.runtime, this._cwd, this.sessionManager, this._modelRegistry)
1409
+ : undefined;
1410
+ if (this._extensionRunnerRef) {
1411
+ this._extensionRunnerRef.current = this._extensionRunner;
1412
+ }
1413
+ if (this._extensionRunner) {
1414
+ this._bindExtensionCore(this._extensionRunner);
1415
+ this._applyExtensionBindings(this._extensionRunner);
1416
+ }
1417
+ const registeredTools = this._extensionRunner?.getAllRegisteredTools() ?? [];
1418
+ const allCustomTools = [
1419
+ ...registeredTools,
1420
+ ...this._customTools.map((def) => ({ definition: def, extensionPath: "<sdk>" })),
1421
+ ];
1422
+ const wrappedExtensionTools = this._extensionRunner
1423
+ ? wrapRegisteredTools(allCustomTools, this._extensionRunner)
1424
+ : [];
1425
+ const toolRegistry = new Map(this._baseToolRegistry);
1426
+ for (const tool of wrappedExtensionTools) {
1427
+ toolRegistry.set(tool.name, tool);
1428
+ }
1429
+ const defaultActiveToolNames = this._baseToolsOverride
1430
+ ? Object.keys(this._baseToolsOverride)
1431
+ : ["read", "bash", "edit", "write"];
1432
+ const baseActiveToolNames = options.activeToolNames ?? defaultActiveToolNames;
1433
+ const activeToolNameSet = new Set(baseActiveToolNames);
1434
+ if (options.includeAllExtensionTools) {
1435
+ for (const tool of wrappedExtensionTools) {
1436
+ activeToolNameSet.add(tool.name);
1437
+ }
1438
+ }
1439
+ const extensionToolNames = new Set(wrappedExtensionTools.map((tool) => tool.name));
1440
+ const activeBaseTools = Array.from(activeToolNameSet)
1441
+ .filter((name) => this._baseToolRegistry.has(name) && !extensionToolNames.has(name))
1442
+ .map((name) => this._baseToolRegistry.get(name));
1443
+ const activeExtensionTools = wrappedExtensionTools.filter((tool) => activeToolNameSet.has(tool.name));
1444
+ const activeToolsArray = [...activeBaseTools, ...activeExtensionTools];
1445
+ if (this._extensionRunner) {
1446
+ const wrappedActiveTools = wrapToolsWithExtensions(activeToolsArray, this._extensionRunner);
1447
+ this.agent.setTools(wrappedActiveTools);
1448
+ const wrappedAllTools = wrapToolsWithExtensions(Array.from(toolRegistry.values()), this._extensionRunner);
1449
+ this._toolRegistry = new Map(wrappedAllTools.map((tool) => [tool.name, tool]));
1450
+ }
1451
+ else {
1452
+ this.agent.setTools(activeToolsArray);
1453
+ this._toolRegistry = toolRegistry;
1454
+ }
1455
+ const systemPromptToolNames = Array.from(activeToolNameSet).filter((name) => this._baseToolRegistry.has(name));
1456
+ this._baseSystemPrompt = this._rebuildSystemPrompt(systemPromptToolNames);
1457
+ this.agent.setSystemPrompt(this._baseSystemPrompt);
1458
+ }
1459
+ async reload() {
1460
+ const previousFlagValues = this._extensionRunner?.getFlagValues();
1461
+ await this._extensionRunner?.emit({ type: "session_shutdown" });
1462
+ resetApiProviders();
1463
+ await this._resourceLoader.reload();
1464
+ this._buildRuntime({
1465
+ activeToolNames: this.getActiveToolNames(),
1466
+ flagValues: previousFlagValues,
1467
+ includeAllExtensionTools: true,
1468
+ });
1469
+ const hasBindings = this._extensionUIContext ||
1470
+ this._extensionCommandContextActions ||
1471
+ this._extensionShutdownHandler ||
1472
+ this._extensionErrorListener;
1473
+ if (this._extensionRunner && hasBindings) {
1474
+ await this._extensionRunner.emit({ type: "session_start" });
1475
+ }
1476
+ }
1258
1477
  // =========================================================================
1259
1478
  // Auto-Retry
1260
1479
  // =========================================================================
@@ -1270,8 +1489,8 @@ export class AgentSession {
1270
1489
  if (isContextOverflow(message, contextWindow))
1271
1490
  return false;
1272
1491
  const err = message.errorMessage;
1273
- // Match: overloaded_error, rate limit, 429, 500, 502, 503, 504, service unavailable, connection errors, fetch failed
1274
- return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers/i.test(err);
1492
+ // Match: overloaded_error, rate limit, 429, 500, 502, 503, 504, service unavailable, connection errors, fetch failed, terminated
1493
+ return /overloaded|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server error|internal error|connection.?error|connection.?refused|other side closed|fetch failed|upstream.?connect|reset before headers|terminated/i.test(err);
1275
1494
  }
1276
1495
  /**
1277
1496
  * Handle retryable errors with exponential backoff.
@@ -1316,7 +1535,7 @@ export class AgentSession {
1316
1535
  // Wait with exponential backoff (abortable)
1317
1536
  this._retryAbortController = new AbortController();
1318
1537
  try {
1319
- await this._sleep(delayMs, this._retryAbortController.signal);
1538
+ await sleep(delayMs, this._retryAbortController.signal);
1320
1539
  }
1321
1540
  catch {
1322
1541
  // Aborted during sleep - emit end event so UI can clean up
@@ -1341,22 +1560,6 @@ export class AgentSession {
1341
1560
  }, 0);
1342
1561
  return true;
1343
1562
  }
1344
- /**
1345
- * Sleep helper that respects abort signal.
1346
- */
1347
- _sleep(ms, signal) {
1348
- return new Promise((resolve, reject) => {
1349
- if (signal?.aborted) {
1350
- reject(new Error("Aborted"));
1351
- return;
1352
- }
1353
- const timeout = setTimeout(resolve, ms);
1354
- signal?.addEventListener("abort", () => {
1355
- clearTimeout(timeout);
1356
- reject(new Error("Aborted"));
1357
- });
1358
- });
1359
- }
1360
1563
  /**
1361
1564
  * Cancel in-progress retry.
1362
1565
  */