@clawos-dev/clawd 0.2.248 → 0.2.250
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 +166 -36
- package/dist/dispatch/mcp-server.cjs +37 -9
- package/dist/share-md-viewer-error.html +1 -1
- package/dist/share-md-viewer.html +13 -13
- package/dist/share-ui/assets/{guest-DeJiT1im.js → guest-BsIuA95-.js} +124 -124
- package/dist/share-ui/assets/guest-k2RPEdy5.css +32 -0
- package/dist/share-ui/guest.html +2 -2
- package/package.json +1 -1
- package/dist/share-ui/assets/guest-E3E4Ik_Z.css +0 -32
package/dist/cli.cjs
CHANGED
|
@@ -5132,12 +5132,21 @@ var init_schemas = __esm({
|
|
|
5132
5132
|
SessionListArgs = external_exports.object({
|
|
5133
5133
|
/** true = 只返回 mirror(originOwnerPrincipalId 非空,即「我在朋友 daemon 上创建」的本机存证)。 */
|
|
5134
5134
|
originOnly: external_exports.boolean().optional(),
|
|
5135
|
+
/**
|
|
5136
|
+
* 按 own persona 窄化(2026-07-21 self-persona-sessions 追加):只留
|
|
5137
|
+
* `s.ownerPersonaId === args.ownerPersonaId` 的 sessions。
|
|
5138
|
+
* - mirror 行(`ownerPersonaId` 恒 null)天然排除
|
|
5139
|
+
* - 与 `originOnly=true` 同传恒返 0
|
|
5140
|
+
* - 老 daemon 不识别时忽略字段返回全量,客户端消费侧本就自带过滤/截断,天然向后兼容
|
|
5141
|
+
*/
|
|
5142
|
+
ownerPersonaId: external_exports.string().min(1).optional(),
|
|
5135
5143
|
/** 按 updatedAt desc(list 的既有排序)截断前 N 条。 */
|
|
5136
5144
|
limit: external_exports.number().int().positive().optional(),
|
|
5137
5145
|
/**
|
|
5138
|
-
* 分页起点(2026-07-18 android-tasks-tab 迭代追加):originOnly 过滤后、
|
|
5139
|
-
*
|
|
5140
|
-
*
|
|
5146
|
+
* 分页起点(2026-07-18 android-tasks-tab 迭代追加):originOnly / ownerPersonaId 过滤后、
|
|
5147
|
+
* limit 截断前跳过前 N 条。与 limit 组合 = updatedAt desc 序上的一页。响应带 total
|
|
5148
|
+
* (过滤后总数,见 handler)供客户端判 hasMore。老 daemon 不识别时忽略 → 回全量,
|
|
5149
|
+
* 客户端按全量兼容。
|
|
5141
5150
|
*/
|
|
5142
5151
|
offset: external_exports.number().int().nonnegative().optional()
|
|
5143
5152
|
});
|
|
@@ -6204,7 +6213,12 @@ var init_dispatch = __esm({
|
|
|
6204
6213
|
})
|
|
6205
6214
|
]);
|
|
6206
6215
|
DispatchRunArgsSchema = external_exports.object({
|
|
6207
|
-
|
|
6216
|
+
// 新开必填;续派(dispatchId 非空)时可省略——targetPersonaId 从台账行取。
|
|
6217
|
+
targetPersona: external_exports.string().min(1).optional(),
|
|
6218
|
+
// 续派(spec 2026-07-21,单 dispatchId 语义:一件事一行一 id):非空 = 把 prompt
|
|
6219
|
+
// 作为新 turn 打进该 dispatch 的原 worker session 接着干(台账行复位 running)。
|
|
6220
|
+
// 省略 = 新开一件事,daemon 铸新 id。
|
|
6221
|
+
dispatchId: external_exports.string().min(1).optional(),
|
|
6208
6222
|
prompt: external_exports.string(),
|
|
6209
6223
|
// 跨设备 dispatch:非空 = A 角色,转发到该 contact deviceId 的 peer daemon;
|
|
6210
6224
|
// 省略 = 本地 dispatch / B 角色本地执行。A 转发给 B 时 body 不带此字段。
|
|
@@ -6214,6 +6228,10 @@ var init_dispatch = __esm({
|
|
|
6214
6228
|
// tools/claude.ts CLAUDE_MODEL_IDS);handler 层做二次校验,非法值 fail-fast。
|
|
6215
6229
|
// protocol 层保持 string 便于未来跨 tool 扩展(届时 union 收合法字面量)。
|
|
6216
6230
|
model: external_exports.string().min(1).optional()
|
|
6231
|
+
}).refine((v2) => v2.targetPersona !== void 0 || v2.dispatchId !== void 0, {
|
|
6232
|
+
message: "either targetPersona (new dispatch) or dispatchId (follow-up) is required"
|
|
6233
|
+
}).refine((v2) => !(v2.dispatchId !== void 0 && v2.targetDeviceId !== void 0), {
|
|
6234
|
+
message: "cross-device dispatch cannot be continued (dispatchId + targetDeviceId are mutually exclusive)"
|
|
6217
6235
|
});
|
|
6218
6236
|
DispatchCompleteArgsSchema = external_exports.object({
|
|
6219
6237
|
dispatchId: external_exports.string().min(1),
|
|
@@ -41917,6 +41935,10 @@ tool \u4F1A**\u7ACB\u5373\u8FD4\u56DE** dispatchId\uFF08\u5F02\u6B65\u59D4\u6D3E
|
|
|
41917
41935
|
- \u4E00\u53E5\u8BDD\u91CC\u591A\u4E2A \`@persona/<id>\` \u65F6\u9009\u7B2C\u4E00\u4E2A\uFF08tool \u4E00\u6B21\u53EA\u5904\u7406\u4E00\u4E2A target\uFF09
|
|
41918
41936
|
- \u59D4\u6D3E\u5931\u8D25\uFF08tool_result.is_error=true\uFF09\u65F6\u5982\u5B9E\u544A\u8BC9\u7528\u6237\u539F\u56E0\uFF0C**\u4E0D\u8981\u91CD\u8BD5**
|
|
41919
41937
|
|
|
41938
|
+
### \u7EED\u6D3E\uFF08\u540C\u4E00\u4EF6\u4E8B\u7684\u540E\u7EED\u6307\u4EE4\uFF09
|
|
41939
|
+
|
|
41940
|
+
\u7528\u6237\u5BF9\u4E00\u4EF6**\u5DF2\u59D4\u6D3E**\u7684\u4E8B\u4E0B\u540E\u7EED\u6307\u4EE4\u65F6\uFF0C\u8C03 \`personaDispatch({ dispatchId: <\u8BE5\u4E8B\u7684 dispatchId>, prompt: <\u65B0\u6307\u4EE4> })\`\uFF08targetPersona \u53EF\u7701\u7565\uFF09\u2014\u2014\u65B0\u6307\u4EE4\u4F1A\u8FDB\u5165\u539F worker session \u63A5\u7740\u5E72\uFF0C\u4E0A\u4E0B\u6587\u4E0D\u4E22\u3002dispatchId \u7528\u4F60\u9996\u6B21\u59D4\u6D3E\u65F6\u62FF\u5230\u7684\u90A3\u4E2A\uFF1B\u4E0D\u786E\u5B9A\u662F\u54EA\u4EF6\u4E8B\u5C31\u5148\u8C03 \`personaDispatchList\` \u67E5\u3002
|
|
41941
|
+
|
|
41920
41942
|
### \u8DE8\u8BBE\u5907\u59D4\u6D3E\uFF08remote persona\uFF09
|
|
41921
41943
|
|
|
41922
41944
|
\u5F53 @ token \u5F62\u5982 \`@persona/<id>@<deviceId>\`\uFF08\u7B2C\u4E8C\u6BB5\u662F\u8054\u7CFB\u4EBA**\u8BBE\u5907\u7684 deviceId**\uFF0CUI \u9009\u8054\u7CFB\u4EBA\u65F6\u5DF2\u89E3\u6790\u597D\u586B\u5165\uFF09\u65F6\uFF0C\u662F\u628A\u4EFB\u52A1\u59D4\u6D3E\u7ED9\u90A3\u53F0\u8BBE\u5907\u4E0A\u7684 persona\u3002\u9664 targetPersona / prompt \u5916\u518D\u5E26\u4E00\u4E2A\u53C2\u6570\uFF1A
|
|
@@ -44126,9 +44148,25 @@ var SessionManager = class {
|
|
|
44126
44148
|
if (pending.length === 0) return;
|
|
44127
44149
|
const state = this.tryGetState(sourceSessionId);
|
|
44128
44150
|
if (state && compressStatus(state.status) === "running") return;
|
|
44151
|
+
if (!this.injectIntoSession(sourceSessionId, formatDeliveryText(pending))) return;
|
|
44152
|
+
const nowIso3 = new Date(this.deps.now?.() ?? Date.now()).toISOString();
|
|
44153
|
+
for (const r of pending) store.update(r.dispatchId, { deliveredAt: nowIso3 });
|
|
44154
|
+
this.deps.logger?.info("dispatch.deliver.ok", {
|
|
44155
|
+
sourceSessionId,
|
|
44156
|
+
count: pending.length,
|
|
44157
|
+
dispatchIds: pending.map((r) => r.dispatchId)
|
|
44158
|
+
});
|
|
44159
|
+
}
|
|
44160
|
+
/**
|
|
44161
|
+
* 把一段 plain text 以 owner sender 注入指定 session 当新 turn——dispatch 结果投递
|
|
44162
|
+
* (deliverPending)与续派任务包(handlers/persona-dispatch 经 sendFollowUp 闭包)共用。
|
|
44163
|
+
* session 文件不存在或 send 抛错 → false(记 log,不 throw)。直接 runner.input
|
|
44164
|
+
* (无 collector),frames 经 routeFromRunner 正常广播上 wire。
|
|
44165
|
+
*/
|
|
44166
|
+
injectIntoSession(sessionId, text) {
|
|
44129
44167
|
if (!this.deps.ownerPrincipalId) {
|
|
44130
|
-
this.deps.logger?.warn("dispatch.
|
|
44131
|
-
return;
|
|
44168
|
+
this.deps.logger?.warn("dispatch.inject.no-owner-principal", { sessionId });
|
|
44169
|
+
return false;
|
|
44132
44170
|
}
|
|
44133
44171
|
const sender = principalToSender(
|
|
44134
44172
|
makeOwnerPrincipal(
|
|
@@ -44138,34 +44176,24 @@ var SessionManager = class {
|
|
|
44138
44176
|
)
|
|
44139
44177
|
);
|
|
44140
44178
|
try {
|
|
44141
|
-
const file = this.findOwnedSession(
|
|
44179
|
+
const file = this.findOwnedSession(sessionId);
|
|
44142
44180
|
if (!file) {
|
|
44143
|
-
this.deps.logger?.info("dispatch.
|
|
44144
|
-
return;
|
|
44181
|
+
this.deps.logger?.info("dispatch.inject.session-gone", { sessionId });
|
|
44182
|
+
return false;
|
|
44145
44183
|
}
|
|
44146
44184
|
const runner = this.ensureRunnerForScope(file, this.scopeForFile(file));
|
|
44147
44185
|
runner.input({
|
|
44148
44186
|
kind: "command",
|
|
44149
|
-
command: {
|
|
44150
|
-
kind: "send",
|
|
44151
|
-
text: formatDeliveryText(pending),
|
|
44152
|
-
senderPrincipal: sender
|
|
44153
|
-
}
|
|
44187
|
+
command: { kind: "send", text, senderPrincipal: sender }
|
|
44154
44188
|
});
|
|
44189
|
+
return true;
|
|
44155
44190
|
} catch (err) {
|
|
44156
|
-
this.deps.logger?.warn("dispatch.
|
|
44157
|
-
|
|
44191
|
+
this.deps.logger?.warn("dispatch.inject.send-failed", {
|
|
44192
|
+
sessionId,
|
|
44158
44193
|
reason: err instanceof Error ? err.message : String(err)
|
|
44159
44194
|
});
|
|
44160
|
-
return;
|
|
44195
|
+
return false;
|
|
44161
44196
|
}
|
|
44162
|
-
const nowIso3 = new Date(this.deps.now?.() ?? Date.now()).toISOString();
|
|
44163
|
-
for (const r of pending) store.update(r.dispatchId, { deliveredAt: nowIso3 });
|
|
44164
|
-
this.deps.logger?.info("dispatch.deliver.ok", {
|
|
44165
|
-
sourceSessionId,
|
|
44166
|
-
count: pending.length,
|
|
44167
|
-
dispatchIds: pending.map((r) => r.dispatchId)
|
|
44168
|
-
});
|
|
44169
44197
|
}
|
|
44170
44198
|
/**
|
|
44171
44199
|
* 只读拿 SessionState;session 不存在返 null(不 throw)。
|
|
@@ -48321,6 +48349,21 @@ var PersonaDispatchManager = class {
|
|
|
48321
48349
|
state.waiters.push(d);
|
|
48322
48350
|
return d.promise;
|
|
48323
48351
|
}
|
|
48352
|
+
/**
|
|
48353
|
+
* 续派(spec 2026-07-21):complete 已清掉 map 条目,续派前重建——bSessionId 映射
|
|
48354
|
+
* 保证 worker 进程死后 respawn 时 reducer lookupDispatchByBSessionId 仍注回同一
|
|
48355
|
+
* CLAWD_DISPATCH_ID env。台账行的复位由 handler 直接走 store(本方法只管内存态)。
|
|
48356
|
+
*/
|
|
48357
|
+
reopen(dispatchId, args) {
|
|
48358
|
+
const existing = this.map.get(dispatchId);
|
|
48359
|
+
this.map.set(dispatchId, {
|
|
48360
|
+
dispatchId,
|
|
48361
|
+
sourceSessionId: args.sourceSessionId,
|
|
48362
|
+
targetPersona: args.targetPersona,
|
|
48363
|
+
bSessionId: args.workerSessionId,
|
|
48364
|
+
waiters: existing?.waiters ?? []
|
|
48365
|
+
});
|
|
48366
|
+
}
|
|
48324
48367
|
/** SessionManager 在 createDispatchedSession 时调,建立 dispatchId → B sessionId 映射 */
|
|
48325
48368
|
registerBSession(dispatchId, bSessionId) {
|
|
48326
48369
|
const state = this.map.get(dispatchId);
|
|
@@ -49054,6 +49097,17 @@ function canAccessPersona(grants, personaId, action) {
|
|
|
49054
49097
|
|
|
49055
49098
|
// src/handlers/persona-dispatch.ts
|
|
49056
49099
|
init_claude();
|
|
49100
|
+
function buildFollowUpTaskPack(dispatchId, prompt) {
|
|
49101
|
+
return [
|
|
49102
|
+
`[Follow-up instruction for dispatch ${dispatchId}]`,
|
|
49103
|
+
"",
|
|
49104
|
+
`Task: ${prompt}`,
|
|
49105
|
+
"",
|
|
49106
|
+
`This is a follow-up turn in the same dispatched task (dispatchId=${dispatchId}).`,
|
|
49107
|
+
"When you finish this round, report back by calling personaDispatchComplete again,",
|
|
49108
|
+
"exactly as you did before."
|
|
49109
|
+
].join("\n");
|
|
49110
|
+
}
|
|
49057
49111
|
function buildPersonaDispatchHandlers(deps) {
|
|
49058
49112
|
const { personaDispatchManager: mgr, spawnB, logger } = deps;
|
|
49059
49113
|
const run = async (frame, _client, ctx) => {
|
|
@@ -49066,6 +49120,76 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
49066
49120
|
`model '${args.model}' not supported; allowed: ${CLAUDE_MODEL_IDS.join(", ")}`
|
|
49067
49121
|
);
|
|
49068
49122
|
}
|
|
49123
|
+
if (args.dispatchId) {
|
|
49124
|
+
if (ctx?.principal.kind === "guest") {
|
|
49125
|
+
throw new ClawdError(
|
|
49126
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
49127
|
+
"follow-up dispatch cannot be initiated by a guest"
|
|
49128
|
+
);
|
|
49129
|
+
}
|
|
49130
|
+
if (!sourceSessionId) {
|
|
49131
|
+
throw new Error(
|
|
49132
|
+
"personaDispatch:run requires sessionId (caller must pass x-clawd-session-id header)"
|
|
49133
|
+
);
|
|
49134
|
+
}
|
|
49135
|
+
const rec3 = deps.store.get(args.dispatchId);
|
|
49136
|
+
if (!rec3) {
|
|
49137
|
+
throw new ClawdError(ERROR_CODES.INVALID_PARAM, `dispatch not found: ${args.dispatchId}`);
|
|
49138
|
+
}
|
|
49139
|
+
if (!rec3.workerSessionId) {
|
|
49140
|
+
throw new ClawdError(
|
|
49141
|
+
ERROR_CODES.INVALID_PARAM,
|
|
49142
|
+
`dispatch ${args.dispatchId} has no worker session and cannot be continued (cross-device or spawn incomplete)`
|
|
49143
|
+
);
|
|
49144
|
+
}
|
|
49145
|
+
if (args.targetPersona && args.targetPersona !== rec3.targetPersonaId) {
|
|
49146
|
+
throw new ClawdError(
|
|
49147
|
+
ERROR_CODES.INVALID_PARAM,
|
|
49148
|
+
`targetPersona mismatch: dispatch ${args.dispatchId} belongs to ${rec3.targetPersonaId}`
|
|
49149
|
+
);
|
|
49150
|
+
}
|
|
49151
|
+
if (!deps.sendFollowUp) {
|
|
49152
|
+
throw new Error("follow-up dispatch not wired (sendFollowUp missing)");
|
|
49153
|
+
}
|
|
49154
|
+
deps.store.update(args.dispatchId, {
|
|
49155
|
+
status: "running",
|
|
49156
|
+
outcome: void 0,
|
|
49157
|
+
completedAt: void 0,
|
|
49158
|
+
deliveredAt: void 0,
|
|
49159
|
+
taskText: args.prompt.slice(0, TASK_TEXT_MAX)
|
|
49160
|
+
});
|
|
49161
|
+
mgr.reopen(args.dispatchId, {
|
|
49162
|
+
sourceSessionId,
|
|
49163
|
+
targetPersona: rec3.targetPersonaId,
|
|
49164
|
+
workerSessionId: rec3.workerSessionId
|
|
49165
|
+
});
|
|
49166
|
+
logger?.info("dispatch.run.follow-up", {
|
|
49167
|
+
dispatchId: args.dispatchId,
|
|
49168
|
+
workerSessionId: rec3.workerSessionId,
|
|
49169
|
+
promptLen: args.prompt.length
|
|
49170
|
+
});
|
|
49171
|
+
const ok = deps.sendFollowUp(
|
|
49172
|
+
rec3.workerSessionId,
|
|
49173
|
+
buildFollowUpTaskPack(args.dispatchId, args.prompt)
|
|
49174
|
+
);
|
|
49175
|
+
if (!ok) {
|
|
49176
|
+
mgr.complete(args.dispatchId, {
|
|
49177
|
+
kind: "failure",
|
|
49178
|
+
reason: "worker session unavailable for follow-up"
|
|
49179
|
+
});
|
|
49180
|
+
throw new ClawdError(
|
|
49181
|
+
ERROR_CODES.SESSION_NOT_FOUND,
|
|
49182
|
+
`worker session unavailable; follow-up failed for dispatch ${args.dispatchId}`
|
|
49183
|
+
);
|
|
49184
|
+
}
|
|
49185
|
+
return {
|
|
49186
|
+
response: { type: "personaDispatch:run:ok", dispatchId: args.dispatchId }
|
|
49187
|
+
};
|
|
49188
|
+
}
|
|
49189
|
+
const targetPersona = args.targetPersona;
|
|
49190
|
+
if (!targetPersona) {
|
|
49191
|
+
throw new ClawdError(ERROR_CODES.INVALID_PARAM, "targetPersona is required for a new dispatch");
|
|
49192
|
+
}
|
|
49069
49193
|
if (args.targetDeviceId) {
|
|
49070
49194
|
if (ctx?.principal.kind === "guest") {
|
|
49071
49195
|
throw new ClawdError(
|
|
@@ -49083,17 +49207,17 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
49083
49207
|
}
|
|
49084
49208
|
const { dispatchId: dispatchId2 } = mgr.start({
|
|
49085
49209
|
sourceSessionId,
|
|
49086
|
-
targetPersona
|
|
49210
|
+
targetPersona,
|
|
49087
49211
|
taskText: args.prompt
|
|
49088
49212
|
});
|
|
49089
49213
|
logger?.info("dispatch.run.forward", {
|
|
49090
49214
|
dispatchId: dispatchId2,
|
|
49091
49215
|
targetDeviceId: args.targetDeviceId,
|
|
49092
|
-
targetPersona
|
|
49216
|
+
targetPersona
|
|
49093
49217
|
});
|
|
49094
49218
|
void deps.forwardToPeer({
|
|
49095
49219
|
targetDeviceId: args.targetDeviceId,
|
|
49096
|
-
targetPersona
|
|
49220
|
+
targetPersona,
|
|
49097
49221
|
prompt: args.prompt,
|
|
49098
49222
|
...args.model ? { model: args.model } : {}
|
|
49099
49223
|
}).then((outcome) => mgr.complete(dispatchId2, outcome)).catch((err) => {
|
|
@@ -49106,28 +49230,28 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
49106
49230
|
};
|
|
49107
49231
|
}
|
|
49108
49232
|
if (ctx?.principal.kind === "guest") {
|
|
49109
|
-
const isPublic = deps.getPersonaPublic?.(
|
|
49110
|
-
if (!isPublic || !canAccessPersona(ctx.grants,
|
|
49233
|
+
const isPublic = deps.getPersonaPublic?.(targetPersona) ?? false;
|
|
49234
|
+
if (!isPublic || !canAccessPersona(ctx.grants, targetPersona, "send")) {
|
|
49111
49235
|
throw new ClawdError(
|
|
49112
49236
|
ERROR_CODES.UNAUTHORIZED,
|
|
49113
|
-
`persona not dispatchable: ${
|
|
49237
|
+
`persona not dispatchable: ${targetPersona}`
|
|
49114
49238
|
);
|
|
49115
49239
|
}
|
|
49116
49240
|
const { dispatchId: dispatchId2 } = mgr.start({
|
|
49117
49241
|
sourceSessionId: ctx.principal.id,
|
|
49118
|
-
targetPersona
|
|
49242
|
+
targetPersona,
|
|
49119
49243
|
taskText: args.prompt
|
|
49120
49244
|
});
|
|
49121
49245
|
logger?.info("dispatch.run.received.guest", {
|
|
49122
49246
|
dispatchId: dispatchId2,
|
|
49123
49247
|
sourcePrincipal: ctx.principal.id,
|
|
49124
|
-
targetPersona
|
|
49248
|
+
targetPersona,
|
|
49125
49249
|
promptLen: args.prompt.length
|
|
49126
49250
|
});
|
|
49127
49251
|
void spawnB({
|
|
49128
49252
|
dispatchId: dispatchId2,
|
|
49129
49253
|
sourceSessionId: ctx.principal.id,
|
|
49130
|
-
targetPersona
|
|
49254
|
+
targetPersona,
|
|
49131
49255
|
prompt: args.prompt,
|
|
49132
49256
|
guestPrincipalId: ctx.principal.id,
|
|
49133
49257
|
guestDisplayName: ctx.principal.displayName,
|
|
@@ -49154,19 +49278,19 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
49154
49278
|
}
|
|
49155
49279
|
const { dispatchId } = mgr.start({
|
|
49156
49280
|
sourceSessionId,
|
|
49157
|
-
targetPersona
|
|
49281
|
+
targetPersona,
|
|
49158
49282
|
taskText: args.prompt
|
|
49159
49283
|
});
|
|
49160
49284
|
logger?.info("dispatch.run.received", {
|
|
49161
49285
|
dispatchId,
|
|
49162
49286
|
sourceSessionId,
|
|
49163
|
-
targetPersona
|
|
49287
|
+
targetPersona,
|
|
49164
49288
|
promptLen: args.prompt.length
|
|
49165
49289
|
});
|
|
49166
49290
|
void spawnB({
|
|
49167
49291
|
dispatchId,
|
|
49168
49292
|
sourceSessionId,
|
|
49169
|
-
targetPersona
|
|
49293
|
+
targetPersona,
|
|
49170
49294
|
prompt: args.prompt,
|
|
49171
49295
|
...args.model ? { model: args.model } : {}
|
|
49172
49296
|
}).then(() => {
|
|
@@ -50698,6 +50822,9 @@ var EXPOSED_RPC_METHODS = [
|
|
|
50698
50822
|
"peerExec.run",
|
|
50699
50823
|
"personaDispatch.complete",
|
|
50700
50824
|
"personaDispatch.list",
|
|
50825
|
+
// persona 清单(spec 2026-07-21):两级路由第一级——发起方 cc(loopback owner 语义)
|
|
50826
|
+
// 用 curl 拿全量 personaId+label 紧凑清单,再按需读候选 CLAUDE.md 首段做终选。
|
|
50827
|
+
"persona.list",
|
|
50701
50828
|
// clawd-shift v1 (spec 2026-06-24-clawd-shift):cc 子进程内的 shift MCP server 调
|
|
50702
50829
|
// 这 5 个 method 管理 owner 的 shift(fire-and-forget 排班)。鉴权靠 body.personaId
|
|
50703
50830
|
// (mcp-server 从 process.env.CLAWD_PERSONA_ID 读,源 = SessionFile.ownerPersonaId)。
|
|
@@ -54227,6 +54354,7 @@ function buildSessionHandlers(deps) {
|
|
|
54227
54354
|
const narrow = (sessions) => {
|
|
54228
54355
|
let out = sessions;
|
|
54229
54356
|
if (args.originOnly) out = out.filter((s) => s.originOwnerPrincipalId != null);
|
|
54357
|
+
if (args.ownerPersonaId) out = out.filter((s) => s.ownerPersonaId === args.ownerPersonaId);
|
|
54230
54358
|
const total = out.length;
|
|
54231
54359
|
const offset = args.offset ?? 0;
|
|
54232
54360
|
if (offset > 0 || args.limit != null) {
|
|
@@ -59600,6 +59728,8 @@ async function startDaemon(config) {
|
|
|
59600
59728
|
const dispatchHandlers = buildPersonaDispatchHandlers({
|
|
59601
59729
|
personaDispatchManager,
|
|
59602
59730
|
store: dispatchStore,
|
|
59731
|
+
// 续派(spec 2026-07-21):任务包注入 worker session,复用投递同款注入路径。
|
|
59732
|
+
sendFollowUp: (workerSessionId, text) => manager.injectIntoSession(workerSessionId, text),
|
|
59603
59733
|
logger,
|
|
59604
59734
|
spawnB: async (args) => {
|
|
59605
59735
|
logger.info("dispatch.spawnB.start", {
|
|
@@ -21770,12 +21770,21 @@ var SessionCreateArgs = external_exports.object({
|
|
|
21770
21770
|
var SessionListArgs = external_exports.object({
|
|
21771
21771
|
/** true = 只返回 mirror(originOwnerPrincipalId 非空,即「我在朋友 daemon 上创建」的本机存证)。 */
|
|
21772
21772
|
originOnly: external_exports.boolean().optional(),
|
|
21773
|
+
/**
|
|
21774
|
+
* 按 own persona 窄化(2026-07-21 self-persona-sessions 追加):只留
|
|
21775
|
+
* `s.ownerPersonaId === args.ownerPersonaId` 的 sessions。
|
|
21776
|
+
* - mirror 行(`ownerPersonaId` 恒 null)天然排除
|
|
21777
|
+
* - 与 `originOnly=true` 同传恒返 0
|
|
21778
|
+
* - 老 daemon 不识别时忽略字段返回全量,客户端消费侧本就自带过滤/截断,天然向后兼容
|
|
21779
|
+
*/
|
|
21780
|
+
ownerPersonaId: external_exports.string().min(1).optional(),
|
|
21773
21781
|
/** 按 updatedAt desc(list 的既有排序)截断前 N 条。 */
|
|
21774
21782
|
limit: external_exports.number().int().positive().optional(),
|
|
21775
21783
|
/**
|
|
21776
|
-
* 分页起点(2026-07-18 android-tasks-tab 迭代追加):originOnly 过滤后、
|
|
21777
|
-
*
|
|
21778
|
-
*
|
|
21784
|
+
* 分页起点(2026-07-18 android-tasks-tab 迭代追加):originOnly / ownerPersonaId 过滤后、
|
|
21785
|
+
* limit 截断前跳过前 N 条。与 limit 组合 = updatedAt desc 序上的一页。响应带 total
|
|
21786
|
+
* (过滤后总数,见 handler)供客户端判 hasMore。老 daemon 不识别时忽略 → 回全量,
|
|
21787
|
+
* 客户端按全量兼容。
|
|
21779
21788
|
*/
|
|
21780
21789
|
offset: external_exports.number().int().nonnegative().optional()
|
|
21781
21790
|
});
|
|
@@ -22699,7 +22708,12 @@ var DispatchOutcomeSchema = external_exports.discriminatedUnion("kind", [
|
|
|
22699
22708
|
})
|
|
22700
22709
|
]);
|
|
22701
22710
|
var DispatchRunArgsSchema = external_exports.object({
|
|
22702
|
-
|
|
22711
|
+
// 新开必填;续派(dispatchId 非空)时可省略——targetPersonaId 从台账行取。
|
|
22712
|
+
targetPersona: external_exports.string().min(1).optional(),
|
|
22713
|
+
// 续派(spec 2026-07-21,单 dispatchId 语义:一件事一行一 id):非空 = 把 prompt
|
|
22714
|
+
// 作为新 turn 打进该 dispatch 的原 worker session 接着干(台账行复位 running)。
|
|
22715
|
+
// 省略 = 新开一件事,daemon 铸新 id。
|
|
22716
|
+
dispatchId: external_exports.string().min(1).optional(),
|
|
22703
22717
|
prompt: external_exports.string(),
|
|
22704
22718
|
// 跨设备 dispatch:非空 = A 角色,转发到该 contact deviceId 的 peer daemon;
|
|
22705
22719
|
// 省略 = 本地 dispatch / B 角色本地执行。A 转发给 B 时 body 不带此字段。
|
|
@@ -22709,6 +22723,10 @@ var DispatchRunArgsSchema = external_exports.object({
|
|
|
22709
22723
|
// tools/claude.ts CLAUDE_MODEL_IDS);handler 层做二次校验,非法值 fail-fast。
|
|
22710
22724
|
// protocol 层保持 string 便于未来跨 tool 扩展(届时 union 收合法字面量)。
|
|
22711
22725
|
model: external_exports.string().min(1).optional()
|
|
22726
|
+
}).refine((v) => v.targetPersona !== void 0 || v.dispatchId !== void 0, {
|
|
22727
|
+
message: "either targetPersona (new dispatch) or dispatchId (follow-up) is required"
|
|
22728
|
+
}).refine((v) => !(v.dispatchId !== void 0 && v.targetDeviceId !== void 0), {
|
|
22729
|
+
message: "cross-device dispatch cannot be continued (dispatchId + targetDeviceId are mutually exclusive)"
|
|
22712
22730
|
});
|
|
22713
22731
|
var DispatchCompleteArgsSchema = external_exports.object({
|
|
22714
22732
|
dispatchId: external_exports.string().min(1),
|
|
@@ -23416,6 +23434,7 @@ async function handlePersonaDispatchToolCall(input, ctx) {
|
|
|
23416
23434
|
const f = ctx.fetch ?? fetch;
|
|
23417
23435
|
log("personaDispatch tool entered", {
|
|
23418
23436
|
targetPersona: input.targetPersona,
|
|
23437
|
+
dispatchId: input.dispatchId,
|
|
23419
23438
|
promptLen: input.prompt.length,
|
|
23420
23439
|
sessionId: ctx.sessionId,
|
|
23421
23440
|
daemonUrl: ctx.daemonUrl
|
|
@@ -23443,8 +23462,11 @@ async function handlePersonaDispatchToolCall(input, ctx) {
|
|
|
23443
23462
|
"x-clawd-session-id": ctx.sessionId
|
|
23444
23463
|
},
|
|
23445
23464
|
body: JSON.stringify({
|
|
23446
|
-
targetPersona
|
|
23465
|
+
// 续派时可省 targetPersona(daemon 从台账取)
|
|
23466
|
+
...input.targetPersona ? { targetPersona: input.targetPersona } : {},
|
|
23447
23467
|
prompt: input.prompt,
|
|
23468
|
+
// 续派(spec 2026-07-21):非空 = 把 prompt 打进该 dispatch 的原 worker session。
|
|
23469
|
+
...input.dispatchId ? { dispatchId: input.dispatchId } : {},
|
|
23448
23470
|
// 跨设备:非空时透传,daemon run handler 据此走 A 角色转发分支。
|
|
23449
23471
|
...input.targetDeviceId ? { targetDeviceId: input.targetDeviceId } : {},
|
|
23450
23472
|
// T-20: 可选 override B session 使用的 claude 模型;MCP tool 层已 enum 校验,
|
|
@@ -23478,12 +23500,13 @@ async function handlePersonaDispatchToolCall(input, ctx) {
|
|
|
23478
23500
|
};
|
|
23479
23501
|
}
|
|
23480
23502
|
const dispatchId = json.result.dispatchId;
|
|
23481
|
-
log("personaDispatch submitted", { dispatchId });
|
|
23503
|
+
log("personaDispatch submitted", { dispatchId, followUp: Boolean(input.dispatchId) });
|
|
23504
|
+
const head = input.dispatchId ? `Follow-up sent for dispatch ${dispatchId} \u2014 the new instruction was delivered into the same worker session.` : `Task dispatched to ${input.targetPersona} (dispatchId=${dispatchId}).`;
|
|
23482
23505
|
return {
|
|
23483
23506
|
content: [
|
|
23484
23507
|
{
|
|
23485
23508
|
type: "text",
|
|
23486
|
-
text:
|
|
23509
|
+
text: `${head} The result will be delivered back into this session when the target persona finishes \u2014 you do NOT need to wait or poll. Tell the user the task has been delegated and end your turn. Use personaDispatchList only if the user later asks about progress.`
|
|
23487
23510
|
}
|
|
23488
23511
|
]
|
|
23489
23512
|
};
|
|
@@ -23610,9 +23633,14 @@ async function main() {
|
|
|
23610
23633
|
"personaDispatch",
|
|
23611
23634
|
{
|
|
23612
23635
|
title: "Dispatch task to another persona",
|
|
23613
|
-
description: "Delegate a task to another clawd persona. Use this when the user message contains `@persona/<id>` to hand the task off to that persona. Returns immediately with a dispatchId; the result will be delivered back into your session automatically when the target persona finishes \u2014 do not wait or poll. Use personaDispatchList to check progress if asked.",
|
|
23636
|
+
description: "Delegate a task to another clawd persona. Use this when the user message contains `@persona/<id>` to hand the task off to that persona. Returns immediately with a dispatchId; the result will be delivered back into your session automatically when the target persona finishes \u2014 do not wait or poll. To continue an existing dispatched task (follow-up instruction into the same worker session), pass its dispatchId and omit targetPersona. Use personaDispatchList to check progress if asked.",
|
|
23614
23637
|
inputSchema: {
|
|
23615
|
-
targetPersona: external_exports.string().describe(
|
|
23638
|
+
targetPersona: external_exports.string().optional().describe(
|
|
23639
|
+
"persona id (e.g. persona-app-builder); omit when continuing an existing dispatch via dispatchId"
|
|
23640
|
+
),
|
|
23641
|
+
dispatchId: external_exports.string().optional().describe(
|
|
23642
|
+
"continue an existing dispatched task: the prompt is delivered into the same worker session as a new turn (context preserved); model cannot be changed on follow-up (the worker session keeps its original model)"
|
|
23643
|
+
),
|
|
23616
23644
|
prompt: external_exports.string().describe(
|
|
23617
23645
|
"the task to perform, with the @persona/<id> token stripped out"
|
|
23618
23646
|
),
|