deskbuddy 0.2.0-beta.10 → 0.2.0-beta.11
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/dist/cli.js +2 -2
- package/dist/hook.cjs +46 -8
- package/dist/wake.cjs +8 -5
- package/hook/hook.cjs +46 -8
- package/hook/wake.cjs +8 -5
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6323,7 +6323,7 @@ var Daemon = class {
|
|
|
6323
6323
|
if (cmd.sessionId) {
|
|
6324
6324
|
const sid = resolveAlias(cmd.sessionId);
|
|
6325
6325
|
const st = readState(sid);
|
|
6326
|
-
if (st && st.cwd && st.interactive === true && st.remoteWindow !== true && !!st.pid && isAlive(st.pid, st.pidName)) {
|
|
6326
|
+
if (st && st.cwd && (st.interactive === true || st.embedded === true) && st.remoteWindow !== true && !!st.pid && isAlive(st.pid, st.pidName)) {
|
|
6327
6327
|
void this.#track(this.#idleReply(cmd, sid, st.cwd));
|
|
6328
6328
|
return;
|
|
6329
6329
|
}
|
|
@@ -6392,7 +6392,7 @@ var Daemon = class {
|
|
|
6392
6392
|
try {
|
|
6393
6393
|
await this.#execute(cmd, sid, runCwd, cmd.tool, true, (r) => {
|
|
6394
6394
|
const now = readState(sid);
|
|
6395
|
-
if (!now || now.interactive !== true || !now.pid || !isAlive(now.pid, now.pidName)) return;
|
|
6395
|
+
if (!now || now.interactive !== true && now.embedded !== true || !now.pid || !isAlive(now.pid, now.pidName)) return;
|
|
6396
6396
|
writeAppended(sid, { text: cmd.text, result: resultSummary(cmd.tool, r.stdout), ok: r.ok, at: this.#deps.now() });
|
|
6397
6397
|
});
|
|
6398
6398
|
} finally {
|
package/dist/hook.cjs
CHANGED
|
@@ -1027,6 +1027,22 @@ function isInteractiveArgs(args) {
|
|
|
1027
1027
|
)
|
|
1028
1028
|
}
|
|
1029
1029
|
|
|
1030
|
+
/**
|
|
1031
|
+
* 嵌入宿主(插件 / 桌面版 / SDK 长连):`--input-format stream-json`(含 `=` 写法)且不带 -p / --print。
|
|
1032
|
+
* 这类会话 isInteractiveArgs 判 false(不是终端窗口),但用户就在宿主里一轮轮发话:没有唤醒等待者(wake.cjs 铁律③),
|
|
1033
|
+
* daemon 收到设备回复要按「用户自己的窗口」处理(正在跑就排队等 Stop、空闲才后台续跑并写 appended),不能直接并发 --resume。
|
|
1034
|
+
* 🔴 只落本机状态,不改 interactive(服务端按 interactive 挡 8348),也不上报
|
|
1035
|
+
*/
|
|
1036
|
+
function isEmbeddedArgs(args) {
|
|
1037
|
+
const tokens = String(args || '').trim().split(/\s+/).slice(1)
|
|
1038
|
+
let streamIn = false
|
|
1039
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1040
|
+
const t = tokens[i]
|
|
1041
|
+
if (t === '--input-format=stream-json' || (t === '--input-format' && tokens[i + 1] === 'stream-json')) streamIn = true
|
|
1042
|
+
}
|
|
1043
|
+
return streamIn && !tokens.some((t) => t === '--print' || /^-[a-zA-Z]*p[a-zA-Z]*$/.test(t))
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1030
1046
|
/** 只看进程在不在(kill 0),不查进程名:每次事件都要调,ps 太贵 */
|
|
1031
1047
|
function pidExists(pid) {
|
|
1032
1048
|
try {
|
|
@@ -1037,23 +1053,34 @@ function pidExists(pid) {
|
|
|
1037
1053
|
}
|
|
1038
1054
|
}
|
|
1039
1055
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1056
|
+
/** 智能体进程的命令行(Windows 见 winproc 的规范化参数串)。取不到返回 '' */
|
|
1057
|
+
function agentArgs(pid) {
|
|
1058
|
+
if (!pid) return ''
|
|
1042
1059
|
if (IS_WIN) {
|
|
1043
1060
|
const p = winProc(pid)
|
|
1044
|
-
return
|
|
1061
|
+
return p ? p.args : ''
|
|
1045
1062
|
}
|
|
1046
1063
|
try {
|
|
1047
|
-
|
|
1064
|
+
return execFileSync('ps', ['-ww', '-o', 'args=', '-p', String(pid)], {
|
|
1048
1065
|
encoding: 'utf8',
|
|
1049
1066
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1050
|
-
})
|
|
1051
|
-
return args.trim() !== '' && isInteractiveArgs(args)
|
|
1067
|
+
}).trim()
|
|
1052
1068
|
} catch (e) {
|
|
1053
|
-
return
|
|
1069
|
+
return ''
|
|
1054
1070
|
}
|
|
1055
1071
|
}
|
|
1056
1072
|
|
|
1073
|
+
/** 交互窗口 / 嵌入宿主两个判定共用一次命令行查询。🔴 取不到命令行两个都按 false */
|
|
1074
|
+
function detectHost(pid) {
|
|
1075
|
+
const args = agentArgs(pid)
|
|
1076
|
+
if (!args) return { interactive: false, embedded: false }
|
|
1077
|
+
return { interactive: isInteractiveArgs(args), embedded: isEmbeddedArgs(args) }
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function detectInteractive(pid) {
|
|
1081
|
+
return detectHost(pid).interactive
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1057
1084
|
// ---------- /clear 改绑(二期 P2) ----------
|
|
1058
1085
|
|
|
1059
1086
|
/** 与 src/core/state.ts 的 ALIAS_TTL_MS 一致 */
|
|
@@ -1465,6 +1492,13 @@ async function main(argv) {
|
|
|
1465
1492
|
// 不能拿死掉的 -p 进程的 interactive=false 挡不住窗口
|
|
1466
1493
|
const prevAlive = !!(prev && prev.pid && pidExists(prev.pid))
|
|
1467
1494
|
const pid = (prevAlive && prev.pid) || resolveAgentPid(process.pid, agentNames(registry, tool)) || undefined
|
|
1495
|
+
// 两个判定沿用规则相同:进程还活着且上次判过就沿用,否则查一次命令行(只查一次)
|
|
1496
|
+
let host = null
|
|
1497
|
+
const hostFlag = (k) => {
|
|
1498
|
+
if (prevAlive && typeof prev[k] === 'boolean') return prev[k]
|
|
1499
|
+
if (!host) host = detectHost(pid)
|
|
1500
|
+
return host[k]
|
|
1501
|
+
}
|
|
1468
1502
|
const next = {
|
|
1469
1503
|
sessionId: sessionId,
|
|
1470
1504
|
tool: tool,
|
|
@@ -1485,7 +1519,9 @@ async function main(argv) {
|
|
|
1485
1519
|
pid: pid,
|
|
1486
1520
|
pidName: pid ? (prevAlive && prev.pidName) || processName(pid) || undefined : undefined,
|
|
1487
1521
|
// 二期 P1:窗口开着的会话,设备不能再往里下指令(两个进程会互相吞掉对话)。每个会话只判一次
|
|
1488
|
-
interactive: windowId ? true :
|
|
1522
|
+
interactive: windowId ? true : hostFlag('interactive'),
|
|
1523
|
+
// 嵌入宿主(插件 / 桌面版,见 isEmbeddedArgs):daemon 据此按用户窗口处理设备回复。只落本机、不上报
|
|
1524
|
+
embedded: hostFlag('embedded'),
|
|
1489
1525
|
// 会话开在 DeskBuddy 窗口里(spec 2026-09-30 §3.2):服务端据此放行 8348,指令由包装层写进窗口
|
|
1490
1526
|
// Codex(切片 B):会话加载在 daemon 连着的 app-server 里 = 窗口开着且能往里发
|
|
1491
1527
|
remoteWindow: windowId || (tool === 'codex' && codexRemoteHas(sessionId)) ? true : undefined,
|
|
@@ -1602,6 +1638,8 @@ module.exports = {
|
|
|
1602
1638
|
APPENDED_SYNC_TOOLS,
|
|
1603
1639
|
RUNNING_MESSAGE,
|
|
1604
1640
|
isInteractiveArgs,
|
|
1641
|
+
isEmbeddedArgs,
|
|
1642
|
+
detectHost,
|
|
1605
1643
|
recordDir,
|
|
1606
1644
|
markClear,
|
|
1607
1645
|
pairClear,
|
package/dist/wake.cjs
CHANGED
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
// ② 除了「认证过的 daemon 发来、且逐条 HMAC 校验通过的指令」这一种情况,一律 exit 0、绝不写 stderr:
|
|
12
12
|
// exit 2 会唤醒窗口,stderr 的内容会被模型当成用户输入执行。通道(Windows 命名管道 / tmp 下的 socket)
|
|
13
13
|
// 别的进程也能连、能抢注,所以先双向认证(密钥 ~/.deskbuddy/wake/secret,daemon 每次启动重新生成)。
|
|
14
|
-
// ③ 非交互的 `claude -p`
|
|
15
|
-
//
|
|
14
|
+
// ③ 非交互的 `claude -p` 和 `--input-format stream-json` 宿主(IDE 插件 / 桌面版)里 asyncRewake 会被当成**同步** hook
|
|
15
|
+
// 执行(源码 `!isInteractive || hasStreamingInput`),等在那儿就把人家卡住:插件里 SessionStart 挂着不退,
|
|
16
|
+
// 60 秒后报「Subprocess initialization did not complete」会话被杀(2026-10-02 Trae CN 内置 Claude Code 插件 2.1.281 必现)。
|
|
17
|
+
// 只对交互 CLI 窗口放行,认不出宿主一律立刻退出。插件 / 桌面版的会话靠 daemon 后台续跑接设备回复。
|
|
16
18
|
// ④ 窗口关了(智能体进程没了)就退出:Claude Code 被强杀时不会替我们收尸。
|
|
17
19
|
//
|
|
18
20
|
// ⚠️ 通道地址 / 密钥 / HMAC / 标记 / transcript 分析与 src/core/wake.ts、src/daemon/wake.ts 同构,
|
|
@@ -193,8 +195,9 @@ function entrypointAllows(ep) {
|
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
/**
|
|
196
|
-
*
|
|
197
|
-
* 带 -p / --print
|
|
198
|
+
* 宿主命令行判定(环境变量里没有入口时才用):只有交互窗口可以。
|
|
199
|
+
* 带 -p / --print 的一次性调用、`--input-format stream-json` 的插件 / 桌面版宿主里 asyncRewake 都是同步的,绝不能等(见铁律③)。
|
|
200
|
+
* 🔴 拿不到命令行 → false
|
|
198
201
|
*/
|
|
199
202
|
function canWake(args) {
|
|
200
203
|
const all = String(args || '').trim()
|
|
@@ -202,7 +205,7 @@ function canWake(args) {
|
|
|
202
205
|
const tokens = all.split(/\s+/).slice(1)
|
|
203
206
|
for (let i = 0; i < tokens.length; i++) {
|
|
204
207
|
const t = tokens[i]
|
|
205
|
-
if (t === '--input-format=stream-json' || (t === '--input-format' && tokens[i + 1] === 'stream-json')) return
|
|
208
|
+
if (t === '--input-format=stream-json' || (t === '--input-format' && tokens[i + 1] === 'stream-json')) return false
|
|
206
209
|
}
|
|
207
210
|
return !tokens.some((t) => t === '--print' || /^-[a-zA-Z]*p[a-zA-Z]*$/.test(t))
|
|
208
211
|
}
|
package/hook/hook.cjs
CHANGED
|
@@ -1027,6 +1027,22 @@ function isInteractiveArgs(args) {
|
|
|
1027
1027
|
)
|
|
1028
1028
|
}
|
|
1029
1029
|
|
|
1030
|
+
/**
|
|
1031
|
+
* 嵌入宿主(插件 / 桌面版 / SDK 长连):`--input-format stream-json`(含 `=` 写法)且不带 -p / --print。
|
|
1032
|
+
* 这类会话 isInteractiveArgs 判 false(不是终端窗口),但用户就在宿主里一轮轮发话:没有唤醒等待者(wake.cjs 铁律③),
|
|
1033
|
+
* daemon 收到设备回复要按「用户自己的窗口」处理(正在跑就排队等 Stop、空闲才后台续跑并写 appended),不能直接并发 --resume。
|
|
1034
|
+
* 🔴 只落本机状态,不改 interactive(服务端按 interactive 挡 8348),也不上报
|
|
1035
|
+
*/
|
|
1036
|
+
function isEmbeddedArgs(args) {
|
|
1037
|
+
const tokens = String(args || '').trim().split(/\s+/).slice(1)
|
|
1038
|
+
let streamIn = false
|
|
1039
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
1040
|
+
const t = tokens[i]
|
|
1041
|
+
if (t === '--input-format=stream-json' || (t === '--input-format' && tokens[i + 1] === 'stream-json')) streamIn = true
|
|
1042
|
+
}
|
|
1043
|
+
return streamIn && !tokens.some((t) => t === '--print' || /^-[a-zA-Z]*p[a-zA-Z]*$/.test(t))
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1030
1046
|
/** 只看进程在不在(kill 0),不查进程名:每次事件都要调,ps 太贵 */
|
|
1031
1047
|
function pidExists(pid) {
|
|
1032
1048
|
try {
|
|
@@ -1037,23 +1053,34 @@ function pidExists(pid) {
|
|
|
1037
1053
|
}
|
|
1038
1054
|
}
|
|
1039
1055
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1056
|
+
/** 智能体进程的命令行(Windows 见 winproc 的规范化参数串)。取不到返回 '' */
|
|
1057
|
+
function agentArgs(pid) {
|
|
1058
|
+
if (!pid) return ''
|
|
1042
1059
|
if (IS_WIN) {
|
|
1043
1060
|
const p = winProc(pid)
|
|
1044
|
-
return
|
|
1061
|
+
return p ? p.args : ''
|
|
1045
1062
|
}
|
|
1046
1063
|
try {
|
|
1047
|
-
|
|
1064
|
+
return execFileSync('ps', ['-ww', '-o', 'args=', '-p', String(pid)], {
|
|
1048
1065
|
encoding: 'utf8',
|
|
1049
1066
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1050
|
-
})
|
|
1051
|
-
return args.trim() !== '' && isInteractiveArgs(args)
|
|
1067
|
+
}).trim()
|
|
1052
1068
|
} catch (e) {
|
|
1053
|
-
return
|
|
1069
|
+
return ''
|
|
1054
1070
|
}
|
|
1055
1071
|
}
|
|
1056
1072
|
|
|
1073
|
+
/** 交互窗口 / 嵌入宿主两个判定共用一次命令行查询。🔴 取不到命令行两个都按 false */
|
|
1074
|
+
function detectHost(pid) {
|
|
1075
|
+
const args = agentArgs(pid)
|
|
1076
|
+
if (!args) return { interactive: false, embedded: false }
|
|
1077
|
+
return { interactive: isInteractiveArgs(args), embedded: isEmbeddedArgs(args) }
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function detectInteractive(pid) {
|
|
1081
|
+
return detectHost(pid).interactive
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1057
1084
|
// ---------- /clear 改绑(二期 P2) ----------
|
|
1058
1085
|
|
|
1059
1086
|
/** 与 src/core/state.ts 的 ALIAS_TTL_MS 一致 */
|
|
@@ -1465,6 +1492,13 @@ async function main(argv) {
|
|
|
1465
1492
|
// 不能拿死掉的 -p 进程的 interactive=false 挡不住窗口
|
|
1466
1493
|
const prevAlive = !!(prev && prev.pid && pidExists(prev.pid))
|
|
1467
1494
|
const pid = (prevAlive && prev.pid) || resolveAgentPid(process.pid, agentNames(registry, tool)) || undefined
|
|
1495
|
+
// 两个判定沿用规则相同:进程还活着且上次判过就沿用,否则查一次命令行(只查一次)
|
|
1496
|
+
let host = null
|
|
1497
|
+
const hostFlag = (k) => {
|
|
1498
|
+
if (prevAlive && typeof prev[k] === 'boolean') return prev[k]
|
|
1499
|
+
if (!host) host = detectHost(pid)
|
|
1500
|
+
return host[k]
|
|
1501
|
+
}
|
|
1468
1502
|
const next = {
|
|
1469
1503
|
sessionId: sessionId,
|
|
1470
1504
|
tool: tool,
|
|
@@ -1485,7 +1519,9 @@ async function main(argv) {
|
|
|
1485
1519
|
pid: pid,
|
|
1486
1520
|
pidName: pid ? (prevAlive && prev.pidName) || processName(pid) || undefined : undefined,
|
|
1487
1521
|
// 二期 P1:窗口开着的会话,设备不能再往里下指令(两个进程会互相吞掉对话)。每个会话只判一次
|
|
1488
|
-
interactive: windowId ? true :
|
|
1522
|
+
interactive: windowId ? true : hostFlag('interactive'),
|
|
1523
|
+
// 嵌入宿主(插件 / 桌面版,见 isEmbeddedArgs):daemon 据此按用户窗口处理设备回复。只落本机、不上报
|
|
1524
|
+
embedded: hostFlag('embedded'),
|
|
1489
1525
|
// 会话开在 DeskBuddy 窗口里(spec 2026-09-30 §3.2):服务端据此放行 8348,指令由包装层写进窗口
|
|
1490
1526
|
// Codex(切片 B):会话加载在 daemon 连着的 app-server 里 = 窗口开着且能往里发
|
|
1491
1527
|
remoteWindow: windowId || (tool === 'codex' && codexRemoteHas(sessionId)) ? true : undefined,
|
|
@@ -1602,6 +1638,8 @@ module.exports = {
|
|
|
1602
1638
|
APPENDED_SYNC_TOOLS,
|
|
1603
1639
|
RUNNING_MESSAGE,
|
|
1604
1640
|
isInteractiveArgs,
|
|
1641
|
+
isEmbeddedArgs,
|
|
1642
|
+
detectHost,
|
|
1605
1643
|
recordDir,
|
|
1606
1644
|
markClear,
|
|
1607
1645
|
pairClear,
|
package/hook/wake.cjs
CHANGED
|
@@ -11,8 +11,10 @@
|
|
|
11
11
|
// ② 除了「认证过的 daemon 发来、且逐条 HMAC 校验通过的指令」这一种情况,一律 exit 0、绝不写 stderr:
|
|
12
12
|
// exit 2 会唤醒窗口,stderr 的内容会被模型当成用户输入执行。通道(Windows 命名管道 / tmp 下的 socket)
|
|
13
13
|
// 别的进程也能连、能抢注,所以先双向认证(密钥 ~/.deskbuddy/wake/secret,daemon 每次启动重新生成)。
|
|
14
|
-
// ③ 非交互的 `claude -p`
|
|
15
|
-
//
|
|
14
|
+
// ③ 非交互的 `claude -p` 和 `--input-format stream-json` 宿主(IDE 插件 / 桌面版)里 asyncRewake 会被当成**同步** hook
|
|
15
|
+
// 执行(源码 `!isInteractive || hasStreamingInput`),等在那儿就把人家卡住:插件里 SessionStart 挂着不退,
|
|
16
|
+
// 60 秒后报「Subprocess initialization did not complete」会话被杀(2026-10-02 Trae CN 内置 Claude Code 插件 2.1.281 必现)。
|
|
17
|
+
// 只对交互 CLI 窗口放行,认不出宿主一律立刻退出。插件 / 桌面版的会话靠 daemon 后台续跑接设备回复。
|
|
16
18
|
// ④ 窗口关了(智能体进程没了)就退出:Claude Code 被强杀时不会替我们收尸。
|
|
17
19
|
//
|
|
18
20
|
// ⚠️ 通道地址 / 密钥 / HMAC / 标记 / transcript 分析与 src/core/wake.ts、src/daemon/wake.ts 同构,
|
|
@@ -193,8 +195,9 @@ function entrypointAllows(ep) {
|
|
|
193
195
|
}
|
|
194
196
|
|
|
195
197
|
/**
|
|
196
|
-
*
|
|
197
|
-
* 带 -p / --print
|
|
198
|
+
* 宿主命令行判定(环境变量里没有入口时才用):只有交互窗口可以。
|
|
199
|
+
* 带 -p / --print 的一次性调用、`--input-format stream-json` 的插件 / 桌面版宿主里 asyncRewake 都是同步的,绝不能等(见铁律③)。
|
|
200
|
+
* 🔴 拿不到命令行 → false
|
|
198
201
|
*/
|
|
199
202
|
function canWake(args) {
|
|
200
203
|
const all = String(args || '').trim()
|
|
@@ -202,7 +205,7 @@ function canWake(args) {
|
|
|
202
205
|
const tokens = all.split(/\s+/).slice(1)
|
|
203
206
|
for (let i = 0; i < tokens.length; i++) {
|
|
204
207
|
const t = tokens[i]
|
|
205
|
-
if (t === '--input-format=stream-json' || (t === '--input-format' && tokens[i + 1] === 'stream-json')) return
|
|
208
|
+
if (t === '--input-format=stream-json' || (t === '--input-format' && tokens[i + 1] === 'stream-json')) return false
|
|
206
209
|
}
|
|
207
210
|
return !tokens.some((t) => t === '--print' || /^-[a-zA-Z]*p[a-zA-Z]*$/.test(t))
|
|
208
211
|
}
|