@zq-silk/yui 0.10.1 → 0.11.1

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 (46) hide show
  1. package/README.md +53 -0
  2. package/dist/cli/commandCatalog.js +28 -7
  3. package/dist/cli.js +50 -47
  4. package/dist/commands/projectCommands.js +69 -6
  5. package/dist/commands/taskCommands.js +639 -89
  6. package/dist/commands/taskContextCommand.js +78 -27
  7. package/dist/commands/taskNextActionCommand.js +13 -2
  8. package/dist/commands/taskOverviewCommand.js +21 -5
  9. package/dist/commands/taskUpstreamCommands.js +136 -0
  10. package/dist/context/runContextPack.js +184 -17
  11. package/dist/controller/agentRuntimeObserver.js +31 -20
  12. package/dist/controller/fileSchedulerStoreAdapter.js +7 -13
  13. package/dist/execution/candidateConvergence.js +623 -0
  14. package/dist/execution/executionGroup.js +255 -13
  15. package/dist/execution/executionHealth.js +324 -0
  16. package/dist/execution/resourceBroker.js +425 -0
  17. package/dist/executor/fileRoleLaunchPlanner.js +6 -9
  18. package/dist/executor/workspacePreflightClassification.js +117 -0
  19. package/dist/lifecycle/exactRunTerminalization.js +13 -2
  20. package/dist/lifecycle/taskRoleSessionReset.js +4 -2
  21. package/dist/repository/taskBaseFreshness.js +26 -1
  22. package/dist/repository/taskWorkspacePreparer.js +17 -1
  23. package/dist/review/reviewRound.js +27 -6
  24. package/dist/run/agentRun.js +2 -2
  25. package/dist/run/recoveryProjection.js +15 -0
  26. package/dist/runtime/runtimeContinuationProjection.js +7 -0
  27. package/dist/scheduler/actionability.js +169 -3
  28. package/dist/scheduler/activeTaskProgress.js +15 -10
  29. package/dist/scheduler/leaderWakeupProcessor.js +17 -1
  30. package/dist/scheduler/taskExecutionProjection.js +105 -8
  31. package/dist/scheduler/taskObservabilityProjection.js +282 -0
  32. package/dist/storage/migration/productionRegistry.js +14 -0
  33. package/dist/storage/sqliteStore.js +12 -0
  34. package/dist/storage/taskStore.js +1 -1
  35. package/dist/storage/upgrade/sqliteStateMigration.js +7 -3
  36. package/dist/task/completionReadiness.js +1 -1
  37. package/dist/task/nextAction.js +314 -2
  38. package/dist/web/assets/client/components.js +116 -0
  39. package/dist/web/assets/client/i18n.js +66 -0
  40. package/dist/web/assets/client/view.js +15 -0
  41. package/dist/web/assets/styles/cards.js +23 -0
  42. package/dist/web/assets/styles/responsive.js +2 -0
  43. package/dist/web/webSnapshot.js +8 -2
  44. package/dist/workItem/workItem.js +262 -5
  45. package/i18n/README.zh-CN.md +42 -0
  46. package/package.json +1 -1
@@ -6,7 +6,47 @@ export const RUN_CONTEXT_PACK_SCHEMA_VERSION = 1;
6
6
  export const RUN_CONTEXT_PACK_MAX_REFS = 256;
7
7
  export const RUN_CONTEXT_PACK_MAX_BYTES = 8 * 1024 * 1024;
8
8
  export const RUN_CONTEXT_EXPAND_MAX_BYTES = 4 * 1024 * 1024;
9
- export function freezeRunContextSnapshot(store, run, now, frozenBy = "controller") {
9
+ export function freezeRunContextSnapshot(store, run, now, frozenBy = "controller", baselineRef) {
10
+ if (baselineRef !== undefined) {
11
+ const baseline = store.getContextSnapshot(run.taskId, baselineRef.id);
12
+ if (baseline === null
13
+ || baseline.taskId !== run.taskId
14
+ || baselineRef.taskId !== run.taskId
15
+ || baseline.digest !== baselineRef.digest
16
+ || baseline.sequence !== baselineRef.sequence
17
+ || baseline.scope !== "stage"
18
+ || baselineRef.scope !== baseline.scope
19
+ || baseline.scopeRef !== baselineRef.scopeRef) {
20
+ throw new Error(`Run Context baseline is missing or drifted: ${baselineRef.id}.`);
21
+ }
22
+ validateContextSnapshot(baseline);
23
+ const overlays = collectRunContextOverlays(store, run);
24
+ const resources = [...new Map([...baseline.resources, ...overlays].map((entry) => [
25
+ contextRefIdentity(entry.ref),
26
+ entry
27
+ ])).values()].sort((left, right) => (contextRefIdentity(left.ref).localeCompare(contextRefIdentity(right.ref))));
28
+ const previous = store.listContextSnapshots(run.taskId)
29
+ .filter((candidate) => candidate.scope === baseline.scope
30
+ && candidate.scopeRef === baseline.scopeRef)
31
+ .sort((left, right) => left.sequence - right.sequence)
32
+ .at(-1);
33
+ const snapshot = createContextSnapshot({
34
+ id: store.nextContextSnapshotId(run.taskId),
35
+ taskId: run.taskId,
36
+ scope: baseline.scope,
37
+ scopeRef: baseline.scopeRef,
38
+ sequence: (previous?.sequence ?? baseline.sequence) + 1,
39
+ refs: resources.map(({ ref }) => ref),
40
+ resources,
41
+ ...(baseline.repoCommit === undefined ? {} : { repoCommit: baseline.repoCommit }),
42
+ acceptRefs: baseline.acceptRefs,
43
+ parentRef: contextSnapshotRef(baseline),
44
+ frozenAt: now,
45
+ frozenBy
46
+ });
47
+ store.saveContextSnapshot(snapshot);
48
+ return snapshot;
49
+ }
10
50
  const scope = run.reviewRoundId !== undefined
11
51
  ? "stage"
12
52
  : run.workItemId !== undefined
@@ -34,6 +74,127 @@ export function freezeRunContextSnapshot(store, run, now, frozenBy = "controller
34
74
  store.saveContextSnapshot(snapshot);
35
75
  return snapshot;
36
76
  }
77
+ /**
78
+ * Freeze the shared, role-neutral ContextSnapshot anchored by one WorkItem
79
+ * exploration stage Group. AgentRun snapshots remain role-specific; this
80
+ * record is the durable stage baseline and derives from a fresh WorkItem
81
+ * snapshot so the Group never depends on ambient latest state.
82
+ */
83
+ export function freezeExecutionStageContextSnapshot(store, input, now) {
84
+ const task = store.getTask(input.taskId);
85
+ if (task === null)
86
+ throw new Error(`Task not found: ${input.taskId}.`);
87
+ const workItem = store.getWorkItem(input.taskId, input.workItemId);
88
+ if (workItem === null)
89
+ throw new Error(`WorkItem not found: ${input.workItemId}.`);
90
+ const materialized = [
91
+ materialize("L2", "task", task.id, task),
92
+ materialize("L3", "work-item", workItem.id, workItem)
93
+ ];
94
+ for (const dependencyId of workItem.dependsOn) {
95
+ const dependency = store.getWorkItem(task.id, dependencyId);
96
+ if (dependency === null || dependency.status !== "completed") {
97
+ throw new Error(`Exploration stage dependency is not accepted: ${dependencyId}.`);
98
+ }
99
+ materialized.push(materialize("L3", "accepted-work-item", dependency.id, dependency));
100
+ }
101
+ for (const binding of task.projectBindings) {
102
+ const project = store.getProject(binding.projectId);
103
+ if (project === null)
104
+ throw new Error(`Run Project not found: ${binding.projectId}.`);
105
+ const { knowledge, ...projectPolicy } = project;
106
+ materialized.push(materialize("L1", "project-policy", project.id, projectPolicy));
107
+ for (const entry of knowledge.filter(({ status }) => status === "active")) {
108
+ materialized.push(materialize("L1", "project-knowledge", `${project.id}:${entry.id}`, { projectId: project.id, ...entry }));
109
+ }
110
+ }
111
+ const resources = [...new Map(materialized.map((entry) => [
112
+ contextRefIdentity(entry.ref),
113
+ entry
114
+ ])).values()].sort((left, right) => (contextRefIdentity(left.ref).localeCompare(contextRefIdentity(right.ref))));
115
+ const previousWorkItem = store.listContextSnapshots(task.id)
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({
137
+ id: store.nextContextSnapshotId(task.id),
138
+ taskId: task.id,
139
+ scope: "stage",
140
+ scopeRef: input.executionGroupId,
141
+ sequence: workItemSnapshot.sequence + 1,
142
+ refs: resources.map(({ ref }) => ref),
143
+ resources,
144
+ acceptRefs: [`work-item:${workItem.id}:acceptance`],
145
+ parentRef: contextSnapshotRef(workItemSnapshot),
146
+ frozenAt: now,
147
+ frozenBy: "controller"
148
+ });
149
+ store.saveContextSnapshot(stageSnapshot);
150
+ return stageSnapshot;
151
+ }
152
+ /**
153
+ * Freeze the role-neutral baseline for one Reviewer ExecutionGroup before
154
+ * admission. Delayed or retried sibling Lanes reuse the same immutable
155
+ * ReviewRound/Task/Project values and add only their Role overlay.
156
+ */
157
+ export function freezeReviewStageContextSnapshot(store, input, now) {
158
+ const existing = store.listContextSnapshots(input.taskId)
159
+ .filter((candidate) => candidate.scope === "stage"
160
+ && candidate.scopeRef === input.executionGroupId
161
+ && candidate.parentRef === undefined)
162
+ .sort((left, right) => left.sequence - right.sequence)
163
+ .at(0);
164
+ if (existing !== undefined)
165
+ return validateContextSnapshot(existing);
166
+ const round = store.getReviewRound(input.taskId, input.reviewRoundId);
167
+ if (round === null)
168
+ throw new Error(`ReviewRound not found: ${input.reviewRoundId}.`);
169
+ const materialized = collectAuthorizedContext(store, {
170
+ taskId: input.taskId,
171
+ roleName: round.reviewerRoleName,
172
+ purpose: "review",
173
+ ...(round.workItemId === undefined ? {} : { workItemId: round.workItemId }),
174
+ reviewRoundId: round.id
175
+ }).filter(({ ref }) => (ref.store !== "role-profile" && ref.store !== "managed-workspace"));
176
+ if (!materialized.some(({ ref }) => (ref.store === "review-round" && ref.refId === round.id))) {
177
+ throw new Error(`ReviewRound Context baseline is unavailable: ${round.id}.`);
178
+ }
179
+ const resources = [...new Map(materialized.map((entry) => [
180
+ contextRefIdentity(entry.ref),
181
+ entry
182
+ ])).values()].sort((left, right) => (contextRefIdentity(left.ref).localeCompare(contextRefIdentity(right.ref))));
183
+ const snapshot = createContextSnapshot({
184
+ id: store.nextContextSnapshotId(input.taskId),
185
+ taskId: input.taskId,
186
+ scope: "stage",
187
+ scopeRef: input.executionGroupId,
188
+ sequence: 1,
189
+ refs: resources.map(({ ref }) => ref),
190
+ resources,
191
+ acceptRefs: round.workItemId === undefined ? [] : [`work-item:${round.workItemId}:acceptance`],
192
+ frozenAt: now,
193
+ frozenBy: "controller"
194
+ });
195
+ store.saveContextSnapshot(snapshot);
196
+ return snapshot;
197
+ }
37
198
  /** Bounded changed-ref hint between one frozen Snapshot and its exact parent. */
38
199
  export function contextSnapshotDeltaRefIds(store, snapshot) {
39
200
  validateContextSnapshot(snapshot);
@@ -190,22 +351,7 @@ function collectAuthorizedContext(store, run) {
190
351
  if (brief !== null && view === "leader") {
191
352
  result.push(materialize("L2", "task-brief", task.id, brief));
192
353
  }
193
- const role = store.getRole(task.id, run.roleName);
194
- if (role === null)
195
- throw new Error(`Run Role not found: ${task.id}/${run.roleName}.`);
196
- result.push(materialize("L1", "role-profile", role.name, {
197
- name: role.name,
198
- defaultAccess: role.defaultAccess,
199
- description: role.description,
200
- responsibilities: role.responsibilities ?? [],
201
- constraints: role.constraints ?? [],
202
- expectedOutput: role.expectedOutput,
203
- skills: role.skills ?? [],
204
- launchRevision: role.launchRevision
205
- }));
206
- if ("workspace" in run && run.workspace !== undefined) {
207
- result.push(materialize("L3", "managed-workspace", `${run.taskId}/${run.roleName}`, run.workspace));
208
- }
354
+ result.push(...collectRunContextOverlays(store, run));
209
355
  if (run.workItemId !== undefined) {
210
356
  const item = store.getWorkItem(task.id, run.workItemId);
211
357
  if (item === null)
@@ -287,6 +433,27 @@ function collectAuthorizedContext(store, run) {
287
433
  const unique = new Map(result.map((entry) => [contextRefIdentity(entry.ref), entry]));
288
434
  return [...unique.values()].sort((left, right) => (contextRefIdentity(left.ref).localeCompare(contextRefIdentity(right.ref))));
289
435
  }
436
+ /** Lane-specific context that may be layered over an immutable stage base. */
437
+ function collectRunContextOverlays(store, run) {
438
+ const role = store.getRole(run.taskId, run.roleName);
439
+ if (role === null)
440
+ throw new Error(`Run Role not found: ${run.taskId}/${run.roleName}.`);
441
+ return [
442
+ materialize("L1", "role-profile", role.name, {
443
+ name: role.name,
444
+ defaultAccess: role.defaultAccess,
445
+ description: role.description,
446
+ responsibilities: role.responsibilities ?? [],
447
+ constraints: role.constraints ?? [],
448
+ expectedOutput: role.expectedOutput,
449
+ skills: role.skills ?? [],
450
+ launchRevision: role.launchRevision
451
+ }),
452
+ ...("workspace" in run && run.workspace !== undefined
453
+ ? [materialize("L3", "managed-workspace", `${run.taskId}/${run.roleName}`, run.workspace)]
454
+ : [])
455
+ ];
456
+ }
290
457
  function materialize(layer, store, refId, value) {
291
458
  const digest = contextContentDigest(value);
292
459
  const record = value;
@@ -59,26 +59,37 @@ export class AgentRuntimeObserver {
59
59
  // latency can change completion order without changing observation
60
60
  // identity or the canonical sequence assigned to a source.
61
61
  const sequence = sequenceBase + index;
62
- if (existingState === undefined && freshSession && state.usage === undefined) {
63
- const zero = Object.freeze({
64
- semantics: "cumulative-session",
65
- inputTokens: 0,
66
- outputTokens: 0
67
- });
68
- this.inbox.enqueueObservation(createRuntimeObservation({
69
- schemaVersion: 2,
70
- eventId: observationId("baseline", fence, source.sourceId, "zero"),
71
- semanticKey: observationId("baseline", fence, source.sourceId, "zero"),
72
- kind: "activity.observed",
73
- authority: "controller",
74
- receivedAt: at,
75
- sequence,
76
- ordinal: 1,
77
- fence,
78
- payload: { activity: "model", usage: zero }
79
- }));
80
- state.usage = zero;
81
- dirty.add(`role:${fence.taskId}/${fence.roleName}`);
62
+ if (existingState === undefined && state.usage === undefined) {
63
+ // A fresh native Session begins at zero. A resumed Session instead
64
+ // freezes its first cumulative sample as this Run's lower-bound
65
+ // baseline so later samples can prove spend without charging usage
66
+ // that belongs to an earlier Run in the same native conversation.
67
+ const baseline = freshSession
68
+ ? Object.freeze({
69
+ semantics: "cumulative-session",
70
+ inputTokens: 0,
71
+ outputTokens: 0
72
+ })
73
+ : sample.usage?.semantics === "cumulative-session"
74
+ ? sample.usage
75
+ : undefined;
76
+ if (baseline !== undefined) {
77
+ const baselineKey = freshSession ? "zero" : JSON.stringify(baseline);
78
+ this.inbox.enqueueObservation(createRuntimeObservation({
79
+ schemaVersion: 2,
80
+ eventId: observationId("baseline", fence, source.sourceId, baselineKey),
81
+ semanticKey: observationId("baseline", fence, source.sourceId, baselineKey),
82
+ kind: "activity.observed",
83
+ authority: freshSession ? "controller" : "driver-inferred",
84
+ receivedAt: at,
85
+ sequence,
86
+ ordinal: 1,
87
+ fence,
88
+ payload: { activity: "model", usage: baseline }
89
+ }));
90
+ state.usage = baseline;
91
+ dirty.add(`role:${fence.taskId}/${fence.roleName}`);
92
+ }
82
93
  }
83
94
  const health = JSON.stringify([sample.status, sample.detail ?? null]);
84
95
  if (state.health !== health) {
@@ -30,7 +30,7 @@ import { wakeReason } from "../scheduler/wakeReason.js";
30
30
  import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey, clearMatchingLeaderStallAttention, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT } from "../scheduler/roleRunStall.js";
31
31
  import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
32
32
  import { providerContinuationKey } from "../runtime/providerContinuation.js";
33
- import { currentWorkItemExecutionGroup, updateWorkItemExecutionGroup, updateWorkItemStatus } from "../workItem/workItem.js";
33
+ import { currentWorkItemExecutionGroup, updateWorkItemExecutionGroup, updateWorkItemStatus, workItemOwnsUnresolvedExecutionLane } from "../workItem/workItem.js";
34
34
  import { recordExecutionLaneResult } from "../execution/executionGroup.js";
35
35
  import { formatAgentRunReceiptId, formatTaskRecordReference } from "../task/taskRecordReference.js";
36
36
  import { bindExecution, claimPending, claimInputDelivery as claimMailboxInputDelivery, completeInputDelivery as completeMailboxInputDelivery, completeProcessing, mailboxHasPending, mailboxHasWork, pendingLane, markInputDeliveryPushed as markMailboxInputDeliveryPushed, markInputDeliveryUnknown as markMailboxInputDeliveryUnknown, releaseInputDelivery as releaseMailboxInputDelivery, resolveInputDeliveryNotAccepted as resolveMailboxInputDeliveryNotAccepted, releaseProcessing } from "../coordination/workMailbox.js";
@@ -1547,11 +1547,7 @@ export class FileSchedulerStoreAdapter {
1547
1547
  if (item !== null && ![
1548
1548
  "completed", "failed", "retired"
1549
1549
  ].includes(item.status)) {
1550
- const group = currentWorkItemExecutionGroup(item);
1551
- const groupedPanel = group !== undefined
1552
- && (group.lanes.length > 1 || group.strategy.mode === "adaptive")
1553
- && group.resolution === undefined;
1554
- if (!groupedPanel) {
1550
+ if (!workItemOwnsUnresolvedExecutionLane(item, terminal.executionGroupId, terminal.executionLaneId)) {
1555
1551
  store.saveWorkItem(input.taskId, updateWorkItemStatus(item, "failed", input.now, summary));
1556
1552
  }
1557
1553
  }
@@ -1705,11 +1701,7 @@ export class FileSchedulerStoreAdapter {
1705
1701
  store.saveWorkItem(task.id, updateWorkItemExecutionGroup(workItem, recordExecutionLaneResult(group, currentRun.executionLaneId, { summary: input.summary }, "failed", input.now), input.now));
1706
1702
  }
1707
1703
  const laneUpdated = store.getWorkItem(task.id, currentRun.workItemId);
1708
- const laneGroup = currentWorkItemExecutionGroup(laneUpdated);
1709
- const groupedPanel = laneGroup !== undefined
1710
- && (laneGroup.lanes.length > 1 || laneGroup.strategy.mode === "adaptive")
1711
- && laneGroup.resolution === undefined;
1712
- if (!groupedPanel) {
1704
+ if (!workItemOwnsUnresolvedExecutionLane(laneUpdated, currentRun.executionGroupId, currentRun.executionLaneId)) {
1713
1705
  store.saveWorkItem(task.id, updateWorkItemStatus(laneUpdated, "failed", input.now, input.summary));
1714
1706
  }
1715
1707
  }
@@ -2109,7 +2101,7 @@ export class FileSchedulerStoreAdapter {
2109
2101
  const item = store.getWorkItem(input.taskId, before.workItemId);
2110
2102
  if (item !== null && ![
2111
2103
  "completed", "failed", "retired"
2112
- ].includes(item.status)) {
2104
+ ].includes(item.status) && !workItemOwnsUnresolvedExecutionLane(item, before.executionGroupId, before.executionLaneId)) {
2113
2105
  store.saveWorkItem(input.taskId, updateWorkItemStatus(item, "failed", now, summary));
2114
2106
  }
2115
2107
  }
@@ -3388,7 +3380,9 @@ function finalizeProviderRetryDeadline(store, run, summary, reason, now) {
3388
3380
  }, now));
3389
3381
  if (terminal.purpose === "execution" && terminal.workItemId !== undefined) {
3390
3382
  const item = store.getWorkItem(run.taskId, terminal.workItemId);
3391
- if (item !== null && !["completed", "failed", "retired"].includes(item.status)) {
3383
+ if (item !== null
3384
+ && !["completed", "failed", "retired"].includes(item.status)
3385
+ && !workItemOwnsUnresolvedExecutionLane(item, terminal.executionGroupId, terminal.executionLaneId)) {
3392
3386
  store.saveWorkItem(run.taskId, updateWorkItemStatus(item, "failed", now, summary));
3393
3387
  }
3394
3388
  }