@zq-silk/yui 0.8.3 → 0.8.7

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 (73) hide show
  1. package/ARCHITECTURE.md +40 -22
  2. package/README.md +66 -19
  3. package/dist/cli/commandCatalog.js +43 -14
  4. package/dist/cli/operatorWizard.js +10 -20
  5. package/dist/cli/updatePorts.js +6 -0
  6. package/dist/cli.js +252 -37
  7. package/dist/commands/executionAuditCommands.js +30 -0
  8. package/dist/commands/globalRoleCommands.js +8 -4
  9. package/dist/commands/operatorCommands.js +42 -1
  10. package/dist/commands/taskCommands.js +527 -147
  11. package/dist/commands/taskCompletionGate.js +36 -24
  12. package/dist/commands/taskContextCommand.js +11 -4
  13. package/dist/commands/taskInputCommands.js +48 -10
  14. package/dist/commands/taskNextActionCommand.js +38 -3
  15. package/dist/commands/taskOverviewCommand.js +2 -1
  16. package/dist/commands/taskRoleRuntimeStatus.js +2 -1
  17. package/dist/context/runContextPack.js +9 -5
  18. package/dist/context/sessionBootstrapManifest.js +158 -11
  19. package/dist/context/wakeNotification.js +5 -3
  20. package/dist/controller/clientRuntime.js +15 -15
  21. package/dist/controller/controller.js +16 -8
  22. package/dist/controller/fileSchedulerStoreAdapter.js +67 -7
  23. package/dist/controller/handoverCandidate.js +10 -3
  24. package/dist/controller/sessionNotify.js +4 -22
  25. package/dist/executor/agentAdapter.js +2 -2
  26. package/dist/executor/agentExecutor.js +29 -9
  27. package/dist/executor/fileRoleLaunchPlanner.js +37 -45
  28. package/dist/integration/gitIntegrationService.js +50 -2
  29. package/dist/integration/integrationCheckEvidenceReuse.js +53 -0
  30. package/dist/observability/executionAudit.js +47 -1
  31. package/dist/observability/faultClassification.js +6 -4
  32. package/dist/observability/orchestrationMetrics.js +196 -0
  33. package/dist/operator/operatorSessionHistory.js +36 -0
  34. package/dist/release/releaseHandover.js +7 -5
  35. package/dist/release/runtimeRelease.js +15 -0
  36. package/dist/repository/taskWorkspaceCoordinator.js +13 -10
  37. package/dist/review/deltaRecheck.js +3 -2
  38. package/dist/review/reviewFindingLedger.js +5 -4
  39. package/dist/review/reviewOutcomeClassifier.js +263 -54
  40. package/dist/review/taskFinalReviewContractEvent.js +1 -0
  41. package/dist/review/taskFinalReviewContractRebind.js +367 -0
  42. package/dist/run/runIdentity.js +10 -70
  43. package/dist/runtime/agentHost.js +3 -4
  44. package/dist/runtime/codexAppServerRuntime.js +6 -0
  45. package/dist/runtime/exactControlPlane.js +47 -37
  46. package/dist/runtime/firstProgressStopLoss.js +54 -0
  47. package/dist/runtime/launchBroker.js +10 -2
  48. package/dist/runtime/runtimeDeadlines.js +14 -0
  49. package/dist/runtime/sessionTitle.js +24 -12
  50. package/dist/runtime/structuredProviderHost.js +7 -1
  51. package/dist/runtime/tmuxAdapters.js +10 -3
  52. package/dist/scheduler/actionability.js +4 -2
  53. package/dist/scheduler/activeRoleRunDelivery.js +20 -18
  54. package/dist/scheduler/activeTaskProgress.js +2 -1
  55. package/dist/scheduler/leaderWakeupProcessor.js +33 -2
  56. package/dist/scheduler/taskExecutionProjection.js +13 -4
  57. package/dist/scheduler/wakeReason.js +1 -0
  58. package/dist/storage/sqliteStore.js +18 -3
  59. package/dist/storage/taskStore.js +14 -3
  60. package/dist/task/completionReadiness.js +48 -19
  61. package/dist/task/deliveryGuard.js +3 -1
  62. package/dist/task/nextAction.js +153 -55
  63. package/dist/task/repairWave.js +14 -1
  64. package/dist/task/task.js +10 -0
  65. package/dist/task/taskRecordRetirement.js +72 -0
  66. package/dist/web/webSnapshot.js +7 -1
  67. package/dist/workItem/workItem.js +6 -4
  68. package/i18n/README.zh-CN.md +48 -9
  69. package/package.json +1 -1
  70. package/skills/yui-leader/SKILL.md +73 -31
  71. package/skills/yui-operator/SKILL.md +58 -10
  72. package/skills/yui-reviewer/SKILL.md +23 -0
  73. package/skills/yui-runtime/SKILL.md +6 -6
@@ -0,0 +1,367 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { requireIdentity, requireTimestamp } from "../domain/validation.js";
4
+ import { readActiveReleasePointer, readHandoverFence, readHandoverReceipt, releaseDirectoryName, releasesDirectory, verifyReleaseIntegrity } from "../release/runtimeRelease.js";
5
+ import { exactControlPlaneDigest, parseExactControlPlaneDescriptor } from "../runtime/exactControlPlane.js";
6
+ import { createTaskFinalReviewContract, sameTaskFinalReviewContract, validateTaskFinalReviewContract } from "./taskFinalReviewContract.js";
7
+ import { governingWorkItemCandidate } from "../workItem/workItem.js";
8
+ import { TASK_FINAL_REVIEW_CONTRACT_REBOUND_EVENT } from "./taskFinalReviewContractEvent.js";
9
+ export { TASK_FINAL_REVIEW_CONTRACT_REBOUND_EVENT } from "./taskFinalReviewContractEvent.js";
10
+ /**
11
+ * Resolves the effective Task-final Review contract from its immutable Task
12
+ * records. Mutation and read-model callers share this adapter so a legal
13
+ * release rebind cannot be interpreted differently by the two paths.
14
+ */
15
+ export function resolveRecordedTaskFinalReviewContract(taskId, workItems, reviewRounds, events) {
16
+ const candidateObservations = workItems.flatMap((item) => {
17
+ const candidate = governingWorkItemCandidate(item);
18
+ return candidate?.taskFinalReviewContract === undefined
19
+ ? []
20
+ : [{
21
+ contract: candidate.taskFinalReviewContract,
22
+ createdAt: candidate.createdAt,
23
+ source: `Candidate ${item.id}/${candidate.id}`
24
+ }];
25
+ });
26
+ const historicalCandidateObservations = workItems.flatMap((item) => (item.candidates
27
+ .filter((candidate) => candidate.taskFinalReviewContract !== undefined)
28
+ .map((candidate) => ({
29
+ contract: candidate.taskFinalReviewContract,
30
+ createdAt: candidate.createdAt,
31
+ source: `Historical Candidate ${item.id}/${candidate.id}`
32
+ }))));
33
+ const reviewObservations = reviewRounds.flatMap((round) => ((round.scope ?? "work-item") !== "task"
34
+ || round.taskFinalReviewContract === undefined
35
+ ? []
36
+ : [{
37
+ contract: round.taskFinalReviewContract,
38
+ createdAt: round.createdAt,
39
+ source: `ReviewRound ${round.id}`
40
+ }]));
41
+ return resolveTaskFinalReviewContract(taskId, [...candidateObservations, ...reviewObservations], events, historicalCandidateObservations);
42
+ }
43
+ export function createTaskFinalReviewContractRebind(input) {
44
+ const taskId = requireIdentity(input.taskId, "Task final-review rebind Task id");
45
+ const reviewerRoleName = requireIdentity(input.reviewerRoleName, "Task final-review rebind Reviewer Role");
46
+ const fromContract = validateTaskFinalReviewContract(input.fromContract);
47
+ if (fromContract.taskId !== taskId) {
48
+ throw new Error("Task final-review rebind contract belongs to another Task.");
49
+ }
50
+ if (fromContract.reviewerRoleName !== reviewerRoleName) {
51
+ throw new Error("Task final-review rebind changes the Reviewer identity.");
52
+ }
53
+ const toContract = createTaskFinalReviewContract({
54
+ taskId,
55
+ reviewerRoleName,
56
+ controlPlaneDigest: requireDigest(input.toControlPlaneDigest, "Task final-review rebind target control-plane digest")
57
+ });
58
+ if (fromContract.controlPlaneDigest === toContract.controlPlaneDigest) {
59
+ throw new Error("Task final-review rebind must change the control-plane digest.");
60
+ }
61
+ const fromRelease = validateReleaseIdentity(input.fromRelease, "source");
62
+ const toRelease = validateReleaseIdentity(input.toRelease, "target");
63
+ if (fromRelease.releaseId === toRelease.releaseId) {
64
+ throw new Error("Task final-review rebind must cross an immutable release handover.");
65
+ }
66
+ return Object.freeze({
67
+ schemaVersion: 1,
68
+ taskId,
69
+ reviewerRoleName,
70
+ fromContract,
71
+ toContract,
72
+ fromRelease,
73
+ toRelease,
74
+ handoverId: requireIdentity(input.handoverId, "Task final-review rebind handover id"),
75
+ authorizedBy: requireText(input.authorizedBy, "Task final-review rebind authorizer")
76
+ });
77
+ }
78
+ export function taskFinalReviewContractRebindPayload(rebind) {
79
+ const validated = createTaskFinalReviewContractRebind({
80
+ ...rebind,
81
+ toControlPlaneDigest: rebind.toContract.controlPlaneDigest
82
+ });
83
+ return {
84
+ schemaVersion: "1",
85
+ taskId: validated.taskId,
86
+ reviewerRoleName: validated.reviewerRoleName,
87
+ fromContractDigest: validated.fromContract.digest,
88
+ toContractDigest: validated.toContract.digest,
89
+ fromControlPlaneDigest: validated.fromContract.controlPlaneDigest,
90
+ toControlPlaneDigest: validated.toContract.controlPlaneDigest,
91
+ fromReleaseId: validated.fromRelease.releaseId,
92
+ fromReleaseVersion: validated.fromRelease.version,
93
+ fromReleaseBuildId: validated.fromRelease.buildId,
94
+ fromReleasePackageDigest: validated.fromRelease.packageDigest,
95
+ ...(validated.fromRelease.sourceCommit === undefined
96
+ ? {}
97
+ : { fromReleaseSourceCommit: validated.fromRelease.sourceCommit }),
98
+ toReleaseId: validated.toRelease.releaseId,
99
+ toReleaseVersion: validated.toRelease.version,
100
+ toReleaseBuildId: validated.toRelease.buildId,
101
+ toReleasePackageDigest: validated.toRelease.packageDigest,
102
+ ...(validated.toRelease.sourceCommit === undefined
103
+ ? {}
104
+ : { toReleaseSourceCommit: validated.toRelease.sourceCommit }),
105
+ handoverId: validated.handoverId,
106
+ authorizedBy: validated.authorizedBy
107
+ };
108
+ }
109
+ export function taskFinalReviewContractRebindFromEvent(event) {
110
+ if (event.type !== TASK_FINAL_REVIEW_CONTRACT_REBOUND_EVENT) {
111
+ throw new Error(`Task event is not a final-review contract rebind: ${event.id}.`);
112
+ }
113
+ const payload = event.payload;
114
+ if (payload.schemaVersion !== "1") {
115
+ throw new Error(`Task final-review rebind event schema is invalid: ${event.id}.`);
116
+ }
117
+ if (payload.taskId !== event.taskId) {
118
+ throw new Error(`Task final-review rebind event belongs to another Task: ${event.id}.`);
119
+ }
120
+ const fromContract = createTaskFinalReviewContract({
121
+ taskId: payload.taskId,
122
+ reviewerRoleName: payload.reviewerRoleName,
123
+ controlPlaneDigest: payload.fromControlPlaneDigest
124
+ });
125
+ if (fromContract.digest !== payload.fromContractDigest) {
126
+ throw new Error(`Task final-review rebind source digest is invalid: ${event.id}.`);
127
+ }
128
+ const rebind = createTaskFinalReviewContractRebind({
129
+ taskId: payload.taskId,
130
+ reviewerRoleName: payload.reviewerRoleName,
131
+ fromContract,
132
+ toControlPlaneDigest: payload.toControlPlaneDigest,
133
+ fromRelease: releaseIdentityFromPayload(payload, "from"),
134
+ toRelease: releaseIdentityFromPayload(payload, "to"),
135
+ handoverId: payload.handoverId,
136
+ authorizedBy: payload.authorizedBy
137
+ });
138
+ if (rebind.toContract.digest !== payload.toContractDigest) {
139
+ throw new Error(`Task final-review rebind target digest is invalid: ${event.id}.`);
140
+ }
141
+ return rebind;
142
+ }
143
+ /**
144
+ * Historical Candidate and Review records remain immutable. Before the first
145
+ * explicit rebind event, older Yui releases may have emitted successive
146
+ * control-plane contracts without a rebind event. Preserve that legacy prefix
147
+ * as a forward-only sequence, then require one strict append-only rebind chain.
148
+ * Any Reviewer change, reversion, fork, or post-rebind drift fails closed.
149
+ */
150
+ export function resolveTaskFinalReviewContract(taskId, observations, events, historicalObservations = []) {
151
+ const normalizedTaskId = requireIdentity(taskId, "Task final-review contract Task id");
152
+ const orderedObservations = [...observations]
153
+ .map((observation) => ({
154
+ contract: validateTaskFinalReviewContract(observation.contract),
155
+ createdAt: requireTimestamp(observation.createdAt, "Task final-review observation createdAt"),
156
+ source: requireText(observation.source, "Task final-review observation source")
157
+ }))
158
+ .sort(compareCreatedAt);
159
+ const orderedHistoricalObservations = [...historicalObservations]
160
+ .map((observation) => ({
161
+ contract: validateTaskFinalReviewContract(observation.contract),
162
+ createdAt: requireTimestamp(observation.createdAt, "Task final-review historical observation createdAt"),
163
+ source: requireText(observation.source, "Task final-review historical observation source")
164
+ }))
165
+ .sort(compareCreatedAt);
166
+ const orderedEvents = events
167
+ .filter(({ type }) => type === TASK_FINAL_REVIEW_CONTRACT_REBOUND_EVENT)
168
+ .map((event) => ({
169
+ event,
170
+ createdAt: requireTimestamp(event.createdAt, "Task final-review rebind event createdAt"),
171
+ rebind: taskFinalReviewContractRebindFromEvent(event)
172
+ }))
173
+ .sort((left, right) => compareCreatedAt(left, right)
174
+ || left.event.id.localeCompare(right.event.id, undefined, { numeric: true }));
175
+ if (orderedObservations.length === 0 && orderedEvents.length === 0)
176
+ return undefined;
177
+ for (const observation of [...orderedObservations, ...orderedHistoricalObservations]) {
178
+ if (observation.contract.taskId !== normalizedTaskId) {
179
+ throw new Error(`${observation.source} carries a final-review contract for another Task.`);
180
+ }
181
+ }
182
+ const firstRebindAt = orderedEvents[0]?.createdAt;
183
+ const primaryLegacyObservations = firstRebindAt === undefined
184
+ ? orderedObservations
185
+ : orderedObservations.filter(({ createdAt }) => createdAt < firstRebindAt);
186
+ const historicalRebindSource = orderedEvents[0] === undefined
187
+ ? undefined
188
+ : orderedHistoricalObservations.find((observation) => (observation.createdAt < orderedEvents[0].createdAt
189
+ && sameTaskFinalReviewContract(observation.contract, orderedEvents[0].rebind.fromContract)));
190
+ const legacyObservations = primaryLegacyObservations.length > 0
191
+ ? primaryLegacyObservations
192
+ : historicalRebindSource === undefined ? [] : [historicalRebindSource];
193
+ if (legacyObservations.length === 0) {
194
+ throw new Error(`Task ${normalizedTaskId} has a final-review rebind without an established source contract.`);
195
+ }
196
+ const initial = legacyObservations[0].contract;
197
+ const reviewerRoleName = initial.reviewerRoleName;
198
+ let effective = initial;
199
+ const legacyContractDigests = new Set([initial.digest]);
200
+ for (const observation of legacyObservations) {
201
+ if (observation.contract.reviewerRoleName !== reviewerRoleName) {
202
+ throw new Error(`${observation.source} changes the Task final-review Reviewer identity.`);
203
+ }
204
+ if (sameTaskFinalReviewContract(observation.contract, effective))
205
+ continue;
206
+ if (legacyContractDigests.has(observation.contract.digest)) {
207
+ throw new Error(`${observation.source} reverts a legacy Task final-review contract.`);
208
+ }
209
+ legacyContractDigests.add(observation.contract.digest);
210
+ effective = observation.contract;
211
+ }
212
+ for (const entry of orderedEvents) {
213
+ const { rebind } = entry;
214
+ if (!sameTaskFinalReviewContract(effective, rebind.fromContract)) {
215
+ throw new Error(`Task final-review rebind chain forks at ${entry.event.id}.`);
216
+ }
217
+ if (rebind.reviewerRoleName !== initial.reviewerRoleName) {
218
+ throw new Error(`Task final-review rebind changes Reviewer identity at ${entry.event.id}.`);
219
+ }
220
+ effective = rebind.toContract;
221
+ }
222
+ for (const observation of orderedObservations) {
223
+ if (firstRebindAt === undefined || observation.createdAt < firstRebindAt)
224
+ continue;
225
+ let expected = legacyObservations.at(-1).contract;
226
+ for (const entry of orderedEvents) {
227
+ if (entry.createdAt > observation.createdAt)
228
+ break;
229
+ expected = entry.rebind.toContract;
230
+ }
231
+ if (!sameTaskFinalReviewContract(observation.contract, expected)) {
232
+ throw new Error(`${observation.source} does not match the effective Task final-review contract at creation.`);
233
+ }
234
+ }
235
+ return Object.freeze({
236
+ initial,
237
+ effective,
238
+ rebinds: Object.freeze(orderedEvents.map(({ rebind }) => rebind))
239
+ });
240
+ }
241
+ /**
242
+ * Builds the immutable release proof while the caller holds the release
243
+ * handover lock. The current exact control descriptor is supplied only by the
244
+ * CLI preflight; caller-provided target identities are checked against it.
245
+ */
246
+ export function prepareTaskFinalReviewContractRebindProof(input) {
247
+ const home = resolve(input.home);
248
+ const taskId = requireIdentity(input.taskId, "Task final-review rebind Task id");
249
+ const fromControlPlaneDigest = requireDigest(input.fromControlPlaneDigest, "Task final-review rebind source control-plane digest");
250
+ const toControlPlaneDigest = requireDigest(input.toControlPlaneDigest, "Task final-review rebind target control-plane digest");
251
+ if (fromControlPlaneDigest === toControlPlaneDigest) {
252
+ throw new Error("Task final-review rebind source and target control planes are identical.");
253
+ }
254
+ if (exactControlPlaneDigest(input.currentControlPlane) !== toControlPlaneDigest) {
255
+ throw new Error("Task final-review rebind target is not the current exact control plane.");
256
+ }
257
+ if (resolve(input.currentControlPlane.yuiHome) !== home) {
258
+ throw new Error("Task final-review rebind target control plane belongs to another Home.");
259
+ }
260
+ const fromReleaseId = requireText(input.fromReleaseId, "Source release id");
261
+ const toReleaseId = requireText(input.toReleaseId, "Target release id");
262
+ if (fromReleaseId === toReleaseId) {
263
+ throw new Error("Task final-review rebind source and target releases are identical.");
264
+ }
265
+ if (readHandoverFence(home) !== null) {
266
+ throw new Error("Task final-review rebind is blocked by an unfinished release handover.");
267
+ }
268
+ const active = readActiveReleasePointer(home);
269
+ if (active === null
270
+ || active.releaseId !== toReleaseId
271
+ || active.buildId !== input.currentControlPlane.buildId
272
+ || active.packageDigest !== input.currentControlPlane.activeReleaseDigest) {
273
+ throw new Error("The active release does not match the requested target release and exact control plane.");
274
+ }
275
+ const sourcePath = join(home, "runtime", "control-plane", `${fromControlPlaneDigest}.json`);
276
+ const sourceControl = parseExactControlPlaneDescriptor(readFileSync(sourcePath, "utf8"));
277
+ if (exactControlPlaneDigest(sourceControl) !== fromControlPlaneDigest) {
278
+ throw new Error("Task final-review rebind source control-plane descriptor digest is invalid.");
279
+ }
280
+ if (resolve(sourceControl.yuiHome) !== home) {
281
+ throw new Error("Task final-review rebind source control plane belongs to another Home.");
282
+ }
283
+ const fromRelease = installedReleaseIdentity(home, fromReleaseId, sourceControl, "source");
284
+ const toRelease = installedReleaseIdentity(home, toReleaseId, input.currentControlPlane, "target");
285
+ const handover = readHandoverReceipt(home);
286
+ if (handover === null
287
+ || handover.outcome !== "completed"
288
+ || handover.activatedReleaseId !== toReleaseId
289
+ || typeof handover.handoverId !== "string"
290
+ || handover.handoverId.length === 0) {
291
+ throw new Error("Task final-review rebind requires the completed release handover that activated the target.");
292
+ }
293
+ return Object.freeze({
294
+ schemaVersion: 1,
295
+ taskId,
296
+ fromControlPlaneDigest,
297
+ toControlPlaneDigest,
298
+ fromRelease,
299
+ toRelease,
300
+ handoverId: handover.handoverId
301
+ });
302
+ }
303
+ function installedReleaseIdentity(home, releaseId, control, label) {
304
+ if (control.buildId === undefined || control.activeReleaseDigest === undefined) {
305
+ throw new Error(`Task final-review rebind ${label} control plane is not an immutable release.`);
306
+ }
307
+ let manifest;
308
+ try {
309
+ manifest = verifyReleaseIntegrity(join(releasesDirectory(home), releaseId));
310
+ }
311
+ catch (error) {
312
+ throw new Error(`Task final-review rebind ${label} release is unknown or invalid: ${releaseId}.`, { cause: error });
313
+ }
314
+ if (releaseDirectoryName(manifest) !== releaseId
315
+ || manifest.buildId !== control.buildId
316
+ || manifest.packageDigest !== control.activeReleaseDigest) {
317
+ throw new Error(`Task final-review rebind ${label} release does not match its control plane.`);
318
+ }
319
+ return validateReleaseIdentity({
320
+ releaseId,
321
+ version: manifest.version,
322
+ buildId: manifest.buildId,
323
+ packageDigest: manifest.packageDigest,
324
+ ...(manifest.sourceCommit === undefined ? {} : { sourceCommit: manifest.sourceCommit })
325
+ }, label);
326
+ }
327
+ function releaseIdentityFromPayload(payload, prefix) {
328
+ const title = prefix === "from" ? "From" : "To";
329
+ return validateReleaseIdentity({
330
+ releaseId: payload[`${prefix}ReleaseId`],
331
+ version: payload[`${prefix}ReleaseVersion`],
332
+ buildId: payload[`${prefix}ReleaseBuildId`],
333
+ packageDigest: payload[`${prefix}ReleasePackageDigest`],
334
+ ...(payload[`${prefix}ReleaseSourceCommit`] === undefined
335
+ ? {}
336
+ : { sourceCommit: payload[`${prefix}ReleaseSourceCommit`] })
337
+ }, title);
338
+ }
339
+ function validateReleaseIdentity(value, label) {
340
+ return Object.freeze({
341
+ releaseId: requireText(value.releaseId, `Task final-review rebind ${label} release id`),
342
+ version: requireText(value.version, `Task final-review rebind ${label} release version`),
343
+ buildId: requireText(value.buildId, `Task final-review rebind ${label} release build id`),
344
+ packageDigest: requireDigest(value.packageDigest, `Task final-review rebind ${label} release package digest`),
345
+ ...(value.sourceCommit === undefined
346
+ ? {}
347
+ : { sourceCommit: requireText(value.sourceCommit, `Task final-review rebind ${label} source commit`) })
348
+ });
349
+ }
350
+ function compareCreatedAt(left, right) {
351
+ return left.createdAt.localeCompare(right.createdAt);
352
+ }
353
+ function requireDigest(value, label) {
354
+ if (typeof value !== "string" || !/^[a-f0-9]{64}$/u.test(value)) {
355
+ throw new Error(`${label} is invalid.`);
356
+ }
357
+ return value;
358
+ }
359
+ function requireText(value, label) {
360
+ if (typeof value !== "string" || value.includes("\0")) {
361
+ throw new Error(`${label} is invalid.`);
362
+ }
363
+ const normalized = value.trim();
364
+ if (normalized.length === 0)
365
+ throw new Error(`${label} is required.`);
366
+ return normalized;
367
+ }
@@ -1,82 +1,22 @@
1
1
  import { MAX_SESSION_TITLE_LENGTH } from "../runtime/sessionTitle.js";
2
- const TITLED_RUN_SEPARATOR = " · Run ";
3
- /** Adds the causal token consumed by the native Turn-complete Hook. */
4
- export function markYuiRunInput(input, runId, title) {
2
+ /**
3
+ * Prefixes managed launch input with its session title. The bootstrap body
4
+ * exposes the exact current Run identity to the Agent, while structured
5
+ * delivery metadata and control hooks retain the delivery fence.
6
+ */
7
+ export function prefixYuiTitleInput(input, title) {
5
8
  const text = input.replace(/\r/g, "").trim();
6
- return managedRunInput(text, runId, title);
7
- }
8
- /** Replaces the causal token on an input already owned by a persisted Yui Run. */
9
- export function retagYuiRunInput(input, runId, title) {
10
- const lines = input.replace(/\r/g, "").split("\n");
11
- if (lines.length < 3
12
- || lines[1] !== ""
13
- || runIdFromHeader(lines[0]) === undefined) {
14
- throw new Error("Managed Run input header is required.");
15
- }
16
- return managedRunInput(lines.slice(2).join("\n").trim(), runId, title);
17
- }
18
- function managedRunInput(text, runId, title) {
19
- const id = requireRunId(runId);
20
9
  if (text.length === 0)
21
- throw new Error("Run input is required.");
22
- return `${requireRunTitle(title)}${TITLED_RUN_SEPARATOR}${id}\n\n${text}`;
23
- }
24
- /** Reads the last managed Yui header from Codex's structured input-messages. */
25
- export function yuiRunIdFromInputMessages(value) {
26
- if (!Array.isArray(value))
27
- return undefined;
28
- let found;
29
- for (const entry of value) {
30
- if (typeof entry !== "string")
31
- continue;
32
- const lines = entry.replace(/\r/g, "").split("\n");
33
- if (lines.length < 3 || lines[1] !== "")
34
- continue;
35
- const runId = runIdFromHeader(lines[0]);
36
- if (runId !== undefined)
37
- found = runId;
38
- }
39
- return found;
40
- }
41
- /** Removes only a valid managed Run header, preserving user-authored lookalikes. */
42
- export function yuiRunBodyFromInputMessage(value) {
43
- const lines = value.replace(/\r/g, "").trim().split("\n");
44
- return lines.length >= 3
45
- && lines[1] === ""
46
- && runIdFromHeader(lines[0]) !== undefined
47
- ? lines.slice(2).join("\n")
48
- : lines.join("\n");
49
- }
50
- function runIdFromHeader(line) {
51
- const separator = line.lastIndexOf(TITLED_RUN_SEPARATOR);
52
- if (separator > 0) {
53
- try {
54
- requireRunTitle(line.slice(0, separator));
55
- return requireRunId(line.slice(separator + TITLED_RUN_SEPARATOR.length));
56
- }
57
- catch {
58
- return undefined;
59
- }
60
- }
61
- return undefined;
10
+ throw new Error("Managed input body is required.");
11
+ return `${requireRunTitle(title)}\n\n${text}`;
62
12
  }
63
13
  function requireRunTitle(value) {
64
14
  const title = value.trim();
65
15
  if (title.length === 0
66
16
  || title.length > MAX_SESSION_TITLE_LENGTH
67
- || !title.startsWith("Yui · ")
68
- || /[\r\n\0]/u.test(title)) {
17
+ || /[\r\n\0]/u.test(title)
18
+ || !title.startsWith("Yui ")) {
69
19
  throw new Error("Run title is invalid.");
70
20
  }
71
21
  return title;
72
22
  }
73
- function requireRunId(value) {
74
- const id = value.trim();
75
- if (id.length === 0
76
- || id.length > 1_024
77
- || ["__proto__", "prototype", "constructor", ".", ".."].includes(id)
78
- || /[\/\\\0\s]/.test(id)) {
79
- throw new Error("Run id is invalid.");
80
- }
81
- return id;
82
- }
@@ -13,9 +13,8 @@ import { ProviderDeliveryUnknownError, ProviderConversationMissingError, Provide
13
13
  import { sameProviderAuthorityFence, validateProviderAuthorityFence } from "./providerAuthorityFence.js";
14
14
  import { validateRuntimeProcessExitObservation } from "./processExitObservation.js";
15
15
  import { readRuntimeStopReceipt, removeRuntimeStopReceipt } from "./runtimeStopReceipt.js";
16
+ import { AGENT_HOST_CONTROL_TIMEOUT_MS, AGENT_HOST_READY_TIMEOUT_MS } from "./runtimeDeadlines.js";
16
17
  export const AGENT_HOST_CONTROL_PROTOCOL = "yui-agent-host/v2";
17
- const HOST_CONTROL_TIMEOUT_MS = 35_000;
18
- const HOST_READY_TIMEOUT_MS = 35_000;
19
18
  const HOST_CONTROL_MAX_BYTES = 32 * 1024;
20
19
  export function serializeAgentHostLaunchControl(control) {
21
20
  return JSON.stringify(validateControl(control));
@@ -599,7 +598,7 @@ export async function inspectAgentHost(input) {
599
598
  return result.snapshot;
600
599
  }
601
600
  export async function waitForAgentHostLaunchAck(input) {
602
- const deadline = Date.now() + (input.timeoutMs ?? HOST_READY_TIMEOUT_MS);
601
+ const deadline = Date.now() + (input.timeoutMs ?? AGENT_HOST_READY_TIMEOUT_MS);
603
602
  let lastError;
604
603
  while (Date.now() < deadline) {
605
604
  try {
@@ -634,7 +633,7 @@ async function sendAgentHostControl(input) {
634
633
  const timer = setTimeout(() => {
635
634
  client.destroy();
636
635
  reject(new Error("Agent Host control request timed out."));
637
- }, HOST_CONTROL_TIMEOUT_MS);
636
+ }, AGENT_HOST_CONTROL_TIMEOUT_MS);
638
637
  let settled = false;
639
638
  const settle = (callback, value) => {
640
639
  if (settled)
@@ -39,6 +39,12 @@ export class CodexAppServerRuntime {
39
39
  const result = await this.transport.request("thread/resume", { threadId: id });
40
40
  return parseThreadSnapshot(result, id, true);
41
41
  }
42
+ async setConversationName(input) {
43
+ await this.transport.request("thread/name/set", {
44
+ threadId: text(input.conversationId, "Codex thread id"),
45
+ name: text(input.name, "Codex thread name")
46
+ });
47
+ }
42
48
  async readConversation(conversationId) {
43
49
  const id = text(conversationId, "Codex thread id");
44
50
  const result = await this.transport.request("thread/read", {
@@ -9,7 +9,6 @@ import { hasRuntimeCleanupObligation, isRuntimeLaunchReservation, runtimeLifecyc
9
9
  import { nativeSessionIdForLaunch } from "./preallocatedNativeSession.js";
10
10
  import { agentRunDeliveryReceiptId } from "../run/agentRun.js";
11
11
  import { writeTextFileAtomically } from "../storage/durableFile.js";
12
- import { readActiveReleasePointer } from "../release/runtimeRelease.js";
13
12
  export const EXACT_CONTROL_ARGUMENT = "--yui-control";
14
13
  export const YUI_CONTROL_PLANE_DESCRIPTOR = "YUI_CONTROL_PLANE_DESCRIPTOR";
15
14
  export const YUI_TASK_RUNTIME_DESCRIPTOR = "YUI_TASK_RUNTIME_DESCRIPTOR";
@@ -174,43 +173,67 @@ export async function assertExactControlPlanePreflight(input, options = {}) {
174
173
  + `(expected ${descriptor.identity.aggregateSchemaVersion}, found `
175
174
  + `${storage.currentAggregateSchemaVersion ?? "unknown"}).`);
176
175
  }
177
- // Issue 02: bind the descriptor to the active release. A descriptor that
178
- // carries a build ID must match the Home's active release pointer; a
179
- // descriptor without a build ID is rejected once a Home has an active
180
- // release (old Sessions cannot mutate a released control plane). Homes
181
- // without an active release pointer keep the legacy continuity contract.
182
- const readActiveRelease = options.readActiveRelease ?? defaultReadActiveRelease;
183
- const activeRelease = readActiveRelease(descriptor.yuiHome);
184
- if (descriptor.buildId !== undefined) {
185
- if (activeRelease === null) {
186
- throw new Error("Exact control-plane descriptor names a release build but the Home has "
187
- + "no active release pointer.");
176
+ // A frozen descriptor authenticates the command that created it; it no
177
+ // longer pins the Home's deployment pointer for the lifetime of a Session.
178
+ // Continuity is the protocol/storage contract checked above and the durable
179
+ // Task/Role/Run identity checked below. This lets a compatible Controller or
180
+ // active release advance without invalidating a still-current Session.
181
+ if (options.checkController !== false) {
182
+ const call = options.callController ?? defaultCallController;
183
+ try {
184
+ const status = await call(descriptor.yuiHome, "controller.status", {});
185
+ assertControllerContinuityIdentity(status, descriptor.identity);
188
186
  }
189
- if (activeRelease.buildId !== descriptor.buildId) {
190
- throw new Error("Exact control-plane release changed since the descriptor was frozen "
191
- + `(expected ${descriptor.buildId}, active ${activeRelease.buildId}).`);
187
+ catch (error) {
188
+ if (!isDefinitelyNotRunning(error))
189
+ throw error;
192
190
  }
193
- if (descriptor.activeReleaseDigest !== undefined
194
- && activeRelease.packageDigest !== descriptor.activeReleaseDigest) {
195
- throw new Error("Exact control-plane release digest does not match the active release pointer.");
191
+ }
192
+ return descriptor;
193
+ }
194
+ /**
195
+ * Compatibility gate for an ordinary `yui` invocation inside a managed
196
+ * Session. The Session Manifest and durable runtime state authenticate the
197
+ * actor separately; this gate proves that the current CLI can safely share the
198
+ * Home with its storage and Controller without pinning package/build identity.
199
+ */
200
+ export async function assertCompatibleControlPlanePreflight(input, options = {}) {
201
+ const home = canonicalPath(input.actualHome);
202
+ const identity = validateVersionIdentity(options.identity ?? yuiVersionIdentity());
203
+ const storage = (options.inspectStorage ?? inspectStorageSchema)(home);
204
+ if (storage.status !== "current") {
205
+ const compatibleRecordOnlyOlder = storage.status === "unsupported"
206
+ && storage.incompatibleComponent === "record"
207
+ && storage.direction === "older"
208
+ && storage.currentLayoutVersion === identity.storageLayoutVersion
209
+ && storage.currentAggregateSchemaVersion === identity.aggregateSchemaVersion;
210
+ if (!compatibleRecordOnlyOlder) {
211
+ throw new Error(`Managed control-plane storage is not current: ${storage.status}.`);
196
212
  }
213
+ (options.openCompatibleStore ?? openCompatibleFileTaskStore)(home).getConfig();
197
214
  }
198
- else if (activeRelease !== null) {
199
- throw new Error("Exact control-plane descriptor predates the active release; old Sessions "
200
- + "cannot mutate a released control plane. Re-launch through the current release.");
215
+ if (storage.currentLayoutVersion !== identity.storageLayoutVersion) {
216
+ throw new Error("Managed control-plane storage layout is incompatible "
217
+ + `(expected ${identity.storageLayoutVersion}, found `
218
+ + `${storage.currentLayoutVersion ?? "unknown"}).`);
219
+ }
220
+ if (storage.currentAggregateSchemaVersion !== identity.aggregateSchemaVersion) {
221
+ throw new Error("Managed control-plane aggregate schema is incompatible "
222
+ + `(expected ${identity.aggregateSchemaVersion}, found `
223
+ + `${storage.currentAggregateSchemaVersion ?? "unknown"}).`);
201
224
  }
202
225
  if (options.checkController !== false) {
203
226
  const call = options.callController ?? defaultCallController;
204
227
  try {
205
- const status = await call(descriptor.yuiHome, "controller.status", {});
206
- assertControllerContinuityIdentity(status, descriptor.identity);
228
+ const status = await call(home, "controller.status", {});
229
+ assertControllerContinuityIdentity(status, identity);
207
230
  }
208
231
  catch (error) {
209
232
  if (!isDefinitelyNotRunning(error))
210
233
  throw error;
211
234
  }
212
235
  }
213
- return descriptor;
236
+ return identity;
214
237
  }
215
238
  export function assertControllerStatusIdentity(status, expected = yuiVersionIdentity()) {
216
239
  if (!isRecord(status) || status.running !== true) {
@@ -539,16 +562,3 @@ function isRecord(value) {
539
562
  function isDefinitelyNotRunning(error) {
540
563
  return isRecord(error) && error.code === "CONTROLLER_NOT_RUNNING";
541
564
  }
542
- function defaultReadActiveRelease(home) {
543
- try {
544
- const pointer = readActiveReleasePointer(home);
545
- return pointer === null
546
- ? null
547
- : { buildId: pointer.buildId, packageDigest: pointer.packageDigest };
548
- }
549
- catch {
550
- // A damaged pointer fails closed: treat it as an active release the
551
- // descriptor cannot match, rather than silently skipping the gate.
552
- return { buildId: "unknown", packageDigest: "unknown" };
553
- }
554
- }