@volter/supercode-teams 0.2.106 → 0.2.109
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 +36 -7
- package/package.json +1 -1
package/machine.mjs
CHANGED
|
@@ -51,12 +51,8 @@ import { JobOwner, executionCapabilities, streamProgram, serveWorkspace } from '
|
|
|
51
51
|
|
|
52
52
|
/** The preload every launched pane carries: it announces the ports a pane's node processes listen on. */
|
|
53
53
|
const PORTHOLE_HOOK = fileURLToPath(new URL('./porthole-hook.cjs', import.meta.url));
|
|
54
|
-
/** How long
|
|
55
|
-
*
|
|
56
|
-
* start anyone has reported (a first-run agent TUI fetching its config over a
|
|
57
|
-
* slow link); past it the input goes in regardless and the launch record says
|
|
58
|
-
* so, because silently dropping a caller's first message is worse than an
|
|
59
|
-
* imperfectly-timed one. */
|
|
54
|
+
/** How long launch input waits for a ready composer. At the deadline it stays
|
|
55
|
+
* undelivered; startup dialogs must never receive the queued message. */
|
|
60
56
|
const INITIAL_INPUT_READY_DEADLINE_MS = 90_000;
|
|
61
57
|
/** How often the readiness evidence is re-read while waiting. Same cadence the
|
|
62
58
|
* terminal controller polls its own composer at: fast enough that a ready pane
|
|
@@ -187,6 +183,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
187
183
|
//. The launch reply does not wait for delivery — a cold TUI can take
|
|
188
184
|
// longer than the caller's own RPC timeout — so the wait lives here, where
|
|
189
185
|
// `stop()` can abandon it rather than leave a timer running past the daemon.
|
|
186
|
+
this.voiceProcesses = new Map(); // session address -> optional voice pack child
|
|
190
187
|
this.inputInFlight = new Set(); // one input transaction per pane
|
|
191
188
|
this.pendingInitialInputs = new Map(); // launch key → { abort, task }
|
|
192
189
|
this.registerDoor('harness', (entry, stream, accept, refuse, index) => this.#serveHarness(index, entry, stream, accept, refuse));
|
|
@@ -223,6 +220,8 @@ export class TeamsMachine extends MachineCore {
|
|
|
223
220
|
}
|
|
224
221
|
|
|
225
222
|
async afterStop() {
|
|
223
|
+
for (const child of this.voiceProcesses.values()) child.kill('SIGTERM');
|
|
224
|
+
this.voiceProcesses.clear();
|
|
226
225
|
this.jobs?.stopAll();
|
|
227
226
|
this.stoppingMailWatch = true;
|
|
228
227
|
this.mailWatch?.kill('SIGTERM');
|
|
@@ -823,6 +822,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
823
822
|
async #awaitInputReady(session, contextKey, kind, signal) {
|
|
824
823
|
const composerKind = this.#paneKind(contextKey, session);
|
|
825
824
|
const until = Date.now() + INITIAL_INPUT_READY_DEADLINE_MS;
|
|
825
|
+
let readySince = null;
|
|
826
826
|
while (!signal.aborted && Date.now() < until) {
|
|
827
827
|
const capture = await Promise.resolve().then(() => this.host.capture(session.id, { preserveEscapes: true })).catch(() => '');
|
|
828
828
|
const unchangedMs = this.#screenUnchangedMs(session.id, capture, Date.now());
|
|
@@ -837,7 +837,8 @@ export class TeamsMachine extends MachineCore {
|
|
|
837
837
|
// never "not yet", or an unrecognized kind would wait out the whole
|
|
838
838
|
// deadline on a screen nobody is reading.
|
|
839
839
|
const composer = composerReadyForInput(composerKind, capture);
|
|
840
|
-
|
|
840
|
+
readySince = composer === true ? (readySince ?? Date.now()) : null;
|
|
841
|
+
if (composer === true && Date.now() - readySince >= INITIAL_INPUT_SETTLE_MS) return 'composer';
|
|
841
842
|
if (composer === null && unchangedMs >= INITIAL_INPUT_SETTLE_MS && (!blank || livenessOf(live.activeCommand) === 'waiting')) return 'settled';
|
|
842
843
|
await new Promise((resolve) => setTimeout(resolve, INITIAL_INPUT_POLL_MS));
|
|
843
844
|
}
|
|
@@ -1161,6 +1162,34 @@ export class TeamsMachine extends MachineCore {
|
|
|
1161
1162
|
const act = (cap, target, outcome = 'ok', detail = null) => this.log.append({ principal, cap, target, outcome, detail });
|
|
1162
1163
|
|
|
1163
1164
|
switch (method) {
|
|
1165
|
+
case 'voice.start': {
|
|
1166
|
+
if (!operator) throw Object.assign(new Error('starting a machine voice pack requires operator authority'), { code: 403 });
|
|
1167
|
+
if (typeof params.session !== 'string' || !params.session.startsWith('sc:') || typeof params.config !== 'string') throw new Error('voice.start needs session and config path');
|
|
1168
|
+
if (this.voiceProcesses.has(params.session)) return { running: true, session: params.session };
|
|
1169
|
+
const configPath = resolve(params.config);
|
|
1170
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
1171
|
+
if (config.session !== params.session) throw new Error('voice config must name the same session');
|
|
1172
|
+
if (config.adapter?.kind !== 'rh2') throw new Error('machine-owned voice needs a persistent meeting adapter; stdio runs in its caller');
|
|
1173
|
+
const voiceEntry = this.env.SUPERCODE_VOICE_ENTRY || fileURLToPath(new URL('../voice/bin/voice.mjs', import.meta.url));
|
|
1174
|
+
if (!existsSync(voiceEntry)) throw new Error('voice pack is not installed; configure SUPERCODE_VOICE_ENTRY');
|
|
1175
|
+
const child = spawn(process.execPath, [voiceEntry, 'run', '--config', configPath], { env: { ...this.env, SUPERCODE_BIN: this.supercodeBin, SUPERCODE_CALLER: params.session }, stdio: ['ignore', 'ignore', 'ignore'] });
|
|
1176
|
+
this.voiceProcesses.set(params.session, child);
|
|
1177
|
+
const ended = () => { if (this.voiceProcesses.get(params.session) === child) this.voiceProcesses.delete(params.session); };
|
|
1178
|
+
child.once('error', ended); child.once('exit', ended);
|
|
1179
|
+
await new Promise((resolve, reject) => { child.once('spawn', resolve); child.once('error', reject); });
|
|
1180
|
+
act(method, params.session);
|
|
1181
|
+
return { running: true, session: params.session, pid: child.pid };
|
|
1182
|
+
}
|
|
1183
|
+
case 'voice.stop': {
|
|
1184
|
+
if (!operator) throw Object.assign(new Error('stopping a machine voice pack requires operator authority'), { code: 403 });
|
|
1185
|
+
const child = this.voiceProcesses.get(params.session);
|
|
1186
|
+
child?.kill('SIGTERM'); act(method, params.session);
|
|
1187
|
+
return { stopping: Boolean(child), session: params.session };
|
|
1188
|
+
}
|
|
1189
|
+
case 'voice.list': {
|
|
1190
|
+
if (!operator) throw Object.assign(new Error('listing machine voice packs requires operator authority'), { code: 403 });
|
|
1191
|
+
return [...this.voiceProcesses].map(([session, child]) => ({ session, pid: child.pid }));
|
|
1192
|
+
}
|
|
1164
1193
|
case 'describe':
|
|
1165
1194
|
return this.describe();
|
|
1166
1195
|
case 'panes.list': {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.109",
|
|
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",
|