@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.
- package/ARCHITECTURE.md +603 -133
- package/README.md +806 -31
- package/dist/agent/agent.js +2 -1
- package/dist/agent/argumentPolicy.js +3 -1
- package/dist/agent/launchEnvironment.js +106 -0
- package/dist/agent/managedRuntimeEnvironment.js +34 -0
- package/dist/brief/taskBrief.js +11 -1
- package/dist/cli/agentConfigurationPicker.js +287 -0
- package/dist/cli/commandCatalog.js +488 -60
- package/dist/cli/completion.js +146 -22
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli/interactionCandidates.js +53 -15
- package/dist/cli/interactionPolicy.js +267 -30
- package/dist/cli/interactiveSelection.js +6 -2
- package/dist/cli/invocationRouter.js +5 -1
- package/dist/cli/operatorWizard.js +87 -0
- package/dist/cli/roleOptionCatalog.js +1 -0
- package/dist/cli/roleWizard.js +185 -21
- package/dist/cli/updateCommand.js +62 -19
- package/dist/cli/updateOrchestrator.js +539 -0
- package/dist/cli/updatePorts.js +1119 -0
- package/dist/cli/upgradeCommand.js +112 -0
- package/dist/cli.js +1420 -86
- package/dist/commands/agentCommands.js +146 -3
- package/dist/commands/configCommands.js +126 -0
- package/dist/commands/controllerCommands.js +365 -0
- package/dist/commands/globalRoleCommands.js +168 -126
- package/dist/commands/jobCommands.js +18 -8
- package/dist/commands/operatorCommands.js +159 -9
- package/dist/commands/profileCommands.js +203 -0
- package/dist/commands/projectCommands.js +650 -0
- package/dist/commands/roleConfiguration.js +85 -24
- package/dist/commands/roleRuntimeGuard.js +12 -0
- package/dist/commands/roleSkillValidation.js +47 -0
- package/dist/commands/taskActor.js +127 -0
- package/dist/commands/taskCommands.js +4201 -313
- package/dist/commands/taskCompletionGate.js +131 -0
- package/dist/commands/taskContextCommand.js +244 -30
- package/dist/commands/taskInputCommands.js +177 -59
- package/dist/commands/taskIntegrationCommands.js +303 -0
- package/dist/commands/taskOverviewCommand.js +363 -0
- package/dist/commands/taskRoleRuntimeStatus.js +125 -19
- package/dist/commands/textInput.js +15 -0
- package/dist/completion/completionInstaller.js +26 -22
- package/dist/config/yuiConfig.js +4 -3
- package/dist/context/dispatchContext.js +90 -38
- package/dist/context/roleSessionContext.js +119 -0
- package/dist/controller/claudeLifecycleHook.js +203 -0
- package/dist/controller/clientRuntime.js +408 -56
- package/dist/controller/codexLifecycleHook.js +108 -0
- package/dist/controller/controller.js +1089 -32
- package/dist/controller/domainIdentity.js +505 -0
- package/dist/controller/ephemeralResourceReaper.js +131 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +2153 -103
- package/dist/controller/providerHookRunFence.js +127 -0
- package/dist/controller/resourceCleanupLinux.js +286 -0
- package/dist/controller/resourceInventory.js +531 -0
- package/dist/controller/resourceInventoryLinux.js +610 -0
- package/dist/controller/runtime.js +629 -10
- package/dist/controller/runtimeEventInbox.js +564 -0
- package/dist/controller/runtimeEventProcessor.js +248 -0
- package/dist/controller/runtimeLaunchCoordinator.js +477 -0
- package/dist/controller/sessionNotify.js +121 -78
- package/dist/coordination/deadlineScheduler.js +15 -0
- package/dist/coordination/mailboxScheduler.js +108 -0
- package/dist/coordination/workMailbox.js +329 -0
- package/dist/coordination/workMailboxQueue.js +86 -0
- package/dist/core/controllerClient.js +19 -5
- package/dist/core/controllerEndpoint.js +37 -0
- package/dist/core/controllerServer.js +218 -10
- package/dist/core/protocol.js +6 -2
- package/dist/decision/decision.js +2 -1
- package/dist/doctor/doctor.js +681 -32
- package/dist/domain/validation.js +53 -0
- package/dist/errors/cliError.js +5 -3
- package/dist/event/taskEvent.js +7 -3
- package/dist/execution/codexThreadNaming.js +160 -0
- package/dist/execution/executionGroup.js +579 -0
- package/dist/executor/agentAdapter.js +255 -40
- package/dist/executor/agentConfigurationCatalog.js +326 -0
- package/dist/executor/agentConfigurationProbe.js +506 -0
- package/dist/executor/agentExecutor.js +625 -10
- package/dist/executor/codexConfigConflict.js +290 -0
- package/dist/executor/effectiveLaunch.js +340 -0
- package/dist/executor/executorRegistry.js +238 -36
- package/dist/executor/fileRoleLaunchPlanner.js +550 -40
- package/dist/executor/turnCompletion.js +126 -0
- package/dist/input/inputRequest.js +30 -9
- package/dist/integration/changeSet.js +36 -0
- package/dist/integration/checkResult.js +24 -0
- package/dist/integration/gitIntegrationService.js +695 -0
- package/dist/integration/integrationAttempt.js +142 -0
- package/dist/interaction/operatorPresentation.js +96 -0
- package/dist/lifecycle/canonicalLifecycleEvent.js +342 -0
- package/dist/lifecycle/exactRunTerminalization.js +572 -0
- package/dist/lifecycle/providerLifecycleMapping.js +190 -0
- package/dist/lifecycle/taskRoleSessionReset.js +124 -0
- package/dist/message/message.js +23 -7
- package/dist/milestone/milestone.js +2 -1
- package/dist/operator/operatorSessionHistory.js +124 -0
- package/dist/output/agentConfigurationPresentation.js +43 -0
- package/dist/output/rolePresentation.js +34 -10
- package/dist/output/terminal.js +8 -0
- package/dist/output/timePresentation.js +55 -0
- package/dist/profile/agentProfile.js +128 -0
- package/dist/repository/gitWorkspace.js +578 -24
- package/dist/repository/project.js +213 -0
- package/dist/repository/taskWorkspaceCoordinator.js +392 -0
- package/dist/repository/taskWorkspacePreparer.js +1688 -191
- package/dist/review/reviewConfig.js +11 -0
- package/dist/review/reviewRound.js +399 -0
- package/dist/review/taskFinalReviewContract.js +90 -0
- package/dist/role/role.js +124 -23
- package/dist/run/agentRun.js +155 -12
- package/dist/run/runIdentity.js +82 -0
- package/dist/runtime/exactControlPlane.js +472 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/lifecycleReservation.js +38 -0
- package/dist/runtime/ports.js +11 -0
- package/dist/runtime/preallocatedNativeSession.js +13 -0
- package/dist/runtime/promptEnvelope.js +30 -0
- package/dist/runtime/runtimeBinding.js +31 -0
- package/dist/runtime/runtimeOwner.js +14 -0
- package/dist/runtime/sessionLaunchRequest.js +62 -0
- package/dist/runtime/sessionTitle.js +54 -0
- package/dist/runtime/taskRuntimeIsolation.js +643 -0
- package/dist/runtime/tmuxAdapters.js +315 -0
- package/dist/runtime/turnCompletion.js +3 -0
- package/dist/runtime/validation.js +23 -0
- package/dist/scheduler/activeRoleRunDelivery.js +342 -32
- package/dist/scheduler/activeTaskProgress.js +63 -0
- package/dist/scheduler/leaderFailure.js +2 -1
- package/dist/scheduler/leaderWakeupProcessor.js +307 -66
- package/dist/scheduler/operatorInputNotificationProcessor.js +109 -46
- package/dist/scheduler/operatorNotification.js +44 -2
- package/dist/scheduler/ports.js +28 -1
- package/dist/scheduler/roleRunLiveness.js +131 -25
- package/dist/scheduler/roleRunStall.js +951 -0
- package/dist/scheduler/taskExecutionProjection.js +544 -0
- package/dist/scheduler/wakeupQueue.js +3 -0
- package/dist/setup/setupCommand.js +302 -52
- package/dist/storage/compatibleTaskStore.js +102 -0
- package/dist/storage/migration/baseline.js +78 -0
- package/dist/storage/migration/classifier.js +51 -0
- package/dist/storage/migration/compatibleCodec.js +53 -0
- package/dist/storage/migration/engine.js +147 -0
- package/dist/storage/migration/index.js +33 -0
- package/dist/storage/migration/planner.js +154 -0
- package/dist/storage/migration/productionRegistry.js +486 -0
- package/dist/storage/migration/registry.js +169 -0
- package/dist/storage/migration/report.js +54 -0
- package/dist/storage/migration/types.js +31 -0
- package/dist/storage/storageSchema.js +147 -123
- package/dist/storage/storageVersions.js +11 -0
- package/dist/storage/taskStore.js +1793 -197
- package/dist/storage/upgrade/homeClassification.js +156 -0
- package/dist/storage/upgrade/homeMigrationTarget.js +595 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +315 -0
- package/dist/storage/upgrade/productionMigrationRegistry.js +6 -0
- package/dist/storage/upgrade/recordVersionScan.js +176 -0
- package/dist/storage/upgrade/recordVersions.js +159 -0
- package/dist/storage/upgrade/switchProgress.js +80 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +948 -0
- package/dist/storage/upgrade/upgradeReceipt.js +161 -0
- package/dist/storage/upgradeCoordination.js +186 -0
- package/dist/storage/upgradeFence.js +366 -0
- package/dist/task/task.js +132 -26
- package/dist/task/taskRecordReference.js +66 -0
- package/dist/tmux/commandExecutor.js +75 -2
- package/dist/tmux/tmuxManager.js +747 -49
- package/dist/version.js +23 -0
- package/dist/web/assets/assetManifest.js +62 -0
- package/dist/web/assets/client/app.js +631 -0
- package/dist/web/assets/client/components.js +605 -0
- package/dist/web/assets/client/dom.js +14 -0
- package/dist/web/assets/client/format.js +28 -0
- package/dist/web/assets/client/i18n.js +494 -0
- package/dist/web/assets/client/markdown.js +114 -0
- package/dist/web/assets/client/theme.js +32 -0
- package/dist/web/assets/client/view.js +458 -0
- package/dist/web/assets/fontData.js +12 -0
- package/dist/web/assets/fonts.js +12 -0
- package/dist/web/assets/shell.js +114 -0
- package/dist/web/assets/styles/cards.js +135 -0
- package/dist/web/assets/styles/layout.js +47 -0
- package/dist/web/assets/styles/markdown.js +29 -0
- package/dist/web/assets/styles/responsive.js +39 -0
- package/dist/web/assets/styles/tokens.js +101 -0
- package/dist/web/assets/styles/widgets.js +147 -0
- package/dist/web/tmuxWebTerminal.js +158 -0
- package/dist/web/webServer.js +463 -0
- package/dist/web/webSnapshot.js +148 -0
- package/dist/workItem/workItem.js +642 -23
- package/dist/workspace/gitChangeSetCapture.js +86 -0
- package/dist/workspace/workItemChangeSetManager.js +445 -0
- package/dist/worktree/managedWorkspace.js +202 -0
- package/docs/task-local-identity.md +62 -0
- package/i18n/README.zh-CN.md +406 -31
- package/package.json +10 -2
- package/skills/yui-leader/SKILL.md +601 -39
- package/skills/yui-operator/SKILL.md +255 -34
- package/skills/yui-reviewer/SKILL.md +57 -0
- package/skills/yui-worker/SKILL.md +214 -17
- package/dist/commands/repositoryCommands.js +0 -86
- package/dist/operator/operatorContext.js +0 -66
- package/dist/repository/repository.js +0 -55
- package/dist/scheduler/archivedTaskRuntime.js +0 -12
- 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,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
|
+
}
|