@clawos-dev/clawd 0.2.112-beta.214.dc7a4d7 → 0.2.112-beta.216.545c7ca
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 +1070 -1127
- package/package.json +1 -1
- package/dist/persona-defaults/persona-app-builder/CLAUDE.md +0 -145
- package/dist/persona-defaults/persona-app-builder/extension-kit/README.md +0 -96
- package/dist/persona-defaults/persona-app-builder/extension-kit/config.env +0 -20
- package/dist/persona-defaults/persona-app-builder/extension-kit/contract/bootstrap +0 -22
- package/dist/persona-defaults/persona-app-builder/extension-kit/contract/s.yaml.tmpl +0 -54
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/.env.example +0 -3
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/.fcignore +0 -7
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/nest-cli.json +0 -8
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/package-lock.json +0 -4531
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/package.json +0 -26
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +0 -14
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/main.ts +0 -14
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.controller.ts +0 -27
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.module.ts +0 -9
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/messages/messages.service.ts +0 -38
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/polyfill.ts +0 -8
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/tsconfig.json +0 -14
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/index.html +0 -12
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/package-lock.json +0 -1680
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/package.json +0 -18
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/src/App.jsx +0 -161
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/src/main.jsx +0 -5
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/vite.config.js +0 -31
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/new-extension.sh +0 -49
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/publish.sh +0 -78
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/remove-extension.sh +0 -57
- package/dist/persona-defaults/persona-app-builder/extension-kit/scripts/verify.sh +0 -20
package/dist/cli.cjs
CHANGED
|
@@ -132,15 +132,11 @@ var init_methods = __esm({
|
|
|
132
132
|
"attachment.groupRemove",
|
|
133
133
|
"attachment.groupList",
|
|
134
134
|
// ---- capability:* (capability platform 鉴权底座) ----
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
// 老板手动清旧 cap);capability:issue / capability:bindPeer 已下线。
|
|
135
|
+
// 飞书统一身份 Phase 2 (2026-06-01, spec 决策 #12): personal token / 邀请码全链路删除——
|
|
136
|
+
// hub 已完整替代(中心 server 签发 per-hub connect token)。capability:list / capability:delete
|
|
137
|
+
// 保留:调试 + whoami 反查 + 撤销级联 + 清旧 per-peer cap。personal-cap:get 已下线。
|
|
139
138
|
"capability:list",
|
|
140
139
|
"capability:delete",
|
|
141
|
-
// global personal token (2026-05-25): UI invite/accept dialog 挂载即调,拿 personal
|
|
142
|
-
// token + capability + shareBaseUrl 用于拼 inviteUrl / autoAttach 反向推。owner-only。
|
|
143
|
-
"personal-cap:get",
|
|
144
140
|
// ---- inbox:* (capability platform Phase 3 跨用户通知 + Phase 4 DM) ----
|
|
145
141
|
// owner 接 guest 的 cross-principal 消息事件 + DM 双向私聊.
|
|
146
142
|
// inbox:list / markRead: admin-only (Phase 3); inbox:postMessage: DM 自带能力,
|
|
@@ -148,14 +144,11 @@ var init_methods = __esm({
|
|
|
148
144
|
"inbox:list",
|
|
149
145
|
"inbox:markRead",
|
|
150
146
|
"inbox:postMessage",
|
|
151
|
-
// ---- received-capability:* (
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
// 详见 received-capability.ts JSDoc + spec 2026-05-23-bidirectional-cap-design.md
|
|
147
|
+
// ---- received-capability:* (联系人列表, 视角 B 双向授权模型 2026-05-23) ----
|
|
148
|
+
// 飞书统一身份 Phase 2 (2026-06-01, spec 决策 #12): 邀请码 add / autoAttach 已删除——
|
|
149
|
+
// hub:connect 落同一 store + 自动反向(决策 #11)替代。list / remove 保留作联系人列表读删。
|
|
155
150
|
"received-capability:list",
|
|
156
|
-
"received-capability:add",
|
|
157
151
|
"received-capability:remove",
|
|
158
|
-
"received-capability:autoAttach",
|
|
159
152
|
// ---- whoami (v2 capability platform) ----
|
|
160
153
|
// 任何 authed connection 都能调;返回 owner 显示名 + 当前 capability wire 形态 +
|
|
161
154
|
// grants 对应的 persona 元数据 (id + displayName)。UI 端 AddRemotePersonaDialog
|
|
@@ -181,16 +174,17 @@ var init_methods = __esm({
|
|
|
181
174
|
// 无 subscription 持久化:guest 端身份完全来自文件系统 + reconciled publishedExtensions.
|
|
182
175
|
"extension.install-from-channel",
|
|
183
176
|
"extension.update-from-channel",
|
|
184
|
-
// ----
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
"
|
|
177
|
+
// ---- auth:* 飞书统一身份 Phase 1 (spec 2026-06-01-feishu-identity-design) ----
|
|
178
|
+
// owner-only:daemon loopback 登录流程(auth:login:start 起 state → HTTP /auth/callback
|
|
179
|
+
// 回调落盘 → auth:login:done 事件)。中央字面量见 protocol/feishu-auth.ts FEISHU_AUTH_METHODS。
|
|
180
|
+
"auth:login:start",
|
|
181
|
+
"auth:getIdentity",
|
|
182
|
+
"auth:logout",
|
|
183
|
+
// ---- hub:* 飞书统一身份 Phase 2 (spec §10.2) ----
|
|
184
|
+
// owner-only:公共 hub 目录 + per-hub connect token 连接(daemon 代理中心 server)。
|
|
185
|
+
// 中央字面量见 protocol/feishu-auth.ts HUB_METHODS。
|
|
186
|
+
"hub:list",
|
|
187
|
+
"hub:connect"
|
|
194
188
|
];
|
|
195
189
|
}
|
|
196
190
|
});
|
|
@@ -252,7 +246,11 @@ var init_errors = __esm({
|
|
|
252
246
|
INTERNAL: "INTERNAL",
|
|
253
247
|
UNAUTHORIZED: "UNAUTHORIZED",
|
|
254
248
|
FORBIDDEN: "FORBIDDEN",
|
|
255
|
-
INVALID_PARAM: "INVALID_PARAM"
|
|
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"
|
|
256
254
|
};
|
|
257
255
|
ClawdError = class extends Error {
|
|
258
256
|
constructor(code, message) {
|
|
@@ -4532,7 +4530,7 @@ var init_persona_schemas = __esm({
|
|
|
4532
4530
|
});
|
|
4533
4531
|
|
|
4534
4532
|
// ../protocol/src/schemas.ts
|
|
4535
|
-
var SessionStatusSchema, UsageSchema, ContextUsageSchema, sessionMetaShape, SessionMetaSchema, ModelInfoSchema, ModeInfoSchema, ConfigFieldSchemaSchema, CapabilitiesGetArgs, CapabilitiesResponseSchema, AllowRuleSchema, SessionFileSchema, ParsedEventBase, HistoryUserMetaSchema, SubagentToolStatsSchema, StructuredPatchHunkSchema, ToolResultExtraSchema, MemoryEntrySchema, AskQuestionOptionSchema, AskQuestionItemSchema, ParsedEventSchema, SessionCreateArgs, SessionIdArgs, SessionUpdateArgs, SessionSendArgs, SessionRewindArgs, SessionRewindResponseSchema, SessionRewindDiffArgs, RewindDiffHunkSchema, RewindDiffFileSchema, SessionRewindDiffResponseSchema, SessionRewindableMessageIdsArgs, SessionRewindableMessageIdsResponseSchema, SessionResumeArgs, SessionForkArgs, SessionForkResponseSchema, SessionObserveArgs, SessionEventsArgs, PermissionRespondArgs, HistoryListArgs, HistoryReadArgs, HistorySubagentsArgs, HistorySubagentReadArgs, WorkspaceListArgs, WorkspaceReadArgs, SkillsListArgs, SkillEntrySchema, AgentEntrySchema, AgentsListArgs, AgentsListResponseSchema, SessionSubscribeArgs, SessionPinArgs, PeerSessionUpsertArgs, PeerSessionUpsertResponseSchema, PeerSessionRemoveArgs, PeerSessionRemoveResponseSchema, SessionReorderPinsArgs, GitRootArgs, GitRootResponseSchema, GitBranchArgs, GitBranchResponseSchema, GitBranchesArgs, GitBranchesResponseSchema, HistoryRecentDirsArgs, RecentDirEntrySchema, HistoryRecentDirsResponseSchema, SessionQuestionFrameSchema, SessionQuestionClearedFrameSchema, AnswerQuestionArgs, AnswerQuestionResponseSchema, CancelQuestionArgs, CancelQuestionResponseSchema, AuthRequestFrameSchema, AuthOkFrameSchema,
|
|
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;
|
|
4536
4534
|
var init_schemas = __esm({
|
|
4537
4535
|
"../protocol/src/schemas.ts"() {
|
|
4538
4536
|
"use strict";
|
|
@@ -4629,10 +4627,6 @@ var init_schemas = __esm({
|
|
|
4629
4627
|
// owner-mode persona session 身份标识;UI 用它在 SessionList 过滤 + jump,
|
|
4630
4628
|
// daemon 用它做 idempotent dedupe + spawn 时派生 ctx.personaMode='owner'
|
|
4631
4629
|
ownerPersonaId: external_exports.string().min(1).optional(),
|
|
4632
|
-
// app-builder Project 反向索引(spec: superpowers/specs/2026-06-01-app-builder-project-picker-design.md)。
|
|
4633
|
-
// 当此 session 绑定到某个 build 中的 project 时填写;name 与 projects/<name>/ 子目录同名,
|
|
4634
|
-
// 受 projectNameSchema regex 约束。daemon session:resume 时校验该目录存在,不存在则拒。
|
|
4635
|
-
appBuilderProject: external_exports.string().regex(/^[a-z][a-z0-9-]{0,39}$/).optional(),
|
|
4636
4630
|
// listener-mode sub-session 的原始 chatId(owner-mode 不写;listener-scope 强制写入)。
|
|
4637
4631
|
// 派生 sessionId 是单向 hash `${personaId}-${tokenHash12}-${chatHash8}`,必须保留原始 chatId
|
|
4638
4632
|
// 才能让 alice 重连同一 sub-session(持久化在 alice localStorage 之外,由 daemon push 同步)。
|
|
@@ -4905,12 +4899,7 @@ var init_schemas = __esm({
|
|
|
4905
4899
|
forkedFromSessionId: external_exports.string().min(1).optional(),
|
|
4906
4900
|
// owner-mode persona session:传此字段时 daemon 自行派生 cwd 和启动参数,
|
|
4907
4901
|
// 且按 ownerPersonaId 做 idempotent dedupe(每 persona 永远只有一个 owner session)
|
|
4908
|
-
ownerPersonaId: external_exports.string().min(1).optional()
|
|
4909
|
-
// app-builder Project 绑定(spec 2026-06-01-app-builder-project-picker-design)。
|
|
4910
|
-
// UI picker 上点 idle project / 新建 project 时传入;daemon 透传写入 SessionFile.appBuilderProject。
|
|
4911
|
-
// 跟 ownerPersonaId 同层但不互斥(典型场景两者都传:ownerPersonaId='persona-app-builder' +
|
|
4912
|
-
// appBuilderProject='<project-name>')。
|
|
4913
|
-
appBuilderProject: external_exports.string().regex(/^[a-z][a-z0-9-]{0,39}$/).optional()
|
|
4902
|
+
ownerPersonaId: external_exports.string().min(1).optional()
|
|
4914
4903
|
}).refine((args) => args.cwd != null || args.ownerPersonaId != null, {
|
|
4915
4904
|
message: "cwd \u4E0E ownerPersonaId \u81F3\u5C11\u4F20\u4E00\u4E2A"
|
|
4916
4905
|
});
|
|
@@ -4924,11 +4913,7 @@ var init_schemas = __esm({
|
|
|
4924
4913
|
effort: external_exports.string().optional(),
|
|
4925
4914
|
cwd: external_exports.string().optional(),
|
|
4926
4915
|
// 用户在 Edit modal 选择的 icon 标识;UI 端做 enum 兜底
|
|
4927
|
-
iconKey: external_exports.string().optional()
|
|
4928
|
-
// app-builder picker 在当前 session 内切 project 时写入(spec 2026-06-01-app-builder-project-picker-design)。
|
|
4929
|
-
// daemon session:update handler 检测此字段变化时停旧 project dev server + 起新 project dev server。
|
|
4930
|
-
// 设为空字符串 '' 表示解绑(picker 不暴露解绑入口,但 schema 允许)。
|
|
4931
|
-
appBuilderProject: external_exports.string().regex(/^[a-z][a-z0-9-]{0,39}$/).or(external_exports.literal("")).optional()
|
|
4916
|
+
iconKey: external_exports.string().optional()
|
|
4932
4917
|
})
|
|
4933
4918
|
});
|
|
4934
4919
|
SessionSendArgs = external_exports.object({
|
|
@@ -5125,6 +5110,12 @@ var init_schemas = __esm({
|
|
|
5125
5110
|
* 后调 capabilityManager.recordPeerHello(capId, ownerPrincipalId, displayName)
|
|
5126
5111
|
* 把 cap.peerOwnerId / firstUsedByPeerAt 字段回写,让 owner 端 UI 在 People
|
|
5127
5112
|
* 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)。
|
|
5128
5119
|
*/
|
|
5129
5120
|
selfPrincipalId: external_exports.string().min(1).optional(),
|
|
5130
5121
|
/**
|
|
@@ -5132,18 +5123,23 @@ var init_schemas = __esm({
|
|
|
5132
5123
|
* 显示名写入 cap.peerOwnerDisplayName(cap.displayName 本来就是 owner 颁时填
|
|
5133
5124
|
* 的"颁给谁/用途",hello 收到的 displayName 是辅助)。
|
|
5134
5125
|
*/
|
|
5135
|
-
selfDisplayName: external_exports.string().optional()
|
|
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()
|
|
5136
5137
|
});
|
|
5137
5138
|
AuthOkFrameSchema = external_exports.object({
|
|
5138
5139
|
type: external_exports.literal("auth:ok"),
|
|
5139
5140
|
version: external_exports.string().min(1).optional(),
|
|
5140
5141
|
protocolVersion: external_exports.number().int().nonnegative().optional()
|
|
5141
5142
|
});
|
|
5142
|
-
TunnelReadyEventSchema = external_exports.object({
|
|
5143
|
-
type: external_exports.literal("tunnel:ready"),
|
|
5144
|
-
url: external_exports.string().min(1),
|
|
5145
|
-
subdomain: external_exports.string().min(1)
|
|
5146
|
-
});
|
|
5147
5143
|
TunnelExitedEventSchema = external_exports.object({
|
|
5148
5144
|
type: external_exports.literal("tunnel:exited"),
|
|
5149
5145
|
code: external_exports.number().int().nullable(),
|
|
@@ -5185,41 +5181,6 @@ var init_schemas = __esm({
|
|
|
5185
5181
|
/** file-sharing HTTP 路由的 Authorization: Bearer token;与 WS token 解耦,HTTP 401 仅触发 ReAuth 不强踢 WS(spec §11 第 4 条) */
|
|
5186
5182
|
httpToken: external_exports.string().optional()
|
|
5187
5183
|
});
|
|
5188
|
-
PROJECT_PORT_MIN = 6173;
|
|
5189
|
-
PROJECT_PORT_MAX = 6182;
|
|
5190
|
-
projectNameRegex = /^[a-z][a-z0-9-]{0,39}$/;
|
|
5191
|
-
ProjectMetadataSchema = external_exports.object({
|
|
5192
|
-
name: external_exports.string().regex(projectNameRegex, {
|
|
5193
|
-
message: "kebab-case, \u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u53EA\u5141\u8BB8\u5C0F\u5199\u5B57\u6BCD / \u6570\u5B57 / -\uFF0C\u6700\u957F 40 \u5B57\u7B26"
|
|
5194
|
-
}),
|
|
5195
|
-
port: external_exports.number().int().min(PROJECT_PORT_MIN).max(PROJECT_PORT_MAX),
|
|
5196
|
-
createdAt: external_exports.string().datetime()
|
|
5197
|
-
});
|
|
5198
|
-
ProjectWithStatusSchema = ProjectMetadataSchema.extend({
|
|
5199
|
-
isRunning: external_exports.boolean(),
|
|
5200
|
-
boundSessionId: external_exports.string().nullable()
|
|
5201
|
-
});
|
|
5202
|
-
ListProjectsArgsSchema = external_exports.object({}).strict();
|
|
5203
|
-
ListProjectsResultSchema = external_exports.object({
|
|
5204
|
-
projects: external_exports.array(ProjectWithStatusSchema)
|
|
5205
|
-
}).strict();
|
|
5206
|
-
CreateProjectArgsSchema = external_exports.object({
|
|
5207
|
-
name: external_exports.string()
|
|
5208
|
-
}).strict();
|
|
5209
|
-
CreateProjectResultSchema = external_exports.object({
|
|
5210
|
-
project: ProjectMetadataSchema
|
|
5211
|
-
}).strict();
|
|
5212
|
-
DeleteProjectArgsSchema = external_exports.object({
|
|
5213
|
-
name: external_exports.string()
|
|
5214
|
-
}).strict();
|
|
5215
|
-
DeleteProjectResultSchema = external_exports.object({}).strict();
|
|
5216
|
-
UpdateProjectPortArgsSchema = external_exports.object({
|
|
5217
|
-
name: external_exports.string(),
|
|
5218
|
-
newPort: external_exports.number().int()
|
|
5219
|
-
}).strict();
|
|
5220
|
-
UpdateProjectPortResultSchema = external_exports.object({
|
|
5221
|
-
project: ProjectMetadataSchema
|
|
5222
|
-
}).strict();
|
|
5223
5184
|
}
|
|
5224
5185
|
});
|
|
5225
5186
|
|
|
@@ -5262,7 +5223,7 @@ function stripSecretHash(cap) {
|
|
|
5262
5223
|
const { secretHash: _hash, ...wire } = cap;
|
|
5263
5224
|
return wire;
|
|
5264
5225
|
}
|
|
5265
|
-
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS
|
|
5226
|
+
var ResourceSchema, ActionSchema, GrantSchema, CapabilitySchema, CapabilityWireSchema, CapabilityErrorCodeSchema, WhoamiResponseSchema, PERSONAL_CAP_GRANTS;
|
|
5266
5227
|
var init_capability = __esm({
|
|
5267
5228
|
"../protocol/src/capability.ts"() {
|
|
5268
5229
|
"use strict";
|
|
@@ -5330,13 +5291,6 @@ var init_capability = __esm({
|
|
|
5330
5291
|
actions: Object.freeze(["read", "send"])
|
|
5331
5292
|
})
|
|
5332
5293
|
]);
|
|
5333
|
-
PersonalCapGetResponseSchema = external_exports.object({
|
|
5334
|
-
type: external_exports.literal("personal-cap:get:ok"),
|
|
5335
|
-
token: external_exports.string().min(1),
|
|
5336
|
-
capability: CapabilityWireSchema,
|
|
5337
|
-
/** ws/wss base URL,UI deriveInviteBase 用来拼 inviteUrl(tunnel 优先 / 否则本机 ws) */
|
|
5338
|
-
shareBaseUrl: external_exports.string().min(1)
|
|
5339
|
-
}).strict();
|
|
5340
5294
|
}
|
|
5341
5295
|
});
|
|
5342
5296
|
|
|
@@ -5372,7 +5326,7 @@ var init_inbox = __esm({
|
|
|
5372
5326
|
});
|
|
5373
5327
|
|
|
5374
5328
|
// ../protocol/src/received-capability.ts
|
|
5375
|
-
var ReceivedCapabilitySchema, ReceivedCapabilityWireSchema,
|
|
5329
|
+
var ReceivedCapabilitySchema, ReceivedCapabilityWireSchema, ReceivedCapabilityRemoveArgsSchema, ReceivedCapabilityRemoveOkSchema, ReceivedCapabilityListOkSchema, ReceivedCapabilityAddedFrameSchema, ReceivedCapabilityRemovedFrameSchema;
|
|
5376
5330
|
var init_received_capability = __esm({
|
|
5377
5331
|
"../protocol/src/received-capability.ts"() {
|
|
5378
5332
|
"use strict";
|
|
@@ -5383,19 +5337,15 @@ var init_received_capability = __esm({
|
|
|
5383
5337
|
ownerDisplayName: external_exports.string(),
|
|
5384
5338
|
remoteUrl: external_exports.string().min(1),
|
|
5385
5339
|
capabilityId: external_exports.string().min(1),
|
|
5386
|
-
|
|
5340
|
+
// Phase 2 自动反向建立联系人(spec 决策 #11):hub introspect 验票成功后自动落 A 的
|
|
5341
|
+
// received cap,但 hub 不持有 A 颁发的 token(A 从未给 hub 换票)→ connectToken 为空串。
|
|
5342
|
+
// hub:connect 出站路径仍存中心 server 签发的真实 connect token(断线重连复用)。
|
|
5343
|
+
// 不用 .min(1):自动反向路径合法地存空串,禁止填假 token 占位(不造假数据)。
|
|
5344
|
+
connectToken: external_exports.string(),
|
|
5387
5345
|
grants: external_exports.array(GrantSchema),
|
|
5388
5346
|
receivedAt: external_exports.number().int().nonnegative()
|
|
5389
5347
|
}).strict();
|
|
5390
5348
|
ReceivedCapabilityWireSchema = ReceivedCapabilitySchema;
|
|
5391
|
-
ReceivedCapabilityAddArgsSchema = external_exports.object({
|
|
5392
|
-
remoteUrl: external_exports.string().min(1),
|
|
5393
|
-
token: external_exports.string().min(1)
|
|
5394
|
-
}).strict();
|
|
5395
|
-
ReceivedCapabilityAddOkSchema = external_exports.object({
|
|
5396
|
-
type: external_exports.literal("received-capability:add:ok"),
|
|
5397
|
-
receivedCap: ReceivedCapabilityWireSchema
|
|
5398
|
-
}).strict();
|
|
5399
5349
|
ReceivedCapabilityRemoveArgsSchema = external_exports.object({
|
|
5400
5350
|
ownerPrincipalId: external_exports.string().min(1)
|
|
5401
5351
|
}).strict();
|
|
@@ -5407,17 +5357,6 @@ var init_received_capability = __esm({
|
|
|
5407
5357
|
type: external_exports.literal("received-capability:list:ok"),
|
|
5408
5358
|
receivedCaps: external_exports.array(ReceivedCapabilityWireSchema)
|
|
5409
5359
|
}).strict();
|
|
5410
|
-
ReceivedCapabilityAutoAttachArgsSchema = external_exports.object({
|
|
5411
|
-
ownerPrincipalId: external_exports.string().min(1),
|
|
5412
|
-
ownerDisplayName: external_exports.string(),
|
|
5413
|
-
remoteUrl: external_exports.string().min(1),
|
|
5414
|
-
capabilityId: external_exports.string().min(1),
|
|
5415
|
-
token: external_exports.string().min(1),
|
|
5416
|
-
grants: external_exports.array(GrantSchema)
|
|
5417
|
-
}).strict();
|
|
5418
|
-
ReceivedCapabilityAutoAttachOkSchema = external_exports.object({
|
|
5419
|
-
type: external_exports.literal("received-capability:autoAttach:ok")
|
|
5420
|
-
}).strict();
|
|
5421
5360
|
ReceivedCapabilityAddedFrameSchema = external_exports.object({
|
|
5422
5361
|
type: external_exports.literal("received-capability:added"),
|
|
5423
5362
|
receivedCap: ReceivedCapabilityWireSchema
|
|
@@ -5628,6 +5567,94 @@ var init_extension = __esm({
|
|
|
5628
5567
|
}
|
|
5629
5568
|
});
|
|
5630
5569
|
|
|
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
|
+
|
|
5631
5658
|
// ../protocol/src/runtime.ts
|
|
5632
5659
|
var init_runtime = __esm({
|
|
5633
5660
|
"../protocol/src/runtime.ts"() {
|
|
@@ -5645,6 +5672,7 @@ var init_runtime = __esm({
|
|
|
5645
5672
|
init_inbox();
|
|
5646
5673
|
init_received_capability();
|
|
5647
5674
|
init_extension();
|
|
5675
|
+
init_feishu_auth();
|
|
5648
5676
|
}
|
|
5649
5677
|
});
|
|
5650
5678
|
|
|
@@ -6831,8 +6859,8 @@ var require_atomic_sleep = __commonJS({
|
|
|
6831
6859
|
var require_sonic_boom = __commonJS({
|
|
6832
6860
|
"../node_modules/.pnpm/sonic-boom@4.2.1/node_modules/sonic-boom/index.js"(exports2, module2) {
|
|
6833
6861
|
"use strict";
|
|
6834
|
-
var
|
|
6835
|
-
var
|
|
6862
|
+
var fs47 = require("fs");
|
|
6863
|
+
var EventEmitter2 = require("events");
|
|
6836
6864
|
var inherits = require("util").inherits;
|
|
6837
6865
|
var path59 = require("path");
|
|
6838
6866
|
var sleep = require_atomic_sleep();
|
|
@@ -6888,20 +6916,20 @@ var require_sonic_boom = __commonJS({
|
|
|
6888
6916
|
const mode = sonic.mode;
|
|
6889
6917
|
if (sonic.sync) {
|
|
6890
6918
|
try {
|
|
6891
|
-
if (sonic.mkdir)
|
|
6892
|
-
const fd =
|
|
6919
|
+
if (sonic.mkdir) fs47.mkdirSync(path59.dirname(file), { recursive: true });
|
|
6920
|
+
const fd = fs47.openSync(file, flags, mode);
|
|
6893
6921
|
fileOpened(null, fd);
|
|
6894
6922
|
} catch (err) {
|
|
6895
6923
|
fileOpened(err);
|
|
6896
6924
|
throw err;
|
|
6897
6925
|
}
|
|
6898
6926
|
} else if (sonic.mkdir) {
|
|
6899
|
-
|
|
6927
|
+
fs47.mkdir(path59.dirname(file), { recursive: true }, (err) => {
|
|
6900
6928
|
if (err) return fileOpened(err);
|
|
6901
|
-
|
|
6929
|
+
fs47.open(file, flags, mode, fileOpened);
|
|
6902
6930
|
});
|
|
6903
6931
|
} else {
|
|
6904
|
-
|
|
6932
|
+
fs47.open(file, flags, mode, fileOpened);
|
|
6905
6933
|
}
|
|
6906
6934
|
}
|
|
6907
6935
|
function SonicBoom(opts) {
|
|
@@ -6942,8 +6970,8 @@ var require_sonic_boom = __commonJS({
|
|
|
6942
6970
|
this.flush = flushBuffer;
|
|
6943
6971
|
this.flushSync = flushBufferSync;
|
|
6944
6972
|
this._actualWrite = actualWriteBuffer;
|
|
6945
|
-
fsWriteSync = () =>
|
|
6946
|
-
fsWrite = () =>
|
|
6973
|
+
fsWriteSync = () => fs47.writeSync(this.fd, this._writingBuf);
|
|
6974
|
+
fsWrite = () => fs47.write(this.fd, this._writingBuf, this.release);
|
|
6947
6975
|
} else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
|
|
6948
6976
|
this._writingBuf = "";
|
|
6949
6977
|
this.write = write;
|
|
@@ -6952,15 +6980,15 @@ var require_sonic_boom = __commonJS({
|
|
|
6952
6980
|
this._actualWrite = actualWrite;
|
|
6953
6981
|
fsWriteSync = () => {
|
|
6954
6982
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6955
|
-
return
|
|
6983
|
+
return fs47.writeSync(this.fd, this._writingBuf);
|
|
6956
6984
|
}
|
|
6957
|
-
return
|
|
6985
|
+
return fs47.writeSync(this.fd, this._writingBuf, "utf8");
|
|
6958
6986
|
};
|
|
6959
6987
|
fsWrite = () => {
|
|
6960
6988
|
if (Buffer.isBuffer(this._writingBuf)) {
|
|
6961
|
-
return
|
|
6989
|
+
return fs47.write(this.fd, this._writingBuf, this.release);
|
|
6962
6990
|
}
|
|
6963
|
-
return
|
|
6991
|
+
return fs47.write(this.fd, this._writingBuf, "utf8", this.release);
|
|
6964
6992
|
};
|
|
6965
6993
|
} else {
|
|
6966
6994
|
throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
|
|
@@ -7017,7 +7045,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7017
7045
|
}
|
|
7018
7046
|
}
|
|
7019
7047
|
if (this._fsync) {
|
|
7020
|
-
|
|
7048
|
+
fs47.fsyncSync(this.fd);
|
|
7021
7049
|
}
|
|
7022
7050
|
const len = this._len;
|
|
7023
7051
|
if (this._reopening) {
|
|
@@ -7069,7 +7097,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7069
7097
|
sonic._asyncDrainScheduled = false;
|
|
7070
7098
|
sonic.emit("drain");
|
|
7071
7099
|
}
|
|
7072
|
-
inherits(SonicBoom,
|
|
7100
|
+
inherits(SonicBoom, EventEmitter2);
|
|
7073
7101
|
function mergeBuf(bufs, len) {
|
|
7074
7102
|
if (bufs.length === 0) {
|
|
7075
7103
|
return kEmptyBuffer;
|
|
@@ -7131,7 +7159,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7131
7159
|
const onDrain = () => {
|
|
7132
7160
|
if (!this._fsync) {
|
|
7133
7161
|
try {
|
|
7134
|
-
|
|
7162
|
+
fs47.fsync(this.fd, (err) => {
|
|
7135
7163
|
this._flushPending = false;
|
|
7136
7164
|
cb(err);
|
|
7137
7165
|
});
|
|
@@ -7233,7 +7261,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7233
7261
|
const fd = this.fd;
|
|
7234
7262
|
this.once("ready", () => {
|
|
7235
7263
|
if (fd !== this.fd) {
|
|
7236
|
-
|
|
7264
|
+
fs47.close(fd, (err) => {
|
|
7237
7265
|
if (err) {
|
|
7238
7266
|
return this.emit("error", err);
|
|
7239
7267
|
}
|
|
@@ -7282,7 +7310,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7282
7310
|
buf = this._bufs[0];
|
|
7283
7311
|
}
|
|
7284
7312
|
try {
|
|
7285
|
-
const n = Buffer.isBuffer(buf) ?
|
|
7313
|
+
const n = Buffer.isBuffer(buf) ? fs47.writeSync(this.fd, buf) : fs47.writeSync(this.fd, buf, "utf8");
|
|
7286
7314
|
const releasedBufObj = releaseWritingBuf(buf, this._len, n);
|
|
7287
7315
|
buf = releasedBufObj.writingBuf;
|
|
7288
7316
|
this._len = releasedBufObj.len;
|
|
@@ -7298,7 +7326,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7298
7326
|
}
|
|
7299
7327
|
}
|
|
7300
7328
|
try {
|
|
7301
|
-
|
|
7329
|
+
fs47.fsyncSync(this.fd);
|
|
7302
7330
|
} catch {
|
|
7303
7331
|
}
|
|
7304
7332
|
}
|
|
@@ -7319,7 +7347,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7319
7347
|
buf = mergeBuf(this._bufs[0], this._lens[0]);
|
|
7320
7348
|
}
|
|
7321
7349
|
try {
|
|
7322
|
-
const n =
|
|
7350
|
+
const n = fs47.writeSync(this.fd, buf);
|
|
7323
7351
|
buf = buf.subarray(n);
|
|
7324
7352
|
this._len = Math.max(this._len - n, 0);
|
|
7325
7353
|
if (buf.length <= 0) {
|
|
@@ -7347,13 +7375,13 @@ var require_sonic_boom = __commonJS({
|
|
|
7347
7375
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
|
|
7348
7376
|
if (this.sync) {
|
|
7349
7377
|
try {
|
|
7350
|
-
const written = Buffer.isBuffer(this._writingBuf) ?
|
|
7378
|
+
const written = Buffer.isBuffer(this._writingBuf) ? fs47.writeSync(this.fd, this._writingBuf) : fs47.writeSync(this.fd, this._writingBuf, "utf8");
|
|
7351
7379
|
release(null, written);
|
|
7352
7380
|
} catch (err) {
|
|
7353
7381
|
release(err);
|
|
7354
7382
|
}
|
|
7355
7383
|
} else {
|
|
7356
|
-
|
|
7384
|
+
fs47.write(this.fd, this._writingBuf, release);
|
|
7357
7385
|
}
|
|
7358
7386
|
}
|
|
7359
7387
|
function actualWriteBuffer() {
|
|
@@ -7362,7 +7390,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7362
7390
|
this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
|
|
7363
7391
|
if (this.sync) {
|
|
7364
7392
|
try {
|
|
7365
|
-
const written =
|
|
7393
|
+
const written = fs47.writeSync(this.fd, this._writingBuf);
|
|
7366
7394
|
release(null, written);
|
|
7367
7395
|
} catch (err) {
|
|
7368
7396
|
release(err);
|
|
@@ -7371,7 +7399,7 @@ var require_sonic_boom = __commonJS({
|
|
|
7371
7399
|
if (kCopyBuffer) {
|
|
7372
7400
|
this._writingBuf = Buffer.from(this._writingBuf);
|
|
7373
7401
|
}
|
|
7374
|
-
|
|
7402
|
+
fs47.write(this.fd, this._writingBuf, release);
|
|
7375
7403
|
}
|
|
7376
7404
|
}
|
|
7377
7405
|
function actualClose(sonic) {
|
|
@@ -7387,12 +7415,12 @@ var require_sonic_boom = __commonJS({
|
|
|
7387
7415
|
sonic._lens = [];
|
|
7388
7416
|
assert(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
|
|
7389
7417
|
try {
|
|
7390
|
-
|
|
7418
|
+
fs47.fsync(sonic.fd, closeWrapped);
|
|
7391
7419
|
} catch {
|
|
7392
7420
|
}
|
|
7393
7421
|
function closeWrapped() {
|
|
7394
7422
|
if (sonic.fd !== 1 && sonic.fd !== 2) {
|
|
7395
|
-
|
|
7423
|
+
fs47.close(sonic.fd, done);
|
|
7396
7424
|
} else {
|
|
7397
7425
|
done();
|
|
7398
7426
|
}
|
|
@@ -7647,9 +7675,9 @@ var require_thread_stream = __commonJS({
|
|
|
7647
7675
|
"../node_modules/.pnpm/thread-stream@3.1.0/node_modules/thread-stream/index.js"(exports2, module2) {
|
|
7648
7676
|
"use strict";
|
|
7649
7677
|
var { version: version2 } = require_package();
|
|
7650
|
-
var { EventEmitter:
|
|
7678
|
+
var { EventEmitter: EventEmitter2 } = require("events");
|
|
7651
7679
|
var { Worker } = require("worker_threads");
|
|
7652
|
-
var { join:
|
|
7680
|
+
var { join: join11 } = require("path");
|
|
7653
7681
|
var { pathToFileURL } = require("url");
|
|
7654
7682
|
var { wait } = require_wait();
|
|
7655
7683
|
var {
|
|
@@ -7685,7 +7713,7 @@ var require_thread_stream = __commonJS({
|
|
|
7685
7713
|
function createWorker(stream, opts) {
|
|
7686
7714
|
const { filename, workerData } = opts;
|
|
7687
7715
|
const bundlerOverrides = "__bundlerPathsOverrides" in globalThis ? globalThis.__bundlerPathsOverrides : {};
|
|
7688
|
-
const toExecute = bundlerOverrides["thread-stream-worker"] ||
|
|
7716
|
+
const toExecute = bundlerOverrides["thread-stream-worker"] || join11(__dirname, "lib", "worker.js");
|
|
7689
7717
|
const worker = new Worker(toExecute, {
|
|
7690
7718
|
...opts.workerOpts,
|
|
7691
7719
|
trackUnmanagedFds: false,
|
|
@@ -7803,7 +7831,7 @@ var require_thread_stream = __commonJS({
|
|
|
7803
7831
|
stream.worker.off("exit", onWorkerExit);
|
|
7804
7832
|
destroy(stream, code !== 0 ? new Error("the worker thread exited") : null);
|
|
7805
7833
|
}
|
|
7806
|
-
var ThreadStream = class extends
|
|
7834
|
+
var ThreadStream = class extends EventEmitter2 {
|
|
7807
7835
|
constructor(opts = {}) {
|
|
7808
7836
|
super();
|
|
7809
7837
|
if (opts.bufferSize < 4) {
|
|
@@ -8071,7 +8099,7 @@ var require_transport = __commonJS({
|
|
|
8071
8099
|
"use strict";
|
|
8072
8100
|
var { createRequire } = require("module");
|
|
8073
8101
|
var getCallers = require_caller();
|
|
8074
|
-
var { join:
|
|
8102
|
+
var { join: join11, isAbsolute: isAbsolute2, sep: sep3 } = require("path");
|
|
8075
8103
|
var sleep = require_atomic_sleep();
|
|
8076
8104
|
var onExit = require_on_exit_leak_free();
|
|
8077
8105
|
var ThreadStream = require_thread_stream();
|
|
@@ -8134,7 +8162,7 @@ var require_transport = __commonJS({
|
|
|
8134
8162
|
throw new Error("only one of target or targets can be specified");
|
|
8135
8163
|
}
|
|
8136
8164
|
if (targets) {
|
|
8137
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8165
|
+
target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
|
|
8138
8166
|
options.targets = targets.filter((dest) => dest.target).map((dest) => {
|
|
8139
8167
|
return {
|
|
8140
8168
|
...dest,
|
|
@@ -8152,7 +8180,7 @@ var require_transport = __commonJS({
|
|
|
8152
8180
|
});
|
|
8153
8181
|
});
|
|
8154
8182
|
} else if (pipeline3) {
|
|
8155
|
-
target = bundlerOverrides["pino-worker"] ||
|
|
8183
|
+
target = bundlerOverrides["pino-worker"] || join11(__dirname, "worker.js");
|
|
8156
8184
|
options.pipelines = [pipeline3.map((dest) => {
|
|
8157
8185
|
return {
|
|
8158
8186
|
...dest,
|
|
@@ -8174,7 +8202,7 @@ var require_transport = __commonJS({
|
|
|
8174
8202
|
return origin;
|
|
8175
8203
|
}
|
|
8176
8204
|
if (origin === "pino/file") {
|
|
8177
|
-
return
|
|
8205
|
+
return join11(__dirname, "..", "file.js");
|
|
8178
8206
|
}
|
|
8179
8207
|
let fixTarget2;
|
|
8180
8208
|
for (const filePath of callers) {
|
|
@@ -8761,7 +8789,7 @@ var require_meta = __commonJS({
|
|
|
8761
8789
|
var require_proto = __commonJS({
|
|
8762
8790
|
"../node_modules/.pnpm/pino@9.14.0/node_modules/pino/lib/proto.js"(exports2, module2) {
|
|
8763
8791
|
"use strict";
|
|
8764
|
-
var { EventEmitter:
|
|
8792
|
+
var { EventEmitter: EventEmitter2 } = require("events");
|
|
8765
8793
|
var {
|
|
8766
8794
|
lsCacheSym,
|
|
8767
8795
|
levelValSym,
|
|
@@ -8843,7 +8871,7 @@ var require_proto = __commonJS({
|
|
|
8843
8871
|
[getLevelSym]: getLevel,
|
|
8844
8872
|
[setLevelSym]: setLevel
|
|
8845
8873
|
};
|
|
8846
|
-
Object.setPrototypeOf(prototype,
|
|
8874
|
+
Object.setPrototypeOf(prototype, EventEmitter2.prototype);
|
|
8847
8875
|
module2.exports = function() {
|
|
8848
8876
|
return Object.create(prototype);
|
|
8849
8877
|
};
|
|
@@ -9164,7 +9192,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9164
9192
|
return circularValue;
|
|
9165
9193
|
}
|
|
9166
9194
|
let res = "";
|
|
9167
|
-
let
|
|
9195
|
+
let join11 = ",";
|
|
9168
9196
|
const originalIndentation = indentation;
|
|
9169
9197
|
if (Array.isArray(value)) {
|
|
9170
9198
|
if (value.length === 0) {
|
|
@@ -9178,7 +9206,7 @@ var require_safe_stable_stringify = __commonJS({
|
|
|
9178
9206
|
indentation += spacer;
|
|
9179
9207
|
res += `
|
|
9180
9208
|
${indentation}`;
|
|
9181
|
-
|
|
9209
|
+
join11 = `,
|
|
9182
9210
|
${indentation}`;
|
|
9183
9211
|
}
|
|
9184
9212
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9186,13 +9214,13 @@ ${indentation}`;
|
|
|
9186
9214
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9187
9215
|
const tmp2 = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9188
9216
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9189
|
-
res +=
|
|
9217
|
+
res += join11;
|
|
9190
9218
|
}
|
|
9191
9219
|
const tmp = stringifyFnReplacer(String(i), value, stack, replacer, spacer, indentation);
|
|
9192
9220
|
res += tmp !== void 0 ? tmp : "null";
|
|
9193
9221
|
if (value.length - 1 > maximumBreadth) {
|
|
9194
9222
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9195
|
-
res += `${
|
|
9223
|
+
res += `${join11}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9196
9224
|
}
|
|
9197
9225
|
if (spacer !== "") {
|
|
9198
9226
|
res += `
|
|
@@ -9213,7 +9241,7 @@ ${originalIndentation}`;
|
|
|
9213
9241
|
let separator = "";
|
|
9214
9242
|
if (spacer !== "") {
|
|
9215
9243
|
indentation += spacer;
|
|
9216
|
-
|
|
9244
|
+
join11 = `,
|
|
9217
9245
|
${indentation}`;
|
|
9218
9246
|
whitespace = " ";
|
|
9219
9247
|
}
|
|
@@ -9227,13 +9255,13 @@ ${indentation}`;
|
|
|
9227
9255
|
const tmp = stringifyFnReplacer(key2, value, stack, replacer, spacer, indentation);
|
|
9228
9256
|
if (tmp !== void 0) {
|
|
9229
9257
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9230
|
-
separator =
|
|
9258
|
+
separator = join11;
|
|
9231
9259
|
}
|
|
9232
9260
|
}
|
|
9233
9261
|
if (keyLength > maximumBreadth) {
|
|
9234
9262
|
const removedKeys = keyLength - maximumBreadth;
|
|
9235
9263
|
res += `${separator}"...":${whitespace}"${getItemCount(removedKeys)} not stringified"`;
|
|
9236
|
-
separator =
|
|
9264
|
+
separator = join11;
|
|
9237
9265
|
}
|
|
9238
9266
|
if (spacer !== "" && separator.length > 1) {
|
|
9239
9267
|
res = `
|
|
@@ -9274,7 +9302,7 @@ ${originalIndentation}`;
|
|
|
9274
9302
|
}
|
|
9275
9303
|
const originalIndentation = indentation;
|
|
9276
9304
|
let res = "";
|
|
9277
|
-
let
|
|
9305
|
+
let join11 = ",";
|
|
9278
9306
|
if (Array.isArray(value)) {
|
|
9279
9307
|
if (value.length === 0) {
|
|
9280
9308
|
return "[]";
|
|
@@ -9287,7 +9315,7 @@ ${originalIndentation}`;
|
|
|
9287
9315
|
indentation += spacer;
|
|
9288
9316
|
res += `
|
|
9289
9317
|
${indentation}`;
|
|
9290
|
-
|
|
9318
|
+
join11 = `,
|
|
9291
9319
|
${indentation}`;
|
|
9292
9320
|
}
|
|
9293
9321
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
@@ -9295,13 +9323,13 @@ ${indentation}`;
|
|
|
9295
9323
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9296
9324
|
const tmp2 = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9297
9325
|
res += tmp2 !== void 0 ? tmp2 : "null";
|
|
9298
|
-
res +=
|
|
9326
|
+
res += join11;
|
|
9299
9327
|
}
|
|
9300
9328
|
const tmp = stringifyArrayReplacer(String(i), value[i], stack, replacer, spacer, indentation);
|
|
9301
9329
|
res += tmp !== void 0 ? tmp : "null";
|
|
9302
9330
|
if (value.length - 1 > maximumBreadth) {
|
|
9303
9331
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9304
|
-
res += `${
|
|
9332
|
+
res += `${join11}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9305
9333
|
}
|
|
9306
9334
|
if (spacer !== "") {
|
|
9307
9335
|
res += `
|
|
@@ -9314,7 +9342,7 @@ ${originalIndentation}`;
|
|
|
9314
9342
|
let whitespace = "";
|
|
9315
9343
|
if (spacer !== "") {
|
|
9316
9344
|
indentation += spacer;
|
|
9317
|
-
|
|
9345
|
+
join11 = `,
|
|
9318
9346
|
${indentation}`;
|
|
9319
9347
|
whitespace = " ";
|
|
9320
9348
|
}
|
|
@@ -9323,7 +9351,7 @@ ${indentation}`;
|
|
|
9323
9351
|
const tmp = stringifyArrayReplacer(key2, value[key2], stack, replacer, spacer, indentation);
|
|
9324
9352
|
if (tmp !== void 0) {
|
|
9325
9353
|
res += `${separator}${strEscape(key2)}:${whitespace}${tmp}`;
|
|
9326
|
-
separator =
|
|
9354
|
+
separator = join11;
|
|
9327
9355
|
}
|
|
9328
9356
|
}
|
|
9329
9357
|
if (spacer !== "" && separator.length > 1) {
|
|
@@ -9381,20 +9409,20 @@ ${originalIndentation}`;
|
|
|
9381
9409
|
indentation += spacer;
|
|
9382
9410
|
let res2 = `
|
|
9383
9411
|
${indentation}`;
|
|
9384
|
-
const
|
|
9412
|
+
const join12 = `,
|
|
9385
9413
|
${indentation}`;
|
|
9386
9414
|
const maximumValuesToStringify = Math.min(value.length, maximumBreadth);
|
|
9387
9415
|
let i = 0;
|
|
9388
9416
|
for (; i < maximumValuesToStringify - 1; i++) {
|
|
9389
9417
|
const tmp2 = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9390
9418
|
res2 += tmp2 !== void 0 ? tmp2 : "null";
|
|
9391
|
-
res2 +=
|
|
9419
|
+
res2 += join12;
|
|
9392
9420
|
}
|
|
9393
9421
|
const tmp = stringifyIndent(String(i), value[i], stack, spacer, indentation);
|
|
9394
9422
|
res2 += tmp !== void 0 ? tmp : "null";
|
|
9395
9423
|
if (value.length - 1 > maximumBreadth) {
|
|
9396
9424
|
const removedKeys = value.length - maximumBreadth - 1;
|
|
9397
|
-
res2 += `${
|
|
9425
|
+
res2 += `${join12}"... ${getItemCount(removedKeys)} not stringified"`;
|
|
9398
9426
|
}
|
|
9399
9427
|
res2 += `
|
|
9400
9428
|
${originalIndentation}`;
|
|
@@ -9410,16 +9438,16 @@ ${originalIndentation}`;
|
|
|
9410
9438
|
return '"[Object]"';
|
|
9411
9439
|
}
|
|
9412
9440
|
indentation += spacer;
|
|
9413
|
-
const
|
|
9441
|
+
const join11 = `,
|
|
9414
9442
|
${indentation}`;
|
|
9415
9443
|
let res = "";
|
|
9416
9444
|
let separator = "";
|
|
9417
9445
|
let maximumPropertiesToStringify = Math.min(keyLength, maximumBreadth);
|
|
9418
9446
|
if (isTypedArrayWithEntries(value)) {
|
|
9419
|
-
res += stringifyTypedArray(value,
|
|
9447
|
+
res += stringifyTypedArray(value, join11, maximumBreadth);
|
|
9420
9448
|
keys = keys.slice(value.length);
|
|
9421
9449
|
maximumPropertiesToStringify -= value.length;
|
|
9422
|
-
separator =
|
|
9450
|
+
separator = join11;
|
|
9423
9451
|
}
|
|
9424
9452
|
if (deterministic) {
|
|
9425
9453
|
keys = sort(keys, comparator);
|
|
@@ -9430,13 +9458,13 @@ ${indentation}`;
|
|
|
9430
9458
|
const tmp = stringifyIndent(key2, value[key2], stack, spacer, indentation);
|
|
9431
9459
|
if (tmp !== void 0) {
|
|
9432
9460
|
res += `${separator}${strEscape(key2)}: ${tmp}`;
|
|
9433
|
-
separator =
|
|
9461
|
+
separator = join11;
|
|
9434
9462
|
}
|
|
9435
9463
|
}
|
|
9436
9464
|
if (keyLength > maximumBreadth) {
|
|
9437
9465
|
const removedKeys = keyLength - maximumBreadth;
|
|
9438
9466
|
res += `${separator}"...": "${getItemCount(removedKeys)} not stringified"`;
|
|
9439
|
-
separator =
|
|
9467
|
+
separator = join11;
|
|
9440
9468
|
}
|
|
9441
9469
|
if (separator !== "") {
|
|
9442
9470
|
res = `
|
|
@@ -19318,10 +19346,10 @@ var require_extension = __commonJS({
|
|
|
19318
19346
|
var require_websocket = __commonJS({
|
|
19319
19347
|
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket.js"(exports2, module2) {
|
|
19320
19348
|
"use strict";
|
|
19321
|
-
var
|
|
19349
|
+
var EventEmitter2 = require("events");
|
|
19322
19350
|
var https = require("https");
|
|
19323
|
-
var
|
|
19324
|
-
var
|
|
19351
|
+
var http2 = require("http");
|
|
19352
|
+
var net2 = require("net");
|
|
19325
19353
|
var tls = require("tls");
|
|
19326
19354
|
var { randomBytes, createHash: createHash2 } = require("crypto");
|
|
19327
19355
|
var { Duplex, Readable: Readable3 } = require("stream");
|
|
@@ -19350,7 +19378,7 @@ var require_websocket = __commonJS({
|
|
|
19350
19378
|
var protocolVersions = [8, 13];
|
|
19351
19379
|
var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
|
|
19352
19380
|
var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
|
|
19353
|
-
var WebSocket2 = class _WebSocket extends
|
|
19381
|
+
var WebSocket2 = class _WebSocket extends EventEmitter2 {
|
|
19354
19382
|
/**
|
|
19355
19383
|
* Create a new `WebSocket`.
|
|
19356
19384
|
*
|
|
@@ -19854,7 +19882,7 @@ var require_websocket = __commonJS({
|
|
|
19854
19882
|
}
|
|
19855
19883
|
const defaultPort = isSecure ? 443 : 80;
|
|
19856
19884
|
const key = randomBytes(16).toString("base64");
|
|
19857
|
-
const request = isSecure ? https.request :
|
|
19885
|
+
const request = isSecure ? https.request : http2.request;
|
|
19858
19886
|
const protocolSet = /* @__PURE__ */ new Set();
|
|
19859
19887
|
let perMessageDeflate;
|
|
19860
19888
|
opts.createConnection = opts.createConnection || (isSecure ? tlsConnect : netConnect);
|
|
@@ -20055,12 +20083,12 @@ var require_websocket = __commonJS({
|
|
|
20055
20083
|
}
|
|
20056
20084
|
function netConnect(options) {
|
|
20057
20085
|
options.path = options.socketPath;
|
|
20058
|
-
return
|
|
20086
|
+
return net2.connect(options);
|
|
20059
20087
|
}
|
|
20060
20088
|
function tlsConnect(options) {
|
|
20061
20089
|
options.path = void 0;
|
|
20062
20090
|
if (!options.servername && options.servername !== "") {
|
|
20063
|
-
options.servername =
|
|
20091
|
+
options.servername = net2.isIP(options.host) ? "" : options.host;
|
|
20064
20092
|
}
|
|
20065
20093
|
return tls.connect(options);
|
|
20066
20094
|
}
|
|
@@ -20347,8 +20375,8 @@ var require_subprotocol = __commonJS({
|
|
|
20347
20375
|
var require_websocket_server = __commonJS({
|
|
20348
20376
|
"../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports2, module2) {
|
|
20349
20377
|
"use strict";
|
|
20350
|
-
var
|
|
20351
|
-
var
|
|
20378
|
+
var EventEmitter2 = require("events");
|
|
20379
|
+
var http2 = require("http");
|
|
20352
20380
|
var { Duplex } = require("stream");
|
|
20353
20381
|
var { createHash: createHash2 } = require("crypto");
|
|
20354
20382
|
var extension2 = require_extension();
|
|
@@ -20360,7 +20388,7 @@ var require_websocket_server = __commonJS({
|
|
|
20360
20388
|
var RUNNING = 0;
|
|
20361
20389
|
var CLOSING = 1;
|
|
20362
20390
|
var CLOSED = 2;
|
|
20363
|
-
var WebSocketServer2 = class extends
|
|
20391
|
+
var WebSocketServer2 = class extends EventEmitter2 {
|
|
20364
20392
|
/**
|
|
20365
20393
|
* Create a `WebSocketServer` instance.
|
|
20366
20394
|
*
|
|
@@ -20423,8 +20451,8 @@ var require_websocket_server = __commonJS({
|
|
|
20423
20451
|
);
|
|
20424
20452
|
}
|
|
20425
20453
|
if (options.port != null) {
|
|
20426
|
-
this._server =
|
|
20427
|
-
const body =
|
|
20454
|
+
this._server = http2.createServer((req, res) => {
|
|
20455
|
+
const body = http2.STATUS_CODES[426];
|
|
20428
20456
|
res.writeHead(426, {
|
|
20429
20457
|
"Content-Length": body.length,
|
|
20430
20458
|
"Content-Type": "text/plain"
|
|
@@ -20711,7 +20739,7 @@ var require_websocket_server = __commonJS({
|
|
|
20711
20739
|
this.destroy();
|
|
20712
20740
|
}
|
|
20713
20741
|
function abortHandshake(socket, code, message, headers) {
|
|
20714
|
-
message = message ||
|
|
20742
|
+
message = message || http2.STATUS_CODES[code];
|
|
20715
20743
|
headers = {
|
|
20716
20744
|
Connection: "close",
|
|
20717
20745
|
"Content-Type": "text/html",
|
|
@@ -20720,7 +20748,7 @@ var require_websocket_server = __commonJS({
|
|
|
20720
20748
|
};
|
|
20721
20749
|
socket.once("finish", socket.destroy);
|
|
20722
20750
|
socket.end(
|
|
20723
|
-
`HTTP/1.1 ${code} ${
|
|
20751
|
+
`HTTP/1.1 ${code} ${http2.STATUS_CODES[code]}\r
|
|
20724
20752
|
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message
|
|
20725
20753
|
);
|
|
20726
20754
|
}
|
|
@@ -21023,7 +21051,7 @@ var require_BufferList = __commonJS({
|
|
|
21023
21051
|
this.head = this.tail = null;
|
|
21024
21052
|
this.length = 0;
|
|
21025
21053
|
};
|
|
21026
|
-
BufferList.prototype.join = function
|
|
21054
|
+
BufferList.prototype.join = function join11(s) {
|
|
21027
21055
|
if (this.length === 0) return "";
|
|
21028
21056
|
var p2 = this.head;
|
|
21029
21057
|
var ret = "" + p2.data;
|
|
@@ -30480,7 +30508,7 @@ var require_lib3 = __commonJS({
|
|
|
30480
30508
|
// src/run-case/recorder.ts
|
|
30481
30509
|
function startRunCaseRecorder(opts) {
|
|
30482
30510
|
const now = opts.now ?? Date.now;
|
|
30483
|
-
const dir =
|
|
30511
|
+
const dir = import_node_path45.default.dirname(opts.recordPath);
|
|
30484
30512
|
let stream = null;
|
|
30485
30513
|
let closing = false;
|
|
30486
30514
|
let closedSettled = false;
|
|
@@ -30494,8 +30522,8 @@ function startRunCaseRecorder(opts) {
|
|
|
30494
30522
|
});
|
|
30495
30523
|
const ensureStream = () => {
|
|
30496
30524
|
if (stream) return stream;
|
|
30497
|
-
|
|
30498
|
-
stream =
|
|
30525
|
+
import_node_fs30.default.mkdirSync(dir, { recursive: true });
|
|
30526
|
+
stream = import_node_fs30.default.createWriteStream(opts.recordPath, { flags: "a" });
|
|
30499
30527
|
stream.on("close", () => closedResolve());
|
|
30500
30528
|
return stream;
|
|
30501
30529
|
};
|
|
@@ -30520,12 +30548,12 @@ function startRunCaseRecorder(opts) {
|
|
|
30520
30548
|
};
|
|
30521
30549
|
return { tap, close, closed };
|
|
30522
30550
|
}
|
|
30523
|
-
var
|
|
30551
|
+
var import_node_fs30, import_node_path45;
|
|
30524
30552
|
var init_recorder = __esm({
|
|
30525
30553
|
"src/run-case/recorder.ts"() {
|
|
30526
30554
|
"use strict";
|
|
30527
|
-
|
|
30528
|
-
|
|
30555
|
+
import_node_fs30 = __toESM(require("fs"), 1);
|
|
30556
|
+
import_node_path45 = __toESM(require("path"), 1);
|
|
30529
30557
|
}
|
|
30530
30558
|
});
|
|
30531
30559
|
|
|
@@ -30568,7 +30596,7 @@ var init_wire = __esm({
|
|
|
30568
30596
|
// src/run-case/controller.ts
|
|
30569
30597
|
async function runController(opts) {
|
|
30570
30598
|
const now = opts.now ?? Date.now;
|
|
30571
|
-
const cwd = opts.cwd ?? (0,
|
|
30599
|
+
const cwd = opts.cwd ?? (0, import_node_fs31.mkdtempSync)(import_node_path46.default.join(import_node_os19.default.tmpdir(), "clawd-runcase-"));
|
|
30572
30600
|
const ownsCwd = opts.cwd === void 0;
|
|
30573
30601
|
const recorder = startRunCaseRecorder({ recordPath: opts.record, now });
|
|
30574
30602
|
const spawnCtx = { cwd };
|
|
@@ -30729,19 +30757,19 @@ async function runController(opts) {
|
|
|
30729
30757
|
if (sigintHandler) process.off("SIGINT", sigintHandler);
|
|
30730
30758
|
if (ownsCwd) {
|
|
30731
30759
|
try {
|
|
30732
|
-
(0,
|
|
30760
|
+
(0, import_node_fs31.rmSync)(cwd, { recursive: true, force: true });
|
|
30733
30761
|
} catch {
|
|
30734
30762
|
}
|
|
30735
30763
|
}
|
|
30736
30764
|
return exitCode ?? 0;
|
|
30737
30765
|
}
|
|
30738
|
-
var
|
|
30766
|
+
var import_node_fs31, import_node_os19, import_node_path46;
|
|
30739
30767
|
var init_controller = __esm({
|
|
30740
30768
|
"src/run-case/controller.ts"() {
|
|
30741
30769
|
"use strict";
|
|
30742
|
-
|
|
30770
|
+
import_node_fs31 = require("fs");
|
|
30743
30771
|
import_node_os19 = __toESM(require("os"), 1);
|
|
30744
|
-
|
|
30772
|
+
import_node_path46 = __toESM(require("path"), 1);
|
|
30745
30773
|
init_claude();
|
|
30746
30774
|
init_stdout_splitter();
|
|
30747
30775
|
init_permission_stdio();
|
|
@@ -30933,8 +30961,6 @@ function resolveConfig(opts) {
|
|
|
30933
30961
|
const frpcBinary = env.CLAWD_FRPC_BIN ?? null;
|
|
30934
30962
|
const rawMode = env.CLAWD_CC_MODE;
|
|
30935
30963
|
const mode = DAEMON_MODE_VALUES.includes(rawMode ?? "") ? rawMode : "sdk";
|
|
30936
|
-
const filePreviewPorts = Array.isArray(fileCfg.previewPorts) ? fileCfg.previewPorts.map((n) => Number(n)).filter((n) => Number.isInteger(n) && n > 0) : null;
|
|
30937
|
-
const previewPorts = env.CLAWD_PREVIEW_PORTS ? env.CLAWD_PREVIEW_PORTS.split(",").map((s) => Number(s.trim())).filter((n) => Number.isInteger(n) && n > 0) : filePreviewPorts && filePreviewPorts.length > 0 ? filePreviewPorts : null;
|
|
30938
30964
|
return {
|
|
30939
30965
|
port,
|
|
30940
30966
|
host,
|
|
@@ -30946,8 +30972,7 @@ function resolveConfig(opts) {
|
|
|
30946
30972
|
authToken,
|
|
30947
30973
|
noTunnelPersist,
|
|
30948
30974
|
frpcBinary,
|
|
30949
|
-
mode
|
|
30950
|
-
previewPorts
|
|
30975
|
+
mode
|
|
30951
30976
|
};
|
|
30952
30977
|
}
|
|
30953
30978
|
var HELP_TEXT = `clawd [options]
|
|
@@ -30976,8 +31001,8 @@ Env (advanced):
|
|
|
30976
31001
|
`;
|
|
30977
31002
|
|
|
30978
31003
|
// src/index.ts
|
|
30979
|
-
var
|
|
30980
|
-
var
|
|
31004
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
31005
|
+
var import_node_fs29 = __toESM(require("fs"), 1);
|
|
30981
31006
|
|
|
30982
31007
|
// src/logger.ts
|
|
30983
31008
|
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
@@ -32986,7 +33011,6 @@ var SessionManager = class {
|
|
|
32986
33011
|
iconKey: args.iconKey,
|
|
32987
33012
|
forkedFromSessionId: args.forkedFromSessionId,
|
|
32988
33013
|
ownerPersonaId: args.ownerPersonaId,
|
|
32989
|
-
appBuilderProject: args.appBuilderProject,
|
|
32990
33014
|
creatorPrincipalId,
|
|
32991
33015
|
...creatorOwnerPrincipalId ? { creatorOwnerPrincipalId } : {},
|
|
32992
33016
|
createdAt: iso,
|
|
@@ -34623,15 +34647,6 @@ var DEFAULT_PERSONAS = [
|
|
|
34623
34647
|
model: "opus",
|
|
34624
34648
|
iconKey: "assist",
|
|
34625
34649
|
public: false
|
|
34626
|
-
},
|
|
34627
|
-
{
|
|
34628
|
-
// app-builder:全栈应用搭建师,绑 extension-kit 模板 + multi-project picker
|
|
34629
|
-
// spec: superpowers/specs/2026-06-01-app-builder-project-picker-design.md
|
|
34630
|
-
personaId: "persona-app-builder",
|
|
34631
|
-
label: "App Builder",
|
|
34632
|
-
model: "opus",
|
|
34633
|
-
iconKey: "assist",
|
|
34634
|
-
public: false
|
|
34635
34650
|
}
|
|
34636
34651
|
];
|
|
34637
34652
|
function findDefaultsRoot() {
|
|
@@ -36328,63 +36343,13 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
36328
36343
|
var wrapper_default = import_websocket.default;
|
|
36329
36344
|
|
|
36330
36345
|
// src/transport/local-ws-server.ts
|
|
36331
|
-
var import_node_http2 = __toESM(require("http"), 1);
|
|
36332
|
-
|
|
36333
|
-
// src/transport/preview-proxy.ts
|
|
36334
36346
|
var import_node_http = __toESM(require("http"), 1);
|
|
36335
|
-
var import_node_net = __toESM(require("net"), 1);
|
|
36336
|
-
var PREVIEW_RE = /^\/preview\/(\d+)(?:\/.*)?$/;
|
|
36337
|
-
function matchPreviewPort(pathname) {
|
|
36338
|
-
const m2 = pathname.match(PREVIEW_RE);
|
|
36339
|
-
return m2 ? Number(m2[1]) : null;
|
|
36340
|
-
}
|
|
36341
|
-
function isPreviewPortAllowed(port, cfg) {
|
|
36342
|
-
return cfg.allowedPorts.includes(port);
|
|
36343
|
-
}
|
|
36344
|
-
function proxyPreviewHttp(req, res, port) {
|
|
36345
|
-
return new Promise((resolve6) => {
|
|
36346
|
-
const upstream = import_node_http.default.request(
|
|
36347
|
-
{ host: "localhost", port, method: req.method, path: req.url, headers: req.headers },
|
|
36348
|
-
(upRes) => {
|
|
36349
|
-
res.writeHead(upRes.statusCode || 502, upRes.headers);
|
|
36350
|
-
upRes.pipe(res);
|
|
36351
|
-
upRes.on("end", () => resolve6());
|
|
36352
|
-
}
|
|
36353
|
-
);
|
|
36354
|
-
upstream.on("error", (e) => {
|
|
36355
|
-
if (!res.headersSent) {
|
|
36356
|
-
res.writeHead(502, { "content-type": "text/plain; charset=utf-8" });
|
|
36357
|
-
}
|
|
36358
|
-
res.end(`preview upstream error: ${e.message}`);
|
|
36359
|
-
resolve6();
|
|
36360
|
-
});
|
|
36361
|
-
req.pipe(upstream);
|
|
36362
|
-
});
|
|
36363
|
-
}
|
|
36364
|
-
function proxyPreviewUpgrade(req, socket, head, port) {
|
|
36365
|
-
const upstream = import_node_net.default.connect(port, "localhost", () => {
|
|
36366
|
-
let raw = `${req.method} ${req.url} HTTP/1.1\r
|
|
36367
|
-
`;
|
|
36368
|
-
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
36369
|
-
raw += `${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r
|
|
36370
|
-
`;
|
|
36371
|
-
}
|
|
36372
|
-
raw += "\r\n";
|
|
36373
|
-
upstream.write(raw);
|
|
36374
|
-
if (head && head.length) upstream.write(head);
|
|
36375
|
-
upstream.pipe(socket);
|
|
36376
|
-
socket.pipe(upstream);
|
|
36377
|
-
});
|
|
36378
|
-
upstream.on("error", () => socket.destroy());
|
|
36379
|
-
socket.on("error", () => upstream.destroy());
|
|
36380
|
-
}
|
|
36381
|
-
|
|
36382
|
-
// src/transport/local-ws-server.ts
|
|
36383
36347
|
var LocalWsServer = class {
|
|
36384
36348
|
constructor(opts) {
|
|
36385
36349
|
this.opts = opts;
|
|
36386
36350
|
this.logger = opts.logger;
|
|
36387
36351
|
this.pingIntervalMs = opts.pingIntervalMs ?? 3e4;
|
|
36352
|
+
this.currentOwnerPrincipalId = opts.ownerPrincipalId;
|
|
36388
36353
|
}
|
|
36389
36354
|
opts;
|
|
36390
36355
|
wss = null;
|
|
@@ -36396,28 +36361,19 @@ var LocalWsServer = class {
|
|
|
36396
36361
|
capabilityIdToClients = /* @__PURE__ */ new Map();
|
|
36397
36362
|
logger;
|
|
36398
36363
|
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
|
+
}
|
|
36399
36371
|
async start() {
|
|
36400
36372
|
const host = this.opts.host ?? "127.0.0.1";
|
|
36401
36373
|
await new Promise((resolve6, reject) => {
|
|
36402
|
-
const httpServer =
|
|
36374
|
+
const httpServer = import_node_http.default.createServer((req, res) => this.handleHttpRequest(req, res));
|
|
36403
36375
|
const wss = new import_websocket_server.default({ noServer: true, clientTracking: true });
|
|
36404
36376
|
httpServer.on("upgrade", (req, socket, head) => {
|
|
36405
|
-
if (req.url?.startsWith("/preview/")) {
|
|
36406
|
-
const pathname = (() => {
|
|
36407
|
-
try {
|
|
36408
|
-
return new URL(req.url, "http://localhost").pathname;
|
|
36409
|
-
} catch {
|
|
36410
|
-
return "/";
|
|
36411
|
-
}
|
|
36412
|
-
})();
|
|
36413
|
-
const port = matchPreviewPort(pathname);
|
|
36414
|
-
if (port != null && (this.opts.previewPorts ?? []).includes(port)) {
|
|
36415
|
-
proxyPreviewUpgrade(req, socket, head, port);
|
|
36416
|
-
} else {
|
|
36417
|
-
socket.destroy();
|
|
36418
|
-
}
|
|
36419
|
-
return;
|
|
36420
|
-
}
|
|
36421
36377
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
36422
36378
|
this.routeConnection(ws, req);
|
|
36423
36379
|
});
|
|
@@ -36531,7 +36487,7 @@ var LocalWsServer = class {
|
|
|
36531
36487
|
// principalId === 'cap_xxx' → 该 capability 的所有 guest ws (多端 / 多 tab)
|
|
36532
36488
|
// 用于 DM inbox:event 路由: from + to 两侧各播一次, 让双方 UI 实时看到 thread 更新.
|
|
36533
36489
|
broadcastToPrincipal(principalId, frame) {
|
|
36534
|
-
if (principalId === "owner" || principalId === this.
|
|
36490
|
+
if (principalId === "owner" || principalId === this.currentOwnerPrincipalId) {
|
|
36535
36491
|
this.broadcastToOwners(frame);
|
|
36536
36492
|
return;
|
|
36537
36493
|
}
|
|
@@ -36583,6 +36539,21 @@ var LocalWsServer = class {
|
|
|
36583
36539
|
getClientContext(clientId) {
|
|
36584
36540
|
return this.clients.get(clientId)?.ctx ?? null;
|
|
36585
36541
|
}
|
|
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
|
+
}
|
|
36586
36557
|
// Task 1.10 撤销级联:关闭某 capability 的所有活跃 ws 连接。close code 4401
|
|
36587
36558
|
// 让客户端识别 'capability revoked' 而非普通断连。
|
|
36588
36559
|
closeConnectionsByCapability(capabilityId, code = 4401, reason = "TOKEN_REVOKED") {
|
|
@@ -36651,7 +36622,9 @@ var LocalWsServer = class {
|
|
|
36651
36622
|
this.logger?.warn("ready frame build failed", { err: err.message });
|
|
36652
36623
|
}
|
|
36653
36624
|
socket.on("message", (data) => {
|
|
36654
|
-
this.onMessage(handle, data, remoteAddress)
|
|
36625
|
+
void this.onMessage(handle, data, remoteAddress).catch((err) => {
|
|
36626
|
+
this.logger?.warn("onMessage failed", { err: err.message });
|
|
36627
|
+
});
|
|
36655
36628
|
});
|
|
36656
36629
|
socket.on("close", () => {
|
|
36657
36630
|
const c = this.clients.get(id);
|
|
@@ -36670,7 +36643,7 @@ var LocalWsServer = class {
|
|
|
36670
36643
|
this.logger?.warn("client socket error", { clientId: id, err: err.message });
|
|
36671
36644
|
});
|
|
36672
36645
|
}
|
|
36673
|
-
onMessage(client, data, remoteAddress) {
|
|
36646
|
+
async onMessage(client, data, remoteAddress) {
|
|
36674
36647
|
let frame;
|
|
36675
36648
|
try {
|
|
36676
36649
|
frame = JSON.parse(data.toString());
|
|
@@ -36681,7 +36654,7 @@ var LocalWsServer = class {
|
|
|
36681
36654
|
const authGate = this.opts.authGate;
|
|
36682
36655
|
if (authGate) {
|
|
36683
36656
|
const wasAuthed = authGate.isAuthed(client.id);
|
|
36684
|
-
const verdict = authGate.handleFrame({ id: client.id, remoteAddress }, frame);
|
|
36657
|
+
const verdict = await authGate.handleFrame({ id: client.id, remoteAddress }, frame);
|
|
36685
36658
|
if (verdict !== "pass") {
|
|
36686
36659
|
if (!wasAuthed && authGate.isAuthed(client.id)) {
|
|
36687
36660
|
try {
|
|
@@ -36784,7 +36757,7 @@ var AuthGate = class {
|
|
|
36784
36757
|
registerConnection(handle) {
|
|
36785
36758
|
const enforce = this.opts.shouldEnforce(handle.remoteAddress);
|
|
36786
36759
|
if (!enforce) {
|
|
36787
|
-
this.states.set(handle.id, { authed: true, enforce: false, timer: null });
|
|
36760
|
+
this.states.set(handle.id, { authed: true, enforce: false, timer: null, pending: false });
|
|
36788
36761
|
return true;
|
|
36789
36762
|
}
|
|
36790
36763
|
const setT = this.opts.setTimer ?? ((cb, ms) => setTimeout(cb, ms));
|
|
@@ -36793,7 +36766,7 @@ var AuthGate = class {
|
|
|
36793
36766
|
if (!st || st.authed) return;
|
|
36794
36767
|
this.opts.closeConnection(handle, 1008, "auth timeout");
|
|
36795
36768
|
}, this.opts.timeoutMs);
|
|
36796
|
-
this.states.set(handle.id, { authed: false, enforce: true, timer });
|
|
36769
|
+
this.states.set(handle.id, { authed: false, enforce: true, timer, pending: false });
|
|
36797
36770
|
return false;
|
|
36798
36771
|
}
|
|
36799
36772
|
unregister(handleId) {
|
|
@@ -36809,12 +36782,15 @@ var AuthGate = class {
|
|
|
36809
36782
|
}
|
|
36810
36783
|
// 收到一帧时调;返回 'consumed' / 'reject' / 'pass'
|
|
36811
36784
|
// - consumed:这一帧是 auth 帧(成功或失败),调用方不要再走业务路由
|
|
36812
|
-
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate
|
|
36785
|
+
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate 已关连接 / 验证进行中)
|
|
36813
36786
|
// - pass:已 authed,调用方继续走业务路由
|
|
36814
|
-
|
|
36787
|
+
//
|
|
36788
|
+
// Phase 2:async(authenticate 注入路径走中心 server introspect HTTP 调用)。
|
|
36789
|
+
async handleFrame(handle, frame) {
|
|
36815
36790
|
const st = this.states.get(handle.id);
|
|
36816
36791
|
if (!st) return "reject";
|
|
36817
36792
|
if (st.authed) return "pass";
|
|
36793
|
+
if (st.pending) return "reject";
|
|
36818
36794
|
const parsed = AuthRequestFrameSchema.safeParse(frame);
|
|
36819
36795
|
if (!parsed.success) {
|
|
36820
36796
|
const reason = frame.type === "auth" ? "auth failed" : "auth required";
|
|
@@ -36824,11 +36800,19 @@ var AuthGate = class {
|
|
|
36824
36800
|
}
|
|
36825
36801
|
let ctx = null;
|
|
36826
36802
|
if (this.opts.authenticate) {
|
|
36827
|
-
|
|
36828
|
-
|
|
36829
|
-
|
|
36830
|
-
|
|
36831
|
-
|
|
36803
|
+
st.pending = true;
|
|
36804
|
+
let r;
|
|
36805
|
+
try {
|
|
36806
|
+
r = await this.opts.authenticate(
|
|
36807
|
+
parsed.data.token,
|
|
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";
|
|
36832
36816
|
if (!r.ok) {
|
|
36833
36817
|
this.opts.closeConnection(handle, 4401, r.code);
|
|
36834
36818
|
this.markFailed(handle.id);
|
|
@@ -36931,26 +36915,33 @@ function ownerContext(ownerPrincipalId, displayName) {
|
|
|
36931
36915
|
grants: [{ resource: { type: "*" }, actions: ["admin"] }]
|
|
36932
36916
|
};
|
|
36933
36917
|
}
|
|
36934
|
-
function
|
|
36918
|
+
function connectGuestContext(unionId, displayName) {
|
|
36935
36919
|
return {
|
|
36936
|
-
principal: { id:
|
|
36937
|
-
grants:
|
|
36938
|
-
|
|
36939
|
-
|
|
36920
|
+
principal: { id: unionId, kind: "guest", displayName },
|
|
36921
|
+
grants: PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
36922
|
+
resource: { ...g2.resource },
|
|
36923
|
+
actions: [...g2.actions]
|
|
36924
|
+
})),
|
|
36925
|
+
capabilityId: unionId,
|
|
36926
|
+
peerOwnerPrincipalId: unionId
|
|
36940
36927
|
};
|
|
36941
36928
|
}
|
|
36942
|
-
function authenticate(token, deps) {
|
|
36929
|
+
async function authenticate(token, deps) {
|
|
36943
36930
|
if (!token) return { ok: false, code: "NO_TOKEN" };
|
|
36944
36931
|
if (deps.isOwnerToken(token)) {
|
|
36945
36932
|
return { ok: true, context: ownerContext(deps.ownerPrincipalId, deps.ownerDisplayName) };
|
|
36946
36933
|
}
|
|
36947
|
-
if (!deps.
|
|
36948
|
-
|
|
36949
|
-
|
|
36950
|
-
|
|
36951
|
-
|
|
36934
|
+
if (!deps.introspectConnectToken) return { ok: false, code: "BAD_TOKEN" };
|
|
36935
|
+
let result;
|
|
36936
|
+
try {
|
|
36937
|
+
result = await deps.introspectConnectToken(token);
|
|
36938
|
+
} catch {
|
|
36939
|
+
return { ok: false, code: "BAD_TOKEN" };
|
|
36940
|
+
}
|
|
36941
|
+
if (!result.active) {
|
|
36942
|
+
return { ok: false, code: "TOKEN_REVOKED" };
|
|
36952
36943
|
}
|
|
36953
|
-
return { ok: true, context:
|
|
36944
|
+
return { ok: true, context: connectGuestContext(result.unionId, result.displayName) };
|
|
36954
36945
|
}
|
|
36955
36946
|
|
|
36956
36947
|
// src/permission/capability-store.ts
|
|
@@ -37130,89 +37121,14 @@ function cleanupGuestSessionsForCapability(cap, factory) {
|
|
|
37130
37121
|
return { removed };
|
|
37131
37122
|
}
|
|
37132
37123
|
|
|
37133
|
-
// src/permission/personal-capability.ts
|
|
37134
|
-
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
37135
|
-
var import_node_path16 = __toESM(require("path"), 1);
|
|
37136
|
-
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
37137
|
-
var PERSONAL_CAP_FILE_NAME = "personal-capability.json";
|
|
37138
|
-
var PERSONAL_CAP_DISPLAY_NAME = "personal";
|
|
37139
|
-
function personalCapFilePath(dataDir) {
|
|
37140
|
-
return import_node_path16.default.join(dataDir, PERSONAL_CAP_FILE_NAME);
|
|
37141
|
-
}
|
|
37142
|
-
function loadOrCreatePersonalCapability(opts) {
|
|
37143
|
-
const file = personalCapFilePath(opts.dataDir);
|
|
37144
|
-
const generateToken = opts.generateToken ?? defaultGenerateToken;
|
|
37145
|
-
const generateId = opts.generateId ?? defaultGenerateId;
|
|
37146
|
-
const now = opts.now ?? Date.now;
|
|
37147
|
-
const existing = readFile(file);
|
|
37148
|
-
if (existing) return existing;
|
|
37149
|
-
const token = generateToken();
|
|
37150
|
-
const id = generateId();
|
|
37151
|
-
const capability = {
|
|
37152
|
-
id,
|
|
37153
|
-
secretHash: sha256Hex2(token),
|
|
37154
|
-
displayName: PERSONAL_CAP_DISPLAY_NAME,
|
|
37155
|
-
// CapabilitySchema 期待 mutable Grant[],protocol 常量是 readonly,落盘前拷一份
|
|
37156
|
-
grants: PERSONAL_CAP_GRANTS.map((g2) => ({
|
|
37157
|
-
resource: { ...g2.resource },
|
|
37158
|
-
actions: [...g2.actions]
|
|
37159
|
-
})),
|
|
37160
|
-
issuedAt: now(),
|
|
37161
|
-
usedCount: 0
|
|
37162
|
-
};
|
|
37163
|
-
const content = { token, capability };
|
|
37164
|
-
writeFile(file, content);
|
|
37165
|
-
return content;
|
|
37166
|
-
}
|
|
37167
|
-
function readFile(file) {
|
|
37168
|
-
let raw;
|
|
37169
|
-
try {
|
|
37170
|
-
raw = import_node_fs15.default.readFileSync(file, "utf8");
|
|
37171
|
-
} catch (err) {
|
|
37172
|
-
const code = err?.code;
|
|
37173
|
-
if (code === "ENOENT") return null;
|
|
37174
|
-
return null;
|
|
37175
|
-
}
|
|
37176
|
-
let parsed;
|
|
37177
|
-
try {
|
|
37178
|
-
parsed = JSON.parse(raw);
|
|
37179
|
-
} catch {
|
|
37180
|
-
return null;
|
|
37181
|
-
}
|
|
37182
|
-
if (!parsed || typeof parsed !== "object") return null;
|
|
37183
|
-
const obj = parsed;
|
|
37184
|
-
if (typeof obj.token !== "string" || obj.token.length === 0) return null;
|
|
37185
|
-
const capParsed = CapabilitySchema.safeParse(obj.capability);
|
|
37186
|
-
if (!capParsed.success) return null;
|
|
37187
|
-
if (sha256Hex2(obj.token) !== capParsed.data.secretHash) return null;
|
|
37188
|
-
return { token: obj.token, capability: capParsed.data };
|
|
37189
|
-
}
|
|
37190
|
-
function writeFile(file, content) {
|
|
37191
|
-
import_node_fs15.default.mkdirSync(import_node_path16.default.dirname(file), { recursive: true });
|
|
37192
|
-
import_node_fs15.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
37193
|
-
try {
|
|
37194
|
-
import_node_fs15.default.chmodSync(file, 384);
|
|
37195
|
-
} catch {
|
|
37196
|
-
}
|
|
37197
|
-
}
|
|
37198
|
-
function defaultGenerateToken() {
|
|
37199
|
-
return import_node_crypto4.default.randomBytes(24).toString("base64url");
|
|
37200
|
-
}
|
|
37201
|
-
function defaultGenerateId() {
|
|
37202
|
-
return "personal_" + import_node_crypto4.default.randomBytes(6).toString("base64url");
|
|
37203
|
-
}
|
|
37204
|
-
function sha256Hex2(s) {
|
|
37205
|
-
return import_node_crypto4.default.createHash("sha256").update(s).digest("hex");
|
|
37206
|
-
}
|
|
37207
|
-
|
|
37208
37124
|
// src/inbox/inbox-store.ts
|
|
37209
|
-
var
|
|
37210
|
-
var
|
|
37125
|
+
var fs19 = __toESM(require("fs"), 1);
|
|
37126
|
+
var path21 = __toESM(require("path"), 1);
|
|
37211
37127
|
var INBOX_SUBDIR = "inbox";
|
|
37212
37128
|
var InboxStore = class {
|
|
37213
37129
|
constructor(dataDir) {
|
|
37214
37130
|
this.dataDir = dataDir;
|
|
37215
|
-
|
|
37131
|
+
fs19.mkdirSync(this.dirPath(), { recursive: true });
|
|
37216
37132
|
}
|
|
37217
37133
|
dataDir;
|
|
37218
37134
|
/**
|
|
@@ -37224,7 +37140,7 @@ var InboxStore = class {
|
|
|
37224
37140
|
const file = this.filePath(peerOwnerId);
|
|
37225
37141
|
let raw;
|
|
37226
37142
|
try {
|
|
37227
|
-
raw =
|
|
37143
|
+
raw = fs19.readFileSync(file, "utf8");
|
|
37228
37144
|
} catch (err) {
|
|
37229
37145
|
if (err?.code === "ENOENT") return [];
|
|
37230
37146
|
return [];
|
|
@@ -37240,7 +37156,7 @@ var InboxStore = class {
|
|
|
37240
37156
|
const dir = this.dirPath();
|
|
37241
37157
|
let entries;
|
|
37242
37158
|
try {
|
|
37243
|
-
entries =
|
|
37159
|
+
entries = fs19.readdirSync(dir);
|
|
37244
37160
|
} catch (err) {
|
|
37245
37161
|
if (err?.code === "ENOENT") return [];
|
|
37246
37162
|
return [];
|
|
@@ -37256,9 +37172,9 @@ var InboxStore = class {
|
|
|
37256
37172
|
if (existing.some((m2) => m2.id === message.id)) return;
|
|
37257
37173
|
const file = this.filePath(message.peerOwnerId);
|
|
37258
37174
|
const line = JSON.stringify(message) + "\n";
|
|
37259
|
-
|
|
37175
|
+
fs19.appendFileSync(file, line, { mode: 384 });
|
|
37260
37176
|
try {
|
|
37261
|
-
|
|
37177
|
+
fs19.chmodSync(file, 384);
|
|
37262
37178
|
} catch {
|
|
37263
37179
|
}
|
|
37264
37180
|
}
|
|
@@ -37288,7 +37204,7 @@ var InboxStore = class {
|
|
|
37288
37204
|
removeByPeerOwnerId(peerOwnerId) {
|
|
37289
37205
|
const file = this.filePath(peerOwnerId);
|
|
37290
37206
|
try {
|
|
37291
|
-
|
|
37207
|
+
fs19.unlinkSync(file);
|
|
37292
37208
|
} catch (err) {
|
|
37293
37209
|
if (err?.code === "ENOENT") return;
|
|
37294
37210
|
}
|
|
@@ -37297,18 +37213,18 @@ var InboxStore = class {
|
|
|
37297
37213
|
const file = this.filePath(peerOwnerId);
|
|
37298
37214
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
37299
37215
|
const content = messages.map((m2) => JSON.stringify(m2)).join("\n") + (messages.length > 0 ? "\n" : "");
|
|
37300
|
-
|
|
37301
|
-
|
|
37216
|
+
fs19.writeFileSync(tmp, content, { mode: 384 });
|
|
37217
|
+
fs19.renameSync(tmp, file);
|
|
37302
37218
|
try {
|
|
37303
|
-
|
|
37219
|
+
fs19.chmodSync(file, 384);
|
|
37304
37220
|
} catch {
|
|
37305
37221
|
}
|
|
37306
37222
|
}
|
|
37307
37223
|
dirPath() {
|
|
37308
|
-
return
|
|
37224
|
+
return path21.join(this.dataDir, INBOX_SUBDIR);
|
|
37309
37225
|
}
|
|
37310
37226
|
filePath(peerOwnerId) {
|
|
37311
|
-
return
|
|
37227
|
+
return path21.join(this.dirPath(), `${peerOwnerId}.jsonl`);
|
|
37312
37228
|
}
|
|
37313
37229
|
};
|
|
37314
37230
|
function parseAllLines(raw) {
|
|
@@ -37395,8 +37311,8 @@ var InboxManager = class {
|
|
|
37395
37311
|
};
|
|
37396
37312
|
|
|
37397
37313
|
// src/state/received-capability-store.ts
|
|
37398
|
-
var
|
|
37399
|
-
var
|
|
37314
|
+
var fs20 = __toESM(require("fs"), 1);
|
|
37315
|
+
var path22 = __toESM(require("path"), 1);
|
|
37400
37316
|
var FILE_NAME = "received-capabilities.json";
|
|
37401
37317
|
var ReceivedCapabilityStore = class {
|
|
37402
37318
|
constructor(dataDir) {
|
|
@@ -37406,10 +37322,10 @@ var ReceivedCapabilityStore = class {
|
|
|
37406
37322
|
caps = /* @__PURE__ */ new Map();
|
|
37407
37323
|
load() {
|
|
37408
37324
|
this.caps.clear();
|
|
37409
|
-
const file =
|
|
37325
|
+
const file = path22.join(this.dataDir, FILE_NAME);
|
|
37410
37326
|
let raw;
|
|
37411
37327
|
try {
|
|
37412
|
-
raw =
|
|
37328
|
+
raw = fs20.readFileSync(file, "utf8");
|
|
37413
37329
|
} catch (err) {
|
|
37414
37330
|
if (err?.code !== "ENOENT") this.renameBak(file);
|
|
37415
37331
|
return;
|
|
@@ -37446,27 +37362,27 @@ var ReceivedCapabilityStore = class {
|
|
|
37446
37362
|
this.flush();
|
|
37447
37363
|
}
|
|
37448
37364
|
flush() {
|
|
37449
|
-
const file =
|
|
37365
|
+
const file = path22.join(this.dataDir, FILE_NAME);
|
|
37450
37366
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
37451
37367
|
const content = JSON.stringify(
|
|
37452
37368
|
{ receivedCaps: Array.from(this.caps.values()) },
|
|
37453
37369
|
null,
|
|
37454
37370
|
2
|
|
37455
37371
|
);
|
|
37456
|
-
|
|
37457
|
-
|
|
37458
|
-
|
|
37372
|
+
fs20.mkdirSync(this.dataDir, { recursive: true });
|
|
37373
|
+
fs20.writeFileSync(tmp, content, { mode: 384 });
|
|
37374
|
+
fs20.renameSync(tmp, file);
|
|
37459
37375
|
}
|
|
37460
37376
|
renameBak(file) {
|
|
37461
37377
|
try {
|
|
37462
|
-
|
|
37378
|
+
fs20.renameSync(file, `${file}.bak`);
|
|
37463
37379
|
} catch {
|
|
37464
37380
|
}
|
|
37465
37381
|
}
|
|
37466
37382
|
};
|
|
37467
37383
|
|
|
37468
37384
|
// src/received-capability/connect-remote.ts
|
|
37469
|
-
var
|
|
37385
|
+
var crypto5 = __toESM(require("crypto"), 1);
|
|
37470
37386
|
var HANDSHAKE_TIMEOUT_MS = 5e3;
|
|
37471
37387
|
var RPC_TIMEOUT_MS = 5e3;
|
|
37472
37388
|
async function connectRemote(args) {
|
|
@@ -37484,7 +37400,10 @@ async function connectRemote(args) {
|
|
|
37484
37400
|
type: "auth",
|
|
37485
37401
|
token: args.token,
|
|
37486
37402
|
selfPrincipalId: args.selfPrincipalId,
|
|
37487
|
-
selfDisplayName: args.selfDisplayName
|
|
37403
|
+
selfDisplayName: args.selfDisplayName,
|
|
37404
|
+
// Phase 2 自动反向(spec 决策 #11):带本机可达地址时 hub 反向建联系人;
|
|
37405
|
+
// 缺省时省略字段(不发空串),hub 端 schema selfUrl 是 .min(1).optional()
|
|
37406
|
+
...args.selfUrl ? { selfUrl: args.selfUrl } : {}
|
|
37488
37407
|
})
|
|
37489
37408
|
);
|
|
37490
37409
|
await new Promise((resolve6, reject) => {
|
|
@@ -37512,7 +37431,7 @@ async function connectRemote(args) {
|
|
|
37512
37431
|
}, HANDSHAKE_TIMEOUT_MS);
|
|
37513
37432
|
});
|
|
37514
37433
|
function call(method, payload) {
|
|
37515
|
-
const requestId =
|
|
37434
|
+
const requestId = crypto5.randomUUID();
|
|
37516
37435
|
return new Promise((resolve6, reject) => {
|
|
37517
37436
|
const onMessage = (raw) => {
|
|
37518
37437
|
let f;
|
|
@@ -37556,62 +37475,85 @@ async function connectRemote(args) {
|
|
|
37556
37475
|
};
|
|
37557
37476
|
}
|
|
37558
37477
|
|
|
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
|
+
|
|
37559
37501
|
// src/migrations/2026-05-20-flatten-sessions.ts
|
|
37560
|
-
var
|
|
37561
|
-
var
|
|
37502
|
+
var fs21 = __toESM(require("fs"), 1);
|
|
37503
|
+
var path23 = __toESM(require("path"), 1);
|
|
37562
37504
|
var MIGRATION_FLAG_NAME = ".migration.v1.done";
|
|
37563
37505
|
function migrateFlattenSessions(opts) {
|
|
37564
37506
|
const dataDir = opts.dataDir;
|
|
37565
37507
|
const now = opts.now ?? Date.now;
|
|
37566
|
-
const sessionsDir =
|
|
37567
|
-
const flagPath =
|
|
37508
|
+
const sessionsDir = path23.join(dataDir, "sessions");
|
|
37509
|
+
const flagPath = path23.join(sessionsDir, MIGRATION_FLAG_NAME);
|
|
37568
37510
|
if (existsSync3(flagPath)) {
|
|
37569
37511
|
return { skipped: true, flagWritten: false, movedBare: 0, movedVmOwner: 0, archivedListener: 0 };
|
|
37570
37512
|
}
|
|
37571
37513
|
let movedBare = 0;
|
|
37572
37514
|
let movedVmOwner = 0;
|
|
37573
37515
|
let archivedListener = 0;
|
|
37574
|
-
const defaultDir =
|
|
37516
|
+
const defaultDir = path23.join(sessionsDir, "default");
|
|
37575
37517
|
if (existsSync3(defaultDir)) {
|
|
37576
37518
|
for (const entry of readdirSafe(defaultDir)) {
|
|
37577
37519
|
if (!entry.endsWith(".json")) continue;
|
|
37578
|
-
const src =
|
|
37579
|
-
const dst =
|
|
37580
|
-
|
|
37520
|
+
const src = path23.join(defaultDir, entry);
|
|
37521
|
+
const dst = path23.join(sessionsDir, entry);
|
|
37522
|
+
fs21.renameSync(src, dst);
|
|
37581
37523
|
movedBare += 1;
|
|
37582
37524
|
}
|
|
37583
37525
|
rmdirIfEmpty(defaultDir);
|
|
37584
37526
|
}
|
|
37585
37527
|
for (const pid of readdirSafe(sessionsDir)) {
|
|
37586
|
-
const personaDir =
|
|
37528
|
+
const personaDir = path23.join(sessionsDir, pid);
|
|
37587
37529
|
if (!isDir(personaDir)) continue;
|
|
37588
37530
|
if (pid === "default") continue;
|
|
37589
|
-
const ownerSrc =
|
|
37531
|
+
const ownerSrc = path23.join(personaDir, "owner");
|
|
37590
37532
|
if (existsSync3(ownerSrc) && isDir(ownerSrc)) {
|
|
37591
|
-
const ownerDst =
|
|
37592
|
-
|
|
37533
|
+
const ownerDst = path23.join(dataDir, "personas", pid, ".clawd", "sessions", "owner");
|
|
37534
|
+
fs21.mkdirSync(ownerDst, { recursive: true });
|
|
37593
37535
|
for (const file of readdirSafe(ownerSrc)) {
|
|
37594
37536
|
if (!file.endsWith(".json")) continue;
|
|
37595
|
-
|
|
37537
|
+
fs21.renameSync(path23.join(ownerSrc, file), path23.join(ownerDst, file));
|
|
37596
37538
|
movedVmOwner += 1;
|
|
37597
37539
|
}
|
|
37598
37540
|
rmdirIfEmpty(ownerSrc);
|
|
37599
37541
|
}
|
|
37600
|
-
const listenerSrc =
|
|
37542
|
+
const listenerSrc = path23.join(personaDir, "listener");
|
|
37601
37543
|
if (existsSync3(listenerSrc) && isDir(listenerSrc)) {
|
|
37602
|
-
const archiveDst =
|
|
37603
|
-
|
|
37544
|
+
const archiveDst = path23.join(dataDir, ".legacy", `listener-${pid}`);
|
|
37545
|
+
fs21.mkdirSync(archiveDst, { recursive: true });
|
|
37604
37546
|
for (const file of readdirSafe(listenerSrc)) {
|
|
37605
37547
|
if (!file.endsWith(".json")) continue;
|
|
37606
|
-
|
|
37548
|
+
fs21.renameSync(path23.join(listenerSrc, file), path23.join(archiveDst, file));
|
|
37607
37549
|
archivedListener += 1;
|
|
37608
37550
|
}
|
|
37609
37551
|
rmdirIfEmpty(listenerSrc);
|
|
37610
37552
|
}
|
|
37611
37553
|
rmdirIfEmpty(personaDir);
|
|
37612
37554
|
}
|
|
37613
|
-
|
|
37614
|
-
|
|
37555
|
+
fs21.mkdirSync(sessionsDir, { recursive: true });
|
|
37556
|
+
fs21.writeFileSync(flagPath, JSON.stringify({ migratedAt: now() }, null, 2));
|
|
37615
37557
|
return {
|
|
37616
37558
|
skipped: false,
|
|
37617
37559
|
flagWritten: true,
|
|
@@ -37622,7 +37564,7 @@ function migrateFlattenSessions(opts) {
|
|
|
37622
37564
|
}
|
|
37623
37565
|
function existsSync3(p2) {
|
|
37624
37566
|
try {
|
|
37625
|
-
|
|
37567
|
+
fs21.statSync(p2);
|
|
37626
37568
|
return true;
|
|
37627
37569
|
} catch {
|
|
37628
37570
|
return false;
|
|
@@ -37630,31 +37572,31 @@ function existsSync3(p2) {
|
|
|
37630
37572
|
}
|
|
37631
37573
|
function isDir(p2) {
|
|
37632
37574
|
try {
|
|
37633
|
-
return
|
|
37575
|
+
return fs21.statSync(p2).isDirectory();
|
|
37634
37576
|
} catch {
|
|
37635
37577
|
return false;
|
|
37636
37578
|
}
|
|
37637
37579
|
}
|
|
37638
37580
|
function readdirSafe(p2) {
|
|
37639
37581
|
try {
|
|
37640
|
-
return
|
|
37582
|
+
return fs21.readdirSync(p2);
|
|
37641
37583
|
} catch {
|
|
37642
37584
|
return [];
|
|
37643
37585
|
}
|
|
37644
37586
|
}
|
|
37645
37587
|
function rmdirIfEmpty(p2) {
|
|
37646
37588
|
try {
|
|
37647
|
-
|
|
37589
|
+
fs21.rmdirSync(p2);
|
|
37648
37590
|
} catch {
|
|
37649
37591
|
}
|
|
37650
37592
|
}
|
|
37651
37593
|
|
|
37652
37594
|
// src/transport/http-router.ts
|
|
37653
|
-
var
|
|
37654
|
-
var
|
|
37595
|
+
var import_node_fs16 = __toESM(require("fs"), 1);
|
|
37596
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
37655
37597
|
|
|
37656
37598
|
// src/attachment/mime.ts
|
|
37657
|
-
var
|
|
37599
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
37658
37600
|
var TEXT_PLAIN = "text/plain; charset=utf-8";
|
|
37659
37601
|
var EXT_TO_NATIVE_MIME = {
|
|
37660
37602
|
// 图片
|
|
@@ -37761,14 +37703,14 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
37761
37703
|
".mk"
|
|
37762
37704
|
]);
|
|
37763
37705
|
function lookupMime(filePathOrName) {
|
|
37764
|
-
const ext =
|
|
37706
|
+
const ext = import_node_path16.default.extname(filePathOrName).toLowerCase();
|
|
37765
37707
|
if (EXT_TO_NATIVE_MIME[ext]) return EXT_TO_NATIVE_MIME[ext];
|
|
37766
37708
|
if (TEXT_EXTENSIONS.has(ext)) return TEXT_PLAIN;
|
|
37767
37709
|
return "application/octet-stream";
|
|
37768
37710
|
}
|
|
37769
37711
|
|
|
37770
37712
|
// src/attachment/sign-url.ts
|
|
37771
|
-
var
|
|
37713
|
+
var import_node_crypto4 = __toESM(require("crypto"), 1);
|
|
37772
37714
|
var HMAC_ALGO = "sha256";
|
|
37773
37715
|
function base64urlEncode(buf) {
|
|
37774
37716
|
const b2 = typeof buf === "string" ? Buffer.from(buf, "utf8") : buf;
|
|
@@ -37785,7 +37727,7 @@ function decodeAbsPathFromUrl(encoded) {
|
|
|
37785
37727
|
}
|
|
37786
37728
|
function computeSig(secret, absPath, e) {
|
|
37787
37729
|
const msg = e === null ? absPath : `${absPath}|${e}`;
|
|
37788
|
-
return
|
|
37730
|
+
return import_node_crypto4.default.createHmac(HMAC_ALGO, secret).update(msg).digest();
|
|
37789
37731
|
}
|
|
37790
37732
|
function signUrlParts(secret, absPath, ttlSeconds, now = Date.now) {
|
|
37791
37733
|
const e = ttlSeconds === null ? null : Math.floor(now() / 1e3) + ttlSeconds;
|
|
@@ -37820,7 +37762,7 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
37820
37762
|
if (provided.length !== expected.length) {
|
|
37821
37763
|
return { ok: false, code: "BAD_SIG" };
|
|
37822
37764
|
}
|
|
37823
|
-
if (!
|
|
37765
|
+
if (!import_node_crypto4.default.timingSafeEqual(provided, expected)) {
|
|
37824
37766
|
return { ok: false, code: "BAD_SIG" };
|
|
37825
37767
|
}
|
|
37826
37768
|
if (e !== null && now() / 1e3 > e) {
|
|
@@ -37830,9 +37772,9 @@ function verifySignedUrl(secret, absPath, eRaw, s, now = Date.now) {
|
|
|
37830
37772
|
}
|
|
37831
37773
|
|
|
37832
37774
|
// src/attachment/upload.ts
|
|
37833
|
-
var
|
|
37834
|
-
var
|
|
37835
|
-
var
|
|
37775
|
+
var import_node_fs15 = __toESM(require("fs"), 1);
|
|
37776
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
37777
|
+
var import_node_crypto5 = __toESM(require("crypto"), 1);
|
|
37836
37778
|
var import_promises = require("stream/promises");
|
|
37837
37779
|
var UploadError = class extends Error {
|
|
37838
37780
|
constructor(code, message) {
|
|
@@ -37843,24 +37785,24 @@ var UploadError = class extends Error {
|
|
|
37843
37785
|
code;
|
|
37844
37786
|
};
|
|
37845
37787
|
function assertValidFileName(fileName) {
|
|
37846
|
-
if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !==
|
|
37788
|
+
if (fileName.length === 0 || fileName === "." || fileName === ".." || fileName.startsWith(".") || fileName.includes("/") || fileName.includes("\\") || fileName !== import_node_path17.default.basename(fileName)) {
|
|
37847
37789
|
throw new UploadError("INVALID_FILENAME", `fileName must be a plain basename, got: ${fileName}`);
|
|
37848
37790
|
}
|
|
37849
37791
|
}
|
|
37850
37792
|
var HASH_PREFIX_LEN = 16;
|
|
37851
37793
|
async function writeUploadedAttachment(args) {
|
|
37852
37794
|
assertValidFileName(args.fileName);
|
|
37853
|
-
const attachmentsRoot =
|
|
37795
|
+
const attachmentsRoot = import_node_path17.default.join(args.sessionDir, ".attachments");
|
|
37854
37796
|
try {
|
|
37855
|
-
|
|
37797
|
+
import_node_fs15.default.mkdirSync(attachmentsRoot, { recursive: true });
|
|
37856
37798
|
} catch (err) {
|
|
37857
37799
|
throw new UploadError("STORAGE_ERROR", `mkdir failed: ${err.message}`);
|
|
37858
37800
|
}
|
|
37859
|
-
const hasher =
|
|
37801
|
+
const hasher = import_node_crypto5.default.createHash("sha256");
|
|
37860
37802
|
let actualSize = 0;
|
|
37861
|
-
const tmpPath =
|
|
37803
|
+
const tmpPath = import_node_path17.default.join(
|
|
37862
37804
|
attachmentsRoot,
|
|
37863
|
-
`.upload-${process.pid}-${Date.now()}-${
|
|
37805
|
+
`.upload-${process.pid}-${Date.now()}-${import_node_crypto5.default.randomBytes(4).toString("hex")}`
|
|
37864
37806
|
);
|
|
37865
37807
|
try {
|
|
37866
37808
|
await (0, import_promises.pipeline)(
|
|
@@ -37873,18 +37815,18 @@ async function writeUploadedAttachment(args) {
|
|
|
37873
37815
|
yield buf;
|
|
37874
37816
|
}
|
|
37875
37817
|
},
|
|
37876
|
-
|
|
37818
|
+
import_node_fs15.default.createWriteStream(tmpPath, { mode: 384 })
|
|
37877
37819
|
);
|
|
37878
37820
|
} catch (err) {
|
|
37879
37821
|
try {
|
|
37880
|
-
|
|
37822
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
37881
37823
|
} catch {
|
|
37882
37824
|
}
|
|
37883
37825
|
throw new UploadError("STORAGE_ERROR", `write failed: ${err.message}`);
|
|
37884
37826
|
}
|
|
37885
37827
|
if (actualSize !== args.contentLength) {
|
|
37886
37828
|
try {
|
|
37887
|
-
|
|
37829
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
37888
37830
|
} catch {
|
|
37889
37831
|
}
|
|
37890
37832
|
throw new UploadError(
|
|
@@ -37893,35 +37835,35 @@ async function writeUploadedAttachment(args) {
|
|
|
37893
37835
|
);
|
|
37894
37836
|
}
|
|
37895
37837
|
const attachmentId = hasher.digest("hex").slice(0, HASH_PREFIX_LEN);
|
|
37896
|
-
const hashDir =
|
|
37838
|
+
const hashDir = import_node_path17.default.join(attachmentsRoot, attachmentId);
|
|
37897
37839
|
let finalFileName;
|
|
37898
37840
|
let hashDirExists = false;
|
|
37899
37841
|
try {
|
|
37900
|
-
hashDirExists =
|
|
37842
|
+
hashDirExists = import_node_fs15.default.statSync(hashDir).isDirectory();
|
|
37901
37843
|
} catch {
|
|
37902
37844
|
}
|
|
37903
37845
|
if (hashDirExists) {
|
|
37904
|
-
const existing =
|
|
37846
|
+
const existing = import_node_fs15.default.readdirSync(hashDir).filter((n) => !n.startsWith("."));
|
|
37905
37847
|
finalFileName = existing[0] ?? args.fileName;
|
|
37906
37848
|
try {
|
|
37907
|
-
|
|
37849
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
37908
37850
|
} catch {
|
|
37909
37851
|
}
|
|
37910
37852
|
} else {
|
|
37911
37853
|
try {
|
|
37912
|
-
|
|
37854
|
+
import_node_fs15.default.mkdirSync(hashDir, { recursive: true });
|
|
37913
37855
|
finalFileName = args.fileName;
|
|
37914
|
-
|
|
37856
|
+
import_node_fs15.default.renameSync(tmpPath, import_node_path17.default.join(hashDir, finalFileName));
|
|
37915
37857
|
} catch (err) {
|
|
37916
37858
|
try {
|
|
37917
|
-
|
|
37859
|
+
import_node_fs15.default.unlinkSync(tmpPath);
|
|
37918
37860
|
} catch {
|
|
37919
37861
|
}
|
|
37920
37862
|
throw new UploadError("STORAGE_ERROR", `rename failed: ${err.message}`);
|
|
37921
37863
|
}
|
|
37922
37864
|
}
|
|
37923
|
-
const absPath =
|
|
37924
|
-
const relPath =
|
|
37865
|
+
const absPath = import_node_path17.default.join(hashDir, finalFileName);
|
|
37866
|
+
const relPath = import_node_path17.default.relative(args.sessionDir, absPath);
|
|
37925
37867
|
args.groupFileStore.upsert(args.scope, args.sessionId, {
|
|
37926
37868
|
relPath: absPath,
|
|
37927
37869
|
// 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
|
|
@@ -37935,7 +37877,7 @@ async function writeUploadedAttachment(args) {
|
|
|
37935
37877
|
|
|
37936
37878
|
// src/extension/import.ts
|
|
37937
37879
|
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
37938
|
-
var
|
|
37880
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
37939
37881
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
37940
37882
|
var import_jszip = __toESM(require_lib3(), 1);
|
|
37941
37883
|
var ImportError = class extends Error {
|
|
@@ -37953,7 +37895,7 @@ async function importZip(buf, root) {
|
|
|
37953
37895
|
throw new ImportError("ZIP_INVALID", `failed to load zip: ${e.message}`);
|
|
37954
37896
|
}
|
|
37955
37897
|
for (const name of Object.keys(zip.files)) {
|
|
37956
|
-
if (name.includes("..") || name.startsWith("/") ||
|
|
37898
|
+
if (name.includes("..") || name.startsWith("/") || import_node_path18.default.isAbsolute(name)) {
|
|
37957
37899
|
throw new ImportError("ZIP_INVALID", `unsafe zip entry path: ${name}`);
|
|
37958
37900
|
}
|
|
37959
37901
|
}
|
|
@@ -37986,7 +37928,7 @@ async function importZip(buf, root) {
|
|
|
37986
37928
|
);
|
|
37987
37929
|
}
|
|
37988
37930
|
}
|
|
37989
|
-
const destDir =
|
|
37931
|
+
const destDir = import_node_path18.default.join(root, manifest.id);
|
|
37990
37932
|
let destExists = false;
|
|
37991
37933
|
try {
|
|
37992
37934
|
await import_promises2.default.access(destDir);
|
|
@@ -37996,15 +37938,15 @@ async function importZip(buf, root) {
|
|
|
37996
37938
|
if (destExists) {
|
|
37997
37939
|
throw new ImportError("ALREADY_EXISTS", `extension ${manifest.id} already installed`);
|
|
37998
37940
|
}
|
|
37999
|
-
const stage = await import_promises2.default.mkdtemp(
|
|
37941
|
+
const stage = await import_promises2.default.mkdtemp(import_node_path18.default.join(import_node_os10.default.tmpdir(), `clawd-ext-stage-${manifest.id}-`));
|
|
38000
37942
|
try {
|
|
38001
37943
|
for (const [name, entry] of Object.entries(zip.files)) {
|
|
38002
|
-
const dest =
|
|
37944
|
+
const dest = import_node_path18.default.join(stage, name);
|
|
38003
37945
|
if (entry.dir) {
|
|
38004
37946
|
await import_promises2.default.mkdir(dest, { recursive: true });
|
|
38005
37947
|
continue;
|
|
38006
37948
|
}
|
|
38007
|
-
await import_promises2.default.mkdir(
|
|
37949
|
+
await import_promises2.default.mkdir(import_node_path18.default.dirname(dest), { recursive: true });
|
|
38008
37950
|
const content = await entry.async("nodebuffer");
|
|
38009
37951
|
await import_promises2.default.writeFile(dest, content);
|
|
38010
37952
|
}
|
|
@@ -38050,7 +37992,7 @@ function isValidUploadFileName(fileName) {
|
|
|
38050
37992
|
if (fileName === "." || fileName === "..") return false;
|
|
38051
37993
|
if (fileName.startsWith(".")) return false;
|
|
38052
37994
|
if (fileName.includes("/") || fileName.includes("\\")) return false;
|
|
38053
|
-
return fileName ===
|
|
37995
|
+
return fileName === import_node_path19.default.basename(fileName);
|
|
38054
37996
|
}
|
|
38055
37997
|
function createHttpRouter(deps) {
|
|
38056
37998
|
return async (req, res) => {
|
|
@@ -38065,13 +38007,23 @@ function createHttpRouter(deps) {
|
|
|
38065
38007
|
});
|
|
38066
38008
|
return true;
|
|
38067
38009
|
}
|
|
38068
|
-
if (url.pathname
|
|
38069
|
-
|
|
38070
|
-
|
|
38071
|
-
|
|
38010
|
+
if (url.pathname === "/auth/callback") {
|
|
38011
|
+
if (req.method !== "GET") {
|
|
38012
|
+
sendJson(res, 404, { code: "NOT_FOUND", message: `no route for ${req.method} ${url.pathname}` });
|
|
38013
|
+
return true;
|
|
38014
|
+
}
|
|
38015
|
+
if (!deps.feishuLogin) {
|
|
38016
|
+
sendJson(res, 501, { code: "NOT_IMPLEMENTED", message: "feishu login not wired" });
|
|
38072
38017
|
return true;
|
|
38073
38018
|
}
|
|
38074
|
-
|
|
38019
|
+
const result = await deps.feishuLogin.handleLoginCallback({
|
|
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);
|
|
38075
38027
|
return true;
|
|
38076
38028
|
}
|
|
38077
38029
|
if (!url.pathname.startsWith("/session/") && !url.pathname.startsWith("/files/") && url.pathname !== "/attachments/upload" && url.pathname !== "/extensions/import") {
|
|
@@ -38234,7 +38186,7 @@ function createHttpRouter(deps) {
|
|
|
38234
38186
|
return true;
|
|
38235
38187
|
}
|
|
38236
38188
|
let absPath;
|
|
38237
|
-
if (
|
|
38189
|
+
if (import_node_path19.default.isAbsolute(pathParam)) {
|
|
38238
38190
|
absPath = pathParam;
|
|
38239
38191
|
} else if (deps.sessionStore) {
|
|
38240
38192
|
const file = deps.sessionStore.read(sid);
|
|
@@ -38242,7 +38194,7 @@ function createHttpRouter(deps) {
|
|
|
38242
38194
|
sendJson(res, 404, { code: "NOT_FOUND", message: `session ${sid} not found` });
|
|
38243
38195
|
return true;
|
|
38244
38196
|
}
|
|
38245
|
-
absPath =
|
|
38197
|
+
absPath = import_node_path19.default.join(file.cwd, pathParam);
|
|
38246
38198
|
} else {
|
|
38247
38199
|
sendJson(res, 501, withCtx(ctx, { code: "NOT_IMPLEMENTED", message: "sessionStore not wired" }));
|
|
38248
38200
|
return true;
|
|
@@ -38309,6 +38261,9 @@ function parseUrl(rawUrl) {
|
|
|
38309
38261
|
return null;
|
|
38310
38262
|
}
|
|
38311
38263
|
}
|
|
38264
|
+
function escapeHtml(s) {
|
|
38265
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
38266
|
+
}
|
|
38312
38267
|
function sendJson(res, status, body, extraHeaders) {
|
|
38313
38268
|
res.writeHead(status, {
|
|
38314
38269
|
"Content-Type": "application/json; charset=utf-8",
|
|
@@ -38322,7 +38277,7 @@ function withCtx(ctx, body) {
|
|
|
38322
38277
|
function streamFile(res, absPath, logger) {
|
|
38323
38278
|
let stat;
|
|
38324
38279
|
try {
|
|
38325
|
-
stat =
|
|
38280
|
+
stat = import_node_fs16.default.statSync(absPath);
|
|
38326
38281
|
} catch (err) {
|
|
38327
38282
|
const code = err?.code;
|
|
38328
38283
|
if (code === "ENOENT") {
|
|
@@ -38337,7 +38292,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38337
38292
|
return;
|
|
38338
38293
|
}
|
|
38339
38294
|
const mime = lookupMime(absPath);
|
|
38340
|
-
const basename =
|
|
38295
|
+
const basename = import_node_path19.default.basename(absPath);
|
|
38341
38296
|
res.writeHead(200, {
|
|
38342
38297
|
"Content-Type": mime,
|
|
38343
38298
|
"Content-Length": String(stat.size),
|
|
@@ -38345,7 +38300,7 @@ function streamFile(res, absPath, logger) {
|
|
|
38345
38300
|
// 防止浏览器把任意 mime 当 html 渲染
|
|
38346
38301
|
"X-Content-Type-Options": "nosniff"
|
|
38347
38302
|
});
|
|
38348
|
-
const stream =
|
|
38303
|
+
const stream = import_node_fs16.default.createReadStream(absPath);
|
|
38349
38304
|
stream.on("error", (err) => {
|
|
38350
38305
|
logger?.warn("streamFile read error", { absPath, err: err.message });
|
|
38351
38306
|
res.destroy();
|
|
@@ -38354,8 +38309,8 @@ function streamFile(res, absPath, logger) {
|
|
|
38354
38309
|
}
|
|
38355
38310
|
|
|
38356
38311
|
// src/attachment/gc.ts
|
|
38357
|
-
var
|
|
38358
|
-
var
|
|
38312
|
+
var import_node_fs17 = __toESM(require("fs"), 1);
|
|
38313
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
38359
38314
|
var DEFAULT_TTL_MS = 30 * 24 * 3600 * 1e3;
|
|
38360
38315
|
function runAttachmentGc(args) {
|
|
38361
38316
|
const now = (args.now ?? Date.now)();
|
|
@@ -38364,38 +38319,38 @@ function runAttachmentGc(args) {
|
|
|
38364
38319
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38365
38320
|
for (const entry of args.groupFileStore.list(scope, sessionId)) {
|
|
38366
38321
|
if (entry.stale) continue;
|
|
38367
|
-
if (
|
|
38322
|
+
if (import_node_path20.default.isAbsolute(entry.relPath)) liveAbs.add(entry.relPath);
|
|
38368
38323
|
}
|
|
38369
38324
|
}
|
|
38370
38325
|
for (const { scope, sessionId } of args.sessionScopes) {
|
|
38371
|
-
const sessionDir = args.getSessionCwd?.(sessionId) ??
|
|
38326
|
+
const sessionDir = args.getSessionCwd?.(sessionId) ?? import_node_path20.default.join(
|
|
38372
38327
|
args.dataDir,
|
|
38373
38328
|
"sessions",
|
|
38374
38329
|
...scopeSubPath(scope).map(safeFileName),
|
|
38375
38330
|
safeFileName(sessionId)
|
|
38376
38331
|
);
|
|
38377
|
-
const attRoot =
|
|
38332
|
+
const attRoot = import_node_path20.default.join(sessionDir, ".attachments");
|
|
38378
38333
|
let hashDirs;
|
|
38379
38334
|
try {
|
|
38380
|
-
hashDirs =
|
|
38335
|
+
hashDirs = import_node_fs17.default.readdirSync(attRoot);
|
|
38381
38336
|
} catch (err) {
|
|
38382
38337
|
if (err.code === "ENOENT") continue;
|
|
38383
38338
|
args.logger?.warn("attachment gc: readdir failed", { attRoot, err: err.message });
|
|
38384
38339
|
continue;
|
|
38385
38340
|
}
|
|
38386
38341
|
for (const hashDir of hashDirs) {
|
|
38387
|
-
const hashDirAbs =
|
|
38342
|
+
const hashDirAbs = import_node_path20.default.join(attRoot, hashDir);
|
|
38388
38343
|
let files;
|
|
38389
38344
|
try {
|
|
38390
|
-
files =
|
|
38345
|
+
files = import_node_fs17.default.readdirSync(hashDirAbs);
|
|
38391
38346
|
} catch {
|
|
38392
38347
|
continue;
|
|
38393
38348
|
}
|
|
38394
38349
|
for (const name of files) {
|
|
38395
|
-
const file =
|
|
38350
|
+
const file = import_node_path20.default.join(hashDirAbs, name);
|
|
38396
38351
|
let stat;
|
|
38397
38352
|
try {
|
|
38398
|
-
stat =
|
|
38353
|
+
stat = import_node_fs17.default.statSync(file);
|
|
38399
38354
|
} catch {
|
|
38400
38355
|
continue;
|
|
38401
38356
|
}
|
|
@@ -38404,27 +38359,27 @@ function runAttachmentGc(args) {
|
|
|
38404
38359
|
if (age < ttlMs) continue;
|
|
38405
38360
|
if (liveAbs.has(file)) continue;
|
|
38406
38361
|
try {
|
|
38407
|
-
|
|
38362
|
+
import_node_fs17.default.unlinkSync(file);
|
|
38408
38363
|
} catch (err) {
|
|
38409
38364
|
args.logger?.warn("attachment gc: unlink failed", { file, err: err.message });
|
|
38410
38365
|
}
|
|
38411
38366
|
}
|
|
38412
38367
|
try {
|
|
38413
|
-
if (
|
|
38368
|
+
if (import_node_fs17.default.readdirSync(hashDirAbs).length === 0) import_node_fs17.default.rmdirSync(hashDirAbs);
|
|
38414
38369
|
} catch {
|
|
38415
38370
|
}
|
|
38416
38371
|
}
|
|
38417
38372
|
try {
|
|
38418
|
-
if (
|
|
38373
|
+
if (import_node_fs17.default.readdirSync(attRoot).length === 0) import_node_fs17.default.rmdirSync(attRoot);
|
|
38419
38374
|
} catch {
|
|
38420
38375
|
}
|
|
38421
38376
|
}
|
|
38422
38377
|
}
|
|
38423
38378
|
|
|
38424
38379
|
// src/attachment/group.ts
|
|
38425
|
-
var
|
|
38426
|
-
var
|
|
38427
|
-
var
|
|
38380
|
+
var import_node_fs18 = __toESM(require("fs"), 1);
|
|
38381
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
38382
|
+
var import_node_crypto6 = __toESM(require("crypto"), 1);
|
|
38428
38383
|
init_protocol();
|
|
38429
38384
|
var GroupFileStore = class {
|
|
38430
38385
|
dataDir;
|
|
@@ -38435,11 +38390,11 @@ var GroupFileStore = class {
|
|
|
38435
38390
|
this.logger = opts.logger;
|
|
38436
38391
|
}
|
|
38437
38392
|
rootForScope(scope) {
|
|
38438
|
-
return
|
|
38393
|
+
return import_node_path21.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
38439
38394
|
}
|
|
38440
38395
|
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
38441
38396
|
filePath(scope, sessionId) {
|
|
38442
|
-
return
|
|
38397
|
+
return import_node_path21.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
38443
38398
|
}
|
|
38444
38399
|
cacheKey(scope, sessionId) {
|
|
38445
38400
|
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
@@ -38448,7 +38403,7 @@ var GroupFileStore = class {
|
|
|
38448
38403
|
readFile(scope, sessionId) {
|
|
38449
38404
|
const file = this.filePath(scope, sessionId);
|
|
38450
38405
|
try {
|
|
38451
|
-
const raw =
|
|
38406
|
+
const raw = import_node_fs18.default.readFileSync(file, "utf8");
|
|
38452
38407
|
const parsed = JSON.parse(raw);
|
|
38453
38408
|
if (!Array.isArray(parsed)) {
|
|
38454
38409
|
this.logger?.warn("GroupFileStore.readFile: not an array; resetting session entries", {
|
|
@@ -38474,10 +38429,10 @@ var GroupFileStore = class {
|
|
|
38474
38429
|
}
|
|
38475
38430
|
writeFile(scope, sessionId, entries) {
|
|
38476
38431
|
const file = this.filePath(scope, sessionId);
|
|
38477
|
-
|
|
38432
|
+
import_node_fs18.default.mkdirSync(import_node_path21.default.dirname(file), { recursive: true });
|
|
38478
38433
|
const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;
|
|
38479
|
-
|
|
38480
|
-
|
|
38434
|
+
import_node_fs18.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
38435
|
+
import_node_fs18.default.renameSync(tmp, file);
|
|
38481
38436
|
}
|
|
38482
38437
|
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
38483
38438
|
list(scope, sessionId) {
|
|
@@ -38513,7 +38468,7 @@ var GroupFileStore = class {
|
|
|
38513
38468
|
entries[idx] = next;
|
|
38514
38469
|
} else {
|
|
38515
38470
|
next = {
|
|
38516
|
-
id: `gf-${
|
|
38471
|
+
id: `gf-${import_node_crypto6.default.randomBytes(6).toString("base64url")}`,
|
|
38517
38472
|
relPath: input.relPath,
|
|
38518
38473
|
from: input.from,
|
|
38519
38474
|
label: input.label,
|
|
@@ -38563,10 +38518,10 @@ var GroupFileStore = class {
|
|
|
38563
38518
|
};
|
|
38564
38519
|
|
|
38565
38520
|
// src/discovery/state-file.ts
|
|
38566
|
-
var
|
|
38567
|
-
var
|
|
38521
|
+
var import_node_fs19 = __toESM(require("fs"), 1);
|
|
38522
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
38568
38523
|
function defaultStateFilePath(dataDir) {
|
|
38569
|
-
return
|
|
38524
|
+
return import_node_path22.default.join(dataDir, "state.json");
|
|
38570
38525
|
}
|
|
38571
38526
|
function isPidAlive(pid) {
|
|
38572
38527
|
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
@@ -38588,7 +38543,7 @@ var StateFileManager = class {
|
|
|
38588
38543
|
}
|
|
38589
38544
|
read() {
|
|
38590
38545
|
try {
|
|
38591
|
-
const raw =
|
|
38546
|
+
const raw = import_node_fs19.default.readFileSync(this.file, "utf8");
|
|
38592
38547
|
const parsed = JSON.parse(raw);
|
|
38593
38548
|
return parsed;
|
|
38594
38549
|
} catch {
|
|
@@ -38602,34 +38557,34 @@ var StateFileManager = class {
|
|
|
38602
38557
|
return { status: "stale", existing };
|
|
38603
38558
|
}
|
|
38604
38559
|
write(state) {
|
|
38605
|
-
|
|
38560
|
+
import_node_fs19.default.mkdirSync(import_node_path22.default.dirname(this.file), { recursive: true });
|
|
38606
38561
|
const tmp = `${this.file}.tmp.${process.pid}.${Date.now()}`;
|
|
38607
|
-
|
|
38608
|
-
|
|
38562
|
+
import_node_fs19.default.writeFileSync(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
38563
|
+
import_node_fs19.default.renameSync(tmp, this.file);
|
|
38609
38564
|
if (process.platform !== "win32") {
|
|
38610
38565
|
try {
|
|
38611
|
-
|
|
38566
|
+
import_node_fs19.default.chmodSync(this.file, 384);
|
|
38612
38567
|
} catch {
|
|
38613
38568
|
}
|
|
38614
38569
|
}
|
|
38615
38570
|
}
|
|
38616
38571
|
delete() {
|
|
38617
38572
|
try {
|
|
38618
|
-
|
|
38573
|
+
import_node_fs19.default.unlinkSync(this.file);
|
|
38619
38574
|
} catch {
|
|
38620
38575
|
}
|
|
38621
38576
|
}
|
|
38622
38577
|
};
|
|
38623
38578
|
|
|
38624
38579
|
// src/tunnel/tunnel-manager.ts
|
|
38625
|
-
var
|
|
38626
|
-
var
|
|
38627
|
-
var
|
|
38580
|
+
var import_node_fs23 = __toESM(require("fs"), 1);
|
|
38581
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
38582
|
+
var import_node_crypto7 = __toESM(require("crypto"), 1);
|
|
38628
38583
|
var import_node_child_process5 = require("child_process");
|
|
38629
38584
|
|
|
38630
38585
|
// src/tunnel/tunnel-store.ts
|
|
38631
|
-
var
|
|
38632
|
-
var
|
|
38586
|
+
var import_node_fs20 = __toESM(require("fs"), 1);
|
|
38587
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
38633
38588
|
var TunnelStore = class {
|
|
38634
38589
|
constructor(filePath) {
|
|
38635
38590
|
this.filePath = filePath;
|
|
@@ -38637,7 +38592,7 @@ var TunnelStore = class {
|
|
|
38637
38592
|
filePath;
|
|
38638
38593
|
async get() {
|
|
38639
38594
|
try {
|
|
38640
|
-
const raw = await
|
|
38595
|
+
const raw = await import_node_fs20.default.promises.readFile(this.filePath, "utf8");
|
|
38641
38596
|
const obj = JSON.parse(raw);
|
|
38642
38597
|
if (!isPersistedTunnel(obj)) return null;
|
|
38643
38598
|
return obj;
|
|
@@ -38648,22 +38603,22 @@ var TunnelStore = class {
|
|
|
38648
38603
|
}
|
|
38649
38604
|
}
|
|
38650
38605
|
async set(v2) {
|
|
38651
|
-
const dir =
|
|
38652
|
-
await
|
|
38606
|
+
const dir = import_node_path23.default.dirname(this.filePath);
|
|
38607
|
+
await import_node_fs20.default.promises.mkdir(dir, { recursive: true });
|
|
38653
38608
|
const data = JSON.stringify(v2, null, 2);
|
|
38654
38609
|
const tmp = `${this.filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
38655
|
-
await
|
|
38610
|
+
await import_node_fs20.default.promises.writeFile(tmp, data, { mode: 384 });
|
|
38656
38611
|
if (process.platform !== "win32") {
|
|
38657
38612
|
try {
|
|
38658
|
-
await
|
|
38613
|
+
await import_node_fs20.default.promises.chmod(tmp, 384);
|
|
38659
38614
|
} catch {
|
|
38660
38615
|
}
|
|
38661
38616
|
}
|
|
38662
|
-
await
|
|
38617
|
+
await import_node_fs20.default.promises.rename(tmp, this.filePath);
|
|
38663
38618
|
}
|
|
38664
38619
|
async clear() {
|
|
38665
38620
|
try {
|
|
38666
|
-
await
|
|
38621
|
+
await import_node_fs20.default.promises.unlink(this.filePath);
|
|
38667
38622
|
} catch (err) {
|
|
38668
38623
|
const code = err?.code;
|
|
38669
38624
|
if (code !== "ENOENT") throw err;
|
|
@@ -38758,9 +38713,9 @@ function escape(v2) {
|
|
|
38758
38713
|
}
|
|
38759
38714
|
|
|
38760
38715
|
// src/tunnel/frpc-binary.ts
|
|
38761
|
-
var
|
|
38716
|
+
var import_node_fs21 = __toESM(require("fs"), 1);
|
|
38762
38717
|
var import_node_os11 = __toESM(require("os"), 1);
|
|
38763
|
-
var
|
|
38718
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
38764
38719
|
var import_node_child_process3 = require("child_process");
|
|
38765
38720
|
var import_node_stream2 = require("stream");
|
|
38766
38721
|
var import_promises3 = require("stream/promises");
|
|
@@ -38792,20 +38747,20 @@ function frpcDownloadUrl(version2, p2) {
|
|
|
38792
38747
|
}
|
|
38793
38748
|
async function ensureFrpcBinary(opts) {
|
|
38794
38749
|
if (opts.override) {
|
|
38795
|
-
if (!
|
|
38750
|
+
if (!import_node_fs21.default.existsSync(opts.override)) {
|
|
38796
38751
|
throw new Error(`frpc binary not found at override path: ${opts.override}`);
|
|
38797
38752
|
}
|
|
38798
38753
|
return opts.override;
|
|
38799
38754
|
}
|
|
38800
38755
|
const version2 = opts.version ?? FRPC_VERSION;
|
|
38801
38756
|
const platform = opts.platform ?? detectPlatform();
|
|
38802
|
-
const binDir =
|
|
38803
|
-
|
|
38757
|
+
const binDir = import_node_path24.default.join(opts.dataDir, "bin");
|
|
38758
|
+
import_node_fs21.default.mkdirSync(binDir, { recursive: true });
|
|
38804
38759
|
cleanupStaleArtifacts(binDir);
|
|
38805
|
-
const stableBin =
|
|
38806
|
-
if (
|
|
38760
|
+
const stableBin = import_node_path24.default.join(binDir, "frpc");
|
|
38761
|
+
if (import_node_fs21.default.existsSync(stableBin)) return stableBin;
|
|
38807
38762
|
const partialBin = `${stableBin}.partial`;
|
|
38808
|
-
const tarballPath =
|
|
38763
|
+
const tarballPath = import_node_path24.default.join(binDir, `frp_${version2}_${platform.os}_${platform.arch}.tar.gz.partial`);
|
|
38809
38764
|
try {
|
|
38810
38765
|
const url = frpcDownloadUrl(version2, platform);
|
|
38811
38766
|
await downloadToFile(url, tarballPath, opts.fetchImpl);
|
|
@@ -38814,8 +38769,8 @@ async function ensureFrpcBinary(opts) {
|
|
|
38814
38769
|
} else {
|
|
38815
38770
|
await extractFrpcFromTarball(tarballPath, binDir, version2, platform, partialBin);
|
|
38816
38771
|
}
|
|
38817
|
-
|
|
38818
|
-
|
|
38772
|
+
import_node_fs21.default.chmodSync(partialBin, 493);
|
|
38773
|
+
import_node_fs21.default.renameSync(partialBin, stableBin);
|
|
38819
38774
|
} finally {
|
|
38820
38775
|
safeUnlink(tarballPath);
|
|
38821
38776
|
safeUnlink(partialBin);
|
|
@@ -38825,15 +38780,15 @@ async function ensureFrpcBinary(opts) {
|
|
|
38825
38780
|
function cleanupStaleArtifacts(binDir) {
|
|
38826
38781
|
let entries;
|
|
38827
38782
|
try {
|
|
38828
|
-
entries =
|
|
38783
|
+
entries = import_node_fs21.default.readdirSync(binDir);
|
|
38829
38784
|
} catch {
|
|
38830
38785
|
return;
|
|
38831
38786
|
}
|
|
38832
38787
|
for (const name of entries) {
|
|
38833
38788
|
if (name.endsWith(".partial") || name.startsWith("extract-")) {
|
|
38834
|
-
const full =
|
|
38789
|
+
const full = import_node_path24.default.join(binDir, name);
|
|
38835
38790
|
try {
|
|
38836
|
-
|
|
38791
|
+
import_node_fs21.default.rmSync(full, { recursive: true, force: true });
|
|
38837
38792
|
} catch {
|
|
38838
38793
|
}
|
|
38839
38794
|
}
|
|
@@ -38841,7 +38796,7 @@ function cleanupStaleArtifacts(binDir) {
|
|
|
38841
38796
|
}
|
|
38842
38797
|
function safeUnlink(p2) {
|
|
38843
38798
|
try {
|
|
38844
|
-
|
|
38799
|
+
import_node_fs21.default.unlinkSync(p2);
|
|
38845
38800
|
} catch {
|
|
38846
38801
|
}
|
|
38847
38802
|
}
|
|
@@ -38852,13 +38807,13 @@ async function downloadToFile(url, dest, fetchImpl) {
|
|
|
38852
38807
|
if (!res.ok || !res.body) {
|
|
38853
38808
|
throw new Error(`download failed: ${res.status} ${res.statusText}`);
|
|
38854
38809
|
}
|
|
38855
|
-
const out =
|
|
38810
|
+
const out = import_node_fs21.default.createWriteStream(dest);
|
|
38856
38811
|
const nodeStream = import_node_stream2.Readable.fromWeb(res.body);
|
|
38857
38812
|
await (0, import_promises3.pipeline)(nodeStream, out);
|
|
38858
38813
|
}
|
|
38859
38814
|
async function extractFrpcFromTarball(tarball, binDir, version2, platform, destBin) {
|
|
38860
|
-
const work =
|
|
38861
|
-
|
|
38815
|
+
const work = import_node_path24.default.join(binDir, `extract-${process.pid}-${Date.now()}`);
|
|
38816
|
+
import_node_fs21.default.mkdirSync(work, { recursive: true });
|
|
38862
38817
|
try {
|
|
38863
38818
|
await new Promise((resolve6, reject) => {
|
|
38864
38819
|
const proc = (0, import_node_child_process3.spawn)("tar", ["xzf", tarball, "-C", work], { stdio: "pipe" });
|
|
@@ -38866,32 +38821,32 @@ async function extractFrpcFromTarball(tarball, binDir, version2, platform, destB
|
|
|
38866
38821
|
proc.on("exit", (code) => code === 0 ? resolve6() : reject(new Error(`tar exited ${code}`)));
|
|
38867
38822
|
});
|
|
38868
38823
|
const dirName = `frp_${version2}_${platform.os}_${platform.arch}`;
|
|
38869
|
-
const src =
|
|
38870
|
-
if (!
|
|
38824
|
+
const src = import_node_path24.default.join(work, dirName, "frpc");
|
|
38825
|
+
if (!import_node_fs21.default.existsSync(src)) {
|
|
38871
38826
|
throw new Error(`frpc not found inside tarball at ${src}`);
|
|
38872
38827
|
}
|
|
38873
|
-
|
|
38828
|
+
import_node_fs21.default.copyFileSync(src, destBin);
|
|
38874
38829
|
} finally {
|
|
38875
|
-
|
|
38830
|
+
import_node_fs21.default.rmSync(work, { recursive: true, force: true });
|
|
38876
38831
|
}
|
|
38877
38832
|
}
|
|
38878
38833
|
|
|
38879
38834
|
// src/tunnel/frpc-process.ts
|
|
38880
|
-
var
|
|
38881
|
-
var
|
|
38835
|
+
var import_node_fs22 = __toESM(require("fs"), 1);
|
|
38836
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
38882
38837
|
var import_node_child_process4 = require("child_process");
|
|
38883
38838
|
function frpcPidFilePath(dataDir) {
|
|
38884
|
-
return
|
|
38839
|
+
return import_node_path25.default.join(dataDir, "frpc.pid");
|
|
38885
38840
|
}
|
|
38886
38841
|
function writeFrpcPid(dataDir, pid) {
|
|
38887
38842
|
try {
|
|
38888
|
-
|
|
38843
|
+
import_node_fs22.default.writeFileSync(frpcPidFilePath(dataDir), String(pid), { mode: 384 });
|
|
38889
38844
|
} catch {
|
|
38890
38845
|
}
|
|
38891
38846
|
}
|
|
38892
38847
|
function clearFrpcPid(dataDir) {
|
|
38893
38848
|
try {
|
|
38894
|
-
|
|
38849
|
+
import_node_fs22.default.unlinkSync(frpcPidFilePath(dataDir));
|
|
38895
38850
|
} catch {
|
|
38896
38851
|
}
|
|
38897
38852
|
}
|
|
@@ -38907,7 +38862,7 @@ function defaultIsPidAlive(pid) {
|
|
|
38907
38862
|
}
|
|
38908
38863
|
function defaultReadPidFile(file) {
|
|
38909
38864
|
try {
|
|
38910
|
-
return
|
|
38865
|
+
return import_node_fs22.default.readFileSync(file, "utf8");
|
|
38911
38866
|
} catch {
|
|
38912
38867
|
return null;
|
|
38913
38868
|
}
|
|
@@ -38923,7 +38878,7 @@ function defaultSleep(ms) {
|
|
|
38923
38878
|
}
|
|
38924
38879
|
async function killStaleFrpc(deps) {
|
|
38925
38880
|
const pidFile = frpcPidFilePath(deps.dataDir);
|
|
38926
|
-
const tomlPath =
|
|
38881
|
+
const tomlPath = import_node_path25.default.join(deps.dataDir, "frpc.toml");
|
|
38927
38882
|
const readPidFile = deps.readPidFileImpl ?? defaultReadPidFile;
|
|
38928
38883
|
const isAlive = deps.isPidAliveImpl ?? defaultIsPidAlive;
|
|
38929
38884
|
const killPid = deps.killPidImpl ?? defaultKillPid;
|
|
@@ -38947,7 +38902,7 @@ async function killStaleFrpc(deps) {
|
|
|
38947
38902
|
}
|
|
38948
38903
|
if (victims.size === 0) {
|
|
38949
38904
|
try {
|
|
38950
|
-
|
|
38905
|
+
import_node_fs22.default.unlinkSync(pidFile);
|
|
38951
38906
|
} catch {
|
|
38952
38907
|
}
|
|
38953
38908
|
return;
|
|
@@ -38958,7 +38913,7 @@ async function killStaleFrpc(deps) {
|
|
|
38958
38913
|
}
|
|
38959
38914
|
await sleep(deps.reapWaitMs ?? 300);
|
|
38960
38915
|
try {
|
|
38961
|
-
|
|
38916
|
+
import_node_fs22.default.unlinkSync(pidFile);
|
|
38962
38917
|
} catch {
|
|
38963
38918
|
}
|
|
38964
38919
|
}
|
|
@@ -38995,7 +38950,7 @@ var DEFAULT_TUNNEL_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
|
38995
38950
|
var TunnelManager = class {
|
|
38996
38951
|
constructor(deps) {
|
|
38997
38952
|
this.deps = deps;
|
|
38998
|
-
this.store = deps.store ?? new TunnelStore(
|
|
38953
|
+
this.store = deps.store ?? new TunnelStore(import_node_path26.default.join(deps.dataDir, "tunnel.json"));
|
|
38999
38954
|
this.ttlMs = deps.ttlMs ?? DEFAULT_TUNNEL_TTL_MS;
|
|
39000
38955
|
this.startupTimeoutMs = deps.startupTimeoutMs ?? 15e3;
|
|
39001
38956
|
}
|
|
@@ -39122,8 +39077,8 @@ var TunnelManager = class {
|
|
|
39122
39077
|
dataDir: this.deps.dataDir,
|
|
39123
39078
|
override: this.deps.frpcBinaryOverride ?? void 0
|
|
39124
39079
|
});
|
|
39125
|
-
const tomlPath =
|
|
39126
|
-
const proxyName = `clawd-${t.subdomain}-${localPort}-${
|
|
39080
|
+
const tomlPath = import_node_path26.default.join(this.deps.dataDir, "frpc.toml");
|
|
39081
|
+
const proxyName = `clawd-${t.subdomain}-${localPort}-${import_node_crypto7.default.randomBytes(3).toString("hex")}`;
|
|
39127
39082
|
const toml = buildFrpcToml({
|
|
39128
39083
|
serverAddr: t.frpsHost,
|
|
39129
39084
|
serverPort: t.frpsPort,
|
|
@@ -39133,12 +39088,12 @@ var TunnelManager = class {
|
|
|
39133
39088
|
localPort,
|
|
39134
39089
|
logLevel: "info"
|
|
39135
39090
|
});
|
|
39136
|
-
await
|
|
39091
|
+
await import_node_fs23.default.promises.writeFile(tomlPath, toml, { mode: 384 });
|
|
39137
39092
|
const proc = (this.deps.spawnImpl ?? import_node_child_process5.spawn)(frpcBin, ["-c", tomlPath], {
|
|
39138
39093
|
stdio: ["ignore", "pipe", "pipe"]
|
|
39139
39094
|
});
|
|
39140
|
-
const logFilePath =
|
|
39141
|
-
const logStream =
|
|
39095
|
+
const logFilePath = import_node_path26.default.join(this.deps.dataDir, "frpc.log");
|
|
39096
|
+
const logStream = import_node_fs23.default.createWriteStream(logFilePath, { flags: "a", mode: 384 });
|
|
39142
39097
|
logStream.on("error", () => {
|
|
39143
39098
|
});
|
|
39144
39099
|
const tee = (chunk) => {
|
|
@@ -39221,31 +39176,31 @@ async function waitForFrpcReady(proc, timeoutMs) {
|
|
|
39221
39176
|
|
|
39222
39177
|
// src/tunnel/device-key.ts
|
|
39223
39178
|
var import_node_os12 = __toESM(require("os"), 1);
|
|
39224
|
-
var
|
|
39225
|
-
var
|
|
39179
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
39180
|
+
var import_node_crypto8 = __toESM(require("crypto"), 1);
|
|
39226
39181
|
var DERIVE_SALT = "clawd-tunnel-device-v1";
|
|
39227
39182
|
function deriveStableDeviceKey(opts = {}) {
|
|
39228
39183
|
const hostname = opts.hostname ?? import_node_os12.default.hostname();
|
|
39229
39184
|
const uid = opts.uid ?? (typeof import_node_os12.default.userInfo === "function" ? import_node_os12.default.userInfo().uid : 0);
|
|
39230
39185
|
const home = opts.home ?? import_node_os12.default.homedir();
|
|
39231
|
-
const defaultDataDir =
|
|
39232
|
-
const normalizedDataDir = opts.dataDir ?
|
|
39186
|
+
const defaultDataDir = import_node_path27.default.resolve(import_node_path27.default.join(home, ".clawd"));
|
|
39187
|
+
const normalizedDataDir = opts.dataDir ? import_node_path27.default.resolve(opts.dataDir) : null;
|
|
39233
39188
|
const isDefaultDir = normalizedDataDir == null || normalizedDataDir === defaultDataDir;
|
|
39234
39189
|
const input = isDefaultDir ? `${hostname}::${uid}` : `${hostname}::${uid}::${normalizedDataDir}`;
|
|
39235
|
-
return
|
|
39190
|
+
return import_node_crypto8.default.createHmac("sha256", DERIVE_SALT).update(input).digest("hex").slice(0, 32);
|
|
39236
39191
|
}
|
|
39237
39192
|
|
|
39238
39193
|
// src/auth-store.ts
|
|
39239
|
-
var
|
|
39240
|
-
var
|
|
39241
|
-
var
|
|
39194
|
+
var import_node_fs24 = __toESM(require("fs"), 1);
|
|
39195
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
39196
|
+
var import_node_crypto9 = __toESM(require("crypto"), 1);
|
|
39242
39197
|
var AUTH_FILE_NAME = "auth.json";
|
|
39243
39198
|
function authFilePath(dataDir) {
|
|
39244
|
-
return
|
|
39199
|
+
return import_node_path28.default.join(dataDir, AUTH_FILE_NAME);
|
|
39245
39200
|
}
|
|
39246
39201
|
function loadOrCreateAuthFile(opts) {
|
|
39247
39202
|
const file = authFilePath(opts.dataDir);
|
|
39248
|
-
const generate = opts.generate ??
|
|
39203
|
+
const generate = opts.generate ?? defaultGenerateToken;
|
|
39249
39204
|
const genId = opts.genOwnerPrincipalId ?? defaultGenerateOwnerPrincipalId;
|
|
39250
39205
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
39251
39206
|
const existing = readAuthFile(file);
|
|
@@ -39265,15 +39220,15 @@ function loadOrCreateAuthFile(opts) {
|
|
|
39265
39220
|
writeAuthFile(file, next);
|
|
39266
39221
|
return next;
|
|
39267
39222
|
}
|
|
39268
|
-
function
|
|
39269
|
-
return
|
|
39223
|
+
function defaultGenerateToken() {
|
|
39224
|
+
return import_node_crypto9.default.randomBytes(32).toString("base64url");
|
|
39270
39225
|
}
|
|
39271
39226
|
function defaultGenerateOwnerPrincipalId() {
|
|
39272
|
-
return `owner-${
|
|
39227
|
+
return `owner-${import_node_crypto9.default.randomUUID()}`;
|
|
39273
39228
|
}
|
|
39274
39229
|
function readAuthFile(file) {
|
|
39275
39230
|
try {
|
|
39276
|
-
const raw =
|
|
39231
|
+
const raw = import_node_fs24.default.readFileSync(file, "utf8");
|
|
39277
39232
|
const parsed = JSON.parse(raw);
|
|
39278
39233
|
if (typeof parsed?.token !== "string" || parsed.token.length === 0) {
|
|
39279
39234
|
return null;
|
|
@@ -39291,25 +39246,25 @@ function readAuthFile(file) {
|
|
|
39291
39246
|
}
|
|
39292
39247
|
}
|
|
39293
39248
|
function writeAuthFile(file, content) {
|
|
39294
|
-
|
|
39295
|
-
|
|
39249
|
+
import_node_fs24.default.mkdirSync(import_node_path28.default.dirname(file), { recursive: true });
|
|
39250
|
+
import_node_fs24.default.writeFileSync(file, JSON.stringify(content, null, 2), { mode: 384 });
|
|
39296
39251
|
try {
|
|
39297
|
-
|
|
39252
|
+
import_node_fs24.default.chmodSync(file, 384);
|
|
39298
39253
|
} catch {
|
|
39299
39254
|
}
|
|
39300
39255
|
}
|
|
39301
39256
|
|
|
39302
39257
|
// src/owner-profile.ts
|
|
39303
|
-
var
|
|
39258
|
+
var import_node_fs25 = __toESM(require("fs"), 1);
|
|
39304
39259
|
var import_node_os13 = __toESM(require("os"), 1);
|
|
39305
|
-
var
|
|
39260
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
39306
39261
|
var PROFILE_FILENAME = "profile.json";
|
|
39307
39262
|
function loadOwnerDisplayName(dataDir) {
|
|
39308
39263
|
const fallback = import_node_os13.default.userInfo().username;
|
|
39309
|
-
const profilePath =
|
|
39264
|
+
const profilePath = import_node_path29.default.join(dataDir, PROFILE_FILENAME);
|
|
39310
39265
|
let raw;
|
|
39311
39266
|
try {
|
|
39312
|
-
raw =
|
|
39267
|
+
raw = import_node_fs25.default.readFileSync(profilePath, "utf8");
|
|
39313
39268
|
} catch {
|
|
39314
39269
|
return fallback;
|
|
39315
39270
|
}
|
|
@@ -39331,6 +39286,259 @@ function loadOwnerDisplayName(dataDir) {
|
|
|
39331
39286
|
return displayName;
|
|
39332
39287
|
}
|
|
39333
39288
|
|
|
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
|
+
|
|
39334
39542
|
// src/index.ts
|
|
39335
39543
|
init_protocol();
|
|
39336
39544
|
|
|
@@ -39491,67 +39699,7 @@ function buildSessionHandlers(deps) {
|
|
|
39491
39699
|
const update = async (frame, _client, ctx) => {
|
|
39492
39700
|
const args = SessionUpdateArgs.parse(frame);
|
|
39493
39701
|
ensureSessionAccess(ctx, args.sessionId, "send");
|
|
39494
|
-
const prevFile = manager.findOwnedSession(args.sessionId);
|
|
39495
|
-
const prevProject = prevFile?.appBuilderProject ?? null;
|
|
39496
|
-
const nextProject = args.patch.appBuilderProject ?? prevProject;
|
|
39497
39702
|
const { response, broadcast } = manager.update(args);
|
|
39498
|
-
if (args.patch.appBuilderProject !== void 0) {
|
|
39499
|
-
deps.logger?.info("session-update.appBuilderProject", {
|
|
39500
|
-
sessionId: args.sessionId,
|
|
39501
|
-
prevProject,
|
|
39502
|
-
nextProject,
|
|
39503
|
-
hasLifecycle: !!deps.devServerLifecycle,
|
|
39504
|
-
hasStore: !!deps.appBuilderStore
|
|
39505
|
-
});
|
|
39506
|
-
}
|
|
39507
|
-
if (deps.devServerLifecycle && deps.appBuilderStore && args.patch.appBuilderProject !== void 0) {
|
|
39508
|
-
const lifecycle = deps.devServerLifecycle;
|
|
39509
|
-
const projectStore = deps.appBuilderStore;
|
|
39510
|
-
const tunnelUrl = deps.getTunnelUrl?.() ?? null;
|
|
39511
|
-
try {
|
|
39512
|
-
if (prevProject && prevProject !== nextProject) {
|
|
39513
|
-
await lifecycle.stopForSession(args.sessionId);
|
|
39514
|
-
}
|
|
39515
|
-
if (nextProject && nextProject !== "") {
|
|
39516
|
-
const occupantSessionId = lifecycle.boundSessionId(nextProject);
|
|
39517
|
-
const alreadyRunningForThisSession = lifecycle.isRunning(nextProject) && occupantSessionId === args.sessionId;
|
|
39518
|
-
deps.logger?.info("session-update.maybe-start-dev-server", {
|
|
39519
|
-
sessionId: args.sessionId,
|
|
39520
|
-
projectName: nextProject,
|
|
39521
|
-
occupantSessionId,
|
|
39522
|
-
alreadyRunningForThisSession
|
|
39523
|
-
});
|
|
39524
|
-
if (!alreadyRunningForThisSession) {
|
|
39525
|
-
if (occupantSessionId && occupantSessionId !== args.sessionId) {
|
|
39526
|
-
await lifecycle.stopForSession(occupantSessionId);
|
|
39527
|
-
}
|
|
39528
|
-
const projects = await projectStore.list();
|
|
39529
|
-
const target = projects.find((p2) => p2.name === nextProject);
|
|
39530
|
-
deps.logger?.info("session-update.start-dev-server", {
|
|
39531
|
-
sessionId: args.sessionId,
|
|
39532
|
-
projectName: nextProject,
|
|
39533
|
-
targetFound: !!target,
|
|
39534
|
-
projectCwd: target ? projectStore.projectDir(nextProject) : null,
|
|
39535
|
-
tunnelUrl
|
|
39536
|
-
});
|
|
39537
|
-
if (target) {
|
|
39538
|
-
lifecycle.startForSession({
|
|
39539
|
-
sessionId: args.sessionId,
|
|
39540
|
-
projectName: nextProject,
|
|
39541
|
-
cwd: projectStore.projectDir(nextProject),
|
|
39542
|
-
port: target.port,
|
|
39543
|
-
tunnelHost: tunnelUrl ? new URL(tunnelUrl.replace(/^wss?:\/\//i, "https://")).host : null
|
|
39544
|
-
});
|
|
39545
|
-
}
|
|
39546
|
-
}
|
|
39547
|
-
}
|
|
39548
|
-
} catch (err) {
|
|
39549
|
-
deps.logger?.warn("session-update.lifecycle-failed", {
|
|
39550
|
-
sessionId: args.sessionId,
|
|
39551
|
-
error: err instanceof Error ? err.message : String(err)
|
|
39552
|
-
});
|
|
39553
|
-
}
|
|
39554
|
-
}
|
|
39555
39703
|
return { response: { type: "session:info", ...response }, broadcast };
|
|
39556
39704
|
};
|
|
39557
39705
|
const del = async (frame, _client, ctx) => {
|
|
@@ -40180,7 +40328,7 @@ var DeleteArgsSchema = external_exports.object({
|
|
|
40180
40328
|
capabilityId: external_exports.string().min(1)
|
|
40181
40329
|
}).strict();
|
|
40182
40330
|
function buildCapabilityHandlers(deps) {
|
|
40183
|
-
const { manager
|
|
40331
|
+
const { manager } = deps;
|
|
40184
40332
|
const list = async () => {
|
|
40185
40333
|
return {
|
|
40186
40334
|
response: {
|
|
@@ -40206,21 +40354,9 @@ function buildCapabilityHandlers(deps) {
|
|
|
40206
40354
|
}
|
|
40207
40355
|
};
|
|
40208
40356
|
};
|
|
40209
|
-
const personalCapGet = async () => {
|
|
40210
|
-
const { token, capability } = getPersonalCapability();
|
|
40211
|
-
return {
|
|
40212
|
-
response: {
|
|
40213
|
-
type: "personal-cap:get:ok",
|
|
40214
|
-
token,
|
|
40215
|
-
capability: stripSecretHash(capability),
|
|
40216
|
-
shareBaseUrl: getShareBaseUrl()
|
|
40217
|
-
}
|
|
40218
|
-
};
|
|
40219
|
-
};
|
|
40220
40357
|
return {
|
|
40221
40358
|
"capability:list": list,
|
|
40222
|
-
"capability:delete": del
|
|
40223
|
-
"personal-cap:get": personalCapGet
|
|
40359
|
+
"capability:delete": del
|
|
40224
40360
|
};
|
|
40225
40361
|
}
|
|
40226
40362
|
|
|
@@ -40328,16 +40464,7 @@ function ensureOwner(ctx) {
|
|
|
40328
40464
|
);
|
|
40329
40465
|
}
|
|
40330
40466
|
}
|
|
40331
|
-
function ensureGuestCtx(ctx) {
|
|
40332
|
-
if (!ctx || ctx.principal.kind !== "guest" || !ctx.capabilityId) {
|
|
40333
|
-
throw new ClawdError(
|
|
40334
|
-
ERROR_CODES.UNAUTHORIZED,
|
|
40335
|
-
"UNAUTHORIZED: received-capability:autoAttach requires guest cap ctx"
|
|
40336
|
-
);
|
|
40337
|
-
}
|
|
40338
|
-
}
|
|
40339
40467
|
function buildReceivedCapabilityHandlers(deps) {
|
|
40340
|
-
const now = deps.now ?? Date.now;
|
|
40341
40468
|
const list = async (_frame, _client, ctx) => {
|
|
40342
40469
|
ensureOwner(ctx);
|
|
40343
40470
|
return {
|
|
@@ -40347,47 +40474,6 @@ function buildReceivedCapabilityHandlers(deps) {
|
|
|
40347
40474
|
}
|
|
40348
40475
|
};
|
|
40349
40476
|
};
|
|
40350
|
-
const add = async (frame, _client, ctx) => {
|
|
40351
|
-
ensureOwner(ctx);
|
|
40352
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40353
|
-
const args = ReceivedCapabilityAddArgsSchema.parse(rest);
|
|
40354
|
-
let conn;
|
|
40355
|
-
try {
|
|
40356
|
-
conn = await deps.connectRemote({
|
|
40357
|
-
url: args.remoteUrl,
|
|
40358
|
-
token: args.token,
|
|
40359
|
-
selfPrincipalId: deps.selfPrincipalId(),
|
|
40360
|
-
selfDisplayName: deps.selfDisplayName()
|
|
40361
|
-
});
|
|
40362
|
-
} catch (e) {
|
|
40363
|
-
throw new ClawdError(
|
|
40364
|
-
ERROR_CODES.VALIDATION_ERROR,
|
|
40365
|
-
`INVITE_REMOTE_UNREACHABLE: ${e.message}`
|
|
40366
|
-
);
|
|
40367
|
-
}
|
|
40368
|
-
try {
|
|
40369
|
-
const wh = await conn.whoami();
|
|
40370
|
-
const rc = {
|
|
40371
|
-
ownerPrincipalId: wh.ownerId,
|
|
40372
|
-
ownerDisplayName: wh.displayName,
|
|
40373
|
-
remoteUrl: args.remoteUrl,
|
|
40374
|
-
capabilityId: wh.capabilityId,
|
|
40375
|
-
capabilityToken: args.token,
|
|
40376
|
-
grants: wh.grants,
|
|
40377
|
-
receivedAt: now()
|
|
40378
|
-
};
|
|
40379
|
-
deps.store.upsert(rc);
|
|
40380
|
-
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
40381
|
-
return {
|
|
40382
|
-
response: { type: "received-capability:add:ok", receivedCap: rc }
|
|
40383
|
-
};
|
|
40384
|
-
} finally {
|
|
40385
|
-
try {
|
|
40386
|
-
conn.close();
|
|
40387
|
-
} catch {
|
|
40388
|
-
}
|
|
40389
|
-
}
|
|
40390
|
-
};
|
|
40391
40477
|
const remove = async (frame, _client, ctx) => {
|
|
40392
40478
|
ensureOwner(ctx);
|
|
40393
40479
|
const { type: _t, requestId: _r, ...rest } = frame;
|
|
@@ -40404,30 +40490,9 @@ function buildReceivedCapabilityHandlers(deps) {
|
|
|
40404
40490
|
}
|
|
40405
40491
|
};
|
|
40406
40492
|
};
|
|
40407
|
-
const autoAttach = async (frame, _client, ctx) => {
|
|
40408
|
-
ensureGuestCtx(ctx);
|
|
40409
|
-
const { type: _t, requestId: _r, ...rest } = frame;
|
|
40410
|
-
const args = ReceivedCapabilityAutoAttachArgsSchema.parse(rest);
|
|
40411
|
-
const rc = {
|
|
40412
|
-
ownerPrincipalId: args.ownerPrincipalId,
|
|
40413
|
-
ownerDisplayName: args.ownerDisplayName,
|
|
40414
|
-
remoteUrl: args.remoteUrl,
|
|
40415
|
-
capabilityId: args.capabilityId,
|
|
40416
|
-
capabilityToken: args.token,
|
|
40417
|
-
grants: args.grants,
|
|
40418
|
-
receivedAt: now()
|
|
40419
|
-
};
|
|
40420
|
-
deps.store.upsert(rc);
|
|
40421
|
-
deps.broadcast({ type: "received-capability:added", receivedCap: rc });
|
|
40422
|
-
return {
|
|
40423
|
-
response: { type: "received-capability:autoAttach:ok" }
|
|
40424
|
-
};
|
|
40425
|
-
};
|
|
40426
40493
|
return {
|
|
40427
40494
|
"received-capability:list": list,
|
|
40428
|
-
"received-capability:
|
|
40429
|
-
"received-capability:remove": remove,
|
|
40430
|
-
"received-capability:autoAttach": autoAttach
|
|
40495
|
+
"received-capability:remove": remove
|
|
40431
40496
|
};
|
|
40432
40497
|
}
|
|
40433
40498
|
|
|
@@ -40449,14 +40514,13 @@ function buildWhoamiHandler(deps) {
|
|
|
40449
40514
|
usedCount: 0
|
|
40450
40515
|
};
|
|
40451
40516
|
} else {
|
|
40452
|
-
|
|
40453
|
-
|
|
40454
|
-
|
|
40455
|
-
|
|
40456
|
-
|
|
40457
|
-
|
|
40458
|
-
}
|
|
40459
|
-
capability = stripSecretHash(cap);
|
|
40517
|
+
capability = {
|
|
40518
|
+
id: ctx.principal.id,
|
|
40519
|
+
displayName: ctx.principal.displayName ?? "guest",
|
|
40520
|
+
grants: ctx.grants,
|
|
40521
|
+
issuedAt: 0,
|
|
40522
|
+
usedCount: 0
|
|
40523
|
+
};
|
|
40460
40524
|
}
|
|
40461
40525
|
const grantedPersonas = [];
|
|
40462
40526
|
const isGuest = ctx.principal.kind === "guest";
|
|
@@ -40487,6 +40551,117 @@ function buildWhoamiHandler(deps) {
|
|
|
40487
40551
|
};
|
|
40488
40552
|
}
|
|
40489
40553
|
|
|
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
|
+
|
|
40490
40665
|
// src/handlers/meta.ts
|
|
40491
40666
|
var import_node_os15 = __toESM(require("os"), 1);
|
|
40492
40667
|
init_protocol();
|
|
@@ -41447,61 +41622,6 @@ function buildExtensionHandlers(deps) {
|
|
|
41447
41622
|
};
|
|
41448
41623
|
}
|
|
41449
41624
|
|
|
41450
|
-
// src/handlers/app-builder.ts
|
|
41451
|
-
function buildAppBuilderHandlers(deps) {
|
|
41452
|
-
const { store, deleteSessionsByProject, devServerLifecycle } = deps;
|
|
41453
|
-
const listProjects = async () => {
|
|
41454
|
-
const projects = await store.list();
|
|
41455
|
-
return {
|
|
41456
|
-
response: {
|
|
41457
|
-
projects: projects.map((p2) => ({
|
|
41458
|
-
...p2,
|
|
41459
|
-
isRunning: devServerLifecycle.isRunning(p2.name),
|
|
41460
|
-
boundSessionId: devServerLifecycle.boundSessionId(p2.name)
|
|
41461
|
-
}))
|
|
41462
|
-
}
|
|
41463
|
-
};
|
|
41464
|
-
};
|
|
41465
|
-
const createProject = async (frame) => {
|
|
41466
|
-
const name = frame.name;
|
|
41467
|
-
if (typeof name !== "string") throw new Error("createProject: name must be string");
|
|
41468
|
-
const project = await store.create(name);
|
|
41469
|
-
return { response: { project } };
|
|
41470
|
-
};
|
|
41471
|
-
const deleteProject = async (frame) => {
|
|
41472
|
-
const name = frame.name;
|
|
41473
|
-
if (typeof name !== "string") throw new Error("deleteProject: name must be string");
|
|
41474
|
-
await devServerLifecycle.stopForProject(name);
|
|
41475
|
-
await deleteSessionsByProject(name);
|
|
41476
|
-
await store.delete(name);
|
|
41477
|
-
return { response: {} };
|
|
41478
|
-
};
|
|
41479
|
-
const updateProjectPort = async (frame) => {
|
|
41480
|
-
const f = frame;
|
|
41481
|
-
if (typeof f.name !== "string") throw new Error("updateProjectPort: name must be string");
|
|
41482
|
-
if (typeof f.newPort !== "number") throw new Error("updateProjectPort: newPort must be number");
|
|
41483
|
-
const entry = devServerLifecycle.boundEntry(f.name);
|
|
41484
|
-
await devServerLifecycle.stopForProject(f.name);
|
|
41485
|
-
const project = await store.updatePort(f.name, f.newPort);
|
|
41486
|
-
if (entry) {
|
|
41487
|
-
await devServerLifecycle.restartForProjectAt({
|
|
41488
|
-
projectName: f.name,
|
|
41489
|
-
newPort: f.newPort,
|
|
41490
|
-
sessionId: entry.sessionId,
|
|
41491
|
-
cwd: entry.cwd,
|
|
41492
|
-
tunnelHost: entry.tunnelHost
|
|
41493
|
-
});
|
|
41494
|
-
}
|
|
41495
|
-
return { response: { project } };
|
|
41496
|
-
};
|
|
41497
|
-
return {
|
|
41498
|
-
"appBuilder:listProjects": listProjects,
|
|
41499
|
-
"appBuilder:createProject": createProject,
|
|
41500
|
-
"appBuilder:deleteProject": deleteProject,
|
|
41501
|
-
"appBuilder:updateProjectPort": updateProjectPort
|
|
41502
|
-
};
|
|
41503
|
-
}
|
|
41504
|
-
|
|
41505
41625
|
// src/extension/registry.ts
|
|
41506
41626
|
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
41507
41627
|
var import_node_path39 = __toESM(require("path"), 1);
|
|
@@ -41590,13 +41710,7 @@ async function uninstall(deps) {
|
|
|
41590
41710
|
// src/handlers/index.ts
|
|
41591
41711
|
function buildMethodHandlers(deps) {
|
|
41592
41712
|
return {
|
|
41593
|
-
...buildSessionHandlers(
|
|
41594
|
-
...deps,
|
|
41595
|
-
devServerLifecycle: deps.devServerLifecycle,
|
|
41596
|
-
appBuilderStore: deps.appBuilderStore,
|
|
41597
|
-
getTunnelUrl: deps.getTunnelUrl,
|
|
41598
|
-
logger: deps.logger
|
|
41599
|
-
}),
|
|
41713
|
+
...buildSessionHandlers(deps),
|
|
41600
41714
|
...buildPermissionHandlers(deps),
|
|
41601
41715
|
...buildHistoryHandlers(deps),
|
|
41602
41716
|
...buildWorkspaceHandlers(deps),
|
|
@@ -41608,9 +41722,7 @@ function buildMethodHandlers(deps) {
|
|
|
41608
41722
|
personaRegistry: deps.personaRegistry
|
|
41609
41723
|
}),
|
|
41610
41724
|
...buildCapabilityHandlers({
|
|
41611
|
-
manager: deps.capabilityManager
|
|
41612
|
-
getShareBaseUrl: deps.getShareBaseUrl,
|
|
41613
|
-
getPersonalCapability: deps.getPersonalCapability
|
|
41725
|
+
manager: deps.capabilityManager
|
|
41614
41726
|
}),
|
|
41615
41727
|
...buildInboxHandlers({
|
|
41616
41728
|
manager: deps.inboxManager,
|
|
@@ -41618,10 +41730,7 @@ function buildMethodHandlers(deps) {
|
|
|
41618
41730
|
}),
|
|
41619
41731
|
...buildReceivedCapabilityHandlers({
|
|
41620
41732
|
store: deps.receivedCapabilityStore,
|
|
41621
|
-
|
|
41622
|
-
broadcast: deps.broadcastToOwners,
|
|
41623
|
-
selfPrincipalId: () => deps.ownerPrincipalId,
|
|
41624
|
-
selfDisplayName: () => deps.ownerDisplayName
|
|
41733
|
+
broadcast: deps.broadcastToOwners
|
|
41625
41734
|
}),
|
|
41626
41735
|
whoami: buildWhoamiHandler({
|
|
41627
41736
|
ownerDisplayName: deps.ownerDisplayName,
|
|
@@ -41629,6 +41738,8 @@ function buildMethodHandlers(deps) {
|
|
|
41629
41738
|
personaStore: deps.personaStore,
|
|
41630
41739
|
capabilityManager: deps.capabilityManager
|
|
41631
41740
|
}),
|
|
41741
|
+
...buildFeishuAuthHandlers(deps.feishuAuth),
|
|
41742
|
+
...buildHubHandlers(deps.feishuHub),
|
|
41632
41743
|
...deps.attachment ? buildAttachmentHandlers(deps.attachment) : {},
|
|
41633
41744
|
...buildExtensionHandlers({
|
|
41634
41745
|
loadAll,
|
|
@@ -41637,262 +41748,10 @@ function buildMethodHandlers(deps) {
|
|
|
41637
41748
|
root: extensionsRoot(),
|
|
41638
41749
|
publishedChannelStore: deps.publishedChannelStore,
|
|
41639
41750
|
bundleCache: deps.bundleCache
|
|
41640
|
-
}),
|
|
41641
|
-
...buildAppBuilderHandlers({
|
|
41642
|
-
store: deps.appBuilderStore,
|
|
41643
|
-
deleteSessionsByProject: deps.deleteSessionsByProject,
|
|
41644
|
-
devServerLifecycle: deps.devServerLifecycle
|
|
41645
41751
|
})
|
|
41646
41752
|
};
|
|
41647
41753
|
}
|
|
41648
41754
|
|
|
41649
|
-
// src/app-builder/project-store.ts
|
|
41650
|
-
var import_node_fs29 = require("fs");
|
|
41651
|
-
var import_node_path41 = require("path");
|
|
41652
|
-
init_protocol();
|
|
41653
|
-
var PROJECTS_DIR = "projects";
|
|
41654
|
-
var META_FILE = ".clawd-project.json";
|
|
41655
|
-
var ProjectStore = class {
|
|
41656
|
-
/** @param personaRoot persona 目录,例如 `~/.clawd/personas/persona-app-builder/` */
|
|
41657
|
-
constructor(personaRoot) {
|
|
41658
|
-
this.personaRoot = personaRoot;
|
|
41659
|
-
}
|
|
41660
|
-
personaRoot;
|
|
41661
|
-
/** projects/<name>/.clawd-project.json 路径 */
|
|
41662
|
-
metaPath(name) {
|
|
41663
|
-
return (0, import_node_path41.join)(this.projectsRoot(), name, META_FILE);
|
|
41664
|
-
}
|
|
41665
|
-
/** projects/<name>/ 目录路径(cwd 用) */
|
|
41666
|
-
projectDir(name) {
|
|
41667
|
-
return (0, import_node_path41.join)(this.projectsRoot(), name);
|
|
41668
|
-
}
|
|
41669
|
-
projectsRoot() {
|
|
41670
|
-
return (0, import_node_path41.join)(this.personaRoot, PROJECTS_DIR);
|
|
41671
|
-
}
|
|
41672
|
-
async list() {
|
|
41673
|
-
let entries;
|
|
41674
|
-
try {
|
|
41675
|
-
entries = await import_node_fs29.promises.readdir(this.projectsRoot());
|
|
41676
|
-
} catch (err) {
|
|
41677
|
-
if (err.code === "ENOENT") return [];
|
|
41678
|
-
throw err;
|
|
41679
|
-
}
|
|
41680
|
-
const out = [];
|
|
41681
|
-
for (const name of entries) {
|
|
41682
|
-
try {
|
|
41683
|
-
const raw = await import_node_fs29.promises.readFile(this.metaPath(name), "utf8");
|
|
41684
|
-
const parsed = ProjectMetadataSchema.safeParse(JSON.parse(raw));
|
|
41685
|
-
if (parsed.success) out.push(parsed.data);
|
|
41686
|
-
} catch {
|
|
41687
|
-
}
|
|
41688
|
-
}
|
|
41689
|
-
return out.sort((a, b2) => a.name.localeCompare(b2.name));
|
|
41690
|
-
}
|
|
41691
|
-
async create(name) {
|
|
41692
|
-
const existing = await this.list();
|
|
41693
|
-
if (existing.some((p2) => p2.name === name)) {
|
|
41694
|
-
throw new Error(`project "${name}" already exists / \u5DF2\u5B58\u5728`);
|
|
41695
|
-
}
|
|
41696
|
-
const usedPorts = new Set(existing.map((p2) => p2.port));
|
|
41697
|
-
let port = -1;
|
|
41698
|
-
for (let p2 = PROJECT_PORT_MIN; p2 <= PROJECT_PORT_MAX; p2++) {
|
|
41699
|
-
if (!usedPorts.has(p2)) {
|
|
41700
|
-
port = p2;
|
|
41701
|
-
break;
|
|
41702
|
-
}
|
|
41703
|
-
}
|
|
41704
|
-
if (port < 0) {
|
|
41705
|
-
throw new Error(
|
|
41706
|
-
`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`
|
|
41707
|
-
);
|
|
41708
|
-
}
|
|
41709
|
-
const meta = {
|
|
41710
|
-
name,
|
|
41711
|
-
port,
|
|
41712
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
41713
|
-
};
|
|
41714
|
-
const validated = ProjectMetadataSchema.safeParse(meta);
|
|
41715
|
-
if (!validated.success) {
|
|
41716
|
-
throw new Error(`invalid name "${name}": ${validated.error.message}`);
|
|
41717
|
-
}
|
|
41718
|
-
const dir = this.projectDir(name);
|
|
41719
|
-
await import_node_fs29.promises.mkdir(dir, { recursive: true });
|
|
41720
|
-
await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(meta, null, 2) + "\n", "utf8");
|
|
41721
|
-
return meta;
|
|
41722
|
-
}
|
|
41723
|
-
async delete(name) {
|
|
41724
|
-
const dir = this.projectDir(name);
|
|
41725
|
-
await import_node_fs29.promises.rm(dir, { recursive: true, force: true });
|
|
41726
|
-
}
|
|
41727
|
-
async updatePort(name, newPort) {
|
|
41728
|
-
if (newPort < PROJECT_PORT_MIN || newPort > PROJECT_PORT_MAX) {
|
|
41729
|
-
throw new Error(
|
|
41730
|
-
`port range invalid: must be ${PROJECT_PORT_MIN}-${PROJECT_PORT_MAX}, got ${newPort}`
|
|
41731
|
-
);
|
|
41732
|
-
}
|
|
41733
|
-
const list = await this.list();
|
|
41734
|
-
const target = list.find((p2) => p2.name === name);
|
|
41735
|
-
if (!target) throw new Error(`project "${name}" not found / \u4E0D\u5B58\u5728`);
|
|
41736
|
-
const conflict = list.find((p2) => p2.name !== name && p2.port === newPort);
|
|
41737
|
-
if (conflict) {
|
|
41738
|
-
throw new Error(`port ${newPort} already used / \u5DF2\u88AB project "${conflict.name}" \u5360\u7528`);
|
|
41739
|
-
}
|
|
41740
|
-
const updated = { ...target, port: newPort };
|
|
41741
|
-
await import_node_fs29.promises.writeFile(this.metaPath(name), JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
41742
|
-
return updated;
|
|
41743
|
-
}
|
|
41744
|
-
};
|
|
41745
|
-
|
|
41746
|
-
// src/app-builder/dev-server-supervisor.ts
|
|
41747
|
-
var import_node_child_process7 = require("child_process");
|
|
41748
|
-
var import_node_fs30 = require("fs");
|
|
41749
|
-
var import_node_path42 = require("path");
|
|
41750
|
-
var import_node_events2 = require("events");
|
|
41751
|
-
function resolveDevCwd(projectCwd) {
|
|
41752
|
-
if ((0, import_node_fs30.existsSync)((0, import_node_path42.join)(projectCwd, "package.json"))) return projectCwd;
|
|
41753
|
-
if ((0, import_node_fs30.existsSync)((0, import_node_path42.join)(projectCwd, "web", "package.json"))) return (0, import_node_path42.join)(projectCwd, "web");
|
|
41754
|
-
return null;
|
|
41755
|
-
}
|
|
41756
|
-
var DevServerSupervisor = class extends import_node_events2.EventEmitter {
|
|
41757
|
-
running = /* @__PURE__ */ new Map();
|
|
41758
|
-
// key: sessionId
|
|
41759
|
-
logger = { warn: () => {
|
|
41760
|
-
}, info: () => {
|
|
41761
|
-
} };
|
|
41762
|
-
/** daemon entry 启动后注入 logger,让 child 输出走 clawd.log */
|
|
41763
|
-
setLogger(logger) {
|
|
41764
|
-
this.logger = logger;
|
|
41765
|
-
}
|
|
41766
|
-
startForSession(args) {
|
|
41767
|
-
if (this.running.has(args.sessionId)) return;
|
|
41768
|
-
const augmentedPath = [
|
|
41769
|
-
process.env.PATH ?? "",
|
|
41770
|
-
"/opt/homebrew/bin",
|
|
41771
|
-
"/usr/local/bin",
|
|
41772
|
-
`${process.env.HOME}/.nvm/versions/node`,
|
|
41773
|
-
`${process.env.HOME}/.local/share/pnpm`
|
|
41774
|
-
].filter(Boolean).join(":");
|
|
41775
|
-
const env = {
|
|
41776
|
-
...process.env,
|
|
41777
|
-
PATH: augmentedPath,
|
|
41778
|
-
CLAWD_TUNNEL_HOST: args.tunnelHost ?? "",
|
|
41779
|
-
CLAWD_PREVIEW_PORT: String(args.port)
|
|
41780
|
-
};
|
|
41781
|
-
const resolvedCwd = resolveDevCwd(args.cwd);
|
|
41782
|
-
if (!resolvedCwd) {
|
|
41783
|
-
this.logger.warn("dev-server.no-manifest", {
|
|
41784
|
-
projectName: args.projectName,
|
|
41785
|
-
port: args.port,
|
|
41786
|
-
projectCwd: args.cwd,
|
|
41787
|
-
hint: "\u9879\u76EE\u76EE\u5F55\u91CC\u6CA1\u627E\u5230 package.json\uFF08\u9876\u5C42\u6216 web/ \u90FD\u6CA1\u6709\uFF09\u3002\u5148\u5728 chat \u91CC\u8BA9 assistant \u8DD1 extension-kit/scripts/new-extension.sh \u5B8C\u6210 scaffold"
|
|
41788
|
-
});
|
|
41789
|
-
this.emit("devServer:failed", {
|
|
41790
|
-
sessionId: args.sessionId,
|
|
41791
|
-
projectName: args.projectName,
|
|
41792
|
-
port: args.port,
|
|
41793
|
-
exitCode: null
|
|
41794
|
-
});
|
|
41795
|
-
return;
|
|
41796
|
-
}
|
|
41797
|
-
this.logger.info("dev-server.start", {
|
|
41798
|
-
projectName: args.projectName,
|
|
41799
|
-
port: args.port,
|
|
41800
|
-
projectCwd: args.cwd,
|
|
41801
|
-
resolvedCwd,
|
|
41802
|
-
usedSubdir: resolvedCwd !== args.cwd ? "web" : null,
|
|
41803
|
-
sessionId: args.sessionId,
|
|
41804
|
-
tunnelHost: args.tunnelHost
|
|
41805
|
-
});
|
|
41806
|
-
const child = (0, import_node_child_process7.spawn)("pnpm", ["dev"], { cwd: resolvedCwd, env, stdio: "pipe", shell: true });
|
|
41807
|
-
const entry = { ...args, process: child };
|
|
41808
|
-
this.running.set(args.sessionId, entry);
|
|
41809
|
-
child.stdout?.on("data", (chunk) => {
|
|
41810
|
-
this.logger.info("dev-server.stdout", {
|
|
41811
|
-
projectName: args.projectName,
|
|
41812
|
-
port: args.port,
|
|
41813
|
-
chunk: chunk.toString().trimEnd()
|
|
41814
|
-
});
|
|
41815
|
-
});
|
|
41816
|
-
child.stderr?.on("data", (chunk) => {
|
|
41817
|
-
this.logger.warn("dev-server.stderr", {
|
|
41818
|
-
projectName: args.projectName,
|
|
41819
|
-
port: args.port,
|
|
41820
|
-
chunk: chunk.toString().trimEnd()
|
|
41821
|
-
});
|
|
41822
|
-
});
|
|
41823
|
-
child.on("error", (err) => {
|
|
41824
|
-
this.running.delete(args.sessionId);
|
|
41825
|
-
this.logger.warn("dev-server.spawn-failed", {
|
|
41826
|
-
projectName: args.projectName,
|
|
41827
|
-
port: args.port,
|
|
41828
|
-
error: err.message,
|
|
41829
|
-
code: err.code
|
|
41830
|
-
});
|
|
41831
|
-
this.emit("devServer:failed", {
|
|
41832
|
-
sessionId: args.sessionId,
|
|
41833
|
-
projectName: args.projectName,
|
|
41834
|
-
port: args.port,
|
|
41835
|
-
exitCode: null
|
|
41836
|
-
});
|
|
41837
|
-
});
|
|
41838
|
-
child.on("exit", (code) => {
|
|
41839
|
-
this.running.delete(args.sessionId);
|
|
41840
|
-
this.logger.info("dev-server.exit", { projectName: args.projectName, port: args.port, code });
|
|
41841
|
-
if (code !== 0 && code !== null) {
|
|
41842
|
-
this.emit("devServer:failed", {
|
|
41843
|
-
sessionId: args.sessionId,
|
|
41844
|
-
projectName: args.projectName,
|
|
41845
|
-
port: args.port,
|
|
41846
|
-
exitCode: code
|
|
41847
|
-
});
|
|
41848
|
-
}
|
|
41849
|
-
});
|
|
41850
|
-
}
|
|
41851
|
-
stopForSession(sessionId) {
|
|
41852
|
-
const entry = this.running.get(sessionId);
|
|
41853
|
-
if (!entry) return Promise.resolve();
|
|
41854
|
-
return new Promise((resolve6) => {
|
|
41855
|
-
entry.process.once("exit", () => {
|
|
41856
|
-
this.running.delete(sessionId);
|
|
41857
|
-
resolve6();
|
|
41858
|
-
});
|
|
41859
|
-
entry.process.kill("SIGTERM");
|
|
41860
|
-
});
|
|
41861
|
-
}
|
|
41862
|
-
async stopForProject(projectName) {
|
|
41863
|
-
const entry = [...this.running.values()].find((e) => e.projectName === projectName);
|
|
41864
|
-
if (!entry) return;
|
|
41865
|
-
await this.stopForSession(entry.sessionId);
|
|
41866
|
-
}
|
|
41867
|
-
async restartForProjectAt(args) {
|
|
41868
|
-
await this.stopForSession(args.sessionId);
|
|
41869
|
-
this.startForSession({
|
|
41870
|
-
sessionId: args.sessionId,
|
|
41871
|
-
projectName: args.projectName,
|
|
41872
|
-
port: args.newPort,
|
|
41873
|
-
cwd: args.cwd,
|
|
41874
|
-
tunnelHost: args.tunnelHost
|
|
41875
|
-
});
|
|
41876
|
-
}
|
|
41877
|
-
isRunning(projectName) {
|
|
41878
|
-
return [...this.running.values()].some((e) => e.projectName === projectName);
|
|
41879
|
-
}
|
|
41880
|
-
boundSessionId(projectName) {
|
|
41881
|
-
const entry = [...this.running.values()].find((e) => e.projectName === projectName);
|
|
41882
|
-
return entry?.sessionId ?? null;
|
|
41883
|
-
}
|
|
41884
|
-
boundEntry(projectName) {
|
|
41885
|
-
const entry = [...this.running.values()].find((e) => e.projectName === projectName);
|
|
41886
|
-
if (!entry) return null;
|
|
41887
|
-
return {
|
|
41888
|
-
sessionId: entry.sessionId,
|
|
41889
|
-
cwd: entry.cwd,
|
|
41890
|
-
port: entry.port,
|
|
41891
|
-
tunnelHost: entry.tunnelHost
|
|
41892
|
-
};
|
|
41893
|
-
}
|
|
41894
|
-
};
|
|
41895
|
-
|
|
41896
41755
|
// src/handlers/method-grants.ts
|
|
41897
41756
|
var ADMIN_ANY = {
|
|
41898
41757
|
kind: "fixed",
|
|
@@ -41909,23 +41768,22 @@ var METHOD_GRANT_MAP = {
|
|
|
41909
41768
|
// "查 capabilityManager 拿 caller 的 guest capability"。
|
|
41910
41769
|
"whoami": { kind: "public" },
|
|
41911
41770
|
// ---- capability platform(admin-only) ----
|
|
41912
|
-
//
|
|
41913
|
-
// personal
|
|
41771
|
+
// 飞书统一身份 Phase 2 (2026-06-01, spec 决策 #12): personal-cap:get / capability:issue /
|
|
41772
|
+
// bindPeer 已下线(personal token / 邀请码全链路删除)。capability:list / delete 保留供调试 +
|
|
41773
|
+
// 撤销级联 + 清旧 per-peer cap。
|
|
41914
41774
|
"capability:list": ADMIN_ANY,
|
|
41915
41775
|
"capability:delete": ADMIN_ANY,
|
|
41916
|
-
"personal-cap:get": ADMIN_ANY,
|
|
41917
41776
|
// ---- inbox: channel-based IM (capability platform v3) ----
|
|
41918
41777
|
// 三条都 'public' — capability 本身就是授权凭证. handler 内额外校验
|
|
41919
41778
|
// guest ctx.capabilityId === args.capabilityId, 防 guest 越权操作别的 channel.
|
|
41920
41779
|
"inbox:list": { kind: "public" },
|
|
41921
41780
|
"inbox:markRead": { kind: "public" },
|
|
41922
41781
|
"inbox:postMessage": { kind: "public" },
|
|
41923
|
-
// ---- received-capability:* (
|
|
41924
|
-
// owner-only
|
|
41782
|
+
// ---- received-capability:* (联系人列表,视角 B 双向授权模型) ----
|
|
41783
|
+
// 飞书统一身份 Phase 2 (spec 决策 #12): owner-only add / guest-only autoAttach 已删除
|
|
41784
|
+
// (hub:connect 落同一 store + 自动反向替代)。list / remove 保留作联系人读 / 删。
|
|
41925
41785
|
"received-capability:list": ADMIN_ANY,
|
|
41926
|
-
"received-capability:add": ADMIN_ANY,
|
|
41927
41786
|
"received-capability:remove": ADMIN_ANY,
|
|
41928
|
-
"received-capability:autoAttach": { kind: "public" },
|
|
41929
41787
|
// ---- session:* / chat:* 业务方法(v2 Phase 8 两层模型)----
|
|
41930
41788
|
// dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
|
|
41931
41789
|
// owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
|
|
@@ -42023,14 +41881,15 @@ var METHOD_GRANT_MAP = {
|
|
|
42023
41881
|
// guest-self:guest 在自己 daemon 上触发安装与更新(无持久化 subscription 概念)
|
|
42024
41882
|
"extension.install-from-channel": ADMIN_ANY,
|
|
42025
41883
|
"extension.update-from-channel": ADMIN_ANY,
|
|
42026
|
-
// ----
|
|
42027
|
-
//
|
|
42028
|
-
|
|
42029
|
-
|
|
42030
|
-
"
|
|
42031
|
-
|
|
42032
|
-
|
|
42033
|
-
"
|
|
41884
|
+
// ---- auth:* 飞书统一身份 Phase 1(owner-only) ----
|
|
41885
|
+
// 登录/登出/查身份都是 owner 本机操作,guest 不可调
|
|
41886
|
+
"auth:login:start": ADMIN_ANY,
|
|
41887
|
+
"auth:getIdentity": ADMIN_ANY,
|
|
41888
|
+
"auth:logout": ADMIN_ANY,
|
|
41889
|
+
// ---- hub:* 飞书统一身份 Phase 2(owner-only) ----
|
|
41890
|
+
// hub 目录 / 连接都是 owner 本机操作(用 owner 的 ttcJwt 找中心 server),guest 不可调
|
|
41891
|
+
"hub:list": ADMIN_ANY,
|
|
41892
|
+
"hub:connect": ADMIN_ANY
|
|
42034
41893
|
};
|
|
42035
41894
|
function computeGrantForFrame(method, frame) {
|
|
42036
41895
|
const rule = METHOD_GRANT_MAP[method];
|
|
@@ -42046,12 +41905,12 @@ function computeGrantForFrame(method, frame) {
|
|
|
42046
41905
|
}
|
|
42047
41906
|
|
|
42048
41907
|
// src/extension/runtime.ts
|
|
42049
|
-
var
|
|
42050
|
-
var
|
|
41908
|
+
var import_node_child_process7 = require("child_process");
|
|
41909
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
42051
41910
|
var import_promises10 = require("timers/promises");
|
|
42052
41911
|
|
|
42053
41912
|
// src/extension/port-allocator.ts
|
|
42054
|
-
var
|
|
41913
|
+
var import_node_net = __toESM(require("net"), 1);
|
|
42055
41914
|
var PortExhaustedError = class extends Error {
|
|
42056
41915
|
constructor(min, max) {
|
|
42057
41916
|
super(`no free port in [${min},${max}]`);
|
|
@@ -42064,7 +41923,7 @@ var PortExhaustedError = class extends Error {
|
|
|
42064
41923
|
};
|
|
42065
41924
|
function probe(port) {
|
|
42066
41925
|
return new Promise((resolve6) => {
|
|
42067
|
-
const srv =
|
|
41926
|
+
const srv = import_node_net.default.createServer();
|
|
42068
41927
|
srv.once("error", () => resolve6(false));
|
|
42069
41928
|
srv.once("listening", () => {
|
|
42070
41929
|
srv.close(() => resolve6(true));
|
|
@@ -42148,13 +42007,13 @@ var Runtime = class {
|
|
|
42148
42007
|
/\$CLAWOS_EXT_PORT/g,
|
|
42149
42008
|
String(port)
|
|
42150
42009
|
);
|
|
42151
|
-
const dir =
|
|
42010
|
+
const dir = import_node_path41.default.join(this.root, extId);
|
|
42152
42011
|
const env = {
|
|
42153
42012
|
...process.env,
|
|
42154
42013
|
CLAWOS_EXT_PORT: String(port),
|
|
42155
42014
|
CLAWOS_EXT_ID: extId
|
|
42156
42015
|
};
|
|
42157
|
-
const child = (0,
|
|
42016
|
+
const child = (0, import_node_child_process7.spawn)("sh", ["-c", cmd], {
|
|
42158
42017
|
cwd: dir,
|
|
42159
42018
|
env,
|
|
42160
42019
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -42260,7 +42119,7 @@ ${handle.stderrTail}`
|
|
|
42260
42119
|
|
|
42261
42120
|
// src/extension/published-channels.ts
|
|
42262
42121
|
var import_promises11 = __toESM(require("fs/promises"), 1);
|
|
42263
|
-
var
|
|
42122
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
42264
42123
|
init_zod();
|
|
42265
42124
|
var PublishedChannelsError = class extends Error {
|
|
42266
42125
|
constructor(code, message) {
|
|
@@ -42359,7 +42218,7 @@ var PublishedChannelStore = class {
|
|
|
42359
42218
|
)
|
|
42360
42219
|
};
|
|
42361
42220
|
const tmp = `${this.filePath}.tmp`;
|
|
42362
|
-
await import_promises11.default.mkdir(
|
|
42221
|
+
await import_promises11.default.mkdir(import_node_path42.default.dirname(this.filePath), { recursive: true });
|
|
42363
42222
|
await import_promises11.default.writeFile(tmp, JSON.stringify(data, null, 2), { mode: 384 });
|
|
42364
42223
|
await import_promises11.default.rename(tmp, this.filePath);
|
|
42365
42224
|
}
|
|
@@ -42367,7 +42226,7 @@ var PublishedChannelStore = class {
|
|
|
42367
42226
|
|
|
42368
42227
|
// src/extension/bundle-cache.ts
|
|
42369
42228
|
var import_promises12 = __toESM(require("fs/promises"), 1);
|
|
42370
|
-
var
|
|
42229
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
42371
42230
|
var BundleCache = class {
|
|
42372
42231
|
constructor(rootDir) {
|
|
42373
42232
|
this.rootDir = rootDir;
|
|
@@ -42376,14 +42235,14 @@ var BundleCache = class {
|
|
|
42376
42235
|
/** Atomic write: stage tmp → rename. Caller passes the hex sha256. */
|
|
42377
42236
|
async write(snapshotHash, buffer) {
|
|
42378
42237
|
await import_promises12.default.mkdir(this.rootDir, { recursive: true });
|
|
42379
|
-
const file =
|
|
42238
|
+
const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
42380
42239
|
const tmp = `${file}.tmp`;
|
|
42381
42240
|
await import_promises12.default.writeFile(tmp, buffer, { mode: 384 });
|
|
42382
42241
|
await import_promises12.default.rename(tmp, file);
|
|
42383
42242
|
}
|
|
42384
42243
|
/** Returns the bundle bytes, or null when the file doesn't exist. */
|
|
42385
42244
|
async read(snapshotHash) {
|
|
42386
|
-
const file =
|
|
42245
|
+
const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
42387
42246
|
try {
|
|
42388
42247
|
return await import_promises12.default.readFile(file);
|
|
42389
42248
|
} catch (e) {
|
|
@@ -42393,7 +42252,7 @@ var BundleCache = class {
|
|
|
42393
42252
|
}
|
|
42394
42253
|
/** Idempotent — missing file is not an error. */
|
|
42395
42254
|
async delete(snapshotHash) {
|
|
42396
|
-
const file =
|
|
42255
|
+
const file = import_node_path43.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
42397
42256
|
await import_promises12.default.rm(file, { force: true });
|
|
42398
42257
|
}
|
|
42399
42258
|
};
|
|
@@ -42402,7 +42261,7 @@ var BundleCache = class {
|
|
|
42402
42261
|
async function startDaemon(config) {
|
|
42403
42262
|
const logger = createLogger({
|
|
42404
42263
|
level: config.logLevel,
|
|
42405
|
-
file:
|
|
42264
|
+
file: import_node_path44.default.join(config.dataDir, "clawd.log")
|
|
42406
42265
|
});
|
|
42407
42266
|
logger.info("starting clawd", { version, config: { port: config.port, host: config.host, dataDir: config.dataDir } });
|
|
42408
42267
|
const stateMgr = new StateFileManager({ dataDir: config.dataDir });
|
|
@@ -42420,8 +42279,11 @@ async function startDaemon(config) {
|
|
|
42420
42279
|
} else if (config.tunnel) {
|
|
42421
42280
|
resolvedAuthToken = authFile.token;
|
|
42422
42281
|
}
|
|
42423
|
-
const
|
|
42424
|
-
const
|
|
42282
|
+
const ownerIdentityStore = new OwnerIdentityStore(config.dataDir);
|
|
42283
|
+
const feishuIdentity = ownerIdentityStore.read();
|
|
42284
|
+
let feishuActive = feishuIdentity !== null;
|
|
42285
|
+
let ownerPrincipalId = feishuIdentity?.identity.unionId ?? authFile.ownerPrincipalId;
|
|
42286
|
+
let ownerDisplayName = feishuIdentity?.identity.displayName ?? loadOwnerDisplayName(config.dataDir);
|
|
42425
42287
|
const authMode = resolvedAuthToken == null ? "none" : "first-message";
|
|
42426
42288
|
const inboxStore = new InboxStore(config.dataDir);
|
|
42427
42289
|
const inboxManager = new InboxManager(inboxStore, (_peerOwnerId, frame) => {
|
|
@@ -42431,8 +42293,6 @@ async function startDaemon(config) {
|
|
|
42431
42293
|
receivedCapabilityStore.load();
|
|
42432
42294
|
const capabilityStore = new CapabilityStore(config.dataDir);
|
|
42433
42295
|
const capabilityRegistry = new CapabilityRegistry(capabilityStore);
|
|
42434
|
-
const personalCapability = loadOrCreatePersonalCapability({ dataDir: config.dataDir });
|
|
42435
|
-
capabilityRegistry.upsertCapability(personalCapability.capability);
|
|
42436
42296
|
const capabilityManager = new CapabilityManager(capabilityRegistry, {
|
|
42437
42297
|
onDeleted: (cap) => {
|
|
42438
42298
|
const deletedAt = Date.now();
|
|
@@ -42458,15 +42318,36 @@ async function startDaemon(config) {
|
|
|
42458
42318
|
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
42459
42319
|
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 capabilityRegistry.
|
|
42460
42320
|
expectedToken: resolvedAuthToken,
|
|
42461
|
-
authenticate: (t,
|
|
42462
|
-
|
|
42321
|
+
authenticate: async (t, _selfPrincipalId, _selfDisplayName, selfUrl) => {
|
|
42322
|
+
const result = await authenticate(t, {
|
|
42463
42323
|
isOwnerToken: (x) => resolvedAuthToken != null && constantTimeEqual(x, resolvedAuthToken),
|
|
42464
42324
|
ownerPrincipalId,
|
|
42465
42325
|
ownerDisplayName,
|
|
42466
|
-
|
|
42467
|
-
|
|
42468
|
-
|
|
42326
|
+
// 飞书 gate(spec 决策 #9):未激活飞书身份时不接受 guest 入站(不注入 introspect
|
|
42327
|
+
// → 一律 BAD_TOKEN)。已激活时 hub 用自己的 ttcJwt 做验票方认证;
|
|
42328
|
+
// ttcJwt 每次验票时从 store 现读(热切换后立即用新身份)。
|
|
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
|
+
} : {}
|
|
42469
42340
|
});
|
|
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;
|
|
42470
42351
|
},
|
|
42471
42352
|
onAuthed: (h, ctx) => wsServer?.attachClientContext(h.id, ctx),
|
|
42472
42353
|
buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName),
|
|
@@ -42489,7 +42370,7 @@ async function startDaemon(config) {
|
|
|
42489
42370
|
const agents = new AgentsScanner();
|
|
42490
42371
|
const history = new ClaudeHistoryReader();
|
|
42491
42372
|
let transport = null;
|
|
42492
|
-
const personaStore = new PersonaStore(
|
|
42373
|
+
const personaStore = new PersonaStore(import_node_path44.default.join(config.dataDir, "personas"));
|
|
42493
42374
|
const defaultsRoot = findDefaultsRoot();
|
|
42494
42375
|
if (defaultsRoot) {
|
|
42495
42376
|
seedDefaultPersonas({ store: personaStore, defaultsRoot, logger });
|
|
@@ -42506,7 +42387,7 @@ async function startDaemon(config) {
|
|
|
42506
42387
|
getAdapter,
|
|
42507
42388
|
historyReader: history,
|
|
42508
42389
|
dataDir: config.dataDir,
|
|
42509
|
-
personaRoot:
|
|
42390
|
+
personaRoot: import_node_path44.default.join(config.dataDir, "personas"),
|
|
42510
42391
|
personaStore,
|
|
42511
42392
|
ownerDisplayName,
|
|
42512
42393
|
ownerPrincipalId,
|
|
@@ -42530,10 +42411,10 @@ async function startDaemon(config) {
|
|
|
42530
42411
|
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
42531
42412
|
attachmentGroup: {
|
|
42532
42413
|
onFileEdit: (input) => {
|
|
42533
|
-
const absPath =
|
|
42414
|
+
const absPath = import_node_path44.default.isAbsolute(input.relPath) ? input.relPath : import_node_path44.default.join(input.cwd, input.relPath);
|
|
42534
42415
|
let size = 0;
|
|
42535
42416
|
try {
|
|
42536
|
-
size =
|
|
42417
|
+
size = import_node_fs29.default.statSync(absPath).size;
|
|
42537
42418
|
} catch (err) {
|
|
42538
42419
|
logger.warn("attachment.onFileEdit stat failed", {
|
|
42539
42420
|
sessionId: input.sessionId,
|
|
@@ -42616,23 +42497,33 @@ async function startDaemon(config) {
|
|
|
42616
42497
|
}
|
|
42617
42498
|
return `http://${config.host}:${config.port}`;
|
|
42618
42499
|
};
|
|
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
|
+
};
|
|
42619
42516
|
const extensionRuntime = new Runtime({ root: extensionsRoot() });
|
|
42620
42517
|
const publishedChannelStore = new PublishedChannelStore(publishedChannelsFile());
|
|
42621
42518
|
await publishedChannelStore.load();
|
|
42622
42519
|
const bundleCache = new BundleCache(bundleCacheRoot());
|
|
42623
|
-
const
|
|
42624
|
-
|
|
42625
|
-
|
|
42626
|
-
|
|
42627
|
-
|
|
42628
|
-
|
|
42629
|
-
|
|
42630
|
-
const effectivePreviewPorts = Array.from(
|
|
42631
|
-
/* @__PURE__ */ new Set([...config.previewPorts ?? [], ...appBuilderPortRange])
|
|
42632
|
-
);
|
|
42633
|
-
const deleteSessionsByProject = async (_name) => {
|
|
42634
|
-
};
|
|
42635
|
-
const handlers = buildMethodHandlers({
|
|
42520
|
+
const loginFlow = new LoginFlow({
|
|
42521
|
+
store: ownerIdentityStore,
|
|
42522
|
+
fetchUserInfo: (ttcToken) => fetchTtcUserInfo({ apiBase: TTC_HOSTS.api, token: ttcToken }),
|
|
42523
|
+
ttcAuthBase: TTC_HOSTS.auth,
|
|
42524
|
+
getCallbackUrl: () => `http://127.0.0.1:${config.port}/auth/callback`
|
|
42525
|
+
});
|
|
42526
|
+
const makeHandlers = () => buildMethodHandlers({
|
|
42636
42527
|
manager,
|
|
42637
42528
|
workspace,
|
|
42638
42529
|
skills,
|
|
@@ -42671,17 +42562,12 @@ async function startDaemon(config) {
|
|
|
42671
42562
|
// 'persona/<pid>/owner',default 走 'default'。
|
|
42672
42563
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
42673
42564
|
// guest path guard:candidate 必须在 personaRoot 子树下
|
|
42674
|
-
personaRoot:
|
|
42565
|
+
personaRoot: import_node_path44.default.join(config.dataDir, "personas")
|
|
42675
42566
|
},
|
|
42676
42567
|
// workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
|
|
42677
|
-
personaRoot:
|
|
42568
|
+
personaRoot: import_node_path44.default.join(config.dataDir, "personas"),
|
|
42678
42569
|
// capability:list / delete handler 依赖
|
|
42679
42570
|
capabilityManager,
|
|
42680
|
-
// personal-cap:get 返回的 shareBaseUrl 用此 base URL:
|
|
42681
|
-
// tunnel 拉起后切到反代 wss://... 地址;否则本机 ws://host:port。
|
|
42682
|
-
getShareBaseUrl: () => currentTunnelUrl ?? `ws://${config.host}:${config.port}`,
|
|
42683
|
-
// global personal token (2026-05-25): personal-cap:get handler 直接返回此 token + cap
|
|
42684
|
-
getPersonalCapability: () => personalCapability,
|
|
42685
42571
|
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
42686
42572
|
ownerDisplayName,
|
|
42687
42573
|
// owner-id stabilization: whoami 用稳定 ownerPrincipalId 替代 'owner' 字面量
|
|
@@ -42693,24 +42579,55 @@ async function startDaemon(config) {
|
|
|
42693
42579
|
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
42694
42580
|
inboxManager,
|
|
42695
42581
|
inboxStore,
|
|
42696
|
-
//
|
|
42582
|
+
// 联系人列表 store(hub:connect 落同一 store;视角 B 双向授权模型)
|
|
42697
42583
|
receivedCapabilityStore,
|
|
42698
|
-
// received-capability:
|
|
42584
|
+
// received-capability:removed broadcast;复用 capability:tokenIssued 同款通路
|
|
42699
42585
|
broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
|
|
42700
|
-
// received-capability:add 临时 ws 连远端跑 whoami — 直连 ws (不走 transport listener)
|
|
42701
|
-
connectRemote,
|
|
42702
42586
|
// extension runtime (v1: local-mode only). Instance owned by daemon top
|
|
42703
42587
|
// level so stopAll() runs in the shutdown hook below.
|
|
42704
42588
|
extensionRuntime,
|
|
42705
42589
|
// extension sharing v1 — owner-side published-channels store.
|
|
42706
42590
|
publishedChannelStore,
|
|
42707
42591
|
bundleCache,
|
|
42708
|
-
//
|
|
42709
|
-
|
|
42710
|
-
|
|
42711
|
-
|
|
42712
|
-
|
|
42592
|
+
// 飞书统一身份 Phase 1:登录 RPC handlers(auth:login:start / getIdentity / logout)
|
|
42593
|
+
feishuAuth: { loginFlow, ownerIdentityStore },
|
|
42594
|
+
// 飞书统一身份 Phase 2:hub 目录 + 连接(中心 server 代理)。
|
|
42595
|
+
// exchange/listHubs 包掉 owner ttcJwt(每次现读 store——热切换后立即用新身份);
|
|
42596
|
+
// dispatcher 的 FEISHU_GATED_METHODS gate 已保证未登录时这些 RPC 到不了 handler,
|
|
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
|
+
}
|
|
42713
42629
|
});
|
|
42630
|
+
let handlers = makeHandlers();
|
|
42714
42631
|
const authResolver = new AuthContextResolver({
|
|
42715
42632
|
ownerToken: resolvedAuthToken
|
|
42716
42633
|
});
|
|
@@ -42736,8 +42653,30 @@ async function startDaemon(config) {
|
|
|
42736
42653
|
},
|
|
42737
42654
|
// POST /extensions/import lands in ~/.clawd/extensions/<id>/
|
|
42738
42655
|
extensionsRoot: () => extensionsRoot(),
|
|
42739
|
-
//
|
|
42740
|
-
|
|
42656
|
+
// 飞书登录 loopback 回调(热切换,2026-06-01 拍板):成功落盘后——
|
|
42657
|
+
// 1. 进程内实时切换身份(let binding + 值快照持有者重建)
|
|
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
|
+
}
|
|
42741
42680
|
});
|
|
42742
42681
|
wsServer = new LocalWsServer({
|
|
42743
42682
|
host: config.host,
|
|
@@ -42776,8 +42715,6 @@ async function startDaemon(config) {
|
|
|
42776
42715
|
},
|
|
42777
42716
|
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
42778
42717
|
httpRequestHandler: httpRouter,
|
|
42779
|
-
// app-builder build 模式:/preview/<port>/ HMR websocket upgrade 转发的端口白名单(同 http-router 配置)
|
|
42780
|
-
previewPorts: effectivePreviewPorts,
|
|
42781
42718
|
// 订阅成功后给该 client 重放 in-flight pendingQuestions(plan: clawd-question-server-truth)。
|
|
42782
42719
|
// daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
|
|
42783
42720
|
// 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
|
|
@@ -42822,6 +42759,12 @@ async function startDaemon(config) {
|
|
|
42822
42759
|
const requestId = typeof frame.requestId === "string" ? frame.requestId : void 0;
|
|
42823
42760
|
const handler = handlers[type];
|
|
42824
42761
|
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
|
+
}
|
|
42825
42768
|
const ctx = wsServer.getClientContext(client.id) ?? ownerContext(ownerPrincipalId, ownerDisplayName);
|
|
42826
42769
|
const verdict = computeGrantForFrame(type, frame);
|
|
42827
42770
|
if (verdict.kind === "check") {
|
|
@@ -42894,13 +42837,12 @@ async function startDaemon(config) {
|
|
|
42894
42837
|
stateSnapshot = { ...stateSnapshot, tunnelUrl: r.url, tunnelError: void 0 };
|
|
42895
42838
|
stateMgr.write(stateSnapshot);
|
|
42896
42839
|
currentTunnelUrl = r.url;
|
|
42897
|
-
wss.broadcastAll({ type: "tunnel:ready", url: r.url, subdomain: r.subdomain });
|
|
42898
42840
|
const connectUrl = resolvedAuthToken ? `${r.url}#token=${resolvedAuthToken}` : r.url;
|
|
42899
42841
|
const lines = [
|
|
42900
42842
|
`Tunnel: ${r.url}`,
|
|
42901
42843
|
...resolvedAuthToken ? [`Connect: ${connectUrl}`] : [],
|
|
42902
|
-
`Frpc config: ${
|
|
42903
|
-
`Frpc log: ${
|
|
42844
|
+
`Frpc config: ${import_node_path44.default.join(config.dataDir, "frpc.toml")}`,
|
|
42845
|
+
`Frpc log: ${import_node_path44.default.join(config.dataDir, "frpc.log")}`
|
|
42904
42846
|
];
|
|
42905
42847
|
const width = Math.max(...lines.map((l) => l.length));
|
|
42906
42848
|
const bar = "\u2550".repeat(width + 4);
|
|
@@ -42913,10 +42855,11 @@ ${bar}
|
|
|
42913
42855
|
|
|
42914
42856
|
`);
|
|
42915
42857
|
try {
|
|
42916
|
-
const connectPath =
|
|
42917
|
-
|
|
42858
|
+
const connectPath = import_node_path44.default.join(config.dataDir, "connect.txt");
|
|
42859
|
+
import_node_fs29.default.writeFileSync(connectPath, lines.join("\n") + "\n", { mode: 384 });
|
|
42918
42860
|
} catch {
|
|
42919
42861
|
}
|
|
42862
|
+
void reportHubUrl();
|
|
42920
42863
|
} catch (err) {
|
|
42921
42864
|
const tunnelError = err?.message ?? String(err);
|
|
42922
42865
|
try {
|
|
@@ -42980,9 +42923,9 @@ ${bar}
|
|
|
42980
42923
|
};
|
|
42981
42924
|
}
|
|
42982
42925
|
function migrateDropPersonsDir(dataDir) {
|
|
42983
|
-
const dir =
|
|
42926
|
+
const dir = import_node_path44.default.join(dataDir, "persons");
|
|
42984
42927
|
try {
|
|
42985
|
-
|
|
42928
|
+
import_node_fs29.default.rmSync(dir, { recursive: true, force: true });
|
|
42986
42929
|
} catch {
|
|
42987
42930
|
}
|
|
42988
42931
|
}
|