@dyyz1993/pi-coding-agent 0.70.6 → 0.74.5

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 (275) hide show
  1. package/CHANGELOG.md +266 -80
  2. package/README.md +48 -20
  3. package/dist/bun/cli.d.ts.map +1 -1
  4. package/dist/bun/cli.js +4 -2
  5. package/dist/bun/cli.js.map +1 -1
  6. package/dist/bun/restore-sandbox-env.d.ts +13 -0
  7. package/dist/bun/restore-sandbox-env.d.ts.map +1 -0
  8. package/dist/bun/restore-sandbox-env.js +32 -0
  9. package/dist/bun/restore-sandbox-env.js.map +1 -0
  10. package/dist/cli/args.d.ts +2 -1
  11. package/dist/cli/args.d.ts.map +1 -1
  12. package/dist/cli/args.js +34 -22
  13. package/dist/cli/args.js.map +1 -1
  14. package/dist/cli/list-models.d.ts.map +1 -1
  15. package/dist/cli/list-models.js +2 -1
  16. package/dist/cli/list-models.js.map +1 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +9 -4
  19. package/dist/cli.js.map +1 -1
  20. package/dist/config.d.ts +16 -8
  21. package/dist/config.d.ts.map +1 -1
  22. package/dist/config.js +238 -66
  23. package/dist/config.js.map +1 -1
  24. package/dist/core/agent-session-runtime.d.ts +10 -0
  25. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  26. package/dist/core/agent-session-runtime.js +14 -0
  27. package/dist/core/agent-session-runtime.js.map +1 -1
  28. package/dist/core/agent-session-services.d.ts +2 -1
  29. package/dist/core/agent-session-services.d.ts.map +1 -1
  30. package/dist/core/agent-session-services.js +1 -0
  31. package/dist/core/agent-session-services.js.map +1 -1
  32. package/dist/core/agent-session.d.ts +37 -26
  33. package/dist/core/agent-session.d.ts.map +1 -1
  34. package/dist/core/agent-session.js +1068 -1116
  35. package/dist/core/agent-session.js.map +1 -1
  36. package/dist/core/auth-guidance.d.ts +5 -0
  37. package/dist/core/auth-guidance.d.ts.map +1 -0
  38. package/dist/core/auth-guidance.js +21 -0
  39. package/dist/core/auth-guidance.js.map +1 -0
  40. package/dist/core/auth-storage.d.ts +9 -0
  41. package/dist/core/auth-storage.d.ts.map +1 -1
  42. package/dist/core/auth-storage.js +20 -1
  43. package/dist/core/auth-storage.js.map +1 -1
  44. package/dist/core/bash-executor.d.ts.map +1 -1
  45. package/dist/core/bash-executor.js +9 -6
  46. package/dist/core/bash-executor.js.map +1 -1
  47. package/dist/core/compaction/compaction.d.ts +0 -1
  48. package/dist/core/compaction/compaction.d.ts.map +1 -1
  49. package/dist/core/compaction/compaction.js.map +1 -1
  50. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -1
  51. package/dist/core/export-html/ansi-to-html.js +1 -1
  52. package/dist/core/export-html/ansi-to-html.js.map +1 -1
  53. package/dist/core/export-html/template.css +53 -4
  54. package/dist/core/export-html/template.js +84 -20
  55. package/dist/core/export-html/tool-renderer.d.ts +0 -6
  56. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  57. package/dist/core/export-html/tool-renderer.js +15 -2
  58. package/dist/core/export-html/tool-renderer.js.map +1 -1
  59. package/dist/core/extensions/index.d.ts +1 -1
  60. package/dist/core/extensions/index.d.ts.map +1 -1
  61. package/dist/core/extensions/index.js.map +1 -1
  62. package/dist/core/extensions/loader.d.ts +0 -1
  63. package/dist/core/extensions/loader.d.ts.map +1 -1
  64. package/dist/core/extensions/loader.js +49 -137
  65. package/dist/core/extensions/loader.js.map +1 -1
  66. package/dist/core/extensions/runner.d.ts +24 -20
  67. package/dist/core/extensions/runner.d.ts.map +1 -1
  68. package/dist/core/extensions/runner.js +128 -253
  69. package/dist/core/extensions/runner.js.map +1 -1
  70. package/dist/core/extensions/types.d.ts +88 -60
  71. package/dist/core/extensions/types.d.ts.map +1 -1
  72. package/dist/core/extensions/types.js +10 -0
  73. package/dist/core/extensions/types.js.map +1 -1
  74. package/dist/core/file-store/file-snapshot-manager.d.ts +95 -0
  75. package/dist/core/file-store/file-snapshot-manager.d.ts.map +1 -0
  76. package/dist/core/file-store/file-snapshot-manager.js +508 -0
  77. package/dist/core/file-store/file-snapshot-manager.js.map +1 -0
  78. package/dist/core/file-store/index.d.ts +5 -0
  79. package/dist/core/file-store/index.d.ts.map +1 -0
  80. package/dist/core/file-store/index.js +3 -0
  81. package/dist/core/file-store/index.js.map +1 -0
  82. package/dist/core/messages.d.ts +10 -2
  83. package/dist/core/messages.d.ts.map +1 -1
  84. package/dist/core/messages.js +23 -6
  85. package/dist/core/messages.js.map +1 -1
  86. package/dist/core/model-registry.d.ts +19 -1
  87. package/dist/core/model-registry.d.ts.map +1 -1
  88. package/dist/core/model-registry.js +97 -16
  89. package/dist/core/model-registry.js.map +1 -1
  90. package/dist/core/model-resolver.d.ts.map +1 -1
  91. package/dist/core/model-resolver.js +24 -15
  92. package/dist/core/model-resolver.js.map +1 -1
  93. package/dist/core/package-manager.d.ts +1 -0
  94. package/dist/core/package-manager.d.ts.map +1 -1
  95. package/dist/core/package-manager.js +61 -35
  96. package/dist/core/package-manager.js.map +1 -1
  97. package/dist/core/provider-display-names.d.ts +2 -0
  98. package/dist/core/provider-display-names.d.ts.map +1 -0
  99. package/dist/core/provider-display-names.js +32 -0
  100. package/dist/core/provider-display-names.js.map +1 -0
  101. package/dist/core/resource-loader.d.ts.map +1 -1
  102. package/dist/core/resource-loader.js +9 -21
  103. package/dist/core/resource-loader.js.map +1 -1
  104. package/dist/core/sdk.d.ts +9 -1
  105. package/dist/core/sdk.d.ts.map +1 -1
  106. package/dist/core/sdk.js +39 -18
  107. package/dist/core/sdk.js.map +1 -1
  108. package/dist/core/session-manager.d.ts +27 -17
  109. package/dist/core/session-manager.d.ts.map +1 -1
  110. package/dist/core/session-manager.js +133 -47
  111. package/dist/core/session-manager.js.map +1 -1
  112. package/dist/core/settings-manager.d.ts +22 -3
  113. package/dist/core/settings-manager.d.ts.map +1 -1
  114. package/dist/core/settings-manager.js +54 -6
  115. package/dist/core/settings-manager.js.map +1 -1
  116. package/dist/core/skills.d.ts.map +1 -1
  117. package/dist/core/skills.js +3 -8
  118. package/dist/core/skills.js.map +1 -1
  119. package/dist/core/slash-commands.d.ts.map +1 -1
  120. package/dist/core/slash-commands.js +4 -3
  121. package/dist/core/slash-commands.js.map +1 -1
  122. package/dist/core/tools/bash.d.ts +0 -2
  123. package/dist/core/tools/bash.d.ts.map +1 -1
  124. package/dist/core/tools/bash.js +155 -110
  125. package/dist/core/tools/bash.js.map +1 -1
  126. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  127. package/dist/core/tools/edit-diff.js +3 -2
  128. package/dist/core/tools/edit-diff.js.map +1 -1
  129. package/dist/core/tools/edit.d.ts.map +1 -1
  130. package/dist/core/tools/edit.js +4 -3
  131. package/dist/core/tools/edit.js.map +1 -1
  132. package/dist/core/tools/find.d.ts.map +1 -1
  133. package/dist/core/tools/find.js +1 -1
  134. package/dist/core/tools/find.js.map +1 -1
  135. package/dist/core/tools/grep.d.ts.map +1 -1
  136. package/dist/core/tools/grep.js +1 -1
  137. package/dist/core/tools/grep.js.map +1 -1
  138. package/dist/core/tools/output-accumulator.d.ts +50 -0
  139. package/dist/core/tools/output-accumulator.d.ts.map +1 -0
  140. package/dist/core/tools/output-accumulator.js +178 -0
  141. package/dist/core/tools/output-accumulator.js.map +1 -0
  142. package/dist/core/tools/read.d.ts.map +1 -1
  143. package/dist/core/tools/read.js +70 -13
  144. package/dist/core/tools/read.js.map +1 -1
  145. package/dist/index.d.ts +1 -1
  146. package/dist/index.d.ts.map +1 -1
  147. package/dist/index.js.map +1 -1
  148. package/dist/main.d.ts.map +1 -1
  149. package/dist/main.js +17 -39
  150. package/dist/main.js.map +1 -1
  151. package/dist/migrations.d.ts +1 -1
  152. package/dist/migrations.d.ts.map +1 -1
  153. package/dist/migrations.js +3 -3
  154. package/dist/migrations.js.map +1 -1
  155. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  156. package/dist/modes/interactive/components/config-selector.js +3 -1
  157. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  158. package/dist/modes/interactive/components/extension-selector.d.ts +1 -4
  159. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  160. package/dist/modes/interactive/components/extension-selector.js +14 -56
  161. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  162. package/dist/modes/interactive/components/login-dialog.d.ts +5 -1
  163. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  164. package/dist/modes/interactive/components/login-dialog.js +19 -4
  165. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  166. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  167. package/dist/modes/interactive/components/model-selector.js +1 -1
  168. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  169. package/dist/modes/interactive/components/oauth-selector.d.ts +18 -6
  170. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  171. package/dist/modes/interactive/components/oauth-selector.js +93 -25
  172. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  173. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  174. package/dist/modes/interactive/components/scoped-models-selector.js +1 -1
  175. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  176. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  177. package/dist/modes/interactive/components/session-selector.js +3 -7
  178. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  179. package/dist/modes/interactive/components/settings-selector.d.ts +5 -0
  180. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  181. package/dist/modes/interactive/components/settings-selector.js +53 -1
  182. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  183. package/dist/modes/interactive/interactive-mode.d.ts +20 -4
  184. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  185. package/dist/modes/interactive/interactive-mode.js +423 -186
  186. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  187. package/dist/modes/interactive/theme/dark.json +1 -1
  188. package/dist/modes/interactive/theme/light.json +1 -1
  189. package/dist/modes/print-mode.d.ts +3 -0
  190. package/dist/modes/print-mode.d.ts.map +1 -1
  191. package/dist/modes/print-mode.js +62 -19
  192. package/dist/modes/print-mode.js.map +1 -1
  193. package/dist/modes/rpc/rpc-client.d.ts +80 -60
  194. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  195. package/dist/modes/rpc/rpc-client.js +108 -93
  196. package/dist/modes/rpc/rpc-client.js.map +1 -1
  197. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  198. package/dist/modes/rpc/rpc-mode.js +106 -0
  199. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  200. package/dist/modes/rpc/rpc-types.d.ts +115 -0
  201. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  202. package/dist/modes/rpc/rpc-types.js.map +1 -1
  203. package/dist/package-manager-cli.d.ts.map +1 -1
  204. package/dist/package-manager-cli.js +238 -12
  205. package/dist/package-manager-cli.js.map +1 -1
  206. package/dist/utils/child-process.d.ts +1 -0
  207. package/dist/utils/child-process.d.ts.map +1 -1
  208. package/dist/utils/child-process.js +8 -0
  209. package/dist/utils/child-process.js.map +1 -1
  210. package/dist/utils/clipboard-image.d.ts.map +1 -1
  211. package/dist/utils/clipboard-image.js +2 -2
  212. package/dist/utils/clipboard-image.js.map +1 -1
  213. package/dist/utils/clipboard.d.ts.map +1 -1
  214. package/dist/utils/clipboard.js +84 -45
  215. package/dist/utils/clipboard.js.map +1 -1
  216. package/dist/utils/paths.d.ts +9 -0
  217. package/dist/utils/paths.d.ts.map +1 -1
  218. package/dist/utils/paths.js +31 -0
  219. package/dist/utils/paths.js.map +1 -1
  220. package/dist/utils/pi-user-agent.d.ts +2 -0
  221. package/dist/utils/pi-user-agent.d.ts.map +1 -0
  222. package/dist/utils/pi-user-agent.js +5 -0
  223. package/dist/utils/pi-user-agent.js.map +1 -0
  224. package/dist/utils/structured-output.d.ts +10 -0
  225. package/dist/utils/structured-output.d.ts.map +1 -0
  226. package/dist/utils/structured-output.js +57 -0
  227. package/dist/utils/structured-output.js.map +1 -0
  228. package/dist/utils/tools-manager.d.ts.map +1 -1
  229. package/dist/utils/tools-manager.js +6 -2
  230. package/dist/utils/tools-manager.js.map +1 -1
  231. package/dist/utils/version-check.d.ts +14 -0
  232. package/dist/utils/version-check.d.ts.map +1 -0
  233. package/dist/utils/version-check.js +77 -0
  234. package/dist/utils/version-check.js.map +1 -0
  235. package/docs/compaction.md +14 -14
  236. package/docs/custom-provider.md +40 -31
  237. package/docs/development.md +1 -1
  238. package/docs/docs.json +148 -0
  239. package/docs/extensions.md +116 -56
  240. package/docs/index.md +70 -0
  241. package/docs/json.md +4 -4
  242. package/docs/models.md +150 -3
  243. package/docs/packages.md +10 -5
  244. package/docs/providers.md +62 -17
  245. package/docs/quickstart.md +142 -0
  246. package/docs/rollback-architecture.md +693 -0
  247. package/docs/rollback-test-cases.md +412 -0
  248. package/docs/rpc.md +1 -1
  249. package/docs/sdk.md +26 -26
  250. package/docs/{session.md → session-format.md} +6 -6
  251. package/docs/sessions.md +137 -0
  252. package/docs/settings.md +52 -9
  253. package/docs/termux.md +1 -1
  254. package/docs/themes.md +2 -2
  255. package/docs/tui.md +20 -20
  256. package/docs/usage.md +277 -0
  257. package/examples/extensions/README.md +2 -4
  258. package/examples/extensions/border-status-editor.ts +150 -0
  259. package/examples/extensions/commands.ts +2 -2
  260. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  261. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  262. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  263. package/examples/extensions/dynamic-resources/dynamic.json +1 -1
  264. package/examples/extensions/git-checkpoint.ts +1 -1
  265. package/examples/extensions/handoff.ts +49 -11
  266. package/examples/extensions/plan-mode/index.ts +1 -1
  267. package/examples/extensions/sandbox/package-lock.json +5 -5
  268. package/examples/extensions/sandbox/package.json +1 -1
  269. package/examples/extensions/subagent/agents.ts +126 -0
  270. package/examples/extensions/with-deps/package-lock.json +2 -2
  271. package/examples/extensions/with-deps/package.json +1 -1
  272. package/examples/sdk/README.md +2 -2
  273. package/package.json +7 -16
  274. package/docs/tree.md +0 -233
  275. package/examples/extensions/antigravity-image-gen.ts +0 -418
@@ -1,9 +1,7 @@
1
1
  /**
2
2
  * Extension runner - executes extensions and manages their lifecycle.
3
3
  */
4
- import { randomUUID } from "node:crypto";
5
4
  import { theme } from "../../modes/interactive/theme/theme.js";
6
- import { getCwdDataDir, getGlobalDataDir, getProjectDataDir, getSessionDataDir, resolveProjectRoot, } from "../storage.js";
7
5
  // Extension shortcuts compete with canonical keybinding ids from keybindings.json.
8
6
  // Only editor-global shortcuts are reserved here. Picker-specific bindings are not.
9
7
  const RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS = [
@@ -66,6 +64,7 @@ const noOpUIContext = {
66
64
  onTerminalInput: () => () => { },
67
65
  setStatus: () => { },
68
66
  setWorkingMessage: () => { },
67
+ setWorkingVisible: () => { },
69
68
  setWorkingIndicator: () => { },
70
69
  setHiddenThinkingLabel: () => { },
71
70
  setWidget: () => { },
@@ -79,6 +78,7 @@ const noOpUIContext = {
79
78
  editor: async () => undefined,
80
79
  addAutocompleteProvider: () => { },
81
80
  setEditorComponent: () => { },
81
+ getEditorComponent: () => undefined,
82
82
  get theme() {
83
83
  return theme;
84
84
  },
@@ -92,7 +92,6 @@ export class ExtensionRunner {
92
92
  extensions;
93
93
  runtime;
94
94
  uiContext;
95
- uiContextOriginal;
96
95
  cwd;
97
96
  sessionManager;
98
97
  modelRegistry;
@@ -100,13 +99,20 @@ export class ExtensionRunner {
100
99
  getModel = () => undefined;
101
100
  isIdleFn = () => true;
102
101
  getSignalFn = () => undefined;
103
- getSessionSignalFn = () => AbortSignal.abort();
104
102
  waitForIdleFn = async () => { };
105
103
  abortFn = () => { };
106
104
  hasPendingMessagesFn = () => false;
107
105
  getContextUsageFn = () => undefined;
108
106
  compactFn = () => { };
109
107
  getSystemPromptFn = () => "";
108
+ getSessionSignalFn = () => AbortSignal.abort();
109
+ getExtensionNameFn = () => "";
110
+ getProjectRootFn = () => this.cwd;
111
+ getSessionDataDirFn = () => "";
112
+ getProjectDataDirFn = () => "";
113
+ getCwdDataDirFn = () => "";
114
+ getGlobalDataDirFn = () => "";
115
+ respondUIFn = () => { };
110
116
  newSessionHandler = async () => ({ cancelled: false });
111
117
  forkHandler = async () => ({ cancelled: false });
112
118
  navigateTreeHandler = async () => ({ cancelled: false });
@@ -116,12 +122,30 @@ export class ExtensionRunner {
116
122
  shortcutDiagnostics = [];
117
123
  commandDiagnostics = [];
118
124
  staleMessage;
119
- pendingUIResponses = new Map();
125
+ _fileSnapshotManager = null;
126
+ setFileSnapshotManager(manager) {
127
+ this._fileSnapshotManager = manager;
128
+ }
129
+ setContextDirFns(fns) {
130
+ if (fns.getExtensionName)
131
+ this.getExtensionNameFn = fns.getExtensionName;
132
+ if (fns.getProjectRoot)
133
+ this.getProjectRootFn = fns.getProjectRoot;
134
+ if (fns.getSessionDataDir)
135
+ this.getSessionDataDirFn = fns.getSessionDataDir;
136
+ if (fns.getProjectDataDir)
137
+ this.getProjectDataDirFn = fns.getProjectDataDir;
138
+ if (fns.getCwdDataDir)
139
+ this.getCwdDataDirFn = fns.getCwdDataDir;
140
+ if (fns.getGlobalDataDir)
141
+ this.getGlobalDataDirFn = fns.getGlobalDataDir;
142
+ if (fns.respondUI)
143
+ this.respondUIFn = fns.respondUI;
144
+ }
120
145
  constructor(extensions, runtime, cwd, sessionManager, modelRegistry) {
121
146
  this.extensions = extensions;
122
147
  this.runtime = runtime;
123
148
  this.uiContext = noOpUIContext;
124
- this.uiContextOriginal = undefined;
125
149
  this.cwd = cwd;
126
150
  this.sessionManager = sessionManager;
127
151
  this.modelRegistry = modelRegistry;
@@ -131,7 +155,6 @@ export class ExtensionRunner {
131
155
  this.runtime.sendMessage = actions.sendMessage;
132
156
  this.runtime.sendUserMessage = actions.sendUserMessage;
133
157
  this.runtime.appendEntry = actions.appendEntry;
134
- this.runtime.foldEntry = actions.foldEntry;
135
158
  this.runtime.setSessionName = actions.setSessionName;
136
159
  this.runtime.getSessionName = actions.getSessionName;
137
160
  this.runtime.setLabel = actions.setLabel;
@@ -143,21 +166,19 @@ export class ExtensionRunner {
143
166
  this.runtime.setModel = actions.setModel;
144
167
  this.runtime.getThinkingLevel = actions.getThinkingLevel;
145
168
  this.runtime.setThinkingLevel = actions.setThinkingLevel;
146
- this.runtime.registerChannel = actions.registerChannel;
147
- this.runtime.callLLM = actions.callLLM;
148
- this.runtime.callLLMStructured = actions.callLLMStructured;
149
- this.runtime.background = actions.background;
150
169
  // Context actions (required)
151
170
  this.getModel = contextActions.getModel;
152
171
  this.isIdleFn = contextActions.isIdle;
153
172
  this.getSignalFn = contextActions.getSignal;
154
- this.getSessionSignalFn = contextActions.getSessionSignal;
155
173
  this.abortFn = contextActions.abort;
156
174
  this.hasPendingMessagesFn = contextActions.hasPendingMessages;
157
175
  this.shutdownHandler = contextActions.shutdown;
158
176
  this.getContextUsageFn = contextActions.getContextUsage;
159
177
  this.compactFn = contextActions.compact;
160
178
  this.getSystemPromptFn = contextActions.getSystemPrompt;
179
+ if (contextActions.getSessionSignal) {
180
+ this.getSessionSignalFn = contextActions.getSessionSignal;
181
+ }
161
182
  // Flush provider registrations queued during extension loading
162
183
  for (const { name, config, extensionPath } of this.runtime.pendingProviderRegistrations) {
163
184
  try {
@@ -178,10 +199,6 @@ export class ExtensionRunner {
178
199
  }
179
200
  }
180
201
  this.runtime.pendingProviderRegistrations = [];
181
- // Set registerChannel on the runtime. If this is the throwing stub (non-RPC mode),
182
- // pending channel registrations remain queued until bindExtensions() provides the
183
- // real implementation via flushPendingChannels().
184
- this.runtime.registerChannel = actions.registerChannel;
185
202
  // From this point on, provider registration/unregistration takes effect immediately
186
203
  // without requiring a /reload.
187
204
  this.runtime.registerProvider = (name, config) => {
@@ -216,51 +233,18 @@ export class ExtensionRunner {
216
233
  this.switchSessionHandler = async () => ({ cancelled: false });
217
234
  this.reloadHandler = async () => { };
218
235
  }
219
- /**
220
- * Flush pending channel registrations with the real registerChannel implementation.
221
- * Called from bindCore() and again from bindExtensions() when the real registerChannel
222
- * becomes available (e.g. in RPC mode).
223
- */
224
- flushPendingChannels(registerChannel) {
225
- if (this.runtime.pendingChannelRegistrations.length === 0)
226
- return;
227
- for (const pending of this.runtime.pendingChannelRegistrations) {
228
- try {
229
- const channel = registerChannel(pending.name);
230
- this.runtime.resolvedChannels.set(pending.name, channel);
231
- pending.resolve(channel);
232
- }
233
- catch (err) {
234
- pending.reject(err instanceof Error ? err : new Error(String(err)));
235
- }
236
- }
237
- this.runtime.pendingChannelRegistrations = [];
238
- this.runtime.registerChannel = registerChannel;
239
- }
240
- updateRegisterChannel(registerChannel) {
241
- this.runtime.registerChannel = registerChannel;
242
- }
243
236
  setUIContext(uiContext) {
244
- this.uiContextOriginal = uiContext;
245
- this.uiContext = this.wrapUIForInterception(uiContext ?? noOpUIContext);
237
+ this.uiContext = uiContext ?? noOpUIContext;
246
238
  }
247
239
  getUIContext() {
248
240
  return this.uiContext;
249
241
  }
250
242
  hasUI() {
251
- return this.uiContextOriginal !== undefined && this.uiContextOriginal !== noOpUIContext;
243
+ return this.uiContext !== noOpUIContext;
252
244
  }
253
245
  getExtensionPaths() {
254
246
  return this.extensions.map((e) => e.path);
255
247
  }
256
- getExtensionNameByPath(extensionPath) {
257
- for (const ext of this.extensions) {
258
- if (ext.path === extensionPath) {
259
- return ext.name;
260
- }
261
- }
262
- return undefined;
263
- }
264
248
  /** Get all registered tools from all extensions (first registration per name wins). */
265
249
  getAllRegisteredTools() {
266
250
  const toolsByName = new Map();
@@ -333,7 +317,7 @@ export class ExtensionRunner {
333
317
  getShortcutDiagnostics() {
334
318
  return this.shortcutDiagnostics;
335
319
  }
336
- invalidate(message = "This extension instance is stale after session replacement or reload.") {
320
+ invalidate(message = "This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession(), ctx.fork(), ctx.switchSession(), or ctx.reload(). For newSession, fork, and switchSession, move post-replacement work into withSession and use the ctx passed to withSession. For reload, do not use the old ctx after await ctx.reload().") {
337
321
  if (!this.staleMessage) {
338
322
  this.staleMessage = message;
339
323
  this.runtime.invalidate(message);
@@ -421,7 +405,7 @@ export class ExtensionRunner {
421
405
  * Create an ExtensionContext for use in event handlers and tool execution.
422
406
  * Context values are resolved at call time, so changes via bindCore/bindUI are reflected.
423
407
  */
424
- createContext(ext) {
408
+ createContext() {
425
409
  const runner = this;
426
410
  const getModel = this.getModel;
427
411
  return {
@@ -437,32 +421,6 @@ export class ExtensionRunner {
437
421
  runner.assertActive();
438
422
  return runner.cwd;
439
423
  },
440
- get extensionName() {
441
- runner.assertActive();
442
- return ext?.name ?? "unknown";
443
- },
444
- get projectRoot() {
445
- runner.assertActive();
446
- return resolveProjectRoot(runner.cwd);
447
- },
448
- get sessionDataDir() {
449
- runner.assertActive();
450
- const sessionDir = runner.sessionManager.getSessionDir();
451
- const sessionId = runner.sessionManager.getSessionId();
452
- return getSessionDataDir(sessionDir, sessionId, ext?.name ?? "unknown");
453
- },
454
- get projectDataDir() {
455
- runner.assertActive();
456
- return getProjectDataDir(resolveProjectRoot(runner.cwd), ext?.name ?? "unknown");
457
- },
458
- get cwdDataDir() {
459
- runner.assertActive();
460
- return getCwdDataDir(runner.cwd, ext?.name ?? "unknown");
461
- },
462
- get globalDataDir() {
463
- runner.assertActive();
464
- return getGlobalDataDir(ext?.name ?? "unknown");
465
- },
466
424
  get sessionManager() {
467
425
  runner.assertActive();
468
426
  return runner.sessionManager;
@@ -483,10 +441,6 @@ export class ExtensionRunner {
483
441
  runner.assertActive();
484
442
  return runner.getSignalFn();
485
443
  },
486
- get sessionSignal() {
487
- runner.assertActive();
488
- return runner.getSessionSignalFn();
489
- },
490
444
  abort: () => {
491
445
  runner.assertActive();
492
446
  runner.abortFn();
@@ -511,23 +465,49 @@ export class ExtensionRunner {
511
465
  runner.assertActive();
512
466
  return runner.getSystemPromptFn();
513
467
  },
514
- respondUI: (id, result) => {
468
+ get extensionName() {
469
+ runner.assertActive();
470
+ return runner.getExtensionNameFn();
471
+ },
472
+ get projectRoot() {
473
+ runner.assertActive();
474
+ return runner.getProjectRootFn();
475
+ },
476
+ get sessionDataDir() {
477
+ runner.assertActive();
478
+ return runner.getSessionDataDirFn();
479
+ },
480
+ get projectDataDir() {
481
+ runner.assertActive();
482
+ return runner.getProjectDataDirFn();
483
+ },
484
+ get cwdDataDir() {
515
485
  runner.assertActive();
516
- runner.respondUI(id, result);
486
+ return runner.getCwdDataDirFn();
487
+ },
488
+ get globalDataDir() {
489
+ runner.assertActive();
490
+ return runner.getGlobalDataDirFn();
491
+ },
492
+ get sessionSignal() {
493
+ runner.assertActive();
494
+ return runner.getSessionSignalFn();
495
+ },
496
+ respondUI(id, result) {
497
+ runner.assertActive();
498
+ runner.respondUIFn(id, result);
499
+ },
500
+ get fileSnapshotManager() {
501
+ runner.assertActive();
502
+ return runner._fileSnapshotManager;
517
503
  },
518
504
  };
519
505
  }
520
- createCommandContext(commandName) {
521
- let cmdExt;
522
- if (commandName) {
523
- for (const ext of this.extensions) {
524
- if (ext.commands.has(commandName)) {
525
- cmdExt = ext;
526
- break;
527
- }
528
- }
529
- }
530
- const context = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this.createContext(cmdExt)));
506
+ createCommandContext() {
507
+ // Use property descriptors instead of object spread so the guarded getters from
508
+ // createContext() stay lazy. A spread would eagerly read them once and freeze the
509
+ // old values into the returned object, bypassing stale-instance checks.
510
+ const context = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this.createContext()));
531
511
  context.waitForIdle = () => {
532
512
  this.assertActive();
533
513
  return this.waitForIdleFn();
@@ -561,12 +541,12 @@ export class ExtensionRunner {
561
541
  event.type === "session_before_tree");
562
542
  }
563
543
  async emit(event) {
544
+ const ctx = this.createContext();
564
545
  let result;
565
546
  for (const ext of this.extensions) {
566
547
  const handlers = ext.handlers.get(event.type);
567
548
  if (!handlers || handlers.length === 0)
568
549
  continue;
569
- const ctx = this.createContext(ext);
570
550
  for (const handler of handlers) {
571
551
  try {
572
552
  const handlerResult = await handler(event, ctx);
@@ -591,14 +571,53 @@ export class ExtensionRunner {
591
571
  }
592
572
  return result;
593
573
  }
574
+ async emitMessageEnd(event) {
575
+ const ctx = this.createContext();
576
+ let currentMessage = event.message;
577
+ let modified = false;
578
+ for (const ext of this.extensions) {
579
+ const handlers = ext.handlers.get("message_end");
580
+ if (!handlers || handlers.length === 0)
581
+ continue;
582
+ for (const handler of handlers) {
583
+ try {
584
+ const currentEvent = { ...event, message: currentMessage };
585
+ const handlerResult = (await handler(currentEvent, ctx));
586
+ if (!handlerResult?.message)
587
+ continue;
588
+ if (handlerResult.message.role !== currentMessage.role) {
589
+ this.emitError({
590
+ extensionPath: ext.path,
591
+ event: "message_end",
592
+ error: "message_end handlers must return a message with the same role",
593
+ });
594
+ continue;
595
+ }
596
+ currentMessage = handlerResult.message;
597
+ modified = true;
598
+ }
599
+ catch (err) {
600
+ const message = err instanceof Error ? err.message : String(err);
601
+ const stack = err instanceof Error ? err.stack : undefined;
602
+ this.emitError({
603
+ extensionPath: ext.path,
604
+ event: "message_end",
605
+ error: message,
606
+ stack,
607
+ });
608
+ }
609
+ }
610
+ }
611
+ return modified ? currentMessage : undefined;
612
+ }
594
613
  async emitToolResult(event) {
614
+ const ctx = this.createContext();
595
615
  const currentEvent = { ...event };
596
616
  let modified = false;
597
617
  for (const ext of this.extensions) {
598
618
  const handlers = ext.handlers.get("tool_result");
599
619
  if (!handlers || handlers.length === 0)
600
620
  continue;
601
- const ctx = this.createContext(ext);
602
621
  for (const handler of handlers) {
603
622
  try {
604
623
  const handlerResult = (await handler(currentEvent, ctx));
@@ -639,12 +658,12 @@ export class ExtensionRunner {
639
658
  };
640
659
  }
641
660
  async emitToolCall(event) {
661
+ const ctx = this.createContext();
642
662
  let result;
643
663
  for (const ext of this.extensions) {
644
664
  const handlers = ext.handlers.get("tool_call");
645
665
  if (!handlers || handlers.length === 0)
646
666
  continue;
647
- const ctx = this.createContext(ext);
648
667
  for (const handler of handlers) {
649
668
  const handlerResult = await handler(event, ctx);
650
669
  if (handlerResult) {
@@ -658,11 +677,11 @@ export class ExtensionRunner {
658
677
  return result;
659
678
  }
660
679
  async emitUserBash(event) {
680
+ const ctx = this.createContext();
661
681
  for (const ext of this.extensions) {
662
682
  const handlers = ext.handlers.get("user_bash");
663
683
  if (!handlers || handlers.length === 0)
664
684
  continue;
665
- const ctx = this.createContext(ext);
666
685
  for (const handler of handlers) {
667
686
  try {
668
687
  const handlerResult = await handler(event, ctx);
@@ -684,157 +703,13 @@ export class ExtensionRunner {
684
703
  }
685
704
  return undefined;
686
705
  }
687
- createAsyncUIPromise(id, extract) {
688
- if (!this.hasHandlers("ui"))
689
- return undefined;
690
- return new Promise((resolve) => {
691
- this.pendingUIResponses.set(id, (result) => {
692
- this.pendingUIResponses.delete(id);
693
- resolve(extract(result));
694
- });
695
- });
696
- }
697
- wrapUIForInterception(original) {
698
- return {
699
- ...original,
700
- confirm: async (title, message, opts) => {
701
- if (!this.hasHandlers("ui"))
702
- return original.confirm(title, message, opts);
703
- const id = randomUUID();
704
- const asyncPromise = this.createAsyncUIPromise(id, (r) => r?.action === "responded" && r.confirmed !== undefined ? r.confirmed : false);
705
- const result = await this.emitUIEvent({
706
- type: "ui",
707
- id,
708
- method: "confirm",
709
- title,
710
- message,
711
- signal: opts?.signal,
712
- timeout: opts?.timeout,
713
- });
714
- if (result?.action === "responded" && result.confirmed !== undefined) {
715
- this.pendingUIResponses.delete(id);
716
- return result.confirmed;
717
- }
718
- return Promise.race([original.confirm(title, message, opts), asyncPromise]);
719
- },
720
- select: async (title, options, opts) => {
721
- if (!this.hasHandlers("ui"))
722
- return original.select(title, options, opts);
723
- const id = randomUUID();
724
- const multiple = opts?.multiple;
725
- const asyncPromise = this.createAsyncUIPromise(id, (r) => r?.action === "responded" ? r.value : undefined);
726
- const result = await this.emitUIEvent({
727
- type: "ui",
728
- id,
729
- method: "select",
730
- title,
731
- options,
732
- multiple,
733
- signal: opts?.signal,
734
- timeout: opts?.timeout,
735
- });
736
- if (result?.action === "responded") {
737
- this.pendingUIResponses.delete(id);
738
- if (multiple && result.value !== undefined) {
739
- return Array.isArray(result.value) ? result.value : [result.value];
740
- }
741
- return result.value;
742
- }
743
- return Promise.race([original.select(title, options, opts), asyncPromise]);
744
- },
745
- input: async (title, placeholder, opts) => {
746
- if (!this.hasHandlers("ui"))
747
- return original.input(title, placeholder, opts);
748
- const id = randomUUID();
749
- const asyncPromise = this.createAsyncUIPromise(id, (r) => r?.action === "responded" ? r.value : undefined);
750
- const result = await this.emitUIEvent({
751
- type: "ui",
752
- id,
753
- method: "input",
754
- title,
755
- placeholder,
756
- signal: opts?.signal,
757
- timeout: opts?.timeout,
758
- });
759
- if (result?.action === "responded") {
760
- this.pendingUIResponses.delete(id);
761
- return result.value;
762
- }
763
- return Promise.race([original.input(title, placeholder, opts), asyncPromise]);
764
- },
765
- editor: async (title, prefill) => {
766
- if (!this.hasHandlers("ui"))
767
- return original.editor(title, prefill);
768
- const id = randomUUID();
769
- const asyncPromise = this.createAsyncUIPromise(id, (r) => r?.action === "responded" ? r.value : undefined);
770
- const result = await this.emitUIEvent({
771
- type: "ui",
772
- id,
773
- method: "editor",
774
- title,
775
- prefill,
776
- });
777
- if (result?.action === "responded") {
778
- this.pendingUIResponses.delete(id);
779
- return result.value;
780
- }
781
- return Promise.race([original.editor(title, prefill), asyncPromise]);
782
- },
783
- notify: (message, notifyType) => {
784
- if (this.hasHandlers("ui")) {
785
- this.emitUIEvent({
786
- type: "ui",
787
- id: randomUUID(),
788
- method: "notify",
789
- title: message,
790
- message,
791
- notifyType,
792
- }).catch(() => { });
793
- }
794
- return original.notify(message, notifyType);
795
- },
796
- };
797
- }
798
- async emitUIEvent(event) {
799
- for (const ext of this.extensions) {
800
- const ctx = this.createContext(ext);
801
- for (const handler of ext.handlers.get("ui") ?? []) {
802
- try {
803
- const result = (await handler(event, ctx));
804
- if (result?.action === "responded")
805
- return result;
806
- }
807
- catch (err) {
808
- const message = err instanceof Error ? err.message : String(err);
809
- const stack = err instanceof Error ? err.stack : undefined;
810
- this.emitError({
811
- extensionPath: ext.path,
812
- event: "ui",
813
- error: message,
814
- stack,
815
- });
816
- }
817
- }
818
- }
819
- return undefined;
820
- }
821
- async emitUI(event) {
822
- return this.emitUIEvent(event);
823
- }
824
- respondUI(id, result) {
825
- const resolve = this.pendingUIResponses.get(id);
826
- if (resolve) {
827
- this.pendingUIResponses.delete(id);
828
- resolve(result);
829
- }
830
- }
831
706
  async emitContext(messages) {
707
+ const ctx = this.createContext();
832
708
  let currentMessages = structuredClone(messages);
833
709
  for (const ext of this.extensions) {
834
710
  const handlers = ext.handlers.get("context");
835
711
  if (!handlers || handlers.length === 0)
836
712
  continue;
837
- const ctx = this.createContext(ext);
838
713
  for (const handler of handlers) {
839
714
  try {
840
715
  const event = { type: "context", messages: currentMessages };
@@ -858,12 +733,12 @@ export class ExtensionRunner {
858
733
  return currentMessages;
859
734
  }
860
735
  async emitBeforeProviderRequest(payload) {
736
+ const ctx = this.createContext();
861
737
  let currentPayload = payload;
862
738
  for (const ext of this.extensions) {
863
739
  const handlers = ext.handlers.get("before_provider_request");
864
740
  if (!handlers || handlers.length === 0)
865
741
  continue;
866
- const ctx = this.createContext(ext);
867
742
  for (const handler of handlers) {
868
743
  try {
869
744
  const event = {
@@ -891,17 +766,17 @@ export class ExtensionRunner {
891
766
  }
892
767
  async emitBeforeAgentStart(prompt, images, systemPrompt, systemPromptOptions) {
893
768
  let currentSystemPrompt = systemPrompt;
769
+ const ctx = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this.createContext()));
770
+ ctx.getSystemPrompt = () => {
771
+ this.assertActive();
772
+ return currentSystemPrompt;
773
+ };
894
774
  const messages = [];
895
775
  let systemPromptModified = false;
896
776
  for (const ext of this.extensions) {
897
777
  const handlers = ext.handlers.get("before_agent_start");
898
778
  if (!handlers || handlers.length === 0)
899
779
  continue;
900
- const ctx = Object.defineProperties({}, Object.getOwnPropertyDescriptors(this.createContext(ext)));
901
- ctx.getSystemPrompt = () => {
902
- this.assertActive();
903
- return currentSystemPrompt;
904
- };
905
780
  for (const handler of handlers) {
906
781
  try {
907
782
  const event = {
@@ -944,6 +819,7 @@ export class ExtensionRunner {
944
819
  return undefined;
945
820
  }
946
821
  async emitResourcesDiscover(cwd, reason) {
822
+ const ctx = this.createContext();
947
823
  const skillPaths = [];
948
824
  const promptPaths = [];
949
825
  const themePaths = [];
@@ -951,7 +827,6 @@ export class ExtensionRunner {
951
827
  const handlers = ext.handlers.get("resources_discover");
952
828
  if (!handlers || handlers.length === 0)
953
829
  continue;
954
- const ctx = this.createContext(ext);
955
830
  for (const handler of handlers) {
956
831
  try {
957
832
  const event = { type: "resources_discover", cwd, reason };
@@ -983,10 +858,10 @@ export class ExtensionRunner {
983
858
  }
984
859
  /** Emit input event. Transforms chain, "handled" short-circuits. */
985
860
  async emitInput(text, images, source) {
861
+ const ctx = this.createContext();
986
862
  let currentText = text;
987
863
  let currentImages = images;
988
864
  for (const ext of this.extensions) {
989
- const ctx = this.createContext(ext);
990
865
  for (const handler of ext.handlers.get("input") ?? []) {
991
866
  try {
992
867
  const event = { type: "input", text: currentText, images: currentImages, source };