@volter/supercode-teams 0.2.107 → 0.2.110
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/connector/index.mjs +1 -1
- package/machine.mjs +31 -0
- package/package.json +1 -1
package/connector/index.mjs
CHANGED
|
@@ -141,7 +141,7 @@ export class TeamsConnector {
|
|
|
141
141
|
// large first sync slows down instead of failing its pass and restarting the connector.
|
|
142
142
|
if(response.status!==429||attempt>=5)break;// The server names the wait in error.details (wire.mjs validationFailure); a Retry-After header is the fallback.
|
|
143
143
|
const limited=await response.json().catch(()=>null),named=limited?.error?.details?.retry_after_seconds??limited?.error?.retry_after_seconds??response.headers?.get?.('retry-after'),asked=named==null||named===''?NaN:Number(named),seconds=Number.isFinite(asked)?Math.min(60,Math.max(0,asked)):1;
|
|
144
|
-
await new Promise((resolve,reject)=>{const stop=()=>{clearTimeout(timer);reject(this.abort.signal.reason);},timer=setTimeout(()=>{this.abort.signal.removeEventListener('abort',stop);resolve();},seconds*1000);this.abort.signal.addEventListener('abort',stop,{once:true});});}if(response.type==='opaqueredirect'||response.status>=300&&response.status<400)throw Object.assign(new Error('cross-origin redirects are refused'),{code:'invalid_request',status:400});const value=await response.json();if(!response.ok)throw Object.assign(new Error(value.error?.message??`HTTP ${response.status}`),value.error,{status:response.status});return value.data;}
|
|
144
|
+
await new Promise((resolve,reject)=>{const stop=()=>{clearTimeout(timer);reject(this.abort.signal.reason);},timer=setTimeout(()=>{this.abort.signal.removeEventListener('abort',stop);resolve();},seconds*1000);this.abort.signal.addEventListener('abort',stop,{once:true});});}if(response.type==='opaqueredirect'||response.status>=300&&response.status<400)throw Object.assign(new Error('cross-origin redirects are refused'),{code:'invalid_request',status:400});const value=await response.json().catch(()=>{throw Object.assign(new Error(`Teams ${method} ${path} returned HTTP ${response.status} (${response.headers.get('content-type') ?? 'unknown content type'}), expected JSON`),{code:'invalid_response',status:response.status});});if(!response.ok)throw Object.assign(new Error(value.error?.message??`HTTP ${response.status}`),value.error,{status:response.status});return value.data;}
|
|
145
145
|
async configuration(){const value=await this.request('GET',`/machines/${this.machineId}/configuration`);this.configurationValue=value;this.pruneStamps(value);return value;}
|
|
146
146
|
// A capture stamp belongs to one rule; once that rule is no longer active its stamps are dropped.
|
|
147
147
|
pruneStamps(configuration){
|
package/machine.mjs
CHANGED
|
@@ -183,6 +183,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
183
183
|
//. The launch reply does not wait for delivery — a cold TUI can take
|
|
184
184
|
// longer than the caller's own RPC timeout — so the wait lives here, where
|
|
185
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
|
|
186
187
|
this.inputInFlight = new Set(); // one input transaction per pane
|
|
187
188
|
this.pendingInitialInputs = new Map(); // launch key → { abort, task }
|
|
188
189
|
this.registerDoor('harness', (entry, stream, accept, refuse, index) => this.#serveHarness(index, entry, stream, accept, refuse));
|
|
@@ -219,6 +220,8 @@ export class TeamsMachine extends MachineCore {
|
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
async afterStop() {
|
|
223
|
+
for (const child of this.voiceProcesses.values()) child.kill('SIGTERM');
|
|
224
|
+
this.voiceProcesses.clear();
|
|
222
225
|
this.jobs?.stopAll();
|
|
223
226
|
this.stoppingMailWatch = true;
|
|
224
227
|
this.mailWatch?.kill('SIGTERM');
|
|
@@ -1159,6 +1162,34 @@ export class TeamsMachine extends MachineCore {
|
|
|
1159
1162
|
const act = (cap, target, outcome = 'ok', detail = null) => this.log.append({ principal, cap, target, outcome, detail });
|
|
1160
1163
|
|
|
1161
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
|
+
}
|
|
1162
1193
|
case 'describe':
|
|
1163
1194
|
return this.describe();
|
|
1164
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.110",
|
|
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",
|