@clawos-dev/clawd 0.2.192-beta.386.33a5833 → 0.2.192-beta.387.d592538
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
|
@@ -5996,7 +5996,7 @@ var init_feishu_auth = __esm({
|
|
|
5996
5996
|
});
|
|
5997
5997
|
|
|
5998
5998
|
// ../protocol/src/dispatch.ts
|
|
5999
|
-
var DispatchOutcomeSchema, DispatchRunArgsSchema,
|
|
5999
|
+
var DispatchOutcomeSchema, DispatchRunArgsSchema, DispatchCompleteArgsSchema;
|
|
6000
6000
|
var init_dispatch = __esm({
|
|
6001
6001
|
"../protocol/src/dispatch.ts"() {
|
|
6002
6002
|
"use strict";
|
|
@@ -6017,21 +6017,10 @@ var init_dispatch = __esm({
|
|
|
6017
6017
|
prompt: external_exports.string(),
|
|
6018
6018
|
// 跨设备 dispatch:非空 = A 角色,转发到该 contact deviceId 的 peer daemon;
|
|
6019
6019
|
// 省略 = 本地 dispatch / B 角色本地执行。A 转发给 B 时 body 不带此字段。
|
|
6020
|
-
targetDeviceId: external_exports.string().min(1).optional()
|
|
6021
|
-
// 精确寻址已知 B session(复用其上下文):
|
|
6022
|
-
// - 本地(targetDeviceId 缺省):daemon 校验目标为本机 session 且
|
|
6023
|
-
// dispatchedFromSessionId 匹配 A 的 sessionId(防越权)
|
|
6024
|
-
// - 跨设备(targetDeviceId 非空):peer daemon 在其本地按同规则校验
|
|
6025
|
-
// creatorPrincipalId === A.deviceId
|
|
6026
|
-
// - 省略 = 新建 B session
|
|
6027
|
-
targetSessionId: external_exports.string().min(1).optional()
|
|
6028
|
-
});
|
|
6029
|
-
DispatchRunResponseSchema = external_exports.object({
|
|
6030
|
-
type: external_exports.literal("personaDispatch:run:ok"),
|
|
6031
|
-
outcome: DispatchOutcomeSchema,
|
|
6032
|
-
dispatchedSessionId: external_exports.string().min(1).optional()
|
|
6020
|
+
targetDeviceId: external_exports.string().min(1).optional()
|
|
6033
6021
|
});
|
|
6034
6022
|
DispatchCompleteArgsSchema = external_exports.object({
|
|
6023
|
+
dispatchId: external_exports.string().min(1),
|
|
6035
6024
|
outcome: DispatchOutcomeSchema
|
|
6036
6025
|
});
|
|
6037
6026
|
}
|
|
@@ -41563,6 +41552,8 @@ function buildSpawnContext(state, deps) {
|
|
|
41563
41552
|
env.CLAWD_SESSION_ID = file.sessionId;
|
|
41564
41553
|
const daemonUrl = deps.getDaemonUrl?.() ?? null;
|
|
41565
41554
|
if (daemonUrl) env.CLAWD_DAEMON_URL = daemonUrl;
|
|
41555
|
+
const dispatchId = deps.lookupDispatchByBSessionId?.(file.sessionId);
|
|
41556
|
+
if (dispatchId) env.CLAWD_DISPATCH_ID = dispatchId;
|
|
41566
41557
|
const personaId = file.ownerPersonaId;
|
|
41567
41558
|
if (personaId) env.CLAWD_PERSONA_ID = personaId;
|
|
41568
41559
|
const dispatchMcpConfigPath2 = deps.getDispatchMcpConfigPath?.() ?? null;
|
|
@@ -42368,14 +42359,14 @@ var SessionRunner = class {
|
|
|
42368
42359
|
// 单栏 refactor (spec 2026-06-02 §5.1): cc 子进程 env 注入 CLAWD_DAEMON_URL,让 assistant
|
|
42369
42360
|
// curl daemon HTTP RPC adapter (/api/rpc/<method>) 触发管理操作。null = HTTP adapter 未启。
|
|
42370
42361
|
getDaemonUrl: this.hooks.getDaemonUrl,
|
|
42371
|
-
// Persona dispatch:透传 dispatch.mcp.json
|
|
42372
|
-
//
|
|
42373
|
-
// dispatchId 不注 cc env——complete handler 用 sessionId 反查 in-flight dispatchId 配对。
|
|
42362
|
+
// Persona dispatch:透传 dispatch.mcp.json 路径闭包 + B→dispatchId 反查闭包。
|
|
42363
|
+
// 见 reducer.buildSpawnContext 注 CLAWD_DISPATCH_ID env / 派生 SpawnContext.dispatchMcpConfigPath
|
|
42374
42364
|
getDispatchMcpConfigPath: this.hooks.getDispatchMcpConfigPath,
|
|
42375
42365
|
getShiftMcpConfigPath: this.hooks.getShiftMcpConfigPath,
|
|
42376
42366
|
getInboxMcpConfigPath: this.hooks.getInboxMcpConfigPath,
|
|
42377
42367
|
// Ticket MCP:透传 ticket.mcp.json 路径闭包(reducer 内做 persona-ticket-manager gating)
|
|
42378
42368
|
getTicketMcpConfigPath: this.hooks.getTicketMcpConfigPath,
|
|
42369
|
+
lookupDispatchByBSessionId: this.hooks.lookupDispatchByBSessionId,
|
|
42379
42370
|
// ReadyGate v2:透传 mode 让 reducer send / ready-detected 分支决定走暂存队列还是直写
|
|
42380
42371
|
mode: this.hooks.mode,
|
|
42381
42372
|
// [RG-DBG] 注入 logger 让 reducer rgDbg 走 pino 进 clawd.log;定位完跟 rgDbg 一起删
|
|
@@ -42859,22 +42850,9 @@ You may read this file with your Read tool to understand context. You do not hav
|
|
|
42859
42850
|
|
|
42860
42851
|
When done, call the MCP tool \`mcp__clawd-dispatch__personaDispatchComplete\` with your result:
|
|
42861
42852
|
- Success: { text: "...", filePaths?: ["abs/path", ...] }
|
|
42862
|
-
- Failure: { isFailure: true, reason: "..." }
|
|
42863
|
-
}
|
|
42864
|
-
function buildDispatchContinuationPack(args) {
|
|
42865
|
-
return `[Dispatched from owner \u2014 follow-up query]
|
|
42866
|
-
|
|
42867
|
-
Although you reported back on the previous task, the owner has a follow-up question. Continue from where you left off.
|
|
42868
|
-
|
|
42869
|
-
Owner's new message:
|
|
42870
|
-
${args.prompt}
|
|
42853
|
+
- Failure: { isFailure: true, reason: "..." }
|
|
42871
42854
|
|
|
42872
|
-
|
|
42873
|
-
${args.sourceJsonlPath}
|
|
42874
|
-
|
|
42875
|
-
When done, call \`mcp__clawd-dispatch__personaDispatchComplete\` again with your result:
|
|
42876
|
-
- Success: { text: "...", filePaths?: ["abs/path", ...] }
|
|
42877
|
-
- Failure: { isFailure: true, reason: "..." }`;
|
|
42855
|
+
dispatchId for this task: ${args.dispatchId}`;
|
|
42878
42856
|
}
|
|
42879
42857
|
function derivePersonaSpawnCwd(file, personaRoot) {
|
|
42880
42858
|
const personaId = file.ownerPersonaId;
|
|
@@ -43195,13 +43173,13 @@ var SessionManager = class {
|
|
|
43195
43173
|
// 单栏 refactor (spec 2026-06-02 §5.1): 透传 daemon HTTP RPC base URL 闭包,
|
|
43196
43174
|
// reducer 把它注入 cc 子进程 env CLAWD_DAEMON_URL.
|
|
43197
43175
|
getDaemonUrl: this.deps.getDaemonUrl,
|
|
43198
|
-
// Persona dispatch: dispatch.mcp.json
|
|
43199
|
-
// --mcp-config flag
|
|
43200
|
-
// dispatchId 不再走 cc env——complete handler 用 sessionId 反查 in-flight dispatchId 配对。
|
|
43176
|
+
// Persona dispatch (Task 7): dispatch.mcp.json 路径 + B sessionId → dispatchId 反查
|
|
43177
|
+
// 闭包透传给 reducer,让 cc spawn 加 --mcp-config flag + B session 注 CLAWD_DISPATCH_ID env.
|
|
43201
43178
|
getDispatchMcpConfigPath: this.deps.dispatchMcpConfigPath ? () => this.deps.dispatchMcpConfigPath ?? null : void 0,
|
|
43202
43179
|
getTicketMcpConfigPath: this.deps.ticketMcpConfigPath ? () => this.deps.ticketMcpConfigPath ?? null : void 0,
|
|
43203
43180
|
getShiftMcpConfigPath: this.deps.shiftMcpConfigPath ? () => this.deps.shiftMcpConfigPath ?? null : void 0,
|
|
43204
43181
|
getInboxMcpConfigPath: this.deps.inboxMcpConfigPath ? () => this.deps.inboxMcpConfigPath ?? null : void 0,
|
|
43182
|
+
lookupDispatchByBSessionId: this.deps.personaDispatchManager ? (bSid) => this.deps.personaDispatchManager?.lookupDispatchByBSessionId(bSid) : void 0,
|
|
43205
43183
|
// file-sharing (spec §6 PR 3):闭包 scope + sessionId,runner 只暴露 tool/relPath/cwd
|
|
43206
43184
|
onFileEdit: attachmentGroup ? (input) => attachmentGroup.onFileEdit({
|
|
43207
43185
|
scope,
|
|
@@ -44041,11 +44019,10 @@ var SessionManager = class {
|
|
|
44041
44019
|
* Persona dispatch: 启动 B session 并投递任务包当第一条 user message。
|
|
44042
44020
|
*
|
|
44043
44021
|
* - 复用 persona-owner 创建路径(cwd=personaDir、scope=persona-owner、不沙箱)
|
|
44044
|
-
* - SessionFile 上写 dispatchedFromSessionId 关联回 A(UI 折叠用;mirror 一侧 strip
|
|
44045
|
-
*
|
|
44046
|
-
*
|
|
44047
|
-
*
|
|
44048
|
-
* - 投任务包;包尾教 B 用 personaDispatchComplete tool 回传
|
|
44022
|
+
* - SessionFile 上写 dispatchedFromSessionId 关联回 A(UI 折叠用;mirror 一侧 strip)
|
|
44023
|
+
* - 跟 PersonaDispatchManager 双向登记 dispatchId ↔ B sessionId,让后续 reducer
|
|
44024
|
+
* buildSpawnContext 能反查到 dispatchId 注 CLAWD_DISPATCH_ID env
|
|
44025
|
+
* - 投 inject-owner-text 任务包;包尾教 B 用 personaDispatchComplete tool 回传
|
|
44049
44026
|
*
|
|
44050
44027
|
* cwd 派生与 SessionManager.create 的 owner persona 路径一致(derivePersonaSpawnCwd)。
|
|
44051
44028
|
*/
|
|
@@ -44100,7 +44077,8 @@ var SessionManager = class {
|
|
|
44100
44077
|
});
|
|
44101
44078
|
const taskPack = buildDispatchTaskPack({
|
|
44102
44079
|
prompt: args.prompt,
|
|
44103
|
-
sourceJsonlPath: args.sourceJsonlPath
|
|
44080
|
+
sourceJsonlPath: args.sourceJsonlPath,
|
|
44081
|
+
dispatchId: args.dispatchId
|
|
44104
44082
|
});
|
|
44105
44083
|
runner.input({ kind: "command", command: { kind: "send", text: taskPack } });
|
|
44106
44084
|
this.deps.logger?.info("dispatch.createDispatchedSession.task-pack-sent", {
|
|
@@ -44109,59 +44087,6 @@ var SessionManager = class {
|
|
|
44109
44087
|
});
|
|
44110
44088
|
return { sessionId };
|
|
44111
44089
|
}
|
|
44112
|
-
/**
|
|
44113
|
-
* targetSessionId 精确寻址已知 B session 复用其上下文。投 continuation pack 给 runner。
|
|
44114
|
-
*
|
|
44115
|
-
* 关键差异 vs createDispatchedSession:
|
|
44116
|
-
* - 不 newSessionId() / 不写 SessionFile —— B session 复用(走 findOwnedSession)
|
|
44117
|
-
* - 权限校验:本地 dispatch → SessionFile.dispatchedFromSessionId === A 的 sessionId;
|
|
44118
|
-
* 跨设备 B 角色 → SessionFile.creatorPrincipalId === A 的 deviceId
|
|
44119
|
-
* - 跟 PersonaDispatchManager 重新登记(新 dispatchId 复用同 bSessionId)
|
|
44120
|
-
* - ensureRunnerForScope: cc 活复用;cc 死时首次 send 触发 spawn,jsonl 已存在自动 --resume
|
|
44121
|
-
*
|
|
44122
|
-
* @param callerIdentity 权限校验依据:
|
|
44123
|
-
* - kind='local': A 的 sessionId(本机 dispatch),校验 dispatchedFromSessionId 匹配
|
|
44124
|
-
* - kind='guest': A 的 deviceId(跨设备转发进来的 A),校验 creatorPrincipalId 匹配
|
|
44125
|
-
*/
|
|
44126
|
-
resumeDispatchedSession(args) {
|
|
44127
|
-
if (!this.deps.personaDispatchManager) {
|
|
44128
|
-
throw new Error("resumeDispatchedSession: personaDispatchManager missing in ManagerDeps");
|
|
44129
|
-
}
|
|
44130
|
-
const file = this.findOwnedSession(args.bSessionId);
|
|
44131
|
-
if (!file) {
|
|
44132
|
-
throw new Error(`resumeDispatchedSession: B session ${args.bSessionId} not found`);
|
|
44133
|
-
}
|
|
44134
|
-
if (args.callerIdentity.kind === "local") {
|
|
44135
|
-
if (file.dispatchedFromSessionId !== args.callerIdentity.sourceSessionId) {
|
|
44136
|
-
throw new Error(
|
|
44137
|
-
`resumeDispatchedSession: session ${args.bSessionId} not dispatched by caller (dispatchedFromSessionId mismatch)`
|
|
44138
|
-
);
|
|
44139
|
-
}
|
|
44140
|
-
} else {
|
|
44141
|
-
if (file.creatorPrincipalId !== args.callerIdentity.sourcePrincipalId) {
|
|
44142
|
-
throw new Error(
|
|
44143
|
-
`resumeDispatchedSession: session ${args.bSessionId} not dispatched by caller device (creatorPrincipalId mismatch)`
|
|
44144
|
-
);
|
|
44145
|
-
}
|
|
44146
|
-
}
|
|
44147
|
-
const scope = this.scopeForFile(file);
|
|
44148
|
-
this.deps.personaDispatchManager.registerBSession(args.dispatchId, args.bSessionId);
|
|
44149
|
-
this.deps.logger?.info("dispatch.resumeDispatchedSession.registered", {
|
|
44150
|
-
dispatchId: args.dispatchId,
|
|
44151
|
-
bSessionId: args.bSessionId
|
|
44152
|
-
});
|
|
44153
|
-
const runner = this.ensureRunnerForScope(file, scope);
|
|
44154
|
-
const pack = buildDispatchContinuationPack({
|
|
44155
|
-
prompt: args.prompt,
|
|
44156
|
-
sourceJsonlPath: args.sourceJsonlPath
|
|
44157
|
-
});
|
|
44158
|
-
runner.input({ kind: "command", command: { kind: "send", text: pack } });
|
|
44159
|
-
this.deps.logger?.info("dispatch.resumeDispatchedSession.continuation-sent", {
|
|
44160
|
-
dispatchId: args.dispatchId,
|
|
44161
|
-
bSessionId: args.bSessionId
|
|
44162
|
-
});
|
|
44163
|
-
return { sessionId: args.bSessionId };
|
|
44164
|
-
}
|
|
44165
44090
|
/**
|
|
44166
44091
|
* shift v1 (spec 2026-06-24-clawd-shift):到点 fire 时由 ShiftScheduler 调,
|
|
44167
44092
|
* 起一个新 cc session 跑 prompt(fire-and-forget,不绑 dispatchId / 不挂 caller)。
|
|
@@ -47568,7 +47493,6 @@ var PersonaDispatchManager = class {
|
|
|
47568
47493
|
dispatchId,
|
|
47569
47494
|
sourceSessionId: args.sourceSessionId,
|
|
47570
47495
|
targetPersona: args.targetPersona,
|
|
47571
|
-
bSessionId: args.bSessionId,
|
|
47572
47496
|
waiters: [],
|
|
47573
47497
|
outcome: null
|
|
47574
47498
|
});
|
|
@@ -47599,22 +47523,10 @@ var PersonaDispatchManager = class {
|
|
|
47599
47523
|
if (!state) throw new Error(`unknown dispatchId: ${dispatchId}`);
|
|
47600
47524
|
state.bSessionId = bSessionId;
|
|
47601
47525
|
}
|
|
47602
|
-
/**
|
|
47603
|
-
|
|
47604
|
-
* B 的 outcome 配对回 A 的 waiter —— dispatchId 不再走 cc env,全靠
|
|
47605
|
-
* (bSessionId → in-flight dispatchId) 反查。
|
|
47606
|
-
*
|
|
47607
|
-
* 同一 bSessionId 不会同时有多个 in-flight:A 拿不到 outcome 不会发起新 dispatch;
|
|
47608
|
-
* 老板规则下 A 一次只能挂一个 waiter。
|
|
47609
|
-
*
|
|
47610
|
-
* 历史 entry(已 resolved)暂不清 —— 单 daemon lifetime 累积可接受;未来用量大了加
|
|
47611
|
-
* TTL GC(complete 后 N min 删 entry)。
|
|
47612
|
-
*/
|
|
47613
|
-
findInflightDispatchByBSessionId(bSessionId) {
|
|
47526
|
+
/** reducer buildSpawnContext 调,按 B 的 sessionId 反查 dispatchId(用于注 CLAWD_DISPATCH_ID env) */
|
|
47527
|
+
lookupDispatchByBSessionId(bSessionId) {
|
|
47614
47528
|
for (const state of this.map.values()) {
|
|
47615
|
-
if (state.bSessionId === bSessionId
|
|
47616
|
-
return state.dispatchId;
|
|
47617
|
-
}
|
|
47529
|
+
if (state.bSessionId === bSessionId) return state.dispatchId;
|
|
47618
47530
|
}
|
|
47619
47531
|
return void 0;
|
|
47620
47532
|
}
|
|
@@ -48213,28 +48125,6 @@ function canAccessPersona(grants, personaId, action) {
|
|
|
48213
48125
|
}
|
|
48214
48126
|
|
|
48215
48127
|
// src/handlers/persona-dispatch.ts
|
|
48216
|
-
function assertLocalReuseAllowed(bFile, sourceSessionId) {
|
|
48217
|
-
if (bFile.dispatchedFromSessionId !== sourceSessionId) {
|
|
48218
|
-
throw new ClawdError(
|
|
48219
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
48220
|
-
`targetSessionId not dispatched by caller (dispatchedFromSessionId mismatch)`
|
|
48221
|
-
);
|
|
48222
|
-
}
|
|
48223
|
-
}
|
|
48224
|
-
function assertGuestReuseAllowed(bFile, sourcePrincipalId, targetPersona) {
|
|
48225
|
-
if (bFile.ownerPersonaId !== targetPersona) {
|
|
48226
|
-
throw new ClawdError(
|
|
48227
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
48228
|
-
`targetSessionId does not belong to targetPersona`
|
|
48229
|
-
);
|
|
48230
|
-
}
|
|
48231
|
-
if (bFile.creatorPrincipalId !== sourcePrincipalId) {
|
|
48232
|
-
throw new ClawdError(
|
|
48233
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
48234
|
-
`targetSessionId not dispatched by caller device (creatorPrincipalId mismatch)`
|
|
48235
|
-
);
|
|
48236
|
-
}
|
|
48237
|
-
}
|
|
48238
48128
|
function buildPersonaDispatchHandlers(deps) {
|
|
48239
48129
|
const { personaDispatchManager: mgr, spawnB, logger } = deps;
|
|
48240
48130
|
const run = async (frame, _client, ctx) => {
|
|
@@ -48253,21 +48143,15 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
48253
48143
|
}
|
|
48254
48144
|
logger?.info("dispatch.run.forward", {
|
|
48255
48145
|
targetDeviceId: args.targetDeviceId,
|
|
48256
|
-
targetPersona: args.targetPersona
|
|
48257
|
-
hasTargetSessionId: Boolean(args.targetSessionId)
|
|
48146
|
+
targetPersona: args.targetPersona
|
|
48258
48147
|
});
|
|
48259
|
-
const
|
|
48148
|
+
const outcome2 = await deps.forwardToPeer({
|
|
48260
48149
|
targetDeviceId: args.targetDeviceId,
|
|
48261
48150
|
targetPersona: args.targetPersona,
|
|
48262
|
-
prompt: args.prompt
|
|
48263
|
-
targetSessionId: args.targetSessionId
|
|
48151
|
+
prompt: args.prompt
|
|
48264
48152
|
});
|
|
48265
48153
|
return {
|
|
48266
|
-
response: {
|
|
48267
|
-
type: "personaDispatch:run:ok",
|
|
48268
|
-
outcome: outcome2,
|
|
48269
|
-
...dispatchedSessionId ? { dispatchedSessionId } : {}
|
|
48270
|
-
}
|
|
48154
|
+
response: { type: "personaDispatch:run:ok", outcome: outcome2 }
|
|
48271
48155
|
};
|
|
48272
48156
|
}
|
|
48273
48157
|
if (ctx?.principal.kind === "guest") {
|
|
@@ -48278,65 +48162,34 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
48278
48162
|
`persona not dispatchable: ${args.targetPersona}`
|
|
48279
48163
|
);
|
|
48280
48164
|
}
|
|
48281
|
-
const guestSourceId = ctx.principal.id;
|
|
48282
|
-
let bSessionId2;
|
|
48283
|
-
let route2 = "new";
|
|
48284
|
-
if (args.targetSessionId) {
|
|
48285
|
-
if (!deps.findOwnedSession) {
|
|
48286
|
-
throw new Error("targetSessionId reuse not wired (findOwnedSession missing)");
|
|
48287
|
-
}
|
|
48288
|
-
const bFile = deps.findOwnedSession(args.targetSessionId);
|
|
48289
|
-
if (!bFile) {
|
|
48290
|
-
throw new ClawdError(
|
|
48291
|
-
ERROR_CODES.SESSION_NOT_FOUND,
|
|
48292
|
-
`targetSessionId not found: ${args.targetSessionId}`
|
|
48293
|
-
);
|
|
48294
|
-
}
|
|
48295
|
-
assertGuestReuseAllowed(bFile, guestSourceId, args.targetPersona);
|
|
48296
|
-
route2 = "resume";
|
|
48297
|
-
bSessionId2 = args.targetSessionId;
|
|
48298
|
-
}
|
|
48299
48165
|
const { dispatchId: dispatchId2 } = mgr.start({
|
|
48300
|
-
sourceSessionId:
|
|
48301
|
-
targetPersona: args.targetPersona
|
|
48302
|
-
bSessionId: bSessionId2
|
|
48166
|
+
sourceSessionId: ctx.principal.id,
|
|
48167
|
+
targetPersona: args.targetPersona
|
|
48303
48168
|
});
|
|
48304
48169
|
logger?.info("dispatch.run.received.guest", {
|
|
48305
48170
|
dispatchId: dispatchId2,
|
|
48306
|
-
sourcePrincipal:
|
|
48171
|
+
sourcePrincipal: ctx.principal.id,
|
|
48307
48172
|
targetPersona: args.targetPersona,
|
|
48308
|
-
promptLen: args.prompt.length
|
|
48309
|
-
route: route2
|
|
48173
|
+
promptLen: args.prompt.length
|
|
48310
48174
|
});
|
|
48311
|
-
|
|
48312
|
-
|
|
48313
|
-
|
|
48314
|
-
|
|
48315
|
-
|
|
48316
|
-
|
|
48317
|
-
|
|
48318
|
-
|
|
48319
|
-
bSessionId: bSessionId2,
|
|
48320
|
-
guestPrincipalId: guestSourceId,
|
|
48321
|
-
guestDisplayName: ctx.principal.displayName
|
|
48322
|
-
});
|
|
48323
|
-
resolvedBSessionId2 = minted;
|
|
48324
|
-
logger?.info("dispatch.spawnB.ok", { dispatchId: dispatchId2, route: route2 });
|
|
48325
|
-
} catch (err) {
|
|
48175
|
+
void spawnB({
|
|
48176
|
+
dispatchId: dispatchId2,
|
|
48177
|
+
sourceSessionId: ctx.principal.id,
|
|
48178
|
+
targetPersona: args.targetPersona,
|
|
48179
|
+
prompt: args.prompt,
|
|
48180
|
+
guestPrincipalId: ctx.principal.id,
|
|
48181
|
+
guestDisplayName: ctx.principal.displayName
|
|
48182
|
+
}).then(() => logger?.info("dispatch.spawnB.ok", { dispatchId: dispatchId2 })).catch((err) => {
|
|
48326
48183
|
const reason = err instanceof Error ? err.message : String(err);
|
|
48327
|
-
logger?.warn("dispatch.spawnB.failed", { dispatchId: dispatchId2,
|
|
48184
|
+
logger?.warn("dispatch.spawnB.failed", { dispatchId: dispatchId2, reason });
|
|
48328
48185
|
mgr.complete(dispatchId2, {
|
|
48329
48186
|
kind: "failure",
|
|
48330
48187
|
reason: `failed to spawn B: ${reason}`
|
|
48331
48188
|
});
|
|
48332
|
-
}
|
|
48189
|
+
});
|
|
48333
48190
|
const outcome2 = await mgr.wait(dispatchId2);
|
|
48334
48191
|
return {
|
|
48335
|
-
response: {
|
|
48336
|
-
type: "personaDispatch:run:ok",
|
|
48337
|
-
outcome: outcome2,
|
|
48338
|
-
...resolvedBSessionId2 ? { dispatchedSessionId: resolvedBSessionId2 } : {}
|
|
48339
|
-
}
|
|
48192
|
+
response: { type: "personaDispatch:run:ok", outcome: outcome2 }
|
|
48340
48193
|
};
|
|
48341
48194
|
}
|
|
48342
48195
|
if (!sourceSessionId) {
|
|
@@ -48344,61 +48197,31 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
48344
48197
|
"personaDispatch:run requires sessionId (caller must pass x-clawd-session-id header)"
|
|
48345
48198
|
);
|
|
48346
48199
|
}
|
|
48347
|
-
let bSessionId;
|
|
48348
|
-
let route = "new";
|
|
48349
|
-
if (args.targetSessionId) {
|
|
48350
|
-
if (!deps.findOwnedSession) {
|
|
48351
|
-
throw new Error("targetSessionId reuse not wired (findOwnedSession missing)");
|
|
48352
|
-
}
|
|
48353
|
-
const bFile = deps.findOwnedSession(args.targetSessionId);
|
|
48354
|
-
if (!bFile) {
|
|
48355
|
-
throw new ClawdError(
|
|
48356
|
-
ERROR_CODES.SESSION_NOT_FOUND,
|
|
48357
|
-
`targetSessionId not found: ${args.targetSessionId}`
|
|
48358
|
-
);
|
|
48359
|
-
}
|
|
48360
|
-
if (bFile.ownerPersonaId !== args.targetPersona) {
|
|
48361
|
-
throw new ClawdError(
|
|
48362
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
48363
|
-
`targetSessionId does not belong to targetPersona`
|
|
48364
|
-
);
|
|
48365
|
-
}
|
|
48366
|
-
assertLocalReuseAllowed(bFile, sourceSessionId);
|
|
48367
|
-
route = "resume";
|
|
48368
|
-
bSessionId = args.targetSessionId;
|
|
48369
|
-
}
|
|
48370
48200
|
const { dispatchId } = mgr.start({
|
|
48371
48201
|
sourceSessionId,
|
|
48372
|
-
targetPersona: args.targetPersona
|
|
48373
|
-
bSessionId
|
|
48202
|
+
targetPersona: args.targetPersona
|
|
48374
48203
|
});
|
|
48375
48204
|
logger?.info("dispatch.run.received", {
|
|
48376
48205
|
dispatchId,
|
|
48377
48206
|
sourceSessionId,
|
|
48378
48207
|
targetPersona: args.targetPersona,
|
|
48379
|
-
promptLen: args.prompt.length
|
|
48380
|
-
route
|
|
48208
|
+
promptLen: args.prompt.length
|
|
48381
48209
|
});
|
|
48382
|
-
|
|
48383
|
-
|
|
48384
|
-
|
|
48385
|
-
|
|
48386
|
-
|
|
48387
|
-
|
|
48388
|
-
|
|
48389
|
-
|
|
48390
|
-
bSessionId
|
|
48391
|
-
});
|
|
48392
|
-
resolvedBSessionId = minted;
|
|
48393
|
-
logger?.info("dispatch.spawnB.ok", { dispatchId, route });
|
|
48394
|
-
} catch (err) {
|
|
48210
|
+
void spawnB({
|
|
48211
|
+
dispatchId,
|
|
48212
|
+
sourceSessionId,
|
|
48213
|
+
targetPersona: args.targetPersona,
|
|
48214
|
+
prompt: args.prompt
|
|
48215
|
+
}).then(() => {
|
|
48216
|
+
logger?.info("dispatch.spawnB.ok", { dispatchId });
|
|
48217
|
+
}).catch((err) => {
|
|
48395
48218
|
const reason = err instanceof Error ? err.message : String(err);
|
|
48396
|
-
logger?.warn("dispatch.spawnB.failed", { dispatchId,
|
|
48219
|
+
logger?.warn("dispatch.spawnB.failed", { dispatchId, reason });
|
|
48397
48220
|
mgr.complete(dispatchId, {
|
|
48398
48221
|
kind: "failure",
|
|
48399
48222
|
reason: `failed to spawn B: ${reason}`
|
|
48400
48223
|
});
|
|
48401
|
-
}
|
|
48224
|
+
});
|
|
48402
48225
|
logger?.info("dispatch.run.waiting", { dispatchId });
|
|
48403
48226
|
const outcome = await mgr.wait(dispatchId);
|
|
48404
48227
|
logger?.info("dispatch.run.resolved", {
|
|
@@ -48406,34 +48229,17 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
48406
48229
|
outcomeKind: outcome.kind
|
|
48407
48230
|
});
|
|
48408
48231
|
return {
|
|
48409
|
-
response: {
|
|
48410
|
-
type: "personaDispatch:run:ok",
|
|
48411
|
-
outcome,
|
|
48412
|
-
...resolvedBSessionId ? { dispatchedSessionId: resolvedBSessionId } : {}
|
|
48413
|
-
}
|
|
48232
|
+
response: { type: "personaDispatch:run:ok", outcome }
|
|
48414
48233
|
};
|
|
48415
48234
|
};
|
|
48416
48235
|
const complete = async (frame) => {
|
|
48417
48236
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
48418
|
-
const sessionId = typeof rest.sessionId === "string" ? rest.sessionId : void 0;
|
|
48419
48237
|
const args = DispatchCompleteArgsSchema.parse(rest);
|
|
48420
|
-
if (!sessionId) {
|
|
48421
|
-
throw new Error(
|
|
48422
|
-
"personaDispatch:complete requires sessionId (caller must pass x-clawd-session-id header)"
|
|
48423
|
-
);
|
|
48424
|
-
}
|
|
48425
|
-
const dispatchId = mgr.findInflightDispatchByBSessionId(sessionId);
|
|
48426
|
-
if (!dispatchId) {
|
|
48427
|
-
throw new Error(
|
|
48428
|
-
`no in-flight dispatch for session ${sessionId} (already completed or B was never dispatched)`
|
|
48429
|
-
);
|
|
48430
|
-
}
|
|
48431
48238
|
logger?.info("dispatch.complete.received", {
|
|
48432
|
-
dispatchId,
|
|
48433
|
-
sessionId,
|
|
48239
|
+
dispatchId: args.dispatchId,
|
|
48434
48240
|
outcomeKind: args.outcome.kind
|
|
48435
48241
|
});
|
|
48436
|
-
mgr.complete(dispatchId, args.outcome);
|
|
48242
|
+
mgr.complete(args.dispatchId, args.outcome);
|
|
48437
48243
|
return {
|
|
48438
48244
|
response: { type: "personaDispatch:complete:ok" }
|
|
48439
48245
|
};
|
|
@@ -48463,37 +48269,25 @@ async function forwardDispatchToPeer(args) {
|
|
|
48463
48269
|
authorization: `Bearer ${args.contact.connectToken}`
|
|
48464
48270
|
},
|
|
48465
48271
|
// 注意:不带 targetDeviceId —— B 端据此判定为本地执行(B 角色)。
|
|
48466
|
-
|
|
48467
|
-
body: JSON.stringify({
|
|
48468
|
-
targetPersona: args.targetPersona,
|
|
48469
|
-
prompt: args.prompt,
|
|
48470
|
-
...args.targetSessionId ? { targetSessionId: args.targetSessionId } : {}
|
|
48471
|
-
})
|
|
48272
|
+
body: JSON.stringify({ targetPersona: args.targetPersona, prompt: args.prompt })
|
|
48472
48273
|
});
|
|
48473
48274
|
} catch (err) {
|
|
48474
48275
|
const msg = err instanceof Error ? err.message : String(err);
|
|
48475
|
-
return {
|
|
48276
|
+
return { kind: "failure", reason: `forward to peer failed: ${msg}` };
|
|
48476
48277
|
}
|
|
48477
48278
|
let json;
|
|
48478
48279
|
try {
|
|
48479
48280
|
json = await res.json();
|
|
48480
48281
|
} catch {
|
|
48481
48282
|
return {
|
|
48482
|
-
|
|
48483
|
-
|
|
48484
|
-
reason: `peer returned non-JSON response (HTTP ${res.status})`
|
|
48485
|
-
}
|
|
48283
|
+
kind: "failure",
|
|
48284
|
+
reason: `peer returned non-JSON response (HTTP ${res.status})`
|
|
48486
48285
|
};
|
|
48487
48286
|
}
|
|
48488
48287
|
if (json.ok === false) {
|
|
48489
|
-
return {
|
|
48490
|
-
outcome: { kind: "failure", reason: `peer rejected: ${json.error}: ${json.message}` }
|
|
48491
|
-
};
|
|
48288
|
+
return { kind: "failure", reason: `peer rejected: ${json.error}: ${json.message}` };
|
|
48492
48289
|
}
|
|
48493
|
-
return
|
|
48494
|
-
outcome: json.result.outcome,
|
|
48495
|
-
dispatchedSessionId: json.result.dispatchedSessionId
|
|
48496
|
-
};
|
|
48290
|
+
return json.result.outcome;
|
|
48497
48291
|
}
|
|
48498
48292
|
async function forwardInboxPostToPeer(args) {
|
|
48499
48293
|
const f = args.fetchImpl ?? fetch;
|
|
@@ -57831,9 +57625,9 @@ async function startDaemon(config) {
|
|
|
57831
57625
|
// 127.0.0.1(不是 config.host)—— cc 跑在本机,loopback 最稳;外部访问限制 + http-router
|
|
57832
57626
|
// 的 isLoopback 兜底已确保安全。
|
|
57833
57627
|
getDaemonUrl: () => `http://127.0.0.1:${config.port}`,
|
|
57834
|
-
// Persona dispatch: manager 通过这两个 deps 跟 PersonaDispatchManager 协作。
|
|
57835
|
-
// - personaDispatchManager: createDispatchedSession
|
|
57836
|
-
//
|
|
57628
|
+
// Persona dispatch (Task 8): manager 通过这两个 deps 跟 PersonaDispatchManager 协作。
|
|
57629
|
+
// - personaDispatchManager: manager.createDispatchedSession 用它 registerBSession;
|
|
57630
|
+
// reducer 通过闭包反查 dispatchId 注 CLAWD_DISPATCH_ID env。
|
|
57837
57631
|
// - dispatchMcpConfigPath: reducer 透传到 SpawnContext,cc spawn 加 --mcp-config flag。
|
|
57838
57632
|
personaDispatchManager,
|
|
57839
57633
|
dispatchMcpConfigPath: dispatchMcpConfigPath2,
|
|
@@ -58215,8 +58009,7 @@ async function startDaemon(config) {
|
|
|
58215
58009
|
logger.info("dispatch.spawnB.start", {
|
|
58216
58010
|
dispatchId: args.dispatchId,
|
|
58217
58011
|
targetPersona: args.targetPersona,
|
|
58218
|
-
sourceSessionId: args.sourceSessionId
|
|
58219
|
-
route: args.route
|
|
58012
|
+
sourceSessionId: args.sourceSessionId
|
|
58220
58013
|
});
|
|
58221
58014
|
const sourceFile = manager.findOwnedSession(args.sourceSessionId);
|
|
58222
58015
|
if (!sourceFile && !args.guestPrincipalId) {
|
|
@@ -58235,23 +58028,9 @@ async function startDaemon(config) {
|
|
|
58235
58028
|
logger.info("dispatch.spawnB.source-resolved", {
|
|
58236
58029
|
dispatchId: args.dispatchId,
|
|
58237
58030
|
sourceJsonlPath,
|
|
58238
|
-
hasToolSessionId: Boolean(sourceFile?.toolSessionId)
|
|
58239
|
-
route: args.route
|
|
58031
|
+
hasToolSessionId: Boolean(sourceFile?.toolSessionId)
|
|
58240
58032
|
});
|
|
58241
|
-
|
|
58242
|
-
if (!args.bSessionId) {
|
|
58243
|
-
throw new Error("resume route requires bSessionId");
|
|
58244
|
-
}
|
|
58245
|
-
const { sessionId: sessionId2 } = manager.resumeDispatchedSession({
|
|
58246
|
-
dispatchId: args.dispatchId,
|
|
58247
|
-
bSessionId: args.bSessionId,
|
|
58248
|
-
prompt: args.prompt,
|
|
58249
|
-
sourceJsonlPath,
|
|
58250
|
-
callerIdentity: args.guestPrincipalId ? { kind: "guest", sourcePrincipalId: args.guestPrincipalId } : { kind: "local", sourceSessionId: args.sourceSessionId }
|
|
58251
|
-
});
|
|
58252
|
-
return { bSessionId: sessionId2 };
|
|
58253
|
-
}
|
|
58254
|
-
const { sessionId } = manager.createDispatchedSession({
|
|
58033
|
+
manager.createDispatchedSession({
|
|
58255
58034
|
dispatchId: args.dispatchId,
|
|
58256
58035
|
sourceSessionId: args.sourceSessionId,
|
|
58257
58036
|
targetPersona: args.targetPersona,
|
|
@@ -58261,30 +58040,24 @@ async function startDaemon(config) {
|
|
|
58261
58040
|
guestPrincipalId: args.guestPrincipalId,
|
|
58262
58041
|
guestDisplayName: args.guestDisplayName
|
|
58263
58042
|
});
|
|
58264
|
-
return { bSessionId: sessionId };
|
|
58265
58043
|
},
|
|
58266
58044
|
// A 角色:从 ContactStore 取 peer 可达 URL + connect token,转发到对端 daemon /rpc。
|
|
58267
|
-
forwardToPeer: async ({ targetDeviceId, targetPersona, prompt
|
|
58045
|
+
forwardToPeer: async ({ targetDeviceId, targetPersona, prompt }) => {
|
|
58268
58046
|
const contact = contactStore.get(targetDeviceId);
|
|
58269
58047
|
if (!contact || !contact.remoteUrl || !contact.connectToken) {
|
|
58270
58048
|
return {
|
|
58271
|
-
|
|
58272
|
-
|
|
58273
|
-
reason: `unknown or unreachable contact: ${targetDeviceId}`
|
|
58274
|
-
}
|
|
58049
|
+
kind: "failure",
|
|
58050
|
+
reason: `unknown or unreachable contact: ${targetDeviceId}`
|
|
58275
58051
|
};
|
|
58276
58052
|
}
|
|
58277
58053
|
return forwardDispatchToPeer({
|
|
58278
58054
|
contact: { remoteUrl: contact.remoteUrl, connectToken: contact.connectToken },
|
|
58279
58055
|
targetPersona,
|
|
58280
|
-
prompt
|
|
58281
|
-
targetSessionId
|
|
58056
|
+
prompt
|
|
58282
58057
|
});
|
|
58283
58058
|
},
|
|
58284
58059
|
// B 角色:判断 targetPersona 是否 public(跨设备授权边界,private 拒)。
|
|
58285
|
-
getPersonaPublic: (personaId) => personaRegistry.get(personaId)?.public ?? false
|
|
58286
|
-
// targetSessionId 复用路径的权限校验数据源:读 B session 的 SessionFile。
|
|
58287
|
-
findOwnedSession: (sessionId) => manager.findOwnedSession(sessionId)
|
|
58060
|
+
getPersonaPublic: (personaId) => personaRegistry.get(personaId)?.public ?? false
|
|
58288
58061
|
});
|
|
58289
58062
|
handlers = { ...handlers, ...dispatchHandlers };
|
|
58290
58063
|
const shiftHandlers = buildShiftInternalHandlers({
|