@volter/supercode-teams 0.2.144 → 0.2.145

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 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
- return await terminal.deliverComposerInput(this.host, session.id, kind, submission(text, false), options);
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': {
@@ -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. Model, approval and
15
- // workspace options remain the TUI's own thread-start/resume arguments.
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 (['-c', '--config', '--enable', '--disable'].includes(arg)) config.push(arg, seed.arguments[++index]);
20
- else if (/^--(?:config|enable|disable)=/.test(arg)) config.push(arg);
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 = [...seed.arguments, '--remote', endpoint];
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.144",
3
+ "version": "0.2.145",
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",