@oh-my-pi/pi-coding-agent 17.2.5 → 17.2.7
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 +35 -1
- package/dist/{CHANGELOG-q2w43aw3.md → CHANGELOG-1p8yvde7.md} +35 -1
- package/dist/cli.js +3340 -3339
- package/dist/types/advisor/advise-tool.d.ts +10 -4
- package/dist/types/autoresearch/tools/init-experiment.d.ts +19 -7
- package/dist/types/autoresearch/tools/log-experiment.d.ts +19 -11
- package/dist/types/autoresearch/tools/run-experiment.d.ts +4 -2
- package/dist/types/autoresearch/tools/update-notes.d.ts +5 -2
- package/dist/types/cli/gc-cli.d.ts +1 -0
- package/dist/types/commit/agentic/tools/analyze-file.d.ts +5 -2
- package/dist/types/commit/agentic/tools/git-file-diff.d.ts +5 -2
- package/dist/types/commit/agentic/tools/git-hunk.d.ts +6 -2
- package/dist/types/commit/agentic/tools/git-overview.d.ts +5 -2
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +29 -7
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +14 -4
- package/dist/types/commit/agentic/tools/recent-commits.d.ts +4 -2
- package/dist/types/commit/agentic/tools/schemas.d.ts +7 -3
- package/dist/types/commit/agentic/tools/split-commit.d.ts +37 -10
- package/dist/types/commit/changelog/generate.d.ts +13 -3
- package/dist/types/commit/shared-llm.d.ts +13 -4
- package/dist/types/config/config-file.d.ts +1 -1
- package/dist/types/config/models-config-schema-bundle.d.ts +1992 -981
- package/dist/types/config/models-config-schema.d.ts +1201 -580
- package/dist/types/config/models-config.d.ts +355 -371
- package/dist/types/edit/hashline/params.d.ts +4 -2
- package/dist/types/edit/modes/apply-patch.d.ts +4 -2
- package/dist/types/edit/modes/patch.d.ts +16 -5
- package/dist/types/edit/modes/replace.d.ts +8 -3
- package/dist/types/eval/bridge-timeout.d.ts +5 -2
- package/dist/types/eval/completion-bridge.d.ts +13 -0
- package/dist/types/eval/py/tool-bridge.d.ts +12 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +4 -4
- package/dist/types/extensibility/custom-tools/types.d.ts +4 -4
- package/dist/types/extensibility/extensions/types.d.ts +4 -4
- package/dist/types/extensibility/hooks/types.d.ts +4 -4
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +1 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -1
- package/dist/types/extensibility/legacy-typebox.d.ts +8 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +6 -13
- package/dist/types/goals/tools/goal-tool.d.ts +12 -4
- package/dist/types/launch/client.d.ts +9 -1
- package/dist/types/launch/protocol.d.ts +17 -0
- package/dist/types/lsp/index.d.ts +15 -5
- package/dist/types/lsp/types.d.ts +15 -5
- package/dist/types/modes/components/btw-panel.d.ts +3 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/security/contracts/schemas.d.ts +1115 -537
- package/dist/types/security/publication.d.ts +92 -34
- package/dist/types/session/agent-session-types.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +26 -2
- package/dist/types/session/launch-completion.d.ts +10 -0
- package/dist/types/session/session-entries.d.ts +15 -1
- package/dist/types/session/session-manager.d.ts +7 -0
- package/dist/types/session/yield-queue.d.ts +6 -1
- package/dist/types/task/types.d.ts +63 -18
- package/dist/types/tools/ask.d.ts +34 -8
- package/dist/types/tools/ast-edit.d.ts +16 -4
- package/dist/types/tools/ast-grep.d.ts +12 -4
- package/dist/types/tools/bash.d.ts +20 -11
- package/dist/types/tools/browser.d.ts +64 -20
- package/dist/types/tools/checkpoint.d.ts +16 -8
- package/dist/types/tools/computer.d.ts +1 -1
- package/dist/types/tools/debug.d.ts +118 -48
- package/dist/types/tools/eval.d.ts +11 -5
- package/dist/types/tools/gh.d.ts +74 -26
- package/dist/types/tools/glob.d.ts +16 -6
- package/dist/types/tools/grep.d.ts +18 -6
- package/dist/types/tools/hub/index.d.ts +116 -46
- package/dist/types/tools/image-gen.d.ts +25 -8
- package/dist/types/tools/index.d.ts +9 -0
- package/dist/types/tools/inspect-image.d.ts +10 -4
- package/dist/types/tools/learn.d.ts +26 -8
- package/dist/types/tools/manage-skill.d.ts +16 -6
- package/dist/types/tools/memory-edit.d.ts +18 -6
- package/dist/types/tools/memory-recall.d.ts +8 -4
- package/dist/types/tools/memory-reflect.d.ts +10 -4
- package/dist/types/tools/memory-retain.d.ts +14 -4
- package/dist/types/tools/read.d.ts +8 -4
- package/dist/types/tools/security-scan.d.ts +82 -30
- package/dist/types/tools/todo.d.ts +30 -10
- package/dist/types/tools/tts.d.ts +12 -5
- package/dist/types/tools/vibe.d.ts +42 -18
- package/dist/types/tools/write.d.ts +10 -4
- package/dist/types/web/search/index.d.ts +20 -6
- package/package.json +13 -13
- package/scripts/legacy-pi-virtual-module.ts +2 -2
- package/src/advisor/advise-tool.ts +1 -1
- package/src/advisor/config.ts +1 -1
- package/src/autoresearch/tools/init-experiment.ts +1 -2
- package/src/autoresearch/tools/log-experiment.ts +1 -2
- package/src/autoresearch/tools/run-experiment.ts +1 -1
- package/src/autoresearch/tools/update-notes.ts +1 -1
- package/src/cli/gc-cli.ts +641 -21
- package/src/commit/agentic/tools/analyze-file.ts +1 -1
- package/src/commit/agentic/tools/git-file-diff.ts +1 -1
- package/src/commit/agentic/tools/git-hunk.ts +1 -1
- package/src/commit/agentic/tools/git-overview.ts +1 -1
- package/src/commit/agentic/tools/propose-changelog.ts +1 -1
- package/src/commit/agentic/tools/propose-commit.ts +1 -1
- package/src/commit/agentic/tools/recent-commits.ts +1 -1
- package/src/commit/agentic/tools/schemas.ts +1 -1
- package/src/commit/agentic/tools/split-commit.ts +1 -1
- package/src/commit/analysis/summary.ts +1 -1
- package/src/commit/changelog/generate.ts +1 -1
- package/src/config/config-file.ts +2 -2
- package/src/config/model-discovery.ts +14 -14
- package/src/config/model-registry.ts +10 -8
- package/src/config/models-config-schema-bundle.ts +1 -8
- package/src/config/settings.ts +1 -1
- package/src/discovery/codex.ts +22 -9
- package/src/edit/hashline/params.ts +1 -1
- package/src/edit/modes/apply-patch.ts +1 -1
- package/src/edit/modes/patch.ts +1 -1
- package/src/edit/modes/replace.ts +2 -1
- package/src/eval/agent-bridge.ts +4 -2
- package/src/eval/bridge-timeout.ts +5 -2
- package/src/eval/completion-bridge.ts +3 -2
- package/src/eval/executor-base.ts +20 -1
- package/src/eval/js/context-manager.ts +94 -6
- package/src/eval/py/tool-bridge.ts +29 -9
- package/src/export/html/index.ts +10 -3
- package/src/export/share.ts +4 -0
- package/src/extensibility/custom-commands/loader.ts +3 -3
- package/src/extensibility/custom-commands/types.ts +4 -4
- package/src/extensibility/custom-tools/loader.ts +3 -3
- package/src/extensibility/custom-tools/types.ts +5 -4
- package/src/extensibility/extensions/loader.ts +3 -3
- package/src/extensibility/extensions/types.ts +5 -4
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/hooks/types.ts +4 -4
- package/src/extensibility/legacy-pi-ai-shim.ts +1 -1
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +2 -2
- package/src/extensibility/legacy-typebox.ts +101 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +13 -22
- package/src/goals/tools/goal-tool.ts +1 -1
- package/src/launch/broker.ts +222 -6
- package/src/launch/client.ts +161 -9
- package/src/launch/protocol.ts +52 -0
- package/src/lsp/types.ts +1 -1
- package/src/main.ts +13 -6
- package/src/mcp/config-writer.ts +1 -1
- package/src/modes/components/btw-panel.ts +41 -4
- package/src/modes/controllers/btw-controller.ts +55 -7
- package/src/modes/controllers/command-controller.ts +31 -0
- package/src/modes/controllers/input-controller.ts +24 -7
- package/src/modes/interactive-mode.ts +16 -2
- package/src/modes/theme/theme.ts +3 -3
- package/src/modes/types.ts +8 -1
- package/src/prompts/session/launch-completion.md +1 -0
- package/src/sdk.ts +15 -0
- package/src/security/contracts/schemas.ts +202 -184
- package/src/security/contracts/validation.ts +6 -2
- package/src/security/publication.ts +1 -1
- package/src/security/store.ts +2 -2
- package/src/session/agent-session-types.ts +6 -0
- package/src/session/agent-session.ts +190 -14
- package/src/session/launch-completion.ts +37 -0
- package/src/session/session-context.ts +26 -0
- package/src/session/session-entries.ts +17 -1
- package/src/session/session-manager.ts +21 -0
- package/src/session/yield-queue.ts +121 -16
- package/src/slash-commands/builtin-registry.ts +10 -1
- package/src/task/types.ts +1 -1
- package/src/tools/ask.ts +1 -1
- package/src/tools/ast-edit.ts +1 -1
- package/src/tools/ast-grep.ts +1 -1
- package/src/tools/bash.ts +61 -3
- package/src/tools/browser/cmux/cmux-tab.ts +92 -4
- package/src/tools/browser.ts +1 -1
- package/src/tools/checkpoint.ts +1 -1
- package/src/tools/computer.ts +1 -1
- package/src/tools/debug.ts +1 -1
- package/src/tools/eval.ts +1 -1
- package/src/tools/gh.ts +1 -2
- package/src/tools/glob.ts +1 -1
- package/src/tools/grep.ts +1 -1
- package/src/tools/hub/index.ts +1 -1
- package/src/tools/hub/launch.ts +122 -6
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +9 -0
- package/src/tools/inspect-image.ts +1 -1
- package/src/tools/learn.ts +1 -1
- package/src/tools/manage-skill.ts +1 -1
- package/src/tools/memory-edit.ts +1 -1
- package/src/tools/memory-recall.ts +1 -1
- package/src/tools/memory-reflect.ts +1 -1
- package/src/tools/memory-retain.ts +1 -1
- package/src/tools/read.ts +1 -1
- package/src/tools/security-scan.ts +1 -1
- package/src/tools/todo.ts +1 -1
- package/src/tools/tts.ts +1 -1
- package/src/tools/vibe.ts +2 -1
- package/src/tools/write.ts +1 -1
- package/src/web/search/index.ts +2 -1
- package/dist/types/config/file-lock.d.ts +0 -29
- package/dist/types/extensibility/typebox.d.ts +0 -181
- package/src/config/file-lock.ts +0 -164
- package/src/extensibility/typebox.ts +0 -958
package/src/launch/protocol.ts
CHANGED
|
@@ -122,12 +122,29 @@ export type DaemonRpcResult =
|
|
|
122
122
|
export interface DaemonWireRequest {
|
|
123
123
|
id: string;
|
|
124
124
|
token: string;
|
|
125
|
+
owners?: string[];
|
|
126
|
+
detachedOwners?: string[];
|
|
127
|
+
completionEvents?: boolean;
|
|
128
|
+
completionAcks?: string[];
|
|
129
|
+
completionUnsubscribes?: string[];
|
|
130
|
+
completionReplays?: string[];
|
|
131
|
+
completionSubscriptionId?: string;
|
|
125
132
|
operation: DaemonOperation;
|
|
126
133
|
}
|
|
127
134
|
|
|
128
135
|
/** Response envelope kept raw until matched with its pending operation. */
|
|
129
136
|
export type DaemonWireResponse = { id: string; ok: true; result: unknown } | { id: string; ok: false; error: string };
|
|
130
137
|
|
|
138
|
+
/** Unsolicited terminal completion sent to the socket that owns a daemon. */
|
|
139
|
+
export interface DaemonCompletionNotification {
|
|
140
|
+
event: "daemon-completed";
|
|
141
|
+
completionId: string;
|
|
142
|
+
owner: string;
|
|
143
|
+
daemon: DaemonSnapshot;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export type DaemonWireMessage = DaemonWireResponse | DaemonCompletionNotification;
|
|
147
|
+
|
|
131
148
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
132
149
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
133
150
|
}
|
|
@@ -268,6 +285,27 @@ export function parseDaemonWireRequest(value: unknown): DaemonWireRequest {
|
|
|
268
285
|
return {
|
|
269
286
|
id: stringValue(source.id, "request.id"),
|
|
270
287
|
token: stringValue(source.token, "request.token"),
|
|
288
|
+
owners: source.owners === undefined ? undefined : stringArray(source.owners, "request.owners"),
|
|
289
|
+
detachedOwners:
|
|
290
|
+
source.detachedOwners === undefined ? undefined : stringArray(source.detachedOwners, "request.detachedOwners"),
|
|
291
|
+
completionEvents:
|
|
292
|
+
source.completionEvents === undefined
|
|
293
|
+
? undefined
|
|
294
|
+
: booleanValue(source.completionEvents, "request.completionEvents"),
|
|
295
|
+
completionAcks:
|
|
296
|
+
source.completionAcks === undefined ? undefined : stringArray(source.completionAcks, "request.completionAcks"),
|
|
297
|
+
completionUnsubscribes:
|
|
298
|
+
source.completionUnsubscribes === undefined
|
|
299
|
+
? undefined
|
|
300
|
+
: stringArray(source.completionUnsubscribes, "request.completionUnsubscribes"),
|
|
301
|
+
completionReplays:
|
|
302
|
+
source.completionReplays === undefined
|
|
303
|
+
? undefined
|
|
304
|
+
: stringArray(source.completionReplays, "request.completionReplays"),
|
|
305
|
+
completionSubscriptionId:
|
|
306
|
+
source.completionSubscriptionId === undefined
|
|
307
|
+
? undefined
|
|
308
|
+
: stringValue(source.completionSubscriptionId, "request.completionSubscriptionId"),
|
|
271
309
|
operation: parseDaemonOperation(source.operation),
|
|
272
310
|
};
|
|
273
311
|
}
|
|
@@ -281,6 +319,20 @@ export function parseDaemonWireResponse(value: unknown): DaemonWireResponse {
|
|
|
281
319
|
throw new Error("response.ok must be a boolean");
|
|
282
320
|
}
|
|
283
321
|
|
|
322
|
+
/** Decode one broker response or unsolicited completion notification. */
|
|
323
|
+
export function parseDaemonWireMessage(value: unknown): DaemonWireMessage {
|
|
324
|
+
const source = record(value, "daemon message");
|
|
325
|
+
if (source.event === "daemon-completed") {
|
|
326
|
+
return {
|
|
327
|
+
event: "daemon-completed",
|
|
328
|
+
completionId: stringValue(source.completionId, "completion.id"),
|
|
329
|
+
owner: stringValue(source.owner, "completion.owner"),
|
|
330
|
+
daemon: parseDaemonSnapshot(source.daemon),
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
return parseDaemonWireResponse(value);
|
|
334
|
+
}
|
|
335
|
+
|
|
284
336
|
function parseDaemonOperation(value: unknown): DaemonOperation {
|
|
285
337
|
const source = record(value, "daemon operation");
|
|
286
338
|
const op = stringValue(source.op, "operation.op");
|
package/src/lsp/types.ts
CHANGED
package/src/main.ts
CHANGED
|
@@ -1597,6 +1597,18 @@ export async function runRootCommand(
|
|
|
1597
1597
|
stdoutIsTTY: process.stdout.isTTY,
|
|
1598
1598
|
});
|
|
1599
1599
|
|
|
1600
|
+
// Startup changelog is only consumed by interactive mode below; kick the
|
|
1601
|
+
// CHANGELOG.md parse off now so it overlaps session creation instead of
|
|
1602
|
+
// serializing after it.
|
|
1603
|
+
const startupChangelogPromise = isInteractive
|
|
1604
|
+
? logger.time(
|
|
1605
|
+
"main:getChangelogForDisplay",
|
|
1606
|
+
getChangelogForDisplay,
|
|
1607
|
+
parsedArgs,
|
|
1608
|
+
settingsInstance.get("startup.changelogMode"),
|
|
1609
|
+
)
|
|
1610
|
+
: undefined;
|
|
1611
|
+
|
|
1600
1612
|
const { session, setToolUIContext, modelFallbackMessage, lspServers, mcpManager } = await createSession({
|
|
1601
1613
|
...sessionOptions,
|
|
1602
1614
|
eventBus,
|
|
@@ -1656,12 +1668,7 @@ export async function runRootCommand(
|
|
|
1656
1668
|
await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined, eventBus, rpcInput);
|
|
1657
1669
|
} else if (isInteractive) {
|
|
1658
1670
|
const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
|
|
1659
|
-
const startupChangelog = await
|
|
1660
|
-
"main:getChangelogForDisplay",
|
|
1661
|
-
getChangelogForDisplay,
|
|
1662
|
-
parsedArgs,
|
|
1663
|
-
settingsInstance.get("startup.changelogMode"),
|
|
1664
|
-
);
|
|
1671
|
+
const startupChangelog = await startupChangelogPromise;
|
|
1665
1672
|
|
|
1666
1673
|
const modelScopeNotification = buildModelScopeNotification(
|
|
1667
1674
|
scopedModels,
|
package/src/mcp/config-writer.ts
CHANGED
|
@@ -7,8 +7,8 @@ import { randomUUID } from "node:crypto";
|
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
10
|
+
import { withFileLock } from "@oh-my-pi/pi-utils/file-lock";
|
|
10
11
|
import { invalidate as invalidateFsCache } from "../capability/fs";
|
|
11
|
-
import { withFileLock } from "../config/file-lock";
|
|
12
12
|
|
|
13
13
|
import { validateServerConfig } from "./config";
|
|
14
14
|
import { MCP_CONFIG_SCHEMA_URL, type MCPConfigFile, type MCPServerConfig } from "./types";
|
|
@@ -3,16 +3,37 @@ import { replaceTabs } from "../../tools/render-utils";
|
|
|
3
3
|
import { getMarkdownTheme, theme } from "../theme/theme";
|
|
4
4
|
import { DynamicBorder } from "./dynamic-border";
|
|
5
5
|
|
|
6
|
-
type BtwPanelState = "running" | "complete" | "aborted" | "error";
|
|
6
|
+
type BtwPanelState = "running" | "complete" | "branching" | "aborted" | "error";
|
|
7
7
|
|
|
8
8
|
interface BtwPanelComponentOptions {
|
|
9
9
|
question: string;
|
|
10
10
|
tui: TUI;
|
|
11
|
+
canBranch?: () => boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class BtwFooter implements Component {
|
|
15
|
+
#getLine: () => string;
|
|
16
|
+
#line: string | undefined;
|
|
17
|
+
#text: Text | undefined;
|
|
18
|
+
|
|
19
|
+
constructor(getLine: () => string) {
|
|
20
|
+
this.#getLine = getLine;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
render(width: number): readonly string[] {
|
|
24
|
+
const line = this.#getLine();
|
|
25
|
+
if (line !== this.#line || !this.#text) {
|
|
26
|
+
this.#line = line;
|
|
27
|
+
this.#text = new Text(line, 1, 0);
|
|
28
|
+
}
|
|
29
|
+
return this.#text.render(width);
|
|
30
|
+
}
|
|
11
31
|
}
|
|
12
32
|
|
|
13
33
|
export class BtwPanelComponent extends Container {
|
|
14
34
|
#question: string;
|
|
15
35
|
#tui: TUI;
|
|
36
|
+
#canBranch: (() => boolean) | undefined;
|
|
16
37
|
#state: BtwPanelState = "running";
|
|
17
38
|
#answer = "";
|
|
18
39
|
#errorMessage: string | undefined;
|
|
@@ -23,6 +44,7 @@ export class BtwPanelComponent extends Container {
|
|
|
23
44
|
super();
|
|
24
45
|
this.#question = options.question;
|
|
25
46
|
this.#tui = options.tui;
|
|
47
|
+
this.#canBranch = options.canBranch;
|
|
26
48
|
this.#rebuild();
|
|
27
49
|
}
|
|
28
50
|
|
|
@@ -47,6 +69,14 @@ export class BtwPanelComponent extends Container {
|
|
|
47
69
|
this.#rebuild();
|
|
48
70
|
}
|
|
49
71
|
|
|
72
|
+
/** Shows that the completed answer is being promoted into the chat session. */
|
|
73
|
+
markBranching(): void {
|
|
74
|
+
if (this.#closed) return;
|
|
75
|
+
this.#state = "branching";
|
|
76
|
+
this.#errorMessage = undefined;
|
|
77
|
+
this.#rebuild();
|
|
78
|
+
}
|
|
79
|
+
|
|
50
80
|
markAborted(): void {
|
|
51
81
|
if (this.#closed) return;
|
|
52
82
|
this.#state = "aborted";
|
|
@@ -86,7 +116,7 @@ export class BtwPanelComponent extends Container {
|
|
|
86
116
|
this.addChild(new Spacer(1));
|
|
87
117
|
this.addChild(this.#contentComponent());
|
|
88
118
|
this.addChild(new Spacer(1));
|
|
89
|
-
this.addChild(new
|
|
119
|
+
this.addChild(new BtwFooter(() => this.#footerLine()));
|
|
90
120
|
this.addChild(new Spacer(1));
|
|
91
121
|
this.addChild(new DynamicBorder(str => theme.fg("dim", str)));
|
|
92
122
|
// Component-scoped: a rebuild replaces only this panel's own children
|
|
@@ -100,8 +130,15 @@ export class BtwPanelComponent extends Container {
|
|
|
100
130
|
switch (this.#state) {
|
|
101
131
|
case "running":
|
|
102
132
|
return theme.fg("muted", "Esc cancel /btw");
|
|
103
|
-
case "complete":
|
|
104
|
-
|
|
133
|
+
case "complete": {
|
|
134
|
+
if (!this.isCopyable()) return theme.fg("muted", "Esc dismiss");
|
|
135
|
+
const actions = ["c copy"];
|
|
136
|
+
if (this.#canBranch?.() ?? this.isBranchable()) actions.push("b branch to chat");
|
|
137
|
+
actions.push("Esc dismiss");
|
|
138
|
+
return theme.fg("muted", actions.join(" · "));
|
|
139
|
+
}
|
|
140
|
+
case "branching":
|
|
141
|
+
return theme.fg("muted", `${theme.status.pending} Branching to chat…`);
|
|
105
142
|
case "aborted":
|
|
106
143
|
return theme.fg("warning", `${theme.status.warning} Cancelled · Esc dismiss`);
|
|
107
144
|
case "error":
|
|
@@ -10,6 +10,7 @@ interface BtwRequest {
|
|
|
10
10
|
abortController: AbortController;
|
|
11
11
|
question: string;
|
|
12
12
|
leafId: string | null;
|
|
13
|
+
sessionId: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
function assistantMessageWithReplyText(assistantMessage: AssistantMessage, replyText: string): AssistantMessage {
|
|
@@ -39,6 +40,7 @@ export class BtwController {
|
|
|
39
40
|
#lastReplyText: string | undefined;
|
|
40
41
|
#lastAssistantMessage: AssistantMessage | undefined;
|
|
41
42
|
#lastLeafId: string | null | undefined;
|
|
43
|
+
#lastSessionId: string | undefined;
|
|
42
44
|
#branchInFlight = false;
|
|
43
45
|
#lastCopyText: string | undefined;
|
|
44
46
|
#copyInFlight = false;
|
|
@@ -50,17 +52,39 @@ export class BtwController {
|
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
canBranch(): boolean {
|
|
55
|
+
return this.#branchUnavailableReason() === undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Whether plain `b` is currently reserved for a completed or pending branch action. */
|
|
59
|
+
handlesBranchKey(): boolean {
|
|
60
|
+
if (this.#branchInFlight) return true;
|
|
61
|
+
if (this.#activeRequest?.component.isBranchable() !== true) return false;
|
|
53
62
|
return (
|
|
54
|
-
!this.#branchInFlight &&
|
|
55
|
-
this.#activeRequest?.component.isBranchable() === true &&
|
|
56
63
|
this.#lastQuestion !== undefined &&
|
|
57
64
|
this.#lastReplyText !== undefined &&
|
|
58
65
|
this.#lastAssistantMessage !== undefined &&
|
|
59
|
-
this.#lastLeafId !==
|
|
60
|
-
this.#
|
|
66
|
+
this.#lastLeafId !== undefined &&
|
|
67
|
+
this.#lastSessionId !== undefined
|
|
61
68
|
);
|
|
62
69
|
}
|
|
63
70
|
|
|
71
|
+
#branchUnavailableReason(): string | undefined {
|
|
72
|
+
if (this.#branchInFlight) return "a branch is already in progress";
|
|
73
|
+
if (this.#activeRequest?.component.isBranchable() !== true) return "the answer is not ready";
|
|
74
|
+
if (!this.#lastQuestion || !this.#lastReplyText || !this.#lastAssistantMessage) {
|
|
75
|
+
return "the answer is unavailable";
|
|
76
|
+
}
|
|
77
|
+
if (!this.#lastLeafId) return "the session has no branch point";
|
|
78
|
+
if (
|
|
79
|
+
this.#lastSessionId !== this.ctx.sessionManager.getSessionId() ||
|
|
80
|
+
this.#lastLeafId !== this.ctx.sessionManager.getLeafId()
|
|
81
|
+
) {
|
|
82
|
+
return "the session changed since /btw started";
|
|
83
|
+
}
|
|
84
|
+
if (this.ctx.session.isStreaming) return "a turn is still running";
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
64
88
|
canCopy(): boolean {
|
|
65
89
|
return (
|
|
66
90
|
!this.#copyInFlight && this.#activeRequest?.component.isCopyable() === true && this.#lastCopyText !== undefined
|
|
@@ -83,17 +107,34 @@ export class BtwController {
|
|
|
83
107
|
}
|
|
84
108
|
|
|
85
109
|
async handleBranch(): Promise<boolean> {
|
|
86
|
-
|
|
110
|
+
const unavailableReason = this.#branchUnavailableReason();
|
|
111
|
+
if (unavailableReason) {
|
|
112
|
+
this.ctx.showStatus(`/btw branch unavailable: ${unavailableReason}`, { dim: true });
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const request = this.#activeRequest;
|
|
116
|
+
const question = this.#lastQuestion;
|
|
117
|
+
const assistantMessage = this.#lastAssistantMessage;
|
|
118
|
+
const leafId = this.#lastLeafId;
|
|
119
|
+
const sessionId = this.#lastSessionId;
|
|
120
|
+
if (!request || !question || !assistantMessage || !leafId || !sessionId) return false;
|
|
121
|
+
|
|
87
122
|
this.#branchInFlight = true;
|
|
123
|
+
request.component.markBranching();
|
|
88
124
|
try {
|
|
89
|
-
await this.ctx.handleBtwBranch(
|
|
125
|
+
await this.ctx.handleBtwBranch(question, assistantMessage, leafId, sessionId);
|
|
90
126
|
return true;
|
|
91
127
|
} finally {
|
|
92
128
|
this.#branchInFlight = false;
|
|
129
|
+
if (this.#activeRequest === request) request.component.markComplete();
|
|
93
130
|
}
|
|
94
131
|
}
|
|
95
132
|
|
|
96
133
|
handleEscape(): boolean {
|
|
134
|
+
if (this.#branchInFlight) {
|
|
135
|
+
this.ctx.showStatus("/btw branch is in progress", { dim: true });
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
97
138
|
if (!this.#activeRequest) return false;
|
|
98
139
|
this.#closeActiveRequest({ abort: this.#activeRequest.abortController.signal.aborted === false });
|
|
99
140
|
return true;
|
|
@@ -119,10 +160,15 @@ export class BtwController {
|
|
|
119
160
|
this.#closeActiveRequest({ abort: true });
|
|
120
161
|
|
|
121
162
|
const request: BtwRequest = {
|
|
122
|
-
component: new BtwPanelComponent({
|
|
163
|
+
component: new BtwPanelComponent({
|
|
164
|
+
question: trimmedQuestion,
|
|
165
|
+
tui: this.ctx.ui,
|
|
166
|
+
canBranch: () => this.canBranch(),
|
|
167
|
+
}),
|
|
123
168
|
abortController: new AbortController(),
|
|
124
169
|
question: trimmedQuestion,
|
|
125
170
|
leafId: this.ctx.sessionManager.getLeafId(),
|
|
171
|
+
sessionId: this.ctx.sessionManager.getSessionId(),
|
|
126
172
|
};
|
|
127
173
|
this.ctx.btwContainer.clear();
|
|
128
174
|
this.ctx.btwContainer.addChild(request.component);
|
|
@@ -156,6 +202,7 @@ export class BtwController {
|
|
|
156
202
|
this.#lastCopyText = copyText;
|
|
157
203
|
this.#lastAssistantMessage = assistantMessageWithReplyText(assistantMessage, replyText);
|
|
158
204
|
this.#lastLeafId = request.leafId;
|
|
205
|
+
this.#lastSessionId = request.sessionId;
|
|
159
206
|
} else {
|
|
160
207
|
this.#clearCompletedState();
|
|
161
208
|
}
|
|
@@ -190,6 +237,7 @@ export class BtwController {
|
|
|
190
237
|
this.#lastAssistantMessage = undefined;
|
|
191
238
|
this.#lastCopyText = undefined;
|
|
192
239
|
this.#lastLeafId = undefined;
|
|
240
|
+
this.#lastSessionId = undefined;
|
|
193
241
|
}
|
|
194
242
|
|
|
195
243
|
#isActiveRequest(request: BtwRequest): boolean {
|
|
@@ -961,6 +961,37 @@ export class CommandController {
|
|
|
961
961
|
this.ctx.showStatus(`Fresh provider session started (${result.closedProviderSessions} ${stateLabel} pruned).`);
|
|
962
962
|
}
|
|
963
963
|
|
|
964
|
+
async handleResetContextCommand(): Promise<void> {
|
|
965
|
+
if (this.ctx.session.isCompacting) {
|
|
966
|
+
this.ctx.session.abortCompaction();
|
|
967
|
+
while (this.ctx.session.isCompacting) {
|
|
968
|
+
await Bun.sleep(10);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
const result = await this.ctx.session.resetSessionContext();
|
|
972
|
+
if (!result) {
|
|
973
|
+
this.ctx.showWarning("Wait for the current response to finish or abort it before resetting the context.");
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
// Drop the rendered transcript so the UI matches the now-empty model
|
|
977
|
+
// context (mirrors #runNewSessionFlow's teardown, minus the new session —
|
|
978
|
+
// the session id, title, and transcript file all survive).
|
|
979
|
+
this.ctx.clearTransientSessionUi();
|
|
980
|
+
this.ctx.resetTranscript();
|
|
981
|
+
this.ctx.statusLine.invalidate();
|
|
982
|
+
this.ctx.updateEditorBorderColor();
|
|
983
|
+
const noun = result.droppedCount === 1 ? "message" : "messages";
|
|
984
|
+
this.ctx.present([
|
|
985
|
+
new Spacer(1),
|
|
986
|
+
new Text(
|
|
987
|
+
`${theme.fg("accent", `${theme.status.success} Context reset — ${result.droppedCount} ${noun} dropped; session continues.`)}`,
|
|
988
|
+
1,
|
|
989
|
+
1,
|
|
990
|
+
),
|
|
991
|
+
]);
|
|
992
|
+
this.ctx.ui.requestRender(true, { clearScrollback: true });
|
|
993
|
+
}
|
|
994
|
+
|
|
964
995
|
async handleDropCommand(): Promise<void> {
|
|
965
996
|
if (!this.ctx.sessionManager.getSessionFile()) {
|
|
966
997
|
this.ctx.showError("Nothing to drop (in-memory session)");
|
|
@@ -250,7 +250,7 @@ export class InputController {
|
|
|
250
250
|
this.#btwBranchListenerInstalled = true;
|
|
251
251
|
this.ctx.ui.addInputListener(data => {
|
|
252
252
|
if (!matchesKey(data, "b")) return undefined;
|
|
253
|
-
if (!this.ctx.
|
|
253
|
+
if (!this.ctx.handlesBtwBranchKey()) return undefined;
|
|
254
254
|
if (this.ctx.ui.getFocused() !== this.ctx.editor) return undefined;
|
|
255
255
|
if (this.ctx.editor.getText().trim()) return undefined;
|
|
256
256
|
void this.ctx.handleBtwBranchKey();
|
|
@@ -801,6 +801,19 @@ export class InputController {
|
|
|
801
801
|
this.ctx.queueCompactionMessage(text, "steer", images);
|
|
802
802
|
return;
|
|
803
803
|
}
|
|
804
|
+
// Extension commands are local actions. Execute them before the normal
|
|
805
|
+
// submission path creates an optimistic user message; otherwise a
|
|
806
|
+
// consumed command remains rendered like a prompt sent to the model.
|
|
807
|
+
if (this.#isLocalExtensionCommand(text)) {
|
|
808
|
+
this.ctx.editor.clearDraft(text);
|
|
809
|
+
try {
|
|
810
|
+
await this.ctx.session.prompt(text, { images: inputImages });
|
|
811
|
+
} catch (error) {
|
|
812
|
+
this.ctx.editor.setText(text);
|
|
813
|
+
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
814
|
+
}
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
804
817
|
|
|
805
818
|
// If streaming, use prompt() with steer behavior
|
|
806
819
|
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
@@ -914,14 +927,18 @@ export class InputController {
|
|
|
914
927
|
* Local extension commands are consumed before reaching the shared session
|
|
915
928
|
* title gate and must not name the conversation.
|
|
916
929
|
*/
|
|
917
|
-
#
|
|
918
|
-
const runner = this.ctx.session.extensionRunner;
|
|
930
|
+
#isLocalExtensionCommand(text: string): boolean {
|
|
919
931
|
const extensionCommandSpace = text.indexOf(" ");
|
|
920
|
-
|
|
932
|
+
return (
|
|
921
933
|
text.startsWith("/") &&
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
934
|
+
this.ctx.session.extensionRunner?.getCommand(
|
|
935
|
+
extensionCommandSpace === -1 ? text.slice(1) : text.slice(1, extensionCommandSpace),
|
|
936
|
+
) !== undefined
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
#maybeStartTitleGeneration(text: string): void {
|
|
941
|
+
if (this.#isLocalExtensionCommand(text)) {
|
|
925
942
|
return;
|
|
926
943
|
}
|
|
927
944
|
this.ctx.session.maybeStartTitleGeneration(text, () => {
|
|
@@ -4513,6 +4513,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4513
4513
|
return this.#commandController.handleFreshCommand();
|
|
4514
4514
|
}
|
|
4515
4515
|
|
|
4516
|
+
handleResetContextCommand(): Promise<void> {
|
|
4517
|
+
return this.#commandController.handleResetContextCommand();
|
|
4518
|
+
}
|
|
4519
|
+
|
|
4516
4520
|
async handleDropCommand(): Promise<void> {
|
|
4517
4521
|
if (this.#vibeSessionTransitionBlocked()) return;
|
|
4518
4522
|
this.#prepareSessionSwitch();
|
|
@@ -4844,6 +4848,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4844
4848
|
return this.#btwController.canBranch();
|
|
4845
4849
|
}
|
|
4846
4850
|
|
|
4851
|
+
/** Reserves plain `b` only after /btw has a completed branch action to handle. */
|
|
4852
|
+
handlesBtwBranchKey(): boolean {
|
|
4853
|
+
return this.#btwController.handlesBranchKey();
|
|
4854
|
+
}
|
|
4855
|
+
|
|
4847
4856
|
handleBtwBranchKey(): Promise<boolean> {
|
|
4848
4857
|
return this.#btwController.handleBranch();
|
|
4849
4858
|
}
|
|
@@ -4856,9 +4865,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4856
4865
|
return this.#btwController.handleCopy();
|
|
4857
4866
|
}
|
|
4858
4867
|
|
|
4859
|
-
async handleBtwBranch(
|
|
4868
|
+
async handleBtwBranch(
|
|
4869
|
+
question: string,
|
|
4870
|
+
assistantMessage: AssistantMessage,
|
|
4871
|
+
leafId: string,
|
|
4872
|
+
sessionId: string,
|
|
4873
|
+
): Promise<void> {
|
|
4860
4874
|
try {
|
|
4861
|
-
const result = await this.session.branchFromBtw(question, assistantMessage);
|
|
4875
|
+
const result = await this.session.branchFromBtw(question, assistantMessage, leafId, sessionId);
|
|
4862
4876
|
if (result.cancelled) {
|
|
4863
4877
|
this.showStatus("/btw branch cancelled", { dim: true });
|
|
4864
4878
|
return;
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
+
import { type } from "@oh-my-pi/omptype";
|
|
3
4
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
4
5
|
import type { Effort } from "@oh-my-pi/pi-ai";
|
|
5
6
|
import {
|
|
@@ -19,7 +20,6 @@ import type {
|
|
|
19
20
|
TerminalAppearance,
|
|
20
21
|
} from "@oh-my-pi/pi-tui";
|
|
21
22
|
import { adjustHsv, colorLuma, getCustomThemesDir, isEnoent, logger, relativeLuminance } from "@oh-my-pi/pi-utils";
|
|
22
|
-
import { type } from "arktype";
|
|
23
23
|
import chalk from "chalk";
|
|
24
24
|
import { LRUCache } from "lru-cache/raw";
|
|
25
25
|
// Embed theme JSON files at build time
|
|
@@ -1118,7 +1118,7 @@ const spinnerFramesSchema = type("unknown").narrow((value): value is SpinnerFram
|
|
|
1118
1118
|
const themeJsonSchema = type({
|
|
1119
1119
|
"$schema?": "string",
|
|
1120
1120
|
name: "string",
|
|
1121
|
-
"vars?": "
|
|
1121
|
+
"vars?": { "[string]": "string | number" },
|
|
1122
1122
|
colors: themeColorsSchema,
|
|
1123
1123
|
"export?": {
|
|
1124
1124
|
"pageBg?": "string | number",
|
|
@@ -1127,7 +1127,7 @@ const themeJsonSchema = type({
|
|
|
1127
1127
|
},
|
|
1128
1128
|
"symbols?": {
|
|
1129
1129
|
"preset?": "'unicode' | 'nerd' | 'ascii'",
|
|
1130
|
-
"overrides?": "
|
|
1130
|
+
"overrides?": { "[string]": "string" },
|
|
1131
1131
|
"spinnerFrames?": spinnerFramesSchema,
|
|
1132
1132
|
},
|
|
1133
1133
|
});
|
package/src/modes/types.ts
CHANGED
|
@@ -350,6 +350,7 @@ export interface InteractiveModeContext {
|
|
|
350
350
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
351
351
|
handleClearCommand(): Promise<void>;
|
|
352
352
|
handleFreshCommand(): Promise<void>;
|
|
353
|
+
handleResetContextCommand(): Promise<void>;
|
|
353
354
|
handleDropCommand(): Promise<void>;
|
|
354
355
|
handleForkCommand(): Promise<void>;
|
|
355
356
|
handleBashCommand(command: string, excludeFromContext?: boolean): Promise<void>;
|
|
@@ -418,9 +419,15 @@ export interface InteractiveModeContext {
|
|
|
418
419
|
handleBtwEscape(): boolean;
|
|
419
420
|
handleBtwBranchKey(): Promise<boolean>;
|
|
420
421
|
canBranchBtw(): boolean;
|
|
422
|
+
handlesBtwBranchKey(): boolean;
|
|
421
423
|
canCopyBtw(): boolean;
|
|
422
424
|
handleBtwCopyKey(): Promise<boolean>;
|
|
423
|
-
handleBtwBranch(
|
|
425
|
+
handleBtwBranch(
|
|
426
|
+
question: string,
|
|
427
|
+
assistantMessage: AssistantMessage,
|
|
428
|
+
leafId: string,
|
|
429
|
+
sessionId: string,
|
|
430
|
+
): Promise<void>;
|
|
424
431
|
handleOmfgCommand(complaint: string): Promise<void>;
|
|
425
432
|
hasActiveOmfg(): boolean;
|
|
426
433
|
handleOmfgEscape(): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Supervised process {{name}} {{state}} {{#if hasExitCode}}with exit code {{exitCode}}{{else}}without an exit code{{/if}}.
|
package/src/sdk.ts
CHANGED
|
@@ -1686,6 +1686,7 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
|
|
|
1686
1686
|
// entries capture it at fetch time and are dropped at injection if a newer
|
|
1687
1687
|
// mutation (any tool) bumped it in the meantime.
|
|
1688
1688
|
const fileMutationVersions = new Map<string, number>();
|
|
1689
|
+
const disposeCallbacks = new Set<() => void>();
|
|
1689
1690
|
const activeToolNames = new Set<string>();
|
|
1690
1691
|
const toolRegistry = new Map<string, Tool>();
|
|
1691
1692
|
const setActiveToolNames = (names: Iterable<string>): void => {
|
|
@@ -1739,6 +1740,7 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
|
|
|
1739
1740
|
trackEvalExecution: (execution, abortController) =>
|
|
1740
1741
|
session ? session.trackEvalExecution(execution, abortController) : execution,
|
|
1741
1742
|
getSessionId: () => sessionManager.getSessionId?.() ?? null,
|
|
1743
|
+
isDisposed: () => session?.isDisposed ?? false,
|
|
1742
1744
|
getHindsightSessionState: () => session?.getHindsightSessionState(),
|
|
1743
1745
|
getMnemopiSessionState: () => session?.getMnemopiSessionState(),
|
|
1744
1746
|
getAgentId: () => resolvedAgentId,
|
|
@@ -1765,6 +1767,14 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
|
|
|
1765
1767
|
recordEvalSubagentUsage: output => sessionManager.recordEvalSubagentOutput(output),
|
|
1766
1768
|
getClientBridge: () => session?.clientBridge,
|
|
1767
1769
|
queueDeferredDiagnostics: entry => session?.yieldQueue.enqueue(LSP_LATE_DIAGNOSTIC_MESSAGE_TYPE, entry),
|
|
1770
|
+
queueLaunchCompletion: notification =>
|
|
1771
|
+
session?.queueLaunchCompletion(notification) ??
|
|
1772
|
+
Promise.reject(new Error("Session unavailable for launch completion delivery")),
|
|
1773
|
+
registerDisposeCallback: callback => {
|
|
1774
|
+
disposeCallbacks.add(callback);
|
|
1775
|
+
return () => disposeCallbacks.delete(callback);
|
|
1776
|
+
},
|
|
1777
|
+
registerSessionChangeCallback: callback => session?.registerSessionChangeCallback(callback),
|
|
1768
1778
|
bumpFileMutationVersion: path => {
|
|
1769
1779
|
const next = (fileMutationVersions.get(path) ?? 0) + 1;
|
|
1770
1780
|
fileMutationVersions.set(path, next);
|
|
@@ -3307,6 +3317,9 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
|
|
|
3307
3317
|
const id = sessionManager.getSessionId?.();
|
|
3308
3318
|
return id ? `${id}-advisor` : null;
|
|
3309
3319
|
},
|
|
3320
|
+
queueLaunchCompletion: notification =>
|
|
3321
|
+
session?.queueLaunchCompletion(notification) ??
|
|
3322
|
+
Promise.reject(new Error("Session unavailable for launch completion delivery")),
|
|
3310
3323
|
getAgentId: () => "advisor",
|
|
3311
3324
|
// The primary's availability signals are wrong for advisors: their tool
|
|
3312
3325
|
// slate is filtered separately at runtime (default read/grep/glob, no
|
|
@@ -3522,6 +3535,8 @@ async function createAgentSessionScoped(options: CreateAgentSessionOptions): Pro
|
|
|
3522
3535
|
unsubscribeCredentialDisabled?.();
|
|
3523
3536
|
unsubscribeMcpNotifications?.();
|
|
3524
3537
|
unregisterMcpPostmortem?.();
|
|
3538
|
+
for (const callback of disposeCallbacks) callback();
|
|
3539
|
+
disposeCallbacks.clear();
|
|
3525
3540
|
// Drop refs so the process-global postmortem list doesn't retain
|
|
3526
3541
|
// the bridge closure past explicit dispose.
|
|
3527
3542
|
unsubscribeMcpNotifications = undefined;
|