@seamnet/client 0.20.4 → 0.20.5

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.
Files changed (2) hide show
  1. package/lib/tmux-utils.cjs +24 -13
  2. package/package.json +1 -1
@@ -15,10 +15,18 @@ const { basename } = require('node:path');
15
15
 
16
16
  const EXEC_OPTS = { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] };
17
17
 
18
+ function isCcBusyLine(line) {
19
+ return line.includes('⏵⏵') && /esc to interrupt/i.test(line);
20
+ }
21
+
22
+ function isCodexBusyLine(line) {
23
+ return /^[^\S\r\n]*[◦•]\s+Working \([^)]*esc to interrupt[^)]*\)(?:\s*·.*)?$/i.test(line);
24
+ }
25
+
18
26
  // kind → 启动命令族。声明式数据小表,不是 adapter。新增 kind 加一行即可。
19
27
  // - fresh:全新会话的启动命令
20
28
  // - resume:续上一次会话的命令(restart 默认走这个;--no-continue 走 fresh)
21
- // - busyPatterns/idleWaitMs/idleSettleMs:restart 先等 TUI 真空闲并短暂复核稳定;超时才退回 resetKeys 强推
29
+ // - busyLineMatchers/idleWaitMs/idleSettleMs:restart 先等 TUI 真空闲并短暂复核稳定;超时才退回 resetKeys 强推
22
30
  // - resetKeys/resetWaitMs:仅兜底路径用;发 exit 前尝试把 TUI 从忙窗口里拉回可退出态
23
31
  // - exit:TUI 内的优雅退出命令(空闲时直接发它 + Enter)
24
32
  // - downTimeout:发了 exit 后等进程退出的轮询超时(ms)
@@ -26,7 +34,7 @@ const KIND_CMD = {
26
34
  cc: {
27
35
  fresh: 'claude --dangerously-skip-permissions',
28
36
  resume: 'claude --dangerously-skip-permissions --continue',
29
- busyPatterns: [/esc to interrupt/i],
37
+ busyLineMatchers: [isCcBusyLine],
30
38
  idleWaitMs: 120000,
31
39
  idleSettleMs: 500,
32
40
  resetKeys: ['Escape'],
@@ -35,9 +43,9 @@ const KIND_CMD = {
35
43
  downTimeout: 120000, // cc 退出跑 Stop hook(auto-save / semantic_memory)要 ~90s
36
44
  },
37
45
  codex: {
38
- fresh: 'codex',
39
- resume: 'codex resume --last',
40
- busyPatterns: [/esc to interrupt/i, /Working \(/i],
46
+ fresh: 'codex --dangerously-bypass-approvals-and-sandbox',
47
+ resume: 'codex resume --last --dangerously-bypass-approvals-and-sandbox',
48
+ busyLineMatchers: [isCodexBusyLine],
41
49
  idleWaitMs: 60000,
42
50
  idleSettleMs: 1000,
43
51
  resetKeys: ['Escape', 'Escape'],
@@ -154,16 +162,19 @@ function readViewport(session, { lines = 80, socketPath } = {}) {
154
162
  return raw.replace(/\s+$/, '').split('\n').slice(-n).join('\n');
155
163
  }
156
164
 
157
- function matchesBusy(screenText, busyPatterns = []) {
158
- return busyPatterns.some((pattern) => pattern.test(screenText));
165
+ function matchesBusy(screenText, busyLineMatchers = []) {
166
+ if (!Array.isArray(busyLineMatchers) || busyLineMatchers.length === 0) return false;
167
+ return String(screenText)
168
+ .split('\n')
169
+ .some((line) => busyLineMatchers.some((matchesLine) => matchesLine(line)));
159
170
  }
160
171
 
161
172
  async function waitForIdle(
162
173
  session,
163
174
  socketPath,
164
- { targetKind, busyPatterns = [], idleWaitMs = 0, idleSettleMs = 0 }
175
+ { targetKind, busyLineMatchers = [], idleWaitMs = 0, idleSettleMs = 0 }
165
176
  ) {
166
- if (!(idleWaitMs > 0) || busyPatterns.length === 0) {
177
+ if (!(idleWaitMs > 0) || busyLineMatchers.length === 0) {
167
178
  return { ok: true, lastScreen: '' };
168
179
  }
169
180
  const deadline = Date.now() + idleWaitMs;
@@ -174,7 +185,7 @@ async function waitForIdle(
174
185
  return { ok: true, kindChanged: true, lastScreen };
175
186
  }
176
187
  lastScreen = readViewport(session, { socketPath });
177
- if (!matchesBusy(lastScreen, busyPatterns)) {
188
+ if (!matchesBusy(lastScreen, busyLineMatchers)) {
178
189
  if (!(idleSettleMs > 0)) {
179
190
  return { ok: true, lastScreen };
180
191
  }
@@ -266,7 +277,7 @@ async function restartSkeleton(
266
277
  socketPath,
267
278
  {
268
279
  targetKind,
269
- busyPatterns = [],
280
+ busyLineMatchers = [],
270
281
  idleWaitMs = 0,
271
282
  idleSettleMs = 0,
272
283
  resetAction = async () => {},
@@ -281,7 +292,7 @@ async function restartSkeleton(
281
292
  // 1. 先等真空闲;整段预算都没等到,才退回 resetKey 强推
282
293
  const idleState = await waitForIdle(session, socketPath, {
283
294
  targetKind,
284
- busyPatterns,
295
+ busyLineMatchers,
285
296
  idleWaitMs,
286
297
  idleSettleMs,
287
298
  });
@@ -378,7 +389,7 @@ async function restartTerm(session, { socketPath, continueSession = true } = {})
378
389
  const startCmd = continueSession ? spec.resume : spec.fresh;
379
390
  return restartSkeleton(session, socketPath, {
380
391
  targetKind: kind,
381
- busyPatterns: spec.busyPatterns,
392
+ busyLineMatchers: spec.busyLineMatchers,
382
393
  idleWaitMs: spec.idleWaitMs,
383
394
  idleSettleMs: spec.idleSettleMs,
384
395
  resetAction: async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamnet/client",
3
- "version": "0.20.4",
3
+ "version": "0.20.5",
4
4
  "description": "One command to join Seam — the network where people and AI stay in sync.",
5
5
  "bin": {
6
6
  "seam-client": "bin/cli.js",