the-campfire 0.1.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/bin/cli.ts +182 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/assets/MermaidDiagram-By1J3Whj.js +2 -0
- package/dist/assets/_basePickBy-Dq6gnD-p.js +1 -0
- package/dist/assets/_baseUniq-SpgN6cGc.js +1 -0
- package/dist/assets/arc-D9SEA8dX.js +1 -0
- package/dist/assets/architectureDiagram-VXUJARFQ-CoTcwLeR.js +36 -0
- package/dist/assets/blockDiagram-VD42YOAC-Dvc63voV.js +122 -0
- package/dist/assets/c4Diagram-YG6GDRKO-tuDcG4_4.js +10 -0
- package/dist/assets/channel-CxhcHn7S.js +1 -0
- package/dist/assets/chunk-4BX2VUAB-x1hChYu9.js +1 -0
- package/dist/assets/chunk-55IACEB6-BX6HWnWP.js +1 -0
- package/dist/assets/chunk-B4BG7PRW-B4IlF03G.js +165 -0
- package/dist/assets/chunk-DI55MBZ5-TpYb5rLg.js +220 -0
- package/dist/assets/chunk-FMBD7UC4-DZX4LUPx.js +15 -0
- package/dist/assets/chunk-QN33PNHL-CuQs0NDY.js +1 -0
- package/dist/assets/chunk-QZHKN3VN-CygWwVx8.js +1 -0
- package/dist/assets/chunk-TZMSLE5B-CtI-IdKV.js +1 -0
- package/dist/assets/classDiagram-2ON5EDUG-BM0LptJF.js +1 -0
- package/dist/assets/classDiagram-v2-WZHVMYZB-BM0LptJF.js +1 -0
- package/dist/assets/clone-C6kt1rE7.js +1 -0
- package/dist/assets/cose-bilkent-S5V4N54A-PdySs991.js +1 -0
- package/dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
- package/dist/assets/dagre-6UL2VRFP-J0SxEiEu.js +4 -0
- package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
- package/dist/assets/diagram-PSM6KHXK-CW0HLXaH.js +24 -0
- package/dist/assets/diagram-QEK2KX5R-DADHF-k4.js +43 -0
- package/dist/assets/diagram-S2PKOQOG-9S0-PC3S.js +24 -0
- package/dist/assets/erDiagram-Q2GNP2WA-BEHAiY_q.js +60 -0
- package/dist/assets/flowDiagram-NV44I4VS-Cu6n9Xnd.js +162 -0
- package/dist/assets/ganttDiagram-JELNMOA3-Cr_BHryu.js +267 -0
- package/dist/assets/gitGraphDiagram-V2S2FVAM-D5pT35v5.js +65 -0
- package/dist/assets/graph-DKRbrxcs.js +1 -0
- package/dist/assets/index-Byjg9zgI.css +32 -0
- package/dist/assets/index-Cwj3m0hT.js +284 -0
- package/dist/assets/infoDiagram-HS3SLOUP-BaJlrgKV.js +2 -0
- package/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dist/assets/journeyDiagram-XKPGCS4Q-CXC4-DJv.js +139 -0
- package/dist/assets/kanban-definition-3W4ZIXB7-BtHBu9cx.js +89 -0
- package/dist/assets/katex-BlHpptmG.js +261 -0
- package/dist/assets/layout-DnxSwLDJ.js +1 -0
- package/dist/assets/linear-CWbbIT8l.js +1 -0
- package/dist/assets/mermaid.core-DXn1cFAK.js +256 -0
- package/dist/assets/mindmap-definition-VGOIOE7T-BR8br1hs.js +68 -0
- package/dist/assets/ordinal-BENe2yWM.js +1 -0
- package/dist/assets/pieDiagram-ADFJNKIX-B7RmXV7I.js +30 -0
- package/dist/assets/quadrantDiagram-AYHSOK5B-DK0FhyV_.js +7 -0
- package/dist/assets/requirementDiagram-UZGBJVZJ-3zAGxSpL.js +64 -0
- package/dist/assets/sankeyDiagram-TZEHDZUN-DZppKtjc.js +10 -0
- package/dist/assets/sequenceDiagram-WL72ISMW-D1x3r3wV.js +145 -0
- package/dist/assets/stateDiagram-FKZM4ZOC-2ByD08Vq.js +1 -0
- package/dist/assets/stateDiagram-v2-4FDKWEC3-Vm1cYjgV.js +1 -0
- package/dist/assets/timeline-definition-IT6M3QCI-l5S35biR.js +61 -0
- package/dist/assets/treemap-GDKQZRPO-DIPadPiF.js +162 -0
- package/dist/assets/xychartDiagram-PRI3JC2R-9GUvwS2b.js +7 -0
- package/dist/favicon.svg +3 -0
- package/dist/fonts/Geist-Variable.woff2 +0 -0
- package/dist/fonts/GeistMono-Variable.woff2 +0 -0
- package/dist/icon-192.png +0 -0
- package/dist/icon-512.png +0 -0
- package/dist/index.html +33 -0
- package/dist/logo-codex.svg +14 -0
- package/dist/logo.svg +3 -0
- package/dist/manifest.json +26 -0
- package/dist/og-image.png +0 -0
- package/dist/sw.js +130 -0
- package/package.json +92 -0
- package/server/ADAPTERS.md +121 -0
- package/server/adapter-registry-types.ts +28 -0
- package/server/adapter-registry.ts +293 -0
- package/server/adapter-types.ts +51 -0
- package/server/agent-executor.ts +249 -0
- package/server/agent-mcp-bridge.ts +148 -0
- package/server/agent-mcp-stdio.ts +171 -0
- package/server/agent-mcp-tools.ts +76 -0
- package/server/agent-store.ts +183 -0
- package/server/agent-types.ts +79 -0
- package/server/aider-adapter.ts +525 -0
- package/server/auth.ts +153 -0
- package/server/auto-namer.ts +132 -0
- package/server/capability-discovery.ts +431 -0
- package/server/claude-container-auth.ts +40 -0
- package/server/claude-stdio-adapter.ts +452 -0
- package/server/clawhub-export.ts +218 -0
- package/server/cli-launcher.ts +1613 -0
- package/server/codex-adapter.ts +1987 -0
- package/server/codex-container-auth.ts +40 -0
- package/server/codex-home.ts +26 -0
- package/server/collective-intelligence.ts +401 -0
- package/server/commands-discovery.ts +160 -0
- package/server/constants.ts +5 -0
- package/server/container-manager.ts +328 -0
- package/server/cron-scheduler.ts +243 -0
- package/server/cron-store.ts +149 -0
- package/server/cron-types.ts +63 -0
- package/server/deliberation-engine.ts +323 -0
- package/server/embedding.ts +89 -0
- package/server/env-manager.ts +146 -0
- package/server/environment-detector.ts +106 -0
- package/server/environment-rules.ts +106 -0
- package/server/gallery-store.ts +216 -0
- package/server/gallery-types.ts +52 -0
- package/server/gallery-votes.ts +101 -0
- package/server/git-utils.ts +384 -0
- package/server/github-pr.ts +379 -0
- package/server/goose-adapter.ts +929 -0
- package/server/image-pull-manager.ts +144 -0
- package/server/index.ts +363 -0
- package/server/linear-project-manager.ts +78 -0
- package/server/moltbook-client.ts +160 -0
- package/server/openclaw-adapter.ts +916 -0
- package/server/openclaw-channel/channel.ts +188 -0
- package/server/openclaw-channel/index.ts +68 -0
- package/server/openclaw-channel/package.json +19 -0
- package/server/openclaw-channel/types.ts +93 -0
- package/server/opencode-adapter.ts +951 -0
- package/server/openhands-adapter.ts +775 -0
- package/server/orchestrator-executor.ts +184 -0
- package/server/orchestrator-store.ts +106 -0
- package/server/orchestrator-types.ts +86 -0
- package/server/path-resolver.ts +168 -0
- package/server/pr-poller.ts +162 -0
- package/server/proactive-keepalive.ts +121 -0
- package/server/prompt-manager.ts +103 -0
- package/server/protocol-monitor.ts +197 -0
- package/server/race-controller.ts +295 -0
- package/server/race-store.ts +82 -0
- package/server/recorder.ts +359 -0
- package/server/recording-hub/compat-validator.ts +122 -0
- package/server/recording-hub/diagnostics.ts +289 -0
- package/server/recording-hub/hub-routes.ts +146 -0
- package/server/recording-hub/hub-store.ts +224 -0
- package/server/replay.ts +78 -0
- package/server/routes/adapter-routes.ts +31 -0
- package/server/routes/agent-mcp-routes.ts +35 -0
- package/server/routes/agent-routes.ts +185 -0
- package/server/routes/auth-routes.ts +101 -0
- package/server/routes/ci-routes.ts +165 -0
- package/server/routes/commands-routes.ts +94 -0
- package/server/routes/cron-routes.ts +100 -0
- package/server/routes/env-routes.ts +50 -0
- package/server/routes/folder-routes.ts +56 -0
- package/server/routes/fs-routes.ts +403 -0
- package/server/routes/gallery-routes.ts +410 -0
- package/server/routes/git-routes.ts +108 -0
- package/server/routes/index.ts +62 -0
- package/server/routes/linear-routes.ts +254 -0
- package/server/routes/monitor-routes.ts +11 -0
- package/server/routes/orchestrator-routes.ts +210 -0
- package/server/routes/prompt-routes.ts +47 -0
- package/server/routes/race-routes.ts +103 -0
- package/server/routes/recording-routes.ts +101 -0
- package/server/routes/route-deps.ts +32 -0
- package/server/routes/session-routes.ts +560 -0
- package/server/routes/settings-routes.ts +108 -0
- package/server/routes/skills-routes.ts +52 -0
- package/server/routes/system-routes.ts +243 -0
- package/server/routes/webhook-routes.ts +135 -0
- package/server/routes.ts +44 -0
- package/server/security-middleware.ts +100 -0
- package/server/semantic-memory.ts +389 -0
- package/server/service.ts +733 -0
- package/server/session-folders.ts +107 -0
- package/server/session-git-info.ts +89 -0
- package/server/session-linear-issues.ts +94 -0
- package/server/session-names.ts +67 -0
- package/server/session-store.ts +169 -0
- package/server/session-types.ts +415 -0
- package/server/settings-manager.ts +120 -0
- package/server/shared-context.ts +401 -0
- package/server/skills-manager.ts +250 -0
- package/server/sub-session-manager.ts +263 -0
- package/server/terminal-manager.ts +185 -0
- package/server/update-checker.ts +114 -0
- package/server/usage-limits.ts +192 -0
- package/server/webhook-manager.ts +228 -0
- package/server/webhook-store.ts +168 -0
- package/server/webhook-types.ts +66 -0
- package/server/worktree-tracker.ts +84 -0
- package/server/ws-bridge.ts +2322 -0
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenHands ACP (Agent Client Protocol) Adapter
|
|
3
|
+
*
|
|
4
|
+
* OpenHands supports the same ACP protocol as Goose (`openhands acp`),
|
|
5
|
+
* using JSON-RPC 2.0 over stdio. This adapter closely mirrors GooseAdapter
|
|
6
|
+
* with OpenHands-specific tool name mappings and session handling.
|
|
7
|
+
*
|
|
8
|
+
* Protocol:
|
|
9
|
+
* - JSON-RPC 2.0 over stdio (newline-delimited)
|
|
10
|
+
* - Methods: initialize, session/new, session/prompt, session/cancel
|
|
11
|
+
* - Notifications: session/notification with SessionUpdate payloads
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { randomUUID } from "node:crypto";
|
|
15
|
+
import type { Subprocess } from "bun";
|
|
16
|
+
import type {
|
|
17
|
+
BrowserIncomingMessage,
|
|
18
|
+
BrowserOutgoingMessage,
|
|
19
|
+
SessionState,
|
|
20
|
+
PermissionRequest,
|
|
21
|
+
CLIResultMessage,
|
|
22
|
+
} from "./session-types.js";
|
|
23
|
+
import type { RecorderManager } from "./recorder.js";
|
|
24
|
+
import type { AgentAdapter, AdapterSessionMeta } from "./adapter-types.js";
|
|
25
|
+
|
|
26
|
+
// ─── JSON-RPC Types ──────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
interface JsonRpcRequest {
|
|
29
|
+
method: string;
|
|
30
|
+
id: number;
|
|
31
|
+
params: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface JsonRpcNotification {
|
|
35
|
+
method: string;
|
|
36
|
+
params: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface JsonRpcResponse {
|
|
40
|
+
id: number;
|
|
41
|
+
result?: unknown;
|
|
42
|
+
error?: { code: number; message: string; data?: unknown };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type JsonRpcMessage = JsonRpcRequest | JsonRpcNotification | JsonRpcResponse;
|
|
46
|
+
|
|
47
|
+
// ─── Adapter Options ─────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
export interface OpenHandsAdapterOptions {
|
|
50
|
+
model?: string;
|
|
51
|
+
cwd?: string;
|
|
52
|
+
permissionMode?: string;
|
|
53
|
+
recorder?: RecorderManager;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── JSON-RPC Transport (same as GooseAdapter) ──────────────────────────────
|
|
57
|
+
|
|
58
|
+
class JsonRpcTransport {
|
|
59
|
+
private nextId = 1;
|
|
60
|
+
private pending = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
|
|
61
|
+
private notificationHandler: ((method: string, params: Record<string, unknown>) => void) | null = null;
|
|
62
|
+
private requestHandler: ((method: string, id: number, params: Record<string, unknown>) => void) | null = null;
|
|
63
|
+
private rawInCb: ((line: string) => void) | null = null;
|
|
64
|
+
private rawOutCb: ((data: string) => void) | null = null;
|
|
65
|
+
private writer: WritableStreamDefaultWriter<Uint8Array>;
|
|
66
|
+
private connected = true;
|
|
67
|
+
private buffer = "";
|
|
68
|
+
|
|
69
|
+
constructor(
|
|
70
|
+
stdin: WritableStream<Uint8Array> | { write(data: Uint8Array): number },
|
|
71
|
+
stdout: ReadableStream<Uint8Array>,
|
|
72
|
+
) {
|
|
73
|
+
let writable: WritableStream<Uint8Array>;
|
|
74
|
+
if ("write" in stdin && typeof stdin.write === "function") {
|
|
75
|
+
writable = new WritableStream({
|
|
76
|
+
write(chunk) {
|
|
77
|
+
(stdin as { write(data: Uint8Array): number }).write(chunk);
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
} else {
|
|
81
|
+
writable = stdin as WritableStream<Uint8Array>;
|
|
82
|
+
}
|
|
83
|
+
this.writer = writable.getWriter();
|
|
84
|
+
this.readStdout(stdout);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private async readStdout(stdout: ReadableStream<Uint8Array>): Promise<void> {
|
|
88
|
+
const reader = stdout.getReader();
|
|
89
|
+
const decoder = new TextDecoder();
|
|
90
|
+
try {
|
|
91
|
+
while (true) {
|
|
92
|
+
const { done, value } = await reader.read();
|
|
93
|
+
if (done) break;
|
|
94
|
+
this.buffer += decoder.decode(value, { stream: true });
|
|
95
|
+
this.processBuffer();
|
|
96
|
+
}
|
|
97
|
+
} catch (err) {
|
|
98
|
+
console.error("[openhands-adapter] stdout reader error:", err);
|
|
99
|
+
} finally {
|
|
100
|
+
this.connected = false;
|
|
101
|
+
for (const [, { reject }] of this.pending) {
|
|
102
|
+
reject(new Error("Transport closed"));
|
|
103
|
+
}
|
|
104
|
+
this.pending.clear();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private processBuffer(): void {
|
|
109
|
+
const lines = this.buffer.split("\n");
|
|
110
|
+
this.buffer = lines.pop() || "";
|
|
111
|
+
for (const line of lines) {
|
|
112
|
+
const trimmed = line.trim();
|
|
113
|
+
if (!trimmed) continue;
|
|
114
|
+
this.rawInCb?.(trimmed);
|
|
115
|
+
let msg: JsonRpcMessage;
|
|
116
|
+
try {
|
|
117
|
+
msg = JSON.parse(trimmed);
|
|
118
|
+
} catch {
|
|
119
|
+
console.warn("[openhands-adapter] Failed to parse JSON-RPC:", trimmed.substring(0, 200));
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
this.dispatch(msg);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private dispatch(msg: JsonRpcMessage): void {
|
|
127
|
+
if ("id" in msg && msg.id !== undefined) {
|
|
128
|
+
if ("method" in msg && msg.method) {
|
|
129
|
+
this.requestHandler?.(msg.method, msg.id as number, (msg as JsonRpcRequest).params || {});
|
|
130
|
+
} else {
|
|
131
|
+
const pending = this.pending.get(msg.id as number);
|
|
132
|
+
if (pending) {
|
|
133
|
+
this.pending.delete(msg.id as number);
|
|
134
|
+
const resp = msg as JsonRpcResponse;
|
|
135
|
+
if (resp.error) {
|
|
136
|
+
pending.reject(new Error(resp.error.message));
|
|
137
|
+
} else {
|
|
138
|
+
pending.resolve(resp.result);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} else if ("method" in msg) {
|
|
143
|
+
this.notificationHandler?.(msg.method, (msg as JsonRpcNotification).params || {});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async call(method: string, params: Record<string, unknown> = {}): Promise<unknown> {
|
|
148
|
+
const id = this.nextId++;
|
|
149
|
+
return new Promise(async (resolve, reject) => {
|
|
150
|
+
this.pending.set(id, { resolve, reject });
|
|
151
|
+
const request = JSON.stringify({ jsonrpc: "2.0", method, id, params });
|
|
152
|
+
try {
|
|
153
|
+
await this.writeRaw(request + "\n");
|
|
154
|
+
} catch (err) {
|
|
155
|
+
this.pending.delete(id);
|
|
156
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async notify(method: string, params: Record<string, unknown> = {}): Promise<void> {
|
|
162
|
+
const notification = JSON.stringify({ jsonrpc: "2.0", method, params });
|
|
163
|
+
await this.writeRaw(notification + "\n");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async respond(id: number, result: unknown): Promise<void> {
|
|
167
|
+
const response = JSON.stringify({ jsonrpc: "2.0", id, result });
|
|
168
|
+
await this.writeRaw(response + "\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
onNotification(handler: (method: string, params: Record<string, unknown>) => void): void {
|
|
172
|
+
this.notificationHandler = handler;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
onRequest(handler: (method: string, id: number, params: Record<string, unknown>) => void): void {
|
|
176
|
+
this.requestHandler = handler;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
isConnected(): boolean {
|
|
180
|
+
return this.connected;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
onRawIncoming(cb: (line: string) => void): void {
|
|
184
|
+
this.rawInCb = cb;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
onRawOutgoing(cb: (data: string) => void): void {
|
|
188
|
+
this.rawOutCb = cb;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private async writeRaw(data: string): Promise<void> {
|
|
192
|
+
if (!this.connected) throw new Error("Transport closed");
|
|
193
|
+
this.rawOutCb?.(data);
|
|
194
|
+
await this.writer.write(new TextEncoder().encode(data));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ─── OpenHands Adapter ──────────────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
export class OpenHandsAdapter implements AgentAdapter {
|
|
201
|
+
private transport: JsonRpcTransport;
|
|
202
|
+
private proc: Subprocess;
|
|
203
|
+
private sessionId: string;
|
|
204
|
+
private options: OpenHandsAdapterOptions;
|
|
205
|
+
|
|
206
|
+
private browserMessageCb: ((msg: BrowserIncomingMessage) => void) | null = null;
|
|
207
|
+
private sessionMetaCb: ((meta: AdapterSessionMeta) => void) | null = null;
|
|
208
|
+
private disconnectCb: (() => void) | null = null;
|
|
209
|
+
private initErrorCb: ((error: string) => void) | null = null;
|
|
210
|
+
|
|
211
|
+
// State
|
|
212
|
+
private ohSessionId: string | null = null;
|
|
213
|
+
private connected = false;
|
|
214
|
+
private initialized = false;
|
|
215
|
+
private initFailed = false;
|
|
216
|
+
|
|
217
|
+
// Streaming
|
|
218
|
+
private streamingText = "";
|
|
219
|
+
private streamingActive = false;
|
|
220
|
+
|
|
221
|
+
// Tool tracking
|
|
222
|
+
private activeToolCalls = new Map<string, { toolName: string; startTime: number }>();
|
|
223
|
+
|
|
224
|
+
// Pending outgoing messages (queued before init)
|
|
225
|
+
private pendingOutgoing: BrowserOutgoingMessage[] = [];
|
|
226
|
+
|
|
227
|
+
constructor(proc: Subprocess, sessionId: string, options: OpenHandsAdapterOptions = {}) {
|
|
228
|
+
this.proc = proc;
|
|
229
|
+
this.sessionId = sessionId;
|
|
230
|
+
this.options = options;
|
|
231
|
+
|
|
232
|
+
const stdout = proc.stdout;
|
|
233
|
+
const stdin = proc.stdin;
|
|
234
|
+
if (!stdout || !stdin || typeof stdout === "number" || typeof stdin === "number") {
|
|
235
|
+
throw new Error("OpenHands process must have stdio pipes");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
this.transport = new JsonRpcTransport(
|
|
239
|
+
stdin as WritableStream<Uint8Array> | { write(data: Uint8Array): number },
|
|
240
|
+
stdout as ReadableStream<Uint8Array>,
|
|
241
|
+
);
|
|
242
|
+
this.transport.onNotification((method, params) => this.handleNotification(method, params));
|
|
243
|
+
this.transport.onRequest((method, id, params) => this.handleRequest(method, id, params));
|
|
244
|
+
|
|
245
|
+
// Wire raw message recording
|
|
246
|
+
if (options.recorder) {
|
|
247
|
+
const recorder = options.recorder;
|
|
248
|
+
const cwd = options.cwd || "";
|
|
249
|
+
this.transport.onRawIncoming((line) => {
|
|
250
|
+
recorder.record(sessionId, "in", line, "cli", "openhands", cwd);
|
|
251
|
+
});
|
|
252
|
+
this.transport.onRawOutgoing((data) => {
|
|
253
|
+
recorder.record(sessionId, "out", data.trimEnd(), "cli", "openhands", cwd);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Monitor process exit
|
|
258
|
+
proc.exited.then(() => {
|
|
259
|
+
this.connected = false;
|
|
260
|
+
this.disconnectCb?.();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Start initialization
|
|
264
|
+
this.initialize();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ── Public API ──────────────────────────────────────────────────────────
|
|
268
|
+
|
|
269
|
+
sendBrowserMessage(msg: BrowserOutgoingMessage): boolean {
|
|
270
|
+
if (this.initFailed) return false;
|
|
271
|
+
|
|
272
|
+
if (!this.initialized || !this.ohSessionId) {
|
|
273
|
+
if (msg.type === "user_message" || msg.type === "permission_response") {
|
|
274
|
+
this.pendingOutgoing.push(msg);
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
if (!this.connected) return false;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return this.dispatchOutgoing(msg);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private dispatchOutgoing(msg: BrowserOutgoingMessage): boolean {
|
|
284
|
+
switch (msg.type) {
|
|
285
|
+
case "user_message":
|
|
286
|
+
this.handleOutgoingUserMessage(msg);
|
|
287
|
+
return true;
|
|
288
|
+
case "interrupt":
|
|
289
|
+
this.handleOutgoingInterrupt();
|
|
290
|
+
return true;
|
|
291
|
+
case "permission_response":
|
|
292
|
+
this.handleOutgoingPermissionResponse(msg);
|
|
293
|
+
return true;
|
|
294
|
+
default:
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
onBrowserMessage(cb: (msg: BrowserIncomingMessage) => void): void {
|
|
300
|
+
this.browserMessageCb = cb;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
onSessionMeta(cb: (meta: AdapterSessionMeta) => void): void {
|
|
304
|
+
this.sessionMetaCb = cb;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
onDisconnect(cb: () => void): void {
|
|
308
|
+
this.disconnectCb = cb;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
onInitError(cb: (error: string) => void): void {
|
|
312
|
+
this.initErrorCb = cb;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
isConnected(): boolean {
|
|
316
|
+
return this.connected;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async disconnect(): Promise<void> {
|
|
320
|
+
this.connected = false;
|
|
321
|
+
try {
|
|
322
|
+
this.proc.kill("SIGTERM");
|
|
323
|
+
await Promise.race([
|
|
324
|
+
this.proc.exited,
|
|
325
|
+
new Promise((r) => setTimeout(r, 5000)),
|
|
326
|
+
]);
|
|
327
|
+
} catch {}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
getBackendSessionId(): string | null {
|
|
331
|
+
return this.ohSessionId;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// ── Initialization ─────────────────────────────────────────────────────
|
|
335
|
+
|
|
336
|
+
private async initialize(): Promise<void> {
|
|
337
|
+
try {
|
|
338
|
+
await this.transport.call("initialize", {
|
|
339
|
+
protocolVersion: "v1",
|
|
340
|
+
clientCapabilities: {},
|
|
341
|
+
clientInfo: {
|
|
342
|
+
name: "campfire",
|
|
343
|
+
title: "Campfire",
|
|
344
|
+
version: "1.0.0",
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
this.connected = true;
|
|
349
|
+
this.initialized = true;
|
|
350
|
+
|
|
351
|
+
// Create session
|
|
352
|
+
const newResult = await this.transport.call("session/new", {
|
|
353
|
+
mcpServers: [],
|
|
354
|
+
cwd: this.options.cwd || process.cwd(),
|
|
355
|
+
}) as Record<string, unknown>;
|
|
356
|
+
|
|
357
|
+
this.ohSessionId = newResult?.sessionId as string || randomUUID();
|
|
358
|
+
|
|
359
|
+
this.sessionMetaCb?.({
|
|
360
|
+
cliSessionId: this.ohSessionId,
|
|
361
|
+
model: this.options.model,
|
|
362
|
+
cwd: this.options.cwd,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
// Emit session_init
|
|
366
|
+
const state: SessionState = {
|
|
367
|
+
session_id: this.sessionId,
|
|
368
|
+
backend_type: "openhands",
|
|
369
|
+
model: this.options.model || "default",
|
|
370
|
+
cwd: this.options.cwd || "",
|
|
371
|
+
tools: ["bash", "edit", "read", "write"],
|
|
372
|
+
permissionMode: this.options.permissionMode || "default",
|
|
373
|
+
claude_code_version: "",
|
|
374
|
+
mcp_servers: [],
|
|
375
|
+
agents: [],
|
|
376
|
+
slash_commands: [],
|
|
377
|
+
skills: [],
|
|
378
|
+
total_cost_usd: 0,
|
|
379
|
+
num_turns: 0,
|
|
380
|
+
context_used_percent: 0,
|
|
381
|
+
is_compacting: false,
|
|
382
|
+
git_branch: "",
|
|
383
|
+
is_worktree: false,
|
|
384
|
+
repo_root: "",
|
|
385
|
+
git_ahead: 0,
|
|
386
|
+
git_behind: 0,
|
|
387
|
+
total_lines_added: 0,
|
|
388
|
+
total_lines_removed: 0,
|
|
389
|
+
total_duration_api_ms: 0,
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
this.emit({ type: "session_init", session: state });
|
|
393
|
+
|
|
394
|
+
// Flush queued messages
|
|
395
|
+
if (this.pendingOutgoing.length > 0) {
|
|
396
|
+
const queued = this.pendingOutgoing.splice(0);
|
|
397
|
+
for (const msg of queued) {
|
|
398
|
+
this.dispatchOutgoing(msg);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
} catch (err) {
|
|
402
|
+
const errorMsg = `OpenHands initialization failed: ${err}`;
|
|
403
|
+
console.error(`[openhands-adapter] ${errorMsg}`);
|
|
404
|
+
this.initFailed = true;
|
|
405
|
+
this.connected = false;
|
|
406
|
+
this.pendingOutgoing.length = 0;
|
|
407
|
+
this.emit({ type: "error", message: errorMsg });
|
|
408
|
+
this.initErrorCb?.(errorMsg);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ── Outgoing handlers ──────────────────────────────────────────────────
|
|
413
|
+
|
|
414
|
+
private async handleOutgoingUserMessage(
|
|
415
|
+
msg: { type: "user_message"; content: string; images?: { media_type: string; data: string }[] },
|
|
416
|
+
): Promise<void> {
|
|
417
|
+
if (!this.ohSessionId) {
|
|
418
|
+
this.emit({ type: "error", message: "No OpenHands session started yet" });
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const prompt: Array<Record<string, unknown>> = [];
|
|
423
|
+
if (msg.images?.length) {
|
|
424
|
+
for (const img of msg.images) {
|
|
425
|
+
prompt.push({ type: "image", data: img.data, media_type: img.media_type });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
prompt.push({ type: "text", text: msg.content });
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
const result = await this.transport.call("session/prompt", {
|
|
432
|
+
sessionId: this.ohSessionId,
|
|
433
|
+
prompt,
|
|
434
|
+
}) as Record<string, unknown>;
|
|
435
|
+
|
|
436
|
+
this.finishTurn(result);
|
|
437
|
+
} catch (err) {
|
|
438
|
+
this.emit({ type: "error", message: `Failed to send prompt: ${err}` });
|
|
439
|
+
this.emitResult("error_during_execution", true);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private async handleOutgoingPermissionResponse(
|
|
444
|
+
msg: { type: "permission_response"; request_id: string; behavior: "allow" | "deny" },
|
|
445
|
+
): Promise<void> {
|
|
446
|
+
const permissionMap: Record<string, string> = {
|
|
447
|
+
allow: "AllowOnce",
|
|
448
|
+
deny: "RejectOnce",
|
|
449
|
+
};
|
|
450
|
+
try {
|
|
451
|
+
await this.transport.notify("requestPermission", {
|
|
452
|
+
id: msg.request_id,
|
|
453
|
+
permission: permissionMap[msg.behavior] || "RejectOnce",
|
|
454
|
+
});
|
|
455
|
+
} catch (err) {
|
|
456
|
+
console.warn(`[openhands-adapter] Failed to send permission response: ${err}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private async handleOutgoingInterrupt(): Promise<void> {
|
|
461
|
+
try {
|
|
462
|
+
await this.transport.notify("session/cancel", {});
|
|
463
|
+
} catch (err) {
|
|
464
|
+
console.warn("[openhands-adapter] Cancel failed:", err);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ── Incoming notification handlers ─────────────────────────────────────
|
|
469
|
+
|
|
470
|
+
private handleNotification(method: string, params: Record<string, unknown>): void {
|
|
471
|
+
try {
|
|
472
|
+
if (method === "session/notification") {
|
|
473
|
+
this.handleSessionNotification(params);
|
|
474
|
+
}
|
|
475
|
+
} catch (err) {
|
|
476
|
+
console.error(`[openhands-adapter] Error handling notification ${method}:`, err);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private handleSessionNotification(params: Record<string, unknown>): void {
|
|
481
|
+
const update = params.update as Record<string, unknown>;
|
|
482
|
+
if (!update) return;
|
|
483
|
+
|
|
484
|
+
const updateType = update.sessionUpdate as string;
|
|
485
|
+
|
|
486
|
+
switch (updateType) {
|
|
487
|
+
case "agentMessageChunk":
|
|
488
|
+
this.handleAgentMessageChunk(update);
|
|
489
|
+
break;
|
|
490
|
+
case "agentThoughtChunk":
|
|
491
|
+
this.handleAgentThoughtChunk(update);
|
|
492
|
+
break;
|
|
493
|
+
case "toolCall":
|
|
494
|
+
this.handleToolCall(update);
|
|
495
|
+
break;
|
|
496
|
+
case "toolCallUpdate":
|
|
497
|
+
this.handleToolCallUpdate(update);
|
|
498
|
+
break;
|
|
499
|
+
case "actionRequired":
|
|
500
|
+
this.handleActionRequired(update);
|
|
501
|
+
break;
|
|
502
|
+
case "userMessageChunk":
|
|
503
|
+
break; // Echo — ignore
|
|
504
|
+
default:
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
private handleRequest(method: string, id: number, params: Record<string, unknown>): void {
|
|
510
|
+
if (method === "requestPermission") {
|
|
511
|
+
const requestId = `oh-perm-${randomUUID()}`;
|
|
512
|
+
const data = params.data as Record<string, unknown> || params;
|
|
513
|
+
this.emitPermissionRequest(requestId, data);
|
|
514
|
+
} else {
|
|
515
|
+
this.transport.respond(id, {});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// ── SessionUpdate handlers ─────────────────────────────────────────────
|
|
520
|
+
|
|
521
|
+
private handleAgentMessageChunk(update: Record<string, unknown>): void {
|
|
522
|
+
const content = update.content as Record<string, unknown>;
|
|
523
|
+
if (!content) return;
|
|
524
|
+
const text = content.text as string || "";
|
|
525
|
+
|
|
526
|
+
if (!this.streamingActive) {
|
|
527
|
+
this.streamingActive = true;
|
|
528
|
+
this.streamingText = "";
|
|
529
|
+
|
|
530
|
+
this.emit({
|
|
531
|
+
type: "stream_event",
|
|
532
|
+
event: {
|
|
533
|
+
type: "message_start",
|
|
534
|
+
message: {
|
|
535
|
+
id: `oh-msg-${randomUUID()}`,
|
|
536
|
+
type: "message",
|
|
537
|
+
role: "assistant",
|
|
538
|
+
model: this.options.model || "",
|
|
539
|
+
content: [],
|
|
540
|
+
stop_reason: null,
|
|
541
|
+
usage: { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
parent_tool_use_id: null,
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
this.emit({
|
|
548
|
+
type: "stream_event",
|
|
549
|
+
event: {
|
|
550
|
+
type: "content_block_start",
|
|
551
|
+
index: 0,
|
|
552
|
+
content_block: { type: "text", text: "" },
|
|
553
|
+
},
|
|
554
|
+
parent_tool_use_id: null,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
this.streamingText += text;
|
|
559
|
+
|
|
560
|
+
this.emit({
|
|
561
|
+
type: "stream_event",
|
|
562
|
+
event: {
|
|
563
|
+
type: "content_block_delta",
|
|
564
|
+
index: 0,
|
|
565
|
+
delta: { type: "text_delta", text },
|
|
566
|
+
},
|
|
567
|
+
parent_tool_use_id: null,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private handleAgentThoughtChunk(update: Record<string, unknown>): void {
|
|
572
|
+
const content = update.content as Record<string, unknown>;
|
|
573
|
+
if (!content) return;
|
|
574
|
+
const thinking = content.thinking as string || content.text as string || "";
|
|
575
|
+
if (!thinking) return;
|
|
576
|
+
|
|
577
|
+
this.emit({
|
|
578
|
+
type: "stream_event",
|
|
579
|
+
event: {
|
|
580
|
+
type: "content_block_start",
|
|
581
|
+
index: 0,
|
|
582
|
+
content_block: { type: "thinking", thinking },
|
|
583
|
+
},
|
|
584
|
+
parent_tool_use_id: null,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
private handleToolCall(update: Record<string, unknown>): void {
|
|
589
|
+
this.flushStreaming();
|
|
590
|
+
|
|
591
|
+
const toolName = update.toolName as string || "unknown";
|
|
592
|
+
const args = update.arguments as Record<string, unknown> || {};
|
|
593
|
+
const toolCallId = update.id as string || `oh-tool-${randomUUID()}`;
|
|
594
|
+
|
|
595
|
+
this.activeToolCalls.set(toolCallId, { toolName, startTime: Date.now() });
|
|
596
|
+
const mappedName = this.mapToolName(toolName);
|
|
597
|
+
|
|
598
|
+
this.emit({
|
|
599
|
+
type: "stream_event",
|
|
600
|
+
event: {
|
|
601
|
+
type: "content_block_start",
|
|
602
|
+
index: 0,
|
|
603
|
+
content_block: { type: "tool_use", id: toolCallId, name: mappedName, input: {} },
|
|
604
|
+
},
|
|
605
|
+
parent_tool_use_id: null,
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
this.emit({
|
|
609
|
+
type: "assistant",
|
|
610
|
+
message: {
|
|
611
|
+
id: `oh-tu-${toolCallId}`,
|
|
612
|
+
type: "message",
|
|
613
|
+
role: "assistant",
|
|
614
|
+
model: this.options.model || "",
|
|
615
|
+
content: [{ type: "tool_use", id: toolCallId, name: mappedName, input: args }],
|
|
616
|
+
stop_reason: null,
|
|
617
|
+
usage: { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },
|
|
618
|
+
},
|
|
619
|
+
parent_tool_use_id: null,
|
|
620
|
+
timestamp: Date.now(),
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
this.emit({
|
|
624
|
+
type: "tool_progress",
|
|
625
|
+
tool_use_id: toolCallId,
|
|
626
|
+
tool_name: mappedName,
|
|
627
|
+
elapsed_time_seconds: 0,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
private handleToolCallUpdate(update: Record<string, unknown>): void {
|
|
632
|
+
const toolCallId = update.id as string || "";
|
|
633
|
+
const result = update.result as string || update.content as string || "";
|
|
634
|
+
const status = update.status as string || "completed";
|
|
635
|
+
const isError = status === "failed" || status === "error";
|
|
636
|
+
|
|
637
|
+
this.activeToolCalls.delete(toolCallId);
|
|
638
|
+
|
|
639
|
+
const safeContent = typeof result === "string" ? result : JSON.stringify(result);
|
|
640
|
+
this.emit({
|
|
641
|
+
type: "assistant",
|
|
642
|
+
message: {
|
|
643
|
+
id: `oh-tr-${toolCallId}`,
|
|
644
|
+
type: "message",
|
|
645
|
+
role: "assistant",
|
|
646
|
+
model: this.options.model || "",
|
|
647
|
+
content: [
|
|
648
|
+
{
|
|
649
|
+
type: "tool_result",
|
|
650
|
+
tool_use_id: toolCallId,
|
|
651
|
+
content: safeContent || (isError ? "Tool call failed" : "Tool call completed"),
|
|
652
|
+
is_error: isError,
|
|
653
|
+
},
|
|
654
|
+
],
|
|
655
|
+
stop_reason: null,
|
|
656
|
+
usage: { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },
|
|
657
|
+
},
|
|
658
|
+
parent_tool_use_id: null,
|
|
659
|
+
timestamp: Date.now(),
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private handleActionRequired(update: Record<string, unknown>): void {
|
|
664
|
+
const data = update.data as Record<string, unknown>;
|
|
665
|
+
if (!data) return;
|
|
666
|
+
const actionType = data.actionType as string;
|
|
667
|
+
if (actionType === "toolConfirmation") {
|
|
668
|
+
const requestId = data.id as string || `oh-action-${randomUUID()}`;
|
|
669
|
+
this.emitPermissionRequest(requestId, data);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// ── Helpers ────────────────────────────────────────────────────────────
|
|
674
|
+
|
|
675
|
+
private emit(msg: BrowserIncomingMessage): void {
|
|
676
|
+
this.browserMessageCb?.(msg);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
private flushStreaming(): void {
|
|
680
|
+
if (!this.streamingActive) return;
|
|
681
|
+
|
|
682
|
+
this.emit({
|
|
683
|
+
type: "stream_event",
|
|
684
|
+
event: { type: "content_block_stop", index: 0 },
|
|
685
|
+
parent_tool_use_id: null,
|
|
686
|
+
});
|
|
687
|
+
this.emit({
|
|
688
|
+
type: "stream_event",
|
|
689
|
+
event: {
|
|
690
|
+
type: "message_delta",
|
|
691
|
+
delta: { stop_reason: null },
|
|
692
|
+
usage: { output_tokens: 0 },
|
|
693
|
+
},
|
|
694
|
+
parent_tool_use_id: null,
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
if (this.streamingText) {
|
|
698
|
+
this.emit({
|
|
699
|
+
type: "assistant",
|
|
700
|
+
message: {
|
|
701
|
+
id: `oh-msg-${randomUUID()}`,
|
|
702
|
+
type: "message",
|
|
703
|
+
role: "assistant",
|
|
704
|
+
model: this.options.model || "",
|
|
705
|
+
content: [{ type: "text", text: this.streamingText }],
|
|
706
|
+
stop_reason: "end_turn",
|
|
707
|
+
usage: { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },
|
|
708
|
+
},
|
|
709
|
+
parent_tool_use_id: null,
|
|
710
|
+
timestamp: Date.now(),
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
this.streamingText = "";
|
|
715
|
+
this.streamingActive = false;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
private finishTurn(result: Record<string, unknown>): void {
|
|
719
|
+
this.flushStreaming();
|
|
720
|
+
const stopReason = result?.stopReason as string || result?.stop_reason as string || "end_turn";
|
|
721
|
+
this.emitResult(
|
|
722
|
+
stopReason === "cancelled" ? "error_during_execution" : "success",
|
|
723
|
+
stopReason === "cancelled",
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
private emitResult(subtype: CLIResultMessage["subtype"], isError: boolean): void {
|
|
728
|
+
const result: CLIResultMessage = {
|
|
729
|
+
type: "result",
|
|
730
|
+
subtype,
|
|
731
|
+
is_error: isError,
|
|
732
|
+
duration_ms: 0,
|
|
733
|
+
duration_api_ms: 0,
|
|
734
|
+
num_turns: 1,
|
|
735
|
+
total_cost_usd: 0,
|
|
736
|
+
stop_reason: subtype === "success" ? "end_turn" : "error",
|
|
737
|
+
usage: { input_tokens: 0, output_tokens: 0, cache_creation_input_tokens: 0, cache_read_input_tokens: 0 },
|
|
738
|
+
uuid: randomUUID(),
|
|
739
|
+
session_id: this.sessionId,
|
|
740
|
+
};
|
|
741
|
+
this.emit({ type: "result", data: result });
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
private emitPermissionRequest(requestId: string, data: Record<string, unknown>): void {
|
|
745
|
+
const toolName = data.toolName as string || "unknown";
|
|
746
|
+
const args = data.arguments as Record<string, unknown> || {};
|
|
747
|
+
const prompt = data.prompt as string || data.content as string;
|
|
748
|
+
const mappedName = this.mapToolName(toolName);
|
|
749
|
+
|
|
750
|
+
const perm: PermissionRequest = {
|
|
751
|
+
request_id: requestId,
|
|
752
|
+
tool_name: mappedName,
|
|
753
|
+
input: args,
|
|
754
|
+
description: prompt || `OpenHands wants to use: ${mappedName}`,
|
|
755
|
+
tool_use_id: data.toolCallId as string || requestId,
|
|
756
|
+
timestamp: Date.now(),
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
this.emit({ type: "permission_request", request: perm });
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
private mapToolName(ohToolName: string): string {
|
|
763
|
+
const toolMap: Record<string, string> = {
|
|
764
|
+
"bash": "Bash",
|
|
765
|
+
"execute_bash": "Bash",
|
|
766
|
+
"str_replace_editor": "Edit",
|
|
767
|
+
"edit": "Edit",
|
|
768
|
+
"read": "Read",
|
|
769
|
+
"write": "Write",
|
|
770
|
+
"search": "Grep",
|
|
771
|
+
"browser": "WebFetch",
|
|
772
|
+
};
|
|
773
|
+
return toolMap[ohToolName] || ohToolName;
|
|
774
|
+
}
|
|
775
|
+
}
|