@clawos-dev/clawd 0.2.373 → 0.2.374

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.cjs CHANGED
@@ -11155,7 +11155,7 @@ var require_transport = __commonJS({
11155
11155
  "use strict";
11156
11156
  var { createRequire: createRequire2 } = require("module");
11157
11157
  var getCallers = require_caller();
11158
- var { join: join19, isAbsolute: isAbsolute4, sep: sep5 } = require("path");
11158
+ var { join: join19, isAbsolute: isAbsolute4, sep: sep4 } = require("path");
11159
11159
  var sleep2 = require_atomic_sleep();
11160
11160
  var onExit = require_on_exit_leak_free();
11161
11161
  var ThreadStream = require_thread_stream();
@@ -11263,7 +11263,7 @@ var require_transport = __commonJS({
11263
11263
  let fixTarget2;
11264
11264
  for (const filePath of callers) {
11265
11265
  try {
11266
- const context = filePath === "node:repl" ? process.cwd() + sep5 : filePath;
11266
+ const context = filePath === "node:repl" ? process.cwd() + sep4 : filePath;
11267
11267
  fixTarget2 = createRequire2(context).resolve(origin);
11268
11268
  break;
11269
11269
  } catch (err) {
@@ -48218,13 +48218,6 @@ function readCodexProjects(opts) {
48218
48218
  }
48219
48219
 
48220
48220
  // src/projects/store.ts
48221
- var DEFAULT_TMP_ROOTS = () => [
48222
- "/tmp",
48223
- "/private/tmp",
48224
- "/var/folders",
48225
- "/private/var/folders",
48226
- path12.resolve(os5.tmpdir())
48227
- ];
48228
48221
  var ProjectsStore = class {
48229
48222
  constructor(opts) {
48230
48223
  this.opts = opts;
@@ -48262,23 +48255,8 @@ var ProjectsStore = class {
48262
48255
  const insideClawd = !(rel.startsWith("..") || path12.isAbsolute(rel));
48263
48256
  if (insideClawd) return false;
48264
48257
  if (abs === path12.resolve(os5.homedir()) || abs === path12.parse(abs).root) return false;
48265
- for (const root of this.tmpRoots()) {
48266
- if (abs === root || abs.startsWith(root.endsWith(path12.sep) ? root : root + path12.sep)) {
48267
- return false;
48268
- }
48269
- }
48270
48258
  return true;
48271
48259
  }
48272
- /** 层 2 的临时区根(缺省自适应,见构造器 opts.tmpRoots 注释) */
48273
- tmpRoots() {
48274
- if (this.opts.tmpRoots) return this.opts.tmpRoots;
48275
- const defaults = DEFAULT_TMP_ROOTS();
48276
- const clawd = path12.resolve(this.opts.clawdDir);
48277
- const clawdItselfInTmp = defaults.some(
48278
- (r) => clawd === r || clawd.startsWith(r + path12.sep)
48279
- );
48280
- return clawdItselfInTmp ? [] : defaults;
48281
- }
48282
48260
  list() {
48283
48261
  return [...this.entries];
48284
48262
  }
@@ -48302,6 +48280,7 @@ var ProjectsStore = class {
48302
48280
  stateFile: this.opts.codexStateFile,
48303
48281
  logger: this.opts.logger
48304
48282
  })) {
48283
+ if (!c.createdAt) continue;
48305
48284
  const abs = this.normalize(c.path);
48306
48285
  if (!this.isProjectablePath(abs)) continue;
48307
48286
  byPath.set(abs, {
@@ -59034,23 +59013,6 @@ function toMs(v2) {
59034
59013
  const n = Number(v2 || 0);
59035
59014
  return n > 1e11 ? Math.round(n) : Math.round(n * 1e3);
59036
59015
  }
59037
- function listFiles(dir, depth = 0, maxDepth = 8, out = []) {
59038
- let entries;
59039
- try {
59040
- entries = import_node_fs46.default.readdirSync(dir, { withFileTypes: true });
59041
- } catch {
59042
- return out;
59043
- }
59044
- for (const e of entries) {
59045
- const full = import_node_path51.default.join(dir, e.name);
59046
- if (e.isDirectory()) {
59047
- if (depth < maxDepth) listFiles(full, depth + 1, maxDepth, out);
59048
- } else if (e.isFile()) {
59049
- out.push(full);
59050
- }
59051
- }
59052
- return out;
59053
- }
59054
59016
  async function* jsonLines(file) {
59055
59017
  let raw;
59056
59018
  try {
@@ -59108,6 +59070,7 @@ async function scanClaude(roots) {
59108
59070
  continue;
59109
59071
  }
59110
59072
  let cwd = "";
59073
+ let entrypoint = "";
59111
59074
  let title = "";
59112
59075
  let firstUser = "";
59113
59076
  let turns = 0;
@@ -59116,6 +59079,7 @@ async function scanClaude(roots) {
59116
59079
  let hasMainLine = false;
59117
59080
  for await (const rec4 of jsonLines(file)) {
59118
59081
  if (!cwd && typeof rec4.cwd === "string") cwd = rec4.cwd;
59082
+ if (!entrypoint && typeof rec4.entrypoint === "string") entrypoint = rec4.entrypoint;
59119
59083
  if (typeof rec4.timestamp === "string") {
59120
59084
  const t = Date.parse(rec4.timestamp);
59121
59085
  if (Number.isFinite(t)) {
@@ -59147,7 +59111,7 @@ async function scanClaude(roots) {
59147
59111
  };
59148
59112
  if (!hasMainLine) cand.skip = "subagent \u652F\u7EBF";
59149
59113
  else if (!cwd) cand.skip = "\u65E0 cwd";
59150
- else if (import_node_path51.default.basename(cwd).startsWith(TITLE_GEN_DIR_PREFIX)) cand.skip = "\u6807\u9898\u751F\u6210\u4E34\u65F6\u4F1A\u8BDD";
59114
+ else if (entrypoint !== "cli" && entrypoint !== "claude-desktop") cand.skip = "\u975E\u7528\u6237\u5165\u53E3";
59151
59115
  out.push(cand);
59152
59116
  }
59153
59117
  }
@@ -59168,7 +59132,7 @@ async function countCodexTurns(rolloutPath) {
59168
59132
  return { turns, first };
59169
59133
  }
59170
59134
  function readCodexThreads(db) {
59171
- const COLS = ["id", "cwd", "created_at", "updated_at", "name", "title", "preview", "first_user_message", "archived", "rollout_path"];
59135
+ const COLS = ["id", "cwd", "created_at", "updated_at", "name", "title", "preview", "first_user_message", "archived", "rollout_path", "thread_source"];
59172
59136
  try {
59173
59137
  const { DatabaseSync } = (0, import_node_module.createRequire)(import_meta4.url)("node:sqlite");
59174
59138
  const conn = new DatabaseSync(db, { readOnly: true });
@@ -59176,7 +59140,7 @@ function readCodexThreads(db) {
59176
59140
  const have = new Set(
59177
59141
  conn.prepare("PRAGMA table_info(threads)").all().map((r) => r.name)
59178
59142
  );
59179
- if (!["id", "cwd", "updated_at"].every((c) => have.has(c))) return null;
59143
+ if (!["id", "cwd", "updated_at", "thread_source"].every((c) => have.has(c))) return null;
59180
59144
  const sel = COLS.map((c) => have.has(c) ? c : `NULL AS ${c}`).join(", ");
59181
59145
  return conn.prepare(`SELECT ${sel} FROM threads`).all();
59182
59146
  } finally {
@@ -59190,7 +59154,7 @@ function readCodexThreads(db) {
59190
59154
  const have = new Set(
59191
59155
  info.stdout.split("\n").map((l) => l.split("|")[1]).filter(Boolean)
59192
59156
  );
59193
- if (!["id", "cwd", "updated_at"].every((c) => have.has(c))) return null;
59157
+ if (!["id", "cwd", "updated_at", "thread_source"].every((c) => have.has(c))) return null;
59194
59158
  const sel = COLS.map((c) => have.has(c) ? c : `NULL AS ${c}`).join(", ");
59195
59159
  const res = (0, import_node_child_process12.spawnSync)("sqlite3", ["-json", db, `SELECT ${sel} FROM threads;`], {
59196
59160
  encoding: "utf8",
@@ -59218,58 +59182,22 @@ async function codexFromRows(rows) {
59218
59182
  updatedMs,
59219
59183
  skip: ""
59220
59184
  };
59221
- if (r.archived) cand.skip = "\u5DF2\u5F52\u6863";
59185
+ if (r.thread_source !== "user") cand.skip = "\u975E\u7528\u6237\u65B0\u5EFA";
59186
+ else if (r.archived) cand.skip = "\u5DF2\u5F52\u6863";
59222
59187
  else if (!label) cand.skip = "\u7A7A\u4F1A\u8BDD";
59223
59188
  out.push(cand);
59224
59189
  }
59225
59190
  return out;
59226
59191
  }
59227
- async function scanCodexRollouts(roots) {
59228
- const out = [];
59229
- for (const file of listFiles(import_node_path51.default.join(roots.codexDir, "sessions"))) {
59230
- if (!file.endsWith(".jsonl")) continue;
59231
- let st;
59232
- try {
59233
- st = import_node_fs46.default.statSync(file);
59234
- } catch {
59235
- continue;
59236
- }
59237
- let meta = null;
59238
- for await (const rec4 of jsonLines(file)) {
59239
- if (rec4.type === "session_meta") {
59240
- meta = rec4.payload || {};
59241
- break;
59242
- }
59243
- }
59244
- const tid = meta && (meta.session_id || meta.id);
59245
- if (!tid) continue;
59246
- const { turns, first } = await countCodexTurns(file);
59247
- const cand = {
59248
- tool: "codex",
59249
- toolSessionId: String(tid),
59250
- cwd: String(meta?.cwd || ""),
59251
- label: clipW(first, 56),
59252
- turns,
59253
- createdMs: st.mtimeMs,
59254
- updatedMs: st.mtimeMs,
59255
- skip: ""
59256
- };
59257
- if (!cand.cwd) cand.skip = "\u65E0 cwd";
59258
- out.push(cand);
59259
- }
59260
- return out;
59261
- }
59262
59192
  async function scanCodex(roots) {
59263
59193
  let dbs = [];
59264
59194
  try {
59265
59195
  dbs = import_node_fs46.default.readdirSync(roots.codexDir).filter((n) => /^state_.*\.sqlite$/.test(n)).sort().map((n) => import_node_path51.default.join(roots.codexDir, n));
59266
59196
  } catch {
59267
59197
  }
59268
- if (dbs.length) {
59269
- const rows = readCodexThreads(dbs[dbs.length - 1]);
59270
- if (rows) return codexFromRows(rows);
59271
- }
59272
- return scanCodexRollouts(roots);
59198
+ if (!dbs.length) return [];
59199
+ const rows = readCodexThreads(dbs[dbs.length - 1]);
59200
+ return rows ? codexFromRows(rows) : [];
59273
59201
  }
59274
59202
  async function scanExternalSessions(rootsIn) {
59275
59203
  const roots = { ...defaultRoots(), ...rootsIn };
@@ -59300,14 +59228,19 @@ async function scanExternalSessions(rootsIn) {
59300
59228
  var import_node_fs47 = __toESM(require("fs"), 1);
59301
59229
  var import_node_path52 = __toESM(require("path"), 1);
59302
59230
  var LEDGER_FILENAME = "session-import-ledger.json";
59303
- function appendImportLedger(dataDir, entry) {
59304
- const file = import_node_path52.default.join(dataDir, LEDGER_FILENAME);
59305
- let entries = [];
59231
+ function readImportLedger(dataDir) {
59306
59232
  try {
59307
- const parsed = JSON.parse(import_node_fs47.default.readFileSync(file, "utf8"));
59308
- if (Array.isArray(parsed)) entries = parsed;
59233
+ const parsed = JSON.parse(
59234
+ import_node_fs47.default.readFileSync(import_node_path52.default.join(dataDir, LEDGER_FILENAME), "utf8")
59235
+ );
59236
+ return Array.isArray(parsed) ? parsed : [];
59309
59237
  } catch {
59238
+ return [];
59310
59239
  }
59240
+ }
59241
+ function appendImportLedger(dataDir, entry) {
59242
+ const file = import_node_path52.default.join(dataDir, LEDGER_FILENAME);
59243
+ const entries = readImportLedger(dataDir);
59311
59244
  entries.push(entry);
59312
59245
  import_node_fs47.default.writeFileSync(file, JSON.stringify(entries, null, 2), { encoding: "utf8", mode: 384 });
59313
59246
  }
@@ -59416,6 +59349,9 @@ function buildSessionHandlers(deps) {
59416
59349
  parseArgs2(SessionExternalScanArgs, frame, "session:externalScan args");
59417
59350
  const owned = manager.list().response.sessions;
59418
59351
  const linked = new Set(owned.map((f) => f.toolSessionId).filter(Boolean));
59352
+ if (deps.dataDir) {
59353
+ for (const e of readImportLedger(deps.dataDir)) linked.add(e.toolSessionId);
59354
+ }
59419
59355
  const candidates = (await scanExternalSessions()).filter((c) => !linked.has(c.toolSessionId));
59420
59356
  return { response: { candidates } };
59421
59357
  };
@@ -59847,8 +59783,8 @@ function isGuestPathAllowed(grants, absPath, personaRoot, action = "read", userW
59847
59783
  if (target === u || target.startsWith(u + usep)) return true;
59848
59784
  }
59849
59785
  const root = path65.resolve(personaRoot);
59850
- const sep5 = root.endsWith(path65.sep) ? "" : path65.sep;
59851
- if (!target.startsWith(root + sep5)) return false;
59786
+ const sep4 = root.endsWith(path65.sep) ? "" : path65.sep;
59787
+ if (!target.startsWith(root + sep4)) return false;
59852
59788
  const rel = path65.relative(root, target);
59853
59789
  if (!rel || rel.startsWith("..")) return false;
59854
59790
  const personaId2 = rel.split(path65.sep)[0];
@@ -59860,8 +59796,8 @@ function isGuestPathAllowed(grants, absPath, personaRoot, action = "read", userW
59860
59796
  function personaIdFromPath(absPath, personaRoot) {
59861
59797
  const root = path65.resolve(personaRoot);
59862
59798
  const target = path65.resolve(absPath);
59863
- const sep5 = root.endsWith(path65.sep) ? "" : path65.sep;
59864
- if (!target.startsWith(root + sep5)) return null;
59799
+ const sep4 = root.endsWith(path65.sep) ? "" : path65.sep;
59800
+ if (!target.startsWith(root + sep4)) return null;
59865
59801
  const rel = path65.relative(root, target);
59866
59802
  if (!rel || rel.startsWith("..")) return null;
59867
59803
  const id = rel.split(path65.sep)[0];
@@ -59870,8 +59806,8 @@ function personaIdFromPath(absPath, personaRoot) {
59870
59806
  function isPathWithin(dir, absPath) {
59871
59807
  const d = path65.resolve(dir);
59872
59808
  const t = path65.resolve(absPath);
59873
- const sep5 = d.endsWith(path65.sep) ? "" : path65.sep;
59874
- return t === d || t.startsWith(d + sep5);
59809
+ const sep4 = d.endsWith(path65.sep) ? "" : path65.sep;
59810
+ return t === d || t.startsWith(d + sep4);
59875
59811
  }
59876
59812
  function isPathInGuestBoundary(personaRoot, personaId2, userWorkDir, absPath) {
59877
59813
  if (userWorkDir && isPathWithin(userWorkDir, absPath)) return true;
@@ -61168,7 +61104,7 @@ function computeMethodAccess(args) {
61168
61104
  }
61169
61105
 
61170
61106
  // src/version.ts
61171
- var version = "0.2.373".length > 0 ? "0.2.373" : "dev";
61107
+ var version = "0.2.374".length > 0 ? "0.2.374" : "dev";
61172
61108
 
61173
61109
  // src/cli-probe/probe.ts
61174
61110
  var fs64 = __toESM(require("fs"), 1);
@@ -49,6 +49,10 @@ node .claude/skills/clawd-session-import/scripts/link-sessions.mjs --undo
49
49
  node .claude/skills/clawd-session-import/scripts/link-sessions.mjs --undo --apply # 真删
50
50
  ```
51
51
 
52
+ 台账里 session 已被手动删除的条目,`--undo` 只清账、不再调删除接口 —— 清掉后该会话
53
+ 才能重新出现在「关联外部 session」的候选里(扫盘去重会读这本台账,见
54
+ `superpowers/specs/2026-08-19-external-scan-entrypoint-design.md` §4)。
55
+
52
56
  ## 原理
53
57
 
54
58
  一条 clawd 会话的元数据里有 `cwd` + `toolSessionId` 两个字段,指向底层工具那条真实
@@ -485,9 +485,15 @@ async function doUndo(applyIt) {
485
485
  const res = await rpc('session:list', { limit: 100000 })
486
486
  const alive = new Set((res.sessions || []).map((s) => s.sessionId))
487
487
  const todo = entries.filter((e) => alive.has(e.sessionId))
488
- console.log(`台账 ${entries.length} 条,其中 ${todo.length} 条还在。` +
489
- (applyIt ? '正在删除……' : '(预演;加 --apply 才真删)'))
490
- for (const e of todo) console.log(` ${e.sessionId} ${e.label || ''}`)
488
+ // 台账里 session 已经不在了(多半是用户直接在 clawd UI 里删的):没什么可删,只需
489
+ // 把账清掉。扫盘去重会读这本台账(spec 2026-08-19 §4),条目留着就等于该会话永久
490
+ // 无法重新导入,而 undo 的语义本就是「撤销我的导入记录」。
491
+ const stale = entries.filter((e) => !alive.has(e.sessionId))
492
+ console.log(`台账 ${entries.length} 条,其中 ${todo.length} 条还在` +
493
+ (stale.length ? `,${stale.length} 条已不存在(只清账)` : '') + '。' +
494
+ (applyIt ? '正在处理……' : '(预演;加 --apply 才真删)'))
495
+ for (const e of todo) console.log(` 删 ${e.sessionId} ${e.label || ''}`)
496
+ for (const e of stale) console.log(` 清账 ${e.sessionId} ${e.label || ''}`)
491
497
  if (!applyIt) return
492
498
  let removed = 0
493
499
  const failed = new Set()
@@ -500,9 +506,13 @@ async function doUndo(applyIt) {
500
506
  console.error(` 删 ${e.sessionId} 失败:${err.message}`)
501
507
  }
502
508
  }
503
- const doneIds = new Set(todo.map((t) => t.sessionId).filter((id) => !failed.has(id)))
509
+ // 清账范围 = 删成功的 + 本就不存在的;删失败的留在账上,下次 undo 还能重试
510
+ const doneIds = new Set([
511
+ ...todo.map((t) => t.sessionId).filter((id) => !failed.has(id)),
512
+ ...stale.map((s) => s.sessionId),
513
+ ])
504
514
  ledgerWrite(entries.filter((e) => !doneIds.has(e.sessionId)))
505
- console.log(`已删除 ${removed} 条。原 Claude Code / Codex 的会话文件没有任何改动。`)
515
+ console.log(`已删除 ${removed} 条,清账 ${doneIds.size} 条。原 Claude Code / Codex 的会话文件没有任何改动。`)
506
516
  }
507
517
 
508
518
  // ---------------------------------------------------------------- 参数
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.373",
3
+ "version": "0.2.374",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",