@rubytech/create-maxy-code 0.1.567 → 0.1.569
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/dist/__tests__/brand-agent-root.test.js +91 -0
- package/dist/brand-agent-root.js +58 -0
- package/dist/index.js +25 -28
- package/package.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +4 -2
- package/payload/platform/plugins/admin/hooks/__tests__/quote-path-write-gate.test.sh +152 -0
- package/payload/platform/plugins/admin/hooks/__tests__/quote-pdf-bash-gate.test.sh +95 -0
- package/payload/platform/plugins/admin/hooks/__tests__/quote-render-gate.test.sh +141 -45
- package/payload/platform/plugins/admin/hooks/__tests__/quote-render-pdf-conformance.test.sh +114 -0
- package/payload/platform/plugins/admin/hooks/quote-path-write-gate.sh +137 -0
- package/payload/platform/plugins/admin/hooks/quote-pdf-bash-gate.sh +70 -0
- package/payload/platform/plugins/admin/hooks/quote-render-gate.sh +105 -37
- package/payload/platform/plugins/admin/hooks/quote-render-pdf-conformance.sh +178 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +7 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/lib/roster-census.d.ts +10 -1
- package/payload/platform/plugins/admin/mcp/dist/lib/roster-census.d.ts.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/lib/roster-census.js +52 -1
- package/payload/platform/plugins/admin/mcp/dist/lib/roster-census.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts +9 -0
- package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.d.ts.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js +1 -1
- package/payload/platform/plugins/admin/mcp/dist/tools/capabilities-here.js.map +1 -1
- package/payload/platform/plugins/admin/skills/capabilities-here/SKILL.md +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +44 -1
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +15 -0
- package/payload/platform/plugins/docs/references/channel-wake-and-prompt.md +43 -0
- package/payload/platform/scripts/__tests__/mailbox-inject-registered.test.sh +5 -5
- package/payload/platform/scripts/__tests__/preference-hooks-registered.test.sh +3 -1
- package/payload/platform/scripts/__tests__/public-surface-registered.test.sh +5 -5
- package/payload/platform/scripts/__tests__/quote-ownership-contract.test.sh +86 -0
- package/payload/platform/scripts/lib/__tests__/account-settings-reconcile.test.sh +73 -0
- package/payload/platform/scripts/lib/account-settings-reconcile.sh +88 -0
- package/payload/platform/scripts/lib/provision-account-dir.sh +11 -146
- package/payload/platform/scripts/setup-account.sh +13 -0
- package/payload/platform/services/claude-session-manager/dist/account-agent-registry.d.ts +39 -0
- package/payload/platform/services/claude-session-manager/dist/account-agent-registry.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/account-agent-registry.js +223 -0
- package/payload/platform/services/claude-session-manager/dist/account-agent-registry.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts +5 -0
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.js +5 -5
- package/payload/platform/services/claude-session-manager/dist/fs-watcher.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +11 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +58 -12
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +112 -13
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts +6 -1
- package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-census.js +6 -3
- package/payload/platform/services/claude-session-manager/dist/pty-census.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +21 -8
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +38 -15
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.d.ts +42 -0
- package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.js +215 -0
- package/payload/platform/services/claude-session-manager/dist/quote-delivery-reconcile.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +2 -0
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +4 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/reaper.d.ts +3 -0
- package/payload/platform/services/claude-session-manager/dist/reaper.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/reaper.js +2 -1
- package/payload/platform/services/claude-session-manager/dist/reaper.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-cap-audit.d.ts +2 -0
- package/payload/platform/services/claude-session-manager/dist/session-cap-audit.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-cap-audit.js +5 -2
- package/payload/platform/services/claude-session-manager/dist/session-cap-audit.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts +2 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js +5 -3
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/specialist-preference-audit.d.ts +108 -0
- package/payload/platform/services/claude-session-manager/dist/specialist-preference-audit.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/specialist-preference-audit.js +299 -0
- package/payload/platform/services/claude-session-manager/dist/specialist-preference-audit.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts +26 -16
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.js +84 -31
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts +7 -0
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js +6 -0
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js.map +1 -1
- package/payload/platform/services/telegram-channel/dist/notification.d.ts +10 -2
- package/payload/platform/services/telegram-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/telegram-channel/dist/notification.js +15 -3
- package/payload/platform/services/telegram-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/telegram-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/telegram-channel/dist/server.js +35 -3
- package/payload/platform/services/telegram-channel/dist/server.js.map +1 -1
- package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts +10 -0
- package/payload/platform/services/telegram-channel/dist/turn-follow.d.ts.map +1 -1
- package/payload/platform/services/telegram-channel/dist/turn-follow.js +38 -1
- package/payload/platform/services/telegram-channel/dist/turn-follow.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/server.js +31 -2
- package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts +10 -0
- package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/turn-follow.js +38 -1
- package/payload/platform/services/webchat-channel/dist/turn-follow.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/instructions.d.ts +9 -0
- package/payload/platform/services/whatsapp-channel/dist/instructions.d.ts.map +1 -0
- package/payload/platform/services/whatsapp-channel/dist/instructions.js +22 -0
- package/payload/platform/services/whatsapp-channel/dist/instructions.js.map +1 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +3 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +10 -2
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +26 -2
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/platform/templates/account-settings.json +153 -0
- package/payload/server/server.js +254 -96
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// Task 2469 — a specialist that CAN be reached is not a specialist that WAS.
|
|
2
|
+
//
|
|
3
|
+
// The registry reconcile in account-agent-registry.ts answers the first
|
|
4
|
+
// question and emits `op=agent-registry-resolution`. It was green on the
|
|
5
|
+
// account that filed this, and would have stayed green through the whole
|
|
6
|
+
// incident, because every clerk was reachable and none was reached: four
|
|
7
|
+
// authored clerks owned the works orders, invoices and client emails while
|
|
8
|
+
// content-producer produced all of them. The two questions are deliberately
|
|
9
|
+
// separate signals, because one cannot answer the other.
|
|
10
|
+
//
|
|
11
|
+
// Keyed on the written path rather than on the dispatch prose. A dispatch
|
|
12
|
+
// description is the model's own account of what it is doing, and the incident
|
|
13
|
+
// this closes had twelve descriptions that each read as reasonable. A path is
|
|
14
|
+
// a fact, and it is the same fact a write gate enforces on, so enforcement and
|
|
15
|
+
// audit cannot drift apart.
|
|
16
|
+
//
|
|
17
|
+
// The input is `<projectDir>/<sessionId>/subagents/agent-<agentId>.jsonl` with
|
|
18
|
+
// its sibling `.meta.json`. That directory is the ONLY surface that ties a
|
|
19
|
+
// write to a specialist: the parent transcript records every record as
|
|
20
|
+
// `isSidechain: false`, carries none of the subagent's tool calls, and its
|
|
21
|
+
// `toolUseResult.toolStats` gives aggregate counts with no paths.
|
|
22
|
+
//
|
|
23
|
+
// Shape mirrors public-surface-audit.ts: PURE parsers, selector and formatters
|
|
24
|
+
// plus an impure factory whose setInterval is unref'd and whose readers are
|
|
25
|
+
// injected.
|
|
26
|
+
import { closeSync, openSync, readSync } from 'node:fs';
|
|
27
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
28
|
+
const TAG = '[specialist-preference]';
|
|
29
|
+
/** Lines of a file, read in bounded chunks rather than into one string.
|
|
30
|
+
*
|
|
31
|
+
* One session's `subagents/` directory measured 473 MB, so reading a
|
|
32
|
+
* transcript with `readFileSync` would put the whole thing on the heap to look
|
|
33
|
+
* at one field per line. A `StringDecoder` carries partial multi-byte
|
|
34
|
+
* characters across chunk boundaries: splitting one would corrupt the line,
|
|
35
|
+
* `JSON.parse` would reject it, and the write it carried would vanish from
|
|
36
|
+
* the audit silently. */
|
|
37
|
+
export function* readLinesSync(path, chunkBytes = 1 << 20) {
|
|
38
|
+
const fd = openSync(path, 'r');
|
|
39
|
+
try {
|
|
40
|
+
const buf = Buffer.allocUnsafe(chunkBytes);
|
|
41
|
+
const decoder = new StringDecoder('utf8');
|
|
42
|
+
let carry = '';
|
|
43
|
+
for (;;) {
|
|
44
|
+
const n = readSync(fd, buf, 0, chunkBytes, null);
|
|
45
|
+
if (n === 0)
|
|
46
|
+
break;
|
|
47
|
+
const parts = (carry + decoder.write(buf.subarray(0, n))).split('\n');
|
|
48
|
+
carry = parts.pop() ?? '';
|
|
49
|
+
for (const p of parts)
|
|
50
|
+
if (p.length > 0)
|
|
51
|
+
yield p;
|
|
52
|
+
}
|
|
53
|
+
carry += decoder.end();
|
|
54
|
+
if (carry.length > 0)
|
|
55
|
+
yield carry;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
closeSync(fd);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const WRITE_TOOLS = new Set(['Write', 'Edit', 'MultiEdit', 'NotebookEdit']);
|
|
62
|
+
/** Artefact directory to the specialist that owns it.
|
|
63
|
+
*
|
|
64
|
+
* Every entry is a directory measured in the live job tree: `quote` (17 jobs),
|
|
65
|
+
* `Quotations` (5), `orders` (8), `invoices` (1). There is no valuation
|
|
66
|
+
* directory anywhere in that tree, so valuation-clerk deliberately has no
|
|
67
|
+
* entry — a guessed owner produces misroute lines nobody can act on.
|
|
68
|
+
*
|
|
69
|
+
* Two exclusions are deliberate. `pricing/` holds the internal priced
|
|
70
|
+
* breakdown rather than a client-facing artefact. `Suppliers Invoices` is the
|
|
71
|
+
* supplier's invoice to the business, not the client invoice the
|
|
72
|
+
* invoice-clerk issues, and the match is exact-segment so it cannot be swept
|
|
73
|
+
* in by the `invoices` entry. */
|
|
74
|
+
export const OWNED_PATHS = [
|
|
75
|
+
{ segment: 'quote', owner: 'sitedesk:quoter' },
|
|
76
|
+
{ segment: 'Quotations', owner: 'sitedesk:quoter' },
|
|
77
|
+
{ segment: 'orders', owner: 'order-clerk' },
|
|
78
|
+
{ segment: 'invoices', owner: 'invoice-clerk' },
|
|
79
|
+
];
|
|
80
|
+
/** The specialist that owns a written path, or null when the path is not an
|
|
81
|
+
* owned artefact. Anchored on `jobs/<jobId>/<segment>/`, so a same-named
|
|
82
|
+
* directory elsewhere in the account is not swept in and a file sitting
|
|
83
|
+
* directly in a job directory belongs to nobody in particular. */
|
|
84
|
+
export function ownerForPath(filePath) {
|
|
85
|
+
const seg = filePath.split('/');
|
|
86
|
+
for (let i = 0; i + 3 < seg.length; i++) {
|
|
87
|
+
if (seg[i] !== 'jobs')
|
|
88
|
+
continue;
|
|
89
|
+
const bucket = seg[i + 2];
|
|
90
|
+
const hit = OWNED_PATHS.find((o) => o.segment === bucket);
|
|
91
|
+
if (hit)
|
|
92
|
+
return hit.owner;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
/** `agentType` from a subagent's sibling meta file, or null when the file is
|
|
97
|
+
* unreadable, unparseable, or carries no type. Null is never read as
|
|
98
|
+
* compliance: see `auditTranscript`. */
|
|
99
|
+
export function parseAgentType(metaJson) {
|
|
100
|
+
if (metaJson === null)
|
|
101
|
+
return null;
|
|
102
|
+
try {
|
|
103
|
+
const d = JSON.parse(metaJson);
|
|
104
|
+
const t = d?.agentType;
|
|
105
|
+
return typeof t === 'string' && t.length > 0 ? t : null;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Written paths in one transcript line. A line that will not parse yields
|
|
112
|
+
* nothing rather than throwing: the corpus carries partial trailing writes,
|
|
113
|
+
* and one bad line must not abort the transcript it sits in. */
|
|
114
|
+
export function selectWrites(transcriptLine) {
|
|
115
|
+
let d;
|
|
116
|
+
try {
|
|
117
|
+
d = JSON.parse(transcriptLine);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
const content = d?.message?.content;
|
|
123
|
+
if (!Array.isArray(content))
|
|
124
|
+
return [];
|
|
125
|
+
const out = [];
|
|
126
|
+
for (const b of content) {
|
|
127
|
+
if (!b || typeof b !== 'object')
|
|
128
|
+
continue;
|
|
129
|
+
const blk = b;
|
|
130
|
+
if (blk.type !== 'tool_use')
|
|
131
|
+
continue;
|
|
132
|
+
if (typeof blk.name !== 'string' || !WRITE_TOOLS.has(blk.name))
|
|
133
|
+
continue;
|
|
134
|
+
const fp = blk.input?.file_path;
|
|
135
|
+
if (typeof fp === 'string' && fp.length > 0)
|
|
136
|
+
out.push(fp);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
/** One subagent run.
|
|
141
|
+
*
|
|
142
|
+
* `agentType` null means the meta file was missing or unreadable. Its
|
|
143
|
+
* owned-path writes are counted as unattributed rather than as compliant:
|
|
144
|
+
* "we could not tell who wrote this" is not "the right specialist wrote it",
|
|
145
|
+
* and folding the two together is how a broken reader reads as a clean
|
|
146
|
+
* account. */
|
|
147
|
+
export function auditTranscript(p) {
|
|
148
|
+
let ownedPathWrites = 0;
|
|
149
|
+
let unattributed = 0;
|
|
150
|
+
const misroutes = [];
|
|
151
|
+
for (const line of p.lines) {
|
|
152
|
+
for (const path of selectWrites(line)) {
|
|
153
|
+
const owner = ownerForPath(path);
|
|
154
|
+
if (owner === null)
|
|
155
|
+
continue;
|
|
156
|
+
ownedPathWrites++;
|
|
157
|
+
if (p.agentType === null) {
|
|
158
|
+
unattributed++;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (p.agentType !== owner) {
|
|
162
|
+
misroutes.push({ session: p.session, agent: p.agentType, owner, path });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return { ownedPathWrites, unattributed, misroutes };
|
|
167
|
+
}
|
|
168
|
+
/** The every-tick line. Emitted including zero, so a run that found nothing is
|
|
169
|
+
* distinguishable from a run that did not happen. */
|
|
170
|
+
export function formatPreferenceSummary(p) {
|
|
171
|
+
return `${TAG} op=audit sessionsScanned=${p.sessionsScanned}` +
|
|
172
|
+
` transcriptsScanned=${p.transcriptsScanned}` +
|
|
173
|
+
` ownedPathWrites=${p.ownedPathWrites}` +
|
|
174
|
+
` misrouted=${p.misrouted} unattributed=${p.unattributed}`;
|
|
175
|
+
}
|
|
176
|
+
/** One line per misroute, naming the subagent, the owner it displaced and the
|
|
177
|
+
* path. The displaced owner is the actionable half: "content-producer wrote
|
|
178
|
+
* this" says less than "content-producer wrote this instead of the quoter".
|
|
179
|
+
*
|
|
180
|
+
* The path is JSON-quoted because these paths contain spaces — the document
|
|
181
|
+
* that prompted this task is `Q 1879 - Woolgrove School - Yellow Class
|
|
182
|
+
* Kitchenette.html` — and a bare value would split into four tokens under any
|
|
183
|
+
* `key=value` reader, including `grep -o 'path=\S*'`. */
|
|
184
|
+
export function formatMisroute(p) {
|
|
185
|
+
return `${TAG} op=misroute session=${p.session} agent=${p.agent}` +
|
|
186
|
+
` owner=${p.owner} path=${JSON.stringify(p.path)}`;
|
|
187
|
+
}
|
|
188
|
+
export function createSpecialistPreferenceAudit(deps) {
|
|
189
|
+
/** jsonl path -> the result of reading it at `mtimeMs`.
|
|
190
|
+
*
|
|
191
|
+
* The emitted counts are a STATE — the totals across every transcript
|
|
192
|
+
* currently known — not a per-tick delta. A transcript still being appended
|
|
193
|
+
* to is re-read when its mtime moves and its entry is REPLACED, so its
|
|
194
|
+
* earlier writes are counted once, not once per tick. A delta counter over
|
|
195
|
+
* a growing file would inflate every total it reported. */
|
|
196
|
+
const seen = new Map();
|
|
197
|
+
/** Misroutes already named, so a steady state does not re-log every tick. */
|
|
198
|
+
const logged = new Set();
|
|
199
|
+
let timer = null;
|
|
200
|
+
function tickOnce() {
|
|
201
|
+
let sessionsScanned = 0;
|
|
202
|
+
const live = new Set();
|
|
203
|
+
for (const session of deps.listSessions()) {
|
|
204
|
+
sessionsScanned++;
|
|
205
|
+
let transcripts;
|
|
206
|
+
try {
|
|
207
|
+
transcripts = deps.listTranscripts(session);
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
// A directory that cannot be listed is named and skipped. Silence here
|
|
211
|
+
// would read as a session with no subagent activity, which is the one
|
|
212
|
+
// reading this audit must never produce by accident.
|
|
213
|
+
deps.logger(`${TAG} op=session-unreadable session=${session}` +
|
|
214
|
+
` reason=${err instanceof Error ? err.message : String(err)}`);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
for (const t of transcripts) {
|
|
218
|
+
live.add(t.jsonl);
|
|
219
|
+
const prior = seen.get(t.jsonl);
|
|
220
|
+
if (prior && prior.mtimeMs === t.mtimeMs)
|
|
221
|
+
continue;
|
|
222
|
+
const agentType = parseAgentType(deps.readMeta(t.meta));
|
|
223
|
+
let audit;
|
|
224
|
+
try {
|
|
225
|
+
audit = auditTranscript({ session, agentType, lines: deps.readLines(t.jsonl) });
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
deps.logger(`${TAG} op=transcript-unreadable path=${t.jsonl}` +
|
|
229
|
+
` reason=${err instanceof Error ? err.message : String(err)}`);
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
seen.set(t.jsonl, { mtimeMs: t.mtimeMs, audit });
|
|
233
|
+
for (const m of audit.misroutes) {
|
|
234
|
+
const key = `${m.session}|${m.agent}|${m.path}`;
|
|
235
|
+
if (logged.has(key))
|
|
236
|
+
continue;
|
|
237
|
+
deps.logger(formatMisroute(m));
|
|
238
|
+
logged.add(key);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Drop transcripts that have gone, so the totals describe what is on disk
|
|
243
|
+
// now rather than accumulating across the process's whole lifetime. The
|
|
244
|
+
// logged-misroute keys go with them: this process runs for weeks, and a set
|
|
245
|
+
// that only ever grew would hold every path of every session the install
|
|
246
|
+
// has ever archived.
|
|
247
|
+
for (const path of Array.from(seen.keys()))
|
|
248
|
+
if (!live.has(path))
|
|
249
|
+
seen.delete(path);
|
|
250
|
+
const liveKeys = new Set();
|
|
251
|
+
for (const { audit } of seen.values()) {
|
|
252
|
+
for (const m of audit.misroutes)
|
|
253
|
+
liveKeys.add(`${m.session}|${m.agent}|${m.path}`);
|
|
254
|
+
}
|
|
255
|
+
for (const key of Array.from(logged))
|
|
256
|
+
if (!liveKeys.has(key))
|
|
257
|
+
logged.delete(key);
|
|
258
|
+
let ownedPathWrites = 0;
|
|
259
|
+
let misrouted = 0;
|
|
260
|
+
let unattributed = 0;
|
|
261
|
+
for (const { audit } of seen.values()) {
|
|
262
|
+
ownedPathWrites += audit.ownedPathWrites;
|
|
263
|
+
misrouted += audit.misroutes.length;
|
|
264
|
+
unattributed += audit.unattributed;
|
|
265
|
+
}
|
|
266
|
+
deps.logger(formatPreferenceSummary({
|
|
267
|
+
sessionsScanned,
|
|
268
|
+
transcriptsScanned: seen.size,
|
|
269
|
+
ownedPathWrites,
|
|
270
|
+
misrouted,
|
|
271
|
+
unattributed,
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
tickOnce,
|
|
276
|
+
start() {
|
|
277
|
+
if (timer)
|
|
278
|
+
return;
|
|
279
|
+
timer = setInterval(() => {
|
|
280
|
+
try {
|
|
281
|
+
tickOnce();
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
deps.logger(`${TAG} op=audit-tick-failed message=${err instanceof Error ? err.message : String(err)}`);
|
|
285
|
+
}
|
|
286
|
+
}, deps.intervalMs);
|
|
287
|
+
if (typeof timer.unref === 'function')
|
|
288
|
+
timer.unref();
|
|
289
|
+
deps.logger(`${TAG} op=audit-started intervalMs=${deps.intervalMs}`);
|
|
290
|
+
},
|
|
291
|
+
stop() {
|
|
292
|
+
if (timer) {
|
|
293
|
+
clearInterval(timer);
|
|
294
|
+
timer = null;
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=specialist-preference-audit.js.map
|
package/payload/platform/services/claude-session-manager/dist/specialist-preference-audit.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specialist-preference-audit.js","sourceRoot":"","sources":["../src/specialist-preference-audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -28,11 +28,15 @@ export interface SystemctlResult {
|
|
|
28
28
|
* Tests inject a recording stub. */
|
|
29
29
|
export type SystemctlRunner = (args: readonly string[]) => SystemctlResult;
|
|
30
30
|
export declare function defaultSystemctlRunner(args: readonly string[]): SystemctlResult;
|
|
31
|
-
export declare function scopeUnitName(unitToken: string): string;
|
|
32
|
-
/** Inverse of `scopeUnitName
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
|
|
31
|
+
export declare function scopeUnitName(brandSlug: string, unitToken: string): string;
|
|
32
|
+
/** Inverse of `scopeUnitName`, for THIS brand only. Returns null for a peer
|
|
33
|
+
* brand's unit and for the pre-2457 unbranded shape — both are units this
|
|
34
|
+
* manager must never claim, reattach, or stop.
|
|
35
|
+
*
|
|
36
|
+
* A literal prefix test, not a regex: `^claude-session-(.+)\.scope$` against a
|
|
37
|
+
* branded name hands back `<brand>-claude-session-<token>` as the token, and a
|
|
38
|
+
* `maxy` slug would prefix-match a `maxy-code` unit. */
|
|
39
|
+
export declare function unitTokenFromScopeUnit(brandSlug: string, unit: string): string | null;
|
|
36
40
|
/** One row from `systemctl --user list-units --type=scope --no-legend
|
|
37
41
|
* 'claude-session-*.scope'`. The `loaded`/`active`/`sub` columns let the
|
|
38
42
|
* boot-seed reconciler ignore units that are draining (`sub=stop-sigterm`)
|
|
@@ -48,21 +52,24 @@ export interface SurvivingScope {
|
|
|
48
52
|
* Tolerant of leading column whitespace and trailing description column.
|
|
49
53
|
* Rows whose unit name doesn't extract a unitToken are skipped (defensive
|
|
50
54
|
* against future systemctl-output drift). */
|
|
51
|
-
export declare function parseSurvivingScopes(stdout: string): SurvivingScope[];
|
|
55
|
+
export declare function parseSurvivingScopes(brandSlug: string, stdout: string): SurvivingScope[];
|
|
52
56
|
/** Issue `systemctl --user list-units --type=scope --no-legend
|
|
53
57
|
* 'claude-session-*.scope'` and return the parsed rows. The boot-seed
|
|
54
58
|
* uses this to discover surviving PTYs across a manager restart. */
|
|
55
|
-
export declare function listSurvivingScopes(run: SystemctlRunner): {
|
|
59
|
+
export declare function listSurvivingScopes(run: SystemctlRunner, brandSlug: string): {
|
|
56
60
|
scopes: SurvivingScope[];
|
|
57
61
|
result: SystemctlResult;
|
|
58
62
|
};
|
|
59
63
|
/** Stop any orphan rc scope units left over from prior session-manager
|
|
60
|
-
* boots. Called from the session-manager startup BEFORE the
|
|
61
|
-
* supervisor's first spawn and BEFORE the HTTP server begins
|
|
62
|
-
* /rc-spawn requests, so no in-flight scope can be misidentified
|
|
63
|
-
* orphan. Idempotent and safe to call on every boot — if nothing
|
|
64
|
-
* matches, no stop is issued.
|
|
65
|
-
|
|
64
|
+
* boots OF THIS BRAND. Called from the session-manager startup BEFORE the
|
|
65
|
+
* rc-daemon supervisor's first spawn and BEFORE the HTTP server begins
|
|
66
|
+
* accepting /rc-spawn requests, so no in-flight scope can be misidentified
|
|
67
|
+
* as an orphan. Idempotent and safe to call on every boot — if nothing
|
|
68
|
+
* matches, no stop is issued.
|
|
69
|
+
*
|
|
70
|
+
* Task 2457 — `listSurvivingScopes` globs this brand only, so a peer brand's
|
|
71
|
+
* live session can no longer be classified as an orphan here. */
|
|
72
|
+
export declare function reapOrphanNonPtyScopes(run: SystemctlRunner, logger: (line: string) => void, brandSlug: string): Promise<void>;
|
|
66
73
|
/** Outcome surface for a scope-stop attempt.
|
|
67
74
|
*
|
|
68
75
|
* Task 451 — `outcome=ok` means **unit stopped AND pid is gone**. Pre-451,
|
|
@@ -115,7 +122,7 @@ export declare const defaultProcessSignals: ProcessSignals;
|
|
|
115
122
|
* scope's TimeoutStopSec=5 handles in-cgroup SIGTERM→SIGKILL escalation.
|
|
116
123
|
* Exit code 5 ("not loaded") still means the scope retired naturally —
|
|
117
124
|
* treated as `already-gone`. */
|
|
118
|
-
export declare function stopScopeUnit(run: SystemctlRunner, unitToken: string, opts?: {
|
|
125
|
+
export declare function stopScopeUnit(run: SystemctlRunner, brandSlug: string, unitToken: string, opts?: {
|
|
119
126
|
pid?: number | null;
|
|
120
127
|
killGraceMs?: number;
|
|
121
128
|
signals?: ProcessSignals;
|
|
@@ -128,6 +135,9 @@ export interface SystemdRunArgv {
|
|
|
128
135
|
}
|
|
129
136
|
export interface BuildSystemdRunArgvParams {
|
|
130
137
|
unitToken: string;
|
|
138
|
+
/** Task 2457 — the brand that owns this scope. Lands first in the unit name
|
|
139
|
+
* so no peer brand's enumeration can see it. */
|
|
140
|
+
brandSlug: string;
|
|
131
141
|
claudeBin: string;
|
|
132
142
|
claudeArgs: readonly string[];
|
|
133
143
|
/** Full env map to thread into the scope via `--setenv=KEY=VAL`. Every
|
|
@@ -168,7 +178,7 @@ export declare function buildSystemdRunArgv(p: BuildSystemdRunArgvParams): Syste
|
|
|
168
178
|
* `$CLAUDE_CONFIG_DIR/sessions/<pid>.json`, which in turn carries claude's
|
|
169
179
|
* intrinsic sessionId. Returns null when the unit has no main pid (the
|
|
170
180
|
* scope is draining) or when the systemctl call fails. */
|
|
171
|
-
export declare function getScopeMainPid(run: SystemctlRunner, unitToken: string): number | null;
|
|
181
|
+
export declare function getScopeMainPid(run: SystemctlRunner, brandSlug: string, unitToken: string): number | null;
|
|
172
182
|
/** Task 1398 gap 2 — platform-branching spawn plan. macOS has no `systemd-run`;
|
|
173
183
|
* wrapping a spawn in it makes node-pty exec a missing binary, which the child
|
|
174
184
|
* fails as `exitCode=1 ranMs=5 bytes=0`. On darwin, spawn claude directly and
|
|
@@ -203,7 +213,7 @@ export declare function stopPidDirect(pid: number | null, opts?: {
|
|
|
203
213
|
* the evicted pty would linger until the manager exits. This helper branches
|
|
204
214
|
* on platform: darwin kills `victim.pid` directly; every other platform issues
|
|
205
215
|
* exactly today's pid-omitted scope stop, byte-identical. */
|
|
206
|
-
export declare function evictSessionProcess(platform: NodeJS.Platform, run: SystemctlRunner, victim: {
|
|
216
|
+
export declare function evictSessionProcess(platform: NodeJS.Platform, run: SystemctlRunner, brandSlug: string, victim: {
|
|
207
217
|
scopeUnitToken: string;
|
|
208
218
|
pid: number;
|
|
209
219
|
}, opts?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"systemd-scope.d.ts","sourceRoot":"","sources":["../src/systemd-scope.ts"],"names":[],"mappings":"AAuBA;;gBAEgB;AAChB,eAAO,MAAM,iBAAiB,sBAAsB,CAAA;AAEpD;;;uDAGuD;AACvD,eAAO,MAAM,sBAAsB,IAAI,CAAA;AAEvC;;;gCAGgC;AAChC,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd;;;;mDAI+C;IAC/C,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED;;;qCAGqC;AACrC,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,eAAe,CAAA;AAE1E,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,eAAe,CAgB/E;
|
|
1
|
+
{"version":3,"file":"systemd-scope.d.ts","sourceRoot":"","sources":["../src/systemd-scope.ts"],"names":[],"mappings":"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"}
|
|
@@ -59,24 +59,41 @@ export function defaultSystemctlRunner(args) {
|
|
|
59
59
|
// token for the unit name; boot-seed reconciles unit → claude pid →
|
|
60
60
|
// PID-file → claude sessionId after a restart.
|
|
61
61
|
const SCOPE_NAME_VALID = /^[a-zA-Z0-9:_.\-]+$/;
|
|
62
|
-
|
|
62
|
+
// Task 2457 — the brand goes BEFORE `claude-session-`, not inside it. Every
|
|
63
|
+
// co-resident brand shares one `user@1000.service`, so an unbranded name let any
|
|
64
|
+
// brand's boot enumerate — and stop — every other brand's live scopes. The
|
|
65
|
+
// brand-first shape also means an un-upgraded peer, which globs
|
|
66
|
+
// `claude-session-*.scope`, matches none of these: a brand is protected the
|
|
67
|
+
// moment it upgrades, whatever state its neighbours are in.
|
|
68
|
+
export function scopeUnitName(brandSlug, unitToken) {
|
|
69
|
+
if (!brandSlug || !SCOPE_NAME_VALID.test(brandSlug)) {
|
|
70
|
+
throw new Error(`[systemd-scope] invalid brandSlug for unit name: ${JSON.stringify(brandSlug)}`);
|
|
71
|
+
}
|
|
63
72
|
if (!unitToken || !SCOPE_NAME_VALID.test(unitToken)) {
|
|
64
73
|
throw new Error(`[systemd-scope] invalid unitToken for unit name: ${JSON.stringify(unitToken)}`);
|
|
65
74
|
}
|
|
66
|
-
return
|
|
75
|
+
return `${brandSlug}-claude-session-${unitToken}.scope`;
|
|
67
76
|
}
|
|
68
|
-
/** Inverse of `scopeUnitName
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
/** Inverse of `scopeUnitName`, for THIS brand only. Returns null for a peer
|
|
78
|
+
* brand's unit and for the pre-2457 unbranded shape — both are units this
|
|
79
|
+
* manager must never claim, reattach, or stop.
|
|
80
|
+
*
|
|
81
|
+
* A literal prefix test, not a regex: `^claude-session-(.+)\.scope$` against a
|
|
82
|
+
* branded name hands back `<brand>-claude-session-<token>` as the token, and a
|
|
83
|
+
* `maxy` slug would prefix-match a `maxy-code` unit. */
|
|
84
|
+
export function unitTokenFromScopeUnit(brandSlug, unit) {
|
|
85
|
+
const prefix = `${brandSlug}-claude-session-`;
|
|
86
|
+
const suffix = '.scope';
|
|
87
|
+
if (!unit.startsWith(prefix) || !unit.endsWith(suffix))
|
|
88
|
+
return null;
|
|
89
|
+
const token = unit.slice(prefix.length, unit.length - suffix.length);
|
|
90
|
+
return token.length > 0 ? token : null;
|
|
74
91
|
}
|
|
75
92
|
/** Parse the `--no-legend` output of `systemctl --user list-units`.
|
|
76
93
|
* Tolerant of leading column whitespace and trailing description column.
|
|
77
94
|
* Rows whose unit name doesn't extract a unitToken are skipped (defensive
|
|
78
95
|
* against future systemctl-output drift). */
|
|
79
|
-
export function parseSurvivingScopes(stdout) {
|
|
96
|
+
export function parseSurvivingScopes(brandSlug, stdout) {
|
|
80
97
|
const out = [];
|
|
81
98
|
for (const rawLine of stdout.split('\n')) {
|
|
82
99
|
const line = rawLine.trim();
|
|
@@ -89,9 +106,10 @@ export function parseSurvivingScopes(stdout) {
|
|
|
89
106
|
const loaded = cols[1];
|
|
90
107
|
const active = cols[2];
|
|
91
108
|
const sub = cols[3];
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
109
|
+
// Task 2457 — the shape guard lives in `unitTokenFromScopeUnit`, which
|
|
110
|
+
// rejects a peer brand and the legacy unbranded name as well as a
|
|
111
|
+
// non-scope. A separate prefix test here could only disagree with it.
|
|
112
|
+
const unitToken = unitTokenFromScopeUnit(brandSlug, unitName);
|
|
95
113
|
if (!unitToken)
|
|
96
114
|
continue;
|
|
97
115
|
out.push({ unitName, unitToken, loaded, active, sub });
|
|
@@ -101,43 +119,69 @@ export function parseSurvivingScopes(stdout) {
|
|
|
101
119
|
/** Issue `systemctl --user list-units --type=scope --no-legend
|
|
102
120
|
* 'claude-session-*.scope'` and return the parsed rows. The boot-seed
|
|
103
121
|
* uses this to discover surviving PTYs across a manager restart. */
|
|
104
|
-
export function listSurvivingScopes(run) {
|
|
122
|
+
export function listSurvivingScopes(run, brandSlug) {
|
|
105
123
|
const result = run([
|
|
106
124
|
'--user',
|
|
107
125
|
'list-units',
|
|
108
126
|
'--type=scope',
|
|
109
127
|
'--no-legend',
|
|
110
128
|
'--all',
|
|
111
|
-
|
|
129
|
+
// Task 2457 — the brand is IN the glob. Every consumer of this function
|
|
130
|
+
// (the reaper, the boot-seed reconciler, the liveness tick) inherits the
|
|
131
|
+
// ownership boundary from here rather than re-deriving it.
|
|
132
|
+
`${brandSlug}-claude-session-*.scope`,
|
|
112
133
|
]);
|
|
113
134
|
if (result.exitCode !== 0) {
|
|
114
135
|
return { scopes: [], result };
|
|
115
136
|
}
|
|
116
|
-
return { scopes: parseSurvivingScopes(result.stdout), result };
|
|
137
|
+
return { scopes: parseSurvivingScopes(brandSlug, result.stdout), result };
|
|
117
138
|
}
|
|
118
139
|
/** Task 554 — rc-spawn / rc-daemon scope unit-token prefixes. PTY operator
|
|
119
|
-
* scopes use the bare
|
|
140
|
+
* scopes use the bare `<brand>-claude-session-<uuid>.scope` shape and are
|
|
120
141
|
* explicitly excluded: the boot-seed reconciler claims them as live
|
|
121
142
|
* operator sessions. The reaper only sweeps tokens emitted by the
|
|
122
143
|
* rc-daemon supervisor and the `/rc-spawn` HTTP route. */
|
|
123
144
|
const NON_PTY_TOKEN_PREFIXES = ['rc-daemon-', 'rc-resume-'];
|
|
124
145
|
/** Stop any orphan rc scope units left over from prior session-manager
|
|
125
|
-
* boots. Called from the session-manager startup BEFORE the
|
|
126
|
-
* supervisor's first spawn and BEFORE the HTTP server begins
|
|
127
|
-
* /rc-spawn requests, so no in-flight scope can be misidentified
|
|
128
|
-
* orphan. Idempotent and safe to call on every boot — if nothing
|
|
129
|
-
* matches, no stop is issued.
|
|
130
|
-
|
|
131
|
-
|
|
146
|
+
* boots OF THIS BRAND. Called from the session-manager startup BEFORE the
|
|
147
|
+
* rc-daemon supervisor's first spawn and BEFORE the HTTP server begins
|
|
148
|
+
* accepting /rc-spawn requests, so no in-flight scope can be misidentified
|
|
149
|
+
* as an orphan. Idempotent and safe to call on every boot — if nothing
|
|
150
|
+
* matches, no stop is issued.
|
|
151
|
+
*
|
|
152
|
+
* Task 2457 — `listSurvivingScopes` globs this brand only, so a peer brand's
|
|
153
|
+
* live session can no longer be classified as an orphan here. */
|
|
154
|
+
export async function reapOrphanNonPtyScopes(run, logger, brandSlug) {
|
|
155
|
+
const { scopes, result } = listSurvivingScopes(run, brandSlug);
|
|
132
156
|
if (result.exitCode !== 0) {
|
|
133
157
|
logger(`[systemd-scope] reaper list-units-failed exitCode=${result.exitCode} stderr=${result.stderr.trim()}`);
|
|
134
158
|
return;
|
|
135
159
|
}
|
|
160
|
+
// Task 2457 — count what belongs to other brands, unconditionally. Without
|
|
161
|
+
// this line the boundary holds silently and a regression is indistinguishable
|
|
162
|
+
// from a box with no other brands on it. Counted directly off the unit names,
|
|
163
|
+
// never by subtracting two list sizes, so a systemctl output change cannot
|
|
164
|
+
// turn a miscount into a plausible number.
|
|
165
|
+
const own = `${brandSlug}-claude-session-`;
|
|
166
|
+
// Two explicit patterns rather than one leading-`*` glob: `*-claude-session-*`
|
|
167
|
+
// covers every brand and `claude-session-*` covers the pre-2457 shape, so the
|
|
168
|
+
// count does not rest on how systemd's matcher treats an empty `*`.
|
|
169
|
+
const all = run([
|
|
170
|
+
'--user', 'list-units', '--type=scope', '--no-legend', '--all',
|
|
171
|
+
'*-claude-session-*.scope', 'claude-session-*.scope',
|
|
172
|
+
]);
|
|
173
|
+
const foreign = all.exitCode === 0
|
|
174
|
+
? all.stdout.split('\n')
|
|
175
|
+
.map((l) => l.trim().split(/\s+/)[0] ?? '')
|
|
176
|
+
.filter((u) => u.endsWith('.scope') && u.includes('claude-session-') && !u.startsWith(own))
|
|
177
|
+
.length
|
|
178
|
+
: 0;
|
|
179
|
+
logger(`[systemd-scope] op=foreign-scope-skipped brand=${brandSlug} skipped=${foreign}`);
|
|
136
180
|
const orphans = scopes.filter((s) => NON_PTY_TOKEN_PREFIXES.some((p) => s.unitToken.startsWith(p)));
|
|
137
181
|
if (orphans.length === 0)
|
|
138
182
|
return;
|
|
139
183
|
for (const orphan of orphans) {
|
|
140
|
-
const stop = await stopScopeUnit(run, orphan.unitToken, {});
|
|
184
|
+
const stop = await stopScopeUnit(run, brandSlug, orphan.unitToken, {});
|
|
141
185
|
logger(`[systemd-scope] reaped-orphan unitToken=${orphan.unitToken} active=${orphan.active} sub=${orphan.sub} outcome=${stop.outcome} exitCode=${stop.exitCode}`);
|
|
142
186
|
}
|
|
143
187
|
}
|
|
@@ -180,8 +224,8 @@ function sleep(ms) {
|
|
|
180
224
|
* scope's TimeoutStopSec=5 handles in-cgroup SIGTERM→SIGKILL escalation.
|
|
181
225
|
* Exit code 5 ("not loaded") still means the scope retired naturally —
|
|
182
226
|
* treated as `already-gone`. */
|
|
183
|
-
export async function stopScopeUnit(run, unitToken, opts = {}) {
|
|
184
|
-
const unit = scopeUnitName(unitToken);
|
|
227
|
+
export async function stopScopeUnit(run, brandSlug, unitToken, opts = {}) {
|
|
228
|
+
const unit = scopeUnitName(brandSlug, unitToken);
|
|
185
229
|
const result = run(['--user', 'stop', '--no-block', unit]);
|
|
186
230
|
// Task 1398 gap 2 — no systemctl on this host (macOS): there is no scope to
|
|
187
231
|
// stop, so terminate the pid directly. On linux the injected/default runner
|
|
@@ -190,6 +234,15 @@ export async function stopScopeUnit(run, unitToken, opts = {}) {
|
|
|
190
234
|
return stopPidDirect(opts.pid ?? null, { killGraceMs: opts.killGraceMs, signals: opts.signals });
|
|
191
235
|
}
|
|
192
236
|
if (result.exitCode === 5) {
|
|
237
|
+
// Task 2457 — "not loaded" is only `already-gone` if the process really is
|
|
238
|
+
// gone. Task 451 made that the contract for `ok`; it has to hold here too.
|
|
239
|
+
// The rename makes this reachable: a session alive when its brand upgrades
|
|
240
|
+
// has an UNBRANDED token in its on-disk scope record, which `http-server`
|
|
241
|
+
// falls back to on End, so the branded name resolves to no unit and exit 5
|
|
242
|
+
// used to report success while the PTY kept running.
|
|
243
|
+
if (opts.pid && opts.signals?.isAlive(opts.pid) !== false) {
|
|
244
|
+
return stopPidDirect(opts.pid, { killGraceMs: opts.killGraceMs, signals: opts.signals });
|
|
245
|
+
}
|
|
193
246
|
return { outcome: 'already-gone', exitCode: 5, stderr: result.stderr };
|
|
194
247
|
}
|
|
195
248
|
if (result.exitCode !== 0) {
|
|
@@ -242,7 +295,7 @@ export async function stopScopeUnit(run, unitToken, opts = {}) {
|
|
|
242
295
|
* script(1) wrap (Task 556) plus the non-PTY scope primitive it served
|
|
243
296
|
* are gone. */
|
|
244
297
|
export function buildSystemdRunArgv(p) {
|
|
245
|
-
const unit = scopeUnitName(p.unitToken);
|
|
298
|
+
const unit = scopeUnitName(p.brandSlug, p.unitToken);
|
|
246
299
|
const args = [
|
|
247
300
|
'--user',
|
|
248
301
|
'--scope',
|
|
@@ -282,8 +335,8 @@ export function buildSystemdRunArgv(p) {
|
|
|
282
335
|
* `$CLAUDE_CONFIG_DIR/sessions/<pid>.json`, which in turn carries claude's
|
|
283
336
|
* intrinsic sessionId. Returns null when the unit has no main pid (the
|
|
284
337
|
* scope is draining) or when the systemctl call fails. */
|
|
285
|
-
export function getScopeMainPid(run, unitToken) {
|
|
286
|
-
const unit = scopeUnitName(unitToken);
|
|
338
|
+
export function getScopeMainPid(run, brandSlug, unitToken) {
|
|
339
|
+
const unit = scopeUnitName(brandSlug, unitToken);
|
|
287
340
|
const result = run(['--user', 'show', '-p', 'MainPID', '--value', unit]);
|
|
288
341
|
if (result.exitCode !== 0)
|
|
289
342
|
return null;
|
|
@@ -347,10 +400,10 @@ export async function stopPidDirect(pid, opts = {}) {
|
|
|
347
400
|
* the evicted pty would linger until the manager exits. This helper branches
|
|
348
401
|
* on platform: darwin kills `victim.pid` directly; every other platform issues
|
|
349
402
|
* exactly today's pid-omitted scope stop, byte-identical. */
|
|
350
|
-
export async function evictSessionProcess(platform, run, victim, opts = {}) {
|
|
403
|
+
export async function evictSessionProcess(platform, run, brandSlug, victim, opts = {}) {
|
|
351
404
|
if (platform === 'darwin') {
|
|
352
405
|
return stopPidDirect(victim.pid, opts);
|
|
353
406
|
}
|
|
354
|
-
return stopScopeUnit(run, victim.scopeUnitToken);
|
|
407
|
+
return stopScopeUnit(run, brandSlug, victim.scopeUnitToken);
|
|
355
408
|
}
|
|
356
409
|
//# sourceMappingURL=systemd-scope.js.map
|