@clawos-dev/clawd 0.2.181-beta.361.269b0f5 → 0.2.181

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
@@ -253,11 +253,7 @@ var init_events = __esm({
253
253
  "skill-hint",
254
254
  "meta-text",
255
255
  "attachment-skills",
256
- "attachment-deferred-tools",
257
- // clawd shift v1:scheduler fire 时 daemon 给新 cc 的首条 user message 用
258
- // <clawd-shift-fire> wrap 起头;classifyUserMessage 识别后路由到 UserMetaLine 折叠渲染,
259
- // 不显示成 user 气泡(避免 instruction 文本被当 owner 实时对话)。
260
- "clawd-shift-fire"
256
+ "attachment-deferred-tools"
261
257
  ];
262
258
  SKILL_SOURCE_VALUES = ["builtin", "global", "project", "plugin"];
263
259
  AGENT_SOURCE_VALUES = [
@@ -11244,7 +11240,6 @@ function classifyUserMessage(input) {
11244
11240
  if (SYSTEM_REMINDER_RE.test(text) || OPENSPEC_BLOCK_RE.test(text)) {
11245
11241
  return { meta: "system-reminder" };
11246
11242
  }
11247
- if (CLAWD_SHIFT_FIRE_RE.test(text)) return { meta: "clawd-shift-fire" };
11248
11243
  if (SKILL_HINT_RE.test(text)) return { meta: "skill-hint" };
11249
11244
  const flags = raw;
11250
11245
  if (flags.isMeta === true || flags.isSynthetic === true) return { meta: "meta-text" };
@@ -11459,7 +11454,7 @@ function readCurrentContent(filePath) {
11459
11454
  return null;
11460
11455
  }
11461
11456
  }
11462
- var import_node_fs10, import_node_os5, import_node_path9, TASK_NOTIFICATION_RE, TASK_ID_RE, TOOL_USE_ID_RE, SLASH_COMMAND_RE, LOCAL_COMMAND_RE, SYSTEM_REMINDER_RE, OPENSPEC_BLOCK_RE, SKILL_HINT_RE, CLAWD_SHIFT_FIRE_RE, ATTACHMENT_SILENT_SUBTYPES, ClaudeHistoryReader;
11457
+ var import_node_fs10, import_node_os5, import_node_path9, TASK_NOTIFICATION_RE, TASK_ID_RE, TOOL_USE_ID_RE, SLASH_COMMAND_RE, LOCAL_COMMAND_RE, SYSTEM_REMINDER_RE, OPENSPEC_BLOCK_RE, SKILL_HINT_RE, ATTACHMENT_SILENT_SUBTYPES, ClaudeHistoryReader;
11463
11458
  var init_claude_history = __esm({
11464
11459
  "src/tools/claude-history.ts"() {
11465
11460
  "use strict";
@@ -11476,7 +11471,6 @@ var init_claude_history = __esm({
11476
11471
  SYSTEM_REMINDER_RE = /^\s*<system-reminder\b/i;
11477
11472
  OPENSPEC_BLOCK_RE = /^\s*<!--\s*OPENSPEC\b/i;
11478
11473
  SKILL_HINT_RE = /^\s*Base directory for this skill:\s/;
11479
- CLAWD_SHIFT_FIRE_RE = /^\s*<clawd-shift-fire\b/i;
11480
11474
  ATTACHMENT_SILENT_SUBTYPES = /* @__PURE__ */ new Set([
11481
11475
  "hook_additional_context",
11482
11476
  "hook_success",
@@ -41350,6 +41344,29 @@ function buildConnectionPrompt(args) {
41350
41344
  return result;
41351
41345
  }
41352
41346
 
41347
+ // src/persona/sharing-prompt.ts
41348
+ var ATTACHMENT_SHARING_HINT = `## \u628A\u4EA7\u51FA\u6587\u4EF6\u5206\u4EAB\u7ED9\u5BF9\u65B9\uFF08attachment\uFF09
41349
+
41350
+ \u5F53\u4F60\u4EA7\u51FA\u4E86\u6587\u4EF6\u3001\u9700\u8981\u8BA9\u5BF9\u65B9\u62FF\u5230\u65F6\uFF08\u5C24\u5176 Bash \u89E3\u538B/\u751F\u6210\u7684\u6587\u4EF6\uFF0C\u6216\u8DE8\u8BBE\u5907\u88AB\u59D4\u6D3E\u65F6\u5BF9\u65B9\u5728\u53E6\u4E00\u53F0\u673A\u5668\u3001\u770B\u4E0D\u5230\u4F60\u7684\u672C\u5730\u8DEF\u5F84\uFF09\uFF0C\u7528 daemon \u7684 HTTP RPC \u628A\u6587\u4EF6\u8F6C\u6210\u4E00\u4E2A\u5E26\u7B7E\u540D\u7684\u4E0B\u8F7D URL\uFF1A
41351
+
41352
+ 1. \u5165\u7FA4\uFF08\u767B\u8BB0\u5230\u672C session \u7684\u5171\u4EAB\u6E05\u5355\uFF09\uFF1A
41353
+ \`\`\`bash
41354
+ curl -sS -X POST "$CLAWD_DAEMON_URL/api/rpc/attachment.groupAdd" \\
41355
+ -H "X-Clawd-Session-Id: $CLAWD_SESSION_ID" -H 'Content-Type: application/json' \\
41356
+ -d '{"relPath":"<\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84>"}'
41357
+ \`\`\`
41358
+ 2. \u7B7E\u540D\u62FF URL\uFF1A
41359
+ \`\`\`bash
41360
+ curl -sS -X POST "$CLAWD_DAEMON_URL/api/rpc/attachment.signUrl" \\
41361
+ -H "X-Clawd-Session-Id: $CLAWD_SESSION_ID" -H 'Content-Type: application/json' \\
41362
+ -d '{"relPath":"<\u540C\u4E00\u4E2A\u7EDD\u5BF9\u8DEF\u5F84>"}'
41363
+ \`\`\`
41364
+ \u8FD4\u56DE\u7684 \`url\` \u5C31\u662F\u5BF9\u65B9\u53EF\u76F4\u63A5\u6253\u5F00/\u4E0B\u8F7D\u7684\u94FE\u63A5\u3002
41365
+
41366
+ - \`relPath\` \u5FC5\u987B\u4F20**\u7EDD\u5BF9\u8DEF\u5F84**\uFF08\u4F60\u4EA7\u51FA\u6587\u4EF6\u7684\u771F\u5B9E\u8DEF\u5F84\uFF09\uFF0C\u4E0D\u8981\u4F20\u76F8\u5BF9\u8DEF\u5F84\u3002
41367
+ - \u53EA\u80FD\u5206\u4EAB\u4F60\u81EA\u5DF1\u5DE5\u4F5C\u76EE\u5F55\u5185\u7684\u6587\u4EF6\uFF0C\u8D8A\u754C\u4F1A\u88AB\u62D2\u3002
41368
+ - \u88AB dispatch \u63A5\u5230\u4EFB\u52A1\u3001\u8981\u56DE\u4F20\u4EA7\u7269\u65F6\uFF1A\u628A\u7B7E\u540D URL \u5199\u8FDB \`personaDispatchComplete\` \u7684 \`text\` \u91CC\uFF08\`filePaths\` \u53EA\u662F\u4F60\u672C\u673A\u7684\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u8DE8\u8BBE\u5907\u5BF9\u65B9\u6253\u4E0D\u5F00\uFF0CURL \u624D\u80FD\u6253\u5F00\uFF09\u3002`;
41369
+
41353
41370
  // src/dispatch/system-prompt.ts
41354
41371
  var DISPATCH_SYSTEM_PROMPT_HINT = `## \u59D4\u6D3E\u7ED9\u53E6\u4E00\u4E2A persona\uFF08dispatch\uFF09
41355
41372
 
@@ -41515,6 +41532,9 @@ function buildSpawnContext(state, deps) {
41515
41532
  if (dispatchMcpConfigPath2) {
41516
41533
  ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + DISPATCH_SYSTEM_PROMPT_HINT;
41517
41534
  }
41535
+ if (daemonUrl) {
41536
+ ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + ATTACHMENT_SHARING_HINT;
41537
+ }
41518
41538
  if (meta?.extraSettings) {
41519
41539
  ctx.extraSettings = meta.extraSettings;
41520
41540
  }
@@ -47323,7 +47343,7 @@ var import_node_crypto4 = require("crypto");
47323
47343
  var MAX_RUNS_PER_SHIFT = 30;
47324
47344
  var DEFAULT_TIMEOUT_MS = 30 * 6e4;
47325
47345
  var CONSECUTIVE_ERROR_THRESHOLD = 5;
47326
- var TICK_INTERVAL_MS = 1e3;
47346
+ var TICK_INTERVAL_MS = 1e4;
47327
47347
  var STORE_FLUSH_DEBOUNCE_MS = 500;
47328
47348
  var CLOCK_JUMP_THRESHOLD_MS = 5 * 6e4;
47329
47349
 
@@ -49249,6 +49269,67 @@ function removeSubscription(client, sessionId) {
49249
49269
  else client.subscribedSessions.set(sessionId, next);
49250
49270
  }
49251
49271
 
49272
+ // src/transport/rpc-adapter.ts
49273
+ var EXPOSED_RPC_METHODS = [
49274
+ "appBuilder.createProject",
49275
+ "appBuilder.listProjects",
49276
+ "appBuilder.updateProjectPort",
49277
+ "appBuilder.setProdUrl",
49278
+ // 单栏 refactor §5.6.2 stage fix:scaffold + install 完成调 startDevServer 起 dev server;
49279
+ // 中间用 reportStage 上报阶段(scaffolding / installing / failed),UI 据 stage 推帧切 PreviewPane。
49280
+ "appBuilder.startDevServer",
49281
+ "appBuilder.reportStage",
49282
+ // 单栏 §5.6.5 fix: UI 切 session 时调(assistant 不需要主动调,但暴露白名单避免误猜成 forbidden)
49283
+ "appBuilder.setActiveSession",
49284
+ // dataclaw-builder:persona 建页时取建页人身份(含 union_id)写进生成 app 的配置。
49285
+ // 路由到 handlers['auth:getIdentity'],只返 identity、绝不带 ttcToken(见 handlers/feishu-auth.ts)。
49286
+ "auth.getIdentity",
49287
+ // Persona dispatch (Task 4): A 端 dispatch MCP server 调 run(hold-and-wait 一次);
49288
+ // B 端 dispatch MCP server 调 complete(带 outcome 触发 run 端 resolve)。详见
49289
+ // protocol/src/dispatch.ts。
49290
+ "personaDispatch.run",
49291
+ "personaDispatch.complete",
49292
+ // clawd-shift v1 (spec 2026-06-24-clawd-shift):cc 子进程内的 shift MCP server 调
49293
+ // 这 5 个 method 管理 owner 的 shift(fire-and-forget 排班)。鉴权靠 body.personaId
49294
+ // (mcp-server 从 process.env.CLAWD_PERSONA_ID 读,源 = SessionFile.ownerPersonaId)。
49295
+ // handler 内 cancel/update/history 校验 createdByPersona === personaId 防越权。
49296
+ "shift-internal.add",
49297
+ "shift-internal.list",
49298
+ "shift-internal.cancel",
49299
+ "shift-internal.update",
49300
+ "shift-internal.history",
49301
+ // file-sharing:persona guest agent 把自己工作目录内的产物入群+签名分享。
49302
+ // 路径越界由 handlers/attachment.ts guardAttachmentPath(scope-based)拦截。
49303
+ "attachment.groupAdd",
49304
+ "attachment.signUrl"
49305
+ ];
49306
+ async function handleRpcRequest(input) {
49307
+ const { method, body, sessionId, dispatcher } = input;
49308
+ if (!sessionId) {
49309
+ return { status: 400, body: { ok: false, error: "MISSING_SESSION_ID", message: "X-Clawd-Session-Id header required" } };
49310
+ }
49311
+ if (!EXPOSED_RPC_METHODS.includes(method)) {
49312
+ return { status: 403, body: { ok: false, error: "METHOD_NOT_EXPOSED", message: `method "${method}" not exposed via HTTP RPC` } };
49313
+ }
49314
+ const internalMethod = method.replace(".", ":");
49315
+ const augmentedBody = {
49316
+ ...typeof body === "object" && body != null ? body : {},
49317
+ sessionId
49318
+ };
49319
+ try {
49320
+ const result = await dispatcher.dispatch(internalMethod, augmentedBody, { sessionId });
49321
+ return { status: 200, body: { ok: true, result } };
49322
+ } catch (err) {
49323
+ const e = err;
49324
+ const code = e?.code ?? "INTERNAL";
49325
+ const message = e?.message ?? String(err);
49326
+ return { status: 400, body: { ok: false, error: code, message } };
49327
+ }
49328
+ }
49329
+ function resolveExposedHandler(handlers, internalMethod) {
49330
+ return handlers[internalMethod] ?? handlers[internalMethod.replace(":", ".")];
49331
+ }
49332
+
49252
49333
  // src/transport/auth.ts
49253
49334
  init_protocol();
49254
49335
  var AuthGate = class {
@@ -50491,60 +50572,6 @@ async function importZip(buf, root) {
50491
50572
  }
50492
50573
  }
50493
50574
 
50494
- // src/transport/rpc-adapter.ts
50495
- var EXPOSED_RPC_METHODS = [
50496
- "appBuilder.createProject",
50497
- "appBuilder.listProjects",
50498
- "appBuilder.updateProjectPort",
50499
- "appBuilder.setProdUrl",
50500
- // 单栏 refactor §5.6.2 stage fix:scaffold + install 完成调 startDevServer 起 dev server;
50501
- // 中间用 reportStage 上报阶段(scaffolding / installing / failed),UI 据 stage 推帧切 PreviewPane。
50502
- "appBuilder.startDevServer",
50503
- "appBuilder.reportStage",
50504
- // 单栏 §5.6.5 fix: UI 切 session 时调(assistant 不需要主动调,但暴露白名单避免误猜成 forbidden)
50505
- "appBuilder.setActiveSession",
50506
- // dataclaw-builder:persona 建页时取建页人身份(含 union_id)写进生成 app 的配置。
50507
- // 路由到 handlers['auth:getIdentity'],只返 identity、绝不带 ttcToken(见 handlers/feishu-auth.ts)。
50508
- "auth.getIdentity",
50509
- // Persona dispatch (Task 4): A 端 dispatch MCP server 调 run(hold-and-wait 一次);
50510
- // B 端 dispatch MCP server 调 complete(带 outcome 触发 run 端 resolve)。详见
50511
- // protocol/src/dispatch.ts。
50512
- "personaDispatch.run",
50513
- "personaDispatch.complete",
50514
- // clawd-shift v1 (spec 2026-06-24-clawd-shift):cc 子进程内的 shift MCP server 调
50515
- // 这 5 个 method 管理 owner 的 shift(fire-and-forget 排班)。鉴权靠 body.personaId
50516
- // (mcp-server 从 process.env.CLAWD_PERSONA_ID 读,源 = SessionFile.ownerPersonaId)。
50517
- // handler 内 cancel/update/history 校验 createdByPersona === personaId 防越权。
50518
- "shift-internal.add",
50519
- "shift-internal.list",
50520
- "shift-internal.cancel",
50521
- "shift-internal.update",
50522
- "shift-internal.history"
50523
- ];
50524
- async function handleRpcRequest(input) {
50525
- const { method, body, sessionId, dispatcher } = input;
50526
- if (!sessionId) {
50527
- return { status: 400, body: { ok: false, error: "MISSING_SESSION_ID", message: "X-Clawd-Session-Id header required" } };
50528
- }
50529
- if (!EXPOSED_RPC_METHODS.includes(method)) {
50530
- return { status: 403, body: { ok: false, error: "METHOD_NOT_EXPOSED", message: `method "${method}" not exposed via HTTP RPC` } };
50531
- }
50532
- const internalMethod = method.replace(".", ":");
50533
- const augmentedBody = {
50534
- ...typeof body === "object" && body != null ? body : {},
50535
- sessionId
50536
- };
50537
- try {
50538
- const result = await dispatcher.dispatch(internalMethod, augmentedBody, { sessionId });
50539
- return { status: 200, body: { ok: true, result } };
50540
- } catch (err) {
50541
- const e = err;
50542
- const code = e?.code ?? "INTERNAL";
50543
- const message = e?.message ?? String(err);
50544
- return { status: 400, body: { ok: false, error: code, message } };
50545
- }
50546
- }
50547
-
50548
50575
  // src/transport/http-router.ts
50549
50576
  var RPC_ROUTE_PREFIX = "/api/rpc/";
50550
50577
  var RPC_BODY_MAX_BYTES = 1 * 1024 * 1024;
@@ -52935,6 +52962,16 @@ function personaIdFromPath(absPath, personaRoot) {
52935
52962
  const id = rel.split(path44.sep)[0];
52936
52963
  return id || null;
52937
52964
  }
52965
+ function isPathWithin(dir, absPath) {
52966
+ const d = path44.resolve(dir);
52967
+ const t = path44.resolve(absPath);
52968
+ const sep3 = d.endsWith(path44.sep) ? "" : path44.sep;
52969
+ return t === d || t.startsWith(d + sep3);
52970
+ }
52971
+ function isPathInGuestBoundary(personaRoot, personaId, userWorkDir, absPath) {
52972
+ if (userWorkDir && isPathWithin(userWorkDir, absPath)) return true;
52973
+ return personaIdFromPath(path44.resolve(absPath), personaRoot) === personaId;
52974
+ }
52938
52975
 
52939
52976
  // src/handlers/history.ts
52940
52977
  init_protocol();
@@ -53746,6 +53783,31 @@ function assertGuestAttachmentPath(ctx, absPath, personaRoot, method, usersRoot)
53746
53783
  }
53747
53784
  }
53748
53785
  function buildAttachmentHandlers(deps) {
53786
+ function guardAttachmentPath(ctx, sessionId, candidateAbs, method, nonGuestPolicy) {
53787
+ if (ctx?.principal.kind === "owner") return;
53788
+ const scope = deps.getSessionScope?.(sessionId);
53789
+ if (!scope) return;
53790
+ if (scope.kind === "persona" && scope.mode === "guest") {
53791
+ if (!deps.personaRoot) return;
53792
+ const userWorkDir = deps.usersRoot ? deriveUserWorkDir(scope.capId, deps.usersRoot) : void 0;
53793
+ if (!isPathInGuestBoundary(deps.personaRoot, scope.personaId, userWorkDir, candidateAbs)) {
53794
+ throw new ClawdError(
53795
+ ERROR_CODES.UNAUTHORIZED,
53796
+ `${method}: path outside guest work dir: ${candidateAbs}`
53797
+ );
53798
+ }
53799
+ return;
53800
+ }
53801
+ if (nonGuestPolicy === "group-acl") return;
53802
+ const cwd = deps.sessionStore?.read(sessionId)?.cwd;
53803
+ if (!cwd) return;
53804
+ if (!isPathWithin(cwd, candidateAbs)) {
53805
+ throw new ClawdError(
53806
+ ERROR_CODES.UNAUTHORIZED,
53807
+ `${method}: path outside session work dir: ${candidateAbs}`
53808
+ );
53809
+ }
53810
+ }
53749
53811
  const signUrl = async (frame, _client, ctx) => {
53750
53812
  const parsed = AttachmentSignUrlArgs.safeParse(frame);
53751
53813
  if (!parsed.success) {
@@ -53788,7 +53850,7 @@ function buildAttachmentHandlers(deps) {
53788
53850
  }
53789
53851
  const cwdAbs = import_node_path36.default.resolve(sessionFile.cwd);
53790
53852
  const candidateAbs = import_node_path36.default.isAbsolute(args.relPath) ? import_node_path36.default.resolve(args.relPath) : import_node_path36.default.resolve(cwdAbs, args.relPath);
53791
- assertGuestAttachmentPath(ctx, candidateAbs, deps.personaRoot, "attachment.signUrl", deps.usersRoot);
53853
+ guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.signUrl", "group-acl");
53792
53854
  const entries = deps.groupFileStore.list(scope, args.sessionId);
53793
53855
  const entry = entries.find((e) => {
53794
53856
  const storedAbs = import_node_path36.default.isAbsolute(e.relPath) ? import_node_path36.default.resolve(e.relPath) : import_node_path36.default.resolve(cwdAbs, e.relPath);
@@ -53827,15 +53889,18 @@ function buildAttachmentHandlers(deps) {
53827
53889
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
53828
53890
  }
53829
53891
  const args = parsed.data;
53830
- guardGuestBySessionCwd(ctx, args.sessionId, "attachment.groupAdd");
53831
53892
  const scope = deps.getSessionScope(args.sessionId);
53832
53893
  if (!scope) {
53833
53894
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `session ${args.sessionId} not found`);
53834
53895
  }
53896
+ const cwdAbs = import_node_path36.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
53897
+ const candidateAbs = import_node_path36.default.isAbsolute(args.relPath) ? import_node_path36.default.resolve(args.relPath) : import_node_path36.default.resolve(cwdAbs, args.relPath);
53898
+ guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.groupAdd", "cwd-subtree");
53899
+ const from = ctx?.principal.kind === "owner" ? "owner" : "agent";
53835
53900
  const size = 0;
53836
53901
  const entry = deps.groupFileStore.upsert(scope, args.sessionId, {
53837
53902
  relPath: args.relPath,
53838
- from: "owner",
53903
+ from,
53839
53904
  label: args.label,
53840
53905
  size,
53841
53906
  mime: lookupMime(args.relPath)
@@ -57410,7 +57475,7 @@ async function startDaemon(config) {
57410
57475
  });
57411
57476
  const rpcDispatcher = {
57412
57477
  async dispatch(method, args, _ctx) {
57413
- const handler = handlers[method];
57478
+ const handler = resolveExposedHandler(handlers, method);
57414
57479
  if (!handler) {
57415
57480
  throw Object.assign(new Error(`unknown RPC method "${method}"`), { code: "METHOD_NOT_IMPLEMENTED" });
57416
57481
  }
@@ -21252,7 +21252,7 @@ async function main() {
21252
21252
  name: external_exports.string().describe("short label shown in shift.json + future UI"),
21253
21253
  schedule: ScheduleSchema,
21254
21254
  prompt: external_exports.string().describe(
21255
- "instruction the spawned cc executes on startup (NOT a chat message from the owner). Write what you want the persona to DO or SAY at fire time, in instruction form \u2014 daemon wraps it in a <clawd-shift-fire> tag so the spawned cc treats it as a scheduled action and responds as an assistant turn (no user-bubble appears in the owner's UI)."
21255
+ "instruction the spawned cc executes on startup (NOT a chat message from the owner). Write what you want the persona to DO or SAY at fire time, in instruction form \u2014 daemon wraps it in a `<clawd-shift-fire>` tag so the spawned cc treats it as a scheduled action and responds as an assistant turn (the wrap is stripped from the owner's UI bubble)."
21256
21256
  ),
21257
21257
  targetPersona: external_exports.string().optional().describe("which persona to spawn; defaults to the calling persona"),
21258
21258
  timeoutMs: external_exports.number().optional().describe("hard timeout per fire; default 30 minutes")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.181-beta.361.269b0f5",
3
+ "version": "0.2.181",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",