one-code-extension 0.5.0 → 0.6.0

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 (95) hide show
  1. package/README.md +1 -1
  2. package/docs/guide/differences-from-claude-code.md +9 -6
  3. package/docs/guide/installation.md +3 -4
  4. package/docs/guide/troubleshooting.md +1 -1
  5. package/extensions/auto-mode/denials.ts +3 -2
  6. package/extensions/background/cron.ts +127 -0
  7. package/extensions/background/index.ts +135 -47
  8. package/extensions/background/loop-fire.ts +39 -2
  9. package/extensions/background/switch-guard.ts +10 -6
  10. package/extensions/background/wakeup.ts +21 -2
  11. package/extensions/bash/index.ts +8 -30
  12. package/extensions/btw/index.ts +32 -18
  13. package/extensions/cache-marks/index.ts +42 -0
  14. package/extensions/claude-context/index.ts +157 -172
  15. package/extensions/compaction/index.ts +11 -8
  16. package/extensions/context-budget/budget.ts +16 -2
  17. package/extensions/context-budget/index.ts +15 -14
  18. package/extensions/context-management/index.ts +11 -28
  19. package/extensions/effort/index.ts +70 -7
  20. package/extensions/file-tracker/index.ts +111 -44
  21. package/extensions/file-tracker/replay.ts +76 -49
  22. package/extensions/file-tracker/restore.ts +12 -9
  23. package/extensions/file-tracker/tracker.ts +14 -1
  24. package/extensions/hooks/index.ts +60 -76
  25. package/extensions/hooks/prompt-context.ts +59 -0
  26. package/extensions/hooks/trust.ts +13 -7
  27. package/extensions/ledger/index.ts +46 -0
  28. package/extensions/ledger/wire.ts +154 -0
  29. package/extensions/lib/agent-cron.ts +17 -1
  30. package/extensions/lib/agent-loader.ts +3 -2
  31. package/extensions/lib/anthropic-auth.ts +98 -0
  32. package/extensions/lib/child-extensions.ts +14 -4
  33. package/extensions/lib/context-facts.ts +75 -56
  34. package/extensions/lib/covered-messages.ts +23 -0
  35. package/extensions/lib/deferred.ts +152 -12
  36. package/extensions/lib/ledger/fold-memo.ts +49 -0
  37. package/extensions/lib/ledger/fold.ts +265 -0
  38. package/extensions/lib/ledger/layout.ts +72 -0
  39. package/extensions/lib/ledger/plan.ts +102 -0
  40. package/extensions/lib/ledger/records.ts +276 -0
  41. package/extensions/lib/ledger/state.ts +47 -0
  42. package/extensions/lib/ledger/validate.ts +141 -0
  43. package/extensions/lib/ledger/wire.ts +632 -0
  44. package/extensions/lib/local-command.ts +24 -6
  45. package/extensions/lib/model-catalog/huggingface.json +5 -1
  46. package/extensions/lib/model-catalog/models-dev.json +10535 -10299
  47. package/extensions/lib/model-catalog/openrouter.json +191 -393
  48. package/extensions/lib/notifications.ts +57 -17
  49. package/extensions/lib/pi-version.ts +25 -8
  50. package/extensions/lib/plan-mode-channels.ts +29 -7
  51. package/extensions/lib/previous-mark.ts +101 -0
  52. package/extensions/lib/process-state.ts +48 -0
  53. package/extensions/lib/process-tree.ts +3 -2
  54. package/extensions/lib/reminders.ts +327 -621
  55. package/extensions/lib/request-replay.ts +186 -78
  56. package/extensions/lib/side-call.ts +11 -0
  57. package/extensions/lib/subagent-handback.ts +0 -1
  58. package/extensions/lib/tool-additions.ts +2 -22
  59. package/extensions/lib/turn-budget-layout.ts +18 -2
  60. package/extensions/lsp/index.ts +6 -8
  61. package/extensions/mcp/announce.ts +63 -98
  62. package/extensions/mcp/index.ts +51 -90
  63. package/extensions/mcp/trust.ts +13 -6
  64. package/extensions/model-default/index.ts +86 -19
  65. package/extensions/permissions/index.ts +157 -48
  66. package/extensions/permissions/modes.ts +5 -0
  67. package/extensions/plan-mode/index.ts +142 -45
  68. package/extensions/plan-mode/run-state.ts +26 -0
  69. package/extensions/plugins/index.ts +4 -1
  70. package/extensions/powershell/index.ts +2 -0
  71. package/extensions/recap/index.ts +11 -14
  72. package/extensions/search-tools/index.ts +2 -0
  73. package/extensions/skill/announce.ts +30 -0
  74. package/extensions/skill/index.ts +65 -48
  75. package/extensions/skill/invoke.ts +0 -67
  76. package/extensions/subagents/agents.ts +30 -8
  77. package/extensions/subagents/fork-cache.ts +9 -3
  78. package/extensions/subagents/index.ts +180 -107
  79. package/extensions/subagents/outcome.ts +3 -0
  80. package/extensions/subagents/parent-mode.ts +50 -0
  81. package/extensions/subagents/pending-claim.ts +18 -1
  82. package/extensions/subagents/runner.ts +29 -2
  83. package/extensions/subagents/runs.ts +5 -0
  84. package/extensions/system-prompt/index.ts +40 -6
  85. package/extensions/system-prompt/template.ts +10 -1
  86. package/extensions/system-reminder/index.ts +174 -202
  87. package/extensions/tool-search/index.ts +241 -167
  88. package/extensions/workflow/agent-session.ts +4 -1
  89. package/extensions/workflow/index.ts +16 -4
  90. package/extensions/workflow/run-manager.ts +4 -0
  91. package/extensions/worktree/index.ts +50 -14
  92. package/package.json +5 -2
  93. package/extensions/lib/context-stack.ts +0 -81
  94. package/extensions/subagents/announce.ts +0 -69
  95. package/extensions/tool-search/announce.ts +0 -58
@@ -29,6 +29,8 @@ import { CHILD_EXTENSION_PATHS } from "../lib/child-extensions.ts";
29
29
  import { type HandbackSlot, SUBAGENT_HANDBACK, subagentHandbackExtension } from "../lib/subagent-handback.ts";
30
30
  import type { RequestCapture } from "../lib/request-replay.ts";
31
31
  import { forkCacheExtension } from "./fork-cache.ts";
32
+ import { parentModeExtension } from "./parent-mode.ts";
33
+ import { type DeferRequest, deferLikeParentExtension, injectedByParent } from "../lib/deferred.ts";
32
34
  import type { ChildHandle, ChildOutcome, RpcChildHandle } from "./outcome.ts";
33
35
  import { sendToMainTool } from "./send-to-main-tool.ts";
34
36
  import { SessionTurnTracker } from "./session-turns.ts";
@@ -162,6 +164,10 @@ export class SubagentRuntime {
162
164
  * still-connecting (possibly empty) one.
163
165
  */
164
166
  private readonly getMcpTools: () => ToolDefinition[] | Promise<ToolDefinition[]>;
167
+ /** The tools the parent session defers (lib/deferred.ts queryDeferred): a child defers the ones injected into it the same way. */
168
+ private readonly getParentDeferred: () => DeferRequest[];
169
+ /** The main session's live permission mode, for a fork: told to it when it differs from what it was told (parent-mode.ts). */
170
+ private readonly getParentMode: (() => string) | undefined;
165
171
  /**
166
172
  * The parent's permission decision closure (undefined until permissions
167
173
  * publishes it), wrapped so every call carries the asking run's name: the
@@ -200,8 +206,12 @@ export class SubagentRuntime {
200
206
  onExtensionError: (runName: string | undefined, error: ExtensionError) => void,
201
207
  onModelUnusable: (model: string, reason: string) => void,
202
208
  onChildWrite: (path: string, runName: string | undefined) => void,
209
+ getParentDeferred: () => DeferRequest[],
210
+ getParentMode: (() => string) | undefined,
203
211
  ) {
204
212
  this.modelRuntime = modelRuntime;
213
+ this.getParentDeferred = getParentDeferred;
214
+ this.getParentMode = getParentMode;
205
215
  this.onChildWrite = onChildWrite;
206
216
  this.baseCwd = baseCwd;
207
217
  this.getMcpTools = getMcpTools;
@@ -231,11 +241,13 @@ export class SubagentRuntime {
231
241
  onExtensionError: (runName: string | undefined, error: ExtensionError) => void = () => {},
232
242
  onModelUnusable: (model: string, reason: string) => void = () => {},
233
243
  onChildWrite: (path: string, runName: string | undefined) => void = () => {},
244
+ getParentDeferred: () => DeferRequest[] = () => [],
245
+ getParentMode?: () => string,
234
246
  ): Promise<SubagentRuntime> {
235
247
  const modelRuntime = await createSharedModelRuntime(getAgentDir());
236
248
  // Prime the catalog once; spawns read the live snapshot (see resolveModel).
237
249
  await modelRuntime.getAvailable();
238
- return new SubagentRuntime(modelRuntime, cwd, getMcpTools, getPermissionBridge, getHookBridge, onExtensionError, onModelUnusable, onChildWrite);
250
+ return new SubagentRuntime(modelRuntime, cwd, getMcpTools, getPermissionBridge, getHookBridge, onExtensionError, onModelUnusable, onChildWrite, getParentDeferred, getParentMode);
239
251
  }
240
252
 
241
253
  /**
@@ -275,7 +287,11 @@ export class SubagentRuntime {
275
287
  // lib/subagent-handback.ts). Inline extensions load after the paths
276
288
  // above, so the fork-cache one, last, sees the final body.
277
289
  extraFactories: [
290
+ // The tools injected below (the spawn and cron tools, the shared MCP
291
+ // tools) are deferred as the parent defers them: their owners run there.
292
+ deferLikeParentExtension(this.getParentDeferred(), injectedByParent(spec.extraTools ?? [])),
278
293
  subagentHandbackExtension(handback, { deferred: fork }),
294
+ ...(fork && this.getParentMode ? [parentModeExtension(this.getParentMode)] : []),
279
295
  ...(fork && spec.parentRequest ? [forkCacheExtension(spec.parentRequest)] : []),
280
296
  ],
281
297
  // claude-context (above) injects the CLAUDE.md instructions on the child's session_start;
@@ -390,7 +406,10 @@ export class SubagentRuntime {
390
406
  * agent's own prompt and toolset.
391
407
  */
392
408
  private async buildChildSession(spec: ChildSessionSpec, handback: HandbackSlot): Promise<{ session: Session; note?: string }> {
393
- const [loader, mcpTools] = await Promise.all([buildAgentLoader(this.childLoaderOptions(spec, handback)), this.getMcpTools()]);
409
+ // The MCP wait first: the loader takes the parent's deferrals, which a server
410
+ // connecting during the wait adds to (it is instant once the connects settled).
411
+ const mcpTools = await this.getMcpTools();
412
+ const loader = await buildAgentLoader(this.childLoaderOptions(spec, handback));
394
413
  const newSessionManager = () => newChildSessionManager(spec);
395
414
  // A fork keeps the parent's toolset; only a named agent carries tool lists.
396
415
  const agent = spec.forkFrom ? undefined : spec.agent;
@@ -659,6 +678,14 @@ export class SubagentRuntime {
659
678
  await session.steer(message);
660
679
  return "steered";
661
680
  },
681
+ // The background extension is not loaded in a child, so the parent's
682
+ // cron fires record what they sent straight into the child's session.
683
+ conversation: {
684
+ getBranch: () => session.sessionManager.getBranch(),
685
+ appendEntry: (customType, data) => {
686
+ session.sessionManager.appendCustomEntry(customType, data);
687
+ },
688
+ },
662
689
  busy: () => turnActive || !session.isIdle,
663
690
  exited: () => exited,
664
691
  kill: () => {
@@ -140,6 +140,11 @@ export class RunRegistry {
140
140
  this.reserved.clear();
141
141
  }
142
142
 
143
+ /** Keep only the records `keep` accepts; names held for runs being spawned stay held. */
144
+ retain(keep: (record: AgentRunRecord) => boolean): void {
145
+ for (const record of [...this.byId.values()]) if (!keep(record)) this.remove(record.taskId);
146
+ }
147
+
143
148
  /** Hold a name until its record is added. */
144
149
  reserve(name: string): void {
145
150
  this.reserved.add(name);
@@ -11,16 +11,22 @@
11
11
  * which a model that takes a mid-conversation system message gets there
12
12
  * instead (system-reminder). The scratchpad path the block names embeds the
13
13
  * session id — derived at session_start, constant within the session.
14
+ *
15
+ * Both are live state with facts: the scratchpad the block names and whether
16
+ * the run is one-shot. Inside an epoch message 1 is the record, so a /fork
17
+ * (a new session id, so a new scratchpad) or a session continued in another
18
+ * run mode is announced on the request's tail, live minus what the epoch
19
+ * told (RENDER_CHANNEL), never by rewriting message 1.
14
20
  */
15
21
 
16
22
  import { getCurrentSystemMessage } from "@earendil-works/pi-ai";
17
23
  import type { BuildSystemPromptOptions, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
18
24
  import { environmentBlock, modelLine } from "../lib/environment-block.ts";
19
25
  import { GIT_SNAPSHOT_OWNER_CHANNEL } from "../lib/git-status.ts";
20
- import { oneShotSessionNote, sessionOutlivesTurn } from "../lib/notifications.ts";
26
+ import { interactiveSessionNote, oneShotSessionNote, sessionOutlivesTurn } from "../lib/notifications.ts";
21
27
  import { taskToolsEnabled } from "../lib/model-tier.ts";
22
28
  import { sessionModelTier } from "../lib/session-model-tier.ts";
23
- import { CONTEXT_ORDER, REMINDER_CHANNEL } from "../lib/reminders.ts";
29
+ import { CONTEXT_ORDER, REMINDER_CHANNEL, RENDER_CHANNEL, type RenderRequest, toldBlockFact } from "../lib/reminders.ts";
24
30
  import { privateSessionScratchpadDir } from "../lib/scratchpad.ts";
25
31
  import { collectEnvironment, type EnvironmentInfo } from "./environment.ts";
26
32
  import { totalTokensBlock, turnTokenBudget } from "../context-budget/budget.ts";
@@ -29,6 +35,16 @@ import { buildClaudeCodeSystemPrompt } from "./template.ts";
29
35
  import { WORKSPACE_CHANNEL, type WorkspaceAnnouncement } from "../lib/workspace-channel.ts";
30
36
  import { PROMPT_OPTIONS_CHANNEL, type PromptOptionsAnnouncement, SYSTEM_PROMPT_REQUEST_CHANNEL, type SystemPromptRequest } from "../lib/prompt-options.ts";
31
37
 
38
+ /** The told fact of the scratchpad the environment block names. */
39
+ const SCRATCHPAD_FACT = "environment:scratchpad";
40
+ /** The told mode of the one-shot run note. */
41
+ const ONE_SHOT_MODE = "one-shot";
42
+
43
+ /** The notice that this session's scratchpad is not the one message 1 names (a /fork is a new session). */
44
+ function scratchpadChangedNote(dir: string): string {
45
+ return `This session's scratchpad directory is now ${dir}. Use it for temporary files instead of the scratchpad named earlier in this conversation.`;
46
+ }
47
+
32
48
  export default function systemPromptExtension(pi: ExtensionAPI) {
33
49
  const requestTier = sessionModelTier(pi);
34
50
  let cachedEnv: EnvironmentInfo | undefined;
@@ -40,6 +56,8 @@ export default function systemPromptExtension(pi: ExtensionAPI) {
40
56
  let workspaceDirs: string[] = [];
41
57
  /** The options the last before_agent_start saw; a turn opened from idle rebuilds from them. */
42
58
  let lastOptions: BuildSystemPromptOptions | undefined;
59
+ /** Whether this run is one-shot (`-p`, json): the turn_start that queued the blocks saw it. */
60
+ let oneShotRun = false;
43
61
  pi.events.on(WORKSPACE_CHANNEL, (data) => {
44
62
  const dirs = (data as WorkspaceAnnouncement | undefined)?.dirs;
45
63
  workspaceDirs = Array.isArray(dirs) ? dirs.filter((dir): dir is string => typeof dir === "string") : [];
@@ -88,24 +106,26 @@ export default function systemPromptExtension(pi: ExtensionAPI) {
88
106
  const env = environment(ctx.cwd);
89
107
  pi.events.emit(REMINDER_CHANNEL, {
90
108
  text: environmentBlock({ ...env, scratchpadDir: scratchpad, workspaceDirs }),
91
- scope: "every-turn",
92
109
  key: "environment",
93
110
  placement: "first-prepend",
94
111
  order: CONTEXT_ORDER.environment,
112
+ facts: [toldBlockFact(SCRATCHPAD_FACT, scratchpad ?? "")],
95
113
  });
96
- if (!sessionOutlivesTurn(ctx.mode)) {
114
+ oneShotRun = !sessionOutlivesTurn(ctx.mode);
115
+ if (oneShotRun) {
97
116
  pi.events.emit(REMINDER_CHANNEL, {
98
117
  text: oneShotSessionNote(),
99
- scope: "every-turn",
100
118
  key: "one-shot",
101
119
  placement: "first-prepend",
102
120
  order: CONTEXT_ORDER.oneShot,
121
+ facts: [{ kind: "mode", key: ONE_SHOT_MODE, on: true }],
103
122
  });
123
+ } else {
124
+ pi.events.emit(REMINDER_CHANNEL, { key: "one-shot", remove: true });
104
125
  }
105
126
  if (ctx.model) {
106
127
  pi.events.emit(REMINDER_CHANNEL, {
107
128
  text: modelLine(ctx.model),
108
- scope: "every-turn",
109
129
  key: "model-line",
110
130
  placement: "first-prepend",
111
131
  order: CONTEXT_ORDER.modelLine,
@@ -113,6 +133,20 @@ export default function systemPromptExtension(pi: ExtensionAPI) {
113
133
  }
114
134
  });
115
135
 
136
+ // Inside an epoch, what changed since message 1 was rendered goes on the tail.
137
+ pi.events.on(RENDER_CHANNEL, (data) => {
138
+ const request = data as RenderRequest;
139
+ if (request.epoch || lastOptions?.customPrompt) return;
140
+ const told = request.toldBlocks.get(SCRATCHPAD_FACT);
141
+ const live = toldBlockFact(SCRATCHPAD_FACT, scratchpad ?? "");
142
+ if (told !== undefined && told !== live.hash && scratchpad) {
143
+ request.blocks.push({ text: scratchpadChangedNote(scratchpad), facts: [live] });
144
+ }
145
+ if ((request.modes.get(ONE_SHOT_MODE) ?? false) !== oneShotRun) {
146
+ request.blocks.push({ text: oneShotRun ? oneShotSessionNote() : interactiveSessionNote(), facts: [{ kind: "mode", key: ONE_SHOT_MODE, on: oneShotRun }] });
147
+ }
148
+ });
149
+
116
150
  // A copy: pi goes on mutating this object after the handlers return
117
151
  // (forceSystemPrompt, the live selectedTools).
118
152
  const copyOptions = (options: BuildSystemPromptOptions): BuildSystemPromptOptions => ({
@@ -39,7 +39,16 @@ const BUNDLES: Record<PromptTier, PromptBundle> = {
39
39
 
40
40
  function buildToolsSection(options: BuildSystemPromptOptions): string {
41
41
  const tools = options.selectedTools ?? ["read", "bash", "edit", "write"];
42
- const visible = tools.filter((name) => !!options.toolSnippets?.[name]);
42
+ // In registration order (the snippets' own), never activation order: a tool
43
+ // withdrawn and brought back (a model hop through another tier) is appended
44
+ // to pi's active list, and a prompt built from that order would depend on
45
+ // the process's history instead of the branch (R3, the fresh-process oracle).
46
+ const order = Object.keys(options.toolSnippets ?? {});
47
+ const rank = (name: string) => {
48
+ const at = order.indexOf(name);
49
+ return at === -1 ? order.length : at;
50
+ };
51
+ const visible = tools.filter((name) => !!options.toolSnippets?.[name]).sort((a, b) => rank(a) - rank(b));
43
52
  const toolsList =
44
53
  visible.length > 0 ? visible.map((name) => `- ${name}: ${options.toolSnippets![name]}`).join("\n") : "(none)";
45
54