@volter/supercode-teams 0.2.105 → 0.2.106
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 +48 -34
- package/package.json +1 -1
package/machine.mjs
CHANGED
|
@@ -187,6 +187,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
187
187
|
//. The launch reply does not wait for delivery — a cold TUI can take
|
|
188
188
|
// longer than the caller's own RPC timeout — so the wait lives here, where
|
|
189
189
|
// `stop()` can abandon it rather than leave a timer running past the daemon.
|
|
190
|
+
this.inputInFlight = new Set(); // one input transaction per pane
|
|
190
191
|
this.pendingInitialInputs = new Map(); // launch key → { abort, task }
|
|
191
192
|
this.registerDoor('harness', (entry, stream, accept, refuse, index) => this.#serveHarness(index, entry, stream, accept, refuse));
|
|
192
193
|
this.registerDoor('terminal', (entry, stream, accept, refuse) => this.#attachTerminal(entry, stream, accept, refuse));
|
|
@@ -752,30 +753,16 @@ export class TeamsMachine extends MachineCore {
|
|
|
752
753
|
const task = (async () => {
|
|
753
754
|
const readyBy = await this.#awaitInputReady(session, contextKey, kind, abort.signal);
|
|
754
755
|
if (readyBy === 'aborted') return;
|
|
755
|
-
if (readyBy === 'pane-gone') {
|
|
756
|
+
if (readyBy === 'pane-gone' || readyBy === 'deadline') {
|
|
756
757
|
this.launches.record({ key, pane: contextKey, kind, by, status: 'ready', inputDelivered: false, inputReadyBy: readyBy });
|
|
757
|
-
act('launch', `pane:${contextKey}`, 'failed',
|
|
758
|
+
act('launch', `pane:${contextKey}`, 'failed', `first input undelivered: ${readyBy}`);
|
|
758
759
|
return;
|
|
759
760
|
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
try {
|
|
766
|
-
await this.host.sendInput(session.id, submission(input), { submitMode: 'single' });
|
|
767
|
-
} catch (error) {
|
|
768
|
-
this.launches.record({ key, pane: contextKey, kind, by, status: 'ready', inputDelivered: false, inputReadyBy: readyBy, inputError: error.message });
|
|
769
|
-
act('launch', `pane:${contextKey}`, 'failed', `first input undelivered: ${error.code ?? error.message}`);
|
|
770
|
-
return;
|
|
771
|
-
}
|
|
772
|
-
// `inputDeliveredLate` is the deadline's own confession: the pane never
|
|
773
|
-
// showed us it was ready, and the message went in on a screen nobody
|
|
774
|
-
// vouched for. A launch that reads `ready` without it was given its
|
|
775
|
-
// message by an input box that existed.
|
|
776
|
-
const late = readyBy === 'deadline';
|
|
777
|
-
this.launches.record({ key, pane: contextKey, kind, by, status: 'ready', inputDelivered: true, inputReadyBy: readyBy, ...(late ? { inputDeliveredLate: true } : {}) });
|
|
778
|
-
act('launch', `pane:${contextKey}`, 'ok', `first input delivered (${readyBy})`);
|
|
761
|
+
const result = await this.#deliverPaneText(session, contextKey, input, { signal: abort.signal });
|
|
762
|
+
this.launches.record({ key, pane: contextKey, kind, by, status: 'ready', inputDelivered: result.delivered,
|
|
763
|
+
inputReadyBy: readyBy, ...(!result.delivered ? { inputError: result.reason } : {}) });
|
|
764
|
+
act('launch', `pane:${contextKey}`, result.delivered ? 'ok' : 'failed',
|
|
765
|
+
result.delivered ? `first input delivered (${readyBy})` : `first input unconfirmed: ${result.reason}`);
|
|
779
766
|
})().catch((error) => {
|
|
780
767
|
// Nothing above is expected to throw; if something does, the launch record
|
|
781
768
|
// is where it is answerable, not a crashed daemon.
|
|
@@ -786,6 +773,24 @@ export class TeamsMachine extends MachineCore {
|
|
|
786
773
|
this.pendingInitialInputs.set(key, { abort, task });
|
|
787
774
|
}
|
|
788
775
|
|
|
776
|
+
async #deliverPaneText(session, contextKey, text, options = {}) {
|
|
777
|
+
if (this.inputInFlight.has(session.id)) return { delivered: false, reason: 'input-in-flight' };
|
|
778
|
+
this.inputInFlight.add(session.id);
|
|
779
|
+
try {
|
|
780
|
+
const kind = this.#paneKind(contextKey, session);
|
|
781
|
+
if (kind === 'claude-code' || kind === 'codex') {
|
|
782
|
+
if (typeof terminal.deliverComposerInput !== 'function') return { delivered: false, reason: 'terminal-package-too-old' };
|
|
783
|
+
return await terminal.deliverComposerInput(this.host, session.id, kind, submission(text, false), options);
|
|
784
|
+
}
|
|
785
|
+
const delivery = await this.host.sendInput(session.id, submission(text), { ...options, submitMode: 'single' });
|
|
786
|
+
return { delivered: true, delivery };
|
|
787
|
+
} catch (error) {
|
|
788
|
+
return { delivered: false, reason: error?.message ?? String(error) };
|
|
789
|
+
} finally {
|
|
790
|
+
this.inputInFlight.delete(session.id);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
789
794
|
/** The program a pane runs, as the terminal package names kinds: what it was
|
|
790
795
|
* launched as, else what its foreground command is. */
|
|
791
796
|
#paneKind(contextKey, session) {
|
|
@@ -813,13 +818,13 @@ export class TeamsMachine extends MachineCore {
|
|
|
813
818
|
* the foreground command (`awaiting_me`'s evidence: a prompt,
|
|
814
819
|
* turn over).
|
|
815
820
|
* deadline neither, for `INITIAL_INPUT_READY_DEADLINE_MS`. The caller
|
|
816
|
-
*
|
|
821
|
+
* leaves input undelivered without typing into that screen.
|
|
817
822
|
*/
|
|
818
823
|
async #awaitInputReady(session, contextKey, kind, signal) {
|
|
819
|
-
const composerKind =
|
|
824
|
+
const composerKind = this.#paneKind(contextKey, session);
|
|
820
825
|
const until = Date.now() + INITIAL_INPUT_READY_DEADLINE_MS;
|
|
821
826
|
while (!signal.aborted && Date.now() < until) {
|
|
822
|
-
const capture = await this
|
|
827
|
+
const capture = await Promise.resolve().then(() => this.host.capture(session.id, { preserveEscapes: true })).catch(() => '');
|
|
823
828
|
const unchangedMs = this.#screenUnchangedMs(session.id, capture, Date.now());
|
|
824
829
|
const blank = capture.trim() === '';
|
|
825
830
|
// A blank screen is the one reading that can mean the pane is GONE rather
|
|
@@ -1305,13 +1310,22 @@ export class TeamsMachine extends MachineCore {
|
|
|
1305
1310
|
const session = await this.#paneByContext(params.pane);
|
|
1306
1311
|
if (!session) throw Object.assign(new Error(`no pane with context ${params.pane}`), { code: 404 });
|
|
1307
1312
|
if (this.suspended.has(params.pane)) throw Object.assign(new Error(`pane ${params.pane} is suspended: remote input is refused until resume`), { code: 423 });
|
|
1308
|
-
//
|
|
1309
|
-
//
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1313
|
+
// Named keys and no-submit input are raw terminal writes. A submitted
|
|
1314
|
+
// Claude/Codex turn needs evidence from the composer before claiming delivery.
|
|
1315
|
+
const deadlineMs = Date.now() + Math.min(Number(params.deadlineMs) || 10_000, 30_000);
|
|
1316
|
+
if (this.inputInFlight.has(session.id)) return { delivered: false, reason: 'input-in-flight' };
|
|
1317
|
+
let result;
|
|
1318
|
+
if (params.keys || params.submit === false) {
|
|
1319
|
+
this.inputInFlight.add(session.id);
|
|
1320
|
+
try {
|
|
1321
|
+
const delivery = await this.host.sendInput(session.id, params.keys ? String(params.text ?? '') : submission(params.text, false), { submitMode: 'single', deadlineMs });
|
|
1322
|
+
result = { delivered: true, delivery };
|
|
1323
|
+
} finally { this.inputInFlight.delete(session.id); }
|
|
1324
|
+
} else {
|
|
1325
|
+
result = await this.#deliverPaneText(session, params.pane, params.text, { deadlineMs });
|
|
1326
|
+
}
|
|
1327
|
+
act('control', `pane:${params.pane}`, result.delivered ? 'input' : 'failed', result.reason);
|
|
1328
|
+
return result;
|
|
1315
1329
|
}
|
|
1316
1330
|
case 'panes.turn': {
|
|
1317
1331
|
// A user's own turn, as its own submitted turn: typed only into an empty
|
|
@@ -1332,9 +1346,9 @@ export class TeamsMachine extends MachineCore {
|
|
|
1332
1346
|
if (first !== false || second !== false) {
|
|
1333
1347
|
return { delivered: false, reason: first === true || second === true ? 'draft' : 'no-composer' };
|
|
1334
1348
|
}
|
|
1335
|
-
const
|
|
1336
|
-
act('control', `pane:${params.pane}`, 'input');
|
|
1337
|
-
return
|
|
1349
|
+
const result = await this.#deliverPaneText(session, params.pane, params.text, { deadlineMs: Date.now() + 10_000 });
|
|
1350
|
+
act('control', `pane:${params.pane}`, result.delivered ? 'input' : 'failed', result.reason);
|
|
1351
|
+
return result;
|
|
1338
1352
|
}
|
|
1339
1353
|
case 'panes.capture': {
|
|
1340
1354
|
need('observe', `pane:${params.pane}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.106",
|
|
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",
|