@clawos-dev/clawd 0.2.112-beta.221.030e077 → 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 -1242
- package/package.json +1 -1
- package/dist/persona-defaults/persona-app-builder/CLAUDE.md +0 -145
- 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 -26
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +0 -14
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/main.ts +0 -14
- 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 -31
- 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/persona-defaults/persona-bug-fixer/.claude/settings.json +0 -7
- package/dist/persona-defaults/persona-bug-fixer/CLAUDE.md +0 -122
- package/dist/persona-defaults/persona-developer/.claude/settings.json +0 -8
- package/dist/persona-defaults/persona-developer/CLAUDE.md +0 -126
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,41 +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
|
-
ProjectMetadataSchema = external_exports.object({
|
|
5192
|
-
name: external_exports.string().regex(projectNameRegex, {
|
|
5193
|
-
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"
|
|
5194
|
-
}),
|
|
5195
|
-
port: external_exports.number().int().min(PROJECT_PORT_MIN).max(PROJECT_PORT_MAX),
|
|
5196
|
-
createdAt: external_exports.string().datetime()
|
|
5197
|
-
});
|
|
5198
|
-
ProjectWithStatusSchema = ProjectMetadataSchema.extend({
|
|
5199
|
-
isRunning: external_exports.boolean(),
|
|
5200
|
-
boundSessionId: external_exports.string().nullable()
|
|
5201
|
-
});
|
|
5202
|
-
ListProjectsArgsSchema = external_exports.object({}).strict();
|
|
5203
|
-
ListProjectsResultSchema = external_exports.object({
|
|
5204
|
-
projects: external_exports.array(ProjectWithStatusSchema)
|
|
5205
|
-
}).strict();
|
|
5206
|
-
CreateProjectArgsSchema = external_exports.object({
|
|
5207
|
-
name: external_exports.string()
|
|
5208
|
-
}).strict();
|
|
5209
|
-
CreateProjectResultSchema = external_exports.object({
|
|
5210
|
-
project: ProjectMetadataSchema
|
|
5211
|
-
}).strict();
|
|
5212
|
-
DeleteProjectArgsSchema = external_exports.object({
|
|
5213
|
-
name: external_exports.string()
|
|
5214
|
-
}).strict();
|
|
5215
|
-
DeleteProjectResultSchema = external_exports.object({}).strict();
|
|
5216
|
-
UpdateProjectPortArgsSchema = external_exports.object({
|
|
5217
|
-
name: external_exports.string(),
|
|
5218
|
-
newPort: external_exports.number().int()
|
|
5219
|
-
}).strict();
|
|
5220
|
-
UpdateProjectPortResultSchema = external_exports.object({
|
|
5221
|
-
project: ProjectMetadataSchema
|
|
5222
|
-
}).strict();
|
|
5223
5185
|
}
|
|
5224
5186
|
});
|
|
5225
5187
|
|
|
@@ -5262,7 +5224,7 @@ function stripSecretHash(cap) {
|
|
|
5262
5224
|
const { secretHash: _hash, ...wire } = cap;
|
|
5263
5225
|
return wire;
|
|
5264
5226
|
}
|
|
5265
|
-
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS
|
|
5227
|
+
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS;
|
|
5266
5228
|
var init_capability = __esm({
|
|
5267
5229
|
"../protocol/src/capability.ts"() {
|
|
5268
5230
|
"use strict";
|
|
@@ -5330,13 +5292,6 @@ var init_capability = __esm({
|
|
|
5330
5292
|
actions: Object.freeze(["read", "send"])
|
|
5331
5293
|
})
|
|
5332
5294
|
]);
|
|
5333
|
-
PersonalCapGetResponseSchema = external_exports.object({
|
|
5334
|
-
type: external_exports.literal("personal-cap:get:ok"),
|
|
5335
|
-
token: external_exports.string().min(1),
|
|
5336
|
-
capability: CapabilityWireSchema,
|
|
5337
|
-
/** ws/wss base URL,UI deriveInviteBase 用来拼 inviteUrl(tunnel 优先 / 否则本机 ws) */
|
|
5338
|
-
shareBaseUrl: external_exports.string().min(1)
|
|
5339
|
-
}).strict();
|
|
5340
5295
|
}
|
|
5341
5296
|
});
|
|
5342
5297
|
|
|
@@ -5371,60 +5326,46 @@ var init_inbox = __esm({
|
|
|
5371
5326
|
}
|
|
5372
5327
|
});
|
|
5373
5328
|
|
|
5374
|
-
// ../protocol/src/
|
|
5375
|
-
var
|
|
5376
|
-
var
|
|
5377
|
-
"../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"() {
|
|
5378
5333
|
"use strict";
|
|
5379
5334
|
init_zod();
|
|
5380
5335
|
init_capability();
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5336
|
+
ContactSchema = external_exports.object({
|
|
5337
|
+
/** 联系人(对方 daemon owner)的飞书 union_id */
|
|
5338
|
+
principalId: external_exports.string().min(1),
|
|
5339
|
+
/** 联系人飞书姓名 */
|
|
5340
|
+
displayName: external_exports.string(),
|
|
5384
5341
|
remoteUrl: external_exports.string().min(1),
|
|
5385
|
-
|
|
5386
|
-
|
|
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(),
|
|
5387
5347
|
grants: external_exports.array(GrantSchema),
|
|
5388
|
-
|
|
5389
|
-
}).strict();
|
|
5390
|
-
ReceivedCapabilityWireSchema = ReceivedCapabilitySchema;
|
|
5391
|
-
ReceivedCapabilityAddArgsSchema = external_exports.object({
|
|
5392
|
-
remoteUrl: external_exports.string().min(1),
|
|
5393
|
-
token: external_exports.string().min(1)
|
|
5394
|
-
}).strict();
|
|
5395
|
-
ReceivedCapabilityAddOkSchema = external_exports.object({
|
|
5396
|
-
type: external_exports.literal("received-capability:add:ok"),
|
|
5397
|
-
receivedCap: ReceivedCapabilityWireSchema
|
|
5398
|
-
}).strict();
|
|
5399
|
-
ReceivedCapabilityRemoveArgsSchema = external_exports.object({
|
|
5400
|
-
ownerPrincipalId: external_exports.string().min(1)
|
|
5348
|
+
addedAt: external_exports.number().int().nonnegative()
|
|
5401
5349
|
}).strict();
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5350
|
+
ContactWireSchema = ContactSchema;
|
|
5351
|
+
ContactRemoveArgsSchema = external_exports.object({
|
|
5352
|
+
principalId: external_exports.string().min(1)
|
|
5405
5353
|
}).strict();
|
|
5406
|
-
|
|
5407
|
-
type: external_exports.literal("
|
|
5408
|
-
|
|
5354
|
+
ContactRemoveOkSchema = external_exports.object({
|
|
5355
|
+
type: external_exports.literal("contact:remove:ok"),
|
|
5356
|
+
principalId: external_exports.string().min(1)
|
|
5409
5357
|
}).strict();
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
remoteUrl: external_exports.string().min(1),
|
|
5414
|
-
capabilityId: external_exports.string().min(1),
|
|
5415
|
-
token: external_exports.string().min(1),
|
|
5416
|
-
grants: external_exports.array(GrantSchema)
|
|
5358
|
+
ContactListOkSchema = external_exports.object({
|
|
5359
|
+
type: external_exports.literal("contact:list:ok"),
|
|
5360
|
+
contacts: external_exports.array(ContactWireSchema)
|
|
5417
5361
|
}).strict();
|
|
5418
|
-
|
|
5419
|
-
type: external_exports.literal("
|
|
5362
|
+
ContactAddedFrameSchema = external_exports.object({
|
|
5363
|
+
type: external_exports.literal("contact:added"),
|
|
5364
|
+
contact: ContactWireSchema
|
|
5420
5365
|
}).strict();
|
|
5421
|
-
|
|
5422
|
-
type: external_exports.literal("
|
|
5423
|
-
|
|
5424
|
-
}).strict();
|
|
5425
|
-
ReceivedCapabilityRemovedFrameSchema = external_exports.object({
|
|
5426
|
-
type: external_exports.literal("received-capability:removed"),
|
|
5427
|
-
ownerPrincipalId: external_exports.string().min(1)
|
|
5366
|
+
ContactRemovedFrameSchema = external_exports.object({
|
|
5367
|
+
type: external_exports.literal("contact:removed"),
|
|
5368
|
+
principalId: external_exports.string().min(1)
|
|
5428
5369
|
}).strict();
|
|
5429
5370
|
}
|
|
5430
5371
|
});
|
|
@@ -5628,6 +5569,94 @@ var init_extension = __esm({
|
|
|
5628
5569
|
}
|
|
5629
5570
|
});
|
|
5630
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
|
+
|
|
5631
5660
|
// ../protocol/src/runtime.ts
|
|
5632
5661
|
var init_runtime = __esm({
|
|
5633
5662
|
"../protocol/src/runtime.ts"() {
|
|
@@ -5643,8 +5672,9 @@ var init_runtime = __esm({
|
|
|
5643
5672
|
init_principal();
|
|
5644
5673
|
init_capability();
|
|
5645
5674
|
init_inbox();
|
|
5646
|
-
|
|
5675
|
+
init_contact();
|
|
5647
5676
|
init_extension();
|
|
5677
|
+
init_feishu_auth();
|
|
5648
5678
|
}
|
|
5649
5679
|
});
|
|
5650
5680
|
|
|
@@ -6831,8 +6861,8 @@ var require_atomic_sleep = __commonJS({
|
|
|
6831
6861
|
var require_sonic_boom = __commonJS({
|
|
6832
6862
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
6833
6863
|
"use strict";
|
|
6834
|
-
var
|
|
6835
|
-
var
|
|
6864
|
+
var fs47 = require("fs");
|
|
6865
|
+
var EventEmitter2 = require("events");
|
|
6836
6866
|
var inherits = require("util").inherits;
|
|
6837
6867
|
var path59 = require("path");
|
|
6838
6868
|
var sleep = require_atomic_sleep();
|
|
@@ -6888,20 +6918,20 @@ var require_sonic_boom = __commonJS({
|
|
|
6888
6918
|
const mode = sonic.mode;
|
|
6889
6919
|
if (sonic.sync) {
|
|
6890
6920
|
try {
|
|
6891
|
-
if (sonic.mkdir)
|
|
6892
|
-
const fd =
|
|
6921
|
+
if (sonic.mkdir) fs47.mkdirSync(path59.dirname(file), { recursive: true });
|
|
6922
|
+
const fd = fs47.openSync(file, flags, mode);
|
|
6893
6923
|
fileOpened(null, fd);
|
|
6894
6924
|
} catch (err) {
|
|
6895
6925
|
fileOpened(err);
|
|
6896
6926
|
throw err;
|
|
6897
6927
|
}
|
|
6898
6928
|
} else if (sonic.mkdir) {
|
|
6899
|
-
|
|
6929
|
+
fs47.mkdir(path59.dirname(file), { recursive: true }, (err) => {
|
|
6900
6930
|
if (err) return fileOpened(err);
|
|
6901
|
-
|
|
6931
|
+
fs47.open(file, flags, mode, fileOpened);
|
|
6902
6932
|
});
|
|
6903
6933
|
} else {
|
|
6904
|
-
|
|
6934
|
+
fs47.open(file, flags, mode, fileOpened);
|
|
6905
6935
|
}
|
|
6906
6936
|
}
|
|
6907
6937
|
function SonicBoom(opts) {
|
|
@@ -6942,8 +6972,8 @@ var require_sonic_boom = __commonJS({
|
|
|
6942
6972
|
this.flush = flushBuffer;
|
|
6943
6973
|
this.flushSync = flushBufferSync;
|
|
6944
6974
|
this._actualWrite = actualWriteBuffer;
|
|
6945
|
-
fsWriteSync = () =>
|
|
6946
|
-
fsWrite = () =>
|
|
6975
|
+
fsWriteSync = () => fs47.writeSync(this.fd, this._writingBuf);
|
|
6976
|
+
fsWrite = () => fs47.write(this.fd, this._writingBuf, this.release);
|
|
6947
6977
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
6948
6978
|
this._writingBuf = "";
|
|
6949
6979
|
this.write = write;
|
|
@@ -6952,15 +6982,15 @@ var require_sonic_boom = __commonJS({
|
|
|
6952
6982
|
this._actualWrite = actualWrite;
|
|
6953
6983
|
fsWriteSync = () => {
|
|
6954
6984
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6955
|
-
return
|
|
6985
|
+
return fs47.writeSync(this.fd, this._writingBuf);
|
|
6956
6986
|
}
|
|
6957
|
-
return
|
|
6987
|
+
return fs47.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6958
6988
|
};
|
|
6959
6989
|
fsWrite = () => {
|
|
6960
6990
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6961
|
-
return
|
|
6991
|
+
return fs47.write(this.fd, this._writingBuf, this.release);
|
|
6962
6992
|
}
|
|
6963
|
-
return
|
|
6993
|
+
return fs47.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
6964
6994
|
};
|
|
6965
6995
|
} else {
|
|
6966
6996
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -7017,7 +7047,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7017
7047
|
}
|
|
7018
7048
|
}
|
|
7019
7049
|
if (this._fsync) {
|
|
7020
|
-
|
|
7050
|
+
fs47.fsyncSync(this.fd);
|
|
7021
7051
|
}
|
|
7022
7052
|
const len = this._len;
|
|
7023
7053
|
if (this._reopening) {
|
|
@@ -7069,7 +7099,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7069
7099
|
sonic._asyncDrainScheduled = false;
|
|
7070
7100
|
sonic.emit("drain");
|
|
7071
7101
|
}
|
|
7072
|
-
inherits(SonicBoom,
|
|
7102
|
+
inherits(SonicBoom, EventEmitter2);
|
|
7073
7103
|
function mergeBuf(bufs, len) {
|
|
7074
7104
|
if (bufs.length === 0) {
|
|
7075
7105
|
return kEmptyBuffer;
|
|
@@ -7131,7 +7161,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7131
7161
|
const onDrain = () => {
|
|
7132
7162
|
if (!this._fsync) {
|
|
7133
7163
|
try {
|
|
7134
|
-
|
|
7164
|
+
fs47.fsync(this.fd, (err) => {
|
|
7135
7165
|
this._flushPending = false;
|
|
7136
7166
|
cb(err);
|
|
7137
7167
|
});
|
|
@@ -7233,7 +7263,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7233
7263
|
const fd = this.fd;
|
|
7234
7264
|
this.once("ready", () => {
|
|
7235
7265
|
if (fd !== this.fd) {
|
|
7236
|
-
|
|
7266
|
+
fs47.close(fd, (err) => {
|
|
7237
7267
|
if (err) {
|
|
7238
7268
|
return this.emit("error", err);
|
|
7239
7269
|
}
|
|
@@ -7282,7 +7312,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7282
7312
|
buf = this._bufs[0];
|
|
7283
7313
|
}
|
|
7284
7314
|
try {
|
|
7285
|
-
const n = Buffer.isBuffer(buf) ?
|
|
7315
|
+
const n = Buffer.isBuffer(buf) ? fs47.writeSync(this.fd, buf) : fs47.writeSync(this.fd, buf, "utf8");
|
|
7286
7316
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
7287
7317
|
buf = releasedBufObj.writingBuf;
|
|
7288
7318
|
this._len = releasedBufObj.len;
|
|
@@ -7298,7 +7328,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7298
7328
|
}
|
|
7299
7329
|
}
|
|
7300
7330
|
try {
|
|
7301
|
-
|
|
7331
|
+
fs47.fsyncSync(this.fd);
|
|
7302
7332
|
} catch {
|
|
7303
7333
|
}
|
|
7304
7334
|
}
|
|
@@ -7319,7 +7349,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7319
7349
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
7320
7350
|
}
|
|
7321
7351
|
try {
|
|
7322
|
-
const n =
|
|
7352
|
+
const n = fs47.writeSync(this.fd, buf);
|
|
7323
7353
|
buf = buf.subarray(n);
|
|
7324
7354
|
this._len = Math.max(this._len - n, 0);
|
|
7325
7355
|
if (buf.length <= 0) {
|
|
@@ -7347,13 +7377,13 @@ var require_sonic_boom = __commonJS({
|
|
|
7347
7377
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
7348
7378
|
if (this.sync) {
|
|
7349
7379
|
try {
|
|
7350
|
-
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");
|
|
7351
7381
|
release(null, written);
|
|
7352
7382
|
} catch (err) {
|
|
7353
7383
|
release(err);
|
|
7354
7384
|
}
|
|
7355
7385
|
} else {
|
|
7356
|
-
|
|
7386
|
+
fs47.write(this.fd, this._writingBuf, release);
|
|
7357
7387
|
}
|
|
7358
7388
|
}
|
|
7359
7389
|
function actualWriteBuffer() {
|
|
@@ -7362,7 +7392,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7362
7392
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
7363
7393
|
if (this.sync) {
|
|
7364
7394
|
try {
|
|
7365
|
-
const written =
|
|
7395
|
+
const written = fs47.writeSync(this.fd, this._writingBuf);
|
|
7366
7396
|
release(null, written);
|
|
7367
7397
|
} catch (err) {
|
|
7368
7398
|
release(err);
|
|
@@ -7371,7 +7401,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7371
7401
|
if (kCopyBuffer) {
|
|
7372
7402
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
7373
7403
|
}
|
|
7374
|
-
|
|
7404
|
+
fs47.write(this.fd, this._writingBuf, release);
|
|
7375
7405
|
}
|
|
7376
7406
|
}
|
|
7377
7407
|
function actualClose(sonic) {
|
|
@@ -7387,12 +7417,12 @@ var require_sonic_boom = __commonJS({
|
|
|
7387
7417
|
sonic._lens = [];
|
|
7388
7418
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
7389
7419
|
try {
|
|
7390
|
-
|
|
7420
|
+
fs47.fsync(sonic.fd, closeWrapped);
|
|
7391
7421
|
} catch {
|
|
7392
7422
|
}
|
|
7393
7423
|
function closeWrapped() {
|
|
7394
7424
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7395
|
-
|
|
7425
|
+
fs47.close(sonic.fd, done);
|
|
7396
7426
|
} else {
|
|
7397
7427
|
done();
|
|
7398
7428
|
}
|
|
@@ -7647,9 +7677,9 @@ var require_thread_stream = __commonJS({
|
|
|
7647
7677
|
"../node_modules/.pnpm/thread-stream@3.1.0/node_modules/thread-stream/index.js"(exports2, module2) {
|
|
7648
7678
|
"use strict";
|
|
7649
7679
|
var { version: version2 } = require_package();
|
|
7650
|
-
var { EventEmitter:
|
|
7680
|
+
var { EventEmitter: EventEmitter2 } = require("events");
|
|
7651
7681
|
var { Worker } = require("worker_threads");
|
|
7652
|
-
var { join:
|
|
7682
|
+
var { join: join11 } = require("path");
|
|
7653
7683
|
var { pathToFileURL } = require("url");
|
|
7654
7684
|
var { wait } = require_wait();
|
|
7655
7685
|
var {
|
|
@@ -7685,7 +7715,7 @@ var require_thread_stream = __commonJS({
|
|
|
7685
7715
|
function createWorker(stream, opts) {
|
|
7686
7716
|
const { filename, workerData } = opts;
|
|
7687
7717
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
7688
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
7718
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join11(__dirname, "lib", "worker.js");
|
|
7689
7719
|
const worker = new Worker(toExecute, {
|
|
7690
7720
|
...opts.workerOpts,
|
|
7691
7721
|
trackUnmanagedFds: false,
|
|
@@ -7803,7 +7833,7 @@ var require_thread_stream = __commonJS({
|
|
|
7803
7833
|
stream.worker.off("exit", onWorkerExit);
|
|
7804
7834
|
destroy(stream, code !== 0 ? new Error("the worker thread exited") : null);
|
|
7805
7835
|
}
|
|
7806
|
-
var ThreadStream = class extends
|
|
7836
|
+
var ThreadStream = class extends EventEmitter2 {
|
|
7807
7837
|
constructor(opts = {}) {
|
|
7808
7838
|
super();
|
|
7809
7839
|
if (opts.bufferSize < 4) {
|
|
@@ -8071,7 +8101,7 @@ var require_transport = __commonJS({
|
|
|
8071
8101
|
"use strict";
|
|
8072
8102
|
var { createRequire } = require("module");
|
|
8073
8103
|
var getCallers = require_caller();
|
|
8074
|
-
var { join:
|
|
8104
|
+
var { join: join11, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
|
|
8075
8105
|
var sleep = require_atomic_sleep();
|
|
8076
8106
|
var onExit = require_on_exit_leak_free();
|
|
8077
8107
|
var ThreadStream = require_thread_stream();
|
|
@@ -8134,7 +8164,7 @@ var require_transport = __commonJS({
|
|
|
8134
8164
|
throw new Error("only one of target or targets can be specified");
|
|
8135
8165
|
}
|
|
8136
8166
|
if (targets) {
|
|
8137
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8167
|
+
target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
|
|
8138
8168
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
8139
8169
|
return {
|
|
8140
8170
|
...dest,
|
|
@@ -8152,7 +8182,7 @@ var require_transport = __commonJS({
|
|
|
8152
8182
|
});
|
|
8153
8183
|
});
|
|
8154
8184
|
} else if (pipeline3) {
|
|
8155
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8185
|
+
target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
|
|
8156
8186
|
options.pipelines = [pipeline3.map((dest) => {
|
|
8157
8187
|
return {
|
|
8158
8188
|
...dest,
|
|
@@ -8174,7 +8204,7 @@ var require_transport = __commonJS({
|
|
|
8174
8204
|
return origin;
|
|
8175
8205
|
}
|
|
8176
8206
|
if (origin === "pino/file") {
|
|
8177
|
-
return
|
|
8207
|
+
return join11(__dirname, "..", "file.js");
|
|
8178
8208
|
}
|
|
8179
8209
|
let fixTarget2;
|
|
8180
8210
|
for (const filePath of callers) {
|
|
@@ -8761,7 +8791,7 @@ var require_meta = __commonJS({
|
|
|
8761
8791
|
var require_proto = __commonJS({
|
|
8762
8792
|
"../node_modules/.pnpm/pino@9.14.0/node_modules/pino/lib/proto.js"(exports2, module2) {
|
|
8763
8793
|
"use strict";
|
|
8764
|
-
var { EventEmitter:
|
|
8794
|
+
var { EventEmitter: EventEmitter2 } = require("events");
|
|
8765
8795
|
var {
|
|
8766
8796
|
lsCacheSym,
|
|
8767
8797
|
levelValSym,
|
|
@@ -8843,7 +8873,7 @@ var require_proto = __commonJS({
|
|
|
8843
8873
|
[getLevelSym]: getLevel,
|
|
8844
8874
|
[setLevelSym]: setLevel
|
|
8845
8875
|
};
|
|
8846
|
-
Object.setPrototypeOf(prototype,
|
|
8876
|
+
Object.setPrototypeOf(prototype, EventEmitter2.prototype);
|
|
8847
8877
|
module2.exports = function() {
|
|
8848
8878
|
return Object.create(prototype);
|
|
8849
8879
|
};
|
|
@@ -9164,7 +9194,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9164
9194
|
return circularValue;
|
|
9165
9195
|
}
|
|
9166
9196
|
let res = "";
|
|
9167
|
-
let
|
|
9197
|
+
let join11 = ",";
|
|
9168
9198
|
const originalIndentation = indentation;
|
|
9169
9199
|
if (Array.isArray(value)) {
|
|
9170
9200
|
if (value.length === 0) {
|
|
@@ -9178,7 +9208,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9178
9208
|
indentation += spacer;
|
|
9179
9209
|
res += `
|
|
9180
9210
|
${indentation}`;
|
|
9181
|
-
|
|
9211
|
+
join11 = `,
|
|
9182
9212
|
${indentation}`;
|
|
9183
9213
|
}
|
|
9184
9214
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9186,13 +9216,13 @@ ${indentation}`;
|
|
|
9186
9216
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9187
9217
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9188
9218
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9189
|
-
res +=
|
|
9219
|
+
res += join11;
|
|
9190
9220
|
}
|
|
9191
9221
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9192
9222
|
res += tmp !== void 0 ? tmp : "null";
|
|
9193
9223
|
if (value.length - 1 > maximumBreadth) {
|
|
9194
9224
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9195
|
-
res += `${
|
|
9225
|
+
res += `${join11}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9196
9226
|
}
|
|
9197
9227
|
if (spacer !== "") {
|
|
9198
9228
|
res += `
|
|
@@ -9213,7 +9243,7 @@ ${originalIndentation}`;
|
|
|
9213
9243
|
let separator = "";
|
|
9214
9244
|
if (spacer !== "") {
|
|
9215
9245
|
indentation += spacer;
|
|
9216
|
-
|
|
9246
|
+
join11 = `,
|
|
9217
9247
|
${indentation}`;
|
|
9218
9248
|
whitespace = " ";
|
|
9219
9249
|
}
|
|
@@ -9227,13 +9257,13 @@ ${indentation}`;
|
|
|
9227
9257
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
9228
9258
|
if (tmp !== void 0) {
|
|
9229
9259
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9230
|
-
separator =
|
|
9260
|
+
separator = join11;
|
|
9231
9261
|
}
|
|
9232
9262
|
}
|
|
9233
9263
|
if (keyLength > maximumBreadth) {
|
|
9234
9264
|
const removedKeys = keyLength - maximumBreadth;
|
|
9235
9265
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
9236
|
-
separator =
|
|
9266
|
+
separator = join11;
|
|
9237
9267
|
}
|
|
9238
9268
|
if (spacer !== "" && separator.length > 1) {
|
|
9239
9269
|
res = `
|
|
@@ -9274,7 +9304,7 @@ ${originalIndentation}`;
|
|
|
9274
9304
|
}
|
|
9275
9305
|
const originalIndentation = indentation;
|
|
9276
9306
|
let res = "";
|
|
9277
|
-
let
|
|
9307
|
+
let join11 = ",";
|
|
9278
9308
|
if (Array.isArray(value)) {
|
|
9279
9309
|
if (value.length === 0) {
|
|
9280
9310
|
return "[]";
|
|
@@ -9287,7 +9317,7 @@ ${originalIndentation}`;
|
|
|
9287
9317
|
indentation += spacer;
|
|
9288
9318
|
res += `
|
|
9289
9319
|
${indentation}`;
|
|
9290
|
-
|
|
9320
|
+
join11 = `,
|
|
9291
9321
|
${indentation}`;
|
|
9292
9322
|
}
|
|
9293
9323
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9295,13 +9325,13 @@ ${indentation}`;
|
|
|
9295
9325
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9296
9326
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9297
9327
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9298
|
-
res +=
|
|
9328
|
+
res += join11;
|
|
9299
9329
|
}
|
|
9300
9330
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9301
9331
|
res += tmp !== void 0 ? tmp : "null";
|
|
9302
9332
|
if (value.length - 1 > maximumBreadth) {
|
|
9303
9333
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9304
|
-
res += `${
|
|
9334
|
+
res += `${join11}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9305
9335
|
}
|
|
9306
9336
|
if (spacer !== "") {
|
|
9307
9337
|
res += `
|
|
@@ -9314,7 +9344,7 @@ ${originalIndentation}`;
|
|
|
9314
9344
|
let whitespace = "";
|
|
9315
9345
|
if (spacer !== "") {
|
|
9316
9346
|
indentation += spacer;
|
|
9317
|
-
|
|
9347
|
+
join11 = `,
|
|
9318
9348
|
${indentation}`;
|
|
9319
9349
|
whitespace = " ";
|
|
9320
9350
|
}
|
|
@@ -9323,7 +9353,7 @@ ${indentation}`;
|
|
|
9323
9353
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
9324
9354
|
if (tmp !== void 0) {
|
|
9325
9355
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9326
|
-
separator =
|
|
9356
|
+
separator = join11;
|
|
9327
9357
|
}
|
|
9328
9358
|
}
|
|
9329
9359
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -9381,20 +9411,20 @@ ${originalIndentation}`;
|
|
|
9381
9411
|
indentation += spacer;
|
|
9382
9412
|
let res2 = `
|
|
9383
9413
|
${indentation}`;
|
|
9384
|
-
const
|
|
9414
|
+
const join12 = `,
|
|
9385
9415
|
${indentation}`;
|
|
9386
9416
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
9387
9417
|
let i = 0;
|
|
9388
9418
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9389
9419
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9390
9420
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
9391
|
-
res2 +=
|
|
9421
|
+
res2 += join12;
|
|
9392
9422
|
}
|
|
9393
9423
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9394
9424
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
9395
9425
|
if (value.length - 1 > maximumBreadth) {
|
|
9396
9426
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9397
|
-
res2 += `${
|
|
9427
|
+
res2 += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9398
9428
|
}
|
|
9399
9429
|
res2 += `
|
|
9400
9430
|
${originalIndentation}`;
|
|
@@ -9410,16 +9440,16 @@ ${originalIndentation}`;
|
|
|
9410
9440
|
return '"[Object]"';
|
|
9411
9441
|
}
|
|
9412
9442
|
indentation += spacer;
|
|
9413
|
-
const
|
|
9443
|
+
const join11 = `,
|
|
9414
9444
|
${indentation}`;
|
|
9415
9445
|
let res = "";
|
|
9416
9446
|
let separator = "";
|
|
9417
9447
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
9418
9448
|
if (isTypedArrayWithEntries(value)) {
|
|
9419
|
-
res += stringifyTypedArray(value,
|
|
9449
|
+
res += stringifyTypedArray(value, join11, maximumBreadth);
|
|
9420
9450
|
keys = keys.slice(value.length);
|
|
9421
9451
|
maximumPropertiesToStringify -= value.length;
|
|
9422
|
-
separator =
|
|
9452
|
+
separator = join11;
|
|
9423
9453
|
}
|
|
9424
9454
|
if (deterministic) {
|
|
9425
9455
|
keys = sort(keys, comparator);
|
|
@@ -9430,13 +9460,13 @@ ${indentation}`;
|
|
|
9430
9460
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
9431
9461
|
if (tmp !== void 0) {
|
|
9432
9462
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
9433
|
-
separator =
|
|
9463
|
+
separator = join11;
|
|
9434
9464
|
}
|
|
9435
9465
|
}
|
|
9436
9466
|
if (keyLength > maximumBreadth) {
|
|
9437
9467
|
const removedKeys = keyLength - maximumBreadth;
|
|
9438
9468
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
9439
|
-
separator =
|
|
9469
|
+
separator = join11;
|
|
9440
9470
|
}
|
|
9441
9471
|
if (separator !== "") {
|
|
9442
9472
|
res = `
|
|
@@ -19318,10 +19348,10 @@ var require_extension = __commonJS({
|
|
|
19318
19348
|
var require_websocket = __commonJS({
|
|
19319
19349
|
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket.js"(exports2, module2) {
|
|
19320
19350
|
"use strict";
|
|
19321
|
-
var
|
|
19351
|
+
var EventEmitter2 = require("events");
|
|
19322
19352
|
var https = require("https");
|
|
19323
|
-
var
|
|
19324
|
-
var
|
|
19353
|
+
var http2 = require("http");
|
|
19354
|
+
var net2 = require("net");
|
|
19325
19355
|
var tls = require("tls");
|
|
19326
19356
|
var { randomBytes, createHash: createHash2 } = require("crypto");
|
|
19327
19357
|
var { Duplex, Readable: Readable3 } = require("stream");
|
|
@@ -19350,7 +19380,7 @@ var require_websocket = __commonJS({
|
|
|
19350
19380
|
var protocolVersions = [8, 13];
|
|
19351
19381
|
var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
|
|
19352
19382
|
var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
|
|
19353
|
-
var WebSocket2 = class _WebSocket extends
|
|
19383
|
+
var WebSocket2 = class _WebSocket extends EventEmitter2 {
|
|
19354
19384
|
/**
|
|
19355
19385
|
* Create a new `WebSocket`.
|
|
19356
19386
|
*
|
|
@@ -19854,7 +19884,7 @@ var require_websocket = __commonJS({
|
|
|
19854
19884
|
}
|
|
19855
19885
|
const defaultPort = isSecure ? 443 : 80;
|
|
19856
19886
|
const key = randomBytes(16).toString("base64");
|
|
19857
|
-
const request = isSecure ? https.request :
|
|
19887
|
+
const request = isSecure ? https.request : http2.request;
|
|
19858
19888
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
19859
19889
|
let perMessageDeflate;
|
|
19860
19890
|
opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
|
|
@@ -20055,12 +20085,12 @@ var require_websocket = __commonJS({
|
|
|
20055
20085
|
}
|
|
20056
20086
|
function netConnect(options) {
|
|
20057
20087
|
options.path = options.socketPath;
|
|
20058
|
-
return
|
|
20088
|
+
return net2.connect(options);
|
|
20059
20089
|
}
|
|
20060
20090
|
function tlsConnect(options) {
|
|
20061
20091
|
options.path = void 0;
|
|
20062
20092
|
if (!options.servername && options.servername !== "") {
|
|
20063
|
-
options.servername =
|
|
20093
|
+
options.servername = net2.isIP(options.host) ? "" : options.host;
|
|
20064
20094
|
}
|
|
20065
20095
|
return tls.connect(options);
|
|
20066
20096
|
}
|
|
@@ -20347,8 +20377,8 @@ var require_subprotocol = __commonJS({
|
|
|
20347
20377
|
var require_websocket_server = __commonJS({
|
|
20348
20378
|
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) {
|
|
20349
20379
|
"use strict";
|
|
20350
|
-
var
|
|
20351
|
-
var
|
|
20380
|
+
var EventEmitter2 = require("events");
|
|
20381
|
+
var http2 = require("http");
|
|
20352
20382
|
var { Duplex } = require("stream");
|
|
20353
20383
|
var { createHash: createHash2 } = require("crypto");
|
|
20354
20384
|
var extension2 = require_extension();
|
|
@@ -20360,7 +20390,7 @@ var require_websocket_server = __commonJS({
|
|
|
20360
20390
|
var RUNNING = 0;
|
|
20361
20391
|
var CLOSING = 1;
|
|
20362
20392
|
var CLOSED = 2;
|
|
20363
|
-
var WebSocketServer2 = class extends
|
|
20393
|
+
var WebSocketServer2 = class extends EventEmitter2 {
|
|
20364
20394
|
/**
|
|
20365
20395
|
* Create a `WebSocketServer` instance.
|
|
20366
20396
|
*
|
|
@@ -20423,8 +20453,8 @@ var require_websocket_server = __commonJS({
|
|
|
20423
20453
|
);
|
|
20424
20454
|
}
|
|
20425
20455
|
if (options.port != null) {
|
|
20426
|
-
this._server =
|
|
20427
|
-
const body =
|
|
20456
|
+
this._server = http2.createServer((req, res) => {
|
|
20457
|
+
const body = http2.STATUS_CODES[426];
|
|
20428
20458
|
res.writeHead(426, {
|
|
20429
20459
|
"Content-Length": body.length,
|
|
20430
20460
|
"Content-Type": "text/plain"
|
|
@@ -20711,7 +20741,7 @@ var require_websocket_server = __commonJS({
|
|
|
20711
20741
|
this.destroy();
|
|
20712
20742
|
}
|
|
20713
20743
|
function abortHandshake(socket, code, message, headers) {
|
|
20714
|
-
message = message ||
|
|
20744
|
+
message = message || http2.STATUS_CODES[code];
|
|
20715
20745
|
headers = {
|
|
20716
20746
|
Connection: "close",
|
|
20717
20747
|
"Content-Type": "text/html",
|
|
@@ -20720,7 +20750,7 @@ var require_websocket_server = __commonJS({
|
|
|
20720
20750
|
};
|
|
20721
20751
|
socket.once("finish", socket.destroy);
|
|
20722
20752
|
socket.end(
|
|
20723
|
-
`HTTP/1.1 ${code} ${
|
|
20753
|
+
`HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
|
|
20724
20754
|
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
|
|
20725
20755
|
);
|
|
20726
20756
|
}
|
|
@@ -21023,7 +21053,7 @@ var require_BufferList = __commonJS({
|
|
|
21023
21053
|
this.head = this.tail = null;
|
|
21024
21054
|
this.length = 0;
|
|
21025
21055
|
};
|
|
21026
|
-
BufferList.prototype.join = function
|
|
21056
|
+
BufferList.prototype.join = function join11(s) {
|
|
21027
21057
|
if (this.length === 0) return "";
|
|
21028
21058
|
var p2 = this.head;
|
|
21029
21059
|
var ret = "" + p2.data;
|
|
@@ -30480,7 +30510,7 @@ var require_lib3 = __commonJS({
|
|
|
30480
30510
|
// src/run-case/recorder.ts
|
|
30481
30511
|
function startRunCaseRecorder(opts) {
|
|
30482
30512
|
const now = opts.now ?? Date.now;
|
|
30483
|
-
const dir =
|
|
30513
|
+
const dir = import_node_path45.default.dirname(opts.recordPath);
|
|
30484
30514
|
let stream = null;
|
|
30485
30515
|
let closing = false;
|
|
30486
30516
|
let closedSettled = false;
|
|
@@ -30494,8 +30524,8 @@ function startRunCaseRecorder(opts) {
|
|
|
30494
30524
|
});
|
|
30495
30525
|
const ensureStream = () => {
|
|
30496
30526
|
if (stream) return stream;
|
|
30497
|
-
|
|
30498
|
-
stream =
|
|
30527
|
+
import_node_fs30.default.mkdirSync(dir, { recursive: true });
|
|
30528
|
+
stream = import_node_fs30.default.createWriteStream(opts.recordPath, { flags: "a" });
|
|
30499
30529
|
stream.on("close", () => closedResolve());
|
|
30500
30530
|
return stream;
|
|
30501
30531
|
};
|
|
@@ -30520,12 +30550,12 @@ function startRunCaseRecorder(opts) {
|
|
|
30520
30550
|
};
|
|
30521
30551
|
return { tap, close, closed };
|
|
30522
30552
|
}
|
|
30523
|
-
var
|
|
30553
|
+
var import_node_fs30, import_node_path45;
|
|
30524
30554
|
var init_recorder = __esm({
|
|
30525
30555
|
"src/run-case/recorder.ts"() {
|
|
30526
30556
|
"use strict";
|
|
30527
|
-
|
|
30528
|
-
|
|
30557
|
+
import_node_fs30 = __toESM(require("fs"), 1);
|
|
30558
|
+
import_node_path45 = __toESM(require("path"), 1);
|
|
30529
30559
|
}
|
|
30530
30560
|
});
|
|
30531
30561
|
|
|
@@ -30568,7 +30598,7 @@ var init_wire = __esm({
|
|
|
30568
30598
|
// src/run-case/controller.ts
|
|
30569
30599
|
async function runController(opts) {
|
|
30570
30600
|
const now = opts.now ?? Date.now;
|
|
30571
|
-
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-"));
|
|
30572
30602
|
const ownsCwd = opts.cwd === void 0;
|
|
30573
30603
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
30574
30604
|
const spawnCtx = { cwd };
|
|
@@ -30729,19 +30759,19 @@ async function runController(opts) {
|
|
|
30729
30759
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
30730
30760
|
if (ownsCwd) {
|
|
30731
30761
|
try {
|
|
30732
|
-
(0,
|
|
30762
|
+
(0, import_node_fs31.rmSync)(cwd, { recursive: true, force: true });
|
|
30733
30763
|
} catch {
|
|
30734
30764
|
}
|
|
30735
30765
|
}
|
|
30736
30766
|
return exitCode ?? 0;
|
|
30737
30767
|
}
|
|
30738
|
-
var
|
|
30768
|
+
var import_node_fs31, import_node_os19, import_node_path46;
|
|
30739
30769
|
var init_controller = __esm({
|
|
30740
30770
|
"src/run-case/controller.ts"() {
|
|
30741
30771
|
"use strict";
|
|
30742
|
-
|
|
30772
|
+
import_node_fs31 = require("fs");
|
|
30743
30773
|
import_node_os19 = __toESM(require("os"), 1);
|
|
30744
|
-
|
|
30774
|
+
import_node_path46 = __toESM(require("path"), 1);
|
|
30745
30775
|
init_claude();
|
|
30746
30776
|
init_stdout_splitter();
|
|
30747
30777
|
init_permission_stdio();
|
|
@@ -30933,8 +30963,6 @@ function resolveConfig(opts) {
|
|
|
30933
30963
|
const frpcBinary = env.CLAWD_FRPC_BIN ?? null;
|
|
30934
30964
|
const rawMode = env.CLAWD_CC_MODE;
|
|
30935
30965
|
const mode = DAEMON_MODE_VALUES.includes(rawMode ?? "") ? rawMode : "sdk";
|
|
30936
|
-
const filePreviewPorts = Array.isArray(fileCfg.previewPorts) ? fileCfg.previewPorts.map((n) => Number(n)).filter((n) => Number.isInteger(n) && n > 0) : null;
|
|
30937
|
-
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;
|
|
30938
30966
|
return {
|
|
30939
30967
|
port,
|
|
30940
30968
|
host,
|
|
@@ -30946,8 +30974,7 @@ function resolveConfig(opts) {
|
|
|
30946
30974
|
authToken,
|
|
30947
30975
|
noTunnelPersist,
|
|
30948
30976
|
frpcBinary,
|
|
30949
|
-
mode
|
|
30950
|
-
previewPorts
|
|
30977
|
+
mode
|
|
30951
30978
|
};
|
|
30952
30979
|
}
|
|
30953
30980
|
var HELP_TEXT = `clawd [options]
|
|
@@ -30976,8 +31003,8 @@ Env (advanced):
|
|
|
30976
31003
|
`;
|
|
30977
31004
|
|
|
30978
31005
|
// src/index.ts
|
|
30979
|
-
var
|
|
30980
|
-
var
|
|
31006
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
31007
|
+
var import_node_fs29 = __toESM(require("fs"), 1);
|
|
30981
31008
|
|
|
30982
31009
|
// src/logger.ts
|
|
30983
31010
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -32986,7 +33013,6 @@ var SessionManager = class {
|
|
|
32986
33013
|
iconKey: args.iconKey,
|
|
32987
33014
|
forkedFromSessionId: args.forkedFromSessionId,
|
|
32988
33015
|
ownerPersonaId: args.ownerPersonaId,
|
|
32989
|
-
appBuilderProject: args.appBuilderProject,
|
|
32990
33016
|
creatorPrincipalId,
|
|
32991
33017
|
...creatorOwnerPrincipalId ? { creatorOwnerPrincipalId } : {},
|
|
32992
33018
|
createdAt: iso,
|
|
@@ -34623,29 +34649,6 @@ var DEFAULT_PERSONAS = [
|
|
|
34623
34649
|
model: "opus",
|
|
34624
34650
|
iconKey: "assist",
|
|
34625
34651
|
public: false
|
|
34626
|
-
},
|
|
34627
|
-
{
|
|
34628
|
-
// app-builder:全栈应用搭建师,绑 extension-kit 模板 + multi-project picker
|
|
34629
|
-
// spec: superpowers/specs/2026-06-01-app-builder-project-picker-design.md
|
|
34630
|
-
personaId: "persona-app-builder",
|
|
34631
|
-
label: "App Builder",
|
|
34632
|
-
model: "opus",
|
|
34633
|
-
iconKey: "assist",
|
|
34634
|
-
public: false
|
|
34635
|
-
},
|
|
34636
|
-
{
|
|
34637
|
-
personaId: "persona-developer",
|
|
34638
|
-
label: "\u5F00\u53D1\u8005",
|
|
34639
|
-
model: "opus",
|
|
34640
|
-
iconKey: "code",
|
|
34641
|
-
public: false
|
|
34642
|
-
},
|
|
34643
|
-
{
|
|
34644
|
-
personaId: "persona-bug-fixer",
|
|
34645
|
-
label: "Bug \u4FEE\u590D\u5E08",
|
|
34646
|
-
model: "opus",
|
|
34647
|
-
iconKey: "debug",
|
|
34648
|
-
public: false
|
|
34649
34652
|
}
|
|
34650
34653
|
];
|
|
34651
34654
|
function findDefaultsRoot() {
|
|
@@ -36342,58 +36345,7 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
36342
36345
|
var wrapper_default = import_websocket.default;
|
|
36343
36346
|
|
|
36344
36347
|
// src/transport/local-ws-server.ts
|
|
36345
|
-
var import_node_http2 = __toESM(require("http"), 1);
|
|
36346
|
-
|
|
36347
|
-
// src/transport/preview-proxy.ts
|
|
36348
36348
|
var import_node_http = __toESM(require("http"), 1);
|
|
36349
|
-
var import_node_net = __toESM(require("net"), 1);
|
|
36350
|
-
var PREVIEW_RE = /^\/preview\/(\d+)(?:\/.*)?$/;
|
|
36351
|
-
function matchPreviewPort(pathname) {
|
|
36352
|
-
const m2 = pathname.match(PREVIEW_RE);
|
|
36353
|
-
return m2 ? Number(m2[1]) : null;
|
|
36354
|
-
}
|
|
36355
|
-
function isPreviewPortAllowed(port, cfg) {
|
|
36356
|
-
return cfg.allowedPorts.includes(port);
|
|
36357
|
-
}
|
|
36358
|
-
function proxyPreviewHttp(req, res, port) {
|
|
36359
|
-
return new Promise((resolve6) => {
|
|
36360
|
-
const upstream = import_node_http.default.request(
|
|
36361
|
-
{ host: "localhost", port, method: req.method, path: req.url, headers: req.headers },
|
|
36362
|
-
(upRes) => {
|
|
36363
|
-
res.writeHead(upRes.statusCode || 502, upRes.headers);
|
|
36364
|
-
upRes.pipe(res);
|
|
36365
|
-
upRes.on("end", () => resolve6());
|
|
36366
|
-
}
|
|
36367
|
-
);
|
|
36368
|
-
upstream.on("error", (e) => {
|
|
36369
|
-
if (!res.headersSent) {
|
|
36370
|
-
res.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
|
|
36371
|
-
}
|
|
36372
|
-
res.end(`preview upstream error: ${e.message}`);
|
|
36373
|
-
resolve6();
|
|
36374
|
-
});
|
|
36375
|
-
req.pipe(upstream);
|
|
36376
|
-
});
|
|
36377
|
-
}
|
|
36378
|
-
function proxyPreviewUpgrade(req, socket, head, port) {
|
|
36379
|
-
const upstream = import_node_net.default.connect(port, "localhost", () => {
|
|
36380
|
-
let raw = `${req.method} ${req.url} HTTP/1.1\r
|
|
36381
|
-
`;
|
|
36382
|
-
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
36383
|
-
raw += `${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r
|
|
36384
|
-
`;
|
|
36385
|
-
}
|
|
36386
|
-
raw += "\r\n";
|
|
36387
|
-
upstream.write(raw);
|
|
36388
|
-
if (head && head.length) upstream.write(head);
|
|
36389
|
-
upstream.pipe(socket);
|
|
36390
|
-
socket.pipe(upstream);
|
|
36391
|
-
});
|
|
36392
|
-
upstream.on("error", () => socket.destroy());
|
|
36393
|
-
socket.on("error", () => upstream.destroy());
|
|
36394
|
-
}
|
|
36395
|
-
|
|
36396
|
-
// src/transport/local-ws-server.ts
|
|
36397
36349
|
var LocalWsServer = class {
|
|
36398
36350
|
constructor(opts) {
|
|
36399
36351
|
this.opts = opts;
|
|
@@ -36405,33 +36357,18 @@ var LocalWsServer = class {
|
|
|
36405
36357
|
httpServer = null;
|
|
36406
36358
|
frameHandler = null;
|
|
36407
36359
|
clients = /* @__PURE__ */ new Map();
|
|
36408
|
-
//
|
|
36409
|
-
//
|
|
36410
|
-
|
|
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();
|
|
36411
36364
|
logger;
|
|
36412
36365
|
pingIntervalMs;
|
|
36413
36366
|
async start() {
|
|
36414
36367
|
const host = this.opts.host ?? "127.0.0.1";
|
|
36415
36368
|
await new Promise((resolve6, reject) => {
|
|
36416
|
-
const httpServer =
|
|
36369
|
+
const httpServer = import_node_http.default.createServer((req, res) => this.handleHttpRequest(req, res));
|
|
36417
36370
|
const wss = new import_websocket_server.default({ noServer: true, clientTracking: true });
|
|
36418
36371
|
httpServer.on("upgrade", (req, socket, head) => {
|
|
36419
|
-
if (req.url?.startsWith("/preview/")) {
|
|
36420
|
-
const pathname = (() => {
|
|
36421
|
-
try {
|
|
36422
|
-
return new URL(req.url, "http://localhost").pathname;
|
|
36423
|
-
} catch {
|
|
36424
|
-
return "/";
|
|
36425
|
-
}
|
|
36426
|
-
})();
|
|
36427
|
-
const port = matchPreviewPort(pathname);
|
|
36428
|
-
if (port != null && (this.opts.previewPorts ?? []).includes(port)) {
|
|
36429
|
-
proxyPreviewUpgrade(req, socket, head, port);
|
|
36430
|
-
} else {
|
|
36431
|
-
socket.destroy();
|
|
36432
|
-
}
|
|
36433
|
-
return;
|
|
36434
|
-
}
|
|
36435
36372
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
36436
36373
|
this.routeConnection(ws, req);
|
|
36437
36374
|
});
|
|
@@ -36525,38 +36462,6 @@ var LocalWsServer = class {
|
|
|
36525
36462
|
this.safeSend(c.ws, frame);
|
|
36526
36463
|
}
|
|
36527
36464
|
}
|
|
36528
|
-
// capability platform v3 inbox 重写: 推给某条 cap channel 的两端 ws.
|
|
36529
|
-
// - 所有 owner ws (owner 看自家所有 channel)
|
|
36530
|
-
// - 该 capabilityId 的所有 guest ws (该 cap 持有人的多端/多 tab)
|
|
36531
|
-
// 一次调用同时推两端, inbox:event push 帧由此走.
|
|
36532
|
-
broadcastToCapabilityChannel(capabilityId, frame) {
|
|
36533
|
-
this.broadcastToOwners(frame);
|
|
36534
|
-
const set = this.capabilityIdToClients.get(capabilityId);
|
|
36535
|
-
if (!set) return;
|
|
36536
|
-
for (const id of set) {
|
|
36537
|
-
const c = this.clients.get(id);
|
|
36538
|
-
if (!c) continue;
|
|
36539
|
-
this.safeSend(c.ws, frame);
|
|
36540
|
-
}
|
|
36541
|
-
}
|
|
36542
|
-
// Phase 4 capability platform DM: 广播到指定 principal 的所有活跃 ws.
|
|
36543
|
-
// principalId === ownerPrincipalId → 同 broadcastToOwners (所有 owner ws)
|
|
36544
|
-
// principalId === 'owner' sentinel → 同上(向后兼容遗留 caller / 协议层 sentinel)
|
|
36545
|
-
// principalId === 'cap_xxx' → 该 capability 的所有 guest ws (多端 / 多 tab)
|
|
36546
|
-
// 用于 DM inbox:event 路由: from + to 两侧各播一次, 让双方 UI 实时看到 thread 更新.
|
|
36547
|
-
broadcastToPrincipal(principalId, frame) {
|
|
36548
|
-
if (principalId === "owner" || principalId === this.opts.ownerPrincipalId) {
|
|
36549
|
-
this.broadcastToOwners(frame);
|
|
36550
|
-
return;
|
|
36551
|
-
}
|
|
36552
|
-
const set = this.capabilityIdToClients.get(principalId);
|
|
36553
|
-
if (!set) return;
|
|
36554
|
-
for (const id of set) {
|
|
36555
|
-
const c = this.clients.get(id);
|
|
36556
|
-
if (!c) continue;
|
|
36557
|
-
this.safeSend(c.ws, frame);
|
|
36558
|
-
}
|
|
36559
|
-
}
|
|
36560
36465
|
firstSubscriber(sessionId) {
|
|
36561
36466
|
for (const c of this.clients.values()) {
|
|
36562
36467
|
if (c.handle.subscribedSessions.has(sessionId)) return c.handle;
|
|
@@ -36579,16 +36484,16 @@ var LocalWsServer = class {
|
|
|
36579
36484
|
this.safeSend(c.ws, frame);
|
|
36580
36485
|
}
|
|
36581
36486
|
// Task 1.7 capability platform:AuthGate.onAuthed 回调里调本方法,把 ConnectionContext
|
|
36582
|
-
// 绑到 client;guest 连接同时进
|
|
36487
|
+
// 绑到 client;guest 连接同时进 guest 索引(撤销级联用)。
|
|
36583
36488
|
attachClientContext(clientId, ctx) {
|
|
36584
36489
|
const c = this.clients.get(clientId);
|
|
36585
36490
|
if (!c) return;
|
|
36586
36491
|
c.ctx = ctx;
|
|
36587
|
-
if (ctx.
|
|
36588
|
-
let set = this.
|
|
36492
|
+
if (ctx.principal.kind === "guest") {
|
|
36493
|
+
let set = this.guestIdToClients.get(ctx.principal.id);
|
|
36589
36494
|
if (!set) {
|
|
36590
36495
|
set = /* @__PURE__ */ new Set();
|
|
36591
|
-
this.
|
|
36496
|
+
this.guestIdToClients.set(ctx.principal.id, set);
|
|
36592
36497
|
}
|
|
36593
36498
|
set.add(clientId);
|
|
36594
36499
|
}
|
|
@@ -36597,10 +36502,27 @@ var LocalWsServer = class {
|
|
|
36597
36502
|
getClientContext(clientId) {
|
|
36598
36503
|
return this.clients.get(clientId)?.ctx ?? null;
|
|
36599
36504
|
}
|
|
36600
|
-
//
|
|
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
|
|
36601
36521
|
// 让客户端识别 'capability revoked' 而非普通断连。
|
|
36602
|
-
|
|
36603
|
-
|
|
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);
|
|
36604
36526
|
if (!set) return;
|
|
36605
36527
|
const ids = [...set];
|
|
36606
36528
|
for (const id of ids) {
|
|
@@ -36665,16 +36587,18 @@ var LocalWsServer = class {
|
|
|
36665
36587
|
this.logger?.warn("ready frame build failed", { err: err.message });
|
|
36666
36588
|
}
|
|
36667
36589
|
socket.on("message", (data) => {
|
|
36668
|
-
this.onMessage(handle, data, remoteAddress)
|
|
36590
|
+
void this.onMessage(handle, data, remoteAddress).catch((err) => {
|
|
36591
|
+
this.logger?.warn("onMessage failed", { err: err.message });
|
|
36592
|
+
});
|
|
36669
36593
|
});
|
|
36670
36594
|
socket.on("close", () => {
|
|
36671
36595
|
const c = this.clients.get(id);
|
|
36672
36596
|
if (c?.pingTimer) clearInterval(c.pingTimer);
|
|
36673
|
-
const
|
|
36674
|
-
if (
|
|
36675
|
-
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);
|
|
36676
36600
|
set?.delete(id);
|
|
36677
|
-
if (set && set.size === 0) this.
|
|
36601
|
+
if (set && set.size === 0) this.guestIdToClients.delete(guestId);
|
|
36678
36602
|
}
|
|
36679
36603
|
this.clients.delete(id);
|
|
36680
36604
|
authGate?.unregister(id);
|
|
@@ -36684,7 +36608,7 @@ var LocalWsServer = class {
|
|
|
36684
36608
|
this.logger?.warn("client socket error", { clientId: id, err: err.message });
|
|
36685
36609
|
});
|
|
36686
36610
|
}
|
|
36687
|
-
onMessage(client, data, remoteAddress) {
|
|
36611
|
+
async onMessage(client, data, remoteAddress) {
|
|
36688
36612
|
let frame;
|
|
36689
36613
|
try {
|
|
36690
36614
|
frame = JSON.parse(data.toString());
|
|
@@ -36695,7 +36619,7 @@ var LocalWsServer = class {
|
|
|
36695
36619
|
const authGate = this.opts.authGate;
|
|
36696
36620
|
if (authGate) {
|
|
36697
36621
|
const wasAuthed = authGate.isAuthed(client.id);
|
|
36698
|
-
const verdict = authGate.handleFrame({ id: client.id, remoteAddress }, frame);
|
|
36622
|
+
const verdict = await authGate.handleFrame({ id: client.id, remoteAddress }, frame);
|
|
36699
36623
|
if (verdict !== "pass") {
|
|
36700
36624
|
if (!wasAuthed && authGate.isAuthed(client.id)) {
|
|
36701
36625
|
try {
|
|
@@ -36798,7 +36722,7 @@ var AuthGate = class {
|
|
|
36798
36722
|
registerConnection(handle) {
|
|
36799
36723
|
const enforce = this.opts.shouldEnforce(handle.remoteAddress);
|
|
36800
36724
|
if (!enforce) {
|
|
36801
|
-
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 });
|
|
36802
36726
|
return true;
|
|
36803
36727
|
}
|
|
36804
36728
|
const setT = this.opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
@@ -36807,7 +36731,7 @@ var AuthGate = class {
|
|
|
36807
36731
|
if (!st || st.authed) return;
|
|
36808
36732
|
this.opts.closeConnection(handle, 1008, "auth timeout");
|
|
36809
36733
|
}, this.opts.timeoutMs);
|
|
36810
|
-
this.states.set(handle.id, { authed: false, enforce: true, timer });
|
|
36734
|
+
this.states.set(handle.id, { authed: false, enforce: true, timer, pending: false });
|
|
36811
36735
|
return false;
|
|
36812
36736
|
}
|
|
36813
36737
|
unregister(handleId) {
|
|
@@ -36823,12 +36747,15 @@ var AuthGate = class {
|
|
|
36823
36747
|
}
|
|
36824
36748
|
// 收到一帧时调;返回 'consumed' / 'reject' / 'pass'
|
|
36825
36749
|
// - consumed:这一帧是 auth 帧(成功或失败),调用方不要再走业务路由
|
|
36826
|
-
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate
|
|
36750
|
+
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate 已关连接 / 验证进行中)
|
|
36827
36751
|
// - pass:已 authed,调用方继续走业务路由
|
|
36828
|
-
|
|
36752
|
+
//
|
|
36753
|
+
// Phase 2:async(authenticate 注入路径走中心 server introspect HTTP 调用)。
|
|
36754
|
+
async handleFrame(handle, frame) {
|
|
36829
36755
|
const st = this.states.get(handle.id);
|
|
36830
36756
|
if (!st) return "reject";
|
|
36831
36757
|
if (st.authed) return "pass";
|
|
36758
|
+
if (st.pending) return "reject";
|
|
36832
36759
|
const parsed = AuthRequestFrameSchema.safeParse(frame);
|
|
36833
36760
|
if (!parsed.success) {
|
|
36834
36761
|
const reason = frame.type === "auth" ? "auth failed" : "auth required";
|
|
@@ -36838,11 +36765,19 @@ var AuthGate = class {
|
|
|
36838
36765
|
}
|
|
36839
36766
|
let ctx = null;
|
|
36840
36767
|
if (this.opts.authenticate) {
|
|
36841
|
-
|
|
36842
|
-
|
|
36843
|
-
|
|
36844
|
-
|
|
36845
|
-
|
|
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";
|
|
36846
36781
|
if (!r.ok) {
|
|
36847
36782
|
this.opts.closeConnection(handle, 4401, r.code);
|
|
36848
36783
|
this.markFailed(handle.id);
|
|
@@ -36945,26 +36880,32 @@ function ownerContext(ownerPrincipalId, displayName) {
|
|
|
36945
36880
|
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
36946
36881
|
};
|
|
36947
36882
|
}
|
|
36948
|
-
function
|
|
36883
|
+
function connectGuestContext(unionId, displayName) {
|
|
36949
36884
|
return {
|
|
36950
|
-
principal: { id:
|
|
36951
|
-
grants:
|
|
36952
|
-
|
|
36953
|
-
|
|
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
|
|
36954
36891
|
};
|
|
36955
36892
|
}
|
|
36956
|
-
function authenticate(token, deps) {
|
|
36893
|
+
async function authenticate(token, deps) {
|
|
36957
36894
|
if (!token) return { ok: false, code: "NO_TOKEN" };
|
|
36958
36895
|
if (deps.isOwnerToken(token)) {
|
|
36959
36896
|
return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
|
|
36960
36897
|
}
|
|
36961
|
-
if (!deps.
|
|
36962
|
-
|
|
36963
|
-
|
|
36964
|
-
|
|
36965
|
-
|
|
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" };
|
|
36904
|
+
}
|
|
36905
|
+
if (!result.active) {
|
|
36906
|
+
return { ok: false, code: "TOKEN_REVOKED" };
|
|
36966
36907
|
}
|
|
36967
|
-
return { ok: true, context:
|
|
36908
|
+
return { ok: true, context: connectGuestContext(result.unionId, result.displayName) };
|
|
36968
36909
|
}
|
|
36969
36910
|
|
|
36970
36911
|
// src/permission/capability-store.ts
|
|
@@ -37110,8 +37051,8 @@ var CapabilityManager = class {
|
|
|
37110
37051
|
list() {
|
|
37111
37052
|
return this.registry.list();
|
|
37112
37053
|
}
|
|
37113
|
-
//
|
|
37114
|
-
// 返回 Capability
|
|
37054
|
+
// 调试 / 旧 per-peer cap 反查用(Phase 3 决策 #14 后无生产调用方,保留属三件套范围)。
|
|
37055
|
+
// 返回 Capability 持久化形态;调用方需 stripSecretHash 后再上 wire。
|
|
37115
37056
|
findById(id) {
|
|
37116
37057
|
return this.registry.findById(id);
|
|
37117
37058
|
}
|
|
@@ -37144,89 +37085,14 @@ function cleanupGuestSessionsForCapability(cap, factory) {
|
|
|
37144
37085
|
return { removed };
|
|
37145
37086
|
}
|
|
37146
37087
|
|
|
37147
|
-
// src/permission/personal-capability.ts
|
|
37148
|
-
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
37149
|
-
var import_node_path16 = __toESM(require("path"), 1);
|
|
37150
|
-
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
37151
|
-
var PERSONAL_CAP_FILE_NAME = "personal-capability.json";
|
|
37152
|
-
var PERSONAL_CAP_DISPLAY_NAME = "personal";
|
|
37153
|
-
function personalCapFilePath(dataDir) {
|
|
37154
|
-
return import_node_path16.default.join(dataDir, PERSONAL_CAP_FILE_NAME);
|
|
37155
|
-
}
|
|
37156
|
-
function loadOrCreatePersonalCapability(opts) {
|
|
37157
|
-
const file = personalCapFilePath(opts.dataDir);
|
|
37158
|
-
const generateToken = opts.generateToken ?? defaultGenerateToken;
|
|
37159
|
-
const generateId = opts.generateId ?? defaultGenerateId;
|
|
37160
|
-
const now = opts.now ?? Date.now;
|
|
37161
|
-
const existing = readFile(file);
|
|
37162
|
-
if (existing) return existing;
|
|
37163
|
-
const token = generateToken();
|
|
37164
|
-
const id = generateId();
|
|
37165
|
-
const capability = {
|
|
37166
|
-
id,
|
|
37167
|
-
secretHash: sha256Hex2(token),
|
|
37168
|
-
displayName: PERSONAL_CAP_DISPLAY_NAME,
|
|
37169
|
-
// CapabilitySchema 期待 mutable Grant[],protocol 常量是 readonly,落盘前拷一份
|
|
37170
|
-
grants: PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
37171
|
-
resource: { ...g2.resource },
|
|
37172
|
-
actions: [...g2.actions]
|
|
37173
|
-
})),
|
|
37174
|
-
issuedAt: now(),
|
|
37175
|
-
usedCount: 0
|
|
37176
|
-
};
|
|
37177
|
-
const content = { token, capability };
|
|
37178
|
-
writeFile(file, content);
|
|
37179
|
-
return content;
|
|
37180
|
-
}
|
|
37181
|
-
function readFile(file) {
|
|
37182
|
-
let raw;
|
|
37183
|
-
try {
|
|
37184
|
-
raw = import_node_fs15.default.readFileSync(file, "utf8");
|
|
37185
|
-
} catch (err) {
|
|
37186
|
-
const code = err?.code;
|
|
37187
|
-
if (code === "ENOENT") return null;
|
|
37188
|
-
return null;
|
|
37189
|
-
}
|
|
37190
|
-
let parsed;
|
|
37191
|
-
try {
|
|
37192
|
-
parsed = JSON.parse(raw);
|
|
37193
|
-
} catch {
|
|
37194
|
-
return null;
|
|
37195
|
-
}
|
|
37196
|
-
if (!parsed || typeof parsed !== "object") return null;
|
|
37197
|
-
const obj = parsed;
|
|
37198
|
-
if (typeof obj.token !== "string" || obj.token.length === 0) return null;
|
|
37199
|
-
const capParsed = CapabilitySchema.safeParse(obj.capability);
|
|
37200
|
-
if (!capParsed.success) return null;
|
|
37201
|
-
if (sha256Hex2(obj.token) !== capParsed.data.secretHash) return null;
|
|
37202
|
-
return { token: obj.token, capability: capParsed.data };
|
|
37203
|
-
}
|
|
37204
|
-
function writeFile(file, content) {
|
|
37205
|
-
import_node_fs15.default.mkdirSync(import_node_path16.default.dirname(file), { recursive: true });
|
|
37206
|
-
import_node_fs15.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
37207
|
-
try {
|
|
37208
|
-
import_node_fs15.default.chmodSync(file, 384);
|
|
37209
|
-
} catch {
|
|
37210
|
-
}
|
|
37211
|
-
}
|
|
37212
|
-
function defaultGenerateToken() {
|
|
37213
|
-
return import_node_crypto4.default.randomBytes(24).toString("base64url");
|
|
37214
|
-
}
|
|
37215
|
-
function defaultGenerateId() {
|
|
37216
|
-
return "personal_" + import_node_crypto4.default.randomBytes(6).toString("base64url");
|
|
37217
|
-
}
|
|
37218
|
-
function sha256Hex2(s) {
|
|
37219
|
-
return import_node_crypto4.default.createHash("sha256").update(s).digest("hex");
|
|
37220
|
-
}
|
|
37221
|
-
|
|
37222
37088
|
// src/inbox/inbox-store.ts
|
|
37223
|
-
var
|
|
37224
|
-
var
|
|
37089
|
+
var fs19 = __toESM(require("fs"), 1);
|
|
37090
|
+
var path21 = __toESM(require("path"), 1);
|
|
37225
37091
|
var INBOX_SUBDIR = "inbox";
|
|
37226
37092
|
var InboxStore = class {
|
|
37227
37093
|
constructor(dataDir) {
|
|
37228
37094
|
this.dataDir = dataDir;
|
|
37229
|
-
|
|
37095
|
+
fs19.mkdirSync(this.dirPath(), { recursive: true });
|
|
37230
37096
|
}
|
|
37231
37097
|
dataDir;
|
|
37232
37098
|
/**
|
|
@@ -37238,7 +37104,7 @@ var InboxStore = class {
|
|
|
37238
37104
|
const file = this.filePath(peerOwnerId);
|
|
37239
37105
|
let raw;
|
|
37240
37106
|
try {
|
|
37241
|
-
raw =
|
|
37107
|
+
raw = fs19.readFileSync(file, "utf8");
|
|
37242
37108
|
} catch (err) {
|
|
37243
37109
|
if (err?.code === "ENOENT") return [];
|
|
37244
37110
|
return [];
|
|
@@ -37254,7 +37120,7 @@ var InboxStore = class {
|
|
|
37254
37120
|
const dir = this.dirPath();
|
|
37255
37121
|
let entries;
|
|
37256
37122
|
try {
|
|
37257
|
-
entries =
|
|
37123
|
+
entries = fs19.readdirSync(dir);
|
|
37258
37124
|
} catch (err) {
|
|
37259
37125
|
if (err?.code === "ENOENT") return [];
|
|
37260
37126
|
return [];
|
|
@@ -37270,9 +37136,9 @@ var InboxStore = class {
|
|
|
37270
37136
|
if (existing.some((m2) => m2.id === message.id)) return;
|
|
37271
37137
|
const file = this.filePath(message.peerOwnerId);
|
|
37272
37138
|
const line = JSON.stringify(message) + "\n";
|
|
37273
|
-
|
|
37139
|
+
fs19.appendFileSync(file, line, { mode: 384 });
|
|
37274
37140
|
try {
|
|
37275
|
-
|
|
37141
|
+
fs19.chmodSync(file, 384);
|
|
37276
37142
|
} catch {
|
|
37277
37143
|
}
|
|
37278
37144
|
}
|
|
@@ -37302,7 +37168,7 @@ var InboxStore = class {
|
|
|
37302
37168
|
removeByPeerOwnerId(peerOwnerId) {
|
|
37303
37169
|
const file = this.filePath(peerOwnerId);
|
|
37304
37170
|
try {
|
|
37305
|
-
|
|
37171
|
+
fs19.unlinkSync(file);
|
|
37306
37172
|
} catch (err) {
|
|
37307
37173
|
if (err?.code === "ENOENT") return;
|
|
37308
37174
|
}
|
|
@@ -37311,18 +37177,18 @@ var InboxStore = class {
|
|
|
37311
37177
|
const file = this.filePath(peerOwnerId);
|
|
37312
37178
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
37313
37179
|
const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
|
|
37314
|
-
|
|
37315
|
-
|
|
37180
|
+
fs19.writeFileSync(tmp, content, { mode: 384 });
|
|
37181
|
+
fs19.renameSync(tmp, file);
|
|
37316
37182
|
try {
|
|
37317
|
-
|
|
37183
|
+
fs19.chmodSync(file, 384);
|
|
37318
37184
|
} catch {
|
|
37319
37185
|
}
|
|
37320
37186
|
}
|
|
37321
37187
|
dirPath() {
|
|
37322
|
-
return
|
|
37188
|
+
return path21.join(this.dataDir, INBOX_SUBDIR);
|
|
37323
37189
|
}
|
|
37324
37190
|
filePath(peerOwnerId) {
|
|
37325
|
-
return
|
|
37191
|
+
return path21.join(this.dirPath(), `${peerOwnerId}.jsonl`);
|
|
37326
37192
|
}
|
|
37327
37193
|
};
|
|
37328
37194
|
function parseAllLines(raw) {
|
|
@@ -37408,22 +37274,22 @@ var InboxManager = class {
|
|
|
37408
37274
|
}
|
|
37409
37275
|
};
|
|
37410
37276
|
|
|
37411
|
-
// src/state/
|
|
37412
|
-
var
|
|
37413
|
-
var
|
|
37414
|
-
var FILE_NAME = "
|
|
37415
|
-
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 {
|
|
37416
37282
|
constructor(dataDir) {
|
|
37417
37283
|
this.dataDir = dataDir;
|
|
37418
37284
|
}
|
|
37419
37285
|
dataDir;
|
|
37420
|
-
|
|
37286
|
+
contacts = /* @__PURE__ */ new Map();
|
|
37421
37287
|
load() {
|
|
37422
|
-
this.
|
|
37423
|
-
const file =
|
|
37288
|
+
this.contacts.clear();
|
|
37289
|
+
const file = path22.join(this.dataDir, FILE_NAME);
|
|
37424
37290
|
let raw;
|
|
37425
37291
|
try {
|
|
37426
|
-
raw =
|
|
37292
|
+
raw = fs20.readFileSync(file, "utf8");
|
|
37427
37293
|
} catch (err) {
|
|
37428
37294
|
if (err?.code !== "ENOENT") this.renameBak(file);
|
|
37429
37295
|
return;
|
|
@@ -37435,52 +37301,52 @@ var ReceivedCapabilityStore = class {
|
|
|
37435
37301
|
this.renameBak(file);
|
|
37436
37302
|
return;
|
|
37437
37303
|
}
|
|
37438
|
-
const arr = parsed?.
|
|
37304
|
+
const arr = parsed?.contacts;
|
|
37439
37305
|
if (!Array.isArray(arr)) return;
|
|
37440
37306
|
for (const entry of arr) {
|
|
37441
|
-
const r =
|
|
37442
|
-
if (r.success) this.
|
|
37307
|
+
const r = ContactSchema.safeParse(entry);
|
|
37308
|
+
if (r.success) this.contacts.set(r.data.principalId, r.data);
|
|
37443
37309
|
}
|
|
37444
37310
|
}
|
|
37445
37311
|
list() {
|
|
37446
|
-
return Array.from(this.
|
|
37312
|
+
return Array.from(this.contacts.values());
|
|
37447
37313
|
}
|
|
37448
|
-
get(
|
|
37449
|
-
return this.
|
|
37314
|
+
get(principalId) {
|
|
37315
|
+
return this.contacts.get(principalId) ?? null;
|
|
37450
37316
|
}
|
|
37451
|
-
|
|
37452
|
-
return this.
|
|
37317
|
+
has(principalId) {
|
|
37318
|
+
return this.contacts.has(principalId);
|
|
37453
37319
|
}
|
|
37454
|
-
upsert(
|
|
37455
|
-
this.
|
|
37320
|
+
upsert(contact) {
|
|
37321
|
+
this.contacts.set(contact.principalId, contact);
|
|
37456
37322
|
this.flush();
|
|
37457
37323
|
}
|
|
37458
|
-
remove(
|
|
37459
|
-
if (!this.
|
|
37324
|
+
remove(principalId) {
|
|
37325
|
+
if (!this.contacts.delete(principalId)) return;
|
|
37460
37326
|
this.flush();
|
|
37461
37327
|
}
|
|
37462
37328
|
flush() {
|
|
37463
|
-
const file =
|
|
37329
|
+
const file = path22.join(this.dataDir, FILE_NAME);
|
|
37464
37330
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
37465
37331
|
const content = JSON.stringify(
|
|
37466
|
-
{
|
|
37332
|
+
{ contacts: Array.from(this.contacts.values()) },
|
|
37467
37333
|
null,
|
|
37468
37334
|
2
|
|
37469
37335
|
);
|
|
37470
|
-
|
|
37471
|
-
|
|
37472
|
-
|
|
37336
|
+
fs20.mkdirSync(this.dataDir, { recursive: true });
|
|
37337
|
+
fs20.writeFileSync(tmp, content, { mode: 384 });
|
|
37338
|
+
fs20.renameSync(tmp, file);
|
|
37473
37339
|
}
|
|
37474
37340
|
renameBak(file) {
|
|
37475
37341
|
try {
|
|
37476
|
-
|
|
37342
|
+
fs20.renameSync(file, `${file}.bak`);
|
|
37477
37343
|
} catch {
|
|
37478
37344
|
}
|
|
37479
37345
|
}
|
|
37480
37346
|
};
|
|
37481
37347
|
|
|
37482
|
-
// src/
|
|
37483
|
-
var
|
|
37348
|
+
// src/contact/connect-remote.ts
|
|
37349
|
+
var crypto5 = __toESM(require("crypto"), 1);
|
|
37484
37350
|
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
37485
37351
|
var RPC_TIMEOUT_MS = 5e3;
|
|
37486
37352
|
async function connectRemote(args) {
|
|
@@ -37498,7 +37364,10 @@ async function connectRemote(args) {
|
|
|
37498
37364
|
type: "auth",
|
|
37499
37365
|
token: args.token,
|
|
37500
37366
|
selfPrincipalId: args.selfPrincipalId,
|
|
37501
|
-
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 } : {}
|
|
37502
37371
|
})
|
|
37503
37372
|
);
|
|
37504
37373
|
await new Promise((resolve6, reject) => {
|
|
@@ -37526,7 +37395,7 @@ async function connectRemote(args) {
|
|
|
37526
37395
|
}, HANDSHAKE_TIMEOUT_MS);
|
|
37527
37396
|
});
|
|
37528
37397
|
function call(method, payload) {
|
|
37529
|
-
const requestId =
|
|
37398
|
+
const requestId = crypto5.randomUUID();
|
|
37530
37399
|
return new Promise((resolve6, reject) => {
|
|
37531
37400
|
const onMessage = (raw) => {
|
|
37532
37401
|
let f;
|
|
@@ -37557,7 +37426,6 @@ async function connectRemote(args) {
|
|
|
37557
37426
|
return {
|
|
37558
37427
|
ownerId: f.owner.id,
|
|
37559
37428
|
displayName: f.owner.displayName,
|
|
37560
|
-
capabilityId: f.capability.id,
|
|
37561
37429
|
grants: f.capability.grants
|
|
37562
37430
|
};
|
|
37563
37431
|
},
|
|
@@ -37570,62 +37438,83 @@ async function connectRemote(args) {
|
|
|
37570
37438
|
};
|
|
37571
37439
|
}
|
|
37572
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
|
+
|
|
37573
37462
|
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
37574
|
-
var
|
|
37575
|
-
var
|
|
37463
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
37464
|
+
var path23 = __toESM(require("path"), 1);
|
|
37576
37465
|
var MIGRATION_FLAG_NAME = ".migration.v1.done";
|
|
37577
37466
|
function migrateFlattenSessions(opts) {
|
|
37578
37467
|
const dataDir = opts.dataDir;
|
|
37579
37468
|
const now = opts.now ?? Date.now;
|
|
37580
|
-
const sessionsDir =
|
|
37581
|
-
const flagPath =
|
|
37469
|
+
const sessionsDir = path23.join(dataDir, "sessions");
|
|
37470
|
+
const flagPath = path23.join(sessionsDir, MIGRATION_FLAG_NAME);
|
|
37582
37471
|
if (existsSync3(flagPath)) {
|
|
37583
37472
|
return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
|
|
37584
37473
|
}
|
|
37585
37474
|
let movedBare = 0;
|
|
37586
37475
|
let movedVmOwner = 0;
|
|
37587
37476
|
let archivedListener = 0;
|
|
37588
|
-
const defaultDir =
|
|
37477
|
+
const defaultDir = path23.join(sessionsDir, "default");
|
|
37589
37478
|
if (existsSync3(defaultDir)) {
|
|
37590
37479
|
for (const entry of readdirSafe(defaultDir)) {
|
|
37591
37480
|
if (!entry.endsWith(".json")) continue;
|
|
37592
|
-
const src =
|
|
37593
|
-
const dst =
|
|
37594
|
-
|
|
37481
|
+
const src = path23.join(defaultDir, entry);
|
|
37482
|
+
const dst = path23.join(sessionsDir, entry);
|
|
37483
|
+
fs21.renameSync(src, dst);
|
|
37595
37484
|
movedBare += 1;
|
|
37596
37485
|
}
|
|
37597
37486
|
rmdirIfEmpty(defaultDir);
|
|
37598
37487
|
}
|
|
37599
37488
|
for (const pid of readdirSafe(sessionsDir)) {
|
|
37600
|
-
const personaDir =
|
|
37489
|
+
const personaDir = path23.join(sessionsDir, pid);
|
|
37601
37490
|
if (!isDir(personaDir)) continue;
|
|
37602
37491
|
if (pid === "default") continue;
|
|
37603
|
-
const ownerSrc =
|
|
37492
|
+
const ownerSrc = path23.join(personaDir, "owner");
|
|
37604
37493
|
if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
|
|
37605
|
-
const ownerDst =
|
|
37606
|
-
|
|
37494
|
+
const ownerDst = path23.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
|
|
37495
|
+
fs21.mkdirSync(ownerDst, { recursive: true });
|
|
37607
37496
|
for (const file of readdirSafe(ownerSrc)) {
|
|
37608
37497
|
if (!file.endsWith(".json")) continue;
|
|
37609
|
-
|
|
37498
|
+
fs21.renameSync(path23.join(ownerSrc, file), path23.join(ownerDst, file));
|
|
37610
37499
|
movedVmOwner += 1;
|
|
37611
37500
|
}
|
|
37612
37501
|
rmdirIfEmpty(ownerSrc);
|
|
37613
37502
|
}
|
|
37614
|
-
const listenerSrc =
|
|
37503
|
+
const listenerSrc = path23.join(personaDir, "listener");
|
|
37615
37504
|
if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
|
|
37616
|
-
const archiveDst =
|
|
37617
|
-
|
|
37505
|
+
const archiveDst = path23.join(dataDir, ".legacy", `listener-${pid}`);
|
|
37506
|
+
fs21.mkdirSync(archiveDst, { recursive: true });
|
|
37618
37507
|
for (const file of readdirSafe(listenerSrc)) {
|
|
37619
37508
|
if (!file.endsWith(".json")) continue;
|
|
37620
|
-
|
|
37509
|
+
fs21.renameSync(path23.join(listenerSrc, file), path23.join(archiveDst, file));
|
|
37621
37510
|
archivedListener += 1;
|
|
37622
37511
|
}
|
|
37623
37512
|
rmdirIfEmpty(listenerSrc);
|
|
37624
37513
|
}
|
|
37625
37514
|
rmdirIfEmpty(personaDir);
|
|
37626
37515
|
}
|
|
37627
|
-
|
|
37628
|
-
|
|
37516
|
+
fs21.mkdirSync(sessionsDir, { recursive: true });
|
|
37517
|
+
fs21.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
|
|
37629
37518
|
return {
|
|
37630
37519
|
skipped: false,
|
|
37631
37520
|
flagWritten: true,
|
|
@@ -37636,7 +37525,7 @@ function migrateFlattenSessions(opts) {
|
|
|
37636
37525
|
}
|
|
37637
37526
|
function existsSync3(p2) {
|
|
37638
37527
|
try {
|
|
37639
|
-
|
|
37528
|
+
fs21.statSync(p2);
|
|
37640
37529
|
return true;
|
|
37641
37530
|
} catch {
|
|
37642
37531
|
return false;
|
|
@@ -37644,31 +37533,31 @@ function existsSync3(p2) {
|
|
|
37644
37533
|
}
|
|
37645
37534
|
function isDir(p2) {
|
|
37646
37535
|
try {
|
|
37647
|
-
return
|
|
37536
|
+
return fs21.statSync(p2).isDirectory();
|
|
37648
37537
|
} catch {
|
|
37649
37538
|
return false;
|
|
37650
37539
|
}
|
|
37651
37540
|
}
|
|
37652
37541
|
function readdirSafe(p2) {
|
|
37653
37542
|
try {
|
|
37654
|
-
return
|
|
37543
|
+
return fs21.readdirSync(p2);
|
|
37655
37544
|
} catch {
|
|
37656
37545
|
return [];
|
|
37657
37546
|
}
|
|
37658
37547
|
}
|
|
37659
37548
|
function rmdirIfEmpty(p2) {
|
|
37660
37549
|
try {
|
|
37661
|
-
|
|
37550
|
+
fs21.rmdirSync(p2);
|
|
37662
37551
|
} catch {
|
|
37663
37552
|
}
|
|
37664
37553
|
}
|
|
37665
37554
|
|
|
37666
37555
|
// src/transport/http-router.ts
|
|
37667
|
-
var
|
|
37668
|
-
var
|
|
37556
|
+
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
37557
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
37669
37558
|
|
|
37670
37559
|
// src/attachment/mime.ts
|
|
37671
|
-
var
|
|
37560
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
37672
37561
|
var TEXT_PLAIN = "text/plain; charset=utf-8";
|
|
37673
37562
|
var EXT_TO_NATIVE_MIME = {
|
|
37674
37563
|
// 图片
|
|
@@ -37775,14 +37664,14 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
37775
37664
|
".mk"
|
|
37776
37665
|
]);
|
|
37777
37666
|
function lookupMime(filePathOrName) {
|
|
37778
|
-
const ext =
|
|
37667
|
+
const ext = import_node_path16.default.extname(filePathOrName).toLowerCase();
|
|
37779
37668
|
if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
|
|
37780
37669
|
if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
|
|
37781
37670
|
return "application/octet-stream";
|
|
37782
37671
|
}
|
|
37783
37672
|
|
|
37784
37673
|
// src/attachment/sign-url.ts
|
|
37785
|
-
var
|
|
37674
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
37786
37675
|
var HMAC_ALGO = "sha256";
|
|
37787
37676
|
function base64urlEncode(buf) {
|
|
37788
37677
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -37799,7 +37688,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
37799
37688
|
}
|
|
37800
37689
|
function computeSig(secret, absPath, e) {
|
|
37801
37690
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
37802
|
-
return
|
|
37691
|
+
return import_node_crypto4.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
37803
37692
|
}
|
|
37804
37693
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
37805
37694
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -37834,7 +37723,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
37834
37723
|
if (provided.length !== expected.length) {
|
|
37835
37724
|
return { ok: false, code: "BAD_SIG" };
|
|
37836
37725
|
}
|
|
37837
|
-
if (!
|
|
37726
|
+
if (!import_node_crypto4.default.timingSafeEqual(provided, expected)) {
|
|
37838
37727
|
return { ok: false, code: "BAD_SIG" };
|
|
37839
37728
|
}
|
|
37840
37729
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -37844,9 +37733,9 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
37844
37733
|
}
|
|
37845
37734
|
|
|
37846
37735
|
// src/attachment/upload.ts
|
|
37847
|
-
var
|
|
37848
|
-
var
|
|
37849
|
-
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);
|
|
37850
37739
|
var import_promises = require("stream/promises");
|
|
37851
37740
|
var UploadError = class extends Error {
|
|
37852
37741
|
constructor(code, message) {
|
|
@@ -37857,24 +37746,24 @@ var UploadError = class extends Error {
|
|
|
37857
37746
|
code;
|
|
37858
37747
|
};
|
|
37859
37748
|
function assertValidFileName(fileName) {
|
|
37860
|
-
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)) {
|
|
37861
37750
|
throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
|
|
37862
37751
|
}
|
|
37863
37752
|
}
|
|
37864
37753
|
var HASH_PREFIX_LEN = 16;
|
|
37865
37754
|
async function writeUploadedAttachment(args) {
|
|
37866
37755
|
assertValidFileName(args.fileName);
|
|
37867
|
-
const attachmentsRoot =
|
|
37756
|
+
const attachmentsRoot = import_node_path17.default.join(args.sessionDir, ".attachments");
|
|
37868
37757
|
try {
|
|
37869
|
-
|
|
37758
|
+
import_node_fs15.default.mkdirSync(attachmentsRoot, { recursive: true });
|
|
37870
37759
|
} catch (err) {
|
|
37871
37760
|
throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
|
|
37872
37761
|
}
|
|
37873
|
-
const hasher =
|
|
37762
|
+
const hasher = import_node_crypto5.default.createHash("sha256");
|
|
37874
37763
|
let actualSize = 0;
|
|
37875
|
-
const tmpPath =
|
|
37764
|
+
const tmpPath = import_node_path17.default.join(
|
|
37876
37765
|
attachmentsRoot,
|
|
37877
|
-
`.upload-${process.pid}-${Date.now()}-${
|
|
37766
|
+
`.upload-${process.pid}-${Date.now()}-${import_node_crypto5.default.randomBytes(4).toString("hex")}`
|
|
37878
37767
|
);
|
|
37879
37768
|
try {
|
|
37880
37769
|
await (0, import_promises.pipeline)(
|
|
@@ -37887,18 +37776,18 @@ async function writeUploadedAttachment(args) {
|
|
|
37887
37776
|
yield buf;
|
|
37888
37777
|
}
|
|
37889
37778
|
},
|
|
37890
|
-
|
|
37779
|
+
import_node_fs15.default.createWriteStream(tmpPath, { mode: 384 })
|
|
37891
37780
|
);
|
|
37892
37781
|
} catch (err) {
|
|
37893
37782
|
try {
|
|
37894
|
-
|
|
37783
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
37895
37784
|
} catch {
|
|
37896
37785
|
}
|
|
37897
37786
|
throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
|
|
37898
37787
|
}
|
|
37899
37788
|
if (actualSize !== args.contentLength) {
|
|
37900
37789
|
try {
|
|
37901
|
-
|
|
37790
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
37902
37791
|
} catch {
|
|
37903
37792
|
}
|
|
37904
37793
|
throw new UploadError(
|
|
@@ -37907,35 +37796,35 @@ async function writeUploadedAttachment(args) {
|
|
|
37907
37796
|
);
|
|
37908
37797
|
}
|
|
37909
37798
|
const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
|
|
37910
|
-
const hashDir =
|
|
37799
|
+
const hashDir = import_node_path17.default.join(attachmentsRoot, attachmentId);
|
|
37911
37800
|
let finalFileName;
|
|
37912
37801
|
let hashDirExists = false;
|
|
37913
37802
|
try {
|
|
37914
|
-
hashDirExists =
|
|
37803
|
+
hashDirExists = import_node_fs15.default.statSync(hashDir).isDirectory();
|
|
37915
37804
|
} catch {
|
|
37916
37805
|
}
|
|
37917
37806
|
if (hashDirExists) {
|
|
37918
|
-
const existing =
|
|
37807
|
+
const existing = import_node_fs15.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
|
|
37919
37808
|
finalFileName = existing[0] ?? args.fileName;
|
|
37920
37809
|
try {
|
|
37921
|
-
|
|
37810
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
37922
37811
|
} catch {
|
|
37923
37812
|
}
|
|
37924
37813
|
} else {
|
|
37925
37814
|
try {
|
|
37926
|
-
|
|
37815
|
+
import_node_fs15.default.mkdirSync(hashDir, { recursive: true });
|
|
37927
37816
|
finalFileName = args.fileName;
|
|
37928
|
-
|
|
37817
|
+
import_node_fs15.default.renameSync(tmpPath, import_node_path17.default.join(hashDir, finalFileName));
|
|
37929
37818
|
} catch (err) {
|
|
37930
37819
|
try {
|
|
37931
|
-
|
|
37820
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
37932
37821
|
} catch {
|
|
37933
37822
|
}
|
|
37934
37823
|
throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
|
|
37935
37824
|
}
|
|
37936
37825
|
}
|
|
37937
|
-
const absPath =
|
|
37938
|
-
const relPath =
|
|
37826
|
+
const absPath = import_node_path17.default.join(hashDir, finalFileName);
|
|
37827
|
+
const relPath = import_node_path17.default.relative(args.sessionDir, absPath);
|
|
37939
37828
|
args.groupFileStore.upsert(args.scope, args.sessionId, {
|
|
37940
37829
|
relPath: absPath,
|
|
37941
37830
|
// 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
|
|
@@ -37949,7 +37838,7 @@ async function writeUploadedAttachment(args) {
|
|
|
37949
37838
|
|
|
37950
37839
|
// src/extension/import.ts
|
|
37951
37840
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
37952
|
-
var
|
|
37841
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
37953
37842
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
37954
37843
|
var import_jszip = __toESM(require_lib3(), 1);
|
|
37955
37844
|
var ImportError = class extends Error {
|
|
@@ -37967,7 +37856,7 @@ async function importZip(buf, root) {
|
|
|
37967
37856
|
throw new ImportError("ZIP_INVALID", `failed to load zip: ${e.message}`);
|
|
37968
37857
|
}
|
|
37969
37858
|
for (const name of Object.keys(zip.files)) {
|
|
37970
|
-
if (name.includes("..") || name.startsWith("/") ||
|
|
37859
|
+
if (name.includes("..") || name.startsWith("/") || import_node_path18.default.isAbsolute(name)) {
|
|
37971
37860
|
throw new ImportError("ZIP_INVALID", `unsafe zip entry path: ${name}`);
|
|
37972
37861
|
}
|
|
37973
37862
|
}
|
|
@@ -38000,7 +37889,7 @@ async function importZip(buf, root) {
|
|
|
38000
37889
|
);
|
|
38001
37890
|
}
|
|
38002
37891
|
}
|
|
38003
|
-
const destDir =
|
|
37892
|
+
const destDir = import_node_path18.default.join(root, manifest.id);
|
|
38004
37893
|
let destExists = false;
|
|
38005
37894
|
try {
|
|
38006
37895
|
await import_promises2.default.access(destDir);
|
|
@@ -38010,15 +37899,15 @@ async function importZip(buf, root) {
|
|
|
38010
37899
|
if (destExists) {
|
|
38011
37900
|
throw new ImportError("ALREADY_EXISTS", `extension ${manifest.id} already installed`);
|
|
38012
37901
|
}
|
|
38013
|
-
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}-`));
|
|
38014
37903
|
try {
|
|
38015
37904
|
for (const [name, entry] of Object.entries(zip.files)) {
|
|
38016
|
-
const dest =
|
|
37905
|
+
const dest = import_node_path18.default.join(stage, name);
|
|
38017
37906
|
if (entry.dir) {
|
|
38018
37907
|
await import_promises2.default.mkdir(dest, { recursive: true });
|
|
38019
37908
|
continue;
|
|
38020
37909
|
}
|
|
38021
|
-
await import_promises2.default.mkdir(
|
|
37910
|
+
await import_promises2.default.mkdir(import_node_path18.default.dirname(dest), { recursive: true });
|
|
38022
37911
|
const content = await entry.async("nodebuffer");
|
|
38023
37912
|
await import_promises2.default.writeFile(dest, content);
|
|
38024
37913
|
}
|
|
@@ -38064,7 +37953,7 @@ function isValidUploadFileName(fileName) {
|
|
|
38064
37953
|
if (fileName === "." || fileName === "..") return false;
|
|
38065
37954
|
if (fileName.startsWith(".")) return false;
|
|
38066
37955
|
if (fileName.includes("/") || fileName.includes("\\")) return false;
|
|
38067
|
-
return fileName ===
|
|
37956
|
+
return fileName === import_node_path19.default.basename(fileName);
|
|
38068
37957
|
}
|
|
38069
37958
|
function createHttpRouter(deps) {
|
|
38070
37959
|
return async (req, res) => {
|
|
@@ -38079,13 +37968,23 @@ function createHttpRouter(deps) {
|
|
|
38079
37968
|
});
|
|
38080
37969
|
return true;
|
|
38081
37970
|
}
|
|
38082
|
-
if (url.pathname
|
|
38083
|
-
|
|
38084
|
-
|
|
38085
|
-
|
|
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}` });
|
|
37974
|
+
return true;
|
|
37975
|
+
}
|
|
37976
|
+
if (!deps.feishuLogin) {
|
|
37977
|
+
sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "feishu login not wired" });
|
|
38086
37978
|
return true;
|
|
38087
37979
|
}
|
|
38088
|
-
|
|
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);
|
|
38089
37988
|
return true;
|
|
38090
37989
|
}
|
|
38091
37990
|
if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload" && url.pathname !== "/extensions/import") {
|
|
@@ -38248,7 +38147,7 @@ function createHttpRouter(deps) {
|
|
|
38248
38147
|
return true;
|
|
38249
38148
|
}
|
|
38250
38149
|
let absPath;
|
|
38251
|
-
if (
|
|
38150
|
+
if (import_node_path19.default.isAbsolute(pathParam)) {
|
|
38252
38151
|
absPath = pathParam;
|
|
38253
38152
|
} else if (deps.sessionStore) {
|
|
38254
38153
|
const file = deps.sessionStore.read(sid);
|
|
@@ -38256,7 +38155,7 @@ function createHttpRouter(deps) {
|
|
|
38256
38155
|
sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
|
|
38257
38156
|
return true;
|
|
38258
38157
|
}
|
|
38259
|
-
absPath =
|
|
38158
|
+
absPath = import_node_path19.default.join(file.cwd, pathParam);
|
|
38260
38159
|
} else {
|
|
38261
38160
|
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
|
|
38262
38161
|
return true;
|
|
@@ -38323,6 +38222,9 @@ function parseUrl(rawUrl) {
|
|
|
38323
38222
|
return null;
|
|
38324
38223
|
}
|
|
38325
38224
|
}
|
|
38225
|
+
function escapeHtml(s) {
|
|
38226
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
38227
|
+
}
|
|
38326
38228
|
function sendJson(res, status, body, extraHeaders) {
|
|
38327
38229
|
res.writeHead(status, {
|
|
38328
38230
|
"Content-Type": "application/json; charset=utf-8",
|
|
@@ -38336,7 +38238,7 @@ function withCtx(ctx, body) {
|
|
|
38336
38238
|
function streamFile(res, absPath, logger) {
|
|
38337
38239
|
let stat;
|
|
38338
38240
|
try {
|
|
38339
|
-
stat =
|
|
38241
|
+
stat = import_node_fs16.default.statSync(absPath);
|
|
38340
38242
|
} catch (err) {
|
|
38341
38243
|
const code = err?.code;
|
|
38342
38244
|
if (code === "ENOENT") {
|
|
@@ -38351,7 +38253,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38351
38253
|
return;
|
|
38352
38254
|
}
|
|
38353
38255
|
const mime = lookupMime(absPath);
|
|
38354
|
-
const basename =
|
|
38256
|
+
const basename = import_node_path19.default.basename(absPath);
|
|
38355
38257
|
res.writeHead(200, {
|
|
38356
38258
|
"Content-Type": mime,
|
|
38357
38259
|
"Content-Length": String(stat.size),
|
|
@@ -38359,7 +38261,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38359
38261
|
// 防止浏览器把任意 mime 当 html 渲染
|
|
38360
38262
|
"X-Content-Type-Options": "nosniff"
|
|
38361
38263
|
});
|
|
38362
|
-
const stream =
|
|
38264
|
+
const stream = import_node_fs16.default.createReadStream(absPath);
|
|
38363
38265
|
stream.on("error", (err) => {
|
|
38364
38266
|
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
38365
38267
|
res.destroy();
|
|
@@ -38368,8 +38270,8 @@ function streamFile(res, absPath, logger) {
|
|
|
38368
38270
|
}
|
|
38369
38271
|
|
|
38370
38272
|
// src/attachment/gc.ts
|
|
38371
|
-
var
|
|
38372
|
-
var
|
|
38273
|
+
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
38274
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
38373
38275
|
var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
|
|
38374
38276
|
function runAttachmentGc(args) {
|
|
38375
38277
|
const now = (args.now ?? Date.now)();
|
|
@@ -38378,38 +38280,38 @@ function runAttachmentGc(args) {
|
|
|
38378
38280
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38379
38281
|
for (const entry of args.groupFileStore.list(scope, sessionId)) {
|
|
38380
38282
|
if (entry.stale) continue;
|
|
38381
|
-
if (
|
|
38283
|
+
if (import_node_path20.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
|
|
38382
38284
|
}
|
|
38383
38285
|
}
|
|
38384
38286
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38385
|
-
const sessionDir = args.getSessionCwd?.(sessionId) ??
|
|
38287
|
+
const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path20.default.join(
|
|
38386
38288
|
args.dataDir,
|
|
38387
38289
|
"sessions",
|
|
38388
38290
|
...scopeSubPath(scope).map(safeFileName),
|
|
38389
38291
|
safeFileName(sessionId)
|
|
38390
38292
|
);
|
|
38391
|
-
const attRoot =
|
|
38293
|
+
const attRoot = import_node_path20.default.join(sessionDir, ".attachments");
|
|
38392
38294
|
let hashDirs;
|
|
38393
38295
|
try {
|
|
38394
|
-
hashDirs =
|
|
38296
|
+
hashDirs = import_node_fs17.default.readdirSync(attRoot);
|
|
38395
38297
|
} catch (err) {
|
|
38396
38298
|
if (err.code === "ENOENT") continue;
|
|
38397
38299
|
args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
|
|
38398
38300
|
continue;
|
|
38399
38301
|
}
|
|
38400
38302
|
for (const hashDir of hashDirs) {
|
|
38401
|
-
const hashDirAbs =
|
|
38303
|
+
const hashDirAbs = import_node_path20.default.join(attRoot, hashDir);
|
|
38402
38304
|
let files;
|
|
38403
38305
|
try {
|
|
38404
|
-
files =
|
|
38306
|
+
files = import_node_fs17.default.readdirSync(hashDirAbs);
|
|
38405
38307
|
} catch {
|
|
38406
38308
|
continue;
|
|
38407
38309
|
}
|
|
38408
38310
|
for (const name of files) {
|
|
38409
|
-
const file =
|
|
38311
|
+
const file = import_node_path20.default.join(hashDirAbs, name);
|
|
38410
38312
|
let stat;
|
|
38411
38313
|
try {
|
|
38412
|
-
stat =
|
|
38314
|
+
stat = import_node_fs17.default.statSync(file);
|
|
38413
38315
|
} catch {
|
|
38414
38316
|
continue;
|
|
38415
38317
|
}
|
|
@@ -38418,27 +38320,27 @@ function runAttachmentGc(args) {
|
|
|
38418
38320
|
if (age < ttlMs) continue;
|
|
38419
38321
|
if (liveAbs.has(file)) continue;
|
|
38420
38322
|
try {
|
|
38421
|
-
|
|
38323
|
+
import_node_fs17.default.unlinkSync(file);
|
|
38422
38324
|
} catch (err) {
|
|
38423
38325
|
args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
|
|
38424
38326
|
}
|
|
38425
38327
|
}
|
|
38426
38328
|
try {
|
|
38427
|
-
if (
|
|
38329
|
+
if (import_node_fs17.default.readdirSync(hashDirAbs).length === 0) import_node_fs17.default.rmdirSync(hashDirAbs);
|
|
38428
38330
|
} catch {
|
|
38429
38331
|
}
|
|
38430
38332
|
}
|
|
38431
38333
|
try {
|
|
38432
|
-
if (
|
|
38334
|
+
if (import_node_fs17.default.readdirSync(attRoot).length === 0) import_node_fs17.default.rmdirSync(attRoot);
|
|
38433
38335
|
} catch {
|
|
38434
38336
|
}
|
|
38435
38337
|
}
|
|
38436
38338
|
}
|
|
38437
38339
|
|
|
38438
38340
|
// src/attachment/group.ts
|
|
38439
|
-
var
|
|
38440
|
-
var
|
|
38441
|
-
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);
|
|
38442
38344
|
init_protocol();
|
|
38443
38345
|
var GroupFileStore = class {
|
|
38444
38346
|
dataDir;
|
|
@@ -38449,11 +38351,11 @@ var GroupFileStore = class {
|
|
|
38449
38351
|
this.logger = opts.logger;
|
|
38450
38352
|
}
|
|
38451
38353
|
rootForScope(scope) {
|
|
38452
|
-
return
|
|
38354
|
+
return import_node_path21.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
38453
38355
|
}
|
|
38454
38356
|
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
38455
38357
|
filePath(scope, sessionId) {
|
|
38456
|
-
return
|
|
38358
|
+
return import_node_path21.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
38457
38359
|
}
|
|
38458
38360
|
cacheKey(scope, sessionId) {
|
|
38459
38361
|
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
@@ -38462,7 +38364,7 @@ var GroupFileStore = class {
|
|
|
38462
38364
|
readFile(scope, sessionId) {
|
|
38463
38365
|
const file = this.filePath(scope, sessionId);
|
|
38464
38366
|
try {
|
|
38465
|
-
const raw =
|
|
38367
|
+
const raw = import_node_fs18.default.readFileSync(file, "utf8");
|
|
38466
38368
|
const parsed = JSON.parse(raw);
|
|
38467
38369
|
if (!Array.isArray(parsed)) {
|
|
38468
38370
|
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
@@ -38488,10 +38390,10 @@ var GroupFileStore = class {
|
|
|
38488
38390
|
}
|
|
38489
38391
|
writeFile(scope, sessionId, entries) {
|
|
38490
38392
|
const file = this.filePath(scope, sessionId);
|
|
38491
|
-
|
|
38393
|
+
import_node_fs18.default.mkdirSync(import_node_path21.default.dirname(file), { recursive: true });
|
|
38492
38394
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
38493
|
-
|
|
38494
|
-
|
|
38395
|
+
import_node_fs18.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
38396
|
+
import_node_fs18.default.renameSync(tmp, file);
|
|
38495
38397
|
}
|
|
38496
38398
|
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
38497
38399
|
list(scope, sessionId) {
|
|
@@ -38527,7 +38429,7 @@ var GroupFileStore = class {
|
|
|
38527
38429
|
entries[idx] = next;
|
|
38528
38430
|
} else {
|
|
38529
38431
|
next = {
|
|
38530
|
-
id: `gf-${
|
|
38432
|
+
id: `gf-${import_node_crypto6.default.randomBytes(6).toString("base64url")}`,
|
|
38531
38433
|
relPath: input.relPath,
|
|
38532
38434
|
from: input.from,
|
|
38533
38435
|
label: input.label,
|
|
@@ -38577,10 +38479,10 @@ var GroupFileStore = class {
|
|
|
38577
38479
|
};
|
|
38578
38480
|
|
|
38579
38481
|
// src/discovery/state-file.ts
|
|
38580
|
-
var
|
|
38581
|
-
var
|
|
38482
|
+
var import_node_fs19 = __toESM(require("fs"), 1);
|
|
38483
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
38582
38484
|
function defaultStateFilePath(dataDir) {
|
|
38583
|
-
return
|
|
38485
|
+
return import_node_path22.default.join(dataDir, "state.json");
|
|
38584
38486
|
}
|
|
38585
38487
|
function isPidAlive(pid) {
|
|
38586
38488
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
@@ -38602,7 +38504,7 @@ var StateFileManager = class {
|
|
|
38602
38504
|
}
|
|
38603
38505
|
read() {
|
|
38604
38506
|
try {
|
|
38605
|
-
const raw =
|
|
38507
|
+
const raw = import_node_fs19.default.readFileSync(this.file, "utf8");
|
|
38606
38508
|
const parsed = JSON.parse(raw);
|
|
38607
38509
|
return parsed;
|
|
38608
38510
|
} catch {
|
|
@@ -38616,34 +38518,34 @@ var StateFileManager = class {
|
|
|
38616
38518
|
return { status: "stale", existing };
|
|
38617
38519
|
}
|
|
38618
38520
|
write(state) {
|
|
38619
|
-
|
|
38521
|
+
import_node_fs19.default.mkdirSync(import_node_path22.default.dirname(this.file), { recursive: true });
|
|
38620
38522
|
const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
|
|
38621
|
-
|
|
38622
|
-
|
|
38523
|
+
import_node_fs19.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
38524
|
+
import_node_fs19.default.renameSync(tmp, this.file);
|
|
38623
38525
|
if (process.platform !== "win32") {
|
|
38624
38526
|
try {
|
|
38625
|
-
|
|
38527
|
+
import_node_fs19.default.chmodSync(this.file, 384);
|
|
38626
38528
|
} catch {
|
|
38627
38529
|
}
|
|
38628
38530
|
}
|
|
38629
38531
|
}
|
|
38630
38532
|
delete() {
|
|
38631
38533
|
try {
|
|
38632
|
-
|
|
38534
|
+
import_node_fs19.default.unlinkSync(this.file);
|
|
38633
38535
|
} catch {
|
|
38634
38536
|
}
|
|
38635
38537
|
}
|
|
38636
38538
|
};
|
|
38637
38539
|
|
|
38638
38540
|
// src/tunnel/tunnel-manager.ts
|
|
38639
|
-
var
|
|
38640
|
-
var
|
|
38641
|
-
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);
|
|
38642
38544
|
var import_node_child_process5 = require("child_process");
|
|
38643
38545
|
|
|
38644
38546
|
// src/tunnel/tunnel-store.ts
|
|
38645
|
-
var
|
|
38646
|
-
var
|
|
38547
|
+
var import_node_fs20 = __toESM(require("fs"), 1);
|
|
38548
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
38647
38549
|
var TunnelStore = class {
|
|
38648
38550
|
constructor(filePath) {
|
|
38649
38551
|
this.filePath = filePath;
|
|
@@ -38651,7 +38553,7 @@ var TunnelStore = class {
|
|
|
38651
38553
|
filePath;
|
|
38652
38554
|
async get() {
|
|
38653
38555
|
try {
|
|
38654
|
-
const raw = await
|
|
38556
|
+
const raw = await import_node_fs20.default.promises.readFile(this.filePath, "utf8");
|
|
38655
38557
|
const obj = JSON.parse(raw);
|
|
38656
38558
|
if (!isPersistedTunnel(obj)) return null;
|
|
38657
38559
|
return obj;
|
|
@@ -38662,22 +38564,22 @@ var TunnelStore = class {
|
|
|
38662
38564
|
}
|
|
38663
38565
|
}
|
|
38664
38566
|
async set(v2) {
|
|
38665
|
-
const dir =
|
|
38666
|
-
await
|
|
38567
|
+
const dir = import_node_path23.default.dirname(this.filePath);
|
|
38568
|
+
await import_node_fs20.default.promises.mkdir(dir, { recursive: true });
|
|
38667
38569
|
const data = JSON.stringify(v2, null, 2);
|
|
38668
38570
|
const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
38669
|
-
await
|
|
38571
|
+
await import_node_fs20.default.promises.writeFile(tmp, data, { mode: 384 });
|
|
38670
38572
|
if (process.platform !== "win32") {
|
|
38671
38573
|
try {
|
|
38672
|
-
await
|
|
38574
|
+
await import_node_fs20.default.promises.chmod(tmp, 384);
|
|
38673
38575
|
} catch {
|
|
38674
38576
|
}
|
|
38675
38577
|
}
|
|
38676
|
-
await
|
|
38578
|
+
await import_node_fs20.default.promises.rename(tmp, this.filePath);
|
|
38677
38579
|
}
|
|
38678
38580
|
async clear() {
|
|
38679
38581
|
try {
|
|
38680
|
-
await
|
|
38582
|
+
await import_node_fs20.default.promises.unlink(this.filePath);
|
|
38681
38583
|
} catch (err) {
|
|
38682
38584
|
const code = err?.code;
|
|
38683
38585
|
if (code !== "ENOENT") throw err;
|
|
@@ -38772,9 +38674,9 @@ function escape(v2) {
|
|
|
38772
38674
|
}
|
|
38773
38675
|
|
|
38774
38676
|
// src/tunnel/frpc-binary.ts
|
|
38775
|
-
var
|
|
38677
|
+
var import_node_fs21 = __toESM(require("fs"), 1);
|
|
38776
38678
|
var import_node_os11 = __toESM(require("os"), 1);
|
|
38777
|
-
var
|
|
38679
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
38778
38680
|
var import_node_child_process3 = require("child_process");
|
|
38779
38681
|
var import_node_stream2 = require("stream");
|
|
38780
38682
|
var import_promises3 = require("stream/promises");
|
|
@@ -38806,20 +38708,20 @@ function frpcDownloadUrl(version2, p2) {
|
|
|
38806
38708
|
}
|
|
38807
38709
|
async function ensureFrpcBinary(opts) {
|
|
38808
38710
|
if (opts.override) {
|
|
38809
|
-
if (!
|
|
38711
|
+
if (!import_node_fs21.default.existsSync(opts.override)) {
|
|
38810
38712
|
throw new Error(`frpc binary not found at override path: ${opts.override}`);
|
|
38811
38713
|
}
|
|
38812
38714
|
return opts.override;
|
|
38813
38715
|
}
|
|
38814
38716
|
const version2 = opts.version ?? FRPC_VERSION;
|
|
38815
38717
|
const platform = opts.platform ?? detectPlatform();
|
|
38816
|
-
const binDir =
|
|
38817
|
-
|
|
38718
|
+
const binDir = import_node_path24.default.join(opts.dataDir, "bin");
|
|
38719
|
+
import_node_fs21.default.mkdirSync(binDir, { recursive: true });
|
|
38818
38720
|
cleanupStaleArtifacts(binDir);
|
|
38819
|
-
const stableBin =
|
|
38820
|
-
if (
|
|
38721
|
+
const stableBin = import_node_path24.default.join(binDir, "frpc");
|
|
38722
|
+
if (import_node_fs21.default.existsSync(stableBin)) return stableBin;
|
|
38821
38723
|
const partialBin = `${stableBin}.partial`;
|
|
38822
|
-
const tarballPath =
|
|
38724
|
+
const tarballPath = import_node_path24.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
|
|
38823
38725
|
try {
|
|
38824
38726
|
const url = frpcDownloadUrl(version2, platform);
|
|
38825
38727
|
await downloadToFile(url, tarballPath, opts.fetchImpl);
|
|
@@ -38828,8 +38730,8 @@ async function ensureFrpcBinary(opts) {
|
|
|
38828
38730
|
} else {
|
|
38829
38731
|
await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
|
|
38830
38732
|
}
|
|
38831
|
-
|
|
38832
|
-
|
|
38733
|
+
import_node_fs21.default.chmodSync(partialBin, 493);
|
|
38734
|
+
import_node_fs21.default.renameSync(partialBin, stableBin);
|
|
38833
38735
|
} finally {
|
|
38834
38736
|
safeUnlink(tarballPath);
|
|
38835
38737
|
safeUnlink(partialBin);
|
|
@@ -38839,15 +38741,15 @@ async function ensureFrpcBinary(opts) {
|
|
|
38839
38741
|
function cleanupStaleArtifacts(binDir) {
|
|
38840
38742
|
let entries;
|
|
38841
38743
|
try {
|
|
38842
|
-
entries =
|
|
38744
|
+
entries = import_node_fs21.default.readdirSync(binDir);
|
|
38843
38745
|
} catch {
|
|
38844
38746
|
return;
|
|
38845
38747
|
}
|
|
38846
38748
|
for (const name of entries) {
|
|
38847
38749
|
if (name.endsWith(".partial") || name.startsWith("extract-")) {
|
|
38848
|
-
const full =
|
|
38750
|
+
const full = import_node_path24.default.join(binDir, name);
|
|
38849
38751
|
try {
|
|
38850
|
-
|
|
38752
|
+
import_node_fs21.default.rmSync(full, { recursive: true, force: true });
|
|
38851
38753
|
} catch {
|
|
38852
38754
|
}
|
|
38853
38755
|
}
|
|
@@ -38855,7 +38757,7 @@ function cleanupStaleArtifacts(binDir) {
|
|
|
38855
38757
|
}
|
|
38856
38758
|
function safeUnlink(p2) {
|
|
38857
38759
|
try {
|
|
38858
|
-
|
|
38760
|
+
import_node_fs21.default.unlinkSync(p2);
|
|
38859
38761
|
} catch {
|
|
38860
38762
|
}
|
|
38861
38763
|
}
|
|
@@ -38866,13 +38768,13 @@ async function downloadToFile(url, dest, fetchImpl) {
|
|
|
38866
38768
|
if (!res.ok || !res.body) {
|
|
38867
38769
|
throw new Error(`download failed: ${res.status} ${res.statusText}`);
|
|
38868
38770
|
}
|
|
38869
|
-
const out =
|
|
38771
|
+
const out = import_node_fs21.default.createWriteStream(dest);
|
|
38870
38772
|
const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
|
|
38871
38773
|
await (0, import_promises3.pipeline)(nodeStream, out);
|
|
38872
38774
|
}
|
|
38873
38775
|
async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
|
|
38874
|
-
const work =
|
|
38875
|
-
|
|
38776
|
+
const work = import_node_path24.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
|
|
38777
|
+
import_node_fs21.default.mkdirSync(work, { recursive: true });
|
|
38876
38778
|
try {
|
|
38877
38779
|
await new Promise((resolve6, reject) => {
|
|
38878
38780
|
const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
|
|
@@ -38880,32 +38782,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
|
|
|
38880
38782
|
proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
|
|
38881
38783
|
});
|
|
38882
38784
|
const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
|
|
38883
|
-
const src =
|
|
38884
|
-
if (!
|
|
38785
|
+
const src = import_node_path24.default.join(work, dirName, "frpc");
|
|
38786
|
+
if (!import_node_fs21.default.existsSync(src)) {
|
|
38885
38787
|
throw new Error(`frpc not found inside tarball at ${src}`);
|
|
38886
38788
|
}
|
|
38887
|
-
|
|
38789
|
+
import_node_fs21.default.copyFileSync(src, destBin);
|
|
38888
38790
|
} finally {
|
|
38889
|
-
|
|
38791
|
+
import_node_fs21.default.rmSync(work, { recursive: true, force: true });
|
|
38890
38792
|
}
|
|
38891
38793
|
}
|
|
38892
38794
|
|
|
38893
38795
|
// src/tunnel/frpc-process.ts
|
|
38894
|
-
var
|
|
38895
|
-
var
|
|
38796
|
+
var import_node_fs22 = __toESM(require("fs"), 1);
|
|
38797
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
38896
38798
|
var import_node_child_process4 = require("child_process");
|
|
38897
38799
|
function frpcPidFilePath(dataDir) {
|
|
38898
|
-
return
|
|
38800
|
+
return import_node_path25.default.join(dataDir, "frpc.pid");
|
|
38899
38801
|
}
|
|
38900
38802
|
function writeFrpcPid(dataDir, pid) {
|
|
38901
38803
|
try {
|
|
38902
|
-
|
|
38804
|
+
import_node_fs22.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
|
|
38903
38805
|
} catch {
|
|
38904
38806
|
}
|
|
38905
38807
|
}
|
|
38906
38808
|
function clearFrpcPid(dataDir) {
|
|
38907
38809
|
try {
|
|
38908
|
-
|
|
38810
|
+
import_node_fs22.default.unlinkSync(frpcPidFilePath(dataDir));
|
|
38909
38811
|
} catch {
|
|
38910
38812
|
}
|
|
38911
38813
|
}
|
|
@@ -38921,7 +38823,7 @@ function defaultIsPidAlive(pid) {
|
|
|
38921
38823
|
}
|
|
38922
38824
|
function defaultReadPidFile(file) {
|
|
38923
38825
|
try {
|
|
38924
|
-
return
|
|
38826
|
+
return import_node_fs22.default.readFileSync(file, "utf8");
|
|
38925
38827
|
} catch {
|
|
38926
38828
|
return null;
|
|
38927
38829
|
}
|
|
@@ -38937,7 +38839,7 @@ function defaultSleep(ms) {
|
|
|
38937
38839
|
}
|
|
38938
38840
|
async function killStaleFrpc(deps) {
|
|
38939
38841
|
const pidFile = frpcPidFilePath(deps.dataDir);
|
|
38940
|
-
const tomlPath =
|
|
38842
|
+
const tomlPath = import_node_path25.default.join(deps.dataDir, "frpc.toml");
|
|
38941
38843
|
const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
|
|
38942
38844
|
const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
|
|
38943
38845
|
const killPid = deps.killPidImpl ?? defaultKillPid;
|
|
@@ -38961,7 +38863,7 @@ async function killStaleFrpc(deps) {
|
|
|
38961
38863
|
}
|
|
38962
38864
|
if (victims.size === 0) {
|
|
38963
38865
|
try {
|
|
38964
|
-
|
|
38866
|
+
import_node_fs22.default.unlinkSync(pidFile);
|
|
38965
38867
|
} catch {
|
|
38966
38868
|
}
|
|
38967
38869
|
return;
|
|
@@ -38972,7 +38874,7 @@ async function killStaleFrpc(deps) {
|
|
|
38972
38874
|
}
|
|
38973
38875
|
await sleep(deps.reapWaitMs ?? 300);
|
|
38974
38876
|
try {
|
|
38975
|
-
|
|
38877
|
+
import_node_fs22.default.unlinkSync(pidFile);
|
|
38976
38878
|
} catch {
|
|
38977
38879
|
}
|
|
38978
38880
|
}
|
|
@@ -39009,7 +38911,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
39009
38911
|
var TunnelManager = class {
|
|
39010
38912
|
constructor(deps) {
|
|
39011
38913
|
this.deps = deps;
|
|
39012
|
-
this.store = deps.store ?? new TunnelStore(
|
|
38914
|
+
this.store = deps.store ?? new TunnelStore(import_node_path26.default.join(deps.dataDir, "tunnel.json"));
|
|
39013
38915
|
this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
|
|
39014
38916
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
|
|
39015
38917
|
}
|
|
@@ -39136,8 +39038,8 @@ var TunnelManager = class {
|
|
|
39136
39038
|
dataDir: this.deps.dataDir,
|
|
39137
39039
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
39138
39040
|
});
|
|
39139
|
-
const tomlPath =
|
|
39140
|
-
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")}`;
|
|
39141
39043
|
const toml = buildFrpcToml({
|
|
39142
39044
|
serverAddr: t.frpsHost,
|
|
39143
39045
|
serverPort: t.frpsPort,
|
|
@@ -39147,12 +39049,12 @@ var TunnelManager = class {
|
|
|
39147
39049
|
localPort,
|
|
39148
39050
|
logLevel: "info"
|
|
39149
39051
|
});
|
|
39150
|
-
await
|
|
39052
|
+
await import_node_fs23.default.promises.writeFile(tomlPath, toml, { mode: 384 });
|
|
39151
39053
|
const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
|
|
39152
39054
|
stdio: ["ignore", "pipe", "pipe"]
|
|
39153
39055
|
});
|
|
39154
|
-
const logFilePath =
|
|
39155
|
-
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 });
|
|
39156
39058
|
logStream.on("error", () => {
|
|
39157
39059
|
});
|
|
39158
39060
|
const tee = (chunk) => {
|
|
@@ -39235,31 +39137,31 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
39235
39137
|
|
|
39236
39138
|
// src/tunnel/device-key.ts
|
|
39237
39139
|
var import_node_os12 = __toESM(require("os"), 1);
|
|
39238
|
-
var
|
|
39239
|
-
var
|
|
39140
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
39141
|
+
var import_node_crypto8 = __toESM(require("crypto"), 1);
|
|
39240
39142
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
39241
39143
|
function deriveStableDeviceKey(opts = {}) {
|
|
39242
39144
|
const hostname = opts.hostname ?? import_node_os12.default.hostname();
|
|
39243
39145
|
const uid = opts.uid ?? (typeof import_node_os12.default.userInfo === "function" ? import_node_os12.default.userInfo().uid : 0);
|
|
39244
39146
|
const home = opts.home ?? import_node_os12.default.homedir();
|
|
39245
|
-
const defaultDataDir =
|
|
39246
|
-
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;
|
|
39247
39149
|
const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
|
|
39248
39150
|
const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
|
|
39249
|
-
return
|
|
39151
|
+
return import_node_crypto8.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
39250
39152
|
}
|
|
39251
39153
|
|
|
39252
39154
|
// src/auth-store.ts
|
|
39253
|
-
var
|
|
39254
|
-
var
|
|
39255
|
-
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);
|
|
39256
39158
|
var AUTH_FILE_NAME = "auth.json";
|
|
39257
39159
|
function authFilePath(dataDir) {
|
|
39258
|
-
return
|
|
39160
|
+
return import_node_path28.default.join(dataDir, AUTH_FILE_NAME);
|
|
39259
39161
|
}
|
|
39260
39162
|
function loadOrCreateAuthFile(opts) {
|
|
39261
39163
|
const file = authFilePath(opts.dataDir);
|
|
39262
|
-
const generate = opts.generate ??
|
|
39164
|
+
const generate = opts.generate ?? defaultGenerateToken;
|
|
39263
39165
|
const genId = opts.genOwnerPrincipalId ?? defaultGenerateOwnerPrincipalId;
|
|
39264
39166
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
39265
39167
|
const existing = readAuthFile(file);
|
|
@@ -39279,15 +39181,15 @@ function loadOrCreateAuthFile(opts) {
|
|
|
39279
39181
|
writeAuthFile(file, next);
|
|
39280
39182
|
return next;
|
|
39281
39183
|
}
|
|
39282
|
-
function
|
|
39283
|
-
return
|
|
39184
|
+
function defaultGenerateToken() {
|
|
39185
|
+
return import_node_crypto9.default.randomBytes(32).toString("base64url");
|
|
39284
39186
|
}
|
|
39285
39187
|
function defaultGenerateOwnerPrincipalId() {
|
|
39286
|
-
return `owner-${
|
|
39188
|
+
return `owner-${import_node_crypto9.default.randomUUID()}`;
|
|
39287
39189
|
}
|
|
39288
39190
|
function readAuthFile(file) {
|
|
39289
39191
|
try {
|
|
39290
|
-
const raw =
|
|
39192
|
+
const raw = import_node_fs24.default.readFileSync(file, "utf8");
|
|
39291
39193
|
const parsed = JSON.parse(raw);
|
|
39292
39194
|
if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
|
|
39293
39195
|
return null;
|
|
@@ -39305,25 +39207,25 @@ function readAuthFile(file) {
|
|
|
39305
39207
|
}
|
|
39306
39208
|
}
|
|
39307
39209
|
function writeAuthFile(file, content) {
|
|
39308
|
-
|
|
39309
|
-
|
|
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 });
|
|
39310
39212
|
try {
|
|
39311
|
-
|
|
39213
|
+
import_node_fs24.default.chmodSync(file, 384);
|
|
39312
39214
|
} catch {
|
|
39313
39215
|
}
|
|
39314
39216
|
}
|
|
39315
39217
|
|
|
39316
39218
|
// src/owner-profile.ts
|
|
39317
|
-
var
|
|
39219
|
+
var import_node_fs25 = __toESM(require("fs"), 1);
|
|
39318
39220
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
39319
|
-
var
|
|
39221
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
39320
39222
|
var PROFILE_FILENAME = "profile.json";
|
|
39321
39223
|
function loadOwnerDisplayName(dataDir) {
|
|
39322
39224
|
const fallback = import_node_os13.default.userInfo().username;
|
|
39323
|
-
const profilePath =
|
|
39225
|
+
const profilePath = import_node_path29.default.join(dataDir, PROFILE_FILENAME);
|
|
39324
39226
|
let raw;
|
|
39325
39227
|
try {
|
|
39326
|
-
raw =
|
|
39228
|
+
raw = import_node_fs25.default.readFileSync(profilePath, "utf8");
|
|
39327
39229
|
} catch {
|
|
39328
39230
|
return fallback;
|
|
39329
39231
|
}
|
|
@@ -39345,6 +39247,259 @@ function loadOwnerDisplayName(dataDir) {
|
|
|
39345
39247
|
return displayName;
|
|
39346
39248
|
}
|
|
39347
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
|
+
|
|
39348
39503
|
// src/index.ts
|
|
39349
39504
|
init_protocol();
|
|
39350
39505
|
|
|
@@ -39488,9 +39643,9 @@ function buildSessionHandlers(deps) {
|
|
|
39488
39643
|
if (ctx && ctx.principal.kind === "guest") {
|
|
39489
39644
|
const sessions = response.sessions ?? [];
|
|
39490
39645
|
const peerOwnerId = ctx.peerOwnerPrincipalId;
|
|
39491
|
-
const
|
|
39646
|
+
const guestId = ctx.principal.id;
|
|
39492
39647
|
const filtered = sessions.filter((s) => canAccessPersona(ctx.grants, s.ownerPersonaId, "read")).filter(
|
|
39493
|
-
(s) => peerOwnerId ? s.creatorOwnerPrincipalId === peerOwnerId : s.creatorPrincipalId ===
|
|
39648
|
+
(s) => peerOwnerId ? s.creatorOwnerPrincipalId === peerOwnerId : s.creatorPrincipalId === guestId
|
|
39494
39649
|
);
|
|
39495
39650
|
return { response: { type: "session:list", sessions: filtered } };
|
|
39496
39651
|
}
|
|
@@ -39505,54 +39660,7 @@ function buildSessionHandlers(deps) {
|
|
|
39505
39660
|
const update = async (frame, _client, ctx) => {
|
|
39506
39661
|
const args = SessionUpdateArgs.parse(frame);
|
|
39507
39662
|
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
39508
|
-
const prevFile = manager.findOwnedSession(args.sessionId);
|
|
39509
|
-
const prevProject = prevFile?.appBuilderProject ?? null;
|
|
39510
|
-
const nextProject = args.patch.appBuilderProject ?? prevProject;
|
|
39511
39663
|
const { response, broadcast } = manager.update(args);
|
|
39512
|
-
if (args.patch.appBuilderProject !== void 0) {
|
|
39513
|
-
deps.logger?.info("session-update.appBuilderProject", {
|
|
39514
|
-
sessionId: args.sessionId,
|
|
39515
|
-
prevProject,
|
|
39516
|
-
nextProject,
|
|
39517
|
-
hasLifecycle: !!deps.devServerLifecycle,
|
|
39518
|
-
hasStore: !!deps.appBuilderStore
|
|
39519
|
-
});
|
|
39520
|
-
}
|
|
39521
|
-
if (deps.devServerLifecycle && deps.appBuilderStore && args.patch.appBuilderProject !== void 0) {
|
|
39522
|
-
const lifecycle = deps.devServerLifecycle;
|
|
39523
|
-
const projectStore = deps.appBuilderStore;
|
|
39524
|
-
const tunnelUrl = deps.getTunnelUrl?.() ?? null;
|
|
39525
|
-
try {
|
|
39526
|
-
if (prevProject && prevProject !== nextProject) {
|
|
39527
|
-
await lifecycle.stopForSession(args.sessionId);
|
|
39528
|
-
}
|
|
39529
|
-
if (nextProject && nextProject !== "" && nextProject !== prevProject) {
|
|
39530
|
-
const projects = await projectStore.list();
|
|
39531
|
-
const target = projects.find((p2) => p2.name === nextProject);
|
|
39532
|
-
deps.logger?.info("session-update.start-dev-server", {
|
|
39533
|
-
sessionId: args.sessionId,
|
|
39534
|
-
projectName: nextProject,
|
|
39535
|
-
targetFound: !!target,
|
|
39536
|
-
projectCwd: target ? projectStore.projectDir(nextProject) : null,
|
|
39537
|
-
tunnelUrl
|
|
39538
|
-
});
|
|
39539
|
-
if (target) {
|
|
39540
|
-
lifecycle.startForSession({
|
|
39541
|
-
sessionId: args.sessionId,
|
|
39542
|
-
projectName: nextProject,
|
|
39543
|
-
cwd: projectStore.projectDir(nextProject),
|
|
39544
|
-
port: target.port,
|
|
39545
|
-
tunnelHost: tunnelUrl ? new URL(tunnelUrl.replace(/^wss?:\/\//i, "https://")).host : null
|
|
39546
|
-
});
|
|
39547
|
-
}
|
|
39548
|
-
}
|
|
39549
|
-
} catch (err) {
|
|
39550
|
-
deps.logger?.warn("session-update.lifecycle-failed", {
|
|
39551
|
-
sessionId: args.sessionId,
|
|
39552
|
-
error: err instanceof Error ? err.message : String(err)
|
|
39553
|
-
});
|
|
39554
|
-
}
|
|
39555
|
-
}
|
|
39556
39664
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
39557
39665
|
};
|
|
39558
39666
|
const del = async (frame, _client, ctx) => {
|
|
@@ -39899,7 +40007,7 @@ function buildHistoryHandlers(deps) {
|
|
|
39899
40007
|
const { response: file } = manager.get({ sessionId: args.sessionId });
|
|
39900
40008
|
const f = file;
|
|
39901
40009
|
if (ctx && ctx.principal.kind === "guest") {
|
|
39902
|
-
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;
|
|
39903
40011
|
if (!ok) {
|
|
39904
40012
|
throw new ClawdError(
|
|
39905
40013
|
ERROR_CODES.UNAUTHORIZED,
|
|
@@ -40181,7 +40289,7 @@ var DeleteArgsSchema = external_exports.object({
|
|
|
40181
40289
|
capabilityId: external_exports.string().min(1)
|
|
40182
40290
|
}).strict();
|
|
40183
40291
|
function buildCapabilityHandlers(deps) {
|
|
40184
|
-
const { manager
|
|
40292
|
+
const { manager } = deps;
|
|
40185
40293
|
const list = async () => {
|
|
40186
40294
|
return {
|
|
40187
40295
|
response: {
|
|
@@ -40207,27 +40315,15 @@ function buildCapabilityHandlers(deps) {
|
|
|
40207
40315
|
}
|
|
40208
40316
|
};
|
|
40209
40317
|
};
|
|
40210
|
-
const personalCapGet = async () => {
|
|
40211
|
-
const { token, capability } = getPersonalCapability();
|
|
40212
|
-
return {
|
|
40213
|
-
response: {
|
|
40214
|
-
type: "personal-cap:get:ok",
|
|
40215
|
-
token,
|
|
40216
|
-
capability: stripSecretHash(capability),
|
|
40217
|
-
shareBaseUrl: getShareBaseUrl()
|
|
40218
|
-
}
|
|
40219
|
-
};
|
|
40220
|
-
};
|
|
40221
40318
|
return {
|
|
40222
40319
|
"capability:list": list,
|
|
40223
|
-
"capability:delete": del
|
|
40224
|
-
"personal-cap:get": personalCapGet
|
|
40320
|
+
"capability:delete": del
|
|
40225
40321
|
};
|
|
40226
40322
|
}
|
|
40227
40323
|
|
|
40228
40324
|
// src/handlers/inbox.ts
|
|
40229
40325
|
init_protocol();
|
|
40230
|
-
function resolvePeerOwnerId(ctx, argsPeerOwnerId
|
|
40326
|
+
function resolvePeerOwnerId(ctx, argsPeerOwnerId) {
|
|
40231
40327
|
if (ctx.principal.kind === "owner") {
|
|
40232
40328
|
if (!argsPeerOwnerId) {
|
|
40233
40329
|
throw new ClawdError(
|
|
@@ -40237,18 +40333,11 @@ function resolvePeerOwnerId(ctx, argsPeerOwnerId, capManager) {
|
|
|
40237
40333
|
}
|
|
40238
40334
|
return argsPeerOwnerId;
|
|
40239
40335
|
}
|
|
40240
|
-
|
|
40241
|
-
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "inbox: guest ctx missing capabilityId");
|
|
40242
|
-
}
|
|
40243
|
-
let resolved = ctx.peerOwnerPrincipalId;
|
|
40244
|
-
if (!resolved) {
|
|
40245
|
-
const cap = capManager.findById(ctx.capabilityId);
|
|
40246
|
-
resolved = cap?.peerOwnerId;
|
|
40247
|
-
}
|
|
40336
|
+
const resolved = ctx.peerOwnerPrincipalId;
|
|
40248
40337
|
if (!resolved) {
|
|
40249
40338
|
throw new ClawdError(
|
|
40250
40339
|
ERROR_CODES.UNAUTHORIZED,
|
|
40251
|
-
`inbox: guest
|
|
40340
|
+
`inbox: guest ${ctx.principal.id} has no peerOwnerPrincipalId (auth frame missing selfPrincipalId)`
|
|
40252
40341
|
);
|
|
40253
40342
|
}
|
|
40254
40343
|
if (argsPeerOwnerId && argsPeerOwnerId !== resolved) {
|
|
@@ -40260,14 +40349,14 @@ function resolvePeerOwnerId(ctx, argsPeerOwnerId, capManager) {
|
|
|
40260
40349
|
return resolved;
|
|
40261
40350
|
}
|
|
40262
40351
|
function buildInboxHandlers(deps) {
|
|
40263
|
-
const { manager
|
|
40352
|
+
const { manager } = deps;
|
|
40264
40353
|
const postMessage = async (frame, _client, ctx) => {
|
|
40265
40354
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40266
40355
|
const args = InboxPostMessageArgsSchema.parse(rest);
|
|
40267
40356
|
if (!ctx) {
|
|
40268
40357
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:postMessage: missing ConnectionContext");
|
|
40269
40358
|
}
|
|
40270
|
-
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId
|
|
40359
|
+
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId);
|
|
40271
40360
|
const message = manager.postMessage({
|
|
40272
40361
|
peerOwnerId,
|
|
40273
40362
|
senderPrincipalId: ctx.principal.id,
|
|
@@ -40285,7 +40374,7 @@ function buildInboxHandlers(deps) {
|
|
|
40285
40374
|
if (!ctx) {
|
|
40286
40375
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:list: missing ConnectionContext");
|
|
40287
40376
|
}
|
|
40288
|
-
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId
|
|
40377
|
+
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId);
|
|
40289
40378
|
const messages = manager.list(peerOwnerId, args.sinceCreatedAt);
|
|
40290
40379
|
return {
|
|
40291
40380
|
response: { type: "inbox:list:ok", peerOwnerId, messages }
|
|
@@ -40297,7 +40386,7 @@ function buildInboxHandlers(deps) {
|
|
|
40297
40386
|
if (!ctx) {
|
|
40298
40387
|
throw new ClawdError(ERROR_CODES.INTERNAL, "inbox:markRead: missing ConnectionContext");
|
|
40299
40388
|
}
|
|
40300
|
-
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId
|
|
40389
|
+
const peerOwnerId = resolvePeerOwnerId(ctx, args.peerOwnerId);
|
|
40301
40390
|
const updated = manager.markRead({
|
|
40302
40391
|
peerOwnerId,
|
|
40303
40392
|
principalId: ctx.principal.id,
|
|
@@ -40319,116 +40408,45 @@ function buildInboxHandlers(deps) {
|
|
|
40319
40408
|
};
|
|
40320
40409
|
}
|
|
40321
40410
|
|
|
40322
|
-
// src/handlers/
|
|
40411
|
+
// src/handlers/contact.ts
|
|
40323
40412
|
init_protocol();
|
|
40324
40413
|
function ensureOwner(ctx) {
|
|
40325
40414
|
if (!ctx || ctx.principal.kind !== "owner") {
|
|
40326
40415
|
throw new ClawdError(
|
|
40327
40416
|
ERROR_CODES.UNAUTHORIZED,
|
|
40328
|
-
"UNAUTHORIZED:
|
|
40329
|
-
);
|
|
40330
|
-
}
|
|
40331
|
-
}
|
|
40332
|
-
function ensureGuestCtx(ctx) {
|
|
40333
|
-
if (!ctx || ctx.principal.kind !== "guest" || !ctx.capabilityId) {
|
|
40334
|
-
throw new ClawdError(
|
|
40335
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
40336
|
-
"UNAUTHORIZED: received-capability:autoAttach requires guest cap ctx"
|
|
40417
|
+
"UNAUTHORIZED: contact:* requires owner ctx"
|
|
40337
40418
|
);
|
|
40338
40419
|
}
|
|
40339
40420
|
}
|
|
40340
|
-
function
|
|
40341
|
-
const now = deps.now ?? Date.now;
|
|
40421
|
+
function buildContactHandlers(deps) {
|
|
40342
40422
|
const list = async (_frame, _client, ctx) => {
|
|
40343
40423
|
ensureOwner(ctx);
|
|
40344
40424
|
return {
|
|
40345
40425
|
response: {
|
|
40346
|
-
type: "
|
|
40347
|
-
|
|
40426
|
+
type: "contact:list:ok",
|
|
40427
|
+
contacts: deps.store.list()
|
|
40348
40428
|
}
|
|
40349
40429
|
};
|
|
40350
40430
|
};
|
|
40351
|
-
const add = async (frame, _client, ctx) => {
|
|
40352
|
-
ensureOwner(ctx);
|
|
40353
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40354
|
-
const args = ReceivedCapabilityAddArgsSchema.parse(rest);
|
|
40355
|
-
let conn;
|
|
40356
|
-
try {
|
|
40357
|
-
conn = await deps.connectRemote({
|
|
40358
|
-
url: args.remoteUrl,
|
|
40359
|
-
token: args.token,
|
|
40360
|
-
selfPrincipalId: deps.selfPrincipalId(),
|
|
40361
|
-
selfDisplayName: deps.selfDisplayName()
|
|
40362
|
-
});
|
|
40363
|
-
} catch (e) {
|
|
40364
|
-
throw new ClawdError(
|
|
40365
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
40366
|
-
`INVITE_REMOTE_UNREACHABLE: ${e.message}`
|
|
40367
|
-
);
|
|
40368
|
-
}
|
|
40369
|
-
try {
|
|
40370
|
-
const wh = await conn.whoami();
|
|
40371
|
-
const rc = {
|
|
40372
|
-
ownerPrincipalId: wh.ownerId,
|
|
40373
|
-
ownerDisplayName: wh.displayName,
|
|
40374
|
-
remoteUrl: args.remoteUrl,
|
|
40375
|
-
capabilityId: wh.capabilityId,
|
|
40376
|
-
capabilityToken: args.token,
|
|
40377
|
-
grants: wh.grants,
|
|
40378
|
-
receivedAt: now()
|
|
40379
|
-
};
|
|
40380
|
-
deps.store.upsert(rc);
|
|
40381
|
-
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
40382
|
-
return {
|
|
40383
|
-
response: { type: "received-capability:add:ok", receivedCap: rc }
|
|
40384
|
-
};
|
|
40385
|
-
} finally {
|
|
40386
|
-
try {
|
|
40387
|
-
conn.close();
|
|
40388
|
-
} catch {
|
|
40389
|
-
}
|
|
40390
|
-
}
|
|
40391
|
-
};
|
|
40392
40431
|
const remove = async (frame, _client, ctx) => {
|
|
40393
40432
|
ensureOwner(ctx);
|
|
40394
40433
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40395
|
-
const args =
|
|
40396
|
-
deps.store.remove(args.
|
|
40434
|
+
const args = ContactRemoveArgsSchema.parse(rest);
|
|
40435
|
+
deps.store.remove(args.principalId);
|
|
40397
40436
|
deps.broadcast({
|
|
40398
|
-
type: "
|
|
40399
|
-
|
|
40437
|
+
type: "contact:removed",
|
|
40438
|
+
principalId: args.principalId
|
|
40400
40439
|
});
|
|
40401
40440
|
return {
|
|
40402
40441
|
response: {
|
|
40403
|
-
type: "
|
|
40404
|
-
|
|
40442
|
+
type: "contact:remove:ok",
|
|
40443
|
+
principalId: args.principalId
|
|
40405
40444
|
}
|
|
40406
40445
|
};
|
|
40407
40446
|
};
|
|
40408
|
-
const autoAttach = async (frame, _client, ctx) => {
|
|
40409
|
-
ensureGuestCtx(ctx);
|
|
40410
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40411
|
-
const args = ReceivedCapabilityAutoAttachArgsSchema.parse(rest);
|
|
40412
|
-
const rc = {
|
|
40413
|
-
ownerPrincipalId: args.ownerPrincipalId,
|
|
40414
|
-
ownerDisplayName: args.ownerDisplayName,
|
|
40415
|
-
remoteUrl: args.remoteUrl,
|
|
40416
|
-
capabilityId: args.capabilityId,
|
|
40417
|
-
capabilityToken: args.token,
|
|
40418
|
-
grants: args.grants,
|
|
40419
|
-
receivedAt: now()
|
|
40420
|
-
};
|
|
40421
|
-
deps.store.upsert(rc);
|
|
40422
|
-
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
40423
|
-
return {
|
|
40424
|
-
response: { type: "received-capability:autoAttach:ok" }
|
|
40425
|
-
};
|
|
40426
|
-
};
|
|
40427
40447
|
return {
|
|
40428
|
-
"
|
|
40429
|
-
"
|
|
40430
|
-
"received-capability:remove": remove,
|
|
40431
|
-
"received-capability:autoAttach": autoAttach
|
|
40448
|
+
"contact:list": list,
|
|
40449
|
+
"contact:remove": remove
|
|
40432
40450
|
};
|
|
40433
40451
|
}
|
|
40434
40452
|
|
|
@@ -40450,14 +40468,13 @@ function buildWhoamiHandler(deps) {
|
|
|
40450
40468
|
usedCount: 0
|
|
40451
40469
|
};
|
|
40452
40470
|
} else {
|
|
40453
|
-
|
|
40454
|
-
|
|
40455
|
-
|
|
40456
|
-
|
|
40457
|
-
|
|
40458
|
-
|
|
40459
|
-
}
|
|
40460
|
-
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
|
+
};
|
|
40461
40478
|
}
|
|
40462
40479
|
const grantedPersonas = [];
|
|
40463
40480
|
const isGuest = ctx.principal.kind === "guest";
|
|
@@ -40488,6 +40505,116 @@ function buildWhoamiHandler(deps) {
|
|
|
40488
40505
|
};
|
|
40489
40506
|
}
|
|
40490
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
|
+
|
|
40491
40618
|
// src/handlers/meta.ts
|
|
40492
40619
|
var import_node_os15 = __toESM(require("os"), 1);
|
|
40493
40620
|
init_protocol();
|
|
@@ -41448,61 +41575,6 @@ function buildExtensionHandlers(deps) {
|
|
|
41448
41575
|
};
|
|
41449
41576
|
}
|
|
41450
41577
|
|
|
41451
|
-
// src/handlers/app-builder.ts
|
|
41452
|
-
function buildAppBuilderHandlers(deps) {
|
|
41453
|
-
const { store, deleteSessionsByProject, devServerLifecycle } = deps;
|
|
41454
|
-
const listProjects = async () => {
|
|
41455
|
-
const projects = await store.list();
|
|
41456
|
-
return {
|
|
41457
|
-
response: {
|
|
41458
|
-
projects: projects.map((p2) => ({
|
|
41459
|
-
...p2,
|
|
41460
|
-
isRunning: devServerLifecycle.isRunning(p2.name),
|
|
41461
|
-
boundSessionId: devServerLifecycle.boundSessionId(p2.name)
|
|
41462
|
-
}))
|
|
41463
|
-
}
|
|
41464
|
-
};
|
|
41465
|
-
};
|
|
41466
|
-
const createProject = async (frame) => {
|
|
41467
|
-
const name = frame.name;
|
|
41468
|
-
if (typeof name !== "string") throw new Error("createProject: name must be string");
|
|
41469
|
-
const project = await store.create(name);
|
|
41470
|
-
return { response: { project } };
|
|
41471
|
-
};
|
|
41472
|
-
const deleteProject = async (frame) => {
|
|
41473
|
-
const name = frame.name;
|
|
41474
|
-
if (typeof name !== "string") throw new Error("deleteProject: name must be string");
|
|
41475
|
-
await devServerLifecycle.stopForProject(name);
|
|
41476
|
-
await deleteSessionsByProject(name);
|
|
41477
|
-
await store.delete(name);
|
|
41478
|
-
return { response: {} };
|
|
41479
|
-
};
|
|
41480
|
-
const updateProjectPort = async (frame) => {
|
|
41481
|
-
const f = frame;
|
|
41482
|
-
if (typeof f.name !== "string") throw new Error("updateProjectPort: name must be string");
|
|
41483
|
-
if (typeof f.newPort !== "number") throw new Error("updateProjectPort: newPort must be number");
|
|
41484
|
-
const entry = devServerLifecycle.boundEntry(f.name);
|
|
41485
|
-
await devServerLifecycle.stopForProject(f.name);
|
|
41486
|
-
const project = await store.updatePort(f.name, f.newPort);
|
|
41487
|
-
if (entry) {
|
|
41488
|
-
await devServerLifecycle.restartForProjectAt({
|
|
41489
|
-
projectName: f.name,
|
|
41490
|
-
newPort: f.newPort,
|
|
41491
|
-
sessionId: entry.sessionId,
|
|
41492
|
-
cwd: entry.cwd,
|
|
41493
|
-
tunnelHost: entry.tunnelHost
|
|
41494
|
-
});
|
|
41495
|
-
}
|
|
41496
|
-
return { response: { project } };
|
|
41497
|
-
};
|
|
41498
|
-
return {
|
|
41499
|
-
"appBuilder:listProjects": listProjects,
|
|
41500
|
-
"appBuilder:createProject": createProject,
|
|
41501
|
-
"appBuilder:deleteProject": deleteProject,
|
|
41502
|
-
"appBuilder:updateProjectPort": updateProjectPort
|
|
41503
|
-
};
|
|
41504
|
-
}
|
|
41505
|
-
|
|
41506
41578
|
// src/extension/registry.ts
|
|
41507
41579
|
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
41508
41580
|
var import_node_path39 = __toESM(require("path"), 1);
|
|
@@ -41591,13 +41663,7 @@ async function uninstall(deps) {
|
|
|
41591
41663
|
// src/handlers/index.ts
|
|
41592
41664
|
function buildMethodHandlers(deps) {
|
|
41593
41665
|
return {
|
|
41594
|
-
...buildSessionHandlers(
|
|
41595
|
-
...deps,
|
|
41596
|
-
devServerLifecycle: deps.devServerLifecycle,
|
|
41597
|
-
appBuilderStore: deps.appBuilderStore,
|
|
41598
|
-
getTunnelUrl: deps.getTunnelUrl,
|
|
41599
|
-
logger: deps.logger
|
|
41600
|
-
}),
|
|
41666
|
+
...buildSessionHandlers(deps),
|
|
41601
41667
|
...buildPermissionHandlers(deps),
|
|
41602
41668
|
...buildHistoryHandlers(deps),
|
|
41603
41669
|
...buildWorkspaceHandlers(deps),
|
|
@@ -41609,20 +41675,14 @@ function buildMethodHandlers(deps) {
|
|
|
41609
41675
|
personaRegistry: deps.personaRegistry
|
|
41610
41676
|
}),
|
|
41611
41677
|
...buildCapabilityHandlers({
|
|
41612
|
-
manager: deps.capabilityManager
|
|
41613
|
-
getShareBaseUrl: deps.getShareBaseUrl,
|
|
41614
|
-
getPersonalCapability: deps.getPersonalCapability
|
|
41678
|
+
manager: deps.capabilityManager
|
|
41615
41679
|
}),
|
|
41616
41680
|
...buildInboxHandlers({
|
|
41617
|
-
manager: deps.inboxManager
|
|
41618
|
-
capManager: deps.capabilityManager
|
|
41681
|
+
manager: deps.inboxManager
|
|
41619
41682
|
}),
|
|
41620
|
-
...
|
|
41621
|
-
store: deps.
|
|
41622
|
-
|
|
41623
|
-
broadcast: deps.broadcastToOwners,
|
|
41624
|
-
selfPrincipalId: () => deps.ownerPrincipalId,
|
|
41625
|
-
selfDisplayName: () => deps.ownerDisplayName
|
|
41683
|
+
...buildContactHandlers({
|
|
41684
|
+
store: deps.contactStore,
|
|
41685
|
+
broadcast: deps.broadcastToOwners
|
|
41626
41686
|
}),
|
|
41627
41687
|
whoami: buildWhoamiHandler({
|
|
41628
41688
|
ownerDisplayName: deps.ownerDisplayName,
|
|
@@ -41630,6 +41690,8 @@ function buildMethodHandlers(deps) {
|
|
|
41630
41690
|
personaStore: deps.personaStore,
|
|
41631
41691
|
capabilityManager: deps.capabilityManager
|
|
41632
41692
|
}),
|
|
41693
|
+
...buildFeishuAuthHandlers(deps.feishuAuth),
|
|
41694
|
+
...buildHubHandlers(deps.feishuHub),
|
|
41633
41695
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {},
|
|
41634
41696
|
...buildExtensionHandlers({
|
|
41635
41697
|
loadAll,
|
|
@@ -41638,237 +41700,10 @@ function buildMethodHandlers(deps) {
|
|
|
41638
41700
|
root: extensionsRoot(),
|
|
41639
41701
|
publishedChannelStore: deps.publishedChannelStore,
|
|
41640
41702
|
bundleCache: deps.bundleCache
|
|
41641
|
-
}),
|
|
41642
|
-
...buildAppBuilderHandlers({
|
|
41643
|
-
store: deps.appBuilderStore,
|
|
41644
|
-
deleteSessionsByProject: deps.deleteSessionsByProject,
|
|
41645
|
-
devServerLifecycle: deps.devServerLifecycle
|
|
41646
41703
|
})
|
|
41647
41704
|
};
|
|
41648
41705
|
}
|
|
41649
41706
|
|
|
41650
|
-
// src/app-builder/project-store.ts
|
|
41651
|
-
var import_node_fs29 = require("fs");
|
|
41652
|
-
var import_node_path41 = require("path");
|
|
41653
|
-
init_protocol();
|
|
41654
|
-
var PROJECTS_DIR = "projects";
|
|
41655
|
-
var META_FILE = ".clawd-project.json";
|
|
41656
|
-
var ProjectStore = class {
|
|
41657
|
-
/** @param personaRoot persona 目录,例如 `~/.clawd/personas/persona-app-builder/` */
|
|
41658
|
-
constructor(personaRoot) {
|
|
41659
|
-
this.personaRoot = personaRoot;
|
|
41660
|
-
}
|
|
41661
|
-
personaRoot;
|
|
41662
|
-
/** projects/<name>/.clawd-project.json 路径 */
|
|
41663
|
-
metaPath(name) {
|
|
41664
|
-
return (0, import_node_path41.join)(this.projectsRoot(), name, META_FILE);
|
|
41665
|
-
}
|
|
41666
|
-
/** projects/<name>/ 目录路径(cwd 用) */
|
|
41667
|
-
projectDir(name) {
|
|
41668
|
-
return (0, import_node_path41.join)(this.projectsRoot(), name);
|
|
41669
|
-
}
|
|
41670
|
-
projectsRoot() {
|
|
41671
|
-
return (0, import_node_path41.join)(this.personaRoot, PROJECTS_DIR);
|
|
41672
|
-
}
|
|
41673
|
-
async list() {
|
|
41674
|
-
let entries;
|
|
41675
|
-
try {
|
|
41676
|
-
entries = await import_node_fs29.promises.readdir(this.projectsRoot());
|
|
41677
|
-
} catch (err) {
|
|
41678
|
-
if (err.code === "ENOENT") return [];
|
|
41679
|
-
throw err;
|
|
41680
|
-
}
|
|
41681
|
-
const out = [];
|
|
41682
|
-
for (const name of entries) {
|
|
41683
|
-
try {
|
|
41684
|
-
const raw = await import_node_fs29.promises.readFile(this.metaPath(name), "utf8");
|
|
41685
|
-
const parsed = ProjectMetadataSchema.safeParse(JSON.parse(raw));
|
|
41686
|
-
if (parsed.success) out.push(parsed.data);
|
|
41687
|
-
} catch {
|
|
41688
|
-
}
|
|
41689
|
-
}
|
|
41690
|
-
return out.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
41691
|
-
}
|
|
41692
|
-
async create(name) {
|
|
41693
|
-
const existing = await this.list();
|
|
41694
|
-
if (existing.some((p2) => p2.name === name)) {
|
|
41695
|
-
throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
|
|
41696
|
-
}
|
|
41697
|
-
const usedPorts = new Set(existing.map((p2) => p2.port));
|
|
41698
|
-
let port = -1;
|
|
41699
|
-
for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) {
|
|
41700
|
-
if (!usedPorts.has(p2)) {
|
|
41701
|
-
port = p2;
|
|
41702
|
-
break;
|
|
41703
|
-
}
|
|
41704
|
-
}
|
|
41705
|
-
if (port < 0) {
|
|
41706
|
-
throw new Error(
|
|
41707
|
-
`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`
|
|
41708
|
-
);
|
|
41709
|
-
}
|
|
41710
|
-
const meta = {
|
|
41711
|
-
name,
|
|
41712
|
-
port,
|
|
41713
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
41714
|
-
};
|
|
41715
|
-
const validated = ProjectMetadataSchema.safeParse(meta);
|
|
41716
|
-
if (!validated.success) {
|
|
41717
|
-
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
41718
|
-
}
|
|
41719
|
-
const dir = this.projectDir(name);
|
|
41720
|
-
await import_node_fs29.promises.mkdir(dir, { recursive: true });
|
|
41721
|
-
await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
|
|
41722
|
-
return meta;
|
|
41723
|
-
}
|
|
41724
|
-
async delete(name) {
|
|
41725
|
-
const dir = this.projectDir(name);
|
|
41726
|
-
await import_node_fs29.promises.rm(dir, { recursive: true, force: true });
|
|
41727
|
-
}
|
|
41728
|
-
async updatePort(name, newPort) {
|
|
41729
|
-
if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
|
|
41730
|
-
throw new Error(
|
|
41731
|
-
`port range invalid: must be ${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}, got ${newPort}`
|
|
41732
|
-
);
|
|
41733
|
-
}
|
|
41734
|
-
const list = await this.list();
|
|
41735
|
-
const target = list.find((p2) => p2.name === name);
|
|
41736
|
-
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
41737
|
-
const conflict = list.find((p2) => p2.name !== name && p2.port === newPort);
|
|
41738
|
-
if (conflict) {
|
|
41739
|
-
throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
|
|
41740
|
-
}
|
|
41741
|
-
const updated = { ...target, port: newPort };
|
|
41742
|
-
await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
41743
|
-
return updated;
|
|
41744
|
-
}
|
|
41745
|
-
};
|
|
41746
|
-
|
|
41747
|
-
// src/app-builder/dev-server-supervisor.ts
|
|
41748
|
-
var import_node_child_process7 = require("child_process");
|
|
41749
|
-
var import_node_events2 = require("events");
|
|
41750
|
-
var DevServerSupervisor = class extends import_node_events2.EventEmitter {
|
|
41751
|
-
running = /* @__PURE__ */ new Map();
|
|
41752
|
-
// key: sessionId
|
|
41753
|
-
logger = { warn: () => {
|
|
41754
|
-
}, info: () => {
|
|
41755
|
-
} };
|
|
41756
|
-
/** daemon entry 启动后注入 logger,让 child 输出走 clawd.log */
|
|
41757
|
-
setLogger(logger) {
|
|
41758
|
-
this.logger = logger;
|
|
41759
|
-
}
|
|
41760
|
-
startForSession(args) {
|
|
41761
|
-
if (this.running.has(args.sessionId)) return;
|
|
41762
|
-
const augmentedPath = [
|
|
41763
|
-
process.env.PATH ?? "",
|
|
41764
|
-
"/opt/homebrew/bin",
|
|
41765
|
-
"/usr/local/bin",
|
|
41766
|
-
`${process.env.HOME}/.nvm/versions/node`,
|
|
41767
|
-
`${process.env.HOME}/.local/share/pnpm`
|
|
41768
|
-
].filter(Boolean).join(":");
|
|
41769
|
-
const env = {
|
|
41770
|
-
...process.env,
|
|
41771
|
-
PATH: augmentedPath,
|
|
41772
|
-
CLAWD_TUNNEL_HOST: args.tunnelHost ?? "",
|
|
41773
|
-
CLAWD_PREVIEW_PORT: String(args.port)
|
|
41774
|
-
};
|
|
41775
|
-
this.logger.info("dev-server.start", {
|
|
41776
|
-
projectName: args.projectName,
|
|
41777
|
-
port: args.port,
|
|
41778
|
-
cwd: args.cwd,
|
|
41779
|
-
sessionId: args.sessionId,
|
|
41780
|
-
tunnelHost: args.tunnelHost
|
|
41781
|
-
});
|
|
41782
|
-
const child = (0, import_node_child_process7.spawn)("pnpm", ["dev"], { cwd: args.cwd, env, stdio: "pipe", shell: true });
|
|
41783
|
-
const entry = { ...args, process: child };
|
|
41784
|
-
this.running.set(args.sessionId, entry);
|
|
41785
|
-
child.stdout?.on("data", (chunk) => {
|
|
41786
|
-
this.logger.info("dev-server.stdout", {
|
|
41787
|
-
projectName: args.projectName,
|
|
41788
|
-
port: args.port,
|
|
41789
|
-
chunk: chunk.toString().trimEnd()
|
|
41790
|
-
});
|
|
41791
|
-
});
|
|
41792
|
-
child.stderr?.on("data", (chunk) => {
|
|
41793
|
-
this.logger.warn("dev-server.stderr", {
|
|
41794
|
-
projectName: args.projectName,
|
|
41795
|
-
port: args.port,
|
|
41796
|
-
chunk: chunk.toString().trimEnd()
|
|
41797
|
-
});
|
|
41798
|
-
});
|
|
41799
|
-
child.on("error", (err) => {
|
|
41800
|
-
this.running.delete(args.sessionId);
|
|
41801
|
-
this.logger.warn("dev-server.spawn-failed", {
|
|
41802
|
-
projectName: args.projectName,
|
|
41803
|
-
port: args.port,
|
|
41804
|
-
error: err.message,
|
|
41805
|
-
code: err.code
|
|
41806
|
-
});
|
|
41807
|
-
this.emit("devServer:failed", {
|
|
41808
|
-
sessionId: args.sessionId,
|
|
41809
|
-
projectName: args.projectName,
|
|
41810
|
-
port: args.port,
|
|
41811
|
-
exitCode: null
|
|
41812
|
-
});
|
|
41813
|
-
});
|
|
41814
|
-
child.on("exit", (code) => {
|
|
41815
|
-
this.running.delete(args.sessionId);
|
|
41816
|
-
this.logger.info("dev-server.exit", { projectName: args.projectName, port: args.port, code });
|
|
41817
|
-
if (code !== 0 && code !== null) {
|
|
41818
|
-
this.emit("devServer:failed", {
|
|
41819
|
-
sessionId: args.sessionId,
|
|
41820
|
-
projectName: args.projectName,
|
|
41821
|
-
port: args.port,
|
|
41822
|
-
exitCode: code
|
|
41823
|
-
});
|
|
41824
|
-
}
|
|
41825
|
-
});
|
|
41826
|
-
}
|
|
41827
|
-
stopForSession(sessionId) {
|
|
41828
|
-
const entry = this.running.get(sessionId);
|
|
41829
|
-
if (!entry) return Promise.resolve();
|
|
41830
|
-
return new Promise((resolve6) => {
|
|
41831
|
-
entry.process.once("exit", () => {
|
|
41832
|
-
this.running.delete(sessionId);
|
|
41833
|
-
resolve6();
|
|
41834
|
-
});
|
|
41835
|
-
entry.process.kill("SIGTERM");
|
|
41836
|
-
});
|
|
41837
|
-
}
|
|
41838
|
-
async stopForProject(projectName) {
|
|
41839
|
-
const entry = [...this.running.values()].find((e) => e.projectName === projectName);
|
|
41840
|
-
if (!entry) return;
|
|
41841
|
-
await this.stopForSession(entry.sessionId);
|
|
41842
|
-
}
|
|
41843
|
-
async restartForProjectAt(args) {
|
|
41844
|
-
await this.stopForSession(args.sessionId);
|
|
41845
|
-
this.startForSession({
|
|
41846
|
-
sessionId: args.sessionId,
|
|
41847
|
-
projectName: args.projectName,
|
|
41848
|
-
port: args.newPort,
|
|
41849
|
-
cwd: args.cwd,
|
|
41850
|
-
tunnelHost: args.tunnelHost
|
|
41851
|
-
});
|
|
41852
|
-
}
|
|
41853
|
-
isRunning(projectName) {
|
|
41854
|
-
return [...this.running.values()].some((e) => e.projectName === projectName);
|
|
41855
|
-
}
|
|
41856
|
-
boundSessionId(projectName) {
|
|
41857
|
-
const entry = [...this.running.values()].find((e) => e.projectName === projectName);
|
|
41858
|
-
return entry?.sessionId ?? null;
|
|
41859
|
-
}
|
|
41860
|
-
boundEntry(projectName) {
|
|
41861
|
-
const entry = [...this.running.values()].find((e) => e.projectName === projectName);
|
|
41862
|
-
if (!entry) return null;
|
|
41863
|
-
return {
|
|
41864
|
-
sessionId: entry.sessionId,
|
|
41865
|
-
cwd: entry.cwd,
|
|
41866
|
-
port: entry.port,
|
|
41867
|
-
tunnelHost: entry.tunnelHost
|
|
41868
|
-
};
|
|
41869
|
-
}
|
|
41870
|
-
};
|
|
41871
|
-
|
|
41872
41707
|
// src/handlers/method-grants.ts
|
|
41873
41708
|
var ADMIN_ANY = {
|
|
41874
41709
|
kind: "fixed",
|
|
@@ -41885,23 +41720,22 @@ var METHOD_GRANT_MAP = {
|
|
|
41885
41720
|
// "查 capabilityManager 拿 caller 的 guest capability"。
|
|
41886
41721
|
"whoami": { kind: "public" },
|
|
41887
41722
|
// ---- capability platform(admin-only) ----
|
|
41888
|
-
//
|
|
41889
|
-
// 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。
|
|
41890
41726
|
"capability:list": ADMIN_ANY,
|
|
41891
41727
|
"capability:delete": ADMIN_ANY,
|
|
41892
|
-
|
|
41893
|
-
//
|
|
41894
|
-
//
|
|
41895
|
-
// guest ctx.capabilityId === args.capabilityId, 防 guest 越权操作别的 channel.
|
|
41728
|
+
// ---- inbox: 双投 P2P IM ----
|
|
41729
|
+
// 三条都 'public' — 能连上 = 已通过 introspect 验票. handler 内额外校验
|
|
41730
|
+
// guest peerOwnerId 与 ctx.peerOwnerPrincipalId 一致, 防 guest 越权操作别的 DM 桶.
|
|
41896
41731
|
"inbox:list": { kind: "public" },
|
|
41897
41732
|
"inbox:markRead": { kind: "public" },
|
|
41898
41733
|
"inbox:postMessage": { kind: "public" },
|
|
41899
|
-
// ----
|
|
41900
|
-
//
|
|
41901
|
-
|
|
41902
|
-
"
|
|
41903
|
-
"
|
|
41904
|
-
"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,
|
|
41905
41739
|
// ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
|
|
41906
41740
|
// dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
|
|
41907
41741
|
// owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
|
|
@@ -41942,7 +41776,7 @@ var METHOD_GRANT_MAP = {
|
|
|
41942
41776
|
"history:list": CAPABILITY_SCOPED,
|
|
41943
41777
|
// history:read 取一条 session 的 CC JSONL 历史。guest 进 persona VM 后必须能读自己
|
|
41944
41778
|
// 创建的 session 历史(chat view 加载历史消息走这条),所以下沉到 capability-scoped;
|
|
41945
|
-
// handler 内按 ctx.
|
|
41779
|
+
// handler 内按 ctx.principal.id === file.creatorPrincipalId 拦越权。
|
|
41946
41780
|
"history:read": CAPABILITY_SCOPED,
|
|
41947
41781
|
"history:subagents": CAPABILITY_SCOPED,
|
|
41948
41782
|
"history:subagent-read": CAPABILITY_SCOPED,
|
|
@@ -41999,14 +41833,15 @@ var METHOD_GRANT_MAP = {
|
|
|
41999
41833
|
// guest-self:guest 在自己 daemon 上触发安装与更新(无持久化 subscription 概念)
|
|
42000
41834
|
"extension.install-from-channel": ADMIN_ANY,
|
|
42001
41835
|
"extension.update-from-channel": ADMIN_ANY,
|
|
42002
|
-
// ----
|
|
42003
|
-
//
|
|
42004
|
-
|
|
42005
|
-
|
|
42006
|
-
"
|
|
42007
|
-
|
|
42008
|
-
|
|
42009
|
-
"
|
|
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
|
|
42010
41845
|
};
|
|
42011
41846
|
function computeGrantForFrame(method, frame) {
|
|
42012
41847
|
const rule = METHOD_GRANT_MAP[method];
|
|
@@ -42022,12 +41857,12 @@ function computeGrantForFrame(method, frame) {
|
|
|
42022
41857
|
}
|
|
42023
41858
|
|
|
42024
41859
|
// src/extension/runtime.ts
|
|
42025
|
-
var
|
|
42026
|
-
var
|
|
41860
|
+
var import_node_child_process7 = require("child_process");
|
|
41861
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
42027
41862
|
var import_promises10 = require("timers/promises");
|
|
42028
41863
|
|
|
42029
41864
|
// src/extension/port-allocator.ts
|
|
42030
|
-
var
|
|
41865
|
+
var import_node_net = __toESM(require("net"), 1);
|
|
42031
41866
|
var PortExhaustedError = class extends Error {
|
|
42032
41867
|
constructor(min, max) {
|
|
42033
41868
|
super(`no free port in [${min},${max}]`);
|
|
@@ -42040,7 +41875,7 @@ var PortExhaustedError = class extends Error {
|
|
|
42040
41875
|
};
|
|
42041
41876
|
function probe(port) {
|
|
42042
41877
|
return new Promise((resolve6) => {
|
|
42043
|
-
const srv =
|
|
41878
|
+
const srv = import_node_net.default.createServer();
|
|
42044
41879
|
srv.once("error", () => resolve6(false));
|
|
42045
41880
|
srv.once("listening", () => {
|
|
42046
41881
|
srv.close(() => resolve6(true));
|
|
@@ -42124,13 +41959,13 @@ var Runtime = class {
|
|
|
42124
41959
|
/\$CLAWOS_EXT_PORT/g,
|
|
42125
41960
|
String(port)
|
|
42126
41961
|
);
|
|
42127
|
-
const dir =
|
|
41962
|
+
const dir = import_node_path41.default.join(this.root, extId);
|
|
42128
41963
|
const env = {
|
|
42129
41964
|
...process.env,
|
|
42130
41965
|
CLAWOS_EXT_PORT: String(port),
|
|
42131
41966
|
CLAWOS_EXT_ID: extId
|
|
42132
41967
|
};
|
|
42133
|
-
const child = (0,
|
|
41968
|
+
const child = (0, import_node_child_process7.spawn)("sh", ["-c", cmd], {
|
|
42134
41969
|
cwd: dir,
|
|
42135
41970
|
env,
|
|
42136
41971
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -42236,7 +42071,7 @@ ${handle.stderrTail}`
|
|
|
42236
42071
|
|
|
42237
42072
|
// src/extension/published-channels.ts
|
|
42238
42073
|
var import_promises11 = __toESM(require("fs/promises"), 1);
|
|
42239
|
-
var
|
|
42074
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
42240
42075
|
init_zod();
|
|
42241
42076
|
var PublishedChannelsError = class extends Error {
|
|
42242
42077
|
constructor(code, message) {
|
|
@@ -42335,7 +42170,7 @@ var PublishedChannelStore = class {
|
|
|
42335
42170
|
)
|
|
42336
42171
|
};
|
|
42337
42172
|
const tmp = `${this.filePath}.tmp`;
|
|
42338
|
-
await import_promises11.default.mkdir(
|
|
42173
|
+
await import_promises11.default.mkdir(import_node_path42.default.dirname(this.filePath), { recursive: true });
|
|
42339
42174
|
await import_promises11.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
42340
42175
|
await import_promises11.default.rename(tmp, this.filePath);
|
|
42341
42176
|
}
|
|
@@ -42343,7 +42178,7 @@ var PublishedChannelStore = class {
|
|
|
42343
42178
|
|
|
42344
42179
|
// src/extension/bundle-cache.ts
|
|
42345
42180
|
var import_promises12 = __toESM(require("fs/promises"), 1);
|
|
42346
|
-
var
|
|
42181
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
42347
42182
|
var BundleCache = class {
|
|
42348
42183
|
constructor(rootDir) {
|
|
42349
42184
|
this.rootDir = rootDir;
|
|
@@ -42352,14 +42187,14 @@ var BundleCache = class {
|
|
|
42352
42187
|
/** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
|
|
42353
42188
|
async write(snapshotHash, buffer) {
|
|
42354
42189
|
await import_promises12.default.mkdir(this.rootDir, { recursive: true });
|
|
42355
|
-
const file =
|
|
42190
|
+
const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
42356
42191
|
const tmp = `${file}.tmp`;
|
|
42357
42192
|
await import_promises12.default.writeFile(tmp, buffer, { mode: 384 });
|
|
42358
42193
|
await import_promises12.default.rename(tmp, file);
|
|
42359
42194
|
}
|
|
42360
42195
|
/** Returns the bundle bytes, or null when the file doesn't exist. */
|
|
42361
42196
|
async read(snapshotHash) {
|
|
42362
|
-
const file =
|
|
42197
|
+
const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
42363
42198
|
try {
|
|
42364
42199
|
return await import_promises12.default.readFile(file);
|
|
42365
42200
|
} catch (e) {
|
|
@@ -42369,7 +42204,7 @@ var BundleCache = class {
|
|
|
42369
42204
|
}
|
|
42370
42205
|
/** Idempotent — missing file is not an error. */
|
|
42371
42206
|
async delete(snapshotHash) {
|
|
42372
|
-
const file =
|
|
42207
|
+
const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
42373
42208
|
await import_promises12.default.rm(file, { force: true });
|
|
42374
42209
|
}
|
|
42375
42210
|
};
|
|
@@ -42378,7 +42213,7 @@ var BundleCache = class {
|
|
|
42378
42213
|
async function startDaemon(config) {
|
|
42379
42214
|
const logger = createLogger({
|
|
42380
42215
|
level: config.logLevel,
|
|
42381
|
-
file:
|
|
42216
|
+
file: import_node_path44.default.join(config.dataDir, "clawd.log")
|
|
42382
42217
|
});
|
|
42383
42218
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
42384
42219
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -42396,19 +42231,20 @@ async function startDaemon(config) {
|
|
|
42396
42231
|
} else if (config.tunnel) {
|
|
42397
42232
|
resolvedAuthToken = authFile.token;
|
|
42398
42233
|
}
|
|
42399
|
-
const
|
|
42400
|
-
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);
|
|
42401
42239
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
42402
42240
|
const inboxStore = new InboxStore(config.dataDir);
|
|
42403
42241
|
const inboxManager = new InboxManager(inboxStore, (_peerOwnerId, frame) => {
|
|
42404
42242
|
wsServer?.broadcastToOwners(frame);
|
|
42405
42243
|
});
|
|
42406
|
-
const
|
|
42407
|
-
|
|
42244
|
+
const contactStore = new ContactStore(config.dataDir);
|
|
42245
|
+
contactStore.load();
|
|
42408
42246
|
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
42409
42247
|
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
42410
|
-
const personalCapability = loadOrCreatePersonalCapability({ dataDir: config.dataDir });
|
|
42411
|
-
capabilityRegistry.upsertCapability(personalCapability.capability);
|
|
42412
42248
|
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
42413
42249
|
onDeleted: (cap) => {
|
|
42414
42250
|
const deletedAt = Date.now();
|
|
@@ -42417,7 +42253,7 @@ async function startDaemon(config) {
|
|
|
42417
42253
|
capabilityId: cap.id,
|
|
42418
42254
|
deletedAt
|
|
42419
42255
|
});
|
|
42420
|
-
wsServer?.
|
|
42256
|
+
wsServer?.closeConnectionsByGuestId(cap.id);
|
|
42421
42257
|
const cleanup = cleanupGuestSessionsForCapability(cap, sessionStoreFactory);
|
|
42422
42258
|
if (cleanup.removed.length > 0) {
|
|
42423
42259
|
logger.info("capability delete cascade: guest sessions removed", {
|
|
@@ -42434,15 +42270,36 @@ async function startDaemon(config) {
|
|
|
42434
42270
|
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
42435
42271
|
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
42436
42272
|
expectedToken: resolvedAuthToken,
|
|
42437
|
-
authenticate: (t,
|
|
42438
|
-
|
|
42273
|
+
authenticate: async (t, _selfPrincipalId, _selfDisplayName, selfUrl) => {
|
|
42274
|
+
const result = await authenticate(t, {
|
|
42439
42275
|
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
42440
42276
|
ownerPrincipalId,
|
|
42441
42277
|
ownerDisplayName,
|
|
42442
|
-
|
|
42443
|
-
|
|
42444
|
-
|
|
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
|
+
} : {}
|
|
42445
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;
|
|
42446
42303
|
},
|
|
42447
42304
|
onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
|
|
42448
42305
|
buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
|
|
@@ -42465,7 +42322,7 @@ async function startDaemon(config) {
|
|
|
42465
42322
|
const agents = new AgentsScanner();
|
|
42466
42323
|
const history = new ClaudeHistoryReader();
|
|
42467
42324
|
let transport = null;
|
|
42468
|
-
const personaStore = new PersonaStore(
|
|
42325
|
+
const personaStore = new PersonaStore(import_node_path44.default.join(config.dataDir, "personas"));
|
|
42469
42326
|
const defaultsRoot = findDefaultsRoot();
|
|
42470
42327
|
if (defaultsRoot) {
|
|
42471
42328
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -42482,7 +42339,7 @@ async function startDaemon(config) {
|
|
|
42482
42339
|
getAdapter,
|
|
42483
42340
|
historyReader: history,
|
|
42484
42341
|
dataDir: config.dataDir,
|
|
42485
|
-
personaRoot:
|
|
42342
|
+
personaRoot: import_node_path44.default.join(config.dataDir, "personas"),
|
|
42486
42343
|
personaStore,
|
|
42487
42344
|
ownerDisplayName,
|
|
42488
42345
|
ownerPrincipalId,
|
|
@@ -42506,10 +42363,10 @@ async function startDaemon(config) {
|
|
|
42506
42363
|
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
42507
42364
|
attachmentGroup: {
|
|
42508
42365
|
onFileEdit: (input) => {
|
|
42509
|
-
const absPath =
|
|
42366
|
+
const absPath = import_node_path44.default.isAbsolute(input.relPath) ? input.relPath : import_node_path44.default.join(input.cwd, input.relPath);
|
|
42510
42367
|
let size = 0;
|
|
42511
42368
|
try {
|
|
42512
|
-
size =
|
|
42369
|
+
size = import_node_fs29.default.statSync(absPath).size;
|
|
42513
42370
|
} catch (err) {
|
|
42514
42371
|
logger.warn("attachment.onFileEdit stat failed", {
|
|
42515
42372
|
sessionId: input.sessionId,
|
|
@@ -42592,23 +42449,33 @@ async function startDaemon(config) {
|
|
|
42592
42449
|
}
|
|
42593
42450
|
return `http://${config.host}:${config.port}`;
|
|
42594
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
|
+
};
|
|
42595
42468
|
const extensionRuntime = new Runtime({ root: extensionsRoot() });
|
|
42596
42469
|
const publishedChannelStore = new PublishedChannelStore(publishedChannelsFile());
|
|
42597
42470
|
await publishedChannelStore.load();
|
|
42598
42471
|
const bundleCache = new BundleCache(bundleCacheRoot());
|
|
42599
|
-
const
|
|
42600
|
-
|
|
42601
|
-
|
|
42602
|
-
|
|
42603
|
-
|
|
42604
|
-
|
|
42605
|
-
|
|
42606
|
-
const effectivePreviewPorts = Array.from(
|
|
42607
|
-
/* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
|
|
42608
|
-
);
|
|
42609
|
-
const deleteSessionsByProject = async (_name) => {
|
|
42610
|
-
};
|
|
42611
|
-
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({
|
|
42612
42479
|
manager,
|
|
42613
42480
|
workspace,
|
|
42614
42481
|
skills,
|
|
@@ -42647,17 +42514,12 @@ async function startDaemon(config) {
|
|
|
42647
42514
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
42648
42515
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
42649
42516
|
// guest path guard:candidate 必须在 personaRoot 子树下
|
|
42650
|
-
personaRoot:
|
|
42517
|
+
personaRoot: import_node_path44.default.join(config.dataDir, "personas")
|
|
42651
42518
|
},
|
|
42652
42519
|
// workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
|
|
42653
|
-
personaRoot:
|
|
42520
|
+
personaRoot: import_node_path44.default.join(config.dataDir, "personas"),
|
|
42654
42521
|
// capability:list / delete handler 依赖
|
|
42655
42522
|
capabilityManager,
|
|
42656
|
-
// personal-cap:get 返回的 shareBaseUrl 用此 base URL:
|
|
42657
|
-
// tunnel 拉起后切到反代 wss://... 地址;否则本机 ws://host:port。
|
|
42658
|
-
getShareBaseUrl: () => currentTunnelUrl ?? `ws://${config.host}:${config.port}`,
|
|
42659
|
-
// global personal token (2026-05-25): personal-cap:get handler 直接返回此 token + cap
|
|
42660
|
-
getPersonalCapability: () => personalCapability,
|
|
42661
42523
|
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
42662
42524
|
ownerDisplayName,
|
|
42663
42525
|
// owner-id stabilization: whoami 用稳定 ownerPrincipalId 替代 'owner' 字面量
|
|
@@ -42669,24 +42531,55 @@ async function startDaemon(config) {
|
|
|
42669
42531
|
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
42670
42532
|
inboxManager,
|
|
42671
42533
|
inboxStore,
|
|
42672
|
-
//
|
|
42673
|
-
|
|
42674
|
-
//
|
|
42534
|
+
// 联系人列表 store(hub:connect / 自动反向落同一 store)
|
|
42535
|
+
contactStore,
|
|
42536
|
+
// contact:removed broadcast;复用 capability:tokenIssued 同款通路
|
|
42675
42537
|
broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
|
|
42676
|
-
// received-capability:add 临时 ws 连远端跑 whoami — 直连 ws (不走 transport listener)
|
|
42677
|
-
connectRemote,
|
|
42678
42538
|
// extension runtime (v1: local-mode only). Instance owned by daemon top
|
|
42679
42539
|
// level so stopAll() runs in the shutdown hook below.
|
|
42680
42540
|
extensionRuntime,
|
|
42681
42541
|
// extension sharing v1 — owner-side published-channels store.
|
|
42682
42542
|
publishedChannelStore,
|
|
42683
42543
|
bundleCache,
|
|
42684
|
-
//
|
|
42685
|
-
|
|
42686
|
-
|
|
42687
|
-
|
|
42688
|
-
|
|
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
|
+
}
|
|
42689
42581
|
});
|
|
42582
|
+
let handlers = makeHandlers();
|
|
42690
42583
|
const authResolver = new AuthContextResolver({
|
|
42691
42584
|
ownerToken: resolvedAuthToken
|
|
42692
42585
|
});
|
|
@@ -42712,15 +42605,34 @@ async function startDaemon(config) {
|
|
|
42712
42605
|
},
|
|
42713
42606
|
// POST /extensions/import lands in ~/.clawd/extensions/<id>/
|
|
42714
42607
|
extensionsRoot: () => extensionsRoot(),
|
|
42715
|
-
//
|
|
42716
|
-
|
|
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
|
+
}
|
|
42717
42631
|
});
|
|
42718
42632
|
wsServer = new LocalWsServer({
|
|
42719
42633
|
host: config.host,
|
|
42720
42634
|
port: config.port,
|
|
42721
42635
|
logger,
|
|
42722
|
-
// broadcastToPrincipal 用此 id 路由 owner-targeted 帧(替代字面量 'owner' sentinel)
|
|
42723
|
-
ownerPrincipalId,
|
|
42724
42636
|
readyFrameBuilder: (ctx) => buildReadyFrame(
|
|
42725
42637
|
{
|
|
42726
42638
|
manager,
|
|
@@ -42746,14 +42658,11 @@ async function startDaemon(config) {
|
|
|
42746
42658
|
return {
|
|
42747
42659
|
principal: { id: v2.capability.id, kind: "guest", displayName: v2.capability.displayName },
|
|
42748
42660
|
grants: v2.capability.grants,
|
|
42749
|
-
capabilityId: v2.capability.id,
|
|
42750
42661
|
...selfPrincipalId ? { peerOwnerPrincipalId: selfPrincipalId } : {}
|
|
42751
42662
|
};
|
|
42752
42663
|
},
|
|
42753
42664
|
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
42754
42665
|
httpRequestHandler: httpRouter,
|
|
42755
|
-
// app-builder build 模式:/preview/<port>/ HMR websocket upgrade 转发的端口白名单(同 http-router 配置)
|
|
42756
|
-
previewPorts: effectivePreviewPorts,
|
|
42757
42666
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
42758
42667
|
// daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
|
|
42759
42668
|
// 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
|
|
@@ -42798,6 +42707,12 @@ async function startDaemon(config) {
|
|
|
42798
42707
|
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
42799
42708
|
const handler = handlers[type];
|
|
42800
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
|
+
}
|
|
42801
42716
|
const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
|
|
42802
42717
|
const verdict = computeGrantForFrame(type, frame);
|
|
42803
42718
|
if (verdict.kind === "check") {
|
|
@@ -42870,13 +42785,12 @@ async function startDaemon(config) {
|
|
|
42870
42785
|
stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url, tunnelError: void 0 };
|
|
42871
42786
|
stateMgr.write(stateSnapshot);
|
|
42872
42787
|
currentTunnelUrl = r.url;
|
|
42873
|
-
wss.broadcastAll({ type: "tunnel:ready", url: r.url, subdomain: r.subdomain });
|
|
42874
42788
|
const connectUrl = resolvedAuthToken ? `${r.url}#token=${resolvedAuthToken}` : r.url;
|
|
42875
42789
|
const lines = [
|
|
42876
42790
|
`Tunnel: ${r.url}`,
|
|
42877
42791
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
42878
|
-
`Frpc config: ${
|
|
42879
|
-
`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")}`
|
|
42880
42794
|
];
|
|
42881
42795
|
const width = Math.max(...lines.map((l) => l.length));
|
|
42882
42796
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -42889,10 +42803,11 @@ ${bar}
|
|
|
42889
42803
|
|
|
42890
42804
|
`);
|
|
42891
42805
|
try {
|
|
42892
|
-
const connectPath =
|
|
42893
|
-
|
|
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 });
|
|
42894
42808
|
} catch {
|
|
42895
42809
|
}
|
|
42810
|
+
void reportHubUrl();
|
|
42896
42811
|
} catch (err) {
|
|
42897
42812
|
const tunnelError = err?.message ?? String(err);
|
|
42898
42813
|
try {
|
|
@@ -42956,9 +42871,9 @@ ${bar}
|
|
|
42956
42871
|
};
|
|
42957
42872
|
}
|
|
42958
42873
|
function migrateDropPersonsDir(dataDir) {
|
|
42959
|
-
const dir =
|
|
42874
|
+
const dir = import_node_path44.default.join(dataDir, "persons");
|
|
42960
42875
|
try {
|
|
42961
|
-
|
|
42876
|
+
import_node_fs29.default.rmSync(dir, { recursive: true, force: true });
|
|
42962
42877
|
} catch {
|
|
42963
42878
|
}
|
|
42964
42879
|
}
|