@codex-infinity/pi-infinity 0.63.3 → 0.64.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +7 -3
  3. package/dist/core/agent-session-runtime.d.ts +134 -0
  4. package/dist/core/agent-session-runtime.d.ts.map +1 -0
  5. package/dist/core/agent-session-runtime.js +262 -0
  6. package/dist/core/agent-session-runtime.js.map +1 -0
  7. package/dist/core/agent-session.d.ts +10 -42
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +56 -236
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/export-html/tool-renderer.d.ts +2 -0
  12. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  13. package/dist/core/export-html/tool-renderer.js +2 -2
  14. package/dist/core/export-html/tool-renderer.js.map +1 -1
  15. package/dist/core/extensions/index.d.ts +1 -1
  16. package/dist/core/extensions/index.d.ts.map +1 -1
  17. package/dist/core/extensions/index.js.map +1 -1
  18. package/dist/core/extensions/runner.d.ts.map +1 -1
  19. package/dist/core/extensions/runner.js +1 -0
  20. package/dist/core/extensions/runner.js.map +1 -1
  21. package/dist/core/extensions/types.d.ts +11 -16
  22. package/dist/core/extensions/types.d.ts.map +1 -1
  23. package/dist/core/extensions/types.js.map +1 -1
  24. package/dist/core/footer-data-provider.d.ts +5 -1
  25. package/dist/core/footer-data-provider.d.ts.map +1 -1
  26. package/dist/core/footer-data-provider.js +69 -8
  27. package/dist/core/footer-data-provider.js.map +1 -1
  28. package/dist/core/index.d.ts +2 -1
  29. package/dist/core/index.d.ts.map +1 -1
  30. package/dist/core/index.js +1 -0
  31. package/dist/core/index.js.map +1 -1
  32. package/dist/core/keybindings.d.ts +10 -0
  33. package/dist/core/keybindings.d.ts.map +1 -1
  34. package/dist/core/keybindings.js +10 -0
  35. package/dist/core/keybindings.js.map +1 -1
  36. package/dist/core/model-registry.d.ts +3 -1
  37. package/dist/core/model-registry.d.ts.map +1 -1
  38. package/dist/core/model-registry.js +7 -1
  39. package/dist/core/model-registry.js.map +1 -1
  40. package/dist/core/sdk.d.ts +5 -2
  41. package/dist/core/sdk.d.ts.map +1 -1
  42. package/dist/core/sdk.js +5 -2
  43. package/dist/core/sdk.js.map +1 -1
  44. package/dist/core/session-manager.d.ts +3 -0
  45. package/dist/core/session-manager.d.ts.map +1 -1
  46. package/dist/core/session-manager.js +13 -6
  47. package/dist/core/session-manager.js.map +1 -1
  48. package/dist/core/tools/edit.d.ts.map +1 -1
  49. package/dist/core/tools/edit.js +15 -4
  50. package/dist/core/tools/edit.js.map +1 -1
  51. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  52. package/dist/core/tools/tool-definition-wrapper.js +2 -0
  53. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  54. package/dist/index.d.ts +2 -2
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +2 -2
  57. package/dist/index.js.map +1 -1
  58. package/dist/main.d.ts.map +1 -1
  59. package/dist/main.js +42 -10
  60. package/dist/main.js.map +1 -1
  61. package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  62. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  63. package/dist/modes/interactive/components/assistant-message.js +13 -3
  64. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  65. package/dist/modes/interactive/components/footer.d.ts +1 -0
  66. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  67. package/dist/modes/interactive/components/footer.js +4 -1
  68. package/dist/modes/interactive/components/footer.js.map +1 -1
  69. package/dist/modes/interactive/components/tree-selector.d.ts +4 -2
  70. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  71. package/dist/modes/interactive/components/tree-selector.js +48 -15
  72. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  73. package/dist/modes/interactive/interactive-mode.d.ts +11 -4
  74. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  75. package/dist/modes/interactive/interactive-mode.js +112 -89
  76. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  77. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  78. package/dist/modes/interactive/theme/theme.js +6 -11
  79. package/dist/modes/interactive/theme/theme.js.map +1 -1
  80. package/dist/modes/print-mode.d.ts +2 -2
  81. package/dist/modes/print-mode.d.ts.map +1 -1
  82. package/dist/modes/print-mode.js +37 -36
  83. package/dist/modes/print-mode.js.map +1 -1
  84. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  85. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  86. package/dist/modes/rpc/rpc-mode.js +72 -49
  87. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  88. package/docs/extensions.md +104 -19
  89. package/docs/json.md +5 -2
  90. package/docs/keybindings.md +2 -0
  91. package/docs/rpc.md +21 -7
  92. package/docs/sdk.md +168 -75
  93. package/docs/tree.md +6 -3
  94. package/examples/extensions/README.md +1 -0
  95. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  96. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  97. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  98. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  99. package/examples/extensions/hidden-thinking-label.ts +53 -0
  100. package/examples/extensions/rpc-demo.ts +3 -9
  101. package/examples/extensions/status-line.ts +0 -8
  102. package/examples/extensions/todo.ts +0 -2
  103. package/examples/extensions/tools.ts +0 -5
  104. package/examples/extensions/widget-placement.ts +4 -12
  105. package/examples/extensions/with-deps/package-lock.json +2 -2
  106. package/examples/extensions/with-deps/package.json +1 -1
  107. package/examples/sdk/02-custom-model.ts +1 -1
  108. package/examples/sdk/09-api-keys-and-oauth.ts +3 -3
  109. package/examples/sdk/12-full-control.ts +1 -1
  110. package/examples/sdk/13-session-runtime.ts +49 -0
  111. package/examples/sdk/README.md +5 -4
  112. 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,102 @@ 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 `AgentSessionRuntimeHost`, not on `AgentSession`.
119
+
120
+ ### createAgentSessionRuntime() and AgentSessionRuntimeHost
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
+ ```typescript
126
+ import {
127
+ AgentSessionRuntimeHost,
128
+ createAgentSessionRuntime,
129
+ SessionManager,
130
+ } from "@mariozechner/pi-coding-agent";
131
+
132
+ const bootstrap = {
133
+ // Optional: authStorage, model, thinkingLevel, tools, customTools, resourceLoader
134
+ };
135
+
136
+ const runtime = await createAgentSessionRuntime(bootstrap, {
137
+ cwd: process.cwd(),
138
+ sessionManager: SessionManager.create(process.cwd()),
139
+ });
140
+
141
+ const runtimeHost = new AgentSessionRuntimeHost(bootstrap, runtime);
142
+ ```
143
+
144
+ `createAgentSessionRuntime()` returns an internal runtime bundle. `AgentSessionRuntimeHost` owns replacement of that bundle across:
145
+
146
+ - `newSession()`
147
+ - `switchSession()`
148
+ - `fork()`
149
+ - `importFromJsonl()`
150
+
151
+ Important behavior:
152
+
153
+ - `runtimeHost.session` changes after those operations
154
+ - event subscriptions are attached to a specific `AgentSession`, so re-subscribe after replacement
155
+ - if you use extensions, call `runtimeHost.session.bindExtensions(...)` again for the new session
156
+
157
+ ```typescript
158
+ let session = runtimeHost.session;
159
+ let unsubscribe = session.subscribe(() => {});
160
+
161
+ await runtimeHost.newSession();
162
+
163
+ unsubscribe();
164
+ session = runtimeHost.session;
165
+ unsubscribe = session.subscribe(() => {});
166
+ ```
167
+
127
168
  ### Prompting and Message Queueing
128
169
 
129
170
  The `prompt()` method handles prompt templates, extension commands, and message sending:
@@ -171,10 +212,15 @@ const state = session.agent.state;
171
212
  // state.model: Model - current model
172
213
  // state.thinkingLevel: ThinkingLevel - current thinking level
173
214
  // state.systemPrompt: string - system prompt
174
- // state.tools: Tool[] - available tools
215
+ // state.tools: AgentTool[] - available tools
216
+ // state.streamingMessage?: AgentMessage - current partial assistant message
217
+ // state.errorMessage?: string - latest assistant error
175
218
 
176
- // Replace messages (useful for branching, restoration)
177
- session.agent.replaceMessages(messages);
219
+ // Replace messages (useful for branching or restoration)
220
+ session.agent.state.messages = messages; // copies the top-level array
221
+
222
+ // Replace tools
223
+ session.agent.state.tools = tools; // copies the top-level array
178
224
 
179
225
  // Wait for agent to finish processing
180
226
  await session.agent.waitForIdle();
@@ -232,9 +278,12 @@ session.subscribe((event) => {
232
278
  // event.toolResults: tool results from this turn
233
279
  break;
234
280
 
235
- // Session events (auto-compaction, retry)
236
- case "auto_compaction_start":
237
- case "auto_compaction_end":
281
+ // Session events (queue, compaction, retry)
282
+ case "queue_update":
283
+ console.log(event.steering, event.followUp);
284
+ break;
285
+ case "compaction_start":
286
+ case "compaction_end":
238
287
  case "auto_retry_start":
239
288
  case "auto_retry_end":
240
289
  break;
@@ -286,7 +335,7 @@ import { getModel } from "@mariozechner/pi-ai";
286
335
  import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
287
336
 
288
337
  const authStorage = AuthStorage.create();
289
- const modelRegistry = new ModelRegistry(authStorage);
338
+ const modelRegistry = ModelRegistry.create(authStorage);
290
339
 
291
340
  // Find specific built-in model (doesn't check if API key exists)
292
341
  const opus = getModel("anthropic", "claude-opus-4-5");
@@ -334,7 +383,7 @@ import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
334
383
 
335
384
  // Default: uses ~/.pi/agent/auth.json and ~/.pi/agent/models.json
336
385
  const authStorage = AuthStorage.create();
337
- const modelRegistry = new ModelRegistry(authStorage);
386
+ const modelRegistry = ModelRegistry.create(authStorage);
338
387
 
339
388
  const { session } = await createAgentSession({
340
389
  sessionManager: SessionManager.inMemory(),
@@ -347,7 +396,7 @@ authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");
347
396
 
348
397
  // Custom auth storage location
349
398
  const customAuth = AuthStorage.create("/my/app/auth.json");
350
- const customRegistry = new ModelRegistry(customAuth, "/my/app/models.json");
399
+ const customRegistry = ModelRegistry.create(customAuth, "/my/app/models.json");
351
400
 
352
401
  const { session } = await createAgentSession({
353
402
  sessionManager: SessionManager.inMemory(),
@@ -356,7 +405,7 @@ const { session } = await createAgentSession({
356
405
  });
357
406
 
358
407
  // No custom models.json (built-in models only)
359
- const simpleRegistry = new ModelRegistry(authStorage);
408
+ const simpleRegistry = ModelRegistry.inMemory(authStorage);
360
409
  ```
361
410
 
362
411
  > See [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts)
@@ -594,7 +643,12 @@ const { session } = await createAgentSession({ resourceLoader: loader });
594
643
  Sessions use a tree structure with `id`/`parentId` linking, enabling in-place branching.
595
644
 
596
645
  ```typescript
597
- import { createAgentSession, SessionManager } from "@mariozechner/pi-coding-agent";
646
+ import {
647
+ AgentSessionRuntimeHost,
648
+ createAgentSession,
649
+ createAgentSessionRuntime,
650
+ SessionManager,
651
+ } from "@mariozechner/pi-coding-agent";
598
652
 
599
653
  // In-memory (no persistence)
600
654
  const { session } = await createAgentSession({
@@ -602,12 +656,12 @@ const { session } = await createAgentSession({
602
656
  });
603
657
 
604
658
  // New persistent session
605
- const { session } = await createAgentSession({
659
+ const { session: persisted } = await createAgentSession({
606
660
  sessionManager: SessionManager.create(process.cwd()),
607
661
  });
608
662
 
609
663
  // Continue most recent
610
- const { session, modelFallbackMessage } = await createAgentSession({
664
+ const { session: continued, modelFallbackMessage } = await createAgentSession({
611
665
  sessionManager: SessionManager.continueRecent(process.cwd()),
612
666
  });
613
667
  if (modelFallbackMessage) {
@@ -615,26 +669,30 @@ if (modelFallbackMessage) {
615
669
  }
616
670
 
617
671
  // Open specific file
618
- const { session } = await createAgentSession({
672
+ const { session: opened } = await createAgentSession({
619
673
  sessionManager: SessionManager.open("/path/to/session.jsonl"),
620
674
  });
621
675
 
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
- }
676
+ // List sessions
677
+ const currentProjectSessions = await SessionManager.list(process.cwd());
678
+ const allSessions = await SessionManager.listAll(process.cwd());
627
679
 
628
- // List all sessions across all projects
629
- const allSessions = await SessionManager.listAll((loaded, total) => {
630
- console.log(`Loading ${loaded}/${total}...`);
680
+ // Session replacement API for /new, /resume, /fork, and import flows.
681
+ const bootstrap = {};
682
+ const runtime = await createAgentSessionRuntime(bootstrap, {
683
+ cwd: process.cwd(),
684
+ sessionManager: SessionManager.create(process.cwd()),
631
685
  });
686
+ const runtimeHost = new AgentSessionRuntimeHost(bootstrap, runtime);
632
687
 
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
- });
688
+ // Replace the active session with a fresh one
689
+ await runtimeHost.newSession();
690
+
691
+ // Replace the active session with another saved session
692
+ await runtimeHost.switchSession("/path/to/session.jsonl");
693
+
694
+ // Replace the active session with a fork from a specific entry
695
+ await runtimeHost.fork("entry-id");
638
696
  ```
639
697
 
640
698
  **SessionManager tree API:**
@@ -642,6 +700,10 @@ const { session } = await createAgentSession({
642
700
  ```typescript
643
701
  const sm = SessionManager.open("/path/to/session.jsonl");
644
702
 
703
+ // Session listing
704
+ const currentProjectSessions = await SessionManager.list(process.cwd());
705
+ const allSessions = await SessionManager.listAll(process.cwd());
706
+
645
707
  // Tree traversal
646
708
  const entries = sm.getEntries(); // All entries (excludes header)
647
709
  const tree = sm.getTree(); // Full tree structure
@@ -785,7 +847,7 @@ if (process.env.MY_KEY) {
785
847
  }
786
848
 
787
849
  // Model registry (no custom models.json)
788
- const modelRegistry = new ModelRegistry(authStorage);
850
+ const modelRegistry = ModelRegistry.create(authStorage);
789
851
 
790
852
  // Inline tool
791
853
  const statusTool: ToolDefinition = {
@@ -851,20 +913,29 @@ The SDK exports run mode utilities for building custom interfaces on top of `cre
851
913
  Full TUI interactive mode with editor, chat history, and all built-in commands:
852
914
 
853
915
  ```typescript
854
- import { createAgentSession, InteractiveMode } from "@mariozechner/pi-coding-agent";
855
-
856
- const { session } = await createAgentSession({ /* ... */ });
916
+ import {
917
+ AgentSessionRuntimeHost,
918
+ createAgentSessionRuntime,
919
+ InteractiveMode,
920
+ SessionManager,
921
+ } from "@mariozechner/pi-coding-agent";
857
922
 
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
923
+ const bootstrap = {};
924
+ const runtime = await createAgentSessionRuntime(bootstrap, {
925
+ cwd: process.cwd(),
926
+ sessionManager: SessionManager.create(process.cwd()),
927
+ });
928
+ const runtimeHost = new AgentSessionRuntimeHost(bootstrap, runtime);
929
+
930
+ const mode = new InteractiveMode(runtimeHost, {
931
+ migratedProviders: [],
932
+ modelFallbackMessage: undefined,
933
+ initialMessage: "Hello",
934
+ initialImages: [],
935
+ initialMessages: [],
865
936
  });
866
937
 
867
- await mode.run(); // Blocks until exit
938
+ await mode.run();
868
939
  ```
869
940
 
870
941
  ### runPrintMode
@@ -872,15 +943,25 @@ await mode.run(); // Blocks until exit
872
943
  Single-shot mode: send prompts, output result, exit:
873
944
 
874
945
  ```typescript
875
- import { createAgentSession, runPrintMode } from "@mariozechner/pi-coding-agent";
946
+ import {
947
+ AgentSessionRuntimeHost,
948
+ createAgentSessionRuntime,
949
+ runPrintMode,
950
+ SessionManager,
951
+ } from "@mariozechner/pi-coding-agent";
876
952
 
877
- const { session } = await createAgentSession({ /* ... */ });
953
+ const bootstrap = {};
954
+ const runtime = await createAgentSessionRuntime(bootstrap, {
955
+ cwd: process.cwd(),
956
+ sessionManager: SessionManager.create(process.cwd()),
957
+ });
958
+ const runtimeHost = new AgentSessionRuntimeHost(bootstrap, runtime);
878
959
 
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
960
+ await runPrintMode(runtimeHost, {
961
+ mode: "text",
962
+ initialMessage: "Hello",
963
+ initialImages: [],
964
+ messages: ["Follow up"],
884
965
  });
885
966
  ```
886
967
 
@@ -889,11 +970,21 @@ await runPrintMode(session, {
889
970
  JSON-RPC mode for subprocess integration:
890
971
 
891
972
  ```typescript
892
- import { createAgentSession, runRpcMode } from "@mariozechner/pi-coding-agent";
973
+ import {
974
+ AgentSessionRuntimeHost,
975
+ createAgentSessionRuntime,
976
+ runRpcMode,
977
+ SessionManager,
978
+ } from "@mariozechner/pi-coding-agent";
893
979
 
894
- const { session } = await createAgentSession({ /* ... */ });
980
+ const bootstrap = {};
981
+ const runtime = await createAgentSessionRuntime(bootstrap, {
982
+ cwd: process.cwd(),
983
+ sessionManager: SessionManager.create(process.cwd()),
984
+ });
985
+ const runtimeHost = new AgentSessionRuntimeHost(bootstrap, runtime);
895
986
 
896
- await runRpcMode(session); // Reads JSON commands from stdin, writes to stdout
987
+ await runRpcMode(runtimeHost);
897
988
  ```
898
989
 
899
990
  See [RPC documentation](rpc.md) for the JSON protocol.
@@ -926,6 +1017,8 @@ The main entry point exports:
926
1017
  ```typescript
927
1018
  // Factory
928
1019
  createAgentSession
1020
+ createAgentSessionRuntime
1021
+ AgentSessionRuntimeHost
929
1022
 
930
1023
  // Auth and Models
931
1024
  AuthStorage
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.15.1",
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.15.1",
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.15.1",
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.15.1",
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.14.1",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Hidden Thinking Label Extension
3
+ *
4
+ * Demonstrates `ctx.ui.setHiddenThinkingLabel()` for customizing the label shown
5
+ * when thinking blocks are hidden.
6
+ *
7
+ * Usage:
8
+ * pi --extension examples/extensions/hidden-thinking-label.ts
9
+ *
10
+ * Test:
11
+ * 1. Load this extension
12
+ * 2. Hide thinking blocks with Ctrl+T
13
+ * 3. Ask for something that produces reasoning output
14
+ * 4. The collapsed thinking block label will show the custom text
15
+ *
16
+ * Commands:
17
+ * /thinking-label <text> Set a custom hidden thinking label
18
+ * /thinking-label Reset to the default label
19
+ */
20
+
21
+ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
22
+
23
+ const DEFAULT_LABEL = "Pondering...";
24
+
25
+ export default function (pi: ExtensionAPI) {
26
+ let label = DEFAULT_LABEL;
27
+
28
+ const applyLabel = (ctx: ExtensionContext) => {
29
+ ctx.ui.setHiddenThinkingLabel(label);
30
+ };
31
+
32
+ pi.on("session_start", async (_event, ctx) => {
33
+ applyLabel(ctx);
34
+ });
35
+
36
+ pi.registerCommand("thinking-label", {
37
+ description: "Set the hidden thinking label. Use without args to reset.",
38
+ handler: async (args, ctx) => {
39
+ const nextLabel = args.trim();
40
+
41
+ if (!nextLabel) {
42
+ label = DEFAULT_LABEL;
43
+ ctx.ui.setHiddenThinkingLabel();
44
+ ctx.ui.notify(`Hidden thinking label reset to: ${DEFAULT_LABEL}`);
45
+ return;
46
+ }
47
+
48
+ label = nextLabel;
49
+ ctx.ui.setHiddenThinkingLabel(label);
50
+ ctx.ui.notify(`Hidden thinking label set to: ${label}`);
51
+ },
52
+ });
53
+ }
@@ -13,7 +13,7 @@
13
13
  * - notify() - after each dialog completes
14
14
  * - setStatus() - on turn_start/turn_end
15
15
  * - setWidget() - on session_start
16
- * - setTitle() - on session_start and session_switch
16
+ * - setTitle() - on session_start
17
17
  * - setEditorText() - via /rpc-prefill command
18
18
  */
19
19
 
@@ -24,18 +24,12 @@ export default function (pi: ExtensionAPI) {
24
24
 
25
25
  // -- setTitle, setWidget, setStatus on session lifecycle --
26
26
 
27
- pi.on("session_start", async (_event, ctx) => {
28
- ctx.ui.setTitle("pi RPC Demo");
27
+ pi.on("session_start", async (event, ctx) => {
28
+ ctx.ui.setTitle(event.reason === "new" ? "pi RPC Demo (new session)" : "pi RPC Demo");
29
29
  ctx.ui.setWidget("rpc-demo", ["--- RPC Extension UI Demo ---", "Loaded and ready."]);
30
30
  ctx.ui.setStatus("rpc-demo", `Turns: ${turnCount}`);
31
31
  });
32
32
 
33
- pi.on("session_switch", async (_event, ctx) => {
34
- turnCount = 0;
35
- ctx.ui.setTitle("pi RPC Demo (new session)");
36
- ctx.ui.setStatus("rpc-demo", `Turns: ${turnCount}`);
37
- });
38
-
39
33
  // -- setStatus on turn lifecycle --
40
34
 
41
35
  pi.on("turn_start", async (_event, ctx) => {
@@ -29,12 +29,4 @@ export default function (pi: ExtensionAPI) {
29
29
  const text = theme.fg("dim", ` Turn ${turnCount} complete`);
30
30
  ctx.ui.setStatus("status-demo", check + text);
31
31
  });
32
-
33
- pi.on("session_switch", async (event, ctx) => {
34
- if (event.reason === "new") {
35
- turnCount = 0;
36
- const theme = ctx.ui.theme;
37
- ctx.ui.setStatus("status-demo", theme.fg("dim", "Ready"));
38
- }
39
- });
40
32
  }
@@ -130,8 +130,6 @@ export default function (pi: ExtensionAPI) {
130
130
 
131
131
  // Reconstruct state on session events
132
132
  pi.on("session_start", async (_event, ctx) => reconstructState(ctx));
133
- pi.on("session_switch", async (_event, ctx) => reconstructState(ctx));
134
- pi.on("session_fork", async (_event, ctx) => reconstructState(ctx));
135
133
  pi.on("session_tree", async (_event, ctx) => reconstructState(ctx));
136
134
 
137
135
  // Register the todo tool for the LLM
@@ -138,9 +138,4 @@ export default function toolsExtension(pi: ExtensionAPI) {
138
138
  pi.on("session_tree", async (_event, ctx) => {
139
139
  restoreFromBranch(ctx);
140
140
  });
141
-
142
- // Restore state after forking
143
- pi.on("session_fork", async (_event, ctx) => {
144
- restoreFromBranch(ctx);
145
- });
146
141
  }
@@ -1,17 +1,9 @@
1
- import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
-
3
- const applyWidgets = (ctx: ExtensionContext) => {
4
- if (!ctx.hasUI) return;
5
- ctx.ui.setWidget("widget-above", ["Above editor widget"]);
6
- ctx.ui.setWidget("widget-below", ["Below editor widget"], { placement: "belowEditor" });
7
- };
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
8
2
 
9
3
  export default function widgetPlacementExtension(pi: ExtensionAPI) {
10
4
  pi.on("session_start", (_event, ctx) => {
11
- applyWidgets(ctx);
12
- });
13
-
14
- pi.on("session_switch", (_event, ctx) => {
15
- applyWidgets(ctx);
5
+ if (!ctx.hasUI) return;
6
+ ctx.ui.setWidget("widget-above", ["Above editor widget"]);
7
+ ctx.ui.setWidget("widget-below", ["Below editor widget"], { placement: "belowEditor" });
16
8
  });
17
9
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-with-deps",
3
- "version": "1.27.2",
3
+ "version": "1.28.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-with-deps",
9
- "version": "1.27.2",
9
+ "version": "1.28.1",
10
10
  "dependencies": {
11
11
  "ms": "^2.1.3"
12
12
  },