@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,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
|
+
}
|