@volter/supercode-teams 0.3.139 → 0.3.141
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/machine.mjs +44 -13
- package/package.json +1 -1
package/machine.mjs
CHANGED
|
@@ -194,17 +194,20 @@ async function claudeTranscriptHolds(projectsDirs, sessionId, landed) {
|
|
|
194
194
|
return false;
|
|
195
195
|
}
|
|
196
196
|
/**
|
|
197
|
-
* The doors whose work waits on a session, with the bound each answers within (the node door's own is 30 s): a send or
|
|
197
|
+
* The doors whose work waits on a session or mailbox, with the bound each answers within (the node door's own is 30 s): a send or
|
|
198
198
|
* a hand-over waits on its receiver's door (a relay's receipt, up to 90 s; another machine's door, MAIL_SEND_MS), a
|
|
199
199
|
* delegation or a declaration that starts a session waits for it to appear (up to a minute). Past its bound a call is
|
|
200
|
-
* answered 504, which its client reads as unanswered: the work may still finish.
|
|
200
|
+
* answered 504, which its client reads as unanswered: the work may still finish. A board's mailbox long poll gets
|
|
201
|
+
* its maximum requested wait plus time to return the answer, rather than the ordinary door's shorter bound.
|
|
201
202
|
*/
|
|
203
|
+
const BOARD_WAIT_MAX_SECONDS = 120;
|
|
202
204
|
const DOOR_BOUNDS_MS = {
|
|
203
205
|
'harness.v1.mail.send': 170_000,
|
|
204
206
|
'harness.v1.mail.message': 170_000,
|
|
205
207
|
'harness.v1.mail.handover': 140_000,
|
|
206
208
|
'harness.v1.mail.delegate': 110_000,
|
|
207
209
|
'harness.v1.agent.declare': 110_000,
|
|
210
|
+
'harness.v1.mail.board_wait': (BOARD_WAIT_MAX_SECONDS + 10) * 1000,
|
|
208
211
|
};
|
|
209
212
|
/** How many typed lines' keys a machine remembers; a server retries a line for minutes, not thousands of lines. */
|
|
210
213
|
const TURN_KEYS_KEPT = 1000;
|
|
@@ -2387,6 +2390,13 @@ export class TeamsMachine extends MachineCore {
|
|
|
2387
2390
|
*/
|
|
2388
2391
|
#deliverInitialInput({ key, contextKey, session, kind, by, input, act, nativeInput, executable = {}, resumed = {} }) {
|
|
2389
2392
|
if (this.pendingInitialInputs.has(key)) return;
|
|
2393
|
+
// Input confirmation can finish after the child. Keep the launch's latest
|
|
2394
|
+
// status and completion; a late input receipt must not make it ready again.
|
|
2395
|
+
const recordInput = facts => {
|
|
2396
|
+
const launch = this.launches.find(key);
|
|
2397
|
+
if (launch?.pane !== contextKey) return;
|
|
2398
|
+
this.launches.record({ ...launch, ...facts, status: launch.status });
|
|
2399
|
+
};
|
|
2390
2400
|
const abort = new AbortController();
|
|
2391
2401
|
const task = (async () => {
|
|
2392
2402
|
let nativeReadyBy = 'native-prompt';
|
|
@@ -2396,7 +2406,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
2396
2406
|
const result = nativeReadyBy === 'pane-gone' || nativeReadyBy === 'deadline'
|
|
2397
2407
|
? { delivered: false, refused: true, reason: nativeReadyBy }
|
|
2398
2408
|
: await this.#deliverClaudeInitialInput(session, contextKey, nativeInput, abort.signal);
|
|
2399
|
-
|
|
2409
|
+
recordInput({ key, pane: contextKey, kind, by, ...resumed, ...executable, inputDelivered: result.delivered,
|
|
2400
2410
|
inputReadyBy: nativeReadyBy, ...(result.delivered ? { inputReceipt: result.receipt } : { inputError: result.reason }),
|
|
2401
2411
|
inputRefused: result.refused === true, inputPending: result.queued === true,
|
|
2402
2412
|
...(result.native_session ? { native_session: result.native_session } : {}) });
|
|
@@ -2412,7 +2422,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
2412
2422
|
const typed = nativeReadyBy === 'pane-gone' || nativeReadyBy === 'deadline' ? { delivered: false, reason: nativeReadyBy }
|
|
2413
2423
|
: await this.#typeCodexFirstInput(session, input, abort.signal);
|
|
2414
2424
|
if (!typed.delivered) {
|
|
2415
|
-
|
|
2425
|
+
recordInput({ key, pane: contextKey, kind, by, ...resumed, ...executable, inputDelivered: false,
|
|
2416
2426
|
inputReadyBy: nativeReadyBy, inputError: typed.reason });
|
|
2417
2427
|
act('launch', `pane:${contextKey}`, 'failed', `first input undelivered: ${typed.reason}`);
|
|
2418
2428
|
return;
|
|
@@ -2421,7 +2431,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
2421
2431
|
if (nativeInput) {
|
|
2422
2432
|
const result = await confirmNativeInitialInput(nativeInput, { signal: abort.signal,
|
|
2423
2433
|
deadlineMs: Date.now() + INITIAL_INPUT_READY_DEADLINE_MS, live: async () => Boolean(await this.#paneByContext(contextKey)) });
|
|
2424
|
-
|
|
2434
|
+
recordInput({ key, pane: contextKey, kind, by, ...resumed, ...executable, inputDelivered: result.delivered,
|
|
2425
2435
|
inputReadyBy: nativeReadyBy, ...(result.delivered ? { inputReceipt: result.receipt } : { inputError: result.reason }),
|
|
2426
2436
|
...(result.native_session ? { native_session: result.native_session } : {}) });
|
|
2427
2437
|
act('launch', `pane:${contextKey}`, result.delivered ? 'ok' : result.queued ? 'input-pending' : 'failed',
|
|
@@ -2431,19 +2441,19 @@ export class TeamsMachine extends MachineCore {
|
|
|
2431
2441
|
const readyBy = await this.#awaitInputReady(session, contextKey, kind, abort.signal);
|
|
2432
2442
|
if (readyBy === 'aborted') return;
|
|
2433
2443
|
if (readyBy === 'pane-gone' || readyBy === 'deadline') {
|
|
2434
|
-
|
|
2444
|
+
recordInput({ key, pane: contextKey, kind, by, ...resumed, ...executable, inputDelivered: false, inputReadyBy: readyBy });
|
|
2435
2445
|
act('launch', `pane:${contextKey}`, 'failed', `first input undelivered: ${readyBy}`);
|
|
2436
2446
|
return;
|
|
2437
2447
|
}
|
|
2438
2448
|
const result = await this.#deliverPaneText(session, contextKey, input, { signal: abort.signal });
|
|
2439
|
-
|
|
2449
|
+
recordInput({ key, pane: contextKey, kind, by, ...resumed, ...executable, inputDelivered: result.delivered,
|
|
2440
2450
|
inputReadyBy: readyBy, ...(!result.delivered ? { inputError: result.reason } : {}) });
|
|
2441
2451
|
act('launch', `pane:${contextKey}`, result.delivered ? 'ok' : result.queued ? 'input-pending' : 'failed',
|
|
2442
2452
|
result.delivered ? `first input delivered (${readyBy})` : `first input unconfirmed: ${result.reason}`);
|
|
2443
2453
|
})().catch((error) => {
|
|
2444
2454
|
// Nothing above is expected to throw; if something does, the launch record
|
|
2445
2455
|
// is where it is answerable, not a crashed daemon.
|
|
2446
|
-
|
|
2456
|
+
recordInput({ key, pane: contextKey, kind, by, ...resumed, ...executable, inputDelivered: false, inputError: error?.message ?? String(error) });
|
|
2447
2457
|
}).finally(() => {
|
|
2448
2458
|
if (this.pendingInitialInputs.get(key)?.task === task) this.pendingInitialInputs.delete(key);
|
|
2449
2459
|
});
|
|
@@ -3199,7 +3209,8 @@ export class TeamsMachine extends MachineCore {
|
|
|
3199
3209
|
cancelled_at: found.cancelled_at ?? null,
|
|
3200
3210
|
live: Boolean(session && !session.dead), label: session?.label ?? null, activeCommand: session?.activeCommand ?? null,
|
|
3201
3211
|
exit_output: found.pane && (!session || session.dead) ? paneExitOutput(found.pane) : null,
|
|
3202
|
-
exit:
|
|
3212
|
+
exit: found.completion ?? (found.status === 'finished' ? { failed: false, how: 'status 0' }
|
|
3213
|
+
: session?.dead ? deadPaneExit(session) : found.pane && !session ? paneExit(found.pane) : null),
|
|
3203
3214
|
...initialInputFacts(found),
|
|
3204
3215
|
};
|
|
3205
3216
|
}
|
|
@@ -3499,6 +3510,11 @@ export class TeamsMachine extends MachineCore {
|
|
|
3499
3510
|
let existing = this.launches.find(key);
|
|
3500
3511
|
while (existing) {
|
|
3501
3512
|
if (existing.status === 'cancelled') throw Object.assign(new Error(`launch key ${key} was cancelled before issuance; use a new key`), { code: 409, reason: 'launch_key_cancelled' });
|
|
3513
|
+
// Finished itself records an observed clean exit, including older
|
|
3514
|
+
// journals that did not keep a separate completion field.
|
|
3515
|
+
if (existing.status === 'finished') {
|
|
3516
|
+
return { key, pane: existing.pane, reused: true, status: 'finished', exit: existing.completion?.how ?? 'status 0' };
|
|
3517
|
+
}
|
|
3502
3518
|
if (existing.status === 'issued') return { key, pane: existing.pane, reused: true, status: existing.status };
|
|
3503
3519
|
if (existing.status === 'ready') {
|
|
3504
3520
|
const session = await this.#paneByContext(existing.pane);
|
|
@@ -3508,6 +3524,14 @@ export class TeamsMachine extends MachineCore {
|
|
|
3508
3524
|
this.#recoverClaudeInitialInputs([session], key);
|
|
3509
3525
|
return { key, pane: existing.pane, reused: true, status: existing.status };
|
|
3510
3526
|
}
|
|
3527
|
+
// The child can finish after creation but before the original
|
|
3528
|
+
// caller's settlement read. Retain its observed exit before a
|
|
3529
|
+
// concurrent or restarted caller can issue this key again.
|
|
3530
|
+
const exit = session?.dead ? deadPaneExit(session) : paneExit(existing.pane);
|
|
3531
|
+
if (exit?.failed === false) {
|
|
3532
|
+
this.launches.record({ ...existing, status: 'finished', completion: exit });
|
|
3533
|
+
return { key, pane: existing.pane, reused: true, status: 'finished', exit: exit.how };
|
|
3534
|
+
}
|
|
3511
3535
|
}
|
|
3512
3536
|
break;
|
|
3513
3537
|
}
|
|
@@ -3558,7 +3582,13 @@ export class TeamsMachine extends MachineCore {
|
|
|
3558
3582
|
// The agent the launch's environment names is kept with the launch: the pane's session is bound to it once the
|
|
3559
3583
|
// session is seen in the pane (sessionBindings).
|
|
3560
3584
|
const agent = launchAgent(params.env);
|
|
3561
|
-
|
|
3585
|
+
// createSession can return a retained exit descriptor with no pane.
|
|
3586
|
+
// Its first journal write must already contain that completion, before
|
|
3587
|
+
// any title/input/settlement await lets another caller see the key.
|
|
3588
|
+
const createdExit = session.dead ? deadPaneExit(session) : null;
|
|
3589
|
+
this.launches.record({ key, pane: contextKey, kind: params.kind ?? null, by, ...resumed, ...executable,
|
|
3590
|
+
status: createdExit ? createdExit.failed ? 'exited' : 'finished' : 'ready',
|
|
3591
|
+
...(createdExit?.failed === false ? { completion: createdExit } : {}), ...(agent ? { agent } : {}) });
|
|
3562
3592
|
if (params.kind === 'codex' && (params.arguments ?? []).includes('--dangerously-bypass-hook-trust')) this.#settleCodexStartup(key, contextKey, session, act);
|
|
3563
3593
|
act('launch', `pane:${contextKey}`, 'ok', params.kind ?? params.program);
|
|
3564
3594
|
if (typeof params.name === 'string' && params.name.trim()) {
|
|
@@ -3582,9 +3612,10 @@ export class TeamsMachine extends MachineCore {
|
|
|
3582
3612
|
const settled = await this.#paneByContext(contextKey);
|
|
3583
3613
|
if (!settled || settled.dead) {
|
|
3584
3614
|
// Finished (it ran to a clean end) is an answer; exited (it failed at once) is the caller's error.
|
|
3585
|
-
const exit = settled?.dead ? deadPaneExit(settled) : paneExit(contextKey);
|
|
3615
|
+
const exit = settled?.dead ? deadPaneExit(settled) : session.dead ? deadPaneExit(session) : paneExit(contextKey);
|
|
3586
3616
|
const status = exit && !exit.failed ? 'finished' : 'exited';
|
|
3587
|
-
this.launches.record({ key, pane: contextKey, kind: params.kind ?? null, by, ...resumed, ...executable, status,
|
|
3617
|
+
this.launches.record({ ...this.launches.find(key), key, pane: contextKey, kind: params.kind ?? null, by, ...resumed, ...executable, status,
|
|
3618
|
+
...(exit && !exit.failed ? { completion: exit } : {}), error: exit?.failed ? exit.how : undefined });
|
|
3588
3619
|
this.#panesChanged();
|
|
3589
3620
|
return { key, pane: contextKey, reused: false, status, exit: exit?.how ?? null };
|
|
3590
3621
|
}
|
|
@@ -4133,7 +4164,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
4133
4164
|
const board = new RegExp(`^sc:${machineName(this.env).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}:board:[^:\\s]+$`);
|
|
4134
4165
|
if (method === 'harness.v1.mail.board_wait') {
|
|
4135
4166
|
const boards = (Array.isArray(params.boards) ? params.boards : []).filter((address) => typeof address === 'string' && board.test(address)).slice(0, 256);
|
|
4136
|
-
const seconds = Math.min(
|
|
4167
|
+
const seconds = Math.min(BOARD_WAIT_MAX_SECONDS, Math.max(0, Number.isFinite(Number(params.timeout)) ? Math.floor(Number(params.timeout)) : 60));
|
|
4137
4168
|
const waiting = () => boards.filter((address) => this.mailStore.hasClaimable(address));
|
|
4138
4169
|
let woken = waiting();
|
|
4139
4170
|
if (!woken.length && boards.length) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.141",
|
|
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",
|