deskbuddy 0.2.0-beta.3 → 0.2.0-beta.4

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/hook.cjs CHANGED
@@ -26,7 +26,7 @@ const MAX_SESSIONS = 20
26
26
  * 快照整包带的能力声明(前置 spec 2026-09-29 §4.4,服务端写 deskbuddy:caps:{clientId})。
27
27
  * 与 src/core/snapshot.ts 的 CAPS 一致
28
28
  */
29
- const CAPS = ['cmd_result', 'window_inject']
29
+ const CAPS = ['cmd_result', 'window_inject', 'resume_ended']
30
30
  /** 状态文件里保留的最近事件条数,与 src/core/state.ts 的 RECENT_MAX 一致 */
31
31
  const RECENT_MAX = 8
32
32
  const REPORT_TIMEOUT_MS = 2000
@@ -556,6 +556,40 @@ function writeState(s) {
556
556
  fs.renameSync(tmp, statePath(s.sessionId))
557
557
  }
558
558
 
559
+ // ---------- 已结束记录(与 src/core/ended.ts 同构,续跑方案 spec 2026-09-29 §5.1) ----------
560
+
561
+ const ENDED_TTL_MS = 7 * 24 * 60 * 60 * 1000
562
+
563
+ function writeEnded(s, now) {
564
+ const sid = String((s && s.sessionId) || '')
565
+ if (!/^[A-Za-z0-9_-]{1,64}$/.test(sid)) return
566
+ try {
567
+ const dir = path.join(deskbuddyHome(), 'ended')
568
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 })
569
+ const t = now || Date.now()
570
+ const rec = { sessionId: sid, tool: s.tool || '', cwd: s.cwd || '', interactive: s.interactive === true, endedAt: t }
571
+ const file = path.join(dir, sid + '.json')
572
+ fs.writeFileSync(file + '.tmp', JSON.stringify(rec), { encoding: 'utf8', mode: 0o600 })
573
+ fs.renameSync(file + '.tmp', file)
574
+ for (const n of fs.readdirSync(dir)) {
575
+ const f = path.join(dir, n)
576
+ try {
577
+ if (t - fs.statSync(f).mtimeMs > ENDED_TTL_MS) fs.rmSync(f, { force: true })
578
+ } catch (e) {
579
+ /* ignore */
580
+ }
581
+ }
582
+ } catch (e) {
583
+ /* 记不下:只是这个会话结束后不能从设备续跑 */
584
+ }
585
+ }
586
+
587
+ /** 会话结束:先写已结束记录,再删状态(删状态的地方都走这里,/clear 除外) */
588
+ function retireState(s, now) {
589
+ writeEnded(s, now)
590
+ removeState(s.sessionId)
591
+ }
592
+
559
593
  function removeState(sessionId) {
560
594
  try {
561
595
  fs.rmSync(statePath(sessionId), { force: true })
@@ -591,7 +625,7 @@ function readAliveStates(tool) {
591
625
  }
592
626
  if (!s || s.tool !== tool) continue
593
627
  if (s.pid && !isAlive(s.pid, s.pidName)) {
594
- removeState(s.sessionId) // 窗口已关:剔除并自我清理,state 目录不再无限增长
628
+ retireState(s) // 窗口已关:记下已结束、剔除并自我清理,state 目录不再无限增长
595
629
  continue
596
630
  }
597
631
  alive.push(s)
@@ -1090,8 +1124,13 @@ async function main(argv) {
1090
1124
  if (event === 'SessionEnd' && payload.reason === 'clear') {
1091
1125
  const old = readState(sessionId)
1092
1126
  if (old && old.pid) markClear(old.pid, sessionId)
1127
+ // /clear 引起的结束交给别名改绑,不写已结束记录
1128
+ removeState(sessionId)
1129
+ } else {
1130
+ // 先记下已结束(设备再回复时续跑要知道 cwd),再删状态。状态文件没有就用这次事件里的 cwd
1131
+ const old = readState(sessionId)
1132
+ retireState(old || { sessionId: sessionId, tool: tool, cwd: payload.cwd || '', interactive: false })
1093
1133
  }
1094
- removeState(sessionId)
1095
1134
  // 删完补发一次剩余会话,否则这条会一直挂在屏上等服务端失联 TTL。
1096
1135
  // 关掉最后一个窗口时发的是空列表——服务端放行空快照,该机器该工具随即从屏上消失
1097
1136
  await reportIfAuthorized(tool, Date.now())
@@ -1199,6 +1238,8 @@ module.exports = {
1199
1238
  readGitBranch,
1200
1239
  readState,
1201
1240
  removeState,
1241
+ retireState,
1242
+ writeEnded,
1202
1243
  resolveAgentPid,
1203
1244
  resolveTitle,
1204
1245
  takeNotice,
package/hook/hook.cjs CHANGED
@@ -26,7 +26,7 @@ const MAX_SESSIONS = 20
26
26
  * 快照整包带的能力声明(前置 spec 2026-09-29 §4.4,服务端写 deskbuddy:caps:{clientId})。
27
27
  * 与 src/core/snapshot.ts 的 CAPS 一致
28
28
  */
29
- const CAPS = ['cmd_result', 'window_inject']
29
+ const CAPS = ['cmd_result', 'window_inject', 'resume_ended']
30
30
  /** 状态文件里保留的最近事件条数,与 src/core/state.ts 的 RECENT_MAX 一致 */
31
31
  const RECENT_MAX = 8
32
32
  const REPORT_TIMEOUT_MS = 2000
@@ -556,6 +556,40 @@ function writeState(s) {
556
556
  fs.renameSync(tmp, statePath(s.sessionId))
557
557
  }
558
558
 
559
+ // ---------- 已结束记录(与 src/core/ended.ts 同构,续跑方案 spec 2026-09-29 §5.1) ----------
560
+
561
+ const ENDED_TTL_MS = 7 * 24 * 60 * 60 * 1000
562
+
563
+ function writeEnded(s, now) {
564
+ const sid = String((s && s.sessionId) || '')
565
+ if (!/^[A-Za-z0-9_-]{1,64}$/.test(sid)) return
566
+ try {
567
+ const dir = path.join(deskbuddyHome(), 'ended')
568
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 })
569
+ const t = now || Date.now()
570
+ const rec = { sessionId: sid, tool: s.tool || '', cwd: s.cwd || '', interactive: s.interactive === true, endedAt: t }
571
+ const file = path.join(dir, sid + '.json')
572
+ fs.writeFileSync(file + '.tmp', JSON.stringify(rec), { encoding: 'utf8', mode: 0o600 })
573
+ fs.renameSync(file + '.tmp', file)
574
+ for (const n of fs.readdirSync(dir)) {
575
+ const f = path.join(dir, n)
576
+ try {
577
+ if (t - fs.statSync(f).mtimeMs > ENDED_TTL_MS) fs.rmSync(f, { force: true })
578
+ } catch (e) {
579
+ /* ignore */
580
+ }
581
+ }
582
+ } catch (e) {
583
+ /* 记不下:只是这个会话结束后不能从设备续跑 */
584
+ }
585
+ }
586
+
587
+ /** 会话结束:先写已结束记录,再删状态(删状态的地方都走这里,/clear 除外) */
588
+ function retireState(s, now) {
589
+ writeEnded(s, now)
590
+ removeState(s.sessionId)
591
+ }
592
+
559
593
  function removeState(sessionId) {
560
594
  try {
561
595
  fs.rmSync(statePath(sessionId), { force: true })
@@ -591,7 +625,7 @@ function readAliveStates(tool) {
591
625
  }
592
626
  if (!s || s.tool !== tool) continue
593
627
  if (s.pid && !isAlive(s.pid, s.pidName)) {
594
- removeState(s.sessionId) // 窗口已关:剔除并自我清理,state 目录不再无限增长
628
+ retireState(s) // 窗口已关:记下已结束、剔除并自我清理,state 目录不再无限增长
595
629
  continue
596
630
  }
597
631
  alive.push(s)
@@ -1090,8 +1124,13 @@ async function main(argv) {
1090
1124
  if (event === 'SessionEnd' && payload.reason === 'clear') {
1091
1125
  const old = readState(sessionId)
1092
1126
  if (old && old.pid) markClear(old.pid, sessionId)
1127
+ // /clear 引起的结束交给别名改绑,不写已结束记录
1128
+ removeState(sessionId)
1129
+ } else {
1130
+ // 先记下已结束(设备再回复时续跑要知道 cwd),再删状态。状态文件没有就用这次事件里的 cwd
1131
+ const old = readState(sessionId)
1132
+ retireState(old || { sessionId: sessionId, tool: tool, cwd: payload.cwd || '', interactive: false })
1093
1133
  }
1094
- removeState(sessionId)
1095
1134
  // 删完补发一次剩余会话,否则这条会一直挂在屏上等服务端失联 TTL。
1096
1135
  // 关掉最后一个窗口时发的是空列表——服务端放行空快照,该机器该工具随即从屏上消失
1097
1136
  await reportIfAuthorized(tool, Date.now())
@@ -1199,6 +1238,8 @@ module.exports = {
1199
1238
  readGitBranch,
1200
1239
  readState,
1201
1240
  removeState,
1241
+ retireState,
1242
+ writeEnded,
1202
1243
  resolveAgentPid,
1203
1244
  resolveTitle,
1204
1245
  takeNotice,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deskbuddy",
3
- "version": "0.2.0-beta.3",
3
+ "version": "0.2.0-beta.4",
4
4
  "description": "把 Claude Code、Codex 等 AI 编码智能体每个会话窗口的状态,实时同步到桌面硬件屏幕",
5
5
  "keywords": [
6
6
  "ai",