@zq-silk/yui 0.2.0 → 0.4.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.
Files changed (208) hide show
  1. package/ARCHITECTURE.md +603 -133
  2. package/README.md +806 -31
  3. package/dist/agent/agent.js +2 -1
  4. package/dist/agent/argumentPolicy.js +3 -1
  5. package/dist/agent/launchEnvironment.js +106 -0
  6. package/dist/agent/managedRuntimeEnvironment.js +34 -0
  7. package/dist/brief/taskBrief.js +11 -1
  8. package/dist/cli/agentConfigurationPicker.js +287 -0
  9. package/dist/cli/commandCatalog.js +488 -60
  10. package/dist/cli/completion.js +146 -22
  11. package/dist/cli/helpRenderer.js +3 -1
  12. package/dist/cli/interactionCandidates.js +53 -15
  13. package/dist/cli/interactionPolicy.js +267 -30
  14. package/dist/cli/interactiveSelection.js +6 -2
  15. package/dist/cli/invocationRouter.js +5 -1
  16. package/dist/cli/operatorWizard.js +87 -0
  17. package/dist/cli/roleOptionCatalog.js +1 -0
  18. package/dist/cli/roleWizard.js +185 -21
  19. package/dist/cli/updateCommand.js +62 -19
  20. package/dist/cli/updateOrchestrator.js +539 -0
  21. package/dist/cli/updatePorts.js +1119 -0
  22. package/dist/cli/upgradeCommand.js +112 -0
  23. package/dist/cli.js +1420 -86
  24. package/dist/commands/agentCommands.js +146 -3
  25. package/dist/commands/configCommands.js +126 -0
  26. package/dist/commands/controllerCommands.js +365 -0
  27. package/dist/commands/globalRoleCommands.js +168 -126
  28. package/dist/commands/jobCommands.js +18 -8
  29. package/dist/commands/operatorCommands.js +159 -9
  30. package/dist/commands/profileCommands.js +203 -0
  31. package/dist/commands/projectCommands.js +650 -0
  32. package/dist/commands/roleConfiguration.js +85 -24
  33. package/dist/commands/roleRuntimeGuard.js +12 -0
  34. package/dist/commands/roleSkillValidation.js +47 -0
  35. package/dist/commands/taskActor.js +127 -0
  36. package/dist/commands/taskCommands.js +4201 -313
  37. package/dist/commands/taskCompletionGate.js +131 -0
  38. package/dist/commands/taskContextCommand.js +244 -30
  39. package/dist/commands/taskInputCommands.js +177 -59
  40. package/dist/commands/taskIntegrationCommands.js +303 -0
  41. package/dist/commands/taskOverviewCommand.js +363 -0
  42. package/dist/commands/taskRoleRuntimeStatus.js +125 -19
  43. package/dist/commands/textInput.js +15 -0
  44. package/dist/completion/completionInstaller.js +26 -22
  45. package/dist/config/yuiConfig.js +4 -3
  46. package/dist/context/dispatchContext.js +90 -38
  47. package/dist/context/roleSessionContext.js +119 -0
  48. package/dist/controller/claudeLifecycleHook.js +203 -0
  49. package/dist/controller/clientRuntime.js +408 -56
  50. package/dist/controller/codexLifecycleHook.js +108 -0
  51. package/dist/controller/controller.js +1089 -32
  52. package/dist/controller/domainIdentity.js +505 -0
  53. package/dist/controller/ephemeralResourceReaper.js +131 -0
  54. package/dist/controller/fileSchedulerStoreAdapter.js +2153 -103
  55. package/dist/controller/providerHookRunFence.js +127 -0
  56. package/dist/controller/resourceCleanupLinux.js +286 -0
  57. package/dist/controller/resourceInventory.js +531 -0
  58. package/dist/controller/resourceInventoryLinux.js +610 -0
  59. package/dist/controller/runtime.js +629 -10
  60. package/dist/controller/runtimeEventInbox.js +564 -0
  61. package/dist/controller/runtimeEventProcessor.js +248 -0
  62. package/dist/controller/runtimeLaunchCoordinator.js +477 -0
  63. package/dist/controller/sessionNotify.js +121 -78
  64. package/dist/coordination/deadlineScheduler.js +15 -0
  65. package/dist/coordination/mailboxScheduler.js +108 -0
  66. package/dist/coordination/workMailbox.js +329 -0
  67. package/dist/coordination/workMailboxQueue.js +86 -0
  68. package/dist/core/controllerClient.js +19 -5
  69. package/dist/core/controllerEndpoint.js +37 -0
  70. package/dist/core/controllerServer.js +218 -10
  71. package/dist/core/protocol.js +6 -2
  72. package/dist/decision/decision.js +2 -1
  73. package/dist/doctor/doctor.js +681 -32
  74. package/dist/domain/validation.js +53 -0
  75. package/dist/errors/cliError.js +5 -3
  76. package/dist/event/taskEvent.js +7 -3
  77. package/dist/execution/codexThreadNaming.js +160 -0
  78. package/dist/execution/executionGroup.js +579 -0
  79. package/dist/executor/agentAdapter.js +255 -40
  80. package/dist/executor/agentConfigurationCatalog.js +326 -0
  81. package/dist/executor/agentConfigurationProbe.js +506 -0
  82. package/dist/executor/agentExecutor.js +625 -10
  83. package/dist/executor/codexConfigConflict.js +290 -0
  84. package/dist/executor/effectiveLaunch.js +340 -0
  85. package/dist/executor/executorRegistry.js +238 -36
  86. package/dist/executor/fileRoleLaunchPlanner.js +550 -40
  87. package/dist/executor/turnCompletion.js +126 -0
  88. package/dist/input/inputRequest.js +30 -9
  89. package/dist/integration/changeSet.js +36 -0
  90. package/dist/integration/checkResult.js +24 -0
  91. package/dist/integration/gitIntegrationService.js +695 -0
  92. package/dist/integration/integrationAttempt.js +142 -0
  93. package/dist/interaction/operatorPresentation.js +96 -0
  94. package/dist/lifecycle/canonicalLifecycleEvent.js +342 -0
  95. package/dist/lifecycle/exactRunTerminalization.js +572 -0
  96. package/dist/lifecycle/providerLifecycleMapping.js +190 -0
  97. package/dist/lifecycle/taskRoleSessionReset.js +124 -0
  98. package/dist/message/message.js +23 -7
  99. package/dist/milestone/milestone.js +2 -1
  100. package/dist/operator/operatorSessionHistory.js +124 -0
  101. package/dist/output/agentConfigurationPresentation.js +43 -0
  102. package/dist/output/rolePresentation.js +34 -10
  103. package/dist/output/terminal.js +8 -0
  104. package/dist/output/timePresentation.js +55 -0
  105. package/dist/profile/agentProfile.js +128 -0
  106. package/dist/repository/gitWorkspace.js +578 -24
  107. package/dist/repository/project.js +213 -0
  108. package/dist/repository/taskWorkspaceCoordinator.js +392 -0
  109. package/dist/repository/taskWorkspacePreparer.js +1688 -191
  110. package/dist/review/reviewConfig.js +11 -0
  111. package/dist/review/reviewRound.js +399 -0
  112. package/dist/review/taskFinalReviewContract.js +90 -0
  113. package/dist/role/role.js +124 -23
  114. package/dist/run/agentRun.js +155 -12
  115. package/dist/run/runIdentity.js +82 -0
  116. package/dist/runtime/exactControlPlane.js +472 -0
  117. package/dist/runtime/index.js +8 -0
  118. package/dist/runtime/lifecycleReservation.js +38 -0
  119. package/dist/runtime/ports.js +11 -0
  120. package/dist/runtime/preallocatedNativeSession.js +13 -0
  121. package/dist/runtime/promptEnvelope.js +30 -0
  122. package/dist/runtime/runtimeBinding.js +31 -0
  123. package/dist/runtime/runtimeOwner.js +14 -0
  124. package/dist/runtime/sessionLaunchRequest.js +62 -0
  125. package/dist/runtime/sessionTitle.js +54 -0
  126. package/dist/runtime/taskRuntimeIsolation.js +643 -0
  127. package/dist/runtime/tmuxAdapters.js +315 -0
  128. package/dist/runtime/turnCompletion.js +3 -0
  129. package/dist/runtime/validation.js +23 -0
  130. package/dist/scheduler/activeRoleRunDelivery.js +342 -32
  131. package/dist/scheduler/activeTaskProgress.js +63 -0
  132. package/dist/scheduler/leaderFailure.js +2 -1
  133. package/dist/scheduler/leaderWakeupProcessor.js +307 -66
  134. package/dist/scheduler/operatorInputNotificationProcessor.js +109 -46
  135. package/dist/scheduler/operatorNotification.js +44 -2
  136. package/dist/scheduler/ports.js +28 -1
  137. package/dist/scheduler/roleRunLiveness.js +131 -25
  138. package/dist/scheduler/roleRunStall.js +951 -0
  139. package/dist/scheduler/taskExecutionProjection.js +544 -0
  140. package/dist/scheduler/wakeupQueue.js +3 -0
  141. package/dist/setup/setupCommand.js +302 -52
  142. package/dist/storage/compatibleTaskStore.js +102 -0
  143. package/dist/storage/migration/baseline.js +78 -0
  144. package/dist/storage/migration/classifier.js +51 -0
  145. package/dist/storage/migration/compatibleCodec.js +53 -0
  146. package/dist/storage/migration/engine.js +147 -0
  147. package/dist/storage/migration/index.js +33 -0
  148. package/dist/storage/migration/planner.js +154 -0
  149. package/dist/storage/migration/productionRegistry.js +486 -0
  150. package/dist/storage/migration/registry.js +169 -0
  151. package/dist/storage/migration/report.js +54 -0
  152. package/dist/storage/migration/types.js +31 -0
  153. package/dist/storage/storageSchema.js +147 -123
  154. package/dist/storage/storageVersions.js +11 -0
  155. package/dist/storage/taskStore.js +1793 -197
  156. package/dist/storage/upgrade/homeClassification.js +156 -0
  157. package/dist/storage/upgrade/homeMigrationTarget.js +595 -0
  158. package/dist/storage/upgrade/offlineUpgradeInventory.js +315 -0
  159. package/dist/storage/upgrade/productionMigrationRegistry.js +6 -0
  160. package/dist/storage/upgrade/recordVersionScan.js +176 -0
  161. package/dist/storage/upgrade/recordVersions.js +159 -0
  162. package/dist/storage/upgrade/switchProgress.js +80 -0
  163. package/dist/storage/upgrade/upgradeOrchestrator.js +948 -0
  164. package/dist/storage/upgrade/upgradeReceipt.js +161 -0
  165. package/dist/storage/upgradeCoordination.js +186 -0
  166. package/dist/storage/upgradeFence.js +366 -0
  167. package/dist/task/task.js +132 -26
  168. package/dist/task/taskRecordReference.js +66 -0
  169. package/dist/tmux/commandExecutor.js +75 -2
  170. package/dist/tmux/tmuxManager.js +747 -49
  171. package/dist/version.js +23 -0
  172. package/dist/web/assets/assetManifest.js +62 -0
  173. package/dist/web/assets/client/app.js +631 -0
  174. package/dist/web/assets/client/components.js +605 -0
  175. package/dist/web/assets/client/dom.js +14 -0
  176. package/dist/web/assets/client/format.js +28 -0
  177. package/dist/web/assets/client/i18n.js +494 -0
  178. package/dist/web/assets/client/markdown.js +114 -0
  179. package/dist/web/assets/client/theme.js +32 -0
  180. package/dist/web/assets/client/view.js +458 -0
  181. package/dist/web/assets/fontData.js +12 -0
  182. package/dist/web/assets/fonts.js +12 -0
  183. package/dist/web/assets/shell.js +114 -0
  184. package/dist/web/assets/styles/cards.js +135 -0
  185. package/dist/web/assets/styles/layout.js +47 -0
  186. package/dist/web/assets/styles/markdown.js +29 -0
  187. package/dist/web/assets/styles/responsive.js +39 -0
  188. package/dist/web/assets/styles/tokens.js +101 -0
  189. package/dist/web/assets/styles/widgets.js +147 -0
  190. package/dist/web/tmuxWebTerminal.js +158 -0
  191. package/dist/web/webServer.js +463 -0
  192. package/dist/web/webSnapshot.js +148 -0
  193. package/dist/workItem/workItem.js +642 -23
  194. package/dist/workspace/gitChangeSetCapture.js +86 -0
  195. package/dist/workspace/workItemChangeSetManager.js +445 -0
  196. package/dist/worktree/managedWorkspace.js +202 -0
  197. package/docs/task-local-identity.md +62 -0
  198. package/i18n/README.zh-CN.md +406 -31
  199. package/package.json +10 -2
  200. package/skills/yui-leader/SKILL.md +601 -39
  201. package/skills/yui-operator/SKILL.md +255 -34
  202. package/skills/yui-reviewer/SKILL.md +57 -0
  203. package/skills/yui-worker/SKILL.md +214 -17
  204. package/dist/commands/repositoryCommands.js +0 -86
  205. package/dist/operator/operatorContext.js +0 -66
  206. package/dist/repository/repository.js +0 -55
  207. package/dist/scheduler/archivedTaskRuntime.js +0 -12
  208. package/dist/worktree/roleWorkspace.js +0 -62
@@ -0,0 +1,190 @@
1
+ import { createCanonicalLifecycleEvent, CanonicalLifecycleError } from "./canonicalLifecycleEvent.js";
2
+ /**
3
+ * Claude, mapped from its observed SessionStart / UserPromptSubmit / StopFailure
4
+ * contract (claude 2.1.x). SessionStart with source=startup fires at process
5
+ * startup, *before* the first prompt, so only that exact variant proves pre-input
6
+ * readiness. A resume/clear/compact SessionStart occurs within an existing
7
+ * session and is downgraded to provider-session-started (never pre-input-ready).
8
+ * A single prompt push precedes the only acceptance fence, an identity-matched
9
+ * UserPromptSubmit.
10
+ */
11
+ const CLAUDE_PRE_INPUT_SESSION_SOURCE = "startup";
12
+ const CLAUDE_LIFECYCLE_MAPPING = {
13
+ adapterId: "claude",
14
+ preInputReadiness: {
15
+ status: "supported",
16
+ nativeEvent: "SessionStart(source=startup)",
17
+ note: "Claude fires SessionStart at process startup before the first prompt, so "
18
+ + "readiness is proven pre-input by a native durable event. Only the "
19
+ + "startup variant qualifies; resume/clear/compact are session-started only."
20
+ },
21
+ supportedSignals: [
22
+ "native-session-start",
23
+ "native-prompt-submit",
24
+ "native-turn-progress",
25
+ "native-turn-complete",
26
+ "native-stop-failure"
27
+ ],
28
+ map(signal) {
29
+ switch (signal.kind) {
30
+ case "native-session-start": {
31
+ // The SessionStart source discriminator is required for Claude: readiness
32
+ // safety depends on distinguishing startup from later in-session variants.
33
+ if (signal.sessionSource === undefined) {
34
+ throw new CanonicalLifecycleError("Claude native-session-start requires the SessionStart source variant.");
35
+ }
36
+ if (signal.sessionSource !== CLAUDE_PRE_INPUT_SESSION_SOURCE) {
37
+ // A non-startup SessionStart (resume/clear/compact) fires inside an
38
+ // existing session and cannot prove pre-input readiness; downgrade it.
39
+ return createCanonicalLifecycleEvent({
40
+ phase: "provider-session-started",
41
+ source: "provider-native",
42
+ evidence: "provider-native-durable",
43
+ fence: signal.fence
44
+ });
45
+ }
46
+ // SessionStart(startup) proves readiness before any input for Claude.
47
+ return createCanonicalLifecycleEvent({
48
+ phase: "provider-ready",
49
+ source: "provider-native",
50
+ evidence: "provider-native-durable",
51
+ preInputReady: true,
52
+ readinessVariant: `SessionStart(source=${signal.sessionSource})`,
53
+ fence: signal.fence
54
+ });
55
+ }
56
+ case "native-prompt-submit":
57
+ return createCanonicalLifecycleEvent({
58
+ phase: "provider-accepted",
59
+ source: "provider-native",
60
+ evidence: "provider-native-durable",
61
+ fence: signal.fence
62
+ });
63
+ case "native-turn-progress":
64
+ return createCanonicalLifecycleEvent({
65
+ phase: "turn-progress",
66
+ source: "provider-native",
67
+ evidence: "provider-native-durable",
68
+ ...(signal.sequence === undefined ? {} : { sequence: signal.sequence }),
69
+ fence: signal.fence
70
+ });
71
+ case "native-turn-complete":
72
+ return createCanonicalLifecycleEvent({
73
+ phase: "turn-terminal",
74
+ source: "provider-native",
75
+ evidence: "provider-native-durable",
76
+ summary: signal.summary,
77
+ fence: signal.fence
78
+ });
79
+ case "native-stop-failure":
80
+ return createCanonicalLifecycleEvent({
81
+ phase: "turn-terminal",
82
+ source: "provider-native",
83
+ evidence: "provider-native-durable",
84
+ summary: signal.summary,
85
+ fence: signal.fence
86
+ });
87
+ default:
88
+ throw unsupportedSignal("claude", signal);
89
+ }
90
+ }
91
+ };
92
+ /**
93
+ * Codex 0.145, mapped from its observed run_turn(input) -> SessionStart ->
94
+ * UserPromptSubmit ordering. SessionStart fires *inside* run_turn — after the
95
+ * first input — so it can only prove that the session/thread now exists, never
96
+ * pre-input readiness. UserPromptSubmit is the acceptance fence; the notify
97
+ * agent-turn-complete is the terminal fact. Codex emits no StopFailure hook.
98
+ */
99
+ const CODEX_LIFECYCLE_MAPPING = {
100
+ adapterId: "codex",
101
+ preInputReadiness: {
102
+ status: "unsupported",
103
+ reason: "not-available",
104
+ note: "Codex 0.145 SessionStart fires within run_turn(input), i.e. after the "
105
+ + "first input; no native event precedes the first prompt, so pre-input "
106
+ + "readiness is not available and fails closed."
107
+ },
108
+ supportedSignals: [
109
+ "native-session-start",
110
+ "native-prompt-submit",
111
+ "native-turn-progress",
112
+ "native-turn-complete"
113
+ ],
114
+ map(signal) {
115
+ switch (signal.kind) {
116
+ case "native-session-start":
117
+ // SessionStart proves the thread exists but arrives after the first
118
+ // input, so it maps to session-started only — never ready, never pre-input.
119
+ return createCanonicalLifecycleEvent({
120
+ phase: "provider-session-started",
121
+ source: "provider-native",
122
+ evidence: "provider-native-durable",
123
+ fence: signal.fence
124
+ });
125
+ case "native-prompt-submit":
126
+ return createCanonicalLifecycleEvent({
127
+ phase: "provider-accepted",
128
+ source: "provider-native",
129
+ evidence: "provider-native-durable",
130
+ fence: signal.fence
131
+ });
132
+ case "native-turn-progress":
133
+ return createCanonicalLifecycleEvent({
134
+ phase: "turn-progress",
135
+ source: "provider-native",
136
+ evidence: "provider-native-durable",
137
+ ...(signal.sequence === undefined ? {} : { sequence: signal.sequence }),
138
+ fence: signal.fence
139
+ });
140
+ case "native-turn-complete":
141
+ return createCanonicalLifecycleEvent({
142
+ phase: "turn-terminal",
143
+ source: "provider-native",
144
+ evidence: "provider-native-durable",
145
+ summary: signal.summary,
146
+ fence: signal.fence
147
+ });
148
+ case "native-stop-failure":
149
+ // Codex 0.145 has no StopFailure hook; refuse rather than invent one.
150
+ throw unsupportedSignal("codex", signal);
151
+ default:
152
+ throw unsupportedSignal("codex", signal);
153
+ }
154
+ }
155
+ };
156
+ const PROVIDER_LIFECYCLE_MAPPINGS = Object.freeze({
157
+ claude: CLAUDE_LIFECYCLE_MAPPING,
158
+ codex: CODEX_LIFECYCLE_MAPPING
159
+ });
160
+ /**
161
+ * Registry lookup: returns the mapping for an adapter without the caller
162
+ * branching on the provider name. Unknown adapters fail closed.
163
+ */
164
+ export function providerLifecycleMapping(adapterId) {
165
+ const mapping = findProviderLifecycleMapping(adapterId);
166
+ if (mapping === null) {
167
+ throw new CanonicalLifecycleError(`No lifecycle mapping for adapter: ${adapterId}.`);
168
+ }
169
+ return mapping;
170
+ }
171
+ export function findProviderLifecycleMapping(adapterId) {
172
+ return adapterId === "codex" || adapterId === "claude"
173
+ ? PROVIDER_LIFECYCLE_MAPPINGS[adapterId]
174
+ : null;
175
+ }
176
+ /** Neutral capability lookup for consumers that need only the readiness fact. */
177
+ export function preInputReadinessCapability(adapterId) {
178
+ return providerLifecycleMapping(adapterId).preInputReadiness;
179
+ }
180
+ /**
181
+ * Maps a native signal through the adapter selected by its fence. The fence's
182
+ * adapterId chooses the mapping, so a Codex signal can never be mapped by
183
+ * Claude's rules or vice versa.
184
+ */
185
+ export function mapNativeLifecycleSignal(signal) {
186
+ return providerLifecycleMapping(signal.fence.adapterId).map(signal);
187
+ }
188
+ function unsupportedSignal(adapterId, signal) {
189
+ return new CanonicalLifecycleError(`Adapter ${adapterId} does not emit native signal: ${signal.kind}.`);
190
+ }
@@ -0,0 +1,124 @@
1
+ import { enqueueWork } from "../coordination/workMailboxQueue.js";
2
+ import { resetTaskRoleSession } from "../executor/agentExecutor.js";
3
+ import { createTaskEvent } from "../event/taskEvent.js";
4
+ import { createTaskMessage } from "../message/message.js";
5
+ import { updateRoleStatus } from "../role/role.js";
6
+ import { createLeaderRecoveryNotification } from "../scheduler/operatorNotification.js";
7
+ import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
8
+ import { RUNTIME_CLEANUP_REQUIRED_REASON, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
9
+ import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
10
+ import { updateWorkItemStatus } from "../workItem/workItem.js";
11
+ import { terminalizeExactTaskRun } from "./exactRunTerminalization.js";
12
+ /**
13
+ * Resets the current native generation using Yui's own persisted identities.
14
+ * The caller supplies intent only; the Controller verifies process cleanup.
15
+ */
16
+ export function resetTaskRoleSessionGeneration(store, taskId, roleName, reason, now) {
17
+ const task = store.getTask(requiredIdentity(taskId, "Task id"));
18
+ if (task === null)
19
+ throw new Error(`Task not found: ${taskId}.`);
20
+ if (task.status !== "active")
21
+ throw new Error(`Task is not active: ${task.id}/${task.status}.`);
22
+ const normalizedRole = requiredIdentity(roleName, "Role name");
23
+ const role = store.getRole(task.id, normalizedRole);
24
+ if (role === null)
25
+ throw new Error(`Role not found: ${task.id}/${normalizedRole}.`);
26
+ const summary = `Reset native Session: ${requiredText(reason, "Reset reason")}`;
27
+ let sessions = store.getTaskRoleSessionSet(task.id, role.name);
28
+ const current = sessions?.sessions[sessions.activeAgentId];
29
+ const activeRun = store.getActiveAgentRun(task.id, role.name);
30
+ if (activeRun !== null) {
31
+ const receiptId = sessions?.inFlight?.runId === activeRun.id
32
+ ? sessions.inFlight.receiptId
33
+ : formatAgentRunReceiptId(task.id, activeRun.id);
34
+ const terminal = terminalizeExactTaskRun(store, {
35
+ taskId: task.id,
36
+ roleName: role.name,
37
+ agentId: activeRun.effective.agentId,
38
+ runId: activeRun.id,
39
+ receiptId,
40
+ ...(current === undefined ? {} : {
41
+ nativeSessionId: current.nativeSessionId,
42
+ ...(current.launchId === undefined ? {} : { launchId: current.launchId })
43
+ }),
44
+ outcome: { status: "failed", summary }
45
+ }, now);
46
+ if (terminal.disposition !== "applied" || terminal.run === null) {
47
+ throw new Error(`Task Role reset lost its exact Run fence: ${terminal.reason ?? "obsolete"}.`);
48
+ }
49
+ if (activeRun.purpose === "execution" && activeRun.workItemId !== undefined) {
50
+ const item = store.getWorkItem(task.id, activeRun.workItemId);
51
+ if (item !== null && !["completed", "failed", "retired"].includes(item.status)) {
52
+ store.saveWorkItem(task.id, updateWorkItemStatus(item, "failed", now, summary));
53
+ }
54
+ }
55
+ }
56
+ enqueueWork(store, runtimeLifecycleTarget({
57
+ scope: "task",
58
+ taskId: task.id,
59
+ roleName: role.name
60
+ }), RUNTIME_CLEANUP_REQUIRED_REASON, now, [{ type: "task", id: task.id }]);
61
+ sessions = store.getTaskRoleSessionSet(task.id, role.name);
62
+ if (sessions !== null)
63
+ store.saveTaskRoleSessionSet(resetTaskRoleSession(sessions, now));
64
+ const updatedRole = store.getRole(task.id, role.name);
65
+ store.saveRole(task.id, updateRoleStatus(updatedRole, role.name === "leader" ? "failed" : "idle", now));
66
+ const message = createTaskMessage(store.nextMessageId(task.id), task.id, summary, "system", { type: "system" }, now, activeRun === null ? {} : {
67
+ runId: activeRun.id,
68
+ ...(activeRun.workItemId === undefined ? {} : { workItemId: activeRun.workItemId })
69
+ });
70
+ store.saveMessage(task.id, message);
71
+ store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, "runtime.role-session-reset", {
72
+ roleName: role.name,
73
+ reason: summary,
74
+ ...(activeRun === null ? {} : { runId: activeRun.id }),
75
+ ...(current?.nativeSessionId === undefined
76
+ ? {}
77
+ : { nativeSessionId: current.nativeSessionId })
78
+ }, now));
79
+ if (role.name === "leader") {
80
+ const nativeSessionId = current?.nativeSessionId ?? `reset-${task.id}`;
81
+ store.saveLeaderFailure(recordLeaderFailure(task.id, nativeSessionId, summary, now, store.getLeaderFailure(task.id)));
82
+ store.saveOperatorNotification(createLeaderRecoveryNotification(task.id, summary, now, store.getOperatorNotification(task.id)));
83
+ enqueueWork(store, { kind: "operator" }, "leader-run-failed", now, [
84
+ { type: "task", id: task.id },
85
+ { type: "message", taskId: task.id, id: message.id },
86
+ ...(activeRun === null ? [] : [{
87
+ type: "run",
88
+ taskId: task.id,
89
+ id: activeRun.id
90
+ }])
91
+ ]);
92
+ }
93
+ else {
94
+ enqueueWork(store, { kind: "role", taskId: task.id, roleName: "leader" }, "role-run-failed", now, [
95
+ { type: "message", taskId: task.id, id: message.id },
96
+ ...(activeRun === null ? [] : [{
97
+ type: "run",
98
+ taskId: task.id,
99
+ id: activeRun.id
100
+ }])
101
+ ]);
102
+ }
103
+ return {
104
+ taskId: task.id,
105
+ roleName: role.name,
106
+ run: activeRun === null ? null : store.getAgentRun(task.id, activeRun.id),
107
+ ...(current === undefined ? {} : { nativeSessionId: current.nativeSessionId })
108
+ };
109
+ }
110
+ function requiredIdentity(value, label) {
111
+ const normalized = requiredText(value, label);
112
+ if (["__proto__", "prototype", "constructor", ".", ".."].includes(normalized)
113
+ || /[\/\\\0]/u.test(normalized))
114
+ throw new Error(`${label} is invalid.`);
115
+ return normalized;
116
+ }
117
+ function requiredText(value, label) {
118
+ if (typeof value !== "string" || value.includes("\0"))
119
+ throw new Error(`${label} is invalid.`);
120
+ const normalized = value.trim();
121
+ if (normalized.length === 0)
122
+ throw new Error(`${label} is required.`);
123
+ return normalized;
124
+ }
@@ -1,12 +1,14 @@
1
+ import { validateTaskRecordReference } from "../task/taskRecordReference.js";
1
2
  export const TASK_MESSAGE_KINDS = ["user", "operator", "role-result", "system"];
2
- export function createTaskMessage(id, body, kind, author, now, context = {}) {
3
+ export function createTaskMessage(id, taskId, body, kind, author, now, context = {}) {
3
4
  validateKindAndAuthor(kind, author);
4
5
  const message = {
5
- schemaVersion: 1,
6
+ schemaVersion: 2,
6
7
  id: requireSafeIdentity(id, "Message id"),
8
+ taskId: requireSafeIdentity(taskId, "Message Task id"),
7
9
  kind,
8
10
  author: normalizeAuthor(author),
9
- body: requireText(body, "Message body"),
11
+ body: requireBody(body),
10
12
  ...(context.runId === undefined
11
13
  ? {}
12
14
  : { runId: requireSafeIdentity(context.runId, "Message Run id") }),
@@ -22,16 +24,22 @@ export function taskMessageAuthorLabel(author) {
22
24
  return author.type === "role" ? author.roleName : author.type;
23
25
  }
24
26
  export function validateTaskMessage(message) {
25
- if (message.schemaVersion !== 1)
26
- throw new Error("Task Message must use schemaVersion 1.");
27
- requireSafeIdentity(message.id, "Message id");
27
+ if (message.schemaVersion !== 2)
28
+ throw new Error("Task Message must use schemaVersion 2.");
29
+ validateTaskRecordReference({ taskId: message.taskId, localId: message.id }, "message");
28
30
  requireText(message.body, "Message body");
29
31
  validateKindAndAuthor(message.kind, message.author);
30
32
  normalizeAuthor(message.author);
31
33
  if (message.runId !== undefined)
32
34
  requireSafeIdentity(message.runId, "Message Run id");
33
35
  if (message.workItemId !== undefined) {
34
- requireSafeIdentity(message.workItemId, "Message Work item id");
36
+ validateTaskRecordReference({
37
+ taskId: message.taskId,
38
+ localId: message.workItemId
39
+ }, "workItem");
40
+ }
41
+ if (message.runId !== undefined) {
42
+ validateTaskRecordReference({ taskId: message.taskId, localId: message.runId }, "agentRun");
35
43
  }
36
44
  if (typeof message.createdAt !== "string" || Number.isNaN(Date.parse(message.createdAt))) {
37
45
  throw new Error("Message createdAt is invalid.");
@@ -67,3 +75,11 @@ function requireText(value, label) {
67
75
  throw new Error(`${label} is required.`);
68
76
  return normalized;
69
77
  }
78
+ function requireBody(value) {
79
+ if (typeof value !== "string" || value.includes("\0")) {
80
+ throw new Error("Message body is invalid.");
81
+ }
82
+ if (value.trim().length === 0)
83
+ throw new Error("Message body is required.");
84
+ return value;
85
+ }
@@ -1,7 +1,8 @@
1
+ import { validateTaskRecordReference } from "../task/taskRecordReference.js";
1
2
  export function createMilestone(id, taskId, title, summary, now) {
2
3
  return {
3
4
  schemaVersion: 1,
4
- id: requireSafeIdentity(id, "Milestone id"),
5
+ id: validateTaskRecordReference({ taskId, localId: id }, "milestone").localId,
5
6
  taskId: requireSafeIdentity(taskId, "Task id"),
6
7
  title: requireText(title, "Milestone title"),
7
8
  summary: requireText(summary, "Milestone summary"),
@@ -0,0 +1,124 @@
1
+ import { normalizeRoleAgentSessionText, roleAgentSessionRef, validateRoleSessionSet } from "../executor/agentExecutor.js";
2
+ export function operatorSessionRef(session) {
3
+ return roleAgentSessionRef(session);
4
+ }
5
+ export function listOperatorSessions(sessions) {
6
+ if (sessions === null)
7
+ return [];
8
+ validateRoleSessionSet(sessions);
9
+ const current = Object.values(sessions.sessions).map((session) => (listItem(session, session.status === "stopped" || session.status === "broken"
10
+ ? "current"
11
+ : "running")));
12
+ const history = Object.values(sessions.history ?? {}).map((session) => (listItem(session, "history")));
13
+ return [...current, ...history].sort((left, right) => (right.updatedAt.localeCompare(left.updatedAt)
14
+ || right.createdAt.localeCompare(left.createdAt)
15
+ || left.ref.localeCompare(right.ref)));
16
+ }
17
+ export function prepareOperatorNewSession(sessions, targetAgentId, now) {
18
+ validateRoleSessionSet(sessions);
19
+ const agentId = requireText(targetAgentId, "Target Agent id");
20
+ const current = sessions.sessions[agentId];
21
+ const nextSessions = { ...sessions.sessions };
22
+ let history = { ...(sessions.history ?? {}) };
23
+ if (current !== undefined) {
24
+ history = archiveCurrent(history, current);
25
+ delete nextSessions[agentId];
26
+ }
27
+ return validateRoleSessionSet({
28
+ ...sessions,
29
+ activeAgentId: agentId,
30
+ sessions: nextSessions,
31
+ history,
32
+ updatedAt: requireDate(now)
33
+ });
34
+ }
35
+ export function prepareOperatorResumeSession(sessions, ref, now) {
36
+ validateRoleSessionSet(sessions);
37
+ const normalizedRef = requireText(ref, "Operator session ref");
38
+ const current = Object.values(sessions.sessions).find((candidate) => operatorSessionRef(candidate) === normalizedRef);
39
+ if (current !== undefined) {
40
+ return validateRoleSessionSet({
41
+ ...sessions,
42
+ activeAgentId: current.agentId,
43
+ updatedAt: requireDate(now)
44
+ });
45
+ }
46
+ const selected = sessions.history?.[normalizedRef];
47
+ if (selected === undefined) {
48
+ throw new Error(`Operator session not found: ${normalizedRef}.`);
49
+ }
50
+ const nextSessions = { ...sessions.sessions };
51
+ const history = { ...(sessions.history ?? {}) };
52
+ const replaced = nextSessions[selected.agentId];
53
+ if (replaced !== undefined) {
54
+ Object.assign(history, archiveCurrent(history, replaced));
55
+ }
56
+ delete history[normalizedRef];
57
+ nextSessions[selected.agentId] = {
58
+ ...selected,
59
+ status: "stopped",
60
+ updatedAt: requireDate(now)
61
+ };
62
+ return validateRoleSessionSet({
63
+ ...sessions,
64
+ activeAgentId: selected.agentId,
65
+ sessions: nextSessions,
66
+ history,
67
+ updatedAt: requireDate(now)
68
+ });
69
+ }
70
+ function archiveCurrent(history, session) {
71
+ if (session.status !== "stopped" && session.status !== "broken") {
72
+ throw new Error(`Cannot replace Operator session while its native process is ${session.status}.`);
73
+ }
74
+ const archived = {
75
+ ...session,
76
+ status: "stopped"
77
+ };
78
+ return {
79
+ ...history,
80
+ [operatorSessionRef(archived)]: archived
81
+ };
82
+ }
83
+ function listItem(session, state) {
84
+ const ref = operatorSessionRef(session);
85
+ const title = optionalDisplayText(session.title);
86
+ const preview = optionalDisplayText(session.preview);
87
+ const fallback = `${adapterLabel(session.adapterId)} session · ${ref.slice(-8)}`;
88
+ return {
89
+ ref,
90
+ agentId: session.agentId,
91
+ adapterId: session.adapterId,
92
+ displayTitle: title ?? preview ?? fallback,
93
+ ...(title === undefined ? {} : { title }),
94
+ ...(preview === undefined ? {} : { preview }),
95
+ state,
96
+ createdAt: session.createdAt,
97
+ updatedAt: session.updatedAt
98
+ };
99
+ }
100
+ function optionalDisplayText(value) {
101
+ if (value === undefined)
102
+ return undefined;
103
+ const normalized = normalizeRoleAgentSessionText(value);
104
+ return normalized.length === 0 ? undefined : normalized;
105
+ }
106
+ function adapterLabel(adapterId) {
107
+ return adapterId === "codex"
108
+ ? "Codex"
109
+ : adapterId === "claude"
110
+ ? "Claude"
111
+ : adapterId;
112
+ }
113
+ function requireText(value, label) {
114
+ if (typeof value !== "string" || value.includes("\0") || value.trim().length === 0) {
115
+ throw new Error(`${label} is required.`);
116
+ }
117
+ return value.trim();
118
+ }
119
+ function requireDate(value) {
120
+ if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {
121
+ throw new Error("Operator session timestamp is invalid.");
122
+ }
123
+ return value.toISOString();
124
+ }
@@ -0,0 +1,43 @@
1
+ import { renderAgentConfigurationResolutionNotice } from "../cli/agentConfigurationPicker.js";
2
+ import { defaultTableWidth, renderTable } from "./table.js";
3
+ export function renderAgentConfigurationCatalog(resolved) {
4
+ const { catalog } = resolved;
5
+ const version = catalog.cliVersion === undefined ? "" : ` ${catalog.cliVersion}`;
6
+ const modelRows = catalog.models.map((model) => [
7
+ model.label,
8
+ model.isDefault ? "default" : "",
9
+ model.efforts.map((effort) => effort.value).join(", ") || "not configurable",
10
+ model.defaultEffort ?? "",
11
+ model.serviceTiers?.map((tier) => tier.value).join(", ") || "none reported"
12
+ ]);
13
+ const fieldRows = catalog.fields
14
+ .filter((field) => field.key !== "model" && field.key !== "effort")
15
+ .map((field) => [
16
+ field.key,
17
+ field.choices.map((choice) => choice.value).join(", ") || "none reported",
18
+ field.available === false
19
+ ? `unavailable${field.reason === undefined ? "" : `: ${field.reason}`}`
20
+ : "available",
21
+ field.allowCustom ? "yes" : "no"
22
+ ]);
23
+ const sections = [
24
+ modelRows.length === 0
25
+ ? "No runtime models were reported."
26
+ : renderTable(`Agent capabilities: ${catalog.agentId} (${catalog.adapterId}${version})`, [
27
+ { header: "Model", minWidth: 12, maxWidth: 34 },
28
+ { header: "Default", minWidth: 7, maxWidth: 8 },
29
+ { header: "Efforts", minWidth: 12, maxWidth: 44 },
30
+ { header: "Default effort", minWidth: 14, maxWidth: 18 },
31
+ { header: "Service tiers", minWidth: 13, maxWidth: 24 }
32
+ ], modelRows, defaultTableWidth()),
33
+ ...(fieldRows.length === 0
34
+ ? []
35
+ : [renderTable("Other runtime configuration", [
36
+ { header: "Field", minWidth: 12, maxWidth: 30 },
37
+ { header: "Values", minWidth: 18, maxWidth: 58 },
38
+ { header: "Status", minWidth: 10, maxWidth: 38 },
39
+ { header: "Custom", minWidth: 6, maxWidth: 8 }
40
+ ], fieldRows, defaultTableWidth())])
41
+ ];
42
+ return `${sections.join("\n\n")}\n${renderAgentConfigurationResolutionNotice(resolved)}`;
43
+ }
@@ -1,3 +1,4 @@
1
+ import { activeRoleAgentSession } from "../executor/agentExecutor.js";
1
2
  import { defaultTableWidth, renderTable } from "./table.js";
2
3
  export function activeRoleSummary(role) {
3
4
  const binding = role.agentBindings[role.activeAgentId];
@@ -10,6 +11,7 @@ export function activeRoleSummary(role) {
10
11
  export function renderRoleDetails(title, role, input) {
11
12
  const bindings = Object.values(role.agentBindings)
12
13
  .sort((left, right) => left.agentId.localeCompare(right.agentId));
14
+ const effective = activeRoleAgentSession(input.sessions ?? null)?.effective;
13
15
  const profile = [
14
16
  ` Description ${present(role.description)}`,
15
17
  ` Responsibilities ${presentList(role.responsibilities)}`,
@@ -22,7 +24,16 @@ export function renderRoleDetails(title, role, input) {
22
24
  ` Kind ${input.kind}`,
23
25
  ` Active Agent ${role.activeAgentId}`,
24
26
  ...("status" in role ? [` Status ${role.status}`] : []),
25
- ` Workspace ${role.workspace}`
27
+ ` Workspace ${role.workspace}`,
28
+ ` Desired launch r${role.launchRevision}; Profile intent=${role.defaultAccess}`,
29
+ ` Effective launch ${effective === undefined
30
+ ? "not started"
31
+ : `${effective.agentId}/${effective.adapterId}; r${effective.sourceDesiredRevision}; Profile intent=${effective.profileAccess}; permission=${effective.permission.strategy}`}`,
32
+ ` Desired drift ${effective === undefined
33
+ ? "-"
34
+ : effective.sourceDesiredRevision === role.launchRevision
35
+ ? "none"
36
+ : "pending next launch"}`
26
37
  ];
27
38
  return [
28
39
  title,
@@ -61,18 +72,31 @@ function bindingRow(binding, role, sessions) {
61
72
  }
62
73
  function permission(binding) {
63
74
  if (binding.config.adapterId === "codex") {
64
- const sandbox = binding.config.permission?.sandbox;
65
- const approval = binding.config.permission?.approval;
66
- if (sandbox === undefined && approval === undefined)
75
+ const permission = binding.config.permission;
76
+ if (permission.strategy === "default")
67
77
  return "CLI default";
78
+ if (permission.strategy === "bypass")
79
+ return "bypass";
68
80
  return [
69
- sandbox === undefined ? undefined : `sandbox=${sandbox}`,
70
- approval === undefined ? undefined : `approval=${approval}`
71
- ].filter((value) => value !== undefined).join(", ");
81
+ permission.sandbox === undefined ? undefined : `sandbox=${permission.sandbox}`,
82
+ permission.approval === undefined ? undefined : `approval=${permission.approval}`
83
+ ].filter((value) => value !== undefined).join("; ");
72
84
  }
73
- return binding.config.permission?.mode === undefined
74
- ? "CLI default"
75
- : `mode=${binding.config.permission.mode}`;
85
+ const permission = binding.config.permission;
86
+ if (permission.strategy === "default")
87
+ return "CLI default";
88
+ if (permission.strategy === "bypass")
89
+ return "bypass";
90
+ const rules = [
91
+ permission.mode === undefined ? undefined : `mode=${permission.mode}`,
92
+ permission.allowedTools === undefined
93
+ ? undefined
94
+ : `allow=${permission.allowedTools.join(", ")}`,
95
+ permission.disallowedTools === undefined
96
+ ? undefined
97
+ : `deny=${permission.disallowedTools.join(", ")}`
98
+ ].filter((value) => value !== undefined);
99
+ return rules.join("; ");
76
100
  }
77
101
  function present(value) {
78
102
  return value === undefined || value.length === 0 ? "-" : value;
@@ -61,6 +61,14 @@ export function terminalSupportsColor(stream, env = process.env) {
61
61
  return true;
62
62
  return stream.isTTY === true && env.TERM !== "dumb";
63
63
  }
64
+ export function usableInteractiveTerminal(value) {
65
+ const normalized = value?.trim();
66
+ return normalized === undefined
67
+ || normalized.length === 0
68
+ || normalized.toLowerCase() === "dumb"
69
+ ? "xterm-256color"
70
+ : normalized;
71
+ }
64
72
  export function defaultTerminalWidth(stream = process.stdout) {
65
73
  const columns = stream.columns;
66
74
  return columns === undefined || !Number.isFinite(columns) || columns <= 0