@akagilnc/pi-workflow-roles 0.1.4444 → 0.1.4503
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 -0
- package/README.zh-CN.md +2 -0
- package/dist/acp-host/description.js +2 -3
- package/dist/acp-host/production-host.js +512 -288
- package/dist/auditor-soul.js +8 -1
- package/dist/headless-host/description.js +3 -3
- package/dist/headless-host/production-host.js +519 -257
- package/dist/host-descriptions.js +3 -18
- package/dist/method-host-plugin/.claude-plugin/plugin.json +5 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/CONTEXT.md +48 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/LICENSE +21 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/SKILL.md +170 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/prompts/cmr-completeness.md +118 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/prompts/cmr-reviewer.md +128 -0
- package/dist/method-host-plugin/skills/ak-cross-m-review/provenance.json +41 -0
- package/dist/method-host-plugin/skills/diagnosing-bugs/SKILL.md +134 -0
- package/dist/method-host-plugin/skills/diagnosing-bugs/agents/openai.yaml +3 -0
- package/dist/method-host-plugin/skills/diagnosing-bugs/provenance.json +31 -0
- package/dist/method-host-plugin/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/dist/method-host-plugin/skills/resolving-merge-conflicts/SKILL.md +14 -0
- package/dist/method-host-plugin/skills/resolving-merge-conflicts/agents/openai.yaml +3 -0
- package/dist/method-host-plugin/skills/resolving-merge-conflicts/provenance.json +26 -0
- package/dist/method-host-plugin/skills/tdd/SKILL.md +38 -0
- package/dist/method-host-plugin/skills/tdd/agents/openai.yaml +3 -0
- package/dist/method-host-plugin/skills/tdd/mocking.md +59 -0
- package/dist/method-host-plugin/skills/tdd/provenance.json +36 -0
- package/dist/method-host-plugin/skills/tdd/tests.md +77 -0
- package/dist/public-cli/countersign-run.js +214 -155
- package/dist/public-cli/diarist-run.js +4 -1
- package/dist/public-cli/invocation.js +129 -21
- package/dist/public-cli/main.js +386 -202
- package/dist/session-opening-materials.js +17 -5
- package/extensions/role-runtime.ts +16 -6
- package/package.json +1 -1
- package/resources/method-host-plugin/.claude-plugin/plugin.json +5 -0
- package/scripts/build-package.mjs +6 -1
- package/src/acp-host/description.ts +2 -4
- package/src/acp-host/production-host.ts +0 -1
- package/src/auditor-soul.ts +10 -1
- package/src/headless-host/description.ts +4 -3
- package/src/headless-host/role-turn-host.ts +27 -4
- package/src/host-descriptions.ts +3 -23
- package/src/host-native-method.ts +67 -0
- package/src/public-cli/countersign-run.ts +326 -218
- package/src/public-cli/diarist-run.ts +4 -0
- package/src/public-cli/invocation.ts +205 -23
- package/src/role-envelope.ts +17 -47
- package/src/role-runtime-dependencies.ts +17 -2
- package/src/role-runtime.ts +12 -7
- package/src/session-opening-materials.ts +27 -11
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
bindAdmittedTicketNumber,
|
|
14
14
|
buildInstructionTransportPrompt,
|
|
15
15
|
relocateAdmittedRunToTicket,
|
|
16
|
+
recordAdmittedCorrelation,
|
|
16
17
|
type AdmittedDiaristInvocation,
|
|
17
18
|
type ParseDiaristArgvResult,
|
|
18
19
|
} from "./invocation.ts";
|
|
@@ -94,6 +95,9 @@ function diaristAdapters(
|
|
|
94
95
|
shouldPresentSettled: () => true,
|
|
95
96
|
// Resume interrupted state: board ticket already on pages, run still under unbound/.
|
|
96
97
|
beforeDispatch: async (admitted, lease) => {
|
|
98
|
+
if (env.correlationId !== undefined && env.correlationId.trim() !== "") {
|
|
99
|
+
await recordAdmittedCorrelation(admitted, env.correlationId);
|
|
100
|
+
}
|
|
97
101
|
await bindAndRelocateDiaristIfBoardBound(admitted, env.principalAuthority, lease);
|
|
98
102
|
},
|
|
99
103
|
// First post-turn assert: accept hook wrote board pages; relocate before lease release.
|
|
@@ -6,11 +6,14 @@ import { execFileSync } from "node:child_process";
|
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import {
|
|
8
8
|
lstat,
|
|
9
|
+
mkdtemp,
|
|
9
10
|
readFile,
|
|
10
11
|
realpath,
|
|
11
12
|
rename,
|
|
13
|
+
rm,
|
|
12
14
|
writeFile,
|
|
13
15
|
} from "node:fs/promises";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
14
17
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
15
18
|
|
|
16
19
|
import {
|
|
@@ -123,6 +126,8 @@ export type AdmittedRoleInvocationBase = {
|
|
|
123
126
|
* (ADR 0049 host channel). Admission does not mint ticket-binding ids.
|
|
124
127
|
*/
|
|
125
128
|
readonly correlationId?: string;
|
|
129
|
+
/** Direct callers retained across same-run resumes, in first-observed order. */
|
|
130
|
+
readonly correlationIds?: readonly string[];
|
|
126
131
|
/**
|
|
127
132
|
* Typed ticketNumber after known-identity reuse or notary source-run inheritance
|
|
128
133
|
* (#635 / #709). Admission does not bind from CLI flag or attachment frontmatter.
|
|
@@ -315,6 +320,8 @@ export function issueAdmissionPlacement(
|
|
|
315
320
|
/** Identity already asserted by 起居郎; never derive this from CLI parameters. */
|
|
316
321
|
readonly subject: RoleRunSubject;
|
|
317
322
|
readonly home?: string;
|
|
323
|
+
/** Placement may be computed before a same-ticket lookup without touching disk. */
|
|
324
|
+
readonly materialize?: boolean;
|
|
318
325
|
},
|
|
319
326
|
): AdmissionPlacement {
|
|
320
327
|
const ledgerHome = resolveActivationLedgerHome(request.home);
|
|
@@ -325,7 +332,7 @@ export function issueAdmissionPlacement(
|
|
|
325
332
|
runId: request.runId,
|
|
326
333
|
role: request.role,
|
|
327
334
|
});
|
|
328
|
-
ensureRoleRunPlacement(ledgerHome, placement);
|
|
335
|
+
if (request.materialize !== false) ensureRoleRunPlacement(ledgerHome, placement);
|
|
329
336
|
return {
|
|
330
337
|
principal: authority.seal(placement),
|
|
331
338
|
...placement,
|
|
@@ -530,6 +537,42 @@ export async function bindAdmittedTicketNumber(
|
|
|
530
537
|
(admitted as { ticketNumber?: number }).ticketNumber = ticketNumber;
|
|
531
538
|
}
|
|
532
539
|
|
|
540
|
+
/** Persist each direct caller observed while a retained run is resumed. */
|
|
541
|
+
export async function recordAdmittedCorrelation(
|
|
542
|
+
admitted: AdmittedRoleInvocation,
|
|
543
|
+
correlationId: string,
|
|
544
|
+
): Promise<void> {
|
|
545
|
+
const current = JSON.parse(
|
|
546
|
+
await readFile(admitted.admittedRequestPath, "utf8"),
|
|
547
|
+
) as Record<string, unknown>;
|
|
548
|
+
const prior = [
|
|
549
|
+
...(Array.isArray(current.correlationIds)
|
|
550
|
+
? current.correlationIds.filter((value): value is string =>
|
|
551
|
+
typeof value === "string" && value.trim() !== ""
|
|
552
|
+
)
|
|
553
|
+
: []),
|
|
554
|
+
...(typeof current.correlationId === "string" && current.correlationId.trim() !== ""
|
|
555
|
+
? [current.correlationId]
|
|
556
|
+
: []),
|
|
557
|
+
];
|
|
558
|
+
const correlationIds = [...new Set([...prior, correlationId])];
|
|
559
|
+
await writeFile(
|
|
560
|
+
admitted.admittedRequestPath,
|
|
561
|
+
`${JSON.stringify({ ...current, correlationId, correlationIds }, null, 2)}\n`,
|
|
562
|
+
"utf8",
|
|
563
|
+
);
|
|
564
|
+
await mergeInvocationIdentityPage(admitted.runDirectory, {
|
|
565
|
+
correlationId,
|
|
566
|
+
correlationIds,
|
|
567
|
+
});
|
|
568
|
+
const mutable = admitted as {
|
|
569
|
+
correlationId?: string;
|
|
570
|
+
correlationIds?: readonly string[];
|
|
571
|
+
};
|
|
572
|
+
mutable.correlationId = correlationId;
|
|
573
|
+
mutable.correlationIds = correlationIds;
|
|
574
|
+
}
|
|
575
|
+
|
|
533
576
|
/**
|
|
534
577
|
* #871: persist the typed co-review set as a countersign run fact (admitted-request +
|
|
535
578
|
* invocation.json). Whole-set replace — never union with a prior set. Main ticket
|
|
@@ -1200,11 +1243,9 @@ export function parseFixerArgv(args: readonly string[]): ParseFixerArgvResult {
|
|
|
1200
1243
|
};
|
|
1201
1244
|
}
|
|
1202
1245
|
|
|
1203
|
-
async function
|
|
1246
|
+
async function readRegularFileAttachment(
|
|
1204
1247
|
sourcePath: string,
|
|
1205
|
-
|
|
1206
|
-
index: number,
|
|
1207
|
-
): Promise<{ attachment: FrozenAttachment; body: Buffer }> {
|
|
1248
|
+
): Promise<{ absolute: string; bytes: Buffer }> {
|
|
1208
1249
|
const absolute = isAbsolute(sourcePath) ? sourcePath : resolve(sourcePath);
|
|
1209
1250
|
let st;
|
|
1210
1251
|
try {
|
|
@@ -1220,18 +1261,103 @@ async function freezeRegularFileAttachment(
|
|
|
1220
1261
|
`attachment must be a regular file (not a directory or symlink): ${sourcePath}`,
|
|
1221
1262
|
);
|
|
1222
1263
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1264
|
+
try {
|
|
1265
|
+
return { absolute, bytes: await readFile(absolute) };
|
|
1266
|
+
} catch (error) {
|
|
1267
|
+
throw new CliUsageError(
|
|
1268
|
+
`attachment is not a readable regular file: ${sourcePath}`,
|
|
1269
|
+
{ cause: error },
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
export type PreparedAttachment = {
|
|
1275
|
+
readonly absolute: string;
|
|
1276
|
+
readonly snapshotPath: string;
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Snapshot deferred inputs sequentially before identity side effects. The staging
|
|
1281
|
+
* files keep aggregate attachment bytes off heap until their final run is known.
|
|
1282
|
+
*/
|
|
1283
|
+
async function removePreparedAttachmentDirectory(stagingDirectory: string): Promise<void> {
|
|
1284
|
+
await rm(stagingDirectory, { recursive: true, force: true });
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/** Own the complete deferred-snapshot lifetime without masking either failure. */
|
|
1288
|
+
export async function withPreparedAttachments<T>(
|
|
1289
|
+
attachmentPaths: readonly string[],
|
|
1290
|
+
use: (prepared: readonly PreparedAttachment[]) => Promise<T>,
|
|
1291
|
+
): Promise<T> {
|
|
1292
|
+
if (attachmentPaths.length === 0) return await use([]);
|
|
1293
|
+
const stagingDirectory = await mkdtemp(join(tmpdir(), "ak-role-attachments-"));
|
|
1294
|
+
const prepared: PreparedAttachment[] = [];
|
|
1295
|
+
let result: T;
|
|
1296
|
+
try {
|
|
1297
|
+
for (let index = 0; index < attachmentPaths.length; index += 1) {
|
|
1298
|
+
const { absolute, bytes } = await readRegularFileAttachment(attachmentPaths[index]!);
|
|
1299
|
+
const snapshotPath = join(stagingDirectory, String(index).padStart(6, "0"));
|
|
1300
|
+
await writeFile(snapshotPath, bytes);
|
|
1301
|
+
prepared.push({ absolute, snapshotPath });
|
|
1302
|
+
}
|
|
1303
|
+
result = await use(prepared);
|
|
1304
|
+
} catch (primary) {
|
|
1305
|
+
try {
|
|
1306
|
+
await removePreparedAttachmentDirectory(stagingDirectory);
|
|
1307
|
+
} catch (cleanup) {
|
|
1308
|
+
throw new AggregateError(
|
|
1309
|
+
[primary, cleanup],
|
|
1310
|
+
"attachment snapshot operation failed and cleanup also failed",
|
|
1311
|
+
{ cause: primary },
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
throw primary;
|
|
1315
|
+
}
|
|
1316
|
+
await removePreparedAttachmentDirectory(stagingDirectory);
|
|
1317
|
+
return result;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
async function freezeAttachmentBytes(
|
|
1321
|
+
provenancePath: string,
|
|
1322
|
+
bytes: Buffer,
|
|
1323
|
+
destinationDir: string,
|
|
1324
|
+
index: number,
|
|
1325
|
+
): Promise<FrozenAttachment> {
|
|
1326
|
+
const frozenPath = join(
|
|
1327
|
+
destinationDir,
|
|
1328
|
+
`${String(index).padStart(2, "0")}-${basename(provenancePath)}`,
|
|
1329
|
+
);
|
|
1226
1330
|
await writeFile(frozenPath, bytes);
|
|
1227
1331
|
return {
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1332
|
+
provenancePath,
|
|
1333
|
+
frozenPath,
|
|
1334
|
+
byteLength: bytes.byteLength,
|
|
1335
|
+
sha256: sha256Hex(bytes),
|
|
1336
|
+
mediaKind: "regular-file",
|
|
1337
|
+
};
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
async function freezePreparedAttachment(
|
|
1341
|
+
prepared: PreparedAttachment,
|
|
1342
|
+
destinationDir: string,
|
|
1343
|
+
index: number,
|
|
1344
|
+
): Promise<FrozenAttachment> {
|
|
1345
|
+
return freezeAttachmentBytes(
|
|
1346
|
+
prepared.absolute,
|
|
1347
|
+
await readFile(prepared.snapshotPath),
|
|
1348
|
+
destinationDir,
|
|
1349
|
+
index,
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
async function freezeRegularFileAttachment(
|
|
1354
|
+
sourcePath: string,
|
|
1355
|
+
destinationDir: string,
|
|
1356
|
+
index: number,
|
|
1357
|
+
): Promise<{ attachment: FrozenAttachment; body: Buffer }> {
|
|
1358
|
+
const { absolute, bytes } = await readRegularFileAttachment(sourcePath);
|
|
1359
|
+
return {
|
|
1360
|
+
attachment: await freezeAttachmentBytes(absolute, bytes, destinationDir, index),
|
|
1235
1361
|
body: bytes,
|
|
1236
1362
|
};
|
|
1237
1363
|
}
|
|
@@ -1270,6 +1396,25 @@ export async function freezeAttachmentsIntoRun(
|
|
|
1270
1396
|
return freezeAttachments(attachmentPaths, attachmentsDirectory);
|
|
1271
1397
|
}
|
|
1272
1398
|
|
|
1399
|
+
/** Freeze prepared bytes and metadata through one path for birth or retained runs. */
|
|
1400
|
+
export async function freezePreparedAttachmentsIntoRun(
|
|
1401
|
+
prepared: readonly PreparedAttachment[],
|
|
1402
|
+
runDirectory: string,
|
|
1403
|
+
summonsKey?: string,
|
|
1404
|
+
): Promise<readonly FrozenAttachment[]> {
|
|
1405
|
+
if (prepared.length === 0) return [];
|
|
1406
|
+
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(runDirectory));
|
|
1407
|
+
const attachmentsDirectory = summonsKey === undefined
|
|
1408
|
+
? join(runDirectory, "attachments")
|
|
1409
|
+
: join(runDirectory, "attachments", summonsKey);
|
|
1410
|
+
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
1411
|
+
const attachments: FrozenAttachment[] = [];
|
|
1412
|
+
for (let index = 0; index < prepared.length; index += 1) {
|
|
1413
|
+
attachments.push(await freezePreparedAttachment(prepared[index]!, attachmentsDirectory, index));
|
|
1414
|
+
}
|
|
1415
|
+
return attachments;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1273
1418
|
function ticketAdmissionFields(
|
|
1274
1419
|
ticketNumber: number | undefined,
|
|
1275
1420
|
): { ticketNumber?: number } {
|
|
@@ -1515,11 +1660,13 @@ export type AdmitCountersignInvocationOptions = {
|
|
|
1515
1660
|
/** Effective model for this invocation — written onto invocation.json. */
|
|
1516
1661
|
model?: InvocationEffectiveModel;
|
|
1517
1662
|
correlationId?: string;
|
|
1663
|
+
/** Same-ticket lookup may select an existing run before a new run is persisted. */
|
|
1664
|
+
deferPersistence?: boolean;
|
|
1518
1665
|
};
|
|
1519
1666
|
|
|
1520
1667
|
/**
|
|
1521
|
-
*
|
|
1522
|
-
*
|
|
1668
|
+
* Admit a Countersign run immediately, or reserve its coordinates for deferred
|
|
1669
|
+
* materialization after identity lookup (#572 / ADR 0074 / #863).
|
|
1523
1670
|
*/
|
|
1524
1671
|
export async function admitCountersignInvocation(
|
|
1525
1672
|
options: AdmitCountersignInvocationOptions,
|
|
@@ -1545,9 +1692,12 @@ export async function admitCountersignInvocation(
|
|
|
1545
1692
|
role: "countersign",
|
|
1546
1693
|
subject: { unbound: true },
|
|
1547
1694
|
home: options.home,
|
|
1695
|
+
materialize: options.deferPersistence !== true,
|
|
1548
1696
|
});
|
|
1549
1697
|
|
|
1550
|
-
const attachments =
|
|
1698
|
+
const attachments = options.deferPersistence === true
|
|
1699
|
+
? []
|
|
1700
|
+
: await freezeAttachments(options.attachmentPaths, attachmentsDirectory);
|
|
1551
1701
|
// Ticket binding is LLM-only post-admission (#635); admission stays unbound.
|
|
1552
1702
|
|
|
1553
1703
|
const instruction = options.instruction;
|
|
@@ -1571,11 +1721,13 @@ export async function admitCountersignInvocation(
|
|
|
1571
1721
|
})),
|
|
1572
1722
|
};
|
|
1573
1723
|
const admittedRequestPath = join(runDirectory, "admitted-request.json");
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1724
|
+
if (options.deferPersistence !== true) {
|
|
1725
|
+
await writeAdmittedRequestPersistence(admittedRequestPath, admitted, {
|
|
1726
|
+
sessionDirectory,
|
|
1727
|
+
sessionFile,
|
|
1728
|
+
});
|
|
1729
|
+
await writeRoleInvocationLedger({ ...admitted, sessionDirectory, sessionFile }, admitted.role, options.model);
|
|
1730
|
+
}
|
|
1579
1731
|
|
|
1580
1732
|
return {
|
|
1581
1733
|
role: "countersign",
|
|
@@ -1592,6 +1744,36 @@ export async function admitCountersignInvocation(
|
|
|
1592
1744
|
};
|
|
1593
1745
|
}
|
|
1594
1746
|
|
|
1747
|
+
/** Persist a deferred Countersign admission after same-ticket lookup found no retained run. */
|
|
1748
|
+
export async function materializeCountersignInvocation(
|
|
1749
|
+
admitted: AdmittedCountersignInvocation,
|
|
1750
|
+
options: Pick<AdmitCountersignInvocationOptions, "home" | "principalAuthority" | "model"> & {
|
|
1751
|
+
preparedAttachments: readonly PreparedAttachment[];
|
|
1752
|
+
},
|
|
1753
|
+
): Promise<void> {
|
|
1754
|
+
const placement = issueAdmissionPlacement(options.principalAuthority, {
|
|
1755
|
+
cwd: admitted.projectRoot,
|
|
1756
|
+
runId: admitted.runId,
|
|
1757
|
+
role: "countersign",
|
|
1758
|
+
subject: { unbound: true },
|
|
1759
|
+
home: options.home,
|
|
1760
|
+
});
|
|
1761
|
+
const attachments = await freezePreparedAttachmentsIntoRun(
|
|
1762
|
+
options.preparedAttachments,
|
|
1763
|
+
admitted.runDirectory,
|
|
1764
|
+
);
|
|
1765
|
+
(admitted as { attachments: readonly FrozenAttachment[] }).attachments = attachments;
|
|
1766
|
+
await writeAdmittedRequestPersistence(admitted.admittedRequestPath, admitted, {
|
|
1767
|
+
sessionDirectory: placement.sessionDirectory,
|
|
1768
|
+
sessionFile: placement.sessionFile,
|
|
1769
|
+
});
|
|
1770
|
+
await writeRoleInvocationLedger(
|
|
1771
|
+
{ ...admitted, sessionDirectory: placement.sessionDirectory, sessionFile: placement.sessionFile },
|
|
1772
|
+
admitted.role,
|
|
1773
|
+
options.model,
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1595
1777
|
/** Build the Pi prompt transport for an admitted Countersign request. */
|
|
1596
1778
|
export function buildCountersignTransportPrompt(
|
|
1597
1779
|
admitted: AdmittedCountersignInvocation,
|
package/src/role-envelope.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { mkdir,
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { createServer, type Server, type Socket } from "node:net";
|
|
4
|
-
import {
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
import {
|
|
@@ -13,15 +13,14 @@ import { requireGatekeeperPass } from "./gatekeeper-pass-envelope.ts";
|
|
|
13
13
|
import type {
|
|
14
14
|
HostContext,
|
|
15
15
|
HostEventRegistration,
|
|
16
|
-
HostSkillExpansionEvidence,
|
|
17
16
|
HostToolDefinition,
|
|
18
17
|
RoleEnvelopeHost,
|
|
19
18
|
RoleHost,
|
|
20
19
|
RoleTurnKnownFailure,
|
|
21
20
|
RoleTurnRequest,
|
|
22
21
|
} from "./host-contracts.ts";
|
|
22
|
+
import { hostMethodSkills, installWorkspaceMethodSkills } from "./host-native-method.ts";
|
|
23
23
|
import { packagedRoleOutputTool } from "./packaged-role-registry.ts";
|
|
24
|
-
import { stripSkillFrontmatter } from "./package-resources/method-skill.ts";
|
|
25
24
|
import {
|
|
26
25
|
createRoleRuntimeExtension,
|
|
27
26
|
type RoleRuntimeDependencies,
|
|
@@ -44,30 +43,6 @@ type RpcRequest = { readonly id: number; readonly token: string; readonly method
|
|
|
44
43
|
type ToolCallParams = { readonly name?: unknown; readonly arguments?: unknown };
|
|
45
44
|
type ContentPart = { type: "text"; text: string } | { type: "image"; data: string; mimeType: string };
|
|
46
45
|
|
|
47
|
-
/**
|
|
48
|
-
* Build host-side Skill expansion evidence from pre-read RoleTurnRequest.methods.
|
|
49
|
-
* Non-pi hosts never parse Pi `/skill:` syntax (ADR 0082: pi is one adapter with
|
|
50
|
-
* no privilege; Pi-only seams stay inside the pi adapter);
|
|
51
|
-
* a single bound method treats the plain prompt as the preserved user message.
|
|
52
|
-
* Pi-native slash forms stay inside `src/pi/` only.
|
|
53
|
-
*/
|
|
54
|
-
export function buildSkillExpansion(
|
|
55
|
-
methodSkills: ReadonlyMap<string, { readonly path: string; readonly body: string }>,
|
|
56
|
-
prompt: string,
|
|
57
|
-
): HostSkillExpansionEvidence | undefined {
|
|
58
|
-
if (methodSkills.size !== 1) return undefined;
|
|
59
|
-
const entry = methodSkills.entries().next().value;
|
|
60
|
-
if (entry === undefined) return undefined;
|
|
61
|
-
const [name, method] = entry;
|
|
62
|
-
return Object.freeze({
|
|
63
|
-
name,
|
|
64
|
-
location: method.path,
|
|
65
|
-
content: method.body,
|
|
66
|
-
// Non-pi typed chain keeps original task bytes (ticket #822 r3); no consumer trim.
|
|
67
|
-
userMessage: prompt,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
46
|
async function listen(server: Server, path: string): Promise<void> {
|
|
72
47
|
await new Promise<void>((resolve, reject) => {
|
|
73
48
|
server.once("error", reject);
|
|
@@ -129,7 +104,6 @@ export async function prepareRoleEnvelope(options: {
|
|
|
129
104
|
const customEntries: Array<{ customType: string; data: unknown }> = [];
|
|
130
105
|
/** In-memory turn books for role lifecycle and audit subjects. */
|
|
131
106
|
const sessionEntries: Array<Record<string, unknown>> = [];
|
|
132
|
-
const methodSkills = new Map<string, { path: string; body: string }>();
|
|
133
107
|
let preferredTools: string[] = [];
|
|
134
108
|
let rejection:
|
|
135
109
|
| { readonly code: string; readonly toolCallIds: readonly string[]; readonly message: string }
|
|
@@ -140,14 +114,6 @@ export async function prepareRoleEnvelope(options: {
|
|
|
140
114
|
const runId = request.runDirectory.split("/").filter(Boolean).at(-1) ?? randomUUID();
|
|
141
115
|
await mkdir(request.runDirectory, { recursive: true });
|
|
142
116
|
|
|
143
|
-
// Canonical Skill expansion consumes RoleTurnRequest.methods (typed true source).
|
|
144
|
-
for (const method of request.methods) {
|
|
145
|
-
if (method.kind !== "skill") continue;
|
|
146
|
-
const name = basename(dirname(method.path));
|
|
147
|
-
const raw = await readFile(method.path, "utf8");
|
|
148
|
-
methodSkills.set(name, { path: method.path, body: stripSkillFrontmatter(raw).trim() });
|
|
149
|
-
}
|
|
150
|
-
|
|
151
117
|
// Durable principal file for isAvailable / resumable settlement (public-cli).
|
|
152
118
|
// #617 DK-4: header layout only — never host conversation/tool writeback into Pi JSONL.
|
|
153
119
|
let sessionFile = options.sessionFile ?? join(request.runDirectory, "session", "session.jsonl");
|
|
@@ -219,9 +185,8 @@ export async function prepareRoleEnvelope(options: {
|
|
|
219
185
|
// #836 删 1/A4.5: do not arm closeRound retry with「终局交卷并非本轮唯一工具调用」.
|
|
220
186
|
},
|
|
221
187
|
capabilities: {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
},
|
|
188
|
+
// #922: native loaders own expansion; package does not pre-read bodies (ADR 0032).
|
|
189
|
+
skillExpansion() { return undefined; },
|
|
225
190
|
},
|
|
226
191
|
registerFlag(name, definition) { if (!flags.has(name) && definition.default !== undefined) flags.set(name, definition.default); },
|
|
227
192
|
getFlag(name) { return flags.get(name); },
|
|
@@ -625,14 +590,10 @@ export async function prepareRoleEnvelope(options: {
|
|
|
625
590
|
message: { role: "user", content: prompt },
|
|
626
591
|
});
|
|
627
592
|
}
|
|
628
|
-
//
|
|
629
|
-
// Preloading session materials duplicated soul under the role tag (#632).
|
|
630
|
-
// #879: case-dossier owner reads runDirectory from this turn's HostContext.
|
|
631
|
-
|
|
632
|
-
const methodPrompt = (await Promise.all(request.methods.map(({ path }) => readFile(path, "utf8")))).join("\n\n");
|
|
593
|
+
// Soul/materials only — forced methods use host-native loaders (#922/#632/#879).
|
|
633
594
|
const promptResults = await emit("before_agent_start", {
|
|
634
595
|
prompt,
|
|
635
|
-
systemPrompt:
|
|
596
|
+
systemPrompt: "",
|
|
636
597
|
systemPromptOptions: {},
|
|
637
598
|
});
|
|
638
599
|
const systemPromptParts = promptResults.flatMap((value) => {
|
|
@@ -640,7 +601,7 @@ export async function prepareRoleEnvelope(options: {
|
|
|
640
601
|
if (!("systemPrompt" in value) || typeof value.systemPrompt !== "string") return [];
|
|
641
602
|
return [value.systemPrompt];
|
|
642
603
|
});
|
|
643
|
-
const systemPromptBody = systemPromptParts.
|
|
604
|
+
const systemPromptBody = systemPromptParts.join("\n\n");
|
|
644
605
|
// Typed reading materials from agent-start handlers (incl. single shared
|
|
645
606
|
// case-dossier owner). Folded into provider-visible systemPrompt at send.
|
|
646
607
|
const readingMaterials: unknown[] = [];
|
|
@@ -656,6 +617,15 @@ export async function prepareRoleEnvelope(options: {
|
|
|
656
617
|
throw new Error(`terminating tool not registered after activation: ${terminatingToolName}`);
|
|
657
618
|
}
|
|
658
619
|
const jsonSchema = terminatingToolJsonSchema(terminating.parameters);
|
|
620
|
+
|
|
621
|
+
if (request.host === "codex" && hostMethodSkills(request.methods).length > 0) {
|
|
622
|
+
const packageRoot = options.dependencies.packageRoot;
|
|
623
|
+
if (typeof packageRoot !== "string" || packageRoot === "") {
|
|
624
|
+
throw new Error("codex project Skill catalog requires packageRoot");
|
|
625
|
+
}
|
|
626
|
+
await installWorkspaceMethodSkills(request.cwd, packageRoot);
|
|
627
|
+
}
|
|
628
|
+
|
|
659
629
|
return {
|
|
660
630
|
mcpServers: [{
|
|
661
631
|
name: `ak-${request.activation.role}`,
|
|
@@ -10,8 +10,15 @@ import { loadNavigatorWorkContext } from "./navigator-work-context.ts";
|
|
|
10
10
|
import { loadNotarySourceRunLocator } from "./notary-source-run.ts";
|
|
11
11
|
import { loadPackagedCanonicalSkillBinding } from "./package-resources/method-skill-binding.ts";
|
|
12
12
|
import { formatNavigatorRoleHelp, type RoleRuntimeDependencies } from "./role-runtime.ts";
|
|
13
|
-
import {
|
|
14
|
-
|
|
13
|
+
import {
|
|
14
|
+
loadAuditorReferenceMaterialsFromSubjectInput,
|
|
15
|
+
loadAuditorSoulFromSubjectInput,
|
|
16
|
+
} from "./auditor-soul.ts";
|
|
17
|
+
import {
|
|
18
|
+
loadGatekeeperSessionMaterials,
|
|
19
|
+
loadMainRoleReferenceMaterials,
|
|
20
|
+
loadMainRoleSessionMaterials,
|
|
21
|
+
} from "./session-opening-materials.ts";
|
|
15
22
|
|
|
16
23
|
const navigatorRoutePlaybookPath = fileURLToPath(
|
|
17
24
|
new URL("../resources/navigator-route-playbook.md", import.meta.url),
|
|
@@ -20,12 +27,20 @@ const collectorHandbookSeedPath = fileURLToPath(
|
|
|
20
27
|
new URL("../resources/collector-bot-handbook.md", import.meta.url),
|
|
21
28
|
);
|
|
22
29
|
|
|
30
|
+
/** Single packaged source for reference materials across production composition roots. */
|
|
31
|
+
export function loadPackagedRoleReferenceMaterials(role: Parameters<NonNullable<RoleRuntimeDependencies["loadRoleReferenceMaterials"]>>[0]): Promise<string> {
|
|
32
|
+
return role === "auditor"
|
|
33
|
+
? loadAuditorReferenceMaterialsFromSubjectInput()
|
|
34
|
+
: loadMainRoleReferenceMaterials(role);
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
/** Host-neutral packaged role runtime deps for the parent-process envelope. */
|
|
24
38
|
export function createRoleRuntimeDependencies(packageRoot: string): RoleRuntimeDependencies {
|
|
25
39
|
const doctorAuditor = createPiDoctorAuditor();
|
|
26
40
|
const navigatorSessionFactory = createNativeNavigatorSessionFactory();
|
|
27
41
|
return {
|
|
28
42
|
packageRoot,
|
|
43
|
+
loadRoleReferenceMaterials: loadPackagedRoleReferenceMaterials,
|
|
29
44
|
loadJudgeSoul: () => loadMainRoleSessionMaterials("judge"),
|
|
30
45
|
loadFixerSoul: () => loadMainRoleSessionMaterials("fixer"),
|
|
31
46
|
loadFixPacket: (path) => readFile(path, "utf8"),
|
package/src/role-runtime.ts
CHANGED
|
@@ -580,6 +580,8 @@ type NavigatorAttendanceDependency = Omit<NavigatorAttendance, "knownRoutePlaybo
|
|
|
580
580
|
export type RoleRuntimeDependencies = {
|
|
581
581
|
/** Package root for packaged engine-note resolution (#879). */
|
|
582
582
|
packageRoot?: string;
|
|
583
|
+
/** Non-identity opening materials delivered in the ordinary role brief. */
|
|
584
|
+
loadRoleReferenceMaterials?(role: PackagedRole): Promise<string>;
|
|
583
585
|
loadJudgeSoul(): Promise<string>;
|
|
584
586
|
loadFixerSoul?(): Promise<string>;
|
|
585
587
|
loadFixPacket?(path: string): Promise<string>;
|
|
@@ -1266,7 +1268,8 @@ export function createRoleRuntimeExtension(
|
|
|
1266
1268
|
});
|
|
1267
1269
|
|
|
1268
1270
|
let admitted = false;
|
|
1269
|
-
let selectedRole:
|
|
1271
|
+
let selectedRole: PackagedRole | undefined;
|
|
1272
|
+
let roleReferenceMaterials = "";
|
|
1270
1273
|
/** Live Reviewer parent activation for envelope agent_start prompt assembly. */
|
|
1271
1274
|
let activeReviewerParent: ReviewerActivation | undefined;
|
|
1272
1275
|
/** Envelope-owned Reviewer Skill expansion state (ADR 0018 — not a role-module facade). */
|
|
@@ -1379,14 +1382,14 @@ export function createRoleRuntimeExtension(
|
|
|
1379
1382
|
activeReviewerParent.skillBinding.name,
|
|
1380
1383
|
text,
|
|
1381
1384
|
) ?? text;
|
|
1382
|
-
return text === event.text
|
|
1383
|
-
? { action: "continue" as const }
|
|
1384
|
-
: { action: "transform" as const, text };
|
|
1385
1385
|
}
|
|
1386
|
-
return
|
|
1387
|
-
? { action: "continue" as const }
|
|
1388
|
-
: { action: "transform" as const, text };
|
|
1386
|
+
return { action: "continue" as const };
|
|
1389
1387
|
});
|
|
1388
|
+
// Reference law/guides use the existing typed reading-material channel;
|
|
1389
|
+
// they are not rewritten into operator dialogue or the identity Soul.
|
|
1390
|
+
roleHost.on("before_agent_start", () => roleReferenceMaterials === ""
|
|
1391
|
+
? undefined
|
|
1392
|
+
: { readingMaterial: { kind: "role-reference-materials", content: roleReferenceMaterials } });
|
|
1390
1393
|
roleHost.on("before_agent_start", async (event, ctx) => {
|
|
1391
1394
|
const role = roleHost.getFlag(ROLE_FLAG.name);
|
|
1392
1395
|
const prompt = event.prompt;
|
|
@@ -2070,6 +2073,7 @@ export function createRoleRuntimeExtension(
|
|
|
2070
2073
|
}
|
|
2071
2074
|
admitted = false;
|
|
2072
2075
|
selectedRole = undefined;
|
|
2076
|
+
roleReferenceMaterials = "";
|
|
2073
2077
|
activeReviewerParent = undefined;
|
|
2074
2078
|
reviewerOriginalRequest = undefined;
|
|
2075
2079
|
reviewerExpansionCaptured = false;
|
|
@@ -2230,6 +2234,7 @@ export function createRoleRuntimeExtension(
|
|
|
2230
2234
|
}
|
|
2231
2235
|
|
|
2232
2236
|
await executeActivationStage(entry.role, activationStage(entry.role, runtime), { clock, writeTrace });
|
|
2237
|
+
roleReferenceMaterials = await dependencies.loadRoleReferenceMaterials?.(entry.role) ?? "";
|
|
2233
2238
|
// #357 T2 / #378 / #380 / #391 / #818: any role+engine activation registers the package detour tool once.
|
|
2234
2239
|
// Gate is resolveEngineName (RoleHost flag → env fallback) — no per-engine execute branch; no role-module spawn.
|
|
2235
2240
|
if (!engineDetourRegistered) {
|
|
@@ -50,12 +50,28 @@ export async function joinPackageMaterials(
|
|
|
50
50
|
relativePaths: readonly string[],
|
|
51
51
|
): Promise<string> {
|
|
52
52
|
const chunks: string[] = [];
|
|
53
|
-
for (const relativePath of relativePaths)
|
|
54
|
-
chunks.push(await readPackageMaterial(relativePath));
|
|
55
|
-
}
|
|
53
|
+
for (const relativePath of relativePaths) chunks.push(await readPackageMaterial(relativePath));
|
|
56
54
|
return chunks.join("\n\n");
|
|
57
55
|
}
|
|
58
56
|
|
|
57
|
+
function roleSoulPath(role: string, materials: readonly string[]): string {
|
|
58
|
+
const suffix = `/souls/${role}.md`;
|
|
59
|
+
const path = materials.find((candidate) => `/${candidate}`.endsWith(suffix));
|
|
60
|
+
if (path === undefined) throw new Error(`session materials omit the ${role} Soul`);
|
|
61
|
+
return path;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function loadSeparatedSessionPart(
|
|
65
|
+
role: string,
|
|
66
|
+
materials: readonly string[],
|
|
67
|
+
part: "soul" | "references",
|
|
68
|
+
): Promise<string> {
|
|
69
|
+
const soul = roleSoulPath(role, materials);
|
|
70
|
+
return part === "soul"
|
|
71
|
+
? readPackageMaterial(soul)
|
|
72
|
+
: joinPackageMaterials(materials.filter((path) => path !== soul));
|
|
73
|
+
}
|
|
74
|
+
|
|
59
75
|
type PublicRoleMaterials = {
|
|
60
76
|
readonly [Role in PackagedRole]: Extract<
|
|
61
77
|
(typeof PUBLIC_ROLE_RECORDS)[number],
|
|
@@ -72,10 +88,12 @@ export const MAIN_ROLE_SESSION_MATERIALS = {
|
|
|
72
88
|
|
|
73
89
|
export type MainRoleSession = keyof typeof MAIN_ROLE_SESSION_MATERIALS;
|
|
74
90
|
|
|
75
|
-
export function loadMainRoleSessionMaterials(
|
|
76
|
-
role
|
|
77
|
-
|
|
78
|
-
|
|
91
|
+
export function loadMainRoleSessionMaterials(role: MainRoleSession): Promise<string> {
|
|
92
|
+
return loadSeparatedSessionPart(role, MAIN_ROLE_SESSION_MATERIALS[role], "soul");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function loadMainRoleReferenceMaterials(role: MainRoleSession): Promise<string> {
|
|
96
|
+
return loadSeparatedSessionPart(role, MAIN_ROLE_SESSION_MATERIALS[role], "references");
|
|
79
97
|
}
|
|
80
98
|
|
|
81
99
|
/** Gatekeeper province; notary reuses the public notary materials definition. */
|
|
@@ -88,8 +106,6 @@ export const GATEKEEPER_SESSION_MATERIALS = {
|
|
|
88
106
|
|
|
89
107
|
export type GatekeeperSessionRole = keyof typeof GATEKEEPER_SESSION_MATERIALS;
|
|
90
108
|
|
|
91
|
-
export function loadGatekeeperSessionMaterials(
|
|
92
|
-
role
|
|
93
|
-
): Promise<string> {
|
|
94
|
-
return joinPackageMaterials(GATEKEEPER_SESSION_MATERIALS[role]);
|
|
109
|
+
export function loadGatekeeperSessionMaterials(role: GatekeeperSessionRole): Promise<string> {
|
|
110
|
+
return loadSeparatedSessionPart(role, GATEKEEPER_SESSION_MATERIALS[role], "soul");
|
|
95
111
|
}
|