@tiangong-ai/cli 0.0.59 → 0.0.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/README.md +287 -41
- package/dist/research/orchestration.js +209 -62
- package/dist/research/orchestration.js.map +1 -1
- package/dist/research/workspace/acquisition-forecast.d.ts +3 -0
- package/dist/research/workspace/acquisition-forecast.js +28 -3
- package/dist/research/workspace/acquisition-forecast.js.map +1 -1
- package/dist/research/workspace/acquisition-revision.d.ts +13 -0
- package/dist/research/workspace/acquisition-revision.js +142 -0
- package/dist/research/workspace/acquisition-revision.js.map +1 -0
- package/dist/research/workspace/acquisition-routes.d.ts +1 -1
- package/dist/research/workspace/acquisition-routes.js +2 -11
- package/dist/research/workspace/acquisition-routes.js.map +1 -1
- package/dist/research/workspace/acquisition.d.ts +3 -1
- package/dist/research/workspace/acquisition.js +67 -35
- package/dist/research/workspace/acquisition.js.map +1 -1
- package/dist/research/workspace/analysis-run.d.ts +2 -0
- package/dist/research/workspace/analysis-run.js +28 -0
- package/dist/research/workspace/analysis-run.js.map +1 -0
- package/dist/research/workspace/artifact-read-audit.d.ts +16 -0
- package/dist/research/workspace/artifact-read-audit.js +176 -0
- package/dist/research/workspace/artifact-read-audit.js.map +1 -0
- package/dist/research/workspace/artifact-trace.d.ts +12 -0
- package/dist/research/workspace/artifact-trace.js +109 -0
- package/dist/research/workspace/artifact-trace.js.map +1 -0
- package/dist/research/workspace/artifact-view-mcp.d.ts +75 -0
- package/dist/research/workspace/artifact-view-mcp.js +214 -0
- package/dist/research/workspace/artifact-view-mcp.js.map +1 -0
- package/dist/research/workspace/artifact-views.d.ts +100 -0
- package/dist/research/workspace/artifact-views.js +395 -0
- package/dist/research/workspace/artifact-views.js.map +1 -0
- package/dist/research/workspace/artifacts.d.ts +3 -0
- package/dist/research/workspace/artifacts.js +13 -0
- package/dist/research/workspace/artifacts.js.map +1 -1
- package/dist/research/workspace/audit-bundle.d.ts +9 -0
- package/dist/research/workspace/audit-bundle.js +124 -8
- package/dist/research/workspace/audit-bundle.js.map +1 -1
- package/dist/research/workspace/content-evidence.d.ts +3 -0
- package/dist/research/workspace/content-evidence.js +149 -22
- package/dist/research/workspace/content-evidence.js.map +1 -1
- package/dist/research/workspace/executor.d.ts +5 -1
- package/dist/research/workspace/executor.js +106 -22
- package/dist/research/workspace/executor.js.map +1 -1
- package/dist/research/workspace/native-run.d.ts +77 -0
- package/dist/research/workspace/native-run.js +637 -0
- package/dist/research/workspace/native-run.js.map +1 -0
- package/dist/research/workspace/preflight.d.ts +8 -2
- package/dist/research/workspace/preflight.js +17 -14
- package/dist/research/workspace/preflight.js.map +1 -1
- package/dist/research/workspace/project-authority.d.ts +21 -0
- package/dist/research/workspace/project-authority.js +186 -0
- package/dist/research/workspace/project-authority.js.map +1 -0
- package/dist/research/workspace/project-mutations.d.ts +26 -0
- package/dist/research/workspace/project-mutations.js +332 -0
- package/dist/research/workspace/project-mutations.js.map +1 -0
- package/dist/research/workspace/projects.d.ts +2 -1
- package/dist/research/workspace/projects.js +133 -91
- package/dist/research/workspace/projects.js.map +1 -1
- package/dist/research/workspace/publication-workflow.d.ts +3 -0
- package/dist/research/workspace/publication-workflow.js +15 -3
- package/dist/research/workspace/publication-workflow.js.map +1 -1
- package/dist/research/workspace/request-provenance.d.ts +67 -0
- package/dist/research/workspace/request-provenance.js +115 -0
- package/dist/research/workspace/request-provenance.js.map +1 -0
- package/dist/research/workspace/review-executor.js +22 -11
- package/dist/research/workspace/review-executor.js.map +1 -1
- package/dist/research/workspace/runtime.d.ts +57 -0
- package/dist/research/workspace/runtime.js +292 -140
- package/dist/research/workspace/runtime.js.map +1 -1
- package/dist/research/workspace/sanitization.js +1 -1
- package/dist/research/workspace/sanitization.js.map +1 -1
- package/dist/research/workspace/schema-compatibility.d.ts +5 -0
- package/dist/research/workspace/schema-compatibility.js +55 -0
- package/dist/research/workspace/schema-compatibility.js.map +1 -0
- package/dist/research/workspace/schemas.d.ts +6 -0
- package/dist/research/workspace/schemas.js +13 -2
- package/dist/research/workspace/schemas.js.map +1 -1
- package/dist/research/workspace/scientific-design.js +8 -17
- package/dist/research/workspace/scientific-design.js.map +1 -1
- package/dist/research/workspace/scientific-fulfillment-audit.d.ts +3 -0
- package/dist/research/workspace/scientific-fulfillment-audit.js +137 -0
- package/dist/research/workspace/scientific-fulfillment-audit.js.map +1 -0
- package/dist/research/workspace/scientific-fulfillment.d.ts +121 -0
- package/dist/research/workspace/scientific-fulfillment.js +539 -0
- package/dist/research/workspace/scientific-fulfillment.js.map +1 -0
- package/dist/research/workspace/scientific-review-execution.js +84 -61
- package/dist/research/workspace/scientific-review-execution.js.map +1 -1
- package/dist/research/workspace/scientific-review.d.ts +8 -2
- package/dist/research/workspace/scientific-review.js +205 -53
- package/dist/research/workspace/scientific-review.js.map +1 -1
- package/dist/research/workspace/setup-catalog.js +2 -2
- package/dist/research/workspace/task-acceptance.d.ts +134 -0
- package/dist/research/workspace/task-acceptance.js +544 -0
- package/dist/research/workspace/task-acceptance.js.map +1 -0
- package/dist/research/workspace/task-audit.d.ts +12 -0
- package/dist/research/workspace/task-audit.js +231 -0
- package/dist/research/workspace/task-audit.js.map +1 -0
- package/dist/research/workspace/task-contract.d.ts +142 -0
- package/dist/research/workspace/task-contract.js +506 -0
- package/dist/research/workspace/task-contract.js.map +1 -0
- package/dist/research/workspace/types.d.ts +6 -3
- package/dist/research/workspace/workspace.js +11 -8
- package/dist/research/workspace/workspace.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { cp, lstat, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { cp, lstat, readFile, realpath, rm } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { CliError } from "../../errors.js";
|
|
5
|
+
import { isConsistentAnalysisRunMetadata } from "./analysis-run.js";
|
|
6
|
+
import { artifactPromptContext, artifactReadInstructions, openArtifactViews, persistArtifactReads, persistArtifactViewIndex, writeArtifactViewIndex, verifyPersistedArtifactViewIndex, } from "./artifact-views.js";
|
|
7
|
+
import { taskContext } from "./task-contract.js";
|
|
8
|
+
import { nativeRunArtifactRecords } from "./native-run.js";
|
|
9
|
+
import { compileTaskAcceptanceContext, inspectProjectTask, taskAcceptancePrompt, validateTaskReview, } from "./task-acceptance.js";
|
|
10
|
+
import { assertProjectAuthority, projectAuthority, projectAuthorityIndex, projectWithEffectiveAuthority, readProjectAuthorityIndex, } from "./project-authority.js";
|
|
5
11
|
import { dataPublicSchemas } from "../../data/schemas.js";
|
|
6
12
|
import { loadCapabilityDeclarations, stageLockedCapabilities, verifyCapabilities, } from "./capabilities.js";
|
|
7
13
|
import { startCapabilityBroker } from "./broker.js";
|
|
@@ -19,9 +25,9 @@ import { createReviewExecutor } from "./review-executor.js";
|
|
|
19
25
|
import { requiredDiscoveryCapabilityIds } from "./external-skills.js";
|
|
20
26
|
import { renderInputLineContext } from "./input-plan.js";
|
|
21
27
|
import { freezeClaimEvidenceGraph, freezeInferenceSnapshot, loadCurrentInferenceSnapshot, } from "./inference.js";
|
|
22
|
-
import { appendJournalEvent, readJournal, verifyJournal } from "./journal.js";
|
|
28
|
+
import { appendJournalEvent, readJournal, readVerifiedJournal, verifyJournal } from "./journal.js";
|
|
23
29
|
import { nativeActivityRecordSchema } from "./native-activity.js";
|
|
24
|
-
import { calculateAgentCallTokenReservation, RESEARCH_BROKER_MAX_TURNS, RESEARCH_ESTIMATED_BYTES_PER_TOKEN, RESEARCH_MAX_REPAIR_SOURCE_BYTES, RESEARCH_REPAIR_MAX_TURNS,
|
|
30
|
+
import { calculateAgentCallTokenReservation, RESEARCH_BROKER_MAX_TURNS, RESEARCH_ESTIMATED_BYTES_PER_TOKEN, RESEARCH_MAX_REPAIR_SOURCE_BYTES, RESEARCH_REPAIR_MAX_TURNS, RESEARCH_PACKET_READ_MAX_TURNS, RESEARCH_EXPECTED_ARTIFACT_READ_TOKENS, researchStructuredOutputMaxTurns, reservedAgentPackageCost, } from "./preflight.js";
|
|
25
31
|
import { blockingScientificGate, listProjects, loadProject, nextReadyPackage, packageById, refreshProject, saveProject, scientificGateRecommendedAction, } from "./projects.js";
|
|
26
32
|
import { assertResearchPolicyBinding } from "./research-policy.js";
|
|
27
33
|
import { assertScientificGateForStage } from "./scientific-review.js";
|
|
@@ -177,13 +183,13 @@ async function runResearchWorkspaceInternal(root, options, packageExecutor, allo
|
|
|
177
183
|
if (options.dryRun)
|
|
178
184
|
return dryRunResult(root, requestId, options.projectId);
|
|
179
185
|
return withWorkspaceLock(root, "research.run", async () => {
|
|
180
|
-
await
|
|
186
|
+
const authority = await readProjectAuthorityIndex(root);
|
|
181
187
|
const config = await loadWorkspaceConfig(root);
|
|
182
188
|
assertExecutionConfiguration(config);
|
|
183
189
|
const reviewerPackageExecutor = useConfiguredReviewerExecutor
|
|
184
190
|
? createReviewExecutor({ root, execution: config.reviewerExecution }).execute
|
|
185
191
|
: null;
|
|
186
|
-
for (const project of await projectsForRun(root, options.projectId)) {
|
|
192
|
+
for (const project of await projectsForRun(root, options.projectId, authority)) {
|
|
187
193
|
await assertProjectPublicationPolicy(root, project);
|
|
188
194
|
}
|
|
189
195
|
let doctorAttestation = null;
|
|
@@ -205,7 +211,7 @@ async function runResearchWorkspaceInternal(root, options, packageExecutor, allo
|
|
|
205
211
|
});
|
|
206
212
|
}
|
|
207
213
|
doctorAttestation = verification.attestation;
|
|
208
|
-
const unconfirmed = (await projectsForRun(root, options.projectId)).filter((project) => config.budget.maxCostUsd > config.budget.confirmationCostUsd &&
|
|
214
|
+
const unconfirmed = (await projectsForRun(root, options.projectId, authority)).filter((project) => config.budget.maxCostUsd > config.budget.confirmationCostUsd &&
|
|
209
215
|
!project.budgetConfirmedAt);
|
|
210
216
|
if (unconfirmed.length) {
|
|
211
217
|
throw new CliError("Production research budget has not been explicitly confirmed.", {
|
|
@@ -219,7 +225,7 @@ async function runResearchWorkspaceInternal(root, options, packageExecutor, allo
|
|
|
219
225
|
const executed = [];
|
|
220
226
|
let cycles = 0;
|
|
221
227
|
while (cycles < options.maxCycles) {
|
|
222
|
-
const projects = await projectsForRun(root, options.projectId);
|
|
228
|
+
const projects = await projectsForRun(root, options.projectId, authority);
|
|
223
229
|
const selected = projects
|
|
224
230
|
.map((project) => ({ project, workPackage: nextReadyPackage(project) }))
|
|
225
231
|
.filter((item) => Boolean(item.workPackage) &&
|
|
@@ -233,7 +239,7 @@ async function runResearchWorkspaceInternal(root, options, packageExecutor, allo
|
|
|
233
239
|
const results = await Promise.all(selected.map(({ project, workPackage }) => executeWorkPackage(root, project.id, workPackage.id, config, options, requestId, packageExecutor, reviewerPackageExecutor, doctorAttestation)));
|
|
234
240
|
executed.push(...results);
|
|
235
241
|
}
|
|
236
|
-
const result = await summarizeRun(root, requestId, cycles, executed, options.maxCycles, options.projectId);
|
|
242
|
+
const result = await summarizeRun(root, requestId, cycles, executed, options.maxCycles, options.projectId, authority);
|
|
237
243
|
emitProgress(options, progressEvent("run.completed", requestId, options.projectId ?? null, null, null, {
|
|
238
244
|
status: result.status,
|
|
239
245
|
stopReason: result.stopReason,
|
|
@@ -304,12 +310,70 @@ export async function inspectNativeResearchStage(root, project) {
|
|
|
304
310
|
};
|
|
305
311
|
}
|
|
306
312
|
}
|
|
313
|
+
export async function readNativeStageArtifact(input) {
|
|
314
|
+
return withWorkspaceLock(input.root, "research.native-stage.read", async () => {
|
|
315
|
+
const project = await loadProject(input.root, input.projectId);
|
|
316
|
+
const events = await readVerifiedJournal(workspacePaths(input.root).journal);
|
|
317
|
+
assertProjectAuthority(project, projectAuthorityIndex(events));
|
|
318
|
+
const session = await readNativeStageSession(input.root, input.projectId);
|
|
319
|
+
const prepared = events.findLast((event) => event.scope === project.id &&
|
|
320
|
+
event.type === "native.stage.prepared" &&
|
|
321
|
+
event.payload.sessionId === input.sessionId);
|
|
322
|
+
const runtimeRoot = await realpath(workspacePaths(input.root).runtime);
|
|
323
|
+
const capsule = await realpath(session.capsuleProject);
|
|
324
|
+
if (session.packet.sessionId !== input.sessionId ||
|
|
325
|
+
prepared?.payload.packetSha256 !== session.packet.packetSha256 ||
|
|
326
|
+
project.handoff.state !== "agent-actionable" ||
|
|
327
|
+
relative(runtimeRoot, capsule).startsWith("..") ||
|
|
328
|
+
isAbsolute(relative(runtimeRoot, capsule)) ||
|
|
329
|
+
Date.now() - Date.parse(session.packet.preparedAt) >
|
|
330
|
+
session.packet.limits.maxWallSeconds * 1000 ||
|
|
331
|
+
packageById(project, session.packet.packageId).status !== "running" ||
|
|
332
|
+
!session.packet.artifactViews) {
|
|
333
|
+
throw new CliError("Artifact reads require the exact active native stage packet.", {
|
|
334
|
+
code: "RESEARCH_NATIVE_STAGE_SESSION_REQUIRED",
|
|
335
|
+
exitCode: 3,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
await assertNativeStageBinding(input.root, project, session.packet);
|
|
339
|
+
const views = await openArtifactViews(session.capsuleProject, session.packet.artifactViews, session.packet.packetSha256);
|
|
340
|
+
if (views.index.projectId !== project.id)
|
|
341
|
+
throw new CliError("Artifact directory belongs to another project.", {
|
|
342
|
+
code: "RESEARCH_ARTIFACT_VIEW_INVALID",
|
|
343
|
+
exitCode: 3,
|
|
344
|
+
});
|
|
345
|
+
if (!input.selection)
|
|
346
|
+
return views.list(input.listing);
|
|
347
|
+
const result = await views.read(input.selection);
|
|
348
|
+
await persistArtifactReads(projectRoot(input.root, project.id), session.capsuleProject, session.packet.artifactViews, session.packet.packetSha256, [result.receipt]);
|
|
349
|
+
await appendJournalEvent(workspacePaths(input.root).journal, "native.artifact.read", project.id, sanitizeResearchRecord({ ...result.receipt }));
|
|
350
|
+
return result;
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
/** Called under an existing short lease; observing a calculation never opens a producer. */
|
|
354
|
+
export async function inspectNativeCalculationScope(root, project, sessionId) {
|
|
355
|
+
const session = await readNativeStageSession(root, project.id);
|
|
356
|
+
const events = await readVerifiedJournal(workspacePaths(root).journal);
|
|
357
|
+
const prepared = events.findLast((event) => event.scope === project.id &&
|
|
358
|
+
event.type === "native.stage.prepared" &&
|
|
359
|
+
event.payload.sessionId === sessionId);
|
|
360
|
+
if (session.packet.sessionId !== sessionId ||
|
|
361
|
+
!["analyze", "synthesize"].includes(session.packet.stage) ||
|
|
362
|
+
prepared?.payload.packetSha256 !== session.packet.packetSha256 ||
|
|
363
|
+
packageById(project, session.packet.packageId).status !== "running" ||
|
|
364
|
+
Date.now() - Date.parse(session.packet.preparedAt) > session.packet.limits.maxWallSeconds * 1000) {
|
|
365
|
+
throw new CliError("The calculation must bind the exact active post-acquisition native packet.", { code: "RESEARCH_NATIVE_RUN_BINDING_INVALID", exitCode: 3 });
|
|
366
|
+
}
|
|
367
|
+
await assertNativeStageBinding(root, project, session.packet);
|
|
368
|
+
return session.packet.packetSha256;
|
|
369
|
+
}
|
|
307
370
|
export async function prepareNativeResearchStage(input) {
|
|
308
371
|
return withWorkspaceLock(input.root, "research.native-stage.prepare", async () => {
|
|
309
|
-
await
|
|
372
|
+
const authority = await readProjectAuthorityIndex(input.root);
|
|
310
373
|
const config = await loadWorkspaceConfig(input.root);
|
|
311
374
|
assertExecutionConfiguration(config);
|
|
312
|
-
const project = await loadProject(input.root, input.projectId);
|
|
375
|
+
const project = projectWithEffectiveAuthority(await loadProject(input.root, input.projectId), authority);
|
|
376
|
+
assertProjectAuthority(project, authority);
|
|
313
377
|
await assertScientificGateForStage(input.root, project, input.stage);
|
|
314
378
|
await assertProjectPublicationPolicy(input.root, project);
|
|
315
379
|
if (config.producer.agent !== input.hostAgent) {
|
|
@@ -391,15 +455,24 @@ export async function prepareNativeResearchStage(input) {
|
|
|
391
455
|
let preparedStatePersisted = false;
|
|
392
456
|
try {
|
|
393
457
|
capsule = await createCapsule(input.root, project, workPackage, sessionId, config);
|
|
394
|
-
const stageContextContent = await stageContextForPackage(capsule.projectRoot, project, workPackage, config);
|
|
458
|
+
const stageContextContent = await stageContextForPackage(capsule.projectRoot, project, workPackage, capsule.artifactViews, config);
|
|
395
459
|
const declarations = await loadCapabilityDeclarations(input.root);
|
|
396
460
|
const hasBrokeredEvidence = declarations.capabilities.some((capability) => capability.permissions.includes("brokered-network"));
|
|
397
461
|
const dataCapabilities = projectResearchDataCapabilities();
|
|
398
462
|
const hasDataEvidence = dataCapabilities.capabilities.length > 0;
|
|
399
|
-
const basePrompt = packagePrompt(project, workPackage, capsule.inputManifest, capsule.stagedSkills, capsule.capabilityDocumentation, null, capsule.contextBundle, capsule.contextBundleContent, stageContextContent, discovery, await listEvidenceCandidates(input.root, project.id));
|
|
463
|
+
const basePrompt = packagePrompt(project, workPackage, capsule.inputManifest, capsule.stagedSkills, capsule.capabilityDocumentation, null, capsule.contextBundle, capsule.contextBundleContent, stageContextContent, discovery, await listEvidenceCandidates(input.root, project.id), "native-host");
|
|
464
|
+
const taskContract = await taskContext(input.root, project.id, authority.taskEvents.get(project.id) ?? []);
|
|
465
|
+
const taskPrompt = taskContract
|
|
466
|
+
? "Original task and current authorized scope (a workflow finish is not task completion):\n" +
|
|
467
|
+
(await artifactPromptContext(capsule.projectRoot, capsule.artifactViews, [
|
|
468
|
+
"inputs/task-context.json",
|
|
469
|
+
]))
|
|
470
|
+
: "";
|
|
400
471
|
const prompt = [
|
|
401
472
|
"Perform this producer stage in the current interactive host session. Do not launch codex exec, claude -p, or any other nested reasoning agent.",
|
|
402
473
|
capsule.publicationPolicyDocumentation,
|
|
474
|
+
artifactReadInstructions(capsule.artifactViews),
|
|
475
|
+
taskPrompt,
|
|
403
476
|
input.stage === "discover" && (hasBrokeredEvidence || hasDataEvidence)
|
|
404
477
|
? [
|
|
405
478
|
hasBrokeredEvidence
|
|
@@ -416,7 +489,6 @@ export async function prepareNativeResearchStage(input) {
|
|
|
416
489
|
? "Acquire the provisionally admitted sources with the installed external acquisition/document Skills or an explicitly selected user-authorized browser. Capture the exact browser/adapter Download object, save it to the planned unique staging path, and call bindDownload before registerArtifact. Record browser/download/file-inspection activity with recordActivity. Failed or cancelled downloads cannot create bindings or artifacts. Never scan a download directory or infer success from file existence."
|
|
417
490
|
: "Do not acquire additional evidence in this stage.",
|
|
418
491
|
basePrompt,
|
|
419
|
-
"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.",
|
|
420
492
|
].join("\n\n");
|
|
421
493
|
const preparedAt = new Date().toISOString();
|
|
422
494
|
const bindingSha256 = await nativeStageBinding(input.root, project, workPackage);
|
|
@@ -436,6 +508,8 @@ export async function prepareNativeResearchStage(input) {
|
|
|
436
508
|
? { inputOnlyProvenanceIds: capsule.inputManifest.map((record) => record.id) }
|
|
437
509
|
: {}),
|
|
438
510
|
publicationPolicy: capsule.publicationPolicy,
|
|
511
|
+
taskContract,
|
|
512
|
+
artifactViews: capsule.artifactViews,
|
|
439
513
|
discovery,
|
|
440
514
|
limits: {
|
|
441
515
|
maxOutputBytes: config.budget.maxBytesPerPackage,
|
|
@@ -446,6 +520,42 @@ export async function prepareNativeResearchStage(input) {
|
|
|
446
520
|
maxWallSeconds: config.budget.packageMaxWallSeconds[input.stage],
|
|
447
521
|
},
|
|
448
522
|
commands: {
|
|
523
|
+
listArtifacts: {
|
|
524
|
+
argv: [
|
|
525
|
+
"tiangong-ai",
|
|
526
|
+
"research",
|
|
527
|
+
"project",
|
|
528
|
+
"stage",
|
|
529
|
+
"artifacts",
|
|
530
|
+
project.id,
|
|
531
|
+
"--session",
|
|
532
|
+
sessionId,
|
|
533
|
+
"--workspace",
|
|
534
|
+
input.root,
|
|
535
|
+
"--json",
|
|
536
|
+
],
|
|
537
|
+
},
|
|
538
|
+
readArtifact: {
|
|
539
|
+
argv: [
|
|
540
|
+
"tiangong-ai",
|
|
541
|
+
"research",
|
|
542
|
+
"project",
|
|
543
|
+
"stage",
|
|
544
|
+
"read",
|
|
545
|
+
project.id,
|
|
546
|
+
"--session",
|
|
547
|
+
sessionId,
|
|
548
|
+
"--artifact",
|
|
549
|
+
"<objectId-from-directory>",
|
|
550
|
+
"--offset",
|
|
551
|
+
"<byte-offset>",
|
|
552
|
+
"--length",
|
|
553
|
+
"<bytes-or-all>",
|
|
554
|
+
"--workspace",
|
|
555
|
+
input.root,
|
|
556
|
+
"--json",
|
|
557
|
+
],
|
|
558
|
+
},
|
|
449
559
|
inspectAccess: project.scientificDesign
|
|
450
560
|
? {
|
|
451
561
|
argv: [
|
|
@@ -745,6 +855,7 @@ export async function prepareNativeResearchStage(input) {
|
|
|
745
855
|
await appendJournalEvent(workspacePaths(input.root).journal, "native.stage.prepared", project.id, {
|
|
746
856
|
sessionId,
|
|
747
857
|
packetSha256: packet.packetSha256,
|
|
858
|
+
artifactViewIndexSha256: packet.artifactViews.sha256,
|
|
748
859
|
bindingSha256,
|
|
749
860
|
projectId: project.id,
|
|
750
861
|
packageId: workPackage.id,
|
|
@@ -792,6 +903,8 @@ export function nativeEvidenceRequestSchema(requireAcquisitionRoute) {
|
|
|
792
903
|
}
|
|
793
904
|
export async function submitNativeResearchStage(input) {
|
|
794
905
|
return withWorkspaceLock(input.root, "research.native-stage.submit", async () => {
|
|
906
|
+
const authority = await readProjectAuthorityIndex(input.root);
|
|
907
|
+
assertProjectAuthority(await loadProject(input.root, input.projectId), authority);
|
|
795
908
|
const session = await readNativeStageSession(input.root, input.projectId);
|
|
796
909
|
if (session.packet.sessionId !== input.sessionId) {
|
|
797
910
|
throw new CliError("Native stage session ID does not match the active session.", {
|
|
@@ -1264,7 +1377,11 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
|
|
|
1264
1377
|
: null;
|
|
1265
1378
|
const stageOutputTokens = discovery?.plan.outputTokenLimit ?? config.budget.maxOutputTokens;
|
|
1266
1379
|
const reservation = reservePackageBudget(project, workPackage, config, discovery?.plan.reservedDiscoverTokens);
|
|
1267
|
-
const
|
|
1380
|
+
const taskAcceptance = workPackage.stage === "review" ? await compileTaskAcceptanceContext(root, project) : null;
|
|
1381
|
+
if (taskAcceptance?.requirements.some((row) => row.current && row.status === "unanswered")) {
|
|
1382
|
+
throw new CliError("Record an actual check or an honest not-run/inconclusive disposition for each current requirement before spending review budget.", { code: "RESEARCH_TASK_ACCEPTANCE_REQUIRED", exitCode: 3 });
|
|
1383
|
+
}
|
|
1384
|
+
const capsule = await createCapsule(root, project, workPackage, runId, config, taskAcceptance);
|
|
1268
1385
|
capsuleRoot = capsule.capsuleRoot;
|
|
1269
1386
|
capsuleDisposition = capsuleDispositionForHost(config.producer.agent);
|
|
1270
1387
|
retainedCapsuleId =
|
|
@@ -1278,7 +1395,7 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
|
|
|
1278
1395
|
packet: capsule.reviewPacketRecord,
|
|
1279
1396
|
});
|
|
1280
1397
|
}
|
|
1281
|
-
const stageContextContent = await stageContextForPackage(capsule.projectRoot, project, workPackage, config);
|
|
1398
|
+
const stageContextContent = await stageContextForPackage(capsule.projectRoot, project, workPackage, capsule.artifactViews, config);
|
|
1282
1399
|
const route = workPackage.executor === "reviewer" ? config.reviewer : config.producer;
|
|
1283
1400
|
const selectedPackageExecutor = workPackage.executor === "reviewer" && reviewerPackageExecutor
|
|
1284
1401
|
? reviewerPackageExecutor
|
|
@@ -1300,9 +1417,13 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
|
|
|
1300
1417
|
options,
|
|
1301
1418
|
requestId,
|
|
1302
1419
|
purpose: "primary",
|
|
1303
|
-
prompt: packagePrompt(project, workPackage, capsule.inputManifest, capsule.stagedSkills, capsule.capabilityDocumentation, capsule.reviewPacketSha256, capsule.contextBundle, capsule.contextBundleContent, stageContextContent, discovery, await listEvidenceCandidates(root, project.id)) +
|
|
1420
|
+
prompt: packagePrompt(project, workPackage, capsule.inputManifest, capsule.stagedSkills, capsule.capabilityDocumentation, capsule.reviewPacketSha256, capsule.contextBundle, capsule.contextBundleContent, stageContextContent, discovery, await listEvidenceCandidates(root, project.id), "headless-cli") +
|
|
1304
1421
|
(capsule.publicationPolicyDocumentation
|
|
1305
1422
|
? `\n\n${capsule.publicationPolicyDocumentation}`
|
|
1423
|
+
: "") +
|
|
1424
|
+
(capsule.taskAcceptancePrompt ? `\n\n${capsule.taskAcceptancePrompt}` : "") +
|
|
1425
|
+
(workPackage.stage === "review"
|
|
1426
|
+
? `\n\n${artifactReadInstructions(capsule.artifactViews)}`
|
|
1306
1427
|
: ""),
|
|
1307
1428
|
brokerUrl: primaryBrokerUrl,
|
|
1308
1429
|
inputOnlyProvenance,
|
|
@@ -1314,6 +1435,15 @@ async function executeWorkPackage(root, projectId, packageId, config, options, r
|
|
|
1314
1435
|
assertPreCallTokenReservation(project, workPackage, config, primaryRequest, 0, true);
|
|
1315
1436
|
result = await withHeartbeat(selectedPackageExecutor(primaryRequest), options, requestId, project, workPackage, config);
|
|
1316
1437
|
accountedResult = result;
|
|
1438
|
+
if (primaryRequest.artifactViews && result.artifactReads?.length) {
|
|
1439
|
+
await persistArtifactReads(projectRoot(root, project.id), capsule.projectRoot, capsule.artifactViews, primaryRequest.artifactViews.packetSha256, result.artifactReads);
|
|
1440
|
+
await appendJournalEvent(workspacePaths(root).journal, "review.artifacts.read", project.id, sanitizeResearchRecord({
|
|
1441
|
+
requestId,
|
|
1442
|
+
packetSha256: primaryRequest.artifactViews.packetSha256,
|
|
1443
|
+
indexSha256: capsule.artifactViews.sha256,
|
|
1444
|
+
receipts: result.artifactReads.map((receipt) => receipt.receiptSha256),
|
|
1445
|
+
}));
|
|
1446
|
+
}
|
|
1317
1447
|
assertExecutorSucceeded(result);
|
|
1318
1448
|
assertActualPackageBudget(project, workPackage, config, result, stageOutputTokens);
|
|
1319
1449
|
let acceptedRaw = result.stdout;
|
|
@@ -1554,10 +1684,17 @@ async function stagePublicationPolicy(root, project, capsuleProject) {
|
|
|
1554
1684
|
manifestPath: resolveContained(capsuleProject, manifestPath),
|
|
1555
1685
|
documents,
|
|
1556
1686
|
};
|
|
1557
|
-
await writeJsonAtomic(staged.manifestPath,
|
|
1687
|
+
await writeJsonAtomic(staged.manifestPath, {
|
|
1688
|
+
...staged,
|
|
1689
|
+
manifestPath,
|
|
1690
|
+
documents: documents.map((document) => ({
|
|
1691
|
+
...document,
|
|
1692
|
+
path: relative(capsuleProject, document.path).replaceAll("\\", "/"),
|
|
1693
|
+
})),
|
|
1694
|
+
});
|
|
1558
1695
|
return staged;
|
|
1559
1696
|
}
|
|
1560
|
-
async function createCapsule(root, project, workPackage, runId, config) {
|
|
1697
|
+
async function createCapsule(root, project, workPackage, runId, config, taskAcceptance = null) {
|
|
1561
1698
|
const paths = workspacePaths(root);
|
|
1562
1699
|
const capsuleRoot = join(paths.runtime, runId);
|
|
1563
1700
|
const capsuleProject = join(capsuleRoot, "project");
|
|
@@ -1663,33 +1800,65 @@ async function createCapsule(root, project, workPackage, runId, config) {
|
|
|
1663
1800
|
"Approved Research Policy (mandatory for every producer and reviewer decision):",
|
|
1664
1801
|
`resolvedPolicySha256=${publicationPolicy.resolvedPolicySha256}`,
|
|
1665
1802
|
`verdictCeiling=${publicationPolicy.verdictCeiling}`,
|
|
1666
|
-
...publicationPolicy.documents.map((document) => `- ${document.kind}:${document.id} sha256=${document.sha256} path=${document.path}`),
|
|
1803
|
+
...publicationPolicy.documents.map((document) => `- ${document.kind}:${document.id} sha256=${document.sha256} path=${relative(capsuleProject, document.path).replaceAll("\\", "/")}`),
|
|
1667
1804
|
"Read every listed policy document before reasoning. Generic defaults are explicit constraints, not evidence that a target journal will accept the manuscript.",
|
|
1668
1805
|
].join("\n")
|
|
1669
1806
|
: "";
|
|
1670
1807
|
const stagedSkills = await stageLockedCapabilities(root, join(capsuleProject, "skills"));
|
|
1671
|
-
const capabilityDocumentation = await buildCapabilityDocumentation(capsuleProject, stagedSkills
|
|
1808
|
+
const capabilityDocumentation = await buildCapabilityDocumentation(capsuleProject, stagedSkills);
|
|
1809
|
+
await writeTextAtomic(join(capsuleProject, "inputs/capability-documentation.txt"), capabilityDocumentation);
|
|
1672
1810
|
const reviewEvidenceContext = workPackage.stage === "review"
|
|
1673
1811
|
? await writeReviewEvidenceContext(root, project.id, capsuleProject, contextBundleContent, evidenceReceipts, contextualEvidenceArtifacts, config.budget.maxInputContextTokens * RESEARCH_ESTIMATED_BYTES_PER_TOKEN)
|
|
1674
1812
|
: null;
|
|
1813
|
+
const task = taskAcceptance ?? (await taskContext(root, project.id));
|
|
1814
|
+
if (task) {
|
|
1815
|
+
await writeJsonAtomic(join(capsuleProject, "inputs/task-context.json"), task);
|
|
1816
|
+
const source = task.requestProvenance.source;
|
|
1817
|
+
if (source) {
|
|
1818
|
+
const logical = `task/request-sources/${source.objectSha256}.json`;
|
|
1819
|
+
await ensureDirectory(dirname(resolveContained(capsuleProject, logical)));
|
|
1820
|
+
await cp(resolveContained(projectRoot(root, project.id), logical), resolveContained(capsuleProject, logical), { force: false });
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
const taskArtifacts = new Map((taskAcceptance?.results ?? []).map((record) => [record.path, record]));
|
|
1824
|
+
if (task)
|
|
1825
|
+
for (const record of await nativeRunArtifactRecords(root, project.id))
|
|
1826
|
+
taskArtifacts.set(record.path, record);
|
|
1827
|
+
for (const result of taskArtifacts.values()) {
|
|
1828
|
+
await ensureDirectory(dirname(resolveContained(capsuleProject, result.path)));
|
|
1829
|
+
await cp(resolveContained(projectRoot(root, project.id), result.path), resolveContained(capsuleProject, result.path), { force: false });
|
|
1830
|
+
if ((await sha256File(resolveContained(capsuleProject, result.path))) !== result.sha256)
|
|
1831
|
+
throw new CliError("Native run/check artifact changed while staging the packet.", {
|
|
1832
|
+
code: "RESEARCH_TASK_ARTIFACT_DRIFT",
|
|
1833
|
+
exitCode: 3,
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1675
1836
|
const reviewPacket = reviewEvidenceContext
|
|
1676
|
-
? await writeReviewPacket(root, capsuleProject, project, inputManifest, evidenceReceipts, evidenceArtifacts, reviewEvidenceContext.persistent)
|
|
1837
|
+
? await writeReviewPacket(root, capsuleProject, project, inputManifest, evidenceReceipts, evidenceArtifacts, reviewEvidenceContext.persistent, taskAcceptance)
|
|
1677
1838
|
: null;
|
|
1839
|
+
const artifactViews = reviewPacket?.artifactViews ?? (await writeArtifactViewIndex(capsuleProject, project.id));
|
|
1678
1840
|
return {
|
|
1679
1841
|
capsuleRoot,
|
|
1680
1842
|
projectRoot: capsuleProject,
|
|
1681
1843
|
inputManifest,
|
|
1682
1844
|
contextBundle,
|
|
1683
|
-
contextBundleContent,
|
|
1845
|
+
contextBundleContent: await artifactPromptContext(capsuleProject, artifactViews, [
|
|
1846
|
+
"inputs/context-bundle.txt",
|
|
1847
|
+
]),
|
|
1684
1848
|
stagedSkills,
|
|
1685
|
-
capabilityDocumentation,
|
|
1849
|
+
capabilityDocumentation: await artifactPromptContext(capsuleProject, artifactViews, [
|
|
1850
|
+
"inputs/capability-documentation.txt",
|
|
1851
|
+
]),
|
|
1686
1852
|
publicationPolicy,
|
|
1687
1853
|
publicationPolicyDocumentation,
|
|
1688
1854
|
reviewPacketSha256: reviewPacket?.sha256 ?? null,
|
|
1689
1855
|
reviewPacketRecord: reviewPacket?.record ?? null,
|
|
1856
|
+
taskAcceptance,
|
|
1857
|
+
taskAcceptancePrompt: await taskAcceptancePrompt(taskAcceptance, capsuleProject, artifactViews),
|
|
1858
|
+
artifactViews,
|
|
1690
1859
|
};
|
|
1691
1860
|
}
|
|
1692
|
-
async function buildCapabilityDocumentation(capsuleProject, stagedSkills
|
|
1861
|
+
async function buildCapabilityDocumentation(capsuleProject, stagedSkills) {
|
|
1693
1862
|
const documents = [
|
|
1694
1863
|
{
|
|
1695
1864
|
path: "skills/manifest.json",
|
|
@@ -1705,14 +1874,6 @@ async function buildCapabilityDocumentation(capsuleProject, stagedSkills, maxByt
|
|
|
1705
1874
|
const bundle = documents
|
|
1706
1875
|
.map((document) => `--- ${document.path} ---\n${document.content.trimEnd()}`)
|
|
1707
1876
|
.join("\n\n");
|
|
1708
|
-
const bytes = Buffer.byteLength(bundle, "utf8");
|
|
1709
|
-
if (bytes > maxBytes) {
|
|
1710
|
-
throw new CliError("Staged external Skill documentation exceeds the input context budget.", {
|
|
1711
|
-
code: "RESEARCH_CAPABILITY_CONTEXT_BUDGET_EXCEEDED",
|
|
1712
|
-
exitCode: 3,
|
|
1713
|
-
details: { bytes, maxBytes, skills: stagedSkills.map((path) => basename(path)) },
|
|
1714
|
-
});
|
|
1715
|
-
}
|
|
1716
1877
|
return bundle;
|
|
1717
1878
|
}
|
|
1718
1879
|
async function buildInputContextBundle(capsuleProject, inputManifest) {
|
|
@@ -1733,7 +1894,7 @@ async function buildInputContextBundle(capsuleProject, inputManifest) {
|
|
|
1733
1894
|
}
|
|
1734
1895
|
return `${sections.join("\n\n")}\n`;
|
|
1735
1896
|
}
|
|
1736
|
-
async function writeReviewPacket(root, capsuleProject, project, inputManifest, evidenceReceipts, evidenceArtifacts, reviewEvidenceContext) {
|
|
1897
|
+
async function writeReviewPacket(root, capsuleProject, project, inputManifest, evidenceReceipts, evidenceArtifacts, reviewEvidenceContext, taskAcceptance) {
|
|
1737
1898
|
const snapshot = await loadCurrentEvidenceSnapshot(root, project.id);
|
|
1738
1899
|
const immutableSnapshots = await loadImmutableEvidenceSnapshotChain(root, project.id, snapshot.snapshotSha256);
|
|
1739
1900
|
const snapshotChain = await Promise.all(immutableSnapshots.map((immutableSnapshot) => {
|
|
@@ -1778,6 +1939,8 @@ async function writeReviewPacket(root, capsuleProject, project, inputManifest, e
|
|
|
1778
1939
|
}
|
|
1779
1940
|
}
|
|
1780
1941
|
await writeJsonAtomic(join(capsuleProject, "inputs", "runtime-fingerprint.json"), environment);
|
|
1942
|
+
const artifactViews = await writeArtifactViewIndex(capsuleProject, project.id);
|
|
1943
|
+
const persistedViews = await persistArtifactViewIndex(projectRoot(root, project.id), capsuleProject, artifactViews);
|
|
1781
1944
|
const packet = {
|
|
1782
1945
|
schemaVersion: 1,
|
|
1783
1946
|
projectId: project.id,
|
|
@@ -1790,6 +1953,8 @@ async function writeReviewPacket(root, capsuleProject, project, inputManifest, e
|
|
|
1790
1953
|
parentSnapshotSha256: snapshot.parentSnapshotSha256,
|
|
1791
1954
|
},
|
|
1792
1955
|
snapshotChain,
|
|
1956
|
+
taskAcceptance,
|
|
1957
|
+
artifactViews: persistedViews,
|
|
1793
1958
|
inputs: inputManifest,
|
|
1794
1959
|
reviewEvidenceContext,
|
|
1795
1960
|
inputFiles: [...inputFiles.values()].sort((left, right) => left.path.localeCompare(right.path)),
|
|
@@ -1809,7 +1974,7 @@ async function writeReviewPacket(root, capsuleProject, project, inputManifest, e
|
|
|
1809
1974
|
};
|
|
1810
1975
|
await writeJsonAtomic(join(capsuleProject, "inputs", "review-packet.json"), completePacket);
|
|
1811
1976
|
const record = await persistReviewPacket(root, project.id, completePacket, packetSha256);
|
|
1812
|
-
return { sha256: packetSha256, record };
|
|
1977
|
+
return { sha256: packetSha256, record, artifactViews };
|
|
1813
1978
|
}
|
|
1814
1979
|
async function writeReviewEvidenceContext(root, projectId, capsuleProject, inputContextBundle, evidenceReceipts, evidenceArtifacts, maxBytes) {
|
|
1815
1980
|
const header = [
|
|
@@ -1891,15 +2056,10 @@ async function writeReviewEvidenceContext(root, projectId, capsuleProject, input
|
|
|
1891
2056
|
...views.map((view) => `${view.prefix}${view.active ? "" : view.content}${view.suffix}`),
|
|
1892
2057
|
].join("\n\n");
|
|
1893
2058
|
const fixedBytes = Buffer.byteLength(`${fixedContent}\n`, "utf8");
|
|
1894
|
-
if (fixedBytes > maxBytes) {
|
|
1895
|
-
throw new CliError("Review evidence metadata exceeds the configured context budget.", {
|
|
1896
|
-
code: "RESEARCH_REVIEW_CONTEXT_BUDGET_EXCEEDED",
|
|
1897
|
-
exitCode: 3,
|
|
1898
|
-
details: { fixedBytes, maxBytes, views: views.length },
|
|
1899
|
-
});
|
|
1900
|
-
}
|
|
1901
2059
|
const activeViews = views.filter((view) => view.active).length;
|
|
1902
|
-
const contentBudgetPerView = activeViews
|
|
2060
|
+
const contentBudgetPerView = activeViews
|
|
2061
|
+
? Math.max(0, Math.floor((maxBytes - fixedBytes) / activeViews))
|
|
2062
|
+
: 0;
|
|
1903
2063
|
const sections = [
|
|
1904
2064
|
header,
|
|
1905
2065
|
...views.map((view) => `${view.prefix}${view.active ? boundedUtf8ReviewExcerpt(view.content, contentBudgetPerView) : view.content}${view.suffix}`),
|
|
@@ -1907,14 +2067,6 @@ async function writeReviewEvidenceContext(root, projectId, capsuleProject, input
|
|
|
1907
2067
|
const logicalPath = "inputs/review-evidence-context.txt";
|
|
1908
2068
|
const path = resolveContained(capsuleProject, logicalPath);
|
|
1909
2069
|
const content = `${sections.join("\n\n")}\n`;
|
|
1910
|
-
const actualBytes = Buffer.byteLength(content, "utf8");
|
|
1911
|
-
if (actualBytes > maxBytes) {
|
|
1912
|
-
throw new CliError("Review evidence context exceeded its deterministic byte budget.", {
|
|
1913
|
-
code: "RESEARCH_REVIEW_CONTEXT_BUDGET_EXCEEDED",
|
|
1914
|
-
exitCode: 3,
|
|
1915
|
-
details: { actualBytes, maxBytes },
|
|
1916
|
-
});
|
|
1917
|
-
}
|
|
1918
2070
|
await writeTextAtomic(path, content);
|
|
1919
2071
|
const capsule = await fileRecord(path, logicalPath);
|
|
1920
2072
|
const persistentLogicalPath = `review/contexts/${capsule.sha256}.txt`;
|
|
@@ -1966,15 +2118,8 @@ async function writeProducerArtifactContext(capsuleProject, evidenceArtifacts, m
|
|
|
1966
2118
|
.map((view) => `${view.prefix}${view.active ? "" : view.content}${view.suffix}`)
|
|
1967
2119
|
.join("\n\n")}\n`;
|
|
1968
2120
|
const fixedBytes = Buffer.byteLength(fixed, "utf8");
|
|
1969
|
-
if (fixedBytes > maxBytes) {
|
|
1970
|
-
throw new CliError("Producer artifact metadata exceeds the configured context budget.", {
|
|
1971
|
-
code: "RESEARCH_INPUT_CONTEXT_BUDGET_EXCEEDED",
|
|
1972
|
-
exitCode: 3,
|
|
1973
|
-
details: { fixedBytes, maxBytes, artifacts: views.length },
|
|
1974
|
-
});
|
|
1975
|
-
}
|
|
1976
2121
|
const activeViews = views.filter((view) => view.active).length;
|
|
1977
|
-
const perView = activeViews ? Math.floor((maxBytes - fixedBytes) / activeViews) : 0;
|
|
2122
|
+
const perView = activeViews ? Math.max(0, Math.floor((maxBytes - fixedBytes) / activeViews)) : 0;
|
|
1978
2123
|
const content = `${header}\n\n${views
|
|
1979
2124
|
.map((view) => `${view.prefix}${view.active ? boundedUtf8ReviewExcerpt(view.content, perView) : view.content}${view.suffix}`)
|
|
1980
2125
|
.join("\n\n")}\n`;
|
|
@@ -2130,11 +2275,18 @@ async function persistReviewPacket(root, projectId, packet, packetSha256) {
|
|
|
2130
2275
|
}
|
|
2131
2276
|
return fileRecord(path, logicalPath);
|
|
2132
2277
|
}
|
|
2133
|
-
async function loadVerifiedReviewPacket(root, projectId, packetSha256) {
|
|
2278
|
+
export async function loadVerifiedReviewPacket(root, projectId, packetSha256) {
|
|
2134
2279
|
const logicalPath = `review/packets/${packetSha256}.json`;
|
|
2135
2280
|
const path = resolveContained(projectRoot(root, projectId), logicalPath);
|
|
2136
2281
|
const packet = await readJsonFile(path, "Research review packet");
|
|
2137
2282
|
verifyReviewPacketValue(packet, packetSha256);
|
|
2283
|
+
if (packet.projectId !== projectId)
|
|
2284
|
+
throw new CliError("Review packet belongs to another project.", {
|
|
2285
|
+
code: "RESEARCH_REVIEW_PACKET_DRIFT",
|
|
2286
|
+
exitCode: 3,
|
|
2287
|
+
});
|
|
2288
|
+
if (packet.artifactViews !== undefined)
|
|
2289
|
+
await verifyPersistedArtifactViewIndex(projectRoot(root, projectId), projectId, packet.artifactViews);
|
|
2138
2290
|
const context = packet.reviewEvidenceContext;
|
|
2139
2291
|
if (!isObject(context) ||
|
|
2140
2292
|
typeof context.path !== "string" ||
|
|
@@ -2286,18 +2438,33 @@ function reviewSafeReceipt(receipt) {
|
|
|
2286
2438
|
};
|
|
2287
2439
|
}
|
|
2288
2440
|
function agentRequest(input) {
|
|
2289
|
-
const
|
|
2441
|
+
const packetRead = input.purpose === "primary" &&
|
|
2442
|
+
input.workPackage.stage === "review" &&
|
|
2443
|
+
input.capsule.reviewPacketSha256 !== null;
|
|
2444
|
+
const toolPolicy = packetRead ? "packet-read" : "none";
|
|
2290
2445
|
const maxTurns = input.purpose === "repair"
|
|
2291
2446
|
? RESEARCH_REPAIR_MAX_TURNS
|
|
2292
|
-
:
|
|
2293
|
-
?
|
|
2294
|
-
:
|
|
2447
|
+
: packetRead
|
|
2448
|
+
? RESEARCH_PACKET_READ_MAX_TURNS
|
|
2449
|
+
: input.brokerUrl
|
|
2450
|
+
? RESEARCH_BROKER_MAX_TURNS
|
|
2451
|
+
: researchStructuredOutputMaxTurns(input.route);
|
|
2295
2452
|
return {
|
|
2296
2453
|
route: input.route,
|
|
2297
2454
|
prompt: input.prompt,
|
|
2298
|
-
outputSchema: schemaForStage(input.workPackage.stage, input.capsule.reviewPacketSha256,
|
|
2299
|
-
|
|
2300
|
-
|
|
2455
|
+
outputSchema: schemaForStage(input.workPackage.stage, input.capsule.reviewPacketSha256, {
|
|
2456
|
+
...(input.inputOnlyProvenance
|
|
2457
|
+
? { inputOnlyProvenanceIds: input.capsule.inputManifest.map((record) => record.id) }
|
|
2458
|
+
: {}),
|
|
2459
|
+
...(input.capsule.taskAcceptance
|
|
2460
|
+
? {
|
|
2461
|
+
taskAcceptance: {
|
|
2462
|
+
contextSha256: input.capsule.taskAcceptance.contextSha256,
|
|
2463
|
+
requirementSha256s: input.capsule.taskAcceptance.requirements.map((row) => row.requirementSha256),
|
|
2464
|
+
},
|
|
2465
|
+
}
|
|
2466
|
+
: {}),
|
|
2467
|
+
}),
|
|
2301
2468
|
requestId: input.requestId,
|
|
2302
2469
|
purpose: input.purpose,
|
|
2303
2470
|
capsuleRoot: input.capsule.capsuleRoot,
|
|
@@ -2306,11 +2473,22 @@ function agentRequest(input) {
|
|
|
2306
2473
|
timeoutSeconds: Math.min(remainingWallSeconds(input.project, input.config), input.maxWallSeconds ??
|
|
2307
2474
|
input.config.budget.packageMaxWallSeconds[input.workPackage.stage]),
|
|
2308
2475
|
maxTurns,
|
|
2476
|
+
...(packetRead
|
|
2477
|
+
? {
|
|
2478
|
+
reservationTurns: researchStructuredOutputMaxTurns(input.route),
|
|
2479
|
+
artifactViews: {
|
|
2480
|
+
index: input.capsule.artifactViews,
|
|
2481
|
+
packetSha256: input.capsule.reviewPacketSha256,
|
|
2482
|
+
},
|
|
2483
|
+
}
|
|
2484
|
+
: {}),
|
|
2309
2485
|
maxOutputTokens: input.maxOutputTokens,
|
|
2310
2486
|
maxToolContextTokens: input.brokerUrl
|
|
2311
2487
|
? input.config.budget.maxBrokerContextTokens *
|
|
2312
2488
|
(input.brokerCallBudget ?? input.config.budget.maxBrokerCalls)
|
|
2313
|
-
:
|
|
2489
|
+
: packetRead
|
|
2490
|
+
? RESEARCH_EXPECTED_ARTIFACT_READ_TOKENS
|
|
2491
|
+
: 0,
|
|
2314
2492
|
maxCostUsd: input.maxCostUsd,
|
|
2315
2493
|
expectedRuntime: input.expectedRuntime,
|
|
2316
2494
|
toolPolicy,
|
|
@@ -2400,6 +2578,7 @@ async function validateOutputShape(root, project, workPackage, path, reviewPacke
|
|
|
2400
2578
|
await validateAnalysis(path, value);
|
|
2401
2579
|
}
|
|
2402
2580
|
if (workPackage.stage === "review") {
|
|
2581
|
+
validateTaskReview(value, await compileTaskAcceptanceContext(root, project));
|
|
2403
2582
|
if (value.decision !== "pass") {
|
|
2404
2583
|
throw new CliError("Independent review requested revision.", {
|
|
2405
2584
|
code: "RESEARCH_REVIEW_REVISION_REQUIRED",
|
|
@@ -2514,23 +2693,10 @@ async function validateAnalysis(path, value) {
|
|
|
2514
2693
|
inputArtifactSha256s.some((sha256) => !allowedInputSha256s.has(sha256))) {
|
|
2515
2694
|
throw new StructuredOutputError("Analysis run refers to an artifact outside the inference snapshot.");
|
|
2516
2695
|
}
|
|
2517
|
-
if (run
|
|
2518
|
-
|
|
2519
|
-
run
|
|
2520
|
-
|
|
2521
|
-
implementationSha256s.length > 0 ||
|
|
2522
|
-
environmentSha256s.length > 0) {
|
|
2523
|
-
throw new StructuredOutputError("Qualitative analysis run metadata is inconsistent.");
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
else if (run.status !== "reproduced" ||
|
|
2527
|
-
typeof run.command !== "string" ||
|
|
2528
|
-
run.command.trim().length < 1 ||
|
|
2529
|
-
typeof run.randomSeed !== "string" ||
|
|
2530
|
-
run.randomSeed.trim().length < 1 ||
|
|
2531
|
-
implementationSha256s.length < 1 ||
|
|
2532
|
-
environmentSha256s.length < 1) {
|
|
2533
|
-
throw new StructuredOutputError("Computational analysis requires exact reproduced run metadata.");
|
|
2696
|
+
if (!isConsistentAnalysisRunMetadata(run)) {
|
|
2697
|
+
throw new StructuredOutputError(run.mode === "qualitative"
|
|
2698
|
+
? "Qualitative analysis run metadata is inconsistent."
|
|
2699
|
+
: "Computational analysis requires exact reproduced run metadata.");
|
|
2534
2700
|
}
|
|
2535
2701
|
const findings = value.findings;
|
|
2536
2702
|
const findingIds = new Set();
|
|
@@ -2872,6 +3038,7 @@ async function closeProjectMechanically(root, project, workPackage) {
|
|
|
2872
3038
|
throw deterministicError("Project review does not bind a valid review packet hash.");
|
|
2873
3039
|
}
|
|
2874
3040
|
const reviewPacket = await loadVerifiedReviewPacket(root, project.id, review.packetSha256);
|
|
3041
|
+
validateTaskReview(review, await compileTaskAcceptanceContext(root, project));
|
|
2875
3042
|
await verifyReviewLedgerBinding(root, project.id, snapshot.snapshotId, review.packetSha256);
|
|
2876
3043
|
const evidenceReceipts = await loadProjectEvidenceReceipts(root, project.id);
|
|
2877
3044
|
const journal = await verifyJournal(workspacePaths(root).journal);
|
|
@@ -3038,7 +3205,7 @@ async function verifyProjectInputBindings(project) {
|
|
|
3038
3205
|
async function outputRecords(root, project, logicalPaths) {
|
|
3039
3206
|
return Promise.all(logicalPaths.map((logicalPath) => fileRecord(resolveContained(projectRoot(root, project.id), logicalPath), logicalPath)));
|
|
3040
3207
|
}
|
|
3041
|
-
async function stageContextForPackage(capsuleProject, project, workPackage, config) {
|
|
3208
|
+
async function stageContextForPackage(capsuleProject, project, workPackage, index, config) {
|
|
3042
3209
|
let analyzeFallbackContext = [];
|
|
3043
3210
|
if (workPackage.stage === "analyze") {
|
|
3044
3211
|
const inference = JSON.parse(await readFile(resolveContained(capsuleProject, "outputs/inference-snapshot.json"), "utf8"));
|
|
@@ -3073,41 +3240,21 @@ async function stageContextForPackage(capsuleProject, project, workPackage, conf
|
|
|
3073
3240
|
"outputs/report.md",
|
|
3074
3241
|
]
|
|
3075
3242
|
: [];
|
|
3076
|
-
|
|
3077
|
-
for (const logicalPath of logicalPaths) {
|
|
3078
|
-
const content = await readFile(resolveContained(capsuleProject, logicalPath), "utf8");
|
|
3079
|
-
sections.push(`### ${logicalPath}\n${content.trimEnd()}`);
|
|
3080
|
-
}
|
|
3081
|
-
const bundled = sections.join("\n\n");
|
|
3082
|
-
const estimatedTokens = Math.ceil(Buffer.byteLength(bundled, "utf8") / RESEARCH_ESTIMATED_BYTES_PER_TOKEN);
|
|
3083
|
-
const maxStageContextTokens = researchStageContextTokenLimit(config, workPackage.stage, project.lineage.kind === "addendum");
|
|
3084
|
-
if (estimatedTokens > maxStageContextTokens) {
|
|
3085
|
-
throw new CliError(`Admitted stage context exceeds the configured input context limit for ${workPackage.id}.`, {
|
|
3086
|
-
code: "RESEARCH_INPUT_CONTEXT_BUDGET_EXCEEDED",
|
|
3087
|
-
exitCode: 3,
|
|
3088
|
-
details: {
|
|
3089
|
-
packageId: workPackage.id,
|
|
3090
|
-
estimatedTokens,
|
|
3091
|
-
maxStageContextTokens,
|
|
3092
|
-
limitField: "budget.maxInputContextTokens",
|
|
3093
|
-
contextPaths: logicalPaths,
|
|
3094
|
-
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.",
|
|
3095
|
-
},
|
|
3096
|
-
});
|
|
3097
|
-
}
|
|
3098
|
-
return bundled;
|
|
3243
|
+
return artifactPromptContext(capsuleProject, index, logicalPaths, Math.min(8_000, config.budget.maxInputContextTokens) * RESEARCH_ESTIMATED_BYTES_PER_TOKEN);
|
|
3099
3244
|
}
|
|
3100
|
-
function packagePrompt(project, workPackage, inputs, stagedSkills, capabilityDocumentation, reviewPacketSha256, contextBundle, contextBundleContent, stageContextContent, discovery, evidenceCandidates) {
|
|
3245
|
+
function packagePrompt(project, workPackage, inputs, stagedSkills, capabilityDocumentation, reviewPacketSha256, contextBundle, contextBundleContent, stageContextContent, discovery, evidenceCandidates, executionMode) {
|
|
3101
3246
|
const stageInstructions = {
|
|
3102
3247
|
discover: "Assess candidates incrementally through the packet's recordAssessment command; do not accumulate a source-sized final response. The control plane has already assigned every immutable input, broker result, and structured data result a candidateId and retains its title, URL, DOI, dates, receipt, locator, JSON Pointer, hashes, and retrieval metadata. Reference candidateId; never repeat or invent those deterministic fields. Give each admitted candidate a concise sourceId plus source type, relevance, quality, applicability, coverage dimensions, and limitations. Record meaningful explicit rejections; omitted candidates remain unassessed for later gap filling. Native Web or Browser discoveries are supplemental candidates only and cannot be admitted until an immutable broker receipt is attached to the same canonical URL or DOI. After broad search, strict assessment, and focused gap filling, return only the small closeout object with one judgment for every reviewed dimension plus limitations and remaining gaps. The CLI mechanically joins the latest recorded assessments to provenance, derives counts/date range/coverage, and rejects unknown or unformalized candidates.",
|
|
3103
3248
|
acquire: "Audit every provisionally admitted source exactly once. For each source, bind its ledger candidateId, list only artifactIds returned by the exact artifact registration command, and choose accepted, limited, or rejected with a concise rationale and explicit limitations. A broker receipt is an immutable discovery record but is not full text. Use an empty artifactIds array only when intentionally retaining a source as metadata/abstract evidence or when the source is an already registered local input. Put unresolved blocking acquisition or coverage deficiencies in gaps; put honest non-blocking scope constraints in limitations. Do not invent file paths, hashes, URLs, artifact IDs, or successful downloads.",
|
|
3104
|
-
analyze: "Use only the
|
|
3105
|
-
synthesize: "Use only the
|
|
3106
|
-
review: `Independently inspect the
|
|
3249
|
+
analyze: "Use only the admitted inference snapshot and its packet-bound on-demand artifacts; return analysis schema v2. Bind the exact inference snapshot hash and one reproducible analysisRun. Every finding must cite admitted source IDs and exact evidenceAtomIds; a top-journal finding must also bind design claimIds. State uncertainty and applicability. Never promote a source-level citation when no exact atom supports the statement.",
|
|
3250
|
+
synthesize: "Use only the admitted inference snapshot, analysis, and Claim–Evidence Graph, reading their complete packet-bound objects on demand. Return the schema-defined object whose reportMarkdown separates supported conclusions, uncertainty, limitations, and next actions. Every material statement must remain within a graph-bound finding. Use real Markdown line breaks encoded exactly once for JSON; never place literal /n or double-escaped \\n markers in reportMarkdown.",
|
|
3251
|
+
review: `Independently inspect the supplied excerpts and use the packet-only artifact tools for further material, including failed checks and counterevidence. Every full object's size and SHA-256 is bound; metadata or a digest is not proof that you read it. Return the schema-defined review bound to packetSha256 ${reviewPacketSha256 ?? "unavailable"}. Use pass only after sufficient inspection supports every material claim and its stated limitations.`,
|
|
3107
3252
|
close: "No agent action is allowed for mechanical closure.",
|
|
3108
3253
|
};
|
|
3109
3254
|
const prompt = [
|
|
3110
|
-
|
|
3255
|
+
executionMode === "native-host"
|
|
3256
|
+
? "Operate in the current native host with this project's authorized inputs and packet operations; host permissions still apply."
|
|
3257
|
+
: "Operate only inside this isolated research capsule.",
|
|
3111
3258
|
`Project: ${project.id}`,
|
|
3112
3259
|
`Question: ${project.question}`,
|
|
3113
3260
|
`Stage: ${workPackage.stage}`,
|
|
@@ -3116,15 +3263,19 @@ function packagePrompt(project, workPackage, inputs, stagedSkills, capabilityDoc
|
|
|
3116
3263
|
`Bounded input context bundle: ${JSON.stringify(contextBundle)}`,
|
|
3117
3264
|
`Staged capability directories: ${JSON.stringify(stagedSkills.map((path) => `skills/${basename(path)}`))}`,
|
|
3118
3265
|
workPackage.stage === "discover"
|
|
3119
|
-
? "The exact capability manifest and each staged external SKILL.md are
|
|
3266
|
+
? "The exact capability manifest and each staged external SKILL.md are supplied inline or through the native packet's artifact read commands. Read the required documentation; do not launch its standalone commands for broker work."
|
|
3120
3267
|
: workPackage.stage === "acquire"
|
|
3121
3268
|
? "Use the installed external acquisition/document Skills in the current native host, but treat the CLI artifact registry and acquisition schema as the only authority for durable evidence."
|
|
3122
3269
|
: "Capability files are provenance-bound but are not available as execution tools in this stage.",
|
|
3123
3270
|
workPackage.stage === "discover"
|
|
3124
3271
|
? `Follow the reviewed discovery plan: ${JSON.stringify(discovery)}. The plan's max evidence-call count is a hard working ceiling, not a target to exhaust. Execute required first-pass channels before supplemental channels, prefer broad high-yield queries, assess registered candidates between batches, and use the next gap-fill batch only for explicit uncovered dimensions, source types, date ranges, full text, limitations, or counterevidence. Stop fetching as soon as the declared coverage minimums are supportable. Native Web/Browser may broaden lead discovery, but every such action must be recorded through recordActivity, every useful result must be registered as a candidate, and the same URL/DOI must then be formalized through the broker before admission. A broker receipt, structured data-runtime receipt, or immutable registered input is an admissible evidence path. Do not execute a staged Skill's curl/CLI examples or read provider environment variables. For generic broker capabilities, invoke fetch_candidate_source with the manifest capability ID and obey its exact declared HTTP method. For structured data capabilities, use the packet's dynamic catalog and describe command, then invoke runDataCapability; never call standalone data run for project evidence. Never place API keys, tokens, authorization data, cookies, or other credential-like fields in request files. The Research control plane injects declared logical credentials and persists only safe hash-bound results. Exercise every manifest capability with requiredForDiscovery=true and every project-required data capability ID, or the mechanical coverage gate will stop downstream work.`
|
|
3125
|
-
:
|
|
3272
|
+
: executionMode === "native-host" && workPackage.stage === "acquire"
|
|
3273
|
+
? "Acquire files and readable derivatives only for provisionally admitted sources using the packet's bindDownload and registerArtifact commands. Do not reopen discovery, admit new sources, or access unrelated host files."
|
|
3274
|
+
: "Use only admitted stage context and this packet's read-only artifact channel. No new evidence acquisition or arbitrary host-file access is authorized.",
|
|
3126
3275
|
stageInstructions[workPackage.stage],
|
|
3127
|
-
|
|
3276
|
+
executionMode === "native-host"
|
|
3277
|
+
? "Do not write admitted output paths or control-plane files directly. Save only the final schema-conforming JSON object to a new regular file, then submit it with the packet's submit command. The CLI remains the sole authority for validation and atomic promotion."
|
|
3278
|
+
: "Do not write stage output files directly. Your final response must be only the JSON object required by the supplied output schema; the CLI will validate and atomically materialize it.",
|
|
3128
3279
|
"Do not edit project.json, input manifests, prior outputs, evidence objects, or staged capability files.",
|
|
3129
3280
|
];
|
|
3130
3281
|
if (workPackage.stage === "discover") {
|
|
@@ -3136,10 +3287,10 @@ function packagePrompt(project, workPackage, inputs, stagedSkills, capabilityDoc
|
|
|
3136
3287
|
publicationDate: candidate.publicationDate,
|
|
3137
3288
|
excerpt: candidate.excerpt,
|
|
3138
3289
|
originKind: candidate.origin.kind,
|
|
3139
|
-
})))}`, "
|
|
3290
|
+
})))}`, "External capability documentation (inline or exact on-demand reference):", capabilityDocumentation, "Authorized local-input context (inline or exact on-demand reference). Full evidence files are intentionally withheld from producer packages when fullTextStaged=false; the read channel does not widen that authorization.", contextBundleContent);
|
|
3140
3291
|
}
|
|
3141
3292
|
if (stageContextContent) {
|
|
3142
|
-
prompt.push("
|
|
3293
|
+
prompt.push("Admitted stage context (inline or exact on-demand reference; no material is discarded to meet an embedding preference):", stageContextContent);
|
|
3143
3294
|
}
|
|
3144
3295
|
return prompt.join("\n\n");
|
|
3145
3296
|
}
|
|
@@ -3160,7 +3311,7 @@ function assertPreCallTokenReservation(project, workPackage, config, request, al
|
|
|
3160
3311
|
route: request.route,
|
|
3161
3312
|
primaryPayloadTokens: Math.ceil((schemaBytes + promptBytes) / RESEARCH_ESTIMATED_BYTES_PER_TOKEN),
|
|
3162
3313
|
repairPayloadTokens: Math.ceil((schemaBytes + RESEARCH_MAX_REPAIR_SOURCE_BYTES + 2_048) / RESEARCH_ESTIMATED_BYTES_PER_TOKEN),
|
|
3163
|
-
maxTurns: request.maxTurns,
|
|
3314
|
+
maxTurns: request.reservationTurns ?? request.maxTurns,
|
|
3164
3315
|
maxOutputTokens: request.maxOutputTokens,
|
|
3165
3316
|
maxToolContextTokens: request.maxToolContextTokens ?? 0,
|
|
3166
3317
|
maxRepairTokens: config.budget.maxRepairTokens,
|
|
@@ -3529,9 +3680,20 @@ function projectRunSummary(root, project) {
|
|
|
3529
3680
|
usage: project.usage,
|
|
3530
3681
|
};
|
|
3531
3682
|
}
|
|
3532
|
-
async function summarizeRun(root, requestId, cycles, executed, maxCycles, projectId) {
|
|
3533
|
-
|
|
3534
|
-
const
|
|
3683
|
+
async function summarizeRun(root, requestId, cycles, executed, maxCycles, projectId, authority) {
|
|
3684
|
+
// One fresh journal view after execution includes newly committed reviewer results.
|
|
3685
|
+
const summaryAuthority = executed.length
|
|
3686
|
+
? await readProjectAuthorityIndex(root)
|
|
3687
|
+
: (authority ?? (await readProjectAuthorityIndex(root)));
|
|
3688
|
+
const projects = await projectsForRun(root, projectId, summaryAuthority);
|
|
3689
|
+
const summaries = await Promise.all(projects.map(async (project) => {
|
|
3690
|
+
const summary = projectRunSummary(root, project);
|
|
3691
|
+
const events = summaryAuthority.taskEvents.get(project.id);
|
|
3692
|
+
if (!events)
|
|
3693
|
+
return summary;
|
|
3694
|
+
const task = await inspectProjectTask(root, project.id, events);
|
|
3695
|
+
return task.status === "configured" ? { ...summary, task } : summary;
|
|
3696
|
+
}));
|
|
3535
3697
|
const unfinished = summaries.filter((project) => project.status !== "complete");
|
|
3536
3698
|
const waiting = unfinished.filter((project) => project.status === "waiting-user" || project.status === "waiting-external");
|
|
3537
3699
|
const hasReadyPackage = summaries.some((project) => project.readyPackage !== null);
|
|
@@ -3583,27 +3745,16 @@ async function summarizeRun(root, requestId, cycles, executed, maxCycles, projec
|
|
|
3583
3745
|
projects: summaries,
|
|
3584
3746
|
};
|
|
3585
3747
|
}
|
|
3586
|
-
async function projectsForRun(root, projectId) {
|
|
3748
|
+
async function projectsForRun(root, projectId, knownAuthority) {
|
|
3749
|
+
const authority = knownAuthority ?? (await readProjectAuthorityIndex(root));
|
|
3587
3750
|
if (projectId) {
|
|
3588
|
-
const project = await loadProject(root, projectId);
|
|
3589
|
-
|
|
3590
|
-
project.status === "archived" ||
|
|
3591
|
-
project.status === "abandoned") {
|
|
3592
|
-
throw new CliError(`Research project ${projectId} is historical and cannot be executed.`, {
|
|
3593
|
-
code: "RESEARCH_PROJECT_NOT_AUTHORITATIVE",
|
|
3594
|
-
exitCode: 3,
|
|
3595
|
-
details: {
|
|
3596
|
-
projectId,
|
|
3597
|
-
status: project.status,
|
|
3598
|
-
authoritativeProjectId: project.lineage.supersededBy ?? null,
|
|
3599
|
-
},
|
|
3600
|
-
});
|
|
3601
|
-
}
|
|
3751
|
+
const project = projectWithEffectiveAuthority(await loadProject(root, projectId), authority);
|
|
3752
|
+
assertProjectAuthority(project, authority);
|
|
3602
3753
|
return [project];
|
|
3603
3754
|
}
|
|
3604
|
-
return (await listProjects(root))
|
|
3605
|
-
project.
|
|
3606
|
-
project
|
|
3755
|
+
return (await listProjects(root, authority))
|
|
3756
|
+
.filter((project) => projectAuthority(project, authority).state === "authoritative")
|
|
3757
|
+
.map((project) => projectWithEffectiveAuthority(project, authority));
|
|
3607
3758
|
}
|
|
3608
3759
|
function assertPublicEvidenceUrl(value, sourceId) {
|
|
3609
3760
|
let url;
|
|
@@ -3704,6 +3855,7 @@ async function nativeStageBinding(root, project, workPackage) {
|
|
|
3704
3855
|
return sha256Text(canonicalJson({
|
|
3705
3856
|
projectId: project.id,
|
|
3706
3857
|
questionSha256: sha256Text(project.question),
|
|
3858
|
+
taskContractSha256: (await taskContext(root, project.id))?.contractSha256 ?? null,
|
|
3707
3859
|
evidenceRequirements: project.evidenceRequirements,
|
|
3708
3860
|
inputs: project.inputs.map((record) => ({
|
|
3709
3861
|
id: record.id,
|