open-multi-agent-kit 0.99.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +3 -3
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +1 -0
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/register-bundled-coding-agent.d.ts +10 -0
- package/dist/bun/register-bundled-coding-agent.d.ts.map +1 -0
- package/dist/bun/register-bundled-coding-agent.js +12 -0
- package/dist/bun/register-bundled-coding-agent.js.map +1 -0
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +1 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/neo-cli.d.ts +9 -0
- package/dist/commands/neo-cli.d.ts.map +1 -0
- package/dist/commands/neo-cli.js +61 -0
- package/dist/commands/neo-cli.js.map +1 -0
- package/dist/core/agent-session.d.ts +29 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +107 -30
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/bundled-skills.d.ts +4 -0
- package/dist/core/bundled-skills.d.ts.map +1 -0
- package/dist/core/bundled-skills.js +32 -0
- package/dist/core/bundled-skills.js.map +1 -0
- package/dist/core/cli-diagnostics.d.ts +6 -0
- package/dist/core/cli-diagnostics.d.ts.map +1 -0
- package/dist/core/cli-diagnostics.js +20 -0
- package/dist/core/cli-diagnostics.js.map +1 -0
- package/dist/core/context-budget-headroom.d.ts +12 -0
- package/dist/core/context-budget-headroom.d.ts.map +1 -1
- package/dist/core/context-budget-headroom.js +35 -0
- package/dist/core/context-budget-headroom.js.map +1 -1
- package/dist/core/context-budget-v2-input-validation.d.ts +4 -0
- package/dist/core/context-budget-v2-input-validation.d.ts.map +1 -0
- package/dist/core/context-budget-v2-input-validation.js +79 -0
- package/dist/core/context-budget-v2-input-validation.js.map +1 -0
- package/dist/core/context-budget-v2-observability.d.ts +15 -0
- package/dist/core/context-budget-v2-observability.d.ts.map +1 -0
- package/dist/core/context-budget-v2-observability.js +35 -0
- package/dist/core/context-budget-v2-observability.js.map +1 -0
- package/dist/core/context-budget-v2-planned-items.d.ts +5 -0
- package/dist/core/context-budget-v2-planned-items.d.ts.map +1 -0
- package/dist/core/context-budget-v2-planned-items.js +40 -0
- package/dist/core/context-budget-v2-planned-items.js.map +1 -0
- package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
- package/dist/core/context-budget-v2-planner.js +57 -38
- package/dist/core/context-budget-v2-planner.js.map +1 -1
- package/dist/core/context-budget-v2-selection.d.ts +19 -3
- package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
- package/dist/core/context-budget-v2-selection.js +35 -57
- package/dist/core/context-budget-v2-selection.js.map +1 -1
- package/dist/core/context-budget-v2-tiers.d.ts.map +1 -1
- package/dist/core/context-budget-v2-tiers.js +9 -42
- package/dist/core/context-budget-v2-tiers.js.map +1 -1
- package/dist/core/context-budget-v2-types.d.ts +8 -1
- package/dist/core/context-budget-v2-types.d.ts.map +1 -1
- package/dist/core/context-budget-v2-types.js.map +1 -1
- package/dist/core/extensions/bundled-virtual-modules.d.ts +15 -0
- package/dist/core/extensions/bundled-virtual-modules.d.ts.map +1 -0
- package/dist/core/extensions/bundled-virtual-modules.js +63 -0
- package/dist/core/extensions/bundled-virtual-modules.js.map +1 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +2 -42
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +6 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +10 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/loadout-runtime.d.ts +2 -8
- package/dist/core/loadout-runtime.d.ts.map +1 -1
- package/dist/core/loadout-runtime.js.map +1 -1
- package/dist/core/mcp/client.d.ts +27 -6
- package/dist/core/mcp/client.d.ts.map +1 -1
- package/dist/core/mcp/client.js +80 -20
- package/dist/core/mcp/client.js.map +1 -1
- package/dist/core/mcp/manager.d.ts +10 -1
- package/dist/core/mcp/manager.d.ts.map +1 -1
- package/dist/core/mcp/manager.js +68 -9
- package/dist/core/mcp/manager.js.map +1 -1
- package/dist/core/mcp/protocol.d.ts +9 -3
- package/dist/core/mcp/protocol.d.ts.map +1 -1
- package/dist/core/mcp/protocol.js +61 -15
- package/dist/core/mcp/protocol.js.map +1 -1
- package/dist/core/mcp/stdio-transport.d.ts +12 -1
- package/dist/core/mcp/stdio-transport.d.ts.map +1 -1
- package/dist/core/mcp/stdio-transport.js +30 -2
- package/dist/core/mcp/stdio-transport.js.map +1 -1
- package/dist/core/mcp-descriptor-injection.d.ts +26 -0
- package/dist/core/mcp-descriptor-injection.d.ts.map +1 -0
- package/dist/core/mcp-descriptor-injection.js +25 -0
- package/dist/core/mcp-descriptor-injection.js.map +1 -0
- package/dist/core/mcp-public-presets.d.ts +1 -3
- package/dist/core/mcp-public-presets.d.ts.map +1 -1
- package/dist/core/mcp-public-presets.js +3 -15
- package/dist/core/mcp-public-presets.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +24 -3
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/neo/catalog.d.ts +20 -0
- package/dist/core/neo/catalog.d.ts.map +1 -0
- package/dist/core/neo/catalog.js +50 -0
- package/dist/core/neo/catalog.js.map +1 -0
- package/dist/core/neo/setup.d.ts +3 -0
- package/dist/core/neo/setup.d.ts.map +1 -0
- package/dist/core/neo/setup.js +47 -0
- package/dist/core/neo/setup.js.map +1 -0
- package/dist/core/provider-default-models.d.ts +1 -0
- package/dist/core/provider-default-models.d.ts.map +1 -1
- package/dist/core/provider-default-models.js +1 -0
- package/dist/core/provider-default-models.js.map +1 -1
- package/dist/core/provider-display-names.d.ts.map +1 -1
- package/dist/core/provider-display-names.js +1 -0
- package/dist/core/provider-display-names.js.map +1 -1
- package/dist/core/provider-error-classification.d.ts +57 -0
- package/dist/core/provider-error-classification.d.ts.map +1 -0
- package/dist/core/provider-error-classification.js +103 -0
- package/dist/core/provider-error-classification.js.map +1 -0
- package/dist/core/provider-resilience.d.ts +10 -0
- package/dist/core/provider-resilience.d.ts.map +1 -1
- package/dist/core/provider-resilience.js +36 -3
- package/dist/core/provider-resilience.js.map +1 -1
- package/dist/core/provider-usage-commandcode.d.ts +9 -0
- package/dist/core/provider-usage-commandcode.d.ts.map +1 -0
- package/dist/core/provider-usage-commandcode.js +198 -0
- package/dist/core/provider-usage-commandcode.js.map +1 -0
- package/dist/core/provider-usage-devin.d.ts +5 -3
- package/dist/core/provider-usage-devin.d.ts.map +1 -1
- package/dist/core/provider-usage-devin.js +15 -5
- package/dist/core/provider-usage-devin.js.map +1 -1
- package/dist/core/provider-usage-types.d.ts +1 -1
- package/dist/core/provider-usage-types.d.ts.map +1 -1
- package/dist/core/provider-usage-types.js.map +1 -1
- package/dist/core/provider-usage.d.ts.map +1 -1
- package/dist/core/provider-usage.js +6 -0
- package/dist/core/provider-usage.js.map +1 -1
- package/dist/core/resource-admission.d.ts +36 -0
- package/dist/core/resource-admission.d.ts.map +1 -1
- package/dist/core/resource-admission.js +59 -0
- package/dist/core/resource-admission.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +2 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/run-usage-ledger.d.ts +47 -0
- package/dist/core/run-usage-ledger.d.ts.map +1 -0
- package/dist/core/run-usage-ledger.js +162 -0
- package/dist/core/run-usage-ledger.js.map +1 -0
- package/dist/core/run-usage-operation.d.ts +8 -0
- package/dist/core/run-usage-operation.d.ts.map +1 -0
- package/dist/core/run-usage-operation.js +12 -0
- package/dist/core/run-usage-operation.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +5 -6
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-failure-cause.d.ts.map +1 -1
- package/dist/core/session-failure-cause.js +14 -5
- package/dist/core/session-failure-cause.js.map +1 -1
- package/dist/core/session-prompt-lifecycle.d.ts +6 -0
- package/dist/core/session-prompt-lifecycle.d.ts.map +1 -1
- package/dist/core/session-prompt-lifecycle.js +47 -2
- package/dist/core/session-prompt-lifecycle.js.map +1 -1
- package/dist/core/session-termination.d.ts.map +1 -1
- package/dist/core/session-termination.js +4 -2
- package/dist/core/session-termination.js.map +1 -1
- package/dist/core/subagent-lane-authority.d.ts +28 -0
- package/dist/core/subagent-lane-authority.d.ts.map +1 -0
- package/dist/core/subagent-lane-authority.js +119 -0
- package/dist/core/subagent-lane-authority.js.map +1 -0
- package/dist/core/subagent-lane-contract.d.ts +114 -0
- package/dist/core/subagent-lane-contract.d.ts.map +1 -0
- package/dist/core/subagent-lane-contract.js +18 -0
- package/dist/core/subagent-lane-contract.js.map +1 -0
- package/dist/core/subagent-lane-launcher.d.ts +3 -6
- package/dist/core/subagent-lane-launcher.d.ts.map +1 -1
- package/dist/core/subagent-lane-launcher.js +53 -12
- package/dist/core/subagent-lane-launcher.js.map +1 -1
- package/dist/core/subagent-orchestration.d.ts +4 -17
- package/dist/core/subagent-orchestration.d.ts.map +1 -1
- package/dist/core/subagent-orchestration.js.map +1 -1
- package/dist/core/verified-run/broker.d.ts.map +1 -1
- package/dist/core/verified-run/broker.js +4 -1
- package/dist/core/verified-run/broker.js.map +1 -1
- package/dist/core/workload-permit-pool.d.ts +1 -1
- package/dist/core/workload-permit-pool.d.ts.map +1 -1
- package/dist/core/workload-permit-pool.js +3 -0
- package/dist/core/workload-permit-pool.js.map +1 -1
- package/dist/guardrails/strict-evidence-approval-adapter.d.ts +34 -0
- package/dist/guardrails/strict-evidence-approval-adapter.d.ts.map +1 -0
- package/dist/guardrails/strict-evidence-approval-adapter.js +70 -0
- package/dist/guardrails/strict-evidence-approval-adapter.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +11 -18
- package/dist/main.js.map +1 -1
- package/dist/modes/acp/acp-agent.d.ts +24 -0
- package/dist/modes/acp/acp-agent.d.ts.map +1 -0
- package/dist/modes/acp/acp-agent.js +133 -0
- package/dist/modes/acp/acp-agent.js.map +1 -0
- package/dist/modes/acp/acp-mode.d.ts +4 -0
- package/dist/modes/acp/acp-mode.d.ts.map +1 -0
- package/dist/modes/acp/acp-mode.js +34 -0
- package/dist/modes/acp/acp-mode.js.map +1 -0
- package/dist/modes/acp/acp-session.d.ts +4 -0
- package/dist/modes/acp/acp-session.d.ts.map +1 -0
- package/dist/modes/acp/acp-session.js +76 -0
- package/dist/modes/acp/acp-session.js.map +1 -0
- package/dist/modes/acp/acp-transport.d.ts +5 -0
- package/dist/modes/acp/acp-transport.d.ts.map +1 -0
- package/dist/modes/acp/acp-transport.js +90 -0
- package/dist/modes/acp/acp-transport.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/audit-revalidation-2026-09-17.md +77 -0
- package/docs/devin-harness.md +33 -4
- package/docs/ecraf-normalization.md +83 -0
- package/docs/model-catalog-refresh.md +51 -1
- package/docs/models.md +1 -1
- package/docs/neo.md +134 -0
- package/docs/providers.md +72 -6
- package/docs/run-usage-ledger.md +56 -0
- package/docs/runtime-algorithms.md +44 -0
- package/docs/skills.md +1 -1
- package/docs/usage.md +2 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/adaptive-agent-runtime.ts +14 -1
- package/examples/extensions/subagent/graph-result.ts +48 -0
- package/examples/extensions/subagent/index.ts +246 -111
- package/examples/extensions/subagent/managed-process-tree.ts +42 -0
- package/examples/extensions/subagent/managed-process.test.ts +24 -0
- package/examples/extensions/subagent/managed-process.ts +93 -112
- package/examples/extensions/subagent/subagent-runtime-types.ts +17 -1
- package/examples/extensions/subagent/subagent-stream.ts +161 -0
- package/examples/extensions/terminal-browser/README.md +54 -0
- package/examples/extensions/terminal-browser/bridge-protocol.ts +29 -0
- package/examples/extensions/terminal-browser/bridge.ts +219 -0
- package/examples/extensions/terminal-browser/browser-surface.ts +288 -0
- package/examples/extensions/terminal-browser/index.ts +215 -0
- package/examples/extensions/terminal-browser/placeholders.ts +49 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +18 -18
- package/package.json +8 -7
- package/resources/neo/skills/omk-browser/SKILL.md +32 -0
- package/resources/neo/skills/omk-code-review/SKILL.md +24 -0
- package/resources/neo/skills/omk-computeruse/SKILL.md +34 -0
- package/resources/neo/skills/omk-mcp-setup/SKILL.md +48 -0
- package/resources/neo/skills/omk-research/SKILL.md +24 -0
- package/resources/neo/skills/omk-site/SKILL.md +28 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* terminal-browser claude-bridge client for OMK.
|
|
3
|
+
*
|
|
4
|
+
* Launches `terminal-browser claude-bridge launch`, then talks to the bridge's
|
|
5
|
+
* loopback HTTP API: GET /state, POST /open, /size, /input, /inbox/take,
|
|
6
|
+
* /browser/close, /close.
|
|
7
|
+
*
|
|
8
|
+
* The bridge child process owns the pixel channel and the chromium lifecycle:
|
|
9
|
+
* it spawns `terminal-browser open` with PIXEL_EMBED/PIXEL_TTY, receives
|
|
10
|
+
* join/placed/title on its own socket, and writes kitty graphics frames to the
|
|
11
|
+
* TTY. This client only polls state and forwards size/input/agent text.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
15
|
+
import { promisify } from "node:util";
|
|
16
|
+
import { type BridgeState, isBridgeState, isLaunchReport, takenTexts } from "./bridge-protocol.ts";
|
|
17
|
+
|
|
18
|
+
const execFile = promisify(execFileCb);
|
|
19
|
+
|
|
20
|
+
const INSTALL_URL = "https://terminal-browser.sh";
|
|
21
|
+
const REQUIRED_CAPABILITIES = ["embedding"];
|
|
22
|
+
const POLL_MS = 250;
|
|
23
|
+
const IDLE_POLL_MS = 1200;
|
|
24
|
+
|
|
25
|
+
export type BridgeHooks = {
|
|
26
|
+
onAgentText: (text: string) => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class TerminalBrowserBridge {
|
|
30
|
+
private port: number | null = null;
|
|
31
|
+
private token: string | null = null;
|
|
32
|
+
private last: BridgeState | null = null;
|
|
33
|
+
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
34
|
+
private open = false;
|
|
35
|
+
private closing = false;
|
|
36
|
+
private readonly hooks: BridgeHooks;
|
|
37
|
+
|
|
38
|
+
constructor(hooks: BridgeHooks) {
|
|
39
|
+
this.hooks = hooks;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getState(): BridgeState | null {
|
|
43
|
+
return this.last;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
isRunning(): boolean {
|
|
47
|
+
return this.port !== null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private terminalBrowserCommand(): string[] {
|
|
51
|
+
const fromEnv = process.env.TERMINAL_BROWSER_COMMAND;
|
|
52
|
+
if (fromEnv) return fromEnv.split(/\s+/);
|
|
53
|
+
return ["terminal-browser"];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private async checkCapabilities(command: string[]): Promise<{ ok: true } | { ok: false; installed: boolean }> {
|
|
57
|
+
try {
|
|
58
|
+
const { stdout } = await execFile(command[0]!, [...command.slice(1), "capabilities"], { timeout: 10_000 });
|
|
59
|
+
const line = stdout.split("\n").find((text) => text.startsWith("{"));
|
|
60
|
+
const parsed = line ? (JSON.parse(line) as { capabilities?: unknown }) : null;
|
|
61
|
+
const capabilities = Array.isArray(parsed?.capabilities) ? (parsed!.capabilities as string[]) : [];
|
|
62
|
+
const ok = REQUIRED_CAPABILITIES.every((need) => capabilities.includes(need));
|
|
63
|
+
return ok ? { ok: true } : { ok: false, installed: true };
|
|
64
|
+
} catch {
|
|
65
|
+
return { ok: false, installed: false };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Loopback-only surface: the bridge is a child process we launched. The path
|
|
70
|
+
// is constrained to the fixed route table below and the port to a valid TCP
|
|
71
|
+
// port, so a malformed report cannot steer this client to an arbitrary origin.
|
|
72
|
+
private static readonly ROUTES = new Set([
|
|
73
|
+
"/state",
|
|
74
|
+
"/open",
|
|
75
|
+
"/size",
|
|
76
|
+
"/input",
|
|
77
|
+
"/inbox/take",
|
|
78
|
+
"/browser/close",
|
|
79
|
+
"/close",
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
private bridgeUrl(pathName: string): string | null {
|
|
83
|
+
if (!TerminalBrowserBridge.ROUTES.has(pathName)) return null;
|
|
84
|
+
const port = this.port;
|
|
85
|
+
if (port === null || !Number.isInteger(port) || port < 1 || port > 65535) return null;
|
|
86
|
+
return `http://127.0.0.1:${port}${pathName}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private authHeaders(): Record<string, string> {
|
|
90
|
+
return { authorization: `Bearer ${this.token}` };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async post(pathName: string, body: unknown): Promise<unknown> {
|
|
94
|
+
const url = this.bridgeUrl(pathName);
|
|
95
|
+
if (url === null) return null;
|
|
96
|
+
try {
|
|
97
|
+
const response = await fetch(url, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: { "content-type": "application/json", ...this.authHeaders() },
|
|
100
|
+
body: JSON.stringify(body),
|
|
101
|
+
});
|
|
102
|
+
return response.ok ? JSON.parse((await response.text()) || "{}") : null;
|
|
103
|
+
} catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private async fetchState(): Promise<BridgeState | null> {
|
|
109
|
+
const url = this.bridgeUrl("/state");
|
|
110
|
+
if (url === null) return null;
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetch(url, { headers: this.authHeaders() });
|
|
113
|
+
const parsed: unknown = response.ok ? JSON.parse(await response.text()) : null;
|
|
114
|
+
return isBridgeState(parsed) ? parsed : null;
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async start(): Promise<{ ok: true } | { ok: false; error: string }> {
|
|
121
|
+
if (this.port !== null) return { ok: true };
|
|
122
|
+
this.closing = false;
|
|
123
|
+
const command = this.terminalBrowserCommand();
|
|
124
|
+
const check = await this.checkCapabilities(command);
|
|
125
|
+
if (!check.ok) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
error: check.installed
|
|
129
|
+
? "Newer terminal-browser required — run `terminal-browser upgrade`"
|
|
130
|
+
: `terminal-browser is not installed — ${INSTALL_URL}`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let report: unknown = null;
|
|
135
|
+
try {
|
|
136
|
+
const { stdout, stderr } = await execFile(command[0]!, [...command.slice(1), "claude-bridge", "launch"], {
|
|
137
|
+
timeout: 20_000,
|
|
138
|
+
});
|
|
139
|
+
const line = stdout.split("\n").find((text) => text.startsWith("{"));
|
|
140
|
+
report = line ? JSON.parse(line) : { error: stderr.trim() || "no report", code: "start" };
|
|
141
|
+
} catch (error) {
|
|
142
|
+
report = { error: String(error), code: "start" };
|
|
143
|
+
}
|
|
144
|
+
if (!isLaunchReport(report) || !("port" in report)) {
|
|
145
|
+
const detail = isLaunchReport(report) && "error" in report ? report.error : "terminal-browser could not start";
|
|
146
|
+
return { ok: false, error: detail };
|
|
147
|
+
}
|
|
148
|
+
this.port = report.port;
|
|
149
|
+
this.token = report.token;
|
|
150
|
+
this.startPolling();
|
|
151
|
+
return { ok: true };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private startPolling(): void {
|
|
155
|
+
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
156
|
+
this.pollTimer = setInterval(
|
|
157
|
+
() => {
|
|
158
|
+
void this.poll();
|
|
159
|
+
},
|
|
160
|
+
this.open ? POLL_MS : IDLE_POLL_MS,
|
|
161
|
+
);
|
|
162
|
+
this.pollTimer.unref?.();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private async poll(): Promise<void> {
|
|
166
|
+
const fresh = await this.fetchState();
|
|
167
|
+
if (!fresh) return;
|
|
168
|
+
const hadInbox = fresh.inbox > 0;
|
|
169
|
+
this.last = { ...fresh, placed: fresh.placed ?? this.last?.placed ?? null };
|
|
170
|
+
if (hadInbox) await this.deliverAgentText();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private async deliverAgentText(): Promise<void> {
|
|
174
|
+
const texts = takenTexts(await this.post("/inbox/take", {}));
|
|
175
|
+
for (const text of texts) {
|
|
176
|
+
const cleaned = text
|
|
177
|
+
.replace(/[-\u001f\u007f-]/g, " ")
|
|
178
|
+
.replace(/\s+/g, " ")
|
|
179
|
+
.trim();
|
|
180
|
+
if (cleaned) this.hooks.onAgentText(`> ${cleaned}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async openUrl(url: string): Promise<void> {
|
|
185
|
+
this.open = true;
|
|
186
|
+
await this.post("/open", { url });
|
|
187
|
+
this.startPolling();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async setSize(cols: number, rows: number): Promise<void> {
|
|
191
|
+
await this.post("/size", { cols, rows });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async sendInput(events: unknown[]): Promise<void> {
|
|
195
|
+
if (events.length === 0) return;
|
|
196
|
+
await this.post("/input", { events });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async hide(): Promise<void> {
|
|
200
|
+
this.open = false;
|
|
201
|
+
await this.post("/browser/close", {});
|
|
202
|
+
this.startPolling();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async shutdown(): Promise<void> {
|
|
206
|
+
if (this.closing) return;
|
|
207
|
+
this.closing = true;
|
|
208
|
+
if (this.pollTimer) {
|
|
209
|
+
clearInterval(this.pollTimer);
|
|
210
|
+
this.pollTimer = null;
|
|
211
|
+
}
|
|
212
|
+
if (this.port !== null) {
|
|
213
|
+
await this.post("/close", {});
|
|
214
|
+
}
|
|
215
|
+
this.port = null;
|
|
216
|
+
this.token = null;
|
|
217
|
+
this.open = false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overlay surface for terminal-browser inside OMK.
|
|
3
|
+
*
|
|
4
|
+
* Renders kitty unicode placeholders so the terminal composites the real
|
|
5
|
+
* browser pixels at this region, and translates OMK input into bridge input
|
|
6
|
+
* events. The bridge scales cell coordinates to pixels itself — mouse x/y here
|
|
7
|
+
* stay in cell units.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { type Component, isKeyRelease, matchesKey, type TUI } from "omk-tui";
|
|
11
|
+
import type { Theme } from "open-multi-agent-kit";
|
|
12
|
+
import type { BridgeState } from "./bridge-protocol.ts";
|
|
13
|
+
import { imageColor, MAX_PLACEHOLDER_CELLS, placeholderRow } from "./placeholders.ts";
|
|
14
|
+
|
|
15
|
+
export type Mods = { shift: boolean; alt: boolean; ctrl: boolean; super: boolean };
|
|
16
|
+
export type SurfaceInputEvent =
|
|
17
|
+
| { type: "mouse"; kind: string; button?: string; x: number; y: number; mods: Mods }
|
|
18
|
+
| { type: "key"; key: string; text?: string; mods: Mods }
|
|
19
|
+
| { type: "paste"; text: string }
|
|
20
|
+
| { type: "focus"; focused: boolean };
|
|
21
|
+
|
|
22
|
+
type SurfaceHooks = {
|
|
23
|
+
getState: () => BridgeState | null;
|
|
24
|
+
post: (path: string, body: unknown) => Promise<unknown>;
|
|
25
|
+
onSized: (cols: number, rows: number) => void;
|
|
26
|
+
onClose: () => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** OMK key name → bridge key name. Names the bridge already accepts are omitted. */
|
|
30
|
+
const KEY_MAP: Record<string, string> = {
|
|
31
|
+
enter: "enter",
|
|
32
|
+
return: "enter",
|
|
33
|
+
backspace: "backspace",
|
|
34
|
+
delete: "delete",
|
|
35
|
+
tab: "tab",
|
|
36
|
+
up: "up",
|
|
37
|
+
down: "down",
|
|
38
|
+
left: "left",
|
|
39
|
+
right: "right",
|
|
40
|
+
home: "home",
|
|
41
|
+
end: "end",
|
|
42
|
+
pageup: "pageup",
|
|
43
|
+
pagedown: "pagedown",
|
|
44
|
+
insert: "insert",
|
|
45
|
+
escape: "escape",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const MODIFIER_PARTS = new Set(["shift", "ctrl", "alt", "super", "meta", "cmd", "win"]);
|
|
49
|
+
|
|
50
|
+
function modsFromParts(parts: string[]): Mods {
|
|
51
|
+
return {
|
|
52
|
+
shift: parts.includes("shift"),
|
|
53
|
+
alt: parts.includes("alt"),
|
|
54
|
+
ctrl: parts.includes("ctrl"),
|
|
55
|
+
super: parts.includes("super") || parts.includes("meta") || parts.includes("cmd") || parts.includes("win"),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseSgrMouse(data: string): { code: number; x: number; y: number; press: boolean } | null {
|
|
60
|
+
const match = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/.exec(data);
|
|
61
|
+
if (!match) return null;
|
|
62
|
+
return { code: Number(match[1]), x: Number(match[2]), y: Number(match[3]), press: match[4] === "M" };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Legacy F-key tilde sequences and kitty CSI-u functional codepoints. */
|
|
66
|
+
function functionKeyFromSequence(data: string): string | null {
|
|
67
|
+
const tilde = /^\x1b\[(\d+)~$/.exec(data);
|
|
68
|
+
if (tilde) {
|
|
69
|
+
const map: Record<number, string> = {
|
|
70
|
+
11: "f1",
|
|
71
|
+
12: "f2",
|
|
72
|
+
13: "f3",
|
|
73
|
+
14: "f4",
|
|
74
|
+
15: "f5",
|
|
75
|
+
17: "f6",
|
|
76
|
+
18: "f7",
|
|
77
|
+
19: "f8",
|
|
78
|
+
20: "f9",
|
|
79
|
+
21: "f10",
|
|
80
|
+
23: "f11",
|
|
81
|
+
24: "f12",
|
|
82
|
+
};
|
|
83
|
+
return map[Number(tilde[1])] ?? null;
|
|
84
|
+
}
|
|
85
|
+
const csiU = /^\x1b\[(\d+)(?::\d*)?(?::\d+)?(?:;\d+)?(?::\d+)?u$/.exec(data);
|
|
86
|
+
if (csiU) {
|
|
87
|
+
const cp = Number(csiU[1]);
|
|
88
|
+
if (cp >= 57376 && cp <= 57398) return `f${cp - 57375}`;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export class BrowserSurfaceComponent implements Component {
|
|
94
|
+
private cols = 0;
|
|
95
|
+
private rows = 0;
|
|
96
|
+
private disposed = false;
|
|
97
|
+
private lastSize: { cols: number; rows: number } | null = null;
|
|
98
|
+
private readonly interval: ReturnType<typeof setInterval>;
|
|
99
|
+
private lastRendered: string[] | null = null;
|
|
100
|
+
private readonly tui: TUI;
|
|
101
|
+
private readonly hooks: SurfaceHooks;
|
|
102
|
+
|
|
103
|
+
constructor(tui: TUI, _theme: Theme, hooks: SurfaceHooks) {
|
|
104
|
+
this.tui = tui;
|
|
105
|
+
this.hooks = hooks;
|
|
106
|
+
// SGR mouse: button events + drag move (1002), any move (1003), SGR encoding (1006).
|
|
107
|
+
this.tui.terminal.write("\x1b[?1003h\x1b[?1002h\x1b[?1006h");
|
|
108
|
+
this.interval = setInterval(() => {
|
|
109
|
+
this.tui.requestRender();
|
|
110
|
+
}, 250);
|
|
111
|
+
this.interval.unref?.();
|
|
112
|
+
queueMicrotask(() => {
|
|
113
|
+
void this.hooks.post("/input", { events: [{ type: "focus", focused: true }] });
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private post(events: SurfaceInputEvent[]): void {
|
|
118
|
+
if (events.length === 0) return;
|
|
119
|
+
void this.hooks.post("/input", { events });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private keyEventFromInput(data: string): SurfaceInputEvent | SurfaceInputEvent[] | null {
|
|
123
|
+
const paste = /^\x1b\[200~([\s\S]*?)\x1b\[201~$/.exec(data);
|
|
124
|
+
if (paste) {
|
|
125
|
+
return { type: "paste", text: paste[1] ?? "" };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (isKeyRelease(data)) return null; // bridge accepts press events only
|
|
129
|
+
|
|
130
|
+
for (const name of Object.keys(KEY_MAP)) {
|
|
131
|
+
const bridgeKey = KEY_MAP[name]!;
|
|
132
|
+
const candidates = [name, `shift+${name}`, `ctrl+${name}`, `alt+${name}`] as const;
|
|
133
|
+
for (const candidate of candidates) {
|
|
134
|
+
if (matchesKey(data, candidate as Parameters<typeof matchesKey>[1])) {
|
|
135
|
+
const mods = modsFromParts(candidate.split("+").filter((p) => MODIFIER_PARTS.has(p)));
|
|
136
|
+
return { type: "key", key: bridgeKey, mods };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const fn = functionKeyFromSequence(data);
|
|
142
|
+
if (fn) {
|
|
143
|
+
return { type: "key", key: fn, mods: { shift: false, alt: false, ctrl: false, super: false } };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (data === " ") {
|
|
147
|
+
return { type: "key", key: " ", text: " ", mods: { shift: false, alt: false, ctrl: false, super: false } };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (data.length === 1) {
|
|
151
|
+
const code = data.charCodeAt(0);
|
|
152
|
+
if (code >= 1 && code <= 26) {
|
|
153
|
+
return {
|
|
154
|
+
type: "key",
|
|
155
|
+
key: String.fromCharCode(code + 96),
|
|
156
|
+
mods: { shift: false, alt: false, ctrl: true, super: false },
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (data === "\x7f") {
|
|
160
|
+
return { type: "key", key: "backspace", mods: { shift: false, alt: false, ctrl: false, super: false } };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!data.startsWith("\x1b")) {
|
|
165
|
+
const chars = [...data];
|
|
166
|
+
if (chars.length === 1) {
|
|
167
|
+
const upper = data !== data.toLowerCase() && data === data.toUpperCase();
|
|
168
|
+
return {
|
|
169
|
+
type: "key",
|
|
170
|
+
key: data.toLowerCase(),
|
|
171
|
+
text: data,
|
|
172
|
+
mods: { shift: upper, alt: false, ctrl: false, super: false },
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return { type: "paste", text: data };
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private mouseEventFromInput(data: string): SurfaceInputEvent | null {
|
|
181
|
+
const mouse = parseSgrMouse(data);
|
|
182
|
+
if (!mouse) return null;
|
|
183
|
+
const { code, x, y, press } = mouse;
|
|
184
|
+
const originCol = Math.max(0, this.tui.terminal.columns - this.cols - 1);
|
|
185
|
+
const cellX = x - 1 - originCol;
|
|
186
|
+
const cellY = y - 2; // first rendered line is the status header
|
|
187
|
+
if (cellX < 0 || cellY < 0 || cellX >= this.cols || cellY >= this.rows) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const mods: Mods = {
|
|
191
|
+
shift: Boolean(code & 4),
|
|
192
|
+
alt: Boolean(code & 8),
|
|
193
|
+
ctrl: Boolean(code & 16),
|
|
194
|
+
super: false,
|
|
195
|
+
};
|
|
196
|
+
let kind: string;
|
|
197
|
+
let button = "none";
|
|
198
|
+
if (code & 64) {
|
|
199
|
+
kind = (code & 1) === 0 ? "scrollup" : "scrolldown";
|
|
200
|
+
} else if (code & 32) {
|
|
201
|
+
kind = "move";
|
|
202
|
+
} else {
|
|
203
|
+
kind = press ? "down" : "up";
|
|
204
|
+
button = ["left", "middle", "right", "none"][code & 3] ?? "none";
|
|
205
|
+
}
|
|
206
|
+
return { type: "mouse", kind, button, x: cellX, y: cellY, mods };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
handleInput(data: string): void {
|
|
210
|
+
if (matchesKey(data, "ctrl+q")) {
|
|
211
|
+
this.close();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const mouse = this.mouseEventFromInput(data);
|
|
215
|
+
if (mouse) {
|
|
216
|
+
this.post([mouse]);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const key = this.keyEventFromInput(data);
|
|
220
|
+
if (key) {
|
|
221
|
+
this.post(Array.isArray(key) ? key : [key]);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Idempotent close; also the path used when OMK or the command asks to close. */
|
|
226
|
+
close(): void {
|
|
227
|
+
if (this.disposed) return;
|
|
228
|
+
this.dispose();
|
|
229
|
+
this.hooks.onClose();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
render(width: number): string[] {
|
|
233
|
+
const state = this.hooks.getState();
|
|
234
|
+
const cols = Math.max(1, Math.min(width, MAX_PLACEHOLDER_CELLS));
|
|
235
|
+
const rows = Math.max(4, Math.min(this.tui.terminal.rows - 4, MAX_PLACEHOLDER_CELLS));
|
|
236
|
+
this.cols = cols;
|
|
237
|
+
this.rows = rows;
|
|
238
|
+
|
|
239
|
+
if (!this.lastSize || this.lastSize.cols !== cols || this.lastSize.rows !== rows) {
|
|
240
|
+
this.lastSize = { cols, rows };
|
|
241
|
+
queueMicrotask(() => {
|
|
242
|
+
void this.hooks.post("/size", { cols, rows });
|
|
243
|
+
this.hooks.onSized(cols, rows);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const title = state?.title ? ` — ${state.title.slice(0, Math.max(0, cols - 30))}` : "";
|
|
248
|
+
const status =
|
|
249
|
+
state?.alive === false
|
|
250
|
+
? ` not running${state?.error ? `: ${state.error}` : ""}`
|
|
251
|
+
: state?.url
|
|
252
|
+
? ` ${state.url}`
|
|
253
|
+
: "";
|
|
254
|
+
const header = `\x1b[2m browser${title}${status} ctrl+q close\x1b[0m`;
|
|
255
|
+
const lines: string[] = [header];
|
|
256
|
+
|
|
257
|
+
if (state?.placed) {
|
|
258
|
+
const color = imageColor(state.placed.imageId);
|
|
259
|
+
const drawCols = Math.min(state.placed.cols, cols);
|
|
260
|
+
const drawRows = Math.min(state.placed.rows, rows);
|
|
261
|
+
for (let row = 0; row < drawRows; row++) {
|
|
262
|
+
lines.push(`${color}${placeholderRow(row, drawCols)}\x1b[0m`);
|
|
263
|
+
}
|
|
264
|
+
for (let row = drawRows; row < rows; row++) lines.push("");
|
|
265
|
+
} else {
|
|
266
|
+
for (let row = 0; row < rows; row++) lines.push("");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (state?.placed) {
|
|
270
|
+
this.lastRendered = lines;
|
|
271
|
+
} else if (this.lastRendered) {
|
|
272
|
+
return this.lastRendered;
|
|
273
|
+
}
|
|
274
|
+
return lines;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
invalidate(): void {
|
|
278
|
+
this.lastRendered = null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
dispose(): void {
|
|
282
|
+
if (this.disposed) return;
|
|
283
|
+
this.disposed = true;
|
|
284
|
+
clearInterval(this.interval);
|
|
285
|
+
this.tui.terminal.write("\x1b[?1003l\x1b[?1002l\x1b[?1006l");
|
|
286
|
+
this.post([{ type: "focus", focused: false }]);
|
|
287
|
+
}
|
|
288
|
+
}
|