@di-code/coding-agent 0.2.1 → 0.2.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/README.md +5 -0
- package/dist/core/session/session-manager.d.ts +18 -1
- package/dist/core/session/session-manager.d.ts.map +1 -1
- package/dist/core/session/session-manager.js +90 -1
- package/dist/core/session/session-manager.js.map +1 -1
- package/dist/core/session/session-storage.d.ts +2 -0
- package/dist/core/session/session-storage.d.ts.map +1 -1
- package/dist/core/session/session-storage.js +22 -1
- package/dist/core/session/session-storage.js.map +1 -1
- package/dist/core/session/types.d.ts +16 -1
- package/dist/core/session/types.d.ts.map +1 -1
- package/dist/core/session/types.js.map +1 -1
- package/dist/core/session.d.ts +27 -2
- package/dist/core/session.d.ts.map +1 -1
- package/dist/core/session.js +59 -0
- package/dist/core/session.js.map +1 -1
- package/dist/entry.js +1 -1
- package/dist/entry.js.map +1 -1
- package/dist/i18n.js +4 -4
- package/dist/i18n.js.map +1 -1
- package/dist/interactive-host-entry.d.ts.map +1 -1
- package/dist/interactive-host-entry.js +107 -12
- package/dist/interactive-host-entry.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +3 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive-components.d.ts +8 -0
- package/dist/modes/interactive-components.d.ts.map +1 -1
- package/dist/modes/interactive-components.js +7 -2
- package/dist/modes/interactive-components.js.map +1 -1
- package/dist/modes/interactive.d.ts +4 -0
- package/dist/modes/interactive.d.ts.map +1 -1
- package/dist/modes/interactive.js +69 -0
- package/dist/modes/interactive.js.map +1 -1
- package/dist/modes/tree-selector.d.ts.map +1 -1
- package/dist/modes/tree-selector.js +4 -0
- package/dist/modes/tree-selector.js.map +1 -1
- package/dist/rpc/client.d.ts +2 -0
- package/dist/rpc/client.d.ts.map +1 -1
- package/dist/rpc/client.js +3 -0
- package/dist/rpc/client.js.map +1 -1
- package/dist/rpc/dispatcher.d.ts +14 -0
- package/dist/rpc/dispatcher.d.ts.map +1 -1
- package/dist/rpc/dispatcher.js +222 -7
- package/dist/rpc/dispatcher.js.map +1 -1
- package/dist/rpc/protocol.d.ts +12 -5
- package/dist/rpc/protocol.d.ts.map +1 -1
- package/dist/rpc/protocol.js +34 -0
- package/dist/rpc/protocol.js.map +1 -1
- package/dist/rpc/server.d.ts +5 -0
- package/dist/rpc/server.d.ts.map +1 -1
- package/dist/rpc/server.js +4 -0
- package/dist/rpc/server.js.map +1 -1
- package/dist/rpc-server-entry.d.ts.map +1 -1
- package/dist/rpc-server-entry.js +15 -1
- package/dist/rpc-server-entry.js.map +1 -1
- package/dist/runtime/product-host.d.ts +7 -0
- package/dist/runtime/product-host.d.ts.map +1 -1
- package/dist/runtime/product-host.js +29 -9
- package/dist/runtime/product-host.js.map +1 -1
- package/dist/runtime/session-factory.d.ts +18 -2
- package/dist/runtime/session-factory.d.ts.map +1 -1
- package/dist/runtime/session-factory.js +286 -24
- package/dist/runtime/session-factory.js.map +1 -1
- package/dist/runtime/session-host.d.ts +46 -1
- package/dist/runtime/session-host.d.ts.map +1 -1
- package/dist/runtime/session-host.js +38 -6
- package/dist/runtime/session-host.js.map +1 -1
- package/dist/ui-host.d.ts.map +1 -1
- package/dist/ui-host.js +3 -0
- package/dist/ui-host.js.map +1 -1
- package/dist/web/assets/index-C2SOU925.css +1 -0
- package/dist/web/assets/index-knXkInvO.js +41 -0
- package/dist/web/assets/index-knXkInvO.js.map +1 -0
- package/dist/web/index.html +2 -2
- package/dist/web-command.d.ts.map +1 -1
- package/dist/web-command.js +8 -1
- package/dist/web-command.js.map +1 -1
- package/dist/webui-entry.js +9 -1
- package/dist/webui-entry.js.map +1 -1
- package/dist/webui.d.ts +10 -0
- package/dist/webui.d.ts.map +1 -1
- package/dist/webui.js +21 -0
- package/dist/webui.js.map +1 -1
- package/package.json +11 -9
- package/dist/web/assets/index-C-NyCPxd.css +0 -1
- package/dist/web/assets/index-DgyxuNNG.js +0 -41
- package/dist/web/assets/index-DgyxuNNG.js.map +0 -1
package/dist/rpc/dispatcher.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { redactSensitiveText } from "@di-code/plugin-runtime";
|
|
3
3
|
import { RPC_PROTOCOL_VERSION, RpcProtocolError, } from "./protocol.js";
|
|
4
|
+
const MAX_PROJECTION_BYTES = 256 * 1024;
|
|
5
|
+
const FORBIDDEN_PROJECTION_KEYS = /(?:^|[_-])(path|cwd|root|token|secret|authorization|api[_-]?key)(?:$|[_-])/iu;
|
|
4
6
|
function transcriptEntryIds(tree, leafId) {
|
|
5
7
|
if (!leafId)
|
|
6
8
|
return [];
|
|
@@ -108,6 +110,7 @@ export class RpcDispatcher {
|
|
|
108
110
|
methods;
|
|
109
111
|
onError;
|
|
110
112
|
approvals = new Map();
|
|
113
|
+
interactions = new Map();
|
|
111
114
|
maxEvents;
|
|
112
115
|
productState;
|
|
113
116
|
productHost;
|
|
@@ -168,6 +171,33 @@ export class RpcDispatcher {
|
|
|
168
171
|
this.listeners.add(listener);
|
|
169
172
|
return () => this.listeners.delete(listener);
|
|
170
173
|
}
|
|
174
|
+
/** Publishes a versioned projection only to clients that negotiated projection events. */
|
|
175
|
+
emitProjection(input) {
|
|
176
|
+
if (!this.negotiatedEvents.has("projection"))
|
|
177
|
+
return;
|
|
178
|
+
if (!isBrowserSafeProjection(input.state)) {
|
|
179
|
+
this.onError(new Error("Skipped an unsafe extension projection."));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const record = {
|
|
183
|
+
version: RPC_PROTOCOL_VERSION,
|
|
184
|
+
kind: "event",
|
|
185
|
+
requestId: input.requestId ?? "projection",
|
|
186
|
+
sessionId: this.activeSessionId(),
|
|
187
|
+
sequence: ++this.sequence,
|
|
188
|
+
event: {
|
|
189
|
+
type: "projection",
|
|
190
|
+
namespace: input.namespace,
|
|
191
|
+
projectionName: input.projectionName,
|
|
192
|
+
version: input.version,
|
|
193
|
+
state: structuredClone(input.state),
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
this.events.push(record);
|
|
197
|
+
if (this.events.length > this.maxEvents)
|
|
198
|
+
this.events.shift();
|
|
199
|
+
this.emit(record);
|
|
200
|
+
}
|
|
171
201
|
async dispatch(request) {
|
|
172
202
|
this.pruneOperations();
|
|
173
203
|
if (this.disposed)
|
|
@@ -203,6 +233,16 @@ export class RpcDispatcher {
|
|
|
203
233
|
for (const operation of this.operations.values())
|
|
204
234
|
if (operation.status === "queued" || operation.status === "running")
|
|
205
235
|
operation.controller.abort();
|
|
236
|
+
for (const pending of this.approvals.values())
|
|
237
|
+
pending.resolve(false);
|
|
238
|
+
this.approvals.clear();
|
|
239
|
+
for (const pending of this.interactions.values())
|
|
240
|
+
pending.resolve({
|
|
241
|
+
requestId: pending.request.requestId,
|
|
242
|
+
toolCallId: pending.request.toolCallId,
|
|
243
|
+
status: "disposed",
|
|
244
|
+
});
|
|
245
|
+
this.interactions.clear();
|
|
206
246
|
await Promise.allSettled([...this.operations.values()].map((operation) => operation.promise));
|
|
207
247
|
await this.attachments.dispose();
|
|
208
248
|
this.listeners.clear();
|
|
@@ -256,13 +296,19 @@ export class RpcDispatcher {
|
|
|
256
296
|
});
|
|
257
297
|
case "get_usage":
|
|
258
298
|
return this.success(request.id, { method: "get_usage", usage: this.host().usage() });
|
|
259
|
-
case "list_skills":
|
|
299
|
+
case "list_skills": {
|
|
300
|
+
const host = this.host();
|
|
301
|
+
const ui = host.ui();
|
|
302
|
+
const skills = ui.availableSkills.map(({ name, description, scope }) => ({ name, description, scope }));
|
|
303
|
+
const projectResourcesDetected = host
|
|
304
|
+
.startupStatus()
|
|
305
|
+
.resourceDiagnostics.some((diagnostic) => diagnostic.kind === "skill" && diagnostic.stage === "trust");
|
|
260
306
|
return this.success(request.id, {
|
|
261
307
|
method: "list_skills",
|
|
262
|
-
skills
|
|
263
|
-
|
|
264
|
-
.availableSkills.map(({ name, description, scope }) => ({ name, description, scope })),
|
|
308
|
+
skills,
|
|
309
|
+
projectResourcesDetected,
|
|
265
310
|
});
|
|
311
|
+
}
|
|
266
312
|
case "get_resources":
|
|
267
313
|
return this.success(request.id, {
|
|
268
314
|
method: "get_resources",
|
|
@@ -331,6 +377,22 @@ export class RpcDispatcher {
|
|
|
331
377
|
approvalId: request.params.approvalId,
|
|
332
378
|
approved: request.params.approved,
|
|
333
379
|
});
|
|
380
|
+
case "respond_interaction": {
|
|
381
|
+
const interactionId = request.params.requestId;
|
|
382
|
+
const pending = this.interactions.get(interactionId);
|
|
383
|
+
if (pending) {
|
|
384
|
+
this.interactions.delete(interactionId);
|
|
385
|
+
pending.resolve({
|
|
386
|
+
requestId: interactionId,
|
|
387
|
+
toolCallId: pending.request.toolCallId,
|
|
388
|
+
status: request.params.status,
|
|
389
|
+
...(typeof request.params.approved === "boolean" ? { approved: request.params.approved } : {}),
|
|
390
|
+
...(typeof request.params.value === "string" ? { value: request.params.value } : {}),
|
|
391
|
+
...(typeof request.params.feedback === "string" ? { feedback: request.params.feedback } : {}),
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
return this.success(request.id, { method: "respond_interaction", requestId: interactionId });
|
|
395
|
+
}
|
|
334
396
|
case "run_command":
|
|
335
397
|
return this.failure(request.id, "METHOD_NOT_FOUND", "Command execution requires operation dispatch.");
|
|
336
398
|
default:
|
|
@@ -343,8 +405,12 @@ export class RpcDispatcher {
|
|
|
343
405
|
}
|
|
344
406
|
/** Waits for the browser to approve a specific tool invocation. */
|
|
345
407
|
async requestToolApproval(toolName, parameters, signal) {
|
|
346
|
-
if (!this.negotiatedEvents.has("tool_approval"))
|
|
347
|
-
return
|
|
408
|
+
if (!this.negotiatedEvents.has("tool_approval") && !this.negotiatedEvents.has("interaction_request"))
|
|
409
|
+
return false;
|
|
410
|
+
if (this.negotiatedEvents.has("interaction_request")) {
|
|
411
|
+
const result = await this.requestInteraction({ kind: "approval", prompt: `Allow tool ${toolName}?`, toolCallId: randomUUID(), intent: "tool-approval" }, signal);
|
|
412
|
+
return result.status === "answered" && result.approved === true;
|
|
413
|
+
}
|
|
348
414
|
const approvalId = randomUUID();
|
|
349
415
|
const requestId = [...this.operations.values()].find((operation) => operation.status === "running")?.requestId ?? approvalId;
|
|
350
416
|
return await new Promise((resolve, reject) => {
|
|
@@ -361,7 +427,7 @@ export class RpcDispatcher {
|
|
|
361
427
|
resolve(approved);
|
|
362
428
|
},
|
|
363
429
|
});
|
|
364
|
-
|
|
430
|
+
const record = {
|
|
365
431
|
version: RPC_PROTOCOL_VERSION,
|
|
366
432
|
kind: "event",
|
|
367
433
|
requestId,
|
|
@@ -373,7 +439,71 @@ export class RpcDispatcher {
|
|
|
373
439
|
toolName,
|
|
374
440
|
arguments: typeof parameters === "object" && parameters !== null ? parameters : {},
|
|
375
441
|
},
|
|
442
|
+
};
|
|
443
|
+
this.events.push(record);
|
|
444
|
+
if (this.events.length > this.maxEvents)
|
|
445
|
+
this.events.shift();
|
|
446
|
+
this.emit(record);
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
/** Requests a structured user response through the negotiated interaction channel. */
|
|
450
|
+
async requestInteraction(input, signal) {
|
|
451
|
+
if (!this.negotiatedEvents.has("interaction_request")) {
|
|
452
|
+
throw Object.assign(new Error("No user interaction channel is available."), { code: "INTERACTION_UNAVAILABLE" });
|
|
453
|
+
}
|
|
454
|
+
const requestId = input.requestId ?? randomUUID();
|
|
455
|
+
const request = { ...input, requestId };
|
|
456
|
+
const existing = this.interactions.get(requestId);
|
|
457
|
+
if (existing)
|
|
458
|
+
return await new Promise((resolve) => {
|
|
459
|
+
const original = existing.resolve;
|
|
460
|
+
existing.resolve = (result) => {
|
|
461
|
+
original(result);
|
|
462
|
+
resolve(result);
|
|
463
|
+
};
|
|
464
|
+
});
|
|
465
|
+
return await new Promise((resolve, reject) => {
|
|
466
|
+
let timer;
|
|
467
|
+
const abort = () => {
|
|
468
|
+
this.interactions.delete(requestId);
|
|
469
|
+
if (timer !== undefined)
|
|
470
|
+
clearTimeout(timer);
|
|
471
|
+
reject(Object.assign(new Error("User interaction was cancelled."), { code: "CANCELLED" }));
|
|
472
|
+
};
|
|
473
|
+
if (signal?.aborted)
|
|
474
|
+
return abort();
|
|
475
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
476
|
+
this.interactions.set(requestId, {
|
|
477
|
+
request,
|
|
478
|
+
resolve: (result) => {
|
|
479
|
+
signal?.removeEventListener("abort", abort);
|
|
480
|
+
if (timer !== undefined)
|
|
481
|
+
clearTimeout(timer);
|
|
482
|
+
resolve(result);
|
|
483
|
+
},
|
|
376
484
|
});
|
|
485
|
+
if (input.timeoutMs !== undefined) {
|
|
486
|
+
timer = setTimeout(() => {
|
|
487
|
+
const current = this.interactions.get(requestId);
|
|
488
|
+
if (!current)
|
|
489
|
+
return;
|
|
490
|
+
this.interactions.delete(requestId);
|
|
491
|
+
signal?.removeEventListener("abort", abort);
|
|
492
|
+
resolve({ requestId, toolCallId: input.toolCallId, status: "timeout" });
|
|
493
|
+
}, input.timeoutMs);
|
|
494
|
+
}
|
|
495
|
+
const record = {
|
|
496
|
+
version: RPC_PROTOCOL_VERSION,
|
|
497
|
+
kind: "event",
|
|
498
|
+
requestId,
|
|
499
|
+
sessionId: this.activeSessionId(),
|
|
500
|
+
sequence: ++this.sequence,
|
|
501
|
+
event: { type: "interaction_request", interactionRequestId: requestId, ...request },
|
|
502
|
+
};
|
|
503
|
+
this.events.push(record);
|
|
504
|
+
if (this.events.length > this.maxEvents)
|
|
505
|
+
this.events.shift();
|
|
506
|
+
this.emit(record);
|
|
377
507
|
});
|
|
378
508
|
}
|
|
379
509
|
async runOperation(operation, request) {
|
|
@@ -729,11 +859,27 @@ export class RpcDispatcher {
|
|
|
729
859
|
description: skill.description,
|
|
730
860
|
kind: "skill",
|
|
731
861
|
}));
|
|
862
|
+
if (sessionHost(this.session) && !commands.some((command) => command.name === "plan")) {
|
|
863
|
+
commands.push({ name: "plan", description: "Enter or leave plan mode.", kind: "command" });
|
|
864
|
+
}
|
|
732
865
|
return [...commands, ...skills].sort((left, right) => left.name.localeCompare(right.name));
|
|
733
866
|
}
|
|
734
867
|
async runCommand(request, signal) {
|
|
735
868
|
const name = request.params.name;
|
|
736
869
|
const args = request.params.args ?? "";
|
|
870
|
+
if (name === "plan" && sessionHost(this.session)) {
|
|
871
|
+
await this.session.planCommand(args);
|
|
872
|
+
const projection = this.session.planMode();
|
|
873
|
+
if (projection)
|
|
874
|
+
this.emitProjection({
|
|
875
|
+
namespace: "plan",
|
|
876
|
+
projectionName: "mode",
|
|
877
|
+
version: 1,
|
|
878
|
+
state: projection,
|
|
879
|
+
requestId: request.id,
|
|
880
|
+
});
|
|
881
|
+
return { method: "run_command", command: name, action: { command: name, args } };
|
|
882
|
+
}
|
|
737
883
|
if (!this.commandRegistry?.list().some((command) => command.name === name))
|
|
738
884
|
throw new RpcProtocolError("NOT_FOUND", `Unknown command: /${name}`, request.id);
|
|
739
885
|
let action;
|
|
@@ -765,9 +911,32 @@ export class RpcDispatcher {
|
|
|
765
911
|
"snapshot_required",
|
|
766
912
|
"session_changed",
|
|
767
913
|
"tool_approval",
|
|
914
|
+
"interaction_request",
|
|
768
915
|
"product_audit",
|
|
916
|
+
"projection",
|
|
769
917
|
].includes(event))
|
|
770
918
|
this.negotiatedEvents.add(event);
|
|
919
|
+
if (this.negotiatedEvents.has("projection") && sessionHost(this.session)) {
|
|
920
|
+
const host = this.session;
|
|
921
|
+
const plan = host.planMode?.();
|
|
922
|
+
if (plan)
|
|
923
|
+
this.emitProjection({ namespace: "plan", projectionName: "mode", version: 1, state: plan });
|
|
924
|
+
for (const projection of host.projections?.() ?? [])
|
|
925
|
+
this.emitProjection({
|
|
926
|
+
namespace: projection.namespace,
|
|
927
|
+
projectionName: projection.projectionName,
|
|
928
|
+
version: projection.version,
|
|
929
|
+
state: projection.state,
|
|
930
|
+
});
|
|
931
|
+
const extensions = host.extensions?.();
|
|
932
|
+
if (extensions)
|
|
933
|
+
this.emitProjection({
|
|
934
|
+
namespace: "extension",
|
|
935
|
+
projectionName: "ui",
|
|
936
|
+
version: extensions.apiVersion,
|
|
937
|
+
state: { badges: extensions.badges, ui: extensions.ui },
|
|
938
|
+
});
|
|
939
|
+
}
|
|
771
940
|
return this.success(request.id, {
|
|
772
941
|
method: "get_capabilities",
|
|
773
942
|
methods: RPC_METHODS_FOR_CAPABILITIES,
|
|
@@ -858,6 +1027,7 @@ export class RpcDispatcher {
|
|
|
858
1027
|
},
|
|
859
1028
|
transcript: snapshot.transcript,
|
|
860
1029
|
tree: snapshot.tree,
|
|
1030
|
+
...(snapshot.events ? { events: snapshot.events } : {}),
|
|
861
1031
|
stats: snapshot.stats,
|
|
862
1032
|
readOnly: true,
|
|
863
1033
|
},
|
|
@@ -912,6 +1082,27 @@ export class RpcDispatcher {
|
|
|
912
1082
|
});
|
|
913
1083
|
}
|
|
914
1084
|
onSessionEvent(event) {
|
|
1085
|
+
if (sessionHost(this.session) && this.negotiatedEvents.has("projection")) {
|
|
1086
|
+
const host = this.session;
|
|
1087
|
+
const plan = host.planMode?.();
|
|
1088
|
+
if (plan)
|
|
1089
|
+
this.emitProjection({ namespace: "plan", projectionName: "mode", version: 1, state: plan });
|
|
1090
|
+
for (const projection of host.projections?.() ?? [])
|
|
1091
|
+
this.emitProjection({
|
|
1092
|
+
namespace: projection.namespace,
|
|
1093
|
+
projectionName: projection.projectionName,
|
|
1094
|
+
version: projection.version,
|
|
1095
|
+
state: projection.state,
|
|
1096
|
+
});
|
|
1097
|
+
const extensions = host.extensions?.();
|
|
1098
|
+
if (extensions)
|
|
1099
|
+
this.emitProjection({
|
|
1100
|
+
namespace: "extension",
|
|
1101
|
+
projectionName: "ui",
|
|
1102
|
+
version: extensions.apiVersion,
|
|
1103
|
+
state: { badges: extensions.badges, ui: extensions.ui },
|
|
1104
|
+
});
|
|
1105
|
+
}
|
|
915
1106
|
const operation = [...this.operations.values()].find((item) => item.status === "running");
|
|
916
1107
|
const extended = event.type === "session_changed";
|
|
917
1108
|
if (extended && !this.negotiatedEvents.has("session_changed"))
|
|
@@ -943,6 +1134,30 @@ export class RpcDispatcher {
|
|
|
943
1134
|
}
|
|
944
1135
|
}
|
|
945
1136
|
}
|
|
1137
|
+
function isBrowserSafeProjection(value) {
|
|
1138
|
+
const visit = (candidate) => {
|
|
1139
|
+
if (candidate === null || typeof candidate === "string" || typeof candidate === "boolean")
|
|
1140
|
+
return true;
|
|
1141
|
+
if (typeof candidate === "number")
|
|
1142
|
+
return Number.isFinite(candidate);
|
|
1143
|
+
if (Array.isArray(candidate))
|
|
1144
|
+
return candidate.every(visit);
|
|
1145
|
+
if (typeof candidate !== "object")
|
|
1146
|
+
return false;
|
|
1147
|
+
for (const [key, item] of Object.entries(candidate))
|
|
1148
|
+
if (FORBIDDEN_PROJECTION_KEYS.test(key) || !visit(item))
|
|
1149
|
+
return false;
|
|
1150
|
+
return true;
|
|
1151
|
+
};
|
|
1152
|
+
if (!visit(value))
|
|
1153
|
+
return false;
|
|
1154
|
+
try {
|
|
1155
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8") <= MAX_PROJECTION_BYTES;
|
|
1156
|
+
}
|
|
1157
|
+
catch {
|
|
1158
|
+
return false;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
946
1161
|
const RPC_METHODS_FOR_CAPABILITIES = [
|
|
947
1162
|
"get_state",
|
|
948
1163
|
"get_capabilities",
|