@zq-silk/yui 2.1.0 → 2.2.0

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 (102) hide show
  1. package/README.md +4 -0
  2. package/dist/cli/commandCatalog.js +24 -3
  3. package/dist/cli/commandDiscovery.js +4 -1
  4. package/dist/cli.js +23 -3078
  5. package/dist/commands/taskCommands.js +79 -21
  6. package/dist/commands/taskIntegrationCommands.js +3 -1
  7. package/dist/commands/taskUpstreamCommands.js +3 -1
  8. package/dist/context/runContextPack.js +132 -5
  9. package/dist/context/sourceRunContext.js +4 -2
  10. package/dist/context/taskContext.js +6 -1
  11. package/dist/controlPlaneCli.js +3092 -0
  12. package/dist/controller/fileSchedulerStoreAdapter.js +19 -6
  13. package/dist/controller/jobControl.js +21 -0
  14. package/dist/executor/agentExecutor.js +1 -1
  15. package/dist/executor/effectiveLaunch.js +18 -5
  16. package/dist/executor/fileRoleLaunchPlanner.js +15 -19
  17. package/dist/integration/gitIntegrationService.js +50 -15
  18. package/dist/message/messageContinuation.js +7 -2
  19. package/dist/nativeAgent/agent.js +176 -74
  20. package/dist/nativeAgent/cliDemo.js +37 -0
  21. package/dist/nativeAgent/codingTools.js +11 -0
  22. package/dist/nativeAgent/commandTool.js +215 -0
  23. package/dist/nativeAgent/compactionDemo.js +158 -0
  24. package/dist/nativeAgent/composition.js +71 -0
  25. package/dist/nativeAgent/context/budget.js +44 -0
  26. package/dist/nativeAgent/context/index.js +339 -0
  27. package/dist/nativeAgent/context/providerCompressor.js +103 -0
  28. package/dist/nativeAgent/demo.js +12 -1
  29. package/dist/nativeAgent/evaluation/cases.js +38 -0
  30. package/dist/nativeAgent/evaluation/checks.js +91 -0
  31. package/dist/nativeAgent/evaluation/demo.js +19 -0
  32. package/dist/nativeAgent/evaluation/files.js +54 -0
  33. package/dist/nativeAgent/evaluation/fixture.js +36 -0
  34. package/dist/nativeAgent/evaluation/index.js +239 -0
  35. package/dist/nativeAgent/executionOwner.js +209 -0
  36. package/dist/nativeAgent/filePatterns.js +170 -0
  37. package/dist/nativeAgent/fileToolsSupport.js +202 -0
  38. package/dist/nativeAgent/index.js +13 -0
  39. package/dist/nativeAgent/interaction/cli.js +358 -0
  40. package/dist/nativeAgent/interaction/contracts.js +1 -0
  41. package/dist/nativeAgent/interaction/index.js +3 -0
  42. package/dist/nativeAgent/interaction/memoryDemo.js +115 -0
  43. package/dist/nativeAgent/interaction/renderer.js +34 -0
  44. package/dist/nativeAgent/localSafety.js +258 -0
  45. package/dist/nativeAgent/model/anthropicMessages.js +204 -0
  46. package/dist/nativeAgent/model/chatCompletions.js +210 -0
  47. package/dist/nativeAgent/model/errors.js +47 -0
  48. package/dist/nativeAgent/model/gateway.js +423 -0
  49. package/dist/nativeAgent/model/index.js +7 -0
  50. package/dist/nativeAgent/model/observationAdapter.js +21 -0
  51. package/dist/nativeAgent/model/protocols.js +19 -0
  52. package/dist/nativeAgent/model/responses.js +263 -0
  53. package/dist/nativeAgent/model/types.js +1 -0
  54. package/dist/nativeAgent/model/wire.js +73 -0
  55. package/dist/nativeAgent/observability/index.js +220 -0
  56. package/dist/nativeAgent/product/catalog.js +82 -0
  57. package/dist/nativeAgent/product/config.js +295 -0
  58. package/dist/nativeAgent/product/facts.js +30 -0
  59. package/dist/nativeAgent/product/index.js +62 -0
  60. package/dist/nativeAgent/product/location.js +44 -0
  61. package/dist/nativeAgent/product/runtime.js +276 -0
  62. package/dist/nativeAgent/product/storage.js +49 -0
  63. package/dist/nativeAgent/product/tools.js +47 -0
  64. package/dist/nativeAgent/product/transport.js +54 -0
  65. package/dist/nativeAgent/projectGuidance/index.js +425 -0
  66. package/dist/nativeAgent/searchTools.js +305 -0
  67. package/dist/nativeAgent/session/backends.js +293 -0
  68. package/dist/nativeAgent/session/catalog.js +97 -0
  69. package/dist/nativeAgent/session/catalogDemo.js +87 -0
  70. package/dist/nativeAgent/session/contracts.js +1 -0
  71. package/dist/nativeAgent/session/format.js +269 -0
  72. package/dist/nativeAgent/session/index.js +5 -0
  73. package/dist/nativeAgent/session/location.js +36 -0
  74. package/dist/nativeAgent/session/sqliteFormat.js +134 -0
  75. package/dist/nativeAgent/session/store.js +248 -0
  76. package/dist/nativeAgent/textTools.js +270 -133
  77. package/dist/nativeAgent/toolManager/executor.js +290 -0
  78. package/dist/nativeAgent/toolManager/index.js +4 -0
  79. package/dist/nativeAgent/validation.js +2 -2
  80. package/dist/task/taskAuthority.js +56 -0
  81. package/dist/web/assets/client/app.js +64 -3
  82. package/dist/web/assets/client/detail.js +6 -6
  83. package/dist/web/assets/client/i18n.js +4 -0
  84. package/dist/web/assets/client/overview.js +12 -9
  85. package/dist/web/assets/client/sidebar.js +31 -2
  86. package/dist/web/assets/shell.js +2 -1
  87. package/dist/web/assets/styles/components.js +2 -0
  88. package/dist/web/assets/styles/layout.js +11 -4
  89. package/dist/web/assets/styles/responsive.js +12 -4
  90. package/dist/web/assets/styles/views.js +16 -13
  91. package/docs/agent-result-consumption.md +16 -0
  92. package/docs/agent-result-consumption.zh-CN.md +13 -0
  93. package/docs/examples/agent-offline.mjs +194 -0
  94. package/docs/native-agent.md +283 -0
  95. package/docs/release-workflow.md +47 -9
  96. package/docs/release-workflow.zh-CN.md +36 -6
  97. package/docs/roles-and-configuration.md +32 -0
  98. package/docs/roles-and-configuration.zh-CN.md +26 -0
  99. package/package.json +1 -1
  100. package/skills/yui-leader/SKILL.md +9 -0
  101. package/skills/yui-reviewer/SKILL.md +5 -0
  102. package/skills/yui-runtime/SKILL.md +7 -0
@@ -4,11 +4,12 @@ import { agentExecutionComponentLabel } from "../agent/executionComponents.js";
4
4
  import { assertLeaderArchiveAdmission } from "../task/leaderArchiveAuthority.js";
5
5
  import { createRun, runExecutionObservation, runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
6
6
  import { isGitArtifactRefString, parseGitArtifactRef } from "../artifacts/gitArtifactRef.js";
7
- import { contextSnapshotRef } from "../context/contextSnapshot.js";
8
- import { buildRunContextDelta, buildRunContextPack, contextSnapshotDeltaRefIds, expandRunContextRef, freezeReviewStageContextSnapshot, freezeRunContextSnapshot, freezeWorkItemExecutionAssignmentContextSnapshot, readRunContextSnapshot, synthesisSourceRunIds } from "../context/runContextPack.js";
7
+ import { contextContentDigest, contextSnapshotRef } from "../context/contextSnapshot.js";
8
+ import { sourceRunContextValue } from "../context/sourceRunContext.js";
9
+ import { buildRunContextDelta, buildRunContextPack, contextSnapshotDeltaRefIds, expandRunContextRef, freezeReviewStageContextSnapshot, freezeRunContextSnapshot, freezeWorkItemExecutionAssignmentContextSnapshot, freezeCandidateContextSnapshot, readRunContextSnapshot, synthesisSourceRunIds } from "../context/runContextPack.js";
9
10
  import { createRunInput } from "../context/runInputContract.js";
10
11
  import { parseTaskCatalogOptions, readTaskCatalog, renderTaskCatalog } from "../context/taskCatalog.js";
11
- import { assertContextRecordReadable } from "../context/taskContext.js";
12
+ import { assertContextRecordReadable, readTaskContextResource } from "../context/taskContext.js";
12
13
  import { boundedDocument, readOptions, messageReceipt, recordPage } from "../output/boundedRead.js";
13
14
  import { referencedWakeRunIds } from "../context/wakeRunReferences.js";
14
15
  import { completeProcessing, releaseProcessing } from "../coordination/workMailbox.js";
@@ -19,7 +20,7 @@ import { createExecutionGroup, createReviewExecutionAssignment, createWorkItemEx
19
20
  import { projectWorkItemExecution } from "../execution/workItemExecutionProjection.js";
20
21
  import { dispatchWorkItemSynthesis, selectedWorkItemSynthesisProducers } from "../execution/workItemMainRun.js";
21
22
  import { createRoleSessionSet, roleAgentSessionResumeMode, roleSessionControlTarget, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
22
- import { resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
23
+ import { resolveEffectiveLaunch, effectiveLaunchWithTaskMainWorkspace } from "../executor/effectiveLaunch.js";
23
24
  import { cancelInputRequest } from "../input/inputRequest.js";
24
25
  import { retireExactActiveRun, terminalizeExactTaskRun, validateExactRunReviewRound } from "../lifecycle/exactRunTerminalization.js";
25
26
  import { resolveTaskInputControl } from "../message/inputControlResolution.js";
@@ -3412,6 +3413,7 @@ function updateWork(args, store, options) {
3412
3413
  : { taskMainSnapshot: options.directTaskMainSnapshot })
3413
3414
  }, now);
3414
3415
  tx.saveWorkItem(task.id, updated);
3416
+ freezeCandidateContextSnapshot(tx, updated.candidates.at(-1), now);
3415
3417
  recordTaskEvent(tx, task.id, "work.updated", {
3416
3418
  workItemId: updated.id,
3417
3419
  status: updated.status,
@@ -3463,8 +3465,8 @@ export function requireWorkItemAssignee(item) {
3463
3465
  return item.assignee;
3464
3466
  }
3465
3467
  function dispatchWork(args, store, options) {
3466
- const usage = "Task work dispatch usage: yui task work dispatch <task>/<work> [--input <text>] [--lane-role <role> ...].";
3467
- const parsed = parseMultiValueTail(args, new Set(["--input"]), new Set(["--lane-role"]), usage);
3468
+ const usage = "Task work dispatch usage: yui task work dispatch <task>/<work> [--input <text>] [--context-ref <store/refId@digest> ...] [--lane-role <role> ...].";
3469
+ const parsed = parseMultiValueTail(args, new Set(["--input"]), new Set(["--lane-role", "--context-ref"]), usage);
3468
3470
  exactPositionals(parsed.positionals, 1, usage);
3469
3471
  const requestedLaneRoles = parsed.multiOptions.get("--lane-role") ?? [];
3470
3472
  const now = clock(options);
@@ -3472,6 +3474,7 @@ function dispatchWork(args, store, options) {
3472
3474
  const item = requireWorkItem(tx, parsed.positionals[0], options);
3473
3475
  const task = requireTask(tx, item.taskId);
3474
3476
  taskActor(tx, options, task.id);
3477
+ const requiredResources = readDispatchContextRefs(tx, task.id, parsed.multiOptions.get("--context-ref") ?? [], options.environment);
3475
3478
  if (task.status !== "active")
3476
3479
  throw usageError(inactiveTaskMessage(task, "dispatch"));
3477
3480
  assertTaskExecutionEnabled(task, "dispatching work");
@@ -3531,7 +3534,7 @@ function dispatchWork(args, store, options) {
3531
3534
  purpose: "execution",
3532
3535
  workItemId: item.id,
3533
3536
  workspace
3534
- }, now, "controller");
3537
+ }, now, "controller", undefined, undefined, requiredResources);
3535
3538
  const run = createRun(runId, task.id, role.name, roleAgentSessionResumeMode(tx.getTaskRoleSessionSet(task.id, role.name), effective.agentId, effective), createRunInput({
3536
3539
  source: { type: "yui", channel: "workitem-dispatch" },
3537
3540
  directive: rawInput,
@@ -3563,7 +3566,8 @@ function dispatchWork(args, store, options) {
3563
3566
  const assignmentContext = contextSnapshotRef(freezeWorkItemExecutionAssignmentContextSnapshot(tx, {
3564
3567
  taskId: task.id,
3565
3568
  workItemId: item.id,
3566
- executionGroupId: groupId
3569
+ executionGroupId: groupId,
3570
+ requiredResources
3567
3571
  }, now));
3568
3572
  const plans = roles.map((role, index) => {
3569
3573
  const laneId = `${groupId}-lane-${index + 1}`;
@@ -3668,6 +3672,45 @@ function dispatchWork(args, store, options) {
3668
3672
  ? `Direct WorkItem AgentRun queued as ${dispatch.runs[0].id}\n`
3669
3673
  : `Dispatch queued for ${dispatch.runs.length} replicated Lanes\n`;
3670
3674
  }
3675
+ /** Explicit, bounded reads; prose never grants access or selects records. */
3676
+ export function readDispatchContextRefs(store, taskId, selectors, environment = {}) {
3677
+ if (selectors.length > 16 || new Set(selectors).size !== selectors.length) {
3678
+ throw usageError("Dispatch requires at most 16 distinct --context-ref values.");
3679
+ }
3680
+ return selectors.flatMap(selector => {
3681
+ const match = /^([^/]+)\/(.+)@([0-9a-f]{64})$/u.exec(selector);
3682
+ if (match === null) {
3683
+ throw usageError(`Invalid required Context reference: ${selector}. Use store/refId@digest from task context list/inspect.`);
3684
+ }
3685
+ const [, family, refId, digest] = match;
3686
+ try {
3687
+ const entry = readTaskContextResource(store, taskId, {
3688
+ store: family, refId: refId, digest: digest
3689
+ }, environment);
3690
+ const resources = [{
3691
+ ref: { ...entry.ref, layer: "L4", evidenceOf: "dispatch-required" },
3692
+ value: entry.value
3693
+ }];
3694
+ // Selecting a result Message selects its already-authorized original
3695
+ // result too, not just a pointer the receiving Assignment cannot expand.
3696
+ if (family === "task-message" && "result" in entry) {
3697
+ const message = entry.value;
3698
+ const source = store.getRun(taskId, message.resultRef.runId);
3699
+ const value = sourceRunContextValue(source);
3700
+ resources.push({
3701
+ ref: { layer: "L4", store: "source-run", refId: source.id,
3702
+ revision: source.updatedAt, digest: contextContentDigest(value),
3703
+ evidenceOf: "dispatch-required", summary: `Original result for Message ${message.id}` },
3704
+ value
3705
+ });
3706
+ }
3707
+ return resources;
3708
+ }
3709
+ catch (error) {
3710
+ throw usageError(`Required Context reference ${selector} cannot be frozen: ${error instanceof Error ? error.message : String(error)} Read an authorized current reference, correct the selection or include the requirement in the WorkItem, then dispatch again.`);
3711
+ }
3712
+ });
3713
+ }
3671
3714
  function replicatedProducerAssignmentInput(input, requiresCodeRef) {
3672
3715
  return [
3673
3716
  input,
@@ -5051,12 +5094,20 @@ function retryRunOperation(args, store, options) {
5051
5094
  throw dataError(`AgentRun ${previous.id} direct WorkItem workspace is missing or has drifted.`);
5052
5095
  }
5053
5096
  }
5054
- const effective = retryLane?.effective ?? resolveEffectiveLaunch({
5055
- role,
5056
- purpose: previous.purpose,
5057
- ...(retryManagedWorkspace === undefined ? {} : { workspace: retryManagedWorkspace }),
5058
- ...(retryItem === null ? {} : { workItemWriteProjectIds: retryItem.writeProjectIds })
5059
- });
5097
+ // Direct Task delivery resumes the native Session's captured authority.
5098
+ // New source/Role defaults are adopted only after formal replacement.
5099
+ const directSession = retryItem === null && previous.purpose === "execution"
5100
+ && retryManagedWorkspace?.owner.type === "task"
5101
+ ? sessions?.sessions[role.activeAgentId] : undefined;
5102
+ const effective = retryLane?.effective
5103
+ ?? (directSession === undefined ? undefined
5104
+ : effectiveLaunchWithTaskMainWorkspace(directSession.effective, retryManagedWorkspace))
5105
+ ?? resolveEffectiveLaunch({
5106
+ role,
5107
+ purpose: previous.purpose,
5108
+ ...(retryManagedWorkspace === undefined ? {} : { workspace: retryManagedWorkspace }),
5109
+ ...(retryItem === null ? {} : { workItemWriteProjectIds: retryItem.writeProjectIds })
5110
+ });
5060
5111
  const runId = tx.nextRunId(task.id);
5061
5112
  const runningGroup = retryGroup === undefined || retryLane === undefined
5062
5113
  ? undefined
@@ -5082,13 +5133,19 @@ function retryRunOperation(args, store, options) {
5082
5133
  const input = retriesSynthesisMain
5083
5134
  ? previous.inputs[0].input
5084
5135
  : (() => {
5136
+ // A retry keeps the explicit frozen handoff, not today's mutable
5137
+ // versions of those records. Legacy Runs without a Snapshot retain
5138
+ // the existing explicit recovery from current facts.
5139
+ const requiredResources = previous.inputs[0].input.contextSnapshotRef === undefined
5140
+ ? []
5141
+ : readRunContextSnapshot(tx, previous).resources.filter(entry => entry.ref.evidenceOf === "dispatch-required");
5085
5142
  const retrySnapshot = freezeRunContextSnapshot(tx, {
5086
5143
  taskId: task.id,
5087
5144
  roleName: role.name,
5088
5145
  purpose: previous.purpose,
5089
5146
  ...(retryManagedWorkspace === undefined ? {} : { workspace: retryManagedWorkspace }),
5090
5147
  ...(previous.workItemId === undefined ? {} : { workItemId: previous.workItemId })
5091
- }, now, "controller", retryGroup?.assignment.contextSnapshotRef);
5148
+ }, now, "controller", retryGroup?.assignment.contextSnapshotRef, undefined, requiredResources, "frozen");
5092
5149
  return createRunInput({
5093
5150
  source: {
5094
5151
  type: "yui",
@@ -5211,12 +5268,13 @@ function taskReviewProvenance(store, task, options, expected) {
5211
5268
  break;
5212
5269
  }
5213
5270
  }
5214
- if (headIndex < 0) {
5215
- throw dataError(`Committed Integration provenance is unavailable for Project ${projectId}@${commit}.`);
5216
- }
5217
- const head = committedAttempts[headIndex];
5218
- const lineage = committedAttempts.slice(0, headIndex + 1)
5219
- .filter(({ targetRef }) => targetRef === head.targetRef);
5271
+ const target = store.getTaskWorkspace(task.id)?.entries.find(entry => entry.projectId === projectId)?.branch ?? committedAttempts[headIndex]?.targetRef;
5272
+ // A direct Task-main repair has no Integration at its new head. It is
5273
+ // still reviewable; conservatively exclude prior producers on this target
5274
+ // from reviewing it. This is an independence fence, not ancestry or proof
5275
+ // that any prior report validates the current head.
5276
+ const lineage = (headIndex < 0 ? committedAttempts : committedAttempts.slice(0, headIndex + 1))
5277
+ .filter(({ targetRef }) => targetRef === target);
5220
5278
  for (const committed of lineage) {
5221
5279
  if (committed.source.kind !== "work-item")
5222
5280
  continue;
@@ -7,6 +7,7 @@ import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
7
7
  import { assertProjectActive, resolveProject } from "../repository/project.js";
8
8
  import { FileTaskWorkspacePreparer } from "../repository/taskWorkspacePreparer.js";
9
9
  import { assertTaskDeliveryAuthority as taskLocalActor } from "../task/taskAuthority.js";
10
+ import { taskIntegrationTargetCheckout } from "../task/taskAuthority.js";
10
11
  import { resolveTaskRecordReference } from "../task/taskRecordReference.js";
11
12
  import { governingWorkItemCandidate } from "../workItem/workItem.js";
12
13
  import { workspaceProjectEntry } from "../worktree/managedWorkspace.js";
@@ -132,6 +133,7 @@ async function start(args, store, home, now, options) {
132
133
  if (targetRef === undefined) {
133
134
  throw usageError(`Task main worktree is not ready; reconcile the Task first: ${task.id}.`);
134
135
  }
136
+ taskIntegrationTargetCheckout(store, options.environment, task.id, project.id, targetRef);
135
137
  const expectedHead = (await new NodeGitWorkspace().inspect(mainEntry.path, targetRef)).baseCommit;
136
138
  const snapshotEntry = candidate.workspace.entries.find(({ projectId }) => projectId === project.id);
137
139
  const resultCommit = candidate.gitSnapshot.projects.find(({ projectId }) => projectId === project.id)?.commit;
@@ -139,7 +141,7 @@ async function start(args, store, home, now, options) {
139
141
  throw usageError(`WorkItem result Project snapshot is incomplete: ${workItem.id}/${project.id}.`);
140
142
  }
141
143
  const integration = store.transaction((tx) => {
142
- taskLocalActor(tx, options.environment, task.id);
144
+ taskIntegrationTargetCheckout(tx, options.environment, task.id, project.id, targetRef);
143
145
  const created = createIntegrationAttempt({
144
146
  id: tx.nextIntegrationAttemptId(task.id),
145
147
  taskId: task.id,
@@ -6,7 +6,7 @@ import { createIntegrationAttempt } from "../integration/integrationAttempt.js";
6
6
  import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
7
7
  import { FileTaskWorkspacePreparer } from "../repository/taskWorkspacePreparer.js";
8
8
  import { workspaceProjectEntry } from "../worktree/managedWorkspace.js";
9
- import { taskLocalActor } from "../task/taskAuthority.js";
9
+ import { taskLocalActor, assertTaskProjectWriteAuthority } from "../task/taskAuthority.js";
10
10
  export async function runTaskUpstreamCommand(args, store, home, options = {}) {
11
11
  const [command, ...rest] = args;
12
12
  if (command === "integrate")
@@ -71,6 +71,7 @@ async function integrateUpstream(args, store, home, options) {
71
71
  let phase = "prepare";
72
72
  let integrationId;
73
73
  try {
74
+ assertTaskProjectWriteAuthority(store, options.environment, task.id, binding.projectId);
74
75
  const project = store.getProject(binding.projectId);
75
76
  if (project === null)
76
77
  throw usageError(`Project not found: ${binding.projectId}.`);
@@ -94,6 +95,7 @@ async function integrateUpstream(args, store, home, options) {
94
95
  branch: binding.baseRef
95
96
  });
96
97
  const attempt = store.transaction((tx) => {
98
+ assertTaskProjectWriteAuthority(tx, options.environment, task.id, binding.projectId);
97
99
  const created = createIntegrationAttempt({
98
100
  id: tx.nextIntegrationAttemptId(task.id),
99
101
  taskId: task.id,
@@ -10,7 +10,112 @@ import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
10
10
  import { contextContentDigest, contextSnapshotRef, createContextSnapshot, validateContextSnapshot } from "./contextSnapshot.js";
11
11
  import { MAX_SYNTHESIS_SOURCE_RUNS, sourceRunContextValue } from "./sourceRunContext.js";
12
12
  export const RUN_CONTEXT_PACK_SCHEMA_VERSION = 1;
13
- export function freezeRunContextSnapshot(store, run, now, frozenBy = "controller", baselineRef, sourceRunIds) {
13
+ function mergeRequiredContext(automatic, required, requiredPolicy = "current") {
14
+ const entries = new Map(automatic.map(entry => [`${entry.ref.store}/${entry.ref.refId}`, entry]));
15
+ for (const entry of required) {
16
+ const key = `${entry.ref.store}/${entry.ref.refId}`;
17
+ const existing = entries.get(key);
18
+ if (requiredPolicy === "current" && existing !== undefined && existing.ref.digest !== entry.ref.digest) {
19
+ throw new Error(`Required Context changed during dispatch: ${key}. Read its current reference and dispatch again.`);
20
+ }
21
+ entries.set(key, entry);
22
+ }
23
+ return [...entries.values()];
24
+ }
25
+ /** Pin the original result together with the candidate, using the existing
26
+ * immutable Snapshot store rather than a second result/provenance ledger. */
27
+ export function freezeCandidateContextSnapshot(store, candidate, now) {
28
+ if (candidate.source.type !== "run")
29
+ return undefined;
30
+ const source = store.getRun(candidate.taskId, candidate.source.runId);
31
+ if (source === null || source.taskId !== candidate.taskId
32
+ || source.workItemId !== candidate.workItemId || source.purpose !== "execution"
33
+ || source.status !== "completed" || source.result === undefined
34
+ || Date.parse(source.updatedAt) > Date.parse(candidate.createdAt)) {
35
+ throw new Error(`Candidate source report is missing or mismatched: ${candidate.workItemId}/${candidate.id}.`);
36
+ }
37
+ const evidenceOf = `${candidate.workItemId}/${candidate.id}`;
38
+ const report = materialize("L4", "source-run", source.id, sourceRunContextValue(source));
39
+ const resources = [
40
+ materialize("L3", "candidate", evidenceOf, candidate),
41
+ { ...report, ref: { ...report.ref, evidenceOf } }
42
+ ];
43
+ const snapshot = createContextSnapshot({
44
+ id: store.nextContextSnapshotId(candidate.taskId),
45
+ taskId: candidate.taskId, scope: "workitem", scopeRef: `candidate:${evidenceOf}`,
46
+ sequence: 1, refs: resources.map(({ ref }) => ref), resources, acceptRefs: [],
47
+ frozenAt: now, frozenBy: "controller"
48
+ });
49
+ store.saveContextSnapshot(snapshot);
50
+ return snapshot;
51
+ }
52
+ function candidateReviewContext(store, candidate) {
53
+ const identity = `${candidate.workItemId}/${candidate.id}`;
54
+ if (candidate.source.type === "direct") {
55
+ return [materialize("L3", "candidate", identity, candidate)];
56
+ }
57
+ const sourceRunId = candidate.source.runId;
58
+ const snapshots = store.listContextSnapshots(candidate.taskId).filter(snapshot => snapshot.scope === "workitem" && snapshot.scopeRef === `candidate:${identity}`);
59
+ if (snapshots.length !== 1) {
60
+ throw new Error(`Frozen candidate report is unavailable: ${identity}. Inspect source Run ${candidate.taskId}/${sourceRunId}; for a new Review use newly authorized delivery with a newly captured Candidate. Historical Snapshots are not rewritten.`);
61
+ }
62
+ const snapshot = validateContextSnapshot(snapshots[0]);
63
+ const proof = snapshot.resources.find(({ ref }) => ref.store === "candidate" && ref.refId === identity);
64
+ const report = snapshot.resources.find(({ ref }) => ref.store === "source-run"
65
+ && ref.refId === sourceRunId && ref.evidenceOf === identity);
66
+ const source = store.getRun(candidate.taskId, sourceRunId);
67
+ if (proof?.ref.digest !== contextContentDigest(candidate) || report === undefined
68
+ || source === null || source.status !== "completed" || source.result === undefined
69
+ || source.taskId !== candidate.taskId || source.workItemId !== candidate.workItemId
70
+ || source.purpose !== "execution"
71
+ || report.ref.digest !== contextContentDigest(sourceRunContextValue(source))) {
72
+ throw new Error(`Frozen candidate source/report drifted: ${identity}. Inspect the exact source and capture a new Candidate; do not substitute a newer result.`);
73
+ }
74
+ return [proof, report];
75
+ }
76
+ /** Reuse only exact head/target evidence. Temporal ordering and a shared branch
77
+ * name do not prove ancestry; absent proof is a reviewable evidence gap. */
78
+ function taskReviewCandidateSources(store, taskId, heads) {
79
+ const selected = new Map();
80
+ const select = (candidate) => selected.set(`${candidate.workItemId}/${candidate.id}`, candidate);
81
+ const workspace = store.getTaskWorkspace(taskId);
82
+ const attempts = store.listIntegrationAttempts(taskId);
83
+ const items = store.listWorkItems(taskId);
84
+ const unmatchedHeads = [];
85
+ for (const { projectId, commit } of heads.projects) {
86
+ const targetRef = workspace?.entries.find(entry => entry.projectId === projectId)?.branch;
87
+ let matched = false;
88
+ for (const attempt of attempts.filter(entry => entry.projectId === projectId
89
+ && entry.status === "committed" && entry.candidateCommit === commit
90
+ && entry.targetRef === targetRef)) {
91
+ if (attempt.source.kind !== "work-item")
92
+ continue;
93
+ const source = attempt.source;
94
+ const item = store.getWorkItem(taskId, source.workItemId);
95
+ const candidate = [...(item?.candidates ?? [])].reverse().find(entry => entry.gitSnapshot?.projects.some(project => project.projectId === projectId && project.commit === source.resultCommit));
96
+ if (candidate !== undefined) {
97
+ select(candidate);
98
+ matched = true;
99
+ }
100
+ }
101
+ for (const item of items) {
102
+ const candidate = [...item.candidates].reverse().find(entry => entry.taskMainSnapshot?.projects.some(project => project.projectId === projectId && project.headCommit === commit
103
+ && project.branch === targetRef)
104
+ || entry.gitSnapshot?.projects.some(project => project.projectId === projectId && project.commit === commit));
105
+ if (candidate !== undefined) {
106
+ select(candidate);
107
+ matched = true;
108
+ }
109
+ }
110
+ if (!matched)
111
+ unmatchedHeads.push(`${projectId}@${commit}`);
112
+ }
113
+ return { candidates: [...selected.values()], unmatchedHeads };
114
+ }
115
+ export function freezeRunContextSnapshot(store, run, now, frozenBy = "controller", baselineRef, sourceRunIds, requiredResources = [],
116
+ // Only retry of a validated prior Snapshot inherits frozen values over live
117
+ // automatic context; a new dispatch must still match current references.
118
+ requiredPolicy = "current") {
14
119
  if (baselineRef !== undefined) {
15
120
  const baseline = store.getContextSnapshot(run.taskId, baselineRef.id);
16
121
  if (baseline === null
@@ -28,7 +133,7 @@ export function freezeRunContextSnapshot(store, run, now, frozenBy = "controller
28
133
  ...collectRunContextOverlays(store, run),
29
134
  ...collectSourceRunContext(store, run, sourceRunIds)
30
135
  ];
31
- const resources = [...new Map([...baseline.resources, ...overlays].map((entry) => [
136
+ const resources = [...new Map(mergeRequiredContext([...baseline.resources, ...overlays], requiredResources, requiredPolicy).map((entry) => [
32
137
  contextRefIdentity(entry.ref),
33
138
  entry
34
139
  ])).values()].sort((left, right) => (contextRefIdentity(left.ref).localeCompare(contextRefIdentity(right.ref))));
@@ -60,7 +165,7 @@ export function freezeRunContextSnapshot(store, run, now, frozenBy = "controller
60
165
  ? "workitem"
61
166
  : "task";
62
167
  const scopeRef = run.reviewRoundId ?? run.workItemId;
63
- const materialized = collectAuthorizedContext(store, run);
168
+ const materialized = mergeRequiredContext(collectAuthorizedContext(store, run), requiredResources, requiredPolicy);
64
169
  const previous = store.listContextSnapshots(run.taskId)
65
170
  .filter((candidate) => candidate.scope === scope && candidate.scopeRef === scopeRef)
66
171
  .sort((left, right) => left.sequence - right.sequence)
@@ -117,7 +222,7 @@ export function freezeWorkItemExecutionAssignmentContextSnapshot(store, input, n
117
222
  materialized.push(materialize("L1", "project-knowledge", `${project.id}:${entry.id}`, { projectId: project.id, ...entry }));
118
223
  }
119
224
  }
120
- const resources = [...new Map(materialized.map((entry) => [
225
+ const resources = [...new Map(mergeRequiredContext(materialized, input.requiredResources ?? []).map((entry) => [
121
226
  contextRefIdentity(entry.ref),
122
227
  entry
123
228
  ])).values()].sort((left, right) => (contextRefIdentity(left.ref).localeCompare(contextRefIdentity(right.ref))));
@@ -365,7 +470,14 @@ function collectAuthorizedContext(store, run) {
365
470
  const round = store.getReviewRound(task.id, run.reviewRoundId);
366
471
  if (round === null)
367
472
  throw new Error(`AgentRun ReviewRound not found: ${run.reviewRoundId}.`);
368
- result.push(materialize("L3", "review-round", round.id, round));
473
+ let roundContext = materialize("L3", "review-round", round.id, round);
474
+ if (round.scope === "work-item") {
475
+ const item = store.getWorkItem(task.id, round.workItemId);
476
+ const candidate = item?.candidates.find(({ id }) => id === round.candidateId);
477
+ if (candidate === undefined)
478
+ throw new Error(`Review candidate is unavailable: ${round.candidateId}.`);
479
+ result.push(...candidateReviewContext(store, candidate));
480
+ }
369
481
  if (round.scope === "task") {
370
482
  for (const item of store.listWorkItems(task.id).filter(({ status }) => status !== "retired")) {
371
483
  const candidate = governingWorkItemCandidate(item);
@@ -374,7 +486,22 @@ function collectAuthorizedContext(store, run) {
374
486
  result.push(materialize("L3", "candidate", `${item.id}/${candidate.id}`, candidate));
375
487
  result.push(...candidateArtifacts(task.id, [candidate]));
376
488
  }
489
+ const evidence = taskReviewCandidateSources(store, task.id, round.taskCandidate);
490
+ // Existing pointer metadata exposes the gap without changing the Round,
491
+ // inventing validation, or adding another persisted evidence record.
492
+ roundContext = {
493
+ ...roundContext, ref: {
494
+ ...roundContext.ref,
495
+ summary: `Task-final Review ${round.id}. Producer reports are evidence only for their named Candidate, not validation of the entire Task head.${evidence.unmatchedHeads.length === 0 ? ""
496
+ : ` Producer evidence gap: ${evidence.unmatchedHeads.join(", ")}. Review these fixed heads and report missing validation; the gap does not prohibit review.`}`
497
+ }
498
+ };
499
+ for (const candidate of evidence.candidates) {
500
+ result.push(...candidateReviewContext(store, candidate));
501
+ result.push(...candidateArtifacts(task.id, [candidate]));
502
+ }
377
503
  }
504
+ result.push(roundContext);
378
505
  if (round.deltaRecheck !== undefined) {
379
506
  const previous = store.getReviewRound(task.id, round.deltaRecheck.previousReviewRoundId);
380
507
  const previousRun = previous?.reviewerRunId === undefined
@@ -1,7 +1,9 @@
1
1
  import { MAX_RUN_RESULT_OUTPUT_BYTES } from "../domain/agentResultTransport.js";
2
2
  export const MAX_SYNTHESIS_SOURCE_RUNS = 8;
3
- export const MAX_CONTEXT_SOURCE_RUNS = MAX_SYNTHESIS_SOURCE_RUNS + 1;
4
- export const MAX_CONTEXT_SOURCE_RUN_BYTES = MAX_CONTEXT_SOURCE_RUNS * (MAX_RUN_RESULT_OUTPUT_BYTES + 16 * 1024);
3
+ // A Task review may combine several candidate reports, synthesis and
4
+ // continuation evidence. Preserve the existing aggregate byte ceiling.
5
+ export const MAX_CONTEXT_SOURCE_RUNS = 64;
6
+ export const MAX_CONTEXT_SOURCE_RUN_BYTES = (MAX_SYNTHESIS_SOURCE_RUNS + 1) * (MAX_RUN_RESULT_OUTPUT_BYTES + 16 * 1024);
5
7
  /**
6
8
  * Frozen input for synthesis. The main Agent needs the producer identity and
7
9
  * exact result, not another copy of its prompt history, launch configuration,
@@ -158,6 +158,11 @@ export function readTaskContextDelta(store, taskId, input, environment = {}) {
158
158
  });
159
159
  }
160
160
  export function inspectTaskContext(store, taskId, selector, environment = {}) {
161
+ const response = readTaskContextResource(store, taskId, selector, environment);
162
+ return boundedDocument(response, `task:${taskId}/${selector.store}/${selector.refId}`, selector.cursor);
163
+ }
164
+ /** The same authorized read as inspect, before transport pagination. */
165
+ export function readTaskContextResource(store, taskId, selector, environment = {}) {
161
166
  return store.readTransaction((reader) => {
162
167
  const scope = authorizeContext(reader, taskId, environment);
163
168
  const value = isAllowed(scope.allow, selector.store, selector.refId)
@@ -177,7 +182,7 @@ export function inspectTaskContext(store, taskId, selector, environment = {}) {
177
182
  ? { result: expansion.result } : {}),
178
183
  ...(selector.store === "run" ? { execution: runExecutionObservation(value, reader.getTaskRoleSessionSet(taskId, value.roleName)?.providerBinding, reader.listEventsByType(taskId, ["runtime.agent-error"])) } : {}),
179
184
  };
180
- return boundedDocument(response, `task:${taskId}/${selector.store}/${selector.refId}`, selector.cursor);
185
+ return response;
181
186
  });
182
187
  }
183
188
  /** Discover one authorized family without copying its bodies or unrelated Task