@oh-my-pi/pi-coding-agent 15.12.2 → 15.12.4
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 +49 -1
- package/dist/cli.js +1121 -871
- package/dist/types/autoresearch/tools/init-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/log-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/run-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/update-notes.d.ts +1 -1
- package/dist/types/cli/args.d.ts +0 -1
- package/dist/types/cli/models-cli.d.ts +49 -0
- package/dist/types/commands/launch.d.ts +0 -3
- package/dist/types/commands/models.d.ts +33 -0
- package/dist/types/commands/token.d.ts +25 -0
- package/dist/types/commit/agentic/tools/analyze-file.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-file-diff.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-hunk.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-overview.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +1 -1
- package/dist/types/commit/agentic/tools/recent-commits.d.ts +1 -1
- package/dist/types/commit/agentic/tools/schemas.d.ts +1 -1
- package/dist/types/commit/agentic/tools/split-commit.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +1 -1
- package/dist/types/commit/shared-llm.d.ts +1 -1
- package/dist/types/config/model-registry.d.ts +7 -0
- package/dist/types/config/models-config-schema.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +21 -1
- package/dist/types/edit/hashline/params.d.ts +1 -1
- package/dist/types/edit/modes/apply-patch.d.ts +1 -1
- package/dist/types/edit/modes/patch.d.ts +1 -1
- package/dist/types/edit/modes/replace.d.ts +1 -1
- package/dist/types/extensibility/custom-commands/types.d.ts +2 -2
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +2 -2
- package/dist/types/extensibility/hooks/types.d.ts +2 -2
- package/dist/types/goals/tools/goal-tool.d.ts +1 -1
- package/dist/types/lsp/types.d.ts +1 -1
- package/dist/types/mcp/manager.d.ts +8 -0
- package/dist/types/mnemopi/config.d.ts +28 -0
- package/dist/types/modes/acp/acp-agent.d.ts +1 -2
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/logout-account-selector.d.ts +8 -0
- package/dist/types/modes/components/status-line/component.d.ts +9 -5
- package/dist/types/modes/components/status-line/types.d.ts +2 -1
- package/dist/types/modes/controllers/event-controller.d.ts +0 -17
- package/dist/types/modes/interactive-mode.d.ts +0 -3
- package/dist/types/modes/types.d.ts +0 -5
- package/dist/types/session/agent-session.d.ts +14 -33
- package/dist/types/session/agent-storage.d.ts +2 -1
- package/dist/types/session/indexed-session-storage.d.ts +1 -0
- package/dist/types/session/messages.d.ts +8 -10
- package/dist/types/session/session-manager.d.ts +15 -0
- package/dist/types/session/session-storage.d.ts +5 -0
- package/dist/types/slash-commands/helpers/logout.d.ts +15 -0
- package/dist/types/task/types.d.ts +1 -1
- package/dist/types/tools/ask.d.ts +1 -1
- package/dist/types/tools/ast-edit.d.ts +1 -1
- package/dist/types/tools/ast-grep.d.ts +1 -1
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +202 -0
- package/dist/types/tools/browser/cmux/rpc.d.ts +70 -0
- package/dist/types/tools/browser/cmux/socket-client.d.ts +19 -0
- package/dist/types/tools/browser/registry.d.ts +16 -3
- package/dist/types/tools/browser/render.d.ts +2 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +16 -4
- package/dist/types/tools/browser.d.ts +3 -1
- package/dist/types/tools/checkpoint.d.ts +1 -1
- package/dist/types/tools/debug.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +1 -1
- package/dist/types/tools/find.d.ts +1 -1
- package/dist/types/tools/gh.d.ts +1 -1
- package/dist/types/tools/image-gen.d.ts +1 -1
- package/dist/types/tools/index.d.ts +3 -1
- package/dist/types/tools/inspect-image.d.ts +1 -1
- package/dist/types/tools/irc.d.ts +1 -1
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/memory-edit.d.ts +1 -1
- package/dist/types/tools/memory-recall.d.ts +1 -1
- package/dist/types/tools/memory-reflect.d.ts +1 -1
- package/dist/types/tools/memory-retain.d.ts +1 -1
- package/dist/types/tools/read.d.ts +1 -1
- package/dist/types/tools/render-mermaid.d.ts +1 -1
- package/dist/types/tools/resolve.d.ts +1 -1
- package/dist/types/tools/review.d.ts +1 -1
- package/dist/types/tools/search-tool-bm25.d.ts +1 -1
- package/dist/types/tools/search.d.ts +1 -1
- package/dist/types/tools/ssh.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +1 -1
- package/dist/types/tools/tts.d.ts +1 -1
- package/dist/types/tools/write.d.ts +1 -1
- package/dist/types/utils/clipboard.d.ts +4 -3
- package/dist/types/utils/image-loading.d.ts +18 -1
- package/dist/types/utils/thinking-display.d.ts +17 -0
- package/dist/types/web/search/index.d.ts +1 -1
- package/package.json +14 -14
- package/src/autoresearch/storage.ts +2 -1
- package/src/autoresearch/tools/init-experiment.ts +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/autoresearch/tools/run-experiment.ts +1 -1
- package/src/autoresearch/tools/update-notes.ts +1 -1
- package/src/cli/args.ts +0 -8
- package/src/cli/auth-gateway-cli.ts +1 -1
- package/src/cli/bench-cli.ts +1 -1
- package/src/cli/dry-balance-cli.ts +1 -1
- package/src/cli/models-cli.ts +427 -0
- package/src/cli-commands.ts +2 -0
- package/src/collab/host.ts +9 -12
- package/src/commands/launch.ts +0 -3
- package/src/commands/models.ts +61 -0
- package/src/commands/token.ts +89 -0
- 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/commit/shared-llm.ts +1 -1
- package/src/config/model-registry.ts +15 -12
- package/src/config/model-resolver.ts +2 -2
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +19 -1
- 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 +1 -1
- package/src/eval/agent-bridge.ts +1 -1
- package/src/eval/completion-bridge.ts +1 -1
- package/src/export/html/template.js +24 -2
- package/src/export/html/tool-views.generated.js +2 -2
- package/src/extensibility/custom-commands/loader.ts +1 -1
- package/src/extensibility/custom-commands/types.ts +2 -2
- package/src/extensibility/custom-tools/loader.ts +1 -1
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/loader.ts +2 -2
- package/src/extensibility/extensions/types.ts +2 -2
- package/src/extensibility/hooks/loader.ts +1 -1
- package/src/extensibility/hooks/types.ts +2 -2
- package/src/extensibility/skills.ts +18 -3
- package/src/goals/tools/goal-tool.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +6 -3
- package/src/lsp/types.ts +1 -1
- package/src/main.ts +0 -25
- package/src/mcp/config-writer.ts +7 -3
- package/src/mcp/manager.ts +11 -0
- package/src/memories/index.ts +3 -1
- package/src/memories/storage.ts +2 -1
- package/src/mnemopi/config.ts +95 -11
- package/src/modes/acp/acp-agent.ts +5 -48
- package/src/modes/acp/acp-event-mapper.ts +5 -1
- package/src/modes/components/agent-hub.ts +2 -1
- package/src/modes/components/assistant-message.ts +8 -7
- package/src/modes/components/index.ts +1 -0
- package/src/modes/components/logout-account-selector.ts +130 -0
- package/src/modes/components/mcp-add-wizard.ts +1 -1
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/status-line/component.ts +54 -157
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/status-line/types.ts +2 -1
- package/src/modes/controllers/command-controller.ts +0 -12
- package/src/modes/controllers/event-controller.ts +23 -62
- package/src/modes/controllers/input-controller.ts +60 -31
- package/src/modes/controllers/mcp-command-controller.ts +44 -3
- package/src/modes/controllers/selector-controller.ts +56 -10
- package/src/modes/controllers/streaming-reveal.ts +4 -3
- package/src/modes/interactive-mode.ts +2 -8
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +0 -5
- package/src/modes/utils/ui-helpers.ts +2 -1
- package/src/prompts/system/empty-stop-retry.md +4 -6
- package/src/sdk.ts +15 -19
- package/src/session/agent-session.ts +125 -234
- package/src/session/agent-storage.ts +18 -9
- package/src/session/history-storage.ts +2 -1
- package/src/session/indexed-session-storage.ts +7 -0
- package/src/session/messages.ts +9 -11
- package/src/session/session-dump-format.ts +4 -2
- package/src/session/session-manager.ts +116 -0
- package/src/session/session-storage.ts +20 -0
- package/src/slash-commands/builtin-registry.ts +15 -1
- package/src/slash-commands/helpers/logout.ts +88 -0
- package/src/task/types.ts +1 -1
- package/src/tools/ask.ts +1 -1
- package/src/tools/ast-edit.ts +13 -4
- package/src/tools/ast-grep.ts +1 -1
- package/src/tools/bash.ts +1 -1
- package/src/tools/browser/cmux/cmux-tab.ts +1264 -0
- package/src/tools/browser/cmux/rpc.ts +156 -0
- package/src/tools/browser/cmux/socket-client.ts +309 -0
- package/src/tools/browser/registry.ts +37 -3
- package/src/tools/browser/render.ts +6 -1
- package/src/tools/browser/tab-protocol.ts +2 -0
- package/src/tools/browser/tab-supervisor.ts +189 -18
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/browser.ts +16 -1
- package/src/tools/checkpoint.ts +1 -1
- package/src/tools/debug.ts +1 -1
- package/src/tools/eval.ts +11 -6
- package/src/tools/fetch.ts +13 -2
- package/src/tools/find.ts +1 -1
- package/src/tools/gh.ts +1 -1
- package/src/tools/github-cache.ts +2 -1
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +3 -1
- package/src/tools/inspect-image.ts +3 -1
- package/src/tools/irc.ts +1 -1
- package/src/tools/job.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 +8 -2
- package/src/tools/render-mermaid.ts +1 -1
- package/src/tools/report-tool-issue.ts +3 -2
- package/src/tools/resolve.ts +1 -1
- package/src/tools/review.ts +1 -1
- package/src/tools/search-tool-bm25.ts +1 -1
- package/src/tools/search.ts +1 -1
- package/src/tools/ssh.ts +1 -1
- package/src/tools/todo.ts +1 -1
- package/src/tools/tts.ts +1 -1
- package/src/tools/write.ts +1 -1
- package/src/utils/clipboard.ts +35 -18
- package/src/utils/image-loading.ts +35 -4
- package/src/utils/thinking-display.ts +37 -0
- package/src/web/search/index.ts +1 -1
- package/dist/types/cli/list-models.d.ts +0 -30
- package/src/cli/list-models.ts +0 -194
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { Observation, ObservationEntry } from "../tab-protocol";
|
|
2
|
+
|
|
3
|
+
export interface CmuxKind {
|
|
4
|
+
kind: "cmux";
|
|
5
|
+
socketPath: string;
|
|
6
|
+
password?: string;
|
|
7
|
+
surface?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface CmuxOpenSplitResult {
|
|
11
|
+
surface_id?: unknown;
|
|
12
|
+
url?: unknown;
|
|
13
|
+
workspace_id?: unknown;
|
|
14
|
+
created_split?: unknown;
|
|
15
|
+
placement_strategy?: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface CmuxSnapshotRef {
|
|
19
|
+
role?: unknown;
|
|
20
|
+
name?: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CmuxSnapshotPage {
|
|
24
|
+
title?: unknown;
|
|
25
|
+
url?: unknown;
|
|
26
|
+
ready_state?: unknown;
|
|
27
|
+
text?: unknown;
|
|
28
|
+
html?: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CmuxSnapshotResult {
|
|
32
|
+
snapshot?: unknown;
|
|
33
|
+
refs?: Record<string, CmuxSnapshotRef>;
|
|
34
|
+
page?: CmuxSnapshotPage;
|
|
35
|
+
url?: unknown;
|
|
36
|
+
title?: unknown;
|
|
37
|
+
ready_state?: unknown;
|
|
38
|
+
surface_id?: unknown;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface CmuxEvalResult {
|
|
42
|
+
value?: unknown;
|
|
43
|
+
surface_id?: unknown;
|
|
44
|
+
content_world?: unknown;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface CmuxUrlGetResult {
|
|
48
|
+
url?: unknown;
|
|
49
|
+
surface_id?: unknown;
|
|
50
|
+
workspace_id?: unknown;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface CmuxScreenshotResult {
|
|
54
|
+
png_base64?: unknown;
|
|
55
|
+
path?: unknown;
|
|
56
|
+
url?: unknown;
|
|
57
|
+
surface_id?: unknown;
|
|
58
|
+
width?: unknown;
|
|
59
|
+
height?: unknown;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface CmuxGeometry {
|
|
63
|
+
innerWidth: number;
|
|
64
|
+
innerHeight: number;
|
|
65
|
+
dpr: number;
|
|
66
|
+
scrollX: number;
|
|
67
|
+
scrollY: number;
|
|
68
|
+
scrollWidth: number;
|
|
69
|
+
scrollHeight: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const GEOMETRY_SCRIPT =
|
|
73
|
+
"(() => ({ innerWidth: window.innerWidth, innerHeight: window.innerHeight, dpr: window.devicePixelRatio||1, scrollX: window.scrollX, scrollY: window.scrollY, scrollWidth: document.documentElement.scrollWidth, scrollHeight: document.documentElement.scrollHeight }))()";
|
|
74
|
+
|
|
75
|
+
export function cmuxSnapshotToObservation(
|
|
76
|
+
result: CmuxSnapshotResult,
|
|
77
|
+
viewport: Observation["viewport"],
|
|
78
|
+
geometry: CmuxGeometry,
|
|
79
|
+
): Observation {
|
|
80
|
+
const elements: ObservationEntry[] = [];
|
|
81
|
+
const refs = result.refs ?? {};
|
|
82
|
+
for (const ref in refs) {
|
|
83
|
+
const value = refs[ref];
|
|
84
|
+
if (!value) continue;
|
|
85
|
+
const id = Number.parseInt(ref.replace(/^@?e/, ""), 10);
|
|
86
|
+
if (Number.isNaN(id)) continue;
|
|
87
|
+
const role = typeof value.role === "string" && value.role.length > 0 ? value.role : "generic";
|
|
88
|
+
const name = typeof value.name === "string" && value.name.length > 0 ? value.name : undefined;
|
|
89
|
+
elements.push({ id, role, name, states: [] });
|
|
90
|
+
}
|
|
91
|
+
elements.sort((a, b) => a.id - b.id);
|
|
92
|
+
|
|
93
|
+
const url =
|
|
94
|
+
(typeof result.url === "string" && result.url.length > 0 ? result.url : undefined) ??
|
|
95
|
+
(typeof result.page?.url === "string" && result.page.url.length > 0 ? result.page.url : undefined) ??
|
|
96
|
+
"about:blank";
|
|
97
|
+
const title =
|
|
98
|
+
(typeof result.title === "string" && result.title.length > 0 ? result.title : undefined) ??
|
|
99
|
+
(typeof result.page?.title === "string" && result.page.title.length > 0 ? result.page.title : undefined);
|
|
100
|
+
return {
|
|
101
|
+
url,
|
|
102
|
+
title,
|
|
103
|
+
viewport,
|
|
104
|
+
scroll: {
|
|
105
|
+
x: geometry.scrollX,
|
|
106
|
+
y: geometry.scrollY,
|
|
107
|
+
width: geometry.innerWidth,
|
|
108
|
+
height: geometry.innerHeight,
|
|
109
|
+
scrollWidth: geometry.scrollWidth,
|
|
110
|
+
scrollHeight: geometry.scrollHeight,
|
|
111
|
+
},
|
|
112
|
+
elements,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function serializeEval(fn: string | ((...args: unknown[]) => unknown), args: unknown[]): string {
|
|
117
|
+
if (typeof fn === "string") {
|
|
118
|
+
return fn;
|
|
119
|
+
}
|
|
120
|
+
return `(${fn.toString()})(${args.map(arg => JSON.stringify(arg)).join(",")})`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function mapWaitUntil(waitUntil: string | undefined): "interactive" | "complete" {
|
|
124
|
+
return waitUntil === "domcontentloaded" ? "interactive" : "complete";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const TRUTHY_ENV_VALUES = new Set(["1", "Y", "y", "TRUE", "true", "YES", "yes", "ON", "on"]);
|
|
128
|
+
|
|
129
|
+
function resolveCmuxEnabled(envValue: string | undefined, settingEnabled: boolean): boolean {
|
|
130
|
+
if (!envValue) return settingEnabled;
|
|
131
|
+
return TRUTHY_ENV_VALUES.has(envValue);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface ResolveCmuxKindOptions {
|
|
135
|
+
surface?: string;
|
|
136
|
+
settingEnabled?: boolean;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function resolveCmuxKind(
|
|
140
|
+
options?: ResolveCmuxKindOptions | null,
|
|
141
|
+
env: Record<string, string | undefined> = process.env,
|
|
142
|
+
): CmuxKind | null {
|
|
143
|
+
if (!resolveCmuxEnabled(env.PI_BROWSER_CMUX, options?.settingEnabled ?? true)) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const socketPath = env.CMUX_SOCKET_PATH;
|
|
147
|
+
if (!socketPath) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
kind: "cmux",
|
|
152
|
+
socketPath,
|
|
153
|
+
password: env.CMUX_SOCKET_PASSWORD || undefined,
|
|
154
|
+
surface: options?.surface,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import * as net from "node:net";
|
|
3
|
+
import { ToolError } from "../../tool-errors";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 10_000;
|
|
6
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
7
|
+
|
|
8
|
+
type RequestJob = {
|
|
9
|
+
method: string;
|
|
10
|
+
params: Record<string, unknown>;
|
|
11
|
+
timeoutMs: number;
|
|
12
|
+
resolve: (value: Record<string, unknown>) => void;
|
|
13
|
+
reject: (error: unknown) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type LineWaiter = {
|
|
17
|
+
resolve: (line: string) => void;
|
|
18
|
+
reject: (error: unknown) => void;
|
|
19
|
+
timer: NodeJS.Timeout;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type CmuxErrorPayload = {
|
|
23
|
+
code?: unknown;
|
|
24
|
+
message?: unknown;
|
|
25
|
+
details?: unknown;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function formatCmuxError(error: CmuxErrorPayload | undefined): string {
|
|
29
|
+
const code = typeof error?.code === "string" && error.code.length > 0 ? error.code : "error";
|
|
30
|
+
const message = typeof error?.message === "string" && error.message.length > 0 ? error.message : "cmux error";
|
|
31
|
+
const details = error?.details === undefined ? "" : ` details=${JSON.stringify(error.details)}`;
|
|
32
|
+
return `${code}: ${message}${details}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class CmuxSocketClient {
|
|
36
|
+
readonly #socketPath: string;
|
|
37
|
+
readonly #password: string | undefined;
|
|
38
|
+
#socket: net.Socket | null = null;
|
|
39
|
+
#connectPromise: Promise<void> | null = null;
|
|
40
|
+
#connected = false;
|
|
41
|
+
#disposed = false;
|
|
42
|
+
#buffer = "";
|
|
43
|
+
readonly #lineWaiters: LineWaiter[] = [];
|
|
44
|
+
readonly #queue: RequestJob[] = [];
|
|
45
|
+
#activeJob: RequestJob | null = null;
|
|
46
|
+
#pumping = false;
|
|
47
|
+
|
|
48
|
+
constructor(opts: { socketPath: string; password?: string }) {
|
|
49
|
+
this.#socketPath = opts.socketPath;
|
|
50
|
+
this.#password = opts.password;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async connect(): Promise<void> {
|
|
54
|
+
if (this.#disposed) {
|
|
55
|
+
throw new ToolError("cmux socket closed");
|
|
56
|
+
}
|
|
57
|
+
if (this.#connected && this.#socket && !this.#socket.destroyed) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (this.#connectPromise) {
|
|
61
|
+
return await this.#connectPromise;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.#connectPromise = this.#openSocket();
|
|
65
|
+
try {
|
|
66
|
+
await this.#connectPromise;
|
|
67
|
+
} finally {
|
|
68
|
+
this.#connectPromise = null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async request(
|
|
73
|
+
method: string,
|
|
74
|
+
params: Record<string, unknown>,
|
|
75
|
+
opts?: { timeoutMs?: number },
|
|
76
|
+
): Promise<Record<string, unknown>> {
|
|
77
|
+
if (this.#disposed) {
|
|
78
|
+
throw new ToolError("cmux socket closed");
|
|
79
|
+
}
|
|
80
|
+
const { promise, resolve, reject } = Promise.withResolvers<Record<string, unknown>>();
|
|
81
|
+
this.#queue.push({
|
|
82
|
+
method,
|
|
83
|
+
params,
|
|
84
|
+
timeoutMs: opts?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS,
|
|
85
|
+
resolve,
|
|
86
|
+
reject,
|
|
87
|
+
});
|
|
88
|
+
this.#pump();
|
|
89
|
+
return await promise;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
close(): void {
|
|
93
|
+
this.#disposed = true;
|
|
94
|
+
const err = new ToolError("cmux socket closed");
|
|
95
|
+
this.#rejectAll(err);
|
|
96
|
+
this.#socket?.end();
|
|
97
|
+
this.#socket?.destroy();
|
|
98
|
+
this.#socket = null;
|
|
99
|
+
this.#connected = false;
|
|
100
|
+
this.#connectPromise = null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async #openSocket(): Promise<void> {
|
|
104
|
+
const socket = net.createConnection({ path: this.#socketPath });
|
|
105
|
+
this.#socket = socket;
|
|
106
|
+
this.#buffer = "";
|
|
107
|
+
socket.setEncoding("utf8");
|
|
108
|
+
socket.on("data", chunk => this.#onData(String(chunk)));
|
|
109
|
+
socket.on("error", err => this.#handleSocketFailure(err));
|
|
110
|
+
socket.on("close", () => this.#handleSocketClose());
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
await this.#waitForConnect(socket);
|
|
114
|
+
this.#connected = true;
|
|
115
|
+
if (this.#password) {
|
|
116
|
+
const line = await this.#sendLine(`auth ${this.#password}`, DEFAULT_CONNECT_TIMEOUT_MS);
|
|
117
|
+
if (line.startsWith("ERROR:") && !line.includes("Unknown command 'auth'")) {
|
|
118
|
+
throw new ToolError(line);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (err) {
|
|
122
|
+
this.#connected = false;
|
|
123
|
+
socket.destroy();
|
|
124
|
+
if (err instanceof ToolError) throw err;
|
|
125
|
+
throw new ToolError(
|
|
126
|
+
`Failed to connect to cmux socket at ${this.#socketPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#waitForConnect(socket: net.Socket): Promise<void> {
|
|
132
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
133
|
+
const timer = setTimeout(() => {
|
|
134
|
+
socket.destroy();
|
|
135
|
+
reject(new ToolError(`Failed to connect to cmux socket at ${this.#socketPath}: timed out`));
|
|
136
|
+
}, DEFAULT_CONNECT_TIMEOUT_MS);
|
|
137
|
+
const cleanup = (): void => {
|
|
138
|
+
clearTimeout(timer);
|
|
139
|
+
socket.off("connect", onConnect);
|
|
140
|
+
socket.off("error", onError);
|
|
141
|
+
};
|
|
142
|
+
const onConnect = (): void => {
|
|
143
|
+
cleanup();
|
|
144
|
+
resolve();
|
|
145
|
+
};
|
|
146
|
+
const onError = (err: Error): void => {
|
|
147
|
+
cleanup();
|
|
148
|
+
reject(new ToolError(`Failed to connect to cmux socket at ${this.#socketPath}: ${err.message}`));
|
|
149
|
+
};
|
|
150
|
+
socket.once("connect", onConnect);
|
|
151
|
+
socket.once("error", onError);
|
|
152
|
+
return promise;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#pump(): void {
|
|
156
|
+
if (this.#pumping) return;
|
|
157
|
+
this.#pumping = true;
|
|
158
|
+
void this.#pumpLoop();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async #pumpLoop(): Promise<void> {
|
|
162
|
+
try {
|
|
163
|
+
while (this.#queue.length > 0 && !this.#disposed) {
|
|
164
|
+
const job = this.#queue.shift();
|
|
165
|
+
if (!job) continue;
|
|
166
|
+
this.#activeJob = job;
|
|
167
|
+
try {
|
|
168
|
+
await this.connect();
|
|
169
|
+
const request = JSON.stringify({ id: randomUUID(), method: job.method, params: job.params });
|
|
170
|
+
const line = await this.#sendLine(request, job.timeoutMs);
|
|
171
|
+
job.resolve(this.#parseResponse(line));
|
|
172
|
+
} catch (err) {
|
|
173
|
+
job.reject(err instanceof Error ? err : new ToolError(String(err)));
|
|
174
|
+
} finally {
|
|
175
|
+
if (this.#activeJob === job) {
|
|
176
|
+
this.#activeJob = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
} finally {
|
|
181
|
+
this.#pumping = false;
|
|
182
|
+
if (this.#queue.length > 0 && !this.#disposed) {
|
|
183
|
+
this.#pump();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#sendLine(line: string, timeoutMs: number): Promise<string> {
|
|
189
|
+
if (!this.#socket || this.#socket.destroyed) {
|
|
190
|
+
throw new ToolError("cmux socket is not connected");
|
|
191
|
+
}
|
|
192
|
+
const read = this.#nextLine(timeoutMs);
|
|
193
|
+
this.#socket.write(`${line}\n`, err => {
|
|
194
|
+
if (err) {
|
|
195
|
+
this.#handleSocketFailure(err);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return read;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#nextLine(timeoutMs: number): Promise<string> {
|
|
202
|
+
const { promise, resolve, reject } = Promise.withResolvers<string>();
|
|
203
|
+
let waiter: LineWaiter;
|
|
204
|
+
const timer = setTimeout(() => {
|
|
205
|
+
const index = this.#lineWaiters.indexOf(waiter);
|
|
206
|
+
if (index >= 0) {
|
|
207
|
+
this.#lineWaiters.splice(index, 1);
|
|
208
|
+
}
|
|
209
|
+
reject(new ToolError("Timed out waiting for cmux socket response"));
|
|
210
|
+
this.#destroySocketForDesync();
|
|
211
|
+
}, timeoutMs);
|
|
212
|
+
waiter = {
|
|
213
|
+
resolve: line => {
|
|
214
|
+
clearTimeout(timer);
|
|
215
|
+
resolve(line);
|
|
216
|
+
},
|
|
217
|
+
reject: err => {
|
|
218
|
+
clearTimeout(timer);
|
|
219
|
+
reject(err);
|
|
220
|
+
},
|
|
221
|
+
timer,
|
|
222
|
+
};
|
|
223
|
+
this.#lineWaiters.push(waiter);
|
|
224
|
+
this.#drainLines();
|
|
225
|
+
return promise;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#onData(chunk: string): void {
|
|
229
|
+
this.#buffer += chunk;
|
|
230
|
+
this.#drainLines();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#drainLines(): void {
|
|
234
|
+
while (this.#lineWaiters.length > 0) {
|
|
235
|
+
const newlineIndex = this.#buffer.indexOf("\n");
|
|
236
|
+
if (newlineIndex < 0) return;
|
|
237
|
+
let line = this.#buffer.slice(0, newlineIndex);
|
|
238
|
+
this.#buffer = this.#buffer.slice(newlineIndex + 1);
|
|
239
|
+
if (line.endsWith("\r")) {
|
|
240
|
+
line = line.slice(0, -1);
|
|
241
|
+
}
|
|
242
|
+
const waiter = this.#lineWaiters.shift();
|
|
243
|
+
waiter?.resolve(line);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
#parseResponse(line: string): Record<string, unknown> {
|
|
248
|
+
if (line.startsWith("ERROR:")) {
|
|
249
|
+
throw new ToolError(line);
|
|
250
|
+
}
|
|
251
|
+
let payload: unknown;
|
|
252
|
+
try {
|
|
253
|
+
payload = JSON.parse(line);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
throw new ToolError(`Invalid cmux socket JSON response: ${err instanceof Error ? err.message : String(err)}`);
|
|
256
|
+
}
|
|
257
|
+
if (!payload || typeof payload !== "object") {
|
|
258
|
+
throw new ToolError("Invalid cmux socket response: expected object");
|
|
259
|
+
}
|
|
260
|
+
const response = payload as { ok?: unknown; result?: unknown; error?: CmuxErrorPayload };
|
|
261
|
+
if (response.ok === true) {
|
|
262
|
+
return (response.result ?? {}) as Record<string, unknown>;
|
|
263
|
+
}
|
|
264
|
+
if (response.ok === false) {
|
|
265
|
+
throw new ToolError(formatCmuxError(response.error));
|
|
266
|
+
}
|
|
267
|
+
throw new ToolError("Invalid cmux socket response: missing ok flag");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
#handleSocketFailure(err: Error): void {
|
|
271
|
+
if (this.#disposed) return;
|
|
272
|
+
this.#connected = false;
|
|
273
|
+
this.#connectPromise = null;
|
|
274
|
+
this.#rejectAll(new ToolError(`cmux socket error: ${err.message}`));
|
|
275
|
+
this.#socket?.destroy();
|
|
276
|
+
this.#socket = null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
#handleSocketClose(): void {
|
|
280
|
+
const hadPendingRead = this.#lineWaiters.length > 0;
|
|
281
|
+
this.#connected = false;
|
|
282
|
+
this.#connectPromise = null;
|
|
283
|
+
this.#socket = null;
|
|
284
|
+
if (!this.#disposed && hadPendingRead) {
|
|
285
|
+
this.#rejectAll(new ToolError("cmux socket closed"));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
#destroySocketForDesync(): void {
|
|
290
|
+
this.#connected = false;
|
|
291
|
+
this.#connectPromise = null;
|
|
292
|
+
this.#socket?.destroy();
|
|
293
|
+
this.#socket = null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
#rejectAll(err: ToolError): void {
|
|
297
|
+
for (const waiter of this.#lineWaiters.splice(0)) {
|
|
298
|
+
clearTimeout(waiter.timer);
|
|
299
|
+
waiter.reject(err);
|
|
300
|
+
}
|
|
301
|
+
if (this.#activeJob) {
|
|
302
|
+
this.#activeJob.reject(err);
|
|
303
|
+
this.#activeJob = null;
|
|
304
|
+
}
|
|
305
|
+
for (const job of this.#queue.splice(0)) {
|
|
306
|
+
job.reject(err);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -4,26 +4,42 @@ import type { Subprocess } from "bun";
|
|
|
4
4
|
import type { Browser, CDPSession } from "puppeteer-core";
|
|
5
5
|
import { ToolAbortError, ToolError } from "../tool-errors";
|
|
6
6
|
import { findFreeCdpPort, findReusableCdp, gracefulKillTreeOnce, killExistingByPath, waitForCdp } from "./attach";
|
|
7
|
+
import type { CmuxKind } from "./cmux/rpc";
|
|
8
|
+
import { CmuxSocketClient } from "./cmux/socket-client";
|
|
7
9
|
import { BROWSER_PROTOCOL_TIMEOUT_MS, launchHeadlessBrowser, loadPuppeteer, type UserAgentOverride } from "./launch";
|
|
8
10
|
|
|
9
|
-
export type
|
|
11
|
+
export type PuppeteerBrowserKind =
|
|
10
12
|
| { kind: "headless"; headless: boolean }
|
|
11
13
|
| { kind: "spawned"; path: string }
|
|
12
14
|
| { kind: "connected"; cdpUrl: string };
|
|
13
15
|
|
|
16
|
+
export type BrowserKind = PuppeteerBrowserKind | CmuxKind;
|
|
17
|
+
|
|
14
18
|
export type BrowserKindTag = BrowserKind["kind"];
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
interface BrowserHandleCommon {
|
|
17
21
|
key: string;
|
|
18
22
|
kind: BrowserKind;
|
|
23
|
+
refCount: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface PuppeteerBrowserHandle extends BrowserHandleCommon {
|
|
27
|
+
kind: PuppeteerBrowserKind;
|
|
19
28
|
browser: Browser;
|
|
20
29
|
cdpUrl?: string;
|
|
21
30
|
pid?: number;
|
|
22
31
|
subprocess?: Subprocess;
|
|
23
|
-
refCount: number;
|
|
24
32
|
stealth: { browserSession: CDPSession | null; override: UserAgentOverride | null };
|
|
25
33
|
}
|
|
26
34
|
|
|
35
|
+
export interface CmuxBrowserHandle extends BrowserHandleCommon {
|
|
36
|
+
kind: CmuxKind;
|
|
37
|
+
client: CmuxSocketClient;
|
|
38
|
+
surface?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type BrowserHandle = PuppeteerBrowserHandle | CmuxBrowserHandle;
|
|
42
|
+
|
|
27
43
|
const browsers = new Map<string, BrowserHandle>();
|
|
28
44
|
|
|
29
45
|
function browserKey(kind: BrowserKind): string {
|
|
@@ -34,6 +50,8 @@ function browserKey(kind: BrowserKind): string {
|
|
|
34
50
|
return `spawned:${kind.path}`;
|
|
35
51
|
case "connected":
|
|
36
52
|
return `connected:${kind.cdpUrl}`;
|
|
53
|
+
case "cmux":
|
|
54
|
+
return `cmux:${kind.socketPath}`;
|
|
37
55
|
}
|
|
38
56
|
}
|
|
39
57
|
|
|
@@ -48,6 +66,7 @@ export async function acquireBrowser(kind: BrowserKind, opts: AcquireBrowserOpti
|
|
|
48
66
|
const key = browserKey(kind);
|
|
49
67
|
const existing = browsers.get(key);
|
|
50
68
|
if (existing) {
|
|
69
|
+
if ("client" in existing) return existing;
|
|
51
70
|
if (existing.browser.connected) return existing;
|
|
52
71
|
browsers.delete(key);
|
|
53
72
|
await disposeBrowserHandle(existing, { kill: false });
|
|
@@ -69,6 +88,17 @@ export function normalizeConnectedCdpUrl(rawCdpUrl: string): string {
|
|
|
69
88
|
}
|
|
70
89
|
|
|
71
90
|
async function openBrowserHandle(kind: BrowserKind, opts: AcquireBrowserOptions): Promise<BrowserHandle> {
|
|
91
|
+
if (kind.kind === "cmux") {
|
|
92
|
+
const client = new CmuxSocketClient({ socketPath: kind.socketPath, password: kind.password });
|
|
93
|
+
await client.connect();
|
|
94
|
+
return {
|
|
95
|
+
key: browserKey(kind),
|
|
96
|
+
kind,
|
|
97
|
+
client,
|
|
98
|
+
surface: kind.surface,
|
|
99
|
+
refCount: 0,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
72
102
|
if (kind.kind === "headless") {
|
|
73
103
|
const browser = await launchHeadlessBrowser({ headless: kind.headless, viewport: opts.viewport });
|
|
74
104
|
return {
|
|
@@ -176,6 +206,10 @@ export async function releaseBrowser(handle: BrowserHandle, opts: { kill: boolea
|
|
|
176
206
|
}
|
|
177
207
|
|
|
178
208
|
async function disposeBrowserHandle(handle: BrowserHandle, opts: { kill: boolean }): Promise<void> {
|
|
209
|
+
if ("client" in handle) {
|
|
210
|
+
handle.client.close();
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
179
213
|
if (handle.kind.kind === "headless") {
|
|
180
214
|
if (handle.browser.connected) {
|
|
181
215
|
try {
|
|
@@ -23,7 +23,7 @@ interface BrowserRenderArgs {
|
|
|
23
23
|
code?: string;
|
|
24
24
|
all?: boolean;
|
|
25
25
|
kill?: boolean;
|
|
26
|
-
app?: { path?: string; cdp_url?: string; target?: string };
|
|
26
|
+
app?: { path?: string; cdp_url?: string; target?: string; cmux?: boolean; surface?: string };
|
|
27
27
|
viewport?: { width: number; height: number; scale?: number };
|
|
28
28
|
timeout?: number;
|
|
29
29
|
}
|
|
@@ -36,6 +36,9 @@ interface BrowserRenderContext {
|
|
|
36
36
|
function describeBrowser(args: BrowserRenderArgs, details: BrowserToolDetails | undefined): string | undefined {
|
|
37
37
|
if (args.app?.cdp_url) return `connected ${args.app.cdp_url}`;
|
|
38
38
|
if (args.app?.path) return `spawned ${shortenPath(args.app.path)}`;
|
|
39
|
+
if (args.app?.cmux !== false && (args.app?.cmux === true || args.app?.surface)) {
|
|
40
|
+
return args.app.surface ? `cmux ${args.app.surface}` : "cmux";
|
|
41
|
+
}
|
|
39
42
|
switch (details?.browser) {
|
|
40
43
|
case "headless":
|
|
41
44
|
return "headless";
|
|
@@ -43,6 +46,8 @@ function describeBrowser(args: BrowserRenderArgs, details: BrowserToolDetails |
|
|
|
43
46
|
return "spawned";
|
|
44
47
|
case "connected":
|
|
45
48
|
return "connected";
|
|
49
|
+
case "cmux":
|
|
50
|
+
return "cmux";
|
|
46
51
|
default:
|
|
47
52
|
return undefined;
|
|
48
53
|
}
|
|
@@ -38,6 +38,8 @@ export interface ScreenshotResult {
|
|
|
38
38
|
export interface SessionSnapshot {
|
|
39
39
|
cwd: string;
|
|
40
40
|
browserScreenshotDir?: string;
|
|
41
|
+
/** Force non-WebP screenshot encoding (e.g. for Ollama). Unset honors `OMP_NO_WEBP`. */
|
|
42
|
+
excludeWebP?: boolean;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
export type WorkerInitPayload =
|