@oh-my-pi/pi-coding-agent 15.10.11 → 15.10.12

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 (121) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/cli.js +5349 -5328
  3. package/dist/types/cli/args.d.ts +1 -0
  4. package/dist/types/cli-commands.d.ts +12 -0
  5. package/dist/types/commands/launch.d.ts +4 -0
  6. package/dist/types/config/api-key-resolver.d.ts +3 -0
  7. package/dist/types/config/model-registry.d.ts +1 -0
  8. package/dist/types/config/model-resolver.d.ts +18 -0
  9. package/dist/types/config/settings-schema.d.ts +29 -1
  10. package/dist/types/config/settings.d.ts +7 -0
  11. package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
  12. package/dist/types/eval/py/executor.d.ts +5 -0
  13. package/dist/types/eval/py/kernel.d.ts +6 -1
  14. package/dist/types/eval/py/runtime.d.ts +9 -0
  15. package/dist/types/exec/bash-executor.d.ts +2 -0
  16. package/dist/types/extensibility/extensions/runner.d.ts +3 -2
  17. package/dist/types/extensibility/extensions/types.d.ts +3 -0
  18. package/dist/types/memory-backend/index.d.ts +1 -0
  19. package/dist/types/memory-backend/runtime.d.ts +4 -0
  20. package/dist/types/memory-backend/types.d.ts +66 -1
  21. package/dist/types/modes/index.d.ts +3 -3
  22. package/dist/types/modes/interactive-mode.d.ts +7 -2
  23. package/dist/types/modes/oauth-manual-input.d.ts +7 -0
  24. package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
  25. package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
  26. package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
  27. package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
  28. package/dist/types/modes/setup-wizard/index.d.ts +5 -1
  29. package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
  30. package/dist/types/modes/types.d.ts +2 -0
  31. package/dist/types/secrets/index.d.ts +1 -1
  32. package/dist/types/secrets/obfuscator.d.ts +8 -2
  33. package/dist/types/session/agent-session.d.ts +14 -2
  34. package/dist/types/session/streaming-output.d.ts +23 -0
  35. package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
  36. package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
  37. package/dist/types/slash-commands/types.d.ts +1 -1
  38. package/dist/types/system-prompt.d.ts +2 -0
  39. package/dist/types/task/executor.d.ts +1 -0
  40. package/dist/types/task/index.d.ts +2 -2
  41. package/dist/types/task/types.d.ts +8 -0
  42. package/dist/types/thinking.d.ts +4 -0
  43. package/dist/types/tiny/title-client.d.ts +11 -0
  44. package/dist/types/tiny/title-protocol.d.ts +1 -0
  45. package/dist/types/tools/index.d.ts +6 -0
  46. package/dist/types/utils/git.d.ts +15 -2
  47. package/dist/types/utils/title-generator.d.ts +3 -2
  48. package/package.json +10 -10
  49. package/src/auto-thinking/classifier.ts +1 -0
  50. package/src/cli/args.ts +3 -0
  51. package/src/cli-commands.ts +29 -0
  52. package/src/cli.ts +8 -9
  53. package/src/commands/launch.ts +4 -0
  54. package/src/commit/model-selection.ts +3 -2
  55. package/src/config/api-key-resolver.ts +8 -6
  56. package/src/config/model-registry.ts +97 -30
  57. package/src/config/model-resolver.ts +60 -0
  58. package/src/config/settings-schema.ts +43 -15
  59. package/src/config/settings.ts +61 -3
  60. package/src/edit/hashline/execute.ts +39 -2
  61. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  62. package/src/eval/completion-bridge.ts +1 -0
  63. package/src/eval/py/executor.ts +29 -7
  64. package/src/eval/py/index.ts +6 -1
  65. package/src/eval/py/kernel.ts +31 -11
  66. package/src/eval/py/runtime.ts +37 -0
  67. package/src/exec/bash-executor.ts +82 -3
  68. package/src/extensibility/extensions/get-commands-handler.ts +2 -1
  69. package/src/extensibility/extensions/runner.ts +6 -1
  70. package/src/extensibility/extensions/types.ts +3 -0
  71. package/src/hindsight/bank.ts +17 -2
  72. package/src/internal-urls/docs-index.generated.ts +3 -3
  73. package/src/main.ts +18 -6
  74. package/src/memories/index.ts +2 -0
  75. package/src/memory-backend/index.ts +1 -0
  76. package/src/memory-backend/local-backend.ts +9 -0
  77. package/src/memory-backend/off-backend.ts +9 -0
  78. package/src/memory-backend/runtime.ts +66 -0
  79. package/src/memory-backend/types.ts +81 -1
  80. package/src/mnemopi/backend.ts +151 -4
  81. package/src/modes/acp/acp-agent.ts +119 -11
  82. package/src/modes/components/assistant-message.ts +19 -21
  83. package/src/modes/components/footer.ts +3 -1
  84. package/src/modes/components/status-line/component.ts +118 -34
  85. package/src/modes/controllers/command-controller.ts +1 -1
  86. package/src/modes/controllers/input-controller.ts +1 -0
  87. package/src/modes/controllers/mcp-command-controller.ts +38 -3
  88. package/src/modes/index.ts +3 -21
  89. package/src/modes/interactive-mode.ts +39 -9
  90. package/src/modes/oauth-manual-input.ts +30 -3
  91. package/src/modes/rpc/rpc-client.ts +154 -3
  92. package/src/modes/rpc/rpc-mode.ts +97 -12
  93. package/src/modes/rpc/rpc-subagents.ts +265 -0
  94. package/src/modes/rpc/rpc-types.ts +81 -1
  95. package/src/modes/setup-wizard/index.ts +12 -2
  96. package/src/modes/setup-wizard/lazy.ts +16 -0
  97. package/src/modes/types.ts +2 -0
  98. package/src/sdk.ts +8 -1
  99. package/src/secrets/index.ts +8 -1
  100. package/src/secrets/obfuscator.ts +39 -18
  101. package/src/session/agent-session.ts +179 -54
  102. package/src/session/streaming-output.ts +166 -10
  103. package/src/slash-commands/acp-builtins.ts +24 -0
  104. package/src/slash-commands/builtin-registry.ts +20 -0
  105. package/src/slash-commands/types.ts +1 -1
  106. package/src/system-prompt.ts +14 -0
  107. package/src/task/executor.ts +13 -12
  108. package/src/task/index.ts +9 -8
  109. package/src/task/render.ts +18 -3
  110. package/src/task/types.ts +9 -0
  111. package/src/thinking.ts +7 -0
  112. package/src/tiny/title-client.ts +34 -5
  113. package/src/tiny/title-protocol.ts +1 -1
  114. package/src/tiny/worker.ts +6 -4
  115. package/src/tools/bash.ts +46 -5
  116. package/src/tools/image-gen.ts +11 -4
  117. package/src/tools/index.ts +13 -1
  118. package/src/tools/inspect-image.ts +1 -0
  119. package/src/utils/commit-message-generator.ts +1 -0
  120. package/src/utils/git.ts +267 -13
  121. package/src/utils/title-generator.ts +24 -5
@@ -0,0 +1,265 @@
1
+ import * as fs from "node:fs/promises";
2
+ import { isEnoent } from "@oh-my-pi/pi-utils";
3
+ import type { FileEntry, SessionMessageEntry } from "../../session/session-manager";
4
+ import { parseSessionEntries } from "../../session/session-manager";
5
+ import {
6
+ type AgentProgress,
7
+ type SubagentEventPayload,
8
+ type SubagentLifecyclePayload,
9
+ type SubagentProgressPayload,
10
+ TASK_SUBAGENT_EVENT_CHANNEL,
11
+ TASK_SUBAGENT_LIFECYCLE_CHANNEL,
12
+ TASK_SUBAGENT_PROGRESS_CHANNEL,
13
+ } from "../../task";
14
+ import type { EventBus } from "../../utils/event-bus";
15
+ import type {
16
+ RpcSubagentEventFrame,
17
+ RpcSubagentFrame,
18
+ RpcSubagentMessagesResult,
19
+ RpcSubagentSnapshot,
20
+ RpcSubagentSubscriptionLevel,
21
+ } from "./rpc-types";
22
+
23
+ export interface RpcSubagentTranscriptSelector {
24
+ subagentId?: string;
25
+ sessionFile?: string;
26
+ fromByte?: number;
27
+ }
28
+
29
+ type RpcSubagentOutput = (frame: RpcSubagentFrame) => void;
30
+
31
+ const MAX_RETAINED_TRANSCRIPT_REFERENCES = 256;
32
+
33
+ function isSessionMessageEntry(entry: FileEntry): entry is SessionMessageEntry {
34
+ return entry.type === "message";
35
+ }
36
+
37
+ function statusFromLifecycle(status: SubagentLifecyclePayload["status"]): AgentProgress["status"] {
38
+ return status === "started" ? "running" : status;
39
+ }
40
+
41
+ function isTerminalLifecycleStatus(status: SubagentLifecyclePayload["status"]): boolean {
42
+ return status !== "started";
43
+ }
44
+
45
+ function hasSameOwner(
46
+ payload: Pick<SubagentLifecyclePayload | SubagentProgressPayload, "parentToolCallId" | "sessionFile">,
47
+ snapshot: RpcSubagentSnapshot,
48
+ ): boolean {
49
+ if (payload.parentToolCallId !== undefined && snapshot.parentToolCallId !== undefined) {
50
+ return payload.parentToolCallId === snapshot.parentToolCallId;
51
+ }
52
+ if (payload.sessionFile !== undefined && snapshot.sessionFile !== undefined) {
53
+ return payload.sessionFile === snapshot.sessionFile;
54
+ }
55
+ return true;
56
+ }
57
+
58
+ function addPruned(set: Set<string>, value: string, maxSize: number): void {
59
+ set.delete(value);
60
+ set.add(value);
61
+ while (set.size > maxSize) {
62
+ const oldest = set.keys().next();
63
+ if (oldest.done) break;
64
+ set.delete(oldest.value);
65
+ }
66
+ }
67
+
68
+ export async function readRpcSubagentTranscript(sessionFile: string, fromByte = 0): Promise<RpcSubagentMessagesResult> {
69
+ let startByte = Number.isFinite(fromByte) ? Math.max(0, Math.trunc(fromByte)) : 0;
70
+ const file = Bun.file(sessionFile);
71
+ let size: number;
72
+ try {
73
+ ({ size } = await fs.stat(sessionFile));
74
+ } catch (err) {
75
+ if (!isEnoent(err)) throw err;
76
+ return {
77
+ sessionFile,
78
+ fromByte: startByte,
79
+ nextByte: startByte,
80
+ reset: false,
81
+ entries: [],
82
+ messages: [],
83
+ };
84
+ }
85
+ let reset = false;
86
+ if (startByte > size) {
87
+ startByte = 0;
88
+ reset = true;
89
+ }
90
+
91
+ const text = startByte >= size ? "" : await file.slice(startByte).text();
92
+ const lastNewline = text.lastIndexOf("\n");
93
+ const completeText = lastNewline >= 0 ? text.slice(0, lastNewline + 1) : "";
94
+ const entries = completeText.length > 0 ? parseSessionEntries(completeText) : [];
95
+ const nextByte = startByte + Buffer.byteLength(completeText, "utf8");
96
+
97
+ return {
98
+ sessionFile,
99
+ fromByte: startByte,
100
+ nextByte,
101
+ reset,
102
+ entries,
103
+ messages: entries.filter(isSessionMessageEntry).map(entry => entry.message),
104
+ };
105
+ }
106
+
107
+ export class RpcSubagentRegistry {
108
+ #subagents = new Map<string, RpcSubagentSnapshot>();
109
+ #transcriptSessionFilesBySubagentId = new Map<string, string>();
110
+ #staleSubagentIds = new Set<string>();
111
+ #unsubscribers: Array<() => void> = [];
112
+ #output: RpcSubagentOutput;
113
+ #subscriptionLevel: RpcSubagentSubscriptionLevel = "off";
114
+
115
+ constructor(eventBus: EventBus, output: RpcSubagentOutput) {
116
+ this.#output = output;
117
+ this.#unsubscribers.push(
118
+ eventBus.on(TASK_SUBAGENT_LIFECYCLE_CHANNEL, data => {
119
+ this.handleLifecycle(data as SubagentLifecyclePayload);
120
+ }),
121
+ eventBus.on(TASK_SUBAGENT_PROGRESS_CHANNEL, data => {
122
+ this.handleProgress(data as SubagentProgressPayload);
123
+ }),
124
+ eventBus.on(TASK_SUBAGENT_EVENT_CHANNEL, data => {
125
+ this.handleEvent(data as SubagentEventPayload);
126
+ }),
127
+ );
128
+ }
129
+
130
+ dispose(): void {
131
+ for (const unsubscribe of this.#unsubscribers) unsubscribe();
132
+ this.#unsubscribers = [];
133
+ this.#subagents.clear();
134
+ this.#transcriptSessionFilesBySubagentId.clear();
135
+ this.#staleSubagentIds.clear();
136
+ }
137
+
138
+ clear(): void {
139
+ for (const subagentId of this.#subagents.keys()) {
140
+ addPruned(this.#staleSubagentIds, subagentId, MAX_RETAINED_TRANSCRIPT_REFERENCES);
141
+ }
142
+ for (const subagentId of this.#transcriptSessionFilesBySubagentId.keys()) {
143
+ addPruned(this.#staleSubagentIds, subagentId, MAX_RETAINED_TRANSCRIPT_REFERENCES);
144
+ }
145
+ this.#subagents.clear();
146
+ this.#transcriptSessionFilesBySubagentId.clear();
147
+ }
148
+
149
+ setSubscriptionLevel(level: RpcSubagentSubscriptionLevel): void {
150
+ this.#subscriptionLevel = level;
151
+ }
152
+
153
+ getSubscriptionLevel(): RpcSubagentSubscriptionLevel {
154
+ return this.#subscriptionLevel;
155
+ }
156
+
157
+ getSubagents(): RpcSubagentSnapshot[] {
158
+ return [...this.#subagents.values()].sort((a, b) => a.index - b.index || a.id.localeCompare(b.id));
159
+ }
160
+
161
+ #rememberTranscriptSession(subagentId: string, sessionFile: string | undefined): void {
162
+ if (!sessionFile) return;
163
+ this.#transcriptSessionFilesBySubagentId.delete(subagentId);
164
+ this.#transcriptSessionFilesBySubagentId.set(subagentId, sessionFile);
165
+ while (this.#transcriptSessionFilesBySubagentId.size > MAX_RETAINED_TRANSCRIPT_REFERENCES) {
166
+ const oldest = this.#transcriptSessionFilesBySubagentId.keys().next();
167
+ if (oldest.done) break;
168
+ this.#transcriptSessionFilesBySubagentId.delete(oldest.value);
169
+ }
170
+ }
171
+
172
+ #hasTranscriptSessionFile(sessionFile: string): boolean {
173
+ for (const snapshot of this.#subagents.values()) {
174
+ if (snapshot.sessionFile === sessionFile) return true;
175
+ }
176
+ for (const transcriptSessionFile of this.#transcriptSessionFilesBySubagentId.values()) {
177
+ if (transcriptSessionFile === sessionFile) return true;
178
+ }
179
+ return false;
180
+ }
181
+
182
+ handleLifecycle(payload: SubagentLifecyclePayload): void {
183
+ const existing = this.#subagents.get(payload.id);
184
+ if (existing && !hasSameOwner(payload, existing)) return;
185
+ if (!existing && payload.status !== "started") return;
186
+ if (payload.status === "started") {
187
+ this.#staleSubagentIds.delete(payload.id);
188
+ }
189
+ const sessionFile = payload.sessionFile ?? existing?.sessionFile;
190
+ const snapshot: RpcSubagentSnapshot = {
191
+ id: payload.id,
192
+ index: payload.index,
193
+ agent: payload.agent,
194
+ agentSource: payload.agentSource,
195
+ description: payload.description ?? existing?.description,
196
+ status: statusFromLifecycle(payload.status),
197
+ task: existing?.task,
198
+ assignment: existing?.assignment,
199
+ sessionFile,
200
+ parentToolCallId: payload.parentToolCallId ?? existing?.parentToolCallId,
201
+ lastUpdate: Date.now(),
202
+ progress: existing?.progress,
203
+ };
204
+ this.#rememberTranscriptSession(payload.id, sessionFile);
205
+ if (isTerminalLifecycleStatus(payload.status)) {
206
+ this.#subagents.delete(payload.id);
207
+ } else {
208
+ this.#subagents.set(payload.id, snapshot);
209
+ }
210
+ if (this.#subscriptionLevel !== "off") {
211
+ this.#output({ type: "subagent_lifecycle", payload });
212
+ }
213
+ }
214
+
215
+ handleProgress(payload: SubagentProgressPayload): void {
216
+ const progress = payload.progress;
217
+ if (this.#staleSubagentIds.has(progress.id)) return;
218
+ const existing = this.#subagents.get(progress.id);
219
+ if (!existing) return;
220
+ if (!hasSameOwner(payload, existing)) return;
221
+ const sessionFile = payload.sessionFile ?? existing?.sessionFile;
222
+ this.#rememberTranscriptSession(progress.id, sessionFile);
223
+ this.#subagents.set(progress.id, {
224
+ id: progress.id,
225
+ index: payload.index,
226
+ agent: payload.agent,
227
+ agentSource: payload.agentSource,
228
+ description: progress.description ?? existing?.description,
229
+ status: progress.status,
230
+ task: payload.task,
231
+ assignment: payload.assignment,
232
+ sessionFile,
233
+ lastUpdate: Date.now(),
234
+ parentToolCallId: payload.parentToolCallId ?? existing?.parentToolCallId,
235
+ progress,
236
+ });
237
+ if (this.#subscriptionLevel !== "off") {
238
+ this.#output({ type: "subagent_progress", payload });
239
+ }
240
+ }
241
+
242
+ handleEvent(payload: SubagentEventPayload): void {
243
+ if (this.#staleSubagentIds.has(payload.id)) return;
244
+ if (this.#subscriptionLevel !== "events") return;
245
+ this.#output({ type: "subagent_event", payload } satisfies RpcSubagentEventFrame);
246
+ }
247
+
248
+ resolveSessionFile(selector: RpcSubagentTranscriptSelector): string {
249
+ if (selector.subagentId) {
250
+ const snapshot = this.#subagents.get(selector.subagentId);
251
+ const sessionFile = snapshot?.sessionFile ?? this.#transcriptSessionFilesBySubagentId.get(selector.subagentId);
252
+ if (!sessionFile) {
253
+ throw new Error(`Unknown subagent or session file unavailable: ${selector.subagentId}`);
254
+ }
255
+ return sessionFile;
256
+ }
257
+
258
+ if (selector.sessionFile) {
259
+ if (this.#hasTranscriptSessionFile(selector.sessionFile)) return selector.sessionFile;
260
+ throw new Error("Unknown subagent session file");
261
+ }
262
+
263
+ throw new Error("get_subagent_messages requires subagentId or sessionFile");
264
+ }
265
+ }
@@ -9,7 +9,14 @@ import type { CompactionResult } from "@oh-my-pi/pi-agent-core/compaction";
9
9
  import type { Effort, ImageContent, Model } from "@oh-my-pi/pi-ai";
10
10
  import type { BashResult } from "../../exec/bash-executor";
11
11
  import type { ContextUsage } from "../../extensibility/extensions/types";
12
- import type { SessionStats } from "../../session/agent-session";
12
+ import type { AgentSessionEvent, SessionStats } from "../../session/agent-session";
13
+ import type { FileEntry } from "../../session/session-manager";
14
+ import type {
15
+ AgentProgress,
16
+ SubagentEventPayload,
17
+ SubagentLifecyclePayload,
18
+ SubagentProgressPayload,
19
+ } from "../../task";
13
20
  import type { TodoPhase } from "../../tools/todo";
14
21
 
15
22
  // ============================================================================
@@ -30,6 +37,9 @@ export type RpcCommand =
30
37
  | { id?: string; type: "set_todos"; phases: TodoPhase[] }
31
38
  | { id?: string; type: "set_host_tools"; tools: RpcHostToolDefinition[] }
32
39
  | { id?: string; type: "set_host_uri_schemes"; schemes: RpcHostUriSchemeDefinition[] }
40
+ | { id?: string; type: "set_subagent_subscription"; level: RpcSubagentSubscriptionLevel }
41
+ | { id?: string; type: "get_subagents" }
42
+ | { id?: string; type: "get_subagent_messages"; subagentId?: string; sessionFile?: string; fromByte?: number }
33
43
 
34
44
  // Model
35
45
  | { id?: string; type: "set_model"; provider: string; modelId: string }
@@ -104,6 +114,32 @@ export interface RpcHandoffResult {
104
114
  savedPath?: string;
105
115
  }
106
116
 
117
+ export type RpcSubagentSubscriptionLevel = "off" | "progress" | "events";
118
+
119
+ export interface RpcSubagentSnapshot {
120
+ id: string;
121
+ index: number;
122
+ agent: string;
123
+ agentSource: AgentProgress["agentSource"];
124
+ description?: string;
125
+ status: AgentProgress["status"];
126
+ task?: string;
127
+ assignment?: string;
128
+ sessionFile?: string;
129
+ lastUpdate: number;
130
+ progress?: AgentProgress;
131
+ parentToolCallId?: string;
132
+ }
133
+
134
+ export interface RpcSubagentMessagesResult {
135
+ sessionFile: string;
136
+ fromByte: number;
137
+ nextByte: number;
138
+ reset: boolean;
139
+ entries: FileEntry[];
140
+ messages: AgentMessage[];
141
+ }
142
+
107
143
  // ============================================================================
108
144
  // RPC Responses (stdout)
109
145
  // ============================================================================
@@ -123,6 +159,27 @@ export type RpcResponse =
123
159
  | { id?: string; type: "response"; command: "set_todos"; success: true; data: { todoPhases: TodoPhase[] } }
124
160
  | { id?: string; type: "response"; command: "set_host_tools"; success: true; data: { toolNames: string[] } }
125
161
  | { id?: string; type: "response"; command: "set_host_uri_schemes"; success: true; data: { schemes: string[] } }
162
+ | {
163
+ id?: string;
164
+ type: "response";
165
+ command: "set_subagent_subscription";
166
+ success: true;
167
+ data: { level: RpcSubagentSubscriptionLevel };
168
+ }
169
+ | {
170
+ id?: string;
171
+ type: "response";
172
+ command: "get_subagents";
173
+ success: true;
174
+ data: { subagents: RpcSubagentSnapshot[] };
175
+ }
176
+ | {
177
+ id?: string;
178
+ type: "response";
179
+ command: "get_subagent_messages";
180
+ success: true;
181
+ data: RpcSubagentMessagesResult;
182
+ }
126
183
 
127
184
  // Model
128
185
  | {
@@ -212,6 +269,29 @@ export type RpcResponse =
212
269
  // Error response (any command can fail)
213
270
  | { id?: string; type: "response"; command: string; success: false; error: string };
214
271
 
272
+ // ============================================================================
273
+ // Subagent Events (stdout)
274
+ // ============================================================================
275
+
276
+ export interface RpcSubagentLifecycleFrame {
277
+ type: "subagent_lifecycle";
278
+ payload: SubagentLifecyclePayload;
279
+ }
280
+
281
+ export interface RpcSubagentProgressFrame {
282
+ type: "subagent_progress";
283
+ payload: SubagentProgressPayload;
284
+ }
285
+
286
+ export interface RpcSubagentEventFrame {
287
+ type: "subagent_event";
288
+ payload: SubagentEventPayload;
289
+ }
290
+
291
+ export type RpcSubagentFrame = RpcSubagentLifecycleFrame | RpcSubagentProgressFrame | RpcSubagentEventFrame;
292
+
293
+ export type RpcSessionEventFrame = AgentSessionEvent | RpcSubagentFrame;
294
+
215
295
  // ============================================================================
216
296
  // Extension UI Events (stdout)
217
297
  // ============================================================================
@@ -65,9 +65,15 @@ export async function markSetupWizardComplete(
65
65
  await settings.flush();
66
66
  }
67
67
 
68
+ export interface RunSetupWizardOptions {
69
+ markComplete?: boolean;
70
+ playWelcomeIntro?: boolean;
71
+ }
72
+
68
73
  export async function runSetupWizard(
69
74
  ctx: InteractiveModeContext,
70
75
  scenes: readonly SetupScene[] = ALL_SCENES,
76
+ options: RunSetupWizardOptions = {},
71
77
  ): Promise<void> {
72
78
  if (scenes.length === 0) return;
73
79
  const component = new SetupWizardComponent(ctx, scenes);
@@ -79,11 +85,15 @@ export async function runSetupWizard(
79
85
  });
80
86
  try {
81
87
  await component.run();
82
- await markSetupWizardComplete(ctx.settings);
88
+ if (options.markComplete !== false) {
89
+ await markSetupWizardComplete(ctx.settings);
90
+ }
83
91
  } finally {
84
92
  component.dispose();
85
93
  ctx.ui.setFocus(component);
86
94
  overlay.hide();
87
95
  }
88
- ctx.playWelcomeIntro();
96
+ if (options.playWelcomeIntro !== false) {
97
+ ctx.playWelcomeIntro();
98
+ }
89
99
  }
@@ -0,0 +1,16 @@
1
+ import type { InteractiveModeContext } from "../types";
2
+
3
+ export async function runProviderSetupWizard(ctx: InteractiveModeContext): Promise<void> {
4
+ // Keep the full setup wizard behind the existing cold-start boundary; a static
5
+ // import here would load provider/OAuth/search/theme setup deps on every TUI startup.
6
+ const { ALL_SCENES, runSetupWizard } = await import("./index");
7
+ const providersScene = ALL_SCENES.find(scene => scene.id === "providers");
8
+ if (!providersScene) {
9
+ ctx.showError("Provider setup is unavailable.");
10
+ return;
11
+ }
12
+ await runSetupWizard(ctx, [providersScene], {
13
+ markComplete: false,
14
+ playWelcomeIntro: false,
15
+ });
16
+ }
@@ -99,6 +99,7 @@ export interface InteractiveModeContext {
99
99
  historyStorage?: HistoryStorage;
100
100
  mcpManager?: MCPManager;
101
101
  lspServers?: LspStartupServerInfo[];
102
+ titleSystemPrompt?: string;
102
103
 
103
104
  // State
104
105
  isInitialized: boolean;
@@ -288,6 +289,7 @@ export interface InteractiveModeContext {
288
289
  handleResumeSession(sessionPath: string): Promise<void>;
289
290
  handleSessionDeleteCommand(): Promise<void>;
290
291
  showOAuthSelector(mode: "login" | "logout", providerId?: string): Promise<void>;
292
+ showProviderSetup(): Promise<void>;
291
293
  showHookConfirm(title: string, message: string): Promise<boolean>;
292
294
  showDebugSelector(): Promise<void>;
293
295
  showSessionObserver(): void;
package/src/sdk.ts CHANGED
@@ -89,7 +89,7 @@ import type { HindsightSessionState } from "./hindsight/state";
89
89
  import { LocalProtocolHandler, type LocalProtocolOptions } from "./internal-urls";
90
90
  import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
91
91
  import { discoverAndLoadMCPTools, MCPManager, type MCPToolsLoadResult } from "./mcp";
92
- import { resolveMemoryBackend } from "./memory-backend";
92
+ import { createSessionMemoryRuntimeContext, resolveMemoryBackend } from "./memory-backend";
93
93
  import type { MnemopiSessionState } from "./mnemopi/state";
94
94
  import asyncResultTemplate from "./prompts/tools/async-result.md" with { type: "text" };
95
95
  import lateDiagnosticTemplate from "./prompts/tools/lsp-late-diagnostic.md" with { type: "text" };
@@ -99,6 +99,7 @@ import {
99
99
  deobfuscateSessionContext,
100
100
  loadSecrets,
101
101
  obfuscateMessages,
102
+ obfuscateProviderContext,
102
103
  SecretObfuscator,
103
104
  } from "./secrets";
104
105
  import { AgentSession } from "./session/agent-session";
@@ -134,6 +135,7 @@ import {
134
135
  parseThinkingLevel,
135
136
  resolveProvisionalAutoLevel,
136
137
  resolveThinkingLevelForModel,
138
+ shouldDisableReasoning,
137
139
  toReasoningEffort,
138
140
  } from "./thinking";
139
141
  import { countToolsForAutoDiscovery, resolveEffectiveToolDiscoveryMode } from "./tool-discovery/mode";
@@ -1791,6 +1793,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1791
1793
  cwd,
1792
1794
  sessionManager,
1793
1795
  modelRegistry,
1796
+ () => (hasSession ? createSessionMemoryRuntimeContext(session, agentDir, cwd) : undefined),
1794
1797
  );
1795
1798
 
1796
1799
  credentialDisabledTarget = extensionRunner;
@@ -2138,6 +2141,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2138
2141
  if (!obfuscator?.hasSecrets()) return converted;
2139
2142
  return obfuscateMessages(obfuscator, converted);
2140
2143
  };
2144
+
2141
2145
  const transformContext = async (messages: AgentMessage[], _signal?: AbortSignal) => {
2142
2146
  const withContext = await extensionRunner.emitContext(messages);
2143
2147
  return wrapSteeringForModel(withContext);
@@ -2173,6 +2177,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2173
2177
  systemPrompt,
2174
2178
  model,
2175
2179
  thinkingLevel: toReasoningEffort(effectiveThinkingLevel),
2180
+ disableReasoning: shouldDisableReasoning(effectiveThinkingLevel),
2176
2181
  tools: initialTools,
2177
2182
  },
2178
2183
  convertToLlm: convertToLlmFinal,
@@ -2181,6 +2186,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2181
2186
  sessionId: providerSessionId,
2182
2187
  promptCacheKey: options.providerPromptCacheKey,
2183
2188
  transformContext,
2189
+ transformProviderContext: obfuscator ? context => obfuscateProviderContext(obfuscator, context) : undefined,
2184
2190
  steeringMode: settings.get("steeringMode") ?? "one-at-a-time",
2185
2191
  followUpMode: settings.get("followUpMode") ?? "one-at-a-time",
2186
2192
  interruptMode: settings.get("interruptMode") ?? "immediate",
@@ -2267,6 +2273,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2267
2273
  thinkingLevel: autoThinking ? AUTO_THINKING : effectiveThinkingLevel,
2268
2274
  sessionManager,
2269
2275
  settings,
2276
+ autoApprove: options.autoApprove,
2270
2277
  evalKernelOwnerId,
2271
2278
  // Defined only for top-level sessions (creation is gated above).
2272
2279
  // AgentSession uses this to decide whether it may dispose the global
@@ -4,7 +4,14 @@ import { YAML } from "bun";
4
4
  import type { SecretEntry } from "./obfuscator";
5
5
  import { compileSecretRegex } from "./regex";
6
6
 
7
- export { deobfuscateSessionContext, obfuscateMessages, type SecretEntry, SecretObfuscator } from "./obfuscator";
7
+ export {
8
+ deobfuscateSessionContext,
9
+ obfuscateMessages,
10
+ obfuscateProviderContext,
11
+ obfuscateProviderTools,
12
+ type SecretEntry,
13
+ SecretObfuscator,
14
+ } from "./obfuscator";
8
15
 
9
16
  /**
10
17
  * Load secrets from project-local and global secrets.yml files.
@@ -1,4 +1,5 @@
1
- import type { Message, TextContent } from "@oh-my-pi/pi-ai";
1
+ import type { Context, Message, Tool } from "@oh-my-pi/pi-ai";
2
+ import { toolWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
2
3
  import type { SessionContext } from "../session/session-manager";
3
4
  import { compileSecretRegex } from "./regex";
4
5
 
@@ -184,6 +185,12 @@ export class SecretObfuscator {
184
185
  return deepWalkStrings(obj, s => this.deobfuscate(s));
185
186
  }
186
187
 
188
+ /** Deep-walk an object, obfuscating all string values. */
189
+ obfuscateObject<T>(obj: T): T {
190
+ if (!this.#hasAny) return obj;
191
+ return deepWalkStrings(obj, s => this.obfuscate(s));
192
+ }
193
+
187
194
  /** Find the obfuscate index for a known secret value. */
188
195
  #findObfuscateIndex(secret: string): number | undefined {
189
196
  // Check plain mappings first
@@ -211,25 +218,34 @@ export function deobfuscateSessionContext(
211
218
  // Message obfuscation (outbound to LLM)
212
219
  // ═══════════════════════════════════════════════════════════════════════════
213
220
 
214
- /** Obfuscate all text content in LLM messages (for outbound interception). */
221
+ /** Obfuscate all string content in LLM messages (for outbound interception). */
215
222
  export function obfuscateMessages(obfuscator: SecretObfuscator, messages: Message[]): Message[] {
216
- return messages.map(msg => {
217
- if (!Array.isArray(msg.content)) return msg;
223
+ return obfuscator.obfuscateObject(messages);
224
+ }
218
225
 
219
- let changed = false;
220
- const content = msg.content.map(block => {
221
- if (block.type === "text") {
222
- const obfuscated = obfuscator.obfuscate(block.text);
223
- if (obfuscated !== block.text) {
224
- changed = true;
225
- return { ...block, text: obfuscated } as TextContent;
226
- }
227
- }
228
- return block;
229
- });
226
+ /** Obfuscate provider request context without walking live tool schema instances. */
227
+ export function obfuscateProviderContext(obfuscator: SecretObfuscator | undefined, context: Context): Context {
228
+ if (!obfuscator?.hasSecrets()) return context;
229
+ return {
230
+ ...context,
231
+ systemPrompt: obfuscator.obfuscateObject(context.systemPrompt),
232
+ messages: obfuscator.obfuscateObject(context.messages),
233
+ tools: obfuscateProviderTools(obfuscator, context.tools),
234
+ };
235
+ }
230
236
 
231
- return changed ? ({ ...msg, content } as typeof msg) : msg;
232
- });
237
+ /** Convert tool schemas to wire JSON Schema before obfuscating provider-visible strings. */
238
+ export function obfuscateProviderTools(
239
+ obfuscator: SecretObfuscator | undefined,
240
+ tools: Tool[] | undefined,
241
+ ): Tool[] | undefined {
242
+ if (!tools || !obfuscator?.hasSecrets()) return tools;
243
+ return tools.map(tool => ({
244
+ ...tool,
245
+ description: obfuscator.obfuscate(tool.description),
246
+ parameters: obfuscator.obfuscateObject(toolWireSchema(tool)),
247
+ customFormat: tool.customFormat ? obfuscator.obfuscateObject(tool.customFormat) : undefined,
248
+ }));
233
249
  }
234
250
 
235
251
  // ═══════════════════════════════════════════════════════════════════════════
@@ -262,7 +278,7 @@ function deepWalkStrings<T>(obj: T, transform: (s: string) => string): T {
262
278
  });
263
279
  return (changed ? result : obj) as unknown as T;
264
280
  }
265
- if (obj !== null && typeof obj === "object") {
281
+ if (obj !== null && typeof obj === "object" && isPlainRecord(obj)) {
266
282
  let changed = false;
267
283
  const result: Record<string, unknown> = {};
268
284
  for (const key of Object.keys(obj)) {
@@ -275,3 +291,8 @@ function deepWalkStrings<T>(obj: T, transform: (s: string) => string): T {
275
291
  }
276
292
  return obj;
277
293
  }
294
+
295
+ function isPlainRecord(obj: object): obj is Record<string, unknown> {
296
+ const prototype = Object.getPrototypeOf(obj);
297
+ return prototype === Object.prototype || prototype === null;
298
+ }