@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
|
@@ -8,17 +8,17 @@ import { resolve, sep } from "node:path";
|
|
|
8
8
|
import { isSafePositiveTicketNumber, readBoardTicketNumber } from "../run-ticket-number.js";
|
|
9
9
|
import { NotarySourceRunError, resolveNotarySourceRunLocator } from "../notary-source-run.js";
|
|
10
10
|
import { engineSessionMaterialFromOptions, pickEngineAxis } from "../package-resources/engine-material.js";
|
|
11
|
-
import {
|
|
12
|
-
import { packagedAdmitsCountersign, packagedBindsBoardTicket, packagedMethodLoadFailureCause, packagedRebindSourceOnResume, packagedRoleMetadata, } from "../packaged-role-registry.js";
|
|
11
|
+
import { packagedAdmitsCountersign, packagedBindsBoardTicket, packagedRebindSourceOnResume, packagedRoleMetadata, } from "../packaged-role-registry.js";
|
|
13
12
|
import { isAuditorSoulRole } from "../auditor-soul.js";
|
|
14
13
|
import { CliUsageError } from "./cli-errors.js";
|
|
15
14
|
import { admitPublicRole, bindAdmittedTicketNumber, bindCourtTicketNumbersOnAdmitted, buildInstructionTransportPrompt, materializeCountersignInvocation, persistAdmittedSourceRunPath, recordAdmittedCorrelation, recordChildDiaristRun, relocateAdmittedRunToTicket, withPreparedAttachments, } from "./invocation.js";
|
|
16
|
-
import { presentControlledFailure, prepareSummonsResumeMaterials,
|
|
15
|
+
import { presentControlledFailure, prepareSummonsResumeMaterials, roleTurnOptions, runPostAdmissionOneShot, runPostAdmissionResumable, runPostAdmissionSeatResume, resumeTurnRequestProjectionOptions, } from "./post-admission.js";
|
|
17
16
|
import { buildAutoResumeContinuationPrompt, loadResumablePublicRole, markRunAdmitted, parentRunPathFromGatePointerInstruction, } from "./run-lifecycle.js";
|
|
18
17
|
import { tryResumeSameTicketSeatRun } from "./seat-ticket-binding.js";
|
|
19
18
|
import { presentStructuralRejection, seatKnownFailureResolver, trySettlePublicSeat, } from "./settlement.js";
|
|
19
|
+
import { warnMissingMethodSkills } from "./machine-method-skills.js";
|
|
20
20
|
import { formatTerminalResult, isLawfulTypedTerminalOutcome, } from "./terminal.js";
|
|
21
|
-
import { admittedSeatTurnDetails,
|
|
21
|
+
import { admittedSeatTurnDetails, projectRoleTurnRequest, requiredMethodSkills, } from "./turn-request.js";
|
|
22
22
|
import { invokeCourtDiarist, runCountersignCourtDiaristStation, } from "./countersign-run.js";
|
|
23
23
|
function roleRecord(role) {
|
|
24
24
|
const record = packagedRoleMetadata(role);
|
|
@@ -29,7 +29,7 @@ function roleRecord(role) {
|
|
|
29
29
|
}
|
|
30
30
|
/** Project any admitted public role onto the host-neutral turn request. */
|
|
31
31
|
export function buildInstructionSeatTurnRequest(admitted, options) {
|
|
32
|
-
return projectRoleTurnRequest(admitted, admittedSeatTurnDetails(admitted, options.
|
|
32
|
+
return projectRoleTurnRequest(admitted, admittedSeatTurnDetails(admitted, options.home, options.host), options);
|
|
33
33
|
}
|
|
34
34
|
function initialPrompt(admitted, env) {
|
|
35
35
|
const record = roleRecord(admitted.role);
|
|
@@ -58,11 +58,11 @@ async function bindAndRelocateDiarist(admitted, authority, lease) {
|
|
|
58
58
|
}
|
|
59
59
|
await relocateAdmittedRunToTicket(admitted, authority, lease);
|
|
60
60
|
}
|
|
61
|
-
function seatAdapters(admitted, env
|
|
61
|
+
function seatAdapters(admitted, env) {
|
|
62
62
|
const record = roleRecord(admitted.role);
|
|
63
63
|
const present = "presentSettled" in record ? record.presentSettled : "default";
|
|
64
64
|
return {
|
|
65
|
-
trySettle: (seat, authority, scope) => trySettlePublicSeat(seat, authority, scope
|
|
65
|
+
trySettle: (seat, authority, scope) => trySettlePublicSeat(seat, authority, scope),
|
|
66
66
|
...(present === "always" ? { shouldPresentSettled: () => true } : {}),
|
|
67
67
|
...(present === "typed"
|
|
68
68
|
? { shouldPresentSettled: (terminal) => isLawfulTypedTerminalOutcome(terminal.roleOutcome) }
|
|
@@ -126,24 +126,8 @@ async function withAuditorSoulEnv(options) {
|
|
|
126
126
|
}
|
|
127
127
|
async function dispatchAdmitted(admitted, env, io) {
|
|
128
128
|
const record = roleRecord(admitted.role);
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
if (skill !== undefined) {
|
|
132
|
-
try {
|
|
133
|
-
material = await loadPackagedMethodSkillMaterial(env.packageRoot, skill);
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
const knownCause = packagedMethodLoadFailureCause(admitted.role);
|
|
137
|
-
return await presentControlledFailure(admitted, {
|
|
138
|
-
timedOut: false,
|
|
139
|
-
code: null,
|
|
140
|
-
stderr: "",
|
|
141
|
-
thrown: error,
|
|
142
|
-
...(knownCause === undefined ? {} : { knownCause }),
|
|
143
|
-
}, seatAdapters(admitted, env), env.principalAuthority, io);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
const adapters = seatAdapters(admitted, env, material);
|
|
129
|
+
await warnMissingMethodSkills(env.home, env.host, admitted.role, requiredMethodSkills(admitted), io.stdout);
|
|
130
|
+
const adapters = seatAdapters(admitted, env);
|
|
147
131
|
const execute = async (activeEnv) => {
|
|
148
132
|
const auto = "inCallAutoResume" in record && record.inCallAutoResume === true;
|
|
149
133
|
if (auto) {
|
|
@@ -258,9 +242,7 @@ async function runCountersignBody(parsed, env, io) {
|
|
|
258
242
|
projectRoot: resolve(parsed.project ?? env.cwd),
|
|
259
243
|
role: "countersign",
|
|
260
244
|
parentRunPath: gateParentRunPath,
|
|
261
|
-
...(env.boundTicketNumber
|
|
262
|
-
? {}
|
|
263
|
-
: { ticketNumber: env.boundTicketNumber }),
|
|
245
|
+
...(isSafePositiveTicketNumber(env.boundTicketNumber) ? { ticketNumber: env.boundTicketNumber } : {}),
|
|
264
246
|
freshSummons: env.freshSummons,
|
|
265
247
|
summons: {
|
|
266
248
|
sourceRunPath: gateParentRunPath,
|
|
@@ -296,14 +278,16 @@ async function runCountersignBody(parsed, env, io) {
|
|
|
296
278
|
let typedTicket;
|
|
297
279
|
let typedCourtTicketNumbers;
|
|
298
280
|
let identityDiaristRan = false;
|
|
299
|
-
let
|
|
300
|
-
|
|
281
|
+
let childDiaristRunId;
|
|
282
|
+
let pausedDiaristTerminal;
|
|
283
|
+
if (gateParentRunPath === undefined && env.runCourtDiaristStation === undefined) {
|
|
301
284
|
let outcome;
|
|
302
285
|
try {
|
|
303
286
|
outcome = await invokeCourtDiarist({
|
|
304
287
|
instruction: parsed.instruction ?? "",
|
|
305
288
|
projectRoot: admitted.projectRoot,
|
|
306
289
|
failureLabel: "unbound summons",
|
|
290
|
+
correlationId: admitted.runId,
|
|
307
291
|
...(env.boundTicketNumber === undefined ? {} : { boundTicketNumber: env.boundTicketNumber }),
|
|
308
292
|
}, env, io);
|
|
309
293
|
}
|
|
@@ -318,16 +302,16 @@ async function runCountersignBody(parsed, env, io) {
|
|
|
318
302
|
}, seatAdapters(admitted, env), env.principalAuthority, io);
|
|
319
303
|
}
|
|
320
304
|
identityDiaristRan = true;
|
|
305
|
+
if (outcome.identity.kind === "escalate")
|
|
306
|
+
pausedDiaristTerminal = outcome.terminal;
|
|
321
307
|
if (outcome.admitted?.runDirectory.includes(`${sep}unbound${sep}runs${sep}`)) {
|
|
322
|
-
|
|
308
|
+
childDiaristRunId = outcome.admitted.runId;
|
|
323
309
|
}
|
|
324
|
-
if (outcome.
|
|
325
|
-
const diagnostic = outcome.
|
|
326
|
-
? outcome.identity.diagnostic
|
|
327
|
-
: outcome.failedWithoutEscalate?.diagnostic ?? "";
|
|
310
|
+
if (outcome.failedWithoutEscalate !== undefined) {
|
|
311
|
+
const diagnostic = outcome.failedWithoutEscalate.diagnostic;
|
|
328
312
|
await materializeAdmission(isSafePositiveTicketNumber(env.boundTicketNumber) ? env.boundTicketNumber : undefined);
|
|
329
|
-
if (
|
|
330
|
-
await recordChildDiaristRun(admitted,
|
|
313
|
+
if (childDiaristRunId !== undefined)
|
|
314
|
+
await recordChildDiaristRun(admitted, childDiaristRunId);
|
|
331
315
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
332
316
|
return await presentControlledFailure(admitted, {
|
|
333
317
|
timedOut: false,
|
|
@@ -337,19 +321,19 @@ async function runCountersignBody(parsed, env, io) {
|
|
|
337
321
|
}, seatAdapters(admitted, env), env.principalAuthority, io);
|
|
338
322
|
}
|
|
339
323
|
if (outcome.identity.kind === "ticket") {
|
|
340
|
-
typedTicket =
|
|
341
|
-
? env.boundTicketNumber
|
|
342
|
-
: outcome.identity.ticketNumber;
|
|
324
|
+
typedTicket = outcome.identity.ticketNumber;
|
|
343
325
|
typedCourtTicketNumbers = outcome.identity.courtTicketNumbers;
|
|
344
326
|
}
|
|
345
|
-
else if (isSafePositiveTicketNumber(env.boundTicketNumber)) {
|
|
346
|
-
typedTicket = env.boundTicketNumber;
|
|
347
|
-
}
|
|
348
327
|
}
|
|
349
328
|
await materializeAdmission(typedTicket);
|
|
350
329
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
351
|
-
if (
|
|
352
|
-
await recordChildDiaristRun(admitted,
|
|
330
|
+
if (childDiaristRunId !== undefined) {
|
|
331
|
+
await recordChildDiaristRun(admitted, childDiaristRunId);
|
|
332
|
+
}
|
|
333
|
+
if (pausedDiaristTerminal !== undefined) {
|
|
334
|
+
// The child already submitted its own pause; no parent turn exists yet.
|
|
335
|
+
io.stdout(formatTerminalResult(pausedDiaristTerminal));
|
|
336
|
+
return { exitCode: 0, admitted, terminal: pausedDiaristTerminal };
|
|
353
337
|
}
|
|
354
338
|
if (gateParentRunPath !== undefined) {
|
|
355
339
|
await persistAdmittedSourceRunPath(admitted, gateParentRunPath);
|
|
@@ -401,7 +385,11 @@ async function runCountersignBody(parsed, env, io) {
|
|
|
401
385
|
if (!packagedAdmitsCountersign(admittedSeat.role))
|
|
402
386
|
return;
|
|
403
387
|
if (!identityDiaristRan || typedTicket !== undefined) {
|
|
404
|
-
await runCountersignCourtDiaristStation(admittedSeat, env, io);
|
|
388
|
+
const refresh = await runCountersignCourtDiaristStation(admittedSeat, env, io);
|
|
389
|
+
if (refresh !== undefined) {
|
|
390
|
+
io.stdout(formatTerminalResult(refresh.terminal));
|
|
391
|
+
return refresh.terminal;
|
|
392
|
+
}
|
|
405
393
|
}
|
|
406
394
|
await relocateAdmittedRunToTicket(admittedSeat, env.principalAuthority, lease);
|
|
407
395
|
Object.assign(turnRequest, buildInstructionSeatTurnRequest(admittedSeat, turnProjection));
|
|
@@ -486,7 +474,6 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
486
474
|
projectRoot,
|
|
487
475
|
role,
|
|
488
476
|
parentRunPath: sourceDirectory,
|
|
489
|
-
...(auditorTicket === undefined ? {} : { ticketNumber: auditorTicket }),
|
|
490
477
|
freshSummons: env.freshSummons,
|
|
491
478
|
summons,
|
|
492
479
|
resume: (runId, materials) => runPublicInstructionSeatResume({ runId, ...(materials === undefined ? {} : { summons: materials }) }, env, io),
|
|
@@ -511,7 +498,6 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
511
498
|
projectRoot,
|
|
512
499
|
role,
|
|
513
500
|
parentRunPath,
|
|
514
|
-
...(env.boundTicketNumber === undefined ? {} : { ticketNumber: env.boundTicketNumber }),
|
|
515
501
|
freshSummons: env.freshSummons,
|
|
516
502
|
summons,
|
|
517
503
|
resume: (runId, materials) => runPublicInstructionSeatResume({ runId, ...(materials === undefined ? {} : { summons: materials }) }, env, io),
|
|
@@ -537,7 +523,6 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
537
523
|
throw error;
|
|
538
524
|
}
|
|
539
525
|
const resumeInstruction = env.reviewReask ?? env.gateReviewInstruction;
|
|
540
|
-
const knownTicket = (await readBoardTicketNumber(source.runDirectory)) ?? env.boundTicketNumber;
|
|
541
526
|
const summons = {
|
|
542
527
|
sourceRunPath: source.runDirectory,
|
|
543
528
|
sourceRun: source,
|
|
@@ -548,7 +533,6 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
548
533
|
projectRoot,
|
|
549
534
|
role,
|
|
550
535
|
parentRunPath: source.runDirectory,
|
|
551
|
-
...(knownTicket === undefined ? {} : { ticketNumber: knownTicket }),
|
|
552
536
|
freshSummons: env.freshSummons,
|
|
553
537
|
summons,
|
|
554
538
|
resume: (runId, materials) => runPublicInstructionSeatResume({ runId, ...(materials === undefined ? {} : { summons: materials }) }, env, io),
|
|
@@ -579,54 +563,34 @@ export async function runPublicInstructionSeat(argv, env, io, role, parseArgv) {
|
|
|
579
563
|
}
|
|
580
564
|
const runAdmitted = async () => {
|
|
581
565
|
await markRunAdmitted(admitted, env.principalAuthority);
|
|
582
|
-
if (
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}, io);
|
|
566
|
+
if (role === "secretariat") {
|
|
567
|
+
let outcome;
|
|
568
|
+
try {
|
|
569
|
+
outcome = await invokeCourtDiarist({
|
|
570
|
+
instruction: parsed.instruction ?? "",
|
|
571
|
+
projectRoot: admitted.projectRoot,
|
|
572
|
+
failureLabel: "secretariat unbound summons",
|
|
573
|
+
attachmentPaths: admitted.attachments.map((attachment) => attachment.frozenPath),
|
|
574
|
+
correlationId: admitted.runId,
|
|
575
|
+
}, env, io);
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
return await presentControlledFailure(admitted, {
|
|
579
|
+
timedOut: false, code: null, stderr: "", thrown: error,
|
|
580
|
+
}, seatAdapters(admitted, env), env.principalAuthority, io);
|
|
581
|
+
}
|
|
599
582
|
if (outcome.admitted?.runDirectory.includes(`${sep}unbound${sep}runs${sep}`)) {
|
|
600
583
|
await recordChildDiaristRun(admitted, outcome.admitted.runId);
|
|
601
584
|
}
|
|
602
|
-
if (outcome.
|
|
603
|
-
const diagnostic = outcome.identity.kind === "escalate"
|
|
604
|
-
? outcome.identity.diagnostic
|
|
605
|
-
: outcome.failedWithoutEscalate?.diagnostic ?? "";
|
|
585
|
+
if (outcome.failedWithoutEscalate !== undefined) {
|
|
606
586
|
return await presentControlledFailure(admitted, {
|
|
607
|
-
timedOut: false,
|
|
608
|
-
|
|
609
|
-
stderr: "",
|
|
610
|
-
thrown: new Error(diagnostic),
|
|
587
|
+
timedOut: false, code: null, stderr: "",
|
|
588
|
+
thrown: new Error(outcome.failedWithoutEscalate.diagnostic),
|
|
611
589
|
}, seatAdapters(admitted, env), env.principalAuthority, io);
|
|
612
590
|
}
|
|
613
|
-
if (outcome.identity.kind === "
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
throw new Error("court diarist ticket has no admitted run");
|
|
617
|
-
await bindAdmittedTicketNumber(outcome.admitted, outcome.identity.ticketNumber);
|
|
618
|
-
await relocateAdmittedRunToTicket(outcome.admitted, env.principalAuthority);
|
|
619
|
-
await bindAdmittedTicketNumber(admitted, outcome.identity.ticketNumber);
|
|
620
|
-
await relocateAdmittedRunToTicket(admitted, env.principalAuthority);
|
|
621
|
-
}
|
|
622
|
-
catch (error) {
|
|
623
|
-
return await presentControlledFailure(admitted, {
|
|
624
|
-
timedOut: false,
|
|
625
|
-
code: null,
|
|
626
|
-
stderr: "",
|
|
627
|
-
thrown: error,
|
|
628
|
-
}, seatAdapters(admitted, env), env.principalAuthority, io);
|
|
629
|
-
}
|
|
591
|
+
if (outcome.identity.kind === "escalate" && outcome.terminal !== undefined) {
|
|
592
|
+
io.stdout(formatTerminalResult(outcome.terminal));
|
|
593
|
+
return { exitCode: 0, admitted, terminal: outcome.terminal };
|
|
630
594
|
}
|
|
631
595
|
}
|
|
632
596
|
return dispatchAdmitted(admitted, env, io);
|
|
@@ -670,22 +634,26 @@ export async function runPublicInstructionSeatResume(request, env, io) {
|
|
|
670
634
|
adapters: {
|
|
671
635
|
trySettle: async () => undefined,
|
|
672
636
|
},
|
|
673
|
-
afterAdmittedLoad: (admitted) => {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
return Promise.resolve({ kind: "continue", adapters: seatAdapters(admitted, env) });
|
|
677
|
-
}
|
|
678
|
-
const knownCause = packagedMethodLoadFailureCause(admitted.role);
|
|
679
|
-
return resolveResumeMethodMaterialAdapters({
|
|
680
|
-
admitted,
|
|
681
|
-
authority: env.principalAuthority,
|
|
682
|
-
io,
|
|
683
|
-
loadMaterial: () => loadPackagedMethodSkillMaterial(env.packageRoot, skill),
|
|
684
|
-
adaptersWith: (material) => seatAdapters(admitted, env, material),
|
|
685
|
-
emptyAdapters: seatAdapters(admitted, env),
|
|
686
|
-
...(knownCause === undefined ? {} : { knownCause }),
|
|
687
|
-
});
|
|
637
|
+
afterAdmittedLoad: async (admitted) => {
|
|
638
|
+
await warnMissingMethodSkills(env.home, env.host, admitted.role, requiredMethodSkills(admitted), io.stdout);
|
|
639
|
+
return { kind: "continue", adapters: seatAdapters(admitted, env) };
|
|
688
640
|
},
|
|
689
641
|
...(env.engine === undefined ? {} : { effectiveEngine: env.engine }),
|
|
690
642
|
});
|
|
691
643
|
}
|
|
644
|
+
/** Continue the parent once any escalated child has submitted. */
|
|
645
|
+
export async function continueParentAfterChild(parentRunId, child, env, io) {
|
|
646
|
+
const loaded = await loadResumablePublicRole(env.home, parentRunId, env.principalAuthority, true);
|
|
647
|
+
if (loaded.run.state !== "admitted") {
|
|
648
|
+
return runPublicInstructionSeatResume({ runId: parentRunId }, env, io);
|
|
649
|
+
}
|
|
650
|
+
const admitted = loaded.admitted;
|
|
651
|
+
if (admitted.role === "countersign" && admitted.ticketNumber !== undefined) {
|
|
652
|
+
const refresh = await runCountersignCourtDiaristStation(admitted, env, io, child.ticketNumber);
|
|
653
|
+
if (refresh !== undefined) {
|
|
654
|
+
io.stdout(formatTerminalResult(refresh.terminal));
|
|
655
|
+
return { exitCode: 0, admitted, terminal: refresh.terminal };
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return dispatchAdmitted(admitted, env, io);
|
|
659
|
+
}
|
|
@@ -9,7 +9,7 @@ import { tmpdir } from "node:os";
|
|
|
9
9
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
10
10
|
import { activationBookDirectory, ensureRealDirectoryTree, homeFromRunDirectory, pathContainedIn, resolveActivationLedgerHome, } from "../activation-ledger-topology.js";
|
|
11
11
|
import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
|
|
12
|
-
import { ensureRoleRunDirectory, ensureRoleRunPlacement, roleRunArtifactsDirectory, roleRunPlacement, } from "../role-run-placement.js";
|
|
12
|
+
import { ensureRoleRunDirectory, ensureRoleRunPlacement, listBookRunDirectories, roleRunArtifactsDirectory, roleRunPlacement, } from "../role-run-placement.js";
|
|
13
13
|
import { packagedAdmittedSubject, packagedArgvResult, packagedBareToken, packagedBaseIsRevision, packagedEmitsAuthorityRefs, packagedPublicInstructionSubject, packagedRoleMetadata, packagedStoresSourceRunRaw, packagedSubjectChoices, } from "../packaged-role-registry.js";
|
|
14
14
|
import { isSafePositiveTicketNumber, readBoardTicketNumber, requireSafePositiveTicketNumber, } from "../run-ticket-number.js";
|
|
15
15
|
import { rewriteRunDirectoryPathFields, rewriteRunDirectoryPathValue, } from "../role-run-relocation.js";
|
|
@@ -164,15 +164,11 @@ export async function persistAdmittedSourceRunPath(admitted, sourceRunPath) {
|
|
|
164
164
|
/**
|
|
165
165
|
* Bind a post-admission resolved ticketNumber onto the in-memory admitted
|
|
166
166
|
* object and both durable pages (invocation.json + admitted-request.json).
|
|
167
|
-
*
|
|
168
|
-
* Never clears an existing binding.
|
|
167
|
+
* A later typed role receipt replaces an earlier assertion (#1025).
|
|
169
168
|
*/
|
|
170
169
|
export async function bindAdmittedTicketNumber(admitted, ticketNumber) {
|
|
171
|
-
if (admitted.ticketNumber
|
|
172
|
-
|
|
173
|
-
return;
|
|
174
|
-
throw new Error(`bindAdmittedTicketNumber refuses to replace existing ticket #${admitted.ticketNumber} with #${ticketNumber}`);
|
|
175
|
-
}
|
|
170
|
+
if (admitted.ticketNumber === ticketNumber)
|
|
171
|
+
return;
|
|
176
172
|
await bindTicketNumberOnRunDirectory(admitted.runDirectory, ticketNumber);
|
|
177
173
|
admitted.ticketNumber = ticketNumber;
|
|
178
174
|
}
|
|
@@ -229,7 +225,7 @@ export async function bindCourtTicketNumbersOnAdmitted(admitted, courtTicketNumb
|
|
|
229
225
|
});
|
|
230
226
|
admitted.courtTicketNumbers = frozen;
|
|
231
227
|
}
|
|
232
|
-
/**
|
|
228
|
+
/** File a role run under its latest typed ticket identity. */
|
|
233
229
|
export async function relocateAdmittedRunToTicket(admitted, authority, heldLease) {
|
|
234
230
|
if (admitted.ticketNumber === undefined || !admitted.runDirectory.includes(`${sep}unbound${sep}runs${sep}`))
|
|
235
231
|
return undefined;
|
|
@@ -252,6 +248,9 @@ export async function relocateAdmittedRunToTicket(admitted, authority, heldLease
|
|
|
252
248
|
if (typeof childRunId !== "string")
|
|
253
249
|
continue;
|
|
254
250
|
const childDirectory = join(dirname(oldRunDirectory), `${childRunId}@diarist`);
|
|
251
|
+
// A child that already filed under a ticket keeps its own assertion.
|
|
252
|
+
if (!existsSync(childDirectory))
|
|
253
|
+
continue;
|
|
255
254
|
const childTarget = roleRunPlacement(ledgerHome, {
|
|
256
255
|
bookKey: admitted.bookKey,
|
|
257
256
|
subject: { ticketNumber: admitted.ticketNumber },
|
|
@@ -259,21 +258,18 @@ export async function relocateAdmittedRunToTicket(admitted, authority, heldLease
|
|
|
259
258
|
role: "diarist",
|
|
260
259
|
});
|
|
261
260
|
authority.seal(childTarget);
|
|
262
|
-
if (!existsSync(childDirectory) && existsSync(childTarget.runDirectory))
|
|
263
|
-
continue;
|
|
264
261
|
await bindTicketNumberOnRunDirectory(childDirectory, admitted.ticketNumber);
|
|
265
262
|
await rehomeUnboundTicketProvenance(childDirectory, admitted.ticketNumber, admitted.projectRoot, homeFromRunDirectory(oldRunDirectory));
|
|
266
263
|
ensureRoleRunDirectory(ledgerHome, dirname(childTarget.runDirectory));
|
|
267
264
|
await rename(childDirectory, childTarget.runDirectory);
|
|
268
265
|
}
|
|
269
266
|
}
|
|
270
|
-
//
|
|
271
|
-
// own bind and the caller's later bind use this existing relocation seam.
|
|
267
|
+
// The first ticket assignment moves only an unbound diarist's own diary.
|
|
272
268
|
if (admitted.role === "diarist") {
|
|
273
269
|
await rehomeUnboundTicketProvenance(oldRunDirectory, admitted.ticketNumber, admitted.projectRoot, homeFromRunDirectory(oldRunDirectory));
|
|
274
270
|
}
|
|
275
|
-
// Rename commits the run placement. Diary assignment above is
|
|
276
|
-
//
|
|
271
|
+
// Rename commits the run placement. Diary assignment above is a Sitian append
|
|
272
|
+
// outside the atomic directory move.
|
|
277
273
|
// Persisted paths are resolved from typed run identity on read.
|
|
278
274
|
await rename(oldRunDirectory, target.runDirectory);
|
|
279
275
|
// rename moved the open lock inode with the directory. Transfer cleanup
|
|
@@ -312,24 +308,9 @@ export async function recordChildDiaristRun(parent, childRunId) {
|
|
|
312
308
|
export async function bindTicketNumberOnRunDirectory(runDirectory, ticketNumber) {
|
|
313
309
|
requireSafePositiveTicketNumber(ticketNumber, "bindTicketNumberOnRunDirectory");
|
|
314
310
|
const admittedPath = join(runDirectory, "admitted-request.json");
|
|
315
|
-
const invocationPath = join(runDirectory, "invocation.json");
|
|
316
311
|
const admitted = JSON.parse(await readFile(admittedPath, "utf8"));
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (existing === ticketNumber)
|
|
320
|
-
return;
|
|
321
|
-
throw new Error(`bindTicketNumberOnRunDirectory refuses to replace existing ticket #${existing} with #${ticketNumber}`);
|
|
322
|
-
}
|
|
323
|
-
// Conflict guard before any write: crash window of bindAdmittedTicketNumber
|
|
324
|
-
// can leave invocation bound while admitted-request is still unbound — refuse
|
|
325
|
-
// silent rebind. Read-before-merge; never check the page just overwritten.
|
|
326
|
-
if (existsSync(invocationPath)) {
|
|
327
|
-
const invocation = JSON.parse(await readFile(invocationPath, "utf8"));
|
|
328
|
-
if (typeof invocation.ticketNumber === "number" &&
|
|
329
|
-
invocation.ticketNumber !== ticketNumber) {
|
|
330
|
-
throw new Error(`bindTicketNumberOnRunDirectory refuses to replace invocation ticket #${invocation.ticketNumber} with #${ticketNumber}`);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
312
|
+
if (admitted.ticketNumber === ticketNumber)
|
|
313
|
+
return;
|
|
333
314
|
await writeFile(admittedPath, `${JSON.stringify({ ...admitted, ticketNumber }, null, 2)}\n`, "utf8");
|
|
334
315
|
await mergeInvocationIdentityPage(runDirectory, { ticketNumber });
|
|
335
316
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
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.js";
|
|
6
|
+
import { packagedMethodSkillNames } from "../packaged-role-registry.js";
|
|
7
|
+
/** Install source only. Which names are required comes from the role registry. */
|
|
8
|
+
const SKILL_SOURCE = {
|
|
9
|
+
tdd: "mattpocock/skills",
|
|
10
|
+
"diagnosing-bugs": "mattpocock/skills",
|
|
11
|
+
"resolving-merge-conflicts": "mattpocock/skills",
|
|
12
|
+
"ak-cross-m-review": "Akagilnc/ak-cross-m-review",
|
|
13
|
+
};
|
|
14
|
+
function skillRoot(home) {
|
|
15
|
+
return join(home, ".agents/skills");
|
|
16
|
+
}
|
|
17
|
+
export function installedMethodSkillPath(home, name) {
|
|
18
|
+
return installedSkillPath(skillRoot(home), name);
|
|
19
|
+
}
|
|
20
|
+
function installedSkillPath(root, name) {
|
|
21
|
+
const path = join(root, name, "SKILL.md");
|
|
22
|
+
try {
|
|
23
|
+
if (!statSync(path).isFile())
|
|
24
|
+
return undefined;
|
|
25
|
+
accessSync(path, constants.R_OK);
|
|
26
|
+
return path;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
const code = error.code;
|
|
30
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === "EACCES" || code === "EPERM" || code === "ELOOP")
|
|
31
|
+
return undefined;
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function linkedSkillRoot(home, host) {
|
|
36
|
+
if (host === "claude" || host === "claude-code") {
|
|
37
|
+
return join(process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude"), "skills");
|
|
38
|
+
}
|
|
39
|
+
if (host === "hermes")
|
|
40
|
+
return join(home, ".hermes/skills");
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
async function occupied(path) {
|
|
44
|
+
try {
|
|
45
|
+
await lstat(path);
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error.code !== "ENOENT")
|
|
50
|
+
throw error;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function warnOccupied(path, stdout) {
|
|
55
|
+
stdout(`Warning: ${path} is already occupied; left untouched.\n`);
|
|
56
|
+
}
|
|
57
|
+
export async function warnMissingMethodSkills(home, host, role, skills, stdout) {
|
|
58
|
+
const missing = [];
|
|
59
|
+
const linkedRoot = linkedSkillRoot(home, host);
|
|
60
|
+
for (const skill of skills) {
|
|
61
|
+
if (installedMethodSkillPath(home, skill) === undefined ||
|
|
62
|
+
(linkedRoot !== undefined && installedSkillPath(linkedRoot, skill) === undefined))
|
|
63
|
+
missing.push(skill);
|
|
64
|
+
}
|
|
65
|
+
for (const skill of missing) {
|
|
66
|
+
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`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function runSkillsCli(args, home) {
|
|
70
|
+
const result = spawnSync("npx", ["--yes", "skills", ...args], {
|
|
71
|
+
stdio: "inherit",
|
|
72
|
+
env: { ...process.env, HOME: home },
|
|
73
|
+
});
|
|
74
|
+
if (result.error !== undefined)
|
|
75
|
+
throw result.error;
|
|
76
|
+
return result.status === 0;
|
|
77
|
+
}
|
|
78
|
+
function addSkills(source, skills, home) {
|
|
79
|
+
if (skills.length === 0)
|
|
80
|
+
return true;
|
|
81
|
+
return runSkillsCli([
|
|
82
|
+
"add", source,
|
|
83
|
+
...skills.flatMap((skill) => ["--skill", skill]),
|
|
84
|
+
"--agent", "codex", "--global", "--yes",
|
|
85
|
+
], home);
|
|
86
|
+
}
|
|
87
|
+
function updateRequiredSkills(home) {
|
|
88
|
+
const names = packagedMethodSkillNames();
|
|
89
|
+
if (names.length === 0)
|
|
90
|
+
return true;
|
|
91
|
+
return runSkillsCli(["update", "-g", "-y", ...names], home);
|
|
92
|
+
}
|
|
93
|
+
/** Explicit user-run setup; delegates acquisition/install to the ecosystem Skills CLI. */
|
|
94
|
+
export async function runMachineSkillSetup(home, stdout) {
|
|
95
|
+
const canonical = skillRoot(home);
|
|
96
|
+
const missingBySource = new Map();
|
|
97
|
+
let unsourced = false;
|
|
98
|
+
for (const name of packagedMethodSkillNames()) {
|
|
99
|
+
const path = join(canonical, name);
|
|
100
|
+
if (await occupied(path)) {
|
|
101
|
+
warnOccupied(path, stdout);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const source = SKILL_SOURCE[name];
|
|
105
|
+
if (source === undefined) {
|
|
106
|
+
unsourced = true;
|
|
107
|
+
stdout(`Warning: required machine Skill "${name}" has no install source; setup did not install it.\n`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const missing = missingBySource.get(source) ?? [];
|
|
111
|
+
missing.push(name);
|
|
112
|
+
missingBySource.set(source, missing);
|
|
113
|
+
}
|
|
114
|
+
for (const [source, missing] of missingBySource) {
|
|
115
|
+
if (!addSkills(source, missing, home))
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
if (unsourced)
|
|
119
|
+
return 1;
|
|
120
|
+
for (const host of ["claude-code", "hermes"]) {
|
|
121
|
+
const root = linkedSkillRoot(home, host);
|
|
122
|
+
const binaryFromHome = host === "claude-code"
|
|
123
|
+
? HEADLESS_HOST_DESCRIPTIONS.claude.binaryFromHome
|
|
124
|
+
: HOST_DESCRIPTIONS.hermes.binaryFromHome;
|
|
125
|
+
try {
|
|
126
|
+
accessSync(join(home, ...binaryFromHome), constants.X_OK);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (["ENOENT", "ENOTDIR", "EACCES"].includes(error.code ?? ""))
|
|
130
|
+
continue;
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
for (const name of packagedMethodSkillNames()) {
|
|
134
|
+
const target = join(canonical, name);
|
|
135
|
+
if (installedMethodSkillPath(home, name) === undefined)
|
|
136
|
+
continue;
|
|
137
|
+
const link = join(root, name);
|
|
138
|
+
if (await occupied(link)) {
|
|
139
|
+
warnOccupied(link, stdout);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
await mkdir(root, { recursive: true });
|
|
143
|
+
await symlink(target, link, "dir");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!updateRequiredSkills(home))
|
|
147
|
+
return 1;
|
|
148
|
+
stdout("Machine method Skills setup finished. Existing same-name Skills were left untouched.\n");
|
|
149
|
+
return 0;
|
|
150
|
+
}
|