@akagilnc/pi-workflow-roles 0.1.5583 → 0.1.5598
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 +393 -854
- package/dist/collector-config.js +0 -44
- package/dist/headless-host/production-host.js +361 -822
- package/dist/merger-contracts.js +6 -2
- package/dist/migrate-book-topology.js +4855 -10472
- package/dist/package-contracts/worker-output.js +1 -6
- package/dist/packaged-role-registry.js +5 -4
- package/dist/public-cli/instruction-seat-run.js +8 -8
- package/dist/public-cli/invocation.js +93 -236
- package/dist/public-cli/main.js +18409 -18786
- package/dist/public-cli/option-definitions.js +7 -7
- package/dist/public-cli/post-admission.js +11 -40
- package/dist/public-cli/run-lifecycle.js +11 -43
- package/dist/public-role-summons.js +1 -1
- package/dist/role-run-relocation.js +14 -23
- package/package.json +1 -1
- package/scripts/build-package.mjs +0 -1
- package/src/collector-config.ts +0 -42
- package/src/collector-role.ts +10 -20
- package/src/host-contracts.ts +0 -3
- package/src/merger-contracts.ts +6 -2
- package/src/merger-role.ts +11 -1
- package/src/navigator-attendance.ts +2 -2
- package/src/package-contracts/worker-output.ts +0 -8
- package/src/packaged-role-registry.ts +4 -3
- package/src/public-cli/instruction-seat-run.ts +7 -8
- package/src/public-cli/invocation.ts +113 -351
- package/src/public-cli/option-definitions.ts +7 -7
- package/src/public-cli/post-admission.ts +12 -56
- package/src/public-cli/run-lifecycle.ts +11 -55
- package/src/public-role-summons.ts +1 -1
- package/src/role-run-placement.ts +0 -1
- package/src/role-run-relocation.ts +14 -25
- package/src/role-runtime-dependencies.ts +0 -2
- package/src/role-runtime.ts +0 -16
- package/src/worker-role.ts +11 -78
- package/dist/package-contracts/fixer-packet.js +0 -77
- package/packets/fixer-prerequisites.json +0 -6
- package/src/package-contracts/fixer-packet.ts +0 -91
|
@@ -1,23 +1,21 @@
|
|
|
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
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
7
|
+
import { readFile, realpath, rename, writeFile, } from "node:fs/promises";
|
|
8
|
+
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
10
9
|
import { activationBookDirectory, ensureRealDirectoryTree, homeFromRunDirectory, pathContainedIn, resolveActivationLedgerHome, } from "../activation-ledger-topology.js";
|
|
11
10
|
import { resolveBookKeyFromGit } from "../activation-ledger-git.js";
|
|
12
11
|
import { ensureRoleRunDirectory, ensureRoleRunPlacement, formatRunLeaf, isUnboundRunDirectory, listBookRunDirectories, roleRunPlacement, } from "../role-run-placement.js";
|
|
13
12
|
import { packagedAdmittedSubject, packagedArgvResult, packagedBareToken, packagedBaseIsRevision, packagedEmitsAuthorityRefs, packagedPublicInstructionSubject, packagedRoleMetadata, packagedStoresSourceRunRaw, packagedSubjectChoices, } from "../packaged-role-registry.js";
|
|
14
13
|
import { isSafePositiveTicketNumber, parseTicketNumber, readBoardTicketNumber, requireSafePositiveTicketNumber, } from "../run-ticket-number.js";
|
|
15
|
-
import { rewriteRunDirectoryPathFields,
|
|
14
|
+
import { rewriteRunDirectoryPathFields, } from "../role-run-relocation.js";
|
|
16
15
|
import { readPageSync, renderCurrentSync, updateSectionSync, writeSectionSync } from "../run-dossier.js";
|
|
17
16
|
import { loadDoctorCase, } from "../doctor-evidence.js";
|
|
18
|
-
import {
|
|
17
|
+
import { parseCollectorPrNumber, parseCollectorRepository, } from "../collector-config.js";
|
|
19
18
|
import { ownerRepoFromGitHubRemoteUrl } from "./github-remote.js";
|
|
20
|
-
import { FixerPacketValidationError, parseFixerPrerequisites, } from "../package-contracts/fixer-packet.js";
|
|
21
19
|
import { createProductionMergerGitState } from "../merger-git-state.js";
|
|
22
20
|
import { validateMergerInput, } from "../merger-contracts.js";
|
|
23
21
|
import { sha256Hex } from "../sha256.js";
|
|
@@ -28,6 +26,20 @@ import { NotarySourceRunError, resolveNotarySourceRunLocator, } from "../notary-
|
|
|
28
26
|
import { CliUsageError } from "./cli-errors.js";
|
|
29
27
|
import { REJECTED_PUBLIC_SPELLINGS, createTypedOptionConsumer, evaluateAnalystModeOptionContract, optionsForOwner, resolveAnalystMode, } from "./option-definitions.js";
|
|
30
28
|
import { errorText, isRecord } from "../unknown-value.js";
|
|
29
|
+
/** Normalize admitted attachment records (caller path only; #1165). */
|
|
30
|
+
export function normalizeAdmittedAttachment(raw) {
|
|
31
|
+
if (typeof raw !== "object" || raw === null)
|
|
32
|
+
return undefined;
|
|
33
|
+
const record = raw;
|
|
34
|
+
const path = record.path;
|
|
35
|
+
// Opaque caller path — keep "" / whitespace; only a non-string is absent.
|
|
36
|
+
if (typeof path === "string")
|
|
37
|
+
return { path };
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
function admitCallerAttachments(attachmentPaths) {
|
|
41
|
+
return attachmentPaths.map((path) => ({ path }));
|
|
42
|
+
}
|
|
31
43
|
/** Issue a principal from the single authoritative role-run placement. */
|
|
32
44
|
export function issueAdmissionPlacement(authority, request) {
|
|
33
45
|
const ledgerHome = resolveActivationLedgerHome(request.home);
|
|
@@ -258,15 +270,10 @@ export async function relocateAdmittedRunToTicket(admitted, authority, heldLease
|
|
|
258
270
|
const admittedRecord = admitted;
|
|
259
271
|
rewriteRunDirectoryPathFields(admittedRecord, [
|
|
260
272
|
"runDirectory",
|
|
261
|
-
"taskPath",
|
|
262
|
-
"packetPath",
|
|
263
|
-
"prerequisitesPath",
|
|
264
|
-
"requestManifestPath",
|
|
265
273
|
"mergerInputPath",
|
|
266
274
|
], oldRunDirectory, target.runDirectory);
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
275
|
+
// #1165/#1168: caller file-flag paths stay as given. Obsolete copy fields
|
|
276
|
+
// (taskPath/packetPath) are not rewritten — stock volumes keep their bytes.
|
|
270
277
|
admitted.principal = principal;
|
|
271
278
|
// current.json projects host.original from this run directory. Ownership is
|
|
272
279
|
// already transferred; a refuse still carries the true cause to the caller
|
|
@@ -382,7 +389,8 @@ function assignPublicSeatOption(owner, taken, fields) {
|
|
|
382
389
|
const value = taken.value;
|
|
383
390
|
switch (taken.def.id) {
|
|
384
391
|
case "attach":
|
|
385
|
-
|
|
392
|
+
// #1165: file-flag values are opaque; only a missing argument is a usage error.
|
|
393
|
+
fields.attachmentPaths.push(requireProvidedOptionValue(taken.def.canonical, value));
|
|
386
394
|
return;
|
|
387
395
|
case "project":
|
|
388
396
|
fields.project = requireOptionPath(taken.def.canonical, value);
|
|
@@ -417,7 +425,8 @@ function assignPublicSeatOption(owner, taken, fields) {
|
|
|
417
425
|
fields.authorityRefs.push(requireAuthorityRef(value));
|
|
418
426
|
return;
|
|
419
427
|
case "prerequisites":
|
|
420
|
-
|
|
428
|
+
// #1168: same opaque file-flag rule as --attach.
|
|
429
|
+
fields.prerequisitesPath = requireProvidedOptionValue(taken.def.canonical, value);
|
|
421
430
|
return;
|
|
422
431
|
case "pr":
|
|
423
432
|
fields.prNumber = parsePositivePrOption(value);
|
|
@@ -426,7 +435,8 @@ function assignPublicSeatOption(owner, taken, fields) {
|
|
|
426
435
|
fields.repo = parseRepoOption(value);
|
|
427
436
|
return;
|
|
428
437
|
case "request-manifest":
|
|
429
|
-
|
|
438
|
+
// #1165: file-flag values are opaque; only a missing argument is a usage error.
|
|
439
|
+
fields.requestManifestPath = requireProvidedOptionValue(taken.def.canonical, value);
|
|
430
440
|
return;
|
|
431
441
|
case "issue": {
|
|
432
442
|
if (value === undefined || value.trim() === "") {
|
|
@@ -539,6 +549,16 @@ function requireOptionPath(flag, value) {
|
|
|
539
549
|
}
|
|
540
550
|
return value;
|
|
541
551
|
}
|
|
552
|
+
/**
|
|
553
|
+
* #1165 file flags (--attach / --request-manifest): keep the provided string
|
|
554
|
+
* opaque (including spaces and ""). Only a missing argument is a usage error.
|
|
555
|
+
*/
|
|
556
|
+
function requireProvidedOptionValue(flag, value) {
|
|
557
|
+
if (value === undefined) {
|
|
558
|
+
throw new CliUsageError(`${flag} requires a path`);
|
|
559
|
+
}
|
|
560
|
+
return value;
|
|
561
|
+
}
|
|
542
562
|
/**
|
|
543
563
|
* Shared Skill-arg token rule for caller-controlled values projected into the
|
|
544
564
|
* space-joined Skill invocation line. Rejects blank, whitespace (smuggles the
|
|
@@ -624,123 +644,6 @@ export function requireAuthorityRef(value) {
|
|
|
624
644
|
optionLike: "--authority-ref requires a durable reference, not inline Spec prose",
|
|
625
645
|
});
|
|
626
646
|
}
|
|
627
|
-
async function readRegularFileAttachment(sourcePath) {
|
|
628
|
-
const absolute = isAbsolute(sourcePath) ? sourcePath : resolve(sourcePath);
|
|
629
|
-
let st;
|
|
630
|
-
try {
|
|
631
|
-
st = await lstat(absolute);
|
|
632
|
-
}
|
|
633
|
-
catch (error) {
|
|
634
|
-
throw new CliUsageError(`attachment is not a readable regular file: ${sourcePath}`, { cause: error });
|
|
635
|
-
}
|
|
636
|
-
if (!st.isFile() || st.isSymbolicLink()) {
|
|
637
|
-
throw new CliUsageError(`attachment must be a regular file (not a directory or symlink): ${sourcePath}`);
|
|
638
|
-
}
|
|
639
|
-
try {
|
|
640
|
-
return { absolute, bytes: await readFile(absolute) };
|
|
641
|
-
}
|
|
642
|
-
catch (error) {
|
|
643
|
-
throw new CliUsageError(`attachment is not a readable regular file: ${sourcePath}`, { cause: error });
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
/**
|
|
647
|
-
* Snapshot deferred inputs sequentially before identity side effects. The staging
|
|
648
|
-
* files keep aggregate attachment bytes off heap until their final run is known.
|
|
649
|
-
*/
|
|
650
|
-
async function removePreparedAttachmentDirectory(stagingDirectory) {
|
|
651
|
-
await rm(stagingDirectory, { recursive: true, force: true });
|
|
652
|
-
}
|
|
653
|
-
/** Own the complete deferred-snapshot lifetime without masking either failure. */
|
|
654
|
-
export async function withPreparedAttachments(attachmentPaths, use) {
|
|
655
|
-
if (attachmentPaths.length === 0)
|
|
656
|
-
return await use([]);
|
|
657
|
-
const stagingDirectory = await mkdtemp(join(tmpdir(), "ak-role-attachments-"));
|
|
658
|
-
const prepared = [];
|
|
659
|
-
let result;
|
|
660
|
-
try {
|
|
661
|
-
for (let index = 0; index < attachmentPaths.length; index += 1) {
|
|
662
|
-
const { absolute, bytes } = await readRegularFileAttachment(attachmentPaths[index]);
|
|
663
|
-
const snapshotPath = join(stagingDirectory, String(index).padStart(6, "0"));
|
|
664
|
-
await writeFile(snapshotPath, bytes);
|
|
665
|
-
prepared.push({ absolute, snapshotPath });
|
|
666
|
-
}
|
|
667
|
-
result = await use(prepared);
|
|
668
|
-
}
|
|
669
|
-
catch (primary) {
|
|
670
|
-
try {
|
|
671
|
-
await removePreparedAttachmentDirectory(stagingDirectory);
|
|
672
|
-
}
|
|
673
|
-
catch (cleanup) {
|
|
674
|
-
throw new AggregateError([primary, cleanup], "attachment snapshot operation failed and cleanup also failed", { cause: primary });
|
|
675
|
-
}
|
|
676
|
-
throw primary;
|
|
677
|
-
}
|
|
678
|
-
await removePreparedAttachmentDirectory(stagingDirectory);
|
|
679
|
-
return result;
|
|
680
|
-
}
|
|
681
|
-
async function freezeAttachmentBytes(provenancePath, bytes, destinationDir, index) {
|
|
682
|
-
const frozenPath = join(destinationDir, `${String(index).padStart(2, "0")}-${basename(provenancePath)}`);
|
|
683
|
-
await writeFile(frozenPath, bytes);
|
|
684
|
-
return {
|
|
685
|
-
provenancePath,
|
|
686
|
-
frozenPath,
|
|
687
|
-
byteLength: bytes.byteLength,
|
|
688
|
-
sha256: sha256Hex(bytes),
|
|
689
|
-
mediaKind: "regular-file",
|
|
690
|
-
};
|
|
691
|
-
}
|
|
692
|
-
async function freezePreparedAttachment(prepared, destinationDir, index) {
|
|
693
|
-
return freezeAttachmentBytes(prepared.absolute, await readFile(prepared.snapshotPath), destinationDir, index);
|
|
694
|
-
}
|
|
695
|
-
async function freezeRegularFileAttachment(sourcePath, destinationDir, index) {
|
|
696
|
-
const { absolute, bytes } = await readRegularFileAttachment(sourcePath);
|
|
697
|
-
return {
|
|
698
|
-
attachment: await freezeAttachmentBytes(absolute, bytes, destinationDir, index),
|
|
699
|
-
body: bytes,
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
/** Freeze attachments only — ticket binding is the shared LLM seat path (#635). */
|
|
703
|
-
async function freezeAttachments(ledgerHome, attachmentPaths, attachmentsDirectory) {
|
|
704
|
-
const attachments = [];
|
|
705
|
-
// attachments/ appears only when something is frozen; created through the
|
|
706
|
-
// ledger's symlink-rejecting tree walk, never a plain mkdir.
|
|
707
|
-
if (attachmentPaths.length > 0) {
|
|
708
|
-
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
709
|
-
}
|
|
710
|
-
for (let i = 0; i < attachmentPaths.length; i += 1) {
|
|
711
|
-
const frozen = await freezeRegularFileAttachment(attachmentPaths[i], attachmentsDirectory, i);
|
|
712
|
-
attachments.push(frozen.attachment);
|
|
713
|
-
}
|
|
714
|
-
return attachments;
|
|
715
|
-
}
|
|
716
|
-
/**
|
|
717
|
-
* Freeze summons attachments into an already-retained run (#637 same-ticket resume).
|
|
718
|
-
* Writes under attachments/summons-<key>/ so prior freeze names stay intact.
|
|
719
|
-
* Manual resume never calls this — birth attachments keep their original semantics.
|
|
720
|
-
*/
|
|
721
|
-
export async function freezeAttachmentsIntoRun(attachmentPaths, runDirectory, summonsKey = `s-${Date.now().toString(36)}`) {
|
|
722
|
-
if (attachmentPaths.length === 0)
|
|
723
|
-
return [];
|
|
724
|
-
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(runDirectory));
|
|
725
|
-
const attachmentsDirectory = join(runDirectory, "attachments", summonsKey);
|
|
726
|
-
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
727
|
-
return freezeAttachments(ledgerHome, attachmentPaths, attachmentsDirectory);
|
|
728
|
-
}
|
|
729
|
-
/** Freeze prepared bytes and metadata through one path for birth or retained runs. */
|
|
730
|
-
export async function freezePreparedAttachmentsIntoRun(prepared, runDirectory, summonsKey) {
|
|
731
|
-
if (prepared.length === 0)
|
|
732
|
-
return [];
|
|
733
|
-
const ledgerHome = resolveActivationLedgerHome(homeFromRunDirectory(runDirectory));
|
|
734
|
-
const attachmentsDirectory = summonsKey === undefined
|
|
735
|
-
? join(runDirectory, "attachments")
|
|
736
|
-
: join(runDirectory, "attachments", summonsKey);
|
|
737
|
-
ensureRealDirectoryTree(ledgerHome, attachmentsDirectory);
|
|
738
|
-
const attachments = [];
|
|
739
|
-
for (let index = 0; index < prepared.length; index += 1) {
|
|
740
|
-
attachments.push(await freezePreparedAttachment(prepared[index], attachmentsDirectory, index));
|
|
741
|
-
}
|
|
742
|
-
return attachments;
|
|
743
|
-
}
|
|
744
647
|
function ticketAdmissionFields(ticketNumber) {
|
|
745
648
|
if (ticketNumber === undefined)
|
|
746
649
|
return {};
|
|
@@ -811,16 +714,13 @@ export async function admitPublicRole(role, parsed, env, override) {
|
|
|
811
714
|
if (!record.phases.some((item) => item === phase)) {
|
|
812
715
|
throw new CliUsageError("coder phase must be plan or apply");
|
|
813
716
|
}
|
|
717
|
+
// #1168: dispatch text is the first message only — no task.md copy.
|
|
814
718
|
return admitStandardMaterialInvocation("coder", {
|
|
815
719
|
...shared,
|
|
816
720
|
instruction,
|
|
817
721
|
attachmentPaths,
|
|
818
722
|
...project,
|
|
819
|
-
placedFields: async (
|
|
820
|
-
const taskPath = join(placed.runDirectory, "task.md");
|
|
821
|
-
await writeFile(taskPath, instruction, "utf8");
|
|
822
|
-
return { phase, taskPath };
|
|
823
|
-
},
|
|
723
|
+
placedFields: async () => ({ phase }),
|
|
824
724
|
});
|
|
825
725
|
}
|
|
826
726
|
case "worker-packet": {
|
|
@@ -831,48 +731,18 @@ export async function admitPublicRole(role, parsed, env, override) {
|
|
|
831
731
|
if (!record.phases.some((item) => item === phase)) {
|
|
832
732
|
throw new CliUsageError("fixer phase must be plan or apply");
|
|
833
733
|
}
|
|
834
|
-
|
|
835
|
-
let prerequisitesSource;
|
|
836
|
-
if (parsed.prerequisitesPath !== undefined) {
|
|
837
|
-
const absolutePrereq = isAbsolute(parsed.prerequisitesPath)
|
|
838
|
-
? parsed.prerequisitesPath
|
|
839
|
-
: resolve(parsed.prerequisitesPath);
|
|
840
|
-
try {
|
|
841
|
-
prerequisitesSource = await readFile(absolutePrereq, "utf8");
|
|
842
|
-
}
|
|
843
|
-
catch (error) {
|
|
844
|
-
throw new CliUsageError(`fixer prerequisites path is unreadable: ${parsed.prerequisitesPath}`, { cause: error });
|
|
845
|
-
}
|
|
846
|
-
try {
|
|
847
|
-
prerequisites = parseFixerPrerequisites(prerequisitesSource);
|
|
848
|
-
}
|
|
849
|
-
catch (error) {
|
|
850
|
-
if (error instanceof FixerPacketValidationError) {
|
|
851
|
-
throw new CliUsageError(error.message, { cause: error });
|
|
852
|
-
}
|
|
853
|
-
throw error;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
734
|
+
// #1168: dispatch once as first message; --prerequisites path as-is.
|
|
856
735
|
return admitStandardMaterialInvocation("fixer", {
|
|
857
736
|
...shared,
|
|
858
737
|
instruction,
|
|
859
738
|
attachmentPaths,
|
|
860
739
|
...project,
|
|
861
|
-
placedFields: async (
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
const packetPath = join(placed.runDirectory, "fix-packet.md");
|
|
868
|
-
await writeFile(packetPath, instruction, "utf8");
|
|
869
|
-
return {
|
|
870
|
-
phase,
|
|
871
|
-
packetPath,
|
|
872
|
-
prerequisites,
|
|
873
|
-
...(prerequisitesPath === undefined ? {} : { prerequisitesPath }),
|
|
874
|
-
};
|
|
875
|
-
},
|
|
740
|
+
placedFields: async () => ({
|
|
741
|
+
phase,
|
|
742
|
+
...(parsed.prerequisitesPath === undefined
|
|
743
|
+
? {}
|
|
744
|
+
: { prerequisitesPath: parsed.prerequisitesPath }),
|
|
745
|
+
}),
|
|
876
746
|
});
|
|
877
747
|
}
|
|
878
748
|
case "collect-target": {
|
|
@@ -891,36 +761,21 @@ export async function admitPublicRole(role, parsed, env, override) {
|
|
|
891
761
|
else {
|
|
892
762
|
repository = resolveGitHubRemoteRepository(projectRoot);
|
|
893
763
|
}
|
|
894
|
-
|
|
895
|
-
let manifestCanonicalJson;
|
|
896
|
-
if (parsed.requestManifestPath !== undefined) {
|
|
897
|
-
try {
|
|
898
|
-
manifest = await loadCollectorManifest(parsed.requestManifestPath);
|
|
899
|
-
manifestCanonicalJson = manifest.canonicalJson;
|
|
900
|
-
}
|
|
901
|
-
catch (error) {
|
|
902
|
-
throw new CliUsageError(errorText(error), { cause: error });
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
const manifestDigest = manifest.digest;
|
|
764
|
+
// #1165: pass caller path as-is; package does not read, validate, or rewrite.
|
|
906
765
|
const admittedCollector = await admitStandardMaterialInvocation("collector", {
|
|
907
766
|
...shared,
|
|
908
767
|
instruction,
|
|
909
768
|
attachmentPaths,
|
|
910
769
|
...project,
|
|
911
|
-
placedFields: async (
|
|
770
|
+
placedFields: async () => {
|
|
912
771
|
// #1088: explicit --pr only. Unbound PR is located by the LLM via host CLI.
|
|
913
|
-
let requestManifestPath;
|
|
914
|
-
if (manifestCanonicalJson !== undefined) {
|
|
915
|
-
requestManifestPath = join(placed.runDirectory, "request-manifest.json");
|
|
916
|
-
await writeFile(requestManifestPath, manifestCanonicalJson, "utf8");
|
|
917
|
-
}
|
|
918
772
|
return {
|
|
919
773
|
...(explicitPrNumber === undefined ? {} : { prNumber: explicitPrNumber }),
|
|
920
774
|
repository: repository.canonical,
|
|
921
775
|
repositoryDisplay: repository.display,
|
|
922
|
-
...(requestManifestPath === undefined
|
|
923
|
-
|
|
776
|
+
...(parsed.requestManifestPath === undefined
|
|
777
|
+
? {}
|
|
778
|
+
: { requestManifestPath: parsed.requestManifestPath }),
|
|
924
779
|
};
|
|
925
780
|
},
|
|
926
781
|
});
|
|
@@ -931,12 +786,10 @@ export async function admitPublicRole(role, parsed, env, override) {
|
|
|
931
786
|
throw new CliUsageError(`doctor --issue must be a positive integer, got ${Number.NaN}`);
|
|
932
787
|
}
|
|
933
788
|
const issueNumber = parsed.issueNumber;
|
|
934
|
-
|
|
935
|
-
const admittedDoctor = await admitStandardMaterialInvocation("doctor", {
|
|
789
|
+
return admitStandardMaterialInvocation("doctor", {
|
|
936
790
|
...shared,
|
|
937
791
|
instruction,
|
|
938
|
-
attachmentPaths
|
|
939
|
-
freezeAttachments: false,
|
|
792
|
+
attachmentPaths,
|
|
940
793
|
...project,
|
|
941
794
|
placedFields: async (placed) => {
|
|
942
795
|
let caseRunsPath;
|
|
@@ -973,16 +826,13 @@ export async function admitPublicRole(role, parsed, env, override) {
|
|
|
973
826
|
const detail = errorText(error);
|
|
974
827
|
throw new CliUsageError(`doctor case could not be constructed from retained evidence: ${detail}`, { cause: error });
|
|
975
828
|
}
|
|
976
|
-
frozenAttachments = await freezeAttachments(placed.ledgerHome, attachmentPaths, placed.attachmentsDirectory);
|
|
977
829
|
return {
|
|
978
830
|
issueNumber,
|
|
979
831
|
caseRunsPath,
|
|
980
832
|
caseIdentity,
|
|
981
|
-
attachments: persistedAttachmentRefs(frozenAttachments),
|
|
982
833
|
};
|
|
983
834
|
},
|
|
984
835
|
});
|
|
985
|
-
return { ...admittedDoctor, attachments: frozenAttachments };
|
|
986
836
|
}
|
|
987
837
|
case "source-locator": {
|
|
988
838
|
const projectRoot = resolve(parsed.project ?? shared.cwd);
|
|
@@ -1013,7 +863,7 @@ export async function admitPublicRole(role, parsed, env, override) {
|
|
|
1013
863
|
instruction: "",
|
|
1014
864
|
attachmentPaths: [],
|
|
1015
865
|
...project,
|
|
1016
|
-
|
|
866
|
+
recordAttachments: false,
|
|
1017
867
|
admittedFields: {
|
|
1018
868
|
sourceRunPath: sourceRun.runDirectory,
|
|
1019
869
|
sourceRun,
|
|
@@ -1030,7 +880,7 @@ export async function admitPublicRole(role, parsed, env, override) {
|
|
|
1030
880
|
instruction,
|
|
1031
881
|
attachmentPaths: [],
|
|
1032
882
|
...project,
|
|
1033
|
-
|
|
883
|
+
recordAttachments: false,
|
|
1034
884
|
admittedFields: { baseRevision },
|
|
1035
885
|
});
|
|
1036
886
|
}
|
|
@@ -1075,13 +925,12 @@ export async function admitPublicRole(role, parsed, env, override) {
|
|
|
1075
925
|
placedFields: async (placed) => {
|
|
1076
926
|
const targetLabel = derived.targetObjectId === "" ? "(none observed)" : derived.targetObjectId;
|
|
1077
927
|
const sourceLabel = derived.sourceObjectId === "" ? "(none observed)" : derived.sourceObjectId;
|
|
928
|
+
// #1168: git facts stay; do not encode dispatch text as task/authority copies.
|
|
1078
929
|
const mergerInput = validateMergerInput({
|
|
1079
930
|
attemptId: placed.runId,
|
|
1080
931
|
targetObjectId: derived.targetObjectId,
|
|
1081
932
|
sourceObjectId: derived.sourceObjectId,
|
|
1082
933
|
materials: {
|
|
1083
|
-
task: mergerMaterialFromUtf8(instruction),
|
|
1084
|
-
authority: mergerMaterialFromUtf8(instruction),
|
|
1085
934
|
targetIntent: mergerMaterialFromUtf8(`Investigate primary sources for target parent ${targetLabel}. Do not invent intent.`),
|
|
1086
935
|
sourceIntent: mergerMaterialFromUtf8(`Investigate primary sources for source parent ${sourceLabel}. Do not invent intent.`),
|
|
1087
936
|
},
|
|
@@ -1128,9 +977,9 @@ async function placeRoleAdmission(options) {
|
|
|
1128
977
|
home: options.home,
|
|
1129
978
|
...(options.materialize === undefined ? {} : { materialize: options.materialize }),
|
|
1130
979
|
});
|
|
1131
|
-
const attachments = options.
|
|
980
|
+
const attachments = options.recordAttachments === false
|
|
1132
981
|
? []
|
|
1133
|
-
:
|
|
982
|
+
: admitCallerAttachments(options.attachmentPaths);
|
|
1134
983
|
return {
|
|
1135
984
|
runId,
|
|
1136
985
|
bookKey,
|
|
@@ -1146,13 +995,7 @@ async function placeRoleAdmission(options) {
|
|
|
1146
995
|
};
|
|
1147
996
|
}
|
|
1148
997
|
function persistedAttachmentRefs(attachments) {
|
|
1149
|
-
return attachments.map((attachment) => ({
|
|
1150
|
-
provenancePath: attachment.provenancePath,
|
|
1151
|
-
frozenPath: attachment.frozenPath,
|
|
1152
|
-
byteLength: attachment.byteLength,
|
|
1153
|
-
sha256: attachment.sha256,
|
|
1154
|
-
mediaKind: attachment.mediaKind,
|
|
1155
|
-
}));
|
|
998
|
+
return attachments.map((attachment) => ({ path: attachment.path }));
|
|
1156
999
|
}
|
|
1157
1000
|
async function persistPlacedAdmission(admitted, placed, model) {
|
|
1158
1001
|
writeAdmittedRequestPersistence(placed.runDirectory, admitted, {
|
|
@@ -1172,19 +1015,18 @@ async function persistPlacedAdmission(admitted, placed, model) {
|
|
|
1172
1015
|
* before materializeCountersignInvocation writes the page.
|
|
1173
1016
|
* Seats whose extra facts are known before placement pass them as admittedFields.
|
|
1174
1017
|
* Seats whose extra facts depend on the placed run pass placedFields
|
|
1175
|
-
* (
|
|
1176
|
-
* doctor resolves the case, then freezes attachments; merger writes merger-input.json).
|
|
1018
|
+
* (merger writes merger-input.json with git facts; coder/fixer dispatch is first-message only, #1168).
|
|
1177
1019
|
*/
|
|
1178
1020
|
async function admitStandardMaterialInvocation(role, options) {
|
|
1179
1021
|
const defer = options.deferPersistence === true;
|
|
1180
|
-
const
|
|
1022
|
+
const skipAttachments = defer || options.recordAttachments === false;
|
|
1181
1023
|
const placed = await placeRoleAdmission({
|
|
1182
1024
|
role,
|
|
1183
1025
|
home: options.home,
|
|
1184
1026
|
principalAuthority: options.principalAuthority,
|
|
1185
1027
|
cwd: options.cwd,
|
|
1186
1028
|
attachmentPaths: options.attachmentPaths,
|
|
1187
|
-
...(
|
|
1029
|
+
...(skipAttachments ? { recordAttachments: false } : {}),
|
|
1188
1030
|
...(defer ? { materialize: false } : {}),
|
|
1189
1031
|
...(options.project === undefined ? {} : { project: options.project }),
|
|
1190
1032
|
...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
|
|
@@ -1196,7 +1038,8 @@ async function admitStandardMaterialInvocation(role, options) {
|
|
|
1196
1038
|
? {}
|
|
1197
1039
|
: { correlationId: options.correlationId };
|
|
1198
1040
|
const instruction = options.instruction;
|
|
1199
|
-
|
|
1041
|
+
// Admitted emptiness is literal absence, not whitespace-only (#1165 J3).
|
|
1042
|
+
const instructionEmpty = instruction.length === 0;
|
|
1200
1043
|
const placedExtra = options.placedFields === undefined ? undefined : await options.placedFields(placed);
|
|
1201
1044
|
const admittedFields = {
|
|
1202
1045
|
...(options.admittedFields ?? {}),
|
|
@@ -1244,7 +1087,7 @@ function admittedTransportPromptKind(admitted) {
|
|
|
1244
1087
|
/**
|
|
1245
1088
|
* One initial prompt transport. The registry `transportPrompt` leaf selects
|
|
1246
1089
|
* a fixed kickoff, a bound baseline, or frozen skill args. Absent means the
|
|
1247
|
-
* caller instruction plus
|
|
1090
|
+
* caller instruction plus caller attachment paths as-is (#1165).
|
|
1248
1091
|
* Engine / outsourcing material rides startup readingMaterial (#1167), not here.
|
|
1249
1092
|
*/
|
|
1250
1093
|
export function buildInstructionTransportPrompt(admitted) {
|
|
@@ -1260,7 +1103,9 @@ export function buildInstructionTransportPrompt(admitted) {
|
|
|
1260
1103
|
throw new Error("baseline transport prompt is missing the bound revision");
|
|
1261
1104
|
}
|
|
1262
1105
|
const lines = [admitted.baseRevision];
|
|
1263
|
-
|
|
1106
|
+
// Use raw admitted.instruction — do not gate on instructionEmpty metadata
|
|
1107
|
+
// (old pages may set that flag wrong while still holding whitespace text).
|
|
1108
|
+
if (admitted.instruction.length > 0) {
|
|
1264
1109
|
lines.push("", admitted.instruction);
|
|
1265
1110
|
}
|
|
1266
1111
|
return lines.join("\n");
|
|
@@ -1276,17 +1121,28 @@ export function buildInstructionTransportPrompt(admitted) {
|
|
|
1276
1121
|
lens: admitted.lens,
|
|
1277
1122
|
authorityRefs: admitted.authorityRefs,
|
|
1278
1123
|
})];
|
|
1279
|
-
if (
|
|
1124
|
+
if (admitted.instruction.length > 0) {
|
|
1280
1125
|
lines.push("", admitted.instruction);
|
|
1281
1126
|
}
|
|
1282
1127
|
return lines.join("\n");
|
|
1283
1128
|
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1129
|
+
// #1165 J3 / #1168: assemble from the admitted instruction bytes themselves.
|
|
1130
|
+
const lines = [admitted.instruction];
|
|
1131
|
+
// #1164/#1165/#1168: each caller path keeps its file-flag provenance.
|
|
1132
|
+
const flaggedPaths = [
|
|
1133
|
+
...admitted.attachments.map((attachment) => `--attach ${attachment.path}`),
|
|
1134
|
+
...(admitted.requestManifestPath === undefined
|
|
1135
|
+
? []
|
|
1136
|
+
: [`--request-manifest ${admitted.requestManifestPath}`]),
|
|
1137
|
+
...(admitted.prerequisitesPath === undefined
|
|
1138
|
+
? []
|
|
1139
|
+
: [`--prerequisites ${admitted.prerequisitesPath}`]),
|
|
1140
|
+
];
|
|
1141
|
+
if (flaggedPaths.length > 0) {
|
|
1286
1142
|
lines.push("");
|
|
1287
|
-
lines.push("
|
|
1288
|
-
for (const
|
|
1289
|
-
lines.push(`- ${
|
|
1143
|
+
lines.push("已受理附件:");
|
|
1144
|
+
for (const entry of flaggedPaths) {
|
|
1145
|
+
lines.push(`- ${entry}`);
|
|
1290
1146
|
}
|
|
1291
1147
|
}
|
|
1292
1148
|
return lines.join("\n");
|
|
@@ -1298,8 +1154,7 @@ export async function materializeCountersignInvocation(admitted, options) {
|
|
|
1298
1154
|
home: options.home,
|
|
1299
1155
|
principalAuthority: options.principalAuthority,
|
|
1300
1156
|
cwd: admitted.projectRoot,
|
|
1301
|
-
attachmentPaths:
|
|
1302
|
-
freezeAttachments: false,
|
|
1157
|
+
attachmentPaths: options.attachmentPaths,
|
|
1303
1158
|
runId: admitted.runId,
|
|
1304
1159
|
...(options.ticketNumber === undefined ? {} : { assertedTicketNumber: options.ticketNumber }),
|
|
1305
1160
|
});
|
|
@@ -1310,8 +1165,7 @@ export async function materializeCountersignInvocation(admitted, options) {
|
|
|
1310
1165
|
if (ticketFields.ticketNumber !== undefined) {
|
|
1311
1166
|
admitted.ticketNumber = ticketFields.ticketNumber;
|
|
1312
1167
|
}
|
|
1313
|
-
|
|
1314
|
-
admitted.attachments = attachments;
|
|
1168
|
+
admitted.attachments = placement.attachments;
|
|
1315
1169
|
writeAdmittedRequestPersistence(admitted.runDirectory, admitted, {
|
|
1316
1170
|
sessionDirectory: placement.sessionDirectory,
|
|
1317
1171
|
sessionFile: placement.sessionFile,
|
|
@@ -1332,10 +1186,13 @@ export async function loadAdmittedJudgeRequest(runDirectory) {
|
|
|
1332
1186
|
return undefined;
|
|
1333
1187
|
if (!Array.isArray(record.attachments))
|
|
1334
1188
|
return undefined;
|
|
1189
|
+
const attachments = record.attachments
|
|
1190
|
+
.map((item) => normalizeAdmittedAttachment(item))
|
|
1191
|
+
.filter((item) => item !== undefined);
|
|
1335
1192
|
return {
|
|
1336
1193
|
instruction: record.instruction,
|
|
1337
1194
|
instructionEmpty: record.instructionEmpty,
|
|
1338
|
-
attachments
|
|
1195
|
+
attachments,
|
|
1339
1196
|
};
|
|
1340
1197
|
}
|
|
1341
1198
|
catch {
|
|
@@ -1597,7 +1454,7 @@ export function parseAnalystArgv(args) {
|
|
|
1597
1454
|
return;
|
|
1598
1455
|
}
|
|
1599
1456
|
if (taken.def.id === "attach") {
|
|
1600
|
-
pushValue("attach",
|
|
1457
|
+
pushValue("attach", requireProvidedOptionValue(taken.def.canonical, taken.value));
|
|
1601
1458
|
return;
|
|
1602
1459
|
}
|
|
1603
1460
|
if (taken.def.id === "group-a-label" || taken.def.id === "group-b-label") {
|