@tiangong-ai/cli 0.0.60 → 0.0.61

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.
Files changed (73) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +167 -26
  3. package/dist/research/orchestration.js +108 -18
  4. package/dist/research/orchestration.js.map +1 -1
  5. package/dist/research/workspace/acquisition-routes.d.ts +1 -1
  6. package/dist/research/workspace/acquisition-routes.js +2 -11
  7. package/dist/research/workspace/acquisition-routes.js.map +1 -1
  8. package/dist/research/workspace/artifact-read-audit.d.ts +16 -0
  9. package/dist/research/workspace/artifact-read-audit.js +176 -0
  10. package/dist/research/workspace/artifact-read-audit.js.map +1 -0
  11. package/dist/research/workspace/artifact-trace.d.ts +12 -0
  12. package/dist/research/workspace/artifact-trace.js +109 -0
  13. package/dist/research/workspace/artifact-trace.js.map +1 -0
  14. package/dist/research/workspace/artifact-view-mcp.d.ts +75 -0
  15. package/dist/research/workspace/artifact-view-mcp.js +214 -0
  16. package/dist/research/workspace/artifact-view-mcp.js.map +1 -0
  17. package/dist/research/workspace/artifact-views.d.ts +100 -0
  18. package/dist/research/workspace/artifact-views.js +395 -0
  19. package/dist/research/workspace/artifact-views.js.map +1 -0
  20. package/dist/research/workspace/audit-bundle.d.ts +4 -0
  21. package/dist/research/workspace/audit-bundle.js +107 -8
  22. package/dist/research/workspace/audit-bundle.js.map +1 -1
  23. package/dist/research/workspace/executor.d.ts +5 -1
  24. package/dist/research/workspace/executor.js +106 -22
  25. package/dist/research/workspace/executor.js.map +1 -1
  26. package/dist/research/workspace/native-run.d.ts +77 -0
  27. package/dist/research/workspace/native-run.js +637 -0
  28. package/dist/research/workspace/native-run.js.map +1 -0
  29. package/dist/research/workspace/preflight.d.ts +8 -2
  30. package/dist/research/workspace/preflight.js +17 -14
  31. package/dist/research/workspace/preflight.js.map +1 -1
  32. package/dist/research/workspace/project-mutations.d.ts +1 -1
  33. package/dist/research/workspace/project-mutations.js +5 -3
  34. package/dist/research/workspace/project-mutations.js.map +1 -1
  35. package/dist/research/workspace/projects.js +1 -0
  36. package/dist/research/workspace/projects.js.map +1 -1
  37. package/dist/research/workspace/request-provenance.d.ts +67 -0
  38. package/dist/research/workspace/request-provenance.js +115 -0
  39. package/dist/research/workspace/request-provenance.js.map +1 -0
  40. package/dist/research/workspace/review-executor.js +22 -11
  41. package/dist/research/workspace/review-executor.js.map +1 -1
  42. package/dist/research/workspace/runtime.d.ts +52 -0
  43. package/dist/research/workspace/runtime.js +221 -98
  44. package/dist/research/workspace/runtime.js.map +1 -1
  45. package/dist/research/workspace/sanitization.js +1 -1
  46. package/dist/research/workspace/sanitization.js.map +1 -1
  47. package/dist/research/workspace/schema-compatibility.d.ts +5 -0
  48. package/dist/research/workspace/schema-compatibility.js +55 -0
  49. package/dist/research/workspace/schema-compatibility.js.map +1 -0
  50. package/dist/research/workspace/scientific-design.js +8 -17
  51. package/dist/research/workspace/scientific-design.js.map +1 -1
  52. package/dist/research/workspace/scientific-fulfillment-audit.d.ts +3 -0
  53. package/dist/research/workspace/scientific-fulfillment-audit.js +137 -0
  54. package/dist/research/workspace/scientific-fulfillment-audit.js.map +1 -0
  55. package/dist/research/workspace/scientific-fulfillment.d.ts +121 -0
  56. package/dist/research/workspace/scientific-fulfillment.js +539 -0
  57. package/dist/research/workspace/scientific-fulfillment.js.map +1 -0
  58. package/dist/research/workspace/scientific-review-execution.js +82 -61
  59. package/dist/research/workspace/scientific-review-execution.js.map +1 -1
  60. package/dist/research/workspace/scientific-review.d.ts +5 -1
  61. package/dist/research/workspace/scientific-review.js +197 -52
  62. package/dist/research/workspace/scientific-review.js.map +1 -1
  63. package/dist/research/workspace/setup-catalog.js +2 -2
  64. package/dist/research/workspace/task-acceptance.d.ts +10 -2
  65. package/dist/research/workspace/task-acceptance.js +72 -8
  66. package/dist/research/workspace/task-acceptance.js.map +1 -1
  67. package/dist/research/workspace/task-audit.js +52 -2
  68. package/dist/research/workspace/task-audit.js.map +1 -1
  69. package/dist/research/workspace/task-contract.d.ts +4 -1
  70. package/dist/research/workspace/task-contract.js +26 -5
  71. package/dist/research/workspace/task-contract.js.map +1 -1
  72. package/dist/research/workspace/types.d.ts +6 -3
  73. package/package.json +1 -1
@@ -1,11 +1,13 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { cp, lstat, readFile, rm } from "node:fs/promises";
2
+ import { cp, lstat, readFile, realpath, rm } from "node:fs/promises";
3
3
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
4
4
  import { CliError } from "../../errors.js";
5
5
  import { isConsistentAnalysisRunMetadata } from "./analysis-run.js";
6
+ import { artifactPromptContext, artifactReadInstructions, openArtifactViews, persistArtifactReads, persistArtifactViewIndex, writeArtifactViewIndex, verifyPersistedArtifactViewIndex, } from "./artifact-views.js";
6
7
  import { taskContext } from "./task-contract.js";
8
+ import { nativeRunArtifactRecords } from "./native-run.js";
7
9
  import { compileTaskAcceptanceContext, inspectProjectTask, taskAcceptancePrompt, validateTaskReview, } from "./task-acceptance.js";
8
- import { assertProjectAuthority, projectAuthority, projectWithEffectiveAuthority, readProjectAuthorityIndex, } from "./project-authority.js";
10
+ import { assertProjectAuthority, projectAuthority, projectAuthorityIndex, projectWithEffectiveAuthority, readProjectAuthorityIndex, } from "./project-authority.js";
9
11
  import { dataPublicSchemas } from "../../data/schemas.js";
10
12
  import { loadCapabilityDeclarations, stageLockedCapabilities, verifyCapabilities, } from "./capabilities.js";
11
13
  import { startCapabilityBroker } from "./broker.js";
@@ -23,9 +25,9 @@ import { createReviewExecutor } from "./review-executor.js";
23
25
  import { requiredDiscoveryCapabilityIds } from "./external-skills.js";
24
26
  import { renderInputLineContext } from "./input-plan.js";
25
27
  import { freezeClaimEvidenceGraph, freezeInferenceSnapshot, loadCurrentInferenceSnapshot, } from "./inference.js";
26
- import { appendJournalEvent, readJournal, verifyJournal } from "./journal.js";
28
+ import { appendJournalEvent, readJournal, readVerifiedJournal, verifyJournal } from "./journal.js";
27
29
  import { nativeActivityRecordSchema } from "./native-activity.js";
28
- import { calculateAgentCallTokenReservation, RESEARCH_BROKER_MAX_TURNS, RESEARCH_ESTIMATED_BYTES_PER_TOKEN, RESEARCH_MAX_REPAIR_SOURCE_BYTES, RESEARCH_REPAIR_MAX_TURNS, researchStageContextTokenLimit, researchStructuredOutputMaxTurns, reservedAgentPackageCost, } from "./preflight.js";
30
+ import { calculateAgentCallTokenReservation, RESEARCH_BROKER_MAX_TURNS, RESEARCH_ESTIMATED_BYTES_PER_TOKEN, RESEARCH_MAX_REPAIR_SOURCE_BYTES, RESEARCH_REPAIR_MAX_TURNS, RESEARCH_PACKET_READ_MAX_TURNS, RESEARCH_EXPECTED_ARTIFACT_READ_TOKENS, researchStructuredOutputMaxTurns, reservedAgentPackageCost, } from "./preflight.js";
29
31
  import { blockingScientificGate, listProjects, loadProject, nextReadyPackage, packageById, refreshProject, saveProject, scientificGateRecommendedAction, } from "./projects.js";
30
32
  import { assertResearchPolicyBinding } from "./research-policy.js";
31
33
  import { assertScientificGateForStage } from "./scientific-review.js";
@@ -308,6 +310,63 @@ export async function inspectNativeResearchStage(root, project) {
308
310
  };
309
311
  }
310
312
  }
313
+ export async function readNativeStageArtifact(input) {
314
+ return withWorkspaceLock(input.root, "research.native-stage.read", async () => {
315
+ const project = await loadProject(input.root, input.projectId);
316
+ const events = await readVerifiedJournal(workspacePaths(input.root).journal);
317
+ assertProjectAuthority(project, projectAuthorityIndex(events));
318
+ const session = await readNativeStageSession(input.root, input.projectId);
319
+ const prepared = events.findLast((event) => event.scope === project.id &&
320
+ event.type === "native.stage.prepared" &&
321
+ event.payload.sessionId === input.sessionId);
322
+ const runtimeRoot = await realpath(workspacePaths(input.root).runtime);
323
+ const capsule = await realpath(session.capsuleProject);
324
+ if (session.packet.sessionId !== input.sessionId ||
325
+ prepared?.payload.packetSha256 !== session.packet.packetSha256 ||
326
+ project.handoff.state !== "agent-actionable" ||
327
+ relative(runtimeRoot, capsule).startsWith("..") ||
328
+ isAbsolute(relative(runtimeRoot, capsule)) ||
329
+ Date.now() - Date.parse(session.packet.preparedAt) >
330
+ session.packet.limits.maxWallSeconds * 1000 ||
331
+ packageById(project, session.packet.packageId).status !== "running" ||
332
+ !session.packet.artifactViews) {
333
+ throw new CliError("Artifact reads require the exact active native stage packet.", {
334
+ code: "RESEARCH_NATIVE_STAGE_SESSION_REQUIRED",
335
+ exitCode: 3,
336
+ });
337
+ }
338
+ await assertNativeStageBinding(input.root, project, session.packet);
339
+ const views = await openArtifactViews(session.capsuleProject, session.packet.artifactViews, session.packet.packetSha256);
340
+ if (views.index.projectId !== project.id)
341
+ throw new CliError("Artifact directory belongs to another project.", {
342
+ code: "RESEARCH_ARTIFACT_VIEW_INVALID",
343
+ exitCode: 3,
344
+ });
345
+ if (!input.selection)
346
+ return views.list(input.listing);
347
+ const result = await views.read(input.selection);
348
+ await persistArtifactReads(projectRoot(input.root, project.id), session.capsuleProject, session.packet.artifactViews, session.packet.packetSha256, [result.receipt]);
349
+ await appendJournalEvent(workspacePaths(input.root).journal, "native.artifact.read", project.id, sanitizeResearchRecord({ ...result.receipt }));
350
+ return result;
351
+ });
352
+ }
353
+ /** Called under an existing short lease; observing a calculation never opens a producer. */
354
+ export async function inspectNativeCalculationScope(root, project, sessionId) {
355
+ const session = await readNativeStageSession(root, project.id);
356
+ const events = await readVerifiedJournal(workspacePaths(root).journal);
357
+ const prepared = events.findLast((event) => event.scope === project.id &&
358
+ event.type === "native.stage.prepared" &&
359
+ event.payload.sessionId === sessionId);
360
+ if (session.packet.sessionId !== sessionId ||
361
+ !["analyze", "synthesize"].includes(session.packet.stage) ||
362
+ prepared?.payload.packetSha256 !== session.packet.packetSha256 ||
363
+ packageById(project, session.packet.packageId).status !== "running" ||
364
+ Date.now() - Date.parse(session.packet.preparedAt) > session.packet.limits.maxWallSeconds * 1000) {
365
+ throw new CliError("The calculation must bind the exact active post-acquisition native packet.", { code: "RESEARCH_NATIVE_RUN_BINDING_INVALID", exitCode: 3 });
366
+ }
367
+ await assertNativeStageBinding(root, project, session.packet);
368
+ return session.packet.packetSha256;
369
+ }
311
370
  export async function prepareNativeResearchStage(input) {
312
371
  return withWorkspaceLock(input.root, "research.native-stage.prepare", async () => {
313
372
  const authority = await readProjectAuthorityIndex(input.root);
@@ -396,27 +455,23 @@ export async function prepareNativeResearchStage(input) {
396
455
  let preparedStatePersisted = false;
397
456
  try {
398
457
  capsule = await createCapsule(input.root, project, workPackage, sessionId, config);
399
- const stageContextContent = await stageContextForPackage(capsule.projectRoot, project, workPackage, config);
458
+ const stageContextContent = await stageContextForPackage(capsule.projectRoot, project, workPackage, capsule.artifactViews, config);
400
459
  const declarations = await loadCapabilityDeclarations(input.root);
401
460
  const hasBrokeredEvidence = declarations.capabilities.some((capability) => capability.permissions.includes("brokered-network"));
402
461
  const dataCapabilities = projectResearchDataCapabilities();
403
462
  const hasDataEvidence = dataCapabilities.capabilities.length > 0;
404
- const basePrompt = packagePrompt(project, workPackage, capsule.inputManifest, capsule.stagedSkills, capsule.capabilityDocumentation, null, capsule.contextBundle, capsule.contextBundleContent, stageContextContent, discovery, await listEvidenceCandidates(input.root, project.id));
463
+ const basePrompt = packagePrompt(project, workPackage, capsule.inputManifest, capsule.stagedSkills, capsule.capabilityDocumentation, null, capsule.contextBundle, capsule.contextBundleContent, stageContextContent, discovery, await listEvidenceCandidates(input.root, project.id), "native-host");
405
464
  const taskContract = await taskContext(input.root, project.id, authority.taskEvents.get(project.id) ?? []);
406
465
  const taskPrompt = taskContract
407
466
  ? "Original task and current authorized scope (a workflow finish is not task completion):\n" +
408
- JSON.stringify(taskContract)
467
+ (await artifactPromptContext(capsule.projectRoot, capsule.artifactViews, [
468
+ "inputs/task-context.json",
469
+ ]))
409
470
  : "";
410
- if (taskContract &&
411
- Buffer.byteLength(taskPrompt +
412
- (input.stage === "discover" ? capsule.contextBundleContent : "") +
413
- stageContextContent, "utf8") >
414
- config.budget.maxInputContextTokens * RESEARCH_ESTIMATED_BYTES_PER_TOKEN) {
415
- throw new CliError("The complete task and evidence context exceeds the reviewed input allowance. Condense representation without dropping requirements or explicitly review a larger context budget.", { code: "RESEARCH_TASK_CONTEXT_TOO_LARGE", exitCode: 3 });
416
- }
417
471
  const prompt = [
418
472
  "Perform this producer stage in the current interactive host session. Do not launch codex exec, claude -p, or any other nested reasoning agent.",
419
473
  capsule.publicationPolicyDocumentation,
474
+ artifactReadInstructions(capsule.artifactViews),
420
475
  taskPrompt,
421
476
  input.stage === "discover" && (hasBrokeredEvidence || hasDataEvidence)
422
477
  ? [
@@ -434,7 +489,6 @@ export async function prepareNativeResearchStage(input) {
434
489
  ? "Acquire the provisionally admitted sources with the installed external acquisition/document Skills or an explicitly selected user-authorized browser. Capture the exact browser/adapter Download object, save it to the planned unique staging path, and call bindDownload before registerArtifact. Record browser/download/file-inspection activity with recordActivity. Failed or cancelled downloads cannot create bindings or artifacts. Never scan a download directory or infer success from file existence."
435
490
  : "Do not acquire additional evidence in this stage.",
436
491
  basePrompt,
437
- "Save only the final schema-conforming JSON object to a new regular file, then submit it with the packet's submit command. The CLI remains the sole authority for validation and atomic promotion.",
438
492
  ].join("\n\n");
439
493
  const preparedAt = new Date().toISOString();
440
494
  const bindingSha256 = await nativeStageBinding(input.root, project, workPackage);
@@ -455,6 +509,7 @@ export async function prepareNativeResearchStage(input) {
455
509
  : {}),
456
510
  publicationPolicy: capsule.publicationPolicy,
457
511
  taskContract,
512
+ artifactViews: capsule.artifactViews,
458
513
  discovery,
459
514
  limits: {
460
515
  maxOutputBytes: config.budget.maxBytesPerPackage,
@@ -465,6 +520,42 @@ export async function prepareNativeResearchStage(input) {
465
520
  maxWallSeconds: config.budget.packageMaxWallSeconds[input.stage],
466
521
  },
467
522
  commands: {
523
+ listArtifacts: {
524
+ argv: [
525
+ "tiangong-ai",
526
+ "research",
527
+ "project",
528
+ "stage",
529
+ "artifacts",
530
+ project.id,
531
+ "--session",
532
+ sessionId,
533
+ "--workspace",
534
+ input.root,
535
+ "--json",
536
+ ],
537
+ },
538
+ readArtifact: {
539
+ argv: [
540
+ "tiangong-ai",
541
+ "research",
542
+ "project",
543
+ "stage",
544
+ "read",
545
+ project.id,
546
+ "--session",
547
+ sessionId,
548
+ "--artifact",
549
+ "<objectId-from-directory>",
550
+ "--offset",
551
+ "<byte-offset>",
552
+ "--length",
553
+ "<bytes-or-all>",
554
+ "--workspace",
555
+ input.root,
556
+ "--json",
557
+ ],
558
+ },
468
559
  inspectAccess: project.scientificDesign
469
560
  ? {
470
561
  argv: [
@@ -764,6 +855,7 @@ export async function prepareNativeResearchStage(input) {
764
855
  await appendJournalEvent(workspacePaths(input.root).journal, "native.stage.prepared", project.id, {
765
856
  sessionId,
766
857
  packetSha256: packet.packetSha256,
858
+ artifactViewIndexSha256: packet.artifactViews.sha256,
767
859
  bindingSha256,
768
860
  projectId: project.id,
769
861
  packageId: workPackage.id,
@@ -1303,7 +1395,7 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
1303
1395
  packet: capsule.reviewPacketRecord,
1304
1396
  });
1305
1397
  }
1306
- const stageContextContent = await stageContextForPackage(capsule.projectRoot, project, workPackage, config);
1398
+ const stageContextContent = await stageContextForPackage(capsule.projectRoot, project, workPackage, capsule.artifactViews, config);
1307
1399
  const route = workPackage.executor === "reviewer" ? config.reviewer : config.producer;
1308
1400
  const selectedPackageExecutor = workPackage.executor === "reviewer" && reviewerPackageExecutor
1309
1401
  ? reviewerPackageExecutor
@@ -1325,11 +1417,14 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
1325
1417
  options,
1326
1418
  requestId,
1327
1419
  purpose: "primary",
1328
- prompt: packagePrompt(project, workPackage, capsule.inputManifest, capsule.stagedSkills, capsule.capabilityDocumentation, capsule.reviewPacketSha256, capsule.contextBundle, capsule.contextBundleContent, stageContextContent, discovery, await listEvidenceCandidates(root, project.id)) +
1420
+ prompt: packagePrompt(project, workPackage, capsule.inputManifest, capsule.stagedSkills, capsule.capabilityDocumentation, capsule.reviewPacketSha256, capsule.contextBundle, capsule.contextBundleContent, stageContextContent, discovery, await listEvidenceCandidates(root, project.id), "headless-cli") +
1329
1421
  (capsule.publicationPolicyDocumentation
1330
1422
  ? `\n\n${capsule.publicationPolicyDocumentation}`
1331
1423
  : "") +
1332
- (capsule.taskAcceptancePrompt ? `\n\n${capsule.taskAcceptancePrompt}` : ""),
1424
+ (capsule.taskAcceptancePrompt ? `\n\n${capsule.taskAcceptancePrompt}` : "") +
1425
+ (workPackage.stage === "review"
1426
+ ? `\n\n${artifactReadInstructions(capsule.artifactViews)}`
1427
+ : ""),
1333
1428
  brokerUrl: primaryBrokerUrl,
1334
1429
  inputOnlyProvenance,
1335
1430
  maxOutputTokens: Math.min(stageOutputTokens, reservation.tokens),
@@ -1340,6 +1435,15 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
1340
1435
  assertPreCallTokenReservation(project, workPackage, config, primaryRequest, 0, true);
1341
1436
  result = await withHeartbeat(selectedPackageExecutor(primaryRequest), options, requestId, project, workPackage, config);
1342
1437
  accountedResult = result;
1438
+ if (primaryRequest.artifactViews && result.artifactReads?.length) {
1439
+ await persistArtifactReads(projectRoot(root, project.id), capsule.projectRoot, capsule.artifactViews, primaryRequest.artifactViews.packetSha256, result.artifactReads);
1440
+ await appendJournalEvent(workspacePaths(root).journal, "review.artifacts.read", project.id, sanitizeResearchRecord({
1441
+ requestId,
1442
+ packetSha256: primaryRequest.artifactViews.packetSha256,
1443
+ indexSha256: capsule.artifactViews.sha256,
1444
+ receipts: result.artifactReads.map((receipt) => receipt.receiptSha256),
1445
+ }));
1446
+ }
1343
1447
  assertExecutorSucceeded(result);
1344
1448
  assertActualPackageBudget(project, workPackage, config, result, stageOutputTokens);
1345
1449
  let acceptedRaw = result.stdout;
@@ -1580,7 +1684,14 @@ async function stagePublicationPolicy(root, project, capsuleProject) {
1580
1684
  manifestPath: resolveContained(capsuleProject, manifestPath),
1581
1685
  documents,
1582
1686
  };
1583
- await writeJsonAtomic(staged.manifestPath, staged);
1687
+ await writeJsonAtomic(staged.manifestPath, {
1688
+ ...staged,
1689
+ manifestPath,
1690
+ documents: documents.map((document) => ({
1691
+ ...document,
1692
+ path: relative(capsuleProject, document.path).replaceAll("\\", "/"),
1693
+ })),
1694
+ });
1584
1695
  return staged;
1585
1696
  }
1586
1697
  async function createCapsule(root, project, workPackage, runId, config, taskAcceptance = null) {
@@ -1689,35 +1800,65 @@ async function createCapsule(root, project, workPackage, runId, config, taskAcce
1689
1800
  "Approved Research Policy (mandatory for every producer and reviewer decision):",
1690
1801
  `resolvedPolicySha256=${publicationPolicy.resolvedPolicySha256}`,
1691
1802
  `verdictCeiling=${publicationPolicy.verdictCeiling}`,
1692
- ...publicationPolicy.documents.map((document) => `- ${document.kind}:${document.id} sha256=${document.sha256} path=${document.path}`),
1803
+ ...publicationPolicy.documents.map((document) => `- ${document.kind}:${document.id} sha256=${document.sha256} path=${relative(capsuleProject, document.path).replaceAll("\\", "/")}`),
1693
1804
  "Read every listed policy document before reasoning. Generic defaults are explicit constraints, not evidence that a target journal will accept the manuscript.",
1694
1805
  ].join("\n")
1695
1806
  : "";
1696
1807
  const stagedSkills = await stageLockedCapabilities(root, join(capsuleProject, "skills"));
1697
- const capabilityDocumentation = await buildCapabilityDocumentation(capsuleProject, stagedSkills, config.budget.maxInputContextTokens * RESEARCH_ESTIMATED_BYTES_PER_TOKEN);
1808
+ const capabilityDocumentation = await buildCapabilityDocumentation(capsuleProject, stagedSkills);
1809
+ await writeTextAtomic(join(capsuleProject, "inputs/capability-documentation.txt"), capabilityDocumentation);
1698
1810
  const reviewEvidenceContext = workPackage.stage === "review"
1699
1811
  ? await writeReviewEvidenceContext(root, project.id, capsuleProject, contextBundleContent, evidenceReceipts, contextualEvidenceArtifacts, config.budget.maxInputContextTokens * RESEARCH_ESTIMATED_BYTES_PER_TOKEN)
1700
1812
  : null;
1813
+ const task = taskAcceptance ?? (await taskContext(root, project.id));
1814
+ if (task) {
1815
+ await writeJsonAtomic(join(capsuleProject, "inputs/task-context.json"), task);
1816
+ const source = task.requestProvenance.source;
1817
+ if (source) {
1818
+ const logical = `task/request-sources/${source.objectSha256}.json`;
1819
+ await ensureDirectory(dirname(resolveContained(capsuleProject, logical)));
1820
+ await cp(resolveContained(projectRoot(root, project.id), logical), resolveContained(capsuleProject, logical), { force: false });
1821
+ }
1822
+ }
1823
+ const taskArtifacts = new Map((taskAcceptance?.results ?? []).map((record) => [record.path, record]));
1824
+ if (task)
1825
+ for (const record of await nativeRunArtifactRecords(root, project.id))
1826
+ taskArtifacts.set(record.path, record);
1827
+ for (const result of taskArtifacts.values()) {
1828
+ await ensureDirectory(dirname(resolveContained(capsuleProject, result.path)));
1829
+ await cp(resolveContained(projectRoot(root, project.id), result.path), resolveContained(capsuleProject, result.path), { force: false });
1830
+ if ((await sha256File(resolveContained(capsuleProject, result.path))) !== result.sha256)
1831
+ throw new CliError("Native run/check artifact changed while staging the packet.", {
1832
+ code: "RESEARCH_TASK_ARTIFACT_DRIFT",
1833
+ exitCode: 3,
1834
+ });
1835
+ }
1701
1836
  const reviewPacket = reviewEvidenceContext
1702
1837
  ? await writeReviewPacket(root, capsuleProject, project, inputManifest, evidenceReceipts, evidenceArtifacts, reviewEvidenceContext.persistent, taskAcceptance)
1703
1838
  : null;
1839
+ const artifactViews = reviewPacket?.artifactViews ?? (await writeArtifactViewIndex(capsuleProject, project.id));
1704
1840
  return {
1705
1841
  capsuleRoot,
1706
1842
  projectRoot: capsuleProject,
1707
1843
  inputManifest,
1708
1844
  contextBundle,
1709
- contextBundleContent,
1845
+ contextBundleContent: await artifactPromptContext(capsuleProject, artifactViews, [
1846
+ "inputs/context-bundle.txt",
1847
+ ]),
1710
1848
  stagedSkills,
1711
- capabilityDocumentation,
1849
+ capabilityDocumentation: await artifactPromptContext(capsuleProject, artifactViews, [
1850
+ "inputs/capability-documentation.txt",
1851
+ ]),
1712
1852
  publicationPolicy,
1713
1853
  publicationPolicyDocumentation,
1714
1854
  reviewPacketSha256: reviewPacket?.sha256 ?? null,
1715
1855
  reviewPacketRecord: reviewPacket?.record ?? null,
1716
1856
  taskAcceptance,
1717
- taskAcceptancePrompt: await taskAcceptancePrompt(root, taskAcceptance),
1857
+ taskAcceptancePrompt: await taskAcceptancePrompt(taskAcceptance, capsuleProject, artifactViews),
1858
+ artifactViews,
1718
1859
  };
1719
1860
  }
1720
- async function buildCapabilityDocumentation(capsuleProject, stagedSkills, maxBytes) {
1861
+ async function buildCapabilityDocumentation(capsuleProject, stagedSkills) {
1721
1862
  const documents = [
1722
1863
  {
1723
1864
  path: "skills/manifest.json",
@@ -1733,14 +1874,6 @@ async function buildCapabilityDocumentation(capsuleProject, stagedSkills, maxByt
1733
1874
  const bundle = documents
1734
1875
  .map((document) => `--- ${document.path} ---\n${document.content.trimEnd()}`)
1735
1876
  .join("\n\n");
1736
- const bytes = Buffer.byteLength(bundle, "utf8");
1737
- if (bytes > maxBytes) {
1738
- throw new CliError("Staged external Skill documentation exceeds the input context budget.", {
1739
- code: "RESEARCH_CAPABILITY_CONTEXT_BUDGET_EXCEEDED",
1740
- exitCode: 3,
1741
- details: { bytes, maxBytes, skills: stagedSkills.map((path) => basename(path)) },
1742
- });
1743
- }
1744
1877
  return bundle;
1745
1878
  }
1746
1879
  async function buildInputContextBundle(capsuleProject, inputManifest) {
@@ -1806,6 +1939,8 @@ async function writeReviewPacket(root, capsuleProject, project, inputManifest, e
1806
1939
  }
1807
1940
  }
1808
1941
  await writeJsonAtomic(join(capsuleProject, "inputs", "runtime-fingerprint.json"), environment);
1942
+ const artifactViews = await writeArtifactViewIndex(capsuleProject, project.id);
1943
+ const persistedViews = await persistArtifactViewIndex(projectRoot(root, project.id), capsuleProject, artifactViews);
1809
1944
  const packet = {
1810
1945
  schemaVersion: 1,
1811
1946
  projectId: project.id,
@@ -1819,6 +1954,7 @@ async function writeReviewPacket(root, capsuleProject, project, inputManifest, e
1819
1954
  },
1820
1955
  snapshotChain,
1821
1956
  taskAcceptance,
1957
+ artifactViews: persistedViews,
1822
1958
  inputs: inputManifest,
1823
1959
  reviewEvidenceContext,
1824
1960
  inputFiles: [...inputFiles.values()].sort((left, right) => left.path.localeCompare(right.path)),
@@ -1838,7 +1974,7 @@ async function writeReviewPacket(root, capsuleProject, project, inputManifest, e
1838
1974
  };
1839
1975
  await writeJsonAtomic(join(capsuleProject, "inputs", "review-packet.json"), completePacket);
1840
1976
  const record = await persistReviewPacket(root, project.id, completePacket, packetSha256);
1841
- return { sha256: packetSha256, record };
1977
+ return { sha256: packetSha256, record, artifactViews };
1842
1978
  }
1843
1979
  async function writeReviewEvidenceContext(root, projectId, capsuleProject, inputContextBundle, evidenceReceipts, evidenceArtifacts, maxBytes) {
1844
1980
  const header = [
@@ -1920,15 +2056,10 @@ async function writeReviewEvidenceContext(root, projectId, capsuleProject, input
1920
2056
  ...views.map((view) => `${view.prefix}${view.active ? "" : view.content}${view.suffix}`),
1921
2057
  ].join("\n\n");
1922
2058
  const fixedBytes = Buffer.byteLength(`${fixedContent}\n`, "utf8");
1923
- if (fixedBytes > maxBytes) {
1924
- throw new CliError("Review evidence metadata exceeds the configured context budget.", {
1925
- code: "RESEARCH_REVIEW_CONTEXT_BUDGET_EXCEEDED",
1926
- exitCode: 3,
1927
- details: { fixedBytes, maxBytes, views: views.length },
1928
- });
1929
- }
1930
2059
  const activeViews = views.filter((view) => view.active).length;
1931
- const contentBudgetPerView = activeViews ? Math.floor((maxBytes - fixedBytes) / activeViews) : 0;
2060
+ const contentBudgetPerView = activeViews
2061
+ ? Math.max(0, Math.floor((maxBytes - fixedBytes) / activeViews))
2062
+ : 0;
1932
2063
  const sections = [
1933
2064
  header,
1934
2065
  ...views.map((view) => `${view.prefix}${view.active ? boundedUtf8ReviewExcerpt(view.content, contentBudgetPerView) : view.content}${view.suffix}`),
@@ -1936,14 +2067,6 @@ async function writeReviewEvidenceContext(root, projectId, capsuleProject, input
1936
2067
  const logicalPath = "inputs/review-evidence-context.txt";
1937
2068
  const path = resolveContained(capsuleProject, logicalPath);
1938
2069
  const content = `${sections.join("\n\n")}\n`;
1939
- const actualBytes = Buffer.byteLength(content, "utf8");
1940
- if (actualBytes > maxBytes) {
1941
- throw new CliError("Review evidence context exceeded its deterministic byte budget.", {
1942
- code: "RESEARCH_REVIEW_CONTEXT_BUDGET_EXCEEDED",
1943
- exitCode: 3,
1944
- details: { actualBytes, maxBytes },
1945
- });
1946
- }
1947
2070
  await writeTextAtomic(path, content);
1948
2071
  const capsule = await fileRecord(path, logicalPath);
1949
2072
  const persistentLogicalPath = `review/contexts/${capsule.sha256}.txt`;
@@ -1995,15 +2118,8 @@ async function writeProducerArtifactContext(capsuleProject, evidenceArtifacts, m
1995
2118
  .map((view) => `${view.prefix}${view.active ? "" : view.content}${view.suffix}`)
1996
2119
  .join("\n\n")}\n`;
1997
2120
  const fixedBytes = Buffer.byteLength(fixed, "utf8");
1998
- if (fixedBytes > maxBytes) {
1999
- throw new CliError("Producer artifact metadata exceeds the configured context budget.", {
2000
- code: "RESEARCH_INPUT_CONTEXT_BUDGET_EXCEEDED",
2001
- exitCode: 3,
2002
- details: { fixedBytes, maxBytes, artifacts: views.length },
2003
- });
2004
- }
2005
2121
  const activeViews = views.filter((view) => view.active).length;
2006
- const perView = activeViews ? Math.floor((maxBytes - fixedBytes) / activeViews) : 0;
2122
+ const perView = activeViews ? Math.max(0, Math.floor((maxBytes - fixedBytes) / activeViews)) : 0;
2007
2123
  const content = `${header}\n\n${views
2008
2124
  .map((view) => `${view.prefix}${view.active ? boundedUtf8ReviewExcerpt(view.content, perView) : view.content}${view.suffix}`)
2009
2125
  .join("\n\n")}\n`;
@@ -2164,6 +2280,13 @@ export async function loadVerifiedReviewPacket(root, projectId, packetSha256) {
2164
2280
  const path = resolveContained(projectRoot(root, projectId), logicalPath);
2165
2281
  const packet = await readJsonFile(path, "Research review packet");
2166
2282
  verifyReviewPacketValue(packet, packetSha256);
2283
+ if (packet.projectId !== projectId)
2284
+ throw new CliError("Review packet belongs to another project.", {
2285
+ code: "RESEARCH_REVIEW_PACKET_DRIFT",
2286
+ exitCode: 3,
2287
+ });
2288
+ if (packet.artifactViews !== undefined)
2289
+ await verifyPersistedArtifactViewIndex(projectRoot(root, projectId), projectId, packet.artifactViews);
2167
2290
  const context = packet.reviewEvidenceContext;
2168
2291
  if (!isObject(context) ||
2169
2292
  typeof context.path !== "string" ||
@@ -2315,12 +2438,17 @@ function reviewSafeReceipt(receipt) {
2315
2438
  };
2316
2439
  }
2317
2440
  function agentRequest(input) {
2318
- const toolPolicy = "none";
2441
+ const packetRead = input.purpose === "primary" &&
2442
+ input.workPackage.stage === "review" &&
2443
+ input.capsule.reviewPacketSha256 !== null;
2444
+ const toolPolicy = packetRead ? "packet-read" : "none";
2319
2445
  const maxTurns = input.purpose === "repair"
2320
2446
  ? RESEARCH_REPAIR_MAX_TURNS
2321
- : input.brokerUrl
2322
- ? RESEARCH_BROKER_MAX_TURNS
2323
- : researchStructuredOutputMaxTurns(input.route);
2447
+ : packetRead
2448
+ ? RESEARCH_PACKET_READ_MAX_TURNS
2449
+ : input.brokerUrl
2450
+ ? RESEARCH_BROKER_MAX_TURNS
2451
+ : researchStructuredOutputMaxTurns(input.route);
2324
2452
  return {
2325
2453
  route: input.route,
2326
2454
  prompt: input.prompt,
@@ -2345,11 +2473,22 @@ function agentRequest(input) {
2345
2473
  timeoutSeconds: Math.min(remainingWallSeconds(input.project, input.config), input.maxWallSeconds ??
2346
2474
  input.config.budget.packageMaxWallSeconds[input.workPackage.stage]),
2347
2475
  maxTurns,
2476
+ ...(packetRead
2477
+ ? {
2478
+ reservationTurns: researchStructuredOutputMaxTurns(input.route),
2479
+ artifactViews: {
2480
+ index: input.capsule.artifactViews,
2481
+ packetSha256: input.capsule.reviewPacketSha256,
2482
+ },
2483
+ }
2484
+ : {}),
2348
2485
  maxOutputTokens: input.maxOutputTokens,
2349
2486
  maxToolContextTokens: input.brokerUrl
2350
2487
  ? input.config.budget.maxBrokerContextTokens *
2351
2488
  (input.brokerCallBudget ?? input.config.budget.maxBrokerCalls)
2352
- : 0,
2489
+ : packetRead
2490
+ ? RESEARCH_EXPECTED_ARTIFACT_READ_TOKENS
2491
+ : 0,
2353
2492
  maxCostUsd: input.maxCostUsd,
2354
2493
  expectedRuntime: input.expectedRuntime,
2355
2494
  toolPolicy,
@@ -3066,7 +3205,7 @@ async function verifyProjectInputBindings(project) {
3066
3205
  async function outputRecords(root, project, logicalPaths) {
3067
3206
  return Promise.all(logicalPaths.map((logicalPath) => fileRecord(resolveContained(projectRoot(root, project.id), logicalPath), logicalPath)));
3068
3207
  }
3069
- async function stageContextForPackage(capsuleProject, project, workPackage, config) {
3208
+ async function stageContextForPackage(capsuleProject, project, workPackage, index, config) {
3070
3209
  let analyzeFallbackContext = [];
3071
3210
  if (workPackage.stage === "analyze") {
3072
3211
  const inference = JSON.parse(await readFile(resolveContained(capsuleProject, "outputs/inference-snapshot.json"), "utf8"));
@@ -3101,41 +3240,21 @@ async function stageContextForPackage(capsuleProject, project, workPackage, conf
3101
3240
  "outputs/report.md",
3102
3241
  ]
3103
3242
  : [];
3104
- const sections = [];
3105
- for (const logicalPath of logicalPaths) {
3106
- const content = await readFile(resolveContained(capsuleProject, logicalPath), "utf8");
3107
- sections.push(`### ${logicalPath}\n${content.trimEnd()}`);
3108
- }
3109
- const bundled = sections.join("\n\n");
3110
- const estimatedTokens = Math.ceil(Buffer.byteLength(bundled, "utf8") / RESEARCH_ESTIMATED_BYTES_PER_TOKEN);
3111
- const maxStageContextTokens = researchStageContextTokenLimit(config, workPackage.stage, project.lineage.kind === "addendum");
3112
- if (estimatedTokens > maxStageContextTokens) {
3113
- throw new CliError(`Admitted stage context exceeds the configured input context limit for ${workPackage.id}.`, {
3114
- code: "RESEARCH_INPUT_CONTEXT_BUDGET_EXCEEDED",
3115
- exitCode: 3,
3116
- details: {
3117
- packageId: workPackage.id,
3118
- estimatedTokens,
3119
- maxStageContextTokens,
3120
- limitField: "budget.maxInputContextTokens",
3121
- contextPaths: logicalPaths,
3122
- recommendedAction: "Keep frozen evidence unchanged. Use research project fork to regenerate a bounded context with concise metadata, or explicitly review budget.maxInputContextTokens in .tiangong-research/config.json and rerun project preflight plus workspace doctor before preparing this stage again. Do not raise output limits to accommodate input context.",
3123
- },
3124
- });
3125
- }
3126
- return bundled;
3243
+ return artifactPromptContext(capsuleProject, index, logicalPaths, Math.min(8_000, config.budget.maxInputContextTokens) * RESEARCH_ESTIMATED_BYTES_PER_TOKEN);
3127
3244
  }
3128
- function packagePrompt(project, workPackage, inputs, stagedSkills, capabilityDocumentation, reviewPacketSha256, contextBundle, contextBundleContent, stageContextContent, discovery, evidenceCandidates) {
3245
+ function packagePrompt(project, workPackage, inputs, stagedSkills, capabilityDocumentation, reviewPacketSha256, contextBundle, contextBundleContent, stageContextContent, discovery, evidenceCandidates, executionMode) {
3129
3246
  const stageInstructions = {
3130
3247
  discover: "Assess candidates incrementally through the packet's recordAssessment command; do not accumulate a source-sized final response. The control plane has already assigned every immutable input, broker result, and structured data result a candidateId and retains its title, URL, DOI, dates, receipt, locator, JSON Pointer, hashes, and retrieval metadata. Reference candidateId; never repeat or invent those deterministic fields. Give each admitted candidate a concise sourceId plus source type, relevance, quality, applicability, coverage dimensions, and limitations. Record meaningful explicit rejections; omitted candidates remain unassessed for later gap filling. Native Web or Browser discoveries are supplemental candidates only and cannot be admitted until an immutable broker receipt is attached to the same canonical URL or DOI. After broad search, strict assessment, and focused gap filling, return only the small closeout object with one judgment for every reviewed dimension plus limitations and remaining gaps. The CLI mechanically joins the latest recorded assessments to provenance, derives counts/date range/coverage, and rejects unknown or unformalized candidates.",
3131
3248
  acquire: "Audit every provisionally admitted source exactly once. For each source, bind its ledger candidateId, list only artifactIds returned by the exact artifact registration command, and choose accepted, limited, or rejected with a concise rationale and explicit limitations. A broker receipt is an immutable discovery record but is not full text. Use an empty artifactIds array only when intentionally retaining a source as metadata/abstract evidence or when the source is an already registered local input. Put unresolved blocking acquisition or coverage deficiencies in gaps; put honest non-blocking scope constraints in limitations. Do not invent file paths, hashes, URLs, artifact IDs, or successful downloads.",
3132
- analyze: "Use only the complete embedded inference snapshot below and return analysis schema v2. Bind the exact inference snapshot hash and one reproducible analysisRun. Every finding must cite admitted source IDs and exact evidenceAtomIds; a top-journal finding must also bind design claimIds. State uncertainty and applicability. Never promote a source-level citation when no exact atom supports the statement.",
3133
- synthesize: "Use only the complete embedded inference snapshot, analysis, and Claim–Evidence Graph below. Return the schema-defined object whose reportMarkdown separates supported conclusions, uncertainty, limitations, and next actions. Every material statement must remain within a graph-bound finding. Use real Markdown line breaks encoded exactly once for JSON; never place literal /n or double-escaped \\n markers in reportMarkdown.",
3134
- review: `Independently inspect the complete embedded artifacts and globally bounded evidence excerpts. The CLI has already verified every bound full evidence object's size and SHA-256 and persistently stored the complete review packet; its hash is schema-bound even though the packet metadata is not duplicated in model context. Do not claim to have read beyond the embedded excerpts. Return the schema-defined review bound to packetSha256 ${reviewPacketSha256 ?? "unavailable"}. Use pass only when every material claim is traceable within the admitted evidence and clearly scoped to its limitations.`,
3249
+ analyze: "Use only the admitted inference snapshot and its packet-bound on-demand artifacts; return analysis schema v2. Bind the exact inference snapshot hash and one reproducible analysisRun. Every finding must cite admitted source IDs and exact evidenceAtomIds; a top-journal finding must also bind design claimIds. State uncertainty and applicability. Never promote a source-level citation when no exact atom supports the statement.",
3250
+ synthesize: "Use only the admitted inference snapshot, analysis, and Claim–Evidence Graph, reading their complete packet-bound objects on demand. Return the schema-defined object whose reportMarkdown separates supported conclusions, uncertainty, limitations, and next actions. Every material statement must remain within a graph-bound finding. Use real Markdown line breaks encoded exactly once for JSON; never place literal /n or double-escaped \\n markers in reportMarkdown.",
3251
+ review: `Independently inspect the supplied excerpts and use the packet-only artifact tools for further material, including failed checks and counterevidence. Every full object's size and SHA-256 is bound; metadata or a digest is not proof that you read it. Return the schema-defined review bound to packetSha256 ${reviewPacketSha256 ?? "unavailable"}. Use pass only after sufficient inspection supports every material claim and its stated limitations.`,
3135
3252
  close: "No agent action is allowed for mechanical closure.",
3136
3253
  };
3137
3254
  const prompt = [
3138
- "Operate only inside this isolated research capsule.",
3255
+ executionMode === "native-host"
3256
+ ? "Operate in the current native host with this project's authorized inputs and packet operations; host permissions still apply."
3257
+ : "Operate only inside this isolated research capsule.",
3139
3258
  `Project: ${project.id}`,
3140
3259
  `Question: ${project.question}`,
3141
3260
  `Stage: ${workPackage.stage}`,
@@ -3144,15 +3263,19 @@ function packagePrompt(project, workPackage, inputs, stagedSkills, capabilityDoc
3144
3263
  `Bounded input context bundle: ${JSON.stringify(contextBundle)}`,
3145
3264
  `Staged capability directories: ${JSON.stringify(stagedSkills.map((path) => `skills/${basename(path)}`))}`,
3146
3265
  workPackage.stage === "discover"
3147
- ? "The exact capability manifest and each staged external SKILL.md are embedded below. Use this documentation directly; filesystem tools are disabled."
3266
+ ? "The exact capability manifest and each staged external SKILL.md are supplied inline or through the native packet's artifact read commands. Read the required documentation; do not launch its standalone commands for broker work."
3148
3267
  : workPackage.stage === "acquire"
3149
3268
  ? "Use the installed external acquisition/document Skills in the current native host, but treat the CLI artifact registry and acquisition schema as the only authority for durable evidence."
3150
3269
  : "Capability files are provenance-bound but are not available as execution tools in this stage.",
3151
3270
  workPackage.stage === "discover"
3152
3271
  ? `Follow the reviewed discovery plan: ${JSON.stringify(discovery)}. The plan's max evidence-call count is a hard working ceiling, not a target to exhaust. Execute required first-pass channels before supplemental channels, prefer broad high-yield queries, assess registered candidates between batches, and use the next gap-fill batch only for explicit uncovered dimensions, source types, date ranges, full text, limitations, or counterevidence. Stop fetching as soon as the declared coverage minimums are supportable. Native Web/Browser may broaden lead discovery, but every such action must be recorded through recordActivity, every useful result must be registered as a candidate, and the same URL/DOI must then be formalized through the broker before admission. A broker receipt, structured data-runtime receipt, or immutable registered input is an admissible evidence path. Do not execute a staged Skill's curl/CLI examples or read provider environment variables. For generic broker capabilities, invoke fetch_candidate_source with the manifest capability ID and obey its exact declared HTTP method. For structured data capabilities, use the packet's dynamic catalog and describe command, then invoke runDataCapability; never call standalone data run for project evidence. Never place API keys, tokens, authorization data, cookies, or other credential-like fields in request files. The Research control plane injects declared logical credentials and persists only safe hash-bound results. Exercise every manifest capability with requiredForDiscovery=true and every project-required data capability ID, or the mechanical coverage gate will stop downstream work.`
3153
- : "Use only the complete embedded stage context; no tools or additional source reads are allowed.",
3272
+ : executionMode === "native-host" && workPackage.stage === "acquire"
3273
+ ? "Acquire files and readable derivatives only for provisionally admitted sources using the packet's bindDownload and registerArtifact commands. Do not reopen discovery, admit new sources, or access unrelated host files."
3274
+ : "Use only admitted stage context and this packet's read-only artifact channel. No new evidence acquisition or arbitrary host-file access is authorized.",
3154
3275
  stageInstructions[workPackage.stage],
3155
- "Do not write stage output files directly. Your final response must be only the JSON object required by the supplied output schema; the CLI will validate and atomically materialize it.",
3276
+ executionMode === "native-host"
3277
+ ? "Do not write admitted output paths or control-plane files directly. Save only the final schema-conforming JSON object to a new regular file, then submit it with the packet's submit command. The CLI remains the sole authority for validation and atomic promotion."
3278
+ : "Do not write stage output files directly. Your final response must be only the JSON object required by the supplied output schema; the CLI will validate and atomically materialize it.",
3156
3279
  "Do not edit project.json, input manifests, prior outputs, evidence objects, or staged capability files.",
3157
3280
  ];
3158
3281
  if (workPackage.stage === "discover") {
@@ -3164,10 +3287,10 @@ function packagePrompt(project, workPackage, inputs, stagedSkills, capabilityDoc
3164
3287
  publicationDate: candidate.publicationDate,
3165
3288
  excerpt: candidate.excerpt,
3166
3289
  originKind: candidate.origin.kind,
3167
- })))}`, "The complete external capability documentation bundle is embedded below:", capabilityDocumentation, "The complete authorized local-input context is embedded below. Use it directly and do not re-read individual local input files. Full evidence files are intentionally withheld from producer packages when fullTextStaged=false.", contextBundleContent);
3290
+ })))}`, "External capability documentation (inline or exact on-demand reference):", capabilityDocumentation, "Authorized local-input context (inline or exact on-demand reference). Full evidence files are intentionally withheld from producer packages when fullTextStaged=false; the read channel does not widen that authorization.", contextBundleContent);
3168
3291
  }
3169
3292
  if (stageContextContent) {
3170
- prompt.push("The complete admitted stage context is embedded below. Use it directly and do not re-read output files.", stageContextContent);
3293
+ prompt.push("Admitted stage context (inline or exact on-demand reference; no material is discarded to meet an embedding preference):", stageContextContent);
3171
3294
  }
3172
3295
  return prompt.join("\n\n");
3173
3296
  }
@@ -3188,7 +3311,7 @@ function assertPreCallTokenReservation(project, workPackage, config, request, al
3188
3311
  route: request.route,
3189
3312
  primaryPayloadTokens: Math.ceil((schemaBytes + promptBytes) / RESEARCH_ESTIMATED_BYTES_PER_TOKEN),
3190
3313
  repairPayloadTokens: Math.ceil((schemaBytes + RESEARCH_MAX_REPAIR_SOURCE_BYTES + 2_048) / RESEARCH_ESTIMATED_BYTES_PER_TOKEN),
3191
- maxTurns: request.maxTurns,
3314
+ maxTurns: request.reservationTurns ?? request.maxTurns,
3192
3315
  maxOutputTokens: request.maxOutputTokens,
3193
3316
  maxToolContextTokens: request.maxToolContextTokens ?? 0,
3194
3317
  maxRepairTokens: config.budget.maxRepairTokens,