@akagilnc/pi-workflow-roles 0.1.5577 → 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 +338 -617
- package/dist/collector-config.js +0 -44
- package/dist/headless-host/production-host.js +241 -520
- package/dist/migrate-book-topology.js +16 -32
- package/dist/package-resources/engine-material.js +22 -25
- package/dist/public-cli/instruction-seat-run.js +11 -21
- package/dist/public-cli/invocation.js +83 -193
- package/dist/public-cli/main.js +225 -528
- package/dist/public-cli/option-definitions.js +5 -5
- package/dist/public-cli/post-admission.js +12 -48
- package/dist/public-cli/run-lifecycle.js +13 -21
- 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/package-resources/engine-material.ts +42 -35
- package/src/public-cli/instruction-seat-run.ts +10 -27
- package/src/public-cli/invocation.ts +97 -292
- package/src/public-cli/option-definitions.ts +5 -5
- package/src/public-cli/post-admission.ts +13 -64
- package/src/public-cli/run-lifecycle.ts +13 -34
- package/src/public-role-summons.ts +1 -1
- package/src/role-run-placement.ts +0 -1
- package/src/role-run-relocation.ts +12 -22
- package/src/role-runtime.ts +18 -16
|
@@ -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,
|
|
@@ -94,10 +87,6 @@ import {
|
|
|
94
87
|
NotarySourceRunError,
|
|
95
88
|
resolveNotarySourceRunLocator,
|
|
96
89
|
} from "../notary-source-run.ts";
|
|
97
|
-
import {
|
|
98
|
-
appendEngineSessionMaterial,
|
|
99
|
-
type EngineSessionMaterial,
|
|
100
|
-
} from "../package-resources/engine-material.ts";
|
|
101
90
|
import { CliUsageError } from "./cli-errors.ts";
|
|
102
91
|
import {
|
|
103
92
|
REJECTED_PUBLIC_SPELLINGS,
|
|
@@ -114,16 +103,27 @@ import type { PublicThinkingLevel } from "./registry.ts";
|
|
|
114
103
|
|
|
115
104
|
import { errorText, isRecord } from "../unknown-value.ts";
|
|
116
105
|
|
|
106
|
+
/** Caller-supplied attachment path recorded as-is (#1165). */
|
|
117
107
|
export type FrozenAttachment = {
|
|
118
|
-
|
|
119
|
-
readonly provenancePath: string;
|
|
120
|
-
/** Absolute path of the admitted frozen snapshot bytes. */
|
|
121
|
-
readonly frozenPath: string;
|
|
122
|
-
readonly byteLength: number;
|
|
123
|
-
readonly sha256: string;
|
|
124
|
-
readonly mediaKind: "regular-file";
|
|
108
|
+
readonly path: string;
|
|
125
109
|
};
|
|
126
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
|
+
|
|
127
127
|
/** Shared admitted Role run identity (#106 common Invocation + #109 Coder). */
|
|
128
128
|
export type AdmittedRoleInvocationBase = {
|
|
129
129
|
readonly runId: string;
|
|
@@ -131,7 +131,7 @@ export type AdmittedRoleInvocationBase = {
|
|
|
131
131
|
readonly projectRoot: string;
|
|
132
132
|
/** Opaque instruction bytes as submitted. */
|
|
133
133
|
readonly instruction: string;
|
|
134
|
-
/** True when the caller supplied
|
|
134
|
+
/** True when the caller supplied a literally empty instruction (length 0). */
|
|
135
135
|
readonly instructionEmpty: boolean;
|
|
136
136
|
readonly attachments: readonly FrozenAttachment[];
|
|
137
137
|
readonly runDirectory: string;
|
|
@@ -226,7 +226,6 @@ export type AdmittedCollectorInvocation = AdmittedRoleInvocationBase & {
|
|
|
226
226
|
readonly prNumber?: number;
|
|
227
227
|
readonly repository: CollectorRepository;
|
|
228
228
|
readonly requestManifestPath?: string;
|
|
229
|
-
readonly manifestDigest: string;
|
|
230
229
|
};
|
|
231
230
|
|
|
232
231
|
export type AdmittedDoctorInvocation = AdmittedRoleInvocationBase & {
|
|
@@ -626,19 +625,12 @@ export async function relocateAdmittedRunToTicket(
|
|
|
626
625
|
"taskPath",
|
|
627
626
|
"packetPath",
|
|
628
627
|
"prerequisitesPath",
|
|
629
|
-
"requestManifestPath",
|
|
630
628
|
"mergerInputPath",
|
|
631
629
|
],
|
|
632
630
|
oldRunDirectory,
|
|
633
631
|
target.runDirectory,
|
|
634
632
|
);
|
|
635
|
-
|
|
636
|
-
(attachment as { frozenPath: string }).frozenPath = rewriteRunDirectoryPathValue(
|
|
637
|
-
attachment.frozenPath,
|
|
638
|
-
oldRunDirectory,
|
|
639
|
-
target.runDirectory,
|
|
640
|
-
) as string;
|
|
641
|
-
}
|
|
633
|
+
// #1165: caller --attach / --request-manifest paths stay as given; do not rewrite.
|
|
642
634
|
(admitted as { principal: DurablePrincipal }).principal = principal;
|
|
643
635
|
|
|
644
636
|
// current.json projects host.original from this run directory. Ownership is
|
|
@@ -846,7 +838,8 @@ function assignPublicSeatOption(
|
|
|
846
838
|
const value = taken.value;
|
|
847
839
|
switch (taken.def.id) {
|
|
848
840
|
case "attach":
|
|
849
|
-
|
|
841
|
+
// #1165: file-flag values are opaque; only a missing argument is a usage error.
|
|
842
|
+
fields.attachmentPaths.push(requireProvidedOptionValue(taken.def.canonical, value));
|
|
850
843
|
return;
|
|
851
844
|
case "project":
|
|
852
845
|
fields.project = requireOptionPath(taken.def.canonical, value);
|
|
@@ -892,7 +885,8 @@ function assignPublicSeatOption(
|
|
|
892
885
|
fields.repo = parseRepoOption(value);
|
|
893
886
|
return;
|
|
894
887
|
case "request-manifest":
|
|
895
|
-
|
|
888
|
+
// #1165: file-flag values are opaque; only a missing argument is a usage error.
|
|
889
|
+
fields.requestManifestPath = requireProvidedOptionValue(taken.def.canonical, value);
|
|
896
890
|
return;
|
|
897
891
|
case "issue": {
|
|
898
892
|
if (value === undefined || value.trim() === "") {
|
|
@@ -1076,6 +1070,20 @@ function requireOptionPath(
|
|
|
1076
1070
|
return value;
|
|
1077
1071
|
}
|
|
1078
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
|
+
|
|
1079
1087
|
/**
|
|
1080
1088
|
* Shared Skill-arg token rule for caller-controlled values projected into the
|
|
1081
1089
|
* space-joined Skill invocation line. Rejects blank, whitespace (smuggles the
|
|
@@ -1170,184 +1178,6 @@ export function requireAuthorityRef(value: string | undefined): string {
|
|
|
1170
1178
|
});
|
|
1171
1179
|
}
|
|
1172
1180
|
|
|
1173
|
-
async function readRegularFileAttachment(
|
|
1174
|
-
sourcePath: string,
|
|
1175
|
-
): Promise<{ absolute: string; bytes: Buffer }> {
|
|
1176
|
-
const absolute = isAbsolute(sourcePath) ? sourcePath : resolve(sourcePath);
|
|
1177
|
-
let st;
|
|
1178
|
-
try {
|
|
1179
|
-
st = await lstat(absolute);
|
|
1180
|
-
} catch (error) {
|
|
1181
|
-
throw new CliUsageError(
|
|
1182
|
-
`attachment is not a readable regular file: ${sourcePath}`,
|
|
1183
|
-
{ cause: error },
|
|
1184
|
-
);
|
|
1185
|
-
}
|
|
1186
|
-
if (!st.isFile() || st.isSymbolicLink()) {
|
|
1187
|
-
throw new CliUsageError(
|
|
1188
|
-
`attachment must be a regular file (not a directory or symlink): ${sourcePath}`,
|
|
1189
|
-
);
|
|
1190
|
-
}
|
|
1191
|
-
try {
|
|
1192
|
-
return { absolute, bytes: await readFile(absolute) };
|
|
1193
|
-
} catch (error) {
|
|
1194
|
-
throw new CliUsageError(
|
|
1195
|
-
`attachment is not a readable regular file: ${sourcePath}`,
|
|
1196
|
-
{ cause: error },
|
|
1197
|
-
);
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
export type PreparedAttachment = {
|
|
1202
|
-
readonly absolute: string;
|
|
1203
|
-
readonly snapshotPath: string;
|
|
1204
|
-
};
|
|
1205
|
-
|
|
1206
|
-
/**
|
|
1207
|
-
* Snapshot deferred inputs sequentially before identity side effects. The staging
|
|
1208
|
-
* files keep aggregate attachment bytes off heap until their final run is known.
|
|
1209
|
-
*/
|
|
1210
|
-
async function removePreparedAttachmentDirectory(stagingDirectory: string): Promise<void> {
|
|
1211
|
-
await rm(stagingDirectory, { recursive: true, force: true });
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
/** Own the complete deferred-snapshot lifetime without masking either failure. */
|
|
1215
|
-
export async function withPreparedAttachments<T>(
|
|
1216
|
-
attachmentPaths: readonly string[],
|
|
1217
|
-
use: (prepared: readonly PreparedAttachment[]) => Promise<T>,
|
|
1218
|
-
): Promise<T> {
|
|
1219
|
-
if (attachmentPaths.length === 0) return await use([]);
|
|
1220
|
-
const stagingDirectory = await mkdtemp(join(tmpdir(), "ak-role-attachments-"));
|
|
1221
|
-
const prepared: PreparedAttachment[] = [];
|
|
1222
|
-
let result: T;
|
|
1223
|
-
try {
|
|
1224
|
-
for (let index = 0; index < attachmentPaths.length; index += 1) {
|
|
1225
|
-
const { absolute, bytes } = await readRegularFileAttachment(attachmentPaths[index]!);
|
|
1226
|
-
const snapshotPath = join(stagingDirectory, String(index).padStart(6, "0"));
|
|
1227
|
-
await writeFile(snapshotPath, bytes);
|
|
1228
|
-
prepared.push({ absolute, snapshotPath });
|
|
1229
|
-
}
|
|
1230
|
-
result = await use(prepared);
|
|
1231
|
-
} catch (primary) {
|
|
1232
|
-
try {
|
|
1233
|
-
await removePreparedAttachmentDirectory(stagingDirectory);
|
|
1234
|
-
} catch (cleanup) {
|
|
1235
|
-
throw new AggregateError(
|
|
1236
|
-
[primary, cleanup],
|
|
1237
|
-
"attachment snapshot operation failed and cleanup also failed",
|
|
1238
|
-
{ cause: primary },
|
|
1239
|
-
);
|
|
1240
|
-
}
|
|
1241
|
-
throw primary;
|
|
1242
|
-
}
|
|
1243
|
-
await removePreparedAttachmentDirectory(stagingDirectory);
|
|
1244
|
-
return result;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
async function freezeAttachmentBytes(
|
|
1248
|
-
provenancePath: string,
|
|
1249
|
-
bytes: Buffer,
|
|
1250
|
-
destinationDir: string,
|
|
1251
|
-
index: number,
|
|
1252
|
-
): Promise<FrozenAttachment> {
|
|
1253
|
-
const frozenPath = join(
|
|
1254
|
-
destinationDir,
|
|
1255
|
-
`${String(index).padStart(2, "0")}-${basename(provenancePath)}`,
|
|
1256
|
-
);
|
|
1257
|
-
await writeFile(frozenPath, bytes);
|
|
1258
|
-
return {
|
|
1259
|
-
provenancePath,
|
|
1260
|
-
frozenPath,
|
|
1261
|
-
byteLength: bytes.byteLength,
|
|
1262
|
-
sha256: sha256Hex(bytes),
|
|
1263
|
-
mediaKind: "regular-file",
|
|
1264
|
-
};
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
async function freezePreparedAttachment(
|
|
1268
|
-
prepared: PreparedAttachment,
|
|
1269
|
-
destinationDir: string,
|
|
1270
|
-
index: number,
|
|
1271
|
-
): Promise<FrozenAttachment> {
|
|
1272
|
-
return freezeAttachmentBytes(
|
|
1273
|
-
prepared.absolute,
|
|
1274
|
-
await readFile(prepared.snapshotPath),
|
|
1275
|
-
destinationDir,
|
|
1276
|
-
index,
|
|
1277
|
-
);
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
async function freezeRegularFileAttachment(
|
|
1281
|
-
sourcePath: string,
|
|
1282
|
-
destinationDir: string,
|
|
1283
|
-
index: number,
|
|
1284
|
-
): Promise<{ attachment: FrozenAttachment; body: Buffer }> {
|
|
1285
|
-
const { absolute, bytes } = await readRegularFileAttachment(sourcePath);
|
|
1286
|
-
return {
|
|
1287
|
-
attachment: await freezeAttachmentBytes(absolute, bytes, destinationDir, index),
|
|
1288
|
-
body: bytes,
|
|
1289
|
-
};
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
/** Freeze attachments only — ticket binding is the shared LLM seat path (#635). */
|
|
1293
|
-
async function freezeAttachments(
|
|
1294
|
-
ledgerHome: string,
|
|
1295
|
-
attachmentPaths: readonly string[],
|
|
1296
|
-
attachmentsDirectory: string,
|
|
1297
|
-
): Promise<readonly FrozenAttachment[]> {
|
|
1298
|
-
const attachments: FrozenAttachment[] = [];
|
|
1299
|
-
// attachments/ appears only when something is frozen; created through the
|
|
1300
|
-
// ledger's symlink-rejecting tree walk, never a plain mkdir.
|
|
1301
|
-
if (attachmentPaths.length > 0) {
|
|
1302
|
-
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
1303
|
-
}
|
|
1304
|
-
for (let i = 0; i < attachmentPaths.length; i += 1) {
|
|
1305
|
-
const frozen = await freezeRegularFileAttachment(
|
|
1306
|
-
attachmentPaths[i]!,
|
|
1307
|
-
attachmentsDirectory,
|
|
1308
|
-
i,
|
|
1309
|
-
);
|
|
1310
|
-
attachments.push(frozen.attachment);
|
|
1311
|
-
}
|
|
1312
|
-
return attachments;
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
/**
|
|
1316
|
-
* Freeze summons attachments into an already-retained run (#637 same-ticket resume).
|
|
1317
|
-
* Writes under attachments/summons-<key>/ so prior freeze names stay intact.
|
|
1318
|
-
* Manual resume never calls this — birth attachments keep their original semantics.
|
|
1319
|
-
*/
|
|
1320
|
-
export async function freezeAttachmentsIntoRun(
|
|
1321
|
-
attachmentPaths: readonly string[],
|
|
1322
|
-
runDirectory: string,
|
|
1323
|
-
summonsKey: string = `s-${Date.now().toString(36)}`,
|
|
1324
|
-
): Promise<readonly FrozenAttachment[]> {
|
|
1325
|
-
if (attachmentPaths.length === 0) return [];
|
|
1326
|
-
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(runDirectory));
|
|
1327
|
-
const attachmentsDirectory = join(runDirectory, "attachments", summonsKey);
|
|
1328
|
-
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
1329
|
-
return freezeAttachments(ledgerHome, attachmentPaths, attachmentsDirectory);
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
/** Freeze prepared bytes and metadata through one path for birth or retained runs. */
|
|
1333
|
-
export async function freezePreparedAttachmentsIntoRun(
|
|
1334
|
-
prepared: readonly PreparedAttachment[],
|
|
1335
|
-
runDirectory: string,
|
|
1336
|
-
summonsKey?: string,
|
|
1337
|
-
): Promise<readonly FrozenAttachment[]> {
|
|
1338
|
-
if (prepared.length === 0) return [];
|
|
1339
|
-
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(runDirectory));
|
|
1340
|
-
const attachmentsDirectory = summonsKey === undefined
|
|
1341
|
-
? join(runDirectory, "attachments")
|
|
1342
|
-
: join(runDirectory, "attachments", summonsKey);
|
|
1343
|
-
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
1344
|
-
const attachments: FrozenAttachment[] = [];
|
|
1345
|
-
for (let index = 0; index < prepared.length; index += 1) {
|
|
1346
|
-
attachments.push(await freezePreparedAttachment(prepared[index]!, attachmentsDirectory, index));
|
|
1347
|
-
}
|
|
1348
|
-
return attachments;
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
1181
|
function ticketAdmissionFields(
|
|
1352
1182
|
ticketNumber: number | undefined,
|
|
1353
1183
|
): { ticketNumber?: number } {
|
|
@@ -1577,38 +1407,21 @@ export async function admitPublicRole(
|
|
|
1577
1407
|
} else {
|
|
1578
1408
|
repository = resolveGitHubRemoteRepository(projectRoot);
|
|
1579
1409
|
}
|
|
1580
|
-
|
|
1581
|
-
let manifestCanonicalJson: string | undefined;
|
|
1582
|
-
if (parsed.requestManifestPath !== undefined) {
|
|
1583
|
-
try {
|
|
1584
|
-
manifest = await loadCollectorManifest(parsed.requestManifestPath);
|
|
1585
|
-
manifestCanonicalJson = manifest.canonicalJson;
|
|
1586
|
-
} catch (error) {
|
|
1587
|
-
throw new CliUsageError(
|
|
1588
|
-
errorText(error),
|
|
1589
|
-
{ cause: error },
|
|
1590
|
-
);
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
const manifestDigest = manifest.digest;
|
|
1410
|
+
// #1165: pass caller path as-is; package does not read, validate, or rewrite.
|
|
1594
1411
|
const admittedCollector = await admitStandardMaterialInvocation("collector", {
|
|
1595
1412
|
...shared,
|
|
1596
1413
|
instruction,
|
|
1597
1414
|
attachmentPaths,
|
|
1598
1415
|
...project,
|
|
1599
|
-
placedFields: async (
|
|
1416
|
+
placedFields: async () => {
|
|
1600
1417
|
// #1088: explicit --pr only. Unbound PR is located by the LLM via host CLI.
|
|
1601
|
-
let requestManifestPath: string | undefined;
|
|
1602
|
-
if (manifestCanonicalJson !== undefined) {
|
|
1603
|
-
requestManifestPath = join(placed.runDirectory, "request-manifest.json");
|
|
1604
|
-
await writeFile(requestManifestPath, manifestCanonicalJson, "utf8");
|
|
1605
|
-
}
|
|
1606
1418
|
return {
|
|
1607
1419
|
...(explicitPrNumber === undefined ? {} : { prNumber: explicitPrNumber }),
|
|
1608
1420
|
repository: repository.canonical,
|
|
1609
1421
|
repositoryDisplay: repository.display,
|
|
1610
|
-
...(requestManifestPath === undefined
|
|
1611
|
-
|
|
1422
|
+
...(parsed.requestManifestPath === undefined
|
|
1423
|
+
? {}
|
|
1424
|
+
: { requestManifestPath: parsed.requestManifestPath }),
|
|
1612
1425
|
};
|
|
1613
1426
|
},
|
|
1614
1427
|
});
|
|
@@ -1621,12 +1434,10 @@ export async function admitPublicRole(
|
|
|
1621
1434
|
);
|
|
1622
1435
|
}
|
|
1623
1436
|
const issueNumber = parsed.issueNumber;
|
|
1624
|
-
|
|
1625
|
-
const admittedDoctor = await admitStandardMaterialInvocation("doctor", {
|
|
1437
|
+
return admitStandardMaterialInvocation("doctor", {
|
|
1626
1438
|
...shared,
|
|
1627
1439
|
instruction,
|
|
1628
|
-
attachmentPaths
|
|
1629
|
-
freezeAttachments: false,
|
|
1440
|
+
attachmentPaths,
|
|
1630
1441
|
...project,
|
|
1631
1442
|
placedFields: async (placed) => {
|
|
1632
1443
|
let caseRunsPath: string;
|
|
@@ -1667,16 +1478,13 @@ export async function admitPublicRole(
|
|
|
1667
1478
|
{ cause: error },
|
|
1668
1479
|
);
|
|
1669
1480
|
}
|
|
1670
|
-
frozenAttachments = await freezeAttachments(placed.ledgerHome, attachmentPaths, placed.attachmentsDirectory);
|
|
1671
1481
|
return {
|
|
1672
1482
|
issueNumber,
|
|
1673
1483
|
caseRunsPath,
|
|
1674
1484
|
caseIdentity,
|
|
1675
|
-
attachments: persistedAttachmentRefs(frozenAttachments),
|
|
1676
1485
|
};
|
|
1677
1486
|
},
|
|
1678
1487
|
});
|
|
1679
|
-
return { ...admittedDoctor, attachments: frozenAttachments };
|
|
1680
1488
|
}
|
|
1681
1489
|
case "source-locator": {
|
|
1682
1490
|
const projectRoot = resolve(parsed.project ?? shared.cwd);
|
|
@@ -1706,7 +1514,7 @@ export async function admitPublicRole(
|
|
|
1706
1514
|
instruction: "",
|
|
1707
1515
|
attachmentPaths: [],
|
|
1708
1516
|
...project,
|
|
1709
|
-
|
|
1517
|
+
recordAttachments: false,
|
|
1710
1518
|
admittedFields: {
|
|
1711
1519
|
sourceRunPath: sourceRun.runDirectory,
|
|
1712
1520
|
sourceRun,
|
|
@@ -1723,7 +1531,7 @@ export async function admitPublicRole(
|
|
|
1723
1531
|
instruction,
|
|
1724
1532
|
attachmentPaths: [],
|
|
1725
1533
|
...project,
|
|
1726
|
-
|
|
1534
|
+
recordAttachments: false,
|
|
1727
1535
|
admittedFields: { baseRevision },
|
|
1728
1536
|
});
|
|
1729
1537
|
}
|
|
@@ -1872,8 +1680,8 @@ async function placeRoleAdmission(options: {
|
|
|
1872
1680
|
readonly runId?: string;
|
|
1873
1681
|
readonly assertedTicketNumber?: number;
|
|
1874
1682
|
readonly attachmentPaths: readonly string[];
|
|
1875
|
-
/**
|
|
1876
|
-
readonly
|
|
1683
|
+
/** When false, leave attachments empty (deferred countersign / seats without attach). */
|
|
1684
|
+
readonly recordAttachments?: boolean;
|
|
1877
1685
|
/** Countersign reserves coordinates, then materializes after identity lookup. */
|
|
1878
1686
|
readonly materialize?: boolean;
|
|
1879
1687
|
}): Promise<PlacedRoleAdmission> {
|
|
@@ -1896,9 +1704,9 @@ async function placeRoleAdmission(options: {
|
|
|
1896
1704
|
home: options.home,
|
|
1897
1705
|
...(options.materialize === undefined ? {} : { materialize: options.materialize }),
|
|
1898
1706
|
});
|
|
1899
|
-
const attachments = options.
|
|
1707
|
+
const attachments = options.recordAttachments === false
|
|
1900
1708
|
? []
|
|
1901
|
-
:
|
|
1709
|
+
: admitCallerAttachments(options.attachmentPaths);
|
|
1902
1710
|
return {
|
|
1903
1711
|
runId,
|
|
1904
1712
|
bookKey,
|
|
@@ -1916,20 +1724,8 @@ async function placeRoleAdmission(options: {
|
|
|
1916
1724
|
|
|
1917
1725
|
function persistedAttachmentRefs(
|
|
1918
1726
|
attachments: readonly FrozenAttachment[],
|
|
1919
|
-
): ReadonlyArray<{
|
|
1920
|
-
|
|
1921
|
-
frozenPath: string;
|
|
1922
|
-
byteLength: number;
|
|
1923
|
-
sha256: string;
|
|
1924
|
-
mediaKind: FrozenAttachment["mediaKind"];
|
|
1925
|
-
}> {
|
|
1926
|
-
return attachments.map((attachment) => ({
|
|
1927
|
-
provenancePath: attachment.provenancePath,
|
|
1928
|
-
frozenPath: attachment.frozenPath,
|
|
1929
|
-
byteLength: attachment.byteLength,
|
|
1930
|
-
sha256: attachment.sha256,
|
|
1931
|
-
mediaKind: attachment.mediaKind,
|
|
1932
|
-
}));
|
|
1727
|
+
): ReadonlyArray<{ path: string }> {
|
|
1728
|
+
return attachments.map((attachment) => ({ path: attachment.path }));
|
|
1933
1729
|
}
|
|
1934
1730
|
|
|
1935
1731
|
async function persistPlacedAdmission(
|
|
@@ -1971,8 +1767,7 @@ async function persistPlacedAdmission(
|
|
|
1971
1767
|
* before materializeCountersignInvocation writes the page.
|
|
1972
1768
|
* Seats whose extra facts are known before placement pass them as admittedFields.
|
|
1973
1769
|
* Seats whose extra facts depend on the placed run pass placedFields
|
|
1974
|
-
* (coder writes task.md; fixer writes fix-packet.md;
|
|
1975
|
-
* 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).
|
|
1976
1771
|
*/
|
|
1977
1772
|
async function admitStandardMaterialInvocation<
|
|
1978
1773
|
R extends PackagedRole,
|
|
@@ -1980,10 +1775,10 @@ async function admitStandardMaterialInvocation<
|
|
|
1980
1775
|
>(
|
|
1981
1776
|
role: R,
|
|
1982
1777
|
options: AdmitInspectorInvocationOptions & {
|
|
1983
|
-
/** Reserve coordinates; skip
|
|
1778
|
+
/** Reserve coordinates; skip placement disk and admitted section write. */
|
|
1984
1779
|
readonly deferPersistence?: boolean;
|
|
1985
|
-
/**
|
|
1986
|
-
readonly
|
|
1780
|
+
/** Leave attachments empty (gleaner-left / notary / deferred countersign). */
|
|
1781
|
+
readonly recordAttachments?: boolean;
|
|
1987
1782
|
/** Seat facts already validated by admitPublicRole. */
|
|
1988
1783
|
readonly admittedFields?: Extra;
|
|
1989
1784
|
/** Seat facts that exist only after placement. Written before the admitted page. */
|
|
@@ -1991,14 +1786,14 @@ async function admitStandardMaterialInvocation<
|
|
|
1991
1786
|
},
|
|
1992
1787
|
): Promise<AdmittedRoleInvocationBase & { readonly role: R } & Extra> {
|
|
1993
1788
|
const defer = options.deferPersistence === true;
|
|
1994
|
-
const
|
|
1789
|
+
const skipAttachments = defer || options.recordAttachments === false;
|
|
1995
1790
|
const placed = await placeRoleAdmission({
|
|
1996
1791
|
role,
|
|
1997
1792
|
home: options.home,
|
|
1998
1793
|
principalAuthority: options.principalAuthority,
|
|
1999
1794
|
cwd: options.cwd,
|
|
2000
1795
|
attachmentPaths: options.attachmentPaths,
|
|
2001
|
-
...(
|
|
1796
|
+
...(skipAttachments ? { recordAttachments: false } : {}),
|
|
2002
1797
|
...(defer ? { materialize: false } : {}),
|
|
2003
1798
|
...(options.project === undefined ? {} : { project: options.project }),
|
|
2004
1799
|
...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
|
|
@@ -2011,7 +1806,8 @@ async function admitStandardMaterialInvocation<
|
|
|
2011
1806
|
? {}
|
|
2012
1807
|
: { correlationId: options.correlationId };
|
|
2013
1808
|
const instruction = options.instruction;
|
|
2014
|
-
|
|
1809
|
+
// Admitted emptiness is literal absence, not whitespace-only (#1165 J3).
|
|
1810
|
+
const instructionEmpty = instruction.length === 0;
|
|
2015
1811
|
const placedExtra = options.placedFields === undefined ? undefined : await options.placedFields(placed);
|
|
2016
1812
|
const admittedFields = {
|
|
2017
1813
|
...(options.admittedFields ?? ({} as Extra)),
|
|
@@ -2054,7 +1850,8 @@ type InstructionTransportSource = {
|
|
|
2054
1850
|
readonly role?: string;
|
|
2055
1851
|
readonly instruction: string;
|
|
2056
1852
|
readonly instructionEmpty: boolean;
|
|
2057
|
-
readonly attachments: readonly { readonly
|
|
1853
|
+
readonly attachments: readonly { readonly path: string }[];
|
|
1854
|
+
readonly requestManifestPath?: string;
|
|
2058
1855
|
readonly baseRevision?: string;
|
|
2059
1856
|
readonly lens?: ReviewerLens;
|
|
2060
1857
|
readonly authorityRefs?: readonly string[];
|
|
@@ -2073,28 +1870,30 @@ function admittedTransportPromptKind(
|
|
|
2073
1870
|
/**
|
|
2074
1871
|
* One initial prompt transport. The registry `transportPrompt` leaf selects
|
|
2075
1872
|
* a fixed kickoff, a bound baseline, or frozen skill args. Absent means the
|
|
2076
|
-
* caller instruction plus
|
|
1873
|
+
* caller instruction plus caller attachment paths as-is (#1165).
|
|
1874
|
+
* Engine / outsourcing material rides startup readingMaterial (#1167), not here.
|
|
2077
1875
|
*/
|
|
2078
1876
|
export function buildInstructionTransportPrompt(
|
|
2079
1877
|
admitted: InstructionTransportSource,
|
|
2080
|
-
engineMaterial?: EngineSessionMaterial,
|
|
2081
1878
|
): string {
|
|
2082
1879
|
const kind = admittedTransportPromptKind(admitted);
|
|
2083
1880
|
if (kind === "fixed-kickoff") {
|
|
2084
1881
|
if (admitted.sourceRunPath === undefined || admitted.sourceRunPath.trim() === "") {
|
|
2085
1882
|
throw new Error("fixed-kickoff transport prompt is missing the source run pointer");
|
|
2086
1883
|
}
|
|
2087
|
-
return
|
|
1884
|
+
return admitted.sourceRunPath;
|
|
2088
1885
|
}
|
|
2089
1886
|
if (kind === "baseline") {
|
|
2090
1887
|
if (admitted.baseRevision === undefined) {
|
|
2091
1888
|
throw new Error("baseline transport prompt is missing the bound revision");
|
|
2092
1889
|
}
|
|
2093
1890
|
const lines = [admitted.baseRevision];
|
|
2094
|
-
|
|
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) {
|
|
2095
1894
|
lines.push("", admitted.instruction);
|
|
2096
1895
|
}
|
|
2097
|
-
return
|
|
1896
|
+
return lines.join("\n");
|
|
2098
1897
|
}
|
|
2099
1898
|
if (kind === "skill-args") {
|
|
2100
1899
|
if (
|
|
@@ -2109,20 +1908,28 @@ export function buildInstructionTransportPrompt(
|
|
|
2109
1908
|
lens: admitted.lens,
|
|
2110
1909
|
authorityRefs: admitted.authorityRefs,
|
|
2111
1910
|
})];
|
|
2112
|
-
if (
|
|
1911
|
+
if (admitted.instruction.length > 0) {
|
|
2113
1912
|
lines.push("", admitted.instruction);
|
|
2114
1913
|
}
|
|
2115
|
-
return
|
|
1914
|
+
return lines.join("\n");
|
|
2116
1915
|
}
|
|
2117
|
-
|
|
2118
|
-
|
|
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) {
|
|
2119
1926
|
lines.push("");
|
|
2120
|
-
lines.push("
|
|
2121
|
-
for (const
|
|
2122
|
-
lines.push(`- ${
|
|
1927
|
+
lines.push("已受理附件:");
|
|
1928
|
+
for (const entry of flaggedPaths) {
|
|
1929
|
+
lines.push(`- ${entry}`);
|
|
2123
1930
|
}
|
|
2124
1931
|
}
|
|
2125
|
-
return
|
|
1932
|
+
return lines.join("\n");
|
|
2126
1933
|
}
|
|
2127
1934
|
|
|
2128
1935
|
export type AdmitCountersignInvocationOptions = {
|
|
@@ -2147,7 +1954,7 @@ export type AdmitCountersignInvocationOptions = {
|
|
|
2147
1954
|
export async function materializeCountersignInvocation(
|
|
2148
1955
|
admitted: AdmittedCountersignInvocation,
|
|
2149
1956
|
options: Pick<AdmitCountersignInvocationOptions, "home" | "principalAuthority" | "model"> & {
|
|
2150
|
-
|
|
1957
|
+
attachmentPaths: readonly string[];
|
|
2151
1958
|
/** Typed ticket known before the deferred run is written. */
|
|
2152
1959
|
ticketNumber?: number;
|
|
2153
1960
|
},
|
|
@@ -2157,8 +1964,7 @@ export async function materializeCountersignInvocation(
|
|
|
2157
1964
|
home: options.home,
|
|
2158
1965
|
principalAuthority: options.principalAuthority,
|
|
2159
1966
|
cwd: admitted.projectRoot,
|
|
2160
|
-
attachmentPaths:
|
|
2161
|
-
freezeAttachments: false,
|
|
1967
|
+
attachmentPaths: options.attachmentPaths,
|
|
2162
1968
|
runId: admitted.runId,
|
|
2163
1969
|
...(options.ticketNumber === undefined ? {} : { assertedTicketNumber: options.ticketNumber }),
|
|
2164
1970
|
});
|
|
@@ -2169,11 +1975,7 @@ export async function materializeCountersignInvocation(
|
|
|
2169
1975
|
if (ticketFields.ticketNumber !== undefined) {
|
|
2170
1976
|
(admitted as { ticketNumber?: number }).ticketNumber = ticketFields.ticketNumber;
|
|
2171
1977
|
}
|
|
2172
|
-
|
|
2173
|
-
options.preparedAttachments,
|
|
2174
|
-
admitted.runDirectory,
|
|
2175
|
-
);
|
|
2176
|
-
(admitted as { attachments: readonly FrozenAttachment[] }).attachments = attachments;
|
|
1978
|
+
(admitted as { attachments: readonly FrozenAttachment[] }).attachments = placement.attachments;
|
|
2177
1979
|
writeAdmittedRequestPersistence(admitted.runDirectory, admitted, {
|
|
2178
1980
|
sessionDirectory: placement.sessionDirectory,
|
|
2179
1981
|
sessionFile: placement.sessionFile,
|
|
@@ -2200,10 +2002,13 @@ export async function loadAdmittedJudgeRequest(
|
|
|
2200
2002
|
if (typeof record.instruction !== "string") return undefined;
|
|
2201
2003
|
if (typeof record.instructionEmpty !== "boolean") return undefined;
|
|
2202
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);
|
|
2203
2008
|
return {
|
|
2204
2009
|
instruction: record.instruction,
|
|
2205
2010
|
instructionEmpty: record.instructionEmpty,
|
|
2206
|
-
attachments
|
|
2011
|
+
attachments,
|
|
2207
2012
|
};
|
|
2208
2013
|
} catch {
|
|
2209
2014
|
return undefined;
|
|
@@ -2600,7 +2405,7 @@ export function parseAnalystArgv(args: readonly string[]): ParseAnalystArgvResul
|
|
|
2600
2405
|
if (taken.def.id === "attach") {
|
|
2601
2406
|
pushValue(
|
|
2602
2407
|
"attach",
|
|
2603
|
-
|
|
2408
|
+
requireProvidedOptionValue(taken.def.canonical, taken.value),
|
|
2604
2409
|
);
|
|
2605
2410
|
return;
|
|
2606
2411
|
}
|