@akagilnc/pi-workflow-roles 0.1.3758 → 0.1.3783
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 +3 -2
- package/README.zh-CN.md +3 -2
- package/dist/acp-host/production-host.js +1735 -948
- package/dist/collector-config.js +0 -1
- package/dist/collector-github.js +199 -2
- package/dist/collector-identity.js +128 -41
- package/dist/collector-ledger.js +38 -9
- package/dist/collector-receipt.js +19 -7
- package/dist/collector-role.js +330 -369
- package/dist/collector-target.js +169 -0
- package/dist/collector-tool-schemas.js +51 -14
- package/dist/headless-host/description.js +77 -0
- package/dist/headless-host/mcp-relay.mjs +119 -0
- package/dist/headless-host/production-host.js +26590 -0
- package/dist/host-descriptions.js +44 -3
- package/dist/package-contracts/collector-output.js +32 -0
- package/dist/package-contracts/terminating-infrastructure.js +13 -12
- package/dist/pi/role-turn-host.js +1 -2
- package/dist/public-cli/github-remote.js +45 -0
- package/dist/public-cli/invocation.js +53 -54
- package/dist/public-cli/load-production-external-host.js +19 -0
- package/dist/public-cli/load-production-headless-host.js +36 -0
- package/dist/public-cli/main.js +857 -310
- package/dist/public-cli/option-definitions.js +6 -4
- package/dist/public-cli/run-lifecycle.js +3 -3
- package/dist/public-cli/settlement.js +83 -6
- package/dist/public-role-summons.js +40 -5
- package/dist/role-runtime.js +137 -7
- package/dist/submission-correctable-error.js +24 -0
- package/extensions/role-runtime.ts +0 -1
- package/package.json +1 -1
- package/scripts/build-package.mjs +28 -0
- package/src/acp-host/role-envelope.ts +146 -103
- package/src/acp-host/role-turn-host.ts +12 -0
- package/src/collector-config.ts +0 -1
- package/src/collector-github.ts +236 -2
- package/src/collector-identity.ts +148 -40
- package/src/collector-ledger.ts +48 -10
- package/src/collector-receipt.ts +33 -14
- package/src/collector-role.ts +376 -450
- package/src/collector-target.ts +207 -0
- package/src/collector-tool-schemas.ts +62 -15
- package/src/headless-host/description.ts +123 -0
- package/src/headless-host/production-host.ts +75 -0
- package/src/headless-host/role-turn-host.ts +424 -0
- package/src/host-contracts.ts +2 -1
- package/src/host-descriptions.ts +53 -6
- package/src/package-contracts/collector-output.ts +72 -0
- package/src/package-contracts/terminating-infrastructure.ts +24 -13
- package/src/pi/role-turn-host.ts +1 -2
- package/src/public-cli/cli.ts +12 -3
- package/src/public-cli/collector-run.ts +3 -2
- package/src/public-cli/github-remote.ts +45 -0
- package/src/public-cli/invocation.ts +60 -59
- package/src/public-cli/load-production-external-host.ts +29 -0
- package/src/public-cli/load-production-headless-host.ts +48 -0
- package/src/public-cli/main.ts +5 -0
- package/src/public-cli/option-definitions.ts +6 -4
- package/src/public-cli/run-lifecycle.ts +2 -2
- package/src/public-cli/settlement.ts +82 -6
- package/src/public-role-summons.ts +62 -9
- package/src/role-runtime.ts +166 -13
- package/src/submission-correctable-error.ts +38 -0
|
@@ -65,6 +65,12 @@ export type PublicSummonRequest = {
|
|
|
65
65
|
* Never folded into argv / parent-run lookup keys (ADR 0079 卷宗指针 stays pure).
|
|
66
66
|
*/
|
|
67
67
|
readonly gateReviewInstruction?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Parent-invocation host axis (#617 / #645). Nested institutional legs inherit
|
|
70
|
+
* the live parent `--host` so a single public entry does not require mutating
|
|
71
|
+
* persistent seat host. Seat model/thinking stay on the officer seat table.
|
|
72
|
+
*/
|
|
73
|
+
readonly host?: string;
|
|
68
74
|
/**
|
|
69
75
|
* Offline test inject — same face as public CLI env.roleTurnHost. Production
|
|
70
76
|
* summons leave this unset and use the seat-resolved host.
|
|
@@ -211,14 +217,13 @@ async function createSummonEnv(options: {
|
|
|
211
217
|
const hostName = options.seat.host ?? "pi";
|
|
212
218
|
let roleTurnHost = piHost;
|
|
213
219
|
if (hostName !== "pi") {
|
|
214
|
-
const {
|
|
215
|
-
const {
|
|
216
|
-
"./public-cli/load-production-
|
|
220
|
+
const { lookupHostFamily } = await import("./host-descriptions.ts");
|
|
221
|
+
const { loadProductionExternalHostFactory } = await import(
|
|
222
|
+
"./public-cli/load-production-external-host.ts"
|
|
217
223
|
);
|
|
218
|
-
// #729:
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
if (lookupHostDescription(hostName) === undefined) {
|
|
224
|
+
// #729 / #645: description tables are the sole host authority — family
|
|
225
|
+
// dispatch (ACP vs headless) is data-driven. Unregistered keys fail closed.
|
|
226
|
+
if (lookupHostFamily(hostName) === undefined) {
|
|
222
227
|
throw new Error(
|
|
223
228
|
`public role summons host unregistered: host=${hostName} seat=${options.role}`,
|
|
224
229
|
);
|
|
@@ -226,7 +231,7 @@ async function createSummonEnv(options: {
|
|
|
226
231
|
let hostPromise: Promise<RoleTurnHost> | undefined;
|
|
227
232
|
roleTurnHost = {
|
|
228
233
|
executeTurn: async (request) => {
|
|
229
|
-
hostPromise ??=
|
|
234
|
+
hostPromise ??= loadProductionExternalHostFactory(options.packageRoot, hostName).then(
|
|
230
235
|
(create) =>
|
|
231
236
|
create({
|
|
232
237
|
packageRoot: options.packageRoot,
|
|
@@ -286,7 +291,10 @@ export async function summonPublicRole(
|
|
|
286
291
|
const credentials =
|
|
287
292
|
options.credentials ?? (await loadCredentialProviders(agentDir));
|
|
288
293
|
const config = await loadPublicCliConfig(home);
|
|
289
|
-
|
|
294
|
+
// Nested legs inherit parent invocation host when provided (#645 review path).
|
|
295
|
+
// Model/thinking stay on the officer seat — host-only inherit (seat-table law).
|
|
296
|
+
const invocation = options.host === undefined ? undefined : { host: options.host };
|
|
297
|
+
const seat = resolveEffectiveSeat(config, options.role, credentials, invocation);
|
|
290
298
|
const env = {
|
|
291
299
|
...(await createSummonEnv({
|
|
292
300
|
role: options.role,
|
|
@@ -412,6 +420,47 @@ export async function summonPublicRole(
|
|
|
412
420
|
}
|
|
413
421
|
|
|
414
422
|
/** Gate officer summons: notary/auditor via --source-run; inspector via pointer instruction. */
|
|
423
|
+
/**
|
|
424
|
+
* Read parent invocation host so nested officers inherit live --host only (#645).
|
|
425
|
+
* Gate source runs necessarily own invocation.json — missing page, bad JSON, or
|
|
426
|
+
* bad shape stay loud (failure-honesty). Absent host field is lawful (seat default).
|
|
427
|
+
*/
|
|
428
|
+
async function parentInvocationHost(sourceRunDirectory: string): Promise<string | undefined> {
|
|
429
|
+
const { readFile } = await import("node:fs/promises");
|
|
430
|
+
const path = join(sourceRunDirectory, "invocation.json");
|
|
431
|
+
let text: string;
|
|
432
|
+
try {
|
|
433
|
+
text = await readFile(path, "utf8");
|
|
434
|
+
} catch (error) {
|
|
435
|
+
const code =
|
|
436
|
+
error instanceof Error && "code" in error
|
|
437
|
+
? (error as NodeJS.ErrnoException).code
|
|
438
|
+
: undefined;
|
|
439
|
+
throw new Error(
|
|
440
|
+
`parent invocation.json required for gate source run at ${path}: ${code ?? (error instanceof Error ? error.message : String(error))}`,
|
|
441
|
+
{ cause: error },
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
let raw: unknown;
|
|
445
|
+
try {
|
|
446
|
+
raw = JSON.parse(text) as unknown;
|
|
447
|
+
} catch (error) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`parent invocation.json unreadable at ${path}: ${error instanceof Error ? error.message : String(error)}`,
|
|
450
|
+
{ cause: error },
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
454
|
+
throw new Error(`parent invocation.json has non-object shape at ${path}`);
|
|
455
|
+
}
|
|
456
|
+
const host = (raw as Record<string, unknown>).host;
|
|
457
|
+
if (host === undefined) return undefined;
|
|
458
|
+
if (typeof host !== "string" || host.trim() === "") {
|
|
459
|
+
throw new Error(`parent invocation.json host must be a non-empty string at ${path}`);
|
|
460
|
+
}
|
|
461
|
+
return host;
|
|
462
|
+
}
|
|
463
|
+
|
|
415
464
|
export async function summonGateOfficer(options: {
|
|
416
465
|
readonly officer: "inspector" | "notary" | "auditor";
|
|
417
466
|
readonly sourceRunDirectory: string;
|
|
@@ -452,6 +501,9 @@ export async function summonGateOfficer(options: {
|
|
|
452
501
|
submission: options.submission,
|
|
453
502
|
});
|
|
454
503
|
}
|
|
504
|
+
// Inherit parent live --host only so review roundtrip stays on one host without
|
|
505
|
+
// mutating persistent seat tables (#645). Officer model/thinking stay seat-owned.
|
|
506
|
+
const parentHost = await parentInvocationHost(options.sourceRunDirectory);
|
|
455
507
|
const common = {
|
|
456
508
|
cwd: options.cwd,
|
|
457
509
|
...(home === undefined ? {} : { home }),
|
|
@@ -462,6 +514,7 @@ export async function summonGateOfficer(options: {
|
|
|
462
514
|
...(gateReviewInstruction === undefined
|
|
463
515
|
? {}
|
|
464
516
|
: { gateReviewInstruction }),
|
|
517
|
+
...(parentHost === undefined ? {} : { host: parentHost }),
|
|
465
518
|
...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
|
|
466
519
|
...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
|
|
467
520
|
} as const;
|
package/src/role-runtime.ts
CHANGED
|
@@ -43,12 +43,10 @@ import type { AnyCanonicalSkillBinding } from "./canonical-skill-binding.ts";
|
|
|
43
43
|
import type { CollectorClock } from "./collector-evidence.ts";
|
|
44
44
|
import type { CollectorGitHubTransport } from "./collector-github.ts";
|
|
45
45
|
import {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
COLLECTOR_READ_TOOL,
|
|
49
|
-
COLLECTOR_REQUEST_TOOL,
|
|
50
|
-
COLLECTOR_WAIT_TOOL,
|
|
46
|
+
COLLECTOR_REQUIRED_TOOLS,
|
|
47
|
+
COLLECTOR_TRANSPORT_FLAGS,
|
|
51
48
|
createCollectorRoleRuntime,
|
|
49
|
+
type CollectorActivation,
|
|
52
50
|
} from "./collector-role.ts";
|
|
53
51
|
import type { ComplianceDecision } from "./compliance-transport.ts";
|
|
54
52
|
import { createDoctorRoleRuntime } from "./doctor-role.ts";
|
|
@@ -449,7 +447,9 @@ type ActivationRuntime = {
|
|
|
449
447
|
decodeReviewerAdmitted(): ReviewerAdmittedInputs;
|
|
450
448
|
/** Envelope stores live parent activation for agent_start prompt assembly. */
|
|
451
449
|
bindReviewerParent(activation: ReviewerActivation): void;
|
|
452
|
-
collector: {
|
|
450
|
+
collector: {
|
|
451
|
+
activate(context: HostContext, event: { reason: string }): Promise<void>;
|
|
452
|
+
};
|
|
453
453
|
doctor: { activate(): Promise<void> };
|
|
454
454
|
notary: {
|
|
455
455
|
activate(admitted?: import("./notary-role.ts").NotaryAdmittedTicket): Promise<void>;
|
|
@@ -629,7 +629,6 @@ export type RoleRuntimeDependencies = {
|
|
|
629
629
|
createMergerGitState?(repositoryRoot: string): MergerRoleDependencies["gitState"];
|
|
630
630
|
auditDoctorCompliance?(options: { context: HostContext; signal?: AbortSignal }): Promise<ComplianceDecision>;
|
|
631
631
|
createCollectorClock?(): CollectorClock;
|
|
632
|
-
collectorPackageExtensionPath?: string;
|
|
633
632
|
createNavigatorAttendance?(options: { context: HostContext; role: string; phase: NavigatorPhase; subjectKey: string; subject: string; authority: string; contextError?: unknown; invocationId: string; onEvent: (event: import("./navigator-attendance.ts").NavigatorEvent, report: import("./navigator-attendance.ts").NavigatorReport) => void | Promise<void> }): NavigatorAttendanceDependency | Promise<NavigatorAttendanceDependency>;
|
|
634
633
|
loadNavigatorWorkContext?(options: { context: HostContext; role: string; phase: NavigatorPhase; getFlag?: (name: string) => unknown }): Promise<NavigatorWorkContext>;
|
|
635
634
|
loadCanonicalSkillBinding?(
|
|
@@ -1144,6 +1143,10 @@ export function createRoleRuntimeExtension(
|
|
|
1144
1143
|
for (const flag of GLEANER_LEFT_TRANSPORT_FLAGS) {
|
|
1145
1144
|
roleHost.registerFlag(flag.name, flag.definition);
|
|
1146
1145
|
}
|
|
1146
|
+
// Collector transport flags: shared envelope owns registration (ADR 0018 / #676 E).
|
|
1147
|
+
for (const flag of COLLECTOR_TRANSPORT_FLAGS) {
|
|
1148
|
+
roleHost.registerFlag(flag.name, flag.definition);
|
|
1149
|
+
}
|
|
1147
1150
|
|
|
1148
1151
|
let admitted = false;
|
|
1149
1152
|
let selectedRole: string | undefined;
|
|
@@ -1305,10 +1308,46 @@ export function createRoleRuntimeExtension(
|
|
|
1305
1308
|
}),
|
|
1306
1309
|
};
|
|
1307
1310
|
}
|
|
1311
|
+
// #676 E / J1: collector materials + drift gates share this envelope hook (no parallel register).
|
|
1312
|
+
if (role === "collector" && activeCollector !== undefined) {
|
|
1313
|
+
const options = event.systemPromptOptions;
|
|
1314
|
+
if (options.skills && options.skills.length > 0) {
|
|
1315
|
+
failInfrastructure(
|
|
1316
|
+
activeCollector.ledger.latchFatal("通进司检测到系统提示中的环境 skills"),
|
|
1317
|
+
ctx,
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
if (options.contextFiles && options.contextFiles.length > 0) {
|
|
1321
|
+
failInfrastructure(
|
|
1322
|
+
activeCollector.ledger.latchFatal("通进司检测到系统提示中的环境 context files"),
|
|
1323
|
+
ctx,
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
if (
|
|
1327
|
+
typeof options.appendSystemPrompt === "string"
|
|
1328
|
+
&& options.appendSystemPrompt.trim().length > 0
|
|
1329
|
+
) {
|
|
1330
|
+
failInfrastructure(
|
|
1331
|
+
activeCollector.ledger.latchFatal("通进司检测到 appendSystemPrompt 漂移"),
|
|
1332
|
+
ctx,
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
if (!collectorFirstDispatchDone) {
|
|
1336
|
+
collectorFirstDispatchDone = true;
|
|
1337
|
+
activeCollector.ledger.recordActivation(activeCollector.clock);
|
|
1338
|
+
}
|
|
1339
|
+
return {
|
|
1340
|
+
systemPrompt: collectorBusiness.assembleMaterials(activeCollector, event.systemPrompt),
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1308
1343
|
});
|
|
1309
1344
|
roleHost.on("tool_result", async (event) => {
|
|
1310
1345
|
const role = selectedRole;
|
|
1311
1346
|
if (role === undefined) return;
|
|
1347
|
+
// #676 E / J1: collector operational bookkeeping on the shared tool_result seam.
|
|
1348
|
+
if (role === "collector" && activeCollector !== undefined) {
|
|
1349
|
+
collectorBusiness.onToolResult(activeCollector, event);
|
|
1350
|
+
}
|
|
1312
1351
|
const pendingInfra = pendingInfrastructureFailures.get(event.toolCallId);
|
|
1313
1352
|
const isRoleInfrastructureFailure = pendingInfra !== undefined;
|
|
1314
1353
|
if (pendingInfra !== undefined) pendingInfrastructureFailures.delete(event.toolCallId);
|
|
@@ -1439,6 +1478,16 @@ export function createRoleRuntimeExtension(
|
|
|
1439
1478
|
priorFetch = undefined;
|
|
1440
1479
|
fetchWrapped = false;
|
|
1441
1480
|
}
|
|
1481
|
+
// #676 J4: collector fatal latch must surface nonzero exit on shutdown (envelope-owned).
|
|
1482
|
+
if (
|
|
1483
|
+
selectedRole === "collector"
|
|
1484
|
+
&& activeCollector !== undefined
|
|
1485
|
+
&& activeCollector.ledger.fatal
|
|
1486
|
+
) {
|
|
1487
|
+
if (process.exitCode === undefined || process.exitCode === 0) {
|
|
1488
|
+
process.exitCode = 1;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1442
1491
|
// Flush any still-pending affirmative attendance before teardown. Accepted
|
|
1443
1492
|
// grace-timeout paths normally emit on agent_settled; abort can skip that hook.
|
|
1444
1493
|
const presentation = pendingNavigatorPresentation;
|
|
@@ -1630,7 +1679,11 @@ export function createRoleRuntimeExtension(
|
|
|
1630
1679
|
completedMerge(mergeCommitId, automaticMergeTreeId) { if (!sessionMergerGitState) throw new Error("Merger runtime dependencies are not configured"); return sessionMergerGitState.completedMerge(mergeCommitId, automaticMergeTreeId); },
|
|
1631
1680
|
},
|
|
1632
1681
|
}, hostActions);
|
|
1633
|
-
|
|
1682
|
+
// #676 E: shared envelope owns collector lifecycle (mode/fork, tool surface,
|
|
1683
|
+
// event gates). Role module supplies business activate/tools/materials only.
|
|
1684
|
+
let activeCollector: CollectorActivation | undefined;
|
|
1685
|
+
let collectorFirstDispatchDone = false;
|
|
1686
|
+
const collectorBusiness = createCollectorRoleRuntime(
|
|
1634
1687
|
roleHost,
|
|
1635
1688
|
{
|
|
1636
1689
|
async loadSoul() {
|
|
@@ -1664,14 +1717,114 @@ export function createRoleRuntimeExtension(
|
|
|
1664
1717
|
...(dependencies.createCollectorClock === undefined
|
|
1665
1718
|
? {}
|
|
1666
1719
|
: { createClock: dependencies.createCollectorClock }),
|
|
1667
|
-
...(dependencies.collectorPackageExtensionPath === undefined
|
|
1668
|
-
? {}
|
|
1669
|
-
: {
|
|
1670
|
-
packageExtensionPath: dependencies.collectorPackageExtensionPath,
|
|
1671
|
-
}),
|
|
1672
1720
|
},
|
|
1673
1721
|
hostActions,
|
|
1674
1722
|
);
|
|
1723
|
+
// #676 J1: business tools + tool_call gate register only behind admission/activation.
|
|
1724
|
+
// before_agent_start / tool_result collector branches live on the shared envelope hooks above.
|
|
1725
|
+
//
|
|
1726
|
+
// #676 J4 dispositions for guards removed from the role-private collector module:
|
|
1727
|
+
// - skills/contextFiles/appendSystemPrompt fail-closed → shared before_agent_start (above).
|
|
1728
|
+
// - ambient skill/prompt/template commands → activate (below).
|
|
1729
|
+
// - session_shutdown fatal exitCode → shared session_shutdown (above).
|
|
1730
|
+
// - subsequent-input latchFatal + fixed-kickoff rewrite → intentionally not migrated:
|
|
1731
|
+
// #676 materials-first multi-turn abolished the single-shot fixed kickoff; multi-turn
|
|
1732
|
+
// observe/request/wait requires later inputs. Judge r1: fixed-kickoff equality delete is authorized.
|
|
1733
|
+
// - tool sourceInfo path override check → intentionally not migrated: depended on
|
|
1734
|
+
// packageExtensionPath deleted under ADR 0018 / #676 E envelope ownership; uniqueness
|
|
1735
|
+
// + setActiveTools inventory checks remain on activate.
|
|
1736
|
+
let collectorToolCallRegistered = false;
|
|
1737
|
+
const collector = {
|
|
1738
|
+
async activate(context: HostContext, event: { reason: string }) {
|
|
1739
|
+
activeCollector = undefined;
|
|
1740
|
+
collectorFirstDispatchDone = false;
|
|
1741
|
+
// Envelope-owned mode / fork-reload gates (not role-private lifecycle).
|
|
1742
|
+
if (context.mode !== "print" && context.mode !== "json") {
|
|
1743
|
+
throw new Error(
|
|
1744
|
+
`Collector supports only print or json mode (got ${context.mode})`,
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
if (event.reason === "fork" || event.reason === "reload") {
|
|
1748
|
+
throw new Error(
|
|
1749
|
+
`Collector does not support session_start reason ${event.reason}`,
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
// #676 J4: ambient skill/prompt/template commands fail closed at activation.
|
|
1753
|
+
const commands = roleHost.getCommands?.() ?? [];
|
|
1754
|
+
const ambientCommands = commands.filter((command) => {
|
|
1755
|
+
const name = command.name.toLowerCase();
|
|
1756
|
+
return (
|
|
1757
|
+
name.includes("skill")
|
|
1758
|
+
|| name.includes("prompt")
|
|
1759
|
+
|| name.startsWith("template")
|
|
1760
|
+
);
|
|
1761
|
+
});
|
|
1762
|
+
if (ambientCommands.length > 0) {
|
|
1763
|
+
throw new Error(
|
|
1764
|
+
`Collector detected ambient instruction commands: ${
|
|
1765
|
+
ambientCommands.map((c) => c.name).join(", ")
|
|
1766
|
+
}`,
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
// Business tools behind admission barrier (inert-without-role invariant).
|
|
1770
|
+
// First activation: fail closed if a required name is already occupied.
|
|
1771
|
+
// Later activations reuse the once-registered tools (registerBusinessTools is idempotent).
|
|
1772
|
+
const preExisting = roleHost.getAllTools();
|
|
1773
|
+
const alreadyRegistered = COLLECTOR_REQUIRED_TOOLS.every((required) =>
|
|
1774
|
+
preExisting.some((tool) => tool.name === required),
|
|
1775
|
+
);
|
|
1776
|
+
if (!alreadyRegistered) {
|
|
1777
|
+
for (const required of COLLECTOR_REQUIRED_TOOLS) {
|
|
1778
|
+
const prior = preExisting.filter((tool) => tool.name === required);
|
|
1779
|
+
if (prior.length > 0) {
|
|
1780
|
+
throw new Error(`Collector required tool name collision: ${required}`);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
collectorBusiness.registerBusinessTools(() => activeCollector);
|
|
1785
|
+
const allTools = roleHost.getAllTools();
|
|
1786
|
+
for (const required of COLLECTOR_REQUIRED_TOOLS) {
|
|
1787
|
+
const matches = allTools.filter((tool) => tool.name === required);
|
|
1788
|
+
if (matches.length === 0) {
|
|
1789
|
+
throw new Error(`Collector required tool missing: ${required}`);
|
|
1790
|
+
}
|
|
1791
|
+
if (matches.length > 1) {
|
|
1792
|
+
throw new Error(`Collector required tool name collision: ${required}`);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
roleHost.setActiveTools([...COLLECTOR_REQUIRED_TOOLS]);
|
|
1796
|
+
const active = new Set(roleHost.getActiveTools());
|
|
1797
|
+
for (const required of COLLECTOR_REQUIRED_TOOLS) {
|
|
1798
|
+
if (!active.has(required)) {
|
|
1799
|
+
throw new Error(`Collector failed to activate required tool ${required}`);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
for (const name of active) {
|
|
1803
|
+
if (!(COLLECTOR_REQUIRED_TOOLS as readonly string[]).includes(name)) {
|
|
1804
|
+
throw new Error(`Collector active tool surface includes unexpected ${name}`);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
// Seat-scoped tool_call gate — registered only after collector admission (no install-time tool_call).
|
|
1808
|
+
if (!collectorToolCallRegistered) {
|
|
1809
|
+
collectorToolCallRegistered = true;
|
|
1810
|
+
roleHost.on("tool_call", (toolEvent) => {
|
|
1811
|
+
if (activeCollector === undefined || selectedRole !== "collector") return;
|
|
1812
|
+
if (!(COLLECTOR_REQUIRED_TOOLS as readonly string[]).includes(toolEvent.toolName)) {
|
|
1813
|
+
return {
|
|
1814
|
+
block: true,
|
|
1815
|
+
reason: `通进司禁用工具 ${toolEvent.toolName}`,
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
return collectorBusiness.onToolCall(activeCollector, toolEvent);
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
const activation = await collectorBusiness.activate(context);
|
|
1822
|
+
if (activation.ledger.activationRecorded) {
|
|
1823
|
+
collectorFirstDispatchDone = true;
|
|
1824
|
+
}
|
|
1825
|
+
activeCollector = activation;
|
|
1826
|
+
},
|
|
1827
|
+
};
|
|
1675
1828
|
|
|
1676
1829
|
const clock = dependencies.activationClock ?? (() => new Date().toISOString());
|
|
1677
1830
|
const writeTrace = dependencies.activationTraceWriter ?? writeActivationTraceRecord;
|
|
@@ -32,3 +32,41 @@ export function isCorrectableExecuteError(error: unknown): boolean {
|
|
|
32
32
|
|| error instanceof WorkerUnfinishedReasonReminderError
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Durable projection for ACP envelope tool catches only.
|
|
38
|
+
* Pi path keeps native throw → isError toolResult (no shared projection consumer).
|
|
39
|
+
*/
|
|
40
|
+
export type CorrectableExecuteRejectionProjection = {
|
|
41
|
+
readonly diagnostic: string;
|
|
42
|
+
readonly details: Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* One authority for correctable execute → diagnostic text + structured details.
|
|
47
|
+
* Consumed by the ACP envelope tool catch; host adapters wrap transport shape only.
|
|
48
|
+
*/
|
|
49
|
+
export function projectCorrectableExecuteRejection(
|
|
50
|
+
error: unknown,
|
|
51
|
+
): CorrectableExecuteRejectionProjection {
|
|
52
|
+
const diagnostic = error instanceof Error ? error.message : String(error);
|
|
53
|
+
if (error instanceof GatekeeperDecisionError) {
|
|
54
|
+
return { diagnostic, details: { ...(error.result as Record<string, unknown>) } };
|
|
55
|
+
}
|
|
56
|
+
if (
|
|
57
|
+
error instanceof WorkerCommitReminderError
|
|
58
|
+
|| error instanceof WorkerPrefixReminderError
|
|
59
|
+
|| error instanceof WorkerUnfinishedReasonReminderError
|
|
60
|
+
) {
|
|
61
|
+
return { diagnostic, details: { code: error.code } };
|
|
62
|
+
}
|
|
63
|
+
if (typeof (error as { code?: unknown }).code === "string") {
|
|
64
|
+
return { diagnostic, details: { code: (error as { code: string }).code } };
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
diagnostic,
|
|
68
|
+
details: {
|
|
69
|
+
code: error instanceof Error && error.name ? error.name : "correctable-submission-error",
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|