@clawos-dev/clawd 0.2.263 → 0.2.264
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 +228 -227
- package/package.json +4 -3
package/dist/cli.cjs
CHANGED
|
@@ -54704,7 +54704,11 @@ function buildSessionHandlers(deps) {
|
|
|
54704
54704
|
const args = SessionSendArgs.parse(frame);
|
|
54705
54705
|
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
54706
54706
|
const sender = ctx ? principalToSender(ctx.principal) : principalToSender(
|
|
54707
|
-
makeOwnerPrincipal(
|
|
54707
|
+
makeOwnerPrincipal(
|
|
54708
|
+
ownerPrincipalId,
|
|
54709
|
+
deps.getOwnerIdentity().displayName,
|
|
54710
|
+
deps.getOwnerIdentity().feishuUnionId
|
|
54711
|
+
)
|
|
54708
54712
|
);
|
|
54709
54713
|
const { broadcast } = manager.send(args, sender);
|
|
54710
54714
|
return { response: { type: "session:send", ok: true }, broadcast };
|
|
@@ -55703,14 +55707,15 @@ function buildContactHandlers(deps) {
|
|
|
55703
55707
|
contact,
|
|
55704
55708
|
exchangeToken: deps.exchangeToken
|
|
55705
55709
|
});
|
|
55710
|
+
const identity = deps.getOwnerIdentity();
|
|
55706
55711
|
return {
|
|
55707
55712
|
response: {
|
|
55708
55713
|
type: "contact:handshake:ok",
|
|
55709
55714
|
owner: {
|
|
55710
55715
|
id: deps.ownerPrincipalId,
|
|
55711
|
-
ownerId:
|
|
55712
|
-
provider:
|
|
55713
|
-
displayName:
|
|
55716
|
+
ownerId: identity.ownerId,
|
|
55717
|
+
provider: identity.provider,
|
|
55718
|
+
displayName: identity.displayName
|
|
55714
55719
|
},
|
|
55715
55720
|
grants: ctx.grants
|
|
55716
55721
|
}
|
|
@@ -55772,6 +55777,7 @@ function buildWhoamiHandler(deps) {
|
|
|
55772
55777
|
if (!ctx) {
|
|
55773
55778
|
throw new ClawdError(ERROR_CODES.INTERNAL, "whoami: missing ConnectionContext");
|
|
55774
55779
|
}
|
|
55780
|
+
const identity = deps.getOwnerIdentity();
|
|
55775
55781
|
if (ctx.tokenKind === "connect") {
|
|
55776
55782
|
return {
|
|
55777
55783
|
response: {
|
|
@@ -55781,15 +55787,15 @@ function buildWhoamiHandler(deps) {
|
|
|
55781
55787
|
};
|
|
55782
55788
|
}
|
|
55783
55789
|
const owner = {
|
|
55784
|
-
...makeOwnerPrincipal(deps.ownerPrincipalId,
|
|
55785
|
-
ownerId:
|
|
55786
|
-
provider:
|
|
55790
|
+
...makeOwnerPrincipal(deps.ownerPrincipalId, identity.displayName),
|
|
55791
|
+
ownerId: identity.ownerId,
|
|
55792
|
+
provider: identity.provider
|
|
55787
55793
|
};
|
|
55788
55794
|
let capability;
|
|
55789
55795
|
if (ctx.principal.kind === "owner") {
|
|
55790
55796
|
capability = {
|
|
55791
55797
|
id: deps.ownerPrincipalId,
|
|
55792
|
-
displayName:
|
|
55798
|
+
displayName: identity.displayName,
|
|
55793
55799
|
grants: ctx.grants,
|
|
55794
55800
|
issuedAt: 0,
|
|
55795
55801
|
usedCount: 0
|
|
@@ -58174,8 +58180,8 @@ function buildAppBuilderHandlers(deps) {
|
|
|
58174
58180
|
const ownerSender = principalToSender(
|
|
58175
58181
|
makeOwnerPrincipal(
|
|
58176
58182
|
deps.ownerPrincipalId,
|
|
58177
|
-
deps.
|
|
58178
|
-
deps.
|
|
58183
|
+
deps.getOwnerIdentity().displayName || "Owner",
|
|
58184
|
+
deps.getOwnerIdentity().feishuUnionId
|
|
58179
58185
|
)
|
|
58180
58186
|
);
|
|
58181
58187
|
const { broadcast } = sessionManager.send({ sessionId, text }, ownerSender);
|
|
@@ -58568,9 +58574,7 @@ function buildMethodHandlers(deps) {
|
|
|
58568
58574
|
broadcast: deps.broadcastToOwners,
|
|
58569
58575
|
now: () => Date.now(),
|
|
58570
58576
|
ownerPrincipalId: deps.ownerPrincipalId,
|
|
58571
|
-
|
|
58572
|
-
ownerId: deps.ownerId,
|
|
58573
|
-
ownerProvider: deps.ownerProvider,
|
|
58577
|
+
getOwnerIdentity: deps.getOwnerIdentity,
|
|
58574
58578
|
exchangeToken: deps.exchangeToken
|
|
58575
58579
|
}),
|
|
58576
58580
|
...buildContactRemoteAccessHandlers({
|
|
@@ -58584,10 +58588,8 @@ function buildMethodHandlers(deps) {
|
|
|
58584
58588
|
forwardToPeer: (a) => forwardPeerExecToPeer(a)
|
|
58585
58589
|
}),
|
|
58586
58590
|
whoami: buildWhoamiHandler({
|
|
58587
|
-
ownerDisplayName: deps.ownerDisplayName,
|
|
58588
58591
|
ownerPrincipalId: deps.ownerPrincipalId,
|
|
58589
|
-
|
|
58590
|
-
ownerProvider: deps.ownerProvider,
|
|
58592
|
+
getOwnerIdentity: deps.getOwnerIdentity,
|
|
58591
58593
|
personaStore: deps.personaStore
|
|
58592
58594
|
}),
|
|
58593
58595
|
...buildFeishuAuthHandlers(deps.feishuAuth),
|
|
@@ -58608,8 +58610,7 @@ function buildMethodHandlers(deps) {
|
|
|
58608
58610
|
devServerLifecycle: deps.devServerLifecycle,
|
|
58609
58611
|
sessionManager: deps.manager,
|
|
58610
58612
|
ownerPrincipalId: deps.ownerPrincipalId,
|
|
58611
|
-
|
|
58612
|
-
ownerFeishuUnionId: deps.ownerFeishuUnionId,
|
|
58613
|
+
getOwnerIdentity: deps.getOwnerIdentity,
|
|
58613
58614
|
getTunnelUrl: deps.getTunnelUrl,
|
|
58614
58615
|
broadcastProjectUpdated: deps.broadcastProjectUpdated,
|
|
58615
58616
|
logger: deps.logger,
|
|
@@ -59357,7 +59358,6 @@ async function startDaemon(config) {
|
|
|
59357
59358
|
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
59358
59359
|
ownerPrincipalId,
|
|
59359
59360
|
ownerDisplayName,
|
|
59360
|
-
ownerFeishuUnionId,
|
|
59361
59361
|
// persona web 分享:访客 visitor token = daemon 自签 HMAC(无 aud),独立于飞书 connect token
|
|
59362
59362
|
// 基建——secret 恒存在(auth-store backfill),故无条件注入。验签 ok → guest ctx(persona wildcard)。
|
|
59363
59363
|
verifyVisitorToken: (t) => {
|
|
@@ -59868,217 +59868,219 @@ async function startDaemon(config) {
|
|
|
59868
59868
|
const effectivePreviewPorts = Array.from(
|
|
59869
59869
|
/* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
|
|
59870
59870
|
);
|
|
59871
|
-
const
|
|
59872
|
-
|
|
59873
|
-
|
|
59874
|
-
|
|
59875
|
-
|
|
59876
|
-
|
|
59877
|
-
|
|
59878
|
-
|
|
59879
|
-
|
|
59880
|
-
|
|
59881
|
-
|
|
59882
|
-
|
|
59883
|
-
|
|
59884
|
-
|
|
59885
|
-
|
|
59886
|
-
|
|
59887
|
-
|
|
59888
|
-
|
|
59889
|
-
|
|
59890
|
-
|
|
59891
|
-
|
|
59892
|
-
|
|
59893
|
-
|
|
59894
|
-
|
|
59895
|
-
|
|
59896
|
-
|
|
59897
|
-
|
|
59898
|
-
|
|
59899
|
-
|
|
59900
|
-
|
|
59901
|
-
|
|
59902
|
-
|
|
59903
|
-
|
|
59904
|
-
// session,attachment 整套 RPC 对 persona session 都会报 "session not found"。
|
|
59905
|
-
// 详见 fix(daemon) #703:file-sharing v1 预存 wiring bug,#701 把 desktop 默认 --tunnel
|
|
59906
|
-
// 后首次让 desktop 用户用上 file-sharing 才被暴露。
|
|
59907
|
-
attachment: {
|
|
59908
|
-
groupFileStore,
|
|
59909
|
-
sessionStore: { read: (sid) => manager.findOwnedSession(sid) },
|
|
59871
|
+
const getOwnerIdentity = () => ({
|
|
59872
|
+
displayName: ownerDisplayName,
|
|
59873
|
+
ownerId,
|
|
59874
|
+
provider: ownerProvider,
|
|
59875
|
+
feishuUnionId: ownerFeishuUnionId
|
|
59876
|
+
});
|
|
59877
|
+
const handlers = {
|
|
59878
|
+
...buildMethodHandlers({
|
|
59879
|
+
// meta:methods 自省:飞书 gate getter + 最终 handler 表(下面还会 spread 进
|
|
59880
|
+
// dispatch / shift-internal 两组,只看 buildMethodHandlers 内部表会误报死方法)
|
|
59881
|
+
feishuActive: () => feishuActive,
|
|
59882
|
+
getAllHandlers: () => handlers,
|
|
59883
|
+
manager,
|
|
59884
|
+
workspace,
|
|
59885
|
+
skills,
|
|
59886
|
+
agents,
|
|
59887
|
+
// skills:list 按 tool 路由:codex → app-server skills/list;缺省 claude → SkillsScanner(同步包成 Promise)。
|
|
59888
|
+
getSkillsForTool: (tool, cwd) => tool === "codex" ? listCodexSkills(cwd) : Promise.resolve(skills.list({ cwd })),
|
|
59889
|
+
history,
|
|
59890
|
+
// history:read 按 session.tool 路由:codex → adapter.historyReader(CodexHistoryReader);缺省 claude。
|
|
59891
|
+
getHistoryReader: (tool) => getAdapter(tool).historyReader ?? history,
|
|
59892
|
+
observer,
|
|
59893
|
+
getAdapter,
|
|
59894
|
+
store,
|
|
59895
|
+
personaManager,
|
|
59896
|
+
personaRegistry,
|
|
59897
|
+
getTunnelUrl: () => currentTunnelUrl,
|
|
59898
|
+
// ready / info 帧的 mode = daemon CC spawn 模式('sdk' | 'tui')。UI 据此挂 XtermPanel +
|
|
59899
|
+
// 订阅 session:pty / session:control;业务帧名两种 mode 完全一致,UI 业务订阅代码不变
|
|
59900
|
+
mode: config.mode,
|
|
59901
|
+
// file-sharing (spec §8):ready / info 帧把 httpBaseUrl + httpToken 下发给 UI。
|
|
59902
|
+
// PR 2 阶段 httpToken 复用 owner WS token;noAuth 模式下为 null(UI 看到无 httpToken
|
|
59903
|
+
// 时禁用文件 GET/POST,保持 1.0 行为兼容)。
|
|
59910
59904
|
getHttpBaseUrl,
|
|
59911
|
-
|
|
59912
|
-
|
|
59913
|
-
//
|
|
59914
|
-
//
|
|
59915
|
-
getSessionScope
|
|
59916
|
-
//
|
|
59905
|
+
httpToken: resolvedAuthToken,
|
|
59906
|
+
// file-sharing attachment.* RPC。signUrl 用 owner token 做 HMAC secret;group RPC
|
|
59907
|
+
// 根据 sessionId 反查 scope 写盘。
|
|
59908
|
+
//
|
|
59909
|
+
// sessionStore / getSessionScope 都走 manager 的跨 scope 公开 API(findOwnedSession /
|
|
59910
|
+
// findOwnedSessionScope)—— 否则 default-only SessionStore 找不到 persona owner-mode
|
|
59911
|
+
// session,attachment 整套 RPC 对 persona session 都会报 "session not found"。
|
|
59912
|
+
// 详见 fix(daemon) #703:file-sharing v1 预存 wiring bug,#701 把 desktop 默认 --tunnel
|
|
59913
|
+
// 后首次让 desktop 用户用上 file-sharing 才被暴露。
|
|
59914
|
+
attachment: {
|
|
59915
|
+
groupFileStore,
|
|
59916
|
+
sessionStore: { read: (sid) => manager.findOwnedSession(sid) },
|
|
59917
|
+
getHttpBaseUrl,
|
|
59918
|
+
// HMAC sign secret = auth.json 唯一 token(authFile 恒非空,startDaemon 开头加载)。
|
|
59919
|
+
getSignSecret: () => authFile.token,
|
|
59920
|
+
// group RPC + sign 都用:根据 sessionId 反查 scope。owner-mode persona session 走
|
|
59921
|
+
// 'persona/<pid>/owner',default 走 'default'。
|
|
59922
|
+
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
59923
|
+
// guest path guard:candidate 必须在 personaRoot 子树或调用者自己的 user-dir 下
|
|
59924
|
+
personaRoot: import_node_path62.default.join(config.dataDir, "personas"),
|
|
59925
|
+
usersRoot
|
|
59926
|
+
},
|
|
59927
|
+
// workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
|
|
59917
59928
|
personaRoot: import_node_path62.default.join(config.dataDir, "personas"),
|
|
59918
|
-
|
|
59919
|
-
|
|
59920
|
-
|
|
59921
|
-
|
|
59922
|
-
|
|
59923
|
-
|
|
59924
|
-
|
|
59925
|
-
|
|
59926
|
-
|
|
59927
|
-
|
|
59928
|
-
|
|
59929
|
-
|
|
59930
|
-
|
|
59931
|
-
|
|
59932
|
-
|
|
59933
|
-
|
|
59934
|
-
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
59935
|
-
inboxManager,
|
|
59936
|
-
inboxStore,
|
|
59937
|
-
// 联系人列表 store(device:connect 出站 / contact:handshake 入站落同一 store)
|
|
59938
|
-
contactStore,
|
|
59939
|
-
// contact:handshake 换票回填(spec 2026-07-22):用 owner 当前 jwt 换 aud=对方设备 的票。
|
|
59940
|
-
// 现读 ownerIdentityStore(热切换后用新身份);失败 / 未登录 → null → 保持空票。
|
|
59941
|
-
exchangeToken: async (targetDeviceId) => {
|
|
59942
|
-
const record = ownerIdentityStore.read();
|
|
59943
|
-
if (!record) return null;
|
|
59944
|
-
try {
|
|
59945
|
-
const r = await centralExchange({
|
|
59946
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
59947
|
-
jwt: record.ttcToken,
|
|
59948
|
-
provider: record.identity.provider,
|
|
59949
|
-
deviceId: targetDeviceId,
|
|
59950
|
-
selfDeviceId: authFile.deviceId
|
|
59951
|
-
});
|
|
59952
|
-
return r.token;
|
|
59953
|
-
} catch (e) {
|
|
59954
|
-
logger.warn("contact:handshake exchange token failed", {
|
|
59955
|
-
targetDeviceId,
|
|
59956
|
-
err: e.message
|
|
59957
|
-
});
|
|
59958
|
-
return null;
|
|
59959
|
-
}
|
|
59960
|
-
},
|
|
59961
|
-
// inbox:sendDm 用:sessionId → session 出身(复用 attachment 同款 findOwnedSessionScope)
|
|
59962
|
-
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
59963
|
-
// contact:removed broadcast;复用 capability:tokenIssued 同款通路
|
|
59964
|
-
broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
|
|
59965
|
-
// extension runtime (v1: local-mode only). Instance owned by daemon top
|
|
59966
|
-
// level so stopAll() runs in the shutdown hook below.
|
|
59967
|
-
extensionRuntime,
|
|
59968
|
-
// extension sharing v1 — owner-side published-channels store.
|
|
59969
|
-
publishedChannelStore,
|
|
59970
|
-
bundleCache,
|
|
59971
|
-
// 飞书统一身份 Phase 1:登录 RPC handlers(auth:login:start / getIdentity / logout)
|
|
59972
|
-
// deviceId(决策 #15):getIdentity 返回给 UI 拼"我的设备标识"
|
|
59973
|
-
feishuAuth: {
|
|
59974
|
-
loginFlow,
|
|
59975
|
-
ownerIdentityStore,
|
|
59976
|
-
deviceId: authFile.deviceId,
|
|
59977
|
-
serverKeyStore,
|
|
59978
|
-
// 退出登录:登录(feishuLogin.handleLoginCallback)在进程内设置的身份状态的对称逆操作。
|
|
59979
|
-
// 核心是 feishuActive 翻 false——否则 dispatcher 飞书 gate(下方 feishuActive 判定,每帧
|
|
59980
|
-
// 现读 let binding)退登后仍放行 gated 方法。一并把归属人重置回未登录初值 + 重建 handlers
|
|
59981
|
-
// (deps 持 ownerId/displayName 值快照,whoami/出站自报身份用),与登录置位三件套对称。
|
|
59982
|
-
// 不踢连:踢连是登录热切换换身份 ctx 的需要;退登只是降级,gate 每帧现读即时生效,
|
|
59983
|
-
// 既有连接继续用,避免改动连接生命周期 / wire 契约。
|
|
59984
|
-
onLogout: () => {
|
|
59985
|
-
feishuActive = false;
|
|
59986
|
-
ownerId = "";
|
|
59987
|
-
ownerProvider = "";
|
|
59988
|
-
ownerDisplayName = loadOwnerDisplayName(config.dataDir);
|
|
59989
|
-
handlers = makeHandlers();
|
|
59990
|
-
}
|
|
59991
|
-
},
|
|
59992
|
-
// 设备目录 + 连接(决策 #15:daemon 代理中心 server)。
|
|
59993
|
-
// exchange/listDevices 包掉 owner ttcJwt(每次现读 store——热切换后立即用新身份);
|
|
59994
|
-
// dispatcher 的 FEISHU_GATED_METHODS gate 已保证未登录时这些 RPC 到不了 handler,
|
|
59995
|
-
// 这里的 FEISHU_LOGIN_REQUIRED 抛错只是防御纵深。
|
|
59996
|
-
feishuDevice: {
|
|
59997
|
-
store: contactStore,
|
|
59998
|
-
exchange: async (deviceId) => {
|
|
59929
|
+
// v2 多人 persona 隔离:handler 派生 guest user-dir 放行
|
|
59930
|
+
usersRoot,
|
|
59931
|
+
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
59932
|
+
getOwnerIdentity,
|
|
59933
|
+
// 决策 #16: whoami owner.id = 本机 deviceId(路由主键,const,热切换不变)
|
|
59934
|
+
ownerPrincipalId,
|
|
59935
|
+
personaStore,
|
|
59936
|
+
// capability platform v3 inbox: handler 接 manager (post/list/markRead),
|
|
59937
|
+
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
59938
|
+
inboxManager,
|
|
59939
|
+
inboxStore,
|
|
59940
|
+
// 联系人列表 store(device:connect 出站 / contact:handshake 入站落同一 store)
|
|
59941
|
+
contactStore,
|
|
59942
|
+
// contact:handshake 换票回填(spec 2026-07-22):用 owner 当前 jwt 换 aud=对方设备 的票。
|
|
59943
|
+
// 现读 ownerIdentityStore(热切换后用新身份);失败 / 未登录 → null → 保持空票。
|
|
59944
|
+
exchangeToken: async (targetDeviceId) => {
|
|
59999
59945
|
const record = ownerIdentityStore.read();
|
|
60000
|
-
if (!record)
|
|
60001
|
-
|
|
60002
|
-
|
|
60003
|
-
|
|
60004
|
-
|
|
60005
|
-
|
|
60006
|
-
|
|
60007
|
-
|
|
60008
|
-
|
|
60009
|
-
|
|
59946
|
+
if (!record) return null;
|
|
59947
|
+
try {
|
|
59948
|
+
const r = await centralExchange({
|
|
59949
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
59950
|
+
jwt: record.ttcToken,
|
|
59951
|
+
provider: record.identity.provider,
|
|
59952
|
+
deviceId: targetDeviceId,
|
|
59953
|
+
selfDeviceId: authFile.deviceId
|
|
59954
|
+
});
|
|
59955
|
+
return r.token;
|
|
59956
|
+
} catch (e) {
|
|
59957
|
+
logger.warn("contact:handshake exchange token failed", {
|
|
59958
|
+
targetDeviceId,
|
|
59959
|
+
err: e.message
|
|
59960
|
+
});
|
|
59961
|
+
return null;
|
|
59962
|
+
}
|
|
60010
59963
|
},
|
|
60011
|
-
|
|
60012
|
-
|
|
60013
|
-
|
|
60014
|
-
|
|
59964
|
+
// inbox:sendDm 用:sessionId → session 出身(复用 attachment 同款 findOwnedSessionScope)
|
|
59965
|
+
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
59966
|
+
// contact:removed broadcast;复用 capability:tokenIssued 同款通路
|
|
59967
|
+
broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
|
|
59968
|
+
// extension runtime (v1: local-mode only). Instance owned by daemon top
|
|
59969
|
+
// level so stopAll() runs in the shutdown hook below.
|
|
59970
|
+
extensionRuntime,
|
|
59971
|
+
// extension sharing v1 — owner-side published-channels store.
|
|
59972
|
+
publishedChannelStore,
|
|
59973
|
+
bundleCache,
|
|
59974
|
+
// 飞书统一身份 Phase 1:登录 RPC handlers(auth:login:start / getIdentity / logout)
|
|
59975
|
+
// deviceId(决策 #15):getIdentity 返回给 UI 拼"我的设备标识"
|
|
59976
|
+
feishuAuth: {
|
|
59977
|
+
loginFlow,
|
|
59978
|
+
ownerIdentityStore,
|
|
59979
|
+
deviceId: authFile.deviceId,
|
|
59980
|
+
serverKeyStore,
|
|
59981
|
+
// 退出登录:登录(feishuLogin.handleLoginCallback)在进程内设置的身份状态的对称逆操作。
|
|
59982
|
+
// 核心是 feishuActive 翻 false——否则 dispatcher 飞书 gate(下方 feishuActive 判定,每帧
|
|
59983
|
+
// 现读 let binding)退登后仍放行 gated 方法。一并把归属人重置回未登录初值 + 重建 handlers
|
|
59984
|
+
// (deps 持 ownerId/displayName 值快照,whoami/出站自报身份用),与登录置位三件套对称。
|
|
59985
|
+
// 不踢连:踢连是登录热切换换身份 ctx 的需要;退登只是降级,gate 每帧现读即时生效,
|
|
59986
|
+
// 既有连接继续用,避免改动连接生命周期 / wire 契约。
|
|
59987
|
+
onLogout: () => {
|
|
59988
|
+
feishuActive = false;
|
|
59989
|
+
ownerId = "";
|
|
59990
|
+
ownerProvider = "";
|
|
59991
|
+
ownerDisplayName = loadOwnerDisplayName(config.dataDir);
|
|
59992
|
+
}
|
|
59993
|
+
},
|
|
59994
|
+
// 设备目录 + 连接(决策 #15:daemon 代理中心 server)。
|
|
59995
|
+
// exchange/listDevices 包掉 owner ttcJwt(每次现读 store——热切换后立即用新身份);
|
|
59996
|
+
// dispatcher 的 FEISHU_GATED_METHODS gate 已保证未登录时这些 RPC 到不了 handler,
|
|
59997
|
+
// 这里的 FEISHU_LOGIN_REQUIRED 抛错只是防御纵深。
|
|
59998
|
+
feishuDevice: {
|
|
59999
|
+
store: contactStore,
|
|
60000
|
+
exchange: async (deviceId) => {
|
|
60001
|
+
const record = ownerIdentityStore.read();
|
|
60002
|
+
if (!record) {
|
|
60003
|
+
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:connect requires feishu login");
|
|
60004
|
+
}
|
|
60005
|
+
return centralExchange({
|
|
60006
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
60007
|
+
jwt: record.ttcToken,
|
|
60008
|
+
provider: record.identity.provider,
|
|
60009
|
+
deviceId,
|
|
60010
|
+
selfDeviceId: authFile.deviceId
|
|
60011
|
+
});
|
|
60012
|
+
},
|
|
60013
|
+
listDevices: async () => {
|
|
60014
|
+
const record = ownerIdentityStore.read();
|
|
60015
|
+
if (!record) {
|
|
60016
|
+
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "device:list requires feishu login");
|
|
60017
|
+
}
|
|
60018
|
+
return centralListDevices({
|
|
60019
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
60020
|
+
ttcJwt: record.ttcToken,
|
|
60021
|
+
provider: record.identity.provider
|
|
60022
|
+
});
|
|
60023
|
+
},
|
|
60024
|
+
connectRemote,
|
|
60025
|
+
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
60026
|
+
// 自动反向(spec 决策 #11):本机可达地址 = tunnel URL(公网),出站连接时
|
|
60027
|
+
// 透传让对方反向加我为联系人。无 tunnel(仅本机 ws)→ null,对方不自动反向(不造假数据)。
|
|
60028
|
+
// 决策 #16:身份不自报——本机 deviceId/ownerId/provider 由 exchange 签进 token 背书。
|
|
60029
|
+
selfUrl: () => currentTunnelUrl
|
|
60030
|
+
},
|
|
60031
|
+
// app-builder
|
|
60032
|
+
appBuilderStore,
|
|
60033
|
+
getProjectStore,
|
|
60034
|
+
devServerLifecycle: devServerSupervisor,
|
|
60035
|
+
// 单栏 refactor §5.6.2 stage fix:appBuilder:project-updated 帧通过 wsServer broadcast。
|
|
60036
|
+
// 推给所有 owner 连接(picker 可能没订阅特定 session,全局 broadcast 最稳)。
|
|
60037
|
+
// people/mirror(spec 2026-06-04-people-mode-app-builder-write-and-push):额外推给订阅该 project
|
|
60038
|
+
// 绑定 session 的 client(mirror guest 经 session:observe 订阅),让远端协同者也看到 stage / 发布进度。
|
|
60039
|
+
// 同一帧 owner 也走 project-updated 复用给 publish-progress/failed(app-builder.ts 内 broadcast 闭包):
|
|
60040
|
+
// name 在 event.project.name(project-updated)或 event.name(publish 帧)。owner 双栏会同时命中
|
|
60041
|
+
// owner-broadcast + session-broadcast,但 UI patch 幂等(同 project 覆盖)无害。
|
|
60042
|
+
broadcastProjectUpdated: (event) => {
|
|
60043
|
+
const ev = event;
|
|
60044
|
+
wsServer?.broadcastToOwners(ev);
|
|
60045
|
+
const projectName = event.project?.name ?? event.name;
|
|
60046
|
+
const boundSid = projectName ? manager.findOwnedSessionByAppBuilderProject(projectName)?.sessionId : void 0;
|
|
60047
|
+
if (boundSid) wsServer?.broadcastToSession(boundSid, ev);
|
|
60048
|
+
},
|
|
60049
|
+
// 发布上线脚手架化 (spec 2026-06-03 §5.2):
|
|
60050
|
+
// appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
|
|
60051
|
+
// dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
|
|
60052
|
+
appBuilderPersonaRoot: import_node_path62.default.join(config.dataDir, "personas", "persona-app-builder"),
|
|
60053
|
+
// 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
|
|
60054
|
+
deployKitRoot: import_node_path62.default.join(config.dataDir, "deploy-kit"),
|
|
60055
|
+
// scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
|
|
60056
|
+
resolvePersonaRoot: (personaId2) => import_node_path62.default.join(config.dataDir, "personas", personaId2),
|
|
60057
|
+
// 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
|
|
60058
|
+
// 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
|
|
60059
|
+
// 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
|
|
60060
|
+
broadcastSessionFrame: (frame, target) => {
|
|
60061
|
+
if (target === "all") {
|
|
60062
|
+
transport?.broadcastAll(frame);
|
|
60063
|
+
return;
|
|
60015
60064
|
}
|
|
60016
|
-
|
|
60017
|
-
|
|
60018
|
-
|
|
60019
|
-
|
|
60020
|
-
|
|
60065
|
+
const sid = frame.sessionId;
|
|
60066
|
+
if (!sid) return;
|
|
60067
|
+
if (target === "first-subscriber") {
|
|
60068
|
+
const handle = transport?.firstSubscriber(sid);
|
|
60069
|
+
if (handle) handle.send(frame);
|
|
60070
|
+
return;
|
|
60071
|
+
}
|
|
60072
|
+
transport?.broadcastToSession(sid, frame);
|
|
60021
60073
|
},
|
|
60022
|
-
|
|
60023
|
-
|
|
60024
|
-
//
|
|
60025
|
-
//
|
|
60026
|
-
|
|
60027
|
-
|
|
60028
|
-
|
|
60029
|
-
|
|
60030
|
-
|
|
60031
|
-
|
|
60032
|
-
devServerLifecycle: devServerSupervisor,
|
|
60033
|
-
// 单栏 refactor §5.6.2 stage fix:appBuilder:project-updated 帧通过 wsServer broadcast。
|
|
60034
|
-
// 推给所有 owner 连接(picker 可能没订阅特定 session,全局 broadcast 最稳)。
|
|
60035
|
-
// people/mirror(spec 2026-06-04-people-mode-app-builder-write-and-push):额外推给订阅该 project
|
|
60036
|
-
// 绑定 session 的 client(mirror guest 经 session:observe 订阅),让远端协同者也看到 stage / 发布进度。
|
|
60037
|
-
// 同一帧 owner 也走 project-updated 复用给 publish-progress/failed(app-builder.ts 内 broadcast 闭包):
|
|
60038
|
-
// name 在 event.project.name(project-updated)或 event.name(publish 帧)。owner 双栏会同时命中
|
|
60039
|
-
// owner-broadcast + session-broadcast,但 UI patch 幂等(同 project 覆盖)无害。
|
|
60040
|
-
broadcastProjectUpdated: (event) => {
|
|
60041
|
-
const ev = event;
|
|
60042
|
-
wsServer?.broadcastToOwners(ev);
|
|
60043
|
-
const projectName = event.project?.name ?? event.name;
|
|
60044
|
-
const boundSid = projectName ? manager.findOwnedSessionByAppBuilderProject(projectName)?.sessionId : void 0;
|
|
60045
|
-
if (boundSid) wsServer?.broadcastToSession(boundSid, ev);
|
|
60046
|
-
},
|
|
60047
|
-
// 发布上线脚手架化 (spec 2026-06-03 §5.2):
|
|
60048
|
-
// appBuilderPersonaRoot 用于拼 publish.sh 绝对路径(persona-app-builder 安装在
|
|
60049
|
-
// dataDir/personas/persona-app-builder 之下,extension-kit/scripts/publish.sh 是相对路径)。
|
|
60050
|
-
appBuilderPersonaRoot: import_node_path62.default.join(config.dataDir, "personas", "persona-app-builder"),
|
|
60051
|
-
// 共享 deploy-kit 根:scaffold/publish 脚本骨架 + 阿里云凭证单一真源。
|
|
60052
|
-
deployKitRoot: import_node_path62.default.join(config.dataDir, "deploy-kit"),
|
|
60053
|
-
// scaffold/publish 按当前 session 的 persona 解析其安装根,让每个 persona 用自己的模板/注入配置。
|
|
60054
|
-
resolvePersonaRoot: (personaId2) => import_node_path62.default.join(config.dataDir, "personas", personaId2),
|
|
60055
|
-
// 发布上线脚手架化 (spec 2026-06-03 §5.2.2):
|
|
60056
|
-
// 复用 SessionManagerDeps.broadcastFrame 同款 dispatch 逻辑 —— runner 调 manager.send
|
|
60057
|
-
// 取回 broadcast 帧后逐帧 push 到 transport,跟 manager 自身的 deps 一致。
|
|
60058
|
-
broadcastSessionFrame: (frame, target) => {
|
|
60059
|
-
if (target === "all") {
|
|
60060
|
-
transport?.broadcastAll(frame);
|
|
60061
|
-
return;
|
|
60062
|
-
}
|
|
60063
|
-
const sid = frame.sessionId;
|
|
60064
|
-
if (!sid) return;
|
|
60065
|
-
if (target === "first-subscriber") {
|
|
60066
|
-
const handle = transport?.firstSubscriber(sid);
|
|
60067
|
-
if (handle) handle.send(frame);
|
|
60068
|
-
return;
|
|
60069
|
-
}
|
|
60070
|
-
transport?.broadcastToSession(sid, frame);
|
|
60071
|
-
},
|
|
60072
|
-
logger,
|
|
60073
|
-
// shift v1:shift:list 桥接 handler 透传 ShiftStore 单例(同实例也喂给
|
|
60074
|
-
// shift-internal:* MCP loopback handler)。owner 过滤用 ownerPrincipalId(跨
|
|
60075
|
-
// daemon 稳定身份),跟我们 ShiftStore boot 时用的 provider 一致。
|
|
60076
|
-
shiftStore,
|
|
60077
|
-
shiftOwnerIdProvider: () => ownerPrincipalId,
|
|
60078
|
-
// persona web 分享:visitor:list owner RPC 读访客名单(同 exchange upsert 的实例)
|
|
60079
|
-
visitorStore
|
|
60080
|
-
});
|
|
60081
|
-
let handlers = makeHandlers();
|
|
60074
|
+
logger,
|
|
60075
|
+
// shift v1:shift:list 桥接 handler 透传 ShiftStore 单例(同实例也喂给
|
|
60076
|
+
// shift-internal:* MCP loopback handler)。owner 过滤用 ownerPrincipalId(跨
|
|
60077
|
+
// daemon 稳定身份),跟我们 ShiftStore boot 时用的 provider 一致。
|
|
60078
|
+
shiftStore,
|
|
60079
|
+
shiftOwnerIdProvider: () => ownerPrincipalId,
|
|
60080
|
+
// persona web 分享:visitor:list owner RPC 读访客名单(同 exchange upsert 的实例)
|
|
60081
|
+
visitorStore
|
|
60082
|
+
})
|
|
60083
|
+
};
|
|
60082
60084
|
const dispatchHandlers = buildPersonaDispatchHandlers({
|
|
60083
60085
|
personaDispatchManager,
|
|
60084
60086
|
store: dispatchStore,
|
|
@@ -60149,12 +60151,12 @@ async function startDaemon(config) {
|
|
|
60149
60151
|
// model 校验按 target persona 绑定 tool 选表(claude / codex 模型集不同)
|
|
60150
60152
|
getPersonaTool: (personaId2) => personaRegistry.get(personaId2)?.tool
|
|
60151
60153
|
});
|
|
60152
|
-
handlers
|
|
60154
|
+
Object.assign(handlers, dispatchHandlers);
|
|
60153
60155
|
const shiftHandlers = buildShiftInternalHandlers({
|
|
60154
60156
|
store: shiftStore,
|
|
60155
60157
|
ownerIdProvider: () => ownerPrincipalId
|
|
60156
60158
|
});
|
|
60157
|
-
handlers
|
|
60159
|
+
Object.assign(handlers, shiftHandlers);
|
|
60158
60160
|
const shiftSpawnFn = createShiftSpawnFn({ sessionManager: manager });
|
|
60159
60161
|
const shiftScheduler = createShiftScheduler({
|
|
60160
60162
|
store: shiftStore,
|
|
@@ -60233,7 +60235,6 @@ async function startDaemon(config) {
|
|
|
60233
60235
|
ownerProvider = result.identity.provider;
|
|
60234
60236
|
ownerDisplayName = result.identity.displayName;
|
|
60235
60237
|
ownerFeishuUnionId = result.identity.unionId;
|
|
60236
|
-
handlers = makeHandlers();
|
|
60237
60238
|
wsServer?.broadcastToOwners({ type: "auth:login:done", identity: result.identity });
|
|
60238
60239
|
setImmediate(() => wsServer?.closeAllClients());
|
|
60239
60240
|
void reportDevice();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawos-dev/clawd",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.264",
|
|
4
4
|
"description": "Standalone clawd daemon — Claude Code (and future Codex) session server over WebSocket",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,13 +17,14 @@
|
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "pnpm -F @clawos/clawd-ui build:assets && tsup && node scripts/copy-defaults.mjs",
|
|
20
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
20
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p scripts/tsconfig.json --noEmit",
|
|
21
21
|
"dev": "tsx src/cli.ts",
|
|
22
22
|
"dev:watch": "tsx watch --clear-screen=false src/cli.ts",
|
|
23
23
|
"test": "vitest run",
|
|
24
24
|
"test:watch": "vitest",
|
|
25
25
|
"test:changed": "vitest run --changed origin/release",
|
|
26
|
-
"depcruise": "depcruise --config .dependency-cruiser.cjs src"
|
|
26
|
+
"depcruise": "depcruise --config .dependency-cruiser.cjs src",
|
|
27
|
+
"rpc-live": "tsup && node scripts/copy-defaults.mjs && tsx scripts/manual-rpc-live.ts"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@clawos/clawd-protocol": "workspace:*",
|