@akagilnc/pi-workflow-roles 0.1.5583 → 0.1.5593
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/production-host.js +231 -466
- package/dist/collector-config.js +0 -44
- package/dist/headless-host/production-host.js +199 -434
- package/dist/migrate-book-topology.js +16 -30
- package/dist/public-cli/instruction-seat-run.js +8 -8
- package/dist/public-cli/invocation.js +77 -187
- package/dist/public-cli/main.js +137 -363
- package/dist/public-cli/option-definitions.js +5 -5
- package/dist/public-cli/post-admission.js +11 -40
- package/dist/public-cli/run-lifecycle.js +9 -14
- package/dist/public-role-summons.js +1 -1
- package/dist/role-run-relocation.js +12 -20
- package/package.json +1 -1
- package/src/collector-config.ts +0 -42
- package/src/collector-role.ts +10 -20
- package/src/public-cli/instruction-seat-run.ts +7 -8
- package/src/public-cli/invocation.ts +92 -283
- package/src/public-cli/option-definitions.ts +5 -5
- package/src/public-cli/post-admission.ts +12 -56
- package/src/public-cli/run-lifecycle.ts +9 -16
- package/src/public-role-summons.ts +1 -1
- package/src/role-run-placement.ts +0 -1
- package/src/role-run-relocation.ts +12 -22
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Public Invocation request admission: optional opaque instruction,
|
|
3
|
-
*
|
|
2
|
+
* Public Invocation request admission: optional opaque instruction, caller
|
|
3
|
+
* attachment paths as-is, project default/override (ADR 0052 / #106 / #1165).
|
|
4
4
|
*/
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
7
|
import {
|
|
8
|
-
lstat,
|
|
9
|
-
mkdtemp,
|
|
10
8
|
readFile,
|
|
11
9
|
realpath,
|
|
12
10
|
rename,
|
|
13
|
-
rm,
|
|
14
11
|
writeFile,
|
|
15
12
|
} from "node:fs/promises";
|
|
16
|
-
import {
|
|
17
|
-
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
13
|
+
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
18
14
|
|
|
19
15
|
import {
|
|
20
16
|
activationBookDirectory,
|
|
@@ -57,7 +53,6 @@ import {
|
|
|
57
53
|
} from "../run-ticket-number.ts";
|
|
58
54
|
import {
|
|
59
55
|
rewriteRunDirectoryPathFields,
|
|
60
|
-
rewriteRunDirectoryPathValue,
|
|
61
56
|
} from "../role-run-relocation.ts";
|
|
62
57
|
import { readPageSync, renderCurrentSync, updateSectionSync, writeSectionSync } from "../run-dossier.ts";
|
|
63
58
|
import {
|
|
@@ -65,8 +60,6 @@ import {
|
|
|
65
60
|
} from "../doctor-evidence.ts";
|
|
66
61
|
import type { DoctorCaseIdentity } from "../doctor-contracts.ts";
|
|
67
62
|
import {
|
|
68
|
-
emptyCollectorManifest,
|
|
69
|
-
loadCollectorManifest,
|
|
70
63
|
parseCollectorPrNumber,
|
|
71
64
|
parseCollectorRepository,
|
|
72
65
|
type CollectorRepository,
|
|
@@ -110,16 +103,27 @@ import type { PublicThinkingLevel } from "./registry.ts";
|
|
|
110
103
|
|
|
111
104
|
import { errorText, isRecord } from "../unknown-value.ts";
|
|
112
105
|
|
|
106
|
+
/** Caller-supplied attachment path recorded as-is (#1165). */
|
|
113
107
|
export type FrozenAttachment = {
|
|
114
|
-
|
|
115
|
-
readonly provenancePath: string;
|
|
116
|
-
/** Absolute path of the admitted frozen snapshot bytes. */
|
|
117
|
-
readonly frozenPath: string;
|
|
118
|
-
readonly byteLength: number;
|
|
119
|
-
readonly sha256: string;
|
|
120
|
-
readonly mediaKind: "regular-file";
|
|
108
|
+
readonly path: string;
|
|
121
109
|
};
|
|
122
110
|
|
|
111
|
+
/** Normalize admitted attachment records (caller path only; #1165). */
|
|
112
|
+
export function normalizeAdmittedAttachment(raw: unknown): FrozenAttachment | undefined {
|
|
113
|
+
if (typeof raw !== "object" || raw === null) return undefined;
|
|
114
|
+
const record = raw as Record<string, unknown>;
|
|
115
|
+
const path = record.path;
|
|
116
|
+
// Opaque caller path — keep "" / whitespace; only a non-string is absent.
|
|
117
|
+
if (typeof path === "string") return { path };
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function admitCallerAttachments(
|
|
122
|
+
attachmentPaths: readonly string[],
|
|
123
|
+
): readonly FrozenAttachment[] {
|
|
124
|
+
return attachmentPaths.map((path) => ({ path }));
|
|
125
|
+
}
|
|
126
|
+
|
|
123
127
|
/** Shared admitted Role run identity (#106 common Invocation + #109 Coder). */
|
|
124
128
|
export type AdmittedRoleInvocationBase = {
|
|
125
129
|
readonly runId: string;
|
|
@@ -127,7 +131,7 @@ export type AdmittedRoleInvocationBase = {
|
|
|
127
131
|
readonly projectRoot: string;
|
|
128
132
|
/** Opaque instruction bytes as submitted. */
|
|
129
133
|
readonly instruction: string;
|
|
130
|
-
/** True when the caller supplied
|
|
134
|
+
/** True when the caller supplied a literally empty instruction (length 0). */
|
|
131
135
|
readonly instructionEmpty: boolean;
|
|
132
136
|
readonly attachments: readonly FrozenAttachment[];
|
|
133
137
|
readonly runDirectory: string;
|
|
@@ -222,7 +226,6 @@ export type AdmittedCollectorInvocation = AdmittedRoleInvocationBase & {
|
|
|
222
226
|
readonly prNumber?: number;
|
|
223
227
|
readonly repository: CollectorRepository;
|
|
224
228
|
readonly requestManifestPath?: string;
|
|
225
|
-
readonly manifestDigest: string;
|
|
226
229
|
};
|
|
227
230
|
|
|
228
231
|
export type AdmittedDoctorInvocation = AdmittedRoleInvocationBase & {
|
|
@@ -622,19 +625,12 @@ export async function relocateAdmittedRunToTicket(
|
|
|
622
625
|
"taskPath",
|
|
623
626
|
"packetPath",
|
|
624
627
|
"prerequisitesPath",
|
|
625
|
-
"requestManifestPath",
|
|
626
628
|
"mergerInputPath",
|
|
627
629
|
],
|
|
628
630
|
oldRunDirectory,
|
|
629
631
|
target.runDirectory,
|
|
630
632
|
);
|
|
631
|
-
|
|
632
|
-
(attachment as { frozenPath: string }).frozenPath = rewriteRunDirectoryPathValue(
|
|
633
|
-
attachment.frozenPath,
|
|
634
|
-
oldRunDirectory,
|
|
635
|
-
target.runDirectory,
|
|
636
|
-
) as string;
|
|
637
|
-
}
|
|
633
|
+
// #1165: caller --attach / --request-manifest paths stay as given; do not rewrite.
|
|
638
634
|
(admitted as { principal: DurablePrincipal }).principal = principal;
|
|
639
635
|
|
|
640
636
|
// current.json projects host.original from this run directory. Ownership is
|
|
@@ -842,7 +838,8 @@ function assignPublicSeatOption(
|
|
|
842
838
|
const value = taken.value;
|
|
843
839
|
switch (taken.def.id) {
|
|
844
840
|
case "attach":
|
|
845
|
-
|
|
841
|
+
// #1165: file-flag values are opaque; only a missing argument is a usage error.
|
|
842
|
+
fields.attachmentPaths.push(requireProvidedOptionValue(taken.def.canonical, value));
|
|
846
843
|
return;
|
|
847
844
|
case "project":
|
|
848
845
|
fields.project = requireOptionPath(taken.def.canonical, value);
|
|
@@ -888,7 +885,8 @@ function assignPublicSeatOption(
|
|
|
888
885
|
fields.repo = parseRepoOption(value);
|
|
889
886
|
return;
|
|
890
887
|
case "request-manifest":
|
|
891
|
-
|
|
888
|
+
// #1165: file-flag values are opaque; only a missing argument is a usage error.
|
|
889
|
+
fields.requestManifestPath = requireProvidedOptionValue(taken.def.canonical, value);
|
|
892
890
|
return;
|
|
893
891
|
case "issue": {
|
|
894
892
|
if (value === undefined || value.trim() === "") {
|
|
@@ -1072,6 +1070,20 @@ function requireOptionPath(
|
|
|
1072
1070
|
return value;
|
|
1073
1071
|
}
|
|
1074
1072
|
|
|
1073
|
+
/**
|
|
1074
|
+
* #1165 file flags (--attach / --request-manifest): keep the provided string
|
|
1075
|
+
* opaque (including spaces and ""). Only a missing argument is a usage error.
|
|
1076
|
+
*/
|
|
1077
|
+
function requireProvidedOptionValue(
|
|
1078
|
+
flag: string,
|
|
1079
|
+
value: string | undefined,
|
|
1080
|
+
): string {
|
|
1081
|
+
if (value === undefined) {
|
|
1082
|
+
throw new CliUsageError(`${flag} requires a path`);
|
|
1083
|
+
}
|
|
1084
|
+
return value;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1075
1087
|
/**
|
|
1076
1088
|
* Shared Skill-arg token rule for caller-controlled values projected into the
|
|
1077
1089
|
* space-joined Skill invocation line. Rejects blank, whitespace (smuggles the
|
|
@@ -1166,184 +1178,6 @@ export function requireAuthorityRef(value: string | undefined): string {
|
|
|
1166
1178
|
});
|
|
1167
1179
|
}
|
|
1168
1180
|
|
|
1169
|
-
async function readRegularFileAttachment(
|
|
1170
|
-
sourcePath: string,
|
|
1171
|
-
): Promise<{ absolute: string; bytes: Buffer }> {
|
|
1172
|
-
const absolute = isAbsolute(sourcePath) ? sourcePath : resolve(sourcePath);
|
|
1173
|
-
let st;
|
|
1174
|
-
try {
|
|
1175
|
-
st = await lstat(absolute);
|
|
1176
|
-
} catch (error) {
|
|
1177
|
-
throw new CliUsageError(
|
|
1178
|
-
`attachment is not a readable regular file: ${sourcePath}`,
|
|
1179
|
-
{ cause: error },
|
|
1180
|
-
);
|
|
1181
|
-
}
|
|
1182
|
-
if (!st.isFile() || st.isSymbolicLink()) {
|
|
1183
|
-
throw new CliUsageError(
|
|
1184
|
-
`attachment must be a regular file (not a directory or symlink): ${sourcePath}`,
|
|
1185
|
-
);
|
|
1186
|
-
}
|
|
1187
|
-
try {
|
|
1188
|
-
return { absolute, bytes: await readFile(absolute) };
|
|
1189
|
-
} catch (error) {
|
|
1190
|
-
throw new CliUsageError(
|
|
1191
|
-
`attachment is not a readable regular file: ${sourcePath}`,
|
|
1192
|
-
{ cause: error },
|
|
1193
|
-
);
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
|
|
1197
|
-
export type PreparedAttachment = {
|
|
1198
|
-
readonly absolute: string;
|
|
1199
|
-
readonly snapshotPath: string;
|
|
1200
|
-
};
|
|
1201
|
-
|
|
1202
|
-
/**
|
|
1203
|
-
* Snapshot deferred inputs sequentially before identity side effects. The staging
|
|
1204
|
-
* files keep aggregate attachment bytes off heap until their final run is known.
|
|
1205
|
-
*/
|
|
1206
|
-
async function removePreparedAttachmentDirectory(stagingDirectory: string): Promise<void> {
|
|
1207
|
-
await rm(stagingDirectory, { recursive: true, force: true });
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
/** Own the complete deferred-snapshot lifetime without masking either failure. */
|
|
1211
|
-
export async function withPreparedAttachments<T>(
|
|
1212
|
-
attachmentPaths: readonly string[],
|
|
1213
|
-
use: (prepared: readonly PreparedAttachment[]) => Promise<T>,
|
|
1214
|
-
): Promise<T> {
|
|
1215
|
-
if (attachmentPaths.length === 0) return await use([]);
|
|
1216
|
-
const stagingDirectory = await mkdtemp(join(tmpdir(), "ak-role-attachments-"));
|
|
1217
|
-
const prepared: PreparedAttachment[] = [];
|
|
1218
|
-
let result: T;
|
|
1219
|
-
try {
|
|
1220
|
-
for (let index = 0; index < attachmentPaths.length; index += 1) {
|
|
1221
|
-
const { absolute, bytes } = await readRegularFileAttachment(attachmentPaths[index]!);
|
|
1222
|
-
const snapshotPath = join(stagingDirectory, String(index).padStart(6, "0"));
|
|
1223
|
-
await writeFile(snapshotPath, bytes);
|
|
1224
|
-
prepared.push({ absolute, snapshotPath });
|
|
1225
|
-
}
|
|
1226
|
-
result = await use(prepared);
|
|
1227
|
-
} catch (primary) {
|
|
1228
|
-
try {
|
|
1229
|
-
await removePreparedAttachmentDirectory(stagingDirectory);
|
|
1230
|
-
} catch (cleanup) {
|
|
1231
|
-
throw new AggregateError(
|
|
1232
|
-
[primary, cleanup],
|
|
1233
|
-
"attachment snapshot operation failed and cleanup also failed",
|
|
1234
|
-
{ cause: primary },
|
|
1235
|
-
);
|
|
1236
|
-
}
|
|
1237
|
-
throw primary;
|
|
1238
|
-
}
|
|
1239
|
-
await removePreparedAttachmentDirectory(stagingDirectory);
|
|
1240
|
-
return result;
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
async function freezeAttachmentBytes(
|
|
1244
|
-
provenancePath: string,
|
|
1245
|
-
bytes: Buffer,
|
|
1246
|
-
destinationDir: string,
|
|
1247
|
-
index: number,
|
|
1248
|
-
): Promise<FrozenAttachment> {
|
|
1249
|
-
const frozenPath = join(
|
|
1250
|
-
destinationDir,
|
|
1251
|
-
`${String(index).padStart(2, "0")}-${basename(provenancePath)}`,
|
|
1252
|
-
);
|
|
1253
|
-
await writeFile(frozenPath, bytes);
|
|
1254
|
-
return {
|
|
1255
|
-
provenancePath,
|
|
1256
|
-
frozenPath,
|
|
1257
|
-
byteLength: bytes.byteLength,
|
|
1258
|
-
sha256: sha256Hex(bytes),
|
|
1259
|
-
mediaKind: "regular-file",
|
|
1260
|
-
};
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
async function freezePreparedAttachment(
|
|
1264
|
-
prepared: PreparedAttachment,
|
|
1265
|
-
destinationDir: string,
|
|
1266
|
-
index: number,
|
|
1267
|
-
): Promise<FrozenAttachment> {
|
|
1268
|
-
return freezeAttachmentBytes(
|
|
1269
|
-
prepared.absolute,
|
|
1270
|
-
await readFile(prepared.snapshotPath),
|
|
1271
|
-
destinationDir,
|
|
1272
|
-
index,
|
|
1273
|
-
);
|
|
1274
|
-
}
|
|
1275
|
-
|
|
1276
|
-
async function freezeRegularFileAttachment(
|
|
1277
|
-
sourcePath: string,
|
|
1278
|
-
destinationDir: string,
|
|
1279
|
-
index: number,
|
|
1280
|
-
): Promise<{ attachment: FrozenAttachment; body: Buffer }> {
|
|
1281
|
-
const { absolute, bytes } = await readRegularFileAttachment(sourcePath);
|
|
1282
|
-
return {
|
|
1283
|
-
attachment: await freezeAttachmentBytes(absolute, bytes, destinationDir, index),
|
|
1284
|
-
body: bytes,
|
|
1285
|
-
};
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
/** Freeze attachments only — ticket binding is the shared LLM seat path (#635). */
|
|
1289
|
-
async function freezeAttachments(
|
|
1290
|
-
ledgerHome: string,
|
|
1291
|
-
attachmentPaths: readonly string[],
|
|
1292
|
-
attachmentsDirectory: string,
|
|
1293
|
-
): Promise<readonly FrozenAttachment[]> {
|
|
1294
|
-
const attachments: FrozenAttachment[] = [];
|
|
1295
|
-
// attachments/ appears only when something is frozen; created through the
|
|
1296
|
-
// ledger's symlink-rejecting tree walk, never a plain mkdir.
|
|
1297
|
-
if (attachmentPaths.length > 0) {
|
|
1298
|
-
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
1299
|
-
}
|
|
1300
|
-
for (let i = 0; i < attachmentPaths.length; i += 1) {
|
|
1301
|
-
const frozen = await freezeRegularFileAttachment(
|
|
1302
|
-
attachmentPaths[i]!,
|
|
1303
|
-
attachmentsDirectory,
|
|
1304
|
-
i,
|
|
1305
|
-
);
|
|
1306
|
-
attachments.push(frozen.attachment);
|
|
1307
|
-
}
|
|
1308
|
-
return attachments;
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
/**
|
|
1312
|
-
* Freeze summons attachments into an already-retained run (#637 same-ticket resume).
|
|
1313
|
-
* Writes under attachments/summons-<key>/ so prior freeze names stay intact.
|
|
1314
|
-
* Manual resume never calls this — birth attachments keep their original semantics.
|
|
1315
|
-
*/
|
|
1316
|
-
export async function freezeAttachmentsIntoRun(
|
|
1317
|
-
attachmentPaths: readonly string[],
|
|
1318
|
-
runDirectory: string,
|
|
1319
|
-
summonsKey: string = `s-${Date.now().toString(36)}`,
|
|
1320
|
-
): Promise<readonly FrozenAttachment[]> {
|
|
1321
|
-
if (attachmentPaths.length === 0) return [];
|
|
1322
|
-
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(runDirectory));
|
|
1323
|
-
const attachmentsDirectory = join(runDirectory, "attachments", summonsKey);
|
|
1324
|
-
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
1325
|
-
return freezeAttachments(ledgerHome, attachmentPaths, attachmentsDirectory);
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
/** Freeze prepared bytes and metadata through one path for birth or retained runs. */
|
|
1329
|
-
export async function freezePreparedAttachmentsIntoRun(
|
|
1330
|
-
prepared: readonly PreparedAttachment[],
|
|
1331
|
-
runDirectory: string,
|
|
1332
|
-
summonsKey?: string,
|
|
1333
|
-
): Promise<readonly FrozenAttachment[]> {
|
|
1334
|
-
if (prepared.length === 0) return [];
|
|
1335
|
-
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(runDirectory));
|
|
1336
|
-
const attachmentsDirectory = summonsKey === undefined
|
|
1337
|
-
? join(runDirectory, "attachments")
|
|
1338
|
-
: join(runDirectory, "attachments", summonsKey);
|
|
1339
|
-
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
1340
|
-
const attachments: FrozenAttachment[] = [];
|
|
1341
|
-
for (let index = 0; index < prepared.length; index += 1) {
|
|
1342
|
-
attachments.push(await freezePreparedAttachment(prepared[index]!, attachmentsDirectory, index));
|
|
1343
|
-
}
|
|
1344
|
-
return attachments;
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
1181
|
function ticketAdmissionFields(
|
|
1348
1182
|
ticketNumber: number | undefined,
|
|
1349
1183
|
): { ticketNumber?: number } {
|
|
@@ -1573,38 +1407,21 @@ export async function admitPublicRole(
|
|
|
1573
1407
|
} else {
|
|
1574
1408
|
repository = resolveGitHubRemoteRepository(projectRoot);
|
|
1575
1409
|
}
|
|
1576
|
-
|
|
1577
|
-
let manifestCanonicalJson: string | undefined;
|
|
1578
|
-
if (parsed.requestManifestPath !== undefined) {
|
|
1579
|
-
try {
|
|
1580
|
-
manifest = await loadCollectorManifest(parsed.requestManifestPath);
|
|
1581
|
-
manifestCanonicalJson = manifest.canonicalJson;
|
|
1582
|
-
} catch (error) {
|
|
1583
|
-
throw new CliUsageError(
|
|
1584
|
-
errorText(error),
|
|
1585
|
-
{ cause: error },
|
|
1586
|
-
);
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
const manifestDigest = manifest.digest;
|
|
1410
|
+
// #1165: pass caller path as-is; package does not read, validate, or rewrite.
|
|
1590
1411
|
const admittedCollector = await admitStandardMaterialInvocation("collector", {
|
|
1591
1412
|
...shared,
|
|
1592
1413
|
instruction,
|
|
1593
1414
|
attachmentPaths,
|
|
1594
1415
|
...project,
|
|
1595
|
-
placedFields: async (
|
|
1416
|
+
placedFields: async () => {
|
|
1596
1417
|
// #1088: explicit --pr only. Unbound PR is located by the LLM via host CLI.
|
|
1597
|
-
let requestManifestPath: string | undefined;
|
|
1598
|
-
if (manifestCanonicalJson !== undefined) {
|
|
1599
|
-
requestManifestPath = join(placed.runDirectory, "request-manifest.json");
|
|
1600
|
-
await writeFile(requestManifestPath, manifestCanonicalJson, "utf8");
|
|
1601
|
-
}
|
|
1602
1418
|
return {
|
|
1603
1419
|
...(explicitPrNumber === undefined ? {} : { prNumber: explicitPrNumber }),
|
|
1604
1420
|
repository: repository.canonical,
|
|
1605
1421
|
repositoryDisplay: repository.display,
|
|
1606
|
-
...(requestManifestPath === undefined
|
|
1607
|
-
|
|
1422
|
+
...(parsed.requestManifestPath === undefined
|
|
1423
|
+
? {}
|
|
1424
|
+
: { requestManifestPath: parsed.requestManifestPath }),
|
|
1608
1425
|
};
|
|
1609
1426
|
},
|
|
1610
1427
|
});
|
|
@@ -1617,12 +1434,10 @@ export async function admitPublicRole(
|
|
|
1617
1434
|
);
|
|
1618
1435
|
}
|
|
1619
1436
|
const issueNumber = parsed.issueNumber;
|
|
1620
|
-
|
|
1621
|
-
const admittedDoctor = await admitStandardMaterialInvocation("doctor", {
|
|
1437
|
+
return admitStandardMaterialInvocation("doctor", {
|
|
1622
1438
|
...shared,
|
|
1623
1439
|
instruction,
|
|
1624
|
-
attachmentPaths
|
|
1625
|
-
freezeAttachments: false,
|
|
1440
|
+
attachmentPaths,
|
|
1626
1441
|
...project,
|
|
1627
1442
|
placedFields: async (placed) => {
|
|
1628
1443
|
let caseRunsPath: string;
|
|
@@ -1663,16 +1478,13 @@ export async function admitPublicRole(
|
|
|
1663
1478
|
{ cause: error },
|
|
1664
1479
|
);
|
|
1665
1480
|
}
|
|
1666
|
-
frozenAttachments = await freezeAttachments(placed.ledgerHome, attachmentPaths, placed.attachmentsDirectory);
|
|
1667
1481
|
return {
|
|
1668
1482
|
issueNumber,
|
|
1669
1483
|
caseRunsPath,
|
|
1670
1484
|
caseIdentity,
|
|
1671
|
-
attachments: persistedAttachmentRefs(frozenAttachments),
|
|
1672
1485
|
};
|
|
1673
1486
|
},
|
|
1674
1487
|
});
|
|
1675
|
-
return { ...admittedDoctor, attachments: frozenAttachments };
|
|
1676
1488
|
}
|
|
1677
1489
|
case "source-locator": {
|
|
1678
1490
|
const projectRoot = resolve(parsed.project ?? shared.cwd);
|
|
@@ -1702,7 +1514,7 @@ export async function admitPublicRole(
|
|
|
1702
1514
|
instruction: "",
|
|
1703
1515
|
attachmentPaths: [],
|
|
1704
1516
|
...project,
|
|
1705
|
-
|
|
1517
|
+
recordAttachments: false,
|
|
1706
1518
|
admittedFields: {
|
|
1707
1519
|
sourceRunPath: sourceRun.runDirectory,
|
|
1708
1520
|
sourceRun,
|
|
@@ -1719,7 +1531,7 @@ export async function admitPublicRole(
|
|
|
1719
1531
|
instruction,
|
|
1720
1532
|
attachmentPaths: [],
|
|
1721
1533
|
...project,
|
|
1722
|
-
|
|
1534
|
+
recordAttachments: false,
|
|
1723
1535
|
admittedFields: { baseRevision },
|
|
1724
1536
|
});
|
|
1725
1537
|
}
|
|
@@ -1868,8 +1680,8 @@ async function placeRoleAdmission(options: {
|
|
|
1868
1680
|
readonly runId?: string;
|
|
1869
1681
|
readonly assertedTicketNumber?: number;
|
|
1870
1682
|
readonly attachmentPaths: readonly string[];
|
|
1871
|
-
/**
|
|
1872
|
-
readonly
|
|
1683
|
+
/** When false, leave attachments empty (deferred countersign / seats without attach). */
|
|
1684
|
+
readonly recordAttachments?: boolean;
|
|
1873
1685
|
/** Countersign reserves coordinates, then materializes after identity lookup. */
|
|
1874
1686
|
readonly materialize?: boolean;
|
|
1875
1687
|
}): Promise<PlacedRoleAdmission> {
|
|
@@ -1892,9 +1704,9 @@ async function placeRoleAdmission(options: {
|
|
|
1892
1704
|
home: options.home,
|
|
1893
1705
|
...(options.materialize === undefined ? {} : { materialize: options.materialize }),
|
|
1894
1706
|
});
|
|
1895
|
-
const attachments = options.
|
|
1707
|
+
const attachments = options.recordAttachments === false
|
|
1896
1708
|
? []
|
|
1897
|
-
:
|
|
1709
|
+
: admitCallerAttachments(options.attachmentPaths);
|
|
1898
1710
|
return {
|
|
1899
1711
|
runId,
|
|
1900
1712
|
bookKey,
|
|
@@ -1912,20 +1724,8 @@ async function placeRoleAdmission(options: {
|
|
|
1912
1724
|
|
|
1913
1725
|
function persistedAttachmentRefs(
|
|
1914
1726
|
attachments: readonly FrozenAttachment[],
|
|
1915
|
-
): ReadonlyArray<{
|
|
1916
|
-
|
|
1917
|
-
frozenPath: string;
|
|
1918
|
-
byteLength: number;
|
|
1919
|
-
sha256: string;
|
|
1920
|
-
mediaKind: FrozenAttachment["mediaKind"];
|
|
1921
|
-
}> {
|
|
1922
|
-
return attachments.map((attachment) => ({
|
|
1923
|
-
provenancePath: attachment.provenancePath,
|
|
1924
|
-
frozenPath: attachment.frozenPath,
|
|
1925
|
-
byteLength: attachment.byteLength,
|
|
1926
|
-
sha256: attachment.sha256,
|
|
1927
|
-
mediaKind: attachment.mediaKind,
|
|
1928
|
-
}));
|
|
1727
|
+
): ReadonlyArray<{ path: string }> {
|
|
1728
|
+
return attachments.map((attachment) => ({ path: attachment.path }));
|
|
1929
1729
|
}
|
|
1930
1730
|
|
|
1931
1731
|
async function persistPlacedAdmission(
|
|
@@ -1967,8 +1767,7 @@ async function persistPlacedAdmission(
|
|
|
1967
1767
|
* before materializeCountersignInvocation writes the page.
|
|
1968
1768
|
* Seats whose extra facts are known before placement pass them as admittedFields.
|
|
1969
1769
|
* Seats whose extra facts depend on the placed run pass placedFields
|
|
1970
|
-
* (coder writes task.md; fixer writes fix-packet.md;
|
|
1971
|
-
* doctor resolves the case, then freezes attachments; merger writes merger-input.json).
|
|
1770
|
+
* (coder writes task.md; fixer writes fix-packet.md; merger writes merger-input.json).
|
|
1972
1771
|
*/
|
|
1973
1772
|
async function admitStandardMaterialInvocation<
|
|
1974
1773
|
R extends PackagedRole,
|
|
@@ -1976,10 +1775,10 @@ async function admitStandardMaterialInvocation<
|
|
|
1976
1775
|
>(
|
|
1977
1776
|
role: R,
|
|
1978
1777
|
options: AdmitInspectorInvocationOptions & {
|
|
1979
|
-
/** Reserve coordinates; skip
|
|
1778
|
+
/** Reserve coordinates; skip placement disk and admitted section write. */
|
|
1980
1779
|
readonly deferPersistence?: boolean;
|
|
1981
|
-
/**
|
|
1982
|
-
readonly
|
|
1780
|
+
/** Leave attachments empty (gleaner-left / notary / deferred countersign). */
|
|
1781
|
+
readonly recordAttachments?: boolean;
|
|
1983
1782
|
/** Seat facts already validated by admitPublicRole. */
|
|
1984
1783
|
readonly admittedFields?: Extra;
|
|
1985
1784
|
/** Seat facts that exist only after placement. Written before the admitted page. */
|
|
@@ -1987,14 +1786,14 @@ async function admitStandardMaterialInvocation<
|
|
|
1987
1786
|
},
|
|
1988
1787
|
): Promise<AdmittedRoleInvocationBase & { readonly role: R } & Extra> {
|
|
1989
1788
|
const defer = options.deferPersistence === true;
|
|
1990
|
-
const
|
|
1789
|
+
const skipAttachments = defer || options.recordAttachments === false;
|
|
1991
1790
|
const placed = await placeRoleAdmission({
|
|
1992
1791
|
role,
|
|
1993
1792
|
home: options.home,
|
|
1994
1793
|
principalAuthority: options.principalAuthority,
|
|
1995
1794
|
cwd: options.cwd,
|
|
1996
1795
|
attachmentPaths: options.attachmentPaths,
|
|
1997
|
-
...(
|
|
1796
|
+
...(skipAttachments ? { recordAttachments: false } : {}),
|
|
1998
1797
|
...(defer ? { materialize: false } : {}),
|
|
1999
1798
|
...(options.project === undefined ? {} : { project: options.project }),
|
|
2000
1799
|
...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
|
|
@@ -2007,7 +1806,8 @@ async function admitStandardMaterialInvocation<
|
|
|
2007
1806
|
? {}
|
|
2008
1807
|
: { correlationId: options.correlationId };
|
|
2009
1808
|
const instruction = options.instruction;
|
|
2010
|
-
|
|
1809
|
+
// Admitted emptiness is literal absence, not whitespace-only (#1165 J3).
|
|
1810
|
+
const instructionEmpty = instruction.length === 0;
|
|
2011
1811
|
const placedExtra = options.placedFields === undefined ? undefined : await options.placedFields(placed);
|
|
2012
1812
|
const admittedFields = {
|
|
2013
1813
|
...(options.admittedFields ?? ({} as Extra)),
|
|
@@ -2050,7 +1850,8 @@ type InstructionTransportSource = {
|
|
|
2050
1850
|
readonly role?: string;
|
|
2051
1851
|
readonly instruction: string;
|
|
2052
1852
|
readonly instructionEmpty: boolean;
|
|
2053
|
-
readonly attachments: readonly { readonly
|
|
1853
|
+
readonly attachments: readonly { readonly path: string }[];
|
|
1854
|
+
readonly requestManifestPath?: string;
|
|
2054
1855
|
readonly baseRevision?: string;
|
|
2055
1856
|
readonly lens?: ReviewerLens;
|
|
2056
1857
|
readonly authorityRefs?: readonly string[];
|
|
@@ -2069,7 +1870,7 @@ function admittedTransportPromptKind(
|
|
|
2069
1870
|
/**
|
|
2070
1871
|
* One initial prompt transport. The registry `transportPrompt` leaf selects
|
|
2071
1872
|
* a fixed kickoff, a bound baseline, or frozen skill args. Absent means the
|
|
2072
|
-
* caller instruction plus
|
|
1873
|
+
* caller instruction plus caller attachment paths as-is (#1165).
|
|
2073
1874
|
* Engine / outsourcing material rides startup readingMaterial (#1167), not here.
|
|
2074
1875
|
*/
|
|
2075
1876
|
export function buildInstructionTransportPrompt(
|
|
@@ -2087,7 +1888,9 @@ export function buildInstructionTransportPrompt(
|
|
|
2087
1888
|
throw new Error("baseline transport prompt is missing the bound revision");
|
|
2088
1889
|
}
|
|
2089
1890
|
const lines = [admitted.baseRevision];
|
|
2090
|
-
|
|
1891
|
+
// Use raw admitted.instruction — do not gate on instructionEmpty metadata
|
|
1892
|
+
// (old pages may set that flag wrong while still holding whitespace text).
|
|
1893
|
+
if (admitted.instruction.length > 0) {
|
|
2091
1894
|
lines.push("", admitted.instruction);
|
|
2092
1895
|
}
|
|
2093
1896
|
return lines.join("\n");
|
|
@@ -2105,17 +1908,25 @@ export function buildInstructionTransportPrompt(
|
|
|
2105
1908
|
lens: admitted.lens,
|
|
2106
1909
|
authorityRefs: admitted.authorityRefs,
|
|
2107
1910
|
})];
|
|
2108
|
-
if (
|
|
1911
|
+
if (admitted.instruction.length > 0) {
|
|
2109
1912
|
lines.push("", admitted.instruction);
|
|
2110
1913
|
}
|
|
2111
1914
|
return lines.join("\n");
|
|
2112
1915
|
}
|
|
2113
|
-
|
|
2114
|
-
|
|
1916
|
+
// #1165 J3: assemble from the admitted instruction bytes themselves.
|
|
1917
|
+
const lines: string[] = [admitted.instruction];
|
|
1918
|
+
// #1164/#1165: each caller path keeps its file-flag provenance.
|
|
1919
|
+
const flaggedPaths: string[] = [
|
|
1920
|
+
...admitted.attachments.map((attachment) => `--attach ${attachment.path}`),
|
|
1921
|
+
...(admitted.requestManifestPath === undefined
|
|
1922
|
+
? []
|
|
1923
|
+
: [`--request-manifest ${admitted.requestManifestPath}`]),
|
|
1924
|
+
];
|
|
1925
|
+
if (flaggedPaths.length > 0) {
|
|
2115
1926
|
lines.push("");
|
|
2116
|
-
lines.push("
|
|
2117
|
-
for (const
|
|
2118
|
-
lines.push(`- ${
|
|
1927
|
+
lines.push("已受理附件:");
|
|
1928
|
+
for (const entry of flaggedPaths) {
|
|
1929
|
+
lines.push(`- ${entry}`);
|
|
2119
1930
|
}
|
|
2120
1931
|
}
|
|
2121
1932
|
return lines.join("\n");
|
|
@@ -2143,7 +1954,7 @@ export type AdmitCountersignInvocationOptions = {
|
|
|
2143
1954
|
export async function materializeCountersignInvocation(
|
|
2144
1955
|
admitted: AdmittedCountersignInvocation,
|
|
2145
1956
|
options: Pick<AdmitCountersignInvocationOptions, "home" | "principalAuthority" | "model"> & {
|
|
2146
|
-
|
|
1957
|
+
attachmentPaths: readonly string[];
|
|
2147
1958
|
/** Typed ticket known before the deferred run is written. */
|
|
2148
1959
|
ticketNumber?: number;
|
|
2149
1960
|
},
|
|
@@ -2153,8 +1964,7 @@ export async function materializeCountersignInvocation(
|
|
|
2153
1964
|
home: options.home,
|
|
2154
1965
|
principalAuthority: options.principalAuthority,
|
|
2155
1966
|
cwd: admitted.projectRoot,
|
|
2156
|
-
attachmentPaths:
|
|
2157
|
-
freezeAttachments: false,
|
|
1967
|
+
attachmentPaths: options.attachmentPaths,
|
|
2158
1968
|
runId: admitted.runId,
|
|
2159
1969
|
...(options.ticketNumber === undefined ? {} : { assertedTicketNumber: options.ticketNumber }),
|
|
2160
1970
|
});
|
|
@@ -2165,11 +1975,7 @@ export async function materializeCountersignInvocation(
|
|
|
2165
1975
|
if (ticketFields.ticketNumber !== undefined) {
|
|
2166
1976
|
(admitted as { ticketNumber?: number }).ticketNumber = ticketFields.ticketNumber;
|
|
2167
1977
|
}
|
|
2168
|
-
|
|
2169
|
-
options.preparedAttachments,
|
|
2170
|
-
admitted.runDirectory,
|
|
2171
|
-
);
|
|
2172
|
-
(admitted as { attachments: readonly FrozenAttachment[] }).attachments = attachments;
|
|
1978
|
+
(admitted as { attachments: readonly FrozenAttachment[] }).attachments = placement.attachments;
|
|
2173
1979
|
writeAdmittedRequestPersistence(admitted.runDirectory, admitted, {
|
|
2174
1980
|
sessionDirectory: placement.sessionDirectory,
|
|
2175
1981
|
sessionFile: placement.sessionFile,
|
|
@@ -2196,10 +2002,13 @@ export async function loadAdmittedJudgeRequest(
|
|
|
2196
2002
|
if (typeof record.instruction !== "string") return undefined;
|
|
2197
2003
|
if (typeof record.instructionEmpty !== "boolean") return undefined;
|
|
2198
2004
|
if (!Array.isArray(record.attachments)) return undefined;
|
|
2005
|
+
const attachments = record.attachments
|
|
2006
|
+
.map((item) => normalizeAdmittedAttachment(item))
|
|
2007
|
+
.filter((item): item is FrozenAttachment => item !== undefined);
|
|
2199
2008
|
return {
|
|
2200
2009
|
instruction: record.instruction,
|
|
2201
2010
|
instructionEmpty: record.instructionEmpty,
|
|
2202
|
-
attachments
|
|
2011
|
+
attachments,
|
|
2203
2012
|
};
|
|
2204
2013
|
} catch {
|
|
2205
2014
|
return undefined;
|
|
@@ -2596,7 +2405,7 @@ export function parseAnalystArgv(args: readonly string[]): ParseAnalystArgvResul
|
|
|
2596
2405
|
if (taken.def.id === "attach") {
|
|
2597
2406
|
pushValue(
|
|
2598
2407
|
"attach",
|
|
2599
|
-
|
|
2408
|
+
requireProvidedOptionValue(taken.def.canonical, taken.value),
|
|
2600
2409
|
);
|
|
2601
2410
|
return;
|
|
2602
2411
|
}
|
|
@@ -344,7 +344,7 @@ const SHARED_PROJECT_SEMANTICS = {
|
|
|
344
344
|
} as const satisfies Omit<PublicOptionDefinition, "owner">;
|
|
345
345
|
|
|
346
346
|
/**
|
|
347
|
-
* Immutable shared semantics for the common
|
|
347
|
+
* Immutable shared semantics for the common `--attach` face (#1165).
|
|
348
348
|
* Role rows bind owner only. Reviewer has no attach face; analyst sweep attach
|
|
349
349
|
* keeps its own modes/selectsMode/description and must not use this binding.
|
|
350
350
|
*/
|
|
@@ -357,8 +357,8 @@ const SHARED_ATTACH_SEMANTICS = {
|
|
|
357
357
|
repeatable: true,
|
|
358
358
|
form: "option" as const,
|
|
359
359
|
description: {
|
|
360
|
-
en: "
|
|
361
|
-
zh: "
|
|
360
|
+
en: "Caller file path passed as-is to the role (repeatable). Package does not read, copy, or validate; use an absolute path when cwd differs from --project.",
|
|
361
|
+
zh: "调用方文件路径原样递给衙门(可重复)。包不读、不复制、不校验;调用方当前目录与 --project 不同时请给绝对路径。",
|
|
362
362
|
},
|
|
363
363
|
} as const satisfies Omit<PublicOptionDefinition, "owner">;
|
|
364
364
|
|
|
@@ -556,8 +556,8 @@ const COLLECTOR_OPTIONS = [
|
|
|
556
556
|
repeatable: false,
|
|
557
557
|
form: "option",
|
|
558
558
|
description: {
|
|
559
|
-
en: "
|
|
560
|
-
zh: "
|
|
559
|
+
en: "Caller path to a request manifest JSON file, passed as-is. Package does not read, copy, or validate; use an absolute path when cwd differs from --project.",
|
|
560
|
+
zh: "调用方 request manifest JSON 路径,原样传递。包不读、不复制、不校验;调用方当前目录与 --project 不同时请给绝对路径。",
|
|
561
561
|
},
|
|
562
562
|
},
|
|
563
563
|
] as const satisfies readonly PublicOptionDefinition[];
|