@zq-silk/yui 0.6.16 → 0.7.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 (69) hide show
  1. package/dist/cli/commandCatalog.js +3 -7
  2. package/dist/cli.js +12 -33
  3. package/dist/commands/executionAuditCommands.js +19 -0
  4. package/dist/commands/globalRoleCommands.js +70 -0
  5. package/dist/commands/taskActor.js +3 -2
  6. package/dist/commands/taskCommands.js +160 -41
  7. package/dist/commands/taskContextCommand.js +1 -1
  8. package/dist/commands/taskInputCommands.js +3 -2
  9. package/dist/commands/taskRoleRuntimeStatus.js +3 -3
  10. package/dist/context/contextSnapshot.js +228 -0
  11. package/dist/context/roleSessionContext.js +3 -1
  12. package/dist/context/runContextContract.js +162 -0
  13. package/dist/context/runContextPack.js +322 -0
  14. package/dist/context/sessionBootstrapManifest.js +81 -0
  15. package/dist/context/sessionProtocolIdentity.js +23 -0
  16. package/dist/controller/agentRuntimeObserver.js +6 -1
  17. package/dist/controller/controller.js +4 -3
  18. package/dist/controller/fileSchedulerStoreAdapter.js +446 -145
  19. package/dist/controller/jobControl.js +2 -1
  20. package/dist/controller/runtime.js +83 -0
  21. package/dist/controller/runtimeHookRunFence.js +6 -2
  22. package/dist/controller/sessionOwnerReconciliation.js +5 -0
  23. package/dist/executor/agentAdapter.js +7 -2
  24. package/dist/executor/agentExecutor.js +23 -0
  25. package/dist/executor/effectiveLaunch.js +24 -0
  26. package/dist/executor/executorRegistry.js +7 -1
  27. package/dist/executor/fileRoleLaunchPlanner.js +73 -27
  28. package/dist/lifecycle/exactRunTerminalization.js +2 -3
  29. package/dist/lifecycle/providerErrorClass.js +8 -3
  30. package/dist/observability/executionAudit.js +87 -2
  31. package/dist/repository/taskWorkspacePreparer.js +2 -2
  32. package/dist/run/agentRun.js +101 -16
  33. package/dist/run/providerRetry.js +167 -56
  34. package/dist/run/providerRetryConfig.js +5 -1
  35. package/dist/run/runControlRequest.js +50 -0
  36. package/dist/runtime/agentDriver.js +47 -0
  37. package/dist/runtime/agentHost.js +327 -0
  38. package/dist/runtime/builtinAgentDrivers.js +23 -1
  39. package/dist/runtime/builtinTranscriptObserver.js +4 -0
  40. package/dist/runtime/builtinTranscriptUsage.js +2 -0
  41. package/dist/runtime/exactControlPlane.js +2 -2
  42. package/dist/runtime/globalProcessExitStore.js +38 -0
  43. package/dist/runtime/launchBroker.js +95 -0
  44. package/dist/runtime/processExitObservation.js +60 -0
  45. package/dist/runtime/runtimeBinding.js +6 -0
  46. package/dist/runtime/runtimeObservation.js +27 -6
  47. package/dist/runtime/runtimeProjection.js +6 -3
  48. package/dist/runtime/runtimeStopReceipt.js +42 -0
  49. package/dist/runtime/sessionTerminationGuard.js +13 -0
  50. package/dist/runtime/tmuxAdapters.js +203 -220
  51. package/dist/scheduler/activeRoleRunDelivery.js +24 -3
  52. package/dist/scheduler/leaderWakeupProcessor.js +18 -60
  53. package/dist/scheduler/roleRunLiveness.js +61 -27
  54. package/dist/storage/migration/productionRegistry.js +264 -0
  55. package/dist/storage/sqliteSchema.js +23 -2
  56. package/dist/storage/sqliteStore.js +39 -2
  57. package/dist/storage/taskStore.js +54 -5
  58. package/dist/storage/upgrade/recordVersions.js +3 -1
  59. package/dist/storage/upgrade/sqliteStateMigration.js +10 -0
  60. package/dist/task/taskRecordReference.js +1 -0
  61. package/dist/tmux/tmuxManager.js +15 -4
  62. package/dist/web/assets/client/components.js +1 -1
  63. package/package.json +1 -1
  64. package/skills/yui-leader/SKILL.md +10 -5
  65. package/skills/yui-operator/SKILL.md +4 -0
  66. package/skills/yui-reviewer/SKILL.md +4 -0
  67. package/skills/yui-runtime/SKILL.md +61 -0
  68. package/skills/yui-worker/SKILL.md +82 -218
  69. package/dist/executor/managedClaudeRunner.js +0 -121
@@ -0,0 +1,322 @@
1
+ import { RUN_BOOTSTRAP_MAX_DELTAS } from "./runContextContract.js";
2
+ import { contextContentDigest, contextSnapshotRef, createContextSnapshot, validateContextSnapshot } from "./contextSnapshot.js";
3
+ export const RUN_CONTEXT_PACK_SCHEMA_VERSION = 1;
4
+ export const RUN_CONTEXT_PACK_MAX_REFS = 256;
5
+ export const RUN_CONTEXT_PACK_MAX_BYTES = 8 * 1024 * 1024;
6
+ export const RUN_CONTEXT_EXPAND_MAX_BYTES = 4 * 1024 * 1024;
7
+ export function freezeRunContextSnapshot(store, run, now, frozenBy = "controller") {
8
+ const scope = run.reviewRoundId !== undefined
9
+ ? "stage"
10
+ : run.workItemId !== undefined
11
+ ? "workitem"
12
+ : "task";
13
+ const scopeRef = run.reviewRoundId ?? run.workItemId;
14
+ const materialized = collectAuthorizedContext(store, run);
15
+ const previous = store.listContextSnapshots(run.taskId)
16
+ .filter((candidate) => candidate.scope === scope && candidate.scopeRef === scopeRef)
17
+ .sort((left, right) => left.sequence - right.sequence)
18
+ .at(-1);
19
+ const snapshot = createContextSnapshot({
20
+ id: store.nextContextSnapshotId(run.taskId),
21
+ taskId: run.taskId,
22
+ scope,
23
+ ...(scopeRef === undefined ? {} : { scopeRef }),
24
+ sequence: (previous?.sequence ?? 0) + 1,
25
+ refs: materialized.map(({ ref }) => ref),
26
+ resources: materialized,
27
+ acceptRefs: run.workItemId === undefined ? [] : [`work-item:${run.workItemId}:acceptance`],
28
+ ...(previous === undefined ? {} : { parentRef: contextSnapshotRef(previous) }),
29
+ frozenAt: now,
30
+ frozenBy
31
+ });
32
+ store.saveContextSnapshot(snapshot);
33
+ return snapshot;
34
+ }
35
+ /** Bounded changed-ref hint between one frozen Snapshot and its exact parent. */
36
+ export function contextSnapshotDeltaRefIds(store, snapshot) {
37
+ validateContextSnapshot(snapshot);
38
+ if (snapshot.parentRef === undefined)
39
+ return Object.freeze([]);
40
+ const parent = store.getContextSnapshot(snapshot.taskId, snapshot.parentRef.id);
41
+ if (parent === null
42
+ || parent.digest !== snapshot.parentRef.digest
43
+ || parent.sequence !== snapshot.parentRef.sequence) {
44
+ throw new Error(`Context Snapshot parent is missing or drifted: ${snapshot.parentRef.id}.`);
45
+ }
46
+ validateContextSnapshot(parent);
47
+ const previous = new Map(parent.refs.map((ref) => [ref.refId, ref]));
48
+ const changed = snapshot.refs.filter((ref) => {
49
+ const before = previous.get(ref.refId);
50
+ return before === undefined
51
+ || before.digest !== ref.digest
52
+ || before.revision !== ref.revision
53
+ || before.store !== ref.store
54
+ || before.layer !== ref.layer;
55
+ }).map(({ refId }) => refId);
56
+ return Object.freeze([...new Set(changed)].sort().slice(0, RUN_BOOTSTRAP_MAX_DELTAS));
57
+ }
58
+ export function buildRunContextPack(store, taskId, runId) {
59
+ const run = requireExactRun(store, taskId, runId);
60
+ const current = collectAuthorizedContext(store, run);
61
+ let pointers = current.map(({ ref }) => ref);
62
+ let snapshotRef;
63
+ if (run.assignment.contextSnapshotRef !== undefined) {
64
+ const expected = run.assignment.contextSnapshotRef;
65
+ const snapshot = store.getContextSnapshot(taskId, expected.id);
66
+ if (snapshot === null)
67
+ throw new Error(`Run Context Snapshot is missing: ${expected.id}.`);
68
+ validateContextSnapshot(snapshot);
69
+ if (snapshot.digest !== expected.digest || snapshot.taskId !== taskId) {
70
+ throw new Error(`Run Context Snapshot identity drifted: ${expected.id}.`);
71
+ }
72
+ pointers = snapshot.refs;
73
+ snapshotRef = contextSnapshotRef(snapshot);
74
+ }
75
+ if (pointers.length > RUN_CONTEXT_PACK_MAX_REFS) {
76
+ throw new Error(`Run Context exceeds ${RUN_CONTEXT_PACK_MAX_REFS} authorized refs.`);
77
+ }
78
+ const view = contextView(run);
79
+ const writableProjectIds = writableProjects(store, run, view);
80
+ const summaries = pointers.map((ref) => Object.freeze({
81
+ refId: ref.refId,
82
+ store: ref.store,
83
+ summary: ref.summary ?? `${ref.store} ${ref.refId}`,
84
+ digest: ref.digest
85
+ }));
86
+ const body = {
87
+ schemaVersion: RUN_CONTEXT_PACK_SCHEMA_VERSION,
88
+ identity: Object.freeze({
89
+ taskId,
90
+ runId,
91
+ roleName: run.roleName,
92
+ purpose: run.purpose,
93
+ agentId: run.effective.agentId,
94
+ adapterId: run.effective.adapterId,
95
+ workspace: run.effective.workspace.root
96
+ }),
97
+ ...(snapshotRef === undefined ? {} : { snapshot: snapshotRef }),
98
+ assignment: run.assignment,
99
+ authority: Object.freeze({ view, readableRefs: pointers, writableProjectIds }),
100
+ pointers,
101
+ summaries,
102
+ deltas: pointers.filter((ref) => run.assignment.deltaRefIds.includes(ref.refId)),
103
+ completion: Object.freeze({
104
+ allowedActions: completionActions(view),
105
+ exactRunRef: `${taskId}/${runId}`
106
+ })
107
+ };
108
+ const digest = contextContentDigest(body);
109
+ const preliminaryBytes = Buffer.byteLength(JSON.stringify({ ...body, digest }), "utf8");
110
+ if (preliminaryBytes > RUN_CONTEXT_PACK_MAX_BYTES) {
111
+ throw new Error(`Run Context Pack exceeds ${RUN_CONTEXT_PACK_MAX_BYTES} bytes.`);
112
+ }
113
+ const pack = Object.freeze({
114
+ ...body,
115
+ budget: Object.freeze({
116
+ maxRefs: RUN_CONTEXT_PACK_MAX_REFS,
117
+ returnedRefs: pointers.length,
118
+ maxBytes: RUN_CONTEXT_PACK_MAX_BYTES,
119
+ returnedBytes: preliminaryBytes,
120
+ truncated: false
121
+ }),
122
+ digest
123
+ });
124
+ return pack;
125
+ }
126
+ export function expandRunContextRef(store, taskId, runId, refId) {
127
+ const pack = buildRunContextPack(store, taskId, runId);
128
+ const authorized = pack.pointers.filter((ref) => ref.refId === refId);
129
+ if (authorized.length !== 1) {
130
+ throw new Error(`Run Context ref is not uniquely authorized: ${refId}.`);
131
+ }
132
+ const run = requireExactRun(store, taskId, runId);
133
+ const snapshotRef = run.assignment.contextSnapshotRef;
134
+ const materialized = snapshotRef === undefined
135
+ ? collectAuthorizedContext(store, run).find(({ ref }) => (contextRefIdentity(ref) === contextRefIdentity(authorized[0])))
136
+ : store.getContextSnapshot(taskId, snapshotRef.id)?.resources.find(({ ref }) => (contextRefIdentity(ref) === contextRefIdentity(authorized[0])));
137
+ if (materialized === undefined || materialized.ref.digest !== authorized[0].digest) {
138
+ throw new Error(`Run Context ref is unavailable or drifted: ${refId}.`);
139
+ }
140
+ const bytes = Buffer.byteLength(JSON.stringify(materialized.value), "utf8");
141
+ if (bytes > RUN_CONTEXT_EXPAND_MAX_BYTES) {
142
+ throw new Error(`Run Context expansion exceeds ${RUN_CONTEXT_EXPAND_MAX_BYTES} bytes.`);
143
+ }
144
+ return Object.freeze({
145
+ ref: materialized.ref,
146
+ value: materialized.value,
147
+ digest: contextContentDigest({ ref: materialized.ref, value: materialized.value })
148
+ });
149
+ }
150
+ /** Fail-closed delta cursor resolution for one immutable Run lineage. */
151
+ export function buildRunContextDelta(store, taskId, runId, after) {
152
+ const pack = buildRunContextPack(store, taskId, runId);
153
+ if (after === pack.digest || after === pack.snapshot?.digest) {
154
+ return Object.freeze({ schemaVersion: 1, after, cursor: pack.digest, refs: [] });
155
+ }
156
+ const run = requireExactRun(store, taskId, runId);
157
+ const snapshotRef = run.assignment.contextSnapshotRef;
158
+ const snapshot = snapshotRef === undefined
159
+ ? null
160
+ : store.getContextSnapshot(taskId, snapshotRef.id);
161
+ const parentDigest = snapshot?.parentRef?.digest;
162
+ if (!((parentDigest !== undefined && after === parentDigest)
163
+ || (parentDigest === undefined && after === "none"))) {
164
+ throw new Error("Run Context delta cursor is outside the frozen Snapshot lineage.");
165
+ }
166
+ return Object.freeze({
167
+ schemaVersion: 1,
168
+ after,
169
+ cursor: pack.digest,
170
+ refs: pack.deltas
171
+ });
172
+ }
173
+ function requireExactRun(store, taskId, runId) {
174
+ const run = store.getAgentRun(taskId, runId);
175
+ if (run === null || run.taskId !== taskId || run.id !== runId) {
176
+ throw new Error(`Agent Run not found: ${taskId}/${runId}.`);
177
+ }
178
+ return run;
179
+ }
180
+ function collectAuthorizedContext(store, run) {
181
+ const task = store.getTask(run.taskId);
182
+ if (task === null)
183
+ throw new Error(`Task not found: ${run.taskId}.`);
184
+ const view = contextView(run);
185
+ const result = [materialize("L2", "task", task.id, task)];
186
+ const brief = store.getTaskBrief(task.id);
187
+ if (brief !== null && view === "leader") {
188
+ result.push(materialize("L2", "task-brief", task.id, brief));
189
+ }
190
+ const role = store.getRole(task.id, run.roleName);
191
+ if (role === null)
192
+ throw new Error(`Run Role not found: ${task.id}/${run.roleName}.`);
193
+ result.push(materialize("L1", "role-profile", role.name, {
194
+ name: role.name,
195
+ defaultAccess: role.defaultAccess,
196
+ description: role.description,
197
+ responsibilities: role.responsibilities ?? [],
198
+ constraints: role.constraints ?? [],
199
+ expectedOutput: role.expectedOutput,
200
+ skills: role.skills ?? [],
201
+ launchRevision: role.launchRevision
202
+ }));
203
+ if ("workspace" in run && run.workspace !== undefined) {
204
+ result.push(materialize("L3", "managed-workspace", `${run.taskId}/${run.roleName}`, run.workspace));
205
+ }
206
+ if (run.workItemId !== undefined) {
207
+ const item = store.getWorkItem(task.id, run.workItemId);
208
+ if (item === null)
209
+ throw new Error(`Run WorkItem not found: ${run.workItemId}.`);
210
+ result.push(materialize("L3", "work-item", item.id, item));
211
+ if (view === "worker") {
212
+ for (const dependencyId of item.dependsOn) {
213
+ const dependency = store.getWorkItem(task.id, dependencyId);
214
+ if (dependency === null || dependency.status !== "completed") {
215
+ throw new Error(`Run WorkItem dependency is not accepted: ${dependencyId}.`);
216
+ }
217
+ result.push(materialize("L3", "accepted-work-item", dependency.id, dependency));
218
+ }
219
+ }
220
+ }
221
+ if (run.reviewRoundId !== undefined) {
222
+ const round = store.getReviewRound(task.id, run.reviewRoundId);
223
+ if (round === null)
224
+ throw new Error(`Run ReviewRound not found: ${run.reviewRoundId}.`);
225
+ result.push(materialize("L3", "review-round", round.id, round));
226
+ for (const finding of store.listReviewFindings(task.id).filter((candidate) => (candidate.firstReviewRoundId === round.id
227
+ || candidate.lastReviewRoundId === round.id
228
+ || candidate.repair?.workItemId === round.workItemId))) {
229
+ result.push(materialize("L3", "review-finding", finding.id, finding));
230
+ }
231
+ }
232
+ for (const binding of task.projectBindings) {
233
+ const project = store.getProject(binding.projectId);
234
+ if (project === null)
235
+ throw new Error(`Run Project not found: ${binding.projectId}.`);
236
+ const { knowledge, ...projectPolicy } = project;
237
+ result.push(materialize("L1", "project-policy", project.id, projectPolicy));
238
+ for (const entry of knowledge.filter(({ status }) => status === "active")) {
239
+ result.push(materialize("L1", "project-knowledge", `${project.id}:${entry.id}`, { projectId: project.id, ...entry }));
240
+ }
241
+ }
242
+ if (view === "leader") {
243
+ for (const item of store.listWorkItems(task.id)) {
244
+ result.push(materialize("L3", "work-item", item.id, item));
245
+ }
246
+ for (const decision of store.listDecisions(task.id)) {
247
+ result.push(materialize("L2", "task-decision", decision.id, decision));
248
+ }
249
+ for (const milestone of store.listMilestones(task.id).slice(-16)) {
250
+ result.push(materialize("L2", "task-milestone", milestone.id, milestone));
251
+ }
252
+ for (const round of store.listReviewRounds(task.id).slice(-16)) {
253
+ result.push(materialize("L3", "review-round", round.id, round));
254
+ }
255
+ for (const finding of store.listReviewFindings(task.id)) {
256
+ result.push(materialize("L3", "review-finding", finding.id, finding));
257
+ }
258
+ for (const agentRun of store.listAgentRuns(task.id).slice(-24)) {
259
+ result.push(materialize("L4", "agent-run", agentRun.id, agentRun));
260
+ }
261
+ for (const message of store.listMessages(task.id).slice(-16)) {
262
+ result.push(materialize("L4", "task-message", message.id, message));
263
+ }
264
+ for (const request of store.listOpenInputRequests([task.id])) {
265
+ result.push(materialize("L4", "input-request", request.id, request));
266
+ }
267
+ }
268
+ const unique = new Map(result.map((entry) => [contextRefIdentity(entry.ref), entry]));
269
+ return [...unique.values()].sort((left, right) => (contextRefIdentity(left.ref).localeCompare(contextRefIdentity(right.ref))));
270
+ }
271
+ function materialize(layer, store, refId, value) {
272
+ const digest = contextContentDigest(value);
273
+ const record = value;
274
+ const revision = String(record.revision ?? record.updatedAt ?? record.createdAt ?? digest);
275
+ const title = typeof record.title === "string"
276
+ ? record.title
277
+ : typeof record.summary === "string"
278
+ ? record.summary
279
+ : `${store} ${refId}`;
280
+ return Object.freeze({
281
+ ref: Object.freeze({
282
+ layer,
283
+ store,
284
+ refId,
285
+ revision,
286
+ digest,
287
+ summary: title.slice(0, 400)
288
+ }),
289
+ value
290
+ });
291
+ }
292
+ function contextRefIdentity(ref) {
293
+ return `${ref.store}\0${ref.refId}\0${ref.revision}`;
294
+ }
295
+ function contextView(run) {
296
+ if (run.purpose === "review")
297
+ return "reviewer";
298
+ if (run.roleName === "leader")
299
+ return "leader";
300
+ if (run.roleName === "operator")
301
+ return "operator";
302
+ return "worker";
303
+ }
304
+ function writableProjects(store, run, view) {
305
+ if (view === "leader")
306
+ return Object.freeze(store.getTask(run.taskId)?.projectBindings.map(({ projectId }) => projectId) ?? []);
307
+ if (view === "reviewer") {
308
+ return Object.freeze(run.workspace?.entries.filter(({ access }) => access === "write").map(({ projectId }) => projectId) ?? []);
309
+ }
310
+ if (run.workItemId === undefined)
311
+ return Object.freeze([]);
312
+ return Object.freeze(store.getWorkItem(run.taskId, run.workItemId)?.writeProjectIds ?? []);
313
+ }
314
+ function completionActions(view) {
315
+ if (view === "leader")
316
+ return Object.freeze(["yield", "complete-task", "request-input"]);
317
+ if (view === "reviewer")
318
+ return Object.freeze(["checkpoint", "yield-review"]);
319
+ if (view === "operator")
320
+ return Object.freeze(["answer-input", "recover"]);
321
+ return Object.freeze(["checkpoint", "yield"]);
322
+ }
@@ -0,0 +1,81 @@
1
+ import { createHash } from "node:crypto";
2
+ import { chmodSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { exactControlPlaneCommandPrefix, exactControlPlaneDigest, serializeExactDescriptor } from "../runtime/exactControlPlane.js";
5
+ import { writeTextFileAtomically } from "../storage/durableFile.js";
6
+ import { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
7
+ export { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
8
+ export function materializeSessionBootstrap(input) {
9
+ const home = resolve(input.yuiHome);
10
+ const controlDigest = exactControlPlaneDigest(input.controlPlane);
11
+ const descriptorPath = resolve(join(home, "runtime", "control-plane", `${controlDigest}.json`));
12
+ writeImmutableText(descriptorPath, `${serializeExactDescriptor(input.controlPlane)}\n`);
13
+ const sessionCliContent = [
14
+ "#!/bin/sh",
15
+ `exec ${exactControlPlaneCommandPrefix(input.controlPlane)} \"$@\"`,
16
+ ""
17
+ ].join("\n");
18
+ const sessionCliDigest = digest(sessionCliContent);
19
+ const sessionCliPath = resolve(join(home, "runtime", "session-cli", `yui-${sessionCliDigest}.sh`));
20
+ writeImmutableText(sessionCliPath, sessionCliContent);
21
+ chmodSync(sessionCliPath, 0o700);
22
+ const roleProfile = {
23
+ roleName: input.role.name,
24
+ roleKind: input.roleKind,
25
+ defaultAccess: input.role.defaultAccess,
26
+ description: input.role.description,
27
+ responsibilities: input.role.responsibilities ?? [],
28
+ constraints: input.role.constraints ?? [],
29
+ expectedOutput: input.role.expectedOutput,
30
+ systemPrompt: input.role.systemPrompt
31
+ };
32
+ const profileContent = `${JSON.stringify(roleProfile, null, 2)}\n`;
33
+ const profileDigest = digest(profileContent);
34
+ const roleProfilePath = resolve(join(home, "runtime", "role-profiles", `${profileDigest}.json`));
35
+ writeImmutableText(roleProfilePath, profileContent);
36
+ const body = {
37
+ schemaVersion: SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION,
38
+ protocol: SESSION_CONTEXT_PROTOCOL,
39
+ owner: input.owner,
40
+ effectiveRevision: input.role.launchRevision,
41
+ roleKind: input.roleKind,
42
+ compatibilityDigest: sessionManifestCompatibilityDigest(input.role.name, input.roleKind, input.role),
43
+ controlPlane: {
44
+ descriptorPath,
45
+ sessionCliPath,
46
+ digest: controlDigest
47
+ },
48
+ skills: input.skills.map((skill) => Object.freeze({
49
+ id: skill.id,
50
+ path: skill.path,
51
+ digest: digest(skill.content)
52
+ })),
53
+ roleProfileRef: { digest: profileDigest, path: roleProfilePath },
54
+ contextProtocol: input.owner.scope === "global"
55
+ ? {
56
+ loadCommand: `\"${sessionCliPath}\" role context \"$YUI_ROLE\" --json`
57
+ }
58
+ : {
59
+ loadCommand: `\"${sessionCliPath}\" task run context \"$YUI_TASK_ID/<run-id>\" --json`,
60
+ expandCommand: `\"${sessionCliPath}\" task run context expand \"$YUI_TASK_ID/<run-id>\" <ref-id> --mode full --json`
61
+ }
62
+ };
63
+ const manifest = Object.freeze({ ...body, digest: digest(body) });
64
+ const manifestPath = resolve(join(home, "runtime", "session-manifests", `${manifest.digest}.json`));
65
+ writeImmutableText(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
66
+ return Object.freeze({
67
+ manifest,
68
+ manifestPath,
69
+ sessionCliPath,
70
+ roleProfilePath,
71
+ descriptorPath
72
+ });
73
+ }
74
+ function writeImmutableText(path, content) {
75
+ writeTextFileAtomically(path, content);
76
+ chmodSync(path, 0o600);
77
+ }
78
+ function digest(value) {
79
+ const bytes = typeof value === "string" ? value : JSON.stringify(value);
80
+ return createHash("sha256").update(bytes).digest("hex");
81
+ }
@@ -0,0 +1,23 @@
1
+ import { createHash } from "node:crypto";
2
+ export const SESSION_CONTEXT_PROTOCOL = "yui-managed-context/v1";
3
+ export const SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION = 1;
4
+ /** Pure compatibility identity: safe for storage/domain code with no control-plane I/O edge. */
5
+ export function sessionManifestCompatibilityDigest(roleName, roleKind, profile) {
6
+ return createHash("sha256").update(JSON.stringify({
7
+ protocol: SESSION_CONTEXT_PROTOCOL,
8
+ roleName,
9
+ roleKind,
10
+ profile: {
11
+ description: profile.description,
12
+ responsibilities: profile.responsibilities ?? [],
13
+ constraints: profile.constraints ?? [],
14
+ expectedOutput: profile.expectedOutput,
15
+ systemPrompt: profile.systemPrompt,
16
+ skillIds: [
17
+ "yui-runtime",
18
+ ...(roleKind === "global" ? [] : [`yui-${roleKind}`]),
19
+ ...(profile.skills ?? [])
20
+ ]
21
+ }
22
+ })).digest("hex");
23
+ }
@@ -60,7 +60,11 @@ export class AgentRuntimeObserver {
60
60
  // identity or the canonical sequence assigned to a source.
61
61
  const sequence = sequenceBase + index;
62
62
  if (existingState === undefined && freshSession && state.usage === undefined) {
63
- const zero = Object.freeze({ inputTokens: 0, outputTokens: 0 });
63
+ const zero = Object.freeze({
64
+ semantics: "cumulative-session",
65
+ inputTokens: 0,
66
+ outputTokens: 0
67
+ });
64
68
  this.inbox.enqueueObservation(createRuntimeObservation({
65
69
  schemaVersion: 2,
66
70
  eventId: observationId("baseline", fence, source.sourceId, "zero"),
@@ -266,6 +270,7 @@ function observationId(kind, fence, sourceId, value) {
266
270
  }
267
271
  function sameUsage(left, right) {
268
272
  return left !== undefined
273
+ && left.semantics === right.semantics
269
274
  && left.inputTokens === right.inputTokens
270
275
  && left.outputTokens === right.outputTokens
271
276
  && left.cachedInputTokens === right.cachedInputTokens
@@ -1432,13 +1432,14 @@ export class FileTaskController {
1432
1432
  }]
1433
1433
  : []),
1434
1434
  // Issue 04 durable in-place retry timer: arm the Controller wake at the
1435
- // earliest `nextAttemptAt` so a due retry re-pushes on its original
1436
- // Session. The projection is durable, so a restart resumes the lineage.
1435
+ // earliest dispatch/deadline wake. Scheduled retries re-push on their
1436
+ // original Session; in-flight retries only wake at the episode deadline.
1437
+ // The projection is durable, so a restart resumes the lineage.
1437
1438
  ...(typeof this.store.listPendingProviderRetries === "function"
1438
1439
  ? this.store.listPendingProviderRetries()
1439
1440
  : []).map((retry) => ({
1440
1441
  key: `role:${encodeURIComponent(retry.taskId)}/${encodeURIComponent(retry.roleName)}`,
1441
- at: Date.parse(retry.nextAttemptAt)
1442
+ at: Date.parse(retry.dueAt)
1442
1443
  }))
1443
1444
  ];
1444
1445
  const nearest = nearestDeadlineBatch(deadlines);