@tiangong-ai/cli 0.0.60 → 0.0.62

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 (239) hide show
  1. package/AGENTS.md +17 -6
  2. package/README.md +577 -55
  3. package/dist/data/builtins.js +33 -5
  4. package/dist/data/builtins.js.map +1 -1
  5. package/dist/data/catalog.js +46 -0
  6. package/dist/data/catalog.js.map +1 -1
  7. package/dist/data/commands.js +15 -1
  8. package/dist/data/commands.js.map +1 -1
  9. package/dist/data/connectors/airnow-hourly-observations.js +60 -10
  10. package/dist/data/connectors/airnow-hourly-observations.js.map +1 -1
  11. package/dist/data/connectors/epa-eis-records.js +16 -2
  12. package/dist/data/connectors/epa-eis-records.js.map +1 -1
  13. package/dist/data/connectors/gdelt-doc-search.js +54 -18
  14. package/dist/data/connectors/gdelt-doc-search.js.map +1 -1
  15. package/dist/data/connectors/gdelt-file-feeds.js +20 -11
  16. package/dist/data/connectors/gdelt-file-feeds.js.map +1 -1
  17. package/dist/data/connectors/gdelt-web-ngrams.d.ts +2 -0
  18. package/dist/data/connectors/gdelt-web-ngrams.js +350 -0
  19. package/dist/data/connectors/gdelt-web-ngrams.js.map +1 -0
  20. package/dist/data/connectors/gdelt-web-ngrams.schemas.d.ts +188 -0
  21. package/dist/data/connectors/gdelt-web-ngrams.schemas.js +138 -0
  22. package/dist/data/connectors/gdelt-web-ngrams.schemas.js.map +1 -0
  23. package/dist/data/connectors/nasa-firms-fire.js +4 -0
  24. package/dist/data/connectors/nasa-firms-fire.js.map +1 -1
  25. package/dist/data/connectors/open-meteo-air-quality.js +27 -22
  26. package/dist/data/connectors/open-meteo-air-quality.js.map +1 -1
  27. package/dist/data/connectors/open-meteo-air-quality.schemas.d.ts +4 -1
  28. package/dist/data/connectors/open-meteo-air-quality.schemas.js +21 -1
  29. package/dist/data/connectors/open-meteo-air-quality.schemas.js.map +1 -1
  30. package/dist/data/connectors/open-meteo-flood.js +28 -23
  31. package/dist/data/connectors/open-meteo-flood.js.map +1 -1
  32. package/dist/data/connectors/open-meteo-flood.schemas.d.ts +4 -1
  33. package/dist/data/connectors/open-meteo-flood.schemas.js +21 -1
  34. package/dist/data/connectors/open-meteo-flood.schemas.js.map +1 -1
  35. package/dist/data/connectors/open-meteo-historical-weather.js +26 -21
  36. package/dist/data/connectors/open-meteo-historical-weather.js.map +1 -1
  37. package/dist/data/connectors/open-meteo-historical-weather.schemas.d.ts +4 -1
  38. package/dist/data/connectors/open-meteo-historical-weather.schemas.js +21 -1
  39. package/dist/data/connectors/open-meteo-historical-weather.schemas.js.map +1 -1
  40. package/dist/data/connectors/openaq-air-quality.js +50 -12
  41. package/dist/data/connectors/openaq-air-quality.js.map +1 -1
  42. package/dist/data/connectors/openaq-air-quality.schemas.d.ts +0 -4
  43. package/dist/data/connectors/openaq-air-quality.schemas.js +2 -2
  44. package/dist/data/connectors/openaq-air-quality.schemas.js.map +1 -1
  45. package/dist/data/connectors/youtube-public-content.js +76 -6
  46. package/dist/data/connectors/youtube-public-content.js.map +1 -1
  47. package/dist/data/connectors/youtube-public-content.schemas.d.ts +63 -3
  48. package/dist/data/connectors/youtube-public-content.schemas.js +56 -2
  49. package/dist/data/connectors/youtube-public-content.schemas.js.map +1 -1
  50. package/dist/data/contracts.d.ts +22 -1
  51. package/dist/data/runtime/bounded-http.d.ts +2 -0
  52. package/dist/data/runtime/bounded-http.js +221 -59
  53. package/dist/data/runtime/bounded-http.js.map +1 -1
  54. package/dist/data/runtime/execute.d.ts +2 -0
  55. package/dist/data/runtime/execute.js +82 -60
  56. package/dist/data/runtime/execute.js.map +1 -1
  57. package/dist/data/runtime/http-timing.d.ts +15 -0
  58. package/dist/data/runtime/http-timing.js +59 -0
  59. package/dist/data/runtime/http-timing.js.map +1 -0
  60. package/dist/data/runtime/http-transport.d.ts +1 -0
  61. package/dist/data/runtime/http-transport.js +27 -0
  62. package/dist/data/runtime/http-transport.js.map +1 -0
  63. package/dist/data/schemas/catalog.v1.json +46 -0
  64. package/dist/data/schemas/discovery.v1.json +12 -0
  65. package/dist/data/schemas/error.v1.json +1 -0
  66. package/dist/data/schemas/manifest.v1.json +19 -1
  67. package/dist/research/orchestration.js +341 -28
  68. package/dist/research/orchestration.js.map +1 -1
  69. package/dist/research/setup-command.js +37 -6
  70. package/dist/research/setup-command.js.map +1 -1
  71. package/dist/research/workspace/acquisition-routes.d.ts +1 -1
  72. package/dist/research/workspace/acquisition-routes.js +2 -11
  73. package/dist/research/workspace/acquisition-routes.js.map +1 -1
  74. package/dist/research/workspace/artifact-read-audit.d.ts +16 -0
  75. package/dist/research/workspace/artifact-read-audit.js +176 -0
  76. package/dist/research/workspace/artifact-read-audit.js.map +1 -0
  77. package/dist/research/workspace/artifact-trace.d.ts +12 -0
  78. package/dist/research/workspace/artifact-trace.js +109 -0
  79. package/dist/research/workspace/artifact-trace.js.map +1 -0
  80. package/dist/research/workspace/artifact-view-mcp.d.ts +75 -0
  81. package/dist/research/workspace/artifact-view-mcp.js +214 -0
  82. package/dist/research/workspace/artifact-view-mcp.js.map +1 -0
  83. package/dist/research/workspace/artifact-views.d.ts +100 -0
  84. package/dist/research/workspace/artifact-views.js +395 -0
  85. package/dist/research/workspace/artifact-views.js.map +1 -0
  86. package/dist/research/workspace/audit-bundle.d.ts +4 -0
  87. package/dist/research/workspace/audit-bundle.js +130 -11
  88. package/dist/research/workspace/audit-bundle.js.map +1 -1
  89. package/dist/research/workspace/broker.js +25 -4
  90. package/dist/research/workspace/broker.js.map +1 -1
  91. package/dist/research/workspace/constants.d.ts +1 -0
  92. package/dist/research/workspace/constants.js +1 -0
  93. package/dist/research/workspace/constants.js.map +1 -1
  94. package/dist/research/workspace/content-evidence.d.ts +1 -0
  95. package/dist/research/workspace/content-evidence.js +1 -1
  96. package/dist/research/workspace/content-evidence.js.map +1 -1
  97. package/dist/research/workspace/credentials.js +2 -1
  98. package/dist/research/workspace/credentials.js.map +1 -1
  99. package/dist/research/workspace/data-evidence-adapter.d.ts +71 -0
  100. package/dist/research/workspace/data-evidence-adapter.js +269 -59
  101. package/dist/research/workspace/data-evidence-adapter.js.map +1 -1
  102. package/dist/research/workspace/data-evidence-view.d.ts +58 -0
  103. package/dist/research/workspace/data-evidence-view.js +411 -0
  104. package/dist/research/workspace/data-evidence-view.js.map +1 -0
  105. package/dist/research/workspace/evidence-ledger.js +32 -29
  106. package/dist/research/workspace/evidence-ledger.js.map +1 -1
  107. package/dist/research/workspace/evidence.d.ts +28 -0
  108. package/dist/research/workspace/evidence.js +48 -0
  109. package/dist/research/workspace/evidence.js.map +1 -1
  110. package/dist/research/workspace/executor.d.ts +32 -2
  111. package/dist/research/workspace/executor.js +286 -42
  112. package/dist/research/workspace/executor.js.map +1 -1
  113. package/dist/research/workspace/investigation-attempt.d.ts +159 -0
  114. package/dist/research/workspace/investigation-attempt.js +619 -0
  115. package/dist/research/workspace/investigation-attempt.js.map +1 -0
  116. package/dist/research/workspace/investigation-audit.d.ts +20 -0
  117. package/dist/research/workspace/investigation-audit.js +306 -0
  118. package/dist/research/workspace/investigation-audit.js.map +1 -0
  119. package/dist/research/workspace/investigation-candidate.d.ts +84 -0
  120. package/dist/research/workspace/investigation-candidate.js +156 -0
  121. package/dist/research/workspace/investigation-candidate.js.map +1 -0
  122. package/dist/research/workspace/investigation-certification.d.ts +39 -0
  123. package/dist/research/workspace/investigation-certification.js +152 -0
  124. package/dist/research/workspace/investigation-certification.js.map +1 -0
  125. package/dist/research/workspace/investigation-close.d.ts +47 -0
  126. package/dist/research/workspace/investigation-close.js +153 -0
  127. package/dist/research/workspace/investigation-close.js.map +1 -0
  128. package/dist/research/workspace/investigation-lineage.d.ts +8 -0
  129. package/dist/research/workspace/investigation-lineage.js +10 -0
  130. package/dist/research/workspace/investigation-lineage.js.map +1 -0
  131. package/dist/research/workspace/investigation-observer.d.ts +22 -0
  132. package/dist/research/workspace/investigation-observer.js +122 -0
  133. package/dist/research/workspace/investigation-observer.js.map +1 -0
  134. package/dist/research/workspace/investigation-promotion.d.ts +129 -0
  135. package/dist/research/workspace/investigation-promotion.js +316 -0
  136. package/dist/research/workspace/investigation-promotion.js.map +1 -0
  137. package/dist/research/workspace/investigation-requirements.d.ts +5 -0
  138. package/dist/research/workspace/investigation-requirements.js +32 -0
  139. package/dist/research/workspace/investigation-requirements.js.map +1 -0
  140. package/dist/research/workspace/investigation-resources.d.ts +12 -0
  141. package/dist/research/workspace/investigation-resources.js +78 -0
  142. package/dist/research/workspace/investigation-resources.js.map +1 -0
  143. package/dist/research/workspace/investigation-source-export.d.ts +6 -0
  144. package/dist/research/workspace/investigation-source-export.js +68 -0
  145. package/dist/research/workspace/investigation-source-export.js.map +1 -0
  146. package/dist/research/workspace/investigation-store.d.ts +9 -0
  147. package/dist/research/workspace/investigation-store.js +55 -0
  148. package/dist/research/workspace/investigation-store.js.map +1 -0
  149. package/dist/research/workspace/investigation.d.ts +424 -0
  150. package/dist/research/workspace/investigation.js +716 -0
  151. package/dist/research/workspace/investigation.js.map +1 -0
  152. package/dist/research/workspace/native-process-supervisor.d.ts +1 -0
  153. package/dist/research/workspace/native-process-supervisor.js +97 -0
  154. package/dist/research/workspace/native-process-supervisor.js.map +1 -0
  155. package/dist/research/workspace/native-run.d.ts +150 -0
  156. package/dist/research/workspace/native-run.js +879 -0
  157. package/dist/research/workspace/native-run.js.map +1 -0
  158. package/dist/research/workspace/preflight.d.ts +11 -2
  159. package/dist/research/workspace/preflight.js +28 -20
  160. package/dist/research/workspace/preflight.js.map +1 -1
  161. package/dist/research/workspace/project-authority.js +2 -0
  162. package/dist/research/workspace/project-authority.js.map +1 -1
  163. package/dist/research/workspace/project-budget.d.ts +41 -0
  164. package/dist/research/workspace/project-budget.js +265 -0
  165. package/dist/research/workspace/project-budget.js.map +1 -0
  166. package/dist/research/workspace/project-mutations.d.ts +1 -1
  167. package/dist/research/workspace/project-mutations.js +29 -3
  168. package/dist/research/workspace/project-mutations.js.map +1 -1
  169. package/dist/research/workspace/projects.d.ts +66 -0
  170. package/dist/research/workspace/projects.js +182 -3
  171. package/dist/research/workspace/projects.js.map +1 -1
  172. package/dist/research/workspace/provider-budget.d.ts +3 -0
  173. package/dist/research/workspace/provider-budget.js +45 -0
  174. package/dist/research/workspace/provider-budget.js.map +1 -0
  175. package/dist/research/workspace/provider-state.d.ts +1 -0
  176. package/dist/research/workspace/provider-state.js +20 -0
  177. package/dist/research/workspace/provider-state.js.map +1 -0
  178. package/dist/research/workspace/publication-lineage.d.ts +34 -0
  179. package/dist/research/workspace/publication-lineage.js +165 -0
  180. package/dist/research/workspace/publication-lineage.js.map +1 -0
  181. package/dist/research/workspace/publication-workflow.d.ts +24 -0
  182. package/dist/research/workspace/publication-workflow.js +118 -14
  183. package/dist/research/workspace/publication-workflow.js.map +1 -1
  184. package/dist/research/workspace/request-provenance.d.ts +67 -0
  185. package/dist/research/workspace/request-provenance.js +115 -0
  186. package/dist/research/workspace/request-provenance.js.map +1 -0
  187. package/dist/research/workspace/review-executor.d.ts +6 -1
  188. package/dist/research/workspace/review-executor.js +39 -14
  189. package/dist/research/workspace/review-executor.js.map +1 -1
  190. package/dist/research/workspace/runtime.d.ts +64 -1
  191. package/dist/research/workspace/runtime.js +359 -112
  192. package/dist/research/workspace/runtime.js.map +1 -1
  193. package/dist/research/workspace/sanitization.js +1 -1
  194. package/dist/research/workspace/sanitization.js.map +1 -1
  195. package/dist/research/workspace/schema-compatibility.d.ts +5 -0
  196. package/dist/research/workspace/schema-compatibility.js +55 -0
  197. package/dist/research/workspace/schema-compatibility.js.map +1 -0
  198. package/dist/research/workspace/scientific-amendment.d.ts +103 -0
  199. package/dist/research/workspace/scientific-amendment.js +537 -0
  200. package/dist/research/workspace/scientific-amendment.js.map +1 -0
  201. package/dist/research/workspace/scientific-design.js +8 -17
  202. package/dist/research/workspace/scientific-design.js.map +1 -1
  203. package/dist/research/workspace/scientific-fulfillment-audit.d.ts +21 -0
  204. package/dist/research/workspace/scientific-fulfillment-audit.js +217 -0
  205. package/dist/research/workspace/scientific-fulfillment-audit.js.map +1 -0
  206. package/dist/research/workspace/scientific-fulfillment.d.ts +130 -0
  207. package/dist/research/workspace/scientific-fulfillment.js +553 -0
  208. package/dist/research/workspace/scientific-fulfillment.js.map +1 -0
  209. package/dist/research/workspace/scientific-review-execution.js +236 -75
  210. package/dist/research/workspace/scientific-review-execution.js.map +1 -1
  211. package/dist/research/workspace/scientific-review.d.ts +6 -1
  212. package/dist/research/workspace/scientific-review.js +228 -52
  213. package/dist/research/workspace/scientific-review.js.map +1 -1
  214. package/dist/research/workspace/setup-catalog.js +3 -3
  215. package/dist/research/workspace/setup-release.d.ts +22 -0
  216. package/dist/research/workspace/setup-release.js +426 -0
  217. package/dist/research/workspace/setup-release.js.map +1 -0
  218. package/dist/research/workspace/setup-runtime-integrity.d.ts +2 -0
  219. package/dist/research/workspace/setup-runtime-integrity.js +31 -0
  220. package/dist/research/workspace/setup-runtime-integrity.js.map +1 -0
  221. package/dist/research/workspace/setup-upgrade.d.ts +17 -0
  222. package/dist/research/workspace/setup-upgrade.js +784 -0
  223. package/dist/research/workspace/setup-upgrade.js.map +1 -0
  224. package/dist/research/workspace/setup.d.ts +31 -39
  225. package/dist/research/workspace/setup.js +220 -17
  226. package/dist/research/workspace/setup.js.map +1 -1
  227. package/dist/research/workspace/task-acceptance.d.ts +17 -2
  228. package/dist/research/workspace/task-acceptance.js +106 -10
  229. package/dist/research/workspace/task-acceptance.js.map +1 -1
  230. package/dist/research/workspace/task-audit.d.ts +5 -1
  231. package/dist/research/workspace/task-audit.js +70 -5
  232. package/dist/research/workspace/task-audit.js.map +1 -1
  233. package/dist/research/workspace/task-contract.d.ts +4 -1
  234. package/dist/research/workspace/task-contract.js +26 -5
  235. package/dist/research/workspace/task-contract.js.map +1 -1
  236. package/dist/research/workspace/types.d.ts +50 -3
  237. package/dist/research/workspace/workspace.js +34 -4
  238. package/dist/research/workspace/workspace.js.map +1 -1
  239. package/package.json +2 -1
@@ -1,11 +1,14 @@
1
+ import { assertProjectRoutePriced, projectCostLimit, projectCostExposure, remainingProjectCostUsd, reserveProjectCost, settleProjectCost, settleProjectAllocation, } from "./project-budget.js";
1
2
  import { randomUUID } from "node:crypto";
2
- import { cp, lstat, readFile, rm } from "node:fs/promises";
3
+ import { cp, lstat, readFile, realpath, rm } from "node:fs/promises";
3
4
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
4
5
  import { CliError } from "../../errors.js";
5
6
  import { isConsistentAnalysisRunMetadata } from "./analysis-run.js";
7
+ import { artifactPromptContext, artifactReadInstructions, openArtifactViews, persistArtifactReads, persistArtifactViewIndex, writeArtifactViewIndex, verifyPersistedArtifactViewIndex, } from "./artifact-views.js";
6
8
  import { taskContext } from "./task-contract.js";
9
+ import { nativeRunArtifactRecords } from "./native-run.js";
7
10
  import { compileTaskAcceptanceContext, inspectProjectTask, taskAcceptancePrompt, validateTaskReview, } from "./task-acceptance.js";
8
- import { assertProjectAuthority, projectAuthority, projectWithEffectiveAuthority, readProjectAuthorityIndex, } from "./project-authority.js";
11
+ import { assertProjectAuthority, projectAuthority, projectAuthorityIndex, projectWithEffectiveAuthority, readProjectAuthorityIndex, } from "./project-authority.js";
9
12
  import { dataPublicSchemas } from "../../data/schemas.js";
10
13
  import { loadCapabilityDeclarations, stageLockedCapabilities, verifyCapabilities, } from "./capabilities.js";
11
14
  import { startCapabilityBroker } from "./broker.js";
@@ -23,9 +26,9 @@ import { createReviewExecutor } from "./review-executor.js";
23
26
  import { requiredDiscoveryCapabilityIds } from "./external-skills.js";
24
27
  import { renderInputLineContext } from "./input-plan.js";
25
28
  import { freezeClaimEvidenceGraph, freezeInferenceSnapshot, loadCurrentInferenceSnapshot, } from "./inference.js";
26
- import { appendJournalEvent, readJournal, verifyJournal } from "./journal.js";
29
+ import { appendJournalEvent, readJournal, readVerifiedJournal, verifyJournal } from "./journal.js";
27
30
  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";
31
+ 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
32
  import { blockingScientificGate, listProjects, loadProject, nextReadyPackage, packageById, refreshProject, saveProject, scientificGateRecommendedAction, } from "./projects.js";
30
33
  import { assertResearchPolicyBinding } from "./research-policy.js";
31
34
  import { assertScientificGateForStage } from "./scientific-review.js";
@@ -261,6 +264,25 @@ async function releaseNativeStageSession(root, projectId, session) {
261
264
  }
262
265
  return disposition;
263
266
  }
267
+ export async function nativeStageBudgetReservation(root, projectId) {
268
+ if (!(await pathExists(nativeStageSessionPath(root, projectId))))
269
+ return null;
270
+ const session = await readNativeStageSession(root, projectId);
271
+ const maxCostUsd = session.packet.limits.reservedMaxCostUsd;
272
+ if (!Number.isFinite(maxCostUsd) || maxCostUsd < 0)
273
+ throw new CliError("Native reservation cost is invalid.", {
274
+ code: "RESEARCH_NATIVE_STAGE_SESSION_INVALID",
275
+ exitCode: 3,
276
+ });
277
+ return {
278
+ id: `native:${session.packet.sessionId}`,
279
+ sourceProjectId: projectId,
280
+ kind: "native-stage",
281
+ reference: session.packet.packageId,
282
+ maxCostUsd,
283
+ createdAt: session.packet.preparedAt,
284
+ };
285
+ }
264
286
  export async function inspectNativeResearchStage(root, project) {
265
287
  const path = nativeStageSessionPath(root, project.id);
266
288
  if (!(await pathExists(path))) {
@@ -308,6 +330,63 @@ export async function inspectNativeResearchStage(root, project) {
308
330
  };
309
331
  }
310
332
  }
333
+ export async function readNativeStageArtifact(input) {
334
+ return withWorkspaceLock(input.root, "research.native-stage.read", async () => {
335
+ const project = await loadProject(input.root, input.projectId);
336
+ const events = await readVerifiedJournal(workspacePaths(input.root).journal);
337
+ assertProjectAuthority(project, projectAuthorityIndex(events));
338
+ const session = await readNativeStageSession(input.root, input.projectId);
339
+ const prepared = events.findLast((event) => event.scope === project.id &&
340
+ event.type === "native.stage.prepared" &&
341
+ event.payload.sessionId === input.sessionId);
342
+ const runtimeRoot = await realpath(workspacePaths(input.root).runtime);
343
+ const capsule = await realpath(session.capsuleProject);
344
+ if (session.packet.sessionId !== input.sessionId ||
345
+ prepared?.payload.packetSha256 !== session.packet.packetSha256 ||
346
+ project.handoff.state !== "agent-actionable" ||
347
+ relative(runtimeRoot, capsule).startsWith("..") ||
348
+ isAbsolute(relative(runtimeRoot, capsule)) ||
349
+ Date.now() - Date.parse(session.packet.preparedAt) >
350
+ session.packet.limits.maxWallSeconds * 1000 ||
351
+ packageById(project, session.packet.packageId).status !== "running" ||
352
+ !session.packet.artifactViews) {
353
+ throw new CliError("Artifact reads require the exact active native stage packet.", {
354
+ code: "RESEARCH_NATIVE_STAGE_SESSION_REQUIRED",
355
+ exitCode: 3,
356
+ });
357
+ }
358
+ await assertNativeStageBinding(input.root, project, session.packet);
359
+ const views = await openArtifactViews(session.capsuleProject, session.packet.artifactViews, session.packet.packetSha256);
360
+ if (views.index.projectId !== project.id)
361
+ throw new CliError("Artifact directory belongs to another project.", {
362
+ code: "RESEARCH_ARTIFACT_VIEW_INVALID",
363
+ exitCode: 3,
364
+ });
365
+ if (!input.selection)
366
+ return views.list(input.listing);
367
+ const result = await views.read(input.selection);
368
+ await persistArtifactReads(projectRoot(input.root, project.id), session.capsuleProject, session.packet.artifactViews, session.packet.packetSha256, [result.receipt]);
369
+ await appendJournalEvent(workspacePaths(input.root).journal, "native.artifact.read", project.id, sanitizeResearchRecord({ ...result.receipt }));
370
+ return result;
371
+ });
372
+ }
373
+ /** Called under an existing short lease; observing a calculation never opens a producer. */
374
+ export async function inspectNativeCalculationScope(root, project, sessionId) {
375
+ const session = await readNativeStageSession(root, project.id);
376
+ const events = await readVerifiedJournal(workspacePaths(root).journal);
377
+ const prepared = events.findLast((event) => event.scope === project.id &&
378
+ event.type === "native.stage.prepared" &&
379
+ event.payload.sessionId === sessionId);
380
+ if (session.packet.sessionId !== sessionId ||
381
+ !["analyze", "synthesize"].includes(session.packet.stage) ||
382
+ prepared?.payload.packetSha256 !== session.packet.packetSha256 ||
383
+ packageById(project, session.packet.packageId).status !== "running" ||
384
+ Date.now() - Date.parse(session.packet.preparedAt) > session.packet.limits.maxWallSeconds * 1000) {
385
+ throw new CliError("The calculation must bind the exact active post-acquisition native packet.", { code: "RESEARCH_NATIVE_RUN_BINDING_INVALID", exitCode: 3 });
386
+ }
387
+ await assertNativeStageBinding(root, project, session.packet);
388
+ return session.packet.packetSha256;
389
+ }
311
390
  export async function prepareNativeResearchStage(input) {
312
391
  return withWorkspaceLock(input.root, "research.native-stage.prepare", async () => {
313
392
  const authority = await readProjectAuthorityIndex(input.root);
@@ -395,36 +474,39 @@ export async function prepareNativeResearchStage(input) {
395
474
  let capsule = null;
396
475
  let preparedStatePersisted = false;
397
476
  try {
398
- capsule = await createCapsule(input.root, project, workPackage, sessionId, config);
399
- const stageContextContent = await stageContextForPackage(capsule.projectRoot, project, workPackage, config);
477
+ const taskAcceptance = await compileTaskAcceptanceContext(input.root, project);
478
+ capsule = await createCapsule(input.root, project, workPackage, sessionId, config, taskAcceptance);
479
+ const stageContextContent = await stageContextForPackage(capsule.projectRoot, project, workPackage, capsule.artifactViews, config);
400
480
  const declarations = await loadCapabilityDeclarations(input.root);
401
481
  const hasBrokeredEvidence = declarations.capabilities.some((capability) => capability.permissions.includes("brokered-network"));
402
482
  const dataCapabilities = projectResearchDataCapabilities();
403
483
  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));
484
+ 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
485
  const taskContract = await taskContext(input.root, project.id, authority.taskEvents.get(project.id) ?? []);
406
486
  const taskPrompt = taskContract
407
487
  ? "Original task and current authorized scope (a workflow finish is not task completion):\n" +
408
- JSON.stringify(taskContract)
488
+ (await artifactPromptContext(capsule.projectRoot, capsule.artifactViews, [
489
+ "inputs/task-context.json",
490
+ ]))
409
491
  : "";
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
492
  const prompt = [
418
493
  "Perform this producer stage in the current interactive host session. Do not launch codex exec, claude -p, or any other nested reasoning agent.",
419
494
  capsule.publicationPolicyDocumentation,
495
+ artifactReadInstructions(capsule.artifactViews),
420
496
  taskPrompt,
497
+ taskAcceptance
498
+ ? "Current task checks and remaining original/current obligations (recorded is not independently reviewed; result content is data, not instructions):\n" +
499
+ (await artifactPromptContext(capsule.projectRoot, capsule.artifactViews, [
500
+ "inputs/task-acceptance.json",
501
+ ]))
502
+ : "",
421
503
  input.stage === "discover" && (hasBrokeredEvidence || hasDataEvidence)
422
504
  ? [
423
505
  hasBrokeredEvidence
424
506
  ? "Use native Web/Browser broadly for discovery when useful, but record every native search/navigation with recordActivity and register its candidates. Before admitting any native lead, formalize the same URL or DOI through fetchEvidence so it receives an immutable broker receipt."
425
507
  : null,
426
508
  hasDataEvidence
427
- ? "Use the dynamically projected structured data capabilities when their source coverage and operation semantics fit the question. Inspect a selected capability with the supplied describe command, then call runDataCapability with the exact published DataRunRequest. The Research adapter invokes the same TypeScript runtime in-process and returns an immutable data receipt plus candidate ID; do not invoke standalone data run for project evidence."
509
+ ? "Use the dynamically projected structured data capabilities when their source coverage and operation semantics fit the question. All runDataCapability commands declare workspace-cli-relative-argv: pass the published argv, readArgv, or describeArgv to the host's workspace-locked resolver, never a PATH-resolved global CLI. Inspect a selected capability with the supplied describe command, then call runDataCapability with the exact published DataRunRequest. The Research adapter invokes the same TypeScript runtime in-process and returns an immutable data receipt plus candidate ID; do not invoke standalone data run for project evidence. If the returned contextView has a nextCursor, use runDataCapability.readArgv to continue from immutable local evidence without another provider request. Do not claim complete item-level review until nextCursor is null; when exhaustive review is unnecessary, record the presented/total fraction as a limitation."
428
510
  : null,
429
511
  "Assess candidates in bounded batches with recordAssessment as they arrive; the final output is only a small coverage closeout. Native results without broker/data/input provenance are discovery leads, never evidence.",
430
512
  ]
@@ -434,7 +516,6 @@ export async function prepareNativeResearchStage(input) {
434
516
  ? "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
517
  : "Do not acquire additional evidence in this stage.",
436
518
  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
519
  ].join("\n\n");
439
520
  const preparedAt = new Date().toISOString();
440
521
  const bindingSha256 = await nativeStageBinding(input.root, project, workPackage);
@@ -455,6 +536,8 @@ export async function prepareNativeResearchStage(input) {
455
536
  : {}),
456
537
  publicationPolicy: capsule.publicationPolicy,
457
538
  taskContract,
539
+ taskAcceptance,
540
+ artifactViews: capsule.artifactViews,
458
541
  discovery,
459
542
  limits: {
460
543
  maxOutputBytes: config.budget.maxBytesPerPackage,
@@ -465,6 +548,42 @@ export async function prepareNativeResearchStage(input) {
465
548
  maxWallSeconds: config.budget.packageMaxWallSeconds[input.stage],
466
549
  },
467
550
  commands: {
551
+ listArtifacts: {
552
+ argv: [
553
+ "tiangong-ai",
554
+ "research",
555
+ "project",
556
+ "stage",
557
+ "artifacts",
558
+ project.id,
559
+ "--session",
560
+ sessionId,
561
+ "--workspace",
562
+ input.root,
563
+ "--json",
564
+ ],
565
+ },
566
+ readArtifact: {
567
+ argv: [
568
+ "tiangong-ai",
569
+ "research",
570
+ "project",
571
+ "stage",
572
+ "read",
573
+ project.id,
574
+ "--session",
575
+ sessionId,
576
+ "--artifact",
577
+ "<objectId-from-directory>",
578
+ "--offset",
579
+ "<byte-offset>",
580
+ "--length",
581
+ "<bytes-or-all>",
582
+ "--workspace",
583
+ input.root,
584
+ "--json",
585
+ ],
586
+ },
468
587
  inspectAccess: project.scientificDesign
469
588
  ? {
470
589
  argv: [
@@ -535,8 +654,8 @@ export async function prepareNativeResearchStage(input) {
535
654
  : null,
536
655
  runDataCapability: input.stage === "discover" && hasDataEvidence
537
656
  ? {
657
+ executionKind: "workspace-cli-relative-argv",
538
658
  argv: [
539
- "tiangong-ai",
540
659
  "research",
541
660
  "project",
542
661
  "evidence",
@@ -549,7 +668,22 @@ export async function prepareNativeResearchStage(input) {
549
668
  input.root,
550
669
  "--json",
551
670
  ],
552
- describeArgv: ["tiangong-ai", "data", "describe", "<capability-id>", "--json"],
671
+ readArgv: [
672
+ "research",
673
+ "project",
674
+ "evidence",
675
+ "data",
676
+ "read",
677
+ project.id,
678
+ "--receipt",
679
+ "<data-evidence-receipt-id>",
680
+ "--cursor",
681
+ "<opaque-next-cursor>",
682
+ "--workspace",
683
+ input.root,
684
+ "--json",
685
+ ],
686
+ describeArgv: ["data", "describe", "<capability-id>", "--json"],
553
687
  requestSchema: structuredClone(dataPublicSchemas.runRequest),
554
688
  catalog: dataCapabilities,
555
689
  }
@@ -748,6 +882,12 @@ export async function prepareNativeResearchStage(input) {
748
882
  ...sessionCore,
749
883
  sessionSha256: sha256Text(canonicalJson(sessionCore)),
750
884
  };
885
+ reserveProjectCost(project, config, {
886
+ id: `native:${sessionId}`,
887
+ kind: "native-stage",
888
+ reference: workPackage.id,
889
+ maxCostUsd: reservation.costUsd,
890
+ });
751
891
  const now = new Date().toISOString();
752
892
  workPackage.status = "running";
753
893
  workPackage.attempts += 1;
@@ -764,6 +904,7 @@ export async function prepareNativeResearchStage(input) {
764
904
  await appendJournalEvent(workspacePaths(input.root).journal, "native.stage.prepared", project.id, {
765
905
  sessionId,
766
906
  packetSha256: packet.packetSha256,
907
+ artifactViewIndexSha256: packet.artifactViews.sha256,
767
908
  bindingSha256,
768
909
  projectId: project.id,
769
910
  packageId: workPackage.id,
@@ -863,6 +1004,7 @@ export async function submitNativeResearchStage(input) {
863
1004
  }
864
1005
  await assertNativeStageBinding(input.root, project, session.packet);
865
1006
  try {
1007
+ settleProjectAllocation(project, `native:${session.packet.sessionId}`);
866
1008
  await materializeAndValidateStageOutput(input.root, project, session.capsuleProject, workPackage, raw, null);
867
1009
  const elapsed = Math.max(0.001, (Date.now() - Date.parse(session.packet.preparedAt)) / 1_000);
868
1010
  const outputTokens = Math.ceil(Buffer.byteLength(raw, "utf8") / RESEARCH_ESTIMATED_BYTES_PER_TOKEN);
@@ -881,7 +1023,7 @@ export async function submitNativeResearchStage(input) {
881
1023
  runtime: null,
882
1024
  };
883
1025
  assertActualPackageBudget(project, workPackage, config, result, session.packet.limits.maxOutputTokens);
884
- assertProjectedBudget(project, config, result);
1026
+ assertProjectedBudget(project, config, result, `native:${session.packet.sessionId}`);
885
1027
  if (workPackage.stage === "discover") {
886
1028
  await assertDiscoveryCoverage(input.root, project, resolveContained(session.capsuleProject, "outputs/evidence.json"));
887
1029
  }
@@ -1003,6 +1145,7 @@ export async function abortNativeResearchStage(input) {
1003
1145
  exitCode: 3,
1004
1146
  });
1005
1147
  }
1148
+ settleProjectAllocation(project, `native:${session.packet.sessionId}`);
1006
1149
  workPackage.status = workPackage.attempts < workPackage.maxAttempts ? "retry" : "failed";
1007
1150
  workPackage.completedAt = new Date().toISOString();
1008
1151
  workPackage.lastError = "Native stage was explicitly aborted before submission.";
@@ -1258,6 +1401,9 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
1258
1401
  emitProgress(options, progressEvent("package.started", requestId, projectId, packageId, remainingBudget(project, config), { attempt: workPackage.attempts }));
1259
1402
  const runId = randomUUID();
1260
1403
  const startedAt = new Date().toISOString();
1404
+ const budgetEntryId = `package:${runId}`;
1405
+ let executionUncertain = false;
1406
+ let usageSaved = false;
1261
1407
  let capsuleRoot;
1262
1408
  let capsuleDisposition = null;
1263
1409
  let retainedCapsuleId = null;
@@ -1303,7 +1449,7 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
1303
1449
  packet: capsule.reviewPacketRecord,
1304
1450
  });
1305
1451
  }
1306
- const stageContextContent = await stageContextForPackage(capsule.projectRoot, project, workPackage, config);
1452
+ const stageContextContent = await stageContextForPackage(capsule.projectRoot, project, workPackage, capsule.artifactViews, config);
1307
1453
  const route = workPackage.executor === "reviewer" ? config.reviewer : config.producer;
1308
1454
  const selectedPackageExecutor = workPackage.executor === "reviewer" && reviewerPackageExecutor
1309
1455
  ? reviewerPackageExecutor
@@ -1325,11 +1471,14 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
1325
1471
  options,
1326
1472
  requestId,
1327
1473
  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)) +
1474
+ 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
1475
  (capsule.publicationPolicyDocumentation
1330
1476
  ? `\n\n${capsule.publicationPolicyDocumentation}`
1331
1477
  : "") +
1332
- (capsule.taskAcceptancePrompt ? `\n\n${capsule.taskAcceptancePrompt}` : ""),
1478
+ (capsule.taskAcceptancePrompt ? `\n\n${capsule.taskAcceptancePrompt}` : "") +
1479
+ (workPackage.stage === "review"
1480
+ ? `\n\n${artifactReadInstructions(capsule.artifactViews)}`
1481
+ : ""),
1333
1482
  brokerUrl: primaryBrokerUrl,
1334
1483
  inputOnlyProvenance,
1335
1484
  maxOutputTokens: Math.min(stageOutputTokens, reservation.tokens),
@@ -1338,8 +1487,27 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
1338
1487
  expectedRuntime: runtimeForRoute(doctorAttestation, route),
1339
1488
  });
1340
1489
  assertPreCallTokenReservation(project, workPackage, config, primaryRequest, 0, true);
1490
+ const budgetEntry = reserveProjectCost(project, config, {
1491
+ id: budgetEntryId,
1492
+ kind: "review",
1493
+ reference: workPackage.id,
1494
+ maxCostUsd: primaryRequest.maxCostUsd,
1495
+ });
1496
+ if (budgetEntry)
1497
+ await saveProject(root, project);
1498
+ executionUncertain = true;
1341
1499
  result = await withHeartbeat(selectedPackageExecutor(primaryRequest), options, requestId, project, workPackage, config);
1500
+ executionUncertain = false;
1342
1501
  accountedResult = result;
1502
+ if (primaryRequest.artifactViews && result.artifactReads?.length) {
1503
+ await persistArtifactReads(projectRoot(root, project.id), capsule.projectRoot, capsule.artifactViews, primaryRequest.artifactViews.packetSha256, result.artifactReads);
1504
+ await appendJournalEvent(workspacePaths(root).journal, "review.artifacts.read", project.id, sanitizeResearchRecord({
1505
+ requestId,
1506
+ packetSha256: primaryRequest.artifactViews.packetSha256,
1507
+ indexSha256: capsule.artifactViews.sha256,
1508
+ receipts: result.artifactReads.map((receipt) => receipt.receiptSha256),
1509
+ }));
1510
+ }
1343
1511
  assertExecutorSucceeded(result);
1344
1512
  assertActualPackageBudget(project, workPackage, config, result, stageOutputTokens);
1345
1513
  let acceptedRaw = result.stdout;
@@ -1372,14 +1540,16 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
1372
1540
  expectedRuntime: runtimeForRoute(doctorAttestation, route),
1373
1541
  });
1374
1542
  assertPreCallTokenReservation(project, workPackage, config, repairRequest, result.tokens, false);
1543
+ executionUncertain = true;
1375
1544
  const repair = await withHeartbeat(selectedPackageExecutor(repairRequest), options, requestId, project, workPackage, config);
1545
+ executionUncertain = false;
1376
1546
  accountedResult = combineExecutionResults(result, repair);
1377
1547
  assertExecutorSucceeded(repair);
1378
1548
  assertActualPackageBudget(project, workPackage, config, accountedResult, stageOutputTokens + config.budget.maxRepairTokens);
1379
1549
  acceptedRaw = repair.stdout;
1380
1550
  await materializeAndValidateStageOutput(root, project, capsule.projectRoot, workPackage, repair.stdout, capsule.reviewPacketSha256);
1381
1551
  }
1382
- assertProjectedBudget(project, config, accountedResult);
1552
+ assertProjectedBudget(project, config, accountedResult, budgetEntryId);
1383
1553
  promotedOutputs = await validateAndImportOutputs(root, project, workPackage, capsule.projectRoot, config, capsule.reviewPacketSha256);
1384
1554
  if (workPackage.stage === "discover") {
1385
1555
  await assertDiscoveryCoverage(root, project);
@@ -1398,7 +1568,20 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
1398
1568
  }
1399
1569
  await commitStageEvidenceBindings(root, project, workPackage);
1400
1570
  }
1571
+ if (broker) {
1572
+ await broker.stop();
1573
+ broker = undefined;
1574
+ // HTTP handlers persist provider allocations during the executor await.
1575
+ // Keep the local package transition, but use their latest monetary state.
1576
+ const latest = await loadProject(root, projectId);
1577
+ if (latest.budget)
1578
+ project.budget = latest.budget;
1579
+ else
1580
+ delete project.budget;
1581
+ }
1401
1582
  const completedAt = new Date().toISOString();
1583
+ if (project.budget && workPackage.stage !== "close")
1584
+ settleProjectCost(project, budgetEntryId, accountedResult.costUsd, "reported-usage");
1402
1585
  applyUsage(project, accountedResult);
1403
1586
  workPackage.status = "complete";
1404
1587
  workPackage.completedAt = completedAt;
@@ -1407,6 +1590,7 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
1407
1590
  workPackage.retryNotBefore = null;
1408
1591
  refreshProject(project);
1409
1592
  await saveProject(root, project);
1593
+ usageSaved = true;
1410
1594
  await writeRunRecord(root, {
1411
1595
  schemaVersion: 1,
1412
1596
  runId,
@@ -1458,6 +1642,10 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
1458
1642
  capsuleDisposition = "retained-auth-reconciliation";
1459
1643
  retainedCapsuleId = basename(capsuleRoot);
1460
1644
  }
1645
+ if (broker) {
1646
+ await broker.stop();
1647
+ broker = undefined;
1648
+ }
1461
1649
  const failedProject = await loadProject(root, projectId);
1462
1650
  const failedPackage = packageById(failedProject, packageId);
1463
1651
  const secrets = configuredResearchSecrets(options.environment);
@@ -1466,8 +1654,12 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
1466
1654
  ? failureDetails.gaps.filter((gap) => typeof gap === "string").join("; ")
1467
1655
  : "";
1468
1656
  const message = bounded(sanitizeResearchText(`${error instanceof Error ? error.message : String(error)}${gapSummary ? ` ${gapSummary}` : ""}`, secrets), 2000);
1469
- if (accountedResult)
1657
+ if (accountedResult && !usageSaved) {
1658
+ if (!executionUncertain &&
1659
+ failedProject.budget?.entries.some((entry) => entry.id === budgetEntryId))
1660
+ settleProjectCost(failedProject, budgetEntryId, accountedResult.costUsd, "reported-usage");
1470
1661
  applyUsage(failedProject, accountedResult);
1662
+ }
1471
1663
  const classification = classifyFailure(error);
1472
1664
  failedPackage.lastError = message;
1473
1665
  failedPackage.lastFailureKind = classification.kind;
@@ -1580,7 +1772,14 @@ async function stagePublicationPolicy(root, project, capsuleProject) {
1580
1772
  manifestPath: resolveContained(capsuleProject, manifestPath),
1581
1773
  documents,
1582
1774
  };
1583
- await writeJsonAtomic(staged.manifestPath, staged);
1775
+ await writeJsonAtomic(staged.manifestPath, {
1776
+ ...staged,
1777
+ manifestPath,
1778
+ documents: documents.map((document) => ({
1779
+ ...document,
1780
+ path: relative(capsuleProject, document.path).replaceAll("\\", "/"),
1781
+ })),
1782
+ });
1584
1783
  return staged;
1585
1784
  }
1586
1785
  async function createCapsule(root, project, workPackage, runId, config, taskAcceptance = null) {
@@ -1689,35 +1888,71 @@ async function createCapsule(root, project, workPackage, runId, config, taskAcce
1689
1888
  "Approved Research Policy (mandatory for every producer and reviewer decision):",
1690
1889
  `resolvedPolicySha256=${publicationPolicy.resolvedPolicySha256}`,
1691
1890
  `verdictCeiling=${publicationPolicy.verdictCeiling}`,
1692
- ...publicationPolicy.documents.map((document) => `- ${document.kind}:${document.id} sha256=${document.sha256} path=${document.path}`),
1891
+ ...publicationPolicy.documents.map((document) => `- ${document.kind}:${document.id} sha256=${document.sha256} path=${relative(capsuleProject, document.path).replaceAll("\\", "/")}`),
1693
1892
  "Read every listed policy document before reasoning. Generic defaults are explicit constraints, not evidence that a target journal will accept the manuscript.",
1694
1893
  ].join("\n")
1695
1894
  : "";
1696
1895
  const stagedSkills = await stageLockedCapabilities(root, join(capsuleProject, "skills"));
1697
- const capabilityDocumentation = await buildCapabilityDocumentation(capsuleProject, stagedSkills, config.budget.maxInputContextTokens * RESEARCH_ESTIMATED_BYTES_PER_TOKEN);
1896
+ const capabilityDocumentation = await buildCapabilityDocumentation(capsuleProject, stagedSkills);
1897
+ await writeTextAtomic(join(capsuleProject, "inputs/capability-documentation.txt"), capabilityDocumentation);
1698
1898
  const reviewEvidenceContext = workPackage.stage === "review"
1699
1899
  ? await writeReviewEvidenceContext(root, project.id, capsuleProject, contextBundleContent, evidenceReceipts, contextualEvidenceArtifacts, config.budget.maxInputContextTokens * RESEARCH_ESTIMATED_BYTES_PER_TOKEN)
1700
1900
  : null;
1901
+ const task = workPackage.stage === "review" && taskAcceptance
1902
+ ? taskAcceptance
1903
+ : await taskContext(root, project.id);
1904
+ if (taskAcceptance && workPackage.stage !== "review")
1905
+ await writeJsonAtomic(join(capsuleProject, "inputs/task-acceptance.json"), taskAcceptance);
1906
+ if (task) {
1907
+ await writeJsonAtomic(join(capsuleProject, "inputs/task-context.json"), task);
1908
+ const source = task.requestProvenance.source;
1909
+ if (source) {
1910
+ const logical = `task/request-sources/${source.objectSha256}.json`;
1911
+ await ensureDirectory(dirname(resolveContained(capsuleProject, logical)));
1912
+ await cp(resolveContained(projectRoot(root, project.id), logical), resolveContained(capsuleProject, logical), { force: false });
1913
+ }
1914
+ }
1915
+ const taskArtifacts = new Map((taskAcceptance?.results ?? []).map((record) => [record.path, record]));
1916
+ if (task)
1917
+ for (const record of await nativeRunArtifactRecords(root, project.id))
1918
+ taskArtifacts.set(record.path, record);
1919
+ for (const result of taskArtifacts.values()) {
1920
+ await ensureDirectory(dirname(resolveContained(capsuleProject, result.path)));
1921
+ await cp(resolveContained(projectRoot(root, project.id), result.path), resolveContained(capsuleProject, result.path), { force: false });
1922
+ if ((await sha256File(resolveContained(capsuleProject, result.path))) !== result.sha256)
1923
+ throw new CliError("Native run/check artifact changed while staging the packet.", {
1924
+ code: "RESEARCH_TASK_ARTIFACT_DRIFT",
1925
+ exitCode: 3,
1926
+ });
1927
+ }
1701
1928
  const reviewPacket = reviewEvidenceContext
1702
1929
  ? await writeReviewPacket(root, capsuleProject, project, inputManifest, evidenceReceipts, evidenceArtifacts, reviewEvidenceContext.persistent, taskAcceptance)
1703
1930
  : null;
1931
+ const artifactViews = reviewPacket?.artifactViews ?? (await writeArtifactViewIndex(capsuleProject, project.id));
1704
1932
  return {
1705
1933
  capsuleRoot,
1706
1934
  projectRoot: capsuleProject,
1707
1935
  inputManifest,
1708
1936
  contextBundle,
1709
- contextBundleContent,
1937
+ contextBundleContent: await artifactPromptContext(capsuleProject, artifactViews, [
1938
+ "inputs/context-bundle.txt",
1939
+ ]),
1710
1940
  stagedSkills,
1711
- capabilityDocumentation,
1941
+ capabilityDocumentation: await artifactPromptContext(capsuleProject, artifactViews, [
1942
+ "inputs/capability-documentation.txt",
1943
+ ]),
1712
1944
  publicationPolicy,
1713
1945
  publicationPolicyDocumentation,
1714
1946
  reviewPacketSha256: reviewPacket?.sha256 ?? null,
1715
1947
  reviewPacketRecord: reviewPacket?.record ?? null,
1716
1948
  taskAcceptance,
1717
- taskAcceptancePrompt: await taskAcceptancePrompt(root, taskAcceptance),
1949
+ taskAcceptancePrompt: workPackage.stage === "review"
1950
+ ? await taskAcceptancePrompt(taskAcceptance, capsuleProject, artifactViews)
1951
+ : "",
1952
+ artifactViews,
1718
1953
  };
1719
1954
  }
1720
- async function buildCapabilityDocumentation(capsuleProject, stagedSkills, maxBytes) {
1955
+ async function buildCapabilityDocumentation(capsuleProject, stagedSkills) {
1721
1956
  const documents = [
1722
1957
  {
1723
1958
  path: "skills/manifest.json",
@@ -1733,14 +1968,6 @@ async function buildCapabilityDocumentation(capsuleProject, stagedSkills, maxByt
1733
1968
  const bundle = documents
1734
1969
  .map((document) => `--- ${document.path} ---\n${document.content.trimEnd()}`)
1735
1970
  .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
1971
  return bundle;
1745
1972
  }
1746
1973
  async function buildInputContextBundle(capsuleProject, inputManifest) {
@@ -1806,6 +2033,8 @@ async function writeReviewPacket(root, capsuleProject, project, inputManifest, e
1806
2033
  }
1807
2034
  }
1808
2035
  await writeJsonAtomic(join(capsuleProject, "inputs", "runtime-fingerprint.json"), environment);
2036
+ const artifactViews = await writeArtifactViewIndex(capsuleProject, project.id);
2037
+ const persistedViews = await persistArtifactViewIndex(projectRoot(root, project.id), capsuleProject, artifactViews);
1809
2038
  const packet = {
1810
2039
  schemaVersion: 1,
1811
2040
  projectId: project.id,
@@ -1819,6 +2048,7 @@ async function writeReviewPacket(root, capsuleProject, project, inputManifest, e
1819
2048
  },
1820
2049
  snapshotChain,
1821
2050
  taskAcceptance,
2051
+ artifactViews: persistedViews,
1822
2052
  inputs: inputManifest,
1823
2053
  reviewEvidenceContext,
1824
2054
  inputFiles: [...inputFiles.values()].sort((left, right) => left.path.localeCompare(right.path)),
@@ -1838,7 +2068,7 @@ async function writeReviewPacket(root, capsuleProject, project, inputManifest, e
1838
2068
  };
1839
2069
  await writeJsonAtomic(join(capsuleProject, "inputs", "review-packet.json"), completePacket);
1840
2070
  const record = await persistReviewPacket(root, project.id, completePacket, packetSha256);
1841
- return { sha256: packetSha256, record };
2071
+ return { sha256: packetSha256, record, artifactViews };
1842
2072
  }
1843
2073
  async function writeReviewEvidenceContext(root, projectId, capsuleProject, inputContextBundle, evidenceReceipts, evidenceArtifacts, maxBytes) {
1844
2074
  const header = [
@@ -1920,15 +2150,10 @@ async function writeReviewEvidenceContext(root, projectId, capsuleProject, input
1920
2150
  ...views.map((view) => `${view.prefix}${view.active ? "" : view.content}${view.suffix}`),
1921
2151
  ].join("\n\n");
1922
2152
  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
2153
  const activeViews = views.filter((view) => view.active).length;
1931
- const contentBudgetPerView = activeViews ? Math.floor((maxBytes - fixedBytes) / activeViews) : 0;
2154
+ const contentBudgetPerView = activeViews
2155
+ ? Math.max(0, Math.floor((maxBytes - fixedBytes) / activeViews))
2156
+ : 0;
1932
2157
  const sections = [
1933
2158
  header,
1934
2159
  ...views.map((view) => `${view.prefix}${view.active ? boundedUtf8ReviewExcerpt(view.content, contentBudgetPerView) : view.content}${view.suffix}`),
@@ -1936,14 +2161,6 @@ async function writeReviewEvidenceContext(root, projectId, capsuleProject, input
1936
2161
  const logicalPath = "inputs/review-evidence-context.txt";
1937
2162
  const path = resolveContained(capsuleProject, logicalPath);
1938
2163
  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
2164
  await writeTextAtomic(path, content);
1948
2165
  const capsule = await fileRecord(path, logicalPath);
1949
2166
  const persistentLogicalPath = `review/contexts/${capsule.sha256}.txt`;
@@ -1995,15 +2212,8 @@ async function writeProducerArtifactContext(capsuleProject, evidenceArtifacts, m
1995
2212
  .map((view) => `${view.prefix}${view.active ? "" : view.content}${view.suffix}`)
1996
2213
  .join("\n\n")}\n`;
1997
2214
  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
2215
  const activeViews = views.filter((view) => view.active).length;
2006
- const perView = activeViews ? Math.floor((maxBytes - fixedBytes) / activeViews) : 0;
2216
+ const perView = activeViews ? Math.max(0, Math.floor((maxBytes - fixedBytes) / activeViews)) : 0;
2007
2217
  const content = `${header}\n\n${views
2008
2218
  .map((view) => `${view.prefix}${view.active ? boundedUtf8ReviewExcerpt(view.content, perView) : view.content}${view.suffix}`)
2009
2219
  .join("\n\n")}\n`;
@@ -2164,6 +2374,13 @@ export async function loadVerifiedReviewPacket(root, projectId, packetSha256) {
2164
2374
  const path = resolveContained(projectRoot(root, projectId), logicalPath);
2165
2375
  const packet = await readJsonFile(path, "Research review packet");
2166
2376
  verifyReviewPacketValue(packet, packetSha256);
2377
+ if (packet.projectId !== projectId)
2378
+ throw new CliError("Review packet belongs to another project.", {
2379
+ code: "RESEARCH_REVIEW_PACKET_DRIFT",
2380
+ exitCode: 3,
2381
+ });
2382
+ if (packet.artifactViews !== undefined)
2383
+ await verifyPersistedArtifactViewIndex(projectRoot(root, projectId), projectId, packet.artifactViews);
2167
2384
  const context = packet.reviewEvidenceContext;
2168
2385
  if (!isObject(context) ||
2169
2386
  typeof context.path !== "string" ||
@@ -2315,13 +2532,18 @@ function reviewSafeReceipt(receipt) {
2315
2532
  };
2316
2533
  }
2317
2534
  function agentRequest(input) {
2318
- const toolPolicy = "none";
2535
+ const packetRead = input.purpose === "primary" &&
2536
+ input.workPackage.stage === "review" &&
2537
+ input.capsule.reviewPacketSha256 !== null;
2538
+ const toolPolicy = packetRead ? "packet-read" : "none";
2319
2539
  const maxTurns = input.purpose === "repair"
2320
2540
  ? RESEARCH_REPAIR_MAX_TURNS
2321
- : input.brokerUrl
2322
- ? RESEARCH_BROKER_MAX_TURNS
2323
- : researchStructuredOutputMaxTurns(input.route);
2324
- return {
2541
+ : packetRead
2542
+ ? RESEARCH_PACKET_READ_MAX_TURNS
2543
+ : input.brokerUrl
2544
+ ? RESEARCH_BROKER_MAX_TURNS
2545
+ : researchStructuredOutputMaxTurns(input.route);
2546
+ const request = {
2325
2547
  route: input.route,
2326
2548
  prompt: input.prompt,
2327
2549
  outputSchema: schemaForStage(input.workPackage.stage, input.capsule.reviewPacketSha256, {
@@ -2345,17 +2567,48 @@ function agentRequest(input) {
2345
2567
  timeoutSeconds: Math.min(remainingWallSeconds(input.project, input.config), input.maxWallSeconds ??
2346
2568
  input.config.budget.packageMaxWallSeconds[input.workPackage.stage]),
2347
2569
  maxTurns,
2570
+ ...(packetRead
2571
+ ? {
2572
+ reservationTurns: maxTurns,
2573
+ artifactViews: {
2574
+ index: input.capsule.artifactViews,
2575
+ packetSha256: input.capsule.reviewPacketSha256,
2576
+ },
2577
+ }
2578
+ : {}),
2348
2579
  maxOutputTokens: input.maxOutputTokens,
2349
2580
  maxToolContextTokens: input.brokerUrl
2350
2581
  ? input.config.budget.maxBrokerContextTokens *
2351
2582
  (input.brokerCallBudget ?? input.config.budget.maxBrokerCalls)
2352
- : 0,
2583
+ : packetRead
2584
+ ? RESEARCH_EXPECTED_ARTIFACT_READ_TOKENS
2585
+ : 0,
2353
2586
  maxCostUsd: input.maxCostUsd,
2354
2587
  expectedRuntime: input.expectedRuntime,
2355
2588
  toolPolicy,
2356
2589
  environment: input.options.environment,
2357
2590
  brokerUrl: input.brokerUrl,
2358
2591
  };
2592
+ if (packetRead) {
2593
+ const availableTokens = Math.min(input.config.budget.packageMaxTokens.review, input.config.budget.maxTokens - input.project.usage.tokens);
2594
+ const reservation = agentCallReservation(request, input.config, 0, true);
2595
+ const fixedTokens = reservation.totalTokens - reservation.estimatedCallInputTokens;
2596
+ for (let turns = maxTurns; turns >= 1; turns--) {
2597
+ const tokens = fixedTokens + reservation.estimatedCallInputTokensPerTurn * turns;
2598
+ if (tokens <= availableTokens &&
2599
+ reservedAgentPackageCost(input.route, tokens, input.config) <= input.maxCostUsd) {
2600
+ request.maxTurns = turns;
2601
+ request.reservationTurns = turns;
2602
+ return request;
2603
+ }
2604
+ }
2605
+ throw new CliError("No packet-read turn fits the approved review envelope.", {
2606
+ code: "RESEARCH_BUDGET_RESERVATION_FAILED",
2607
+ exitCode: 3,
2608
+ details: { packageId: input.workPackage.id, availableTokens, maxCostUsd: input.maxCostUsd },
2609
+ });
2610
+ }
2611
+ return request;
2359
2612
  }
2360
2613
  function runtimeForRoute(attestation, route) {
2361
2614
  if (!attestation)
@@ -3066,7 +3319,7 @@ async function verifyProjectInputBindings(project) {
3066
3319
  async function outputRecords(root, project, logicalPaths) {
3067
3320
  return Promise.all(logicalPaths.map((logicalPath) => fileRecord(resolveContained(projectRoot(root, project.id), logicalPath), logicalPath)));
3068
3321
  }
3069
- async function stageContextForPackage(capsuleProject, project, workPackage, config) {
3322
+ async function stageContextForPackage(capsuleProject, project, workPackage, index, config) {
3070
3323
  let analyzeFallbackContext = [];
3071
3324
  if (workPackage.stage === "analyze") {
3072
3325
  const inference = JSON.parse(await readFile(resolveContained(capsuleProject, "outputs/inference-snapshot.json"), "utf8"));
@@ -3101,41 +3354,21 @@ async function stageContextForPackage(capsuleProject, project, workPackage, conf
3101
3354
  "outputs/report.md",
3102
3355
  ]
3103
3356
  : [];
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;
3357
+ return artifactPromptContext(capsuleProject, index, logicalPaths, Math.min(8_000, config.budget.maxInputContextTokens) * RESEARCH_ESTIMATED_BYTES_PER_TOKEN);
3127
3358
  }
3128
- function packagePrompt(project, workPackage, inputs, stagedSkills, capabilityDocumentation, reviewPacketSha256, contextBundle, contextBundleContent, stageContextContent, discovery, evidenceCandidates) {
3359
+ function packagePrompt(project, workPackage, inputs, stagedSkills, capabilityDocumentation, reviewPacketSha256, contextBundle, contextBundleContent, stageContextContent, discovery, evidenceCandidates, executionMode) {
3129
3360
  const stageInstructions = {
3130
3361
  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
3362
  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.`,
3363
+ 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.",
3364
+ 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.",
3365
+ 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
3366
  close: "No agent action is allowed for mechanical closure.",
3136
3367
  };
3137
3368
  const prompt = [
3138
- "Operate only inside this isolated research capsule.",
3369
+ executionMode === "native-host"
3370
+ ? "Operate in the current native host with this project's authorized inputs and packet operations; host permissions still apply."
3371
+ : "Operate only inside this isolated research capsule.",
3139
3372
  `Project: ${project.id}`,
3140
3373
  `Question: ${project.question}`,
3141
3374
  `Stage: ${workPackage.stage}`,
@@ -3144,15 +3377,19 @@ function packagePrompt(project, workPackage, inputs, stagedSkills, capabilityDoc
3144
3377
  `Bounded input context bundle: ${JSON.stringify(contextBundle)}`,
3145
3378
  `Staged capability directories: ${JSON.stringify(stagedSkills.map((path) => `skills/${basename(path)}`))}`,
3146
3379
  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."
3380
+ ? "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
3381
  : workPackage.stage === "acquire"
3149
3382
  ? "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
3383
  : "Capability files are provenance-bound but are not available as execution tools in this stage.",
3151
3384
  workPackage.stage === "discover"
3152
- ? `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.",
3385
+ ? `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. A projected data context is not missing acquisition data: follow the packet's resolver-relative continuation arguments from immutable local evidence until complete when row-level exhaustiveness matters, or report the exact presented/total fraction and limitation. 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.`
3386
+ : executionMode === "native-host" && workPackage.stage === "acquire"
3387
+ ? "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."
3388
+ : "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
3389
  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.",
3390
+ executionMode === "native-host"
3391
+ ? "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."
3392
+ : "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
3393
  "Do not edit project.json, input manifests, prior outputs, evidence objects, or staged capability files.",
3157
3394
  ];
3158
3395
  if (workPackage.stage === "discover") {
@@ -3164,10 +3401,10 @@ function packagePrompt(project, workPackage, inputs, stagedSkills, capabilityDoc
3164
3401
  publicationDate: candidate.publicationDate,
3165
3402
  excerpt: candidate.excerpt,
3166
3403
  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);
3404
+ })))}`, "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
3405
  }
3169
3406
  if (stageContextContent) {
3170
- prompt.push("The complete admitted stage context is embedded below. Use it directly and do not re-read output files.", stageContextContent);
3407
+ prompt.push("Admitted stage context (inline or exact on-demand reference; no material is discarded to meet an embedding preference):", stageContextContent);
3171
3408
  }
3172
3409
  return prompt.join("\n\n");
3173
3410
  }
@@ -3181,10 +3418,10 @@ function repairPrompt(workPackage, raw, error) {
3181
3418
  `Invalid output:\n${bounded(sanitizeResearchText(raw), 32_000)}`,
3182
3419
  ].join("\n\n");
3183
3420
  }
3184
- function assertPreCallTokenReservation(project, workPackage, config, request, alreadyUsedTokens, reserveRepair) {
3421
+ function agentCallReservation(request, config, alreadyUsedTokens, reserveRepair) {
3185
3422
  const schemaBytes = Buffer.byteLength(JSON.stringify(request.outputSchema), "utf8");
3186
3423
  const promptBytes = Buffer.byteLength(request.prompt, "utf8");
3187
- const reservation = calculateAgentCallTokenReservation({
3424
+ return calculateAgentCallTokenReservation({
3188
3425
  route: request.route,
3189
3426
  primaryPayloadTokens: Math.ceil((schemaBytes + promptBytes) / RESEARCH_ESTIMATED_BYTES_PER_TOKEN),
3190
3427
  repairPayloadTokens: Math.ceil((schemaBytes + RESEARCH_MAX_REPAIR_SOURCE_BYTES + 2_048) / RESEARCH_ESTIMATED_BYTES_PER_TOKEN),
@@ -3195,6 +3432,9 @@ function assertPreCallTokenReservation(project, workPackage, config, request, al
3195
3432
  reserveRepair,
3196
3433
  alreadyUsedTokens,
3197
3434
  });
3435
+ }
3436
+ function assertPreCallTokenReservation(project, workPackage, config, request, alreadyUsedTokens, reserveRepair) {
3437
+ const reservation = agentCallReservation(request, config, alreadyUsedTokens, reserveRepair);
3198
3438
  const packageMaxTokens = config.budget.packageMaxTokens[workPackage.stage];
3199
3439
  const projectRemainingTokens = Math.max(0, config.budget.maxTokens - project.usage.tokens);
3200
3440
  if (reservation.totalTokens > packageMaxTokens ||
@@ -3215,6 +3455,7 @@ function reservePackageBudget(project, workPackage, config, requestedTokens) {
3215
3455
  if (workPackage.stage === "close")
3216
3456
  return { tokens: 0, costUsd: 0 };
3217
3457
  const route = workPackage.executor === "reviewer" ? config.reviewer : config.producer;
3458
+ assertProjectRoutePriced(project, route);
3218
3459
  const packageMaximum = config.budget.packageMaxTokens[workPackage.stage];
3219
3460
  const tokens = Math.min(packageMaximum, requestedTokens ?? packageMaximum);
3220
3461
  const costUsd = roundMoney(reservedAgentPackageCost(route, tokens, config));
@@ -3283,14 +3524,20 @@ function availableRepairTokens(project, workPackage, config, primary) {
3283
3524
  }
3284
3525
  return Math.max(0, Math.min(config.budget.maxRepairTokens, config.budget.packageMaxTokens[workPackage.stage] - primary.tokens, config.budget.maxTokens - project.usage.tokens - primary.tokens));
3285
3526
  }
3286
- function assertProjectedBudget(project, config, result) {
3527
+ function assertProjectedBudget(project, config, result, budgetEntryId) {
3528
+ const entry = project.budget?.entries.find((item) => item.id === budgetEntryId);
3529
+ const alreadyReserved = entry
3530
+ ? entry.status === "reserved"
3531
+ ? entry.maxCostUsd
3532
+ : entry.accountedCostUsd
3533
+ : 0;
3287
3534
  const projected = {
3288
3535
  tokens: project.usage.tokens + result.tokens,
3289
- costUsd: project.usage.costUsd + result.costUsd,
3536
+ costUsd: projectCostExposure(project) - alreadyReserved + result.costUsd,
3290
3537
  wallSeconds: project.usage.wallSeconds + result.wallSeconds,
3291
3538
  };
3292
3539
  if (projected.tokens > config.budget.maxTokens ||
3293
- projected.costUsd > config.budget.maxCostUsd ||
3540
+ projected.costUsd > projectCostLimit(config, project) ||
3294
3541
  projected.wallSeconds > config.budget.maxWallSeconds) {
3295
3542
  throw new CliError(`Research execution exceeded a hard budget for project ${project.id}.`, {
3296
3543
  code: "RESEARCH_BUDGET_EXHAUSTED",
@@ -3302,7 +3549,7 @@ function assertProjectedBudget(project, config, result) {
3302
3549
  function remainingBudget(project, config) {
3303
3550
  return {
3304
3551
  tokens: Math.max(0, config.budget.maxTokens - project.usage.tokens),
3305
- costUsd: Math.max(0, roundMoney(config.budget.maxCostUsd - project.usage.costUsd)),
3552
+ costUsd: Math.max(0, roundMoney(remainingProjectCostUsd(project, config))),
3306
3553
  wallSeconds: Math.max(0, config.budget.maxWallSeconds - project.usage.wallSeconds),
3307
3554
  };
3308
3555
  }