@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
@@ -6,8 +6,12 @@ import { arch, homedir, platform } from "node:os";
6
6
  import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { CliError } from "../../errors.js";
9
+ import { isArtifactViewBinding, openArtifactViews, reviewNetworkPolicy, } from "./artifact-views.js";
10
+ import { ARTIFACT_VIEW_TOOL_NAMES, startArtifactViewServer } from "./artifact-view-mcp.js";
11
+ import { codexArtifactTrace, isPacketArtifactTool } from "./artifact-trace.js";
9
12
  import { researchPlatformCapabilities } from "./platform-capabilities.js";
10
13
  import { configuredResearchSecrets, isSensitiveEnvironmentName, sanitizeResearchText, } from "./sanitization.js";
14
+ import { claudeCodeCompatibleSchema } from "./schema-compatibility.js";
11
15
  import { isObject, sha256File, sha256Text } from "./storage.js";
12
16
  const MAX_CAPTURE_BYTES = 5 * 1024 * 1024;
13
17
  const MIN_CAPTURE_BYTES = 64 * 1024;
@@ -36,6 +40,7 @@ const CODEX_DISABLED_FEATURES = [
36
40
  "tool_call_mcp_elicitation",
37
41
  "tool_suggest",
38
42
  "workspace_dependencies",
43
+ "view_image",
39
44
  ];
40
45
  const ROUTE_AUTH_ENVIRONMENT = {
41
46
  codex: ["OPENAI_API_KEY"],
@@ -48,11 +53,32 @@ const CLAUDE_SETTINGS_ENVIRONMENT = [
48
53
  "ANTHROPIC_AUTH_TOKEN",
49
54
  "CLAUDE_CODE_OAUTH_TOKEN",
50
55
  "ANTHROPIC_BASE_URL",
56
+ "ANTHROPIC_MODEL",
57
+ "ANTHROPIC_DEFAULT_FABLE_MODEL",
58
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
59
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
60
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
61
+ "ANTHROPIC_SMALL_FAST_MODEL",
62
+ "CLAUDE_CODE_SUBAGENT_MODEL",
51
63
  ];
52
64
  const DEFAULT_AGENT_EFFORT = "low";
53
65
  const DEFAULT_CODEX_VERBOSITY = "low";
54
66
  export async function executeAgent(request) {
67
+ // Use the same configuration snapshot for admission and child execution.
68
+ request = { ...request, environment: { ...request.environment } };
55
69
  requireHeadlessAgentRoute(request.route);
70
+ if ((request.toolPolicy === "packet-read" &&
71
+ (!isArtifactViewBinding(request.artifactViews) || request.brokerUrl !== null)) ||
72
+ (request.artifactViews && request.toolPolicy !== "packet-read")) {
73
+ throw new CliError("Packet-only review requires its exact artifact index and no broker route.", { code: "RESEARCH_ARTIFACT_VIEW_INVALID", exitCode: 3 });
74
+ }
75
+ const artifactSecrets = [
76
+ ...configuredResearchSecrets(process.env),
77
+ ...configuredResearchSecrets(request.environment),
78
+ ];
79
+ const artifactViews = request.artifactViews
80
+ ? await openArtifactViews(request.projectRoot, request.artifactViews.index, request.artifactViews.packetSha256, artifactSecrets)
81
+ : null;
56
82
  validateAgentBinary(request.route);
57
83
  if (!Number.isInteger(request.maxOutputTokens) || request.maxOutputTokens < 1) {
58
84
  throw new CliError("Agent max output tokens must be a positive integer.", {
@@ -69,7 +95,9 @@ export async function executeAgent(request) {
69
95
  const temporaryDirectory = join(request.capsuleRoot, "tmp");
70
96
  await mkdir(temporaryDirectory, { recursive: true, mode: 0o700 });
71
97
  const executables = await resolveAgentExecutables(request.route, request.environment.PATH);
72
- const runtime = await fingerprintResolvedBinary(request.route, executables.launcher, executables.target, request.environment.PATH);
98
+ const settingsEnvironment = await routeSettingsEnvironment(request.route, request.environment);
99
+ const providerRouting = configuredProviderRouting(request.route, request.environment, settingsEnvironment);
100
+ const runtime = await fingerprintResolvedBinary(request.route, executables.launcher, executables.target, request.environment.PATH, providerRouting);
73
101
  if (request.expectedRuntime && !sameRuntimeFingerprint(runtime, request.expectedRuntime)) {
74
102
  throw new CliError(`Configured ${request.route.agent} runtime drifted after the successful doctor smoke.`, {
75
103
  code: "RESEARCH_EXECUTOR_DRIFT",
@@ -82,12 +110,15 @@ export async function executeAgent(request) {
82
110
  actualWrapperSha256: runtime.wrapperSha256,
83
111
  expectedAdapterSha256: request.expectedRuntime.adapterSha256,
84
112
  actualAdapterSha256: runtime.adapterSha256,
113
+ expectedProviderRouting: request.expectedRuntime.providerRouting ?? null,
114
+ actualProviderRouting: runtime.providerRouting,
85
115
  },
86
116
  });
87
117
  }
88
118
  const capsuleHome = join(request.capsuleRoot, "home");
89
- const capsuleAuth = await prepareCapsuleHome(request.route, capsuleHome, request.environment);
119
+ const capsuleAuth = await prepareCapsuleHome(request.route, capsuleHome, request.environment, settingsEnvironment);
90
120
  const secrets = [...configuredResearchSecrets(request.environment), ...capsuleAuth.secrets];
121
+ artifactSecrets.push(...capsuleAuth.secrets);
91
122
  if (request.route.agent === "codex") {
92
123
  // Codex discovers project configuration by walking parent directories. Bound that
93
124
  // discovery to the isolated capsule project so a host workspace .codex/config.toml
@@ -97,11 +128,14 @@ export async function executeAgent(request) {
97
128
  mode: 0o600,
98
129
  });
99
130
  }
100
- const invocation = await buildInvocation(request, executables.launcher, join(request.capsuleRoot, `${request.purpose}-output-schema.json`));
101
- const sandboxed = await sandboxInvocation(invocation.binary, invocation.args, request.capsuleRoot, request.projectRoot, request.workspaceRoot, executables.target);
102
131
  const started = process.hrtime.bigint();
103
132
  let completed;
133
+ let sandboxed;
134
+ let artifactServer = null;
104
135
  try {
136
+ artifactServer = artifactViews ? await startArtifactViewServer(artifactViews) : null;
137
+ const invocation = await buildInvocation(request, executables.launcher, join(request.capsuleRoot, `${request.purpose}-output-schema.json`), artifactServer?.url);
138
+ sandboxed = await sandboxInvocation(invocation.binary, invocation.args, request.capsuleRoot, request.projectRoot, request.workspaceRoot, executables.target);
105
139
  completed = await spawnCaptured({
106
140
  binary: sandboxed.binary,
107
141
  args: sandboxed.args,
@@ -109,6 +143,7 @@ export async function executeAgent(request) {
109
143
  cwd: request.projectRoot,
110
144
  env: sanitizedEnvironment(request.environment, temporaryDirectory, capsuleHome, request.route, executables.target, capsuleAuth.environment),
111
145
  timeoutMs: request.timeoutSeconds * 1000,
146
+ compactArtifactTrace: request.route.agent === "codex" && request.toolPolicy === "packet-read",
112
147
  maxCaptureBytes: Math.min(MAX_CAPTURE_BYTES, Math.max(MIN_CAPTURE_BYTES, request.maxOutputTokens * BYTES_PER_OUTPUT_TOKEN +
113
148
  (request.maxToolContextTokens ?? 0) * BYTES_PER_TOOL_CONTEXT_TOKEN)),
114
149
  });
@@ -123,6 +158,8 @@ export async function executeAgent(request) {
123
158
  throw error;
124
159
  }
125
160
  finally {
161
+ if (artifactServer)
162
+ await artifactServer.stop();
126
163
  secrets.push(...(await reconcileCapsuleAuthentication(capsuleAuth)));
127
164
  }
128
165
  const wallSeconds = Number(process.hrtime.bigint() - started) / 1_000_000_000;
@@ -140,15 +177,21 @@ export async function executeAgent(request) {
140
177
  }
141
178
  const parsed = parseAgentResult(request.route, completed.stdout, completed.stderr);
142
179
  const redacted = redactResult(parsed.stdout, parsed.stderr, secrets);
180
+ const toolPolicyViolated = request.toolPolicy === "packet-read"
181
+ ? (parsed.telemetry.packetReadViolations ?? 0) > 0
182
+ : request.toolPolicy === "none" &&
183
+ (parsed.telemetry.toolCalls > 0 || (parsed.telemetry.packetReadViolations ?? 0) > 0);
143
184
  const exitCode = redacted.exposed
144
185
  ? 86
145
186
  : completed.captureExceeded
146
187
  ? 75
147
188
  : completed.exitCode !== 0
148
189
  ? completed.exitCode
149
- : parsed.parseFailed
150
- ? 3
151
- : 0;
190
+ : toolPolicyViolated
191
+ ? 78
192
+ : parsed.parseFailed
193
+ ? 3
194
+ : 0;
152
195
  const inputTokens = redacted.exposed ? 0 : parsed.inputTokens;
153
196
  const cachedInputTokens = redacted.exposed ? 0 : parsed.cachedInputTokens;
154
197
  const outputTokens = redacted.exposed ? 0 : parsed.outputTokens;
@@ -160,16 +203,19 @@ export async function executeAgent(request) {
160
203
  stdout: redacted.stdout,
161
204
  stderr: completed.captureExceeded
162
205
  ? `${redacted.stderr}\nagent output exceeded the configured capture limit`.trim()
163
- : redacted.stderr,
206
+ : toolPolicyViolated
207
+ ? `${redacted.stderr}\nreviewer tool policy violation: an unapproved I/O tool was reported`.trim()
208
+ : redacted.stderr,
164
209
  tokens,
165
210
  inputTokens,
166
211
  cachedInputTokens,
167
212
  outputTokens,
168
213
  costUsd,
169
214
  wallSeconds,
170
- model: parsed.model ?? request.route.model,
171
- runtime: { ...runtime, model: parsed.model ?? request.route.model },
172
- ...(request.toolPolicy === "none" && request.brokerUrl === null
215
+ model: request.route.model,
216
+ runtime,
217
+ ...((request.toolPolicy === "none" || request.toolPolicy === "packet-read") &&
218
+ request.brokerUrl === null
173
219
  ? {
174
220
  isolation: {
175
221
  ...sandboxed.isolation,
@@ -179,12 +225,13 @@ export async function executeAgent(request) {
179
225
  "private-capsule",
180
226
  ],
181
227
  writeScopes: ["private-capsule"],
182
- networkPolicy: "reviewer-provider-only",
183
- toolPolicy: "none",
228
+ networkPolicy: reviewNetworkPolicy(request.toolPolicy),
229
+ toolPolicy: request.toolPolicy,
184
230
  },
185
231
  }
186
232
  : {}),
187
- telemetry: sanitizeExecutionTelemetry(parsed.telemetry, secrets),
233
+ telemetry: sanitizeExecutionTelemetry({ ...parsed.telemetry, reportedModel: parsed.model }, secrets),
234
+ ...(artifactViews ? { artifactReads: artifactViews.receipts() } : {}),
188
235
  };
189
236
  }
190
237
  function isNestedSandboxDenial(exitCode, stderr) {
@@ -196,9 +243,11 @@ function isNestedSandboxDenial(exitCode, stderr) {
196
243
  }
197
244
  export async function fingerprintAgentRoute(route, environment) {
198
245
  requireHeadlessAgentRoute(route);
246
+ environment = { ...environment };
199
247
  validateAgentBinary(route);
200
248
  const executables = await resolveAgentExecutables(route, environment.PATH);
201
- return fingerprintResolvedBinary(route, executables.launcher, executables.target, environment.PATH);
249
+ const settingsEnvironment = await routeSettingsEnvironment(route, environment);
250
+ return fingerprintResolvedBinary(route, executables.launcher, executables.target, environment.PATH, configuredProviderRouting(route, environment, settingsEnvironment));
202
251
  }
203
252
  export async function probeNativeCapsuleIsolation(input) {
204
253
  const probeId = randomUUID();
@@ -296,8 +345,11 @@ export async function probeNativeCapsuleIsolation(input) {
296
345
  ]);
297
346
  }
298
347
  }
299
- async function buildInvocation(request, resolvedBinary, outputSchemaPath) {
300
- await writeFile(outputSchemaPath, `${JSON.stringify(request.outputSchema, null, 2)}\n`, {
348
+ async function buildInvocation(request, resolvedBinary, outputSchemaPath, artifactServerUrl) {
349
+ const outputSchema = request.route.agent === "claude"
350
+ ? claudeCodeCompatibleSchema(request.outputSchema)
351
+ : request.outputSchema;
352
+ await writeFile(outputSchemaPath, `${JSON.stringify(outputSchema, null, 2)}\n`, {
301
353
  encoding: "utf8",
302
354
  mode: 0o600,
303
355
  });
@@ -324,7 +376,7 @@ async function buildInvocation(request, resolvedBinary, outputSchemaPath) {
324
376
  ];
325
377
  for (const feature of CODEX_DISABLED_FEATURES)
326
378
  args.push("--disable", feature);
327
- if (toolPolicy === "none")
379
+ if (toolPolicy !== "workspace-read")
328
380
  args.push("--disable", "shell_tool", "--disable", "unified_exec");
329
381
  args.push("-c", "include_apps_instructions=false", "-c", "include_collaboration_mode_instructions=false", "-c", "include_environment_context=false", "-c", "include_permissions_instructions=false", "-c", `project_root_markers=[${JSON.stringify(CODEX_CAPSULE_ROOT_MARKER)}]`, "-c", `model_reasoning_effort=${JSON.stringify(request.route.effort ?? DEFAULT_AGENT_EFFORT)}`, "-c", `model_verbosity=${JSON.stringify(request.route.verbosity ?? DEFAULT_CODEX_VERBOSITY)}`);
330
382
  if (request.brokerUrl) {
@@ -332,6 +384,8 @@ async function buildInvocation(request, resolvedBinary, outputSchemaPath) {
332
384
  }
333
385
  if (request.route.model)
334
386
  args.push("--model", request.route.model);
387
+ if (artifactServerUrl)
388
+ args.push("-c", `mcp_servers.research_artifacts.url=${JSON.stringify(artifactServerUrl)}`, "-c", "mcp_servers.research_artifacts.required=true", "-c", `mcp_servers.research_artifacts.enabled_tools=${JSON.stringify(ARTIFACT_VIEW_TOOL_NAMES)}`);
335
389
  return { binary: resolvedBinary, args, stdin: request.prompt };
336
390
  }
337
391
  if (request.route.agent !== "claude") {
@@ -355,14 +409,14 @@ async function buildInvocation(request, resolvedBinary, outputSchemaPath) {
355
409
  "--no-chrome",
356
410
  "--disable-slash-commands",
357
411
  "--tools",
358
- toolPolicy === "none" ? "" : "Read,Glob,Grep",
412
+ toolPolicy === "workspace-read" ? "Read,Glob,Grep" : "",
359
413
  "--effort",
360
414
  request.route.effort ?? DEFAULT_AGENT_EFFORT,
361
415
  ];
362
416
  if (request.purpose !== "repair") {
363
- args.splice(1, 0, "--json-schema", JSON.stringify(request.outputSchema));
417
+ args.splice(1, 0, "--json-schema", JSON.stringify(outputSchema));
364
418
  }
365
- const allowedTools = toolPolicy === "none" ? [] : ["Read", "Glob", "Grep"];
419
+ const allowedTools = toolPolicy === "workspace-read" ? ["Read", "Glob", "Grep"] : [];
366
420
  if (request.brokerUrl) {
367
421
  const mcpConfigPath = join(request.capsuleRoot, "research-mcp.json");
368
422
  await writeFile(mcpConfigPath, `${JSON.stringify({
@@ -373,6 +427,16 @@ async function buildInvocation(request, resolvedBinary, outputSchemaPath) {
373
427
  allowedTools.push("mcp__research_broker__fetch_candidate_source");
374
428
  args.push("--strict-mcp-config", "--mcp-config", mcpConfigPath);
375
429
  }
430
+ if (artifactServerUrl) {
431
+ const mcpConfigPath = join(request.capsuleRoot, "artifact-mcp.json");
432
+ await writeFile(mcpConfigPath, JSON.stringify({
433
+ mcpServers: {
434
+ research_artifacts: { type: "http", url: artifactServerUrl },
435
+ },
436
+ }), { encoding: "utf8", mode: 0o600 });
437
+ allowedTools.push(...ARTIFACT_VIEW_TOOL_NAMES.map((name) => `mcp__research_artifacts__${name}`));
438
+ args.push("--strict-mcp-config", "--mcp-config", mcpConfigPath);
439
+ }
376
440
  args.push("--allowedTools", allowedTools.join(","));
377
441
  if (request.maxCostUsd > 0)
378
442
  args.push("--max-budget-usd", String(request.maxCostUsd));
@@ -385,8 +449,9 @@ function requireHeadlessAgentRoute(route) {
385
449
  return;
386
450
  throw new CliError(`The ${route.agent} producer is native-host only and cannot be launched by the control plane.`, { code: "RESEARCH_EXECUTOR_INVALID", exitCode: 2 });
387
451
  }
388
- async function sandboxInvocation(binary, args, capsuleRoot, projectRoot, workspaceRoot, targetBinary) {
452
+ async function sandboxInvocation(binary, args, capsuleRoot, projectRoot, workspaceRoot, targetBinary, mode = "review") {
389
453
  const configuredCredentialPath = join(workspaceRoot, ".tiangong-research", ".env");
454
+ const workspaceReal = mode === "calculation" ? await realpath(workspaceRoot) : workspaceRoot;
390
455
  const workspaceCredentialPath = await realpath(configuredCredentialPath).catch(() => configuredCredentialPath);
391
456
  const capabilities = researchPlatformCapabilities(platform());
392
457
  if (capabilities.nativeIsolationProvider === "sandbox-exec") {
@@ -400,9 +465,11 @@ async function sandboxInvocation(binary, args, capsuleRoot, projectRoot, workspa
400
465
  "(version 1)",
401
466
  "(deny default)",
402
467
  "(allow process*)",
403
- "(allow network*)",
468
+ ...(mode === "review" ? ["(allow network*)"] : []),
404
469
  "(allow file-read-metadata)",
405
- `(allow file-read* ${readClauses} (literal "/") (literal "/var") (literal "/dev/dtracehelper") (literal "/dev/null") (literal "/dev/urandom"))`,
470
+ mode === "calculation"
471
+ ? `(allow file-read* (require-all (require-any ${readClauses} (literal "/") (literal "/var") (literal "/dev/dtracehelper") (literal "/dev/null") (literal "/dev/urandom")) (require-not (subpath ${sandboxString(workspaceReal)})) (require-not (literal ${sandboxString(workspaceCredentialPath)}))))`
472
+ : `(allow file-read* ${readClauses} (literal "/") (literal "/var") (literal "/dev/dtracehelper") (literal "/dev/null") (literal "/dev/urandom"))`,
406
473
  `(deny file-read* (literal ${sandboxString(workspaceCredentialPath)}))`,
407
474
  '(allow file-ioctl (literal "/dev/dtracehelper"))',
408
475
  "(allow sysctl-read)",
@@ -430,7 +497,13 @@ async function sandboxInvocation(binary, args, capsuleRoot, projectRoot, workspa
430
497
  exitCode: 3,
431
498
  });
432
499
  }
500
+ // Review keeps its original argument order verbatim (the credential
501
+ // /dev/null mask must stay AFTER the capsule bind, or a capsule containing
502
+ // the credential path would re-expose it and shift the policy hash);
503
+ // calculation-only branches append namespaces and the workspace mask.
433
504
  const sandboxArgs = ["--die-with-parent", "--new-session"];
505
+ if (mode === "calculation")
506
+ sandboxArgs.push("--unshare-net", "--unshare-pid");
434
507
  const systemRoots = await existingLinuxSystemRoots();
435
508
  for (const path of systemRoots) {
436
509
  sandboxArgs.push("--ro-bind", path, path);
@@ -447,12 +520,22 @@ async function sandboxInvocation(binary, args, capsuleRoot, projectRoot, workspa
447
520
  continue;
448
521
  }
449
522
  await appendBubblewrapParentDirectories(sandboxArgs, runtimeRoot, createdDirectories);
523
+ // Bound unconditionally: when the workspace sits beneath a runtime root,
524
+ // the calculation-mode workspace mask below shadows only that subtree
525
+ // and the rest of the runtime stays available.
450
526
  sandboxArgs.push("--ro-bind", runtimeRoot, runtimeRoot);
451
527
  createdDirectories.add(runtimeRoot);
452
528
  }
529
+ if (mode === "calculation") {
530
+ // A private read-only tmpfs hides workspace data even beneath an allowed
531
+ // runtime/system root. No writable host alias can populate this mask.
532
+ await appendBubblewrapParentDirectories(sandboxArgs, workspaceReal, createdDirectories);
533
+ sandboxArgs.push("--tmpfs", workspaceReal, "--remount-ro", workspaceReal);
534
+ }
453
535
  await appendBubblewrapParentDirectories(sandboxArgs, capsuleRoot, createdDirectories);
454
536
  sandboxArgs.push("--bind", capsuleRoot, capsuleRoot, "--proc", "/proc", "--dev", "/dev", "--chdir", projectRoot);
455
- if (workspaceCredentialPath.startsWith(`${capsuleRoot}/`) &&
537
+ if (mode === "review" &&
538
+ workspaceCredentialPath.startsWith(`${capsuleRoot}/`) &&
456
539
  (await pathIsReadable(workspaceCredentialPath))) {
457
540
  sandboxArgs.push("--ro-bind", "/dev/null", workspaceCredentialPath);
458
541
  }
@@ -476,7 +559,7 @@ async function sandboxInvocation(binary, args, capsuleRoot, projectRoot, workspa
476
559
  },
477
560
  });
478
561
  }
479
- async function prepareCapsuleHome(route, capsuleHome, environment) {
562
+ async function prepareCapsuleHome(route, capsuleHome, environment, settingsEnvironment) {
480
563
  await mkdir(capsuleHome, { recursive: true, mode: 0o700 });
481
564
  const sourceHome = environment.HOME && isAbsolute(environment.HOME) ? environment.HOME : homedir();
482
565
  const claudeConfigRoot = environment.CLAUDE_CONFIG_DIR && isAbsolute(environment.CLAUDE_CONFIG_DIR)
@@ -522,9 +605,6 @@ async function prepareCapsuleHome(route, capsuleHome, environment) {
522
605
  });
523
606
  secrets.push(...authSecretValues(await readFile(candidate.destination, "utf8")));
524
607
  }
525
- const settingsEnvironment = route.agent === "claude"
526
- ? await readClaudeSettingsEnvironment(join(claudeConfigRoot, "settings.json"))
527
- : {};
528
608
  for (const name of ROUTE_AUTH_ENVIRONMENT[route.agent]) {
529
609
  const value = settingsEnvironment[name];
530
610
  if (value && value.length >= 8)
@@ -658,6 +738,88 @@ function authenticationReconciliationError(reason) {
658
738
  },
659
739
  });
660
740
  }
741
+ async function routeSettingsEnvironment(route, environment) {
742
+ if (route.agent !== "claude")
743
+ return {};
744
+ const sourceHome = environment.HOME && isAbsolute(environment.HOME) ? environment.HOME : homedir();
745
+ const configRoot = environment.CLAUDE_CONFIG_DIR && isAbsolute(environment.CLAUDE_CONFIG_DIR)
746
+ ? environment.CLAUDE_CONFIG_DIR
747
+ : join(sourceHome, ".claude");
748
+ return readClaudeSettingsEnvironment(join(configRoot, "settings.json"));
749
+ }
750
+ function configuredProviderRouting(route, environment, settings) {
751
+ // This is a binding of admitted configuration, never a wire-level identity claim.
752
+ // Keep URL paths/queries, proxy credentials and mapped provider IDs out of receipts.
753
+ const selected = new Map();
754
+ const modelMappingSources = {};
755
+ const names = new Set([...Object.keys(environment), ...Object.keys(settings)]);
756
+ for (const name of [...names].sort()) {
757
+ if (isSensitiveEnvironmentName(name))
758
+ continue;
759
+ const isModel = route.agent === "claude" &&
760
+ (/^ANTHROPIC_(?:DEFAULT_[A-Z_]+_MODEL|MODEL|SMALL_FAST_MODEL)$/.test(name) ||
761
+ name === "CLAUDE_CODE_SUBAGENT_MODEL");
762
+ const isRouting = /^(?:https?_proxy|all_proxy|no_proxy)$/i.test(name) ||
763
+ (route.agent === "claude" &&
764
+ (/^ANTHROPIC_[A-Z_]*BASE_URL$/.test(name) || /^CLAUDE_CODE_USE_[A-Z_]+$/.test(name))) ||
765
+ (route.agent === "codex" && name === "OPENAI_BASE_URL");
766
+ if (!isModel && !isRouting)
767
+ continue;
768
+ const value = environment[name] || settings[name];
769
+ if (!value)
770
+ continue;
771
+ selected.set(name, value);
772
+ if (isModel)
773
+ modelMappingSources[name] = environment[name] ? "process-environment" : "claude-settings-env";
774
+ }
775
+ const endpoint = route.agent === "claude" ? selected.get("ANTHROPIC_BASE_URL") : undefined;
776
+ if (endpoint)
777
+ assertSafeClaudeBaseUrl(endpoint);
778
+ return {
779
+ schemaVersion: 1,
780
+ configurationSha256: sha256Text(JSON.stringify([...selected])),
781
+ endpointOrigin: endpoint ? new URL(endpoint).origin : null,
782
+ endpointSource: endpoint
783
+ ? environment.ANTHROPIC_BASE_URL
784
+ ? "process-environment"
785
+ : "claude-settings-env"
786
+ : "runtime-default",
787
+ modelMappingSources,
788
+ identityVerification: "unverified",
789
+ scope: "configured-routing-only",
790
+ };
791
+ }
792
+ /** Offline configuration inspection; never invokes an agent or sends material. */
793
+ export async function inspectAgentProviderRouting(route, environment) {
794
+ environment = { ...environment };
795
+ return configuredProviderRouting(route, environment, await routeSettingsEnvironment(route, environment));
796
+ }
797
+ export function isAgentProviderRouting(value) {
798
+ if (!isObject(value) ||
799
+ value.schemaVersion !== 1 ||
800
+ typeof value.configurationSha256 !== "string" ||
801
+ !/^[0-9a-f]{64}$/.test(value.configurationSha256) ||
802
+ typeof value.endpointSource !== "string" ||
803
+ !["process-environment", "claude-settings-env", "runtime-default"].includes(value.endpointSource) ||
804
+ value.identityVerification !== "unverified" ||
805
+ value.scope !== "configured-routing-only" ||
806
+ !isObject(value.modelMappingSources))
807
+ return false;
808
+ if (value.endpointOrigin !== null) {
809
+ if (typeof value.endpointOrigin !== "string")
810
+ return false;
811
+ try {
812
+ const url = new URL(value.endpointOrigin);
813
+ if (url.protocol !== "https:" || url.origin !== value.endpointOrigin)
814
+ return false;
815
+ }
816
+ catch {
817
+ return false;
818
+ }
819
+ }
820
+ return Object.entries(value.modelMappingSources).every(([name, source]) => /^(?:ANTHROPIC_(?:DEFAULT_[A-Z_]+_MODEL|MODEL|SMALL_FAST_MODEL)|CLAUDE_CODE_SUBAGENT_MODEL)$/.test(name) &&
821
+ (source === "process-environment" || source === "claude-settings-env"));
822
+ }
661
823
  async function readClaudeSettingsEnvironment(path) {
662
824
  const info = await lstat(path).catch(() => undefined);
663
825
  if (!info)
@@ -732,7 +894,7 @@ function assertOwnerOnlyAuthenticationFile(mode, agent) {
732
894
  });
733
895
  }
734
896
  }
735
- async function fingerprintResolvedBinary(route, launcher, target, pathValue) {
897
+ async function fingerprintResolvedBinary(route, launcher, target, pathValue, providerRouting) {
736
898
  const versionEnvironment = {
737
899
  PATH: pathValue ?? process.env.PATH,
738
900
  NO_COLOR: "1",
@@ -773,6 +935,7 @@ async function fingerprintResolvedBinary(route, launcher, target, pathValue) {
773
935
  binaryVersion: sanitizeResearchText(binaryVersion).slice(0, 300),
774
936
  platform: platform(),
775
937
  architecture: arch(),
938
+ providerRouting,
776
939
  };
777
940
  }
778
941
  async function resolveAgentExecutables(route, pathValue) {
@@ -858,7 +1021,17 @@ async function spawnCaptured(input) {
858
1021
  }
859
1022
  target.push(chunk);
860
1023
  };
861
- child.stdout.on("data", (chunk) => capture(stdout, chunk));
1024
+ const artifactTrace = input.compactArtifactTrace
1025
+ ? codexArtifactTrace({
1026
+ maxControlBytes: input.maxCaptureBytes,
1027
+ line: (chunk) => capture(stdout, chunk),
1028
+ oversized: () => {
1029
+ captureExceeded = true;
1030
+ terminate();
1031
+ },
1032
+ })
1033
+ : null;
1034
+ child.stdout.on("data", (chunk) => artifactTrace ? artifactTrace.write(chunk) : capture(stdout, chunk));
862
1035
  child.stderr.on("data", (chunk) => capture(stderr, chunk));
863
1036
  child.on("error", (error) => {
864
1037
  if (settled)
@@ -870,6 +1043,7 @@ async function spawnCaptured(input) {
870
1043
  child.on("close", (code, signal) => {
871
1044
  if (settled)
872
1045
  return;
1046
+ artifactTrace?.end();
873
1047
  settled = true;
874
1048
  clearTimeout(timer);
875
1049
  const detail = signal ? `\nprocess terminated by ${signal}` : "";
@@ -887,12 +1061,13 @@ function parseAgentResult(route, stdout, stderr) {
887
1061
  let inputTokens = 0;
888
1062
  let cachedInputTokens = 0;
889
1063
  let outputTokens = 0;
890
- let model = route.model;
1064
+ let model = null;
891
1065
  const messages = [];
892
1066
  let parsedEvents = 0;
893
1067
  const eventCounts = {};
894
1068
  const itemCounts = {};
895
1069
  let toolCalls = 0;
1070
+ let packetReadViolations = 0;
896
1071
  let reasoningOutputTokens = 0;
897
1072
  const providerErrors = [];
898
1073
  for (const line of stdout.split(/\r?\n/)) {
@@ -918,6 +1093,9 @@ function parseAgentResult(route, stdout, stderr) {
918
1093
  incrementCount(itemCounts, event.item.type);
919
1094
  if (isToolItemType(event.item.type))
920
1095
  toolCalls += 1;
1096
+ if (event.item.type === "file_change" ||
1097
+ (isToolItemType(event.item.type) && !isPacketArtifactTool(event.item)))
1098
+ packetReadViolations += 1;
921
1099
  if (event.item.type === "error")
922
1100
  appendProviderError(providerErrors, event.item);
923
1101
  }
@@ -943,6 +1121,7 @@ function parseAgentResult(route, stdout, stderr) {
943
1121
  eventCounts,
944
1122
  itemCounts,
945
1123
  toolCalls,
1124
+ ...(packetReadViolations ? { packetReadViolations } : {}),
946
1125
  providerTurns: eventCounts["turn.completed"] ?? eventCounts["turn.started"] ?? null,
947
1126
  reasoningOutputTokens,
948
1127
  providerErrors,
@@ -952,22 +1131,40 @@ function parseAgentResult(route, stdout, stderr) {
952
1131
  try {
953
1132
  const value = JSON.parse(stdout);
954
1133
  const usage = isObject(value.usage) ? value.usage : {};
1134
+ const providerFailed = value.is_error === true ||
1135
+ (typeof value.subtype === "string" && value.subtype.startsWith("error_"));
1136
+ const providerErrors = [];
1137
+ if (providerFailed) {
1138
+ if (typeof value.subtype === "string")
1139
+ appendProviderError(providerErrors, { message: value.subtype });
1140
+ if (Array.isArray(value.errors)) {
1141
+ for (const error of value.errors)
1142
+ if (typeof error === "string")
1143
+ appendProviderError(providerErrors, { message: error });
1144
+ }
1145
+ }
1146
+ const structured = value.structured_output !== undefined && value.structured_output !== null;
1147
+ const answer = structured
1148
+ ? JSON.stringify(value.structured_output)
1149
+ : typeof value.result === "string"
1150
+ ? value.result
1151
+ : stdout;
955
1152
  return {
956
- stdout: typeof value.result === "string" ? value.result : stdout,
1153
+ stdout: providerFailed ? "" : answer,
957
1154
  stderr,
958
1155
  inputTokens: numeric(usage.input_tokens),
959
1156
  cachedInputTokens: numeric(usage.cache_creation_input_tokens) + numeric(usage.cache_read_input_tokens),
960
1157
  outputTokens: numeric(usage.output_tokens),
961
1158
  costUsd: numeric(value.total_cost_usd),
962
- model: typeof value.model === "string" ? value.model : route.model,
963
- parseFailed: typeof value.result !== "string",
1159
+ model: typeof value.model === "string" ? value.model : null,
1160
+ parseFailed: providerFailed || (!structured && typeof value.result !== "string"),
964
1161
  telemetry: {
965
1162
  eventCounts: { result: 1 },
966
1163
  itemCounts: {},
967
1164
  toolCalls: 0,
968
1165
  providerTurns: numeric(value.num_turns) || null,
969
1166
  reasoningOutputTokens: numeric(usage.reasoning_output_tokens),
970
- providerErrors: [],
1167
+ providerErrors,
971
1168
  },
972
1169
  };
973
1170
  }
@@ -979,7 +1176,7 @@ function parseAgentResult(route, stdout, stderr) {
979
1176
  cachedInputTokens: 0,
980
1177
  outputTokens: 0,
981
1178
  costUsd: 0,
982
- model: route.model,
1179
+ model: null,
983
1180
  parseFailed: true,
984
1181
  telemetry: {
985
1182
  eventCounts: {},
@@ -1213,13 +1410,21 @@ function appendProviderError(target, value) {
1213
1410
  .find(Boolean);
1214
1411
  if (!candidate)
1215
1412
  return;
1216
- const bounded = candidate.slice(0, 1_000);
1217
- if (!target.includes(bounded))
1218
- target.push(bounded);
1413
+ // Truncate only after sanitization; otherwise a boundary can leave part of a
1414
+ // configured secret unmatched by the later exact-value redactor.
1415
+ if (!target.includes(candidate))
1416
+ target.push(candidate);
1219
1417
  }
1220
1418
  function sanitizeExecutionTelemetry(telemetry, secrets) {
1221
1419
  return {
1222
1420
  ...telemetry,
1421
+ ...(telemetry.reportedModel === undefined
1422
+ ? {}
1423
+ : {
1424
+ reportedModel: telemetry.reportedModel === null
1425
+ ? null
1426
+ : sanitizeResearchText(telemetry.reportedModel, secrets).slice(0, 300),
1427
+ }),
1223
1428
  providerErrors: telemetry.providerErrors.map((error) => sanitizeResearchText(error, secrets).slice(0, 1_000)),
1224
1429
  };
1225
1430
  }
@@ -1231,7 +1436,7 @@ function isToolItemType(value) {
1231
1436
  value === "tool_call" ||
1232
1437
  value.endsWith("_tool_call"));
1233
1438
  }
1234
- function sameRuntimeFingerprint(actual, expected) {
1439
+ export function sameRuntimeFingerprint(actual, expected) {
1235
1440
  return (actual.agent === expected.agent &&
1236
1441
  actual.model === expected.model &&
1237
1442
  actual.effort === expected.effort &&
@@ -1241,6 +1446,45 @@ function sameRuntimeFingerprint(actual, expected) {
1241
1446
  actual.adapterSha256 === expected.adapterSha256 &&
1242
1447
  actual.binaryVersion === expected.binaryVersion &&
1243
1448
  actual.platform === expected.platform &&
1244
- actual.architecture === expected.architecture);
1449
+ actual.architecture === expected.architecture &&
1450
+ actual.providerRouting?.configurationSha256 === expected.providerRouting?.configurationSha256);
1451
+ }
1452
+ function calculationUnsupported(detail) {
1453
+ return new CliError(`This platform cannot enforce the calculation boundary: ${detail}`, {
1454
+ code: "RESEARCH_SANDBOX_UNAVAILABLE",
1455
+ exitCode: 3,
1456
+ });
1457
+ }
1458
+ function covers(child, parent) {
1459
+ return (child === parent || (parent === "/" ? child.startsWith("/") : child.startsWith(`${parent}/`)));
1460
+ }
1461
+ /**
1462
+ * Build the confinement for one observed calculation attempt. The capsule is
1463
+ * the only writable/readable project surface, runtime and system roots are
1464
+ * read-only, and the workspace tree (data, holdout, external outputs) plus
1465
+ * host network and host process view are denied. Unsupported platforms and
1466
+ * unsupported overlaps are refused before execution; no raw fallback exists.
1467
+ */
1468
+ export async function createCalculationSandboxInvocation(input) {
1469
+ const provider = researchPlatformCapabilities(platform()).nativeIsolationProvider;
1470
+ if (provider !== "sandbox-exec" && provider !== "bubblewrap")
1471
+ throw calculationUnsupported("no sandbox-exec or Bubblewrap provider on this platform");
1472
+ const capsuleRoot = await realpath(input.capsuleRoot).catch(() => {
1473
+ throw calculationUnsupported("capsule root is missing or symbolic");
1474
+ });
1475
+ const workspaceRoot = (await realpath(input.workspaceRoot).catch(() => undefined)) ?? resolve(input.workspaceRoot);
1476
+ const binary = await realpath(input.binary).catch(() => {
1477
+ throw calculationUnsupported("calculation runtime is missing or symbolic");
1478
+ });
1479
+ if (!(await lstat(binary)).isFile())
1480
+ throw calculationUnsupported("calculation runtime is not a regular file");
1481
+ const runtimeRoot = executableReadRoot(binary);
1482
+ if (runtimeRoot === "/" || covers("/", runtimeRoot))
1483
+ throw calculationUnsupported("runtime read root would expose the whole filesystem");
1484
+ if (covers(runtimeRoot, workspaceRoot))
1485
+ throw calculationUnsupported("runtime distribution lives inside the denied workspace tree; masking it would break the runtime");
1486
+ if (covers(capsuleRoot, workspaceRoot) || covers(workspaceRoot, capsuleRoot))
1487
+ throw calculationUnsupported("use a scratch capsule outside the research workspace tree");
1488
+ return sandboxInvocation(binary, input.args, capsuleRoot, capsuleRoot, workspaceRoot, binary, "calculation");
1245
1489
  }
1246
1490
  //# sourceMappingURL=executor.js.map