@mariozechner/pi-coding-agent 0.63.2 → 0.65.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 (144) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/README.md +11 -5
  3. package/dist/cli/args.d.ts +7 -4
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +37 -15
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session-runtime.d.ts +83 -0
  8. package/dist/core/agent-session-runtime.d.ts.map +1 -0
  9. package/dist/core/agent-session-runtime.js +232 -0
  10. package/dist/core/agent-session-runtime.js.map +1 -0
  11. package/dist/core/agent-session-services.d.ts +86 -0
  12. package/dist/core/agent-session-services.d.ts.map +1 -0
  13. package/dist/core/agent-session-services.js +116 -0
  14. package/dist/core/agent-session-services.js.map +1 -0
  15. package/dist/core/agent-session.d.ts +10 -42
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +58 -237
  18. package/dist/core/agent-session.js.map +1 -1
  19. package/dist/core/export-html/tool-renderer.d.ts +2 -0
  20. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  21. package/dist/core/export-html/tool-renderer.js +2 -2
  22. package/dist/core/export-html/tool-renderer.js.map +1 -1
  23. package/dist/core/extensions/index.d.ts +2 -2
  24. package/dist/core/extensions/index.d.ts.map +1 -1
  25. package/dist/core/extensions/index.js +1 -1
  26. package/dist/core/extensions/index.js.map +1 -1
  27. package/dist/core/extensions/runner.d.ts.map +1 -1
  28. package/dist/core/extensions/runner.js +1 -0
  29. package/dist/core/extensions/runner.js.map +1 -1
  30. package/dist/core/extensions/types.d.ts +20 -28
  31. package/dist/core/extensions/types.d.ts.map +1 -1
  32. package/dist/core/extensions/types.js +10 -0
  33. package/dist/core/extensions/types.js.map +1 -1
  34. package/dist/core/footer-data-provider.d.ts +5 -1
  35. package/dist/core/footer-data-provider.d.ts.map +1 -1
  36. package/dist/core/footer-data-provider.js +70 -8
  37. package/dist/core/footer-data-provider.js.map +1 -1
  38. package/dist/core/index.d.ts +3 -1
  39. package/dist/core/index.d.ts.map +1 -1
  40. package/dist/core/index.js +3 -1
  41. package/dist/core/index.js.map +1 -1
  42. package/dist/core/keybindings.d.ts +14 -1
  43. package/dist/core/keybindings.d.ts.map +1 -1
  44. package/dist/core/keybindings.js +13 -14
  45. package/dist/core/keybindings.js.map +1 -1
  46. package/dist/core/model-registry.d.ts +3 -1
  47. package/dist/core/model-registry.d.ts.map +1 -1
  48. package/dist/core/model-registry.js +7 -1
  49. package/dist/core/model-registry.js.map +1 -1
  50. package/dist/core/package-manager.d.ts +20 -0
  51. package/dist/core/package-manager.d.ts.map +1 -1
  52. package/dist/core/package-manager.js +32 -0
  53. package/dist/core/package-manager.js.map +1 -1
  54. package/dist/core/resource-loader.d.ts.map +1 -1
  55. package/dist/core/resource-loader.js +21 -0
  56. package/dist/core/resource-loader.js.map +1 -1
  57. package/dist/core/sdk.d.ts +5 -2
  58. package/dist/core/sdk.d.ts.map +1 -1
  59. package/dist/core/sdk.js +5 -2
  60. package/dist/core/sdk.js.map +1 -1
  61. package/dist/core/session-manager.d.ts +3 -0
  62. package/dist/core/session-manager.d.ts.map +1 -1
  63. package/dist/core/session-manager.js +13 -6
  64. package/dist/core/session-manager.js.map +1 -1
  65. package/dist/core/settings-manager.d.ts +1 -1
  66. package/dist/core/settings-manager.d.ts.map +1 -1
  67. package/dist/core/settings-manager.js +2 -1
  68. package/dist/core/settings-manager.js.map +1 -1
  69. package/dist/core/tools/edit.d.ts.map +1 -1
  70. package/dist/core/tools/edit.js +15 -4
  71. package/dist/core/tools/edit.js.map +1 -1
  72. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  73. package/dist/core/tools/tool-definition-wrapper.js +2 -0
  74. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  75. package/dist/index.d.ts +3 -3
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +3 -3
  78. package/dist/index.js.map +1 -1
  79. package/dist/main.d.ts.map +1 -1
  80. package/dist/main.js +205 -427
  81. package/dist/main.js.map +1 -1
  82. package/dist/migrations.d.ts.map +1 -1
  83. package/dist/migrations.js +20 -0
  84. package/dist/migrations.js.map +1 -1
  85. package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  86. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  87. package/dist/modes/interactive/components/assistant-message.js +14 -3
  88. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  89. package/dist/modes/interactive/components/footer.d.ts +1 -0
  90. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  91. package/dist/modes/interactive/components/footer.js +4 -1
  92. package/dist/modes/interactive/components/footer.js.map +1 -1
  93. package/dist/modes/interactive/components/tree-selector.d.ts +4 -2
  94. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  95. package/dist/modes/interactive/components/tree-selector.js +48 -15
  96. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  97. package/dist/modes/interactive/interactive-mode.d.ts +12 -4
  98. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  99. package/dist/modes/interactive/interactive-mode.js +146 -96
  100. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  101. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  102. package/dist/modes/interactive/theme/theme.js +6 -11
  103. package/dist/modes/interactive/theme/theme.js.map +1 -1
  104. package/dist/modes/print-mode.d.ts +2 -2
  105. package/dist/modes/print-mode.d.ts.map +1 -1
  106. package/dist/modes/print-mode.js +41 -36
  107. package/dist/modes/print-mode.js.map +1 -1
  108. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  109. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  110. package/dist/modes/rpc/rpc-mode.js +95 -64
  111. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  112. package/dist/package-manager-cli.d.ts +4 -0
  113. package/dist/package-manager-cli.d.ts.map +1 -0
  114. package/dist/package-manager-cli.js +234 -0
  115. package/dist/package-manager-cli.js.map +1 -0
  116. package/docs/compaction.md +4 -2
  117. package/docs/extensions.md +133 -40
  118. package/docs/json.md +5 -2
  119. package/docs/keybindings.md +2 -0
  120. package/docs/rpc.md +21 -7
  121. package/docs/sdk.md +239 -83
  122. package/docs/settings.md +1 -1
  123. package/docs/tree.md +6 -3
  124. package/examples/extensions/README.md +1 -0
  125. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  126. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  127. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  128. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  129. package/examples/extensions/hello.ts +18 -17
  130. package/examples/extensions/hidden-thinking-label.ts +53 -0
  131. package/examples/extensions/rpc-demo.ts +3 -9
  132. package/examples/extensions/sandbox/index.ts +4 -0
  133. package/examples/extensions/status-line.ts +0 -8
  134. package/examples/extensions/todo.ts +0 -2
  135. package/examples/extensions/tools.ts +0 -5
  136. package/examples/extensions/widget-placement.ts +4 -12
  137. package/examples/extensions/with-deps/package-lock.json +2 -2
  138. package/examples/extensions/with-deps/package.json +1 -1
  139. package/examples/sdk/02-custom-model.ts +1 -1
  140. package/examples/sdk/09-api-keys-and-oauth.ts +3 -3
  141. package/examples/sdk/12-full-control.ts +1 -1
  142. package/examples/sdk/13-session-runtime.ts +67 -0
  143. package/examples/sdk/README.md +7 -4
  144. package/package.json +4 -4
package/docs/sdk.md CHANGED
@@ -20,7 +20,7 @@ import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "
20
20
 
21
21
  // Set up credential storage and model registry
22
22
  const authStorage = AuthStorage.create();
23
- const modelRegistry = new ModelRegistry(authStorage);
23
+ const modelRegistry = ModelRegistry.create(authStorage);
24
24
 
25
25
  const { session } = await createAgentSession({
26
26
  sessionManager: SessionManager.inMemory(),
@@ -49,7 +49,7 @@ The SDK is included in the main package. No separate installation needed.
49
49
 
50
50
  ### createAgentSession()
51
51
 
52
- The main factory function. Creates an `AgentSession` with configurable options.
52
+ The main factory function for a single `AgentSession`.
53
53
 
54
54
  `createAgentSession()` uses a `ResourceLoader` to supply extensions, skills, prompt templates, themes, and context files. If you do not provide one, it uses `DefaultResourceLoader` with standard discovery.
55
55
 
@@ -69,61 +69,117 @@ const { session } = await createAgentSession({
69
69
 
70
70
  ### AgentSession
71
71
 
72
- The session manages the agent lifecycle, message history, and event streaming.
72
+ The session manages agent lifecycle, message history, model state, compaction, and event streaming.
73
73
 
74
74
  ```typescript
75
75
  interface AgentSession {
76
76
  // Send a prompt and wait for completion
77
- // If streaming, requires streamingBehavior option to queue the message
78
77
  prompt(text: string, options?: PromptOptions): Promise<void>;
79
-
78
+
80
79
  // Queue messages during streaming
81
- steer(text: string): Promise<void>; // Queue for delivery after the current assistant turn finishes its tool calls
82
- followUp(text: string): Promise<void>; // Wait: delivered only when agent finishes
83
-
80
+ steer(text: string): Promise<void>;
81
+ followUp(text: string): Promise<void>;
82
+
84
83
  // Subscribe to events (returns unsubscribe function)
85
84
  subscribe(listener: (event: AgentSessionEvent) => void): () => void;
86
-
85
+
87
86
  // Session info
88
- sessionFile: string | undefined; // undefined for in-memory
87
+ sessionFile: string | undefined;
89
88
  sessionId: string;
90
-
89
+
91
90
  // Model control
92
91
  setModel(model: Model): Promise<void>;
93
92
  setThinkingLevel(level: ThinkingLevel): void;
94
93
  cycleModel(): Promise<ModelCycleResult | undefined>;
95
94
  cycleThinkingLevel(): ThinkingLevel | undefined;
96
-
95
+
97
96
  // State access
98
97
  agent: Agent;
99
98
  model: Model | undefined;
100
99
  thinkingLevel: ThinkingLevel;
101
100
  messages: AgentMessage[];
102
101
  isStreaming: boolean;
103
-
104
- // Session management
105
- newSession(options?: { parentSession?: string }): Promise<boolean>; // Returns false if cancelled by hook
106
- switchSession(sessionPath: string): Promise<boolean>;
107
-
108
- // Forking
109
- fork(entryId: string): Promise<{ selectedText: string; cancelled: boolean }>; // Creates new session file
110
- navigateTree(targetId: string, options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string }): Promise<{ editorText?: string; cancelled: boolean }>; // In-place navigation
111
-
112
- // Hook message injection
113
- sendHookMessage(message: HookMessage, triggerTurn?: boolean): Promise<void>;
114
-
102
+
103
+ // In-place tree navigation within the current session file
104
+ navigateTree(targetId: string, options?: { summarize?: boolean; customInstructions?: string; replaceInstructions?: boolean; label?: string }): Promise<{ editorText?: string; cancelled: boolean }>;
105
+
115
106
  // Compaction
116
107
  compact(customInstructions?: string): Promise<CompactionResult>;
117
108
  abortCompaction(): void;
118
-
109
+
119
110
  // Abort current operation
120
111
  abort(): Promise<void>;
121
-
112
+
122
113
  // Cleanup
123
114
  dispose(): void;
124
115
  }
125
116
  ```
126
117
 
118
+ Session replacement APIs such as new-session, resume, fork, and import live on `AgentSessionRuntime`, not on `AgentSession`.
119
+
120
+ ### createAgentSessionRuntime() and AgentSessionRuntime
121
+
122
+ Use the runtime API when you need to replace the active session and rebuild cwd-bound runtime state.
123
+ This is the same layer used by the built-in interactive, print, and RPC modes.
124
+
125
+ `createAgentSessionRuntime()` takes a runtime factory plus the initial cwd/session target. The factory closes over process-global fixed inputs, recreates cwd-bound services for the effective cwd, resolves session options against those services, and returns a full runtime result.
126
+
127
+ ```typescript
128
+ import {
129
+ type CreateAgentSessionRuntimeFactory,
130
+ createAgentSessionFromServices,
131
+ createAgentSessionRuntime,
132
+ createAgentSessionServices,
133
+ getAgentDir,
134
+ SessionManager,
135
+ } from "@mariozechner/pi-coding-agent";
136
+
137
+ const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
138
+ const services = await createAgentSessionServices({ cwd });
139
+ return {
140
+ ...(await createAgentSessionFromServices({
141
+ services,
142
+ sessionManager,
143
+ sessionStartEvent,
144
+ })),
145
+ services,
146
+ diagnostics: services.diagnostics,
147
+ };
148
+ };
149
+
150
+ const runtime = await createAgentSessionRuntime(createRuntime, {
151
+ cwd: process.cwd(),
152
+ agentDir: getAgentDir(),
153
+ sessionManager: SessionManager.create(process.cwd()),
154
+ });
155
+ ```
156
+
157
+ `AgentSessionRuntime` owns replacement of the active runtime across:
158
+
159
+ - `newSession()`
160
+ - `switchSession()`
161
+ - `fork()`
162
+ - `importFromJsonl()`
163
+
164
+ Important behavior:
165
+
166
+ - `runtime.session` changes after those operations
167
+ - event subscriptions are attached to a specific `AgentSession`, so re-subscribe after replacement
168
+ - if you use extensions, call `runtime.session.bindExtensions(...)` again for the new session
169
+ - creation returns diagnostics on `runtime.diagnostics`
170
+ - if runtime creation or replacement fails, the method throws and the caller decides how to handle it
171
+
172
+ ```typescript
173
+ let session = runtime.session;
174
+ let unsubscribe = session.subscribe(() => {});
175
+
176
+ await runtime.newSession();
177
+
178
+ unsubscribe();
179
+ session = runtime.session;
180
+ unsubscribe = session.subscribe(() => {});
181
+ ```
182
+
127
183
  ### Prompting and Message Queueing
128
184
 
129
185
  The `prompt()` method handles prompt templates, extension commands, and message sending:
@@ -171,10 +227,15 @@ const state = session.agent.state;
171
227
  // state.model: Model - current model
172
228
  // state.thinkingLevel: ThinkingLevel - current thinking level
173
229
  // state.systemPrompt: string - system prompt
174
- // state.tools: Tool[] - available tools
230
+ // state.tools: AgentTool[] - available tools
231
+ // state.streamingMessage?: AgentMessage - current partial assistant message
232
+ // state.errorMessage?: string - latest assistant error
233
+
234
+ // Replace messages (useful for branching or restoration)
235
+ session.agent.state.messages = messages; // copies the top-level array
175
236
 
176
- // Replace messages (useful for branching, restoration)
177
- session.agent.replaceMessages(messages);
237
+ // Replace tools
238
+ session.agent.state.tools = tools; // copies the top-level array
178
239
 
179
240
  // Wait for agent to finish processing
180
241
  await session.agent.waitForIdle();
@@ -232,9 +293,12 @@ session.subscribe((event) => {
232
293
  // event.toolResults: tool results from this turn
233
294
  break;
234
295
 
235
- // Session events (auto-compaction, retry)
236
- case "auto_compaction_start":
237
- case "auto_compaction_end":
296
+ // Session events (queue, compaction, retry)
297
+ case "queue_update":
298
+ console.log(event.steering, event.followUp);
299
+ break;
300
+ case "compaction_start":
301
+ case "compaction_end":
238
302
  case "auto_retry_start":
239
303
  case "auto_retry_end":
240
304
  break;
@@ -286,7 +350,7 @@ import { getModel } from "@mariozechner/pi-ai";
286
350
  import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
287
351
 
288
352
  const authStorage = AuthStorage.create();
289
- const modelRegistry = new ModelRegistry(authStorage);
353
+ const modelRegistry = ModelRegistry.create(authStorage);
290
354
 
291
355
  // Find specific built-in model (doesn't check if API key exists)
292
356
  const opus = getModel("anthropic", "claude-opus-4-5");
@@ -334,7 +398,7 @@ import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
334
398
 
335
399
  // Default: uses ~/.pi/agent/auth.json and ~/.pi/agent/models.json
336
400
  const authStorage = AuthStorage.create();
337
- const modelRegistry = new ModelRegistry(authStorage);
401
+ const modelRegistry = ModelRegistry.create(authStorage);
338
402
 
339
403
  const { session } = await createAgentSession({
340
404
  sessionManager: SessionManager.inMemory(),
@@ -347,7 +411,7 @@ authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");
347
411
 
348
412
  // Custom auth storage location
349
413
  const customAuth = AuthStorage.create("/my/app/auth.json");
350
- const customRegistry = new ModelRegistry(customAuth, "/my/app/models.json");
414
+ const customRegistry = ModelRegistry.create(customAuth, "/my/app/models.json");
351
415
 
352
416
  const { session } = await createAgentSession({
353
417
  sessionManager: SessionManager.inMemory(),
@@ -356,7 +420,7 @@ const { session } = await createAgentSession({
356
420
  });
357
421
 
358
422
  // No custom models.json (built-in models only)
359
- const simpleRegistry = new ModelRegistry(authStorage);
423
+ const simpleRegistry = ModelRegistry.inMemory(authStorage);
360
424
  ```
361
425
 
362
426
  > See [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts)
@@ -444,21 +508,21 @@ const { session } = await createAgentSession({
444
508
 
445
509
  ```typescript
446
510
  import { Type } from "@sinclair/typebox";
447
- import { createAgentSession, type ToolDefinition } from "@mariozechner/pi-coding-agent";
511
+ import { createAgentSession, defineTool } from "@mariozechner/pi-coding-agent";
448
512
 
449
513
  // Inline custom tool
450
- const myTool: ToolDefinition = {
514
+ const myTool = defineTool({
451
515
  name: "my_tool",
452
516
  label: "My Tool",
453
517
  description: "Does something useful",
454
518
  parameters: Type.Object({
455
519
  input: Type.String({ description: "Input value" }),
456
520
  }),
457
- execute: async (toolCallId, params, onUpdate, ctx, signal) => ({
521
+ execute: async (_toolCallId, params) => ({
458
522
  content: [{ type: "text", text: `Result: ${params.input}` }],
459
523
  details: {},
460
524
  }),
461
- };
525
+ });
462
526
 
463
527
  // Pass custom tools directly
464
528
  const { session } = await createAgentSession({
@@ -466,6 +530,8 @@ const { session } = await createAgentSession({
466
530
  });
467
531
  ```
468
532
 
533
+ Use `defineTool()` for standalone definitions and arrays like `customTools: [myTool]`. Inline `pi.registerTool({ ... })` already infers parameter types correctly.
534
+
469
535
  Custom tools passed via `customTools` are combined with extension-registered tools. Extensions loaded by the ResourceLoader can also register tools via `pi.registerTool()`.
470
536
 
471
537
  > See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)
@@ -594,7 +660,15 @@ const { session } = await createAgentSession({ resourceLoader: loader });
594
660
  Sessions use a tree structure with `id`/`parentId` linking, enabling in-place branching.
595
661
 
596
662
  ```typescript
597
- import { createAgentSession, SessionManager } from "@mariozechner/pi-coding-agent";
663
+ import {
664
+ type CreateAgentSessionRuntimeFactory,
665
+ createAgentSession,
666
+ createAgentSessionFromServices,
667
+ createAgentSessionRuntime,
668
+ createAgentSessionServices,
669
+ getAgentDir,
670
+ SessionManager,
671
+ } from "@mariozechner/pi-coding-agent";
598
672
 
599
673
  // In-memory (no persistence)
600
674
  const { session } = await createAgentSession({
@@ -602,12 +676,12 @@ const { session } = await createAgentSession({
602
676
  });
603
677
 
604
678
  // New persistent session
605
- const { session } = await createAgentSession({
679
+ const { session: persisted } = await createAgentSession({
606
680
  sessionManager: SessionManager.create(process.cwd()),
607
681
  });
608
682
 
609
683
  // Continue most recent
610
- const { session, modelFallbackMessage } = await createAgentSession({
684
+ const { session: continued, modelFallbackMessage } = await createAgentSession({
611
685
  sessionManager: SessionManager.continueRecent(process.cwd()),
612
686
  });
613
687
  if (modelFallbackMessage) {
@@ -615,26 +689,42 @@ if (modelFallbackMessage) {
615
689
  }
616
690
 
617
691
  // Open specific file
618
- const { session } = await createAgentSession({
692
+ const { session: opened } = await createAgentSession({
619
693
  sessionManager: SessionManager.open("/path/to/session.jsonl"),
620
694
  });
621
695
 
622
- // List available sessions (async with optional progress callback)
623
- const sessions = await SessionManager.list(process.cwd());
624
- for (const info of sessions) {
625
- console.log(`${info.id}: ${info.firstMessage} (${info.messageCount} messages, cwd: ${info.cwd})`);
626
- }
696
+ // List sessions
697
+ const currentProjectSessions = await SessionManager.list(process.cwd());
698
+ const allSessions = await SessionManager.listAll(process.cwd());
699
+
700
+ // Session replacement API for /new, /resume, /fork, and import flows.
701
+ const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
702
+ const services = await createAgentSessionServices({ cwd });
703
+ return {
704
+ ...(await createAgentSessionFromServices({
705
+ services,
706
+ sessionManager,
707
+ sessionStartEvent,
708
+ })),
709
+ services,
710
+ diagnostics: services.diagnostics,
711
+ };
712
+ };
627
713
 
628
- // List all sessions across all projects
629
- const allSessions = await SessionManager.listAll((loaded, total) => {
630
- console.log(`Loading ${loaded}/${total}...`);
714
+ const runtime = await createAgentSessionRuntime(createRuntime, {
715
+ cwd: process.cwd(),
716
+ agentDir: getAgentDir(),
717
+ sessionManager: SessionManager.create(process.cwd()),
631
718
  });
632
719
 
633
- // Custom session directory (no cwd encoding)
634
- const customDir = "/path/to/my-sessions";
635
- const { session } = await createAgentSession({
636
- sessionManager: SessionManager.create(process.cwd(), customDir),
637
- });
720
+ // Replace the active session with a fresh one
721
+ await runtime.newSession();
722
+
723
+ // Replace the active session with another saved session
724
+ await runtime.switchSession("/path/to/session.jsonl");
725
+
726
+ // Replace the active session with a fork from a specific entry
727
+ await runtime.fork("entry-id");
638
728
  ```
639
729
 
640
730
  **SessionManager tree API:**
@@ -642,6 +732,10 @@ const { session } = await createAgentSession({
642
732
  ```typescript
643
733
  const sm = SessionManager.open("/path/to/session.jsonl");
644
734
 
735
+ // Session listing
736
+ const currentProjectSessions = await SessionManager.list(process.cwd());
737
+ const allSessions = await SessionManager.listAll(process.cwd());
738
+
645
739
  // Tree traversal
646
740
  const entries = sm.getEntries(); // All entries (excludes header)
647
741
  const tree = sm.getTree(); // Full tree structure
@@ -766,14 +860,14 @@ import { getModel } from "@mariozechner/pi-ai";
766
860
  import { Type } from "@sinclair/typebox";
767
861
  import {
768
862
  AuthStorage,
863
+ bashTool,
769
864
  createAgentSession,
770
865
  DefaultResourceLoader,
866
+ defineTool,
771
867
  ModelRegistry,
868
+ readTool,
772
869
  SessionManager,
773
870
  SettingsManager,
774
- readTool,
775
- bashTool,
776
- type ToolDefinition,
777
871
  } from "@mariozechner/pi-coding-agent";
778
872
 
779
873
  // Set up auth storage (custom location)
@@ -785,10 +879,10 @@ if (process.env.MY_KEY) {
785
879
  }
786
880
 
787
881
  // Model registry (no custom models.json)
788
- const modelRegistry = new ModelRegistry(authStorage);
882
+ const modelRegistry = ModelRegistry.create(authStorage);
789
883
 
790
884
  // Inline tool
791
- const statusTool: ToolDefinition = {
885
+ const statusTool = defineTool({
792
886
  name: "status",
793
887
  label: "Status",
794
888
  description: "Get system status",
@@ -797,7 +891,7 @@ const statusTool: ToolDefinition = {
797
891
  content: [{ type: "text", text: `Uptime: ${process.uptime()}s` }],
798
892
  details: {},
799
893
  }),
800
- };
894
+ });
801
895
 
802
896
  const model = getModel("anthropic", "claude-opus-4-5");
803
897
  if (!model) throw new Error("Model not found");
@@ -851,20 +945,39 @@ The SDK exports run mode utilities for building custom interfaces on top of `cre
851
945
  Full TUI interactive mode with editor, chat history, and all built-in commands:
852
946
 
853
947
  ```typescript
854
- import { createAgentSession, InteractiveMode } from "@mariozechner/pi-coding-agent";
948
+ import {
949
+ type CreateAgentSessionRuntimeFactory,
950
+ createAgentSessionFromServices,
951
+ createAgentSessionRuntime,
952
+ createAgentSessionServices,
953
+ getAgentDir,
954
+ InteractiveMode,
955
+ SessionManager,
956
+ } from "@mariozechner/pi-coding-agent";
855
957
 
856
- const { session } = await createAgentSession({ /* ... */ });
958
+ const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
959
+ const services = await createAgentSessionServices({ cwd });
960
+ return {
961
+ ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),
962
+ services,
963
+ diagnostics: services.diagnostics,
964
+ };
965
+ };
966
+ const runtime = await createAgentSessionRuntime(createRuntime, {
967
+ cwd: process.cwd(),
968
+ agentDir: getAgentDir(),
969
+ sessionManager: SessionManager.create(process.cwd()),
970
+ });
857
971
 
858
- const mode = new InteractiveMode(session, {
859
- // All optional
860
- migratedProviders: [], // Show migration warnings
861
- modelFallbackMessage: undefined, // Show model restore warning
862
- initialMessage: "Hello", // Send on startup
863
- initialImages: [], // Images with initial message
864
- initialMessages: [], // Additional startup prompts
972
+ const mode = new InteractiveMode(runtime, {
973
+ migratedProviders: [],
974
+ modelFallbackMessage: undefined,
975
+ initialMessage: "Hello",
976
+ initialImages: [],
977
+ initialMessages: [],
865
978
  });
866
979
 
867
- await mode.run(); // Blocks until exit
980
+ await mode.run();
868
981
  ```
869
982
 
870
983
  ### runPrintMode
@@ -872,15 +985,35 @@ await mode.run(); // Blocks until exit
872
985
  Single-shot mode: send prompts, output result, exit:
873
986
 
874
987
  ```typescript
875
- import { createAgentSession, runPrintMode } from "@mariozechner/pi-coding-agent";
988
+ import {
989
+ type CreateAgentSessionRuntimeFactory,
990
+ createAgentSessionFromServices,
991
+ createAgentSessionRuntime,
992
+ createAgentSessionServices,
993
+ getAgentDir,
994
+ runPrintMode,
995
+ SessionManager,
996
+ } from "@mariozechner/pi-coding-agent";
876
997
 
877
- const { session } = await createAgentSession({ /* ... */ });
998
+ const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
999
+ const services = await createAgentSessionServices({ cwd });
1000
+ return {
1001
+ ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),
1002
+ services,
1003
+ diagnostics: services.diagnostics,
1004
+ };
1005
+ };
1006
+ const runtime = await createAgentSessionRuntime(createRuntime, {
1007
+ cwd: process.cwd(),
1008
+ agentDir: getAgentDir(),
1009
+ sessionManager: SessionManager.create(process.cwd()),
1010
+ });
878
1011
 
879
- await runPrintMode(session, {
880
- mode: "text", // "text" for final response, "json" for all events
881
- initialMessage: "Hello", // First message (can include @file content)
882
- initialImages: [], // Images with initial message
883
- messages: ["Follow up"], // Additional prompts
1012
+ await runPrintMode(runtime, {
1013
+ mode: "text",
1014
+ initialMessage: "Hello",
1015
+ initialImages: [],
1016
+ messages: ["Follow up"],
884
1017
  });
885
1018
  ```
886
1019
 
@@ -889,11 +1022,31 @@ await runPrintMode(session, {
889
1022
  JSON-RPC mode for subprocess integration:
890
1023
 
891
1024
  ```typescript
892
- import { createAgentSession, runRpcMode } from "@mariozechner/pi-coding-agent";
1025
+ import {
1026
+ type CreateAgentSessionRuntimeFactory,
1027
+ createAgentSessionFromServices,
1028
+ createAgentSessionRuntime,
1029
+ createAgentSessionServices,
1030
+ getAgentDir,
1031
+ runRpcMode,
1032
+ SessionManager,
1033
+ } from "@mariozechner/pi-coding-agent";
893
1034
 
894
- const { session } = await createAgentSession({ /* ... */ });
1035
+ const createRuntime: CreateAgentSessionRuntimeFactory = async ({ cwd, sessionManager, sessionStartEvent }) => {
1036
+ const services = await createAgentSessionServices({ cwd });
1037
+ return {
1038
+ ...(await createAgentSessionFromServices({ services, sessionManager, sessionStartEvent })),
1039
+ services,
1040
+ diagnostics: services.diagnostics,
1041
+ };
1042
+ };
1043
+ const runtime = await createAgentSessionRuntime(createRuntime, {
1044
+ cwd: process.cwd(),
1045
+ agentDir: getAgentDir(),
1046
+ sessionManager: SessionManager.create(process.cwd()),
1047
+ });
895
1048
 
896
- await runRpcMode(session); // Reads JSON commands from stdin, writes to stdout
1049
+ await runRpcMode(runtime);
897
1050
  ```
898
1051
 
899
1052
  See [RPC documentation](rpc.md) for the JSON protocol.
@@ -926,6 +1079,8 @@ The main entry point exports:
926
1079
  ```typescript
927
1080
  // Factory
928
1081
  createAgentSession
1082
+ createAgentSessionRuntime
1083
+ AgentSessionRuntime
929
1084
 
930
1085
  // Auth and Models
931
1086
  AuthStorage
@@ -937,6 +1092,7 @@ type ResourceLoader
937
1092
  createEventBus
938
1093
 
939
1094
  // Helpers
1095
+ defineTool
940
1096
 
941
1097
  // Session management
942
1098
  SessionManager
package/docs/settings.md CHANGED
@@ -137,7 +137,7 @@ When a provider requests a retry delay longer than `maxDelayMs` (e.g., Google's
137
137
  { "sessionDir": ".pi/sessions" }
138
138
  ```
139
139
 
140
- When multiple sources specify a session directory, `--session-dir` CLI flag takes precedence, then `sessionDir` in settings.json, then extension hooks.
140
+ When multiple sources specify a session directory, `--session-dir` CLI flag takes precedence over `sessionDir` in settings.json.
141
141
 
142
142
  ### Model Cycling
143
143
 
package/docs/tree.md CHANGED
@@ -13,7 +13,7 @@ Sessions are stored as trees where each entry has an `id` and `parentId`. The "l
13
13
  | View | Flat list of user messages | Full tree structure |
14
14
  | Action | Extracts path to **new session file** | Changes leaf in **same session** |
15
15
  | Summary | Never | Optional (user prompted) |
16
- | Events | `session_before_fork` / `session_fork` | `session_before_tree` / `session_tree` |
16
+ | Events | `session_before_fork` / `session_start` (`reason: "fork"`) | `session_before_tree` / `session_tree` |
17
17
 
18
18
  ## Tree UI
19
19
 
@@ -35,6 +35,8 @@ Sessions are stored as trees where each entry has an `id` and `parentId`. The "l
35
35
  | ↑/↓ | Navigate (depth-first order) |
36
36
  | ←/→ | Page up/down |
37
37
  | Ctrl+←/Ctrl+→ or Alt+←/Alt+→ | Fold/unfold and jump between branch segments |
38
+ | Shift+L | Set or clear a label on the selected node |
39
+ | Shift+T | Toggle label timestamps |
38
40
  | Enter | Select node |
39
41
  | Escape/Ctrl+C | Cancel |
40
42
  | Ctrl+U | Toggle: user messages only |
@@ -49,11 +51,12 @@ Sessions are stored as trees where each entry has an `id` and `parentId`. The "l
49
51
  - Height: half terminal height
50
52
  - Current leaf marked with `← active`
51
53
  - Labels shown inline: `[label-name]`
54
+ - `Shift+T` shows the latest label-change timestamp next to labeled nodes
52
55
  - Foldable branch starts show `⊟` in the connector. Folded branches show `⊞`
53
56
  - Active path marker `•` appears after the fold indicator when applicable
54
57
  - Search and filter changes reset all folds
55
58
  - Default filter hides `label` and `custom` entries (shown in Ctrl+O mode)
56
- - Children sorted by timestamp (oldest first)
59
+ - At each branch point, the active subtree is shown first; other sibling branches are sorted by timestamp (oldest first)
57
60
 
58
61
  ## Selection Behavior
59
62
 
@@ -141,7 +144,7 @@ Flow:
141
144
  4. Fire `session_before_tree` event (hook can cancel or provide summary)
142
145
  5. Run default summarizer if needed
143
146
  6. Switch leaf via `branch()` or `branchWithSummary()`
144
- 7. Update agent: `agent.replaceMessages(sessionManager.buildSessionContext().messages)`
147
+ 7. Update agent: `agent.state.messages = sessionManager.buildSessionContext().messages`
145
148
  8. Fire `session_tree` event
146
149
  9. Notify custom tools via session event
147
150
  10. Return result with `editorText` if user message was selected
@@ -52,6 +52,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
52
52
  | `qna.ts` | Extracts questions from last response into editor via `ctx.ui.setEditorText()` |
53
53
  | `status-line.ts` | Shows turn progress in footer via `ctx.ui.setStatus()` with themed colors |
54
54
  | `widget-placement.ts` | Shows widgets above and below the editor via `ctx.ui.setWidget()` placement |
55
+ | `hidden-thinking-label.ts` | Customizes the collapsed thinking label via `ctx.ui.setHiddenThinkingLabel()` |
55
56
  | `model-status.ts` | Shows model changes in status bar via `model_select` hook |
56
57
  | `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
57
58
  | `send-user-message.ts` | Demonstrates `pi.sendUserMessage()` for sending user messages from extensions |
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "1.14.2",
3
+ "version": "1.16.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "1.14.2",
9
+ "version": "1.16.0",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "1.14.2",
4
+ "version": "1.16.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "1.14.2",
4
+ "version": "1.16.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-qwen-cli",
3
3
  "private": true,
4
- "version": "1.13.2",
4
+ "version": "1.15.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",