@corenel/harness 0.1.0 → 0.3.2
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/dist/acp/agentMap.d.ts +34 -0
- package/dist/acp/agentMap.d.ts.map +1 -0
- package/dist/acp/agentMap.js +110 -0
- package/dist/acp/agentMap.js.map +1 -0
- package/dist/acp/index.d.ts +4 -0
- package/dist/acp/index.d.ts.map +1 -0
- package/dist/acp/index.js +6 -0
- package/dist/acp/index.js.map +1 -0
- package/dist/acp/policyMap.d.ts +55 -0
- package/dist/acp/policyMap.d.ts.map +1 -0
- package/dist/acp/policyMap.js +121 -0
- package/dist/acp/policyMap.js.map +1 -0
- package/dist/acp/types.d.ts +41 -0
- package/dist/acp/types.d.ts.map +1 -0
- package/dist/acp/types.js +14 -0
- package/dist/acp/types.js.map +1 -0
- package/dist/api/backend.d.ts +39 -0
- package/dist/api/backend.d.ts.map +1 -0
- package/dist/api/backend.js +126 -0
- package/dist/api/backend.js.map +1 -0
- package/dist/api/client.d.ts +95 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +212 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/fileMirrorSource.d.ts +24 -0
- package/dist/api/fileMirrorSource.d.ts.map +1 -0
- package/dist/api/fileMirrorSource.js +59 -0
- package/dist/api/fileMirrorSource.js.map +1 -0
- package/dist/api/mirror.d.ts +102 -0
- package/dist/api/mirror.d.ts.map +1 -0
- package/dist/api/mirror.js +204 -0
- package/dist/api/mirror.js.map +1 -0
- package/dist/api/mirrorScheduler.d.ts +56 -0
- package/dist/api/mirrorScheduler.d.ts.map +1 -0
- package/dist/api/mirrorScheduler.js +167 -0
- package/dist/api/mirrorScheduler.js.map +1 -0
- package/dist/conversation.d.ts +0 -11
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +1 -24
- package/dist/conversation.js.map +1 -1
- package/dist/core/budget.d.ts +40 -0
- package/dist/core/budget.d.ts.map +1 -0
- package/dist/core/budget.js +62 -0
- package/dist/core/budget.js.map +1 -0
- package/dist/core/loop.d.ts +11 -0
- package/dist/core/loop.d.ts.map +1 -1
- package/dist/core/loop.js +89 -20
- package/dist/core/loop.js.map +1 -1
- package/dist/core/pricing.d.ts +8 -0
- package/dist/core/pricing.d.ts.map +1 -0
- package/dist/core/pricing.js +12 -0
- package/dist/core/pricing.js.map +1 -0
- package/dist/core/recovery.d.ts +60 -0
- package/dist/core/recovery.d.ts.map +1 -0
- package/dist/core/recovery.js +128 -0
- package/dist/core/recovery.js.map +1 -0
- package/dist/guardrail/builtins.js +6 -6
- package/dist/guardrail/builtins.js.map +1 -1
- package/dist/guardrail/permission-service.js +1 -1
- package/dist/guardrail/permission-service.js.map +1 -1
- package/dist/guardrail/serialize.d.ts.map +1 -1
- package/dist/guardrail/serialize.js +6 -0
- package/dist/guardrail/serialize.js.map +1 -1
- package/dist/guardrail/validate.d.ts.map +1 -1
- package/dist/guardrail/validate.js +13 -0
- package/dist/guardrail/validate.js.map +1 -1
- package/dist/install/lockfile.d.ts +79 -0
- package/dist/install/lockfile.d.ts.map +1 -0
- package/dist/install/lockfile.js +155 -0
- package/dist/install/lockfile.js.map +1 -0
- package/dist/install/store.d.ts +13 -0
- package/dist/install/store.d.ts.map +1 -0
- package/dist/install/store.js +38 -0
- package/dist/install/store.js.map +1 -0
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +6 -1
- package/dist/log.js.map +1 -1
- package/dist/prompts/host.d.ts +4 -0
- package/dist/prompts/host.d.ts.map +1 -1
- package/dist/prompts/host.js.map +1 -1
- package/dist/prompts/index.d.ts +6 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +16 -6
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/library/defaults.generated.d.ts.map +1 -1
- package/dist/prompts/library/defaults.generated.js +22 -4
- package/dist/prompts/library/defaults.generated.js.map +1 -1
- package/dist/prompts/toolsMarker.d.ts +13 -0
- package/dist/prompts/toolsMarker.d.ts.map +1 -0
- package/dist/prompts/toolsMarker.js +21 -0
- package/dist/prompts/toolsMarker.js.map +1 -0
- package/dist/providers/gateway.d.ts +19 -1
- package/dist/providers/gateway.d.ts.map +1 -1
- package/dist/providers/gateway.js +33 -4
- package/dist/providers/gateway.js.map +1 -1
- package/dist/services.d.ts +13 -0
- package/dist/services.d.ts.map +1 -1
- package/dist/services.js +2 -0
- package/dist/services.js.map +1 -1
- package/dist/sessions/apiLeaseStore.d.ts +10 -0
- package/dist/sessions/apiLeaseStore.d.ts.map +1 -0
- package/dist/sessions/apiLeaseStore.js +41 -0
- package/dist/sessions/apiLeaseStore.js.map +1 -0
- package/dist/sessions/fileRecallStore.d.ts +33 -0
- package/dist/sessions/fileRecallStore.d.ts.map +1 -0
- package/dist/sessions/fileRecallStore.js +112 -0
- package/dist/sessions/fileRecallStore.js.map +1 -0
- package/dist/sessions/fileSessionStore.d.ts +20 -0
- package/dist/sessions/fileSessionStore.d.ts.map +1 -0
- package/dist/sessions/fileSessionStore.js +151 -0
- package/dist/sessions/fileSessionStore.js.map +1 -0
- package/dist/sessions/head.d.ts +90 -0
- package/dist/sessions/head.d.ts.map +1 -0
- package/dist/sessions/head.js +82 -0
- package/dist/sessions/head.js.map +1 -0
- package/dist/sessions/inflight.d.ts +58 -0
- package/dist/sessions/inflight.d.ts.map +1 -0
- package/dist/sessions/inflight.js +0 -0
- package/dist/sessions/inflight.js.map +1 -0
- package/dist/sessions/jsonl.d.ts +23 -0
- package/dist/sessions/jsonl.d.ts.map +1 -0
- package/dist/sessions/jsonl.js +52 -0
- package/dist/sessions/jsonl.js.map +1 -0
- package/dist/sessions/lease.d.ts +59 -0
- package/dist/sessions/lease.d.ts.map +1 -0
- package/dist/sessions/lease.js +115 -0
- package/dist/sessions/lease.js.map +1 -0
- package/dist/sessions/leaseStore.d.ts +46 -0
- package/dist/sessions/leaseStore.d.ts.map +1 -0
- package/dist/sessions/leaseStore.js +58 -0
- package/dist/sessions/leaseStore.js.map +1 -0
- package/dist/sessions/testFs.d.ts +5 -0
- package/dist/sessions/testFs.d.ts.map +1 -0
- package/dist/sessions/testFs.js +85 -0
- package/dist/sessions/testFs.js.map +1 -0
- package/dist/sessions/timeline.d.ts +134 -0
- package/dist/sessions/timeline.d.ts.map +1 -0
- package/dist/sessions/timeline.js +127 -0
- package/dist/sessions/timeline.js.map +1 -0
- package/dist/sidecar/host.d.ts +284 -2
- package/dist/sidecar/host.d.ts.map +1 -1
- package/dist/sidecar/host.js +725 -1
- package/dist/sidecar/host.js.map +1 -1
- package/dist/sidecar/server.d.ts +217 -2
- package/dist/sidecar/server.d.ts.map +1 -1
- package/dist/sidecar/server.js +828 -3
- package/dist/sidecar/server.js.map +1 -1
- package/dist/skills.d.ts +8 -3
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +71 -35
- package/dist/skills.js.map +1 -1
- package/dist/snapshot/effects.d.ts +69 -0
- package/dist/snapshot/effects.d.ts.map +1 -0
- package/dist/snapshot/effects.js +67 -0
- package/dist/snapshot/effects.js.map +1 -0
- package/dist/snapshot/manifest.d.ts +73 -0
- package/dist/snapshot/manifest.d.ts.map +1 -0
- package/dist/snapshot/manifest.js +141 -0
- package/dist/snapshot/manifest.js.map +1 -0
- package/dist/snapshot/tree.d.ts +62 -0
- package/dist/snapshot/tree.d.ts.map +1 -0
- package/dist/snapshot/tree.js +147 -0
- package/dist/snapshot/tree.js.map +1 -0
- package/dist/tools/builtins.d.ts +1 -0
- package/dist/tools/builtins.d.ts.map +1 -1
- package/dist/tools/builtins.js +150 -5
- package/dist/tools/builtins.js.map +1 -1
- package/dist/tools/files.d.ts.map +1 -1
- package/dist/tools/files.js +13 -0
- package/dist/tools/files.js.map +1 -1
- package/dist/tools/http.d.ts.map +1 -1
- package/dist/tools/http.js.map +1 -1
- package/dist/tools/namespaces.d.ts +1 -1
- package/dist/tools/namespaces.d.ts.map +1 -1
- package/dist/tools/namespaces.js +24 -0
- package/dist/tools/namespaces.js.map +1 -1
- package/dist/tools/todo.d.ts.map +1 -1
- package/dist/tools/todo.js +12 -0
- package/dist/tools/todo.js.map +1 -1
- package/package.json +26 -34
package/dist/sidecar/host.js
CHANGED
|
@@ -45,12 +45,107 @@ export class SidecarHost {
|
|
|
45
45
|
writable: true,
|
|
46
46
|
value: new Map()
|
|
47
47
|
});
|
|
48
|
+
Object.defineProperty(this, "runSeq", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
configurable: true,
|
|
51
|
+
writable: true,
|
|
52
|
+
value: 0
|
|
53
|
+
});
|
|
54
|
+
Object.defineProperty(this, "runPending", {
|
|
55
|
+
enumerable: true,
|
|
56
|
+
configurable: true,
|
|
57
|
+
writable: true,
|
|
58
|
+
value: new Map()
|
|
59
|
+
});
|
|
60
|
+
Object.defineProperty(this, "acpSeq", {
|
|
61
|
+
enumerable: true,
|
|
62
|
+
configurable: true,
|
|
63
|
+
writable: true,
|
|
64
|
+
value: 0
|
|
65
|
+
});
|
|
66
|
+
Object.defineProperty(this, "acpPending", {
|
|
67
|
+
enumerable: true,
|
|
68
|
+
configurable: true,
|
|
69
|
+
writable: true,
|
|
70
|
+
value: new Map()
|
|
71
|
+
});
|
|
72
|
+
Object.defineProperty(this, "syncSeq", {
|
|
73
|
+
enumerable: true,
|
|
74
|
+
configurable: true,
|
|
75
|
+
writable: true,
|
|
76
|
+
value: 0
|
|
77
|
+
});
|
|
78
|
+
Object.defineProperty(this, "syncPending", {
|
|
79
|
+
enumerable: true,
|
|
80
|
+
configurable: true,
|
|
81
|
+
writable: true,
|
|
82
|
+
value: new Map()
|
|
83
|
+
});
|
|
84
|
+
Object.defineProperty(this, "rootsSeq", {
|
|
85
|
+
enumerable: true,
|
|
86
|
+
configurable: true,
|
|
87
|
+
writable: true,
|
|
88
|
+
value: 0
|
|
89
|
+
});
|
|
90
|
+
Object.defineProperty(this, "rootsPending", {
|
|
91
|
+
enumerable: true,
|
|
92
|
+
configurable: true,
|
|
93
|
+
writable: true,
|
|
94
|
+
value: new Map()
|
|
95
|
+
});
|
|
96
|
+
Object.defineProperty(this, "procSeq", {
|
|
97
|
+
enumerable: true,
|
|
98
|
+
configurable: true,
|
|
99
|
+
writable: true,
|
|
100
|
+
value: 0
|
|
101
|
+
});
|
|
102
|
+
Object.defineProperty(this, "procPending", {
|
|
103
|
+
enumerable: true,
|
|
104
|
+
configurable: true,
|
|
105
|
+
writable: true,
|
|
106
|
+
value: new Map()
|
|
107
|
+
});
|
|
108
|
+
/** Watchers by procId, NOT by request id: a process outlives the request that
|
|
109
|
+
* spawned it, and the same process can be watched by a spawn and later by a
|
|
110
|
+
* reattach. Keyed by the only durable identity there is. */
|
|
111
|
+
Object.defineProperty(this, "procWatchers", {
|
|
112
|
+
enumerable: true,
|
|
113
|
+
configurable: true,
|
|
114
|
+
writable: true,
|
|
115
|
+
value: new Map()
|
|
116
|
+
});
|
|
117
|
+
Object.defineProperty(this, "sessionSeq", {
|
|
118
|
+
enumerable: true,
|
|
119
|
+
configurable: true,
|
|
120
|
+
writable: true,
|
|
121
|
+
value: 0
|
|
122
|
+
});
|
|
123
|
+
Object.defineProperty(this, "sessionPending", {
|
|
124
|
+
enumerable: true,
|
|
125
|
+
configurable: true,
|
|
126
|
+
writable: true,
|
|
127
|
+
value: new Map()
|
|
128
|
+
});
|
|
129
|
+
/** Per-session stream handlers, keyed by session because a session outlives
|
|
130
|
+
* the request that started watching it. */
|
|
131
|
+
Object.defineProperty(this, "sessionWatchers", {
|
|
132
|
+
enumerable: true,
|
|
133
|
+
configurable: true,
|
|
134
|
+
writable: true,
|
|
135
|
+
value: new Map()
|
|
136
|
+
});
|
|
48
137
|
Object.defineProperty(this, "features", {
|
|
49
138
|
enumerable: true,
|
|
50
139
|
configurable: true,
|
|
51
140
|
writable: true,
|
|
52
141
|
value: []
|
|
53
142
|
});
|
|
143
|
+
Object.defineProperty(this, "info", {
|
|
144
|
+
enumerable: true,
|
|
145
|
+
configurable: true,
|
|
146
|
+
writable: true,
|
|
147
|
+
value: {}
|
|
148
|
+
});
|
|
54
149
|
Object.defineProperty(this, "imported", {
|
|
55
150
|
enumerable: true,
|
|
56
151
|
configurable: true,
|
|
@@ -72,7 +167,12 @@ export class SidecarHost {
|
|
|
72
167
|
connect() {
|
|
73
168
|
return new Promise((resolve, reject) => {
|
|
74
169
|
this.welcome = { resolve, reject };
|
|
75
|
-
this.ch.send({
|
|
170
|
+
this.ch.send({
|
|
171
|
+
t: 'hello', protocol: SIDECAR_PROTOCOL, token: this.opts.token,
|
|
172
|
+
...(this.opts.stateDir ? { stateDir: this.opts.stateDir } : {}),
|
|
173
|
+
...(this.opts.clientId ? { clientId: this.opts.clientId } : {}),
|
|
174
|
+
...(this.opts.clientName ? { clientName: this.opts.clientName } : {}),
|
|
175
|
+
});
|
|
76
176
|
});
|
|
77
177
|
}
|
|
78
178
|
/** Drop the connection: unregister imported tools and fail anything in flight. */
|
|
@@ -86,6 +186,32 @@ export class SidecarHost {
|
|
|
86
186
|
for (const p of this.httpPending.values())
|
|
87
187
|
p.onErr(reason);
|
|
88
188
|
this.httpPending.clear();
|
|
189
|
+
for (const p of this.runPending.values())
|
|
190
|
+
p.reject(new Error(reason));
|
|
191
|
+
this.runPending.clear();
|
|
192
|
+
for (const p of this.acpPending.values())
|
|
193
|
+
p.reject(new Error(reason));
|
|
194
|
+
this.acpPending.clear();
|
|
195
|
+
for (const p of this.syncPending.values())
|
|
196
|
+
p.reject(new Error(reason));
|
|
197
|
+
this.syncPending.clear();
|
|
198
|
+
for (const p of this.rootsPending.values())
|
|
199
|
+
p.reject(new Error(reason));
|
|
200
|
+
this.rootsPending.clear();
|
|
201
|
+
/* Session and process calls settle too, and this was MISSING.
|
|
202
|
+
*
|
|
203
|
+
* Every other pending map was swept here and these two were not, so a
|
|
204
|
+
* `sessionPause` or `procStart` in flight when the daemon dropped never
|
|
205
|
+
* settled at all — not resolved, not rejected. A surface awaiting one sits
|
|
206
|
+
* on a disabled button reading "Starting…" until the page is reloaded,
|
|
207
|
+
* because the failure it is waiting to show never arrives. A dropped
|
|
208
|
+
* connection is an outcome; silence is not. */
|
|
209
|
+
for (const p of this.sessionPending.values())
|
|
210
|
+
p.reject(new Error(reason));
|
|
211
|
+
this.sessionPending.clear();
|
|
212
|
+
for (const p of this.procPending.values())
|
|
213
|
+
p.reject(new Error(reason));
|
|
214
|
+
this.procPending.clear();
|
|
89
215
|
this.welcome?.reject(new Error(reason));
|
|
90
216
|
this.welcome = null;
|
|
91
217
|
this.ch.close();
|
|
@@ -94,6 +220,7 @@ export class SidecarHost {
|
|
|
94
220
|
switch (m.t) {
|
|
95
221
|
case 'welcome': {
|
|
96
222
|
this.features = m.features ?? [];
|
|
223
|
+
this.info = m.info ?? {};
|
|
97
224
|
for (const decl of m.tools) {
|
|
98
225
|
const tool = { ...decl, run: (args) => this.invoke(decl.name, args) };
|
|
99
226
|
this.opts.registerTool(tool);
|
|
@@ -119,6 +246,32 @@ export class SidecarHost {
|
|
|
119
246
|
p.reject(new Error(m.error || 'sidecar tool failed'));
|
|
120
247
|
return;
|
|
121
248
|
}
|
|
249
|
+
case 'roots':
|
|
250
|
+
// Unsolicited: the set changed on the daemon (possibly via ANOTHER client).
|
|
251
|
+
this.opts.onRoots?.({ root: m.root, roots: m.roots });
|
|
252
|
+
return;
|
|
253
|
+
case 'roots-browse-result': {
|
|
254
|
+
const p = this.rootsPending.get(m.id);
|
|
255
|
+
if (!p)
|
|
256
|
+
return;
|
|
257
|
+
this.rootsPending.delete(m.id);
|
|
258
|
+
if (m.ok)
|
|
259
|
+
p.resolve(m.dirs ?? []);
|
|
260
|
+
else
|
|
261
|
+
p.reject(new Error(m.error || 'could not browse'));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
case 'roots-add-result': {
|
|
265
|
+
const p = this.rootsPending.get(m.id);
|
|
266
|
+
if (!p)
|
|
267
|
+
return;
|
|
268
|
+
this.rootsPending.delete(m.id);
|
|
269
|
+
if (m.ok)
|
|
270
|
+
p.resolve([]);
|
|
271
|
+
else
|
|
272
|
+
p.reject(new Error(m.error || 'could not add that working directory'));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
122
275
|
case 'host-call':
|
|
123
276
|
void this.serveHostCall(m);
|
|
124
277
|
return;
|
|
@@ -138,12 +291,468 @@ export class SidecarHost {
|
|
|
138
291
|
this.httpPending.get(m.id)?.onErr(m.error);
|
|
139
292
|
return;
|
|
140
293
|
}
|
|
294
|
+
case 'acp-started': {
|
|
295
|
+
const p = this.acpPending.get(m.id);
|
|
296
|
+
// The start call resolves, but the entry STAYS so later acp-update
|
|
297
|
+
// frames on this id still reach the caller's stream handler.
|
|
298
|
+
p?.resolve({
|
|
299
|
+
runId: m.runId, sessionId: m.sessionId, agent: m.agent,
|
|
300
|
+
...(m.modes ? { modes: m.modes } : {}),
|
|
301
|
+
...(m.currentModeId ? { currentModeId: m.currentModeId } : {}),
|
|
302
|
+
...(m.resumed ? { resumed: true } : {}),
|
|
303
|
+
});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
case 'acp-mode-set': {
|
|
307
|
+
const p = this.acpPending.get(m.id);
|
|
308
|
+
this.acpPending.delete(m.id);
|
|
309
|
+
p?.resolve({ modeId: m.modeId });
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
case 'acp-update':
|
|
313
|
+
this.acpPending.get(m.id)?.onUpdate?.(m.update);
|
|
314
|
+
return;
|
|
315
|
+
case 'acp-done': {
|
|
316
|
+
const p = this.acpPending.get(m.id);
|
|
317
|
+
this.acpPending.delete(m.id);
|
|
318
|
+
p?.resolve({ stopReason: m.stopReason });
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
case 'acp-error': {
|
|
322
|
+
const p = this.acpPending.get(m.id);
|
|
323
|
+
this.acpPending.delete(m.id);
|
|
324
|
+
p?.reject(new Error(m.error));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
case 'run-accepted': {
|
|
328
|
+
const p = this.runPending.get(m.id);
|
|
329
|
+
if (p)
|
|
330
|
+
p.runId = m.runId;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
case 'event': {
|
|
334
|
+
this.runPending.get(m.id)?.onEvent(m.event);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
case 'run-done': {
|
|
338
|
+
const p = this.runPending.get(m.id);
|
|
339
|
+
if (!p)
|
|
340
|
+
return;
|
|
341
|
+
this.runPending.delete(m.id);
|
|
342
|
+
p.resolve({ runId: m.runId, text: m.text, stopReason: m.stopReason });
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
case 'run-error': {
|
|
346
|
+
const p = this.runPending.get(m.id);
|
|
347
|
+
if (!p)
|
|
348
|
+
return;
|
|
349
|
+
this.runPending.delete(m.id);
|
|
350
|
+
p.reject(new Error(m.error || 'run failed'));
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
case 'host-sync-result': {
|
|
354
|
+
const p = this.syncPending.get(m.id);
|
|
355
|
+
if (!p)
|
|
356
|
+
return;
|
|
357
|
+
this.syncPending.delete(m.id);
|
|
358
|
+
if (m.ok)
|
|
359
|
+
p.resolve();
|
|
360
|
+
else
|
|
361
|
+
p.reject(new Error(m.error || 'host sync failed'));
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
case 'permission-ask': {
|
|
365
|
+
const { t: _t, ...ask } = m;
|
|
366
|
+
this.opts.onPermissionAsk?.(ask);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
case 'permission-resolved': {
|
|
370
|
+
this.opts.onPermissionResolved?.({ id: m.id, decision: m.decision, by: m.by });
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
case 'run-activity': {
|
|
374
|
+
this.opts.onRunActivity?.(m.activity);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
case 'proc-started': {
|
|
378
|
+
const p = this.procPending.get(m.id);
|
|
379
|
+
if (!p)
|
|
380
|
+
return;
|
|
381
|
+
this.procPending.delete(m.id);
|
|
382
|
+
p.resolve(m.proc);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
case 'proc-error': {
|
|
386
|
+
const p = this.procPending.get(m.id);
|
|
387
|
+
if (!p)
|
|
388
|
+
return;
|
|
389
|
+
this.procPending.delete(m.id);
|
|
390
|
+
p.reject(new Error(m.error));
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
case 'proc-attach-result':
|
|
394
|
+
case 'proc-kill-result':
|
|
395
|
+
case 'proc-input-result': {
|
|
396
|
+
const p = this.procPending.get(m.id);
|
|
397
|
+
if (!p)
|
|
398
|
+
return;
|
|
399
|
+
this.procPending.delete(m.id);
|
|
400
|
+
if (m.ok)
|
|
401
|
+
p.resolve(m);
|
|
402
|
+
else
|
|
403
|
+
p.reject(new Error(m.error || 'process request failed'));
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
case 'proc-list-result': {
|
|
407
|
+
const p = this.procPending.get(m.id);
|
|
408
|
+
if (!p)
|
|
409
|
+
return;
|
|
410
|
+
this.procPending.delete(m.id);
|
|
411
|
+
p.resolve(m.procs);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
case 'proc-out': {
|
|
415
|
+
for (const w of this.procWatchers.get(m.procId) ?? [])
|
|
416
|
+
w.onOut?.({ seq: m.seq, stream: m.stream, text: m.text });
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
case 'proc-exit': {
|
|
420
|
+
const watchers = this.procWatchers.get(m.procId);
|
|
421
|
+
if (!watchers)
|
|
422
|
+
return;
|
|
423
|
+
// Delete BEFORE notifying: an onExit that re-spawns and re-watches must
|
|
424
|
+
// not have its new registration wiped by this cleanup.
|
|
425
|
+
this.procWatchers.delete(m.procId);
|
|
426
|
+
for (const w of watchers)
|
|
427
|
+
w.onExit?.({ code: m.code, ...(m.signal ? { signal: m.signal } : {}) });
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
case 'session-list-result': {
|
|
431
|
+
const p = this.sessionPending.get(m.id);
|
|
432
|
+
if (!p)
|
|
433
|
+
return;
|
|
434
|
+
this.sessionPending.delete(m.id);
|
|
435
|
+
p.resolve(m.sessions);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
case 'session-created': {
|
|
439
|
+
const p = this.sessionPending.get(m.id);
|
|
440
|
+
if (!p)
|
|
441
|
+
return;
|
|
442
|
+
this.sessionPending.delete(m.id);
|
|
443
|
+
p.resolve(m.session);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
case 'session-paused': {
|
|
447
|
+
const p = this.sessionPending.get(m.id);
|
|
448
|
+
if (!p)
|
|
449
|
+
return;
|
|
450
|
+
this.sessionPending.delete(m.id);
|
|
451
|
+
p.resolve({ drained: m.drained, remaining: m.remaining });
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
case 'session-committed': {
|
|
455
|
+
const p = this.sessionPending.get(m.id);
|
|
456
|
+
if (!p)
|
|
457
|
+
return;
|
|
458
|
+
this.sessionPending.delete(m.id);
|
|
459
|
+
p.resolve(m.snapshot);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
case 'run-transcript-result': {
|
|
463
|
+
const p = this.sessionPending.get(m.id);
|
|
464
|
+
if (!p)
|
|
465
|
+
return;
|
|
466
|
+
this.sessionPending.delete(m.id);
|
|
467
|
+
p.resolve(m.lines);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
case 'run-history-result': {
|
|
471
|
+
const p = this.sessionPending.get(m.id);
|
|
472
|
+
if (!p)
|
|
473
|
+
return;
|
|
474
|
+
this.sessionPending.delete(m.id);
|
|
475
|
+
p.resolve(m.runs);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
case 'session-snapshots-result': {
|
|
479
|
+
const p = this.sessionPending.get(m.id);
|
|
480
|
+
if (!p)
|
|
481
|
+
return;
|
|
482
|
+
this.sessionPending.delete(m.id);
|
|
483
|
+
p.resolve(m.snapshots);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
case 'session-ok': {
|
|
487
|
+
const p = this.sessionPending.get(m.id);
|
|
488
|
+
if (!p)
|
|
489
|
+
return;
|
|
490
|
+
this.sessionPending.delete(m.id);
|
|
491
|
+
p.resolve(undefined);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
case 'session-error': {
|
|
495
|
+
const p = this.sessionPending.get(m.id);
|
|
496
|
+
if (!p)
|
|
497
|
+
return;
|
|
498
|
+
this.sessionPending.delete(m.id);
|
|
499
|
+
p.reject(new Error(m.error));
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
case 'session-metrics': {
|
|
503
|
+
this.sessionWatchers.get(m.sessionId)?.onMetrics?.(m.sample);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
case 'session-log': {
|
|
507
|
+
this.sessionWatchers.get(m.sessionId)?.onLog?.(m.line);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
case 'registrations': {
|
|
511
|
+
this.opts.onRegistrations?.(m.registrations);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
141
514
|
default:
|
|
142
515
|
return; // hello / invoke / host-call-result are handled on the sidecar side
|
|
143
516
|
}
|
|
144
517
|
}
|
|
145
518
|
/** Whether the connected sidecar advertised the loopback HTTP proxy. */
|
|
146
519
|
supportsHttpProxy() { return this.features.includes('http-proxy'); }
|
|
520
|
+
/** Everything the sidecar advertised. A copy — the internal list must not be
|
|
521
|
+
* mutable from outside. For surfaces that SHOW capabilities; code that DEPENDS
|
|
522
|
+
* on one should use the specific supportsX() so the feature name lives here. */
|
|
523
|
+
advertisedFeatures() { return [...this.features]; }
|
|
524
|
+
/** Whether the connected sidecar advertised a wired crew runner — i.e. it can
|
|
525
|
+
* actually fire daemon-tier triggers (schedule/continuous/self-directed) on its
|
|
526
|
+
* own clock, not just serve tools. */
|
|
527
|
+
supportsRunner() { return this.features.includes('runner'); }
|
|
528
|
+
/** True when the daemon was started with --acp and can proxy an external
|
|
529
|
+
* agent. A client must check this before offering the surface: a daemon
|
|
530
|
+
* without it answers acp-start with an error. */
|
|
531
|
+
supportsAcp() { return this.features.includes('acp'); }
|
|
532
|
+
/** True when the daemon can run long-lived processes - i.e. the operator
|
|
533
|
+
* allowed shell. A client MUST check this before offering the surface: a
|
|
534
|
+
* daemon without it answers every proc message with an error. */
|
|
535
|
+
supportsProcs() { return this.features.includes('procs'); }
|
|
536
|
+
/** Start a long-running command on the sidecar and stream its output.
|
|
537
|
+
*
|
|
538
|
+
* This is the counterpart to the one-shot `sidecar_shell_exec` tool, not a
|
|
539
|
+
* replacement: use the tool when you want the result of something that
|
|
540
|
+
* finishes, and this when you want to WATCH something that may not.
|
|
541
|
+
*
|
|
542
|
+
* The returned promise settles on the spawn, not on the exit - the process is
|
|
543
|
+
* still running when it resolves. `watch` keeps receiving output until
|
|
544
|
+
* `onExit`, which fires exactly once.
|
|
545
|
+
*/
|
|
546
|
+
procSpawn(command, watch = {}, opts) {
|
|
547
|
+
const id = ++this.procSeq;
|
|
548
|
+
return new Promise((resolve, reject) => {
|
|
549
|
+
this.procPending.set(id, {
|
|
550
|
+
resolve: (v) => {
|
|
551
|
+
const info = v;
|
|
552
|
+
this.addProcWatcher(info.procId, watch);
|
|
553
|
+
resolve(info);
|
|
554
|
+
},
|
|
555
|
+
reject,
|
|
556
|
+
});
|
|
557
|
+
this.ch.send({
|
|
558
|
+
t: 'proc-spawn', id, command,
|
|
559
|
+
...(opts?.cwd ? { cwd: opts.cwd } : {}),
|
|
560
|
+
...(opts?.root ? { root: opts.root } : {}),
|
|
561
|
+
...(opts?.label ? { label: opts.label } : {}),
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
/** Re-attach to a process the daemon is already running - after a reload, or
|
|
566
|
+
* from a second tab. `sinceSeq` is the last chunk this client already has, so
|
|
567
|
+
* the replay is exactly the rest; omit it to replay the whole buffered tail.
|
|
568
|
+
*
|
|
569
|
+
* `truncated` in the result means the daemon's tail no longer reaches back to
|
|
570
|
+
* where you were: there is a GAP, and a UI that renders the replay as if it
|
|
571
|
+
* were continuous would be lying about what the process printed. */
|
|
572
|
+
procAttach(procId, watch = {}, sinceSeq) {
|
|
573
|
+
const id = ++this.procSeq;
|
|
574
|
+
return new Promise((resolve, reject) => {
|
|
575
|
+
this.procPending.set(id, {
|
|
576
|
+
// Register BEFORE resolving so output the daemon replays immediately
|
|
577
|
+
// after the result cannot arrive ahead of the watcher.
|
|
578
|
+
resolve: (v) => {
|
|
579
|
+
const r = v;
|
|
580
|
+
if (!r.proc) {
|
|
581
|
+
reject(new Error('attach returned no process'));
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
this.addProcWatcher(procId, watch);
|
|
585
|
+
resolve({ proc: r.proc, truncated: r.truncated === true });
|
|
586
|
+
},
|
|
587
|
+
reject,
|
|
588
|
+
});
|
|
589
|
+
this.ch.send({ t: 'proc-attach', id, procId, ...(sinceSeq === undefined ? {} : { sinceSeq }) });
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
/** Everything the daemon is running, or recently ran. Called on attach: a
|
|
593
|
+
* process nobody can see is worse than one that died. */
|
|
594
|
+
procList() {
|
|
595
|
+
const id = ++this.procSeq;
|
|
596
|
+
return new Promise((resolve, reject) => {
|
|
597
|
+
this.procPending.set(id, { resolve: (v) => resolve(v), reject });
|
|
598
|
+
this.ch.send({ t: 'proc-list', id });
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
/** Write to a running process's stdin. */
|
|
602
|
+
procInput(procId, text) {
|
|
603
|
+
const id = ++this.procSeq;
|
|
604
|
+
return new Promise((resolve, reject) => {
|
|
605
|
+
this.procPending.set(id, { resolve: () => resolve(), reject });
|
|
606
|
+
this.ch.send({ t: 'proc-input', id, procId, text });
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
/** Stop a process. Idempotent on the daemon side. */
|
|
610
|
+
procKill(procId) {
|
|
611
|
+
const id = ++this.procSeq;
|
|
612
|
+
return new Promise((resolve, reject) => {
|
|
613
|
+
this.procPending.set(id, { resolve: () => resolve(), reject });
|
|
614
|
+
this.ch.send({ t: 'proc-kill', id, procId });
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
/** Stop receiving a process's output without stopping the process. */
|
|
618
|
+
procUnwatch(procId, watch) {
|
|
619
|
+
const set = this.procWatchers.get(procId);
|
|
620
|
+
if (!set)
|
|
621
|
+
return;
|
|
622
|
+
set.delete(watch);
|
|
623
|
+
if (!set.size)
|
|
624
|
+
this.procWatchers.delete(procId);
|
|
625
|
+
}
|
|
626
|
+
addProcWatcher(procId, watch) {
|
|
627
|
+
const set = this.procWatchers.get(procId) ?? new Set();
|
|
628
|
+
set.add(watch);
|
|
629
|
+
this.procWatchers.set(procId, set);
|
|
630
|
+
}
|
|
631
|
+
/** True when the daemon runs sessions at all. A client MUST check this before
|
|
632
|
+
* offering the surface: a daemon without it answers every session message
|
|
633
|
+
* with an error. */
|
|
634
|
+
supportsSessions() { return this.features.includes('sessions'); }
|
|
635
|
+
/**
|
|
636
|
+
* Whether the OPERATOR allowed run transcripts to be kept at all.
|
|
637
|
+
*
|
|
638
|
+
* False means no run on this daemon can be recorded however it asks, so a
|
|
639
|
+
* surface must not offer the choice — a control that cannot be honoured is
|
|
640
|
+
* worse than an absent one. True does NOT mean recording is on: each run
|
|
641
|
+
* still opts in (`run(..., { keepTranscript: true })`).
|
|
642
|
+
*/
|
|
643
|
+
supportsKeepTranscripts() { return this.features.includes('keep-transcripts'); }
|
|
644
|
+
/** Every session the daemon knows about. */
|
|
645
|
+
sessionList() {
|
|
646
|
+
return this.sessionCall((id) => ({ t: 'session-list', id }));
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Create a session on a directory, or FROM a repository.
|
|
650
|
+
*
|
|
651
|
+
* Exactly one of `workdir` and `repo`. With `repo` the daemon clones on its
|
|
652
|
+
* own host, using its own git credentials, and mounts the result — which is
|
|
653
|
+
* why the caller cannot name the directory: it does not exist yet. The
|
|
654
|
+
* returned SessionInfo carries `source` with the resolved commit.
|
|
655
|
+
*/
|
|
656
|
+
sessionCreate(spec) {
|
|
657
|
+
return this.sessionCall((id) => ({
|
|
658
|
+
t: 'session-create', id,
|
|
659
|
+
...(spec.workdir ? { workdir: spec.workdir } : {}),
|
|
660
|
+
...(spec.repo ? { repo: spec.repo } : {}),
|
|
661
|
+
...(spec.owner ? { owner: spec.owner } : {}),
|
|
662
|
+
...(spec.fromSnapshot ? { fromSnapshot: spec.fromSnapshot } : {}),
|
|
663
|
+
}));
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Whether this daemon can create a session FROM a repository.
|
|
667
|
+
*
|
|
668
|
+
* False means the operator declared no `--clone-under` directory, so there is
|
|
669
|
+
* nowhere a checkout could be written and the option must not be offered.
|
|
670
|
+
*/
|
|
671
|
+
supportsSessionClone() { return this.features.includes('session-clone'); }
|
|
672
|
+
/**
|
|
673
|
+
* Pause a session, and learn whether it actually drained.
|
|
674
|
+
*
|
|
675
|
+
* `drained: false` means work was STILL RUNNING when the wait gave up. A
|
|
676
|
+
* caller about to capture needs that: it is the difference between a snapshot
|
|
677
|
+
* and a torn one.
|
|
678
|
+
*/
|
|
679
|
+
sessionPause(sessionId, drainMs) {
|
|
680
|
+
return this.sessionCall((id) => ({
|
|
681
|
+
t: 'session-pause', id, sessionId, ...(drainMs === undefined ? {} : { drainMs }),
|
|
682
|
+
}));
|
|
683
|
+
}
|
|
684
|
+
sessionResume(sessionId) {
|
|
685
|
+
return this.sessionCall((id) => ({ t: 'session-resume', id, sessionId }));
|
|
686
|
+
}
|
|
687
|
+
sessionDestroy(sessionId) {
|
|
688
|
+
return this.sessionCall((id) => ({ t: 'session-destroy', id, sessionId }));
|
|
689
|
+
}
|
|
690
|
+
sessionCommit(sessionId, name) {
|
|
691
|
+
return this.sessionCall((id) => ({
|
|
692
|
+
t: 'session-commit', id, sessionId, ...(name ? { name } : {}),
|
|
693
|
+
}));
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* One agent's finished runs, newest first, as the DAEMON recorded them.
|
|
697
|
+
*
|
|
698
|
+
* The durable counterpart to the live `run-activity` broadcasts: those say
|
|
699
|
+
* what is happening while this browser is attached and are lost on
|
|
700
|
+
* disconnect, this says what happened at all. Scoped to this client by the
|
|
701
|
+
* daemon, from the identity the connection authenticated with.
|
|
702
|
+
*
|
|
703
|
+
* An empty list means no record, which a caller renders rather than treats
|
|
704
|
+
* as an error — a daemon that keeps none is a normal daemon.
|
|
705
|
+
*/
|
|
706
|
+
runHistory(agent, limit) {
|
|
707
|
+
return this.sessionCall((id) => ({
|
|
708
|
+
t: 'run-history', id, agent, ...(limit === undefined ? {} : { limit }),
|
|
709
|
+
}));
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* One run's kept events, as the daemon stored them.
|
|
713
|
+
*
|
|
714
|
+
* Empty means the words were never kept — the default, since transcripts are
|
|
715
|
+
* opt-in. A caller decides what to show from `RunRecordInfo.transcript`,
|
|
716
|
+
* which says whether there was ever anything to fetch.
|
|
717
|
+
*/
|
|
718
|
+
runTranscript(agent, runId) {
|
|
719
|
+
return this.sessionCall((id) => ({ t: 'run-transcript', id, agent, runId }));
|
|
720
|
+
}
|
|
721
|
+
/** Every capture this daemon could restore from, newest first.
|
|
722
|
+
*
|
|
723
|
+
* An empty list is a real answer — a daemon whose backend cannot capture has
|
|
724
|
+
* nothing to restore from — so a caller uses the length to decide whether to
|
|
725
|
+
* offer the choice, not to detect a failure. */
|
|
726
|
+
sessionSnapshots() {
|
|
727
|
+
return this.sessionCall((id) => ({ t: 'session-snapshots', id }));
|
|
728
|
+
}
|
|
729
|
+
/** Start receiving a session's metrics and logs. */
|
|
730
|
+
async sessionWatch(sessionId, watch) {
|
|
731
|
+
// Registered BEFORE the request, so a sample that arrives immediately after
|
|
732
|
+
// the daemon starts following is not dropped on the floor.
|
|
733
|
+
this.sessionWatchers.set(sessionId, watch);
|
|
734
|
+
try {
|
|
735
|
+
await this.sessionCall((id) => ({ t: 'session-watch', id, sessionId }));
|
|
736
|
+
}
|
|
737
|
+
catch (e) {
|
|
738
|
+
this.sessionWatchers.delete(sessionId);
|
|
739
|
+
throw e;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
async sessionUnwatch(sessionId) {
|
|
743
|
+
this.sessionWatchers.delete(sessionId);
|
|
744
|
+
await this.sessionCall((id) => ({ t: 'session-unwatch', id, sessionId }));
|
|
745
|
+
}
|
|
746
|
+
sessionCall(build) {
|
|
747
|
+
const id = ++this.sessionSeq;
|
|
748
|
+
return new Promise((resolve, reject) => {
|
|
749
|
+
this.sessionPending.set(id, { resolve, reject });
|
|
750
|
+
this.ch.send(build(id));
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
/** The sidecar's advertised self-description (root/platform), or {} if it sent
|
|
754
|
+
* none. Valid only after connect() resolves. */
|
|
755
|
+
sidecarInfo() { return this.info; }
|
|
147
756
|
/** Fetch a (loopback) URL THROUGH the sidecar — a server-side fetch on the user's
|
|
148
757
|
* machine, so the browser dodges CORS. Returns a streaming Response: it resolves
|
|
149
758
|
* as soon as headers arrive (http-res) and the body streams in via http-chunk. */
|
|
@@ -177,6 +786,121 @@ export class SidecarHost {
|
|
|
177
786
|
this.ch.send({ t: 'http-req', id, method: req.method, url: req.url, headers: req.headers, body: req.body });
|
|
178
787
|
});
|
|
179
788
|
}
|
|
789
|
+
/** Command the sidecar to run a crew agent, streaming its lifecycle events to
|
|
790
|
+
* `onEvent` as they arrive. Resolves once the run completes (`run-done`) with
|
|
791
|
+
* the daemon-assigned runId, final text, and stop reason; rejects on
|
|
792
|
+
* `run-error` or if the connection drops while the run is in flight. */
|
|
793
|
+
run(agent, input, onEvent, opts) {
|
|
794
|
+
const id = ++this.runSeq;
|
|
795
|
+
return new Promise((resolve, reject) => {
|
|
796
|
+
this.runPending.set(id, { resolve, reject, onEvent });
|
|
797
|
+
// `sessionId` asks for the run to happen INSIDE that session. A daemon
|
|
798
|
+
// that cannot honour it answers run-error rather than running it on the
|
|
799
|
+
// host, so the caller learns rather than being quietly downgraded.
|
|
800
|
+
// Sent only when asked for, so an unset option is indistinguishable on the
|
|
801
|
+
// wire from an older client that never had one — both mean keep nothing.
|
|
802
|
+
this.ch.send({
|
|
803
|
+
t: 'run', id, agent, input,
|
|
804
|
+
...(opts?.sessionId ? { sessionId: opts.sessionId } : {}),
|
|
805
|
+
...(opts?.keepTranscript ? { keepTranscript: true } : {}),
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
/** Start a proxied external ACP agent on the sidecar. The returned promise
|
|
810
|
+
* settles on the handshake; `onUpdate` keeps receiving the agent's stream for
|
|
811
|
+
* the life of the session. Permission asks do NOT come through here — they use
|
|
812
|
+
* the existing permission-ask broadcast, the same path a crew run uses. */
|
|
813
|
+
acpStart(target, onUpdate, opts) {
|
|
814
|
+
const id = ++this.acpSeq;
|
|
815
|
+
return new Promise((resolve, reject) => {
|
|
816
|
+
this.acpPending.set(id, { resolve: resolve, reject, onUpdate });
|
|
817
|
+
// `root` is the working directory the USER has active. The daemon checks it
|
|
818
|
+
// against the operator's approved set and refuses anything else, so naming
|
|
819
|
+
// one here is a request, not a grant.
|
|
820
|
+
// `modeId` is a mode the user picked BEFORE a session existed - modes are
|
|
821
|
+
// only known after session/new, so an early choice has to be carried into
|
|
822
|
+
// the start rather than applied after it. `resume` asks the agent to
|
|
823
|
+
// reattach to a session it may or may not still hold.
|
|
824
|
+
this.ch.send({
|
|
825
|
+
t: 'acp-start', id, target,
|
|
826
|
+
...(opts?.root ? { root: opts.root } : {}),
|
|
827
|
+
...(opts?.modeId ? { modeId: opts.modeId } : {}),
|
|
828
|
+
...(opts?.resume ? { resume: opts.resume } : {}),
|
|
829
|
+
});
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
/** Send one turn to the proxied agent. */
|
|
833
|
+
acpPrompt(text, content) {
|
|
834
|
+
const id = ++this.acpSeq;
|
|
835
|
+
return new Promise((resolve, reject) => {
|
|
836
|
+
this.acpPending.set(id, { resolve: resolve, reject });
|
|
837
|
+
this.ch.send({ t: 'acp-prompt', id, text, ...(content?.length ? { content: [...content] } : {}) });
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
/** Switch the proxied agent's mode. Rejects when it does not offer that mode. */
|
|
841
|
+
acpSetMode(modeId) {
|
|
842
|
+
const id = ++this.acpSeq;
|
|
843
|
+
return new Promise((resolve, reject) => {
|
|
844
|
+
this.acpPending.set(id, { resolve: resolve, reject });
|
|
845
|
+
this.ch.send({ t: 'acp-set-mode', id, modeId });
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
/** Interrupt the current turn. Fire-and-forget, like cancel in ACP itself. */
|
|
849
|
+
acpCancel() {
|
|
850
|
+
this.ch.send({ t: 'acp-cancel', id: ++this.acpSeq });
|
|
851
|
+
}
|
|
852
|
+
/** Answer an outstanding permission ask. Fire-and-forget over the wire — the
|
|
853
|
+
* actual drop of the prompt is driven by the server's `permission-resolved`
|
|
854
|
+
* broadcast (`onPermissionResolved`), not by this call returning. */
|
|
855
|
+
answerPermission(id, decision) {
|
|
856
|
+
this.ch.send({ t: 'permission-answer', id, decision });
|
|
857
|
+
}
|
|
858
|
+
/** Push one definition file's content to the daemon, to be persisted under
|
|
859
|
+
* this client's own `<stateDir>/hosts/<dirName>/` (Slice 2 host sync).
|
|
860
|
+
* Resolves on `host-sync-result { ok: true }`; rejects with the daemon's
|
|
861
|
+
* error, or on disconnect while in flight. */
|
|
862
|
+
hostSyncPut(path, content) {
|
|
863
|
+
const id = ++this.syncSeq;
|
|
864
|
+
return new Promise((resolve, reject) => {
|
|
865
|
+
this.syncPending.set(id, { resolve, reject });
|
|
866
|
+
this.ch.send({ t: 'host-sync-put', id, path, content });
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
/** Delete one definition file on the daemon side (reconcile). Same
|
|
870
|
+
* resolve/reject contract as `hostSyncPut`. Callers are responsible for
|
|
871
|
+
* confining `path` to definition, non-state paths BEFORE calling this --
|
|
872
|
+
* this method has no opinion on what `path` is. */
|
|
873
|
+
hostSyncDelete(path) {
|
|
874
|
+
const id = ++this.syncSeq;
|
|
875
|
+
return new Promise((resolve, reject) => {
|
|
876
|
+
this.syncPending.set(id, { resolve, reject });
|
|
877
|
+
this.ch.send({ t: 'host-sync-delete', id, path });
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
/** True when the operator started the sidecar with --allow-add-under, so a client
|
|
881
|
+
* may add a working directory this session. Feature-detected from the welcome --
|
|
882
|
+
* an older sidecar simply never advertises it. */
|
|
883
|
+
canAddRoots() { return this.features.includes('add-roots'); }
|
|
884
|
+
/** Directory NAMES under `path`, or the declared areas themselves when `path` is
|
|
885
|
+
* omitted. Names only: never files, contents or stats. Rejects with the sidecar's
|
|
886
|
+
* message (e.g. outside every allowed area). */
|
|
887
|
+
browseRoots(path) {
|
|
888
|
+
const id = ++this.rootsSeq;
|
|
889
|
+
return new Promise((resolve, reject) => {
|
|
890
|
+
this.rootsPending.set(id, { resolve, reject });
|
|
891
|
+
this.ch.send(path === undefined ? { t: 'roots-browse', id } : { t: 'roots-browse', id, path });
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
/** Promote `path` to a working directory for the rest of the session. Resolves on
|
|
895
|
+
* acceptance -- the resulting set arrives separately via `onRoots`, broadcast to
|
|
896
|
+
* every connected client -- and rejects with the sidecar's refusal message. */
|
|
897
|
+
async addRoot(path) {
|
|
898
|
+
const id = ++this.rootsSeq;
|
|
899
|
+
await new Promise((resolve, reject) => {
|
|
900
|
+
this.rootsPending.set(id, { resolve, reject });
|
|
901
|
+
this.ch.send({ t: 'roots-add', id, path });
|
|
902
|
+
});
|
|
903
|
+
}
|
|
180
904
|
invoke(name, args) {
|
|
181
905
|
const id = ++this.invokeSeq;
|
|
182
906
|
return new Promise((resolve, reject) => {
|