@abelxiaoxing/cadence 1.1.0 → 1.1.1
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 +11 -1
- package/package.json +1 -1
- package/prompts/abel-design.md +69 -13
- package/prompts/abel-diagnose.md +2 -0
- package/prompts/abel-implement.md +19 -8
- package/skills/abel-workflow/SKILL.md +37 -3
- package/src/contracts.ts +18 -2
- package/src/control-contracts.ts +2 -15
- package/src/delivery-compiler.ts +47 -17
- package/src/design-control.ts +846 -0
- package/src/design-journal.ts +1569 -0
- package/src/index.ts +580 -197
- package/src/run-state.ts +39 -2
- package/src/run-store.ts +81 -9
- package/src/subagent-activity.ts +1 -1
- package/src/verification-capability.ts +43 -15
- package/src/workflow-engine.ts +428 -113
package/src/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { loadAgentDefinitions } from "./agent-registry.ts";
|
|
|
31
31
|
import { runChildSession } from "./child-session.ts";
|
|
32
32
|
import {
|
|
33
33
|
type AtomicVerificationContract,
|
|
34
|
+
type DesignEvidenceResult,
|
|
34
35
|
isValidRelativePath,
|
|
35
36
|
LIMITS,
|
|
36
37
|
type StructuredVerificationContract,
|
|
@@ -41,11 +42,16 @@ import {
|
|
|
41
42
|
assessDeliveryTraceability,
|
|
42
43
|
compileImplementPlan,
|
|
43
44
|
DeliveryValidationError,
|
|
45
|
+
type GateApprovalProof,
|
|
44
46
|
IMPLEMENT_PLAN_SCHEMA_VERSION,
|
|
45
47
|
parseGateAReceipt,
|
|
46
48
|
parseImplementPlan,
|
|
47
49
|
parseReadyReceipt,
|
|
48
50
|
} from "./delivery-compiler.ts";
|
|
51
|
+
import {
|
|
52
|
+
DesignController,
|
|
53
|
+
validateDesignControlRequest,
|
|
54
|
+
} from "./design-control.ts";
|
|
49
55
|
import { canonicalJson, hashCanonicalValue } from "./implement-graph.ts";
|
|
50
56
|
import { BubblewrapIsolationBackend } from "./isolation-backend.ts";
|
|
51
57
|
import { PACKET_ACTIONS, PacketRuntime } from "./packet-runtime.ts";
|
|
@@ -75,10 +81,12 @@ import {
|
|
|
75
81
|
} from "./verification-capability.ts";
|
|
76
82
|
import {
|
|
77
83
|
openDurableWorkflowEngine,
|
|
84
|
+
type WorkflowAvailableDelivery,
|
|
78
85
|
type WorkflowDeliverySource,
|
|
79
86
|
} from "./workflow-engine.ts";
|
|
80
87
|
|
|
81
88
|
export const DISPATCH_TOOL = "abel_dispatch";
|
|
89
|
+
const DESIGN_PARENT_READ_TOOLS = new Set(["read", "grep", "find", "ls"]);
|
|
82
90
|
|
|
83
91
|
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
84
92
|
const ELIGIBLE_PROMPTS = [
|
|
@@ -109,6 +117,10 @@ const DESIGN_REQUEST_SCHEMA = {
|
|
|
109
117
|
enum: ["design-explorer"],
|
|
110
118
|
description: "Exact package-owned read-only Agent role.",
|
|
111
119
|
},
|
|
120
|
+
runId: {
|
|
121
|
+
type: "string",
|
|
122
|
+
description: "Durable Design run identity returned by Design start.",
|
|
123
|
+
},
|
|
112
124
|
id: {
|
|
113
125
|
type: "string",
|
|
114
126
|
description:
|
|
@@ -182,6 +194,7 @@ const DESIGN_REQUEST_SCHEMA = {
|
|
|
182
194
|
required: [
|
|
183
195
|
"stage",
|
|
184
196
|
"role",
|
|
197
|
+
"runId",
|
|
185
198
|
"id",
|
|
186
199
|
"phase",
|
|
187
200
|
"objective",
|
|
@@ -193,6 +206,90 @@ const DESIGN_REQUEST_SCHEMA = {
|
|
|
193
206
|
additionalProperties: false,
|
|
194
207
|
} as const;
|
|
195
208
|
|
|
209
|
+
const DESIGN_CONTROL_REQUEST_SCHEMA = {
|
|
210
|
+
description:
|
|
211
|
+
"One closed durable Design control operation. The operation body is strict and idempotent by operationId.",
|
|
212
|
+
oneOf: [
|
|
213
|
+
{
|
|
214
|
+
type: "object",
|
|
215
|
+
properties: {
|
|
216
|
+
operation: { type: "string", enum: ["record-decision"] },
|
|
217
|
+
runId: { type: "string" },
|
|
218
|
+
operationId: { type: "string" },
|
|
219
|
+
decisionId: { type: "string" },
|
|
220
|
+
category: { type: "string", enum: ["behavior", "technical"] },
|
|
221
|
+
contractHash: { type: "string" },
|
|
222
|
+
refs: { type: "array", items: { type: "string" } },
|
|
223
|
+
},
|
|
224
|
+
required: [
|
|
225
|
+
"operation",
|
|
226
|
+
"runId",
|
|
227
|
+
"operationId",
|
|
228
|
+
"decisionId",
|
|
229
|
+
"category",
|
|
230
|
+
"contractHash",
|
|
231
|
+
"refs",
|
|
232
|
+
],
|
|
233
|
+
additionalProperties: false,
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
type: "object",
|
|
237
|
+
properties: {
|
|
238
|
+
operation: { type: "string", enum: ["approve-gate"] },
|
|
239
|
+
runId: { type: "string" },
|
|
240
|
+
operationId: { type: "string" },
|
|
241
|
+
gate: { type: "string", enum: ["gate-a", "gate-b"] },
|
|
242
|
+
contractHash: { type: "string" },
|
|
243
|
+
},
|
|
244
|
+
required: ["operation", "runId", "operationId", "gate", "contractHash"],
|
|
245
|
+
additionalProperties: false,
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
type: "object",
|
|
249
|
+
properties: {
|
|
250
|
+
operation: { type: "string", enum: ["write-artifact"] },
|
|
251
|
+
runId: { type: "string" },
|
|
252
|
+
operationId: { type: "string" },
|
|
253
|
+
path: {
|
|
254
|
+
type: "string",
|
|
255
|
+
description: "Allowed path relative to this Design change root.",
|
|
256
|
+
},
|
|
257
|
+
content: {
|
|
258
|
+
type: "string",
|
|
259
|
+
maxLength: 16 * 1024 * 1024,
|
|
260
|
+
description: "Exact bounded UTF-8 artifact content.",
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
required: ["operation", "runId", "operationId", "path", "content"],
|
|
264
|
+
additionalProperties: false,
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {
|
|
269
|
+
operation: { type: "string", enum: ["delete-artifact"] },
|
|
270
|
+
runId: { type: "string" },
|
|
271
|
+
operationId: { type: "string" },
|
|
272
|
+
path: {
|
|
273
|
+
type: "string",
|
|
274
|
+
description: "Allowed path relative to this Design change root.",
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
required: ["operation", "runId", "operationId", "path"],
|
|
278
|
+
additionalProperties: false,
|
|
279
|
+
},
|
|
280
|
+
...(["compile-plan", "finalize-delivery"] as const).map((operation) => ({
|
|
281
|
+
type: "object" as const,
|
|
282
|
+
properties: {
|
|
283
|
+
operation: { type: "string" as const, enum: [operation] },
|
|
284
|
+
runId: { type: "string" as const },
|
|
285
|
+
operationId: { type: "string" as const },
|
|
286
|
+
},
|
|
287
|
+
required: ["operation", "runId", "operationId"],
|
|
288
|
+
additionalProperties: false,
|
|
289
|
+
})),
|
|
290
|
+
],
|
|
291
|
+
} as const;
|
|
292
|
+
|
|
196
293
|
function invokedPrompt(text: string): EligiblePrompt | undefined {
|
|
197
294
|
const name = text.match(/^\/([^\s]+)(?:\s|$)/)?.[1];
|
|
198
295
|
return ELIGIBLE_PROMPTS.find((candidate) => candidate === name);
|
|
@@ -273,6 +370,12 @@ export interface WorkflowControlEngine {
|
|
|
273
370
|
signal?: AbortSignal,
|
|
274
371
|
onActivity?: (event: WorkflowActivityUpdate) => void,
|
|
275
372
|
): Promise<Record<string, unknown>>;
|
|
373
|
+
executeDesign?(request: unknown): Promise<Record<string, unknown>>;
|
|
374
|
+
assertDesignRun?(runId: string): void;
|
|
375
|
+
recordDesignEvidence?(input: {
|
|
376
|
+
runId: string;
|
|
377
|
+
evidence: DesignEvidenceResult;
|
|
378
|
+
}): unknown;
|
|
276
379
|
close(): void | Promise<void>;
|
|
277
380
|
}
|
|
278
381
|
|
|
@@ -297,12 +400,40 @@ export interface PackageDeliverySourceOptions {
|
|
|
297
400
|
consumerRoot: string,
|
|
298
401
|
change: string,
|
|
299
402
|
) => Promise<OpenSpecDeliveryInspection>;
|
|
403
|
+
verifyGateProof?: (input: {
|
|
404
|
+
change: string;
|
|
405
|
+
gate: "gate-a" | "gate-b";
|
|
406
|
+
proof: GateApprovalProof;
|
|
407
|
+
}) => boolean;
|
|
408
|
+
verifyFinalizedDelivery?: (input: {
|
|
409
|
+
change: string;
|
|
410
|
+
deliveryRevision: number;
|
|
411
|
+
receiptHash: string;
|
|
412
|
+
gateA: GateApprovalProof;
|
|
413
|
+
gateB: GateApprovalProof;
|
|
414
|
+
planCanonicalHash: string;
|
|
415
|
+
}) => boolean;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export interface PackageWorkflowDeliverySource extends WorkflowDeliverySource {
|
|
419
|
+
discoverLatest(input: {
|
|
420
|
+
stage: "abel-design" | "abel-implement";
|
|
421
|
+
change: string;
|
|
422
|
+
}): Promise<WorkflowAvailableDelivery | undefined>;
|
|
300
423
|
}
|
|
301
424
|
|
|
302
425
|
function sha256(value: Uint8Array | string): string {
|
|
303
426
|
return createHash("sha256").update(value).digest("hex");
|
|
304
427
|
}
|
|
305
428
|
|
|
429
|
+
function proofVerified(verify: () => boolean): boolean {
|
|
430
|
+
try {
|
|
431
|
+
return verify() === true;
|
|
432
|
+
} catch {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
306
437
|
function normalizedTrackingArtifact(
|
|
307
438
|
bytes: Uint8Array,
|
|
308
439
|
taskIds: readonly string[],
|
|
@@ -449,9 +580,80 @@ export async function inspectOpenSpecDelivery(
|
|
|
449
580
|
export function packageDeliverySource(
|
|
450
581
|
consumerRoot: string,
|
|
451
582
|
options: PackageDeliverySourceOptions = {},
|
|
452
|
-
):
|
|
583
|
+
): PackageWorkflowDeliverySource {
|
|
453
584
|
const inspect = options.inspectOpenSpec ?? inspectOpenSpecDelivery;
|
|
585
|
+
const verifyGateProof = options.verifyGateProof;
|
|
586
|
+
const verifyFinalizedDelivery = options.verifyFinalizedDelivery;
|
|
454
587
|
return {
|
|
588
|
+
async discoverLatest(input) {
|
|
589
|
+
if (
|
|
590
|
+
input.stage !== "abel-implement" ||
|
|
591
|
+
!verifyGateProof ||
|
|
592
|
+
!verifyFinalizedDelivery
|
|
593
|
+
) {
|
|
594
|
+
return undefined;
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
const changeRoot = `openspec/changes/${input.change}`;
|
|
598
|
+
const receiptBytes = readPackageDeliveryFile(
|
|
599
|
+
consumerRoot,
|
|
600
|
+
`${changeRoot}/ready.yaml`,
|
|
601
|
+
4 * 1024 * 1024,
|
|
602
|
+
);
|
|
603
|
+
const receipt = parseReadyReceipt(receiptBytes);
|
|
604
|
+
if (receipt.change !== input.change) return undefined;
|
|
605
|
+
const gateABytes = readPackageDeliveryFile(
|
|
606
|
+
consumerRoot,
|
|
607
|
+
`${changeRoot}/${receipt.approvals.gateA.path}`,
|
|
608
|
+
4 * 1024 * 1024,
|
|
609
|
+
);
|
|
610
|
+
if (sha256(gateABytes) !== receipt.approvals.gateA.rawSha256) {
|
|
611
|
+
return undefined;
|
|
612
|
+
}
|
|
613
|
+
const gateA = parseGateAReceipt(gateABytes);
|
|
614
|
+
if (
|
|
615
|
+
gateA.change !== receipt.change ||
|
|
616
|
+
gateA.schema !== receipt.schema ||
|
|
617
|
+
!proofVerified(() =>
|
|
618
|
+
verifyGateProof({
|
|
619
|
+
change: input.change,
|
|
620
|
+
gate: "gate-a",
|
|
621
|
+
proof: gateA.approval,
|
|
622
|
+
}),
|
|
623
|
+
) ||
|
|
624
|
+
!proofVerified(() =>
|
|
625
|
+
verifyGateProof({
|
|
626
|
+
change: input.change,
|
|
627
|
+
gate: "gate-b",
|
|
628
|
+
proof: receipt.approvals.gateB,
|
|
629
|
+
}),
|
|
630
|
+
)
|
|
631
|
+
) {
|
|
632
|
+
return undefined;
|
|
633
|
+
}
|
|
634
|
+
const receiptHash = sha256(receiptBytes);
|
|
635
|
+
if (
|
|
636
|
+
!proofVerified(() =>
|
|
637
|
+
verifyFinalizedDelivery({
|
|
638
|
+
change: input.change,
|
|
639
|
+
deliveryRevision: receipt.deliveryRevision,
|
|
640
|
+
receiptHash,
|
|
641
|
+
gateA: gateA.approval,
|
|
642
|
+
gateB: receipt.approvals.gateB,
|
|
643
|
+
planCanonicalHash: receipt.plan.canonicalHash,
|
|
644
|
+
}),
|
|
645
|
+
)
|
|
646
|
+
) {
|
|
647
|
+
return undefined;
|
|
648
|
+
}
|
|
649
|
+
return {
|
|
650
|
+
deliveryRevision: receipt.deliveryRevision,
|
|
651
|
+
receiptHash,
|
|
652
|
+
};
|
|
653
|
+
} catch {
|
|
654
|
+
return undefined;
|
|
655
|
+
}
|
|
656
|
+
},
|
|
455
657
|
async load(input) {
|
|
456
658
|
if (input.stage !== "abel-implement") {
|
|
457
659
|
throw new DeliveryValidationError(["delivery-stage-invalid"]);
|
|
@@ -567,6 +769,19 @@ export function packageDeliverySource(
|
|
|
567
769
|
}
|
|
568
770
|
}
|
|
569
771
|
if (gateA) {
|
|
772
|
+
if (!verifyGateProof) {
|
|
773
|
+
diagnostics.add("delivery-gate-proof-verifier-unavailable");
|
|
774
|
+
} else if (
|
|
775
|
+
!proofVerified(() =>
|
|
776
|
+
verifyGateProof({
|
|
777
|
+
change: input.change,
|
|
778
|
+
gate: "gate-a",
|
|
779
|
+
proof: gateA.approval,
|
|
780
|
+
}),
|
|
781
|
+
)
|
|
782
|
+
) {
|
|
783
|
+
diagnostics.add("delivery-gate-a-proof-invalid");
|
|
784
|
+
}
|
|
570
785
|
for (const artifact of gateA.artifacts) {
|
|
571
786
|
if (artifactHashes.get(artifact.path) !== artifact.rawSha256) {
|
|
572
787
|
diagnostics.add(
|
|
@@ -575,6 +790,36 @@ export function packageDeliverySource(
|
|
|
575
790
|
}
|
|
576
791
|
}
|
|
577
792
|
}
|
|
793
|
+
if (
|
|
794
|
+
verifyGateProof &&
|
|
795
|
+
!proofVerified(() =>
|
|
796
|
+
verifyGateProof({
|
|
797
|
+
change: input.change,
|
|
798
|
+
gate: "gate-b",
|
|
799
|
+
proof: receipt.approvals.gateB,
|
|
800
|
+
}),
|
|
801
|
+
)
|
|
802
|
+
) {
|
|
803
|
+
diagnostics.add("delivery-gate-b-proof-invalid");
|
|
804
|
+
}
|
|
805
|
+
if (gateA) {
|
|
806
|
+
if (!verifyFinalizedDelivery) {
|
|
807
|
+
diagnostics.add("delivery-finalization-verifier-unavailable");
|
|
808
|
+
} else if (
|
|
809
|
+
!proofVerified(() =>
|
|
810
|
+
verifyFinalizedDelivery({
|
|
811
|
+
change: input.change,
|
|
812
|
+
deliveryRevision: receipt.deliveryRevision,
|
|
813
|
+
receiptHash,
|
|
814
|
+
gateA: gateA.approval,
|
|
815
|
+
gateB: receipt.approvals.gateB,
|
|
816
|
+
planCanonicalHash: receipt.plan.canonicalHash,
|
|
817
|
+
}),
|
|
818
|
+
)
|
|
819
|
+
) {
|
|
820
|
+
diagnostics.add("delivery-finalization-proof-invalid");
|
|
821
|
+
}
|
|
822
|
+
}
|
|
578
823
|
|
|
579
824
|
let plan: ReturnType<typeof parseImplementPlan> | undefined;
|
|
580
825
|
let planBytes: Buffer | undefined;
|
|
@@ -675,7 +920,7 @@ export function packageDeliverySource(
|
|
|
675
920
|
}
|
|
676
921
|
}
|
|
677
922
|
}
|
|
678
|
-
if (!plan || diagnostics.size > 0) {
|
|
923
|
+
if (!plan || !gateA || diagnostics.size > 0) {
|
|
679
924
|
throw new DeliveryValidationError([...diagnostics]);
|
|
680
925
|
}
|
|
681
926
|
return {
|
|
@@ -684,6 +929,10 @@ export function packageDeliverySource(
|
|
|
684
929
|
revision: receipt.deliveryRevision,
|
|
685
930
|
receiptHash,
|
|
686
931
|
plan,
|
|
932
|
+
approvalProofs: {
|
|
933
|
+
gateA: structuredClone(gateA.approval),
|
|
934
|
+
gateB: structuredClone(receipt.approvals.gateB),
|
|
935
|
+
},
|
|
687
936
|
};
|
|
688
937
|
},
|
|
689
938
|
};
|
|
@@ -1175,6 +1424,11 @@ export function openPackageWorkflowControlEngine(
|
|
|
1175
1424
|
xdgStateHome: process.env.XDG_STATE_HOME,
|
|
1176
1425
|
});
|
|
1177
1426
|
const contexts = new AsyncLocalStorage<ExtensionContext>();
|
|
1427
|
+
const design = DesignController.open({
|
|
1428
|
+
consumerRoot,
|
|
1429
|
+
stateRoot,
|
|
1430
|
+
inspectOpenSpec: inspectOpenSpecDelivery,
|
|
1431
|
+
});
|
|
1178
1432
|
const implementationAgent = loadAgentDefinitions().find(
|
|
1179
1433
|
(agent) => agent.role === "implementation-worker",
|
|
1180
1434
|
);
|
|
@@ -1184,7 +1438,10 @@ export function openPackageWorkflowControlEngine(
|
|
|
1184
1438
|
const engine = openDurableWorkflowEngine({
|
|
1185
1439
|
consumerRoot,
|
|
1186
1440
|
stateRoot,
|
|
1187
|
-
deliverySource: packageDeliverySource(consumerRoot
|
|
1441
|
+
deliverySource: packageDeliverySource(consumerRoot, {
|
|
1442
|
+
verifyGateProof: (input) => design.verifyGateProof(input),
|
|
1443
|
+
verifyFinalizedDelivery: (input) => design.verifyFinalizedDelivery(input),
|
|
1444
|
+
}),
|
|
1188
1445
|
routePolicy: routeResolution.ok
|
|
1189
1446
|
? routeResolution.policy
|
|
1190
1447
|
: unavailableRoutePolicy(),
|
|
@@ -1379,12 +1636,27 @@ export function openPackageWorkflowControlEngine(
|
|
|
1379
1636
|
operationRouteResolution,
|
|
1380
1637
|
engine.routePolicyStatus(),
|
|
1381
1638
|
);
|
|
1382
|
-
|
|
1639
|
+
const designStatus =
|
|
1640
|
+
validation.value.stage === "abel-design" &&
|
|
1641
|
+
typeof outcome.runId === "string"
|
|
1642
|
+
? { design: design.status(outcome.runId) }
|
|
1643
|
+
: {};
|
|
1644
|
+
return { ...outcome, ...designStatus, routePolicy };
|
|
1383
1645
|
});
|
|
1384
1646
|
},
|
|
1647
|
+
executeDesign(request: unknown) {
|
|
1648
|
+
return design.execute(request);
|
|
1649
|
+
},
|
|
1650
|
+
assertDesignRun(runId: string) {
|
|
1651
|
+
design.assertDesignRun(runId);
|
|
1652
|
+
},
|
|
1653
|
+
recordDesignEvidence(input) {
|
|
1654
|
+
return design.recordEvidence(input);
|
|
1655
|
+
},
|
|
1385
1656
|
async close() {
|
|
1386
1657
|
contexts.disable();
|
|
1387
1658
|
await engine.close();
|
|
1659
|
+
design.close();
|
|
1388
1660
|
},
|
|
1389
1661
|
};
|
|
1390
1662
|
}
|
|
@@ -1410,6 +1682,7 @@ export function registerWorkflowControl(
|
|
|
1410
1682
|
const engines = new Map<string, Promise<WorkflowControlEngine>>();
|
|
1411
1683
|
let pendingPrompt: EligiblePrompt | undefined;
|
|
1412
1684
|
let activePrompt: EligiblePrompt | undefined;
|
|
1685
|
+
let designToolSnapshot: string[] | undefined;
|
|
1413
1686
|
const activation = new (class implements Activation {
|
|
1414
1687
|
state: Activation["state"] = "inactive";
|
|
1415
1688
|
|
|
@@ -1460,222 +1733,327 @@ export function registerWorkflowControl(
|
|
|
1460
1733
|
),
|
|
1461
1734
|
);
|
|
1462
1735
|
};
|
|
1736
|
+
const restoreDesignTools = () => {
|
|
1737
|
+
if (!designToolSnapshot) return;
|
|
1738
|
+
const snapshot = designToolSnapshot;
|
|
1739
|
+
designToolSnapshot = undefined;
|
|
1740
|
+
pi.setActiveTools([...snapshot]);
|
|
1741
|
+
};
|
|
1742
|
+
const enforceDesignTools = () => {
|
|
1743
|
+
if (!designToolSnapshot) {
|
|
1744
|
+
designToolSnapshot = pi
|
|
1745
|
+
.getActiveTools()
|
|
1746
|
+
.filter((name) => name !== DISPATCH_TOOL);
|
|
1747
|
+
}
|
|
1748
|
+
pi.setActiveTools(
|
|
1749
|
+
activateTool(
|
|
1750
|
+
designToolSnapshot.filter((name) => DESIGN_PARENT_READ_TOOLS.has(name)),
|
|
1751
|
+
DISPATCH_TOOL,
|
|
1752
|
+
),
|
|
1753
|
+
);
|
|
1754
|
+
};
|
|
1463
1755
|
const deactivate = () => {
|
|
1464
1756
|
activation.drain();
|
|
1757
|
+
if (designToolSnapshot) {
|
|
1758
|
+
restoreDesignTools();
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1465
1761
|
const active = pi.getActiveTools();
|
|
1466
1762
|
if (active.includes(DISPATCH_TOOL)) {
|
|
1467
1763
|
pi.setActiveTools(deactivateTool(active, DISPATCH_TOOL));
|
|
1468
1764
|
}
|
|
1469
1765
|
};
|
|
1766
|
+
const deactivateStage = async () => {
|
|
1767
|
+
activePrompt = undefined;
|
|
1768
|
+
await packetRuntime.drain();
|
|
1769
|
+
deactivate();
|
|
1770
|
+
parentPayloadBridge.clear();
|
|
1771
|
+
};
|
|
1470
1772
|
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
properties: {
|
|
1480
|
-
version: { type: "integer", enum: [2] },
|
|
1481
|
-
command: {
|
|
1482
|
-
type: "string",
|
|
1483
|
-
enum: ["start", "status", "resume", "rebind", "cancel", "discard"],
|
|
1484
|
-
},
|
|
1485
|
-
stage: {
|
|
1486
|
-
type: "string",
|
|
1487
|
-
enum: ["abel-design", "abel-implement"],
|
|
1488
|
-
},
|
|
1489
|
-
change: { type: "string" },
|
|
1490
|
-
provisionalKey: { type: "string" },
|
|
1491
|
-
operationId: { type: "string" },
|
|
1492
|
-
deliveryRevision: { type: "integer", minimum: 1 },
|
|
1493
|
-
receiptHash: { type: "string" },
|
|
1494
|
-
routeId: { type: "string" },
|
|
1495
|
-
action: { type: "string", enum: [...PACKET_ACTIONS] },
|
|
1496
|
-
request: {
|
|
1497
|
-
anyOf: [DESIGN_REQUEST_SCHEMA, GENERIC_REQUEST_SCHEMA],
|
|
1498
|
-
},
|
|
1773
|
+
// Pi validates tool arguments before execute(), so expose only the schema
|
|
1774
|
+
// legal for the active stage instead of one ambiguous command/packet union.
|
|
1775
|
+
const CONTROL_COMMAND_PARAMETERS = {
|
|
1776
|
+
type: "object",
|
|
1777
|
+
properties: {
|
|
1778
|
+
command: {
|
|
1779
|
+
type: "string",
|
|
1780
|
+
enum: ["start", "status", "resume", "rebind", "cancel", "discard"],
|
|
1499
1781
|
},
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
not: {
|
|
1511
|
-
anyOf: [
|
|
1512
|
-
{ required: ["version"] },
|
|
1513
|
-
{ required: ["command"] },
|
|
1514
|
-
{ required: ["stage"] },
|
|
1515
|
-
{ required: ["change"] },
|
|
1516
|
-
{ required: ["provisionalKey"] },
|
|
1517
|
-
{ required: ["operationId"] },
|
|
1518
|
-
{ required: ["deliveryRevision"] },
|
|
1519
|
-
{ required: ["receiptHash"] },
|
|
1520
|
-
{ required: ["routeId"] },
|
|
1521
|
-
],
|
|
1522
|
-
},
|
|
1523
|
-
},
|
|
1524
|
-
],
|
|
1782
|
+
stage: {
|
|
1783
|
+
type: "string",
|
|
1784
|
+
enum: ["abel-design", "abel-implement"],
|
|
1785
|
+
},
|
|
1786
|
+
change: { type: "string" },
|
|
1787
|
+
provisionalKey: { type: "string" },
|
|
1788
|
+
operationId: { type: "string" },
|
|
1789
|
+
deliveryRevision: { type: "integer", minimum: 1 },
|
|
1790
|
+
receiptHash: { type: "string" },
|
|
1791
|
+
routeId: { type: "string" },
|
|
1525
1792
|
},
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1793
|
+
required: ["command", "stage"],
|
|
1794
|
+
additionalProperties: false,
|
|
1795
|
+
} as const;
|
|
1796
|
+
const PACKET_PARAMETERS = {
|
|
1797
|
+
type: "object",
|
|
1798
|
+
properties: {
|
|
1799
|
+
action: { type: "string", enum: [...PACKET_ACTIONS, "design"] },
|
|
1800
|
+
request: {
|
|
1801
|
+
anyOf: [
|
|
1802
|
+
DESIGN_REQUEST_SCHEMA,
|
|
1803
|
+
DESIGN_CONTROL_REQUEST_SCHEMA,
|
|
1804
|
+
GENERIC_REQUEST_SCHEMA,
|
|
1805
|
+
],
|
|
1806
|
+
},
|
|
1807
|
+
},
|
|
1808
|
+
required: ["action"],
|
|
1809
|
+
additionalProperties: false,
|
|
1810
|
+
} as const;
|
|
1811
|
+
let registeredParameterKind: "command" | "packet" | undefined;
|
|
1812
|
+
|
|
1813
|
+
const registerDispatchTool = (kind: "command" | "packet") => {
|
|
1814
|
+
if (registeredParameterKind === kind) return;
|
|
1815
|
+
registeredParameterKind = kind;
|
|
1816
|
+
pi.registerTool({
|
|
1817
|
+
name: DISPATCH_TOOL,
|
|
1818
|
+
label: "Abel Control",
|
|
1819
|
+
description:
|
|
1820
|
+
kind === "command"
|
|
1821
|
+
? "Private stage-bound Abel workflow control. Accepts durable Design and Implement change commands."
|
|
1822
|
+
: "Private stage-bound Abel packet control. Accepts bounded Design and Diagnose packet operations.",
|
|
1823
|
+
executionMode: "parallel",
|
|
1824
|
+
parameters:
|
|
1825
|
+
kind === "command" ? CONTROL_COMMAND_PARAMETERS : PACKET_PARAMETERS,
|
|
1826
|
+
async execute(
|
|
1827
|
+
toolCallId: string,
|
|
1828
|
+
params: unknown,
|
|
1829
|
+
signal: AbortSignal | undefined,
|
|
1830
|
+
onUpdate: AgentToolUpdateCallback<unknown> | undefined,
|
|
1831
|
+
ctx: ExtensionContext,
|
|
1832
|
+
) {
|
|
1833
|
+
const record =
|
|
1834
|
+
params && typeof params === "object" && !Array.isArray(params)
|
|
1835
|
+
? (params as Record<string, unknown>)
|
|
1836
|
+
: undefined;
|
|
1837
|
+
if (record && typeof record.action === "string") {
|
|
1838
|
+
if (
|
|
1839
|
+
Object.keys(record).some(
|
|
1840
|
+
(key) => key !== "action" && key !== "request",
|
|
1841
|
+
)
|
|
1842
|
+
) {
|
|
1843
|
+
throw new Error("control-envelope-ambiguous");
|
|
1844
|
+
}
|
|
1845
|
+
if (
|
|
1846
|
+
activePrompt !== "abel-design" &&
|
|
1847
|
+
activePrompt !== "abel-diagnose"
|
|
1848
|
+
) {
|
|
1849
|
+
throw new Error("stage-control-mismatch");
|
|
1850
|
+
}
|
|
1851
|
+
if (record.action === "design") {
|
|
1852
|
+
if (activePrompt !== "abel-design") {
|
|
1853
|
+
throw new Error("stage-control-mismatch");
|
|
1854
|
+
}
|
|
1855
|
+
const designRequest = validateDesignControlRequest(record.request);
|
|
1856
|
+
if (!designRequest.ok) throw new Error(designRequest.code);
|
|
1857
|
+
const engine = await engineFor(ctx);
|
|
1858
|
+
if (!engine.executeDesign)
|
|
1859
|
+
throw new Error("design-control-unavailable");
|
|
1860
|
+
const payload = await engine.executeDesign(designRequest.value);
|
|
1861
|
+
if (
|
|
1862
|
+
designRequest.value.operation === "finalize-delivery" &&
|
|
1863
|
+
payload.state === "completed"
|
|
1864
|
+
) {
|
|
1865
|
+
await deactivateStage();
|
|
1866
|
+
}
|
|
1867
|
+
return {
|
|
1868
|
+
content: [
|
|
1869
|
+
{ type: "text" as const, text: JSON.stringify(payload) },
|
|
1870
|
+
],
|
|
1871
|
+
details: payload,
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
let designEngine: WorkflowControlEngine | undefined;
|
|
1875
|
+
if (record.action === "run") {
|
|
1876
|
+
const packet = packetRuntime.validateRequest(record.request);
|
|
1877
|
+
if (!packet.ok || packet.value.stage !== activePrompt) {
|
|
1878
|
+
throw new Error(
|
|
1879
|
+
packet.ok ? "stage-control-mismatch" : packet.reason,
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
if (packet.value.stage === "abel-design") {
|
|
1883
|
+
designEngine = await engineFor(ctx);
|
|
1884
|
+
if (!designEngine.assertDesignRun || !packet.value.runId) {
|
|
1885
|
+
throw new Error("design-control-unavailable");
|
|
1886
|
+
}
|
|
1887
|
+
designEngine.assertDesignRun(packet.value.runId);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
const operation = {
|
|
1891
|
+
request: record.request,
|
|
1892
|
+
};
|
|
1893
|
+
const tuiRun = ctx.mode === "tui" && record.action === "run";
|
|
1894
|
+
const packetPayload = tuiRun
|
|
1895
|
+
? await packetRuntime.execute(
|
|
1896
|
+
record.action,
|
|
1897
|
+
operation,
|
|
1898
|
+
ctx,
|
|
1899
|
+
signal,
|
|
1900
|
+
activity.observe(
|
|
1901
|
+
toolCallId,
|
|
1902
|
+
onUpdate as ((result: unknown) => void) | undefined,
|
|
1903
|
+
),
|
|
1904
|
+
)
|
|
1905
|
+
: await packetRuntime.execute(
|
|
1906
|
+
record.action,
|
|
1907
|
+
operation,
|
|
1908
|
+
ctx,
|
|
1909
|
+
signal,
|
|
1910
|
+
);
|
|
1911
|
+
let payload:
|
|
1912
|
+
| typeof packetPayload
|
|
1913
|
+
| (typeof packetPayload & {
|
|
1914
|
+
recordedEvidence: unknown;
|
|
1915
|
+
}) = packetPayload;
|
|
1916
|
+
if (
|
|
1917
|
+
packetPayload.ok &&
|
|
1918
|
+
record.action === "run" &&
|
|
1919
|
+
activePrompt === "abel-design"
|
|
1920
|
+
) {
|
|
1921
|
+
const packet = packetRuntime.validateRequest(record.request);
|
|
1922
|
+
if (
|
|
1923
|
+
!packet.ok ||
|
|
1924
|
+
!packet.value.runId ||
|
|
1925
|
+
!designEngine?.recordDesignEvidence
|
|
1926
|
+
) {
|
|
1927
|
+
throw new Error("design-control-unavailable");
|
|
1928
|
+
}
|
|
1929
|
+
const recordedEvidence = designEngine.recordDesignEvidence({
|
|
1930
|
+
runId: packet.value.runId,
|
|
1931
|
+
evidence: packetPayload.result as DesignEvidenceResult,
|
|
1932
|
+
});
|
|
1933
|
+
payload = { ...packetPayload, recordedEvidence };
|
|
1934
|
+
}
|
|
1935
|
+
if (record.action === "finish" && packetPayload.ok) {
|
|
1936
|
+
await deactivateStage();
|
|
1937
|
+
}
|
|
1938
|
+
const display = tuiRun
|
|
1939
|
+
? activity.finalize(toolCallId, payload)
|
|
1940
|
+
: undefined;
|
|
1941
|
+
const { payload: publicPayload, usage } = splitUsage(payload);
|
|
1942
|
+
const details = display
|
|
1943
|
+
? {
|
|
1944
|
+
...(publicPayload as Record<string, unknown>),
|
|
1945
|
+
[ACTIVITY_DETAILS_KEY]: display,
|
|
1946
|
+
}
|
|
1947
|
+
: publicPayload;
|
|
1948
|
+
return {
|
|
1949
|
+
content: [
|
|
1950
|
+
{ type: "text" as const, text: JSON.stringify(publicPayload) },
|
|
1951
|
+
],
|
|
1952
|
+
details,
|
|
1953
|
+
...(usage === undefined ? {} : { usage }),
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
const validation = validateControlCommand(params);
|
|
1957
|
+
if (!validation.ok) {
|
|
1958
|
+
const error = new Error(validation.code);
|
|
1959
|
+
error.name = "ControlCommandError";
|
|
1960
|
+
throw error;
|
|
1544
1961
|
}
|
|
1545
1962
|
if (
|
|
1546
|
-
activePrompt !==
|
|
1547
|
-
activePrompt
|
|
1963
|
+
activePrompt !== undefined &&
|
|
1964
|
+
(activePrompt === "abel-diagnose" ||
|
|
1965
|
+
validation.value.stage !== activePrompt)
|
|
1548
1966
|
) {
|
|
1549
1967
|
throw new Error("stage-control-mismatch");
|
|
1550
1968
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1969
|
+
const engine = await engineFor(ctx);
|
|
1970
|
+
const tuiControl = ctx.mode === "tui";
|
|
1971
|
+
if (tuiControl) {
|
|
1972
|
+
try {
|
|
1973
|
+
activity.beginWorkflow(
|
|
1974
|
+
toolCallId,
|
|
1975
|
+
validation.value,
|
|
1976
|
+
onUpdate as ((result: unknown) => void) | undefined,
|
|
1556
1977
|
);
|
|
1978
|
+
} catch {
|
|
1979
|
+
// Presentation state must never alter workflow execution.
|
|
1557
1980
|
}
|
|
1558
1981
|
}
|
|
1559
|
-
|
|
1560
|
-
request: record.request,
|
|
1561
|
-
};
|
|
1562
|
-
const tuiRun = ctx.mode === "tui" && record.action === "run";
|
|
1563
|
-
const payload = tuiRun
|
|
1564
|
-
? await packetRuntime.execute(
|
|
1565
|
-
record.action,
|
|
1566
|
-
operation,
|
|
1567
|
-
ctx,
|
|
1568
|
-
signal,
|
|
1569
|
-
activity.observe(
|
|
1570
|
-
toolCallId,
|
|
1571
|
-
onUpdate as ((result: unknown) => void) | undefined,
|
|
1572
|
-
),
|
|
1573
|
-
)
|
|
1574
|
-
: await packetRuntime.execute(record.action, operation, ctx, signal);
|
|
1575
|
-
const display = tuiRun
|
|
1576
|
-
? activity.finalize(toolCallId, payload)
|
|
1577
|
-
: undefined;
|
|
1578
|
-
const { payload: publicPayload, usage } = splitUsage(payload);
|
|
1579
|
-
const details = display
|
|
1580
|
-
? {
|
|
1581
|
-
...(publicPayload as Record<string, unknown>),
|
|
1582
|
-
[ACTIVITY_DETAILS_KEY]: display,
|
|
1583
|
-
}
|
|
1584
|
-
: publicPayload;
|
|
1585
|
-
return {
|
|
1586
|
-
content: [
|
|
1587
|
-
{ type: "text" as const, text: JSON.stringify(publicPayload) },
|
|
1588
|
-
],
|
|
1589
|
-
details,
|
|
1590
|
-
...(usage === undefined ? {} : { usage }),
|
|
1591
|
-
};
|
|
1592
|
-
}
|
|
1593
|
-
const validation = validateControlCommand(params);
|
|
1594
|
-
if (!validation.ok) {
|
|
1595
|
-
const error = new Error(validation.code);
|
|
1596
|
-
error.name = "ControlCommandError";
|
|
1597
|
-
throw error;
|
|
1598
|
-
}
|
|
1599
|
-
if (
|
|
1600
|
-
activePrompt !== undefined &&
|
|
1601
|
-
(activePrompt === "abel-diagnose" ||
|
|
1602
|
-
validation.value.stage !== activePrompt)
|
|
1603
|
-
) {
|
|
1604
|
-
throw new Error("stage-control-mismatch");
|
|
1605
|
-
}
|
|
1606
|
-
const engine = await engineFor(ctx);
|
|
1607
|
-
const tuiControl = ctx.mode === "tui";
|
|
1608
|
-
if (tuiControl) {
|
|
1982
|
+
let payload: Record<string, unknown>;
|
|
1609
1983
|
try {
|
|
1610
|
-
|
|
1611
|
-
toolCallId,
|
|
1984
|
+
payload = await engine.execute(
|
|
1612
1985
|
validation.value,
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
? (event) => {
|
|
1627
|
-
try {
|
|
1628
|
-
activity.updateWorkflow(toolCallId, validation.value, event);
|
|
1629
|
-
} catch {
|
|
1630
|
-
// Presentation state must never alter workflow execution.
|
|
1986
|
+
ctx,
|
|
1987
|
+
signal,
|
|
1988
|
+
tuiControl
|
|
1989
|
+
? (event) => {
|
|
1990
|
+
try {
|
|
1991
|
+
activity.updateWorkflow(
|
|
1992
|
+
toolCallId,
|
|
1993
|
+
validation.value,
|
|
1994
|
+
event,
|
|
1995
|
+
);
|
|
1996
|
+
} catch {
|
|
1997
|
+
// Presentation state must never alter workflow execution.
|
|
1998
|
+
}
|
|
1631
1999
|
}
|
|
1632
|
-
|
|
1633
|
-
: undefined,
|
|
1634
|
-
);
|
|
1635
|
-
} catch (error) {
|
|
1636
|
-
if (tuiControl) activity.failWorkflow(toolCallId);
|
|
1637
|
-
throw error;
|
|
1638
|
-
}
|
|
1639
|
-
let display: ReturnType<typeof activity.finalizeWorkflow>;
|
|
1640
|
-
if (tuiControl) {
|
|
1641
|
-
try {
|
|
1642
|
-
display = activity.finalizeWorkflow(
|
|
1643
|
-
toolCallId,
|
|
1644
|
-
validation.value,
|
|
1645
|
-
payload,
|
|
2000
|
+
: undefined,
|
|
1646
2001
|
);
|
|
1647
|
-
} catch {
|
|
1648
|
-
activity.failWorkflow(toolCallId);
|
|
2002
|
+
} catch (error) {
|
|
2003
|
+
if (tuiControl) activity.failWorkflow(toolCallId);
|
|
2004
|
+
throw error;
|
|
1649
2005
|
}
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
2006
|
+
if (
|
|
2007
|
+
["completed", "discarded", "rejected"].includes(String(payload.state))
|
|
2008
|
+
) {
|
|
2009
|
+
await deactivateStage();
|
|
2010
|
+
} else if (validation.value.stage === "abel-design") {
|
|
2011
|
+
// Design starts/statuses through the command surface, then switches to
|
|
2012
|
+
// its bounded evidence and artifact packet surface for the next turn.
|
|
2013
|
+
registerDispatchTool("packet");
|
|
2014
|
+
}
|
|
2015
|
+
let display: ReturnType<typeof activity.finalizeWorkflow>;
|
|
2016
|
+
if (tuiControl) {
|
|
2017
|
+
try {
|
|
2018
|
+
display = activity.finalizeWorkflow(
|
|
2019
|
+
toolCallId,
|
|
2020
|
+
validation.value,
|
|
2021
|
+
payload,
|
|
2022
|
+
);
|
|
2023
|
+
} catch {
|
|
2024
|
+
activity.failWorkflow(toolCallId);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
return {
|
|
2028
|
+
content: [{ type: "text" as const, text: JSON.stringify(payload) }],
|
|
2029
|
+
details: display
|
|
2030
|
+
? { ...payload, [ACTIVITY_DETAILS_KEY]: display }
|
|
2031
|
+
: payload,
|
|
2032
|
+
};
|
|
2033
|
+
},
|
|
2034
|
+
renderCall(args: unknown, theme: Theme, context: unknown) {
|
|
2035
|
+
return renderActivityCall(
|
|
2036
|
+
args,
|
|
2037
|
+
theme,
|
|
2038
|
+
context as Parameters<typeof renderActivityCall>[2],
|
|
2039
|
+
);
|
|
2040
|
+
},
|
|
2041
|
+
renderResult(
|
|
2042
|
+
result: AgentToolResult<unknown>,
|
|
2043
|
+
options: ToolRenderResultOptions,
|
|
2044
|
+
theme: Theme,
|
|
2045
|
+
context: unknown,
|
|
2046
|
+
) {
|
|
2047
|
+
return renderActivityResult(
|
|
2048
|
+
result,
|
|
2049
|
+
options,
|
|
2050
|
+
theme,
|
|
2051
|
+
context as Parameters<typeof renderActivityResult>[3],
|
|
2052
|
+
);
|
|
2053
|
+
},
|
|
2054
|
+
} as never);
|
|
2055
|
+
};
|
|
2056
|
+
registerDispatchTool("packet");
|
|
1679
2057
|
|
|
1680
2058
|
pi.on("input", (event) => {
|
|
1681
2059
|
pendingPrompt = invokedPrompt(event.text);
|
|
@@ -1687,6 +2065,10 @@ export function registerWorkflowControl(
|
|
|
1687
2065
|
const verified =
|
|
1688
2066
|
prompt && isVerifiedStageInvocation(pi, activation, prompt, event.prompt);
|
|
1689
2067
|
if (verified) {
|
|
2068
|
+
if (activePrompt === "abel-design" && prompt !== "abel-design") {
|
|
2069
|
+
restoreDesignTools();
|
|
2070
|
+
}
|
|
2071
|
+
registerDispatchTool(prompt === "abel-diagnose" ? "packet" : "command");
|
|
1690
2072
|
activePrompt = prompt;
|
|
1691
2073
|
const sessionId = ctx.sessionManager?.getSessionId?.();
|
|
1692
2074
|
if (typeof sessionId === "string") {
|
|
@@ -1699,6 +2081,7 @@ export function registerWorkflowControl(
|
|
|
1699
2081
|
parentPayloadBridge.install(ctx.model, ctx.modelRegistry);
|
|
1700
2082
|
}
|
|
1701
2083
|
if (prompt) activateDispatcher(pi, activation, prompt, event.prompt);
|
|
2084
|
+
if (verified && prompt === "abel-design") enforceDesignTools();
|
|
1702
2085
|
});
|
|
1703
2086
|
pi.on("model_select", (event, ctx) => {
|
|
1704
2087
|
const sessionId = ctx.sessionManager?.getSessionId?.();
|