@mariozechner/pi-coding-agent 0.64.0 → 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 (119) hide show
  1. package/CHANGELOG.md +96 -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 +5 -42
  16. package/dist/core/agent-session.d.ts.map +1 -1
  17. package/dist/core/agent-session.js +46 -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/types.d.ts +16 -28
  28. package/dist/core/extensions/types.d.ts.map +1 -1
  29. package/dist/core/extensions/types.js +10 -0
  30. package/dist/core/extensions/types.js.map +1 -1
  31. package/dist/core/footer-data-provider.d.ts +5 -1
  32. package/dist/core/footer-data-provider.d.ts.map +1 -1
  33. package/dist/core/footer-data-provider.js +70 -8
  34. package/dist/core/footer-data-provider.js.map +1 -1
  35. package/dist/core/index.d.ts +3 -1
  36. package/dist/core/index.d.ts.map +1 -1
  37. package/dist/core/index.js +3 -1
  38. package/dist/core/index.js.map +1 -1
  39. package/dist/core/keybindings.d.ts +14 -1
  40. package/dist/core/keybindings.d.ts.map +1 -1
  41. package/dist/core/keybindings.js +13 -14
  42. package/dist/core/keybindings.js.map +1 -1
  43. package/dist/core/package-manager.d.ts +20 -0
  44. package/dist/core/package-manager.d.ts.map +1 -1
  45. package/dist/core/package-manager.js +32 -0
  46. package/dist/core/package-manager.js.map +1 -1
  47. package/dist/core/resource-loader.d.ts.map +1 -1
  48. package/dist/core/resource-loader.js +21 -0
  49. package/dist/core/resource-loader.js.map +1 -1
  50. package/dist/core/sdk.d.ts +4 -1
  51. package/dist/core/sdk.d.ts.map +1 -1
  52. package/dist/core/sdk.js +4 -1
  53. package/dist/core/sdk.js.map +1 -1
  54. package/dist/core/session-manager.d.ts +3 -0
  55. package/dist/core/session-manager.d.ts.map +1 -1
  56. package/dist/core/session-manager.js +13 -6
  57. package/dist/core/session-manager.js.map +1 -1
  58. package/dist/core/settings-manager.d.ts +1 -1
  59. package/dist/core/settings-manager.d.ts.map +1 -1
  60. package/dist/core/settings-manager.js +2 -1
  61. package/dist/core/settings-manager.js.map +1 -1
  62. package/dist/index.d.ts +3 -3
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +3 -3
  65. package/dist/index.js.map +1 -1
  66. package/dist/main.d.ts.map +1 -1
  67. package/dist/main.js +205 -427
  68. package/dist/main.js.map +1 -1
  69. package/dist/migrations.d.ts.map +1 -1
  70. package/dist/migrations.js +20 -0
  71. package/dist/migrations.js.map +1 -1
  72. package/dist/modes/interactive/components/footer.d.ts +1 -0
  73. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  74. package/dist/modes/interactive/components/footer.js +4 -1
  75. package/dist/modes/interactive/components/footer.js.map +1 -1
  76. package/dist/modes/interactive/components/tree-selector.d.ts +4 -2
  77. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  78. package/dist/modes/interactive/components/tree-selector.js +48 -15
  79. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  80. package/dist/modes/interactive/interactive-mode.d.ts +9 -4
  81. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  82. package/dist/modes/interactive/interactive-mode.js +124 -94
  83. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  84. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  85. package/dist/modes/interactive/theme/theme.js +6 -11
  86. package/dist/modes/interactive/theme/theme.js.map +1 -1
  87. package/dist/modes/print-mode.d.ts +2 -2
  88. package/dist/modes/print-mode.d.ts.map +1 -1
  89. package/dist/modes/print-mode.js +41 -36
  90. package/dist/modes/print-mode.js.map +1 -1
  91. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  92. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  93. package/dist/modes/rpc/rpc-mode.js +92 -64
  94. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  95. package/dist/package-manager-cli.d.ts +4 -0
  96. package/dist/package-manager-cli.d.ts.map +1 -0
  97. package/dist/package-manager-cli.js +234 -0
  98. package/dist/package-manager-cli.js.map +1 -0
  99. package/docs/extensions.md +72 -40
  100. package/docs/keybindings.md +2 -0
  101. package/docs/sdk.md +227 -74
  102. package/docs/settings.md +1 -1
  103. package/docs/tree.md +6 -3
  104. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  105. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  106. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  107. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  108. package/examples/extensions/hello.ts +18 -17
  109. package/examples/extensions/hidden-thinking-label.ts +0 -4
  110. package/examples/extensions/rpc-demo.ts +3 -9
  111. package/examples/extensions/status-line.ts +0 -8
  112. package/examples/extensions/todo.ts +0 -2
  113. package/examples/extensions/tools.ts +0 -5
  114. package/examples/extensions/widget-placement.ts +4 -12
  115. package/examples/extensions/with-deps/package-lock.json +2 -2
  116. package/examples/extensions/with-deps/package.json +1 -1
  117. package/examples/sdk/13-session-runtime.ts +67 -0
  118. package/examples/sdk/README.md +4 -1
  119. package/package.json +4 -4
package/docs/sdk.md CHANGED
@@ -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();
@@ -447,21 +508,21 @@ const { session } = await createAgentSession({
447
508
 
448
509
  ```typescript
449
510
  import { Type } from "@sinclair/typebox";
450
- import { createAgentSession, type ToolDefinition } from "@mariozechner/pi-coding-agent";
511
+ import { createAgentSession, defineTool } from "@mariozechner/pi-coding-agent";
451
512
 
452
513
  // Inline custom tool
453
- const myTool: ToolDefinition = {
514
+ const myTool = defineTool({
454
515
  name: "my_tool",
455
516
  label: "My Tool",
456
517
  description: "Does something useful",
457
518
  parameters: Type.Object({
458
519
  input: Type.String({ description: "Input value" }),
459
520
  }),
460
- execute: async (toolCallId, params, onUpdate, ctx, signal) => ({
521
+ execute: async (_toolCallId, params) => ({
461
522
  content: [{ type: "text", text: `Result: ${params.input}` }],
462
523
  details: {},
463
524
  }),
464
- };
525
+ });
465
526
 
466
527
  // Pass custom tools directly
467
528
  const { session } = await createAgentSession({
@@ -469,6 +530,8 @@ const { session } = await createAgentSession({
469
530
  });
470
531
  ```
471
532
 
533
+ Use `defineTool()` for standalone definitions and arrays like `customTools: [myTool]`. Inline `pi.registerTool({ ... })` already infers parameter types correctly.
534
+
472
535
  Custom tools passed via `customTools` are combined with extension-registered tools. Extensions loaded by the ResourceLoader can also register tools via `pi.registerTool()`.
473
536
 
474
537
  > See [examples/sdk/05-tools.ts](../examples/sdk/05-tools.ts)
@@ -597,7 +660,15 @@ const { session } = await createAgentSession({ resourceLoader: loader });
597
660
  Sessions use a tree structure with `id`/`parentId` linking, enabling in-place branching.
598
661
 
599
662
  ```typescript
600
- 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";
601
672
 
602
673
  // In-memory (no persistence)
603
674
  const { session } = await createAgentSession({
@@ -605,12 +676,12 @@ const { session } = await createAgentSession({
605
676
  });
606
677
 
607
678
  // New persistent session
608
- const { session } = await createAgentSession({
679
+ const { session: persisted } = await createAgentSession({
609
680
  sessionManager: SessionManager.create(process.cwd()),
610
681
  });
611
682
 
612
683
  // Continue most recent
613
- const { session, modelFallbackMessage } = await createAgentSession({
684
+ const { session: continued, modelFallbackMessage } = await createAgentSession({
614
685
  sessionManager: SessionManager.continueRecent(process.cwd()),
615
686
  });
616
687
  if (modelFallbackMessage) {
@@ -618,26 +689,42 @@ if (modelFallbackMessage) {
618
689
  }
619
690
 
620
691
  // Open specific file
621
- const { session } = await createAgentSession({
692
+ const { session: opened } = await createAgentSession({
622
693
  sessionManager: SessionManager.open("/path/to/session.jsonl"),
623
694
  });
624
695
 
625
- // List available sessions (async with optional progress callback)
626
- const sessions = await SessionManager.list(process.cwd());
627
- for (const info of sessions) {
628
- console.log(`${info.id}: ${info.firstMessage} (${info.messageCount} messages, cwd: ${info.cwd})`);
629
- }
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
+ };
630
713
 
631
- // List all sessions across all projects
632
- const allSessions = await SessionManager.listAll((loaded, total) => {
633
- console.log(`Loading ${loaded}/${total}...`);
714
+ const runtime = await createAgentSessionRuntime(createRuntime, {
715
+ cwd: process.cwd(),
716
+ agentDir: getAgentDir(),
717
+ sessionManager: SessionManager.create(process.cwd()),
634
718
  });
635
719
 
636
- // Custom session directory (no cwd encoding)
637
- const customDir = "/path/to/my-sessions";
638
- const { session } = await createAgentSession({
639
- sessionManager: SessionManager.create(process.cwd(), customDir),
640
- });
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");
641
728
  ```
642
729
 
643
730
  **SessionManager tree API:**
@@ -645,6 +732,10 @@ const { session } = await createAgentSession({
645
732
  ```typescript
646
733
  const sm = SessionManager.open("/path/to/session.jsonl");
647
734
 
735
+ // Session listing
736
+ const currentProjectSessions = await SessionManager.list(process.cwd());
737
+ const allSessions = await SessionManager.listAll(process.cwd());
738
+
648
739
  // Tree traversal
649
740
  const entries = sm.getEntries(); // All entries (excludes header)
650
741
  const tree = sm.getTree(); // Full tree structure
@@ -769,14 +860,14 @@ import { getModel } from "@mariozechner/pi-ai";
769
860
  import { Type } from "@sinclair/typebox";
770
861
  import {
771
862
  AuthStorage,
863
+ bashTool,
772
864
  createAgentSession,
773
865
  DefaultResourceLoader,
866
+ defineTool,
774
867
  ModelRegistry,
868
+ readTool,
775
869
  SessionManager,
776
870
  SettingsManager,
777
- readTool,
778
- bashTool,
779
- type ToolDefinition,
780
871
  } from "@mariozechner/pi-coding-agent";
781
872
 
782
873
  // Set up auth storage (custom location)
@@ -791,7 +882,7 @@ if (process.env.MY_KEY) {
791
882
  const modelRegistry = ModelRegistry.create(authStorage);
792
883
 
793
884
  // Inline tool
794
- const statusTool: ToolDefinition = {
885
+ const statusTool = defineTool({
795
886
  name: "status",
796
887
  label: "Status",
797
888
  description: "Get system status",
@@ -800,7 +891,7 @@ const statusTool: ToolDefinition = {
800
891
  content: [{ type: "text", text: `Uptime: ${process.uptime()}s` }],
801
892
  details: {},
802
893
  }),
803
- };
894
+ });
804
895
 
805
896
  const model = getModel("anthropic", "claude-opus-4-5");
806
897
  if (!model) throw new Error("Model not found");
@@ -854,20 +945,39 @@ The SDK exports run mode utilities for building custom interfaces on top of `cre
854
945
  Full TUI interactive mode with editor, chat history, and all built-in commands:
855
946
 
856
947
  ```typescript
857
- 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";
858
957
 
859
- 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
+ });
860
971
 
861
- const mode = new InteractiveMode(session, {
862
- // All optional
863
- migratedProviders: [], // Show migration warnings
864
- modelFallbackMessage: undefined, // Show model restore warning
865
- initialMessage: "Hello", // Send on startup
866
- initialImages: [], // Images with initial message
867
- initialMessages: [], // Additional startup prompts
972
+ const mode = new InteractiveMode(runtime, {
973
+ migratedProviders: [],
974
+ modelFallbackMessage: undefined,
975
+ initialMessage: "Hello",
976
+ initialImages: [],
977
+ initialMessages: [],
868
978
  });
869
979
 
870
- await mode.run(); // Blocks until exit
980
+ await mode.run();
871
981
  ```
872
982
 
873
983
  ### runPrintMode
@@ -875,15 +985,35 @@ await mode.run(); // Blocks until exit
875
985
  Single-shot mode: send prompts, output result, exit:
876
986
 
877
987
  ```typescript
878
- 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";
879
997
 
880
- 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
+ });
881
1011
 
882
- await runPrintMode(session, {
883
- mode: "text", // "text" for final response, "json" for all events
884
- initialMessage: "Hello", // First message (can include @file content)
885
- initialImages: [], // Images with initial message
886
- messages: ["Follow up"], // Additional prompts
1012
+ await runPrintMode(runtime, {
1013
+ mode: "text",
1014
+ initialMessage: "Hello",
1015
+ initialImages: [],
1016
+ messages: ["Follow up"],
887
1017
  });
888
1018
  ```
889
1019
 
@@ -892,11 +1022,31 @@ await runPrintMode(session, {
892
1022
  JSON-RPC mode for subprocess integration:
893
1023
 
894
1024
  ```typescript
895
- 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";
896
1034
 
897
- 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
+ });
898
1048
 
899
- await runRpcMode(session); // Reads JSON commands from stdin, writes to stdout
1049
+ await runRpcMode(runtime);
900
1050
  ```
901
1051
 
902
1052
  See [RPC documentation](rpc.md) for the JSON protocol.
@@ -929,6 +1079,8 @@ The main entry point exports:
929
1079
  ```typescript
930
1080
  // Factory
931
1081
  createAgentSession
1082
+ createAgentSessionRuntime
1083
+ AgentSessionRuntime
932
1084
 
933
1085
  // Auth and Models
934
1086
  AuthStorage
@@ -940,6 +1092,7 @@ type ResourceLoader
940
1092
  createEventBus
941
1093
 
942
1094
  // Helpers
1095
+ defineTool
943
1096
 
944
1097
  // Session management
945
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
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "1.15.0",
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.15.0",
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.15.0",
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.15.0",
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.14.0",
4
+ "version": "1.15.0",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -3,23 +3,24 @@
3
3
  */
4
4
 
5
5
  import { Type } from "@mariozechner/pi-ai";
6
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
6
+ import { defineTool, type ExtensionAPI } from "@mariozechner/pi-coding-agent";
7
7
 
8
- export default function (pi: ExtensionAPI) {
9
- pi.registerTool({
10
- name: "hello",
11
- label: "Hello",
12
- description: "A simple greeting tool",
13
- parameters: Type.Object({
14
- name: Type.String({ description: "Name to greet" }),
15
- }),
8
+ const helloTool = defineTool({
9
+ name: "hello",
10
+ label: "Hello",
11
+ description: "A simple greeting tool",
12
+ parameters: Type.Object({
13
+ name: Type.String({ description: "Name to greet" }),
14
+ }),
15
+
16
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
17
+ return {
18
+ content: [{ type: "text", text: `Hello, ${params.name}!` }],
19
+ details: { greeted: params.name },
20
+ };
21
+ },
22
+ });
16
23
 
17
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
18
- const { name } = params as { name: string };
19
- return {
20
- content: [{ type: "text", text: `Hello, ${name}!` }],
21
- details: { greeted: name },
22
- };
23
- },
24
- });
24
+ export default function (pi: ExtensionAPI) {
25
+ pi.registerTool(helloTool);
25
26
  }
@@ -33,10 +33,6 @@ export default function (pi: ExtensionAPI) {
33
33
  applyLabel(ctx);
34
34
  });
35
35
 
36
- pi.on("session_switch", async (_event, ctx) => {
37
- applyLabel(ctx);
38
- });
39
-
40
36
  pi.registerCommand("thinking-label", {
41
37
  description: "Set the hidden thinking label. Use without args to reset.",
42
38
  handler: async (args, ctx) => {
@@ -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
  }