@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,531 @@
1
+ import { resolve } from "node:path";
2
+ export function createRuntimeResourceActivityTracker() {
3
+ const previous = new Map();
4
+ return (identity, resource) => {
5
+ const roleKey = resourceSampleRoleKey(identity, resource);
6
+ const ownerRoleKey = resource.owner.kind === "task-role"
7
+ ? `${resource.owner.taskId}\0${resource.owner.roleName}`
8
+ : undefined;
9
+ const reject = () => {
10
+ // A rejected observation is a generation/process gap, not an adjacent
11
+ // sample. Never let a later valid observation bridge across it.
12
+ const keys = new Set([roleKey, ownerRoleKey].filter((key) => key !== undefined));
13
+ if (keys.size === 0)
14
+ previous.clear();
15
+ else
16
+ for (const key of keys)
17
+ previous.delete(key);
18
+ return false;
19
+ };
20
+ if (!matchesResourceIdentity(identity, resource.owner))
21
+ return reject();
22
+ if (!Array.isArray(resource.processes) || resource.processes.length === 0) {
23
+ return reject();
24
+ }
25
+ const cpuTimeMs = nonNegativeCounter(resource.cpuTimeMs);
26
+ if (cpuTimeMs === undefined)
27
+ return reject();
28
+ const ioReadBytes = optionalCounter(resource.ioReadBytes);
29
+ const ioWriteBytes = optionalCounter(resource.ioWriteBytes);
30
+ if ((resource.ioReadBytes !== undefined && ioReadBytes === undefined)
31
+ || (resource.ioWriteBytes !== undefined && ioWriteBytes === undefined)
32
+ || roleKey === undefined)
33
+ return reject();
34
+ const identityKey = JSON.stringify([
35
+ identity.runId,
36
+ identity.agentId,
37
+ identity.adapterId,
38
+ identity.nativeSessionId ?? null,
39
+ identity.launchId ?? null
40
+ ]);
41
+ const current = {
42
+ identity: identityKey,
43
+ fingerprint: resource.fingerprint,
44
+ cpuTimeMs,
45
+ ...(ioReadBytes === undefined ? {} : { ioReadBytes }),
46
+ ...(ioWriteBytes === undefined ? {} : { ioWriteBytes })
47
+ };
48
+ const prior = previous.get(roleKey);
49
+ if (prior === undefined) {
50
+ previous.set(roleKey, current);
51
+ return false;
52
+ }
53
+ if ((prior.ioReadBytes !== undefined && ioReadBytes === undefined)
54
+ || (prior.ioWriteBytes !== undefined && ioWriteBytes === undefined))
55
+ return reject();
56
+ if (prior.identity !== current.identity
57
+ || prior.fingerprint !== current.fingerprint) {
58
+ // An identity/process generation change is not adjacent to the prior
59
+ // sample. Require a fresh baseline on a subsequent valid observation.
60
+ previous.delete(roleKey);
61
+ return false;
62
+ }
63
+ if (cpuTimeMs < prior.cpuTimeMs
64
+ || counterReset(ioReadBytes, prior.ioReadBytes)
65
+ || counterReset(ioWriteBytes, prior.ioWriteBytes))
66
+ return reject();
67
+ previous.set(roleKey, current);
68
+ return cpuTimeMs > prior.cpuTimeMs
69
+ || counterIncrease(ioReadBytes, prior.ioReadBytes)
70
+ || counterIncrease(ioWriteBytes, prior.ioWriteBytes);
71
+ };
72
+ }
73
+ export function buildControllerResourceInventory(facts) {
74
+ const currentHome = resolve(facts.currentHome);
75
+ const processes = [...facts.processes];
76
+ const resources = [];
77
+ const claimed = new Set();
78
+ for (const homeFact of facts.homes) {
79
+ const yuiHome = resolve(homeFact.yuiHome);
80
+ const homeProcesses = processes.filter((process) => process.yuiHome === yuiHome);
81
+ const currentController = currentControllerProcess(homeFact.discovery, homeProcesses);
82
+ for (const process of homeProcesses.filter(({ kind }) => kind === "controller")) {
83
+ claimed.add(process.pid);
84
+ const exactCurrent = currentController?.pid === process.pid
85
+ && currentController.startIdentity === process.startIdentity;
86
+ const superseded = !exactCurrent && currentController !== undefined;
87
+ const safeEmptyOrphan = !homeFact.exists
88
+ && processes.every((candidate) => (candidate.pid === process.pid || candidate.ppid !== process.pid))
89
+ && homeFact.panes.length === 0;
90
+ resources.push(processResource({
91
+ kind: "controller",
92
+ state: exactCurrent ? "current" : superseded ? "superseded" : "orphaned",
93
+ disposition: exactCurrent
94
+ ? "protected"
95
+ : superseded || safeEmptyOrphan ? "safe" : "review",
96
+ reasonCode: exactCurrent
97
+ ? "current-controller"
98
+ : superseded
99
+ ? "superseded-controller"
100
+ : safeEmptyOrphan
101
+ ? "orphan-controller-empty-home"
102
+ : "orphan-controller",
103
+ yuiHome,
104
+ owner: { kind: "controller-domain", yuiHome },
105
+ processes: [process],
106
+ domain: homeFact.domain
107
+ }));
108
+ }
109
+ for (const pane of homeFact.panes) {
110
+ const paneProcesses = pane.pid === undefined
111
+ ? []
112
+ : processTree(processes, pane.pid);
113
+ for (const process of paneProcesses)
114
+ claimed.add(process.pid);
115
+ const role = findRole(homeFact.roles, pane);
116
+ const archived = role?.taskStatus === "archived";
117
+ resources.push(processResource({
118
+ kind: "agent-session",
119
+ state: pane.dead ? "dead" : role === undefined ? "orphaned" : "running",
120
+ disposition: role === undefined
121
+ ? pane.dead ? "safe" : "review"
122
+ : archived ? "safe" : "protected",
123
+ reasonCode: role === undefined
124
+ ? pane.dead ? "dead-orphan-pane" : "orphan-pane"
125
+ : archived ? "archived-task-pane" : "owned-role-pane",
126
+ yuiHome,
127
+ owner: role === undefined ? { kind: "none" } : roleOwner(role),
128
+ processes: paneProcesses,
129
+ target: pane.target,
130
+ paneDead: pane.dead,
131
+ paneCommand: pane.currentCommand,
132
+ domain: homeFact.domain
133
+ }));
134
+ }
135
+ for (const process of homeProcesses) {
136
+ if (claimed.has(process.pid))
137
+ continue;
138
+ claimed.add(process.pid);
139
+ const kind = publicProcessKind(process.kind);
140
+ const ownedTmuxServer = process.kind === "tmux-server"
141
+ && homeFact.panes.length > 0;
142
+ const disposition = ownedTmuxServer
143
+ ? "protected"
144
+ : process.kind === "app-server"
145
+ || process.kind === "agent"
146
+ || process.kind === "tmux-server"
147
+ ? "review"
148
+ : "report-only";
149
+ resources.push(processResource({
150
+ kind,
151
+ state: ownedTmuxServer
152
+ ? "running"
153
+ : disposition === "review" ? "orphaned" : "unattributed",
154
+ disposition,
155
+ reasonCode: ownedTmuxServer
156
+ ? "owned-tmux-server"
157
+ : disposition === "review"
158
+ ? `orphan-${process.kind}`
159
+ : `unattributed-${process.kind}`,
160
+ yuiHome,
161
+ owner: ownedTmuxServer
162
+ ? { kind: "controller-domain", yuiHome }
163
+ : { kind: "none" },
164
+ processes: [process],
165
+ domain: homeFact.domain
166
+ }));
167
+ }
168
+ const discoveryArtifact = homeFact.discovery.status === "invalid"
169
+ ? homeFact.discovery.artifact
170
+ : homeFact.discovery.status === "valid" && currentController === undefined
171
+ ? homeFact.artifacts.find(({ artifactKind }) => artifactKind === "controller-discovery")
172
+ : undefined;
173
+ const artifacts = discoveryArtifact === undefined
174
+ ? homeFact.artifacts
175
+ : [discoveryArtifact, ...homeFact.artifacts.filter((artifact) => (artifact.path !== discoveryArtifact.path))];
176
+ for (const artifact of artifacts) {
177
+ resources.push(artifactResource(artifact, yuiHome, homeFact.domain));
178
+ }
179
+ }
180
+ for (const artifact of facts.globalArtifacts) {
181
+ resources.push(artifactResource(artifact));
182
+ }
183
+ const ordered = resources.sort(compareResources);
184
+ const domains = facts.homes.map((home) => {
185
+ const yuiHome = resolve(home.yuiHome);
186
+ const owned = ordered.filter((resource) => resource.yuiHome === yuiHome);
187
+ return {
188
+ yuiHome,
189
+ storageStatus: home.storageStatus,
190
+ resourceCount: owned.length,
191
+ liveProcessCount: uniqueProcesses(owned).length,
192
+ rssBytes: sum(uniqueProcesses(owned).map(({ rssBytes }) => rssBytes)),
193
+ issueCount: owned.filter(({ disposition }) => disposition !== "protected").length
194
+ + (home.domain !== undefined && home.domain.disposition !== "protected" ? 1 : 0),
195
+ ...(home.domain === undefined ? {} : {
196
+ domainKind: home.domain.kind,
197
+ liveness: home.domain.liveness,
198
+ disposition: home.domain.disposition,
199
+ reasonCode: home.domain.reasonCode,
200
+ fingerprint: home.domain.fingerprint,
201
+ ...(home.domain.hostPid === undefined ? {} : { hostPid: home.domain.hostPid }),
202
+ ...(home.domain.hostProcessStartIdentity === undefined
203
+ ? {}
204
+ : { hostProcessStartIdentity: home.domain.hostProcessStartIdentity }),
205
+ ...(home.domain.token === undefined ? {} : { token: home.domain.token }),
206
+ ...(home.domain.tmuxServer === undefined ? {} : { tmuxServer: home.domain.tmuxServer }),
207
+ tmuxTargets: home.domain.tmuxTargets,
208
+ ...(home.domain.graceMs === 0 ? {} : { graceMs: home.domain.graceMs }),
209
+ ageMs: home.domain.ageMs
210
+ })
211
+ };
212
+ }).sort((left, right) => left.yuiHome.localeCompare(right.yuiHome));
213
+ const allProcesses = uniqueProcesses(ordered);
214
+ const byDisposition = {
215
+ safe: 0,
216
+ review: 0,
217
+ protected: 0,
218
+ "report-only": 0
219
+ };
220
+ for (const resource of ordered)
221
+ byDisposition[resource.disposition] += 1;
222
+ return {
223
+ schemaVersion: 1,
224
+ observedAt: facts.observedAt,
225
+ currentHome,
226
+ scope: facts.scope,
227
+ summary: {
228
+ domainCount: domains.length,
229
+ resourceCount: ordered.length,
230
+ liveProcessCount: allProcesses.length,
231
+ rssBytes: sum(allProcesses.map(({ rssBytes }) => rssBytes)),
232
+ byDisposition
233
+ },
234
+ domains,
235
+ resources: ordered,
236
+ warnings: [...(facts.warnings ?? [])]
237
+ };
238
+ }
239
+ function currentControllerProcess(discovery, processes) {
240
+ if (discovery.status !== "valid")
241
+ return undefined;
242
+ return processes.find((process) => (process.kind === "controller"
243
+ && process.pid === discovery.pid
244
+ && process.startIdentity === discovery.processStartIdentity));
245
+ }
246
+ function findRole(roles, pane) {
247
+ if (pane.taskId === "operator") {
248
+ return roles.find((role) => (role.ownerKind === "global-role" && role.roleName === pane.roleName));
249
+ }
250
+ return roles.find((role) => (role.ownerKind === "task-role"
251
+ && role.taskId === pane.taskId
252
+ && role.roleName === pane.roleName));
253
+ }
254
+ function roleOwner(role) {
255
+ if (role.ownerKind === "global-role") {
256
+ return {
257
+ kind: "global-role",
258
+ roleName: role.roleName,
259
+ agentId: role.agentId,
260
+ ...(role.adapterId === undefined ? {} : { adapterId: role.adapterId }),
261
+ ...(role.nativeSessionId === undefined ? {} : { nativeSessionId: role.nativeSessionId }),
262
+ ...(role.launchId === undefined ? {} : { launchId: role.launchId }),
263
+ ...(role.runId === undefined ? {} : { runId: role.runId })
264
+ };
265
+ }
266
+ return {
267
+ kind: "task-role",
268
+ taskId: role.taskId ?? "",
269
+ ...(role.taskTitle === undefined ? {} : { taskTitle: role.taskTitle }),
270
+ ...(role.taskStatus === undefined ? {} : { taskStatus: role.taskStatus }),
271
+ roleName: role.roleName,
272
+ agentId: role.agentId,
273
+ ...(role.adapterId === undefined ? {} : { adapterId: role.adapterId }),
274
+ ...(role.nativeSessionId === undefined ? {} : { nativeSessionId: role.nativeSessionId }),
275
+ ...(role.launchId === undefined ? {} : { launchId: role.launchId }),
276
+ ...(role.runId === undefined ? {} : { runId: role.runId })
277
+ };
278
+ }
279
+ function processTree(processes, rootPid) {
280
+ const byParent = new Map();
281
+ for (const process of processes) {
282
+ const children = byParent.get(process.ppid) ?? [];
283
+ children.push(process);
284
+ byParent.set(process.ppid, children);
285
+ }
286
+ const root = processes.find(({ pid }) => pid === rootPid);
287
+ if (root === undefined)
288
+ return [];
289
+ const result = [];
290
+ const queue = [root];
291
+ const seen = new Set();
292
+ while (queue.length > 0) {
293
+ const current = queue.shift();
294
+ if (current === undefined || seen.has(current.pid))
295
+ continue;
296
+ seen.add(current.pid);
297
+ result.push(current);
298
+ queue.push(...(byParent.get(current.pid) ?? []));
299
+ }
300
+ return result;
301
+ }
302
+ function processResource(input) {
303
+ const processes = input.processes.map(publicProcess);
304
+ const identity = processes.map(({ pid, startIdentity }) => `${pid}:${startIdentity}`).sort().join(",");
305
+ const disposition = domainDisposition(input.disposition, input.reasonCode, input.domain, input.target);
306
+ const fingerprint = [
307
+ input.kind,
308
+ identity,
309
+ input.target ?? "",
310
+ input.paneDead === undefined ? "" : String(input.paneDead),
311
+ input.paneCommand ?? "",
312
+ input.domain?.fingerprint ?? ""
313
+ ].join(":");
314
+ return {
315
+ id: `${input.kind}:${input.yuiHome}:${input.target ?? (identity || input.reasonCode)}`,
316
+ fingerprint,
317
+ kind: input.kind,
318
+ state: input.state,
319
+ disposition,
320
+ reasonCode: domainReason(input.reasonCode, input.domain, disposition, input.target),
321
+ yuiHome: input.yuiHome,
322
+ owner: input.owner,
323
+ processes,
324
+ rssBytes: sum(processes.map(({ rssBytes }) => rssBytes)),
325
+ cpuTimeMs: sum(processes.map(({ cpuTimeMs }) => cpuTimeMs)),
326
+ ...optionalCounterFields(processes, "ioReadBytes"),
327
+ ...optionalCounterFields(processes, "ioWriteBytes"),
328
+ ageMs: max(processes.map(({ ageMs }) => ageMs)),
329
+ ...(input.target === undefined ? {} : { target: input.target }),
330
+ ...(input.paneDead === undefined ? {} : { paneDead: input.paneDead }),
331
+ ...(input.paneCommand === undefined ? {} : { paneCommand: input.paneCommand }),
332
+ ...(input.domain === undefined ? {} : { domain: input.domain })
333
+ };
334
+ }
335
+ function artifactResource(artifact, yuiHome, domain) {
336
+ const stale = !artifact.active;
337
+ const baseDisposition = stale ? "safe" : "report-only";
338
+ const disposition = domainDisposition(baseDisposition, undefined, domain);
339
+ return {
340
+ id: `artifact:${artifact.path}`,
341
+ fingerprint: domain === undefined
342
+ ? artifact.fingerprint
343
+ : `${artifact.fingerprint}:${domain.fingerprint}`,
344
+ kind: "artifact",
345
+ state: stale ? "stale" : "unattributed",
346
+ disposition,
347
+ reasonCode: domainReason(stale ? `stale-${artifact.artifactKind}` : `active-unmapped-${artifact.artifactKind}`, domain, disposition),
348
+ ...(yuiHome === undefined ? {} : { yuiHome }),
349
+ owner: yuiHome === undefined
350
+ ? { kind: "none" }
351
+ : { kind: "controller-domain", yuiHome },
352
+ processes: [],
353
+ rssBytes: 0,
354
+ cpuTimeMs: 0,
355
+ ageMs: 0,
356
+ artifact,
357
+ ...(domain === undefined ? {} : { domain })
358
+ };
359
+ }
360
+ function domainDisposition(base, _reason, domain, target) {
361
+ if (domain === undefined)
362
+ return base;
363
+ if (domain.disposition === "safe") {
364
+ // Once the exact host identity has expired past grace, every resource
365
+ // attributed to that marked domain inherits the same ownership fence. A
366
+ // live orphan pane/server is therefore safe too; active Task/Role work is
367
+ // rejected earlier by inspectRuntimeDomain and never receives a safe
368
+ // domain fact. The scanner's own current process is excluded from the
369
+ // process inventory, while a manually supplied current-controller fact is
370
+ // safe to reap by the same exact identity fence.
371
+ if (domain.liveness === "expired") {
372
+ if (target !== undefined
373
+ && !domain.tmuxTargets.includes(target))
374
+ return "review";
375
+ // A tmux server with panes is protected until the pane resources have
376
+ // been removed and a later bounded pass can revalidate the empty
377
+ // server. This avoids killing a server while a target race is in flight.
378
+ if (base === "protected" && _reason === "owned-tmux-server")
379
+ return "protected";
380
+ // `report-only` is deliberately reserved for an unrecognized process;
381
+ // the YUI_HOME environment alone is not a cleanup ownership proof.
382
+ return base === "report-only" ? "report-only" : "safe";
383
+ }
384
+ return base;
385
+ }
386
+ if (domain.liveness === "active")
387
+ return "protected";
388
+ if (base === "protected")
389
+ return "protected";
390
+ return "review";
391
+ }
392
+ function domainReason(base, domain, disposition, target) {
393
+ if (domain?.disposition === "safe"
394
+ && domain.liveness === "expired"
395
+ && target !== undefined
396
+ && !domain.tmuxTargets.includes(target))
397
+ return `ephemeral-tmux-target-unmatched:${base}`;
398
+ if (domain === undefined || domain.disposition === "safe")
399
+ return base;
400
+ if (disposition === "protected")
401
+ return `${domain.reasonCode}:${base}`;
402
+ return `${domain.reasonCode}:${base}`;
403
+ }
404
+ function publicProcess(process) {
405
+ return {
406
+ pid: process.pid,
407
+ ppid: process.ppid,
408
+ uid: process.uid,
409
+ startIdentity: process.startIdentity,
410
+ command: process.command,
411
+ rssBytes: process.rssBytes,
412
+ cpuTimeMs: process.cpuTimeMs,
413
+ ...(process.ioReadBytes === undefined ? {} : { ioReadBytes: process.ioReadBytes }),
414
+ ...(process.ioWriteBytes === undefined ? {} : { ioWriteBytes: process.ioWriteBytes }),
415
+ ageMs: process.ageMs
416
+ };
417
+ }
418
+ function optionalCounterFields(processes, field) {
419
+ const values = processes
420
+ .map((process) => process[field])
421
+ .filter((value) => value !== undefined);
422
+ return values.length === 0
423
+ ? {}
424
+ : field === "ioReadBytes"
425
+ ? { ioReadBytes: sum(values) }
426
+ : { ioWriteBytes: sum(values) };
427
+ }
428
+ function matchesResourceIdentity(identity, owner) {
429
+ if (identity === undefined
430
+ || typeof identity.taskId !== "string"
431
+ || identity.taskId.length === 0
432
+ || typeof identity.roleName !== "string"
433
+ || identity.roleName.length === 0
434
+ || typeof identity.runId !== "string"
435
+ || identity.runId.length === 0
436
+ || typeof identity.agentId !== "string"
437
+ || identity.agentId.length === 0
438
+ || typeof identity.adapterId !== "string"
439
+ || identity.adapterId.length === 0
440
+ || (identity.nativeSessionId !== undefined && !hasIdentityText(identity.nativeSessionId))
441
+ || (identity.launchId !== undefined && !hasIdentityText(identity.launchId)))
442
+ return false;
443
+ if (owner.kind !== "task-role"
444
+ || owner.taskId !== identity.taskId
445
+ || owner.roleName !== identity.roleName
446
+ || owner.runId !== identity.runId
447
+ || owner.agentId !== identity.agentId
448
+ || owner.adapterId !== identity.adapterId)
449
+ return false;
450
+ if (!hasIdentityText(identity.nativeSessionId) && !hasIdentityText(identity.launchId))
451
+ return false;
452
+ if (identity.nativeSessionId !== undefined
453
+ && owner.nativeSessionId !== identity.nativeSessionId)
454
+ return false;
455
+ if (identity.launchId !== undefined && owner.launchId !== identity.launchId)
456
+ return false;
457
+ return true;
458
+ }
459
+ function hasIdentityText(value) {
460
+ return typeof value === "string" && value.trim().length > 0;
461
+ }
462
+ function resourceSampleRoleKey(identity, resource) {
463
+ const taskId = typeof identity?.taskId === "string" && identity.taskId.length > 0
464
+ ? identity.taskId
465
+ : resource?.owner.kind === "task-role"
466
+ ? resource.owner.taskId
467
+ : undefined;
468
+ const roleName = typeof identity?.roleName === "string" && identity.roleName.length > 0
469
+ ? identity.roleName
470
+ : resource?.owner.kind === "task-role"
471
+ ? resource.owner.roleName
472
+ : undefined;
473
+ return taskId === undefined || roleName === undefined
474
+ ? undefined
475
+ : `${taskId}\0${roleName}`;
476
+ }
477
+ function nonNegativeCounter(value) {
478
+ return value !== undefined && Number.isFinite(value) && value >= 0 ? value : undefined;
479
+ }
480
+ function optionalCounter(value) {
481
+ return nonNegativeCounter(value);
482
+ }
483
+ function counterReset(current, prior) {
484
+ return current !== undefined && prior !== undefined && current < prior;
485
+ }
486
+ function counterIncrease(current, prior) {
487
+ return current !== undefined && prior !== undefined && current > prior;
488
+ }
489
+ function publicProcessKind(kind) {
490
+ switch (kind) {
491
+ case "tmux-server": return "tmux-server";
492
+ case "app-server": return "app-server";
493
+ case "web": return "web";
494
+ default: return "process";
495
+ }
496
+ }
497
+ function uniqueProcesses(resources) {
498
+ const processes = new Map();
499
+ for (const resource of resources) {
500
+ for (const process of resource.processes) {
501
+ processes.set(`${process.pid}:${process.startIdentity}`, process);
502
+ }
503
+ }
504
+ return [...processes.values()];
505
+ }
506
+ function compareResources(left, right) {
507
+ const home = (left.yuiHome ?? "\uffff").localeCompare(right.yuiHome ?? "\uffff");
508
+ if (home !== 0)
509
+ return home;
510
+ const kind = resourceKindOrder(left.kind) - resourceKindOrder(right.kind);
511
+ if (kind !== 0)
512
+ return kind;
513
+ return left.id.localeCompare(right.id);
514
+ }
515
+ function resourceKindOrder(kind) {
516
+ return [
517
+ "controller",
518
+ "agent-session",
519
+ "tmux-server",
520
+ "app-server",
521
+ "web",
522
+ "process",
523
+ "artifact"
524
+ ].indexOf(kind);
525
+ }
526
+ function sum(values) {
527
+ return values.reduce((total, value) => total + value, 0);
528
+ }
529
+ function max(values) {
530
+ return values.length === 0 ? 0 : Math.max(...values);
531
+ }