@clawos-dev/clawd 0.2.192-beta.386.33a5833 → 0.2.192-beta.388.6551a3f
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 +79 -303
- package/dist/dispatch/mcp-server.cjs +18 -33
- package/dist/share-md-viewer-error.html +1 -1
- package/dist/share-md-viewer.html +1 -1
- package/dist/share-ui/assets/{guest-BGhERiEC.js → guest-Ys-kGU0S.js} +128 -128
- package/dist/share-ui/assets/guest-uGhPwb9l.css +32 -0
- package/dist/share-ui/guest.html +2 -2
- package/package.json +1 -1
- package/dist/share-ui/assets/guest-DfbgxtZt.css +0 -32
package/dist/cli.cjs
CHANGED
|
@@ -247,6 +247,7 @@ var init_events = __esm({
|
|
|
247
247
|
SESSION_STATUS_VALUES = [
|
|
248
248
|
"idle",
|
|
249
249
|
"running",
|
|
250
|
+
"running-idle",
|
|
250
251
|
"stopped",
|
|
251
252
|
"error",
|
|
252
253
|
"observing"
|
|
@@ -5996,7 +5997,7 @@ var init_feishu_auth = __esm({
|
|
|
5996
5997
|
});
|
|
5997
5998
|
|
|
5998
5999
|
// ../protocol/src/dispatch.ts
|
|
5999
|
-
var DispatchOutcomeSchema, DispatchRunArgsSchema,
|
|
6000
|
+
var DispatchOutcomeSchema, DispatchRunArgsSchema, DispatchCompleteArgsSchema;
|
|
6000
6001
|
var init_dispatch = __esm({
|
|
6001
6002
|
"../protocol/src/dispatch.ts"() {
|
|
6002
6003
|
"use strict";
|
|
@@ -6017,21 +6018,10 @@ var init_dispatch = __esm({
|
|
|
6017
6018
|
prompt: external_exports.string(),
|
|
6018
6019
|
// 跨设备 dispatch:非空 = A 角色,转发到该 contact deviceId 的 peer daemon;
|
|
6019
6020
|
// 省略 = 本地 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()
|
|
6021
|
+
targetDeviceId: external_exports.string().min(1).optional()
|
|
6033
6022
|
});
|
|
6034
6023
|
DispatchCompleteArgsSchema = external_exports.object({
|
|
6024
|
+
dispatchId: external_exports.string().min(1),
|
|
6035
6025
|
outcome: DispatchOutcomeSchema
|
|
6036
6026
|
});
|
|
6037
6027
|
}
|
|
@@ -41563,6 +41553,8 @@ function buildSpawnContext(state, deps) {
|
|
|
41563
41553
|
env.CLAWD_SESSION_ID = file.sessionId;
|
|
41564
41554
|
const daemonUrl = deps.getDaemonUrl?.() ?? null;
|
|
41565
41555
|
if (daemonUrl) env.CLAWD_DAEMON_URL = daemonUrl;
|
|
41556
|
+
const dispatchId = deps.lookupDispatchByBSessionId?.(file.sessionId);
|
|
41557
|
+
if (dispatchId) env.CLAWD_DISPATCH_ID = dispatchId;
|
|
41566
41558
|
const personaId = file.ownerPersonaId;
|
|
41567
41559
|
if (personaId) env.CLAWD_PERSONA_ID = personaId;
|
|
41568
41560
|
const dispatchMcpConfigPath2 = deps.getDispatchMcpConfigPath?.() ?? null;
|
|
@@ -42368,14 +42360,14 @@ var SessionRunner = class {
|
|
|
42368
42360
|
// 单栏 refactor (spec 2026-06-02 §5.1): cc 子进程 env 注入 CLAWD_DAEMON_URL,让 assistant
|
|
42369
42361
|
// curl daemon HTTP RPC adapter (/api/rpc/<method>) 触发管理操作。null = HTTP adapter 未启。
|
|
42370
42362
|
getDaemonUrl: this.hooks.getDaemonUrl,
|
|
42371
|
-
// Persona dispatch:透传 dispatch.mcp.json
|
|
42372
|
-
//
|
|
42373
|
-
// dispatchId 不注 cc env——complete handler 用 sessionId 反查 in-flight dispatchId 配对。
|
|
42363
|
+
// Persona dispatch:透传 dispatch.mcp.json 路径闭包 + B→dispatchId 反查闭包。
|
|
42364
|
+
// 见 reducer.buildSpawnContext 注 CLAWD_DISPATCH_ID env / 派生 SpawnContext.dispatchMcpConfigPath
|
|
42374
42365
|
getDispatchMcpConfigPath: this.hooks.getDispatchMcpConfigPath,
|
|
42375
42366
|
getShiftMcpConfigPath: this.hooks.getShiftMcpConfigPath,
|
|
42376
42367
|
getInboxMcpConfigPath: this.hooks.getInboxMcpConfigPath,
|
|
42377
42368
|
// Ticket MCP:透传 ticket.mcp.json 路径闭包(reducer 内做 persona-ticket-manager gating)
|
|
42378
42369
|
getTicketMcpConfigPath: this.hooks.getTicketMcpConfigPath,
|
|
42370
|
+
lookupDispatchByBSessionId: this.hooks.lookupDispatchByBSessionId,
|
|
42379
42371
|
// ReadyGate v2:透传 mode 让 reducer send / ready-detected 分支决定走暂存队列还是直写
|
|
42380
42372
|
mode: this.hooks.mode,
|
|
42381
42373
|
// [RG-DBG] 注入 logger 让 reducer rgDbg 走 pino 进 clawd.log;定位完跟 rgDbg 一起删
|
|
@@ -42801,10 +42793,11 @@ function compressFrameForWire(frame) {
|
|
|
42801
42793
|
switch (status) {
|
|
42802
42794
|
case "spawning":
|
|
42803
42795
|
case "running":
|
|
42804
|
-
// sub-session(persona)专属内部状态:进程仍活着等 idle-kill;wire 协议看到的是 'running'
|
|
42805
|
-
case "running-idle":
|
|
42806
42796
|
compressed = "running";
|
|
42807
42797
|
break;
|
|
42798
|
+
case "running-idle":
|
|
42799
|
+
compressed = "running-idle";
|
|
42800
|
+
break;
|
|
42808
42801
|
case "stopping":
|
|
42809
42802
|
case "stopped":
|
|
42810
42803
|
compressed = "stopped";
|
|
@@ -42829,8 +42822,9 @@ function compressStatus(status) {
|
|
|
42829
42822
|
switch (status) {
|
|
42830
42823
|
case "spawning":
|
|
42831
42824
|
case "running":
|
|
42832
|
-
case "running-idle":
|
|
42833
42825
|
return "running";
|
|
42826
|
+
case "running-idle":
|
|
42827
|
+
return "running-idle";
|
|
42834
42828
|
case "stopping":
|
|
42835
42829
|
case "stopped":
|
|
42836
42830
|
return "stopped";
|
|
@@ -42859,22 +42853,9 @@ You may read this file with your Read tool to understand context. You do not hav
|
|
|
42859
42853
|
|
|
42860
42854
|
When done, call the MCP tool \`mcp__clawd-dispatch__personaDispatchComplete\` with your result:
|
|
42861
42855
|
- 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}
|
|
42871
|
-
|
|
42872
|
-
Source conversation (jsonl, same path as before, may contain new messages):
|
|
42873
|
-
${args.sourceJsonlPath}
|
|
42856
|
+
- Failure: { isFailure: true, reason: "..." }
|
|
42874
42857
|
|
|
42875
|
-
|
|
42876
|
-
- Success: { text: "...", filePaths?: ["abs/path", ...] }
|
|
42877
|
-
- Failure: { isFailure: true, reason: "..." }`;
|
|
42858
|
+
dispatchId for this task: ${args.dispatchId}`;
|
|
42878
42859
|
}
|
|
42879
42860
|
function derivePersonaSpawnCwd(file, personaRoot) {
|
|
42880
42861
|
const personaId = file.ownerPersonaId;
|
|
@@ -43195,13 +43176,13 @@ var SessionManager = class {
|
|
|
43195
43176
|
// 单栏 refactor (spec 2026-06-02 §5.1): 透传 daemon HTTP RPC base URL 闭包,
|
|
43196
43177
|
// reducer 把它注入 cc 子进程 env CLAWD_DAEMON_URL.
|
|
43197
43178
|
getDaemonUrl: this.deps.getDaemonUrl,
|
|
43198
|
-
// Persona dispatch: dispatch.mcp.json
|
|
43199
|
-
// --mcp-config flag
|
|
43200
|
-
// dispatchId 不再走 cc env——complete handler 用 sessionId 反查 in-flight dispatchId 配对。
|
|
43179
|
+
// Persona dispatch (Task 7): dispatch.mcp.json 路径 + B sessionId → dispatchId 反查
|
|
43180
|
+
// 闭包透传给 reducer,让 cc spawn 加 --mcp-config flag + B session 注 CLAWD_DISPATCH_ID env.
|
|
43201
43181
|
getDispatchMcpConfigPath: this.deps.dispatchMcpConfigPath ? () => this.deps.dispatchMcpConfigPath ?? null : void 0,
|
|
43202
43182
|
getTicketMcpConfigPath: this.deps.ticketMcpConfigPath ? () => this.deps.ticketMcpConfigPath ?? null : void 0,
|
|
43203
43183
|
getShiftMcpConfigPath: this.deps.shiftMcpConfigPath ? () => this.deps.shiftMcpConfigPath ?? null : void 0,
|
|
43204
43184
|
getInboxMcpConfigPath: this.deps.inboxMcpConfigPath ? () => this.deps.inboxMcpConfigPath ?? null : void 0,
|
|
43185
|
+
lookupDispatchByBSessionId: this.deps.personaDispatchManager ? (bSid) => this.deps.personaDispatchManager?.lookupDispatchByBSessionId(bSid) : void 0,
|
|
43205
43186
|
// file-sharing (spec §6 PR 3):闭包 scope + sessionId,runner 只暴露 tool/relPath/cwd
|
|
43206
43187
|
onFileEdit: attachmentGroup ? (input) => attachmentGroup.onFileEdit({
|
|
43207
43188
|
scope,
|
|
@@ -44041,11 +44022,10 @@ var SessionManager = class {
|
|
|
44041
44022
|
* Persona dispatch: 启动 B session 并投递任务包当第一条 user message。
|
|
44042
44023
|
*
|
|
44043
44024
|
* - 复用 persona-owner 创建路径(cwd=personaDir、scope=persona-owner、不沙箱)
|
|
44044
|
-
* - SessionFile 上写 dispatchedFromSessionId 关联回 A(UI 折叠用;mirror 一侧 strip
|
|
44045
|
-
*
|
|
44046
|
-
*
|
|
44047
|
-
*
|
|
44048
|
-
* - 投任务包;包尾教 B 用 personaDispatchComplete tool 回传
|
|
44025
|
+
* - SessionFile 上写 dispatchedFromSessionId 关联回 A(UI 折叠用;mirror 一侧 strip)
|
|
44026
|
+
* - 跟 PersonaDispatchManager 双向登记 dispatchId ↔ B sessionId,让后续 reducer
|
|
44027
|
+
* buildSpawnContext 能反查到 dispatchId 注 CLAWD_DISPATCH_ID env
|
|
44028
|
+
* - 投 inject-owner-text 任务包;包尾教 B 用 personaDispatchComplete tool 回传
|
|
44049
44029
|
*
|
|
44050
44030
|
* cwd 派生与 SessionManager.create 的 owner persona 路径一致(derivePersonaSpawnCwd)。
|
|
44051
44031
|
*/
|
|
@@ -44100,7 +44080,8 @@ var SessionManager = class {
|
|
|
44100
44080
|
});
|
|
44101
44081
|
const taskPack = buildDispatchTaskPack({
|
|
44102
44082
|
prompt: args.prompt,
|
|
44103
|
-
sourceJsonlPath: args.sourceJsonlPath
|
|
44083
|
+
sourceJsonlPath: args.sourceJsonlPath,
|
|
44084
|
+
dispatchId: args.dispatchId
|
|
44104
44085
|
});
|
|
44105
44086
|
runner.input({ kind: "command", command: { kind: "send", text: taskPack } });
|
|
44106
44087
|
this.deps.logger?.info("dispatch.createDispatchedSession.task-pack-sent", {
|
|
@@ -44109,59 +44090,6 @@ var SessionManager = class {
|
|
|
44109
44090
|
});
|
|
44110
44091
|
return { sessionId };
|
|
44111
44092
|
}
|
|
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
44093
|
/**
|
|
44166
44094
|
* shift v1 (spec 2026-06-24-clawd-shift):到点 fire 时由 ShiftScheduler 调,
|
|
44167
44095
|
* 起一个新 cc session 跑 prompt(fire-and-forget,不绑 dispatchId / 不挂 caller)。
|
|
@@ -47568,7 +47496,6 @@ var PersonaDispatchManager = class {
|
|
|
47568
47496
|
dispatchId,
|
|
47569
47497
|
sourceSessionId: args.sourceSessionId,
|
|
47570
47498
|
targetPersona: args.targetPersona,
|
|
47571
|
-
bSessionId: args.bSessionId,
|
|
47572
47499
|
waiters: [],
|
|
47573
47500
|
outcome: null
|
|
47574
47501
|
});
|
|
@@ -47599,22 +47526,10 @@ var PersonaDispatchManager = class {
|
|
|
47599
47526
|
if (!state) throw new Error(`unknown dispatchId: ${dispatchId}`);
|
|
47600
47527
|
state.bSessionId = bSessionId;
|
|
47601
47528
|
}
|
|
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) {
|
|
47529
|
+
/** reducer buildSpawnContext 调,按 B 的 sessionId 反查 dispatchId(用于注 CLAWD_DISPATCH_ID env) */
|
|
47530
|
+
lookupDispatchByBSessionId(bSessionId) {
|
|
47614
47531
|
for (const state of this.map.values()) {
|
|
47615
|
-
if (state.bSessionId === bSessionId
|
|
47616
|
-
return state.dispatchId;
|
|
47617
|
-
}
|
|
47532
|
+
if (state.bSessionId === bSessionId) return state.dispatchId;
|
|
47618
47533
|
}
|
|
47619
47534
|
return void 0;
|
|
47620
47535
|
}
|
|
@@ -48213,28 +48128,6 @@ function canAccessPersona(grants, personaId, action) {
|
|
|
48213
48128
|
}
|
|
48214
48129
|
|
|
48215
48130
|
// 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
48131
|
function buildPersonaDispatchHandlers(deps) {
|
|
48239
48132
|
const { personaDispatchManager: mgr, spawnB, logger } = deps;
|
|
48240
48133
|
const run = async (frame, _client, ctx) => {
|
|
@@ -48253,21 +48146,15 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
48253
48146
|
}
|
|
48254
48147
|
logger?.info("dispatch.run.forward", {
|
|
48255
48148
|
targetDeviceId: args.targetDeviceId,
|
|
48256
|
-
targetPersona: args.targetPersona
|
|
48257
|
-
hasTargetSessionId: Boolean(args.targetSessionId)
|
|
48149
|
+
targetPersona: args.targetPersona
|
|
48258
48150
|
});
|
|
48259
|
-
const
|
|
48151
|
+
const outcome2 = await deps.forwardToPeer({
|
|
48260
48152
|
targetDeviceId: args.targetDeviceId,
|
|
48261
48153
|
targetPersona: args.targetPersona,
|
|
48262
|
-
prompt: args.prompt
|
|
48263
|
-
targetSessionId: args.targetSessionId
|
|
48154
|
+
prompt: args.prompt
|
|
48264
48155
|
});
|
|
48265
48156
|
return {
|
|
48266
|
-
response: {
|
|
48267
|
-
type: "personaDispatch:run:ok",
|
|
48268
|
-
outcome: outcome2,
|
|
48269
|
-
...dispatchedSessionId ? { dispatchedSessionId } : {}
|
|
48270
|
-
}
|
|
48157
|
+
response: { type: "personaDispatch:run:ok", outcome: outcome2 }
|
|
48271
48158
|
};
|
|
48272
48159
|
}
|
|
48273
48160
|
if (ctx?.principal.kind === "guest") {
|
|
@@ -48278,65 +48165,34 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
48278
48165
|
`persona not dispatchable: ${args.targetPersona}`
|
|
48279
48166
|
);
|
|
48280
48167
|
}
|
|
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
48168
|
const { dispatchId: dispatchId2 } = mgr.start({
|
|
48300
|
-
sourceSessionId:
|
|
48301
|
-
targetPersona: args.targetPersona
|
|
48302
|
-
bSessionId: bSessionId2
|
|
48169
|
+
sourceSessionId: ctx.principal.id,
|
|
48170
|
+
targetPersona: args.targetPersona
|
|
48303
48171
|
});
|
|
48304
48172
|
logger?.info("dispatch.run.received.guest", {
|
|
48305
48173
|
dispatchId: dispatchId2,
|
|
48306
|
-
sourcePrincipal:
|
|
48174
|
+
sourcePrincipal: ctx.principal.id,
|
|
48307
48175
|
targetPersona: args.targetPersona,
|
|
48308
|
-
promptLen: args.prompt.length
|
|
48309
|
-
route: route2
|
|
48176
|
+
promptLen: args.prompt.length
|
|
48310
48177
|
});
|
|
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) {
|
|
48178
|
+
void spawnB({
|
|
48179
|
+
dispatchId: dispatchId2,
|
|
48180
|
+
sourceSessionId: ctx.principal.id,
|
|
48181
|
+
targetPersona: args.targetPersona,
|
|
48182
|
+
prompt: args.prompt,
|
|
48183
|
+
guestPrincipalId: ctx.principal.id,
|
|
48184
|
+
guestDisplayName: ctx.principal.displayName
|
|
48185
|
+
}).then(() => logger?.info("dispatch.spawnB.ok", { dispatchId: dispatchId2 })).catch((err) => {
|
|
48326
48186
|
const reason = err instanceof Error ? err.message : String(err);
|
|
48327
|
-
logger?.warn("dispatch.spawnB.failed", { dispatchId: dispatchId2,
|
|
48187
|
+
logger?.warn("dispatch.spawnB.failed", { dispatchId: dispatchId2, reason });
|
|
48328
48188
|
mgr.complete(dispatchId2, {
|
|
48329
48189
|
kind: "failure",
|
|
48330
48190
|
reason: `failed to spawn B: ${reason}`
|
|
48331
48191
|
});
|
|
48332
|
-
}
|
|
48192
|
+
});
|
|
48333
48193
|
const outcome2 = await mgr.wait(dispatchId2);
|
|
48334
48194
|
return {
|
|
48335
|
-
response: {
|
|
48336
|
-
type: "personaDispatch:run:ok",
|
|
48337
|
-
outcome: outcome2,
|
|
48338
|
-
...resolvedBSessionId2 ? { dispatchedSessionId: resolvedBSessionId2 } : {}
|
|
48339
|
-
}
|
|
48195
|
+
response: { type: "personaDispatch:run:ok", outcome: outcome2 }
|
|
48340
48196
|
};
|
|
48341
48197
|
}
|
|
48342
48198
|
if (!sourceSessionId) {
|
|
@@ -48344,61 +48200,31 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
48344
48200
|
"personaDispatch:run requires sessionId (caller must pass x-clawd-session-id header)"
|
|
48345
48201
|
);
|
|
48346
48202
|
}
|
|
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
48203
|
const { dispatchId } = mgr.start({
|
|
48371
48204
|
sourceSessionId,
|
|
48372
|
-
targetPersona: args.targetPersona
|
|
48373
|
-
bSessionId
|
|
48205
|
+
targetPersona: args.targetPersona
|
|
48374
48206
|
});
|
|
48375
48207
|
logger?.info("dispatch.run.received", {
|
|
48376
48208
|
dispatchId,
|
|
48377
48209
|
sourceSessionId,
|
|
48378
48210
|
targetPersona: args.targetPersona,
|
|
48379
|
-
promptLen: args.prompt.length
|
|
48380
|
-
route
|
|
48211
|
+
promptLen: args.prompt.length
|
|
48381
48212
|
});
|
|
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) {
|
|
48213
|
+
void spawnB({
|
|
48214
|
+
dispatchId,
|
|
48215
|
+
sourceSessionId,
|
|
48216
|
+
targetPersona: args.targetPersona,
|
|
48217
|
+
prompt: args.prompt
|
|
48218
|
+
}).then(() => {
|
|
48219
|
+
logger?.info("dispatch.spawnB.ok", { dispatchId });
|
|
48220
|
+
}).catch((err) => {
|
|
48395
48221
|
const reason = err instanceof Error ? err.message : String(err);
|
|
48396
|
-
logger?.warn("dispatch.spawnB.failed", { dispatchId,
|
|
48222
|
+
logger?.warn("dispatch.spawnB.failed", { dispatchId, reason });
|
|
48397
48223
|
mgr.complete(dispatchId, {
|
|
48398
48224
|
kind: "failure",
|
|
48399
48225
|
reason: `failed to spawn B: ${reason}`
|
|
48400
48226
|
});
|
|
48401
|
-
}
|
|
48227
|
+
});
|
|
48402
48228
|
logger?.info("dispatch.run.waiting", { dispatchId });
|
|
48403
48229
|
const outcome = await mgr.wait(dispatchId);
|
|
48404
48230
|
logger?.info("dispatch.run.resolved", {
|
|
@@ -48406,34 +48232,17 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
48406
48232
|
outcomeKind: outcome.kind
|
|
48407
48233
|
});
|
|
48408
48234
|
return {
|
|
48409
|
-
response: {
|
|
48410
|
-
type: "personaDispatch:run:ok",
|
|
48411
|
-
outcome,
|
|
48412
|
-
...resolvedBSessionId ? { dispatchedSessionId: resolvedBSessionId } : {}
|
|
48413
|
-
}
|
|
48235
|
+
response: { type: "personaDispatch:run:ok", outcome }
|
|
48414
48236
|
};
|
|
48415
48237
|
};
|
|
48416
48238
|
const complete = async (frame) => {
|
|
48417
48239
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
48418
|
-
const sessionId = typeof rest.sessionId === "string" ? rest.sessionId : void 0;
|
|
48419
48240
|
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
48241
|
logger?.info("dispatch.complete.received", {
|
|
48432
|
-
dispatchId,
|
|
48433
|
-
sessionId,
|
|
48242
|
+
dispatchId: args.dispatchId,
|
|
48434
48243
|
outcomeKind: args.outcome.kind
|
|
48435
48244
|
});
|
|
48436
|
-
mgr.complete(dispatchId, args.outcome);
|
|
48245
|
+
mgr.complete(args.dispatchId, args.outcome);
|
|
48437
48246
|
return {
|
|
48438
48247
|
response: { type: "personaDispatch:complete:ok" }
|
|
48439
48248
|
};
|
|
@@ -48463,37 +48272,25 @@ async function forwardDispatchToPeer(args) {
|
|
|
48463
48272
|
authorization: `Bearer ${args.contact.connectToken}`
|
|
48464
48273
|
},
|
|
48465
48274
|
// 注意:不带 targetDeviceId —— B 端据此判定为本地执行(B 角色)。
|
|
48466
|
-
|
|
48467
|
-
body: JSON.stringify({
|
|
48468
|
-
targetPersona: args.targetPersona,
|
|
48469
|
-
prompt: args.prompt,
|
|
48470
|
-
...args.targetSessionId ? { targetSessionId: args.targetSessionId } : {}
|
|
48471
|
-
})
|
|
48275
|
+
body: JSON.stringify({ targetPersona: args.targetPersona, prompt: args.prompt })
|
|
48472
48276
|
});
|
|
48473
48277
|
} catch (err) {
|
|
48474
48278
|
const msg = err instanceof Error ? err.message : String(err);
|
|
48475
|
-
return {
|
|
48279
|
+
return { kind: "failure", reason: `forward to peer failed: ${msg}` };
|
|
48476
48280
|
}
|
|
48477
48281
|
let json;
|
|
48478
48282
|
try {
|
|
48479
48283
|
json = await res.json();
|
|
48480
48284
|
} catch {
|
|
48481
48285
|
return {
|
|
48482
|
-
|
|
48483
|
-
|
|
48484
|
-
reason: `peer returned non-JSON response (HTTP ${res.status})`
|
|
48485
|
-
}
|
|
48286
|
+
kind: "failure",
|
|
48287
|
+
reason: `peer returned non-JSON response (HTTP ${res.status})`
|
|
48486
48288
|
};
|
|
48487
48289
|
}
|
|
48488
48290
|
if (json.ok === false) {
|
|
48489
|
-
return {
|
|
48490
|
-
outcome: { kind: "failure", reason: `peer rejected: ${json.error}: ${json.message}` }
|
|
48491
|
-
};
|
|
48291
|
+
return { kind: "failure", reason: `peer rejected: ${json.error}: ${json.message}` };
|
|
48492
48292
|
}
|
|
48493
|
-
return
|
|
48494
|
-
outcome: json.result.outcome,
|
|
48495
|
-
dispatchedSessionId: json.result.dispatchedSessionId
|
|
48496
|
-
};
|
|
48293
|
+
return json.result.outcome;
|
|
48497
48294
|
}
|
|
48498
48295
|
async function forwardInboxPostToPeer(args) {
|
|
48499
48296
|
const f = args.fetchImpl ?? fetch;
|
|
@@ -57831,9 +57628,9 @@ async function startDaemon(config) {
|
|
|
57831
57628
|
// 127.0.0.1(不是 config.host)—— cc 跑在本机,loopback 最稳;外部访问限制 + http-router
|
|
57832
57629
|
// 的 isLoopback 兜底已确保安全。
|
|
57833
57630
|
getDaemonUrl: () => `http://127.0.0.1:${config.port}`,
|
|
57834
|
-
// Persona dispatch: manager 通过这两个 deps 跟 PersonaDispatchManager 协作。
|
|
57835
|
-
// - personaDispatchManager: createDispatchedSession
|
|
57836
|
-
//
|
|
57631
|
+
// Persona dispatch (Task 8): manager 通过这两个 deps 跟 PersonaDispatchManager 协作。
|
|
57632
|
+
// - personaDispatchManager: manager.createDispatchedSession 用它 registerBSession;
|
|
57633
|
+
// reducer 通过闭包反查 dispatchId 注 CLAWD_DISPATCH_ID env。
|
|
57837
57634
|
// - dispatchMcpConfigPath: reducer 透传到 SpawnContext,cc spawn 加 --mcp-config flag。
|
|
57838
57635
|
personaDispatchManager,
|
|
57839
57636
|
dispatchMcpConfigPath: dispatchMcpConfigPath2,
|
|
@@ -58215,8 +58012,7 @@ async function startDaemon(config) {
|
|
|
58215
58012
|
logger.info("dispatch.spawnB.start", {
|
|
58216
58013
|
dispatchId: args.dispatchId,
|
|
58217
58014
|
targetPersona: args.targetPersona,
|
|
58218
|
-
sourceSessionId: args.sourceSessionId
|
|
58219
|
-
route: args.route
|
|
58015
|
+
sourceSessionId: args.sourceSessionId
|
|
58220
58016
|
});
|
|
58221
58017
|
const sourceFile = manager.findOwnedSession(args.sourceSessionId);
|
|
58222
58018
|
if (!sourceFile && !args.guestPrincipalId) {
|
|
@@ -58235,23 +58031,9 @@ async function startDaemon(config) {
|
|
|
58235
58031
|
logger.info("dispatch.spawnB.source-resolved", {
|
|
58236
58032
|
dispatchId: args.dispatchId,
|
|
58237
58033
|
sourceJsonlPath,
|
|
58238
|
-
hasToolSessionId: Boolean(sourceFile?.toolSessionId)
|
|
58239
|
-
route: args.route
|
|
58034
|
+
hasToolSessionId: Boolean(sourceFile?.toolSessionId)
|
|
58240
58035
|
});
|
|
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({
|
|
58036
|
+
manager.createDispatchedSession({
|
|
58255
58037
|
dispatchId: args.dispatchId,
|
|
58256
58038
|
sourceSessionId: args.sourceSessionId,
|
|
58257
58039
|
targetPersona: args.targetPersona,
|
|
@@ -58261,30 +58043,24 @@ async function startDaemon(config) {
|
|
|
58261
58043
|
guestPrincipalId: args.guestPrincipalId,
|
|
58262
58044
|
guestDisplayName: args.guestDisplayName
|
|
58263
58045
|
});
|
|
58264
|
-
return { bSessionId: sessionId };
|
|
58265
58046
|
},
|
|
58266
58047
|
// A 角色:从 ContactStore 取 peer 可达 URL + connect token,转发到对端 daemon /rpc。
|
|
58267
|
-
forwardToPeer: async ({ targetDeviceId, targetPersona, prompt
|
|
58048
|
+
forwardToPeer: async ({ targetDeviceId, targetPersona, prompt }) => {
|
|
58268
58049
|
const contact = contactStore.get(targetDeviceId);
|
|
58269
58050
|
if (!contact || !contact.remoteUrl || !contact.connectToken) {
|
|
58270
58051
|
return {
|
|
58271
|
-
|
|
58272
|
-
|
|
58273
|
-
reason: `unknown or unreachable contact: ${targetDeviceId}`
|
|
58274
|
-
}
|
|
58052
|
+
kind: "failure",
|
|
58053
|
+
reason: `unknown or unreachable contact: ${targetDeviceId}`
|
|
58275
58054
|
};
|
|
58276
58055
|
}
|
|
58277
58056
|
return forwardDispatchToPeer({
|
|
58278
58057
|
contact: { remoteUrl: contact.remoteUrl, connectToken: contact.connectToken },
|
|
58279
58058
|
targetPersona,
|
|
58280
|
-
prompt
|
|
58281
|
-
targetSessionId
|
|
58059
|
+
prompt
|
|
58282
58060
|
});
|
|
58283
58061
|
},
|
|
58284
58062
|
// B 角色:判断 targetPersona 是否 public(跨设备授权边界,private 拒)。
|
|
58285
|
-
getPersonaPublic: (personaId) => personaRegistry.get(personaId)?.public ?? false
|
|
58286
|
-
// targetSessionId 复用路径的权限校验数据源:读 B session 的 SessionFile。
|
|
58287
|
-
findOwnedSession: (sessionId) => manager.findOwnedSession(sessionId)
|
|
58063
|
+
getPersonaPublic: (personaId) => personaRegistry.get(personaId)?.public ?? false
|
|
58288
58064
|
});
|
|
58289
58065
|
handlers = { ...handlers, ...dispatchHandlers };
|
|
58290
58066
|
const shiftHandlers = buildShiftInternalHandlers({
|