@clawos-dev/clawd 0.2.112-beta.217.80b79a2 → 0.2.112-beta.220.a78f1c0
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 +1125 -1070
- package/dist/persona-defaults/persona-app-builder/CLAUDE.md +168 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/README.md +96 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/config.env +20 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/contract/bootstrap +22 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/contract/s.yaml.tmpl +54 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/.env.example +3 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/.fcignore +7 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/nest-cli.json +8 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/package-lock.json +4531 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/package.json +28 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +10 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/main.ts +49 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.controller.ts +27 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.module.ts +9 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.service.ts +38 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/polyfill.ts +8 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/tsconfig.json +14 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/index.html +12 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/package-lock.json +1680 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/package.json +18 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/src/App.jsx +161 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/src/main.jsx +5 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/vite.config.js +30 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/new-extension.sh +49 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/publish.sh +78 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/remove-extension.sh +57 -0
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/verify.sh +20 -0
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -132,11 +132,15 @@ var init_methods = __esm({
|
|
|
132
132
|
"attachment.groupRemove",
|
|
133
133
|
"attachment.groupList",
|
|
134
134
|
// ---- capability:* (capability platform 鉴权底座) ----
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
135
|
+
// global personal token 模型 (2026-05-25): UI 不再调 capability:issue —— daemon 启动时
|
|
136
|
+
// loadOrCreatePersonalCapability 自动创建 1 张 fixed-grants 的 personal capability,
|
|
137
|
+
// 所有联系人共用 personal token。capability:list / capability:delete 保留 (供调试 /
|
|
138
|
+
// 老板手动清旧 cap);capability:issue / capability:bindPeer 已下线。
|
|
138
139
|
"capability:list",
|
|
139
140
|
"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",
|
|
140
144
|
// ---- inbox:* (capability platform Phase 3 跨用户通知 + Phase 4 DM) ----
|
|
141
145
|
// owner 接 guest 的 cross-principal 消息事件 + DM 双向私聊.
|
|
142
146
|
// inbox:list / markRead: admin-only (Phase 3); inbox:postMessage: DM 自带能力,
|
|
@@ -144,11 +148,14 @@ var init_methods = __esm({
|
|
|
144
148
|
"inbox:list",
|
|
145
149
|
"inbox:markRead",
|
|
146
150
|
"inbox:postMessage",
|
|
147
|
-
// ---- received-capability:* (
|
|
148
|
-
//
|
|
149
|
-
//
|
|
151
|
+
// ---- received-capability:* (对方颁给我的 cap, 视角 B 双向授权模型, 2026-05-23) ----
|
|
152
|
+
// owner 入口: list / add / remove
|
|
153
|
+
// guest 端: autoAttach (B 接 A 的 token 后通过 cap channel ws 推 cap 给 A)
|
|
154
|
+
// 详见 received-capability.ts JSDoc + spec 2026-05-23-bidirectional-cap-design.md
|
|
150
155
|
"received-capability:list",
|
|
156
|
+
"received-capability:add",
|
|
151
157
|
"received-capability:remove",
|
|
158
|
+
"received-capability:autoAttach",
|
|
152
159
|
// ---- whoami (v2 capability platform) ----
|
|
153
160
|
// 任何 authed connection 都能调;返回 owner 显示名 + 当前 capability wire 形态 +
|
|
154
161
|
// grants 对应的 persona 元数据 (id + displayName)。UI 端 AddRemotePersonaDialog
|
|
@@ -174,17 +181,16 @@ var init_methods = __esm({
|
|
|
174
181
|
// 无 subscription 持久化:guest 端身份完全来自文件系统 + reconciled publishedExtensions.
|
|
175
182
|
"extension.install-from-channel",
|
|
176
183
|
"extension.update-from-channel",
|
|
177
|
-
// ----
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
"
|
|
187
|
-
"hub:connect"
|
|
184
|
+
// ---- app-builder Project picker (spec 2026-06-01-app-builder-project-picker-design) ----
|
|
185
|
+
// persona-app-builder 多 project 管理;schemas: ProjectMetadataSchema + 4 个 args/result 对。
|
|
186
|
+
// listProjects: readdir projects/,按目录读 .clawd-project.json
|
|
187
|
+
// createProject: 校验名 + 从 6173-6182 段分配端口 + 写元数据 + 创建空目录
|
|
188
|
+
// deleteProject: 停 dev server + rm -rf 目录 + 连带删该 project 名下所有 session
|
|
189
|
+
// updateProjectPort: 校验新端口段内 + 未被别的 project 占 + 重启 dev server
|
|
190
|
+
"appBuilder:listProjects",
|
|
191
|
+
"appBuilder:createProject",
|
|
192
|
+
"appBuilder:deleteProject",
|
|
193
|
+
"appBuilder:updateProjectPort"
|
|
188
194
|
];
|
|
189
195
|
}
|
|
190
196
|
});
|
|
@@ -246,11 +252,7 @@ var init_errors = __esm({
|
|
|
246
252
|
INTERNAL: "INTERNAL",
|
|
247
253
|
UNAUTHORIZED: "UNAUTHORIZED",
|
|
248
254
|
FORBIDDEN: "FORBIDDEN",
|
|
249
|
-
INVALID_PARAM: "INVALID_PARAM"
|
|
250
|
-
// 飞书统一身份(spec 2026-06-01 决策 #9):People/远程体系强制飞书登录。
|
|
251
|
-
// daemon 进程身份未激活为飞书 unionId 时,FEISHU_GATED_METHODS 内的 RPC 一律返回此错误;
|
|
252
|
-
// UI 据此显示登录引导(区别于 UNAUTHORIZED 的越权语义)。
|
|
253
|
-
FEISHU_LOGIN_REQUIRED: "FEISHU_LOGIN_REQUIRED"
|
|
255
|
+
INVALID_PARAM: "INVALID_PARAM"
|
|
254
256
|
};
|
|
255
257
|
ClawdError = class extends Error {
|
|
256
258
|
constructor(code, message) {
|
|
@@ -4530,7 +4532,7 @@ var init_persona_schemas = __esm({
|
|
|
4530
4532
|
});
|
|
4531
4533
|
|
|
4532
4534
|
// ../protocol/src/schemas.ts
|
|
4533
|
-
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;
|
|
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, TunnelReadyEventSchema, TunnelExitedEventSchema, TunnelUnavailableEventSchema, InfoRunningSessionSchema, InfoResponseSchema, PROJECT_PORT_MIN, PROJECT_PORT_MAX, projectNameRegex, DEFAULT_DEV_COMMAND, ProjectMetadataSchema, ProjectWithStatusSchema, ListProjectsArgsSchema, ListProjectsResultSchema, CreateProjectArgsSchema, CreateProjectResultSchema, DeleteProjectArgsSchema, DeleteProjectResultSchema, UpdateProjectPortArgsSchema, UpdateProjectPortResultSchema;
|
|
4534
4536
|
var init_schemas = __esm({
|
|
4535
4537
|
"../protocol/src/schemas.ts"() {
|
|
4536
4538
|
"use strict";
|
|
@@ -4627,6 +4629,10 @@ var init_schemas = __esm({
|
|
|
4627
4629
|
// owner-mode persona session 身份标识;UI 用它在 SessionList 过滤 + jump,
|
|
4628
4630
|
// daemon 用它做 idempotent dedupe + spawn 时派生 ctx.personaMode='owner'
|
|
4629
4631
|
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(),
|
|
4630
4636
|
// listener-mode sub-session 的原始 chatId(owner-mode 不写;listener-scope 强制写入)。
|
|
4631
4637
|
// 派生 sessionId 是单向 hash `${personaId}-${tokenHash12}-${chatHash8}`,必须保留原始 chatId
|
|
4632
4638
|
// 才能让 alice 重连同一 sub-session(持久化在 alice localStorage 之外,由 daemon push 同步)。
|
|
@@ -4899,7 +4905,12 @@ var init_schemas = __esm({
|
|
|
4899
4905
|
forkedFromSessionId: external_exports.string().min(1).optional(),
|
|
4900
4906
|
// owner-mode persona session:传此字段时 daemon 自行派生 cwd 和启动参数,
|
|
4901
4907
|
// 且按 ownerPersonaId 做 idempotent dedupe(每 persona 永远只有一个 owner session)
|
|
4902
|
-
ownerPersonaId: external_exports.string().min(1).optional()
|
|
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
4914
|
}).refine((args) => args.cwd != null || args.ownerPersonaId != null, {
|
|
4904
4915
|
message: "cwd \u4E0E ownerPersonaId \u81F3\u5C11\u4F20\u4E00\u4E2A"
|
|
4905
4916
|
});
|
|
@@ -4913,7 +4924,11 @@ var init_schemas = __esm({
|
|
|
4913
4924
|
effort: external_exports.string().optional(),
|
|
4914
4925
|
cwd: external_exports.string().optional(),
|
|
4915
4926
|
// 用户在 Edit modal 选择的 icon 标识;UI 端做 enum 兜底
|
|
4916
|
-
iconKey: external_exports.string().optional()
|
|
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
4932
|
})
|
|
4918
4933
|
});
|
|
4919
4934
|
SessionSendArgs = external_exports.object({
|
|
@@ -5110,12 +5125,6 @@ var init_schemas = __esm({
|
|
|
5110
5125
|
* 后调 capabilityManager.recordPeerHello(capId, ownerPrincipalId, displayName)
|
|
5111
5126
|
* 把 cap.peerOwnerId / firstUsedByPeerAt 字段回写,让 owner 端 UI 在 People
|
|
5112
5127
|
* tab 顶层 PeerOwner 视图 + Personas tab 二级分组拿到对端身份。
|
|
5113
|
-
*
|
|
5114
|
-
* 飞书统一身份 Phase 1 (2026-06-01):取值升级为飞书 union_id(owner 已飞书登录时;
|
|
5115
|
-
* 来自 whoami.owner.id → daemon ownerPrincipalId → owner-identity.json unionId)。
|
|
5116
|
-
* 未登录 daemon 仍为 auth.json 的 owner-<uuid>。daemon 端不验证该身份真实性
|
|
5117
|
-
* (Phase 1 信任模型,见 spec §9)。Phase 2 将以 connect token 替代(introspect
|
|
5118
|
-
* 返回身份,删除自报字段,见 spec §10)。
|
|
5119
5128
|
*/
|
|
5120
5129
|
selfPrincipalId: external_exports.string().min(1).optional(),
|
|
5121
5130
|
/**
|
|
@@ -5123,23 +5132,18 @@ var init_schemas = __esm({
|
|
|
5123
5132
|
* 显示名写入 cap.peerOwnerDisplayName(cap.displayName 本来就是 owner 颁时填
|
|
5124
5133
|
* 的"颁给谁/用途",hello 收到的 displayName 是辅助)。
|
|
5125
5134
|
*/
|
|
5126
|
-
selfDisplayName: external_exports.string().optional()
|
|
5127
|
-
/**
|
|
5128
|
-
* 飞书统一身份 Phase 2(spec 决策 #11,自动反向建立联系人):guest 自报自己可达的
|
|
5129
|
-
* ws 地址(tunnel 地址优先)。hub introspect 验票成功后,若 guest 带 selfUrl,hub 自动
|
|
5130
|
-
* 把 guest 落入本机 ReceivedCapabilityStore(remoteUrl = selfUrl)+ 广播 received-capability:added,
|
|
5131
|
-
* 让 hub 的 People 立即出现该 guest——双向互为联系人,身份由 introspect 背书不可伪造。
|
|
5132
|
-
*
|
|
5133
|
-
* 缺省(A 无公网地址)→ hub 反向连不上 A,**不**自动建 cap;双向 IM 仅在 A 在线连接
|
|
5134
|
-
* 期间通过该连接收发(spec 决策 #11 限制)。不填假地址(不造假数据)。
|
|
5135
|
-
*/
|
|
5136
|
-
selfUrl: external_exports.string().min(1).optional()
|
|
5135
|
+
selfDisplayName: external_exports.string().optional()
|
|
5137
5136
|
});
|
|
5138
5137
|
AuthOkFrameSchema = external_exports.object({
|
|
5139
5138
|
type: external_exports.literal("auth:ok"),
|
|
5140
5139
|
version: external_exports.string().min(1).optional(),
|
|
5141
5140
|
protocolVersion: external_exports.number().int().nonnegative().optional()
|
|
5142
5141
|
});
|
|
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
|
+
});
|
|
5143
5147
|
TunnelExitedEventSchema = external_exports.object({
|
|
5144
5148
|
type: external_exports.literal("tunnel:exited"),
|
|
5145
5149
|
code: external_exports.number().int().nullable(),
|
|
@@ -5181,6 +5185,44 @@ var init_schemas = __esm({
|
|
|
5181
5185
|
/** file-sharing HTTP 路由的 Authorization: Bearer token;与 WS token 解耦,HTTP 401 仅触发 ReAuth 不强踢 WS(spec §11 第 4 条) */
|
|
5182
5186
|
httpToken: external_exports.string().optional()
|
|
5183
5187
|
});
|
|
5188
|
+
PROJECT_PORT_MIN = 6173;
|
|
5189
|
+
PROJECT_PORT_MAX = 6182;
|
|
5190
|
+
projectNameRegex = /^[a-z][a-z0-9-]{0,39}$/;
|
|
5191
|
+
DEFAULT_DEV_COMMAND = "cd server && pnpm dev";
|
|
5192
|
+
ProjectMetadataSchema = external_exports.object({
|
|
5193
|
+
name: external_exports.string().regex(projectNameRegex, {
|
|
5194
|
+
message: "kebab-case, \u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u53EA\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD / \u6570\u5B57 / -\uFF0C\u6700\u957F 40 \u5B57\u7B26"
|
|
5195
|
+
}),
|
|
5196
|
+
port: external_exports.number().int().min(PROJECT_PORT_MIN).max(PROJECT_PORT_MAX),
|
|
5197
|
+
createdAt: external_exports.string().datetime(),
|
|
5198
|
+
/** dev server 启动命令(shell 字符串);占位符 $CLAWD_PREVIEW_PORT 替换 + env 注入 */
|
|
5199
|
+
devCommand: external_exports.string().min(1)
|
|
5200
|
+
});
|
|
5201
|
+
ProjectWithStatusSchema = ProjectMetadataSchema.extend({
|
|
5202
|
+
isRunning: external_exports.boolean(),
|
|
5203
|
+
boundSessionId: external_exports.string().nullable()
|
|
5204
|
+
});
|
|
5205
|
+
ListProjectsArgsSchema = external_exports.object({}).strict();
|
|
5206
|
+
ListProjectsResultSchema = external_exports.object({
|
|
5207
|
+
projects: external_exports.array(ProjectWithStatusSchema)
|
|
5208
|
+
}).strict();
|
|
5209
|
+
CreateProjectArgsSchema = external_exports.object({
|
|
5210
|
+
name: external_exports.string()
|
|
5211
|
+
}).strict();
|
|
5212
|
+
CreateProjectResultSchema = external_exports.object({
|
|
5213
|
+
project: ProjectMetadataSchema
|
|
5214
|
+
}).strict();
|
|
5215
|
+
DeleteProjectArgsSchema = external_exports.object({
|
|
5216
|
+
name: external_exports.string()
|
|
5217
|
+
}).strict();
|
|
5218
|
+
DeleteProjectResultSchema = external_exports.object({}).strict();
|
|
5219
|
+
UpdateProjectPortArgsSchema = external_exports.object({
|
|
5220
|
+
name: external_exports.string(),
|
|
5221
|
+
newPort: external_exports.number().int()
|
|
5222
|
+
}).strict();
|
|
5223
|
+
UpdateProjectPortResultSchema = external_exports.object({
|
|
5224
|
+
project: ProjectMetadataSchema
|
|
5225
|
+
}).strict();
|
|
5184
5226
|
}
|
|
5185
5227
|
});
|
|
5186
5228
|
|
|
@@ -5223,7 +5265,7 @@ function stripSecretHash(cap) {
|
|
|
5223
5265
|
const { secretHash: _hash, ...wire } = cap;
|
|
5224
5266
|
return wire;
|
|
5225
5267
|
}
|
|
5226
|
-
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS;
|
|
5268
|
+
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS, PersonalCapGetResponseSchema;
|
|
5227
5269
|
var init_capability = __esm({
|
|
5228
5270
|
"../protocol/src/capability.ts"() {
|
|
5229
5271
|
"use strict";
|
|
@@ -5291,6 +5333,13 @@ var init_capability = __esm({
|
|
|
5291
5333
|
actions: Object.freeze(["read", "send"])
|
|
5292
5334
|
})
|
|
5293
5335
|
]);
|
|
5336
|
+
PersonalCapGetResponseSchema = external_exports.object({
|
|
5337
|
+
type: external_exports.literal("personal-cap:get:ok"),
|
|
5338
|
+
token: external_exports.string().min(1),
|
|
5339
|
+
capability: CapabilityWireSchema,
|
|
5340
|
+
/** ws/wss base URL,UI deriveInviteBase 用来拼 inviteUrl(tunnel 优先 / 否则本机 ws) */
|
|
5341
|
+
shareBaseUrl: external_exports.string().min(1)
|
|
5342
|
+
}).strict();
|
|
5294
5343
|
}
|
|
5295
5344
|
});
|
|
5296
5345
|
|
|
@@ -5326,7 +5375,7 @@ var init_inbox = __esm({
|
|
|
5326
5375
|
});
|
|
5327
5376
|
|
|
5328
5377
|
// ../protocol/src/received-capability.ts
|
|
5329
|
-
var ReceivedCapabilitySchema, ReceivedCapabilityWireSchema, ReceivedCapabilityRemoveArgsSchema, ReceivedCapabilityRemoveOkSchema, ReceivedCapabilityListOkSchema, ReceivedCapabilityAddedFrameSchema, ReceivedCapabilityRemovedFrameSchema;
|
|
5378
|
+
var ReceivedCapabilitySchema, ReceivedCapabilityWireSchema, ReceivedCapabilityAddArgsSchema, ReceivedCapabilityAddOkSchema, ReceivedCapabilityRemoveArgsSchema, ReceivedCapabilityRemoveOkSchema, ReceivedCapabilityListOkSchema, ReceivedCapabilityAutoAttachArgsSchema, ReceivedCapabilityAutoAttachOkSchema, ReceivedCapabilityAddedFrameSchema, ReceivedCapabilityRemovedFrameSchema;
|
|
5330
5379
|
var init_received_capability = __esm({
|
|
5331
5380
|
"../protocol/src/received-capability.ts"() {
|
|
5332
5381
|
"use strict";
|
|
@@ -5337,15 +5386,19 @@ var init_received_capability = __esm({
|
|
|
5337
5386
|
ownerDisplayName: external_exports.string(),
|
|
5338
5387
|
remoteUrl: external_exports.string().min(1),
|
|
5339
5388
|
capabilityId: external_exports.string().min(1),
|
|
5340
|
-
|
|
5341
|
-
// received cap,但 hub 不持有 A 颁发的 token(A 从未给 hub 换票)→ connectToken 为空串。
|
|
5342
|
-
// hub:connect 出站路径仍存中心 server 签发的真实 connect token(断线重连复用)。
|
|
5343
|
-
// 不用 .min(1):自动反向路径合法地存空串,禁止填假 token 占位(不造假数据)。
|
|
5344
|
-
connectToken: external_exports.string(),
|
|
5389
|
+
capabilityToken: external_exports.string().min(1),
|
|
5345
5390
|
grants: external_exports.array(GrantSchema),
|
|
5346
5391
|
receivedAt: external_exports.number().int().nonnegative()
|
|
5347
5392
|
}).strict();
|
|
5348
5393
|
ReceivedCapabilityWireSchema = ReceivedCapabilitySchema;
|
|
5394
|
+
ReceivedCapabilityAddArgsSchema = external_exports.object({
|
|
5395
|
+
remoteUrl: external_exports.string().min(1),
|
|
5396
|
+
token: external_exports.string().min(1)
|
|
5397
|
+
}).strict();
|
|
5398
|
+
ReceivedCapabilityAddOkSchema = external_exports.object({
|
|
5399
|
+
type: external_exports.literal("received-capability:add:ok"),
|
|
5400
|
+
receivedCap: ReceivedCapabilityWireSchema
|
|
5401
|
+
}).strict();
|
|
5349
5402
|
ReceivedCapabilityRemoveArgsSchema = external_exports.object({
|
|
5350
5403
|
ownerPrincipalId: external_exports.string().min(1)
|
|
5351
5404
|
}).strict();
|
|
@@ -5357,6 +5410,17 @@ var init_received_capability = __esm({
|
|
|
5357
5410
|
type: external_exports.literal("received-capability:list:ok"),
|
|
5358
5411
|
receivedCaps: external_exports.array(ReceivedCapabilityWireSchema)
|
|
5359
5412
|
}).strict();
|
|
5413
|
+
ReceivedCapabilityAutoAttachArgsSchema = external_exports.object({
|
|
5414
|
+
ownerPrincipalId: external_exports.string().min(1),
|
|
5415
|
+
ownerDisplayName: external_exports.string(),
|
|
5416
|
+
remoteUrl: external_exports.string().min(1),
|
|
5417
|
+
capabilityId: external_exports.string().min(1),
|
|
5418
|
+
token: external_exports.string().min(1),
|
|
5419
|
+
grants: external_exports.array(GrantSchema)
|
|
5420
|
+
}).strict();
|
|
5421
|
+
ReceivedCapabilityAutoAttachOkSchema = external_exports.object({
|
|
5422
|
+
type: external_exports.literal("received-capability:autoAttach:ok")
|
|
5423
|
+
}).strict();
|
|
5360
5424
|
ReceivedCapabilityAddedFrameSchema = external_exports.object({
|
|
5361
5425
|
type: external_exports.literal("received-capability:added"),
|
|
5362
5426
|
receivedCap: ReceivedCapabilityWireSchema
|
|
@@ -5567,94 +5631,6 @@ var init_extension = __esm({
|
|
|
5567
5631
|
}
|
|
5568
5632
|
});
|
|
5569
5633
|
|
|
5570
|
-
// ../protocol/src/feishu-auth.ts
|
|
5571
|
-
var FEISHU_GATED_METHODS, HubEntrySchema, HubListResponseSchema, HubConnectArgsSchema, HubConnectResponseSchema, FeishuIdentitySchema, AuthLoginStartResponseSchema, AuthGetIdentityResponseSchema, AuthLogoutResponseSchema, AuthLoginDoneEventSchema, AuthLoginFailedEventSchema;
|
|
5572
|
-
var init_feishu_auth = __esm({
|
|
5573
|
-
"../protocol/src/feishu-auth.ts"() {
|
|
5574
|
-
"use strict";
|
|
5575
|
-
init_zod();
|
|
5576
|
-
FEISHU_GATED_METHODS = [
|
|
5577
|
-
// capability(调试 / 撤销,仍属远程身份体系)
|
|
5578
|
-
"capability:list",
|
|
5579
|
-
"capability:delete",
|
|
5580
|
-
// 联系人列表(hub:connect 落同一 store;list 读 / remove 删)
|
|
5581
|
-
"received-capability:list",
|
|
5582
|
-
"received-capability:remove",
|
|
5583
|
-
// DM / 跨用户通知
|
|
5584
|
-
"inbox:list",
|
|
5585
|
-
"inbox:markRead",
|
|
5586
|
-
"inbox:postMessage",
|
|
5587
|
-
// mirror peer sessions(远端会话镜像)
|
|
5588
|
-
"peerSession:upsert",
|
|
5589
|
-
"peerSession:remove",
|
|
5590
|
-
// Phase 2: hub 目录 + 连接(中心 server 代理,需要 owner 的 ttcJwt)
|
|
5591
|
-
"hub:list",
|
|
5592
|
-
"hub:connect"
|
|
5593
|
-
];
|
|
5594
|
-
HubEntrySchema = external_exports.object({
|
|
5595
|
-
/** hub owner 的飞书 union_id */
|
|
5596
|
-
hubId: external_exports.string().min(1),
|
|
5597
|
-
/** 显示名 */
|
|
5598
|
-
name: external_exports.string().min(1),
|
|
5599
|
-
/** ws/tunnel 地址 */
|
|
5600
|
-
url: external_exports.string().min(1)
|
|
5601
|
-
});
|
|
5602
|
-
HubListResponseSchema = external_exports.object({
|
|
5603
|
-
type: external_exports.literal("hub:list:ok"),
|
|
5604
|
-
hubs: external_exports.array(HubEntrySchema)
|
|
5605
|
-
});
|
|
5606
|
-
HubConnectArgsSchema = external_exports.object({
|
|
5607
|
-
/** 目标 hub 的 hubId(= hub owner unionId) */
|
|
5608
|
-
hubId: external_exports.string().min(1),
|
|
5609
|
-
/**
|
|
5610
|
-
* 目标 hub 的 ws/tunnel 地址(决策 #13 统一注册表后改为可选):
|
|
5611
|
-
* - 公开目录点击:带 url(hub:list 已下发,省一次 server 查询)
|
|
5612
|
-
* - 私有 hub 仅凭 hubId 连接:缺省 url → daemon exchange 时由中心 server 下发该 hub 上报的 url
|
|
5613
|
-
*/
|
|
5614
|
-
url: external_exports.string().min(1).optional(),
|
|
5615
|
-
/** 显示名(公共 hub 来自目录;私有 hub 缺省,连上后用 whoami 回填) */
|
|
5616
|
-
name: external_exports.string().optional()
|
|
5617
|
-
});
|
|
5618
|
-
HubConnectResponseSchema = external_exports.object({
|
|
5619
|
-
type: external_exports.literal("hub:connect:ok"),
|
|
5620
|
-
hubId: external_exports.string(),
|
|
5621
|
-
name: external_exports.string(),
|
|
5622
|
-
url: external_exports.string()
|
|
5623
|
-
});
|
|
5624
|
-
FeishuIdentitySchema = external_exports.object({
|
|
5625
|
-
/** 飞书 union_id(租户维度,跨应用稳定)。TTC user_info 接口 data.union_id */
|
|
5626
|
-
unionId: external_exports.string().min(1),
|
|
5627
|
-
/** 飞书姓名。TTC user_info 接口 data.name */
|
|
5628
|
-
displayName: external_exports.string().min(1),
|
|
5629
|
-
/** TTC user_info 接口 data.avatar_url,可缺省 */
|
|
5630
|
-
avatarUrl: external_exports.string().optional()
|
|
5631
|
-
});
|
|
5632
|
-
AuthLoginStartResponseSchema = external_exports.object({
|
|
5633
|
-
type: external_exports.literal("auth:login:start:ok"),
|
|
5634
|
-
/** 完整 TTC 授权页 URL(含 callback_url + state + auto=1),UI 直接 window.open */
|
|
5635
|
-
authUrl: external_exports.string().min(1),
|
|
5636
|
-
/** 防 CSRF 的一次性 nonce;回调时 daemon 校验 */
|
|
5637
|
-
state: external_exports.string().min(1)
|
|
5638
|
-
});
|
|
5639
|
-
AuthGetIdentityResponseSchema = external_exports.object({
|
|
5640
|
-
type: external_exports.literal("auth:getIdentity:ok"),
|
|
5641
|
-
/** null = 未登录 */
|
|
5642
|
-
identity: FeishuIdentitySchema.nullable()
|
|
5643
|
-
});
|
|
5644
|
-
AuthLogoutResponseSchema = external_exports.object({
|
|
5645
|
-
type: external_exports.literal("auth:logout:ok")
|
|
5646
|
-
});
|
|
5647
|
-
AuthLoginDoneEventSchema = external_exports.object({
|
|
5648
|
-
type: external_exports.literal("auth:login:done"),
|
|
5649
|
-
identity: FeishuIdentitySchema
|
|
5650
|
-
});
|
|
5651
|
-
AuthLoginFailedEventSchema = external_exports.object({
|
|
5652
|
-
type: external_exports.literal("auth:login:failed"),
|
|
5653
|
-
reason: external_exports.string()
|
|
5654
|
-
});
|
|
5655
|
-
}
|
|
5656
|
-
});
|
|
5657
|
-
|
|
5658
5634
|
// ../protocol/src/runtime.ts
|
|
5659
5635
|
var init_runtime = __esm({
|
|
5660
5636
|
"../protocol/src/runtime.ts"() {
|
|
@@ -5672,7 +5648,6 @@ var init_runtime = __esm({
|
|
|
5672
5648
|
init_inbox();
|
|
5673
5649
|
init_received_capability();
|
|
5674
5650
|
init_extension();
|
|
5675
|
-
init_feishu_auth();
|
|
5676
5651
|
}
|
|
5677
5652
|
});
|
|
5678
5653
|
|
|
@@ -6859,8 +6834,8 @@ var require_atomic_sleep = __commonJS({
|
|
|
6859
6834
|
var require_sonic_boom = __commonJS({
|
|
6860
6835
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
6861
6836
|
"use strict";
|
|
6862
|
-
var
|
|
6863
|
-
var
|
|
6837
|
+
var fs48 = require("fs");
|
|
6838
|
+
var EventEmitter3 = require("events");
|
|
6864
6839
|
var inherits = require("util").inherits;
|
|
6865
6840
|
var path59 = require("path");
|
|
6866
6841
|
var sleep = require_atomic_sleep();
|
|
@@ -6916,20 +6891,20 @@ var require_sonic_boom = __commonJS({
|
|
|
6916
6891
|
const mode = sonic.mode;
|
|
6917
6892
|
if (sonic.sync) {
|
|
6918
6893
|
try {
|
|
6919
|
-
if (sonic.mkdir)
|
|
6920
|
-
const fd =
|
|
6894
|
+
if (sonic.mkdir) fs48.mkdirSync(path59.dirname(file), { recursive: true });
|
|
6895
|
+
const fd = fs48.openSync(file, flags, mode);
|
|
6921
6896
|
fileOpened(null, fd);
|
|
6922
6897
|
} catch (err) {
|
|
6923
6898
|
fileOpened(err);
|
|
6924
6899
|
throw err;
|
|
6925
6900
|
}
|
|
6926
6901
|
} else if (sonic.mkdir) {
|
|
6927
|
-
|
|
6902
|
+
fs48.mkdir(path59.dirname(file), { recursive: true }, (err) => {
|
|
6928
6903
|
if (err) return fileOpened(err);
|
|
6929
|
-
|
|
6904
|
+
fs48.open(file, flags, mode, fileOpened);
|
|
6930
6905
|
});
|
|
6931
6906
|
} else {
|
|
6932
|
-
|
|
6907
|
+
fs48.open(file, flags, mode, fileOpened);
|
|
6933
6908
|
}
|
|
6934
6909
|
}
|
|
6935
6910
|
function SonicBoom(opts) {
|
|
@@ -6970,8 +6945,8 @@ var require_sonic_boom = __commonJS({
|
|
|
6970
6945
|
this.flush = flushBuffer;
|
|
6971
6946
|
this.flushSync = flushBufferSync;
|
|
6972
6947
|
this._actualWrite = actualWriteBuffer;
|
|
6973
|
-
fsWriteSync = () =>
|
|
6974
|
-
fsWrite = () =>
|
|
6948
|
+
fsWriteSync = () => fs48.writeSync(this.fd, this._writingBuf);
|
|
6949
|
+
fsWrite = () => fs48.write(this.fd, this._writingBuf, this.release);
|
|
6975
6950
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
6976
6951
|
this._writingBuf = "";
|
|
6977
6952
|
this.write = write;
|
|
@@ -6980,15 +6955,15 @@ var require_sonic_boom = __commonJS({
|
|
|
6980
6955
|
this._actualWrite = actualWrite;
|
|
6981
6956
|
fsWriteSync = () => {
|
|
6982
6957
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6983
|
-
return
|
|
6958
|
+
return fs48.writeSync(this.fd, this._writingBuf);
|
|
6984
6959
|
}
|
|
6985
|
-
return
|
|
6960
|
+
return fs48.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6986
6961
|
};
|
|
6987
6962
|
fsWrite = () => {
|
|
6988
6963
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6989
|
-
return
|
|
6964
|
+
return fs48.write(this.fd, this._writingBuf, this.release);
|
|
6990
6965
|
}
|
|
6991
|
-
return
|
|
6966
|
+
return fs48.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
6992
6967
|
};
|
|
6993
6968
|
} else {
|
|
6994
6969
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -7045,7 +7020,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7045
7020
|
}
|
|
7046
7021
|
}
|
|
7047
7022
|
if (this._fsync) {
|
|
7048
|
-
|
|
7023
|
+
fs48.fsyncSync(this.fd);
|
|
7049
7024
|
}
|
|
7050
7025
|
const len = this._len;
|
|
7051
7026
|
if (this._reopening) {
|
|
@@ -7097,7 +7072,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7097
7072
|
sonic._asyncDrainScheduled = false;
|
|
7098
7073
|
sonic.emit("drain");
|
|
7099
7074
|
}
|
|
7100
|
-
inherits(SonicBoom,
|
|
7075
|
+
inherits(SonicBoom, EventEmitter3);
|
|
7101
7076
|
function mergeBuf(bufs, len) {
|
|
7102
7077
|
if (bufs.length === 0) {
|
|
7103
7078
|
return kEmptyBuffer;
|
|
@@ -7159,7 +7134,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7159
7134
|
const onDrain = () => {
|
|
7160
7135
|
if (!this._fsync) {
|
|
7161
7136
|
try {
|
|
7162
|
-
|
|
7137
|
+
fs48.fsync(this.fd, (err) => {
|
|
7163
7138
|
this._flushPending = false;
|
|
7164
7139
|
cb(err);
|
|
7165
7140
|
});
|
|
@@ -7261,7 +7236,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7261
7236
|
const fd = this.fd;
|
|
7262
7237
|
this.once("ready", () => {
|
|
7263
7238
|
if (fd !== this.fd) {
|
|
7264
|
-
|
|
7239
|
+
fs48.close(fd, (err) => {
|
|
7265
7240
|
if (err) {
|
|
7266
7241
|
return this.emit("error", err);
|
|
7267
7242
|
}
|
|
@@ -7310,7 +7285,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7310
7285
|
buf = this._bufs[0];
|
|
7311
7286
|
}
|
|
7312
7287
|
try {
|
|
7313
|
-
const n = Buffer.isBuffer(buf) ?
|
|
7288
|
+
const n = Buffer.isBuffer(buf) ? fs48.writeSync(this.fd, buf) : fs48.writeSync(this.fd, buf, "utf8");
|
|
7314
7289
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
7315
7290
|
buf = releasedBufObj.writingBuf;
|
|
7316
7291
|
this._len = releasedBufObj.len;
|
|
@@ -7326,7 +7301,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7326
7301
|
}
|
|
7327
7302
|
}
|
|
7328
7303
|
try {
|
|
7329
|
-
|
|
7304
|
+
fs48.fsyncSync(this.fd);
|
|
7330
7305
|
} catch {
|
|
7331
7306
|
}
|
|
7332
7307
|
}
|
|
@@ -7347,7 +7322,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7347
7322
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
7348
7323
|
}
|
|
7349
7324
|
try {
|
|
7350
|
-
const n =
|
|
7325
|
+
const n = fs48.writeSync(this.fd, buf);
|
|
7351
7326
|
buf = buf.subarray(n);
|
|
7352
7327
|
this._len = Math.max(this._len - n, 0);
|
|
7353
7328
|
if (buf.length <= 0) {
|
|
@@ -7375,13 +7350,13 @@ var require_sonic_boom = __commonJS({
|
|
|
7375
7350
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
7376
7351
|
if (this.sync) {
|
|
7377
7352
|
try {
|
|
7378
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
7353
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs48.writeSync(this.fd, this._writingBuf) : fs48.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7379
7354
|
release(null, written);
|
|
7380
7355
|
} catch (err) {
|
|
7381
7356
|
release(err);
|
|
7382
7357
|
}
|
|
7383
7358
|
} else {
|
|
7384
|
-
|
|
7359
|
+
fs48.write(this.fd, this._writingBuf, release);
|
|
7385
7360
|
}
|
|
7386
7361
|
}
|
|
7387
7362
|
function actualWriteBuffer() {
|
|
@@ -7390,7 +7365,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7390
7365
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
7391
7366
|
if (this.sync) {
|
|
7392
7367
|
try {
|
|
7393
|
-
const written =
|
|
7368
|
+
const written = fs48.writeSync(this.fd, this._writingBuf);
|
|
7394
7369
|
release(null, written);
|
|
7395
7370
|
} catch (err) {
|
|
7396
7371
|
release(err);
|
|
@@ -7399,7 +7374,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7399
7374
|
if (kCopyBuffer) {
|
|
7400
7375
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
7401
7376
|
}
|
|
7402
|
-
|
|
7377
|
+
fs48.write(this.fd, this._writingBuf, release);
|
|
7403
7378
|
}
|
|
7404
7379
|
}
|
|
7405
7380
|
function actualClose(sonic) {
|
|
@@ -7415,12 +7390,12 @@ var require_sonic_boom = __commonJS({
|
|
|
7415
7390
|
sonic._lens = [];
|
|
7416
7391
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
7417
7392
|
try {
|
|
7418
|
-
|
|
7393
|
+
fs48.fsync(sonic.fd, closeWrapped);
|
|
7419
7394
|
} catch {
|
|
7420
7395
|
}
|
|
7421
7396
|
function closeWrapped() {
|
|
7422
7397
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7423
|
-
|
|
7398
|
+
fs48.close(sonic.fd, done);
|
|
7424
7399
|
} else {
|
|
7425
7400
|
done();
|
|
7426
7401
|
}
|
|
@@ -7675,9 +7650,9 @@ var require_thread_stream = __commonJS({
|
|
|
7675
7650
|
"../node_modules/.pnpm/thread-stream@3.1.0/node_modules/thread-stream/index.js"(exports2, module2) {
|
|
7676
7651
|
"use strict";
|
|
7677
7652
|
var { version: version2 } = require_package();
|
|
7678
|
-
var { EventEmitter:
|
|
7653
|
+
var { EventEmitter: EventEmitter3 } = require("events");
|
|
7679
7654
|
var { Worker } = require("worker_threads");
|
|
7680
|
-
var { join:
|
|
7655
|
+
var { join: join12 } = require("path");
|
|
7681
7656
|
var { pathToFileURL } = require("url");
|
|
7682
7657
|
var { wait } = require_wait();
|
|
7683
7658
|
var {
|
|
@@ -7713,7 +7688,7 @@ var require_thread_stream = __commonJS({
|
|
|
7713
7688
|
function createWorker(stream, opts) {
|
|
7714
7689
|
const { filename, workerData } = opts;
|
|
7715
7690
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
7716
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
7691
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join12(__dirname, "lib", "worker.js");
|
|
7717
7692
|
const worker = new Worker(toExecute, {
|
|
7718
7693
|
...opts.workerOpts,
|
|
7719
7694
|
trackUnmanagedFds: false,
|
|
@@ -7831,7 +7806,7 @@ var require_thread_stream = __commonJS({
|
|
|
7831
7806
|
stream.worker.off("exit", onWorkerExit);
|
|
7832
7807
|
destroy(stream, code !== 0 ? new Error("the worker thread exited") : null);
|
|
7833
7808
|
}
|
|
7834
|
-
var ThreadStream = class extends
|
|
7809
|
+
var ThreadStream = class extends EventEmitter3 {
|
|
7835
7810
|
constructor(opts = {}) {
|
|
7836
7811
|
super();
|
|
7837
7812
|
if (opts.bufferSize < 4) {
|
|
@@ -8099,7 +8074,7 @@ var require_transport = __commonJS({
|
|
|
8099
8074
|
"use strict";
|
|
8100
8075
|
var { createRequire } = require("module");
|
|
8101
8076
|
var getCallers = require_caller();
|
|
8102
|
-
var { join:
|
|
8077
|
+
var { join: join12, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
|
|
8103
8078
|
var sleep = require_atomic_sleep();
|
|
8104
8079
|
var onExit = require_on_exit_leak_free();
|
|
8105
8080
|
var ThreadStream = require_thread_stream();
|
|
@@ -8162,7 +8137,7 @@ var require_transport = __commonJS({
|
|
|
8162
8137
|
throw new Error("only one of target or targets can be specified");
|
|
8163
8138
|
}
|
|
8164
8139
|
if (targets) {
|
|
8165
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8140
|
+
target = bundlerOverrides["pino-worker"] || join12(__dirname, "worker.js");
|
|
8166
8141
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
8167
8142
|
return {
|
|
8168
8143
|
...dest,
|
|
@@ -8180,7 +8155,7 @@ var require_transport = __commonJS({
|
|
|
8180
8155
|
});
|
|
8181
8156
|
});
|
|
8182
8157
|
} else if (pipeline3) {
|
|
8183
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8158
|
+
target = bundlerOverrides["pino-worker"] || join12(__dirname, "worker.js");
|
|
8184
8159
|
options.pipelines = [pipeline3.map((dest) => {
|
|
8185
8160
|
return {
|
|
8186
8161
|
...dest,
|
|
@@ -8202,7 +8177,7 @@ var require_transport = __commonJS({
|
|
|
8202
8177
|
return origin;
|
|
8203
8178
|
}
|
|
8204
8179
|
if (origin === "pino/file") {
|
|
8205
|
-
return
|
|
8180
|
+
return join12(__dirname, "..", "file.js");
|
|
8206
8181
|
}
|
|
8207
8182
|
let fixTarget2;
|
|
8208
8183
|
for (const filePath of callers) {
|
|
@@ -8789,7 +8764,7 @@ var require_meta = __commonJS({
|
|
|
8789
8764
|
var require_proto = __commonJS({
|
|
8790
8765
|
"../node_modules/.pnpm/pino@9.14.0/node_modules/pino/lib/proto.js"(exports2, module2) {
|
|
8791
8766
|
"use strict";
|
|
8792
|
-
var { EventEmitter:
|
|
8767
|
+
var { EventEmitter: EventEmitter3 } = require("events");
|
|
8793
8768
|
var {
|
|
8794
8769
|
lsCacheSym,
|
|
8795
8770
|
levelValSym,
|
|
@@ -8871,7 +8846,7 @@ var require_proto = __commonJS({
|
|
|
8871
8846
|
[getLevelSym]: getLevel,
|
|
8872
8847
|
[setLevelSym]: setLevel
|
|
8873
8848
|
};
|
|
8874
|
-
Object.setPrototypeOf(prototype,
|
|
8849
|
+
Object.setPrototypeOf(prototype, EventEmitter3.prototype);
|
|
8875
8850
|
module2.exports = function() {
|
|
8876
8851
|
return Object.create(prototype);
|
|
8877
8852
|
};
|
|
@@ -9192,7 +9167,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9192
9167
|
return circularValue;
|
|
9193
9168
|
}
|
|
9194
9169
|
let res = "";
|
|
9195
|
-
let
|
|
9170
|
+
let join12 = ",";
|
|
9196
9171
|
const originalIndentation = indentation;
|
|
9197
9172
|
if (Array.isArray(value)) {
|
|
9198
9173
|
if (value.length === 0) {
|
|
@@ -9206,7 +9181,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9206
9181
|
indentation += spacer;
|
|
9207
9182
|
res += `
|
|
9208
9183
|
${indentation}`;
|
|
9209
|
-
|
|
9184
|
+
join12 = `,
|
|
9210
9185
|
${indentation}`;
|
|
9211
9186
|
}
|
|
9212
9187
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9214,13 +9189,13 @@ ${indentation}`;
|
|
|
9214
9189
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9215
9190
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9216
9191
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9217
|
-
res +=
|
|
9192
|
+
res += join12;
|
|
9218
9193
|
}
|
|
9219
9194
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9220
9195
|
res += tmp !== void 0 ? tmp : "null";
|
|
9221
9196
|
if (value.length - 1 > maximumBreadth) {
|
|
9222
9197
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9223
|
-
res += `${
|
|
9198
|
+
res += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9224
9199
|
}
|
|
9225
9200
|
if (spacer !== "") {
|
|
9226
9201
|
res += `
|
|
@@ -9241,7 +9216,7 @@ ${originalIndentation}`;
|
|
|
9241
9216
|
let separator = "";
|
|
9242
9217
|
if (spacer !== "") {
|
|
9243
9218
|
indentation += spacer;
|
|
9244
|
-
|
|
9219
|
+
join12 = `,
|
|
9245
9220
|
${indentation}`;
|
|
9246
9221
|
whitespace = " ";
|
|
9247
9222
|
}
|
|
@@ -9255,13 +9230,13 @@ ${indentation}`;
|
|
|
9255
9230
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
9256
9231
|
if (tmp !== void 0) {
|
|
9257
9232
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9258
|
-
separator =
|
|
9233
|
+
separator = join12;
|
|
9259
9234
|
}
|
|
9260
9235
|
}
|
|
9261
9236
|
if (keyLength > maximumBreadth) {
|
|
9262
9237
|
const removedKeys = keyLength - maximumBreadth;
|
|
9263
9238
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
9264
|
-
separator =
|
|
9239
|
+
separator = join12;
|
|
9265
9240
|
}
|
|
9266
9241
|
if (spacer !== "" && separator.length > 1) {
|
|
9267
9242
|
res = `
|
|
@@ -9302,7 +9277,7 @@ ${originalIndentation}`;
|
|
|
9302
9277
|
}
|
|
9303
9278
|
const originalIndentation = indentation;
|
|
9304
9279
|
let res = "";
|
|
9305
|
-
let
|
|
9280
|
+
let join12 = ",";
|
|
9306
9281
|
if (Array.isArray(value)) {
|
|
9307
9282
|
if (value.length === 0) {
|
|
9308
9283
|
return "[]";
|
|
@@ -9315,7 +9290,7 @@ ${originalIndentation}`;
|
|
|
9315
9290
|
indentation += spacer;
|
|
9316
9291
|
res += `
|
|
9317
9292
|
${indentation}`;
|
|
9318
|
-
|
|
9293
|
+
join12 = `,
|
|
9319
9294
|
${indentation}`;
|
|
9320
9295
|
}
|
|
9321
9296
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9323,13 +9298,13 @@ ${indentation}`;
|
|
|
9323
9298
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9324
9299
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9325
9300
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9326
|
-
res +=
|
|
9301
|
+
res += join12;
|
|
9327
9302
|
}
|
|
9328
9303
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9329
9304
|
res += tmp !== void 0 ? tmp : "null";
|
|
9330
9305
|
if (value.length - 1 > maximumBreadth) {
|
|
9331
9306
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9332
|
-
res += `${
|
|
9307
|
+
res += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9333
9308
|
}
|
|
9334
9309
|
if (spacer !== "") {
|
|
9335
9310
|
res += `
|
|
@@ -9342,7 +9317,7 @@ ${originalIndentation}`;
|
|
|
9342
9317
|
let whitespace = "";
|
|
9343
9318
|
if (spacer !== "") {
|
|
9344
9319
|
indentation += spacer;
|
|
9345
|
-
|
|
9320
|
+
join12 = `,
|
|
9346
9321
|
${indentation}`;
|
|
9347
9322
|
whitespace = " ";
|
|
9348
9323
|
}
|
|
@@ -9351,7 +9326,7 @@ ${indentation}`;
|
|
|
9351
9326
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
9352
9327
|
if (tmp !== void 0) {
|
|
9353
9328
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9354
|
-
separator =
|
|
9329
|
+
separator = join12;
|
|
9355
9330
|
}
|
|
9356
9331
|
}
|
|
9357
9332
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -9409,20 +9384,20 @@ ${originalIndentation}`;
|
|
|
9409
9384
|
indentation += spacer;
|
|
9410
9385
|
let res2 = `
|
|
9411
9386
|
${indentation}`;
|
|
9412
|
-
const
|
|
9387
|
+
const join13 = `,
|
|
9413
9388
|
${indentation}`;
|
|
9414
9389
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
9415
9390
|
let i = 0;
|
|
9416
9391
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9417
9392
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9418
9393
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
9419
|
-
res2 +=
|
|
9394
|
+
res2 += join13;
|
|
9420
9395
|
}
|
|
9421
9396
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9422
9397
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
9423
9398
|
if (value.length - 1 > maximumBreadth) {
|
|
9424
9399
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9425
|
-
res2 += `${
|
|
9400
|
+
res2 += `${join13}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9426
9401
|
}
|
|
9427
9402
|
res2 += `
|
|
9428
9403
|
${originalIndentation}`;
|
|
@@ -9438,16 +9413,16 @@ ${originalIndentation}`;
|
|
|
9438
9413
|
return '"[Object]"';
|
|
9439
9414
|
}
|
|
9440
9415
|
indentation += spacer;
|
|
9441
|
-
const
|
|
9416
|
+
const join12 = `,
|
|
9442
9417
|
${indentation}`;
|
|
9443
9418
|
let res = "";
|
|
9444
9419
|
let separator = "";
|
|
9445
9420
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
9446
9421
|
if (isTypedArrayWithEntries(value)) {
|
|
9447
|
-
res += stringifyTypedArray(value,
|
|
9422
|
+
res += stringifyTypedArray(value, join12, maximumBreadth);
|
|
9448
9423
|
keys = keys.slice(value.length);
|
|
9449
9424
|
maximumPropertiesToStringify -= value.length;
|
|
9450
|
-
separator =
|
|
9425
|
+
separator = join12;
|
|
9451
9426
|
}
|
|
9452
9427
|
if (deterministic) {
|
|
9453
9428
|
keys = sort(keys, comparator);
|
|
@@ -9458,13 +9433,13 @@ ${indentation}`;
|
|
|
9458
9433
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
9459
9434
|
if (tmp !== void 0) {
|
|
9460
9435
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
9461
|
-
separator =
|
|
9436
|
+
separator = join12;
|
|
9462
9437
|
}
|
|
9463
9438
|
}
|
|
9464
9439
|
if (keyLength > maximumBreadth) {
|
|
9465
9440
|
const removedKeys = keyLength - maximumBreadth;
|
|
9466
9441
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
9467
|
-
separator =
|
|
9442
|
+
separator = join12;
|
|
9468
9443
|
}
|
|
9469
9444
|
if (separator !== "") {
|
|
9470
9445
|
res = `
|
|
@@ -19346,10 +19321,10 @@ var require_extension = __commonJS({
|
|
|
19346
19321
|
var require_websocket = __commonJS({
|
|
19347
19322
|
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket.js"(exports2, module2) {
|
|
19348
19323
|
"use strict";
|
|
19349
|
-
var
|
|
19324
|
+
var EventEmitter3 = require("events");
|
|
19350
19325
|
var https = require("https");
|
|
19351
|
-
var
|
|
19352
|
-
var
|
|
19326
|
+
var http3 = require("http");
|
|
19327
|
+
var net3 = require("net");
|
|
19353
19328
|
var tls = require("tls");
|
|
19354
19329
|
var { randomBytes, createHash: createHash2 } = require("crypto");
|
|
19355
19330
|
var { Duplex, Readable: Readable3 } = require("stream");
|
|
@@ -19378,7 +19353,7 @@ var require_websocket = __commonJS({
|
|
|
19378
19353
|
var protocolVersions = [8, 13];
|
|
19379
19354
|
var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
|
|
19380
19355
|
var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
|
|
19381
|
-
var WebSocket2 = class _WebSocket extends
|
|
19356
|
+
var WebSocket2 = class _WebSocket extends EventEmitter3 {
|
|
19382
19357
|
/**
|
|
19383
19358
|
* Create a new `WebSocket`.
|
|
19384
19359
|
*
|
|
@@ -19882,7 +19857,7 @@ var require_websocket = __commonJS({
|
|
|
19882
19857
|
}
|
|
19883
19858
|
const defaultPort = isSecure ? 443 : 80;
|
|
19884
19859
|
const key = randomBytes(16).toString("base64");
|
|
19885
|
-
const request = isSecure ? https.request :
|
|
19860
|
+
const request = isSecure ? https.request : http3.request;
|
|
19886
19861
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
19887
19862
|
let perMessageDeflate;
|
|
19888
19863
|
opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
|
|
@@ -20083,12 +20058,12 @@ var require_websocket = __commonJS({
|
|
|
20083
20058
|
}
|
|
20084
20059
|
function netConnect(options) {
|
|
20085
20060
|
options.path = options.socketPath;
|
|
20086
|
-
return
|
|
20061
|
+
return net3.connect(options);
|
|
20087
20062
|
}
|
|
20088
20063
|
function tlsConnect(options) {
|
|
20089
20064
|
options.path = void 0;
|
|
20090
20065
|
if (!options.servername && options.servername !== "") {
|
|
20091
|
-
options.servername =
|
|
20066
|
+
options.servername = net3.isIP(options.host) ? "" : options.host;
|
|
20092
20067
|
}
|
|
20093
20068
|
return tls.connect(options);
|
|
20094
20069
|
}
|
|
@@ -20375,8 +20350,8 @@ var require_subprotocol = __commonJS({
|
|
|
20375
20350
|
var require_websocket_server = __commonJS({
|
|
20376
20351
|
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) {
|
|
20377
20352
|
"use strict";
|
|
20378
|
-
var
|
|
20379
|
-
var
|
|
20353
|
+
var EventEmitter3 = require("events");
|
|
20354
|
+
var http3 = require("http");
|
|
20380
20355
|
var { Duplex } = require("stream");
|
|
20381
20356
|
var { createHash: createHash2 } = require("crypto");
|
|
20382
20357
|
var extension2 = require_extension();
|
|
@@ -20388,7 +20363,7 @@ var require_websocket_server = __commonJS({
|
|
|
20388
20363
|
var RUNNING = 0;
|
|
20389
20364
|
var CLOSING = 1;
|
|
20390
20365
|
var CLOSED = 2;
|
|
20391
|
-
var WebSocketServer2 = class extends
|
|
20366
|
+
var WebSocketServer2 = class extends EventEmitter3 {
|
|
20392
20367
|
/**
|
|
20393
20368
|
* Create a `WebSocketServer` instance.
|
|
20394
20369
|
*
|
|
@@ -20451,8 +20426,8 @@ var require_websocket_server = __commonJS({
|
|
|
20451
20426
|
);
|
|
20452
20427
|
}
|
|
20453
20428
|
if (options.port != null) {
|
|
20454
|
-
this._server =
|
|
20455
|
-
const body =
|
|
20429
|
+
this._server = http3.createServer((req, res) => {
|
|
20430
|
+
const body = http3.STATUS_CODES[426];
|
|
20456
20431
|
res.writeHead(426, {
|
|
20457
20432
|
"Content-Length": body.length,
|
|
20458
20433
|
"Content-Type": "text/plain"
|
|
@@ -20739,7 +20714,7 @@ var require_websocket_server = __commonJS({
|
|
|
20739
20714
|
this.destroy();
|
|
20740
20715
|
}
|
|
20741
20716
|
function abortHandshake(socket, code, message, headers) {
|
|
20742
|
-
message = message ||
|
|
20717
|
+
message = message || http3.STATUS_CODES[code];
|
|
20743
20718
|
headers = {
|
|
20744
20719
|
Connection: "close",
|
|
20745
20720
|
"Content-Type": "text/html",
|
|
@@ -20748,7 +20723,7 @@ var require_websocket_server = __commonJS({
|
|
|
20748
20723
|
};
|
|
20749
20724
|
socket.once("finish", socket.destroy);
|
|
20750
20725
|
socket.end(
|
|
20751
|
-
`HTTP/1.1 ${code} ${
|
|
20726
|
+
`HTTP/1.1 ${code} ${http3.STATUS_CODES[code]}\r
|
|
20752
20727
|
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
|
|
20753
20728
|
);
|
|
20754
20729
|
}
|
|
@@ -21051,7 +21026,7 @@ var require_BufferList = __commonJS({
|
|
|
21051
21026
|
this.head = this.tail = null;
|
|
21052
21027
|
this.length = 0;
|
|
21053
21028
|
};
|
|
21054
|
-
BufferList.prototype.join = function
|
|
21029
|
+
BufferList.prototype.join = function join12(s) {
|
|
21055
21030
|
if (this.length === 0) return "";
|
|
21056
21031
|
var p2 = this.head;
|
|
21057
21032
|
var ret = "" + p2.data;
|
|
@@ -30508,7 +30483,7 @@ var require_lib3 = __commonJS({
|
|
|
30508
30483
|
// src/run-case/recorder.ts
|
|
30509
30484
|
function startRunCaseRecorder(opts) {
|
|
30510
30485
|
const now = opts.now ?? Date.now;
|
|
30511
|
-
const dir =
|
|
30486
|
+
const dir = import_node_path46.default.dirname(opts.recordPath);
|
|
30512
30487
|
let stream = null;
|
|
30513
30488
|
let closing = false;
|
|
30514
30489
|
let closedSettled = false;
|
|
@@ -30522,8 +30497,8 @@ function startRunCaseRecorder(opts) {
|
|
|
30522
30497
|
});
|
|
30523
30498
|
const ensureStream = () => {
|
|
30524
30499
|
if (stream) return stream;
|
|
30525
|
-
|
|
30526
|
-
stream =
|
|
30500
|
+
import_node_fs31.default.mkdirSync(dir, { recursive: true });
|
|
30501
|
+
stream = import_node_fs31.default.createWriteStream(opts.recordPath, { flags: "a" });
|
|
30527
30502
|
stream.on("close", () => closedResolve());
|
|
30528
30503
|
return stream;
|
|
30529
30504
|
};
|
|
@@ -30548,12 +30523,12 @@ function startRunCaseRecorder(opts) {
|
|
|
30548
30523
|
};
|
|
30549
30524
|
return { tap, close, closed };
|
|
30550
30525
|
}
|
|
30551
|
-
var
|
|
30526
|
+
var import_node_fs31, import_node_path46;
|
|
30552
30527
|
var init_recorder = __esm({
|
|
30553
30528
|
"src/run-case/recorder.ts"() {
|
|
30554
30529
|
"use strict";
|
|
30555
|
-
|
|
30556
|
-
|
|
30530
|
+
import_node_fs31 = __toESM(require("fs"), 1);
|
|
30531
|
+
import_node_path46 = __toESM(require("path"), 1);
|
|
30557
30532
|
}
|
|
30558
30533
|
});
|
|
30559
30534
|
|
|
@@ -30596,7 +30571,7 @@ var init_wire = __esm({
|
|
|
30596
30571
|
// src/run-case/controller.ts
|
|
30597
30572
|
async function runController(opts) {
|
|
30598
30573
|
const now = opts.now ?? Date.now;
|
|
30599
|
-
const cwd = opts.cwd ?? (0,
|
|
30574
|
+
const cwd = opts.cwd ?? (0, import_node_fs32.mkdtempSync)(import_node_path47.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
|
|
30600
30575
|
const ownsCwd = opts.cwd === void 0;
|
|
30601
30576
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
30602
30577
|
const spawnCtx = { cwd };
|
|
@@ -30757,19 +30732,19 @@ async function runController(opts) {
|
|
|
30757
30732
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
30758
30733
|
if (ownsCwd) {
|
|
30759
30734
|
try {
|
|
30760
|
-
(0,
|
|
30735
|
+
(0, import_node_fs32.rmSync)(cwd, { recursive: true, force: true });
|
|
30761
30736
|
} catch {
|
|
30762
30737
|
}
|
|
30763
30738
|
}
|
|
30764
30739
|
return exitCode ?? 0;
|
|
30765
30740
|
}
|
|
30766
|
-
var
|
|
30741
|
+
var import_node_fs32, import_node_os19, import_node_path47;
|
|
30767
30742
|
var init_controller = __esm({
|
|
30768
30743
|
"src/run-case/controller.ts"() {
|
|
30769
30744
|
"use strict";
|
|
30770
|
-
|
|
30745
|
+
import_node_fs32 = require("fs");
|
|
30771
30746
|
import_node_os19 = __toESM(require("os"), 1);
|
|
30772
|
-
|
|
30747
|
+
import_node_path47 = __toESM(require("path"), 1);
|
|
30773
30748
|
init_claude();
|
|
30774
30749
|
init_stdout_splitter();
|
|
30775
30750
|
init_permission_stdio();
|
|
@@ -30961,6 +30936,8 @@ function resolveConfig(opts) {
|
|
|
30961
30936
|
const frpcBinary = env.CLAWD_FRPC_BIN ?? null;
|
|
30962
30937
|
const rawMode = env.CLAWD_CC_MODE;
|
|
30963
30938
|
const mode = DAEMON_MODE_VALUES.includes(rawMode ?? "") ? rawMode : "sdk";
|
|
30939
|
+
const filePreviewPorts = Array.isArray(fileCfg.previewPorts) ? fileCfg.previewPorts.map((n) => Number(n)).filter((n) => Number.isInteger(n) && n > 0) : null;
|
|
30940
|
+
const previewPorts = env.CLAWD_PREVIEW_PORTS ? env.CLAWD_PREVIEW_PORTS.split(",").map((s) => Number(s.trim())).filter((n) => Number.isInteger(n) && n > 0) : filePreviewPorts && filePreviewPorts.length > 0 ? filePreviewPorts : null;
|
|
30964
30941
|
return {
|
|
30965
30942
|
port,
|
|
30966
30943
|
host,
|
|
@@ -30972,7 +30949,8 @@ function resolveConfig(opts) {
|
|
|
30972
30949
|
authToken,
|
|
30973
30950
|
noTunnelPersist,
|
|
30974
30951
|
frpcBinary,
|
|
30975
|
-
mode
|
|
30952
|
+
mode,
|
|
30953
|
+
previewPorts
|
|
30976
30954
|
};
|
|
30977
30955
|
}
|
|
30978
30956
|
var HELP_TEXT = `clawd [options]
|
|
@@ -31001,8 +30979,8 @@ Env (advanced):
|
|
|
31001
30979
|
`;
|
|
31002
30980
|
|
|
31003
30981
|
// src/index.ts
|
|
31004
|
-
var
|
|
31005
|
-
var
|
|
30982
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
30983
|
+
var import_node_fs30 = __toESM(require("fs"), 1);
|
|
31006
30984
|
|
|
31007
30985
|
// src/logger.ts
|
|
31008
30986
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -33011,6 +32989,7 @@ var SessionManager = class {
|
|
|
33011
32989
|
iconKey: args.iconKey,
|
|
33012
32990
|
forkedFromSessionId: args.forkedFromSessionId,
|
|
33013
32991
|
ownerPersonaId: args.ownerPersonaId,
|
|
32992
|
+
appBuilderProject: args.appBuilderProject,
|
|
33014
32993
|
creatorPrincipalId,
|
|
33015
32994
|
...creatorOwnerPrincipalId ? { creatorOwnerPrincipalId } : {},
|
|
33016
32995
|
createdAt: iso,
|
|
@@ -34647,6 +34626,15 @@ var DEFAULT_PERSONAS = [
|
|
|
34647
34626
|
model: "opus",
|
|
34648
34627
|
iconKey: "assist",
|
|
34649
34628
|
public: false
|
|
34629
|
+
},
|
|
34630
|
+
{
|
|
34631
|
+
// app-builder:全栈应用搭建师,绑 extension-kit 模板 + multi-project picker
|
|
34632
|
+
// spec: superpowers/specs/2026-06-01-app-builder-project-picker-design.md
|
|
34633
|
+
personaId: "persona-app-builder",
|
|
34634
|
+
label: "App Builder",
|
|
34635
|
+
model: "opus",
|
|
34636
|
+
iconKey: "assist",
|
|
34637
|
+
public: false
|
|
34650
34638
|
}
|
|
34651
34639
|
];
|
|
34652
34640
|
function findDefaultsRoot() {
|
|
@@ -36343,13 +36331,63 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
36343
36331
|
var wrapper_default = import_websocket.default;
|
|
36344
36332
|
|
|
36345
36333
|
// src/transport/local-ws-server.ts
|
|
36334
|
+
var import_node_http2 = __toESM(require("http"), 1);
|
|
36335
|
+
|
|
36336
|
+
// src/transport/preview-proxy.ts
|
|
36346
36337
|
var import_node_http = __toESM(require("http"), 1);
|
|
36338
|
+
var import_node_net = __toESM(require("net"), 1);
|
|
36339
|
+
var PREVIEW_RE = /^\/preview\/(\d+)(?:\/.*)?$/;
|
|
36340
|
+
function matchPreviewPort(pathname) {
|
|
36341
|
+
const m2 = pathname.match(PREVIEW_RE);
|
|
36342
|
+
return m2 ? Number(m2[1]) : null;
|
|
36343
|
+
}
|
|
36344
|
+
function isPreviewPortAllowed(port, cfg) {
|
|
36345
|
+
return cfg.allowedPorts.includes(port);
|
|
36346
|
+
}
|
|
36347
|
+
function proxyPreviewHttp(req, res, port) {
|
|
36348
|
+
return new Promise((resolve6) => {
|
|
36349
|
+
const upstream = import_node_http.default.request(
|
|
36350
|
+
{ host: "localhost", port, method: req.method, path: req.url, headers: req.headers },
|
|
36351
|
+
(upRes) => {
|
|
36352
|
+
res.writeHead(upRes.statusCode || 502, upRes.headers);
|
|
36353
|
+
upRes.pipe(res);
|
|
36354
|
+
upRes.on("end", () => resolve6());
|
|
36355
|
+
}
|
|
36356
|
+
);
|
|
36357
|
+
upstream.on("error", (e) => {
|
|
36358
|
+
if (!res.headersSent) {
|
|
36359
|
+
res.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
|
|
36360
|
+
}
|
|
36361
|
+
res.end(`preview upstream error: ${e.message}`);
|
|
36362
|
+
resolve6();
|
|
36363
|
+
});
|
|
36364
|
+
req.pipe(upstream);
|
|
36365
|
+
});
|
|
36366
|
+
}
|
|
36367
|
+
function proxyPreviewUpgrade(req, socket, head, port) {
|
|
36368
|
+
const upstream = import_node_net.default.connect(port, "localhost", () => {
|
|
36369
|
+
let raw = `${req.method} ${req.url} HTTP/1.1\r
|
|
36370
|
+
`;
|
|
36371
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
36372
|
+
raw += `${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r
|
|
36373
|
+
`;
|
|
36374
|
+
}
|
|
36375
|
+
raw += "\r\n";
|
|
36376
|
+
upstream.write(raw);
|
|
36377
|
+
if (head && head.length) upstream.write(head);
|
|
36378
|
+
upstream.pipe(socket);
|
|
36379
|
+
socket.pipe(upstream);
|
|
36380
|
+
});
|
|
36381
|
+
upstream.on("error", () => socket.destroy());
|
|
36382
|
+
socket.on("error", () => upstream.destroy());
|
|
36383
|
+
}
|
|
36384
|
+
|
|
36385
|
+
// src/transport/local-ws-server.ts
|
|
36347
36386
|
var LocalWsServer = class {
|
|
36348
36387
|
constructor(opts) {
|
|
36349
36388
|
this.opts = opts;
|
|
36350
36389
|
this.logger = opts.logger;
|
|
36351
36390
|
this.pingIntervalMs = opts.pingIntervalMs ?? 3e4;
|
|
36352
|
-
this.currentOwnerPrincipalId = opts.ownerPrincipalId;
|
|
36353
36391
|
}
|
|
36354
36392
|
opts;
|
|
36355
36393
|
wss = null;
|
|
@@ -36361,19 +36399,28 @@ var LocalWsServer = class {
|
|
|
36361
36399
|
capabilityIdToClients = /* @__PURE__ */ new Map();
|
|
36362
36400
|
logger;
|
|
36363
36401
|
pingIntervalMs;
|
|
36364
|
-
// 飞书热切换(spec 决策 #9,2026-06-01 拍板):登录后实时更新,broadcastToPrincipal
|
|
36365
|
-
// 路由用最新身份;初始值来自 opts.ownerPrincipalId
|
|
36366
|
-
currentOwnerPrincipalId;
|
|
36367
|
-
/** 飞书热切换:登录回调成功后由 daemon wiring 调用,更新 DM 路由身份 */
|
|
36368
|
-
setOwnerPrincipalId(id) {
|
|
36369
|
-
this.currentOwnerPrincipalId = id;
|
|
36370
|
-
}
|
|
36371
36402
|
async start() {
|
|
36372
36403
|
const host = this.opts.host ?? "127.0.0.1";
|
|
36373
36404
|
await new Promise((resolve6, reject) => {
|
|
36374
|
-
const httpServer =
|
|
36405
|
+
const httpServer = import_node_http2.default.createServer((req, res) => this.handleHttpRequest(req, res));
|
|
36375
36406
|
const wss = new import_websocket_server.default({ noServer: true, clientTracking: true });
|
|
36376
36407
|
httpServer.on("upgrade", (req, socket, head) => {
|
|
36408
|
+
if (req.url?.startsWith("/preview/")) {
|
|
36409
|
+
const pathname = (() => {
|
|
36410
|
+
try {
|
|
36411
|
+
return new URL(req.url, "http://localhost").pathname;
|
|
36412
|
+
} catch {
|
|
36413
|
+
return "/";
|
|
36414
|
+
}
|
|
36415
|
+
})();
|
|
36416
|
+
const port = matchPreviewPort(pathname);
|
|
36417
|
+
if (port != null && (this.opts.previewPorts ?? []).includes(port)) {
|
|
36418
|
+
proxyPreviewUpgrade(req, socket, head, port);
|
|
36419
|
+
} else {
|
|
36420
|
+
socket.destroy();
|
|
36421
|
+
}
|
|
36422
|
+
return;
|
|
36423
|
+
}
|
|
36377
36424
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
36378
36425
|
this.routeConnection(ws, req);
|
|
36379
36426
|
});
|
|
@@ -36487,7 +36534,7 @@ var LocalWsServer = class {
|
|
|
36487
36534
|
// principalId === 'cap_xxx' → 该 capability 的所有 guest ws (多端 / 多 tab)
|
|
36488
36535
|
// 用于 DM inbox:event 路由: from + to 两侧各播一次, 让双方 UI 实时看到 thread 更新.
|
|
36489
36536
|
broadcastToPrincipal(principalId, frame) {
|
|
36490
|
-
if (principalId === "owner" || principalId === this.
|
|
36537
|
+
if (principalId === "owner" || principalId === this.opts.ownerPrincipalId) {
|
|
36491
36538
|
this.broadcastToOwners(frame);
|
|
36492
36539
|
return;
|
|
36493
36540
|
}
|
|
@@ -36539,21 +36586,6 @@ var LocalWsServer = class {
|
|
|
36539
36586
|
getClientContext(clientId) {
|
|
36540
36587
|
return this.clients.get(clientId)?.ctx ?? null;
|
|
36541
36588
|
}
|
|
36542
|
-
// 飞书热切换(spec 决策 #9):身份切换后踢掉所有连接强制重连——在线连接的 ctx
|
|
36543
|
-
// (attachClientContext 时绑定)持有旧身份,重连后用新身份重建。
|
|
36544
|
-
// close code 4408(非 4401!4401 = token 撤销,客户端会停止重连):
|
|
36545
|
-
// 客户端按普通断连处理 → 自动重连 → 新身份 ctx。
|
|
36546
|
-
closeAllClients(code = 4408, reason = "IDENTITY_SWITCHED") {
|
|
36547
|
-
const ids = [...this.clients.keys()];
|
|
36548
|
-
for (const id of ids) {
|
|
36549
|
-
const c = this.clients.get(id);
|
|
36550
|
-
if (!c) continue;
|
|
36551
|
-
try {
|
|
36552
|
-
c.ws.close(code, reason);
|
|
36553
|
-
} catch {
|
|
36554
|
-
}
|
|
36555
|
-
}
|
|
36556
|
-
}
|
|
36557
36589
|
// Task 1.10 撤销级联:关闭某 capability 的所有活跃 ws 连接。close code 4401
|
|
36558
36590
|
// 让客户端识别 'capability revoked' 而非普通断连。
|
|
36559
36591
|
closeConnectionsByCapability(capabilityId, code = 4401, reason = "TOKEN_REVOKED") {
|
|
@@ -36622,9 +36654,7 @@ var LocalWsServer = class {
|
|
|
36622
36654
|
this.logger?.warn("ready frame build failed", { err: err.message });
|
|
36623
36655
|
}
|
|
36624
36656
|
socket.on("message", (data) => {
|
|
36625
|
-
|
|
36626
|
-
this.logger?.warn("onMessage failed", { err: err.message });
|
|
36627
|
-
});
|
|
36657
|
+
this.onMessage(handle, data, remoteAddress);
|
|
36628
36658
|
});
|
|
36629
36659
|
socket.on("close", () => {
|
|
36630
36660
|
const c = this.clients.get(id);
|
|
@@ -36643,7 +36673,7 @@ var LocalWsServer = class {
|
|
|
36643
36673
|
this.logger?.warn("client socket error", { clientId: id, err: err.message });
|
|
36644
36674
|
});
|
|
36645
36675
|
}
|
|
36646
|
-
|
|
36676
|
+
onMessage(client, data, remoteAddress) {
|
|
36647
36677
|
let frame;
|
|
36648
36678
|
try {
|
|
36649
36679
|
frame = JSON.parse(data.toString());
|
|
@@ -36654,7 +36684,7 @@ var LocalWsServer = class {
|
|
|
36654
36684
|
const authGate = this.opts.authGate;
|
|
36655
36685
|
if (authGate) {
|
|
36656
36686
|
const wasAuthed = authGate.isAuthed(client.id);
|
|
36657
|
-
const verdict =
|
|
36687
|
+
const verdict = authGate.handleFrame({ id: client.id, remoteAddress }, frame);
|
|
36658
36688
|
if (verdict !== "pass") {
|
|
36659
36689
|
if (!wasAuthed && authGate.isAuthed(client.id)) {
|
|
36660
36690
|
try {
|
|
@@ -36757,7 +36787,7 @@ var AuthGate = class {
|
|
|
36757
36787
|
registerConnection(handle) {
|
|
36758
36788
|
const enforce = this.opts.shouldEnforce(handle.remoteAddress);
|
|
36759
36789
|
if (!enforce) {
|
|
36760
|
-
this.states.set(handle.id, { authed: true, enforce: false, timer: null
|
|
36790
|
+
this.states.set(handle.id, { authed: true, enforce: false, timer: null });
|
|
36761
36791
|
return true;
|
|
36762
36792
|
}
|
|
36763
36793
|
const setT = this.opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
@@ -36766,7 +36796,7 @@ var AuthGate = class {
|
|
|
36766
36796
|
if (!st || st.authed) return;
|
|
36767
36797
|
this.opts.closeConnection(handle, 1008, "auth timeout");
|
|
36768
36798
|
}, this.opts.timeoutMs);
|
|
36769
|
-
this.states.set(handle.id, { authed: false, enforce: true, timer
|
|
36799
|
+
this.states.set(handle.id, { authed: false, enforce: true, timer });
|
|
36770
36800
|
return false;
|
|
36771
36801
|
}
|
|
36772
36802
|
unregister(handleId) {
|
|
@@ -36782,15 +36812,12 @@ var AuthGate = class {
|
|
|
36782
36812
|
}
|
|
36783
36813
|
// 收到一帧时调;返回 'consumed' / 'reject' / 'pass'
|
|
36784
36814
|
// - consumed:这一帧是 auth 帧(成功或失败),调用方不要再走业务路由
|
|
36785
|
-
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate
|
|
36815
|
+
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate 已关连接)
|
|
36786
36816
|
// - pass:已 authed,调用方继续走业务路由
|
|
36787
|
-
|
|
36788
|
-
// Phase 2:async(authenticate 注入路径走中心 server introspect HTTP 调用)。
|
|
36789
|
-
async handleFrame(handle, frame) {
|
|
36817
|
+
handleFrame(handle, frame) {
|
|
36790
36818
|
const st = this.states.get(handle.id);
|
|
36791
36819
|
if (!st) return "reject";
|
|
36792
36820
|
if (st.authed) return "pass";
|
|
36793
|
-
if (st.pending) return "reject";
|
|
36794
36821
|
const parsed = AuthRequestFrameSchema.safeParse(frame);
|
|
36795
36822
|
if (!parsed.success) {
|
|
36796
36823
|
const reason = frame.type === "auth" ? "auth failed" : "auth required";
|
|
@@ -36800,19 +36827,11 @@ var AuthGate = class {
|
|
|
36800
36827
|
}
|
|
36801
36828
|
let ctx = null;
|
|
36802
36829
|
if (this.opts.authenticate) {
|
|
36803
|
-
|
|
36804
|
-
|
|
36805
|
-
|
|
36806
|
-
|
|
36807
|
-
|
|
36808
|
-
parsed.data.selfPrincipalId,
|
|
36809
|
-
parsed.data.selfDisplayName,
|
|
36810
|
-
parsed.data.selfUrl
|
|
36811
|
-
);
|
|
36812
|
-
} finally {
|
|
36813
|
-
st.pending = false;
|
|
36814
|
-
}
|
|
36815
|
-
if (!this.states.has(handle.id)) return "reject";
|
|
36830
|
+
const r = this.opts.authenticate(
|
|
36831
|
+
parsed.data.token,
|
|
36832
|
+
parsed.data.selfPrincipalId,
|
|
36833
|
+
parsed.data.selfDisplayName
|
|
36834
|
+
);
|
|
36816
36835
|
if (!r.ok) {
|
|
36817
36836
|
this.opts.closeConnection(handle, 4401, r.code);
|
|
36818
36837
|
this.markFailed(handle.id);
|
|
@@ -36915,33 +36934,26 @@ function ownerContext(ownerPrincipalId, displayName) {
|
|
|
36915
36934
|
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
36916
36935
|
};
|
|
36917
36936
|
}
|
|
36918
|
-
function
|
|
36937
|
+
function guestContext(cap, peerOwnerPrincipalId) {
|
|
36919
36938
|
return {
|
|
36920
|
-
principal: { id:
|
|
36921
|
-
grants:
|
|
36922
|
-
|
|
36923
|
-
|
|
36924
|
-
})),
|
|
36925
|
-
capabilityId: unionId,
|
|
36926
|
-
peerOwnerPrincipalId: unionId
|
|
36939
|
+
principal: { id: cap.id, kind: "guest", displayName: cap.displayName },
|
|
36940
|
+
grants: cap.grants,
|
|
36941
|
+
capabilityId: cap.id,
|
|
36942
|
+
...peerOwnerPrincipalId ? { peerOwnerPrincipalId } : {}
|
|
36927
36943
|
};
|
|
36928
36944
|
}
|
|
36929
|
-
|
|
36945
|
+
function authenticate(token, deps) {
|
|
36930
36946
|
if (!token) return { ok: false, code: "NO_TOKEN" };
|
|
36931
36947
|
if (deps.isOwnerToken(token)) {
|
|
36932
36948
|
return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
|
|
36933
36949
|
}
|
|
36934
|
-
if (!deps.
|
|
36935
|
-
|
|
36936
|
-
|
|
36937
|
-
|
|
36938
|
-
|
|
36939
|
-
return { ok: false, code: "BAD_TOKEN" };
|
|
36940
|
-
}
|
|
36941
|
-
if (!result.active) {
|
|
36942
|
-
return { ok: false, code: "TOKEN_REVOKED" };
|
|
36950
|
+
if (!deps.capabilityRegistry) return { ok: false, code: "BAD_TOKEN" };
|
|
36951
|
+
const v2 = deps.capabilityRegistry.verifyToken(token);
|
|
36952
|
+
if (!v2.ok) {
|
|
36953
|
+
if (v2.code === "TOKEN_INVALID") return { ok: false, code: "BAD_TOKEN" };
|
|
36954
|
+
return { ok: false, code: v2.code };
|
|
36943
36955
|
}
|
|
36944
|
-
return { ok: true, context:
|
|
36956
|
+
return { ok: true, context: guestContext(v2.capability, deps.selfPrincipalId) };
|
|
36945
36957
|
}
|
|
36946
36958
|
|
|
36947
36959
|
// src/permission/capability-store.ts
|
|
@@ -37121,14 +37133,89 @@ function cleanupGuestSessionsForCapability(cap, factory) {
|
|
|
37121
37133
|
return { removed };
|
|
37122
37134
|
}
|
|
37123
37135
|
|
|
37136
|
+
// src/permission/personal-capability.ts
|
|
37137
|
+
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
37138
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
37139
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
37140
|
+
var PERSONAL_CAP_FILE_NAME = "personal-capability.json";
|
|
37141
|
+
var PERSONAL_CAP_DISPLAY_NAME = "personal";
|
|
37142
|
+
function personalCapFilePath(dataDir) {
|
|
37143
|
+
return import_node_path16.default.join(dataDir, PERSONAL_CAP_FILE_NAME);
|
|
37144
|
+
}
|
|
37145
|
+
function loadOrCreatePersonalCapability(opts) {
|
|
37146
|
+
const file = personalCapFilePath(opts.dataDir);
|
|
37147
|
+
const generateToken = opts.generateToken ?? defaultGenerateToken;
|
|
37148
|
+
const generateId = opts.generateId ?? defaultGenerateId;
|
|
37149
|
+
const now = opts.now ?? Date.now;
|
|
37150
|
+
const existing = readFile(file);
|
|
37151
|
+
if (existing) return existing;
|
|
37152
|
+
const token = generateToken();
|
|
37153
|
+
const id = generateId();
|
|
37154
|
+
const capability = {
|
|
37155
|
+
id,
|
|
37156
|
+
secretHash: sha256Hex2(token),
|
|
37157
|
+
displayName: PERSONAL_CAP_DISPLAY_NAME,
|
|
37158
|
+
// CapabilitySchema 期待 mutable Grant[],protocol 常量是 readonly,落盘前拷一份
|
|
37159
|
+
grants: PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
37160
|
+
resource: { ...g2.resource },
|
|
37161
|
+
actions: [...g2.actions]
|
|
37162
|
+
})),
|
|
37163
|
+
issuedAt: now(),
|
|
37164
|
+
usedCount: 0
|
|
37165
|
+
};
|
|
37166
|
+
const content = { token, capability };
|
|
37167
|
+
writeFile(file, content);
|
|
37168
|
+
return content;
|
|
37169
|
+
}
|
|
37170
|
+
function readFile(file) {
|
|
37171
|
+
let raw;
|
|
37172
|
+
try {
|
|
37173
|
+
raw = import_node_fs15.default.readFileSync(file, "utf8");
|
|
37174
|
+
} catch (err) {
|
|
37175
|
+
const code = err?.code;
|
|
37176
|
+
if (code === "ENOENT") return null;
|
|
37177
|
+
return null;
|
|
37178
|
+
}
|
|
37179
|
+
let parsed;
|
|
37180
|
+
try {
|
|
37181
|
+
parsed = JSON.parse(raw);
|
|
37182
|
+
} catch {
|
|
37183
|
+
return null;
|
|
37184
|
+
}
|
|
37185
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
37186
|
+
const obj = parsed;
|
|
37187
|
+
if (typeof obj.token !== "string" || obj.token.length === 0) return null;
|
|
37188
|
+
const capParsed = CapabilitySchema.safeParse(obj.capability);
|
|
37189
|
+
if (!capParsed.success) return null;
|
|
37190
|
+
if (sha256Hex2(obj.token) !== capParsed.data.secretHash) return null;
|
|
37191
|
+
return { token: obj.token, capability: capParsed.data };
|
|
37192
|
+
}
|
|
37193
|
+
function writeFile(file, content) {
|
|
37194
|
+
import_node_fs15.default.mkdirSync(import_node_path16.default.dirname(file), { recursive: true });
|
|
37195
|
+
import_node_fs15.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
37196
|
+
try {
|
|
37197
|
+
import_node_fs15.default.chmodSync(file, 384);
|
|
37198
|
+
} catch {
|
|
37199
|
+
}
|
|
37200
|
+
}
|
|
37201
|
+
function defaultGenerateToken() {
|
|
37202
|
+
return import_node_crypto4.default.randomBytes(24).toString("base64url");
|
|
37203
|
+
}
|
|
37204
|
+
function defaultGenerateId() {
|
|
37205
|
+
return "personal_" + import_node_crypto4.default.randomBytes(6).toString("base64url");
|
|
37206
|
+
}
|
|
37207
|
+
function sha256Hex2(s) {
|
|
37208
|
+
return import_node_crypto4.default.createHash("sha256").update(s).digest("hex");
|
|
37209
|
+
}
|
|
37210
|
+
|
|
37124
37211
|
// src/inbox/inbox-store.ts
|
|
37125
|
-
var
|
|
37126
|
-
var
|
|
37212
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
37213
|
+
var path22 = __toESM(require("path"), 1);
|
|
37127
37214
|
var INBOX_SUBDIR = "inbox";
|
|
37128
37215
|
var InboxStore = class {
|
|
37129
37216
|
constructor(dataDir) {
|
|
37130
37217
|
this.dataDir = dataDir;
|
|
37131
|
-
|
|
37218
|
+
fs20.mkdirSync(this.dirPath(), { recursive: true });
|
|
37132
37219
|
}
|
|
37133
37220
|
dataDir;
|
|
37134
37221
|
/**
|
|
@@ -37140,7 +37227,7 @@ var InboxStore = class {
|
|
|
37140
37227
|
const file = this.filePath(peerOwnerId);
|
|
37141
37228
|
let raw;
|
|
37142
37229
|
try {
|
|
37143
|
-
raw =
|
|
37230
|
+
raw = fs20.readFileSync(file, "utf8");
|
|
37144
37231
|
} catch (err) {
|
|
37145
37232
|
if (err?.code === "ENOENT") return [];
|
|
37146
37233
|
return [];
|
|
@@ -37156,7 +37243,7 @@ var InboxStore = class {
|
|
|
37156
37243
|
const dir = this.dirPath();
|
|
37157
37244
|
let entries;
|
|
37158
37245
|
try {
|
|
37159
|
-
entries =
|
|
37246
|
+
entries = fs20.readdirSync(dir);
|
|
37160
37247
|
} catch (err) {
|
|
37161
37248
|
if (err?.code === "ENOENT") return [];
|
|
37162
37249
|
return [];
|
|
@@ -37172,9 +37259,9 @@ var InboxStore = class {
|
|
|
37172
37259
|
if (existing.some((m2) => m2.id === message.id)) return;
|
|
37173
37260
|
const file = this.filePath(message.peerOwnerId);
|
|
37174
37261
|
const line = JSON.stringify(message) + "\n";
|
|
37175
|
-
|
|
37262
|
+
fs20.appendFileSync(file, line, { mode: 384 });
|
|
37176
37263
|
try {
|
|
37177
|
-
|
|
37264
|
+
fs20.chmodSync(file, 384);
|
|
37178
37265
|
} catch {
|
|
37179
37266
|
}
|
|
37180
37267
|
}
|
|
@@ -37204,7 +37291,7 @@ var InboxStore = class {
|
|
|
37204
37291
|
removeByPeerOwnerId(peerOwnerId) {
|
|
37205
37292
|
const file = this.filePath(peerOwnerId);
|
|
37206
37293
|
try {
|
|
37207
|
-
|
|
37294
|
+
fs20.unlinkSync(file);
|
|
37208
37295
|
} catch (err) {
|
|
37209
37296
|
if (err?.code === "ENOENT") return;
|
|
37210
37297
|
}
|
|
@@ -37213,18 +37300,18 @@ var InboxStore = class {
|
|
|
37213
37300
|
const file = this.filePath(peerOwnerId);
|
|
37214
37301
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
37215
37302
|
const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
|
|
37216
|
-
|
|
37217
|
-
|
|
37303
|
+
fs20.writeFileSync(tmp, content, { mode: 384 });
|
|
37304
|
+
fs20.renameSync(tmp, file);
|
|
37218
37305
|
try {
|
|
37219
|
-
|
|
37306
|
+
fs20.chmodSync(file, 384);
|
|
37220
37307
|
} catch {
|
|
37221
37308
|
}
|
|
37222
37309
|
}
|
|
37223
37310
|
dirPath() {
|
|
37224
|
-
return
|
|
37311
|
+
return path22.join(this.dataDir, INBOX_SUBDIR);
|
|
37225
37312
|
}
|
|
37226
37313
|
filePath(peerOwnerId) {
|
|
37227
|
-
return
|
|
37314
|
+
return path22.join(this.dirPath(), `${peerOwnerId}.jsonl`);
|
|
37228
37315
|
}
|
|
37229
37316
|
};
|
|
37230
37317
|
function parseAllLines(raw) {
|
|
@@ -37311,8 +37398,8 @@ var InboxManager = class {
|
|
|
37311
37398
|
};
|
|
37312
37399
|
|
|
37313
37400
|
// src/state/received-capability-store.ts
|
|
37314
|
-
var
|
|
37315
|
-
var
|
|
37401
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
37402
|
+
var path23 = __toESM(require("path"), 1);
|
|
37316
37403
|
var FILE_NAME = "received-capabilities.json";
|
|
37317
37404
|
var ReceivedCapabilityStore = class {
|
|
37318
37405
|
constructor(dataDir) {
|
|
@@ -37322,10 +37409,10 @@ var ReceivedCapabilityStore = class {
|
|
|
37322
37409
|
caps = /* @__PURE__ */ new Map();
|
|
37323
37410
|
load() {
|
|
37324
37411
|
this.caps.clear();
|
|
37325
|
-
const file =
|
|
37412
|
+
const file = path23.join(this.dataDir, FILE_NAME);
|
|
37326
37413
|
let raw;
|
|
37327
37414
|
try {
|
|
37328
|
-
raw =
|
|
37415
|
+
raw = fs21.readFileSync(file, "utf8");
|
|
37329
37416
|
} catch (err) {
|
|
37330
37417
|
if (err?.code !== "ENOENT") this.renameBak(file);
|
|
37331
37418
|
return;
|
|
@@ -37362,27 +37449,27 @@ var ReceivedCapabilityStore = class {
|
|
|
37362
37449
|
this.flush();
|
|
37363
37450
|
}
|
|
37364
37451
|
flush() {
|
|
37365
|
-
const file =
|
|
37452
|
+
const file = path23.join(this.dataDir, FILE_NAME);
|
|
37366
37453
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
37367
37454
|
const content = JSON.stringify(
|
|
37368
37455
|
{ receivedCaps: Array.from(this.caps.values()) },
|
|
37369
37456
|
null,
|
|
37370
37457
|
2
|
|
37371
37458
|
);
|
|
37372
|
-
|
|
37373
|
-
|
|
37374
|
-
|
|
37459
|
+
fs21.mkdirSync(this.dataDir, { recursive: true });
|
|
37460
|
+
fs21.writeFileSync(tmp, content, { mode: 384 });
|
|
37461
|
+
fs21.renameSync(tmp, file);
|
|
37375
37462
|
}
|
|
37376
37463
|
renameBak(file) {
|
|
37377
37464
|
try {
|
|
37378
|
-
|
|
37465
|
+
fs21.renameSync(file, `${file}.bak`);
|
|
37379
37466
|
} catch {
|
|
37380
37467
|
}
|
|
37381
37468
|
}
|
|
37382
37469
|
};
|
|
37383
37470
|
|
|
37384
37471
|
// src/received-capability/connect-remote.ts
|
|
37385
|
-
var
|
|
37472
|
+
var crypto6 = __toESM(require("crypto"), 1);
|
|
37386
37473
|
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
37387
37474
|
var RPC_TIMEOUT_MS = 5e3;
|
|
37388
37475
|
async function connectRemote(args) {
|
|
@@ -37400,10 +37487,7 @@ async function connectRemote(args) {
|
|
|
37400
37487
|
type: "auth",
|
|
37401
37488
|
token: args.token,
|
|
37402
37489
|
selfPrincipalId: args.selfPrincipalId,
|
|
37403
|
-
selfDisplayName: args.selfDisplayName
|
|
37404
|
-
// Phase 2 自动反向(spec 决策 #11):带本机可达地址时 hub 反向建联系人;
|
|
37405
|
-
// 缺省时省略字段(不发空串),hub 端 schema selfUrl 是 .min(1).optional()
|
|
37406
|
-
...args.selfUrl ? { selfUrl: args.selfUrl } : {}
|
|
37490
|
+
selfDisplayName: args.selfDisplayName
|
|
37407
37491
|
})
|
|
37408
37492
|
);
|
|
37409
37493
|
await new Promise((resolve6, reject) => {
|
|
@@ -37431,7 +37515,7 @@ async function connectRemote(args) {
|
|
|
37431
37515
|
}, HANDSHAKE_TIMEOUT_MS);
|
|
37432
37516
|
});
|
|
37433
37517
|
function call(method, payload) {
|
|
37434
|
-
const requestId =
|
|
37518
|
+
const requestId = crypto6.randomUUID();
|
|
37435
37519
|
return new Promise((resolve6, reject) => {
|
|
37436
37520
|
const onMessage = (raw) => {
|
|
37437
37521
|
let f;
|
|
@@ -37475,85 +37559,62 @@ async function connectRemote(args) {
|
|
|
37475
37559
|
};
|
|
37476
37560
|
}
|
|
37477
37561
|
|
|
37478
|
-
// src/received-capability/auto-reverse.ts
|
|
37479
|
-
init_protocol();
|
|
37480
|
-
function autoReverseContact(args) {
|
|
37481
|
-
if (!args.selfUrl) return;
|
|
37482
|
-
const now = args.now ?? Date.now;
|
|
37483
|
-
const rc = {
|
|
37484
|
-
ownerPrincipalId: args.unionId,
|
|
37485
|
-
ownerDisplayName: args.displayName,
|
|
37486
|
-
remoteUrl: args.selfUrl,
|
|
37487
|
-
// capabilityId 复用 unionId(与 connectGuestContext 的 guest channel 索引键一致)
|
|
37488
|
-
capabilityId: args.unionId,
|
|
37489
|
-
// B 不持有 A 颁的 token:空串。schema 已放宽允许空(不造假数据)。
|
|
37490
|
-
connectToken: "",
|
|
37491
|
-
grants: PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
37492
|
-
resource: { ...g2.resource },
|
|
37493
|
-
actions: [...g2.actions]
|
|
37494
|
-
})),
|
|
37495
|
-
receivedAt: now()
|
|
37496
|
-
};
|
|
37497
|
-
args.store.upsert(rc);
|
|
37498
|
-
args.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
37499
|
-
}
|
|
37500
|
-
|
|
37501
37562
|
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
37502
|
-
var
|
|
37503
|
-
var
|
|
37563
|
+
var fs22 = __toESM(require("fs"), 1);
|
|
37564
|
+
var path24 = __toESM(require("path"), 1);
|
|
37504
37565
|
var MIGRATION_FLAG_NAME = ".migration.v1.done";
|
|
37505
37566
|
function migrateFlattenSessions(opts) {
|
|
37506
37567
|
const dataDir = opts.dataDir;
|
|
37507
37568
|
const now = opts.now ?? Date.now;
|
|
37508
|
-
const sessionsDir =
|
|
37509
|
-
const flagPath =
|
|
37569
|
+
const sessionsDir = path24.join(dataDir, "sessions");
|
|
37570
|
+
const flagPath = path24.join(sessionsDir, MIGRATION_FLAG_NAME);
|
|
37510
37571
|
if (existsSync3(flagPath)) {
|
|
37511
37572
|
return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
|
|
37512
37573
|
}
|
|
37513
37574
|
let movedBare = 0;
|
|
37514
37575
|
let movedVmOwner = 0;
|
|
37515
37576
|
let archivedListener = 0;
|
|
37516
|
-
const defaultDir =
|
|
37577
|
+
const defaultDir = path24.join(sessionsDir, "default");
|
|
37517
37578
|
if (existsSync3(defaultDir)) {
|
|
37518
37579
|
for (const entry of readdirSafe(defaultDir)) {
|
|
37519
37580
|
if (!entry.endsWith(".json")) continue;
|
|
37520
|
-
const src =
|
|
37521
|
-
const dst =
|
|
37522
|
-
|
|
37581
|
+
const src = path24.join(defaultDir, entry);
|
|
37582
|
+
const dst = path24.join(sessionsDir, entry);
|
|
37583
|
+
fs22.renameSync(src, dst);
|
|
37523
37584
|
movedBare += 1;
|
|
37524
37585
|
}
|
|
37525
37586
|
rmdirIfEmpty(defaultDir);
|
|
37526
37587
|
}
|
|
37527
37588
|
for (const pid of readdirSafe(sessionsDir)) {
|
|
37528
|
-
const personaDir =
|
|
37589
|
+
const personaDir = path24.join(sessionsDir, pid);
|
|
37529
37590
|
if (!isDir(personaDir)) continue;
|
|
37530
37591
|
if (pid === "default") continue;
|
|
37531
|
-
const ownerSrc =
|
|
37592
|
+
const ownerSrc = path24.join(personaDir, "owner");
|
|
37532
37593
|
if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
|
|
37533
|
-
const ownerDst =
|
|
37534
|
-
|
|
37594
|
+
const ownerDst = path24.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
|
|
37595
|
+
fs22.mkdirSync(ownerDst, { recursive: true });
|
|
37535
37596
|
for (const file of readdirSafe(ownerSrc)) {
|
|
37536
37597
|
if (!file.endsWith(".json")) continue;
|
|
37537
|
-
|
|
37598
|
+
fs22.renameSync(path24.join(ownerSrc, file), path24.join(ownerDst, file));
|
|
37538
37599
|
movedVmOwner += 1;
|
|
37539
37600
|
}
|
|
37540
37601
|
rmdirIfEmpty(ownerSrc);
|
|
37541
37602
|
}
|
|
37542
|
-
const listenerSrc =
|
|
37603
|
+
const listenerSrc = path24.join(personaDir, "listener");
|
|
37543
37604
|
if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
|
|
37544
|
-
const archiveDst =
|
|
37545
|
-
|
|
37605
|
+
const archiveDst = path24.join(dataDir, ".legacy", `listener-${pid}`);
|
|
37606
|
+
fs22.mkdirSync(archiveDst, { recursive: true });
|
|
37546
37607
|
for (const file of readdirSafe(listenerSrc)) {
|
|
37547
37608
|
if (!file.endsWith(".json")) continue;
|
|
37548
|
-
|
|
37609
|
+
fs22.renameSync(path24.join(listenerSrc, file), path24.join(archiveDst, file));
|
|
37549
37610
|
archivedListener += 1;
|
|
37550
37611
|
}
|
|
37551
37612
|
rmdirIfEmpty(listenerSrc);
|
|
37552
37613
|
}
|
|
37553
37614
|
rmdirIfEmpty(personaDir);
|
|
37554
37615
|
}
|
|
37555
|
-
|
|
37556
|
-
|
|
37616
|
+
fs22.mkdirSync(sessionsDir, { recursive: true });
|
|
37617
|
+
fs22.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
|
|
37557
37618
|
return {
|
|
37558
37619
|
skipped: false,
|
|
37559
37620
|
flagWritten: true,
|
|
@@ -37564,7 +37625,7 @@ function migrateFlattenSessions(opts) {
|
|
|
37564
37625
|
}
|
|
37565
37626
|
function existsSync3(p2) {
|
|
37566
37627
|
try {
|
|
37567
|
-
|
|
37628
|
+
fs22.statSync(p2);
|
|
37568
37629
|
return true;
|
|
37569
37630
|
} catch {
|
|
37570
37631
|
return false;
|
|
@@ -37572,31 +37633,31 @@ function existsSync3(p2) {
|
|
|
37572
37633
|
}
|
|
37573
37634
|
function isDir(p2) {
|
|
37574
37635
|
try {
|
|
37575
|
-
return
|
|
37636
|
+
return fs22.statSync(p2).isDirectory();
|
|
37576
37637
|
} catch {
|
|
37577
37638
|
return false;
|
|
37578
37639
|
}
|
|
37579
37640
|
}
|
|
37580
37641
|
function readdirSafe(p2) {
|
|
37581
37642
|
try {
|
|
37582
|
-
return
|
|
37643
|
+
return fs22.readdirSync(p2);
|
|
37583
37644
|
} catch {
|
|
37584
37645
|
return [];
|
|
37585
37646
|
}
|
|
37586
37647
|
}
|
|
37587
37648
|
function rmdirIfEmpty(p2) {
|
|
37588
37649
|
try {
|
|
37589
|
-
|
|
37650
|
+
fs22.rmdirSync(p2);
|
|
37590
37651
|
} catch {
|
|
37591
37652
|
}
|
|
37592
37653
|
}
|
|
37593
37654
|
|
|
37594
37655
|
// src/transport/http-router.ts
|
|
37595
|
-
var
|
|
37596
|
-
var
|
|
37656
|
+
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
37657
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
37597
37658
|
|
|
37598
37659
|
// src/attachment/mime.ts
|
|
37599
|
-
var
|
|
37660
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
37600
37661
|
var TEXT_PLAIN = "text/plain; charset=utf-8";
|
|
37601
37662
|
var EXT_TO_NATIVE_MIME = {
|
|
37602
37663
|
// 图片
|
|
@@ -37703,14 +37764,14 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
37703
37764
|
".mk"
|
|
37704
37765
|
]);
|
|
37705
37766
|
function lookupMime(filePathOrName) {
|
|
37706
|
-
const ext =
|
|
37767
|
+
const ext = import_node_path17.default.extname(filePathOrName).toLowerCase();
|
|
37707
37768
|
if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
|
|
37708
37769
|
if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
|
|
37709
37770
|
return "application/octet-stream";
|
|
37710
37771
|
}
|
|
37711
37772
|
|
|
37712
37773
|
// src/attachment/sign-url.ts
|
|
37713
|
-
var
|
|
37774
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
37714
37775
|
var HMAC_ALGO = "sha256";
|
|
37715
37776
|
function base64urlEncode(buf) {
|
|
37716
37777
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -37727,7 +37788,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
37727
37788
|
}
|
|
37728
37789
|
function computeSig(secret, absPath, e) {
|
|
37729
37790
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
37730
|
-
return
|
|
37791
|
+
return import_node_crypto5.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
37731
37792
|
}
|
|
37732
37793
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
37733
37794
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -37762,7 +37823,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
37762
37823
|
if (provided.length !== expected.length) {
|
|
37763
37824
|
return { ok: false, code: "BAD_SIG" };
|
|
37764
37825
|
}
|
|
37765
|
-
if (!
|
|
37826
|
+
if (!import_node_crypto5.default.timingSafeEqual(provided, expected)) {
|
|
37766
37827
|
return { ok: false, code: "BAD_SIG" };
|
|
37767
37828
|
}
|
|
37768
37829
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -37772,9 +37833,9 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
37772
37833
|
}
|
|
37773
37834
|
|
|
37774
37835
|
// src/attachment/upload.ts
|
|
37775
|
-
var
|
|
37776
|
-
var
|
|
37777
|
-
var
|
|
37836
|
+
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
37837
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
37838
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
37778
37839
|
var import_promises = require("stream/promises");
|
|
37779
37840
|
var UploadError = class extends Error {
|
|
37780
37841
|
constructor(code, message) {
|
|
@@ -37785,24 +37846,24 @@ var UploadError = class extends Error {
|
|
|
37785
37846
|
code;
|
|
37786
37847
|
};
|
|
37787
37848
|
function assertValidFileName(fileName) {
|
|
37788
|
-
if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !==
|
|
37849
|
+
if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path18.default.basename(fileName)) {
|
|
37789
37850
|
throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
|
|
37790
37851
|
}
|
|
37791
37852
|
}
|
|
37792
37853
|
var HASH_PREFIX_LEN = 16;
|
|
37793
37854
|
async function writeUploadedAttachment(args) {
|
|
37794
37855
|
assertValidFileName(args.fileName);
|
|
37795
|
-
const attachmentsRoot =
|
|
37856
|
+
const attachmentsRoot = import_node_path18.default.join(args.sessionDir, ".attachments");
|
|
37796
37857
|
try {
|
|
37797
|
-
|
|
37858
|
+
import_node_fs16.default.mkdirSync(attachmentsRoot, { recursive: true });
|
|
37798
37859
|
} catch (err) {
|
|
37799
37860
|
throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
|
|
37800
37861
|
}
|
|
37801
|
-
const hasher =
|
|
37862
|
+
const hasher = import_node_crypto6.default.createHash("sha256");
|
|
37802
37863
|
let actualSize = 0;
|
|
37803
|
-
const tmpPath =
|
|
37864
|
+
const tmpPath = import_node_path18.default.join(
|
|
37804
37865
|
attachmentsRoot,
|
|
37805
|
-
`.upload-${process.pid}-${Date.now()}-${
|
|
37866
|
+
`.upload-${process.pid}-${Date.now()}-${import_node_crypto6.default.randomBytes(4).toString("hex")}`
|
|
37806
37867
|
);
|
|
37807
37868
|
try {
|
|
37808
37869
|
await (0, import_promises.pipeline)(
|
|
@@ -37815,18 +37876,18 @@ async function writeUploadedAttachment(args) {
|
|
|
37815
37876
|
yield buf;
|
|
37816
37877
|
}
|
|
37817
37878
|
},
|
|
37818
|
-
|
|
37879
|
+
import_node_fs16.default.createWriteStream(tmpPath, { mode: 384 })
|
|
37819
37880
|
);
|
|
37820
37881
|
} catch (err) {
|
|
37821
37882
|
try {
|
|
37822
|
-
|
|
37883
|
+
import_node_fs16.default.unlinkSync(tmpPath);
|
|
37823
37884
|
} catch {
|
|
37824
37885
|
}
|
|
37825
37886
|
throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
|
|
37826
37887
|
}
|
|
37827
37888
|
if (actualSize !== args.contentLength) {
|
|
37828
37889
|
try {
|
|
37829
|
-
|
|
37890
|
+
import_node_fs16.default.unlinkSync(tmpPath);
|
|
37830
37891
|
} catch {
|
|
37831
37892
|
}
|
|
37832
37893
|
throw new UploadError(
|
|
@@ -37835,35 +37896,35 @@ async function writeUploadedAttachment(args) {
|
|
|
37835
37896
|
);
|
|
37836
37897
|
}
|
|
37837
37898
|
const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
|
|
37838
|
-
const hashDir =
|
|
37899
|
+
const hashDir = import_node_path18.default.join(attachmentsRoot, attachmentId);
|
|
37839
37900
|
let finalFileName;
|
|
37840
37901
|
let hashDirExists = false;
|
|
37841
37902
|
try {
|
|
37842
|
-
hashDirExists =
|
|
37903
|
+
hashDirExists = import_node_fs16.default.statSync(hashDir).isDirectory();
|
|
37843
37904
|
} catch {
|
|
37844
37905
|
}
|
|
37845
37906
|
if (hashDirExists) {
|
|
37846
|
-
const existing =
|
|
37907
|
+
const existing = import_node_fs16.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
|
|
37847
37908
|
finalFileName = existing[0] ?? args.fileName;
|
|
37848
37909
|
try {
|
|
37849
|
-
|
|
37910
|
+
import_node_fs16.default.unlinkSync(tmpPath);
|
|
37850
37911
|
} catch {
|
|
37851
37912
|
}
|
|
37852
37913
|
} else {
|
|
37853
37914
|
try {
|
|
37854
|
-
|
|
37915
|
+
import_node_fs16.default.mkdirSync(hashDir, { recursive: true });
|
|
37855
37916
|
finalFileName = args.fileName;
|
|
37856
|
-
|
|
37917
|
+
import_node_fs16.default.renameSync(tmpPath, import_node_path18.default.join(hashDir, finalFileName));
|
|
37857
37918
|
} catch (err) {
|
|
37858
37919
|
try {
|
|
37859
|
-
|
|
37920
|
+
import_node_fs16.default.unlinkSync(tmpPath);
|
|
37860
37921
|
} catch {
|
|
37861
37922
|
}
|
|
37862
37923
|
throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
|
|
37863
37924
|
}
|
|
37864
37925
|
}
|
|
37865
|
-
const absPath =
|
|
37866
|
-
const relPath =
|
|
37926
|
+
const absPath = import_node_path18.default.join(hashDir, finalFileName);
|
|
37927
|
+
const relPath = import_node_path18.default.relative(args.sessionDir, absPath);
|
|
37867
37928
|
args.groupFileStore.upsert(args.scope, args.sessionId, {
|
|
37868
37929
|
relPath: absPath,
|
|
37869
37930
|
// 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
|
|
@@ -37877,7 +37938,7 @@ async function writeUploadedAttachment(args) {
|
|
|
37877
37938
|
|
|
37878
37939
|
// src/extension/import.ts
|
|
37879
37940
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
37880
|
-
var
|
|
37941
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
37881
37942
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
37882
37943
|
var import_jszip = __toESM(require_lib3(), 1);
|
|
37883
37944
|
var ImportError = class extends Error {
|
|
@@ -37895,7 +37956,7 @@ async function importZip(buf, root) {
|
|
|
37895
37956
|
throw new ImportError("ZIP_INVALID", `failed to load zip: ${e.message}`);
|
|
37896
37957
|
}
|
|
37897
37958
|
for (const name of Object.keys(zip.files)) {
|
|
37898
|
-
if (name.includes("..") || name.startsWith("/") ||
|
|
37959
|
+
if (name.includes("..") || name.startsWith("/") || import_node_path19.default.isAbsolute(name)) {
|
|
37899
37960
|
throw new ImportError("ZIP_INVALID", `unsafe zip entry path: ${name}`);
|
|
37900
37961
|
}
|
|
37901
37962
|
}
|
|
@@ -37928,7 +37989,7 @@ async function importZip(buf, root) {
|
|
|
37928
37989
|
);
|
|
37929
37990
|
}
|
|
37930
37991
|
}
|
|
37931
|
-
const destDir =
|
|
37992
|
+
const destDir = import_node_path19.default.join(root, manifest.id);
|
|
37932
37993
|
let destExists = false;
|
|
37933
37994
|
try {
|
|
37934
37995
|
await import_promises2.default.access(destDir);
|
|
@@ -37938,15 +37999,15 @@ async function importZip(buf, root) {
|
|
|
37938
37999
|
if (destExists) {
|
|
37939
38000
|
throw new ImportError("ALREADY_EXISTS", `extension ${manifest.id} already installed`);
|
|
37940
38001
|
}
|
|
37941
|
-
const stage = await import_promises2.default.mkdtemp(
|
|
38002
|
+
const stage = await import_promises2.default.mkdtemp(import_node_path19.default.join(import_node_os10.default.tmpdir(), `clawd-ext-stage-${manifest.id}-`));
|
|
37942
38003
|
try {
|
|
37943
38004
|
for (const [name, entry] of Object.entries(zip.files)) {
|
|
37944
|
-
const dest =
|
|
38005
|
+
const dest = import_node_path19.default.join(stage, name);
|
|
37945
38006
|
if (entry.dir) {
|
|
37946
38007
|
await import_promises2.default.mkdir(dest, { recursive: true });
|
|
37947
38008
|
continue;
|
|
37948
38009
|
}
|
|
37949
|
-
await import_promises2.default.mkdir(
|
|
38010
|
+
await import_promises2.default.mkdir(import_node_path19.default.dirname(dest), { recursive: true });
|
|
37950
38011
|
const content = await entry.async("nodebuffer");
|
|
37951
38012
|
await import_promises2.default.writeFile(dest, content);
|
|
37952
38013
|
}
|
|
@@ -37992,7 +38053,7 @@ function isValidUploadFileName(fileName) {
|
|
|
37992
38053
|
if (fileName === "." || fileName === "..") return false;
|
|
37993
38054
|
if (fileName.startsWith(".")) return false;
|
|
37994
38055
|
if (fileName.includes("/") || fileName.includes("\\")) return false;
|
|
37995
|
-
return fileName ===
|
|
38056
|
+
return fileName === import_node_path20.default.basename(fileName);
|
|
37996
38057
|
}
|
|
37997
38058
|
function createHttpRouter(deps) {
|
|
37998
38059
|
return async (req, res) => {
|
|
@@ -38007,23 +38068,13 @@ function createHttpRouter(deps) {
|
|
|
38007
38068
|
});
|
|
38008
38069
|
return true;
|
|
38009
38070
|
}
|
|
38010
|
-
if (url.pathname
|
|
38011
|
-
|
|
38012
|
-
|
|
38013
|
-
|
|
38014
|
-
}
|
|
38015
|
-
if (!deps.feishuLogin) {
|
|
38016
|
-
sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "feishu login not wired" });
|
|
38071
|
+
if (url.pathname.startsWith("/preview/")) {
|
|
38072
|
+
const port = matchPreviewPort(url.pathname);
|
|
38073
|
+
if (port != null && isPreviewPortAllowed(port, { allowedPorts: deps.previewPorts ?? [] })) {
|
|
38074
|
+
await proxyPreviewHttp(req, res, port);
|
|
38017
38075
|
return true;
|
|
38018
38076
|
}
|
|
38019
|
-
|
|
38020
|
-
token: url.searchParams.get("token") ?? "",
|
|
38021
|
-
state: url.searchParams.get("state") ?? "",
|
|
38022
|
-
expiresAt: url.searchParams.get("expires_at")
|
|
38023
|
-
});
|
|
38024
|
-
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>`;
|
|
38025
|
-
res.writeHead(result.ok ? 200 : 400, { "Content-Type": "text/html; charset=utf-8" });
|
|
38026
|
-
res.end(html);
|
|
38077
|
+
sendJson(res, 403, { code: "PREVIEW_PORT_FORBIDDEN", message: "preview port not allowed" });
|
|
38027
38078
|
return true;
|
|
38028
38079
|
}
|
|
38029
38080
|
if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload" && url.pathname !== "/extensions/import") {
|
|
@@ -38186,7 +38237,7 @@ function createHttpRouter(deps) {
|
|
|
38186
38237
|
return true;
|
|
38187
38238
|
}
|
|
38188
38239
|
let absPath;
|
|
38189
|
-
if (
|
|
38240
|
+
if (import_node_path20.default.isAbsolute(pathParam)) {
|
|
38190
38241
|
absPath = pathParam;
|
|
38191
38242
|
} else if (deps.sessionStore) {
|
|
38192
38243
|
const file = deps.sessionStore.read(sid);
|
|
@@ -38194,7 +38245,7 @@ function createHttpRouter(deps) {
|
|
|
38194
38245
|
sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
|
|
38195
38246
|
return true;
|
|
38196
38247
|
}
|
|
38197
|
-
absPath =
|
|
38248
|
+
absPath = import_node_path20.default.join(file.cwd, pathParam);
|
|
38198
38249
|
} else {
|
|
38199
38250
|
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
|
|
38200
38251
|
return true;
|
|
@@ -38261,9 +38312,6 @@ function parseUrl(rawUrl) {
|
|
|
38261
38312
|
return null;
|
|
38262
38313
|
}
|
|
38263
38314
|
}
|
|
38264
|
-
function escapeHtml(s) {
|
|
38265
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
38266
|
-
}
|
|
38267
38315
|
function sendJson(res, status, body, extraHeaders) {
|
|
38268
38316
|
res.writeHead(status, {
|
|
38269
38317
|
"Content-Type": "application/json; charset=utf-8",
|
|
@@ -38277,7 +38325,7 @@ function withCtx(ctx, body) {
|
|
|
38277
38325
|
function streamFile(res, absPath, logger) {
|
|
38278
38326
|
let stat;
|
|
38279
38327
|
try {
|
|
38280
|
-
stat =
|
|
38328
|
+
stat = import_node_fs17.default.statSync(absPath);
|
|
38281
38329
|
} catch (err) {
|
|
38282
38330
|
const code = err?.code;
|
|
38283
38331
|
if (code === "ENOENT") {
|
|
@@ -38292,7 +38340,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38292
38340
|
return;
|
|
38293
38341
|
}
|
|
38294
38342
|
const mime = lookupMime(absPath);
|
|
38295
|
-
const basename =
|
|
38343
|
+
const basename = import_node_path20.default.basename(absPath);
|
|
38296
38344
|
res.writeHead(200, {
|
|
38297
38345
|
"Content-Type": mime,
|
|
38298
38346
|
"Content-Length": String(stat.size),
|
|
@@ -38300,7 +38348,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38300
38348
|
// 防止浏览器把任意 mime 当 html 渲染
|
|
38301
38349
|
"X-Content-Type-Options": "nosniff"
|
|
38302
38350
|
});
|
|
38303
|
-
const stream =
|
|
38351
|
+
const stream = import_node_fs17.default.createReadStream(absPath);
|
|
38304
38352
|
stream.on("error", (err) => {
|
|
38305
38353
|
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
38306
38354
|
res.destroy();
|
|
@@ -38309,8 +38357,8 @@ function streamFile(res, absPath, logger) {
|
|
|
38309
38357
|
}
|
|
38310
38358
|
|
|
38311
38359
|
// src/attachment/gc.ts
|
|
38312
|
-
var
|
|
38313
|
-
var
|
|
38360
|
+
var import_node_fs18 = __toESM(require("fs"), 1);
|
|
38361
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
38314
38362
|
var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
|
|
38315
38363
|
function runAttachmentGc(args) {
|
|
38316
38364
|
const now = (args.now ?? Date.now)();
|
|
@@ -38319,38 +38367,38 @@ function runAttachmentGc(args) {
|
|
|
38319
38367
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38320
38368
|
for (const entry of args.groupFileStore.list(scope, sessionId)) {
|
|
38321
38369
|
if (entry.stale) continue;
|
|
38322
|
-
if (
|
|
38370
|
+
if (import_node_path21.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
|
|
38323
38371
|
}
|
|
38324
38372
|
}
|
|
38325
38373
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38326
|
-
const sessionDir = args.getSessionCwd?.(sessionId) ??
|
|
38374
|
+
const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path21.default.join(
|
|
38327
38375
|
args.dataDir,
|
|
38328
38376
|
"sessions",
|
|
38329
38377
|
...scopeSubPath(scope).map(safeFileName),
|
|
38330
38378
|
safeFileName(sessionId)
|
|
38331
38379
|
);
|
|
38332
|
-
const attRoot =
|
|
38380
|
+
const attRoot = import_node_path21.default.join(sessionDir, ".attachments");
|
|
38333
38381
|
let hashDirs;
|
|
38334
38382
|
try {
|
|
38335
|
-
hashDirs =
|
|
38383
|
+
hashDirs = import_node_fs18.default.readdirSync(attRoot);
|
|
38336
38384
|
} catch (err) {
|
|
38337
38385
|
if (err.code === "ENOENT") continue;
|
|
38338
38386
|
args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
|
|
38339
38387
|
continue;
|
|
38340
38388
|
}
|
|
38341
38389
|
for (const hashDir of hashDirs) {
|
|
38342
|
-
const hashDirAbs =
|
|
38390
|
+
const hashDirAbs = import_node_path21.default.join(attRoot, hashDir);
|
|
38343
38391
|
let files;
|
|
38344
38392
|
try {
|
|
38345
|
-
files =
|
|
38393
|
+
files = import_node_fs18.default.readdirSync(hashDirAbs);
|
|
38346
38394
|
} catch {
|
|
38347
38395
|
continue;
|
|
38348
38396
|
}
|
|
38349
38397
|
for (const name of files) {
|
|
38350
|
-
const file =
|
|
38398
|
+
const file = import_node_path21.default.join(hashDirAbs, name);
|
|
38351
38399
|
let stat;
|
|
38352
38400
|
try {
|
|
38353
|
-
stat =
|
|
38401
|
+
stat = import_node_fs18.default.statSync(file);
|
|
38354
38402
|
} catch {
|
|
38355
38403
|
continue;
|
|
38356
38404
|
}
|
|
@@ -38359,27 +38407,27 @@ function runAttachmentGc(args) {
|
|
|
38359
38407
|
if (age < ttlMs) continue;
|
|
38360
38408
|
if (liveAbs.has(file)) continue;
|
|
38361
38409
|
try {
|
|
38362
|
-
|
|
38410
|
+
import_node_fs18.default.unlinkSync(file);
|
|
38363
38411
|
} catch (err) {
|
|
38364
38412
|
args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
|
|
38365
38413
|
}
|
|
38366
38414
|
}
|
|
38367
38415
|
try {
|
|
38368
|
-
if (
|
|
38416
|
+
if (import_node_fs18.default.readdirSync(hashDirAbs).length === 0) import_node_fs18.default.rmdirSync(hashDirAbs);
|
|
38369
38417
|
} catch {
|
|
38370
38418
|
}
|
|
38371
38419
|
}
|
|
38372
38420
|
try {
|
|
38373
|
-
if (
|
|
38421
|
+
if (import_node_fs18.default.readdirSync(attRoot).length === 0) import_node_fs18.default.rmdirSync(attRoot);
|
|
38374
38422
|
} catch {
|
|
38375
38423
|
}
|
|
38376
38424
|
}
|
|
38377
38425
|
}
|
|
38378
38426
|
|
|
38379
38427
|
// src/attachment/group.ts
|
|
38380
|
-
var
|
|
38381
|
-
var
|
|
38382
|
-
var
|
|
38428
|
+
var import_node_fs19 = __toESM(require("fs"), 1);
|
|
38429
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
38430
|
+
var import_node_crypto7 = __toESM(require("crypto"), 1);
|
|
38383
38431
|
init_protocol();
|
|
38384
38432
|
var GroupFileStore = class {
|
|
38385
38433
|
dataDir;
|
|
@@ -38390,11 +38438,11 @@ var GroupFileStore = class {
|
|
|
38390
38438
|
this.logger = opts.logger;
|
|
38391
38439
|
}
|
|
38392
38440
|
rootForScope(scope) {
|
|
38393
|
-
return
|
|
38441
|
+
return import_node_path22.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
38394
38442
|
}
|
|
38395
38443
|
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
38396
38444
|
filePath(scope, sessionId) {
|
|
38397
|
-
return
|
|
38445
|
+
return import_node_path22.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
38398
38446
|
}
|
|
38399
38447
|
cacheKey(scope, sessionId) {
|
|
38400
38448
|
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
@@ -38403,7 +38451,7 @@ var GroupFileStore = class {
|
|
|
38403
38451
|
readFile(scope, sessionId) {
|
|
38404
38452
|
const file = this.filePath(scope, sessionId);
|
|
38405
38453
|
try {
|
|
38406
|
-
const raw =
|
|
38454
|
+
const raw = import_node_fs19.default.readFileSync(file, "utf8");
|
|
38407
38455
|
const parsed = JSON.parse(raw);
|
|
38408
38456
|
if (!Array.isArray(parsed)) {
|
|
38409
38457
|
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
@@ -38429,10 +38477,10 @@ var GroupFileStore = class {
|
|
|
38429
38477
|
}
|
|
38430
38478
|
writeFile(scope, sessionId, entries) {
|
|
38431
38479
|
const file = this.filePath(scope, sessionId);
|
|
38432
|
-
|
|
38480
|
+
import_node_fs19.default.mkdirSync(import_node_path22.default.dirname(file), { recursive: true });
|
|
38433
38481
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
38434
|
-
|
|
38435
|
-
|
|
38482
|
+
import_node_fs19.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
38483
|
+
import_node_fs19.default.renameSync(tmp, file);
|
|
38436
38484
|
}
|
|
38437
38485
|
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
38438
38486
|
list(scope, sessionId) {
|
|
@@ -38468,7 +38516,7 @@ var GroupFileStore = class {
|
|
|
38468
38516
|
entries[idx] = next;
|
|
38469
38517
|
} else {
|
|
38470
38518
|
next = {
|
|
38471
|
-
id: `gf-${
|
|
38519
|
+
id: `gf-${import_node_crypto7.default.randomBytes(6).toString("base64url")}`,
|
|
38472
38520
|
relPath: input.relPath,
|
|
38473
38521
|
from: input.from,
|
|
38474
38522
|
label: input.label,
|
|
@@ -38518,10 +38566,10 @@ var GroupFileStore = class {
|
|
|
38518
38566
|
};
|
|
38519
38567
|
|
|
38520
38568
|
// src/discovery/state-file.ts
|
|
38521
|
-
var
|
|
38522
|
-
var
|
|
38569
|
+
var import_node_fs20 = __toESM(require("fs"), 1);
|
|
38570
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
38523
38571
|
function defaultStateFilePath(dataDir) {
|
|
38524
|
-
return
|
|
38572
|
+
return import_node_path23.default.join(dataDir, "state.json");
|
|
38525
38573
|
}
|
|
38526
38574
|
function isPidAlive(pid) {
|
|
38527
38575
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
@@ -38543,7 +38591,7 @@ var StateFileManager = class {
|
|
|
38543
38591
|
}
|
|
38544
38592
|
read() {
|
|
38545
38593
|
try {
|
|
38546
|
-
const raw =
|
|
38594
|
+
const raw = import_node_fs20.default.readFileSync(this.file, "utf8");
|
|
38547
38595
|
const parsed = JSON.parse(raw);
|
|
38548
38596
|
return parsed;
|
|
38549
38597
|
} catch {
|
|
@@ -38557,34 +38605,34 @@ var StateFileManager = class {
|
|
|
38557
38605
|
return { status: "stale", existing };
|
|
38558
38606
|
}
|
|
38559
38607
|
write(state) {
|
|
38560
|
-
|
|
38608
|
+
import_node_fs20.default.mkdirSync(import_node_path23.default.dirname(this.file), { recursive: true });
|
|
38561
38609
|
const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
|
|
38562
|
-
|
|
38563
|
-
|
|
38610
|
+
import_node_fs20.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
38611
|
+
import_node_fs20.default.renameSync(tmp, this.file);
|
|
38564
38612
|
if (process.platform !== "win32") {
|
|
38565
38613
|
try {
|
|
38566
|
-
|
|
38614
|
+
import_node_fs20.default.chmodSync(this.file, 384);
|
|
38567
38615
|
} catch {
|
|
38568
38616
|
}
|
|
38569
38617
|
}
|
|
38570
38618
|
}
|
|
38571
38619
|
delete() {
|
|
38572
38620
|
try {
|
|
38573
|
-
|
|
38621
|
+
import_node_fs20.default.unlinkSync(this.file);
|
|
38574
38622
|
} catch {
|
|
38575
38623
|
}
|
|
38576
38624
|
}
|
|
38577
38625
|
};
|
|
38578
38626
|
|
|
38579
38627
|
// src/tunnel/tunnel-manager.ts
|
|
38580
|
-
var
|
|
38581
|
-
var
|
|
38582
|
-
var
|
|
38628
|
+
var import_node_fs24 = __toESM(require("fs"), 1);
|
|
38629
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
38630
|
+
var import_node_crypto8 = __toESM(require("crypto"), 1);
|
|
38583
38631
|
var import_node_child_process5 = require("child_process");
|
|
38584
38632
|
|
|
38585
38633
|
// src/tunnel/tunnel-store.ts
|
|
38586
|
-
var
|
|
38587
|
-
var
|
|
38634
|
+
var import_node_fs21 = __toESM(require("fs"), 1);
|
|
38635
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
38588
38636
|
var TunnelStore = class {
|
|
38589
38637
|
constructor(filePath) {
|
|
38590
38638
|
this.filePath = filePath;
|
|
@@ -38592,7 +38640,7 @@ var TunnelStore = class {
|
|
|
38592
38640
|
filePath;
|
|
38593
38641
|
async get() {
|
|
38594
38642
|
try {
|
|
38595
|
-
const raw = await
|
|
38643
|
+
const raw = await import_node_fs21.default.promises.readFile(this.filePath, "utf8");
|
|
38596
38644
|
const obj = JSON.parse(raw);
|
|
38597
38645
|
if (!isPersistedTunnel(obj)) return null;
|
|
38598
38646
|
return obj;
|
|
@@ -38603,22 +38651,22 @@ var TunnelStore = class {
|
|
|
38603
38651
|
}
|
|
38604
38652
|
}
|
|
38605
38653
|
async set(v2) {
|
|
38606
|
-
const dir =
|
|
38607
|
-
await
|
|
38654
|
+
const dir = import_node_path24.default.dirname(this.filePath);
|
|
38655
|
+
await import_node_fs21.default.promises.mkdir(dir, { recursive: true });
|
|
38608
38656
|
const data = JSON.stringify(v2, null, 2);
|
|
38609
38657
|
const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
38610
|
-
await
|
|
38658
|
+
await import_node_fs21.default.promises.writeFile(tmp, data, { mode: 384 });
|
|
38611
38659
|
if (process.platform !== "win32") {
|
|
38612
38660
|
try {
|
|
38613
|
-
await
|
|
38661
|
+
await import_node_fs21.default.promises.chmod(tmp, 384);
|
|
38614
38662
|
} catch {
|
|
38615
38663
|
}
|
|
38616
38664
|
}
|
|
38617
|
-
await
|
|
38665
|
+
await import_node_fs21.default.promises.rename(tmp, this.filePath);
|
|
38618
38666
|
}
|
|
38619
38667
|
async clear() {
|
|
38620
38668
|
try {
|
|
38621
|
-
await
|
|
38669
|
+
await import_node_fs21.default.promises.unlink(this.filePath);
|
|
38622
38670
|
} catch (err) {
|
|
38623
38671
|
const code = err?.code;
|
|
38624
38672
|
if (code !== "ENOENT") throw err;
|
|
@@ -38713,9 +38761,9 @@ function escape(v2) {
|
|
|
38713
38761
|
}
|
|
38714
38762
|
|
|
38715
38763
|
// src/tunnel/frpc-binary.ts
|
|
38716
|
-
var
|
|
38764
|
+
var import_node_fs22 = __toESM(require("fs"), 1);
|
|
38717
38765
|
var import_node_os11 = __toESM(require("os"), 1);
|
|
38718
|
-
var
|
|
38766
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
38719
38767
|
var import_node_child_process3 = require("child_process");
|
|
38720
38768
|
var import_node_stream2 = require("stream");
|
|
38721
38769
|
var import_promises3 = require("stream/promises");
|
|
@@ -38747,20 +38795,20 @@ function frpcDownloadUrl(version2, p2) {
|
|
|
38747
38795
|
}
|
|
38748
38796
|
async function ensureFrpcBinary(opts) {
|
|
38749
38797
|
if (opts.override) {
|
|
38750
|
-
if (!
|
|
38798
|
+
if (!import_node_fs22.default.existsSync(opts.override)) {
|
|
38751
38799
|
throw new Error(`frpc binary not found at override path: ${opts.override}`);
|
|
38752
38800
|
}
|
|
38753
38801
|
return opts.override;
|
|
38754
38802
|
}
|
|
38755
38803
|
const version2 = opts.version ?? FRPC_VERSION;
|
|
38756
38804
|
const platform = opts.platform ?? detectPlatform();
|
|
38757
|
-
const binDir =
|
|
38758
|
-
|
|
38805
|
+
const binDir = import_node_path25.default.join(opts.dataDir, "bin");
|
|
38806
|
+
import_node_fs22.default.mkdirSync(binDir, { recursive: true });
|
|
38759
38807
|
cleanupStaleArtifacts(binDir);
|
|
38760
|
-
const stableBin =
|
|
38761
|
-
if (
|
|
38808
|
+
const stableBin = import_node_path25.default.join(binDir, "frpc");
|
|
38809
|
+
if (import_node_fs22.default.existsSync(stableBin)) return stableBin;
|
|
38762
38810
|
const partialBin = `${stableBin}.partial`;
|
|
38763
|
-
const tarballPath =
|
|
38811
|
+
const tarballPath = import_node_path25.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
|
|
38764
38812
|
try {
|
|
38765
38813
|
const url = frpcDownloadUrl(version2, platform);
|
|
38766
38814
|
await downloadToFile(url, tarballPath, opts.fetchImpl);
|
|
@@ -38769,8 +38817,8 @@ async function ensureFrpcBinary(opts) {
|
|
|
38769
38817
|
} else {
|
|
38770
38818
|
await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
|
|
38771
38819
|
}
|
|
38772
|
-
|
|
38773
|
-
|
|
38820
|
+
import_node_fs22.default.chmodSync(partialBin, 493);
|
|
38821
|
+
import_node_fs22.default.renameSync(partialBin, stableBin);
|
|
38774
38822
|
} finally {
|
|
38775
38823
|
safeUnlink(tarballPath);
|
|
38776
38824
|
safeUnlink(partialBin);
|
|
@@ -38780,15 +38828,15 @@ async function ensureFrpcBinary(opts) {
|
|
|
38780
38828
|
function cleanupStaleArtifacts(binDir) {
|
|
38781
38829
|
let entries;
|
|
38782
38830
|
try {
|
|
38783
|
-
entries =
|
|
38831
|
+
entries = import_node_fs22.default.readdirSync(binDir);
|
|
38784
38832
|
} catch {
|
|
38785
38833
|
return;
|
|
38786
38834
|
}
|
|
38787
38835
|
for (const name of entries) {
|
|
38788
38836
|
if (name.endsWith(".partial") || name.startsWith("extract-")) {
|
|
38789
|
-
const full =
|
|
38837
|
+
const full = import_node_path25.default.join(binDir, name);
|
|
38790
38838
|
try {
|
|
38791
|
-
|
|
38839
|
+
import_node_fs22.default.rmSync(full, { recursive: true, force: true });
|
|
38792
38840
|
} catch {
|
|
38793
38841
|
}
|
|
38794
38842
|
}
|
|
@@ -38796,7 +38844,7 @@ function cleanupStaleArtifacts(binDir) {
|
|
|
38796
38844
|
}
|
|
38797
38845
|
function safeUnlink(p2) {
|
|
38798
38846
|
try {
|
|
38799
|
-
|
|
38847
|
+
import_node_fs22.default.unlinkSync(p2);
|
|
38800
38848
|
} catch {
|
|
38801
38849
|
}
|
|
38802
38850
|
}
|
|
@@ -38807,13 +38855,13 @@ async function downloadToFile(url, dest, fetchImpl) {
|
|
|
38807
38855
|
if (!res.ok || !res.body) {
|
|
38808
38856
|
throw new Error(`download failed: ${res.status} ${res.statusText}`);
|
|
38809
38857
|
}
|
|
38810
|
-
const out =
|
|
38858
|
+
const out = import_node_fs22.default.createWriteStream(dest);
|
|
38811
38859
|
const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
|
|
38812
38860
|
await (0, import_promises3.pipeline)(nodeStream, out);
|
|
38813
38861
|
}
|
|
38814
38862
|
async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
|
|
38815
|
-
const work =
|
|
38816
|
-
|
|
38863
|
+
const work = import_node_path25.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
|
|
38864
|
+
import_node_fs22.default.mkdirSync(work, { recursive: true });
|
|
38817
38865
|
try {
|
|
38818
38866
|
await new Promise((resolve6, reject) => {
|
|
38819
38867
|
const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
|
|
@@ -38821,32 +38869,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
|
|
|
38821
38869
|
proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
|
|
38822
38870
|
});
|
|
38823
38871
|
const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
|
|
38824
|
-
const src =
|
|
38825
|
-
if (!
|
|
38872
|
+
const src = import_node_path25.default.join(work, dirName, "frpc");
|
|
38873
|
+
if (!import_node_fs22.default.existsSync(src)) {
|
|
38826
38874
|
throw new Error(`frpc not found inside tarball at ${src}`);
|
|
38827
38875
|
}
|
|
38828
|
-
|
|
38876
|
+
import_node_fs22.default.copyFileSync(src, destBin);
|
|
38829
38877
|
} finally {
|
|
38830
|
-
|
|
38878
|
+
import_node_fs22.default.rmSync(work, { recursive: true, force: true });
|
|
38831
38879
|
}
|
|
38832
38880
|
}
|
|
38833
38881
|
|
|
38834
38882
|
// src/tunnel/frpc-process.ts
|
|
38835
|
-
var
|
|
38836
|
-
var
|
|
38883
|
+
var import_node_fs23 = __toESM(require("fs"), 1);
|
|
38884
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
38837
38885
|
var import_node_child_process4 = require("child_process");
|
|
38838
38886
|
function frpcPidFilePath(dataDir) {
|
|
38839
|
-
return
|
|
38887
|
+
return import_node_path26.default.join(dataDir, "frpc.pid");
|
|
38840
38888
|
}
|
|
38841
38889
|
function writeFrpcPid(dataDir, pid) {
|
|
38842
38890
|
try {
|
|
38843
|
-
|
|
38891
|
+
import_node_fs23.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
|
|
38844
38892
|
} catch {
|
|
38845
38893
|
}
|
|
38846
38894
|
}
|
|
38847
38895
|
function clearFrpcPid(dataDir) {
|
|
38848
38896
|
try {
|
|
38849
|
-
|
|
38897
|
+
import_node_fs23.default.unlinkSync(frpcPidFilePath(dataDir));
|
|
38850
38898
|
} catch {
|
|
38851
38899
|
}
|
|
38852
38900
|
}
|
|
@@ -38862,7 +38910,7 @@ function defaultIsPidAlive(pid) {
|
|
|
38862
38910
|
}
|
|
38863
38911
|
function defaultReadPidFile(file) {
|
|
38864
38912
|
try {
|
|
38865
|
-
return
|
|
38913
|
+
return import_node_fs23.default.readFileSync(file, "utf8");
|
|
38866
38914
|
} catch {
|
|
38867
38915
|
return null;
|
|
38868
38916
|
}
|
|
@@ -38878,7 +38926,7 @@ function defaultSleep(ms) {
|
|
|
38878
38926
|
}
|
|
38879
38927
|
async function killStaleFrpc(deps) {
|
|
38880
38928
|
const pidFile = frpcPidFilePath(deps.dataDir);
|
|
38881
|
-
const tomlPath =
|
|
38929
|
+
const tomlPath = import_node_path26.default.join(deps.dataDir, "frpc.toml");
|
|
38882
38930
|
const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
|
|
38883
38931
|
const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
|
|
38884
38932
|
const killPid = deps.killPidImpl ?? defaultKillPid;
|
|
@@ -38902,7 +38950,7 @@ async function killStaleFrpc(deps) {
|
|
|
38902
38950
|
}
|
|
38903
38951
|
if (victims.size === 0) {
|
|
38904
38952
|
try {
|
|
38905
|
-
|
|
38953
|
+
import_node_fs23.default.unlinkSync(pidFile);
|
|
38906
38954
|
} catch {
|
|
38907
38955
|
}
|
|
38908
38956
|
return;
|
|
@@ -38913,7 +38961,7 @@ async function killStaleFrpc(deps) {
|
|
|
38913
38961
|
}
|
|
38914
38962
|
await sleep(deps.reapWaitMs ?? 300);
|
|
38915
38963
|
try {
|
|
38916
|
-
|
|
38964
|
+
import_node_fs23.default.unlinkSync(pidFile);
|
|
38917
38965
|
} catch {
|
|
38918
38966
|
}
|
|
38919
38967
|
}
|
|
@@ -38950,7 +38998,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
38950
38998
|
var TunnelManager = class {
|
|
38951
38999
|
constructor(deps) {
|
|
38952
39000
|
this.deps = deps;
|
|
38953
|
-
this.store = deps.store ?? new TunnelStore(
|
|
39001
|
+
this.store = deps.store ?? new TunnelStore(import_node_path27.default.join(deps.dataDir, "tunnel.json"));
|
|
38954
39002
|
this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
|
|
38955
39003
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
|
|
38956
39004
|
}
|
|
@@ -39077,8 +39125,8 @@ var TunnelManager = class {
|
|
|
39077
39125
|
dataDir: this.deps.dataDir,
|
|
39078
39126
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
39079
39127
|
});
|
|
39080
|
-
const tomlPath =
|
|
39081
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
39128
|
+
const tomlPath = import_node_path27.default.join(this.deps.dataDir, "frpc.toml");
|
|
39129
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto8.default.randomBytes(3).toString("hex")}`;
|
|
39082
39130
|
const toml = buildFrpcToml({
|
|
39083
39131
|
serverAddr: t.frpsHost,
|
|
39084
39132
|
serverPort: t.frpsPort,
|
|
@@ -39088,12 +39136,12 @@ var TunnelManager = class {
|
|
|
39088
39136
|
localPort,
|
|
39089
39137
|
logLevel: "info"
|
|
39090
39138
|
});
|
|
39091
|
-
await
|
|
39139
|
+
await import_node_fs24.default.promises.writeFile(tomlPath, toml, { mode: 384 });
|
|
39092
39140
|
const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
|
|
39093
39141
|
stdio: ["ignore", "pipe", "pipe"]
|
|
39094
39142
|
});
|
|
39095
|
-
const logFilePath =
|
|
39096
|
-
const logStream =
|
|
39143
|
+
const logFilePath = import_node_path27.default.join(this.deps.dataDir, "frpc.log");
|
|
39144
|
+
const logStream = import_node_fs24.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
|
|
39097
39145
|
logStream.on("error", () => {
|
|
39098
39146
|
});
|
|
39099
39147
|
const tee = (chunk) => {
|
|
@@ -39176,31 +39224,31 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
39176
39224
|
|
|
39177
39225
|
// src/tunnel/device-key.ts
|
|
39178
39226
|
var import_node_os12 = __toESM(require("os"), 1);
|
|
39179
|
-
var
|
|
39180
|
-
var
|
|
39227
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
39228
|
+
var import_node_crypto9 = __toESM(require("crypto"), 1);
|
|
39181
39229
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
39182
39230
|
function deriveStableDeviceKey(opts = {}) {
|
|
39183
39231
|
const hostname = opts.hostname ?? import_node_os12.default.hostname();
|
|
39184
39232
|
const uid = opts.uid ?? (typeof import_node_os12.default.userInfo === "function" ? import_node_os12.default.userInfo().uid : 0);
|
|
39185
39233
|
const home = opts.home ?? import_node_os12.default.homedir();
|
|
39186
|
-
const defaultDataDir =
|
|
39187
|
-
const normalizedDataDir = opts.dataDir ?
|
|
39234
|
+
const defaultDataDir = import_node_path28.default.resolve(import_node_path28.default.join(home, ".clawd"));
|
|
39235
|
+
const normalizedDataDir = opts.dataDir ? import_node_path28.default.resolve(opts.dataDir) : null;
|
|
39188
39236
|
const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
|
|
39189
39237
|
const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
|
|
39190
|
-
return
|
|
39238
|
+
return import_node_crypto9.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
39191
39239
|
}
|
|
39192
39240
|
|
|
39193
39241
|
// src/auth-store.ts
|
|
39194
|
-
var
|
|
39195
|
-
var
|
|
39196
|
-
var
|
|
39242
|
+
var import_node_fs25 = __toESM(require("fs"), 1);
|
|
39243
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
39244
|
+
var import_node_crypto10 = __toESM(require("crypto"), 1);
|
|
39197
39245
|
var AUTH_FILE_NAME = "auth.json";
|
|
39198
39246
|
function authFilePath(dataDir) {
|
|
39199
|
-
return
|
|
39247
|
+
return import_node_path29.default.join(dataDir, AUTH_FILE_NAME);
|
|
39200
39248
|
}
|
|
39201
39249
|
function loadOrCreateAuthFile(opts) {
|
|
39202
39250
|
const file = authFilePath(opts.dataDir);
|
|
39203
|
-
const generate = opts.generate ??
|
|
39251
|
+
const generate = opts.generate ?? defaultGenerateToken2;
|
|
39204
39252
|
const genId = opts.genOwnerPrincipalId ?? defaultGenerateOwnerPrincipalId;
|
|
39205
39253
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
39206
39254
|
const existing = readAuthFile(file);
|
|
@@ -39220,15 +39268,15 @@ function loadOrCreateAuthFile(opts) {
|
|
|
39220
39268
|
writeAuthFile(file, next);
|
|
39221
39269
|
return next;
|
|
39222
39270
|
}
|
|
39223
|
-
function
|
|
39224
|
-
return
|
|
39271
|
+
function defaultGenerateToken2() {
|
|
39272
|
+
return import_node_crypto10.default.randomBytes(32).toString("base64url");
|
|
39225
39273
|
}
|
|
39226
39274
|
function defaultGenerateOwnerPrincipalId() {
|
|
39227
|
-
return `owner-${
|
|
39275
|
+
return `owner-${import_node_crypto10.default.randomUUID()}`;
|
|
39228
39276
|
}
|
|
39229
39277
|
function readAuthFile(file) {
|
|
39230
39278
|
try {
|
|
39231
|
-
const raw =
|
|
39279
|
+
const raw = import_node_fs25.default.readFileSync(file, "utf8");
|
|
39232
39280
|
const parsed = JSON.parse(raw);
|
|
39233
39281
|
if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
|
|
39234
39282
|
return null;
|
|
@@ -39246,25 +39294,25 @@ function readAuthFile(file) {
|
|
|
39246
39294
|
}
|
|
39247
39295
|
}
|
|
39248
39296
|
function writeAuthFile(file, content) {
|
|
39249
|
-
|
|
39250
|
-
|
|
39297
|
+
import_node_fs25.default.mkdirSync(import_node_path29.default.dirname(file), { recursive: true });
|
|
39298
|
+
import_node_fs25.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
39251
39299
|
try {
|
|
39252
|
-
|
|
39300
|
+
import_node_fs25.default.chmodSync(file, 384);
|
|
39253
39301
|
} catch {
|
|
39254
39302
|
}
|
|
39255
39303
|
}
|
|
39256
39304
|
|
|
39257
39305
|
// src/owner-profile.ts
|
|
39258
|
-
var
|
|
39306
|
+
var import_node_fs26 = __toESM(require("fs"), 1);
|
|
39259
39307
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
39260
|
-
var
|
|
39308
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
39261
39309
|
var PROFILE_FILENAME = "profile.json";
|
|
39262
39310
|
function loadOwnerDisplayName(dataDir) {
|
|
39263
39311
|
const fallback = import_node_os13.default.userInfo().username;
|
|
39264
|
-
const profilePath =
|
|
39312
|
+
const profilePath = import_node_path30.default.join(dataDir, PROFILE_FILENAME);
|
|
39265
39313
|
let raw;
|
|
39266
39314
|
try {
|
|
39267
|
-
raw =
|
|
39315
|
+
raw = import_node_fs26.default.readFileSync(profilePath, "utf8");
|
|
39268
39316
|
} catch {
|
|
39269
39317
|
return fallback;
|
|
39270
39318
|
}
|
|
@@ -39286,259 +39334,6 @@ function loadOwnerDisplayName(dataDir) {
|
|
|
39286
39334
|
return displayName;
|
|
39287
39335
|
}
|
|
39288
39336
|
|
|
39289
|
-
// src/feishu-auth/owner-identity-store.ts
|
|
39290
|
-
var import_node_fs26 = __toESM(require("fs"), 1);
|
|
39291
|
-
var import_node_path30 = __toESM(require("path"), 1);
|
|
39292
|
-
var OWNER_IDENTITY_FILE_NAME = "owner-identity.json";
|
|
39293
|
-
var OwnerIdentityStore = class {
|
|
39294
|
-
file;
|
|
39295
|
-
constructor(dataDir) {
|
|
39296
|
-
this.file = import_node_path30.default.join(dataDir, OWNER_IDENTITY_FILE_NAME);
|
|
39297
|
-
}
|
|
39298
|
-
read() {
|
|
39299
|
-
let raw;
|
|
39300
|
-
try {
|
|
39301
|
-
raw = import_node_fs26.default.readFileSync(this.file, "utf8");
|
|
39302
|
-
} catch {
|
|
39303
|
-
return null;
|
|
39304
|
-
}
|
|
39305
|
-
let parsed;
|
|
39306
|
-
try {
|
|
39307
|
-
parsed = JSON.parse(raw);
|
|
39308
|
-
} catch {
|
|
39309
|
-
return null;
|
|
39310
|
-
}
|
|
39311
|
-
const r = parsed;
|
|
39312
|
-
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) {
|
|
39313
|
-
return null;
|
|
39314
|
-
}
|
|
39315
|
-
return {
|
|
39316
|
-
identity: {
|
|
39317
|
-
unionId: r.identity.unionId,
|
|
39318
|
-
displayName: r.identity.displayName,
|
|
39319
|
-
...typeof r.identity.avatarUrl === "string" ? { avatarUrl: r.identity.avatarUrl } : {}
|
|
39320
|
-
},
|
|
39321
|
-
ttcToken: r.ttcToken,
|
|
39322
|
-
ttcTokenExpiresAt: typeof r.ttcTokenExpiresAt === "number" ? r.ttcTokenExpiresAt : null,
|
|
39323
|
-
loginAt: typeof r.loginAt === "string" ? r.loginAt : (/* @__PURE__ */ new Date(0)).toISOString()
|
|
39324
|
-
};
|
|
39325
|
-
}
|
|
39326
|
-
write(record) {
|
|
39327
|
-
import_node_fs26.default.mkdirSync(import_node_path30.default.dirname(this.file), { recursive: true });
|
|
39328
|
-
import_node_fs26.default.writeFileSync(this.file, JSON.stringify(record, null, 2), { mode: 384 });
|
|
39329
|
-
try {
|
|
39330
|
-
import_node_fs26.default.chmodSync(this.file, 384);
|
|
39331
|
-
} catch {
|
|
39332
|
-
}
|
|
39333
|
-
}
|
|
39334
|
-
clear() {
|
|
39335
|
-
try {
|
|
39336
|
-
import_node_fs26.default.unlinkSync(this.file);
|
|
39337
|
-
} catch (err) {
|
|
39338
|
-
const code = err?.code;
|
|
39339
|
-
if (code !== "ENOENT") throw err;
|
|
39340
|
-
}
|
|
39341
|
-
}
|
|
39342
|
-
};
|
|
39343
|
-
|
|
39344
|
-
// src/feishu-auth/login-flow.ts
|
|
39345
|
-
var import_node_crypto10 = __toESM(require("crypto"), 1);
|
|
39346
|
-
var STATE_TTL_MS = 5 * 60 * 1e3;
|
|
39347
|
-
var LoginFlow = class {
|
|
39348
|
-
constructor(deps) {
|
|
39349
|
-
this.deps = deps;
|
|
39350
|
-
}
|
|
39351
|
-
deps;
|
|
39352
|
-
pendingStates = /* @__PURE__ */ new Map();
|
|
39353
|
-
start() {
|
|
39354
|
-
const state = import_node_crypto10.default.randomBytes(16).toString("base64url");
|
|
39355
|
-
const now = (this.deps.now ?? Date.now)();
|
|
39356
|
-
this.pendingStates.set(state, now);
|
|
39357
|
-
this.gcExpired(now);
|
|
39358
|
-
const url = new URL("/auth/authorize", this.deps.ttcAuthBase);
|
|
39359
|
-
url.searchParams.set("callback_url", this.deps.getCallbackUrl());
|
|
39360
|
-
url.searchParams.set("state", state);
|
|
39361
|
-
url.searchParams.set("auto", "1");
|
|
39362
|
-
return { authUrl: url.toString(), state };
|
|
39363
|
-
}
|
|
39364
|
-
async handleCallback(params) {
|
|
39365
|
-
const now = (this.deps.now ?? Date.now)();
|
|
39366
|
-
const issuedAt = this.pendingStates.get(params.state);
|
|
39367
|
-
if (issuedAt === void 0) {
|
|
39368
|
-
return { ok: false, reason: "state mismatch" };
|
|
39369
|
-
}
|
|
39370
|
-
this.pendingStates.delete(params.state);
|
|
39371
|
-
if (now - issuedAt > STATE_TTL_MS) {
|
|
39372
|
-
return { ok: false, reason: "state expired" };
|
|
39373
|
-
}
|
|
39374
|
-
if (!params.token) {
|
|
39375
|
-
return { ok: false, reason: "missing token" };
|
|
39376
|
-
}
|
|
39377
|
-
let identity;
|
|
39378
|
-
try {
|
|
39379
|
-
identity = await this.deps.fetchUserInfo(params.token);
|
|
39380
|
-
} catch (err) {
|
|
39381
|
-
return { ok: false, reason: `user_info failed: ${err.message}` };
|
|
39382
|
-
}
|
|
39383
|
-
const expiresAtNum = params.expiresAt ? Number.parseInt(params.expiresAt, 10) : NaN;
|
|
39384
|
-
this.deps.store.write({
|
|
39385
|
-
identity,
|
|
39386
|
-
ttcToken: params.token,
|
|
39387
|
-
ttcTokenExpiresAt: Number.isFinite(expiresAtNum) ? expiresAtNum : null,
|
|
39388
|
-
loginAt: new Date(now).toISOString()
|
|
39389
|
-
});
|
|
39390
|
-
return { ok: true, identity };
|
|
39391
|
-
}
|
|
39392
|
-
gcExpired(now) {
|
|
39393
|
-
for (const [state, issuedAt] of this.pendingStates) {
|
|
39394
|
-
if (now - issuedAt > STATE_TTL_MS) this.pendingStates.delete(state);
|
|
39395
|
-
}
|
|
39396
|
-
}
|
|
39397
|
-
};
|
|
39398
|
-
|
|
39399
|
-
// src/feishu-auth/ttc-client.ts
|
|
39400
|
-
var TTC_HOSTS = {
|
|
39401
|
-
auth: "https://app.ttcadvisory.com",
|
|
39402
|
-
api: "https://api.ttcadvisory.com"
|
|
39403
|
-
};
|
|
39404
|
-
var TtcUserInfoError = class extends Error {
|
|
39405
|
-
constructor(code, message) {
|
|
39406
|
-
super(message);
|
|
39407
|
-
this.code = code;
|
|
39408
|
-
this.name = "TtcUserInfoError";
|
|
39409
|
-
}
|
|
39410
|
-
code;
|
|
39411
|
-
};
|
|
39412
|
-
async function fetchTtcUserInfo(opts) {
|
|
39413
|
-
const doFetch = opts.fetchImpl ?? fetch;
|
|
39414
|
-
let res;
|
|
39415
|
-
try {
|
|
39416
|
-
res = await doFetch(`${opts.apiBase}/api/user_service/v1/login/user`, {
|
|
39417
|
-
headers: { Authorization: `Bearer ${opts.token}` }
|
|
39418
|
-
});
|
|
39419
|
-
} catch (err) {
|
|
39420
|
-
throw new TtcUserInfoError("NETWORK", `TTC user_info request failed: ${err.message}`);
|
|
39421
|
-
}
|
|
39422
|
-
if (!res.ok) {
|
|
39423
|
-
if (res.status === 401) {
|
|
39424
|
-
throw new TtcUserInfoError("UNAUTHORIZED", "TTC token invalid or expired");
|
|
39425
|
-
}
|
|
39426
|
-
throw new TtcUserInfoError("API_ERROR", `TTC user_info HTTP ${res.status}`);
|
|
39427
|
-
}
|
|
39428
|
-
const body = await res.json();
|
|
39429
|
-
if (body.code !== 0) {
|
|
39430
|
-
throw new TtcUserInfoError("API_ERROR", `TTC user_info code=${body.code}: ${body.message ?? ""}`);
|
|
39431
|
-
}
|
|
39432
|
-
const d = body.data;
|
|
39433
|
-
if (!d || typeof d.union_id !== "string" || d.union_id.length === 0 || typeof d.name !== "string" || d.name.length === 0) {
|
|
39434
|
-
throw new TtcUserInfoError("BAD_RESPONSE", "TTC user_info missing union_id or name");
|
|
39435
|
-
}
|
|
39436
|
-
return {
|
|
39437
|
-
unionId: d.union_id,
|
|
39438
|
-
displayName: d.name,
|
|
39439
|
-
...typeof d.avatar_url === "string" && d.avatar_url.length > 0 ? { avatarUrl: d.avatar_url } : {}
|
|
39440
|
-
};
|
|
39441
|
-
}
|
|
39442
|
-
|
|
39443
|
-
// src/feishu-auth/central-client.ts
|
|
39444
|
-
var CentralClientError = class extends Error {
|
|
39445
|
-
constructor(code, message, cause) {
|
|
39446
|
-
super(message);
|
|
39447
|
-
this.code = code;
|
|
39448
|
-
this.cause = cause;
|
|
39449
|
-
this.name = "CentralClientError";
|
|
39450
|
-
}
|
|
39451
|
-
code;
|
|
39452
|
-
cause;
|
|
39453
|
-
};
|
|
39454
|
-
async function centralRequest(opts, path59, init) {
|
|
39455
|
-
const f = opts.fetchImpl ?? globalThis.fetch;
|
|
39456
|
-
const url = `${opts.api.replace(/\/+$/, "")}${path59}`;
|
|
39457
|
-
const ctrl = new AbortController();
|
|
39458
|
-
const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs ?? 15e3);
|
|
39459
|
-
let res;
|
|
39460
|
-
try {
|
|
39461
|
-
res = await f(url, {
|
|
39462
|
-
method: init.method,
|
|
39463
|
-
headers: {
|
|
39464
|
-
"content-type": "application/json",
|
|
39465
|
-
...init.bearer ? { authorization: `Bearer ${init.bearer}` } : {}
|
|
39466
|
-
},
|
|
39467
|
-
...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {},
|
|
39468
|
-
signal: ctrl.signal
|
|
39469
|
-
});
|
|
39470
|
-
} catch (err) {
|
|
39471
|
-
clearTimeout(timer);
|
|
39472
|
-
throw new CentralClientError("NETWORK", `central server request failed: ${err.message}`, err);
|
|
39473
|
-
}
|
|
39474
|
-
clearTimeout(timer);
|
|
39475
|
-
if (res.status === 401) {
|
|
39476
|
-
throw new CentralClientError("UNAUTHORIZED", "TTC JWT invalid or expired");
|
|
39477
|
-
}
|
|
39478
|
-
if (!res.ok) {
|
|
39479
|
-
throw new CentralClientError("API_ERROR", `central server HTTP ${res.status}`);
|
|
39480
|
-
}
|
|
39481
|
-
try {
|
|
39482
|
-
return await res.json();
|
|
39483
|
-
} catch (err) {
|
|
39484
|
-
throw new CentralClientError("BAD_RESPONSE", "central server: invalid json response", err);
|
|
39485
|
-
}
|
|
39486
|
-
}
|
|
39487
|
-
async function centralExchange(opts) {
|
|
39488
|
-
const body = await centralRequest(opts, "/api/clawd-identity/exchange", {
|
|
39489
|
-
method: "POST",
|
|
39490
|
-
body: { ttcJwt: opts.ttcJwt, hubId: opts.hubId }
|
|
39491
|
-
});
|
|
39492
|
-
if (typeof body.token !== "string" || body.token.length === 0 || typeof body.unionId !== "string" || typeof body.displayName !== "string") {
|
|
39493
|
-
throw new CentralClientError("BAD_RESPONSE", "exchange: missing token/unionId/displayName");
|
|
39494
|
-
}
|
|
39495
|
-
const hubUrl = typeof body.hubUrl === "string" && body.hubUrl.length > 0 ? body.hubUrl : null;
|
|
39496
|
-
return { token: body.token, unionId: body.unionId, displayName: body.displayName, hubUrl };
|
|
39497
|
-
}
|
|
39498
|
-
async function centralUpsertHub(opts) {
|
|
39499
|
-
const body = await centralRequest(opts, "/api/clawd-identity/hubs/self", {
|
|
39500
|
-
method: "PUT",
|
|
39501
|
-
bearer: opts.ttcJwt,
|
|
39502
|
-
body: { url: opts.url, ...opts.name ? { name: opts.name } : {} }
|
|
39503
|
-
});
|
|
39504
|
-
if (body.ok !== true) {
|
|
39505
|
-
throw new CentralClientError("BAD_RESPONSE", "upsertHub: server did not ack ok");
|
|
39506
|
-
}
|
|
39507
|
-
return { ok: true };
|
|
39508
|
-
}
|
|
39509
|
-
async function centralIntrospect(opts) {
|
|
39510
|
-
const body = await centralRequest(opts, "/api/clawd-identity/introspect", {
|
|
39511
|
-
method: "POST",
|
|
39512
|
-
bearer: opts.hubTtcJwt,
|
|
39513
|
-
body: { token: opts.token }
|
|
39514
|
-
});
|
|
39515
|
-
if (body.active !== true) {
|
|
39516
|
-
return { active: false };
|
|
39517
|
-
}
|
|
39518
|
-
if (typeof body.unionId !== "string" || typeof body.displayName !== "string") {
|
|
39519
|
-
throw new CentralClientError("BAD_RESPONSE", "introspect: active but missing identity");
|
|
39520
|
-
}
|
|
39521
|
-
return { active: true, unionId: body.unionId, displayName: body.displayName };
|
|
39522
|
-
}
|
|
39523
|
-
async function centralListHubs(opts) {
|
|
39524
|
-
const body = await centralRequest(opts, "/api/clawd-identity/hubs", {
|
|
39525
|
-
method: "GET",
|
|
39526
|
-
bearer: opts.ttcJwt
|
|
39527
|
-
});
|
|
39528
|
-
if (!Array.isArray(body.hubs)) {
|
|
39529
|
-
throw new CentralClientError("BAD_RESPONSE", "hubs: missing hubs array");
|
|
39530
|
-
}
|
|
39531
|
-
const out = [];
|
|
39532
|
-
for (const raw of body.hubs) {
|
|
39533
|
-
const h = raw;
|
|
39534
|
-
if (typeof h.hubId !== "string" || typeof h.name !== "string" || typeof h.url !== "string") {
|
|
39535
|
-
throw new CentralClientError("BAD_RESPONSE", "hubs: malformed hub entry");
|
|
39536
|
-
}
|
|
39537
|
-
out.push({ hubId: h.hubId, name: h.name, url: h.url });
|
|
39538
|
-
}
|
|
39539
|
-
return out;
|
|
39540
|
-
}
|
|
39541
|
-
|
|
39542
39337
|
// src/index.ts
|
|
39543
39338
|
init_protocol();
|
|
39544
39339
|
|
|
@@ -39699,7 +39494,69 @@ function buildSessionHandlers(deps) {
|
|
|
39699
39494
|
const update = async (frame, _client, ctx) => {
|
|
39700
39495
|
const args = SessionUpdateArgs.parse(frame);
|
|
39701
39496
|
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
39497
|
+
const prevFile = manager.findOwnedSession(args.sessionId);
|
|
39498
|
+
const prevProject = prevFile?.appBuilderProject ?? null;
|
|
39499
|
+
const nextProject = args.patch.appBuilderProject ?? prevProject;
|
|
39702
39500
|
const { response, broadcast } = manager.update(args);
|
|
39501
|
+
if (args.patch.appBuilderProject !== void 0) {
|
|
39502
|
+
deps.logger?.info("session-update.appBuilderProject", {
|
|
39503
|
+
sessionId: args.sessionId,
|
|
39504
|
+
prevProject,
|
|
39505
|
+
nextProject,
|
|
39506
|
+
hasLifecycle: !!deps.devServerLifecycle,
|
|
39507
|
+
hasStore: !!deps.appBuilderStore
|
|
39508
|
+
});
|
|
39509
|
+
}
|
|
39510
|
+
if (deps.devServerLifecycle && deps.appBuilderStore && args.patch.appBuilderProject !== void 0) {
|
|
39511
|
+
const lifecycle = deps.devServerLifecycle;
|
|
39512
|
+
const projectStore = deps.appBuilderStore;
|
|
39513
|
+
const tunnelUrl = deps.getTunnelUrl?.() ?? null;
|
|
39514
|
+
try {
|
|
39515
|
+
if (prevProject && prevProject !== nextProject) {
|
|
39516
|
+
await lifecycle.stopForSession(args.sessionId);
|
|
39517
|
+
}
|
|
39518
|
+
if (nextProject && nextProject !== "") {
|
|
39519
|
+
const occupantSessionId = lifecycle.boundSessionId(nextProject);
|
|
39520
|
+
const alreadyRunningForThisSession = lifecycle.isRunning(nextProject) && occupantSessionId === args.sessionId;
|
|
39521
|
+
deps.logger?.info("session-update.maybe-start-dev-server", {
|
|
39522
|
+
sessionId: args.sessionId,
|
|
39523
|
+
projectName: nextProject,
|
|
39524
|
+
occupantSessionId,
|
|
39525
|
+
alreadyRunningForThisSession
|
|
39526
|
+
});
|
|
39527
|
+
if (!alreadyRunningForThisSession) {
|
|
39528
|
+
if (occupantSessionId && occupantSessionId !== args.sessionId) {
|
|
39529
|
+
await lifecycle.stopForProject(nextProject);
|
|
39530
|
+
}
|
|
39531
|
+
const projects = await projectStore.list();
|
|
39532
|
+
const target = projects.find((p2) => p2.name === nextProject);
|
|
39533
|
+
deps.logger?.info("session-update.start-dev-server", {
|
|
39534
|
+
sessionId: args.sessionId,
|
|
39535
|
+
projectName: nextProject,
|
|
39536
|
+
targetFound: !!target,
|
|
39537
|
+
projectCwd: target ? projectStore.projectDir(nextProject) : null,
|
|
39538
|
+
devCommand: target?.devCommand,
|
|
39539
|
+
tunnelUrl
|
|
39540
|
+
});
|
|
39541
|
+
if (target) {
|
|
39542
|
+
lifecycle.startForSession({
|
|
39543
|
+
sessionId: args.sessionId,
|
|
39544
|
+
projectName: nextProject,
|
|
39545
|
+
cwd: projectStore.projectDir(nextProject),
|
|
39546
|
+
port: target.port,
|
|
39547
|
+
tunnelHost: tunnelUrl ? new URL(tunnelUrl.replace(/^wss?:\/\//i, "https://")).host : null,
|
|
39548
|
+
devCommand: target.devCommand
|
|
39549
|
+
});
|
|
39550
|
+
}
|
|
39551
|
+
}
|
|
39552
|
+
}
|
|
39553
|
+
} catch (err) {
|
|
39554
|
+
deps.logger?.warn("session-update.lifecycle-failed", {
|
|
39555
|
+
sessionId: args.sessionId,
|
|
39556
|
+
error: err instanceof Error ? err.message : String(err)
|
|
39557
|
+
});
|
|
39558
|
+
}
|
|
39559
|
+
}
|
|
39703
39560
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
39704
39561
|
};
|
|
39705
39562
|
const del = async (frame, _client, ctx) => {
|
|
@@ -40328,7 +40185,7 @@ var DeleteArgsSchema = external_exports.object({
|
|
|
40328
40185
|
capabilityId: external_exports.string().min(1)
|
|
40329
40186
|
}).strict();
|
|
40330
40187
|
function buildCapabilityHandlers(deps) {
|
|
40331
|
-
const { manager } = deps;
|
|
40188
|
+
const { manager, getShareBaseUrl, getPersonalCapability } = deps;
|
|
40332
40189
|
const list = async () => {
|
|
40333
40190
|
return {
|
|
40334
40191
|
response: {
|
|
@@ -40354,9 +40211,21 @@ function buildCapabilityHandlers(deps) {
|
|
|
40354
40211
|
}
|
|
40355
40212
|
};
|
|
40356
40213
|
};
|
|
40214
|
+
const personalCapGet = async () => {
|
|
40215
|
+
const { token, capability } = getPersonalCapability();
|
|
40216
|
+
return {
|
|
40217
|
+
response: {
|
|
40218
|
+
type: "personal-cap:get:ok",
|
|
40219
|
+
token,
|
|
40220
|
+
capability: stripSecretHash(capability),
|
|
40221
|
+
shareBaseUrl: getShareBaseUrl()
|
|
40222
|
+
}
|
|
40223
|
+
};
|
|
40224
|
+
};
|
|
40357
40225
|
return {
|
|
40358
40226
|
"capability:list": list,
|
|
40359
|
-
"capability:delete": del
|
|
40227
|
+
"capability:delete": del,
|
|
40228
|
+
"personal-cap:get": personalCapGet
|
|
40360
40229
|
};
|
|
40361
40230
|
}
|
|
40362
40231
|
|
|
@@ -40464,7 +40333,16 @@ function ensureOwner(ctx) {
|
|
|
40464
40333
|
);
|
|
40465
40334
|
}
|
|
40466
40335
|
}
|
|
40336
|
+
function ensureGuestCtx(ctx) {
|
|
40337
|
+
if (!ctx || ctx.principal.kind !== "guest" || !ctx.capabilityId) {
|
|
40338
|
+
throw new ClawdError(
|
|
40339
|
+
ERROR_CODES.UNAUTHORIZED,
|
|
40340
|
+
"UNAUTHORIZED: received-capability:autoAttach requires guest cap ctx"
|
|
40341
|
+
);
|
|
40342
|
+
}
|
|
40343
|
+
}
|
|
40467
40344
|
function buildReceivedCapabilityHandlers(deps) {
|
|
40345
|
+
const now = deps.now ?? Date.now;
|
|
40468
40346
|
const list = async (_frame, _client, ctx) => {
|
|
40469
40347
|
ensureOwner(ctx);
|
|
40470
40348
|
return {
|
|
@@ -40474,6 +40352,47 @@ function buildReceivedCapabilityHandlers(deps) {
|
|
|
40474
40352
|
}
|
|
40475
40353
|
};
|
|
40476
40354
|
};
|
|
40355
|
+
const add = async (frame, _client, ctx) => {
|
|
40356
|
+
ensureOwner(ctx);
|
|
40357
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40358
|
+
const args = ReceivedCapabilityAddArgsSchema.parse(rest);
|
|
40359
|
+
let conn;
|
|
40360
|
+
try {
|
|
40361
|
+
conn = await deps.connectRemote({
|
|
40362
|
+
url: args.remoteUrl,
|
|
40363
|
+
token: args.token,
|
|
40364
|
+
selfPrincipalId: deps.selfPrincipalId(),
|
|
40365
|
+
selfDisplayName: deps.selfDisplayName()
|
|
40366
|
+
});
|
|
40367
|
+
} catch (e) {
|
|
40368
|
+
throw new ClawdError(
|
|
40369
|
+
ERROR_CODES.VALIDATION_ERROR,
|
|
40370
|
+
`INVITE_REMOTE_UNREACHABLE: ${e.message}`
|
|
40371
|
+
);
|
|
40372
|
+
}
|
|
40373
|
+
try {
|
|
40374
|
+
const wh = await conn.whoami();
|
|
40375
|
+
const rc = {
|
|
40376
|
+
ownerPrincipalId: wh.ownerId,
|
|
40377
|
+
ownerDisplayName: wh.displayName,
|
|
40378
|
+
remoteUrl: args.remoteUrl,
|
|
40379
|
+
capabilityId: wh.capabilityId,
|
|
40380
|
+
capabilityToken: args.token,
|
|
40381
|
+
grants: wh.grants,
|
|
40382
|
+
receivedAt: now()
|
|
40383
|
+
};
|
|
40384
|
+
deps.store.upsert(rc);
|
|
40385
|
+
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
40386
|
+
return {
|
|
40387
|
+
response: { type: "received-capability:add:ok", receivedCap: rc }
|
|
40388
|
+
};
|
|
40389
|
+
} finally {
|
|
40390
|
+
try {
|
|
40391
|
+
conn.close();
|
|
40392
|
+
} catch {
|
|
40393
|
+
}
|
|
40394
|
+
}
|
|
40395
|
+
};
|
|
40477
40396
|
const remove = async (frame, _client, ctx) => {
|
|
40478
40397
|
ensureOwner(ctx);
|
|
40479
40398
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
@@ -40490,9 +40409,30 @@ function buildReceivedCapabilityHandlers(deps) {
|
|
|
40490
40409
|
}
|
|
40491
40410
|
};
|
|
40492
40411
|
};
|
|
40412
|
+
const autoAttach = async (frame, _client, ctx) => {
|
|
40413
|
+
ensureGuestCtx(ctx);
|
|
40414
|
+
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40415
|
+
const args = ReceivedCapabilityAutoAttachArgsSchema.parse(rest);
|
|
40416
|
+
const rc = {
|
|
40417
|
+
ownerPrincipalId: args.ownerPrincipalId,
|
|
40418
|
+
ownerDisplayName: args.ownerDisplayName,
|
|
40419
|
+
remoteUrl: args.remoteUrl,
|
|
40420
|
+
capabilityId: args.capabilityId,
|
|
40421
|
+
capabilityToken: args.token,
|
|
40422
|
+
grants: args.grants,
|
|
40423
|
+
receivedAt: now()
|
|
40424
|
+
};
|
|
40425
|
+
deps.store.upsert(rc);
|
|
40426
|
+
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
40427
|
+
return {
|
|
40428
|
+
response: { type: "received-capability:autoAttach:ok" }
|
|
40429
|
+
};
|
|
40430
|
+
};
|
|
40493
40431
|
return {
|
|
40494
40432
|
"received-capability:list": list,
|
|
40495
|
-
"received-capability:
|
|
40433
|
+
"received-capability:add": add,
|
|
40434
|
+
"received-capability:remove": remove,
|
|
40435
|
+
"received-capability:autoAttach": autoAttach
|
|
40496
40436
|
};
|
|
40497
40437
|
}
|
|
40498
40438
|
|
|
@@ -40514,13 +40454,14 @@ function buildWhoamiHandler(deps) {
|
|
|
40514
40454
|
usedCount: 0
|
|
40515
40455
|
};
|
|
40516
40456
|
} else {
|
|
40517
|
-
|
|
40518
|
-
|
|
40519
|
-
|
|
40520
|
-
|
|
40521
|
-
|
|
40522
|
-
|
|
40523
|
-
}
|
|
40457
|
+
if (!ctx.capabilityId) {
|
|
40458
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "whoami: guest ctx without capabilityId");
|
|
40459
|
+
}
|
|
40460
|
+
const cap = deps.capabilityManager.findById(ctx.capabilityId);
|
|
40461
|
+
if (!cap) {
|
|
40462
|
+
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, `whoami: capability not found: ${ctx.capabilityId}`);
|
|
40463
|
+
}
|
|
40464
|
+
capability = stripSecretHash(cap);
|
|
40524
40465
|
}
|
|
40525
40466
|
const grantedPersonas = [];
|
|
40526
40467
|
const isGuest = ctx.principal.kind === "guest";
|
|
@@ -40551,117 +40492,6 @@ function buildWhoamiHandler(deps) {
|
|
|
40551
40492
|
};
|
|
40552
40493
|
}
|
|
40553
40494
|
|
|
40554
|
-
// src/handlers/feishu-auth.ts
|
|
40555
|
-
function buildFeishuAuthHandlers(deps) {
|
|
40556
|
-
const loginStart = async () => {
|
|
40557
|
-
const { authUrl, state } = deps.loginFlow.start();
|
|
40558
|
-
return {
|
|
40559
|
-
response: { type: "auth:login:start:ok", authUrl, state }
|
|
40560
|
-
};
|
|
40561
|
-
};
|
|
40562
|
-
const getIdentity = async () => {
|
|
40563
|
-
const record = deps.ownerIdentityStore.read();
|
|
40564
|
-
return {
|
|
40565
|
-
response: {
|
|
40566
|
-
type: "auth:getIdentity:ok",
|
|
40567
|
-
identity: record ? record.identity : null
|
|
40568
|
-
}
|
|
40569
|
-
};
|
|
40570
|
-
};
|
|
40571
|
-
const logout = async () => {
|
|
40572
|
-
deps.ownerIdentityStore.clear();
|
|
40573
|
-
return {
|
|
40574
|
-
response: { type: "auth:logout:ok" }
|
|
40575
|
-
};
|
|
40576
|
-
};
|
|
40577
|
-
return {
|
|
40578
|
-
"auth:login:start": loginStart,
|
|
40579
|
-
"auth:getIdentity": getIdentity,
|
|
40580
|
-
"auth:logout": logout
|
|
40581
|
-
};
|
|
40582
|
-
}
|
|
40583
|
-
|
|
40584
|
-
// src/handlers/hub.ts
|
|
40585
|
-
init_protocol();
|
|
40586
|
-
function ensureOwner2(ctx) {
|
|
40587
|
-
if (!ctx || ctx.principal.kind !== "owner") {
|
|
40588
|
-
throw new ClawdError(ERROR_CODES.UNAUTHORIZED, "UNAUTHORIZED: hub:* requires owner ctx");
|
|
40589
|
-
}
|
|
40590
|
-
}
|
|
40591
|
-
function buildHubHandlers(deps) {
|
|
40592
|
-
const now = deps.now ?? Date.now;
|
|
40593
|
-
const list = async (_frame, _client, ctx) => {
|
|
40594
|
-
ensureOwner2(ctx);
|
|
40595
|
-
const hubs = await deps.listHubs();
|
|
40596
|
-
return {
|
|
40597
|
-
response: { type: "hub:list:ok", hubs }
|
|
40598
|
-
};
|
|
40599
|
-
};
|
|
40600
|
-
const connect = async (frame, _client, ctx) => {
|
|
40601
|
-
ensureOwner2(ctx);
|
|
40602
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40603
|
-
const args = HubConnectArgsSchema.parse(rest);
|
|
40604
|
-
const exchanged = await deps.exchange(args.hubId);
|
|
40605
|
-
const url = args.url ?? exchanged.hubUrl;
|
|
40606
|
-
if (!url) {
|
|
40607
|
-
throw new ClawdError(
|
|
40608
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
40609
|
-
"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"
|
|
40610
|
-
);
|
|
40611
|
-
}
|
|
40612
|
-
let conn;
|
|
40613
|
-
try {
|
|
40614
|
-
conn = await deps.connectRemote({
|
|
40615
|
-
url,
|
|
40616
|
-
token: exchanged.token,
|
|
40617
|
-
selfPrincipalId: deps.selfPrincipalId(),
|
|
40618
|
-
selfDisplayName: deps.selfDisplayName(),
|
|
40619
|
-
// Phase 2 自动反向(spec 决策 #11):自报本机可达地址,让 hub 反向加我为联系人;
|
|
40620
|
-
// 本机无 tunnel → null → connectRemote 省略 selfUrl 字段(不造假数据)
|
|
40621
|
-
selfUrl: deps.selfUrl() ?? void 0
|
|
40622
|
-
});
|
|
40623
|
-
} catch (e) {
|
|
40624
|
-
throw new ClawdError(
|
|
40625
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
40626
|
-
`HUB_UNREACHABLE: ${e.message}`
|
|
40627
|
-
);
|
|
40628
|
-
}
|
|
40629
|
-
try {
|
|
40630
|
-
const wh = await conn.whoami();
|
|
40631
|
-
const rc = {
|
|
40632
|
-
ownerPrincipalId: wh.ownerId,
|
|
40633
|
-
ownerDisplayName: wh.displayName || args.name || args.hubId,
|
|
40634
|
-
remoteUrl: url,
|
|
40635
|
-
capabilityId: wh.capabilityId,
|
|
40636
|
-
// Phase 2:connectToken 存中心 server 签发的 connect token(断线重连复用,
|
|
40637
|
-
// 不需要重新换票——token 长期有效,撤销由中心 server introspect 兜底)
|
|
40638
|
-
connectToken: exchanged.token,
|
|
40639
|
-
grants: wh.grants,
|
|
40640
|
-
receivedAt: now()
|
|
40641
|
-
};
|
|
40642
|
-
deps.store.upsert(rc);
|
|
40643
|
-
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
40644
|
-
return {
|
|
40645
|
-
response: {
|
|
40646
|
-
type: "hub:connect:ok",
|
|
40647
|
-
hubId: wh.ownerId,
|
|
40648
|
-
name: rc.ownerDisplayName,
|
|
40649
|
-
url
|
|
40650
|
-
}
|
|
40651
|
-
};
|
|
40652
|
-
} finally {
|
|
40653
|
-
try {
|
|
40654
|
-
conn.close();
|
|
40655
|
-
} catch {
|
|
40656
|
-
}
|
|
40657
|
-
}
|
|
40658
|
-
};
|
|
40659
|
-
return {
|
|
40660
|
-
"hub:list": list,
|
|
40661
|
-
"hub:connect": connect
|
|
40662
|
-
};
|
|
40663
|
-
}
|
|
40664
|
-
|
|
40665
40495
|
// src/handlers/meta.ts
|
|
40666
40496
|
var import_node_os15 = __toESM(require("os"), 1);
|
|
40667
40497
|
init_protocol();
|
|
@@ -41622,6 +41452,62 @@ function buildExtensionHandlers(deps) {
|
|
|
41622
41452
|
};
|
|
41623
41453
|
}
|
|
41624
41454
|
|
|
41455
|
+
// src/handlers/app-builder.ts
|
|
41456
|
+
function buildAppBuilderHandlers(deps) {
|
|
41457
|
+
const { store, deleteSessionsByProject, devServerLifecycle } = deps;
|
|
41458
|
+
const listProjects = async () => {
|
|
41459
|
+
const projects = await store.list();
|
|
41460
|
+
return {
|
|
41461
|
+
response: {
|
|
41462
|
+
projects: projects.map((p2) => ({
|
|
41463
|
+
...p2,
|
|
41464
|
+
isRunning: devServerLifecycle.isRunning(p2.name),
|
|
41465
|
+
boundSessionId: devServerLifecycle.boundSessionId(p2.name)
|
|
41466
|
+
}))
|
|
41467
|
+
}
|
|
41468
|
+
};
|
|
41469
|
+
};
|
|
41470
|
+
const createProject = async (frame) => {
|
|
41471
|
+
const name = frame.name;
|
|
41472
|
+
if (typeof name !== "string") throw new Error("createProject: name must be string");
|
|
41473
|
+
const project = await store.create(name);
|
|
41474
|
+
return { response: { project } };
|
|
41475
|
+
};
|
|
41476
|
+
const deleteProject = async (frame) => {
|
|
41477
|
+
const name = frame.name;
|
|
41478
|
+
if (typeof name !== "string") throw new Error("deleteProject: name must be string");
|
|
41479
|
+
await devServerLifecycle.stopForProject(name);
|
|
41480
|
+
await deleteSessionsByProject(name);
|
|
41481
|
+
await store.delete(name);
|
|
41482
|
+
return { response: {} };
|
|
41483
|
+
};
|
|
41484
|
+
const updateProjectPort = async (frame) => {
|
|
41485
|
+
const f = frame;
|
|
41486
|
+
if (typeof f.name !== "string") throw new Error("updateProjectPort: name must be string");
|
|
41487
|
+
if (typeof f.newPort !== "number") throw new Error("updateProjectPort: newPort must be number");
|
|
41488
|
+
const entry = devServerLifecycle.boundEntry(f.name);
|
|
41489
|
+
await devServerLifecycle.stopForProject(f.name);
|
|
41490
|
+
const project = await store.updatePort(f.name, f.newPort);
|
|
41491
|
+
if (entry) {
|
|
41492
|
+
await devServerLifecycle.restartForProjectAt({
|
|
41493
|
+
projectName: f.name,
|
|
41494
|
+
newPort: f.newPort,
|
|
41495
|
+
sessionId: entry.sessionId,
|
|
41496
|
+
cwd: entry.cwd,
|
|
41497
|
+
tunnelHost: entry.tunnelHost,
|
|
41498
|
+
devCommand: entry.devCommand
|
|
41499
|
+
});
|
|
41500
|
+
}
|
|
41501
|
+
return { response: { project } };
|
|
41502
|
+
};
|
|
41503
|
+
return {
|
|
41504
|
+
"appBuilder:listProjects": listProjects,
|
|
41505
|
+
"appBuilder:createProject": createProject,
|
|
41506
|
+
"appBuilder:deleteProject": deleteProject,
|
|
41507
|
+
"appBuilder:updateProjectPort": updateProjectPort
|
|
41508
|
+
};
|
|
41509
|
+
}
|
|
41510
|
+
|
|
41625
41511
|
// src/extension/registry.ts
|
|
41626
41512
|
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
41627
41513
|
var import_node_path39 = __toESM(require("path"), 1);
|
|
@@ -41710,7 +41596,13 @@ async function uninstall(deps) {
|
|
|
41710
41596
|
// src/handlers/index.ts
|
|
41711
41597
|
function buildMethodHandlers(deps) {
|
|
41712
41598
|
return {
|
|
41713
|
-
...buildSessionHandlers(
|
|
41599
|
+
...buildSessionHandlers({
|
|
41600
|
+
...deps,
|
|
41601
|
+
devServerLifecycle: deps.devServerLifecycle,
|
|
41602
|
+
appBuilderStore: deps.appBuilderStore,
|
|
41603
|
+
getTunnelUrl: deps.getTunnelUrl,
|
|
41604
|
+
logger: deps.logger
|
|
41605
|
+
}),
|
|
41714
41606
|
...buildPermissionHandlers(deps),
|
|
41715
41607
|
...buildHistoryHandlers(deps),
|
|
41716
41608
|
...buildWorkspaceHandlers(deps),
|
|
@@ -41722,7 +41614,9 @@ function buildMethodHandlers(deps) {
|
|
|
41722
41614
|
personaRegistry: deps.personaRegistry
|
|
41723
41615
|
}),
|
|
41724
41616
|
...buildCapabilityHandlers({
|
|
41725
|
-
manager: deps.capabilityManager
|
|
41617
|
+
manager: deps.capabilityManager,
|
|
41618
|
+
getShareBaseUrl: deps.getShareBaseUrl,
|
|
41619
|
+
getPersonalCapability: deps.getPersonalCapability
|
|
41726
41620
|
}),
|
|
41727
41621
|
...buildInboxHandlers({
|
|
41728
41622
|
manager: deps.inboxManager,
|
|
@@ -41730,7 +41624,10 @@ function buildMethodHandlers(deps) {
|
|
|
41730
41624
|
}),
|
|
41731
41625
|
...buildReceivedCapabilityHandlers({
|
|
41732
41626
|
store: deps.receivedCapabilityStore,
|
|
41733
|
-
|
|
41627
|
+
connectRemote: deps.connectRemote,
|
|
41628
|
+
broadcast: deps.broadcastToOwners,
|
|
41629
|
+
selfPrincipalId: () => deps.ownerPrincipalId,
|
|
41630
|
+
selfDisplayName: () => deps.ownerDisplayName
|
|
41734
41631
|
}),
|
|
41735
41632
|
whoami: buildWhoamiHandler({
|
|
41736
41633
|
ownerDisplayName: deps.ownerDisplayName,
|
|
@@ -41738,8 +41635,6 @@ function buildMethodHandlers(deps) {
|
|
|
41738
41635
|
personaStore: deps.personaStore,
|
|
41739
41636
|
capabilityManager: deps.capabilityManager
|
|
41740
41637
|
}),
|
|
41741
|
-
...buildFeishuAuthHandlers(deps.feishuAuth),
|
|
41742
|
-
...buildHubHandlers(deps.feishuHub),
|
|
41743
41638
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {},
|
|
41744
41639
|
...buildExtensionHandlers({
|
|
41745
41640
|
loadAll,
|
|
@@ -41748,10 +41643,254 @@ function buildMethodHandlers(deps) {
|
|
|
41748
41643
|
root: extensionsRoot(),
|
|
41749
41644
|
publishedChannelStore: deps.publishedChannelStore,
|
|
41750
41645
|
bundleCache: deps.bundleCache
|
|
41646
|
+
}),
|
|
41647
|
+
...buildAppBuilderHandlers({
|
|
41648
|
+
store: deps.appBuilderStore,
|
|
41649
|
+
deleteSessionsByProject: deps.deleteSessionsByProject,
|
|
41650
|
+
devServerLifecycle: deps.devServerLifecycle
|
|
41751
41651
|
})
|
|
41752
41652
|
};
|
|
41753
41653
|
}
|
|
41754
41654
|
|
|
41655
|
+
// src/app-builder/project-store.ts
|
|
41656
|
+
var import_node_fs29 = require("fs");
|
|
41657
|
+
var import_node_path41 = require("path");
|
|
41658
|
+
init_protocol();
|
|
41659
|
+
var PROJECTS_DIR = "projects";
|
|
41660
|
+
var META_FILE = ".clawd-project.json";
|
|
41661
|
+
var ProjectStore = class {
|
|
41662
|
+
/** @param personaRoot persona 目录,例如 `~/.clawd/personas/persona-app-builder/` */
|
|
41663
|
+
constructor(personaRoot) {
|
|
41664
|
+
this.personaRoot = personaRoot;
|
|
41665
|
+
}
|
|
41666
|
+
personaRoot;
|
|
41667
|
+
/** projects/<name>/.clawd-project.json 路径 */
|
|
41668
|
+
metaPath(name) {
|
|
41669
|
+
return (0, import_node_path41.join)(this.projectsRoot(), name, META_FILE);
|
|
41670
|
+
}
|
|
41671
|
+
/** projects/<name>/ 目录路径(cwd 用) */
|
|
41672
|
+
projectDir(name) {
|
|
41673
|
+
return (0, import_node_path41.join)(this.projectsRoot(), name);
|
|
41674
|
+
}
|
|
41675
|
+
projectsRoot() {
|
|
41676
|
+
return (0, import_node_path41.join)(this.personaRoot, PROJECTS_DIR);
|
|
41677
|
+
}
|
|
41678
|
+
async list() {
|
|
41679
|
+
let entries;
|
|
41680
|
+
try {
|
|
41681
|
+
entries = await import_node_fs29.promises.readdir(this.projectsRoot());
|
|
41682
|
+
} catch (err) {
|
|
41683
|
+
if (err.code === "ENOENT") return [];
|
|
41684
|
+
throw err;
|
|
41685
|
+
}
|
|
41686
|
+
const out = [];
|
|
41687
|
+
for (const name of entries) {
|
|
41688
|
+
try {
|
|
41689
|
+
const raw = await import_node_fs29.promises.readFile(this.metaPath(name), "utf8");
|
|
41690
|
+
const json = JSON.parse(raw);
|
|
41691
|
+
let migrated = false;
|
|
41692
|
+
if (typeof json.devCommand !== "string" || json.devCommand.length === 0) {
|
|
41693
|
+
json.devCommand = DEFAULT_DEV_COMMAND;
|
|
41694
|
+
migrated = true;
|
|
41695
|
+
}
|
|
41696
|
+
const parsed = ProjectMetadataSchema.safeParse(json);
|
|
41697
|
+
if (parsed.success) {
|
|
41698
|
+
out.push(parsed.data);
|
|
41699
|
+
if (migrated) {
|
|
41700
|
+
void import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(parsed.data, null, 2) + "\n", "utf8").catch(() => {
|
|
41701
|
+
});
|
|
41702
|
+
}
|
|
41703
|
+
}
|
|
41704
|
+
} catch {
|
|
41705
|
+
}
|
|
41706
|
+
}
|
|
41707
|
+
return out.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
41708
|
+
}
|
|
41709
|
+
async create(name) {
|
|
41710
|
+
const existing = await this.list();
|
|
41711
|
+
if (existing.some((p2) => p2.name === name)) {
|
|
41712
|
+
throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
|
|
41713
|
+
}
|
|
41714
|
+
const usedPorts = new Set(existing.map((p2) => p2.port));
|
|
41715
|
+
let port = -1;
|
|
41716
|
+
for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) {
|
|
41717
|
+
if (!usedPorts.has(p2)) {
|
|
41718
|
+
port = p2;
|
|
41719
|
+
break;
|
|
41720
|
+
}
|
|
41721
|
+
}
|
|
41722
|
+
if (port < 0) {
|
|
41723
|
+
throw new Error(
|
|
41724
|
+
`port pool exhausted / \u7AEF\u53E3\u6C60\u5DF2\u6EE1\uFF08${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}\uFF09\uFF0C\u5148\u5220\u4E00\u4E2A project \u91CA\u653E\u7AEF\u53E3`
|
|
41725
|
+
);
|
|
41726
|
+
}
|
|
41727
|
+
const meta = {
|
|
41728
|
+
name,
|
|
41729
|
+
port,
|
|
41730
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41731
|
+
devCommand: DEFAULT_DEV_COMMAND
|
|
41732
|
+
};
|
|
41733
|
+
const validated = ProjectMetadataSchema.safeParse(meta);
|
|
41734
|
+
if (!validated.success) {
|
|
41735
|
+
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
41736
|
+
}
|
|
41737
|
+
const dir = this.projectDir(name);
|
|
41738
|
+
await import_node_fs29.promises.mkdir(dir, { recursive: true });
|
|
41739
|
+
await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
|
|
41740
|
+
return meta;
|
|
41741
|
+
}
|
|
41742
|
+
async delete(name) {
|
|
41743
|
+
const dir = this.projectDir(name);
|
|
41744
|
+
await import_node_fs29.promises.rm(dir, { recursive: true, force: true });
|
|
41745
|
+
}
|
|
41746
|
+
async updatePort(name, newPort) {
|
|
41747
|
+
if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
|
|
41748
|
+
throw new Error(
|
|
41749
|
+
`port range invalid: must be ${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}, got ${newPort}`
|
|
41750
|
+
);
|
|
41751
|
+
}
|
|
41752
|
+
const list = await this.list();
|
|
41753
|
+
const target = list.find((p2) => p2.name === name);
|
|
41754
|
+
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
41755
|
+
const conflict = list.find((p2) => p2.name !== name && p2.port === newPort);
|
|
41756
|
+
if (conflict) {
|
|
41757
|
+
throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
|
|
41758
|
+
}
|
|
41759
|
+
const updated = { ...target, port: newPort };
|
|
41760
|
+
await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
41761
|
+
return updated;
|
|
41762
|
+
}
|
|
41763
|
+
};
|
|
41764
|
+
|
|
41765
|
+
// src/app-builder/dev-server-supervisor.ts
|
|
41766
|
+
var import_node_child_process7 = require("child_process");
|
|
41767
|
+
var import_node_events2 = require("events");
|
|
41768
|
+
var DevServerSupervisor = class extends import_node_events2.EventEmitter {
|
|
41769
|
+
running = /* @__PURE__ */ new Map();
|
|
41770
|
+
// key: sessionId
|
|
41771
|
+
logger = { warn: () => {
|
|
41772
|
+
}, info: () => {
|
|
41773
|
+
} };
|
|
41774
|
+
/** daemon entry 启动后注入 logger,让 child 输出走 clawd.log */
|
|
41775
|
+
setLogger(logger) {
|
|
41776
|
+
this.logger = logger;
|
|
41777
|
+
}
|
|
41778
|
+
startForSession(args) {
|
|
41779
|
+
if (this.running.has(args.sessionId)) return;
|
|
41780
|
+
const augmentedPath = [
|
|
41781
|
+
process.env.PATH ?? "",
|
|
41782
|
+
"/opt/homebrew/bin",
|
|
41783
|
+
"/usr/local/bin",
|
|
41784
|
+
`${process.env.HOME}/.nvm/versions/node`,
|
|
41785
|
+
`${process.env.HOME}/.local/share/pnpm`
|
|
41786
|
+
].filter(Boolean).join(":");
|
|
41787
|
+
const env = {
|
|
41788
|
+
...process.env,
|
|
41789
|
+
PATH: augmentedPath,
|
|
41790
|
+
CLAWD_TUNNEL_HOST: args.tunnelHost ?? "",
|
|
41791
|
+
CLAWD_PREVIEW_PORT: String(args.port)
|
|
41792
|
+
};
|
|
41793
|
+
const cmd = args.devCommand.replace(/\$CLAWD_PREVIEW_PORT/g, String(args.port));
|
|
41794
|
+
this.logger.info("dev-server.start", {
|
|
41795
|
+
projectName: args.projectName,
|
|
41796
|
+
port: args.port,
|
|
41797
|
+
cwd: args.cwd,
|
|
41798
|
+
sessionId: args.sessionId,
|
|
41799
|
+
tunnelHost: args.tunnelHost,
|
|
41800
|
+
devCommand: cmd
|
|
41801
|
+
});
|
|
41802
|
+
const child = (0, import_node_child_process7.spawn)("sh", ["-c", cmd], { cwd: args.cwd, env, stdio: "pipe" });
|
|
41803
|
+
const entry = { ...args, process: child };
|
|
41804
|
+
this.running.set(args.sessionId, entry);
|
|
41805
|
+
child.stdout?.on("data", (chunk) => {
|
|
41806
|
+
this.logger.info("dev-server.stdout", {
|
|
41807
|
+
projectName: args.projectName,
|
|
41808
|
+
port: args.port,
|
|
41809
|
+
chunk: chunk.toString().trimEnd()
|
|
41810
|
+
});
|
|
41811
|
+
});
|
|
41812
|
+
child.stderr?.on("data", (chunk) => {
|
|
41813
|
+
this.logger.warn("dev-server.stderr", {
|
|
41814
|
+
projectName: args.projectName,
|
|
41815
|
+
port: args.port,
|
|
41816
|
+
chunk: chunk.toString().trimEnd()
|
|
41817
|
+
});
|
|
41818
|
+
});
|
|
41819
|
+
child.on("error", (err) => {
|
|
41820
|
+
this.running.delete(args.sessionId);
|
|
41821
|
+
this.logger.warn("dev-server.spawn-failed", {
|
|
41822
|
+
projectName: args.projectName,
|
|
41823
|
+
port: args.port,
|
|
41824
|
+
error: err.message,
|
|
41825
|
+
code: err.code
|
|
41826
|
+
});
|
|
41827
|
+
this.emit("devServer:failed", {
|
|
41828
|
+
sessionId: args.sessionId,
|
|
41829
|
+
projectName: args.projectName,
|
|
41830
|
+
port: args.port,
|
|
41831
|
+
exitCode: null
|
|
41832
|
+
});
|
|
41833
|
+
});
|
|
41834
|
+
child.on("exit", (code) => {
|
|
41835
|
+
this.running.delete(args.sessionId);
|
|
41836
|
+
this.logger.info("dev-server.exit", { projectName: args.projectName, port: args.port, code });
|
|
41837
|
+
if (code !== 0 && code !== null) {
|
|
41838
|
+
this.emit("devServer:failed", {
|
|
41839
|
+
sessionId: args.sessionId,
|
|
41840
|
+
projectName: args.projectName,
|
|
41841
|
+
port: args.port,
|
|
41842
|
+
exitCode: code
|
|
41843
|
+
});
|
|
41844
|
+
}
|
|
41845
|
+
});
|
|
41846
|
+
}
|
|
41847
|
+
stopForSession(sessionId) {
|
|
41848
|
+
const entry = this.running.get(sessionId);
|
|
41849
|
+
if (!entry) return Promise.resolve();
|
|
41850
|
+
return new Promise((resolve6) => {
|
|
41851
|
+
entry.process.once("exit", () => {
|
|
41852
|
+
this.running.delete(sessionId);
|
|
41853
|
+
resolve6();
|
|
41854
|
+
});
|
|
41855
|
+
entry.process.kill("SIGTERM");
|
|
41856
|
+
});
|
|
41857
|
+
}
|
|
41858
|
+
async stopForProject(projectName) {
|
|
41859
|
+
const entry = [...this.running.values()].find((e) => e.projectName === projectName);
|
|
41860
|
+
if (!entry) return;
|
|
41861
|
+
await this.stopForSession(entry.sessionId);
|
|
41862
|
+
}
|
|
41863
|
+
async restartForProjectAt(args) {
|
|
41864
|
+
await this.stopForSession(args.sessionId);
|
|
41865
|
+
this.startForSession({
|
|
41866
|
+
sessionId: args.sessionId,
|
|
41867
|
+
projectName: args.projectName,
|
|
41868
|
+
port: args.newPort,
|
|
41869
|
+
cwd: args.cwd,
|
|
41870
|
+
tunnelHost: args.tunnelHost,
|
|
41871
|
+
devCommand: args.devCommand
|
|
41872
|
+
});
|
|
41873
|
+
}
|
|
41874
|
+
isRunning(projectName) {
|
|
41875
|
+
return [...this.running.values()].some((e) => e.projectName === projectName);
|
|
41876
|
+
}
|
|
41877
|
+
boundSessionId(projectName) {
|
|
41878
|
+
const entry = [...this.running.values()].find((e) => e.projectName === projectName);
|
|
41879
|
+
return entry?.sessionId ?? null;
|
|
41880
|
+
}
|
|
41881
|
+
boundEntry(projectName) {
|
|
41882
|
+
const entry = [...this.running.values()].find((e) => e.projectName === projectName);
|
|
41883
|
+
if (!entry) return null;
|
|
41884
|
+
return {
|
|
41885
|
+
sessionId: entry.sessionId,
|
|
41886
|
+
cwd: entry.cwd,
|
|
41887
|
+
port: entry.port,
|
|
41888
|
+
tunnelHost: entry.tunnelHost,
|
|
41889
|
+
devCommand: entry.devCommand
|
|
41890
|
+
};
|
|
41891
|
+
}
|
|
41892
|
+
};
|
|
41893
|
+
|
|
41755
41894
|
// src/handlers/method-grants.ts
|
|
41756
41895
|
var ADMIN_ANY = {
|
|
41757
41896
|
kind: "fixed",
|
|
@@ -41768,22 +41907,23 @@ var METHOD_GRANT_MAP = {
|
|
|
41768
41907
|
// "查 capabilityManager 拿 caller 的 guest capability"。
|
|
41769
41908
|
"whoami": { kind: "public" },
|
|
41770
41909
|
// ---- capability platform(admin-only) ----
|
|
41771
|
-
//
|
|
41772
|
-
//
|
|
41773
|
-
// 撤销级联 + 清旧 per-peer cap。
|
|
41910
|
+
// global personal token (2026-05-25): capability:issue / bindPeer 已下线;
|
|
41911
|
+
// personal capability 由 daemon 启动时自动创建,personal-cap:get 是 owner-only 拿 token + shareBaseUrl
|
|
41774
41912
|
"capability:list": ADMIN_ANY,
|
|
41775
41913
|
"capability:delete": ADMIN_ANY,
|
|
41914
|
+
"personal-cap:get": ADMIN_ANY,
|
|
41776
41915
|
// ---- inbox: channel-based IM (capability platform v3) ----
|
|
41777
41916
|
// 三条都 'public' — capability 本身就是授权凭证. handler 内额外校验
|
|
41778
41917
|
// guest ctx.capabilityId === args.capabilityId, 防 guest 越权操作别的 channel.
|
|
41779
41918
|
"inbox:list": { kind: "public" },
|
|
41780
41919
|
"inbox:markRead": { kind: "public" },
|
|
41781
41920
|
"inbox:postMessage": { kind: "public" },
|
|
41782
|
-
// ---- received-capability:* (
|
|
41783
|
-
//
|
|
41784
|
-
// (hub:connect 落同一 store + 自动反向替代)。list / remove 保留作联系人读 / 删。
|
|
41921
|
+
// ---- received-capability:* (bidirectional cap 视角 B 2026-05-23) ----
|
|
41922
|
+
// owner-only: list / add / remove;guest-only: autoAttach (capability 本身是凭证)
|
|
41785
41923
|
"received-capability:list": ADMIN_ANY,
|
|
41924
|
+
"received-capability:add": ADMIN_ANY,
|
|
41786
41925
|
"received-capability:remove": ADMIN_ANY,
|
|
41926
|
+
"received-capability:autoAttach": { kind: "public" },
|
|
41787
41927
|
// ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
|
|
41788
41928
|
// dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
|
|
41789
41929
|
// owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
|
|
@@ -41881,15 +42021,14 @@ var METHOD_GRANT_MAP = {
|
|
|
41881
42021
|
// guest-self:guest 在自己 daemon 上触发安装与更新(无持久化 subscription 概念)
|
|
41882
42022
|
"extension.install-from-channel": ADMIN_ANY,
|
|
41883
42023
|
"extension.update-from-channel": ADMIN_ANY,
|
|
41884
|
-
// ----
|
|
41885
|
-
//
|
|
41886
|
-
|
|
41887
|
-
|
|
41888
|
-
"
|
|
41889
|
-
|
|
41890
|
-
|
|
41891
|
-
"
|
|
41892
|
-
"hub:connect": ADMIN_ANY
|
|
42024
|
+
// ---- app-builder Project picker(spec 2026-06-01-app-builder-project-picker-design) ----
|
|
42025
|
+
// owner-only:picker UI 给 owner 管理本机 build 中的 project(fs + 端口分配)。
|
|
42026
|
+
// 即使 persona-app-builder 是 PreinstalledPersona,project 管理也属本机 owner 行为,
|
|
42027
|
+
// 不暴露给 guest(远端接入者)。
|
|
42028
|
+
"appBuilder:listProjects": ADMIN_ANY,
|
|
42029
|
+
"appBuilder:createProject": ADMIN_ANY,
|
|
42030
|
+
"appBuilder:deleteProject": ADMIN_ANY,
|
|
42031
|
+
"appBuilder:updateProjectPort": ADMIN_ANY
|
|
41893
42032
|
};
|
|
41894
42033
|
function computeGrantForFrame(method, frame) {
|
|
41895
42034
|
const rule = METHOD_GRANT_MAP[method];
|
|
@@ -41905,12 +42044,12 @@ function computeGrantForFrame(method, frame) {
|
|
|
41905
42044
|
}
|
|
41906
42045
|
|
|
41907
42046
|
// src/extension/runtime.ts
|
|
41908
|
-
var
|
|
41909
|
-
var
|
|
42047
|
+
var import_node_child_process8 = require("child_process");
|
|
42048
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
41910
42049
|
var import_promises10 = require("timers/promises");
|
|
41911
42050
|
|
|
41912
42051
|
// src/extension/port-allocator.ts
|
|
41913
|
-
var
|
|
42052
|
+
var import_node_net2 = __toESM(require("net"), 1);
|
|
41914
42053
|
var PortExhaustedError = class extends Error {
|
|
41915
42054
|
constructor(min, max) {
|
|
41916
42055
|
super(`no free port in [${min},${max}]`);
|
|
@@ -41923,7 +42062,7 @@ var PortExhaustedError = class extends Error {
|
|
|
41923
42062
|
};
|
|
41924
42063
|
function probe(port) {
|
|
41925
42064
|
return new Promise((resolve6) => {
|
|
41926
|
-
const srv =
|
|
42065
|
+
const srv = import_node_net2.default.createServer();
|
|
41927
42066
|
srv.once("error", () => resolve6(false));
|
|
41928
42067
|
srv.once("listening", () => {
|
|
41929
42068
|
srv.close(() => resolve6(true));
|
|
@@ -42007,13 +42146,13 @@ var Runtime = class {
|
|
|
42007
42146
|
/\$CLAWOS_EXT_PORT/g,
|
|
42008
42147
|
String(port)
|
|
42009
42148
|
);
|
|
42010
|
-
const dir =
|
|
42149
|
+
const dir = import_node_path42.default.join(this.root, extId);
|
|
42011
42150
|
const env = {
|
|
42012
42151
|
...process.env,
|
|
42013
42152
|
CLAWOS_EXT_PORT: String(port),
|
|
42014
42153
|
CLAWOS_EXT_ID: extId
|
|
42015
42154
|
};
|
|
42016
|
-
const child = (0,
|
|
42155
|
+
const child = (0, import_node_child_process8.spawn)("sh", ["-c", cmd], {
|
|
42017
42156
|
cwd: dir,
|
|
42018
42157
|
env,
|
|
42019
42158
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -42119,7 +42258,7 @@ ${handle.stderrTail}`
|
|
|
42119
42258
|
|
|
42120
42259
|
// src/extension/published-channels.ts
|
|
42121
42260
|
var import_promises11 = __toESM(require("fs/promises"), 1);
|
|
42122
|
-
var
|
|
42261
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
42123
42262
|
init_zod();
|
|
42124
42263
|
var PublishedChannelsError = class extends Error {
|
|
42125
42264
|
constructor(code, message) {
|
|
@@ -42218,7 +42357,7 @@ var PublishedChannelStore = class {
|
|
|
42218
42357
|
)
|
|
42219
42358
|
};
|
|
42220
42359
|
const tmp = `${this.filePath}.tmp`;
|
|
42221
|
-
await import_promises11.default.mkdir(
|
|
42360
|
+
await import_promises11.default.mkdir(import_node_path43.default.dirname(this.filePath), { recursive: true });
|
|
42222
42361
|
await import_promises11.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
42223
42362
|
await import_promises11.default.rename(tmp, this.filePath);
|
|
42224
42363
|
}
|
|
@@ -42226,7 +42365,7 @@ var PublishedChannelStore = class {
|
|
|
42226
42365
|
|
|
42227
42366
|
// src/extension/bundle-cache.ts
|
|
42228
42367
|
var import_promises12 = __toESM(require("fs/promises"), 1);
|
|
42229
|
-
var
|
|
42368
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
42230
42369
|
var BundleCache = class {
|
|
42231
42370
|
constructor(rootDir) {
|
|
42232
42371
|
this.rootDir = rootDir;
|
|
@@ -42235,14 +42374,14 @@ var BundleCache = class {
|
|
|
42235
42374
|
/** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
|
|
42236
42375
|
async write(snapshotHash, buffer) {
|
|
42237
42376
|
await import_promises12.default.mkdir(this.rootDir, { recursive: true });
|
|
42238
|
-
const file =
|
|
42377
|
+
const file = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
42239
42378
|
const tmp = `${file}.tmp`;
|
|
42240
42379
|
await import_promises12.default.writeFile(tmp, buffer, { mode: 384 });
|
|
42241
42380
|
await import_promises12.default.rename(tmp, file);
|
|
42242
42381
|
}
|
|
42243
42382
|
/** Returns the bundle bytes, or null when the file doesn't exist. */
|
|
42244
42383
|
async read(snapshotHash) {
|
|
42245
|
-
const file =
|
|
42384
|
+
const file = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
42246
42385
|
try {
|
|
42247
42386
|
return await import_promises12.default.readFile(file);
|
|
42248
42387
|
} catch (e) {
|
|
@@ -42252,7 +42391,7 @@ var BundleCache = class {
|
|
|
42252
42391
|
}
|
|
42253
42392
|
/** Idempotent — missing file is not an error. */
|
|
42254
42393
|
async delete(snapshotHash) {
|
|
42255
|
-
const file =
|
|
42394
|
+
const file = import_node_path44.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
42256
42395
|
await import_promises12.default.rm(file, { force: true });
|
|
42257
42396
|
}
|
|
42258
42397
|
};
|
|
@@ -42261,7 +42400,7 @@ var BundleCache = class {
|
|
|
42261
42400
|
async function startDaemon(config) {
|
|
42262
42401
|
const logger = createLogger({
|
|
42263
42402
|
level: config.logLevel,
|
|
42264
|
-
file:
|
|
42403
|
+
file: import_node_path45.default.join(config.dataDir, "clawd.log")
|
|
42265
42404
|
});
|
|
42266
42405
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
42267
42406
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -42279,11 +42418,8 @@ async function startDaemon(config) {
|
|
|
42279
42418
|
} else if (config.tunnel) {
|
|
42280
42419
|
resolvedAuthToken = authFile.token;
|
|
42281
42420
|
}
|
|
42282
|
-
const
|
|
42283
|
-
const
|
|
42284
|
-
let feishuActive = feishuIdentity !== null;
|
|
42285
|
-
let ownerPrincipalId = feishuIdentity?.identity.unionId ?? authFile.ownerPrincipalId;
|
|
42286
|
-
let ownerDisplayName = feishuIdentity?.identity.displayName ?? loadOwnerDisplayName(config.dataDir);
|
|
42421
|
+
const ownerPrincipalId = authFile.ownerPrincipalId;
|
|
42422
|
+
const ownerDisplayName = loadOwnerDisplayName(config.dataDir);
|
|
42287
42423
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
42288
42424
|
const inboxStore = new InboxStore(config.dataDir);
|
|
42289
42425
|
const inboxManager = new InboxManager(inboxStore, (_peerOwnerId, frame) => {
|
|
@@ -42293,6 +42429,8 @@ async function startDaemon(config) {
|
|
|
42293
42429
|
receivedCapabilityStore.load();
|
|
42294
42430
|
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
42295
42431
|
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
42432
|
+
const personalCapability = loadOrCreatePersonalCapability({ dataDir: config.dataDir });
|
|
42433
|
+
capabilityRegistry.upsertCapability(personalCapability.capability);
|
|
42296
42434
|
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
42297
42435
|
onDeleted: (cap) => {
|
|
42298
42436
|
const deletedAt = Date.now();
|
|
@@ -42318,36 +42456,15 @@ async function startDaemon(config) {
|
|
|
42318
42456
|
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
42319
42457
|
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
42320
42458
|
expectedToken: resolvedAuthToken,
|
|
42321
|
-
authenticate:
|
|
42322
|
-
|
|
42459
|
+
authenticate: (t, selfPrincipalId, selfDisplayName) => {
|
|
42460
|
+
return authenticate(t, {
|
|
42323
42461
|
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
42324
42462
|
ownerPrincipalId,
|
|
42325
42463
|
ownerDisplayName,
|
|
42326
|
-
|
|
42327
|
-
|
|
42328
|
-
|
|
42329
|
-
...feishuActive ? {
|
|
42330
|
-
introspectConnectToken: async (token) => {
|
|
42331
|
-
const record = ownerIdentityStore.read();
|
|
42332
|
-
if (!record) return { active: false };
|
|
42333
|
-
return centralIntrospect({
|
|
42334
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
42335
|
-
hubTtcJwt: record.ttcToken,
|
|
42336
|
-
token
|
|
42337
|
-
});
|
|
42338
|
-
}
|
|
42339
|
-
} : {}
|
|
42464
|
+
capabilityRegistry,
|
|
42465
|
+
selfPrincipalId,
|
|
42466
|
+
selfDisplayName
|
|
42340
42467
|
});
|
|
42341
|
-
if (result.ok && result.context.principal.kind === "guest") {
|
|
42342
|
-
autoReverseContact({
|
|
42343
|
-
store: receivedCapabilityStore,
|
|
42344
|
-
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
42345
|
-
unionId: result.context.principal.id,
|
|
42346
|
-
displayName: result.context.principal.displayName ?? result.context.principal.id,
|
|
42347
|
-
selfUrl
|
|
42348
|
-
});
|
|
42349
|
-
}
|
|
42350
|
-
return result;
|
|
42351
42468
|
},
|
|
42352
42469
|
onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
|
|
42353
42470
|
buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
|
|
@@ -42370,7 +42487,7 @@ async function startDaemon(config) {
|
|
|
42370
42487
|
const agents = new AgentsScanner();
|
|
42371
42488
|
const history = new ClaudeHistoryReader();
|
|
42372
42489
|
let transport = null;
|
|
42373
|
-
const personaStore = new PersonaStore(
|
|
42490
|
+
const personaStore = new PersonaStore(import_node_path45.default.join(config.dataDir, "personas"));
|
|
42374
42491
|
const defaultsRoot = findDefaultsRoot();
|
|
42375
42492
|
if (defaultsRoot) {
|
|
42376
42493
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -42387,7 +42504,7 @@ async function startDaemon(config) {
|
|
|
42387
42504
|
getAdapter,
|
|
42388
42505
|
historyReader: history,
|
|
42389
42506
|
dataDir: config.dataDir,
|
|
42390
|
-
personaRoot:
|
|
42507
|
+
personaRoot: import_node_path45.default.join(config.dataDir, "personas"),
|
|
42391
42508
|
personaStore,
|
|
42392
42509
|
ownerDisplayName,
|
|
42393
42510
|
ownerPrincipalId,
|
|
@@ -42411,10 +42528,10 @@ async function startDaemon(config) {
|
|
|
42411
42528
|
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
42412
42529
|
attachmentGroup: {
|
|
42413
42530
|
onFileEdit: (input) => {
|
|
42414
|
-
const absPath =
|
|
42531
|
+
const absPath = import_node_path45.default.isAbsolute(input.relPath) ? input.relPath : import_node_path45.default.join(input.cwd, input.relPath);
|
|
42415
42532
|
let size = 0;
|
|
42416
42533
|
try {
|
|
42417
|
-
size =
|
|
42534
|
+
size = import_node_fs30.default.statSync(absPath).size;
|
|
42418
42535
|
} catch (err) {
|
|
42419
42536
|
logger.warn("attachment.onFileEdit stat failed", {
|
|
42420
42537
|
sessionId: input.sessionId,
|
|
@@ -42497,33 +42614,23 @@ async function startDaemon(config) {
|
|
|
42497
42614
|
}
|
|
42498
42615
|
return `http://${config.host}:${config.port}`;
|
|
42499
42616
|
};
|
|
42500
|
-
const reportHubUrl = async () => {
|
|
42501
|
-
if (!feishuActive || !currentTunnelUrl) return;
|
|
42502
|
-
const record = ownerIdentityStore.read();
|
|
42503
|
-
if (!record) return;
|
|
42504
|
-
try {
|
|
42505
|
-
await centralUpsertHub({
|
|
42506
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
42507
|
-
ttcJwt: record.ttcToken,
|
|
42508
|
-
url: currentTunnelUrl,
|
|
42509
|
-
name: ownerDisplayName
|
|
42510
|
-
});
|
|
42511
|
-
logger.info("hub url reported to central server", { url: currentTunnelUrl });
|
|
42512
|
-
} catch (err) {
|
|
42513
|
-
logger.warn("hub url report failed (best-effort)", { err: err.message });
|
|
42514
|
-
}
|
|
42515
|
-
};
|
|
42516
42617
|
const extensionRuntime = new Runtime({ root: extensionsRoot() });
|
|
42517
42618
|
const publishedChannelStore = new PublishedChannelStore(publishedChannelsFile());
|
|
42518
42619
|
await publishedChannelStore.load();
|
|
42519
42620
|
const bundleCache = new BundleCache(bundleCacheRoot());
|
|
42520
|
-
const
|
|
42521
|
-
|
|
42522
|
-
|
|
42523
|
-
|
|
42524
|
-
|
|
42525
|
-
|
|
42526
|
-
|
|
42621
|
+
const appBuilderStore = new ProjectStore(
|
|
42622
|
+
import_node_path45.default.join(config.dataDir, "personas/persona-app-builder")
|
|
42623
|
+
);
|
|
42624
|
+
const devServerSupervisor = new DevServerSupervisor();
|
|
42625
|
+
devServerSupervisor.setLogger(logger);
|
|
42626
|
+
const appBuilderPortRange = [];
|
|
42627
|
+
for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) appBuilderPortRange.push(p2);
|
|
42628
|
+
const effectivePreviewPorts = Array.from(
|
|
42629
|
+
/* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
|
|
42630
|
+
);
|
|
42631
|
+
const deleteSessionsByProject = async (_name) => {
|
|
42632
|
+
};
|
|
42633
|
+
const handlers = buildMethodHandlers({
|
|
42527
42634
|
manager,
|
|
42528
42635
|
workspace,
|
|
42529
42636
|
skills,
|
|
@@ -42562,12 +42669,17 @@ async function startDaemon(config) {
|
|
|
42562
42669
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
42563
42670
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
42564
42671
|
// guest path guard:candidate 必须在 personaRoot 子树下
|
|
42565
|
-
personaRoot:
|
|
42672
|
+
personaRoot: import_node_path45.default.join(config.dataDir, "personas")
|
|
42566
42673
|
},
|
|
42567
42674
|
// workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
|
|
42568
|
-
personaRoot:
|
|
42675
|
+
personaRoot: import_node_path45.default.join(config.dataDir, "personas"),
|
|
42569
42676
|
// capability:list / delete handler 依赖
|
|
42570
42677
|
capabilityManager,
|
|
42678
|
+
// personal-cap:get 返回的 shareBaseUrl 用此 base URL:
|
|
42679
|
+
// tunnel 拉起后切到反代 wss://... 地址;否则本机 ws://host:port。
|
|
42680
|
+
getShareBaseUrl: () => currentTunnelUrl ?? `ws://${config.host}:${config.port}`,
|
|
42681
|
+
// global personal token (2026-05-25): personal-cap:get handler 直接返回此 token + cap
|
|
42682
|
+
getPersonalCapability: () => personalCapability,
|
|
42571
42683
|
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
42572
42684
|
ownerDisplayName,
|
|
42573
42685
|
// owner-id stabilization: whoami 用稳定 ownerPrincipalId 替代 'owner' 字面量
|
|
@@ -42579,55 +42691,24 @@ async function startDaemon(config) {
|
|
|
42579
42691
|
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
42580
42692
|
inboxManager,
|
|
42581
42693
|
inboxStore,
|
|
42582
|
-
//
|
|
42694
|
+
// bidirectional cap 视角 B: 对方颁给我的 cap 走这个 store
|
|
42583
42695
|
receivedCapabilityStore,
|
|
42584
|
-
// received-capability:removed broadcast;复用 capability:tokenIssued 同款通路
|
|
42696
|
+
// received-capability:added / removed broadcast;复用 capability:tokenIssued 同款通路
|
|
42585
42697
|
broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
|
|
42698
|
+
// received-capability:add 临时 ws 连远端跑 whoami — 直连 ws (不走 transport listener)
|
|
42699
|
+
connectRemote,
|
|
42586
42700
|
// extension runtime (v1: local-mode only). Instance owned by daemon top
|
|
42587
42701
|
// level so stopAll() runs in the shutdown hook below.
|
|
42588
42702
|
extensionRuntime,
|
|
42589
42703
|
// extension sharing v1 — owner-side published-channels store.
|
|
42590
42704
|
publishedChannelStore,
|
|
42591
42705
|
bundleCache,
|
|
42592
|
-
//
|
|
42593
|
-
|
|
42594
|
-
|
|
42595
|
-
|
|
42596
|
-
|
|
42597
|
-
// 这里的 FEISHU_LOGIN_REQUIRED 抛错只是防御纵深。
|
|
42598
|
-
feishuHub: {
|
|
42599
|
-
store: receivedCapabilityStore,
|
|
42600
|
-
exchange: async (hubId) => {
|
|
42601
|
-
const record = ownerIdentityStore.read();
|
|
42602
|
-
if (!record) {
|
|
42603
|
-
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "hub:connect requires feishu login");
|
|
42604
|
-
}
|
|
42605
|
-
return centralExchange({
|
|
42606
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
42607
|
-
ttcJwt: record.ttcToken,
|
|
42608
|
-
hubId
|
|
42609
|
-
});
|
|
42610
|
-
},
|
|
42611
|
-
listHubs: async () => {
|
|
42612
|
-
const record = ownerIdentityStore.read();
|
|
42613
|
-
if (!record) {
|
|
42614
|
-
throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "hub:list requires feishu login");
|
|
42615
|
-
}
|
|
42616
|
-
return centralListHubs({
|
|
42617
|
-
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
42618
|
-
ttcJwt: record.ttcToken
|
|
42619
|
-
});
|
|
42620
|
-
},
|
|
42621
|
-
connectRemote,
|
|
42622
|
-
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
42623
|
-
selfPrincipalId: () => ownerPrincipalId,
|
|
42624
|
-
selfDisplayName: () => ownerDisplayName,
|
|
42625
|
-
// Phase 2 自动反向(spec 决策 #11):本机可达地址 = tunnel URL(公网),出站连 hub 时
|
|
42626
|
-
// 透传让 hub 反向加我为联系人。无 tunnel(仅本机 ws)→ null,hub 不自动反向(不造假数据)。
|
|
42627
|
-
selfUrl: () => currentTunnelUrl
|
|
42628
|
-
}
|
|
42706
|
+
// app-builder
|
|
42707
|
+
appBuilderStore,
|
|
42708
|
+
deleteSessionsByProject,
|
|
42709
|
+
devServerLifecycle: devServerSupervisor,
|
|
42710
|
+
logger
|
|
42629
42711
|
});
|
|
42630
|
-
let handlers = makeHandlers();
|
|
42631
42712
|
const authResolver = new AuthContextResolver({
|
|
42632
42713
|
ownerToken: resolvedAuthToken
|
|
42633
42714
|
});
|
|
@@ -42653,30 +42734,8 @@ async function startDaemon(config) {
|
|
|
42653
42734
|
},
|
|
42654
42735
|
// POST /extensions/import lands in ~/.clawd/extensions/<id>/
|
|
42655
42736
|
extensionsRoot: () => extensionsRoot(),
|
|
42656
|
-
//
|
|
42657
|
-
|
|
42658
|
-
// 2. 广播 auth:login:done 给在线 owner 连接(UI 即时反馈)
|
|
42659
|
-
// 3. 踢掉所有 ws 连接(在线连接的 ctx 是旧身份)→ UI 自动重连 → 新身份 ctx →
|
|
42660
|
-
// People 立即解锁,不需要重启 daemon
|
|
42661
|
-
// 失败则广播 auth:login:failed。
|
|
42662
|
-
feishuLogin: {
|
|
42663
|
-
handleLoginCallback: async (params) => {
|
|
42664
|
-
const result = await loginFlow.handleCallback(params);
|
|
42665
|
-
if (result.ok) {
|
|
42666
|
-
feishuActive = true;
|
|
42667
|
-
ownerPrincipalId = result.identity.unionId;
|
|
42668
|
-
ownerDisplayName = result.identity.displayName;
|
|
42669
|
-
handlers = makeHandlers();
|
|
42670
|
-
wsServer?.setOwnerPrincipalId(result.identity.unionId);
|
|
42671
|
-
wsServer?.broadcastToOwners({ type: "auth:login:done", identity: result.identity });
|
|
42672
|
-
setImmediate(() => wsServer?.closeAllClients());
|
|
42673
|
-
void reportHubUrl();
|
|
42674
|
-
} else {
|
|
42675
|
-
wsServer?.broadcastToOwners({ type: "auth:login:failed", reason: result.reason });
|
|
42676
|
-
}
|
|
42677
|
-
return result;
|
|
42678
|
-
}
|
|
42679
|
-
}
|
|
42737
|
+
// app-builder build 模式:/preview/<port>/ 反代 dev server 的端口白名单(见 spec §三.2)
|
|
42738
|
+
previewPorts: effectivePreviewPorts
|
|
42680
42739
|
});
|
|
42681
42740
|
wsServer = new LocalWsServer({
|
|
42682
42741
|
host: config.host,
|
|
@@ -42715,6 +42774,8 @@ async function startDaemon(config) {
|
|
|
42715
42774
|
},
|
|
42716
42775
|
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
42717
42776
|
httpRequestHandler: httpRouter,
|
|
42777
|
+
// app-builder build 模式:/preview/<port>/ HMR websocket upgrade 转发的端口白名单(同 http-router 配置)
|
|
42778
|
+
previewPorts: effectivePreviewPorts,
|
|
42718
42779
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
42719
42780
|
// daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
|
|
42720
42781
|
// 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
|
|
@@ -42759,12 +42820,6 @@ async function startDaemon(config) {
|
|
|
42759
42820
|
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
42760
42821
|
const handler = handlers[type];
|
|
42761
42822
|
if (!handler) throw new ClawdError(ERROR_CODES.METHOD_NOT_IMPLEMENTED, `not implemented: ${type}`);
|
|
42762
|
-
if (!feishuActive && FEISHU_GATED_METHODS.includes(type)) {
|
|
42763
|
-
throw new ClawdError(
|
|
42764
|
-
ERROR_CODES.FEISHU_LOGIN_REQUIRED,
|
|
42765
|
-
`${type} requires feishu login (People/remote identity)`
|
|
42766
|
-
);
|
|
42767
|
-
}
|
|
42768
42823
|
const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
|
|
42769
42824
|
const verdict = computeGrantForFrame(type, frame);
|
|
42770
42825
|
if (verdict.kind === "check") {
|
|
@@ -42837,12 +42892,13 @@ async function startDaemon(config) {
|
|
|
42837
42892
|
stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url, tunnelError: void 0 };
|
|
42838
42893
|
stateMgr.write(stateSnapshot);
|
|
42839
42894
|
currentTunnelUrl = r.url;
|
|
42895
|
+
wss.broadcastAll({ type: "tunnel:ready", url: r.url, subdomain: r.subdomain });
|
|
42840
42896
|
const connectUrl = resolvedAuthToken ? `${r.url}#token=${resolvedAuthToken}` : r.url;
|
|
42841
42897
|
const lines = [
|
|
42842
42898
|
`Tunnel: ${r.url}`,
|
|
42843
42899
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
42844
|
-
`Frpc config: ${
|
|
42845
|
-
`Frpc log: ${
|
|
42900
|
+
`Frpc config: ${import_node_path45.default.join(config.dataDir, "frpc.toml")}`,
|
|
42901
|
+
`Frpc log: ${import_node_path45.default.join(config.dataDir, "frpc.log")}`
|
|
42846
42902
|
];
|
|
42847
42903
|
const width = Math.max(...lines.map((l) => l.length));
|
|
42848
42904
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -42855,11 +42911,10 @@ ${bar}
|
|
|
42855
42911
|
|
|
42856
42912
|
`);
|
|
42857
42913
|
try {
|
|
42858
|
-
const connectPath =
|
|
42859
|
-
|
|
42914
|
+
const connectPath = import_node_path45.default.join(config.dataDir, "connect.txt");
|
|
42915
|
+
import_node_fs30.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
42860
42916
|
} catch {
|
|
42861
42917
|
}
|
|
42862
|
-
void reportHubUrl();
|
|
42863
42918
|
} catch (err) {
|
|
42864
42919
|
const tunnelError = err?.message ?? String(err);
|
|
42865
42920
|
try {
|
|
@@ -42923,9 +42978,9 @@ ${bar}
|
|
|
42923
42978
|
};
|
|
42924
42979
|
}
|
|
42925
42980
|
function migrateDropPersonsDir(dataDir) {
|
|
42926
|
-
const dir =
|
|
42981
|
+
const dir = import_node_path45.default.join(dataDir, "persons");
|
|
42927
42982
|
try {
|
|
42928
|
-
|
|
42983
|
+
import_node_fs30.default.rmSync(dir, { recursive: true, force: true });
|
|
42929
42984
|
} catch {
|
|
42930
42985
|
}
|
|
42931
42986
|
}
|