@volter/supercode-teams 0.3.89 → 0.3.90
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/live-sessions.mjs +14 -8
- package/mail/relay.mjs +44 -19
- package/package.json +1 -1
package/live-sessions.mjs
CHANGED
|
@@ -61,14 +61,16 @@ function startedMs(pid) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/** Whether the runtime a receipt names answers at its address as that runtime (its own identity, with the receipt's
|
|
64
|
-
* token): the proof a start read off the wall clock cannot give, as the clock may have stepped since the receipt.
|
|
64
|
+
* token): the proof a start read off the wall clock cannot give, as the clock may have stepped since the receipt.
|
|
65
|
+
* true or false, or null when it did not answer within 2 s (the request is aborted then): not known either way. */
|
|
65
66
|
async function answersAs(receipt) {
|
|
66
67
|
try {
|
|
67
68
|
const client = new FrontendClient({ baseUrl: receipt.base_url, token: receipt.token, clientId: `live-${receipt.receipt_id}`, permissions: ['observe'] });
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
return (await client.describe({ signal: AbortSignal.timeout(2000) }))?.session_id === receipt.runtime_session_id;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error?.name === 'TimeoutError' || error?.name === 'AbortError') return null;
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
/** Why a claim names no caller. */
|
|
@@ -343,7 +345,7 @@ export class LiveSessions extends EventEmitter {
|
|
|
343
345
|
if (!alive(entry.pid)) return false;
|
|
344
346
|
const started = await startedMs(entry.pid);
|
|
345
347
|
if (!Number.isFinite(started) || !Number.isFinite(entry.created) || started <= entry.created + 2000) return true;
|
|
346
|
-
return answersAs(entry.probe);
|
|
348
|
+
return (await answersAs(entry.probe)) !== false;
|
|
347
349
|
}
|
|
348
350
|
|
|
349
351
|
/** One messaging-tools marker: the session process named by its file, the server pid in it. */
|
|
@@ -514,7 +516,7 @@ export class LiveSessions extends EventEmitter {
|
|
|
514
516
|
if (runtime === true) {
|
|
515
517
|
// a pid several receipts name is no one runtime: the rest of the claim names it, never a refusal here
|
|
516
518
|
let own = null;
|
|
517
|
-
try { own = await this.#runtimeOfPid(Number(pid)); } catch (error) { if (!(error instanceof CallerRefused)) throw error; }
|
|
519
|
+
try { own = await this.#runtimeOfPid(Number(pid)); } catch (error) { if (!(error instanceof CallerRefused) || error.unconfirmed) throw error; }
|
|
518
520
|
if (own) return own;
|
|
519
521
|
}
|
|
520
522
|
const parent = this.claude.get(Number(ppid));
|
|
@@ -542,7 +544,11 @@ export class LiveSessions extends EventEmitter {
|
|
|
542
544
|
if (held.length !== 1) return null;
|
|
543
545
|
// a caller's identity: checked again now, with no process (the runtime asked at its own address), as a runtime that
|
|
544
546
|
// ended may have left its pid to another process before the sweep lets its receipt go
|
|
545
|
-
|
|
547
|
+
const answered = await answersAs(held[0].probe);
|
|
548
|
+
// one that did not answer in time is not known either way: refused, never taken for its parent's session or its
|
|
549
|
+
// environment's (which would name another caller)
|
|
550
|
+
if (answered === null) throw Object.assign(new CallerRefused(`process ${pid} is a runtime that did not answer within 2 s, so which session is calling was not confirmed; nothing was done`), { unconfirmed: true });
|
|
551
|
+
if (!answered) return null;
|
|
546
552
|
return this.#address(held[0].harness, held[0].sessionId);
|
|
547
553
|
}
|
|
548
554
|
|
package/mail/relay.mjs
CHANGED
|
@@ -75,11 +75,28 @@ export class Relays {
|
|
|
75
75
|
for (const [directory, relay] of kept.slice(RELAYS_RUNNING)) {
|
|
76
76
|
if (this.sending.has(directory)) continue;
|
|
77
77
|
this.running.delete(directory);
|
|
78
|
-
|
|
78
|
+
// closed under the relay's own lock: a send to it that arrives meanwhile waits for the close, then starts it
|
|
79
|
+
// again, never handing its turn to a runtime that is closing
|
|
80
|
+
void this.#locked(directory, () => this.serve('harness.v1.runtimes.close', { connection: relay.connection, runtime_id: relay.runtime_id }, 15_000))
|
|
79
81
|
.catch((error) => this.log(`relay ${directory.split(/[\\/]/).pop()} beyond the ${RELAYS_RUNNING} most recent was not closed: ${error.message}`));
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
/** `work` run under the relay folder's lock: one send, or one close, at a time per relay, in arrival order. */
|
|
86
|
+
async #locked(directory, work) {
|
|
87
|
+
const previous = this.sending.get(directory) ?? Promise.resolve();
|
|
88
|
+
let release;
|
|
89
|
+
const mine = new Promise((resolve) => { release = resolve; });
|
|
90
|
+
const turn = previous.then(() => mine);
|
|
91
|
+
this.sending.set(directory, turn);
|
|
92
|
+
await previous;
|
|
93
|
+
try { return await work(); }
|
|
94
|
+
finally {
|
|
95
|
+
release();
|
|
96
|
+
if (this.sending.get(directory) === turn) this.sending.delete(directory);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
83
100
|
/** The relay that speaks for `represented`, known as `representedName`: its name and its files. */
|
|
84
101
|
spec(represented, representedName) {
|
|
85
102
|
const name = relayName(String(representedName).split('@')[0], String(represented).split(':')[1]);
|
|
@@ -127,8 +144,9 @@ export class Relays {
|
|
|
127
144
|
|
|
128
145
|
/** The relay's runtime, started in this daemon's harness serve when it is not running (claude_relay.rs
|
|
129
146
|
* `ensure_relay_runtime`): `{ runtime (its receipt), connection }`. */
|
|
130
|
-
async #ensure(spec) {
|
|
147
|
+
async #ensure(spec, step = () => {}) {
|
|
131
148
|
const endpoint = await this.endpoint();
|
|
149
|
+
step('relay endpoint');
|
|
132
150
|
if (!endpoint) throw new Error('the relay endpoint is not answering; the machine daemon\'s `supercode message watch` serves it (see mail/machine-daemon.log)');
|
|
133
151
|
// written on every send: Claude Code reloads a changed settings file, so a running relay takes the hooks this
|
|
134
152
|
// supercode installs
|
|
@@ -137,8 +155,11 @@ export class Relays {
|
|
|
137
155
|
if (record?.runtime_id) {
|
|
138
156
|
const runtime = this.runtimeOf('claude-code', record.runtime_id);
|
|
139
157
|
if (runtime && alive(this.#pid(spec, runtime)) && record.connection && (record.endpoint === endpoint || await answers(Number(String(record.endpoint ?? '').split(':').pop())))) {
|
|
158
|
+
step('runtime reused');
|
|
140
159
|
return { runtime, connection: record.connection };
|
|
141
160
|
}
|
|
161
|
+
// why it is not reused, said in the timing line: a relay that should have been warm and was not shows here
|
|
162
|
+
step(`runtime not reused (${!runtime ? 'no live receipt' : !alive(this.#pid(spec, runtime)) ? 'its process ended' : !record.connection ? 'no connection' : 'endpoint moved'})`);
|
|
142
163
|
// closed through the owner's door even after its receipt went
|
|
143
164
|
try { await this.serve('harness.v1.runtimes.close', { connection: record.connection, runtime_id: record.runtime_id }, 15_000); }
|
|
144
165
|
catch (error) { if (this.runtimeOf('claude-code', record.runtime_id)) throw new Error(`could not retire the old relay: ${error.message}`); }
|
|
@@ -148,7 +169,7 @@ export class Relays {
|
|
|
148
169
|
harness: 'claude-code',
|
|
149
170
|
launch: {
|
|
150
171
|
program: 'claude',
|
|
151
|
-
arguments: ['--print', '--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose', '--permission-prompt-tool', 'stdio', '--model', RELAY_MODEL, '--name', spec.name, '--permission-mode', 'bypassPermissions', '--setting-sources', 'project', '--settings', spec.settings, '--tools', 'ListAgents,SendMessage', '--no-session-persistence'],
|
|
172
|
+
arguments: ['--print', '--input-format', 'stream-json', '--output-format', 'stream-json', '--verbose', '--permission-prompt-tool', 'stdio', '--model', RELAY_MODEL, '--name', spec.name, '--permission-mode', 'bypassPermissions', '--setting-sources', 'project', '--settings', spec.settings, '--tools', 'ListAgents,SendMessage', '--no-session-persistence', '--strict-mcp-config'],
|
|
152
173
|
// its API is the relay endpoint, its key the relay's folder there (which also keeps account connectors out)
|
|
153
174
|
env: { ANTHROPIC_API_KEY: key, ANTHROPIC_BASE_URL: endpoint, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1', SUPERCODE_CLAUDE_RELAY: '1' },
|
|
154
175
|
},
|
|
@@ -158,18 +179,27 @@ export class Relays {
|
|
|
158
179
|
if (typeof runtimeId !== 'string') throw new Error(`the machine daemon did not start the relay: ${JSON.stringify(started)}`);
|
|
159
180
|
const connection = typeof started.connection === 'string' ? started.connection : '';
|
|
160
181
|
const pid = Number.isInteger(started?.handle?.endpoint?.pid) ? started.handle.endpoint.pid : null;
|
|
182
|
+
step('runtime started (runtimes.start)');
|
|
161
183
|
writeFileSync(spec.record, JSON.stringify({ runtime_id: runtimeId, pid, endpoint, connection }));
|
|
184
|
+
// the daemon's from its start: counted by the cap even when the rest of this start fails
|
|
185
|
+
this.running.set(spec.directory, { connection, runtime_id: runtimeId, usedAt: Date.now() });
|
|
162
186
|
// harness serve writes its receipt before it answers the start; the map learns it from its watch a moment later, so
|
|
163
187
|
// one it does not hold yet is read by its own name (the receipt the runtime's terminal launch opens), never waited on
|
|
164
188
|
const known = this.runtimeOf('claude-code', runtimeId);
|
|
165
|
-
if (known) return { runtime: known, connection };
|
|
189
|
+
if (known) { step('its receipt held'); return { runtime: known, connection }; }
|
|
166
190
|
let receiptId = null;
|
|
167
191
|
try {
|
|
168
192
|
const launch = (await this.serve('harness.v1.runtimes.terminal_instructions', { connection }, 5000))?.launch;
|
|
169
193
|
receiptId = (Array.isArray(launch?.arguments) ? launch.arguments : []).find((arg) => typeof arg === 'string' && arg.startsWith('supercode-live://'))?.slice('supercode-live://'.length) ?? null;
|
|
170
194
|
} catch { /* said below */ }
|
|
171
195
|
const runtime = receiptId ? this.runtimeNamed(receiptId) : null;
|
|
172
|
-
|
|
196
|
+
step('its receipt read');
|
|
197
|
+
if (!runtime) {
|
|
198
|
+
// a runtime no send can use is closed, never left running untracked
|
|
199
|
+
this.running.delete(spec.directory);
|
|
200
|
+
try { await this.serve('harness.v1.runtimes.close', { connection, runtime_id: runtimeId }, 15_000); } catch { /* said below */ }
|
|
201
|
+
throw new Error('the relay started but registered no live runtime; it was closed');
|
|
202
|
+
}
|
|
173
203
|
return { runtime, connection };
|
|
174
204
|
}
|
|
175
205
|
|
|
@@ -199,20 +229,19 @@ export class Relays {
|
|
|
199
229
|
async send(sender, senderName, receiver, message, messageId) {
|
|
200
230
|
const spec = this.spec(sender, senderName);
|
|
201
231
|
mkdirSync(spec.directory, { recursive: true });
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
232
|
+
const asked = performance.now();
|
|
233
|
+
try { return await this.#locked(spec.directory, () => this.#send(spec, receiver, message, messageId, asked)); }
|
|
234
|
+
finally { this.#cap(); }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async #send(spec, receiver, message, messageId, asked) {
|
|
208
238
|
try {
|
|
209
239
|
const queued = { to: receiver.socket ? `uds:${receiver.socket}` : receiver.name, message };
|
|
210
|
-
const steps = [], began =
|
|
240
|
+
const steps = [], began = asked;
|
|
211
241
|
let mark = began;
|
|
212
242
|
const step = (name) => { const now = performance.now(); steps.push(`${name} ${Math.round(now - mark)} ms`); mark = now; };
|
|
213
243
|
step('lock');
|
|
214
|
-
let { runtime, connection } = await this.#ensure(spec);
|
|
215
|
-
step('runtime (started or reused)');
|
|
244
|
+
let { runtime, connection } = await this.#ensure(spec, step);
|
|
216
245
|
this.#used(spec, connection);
|
|
217
246
|
rmSync(spec.receipt, { force: true });
|
|
218
247
|
writeFileSync(spec.queue, JSON.stringify(queued));
|
|
@@ -223,7 +252,7 @@ export class Relays {
|
|
|
223
252
|
const pid = this.#pid(spec, runtime);
|
|
224
253
|
// only a relay proven dead is started again; one alive but slow may still deliver
|
|
225
254
|
if (receipt.delivered || attempt === 1 || pid == null || alive(pid)) break;
|
|
226
|
-
try { ({ runtime, connection } = await this.#ensure(spec)); this.#used(spec, connection); } catch (error) { receipt = { delivered: false, detail: error.message }; break; }
|
|
255
|
+
try { ({ runtime, connection } = await this.#ensure(spec, step)); this.#used(spec, connection); } catch (error) { receipt = { delivered: false, detail: error.message }; break; }
|
|
227
256
|
rmSync(spec.receipt, { force: true });
|
|
228
257
|
}
|
|
229
258
|
rmSync(spec.queue, { force: true });
|
|
@@ -238,10 +267,6 @@ export class Relays {
|
|
|
238
267
|
} catch (error) {
|
|
239
268
|
rmSync(spec.queue, { force: true });
|
|
240
269
|
return { delivered: false, detail: error.message };
|
|
241
|
-
} finally {
|
|
242
|
-
release();
|
|
243
|
-
if (this.sending.get(spec.directory) === turn) this.sending.delete(spec.directory);
|
|
244
|
-
this.#cap();
|
|
245
270
|
}
|
|
246
271
|
}
|
|
247
272
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.90",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A machine daemon serving this machine's doors, and a local or remote Teams server for shared session discovery, OpenTelemetry ingestion and access to enrolled machines.",
|
|
6
6
|
"license": "MIT",
|