@volter/supercode-teams 0.2.144 → 0.2.146
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 -2
- package/native-codex-pane.mjs +27 -5
- package/package.json +3 -3
- package/worker/index.mjs +3 -3
package/machine.mjs
CHANGED
|
@@ -198,6 +198,8 @@ export class TeamsMachine extends MachineCore {
|
|
|
198
198
|
// `stop()` can abandon it rather than leave a timer running past the daemon.
|
|
199
199
|
this.voiceProcesses = new Map(); // session address -> optional voice pack child
|
|
200
200
|
this.inputInFlight = new Set(); // one input transaction per pane
|
|
201
|
+
this.inputRepairsPath = join(this.home, 'input-repairs.json');
|
|
202
|
+
this.inputRepairs = readJson(this.inputRepairsPath, {});
|
|
201
203
|
this.pendingInitialInputs = new Map(); // launch key → { abort, task }
|
|
202
204
|
this.pendingStartups = new Map();
|
|
203
205
|
this.registerDoor('harness', (entry, stream, accept, refuse, index) => this.#serveHarness(index, entry, stream, accept, refuse));
|
|
@@ -223,9 +225,12 @@ export class TeamsMachine extends MachineCore {
|
|
|
223
225
|
|
|
224
226
|
async afterListen() {
|
|
225
227
|
this.#startMailWatch();
|
|
228
|
+
if(Object.keys(this.inputRepairs).length)this.#watchInputRepairs();
|
|
226
229
|
}
|
|
227
230
|
|
|
228
231
|
async beforeClose() {
|
|
232
|
+
clearInterval(this.inputRepairTimer);this.inputRepairTimer=null;
|
|
233
|
+
await this.inputRepairWork;
|
|
229
234
|
clearInterval(this.endWatch); this.endWatch = null;
|
|
230
235
|
// A launch still waiting for its pane to be ready is abandoned, not hurried:
|
|
231
236
|
// this daemon is going away, and shoving a message at a half-booted TUI on the
|
|
@@ -237,6 +242,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
237
242
|
this.pendingStartups.clear();
|
|
238
243
|
await Promise.allSettled([...this.pendingInitialInputs.values()].map(({ task }) => task));
|
|
239
244
|
this.pendingInitialInputs.clear();
|
|
245
|
+
clearInterval(this.inputRepairTimer);this.inputRepairTimer=null;
|
|
240
246
|
}
|
|
241
247
|
|
|
242
248
|
async afterStop() {
|
|
@@ -886,13 +892,21 @@ export class TeamsMachine extends MachineCore {
|
|
|
886
892
|
}
|
|
887
893
|
|
|
888
894
|
async #deliverPaneText(session, contextKey, text, options = {}) {
|
|
895
|
+
if(this.inputRepairs[contextKey])return {delivered:false,reason:'input-cleanup-pending',cleanup_required:true};
|
|
889
896
|
if (this.inputInFlight.has(session.id)) return { delivered: false, reason: 'input-in-flight' };
|
|
890
897
|
this.inputInFlight.add(session.id);
|
|
891
898
|
try {
|
|
892
899
|
const kind = this.#paneKind(contextKey, session);
|
|
893
900
|
if (kind === 'claude-code' || kind === 'codex') {
|
|
894
901
|
if (typeof terminal.deliverComposerInput !== 'function') return { delivered: false, reason: 'terminal-package-too-old' };
|
|
895
|
-
|
|
902
|
+
const result=await terminal.deliverComposerInput(this.host, session.id, kind, submission(text, false), options);
|
|
903
|
+
if(result.cleanup_required){
|
|
904
|
+
this.inputRepairs[contextKey]={session:session.id,kind,text:result.cleanup_remaining??submission(text,false),at:Date.now(),reason:result.cleanup_reason};
|
|
905
|
+
writeJson(this.inputRepairsPath,this.inputRepairs);this.#watchInputRepairs();
|
|
906
|
+
this.log.append({principal:null,cap:'control',target:`pane:${contextKey}`,outcome:'input-cleanup-pending',detail:result.cleanup_reason});
|
|
907
|
+
}
|
|
908
|
+
const {cleanup_remaining,...receipt}=result;
|
|
909
|
+
return receipt;
|
|
896
910
|
}
|
|
897
911
|
const delivery = await this.host.sendInput(session.id, submission(text), { ...options, submitMode: 'single' });
|
|
898
912
|
return { delivered: true, delivery };
|
|
@@ -903,6 +917,38 @@ export class TeamsMachine extends MachineCore {
|
|
|
903
917
|
}
|
|
904
918
|
}
|
|
905
919
|
|
|
920
|
+
#watchInputRepairs() {
|
|
921
|
+
if(this.inputRepairTimer)return;
|
|
922
|
+
this.inputRepairTimer=setInterval(()=>{
|
|
923
|
+
if(this.inputRepairWork)return;
|
|
924
|
+
this.inputRepairWork=this.#repairInputs().catch(error=>this.emit('mail-watch-stderr',`input cleanup: ${error.message}`)).finally(()=>{this.inputRepairWork=null;});
|
|
925
|
+
},2000);
|
|
926
|
+
this.inputRepairTimer.unref?.();
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
async #repairInputs() {
|
|
930
|
+
for(const [pane,repair] of Object.entries(this.inputRepairs)) {
|
|
931
|
+
if(this.inputInFlight.has(repair.session)||this.suspended.has(pane))continue;
|
|
932
|
+
const session=await this.#paneByContext(pane);
|
|
933
|
+
if(!session||session.id!==repair.session||session.dead){
|
|
934
|
+
delete this.inputRepairs[pane];writeJson(this.inputRepairsPath,this.inputRepairs);
|
|
935
|
+
this.log.append({principal:null,cap:'control',target:`pane:${pane}`,outcome:'input-cleanup-pane-gone'});continue;
|
|
936
|
+
}
|
|
937
|
+
if(!terminal.restoreComposerInput)continue;
|
|
938
|
+
this.inputInFlight.add(session.id);
|
|
939
|
+
try {
|
|
940
|
+
const result=await terminal.restoreComposerInput(this.host,session.id,repair.kind,repair.text);
|
|
941
|
+
if(result.restored){
|
|
942
|
+
delete this.inputRepairs[pane];writeJson(this.inputRepairsPath,this.inputRepairs);
|
|
943
|
+
this.log.append({principal:null,cap:'control',target:`pane:${pane}`,outcome:'input-cleanup-restored'});
|
|
944
|
+
}else if(result.remaining&&result.remaining!==repair.text){
|
|
945
|
+
repair.text=result.remaining;repair.reason=result.reason;writeJson(this.inputRepairsPath,this.inputRepairs);
|
|
946
|
+
}
|
|
947
|
+
}finally{this.inputInFlight.delete(session.id);}
|
|
948
|
+
}
|
|
949
|
+
if(!Object.keys(this.inputRepairs).length){clearInterval(this.inputRepairTimer);this.inputRepairTimer=null;}
|
|
950
|
+
}
|
|
951
|
+
|
|
906
952
|
/** The program a pane runs, as the terminal package names kinds: what it was
|
|
907
953
|
* launched as, else what its foreground command is. */
|
|
908
954
|
#paneKind(contextKey, session) {
|
|
@@ -1338,7 +1384,7 @@ export class TeamsMachine extends MachineCore {
|
|
|
1338
1384
|
// `title` is what a viewer should show: the override if one is set, else the tmux label.
|
|
1339
1385
|
return rows.filter((row) => row.session.id && shown(row.session.contextKey)).map(({ session: s, attention, rank, notify, evidence }) => ({
|
|
1340
1386
|
pane: s.contextKey, dead: Boolean(s.dead), label: s.label, title: titles[`pane:${s.contextKey}`] ?? s.label, cwd: s.cwd, owned: s.owned, activeCommand: s.activeCommand, pid: s.rootPid ?? null, size: s.size, suspended: this.suspended.has(s.contextKey),
|
|
1341
|
-
attention, rank, notify, evidence,
|
|
1387
|
+
attention, rank, notify, evidence, input_cleanup: this.inputRepairs[s.contextKey] ? {pending:true,at:this.inputRepairs[s.contextKey].at,reason:this.inputRepairs[s.contextKey].reason} : null,
|
|
1342
1388
|
}));
|
|
1343
1389
|
}
|
|
1344
1390
|
case 'launch': {
|
package/native-codex-pane.mjs
CHANGED
|
@@ -11,14 +11,36 @@ const seed = JSON.parse(readFileSync(path, 'utf8'));
|
|
|
11
11
|
const directory = mkdtempSync(join(tmpdir(), 'sc-codex-'));
|
|
12
12
|
const socketPath = join(directory, 'native.sock');
|
|
13
13
|
const endpoint = `unix://${socketPath}`;
|
|
14
|
-
// Configuration is evaluated by the native server.
|
|
15
|
-
//
|
|
14
|
+
// Configuration is evaluated by the native server. Remote resume/fork refuses
|
|
15
|
+
// permission overrides on the TUI command: translate those existing choices
|
|
16
|
+
// into server configuration without changing the requested policy.
|
|
16
17
|
const config = [];
|
|
18
|
+
const tuiArguments = [];
|
|
19
|
+
const continuing = seed.arguments.includes('resume') || seed.arguments.includes('fork');
|
|
20
|
+
const writableRoots = [];
|
|
17
21
|
for (let index = 0; index < seed.arguments.length; index += 1) {
|
|
18
22
|
const arg = seed.arguments[index];
|
|
19
|
-
if (
|
|
20
|
-
|
|
23
|
+
if (continuing && ['--dangerously-bypass-approvals-and-sandbox', '--yolo'].includes(arg)) {
|
|
24
|
+
config.push('-c', 'approval_policy="never"', '-c', 'sandbox_mode="danger-full-access"');
|
|
25
|
+
} else if (continuing && arg === '--full-auto') {
|
|
26
|
+
config.push('-c', 'approval_policy="on-request"', '-c', 'sandbox_mode="workspace-write"');
|
|
27
|
+
} else if (continuing && ['-a', '--ask-for-approval', '-s', '--sandbox', '--add-dir'].includes(arg)) {
|
|
28
|
+
const value = seed.arguments[++index];
|
|
29
|
+
if (value === undefined) throw new Error(`${arg} requires a value`);
|
|
30
|
+
if (arg === '--add-dir') writableRoots.push(value);
|
|
31
|
+
else config.push('-c', `${['-a','--ask-for-approval'].includes(arg)?'approval_policy':'sandbox_mode'}=${JSON.stringify(value)}`);
|
|
32
|
+
} else if (continuing && /^--(?:ask-for-approval|sandbox|add-dir)=/.test(arg)) {
|
|
33
|
+
const at=arg.indexOf('='), name=arg.slice(0,at), value=arg.slice(at+1);
|
|
34
|
+
if(name==='--add-dir')writableRoots.push(value);
|
|
35
|
+
else config.push('-c', `${name==='--sandbox'?'sandbox_mode':'approval_policy'}=${JSON.stringify(value)}`);
|
|
36
|
+
} else {
|
|
37
|
+
tuiArguments.push(arg);
|
|
38
|
+
if (['-c', '--config', '--enable', '--disable'].includes(arg)) {
|
|
39
|
+
const value=seed.arguments[++index];config.push(arg,value);tuiArguments.push(value);
|
|
40
|
+
} else if (/^--(?:config|enable|disable)=/.test(arg)) config.push(arg);
|
|
41
|
+
}
|
|
21
42
|
}
|
|
43
|
+
if(writableRoots.length)config.push('-c',`sandbox_workspace_write.writable_roots=${JSON.stringify(writableRoots)}`);
|
|
22
44
|
const server = spawn(seed.program, ['app-server', '--listen', endpoint, ...config], { stdio: ['ignore', 'ignore', 'inherit'] });
|
|
23
45
|
let tui;
|
|
24
46
|
let ended = false;
|
|
@@ -38,7 +60,7 @@ try {
|
|
|
38
60
|
const staged = `${path}.${process.pid}`;
|
|
39
61
|
writeFileSync(staged, JSON.stringify({ ...seed, socketPath, ownerPid: process.pid }), { mode: 0o600 });
|
|
40
62
|
renameSync(staged, path);
|
|
41
|
-
const args = [...
|
|
63
|
+
const args = [...tuiArguments, '--remote', endpoint];
|
|
42
64
|
if (!args.some((arg) => arg === '-C' || arg === '--cd' || arg.startsWith('--cd='))) args.push('--cd', process.cwd());
|
|
43
65
|
if (seed.initialInput) args.push('--', seed.initialInput);
|
|
44
66
|
tui = spawn(seed.program, args, { stdio: 'inherit' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/supercode-teams",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.146",
|
|
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",
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
"@homebridge/node-pty-prebuilt-multiarch": "^0.14.1",
|
|
61
61
|
"@volter/supercode-frontend": "^0.2.2",
|
|
62
62
|
"@volter/supercode-harness-sdk": "^0.3.43",
|
|
63
|
-
"@volter/supercode-terminal": "^0.2.
|
|
63
|
+
"@volter/supercode-terminal": "^0.2.54",
|
|
64
64
|
"@volter/identity": "^0.4.1",
|
|
65
|
-
"@volter/teams": "^0.1.
|
|
65
|
+
"@volter/teams": "^0.1.58",
|
|
66
66
|
"@xterm/addon-serialize": "^0.14.0",
|
|
67
67
|
"@xterm/headless": "^6.0.0",
|
|
68
68
|
"protobufjs": "8.8.0",
|
package/worker/index.mjs
CHANGED
|
@@ -84,9 +84,9 @@ export class TeamsServer extends DurableObject {
|
|
|
84
84
|
this.api = createTeamsApi({ publicOrigin: normalizePublicOrigin(env.PUBLIC_ORIGIN), identityIssuer: env.VOLTER_ISSUER || null, identityOrganizations: identityOrganizationsFromEnv(env), delegatedIssuer: env.DELEGATED_ISSUER || null });
|
|
85
85
|
this.api.storageUsage = () => {
|
|
86
86
|
const sql = ctx.storage.sql;
|
|
87
|
-
let
|
|
88
|
-
try {
|
|
89
|
-
return { allocated: sql.databaseSize, used: Math.max(0, sql.databaseSize -
|
|
87
|
+
let freeBytes = null;
|
|
88
|
+
try { freeBytes = sql.exec('PRAGMA freelist_count').one().freelist_count * sql.exec('PRAGMA page_size').one().page_size; } catch { /* preserve an explicit unknown if the host omits these pragmas */ }
|
|
89
|
+
return { allocated: sql.databaseSize, free_bytes: freeBytes, used: Math.max(0, sql.databaseSize - (freeBytes ?? 0)), used_is_upper_bound: freeBytes === null, limit: 10 * 1024 ** 3 };
|
|
90
90
|
};
|
|
91
91
|
this.api.attach({ store: new DirectStore(createStoreHandlers(db)), harnessRoutes });
|
|
92
92
|
const existing = await this.api.store.one("SELECT value FROM server_meta WHERE key='server_id'");
|