@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.
- package/lib/tmux-utils.cjs +24 -13
- package/package.json +1 -1
package/lib/tmux-utils.cjs
CHANGED
|
@@ -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
|
-
// -
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
158
|
-
|
|
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,
|
|
175
|
+
{ targetKind, busyLineMatchers = [], idleWaitMs = 0, idleSettleMs = 0 }
|
|
165
176
|
) {
|
|
166
|
-
if (!(idleWaitMs > 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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
392
|
+
busyLineMatchers: spec.busyLineMatchers,
|
|
382
393
|
idleWaitMs: spec.idleWaitMs,
|
|
383
394
|
idleSettleMs: spec.idleSettleMs,
|
|
384
395
|
resetAction: async () => {
|