@clawos-dev/clawd 0.2.112-beta.220.a78f1c0 → 0.2.112-beta.222.19584a4
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 +1157 -1264
- package/package.json +1 -1
- package/dist/persona-defaults/persona-app-builder/CLAUDE.md +0 -168
- package/dist/persona-defaults/persona-app-builder/extension-kit/README.md +0 -96
- package/dist/persona-defaults/persona-app-builder/extension-kit/config.env +0 -20
- package/dist/persona-defaults/persona-app-builder/extension-kit/contract/bootstrap +0 -22
- package/dist/persona-defaults/persona-app-builder/extension-kit/contract/s.yaml.tmpl +0 -54
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/.env.example +0 -3
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/.fcignore +0 -7
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/nest-cli.json +0 -8
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/package-lock.json +0 -4531
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/package.json +0 -28
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +0 -10
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/main.ts +0 -49
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.controller.ts +0 -27
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.module.ts +0 -9
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.service.ts +0 -38
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/polyfill.ts +0 -8
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/tsconfig.json +0 -14
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/index.html +0 -12
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/package-lock.json +0 -1680
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/package.json +0 -18
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/src/App.jsx +0 -161
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/src/main.jsx +0 -5
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/vite.config.js +0 -30
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/new-extension.sh +0 -49
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/publish.sh +0 -78
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/remove-extension.sh +0 -57
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/verify.sh +0 -20
package/dist/cli.cjs
CHANGED
|
@@ -132,15 +132,11 @@ var init_methods = __esm({
|
|
|
132
132
|
"attachment.groupRemove",
|
|
133
133
|
"attachment.groupList",
|
|
134
134
|
// ---- capability:* (capability platform 鉴权底座) ----
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
// 老板手动清旧 cap);capability:issue / capability:bindPeer 已下线。
|
|
135
|
+
// 飞书统一身份 Phase 2 (2026-06-01, spec 决策 #12): personal token / 邀请码全链路删除——
|
|
136
|
+
// hub 已完整替代(中心 server 签发 per-hub connect token)。capability:list / capability:delete
|
|
137
|
+
// 保留:调试 + whoami 反查 + 撤销级联 + 清旧 per-peer cap。personal-cap:get 已下线。
|
|
139
138
|
"capability:list",
|
|
140
139
|
"capability:delete",
|
|
141
|
-
// global personal token (2026-05-25): UI invite/accept dialog 挂载即调,拿 personal
|
|
142
|
-
// token + capability + shareBaseUrl 用于拼 inviteUrl / autoAttach 反向推。owner-only。
|
|
143
|
-
"personal-cap:get",
|
|
144
140
|
// ---- inbox:* (capability platform Phase 3 跨用户通知 + Phase 4 DM) ----
|
|
145
141
|
// owner 接 guest 的 cross-principal 消息事件 + DM 双向私聊.
|
|
146
142
|
// inbox:list / markRead: admin-only (Phase 3); inbox:postMessage: DM 自带能力,
|
|
@@ -148,14 +144,12 @@ var init_methods = __esm({
|
|
|
148
144
|
"inbox:list",
|
|
149
145
|
"inbox:markRead",
|
|
150
146
|
"inbox:postMessage",
|
|
151
|
-
// ----
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"received-capability:remove",
|
|
158
|
-
"received-capability:autoAttach",
|
|
147
|
+
// ---- contact:* (联系人列表) ----
|
|
148
|
+
// 飞书统一身份 Phase 3 (决策 #14): received-capability:* 正名为 contact:*——Phase 2 后
|
|
149
|
+
// 联系人身份由中心 server introspect 背书,不再是"对方颁发的 capability"。
|
|
150
|
+
// 写入路径:hub:connect 落 store + 自动反向(决策 #11);list / remove 作联系人列表读删。
|
|
151
|
+
"contact:list",
|
|
152
|
+
"contact:remove",
|
|
159
153
|
// ---- whoami (v2 capability platform) ----
|
|
160
154
|
// 任何 authed connection 都能调;返回 owner 显示名 + 当前 capability wire 形态 +
|
|
161
155
|
// grants 对应的 persona 元数据 (id + displayName)。UI 端 AddRemotePersonaDialog
|
|
@@ -181,16 +175,17 @@ var init_methods = __esm({
|
|
|
181
175
|
// 无 subscription 持久化:guest 端身份完全来自文件系统 + reconciled publishedExtensions.
|
|
182
176
|
"extension.install-from-channel",
|
|
183
177
|
"extension.update-from-channel",
|
|
184
|
-
// ----
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
"
|
|
178
|
+
// ---- auth:* 飞书统一身份 Phase 1 (spec 2026-06-01-feishu-identity-design) ----
|
|
179
|
+
// owner-only:daemon loopback 登录流程(auth:login:start 起 state → HTTP /auth/callback
|
|
180
|
+
// 回调落盘 → auth:login:done 事件)。中央字面量见 protocol/feishu-auth.ts FEISHU_AUTH_METHODS。
|
|
181
|
+
"auth:login:start",
|
|
182
|
+
"auth:getIdentity",
|
|
183
|
+
"auth:logout",
|
|
184
|
+
// ---- hub:* 飞书统一身份 Phase 2 (spec §10.2) ----
|
|
185
|
+
// owner-only:公共 hub 目录 + per-hub connect token 连接(daemon 代理中心 server)。
|
|
186
|
+
// 中央字面量见 protocol/feishu-auth.ts HUB_METHODS。
|
|
187
|
+
"hub:list",
|
|
188
|
+
"hub:connect"
|
|
194
189
|
];
|
|
195
190
|
}
|
|
196
191
|
});
|
|
@@ -252,7 +247,11 @@ var init_errors = __esm({
|
|
|
252
247
|
INTERNAL: "INTERNAL",
|
|
253
248
|
UNAUTHORIZED: "UNAUTHORIZED",
|
|
254
249
|
FORBIDDEN: "FORBIDDEN",
|
|
255
|
-
INVALID_PARAM: "INVALID_PARAM"
|
|
250
|
+
INVALID_PARAM: "INVALID_PARAM",
|
|
251
|
+
// 飞书统一身份(spec 2026-06-01 决策 #9):People/远程体系强制飞书登录。
|
|
252
|
+
// daemon 进程身份未激活为飞书 unionId 时,FEISHU_GATED_METHODS 内的 RPC 一律返回此错误;
|
|
253
|
+
// UI 据此显示登录引导(区别于 UNAUTHORIZED 的越权语义)。
|
|
254
|
+
FEISHU_LOGIN_REQUIRED: "FEISHU_LOGIN_REQUIRED"
|
|
256
255
|
};
|
|
257
256
|
ClawdError = class extends Error {
|
|
258
257
|
constructor(code, message) {
|
|
@@ -4532,7 +4531,7 @@ var init_persona_schemas = __esm({
|
|
|
4532
4531
|
});
|
|
4533
4532
|
|
|
4534
4533
|
// ../protocol/src/schemas.ts
|
|
4535
|
-
var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema,
|
|
4534
|
+
var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema;
|
|
4536
4535
|
var init_schemas = __esm({
|
|
4537
4536
|
"../protocol/src/schemas.ts"() {
|
|
4538
4537
|
"use strict";
|
|
@@ -4629,10 +4628,6 @@ var init_schemas = __esm({
|
|
|
4629
4628
|
// owner-mode persona session 身份标识;UI 用它在 SessionList 过滤 + jump,
|
|
4630
4629
|
// daemon 用它做 idempotent dedupe + spawn 时派生 ctx.personaMode='owner'
|
|
4631
4630
|
ownerPersonaId: external_exports.string().min(1).optional(),
|
|
4632
|
-
// app-builder Project 反向索引(spec: superpowers/specs/2026-06-01-app-builder-project-picker-design.md)。
|
|
4633
|
-
// 当此 session 绑定到某个 build 中的 project 时填写;name 与 projects/<name>/ 子目录同名,
|
|
4634
|
-
// 受 projectNameSchema regex 约束。daemon session:resume 时校验该目录存在,不存在则拒。
|
|
4635
|
-
appBuilderProject: external_exports.string().regex(/^[a-z][a-z0-9-]{0,39}$/).optional(),
|
|
4636
4631
|
// listener-mode sub-session 的原始 chatId(owner-mode 不写;listener-scope 强制写入)。
|
|
4637
4632
|
// 派生 sessionId 是单向 hash `${personaId}-${tokenHash12}-${chatHash8}`,必须保留原始 chatId
|
|
4638
4633
|
// 才能让 alice 重连同一 sub-session(持久化在 alice localStorage 之外,由 daemon push 同步)。
|
|
@@ -4905,12 +4900,7 @@ var init_schemas = __esm({
|
|
|
4905
4900
|
forkedFromSessionId: external_exports.string().min(1).optional(),
|
|
4906
4901
|
// owner-mode persona session:传此字段时 daemon 自行派生 cwd 和启动参数,
|
|
4907
4902
|
// 且按 ownerPersonaId 做 idempotent dedupe(每 persona 永远只有一个 owner session)
|
|
4908
|
-
ownerPersonaId: external_exports.string().min(1).optional()
|
|
4909
|
-
// app-builder Project 绑定(spec 2026-06-01-app-builder-project-picker-design)。
|
|
4910
|
-
// UI picker 上点 idle project / 新建 project 时传入;daemon 透传写入 SessionFile.appBuilderProject。
|
|
4911
|
-
// 跟 ownerPersonaId 同层但不互斥(典型场景两者都传:ownerPersonaId='persona-app-builder' +
|
|
4912
|
-
// appBuilderProject='<project-name>')。
|
|
4913
|
-
appBuilderProject: external_exports.string().regex(/^[a-z][a-z0-9-]{0,39}$/).optional()
|
|
4903
|
+
ownerPersonaId: external_exports.string().min(1).optional()
|
|
4914
4904
|
}).refine((args) => args.cwd != null || args.ownerPersonaId != null, {
|
|
4915
4905
|
message: "cwd \u4E0E ownerPersonaId \u81F3\u5C11\u4F20\u4E00\u4E2A"
|
|
4916
4906
|
});
|
|
@@ -4924,11 +4914,7 @@ var init_schemas = __esm({
|
|
|
4924
4914
|
effort: external_exports.string().optional(),
|
|
4925
4915
|
cwd: external_exports.string().optional(),
|
|
4926
4916
|
// 用户在 Edit modal 选择的 icon 标识;UI 端做 enum 兜底
|
|
4927
|
-
iconKey: external_exports.string().optional()
|
|
4928
|
-
// app-builder picker 在当前 session 内切 project 时写入(spec 2026-06-01-app-builder-project-picker-design)。
|
|
4929
|
-
// daemon session:update handler 检测此字段变化时停旧 project dev server + 起新 project dev server。
|
|
4930
|
-
// 设为空字符串 '' 表示解绑(picker 不暴露解绑入口,但 schema 允许)。
|
|
4931
|
-
appBuilderProject: external_exports.string().regex(/^[a-z][a-z0-9-]{0,39}$/).or(external_exports.literal("")).optional()
|
|
4917
|
+
iconKey: external_exports.string().optional()
|
|
4932
4918
|
})
|
|
4933
4919
|
});
|
|
4934
4920
|
SessionSendArgs = external_exports.object({
|
|
@@ -5125,6 +5111,12 @@ var init_schemas = __esm({
|
|
|
5125
5111
|
* 后调 capabilityManager.recordPeerHello(capId, ownerPrincipalId, displayName)
|
|
5126
5112
|
* 把 cap.peerOwnerId / firstUsedByPeerAt 字段回写,让 owner 端 UI 在 People
|
|
5127
5113
|
* tab 顶层 PeerOwner 视图 + Personas tab 二级分组拿到对端身份。
|
|
5114
|
+
*
|
|
5115
|
+
* 飞书统一身份 Phase 1 (2026-06-01):取值升级为飞书 union_id(owner 已飞书登录时;
|
|
5116
|
+
* 来自 whoami.owner.id → daemon ownerPrincipalId → owner-identity.json unionId)。
|
|
5117
|
+
* 未登录 daemon 仍为 auth.json 的 owner-<uuid>。daemon 端不验证该身份真实性
|
|
5118
|
+
* (Phase 1 信任模型,见 spec §9)。Phase 2 将以 connect token 替代(introspect
|
|
5119
|
+
* 返回身份,删除自报字段,见 spec §10)。
|
|
5128
5120
|
*/
|
|
5129
5121
|
selfPrincipalId: external_exports.string().min(1).optional(),
|
|
5130
5122
|
/**
|
|
@@ -5132,18 +5124,23 @@ var init_schemas = __esm({
|
|
|
5132
5124
|
* 显示名写入 cap.peerOwnerDisplayName(cap.displayName 本来就是 owner 颁时填
|
|
5133
5125
|
* 的"颁给谁/用途",hello 收到的 displayName 是辅助)。
|
|
5134
5126
|
*/
|
|
5135
|
-
selfDisplayName: external_exports.string().optional()
|
|
5127
|
+
selfDisplayName: external_exports.string().optional(),
|
|
5128
|
+
/**
|
|
5129
|
+
* 飞书统一身份 Phase 2(spec 决策 #11,自动反向建立联系人):guest 自报自己可达的
|
|
5130
|
+
* ws 地址(tunnel 地址优先)。hub introspect 验票成功后,若 guest 带 selfUrl,hub 自动
|
|
5131
|
+
* 把 guest 落入本机 ContactStore(remoteUrl = selfUrl)+ 广播 contact:added,
|
|
5132
|
+
* 让 hub 的 People 立即出现该 guest——双向互为联系人,身份由 introspect 背书不可伪造。
|
|
5133
|
+
*
|
|
5134
|
+
* 缺省(A 无公网地址)→ hub 反向连不上 A,**不**自动建 cap;双向 IM 仅在 A 在线连接
|
|
5135
|
+
* 期间通过该连接收发(spec 决策 #11 限制)。不填假地址(不造假数据)。
|
|
5136
|
+
*/
|
|
5137
|
+
selfUrl: external_exports.string().min(1).optional()
|
|
5136
5138
|
});
|
|
5137
5139
|
AuthOkFrameSchema = external_exports.object({
|
|
5138
5140
|
type: external_exports.literal("auth:ok"),
|
|
5139
5141
|
version: external_exports.string().min(1).optional(),
|
|
5140
5142
|
protocolVersion: external_exports.number().int().nonnegative().optional()
|
|
5141
5143
|
});
|
|
5142
|
-
TunnelReadyEventSchema = external_exports.object({
|
|
5143
|
-
type: external_exports.literal("tunnel:ready"),
|
|
5144
|
-
url: external_exports.string().min(1),
|
|
5145
|
-
subdomain: external_exports.string().min(1)
|
|
5146
|
-
});
|
|
5147
5144
|
TunnelExitedEventSchema = external_exports.object({
|
|
5148
5145
|
type: external_exports.literal("tunnel:exited"),
|
|
5149
5146
|
code: external_exports.number().int().nullable(),
|
|
@@ -5185,44 +5182,6 @@ var init_schemas = __esm({
|
|
|
5185
5182
|
/** file-sharing HTTP 路由的 Authorization: Bearer token;与 WS token 解耦,HTTP 401 仅触发 ReAuth 不强踢 WS(spec §11 第 4 条) */
|
|
5186
5183
|
httpToken: external_exports.string().optional()
|
|
5187
5184
|
});
|
|
5188
|
-
PROJECT_PORT_MIN = 6173;
|
|
5189
|
-
PROJECT_PORT_MAX = 6182;
|
|
5190
|
-
projectNameRegex = /^[a-z][a-z0-9-]{0,39}$/;
|
|
5191
|
-
DEFAULT_DEV_COMMAND = "cd server && pnpm dev";
|
|
5192
|
-
ProjectMetadataSchema = external_exports.object({
|
|
5193
|
-
name: external_exports.string().regex(projectNameRegex, {
|
|
5194
|
-
message: "kebab-case, \u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u53EA\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD / \u6570\u5B57 / -\uFF0C\u6700\u957F 40 \u5B57\u7B26"
|
|
5195
|
-
}),
|
|
5196
|
-
port: external_exports.number().int().min(PROJECT_PORT_MIN).max(PROJECT_PORT_MAX),
|
|
5197
|
-
createdAt: external_exports.string().datetime(),
|
|
5198
|
-
/** dev server 启动命令(shell 字符串);占位符 $CLAWD_PREVIEW_PORT 替换 + env 注入 */
|
|
5199
|
-
devCommand: external_exports.string().min(1)
|
|
5200
|
-
});
|
|
5201
|
-
ProjectWithStatusSchema = ProjectMetadataSchema.extend({
|
|
5202
|
-
isRunning: external_exports.boolean(),
|
|
5203
|
-
boundSessionId: external_exports.string().nullable()
|
|
5204
|
-
});
|
|
5205
|
-
ListProjectsArgsSchema = external_exports.object({}).strict();
|
|
5206
|
-
ListProjectsResultSchema = external_exports.object({
|
|
5207
|
-
projects: external_exports.array(ProjectWithStatusSchema)
|
|
5208
|
-
}).strict();
|
|
5209
|
-
CreateProjectArgsSchema = external_exports.object({
|
|
5210
|
-
name: external_exports.string()
|
|
5211
|
-
}).strict();
|
|
5212
|
-
CreateProjectResultSchema = external_exports.object({
|
|
5213
|
-
project: ProjectMetadataSchema
|
|
5214
|
-
}).strict();
|
|
5215
|
-
DeleteProjectArgsSchema = external_exports.object({
|
|
5216
|
-
name: external_exports.string()
|
|
5217
|
-
}).strict();
|
|
5218
|
-
DeleteProjectResultSchema = external_exports.object({}).strict();
|
|
5219
|
-
UpdateProjectPortArgsSchema = external_exports.object({
|
|
5220
|
-
name: external_exports.string(),
|
|
5221
|
-
newPort: external_exports.number().int()
|
|
5222
|
-
}).strict();
|
|
5223
|
-
UpdateProjectPortResultSchema = external_exports.object({
|
|
5224
|
-
project: ProjectMetadataSchema
|
|
5225
|
-
}).strict();
|
|
5226
5185
|
}
|
|
5227
5186
|
});
|
|
5228
5187
|
|
|
@@ -5265,7 +5224,7 @@ function stripSecretHash(cap) {
|
|
|
5265
5224
|
const { secretHash: _hash, ...wire } = cap;
|
|
5266
5225
|
return wire;
|
|
5267
5226
|
}
|
|
5268
|
-
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS
|
|
5227
|
+
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS;
|
|
5269
5228
|
var init_capability = __esm({
|
|
5270
5229
|
"../protocol/src/capability.ts"() {
|
|
5271
5230
|
"use strict";
|
|
@@ -5333,13 +5292,6 @@ var init_capability = __esm({
|
|
|
5333
5292
|
actions: Object.freeze(["read", "send"])
|
|
5334
5293
|
})
|
|
5335
5294
|
]);
|
|
5336
|
-
PersonalCapGetResponseSchema = external_exports.object({
|
|
5337
|
-
type: external_exports.literal("personal-cap:get:ok"),
|
|
5338
|
-
token: external_exports.string().min(1),
|
|
5339
|
-
capability: CapabilityWireSchema,
|
|
5340
|
-
/** ws/wss base URL,UI deriveInviteBase 用来拼 inviteUrl(tunnel 优先 / 否则本机 ws) */
|
|
5341
|
-
shareBaseUrl: external_exports.string().min(1)
|
|
5342
|
-
}).strict();
|
|
5343
5295
|
}
|
|
5344
5296
|
});
|
|
5345
5297
|
|
|
@@ -5374,60 +5326,46 @@ var init_inbox = __esm({
|
|
|
5374
5326
|
}
|
|
5375
5327
|
});
|
|
5376
5328
|
|
|
5377
|
-
// ../protocol/src/
|
|
5378
|
-
var
|
|
5379
|
-
var
|
|
5380
|
-
"../protocol/src/
|
|
5329
|
+
// ../protocol/src/contact.ts
|
|
5330
|
+
var ContactSchema, ContactWireSchema, ContactRemoveArgsSchema, ContactRemoveOkSchema, ContactListOkSchema, ContactAddedFrameSchema, ContactRemovedFrameSchema;
|
|
5331
|
+
var init_contact = __esm({
|
|
5332
|
+
"../protocol/src/contact.ts"() {
|
|
5381
5333
|
"use strict";
|
|
5382
5334
|
init_zod();
|
|
5383
5335
|
init_capability();
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5336
|
+
ContactSchema = external_exports.object({
|
|
5337
|
+
/** 联系人(对方 daemon owner)的飞书 union_id */
|
|
5338
|
+
principalId: external_exports.string().min(1),
|
|
5339
|
+
/** 联系人飞书姓名 */
|
|
5340
|
+
displayName: external_exports.string(),
|
|
5387
5341
|
remoteUrl: external_exports.string().min(1),
|
|
5388
|
-
|
|
5389
|
-
|
|
5342
|
+
// Phase 2 自动反向建立联系人(spec 决策 #11):hub introspect 验票成功后自动落 A 为
|
|
5343
|
+
// 联系人,但 hub 不持有 A 颁发的 token(A 从未给 hub 换票)→ connectToken 为空串。
|
|
5344
|
+
// hub:connect 出站路径仍存中心 server 签发的真实 connect token(断线重连复用)。
|
|
5345
|
+
// 不用 .min(1):自动反向路径合法地存空串,禁止填假 token 占位(不造假数据)。
|
|
5346
|
+
connectToken: external_exports.string(),
|
|
5390
5347
|
grants: external_exports.array(GrantSchema),
|
|
5391
|
-
|
|
5392
|
-
}).strict();
|
|
5393
|
-
ReceivedCapabilityWireSchema = ReceivedCapabilitySchema;
|
|
5394
|
-
ReceivedCapabilityAddArgsSchema = external_exports.object({
|
|
5395
|
-
remoteUrl: external_exports.string().min(1),
|
|
5396
|
-
token: external_exports.string().min(1)
|
|
5397
|
-
}).strict();
|
|
5398
|
-
ReceivedCapabilityAddOkSchema = external_exports.object({
|
|
5399
|
-
type: external_exports.literal("received-capability:add:ok"),
|
|
5400
|
-
receivedCap: ReceivedCapabilityWireSchema
|
|
5401
|
-
}).strict();
|
|
5402
|
-
ReceivedCapabilityRemoveArgsSchema = external_exports.object({
|
|
5403
|
-
ownerPrincipalId: external_exports.string().min(1)
|
|
5348
|
+
addedAt: external_exports.number().int().nonnegative()
|
|
5404
5349
|
}).strict();
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5350
|
+
ContactWireSchema = ContactSchema;
|
|
5351
|
+
ContactRemoveArgsSchema = external_exports.object({
|
|
5352
|
+
principalId: external_exports.string().min(1)
|
|
5408
5353
|
}).strict();
|
|
5409
|
-
|
|
5410
|
-
type: external_exports.literal("
|
|
5411
|
-
|
|
5354
|
+
ContactRemoveOkSchema = external_exports.object({
|
|
5355
|
+
type: external_exports.literal("contact:remove:ok"),
|
|
5356
|
+
principalId: external_exports.string().min(1)
|
|
5412
5357
|
}).strict();
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
remoteUrl: external_exports.string().min(1),
|
|
5417
|
-
capabilityId: external_exports.string().min(1),
|
|
5418
|
-
token: external_exports.string().min(1),
|
|
5419
|
-
grants: external_exports.array(GrantSchema)
|
|
5420
|
-
}).strict();
|
|
5421
|
-
ReceivedCapabilityAutoAttachOkSchema = external_exports.object({
|
|
5422
|
-
type: external_exports.literal("received-capability:autoAttach:ok")
|
|
5358
|
+
ContactListOkSchema = external_exports.object({
|
|
5359
|
+
type: external_exports.literal("contact:list:ok"),
|
|
5360
|
+
contacts: external_exports.array(ContactWireSchema)
|
|
5423
5361
|
}).strict();
|
|
5424
|
-
|
|
5425
|
-
type: external_exports.literal("
|
|
5426
|
-
|
|
5362
|
+
ContactAddedFrameSchema = external_exports.object({
|
|
5363
|
+
type: external_exports.literal("contact:added"),
|
|
5364
|
+
contact: ContactWireSchema
|
|
5427
5365
|
}).strict();
|
|
5428
|
-
|
|
5429
|
-
type: external_exports.literal("
|
|
5430
|
-
|
|
5366
|
+
ContactRemovedFrameSchema = external_exports.object({
|
|
5367
|
+
type: external_exports.literal("contact:removed"),
|
|
5368
|
+
principalId: external_exports.string().min(1)
|
|
5431
5369
|
}).strict();
|
|
5432
5370
|
}
|
|
5433
5371
|
});
|
|
@@ -5631,6 +5569,94 @@ var init_extension = __esm({
|
|
|
5631
5569
|
}
|
|
5632
5570
|
});
|
|
5633
5571
|
|
|
5572
|
+
// ../protocol/src/feishu-auth.ts
|
|
5573
|
+
var FEISHU_GATED_METHODS, HubEntrySchema, HubListResponseSchema, HubConnectArgsSchema, HubConnectResponseSchema, FeishuIdentitySchema, AuthLoginStartResponseSchema, AuthGetIdentityResponseSchema, AuthLogoutResponseSchema, AuthLoginDoneEventSchema, AuthLoginFailedEventSchema;
|
|
5574
|
+
var init_feishu_auth = __esm({
|
|
5575
|
+
"../protocol/src/feishu-auth.ts"() {
|
|
5576
|
+
"use strict";
|
|
5577
|
+
init_zod();
|
|
5578
|
+
FEISHU_GATED_METHODS = [
|
|
5579
|
+
// capability(调试 / 撤销,仍属远程身份体系)
|
|
5580
|
+
"capability:list",
|
|
5581
|
+
"capability:delete",
|
|
5582
|
+
// 联系人列表(hub:connect 落同一 store;list 读 / remove 删)
|
|
5583
|
+
"contact:list",
|
|
5584
|
+
"contact:remove",
|
|
5585
|
+
// DM / 跨用户通知
|
|
5586
|
+
"inbox:list",
|
|
5587
|
+
"inbox:markRead",
|
|
5588
|
+
"inbox:postMessage",
|
|
5589
|
+
// mirror peer sessions(远端会话镜像)
|
|
5590
|
+
"peerSession:upsert",
|
|
5591
|
+
"peerSession:remove",
|
|
5592
|
+
// Phase 2: hub 目录 + 连接(中心 server 代理,需要 owner 的 ttcJwt)
|
|
5593
|
+
"hub:list",
|
|
5594
|
+
"hub:connect"
|
|
5595
|
+
];
|
|
5596
|
+
HubEntrySchema = external_exports.object({
|
|
5597
|
+
/** hub owner 的飞书 union_id */
|
|
5598
|
+
hubId: external_exports.string().min(1),
|
|
5599
|
+
/** 显示名 */
|
|
5600
|
+
name: external_exports.string().min(1),
|
|
5601
|
+
/** ws/tunnel 地址 */
|
|
5602
|
+
url: external_exports.string().min(1)
|
|
5603
|
+
});
|
|
5604
|
+
HubListResponseSchema = external_exports.object({
|
|
5605
|
+
type: external_exports.literal("hub:list:ok"),
|
|
5606
|
+
hubs: external_exports.array(HubEntrySchema)
|
|
5607
|
+
});
|
|
5608
|
+
HubConnectArgsSchema = external_exports.object({
|
|
5609
|
+
/** 目标 hub 的 hubId(= hub owner unionId) */
|
|
5610
|
+
hubId: external_exports.string().min(1),
|
|
5611
|
+
/**
|
|
5612
|
+
* 目标 hub 的 ws/tunnel 地址(决策 #13 统一注册表后改为可选):
|
|
5613
|
+
* - 公开目录点击:带 url(hub:list 已下发,省一次 server 查询)
|
|
5614
|
+
* - 私有 hub 仅凭 hubId 连接:缺省 url → daemon exchange 时由中心 server 下发该 hub 上报的 url
|
|
5615
|
+
*/
|
|
5616
|
+
url: external_exports.string().min(1).optional(),
|
|
5617
|
+
/** 显示名(公共 hub 来自目录;私有 hub 缺省,连上后用 whoami 回填) */
|
|
5618
|
+
name: external_exports.string().optional()
|
|
5619
|
+
});
|
|
5620
|
+
HubConnectResponseSchema = external_exports.object({
|
|
5621
|
+
type: external_exports.literal("hub:connect:ok"),
|
|
5622
|
+
hubId: external_exports.string(),
|
|
5623
|
+
name: external_exports.string(),
|
|
5624
|
+
url: external_exports.string()
|
|
5625
|
+
});
|
|
5626
|
+
FeishuIdentitySchema = external_exports.object({
|
|
5627
|
+
/** 飞书 union_id(租户维度,跨应用稳定)。TTC user_info 接口 data.union_id */
|
|
5628
|
+
unionId: external_exports.string().min(1),
|
|
5629
|
+
/** 飞书姓名。TTC user_info 接口 data.name */
|
|
5630
|
+
displayName: external_exports.string().min(1),
|
|
5631
|
+
/** TTC user_info 接口 data.avatar_url,可缺省 */
|
|
5632
|
+
avatarUrl: external_exports.string().optional()
|
|
5633
|
+
});
|
|
5634
|
+
AuthLoginStartResponseSchema = external_exports.object({
|
|
5635
|
+
type: external_exports.literal("auth:login:start:ok"),
|
|
5636
|
+
/** 完整 TTC 授权页 URL(含 callback_url + state + auto=1),UI 直接 window.open */
|
|
5637
|
+
authUrl: external_exports.string().min(1),
|
|
5638
|
+
/** 防 CSRF 的一次性 nonce;回调时 daemon 校验 */
|
|
5639
|
+
state: external_exports.string().min(1)
|
|
5640
|
+
});
|
|
5641
|
+
AuthGetIdentityResponseSchema = external_exports.object({
|
|
5642
|
+
type: external_exports.literal("auth:getIdentity:ok"),
|
|
5643
|
+
/** null = 未登录 */
|
|
5644
|
+
identity: FeishuIdentitySchema.nullable()
|
|
5645
|
+
});
|
|
5646
|
+
AuthLogoutResponseSchema = external_exports.object({
|
|
5647
|
+
type: external_exports.literal("auth:logout:ok")
|
|
5648
|
+
});
|
|
5649
|
+
AuthLoginDoneEventSchema = external_exports.object({
|
|
5650
|
+
type: external_exports.literal("auth:login:done"),
|
|
5651
|
+
identity: FeishuIdentitySchema
|
|
5652
|
+
});
|
|
5653
|
+
AuthLoginFailedEventSchema = external_exports.object({
|
|
5654
|
+
type: external_exports.literal("auth:login:failed"),
|
|
5655
|
+
reason: external_exports.string()
|
|
5656
|
+
});
|
|
5657
|
+
}
|
|
5658
|
+
});
|
|
5659
|
+
|
|
5634
5660
|
// ../protocol/src/runtime.ts
|
|
5635
5661
|
var init_runtime = __esm({
|
|
5636
5662
|
"../protocol/src/runtime.ts"() {
|
|
@@ -5646,8 +5672,9 @@ var init_runtime = __esm({
|
|
|
5646
5672
|
init_principal();
|
|
5647
5673
|
init_capability();
|
|
5648
5674
|
init_inbox();
|
|
5649
|
-
|
|
5675
|
+
init_contact();
|
|
5650
5676
|
init_extension();
|
|
5677
|
+
init_feishu_auth();
|
|
5651
5678
|
}
|
|
5652
5679
|
});
|
|
5653
5680
|
|
|
@@ -6834,8 +6861,8 @@ var require_atomic_sleep = __commonJS({
|
|
|
6834
6861
|
var require_sonic_boom = __commonJS({
|
|
6835
6862
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
6836
6863
|
"use strict";
|
|
6837
|
-
var
|
|
6838
|
-
var
|
|
6864
|
+
var fs47 = require("fs");
|
|
6865
|
+
var EventEmitter2 = require("events");
|
|
6839
6866
|
var inherits = require("util").inherits;
|
|
6840
6867
|
var path59 = require("path");
|
|
6841
6868
|
var sleep = require_atomic_sleep();
|
|
@@ -6891,20 +6918,20 @@ var require_sonic_boom = __commonJS({
|
|
|
6891
6918
|
const mode = sonic.mode;
|
|
6892
6919
|
if (sonic.sync) {
|
|
6893
6920
|
try {
|
|
6894
|
-
if (sonic.mkdir)
|
|
6895
|
-
const fd =
|
|
6921
|
+
if (sonic.mkdir) fs47.mkdirSync(path59.dirname(file), { recursive: true });
|
|
6922
|
+
const fd = fs47.openSync(file, flags, mode);
|
|
6896
6923
|
fileOpened(null, fd);
|
|
6897
6924
|
} catch (err) {
|
|
6898
6925
|
fileOpened(err);
|
|
6899
6926
|
throw err;
|
|
6900
6927
|
}
|
|
6901
6928
|
} else if (sonic.mkdir) {
|
|
6902
|
-
|
|
6929
|
+
fs47.mkdir(path59.dirname(file), { recursive: true }, (err) => {
|
|
6903
6930
|
if (err) return fileOpened(err);
|
|
6904
|
-
|
|
6931
|
+
fs47.open(file, flags, mode, fileOpened);
|
|
6905
6932
|
});
|
|
6906
6933
|
} else {
|
|
6907
|
-
|
|
6934
|
+
fs47.open(file, flags, mode, fileOpened);
|
|
6908
6935
|
}
|
|
6909
6936
|
}
|
|
6910
6937
|
function SonicBoom(opts) {
|
|
@@ -6945,8 +6972,8 @@ var require_sonic_boom = __commonJS({
|
|
|
6945
6972
|
this.flush = flushBuffer;
|
|
6946
6973
|
this.flushSync = flushBufferSync;
|
|
6947
6974
|
this._actualWrite = actualWriteBuffer;
|
|
6948
|
-
fsWriteSync = () =>
|
|
6949
|
-
fsWrite = () =>
|
|
6975
|
+
fsWriteSync = () => fs47.writeSync(this.fd, this._writingBuf);
|
|
6976
|
+
fsWrite = () => fs47.write(this.fd, this._writingBuf, this.release);
|
|
6950
6977
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
6951
6978
|
this._writingBuf = "";
|
|
6952
6979
|
this.write = write;
|
|
@@ -6955,15 +6982,15 @@ var require_sonic_boom = __commonJS({
|
|
|
6955
6982
|
this._actualWrite = actualWrite;
|
|
6956
6983
|
fsWriteSync = () => {
|
|
6957
6984
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6958
|
-
return
|
|
6985
|
+
return fs47.writeSync(this.fd, this._writingBuf);
|
|
6959
6986
|
}
|
|
6960
|
-
return
|
|
6987
|
+
return fs47.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6961
6988
|
};
|
|
6962
6989
|
fsWrite = () => {
|
|
6963
6990
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6964
|
-
return
|
|
6991
|
+
return fs47.write(this.fd, this._writingBuf, this.release);
|
|
6965
6992
|
}
|
|
6966
|
-
return
|
|
6993
|
+
return fs47.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
6967
6994
|
};
|
|
6968
6995
|
} else {
|
|
6969
6996
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -7020,7 +7047,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7020
7047
|
}
|
|
7021
7048
|
}
|
|
7022
7049
|
if (this._fsync) {
|
|
7023
|
-
|
|
7050
|
+
fs47.fsyncSync(this.fd);
|
|
7024
7051
|
}
|
|
7025
7052
|
const len = this._len;
|
|
7026
7053
|
if (this._reopening) {
|
|
@@ -7072,7 +7099,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7072
7099
|
sonic._asyncDrainScheduled = false;
|
|
7073
7100
|
sonic.emit("drain");
|
|
7074
7101
|
}
|
|
7075
|
-
inherits(SonicBoom,
|
|
7102
|
+
inherits(SonicBoom, EventEmitter2);
|
|
7076
7103
|
function mergeBuf(bufs, len) {
|
|
7077
7104
|
if (bufs.length === 0) {
|
|
7078
7105
|
return kEmptyBuffer;
|
|
@@ -7134,7 +7161,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7134
7161
|
const onDrain = () => {
|
|
7135
7162
|
if (!this._fsync) {
|
|
7136
7163
|
try {
|
|
7137
|
-
|
|
7164
|
+
fs47.fsync(this.fd, (err) => {
|
|
7138
7165
|
this._flushPending = false;
|
|
7139
7166
|
cb(err);
|
|
7140
7167
|
});
|
|
@@ -7236,7 +7263,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7236
7263
|
const fd = this.fd;
|
|
7237
7264
|
this.once("ready", () => {
|
|
7238
7265
|
if (fd !== this.fd) {
|
|
7239
|
-
|
|
7266
|
+
fs47.close(fd, (err) => {
|
|
7240
7267
|
if (err) {
|
|
7241
7268
|
return this.emit("error", err);
|
|
7242
7269
|
}
|
|
@@ -7285,7 +7312,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7285
7312
|
buf = this._bufs[0];
|
|
7286
7313
|
}
|
|
7287
7314
|
try {
|
|
7288
|
-
const n = Buffer.isBuffer(buf) ?
|
|
7315
|
+
const n = Buffer.isBuffer(buf) ? fs47.writeSync(this.fd, buf) : fs47.writeSync(this.fd, buf, "utf8");
|
|
7289
7316
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
7290
7317
|
buf = releasedBufObj.writingBuf;
|
|
7291
7318
|
this._len = releasedBufObj.len;
|
|
@@ -7301,7 +7328,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7301
7328
|
}
|
|
7302
7329
|
}
|
|
7303
7330
|
try {
|
|
7304
|
-
|
|
7331
|
+
fs47.fsyncSync(this.fd);
|
|
7305
7332
|
} catch {
|
|
7306
7333
|
}
|
|
7307
7334
|
}
|
|
@@ -7322,7 +7349,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7322
7349
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
7323
7350
|
}
|
|
7324
7351
|
try {
|
|
7325
|
-
const n =
|
|
7352
|
+
const n = fs47.writeSync(this.fd, buf);
|
|
7326
7353
|
buf = buf.subarray(n);
|
|
7327
7354
|
this._len = Math.max(this._len - n, 0);
|
|
7328
7355
|
if (buf.length <= 0) {
|
|
@@ -7350,13 +7377,13 @@ var require_sonic_boom = __commonJS({
|
|
|
7350
7377
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
7351
7378
|
if (this.sync) {
|
|
7352
7379
|
try {
|
|
7353
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
7380
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs47.writeSync(this.fd, this._writingBuf) : fs47.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7354
7381
|
release(null, written);
|
|
7355
7382
|
} catch (err) {
|
|
7356
7383
|
release(err);
|
|
7357
7384
|
}
|
|
7358
7385
|
} else {
|
|
7359
|
-
|
|
7386
|
+
fs47.write(this.fd, this._writingBuf, release);
|
|
7360
7387
|
}
|
|
7361
7388
|
}
|
|
7362
7389
|
function actualWriteBuffer() {
|
|
@@ -7365,7 +7392,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7365
7392
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
7366
7393
|
if (this.sync) {
|
|
7367
7394
|
try {
|
|
7368
|
-
const written =
|
|
7395
|
+
const written = fs47.writeSync(this.fd, this._writingBuf);
|
|
7369
7396
|
release(null, written);
|
|
7370
7397
|
} catch (err) {
|
|
7371
7398
|
release(err);
|
|
@@ -7374,7 +7401,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7374
7401
|
if (kCopyBuffer) {
|
|
7375
7402
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
7376
7403
|
}
|
|
7377
|
-
|
|
7404
|
+
fs47.write(this.fd, this._writingBuf, release);
|
|
7378
7405
|
}
|
|
7379
7406
|
}
|
|
7380
7407
|
function actualClose(sonic) {
|
|
@@ -7390,12 +7417,12 @@ var require_sonic_boom = __commonJS({
|
|
|
7390
7417
|
sonic._lens = [];
|
|
7391
7418
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
7392
7419
|
try {
|
|
7393
|
-
|
|
7420
|
+
fs47.fsync(sonic.fd, closeWrapped);
|
|
7394
7421
|
} catch {
|
|
7395
7422
|
}
|
|
7396
7423
|
function closeWrapped() {
|
|
7397
7424
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7398
|
-
|
|
7425
|
+
fs47.close(sonic.fd, done);
|
|
7399
7426
|
} else {
|
|
7400
7427
|
done();
|
|
7401
7428
|
}
|
|
@@ -7650,9 +7677,9 @@ var require_thread_stream = __commonJS({
|
|
|
7650
7677
|
"../node_modules/.pnpm/thread-stream@3.1.0/node_modules/thread-stream/index.js"(exports2, module2) {
|
|
7651
7678
|
"use strict";
|
|
7652
7679
|
var { version: version2 } = require_package();
|
|
7653
|
-
var { EventEmitter:
|
|
7680
|
+
var { EventEmitter: EventEmitter2 } = require("events");
|
|
7654
7681
|
var { Worker } = require("worker_threads");
|
|
7655
|
-
var { join:
|
|
7682
|
+
var { join: join11 } = require("path");
|
|
7656
7683
|
var { pathToFileURL } = require("url");
|
|
7657
7684
|
var { wait } = require_wait();
|
|
7658
7685
|
var {
|
|
@@ -7688,7 +7715,7 @@ var require_thread_stream = __commonJS({
|
|
|
7688
7715
|
function createWorker(stream, opts) {
|
|
7689
7716
|
const { filename, workerData } = opts;
|
|
7690
7717
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
7691
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
7718
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join11(__dirname, "lib", "worker.js");
|
|
7692
7719
|
const worker = new Worker(toExecute, {
|
|
7693
7720
|
...opts.workerOpts,
|
|
7694
7721
|
trackUnmanagedFds: false,
|
|
@@ -7806,7 +7833,7 @@ var require_thread_stream = __commonJS({
|
|
|
7806
7833
|
stream.worker.off("exit", onWorkerExit);
|
|
7807
7834
|
destroy(stream, code !== 0 ? new Error("the worker thread exited") : null);
|
|
7808
7835
|
}
|
|
7809
|
-
var ThreadStream = class extends
|
|
7836
|
+
var ThreadStream = class extends EventEmitter2 {
|
|
7810
7837
|
constructor(opts = {}) {
|
|
7811
7838
|
super();
|
|
7812
7839
|
if (opts.bufferSize < 4) {
|
|
@@ -8074,7 +8101,7 @@ var require_transport = __commonJS({
|
|
|
8074
8101
|
"use strict";
|
|
8075
8102
|
var { createRequire } = require("module");
|
|
8076
8103
|
var getCallers = require_caller();
|
|
8077
|
-
var { join:
|
|
8104
|
+
var { join: join11, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
|
|
8078
8105
|
var sleep = require_atomic_sleep();
|
|
8079
8106
|
var onExit = require_on_exit_leak_free();
|
|
8080
8107
|
var ThreadStream = require_thread_stream();
|
|
@@ -8137,7 +8164,7 @@ var require_transport = __commonJS({
|
|
|
8137
8164
|
throw new Error("only one of target or targets can be specified");
|
|
8138
8165
|
}
|
|
8139
8166
|
if (targets) {
|
|
8140
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8167
|
+
target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
|
|
8141
8168
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
8142
8169
|
return {
|
|
8143
8170
|
...dest,
|
|
@@ -8155,7 +8182,7 @@ var require_transport = __commonJS({
|
|
|
8155
8182
|
});
|
|
8156
8183
|
});
|
|
8157
8184
|
} else if (pipeline3) {
|
|
8158
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8185
|
+
target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
|
|
8159
8186
|
options.pipelines = [pipeline3.map((dest) => {
|
|
8160
8187
|
return {
|
|
8161
8188
|
...dest,
|
|
@@ -8177,7 +8204,7 @@ var require_transport = __commonJS({
|
|
|
8177
8204
|
return origin;
|
|
8178
8205
|
}
|
|
8179
8206
|
if (origin === "pino/file") {
|
|
8180
|
-
return
|
|
8207
|
+
return join11(__dirname, "..", "file.js");
|
|
8181
8208
|
}
|
|
8182
8209
|
let fixTarget2;
|
|
8183
8210
|
for (const filePath of callers) {
|
|
@@ -8764,7 +8791,7 @@ var require_meta = __commonJS({
|
|
|
8764
8791
|
var require_proto = __commonJS({
|
|
8765
8792
|
"../node_modules/.pnpm/pino@9.14.0/node_modules/pino/lib/proto.js"(exports2, module2) {
|
|
8766
8793
|
"use strict";
|
|
8767
|
-
var { EventEmitter:
|
|
8794
|
+
var { EventEmitter: EventEmitter2 } = require("events");
|
|
8768
8795
|
var {
|
|
8769
8796
|
lsCacheSym,
|
|
8770
8797
|
levelValSym,
|
|
@@ -8846,7 +8873,7 @@ var require_proto = __commonJS({
|
|
|
8846
8873
|
[getLevelSym]: getLevel,
|
|
8847
8874
|
[setLevelSym]: setLevel
|
|
8848
8875
|
};
|
|
8849
|
-
Object.setPrototypeOf(prototype,
|
|
8876
|
+
Object.setPrototypeOf(prototype, EventEmitter2.prototype);
|
|
8850
8877
|
module2.exports = function() {
|
|
8851
8878
|
return Object.create(prototype);
|
|
8852
8879
|
};
|
|
@@ -9167,7 +9194,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9167
9194
|
return circularValue;
|
|
9168
9195
|
}
|
|
9169
9196
|
let res = "";
|
|
9170
|
-
let
|
|
9197
|
+
let join11 = ",";
|
|
9171
9198
|
const originalIndentation = indentation;
|
|
9172
9199
|
if (Array.isArray(value)) {
|
|
9173
9200
|
if (value.length === 0) {
|
|
@@ -9181,7 +9208,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9181
9208
|
indentation += spacer;
|
|
9182
9209
|
res += `
|
|
9183
9210
|
${indentation}`;
|
|
9184
|
-
|
|
9211
|
+
join11 = `,
|
|
9185
9212
|
${indentation}`;
|
|
9186
9213
|
}
|
|
9187
9214
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9189,13 +9216,13 @@ ${indentation}`;
|
|
|
9189
9216
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9190
9217
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9191
9218
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9192
|
-
res +=
|
|
9219
|
+
res += join11;
|
|
9193
9220
|
}
|
|
9194
9221
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9195
9222
|
res += tmp !== void 0 ? tmp : "null";
|
|
9196
9223
|
if (value.length - 1 > maximumBreadth) {
|
|
9197
9224
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9198
|
-
res += `${
|
|
9225
|
+
res += `${join11}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9199
9226
|
}
|
|
9200
9227
|
if (spacer !== "") {
|
|
9201
9228
|
res += `
|
|
@@ -9216,7 +9243,7 @@ ${originalIndentation}`;
|
|
|
9216
9243
|
let separator = "";
|
|
9217
9244
|
if (spacer !== "") {
|
|
9218
9245
|
indentation += spacer;
|
|
9219
|
-
|
|
9246
|
+
join11 = `,
|
|
9220
9247
|
${indentation}`;
|
|
9221
9248
|
whitespace = " ";
|
|
9222
9249
|
}
|
|
@@ -9230,13 +9257,13 @@ ${indentation}`;
|
|
|
9230
9257
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
9231
9258
|
if (tmp !== void 0) {
|
|
9232
9259
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9233
|
-
separator =
|
|
9260
|
+
separator = join11;
|
|
9234
9261
|
}
|
|
9235
9262
|
}
|
|
9236
9263
|
if (keyLength > maximumBreadth) {
|
|
9237
9264
|
const removedKeys = keyLength - maximumBreadth;
|
|
9238
9265
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
9239
|
-
separator =
|
|
9266
|
+
separator = join11;
|
|
9240
9267
|
}
|
|
9241
9268
|
if (spacer !== "" && separator.length > 1) {
|
|
9242
9269
|
res = `
|
|
@@ -9277,7 +9304,7 @@ ${originalIndentation}`;
|
|
|
9277
9304
|
}
|
|
9278
9305
|
const originalIndentation = indentation;
|
|
9279
9306
|
let res = "";
|
|
9280
|
-
let
|
|
9307
|
+
let join11 = ",";
|
|
9281
9308
|
if (Array.isArray(value)) {
|
|
9282
9309
|
if (value.length === 0) {
|
|
9283
9310
|
return "[]";
|
|
@@ -9290,7 +9317,7 @@ ${originalIndentation}`;
|
|
|
9290
9317
|
indentation += spacer;
|
|
9291
9318
|
res += `
|
|
9292
9319
|
${indentation}`;
|
|
9293
|
-
|
|
9320
|
+
join11 = `,
|
|
9294
9321
|
${indentation}`;
|
|
9295
9322
|
}
|
|
9296
9323
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9298,13 +9325,13 @@ ${indentation}`;
|
|
|
9298
9325
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9299
9326
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9300
9327
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9301
|
-
res +=
|
|
9328
|
+
res += join11;
|
|
9302
9329
|
}
|
|
9303
9330
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9304
9331
|
res += tmp !== void 0 ? tmp : "null";
|
|
9305
9332
|
if (value.length - 1 > maximumBreadth) {
|
|
9306
9333
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9307
|
-
res += `${
|
|
9334
|
+
res += `${join11}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9308
9335
|
}
|
|
9309
9336
|
if (spacer !== "") {
|
|
9310
9337
|
res += `
|
|
@@ -9317,7 +9344,7 @@ ${originalIndentation}`;
|
|
|
9317
9344
|
let whitespace = "";
|
|
9318
9345
|
if (spacer !== "") {
|
|
9319
9346
|
indentation += spacer;
|
|
9320
|
-
|
|
9347
|
+
join11 = `,
|
|
9321
9348
|
${indentation}`;
|
|
9322
9349
|
whitespace = " ";
|
|
9323
9350
|
}
|
|
@@ -9326,7 +9353,7 @@ ${indentation}`;
|
|
|
9326
9353
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
9327
9354
|
if (tmp !== void 0) {
|
|
9328
9355
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9329
|
-
separator =
|
|
9356
|
+
separator = join11;
|
|
9330
9357
|
}
|
|
9331
9358
|
}
|
|
9332
9359
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -9384,20 +9411,20 @@ ${originalIndentation}`;
|
|
|
9384
9411
|
indentation += spacer;
|
|
9385
9412
|
let res2 = `
|
|
9386
9413
|
${indentation}`;
|
|
9387
|
-
const
|
|
9414
|
+
const join12 = `,
|
|
9388
9415
|
${indentation}`;
|
|
9389
9416
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
9390
9417
|
let i = 0;
|
|
9391
9418
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9392
9419
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9393
9420
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
9394
|
-
res2 +=
|
|
9421
|
+
res2 += join12;
|
|
9395
9422
|
}
|
|
9396
9423
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9397
9424
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
9398
9425
|
if (value.length - 1 > maximumBreadth) {
|
|
9399
9426
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9400
|
-
res2 += `${
|
|
9427
|
+
res2 += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9401
9428
|
}
|
|
9402
9429
|
res2 += `
|
|
9403
9430
|
${originalIndentation}`;
|
|
@@ -9413,16 +9440,16 @@ ${originalIndentation}`;
|
|
|
9413
9440
|
return '"[Object]"';
|
|
9414
9441
|
}
|
|
9415
9442
|
indentation += spacer;
|
|
9416
|
-
const
|
|
9443
|
+
const join11 = `,
|
|
9417
9444
|
${indentation}`;
|
|
9418
9445
|
let res = "";
|
|
9419
9446
|
let separator = "";
|
|
9420
9447
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
9421
9448
|
if (isTypedArrayWithEntries(value)) {
|
|
9422
|
-
res += stringifyTypedArray(value,
|
|
9449
|
+
res += stringifyTypedArray(value, join11, maximumBreadth);
|
|
9423
9450
|
keys = keys.slice(value.length);
|
|
9424
9451
|
maximumPropertiesToStringify -= value.length;
|
|
9425
|
-
separator =
|
|
9452
|
+
separator = join11;
|
|
9426
9453
|
}
|
|
9427
9454
|
if (deterministic) {
|
|
9428
9455
|
keys = sort(keys, comparator);
|
|
@@ -9433,13 +9460,13 @@ ${indentation}`;
|
|
|
9433
9460
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
9434
9461
|
if (tmp !== void 0) {
|
|
9435
9462
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
9436
|
-
separator =
|
|
9463
|
+
separator = join11;
|
|
9437
9464
|
}
|
|
9438
9465
|
}
|
|
9439
9466
|
if (keyLength > maximumBreadth) {
|
|
9440
9467
|
const removedKeys = keyLength - maximumBreadth;
|
|
9441
9468
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
9442
|
-
separator =
|
|
9469
|
+
separator = join11;
|
|
9443
9470
|
}
|
|
9444
9471
|
if (separator !== "") {
|
|
9445
9472
|
res = `
|
|
@@ -19321,10 +19348,10 @@ var require_extension = __commonJS({
|
|
|
19321
19348
|
var require_websocket = __commonJS({
|
|
19322
19349
|
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket.js"(exports2, module2) {
|
|
19323
19350
|
"use strict";
|
|
19324
|
-
var
|
|
19351
|
+
var EventEmitter2 = require("events");
|
|
19325
19352
|
var https = require("https");
|
|
19326
|
-
var
|
|
19327
|
-
var
|
|
19353
|
+
var http2 = require("http");
|
|
19354
|
+
var net2 = require("net");
|
|
19328
19355
|
var tls = require("tls");
|
|
19329
19356
|
var { randomBytes, createHash: createHash2 } = require("crypto");
|
|
19330
19357
|
var { Duplex, Readable: Readable3 } = require("stream");
|
|
@@ -19353,7 +19380,7 @@ var require_websocket = __commonJS({
|
|
|
19353
19380
|
var protocolVersions = [8, 13];
|
|
19354
19381
|
var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
|
|
19355
19382
|
var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
|
|
19356
|
-
var WebSocket2 = class _WebSocket extends
|
|
19383
|
+
var WebSocket2 = class _WebSocket extends EventEmitter2 {
|
|
19357
19384
|
/**
|
|
19358
19385
|
* Create a new `WebSocket`.
|
|
19359
19386
|
*
|
|
@@ -19857,7 +19884,7 @@ var require_websocket = __commonJS({
|
|
|
19857
19884
|
}
|
|
19858
19885
|
const defaultPort = isSecure ? 443 : 80;
|
|
19859
19886
|
const key = randomBytes(16).toString("base64");
|
|
19860
|
-
const request = isSecure ? https.request :
|
|
19887
|
+
const request = isSecure ? https.request : http2.request;
|
|
19861
19888
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
19862
19889
|
let perMessageDeflate;
|
|
19863
19890
|
opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
|
|
@@ -20058,12 +20085,12 @@ var require_websocket = __commonJS({
|
|
|
20058
20085
|
}
|
|
20059
20086
|
function netConnect(options) {
|
|
20060
20087
|
options.path = options.socketPath;
|
|
20061
|
-
return
|
|
20088
|
+
return net2.connect(options);
|
|
20062
20089
|
}
|
|
20063
20090
|
function tlsConnect(options) {
|
|
20064
20091
|
options.path = void 0;
|
|
20065
20092
|
if (!options.servername && options.servername !== "") {
|
|
20066
|
-
options.servername =
|
|
20093
|
+
options.servername = net2.isIP(options.host) ? "" : options.host;
|
|
20067
20094
|
}
|
|
20068
20095
|
return tls.connect(options);
|
|
20069
20096
|
}
|
|
@@ -20350,8 +20377,8 @@ var require_subprotocol = __commonJS({
|
|
|
20350
20377
|
var require_websocket_server = __commonJS({
|
|
20351
20378
|
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) {
|
|
20352
20379
|
"use strict";
|
|
20353
|
-
var
|
|
20354
|
-
var
|
|
20380
|
+
var EventEmitter2 = require("events");
|
|
20381
|
+
var http2 = require("http");
|
|
20355
20382
|
var { Duplex } = require("stream");
|
|
20356
20383
|
var { createHash: createHash2 } = require("crypto");
|
|
20357
20384
|
var extension2 = require_extension();
|
|
@@ -20363,7 +20390,7 @@ var require_websocket_server = __commonJS({
|
|
|
20363
20390
|
var RUNNING = 0;
|
|
20364
20391
|
var CLOSING = 1;
|
|
20365
20392
|
var CLOSED = 2;
|
|
20366
|
-
var WebSocketServer2 = class extends
|
|
20393
|
+
var WebSocketServer2 = class extends EventEmitter2 {
|
|
20367
20394
|
/**
|
|
20368
20395
|
* Create a `WebSocketServer` instance.
|
|
20369
20396
|
*
|
|
@@ -20426,8 +20453,8 @@ var require_websocket_server = __commonJS({
|
|
|
20426
20453
|
);
|
|
20427
20454
|
}
|
|
20428
20455
|
if (options.port != null) {
|
|
20429
|
-
this._server =
|
|
20430
|
-
const body =
|
|
20456
|
+
this._server = http2.createServer((req, res) => {
|
|
20457
|
+
const body = http2.STATUS_CODES[426];
|
|
20431
20458
|
res.writeHead(426, {
|
|
20432
20459
|
"Content-Length": body.length,
|
|
20433
20460
|
"Content-Type": "text/plain"
|
|
@@ -20714,7 +20741,7 @@ var require_websocket_server = __commonJS({
|
|
|
20714
20741
|
this.destroy();
|
|
20715
20742
|
}
|
|
20716
20743
|
function abortHandshake(socket, code, message, headers) {
|
|
20717
|
-
message = message ||
|
|
20744
|
+
message = message || http2.STATUS_CODES[code];
|
|
20718
20745
|
headers = {
|
|
20719
20746
|
Connection: "close",
|
|
20720
20747
|
"Content-Type": "text/html",
|
|
@@ -20723,7 +20750,7 @@ var require_websocket_server = __commonJS({
|
|
|
20723
20750
|
};
|
|
20724
20751
|
socket.once("finish", socket.destroy);
|
|
20725
20752
|
socket.end(
|
|
20726
|
-
`HTTP/1.1 ${code} ${
|
|
20753
|
+
`HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
|
|
20727
20754
|
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
|
|
20728
20755
|
);
|
|
20729
20756
|
}
|
|
@@ -21026,7 +21053,7 @@ var require_BufferList = __commonJS({
|
|
|
21026
21053
|
this.head = this.tail = null;
|
|
21027
21054
|
this.length = 0;
|
|
21028
21055
|
};
|
|
21029
|
-
BufferList.prototype.join = function
|
|
21056
|
+
BufferList.prototype.join = function join11(s) {
|
|
21030
21057
|
if (this.length === 0) return "";
|
|
21031
21058
|
var p2 = this.head;
|
|
21032
21059
|
var ret = "" + p2.data;
|
|
@@ -30483,7 +30510,7 @@ var require_lib3 = __commonJS({
|
|
|
30483
30510
|
// src/run-case/recorder.ts
|
|
30484
30511
|
function startRunCaseRecorder(opts) {
|
|
30485
30512
|
const now = opts.now ?? Date.now;
|
|
30486
|
-
const dir =
|
|
30513
|
+
const dir = import_node_path45.default.dirname(opts.recordPath);
|
|
30487
30514
|
let stream = null;
|
|
30488
30515
|
let closing = false;
|
|
30489
30516
|
let closedSettled = false;
|
|
@@ -30497,8 +30524,8 @@ function startRunCaseRecorder(opts) {
|
|
|
30497
30524
|
});
|
|
30498
30525
|
const ensureStream = () => {
|
|
30499
30526
|
if (stream) return stream;
|
|
30500
|
-
|
|
30501
|
-
stream =
|
|
30527
|
+
import_node_fs30.default.mkdirSync(dir, { recursive: true });
|
|
30528
|
+
stream = import_node_fs30.default.createWriteStream(opts.recordPath, { flags: "a" });
|
|
30502
30529
|
stream.on("close", () => closedResolve());
|
|
30503
30530
|
return stream;
|
|
30504
30531
|
};
|
|
@@ -30523,12 +30550,12 @@ function startRunCaseRecorder(opts) {
|
|
|
30523
30550
|
};
|
|
30524
30551
|
return { tap, close, closed };
|
|
30525
30552
|
}
|
|
30526
|
-
var
|
|
30553
|
+
var import_node_fs30, import_node_path45;
|
|
30527
30554
|
var init_recorder = __esm({
|
|
30528
30555
|
"src/run-case/recorder.ts"() {
|
|
30529
30556
|
"use strict";
|
|
30530
|
-
|
|
30531
|
-
|
|
30557
|
+
import_node_fs30 = __toESM(require("fs"), 1);
|
|
30558
|
+
import_node_path45 = __toESM(require("path"), 1);
|
|
30532
30559
|
}
|
|
30533
30560
|
});
|
|
30534
30561
|
|
|
@@ -30571,7 +30598,7 @@ var init_wire = __esm({
|
|
|
30571
30598
|
// src/run-case/controller.ts
|
|
30572
30599
|
async function runController(opts) {
|
|
30573
30600
|
const now = opts.now ?? Date.now;
|
|
30574
|
-
const cwd = opts.cwd ?? (0,
|
|
30601
|
+
const cwd = opts.cwd ?? (0, import_node_fs31.mkdtempSync)(import_node_path46.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
|
|
30575
30602
|
const ownsCwd = opts.cwd === void 0;
|
|
30576
30603
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
30577
30604
|
const spawnCtx = { cwd };
|
|
@@ -30732,19 +30759,19 @@ async function runController(opts) {
|
|
|
30732
30759
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
30733
30760
|
if (ownsCwd) {
|
|
30734
30761
|
try {
|
|
30735
|
-
(0,
|
|
30762
|
+
(0, import_node_fs31.rmSync)(cwd, { recursive: true, force: true });
|
|
30736
30763
|
} catch {
|
|
30737
30764
|
}
|
|
30738
30765
|
}
|
|
30739
30766
|
return exitCode ?? 0;
|
|
30740
30767
|
}
|
|
30741
|
-
var
|
|
30768
|
+
var import_node_fs31, import_node_os19, import_node_path46;
|
|
30742
30769
|
var init_controller = __esm({
|
|
30743
30770
|
"src/run-case/controller.ts"() {
|
|
30744
30771
|
"use strict";
|
|
30745
|
-
|
|
30772
|
+
import_node_fs31 = require("fs");
|
|
30746
30773
|
import_node_os19 = __toESM(require("os"), 1);
|
|
30747
|
-
|
|
30774
|
+
import_node_path46 = __toESM(require("path"), 1);
|
|
30748
30775
|
init_claude();
|
|
30749
30776
|
init_stdout_splitter();
|
|
30750
30777
|
init_permission_stdio();
|
|
@@ -30936,8 +30963,6 @@ function resolveConfig(opts) {
|
|
|
30936
30963
|
const frpcBinary = env.CLAWD_FRPC_BIN ?? null;
|
|
30937
30964
|
const rawMode = env.CLAWD_CC_MODE;
|
|
30938
30965
|
const mode = DAEMON_MODE_VALUES.includes(rawMode ?? "") ? rawMode : "sdk";
|
|
30939
|
-
const filePreviewPorts = Array.isArray(fileCfg.previewPorts) ? fileCfg.previewPorts.map((n) => Number(n)).filter((n) => Number.isInteger(n) && n > 0) : null;
|
|
30940
|
-
const previewPorts = env.CLAWD_PREVIEW_PORTS ? env.CLAWD_PREVIEW_PORTS.split(",").map((s) => Number(s.trim())).filter((n) => Number.isInteger(n) && n > 0) : filePreviewPorts && filePreviewPorts.length > 0 ? filePreviewPorts : null;
|
|
30941
30966
|
return {
|
|
30942
30967
|
port,
|
|
30943
30968
|
host,
|
|
@@ -30949,8 +30974,7 @@ function resolveConfig(opts) {
|
|
|
30949
30974
|
authToken,
|
|
30950
30975
|
noTunnelPersist,
|
|
30951
30976
|
frpcBinary,
|
|
30952
|
-
mode
|
|
30953
|
-
previewPorts
|
|
30977
|
+
mode
|
|
30954
30978
|
};
|
|
30955
30979
|
}
|
|
30956
30980
|
var HELP_TEXT = `clawd [options]
|
|
@@ -30979,8 +31003,8 @@ Env (advanced):
|
|
|
30979
31003
|
`;
|
|
30980
31004
|
|
|
30981
31005
|
// src/index.ts
|
|
30982
|
-
var
|
|
30983
|
-
var
|
|
31006
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
31007
|
+
var import_node_fs29 = __toESM(require("fs"), 1);
|
|
30984
31008
|
|
|
30985
31009
|
// src/logger.ts
|
|
30986
31010
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -32989,7 +33013,6 @@ var SessionManager = class {
|
|
|
32989
33013
|
iconKey: args.iconKey,
|
|
32990
33014
|
forkedFromSessionId: args.forkedFromSessionId,
|
|
32991
33015
|
ownerPersonaId: args.ownerPersonaId,
|
|
32992
|
-
appBuilderProject: args.appBuilderProject,
|
|
32993
33016
|
creatorPrincipalId,
|
|
32994
33017
|
...creatorOwnerPrincipalId ? { creatorOwnerPrincipalId } : {},
|
|
32995
33018
|
createdAt: iso,
|
|
@@ -34626,15 +34649,6 @@ var DEFAULT_PERSONAS = [
|
|
|
34626
34649
|
model: "opus",
|
|
34627
34650
|
iconKey: "assist",
|
|
34628
34651
|
public: false
|
|
34629
|
-
},
|
|
34630
|
-
{
|
|
34631
|
-
// app-builder:全栈应用搭建师,绑 extension-kit 模板 + multi-project picker
|
|
34632
|
-
// spec: superpowers/specs/2026-06-01-app-builder-project-picker-design.md
|
|
34633
|
-
personaId: "persona-app-builder",
|
|
34634
|
-
label: "App Builder",
|
|
34635
|
-
model: "opus",
|
|
34636
|
-
iconKey: "assist",
|
|
34637
|
-
public: false
|
|
34638
34652
|
}
|
|
34639
34653
|
];
|
|
34640
34654
|
function findDefaultsRoot() {
|
|
@@ -36331,58 +36345,7 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
36331
36345
|
var wrapper_default = import_websocket.default;
|
|
36332
36346
|
|
|
36333
36347
|
// src/transport/local-ws-server.ts
|
|
36334
|
-
var import_node_http2 = __toESM(require("http"), 1);
|
|
36335
|
-
|
|
36336
|
-
// src/transport/preview-proxy.ts
|
|
36337
36348
|
var import_node_http = __toESM(require("http"), 1);
|
|
36338
|
-
var import_node_net = __toESM(require("net"), 1);
|
|
36339
|
-
var PREVIEW_RE = /^\/preview\/(\d+)(?:\/.*)?$/;
|
|
36340
|
-
function matchPreviewPort(pathname) {
|
|
36341
|
-
const m2 = pathname.match(PREVIEW_RE);
|
|
36342
|
-
return m2 ? Number(m2[1]) : null;
|
|
36343
|
-
}
|
|
36344
|
-
function isPreviewPortAllowed(port, cfg) {
|
|
36345
|
-
return cfg.allowedPorts.includes(port);
|
|
36346
|
-
}
|
|
36347
|
-
function proxyPreviewHttp(req, res, port) {
|
|
36348
|
-
return new Promise((resolve6) => {
|
|
36349
|
-
const upstream = import_node_http.default.request(
|
|
36350
|
-
{ host: "localhost", port, method: req.method, path: req.url, headers: req.headers },
|
|
36351
|
-
(upRes) => {
|
|
36352
|
-
res.writeHead(upRes.statusCode || 502, upRes.headers);
|
|
36353
|
-
upRes.pipe(res);
|
|
36354
|
-
upRes.on("end", () => resolve6());
|
|
36355
|
-
}
|
|
36356
|
-
);
|
|
36357
|
-
upstream.on("error", (e) => {
|
|
36358
|
-
if (!res.headersSent) {
|
|
36359
|
-
res.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
|
|
36360
|
-
}
|
|
36361
|
-
res.end(`preview upstream error: ${e.message}`);
|
|
36362
|
-
resolve6();
|
|
36363
|
-
});
|
|
36364
|
-
req.pipe(upstream);
|
|
36365
|
-
});
|
|
36366
|
-
}
|
|
36367
|
-
function proxyPreviewUpgrade(req, socket, head, port) {
|
|
36368
|
-
const upstream = import_node_net.default.connect(port, "localhost", () => {
|
|
36369
|
-
let raw = `${req.method} ${req.url} HTTP/1.1\r
|
|
36370
|
-
`;
|
|
36371
|
-
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
36372
|
-
raw += `${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r
|
|
36373
|
-
`;
|
|
36374
|
-
}
|
|
36375
|
-
raw += "\r\n";
|
|
36376
|
-
upstream.write(raw);
|
|
36377
|
-
if (head && head.length) upstream.write(head);
|
|
36378
|
-
upstream.pipe(socket);
|
|
36379
|
-
socket.pipe(upstream);
|
|
36380
|
-
});
|
|
36381
|
-
upstream.on("error", () => socket.destroy());
|
|
36382
|
-
socket.on("error", () => upstream.destroy());
|
|
36383
|
-
}
|
|
36384
|
-
|
|
36385
|
-
// src/transport/local-ws-server.ts
|
|
36386
36349
|
var LocalWsServer = class {
|
|
36387
36350
|
constructor(opts) {
|
|
36388
36351
|
this.opts = opts;
|
|
@@ -36394,33 +36357,18 @@ var LocalWsServer = class {
|
|
|
36394
36357
|
httpServer = null;
|
|
36395
36358
|
frameHandler = null;
|
|
36396
36359
|
clients = /* @__PURE__ */ new Map();
|
|
36397
|
-
//
|
|
36398
|
-
//
|
|
36399
|
-
|
|
36360
|
+
// guest principal id → Set<clientId>,撤销时 O(1) 找到该 guest 的所有活跃连接做关闭级联。
|
|
36361
|
+
// 同一 guest 可能多端同时连(多设备 / 多 tab)。
|
|
36362
|
+
// Phase 3 (决策 #14): 索引键从 ctx.capabilityId 改为 guest principal.id(两者恒等,纯正名)。
|
|
36363
|
+
guestIdToClients = /* @__PURE__ */ new Map();
|
|
36400
36364
|
logger;
|
|
36401
36365
|
pingIntervalMs;
|
|
36402
36366
|
async start() {
|
|
36403
36367
|
const host = this.opts.host ?? "127.0.0.1";
|
|
36404
36368
|
await new Promise((resolve6, reject) => {
|
|
36405
|
-
const httpServer =
|
|
36369
|
+
const httpServer = import_node_http.default.createServer((req, res) => this.handleHttpRequest(req, res));
|
|
36406
36370
|
const wss = new import_websocket_server.default({ noServer: true, clientTracking: true });
|
|
36407
36371
|
httpServer.on("upgrade", (req, socket, head) => {
|
|
36408
|
-
if (req.url?.startsWith("/preview/")) {
|
|
36409
|
-
const pathname = (() => {
|
|
36410
|
-
try {
|
|
36411
|
-
return new URL(req.url, "http://localhost").pathname;
|
|
36412
|
-
} catch {
|
|
36413
|
-
return "/";
|
|
36414
|
-
}
|
|
36415
|
-
})();
|
|
36416
|
-
const port = matchPreviewPort(pathname);
|
|
36417
|
-
if (port != null && (this.opts.previewPorts ?? []).includes(port)) {
|
|
36418
|
-
proxyPreviewUpgrade(req, socket, head, port);
|
|
36419
|
-
} else {
|
|
36420
|
-
socket.destroy();
|
|
36421
|
-
}
|
|
36422
|
-
return;
|
|
36423
|
-
}
|
|
36424
36372
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
36425
36373
|
this.routeConnection(ws, req);
|
|
36426
36374
|
});
|
|
@@ -36514,38 +36462,6 @@ var LocalWsServer = class {
|
|
|
36514
36462
|
this.safeSend(c.ws, frame);
|
|
36515
36463
|
}
|
|
36516
36464
|
}
|
|
36517
|
-
// capability platform v3 inbox 重写: 推给某条 cap channel 的两端 ws.
|
|
36518
|
-
// - 所有 owner ws (owner 看自家所有 channel)
|
|
36519
|
-
// - 该 capabilityId 的所有 guest ws (该 cap 持有人的多端/多 tab)
|
|
36520
|
-
// 一次调用同时推两端, inbox:event push 帧由此走.
|
|
36521
|
-
broadcastToCapabilityChannel(capabilityId, frame) {
|
|
36522
|
-
this.broadcastToOwners(frame);
|
|
36523
|
-
const set = this.capabilityIdToClients.get(capabilityId);
|
|
36524
|
-
if (!set) return;
|
|
36525
|
-
for (const id of set) {
|
|
36526
|
-
const c = this.clients.get(id);
|
|
36527
|
-
if (!c) continue;
|
|
36528
|
-
this.safeSend(c.ws, frame);
|
|
36529
|
-
}
|
|
36530
|
-
}
|
|
36531
|
-
// Phase 4 capability platform DM: 广播到指定 principal 的所有活跃 ws.
|
|
36532
|
-
// principalId === ownerPrincipalId → 同 broadcastToOwners (所有 owner ws)
|
|
36533
|
-
// principalId === 'owner' sentinel → 同上(向后兼容遗留 caller / 协议层 sentinel)
|
|
36534
|
-
// principalId === 'cap_xxx' → 该 capability 的所有 guest ws (多端 / 多 tab)
|
|
36535
|
-
// 用于 DM inbox:event 路由: from + to 两侧各播一次, 让双方 UI 实时看到 thread 更新.
|
|
36536
|
-
broadcastToPrincipal(principalId, frame) {
|
|
36537
|
-
if (principalId === "owner" || principalId === this.opts.ownerPrincipalId) {
|
|
36538
|
-
this.broadcastToOwners(frame);
|
|
36539
|
-
return;
|
|
36540
|
-
}
|
|
36541
|
-
const set = this.capabilityIdToClients.get(principalId);
|
|
36542
|
-
if (!set) return;
|
|
36543
|
-
for (const id of set) {
|
|
36544
|
-
const c = this.clients.get(id);
|
|
36545
|
-
if (!c) continue;
|
|
36546
|
-
this.safeSend(c.ws, frame);
|
|
36547
|
-
}
|
|
36548
|
-
}
|
|
36549
36465
|
firstSubscriber(sessionId) {
|
|
36550
36466
|
for (const c of this.clients.values()) {
|
|
36551
36467
|
if (c.handle.subscribedSessions.has(sessionId)) return c.handle;
|
|
@@ -36568,16 +36484,16 @@ var LocalWsServer = class {
|
|
|
36568
36484
|
this.safeSend(c.ws, frame);
|
|
36569
36485
|
}
|
|
36570
36486
|
// Task 1.7 capability platform:AuthGate.onAuthed 回调里调本方法,把 ConnectionContext
|
|
36571
|
-
// 绑到 client;guest 连接同时进
|
|
36487
|
+
// 绑到 client;guest 连接同时进 guest 索引(撤销级联用)。
|
|
36572
36488
|
attachClientContext(clientId, ctx) {
|
|
36573
36489
|
const c = this.clients.get(clientId);
|
|
36574
36490
|
if (!c) return;
|
|
36575
36491
|
c.ctx = ctx;
|
|
36576
|
-
if (ctx.
|
|
36577
|
-
let set = this.
|
|
36492
|
+
if (ctx.principal.kind === "guest") {
|
|
36493
|
+
let set = this.guestIdToClients.get(ctx.principal.id);
|
|
36578
36494
|
if (!set) {
|
|
36579
36495
|
set = /* @__PURE__ */ new Set();
|
|
36580
|
-
this.
|
|
36496
|
+
this.guestIdToClients.set(ctx.principal.id, set);
|
|
36581
36497
|
}
|
|
36582
36498
|
set.add(clientId);
|
|
36583
36499
|
}
|
|
@@ -36586,10 +36502,27 @@ var LocalWsServer = class {
|
|
|
36586
36502
|
getClientContext(clientId) {
|
|
36587
36503
|
return this.clients.get(clientId)?.ctx ?? null;
|
|
36588
36504
|
}
|
|
36589
|
-
//
|
|
36505
|
+
// 飞书热切换(spec 决策 #9):身份切换后踢掉所有连接强制重连——在线连接的 ctx
|
|
36506
|
+
// (attachClientContext 时绑定)持有旧身份,重连后用新身份重建。
|
|
36507
|
+
// close code 4408(非 4401!4401 = token 撤销,客户端会停止重连):
|
|
36508
|
+
// 客户端按普通断连处理 → 自动重连 → 新身份 ctx。
|
|
36509
|
+
closeAllClients(code = 4408, reason = "IDENTITY_SWITCHED") {
|
|
36510
|
+
const ids = [...this.clients.keys()];
|
|
36511
|
+
for (const id of ids) {
|
|
36512
|
+
const c = this.clients.get(id);
|
|
36513
|
+
if (!c) continue;
|
|
36514
|
+
try {
|
|
36515
|
+
c.ws.close(code, reason);
|
|
36516
|
+
} catch {
|
|
36517
|
+
}
|
|
36518
|
+
}
|
|
36519
|
+
}
|
|
36520
|
+
// Task 1.10 撤销级联:关闭某 guest principal 的所有活跃 ws 连接。close code 4401
|
|
36590
36521
|
// 让客户端识别 'capability revoked' 而非普通断连。
|
|
36591
|
-
|
|
36592
|
-
|
|
36522
|
+
// capability:delete 级联用 cap.id 调本方法——noAuth 旧 per-peer cap guest 的
|
|
36523
|
+
// principal.id ≡ cap.id,级联语义不变(Phase 3 决策 #14 正名)。
|
|
36524
|
+
closeConnectionsByGuestId(guestPrincipalId, code = 4401, reason = "TOKEN_REVOKED") {
|
|
36525
|
+
const set = this.guestIdToClients.get(guestPrincipalId);
|
|
36593
36526
|
if (!set) return;
|
|
36594
36527
|
const ids = [...set];
|
|
36595
36528
|
for (const id of ids) {
|
|
@@ -36654,16 +36587,18 @@ var LocalWsServer = class {
|
|
|
36654
36587
|
this.logger?.warn("ready frame build failed", { err: err.message });
|
|
36655
36588
|
}
|
|
36656
36589
|
socket.on("message", (data) => {
|
|
36657
|
-
this.onMessage(handle, data, remoteAddress)
|
|
36590
|
+
void this.onMessage(handle, data, remoteAddress).catch((err) => {
|
|
36591
|
+
this.logger?.warn("onMessage failed", { err: err.message });
|
|
36592
|
+
});
|
|
36658
36593
|
});
|
|
36659
36594
|
socket.on("close", () => {
|
|
36660
36595
|
const c = this.clients.get(id);
|
|
36661
36596
|
if (c?.pingTimer) clearInterval(c.pingTimer);
|
|
36662
|
-
const
|
|
36663
|
-
if (
|
|
36664
|
-
const set = this.
|
|
36597
|
+
const guestId = c?.ctx?.principal.kind === "guest" ? c.ctx.principal.id : void 0;
|
|
36598
|
+
if (guestId) {
|
|
36599
|
+
const set = this.guestIdToClients.get(guestId);
|
|
36665
36600
|
set?.delete(id);
|
|
36666
|
-
if (set && set.size === 0) this.
|
|
36601
|
+
if (set && set.size === 0) this.guestIdToClients.delete(guestId);
|
|
36667
36602
|
}
|
|
36668
36603
|
this.clients.delete(id);
|
|
36669
36604
|
authGate?.unregister(id);
|
|
@@ -36673,7 +36608,7 @@ var LocalWsServer = class {
|
|
|
36673
36608
|
this.logger?.warn("client socket error", { clientId: id, err: err.message });
|
|
36674
36609
|
});
|
|
36675
36610
|
}
|
|
36676
|
-
onMessage(client, data, remoteAddress) {
|
|
36611
|
+
async onMessage(client, data, remoteAddress) {
|
|
36677
36612
|
let frame;
|
|
36678
36613
|
try {
|
|
36679
36614
|
frame = JSON.parse(data.toString());
|
|
@@ -36684,7 +36619,7 @@ var LocalWsServer = class {
|
|
|
36684
36619
|
const authGate = this.opts.authGate;
|
|
36685
36620
|
if (authGate) {
|
|
36686
36621
|
const wasAuthed = authGate.isAuthed(client.id);
|
|
36687
|
-
const verdict = authGate.handleFrame({ id: client.id, remoteAddress }, frame);
|
|
36622
|
+
const verdict = await authGate.handleFrame({ id: client.id, remoteAddress }, frame);
|
|
36688
36623
|
if (verdict !== "pass") {
|
|
36689
36624
|
if (!wasAuthed && authGate.isAuthed(client.id)) {
|
|
36690
36625
|
try {
|
|
@@ -36787,7 +36722,7 @@ var AuthGate = class {
|
|
|
36787
36722
|
registerConnection(handle) {
|
|
36788
36723
|
const enforce = this.opts.shouldEnforce(handle.remoteAddress);
|
|
36789
36724
|
if (!enforce) {
|
|
36790
|
-
this.states.set(handle.id, { authed: true, enforce: false, timer: null });
|
|
36725
|
+
this.states.set(handle.id, { authed: true, enforce: false, timer: null, pending: false });
|
|
36791
36726
|
return true;
|
|
36792
36727
|
}
|
|
36793
36728
|
const setT = this.opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
@@ -36796,7 +36731,7 @@ var AuthGate = class {
|
|
|
36796
36731
|
if (!st || st.authed) return;
|
|
36797
36732
|
this.opts.closeConnection(handle, 1008, "auth timeout");
|
|
36798
36733
|
}, this.opts.timeoutMs);
|
|
36799
|
-
this.states.set(handle.id, { authed: false, enforce: true, timer });
|
|
36734
|
+
this.states.set(handle.id, { authed: false, enforce: true, timer, pending: false });
|
|
36800
36735
|
return false;
|
|
36801
36736
|
}
|
|
36802
36737
|
unregister(handleId) {
|
|
@@ -36812,12 +36747,15 @@ var AuthGate = class {
|
|
|
36812
36747
|
}
|
|
36813
36748
|
// 收到一帧时调;返回 'consumed' / 'reject' / 'pass'
|
|
36814
36749
|
// - consumed:这一帧是 auth 帧(成功或失败),调用方不要再走业务路由
|
|
36815
|
-
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate
|
|
36750
|
+
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate 已关连接 / 验证进行中)
|
|
36816
36751
|
// - pass:已 authed,调用方继续走业务路由
|
|
36817
|
-
|
|
36752
|
+
//
|
|
36753
|
+
// Phase 2:async(authenticate 注入路径走中心 server introspect HTTP 调用)。
|
|
36754
|
+
async handleFrame(handle, frame) {
|
|
36818
36755
|
const st = this.states.get(handle.id);
|
|
36819
36756
|
if (!st) return "reject";
|
|
36820
36757
|
if (st.authed) return "pass";
|
|
36758
|
+
if (st.pending) return "reject";
|
|
36821
36759
|
const parsed = AuthRequestFrameSchema.safeParse(frame);
|
|
36822
36760
|
if (!parsed.success) {
|
|
36823
36761
|
const reason = frame.type === "auth" ? "auth failed" : "auth required";
|
|
@@ -36827,11 +36765,19 @@ var AuthGate = class {
|
|
|
36827
36765
|
}
|
|
36828
36766
|
let ctx = null;
|
|
36829
36767
|
if (this.opts.authenticate) {
|
|
36830
|
-
|
|
36831
|
-
|
|
36832
|
-
|
|
36833
|
-
|
|
36834
|
-
|
|
36768
|
+
st.pending = true;
|
|
36769
|
+
let r;
|
|
36770
|
+
try {
|
|
36771
|
+
r = await this.opts.authenticate(
|
|
36772
|
+
parsed.data.token,
|
|
36773
|
+
parsed.data.selfPrincipalId,
|
|
36774
|
+
parsed.data.selfDisplayName,
|
|
36775
|
+
parsed.data.selfUrl
|
|
36776
|
+
);
|
|
36777
|
+
} finally {
|
|
36778
|
+
st.pending = false;
|
|
36779
|
+
}
|
|
36780
|
+
if (!this.states.has(handle.id)) return "reject";
|
|
36835
36781
|
if (!r.ok) {
|
|
36836
36782
|
this.opts.closeConnection(handle, 4401, r.code);
|
|
36837
36783
|
this.markFailed(handle.id);
|
|
@@ -36934,26 +36880,32 @@ function ownerContext(ownerPrincipalId, displayName) {
|
|
|
36934
36880
|
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
36935
36881
|
};
|
|
36936
36882
|
}
|
|
36937
|
-
function
|
|
36883
|
+
function connectGuestContext(unionId, displayName) {
|
|
36938
36884
|
return {
|
|
36939
|
-
principal: { id:
|
|
36940
|
-
grants:
|
|
36941
|
-
|
|
36942
|
-
|
|
36885
|
+
principal: { id: unionId, kind: "guest", displayName },
|
|
36886
|
+
grants: PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
36887
|
+
resource: { ...g2.resource },
|
|
36888
|
+
actions: [...g2.actions]
|
|
36889
|
+
})),
|
|
36890
|
+
peerOwnerPrincipalId: unionId
|
|
36943
36891
|
};
|
|
36944
36892
|
}
|
|
36945
|
-
function authenticate(token, deps) {
|
|
36893
|
+
async function authenticate(token, deps) {
|
|
36946
36894
|
if (!token) return { ok: false, code: "NO_TOKEN" };
|
|
36947
36895
|
if (deps.isOwnerToken(token)) {
|
|
36948
36896
|
return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
|
|
36949
36897
|
}
|
|
36950
|
-
if (!deps.
|
|
36951
|
-
|
|
36952
|
-
|
|
36953
|
-
|
|
36954
|
-
|
|
36898
|
+
if (!deps.introspectConnectToken) return { ok: false, code: "BAD_TOKEN" };
|
|
36899
|
+
let result;
|
|
36900
|
+
try {
|
|
36901
|
+
result = await deps.introspectConnectToken(token);
|
|
36902
|
+
} catch {
|
|
36903
|
+
return { ok: false, code: "BAD_TOKEN" };
|
|
36955
36904
|
}
|
|
36956
|
-
|
|
36905
|
+
if (!result.active) {
|
|
36906
|
+
return { ok: false, code: "TOKEN_REVOKED" };
|
|
36907
|
+
}
|
|
36908
|
+
return { ok: true, context: connectGuestContext(result.unionId, result.displayName) };
|
|
36957
36909
|
}
|
|
36958
36910
|
|
|
36959
36911
|
// src/permission/capability-store.ts
|
|
@@ -37099,8 +37051,8 @@ var CapabilityManager = class {
|
|
|
37099
37051
|
list() {
|
|
37100
37052
|
return this.registry.list();
|
|
37101
37053
|
}
|
|
37102
|
-
//
|
|
37103
|
-
// 返回 Capability
|
|
37054
|
+
// 调试 / 旧 per-peer cap 反查用(Phase 3 决策 #14 后无生产调用方,保留属三件套范围)。
|
|
37055
|
+
// 返回 Capability 持久化形态;调用方需 stripSecretHash 后再上 wire。
|
|
37104
37056
|
findById(id) {
|
|
37105
37057
|
return this.registry.findById(id);
|
|
37106
37058
|
}
|
|
@@ -37133,89 +37085,14 @@ function cleanupGuestSessionsForCapability(cap, factory) {
|
|
|
37133
37085
|
return { removed };
|
|
37134
37086
|
}
|
|
37135
37087
|
|
|
37136
|
-
// src/permission/personal-capability.ts
|
|
37137
|
-
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
37138
|
-
var import_node_path16 = __toESM(require("path"), 1);
|
|
37139
|
-
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
37140
|
-
var PERSONAL_CAP_FILE_NAME = "personal-capability.json";
|
|
37141
|
-
var PERSONAL_CAP_DISPLAY_NAME = "personal";
|
|
37142
|
-
function personalCapFilePath(dataDir) {
|
|
37143
|
-
return import_node_path16.default.join(dataDir, PERSONAL_CAP_FILE_NAME);
|
|
37144
|
-
}
|
|
37145
|
-
function loadOrCreatePersonalCapability(opts) {
|
|
37146
|
-
const file = personalCapFilePath(opts.dataDir);
|
|
37147
|
-
const generateToken = opts.generateToken ?? defaultGenerateToken;
|
|
37148
|
-
const generateId = opts.generateId ?? defaultGenerateId;
|
|
37149
|
-
const now = opts.now ?? Date.now;
|
|
37150
|
-
const existing = readFile(file);
|
|
37151
|
-
if (existing) return existing;
|
|
37152
|
-
const token = generateToken();
|
|
37153
|
-
const id = generateId();
|
|
37154
|
-
const capability = {
|
|
37155
|
-
id,
|
|
37156
|
-
secretHash: sha256Hex2(token),
|
|
37157
|
-
displayName: PERSONAL_CAP_DISPLAY_NAME,
|
|
37158
|
-
// CapabilitySchema 期待 mutable Grant[],protocol 常量是 readonly,落盘前拷一份
|
|
37159
|
-
grants: PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
37160
|
-
resource: { ...g2.resource },
|
|
37161
|
-
actions: [...g2.actions]
|
|
37162
|
-
})),
|
|
37163
|
-
issuedAt: now(),
|
|
37164
|
-
usedCount: 0
|
|
37165
|
-
};
|
|
37166
|
-
const content = { token, capability };
|
|
37167
|
-
writeFile(file, content);
|
|
37168
|
-
return content;
|
|
37169
|
-
}
|
|
37170
|
-
function readFile(file) {
|
|
37171
|
-
let raw;
|
|
37172
|
-
try {
|
|
37173
|
-
raw = import_node_fs15.default.readFileSync(file, "utf8");
|
|
37174
|
-
} catch (err) {
|
|
37175
|
-
const code = err?.code;
|
|
37176
|
-
if (code === "ENOENT") return null;
|
|
37177
|
-
return null;
|
|
37178
|
-
}
|
|
37179
|
-
let parsed;
|
|
37180
|
-
try {
|
|
37181
|
-
parsed = JSON.parse(raw);
|
|
37182
|
-
} catch {
|
|
37183
|
-
return null;
|
|
37184
|
-
}
|
|
37185
|
-
if (!parsed || typeof parsed !== "object") return null;
|
|
37186
|
-
const obj = parsed;
|
|
37187
|
-
if (typeof obj.token !== "string" || obj.token.length === 0) return null;
|
|
37188
|
-
const capParsed = CapabilitySchema.safeParse(obj.capability);
|
|
37189
|
-
if (!capParsed.success) return null;
|
|
37190
|
-
if (sha256Hex2(obj.token) !== capParsed.data.secretHash) return null;
|
|
37191
|
-
return { token: obj.token, capability: capParsed.data };
|
|
37192
|
-
}
|
|
37193
|
-
function writeFile(file, content) {
|
|
37194
|
-
import_node_fs15.default.mkdirSync(import_node_path16.default.dirname(file), { recursive: true });
|
|
37195
|
-
import_node_fs15.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
37196
|
-
try {
|
|
37197
|
-
import_node_fs15.default.chmodSync(file, 384);
|
|
37198
|
-
} catch {
|
|
37199
|
-
}
|
|
37200
|
-
}
|
|
37201
|
-
function defaultGenerateToken() {
|
|
37202
|
-
return import_node_crypto4.default.randomBytes(24).toString("base64url");
|
|
37203
|
-
}
|
|
37204
|
-
function defaultGenerateId() {
|
|
37205
|
-
return "personal_" + import_node_crypto4.default.randomBytes(6).toString("base64url");
|
|
37206
|
-
}
|
|
37207
|
-
function sha256Hex2(s) {
|
|
37208
|
-
return import_node_crypto4.default.createHash("sha256").update(s).digest("hex");
|
|
37209
|
-
}
|
|
37210
|
-
|
|
37211
37088
|
// src/inbox/inbox-store.ts
|
|
37212
|
-
var
|
|
37213
|
-
var
|
|
37089
|
+
var fs19 = __toESM(require("fs"), 1);
|
|
37090
|
+
var path21 = __toESM(require("path"), 1);
|
|
37214
37091
|
var INBOX_SUBDIR = "inbox";
|
|
37215
37092
|
var InboxStore = class {
|
|
37216
37093
|
constructor(dataDir) {
|
|
37217
37094
|
this.dataDir = dataDir;
|
|
37218
|
-
|
|
37095
|
+
fs19.mkdirSync(this.dirPath(), { recursive: true });
|
|
37219
37096
|
}
|
|
37220
37097
|
dataDir;
|
|
37221
37098
|
/**
|
|
@@ -37227,7 +37104,7 @@ var InboxStore = class {
|
|
|
37227
37104
|
const file = this.filePath(peerOwnerId);
|
|
37228
37105
|
let raw;
|
|
37229
37106
|
try {
|
|
37230
|
-
raw =
|
|
37107
|
+
raw = fs19.readFileSync(file, "utf8");
|
|
37231
37108
|
} catch (err) {
|
|
37232
37109
|
if (err?.code === "ENOENT") return [];
|
|
37233
37110
|
return [];
|
|
@@ -37243,7 +37120,7 @@ var InboxStore = class {
|
|
|
37243
37120
|
const dir = this.dirPath();
|
|
37244
37121
|
let entries;
|
|
37245
37122
|
try {
|
|
37246
|
-
entries =
|
|
37123
|
+
entries = fs19.readdirSync(dir);
|
|
37247
37124
|
} catch (err) {
|
|
37248
37125
|
if (err?.code === "ENOENT") return [];
|
|
37249
37126
|
return [];
|
|
@@ -37259,9 +37136,9 @@ var InboxStore = class {
|
|
|
37259
37136
|
if (existing.some((m2) => m2.id === message.id)) return;
|
|
37260
37137
|
const file = this.filePath(message.peerOwnerId);
|
|
37261
37138
|
const line = JSON.stringify(message) + "\n";
|
|
37262
|
-
|
|
37139
|
+
fs19.appendFileSync(file, line, { mode: 384 });
|
|
37263
37140
|
try {
|
|
37264
|
-
|
|
37141
|
+
fs19.chmodSync(file, 384);
|
|
37265
37142
|
} catch {
|
|
37266
37143
|
}
|
|
37267
37144
|
}
|
|
@@ -37291,7 +37168,7 @@ var InboxStore = class {
|
|
|
37291
37168
|
removeByPeerOwnerId(peerOwnerId) {
|
|
37292
37169
|
const file = this.filePath(peerOwnerId);
|
|
37293
37170
|
try {
|
|
37294
|
-
|
|
37171
|
+
fs19.unlinkSync(file);
|
|
37295
37172
|
} catch (err) {
|
|
37296
37173
|
if (err?.code === "ENOENT") return;
|
|
37297
37174
|
}
|
|
@@ -37300,18 +37177,18 @@ var InboxStore = class {
|
|
|
37300
37177
|
const file = this.filePath(peerOwnerId);
|
|
37301
37178
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
37302
37179
|
const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
|
|
37303
|
-
|
|
37304
|
-
|
|
37180
|
+
fs19.writeFileSync(tmp, content, { mode: 384 });
|
|
37181
|
+
fs19.renameSync(tmp, file);
|
|
37305
37182
|
try {
|
|
37306
|
-
|
|
37183
|
+
fs19.chmodSync(file, 384);
|
|
37307
37184
|
} catch {
|
|
37308
37185
|
}
|
|
37309
37186
|
}
|
|
37310
37187
|
dirPath() {
|
|
37311
|
-
return
|
|
37188
|
+
return path21.join(this.dataDir, INBOX_SUBDIR);
|
|
37312
37189
|
}
|
|
37313
37190
|
filePath(peerOwnerId) {
|
|
37314
|
-
return
|
|
37191
|
+
return path21.join(this.dirPath(), `${peerOwnerId}.jsonl`);
|
|
37315
37192
|
}
|
|
37316
37193
|
};
|
|
37317
37194
|
function parseAllLines(raw) {
|
|
@@ -37397,22 +37274,22 @@ var InboxManager = class {
|
|
|
37397
37274
|
}
|
|
37398
37275
|
};
|
|
37399
37276
|
|
|
37400
|
-
// src/state/
|
|
37401
|
-
var
|
|
37402
|
-
var
|
|
37403
|
-
var FILE_NAME = "
|
|
37404
|
-
var
|
|
37277
|
+
// src/state/contact-store.ts
|
|
37278
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
37279
|
+
var path22 = __toESM(require("path"), 1);
|
|
37280
|
+
var FILE_NAME = "contacts.json";
|
|
37281
|
+
var ContactStore = class {
|
|
37405
37282
|
constructor(dataDir) {
|
|
37406
37283
|
this.dataDir = dataDir;
|
|
37407
37284
|
}
|
|
37408
37285
|
dataDir;
|
|
37409
|
-
|
|
37286
|
+
contacts = /* @__PURE__ */ new Map();
|
|
37410
37287
|
load() {
|
|
37411
|
-
this.
|
|
37412
|
-
const file =
|
|
37288
|
+
this.contacts.clear();
|
|
37289
|
+
const file = path22.join(this.dataDir, FILE_NAME);
|
|
37413
37290
|
let raw;
|
|
37414
37291
|
try {
|
|
37415
|
-
raw =
|
|
37292
|
+
raw = fs20.readFileSync(file, "utf8");
|
|
37416
37293
|
} catch (err) {
|
|
37417
37294
|
if (err?.code !== "ENOENT") this.renameBak(file);
|
|
37418
37295
|
return;
|
|
@@ -37424,52 +37301,52 @@ var ReceivedCapabilityStore = class {
|
|
|
37424
37301
|
this.renameBak(file);
|
|
37425
37302
|
return;
|
|
37426
37303
|
}
|
|
37427
|
-
const arr = parsed?.
|
|
37304
|
+
const arr = parsed?.contacts;
|
|
37428
37305
|
if (!Array.isArray(arr)) return;
|
|
37429
37306
|
for (const entry of arr) {
|
|
37430
|
-
const r =
|
|
37431
|
-
if (r.success) this.
|
|
37307
|
+
const r = ContactSchema.safeParse(entry);
|
|
37308
|
+
if (r.success) this.contacts.set(r.data.principalId, r.data);
|
|
37432
37309
|
}
|
|
37433
37310
|
}
|
|
37434
37311
|
list() {
|
|
37435
|
-
return Array.from(this.
|
|
37312
|
+
return Array.from(this.contacts.values());
|
|
37436
37313
|
}
|
|
37437
|
-
get(
|
|
37438
|
-
return this.
|
|
37314
|
+
get(principalId) {
|
|
37315
|
+
return this.contacts.get(principalId) ?? null;
|
|
37439
37316
|
}
|
|
37440
|
-
|
|
37441
|
-
return this.
|
|
37317
|
+
has(principalId) {
|
|
37318
|
+
return this.contacts.has(principalId);
|
|
37442
37319
|
}
|
|
37443
|
-
upsert(
|
|
37444
|
-
this.
|
|
37320
|
+
upsert(contact) {
|
|
37321
|
+
this.contacts.set(contact.principalId, contact);
|
|
37445
37322
|
this.flush();
|
|
37446
37323
|
}
|
|
37447
|
-
remove(
|
|
37448
|
-
if (!this.
|
|
37324
|
+
remove(principalId) {
|
|
37325
|
+
if (!this.contacts.delete(principalId)) return;
|
|
37449
37326
|
this.flush();
|
|
37450
37327
|
}
|
|
37451
37328
|
flush() {
|
|
37452
|
-
const file =
|
|
37329
|
+
const file = path22.join(this.dataDir, FILE_NAME);
|
|
37453
37330
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
37454
37331
|
const content = JSON.stringify(
|
|
37455
|
-
{
|
|
37332
|
+
{ contacts: Array.from(this.contacts.values()) },
|
|
37456
37333
|
null,
|
|
37457
37334
|
2
|
|
37458
37335
|
);
|
|
37459
|
-
|
|
37460
|
-
|
|
37461
|
-
|
|
37336
|
+
fs20.mkdirSync(this.dataDir, { recursive: true });
|
|
37337
|
+
fs20.writeFileSync(tmp, content, { mode: 384 });
|
|
37338
|
+
fs20.renameSync(tmp, file);
|
|
37462
37339
|
}
|
|
37463
37340
|
renameBak(file) {
|
|
37464
37341
|
try {
|
|
37465
|
-
|
|
37342
|
+
fs20.renameSync(file, `${file}.bak`);
|
|
37466
37343
|
} catch {
|
|
37467
37344
|
}
|
|
37468
37345
|
}
|
|
37469
37346
|
};
|
|
37470
37347
|
|
|
37471
|
-
// src/
|
|
37472
|
-
var
|
|
37348
|
+
// src/contact/connect-remote.ts
|
|
37349
|
+
var crypto5 = __toESM(require("crypto"), 1);
|
|
37473
37350
|
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
37474
37351
|
var RPC_TIMEOUT_MS = 5e3;
|
|
37475
37352
|
async function connectRemote(args) {
|
|
@@ -37487,7 +37364,10 @@ async function connectRemote(args) {
|
|
|
37487
37364
|
type: "auth",
|
|
37488
37365
|
token: args.token,
|
|
37489
37366
|
selfPrincipalId: args.selfPrincipalId,
|
|
37490
|
-
selfDisplayName: args.selfDisplayName
|
|
37367
|
+
selfDisplayName: args.selfDisplayName,
|
|
37368
|
+
// Phase 2 自动反向(spec 决策 #11):带本机可达地址时 hub 反向建联系人;
|
|
37369
|
+
// 缺省时省略字段(不发空串),hub 端 schema selfUrl 是 .min(1).optional()
|
|
37370
|
+
...args.selfUrl ? { selfUrl: args.selfUrl } : {}
|
|
37491
37371
|
})
|
|
37492
37372
|
);
|
|
37493
37373
|
await new Promise((resolve6, reject) => {
|
|
@@ -37515,7 +37395,7 @@ async function connectRemote(args) {
|
|
|
37515
37395
|
}, HANDSHAKE_TIMEOUT_MS);
|
|
37516
37396
|
});
|
|
37517
37397
|
function call(method, payload) {
|
|
37518
|
-
const requestId =
|
|
37398
|
+
const requestId = crypto5.randomUUID();
|
|
37519
37399
|
return new Promise((resolve6, reject) => {
|
|
37520
37400
|
const onMessage = (raw) => {
|
|
37521
37401
|
let f;
|
|
@@ -37546,7 +37426,6 @@ async function connectRemote(args) {
|
|
|
37546
37426
|
return {
|
|
37547
37427
|
ownerId: f.owner.id,
|
|
37548
37428
|
displayName: f.owner.displayName,
|
|
37549
|
-
capabilityId: f.capability.id,
|
|
37550
37429
|
grants: f.capability.grants
|
|
37551
37430
|
};
|
|
37552
37431
|
},
|
|
@@ -37559,62 +37438,83 @@ async function connectRemote(args) {
|
|
|
37559
37438
|
};
|
|
37560
37439
|
}
|
|
37561
37440
|
|
|
37441
|
+
// src/contact/auto-reverse.ts
|
|
37442
|
+
init_protocol();
|
|
37443
|
+
function autoReverseContact(args) {
|
|
37444
|
+
if (!args.selfUrl) return;
|
|
37445
|
+
const now = args.now ?? Date.now;
|
|
37446
|
+
const contact = {
|
|
37447
|
+
principalId: args.unionId,
|
|
37448
|
+
displayName: args.displayName,
|
|
37449
|
+
remoteUrl: args.selfUrl,
|
|
37450
|
+
// B 不持有 A 颁的 token:空串。schema 已放宽允许空(不造假数据)。
|
|
37451
|
+
connectToken: "",
|
|
37452
|
+
grants: PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
37453
|
+
resource: { ...g2.resource },
|
|
37454
|
+
actions: [...g2.actions]
|
|
37455
|
+
})),
|
|
37456
|
+
addedAt: now()
|
|
37457
|
+
};
|
|
37458
|
+
args.store.upsert(contact);
|
|
37459
|
+
args.broadcast({ type: "contact:added", contact });
|
|
37460
|
+
}
|
|
37461
|
+
|
|
37562
37462
|
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
37563
|
-
var
|
|
37564
|
-
var
|
|
37463
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
37464
|
+
var path23 = __toESM(require("path"), 1);
|
|
37565
37465
|
var MIGRATION_FLAG_NAME = ".migration.v1.done";
|
|
37566
37466
|
function migrateFlattenSessions(opts) {
|
|
37567
37467
|
const dataDir = opts.dataDir;
|
|
37568
37468
|
const now = opts.now ?? Date.now;
|
|
37569
|
-
const sessionsDir =
|
|
37570
|
-
const flagPath =
|
|
37469
|
+
const sessionsDir = path23.join(dataDir, "sessions");
|
|
37470
|
+
const flagPath = path23.join(sessionsDir, MIGRATION_FLAG_NAME);
|
|
37571
37471
|
if (existsSync3(flagPath)) {
|
|
37572
37472
|
return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
|
|
37573
37473
|
}
|
|
37574
37474
|
let movedBare = 0;
|
|
37575
37475
|
let movedVmOwner = 0;
|
|
37576
37476
|
let archivedListener = 0;
|
|
37577
|
-
const defaultDir =
|
|
37477
|
+
const defaultDir = path23.join(sessionsDir, "default");
|
|
37578
37478
|
if (existsSync3(defaultDir)) {
|
|
37579
37479
|
for (const entry of readdirSafe(defaultDir)) {
|
|
37580
37480
|
if (!entry.endsWith(".json")) continue;
|
|
37581
|
-
const src =
|
|
37582
|
-
const dst =
|
|
37583
|
-
|
|
37481
|
+
const src = path23.join(defaultDir, entry);
|
|
37482
|
+
const dst = path23.join(sessionsDir, entry);
|
|
37483
|
+
fs21.renameSync(src, dst);
|
|
37584
37484
|
movedBare += 1;
|
|
37585
37485
|
}
|
|
37586
37486
|
rmdirIfEmpty(defaultDir);
|
|
37587
37487
|
}
|
|
37588
37488
|
for (const pid of readdirSafe(sessionsDir)) {
|
|
37589
|
-
const personaDir =
|
|
37489
|
+
const personaDir = path23.join(sessionsDir, pid);
|
|
37590
37490
|
if (!isDir(personaDir)) continue;
|
|
37591
37491
|
if (pid === "default") continue;
|
|
37592
|
-
const ownerSrc =
|
|
37492
|
+
const ownerSrc = path23.join(personaDir, "owner");
|
|
37593
37493
|
if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
|
|
37594
|
-
const ownerDst =
|
|
37595
|
-
|
|
37494
|
+
const ownerDst = path23.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
|
|
37495
|
+
fs21.mkdirSync(ownerDst, { recursive: true });
|
|
37596
37496
|
for (const file of readdirSafe(ownerSrc)) {
|
|
37597
37497
|
if (!file.endsWith(".json")) continue;
|
|
37598
|
-
|
|
37498
|
+
fs21.renameSync(path23.join(ownerSrc, file), path23.join(ownerDst, file));
|
|
37599
37499
|
movedVmOwner += 1;
|
|
37600
37500
|
}
|
|
37601
37501
|
rmdirIfEmpty(ownerSrc);
|
|
37602
37502
|
}
|
|
37603
|
-
const listenerSrc =
|
|
37503
|
+
const listenerSrc = path23.join(personaDir, "listener");
|
|
37604
37504
|
if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
|
|
37605
|
-
const archiveDst =
|
|
37606
|
-
|
|
37505
|
+
const archiveDst = path23.join(dataDir, ".legacy", `listener-${pid}`);
|
|
37506
|
+
fs21.mkdirSync(archiveDst, { recursive: true });
|
|
37607
37507
|
for (const file of readdirSafe(listenerSrc)) {
|
|
37608
37508
|
if (!file.endsWith(".json")) continue;
|
|
37609
|
-
|
|
37509
|
+
fs21.renameSync(path23.join(listenerSrc, file), path23.join(archiveDst, file));
|
|
37610
37510
|
archivedListener += 1;
|
|
37611
37511
|
}
|
|
37612
37512
|
rmdirIfEmpty(listenerSrc);
|
|
37613
37513
|
}
|
|
37614
37514
|
rmdirIfEmpty(personaDir);
|
|
37615
37515
|
}
|
|
37616
|
-
|
|
37617
|
-
|
|
37516
|
+
fs21.mkdirSync(sessionsDir, { recursive: true });
|
|
37517
|
+
fs21.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
|
|
37618
37518
|
return {
|
|
37619
37519
|
skipped: false,
|
|
37620
37520
|
flagWritten: true,
|
|
@@ -37625,7 +37525,7 @@ function migrateFlattenSessions(opts) {
|
|
|
37625
37525
|
}
|
|
37626
37526
|
function existsSync3(p2) {
|
|
37627
37527
|
try {
|
|
37628
|
-
|
|
37528
|
+
fs21.statSync(p2);
|
|
37629
37529
|
return true;
|
|
37630
37530
|
} catch {
|
|
37631
37531
|
return false;
|
|
@@ -37633,31 +37533,31 @@ function existsSync3(p2) {
|
|
|
37633
37533
|
}
|
|
37634
37534
|
function isDir(p2) {
|
|
37635
37535
|
try {
|
|
37636
|
-
return
|
|
37536
|
+
return fs21.statSync(p2).isDirectory();
|
|
37637
37537
|
} catch {
|
|
37638
37538
|
return false;
|
|
37639
37539
|
}
|
|
37640
37540
|
}
|
|
37641
37541
|
function readdirSafe(p2) {
|
|
37642
37542
|
try {
|
|
37643
|
-
return
|
|
37543
|
+
return fs21.readdirSync(p2);
|
|
37644
37544
|
} catch {
|
|
37645
37545
|
return [];
|
|
37646
37546
|
}
|
|
37647
37547
|
}
|
|
37648
37548
|
function rmdirIfEmpty(p2) {
|
|
37649
37549
|
try {
|
|
37650
|
-
|
|
37550
|
+
fs21.rmdirSync(p2);
|
|
37651
37551
|
} catch {
|
|
37652
37552
|
}
|
|
37653
37553
|
}
|
|
37654
37554
|
|
|
37655
37555
|
// src/transport/http-router.ts
|
|
37656
|
-
var
|
|
37657
|
-
var
|
|
37556
|
+
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
37557
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
37658
37558
|
|
|
37659
37559
|
// src/attachment/mime.ts
|
|
37660
|
-
var
|
|
37560
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
37661
37561
|
var TEXT_PLAIN = "text/plain; charset=utf-8";
|
|
37662
37562
|
var EXT_TO_NATIVE_MIME = {
|
|
37663
37563
|
// 图片
|
|
@@ -37764,14 +37664,14 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
37764
37664
|
".mk"
|
|
37765
37665
|
]);
|
|
37766
37666
|
function lookupMime(filePathOrName) {
|
|
37767
|
-
const ext =
|
|
37667
|
+
const ext = import_node_path16.default.extname(filePathOrName).toLowerCase();
|
|
37768
37668
|
if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
|
|
37769
37669
|
if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
|
|
37770
37670
|
return "application/octet-stream";
|
|
37771
37671
|
}
|
|
37772
37672
|
|
|
37773
37673
|
// src/attachment/sign-url.ts
|
|
37774
|
-
var
|
|
37674
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
37775
37675
|
var HMAC_ALGO = "sha256";
|
|
37776
37676
|
function base64urlEncode(buf) {
|
|
37777
37677
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -37788,7 +37688,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
37788
37688
|
}
|
|
37789
37689
|
function computeSig(secret, absPath, e) {
|
|
37790
37690
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
37791
|
-
return
|
|
37691
|
+
return import_node_crypto4.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
37792
37692
|
}
|
|
37793
37693
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
37794
37694
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -37823,7 +37723,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
37823
37723
|
if (provided.length !== expected.length) {
|
|
37824
37724
|
return { ok: false, code: "BAD_SIG" };
|
|
37825
37725
|
}
|
|
37826
|
-
if (!
|
|
37726
|
+
if (!import_node_crypto4.default.timingSafeEqual(provided, expected)) {
|
|
37827
37727
|
return { ok: false, code: "BAD_SIG" };
|
|
37828
37728
|
}
|
|
37829
37729
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -37833,9 +37733,9 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
37833
37733
|
}
|
|
37834
37734
|
|
|
37835
37735
|
// src/attachment/upload.ts
|
|
37836
|
-
var
|
|
37837
|
-
var
|
|
37838
|
-
var
|
|
37736
|
+
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
37737
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
37738
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
37839
37739
|
var import_promises = require("stream/promises");
|
|
37840
37740
|
var UploadError = class extends Error {
|
|
37841
37741
|
constructor(code, message) {
|
|
@@ -37846,24 +37746,24 @@ var UploadError = class extends Error {
|
|
|
37846
37746
|
code;
|
|
37847
37747
|
};
|
|
37848
37748
|
function assertValidFileName(fileName) {
|
|
37849
|
-
if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !==
|
|
37749
|
+
if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path17.default.basename(fileName)) {
|
|
37850
37750
|
throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
|
|
37851
37751
|
}
|
|
37852
37752
|
}
|
|
37853
37753
|
var HASH_PREFIX_LEN = 16;
|
|
37854
37754
|
async function writeUploadedAttachment(args) {
|
|
37855
37755
|
assertValidFileName(args.fileName);
|
|
37856
|
-
const attachmentsRoot =
|
|
37756
|
+
const attachmentsRoot = import_node_path17.default.join(args.sessionDir, ".attachments");
|
|
37857
37757
|
try {
|
|
37858
|
-
|
|
37758
|
+
import_node_fs15.default.mkdirSync(attachmentsRoot, { recursive: true });
|
|
37859
37759
|
} catch (err) {
|
|
37860
37760
|
throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
|
|
37861
37761
|
}
|
|
37862
|
-
const hasher =
|
|
37762
|
+
const hasher = import_node_crypto5.default.createHash("sha256");
|
|
37863
37763
|
let actualSize = 0;
|
|
37864
|
-
const tmpPath =
|
|
37764
|
+
const tmpPath = import_node_path17.default.join(
|
|
37865
37765
|
attachmentsRoot,
|
|
37866
|
-
`.upload-${process.pid}-${Date.now()}-${
|
|
37766
|
+
`.upload-${process.pid}-${Date.now()}-${import_node_crypto5.default.randomBytes(4).toString("hex")}`
|
|
37867
37767
|
);
|
|
37868
37768
|
try {
|
|
37869
37769
|
await (0, import_promises.pipeline)(
|
|
@@ -37876,18 +37776,18 @@ async function writeUploadedAttachment(args) {
|
|
|
37876
37776
|
yield buf;
|
|
37877
37777
|
}
|
|
37878
37778
|
},
|
|
37879
|
-
|
|
37779
|
+
import_node_fs15.default.createWriteStream(tmpPath, { mode: 384 })
|
|
37880
37780
|
);
|
|
37881
37781
|
} catch (err) {
|
|
37882
37782
|
try {
|
|
37883
|
-
|
|
37783
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
37884
37784
|
} catch {
|
|
37885
37785
|
}
|
|
37886
37786
|
throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
|
|
37887
37787
|
}
|
|
37888
37788
|
if (actualSize !== args.contentLength) {
|
|
37889
37789
|
try {
|
|
37890
|
-
|
|
37790
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
37891
37791
|
} catch {
|
|
37892
37792
|
}
|
|
37893
37793
|
throw new UploadError(
|
|
@@ -37896,35 +37796,35 @@ async function writeUploadedAttachment(args) {
|
|
|
37896
37796
|
);
|
|
37897
37797
|
}
|
|
37898
37798
|
const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
|
|
37899
|
-
const hashDir =
|
|
37799
|
+
const hashDir = import_node_path17.default.join(attachmentsRoot, attachmentId);
|
|
37900
37800
|
let finalFileName;
|
|
37901
37801
|
let hashDirExists = false;
|
|
37902
37802
|
try {
|
|
37903
|
-
hashDirExists =
|
|
37803
|
+
hashDirExists = import_node_fs15.default.statSync(hashDir).isDirectory();
|
|
37904
37804
|
} catch {
|
|
37905
37805
|
}
|
|
37906
37806
|
if (hashDirExists) {
|
|
37907
|
-
const existing =
|
|
37807
|
+
const existing = import_node_fs15.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
|
|
37908
37808
|
finalFileName = existing[0] ?? args.fileName;
|
|
37909
37809
|
try {
|
|
37910
|
-
|
|
37810
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
37911
37811
|
} catch {
|
|
37912
37812
|
}
|
|
37913
37813
|
} else {
|
|
37914
37814
|
try {
|
|
37915
|
-
|
|
37815
|
+
import_node_fs15.default.mkdirSync(hashDir, { recursive: true });
|
|
37916
37816
|
finalFileName = args.fileName;
|
|
37917
|
-
|
|
37817
|
+
import_node_fs15.default.renameSync(tmpPath, import_node_path17.default.join(hashDir, finalFileName));
|
|
37918
37818
|
} catch (err) {
|
|
37919
37819
|
try {
|
|
37920
|
-
|
|
37820
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
37921
37821
|
} catch {
|
|
37922
37822
|
}
|
|
37923
37823
|
throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
|
|
37924
37824
|
}
|
|
37925
37825
|
}
|
|
37926
|
-
const absPath =
|
|
37927
|
-
const relPath =
|
|
37826
|
+
const absPath = import_node_path17.default.join(hashDir, finalFileName);
|
|
37827
|
+
const relPath = import_node_path17.default.relative(args.sessionDir, absPath);
|
|
37928
37828
|
args.groupFileStore.upsert(args.scope, args.sessionId, {
|
|
37929
37829
|
relPath: absPath,
|
|
37930
37830
|
// 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
|
|
@@ -37938,7 +37838,7 @@ async function writeUploadedAttachment(args) {
|
|
|
37938
37838
|
|
|
37939
37839
|
// src/extension/import.ts
|
|
37940
37840
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
37941
|
-
var
|
|
37841
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
37942
37842
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
37943
37843
|
var import_jszip = __toESM(require_lib3(), 1);
|
|
37944
37844
|
var ImportError = class extends Error {
|
|
@@ -37956,7 +37856,7 @@ async function importZip(buf, root) {
|
|
|
37956
37856
|
throw new ImportError("ZIP_INVALID", `failed to load zip: ${e.message}`);
|
|
37957
37857
|
}
|
|
37958
37858
|
for (const name of Object.keys(zip.files)) {
|
|
37959
|
-
if (name.includes("..") || name.startsWith("/") ||
|
|
37859
|
+
if (name.includes("..") || name.startsWith("/") || import_node_path18.default.isAbsolute(name)) {
|
|
37960
37860
|
throw new ImportError("ZIP_INVALID", `unsafe zip entry path: ${name}`);
|
|
37961
37861
|
}
|
|
37962
37862
|
}
|
|
@@ -37989,7 +37889,7 @@ async function importZip(buf, root) {
|
|
|
37989
37889
|
);
|
|
37990
37890
|
}
|
|
37991
37891
|
}
|
|
37992
|
-
const destDir =
|
|
37892
|
+
const destDir = import_node_path18.default.join(root, manifest.id);
|
|
37993
37893
|
let destExists = false;
|
|
37994
37894
|
try {
|
|
37995
37895
|
await import_promises2.default.access(destDir);
|
|
@@ -37999,15 +37899,15 @@ async function importZip(buf, root) {
|
|
|
37999
37899
|
if (destExists) {
|
|
38000
37900
|
throw new ImportError("ALREADY_EXISTS", `extension ${manifest.id} already installed`);
|
|
38001
37901
|
}
|
|
38002
|
-
const stage = await import_promises2.default.mkdtemp(
|
|
37902
|
+
const stage = await import_promises2.default.mkdtemp(import_node_path18.default.join(import_node_os10.default.tmpdir(), `clawd-ext-stage-${manifest.id}-`));
|
|
38003
37903
|
try {
|
|
38004
37904
|
for (const [name, entry] of Object.entries(zip.files)) {
|
|
38005
|
-
const dest =
|
|
37905
|
+
const dest = import_node_path18.default.join(stage, name);
|
|
38006
37906
|
if (entry.dir) {
|
|
38007
37907
|
await import_promises2.default.mkdir(dest, { recursive: true });
|
|
38008
37908
|
continue;
|
|
38009
37909
|
}
|
|
38010
|
-
await import_promises2.default.mkdir(
|
|
37910
|
+
await import_promises2.default.mkdir(import_node_path18.default.dirname(dest), { recursive: true });
|
|
38011
37911
|
const content = await entry.async("nodebuffer");
|
|
38012
37912
|
await import_promises2.default.writeFile(dest, content);
|
|
38013
37913
|
}
|
|
@@ -38053,7 +37953,7 @@ function isValidUploadFileName(fileName) {
|
|
|
38053
37953
|
if (fileName === "." || fileName === "..") return false;
|
|
38054
37954
|
if (fileName.startsWith(".")) return false;
|
|
38055
37955
|
if (fileName.includes("/") || fileName.includes("\\")) return false;
|
|
38056
|
-
return fileName ===
|
|
37956
|
+
return fileName === import_node_path19.default.basename(fileName);
|
|
38057
37957
|
}
|
|
38058
37958
|
function createHttpRouter(deps) {
|
|
38059
37959
|
return async (req, res) => {
|
|
@@ -38068,13 +37968,23 @@ function createHttpRouter(deps) {
|
|
|
38068
37968
|
});
|
|
38069
37969
|
return true;
|
|
38070
37970
|
}
|
|
38071
|
-
if (url.pathname
|
|
38072
|
-
|
|
38073
|
-
|
|
38074
|
-
await proxyPreviewHttp(req, res, port);
|
|
37971
|
+
if (url.pathname === "/auth/callback") {
|
|
37972
|
+
if (req.method !== "GET") {
|
|
37973
|
+
sendJson(res, 404, { code: "NOT_FOUND", message: `no route for ${req.method} ${url.pathname}` });
|
|
38075
37974
|
return true;
|
|
38076
37975
|
}
|
|
38077
|
-
|
|
37976
|
+
if (!deps.feishuLogin) {
|
|
37977
|
+
sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "feishu login not wired" });
|
|
37978
|
+
return true;
|
|
37979
|
+
}
|
|
37980
|
+
const result = await deps.feishuLogin.handleLoginCallback({
|
|
37981
|
+
token: url.searchParams.get("token") ?? "",
|
|
37982
|
+
state: url.searchParams.get("state") ?? "",
|
|
37983
|
+
expiresAt: url.searchParams.get("expires_at")
|
|
37984
|
+
});
|
|
37985
|
+
const html = result.ok ? `<!doctype html><meta charset="utf-8"><title>clawd</title><style>body{font-family:-apple-system,sans-serif;padding:48px;color:#222}</style><h2>\u767B\u5F55\u6210\u529F\uFF0C\u53EF\u5173\u95ED\u6B64\u9875\u3002</h2><p>\u8EAB\u4EFD\uFF1A${escapeHtml(result.identity.displayName)}</p><script>setTimeout(()=>window.close(),1500)</script>` : `<!doctype html><meta charset="utf-8"><title>clawd</title><style>body{font-family:-apple-system,sans-serif;padding:48px;color:#922}</style><h2>\u767B\u5F55\u5931\u8D25</h2><p>${escapeHtml(result.reason)}</p>`;
|
|
37986
|
+
res.writeHead(result.ok ? 200 : 400, { "Content-Type": "text/html; charset=utf-8" });
|
|
37987
|
+
res.end(html);
|
|
38078
37988
|
return true;
|
|
38079
37989
|
}
|
|
38080
37990
|
if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload" && url.pathname !== "/extensions/import") {
|
|
@@ -38237,7 +38147,7 @@ function createHttpRouter(deps) {
|
|
|
38237
38147
|
return true;
|
|
38238
38148
|
}
|
|
38239
38149
|
let absPath;
|
|
38240
|
-
if (
|
|
38150
|
+
if (import_node_path19.default.isAbsolute(pathParam)) {
|
|
38241
38151
|
absPath = pathParam;
|
|
38242
38152
|
} else if (deps.sessionStore) {
|
|
38243
38153
|
const file = deps.sessionStore.read(sid);
|
|
@@ -38245,7 +38155,7 @@ function createHttpRouter(deps) {
|
|
|
38245
38155
|
sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
|
|
38246
38156
|
return true;
|
|
38247
38157
|
}
|
|
38248
|
-
absPath =
|
|
38158
|
+
absPath = import_node_path19.default.join(file.cwd, pathParam);
|
|
38249
38159
|
} else {
|
|
38250
38160
|
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
|
|
38251
38161
|
return true;
|
|
@@ -38312,6 +38222,9 @@ function parseUrl(rawUrl) {
|
|
|
38312
38222
|
return null;
|
|
38313
38223
|
}
|
|
38314
38224
|
}
|
|
38225
|
+
function escapeHtml(s) {
|
|
38226
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
38227
|
+
}
|
|
38315
38228
|
function sendJson(res, status, body, extraHeaders) {
|
|
38316
38229
|
res.writeHead(status, {
|
|
38317
38230
|
"Content-Type": "application/json; charset=utf-8",
|
|
@@ -38325,7 +38238,7 @@ function withCtx(ctx, body) {
|
|
|
38325
38238
|
function streamFile(res, absPath, logger) {
|
|
38326
38239
|
let stat;
|
|
38327
38240
|
try {
|
|
38328
|
-
stat =
|
|
38241
|
+
stat = import_node_fs16.default.statSync(absPath);
|
|
38329
38242
|
} catch (err) {
|
|
38330
38243
|
const code = err?.code;
|
|
38331
38244
|
if (code === "ENOENT") {
|
|
@@ -38340,7 +38253,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38340
38253
|
return;
|
|
38341
38254
|
}
|
|
38342
38255
|
const mime = lookupMime(absPath);
|
|
38343
|
-
const basename =
|
|
38256
|
+
const basename = import_node_path19.default.basename(absPath);
|
|
38344
38257
|
res.writeHead(200, {
|
|
38345
38258
|
"Content-Type": mime,
|
|
38346
38259
|
"Content-Length": String(stat.size),
|
|
@@ -38348,7 +38261,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38348
38261
|
// 防止浏览器把任意 mime 当 html 渲染
|
|
38349
38262
|
"X-Content-Type-Options": "nosniff"
|
|
38350
38263
|
});
|
|
38351
|
-
const stream =
|
|
38264
|
+
const stream = import_node_fs16.default.createReadStream(absPath);
|
|
38352
38265
|
stream.on("error", (err) => {
|
|
38353
38266
|
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
38354
38267
|
res.destroy();
|
|
@@ -38357,8 +38270,8 @@ function streamFile(res, absPath, logger) {
|
|
|
38357
38270
|
}
|
|
38358
38271
|
|
|
38359
38272
|
// src/attachment/gc.ts
|
|
38360
|
-
var
|
|
38361
|
-
var
|
|
38273
|
+
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
38274
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
38362
38275
|
var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
|
|
38363
38276
|
function runAttachmentGc(args) {
|
|
38364
38277
|
const now = (args.now ?? Date.now)();
|
|
@@ -38367,38 +38280,38 @@ function runAttachmentGc(args) {
|
|
|
38367
38280
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38368
38281
|
for (const entry of args.groupFileStore.list(scope, sessionId)) {
|
|
38369
38282
|
if (entry.stale) continue;
|
|
38370
|
-
if (
|
|
38283
|
+
if (import_node_path20.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
|
|
38371
38284
|
}
|
|
38372
38285
|
}
|
|
38373
38286
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38374
|
-
const sessionDir = args.getSessionCwd?.(sessionId) ??
|
|
38287
|
+
const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path20.default.join(
|
|
38375
38288
|
args.dataDir,
|
|
38376
38289
|
"sessions",
|
|
38377
38290
|
...scopeSubPath(scope).map(safeFileName),
|
|
38378
38291
|
safeFileName(sessionId)
|
|
38379
38292
|
);
|
|
38380
|
-
const attRoot =
|
|
38293
|
+
const attRoot = import_node_path20.default.join(sessionDir, ".attachments");
|
|
38381
38294
|
let hashDirs;
|
|
38382
38295
|
try {
|
|
38383
|
-
hashDirs =
|
|
38296
|
+
hashDirs = import_node_fs17.default.readdirSync(attRoot);
|
|
38384
38297
|
} catch (err) {
|
|
38385
38298
|
if (err.code === "ENOENT") continue;
|
|
38386
38299
|
args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
|
|
38387
38300
|
continue;
|
|
38388
38301
|
}
|
|
38389
38302
|
for (const hashDir of hashDirs) {
|
|
38390
|
-
const hashDirAbs =
|
|
38303
|
+
const hashDirAbs = import_node_path20.default.join(attRoot, hashDir);
|
|
38391
38304
|
let files;
|
|
38392
38305
|
try {
|
|
38393
|
-
files =
|
|
38306
|
+
files = import_node_fs17.default.readdirSync(hashDirAbs);
|
|
38394
38307
|
} catch {
|
|
38395
38308
|
continue;
|
|
38396
38309
|
}
|
|
38397
38310
|
for (const name of files) {
|
|
38398
|
-
const file =
|
|
38311
|
+
const file = import_node_path20.default.join(hashDirAbs, name);
|
|
38399
38312
|
let stat;
|
|
38400
38313
|
try {
|
|
38401
|
-
stat =
|
|
38314
|
+
stat = import_node_fs17.default.statSync(file);
|
|
38402
38315
|
} catch {
|
|
38403
38316
|
continue;
|
|
38404
38317
|
}
|
|
@@ -38407,27 +38320,27 @@ function runAttachmentGc(args) {
|
|
|
38407
38320
|
if (age < ttlMs) continue;
|
|
38408
38321
|
if (liveAbs.has(file)) continue;
|
|
38409
38322
|
try {
|
|
38410
|
-
|
|
38323
|
+
import_node_fs17.default.unlinkSync(file);
|
|
38411
38324
|
} catch (err) {
|
|
38412
38325
|
args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
|
|
38413
38326
|
}
|
|
38414
38327
|
}
|
|
38415
38328
|
try {
|
|
38416
|
-
if (
|
|
38329
|
+
if (import_node_fs17.default.readdirSync(hashDirAbs).length === 0) import_node_fs17.default.rmdirSync(hashDirAbs);
|
|
38417
38330
|
} catch {
|
|
38418
38331
|
}
|
|
38419
38332
|
}
|
|
38420
38333
|
try {
|
|
38421
|
-
if (
|
|
38334
|
+
if (import_node_fs17.default.readdirSync(attRoot).length === 0) import_node_fs17.default.rmdirSync(attRoot);
|
|
38422
38335
|
} catch {
|
|
38423
38336
|
}
|
|
38424
38337
|
}
|
|
38425
38338
|
}
|
|
38426
38339
|
|
|
38427
38340
|
// src/attachment/group.ts
|
|
38428
|
-
var
|
|
38429
|
-
var
|
|
38430
|
-
var
|
|
38341
|
+
var import_node_fs18 = __toESM(require("fs"), 1);
|
|
38342
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
38343
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
38431
38344
|
init_protocol();
|
|
38432
38345
|
var GroupFileStore = class {
|
|
38433
38346
|
dataDir;
|
|
@@ -38438,11 +38351,11 @@ var GroupFileStore = class {
|
|
|
38438
38351
|
this.logger = opts.logger;
|
|
38439
38352
|
}
|
|
38440
38353
|
rootForScope(scope) {
|
|
38441
|
-
return
|
|
38354
|
+
return import_node_path21.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
38442
38355
|
}
|
|
38443
38356
|
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
38444
38357
|
filePath(scope, sessionId) {
|
|
38445
|
-
return
|
|
38358
|
+
return import_node_path21.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
38446
38359
|
}
|
|
38447
38360
|
cacheKey(scope, sessionId) {
|
|
38448
38361
|
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
@@ -38451,7 +38364,7 @@ var GroupFileStore = class {
|
|
|
38451
38364
|
readFile(scope, sessionId) {
|
|
38452
38365
|
const file = this.filePath(scope, sessionId);
|
|
38453
38366
|
try {
|
|
38454
|
-
const raw =
|
|
38367
|
+
const raw = import_node_fs18.default.readFileSync(file, "utf8");
|
|
38455
38368
|
const parsed = JSON.parse(raw);
|
|
38456
38369
|
if (!Array.isArray(parsed)) {
|
|
38457
38370
|
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
@@ -38477,10 +38390,10 @@ var GroupFileStore = class {
|
|
|
38477
38390
|
}
|
|
38478
38391
|
writeFile(scope, sessionId, entries) {
|
|
38479
38392
|
const file = this.filePath(scope, sessionId);
|
|
38480
|
-
|
|
38393
|
+
import_node_fs18.default.mkdirSync(import_node_path21.default.dirname(file), { recursive: true });
|
|
38481
38394
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
38482
|
-
|
|
38483
|
-
|
|
38395
|
+
import_node_fs18.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
38396
|
+
import_node_fs18.default.renameSync(tmp, file);
|
|
38484
38397
|
}
|
|
38485
38398
|
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
38486
38399
|
list(scope, sessionId) {
|
|
@@ -38516,7 +38429,7 @@ var GroupFileStore = class {
|
|
|
38516
38429
|
entries[idx] = next;
|
|
38517
38430
|
} else {
|
|
38518
38431
|
next = {
|
|
38519
|
-
id: `gf-${
|
|
38432
|
+
id: `gf-${import_node_crypto6.default.randomBytes(6).toString("base64url")}`,
|
|
38520
38433
|
relPath: input.relPath,
|
|
38521
38434
|
from: input.from,
|
|
38522
38435
|
label: input.label,
|
|
@@ -38566,10 +38479,10 @@ var GroupFileStore = class {
|
|
|
38566
38479
|
};
|
|
38567
38480
|
|
|
38568
38481
|
// src/discovery/state-file.ts
|
|
38569
|
-
var
|
|
38570
|
-
var
|
|
38482
|
+
var import_node_fs19 = __toESM(require("fs"), 1);
|
|
38483
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
38571
38484
|
function defaultStateFilePath(dataDir) {
|
|
38572
|
-
return
|
|
38485
|
+
return import_node_path22.default.join(dataDir, "state.json");
|
|
38573
38486
|
}
|
|
38574
38487
|
function isPidAlive(pid) {
|
|
38575
38488
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
@@ -38591,7 +38504,7 @@ var StateFileManager = class {
|
|
|
38591
38504
|
}
|
|
38592
38505
|
read() {
|
|
38593
38506
|
try {
|
|
38594
|
-
const raw =
|
|
38507
|
+
const raw = import_node_fs19.default.readFileSync(this.file, "utf8");
|
|
38595
38508
|
const parsed = JSON.parse(raw);
|
|
38596
38509
|
return parsed;
|
|
38597
38510
|
} catch {
|
|
@@ -38605,34 +38518,34 @@ var StateFileManager = class {
|
|
|
38605
38518
|
return { status: "stale", existing };
|
|
38606
38519
|
}
|
|
38607
38520
|
write(state) {
|
|
38608
|
-
|
|
38521
|
+
import_node_fs19.default.mkdirSync(import_node_path22.default.dirname(this.file), { recursive: true });
|
|
38609
38522
|
const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
|
|
38610
|
-
|
|
38611
|
-
|
|
38523
|
+
import_node_fs19.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
38524
|
+
import_node_fs19.default.renameSync(tmp, this.file);
|
|
38612
38525
|
if (process.platform !== "win32") {
|
|
38613
38526
|
try {
|
|
38614
|
-
|
|
38527
|
+
import_node_fs19.default.chmodSync(this.file, 384);
|
|
38615
38528
|
} catch {
|
|
38616
38529
|
}
|
|
38617
38530
|
}
|
|
38618
38531
|
}
|
|
38619
38532
|
delete() {
|
|
38620
38533
|
try {
|
|
38621
|
-
|
|
38534
|
+
import_node_fs19.default.unlinkSync(this.file);
|
|
38622
38535
|
} catch {
|
|
38623
38536
|
}
|
|
38624
38537
|
}
|
|
38625
38538
|
};
|
|
38626
38539
|
|
|
38627
38540
|
// src/tunnel/tunnel-manager.ts
|
|
38628
|
-
var
|
|
38629
|
-
var
|
|
38630
|
-
var
|
|
38541
|
+
var import_node_fs23 = __toESM(require("fs"), 1);
|
|
38542
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
38543
|
+
var import_node_crypto7 = __toESM(require("crypto"), 1);
|
|
38631
38544
|
var import_node_child_process5 = require("child_process");
|
|
38632
38545
|
|
|
38633
38546
|
// src/tunnel/tunnel-store.ts
|
|
38634
|
-
var
|
|
38635
|
-
var
|
|
38547
|
+
var import_node_fs20 = __toESM(require("fs"), 1);
|
|
38548
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
38636
38549
|
var TunnelStore = class {
|
|
38637
38550
|
constructor(filePath) {
|
|
38638
38551
|
this.filePath = filePath;
|
|
@@ -38640,7 +38553,7 @@ var TunnelStore = class {
|
|
|
38640
38553
|
filePath;
|
|
38641
38554
|
async get() {
|
|
38642
38555
|
try {
|
|
38643
|
-
const raw = await
|
|
38556
|
+
const raw = await import_node_fs20.default.promises.readFile(this.filePath, "utf8");
|
|
38644
38557
|
const obj = JSON.parse(raw);
|
|
38645
38558
|
if (!isPersistedTunnel(obj)) return null;
|
|
38646
38559
|
return obj;
|
|
@@ -38651,22 +38564,22 @@ var TunnelStore = class {
|
|
|
38651
38564
|
}
|
|
38652
38565
|
}
|
|
38653
38566
|
async set(v2) {
|
|
38654
|
-
const dir =
|
|
38655
|
-
await
|
|
38567
|
+
const dir = import_node_path23.default.dirname(this.filePath);
|
|
38568
|
+
await import_node_fs20.default.promises.mkdir(dir, { recursive: true });
|
|
38656
38569
|
const data = JSON.stringify(v2, null, 2);
|
|
38657
38570
|
const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
38658
|
-
await
|
|
38571
|
+
await import_node_fs20.default.promises.writeFile(tmp, data, { mode: 384 });
|
|
38659
38572
|
if (process.platform !== "win32") {
|
|
38660
38573
|
try {
|
|
38661
|
-
await
|
|
38574
|
+
await import_node_fs20.default.promises.chmod(tmp, 384);
|
|
38662
38575
|
} catch {
|
|
38663
38576
|
}
|
|
38664
38577
|
}
|
|
38665
|
-
await
|
|
38578
|
+
await import_node_fs20.default.promises.rename(tmp, this.filePath);
|
|
38666
38579
|
}
|
|
38667
38580
|
async clear() {
|
|
38668
38581
|
try {
|
|
38669
|
-
await
|
|
38582
|
+
await import_node_fs20.default.promises.unlink(this.filePath);
|
|
38670
38583
|
} catch (err) {
|
|
38671
38584
|
const code = err?.code;
|
|
38672
38585
|
if (code !== "ENOENT") throw err;
|
|
@@ -38761,9 +38674,9 @@ function escape(v2) {
|
|
|
38761
38674
|
}
|
|
38762
38675
|
|
|
38763
38676
|
// src/tunnel/frpc-binary.ts
|
|
38764
|
-
var
|
|
38677
|
+
var import_node_fs21 = __toESM(require("fs"), 1);
|
|
38765
38678
|
var import_node_os11 = __toESM(require("os"), 1);
|
|
38766
|
-
var
|
|
38679
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
38767
38680
|
var import_node_child_process3 = require("child_process");
|
|
38768
38681
|
var import_node_stream2 = require("stream");
|
|
38769
38682
|
var import_promises3 = require("stream/promises");
|
|
@@ -38795,20 +38708,20 @@ function frpcDownloadUrl(version2, p2) {
|
|
|
38795
38708
|
}
|
|
38796
38709
|
async function ensureFrpcBinary(opts) {
|
|
38797
38710
|
if (opts.override) {
|
|
38798
|
-
if (!
|
|
38711
|
+
if (!import_node_fs21.default.existsSync(opts.override)) {
|
|
38799
38712
|
throw new Error(`frpc binary not found at override path: ${opts.override}`);
|
|
38800
38713
|
}
|
|
38801
38714
|
return opts.override;
|
|
38802
38715
|
}
|
|
38803
38716
|
const version2 = opts.version ?? FRPC_VERSION;
|
|
38804
38717
|
const platform = opts.platform ?? detectPlatform();
|
|
38805
|
-
const binDir =
|
|
38806
|
-
|
|
38718
|
+
const binDir = import_node_path24.default.join(opts.dataDir, "bin");
|
|
38719
|
+
import_node_fs21.default.mkdirSync(binDir, { recursive: true });
|
|
38807
38720
|
cleanupStaleArtifacts(binDir);
|
|
38808
|
-
const stableBin =
|
|
38809
|
-
if (
|
|
38721
|
+
const stableBin = import_node_path24.default.join(binDir, "frpc");
|
|
38722
|
+
if (import_node_fs21.default.existsSync(stableBin)) return stableBin;
|
|
38810
38723
|
const partialBin = `${stableBin}.partial`;
|
|
38811
|
-
const tarballPath =
|
|
38724
|
+
const tarballPath = import_node_path24.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
|
|
38812
38725
|
try {
|
|
38813
38726
|
const url = frpcDownloadUrl(version2, platform);
|
|
38814
38727
|
await downloadToFile(url, tarballPath, opts.fetchImpl);
|
|
@@ -38817,8 +38730,8 @@ async function ensureFrpcBinary(opts) {
|
|
|
38817
38730
|
} else {
|
|
38818
38731
|
await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
|
|
38819
38732
|
}
|
|
38820
|
-
|
|
38821
|
-
|
|
38733
|
+
import_node_fs21.default.chmodSync(partialBin, 493);
|
|
38734
|
+
import_node_fs21.default.renameSync(partialBin, stableBin);
|
|
38822
38735
|
} finally {
|
|
38823
38736
|
safeUnlink(tarballPath);
|
|
38824
38737
|
safeUnlink(partialBin);
|
|
@@ -38828,15 +38741,15 @@ async function ensureFrpcBinary(opts) {
|
|
|
38828
38741
|
function cleanupStaleArtifacts(binDir) {
|
|
38829
38742
|
let entries;
|
|
38830
38743
|
try {
|
|
38831
|
-
entries =
|
|
38744
|
+
entries = import_node_fs21.default.readdirSync(binDir);
|
|
38832
38745
|
} catch {
|
|
38833
38746
|
return;
|
|
38834
38747
|
}
|
|
38835
38748
|
for (const name of entries) {
|
|
38836
38749
|
if (name.endsWith(".partial") || name.startsWith("extract-")) {
|
|
38837
|
-
const full =
|
|
38750
|
+
const full = import_node_path24.default.join(binDir, name);
|
|
38838
38751
|
try {
|
|
38839
|
-
|
|
38752
|
+
import_node_fs21.default.rmSync(full, { recursive: true, force: true });
|
|
38840
38753
|
} catch {
|
|
38841
38754
|
}
|
|
38842
38755
|
}
|
|
@@ -38844,7 +38757,7 @@ function cleanupStaleArtifacts(binDir) {
|
|
|
38844
38757
|
}
|
|
38845
38758
|
function safeUnlink(p2) {
|
|
38846
38759
|
try {
|
|
38847
|
-
|
|
38760
|
+
import_node_fs21.default.unlinkSync(p2);
|
|
38848
38761
|
} catch {
|
|
38849
38762
|
}
|
|
38850
38763
|
}
|
|
@@ -38855,13 +38768,13 @@ async function downloadToFile(url, dest, fetchImpl) {
|
|
|
38855
38768
|
if (!res.ok || !res.body) {
|
|
38856
38769
|
throw new Error(`download failed: ${res.status} ${res.statusText}`);
|
|
38857
38770
|
}
|
|
38858
|
-
const out =
|
|
38771
|
+
const out = import_node_fs21.default.createWriteStream(dest);
|
|
38859
38772
|
const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
|
|
38860
38773
|
await (0, import_promises3.pipeline)(nodeStream, out);
|
|
38861
38774
|
}
|
|
38862
38775
|
async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
|
|
38863
|
-
const work =
|
|
38864
|
-
|
|
38776
|
+
const work = import_node_path24.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
|
|
38777
|
+
import_node_fs21.default.mkdirSync(work, { recursive: true });
|
|
38865
38778
|
try {
|
|
38866
38779
|
await new Promise((resolve6, reject) => {
|
|
38867
38780
|
const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
|
|
@@ -38869,32 +38782,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
|
|
|
38869
38782
|
proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
|
|
38870
38783
|
});
|
|
38871
38784
|
const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
|
|
38872
|
-
const src =
|
|
38873
|
-
if (!
|
|
38785
|
+
const src = import_node_path24.default.join(work, dirName, "frpc");
|
|
38786
|
+
if (!import_node_fs21.default.existsSync(src)) {
|
|
38874
38787
|
throw new Error(`frpc not found inside tarball at ${src}`);
|
|
38875
38788
|
}
|
|
38876
|
-
|
|
38789
|
+
import_node_fs21.default.copyFileSync(src, destBin);
|
|
38877
38790
|
} finally {
|
|
38878
|
-
|
|
38791
|
+
import_node_fs21.default.rmSync(work, { recursive: true, force: true });
|
|
38879
38792
|
}
|
|
38880
38793
|
}
|
|
38881
38794
|
|
|
38882
38795
|
// src/tunnel/frpc-process.ts
|
|
38883
|
-
var
|
|
38884
|
-
var
|
|
38796
|
+
var import_node_fs22 = __toESM(require("fs"), 1);
|
|
38797
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
38885
38798
|
var import_node_child_process4 = require("child_process");
|
|
38886
38799
|
function frpcPidFilePath(dataDir) {
|
|
38887
|
-
return
|
|
38800
|
+
return import_node_path25.default.join(dataDir, "frpc.pid");
|
|
38888
38801
|
}
|
|
38889
38802
|
function writeFrpcPid(dataDir, pid) {
|
|
38890
38803
|
try {
|
|
38891
|
-
|
|
38804
|
+
import_node_fs22.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
|
|
38892
38805
|
} catch {
|
|
38893
38806
|
}
|
|
38894
38807
|
}
|
|
38895
38808
|
function clearFrpcPid(dataDir) {
|
|
38896
38809
|
try {
|
|
38897
|
-
|
|
38810
|
+
import_node_fs22.default.unlinkSync(frpcPidFilePath(dataDir));
|
|
38898
38811
|
} catch {
|
|
38899
38812
|
}
|
|
38900
38813
|
}
|
|
@@ -38910,7 +38823,7 @@ function defaultIsPidAlive(pid) {
|
|
|
38910
38823
|
}
|
|
38911
38824
|
function defaultReadPidFile(file) {
|
|
38912
38825
|
try {
|
|
38913
|
-
return
|
|
38826
|
+
return import_node_fs22.default.readFileSync(file, "utf8");
|
|
38914
38827
|
} catch {
|
|
38915
38828
|
return null;
|
|
38916
38829
|
}
|
|
@@ -38926,7 +38839,7 @@ function defaultSleep(ms) {
|
|
|
38926
38839
|
}
|
|
38927
38840
|
async function killStaleFrpc(deps) {
|
|
38928
38841
|
const pidFile = frpcPidFilePath(deps.dataDir);
|
|
38929
|
-
const tomlPath =
|
|
38842
|
+
const tomlPath = import_node_path25.default.join(deps.dataDir, "frpc.toml");
|
|
38930
38843
|
const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
|
|
38931
38844
|
const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
|
|
38932
38845
|
const killPid = deps.killPidImpl ?? defaultKillPid;
|
|
@@ -38950,7 +38863,7 @@ async function killStaleFrpc(deps) {
|
|
|
38950
38863
|
}
|
|
38951
38864
|
if (victims.size === 0) {
|
|
38952
38865
|
try {
|
|
38953
|
-
|
|
38866
|
+
import_node_fs22.default.unlinkSync(pidFile);
|
|
38954
38867
|
} catch {
|
|
38955
38868
|
}
|
|
38956
38869
|
return;
|
|
@@ -38961,7 +38874,7 @@ async function killStaleFrpc(deps) {
|
|
|
38961
38874
|
}
|
|
38962
38875
|
await sleep(deps.reapWaitMs ?? 300);
|
|
38963
38876
|
try {
|
|
38964
|
-
|
|
38877
|
+
import_node_fs22.default.unlinkSync(pidFile);
|
|
38965
38878
|
} catch {
|
|
38966
38879
|
}
|
|
38967
38880
|
}
|
|
@@ -38998,7 +38911,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
38998
38911
|
var TunnelManager = class {
|
|
38999
38912
|
constructor(deps) {
|
|
39000
38913
|
this.deps = deps;
|
|
39001
|
-
this.store = deps.store ?? new TunnelStore(
|
|
38914
|
+
this.store = deps.store ?? new TunnelStore(import_node_path26.default.join(deps.dataDir, "tunnel.json"));
|
|
39002
38915
|
this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
|
|
39003
38916
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
|
|
39004
38917
|
}
|
|
@@ -39125,8 +39038,8 @@ var TunnelManager = class {
|
|
|
39125
39038
|
dataDir: this.deps.dataDir,
|
|
39126
39039
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
39127
39040
|
});
|
|
39128
|
-
const tomlPath =
|
|
39129
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
39041
|
+
const tomlPath = import_node_path26.default.join(this.deps.dataDir, "frpc.toml");
|
|
39042
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto7.default.randomBytes(3).toString("hex")}`;
|
|
39130
39043
|
const toml = buildFrpcToml({
|
|
39131
39044
|
serverAddr: t.frpsHost,
|
|
39132
39045
|
serverPort: t.frpsPort,
|
|
@@ -39136,12 +39049,12 @@ var TunnelManager = class {
|
|
|
39136
39049
|
localPort,
|
|
39137
39050
|
logLevel: "info"
|
|
39138
39051
|
});
|
|
39139
|
-
await
|
|
39052
|
+
await import_node_fs23.default.promises.writeFile(tomlPath, toml, { mode: 384 });
|
|
39140
39053
|
const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
|
|
39141
39054
|
stdio: ["ignore", "pipe", "pipe"]
|
|
39142
39055
|
});
|
|
39143
|
-
const logFilePath =
|
|
39144
|
-
const logStream =
|
|
39056
|
+
const logFilePath = import_node_path26.default.join(this.deps.dataDir, "frpc.log");
|
|
39057
|
+
const logStream = import_node_fs23.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
|
|
39145
39058
|
logStream.on("error", () => {
|
|
39146
39059
|
});
|
|
39147
39060
|
const tee = (chunk) => {
|
|
@@ -39224,31 +39137,31 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
39224
39137
|
|
|
39225
39138
|
// src/tunnel/device-key.ts
|
|
39226
39139
|
var import_node_os12 = __toESM(require("os"), 1);
|
|
39227
|
-
var
|
|
39228
|
-
var
|
|
39140
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
39141
|
+
var import_node_crypto8 = __toESM(require("crypto"), 1);
|
|
39229
39142
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
39230
39143
|
function deriveStableDeviceKey(opts = {}) {
|
|
39231
39144
|
const hostname = opts.hostname ?? import_node_os12.default.hostname();
|
|
39232
39145
|
const uid = opts.uid ?? (typeof import_node_os12.default.userInfo === "function" ? import_node_os12.default.userInfo().uid : 0);
|
|
39233
39146
|
const home = opts.home ?? import_node_os12.default.homedir();
|
|
39234
|
-
const defaultDataDir =
|
|
39235
|
-
const normalizedDataDir = opts.dataDir ?
|
|
39147
|
+
const defaultDataDir = import_node_path27.default.resolve(import_node_path27.default.join(home, ".clawd"));
|
|
39148
|
+
const normalizedDataDir = opts.dataDir ? import_node_path27.default.resolve(opts.dataDir) : null;
|
|
39236
39149
|
const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
|
|
39237
39150
|
const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
|
|
39238
|
-
return
|
|
39151
|
+
return import_node_crypto8.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
39239
39152
|
}
|
|
39240
39153
|
|
|
39241
39154
|
// src/auth-store.ts
|
|
39242
|
-
var
|
|
39243
|
-
var
|
|
39244
|
-
var
|
|
39155
|
+
var import_node_fs24 = __toESM(require("fs"), 1);
|
|
39156
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
39157
|
+
var import_node_crypto9 = __toESM(require("crypto"), 1);
|
|
39245
39158
|
var AUTH_FILE_NAME = "auth.json";
|
|
39246
39159
|
function authFilePath(dataDir) {
|
|
39247
|
-
return
|
|
39160
|
+
return import_node_path28.default.join(dataDir, AUTH_FILE_NAME);
|
|
39248
39161
|
}
|
|
39249
39162
|
function loadOrCreateAuthFile(opts) {
|
|
39250
39163
|
const file = authFilePath(opts.dataDir);
|
|
39251
|
-
const generate = opts.generate ??
|
|
39164
|
+
const generate = opts.generate ?? defaultGenerateToken;
|
|
39252
39165
|
const genId = opts.genOwnerPrincipalId ?? defaultGenerateOwnerPrincipalId;
|
|
39253
39166
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
39254
39167
|
const existing = readAuthFile(file);
|
|
@@ -39268,15 +39181,15 @@ function loadOrCreateAuthFile(opts) {
|
|
|
39268
39181
|
writeAuthFile(file, next);
|
|
39269
39182
|
return next;
|
|
39270
39183
|
}
|
|
39271
|
-
function
|
|
39272
|
-
return
|
|
39184
|
+
function defaultGenerateToken() {
|
|
39185
|
+
return import_node_crypto9.default.randomBytes(32).toString("base64url");
|
|
39273
39186
|
}
|
|
39274
39187
|
function defaultGenerateOwnerPrincipalId() {
|
|
39275
|
-
return `owner-${
|
|
39188
|
+
return `owner-${import_node_crypto9.default.randomUUID()}`;
|
|
39276
39189
|
}
|
|
39277
39190
|
function readAuthFile(file) {
|
|
39278
39191
|
try {
|
|
39279
|
-
const raw =
|
|
39192
|
+
const raw = import_node_fs24.default.readFileSync(file, "utf8");
|
|
39280
39193
|
const parsed = JSON.parse(raw);
|
|
39281
39194
|
if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
|
|
39282
39195
|
return null;
|
|
@@ -39294,25 +39207,25 @@ function readAuthFile(file) {
|
|
|
39294
39207
|
}
|
|
39295
39208
|
}
|
|
39296
39209
|
function writeAuthFile(file, content) {
|
|
39297
|
-
|
|
39298
|
-
|
|
39210
|
+
import_node_fs24.default.mkdirSync(import_node_path28.default.dirname(file), { recursive: true });
|
|
39211
|
+
import_node_fs24.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
39299
39212
|
try {
|
|
39300
|
-
|
|
39213
|
+
import_node_fs24.default.chmodSync(file, 384);
|
|
39301
39214
|
} catch {
|
|
39302
39215
|
}
|
|
39303
39216
|
}
|
|
39304
39217
|
|
|
39305
39218
|
// src/owner-profile.ts
|
|
39306
|
-
var
|
|
39219
|
+
var import_node_fs25 = __toESM(require("fs"), 1);
|
|
39307
39220
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
39308
|
-
var
|
|
39221
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
39309
39222
|
var PROFILE_FILENAME = "profile.json";
|
|
39310
39223
|
function loadOwnerDisplayName(dataDir) {
|
|
39311
39224
|
const fallback = import_node_os13.default.userInfo().username;
|
|
39312
|
-
const profilePath =
|
|
39225
|
+
const profilePath = import_node_path29.default.join(dataDir, PROFILE_FILENAME);
|
|
39313
39226
|
let raw;
|
|
39314
39227
|
try {
|
|
39315
|
-
raw =
|
|
39228
|
+
raw = import_node_fs25.default.readFileSync(profilePath, "utf8");
|
|
39316
39229
|
} catch {
|
|
39317
39230
|
return fallback;
|
|
39318
39231
|
}
|
|
@@ -39334,6 +39247,259 @@ function loadOwnerDisplayName(dataDir) {
|
|
|
39334
39247
|
return displayName;
|
|
39335
39248
|
}
|
|
39336
39249
|
|
|
39250
|
+
// src/feishu-auth/owner-identity-store.ts
|
|
39251
|
+
var import_node_fs26 = __toESM(require("fs"), 1);
|
|
39252
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
39253
|
+
var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
|
|
39254
|
+
var OwnerIdentityStore = class {
|
|
39255
|
+
file;
|
|
39256
|
+
constructor(dataDir) {
|
|
39257
|
+
this.file = import_node_path30.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
|
|
39258
|
+
}
|
|
39259
|
+
read() {
|
|
39260
|
+
let raw;
|
|
39261
|
+
try {
|
|
39262
|
+
raw = import_node_fs26.default.readFileSync(this.file, "utf8");
|
|
39263
|
+
} catch {
|
|
39264
|
+
return null;
|
|
39265
|
+
}
|
|
39266
|
+
let parsed;
|
|
39267
|
+
try {
|
|
39268
|
+
parsed = JSON.parse(raw);
|
|
39269
|
+
} catch {
|
|
39270
|
+
return null;
|
|
39271
|
+
}
|
|
39272
|
+
const r = parsed;
|
|
39273
|
+
if (!r.identity || typeof r.identity.unionId !== "string" || r.identity.unionId.length === 0 || typeof r.identity.displayName !== "string" || r.identity.displayName.length === 0 || typeof r.ttcToken !== "string" || r.ttcToken.length === 0) {
|
|
39274
|
+
return null;
|
|
39275
|
+
}
|
|
39276
|
+
return {
|
|
39277
|
+
identity: {
|
|
39278
|
+
unionId: r.identity.unionId,
|
|
39279
|
+
displayName: r.identity.displayName,
|
|
39280
|
+
...typeof r.identity.avatarUrl === "string" ? { avatarUrl: r.identity.avatarUrl } : {}
|
|
39281
|
+
},
|
|
39282
|
+
ttcToken: r.ttcToken,
|
|
39283
|
+
ttcTokenExpiresAt: typeof r.ttcTokenExpiresAt === "number" ? r.ttcTokenExpiresAt : null,
|
|
39284
|
+
loginAt: typeof r.loginAt === "string" ? r.loginAt : (/* @__PURE__ */ new Date(0)).toISOString()
|
|
39285
|
+
};
|
|
39286
|
+
}
|
|
39287
|
+
write(record) {
|
|
39288
|
+
import_node_fs26.default.mkdirSync(import_node_path30.default.dirname(this.file), { recursive: true });
|
|
39289
|
+
import_node_fs26.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
|
|
39290
|
+
try {
|
|
39291
|
+
import_node_fs26.default.chmodSync(this.file, 384);
|
|
39292
|
+
} catch {
|
|
39293
|
+
}
|
|
39294
|
+
}
|
|
39295
|
+
clear() {
|
|
39296
|
+
try {
|
|
39297
|
+
import_node_fs26.default.unlinkSync(this.file);
|
|
39298
|
+
} catch (err) {
|
|
39299
|
+
const code = err?.code;
|
|
39300
|
+
if (code !== "ENOENT") throw err;
|
|
39301
|
+
}
|
|
39302
|
+
}
|
|
39303
|
+
};
|
|
39304
|
+
|
|
39305
|
+
// src/feishu-auth/login-flow.ts
|
|
39306
|
+
var import_node_crypto10 = __toESM(require("crypto"), 1);
|
|
39307
|
+
var STATE_TTL_MS = 5 * 60 * 1e3;
|
|
39308
|
+
var LoginFlow = class {
|
|
39309
|
+
constructor(deps) {
|
|
39310
|
+
this.deps = deps;
|
|
39311
|
+
}
|
|
39312
|
+
deps;
|
|
39313
|
+
pendingStates = /* @__PURE__ */ new Map();
|
|
39314
|
+
start() {
|
|
39315
|
+
const state = import_node_crypto10.default.randomBytes(16).toString("base64url");
|
|
39316
|
+
const now = (this.deps.now ?? Date.now)();
|
|
39317
|
+
this.pendingStates.set(state, now);
|
|
39318
|
+
this.gcExpired(now);
|
|
39319
|
+
const url = new URL("/auth/authorize", this.deps.ttcAuthBase);
|
|
39320
|
+
url.searchParams.set("callback_url", this.deps.getCallbackUrl());
|
|
39321
|
+
url.searchParams.set("state", state);
|
|
39322
|
+
url.searchParams.set("auto", "1");
|
|
39323
|
+
return { authUrl: url.toString(), state };
|
|
39324
|
+
}
|
|
39325
|
+
async handleCallback(params) {
|
|
39326
|
+
const now = (this.deps.now ?? Date.now)();
|
|
39327
|
+
const issuedAt = this.pendingStates.get(params.state);
|
|
39328
|
+
if (issuedAt === void 0) {
|
|
39329
|
+
return { ok: false, reason: "state mismatch" };
|
|
39330
|
+
}
|
|
39331
|
+
this.pendingStates.delete(params.state);
|
|
39332
|
+
if (now - issuedAt > STATE_TTL_MS) {
|
|
39333
|
+
return { ok: false, reason: "state expired" };
|
|
39334
|
+
}
|
|
39335
|
+
if (!params.token) {
|
|
39336
|
+
return { ok: false, reason: "missing token" };
|
|
39337
|
+
}
|
|
39338
|
+
let identity;
|
|
39339
|
+
try {
|
|
39340
|
+
identity = await this.deps.fetchUserInfo(params.token);
|
|
39341
|
+
} catch (err) {
|
|
39342
|
+
return { ok: false, reason: `user_info failed: ${err.message}` };
|
|
39343
|
+
}
|
|
39344
|
+
const expiresAtNum = params.expiresAt ? Number.parseInt(params.expiresAt, 10) : NaN;
|
|
39345
|
+
this.deps.store.write({
|
|
39346
|
+
identity,
|
|
39347
|
+
ttcToken: params.token,
|
|
39348
|
+
ttcTokenExpiresAt: Number.isFinite(expiresAtNum) ? expiresAtNum : null,
|
|
39349
|
+
loginAt: new Date(now).toISOString()
|
|
39350
|
+
});
|
|
39351
|
+
return { ok: true, identity };
|
|
39352
|
+
}
|
|
39353
|
+
gcExpired(now) {
|
|
39354
|
+
for (const [state, issuedAt] of this.pendingStates) {
|
|
39355
|
+
if (now - issuedAt > STATE_TTL_MS) this.pendingStates.delete(state);
|
|
39356
|
+
}
|
|
39357
|
+
}
|
|
39358
|
+
};
|
|
39359
|
+
|
|
39360
|
+
// src/feishu-auth/ttc-client.ts
|
|
39361
|
+
var TTC_HOSTS = {
|
|
39362
|
+
auth: "https://app.ttcadvisory.com",
|
|
39363
|
+
api: "https://api.ttcadvisory.com"
|
|
39364
|
+
};
|
|
39365
|
+
var TtcUserInfoError = class extends Error {
|
|
39366
|
+
constructor(code, message) {
|
|
39367
|
+
super(message);
|
|
39368
|
+
this.code = code;
|
|
39369
|
+
this.name = "TtcUserInfoError";
|
|
39370
|
+
}
|
|
39371
|
+
code;
|
|
39372
|
+
};
|
|
39373
|
+
async function fetchTtcUserInfo(opts) {
|
|
39374
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
39375
|
+
let res;
|
|
39376
|
+
try {
|
|
39377
|
+
res = await doFetch(`${opts.apiBase}/api/user_service/v1/login/user`, {
|
|
39378
|
+
headers: { Authorization: `Bearer ${opts.token}` }
|
|
39379
|
+
});
|
|
39380
|
+
} catch (err) {
|
|
39381
|
+
throw new TtcUserInfoError("NETWORK", `TTC user_info request failed: ${err.message}`);
|
|
39382
|
+
}
|
|
39383
|
+
if (!res.ok) {
|
|
39384
|
+
if (res.status === 401) {
|
|
39385
|
+
throw new TtcUserInfoError("UNAUTHORIZED", "TTC token invalid or expired");
|
|
39386
|
+
}
|
|
39387
|
+
throw new TtcUserInfoError("API_ERROR", `TTC user_info HTTP ${res.status}`);
|
|
39388
|
+
}
|
|
39389
|
+
const body = await res.json();
|
|
39390
|
+
if (body.code !== 0) {
|
|
39391
|
+
throw new TtcUserInfoError("API_ERROR", `TTC user_info code=${body.code}: ${body.message ?? ""}`);
|
|
39392
|
+
}
|
|
39393
|
+
const d = body.data;
|
|
39394
|
+
if (!d || typeof d.union_id !== "string" || d.union_id.length === 0 || typeof d.name !== "string" || d.name.length === 0) {
|
|
39395
|
+
throw new TtcUserInfoError("BAD_RESPONSE", "TTC user_info missing union_id or name");
|
|
39396
|
+
}
|
|
39397
|
+
return {
|
|
39398
|
+
unionId: d.union_id,
|
|
39399
|
+
displayName: d.name,
|
|
39400
|
+
...typeof d.avatar_url === "string" && d.avatar_url.length > 0 ? { avatarUrl: d.avatar_url } : {}
|
|
39401
|
+
};
|
|
39402
|
+
}
|
|
39403
|
+
|
|
39404
|
+
// src/feishu-auth/central-client.ts
|
|
39405
|
+
var CentralClientError = class extends Error {
|
|
39406
|
+
constructor(code, message, cause) {
|
|
39407
|
+
super(message);
|
|
39408
|
+
this.code = code;
|
|
39409
|
+
this.cause = cause;
|
|
39410
|
+
this.name = "CentralClientError";
|
|
39411
|
+
}
|
|
39412
|
+
code;
|
|
39413
|
+
cause;
|
|
39414
|
+
};
|
|
39415
|
+
async function centralRequest(opts, path59, init) {
|
|
39416
|
+
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
39417
|
+
const url = `${opts.api.replace(/\/+$/, "")}${path59}`;
|
|
39418
|
+
const ctrl = new AbortController();
|
|
39419
|
+
const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
|
|
39420
|
+
let res;
|
|
39421
|
+
try {
|
|
39422
|
+
res = await f(url, {
|
|
39423
|
+
method: init.method,
|
|
39424
|
+
headers: {
|
|
39425
|
+
"content-type": "application/json",
|
|
39426
|
+
...init.bearer ? { authorization: `Bearer ${init.bearer}` } : {}
|
|
39427
|
+
},
|
|
39428
|
+
...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
|
|
39429
|
+
signal: ctrl.signal
|
|
39430
|
+
});
|
|
39431
|
+
} catch (err) {
|
|
39432
|
+
clearTimeout(timer);
|
|
39433
|
+
throw new CentralClientError("NETWORK", `central server request failed: ${err.message}`, err);
|
|
39434
|
+
}
|
|
39435
|
+
clearTimeout(timer);
|
|
39436
|
+
if (res.status === 401) {
|
|
39437
|
+
throw new CentralClientError("UNAUTHORIZED", "TTC JWT invalid or expired");
|
|
39438
|
+
}
|
|
39439
|
+
if (!res.ok) {
|
|
39440
|
+
throw new CentralClientError("API_ERROR", `central server HTTP ${res.status}`);
|
|
39441
|
+
}
|
|
39442
|
+
try {
|
|
39443
|
+
return await res.json();
|
|
39444
|
+
} catch (err) {
|
|
39445
|
+
throw new CentralClientError("BAD_RESPONSE", "central server: invalid json response", err);
|
|
39446
|
+
}
|
|
39447
|
+
}
|
|
39448
|
+
async function centralExchange(opts) {
|
|
39449
|
+
const body = await centralRequest(opts, "/api/clawd-identity/exchange", {
|
|
39450
|
+
method: "POST",
|
|
39451
|
+
body: { ttcJwt: opts.ttcJwt, hubId: opts.hubId }
|
|
39452
|
+
});
|
|
39453
|
+
if (typeof body.token !== "string" || body.token.length === 0 || typeof body.unionId !== "string" || typeof body.displayName !== "string") {
|
|
39454
|
+
throw new CentralClientError("BAD_RESPONSE", "exchange: missing token/unionId/displayName");
|
|
39455
|
+
}
|
|
39456
|
+
const hubUrl = typeof body.hubUrl === "string" && body.hubUrl.length > 0 ? body.hubUrl : null;
|
|
39457
|
+
return { token: body.token, unionId: body.unionId, displayName: body.displayName, hubUrl };
|
|
39458
|
+
}
|
|
39459
|
+
async function centralUpsertHub(opts) {
|
|
39460
|
+
const body = await centralRequest(opts, "/api/clawd-identity/hubs/self", {
|
|
39461
|
+
method: "PUT",
|
|
39462
|
+
bearer: opts.ttcJwt,
|
|
39463
|
+
body: { url: opts.url, ...opts.name ? { name: opts.name } : {} }
|
|
39464
|
+
});
|
|
39465
|
+
if (body.ok !== true) {
|
|
39466
|
+
throw new CentralClientError("BAD_RESPONSE", "upsertHub: server did not ack ok");
|
|
39467
|
+
}
|
|
39468
|
+
return { ok: true };
|
|
39469
|
+
}
|
|
39470
|
+
async function centralIntrospect(opts) {
|
|
39471
|
+
const body = await centralRequest(opts, "/api/clawd-identity/introspect", {
|
|
39472
|
+
method: "POST",
|
|
39473
|
+
bearer: opts.hubTtcJwt,
|
|
39474
|
+
body: { token: opts.token }
|
|
39475
|
+
});
|
|
39476
|
+
if (body.active !== true) {
|
|
39477
|
+
return { active: false };
|
|
39478
|
+
}
|
|
39479
|
+
if (typeof body.unionId !== "string" || typeof body.displayName !== "string") {
|
|
39480
|
+
throw new CentralClientError("BAD_RESPONSE", "introspect: active but missing identity");
|
|
39481
|
+
}
|
|
39482
|
+
return { active: true, unionId: body.unionId, displayName: body.displayName };
|
|
39483
|
+
}
|
|
39484
|
+
async function centralListHubs(opts) {
|
|
39485
|
+
const body = await centralRequest(opts, "/api/clawd-identity/hubs", {
|
|
39486
|
+
method: "GET",
|
|
39487
|
+
bearer: opts.ttcJwt
|
|
39488
|
+
});
|
|
39489
|
+
if (!Array.isArray(body.hubs)) {
|
|
39490
|
+
throw new CentralClientError("BAD_RESPONSE", "hubs: missing hubs array");
|
|
39491
|
+
}
|
|
39492
|
+
const out = [];
|
|
39493
|
+
for (const raw of body.hubs) {
|
|
39494
|
+
const h = raw;
|
|
39495
|
+
if (typeof h.hubId !== "string" || typeof h.name !== "string" || typeof h.url !== "string") {
|
|
39496
|
+
throw new CentralClientError("BAD_RESPONSE", "hubs: malformed hub entry");
|
|
39497
|
+
}
|
|
39498
|
+
out.push({ hubId: h.hubId, name: h.name, url: h.url });
|
|
39499
|
+
}
|
|
39500
|
+
return out;
|
|
39501
|
+
}
|
|
39502
|
+
|
|
39337
39503
|
// src/index.ts
|
|
39338
39504
|
init_protocol();
|
|
39339
39505
|
|
|
@@ -39477,9 +39643,9 @@ function buildSessionHandlers(deps) {
|
|
|
39477
39643
|
if (ctx && ctx.principal.kind === "guest") {
|
|
39478
39644
|
const sessions = response.sessions ?? [];
|
|
39479
39645
|
const peerOwnerId = ctx.peerOwnerPrincipalId;
|
|
39480
|
-
const
|
|
39646
|
+
const guestId = ctx.principal.id;
|
|
39481
39647
|
const filtered = sessions.filter((s) => canAccessPersona(ctx.grants, s.ownerPersonaId, "read")).filter(
|
|
39482
|
-
(s) => peerOwnerId ? s.creatorOwnerPrincipalId === peerOwnerId : s.creatorPrincipalId ===
|
|
39648
|
+
(s) => peerOwnerId ? s.creatorOwnerPrincipalId === peerOwnerId : s.creatorPrincipalId === guestId
|
|
39483
39649
|
);
|
|
39484
39650
|
return { response: { type: "session:list", sessions: filtered } };
|
|
39485
39651
|
}
|
|
@@ -39494,69 +39660,7 @@ function buildSessionHandlers(deps) {
|
|
|
39494
39660
|
const update = async (frame, _client, ctx) => {
|
|
39495
39661
|
const args = SessionUpdateArgs.parse(frame);
|
|
39496
39662
|
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
39497
|
-
const prevFile = manager.findOwnedSession(args.sessionId);
|
|
39498
|
-
const prevProject = prevFile?.appBuilderProject ?? null;
|
|
39499
|
-
const nextProject = args.patch.appBuilderProject ?? prevProject;
|
|
39500
39663
|
const { response, broadcast } = manager.update(args);
|
|
39501
|
-
if (args.patch.appBuilderProject !== void 0) {
|
|
39502
|
-
deps.logger?.info("session-update.appBuilderProject", {
|
|
39503
|
-
sessionId: args.sessionId,
|
|
39504
|
-
prevProject,
|
|
39505
|
-
nextProject,
|
|
39506
|
-
hasLifecycle: !!deps.devServerLifecycle,
|
|
39507
|
-
hasStore: !!deps.appBuilderStore
|
|
39508
|
-
});
|
|
39509
|
-
}
|
|
39510
|
-
if (deps.devServerLifecycle && deps.appBuilderStore && args.patch.appBuilderProject !== void 0) {
|
|
39511
|
-
const lifecycle = deps.devServerLifecycle;
|
|
39512
|
-
const projectStore = deps.appBuilderStore;
|
|
39513
|
-
const tunnelUrl = deps.getTunnelUrl?.() ?? null;
|
|
39514
|
-
try {
|
|
39515
|
-
if (prevProject && prevProject !== nextProject) {
|
|
39516
|
-
await lifecycle.stopForSession(args.sessionId);
|
|
39517
|
-
}
|
|
39518
|
-
if (nextProject && nextProject !== "") {
|
|
39519
|
-
const occupantSessionId = lifecycle.boundSessionId(nextProject);
|
|
39520
|
-
const alreadyRunningForThisSession = lifecycle.isRunning(nextProject) && occupantSessionId === args.sessionId;
|
|
39521
|
-
deps.logger?.info("session-update.maybe-start-dev-server", {
|
|
39522
|
-
sessionId: args.sessionId,
|
|
39523
|
-
projectName: nextProject,
|
|
39524
|
-
occupantSessionId,
|
|
39525
|
-
alreadyRunningForThisSession
|
|
39526
|
-
});
|
|
39527
|
-
if (!alreadyRunningForThisSession) {
|
|
39528
|
-
if (occupantSessionId && occupantSessionId !== args.sessionId) {
|
|
39529
|
-
await lifecycle.stopForProject(nextProject);
|
|
39530
|
-
}
|
|
39531
|
-
const projects = await projectStore.list();
|
|
39532
|
-
const target = projects.find((p2) => p2.name === nextProject);
|
|
39533
|
-
deps.logger?.info("session-update.start-dev-server", {
|
|
39534
|
-
sessionId: args.sessionId,
|
|
39535
|
-
projectName: nextProject,
|
|
39536
|
-
targetFound: !!target,
|
|
39537
|
-
projectCwd: target ? projectStore.projectDir(nextProject) : null,
|
|
39538
|
-
devCommand: target?.devCommand,
|
|
39539
|
-
tunnelUrl
|
|
39540
|
-
});
|
|
39541
|
-
if (target) {
|
|
39542
|
-
lifecycle.startForSession({
|
|
39543
|
-
sessionId: args.sessionId,
|
|
39544
|
-
projectName: nextProject,
|
|
39545
|
-
cwd: projectStore.projectDir(nextProject),
|
|
39546
|
-
port: target.port,
|
|
39547
|
-
tunnelHost: tunnelUrl ? new URL(tunnelUrl.replace(/^wss?:\/\//i, "https://")).host : null,
|
|
39548
|
-
devCommand: target.devCommand
|
|
39549
|
-
});
|
|
39550
|
-
}
|
|
39551
|
-
}
|
|
39552
|
-
}
|
|
39553
|
-
} catch (err) {
|
|
39554
|
-
deps.logger?.warn("session-update.lifecycle-failed", {
|
|
39555
|
-
sessionId: args.sessionId,
|
|
39556
|
-
error: err instanceof Error ? err.message : String(err)
|
|
39557
|
-
});
|
|
39558
|
-
}
|
|
39559
|
-
}
|
|
39560
39664
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
39561
39665
|
};
|
|
39562
39666
|
const del = async (frame, _client, ctx) => {
|
|
@@ -39903,7 +40007,7 @@ function buildHistoryHandlers(deps) {
|
|
|
39903
40007
|
const { response: file } = manager.get({ sessionId: args.sessionId });
|
|
39904
40008
|
const f = file;
|
|
39905
40009
|
if (ctx && ctx.principal.kind === "guest") {
|
|
39906
|
-
const ok = canAccessPersona(ctx.grants, f.ownerPersonaId, "read") && f.creatorPrincipalId === ctx.
|
|
40010
|
+
const ok = canAccessPersona(ctx.grants, f.ownerPersonaId, "read") && f.creatorPrincipalId === ctx.principal.id;
|
|
39907
40011
|
if (!ok) {
|
|
39908
40012
|
throw new ClawdError(
|
|
39909
40013
|
ERROR_CODES.UNAUTHORIZED,
|
|
@@ -40185,7 +40289,7 @@ var DeleteArgsSchema = external_exports.object({
|
|
|
40185
40289
|
capabilityId: external_exports.string().min(1)
|
|
40186
40290
|
}).strict();
|
|
40187
40291
|
function buildCapabilityHandlers(deps) {
|
|
40188
|
-
const { manager
|
|
40292
|
+
const { manager } = deps;
|
|
40189
40293
|
const list = async () => {
|
|
40190
40294
|
return {
|
|
40191
40295
|
response: {
|
|
@@ -40211,27 +40315,15 @@ function buildCapabilityHandlers(deps) {
|
|
|
40211
40315
|
}
|
|
40212
40316
|
};
|
|
40213
40317
|
};
|
|
40214
|
-
const personalCapGet = async () => {
|
|
40215
|
-
const { token, capability } = getPersonalCapability();
|
|
40216
|
-
return {
|
|
40217
|
-
response: {
|
|
40218
|
-
type: "personal-cap:get:ok",
|
|
40219
|
-
token,
|
|
40220
|
-
capability: stripSecretHash(capability),
|
|
40221
|
-
shareBaseUrl: getShareBaseUrl()
|
|
40222
|
-
}
|
|
40223
|
-
};
|
|
40224
|
-
};
|
|
40225
40318
|
return {
|
|
40226
40319
|
"capability:list": list,
|
|
40227
|
-
"capability:delete": del
|
|
40228
|
-
"personal-cap:get": personalCapGet
|
|
40320
|
+
"capability:delete": del
|
|
40229
40321
|
};
|
|
40230
40322
|
}
|
|
40231
40323
|
|
|
40232
40324
|
// src/handlers/inbox.ts
|
|
40233
40325
|
init_protocol();
|
|
40234
|
-
function resolvePeerOwnerId(ctx, argsPeerOwnerId
|
|
40326
|
+
function resolvePeerOwnerId(ctx, argsPeerOwnerId) {
|
|
40235
40327
|
if (ctx.principal.kind === "owner") {
|
|
40236
40328
|
if (!argsPeerOwnerId) {
|
|
40237
40329
|
throw new ClawdError(
|
|
@@ -40241,18 +40333,11 @@ function resolvePeerOwnerId(ctx, argsPeerOwnerId, capManager) {
|
|
|
40241
40333
|
}
|
|
40242
40334
|
return argsPeerOwnerId;
|
|
40243
40335
|
}
|
|
40244
|
-
|
|
40245
|
-
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "inbox: guest ctx missing capabilityId");
|
|
40246
|
-
}
|
|
40247
|
-
let resolved = ctx.peerOwnerPrincipalId;
|
|
40248
|
-
if (!resolved) {
|
|
40249
|
-
const cap = capManager.findById(ctx.capabilityId);
|
|
40250
|
-
resolved = cap?.peerOwnerId;
|
|
40251
|
-
}
|
|
40336
|
+
const resolved = ctx.peerOwnerPrincipalId;
|
|
40252
40337
|
if (!resolved) {
|
|
40253
40338
|
throw new ClawdError(
|
|
40254
40339
|
ERROR_CODES.UNAUTHORIZED,
|
|
40255
|
-
`inbox: guest
|
|
40340
|
+
`inbox: guest ${ctx.principal.id} has no peerOwnerPrincipalId (auth frame missing selfPrincipalId)`
|
|
40256
40341
|
);
|
|
40257
40342
|
}
|
|
40258
40343
|
if (argsPeerOwnerId && argsPeerOwnerId !== resolved) {
|
|
@@ -40264,14 +40349,14 @@ function resolvePeerOwnerId(ctx, argsPeerOwnerId, capManager) {
|
|
|
40264
40349
|
return resolved;
|
|
40265
40350
|
}
|
|
40266
40351
|
function buildInboxHandlers(deps) {
|
|
40267
|
-
const { manager
|
|
40352
|
+
const { manager } = deps;
|
|
40268
40353
|
const postMessage = async (frame, _client, ctx) => {
|
|
40269
40354
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40270
40355
|
const args = InboxPostMessageArgsSchema.parse(rest);
|
|
40271
40356
|
if (!ctx) {
|
|
40272
40357
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:postMessage: missing ConnectionContext");
|
|
40273
40358
|
}
|
|
40274
|
-
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId
|
|
40359
|
+
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId);
|
|
40275
40360
|
const message = manager.postMessage({
|
|
40276
40361
|
peerOwnerId,
|
|
40277
40362
|
senderPrincipalId: ctx.principal.id,
|
|
@@ -40289,7 +40374,7 @@ function buildInboxHandlers(deps) {
|
|
|
40289
40374
|
if (!ctx) {
|
|
40290
40375
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:list: missing ConnectionContext");
|
|
40291
40376
|
}
|
|
40292
|
-
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId
|
|
40377
|
+
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId);
|
|
40293
40378
|
const messages = manager.list(peerOwnerId, args.sinceCreatedAt);
|
|
40294
40379
|
return {
|
|
40295
40380
|
response: { type: "inbox:list:ok", peerOwnerId, messages }
|
|
@@ -40301,7 +40386,7 @@ function buildInboxHandlers(deps) {
|
|
|
40301
40386
|
if (!ctx) {
|
|
40302
40387
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:markRead: missing ConnectionContext");
|
|
40303
40388
|
}
|
|
40304
|
-
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId
|
|
40389
|
+
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId);
|
|
40305
40390
|
const updated = manager.markRead({
|
|
40306
40391
|
peerOwnerId,
|
|
40307
40392
|
principalId: ctx.principal.id,
|
|
@@ -40323,116 +40408,45 @@ function buildInboxHandlers(deps) {
|
|
|
40323
40408
|
};
|
|
40324
40409
|
}
|
|
40325
40410
|
|
|
40326
|
-
// src/handlers/
|
|
40411
|
+
// src/handlers/contact.ts
|
|
40327
40412
|
init_protocol();
|
|
40328
40413
|
function ensureOwner(ctx) {
|
|
40329
40414
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
40330
40415
|
throw new ClawdError(
|
|
40331
40416
|
ERROR_CODES.UNAUTHORIZED,
|
|
40332
|
-
"UNAUTHORIZED:
|
|
40333
|
-
);
|
|
40334
|
-
}
|
|
40335
|
-
}
|
|
40336
|
-
function ensureGuestCtx(ctx) {
|
|
40337
|
-
if (!ctx || ctx.principal.kind !== "guest" || !ctx.capabilityId) {
|
|
40338
|
-
throw new ClawdError(
|
|
40339
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
40340
|
-
"UNAUTHORIZED: received-capability:autoAttach requires guest cap ctx"
|
|
40417
|
+
"UNAUTHORIZED: contact:* requires owner ctx"
|
|
40341
40418
|
);
|
|
40342
40419
|
}
|
|
40343
40420
|
}
|
|
40344
|
-
function
|
|
40345
|
-
const now = deps.now ?? Date.now;
|
|
40421
|
+
function buildContactHandlers(deps) {
|
|
40346
40422
|
const list = async (_frame, _client, ctx) => {
|
|
40347
40423
|
ensureOwner(ctx);
|
|
40348
40424
|
return {
|
|
40349
40425
|
response: {
|
|
40350
|
-
type: "
|
|
40351
|
-
|
|
40426
|
+
type: "contact:list:ok",
|
|
40427
|
+
contacts: deps.store.list()
|
|
40352
40428
|
}
|
|
40353
40429
|
};
|
|
40354
40430
|
};
|
|
40355
|
-
const add = async (frame, _client, ctx) => {
|
|
40356
|
-
ensureOwner(ctx);
|
|
40357
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40358
|
-
const args = ReceivedCapabilityAddArgsSchema.parse(rest);
|
|
40359
|
-
let conn;
|
|
40360
|
-
try {
|
|
40361
|
-
conn = await deps.connectRemote({
|
|
40362
|
-
url: args.remoteUrl,
|
|
40363
|
-
token: args.token,
|
|
40364
|
-
selfPrincipalId: deps.selfPrincipalId(),
|
|
40365
|
-
selfDisplayName: deps.selfDisplayName()
|
|
40366
|
-
});
|
|
40367
|
-
} catch (e) {
|
|
40368
|
-
throw new ClawdError(
|
|
40369
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
40370
|
-
`INVITE_REMOTE_UNREACHABLE: ${e.message}`
|
|
40371
|
-
);
|
|
40372
|
-
}
|
|
40373
|
-
try {
|
|
40374
|
-
const wh = await conn.whoami();
|
|
40375
|
-
const rc = {
|
|
40376
|
-
ownerPrincipalId: wh.ownerId,
|
|
40377
|
-
ownerDisplayName: wh.displayName,
|
|
40378
|
-
remoteUrl: args.remoteUrl,
|
|
40379
|
-
capabilityId: wh.capabilityId,
|
|
40380
|
-
capabilityToken: args.token,
|
|
40381
|
-
grants: wh.grants,
|
|
40382
|
-
receivedAt: now()
|
|
40383
|
-
};
|
|
40384
|
-
deps.store.upsert(rc);
|
|
40385
|
-
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
40386
|
-
return {
|
|
40387
|
-
response: { type: "received-capability:add:ok", receivedCap: rc }
|
|
40388
|
-
};
|
|
40389
|
-
} finally {
|
|
40390
|
-
try {
|
|
40391
|
-
conn.close();
|
|
40392
|
-
} catch {
|
|
40393
|
-
}
|
|
40394
|
-
}
|
|
40395
|
-
};
|
|
40396
40431
|
const remove = async (frame, _client, ctx) => {
|
|
40397
40432
|
ensureOwner(ctx);
|
|
40398
40433
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40399
|
-
const args =
|
|
40400
|
-
deps.store.remove(args.
|
|
40434
|
+
const args = ContactRemoveArgsSchema.parse(rest);
|
|
40435
|
+
deps.store.remove(args.principalId);
|
|
40401
40436
|
deps.broadcast({
|
|
40402
|
-
type: "
|
|
40403
|
-
|
|
40437
|
+
type: "contact:removed",
|
|
40438
|
+
principalId: args.principalId
|
|
40404
40439
|
});
|
|
40405
40440
|
return {
|
|
40406
40441
|
response: {
|
|
40407
|
-
type: "
|
|
40408
|
-
|
|
40442
|
+
type: "contact:remove:ok",
|
|
40443
|
+
principalId: args.principalId
|
|
40409
40444
|
}
|
|
40410
40445
|
};
|
|
40411
40446
|
};
|
|
40412
|
-
const autoAttach = async (frame, _client, ctx) => {
|
|
40413
|
-
ensureGuestCtx(ctx);
|
|
40414
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40415
|
-
const args = ReceivedCapabilityAutoAttachArgsSchema.parse(rest);
|
|
40416
|
-
const rc = {
|
|
40417
|
-
ownerPrincipalId: args.ownerPrincipalId,
|
|
40418
|
-
ownerDisplayName: args.ownerDisplayName,
|
|
40419
|
-
remoteUrl: args.remoteUrl,
|
|
40420
|
-
capabilityId: args.capabilityId,
|
|
40421
|
-
capabilityToken: args.token,
|
|
40422
|
-
grants: args.grants,
|
|
40423
|
-
receivedAt: now()
|
|
40424
|
-
};
|
|
40425
|
-
deps.store.upsert(rc);
|
|
40426
|
-
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
40427
|
-
return {
|
|
40428
|
-
response: { type: "received-capability:autoAttach:ok" }
|
|
40429
|
-
};
|
|
40430
|
-
};
|
|
40431
40447
|
return {
|
|
40432
|
-
"
|
|
40433
|
-
"
|
|
40434
|
-
"received-capability:remove": remove,
|
|
40435
|
-
"received-capability:autoAttach": autoAttach
|
|
40448
|
+
"contact:list": list,
|
|
40449
|
+
"contact:remove": remove
|
|
40436
40450
|
};
|
|
40437
40451
|
}
|
|
40438
40452
|
|
|
@@ -40454,14 +40468,13 @@ function buildWhoamiHandler(deps) {
|
|
|
40454
40468
|
usedCount: 0
|
|
40455
40469
|
};
|
|
40456
40470
|
} else {
|
|
40457
|
-
|
|
40458
|
-
|
|
40459
|
-
|
|
40460
|
-
|
|
40461
|
-
|
|
40462
|
-
|
|
40463
|
-
}
|
|
40464
|
-
capability = stripSecretHash(cap);
|
|
40471
|
+
capability = {
|
|
40472
|
+
id: ctx.principal.id,
|
|
40473
|
+
displayName: ctx.principal.displayName ?? "guest",
|
|
40474
|
+
grants: ctx.grants,
|
|
40475
|
+
issuedAt: 0,
|
|
40476
|
+
usedCount: 0
|
|
40477
|
+
};
|
|
40465
40478
|
}
|
|
40466
40479
|
const grantedPersonas = [];
|
|
40467
40480
|
const isGuest = ctx.principal.kind === "guest";
|
|
@@ -40492,6 +40505,116 @@ function buildWhoamiHandler(deps) {
|
|
|
40492
40505
|
};
|
|
40493
40506
|
}
|
|
40494
40507
|
|
|
40508
|
+
// src/handlers/feishu-auth.ts
|
|
40509
|
+
function buildFeishuAuthHandlers(deps) {
|
|
40510
|
+
const loginStart = async () => {
|
|
40511
|
+
const { authUrl, state } = deps.loginFlow.start();
|
|
40512
|
+
return {
|
|
40513
|
+
response: { type: "auth:login:start:ok", authUrl, state }
|
|
40514
|
+
};
|
|
40515
|
+
};
|
|
40516
|
+
const getIdentity = async () => {
|
|
40517
|
+
const record = deps.ownerIdentityStore.read();
|
|
40518
|
+
return {
|
|
40519
|
+
response: {
|
|
40520
|
+
type: "auth:getIdentity:ok",
|
|
40521
|
+
identity: record ? record.identity : null
|
|
40522
|
+
}
|
|
40523
|
+
};
|
|
40524
|
+
};
|
|
40525
|
+
const logout = async () => {
|
|
40526
|
+
deps.ownerIdentityStore.clear();
|
|
40527
|
+
return {
|
|
40528
|
+
response: { type: "auth:logout:ok" }
|
|
40529
|
+
};
|
|
40530
|
+
};
|
|
40531
|
+
return {
|
|
40532
|
+
"auth:login:start": loginStart,
|
|
40533
|
+
"auth:getIdentity": getIdentity,
|
|
40534
|
+
"auth:logout": logout
|
|
40535
|
+
};
|
|
40536
|
+
}
|
|
40537
|
+
|
|
40538
|
+
// src/handlers/hub.ts
|
|
40539
|
+
init_protocol();
|
|
40540
|
+
function ensureOwner2(ctx) {
|
|
40541
|
+
if (!ctx || ctx.principal.kind !== "owner") {
|
|
40542
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: hub:* requires owner ctx");
|
|
40543
|
+
}
|
|
40544
|
+
}
|
|
40545
|
+
function buildHubHandlers(deps) {
|
|
40546
|
+
const now = deps.now ?? Date.now;
|
|
40547
|
+
const list = async (_frame, _client, ctx) => {
|
|
40548
|
+
ensureOwner2(ctx);
|
|
40549
|
+
const hubs = await deps.listHubs();
|
|
40550
|
+
return {
|
|
40551
|
+
response: { type: "hub:list:ok", hubs }
|
|
40552
|
+
};
|
|
40553
|
+
};
|
|
40554
|
+
const connect = async (frame, _client, ctx) => {
|
|
40555
|
+
ensureOwner2(ctx);
|
|
40556
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40557
|
+
const args = HubConnectArgsSchema.parse(rest);
|
|
40558
|
+
const exchanged = await deps.exchange(args.hubId);
|
|
40559
|
+
const url = args.url ?? exchanged.hubUrl;
|
|
40560
|
+
if (!url) {
|
|
40561
|
+
throw new ClawdError(
|
|
40562
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
40563
|
+
"HUB_URL_UNKNOWN: hub \u672A\u4E0A\u62A5\u5730\u5740\uFF08\u5BF9\u65B9 daemon \u672A\u767B\u5F55\u6216\u672A\u5F00 tunnel\uFF09\uFF0C\u65E0\u6CD5\u8FDE\u63A5"
|
|
40564
|
+
);
|
|
40565
|
+
}
|
|
40566
|
+
let conn;
|
|
40567
|
+
try {
|
|
40568
|
+
conn = await deps.connectRemote({
|
|
40569
|
+
url,
|
|
40570
|
+
token: exchanged.token,
|
|
40571
|
+
selfPrincipalId: deps.selfPrincipalId(),
|
|
40572
|
+
selfDisplayName: deps.selfDisplayName(),
|
|
40573
|
+
// Phase 2 自动反向(spec 决策 #11):自报本机可达地址,让 hub 反向加我为联系人;
|
|
40574
|
+
// 本机无 tunnel → null → connectRemote 省略 selfUrl 字段(不造假数据)
|
|
40575
|
+
selfUrl: deps.selfUrl() ?? void 0
|
|
40576
|
+
});
|
|
40577
|
+
} catch (e) {
|
|
40578
|
+
throw new ClawdError(
|
|
40579
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
40580
|
+
`HUB_UNREACHABLE: ${e.message}`
|
|
40581
|
+
);
|
|
40582
|
+
}
|
|
40583
|
+
try {
|
|
40584
|
+
const wh = await conn.whoami();
|
|
40585
|
+
const contact = {
|
|
40586
|
+
principalId: wh.ownerId,
|
|
40587
|
+
displayName: wh.displayName || args.name || args.hubId,
|
|
40588
|
+
remoteUrl: url,
|
|
40589
|
+
// Phase 2:connectToken 存中心 server 签发的 connect token(断线重连复用,
|
|
40590
|
+
// 不需要重新换票——token 长期有效,撤销由中心 server introspect 兜底)
|
|
40591
|
+
connectToken: exchanged.token,
|
|
40592
|
+
grants: wh.grants,
|
|
40593
|
+
addedAt: now()
|
|
40594
|
+
};
|
|
40595
|
+
deps.store.upsert(contact);
|
|
40596
|
+
deps.broadcast({ type: "contact:added", contact });
|
|
40597
|
+
return {
|
|
40598
|
+
response: {
|
|
40599
|
+
type: "hub:connect:ok",
|
|
40600
|
+
hubId: wh.ownerId,
|
|
40601
|
+
name: contact.displayName,
|
|
40602
|
+
url
|
|
40603
|
+
}
|
|
40604
|
+
};
|
|
40605
|
+
} finally {
|
|
40606
|
+
try {
|
|
40607
|
+
conn.close();
|
|
40608
|
+
} catch {
|
|
40609
|
+
}
|
|
40610
|
+
}
|
|
40611
|
+
};
|
|
40612
|
+
return {
|
|
40613
|
+
"hub:list": list,
|
|
40614
|
+
"hub:connect": connect
|
|
40615
|
+
};
|
|
40616
|
+
}
|
|
40617
|
+
|
|
40495
40618
|
// src/handlers/meta.ts
|
|
40496
40619
|
var import_node_os15 = __toESM(require("os"), 1);
|
|
40497
40620
|
init_protocol();
|
|
@@ -41452,62 +41575,6 @@ function buildExtensionHandlers(deps) {
|
|
|
41452
41575
|
};
|
|
41453
41576
|
}
|
|
41454
41577
|
|
|
41455
|
-
// src/handlers/app-builder.ts
|
|
41456
|
-
function buildAppBuilderHandlers(deps) {
|
|
41457
|
-
const { store, deleteSessionsByProject, devServerLifecycle } = deps;
|
|
41458
|
-
const listProjects = async () => {
|
|
41459
|
-
const projects = await store.list();
|
|
41460
|
-
return {
|
|
41461
|
-
response: {
|
|
41462
|
-
projects: projects.map((p2) => ({
|
|
41463
|
-
...p2,
|
|
41464
|
-
isRunning: devServerLifecycle.isRunning(p2.name),
|
|
41465
|
-
boundSessionId: devServerLifecycle.boundSessionId(p2.name)
|
|
41466
|
-
}))
|
|
41467
|
-
}
|
|
41468
|
-
};
|
|
41469
|
-
};
|
|
41470
|
-
const createProject = async (frame) => {
|
|
41471
|
-
const name = frame.name;
|
|
41472
|
-
if (typeof name !== "string") throw new Error("createProject: name must be string");
|
|
41473
|
-
const project = await store.create(name);
|
|
41474
|
-
return { response: { project } };
|
|
41475
|
-
};
|
|
41476
|
-
const deleteProject = async (frame) => {
|
|
41477
|
-
const name = frame.name;
|
|
41478
|
-
if (typeof name !== "string") throw new Error("deleteProject: name must be string");
|
|
41479
|
-
await devServerLifecycle.stopForProject(name);
|
|
41480
|
-
await deleteSessionsByProject(name);
|
|
41481
|
-
await store.delete(name);
|
|
41482
|
-
return { response: {} };
|
|
41483
|
-
};
|
|
41484
|
-
const updateProjectPort = async (frame) => {
|
|
41485
|
-
const f = frame;
|
|
41486
|
-
if (typeof f.name !== "string") throw new Error("updateProjectPort: name must be string");
|
|
41487
|
-
if (typeof f.newPort !== "number") throw new Error("updateProjectPort: newPort must be number");
|
|
41488
|
-
const entry = devServerLifecycle.boundEntry(f.name);
|
|
41489
|
-
await devServerLifecycle.stopForProject(f.name);
|
|
41490
|
-
const project = await store.updatePort(f.name, f.newPort);
|
|
41491
|
-
if (entry) {
|
|
41492
|
-
await devServerLifecycle.restartForProjectAt({
|
|
41493
|
-
projectName: f.name,
|
|
41494
|
-
newPort: f.newPort,
|
|
41495
|
-
sessionId: entry.sessionId,
|
|
41496
|
-
cwd: entry.cwd,
|
|
41497
|
-
tunnelHost: entry.tunnelHost,
|
|
41498
|
-
devCommand: entry.devCommand
|
|
41499
|
-
});
|
|
41500
|
-
}
|
|
41501
|
-
return { response: { project } };
|
|
41502
|
-
};
|
|
41503
|
-
return {
|
|
41504
|
-
"appBuilder:listProjects": listProjects,
|
|
41505
|
-
"appBuilder:createProject": createProject,
|
|
41506
|
-
"appBuilder:deleteProject": deleteProject,
|
|
41507
|
-
"appBuilder:updateProjectPort": updateProjectPort
|
|
41508
|
-
};
|
|
41509
|
-
}
|
|
41510
|
-
|
|
41511
41578
|
// src/extension/registry.ts
|
|
41512
41579
|
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
41513
41580
|
var import_node_path39 = __toESM(require("path"), 1);
|
|
@@ -41596,13 +41663,7 @@ async function uninstall(deps) {
|
|
|
41596
41663
|
// src/handlers/index.ts
|
|
41597
41664
|
function buildMethodHandlers(deps) {
|
|
41598
41665
|
return {
|
|
41599
|
-
...buildSessionHandlers(
|
|
41600
|
-
...deps,
|
|
41601
|
-
devServerLifecycle: deps.devServerLifecycle,
|
|
41602
|
-
appBuilderStore: deps.appBuilderStore,
|
|
41603
|
-
getTunnelUrl: deps.getTunnelUrl,
|
|
41604
|
-
logger: deps.logger
|
|
41605
|
-
}),
|
|
41666
|
+
...buildSessionHandlers(deps),
|
|
41606
41667
|
...buildPermissionHandlers(deps),
|
|
41607
41668
|
...buildHistoryHandlers(deps),
|
|
41608
41669
|
...buildWorkspaceHandlers(deps),
|
|
@@ -41614,20 +41675,14 @@ function buildMethodHandlers(deps) {
|
|
|
41614
41675
|
personaRegistry: deps.personaRegistry
|
|
41615
41676
|
}),
|
|
41616
41677
|
...buildCapabilityHandlers({
|
|
41617
|
-
manager: deps.capabilityManager
|
|
41618
|
-
getShareBaseUrl: deps.getShareBaseUrl,
|
|
41619
|
-
getPersonalCapability: deps.getPersonalCapability
|
|
41678
|
+
manager: deps.capabilityManager
|
|
41620
41679
|
}),
|
|
41621
41680
|
...buildInboxHandlers({
|
|
41622
|
-
manager: deps.inboxManager
|
|
41623
|
-
capManager: deps.capabilityManager
|
|
41681
|
+
manager: deps.inboxManager
|
|
41624
41682
|
}),
|
|
41625
|
-
...
|
|
41626
|
-
store: deps.
|
|
41627
|
-
|
|
41628
|
-
broadcast: deps.broadcastToOwners,
|
|
41629
|
-
selfPrincipalId: () => deps.ownerPrincipalId,
|
|
41630
|
-
selfDisplayName: () => deps.ownerDisplayName
|
|
41683
|
+
...buildContactHandlers({
|
|
41684
|
+
store: deps.contactStore,
|
|
41685
|
+
broadcast: deps.broadcastToOwners
|
|
41631
41686
|
}),
|
|
41632
41687
|
whoami: buildWhoamiHandler({
|
|
41633
41688
|
ownerDisplayName: deps.ownerDisplayName,
|
|
@@ -41635,6 +41690,8 @@ function buildMethodHandlers(deps) {
|
|
|
41635
41690
|
personaStore: deps.personaStore,
|
|
41636
41691
|
capabilityManager: deps.capabilityManager
|
|
41637
41692
|
}),
|
|
41693
|
+
...buildFeishuAuthHandlers(deps.feishuAuth),
|
|
41694
|
+
...buildHubHandlers(deps.feishuHub),
|
|
41638
41695
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {},
|
|
41639
41696
|
...buildExtensionHandlers({
|
|
41640
41697
|
loadAll,
|
|
@@ -41643,254 +41700,10 @@ function buildMethodHandlers(deps) {
|
|
|
41643
41700
|
root: extensionsRoot(),
|
|
41644
41701
|
publishedChannelStore: deps.publishedChannelStore,
|
|
41645
41702
|
bundleCache: deps.bundleCache
|
|
41646
|
-
}),
|
|
41647
|
-
...buildAppBuilderHandlers({
|
|
41648
|
-
store: deps.appBuilderStore,
|
|
41649
|
-
deleteSessionsByProject: deps.deleteSessionsByProject,
|
|
41650
|
-
devServerLifecycle: deps.devServerLifecycle
|
|
41651
41703
|
})
|
|
41652
41704
|
};
|
|
41653
41705
|
}
|
|
41654
41706
|
|
|
41655
|
-
// src/app-builder/project-store.ts
|
|
41656
|
-
var import_node_fs29 = require("fs");
|
|
41657
|
-
var import_node_path41 = require("path");
|
|
41658
|
-
init_protocol();
|
|
41659
|
-
var PROJECTS_DIR = "projects";
|
|
41660
|
-
var META_FILE = ".clawd-project.json";
|
|
41661
|
-
var ProjectStore = class {
|
|
41662
|
-
/** @param personaRoot persona 目录,例如 `~/.clawd/personas/persona-app-builder/` */
|
|
41663
|
-
constructor(personaRoot) {
|
|
41664
|
-
this.personaRoot = personaRoot;
|
|
41665
|
-
}
|
|
41666
|
-
personaRoot;
|
|
41667
|
-
/** projects/<name>/.clawd-project.json 路径 */
|
|
41668
|
-
metaPath(name) {
|
|
41669
|
-
return (0, import_node_path41.join)(this.projectsRoot(), name, META_FILE);
|
|
41670
|
-
}
|
|
41671
|
-
/** projects/<name>/ 目录路径(cwd 用) */
|
|
41672
|
-
projectDir(name) {
|
|
41673
|
-
return (0, import_node_path41.join)(this.projectsRoot(), name);
|
|
41674
|
-
}
|
|
41675
|
-
projectsRoot() {
|
|
41676
|
-
return (0, import_node_path41.join)(this.personaRoot, PROJECTS_DIR);
|
|
41677
|
-
}
|
|
41678
|
-
async list() {
|
|
41679
|
-
let entries;
|
|
41680
|
-
try {
|
|
41681
|
-
entries = await import_node_fs29.promises.readdir(this.projectsRoot());
|
|
41682
|
-
} catch (err) {
|
|
41683
|
-
if (err.code === "ENOENT") return [];
|
|
41684
|
-
throw err;
|
|
41685
|
-
}
|
|
41686
|
-
const out = [];
|
|
41687
|
-
for (const name of entries) {
|
|
41688
|
-
try {
|
|
41689
|
-
const raw = await import_node_fs29.promises.readFile(this.metaPath(name), "utf8");
|
|
41690
|
-
const json = JSON.parse(raw);
|
|
41691
|
-
let migrated = false;
|
|
41692
|
-
if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
|
|
41693
|
-
json.devCommand = DEFAULT_DEV_COMMAND;
|
|
41694
|
-
migrated = true;
|
|
41695
|
-
}
|
|
41696
|
-
const parsed = ProjectMetadataSchema.safeParse(json);
|
|
41697
|
-
if (parsed.success) {
|
|
41698
|
-
out.push(parsed.data);
|
|
41699
|
-
if (migrated) {
|
|
41700
|
-
void import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
|
|
41701
|
-
});
|
|
41702
|
-
}
|
|
41703
|
-
}
|
|
41704
|
-
} catch {
|
|
41705
|
-
}
|
|
41706
|
-
}
|
|
41707
|
-
return out.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
41708
|
-
}
|
|
41709
|
-
async create(name) {
|
|
41710
|
-
const existing = await this.list();
|
|
41711
|
-
if (existing.some((p2) => p2.name === name)) {
|
|
41712
|
-
throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
|
|
41713
|
-
}
|
|
41714
|
-
const usedPorts = new Set(existing.map((p2) => p2.port));
|
|
41715
|
-
let port = -1;
|
|
41716
|
-
for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) {
|
|
41717
|
-
if (!usedPorts.has(p2)) {
|
|
41718
|
-
port = p2;
|
|
41719
|
-
break;
|
|
41720
|
-
}
|
|
41721
|
-
}
|
|
41722
|
-
if (port < 0) {
|
|
41723
|
-
throw new Error(
|
|
41724
|
-
`port pool exhausted / \u7AEF\u53E3\u6C60\u5DF2\u6EE1\uFF08${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}\uFF09\uFF0C\u5148\u5220\u4E00\u4E2A project \u91CA\u653E\u7AEF\u53E3`
|
|
41725
|
-
);
|
|
41726
|
-
}
|
|
41727
|
-
const meta = {
|
|
41728
|
-
name,
|
|
41729
|
-
port,
|
|
41730
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41731
|
-
devCommand: DEFAULT_DEV_COMMAND
|
|
41732
|
-
};
|
|
41733
|
-
const validated = ProjectMetadataSchema.safeParse(meta);
|
|
41734
|
-
if (!validated.success) {
|
|
41735
|
-
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
41736
|
-
}
|
|
41737
|
-
const dir = this.projectDir(name);
|
|
41738
|
-
await import_node_fs29.promises.mkdir(dir, { recursive: true });
|
|
41739
|
-
await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
|
|
41740
|
-
return meta;
|
|
41741
|
-
}
|
|
41742
|
-
async delete(name) {
|
|
41743
|
-
const dir = this.projectDir(name);
|
|
41744
|
-
await import_node_fs29.promises.rm(dir, { recursive: true, force: true });
|
|
41745
|
-
}
|
|
41746
|
-
async updatePort(name, newPort) {
|
|
41747
|
-
if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
|
|
41748
|
-
throw new Error(
|
|
41749
|
-
`port range invalid: must be ${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}, got ${newPort}`
|
|
41750
|
-
);
|
|
41751
|
-
}
|
|
41752
|
-
const list = await this.list();
|
|
41753
|
-
const target = list.find((p2) => p2.name === name);
|
|
41754
|
-
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
41755
|
-
const conflict = list.find((p2) => p2.name !== name && p2.port === newPort);
|
|
41756
|
-
if (conflict) {
|
|
41757
|
-
throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
|
|
41758
|
-
}
|
|
41759
|
-
const updated = { ...target, port: newPort };
|
|
41760
|
-
await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
41761
|
-
return updated;
|
|
41762
|
-
}
|
|
41763
|
-
};
|
|
41764
|
-
|
|
41765
|
-
// src/app-builder/dev-server-supervisor.ts
|
|
41766
|
-
var import_node_child_process7 = require("child_process");
|
|
41767
|
-
var import_node_events2 = require("events");
|
|
41768
|
-
var DevServerSupervisor = class extends import_node_events2.EventEmitter {
|
|
41769
|
-
running = /* @__PURE__ */ new Map();
|
|
41770
|
-
// key: sessionId
|
|
41771
|
-
logger = { warn: () => {
|
|
41772
|
-
}, info: () => {
|
|
41773
|
-
} };
|
|
41774
|
-
/** daemon entry 启动后注入 logger,让 child 输出走 clawd.log */
|
|
41775
|
-
setLogger(logger) {
|
|
41776
|
-
this.logger = logger;
|
|
41777
|
-
}
|
|
41778
|
-
startForSession(args) {
|
|
41779
|
-
if (this.running.has(args.sessionId)) return;
|
|
41780
|
-
const augmentedPath = [
|
|
41781
|
-
process.env.PATH ?? "",
|
|
41782
|
-
"/opt/homebrew/bin",
|
|
41783
|
-
"/usr/local/bin",
|
|
41784
|
-
`${process.env.HOME}/.nvm/versions/node`,
|
|
41785
|
-
`${process.env.HOME}/.local/share/pnpm`
|
|
41786
|
-
].filter(Boolean).join(":");
|
|
41787
|
-
const env = {
|
|
41788
|
-
...process.env,
|
|
41789
|
-
PATH: augmentedPath,
|
|
41790
|
-
CLAWD_TUNNEL_HOST: args.tunnelHost ?? "",
|
|
41791
|
-
CLAWD_PREVIEW_PORT: String(args.port)
|
|
41792
|
-
};
|
|
41793
|
-
const cmd = args.devCommand.replace(/\$CLAWD_PREVIEW_PORT/g, String(args.port));
|
|
41794
|
-
this.logger.info("dev-server.start", {
|
|
41795
|
-
projectName: args.projectName,
|
|
41796
|
-
port: args.port,
|
|
41797
|
-
cwd: args.cwd,
|
|
41798
|
-
sessionId: args.sessionId,
|
|
41799
|
-
tunnelHost: args.tunnelHost,
|
|
41800
|
-
devCommand: cmd
|
|
41801
|
-
});
|
|
41802
|
-
const child = (0, import_node_child_process7.spawn)("sh", ["-c", cmd], { cwd: args.cwd, env, stdio: "pipe" });
|
|
41803
|
-
const entry = { ...args, process: child };
|
|
41804
|
-
this.running.set(args.sessionId, entry);
|
|
41805
|
-
child.stdout?.on("data", (chunk) => {
|
|
41806
|
-
this.logger.info("dev-server.stdout", {
|
|
41807
|
-
projectName: args.projectName,
|
|
41808
|
-
port: args.port,
|
|
41809
|
-
chunk: chunk.toString().trimEnd()
|
|
41810
|
-
});
|
|
41811
|
-
});
|
|
41812
|
-
child.stderr?.on("data", (chunk) => {
|
|
41813
|
-
this.logger.warn("dev-server.stderr", {
|
|
41814
|
-
projectName: args.projectName,
|
|
41815
|
-
port: args.port,
|
|
41816
|
-
chunk: chunk.toString().trimEnd()
|
|
41817
|
-
});
|
|
41818
|
-
});
|
|
41819
|
-
child.on("error", (err) => {
|
|
41820
|
-
this.running.delete(args.sessionId);
|
|
41821
|
-
this.logger.warn("dev-server.spawn-failed", {
|
|
41822
|
-
projectName: args.projectName,
|
|
41823
|
-
port: args.port,
|
|
41824
|
-
error: err.message,
|
|
41825
|
-
code: err.code
|
|
41826
|
-
});
|
|
41827
|
-
this.emit("devServer:failed", {
|
|
41828
|
-
sessionId: args.sessionId,
|
|
41829
|
-
projectName: args.projectName,
|
|
41830
|
-
port: args.port,
|
|
41831
|
-
exitCode: null
|
|
41832
|
-
});
|
|
41833
|
-
});
|
|
41834
|
-
child.on("exit", (code) => {
|
|
41835
|
-
this.running.delete(args.sessionId);
|
|
41836
|
-
this.logger.info("dev-server.exit", { projectName: args.projectName, port: args.port, code });
|
|
41837
|
-
if (code !== 0 && code !== null) {
|
|
41838
|
-
this.emit("devServer:failed", {
|
|
41839
|
-
sessionId: args.sessionId,
|
|
41840
|
-
projectName: args.projectName,
|
|
41841
|
-
port: args.port,
|
|
41842
|
-
exitCode: code
|
|
41843
|
-
});
|
|
41844
|
-
}
|
|
41845
|
-
});
|
|
41846
|
-
}
|
|
41847
|
-
stopForSession(sessionId) {
|
|
41848
|
-
const entry = this.running.get(sessionId);
|
|
41849
|
-
if (!entry) return Promise.resolve();
|
|
41850
|
-
return new Promise((resolve6) => {
|
|
41851
|
-
entry.process.once("exit", () => {
|
|
41852
|
-
this.running.delete(sessionId);
|
|
41853
|
-
resolve6();
|
|
41854
|
-
});
|
|
41855
|
-
entry.process.kill("SIGTERM");
|
|
41856
|
-
});
|
|
41857
|
-
}
|
|
41858
|
-
async stopForProject(projectName) {
|
|
41859
|
-
const entry = [...this.running.values()].find((e) => e.projectName === projectName);
|
|
41860
|
-
if (!entry) return;
|
|
41861
|
-
await this.stopForSession(entry.sessionId);
|
|
41862
|
-
}
|
|
41863
|
-
async restartForProjectAt(args) {
|
|
41864
|
-
await this.stopForSession(args.sessionId);
|
|
41865
|
-
this.startForSession({
|
|
41866
|
-
sessionId: args.sessionId,
|
|
41867
|
-
projectName: args.projectName,
|
|
41868
|
-
port: args.newPort,
|
|
41869
|
-
cwd: args.cwd,
|
|
41870
|
-
tunnelHost: args.tunnelHost,
|
|
41871
|
-
devCommand: args.devCommand
|
|
41872
|
-
});
|
|
41873
|
-
}
|
|
41874
|
-
isRunning(projectName) {
|
|
41875
|
-
return [...this.running.values()].some((e) => e.projectName === projectName);
|
|
41876
|
-
}
|
|
41877
|
-
boundSessionId(projectName) {
|
|
41878
|
-
const entry = [...this.running.values()].find((e) => e.projectName === projectName);
|
|
41879
|
-
return entry?.sessionId ?? null;
|
|
41880
|
-
}
|
|
41881
|
-
boundEntry(projectName) {
|
|
41882
|
-
const entry = [...this.running.values()].find((e) => e.projectName === projectName);
|
|
41883
|
-
if (!entry) return null;
|
|
41884
|
-
return {
|
|
41885
|
-
sessionId: entry.sessionId,
|
|
41886
|
-
cwd: entry.cwd,
|
|
41887
|
-
port: entry.port,
|
|
41888
|
-
tunnelHost: entry.tunnelHost,
|
|
41889
|
-
devCommand: entry.devCommand
|
|
41890
|
-
};
|
|
41891
|
-
}
|
|
41892
|
-
};
|
|
41893
|
-
|
|
41894
41707
|
// src/handlers/method-grants.ts
|
|
41895
41708
|
var ADMIN_ANY = {
|
|
41896
41709
|
kind: "fixed",
|
|
@@ -41907,23 +41720,22 @@ var METHOD_GRANT_MAP = {
|
|
|
41907
41720
|
// "查 capabilityManager 拿 caller 的 guest capability"。
|
|
41908
41721
|
"whoami": { kind: "public" },
|
|
41909
41722
|
// ---- capability platform(admin-only) ----
|
|
41910
|
-
//
|
|
41911
|
-
// personal
|
|
41723
|
+
// 飞书统一身份 Phase 2 (2026-06-01, spec 决策 #12): personal-cap:get / capability:issue /
|
|
41724
|
+
// bindPeer 已下线(personal token / 邀请码全链路删除)。capability:list / delete 保留供调试 +
|
|
41725
|
+
// 撤销级联 + 清旧 per-peer cap。
|
|
41912
41726
|
"capability:list": ADMIN_ANY,
|
|
41913
41727
|
"capability:delete": ADMIN_ANY,
|
|
41914
|
-
|
|
41915
|
-
//
|
|
41916
|
-
//
|
|
41917
|
-
// guest ctx.capabilityId === args.capabilityId, 防 guest 越权操作别的 channel.
|
|
41728
|
+
// ---- inbox: 双投 P2P IM ----
|
|
41729
|
+
// 三条都 'public' — 能连上 = 已通过 introspect 验票. handler 内额外校验
|
|
41730
|
+
// guest peerOwnerId 与 ctx.peerOwnerPrincipalId 一致, 防 guest 越权操作别的 DM 桶.
|
|
41918
41731
|
"inbox:list": { kind: "public" },
|
|
41919
41732
|
"inbox:markRead": { kind: "public" },
|
|
41920
41733
|
"inbox:postMessage": { kind: "public" },
|
|
41921
|
-
// ----
|
|
41922
|
-
//
|
|
41923
|
-
|
|
41924
|
-
"
|
|
41925
|
-
"
|
|
41926
|
-
"received-capability:autoAttach": { kind: "public" },
|
|
41734
|
+
// ---- contact:* (联系人列表) ----
|
|
41735
|
+
// 飞书统一身份 Phase 3 (决策 #14): received-capability:* 正名 contact:*。
|
|
41736
|
+
// 写入路径在 hub:connect / 自动反向;list / remove 作联系人读 / 删,owner-only。
|
|
41737
|
+
"contact:list": ADMIN_ANY,
|
|
41738
|
+
"contact:remove": ADMIN_ANY,
|
|
41927
41739
|
// ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
|
|
41928
41740
|
// dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
|
|
41929
41741
|
// owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
|
|
@@ -41964,7 +41776,7 @@ var METHOD_GRANT_MAP = {
|
|
|
41964
41776
|
"history:list": CAPABILITY_SCOPED,
|
|
41965
41777
|
// history:read 取一条 session 的 CC JSONL 历史。guest 进 persona VM 后必须能读自己
|
|
41966
41778
|
// 创建的 session 历史(chat view 加载历史消息走这条),所以下沉到 capability-scoped;
|
|
41967
|
-
// handler 内按 ctx.
|
|
41779
|
+
// handler 内按 ctx.principal.id === file.creatorPrincipalId 拦越权。
|
|
41968
41780
|
"history:read": CAPABILITY_SCOPED,
|
|
41969
41781
|
"history:subagents": CAPABILITY_SCOPED,
|
|
41970
41782
|
"history:subagent-read": CAPABILITY_SCOPED,
|
|
@@ -42021,14 +41833,15 @@ var METHOD_GRANT_MAP = {
|
|
|
42021
41833
|
// guest-self:guest 在自己 daemon 上触发安装与更新(无持久化 subscription 概念)
|
|
42022
41834
|
"extension.install-from-channel": ADMIN_ANY,
|
|
42023
41835
|
"extension.update-from-channel": ADMIN_ANY,
|
|
42024
|
-
// ----
|
|
42025
|
-
//
|
|
42026
|
-
|
|
42027
|
-
|
|
42028
|
-
"
|
|
42029
|
-
|
|
42030
|
-
|
|
42031
|
-
"
|
|
41836
|
+
// ---- auth:* 飞书统一身份 Phase 1(owner-only) ----
|
|
41837
|
+
// 登录/登出/查身份都是 owner 本机操作,guest 不可调
|
|
41838
|
+
"auth:login:start": ADMIN_ANY,
|
|
41839
|
+
"auth:getIdentity": ADMIN_ANY,
|
|
41840
|
+
"auth:logout": ADMIN_ANY,
|
|
41841
|
+
// ---- hub:* 飞书统一身份 Phase 2(owner-only) ----
|
|
41842
|
+
// hub 目录 / 连接都是 owner 本机操作(用 owner 的 ttcJwt 找中心 server),guest 不可调
|
|
41843
|
+
"hub:list": ADMIN_ANY,
|
|
41844
|
+
"hub:connect": ADMIN_ANY
|
|
42032
41845
|
};
|
|
42033
41846
|
function computeGrantForFrame(method, frame) {
|
|
42034
41847
|
const rule = METHOD_GRANT_MAP[method];
|
|
@@ -42044,12 +41857,12 @@ function computeGrantForFrame(method, frame) {
|
|
|
42044
41857
|
}
|
|
42045
41858
|
|
|
42046
41859
|
// src/extension/runtime.ts
|
|
42047
|
-
var
|
|
42048
|
-
var
|
|
41860
|
+
var import_node_child_process7 = require("child_process");
|
|
41861
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
42049
41862
|
var import_promises10 = require("timers/promises");
|
|
42050
41863
|
|
|
42051
41864
|
// src/extension/port-allocator.ts
|
|
42052
|
-
var
|
|
41865
|
+
var import_node_net = __toESM(require("net"), 1);
|
|
42053
41866
|
var PortExhaustedError = class extends Error {
|
|
42054
41867
|
constructor(min, max) {
|
|
42055
41868
|
super(`no free port in [${min},${max}]`);
|
|
@@ -42062,7 +41875,7 @@ var PortExhaustedError = class extends Error {
|
|
|
42062
41875
|
};
|
|
42063
41876
|
function probe(port) {
|
|
42064
41877
|
return new Promise((resolve6) => {
|
|
42065
|
-
const srv =
|
|
41878
|
+
const srv = import_node_net.default.createServer();
|
|
42066
41879
|
srv.once("error", () => resolve6(false));
|
|
42067
41880
|
srv.once("listening", () => {
|
|
42068
41881
|
srv.close(() => resolve6(true));
|
|
@@ -42146,13 +41959,13 @@ var Runtime = class {
|
|
|
42146
41959
|
/\$CLAWOS_EXT_PORT/g,
|
|
42147
41960
|
String(port)
|
|
42148
41961
|
);
|
|
42149
|
-
const dir =
|
|
41962
|
+
const dir = import_node_path41.default.join(this.root, extId);
|
|
42150
41963
|
const env = {
|
|
42151
41964
|
...process.env,
|
|
42152
41965
|
CLAWOS_EXT_PORT: String(port),
|
|
42153
41966
|
CLAWOS_EXT_ID: extId
|
|
42154
41967
|
};
|
|
42155
|
-
const child = (0,
|
|
41968
|
+
const child = (0, import_node_child_process7.spawn)("sh", ["-c", cmd], {
|
|
42156
41969
|
cwd: dir,
|
|
42157
41970
|
env,
|
|
42158
41971
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -42258,7 +42071,7 @@ ${handle.stderrTail}`
|
|
|
42258
42071
|
|
|
42259
42072
|
// src/extension/published-channels.ts
|
|
42260
42073
|
var import_promises11 = __toESM(require("fs/promises"), 1);
|
|
42261
|
-
var
|
|
42074
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
42262
42075
|
init_zod();
|
|
42263
42076
|
var PublishedChannelsError = class extends Error {
|
|
42264
42077
|
constructor(code, message) {
|
|
@@ -42357,7 +42170,7 @@ var PublishedChannelStore = class {
|
|
|
42357
42170
|
)
|
|
42358
42171
|
};
|
|
42359
42172
|
const tmp = `${this.filePath}.tmp`;
|
|
42360
|
-
await import_promises11.default.mkdir(
|
|
42173
|
+
await import_promises11.default.mkdir(import_node_path42.default.dirname(this.filePath), { recursive: true });
|
|
42361
42174
|
await import_promises11.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
42362
42175
|
await import_promises11.default.rename(tmp, this.filePath);
|
|
42363
42176
|
}
|
|
@@ -42365,7 +42178,7 @@ var PublishedChannelStore = class {
|
|
|
42365
42178
|
|
|
42366
42179
|
// src/extension/bundle-cache.ts
|
|
42367
42180
|
var import_promises12 = __toESM(require("fs/promises"), 1);
|
|
42368
|
-
var
|
|
42181
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
42369
42182
|
var BundleCache = class {
|
|
42370
42183
|
constructor(rootDir) {
|
|
42371
42184
|
this.rootDir = rootDir;
|
|
@@ -42374,14 +42187,14 @@ var BundleCache = class {
|
|
|
42374
42187
|
/** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
|
|
42375
42188
|
async write(snapshotHash, buffer) {
|
|
42376
42189
|
await import_promises12.default.mkdir(this.rootDir, { recursive: true });
|
|
42377
|
-
const file =
|
|
42190
|
+
const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
42378
42191
|
const tmp = `${file}.tmp`;
|
|
42379
42192
|
await import_promises12.default.writeFile(tmp, buffer, { mode: 384 });
|
|
42380
42193
|
await import_promises12.default.rename(tmp, file);
|
|
42381
42194
|
}
|
|
42382
42195
|
/** Returns the bundle bytes, or null when the file doesn't exist. */
|
|
42383
42196
|
async read(snapshotHash) {
|
|
42384
|
-
const file =
|
|
42197
|
+
const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
42385
42198
|
try {
|
|
42386
42199
|
return await import_promises12.default.readFile(file);
|
|
42387
42200
|
} catch (e) {
|
|
@@ -42391,7 +42204,7 @@ var BundleCache = class {
|
|
|
42391
42204
|
}
|
|
42392
42205
|
/** Idempotent — missing file is not an error. */
|
|
42393
42206
|
async delete(snapshotHash) {
|
|
42394
|
-
const file =
|
|
42207
|
+
const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
42395
42208
|
await import_promises12.default.rm(file, { force: true });
|
|
42396
42209
|
}
|
|
42397
42210
|
};
|
|
@@ -42400,7 +42213,7 @@ var BundleCache = class {
|
|
|
42400
42213
|
async function startDaemon(config) {
|
|
42401
42214
|
const logger = createLogger({
|
|
42402
42215
|
level: config.logLevel,
|
|
42403
|
-
file:
|
|
42216
|
+
file: import_node_path44.default.join(config.dataDir, "clawd.log")
|
|
42404
42217
|
});
|
|
42405
42218
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
42406
42219
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -42418,19 +42231,20 @@ async function startDaemon(config) {
|
|
|
42418
42231
|
} else if (config.tunnel) {
|
|
42419
42232
|
resolvedAuthToken = authFile.token;
|
|
42420
42233
|
}
|
|
42421
|
-
const
|
|
42422
|
-
const
|
|
42234
|
+
const ownerIdentityStore = new OwnerIdentityStore(config.dataDir);
|
|
42235
|
+
const feishuIdentity = ownerIdentityStore.read();
|
|
42236
|
+
let feishuActive = feishuIdentity !== null;
|
|
42237
|
+
let ownerPrincipalId = feishuIdentity?.identity.unionId ?? authFile.ownerPrincipalId;
|
|
42238
|
+
let ownerDisplayName = feishuIdentity?.identity.displayName ?? loadOwnerDisplayName(config.dataDir);
|
|
42423
42239
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
42424
42240
|
const inboxStore = new InboxStore(config.dataDir);
|
|
42425
42241
|
const inboxManager = new InboxManager(inboxStore, (_peerOwnerId, frame) => {
|
|
42426
42242
|
wsServer?.broadcastToOwners(frame);
|
|
42427
42243
|
});
|
|
42428
|
-
const
|
|
42429
|
-
|
|
42244
|
+
const contactStore = new ContactStore(config.dataDir);
|
|
42245
|
+
contactStore.load();
|
|
42430
42246
|
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
42431
42247
|
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
42432
|
-
const personalCapability = loadOrCreatePersonalCapability({ dataDir: config.dataDir });
|
|
42433
|
-
capabilityRegistry.upsertCapability(personalCapability.capability);
|
|
42434
42248
|
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
42435
42249
|
onDeleted: (cap) => {
|
|
42436
42250
|
const deletedAt = Date.now();
|
|
@@ -42439,7 +42253,7 @@ async function startDaemon(config) {
|
|
|
42439
42253
|
capabilityId: cap.id,
|
|
42440
42254
|
deletedAt
|
|
42441
42255
|
});
|
|
42442
|
-
wsServer?.
|
|
42256
|
+
wsServer?.closeConnectionsByGuestId(cap.id);
|
|
42443
42257
|
const cleanup = cleanupGuestSessionsForCapability(cap, sessionStoreFactory);
|
|
42444
42258
|
if (cleanup.removed.length > 0) {
|
|
42445
42259
|
logger.info("capability delete cascade: guest sessions removed", {
|
|
@@ -42456,15 +42270,36 @@ async function startDaemon(config) {
|
|
|
42456
42270
|
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
42457
42271
|
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
42458
42272
|
expectedToken: resolvedAuthToken,
|
|
42459
|
-
authenticate: (t,
|
|
42460
|
-
|
|
42273
|
+
authenticate: async (t, _selfPrincipalId, _selfDisplayName, selfUrl) => {
|
|
42274
|
+
const result = await authenticate(t, {
|
|
42461
42275
|
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
42462
42276
|
ownerPrincipalId,
|
|
42463
42277
|
ownerDisplayName,
|
|
42464
|
-
|
|
42465
|
-
|
|
42466
|
-
|
|
42278
|
+
// 飞书 gate(spec 决策 #9):未激活飞书身份时不接受 guest 入站(不注入 introspect
|
|
42279
|
+
// → 一律 BAD_TOKEN)。已激活时 hub 用自己的 ttcJwt 做验票方认证;
|
|
42280
|
+
// ttcJwt 每次验票时从 store 现读(热切换后立即用新身份)。
|
|
42281
|
+
...feishuActive ? {
|
|
42282
|
+
introspectConnectToken: async (token) => {
|
|
42283
|
+
const record = ownerIdentityStore.read();
|
|
42284
|
+
if (!record) return { active: false };
|
|
42285
|
+
return centralIntrospect({
|
|
42286
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
42287
|
+
hubTtcJwt: record.ttcToken,
|
|
42288
|
+
token
|
|
42289
|
+
});
|
|
42290
|
+
}
|
|
42291
|
+
} : {}
|
|
42467
42292
|
});
|
|
42293
|
+
if (result.ok && result.context.principal.kind === "guest") {
|
|
42294
|
+
autoReverseContact({
|
|
42295
|
+
store: contactStore,
|
|
42296
|
+
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
42297
|
+
unionId: result.context.principal.id,
|
|
42298
|
+
displayName: result.context.principal.displayName ?? result.context.principal.id,
|
|
42299
|
+
selfUrl
|
|
42300
|
+
});
|
|
42301
|
+
}
|
|
42302
|
+
return result;
|
|
42468
42303
|
},
|
|
42469
42304
|
onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
|
|
42470
42305
|
buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
|
|
@@ -42487,7 +42322,7 @@ async function startDaemon(config) {
|
|
|
42487
42322
|
const agents = new AgentsScanner();
|
|
42488
42323
|
const history = new ClaudeHistoryReader();
|
|
42489
42324
|
let transport = null;
|
|
42490
|
-
const personaStore = new PersonaStore(
|
|
42325
|
+
const personaStore = new PersonaStore(import_node_path44.default.join(config.dataDir, "personas"));
|
|
42491
42326
|
const defaultsRoot = findDefaultsRoot();
|
|
42492
42327
|
if (defaultsRoot) {
|
|
42493
42328
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -42504,7 +42339,7 @@ async function startDaemon(config) {
|
|
|
42504
42339
|
getAdapter,
|
|
42505
42340
|
historyReader: history,
|
|
42506
42341
|
dataDir: config.dataDir,
|
|
42507
|
-
personaRoot:
|
|
42342
|
+
personaRoot: import_node_path44.default.join(config.dataDir, "personas"),
|
|
42508
42343
|
personaStore,
|
|
42509
42344
|
ownerDisplayName,
|
|
42510
42345
|
ownerPrincipalId,
|
|
@@ -42528,10 +42363,10 @@ async function startDaemon(config) {
|
|
|
42528
42363
|
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
42529
42364
|
attachmentGroup: {
|
|
42530
42365
|
onFileEdit: (input) => {
|
|
42531
|
-
const absPath =
|
|
42366
|
+
const absPath = import_node_path44.default.isAbsolute(input.relPath) ? input.relPath : import_node_path44.default.join(input.cwd, input.relPath);
|
|
42532
42367
|
let size = 0;
|
|
42533
42368
|
try {
|
|
42534
|
-
size =
|
|
42369
|
+
size = import_node_fs29.default.statSync(absPath).size;
|
|
42535
42370
|
} catch (err) {
|
|
42536
42371
|
logger.warn("attachment.onFileEdit stat failed", {
|
|
42537
42372
|
sessionId: input.sessionId,
|
|
@@ -42614,23 +42449,33 @@ async function startDaemon(config) {
|
|
|
42614
42449
|
}
|
|
42615
42450
|
return `http://${config.host}:${config.port}`;
|
|
42616
42451
|
};
|
|
42452
|
+
const reportHubUrl = async () => {
|
|
42453
|
+
if (!feishuActive || !currentTunnelUrl) return;
|
|
42454
|
+
const record = ownerIdentityStore.read();
|
|
42455
|
+
if (!record) return;
|
|
42456
|
+
try {
|
|
42457
|
+
await centralUpsertHub({
|
|
42458
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
42459
|
+
ttcJwt: record.ttcToken,
|
|
42460
|
+
url: currentTunnelUrl,
|
|
42461
|
+
name: ownerDisplayName
|
|
42462
|
+
});
|
|
42463
|
+
logger.info("hub url reported to central server", { url: currentTunnelUrl });
|
|
42464
|
+
} catch (err) {
|
|
42465
|
+
logger.warn("hub url report failed (best-effort)", { err: err.message });
|
|
42466
|
+
}
|
|
42467
|
+
};
|
|
42617
42468
|
const extensionRuntime = new Runtime({ root: extensionsRoot() });
|
|
42618
42469
|
const publishedChannelStore = new PublishedChannelStore(publishedChannelsFile());
|
|
42619
42470
|
await publishedChannelStore.load();
|
|
42620
42471
|
const bundleCache = new BundleCache(bundleCacheRoot());
|
|
42621
|
-
const
|
|
42622
|
-
|
|
42623
|
-
|
|
42624
|
-
|
|
42625
|
-
|
|
42626
|
-
|
|
42627
|
-
|
|
42628
|
-
const effectivePreviewPorts = Array.from(
|
|
42629
|
-
/* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
|
|
42630
|
-
);
|
|
42631
|
-
const deleteSessionsByProject = async (_name) => {
|
|
42632
|
-
};
|
|
42633
|
-
const handlers = buildMethodHandlers({
|
|
42472
|
+
const loginFlow = new LoginFlow({
|
|
42473
|
+
store: ownerIdentityStore,
|
|
42474
|
+
fetchUserInfo: (ttcToken) => fetchTtcUserInfo({ apiBase: TTC_HOSTS.api, token: ttcToken }),
|
|
42475
|
+
ttcAuthBase: TTC_HOSTS.auth,
|
|
42476
|
+
getCallbackUrl: () => `http://127.0.0.1:${config.port}/auth/callback`
|
|
42477
|
+
});
|
|
42478
|
+
const makeHandlers = () => buildMethodHandlers({
|
|
42634
42479
|
manager,
|
|
42635
42480
|
workspace,
|
|
42636
42481
|
skills,
|
|
@@ -42669,17 +42514,12 @@ async function startDaemon(config) {
|
|
|
42669
42514
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
42670
42515
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
42671
42516
|
// guest path guard:candidate 必须在 personaRoot 子树下
|
|
42672
|
-
personaRoot:
|
|
42517
|
+
personaRoot: import_node_path44.default.join(config.dataDir, "personas")
|
|
42673
42518
|
},
|
|
42674
42519
|
// workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
|
|
42675
|
-
personaRoot:
|
|
42520
|
+
personaRoot: import_node_path44.default.join(config.dataDir, "personas"),
|
|
42676
42521
|
// capability:list / delete handler 依赖
|
|
42677
42522
|
capabilityManager,
|
|
42678
|
-
// personal-cap:get 返回的 shareBaseUrl 用此 base URL:
|
|
42679
|
-
// tunnel 拉起后切到反代 wss://... 地址;否则本机 ws://host:port。
|
|
42680
|
-
getShareBaseUrl: () => currentTunnelUrl ?? `ws://${config.host}:${config.port}`,
|
|
42681
|
-
// global personal token (2026-05-25): personal-cap:get handler 直接返回此 token + cap
|
|
42682
|
-
getPersonalCapability: () => personalCapability,
|
|
42683
42523
|
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
42684
42524
|
ownerDisplayName,
|
|
42685
42525
|
// owner-id stabilization: whoami 用稳定 ownerPrincipalId 替代 'owner' 字面量
|
|
@@ -42691,24 +42531,55 @@ async function startDaemon(config) {
|
|
|
42691
42531
|
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
42692
42532
|
inboxManager,
|
|
42693
42533
|
inboxStore,
|
|
42694
|
-
//
|
|
42695
|
-
|
|
42696
|
-
//
|
|
42534
|
+
// 联系人列表 store(hub:connect / 自动反向落同一 store)
|
|
42535
|
+
contactStore,
|
|
42536
|
+
// contact:removed broadcast;复用 capability:tokenIssued 同款通路
|
|
42697
42537
|
broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
|
|
42698
|
-
// received-capability:add 临时 ws 连远端跑 whoami — 直连 ws (不走 transport listener)
|
|
42699
|
-
connectRemote,
|
|
42700
42538
|
// extension runtime (v1: local-mode only). Instance owned by daemon top
|
|
42701
42539
|
// level so stopAll() runs in the shutdown hook below.
|
|
42702
42540
|
extensionRuntime,
|
|
42703
42541
|
// extension sharing v1 — owner-side published-channels store.
|
|
42704
42542
|
publishedChannelStore,
|
|
42705
42543
|
bundleCache,
|
|
42706
|
-
//
|
|
42707
|
-
|
|
42708
|
-
|
|
42709
|
-
|
|
42710
|
-
|
|
42544
|
+
// 飞书统一身份 Phase 1:登录 RPC handlers(auth:login:start / getIdentity / logout)
|
|
42545
|
+
feishuAuth: { loginFlow, ownerIdentityStore },
|
|
42546
|
+
// 飞书统一身份 Phase 2:hub 目录 + 连接(中心 server 代理)。
|
|
42547
|
+
// exchange/listHubs 包掉 owner ttcJwt(每次现读 store——热切换后立即用新身份);
|
|
42548
|
+
// dispatcher 的 FEISHU_GATED_METHODS gate 已保证未登录时这些 RPC 到不了 handler,
|
|
42549
|
+
// 这里的 FEISHU_LOGIN_REQUIRED 抛错只是防御纵深。
|
|
42550
|
+
feishuHub: {
|
|
42551
|
+
store: contactStore,
|
|
42552
|
+
exchange: async (hubId) => {
|
|
42553
|
+
const record = ownerIdentityStore.read();
|
|
42554
|
+
if (!record) {
|
|
42555
|
+
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "hub:connect requires feishu login");
|
|
42556
|
+
}
|
|
42557
|
+
return centralExchange({
|
|
42558
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
42559
|
+
ttcJwt: record.ttcToken,
|
|
42560
|
+
hubId
|
|
42561
|
+
});
|
|
42562
|
+
},
|
|
42563
|
+
listHubs: async () => {
|
|
42564
|
+
const record = ownerIdentityStore.read();
|
|
42565
|
+
if (!record) {
|
|
42566
|
+
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "hub:list requires feishu login");
|
|
42567
|
+
}
|
|
42568
|
+
return centralListHubs({
|
|
42569
|
+
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
42570
|
+
ttcJwt: record.ttcToken
|
|
42571
|
+
});
|
|
42572
|
+
},
|
|
42573
|
+
connectRemote,
|
|
42574
|
+
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
42575
|
+
selfPrincipalId: () => ownerPrincipalId,
|
|
42576
|
+
selfDisplayName: () => ownerDisplayName,
|
|
42577
|
+
// Phase 2 自动反向(spec 决策 #11):本机可达地址 = tunnel URL(公网),出站连 hub 时
|
|
42578
|
+
// 透传让 hub 反向加我为联系人。无 tunnel(仅本机 ws)→ null,hub 不自动反向(不造假数据)。
|
|
42579
|
+
selfUrl: () => currentTunnelUrl
|
|
42580
|
+
}
|
|
42711
42581
|
});
|
|
42582
|
+
let handlers = makeHandlers();
|
|
42712
42583
|
const authResolver = new AuthContextResolver({
|
|
42713
42584
|
ownerToken: resolvedAuthToken
|
|
42714
42585
|
});
|
|
@@ -42734,15 +42605,34 @@ async function startDaemon(config) {
|
|
|
42734
42605
|
},
|
|
42735
42606
|
// POST /extensions/import lands in ~/.clawd/extensions/<id>/
|
|
42736
42607
|
extensionsRoot: () => extensionsRoot(),
|
|
42737
|
-
//
|
|
42738
|
-
|
|
42608
|
+
// 飞书登录 loopback 回调(热切换,2026-06-01 拍板):成功落盘后——
|
|
42609
|
+
// 1. 进程内实时切换身份(let binding + 值快照持有者重建)
|
|
42610
|
+
// 2. 广播 auth:login:done 给在线 owner 连接(UI 即时反馈)
|
|
42611
|
+
// 3. 踢掉所有 ws 连接(在线连接的 ctx 是旧身份)→ UI 自动重连 → 新身份 ctx →
|
|
42612
|
+
// People 立即解锁,不需要重启 daemon
|
|
42613
|
+
// 失败则广播 auth:login:failed。
|
|
42614
|
+
feishuLogin: {
|
|
42615
|
+
handleLoginCallback: async (params) => {
|
|
42616
|
+
const result = await loginFlow.handleCallback(params);
|
|
42617
|
+
if (result.ok) {
|
|
42618
|
+
feishuActive = true;
|
|
42619
|
+
ownerPrincipalId = result.identity.unionId;
|
|
42620
|
+
ownerDisplayName = result.identity.displayName;
|
|
42621
|
+
handlers = makeHandlers();
|
|
42622
|
+
wsServer?.broadcastToOwners({ type: "auth:login:done", identity: result.identity });
|
|
42623
|
+
setImmediate(() => wsServer?.closeAllClients());
|
|
42624
|
+
void reportHubUrl();
|
|
42625
|
+
} else {
|
|
42626
|
+
wsServer?.broadcastToOwners({ type: "auth:login:failed", reason: result.reason });
|
|
42627
|
+
}
|
|
42628
|
+
return result;
|
|
42629
|
+
}
|
|
42630
|
+
}
|
|
42739
42631
|
});
|
|
42740
42632
|
wsServer = new LocalWsServer({
|
|
42741
42633
|
host: config.host,
|
|
42742
42634
|
port: config.port,
|
|
42743
42635
|
logger,
|
|
42744
|
-
// broadcastToPrincipal 用此 id 路由 owner-targeted 帧(替代字面量 'owner' sentinel)
|
|
42745
|
-
ownerPrincipalId,
|
|
42746
42636
|
readyFrameBuilder: (ctx) => buildReadyFrame(
|
|
42747
42637
|
{
|
|
42748
42638
|
manager,
|
|
@@ -42768,14 +42658,11 @@ async function startDaemon(config) {
|
|
|
42768
42658
|
return {
|
|
42769
42659
|
principal: { id: v2.capability.id, kind: "guest", displayName: v2.capability.displayName },
|
|
42770
42660
|
grants: v2.capability.grants,
|
|
42771
|
-
capabilityId: v2.capability.id,
|
|
42772
42661
|
...selfPrincipalId ? { peerOwnerPrincipalId: selfPrincipalId } : {}
|
|
42773
42662
|
};
|
|
42774
42663
|
},
|
|
42775
42664
|
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
42776
42665
|
httpRequestHandler: httpRouter,
|
|
42777
|
-
// app-builder build 模式:/preview/<port>/ HMR websocket upgrade 转发的端口白名单(同 http-router 配置)
|
|
42778
|
-
previewPorts: effectivePreviewPorts,
|
|
42779
42666
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
42780
42667
|
// daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
|
|
42781
42668
|
// 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
|
|
@@ -42820,6 +42707,12 @@ async function startDaemon(config) {
|
|
|
42820
42707
|
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
42821
42708
|
const handler = handlers[type];
|
|
42822
42709
|
if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
|
|
42710
|
+
if (!feishuActive && FEISHU_GATED_METHODS.includes(type)) {
|
|
42711
|
+
throw new ClawdError(
|
|
42712
|
+
ERROR_CODES.FEISHU_LOGIN_REQUIRED,
|
|
42713
|
+
`${type} requires feishu login (People/remote identity)`
|
|
42714
|
+
);
|
|
42715
|
+
}
|
|
42823
42716
|
const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
|
|
42824
42717
|
const verdict = computeGrantForFrame(type, frame);
|
|
42825
42718
|
if (verdict.kind === "check") {
|
|
@@ -42892,13 +42785,12 @@ async function startDaemon(config) {
|
|
|
42892
42785
|
stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url, tunnelError: void 0 };
|
|
42893
42786
|
stateMgr.write(stateSnapshot);
|
|
42894
42787
|
currentTunnelUrl = r.url;
|
|
42895
|
-
wss.broadcastAll({ type: "tunnel:ready", url: r.url, subdomain: r.subdomain });
|
|
42896
42788
|
const connectUrl = resolvedAuthToken ? `${r.url}#token=${resolvedAuthToken}` : r.url;
|
|
42897
42789
|
const lines = [
|
|
42898
42790
|
`Tunnel: ${r.url}`,
|
|
42899
42791
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
42900
|
-
`Frpc config: ${
|
|
42901
|
-
`Frpc log: ${
|
|
42792
|
+
`Frpc config: ${import_node_path44.default.join(config.dataDir, "frpc.toml")}`,
|
|
42793
|
+
`Frpc log: ${import_node_path44.default.join(config.dataDir, "frpc.log")}`
|
|
42902
42794
|
];
|
|
42903
42795
|
const width = Math.max(...lines.map((l) => l.length));
|
|
42904
42796
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -42911,10 +42803,11 @@ ${bar}
|
|
|
42911
42803
|
|
|
42912
42804
|
`);
|
|
42913
42805
|
try {
|
|
42914
|
-
const connectPath =
|
|
42915
|
-
|
|
42806
|
+
const connectPath = import_node_path44.default.join(config.dataDir, "connect.txt");
|
|
42807
|
+
import_node_fs29.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
42916
42808
|
} catch {
|
|
42917
42809
|
}
|
|
42810
|
+
void reportHubUrl();
|
|
42918
42811
|
} catch (err) {
|
|
42919
42812
|
const tunnelError = err?.message ?? String(err);
|
|
42920
42813
|
try {
|
|
@@ -42978,9 +42871,9 @@ ${bar}
|
|
|
42978
42871
|
};
|
|
42979
42872
|
}
|
|
42980
42873
|
function migrateDropPersonsDir(dataDir) {
|
|
42981
|
-
const dir =
|
|
42874
|
+
const dir = import_node_path44.default.join(dataDir, "persons");
|
|
42982
42875
|
try {
|
|
42983
|
-
|
|
42876
|
+
import_node_fs29.default.rmSync(dir, { recursive: true, force: true });
|
|
42984
42877
|
} catch {
|
|
42985
42878
|
}
|
|
42986
42879
|
}
|