@zq-silk/yui 0.14.0 → 0.14.2
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/ARCHITECTURE.md +64 -35
- package/README.md +258 -125
- package/dist/agent/argumentPolicy.js +2 -1
- package/dist/agent/managedRuntimeEnvironment.js +5 -8
- package/dist/cli/commandCatalog.js +74 -109
- package/dist/cli/helpRenderer.js +1 -2
- package/dist/cli/interactionCandidates.js +10 -0
- package/dist/cli/interactionPolicy.js +76 -37
- package/dist/cli/roleWizard.js +5 -6
- package/dist/cli/updateOrchestrator.js +10 -8
- package/dist/cli/updatePorts.js +17 -13
- package/dist/cli/upgradeCommand.js +2 -5
- package/dist/cli.js +268 -231
- package/dist/commands/agentCommands.js +13 -0
- package/dist/commands/configCommands.js +7 -19
- package/dist/commands/configOverview.js +3 -2
- package/dist/commands/durableJobCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +4 -9
- package/dist/commands/globalRoleCommands.js +17 -0
- package/dist/commands/profileCommands.js +200 -30
- package/dist/commands/sessionCommands.js +1 -1
- package/dist/commands/taskActor.js +29 -127
- package/dist/commands/taskChangeSetCommands.js +1 -3
- package/dist/commands/taskCommands.js +1771 -2107
- package/dist/commands/taskCompletionGate.js +27 -103
- package/dist/commands/taskContextCommand.js +47 -80
- package/dist/commands/taskExecutionCommands.js +9 -27
- package/dist/commands/taskInputCommands.js +10 -10
- package/dist/commands/taskIntegrationCommands.js +69 -61
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -29
- package/dist/commands/taskOverviewCommand.js +25 -3
- package/dist/commands/taskPublicationCommands.js +151 -85
- package/dist/commands/taskPublicationVerifyCommand.js +181 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
- package/dist/commands/taskRoleRuntimeStatus.js +5 -6
- package/dist/commands/taskUpstreamCommands.js +79 -95
- package/dist/config/configCatalog.js +1 -1
- package/dist/context/contextSnapshot.js +17 -2
- package/dist/context/sessionBootstrapManifest.js +18 -1
- package/dist/context/sourceTurnContext.js +30 -0
- package/dist/context/turnContextPack.js +107 -48
- package/dist/context/turnInputContract.js +11 -4
- package/dist/context/wakeNotification.js +84 -22
- package/dist/controller/agentRuntimeObserver.js +9 -12
- package/dist/controller/clientRuntime.js +13 -13
- package/dist/controller/controller.js +11 -11
- package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
- package/dist/controller/jobControl.js +11 -10
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +2 -2
- package/dist/controller/runtime.js +18 -61
- package/dist/controller/runtimeEventInbox.js +57 -34
- package/dist/controller/runtimeEventProcessor.js +36 -96
- package/dist/controller/runtimeHookTurnFence.js +33 -57
- package/dist/controller/runtimeLaunchCoordinator.js +44 -40
- package/dist/controller/runtimeObservationHook.js +18 -12
- package/dist/controller/sessionNotify.js +43 -28
- package/dist/controller/sessionOwnerReconciliation.js +21 -21
- package/dist/controller/structuredProviderObservation.js +13 -16
- package/dist/coordination/workMailboxQueue.js +132 -0
- package/dist/core/controllerServer.js +1 -0
- package/dist/domain/agentResultTransport.js +60 -0
- package/dist/execution/executionHealth.js +111 -118
- package/dist/execution/reviewMainTurn.js +161 -0
- package/dist/execution/workItemExecution.js +454 -0
- package/dist/execution/workItemExecutionProjection.js +309 -0
- package/dist/execution/workItemMainTurn.js +164 -0
- package/dist/executor/agentExecutor.js +19 -19
- package/dist/executor/executorRegistry.js +7 -28
- package/dist/executor/fileRoleLaunchPlanner.js +51 -83
- package/dist/external/githubPublicationVerifier.js +114 -0
- package/dist/external/gitlabPublicationVerifier.js +158 -0
- package/dist/external/pinnedCommandRunner.js +73 -0
- package/dist/integration/changeSetManifest.js +0 -3
- package/dist/integration/deliveryObligation.js +43 -6
- package/dist/integration/gitIntegrationService.js +284 -101
- package/dist/integration/integrationAttempt.js +64 -14
- package/dist/integration/integrationQueueEntry.js +5 -35
- package/dist/integration/integrationQueueService.js +65 -194
- package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
- package/dist/lifecycle/exactTurnTerminalization.js +100 -97
- package/dist/message/message.js +16 -0
- package/dist/observability/executionAudit.js +4 -25
- package/dist/observability/faultClassification.js +58 -70
- package/dist/observability/orchestrationMetrics.js +8 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/repository/executionLaneGitSnapshot.js +80 -0
- package/dist/repository/gitWorkspace.js +9 -67
- package/dist/repository/taskBaseFreshness.js +3 -1
- package/dist/repository/taskWorkspaceCoordinator.js +32 -4
- package/dist/repository/taskWorkspacePreparer.js +252 -411
- package/dist/resources/liveReferences.js +6 -55
- package/dist/resources/resourceDiscovery.js +1 -30
- package/dist/resources/resourceRegistrar.js +0 -23
- package/dist/review/deltaRecheck.js +8 -65
- package/dist/review/reviewAcceptance.js +22 -19
- package/dist/review/reviewConfig.js +0 -10
- package/dist/review/reviewDecision.js +2 -2
- package/dist/review/reviewRound.js +147 -273
- package/dist/review/reviewerAvailability.js +13 -23
- package/dist/role/role.js +3 -0
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentHost.js +51 -51
- package/dist/runtime/builtinAgentDrivers.js +9 -2
- package/dist/runtime/codexAppServerRuntime.js +23 -5
- package/dist/runtime/exactControlPlane.js +2 -267
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +11 -11
- package/dist/runtime/lifecycleReservation.js +2 -2
- package/dist/runtime/managedCaller.js +111 -0
- package/dist/runtime/ports.js +16 -3
- package/dist/runtime/preallocatedNativeSession.js +2 -2
- package/dist/runtime/processExitObservation.js +3 -2
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/runtimeBinding.js +1 -1
- package/dist/runtime/runtimeObservation.js +33 -16
- package/dist/runtime/runtimeSessionCandidate.js +2 -2
- package/dist/runtime/runtimeStopReceipt.js +11 -11
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/sessionOwnerIdentity.js +9 -9
- package/dist/runtime/sessionOwnerRegistry.js +13 -13
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTerminationGuard.js +9 -9
- package/dist/runtime/sessionTokenMetrics.js +5 -7
- package/dist/runtime/structuredProviderHost.js +5 -5
- package/dist/runtime/taskRuntimeIsolation.js +24 -24
- package/dist/runtime/tmuxAdapters.js +32 -30
- package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
- package/dist/scheduler/leaderWakeupProcessor.js +78 -55
- package/dist/scheduler/roleTurnLiveness.js +6 -6
- package/dist/scheduler/roleTurnStall.js +8 -9
- package/dist/scheduler/taskExecutionProjection.js +19 -41
- package/dist/scheduler/taskObservabilityProjection.js +75 -122
- package/dist/storage/sqliteSchema.js +1 -21
- package/dist/storage/sqliteStore.js +62 -47
- package/dist/storage/storageSchema.js +1 -8
- package/dist/storage/storageVersions.js +7 -4
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
- package/dist/task/completionReadiness.js +17 -98
- package/dist/task/deliveryGuard.js +9 -11
- package/dist/task/draftPlan.js +142 -0
- package/dist/task/nextAction.js +179 -477
- package/dist/task/publicationVerification.js +1 -0
- package/dist/task/remoteDelivery.js +233 -0
- package/dist/task/task.js +108 -4
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/turn/turn.js +109 -16
- package/dist/verification/verificationGateService.js +0 -26
- package/dist/web/assets/client/components.js +99 -121
- package/dist/web/assets/client/i18n.js +136 -58
- package/dist/web/assets/client/view.js +44 -0
- package/dist/web/assets/styles/cards.js +3 -15
- package/dist/web/assets/styles/widgets.js +4 -0
- package/dist/web/webSnapshot.js +29 -14
- package/dist/workItem/dependencyGate.js +34 -0
- package/dist/workItem/workItem.js +99 -266
- package/dist/workspace/workItemChangeSetManager.js +15 -59
- package/docs/task-local-identity.md +1 -1
- package/i18n/README.zh-CN.md +165 -59
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +272 -782
- package/skills/yui-operator/SKILL.md +190 -365
- package/skills/yui-reviewer/SKILL.md +40 -33
- package/skills/yui-runtime/SKILL.md +27 -6
- package/skills/yui-worker/SKILL.md +19 -6
- package/dist/execution/candidateConvergence.js +0 -623
- package/dist/execution/executionGroup.js +0 -854
- package/dist/execution/resourceBroker.js +0 -426
- package/dist/executor/launchPlan.js +0 -16
- package/dist/review/reviewFinding.js +0 -240
- package/dist/review/reviewFindingLedger.js +0 -550
- package/dist/review/reviewOutcomeClassifier.js +0 -255
- package/dist/scheduler/resourceQueueProjection.js +0 -71
- package/dist/task/repairWave.js +0 -150
|
@@ -335,7 +335,7 @@ function calculateHealth(role, activeTurn, lastTurn, nativeSession, runtimeClean
|
|
|
335
335
|
: { health: "idle", healthReason: "there is no active work or live tmux pane" };
|
|
336
336
|
}
|
|
337
337
|
function projectTaskRoleRuntime(run, session, tmux, events, _mailbox, store, taskId, roleName, now) {
|
|
338
|
-
if (run === null || session?.
|
|
338
|
+
if (run === null || session?.runtimeGenerationId === undefined)
|
|
339
339
|
return null;
|
|
340
340
|
let driverId;
|
|
341
341
|
try {
|
|
@@ -350,8 +350,7 @@ function projectTaskRoleRuntime(run, session, tmux, events, _mailbox, store, tas
|
|
|
350
350
|
turnId: run.id,
|
|
351
351
|
agentId: run.effective.agentId,
|
|
352
352
|
driverId,
|
|
353
|
-
|
|
354
|
-
sessionGenerationId: session.launchId,
|
|
353
|
+
runtimeGenerationId: session.runtimeGenerationId,
|
|
355
354
|
nativeSessionId: session.nativeSessionId,
|
|
356
355
|
nativeTurnId: runtimeNativeTurnId(events, {
|
|
357
356
|
taskId: run.taskId,
|
|
@@ -359,7 +358,7 @@ function projectTaskRoleRuntime(run, session, tmux, events, _mailbox, store, tas
|
|
|
359
358
|
turnId: run.id,
|
|
360
359
|
agentId: run.effective.agentId,
|
|
361
360
|
driverId,
|
|
362
|
-
|
|
361
|
+
runtimeGenerationId: session.runtimeGenerationId,
|
|
363
362
|
nativeSessionId: session.nativeSessionId,
|
|
364
363
|
receiptId: store.getTaskRoleSessionSet(taskId, roleName)?.providerBinding?.turn?.attemptId
|
|
365
364
|
?? formatTurnReceiptId(run.taskId, run.id)
|
|
@@ -369,7 +368,7 @@ function projectTaskRoleRuntime(run, session, tmux, events, _mailbox, store, tas
|
|
|
369
368
|
};
|
|
370
369
|
let projection = projectRuntimeTaskEvents(fence, run.createdAt, events);
|
|
371
370
|
projection = projectRuntimeObservation(projection, createRuntimeObservation({
|
|
372
|
-
schemaVersion:
|
|
371
|
+
schemaVersion: 4,
|
|
373
372
|
eventId: `runtime-host-${run.id}`,
|
|
374
373
|
semanticKey: `runtime-host-${run.id}`,
|
|
375
374
|
kind: "host.observed",
|
|
@@ -421,7 +420,7 @@ function runtimeNativeTurnId(events, expected) {
|
|
|
421
420
|
&& observation.fence.turnId === expected.turnId
|
|
422
421
|
&& observation.fence.agentId === expected.agentId
|
|
423
422
|
&& observation.fence.driverId === expected.driverId
|
|
424
|
-
&& observation.fence.
|
|
423
|
+
&& observation.fence.runtimeGenerationId === expected.runtimeGenerationId
|
|
425
424
|
&& observation.fence.nativeSessionId === expected.nativeSessionId
|
|
426
425
|
&& observation.fence.receiptId === expected.receiptId
|
|
427
426
|
&& observation.fence.nativeTurnId !== undefined)
|
|
@@ -1,136 +1,120 @@
|
|
|
1
1
|
import { usageError } from "../errors/cliError.js";
|
|
2
|
-
import {
|
|
2
|
+
import { GitIntegrationService } from "../integration/gitIntegrationService.js";
|
|
3
|
+
import { createIntegrationAttempt } from "../integration/integrationAttempt.js";
|
|
3
4
|
import { NodeGitWorkspace } from "../repository/gitWorkspace.js";
|
|
4
|
-
import {
|
|
5
|
+
import { FileTaskWorkspacePreparer } from "../repository/taskWorkspacePreparer.js";
|
|
5
6
|
import { workspaceProjectEntry } from "../worktree/managedWorkspace.js";
|
|
6
|
-
|
|
7
|
+
import { parseRepeatable } from "./taskIntegrationCommands.js";
|
|
8
|
+
import { taskLocalActor } from "./taskActor.js";
|
|
9
|
+
export async function runTaskUpstreamCommand(args, store, home, options = {}) {
|
|
7
10
|
const [command, ...rest] = args;
|
|
8
|
-
if (command === "integrate")
|
|
9
|
-
return integrateUpstream(rest, store, options);
|
|
10
|
-
}
|
|
11
|
+
if (command === "integrate")
|
|
12
|
+
return integrateUpstream(rest, store, home, options);
|
|
11
13
|
throw usageError(command === undefined
|
|
12
14
|
? "Task upstream command is required."
|
|
13
15
|
: `Unknown command: task upstream ${command}`);
|
|
14
16
|
}
|
|
15
|
-
async function integrateUpstream(args, store, options) {
|
|
16
|
-
const usage = "Task upstream integrate usage: yui task upstream integrate <task>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
async function integrateUpstream(args, store, home, options) {
|
|
18
|
+
const usage = "Task upstream integrate usage: yui task upstream integrate <task> (--latest|--project <project>) [--check <command> ...].";
|
|
19
|
+
let latest = false;
|
|
20
|
+
const normalized = [];
|
|
21
|
+
for (const arg of args) {
|
|
22
|
+
if (arg === "--latest") {
|
|
23
|
+
if (latest)
|
|
24
|
+
throw usageError("Option may only be specified once: --latest.", usage);
|
|
25
|
+
latest = true;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
normalized.push(...(arg.startsWith("--project=")
|
|
29
|
+
? ["--project", arg.slice("--project=".length)]
|
|
30
|
+
: [arg]));
|
|
23
31
|
}
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
const parsed = parseRepeatable(normalized, new Set(["--check"]), new Set(["--project"]), usage);
|
|
33
|
+
if (parsed.positionals.length !== 1)
|
|
34
|
+
throw usageError(usage);
|
|
35
|
+
const taskId = parsed.positionals[0];
|
|
36
|
+
const projectRef = parsed.one.get("--project");
|
|
27
37
|
if (!latest && projectRef === undefined) {
|
|
28
|
-
throw usageError("Specify --latest
|
|
38
|
+
throw usageError("Specify --latest for every Task Project, or --project <project>.");
|
|
29
39
|
}
|
|
30
40
|
if (latest && projectRef !== undefined) {
|
|
31
41
|
throw usageError("--latest and --project are mutually exclusive.");
|
|
32
42
|
}
|
|
33
|
-
|
|
34
|
-
if (flag !== "--latest" && !flag.startsWith("--project=")) {
|
|
35
|
-
throw usageError(usage);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
const task = store.getTask(taskId);
|
|
43
|
+
let task = store.getTask(taskId);
|
|
39
44
|
if (task === null)
|
|
40
45
|
throw usageError(`Task not found: ${taskId}.`);
|
|
41
46
|
if (task.status !== "active") {
|
|
42
47
|
throw usageError(`Task must be active to integrate upstream: ${taskId}/${task.status}.`);
|
|
43
48
|
}
|
|
44
|
-
|
|
45
|
-
// while the Leader has an active Turn so the merge never lands under a
|
|
46
|
-
// running Session.
|
|
47
|
-
const activeLeaderRun = store.getActiveTurn(taskId, "leader");
|
|
48
|
-
if (activeLeaderRun !== null) {
|
|
49
|
-
throw usageError(`Task has an active Leader Turn (${activeLeaderRun.id}); stop it before integrating upstream: ${taskId}.`);
|
|
50
|
-
}
|
|
51
|
-
const workspace = store.getTaskWorkspace(taskId);
|
|
52
|
-
if (workspace === null) {
|
|
53
|
-
throw usageError(`Task has no workspace: ${taskId}. Activate it first.`);
|
|
54
|
-
}
|
|
49
|
+
taskLocalActor(store, options.environment, task.id);
|
|
55
50
|
const git = options.git ?? new NodeGitWorkspace();
|
|
56
51
|
const now = options.now ?? (() => new Date());
|
|
52
|
+
await new FileTaskWorkspacePreparer(home, store, git, now)
|
|
53
|
+
.prepareTaskWorkspace(task.id);
|
|
54
|
+
task = store.getTask(task.id);
|
|
55
|
+
if (task === null || task.status !== "active") {
|
|
56
|
+
throw usageError(`Task is no longer active: ${taskId}.`);
|
|
57
|
+
}
|
|
58
|
+
const workspace = store.getTaskWorkspace(task.id);
|
|
59
|
+
if (workspace === null || workspace.owner.type !== "task") {
|
|
60
|
+
throw usageError(`Task has no authoritative main clone: ${task.id}.`);
|
|
61
|
+
}
|
|
62
|
+
const service = new GitIntegrationService(store.rootDirectory(), store, git, now);
|
|
57
63
|
const results = [];
|
|
58
64
|
for (const binding of task.projectBindings) {
|
|
59
65
|
if (projectRef !== undefined && binding.projectId !== projectRef)
|
|
60
66
|
continue;
|
|
61
67
|
const project = store.getProject(binding.projectId);
|
|
62
|
-
if (project === null)
|
|
68
|
+
if (project === null)
|
|
63
69
|
throw usageError(`Project not found: ${binding.projectId}.`);
|
|
64
|
-
}
|
|
65
70
|
if (project.remoteUrl === undefined) {
|
|
66
71
|
throw usageError(`Project has no remote URL: ${project.id}.`);
|
|
67
72
|
}
|
|
73
|
+
if (binding.baseCommit === undefined || binding.currentCommit === undefined) {
|
|
74
|
+
throw usageError(`Task Project has no activated commit boundary: ${task.id}/${project.id}.`);
|
|
75
|
+
}
|
|
68
76
|
const entry = workspaceProjectEntry(workspace, project.id);
|
|
69
77
|
if (entry === undefined) {
|
|
70
78
|
throw usageError(`Task workspace has no entry for Project: ${project.id}.`);
|
|
71
79
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const releaseMaintenance = acquireProjectMaintenanceLock(store.rootDirectory(), project.id);
|
|
76
|
-
let oldHead;
|
|
77
|
-
let newHead;
|
|
78
|
-
let upstreamCommit;
|
|
79
|
-
try {
|
|
80
|
-
// Resolve the exact upstream commit.
|
|
81
|
-
const upstream = await git.resolveRemoteBaseline({
|
|
82
|
-
repositoryPath: project.path,
|
|
83
|
-
remoteUrl: project.remoteUrl,
|
|
84
|
-
developmentRef: project.developmentBranch
|
|
85
|
-
});
|
|
86
|
-
upstreamCommit = upstream.commit;
|
|
87
|
-
// Record the current HEAD as a backup before merging.
|
|
88
|
-
const current = await git.inspect(entry.path, "HEAD");
|
|
89
|
-
oldHead = current.baseCommit;
|
|
90
|
-
try {
|
|
91
|
-
// mergeWorktree always creates a merge commit (--no-ff); record the
|
|
92
|
-
// actual resulting HEAD rather than assuming a fast-forward.
|
|
93
|
-
await git.mergeWorktree({
|
|
94
|
-
targetPath: entry.path,
|
|
95
|
-
sourceRefs: [upstream.commit]
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
// Restore the original HEAD on failure. mergeWorktree already aborts
|
|
100
|
-
// a conflicted merge, so HEAD is usually already at oldHead; reset
|
|
101
|
-
// only when the merge left HEAD moved or the tree dirty.
|
|
102
|
-
let restoreNote;
|
|
103
|
-
try {
|
|
104
|
-
await git.resetWorktree({
|
|
105
|
-
targetPath: entry.path,
|
|
106
|
-
expectedHead: oldHead,
|
|
107
|
-
restoreHead: oldHead
|
|
108
|
-
});
|
|
109
|
-
restoreNote = ` The workspace has been restored to ${oldHead}.`;
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
restoreNote = ` The workspace may be left in a conflicted state; inspect ${entry.path} manually.`;
|
|
113
|
-
}
|
|
114
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
115
|
-
throw new Error(`Upstream integration failed for Project ${project.id}: ${message}.` + restoreNote);
|
|
116
|
-
}
|
|
117
|
-
const after = await git.inspect(entry.path, "HEAD");
|
|
118
|
-
newHead = after.baseCommit;
|
|
119
|
-
}
|
|
120
|
-
finally {
|
|
121
|
-
releaseMaintenance();
|
|
80
|
+
const head = (await git.inspect(entry.path, entry.branch)).baseCommit;
|
|
81
|
+
if (head !== binding.currentCommit) {
|
|
82
|
+
throw usageError(`Task Project current commit diverged from its main clone: ${task.id}/${project.id}.`);
|
|
122
83
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
store.transaction((tx) => {
|
|
127
|
-
tx.saveEvent(task.id, createTaskEvent(tx.nextEventId(task.id), task.id, "task.upstream-integrated", result, now()));
|
|
84
|
+
const remote = await git.resolveRemoteHead({
|
|
85
|
+
remoteUrl: project.remoteUrl,
|
|
86
|
+
branch: binding.baseRef
|
|
128
87
|
});
|
|
88
|
+
const attempt = store.transaction((tx) => {
|
|
89
|
+
const created = createIntegrationAttempt({
|
|
90
|
+
id: tx.nextIntegrationAttemptId(task.id),
|
|
91
|
+
taskId: task.id,
|
|
92
|
+
projectId: project.id,
|
|
93
|
+
targetRef: entry.branch,
|
|
94
|
+
beforeCommit: binding.currentCommit,
|
|
95
|
+
source: {
|
|
96
|
+
kind: "upstream",
|
|
97
|
+
branch: remote.branch,
|
|
98
|
+
remoteCommit: remote.commit,
|
|
99
|
+
taskBaseCommit: binding.baseCommit,
|
|
100
|
+
strategy: "rebase"
|
|
101
|
+
},
|
|
102
|
+
checkCommands: parsed.many.get("--check") ?? []
|
|
103
|
+
}, now());
|
|
104
|
+
tx.saveIntegrationAttempt(task.id, created);
|
|
105
|
+
return created;
|
|
106
|
+
});
|
|
107
|
+
const result = await service.integrate(task.id, attempt.id);
|
|
129
108
|
results.push(result);
|
|
109
|
+
if (result.status !== "committed")
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
if (projectRef !== undefined && results.length === 0) {
|
|
113
|
+
throw usageError(`Task Project not found: ${task.id}/${projectRef}.`);
|
|
130
114
|
}
|
|
131
|
-
const lines = results.map((
|
|
115
|
+
const lines = results.map(({ attempt, status }) => (` ${attempt.projectId}: ${attempt.beforeCommit.slice(0, 12)} -> ${(attempt.afterCommit ?? attempt.candidateCommit ?? attempt.beforeCommit).slice(0, 12)} (${status})`));
|
|
132
116
|
return {
|
|
133
|
-
output: `
|
|
134
|
-
data: { taskId, integrations: results }
|
|
117
|
+
output: `Upstream Integration results for Task ${task.id}:\n${lines.join("\n")}\n`,
|
|
118
|
+
data: { taskId: task.id, integrations: results }
|
|
135
119
|
};
|
|
136
120
|
}
|
|
@@ -16,7 +16,7 @@ export const CONFIG_DEFINITIONS = Object.freeze([
|
|
|
16
16
|
{ key: "delivery-timeout-seconds", domain: "runtime", property: "deliveryTimeoutSeconds", label: "Delivery timeout", summary: "Total control-plane delivery retry budget, 5-600 seconds (default: 120).", takesEffect: "After the Controller restarts; internal retry cadence remains automatic." },
|
|
17
17
|
{ key: "leader-next-action", domain: "workflow", property: "leaderNextActionMode", label: "Leader next-action mode", summary: "Leader next-action mode: display, warn, or enforce (default: display).", takesEffect: "The next Leader next-action projection or gate." },
|
|
18
18
|
{ key: "leader-semantic-budget-turns", domain: "workflow", property: "leaderSemanticBudgetTurns", label: "Leader semantic budget", summary: "Consecutive completed Leader turns without durable delivery progress before warning, 1-20 (default: 3).", takesEffect: "The next Leader delivery guard evaluation." },
|
|
19
|
-
{ key: "review", domain: "workflow", property: "review", label: "Review", summary: "Optional global WorkItem review rule: a configured Role plus always, leader, or final trigger
|
|
19
|
+
{ key: "review", domain: "workflow", property: "review", label: "Review", summary: "Optional global WorkItem review rule: a configured Role plus always, leader, or final trigger. Setup leaves it disabled so the Leader may review directly or delegate selectively.", takesEffect: "The next Candidate snapshot; in-flight Candidates and ReviewRounds keep their policy." },
|
|
20
20
|
{ key: "resources-gc-mode", domain: "resources", property: "resourcesGcMode", label: "Resources GC mode", summary: "Resource GC mode: report or quarantine (default: report).", takesEffect: "The next resource GC operation." },
|
|
21
21
|
{ key: "resources-gc-auto-quarantine", domain: "resources", property: "resourcesGcAutoQuarantine", label: "Resources GC auto-quarantine", summary: "Automatically quarantine eligible terminal Task resources (default: false).", takesEffect: "The next eligible automatic resource GC operation." },
|
|
22
22
|
{ key: "resources-quarantine-ttl-hours", domain: "resources", property: "resourcesQuarantineTtlHours", label: "Resource quarantine TTL", summary: "Default observation window before purge, 1-720 hours (default: 24).", takesEffect: "The next resource GC command without an explicit TTL override." },
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { normalizedUniqueText, optionalText, requireIdentity, requirePositiveInteger, requireText, requireTimestamp } from "../domain/validation.js";
|
|
3
|
+
import { MAX_CONTEXT_SOURCE_TURN_BYTES, MAX_CONTEXT_SOURCE_TURNS } from "./sourceTurnContext.js";
|
|
3
4
|
export const CONTEXT_SNAPSHOT_SCHEMA_VERSION = 1;
|
|
4
5
|
export const CONTEXT_SNAPSHOT_MAX_RESOURCES = 256;
|
|
5
6
|
export const CONTEXT_SNAPSHOT_MAX_RESOURCE_BYTES = 4 * 1024 * 1024;
|
|
@@ -184,7 +185,16 @@ function validateContextRef(value) {
|
|
|
184
185
|
function normalizeSnapshotResources(values, refs) {
|
|
185
186
|
if (!Array.isArray(values))
|
|
186
187
|
throw new Error("Context Snapshot resources must be an array.");
|
|
187
|
-
|
|
188
|
+
const sourceTurnCount = values.filter((resource) => (resource !== null
|
|
189
|
+
&& typeof resource === "object"
|
|
190
|
+
&& !Array.isArray(resource)
|
|
191
|
+
&& resource.ref !== null
|
|
192
|
+
&& typeof resource.ref === "object"
|
|
193
|
+
&& resource.ref.store === "source-turn")).length;
|
|
194
|
+
if (sourceTurnCount > MAX_CONTEXT_SOURCE_TURNS) {
|
|
195
|
+
throw new Error(`Context Snapshot exceeds ${MAX_CONTEXT_SOURCE_TURNS} source Turns.`);
|
|
196
|
+
}
|
|
197
|
+
if (values.length - sourceTurnCount > CONTEXT_SNAPSHOT_MAX_RESOURCES) {
|
|
188
198
|
throw new Error(`Context Snapshot exceeds ${CONTEXT_SNAPSHOT_MAX_RESOURCES} resources.`);
|
|
189
199
|
}
|
|
190
200
|
const byRef = new Map(refs.map((ref) => [contextRefKey(ref), ref]));
|
|
@@ -210,9 +220,14 @@ function normalizeSnapshotResources(values, refs) {
|
|
|
210
220
|
|| new Set(resources.map(({ ref }) => contextRefKey(ref))).size !== refs.length) {
|
|
211
221
|
throw new Error("Context Snapshot resources must cover every ref exactly once.");
|
|
212
222
|
}
|
|
213
|
-
|
|
223
|
+
const ordinary = resources.filter(({ ref }) => ref.store !== "source-turn");
|
|
224
|
+
const sourceTurns = resources.filter(({ ref }) => ref.store === "source-turn");
|
|
225
|
+
if (Buffer.byteLength(JSON.stringify(ordinary), "utf8") > CONTEXT_SNAPSHOT_MAX_BYTES) {
|
|
214
226
|
throw new Error(`Context Snapshot resources exceed ${CONTEXT_SNAPSHOT_MAX_BYTES} bytes.`);
|
|
215
227
|
}
|
|
228
|
+
if (Buffer.byteLength(JSON.stringify(sourceTurns), "utf8") > MAX_CONTEXT_SOURCE_TURN_BYTES) {
|
|
229
|
+
throw new Error(`Context Snapshot source Turns exceed ${MAX_CONTEXT_SOURCE_TURN_BYTES} bytes.`);
|
|
230
|
+
}
|
|
216
231
|
return Object.freeze(resources);
|
|
217
232
|
}
|
|
218
233
|
function contextRefKey(ref) {
|
|
@@ -130,7 +130,10 @@ export function materializeSessionBootstrap(input) {
|
|
|
130
130
|
roleProfileRef: { digest: profileDigest, path: roleProfilePath },
|
|
131
131
|
contextProtocol: input.owner.scope === "global"
|
|
132
132
|
? {
|
|
133
|
-
|
|
133
|
+
// A Global Session may move between Yui's remote TUI and Desktop.
|
|
134
|
+
// Carry its read entry in the Thread-visible Manifest instead of
|
|
135
|
+
// depending on the client process that happened to create it.
|
|
136
|
+
loadCommand: renderGlobalContextCommand(input.controlPlane, home, input.role.name)
|
|
134
137
|
}
|
|
135
138
|
: {
|
|
136
139
|
loadCommand: "\"$YUI_SESSION_CLI\" task turn context \"$YUI_TASK_ID/<turn-id>\" --json",
|
|
@@ -148,6 +151,20 @@ export function materializeSessionBootstrap(input) {
|
|
|
148
151
|
descriptorPath
|
|
149
152
|
});
|
|
150
153
|
}
|
|
154
|
+
function renderGlobalContextCommand(controlPlane, home, roleName) {
|
|
155
|
+
return [
|
|
156
|
+
`YUI_HOME=${quoteShellWord(home)}`,
|
|
157
|
+
quoteShellWord(controlPlane.executable),
|
|
158
|
+
quoteShellWord(controlPlane.cliEntry),
|
|
159
|
+
"session",
|
|
160
|
+
"context",
|
|
161
|
+
quoteShellWord(roleName),
|
|
162
|
+
"--json"
|
|
163
|
+
].join(" ");
|
|
164
|
+
}
|
|
165
|
+
function quoteShellWord(value) {
|
|
166
|
+
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
|
167
|
+
}
|
|
151
168
|
/**
|
|
152
169
|
* Retargets known managed wrappers to the current resolved control plane after
|
|
153
170
|
* a compatible update. Only a valid Session Manifest may nominate a wrapper,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { MAX_TURN_RESULT_OUTPUT_BYTES } from "../domain/agentResultTransport.js";
|
|
2
|
+
export const MAX_SYNTHESIS_SOURCE_TURNS = 8;
|
|
3
|
+
export const MAX_CONTEXT_SOURCE_TURNS = MAX_SYNTHESIS_SOURCE_TURNS + 1;
|
|
4
|
+
export const MAX_CONTEXT_SOURCE_TURN_BYTES = MAX_CONTEXT_SOURCE_TURNS * (MAX_TURN_RESULT_OUTPUT_BYTES + 16 * 1024);
|
|
5
|
+
/**
|
|
6
|
+
* Frozen input for synthesis. The main Agent needs the producer identity and
|
|
7
|
+
* exact result, not another copy of its prompt history, launch configuration,
|
|
8
|
+
* or workspace descriptor.
|
|
9
|
+
*/
|
|
10
|
+
export function sourceTurnContextValue(turn) {
|
|
11
|
+
if (turn.result === undefined) {
|
|
12
|
+
throw new Error(`Source Turn has no result: ${turn.id}.`);
|
|
13
|
+
}
|
|
14
|
+
return Object.freeze({
|
|
15
|
+
schemaVersion: 1,
|
|
16
|
+
id: turn.id,
|
|
17
|
+
taskId: turn.taskId,
|
|
18
|
+
roleName: turn.roleName,
|
|
19
|
+
purpose: turn.purpose,
|
|
20
|
+
...(turn.workItemId === undefined ? {} : { workItemId: turn.workItemId }),
|
|
21
|
+
...(turn.reviewRoundId === undefined ? {} : { reviewRoundId: turn.reviewRoundId }),
|
|
22
|
+
...(turn.executionGroupId === undefined
|
|
23
|
+
? {}
|
|
24
|
+
: { executionGroupId: turn.executionGroupId }),
|
|
25
|
+
...(turn.executionLaneId === undefined ? {} : { executionLaneId: turn.executionLaneId }),
|
|
26
|
+
result: turn.result,
|
|
27
|
+
createdAt: turn.createdAt,
|
|
28
|
+
updatedAt: turn.updatedAt
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
2
2
|
import { TASK_COMPLETION_PUBLISHED_TREE_AUTHORIZED_EVENT } from "../task/publicationReference.js";
|
|
3
3
|
import { TURN_INPUT_MAX_DELTAS } from "./turnInputContract.js";
|
|
4
|
+
import { assertWorkItemDependenciesCompleted } from "../workItem/dependencyGate.js";
|
|
5
|
+
import { workItemExecutionGroupById } from "../workItem/workItem.js";
|
|
4
6
|
import { contextContentDigest, contextSnapshotRef, createContextSnapshot, validateContextSnapshot } from "./contextSnapshot.js";
|
|
7
|
+
import { sourceTurnContextValue } from "./sourceTurnContext.js";
|
|
5
8
|
export const TURN_CONTEXT_PACK_SCHEMA_VERSION = 1;
|
|
6
9
|
export const TURN_CONTEXT_PACK_MAX_REFS = 256;
|
|
7
10
|
export const TURN_CONTEXT_PACK_MAX_BYTES = 8 * 1024 * 1024;
|
|
@@ -20,7 +23,10 @@ export function freezeTurnContextSnapshot(store, run, now, frozenBy = "controlle
|
|
|
20
23
|
throw new Error(`Turn Context baseline is missing or drifted: ${baselineRef.id}.`);
|
|
21
24
|
}
|
|
22
25
|
validateContextSnapshot(baseline);
|
|
23
|
-
const overlays =
|
|
26
|
+
const overlays = [
|
|
27
|
+
...collectTurnContextOverlays(store, run),
|
|
28
|
+
...collectSourceTurnContext(store, run)
|
|
29
|
+
];
|
|
24
30
|
const resources = [...new Map([...baseline.resources, ...overlays].map((entry) => [
|
|
25
31
|
contextRefIdentity(entry.ref),
|
|
26
32
|
entry
|
|
@@ -76,11 +82,11 @@ export function freezeTurnContextSnapshot(store, run, now, frozenBy = "controlle
|
|
|
76
82
|
}
|
|
77
83
|
/**
|
|
78
84
|
* Freeze the shared, role-neutral ContextSnapshot anchored by one WorkItem
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
85
|
+
* ExecutionAssignment. Turn snapshots remain role-specific; this record is
|
|
86
|
+
* the durable Assignment baseline and freezes the current WorkItem facts so
|
|
87
|
+
* the Group never depends on ambient latest state.
|
|
82
88
|
*/
|
|
83
|
-
export function
|
|
89
|
+
export function freezeWorkItemExecutionAssignmentContextSnapshot(store, input, now) {
|
|
84
90
|
const task = store.getTask(input.taskId);
|
|
85
91
|
if (task === null)
|
|
86
92
|
throw new Error(`Task not found: ${input.taskId}.`);
|
|
@@ -91,11 +97,11 @@ export function freezeExecutionStageContextSnapshot(store, input, now) {
|
|
|
91
97
|
materialize("L2", "task", task.id, task),
|
|
92
98
|
materialize("L3", "work-item", workItem.id, workItem)
|
|
93
99
|
];
|
|
100
|
+
assertWorkItemDependenciesCompleted(store, workItem);
|
|
94
101
|
for (const dependencyId of workItem.dependsOn) {
|
|
95
102
|
const dependency = store.getWorkItem(task.id, dependencyId);
|
|
96
|
-
if (dependency === null
|
|
97
|
-
throw new Error(`
|
|
98
|
-
}
|
|
103
|
+
if (dependency === null)
|
|
104
|
+
throw new Error(`WorkItem dependency disappeared: ${dependencyId}.`);
|
|
99
105
|
materialized.push(materialize("L3", "accepted-work-item", dependency.id, dependency));
|
|
100
106
|
}
|
|
101
107
|
for (const binding of task.projectBindings) {
|
|
@@ -112,42 +118,20 @@ export function freezeExecutionStageContextSnapshot(store, input, now) {
|
|
|
112
118
|
contextRefIdentity(entry.ref),
|
|
113
119
|
entry
|
|
114
120
|
])).values()].sort((left, right) => (contextRefIdentity(left.ref).localeCompare(contextRefIdentity(right.ref))));
|
|
115
|
-
const
|
|
116
|
-
.filter((candidate) => candidate.scope === "workitem"
|
|
117
|
-
&& candidate.scopeRef === workItem.id)
|
|
118
|
-
.sort((left, right) => left.sequence - right.sequence)
|
|
119
|
-
.at(-1);
|
|
120
|
-
const workItemSnapshot = createContextSnapshot({
|
|
121
|
-
id: store.nextContextSnapshotId(task.id),
|
|
122
|
-
taskId: task.id,
|
|
123
|
-
scope: "workitem",
|
|
124
|
-
scopeRef: workItem.id,
|
|
125
|
-
sequence: (previousWorkItem?.sequence ?? 0) + 1,
|
|
126
|
-
refs: resources.map(({ ref }) => ref),
|
|
127
|
-
resources,
|
|
128
|
-
acceptRefs: [`work-item:${workItem.id}:acceptance`],
|
|
129
|
-
...(previousWorkItem === undefined
|
|
130
|
-
? {}
|
|
131
|
-
: { parentRef: contextSnapshotRef(previousWorkItem) }),
|
|
132
|
-
frozenAt: now,
|
|
133
|
-
frozenBy: "controller"
|
|
134
|
-
});
|
|
135
|
-
store.saveContextSnapshot(workItemSnapshot);
|
|
136
|
-
const stageSnapshot = createContextSnapshot({
|
|
121
|
+
const assignmentSnapshot = createContextSnapshot({
|
|
137
122
|
id: store.nextContextSnapshotId(task.id),
|
|
138
123
|
taskId: task.id,
|
|
139
124
|
scope: "stage",
|
|
140
125
|
scopeRef: input.executionGroupId,
|
|
141
|
-
sequence:
|
|
126
|
+
sequence: 1,
|
|
142
127
|
refs: resources.map(({ ref }) => ref),
|
|
143
128
|
resources,
|
|
144
129
|
acceptRefs: [`work-item:${workItem.id}:acceptance`],
|
|
145
|
-
parentRef: contextSnapshotRef(workItemSnapshot),
|
|
146
130
|
frozenAt: now,
|
|
147
131
|
frozenBy: "controller"
|
|
148
132
|
});
|
|
149
|
-
store.saveContextSnapshot(
|
|
150
|
-
return
|
|
133
|
+
store.saveContextSnapshot(assignmentSnapshot);
|
|
134
|
+
return assignmentSnapshot;
|
|
151
135
|
}
|
|
152
136
|
/**
|
|
153
137
|
* Freeze the role-neutral baseline for one Reviewer ExecutionGroup before
|
|
@@ -282,10 +266,35 @@ export function buildTurnContextPack(store, taskId, turnId) {
|
|
|
282
266
|
returnedBytes: preliminaryBytes,
|
|
283
267
|
truncated: false
|
|
284
268
|
}),
|
|
285
|
-
digest
|
|
269
|
+
digest,
|
|
270
|
+
liveTaskState: readLiveTaskState(store, taskId)
|
|
286
271
|
});
|
|
287
272
|
return pack;
|
|
288
273
|
}
|
|
274
|
+
/** Reads the Task's current in-flight execution, outside the frozen contract. */
|
|
275
|
+
function readLiveTaskState(store, taskId) {
|
|
276
|
+
const activeTurns = store.listTurns(taskId)
|
|
277
|
+
.filter((turn) => turn.status === "active")
|
|
278
|
+
.map((turn) => Object.freeze({
|
|
279
|
+
turnId: turn.id,
|
|
280
|
+
roleName: turn.roleName,
|
|
281
|
+
purpose: turn.purpose,
|
|
282
|
+
...(turn.workItemId === undefined ? {} : { workItemId: turn.workItemId }),
|
|
283
|
+
...(turn.reviewRoundId === undefined ? {} : { reviewRoundId: turn.reviewRoundId })
|
|
284
|
+
}));
|
|
285
|
+
const activeTaskReviews = store.listReviewRounds(taskId)
|
|
286
|
+
.filter((round) => ((round.scope ?? "work-item") === "task"
|
|
287
|
+
&& (round.status === "pending" || round.status === "running")))
|
|
288
|
+
.map((round) => Object.freeze({
|
|
289
|
+
reviewRoundId: round.id,
|
|
290
|
+
reviewerRoleName: round.reviewerRoleName,
|
|
291
|
+
status: round.status
|
|
292
|
+
}));
|
|
293
|
+
return Object.freeze({
|
|
294
|
+
activeTurns: Object.freeze(activeTurns),
|
|
295
|
+
activeTaskReviews: Object.freeze(activeTaskReviews)
|
|
296
|
+
});
|
|
297
|
+
}
|
|
289
298
|
export function expandTurnContextRef(store, taskId, turnId, refId, refStore) {
|
|
290
299
|
const pack = buildTurnContextPack(store, taskId, turnId);
|
|
291
300
|
const authorized = pack.pointers.filter((ref) => (ref.refId === refId && (refStore === undefined || ref.store === refStore)));
|
|
@@ -352,20 +361,18 @@ function collectAuthorizedContext(store, run) {
|
|
|
352
361
|
result.push(materialize("L2", "task-brief", task.id, brief));
|
|
353
362
|
}
|
|
354
363
|
result.push(...collectTurnContextOverlays(store, run));
|
|
364
|
+
result.push(...collectSourceTurnContext(store, run));
|
|
355
365
|
if (run.workItemId !== undefined) {
|
|
356
366
|
const item = store.getWorkItem(task.id, run.workItemId);
|
|
357
367
|
if (item === null)
|
|
358
368
|
throw new Error(`Turn WorkItem not found: ${run.workItemId}.`);
|
|
359
369
|
result.push(materialize("L3", "work-item", item.id, item));
|
|
360
370
|
if (view === "worker") {
|
|
371
|
+
assertWorkItemDependenciesCompleted(store, item);
|
|
361
372
|
for (const dependencyId of item.dependsOn) {
|
|
362
373
|
const dependency = store.getWorkItem(task.id, dependencyId);
|
|
363
|
-
if (dependency === null
|
|
364
|
-
|
|
365
|
-
throw new Error(`Turn WorkItem dependency is not accepted: ${dependencyId}.`);
|
|
366
|
-
}
|
|
367
|
-
if (dependency.status === "retired")
|
|
368
|
-
continue;
|
|
374
|
+
if (dependency === null)
|
|
375
|
+
throw new Error(`WorkItem dependency disappeared: ${dependencyId}.`);
|
|
369
376
|
result.push(materialize("L3", "accepted-work-item", dependency.id, dependency));
|
|
370
377
|
}
|
|
371
378
|
}
|
|
@@ -375,10 +382,21 @@ function collectAuthorizedContext(store, run) {
|
|
|
375
382
|
if (round === null)
|
|
376
383
|
throw new Error(`Turn ReviewRound not found: ${run.reviewRoundId}.`);
|
|
377
384
|
result.push(materialize("L3", "review-round", round.id, round));
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
385
|
+
if (round.deltaRecheck !== undefined) {
|
|
386
|
+
const previous = store.getReviewRound(task.id, round.deltaRecheck.previousReviewRoundId);
|
|
387
|
+
const previousTurn = previous?.reviewerTurnId === undefined
|
|
388
|
+
? null
|
|
389
|
+
: store.getTurn(task.id, previous.reviewerTurnId);
|
|
390
|
+
if (previous === null
|
|
391
|
+
|| previous === undefined
|
|
392
|
+
|| previous.status !== "completed"
|
|
393
|
+
|| previousTurn === null
|
|
394
|
+
|| previousTurn.status !== "completed"
|
|
395
|
+
|| previousTurn.result === undefined) {
|
|
396
|
+
throw new Error(`Delta recheck source Review result is unavailable: ${round.deltaRecheck.previousReviewRoundId}.`);
|
|
397
|
+
}
|
|
398
|
+
result.push(materialize("L3", "review-round", previous.id, previous));
|
|
399
|
+
result.push(materialize("L4", "source-turn", previousTurn.id, sourceTurnContextValue(previousTurn)));
|
|
382
400
|
}
|
|
383
401
|
}
|
|
384
402
|
for (const binding of task.projectBindings) {
|
|
@@ -405,9 +423,6 @@ function collectAuthorizedContext(store, run) {
|
|
|
405
423
|
for (const round of store.listReviewRounds(task.id).slice(-16)) {
|
|
406
424
|
result.push(materialize("L3", "review-round", round.id, round));
|
|
407
425
|
}
|
|
408
|
-
for (const finding of store.listReviewFindings(task.id)) {
|
|
409
|
-
result.push(materialize("L3", "review-finding", finding.id, finding));
|
|
410
|
-
}
|
|
411
426
|
for (const turn of operationalTaskRecords(store.listTurns(task.id), events, "turn").slice(-24)) {
|
|
412
427
|
result.push(materialize("L4", "turn", turn.id, turn));
|
|
413
428
|
}
|
|
@@ -454,6 +469,50 @@ function collectTurnContextOverlays(store, run) {
|
|
|
454
469
|
: [])
|
|
455
470
|
];
|
|
456
471
|
}
|
|
472
|
+
function collectSourceTurnContext(store, run) {
|
|
473
|
+
if (run.sourceExecutionGroupId === undefined)
|
|
474
|
+
return [];
|
|
475
|
+
const group = run.purpose === "execution"
|
|
476
|
+
? (() => {
|
|
477
|
+
if (run.workItemId === undefined) {
|
|
478
|
+
throw new Error("Execution synthesis source requires a WorkItem.");
|
|
479
|
+
}
|
|
480
|
+
const item = store.getWorkItem(run.taskId, run.workItemId);
|
|
481
|
+
if (item === null)
|
|
482
|
+
throw new Error(`Turn WorkItem not found: ${run.workItemId}.`);
|
|
483
|
+
return workItemExecutionGroupById(item, run.sourceExecutionGroupId);
|
|
484
|
+
})()
|
|
485
|
+
: (() => {
|
|
486
|
+
if (run.reviewRoundId === undefined) {
|
|
487
|
+
throw new Error("Review synthesis source requires a ReviewRound.");
|
|
488
|
+
}
|
|
489
|
+
const round = store.getReviewRound(run.taskId, run.reviewRoundId);
|
|
490
|
+
if (round === null)
|
|
491
|
+
throw new Error(`Turn ReviewRound not found: ${run.reviewRoundId}.`);
|
|
492
|
+
return round.executionGroup?.id === run.sourceExecutionGroupId
|
|
493
|
+
? round.executionGroup
|
|
494
|
+
: undefined;
|
|
495
|
+
})();
|
|
496
|
+
if (group === undefined) {
|
|
497
|
+
throw new Error(`Source ExecutionGroup not found: ${run.sourceExecutionGroupId}.`);
|
|
498
|
+
}
|
|
499
|
+
return [...group.lanes]
|
|
500
|
+
.sort((left, right) => left.ordinal - right.ordinal || left.id.localeCompare(right.id))
|
|
501
|
+
.flatMap((lane) => {
|
|
502
|
+
if (lane.disposition !== "succeeded" || lane.successfulTurnId === undefined)
|
|
503
|
+
return [];
|
|
504
|
+
const source = store.getTurn(run.taskId, lane.successfulTurnId);
|
|
505
|
+
if (source === null
|
|
506
|
+
|| source.status !== "completed"
|
|
507
|
+
|| source.result === undefined
|
|
508
|
+
|| source.executionGroupId !== group.id
|
|
509
|
+
|| source.executionLaneId !== lane.id
|
|
510
|
+
|| source.roleName !== lane.roleName) {
|
|
511
|
+
throw new Error(`Successful source Turn is missing or drifted: ${group.id}/${lane.id}.`);
|
|
512
|
+
}
|
|
513
|
+
return [materialize("L4", "source-turn", source.id, sourceTurnContextValue(source))];
|
|
514
|
+
});
|
|
515
|
+
}
|
|
457
516
|
function materialize(layer, store, refId, value) {
|
|
458
517
|
const digest = contextContentDigest(value);
|
|
459
518
|
const record = value;
|