@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,315 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { createRuntimeBinding } from "./runtimeBinding.js";
3
+ import { normalizeRuntimeOwner } from "./runtimeOwner.js";
4
+ import { requireSafeIdentity } from "./validation.js";
5
+ /**
6
+ * Runtime lifecycle adapter for the current tmux host. The returned hostRef is
7
+ * opaque to the domain and self-contained for a later inspect/stop operation.
8
+ */
9
+ export class TmuxSessionHost {
10
+ planner;
11
+ tmux;
12
+ #globalHostId;
13
+ #createBindingId;
14
+ #launchTails = new Map();
15
+ constructor(planner, tmux, options = {}) {
16
+ this.planner = planner;
17
+ this.tmux = tmux;
18
+ this.#globalHostId = requireSafeIdentity(options.globalHostId ?? "operator", "Global tmux host id");
19
+ this.#createBindingId = options.createBindingId ?? randomUUID;
20
+ }
21
+ async start(request, beforeHostStart) {
22
+ return this.#launch(request, beforeHostStart);
23
+ }
24
+ async resume(request, beforeHostStart) {
25
+ return this.#launch(request, beforeHostStart);
26
+ }
27
+ async stop(binding) {
28
+ const ref = requireMatchingHostRef(binding);
29
+ await stopExactRole(this.tmux, ref.hostId, ref.roleName);
30
+ }
31
+ async inspect(binding) {
32
+ const ref = requireMatchingHostRef(binding);
33
+ try {
34
+ const state = await probeRoleStatus(this.tmux, ref.hostId, ref.roleName) === "running"
35
+ ? "running"
36
+ : "stopped";
37
+ return {
38
+ state,
39
+ ...(binding.nativeSessionId === undefined
40
+ ? {}
41
+ : { nativeSessionId: binding.nativeSessionId })
42
+ };
43
+ }
44
+ catch {
45
+ return {
46
+ state: "unavailable",
47
+ ...(binding.nativeSessionId === undefined
48
+ ? {}
49
+ : { nativeSessionId: binding.nativeSessionId })
50
+ };
51
+ }
52
+ }
53
+ async inspectOwner(owner) {
54
+ const normalized = normalizeRuntimeOwner(owner);
55
+ const hostId = normalized.scope === "task"
56
+ ? normalized.taskId
57
+ : this.#globalHostId;
58
+ try {
59
+ return {
60
+ state: await probeRoleStatus(this.tmux, hostId, normalized.roleName) === "running"
61
+ ? "running"
62
+ : "stopped"
63
+ };
64
+ }
65
+ catch {
66
+ return { state: "unavailable" };
67
+ }
68
+ }
69
+ async inspectOwners(owners) {
70
+ const normalized = owners.map(normalizeRuntimeOwner);
71
+ if (this.tmux.inspectRolePaneInventory === undefined
72
+ && this.tmux.inspectRolePaneInventoryAsync === undefined) {
73
+ return Promise.all(normalized.map(async (owner) => ({
74
+ owner,
75
+ inspection: await this.inspectOwner(owner)
76
+ })));
77
+ }
78
+ try {
79
+ const inventory = this.tmux.inspectRolePaneInventoryAsync === undefined
80
+ ? this.tmux.inspectRolePaneInventory()
81
+ : await this.tmux.inspectRolePaneInventoryAsync();
82
+ const running = new Set(inventory
83
+ .filter((pane) => !pane.dead)
84
+ .map((pane) => `${pane.taskId}\0${pane.roleName}`));
85
+ return normalized.map((owner) => {
86
+ const hostId = owner.scope === "task"
87
+ ? owner.taskId
88
+ : this.#globalHostId;
89
+ return {
90
+ owner,
91
+ inspection: {
92
+ state: running.has(`${hostId}\0${owner.roleName}`)
93
+ ? "running"
94
+ : "stopped"
95
+ }
96
+ };
97
+ });
98
+ }
99
+ catch {
100
+ return normalized.map((owner) => ({
101
+ owner,
102
+ inspection: { state: "unavailable" }
103
+ }));
104
+ }
105
+ }
106
+ async stopOwner(owner) {
107
+ const normalized = normalizeRuntimeOwner(owner);
108
+ const hostId = normalized.scope === "task"
109
+ ? normalized.taskId
110
+ : this.#globalHostId;
111
+ try {
112
+ await stopExactRole(this.tmux, hostId, normalized.roleName);
113
+ return await probeRoleStatus(this.tmux, hostId, normalized.roleName)
114
+ === "exited";
115
+ }
116
+ catch {
117
+ return false;
118
+ }
119
+ }
120
+ async #launch(request, beforeHostStart) {
121
+ const hostId = request.owner.scope === "task"
122
+ ? request.owner.taskId
123
+ : this.#globalHostId;
124
+ // All Role windows for one Task share the same tmux session. Serialize the
125
+ // short create/ensure boundary per host so two first Roles cannot race
126
+ // `new-session`; different Tasks and the global Operator remain parallel.
127
+ const key = hostId;
128
+ const previous = this.#launchTails.get(key) ?? Promise.resolve();
129
+ let release;
130
+ const turn = new Promise((resolve) => { release = resolve; });
131
+ const tail = previous.then(() => turn);
132
+ this.#launchTails.set(key, tail);
133
+ await previous;
134
+ try {
135
+ return await this.#launchUnlocked(request, hostId, beforeHostStart);
136
+ }
137
+ finally {
138
+ release();
139
+ if (this.#launchTails.get(key) === tail)
140
+ this.#launchTails.delete(key);
141
+ }
142
+ }
143
+ async #launchUnlocked(request, hostId, beforeHostStart) {
144
+ // Validate generated identity before starting an external process.
145
+ const bindingId = requireSafeIdentity(this.#createBindingId(), "Runtime binding id");
146
+ const input = {
147
+ roleName: request.owner.roleName,
148
+ agentId: request.agentId,
149
+ adapterId: request.adapterId,
150
+ effective: request.effective,
151
+ launchId: request.launchId,
152
+ mode: request.mode,
153
+ ...(request.runId === undefined ? {} : { runId: request.runId }),
154
+ ...(request.runtimeIsolation === undefined
155
+ ? {}
156
+ : { runtimeIsolation: request.runtimeIsolation }),
157
+ ...(request.environment === undefined
158
+ ? {}
159
+ : { environment: request.environment }),
160
+ ...(request.mode === "resume" ? { nativeSessionId: request.nativeSessionId } : {})
161
+ };
162
+ const planned = request.owner.scope === "task"
163
+ ? this.planner.plan({ taskId: request.owner.taskId, ...input })
164
+ : this.planner.planGlobalRole(input);
165
+ if (planned.role.name !== request.owner.roleName) {
166
+ throw new Error("Planned Role does not match the runtime owner.");
167
+ }
168
+ if (planned.role.workspace !== request.workspace) {
169
+ throw new Error("Planned Role workspace does not match the runtime request.");
170
+ }
171
+ const plannedNativeSessionId = planned.session?.nativeSessionId;
172
+ if (request.mode === "resume"
173
+ && plannedNativeSessionId !== undefined
174
+ && plannedNativeSessionId !== request.nativeSessionId) {
175
+ throw new Error("Planned native session does not match the resume request.");
176
+ }
177
+ const nativeSessionId = plannedNativeSessionId
178
+ ?? (request.mode === "resume" ? request.nativeSessionId : undefined);
179
+ beforeHostStart?.({
180
+ owner: request.owner,
181
+ launchId: request.launchId,
182
+ ...(request.runId === undefined ? {} : { runId: request.runId }),
183
+ agentId: request.agentId,
184
+ adapterId: request.adapterId,
185
+ effective: request.effective,
186
+ ...(nativeSessionId === undefined ? {} : { nativeSessionId }),
187
+ ...(planned.initialPromptRunId === undefined
188
+ ? {}
189
+ : { initialPromptRunId: planned.initialPromptRunId })
190
+ });
191
+ // Process creation is last: every local invariant has already passed.
192
+ const hostCreated = await ensureRoleWindow(this.tmux, hostId, planned.role, planned.launch);
193
+ return createRuntimeBinding({
194
+ id: bindingId,
195
+ launchId: request.launchId,
196
+ owner: request.owner,
197
+ agentId: request.agentId,
198
+ adapterId: request.adapterId,
199
+ hostRef: encodeHostRef({
200
+ scope: request.owner.scope,
201
+ hostId,
202
+ roleName: request.owner.roleName
203
+ }),
204
+ hostCreated,
205
+ ...(hostCreated && planned.initialPromptRunId !== undefined
206
+ ? { initialPromptRunId: planned.initialPromptRunId }
207
+ : {}),
208
+ ...(nativeSessionId === undefined ? {} : { nativeSessionId })
209
+ });
210
+ }
211
+ }
212
+ /** Non-blocking receipt-backed tmux push; it never interprets provider output. */
213
+ export class TmuxPromptPushAdapter {
214
+ tmux;
215
+ readiness;
216
+ constructor(tmux, readiness) {
217
+ this.tmux = tmux;
218
+ this.readiness = readiness;
219
+ }
220
+ async tryPush(request) {
221
+ const ref = requireMatchingHostRef(request.binding);
222
+ const readinessProbe = this.readiness(request.binding.adapterId);
223
+ const outcome = this.tmux.sendRoleInputOnceIfReadyAsync === undefined
224
+ ? this.tmux.sendRoleInputOnceIfReady(ref.hostId, ref.roleName, request.envelope.id, request.envelope.text, readinessProbe)
225
+ : await this.tmux.sendRoleInputOnceIfReadyAsync(ref.hostId, ref.roleName, request.envelope.id, request.envelope.text, readinessProbe);
226
+ if (outcome === "unavailable")
227
+ return "unavailable";
228
+ return outcome === "not-ready" ? "busy" : "delivered";
229
+ }
230
+ }
231
+ async function ensureRoleWindow(tmux, hostId, role, launch) {
232
+ return tmux.ensureRoleWindowAsync === undefined
233
+ ? tmux.ensureRoleWindow(hostId, role, launch)
234
+ : tmux.ensureRoleWindowAsync(hostId, role, launch);
235
+ }
236
+ async function probeRoleStatus(tmux, hostId, roleName) {
237
+ return tmux.probeRoleStatusAsync === undefined
238
+ ? tmux.probeRoleStatus(hostId, roleName)
239
+ : tmux.probeRoleStatusAsync(hostId, roleName);
240
+ }
241
+ async function killRole(tmux, hostId, roleName) {
242
+ if (tmux.killRoleAsync === undefined) {
243
+ tmux.killRole(hostId, roleName);
244
+ return;
245
+ }
246
+ await tmux.killRoleAsync(hostId, roleName);
247
+ }
248
+ async function stopExactRole(tmux, hostId, roleName) {
249
+ if (await probeRoleStatus(tmux, hostId, roleName) !== "running")
250
+ return;
251
+ try {
252
+ await killRole(tmux, hostId, roleName);
253
+ }
254
+ catch (error) {
255
+ // Killing the final window may make the tmux server exit before the
256
+ // client observes a clean command status. The authoritative outcome is
257
+ // the exact Role's postcondition. Preserve the original error unless the
258
+ // Role is positively proven stopped.
259
+ try {
260
+ if (await probeRoleStatus(tmux, hostId, roleName) === "exited")
261
+ return;
262
+ }
263
+ catch {
264
+ // Fall through to the original kill error; an unavailable postcondition
265
+ // is not evidence that cleanup succeeded.
266
+ }
267
+ throw error;
268
+ }
269
+ }
270
+ const HOST_REF_PREFIX = "yui-tmux:v1:";
271
+ function encodeHostRef(ref) {
272
+ return `${HOST_REF_PREFIX}${Buffer.from(JSON.stringify(ref), "utf8").toString("base64url")}`;
273
+ }
274
+ function requireMatchingHostRef(binding) {
275
+ const ref = decodeHostRef(binding.hostRef);
276
+ const matches = binding.owner.scope === ref.scope
277
+ && binding.owner.roleName === ref.roleName
278
+ && (binding.owner.scope === "global" || binding.owner.taskId === ref.hostId);
279
+ if (!matches)
280
+ throw new Error("Tmux host reference does not match runtime owner.");
281
+ return ref;
282
+ }
283
+ function decodeHostRef(value) {
284
+ if (!value.startsWith(HOST_REF_PREFIX)) {
285
+ throw new Error("Tmux host reference is invalid.");
286
+ }
287
+ let input;
288
+ try {
289
+ const encoded = value.slice(HOST_REF_PREFIX.length);
290
+ input = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
291
+ }
292
+ catch {
293
+ throw new Error("Tmux host reference is invalid.");
294
+ }
295
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
296
+ throw new Error("Tmux host reference is invalid.");
297
+ }
298
+ const record = input;
299
+ const expectedKeys = ["hostId", "roleName", "scope"];
300
+ if (Object.keys(record).sort().join("\0") !== expectedKeys.sort().join("\0")) {
301
+ throw new Error("Tmux host reference is invalid.");
302
+ }
303
+ if (record.scope !== "global" && record.scope !== "task") {
304
+ throw new Error("Tmux host reference is invalid.");
305
+ }
306
+ const hostId = requireSafeIdentity(record.hostId, "Tmux host id");
307
+ const roleName = requireSafeIdentity(record.roleName, "Tmux Role name");
308
+ if (record.scope === "global")
309
+ return { scope: "global", hostId, roleName };
310
+ return {
311
+ scope: "task",
312
+ hostId,
313
+ roleName
314
+ };
315
+ }
@@ -0,0 +1,3 @@
1
+ // Compatibility facade. The authoritative Turn-completion model belongs to
2
+ // the Execution domain; runtime adapters only translate external Hook facts.
3
+ export * from "../executor/turnCompletion.js";
@@ -0,0 +1,23 @@
1
+ export function requireSafeIdentity(value, label) {
2
+ const normalized = requireText(value, label);
3
+ if (["__proto__", "prototype", "constructor", ".", ".."].includes(normalized)
4
+ || /[\/\\\0]/.test(normalized)) {
5
+ throw new Error(`${label} is invalid.`);
6
+ }
7
+ return normalized;
8
+ }
9
+ export function requireText(value, label) {
10
+ if (typeof value !== "string" || value.includes("\0")) {
11
+ throw new Error(`${label} is invalid.`);
12
+ }
13
+ const normalized = value.trim();
14
+ if (normalized.length === 0)
15
+ throw new Error(`${label} is required.`);
16
+ return normalized;
17
+ }
18
+ export function requireTimestamp(value, label) {
19
+ if (!(value instanceof Date) || !Number.isFinite(value.getTime())) {
20
+ throw new Error(`${label} must be a valid date.`);
21
+ }
22
+ return value.toISOString();
23
+ }