@clawos-dev/clawd 0.2.177 → 0.2.178-beta.358.7f6c694

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 = [
@@ -4788,16 +4784,6 @@ var init_schemas = __esm({
4788
4784
  * 反查路径走错;按 #750 一族防线处理,跟 ownerPersonaId 同地点 strip)。
4789
4785
  */
4790
4786
  dispatchedFromSessionId: external_exports.string().min(1).optional(),
4791
- /**
4792
- * Shift fire 派生的 session 记录调 addShift 的 source cc session id(稳定 key);
4793
- * sidebar 据此把 fire 出的 session 折叠在 source session 下,跟 dispatchedFromSessionId
4794
- * 平行但渲染不同 icon(AlarmClock 区别于 dispatch 的 CornerDownRight),让 owner 一眼
4795
- * 分清"派生于人为委派"还是"派生于定时唤起"。非 shift fire session 缺省。
4796
- * 跟 dispatchedFromSessionId / forkedFromSessionId 三者互斥(同一 session 不会兼任)。
4797
- * mirror SessionFile 同 dispatchedFromSessionId 处理(shift fire 本地概念,理论上不会
4798
- * mirror,留 strip 防御)。
4799
- */
4800
- shiftFiredFromSessionId: external_exports.string().min(1).optional(),
4801
4787
  createdAt: external_exports.string().min(1),
4802
4788
  updatedAt: external_exports.string().min(1)
4803
4789
  });
@@ -11244,7 +11230,6 @@ function classifyUserMessage(input) {
11244
11230
  if (SYSTEM_REMINDER_RE.test(text) || OPENSPEC_BLOCK_RE.test(text)) {
11245
11231
  return { meta: "system-reminder" };
11246
11232
  }
11247
- if (CLAWD_SHIFT_FIRE_RE.test(text)) return { meta: "clawd-shift-fire" };
11248
11233
  if (SKILL_HINT_RE.test(text)) return { meta: "skill-hint" };
11249
11234
  const flags = raw;
11250
11235
  if (flags.isMeta === true || flags.isSynthetic === true) return { meta: "meta-text" };
@@ -11459,7 +11444,7 @@ function readCurrentContent(filePath) {
11459
11444
  return null;
11460
11445
  }
11461
11446
  }
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;
11447
+ 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
11448
  var init_claude_history = __esm({
11464
11449
  "src/tools/claude-history.ts"() {
11465
11450
  "use strict";
@@ -11476,7 +11461,6 @@ var init_claude_history = __esm({
11476
11461
  SYSTEM_REMINDER_RE = /^\s*<system-reminder\b/i;
11477
11462
  OPENSPEC_BLOCK_RE = /^\s*<!--\s*OPENSPEC\b/i;
11478
11463
  SKILL_HINT_RE = /^\s*Base directory for this skill:\s/;
11479
- CLAWD_SHIFT_FIRE_RE = /^\s*<clawd-shift-fire\b/i;
11480
11464
  ATTACHMENT_SILENT_SUBTYPES = /* @__PURE__ */ new Set([
11481
11465
  "hook_additional_context",
11482
11466
  "hook_success",
@@ -41277,22 +41261,6 @@ function composeGuestSandbox(base, userWorkDir, spawnCwd) {
41277
41261
  return s;
41278
41262
  }
41279
41263
 
41280
- // src/shift/wrap.ts
41281
- function wrapShiftFirePrompt(args) {
41282
- return [
41283
- `<clawd-shift-fire target-persona="${escapeAttr(args.targetPersona)}">`,
41284
- "This is a scheduled shift fire \u2014 not a live user message.",
41285
- "Respond as an assistant turn (start your output directly).",
41286
- "",
41287
- "Instruction:",
41288
- args.prompt,
41289
- "</clawd-shift-fire>"
41290
- ].join("\n");
41291
- }
41292
- function escapeAttr(v2) {
41293
- return v2.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
41294
- }
41295
-
41296
41264
  // src/session/reducer.ts
41297
41265
  init_runtime();
41298
41266
 
@@ -41345,11 +41313,34 @@ function buildConnectionPrompt(args) {
41345
41313
  }
41346
41314
  if (args.workDir) {
41347
41315
  result += `
41348
- \u4F60\u7684\u6587\u4EF6\u5DE5\u4F5C\u76EE\u5F55\u662F ${args.workDir}\uFF0C\u8FD9\u4E2A\u76EE\u5F55\u5BF9\u4F60\u53EF\u8BFB\u53EF\u5199\uFF0C\u6587\u4EF6\u7684\u8BFB\u53D6/\u521B\u5EFA/\u4FEE\u6539\u90FD\u5728\u8FD9\u91CC\u8FDB\u884C\u3002persona \u76EE\u5F55\u5BF9\u4F60\u53EA\u8BFB\uFF0C\u4E0D\u8981\u5F80\u91CC\u5199\u3002`;
41316
+ \u4F60\u7684\u6587\u4EF6\u5DE5\u4F5C\u76EE\u5F55\u662F ${args.workDir}\uFF0C\u6240\u6709\u6587\u4EF6\u8BFB\u5199\u90FD\u653E\u5728\u8FD9\u4E2A\u76EE\u5F55\u4E0B\u3002persona \u76EE\u5F55\u5BF9\u4F60\u53EA\u8BFB\uFF0C\u4E0D\u8981\u5F80\u91CC\u5199\u3002`;
41349
41317
  }
41350
41318
  return result;
41351
41319
  }
41352
41320
 
41321
+ // src/persona/sharing-prompt.ts
41322
+ var ATTACHMENT_SHARING_HINT = `## \u628A\u4EA7\u51FA\u6587\u4EF6\u5206\u4EAB\u7ED9\u5BF9\u65B9\uFF08attachment\uFF09
41323
+
41324
+ \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
41325
+
41326
+ 1. \u5165\u7FA4\uFF08\u767B\u8BB0\u5230\u672C session \u7684\u5171\u4EAB\u6E05\u5355\uFF09\uFF1A
41327
+ \`\`\`bash
41328
+ curl -sS -X POST "$CLAWD_DAEMON_URL/api/rpc/attachment.groupAdd" \\
41329
+ -H "X-Clawd-Session-Id: $CLAWD_SESSION_ID" -H 'Content-Type: application/json' \\
41330
+ -d '{"relPath":"<\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84>"}'
41331
+ \`\`\`
41332
+ 2. \u7B7E\u540D\u62FF URL\uFF1A
41333
+ \`\`\`bash
41334
+ curl -sS -X POST "$CLAWD_DAEMON_URL/api/rpc/attachment.signUrl" \\
41335
+ -H "X-Clawd-Session-Id: $CLAWD_SESSION_ID" -H 'Content-Type: application/json' \\
41336
+ -d '{"relPath":"<\u540C\u4E00\u4E2A\u7EDD\u5BF9\u8DEF\u5F84>"}'
41337
+ \`\`\`
41338
+ \u8FD4\u56DE\u7684 \`url\` \u5C31\u662F\u5BF9\u65B9\u53EF\u76F4\u63A5\u6253\u5F00/\u4E0B\u8F7D\u7684\u94FE\u63A5\u3002
41339
+
41340
+ - \`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
41341
+ - \u53EA\u80FD\u5206\u4EAB\u4F60\u81EA\u5DF1\u5DE5\u4F5C\u76EE\u5F55\u5185\u7684\u6587\u4EF6\uFF0C\u8D8A\u754C\u4F1A\u88AB\u62D2\u3002
41342
+ - \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`;
41343
+
41353
41344
  // src/dispatch/system-prompt.ts
41354
41345
  var DISPATCH_SYSTEM_PROMPT_HINT = `## \u59D4\u6D3E\u7ED9\u53E6\u4E00\u4E2A persona\uFF08dispatch\uFF09
41355
41346
 
@@ -41515,6 +41506,9 @@ function buildSpawnContext(state, deps) {
41515
41506
  if (dispatchMcpConfigPath2) {
41516
41507
  ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + DISPATCH_SYSTEM_PROMPT_HINT;
41517
41508
  }
41509
+ if (daemonUrl) {
41510
+ ctx.extraSystemPrompt = (ctx.extraSystemPrompt ? ctx.extraSystemPrompt + "\n\n" : "") + ATTACHMENT_SHARING_HINT;
41511
+ }
41518
41512
  if (meta?.extraSettings) {
41519
41513
  ctx.extraSettings = meta.extraSettings;
41520
41514
  }
@@ -43274,14 +43268,10 @@ var SessionManager = class {
43274
43268
  // 强制走 deps.store(即 default scope),不走 scopeForFile:mirror 物理落 default 即可。
43275
43269
  // 远端 creator / origin 字段原值保留(不被本机 ctx 派生覆写)。
43276
43270
  //
43277
- // strip 远端 personaId / dispatchedFromSessionId / shiftFiredFromSessionId:mirror
43278
- // SessionFile 上这些字段都指向朋友 daemon 上的本地 id,本机不持有对应 persona /
43279
- // 源 session。留着会让按这些字段反查的本机消费者(MyPersonasSection 按 ownerPersonaId
43280
- // 反查 persona、sidebar 按 dispatchedFromSessionId / shiftFiredFromSessionId 折叠
43281
- // sub-session)走错分支。写入前清掉。#750 一族防线。
43282
- //
43283
- // shift fire 是本地 daemon scheduler 行为,理论上不产生 mirror SessionFile,但留 strip
43284
- // 防御(同 dispatchedFromSessionId 处理姿势对齐)。
43271
+ // strip 远端 personaId / dispatchedFromSessionId:mirror SessionFile 上这两个字段都指向
43272
+ // 朋友 daemon 上的本地 id,本机不持有对应 persona / 源 session。留着会让按这些字段反查的
43273
+ // 本机消费者(MyPersonasSection 按 ownerPersonaId 反查 persona、sidebar 按
43274
+ // dispatchedFromSessionId 折叠 sub-session)走错分支。写入前清掉。#750 一族防线。
43285
43275
  //
43286
43276
  // 与 manager.create / manager.update 区别:
43287
43277
  // - 不走 newRunner / attachObserver / cwd 校验(mirror 是纯 metadata 层)
@@ -43290,7 +43280,6 @@ var SessionManager = class {
43290
43280
  const {
43291
43281
  ownerPersonaId: _strippedPid,
43292
43282
  dispatchedFromSessionId: _strippedDsp,
43293
- shiftFiredFromSessionId: _strippedSft,
43294
43283
  ...rest
43295
43284
  } = file;
43296
43285
  return this.deps.store.write(rest);
@@ -43993,7 +43982,6 @@ var SessionManager = class {
43993
43982
  cwd,
43994
43983
  tool: "claude",
43995
43984
  ownerPersonaId: args.targetPersona,
43996
- ...args.sourceSessionId ? { shiftFiredFromSessionId: args.sourceSessionId } : {},
43997
43985
  createdAt: now,
43998
43986
  updatedAt: now
43999
43987
  };
@@ -44029,16 +44017,7 @@ var SessionManager = class {
44029
44017
  });
44030
44018
  const runner = this.ensureRunnerForScope(file, scope);
44031
44019
  this.deps.logger?.info("shift.createShiftSession.runner-ready", { sessionId });
44032
- runner.input({
44033
- kind: "command",
44034
- command: {
44035
- kind: "send",
44036
- text: wrapShiftFirePrompt({
44037
- prompt: args.prompt,
44038
- targetPersona: args.targetPersona
44039
- })
44040
- }
44041
- });
44020
+ runner.input({ kind: "command", command: { kind: "send", text: args.prompt } });
44042
44021
  const kill = () => {
44043
44022
  const r = this.runners.get(sessionId);
44044
44023
  if (!r) return;
@@ -47560,8 +47539,7 @@ function createShiftScheduler(deps) {
47560
47539
  shiftId: shift.id,
47561
47540
  targetPersona: shift.targetPersona,
47562
47541
  prompt: shift.prompt,
47563
- timeoutMs,
47564
- ...shift.createdBySessionId ? { sourceSessionId: shift.createdBySessionId } : {}
47542
+ timeoutMs
47565
47543
  });
47566
47544
  sessionId = spawned.sessionId;
47567
47545
  const startRun = {
@@ -47615,12 +47593,8 @@ function createShiftScheduler(deps) {
47615
47593
 
47616
47594
  // src/shift/spawn.ts
47617
47595
  function createShiftSpawnFn(deps) {
47618
- return async ({ targetPersona, prompt, timeoutMs, sourceSessionId }) => {
47619
- const created = deps.sessionManager.createShiftSession({
47620
- targetPersona,
47621
- prompt,
47622
- ...sourceSessionId ? { sourceSessionId } : {}
47623
- });
47596
+ return async ({ targetPersona, prompt, timeoutMs }) => {
47597
+ const created = deps.sessionManager.createShiftSession({ targetPersona, prompt });
47624
47598
  let resolveOuter = () => {
47625
47599
  };
47626
47600
  const ended = new Promise((r) => {
@@ -47674,14 +47648,12 @@ function validateSchedule(schedule) {
47674
47648
  function buildShiftInternalHandlers(deps) {
47675
47649
  const add = async (frame) => {
47676
47650
  const persona = readPersonaId(frame);
47677
- const callerSessionId = typeof frame.sessionId === "string" ? frame.sessionId : void 0;
47678
47651
  const { type: _t, requestId: _r, personaId: _p, sessionId: _s, ...rest } = frame;
47679
47652
  const args = rest;
47680
47653
  validateSchedule(args.schedule);
47681
47654
  const shift = deps.store.add({
47682
47655
  ownerId: deps.ownerIdProvider(),
47683
47656
  createdByPersona: persona,
47684
- ...callerSessionId ? { createdBySessionId: callerSessionId } : {},
47685
47657
  name: args.name,
47686
47658
  enabled: true,
47687
47659
  schedule: args.schedule,
@@ -49249,6 +49221,67 @@ function removeSubscription(client, sessionId) {
49249
49221
  else client.subscribedSessions.set(sessionId, next);
49250
49222
  }
49251
49223
 
49224
+ // src/transport/rpc-adapter.ts
49225
+ var EXPOSED_RPC_METHODS = [
49226
+ "appBuilder.createProject",
49227
+ "appBuilder.listProjects",
49228
+ "appBuilder.updateProjectPort",
49229
+ "appBuilder.setProdUrl",
49230
+ // 单栏 refactor §5.6.2 stage fix:scaffold + install 完成调 startDevServer 起 dev server;
49231
+ // 中间用 reportStage 上报阶段(scaffolding / installing / failed),UI 据 stage 推帧切 PreviewPane。
49232
+ "appBuilder.startDevServer",
49233
+ "appBuilder.reportStage",
49234
+ // 单栏 §5.6.5 fix: UI 切 session 时调(assistant 不需要主动调,但暴露白名单避免误猜成 forbidden)
49235
+ "appBuilder.setActiveSession",
49236
+ // dataclaw-builder:persona 建页时取建页人身份(含 union_id)写进生成 app 的配置。
49237
+ // 路由到 handlers['auth:getIdentity'],只返 identity、绝不带 ttcToken(见 handlers/feishu-auth.ts)。
49238
+ "auth.getIdentity",
49239
+ // Persona dispatch (Task 4): A 端 dispatch MCP server 调 run(hold-and-wait 一次);
49240
+ // B 端 dispatch MCP server 调 complete(带 outcome 触发 run 端 resolve)。详见
49241
+ // protocol/src/dispatch.ts。
49242
+ "personaDispatch.run",
49243
+ "personaDispatch.complete",
49244
+ // clawd-shift v1 (spec 2026-06-24-clawd-shift):cc 子进程内的 shift MCP server 调
49245
+ // 这 5 个 method 管理 owner 的 shift(fire-and-forget 排班)。鉴权靠 body.personaId
49246
+ // (mcp-server 从 process.env.CLAWD_PERSONA_ID 读,源 = SessionFile.ownerPersonaId)。
49247
+ // handler 内 cancel/update/history 校验 createdByPersona === personaId 防越权。
49248
+ "shift-internal.add",
49249
+ "shift-internal.list",
49250
+ "shift-internal.cancel",
49251
+ "shift-internal.update",
49252
+ "shift-internal.history",
49253
+ // file-sharing:persona guest agent 把自己工作目录内的产物入群+签名分享。
49254
+ // 路径越界由 handlers/attachment.ts guardAttachmentPath(scope-based)拦截。
49255
+ "attachment.groupAdd",
49256
+ "attachment.signUrl"
49257
+ ];
49258
+ async function handleRpcRequest(input) {
49259
+ const { method, body, sessionId, dispatcher } = input;
49260
+ if (!sessionId) {
49261
+ return { status: 400, body: { ok: false, error: "MISSING_SESSION_ID", message: "X-Clawd-Session-Id header required" } };
49262
+ }
49263
+ if (!EXPOSED_RPC_METHODS.includes(method)) {
49264
+ return { status: 403, body: { ok: false, error: "METHOD_NOT_EXPOSED", message: `method "${method}" not exposed via HTTP RPC` } };
49265
+ }
49266
+ const internalMethod = method.replace(".", ":");
49267
+ const augmentedBody = {
49268
+ ...typeof body === "object" && body != null ? body : {},
49269
+ sessionId
49270
+ };
49271
+ try {
49272
+ const result = await dispatcher.dispatch(internalMethod, augmentedBody, { sessionId });
49273
+ return { status: 200, body: { ok: true, result } };
49274
+ } catch (err) {
49275
+ const e = err;
49276
+ const code = e?.code ?? "INTERNAL";
49277
+ const message = e?.message ?? String(err);
49278
+ return { status: 400, body: { ok: false, error: code, message } };
49279
+ }
49280
+ }
49281
+ function resolveExposedHandler(handlers, internalMethod) {
49282
+ return handlers[internalMethod] ?? handlers[internalMethod.replace(":", ".")];
49283
+ }
49284
+
49252
49285
  // src/transport/auth.ts
49253
49286
  init_protocol();
49254
49287
  var AuthGate = class {
@@ -50491,60 +50524,6 @@ async function importZip(buf, root) {
50491
50524
  }
50492
50525
  }
50493
50526
 
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
50527
  // src/transport/http-router.ts
50549
50528
  var RPC_ROUTE_PREFIX = "/api/rpc/";
50550
50529
  var RPC_BODY_MAX_BYTES = 1 * 1024 * 1024;
@@ -52935,6 +52914,16 @@ function personaIdFromPath(absPath, personaRoot) {
52935
52914
  const id = rel.split(path44.sep)[0];
52936
52915
  return id || null;
52937
52916
  }
52917
+ function isPathWithin(dir, absPath) {
52918
+ const d = path44.resolve(dir);
52919
+ const t = path44.resolve(absPath);
52920
+ const sep3 = d.endsWith(path44.sep) ? "" : path44.sep;
52921
+ return t === d || t.startsWith(d + sep3);
52922
+ }
52923
+ function isPathInGuestBoundary(personaRoot, personaId, userWorkDir, absPath) {
52924
+ if (userWorkDir && isPathWithin(userWorkDir, absPath)) return true;
52925
+ return personaIdFromPath(path44.resolve(absPath), personaRoot) === personaId;
52926
+ }
52938
52927
 
52939
52928
  // src/handlers/history.ts
52940
52929
  init_protocol();
@@ -53746,6 +53735,31 @@ function assertGuestAttachmentPath(ctx, absPath, personaRoot, method, usersRoot)
53746
53735
  }
53747
53736
  }
53748
53737
  function buildAttachmentHandlers(deps) {
53738
+ function guardAttachmentPath(ctx, sessionId, candidateAbs, method, nonGuestPolicy) {
53739
+ if (ctx?.principal.kind === "owner") return;
53740
+ const scope = deps.getSessionScope?.(sessionId);
53741
+ if (!scope) return;
53742
+ if (scope.kind === "persona" && scope.mode === "guest") {
53743
+ if (!deps.personaRoot) return;
53744
+ const userWorkDir = deps.usersRoot ? deriveUserWorkDir(scope.capId, deps.usersRoot) : void 0;
53745
+ if (!isPathInGuestBoundary(deps.personaRoot, scope.personaId, userWorkDir, candidateAbs)) {
53746
+ throw new ClawdError(
53747
+ ERROR_CODES.UNAUTHORIZED,
53748
+ `${method}: path outside guest work dir: ${candidateAbs}`
53749
+ );
53750
+ }
53751
+ return;
53752
+ }
53753
+ if (nonGuestPolicy === "group-acl") return;
53754
+ const cwd = deps.sessionStore?.read(sessionId)?.cwd;
53755
+ if (!cwd) return;
53756
+ if (!isPathWithin(cwd, candidateAbs)) {
53757
+ throw new ClawdError(
53758
+ ERROR_CODES.UNAUTHORIZED,
53759
+ `${method}: path outside session work dir: ${candidateAbs}`
53760
+ );
53761
+ }
53762
+ }
53749
53763
  const signUrl = async (frame, _client, ctx) => {
53750
53764
  const parsed = AttachmentSignUrlArgs.safeParse(frame);
53751
53765
  if (!parsed.success) {
@@ -53788,7 +53802,7 @@ function buildAttachmentHandlers(deps) {
53788
53802
  }
53789
53803
  const cwdAbs = import_node_path36.default.resolve(sessionFile.cwd);
53790
53804
  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);
53805
+ guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.signUrl", "group-acl");
53792
53806
  const entries = deps.groupFileStore.list(scope, args.sessionId);
53793
53807
  const entry = entries.find((e) => {
53794
53808
  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 +53841,18 @@ function buildAttachmentHandlers(deps) {
53827
53841
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, parsed.error.message);
53828
53842
  }
53829
53843
  const args = parsed.data;
53830
- guardGuestBySessionCwd(ctx, args.sessionId, "attachment.groupAdd");
53831
53844
  const scope = deps.getSessionScope(args.sessionId);
53832
53845
  if (!scope) {
53833
53846
  throw new ClawdError(ERROR_CODES.VALIDATION_ERROR, `session ${args.sessionId} not found`);
53834
53847
  }
53848
+ const cwdAbs = import_node_path36.default.resolve(deps.sessionStore?.read(args.sessionId)?.cwd ?? ".");
53849
+ const candidateAbs = import_node_path36.default.isAbsolute(args.relPath) ? import_node_path36.default.resolve(args.relPath) : import_node_path36.default.resolve(cwdAbs, args.relPath);
53850
+ guardAttachmentPath(ctx, args.sessionId, candidateAbs, "attachment.groupAdd", "cwd-subtree");
53851
+ const from = ctx?.principal.kind === "owner" ? "owner" : "agent";
53835
53852
  const size = 0;
53836
53853
  const entry = deps.groupFileStore.upsert(scope, args.sessionId, {
53837
53854
  relPath: args.relPath,
53838
- from: "owner",
53855
+ from,
53839
53856
  label: args.label,
53840
53857
  size,
53841
53858
  mime: lookupMime(args.relPath)
@@ -55660,11 +55677,22 @@ function buildAppBuilderHandlers(deps) {
55660
55677
  }
55661
55678
 
55662
55679
  // src/handlers/shift.ts
55663
- function daemonShiftToUiShift(d, _nowMs) {
55664
- if (d.schedule.kind === "at") {
55665
- const atMs = Date.parse(d.schedule.at);
55666
- if (Number.isNaN(atMs)) return null;
55667
- }
55680
+ var UI_DEFAULT_DURATION_MS = 30 * 6e4;
55681
+ var UI_MAX_DURATION_MS = 60 * 6e4;
55682
+ function pickUiDurationMs(timeoutMs) {
55683
+ return Math.min(timeoutMs ?? UI_DEFAULT_DURATION_MS, UI_MAX_DURATION_MS);
55684
+ }
55685
+ function everyMsToCron(everyMs) {
55686
+ if (everyMs <= 6e4) return "* * * * *";
55687
+ const minutes = Math.floor(everyMs / 6e4);
55688
+ if (minutes < 60) return `*/${minutes} * * * *`;
55689
+ const hours = Math.floor(minutes / 60);
55690
+ if (hours < 24) return `0 */${hours} * * *`;
55691
+ const days = Math.max(1, Math.floor(hours / 24));
55692
+ return `0 0 */${days} * *`;
55693
+ }
55694
+ function daemonShiftToUiShift(d, nowMs) {
55695
+ const durationMs = pickUiDurationMs(d.timeoutMs);
55668
55696
  const noteParts = [d.name];
55669
55697
  if (d.prompt && d.prompt.length > 0) {
55670
55698
  const promptPreview = d.prompt.length > 120 ? `${d.prompt.slice(0, 117)}\u2026` : d.prompt;
@@ -55672,12 +55700,49 @@ function daemonShiftToUiShift(d, _nowMs) {
55672
55700
  }
55673
55701
  if (!d.enabled) noteParts.push("(disabled)");
55674
55702
  const note = noteParts.join(" \u2014 ");
55675
- return {
55676
- id: d.id,
55677
- agentId: d.targetPersona,
55678
- schedule: d.schedule,
55679
- note
55680
- };
55703
+ switch (d.schedule.kind) {
55704
+ case "at": {
55705
+ const startMs = Date.parse(d.schedule.at);
55706
+ if (Number.isNaN(startMs)) return null;
55707
+ if (startMs + durationMs <= nowMs) return null;
55708
+ return {
55709
+ id: d.id,
55710
+ agentId: d.targetPersona,
55711
+ schedule: {
55712
+ kind: "single",
55713
+ start: new Date(startMs).toISOString(),
55714
+ end: new Date(startMs + durationMs).toISOString()
55715
+ },
55716
+ note
55717
+ };
55718
+ }
55719
+ case "cron": {
55720
+ return {
55721
+ id: d.id,
55722
+ agentId: d.targetPersona,
55723
+ schedule: {
55724
+ kind: "recurring",
55725
+ cron: d.schedule.expr,
55726
+ ...d.schedule.tz ? { tz: d.schedule.tz } : {},
55727
+ durationMs
55728
+ },
55729
+ note
55730
+ };
55731
+ }
55732
+ case "every": {
55733
+ const cron = everyMsToCron(d.schedule.everyMs);
55734
+ return {
55735
+ id: d.id,
55736
+ agentId: d.targetPersona,
55737
+ schedule: {
55738
+ kind: "recurring",
55739
+ cron,
55740
+ durationMs
55741
+ },
55742
+ note
55743
+ };
55744
+ }
55745
+ }
55681
55746
  }
55682
55747
  function buildShiftHandlers(deps) {
55683
55748
  return {
@@ -57405,7 +57470,7 @@ async function startDaemon(config) {
57405
57470
  });
57406
57471
  const rpcDispatcher = {
57407
57472
  async dispatch(method, args, _ctx) {
57408
- const handler = handlers[method];
57473
+ const handler = resolveExposedHandler(handlers, method);
57409
57474
  if (!handler) {
57410
57475
  throw Object.assign(new Error(`unknown RPC method "${method}"`), { code: "METHOD_NOT_IMPLEMENTED" });
57411
57476
  }
@@ -21251,9 +21251,7 @@ async function main() {
21251
21251
  inputSchema: {
21252
21252
  name: external_exports.string().describe("short label shown in shift.json + future UI"),
21253
21253
  schedule: ScheduleSchema,
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)."
21256
- ),
21254
+ prompt: external_exports.string().describe("first user message injected when the new cc starts"),
21257
21255
  targetPersona: external_exports.string().optional().describe("which persona to spawn; defaults to the calling persona"),
21258
21256
  timeoutMs: external_exports.number().optional().describe("hard timeout per fire; default 30 minutes")
21259
21257
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawos-dev/clawd",
3
- "version": "0.2.177",
3
+ "version": "0.2.178-beta.358.7f6c694",
4
4
  "description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
5
5
  "type": "module",
6
6
  "license": "MIT",