@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.
- package/CHANGELOG.md +44 -0
- package/dist/cli.js +5349 -5328
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli-commands.d.ts +12 -0
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/api-key-resolver.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +18 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
- package/dist/types/eval/py/executor.d.ts +5 -0
- package/dist/types/eval/py/kernel.d.ts +6 -1
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/runtime.d.ts +4 -0
- package/dist/types/memory-backend/types.d.ts +66 -1
- package/dist/types/modes/index.d.ts +3 -3
- package/dist/types/modes/interactive-mode.d.ts +7 -2
- package/dist/types/modes/oauth-manual-input.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
- package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
- package/dist/types/modes/setup-wizard/index.d.ts +5 -1
- package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/secrets/index.d.ts +1 -1
- package/dist/types/secrets/obfuscator.d.ts +8 -2
- package/dist/types/session/agent-session.d.ts +14 -2
- package/dist/types/session/streaming-output.d.ts +23 -0
- package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
- package/dist/types/slash-commands/types.d.ts +1 -1
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/index.d.ts +2 -2
- package/dist/types/task/types.d.ts +8 -0
- package/dist/types/thinking.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +11 -0
- package/dist/types/tiny/title-protocol.d.ts +1 -0
- package/dist/types/tools/index.d.ts +6 -0
- package/dist/types/utils/git.d.ts +15 -2
- package/dist/types/utils/title-generator.d.ts +3 -2
- package/package.json +10 -10
- package/src/auto-thinking/classifier.ts +1 -0
- package/src/cli/args.ts +3 -0
- package/src/cli-commands.ts +29 -0
- package/src/cli.ts +8 -9
- package/src/commands/launch.ts +4 -0
- package/src/commit/model-selection.ts +3 -2
- package/src/config/api-key-resolver.ts +8 -6
- package/src/config/model-registry.ts +97 -30
- package/src/config/model-resolver.ts +60 -0
- package/src/config/settings-schema.ts +43 -15
- package/src/config/settings.ts +61 -3
- package/src/edit/hashline/execute.ts +39 -2
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/eval/completion-bridge.ts +1 -0
- package/src/eval/py/executor.ts +29 -7
- package/src/eval/py/index.ts +6 -1
- package/src/eval/py/kernel.ts +31 -11
- package/src/eval/py/runtime.ts +37 -0
- package/src/exec/bash-executor.ts +82 -3
- package/src/extensibility/extensions/get-commands-handler.ts +2 -1
- package/src/extensibility/extensions/runner.ts +6 -1
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/hindsight/bank.ts +17 -2
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/main.ts +18 -6
- package/src/memories/index.ts +2 -0
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/local-backend.ts +9 -0
- package/src/memory-backend/off-backend.ts +9 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/types.ts +81 -1
- package/src/mnemopi/backend.ts +151 -4
- package/src/modes/acp/acp-agent.ts +119 -11
- package/src/modes/components/assistant-message.ts +19 -21
- package/src/modes/components/footer.ts +3 -1
- package/src/modes/components/status-line/component.ts +118 -34
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +1 -0
- package/src/modes/controllers/mcp-command-controller.ts +38 -3
- package/src/modes/index.ts +3 -21
- package/src/modes/interactive-mode.ts +39 -9
- package/src/modes/oauth-manual-input.ts +30 -3
- package/src/modes/rpc/rpc-client.ts +154 -3
- package/src/modes/rpc/rpc-mode.ts +97 -12
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +81 -1
- package/src/modes/setup-wizard/index.ts +12 -2
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/types.ts +2 -0
- package/src/sdk.ts +8 -1
- package/src/secrets/index.ts +8 -1
- package/src/secrets/obfuscator.ts +39 -18
- package/src/session/agent-session.ts +179 -54
- package/src/session/streaming-output.ts +166 -10
- package/src/slash-commands/acp-builtins.ts +24 -0
- package/src/slash-commands/builtin-registry.ts +20 -0
- package/src/slash-commands/types.ts +1 -1
- package/src/system-prompt.ts +14 -0
- package/src/task/executor.ts +13 -12
- package/src/task/index.ts +9 -8
- package/src/task/render.ts +18 -3
- package/src/task/types.ts +9 -0
- package/src/thinking.ts +7 -0
- package/src/tiny/title-client.ts +34 -5
- package/src/tiny/title-protocol.ts +1 -1
- package/src/tiny/worker.ts +6 -4
- package/src/tools/bash.ts +46 -5
- package/src/tools/image-gen.ts +11 -4
- package/src/tools/index.ts +13 -1
- package/src/tools/inspect-image.ts +1 -0
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/git.ts +267 -13
- 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
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/modes/types.ts
CHANGED
|
@@ -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
|
package/src/secrets/index.ts
CHANGED
|
@@ -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 {
|
|
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,
|
|
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
|
|
221
|
+
/** Obfuscate all string content in LLM messages (for outbound interception). */
|
|
215
222
|
export function obfuscateMessages(obfuscator: SecretObfuscator, messages: Message[]): Message[] {
|
|
216
|
-
return
|
|
217
|
-
|
|
223
|
+
return obfuscator.obfuscateObject(messages);
|
|
224
|
+
}
|
|
218
225
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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
|
+
}
|