@akagilnc/pi-workflow-roles 0.1.5015 → 0.1.5063
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 +2 -2
- package/README.zh-CN.md +2 -2
- package/THIRD_PARTY_NOTICES.md +8 -8
- package/dist/acp-host/production-host.js +525 -978
- package/dist/headless-host/description.js +0 -2
- package/dist/headless-host/production-host.js +528 -1014
- package/dist/host-descriptions.js +2 -3
- package/dist/migrate-book-topology.js +7 -12
- package/dist/packaged-role-registry.js +20 -18
- package/dist/pi/role-turn-host.js +3 -27
- package/dist/public-cli/cli.js +23 -2
- package/dist/public-cli/countersign-run.js +13 -8
- package/dist/public-cli/instruction-seat-run.js +75 -107
- package/dist/public-cli/invocation.js +13 -32
- package/dist/public-cli/machine-method-skills.js +150 -0
- package/dist/public-cli/main.js +549 -679
- package/dist/public-cli/post-admission.js +10 -5
- package/dist/public-cli/registry.js +2 -1
- package/dist/public-cli/run-lifecycle.js +12 -15
- package/dist/public-cli/seat-ticket-binding.js +5 -8
- package/dist/public-cli/settlement.js +12 -99
- package/dist/public-cli/turn-request.js +16 -8
- package/dist/public-role-summons.js +8 -3
- package/package.json +5 -1
- package/resources/countersign-ticket-issue.md +5 -0
- package/resources/diarist-collect.md +1 -0
- package/resources/secretariat-ticket-identity.md +15 -0
- package/scripts/build-package.mjs +1 -6
- package/src/headless-host/description.ts +0 -3
- package/src/headless-host/role-turn-host.ts +2 -23
- package/src/host-contracts.ts +2 -23
- package/src/host-descriptions.ts +2 -3
- package/src/packaged-role-registry.ts +17 -16
- package/src/pi/adapter.ts +0 -27
- package/src/pi/role-turn-host.ts +3 -31
- package/src/public-cli/cli.ts +24 -2
- package/src/public-cli/countersign-run.ts +17 -10
- package/src/public-cli/instruction-seat-run.ts +75 -105
- package/src/public-cli/invocation.ts +10 -38
- package/src/public-cli/machine-method-skills.ts +153 -0
- package/src/public-cli/post-admission.ts +12 -6
- package/src/public-cli/registry.ts +1 -0
- package/src/public-cli/run-lifecycle.ts +12 -13
- package/src/public-cli/seat-ticket-binding.ts +5 -8
- package/src/public-cli/settlement.ts +6 -138
- package/src/public-cli/turn-request.ts +24 -16
- package/src/public-role-summons.ts +16 -3
- package/src/reviewer-role.ts +1 -12
- package/src/role-envelope.ts +0 -13
- package/src/role-runtime-dependencies.ts +0 -13
- package/src/role-runtime.ts +4 -56
- package/src/worker-role.ts +1 -81
- package/dist/method-host-plugin/.claude-plugin/plugin.json +0 -5
- package/dist/method-host-plugin/skills/ak-cross-m-review/CONTEXT.md +0 -48
- package/dist/method-host-plugin/skills/ak-cross-m-review/LICENSE +0 -21
- package/dist/method-host-plugin/skills/ak-cross-m-review/SKILL.md +0 -170
- package/dist/method-host-plugin/skills/ak-cross-m-review/prompts/cmr-completeness.md +0 -118
- package/dist/method-host-plugin/skills/ak-cross-m-review/prompts/cmr-reviewer.md +0 -128
- package/dist/method-host-plugin/skills/ak-cross-m-review/provenance.json +0 -41
- package/dist/method-host-plugin/skills/diagnosing-bugs/SKILL.md +0 -134
- package/dist/method-host-plugin/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/dist/method-host-plugin/skills/diagnosing-bugs/provenance.json +0 -31
- package/dist/method-host-plugin/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -41
- package/dist/method-host-plugin/skills/resolving-merge-conflicts/SKILL.md +0 -14
- package/dist/method-host-plugin/skills/resolving-merge-conflicts/agents/openai.yaml +0 -3
- package/dist/method-host-plugin/skills/resolving-merge-conflicts/provenance.json +0 -26
- package/dist/method-host-plugin/skills/tdd/SKILL.md +0 -38
- package/dist/method-host-plugin/skills/tdd/agents/openai.yaml +0 -3
- package/dist/method-host-plugin/skills/tdd/mocking.md +0 -59
- package/dist/method-host-plugin/skills/tdd/provenance.json +0 -36
- package/dist/method-host-plugin/skills/tdd/tests.md +0 -77
- package/dist/package-resources/method-skill.js +0 -259
- package/resources/method-host-plugin/.claude-plugin/plugin.json +0 -5
- package/resources/methods/ak-cross-m-review/CONTEXT.md +0 -48
- package/resources/methods/ak-cross-m-review/LICENSE +0 -21
- package/resources/methods/ak-cross-m-review/SKILL.md +0 -170
- package/resources/methods/ak-cross-m-review/prompts/cmr-completeness.md +0 -118
- package/resources/methods/ak-cross-m-review/prompts/cmr-reviewer.md +0 -128
- package/resources/methods/ak-cross-m-review/provenance.json +0 -41
- package/resources/methods/diagnosing-bugs/SKILL.md +0 -134
- package/resources/methods/diagnosing-bugs/agents/openai.yaml +0 -3
- package/resources/methods/diagnosing-bugs/provenance.json +0 -31
- package/resources/methods/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -41
- package/resources/methods/resolving-merge-conflicts/SKILL.md +0 -14
- package/resources/methods/resolving-merge-conflicts/agents/openai.yaml +0 -3
- package/resources/methods/resolving-merge-conflicts/provenance.json +0 -26
- package/resources/methods/tdd/SKILL.md +0 -38
- package/resources/methods/tdd/agents/openai.yaml +0 -3
- package/resources/methods/tdd/mocking.md +0 -59
- package/resources/methods/tdd/provenance.json +0 -36
- package/resources/methods/tdd/tests.md +0 -77
- package/src/canonical-skill-binding.ts +0 -118
- package/src/host-native-method.ts +0 -185
- package/src/package-resources/method-skill-binding.ts +0 -60
- package/src/package-resources/method-skill.ts +0 -363
|
@@ -10,15 +10,10 @@ import type { DurablePrincipalAuthority, RoleTurnRequest } from "../host-contrac
|
|
|
10
10
|
import { isSafePositiveTicketNumber, readBoardTicketNumber } from "../run-ticket-number.ts";
|
|
11
11
|
import { NotarySourceRunError, resolveNotarySourceRunLocator } from "../notary-source-run.ts";
|
|
12
12
|
import { engineSessionMaterialFromOptions, pickEngineAxis } from "../package-resources/engine-material.ts";
|
|
13
|
-
import {
|
|
14
|
-
loadPackagedMethodSkillMaterial,
|
|
15
|
-
type PackagedMethodSkillMaterial,
|
|
16
|
-
} from "../package-resources/method-skill.ts";
|
|
17
13
|
import type { PackagedRole } from "../packaged-role-registry.ts";
|
|
18
14
|
import {
|
|
19
15
|
packagedAdmitsCountersign,
|
|
20
16
|
packagedBindsBoardTicket,
|
|
21
|
-
packagedMethodLoadFailureCause,
|
|
22
17
|
packagedRebindSourceOnResume,
|
|
23
18
|
packagedRoleMetadata,
|
|
24
19
|
} from "../packaged-role-registry.ts";
|
|
@@ -42,7 +37,6 @@ import {
|
|
|
42
37
|
import {
|
|
43
38
|
presentControlledFailure,
|
|
44
39
|
prepareSummonsResumeMaterials,
|
|
45
|
-
resolveResumeMethodMaterialAdapters,
|
|
46
40
|
roleTurnOptions,
|
|
47
41
|
runPostAdmissionOneShot,
|
|
48
42
|
runPostAdmissionResumable,
|
|
@@ -67,6 +61,7 @@ import {
|
|
|
67
61
|
trySettlePublicSeat,
|
|
68
62
|
} from "./settlement.ts";
|
|
69
63
|
import type { CliIo } from "./cli-io.ts";
|
|
64
|
+
import { warnMissingMethodSkills } from "./machine-method-skills.ts";
|
|
70
65
|
import {
|
|
71
66
|
formatTerminalResult,
|
|
72
67
|
isLawfulTypedTerminalOutcome,
|
|
@@ -74,8 +69,8 @@ import {
|
|
|
74
69
|
} from "./terminal.ts";
|
|
75
70
|
import {
|
|
76
71
|
admittedSeatTurnDetails,
|
|
77
|
-
packagedSettleSkill,
|
|
78
72
|
projectRoleTurnRequest,
|
|
73
|
+
requiredMethodSkills,
|
|
79
74
|
type RoleTurnRequestProjectionOptions,
|
|
80
75
|
} from "./turn-request.ts";
|
|
81
76
|
import {
|
|
@@ -110,7 +105,7 @@ export function buildInstructionSeatTurnRequest(
|
|
|
110
105
|
): RoleTurnRequest {
|
|
111
106
|
return projectRoleTurnRequest(
|
|
112
107
|
admitted,
|
|
113
|
-
admittedSeatTurnDetails(admitted, options.
|
|
108
|
+
admittedSeatTurnDetails(admitted, options.home, options.host),
|
|
114
109
|
options,
|
|
115
110
|
);
|
|
116
111
|
}
|
|
@@ -160,13 +155,12 @@ async function bindAndRelocateDiarist(
|
|
|
160
155
|
function seatAdapters(
|
|
161
156
|
admitted: AdmittedRoleInvocation,
|
|
162
157
|
env: InstructionSeatRunEnv,
|
|
163
|
-
material?: PackagedMethodSkillMaterial,
|
|
164
158
|
): PostAdmissionAdapters<AdmittedRoleInvocation> {
|
|
165
159
|
const record = roleRecord(admitted.role);
|
|
166
160
|
const present = "presentSettled" in record ? record.presentSettled : "default";
|
|
167
161
|
return {
|
|
168
162
|
trySettle: (seat, authority, scope) =>
|
|
169
|
-
trySettlePublicSeat(seat, authority, scope
|
|
163
|
+
trySettlePublicSeat(seat, authority, scope),
|
|
170
164
|
...(present === "always" ? { shouldPresentSettled: () => true } : {}),
|
|
171
165
|
...(present === "typed"
|
|
172
166
|
? { shouldPresentSettled: (terminal: TerminalResult) => isLawfulTypedTerminalOutcome(terminal.roleOutcome) }
|
|
@@ -233,23 +227,8 @@ async function dispatchAdmitted(
|
|
|
233
227
|
io: CliIo,
|
|
234
228
|
): Promise<SeatRunResult> {
|
|
235
229
|
const record = roleRecord(admitted.role);
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
if (skill !== undefined) {
|
|
239
|
-
try {
|
|
240
|
-
material = await loadPackagedMethodSkillMaterial(env.packageRoot, skill);
|
|
241
|
-
} catch (error) {
|
|
242
|
-
const knownCause = packagedMethodLoadFailureCause(admitted.role);
|
|
243
|
-
return await presentControlledFailure(admitted, {
|
|
244
|
-
timedOut: false,
|
|
245
|
-
code: null,
|
|
246
|
-
stderr: "",
|
|
247
|
-
thrown: error,
|
|
248
|
-
...(knownCause === undefined ? {} : { knownCause }),
|
|
249
|
-
}, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
const adapters = seatAdapters(admitted, env, material);
|
|
230
|
+
await warnMissingMethodSkills(env.home, env.host, admitted.role, requiredMethodSkills(admitted), io.stdout);
|
|
231
|
+
const adapters = seatAdapters(admitted, env);
|
|
253
232
|
const execute = async (activeEnv: InstructionSeatRunEnv): Promise<SeatRunResult> => {
|
|
254
233
|
const auto = "inCallAutoResume" in record && record.inCallAutoResume === true;
|
|
255
234
|
if (auto) {
|
|
@@ -388,9 +367,7 @@ async function runCountersignBody(
|
|
|
388
367
|
projectRoot: resolve(parsed.project ?? env.cwd),
|
|
389
368
|
role: "countersign",
|
|
390
369
|
parentRunPath: gateParentRunPath,
|
|
391
|
-
...(env.boundTicketNumber
|
|
392
|
-
? {}
|
|
393
|
-
: { ticketNumber: env.boundTicketNumber }),
|
|
370
|
+
...(isSafePositiveTicketNumber(env.boundTicketNumber) ? { ticketNumber: env.boundTicketNumber } : {}),
|
|
394
371
|
freshSummons: env.freshSummons,
|
|
395
372
|
summons: {
|
|
396
373
|
sourceRunPath: gateParentRunPath,
|
|
@@ -430,15 +407,17 @@ async function runCountersignBody(
|
|
|
430
407
|
let typedTicket: number | undefined;
|
|
431
408
|
let typedCourtTicketNumbers: readonly number[] | undefined;
|
|
432
409
|
let identityDiaristRan = false;
|
|
433
|
-
let
|
|
410
|
+
let childDiaristRunId: string | undefined;
|
|
411
|
+
let pausedDiaristTerminal: TerminalResult | undefined;
|
|
434
412
|
|
|
435
|
-
if (env.runCourtDiaristStation === undefined) {
|
|
413
|
+
if (gateParentRunPath === undefined && env.runCourtDiaristStation === undefined) {
|
|
436
414
|
let outcome: Awaited<ReturnType<typeof invokeCourtDiarist>>;
|
|
437
415
|
try {
|
|
438
416
|
outcome = await invokeCourtDiarist({
|
|
439
417
|
instruction: parsed.instruction ?? "",
|
|
440
418
|
projectRoot: admitted.projectRoot,
|
|
441
419
|
failureLabel: "unbound summons",
|
|
420
|
+
correlationId: admitted.runId,
|
|
442
421
|
...(env.boundTicketNumber === undefined ? {} : { boundTicketNumber: env.boundTicketNumber }),
|
|
443
422
|
}, env, io);
|
|
444
423
|
} catch (error) {
|
|
@@ -454,17 +433,16 @@ async function runCountersignBody(
|
|
|
454
433
|
}, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
|
|
455
434
|
}
|
|
456
435
|
identityDiaristRan = true;
|
|
436
|
+
if (outcome.identity.kind === "escalate") pausedDiaristTerminal = outcome.terminal;
|
|
457
437
|
if (outcome.admitted?.runDirectory.includes(`${sep}unbound${sep}runs${sep}`)) {
|
|
458
|
-
|
|
438
|
+
childDiaristRunId = outcome.admitted.runId;
|
|
459
439
|
}
|
|
460
|
-
if (outcome.
|
|
461
|
-
const diagnostic = outcome.
|
|
462
|
-
? outcome.identity.diagnostic
|
|
463
|
-
: outcome.failedWithoutEscalate?.diagnostic ?? "";
|
|
440
|
+
if (outcome.failedWithoutEscalate !== undefined) {
|
|
441
|
+
const diagnostic = outcome.failedWithoutEscalate.diagnostic;
|
|
464
442
|
await materializeAdmission(
|
|
465
443
|
isSafePositiveTicketNumber(env.boundTicketNumber) ? env.boundTicketNumber : undefined,
|
|
466
444
|
);
|
|
467
|
-
if (
|
|
445
|
+
if (childDiaristRunId !== undefined) await recordChildDiaristRun(admitted, childDiaristRunId);
|
|
468
446
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
469
447
|
return await presentControlledFailure(admitted, {
|
|
470
448
|
timedOut: false,
|
|
@@ -474,19 +452,20 @@ async function runCountersignBody(
|
|
|
474
452
|
}, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
|
|
475
453
|
}
|
|
476
454
|
if (outcome.identity.kind === "ticket") {
|
|
477
|
-
typedTicket =
|
|
478
|
-
? env.boundTicketNumber
|
|
479
|
-
: outcome.identity.ticketNumber;
|
|
455
|
+
typedTicket = outcome.identity.ticketNumber;
|
|
480
456
|
typedCourtTicketNumbers = outcome.identity.courtTicketNumbers;
|
|
481
|
-
} else if (isSafePositiveTicketNumber(env.boundTicketNumber)) {
|
|
482
|
-
typedTicket = env.boundTicketNumber;
|
|
483
457
|
}
|
|
484
458
|
}
|
|
485
459
|
|
|
486
460
|
await materializeAdmission(typedTicket);
|
|
487
461
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
488
|
-
if (
|
|
489
|
-
await recordChildDiaristRun(admitted,
|
|
462
|
+
if (childDiaristRunId !== undefined) {
|
|
463
|
+
await recordChildDiaristRun(admitted, childDiaristRunId);
|
|
464
|
+
}
|
|
465
|
+
if (pausedDiaristTerminal !== undefined) {
|
|
466
|
+
// The child already submitted its own pause; no parent turn exists yet.
|
|
467
|
+
io.stdout(formatTerminalResult(pausedDiaristTerminal));
|
|
468
|
+
return { exitCode: 0, admitted, terminal: pausedDiaristTerminal };
|
|
490
469
|
}
|
|
491
470
|
if (gateParentRunPath !== undefined) {
|
|
492
471
|
await persistAdmittedSourceRunPath(admitted, gateParentRunPath);
|
|
@@ -540,7 +519,11 @@ async function runCountersignBody(
|
|
|
540
519
|
beforeDispatch: async (admittedSeat, lease) => {
|
|
541
520
|
if (!packagedAdmitsCountersign(admittedSeat.role)) return;
|
|
542
521
|
if (!identityDiaristRan || typedTicket !== undefined) {
|
|
543
|
-
await runCountersignCourtDiaristStation(admittedSeat, env, io);
|
|
522
|
+
const refresh = await runCountersignCourtDiaristStation(admittedSeat, env, io);
|
|
523
|
+
if (refresh !== undefined) {
|
|
524
|
+
io.stdout(formatTerminalResult(refresh.terminal));
|
|
525
|
+
return refresh.terminal;
|
|
526
|
+
}
|
|
544
527
|
}
|
|
545
528
|
await relocateAdmittedRunToTicket(admittedSeat, env.principalAuthority, lease);
|
|
546
529
|
Object.assign(turnRequest, buildInstructionSeatTurnRequest(admittedSeat, turnProjection));
|
|
@@ -628,7 +611,6 @@ export async function runPublicInstructionSeat(
|
|
|
628
611
|
projectRoot,
|
|
629
612
|
role,
|
|
630
613
|
parentRunPath: sourceDirectory,
|
|
631
|
-
...(auditorTicket === undefined ? {} : { ticketNumber: auditorTicket }),
|
|
632
614
|
freshSummons: env.freshSummons,
|
|
633
615
|
summons,
|
|
634
616
|
resume: (runId, materials) => runPublicInstructionSeatResume(
|
|
@@ -657,7 +639,6 @@ export async function runPublicInstructionSeat(
|
|
|
657
639
|
projectRoot,
|
|
658
640
|
role,
|
|
659
641
|
parentRunPath,
|
|
660
|
-
...(env.boundTicketNumber === undefined ? {} : { ticketNumber: env.boundTicketNumber }),
|
|
661
642
|
freshSummons: env.freshSummons,
|
|
662
643
|
summons,
|
|
663
644
|
resume: (runId, materials) => runPublicInstructionSeatResume(
|
|
@@ -686,7 +667,6 @@ export async function runPublicInstructionSeat(
|
|
|
686
667
|
throw error;
|
|
687
668
|
}
|
|
688
669
|
const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
|
|
689
|
-
const knownTicket = (await readBoardTicketNumber(source.runDirectory)) ?? env.boundTicketNumber;
|
|
690
670
|
const summons: SameTicketSummonsMaterials = {
|
|
691
671
|
sourceRunPath: source.runDirectory,
|
|
692
672
|
sourceRun: source,
|
|
@@ -697,7 +677,6 @@ export async function runPublicInstructionSeat(
|
|
|
697
677
|
projectRoot,
|
|
698
678
|
role,
|
|
699
679
|
parentRunPath: source.runDirectory,
|
|
700
|
-
...(knownTicket === undefined ? {} : { ticketNumber: knownTicket }),
|
|
701
680
|
freshSummons: env.freshSummons,
|
|
702
681
|
summons,
|
|
703
682
|
resume: (runId, materials) => runPublicInstructionSeatResume(
|
|
@@ -737,52 +716,33 @@ export async function runPublicInstructionSeat(
|
|
|
737
716
|
|
|
738
717
|
const runAdmitted = async (): Promise<SeatRunResult> => {
|
|
739
718
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
740
|
-
if (
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
...(env.hostAdapters === undefined ? {} : { hostAdapters: env.hostAdapters }),
|
|
756
|
-
}, io);
|
|
719
|
+
if (role === "secretariat") {
|
|
720
|
+
let outcome: Awaited<ReturnType<typeof invokeCourtDiarist>>;
|
|
721
|
+
try {
|
|
722
|
+
outcome = await invokeCourtDiarist({
|
|
723
|
+
instruction: parsed.instruction ?? "",
|
|
724
|
+
projectRoot: admitted.projectRoot,
|
|
725
|
+
failureLabel: "secretariat unbound summons",
|
|
726
|
+
attachmentPaths: admitted.attachments.map((attachment) => attachment.frozenPath),
|
|
727
|
+
correlationId: admitted.runId,
|
|
728
|
+
}, env, io);
|
|
729
|
+
} catch (error) {
|
|
730
|
+
return await presentControlledFailure(admitted, {
|
|
731
|
+
timedOut: false, code: null, stderr: "", thrown: error,
|
|
732
|
+
}, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
|
|
733
|
+
}
|
|
757
734
|
if (outcome.admitted?.runDirectory.includes(`${sep}unbound${sep}runs${sep}`)) {
|
|
758
735
|
await recordChildDiaristRun(admitted, outcome.admitted.runId);
|
|
759
736
|
}
|
|
760
|
-
if (outcome.
|
|
761
|
-
const diagnostic = outcome.identity.kind === "escalate"
|
|
762
|
-
? outcome.identity.diagnostic
|
|
763
|
-
: outcome.failedWithoutEscalate?.diagnostic ?? "";
|
|
737
|
+
if (outcome.failedWithoutEscalate !== undefined) {
|
|
764
738
|
return await presentControlledFailure(admitted, {
|
|
765
|
-
timedOut: false,
|
|
766
|
-
|
|
767
|
-
stderr: "",
|
|
768
|
-
thrown: new Error(diagnostic),
|
|
739
|
+
timedOut: false, code: null, stderr: "",
|
|
740
|
+
thrown: new Error(outcome.failedWithoutEscalate.diagnostic),
|
|
769
741
|
}, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
|
|
770
742
|
}
|
|
771
|
-
if (outcome.identity.kind === "
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
await bindAdmittedTicketNumber(outcome.admitted, outcome.identity.ticketNumber);
|
|
775
|
-
await relocateAdmittedRunToTicket(outcome.admitted, env.principalAuthority);
|
|
776
|
-
await bindAdmittedTicketNumber(admitted, outcome.identity.ticketNumber);
|
|
777
|
-
await relocateAdmittedRunToTicket(admitted, env.principalAuthority);
|
|
778
|
-
} catch (error) {
|
|
779
|
-
return await presentControlledFailure(admitted, {
|
|
780
|
-
timedOut: false,
|
|
781
|
-
code: null,
|
|
782
|
-
stderr: "",
|
|
783
|
-
thrown: error,
|
|
784
|
-
}, seatAdapters(admitted, env), env.principalAuthority, io) as SeatRunResult;
|
|
785
|
-
}
|
|
743
|
+
if (outcome.identity.kind === "escalate" && outcome.terminal !== undefined) {
|
|
744
|
+
io.stdout(formatTerminalResult(outcome.terminal));
|
|
745
|
+
return { exitCode: 0, admitted, terminal: outcome.terminal };
|
|
786
746
|
}
|
|
787
747
|
}
|
|
788
748
|
return dispatchAdmitted(admitted, env, io);
|
|
@@ -837,22 +797,32 @@ export async function runPublicInstructionSeatResume(
|
|
|
837
797
|
adapters: {
|
|
838
798
|
trySettle: async () => undefined,
|
|
839
799
|
},
|
|
840
|
-
afterAdmittedLoad: (admitted) => {
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
return Promise.resolve({ kind: "continue" as const, adapters: seatAdapters(admitted, env) });
|
|
844
|
-
}
|
|
845
|
-
const knownCause = packagedMethodLoadFailureCause(admitted.role);
|
|
846
|
-
return resolveResumeMethodMaterialAdapters({
|
|
847
|
-
admitted,
|
|
848
|
-
authority: env.principalAuthority,
|
|
849
|
-
io,
|
|
850
|
-
loadMaterial: () => loadPackagedMethodSkillMaterial(env.packageRoot, skill),
|
|
851
|
-
adaptersWith: (material) => seatAdapters(admitted, env, material),
|
|
852
|
-
emptyAdapters: seatAdapters(admitted, env),
|
|
853
|
-
...(knownCause === undefined ? {} : { knownCause }),
|
|
854
|
-
});
|
|
800
|
+
afterAdmittedLoad: async (admitted) => {
|
|
801
|
+
await warnMissingMethodSkills(env.home, env.host, admitted.role, requiredMethodSkills(admitted), io.stdout);
|
|
802
|
+
return { kind: "continue" as const, adapters: seatAdapters(admitted, env) };
|
|
855
803
|
},
|
|
856
804
|
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
857
805
|
});
|
|
858
806
|
}
|
|
807
|
+
|
|
808
|
+
/** Continue the parent once any escalated child has submitted. */
|
|
809
|
+
export async function continueParentAfterChild(
|
|
810
|
+
parentRunId: string,
|
|
811
|
+
child: AdmittedRoleInvocation,
|
|
812
|
+
env: InstructionSeatRunEnv,
|
|
813
|
+
io: CliIo,
|
|
814
|
+
): Promise<SeatRunResult> {
|
|
815
|
+
const loaded = await loadResumablePublicRole(env.home, parentRunId, env.principalAuthority, true);
|
|
816
|
+
if (loaded.run.state !== "admitted") {
|
|
817
|
+
return runPublicInstructionSeatResume({ runId: parentRunId }, env, io);
|
|
818
|
+
}
|
|
819
|
+
const admitted = loaded.admitted;
|
|
820
|
+
if (admitted.role === "countersign" && admitted.ticketNumber !== undefined) {
|
|
821
|
+
const refresh = await runCountersignCourtDiaristStation(admitted, env, io, child.ticketNumber);
|
|
822
|
+
if (refresh !== undefined) {
|
|
823
|
+
io.stdout(formatTerminalResult(refresh.terminal));
|
|
824
|
+
return { exitCode: 0, admitted, terminal: refresh.terminal };
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return dispatchAdmitted(admitted, env, io);
|
|
828
|
+
}
|
|
@@ -27,6 +27,7 @@ import { resolveBookKeyFromGit } from "../activation-ledger-git.ts";
|
|
|
27
27
|
import {
|
|
28
28
|
ensureRoleRunDirectory,
|
|
29
29
|
ensureRoleRunPlacement,
|
|
30
|
+
listBookRunDirectories,
|
|
30
31
|
roleRunArtifactsDirectory,
|
|
31
32
|
roleRunPlacement,
|
|
32
33
|
type RoleRunSubject,
|
|
@@ -539,19 +540,13 @@ export async function persistAdmittedSourceRunPath(
|
|
|
539
540
|
/**
|
|
540
541
|
* Bind a post-admission resolved ticketNumber onto the in-memory admitted
|
|
541
542
|
* object and both durable pages (invocation.json + admitted-request.json).
|
|
542
|
-
*
|
|
543
|
-
* Never clears an existing binding.
|
|
543
|
+
* A later typed role receipt replaces an earlier assertion (#1025).
|
|
544
544
|
*/
|
|
545
545
|
export async function bindAdmittedTicketNumber(
|
|
546
546
|
admitted: AdmittedRoleInvocation,
|
|
547
547
|
ticketNumber: number,
|
|
548
548
|
): Promise<void> {
|
|
549
|
-
if (admitted.ticketNumber
|
|
550
|
-
if (admitted.ticketNumber === ticketNumber) return;
|
|
551
|
-
throw new Error(
|
|
552
|
-
`bindAdmittedTicketNumber refuses to replace existing ticket #${admitted.ticketNumber} with #${ticketNumber}`,
|
|
553
|
-
);
|
|
554
|
-
}
|
|
549
|
+
if (admitted.ticketNumber === ticketNumber) return;
|
|
555
550
|
await bindTicketNumberOnRunDirectory(admitted.runDirectory, ticketNumber);
|
|
556
551
|
(admitted as { ticketNumber?: number }).ticketNumber = ticketNumber;
|
|
557
552
|
}
|
|
@@ -642,7 +637,7 @@ export async function bindCourtTicketNumbersOnAdmitted(
|
|
|
642
637
|
admitted.courtTicketNumbers = frozen;
|
|
643
638
|
}
|
|
644
639
|
|
|
645
|
-
/**
|
|
640
|
+
/** File a role run under its latest typed ticket identity. */
|
|
646
641
|
export async function relocateAdmittedRunToTicket(
|
|
647
642
|
admitted: AdmittedRoleInvocation,
|
|
648
643
|
authority: DurablePrincipalAuthority,
|
|
@@ -668,6 +663,8 @@ export async function relocateAdmittedRunToTicket(
|
|
|
668
663
|
for (const childRunId of Array.isArray(childRunIds) ? childRunIds : []) {
|
|
669
664
|
if (typeof childRunId !== "string") continue;
|
|
670
665
|
const childDirectory = join(dirname(oldRunDirectory), `${childRunId}@diarist`);
|
|
666
|
+
// A child that already filed under a ticket keeps its own assertion.
|
|
667
|
+
if (!existsSync(childDirectory)) continue;
|
|
671
668
|
const childTarget = roleRunPlacement(ledgerHome, {
|
|
672
669
|
bookKey: admitted.bookKey,
|
|
673
670
|
subject: { ticketNumber: admitted.ticketNumber },
|
|
@@ -675,7 +672,6 @@ export async function relocateAdmittedRunToTicket(
|
|
|
675
672
|
role: "diarist",
|
|
676
673
|
});
|
|
677
674
|
authority.seal(childTarget);
|
|
678
|
-
if (!existsSync(childDirectory) && existsSync(childTarget.runDirectory)) continue;
|
|
679
675
|
await bindTicketNumberOnRunDirectory(childDirectory, admitted.ticketNumber);
|
|
680
676
|
await rehomeUnboundTicketProvenance(childDirectory, admitted.ticketNumber, admitted.projectRoot, homeFromRunDirectory(oldRunDirectory));
|
|
681
677
|
ensureRoleRunDirectory(ledgerHome, dirname(childTarget.runDirectory));
|
|
@@ -683,14 +679,13 @@ export async function relocateAdmittedRunToTicket(
|
|
|
683
679
|
}
|
|
684
680
|
}
|
|
685
681
|
|
|
686
|
-
//
|
|
687
|
-
// own bind and the caller's later bind use this existing relocation seam.
|
|
682
|
+
// The first ticket assignment moves only an unbound diarist's own diary.
|
|
688
683
|
if (admitted.role === "diarist") {
|
|
689
684
|
await rehomeUnboundTicketProvenance(oldRunDirectory, admitted.ticketNumber, admitted.projectRoot, homeFromRunDirectory(oldRunDirectory));
|
|
690
685
|
}
|
|
691
686
|
|
|
692
|
-
// Rename commits the run placement. Diary assignment above is
|
|
693
|
-
//
|
|
687
|
+
// Rename commits the run placement. Diary assignment above is a Sitian append
|
|
688
|
+
// outside the atomic directory move.
|
|
694
689
|
// Persisted paths are resolved from typed run identity on read.
|
|
695
690
|
await rename(oldRunDirectory, target.runDirectory);
|
|
696
691
|
|
|
@@ -752,34 +747,11 @@ export async function bindTicketNumberOnRunDirectory(
|
|
|
752
747
|
"bindTicketNumberOnRunDirectory",
|
|
753
748
|
);
|
|
754
749
|
const admittedPath = join(runDirectory, "admitted-request.json");
|
|
755
|
-
const invocationPath = join(runDirectory, "invocation.json");
|
|
756
750
|
const admitted = JSON.parse(await readFile(admittedPath, "utf8")) as Record<
|
|
757
751
|
string,
|
|
758
752
|
unknown
|
|
759
753
|
>;
|
|
760
|
-
|
|
761
|
-
if (typeof existing === "number") {
|
|
762
|
-
if (existing === ticketNumber) return;
|
|
763
|
-
throw new Error(
|
|
764
|
-
`bindTicketNumberOnRunDirectory refuses to replace existing ticket #${existing} with #${ticketNumber}`,
|
|
765
|
-
);
|
|
766
|
-
}
|
|
767
|
-
// Conflict guard before any write: crash window of bindAdmittedTicketNumber
|
|
768
|
-
// can leave invocation bound while admitted-request is still unbound — refuse
|
|
769
|
-
// silent rebind. Read-before-merge; never check the page just overwritten.
|
|
770
|
-
if (existsSync(invocationPath)) {
|
|
771
|
-
const invocation = JSON.parse(
|
|
772
|
-
await readFile(invocationPath, "utf8"),
|
|
773
|
-
) as Record<string, unknown>;
|
|
774
|
-
if (
|
|
775
|
-
typeof invocation.ticketNumber === "number" &&
|
|
776
|
-
invocation.ticketNumber !== ticketNumber
|
|
777
|
-
) {
|
|
778
|
-
throw new Error(
|
|
779
|
-
`bindTicketNumberOnRunDirectory refuses to replace invocation ticket #${invocation.ticketNumber} with #${ticketNumber}`,
|
|
780
|
-
);
|
|
781
|
-
}
|
|
782
|
-
}
|
|
754
|
+
if (admitted.ticketNumber === ticketNumber) return;
|
|
783
755
|
await writeFile(
|
|
784
756
|
admittedPath,
|
|
785
757
|
`${JSON.stringify({ ...admitted, ticketNumber }, null, 2)}\n`,
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { accessSync, constants, statSync } from "node:fs";
|
|
2
|
+
import { lstat, mkdir, symlink } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { HEADLESS_HOST_DESCRIPTIONS, HOST_DESCRIPTIONS } from "../host-descriptions.ts";
|
|
6
|
+
import { packagedMethodSkillNames } from "../packaged-role-registry.ts";
|
|
7
|
+
|
|
8
|
+
/** Install source only. Which names are required comes from the role registry. */
|
|
9
|
+
const SKILL_SOURCE: Readonly<Record<string, string>> = {
|
|
10
|
+
tdd: "mattpocock/skills",
|
|
11
|
+
"diagnosing-bugs": "mattpocock/skills",
|
|
12
|
+
"resolving-merge-conflicts": "mattpocock/skills",
|
|
13
|
+
"ak-cross-m-review": "Akagilnc/ak-cross-m-review",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function skillRoot(home: string): string {
|
|
17
|
+
return join(home, ".agents/skills");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function installedMethodSkillPath(home: string, name: string): string | undefined {
|
|
21
|
+
return installedSkillPath(skillRoot(home), name);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function installedSkillPath(root: string, name: string): string | undefined {
|
|
25
|
+
const path = join(root, name, "SKILL.md");
|
|
26
|
+
try {
|
|
27
|
+
if (!statSync(path).isFile()) return undefined;
|
|
28
|
+
accessSync(path, constants.R_OK);
|
|
29
|
+
return path;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
32
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "EPERM" || code === "ELOOP") return undefined;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function linkedSkillRoot(home: string, host: string | undefined): string | undefined {
|
|
38
|
+
if (host === "claude" || host === "claude-code") {
|
|
39
|
+
return join(process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude"), "skills");
|
|
40
|
+
}
|
|
41
|
+
if (host === "hermes") return join(home, ".hermes/skills");
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function occupied(path: string): Promise<boolean> {
|
|
46
|
+
try {
|
|
47
|
+
await lstat(path);
|
|
48
|
+
return true;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function warnOccupied(path: string, stdout: (text: string) => void): void {
|
|
56
|
+
stdout(`Warning: ${path} is already occupied; left untouched.\n`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function warnMissingMethodSkills(
|
|
60
|
+
home: string,
|
|
61
|
+
host: string | undefined,
|
|
62
|
+
role: string,
|
|
63
|
+
skills: readonly string[],
|
|
64
|
+
stdout: (text: string) => void,
|
|
65
|
+
): Promise<void> {
|
|
66
|
+
const missing: string[] = [];
|
|
67
|
+
const linkedRoot = linkedSkillRoot(home, host);
|
|
68
|
+
for (const skill of skills) {
|
|
69
|
+
if (installedMethodSkillPath(home, skill) === undefined ||
|
|
70
|
+
(linkedRoot !== undefined && installedSkillPath(linkedRoot, skill) === undefined)) missing.push(skill);
|
|
71
|
+
}
|
|
72
|
+
for (const skill of missing) {
|
|
73
|
+
stdout(`Warning: required machine Skill "${skill}" is missing for ${role}; its method guidance may be unavailable and reduce work quality. Run \`ak-role setup\` to install required Skills.\n`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function runSkillsCli(args: readonly string[], home: string): boolean {
|
|
78
|
+
const result = spawnSync("npx", ["--yes", "skills", ...args], {
|
|
79
|
+
stdio: "inherit",
|
|
80
|
+
env: { ...process.env, HOME: home },
|
|
81
|
+
});
|
|
82
|
+
if (result.error !== undefined) throw result.error;
|
|
83
|
+
return result.status === 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function addSkills(source: string, skills: readonly string[], home: string): boolean {
|
|
87
|
+
if (skills.length === 0) return true;
|
|
88
|
+
return runSkillsCli([
|
|
89
|
+
"add", source,
|
|
90
|
+
...skills.flatMap((skill) => ["--skill", skill]),
|
|
91
|
+
"--agent", "codex", "--global", "--yes",
|
|
92
|
+
], home);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function updateRequiredSkills(home: string): boolean {
|
|
96
|
+
const names = packagedMethodSkillNames();
|
|
97
|
+
if (names.length === 0) return true;
|
|
98
|
+
return runSkillsCli(["update", "-g", "-y", ...names], home);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Explicit user-run setup; delegates acquisition/install to the ecosystem Skills CLI. */
|
|
102
|
+
export async function runMachineSkillSetup(home: string, stdout: (text: string) => void): Promise<number> {
|
|
103
|
+
const canonical = skillRoot(home);
|
|
104
|
+
const missingBySource = new Map<string, string[]>();
|
|
105
|
+
let unsourced = false;
|
|
106
|
+
for (const name of packagedMethodSkillNames()) {
|
|
107
|
+
const path = join(canonical, name);
|
|
108
|
+
if (await occupied(path)) {
|
|
109
|
+
warnOccupied(path, stdout);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const source = SKILL_SOURCE[name];
|
|
113
|
+
if (source === undefined) {
|
|
114
|
+
unsourced = true;
|
|
115
|
+
stdout(`Warning: required machine Skill "${name}" has no install source; setup did not install it.\n`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const missing = missingBySource.get(source) ?? [];
|
|
119
|
+
missing.push(name);
|
|
120
|
+
missingBySource.set(source, missing);
|
|
121
|
+
}
|
|
122
|
+
for (const [source, missing] of missingBySource) {
|
|
123
|
+
if (!addSkills(source, missing, home)) return 1;
|
|
124
|
+
}
|
|
125
|
+
if (unsourced) return 1;
|
|
126
|
+
|
|
127
|
+
for (const host of ["claude-code", "hermes"] as const) {
|
|
128
|
+
const root = linkedSkillRoot(home, host)!;
|
|
129
|
+
const binaryFromHome = host === "claude-code"
|
|
130
|
+
? HEADLESS_HOST_DESCRIPTIONS.claude!.binaryFromHome
|
|
131
|
+
: HOST_DESCRIPTIONS.hermes!.binaryFromHome;
|
|
132
|
+
try {
|
|
133
|
+
accessSync(join(home, ...binaryFromHome), constants.X_OK);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (["ENOENT", "ENOTDIR", "EACCES"].includes((error as NodeJS.ErrnoException).code ?? "")) continue;
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
for (const name of packagedMethodSkillNames()) {
|
|
139
|
+
const target = join(canonical, name);
|
|
140
|
+
if (installedMethodSkillPath(home, name) === undefined) continue;
|
|
141
|
+
const link = join(root, name);
|
|
142
|
+
if (await occupied(link)) {
|
|
143
|
+
warnOccupied(link, stdout);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
await mkdir(root, { recursive: true });
|
|
147
|
+
await symlink(target, link, "dir");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (!updateRequiredSkills(home)) return 1;
|
|
151
|
+
stdout("Machine method Skills setup finished. Existing same-name Skills were left untouched.\n");
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
@@ -223,8 +223,9 @@ function withOnceSuccessfulBeforeDispatch<
|
|
|
223
223
|
...adapters,
|
|
224
224
|
beforeDispatch: async (admitted, lease) => {
|
|
225
225
|
if (succeeded) return;
|
|
226
|
-
await hook(admitted, lease);
|
|
226
|
+
const result = await hook(admitted, lease);
|
|
227
227
|
succeeded = true;
|
|
228
|
+
return result;
|
|
228
229
|
},
|
|
229
230
|
};
|
|
230
231
|
}
|
|
@@ -352,7 +353,8 @@ export type PostAdmissionAdapters<
|
|
|
352
353
|
result: RoleTurnResult;
|
|
353
354
|
sessionFile: string;
|
|
354
355
|
}) => Promise<RoleTurnKnownFailure | undefined>;
|
|
355
|
-
|
|
356
|
+
/** A child office may pause this run with its unchanged terminal before a host turn starts. */
|
|
357
|
+
beforeDispatch?: (admitted: A, lease?: RunWriterLease) => Promise<T | void> | T | void;
|
|
356
358
|
/**
|
|
357
359
|
* After the host turn settles and before any held writer lease is released.
|
|
358
360
|
* Post-turn bind/relocate (e.g. Diarist first assert) must run here so a
|
|
@@ -709,9 +711,8 @@ export async function dispatchPostAdmissionTurn<
|
|
|
709
711
|
};
|
|
710
712
|
}
|
|
711
713
|
try {
|
|
712
|
-
// #858:
|
|
713
|
-
//
|
|
714
|
-
// or reject missing/malformed declarations. An existing binding wins.
|
|
714
|
+
// #858: the first typed assertion files an unbound run. Later receipts
|
|
715
|
+
// remain untouched; this seam does not adjudicate a ticket change.
|
|
715
716
|
if (admitted.ticketNumber === undefined) {
|
|
716
717
|
const rows = await readRecordedSubmissionRows(
|
|
717
718
|
admitted.projectRoot,
|
|
@@ -825,7 +826,10 @@ export async function dispatchPostAdmissionTurn<
|
|
|
825
826
|
// (#840 父子不层叠).
|
|
826
827
|
if (adapters.beforeDispatch !== undefined) {
|
|
827
828
|
try {
|
|
828
|
-
await adapters.beforeDispatch(admitted, lease);
|
|
829
|
+
const paused = await adapters.beforeDispatch(admitted, lease);
|
|
830
|
+
if (paused !== undefined) {
|
|
831
|
+
return { exitCode: 0, admitted, terminal: paused, skipAutoResume: true as const };
|
|
832
|
+
}
|
|
829
833
|
} catch (error) {
|
|
830
834
|
const settled = (await presentControlledFailure(
|
|
831
835
|
admitted,
|
|
@@ -1363,6 +1367,7 @@ export function resumeTurnRequestProjectionOptions(
|
|
|
1363
1367
|
return {
|
|
1364
1368
|
packageRoot: env.packageRoot,
|
|
1365
1369
|
home: env.home,
|
|
1370
|
+
...(env.host === undefined ? {} : { host: env.host }),
|
|
1366
1371
|
agentDir: env.agentDir,
|
|
1367
1372
|
...(env.model === undefined ? {} : { model: env.model }),
|
|
1368
1373
|
...pickEngineAxis(env),
|
|
@@ -1389,6 +1394,7 @@ export function roleTurnOptions(
|
|
|
1389
1394
|
return {
|
|
1390
1395
|
packageRoot: env.packageRoot,
|
|
1391
1396
|
home: env.home,
|
|
1397
|
+
...(env.host === undefined ? {} : { host: env.host }),
|
|
1392
1398
|
agentDir: env.agentDir,
|
|
1393
1399
|
...(env.model === undefined ? {} : { model: env.model }),
|
|
1394
1400
|
...pickEngineAxis(env),
|