@clawos-dev/clawd 0.2.164-beta.340.60574f3 → 0.2.165-beta.341.c72ef11
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 +202 -63
- package/dist/dispatch/mcp-server.cjs +6 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -105,6 +105,12 @@ var init_methods = __esm({
|
|
|
105
105
|
"persona:get",
|
|
106
106
|
"persona:update",
|
|
107
107
|
"persona:delete",
|
|
108
|
+
// ---- personaDispatch:* (跨设备 dispatch) ----
|
|
109
|
+
// personaDispatch:run 有两个入口:本地 cc 经 /api/rpc(EXPOSED_RPC_METHODS,127.0.0.1 无 token);
|
|
110
|
+
// 跨设备时 A 的 peer daemon 经 /rpc(Bearer connect token)。后者要求它是 WS METHOD_NAME +
|
|
111
|
+
// METHOD_GRANT_MAP capability-scoped(handler 自检 persona.public + assertGrant(send))。
|
|
112
|
+
// personaDispatch:complete 仅 B 本地 cc 调(/api/rpc),不跨设备,故不列入 WS method。
|
|
113
|
+
"personaDispatch:run",
|
|
108
114
|
// ---- session:pty 双向透传(仅 CLAWD_CC_MODE=tui 才生效,对应 session:pty push 帧的上行) ----
|
|
109
115
|
// session:pty:input — UI 把用户键盘字节(base64 UTF-8)发给 daemon,daemon 直写 pty stdin。
|
|
110
116
|
// 高频帧(每次按键 1 帧),不入 reducer / 不广播。response 是 fire-and-forget 风格 ack。
|
|
@@ -4704,9 +4710,6 @@ var init_schemas = __esm({
|
|
|
4704
4710
|
// 不受 parent clear / re-spawn 影响);sidebar 据此挂 fork 图标。非 fork session 该字段缺省。
|
|
4705
4711
|
// 旧字段 forkedFromToolSessionId 已弃用,daemon 启动时一次性迁移老数据
|
|
4706
4712
|
forkedFromSessionId: external_exports.string().min(1).optional(),
|
|
4707
|
-
// 快速问答临时会话标记(spec 2026-06-23-fork-quick-ask):true = 关闭抽屉即删、
|
|
4708
|
-
// 不进 sidebar 树、daemon 启动清扫孤儿。普通会话缺省。
|
|
4709
|
-
ephemeral: external_exports.boolean().optional(),
|
|
4710
4713
|
// owner-mode persona session 身份标识;UI 用它在 SessionList 过滤 + jump,
|
|
4711
4714
|
// daemon 用它做 idempotent dedupe + spawn 时派生 ctx.personaMode='owner'
|
|
4712
4715
|
ownerPersonaId: external_exports.string().min(1).optional(),
|
|
@@ -4995,8 +4998,6 @@ var init_schemas = __esm({
|
|
|
4995
4998
|
iconKey: external_exports.string().optional(),
|
|
4996
4999
|
// session:fork 派生 session 时由 UI 传入源 sessionId,daemon 写到 SessionFile.forkedFromSessionId
|
|
4997
5000
|
forkedFromSessionId: external_exports.string().min(1).optional(),
|
|
4998
|
-
// 快速问答:创建时打 ephemeral 标记,daemon 写到 SessionFile.ephemeral
|
|
4999
|
-
ephemeral: external_exports.boolean().optional(),
|
|
5000
5001
|
// owner-mode persona session:传此字段时 daemon 自行派生 cwd 和启动参数,
|
|
5001
5002
|
// 且按 ownerPersonaId 做 idempotent dedupe(每 persona 永远只有一个 owner session)
|
|
5002
5003
|
ownerPersonaId: external_exports.string().min(1).optional()
|
|
@@ -5982,7 +5983,10 @@ var init_dispatch = __esm({
|
|
|
5982
5983
|
]);
|
|
5983
5984
|
DispatchRunArgsSchema = external_exports.object({
|
|
5984
5985
|
targetPersona: external_exports.string().min(1),
|
|
5985
|
-
prompt: external_exports.string()
|
|
5986
|
+
prompt: external_exports.string(),
|
|
5987
|
+
// 跨设备 dispatch:非空 = A 角色,转发到该 contact deviceId 的 peer daemon;
|
|
5988
|
+
// 省略 = 本地 dispatch / B 角色本地执行。A 转发给 B 时 body 不带此字段。
|
|
5989
|
+
targetDeviceId: external_exports.string().min(1).optional()
|
|
5986
5990
|
});
|
|
5987
5991
|
DispatchCompleteArgsSchema = external_exports.object({
|
|
5988
5992
|
dispatchId: external_exports.string().min(1),
|
|
@@ -32142,6 +32146,14 @@ tool \u4F1A\u963B\u585E\u6302\u8D77\u7B49\u5BF9\u65B9\u4EBA\u683C\u5E72\u5B8C\uF
|
|
|
32142
32146
|
- \u4E00\u53E5\u8BDD\u91CC\u591A\u4E2A \`@persona/<id>\` \u65F6\u9009\u7B2C\u4E00\u4E2A\uFF08tool \u4E00\u6B21\u53EA\u5904\u7406\u4E00\u4E2A target\uFF09
|
|
32143
32147
|
- \u59D4\u6D3E\u5931\u8D25\uFF08tool_result.is_error=true\uFF09\u65F6\u5982\u5B9E\u544A\u8BC9\u7528\u6237\u539F\u56E0\uFF0C**\u4E0D\u8981\u91CD\u8BD5**
|
|
32144
32148
|
|
|
32149
|
+
### \u8DE8\u8BBE\u5907\u59D4\u6D3E\uFF08remote persona\uFF09
|
|
32150
|
+
|
|
32151
|
+
\u5F53 @ token \u5F62\u5982 \`@persona/<id>@<\u5BF9\u65B9>\`\uFF08\`<\u5BF9\u65B9>\` \u662F\u8054\u7CFB\u4EBA\u540D\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
|
|
32152
|
+
|
|
32153
|
+
- \`targetDeviceId = <\u5BF9\u65B9\u5BF9\u5E94\u7684 contact deviceId>\`
|
|
32154
|
+
|
|
32155
|
+
\u4F60\u4E0D\u77E5\u9053\u786E\u5207 deviceId \u65F6\u6309\u8054\u7CFB\u4EBA\u540D\u539F\u6837\u586B \`targetDeviceId\`\uFF0Cdaemon \u4F1A\u89E3\u6790\uFF1B\u89E3\u6790\u4E0D\u5230 / \u5BF9\u65B9\u672A\u6388\u6743\u4F1A\u8FD4\u56DE\u660E\u786E\u9519\u8BEF\uFF0C\u7167\u5E38\u628A\u9519\u8BEF\u8F6C\u8FF0\u7ED9\u7528\u6237\u5373\u53EF\u3002\u5176\u4F59\u884C\u4E3A\uFF08\u963B\u585E\u7B49\u7ED3\u679C\u3001\u5931\u8D25\u4E0D\u91CD\u8BD5\uFF09\u4E0E\u672C\u5730\u59D4\u6D3E\u4E00\u81F4\u3002
|
|
32156
|
+
|
|
32145
32157
|
## \u88AB dispatch \u63A5\u5230\u4EFB\u52A1
|
|
32146
32158
|
|
|
32147
32159
|
\u5982\u679C\u4F60\u7684 session \u7B2C\u4E00\u6761 user message \u662F\u4EE5 \`[Dispatched from owner \u2014 another persona session\u59D4\u6D3E\u7ED9\u4F60\u6267\u884C\u4EFB\u52A1]\` \u8D77\u5934\u7684 plain text \u4EFB\u52A1\u5305\uFF0C\u4F60\u662F\u88AB\u53E6\u4E00\u4E2A persona \u59D4\u6D3E\u6765\u5E72\u6D3B\u7684\uFF08\u4EFB\u52A1\u5305\u4E0D\u7528 \`<system-reminder>\` \u5305\u662F\u4E3A\u4E86 reducer dedup \u5DE5\u4F5C\u6B63\u5E38\uFF0C\u89C1 manager.ts buildDispatchTaskPack \u6CE8\u91CA\uFF09\uFF1A
|
|
@@ -33956,7 +33968,6 @@ var SessionManager = class {
|
|
|
33956
33968
|
forkedFromSessionId: args.forkedFromSessionId,
|
|
33957
33969
|
ownerPersonaId: args.ownerPersonaId,
|
|
33958
33970
|
appBuilderProject: args.appBuilderProject,
|
|
33959
|
-
ephemeral: args.ephemeral,
|
|
33960
33971
|
creatorPrincipalId,
|
|
33961
33972
|
creatorDisplayName,
|
|
33962
33973
|
createdAt: iso,
|
|
@@ -34125,19 +34136,6 @@ var SessionManager = class {
|
|
|
34125
34136
|
this.writeOwned(updated);
|
|
34126
34137
|
return { response: updated, broadcast: [] };
|
|
34127
34138
|
}
|
|
34128
|
-
/**
|
|
34129
|
-
* 启动清扫:删除所有磁盘残留的 ephemeral(快速问答临时)session。
|
|
34130
|
-
* 客户端崩溃 / 没关抽屉会留下永久不可见的孤儿(ephemeral 被 sidebar 过滤,用户自己也删不掉)。
|
|
34131
|
-
* 启动时调用——此刻无客户端连接,纯文件删除,不广播。
|
|
34132
|
-
*/
|
|
34133
|
-
sweepEphemeralOnStartup() {
|
|
34134
|
-
for (const file of this.listAllOwned()) {
|
|
34135
|
-
if (file.ephemeral) {
|
|
34136
|
-
this.deleteOwned(file.sessionId);
|
|
34137
|
-
this.deps.logger?.info?.(`swept ephemeral session ${file.sessionId}`);
|
|
34138
|
-
}
|
|
34139
|
-
}
|
|
34140
|
-
}
|
|
34141
34139
|
delete(args) {
|
|
34142
34140
|
const existing = this.findOwnedSession(args.sessionId);
|
|
34143
34141
|
if (!existing) throw new ClawdError(ERROR_CODES.SESSION_NOT_FOUND, args.sessionId);
|
|
@@ -34666,7 +34664,12 @@ var SessionManager = class {
|
|
|
34666
34664
|
throw new Error("createDispatchedSession: personaDispatchManager missing in ManagerDeps");
|
|
34667
34665
|
}
|
|
34668
34666
|
const sessionId = newSessionId();
|
|
34669
|
-
const scope = {
|
|
34667
|
+
const scope = args.guestPrincipalId ? {
|
|
34668
|
+
kind: "persona",
|
|
34669
|
+
personaId: args.targetPersona,
|
|
34670
|
+
mode: "guest",
|
|
34671
|
+
capId: args.guestPrincipalId
|
|
34672
|
+
} : {
|
|
34670
34673
|
kind: "persona",
|
|
34671
34674
|
personaId: args.targetPersona,
|
|
34672
34675
|
mode: "owner"
|
|
@@ -34679,6 +34682,13 @@ var SessionManager = class {
|
|
|
34679
34682
|
tool: "claude",
|
|
34680
34683
|
ownerPersonaId: args.targetPersona,
|
|
34681
34684
|
dispatchedFromSessionId: args.sourceSessionId,
|
|
34685
|
+
// 跨设备显示关键(spec §8):creatorPrincipalId = A 的 deviceId,让 A 的
|
|
34686
|
+
// useRemoteSessionsByOwner(按 creatorPrincipalId === A.deviceId 过滤)能 list 到此 B session。
|
|
34687
|
+
// creatorDisplayName = A 的 displayName,供 guest meta callerDisplayName(reducer 要求)。
|
|
34688
|
+
...args.guestPrincipalId ? {
|
|
34689
|
+
creatorPrincipalId: args.guestPrincipalId,
|
|
34690
|
+
...args.guestDisplayName ? { creatorDisplayName: args.guestDisplayName } : {}
|
|
34691
|
+
} : {},
|
|
34682
34692
|
createdAt: now,
|
|
34683
34693
|
updatedAt: now
|
|
34684
34694
|
};
|
|
@@ -37908,12 +37918,104 @@ function resolveClawdTicketsApiKey() {
|
|
|
37908
37918
|
|
|
37909
37919
|
// src/handlers/persona-dispatch.ts
|
|
37910
37920
|
init_protocol();
|
|
37921
|
+
|
|
37922
|
+
// src/permission/capability.ts
|
|
37923
|
+
function matchResource(grant, target) {
|
|
37924
|
+
if (grant.type === "*") return true;
|
|
37925
|
+
if (grant.type !== target.type) return false;
|
|
37926
|
+
if (grant.id === "*") return true;
|
|
37927
|
+
return grant.id === target.id;
|
|
37928
|
+
}
|
|
37929
|
+
function assertGrant(grants, resource, action) {
|
|
37930
|
+
for (const g2 of grants) {
|
|
37931
|
+
if (!matchResource(g2.resource, resource)) continue;
|
|
37932
|
+
if (g2.actions.includes(action)) return true;
|
|
37933
|
+
if (g2.actions.includes("admin")) return true;
|
|
37934
|
+
}
|
|
37935
|
+
return false;
|
|
37936
|
+
}
|
|
37937
|
+
|
|
37938
|
+
// src/permission/session-access.ts
|
|
37939
|
+
function canAccessSession(ctx, sessionId, action, deps) {
|
|
37940
|
+
if (ctx.principal.kind === "owner") return true;
|
|
37941
|
+
const file = deps.readSession(sessionId);
|
|
37942
|
+
if (!file) return true;
|
|
37943
|
+
return canAccessPersona(ctx.grants, file.ownerPersonaId, action);
|
|
37944
|
+
}
|
|
37945
|
+
function canAccessPersona(grants, personaId, action) {
|
|
37946
|
+
if (!personaId) return false;
|
|
37947
|
+
const resource = { type: "persona", id: personaId };
|
|
37948
|
+
return assertGrant(grants, resource, action);
|
|
37949
|
+
}
|
|
37950
|
+
|
|
37951
|
+
// src/handlers/persona-dispatch.ts
|
|
37911
37952
|
function buildPersonaDispatchHandlers(deps) {
|
|
37912
37953
|
const { personaDispatchManager: mgr, spawnB, logger } = deps;
|
|
37913
|
-
const run = async (frame) => {
|
|
37954
|
+
const run = async (frame, _client, ctx) => {
|
|
37914
37955
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
37915
37956
|
const sourceSessionId = typeof rest.sessionId === "string" ? rest.sessionId : void 0;
|
|
37916
37957
|
const args = DispatchRunArgsSchema.parse(rest);
|
|
37958
|
+
if (args.targetDeviceId) {
|
|
37959
|
+
if (ctx?.principal.kind === "guest") {
|
|
37960
|
+
throw new ClawdError(
|
|
37961
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
37962
|
+
"cross-device forward cannot be initiated by a guest"
|
|
37963
|
+
);
|
|
37964
|
+
}
|
|
37965
|
+
if (!deps.forwardToPeer) {
|
|
37966
|
+
throw new Error("cross-device dispatch not wired (forwardToPeer missing)");
|
|
37967
|
+
}
|
|
37968
|
+
logger?.info("dispatch.run.forward", {
|
|
37969
|
+
targetDeviceId: args.targetDeviceId,
|
|
37970
|
+
targetPersona: args.targetPersona
|
|
37971
|
+
});
|
|
37972
|
+
const outcome2 = await deps.forwardToPeer({
|
|
37973
|
+
targetDeviceId: args.targetDeviceId,
|
|
37974
|
+
targetPersona: args.targetPersona,
|
|
37975
|
+
prompt: args.prompt
|
|
37976
|
+
});
|
|
37977
|
+
return {
|
|
37978
|
+
response: { type: "personaDispatch:run:ok", outcome: outcome2 }
|
|
37979
|
+
};
|
|
37980
|
+
}
|
|
37981
|
+
if (ctx?.principal.kind === "guest") {
|
|
37982
|
+
const isPublic = deps.getPersonaPublic?.(args.targetPersona) ?? false;
|
|
37983
|
+
if (!isPublic || !canAccessPersona(ctx.grants, args.targetPersona, "send")) {
|
|
37984
|
+
throw new ClawdError(
|
|
37985
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
37986
|
+
`persona not dispatchable: ${args.targetPersona}`
|
|
37987
|
+
);
|
|
37988
|
+
}
|
|
37989
|
+
const { dispatchId: dispatchId2 } = mgr.start({
|
|
37990
|
+
sourceSessionId: ctx.principal.id,
|
|
37991
|
+
targetPersona: args.targetPersona
|
|
37992
|
+
});
|
|
37993
|
+
logger?.info("dispatch.run.received.guest", {
|
|
37994
|
+
dispatchId: dispatchId2,
|
|
37995
|
+
sourcePrincipal: ctx.principal.id,
|
|
37996
|
+
targetPersona: args.targetPersona,
|
|
37997
|
+
promptLen: args.prompt.length
|
|
37998
|
+
});
|
|
37999
|
+
void spawnB({
|
|
38000
|
+
dispatchId: dispatchId2,
|
|
38001
|
+
sourceSessionId: ctx.principal.id,
|
|
38002
|
+
targetPersona: args.targetPersona,
|
|
38003
|
+
prompt: args.prompt,
|
|
38004
|
+
guestPrincipalId: ctx.principal.id,
|
|
38005
|
+
guestDisplayName: ctx.principal.displayName
|
|
38006
|
+
}).then(() => logger?.info("dispatch.spawnB.ok", { dispatchId: dispatchId2 })).catch((err) => {
|
|
38007
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
38008
|
+
logger?.warn("dispatch.spawnB.failed", { dispatchId: dispatchId2, reason });
|
|
38009
|
+
mgr.complete(dispatchId2, {
|
|
38010
|
+
kind: "failure",
|
|
38011
|
+
reason: `failed to spawn B: ${reason}`
|
|
38012
|
+
});
|
|
38013
|
+
});
|
|
38014
|
+
const outcome2 = await mgr.wait(dispatchId2);
|
|
38015
|
+
return {
|
|
38016
|
+
response: { type: "personaDispatch:run:ok", outcome: outcome2 }
|
|
38017
|
+
};
|
|
38018
|
+
}
|
|
37917
38019
|
if (!sourceSessionId) {
|
|
37918
38020
|
throw new Error(
|
|
37919
38021
|
"personaDispatch:run requires sessionId (caller must pass x-clawd-session-id header)"
|
|
@@ -37972,6 +38074,46 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
37972
38074
|
};
|
|
37973
38075
|
}
|
|
37974
38076
|
|
|
38077
|
+
// src/dispatch/peer-forward.ts
|
|
38078
|
+
function wsUrlToHttp(url) {
|
|
38079
|
+
if (url.startsWith("wss://")) return "https://" + url.slice("wss://".length);
|
|
38080
|
+
if (url.startsWith("ws://")) return "http://" + url.slice("ws://".length);
|
|
38081
|
+
return url;
|
|
38082
|
+
}
|
|
38083
|
+
async function forwardDispatchToPeer(args) {
|
|
38084
|
+
const f = args.fetchImpl ?? fetch;
|
|
38085
|
+
const base = wsUrlToHttp(args.contact.remoteUrl).replace(/\/+$/, "");
|
|
38086
|
+
const url = `${base}/rpc/personaDispatch:run`;
|
|
38087
|
+
let res;
|
|
38088
|
+
try {
|
|
38089
|
+
res = await f(url, {
|
|
38090
|
+
method: "POST",
|
|
38091
|
+
headers: {
|
|
38092
|
+
"content-type": "application/json",
|
|
38093
|
+
authorization: `Bearer ${args.contact.connectToken}`
|
|
38094
|
+
},
|
|
38095
|
+
// 注意:不带 targetDeviceId —— B 端据此判定为本地执行(B 角色)。
|
|
38096
|
+
body: JSON.stringify({ targetPersona: args.targetPersona, prompt: args.prompt })
|
|
38097
|
+
});
|
|
38098
|
+
} catch (err) {
|
|
38099
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38100
|
+
return { kind: "failure", reason: `forward to peer failed: ${msg}` };
|
|
38101
|
+
}
|
|
38102
|
+
let json;
|
|
38103
|
+
try {
|
|
38104
|
+
json = await res.json();
|
|
38105
|
+
} catch {
|
|
38106
|
+
return {
|
|
38107
|
+
kind: "failure",
|
|
38108
|
+
reason: `peer returned non-JSON response (HTTP ${res.status})`
|
|
38109
|
+
};
|
|
38110
|
+
}
|
|
38111
|
+
if (json.ok === false) {
|
|
38112
|
+
return { kind: "failure", reason: `peer rejected: ${json.error}: ${json.message}` };
|
|
38113
|
+
}
|
|
38114
|
+
return json.result.outcome;
|
|
38115
|
+
}
|
|
38116
|
+
|
|
37975
38117
|
// src/tools/codex-history.ts
|
|
37976
38118
|
var import_node_child_process5 = require("child_process");
|
|
37977
38119
|
var DEFAULT_TIMEOUT_MS = 8e3;
|
|
@@ -42472,35 +42614,6 @@ function forkSession(input) {
|
|
|
42472
42614
|
return { forkedToolSessionId, forkedFilePath };
|
|
42473
42615
|
}
|
|
42474
42616
|
|
|
42475
|
-
// src/permission/capability.ts
|
|
42476
|
-
function matchResource(grant, target) {
|
|
42477
|
-
if (grant.type === "*") return true;
|
|
42478
|
-
if (grant.type !== target.type) return false;
|
|
42479
|
-
if (grant.id === "*") return true;
|
|
42480
|
-
return grant.id === target.id;
|
|
42481
|
-
}
|
|
42482
|
-
function assertGrant(grants, resource, action) {
|
|
42483
|
-
for (const g2 of grants) {
|
|
42484
|
-
if (!matchResource(g2.resource, resource)) continue;
|
|
42485
|
-
if (g2.actions.includes(action)) return true;
|
|
42486
|
-
if (g2.actions.includes("admin")) return true;
|
|
42487
|
-
}
|
|
42488
|
-
return false;
|
|
42489
|
-
}
|
|
42490
|
-
|
|
42491
|
-
// src/permission/session-access.ts
|
|
42492
|
-
function canAccessSession(ctx, sessionId, action, deps) {
|
|
42493
|
-
if (ctx.principal.kind === "owner") return true;
|
|
42494
|
-
const file = deps.readSession(sessionId);
|
|
42495
|
-
if (!file) return true;
|
|
42496
|
-
return canAccessPersona(ctx.grants, file.ownerPersonaId, action);
|
|
42497
|
-
}
|
|
42498
|
-
function canAccessPersona(grants, personaId, action) {
|
|
42499
|
-
if (!personaId) return false;
|
|
42500
|
-
const resource = { type: "persona", id: personaId };
|
|
42501
|
-
return assertGrant(grants, resource, action);
|
|
42502
|
-
}
|
|
42503
|
-
|
|
42504
42617
|
// src/handlers/session.ts
|
|
42505
42618
|
init_protocol();
|
|
42506
42619
|
function buildSessionHandlers(deps) {
|
|
@@ -46138,6 +46251,10 @@ var METHOD_GRANT_MAP = {
|
|
|
46138
46251
|
"persona:get": CAPABILITY_SCOPED,
|
|
46139
46252
|
"persona:update": ADMIN_ANY,
|
|
46140
46253
|
"persona:delete": ADMIN_ANY,
|
|
46254
|
+
// 跨设备 dispatch:A 的 peer daemon 凭 connect token 经 /rpc 调进来;dispatcher 放行,
|
|
46255
|
+
// run handler 内做 persona.public + assertGrant(send) 双重校验(guest grants 是 wildcard,
|
|
46256
|
+
// 单 assertGrant 不够)。本地 cc 仍走 /api/rpc(EXPOSED_RPC_METHODS),不经此表。
|
|
46257
|
+
"personaDispatch:run": CAPABILITY_SCOPED,
|
|
46141
46258
|
// 2026-05-26 修 clawd people TUI 渲染 mix 串扰回归(参考 f1133ff0 原 owner 路径
|
|
46142
46259
|
// snapshot/buffer/flush 修复):之前三件套 ADMIN_ANY 把 guest 拦在 dispatcher 401,
|
|
46143
46260
|
// 导致 XtermPanel 重挂载拉 snapshot 在 guest 路径不生效,老的字节流断裂串扰回来。
|
|
@@ -47212,29 +47329,52 @@ async function startDaemon(config) {
|
|
|
47212
47329
|
sourceSessionId: args.sourceSessionId
|
|
47213
47330
|
});
|
|
47214
47331
|
const sourceFile = manager.findOwnedSession(args.sourceSessionId);
|
|
47215
|
-
if (!sourceFile) {
|
|
47332
|
+
if (!sourceFile && !args.guestPrincipalId) {
|
|
47216
47333
|
throw new Error(`personaDispatch:run source session not found: ${args.sourceSessionId}`);
|
|
47217
47334
|
}
|
|
47218
|
-
|
|
47219
|
-
|
|
47220
|
-
|
|
47221
|
-
|
|
47222
|
-
|
|
47223
|
-
|
|
47224
|
-
|
|
47335
|
+
let sourceJsonlPath = "(no transcript yet \u2014 operate from the task description alone)";
|
|
47336
|
+
if (sourceFile && sourceFile.toolSessionId) {
|
|
47337
|
+
sourceJsonlPath = import_node_path48.default.join(
|
|
47338
|
+
import_node_os20.default.homedir(),
|
|
47339
|
+
".claude",
|
|
47340
|
+
"projects",
|
|
47341
|
+
cwdToHashDir(sourceFile.cwd),
|
|
47342
|
+
`${sourceFile.toolSessionId}.jsonl`
|
|
47343
|
+
);
|
|
47344
|
+
}
|
|
47225
47345
|
logger.info("dispatch.spawnB.source-resolved", {
|
|
47226
47346
|
dispatchId: args.dispatchId,
|
|
47227
47347
|
sourceJsonlPath,
|
|
47228
|
-
hasToolSessionId: Boolean(sourceFile
|
|
47348
|
+
hasToolSessionId: Boolean(sourceFile?.toolSessionId)
|
|
47229
47349
|
});
|
|
47230
47350
|
manager.createDispatchedSession({
|
|
47231
47351
|
dispatchId: args.dispatchId,
|
|
47232
47352
|
sourceSessionId: args.sourceSessionId,
|
|
47233
47353
|
targetPersona: args.targetPersona,
|
|
47234
47354
|
prompt: args.prompt,
|
|
47235
|
-
sourceJsonlPath
|
|
47355
|
+
sourceJsonlPath,
|
|
47356
|
+
// B 角色(跨设备):非空 → guest scope + creatorPrincipalId/creatorDisplayName = A。
|
|
47357
|
+
guestPrincipalId: args.guestPrincipalId,
|
|
47358
|
+
guestDisplayName: args.guestDisplayName
|
|
47236
47359
|
});
|
|
47237
|
-
}
|
|
47360
|
+
},
|
|
47361
|
+
// A 角色:从 ContactStore 取 peer 可达 URL + connect token,转发到对端 daemon /rpc。
|
|
47362
|
+
forwardToPeer: async ({ targetDeviceId, targetPersona, prompt }) => {
|
|
47363
|
+
const contact = contactStore.get(targetDeviceId);
|
|
47364
|
+
if (!contact || !contact.remoteUrl || !contact.connectToken) {
|
|
47365
|
+
return {
|
|
47366
|
+
kind: "failure",
|
|
47367
|
+
reason: `unknown or unreachable contact: ${targetDeviceId}`
|
|
47368
|
+
};
|
|
47369
|
+
}
|
|
47370
|
+
return forwardDispatchToPeer({
|
|
47371
|
+
contact: { remoteUrl: contact.remoteUrl, connectToken: contact.connectToken },
|
|
47372
|
+
targetPersona,
|
|
47373
|
+
prompt
|
|
47374
|
+
});
|
|
47375
|
+
},
|
|
47376
|
+
// B 角色:判断 targetPersona 是否 public(跨设备授权边界,private 拒)。
|
|
47377
|
+
getPersonaPublic: (personaId) => personaRegistry.get(personaId)?.public ?? false
|
|
47238
47378
|
});
|
|
47239
47379
|
handlers = { ...handlers, ...dispatchHandlers };
|
|
47240
47380
|
const authResolver = new AuthContextResolver({
|
|
@@ -47424,7 +47564,6 @@ async function startDaemon(config) {
|
|
|
47424
47564
|
}
|
|
47425
47565
|
}
|
|
47426
47566
|
});
|
|
47427
|
-
manager.sweepEphemeralOnStartup();
|
|
47428
47567
|
await wss.start();
|
|
47429
47568
|
const url = `ws://${config.host}:${config.port}`;
|
|
47430
47569
|
let stateSnapshot = {
|
|
@@ -39576,7 +39576,9 @@ async function handlePersonaDispatchToolCall(input, ctx) {
|
|
|
39576
39576
|
},
|
|
39577
39577
|
body: JSON.stringify({
|
|
39578
39578
|
targetPersona: input.targetPersona,
|
|
39579
|
-
prompt: input.prompt
|
|
39579
|
+
prompt: input.prompt,
|
|
39580
|
+
// 跨设备:非空时透传,daemon run handler 据此走 A 角色转发分支。
|
|
39581
|
+
...input.targetDeviceId ? { targetDeviceId: input.targetDeviceId } : {}
|
|
39580
39582
|
})
|
|
39581
39583
|
});
|
|
39582
39584
|
} catch (err) {
|
|
@@ -39708,6 +39710,9 @@ async function main() {
|
|
|
39708
39710
|
targetPersona: external_exports.string().describe("persona id (e.g. persona-bug-fixer)"),
|
|
39709
39711
|
prompt: external_exports.string().describe(
|
|
39710
39712
|
"the task to perform, with the @persona/<id> token stripped out"
|
|
39713
|
+
),
|
|
39714
|
+
targetDeviceId: external_exports.string().optional().describe(
|
|
39715
|
+
"contact deviceId of the remote device when delegating cross-device (omit for a local persona)"
|
|
39711
39716
|
)
|
|
39712
39717
|
}
|
|
39713
39718
|
},
|
package/package.json
CHANGED