@clawos-dev/clawd 0.2.620 → 0.2.622
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/app-builder-plugin/mcp-server.cjs +73 -567
- package/dist/cli.cjs +171 -1024
- package/dist/dispatch/mcp-server.cjs +73 -567
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/deploy-kit/scripts/ensure-credentials.sh +10 -47
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/deploy-kit/scripts/ensure-toolchain.sh +3 -17
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/deploy-kit/scripts/new-extension.sh +8 -21
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/deploy-kit/scripts/publish.sh +14 -67
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/deploy-kit/scripts/remove-extension.sh +6 -24
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/deploy-kit/scripts/verify.sh +3 -15
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/mcp-server.cjs +73 -567
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/hooks/pre-deploy.sh +0 -2
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/deploy-kit/scripts/ensure-credentials.sh +10 -47
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/deploy-kit/scripts/ensure-toolchain.sh +3 -17
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/deploy-kit/scripts/new-extension.sh +8 -21
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/deploy-kit/scripts/publish.sh +14 -67
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/deploy-kit/scripts/remove-extension.sh +6 -24
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/deploy-kit/scripts/verify.sh +3 -15
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/mcp-server.cjs +73 -567
- package/dist/pi-mcp-bridge/extension.cjs +0 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -5842,14 +5842,11 @@ var init_attachment_schemas = __esm({
|
|
|
5842
5842
|
TOKEN_ROLES = ["owner"];
|
|
5843
5843
|
GROUP_FILE_SOURCES = ["agent", "owner", "user-upload"];
|
|
5844
5844
|
GroupFileEntrySchema = external_exports.object({
|
|
5845
|
-
/** daemon 派发的稳定 id(用于 RPC remove / UI key) */
|
|
5846
5845
|
id: external_exports.string().min(1),
|
|
5847
|
-
/**
|
|
5846
|
+
/** Current entries may be absolute; legacy entries can be relative to session.cwd. */
|
|
5848
5847
|
relPath: external_exports.string().min(1),
|
|
5849
5848
|
from: external_exports.enum(GROUP_FILE_SOURCES),
|
|
5850
|
-
/** owner 手动加入时的可选备注 */
|
|
5851
5849
|
label: external_exports.string().optional(),
|
|
5852
|
-
/** 文件字节数(stat 时拍) */
|
|
5853
5850
|
size: external_exports.number().int().nonnegative(),
|
|
5854
5851
|
mime: external_exports.string().min(1),
|
|
5855
5852
|
/** 加入清单时间戳(ms) */
|
|
@@ -5860,19 +5857,15 @@ var init_attachment_schemas = __esm({
|
|
|
5860
5857
|
stale: external_exports.boolean().optional()
|
|
5861
5858
|
});
|
|
5862
5859
|
AttachmentSignUrlArgs = external_exports.object({
|
|
5863
|
-
/** 群文件所属的 session */
|
|
5864
5860
|
sessionId: external_exports.string().min(1),
|
|
5865
|
-
/**
|
|
5861
|
+
/** Absolute path or legacy cwd-relative path; normalized for group ACL matching. */
|
|
5866
5862
|
relPath: external_exports.string().min(1),
|
|
5867
5863
|
/** TTL 秒数;缺省 / null = 永久 URL(不带 e 字段);只有显式传秒数才限时 */
|
|
5868
5864
|
ttlSeconds: external_exports.number().int().positive().nullable().optional(),
|
|
5869
|
-
/**
|
|
5870
|
-
* /share/md/... viewer URL(同一签名,daemon 端换前缀),外人浏览器打开就是
|
|
5871
|
-
* drawer 同款 ReactMarkdown 渲染。FilesDrawer 等不关心渲染的 caller 不传。 */
|
|
5865
|
+
/** Select raw-file or markdown-viewer route; both use the same signature. */
|
|
5872
5866
|
view: external_exports.enum(["raw", "md-rendered"]).optional()
|
|
5873
5867
|
});
|
|
5874
5868
|
AttachmentSignUrlResponseSchema = external_exports.object({
|
|
5875
|
-
/** 完整 URL(含 httpBaseUrl 前缀),UI 直接 window.open / 复制到剪贴板 */
|
|
5876
5869
|
url: external_exports.string().min(1),
|
|
5877
5870
|
/** 失效时间戳(ms);null = 永久 */
|
|
5878
5871
|
expiresAt: external_exports.number().int().nonnegative().nullable()
|
|
@@ -5880,7 +5873,6 @@ var init_attachment_schemas = __esm({
|
|
|
5880
5873
|
AttachmentGroupAddArgs = external_exports.object({
|
|
5881
5874
|
sessionId: external_exports.string().min(1),
|
|
5882
5875
|
relPath: external_exports.string().min(1),
|
|
5883
|
-
/** owner 手动加入时可选备注;agent 自动入清单不走此 RPC */
|
|
5884
5876
|
label: external_exports.string().optional(),
|
|
5885
5877
|
/**
|
|
5886
5878
|
* 统一 RPC 鉴权(spec 2026-07-28 §8 决策 B):agent 经 clawd-rpc 调用持 owner token,
|
|
@@ -5907,9 +5899,8 @@ var init_attachment_schemas = __esm({
|
|
|
5907
5899
|
AttachmentUploadResponseSchema = external_exports.object({
|
|
5908
5900
|
/** sha256(bytes) 前 16 位,前端持有的稳定 ref */
|
|
5909
5901
|
attachmentId: external_exports.string().min(1),
|
|
5910
|
-
/** 完整 tunnel signed URL,前端拼 marker / 直接送 Anthropic 都用它 */
|
|
5911
5902
|
url: external_exports.string().min(1),
|
|
5912
|
-
/**
|
|
5903
|
+
/** Absolute path for subsequent group RPCs, despite the legacy field name. */
|
|
5913
5904
|
relPath: external_exports.string().min(1),
|
|
5914
5905
|
sizeBytes: external_exports.number().int().nonnegative(),
|
|
5915
5906
|
mimeType: external_exports.string().min(1)
|
|
@@ -5935,27 +5926,15 @@ var init_capability = __esm({
|
|
|
5935
5926
|
}).strict();
|
|
5936
5927
|
CapabilitySchema = external_exports.object({
|
|
5937
5928
|
id: external_exports.string().min(1),
|
|
5938
|
-
// sha256(token) hex 64
|
|
5939
5929
|
secretHash: external_exports.string().regex(/^[a-f0-9]{64}$/),
|
|
5940
5930
|
displayName: external_exports.string(),
|
|
5941
|
-
// 空数组合法 = 纯访客(只能 DM)
|
|
5942
5931
|
grants: external_exports.array(GrantSchema),
|
|
5943
5932
|
issuedAt: external_exports.number().int().nonnegative(),
|
|
5944
5933
|
expiresAt: external_exports.number().int().positive().optional(),
|
|
5945
5934
|
maxUses: external_exports.number().int().positive().optional(),
|
|
5946
5935
|
usedCount: external_exports.number().int().nonnegative(),
|
|
5947
|
-
/**
|
|
5948
|
-
* guest 在 auth 帧捎带 selfPrincipalId 时由 AuthGate.authenticate 回写;
|
|
5949
|
-
* 未被消费的 cap 此字段缺失。
|
|
5950
|
-
*/
|
|
5951
5936
|
peerOwnerId: external_exports.string().min(1).optional(),
|
|
5952
|
-
/** 首次 client hello 时间戳(与 peerOwnerId 同步写入) */
|
|
5953
5937
|
firstUsedByPeerAt: external_exports.number().int().positive().optional(),
|
|
5954
|
-
/**
|
|
5955
|
-
* 对方 daemon owner 的 displayName,guest auth 帧 selfDisplayName 触发
|
|
5956
|
-
* recordPeerHello 时同步回写。视角 B (2026-05-23) UI 派生联系人列表用此字段
|
|
5957
|
-
* 显示对方名字(避免 fallback 到我颁 cap 时填的 displayName)。
|
|
5958
|
-
*/
|
|
5959
5938
|
peerDisplayName: external_exports.string().optional()
|
|
5960
5939
|
}).strict();
|
|
5961
5940
|
CapabilityWireSchema = CapabilitySchema.omit({ secretHash: true });
|
|
@@ -5965,9 +5944,7 @@ var init_capability = __esm({
|
|
|
5965
5944
|
id: external_exports.string(),
|
|
5966
5945
|
kind: external_exports.enum(["owner", "guest"]),
|
|
5967
5946
|
displayName: external_exports.string(),
|
|
5968
|
-
/** 归属人 id(决策 #16:TTC unique_id 等,落 contact.ownerId) */
|
|
5969
5947
|
ownerId: external_exports.string(),
|
|
5970
|
-
/** 身份来源(决策 #16:'ttc'/'google',落 contact.provider) */
|
|
5971
5948
|
provider: external_exports.string()
|
|
5972
5949
|
}).strict(),
|
|
5973
5950
|
capability: CapabilityWireSchema,
|
|
@@ -6095,27 +6072,10 @@ var init_environment_schemas = __esm({
|
|
|
6095
6072
|
"aliyun"
|
|
6096
6073
|
];
|
|
6097
6074
|
EnvPackageSchema = external_exports.object({
|
|
6098
|
-
// 装完后在 PATH 里能直接调到的名字('aliyun' / 's' / 'pnpm' / 'python3');npm 包报 bin 名,包名进 from
|
|
6099
6075
|
name: external_exports.string().min(1),
|
|
6100
|
-
/**
|
|
6101
|
-
* 从哪装,约定 `<kind>:<ref>`:`npm:@serverless-devs/s` / `github:aliyun/aliyun-cli` /
|
|
6102
|
-
* `apt:python3`。
|
|
6103
|
-
*
|
|
6104
|
-
* **不是装饰字段,是构建器的必需输入**:可执行名往往推不出安装源——Serverless Devs 的命令
|
|
6105
|
-
* 就叫单个字母 `s`,没人能从 's' 猜到要 `npm i -g @serverless-devs/s`;`aliyun` 也不在
|
|
6106
|
-
* apt 源里,得从 GitHub release 拉 tarball。扫描器读 ensure-toolchain.sh 的时候本来就看见
|
|
6107
|
-
* 了这些,顺手带出来即可。
|
|
6108
|
-
*
|
|
6109
|
-
* 它同时解决人的阅读问题:单据上只写一个 `s`,看的人不知道那是什么东西。
|
|
6110
|
-
*
|
|
6111
|
-
* 允许缺省:扫描器拿不准安装源时不该编一个(编错了构建期才炸,还查不出来源)。缺省时构建
|
|
6112
|
-
* 器会失败或猜错,流水线当场红——这跟整体「宁可让流水线发现,不要靠猜」的取向一致。
|
|
6113
|
-
*
|
|
6114
|
-
* 只给一个源,不给平台回落链:本地那套 brew → GitHub → npm 的三级兜底是
|
|
6115
|
-
* ensure-toolchain.sh 的事,云上只构一个镜像、一个基础环境,多给反而要求构建器做选择。
|
|
6116
|
-
*/
|
|
6076
|
+
/** Installation source, not inferable from executable name. A missing source remains unresolved rather than invented; cloud uses one source instead of local fallback chains. */
|
|
6117
6077
|
from: external_exports.string().optional(),
|
|
6118
|
-
//
|
|
6078
|
+
// Exact requested version; daemon can use local version for non-apt packages. Apt versions follow the cloud Debian repository; absence means unpinned.
|
|
6119
6079
|
version: external_exports.string().optional(),
|
|
6120
6080
|
// 版本下限('20.6' 这种)。与 version 的区别:version 是「就要这个」,min 是「不低于」。
|
|
6121
6081
|
// 两者都缺省即完全不约束。
|
|
@@ -6131,7 +6091,6 @@ var init_environment_schemas = __esm({
|
|
|
6131
6091
|
* 与 version 并存:老板事后在页面上手改 version(显式接受差异)时,version ≠ localVersion 一眼可见。
|
|
6132
6092
|
*/
|
|
6133
6093
|
localVersion: external_exports.string().optional(),
|
|
6134
|
-
/** `github:` 专用——那个版本是哪个文件。见 EnvArtifactSchema */
|
|
6135
6094
|
artifact: EnvArtifactSchema.optional(),
|
|
6136
6095
|
/**
|
|
6137
6096
|
* `apt:` 专用——daemon 上报时查过底座那个 Debian(CLOUD_DEBIAN)的仓库,**没有**这个包。云上装不了,怎么办由老板在页面上定(换源 / 去掉 / 接受);
|
|
@@ -6146,7 +6105,6 @@ var init_environment_schemas = __esm({
|
|
|
6146
6105
|
aptVersion: external_exports.string().optional()
|
|
6147
6106
|
});
|
|
6148
6107
|
EnvSecretSchema = external_exports.object({
|
|
6149
|
-
// 凭据的命名引用('aliyun-ak' / 'supabase-shared')。部署时按这个名字去解析实际的值。
|
|
6150
6108
|
name: external_exports.string().min(1),
|
|
6151
6109
|
// 解析出的值注入到哪些环境变量。一份凭据可含多个字段(AK 是 id+secret 两个,
|
|
6152
6110
|
// supabase 是 url/anon/service-role/jwt 四个),所以是数组不是单值。
|
|
@@ -6184,7 +6142,6 @@ var init_environment_schemas = __esm({
|
|
|
6184
6142
|
return o2;
|
|
6185
6143
|
};
|
|
6186
6144
|
MANIFEST_BODY = {
|
|
6187
|
-
// 构镜像时要装进去的所有东西(工具 / 运行时 / 包)。底座已有的构建器会跳过;persona 目录整个进镜像,不在这里列
|
|
6188
6145
|
packages: external_exports.array(EnvPackageSchema),
|
|
6189
6146
|
secrets: external_exports.array(EnvSecretSchema),
|
|
6190
6147
|
/**
|
|
@@ -6207,9 +6164,7 @@ var init_environment_schemas = __esm({
|
|
|
6207
6164
|
EnvEvidenceSchema = external_exports.object({
|
|
6208
6165
|
// 这条证据支持的是哪一项:`${kind}:${name}`,如 'package:aliyun' / 'secret:aliyun-ak' / 'connector:feishu'
|
|
6209
6166
|
target: external_exports.string().min(1),
|
|
6210
|
-
// 一句人话:为什么需要它 + 从哪看出来的。单据默认显示这句。
|
|
6211
6167
|
reason: external_exports.string().min(1),
|
|
6212
|
-
// persona 目录内的相对路径('CLAUDE.md' / '.claude/skills/foo/SKILL.md')
|
|
6213
6168
|
file: external_exports.string().min(1),
|
|
6214
6169
|
// 原文摘录(一两句即可,不是整段)。默认收起,起疑时才看。
|
|
6215
6170
|
//
|
|
@@ -6272,35 +6227,15 @@ var init_deploy_schemas = __esm({
|
|
|
6272
6227
|
DeployScanSchema = external_exports.object({
|
|
6273
6228
|
/** 历史里指名用(发布 / patch / putSecret 都按它找)。daemon 生成;存量单条记录迁过来的叫 `legacy` */
|
|
6274
6229
|
id: external_exports.string().min(1),
|
|
6275
|
-
/**
|
|
6276
|
-
* 这次扫描的清单——原来在 `deploy.json` 里的那份,现在每次扫描各存一份。
|
|
6277
|
-
* 扫描期由 `deploy:reportFinding` 逐条长出来,扫完人在页面上改(`deploy:patch`)也改这份;
|
|
6278
|
-
* 发布时选中的那条被写成 `deploy.json` 进仓库。
|
|
6279
|
-
*/
|
|
6230
|
+
/** Scan findings and manual patches modify this snapshot; release writes the selected snapshot into deploy.json. */
|
|
6280
6231
|
manifest: EnvironmentManifestSchema,
|
|
6281
|
-
/** 干这次扫描的 butler 会话,「看它怎么干的」入口 */
|
|
6282
6232
|
scanSessionId: external_exports.string().optional(),
|
|
6283
6233
|
status: DeployScanStatusSchema,
|
|
6284
6234
|
evidence: external_exports.array(EnvEvidenceSchema).default([]),
|
|
6285
6235
|
notes: external_exports.array(external_exports.string()).default([]),
|
|
6286
|
-
/**
|
|
6287
|
-
* 这次扫描是**对哪个内容版本的断言**:起扫描前先把 persona 目录提交到本地(只提交不推,推是发布的事),
|
|
6288
|
-
* 落存那一下的 HEAD 就是它——发布 commit 不算内容版本,见 `contentHeadOf`。所以这个值一定是仓库里真实存在的一版,
|
|
6289
|
-
* 同一内容上扫几次 `head` 都相同。**从此不动**:发布不再把它挪成发布 commit(线上是哪版记在 `DeployReleased` 里)。
|
|
6290
|
-
*
|
|
6291
|
-
* 有了它,「清单过没过时」才是个可判定的等式(`head === contentHeadOf(repo, released)`),
|
|
6292
|
-
* 而不是从 `dirty` / `ahead` 里猜——那两个字段判不出「清单是好几版之前扫的」。
|
|
6293
|
-
*
|
|
6294
|
-
* 缺席 = 旧版 clawd 扫的记录。**一律当过时**:没有第三种情况,重扫一次就有了。
|
|
6295
|
-
*/
|
|
6236
|
+
/** Immutable content commit asserted by this scan. Missing on old records means stale; release commits are tracked separately. */
|
|
6296
6237
|
head: external_exports.string().min(1).optional(),
|
|
6297
|
-
/**
|
|
6298
|
-
* 这次**实际**怎么扫的。档位由 daemon 定(有线上版本可比 + 有跑完的清单 → 软扫,否则硬扫),
|
|
6299
|
-
* 页面要显示的是真正发生的那个。
|
|
6300
|
-
*
|
|
6301
|
-
* `.default('hard')` 而不是必填:这个文件是 strict 解析的,存量记录没有这个字段,
|
|
6302
|
-
* 定成必填会让 `readScan` 当场抛、上云页面直接打不开。而老记录确实都是从零扫的。
|
|
6303
|
-
*/
|
|
6238
|
+
/** Actual scan mode, including fallback. Default hard keeps pre-field records readable because those scans were full scans. */
|
|
6304
6239
|
baseKind: ScanBaseKindSchema.default("hard"),
|
|
6305
6240
|
/** 软扫比的那个 commit(= 当时 `DeployReleased.head`)。硬扫时缺席 */
|
|
6306
6241
|
base: external_exports.string().min(1).optional(),
|
|
@@ -6323,17 +6258,8 @@ var init_deploy_schemas = __esm({
|
|
|
6323
6258
|
url: external_exports.string().min(1),
|
|
6324
6259
|
branch: external_exports.string().min(1),
|
|
6325
6260
|
head: external_exports.string().min(1),
|
|
6326
|
-
/**
|
|
6327
|
-
* persona 的**内容**有没有未提交的改动。
|
|
6328
|
-
*
|
|
6329
|
-
* **两个文件不算**(daemon 侧 `repoInfo` 的 `DIRTY_IGNORED` 把它们排出了 pathspec):
|
|
6330
|
-
* - `.clawd/deploy.json`——发布产物,`deploy:release` 那一刻整份重写;算进来的话发布前后那几秒页面会闪「有未提交的改动」
|
|
6331
|
-
* - `.clawd/persona.json`——结构化配置(tool / model / size …),扫描不读它、流水线从发布 commit 读它,改了不需要重扫
|
|
6332
|
-
* (design 2026-09-21-deploy-page-polish §3.4)
|
|
6333
|
-
* `.clawd/` 下别的、以及 persona 的内容(CLAUDE.md / skills / .mcp.json)照旧算。
|
|
6334
|
-
*/
|
|
6261
|
+
/** Uncommitted persona content, excluding release output and structured persona configuration, neither of which requires rescanning. */
|
|
6335
6262
|
dirty: external_exports.boolean(),
|
|
6336
|
-
/** 本地领先远端几个提交 */
|
|
6337
6263
|
ahead: external_exports.number()
|
|
6338
6264
|
}).strict();
|
|
6339
6265
|
DeploySecretPresenceSchema = external_exports.object({
|
|
@@ -6349,7 +6275,6 @@ var init_deploy_schemas = __esm({
|
|
|
6349
6275
|
DeployJobSchema = external_exports.object({
|
|
6350
6276
|
key: DeployStageKeySchema,
|
|
6351
6277
|
status: DeployStageStatusSchema,
|
|
6352
|
-
/** 这个 job 在 GitHub 上的页面。还没跑起来的段没有 */
|
|
6353
6278
|
url: external_exports.string().optional(),
|
|
6354
6279
|
/** GitHub 的 job databaseId,`gh run rerun --job` 要用。还没跑起来的段没有 */
|
|
6355
6280
|
jobId: external_exports.number().optional(),
|
|
@@ -6377,9 +6302,7 @@ var init_deploy_schemas = __esm({
|
|
|
6377
6302
|
/** 本机 GitHub 身份(机器级,githubAuth:* 的同一份;上云页顶上那张卡用)。老 daemon 不给(可选只为兼容),UI 缺席时按未知处理 */
|
|
6378
6303
|
github: GithubAuthStatusSchema.optional(),
|
|
6379
6304
|
repo: DeployRepoSchema.optional(),
|
|
6380
|
-
/** 扫描历史,升序,最后一条最新。没扫过就是空数组 */
|
|
6381
6305
|
scans: external_exports.array(DeployScanSchema).default([]),
|
|
6382
|
-
/** 发布历史,升序,最后一条 = 线上正跑的那版。从没发布过就是空数组 */
|
|
6383
6306
|
releases: external_exports.array(DeployReleasedSchema).default([]),
|
|
6384
6307
|
/** 没仓库时缺席(查不了) */
|
|
6385
6308
|
secrets: external_exports.array(DeploySecretPresenceSchema).optional(),
|
|
@@ -6394,7 +6317,6 @@ var init_deploy_schemas = __esm({
|
|
|
6394
6317
|
* 不看 `releases`:发布过不等于还在线上(下线过、或者被人用脚本撤了)
|
|
6395
6318
|
*/
|
|
6396
6319
|
online: external_exports.boolean().optional(),
|
|
6397
|
-
/** 最新那次下线流水线。没下线过 / 没仓库 / 没 GitHub 身份 / 查不到都缺席 */
|
|
6398
6320
|
unpublish: DeployUnpublishRunSchema.optional(),
|
|
6399
6321
|
/**
|
|
6400
6322
|
* `.clawd/deploy-scan.json` 读不出来(坏 JSON / 不合形状)时的人话。有它 `scans` 为空。
|
|
@@ -6424,7 +6346,6 @@ var init_deploy_schemas = __esm({
|
|
|
6424
6346
|
}).strict();
|
|
6425
6347
|
DeployPatchArgsSchema = external_exports.object({
|
|
6426
6348
|
personaId: external_exports.string().min(1),
|
|
6427
|
-
/** 改的是历史里哪次扫描的清单。扫描期 butler 删条目也走这条(scanId 在它的任务提示里) */
|
|
6428
6349
|
scanId: external_exports.string().min(1),
|
|
6429
6350
|
patch: external_exports.object({
|
|
6430
6351
|
/**
|
|
@@ -6432,10 +6353,7 @@ var init_deploy_schemas = __esm({
|
|
|
6432
6353
|
* aptMissing、连接器上点过的 credentialSource / auth)快照里缺了会从记录里补回——旧快照冲不掉它们。
|
|
6433
6354
|
*/
|
|
6434
6355
|
manifest: EnvironmentManifestSchema.optional(),
|
|
6435
|
-
/**
|
|
6436
|
-
* 只换清单里**同名那一条**连接器(上云页连接器卡片上的点选走这条)。不带整份清单:页面手里的清单可能是旧快照,
|
|
6437
|
-
* 整份写回会把 daemon 之后写进去的东西冲掉(2026-09-23 persona-log-tracer 的 hbi 资产就是这么丢的)。同名的不在 → 拒
|
|
6438
|
-
*/
|
|
6356
|
+
/** Replace one named connector to avoid overwriting newer unrelated fields with a stale UI manifest snapshot. Missing names reject. */
|
|
6439
6357
|
connector: EnvConnectorSchema.optional()
|
|
6440
6358
|
}).strict()
|
|
6441
6359
|
}).strict();
|
|
@@ -6443,7 +6361,6 @@ var init_deploy_schemas = __esm({
|
|
|
6443
6361
|
personaId: external_exports.string().min(1),
|
|
6444
6362
|
/** 按哪次扫描的清单校验凭据名 / env 名。值本身是仓库级的(GitHub Secrets),跟扫描无关 */
|
|
6445
6363
|
scanId: external_exports.string().min(1),
|
|
6446
|
-
// 对应 manifest.secrets[].name
|
|
6447
6364
|
name: external_exports.string().min(1),
|
|
6448
6365
|
// 环境变量名 → 值。key 必须是该 secret 声明过的 env 之一,handler 校验。
|
|
6449
6366
|
values: external_exports.record(external_exports.string(), external_exports.string())
|
|
@@ -6457,7 +6374,6 @@ var init_deploy_schemas = __esm({
|
|
|
6457
6374
|
});
|
|
6458
6375
|
DeployRerunJobArgsSchema = external_exports.object({
|
|
6459
6376
|
personaId: external_exports.string().min(1),
|
|
6460
|
-
/** GitHub 的 job databaseId。**是数字**——字符串会在拼命令时静默出错 */
|
|
6461
6377
|
jobId: external_exports.number()
|
|
6462
6378
|
}).strict();
|
|
6463
6379
|
DeployReportFindingArgsSchema = external_exports.object({
|
|
@@ -6562,38 +6478,17 @@ var init_contact = __esm({
|
|
|
6562
6478
|
ownerId: external_exports.string().min(1),
|
|
6563
6479
|
/** 身份来源(决策 #16):'ttc'(飞书/手机/邮箱登入 TTC)| 'google' | ...,token 背书 */
|
|
6564
6480
|
provider: external_exports.string().min(1),
|
|
6565
|
-
/** 联系人显示名 */
|
|
6566
6481
|
displayName: external_exports.string(),
|
|
6567
|
-
/** 该设备可达的 ws/tunnel 地址 */
|
|
6568
6482
|
remoteUrl: external_exports.string().min(1),
|
|
6569
|
-
//
|
|
6570
|
-
// 联系人,但它不持有 A 颁发的 token(A 从未给它换票)→ connectToken 为空串。
|
|
6571
|
-
// device:connect 出站路径仍存中心 server 签发的真实 connect token(断线重连复用)。
|
|
6572
|
-
// 不用 .min(1):自动反向路径合法地存空串,禁止填假 token 占位(不造假数据)。
|
|
6483
|
+
// Accept historical empty tokens for schema compatibility. Current handshake paths exchange a real audience-bound token before storing.
|
|
6573
6484
|
connectToken: external_exports.string(),
|
|
6574
6485
|
grants: external_exports.array(GrantSchema),
|
|
6575
6486
|
addedAt: external_exports.number().int().nonnegative(),
|
|
6576
|
-
/**
|
|
6577
|
-
* pin 时间戳(ms,Date.now() 语义);null = 未 pin。sidebar 顶部固定区渲染由此驱动。
|
|
6578
|
-
* 老 contacts.json 缺此字段 → zod default 补 null(无破坏性升级;不 orphan)。
|
|
6579
|
-
* 对齐 SessionFile.pinnedAt 语义(daemon 侧持久化,contact:pin RPC 更新 → contact:pinned push)。
|
|
6580
|
-
*/
|
|
6487
|
+
/** Pin timestamp in ms; null means unpinned. The default preserves records created before this field existed. */
|
|
6581
6488
|
pinnedAt: external_exports.number().int().nullable().default(null),
|
|
6582
|
-
/**
|
|
6583
|
-
* 远程访问授权(spec 2026-07-16)。owner 视角:允许该 contact 反向操作我的设备——
|
|
6584
|
-
* 跨设备 exec 的总开关(peerExec:run handler 运行时判它)。default=false,owner 显式开。
|
|
6585
|
-
* 授权粒度 per-people (per-contact):借用别人 persona 本来就带信任对方本人的前提。
|
|
6586
|
-
*/
|
|
6489
|
+
/** Owner-controlled permission for this contact to execute commands on the local device. Default denial requires explicit opt-in. */
|
|
6587
6490
|
remoteAccessAllowed: external_exports.boolean().default(false),
|
|
6588
|
-
/**
|
|
6589
|
-
* owner 给这台设备写的备注(2026-08-31)。**我对他那台机器的私记**,不上行、不同步给对方。
|
|
6590
|
-
*
|
|
6591
|
-
* 动机:一人多设备时卡片里只能显示 deviceId(`owner-8bf96a56-6b7e-…` 这种 uuid),
|
|
6592
|
-
* 认不出哪台是哪台。备注让用户能写「老王的 MacBook」。
|
|
6593
|
-
*
|
|
6594
|
-
* 空串 = 没写。default('') 兼容老 contacts.json(无破坏性升级,不 orphan)。
|
|
6595
|
-
* 跟 pinnedAt / remoteAccessAllowed 同类:owner 已配置状态,重复建联 upsert 必须保留。
|
|
6596
|
-
*/
|
|
6491
|
+
/** Local-only owner note. Empty-string default preserves older records; repeated handshakes must retain it. */
|
|
6597
6492
|
note: external_exports.string().default("")
|
|
6598
6493
|
}).strict();
|
|
6599
6494
|
ContactWireSchema = ContactSchema;
|
|
@@ -6650,20 +6545,15 @@ var init_feishu_auth = __esm({
|
|
|
6650
6545
|
"use strict";
|
|
6651
6546
|
init_zod();
|
|
6652
6547
|
FEISHU_GATED_METHODS = [
|
|
6653
|
-
// 联系人列表(device:connect 落同一 store;list 读 / remove 删)
|
|
6654
6548
|
"contact:list",
|
|
6655
6549
|
"contact:remove",
|
|
6656
|
-
// DM / 跨用户通知
|
|
6657
6550
|
"inbox:list",
|
|
6658
6551
|
"inbox:markRead",
|
|
6659
6552
|
"inbox:postMessage",
|
|
6660
|
-
// mirror peer sessions(远端会话镜像)
|
|
6661
6553
|
"peerSession:upsert",
|
|
6662
6554
|
"peerSession:remove",
|
|
6663
|
-
// 设备目录 + 连接(决策 #15:daemon 代理中心 server,需要 owner 的登录票)
|
|
6664
6555
|
"device:list",
|
|
6665
6556
|
"device:connect",
|
|
6666
|
-
// iOS TestFlight 意向登记(需要 owner unionId 上报中心 server)
|
|
6667
6557
|
"onboarding:iosInterest"
|
|
6668
6558
|
];
|
|
6669
6559
|
LOGIN_EXPIRED_ERROR_MARKER = "LOGIN_EXPIRED";
|
|
@@ -6673,9 +6563,7 @@ var init_feishu_auth = __esm({
|
|
|
6673
6563
|
* 中心 server clawd_devices 注册表仅作服务发现(url 下发 + 目录),不在安全路径上。
|
|
6674
6564
|
*/
|
|
6675
6565
|
deviceId: external_exports.string().min(1),
|
|
6676
|
-
/** 显示名 */
|
|
6677
6566
|
name: external_exports.string().min(1),
|
|
6678
|
-
/** ws/tunnel 地址 */
|
|
6679
6567
|
url: external_exports.string().min(1)
|
|
6680
6568
|
});
|
|
6681
6569
|
DeviceListResponseSchema = external_exports.object({
|
|
@@ -6683,7 +6571,6 @@ var init_feishu_auth = __esm({
|
|
|
6683
6571
|
devices: external_exports.array(DeviceEntrySchema)
|
|
6684
6572
|
});
|
|
6685
6573
|
DeviceConnectArgsSchema = external_exports.object({
|
|
6686
|
-
/** 目标设备的 deviceId(对方 daemon 生成的 dev-<uuid>) */
|
|
6687
6574
|
deviceId: external_exports.string().min(1),
|
|
6688
6575
|
/**
|
|
6689
6576
|
* 目标设备的 ws/tunnel 地址(可选):
|
|
@@ -6692,12 +6579,11 @@ var init_feishu_auth = __esm({
|
|
|
6692
6579
|
* - 注册表不在安全路径上:知道对方 url 时显式传入即可连接(不依赖中心 server 服务发现)
|
|
6693
6580
|
*/
|
|
6694
6581
|
url: external_exports.string().min(1).optional(),
|
|
6695
|
-
/**
|
|
6582
|
+
/** Optional display name; otherwise populate it from the handshake acknowledgement. */
|
|
6696
6583
|
name: external_exports.string().optional()
|
|
6697
6584
|
});
|
|
6698
6585
|
DeviceConnectResponseSchema = external_exports.object({
|
|
6699
6586
|
type: external_exports.literal("device:connect:ok"),
|
|
6700
|
-
/** 连接的目标设备标识(= 请求 args.deviceId) */
|
|
6701
6587
|
deviceId: external_exports.string(),
|
|
6702
6588
|
name: external_exports.string(),
|
|
6703
6589
|
url: external_exports.string()
|
|
@@ -6707,7 +6593,6 @@ var init_feishu_auth = __esm({
|
|
|
6707
6593
|
ownerId: external_exports.string().min(1),
|
|
6708
6594
|
/** 身份来源:'ttc'(飞书/手机/邮箱登入 TTC)| 'google' | ...(决策 #16,拓展用) */
|
|
6709
6595
|
provider: external_exports.string().min(1),
|
|
6710
|
-
/** 显示名。TTC user_info 接口 data.name */
|
|
6711
6596
|
displayName: external_exports.string().min(1),
|
|
6712
6597
|
/** TTC user_info 接口 data.avatar_url,可缺省 */
|
|
6713
6598
|
avatarUrl: external_exports.string().optional(),
|
|
@@ -6716,7 +6601,6 @@ var init_feishu_auth = __esm({
|
|
|
6716
6601
|
});
|
|
6717
6602
|
AuthLoginStartResponseSchema = external_exports.object({
|
|
6718
6603
|
type: external_exports.literal("auth:login:start:ok"),
|
|
6719
|
-
/** 完整 TTC 授权页 URL(含 callback_url + state + auto=1),UI 直接 window.open */
|
|
6720
6604
|
authUrl: external_exports.string().min(1),
|
|
6721
6605
|
/** 防 CSRF 的一次性 nonce;回调时 daemon 校验 */
|
|
6722
6606
|
state: external_exports.string().min(1)
|
|
@@ -6725,11 +6609,7 @@ var init_feishu_auth = __esm({
|
|
|
6725
6609
|
type: external_exports.literal("auth:getIdentity:ok"),
|
|
6726
6610
|
/** null = 未登录 */
|
|
6727
6611
|
identity: FeishuIdentitySchema.nullable(),
|
|
6728
|
-
/**
|
|
6729
|
-
* 本机设备标识(决策 #15:daemon 生成并持久化在 auth.json,如 dev-<uuid>)。
|
|
6730
|
-
* 设备属性,与登录态无关、登录登出不变。UI"我的设备标识"用它拼
|
|
6731
|
-
* clawos://device/<deviceId>(替代旧的 unionId——不再向外暴露人的身份标识)。
|
|
6732
|
-
*/
|
|
6612
|
+
/** Stable device identifier independent of login, used for the shareable device link. */
|
|
6733
6613
|
deviceId: external_exports.string().min(1)
|
|
6734
6614
|
});
|
|
6735
6615
|
AuthLogoutResponseSchema = external_exports.object({
|
|
@@ -6788,39 +6668,21 @@ var init_dispatch = __esm({
|
|
|
6788
6668
|
})
|
|
6789
6669
|
]);
|
|
6790
6670
|
DispatchRunArgsSchema = external_exports.object({
|
|
6791
|
-
// 新开必填;续派(dispatchId 非空)时可省略——targetPersonaId 从台账行取。
|
|
6792
6671
|
targetPersona: external_exports.string().min(1).optional(),
|
|
6793
6672
|
// 续派(spec 2026-07-21,单 dispatchId 语义:一件事一行一 id):非空 = 把 prompt
|
|
6794
6673
|
// 作为新 turn 打进该 dispatch 的原 worker session 接着干(台账行复位 running)。
|
|
6795
6674
|
// 省略 = 新开一件事,daemon 铸新 id。
|
|
6796
6675
|
dispatchId: external_exports.string().min(1).optional(),
|
|
6797
6676
|
prompt: external_exports.string(),
|
|
6798
|
-
// 派活方写的 2-3 句摘要(与 prompt 同源同结构)。落台账 `taskBrief` + 进任务包信封
|
|
6799
|
-
// `<brief>`,worker session 的卡片头常显。optional 同 DispatchOutcome.brief 的理由。
|
|
6800
6677
|
brief: external_exports.string().optional(),
|
|
6801
6678
|
// 跨设备 dispatch:非空 = A 角色,转发到该 contact deviceId 的 peer daemon;
|
|
6802
6679
|
// 省略 = 本地 dispatch / B 角色本地执行。A 转发给 B 时 body 不带此字段。
|
|
6803
6680
|
targetDeviceId: external_exports.string().min(1).optional(),
|
|
6804
|
-
//
|
|
6805
|
-
// 合法值集**从属于 tool**(见下面的 tool 字段):claude 侧与 tools/claude.ts
|
|
6806
|
-
// CLAUDE_MODEL_IDS 一致,codex 侧是运行时查来的模型表;handler 层做二次校验,
|
|
6807
|
-
// 非法值 fail-fast。protocol 层保持 string(不在协议里钉死枚举)。
|
|
6808
|
-
// **只能用于新开 dispatch**:续派不改任何 spawn 参数(worker session 已经跑起来了),
|
|
6809
|
-
// 与 dispatchId 互斥(下方 refine 硬拒)。
|
|
6681
|
+
// Model values depend on the selected engine and are validated against its runtime catalog. Overrides apply only to new dispatches.
|
|
6810
6682
|
model: external_exports.string().min(1).optional(),
|
|
6811
|
-
//
|
|
6812
|
-
// 语义是**一次性**——不改 persona 的持久绑定,只决定这条 dispatch 起的 worker session。
|
|
6813
|
-
// 为什么需要:`model` 的合法值集从属于 tool,想让一个 claude persona 这次用
|
|
6814
|
-
// `gpt-5.6-sol` 干活,必须能连 engine 一起换(否则 model 校验按 persona.tool 选表直接拒)。
|
|
6815
|
-
// 合法值集 = daemon 的 adapter 注册表('claude' / 'codex'),handler 层运行时校验;
|
|
6816
|
-
// protocol 层保持 string(与 model / SessionFile.tool 同款处理,不在协议里钉死枚举)。
|
|
6817
|
-
// 与 model 同样**只能用于新开 dispatch**(下方 refine 硬拒 dispatchId 组合)。
|
|
6683
|
+
// One-time engine override for the worker session; it does not change the persona binding. Validate against registered adapters at runtime.
|
|
6818
6684
|
tool: external_exports.string().min(1).optional(),
|
|
6819
|
-
//
|
|
6820
|
-
// 仅在**新开 dispatch**(dispatchId 缺席)时生效;续派(dispatchId 非空)
|
|
6821
|
-
// 时 daemon 层静默丢弃(避免半途改 label 造成 sidebar 抖动)。
|
|
6822
|
-
// 上限 60 字符——sidebar 显示 tolerance,卡在 dispatch 入口即可。
|
|
6823
|
-
// 协议层不硬拒 sessionLabel + dispatchId 同传,保留 forward-compat。
|
|
6685
|
+
// Worker label applies to new dispatches only. Follow-ups tolerate this field but do not update the running session label.
|
|
6824
6686
|
sessionLabel: external_exports.string().min(1).max(60).optional(),
|
|
6825
6687
|
// 上层消费者(Master)挂载话题/线语义:topicId / lineId / 验收标准 / rejectCount 等。
|
|
6826
6688
|
// daemon **不解释**内容,只透传进台账行 DispatchRecord.meta(新开)或与既有 meta
|
|
@@ -6846,35 +6708,17 @@ var init_dispatch = __esm({
|
|
|
6846
6708
|
sourceSessionId: external_exports.string().min(1),
|
|
6847
6709
|
sourcePersonaId: external_exports.string().min(1).optional(),
|
|
6848
6710
|
targetPersonaId: external_exports.string().min(1),
|
|
6849
|
-
/**
|
|
6850
|
-
* 跨设备派活时 B 所在的设备(`DispatchRunArgs.targetDeviceId`),A 端台账记「我派给了谁的机器」。
|
|
6851
|
-
*
|
|
6852
|
-
* **缺省即本机**,与入参侧用 `undefined` 表示本机是同一条口径——不引第二种表示法。
|
|
6853
|
-
* 存量台账行没有这个字段,消费方(dispatch 卡片的归属带)据此只显示 persona、不显示设备。
|
|
6854
|
-
*
|
|
6855
|
-
* B 端自己的台账行**不会**有它:peer-forward 转发时故意 strip 掉(B 据此判定本地执行),
|
|
6856
|
-
* 那条行记的是 B 视角的本地 dispatch。
|
|
6857
|
-
*/
|
|
6711
|
+
/** A-side target device attribution; absence means local for existing records. Forwarding strips it from the B-side local ledger. */
|
|
6858
6712
|
targetDeviceId: external_exports.string().min(1).optional(),
|
|
6859
6713
|
/** B 的 sessionId;registerBSession 后回填 */
|
|
6860
6714
|
workerSessionId: external_exports.string().min(1).optional(),
|
|
6861
|
-
/** 任务摘要;截断(≤500 字符)在 daemon store.add 处保证 */
|
|
6862
6715
|
taskText: external_exports.string(),
|
|
6863
|
-
/**
|
|
6864
|
-
* 派活方写的 2-3 句任务摘要(`DispatchRunArgs.brief`)。台账列表 / butler 面板显示它
|
|
6865
|
-
* 而不是被截断到 500 字符的 `taskText` 半截原文。回报侧的 brief 随 `outcome` 已落盘,
|
|
6866
|
-
* 不在这里重复存。
|
|
6867
|
-
*/
|
|
6716
|
+
/** Explicit task summary for list/cards instead of truncated taskText; outcome owns the result summary. */
|
|
6868
6717
|
taskBrief: external_exports.string().optional(),
|
|
6869
|
-
/**
|
|
6870
|
-
* 第几轮:新开 = 1,每次续派 +1。续派**不单开分型**(一件事一行一 id),
|
|
6871
|
-
* 「第 N 轮」这个语义就落在这里,并透进任务包信封的 `round` 属性供卡片头显示。
|
|
6872
|
-
* 老台账行缺省 → 消费方按 1 处理。
|
|
6873
|
-
*/
|
|
6718
|
+
/** One ledger row across follow-up rounds. Missing on old rows is consumed as round one. */
|
|
6874
6719
|
round: external_exports.number().int().positive().optional(),
|
|
6875
6720
|
status: DispatchStatusSchema,
|
|
6876
6721
|
outcome: DispatchOutcomeSchema.optional(),
|
|
6877
|
-
/** 上层消费者语义(如司礼监的口谕/验收状态),daemon 不解释 */
|
|
6878
6722
|
meta: external_exports.record(external_exports.unknown()).optional(),
|
|
6879
6723
|
createdAt: external_exports.string().min(1),
|
|
6880
6724
|
completedAt: external_exports.string().min(1).optional(),
|
|
@@ -6883,15 +6727,7 @@ var init_dispatch = __esm({
|
|
|
6883
6727
|
});
|
|
6884
6728
|
DispatchListArgsSchema = external_exports.object({
|
|
6885
6729
|
sourceSessionId: external_exports.string().min(1).optional(),
|
|
6886
|
-
/**
|
|
6887
|
-
* 「**这条会话卷入的**所有台账行」:`sourceSessionId === it || workerSessionId === it`。
|
|
6888
|
-
*
|
|
6889
|
-
* 为什么不复用 `sourceSessionId`:一条会话既可能是派活方(在自己这儿看回报卡),也可能是
|
|
6890
|
-
* 干活方(在 worker 会话里看任务包),后者用 sourceSessionId 过滤一条也查不到。UI 的
|
|
6891
|
-
* dispatch 卡片要的正是「跟我这条会话有关的全部 dispatch」,一次调用答完,不必拉全量再本地筛。
|
|
6892
|
-
*
|
|
6893
|
-
* 与 `sourceSessionId` 同时给时两个条件都要满足(AND),但调用方没有这么用的场景。
|
|
6894
|
-
*/
|
|
6730
|
+
/** Include rows where this session is either source or worker. If sourceSessionId is also provided, both filters must match. */
|
|
6895
6731
|
relatedSessionId: external_exports.string().min(1).optional()
|
|
6896
6732
|
});
|
|
6897
6733
|
DispatchListResultSchema = external_exports.object({
|
|
@@ -7150,7 +6986,6 @@ var init_inbox = __esm({
|
|
|
7150
6986
|
id: external_exports.string().min(1),
|
|
7151
6987
|
text: external_exports.string().min(1),
|
|
7152
6988
|
createdAt: external_exports.number().int().nonnegative(),
|
|
7153
|
-
// persona 代发署名(跨 daemon forward 时随远程腿透传;真人发缺省)。
|
|
7154
6989
|
origin: external_exports.object({ kind: external_exports.literal("persona"), personaId: external_exports.string().min(1) }).optional()
|
|
7155
6990
|
}).strict();
|
|
7156
6991
|
InboxListArgsSchema = external_exports.object({
|
|
@@ -7267,7 +7102,6 @@ var init_channel = __esm({
|
|
|
7267
7102
|
ChannelSchema = external_exports.object({
|
|
7268
7103
|
id: external_exports.string().min(1),
|
|
7269
7104
|
name: external_exports.string().min(1),
|
|
7270
|
-
/** 该频道里可被派活的 persona 全集(本机 + 远程) */
|
|
7271
7105
|
memberPersonas: external_exports.array(ChannelMemberPersonaSchema),
|
|
7272
7106
|
/** 人类成员(contact deviceId):可发 DM。**不隐含任何派活权限** —— 跨设备派活认的是
|
|
7273
7107
|
* 上面那张 memberPersonas。P1 人类异步询问也用它 */
|
|
@@ -7315,20 +7149,9 @@ var init_topic = __esm({
|
|
|
7315
7149
|
TopicDecisionTypeSchema = external_exports.enum(TOPIC_DECISION_TYPES);
|
|
7316
7150
|
TopicDecisionStatusSchema = external_exports.enum(TOPIC_DECISION_STATUSES);
|
|
7317
7151
|
TopicDecisionSchema = external_exports.object({
|
|
7318
|
-
/**
|
|
7319
|
-
* 这条决策的标识,供后来的答复回指。可选:存量记录一条都没有,**不迁移**。
|
|
7320
|
-
*
|
|
7321
|
-
* 之所以要它——提问和答复是**两条**流水(`open` 一条、`resolved` 一条,见 `resolves`),
|
|
7322
|
-
* 两条之间没有任何关联时,展示层只能逐条渲染,于是**已经拍板完的待办永远挂在那儿**。
|
|
7323
|
-
*/
|
|
7152
|
+
/** Optional stable decision id for later resolution links; old entries remain readable without migration. */
|
|
7324
7153
|
id: nonBlank2.optional(),
|
|
7325
|
-
/**
|
|
7326
|
-
* 本条解决的是哪条决策(填对方的 `id`)。展示层据此把提问与答复合并成一行。
|
|
7327
|
-
*
|
|
7328
|
-
* **只关联、不改写**:不去改动已落盘那条 `open` 记录的 status。原地改写会丢掉
|
|
7329
|
-
* 「什么时候提出 / 什么时候解决」的双时间戳,也丢掉「Master 建议了什么 vs
|
|
7330
|
-
* 老板最终定了什么」的差异——而老板事后要看的恰恰是这个差异。合并只发生在展示层。
|
|
7331
|
-
*/
|
|
7154
|
+
/** Link the resolved entry to its original question without rewriting history, preserving both timestamps and proposed/final conclusions. */
|
|
7332
7155
|
resolves: nonBlank2.optional(),
|
|
7333
7156
|
type: TopicDecisionTypeSchema,
|
|
7334
7157
|
status: TopicDecisionStatusSchema,
|
|
@@ -7351,7 +7174,6 @@ var init_topic = __esm({
|
|
|
7351
7174
|
action: external_exports.string().min(1),
|
|
7352
7175
|
target: external_exports.string().optional(),
|
|
7353
7176
|
reason: external_exports.string().min(1),
|
|
7354
|
-
/** 该动作确认的产出物(watcher 返回的 artifacts);影响面视图的唯一数据源 */
|
|
7355
7177
|
artifacts: external_exports.array(nonBlank2).optional(),
|
|
7356
7178
|
/**
|
|
7357
7179
|
* Optional decision projection; legacy activity entries omit this field. `open` means the
|
|
@@ -7362,57 +7184,22 @@ var init_topic = __esm({
|
|
|
7362
7184
|
TopicLineMetaSchema = external_exports.object({
|
|
7363
7185
|
rejectCount: external_exports.number().int().min(0),
|
|
7364
7186
|
accepted: external_exports.boolean().default(false),
|
|
7365
|
-
/**
|
|
7366
|
-
* 该线在本话题内的**序号**,daemon 按建线先后顺序发,从 1 起。
|
|
7367
|
-
*
|
|
7368
|
-
* 存在的理由只有一个:老板说「第 2 条线」和 Master 说「线2」得指同一条。所以它必须
|
|
7369
|
-
* **稳定且不复用**——删掉第 2 条后新建的线拿 4 而不是 2,否则昨天说的号今天指向别的线,
|
|
7370
|
-
* 序号就没有价值了。发号点见 topic-store.claimLineOrdinal。
|
|
7371
|
-
*
|
|
7372
|
-
* 早先这个号是 UI 从 lineId 字符串里 parse 的(`line-<N>-<slug>`),等于把序号语义
|
|
7373
|
-
* 寄生在 Master 随手起的名字上:Master 用语义命名(`line-channel-fix`)就 parse 不出,
|
|
7374
|
-
* sidebar 整个不显示号。序号的产生端必须是 daemon。
|
|
7375
|
-
*
|
|
7376
|
-
* optional 而非 default:store 不按 schema parse(见 topic-store),存量 lineMeta
|
|
7377
|
-
* 里没有这个字段,读侧一律 `?? null`,回填由 `topic:get` 的发号 pass 幂等补上。
|
|
7378
|
-
*/
|
|
7187
|
+
/** Stable daemon-issued line number, never reused. Old metadata is filled by the handler; readers tolerate missing values before that pass. */
|
|
7379
7188
|
ordinal: external_exports.number().int().min(1).optional(),
|
|
7380
|
-
/**
|
|
7381
|
-
* Master 在 watcher 判 fail 的情况下仍判合格的次数。
|
|
7382
|
-
*
|
|
7383
|
-
* 为什么要单独数:watcher 的 fail 若能被一句「这是可观测性缺口」无声绕过,核验就形同
|
|
7384
|
-
* 虚设——实测一个话题三条线的 watcher 全报 fail、无一打回,Master 自己下场补证后判了合格。
|
|
7385
|
-
* 覆盖本身不禁止(watcher 确实会误判),但必须留痕、必须计数、反复覆盖必须惊动老板。
|
|
7386
|
-
*
|
|
7387
|
-
* optional 而非 default(0):store 不按 schema parse(见 topic-store),存量 lineMeta
|
|
7388
|
-
* 里本来就没有这个字段,读侧一律 `?? 0`。
|
|
7389
|
-
*/
|
|
7189
|
+
/** Count accepted watcher-failure overrides for audit/escalation. Legacy entries omit it and readers treat absence as zero. */
|
|
7390
7190
|
overrideCount: external_exports.number().int().min(0).optional()
|
|
7391
7191
|
});
|
|
7392
7192
|
TopicSchema = external_exports.object({
|
|
7393
7193
|
id: external_exports.string().min(1),
|
|
7394
7194
|
channelId: external_exports.string().min(1),
|
|
7395
7195
|
title: external_exports.string(),
|
|
7396
|
-
/** 该话题的 Master session;topic:create 时一并起,1:1 */
|
|
7397
7196
|
masterSessionId: external_exports.string().min(1),
|
|
7398
7197
|
goal: external_exports.string(),
|
|
7399
7198
|
boundaries: external_exports.string(),
|
|
7400
7199
|
acceptanceCriteria: external_exports.array(external_exports.string()),
|
|
7401
7200
|
activity: external_exports.array(TopicActivityEntrySchema),
|
|
7402
|
-
/** lineId -> 线级元数据 */
|
|
7403
7201
|
lineMeta: external_exports.record(TopicLineMetaSchema),
|
|
7404
|
-
/**
|
|
7405
|
-
* 线号水位:下一条线该拿的号。发一个加一,**只增不减**。
|
|
7406
|
-
*
|
|
7407
|
-
* 为什么是话题上的一个独立字段,而不是「现有 lineMeta 里的最大号 + 1」现算:现算的水位
|
|
7408
|
-
* 跟着现存线数走,删掉最大号那条线水位就退,下一条新线会**捡回那个号**。老板原话:
|
|
7409
|
-
* 「删了线,已经标记的线的序号不能变吧,要不然留档的记录不都坏了么」——号对不上只是难用,
|
|
7410
|
-
* 号能对上、却对到了另一条线(流水里「#3 判合格」后来指向一条新线)是把留档直接读坏。
|
|
7411
|
-
* 所以不变式有两条:**已发的号永不改**、**号永不复用**,后者必须与现存线数彻底脱钩。
|
|
7412
|
-
*
|
|
7413
|
-
* optional 而非 default:store 不按 schema parse(见 topic-store),存量话题没有这个
|
|
7414
|
-
* 字段,读盘时按「现有最大号 + 1」一次性归一(不能从 1 重开,否则现有的 #1 #2 会被复用)。
|
|
7415
|
-
*/
|
|
7202
|
+
/** Monotonic allocation watermark independent of live line count. Removing the highest line must never recycle its number; legacy load starts above existing ordinals. */
|
|
7416
7203
|
nextLineOrdinal: external_exports.number().int().min(1).optional(),
|
|
7417
7204
|
state: TopicStateSchema,
|
|
7418
7205
|
createdAt: external_exports.string().min(1),
|
|
@@ -7427,7 +7214,6 @@ var init_topic = __esm({
|
|
|
7427
7214
|
status: DispatchStatusSchema,
|
|
7428
7215
|
acceptance: external_exports.array(external_exports.string()),
|
|
7429
7216
|
rejectCount: external_exports.number().int().min(0),
|
|
7430
|
-
/** 已被 Master 判定合格(`topic:accept`)。UI 的「合格」态靠它,不是猜的 */
|
|
7431
7217
|
accepted: external_exports.boolean(),
|
|
7432
7218
|
/**
|
|
7433
7219
|
* 线号(见 TopicLineMetaSchema.ordinal)。UI 直接渲染,**不再从 lineId 猜**。
|
|
@@ -7446,7 +7232,6 @@ var init_topic = __esm({
|
|
|
7446
7232
|
});
|
|
7447
7233
|
TopicCreateArgsSchema = external_exports.object({
|
|
7448
7234
|
channelId: external_exports.string().min(1),
|
|
7449
|
-
/** 可后补:spike 动线是老板先说一句话,标题随 topic:setPlan 落 */
|
|
7450
7235
|
title: nonBlank2.optional()
|
|
7451
7236
|
});
|
|
7452
7237
|
TopicSetPlanArgsSchema = external_exports.object({
|
|
@@ -7463,7 +7248,6 @@ var init_topic = __esm({
|
|
|
7463
7248
|
action: nonBlank2,
|
|
7464
7249
|
target: nonBlank2.optional(),
|
|
7465
7250
|
reason: nonBlank2,
|
|
7466
|
-
/** 该动作确认的产出物;影响面视图只吃这个字段 */
|
|
7467
7251
|
artifacts: external_exports.array(nonBlank2).optional(),
|
|
7468
7252
|
decision: TopicActivityEntrySchema.shape.decision
|
|
7469
7253
|
});
|
|
@@ -7475,9 +7259,7 @@ var init_topic = __esm({
|
|
|
7475
7259
|
TopicAcceptArgsSchema = external_exports.object({
|
|
7476
7260
|
topicId: external_exports.string().min(1),
|
|
7477
7261
|
lineId: nonBlank2,
|
|
7478
|
-
/** 凭什么判合格(逐条标准对应到什么证据)——与打回同样必填 */
|
|
7479
7262
|
reason: nonBlank2,
|
|
7480
|
-
/** watcher 认定的产出物,一并进影响面 */
|
|
7481
7263
|
artifacts: external_exports.array(nonBlank2).optional(),
|
|
7482
7264
|
/**
|
|
7483
7265
|
* 这条线的 watcher 核验结论。**必填**:不填等于允许「压根没跑 watcher 就发合格证」,
|
|
@@ -7515,15 +7297,12 @@ var init_topic = __esm({
|
|
|
7515
7297
|
});
|
|
7516
7298
|
TopicRejectResultSchema = external_exports.object({
|
|
7517
7299
|
rejectCount: external_exports.number().int().min(1),
|
|
7518
|
-
/** 连续两次打回仍不合格 → 必须上报老板(PRD §10.3),不靠 Master 自己数 */
|
|
7519
7300
|
needsOwnerAttention: external_exports.boolean()
|
|
7520
7301
|
});
|
|
7521
7302
|
TopicAcceptResultSchema = external_exports.object({
|
|
7522
7303
|
acceptedLines: external_exports.number().int().min(1),
|
|
7523
7304
|
totalLines: external_exports.number().int().min(1),
|
|
7524
|
-
/** 全部线都合格了 → 可以收敛上报。由 daemon 数,不让 Master 自己盘 */
|
|
7525
7305
|
allAccepted: external_exports.boolean(),
|
|
7526
|
-
/** 本话题累计推翻 watcher 的次数(含这一次) */
|
|
7527
7306
|
overrides: external_exports.number().int().min(0),
|
|
7528
7307
|
/**
|
|
7529
7308
|
* 反复推翻 watcher = 取证链路有系统性问题(如 worker 用 heredoc 写文件导致正文
|
|
@@ -7620,7 +7399,6 @@ var init_lark_bot_schemas = __esm({
|
|
|
7620
7399
|
LarkBotGroupSchema = external_exports.object({
|
|
7621
7400
|
chatId: external_exports.string().min(1),
|
|
7622
7401
|
chatName: external_exports.string().optional(),
|
|
7623
|
-
/** epoch ms,该群 session 最近一次活跃时间 */
|
|
7624
7402
|
lastActiveAt: external_exports.number().int().optional()
|
|
7625
7403
|
});
|
|
7626
7404
|
LarkBotStatusResultSchema = external_exports.object({
|
|
@@ -7635,19 +7413,9 @@ var init_lark_bot_schemas = __esm({
|
|
|
7635
7413
|
/** provision 失败后的 unbound 帧携带;正常 unbound 无此字段 */
|
|
7636
7414
|
errorReason: LarkBotProvisionErrorReasonSchema.optional(),
|
|
7637
7415
|
groups: external_exports.array(LarkBotGroupSchema),
|
|
7638
|
-
/**
|
|
7639
|
-
* bound 态:该 bot 实际授予的 scope 缺了 LARK_PROVISION_ADDONS 里声明的项
|
|
7640
|
-
* (spec 2026-09-01)——addons 是扫码时一次性写进飞书应用配置的,后来新增的
|
|
7641
|
-
* scope / 事件对存量 bot 不生效,不提示的话用户只会觉得「功能没生效」。
|
|
7642
|
-
* 判定真源是飞书回读的 granted scopes,不落库任何版本号。
|
|
7643
|
-
*/
|
|
7416
|
+
/** Granted scopes, not a stored version, determine whether an existing bot needs an authorization upgrade. */
|
|
7644
7417
|
needsUpgrade: external_exports.boolean().optional(),
|
|
7645
|
-
/**
|
|
7646
|
-
* bound 态叠加:正在扫码更新授权(spec 2026-09-01)。
|
|
7647
|
-
* **不复用 provisioning 态**——升级期间 bot 仍在正常服务(长连接不断、群照常响应),
|
|
7648
|
-
* 降级成 provisioning 会丢掉 botName / groups,UI 上像是断了。
|
|
7649
|
-
* 有值即渲染二维码弹层;升级结束(成功或取消)该字段消失。
|
|
7650
|
-
*/
|
|
7418
|
+
/** Upgrade QR overlays bound status so a still-serving bot retains its name/groups. Removed after upgrade success or cancellation. */
|
|
7651
7419
|
upgradeQrUrl: external_exports.string().optional()
|
|
7652
7420
|
});
|
|
7653
7421
|
LarkBotStateFrameSchema = LarkBotStatusResultSchema.extend({
|
|
@@ -7720,15 +7488,11 @@ var init_engine_schemas = __esm({
|
|
|
7720
7488
|
external_exports.object({
|
|
7721
7489
|
kind: external_exports.literal("claude"),
|
|
7722
7490
|
loggedIn: external_exports.boolean(),
|
|
7723
|
-
/** claude.ai / console / … */
|
|
7724
7491
|
authMethod: external_exports.string().optional(),
|
|
7725
|
-
/** firstParty / bedrock / vertex / foundry */
|
|
7726
7492
|
apiProvider: external_exports.string().optional(),
|
|
7727
|
-
/** ANTHROPIC_API_KEY / apiKeyHelper */
|
|
7728
7493
|
apiKeySource: external_exports.string().optional(),
|
|
7729
7494
|
email: external_exports.string().optional(),
|
|
7730
7495
|
orgName: external_exports.string().optional(),
|
|
7731
|
-
/** max / pro / team / … */
|
|
7732
7496
|
subscriptionType: external_exports.string().optional()
|
|
7733
7497
|
}).strict(),
|
|
7734
7498
|
// app-server `account/read`:account 为 null → none;`config/read`:model_provider + 自定义 provider 的 base_url
|
|
@@ -7795,7 +7559,6 @@ var init_dashboard_schemas = __esm({
|
|
|
7795
7559
|
DASHBOARD_LINK_TTL_SECONDS = 24 * 3600;
|
|
7796
7560
|
DashboardOpenUrlArgs = external_exports.object({ page: DashboardPageSchema }).strict();
|
|
7797
7561
|
DashboardOpenUrlResultSchema = external_exports.object({
|
|
7798
|
-
/** `https://<tunnel 地址>/dashboard/<page>?e=&s=` */
|
|
7799
7562
|
url: external_exports.string(),
|
|
7800
7563
|
/** 过期时刻(ms) */
|
|
7801
7564
|
expiresAt: external_exports.number().int()
|
|
@@ -7823,44 +7586,23 @@ var init_persona_schemas = __esm({
|
|
|
7823
7586
|
// 8-key set defined UI-side in `lib/session-icons.ts`; protocol stays plain string
|
|
7824
7587
|
// for forward compatibility, consumer fallbacks to default when key not found.
|
|
7825
7588
|
iconKey: external_exports.string().optional(),
|
|
7826
|
-
//
|
|
7827
|
-
// 与 model 平行:persona 新建 session 的默认 tool,可平滑切换。
|
|
7589
|
+
// Default engine binding for new sessions; missing values are consumed as claude.
|
|
7828
7590
|
tool: external_exports.string().optional(),
|
|
7829
|
-
/**
|
|
7830
|
-
* 云上任务 Pod 的规格档(design 2026-09-18-persona-size):`tiny` / `small` / `medium` / `large`,没写按 medium。
|
|
7831
|
-
* 只有发布上云的流水线读它(cloud-host/pipeline/deploy-file.mjs 收窄值域,不认识的拒发);本机跑不看。
|
|
7832
|
-
* 协议层同 model / effort 保持 string(文件层向前兼容);改它的入口是上云页标题行的规格下拉
|
|
7833
|
-
* (design 2026-09-20-persona-size-ui),`persona:update` 那头按 PersonaSizeSchema 收窄。
|
|
7834
|
-
*/
|
|
7591
|
+
/** Cloud resource size is string-compatible in stored files and narrowed at update/deploy boundaries; local execution ignores it. */
|
|
7835
7592
|
size: external_exports.string().optional(),
|
|
7836
|
-
/**
|
|
7837
|
-
* 云上 task 运行模式下一个 task Pod 同时跑几个 task(design 2026-09-19-cloud-task-mode §3.6),没写按 4。
|
|
7838
|
-
* 只有发布上云的流水线读它(cloud-host/pipeline/deploy-file.mjs 校验正整数、不合格拒发);本机跑不看。
|
|
7839
|
-
* 发布页的资源预估卡通过 persona:update 保存,下次发布生效。
|
|
7840
|
-
*/
|
|
7593
|
+
/** Cloud task-Pod concurrency, validated at release. Local execution ignores it; changes apply on the next publication. */
|
|
7841
7594
|
taskConcurrency: external_exports.number().int().positive().optional(),
|
|
7842
7595
|
createdAt: external_exports.number(),
|
|
7843
7596
|
updatedAt: external_exports.number(),
|
|
7844
|
-
/**
|
|
7845
|
-
* 云上 persona(design 2026-09-09-cloud-persona-in-clawd):这个 persona 跑在哪台 gateway 上、在那台 gateway 上叫什么。
|
|
7846
|
-
* 有这个字段 = 条目来自 gateway 的登记表(daemon 每次 persona:list 拉 /healthz 合进 registry),
|
|
7847
|
-
* 本机没有它的目录;会话的 cwd 是 `<gatewayUrl>/p/<cloud.personaId>`,tool 是 `codex-cloud`。
|
|
7848
|
-
* 没有 = 本机 persona,一切照旧。UI 按它分组、隐藏设置 / 上云 / 删除入口。
|
|
7849
|
-
*
|
|
7850
|
-
* 顶层 `personaId` 是本机列表里的键,固定为 `<cloud.personaId>-cloud`(design 2026-09-11-cloud-persona-slug):
|
|
7851
|
-
* 上云的就是本机同名 persona,不加后缀发布者自己永远看不到「云上」组。gateway 不认识带后缀的名字,
|
|
7852
|
-
* 跟它说话(路径段、登记表)一律用 `cloud.personaId`。
|
|
7853
|
-
*/
|
|
7597
|
+
/** Registry-only cloud entry, with no local persona directory. Local key adds -cloud; gateway paths use cloud.personaId and the registered engine determines the cloud tool id. */
|
|
7854
7598
|
cloud: external_exports.object({ gatewayUrl: external_exports.string().min(1), personaId: external_exports.string().min(1), publishedByMe: external_exports.boolean().optional() }).strict().optional()
|
|
7855
7599
|
}).strict();
|
|
7856
7600
|
PersonaSkillSummarySchema = external_exports.object({
|
|
7857
7601
|
// 目录名作为 skill name(对齐 CC:frontmatter `name:` 仅作 displayName 用)
|
|
7858
7602
|
name: external_exports.string().min(1),
|
|
7859
|
-
// SKILL.md frontmatter `description:`;缺省时省略字段
|
|
7860
7603
|
description: external_exports.string().optional()
|
|
7861
7604
|
});
|
|
7862
7605
|
PersonaPluginSummarySchema = external_exports.object({
|
|
7863
|
-
// settings.json 中 enabledPlugins 的 key,例如 'superpowers@claude-plugins-official'
|
|
7864
7606
|
id: external_exports.string().min(1)
|
|
7865
7607
|
});
|
|
7866
7608
|
CodexSandboxSettingsSchema = external_exports.object({
|
|
@@ -7876,7 +7618,6 @@ var init_persona_schemas = __esm({
|
|
|
7876
7618
|
PersonaSandboxSettingsSchema = external_exports.object({
|
|
7877
7619
|
permissions: external_exports.object({
|
|
7878
7620
|
defaultMode: external_exports.string().optional(),
|
|
7879
|
-
// CC 工具 allowlist('Bash'/'Read'/...)。per-persona profile 在最严格 base 上追加放开。
|
|
7880
7621
|
allow: external_exports.array(external_exports.string()).optional(),
|
|
7881
7622
|
// deny 优先级最高;强制底座(persona 私密区)由 daemon compose 强制 union,owner 删不掉。
|
|
7882
7623
|
deny: external_exports.array(external_exports.string()).optional()
|
|
@@ -7894,7 +7635,6 @@ var init_persona_schemas = __esm({
|
|
|
7894
7635
|
denyWrite: external_exports.array(external_exports.string()).optional(),
|
|
7895
7636
|
allowWrite: external_exports.array(external_exports.string()).optional()
|
|
7896
7637
|
}).optional(),
|
|
7897
|
-
// 出站网络 + 本机端口。base 不放(空),profile 按需追加域名(app-builder = ['*'])。
|
|
7898
7638
|
network: external_exports.object({
|
|
7899
7639
|
allowedDomains: external_exports.array(external_exports.string()).optional(),
|
|
7900
7640
|
allowLocalBinding: external_exports.boolean().optional()
|
|
@@ -7905,17 +7645,9 @@ var init_persona_schemas = __esm({
|
|
|
7905
7645
|
// 人格 builtin 层:`<personaDir>/CLAUDE.md`(= AGENTS.md 镜像)。预装 persona 的这份是
|
|
7906
7646
|
// daemon ship 的产品规格,每次 daemon 启动被覆盖回 bundle 版(personalityManaged 标记它)。
|
|
7907
7647
|
personality: external_exports.string().optional(),
|
|
7908
|
-
//
|
|
7909
|
-
// persona 的配置,只是所有权不同**:builtin 归 daemon、随版本更新覆盖;这一层归 persona 的
|
|
7910
|
-
// 主人,不在 DAEMON_MANAGED_PATHS 里,OTA / daemon 重启都不碰。两层叠加生效(人格上冲突时
|
|
7911
|
-
// 以补充层为准),经 SpawnContext.extraSystemPrompt 注入,claude / codex 两端同源,
|
|
7912
|
-
// **owner / guest 会话一律注入**(它是 persona 属性不是私人偏好,同 builtin 人格的待遇)。
|
|
7913
|
-
// 仅 'persona:get' 携带;'' = 没写补充(与 personality 同口径,不用 null)。
|
|
7648
|
+
// Owner-managed personality overlay survives daemon resource refreshes and is injected for owner and guest sessions. Empty text clears it.
|
|
7914
7649
|
personalityLocal: external_exports.string().optional(),
|
|
7915
|
-
// builtin
|
|
7916
|
-
// 下次启动覆盖 → UI 渲染成只读,编辑落到 personalityLocal;false = 用户自建 persona,
|
|
7917
|
-
// 它的 CLAUDE.md daemon 不碰,照旧直接编辑。派生自 daemon 的预装名单,**只读不可回写**。
|
|
7918
|
-
// 仅 'persona:get' 携带。
|
|
7650
|
+
// Managed builtin personality is refreshed by daemon and shown read-only; user edits belong to the overlay. User-created persona files remain editable.
|
|
7919
7651
|
personalityManaged: external_exports.boolean().optional(),
|
|
7920
7652
|
// 仅 'persona:get' 携带;null = 文件不存在 / parse 失败;undefined = 不在该响应类型上
|
|
7921
7653
|
skills: external_exports.array(PersonaSkillSummarySchema).optional(),
|
|
@@ -7927,10 +7659,7 @@ var init_persona_schemas = __esm({
|
|
|
7927
7659
|
codexSandbox: CodexSandboxSettingsSchema.nullable().optional()
|
|
7928
7660
|
});
|
|
7929
7661
|
PersonaCreateArgsSchema = external_exports.object({
|
|
7930
|
-
//
|
|
7931
|
-
// 「所有 persona id 带该前缀」的约定由构造保证。与 label(显示名,可中文、可随时改)是两个概念:
|
|
7932
|
-
// 曾经只有 label、id 从 label 推,导致中文 label 被剥空后一律降级成 persona-persona。
|
|
7933
|
-
// 正则即安全边界——该值直接落进 ~/.clawd/personas/<id>/,此处排除 . / 空格后路径穿越无从构造。
|
|
7662
|
+
// Caller provides a safe slug, daemon adds persona-. Keep identity separate from the freely editable display label.
|
|
7934
7663
|
slug: external_exports.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/).max(32),
|
|
7935
7664
|
label: external_exports.string().min(1),
|
|
7936
7665
|
personality: external_exports.string(),
|
|
@@ -7950,7 +7679,6 @@ var init_persona_schemas = __esm({
|
|
|
7950
7679
|
model: external_exports.string().optional(),
|
|
7951
7680
|
effort: external_exports.string().optional(),
|
|
7952
7681
|
tool: external_exports.string().optional(),
|
|
7953
|
-
// 云上任务 Pod 的规格档:只收四档之一(PersonaFile 层是 string,边界在这里收窄)
|
|
7954
7682
|
size: PersonaSizeSchema.optional(),
|
|
7955
7683
|
taskConcurrency: PersonaFileSchema.shape.taskConcurrency,
|
|
7956
7684
|
personality: external_exports.string().optional(),
|
|
@@ -8048,15 +7776,10 @@ var init_principal = __esm({
|
|
|
8048
7776
|
id: external_exports.string().min(1),
|
|
8049
7777
|
kind: PrincipalKindSchema,
|
|
8050
7778
|
displayName: external_exports.string(),
|
|
8051
|
-
/**
|
|
8052
|
-
* 飞书 union_id。owner: 本机 daemon 已飞书登录时透传自 owner-identity.json;未登录 undefined。
|
|
8053
|
-
* guest: v1 保持 undefined(connect token 当前不携带 unionId;未来若签名 payload 加字段可透传)。
|
|
8054
|
-
* 消费方(如 ticket MCP / 未来别的飞书身份挂钩场景)按 undefined 决定是否降级;本层不做 fallback。
|
|
8055
|
-
*/
|
|
7779
|
+
/** Optional identity attribution: owner login supplies it; connect guests receive token subOwner. Consumers decide how to handle absence. */
|
|
8056
7780
|
feishuUnionId: external_exports.string().optional()
|
|
8057
7781
|
}).strict();
|
|
8058
7782
|
SenderInfoSchema = external_exports.object({
|
|
8059
|
-
/** = principal.id = deviceId(决策 #16)。稳定路由 id */
|
|
8060
7783
|
did: external_exports.string().min(1),
|
|
8061
7784
|
displayName: external_exports.string(),
|
|
8062
7785
|
/** owner 未登录 / guest 无飞书身份时缺省。消费方各自决定是否降级 */
|
|
@@ -8086,16 +7809,11 @@ var init_schemas = __esm({
|
|
|
8086
7809
|
sessionMetaShape = {
|
|
8087
7810
|
gitBranch: external_exports.string().optional(),
|
|
8088
7811
|
resolvedModel: external_exports.string().optional(),
|
|
8089
|
-
//
|
|
8090
|
-
// 与 resolvedModel 对称:session.effort 是「用户选的」('' = 没选),这个是「实际用的」。
|
|
8091
|
-
// 没选时 UI 拿它显示确切值,而不是「由 CC 决定」这种占位。
|
|
7812
|
+
// Effective reasoning effort from the engine, distinct from the user-selected effort.
|
|
8092
7813
|
resolvedEffort: external_exports.string().optional(),
|
|
8093
7814
|
contextUsage: ContextUsageSchema.optional(),
|
|
8094
7815
|
contextWindowSize: external_exports.number().int().positive().optional(),
|
|
8095
|
-
//
|
|
8096
|
-
// 与用户手改的 label 分离:用户 Rename 只写 label,AI 只写 aiLabel,显示优先级
|
|
8097
|
-
// label > aiLabel——结构上杜绝「自动标题覆盖用户改名」。创建时 UI 把首行截断
|
|
8098
|
-
// 占位写进 SessionCreateArgs.aiLabel,之后由 meta_update 用真标题覆盖。
|
|
7816
|
+
// User label overrides aiLabel. Automatic title updates must never overwrite the user's label.
|
|
8099
7817
|
aiLabel: external_exports.string().optional()
|
|
8100
7818
|
};
|
|
8101
7819
|
SessionMetaSchema = external_exports.object(sessionMetaShape);
|
|
@@ -8110,15 +7828,10 @@ var init_schemas = __esm({
|
|
|
8110
7828
|
description: external_exports.string().optional(),
|
|
8111
7829
|
contextWindowSize: external_exports.number().int().positive(),
|
|
8112
7830
|
default: external_exports.boolean().optional(),
|
|
8113
|
-
//
|
|
8114
|
-
// 缺省 = 该 tool 的 effort 与模型无关(claude 即此情形),消费方回落到 configSchema 里
|
|
8115
|
-
// name==='effort' 那条字段的 options。
|
|
7831
|
+
// Per-model efforts override the global effort options; absence uses the tool's global config field.
|
|
8116
7832
|
efforts: external_exports.array(SelectOptionSchema).optional(),
|
|
8117
7833
|
defaultEffort: external_exports.string().optional(),
|
|
8118
|
-
//
|
|
8119
|
-
// 缺省 = 该 tool 的模型不分组(claude 即此情形),消费方平铺渲染、不出标题。
|
|
8120
|
-
// 全表只落在一个家族时 daemon 也不填——一个横跨全表的标题没有信息量。
|
|
8121
|
-
// 消费方假定同 group 的条目已连续排好(排序在 daemon 侧,见 codex-model-list.mergeProviderModels)。
|
|
7834
|
+
// Optional display grouping; same-group models must already be contiguous. A single whole-catalog group adds no information.
|
|
8122
7835
|
group: external_exports.string().optional()
|
|
8123
7836
|
});
|
|
8124
7837
|
ModeInfoSchema = external_exports.object({
|
|
@@ -8180,24 +7893,17 @@ var init_schemas = __esm({
|
|
|
8180
7893
|
cloudPendingCommand: external_exports.object({ id: external_exports.string().min(1), text: external_exports.string(), prepared: external_exports.boolean().optional() }).optional(),
|
|
8181
7894
|
cwd: external_exports.string().min(1),
|
|
8182
7895
|
tool: external_exports.literal("claude").or(external_exports.string().min(1)).default("claude"),
|
|
8183
|
-
// 归一化 tool 侧 session id(CC = claude session uuid,Codex = codex 自己的 id)
|
|
8184
7896
|
toolSessionId: external_exports.string().optional(),
|
|
8185
7897
|
label: external_exports.string().optional(),
|
|
8186
7898
|
model: external_exports.string().optional(),
|
|
8187
7899
|
permissionMode: external_exports.string().optional(),
|
|
8188
7900
|
effort: external_exports.string().optional(),
|
|
8189
|
-
// 归一化运行时观测字段(daemon 独立计算,session:update 不接受;meta_update 事件投递)
|
|
8190
7901
|
...sessionMetaShape,
|
|
8191
|
-
//
|
|
8192
|
-
// (observer ai-title 回灌与 daemon 自产两个生产者同路)。为空 = 还没生成过 →
|
|
8193
|
-
// manager.send 首条消息触发生成。session:update(外部 RPC)zod strip 不接受此字段。
|
|
7902
|
+
// Daemon-owned title generation marker; external session:update cannot set it. Missing marker allows first-message generation.
|
|
8194
7903
|
aiLabelGeneratedAt: external_exports.string().optional(),
|
|
8195
7904
|
permissionRules: external_exports.array(AllowRuleSchema).optional(),
|
|
8196
|
-
// Sidebar 置顶时间戳;null/undefined 表示未置顶
|
|
8197
7905
|
pinnedAt: external_exports.number().int().nonnegative().nullable().optional(),
|
|
8198
|
-
//
|
|
8199
|
-
// marker 语义:不 bump updatedAt(与 pinnedAt 同款)。session:archive 归档时 daemon
|
|
8200
|
-
// 在同一次落盘里原子清 pinnedAt;取消归档不恢复置顶。
|
|
7906
|
+
// Archive does not bump updatedAt; archiving clears pin atomically, while unarchiving does not restore it.
|
|
8201
7907
|
archivedAt: external_exports.number().int().nonnegative().nullable().optional(),
|
|
8202
7908
|
// pinned 行手动拖拽顺序;越小越靠前。session:reorderPins 整体重写所有 pinned 的此字段。
|
|
8203
7909
|
// 缺失时 view-state 排序回退到 pinnedAt desc(旧数据兼容)
|
|
@@ -8207,22 +7913,13 @@ var init_schemas = __esm({
|
|
|
8207
7913
|
// session:markRead RPC 或新 turn 开始(→ running)时清为 null。
|
|
8208
7914
|
// 所有客户端(desktop / mobile)共享同一份,任一端标读全端清。
|
|
8209
7915
|
unreadAt: external_exports.string().min(1).nullable().optional(),
|
|
8210
|
-
// 用户在 NewSessionDialog 或 Edit modal 选择的 icon 标识;UI 端做 enum 兜底
|
|
8211
7916
|
iconKey: external_exports.string().optional(),
|
|
8212
|
-
//
|
|
8213
|
-
// 不受 parent clear / re-spawn 影响);sidebar 据此挂 fork 图标。非 fork session 该字段缺省。
|
|
8214
|
-
// 旧字段 forkedFromToolSessionId 已弃用,daemon 启动时一次性迁移老数据
|
|
7917
|
+
// Stable source clawd session id for a fork, independent of clearing/restarting the source engine session.
|
|
8215
7918
|
forkedFromSessionId: external_exports.string().min(1).optional(),
|
|
8216
7919
|
// 快速问答临时会话标记(spec 2026-06-23-fork-quick-ask):true = 关闭抽屉即删、
|
|
8217
7920
|
// 不进 sidebar 树、daemon 启动清扫孤儿。普通会话缺省。
|
|
8218
7921
|
ephemeral: external_exports.boolean().optional(),
|
|
8219
|
-
/**
|
|
8220
|
-
* 这条会话属于哪张部署单(persona 上云的环境扫描会话)。
|
|
8221
|
-
*
|
|
8222
|
-
* 有值 = 它在单据页右栏里活着,**sidebar 不再列它**——同一条会话两处出现,和话题 worker
|
|
8223
|
-
* 会话不进「最近」是同一条规矩。不复用 `ephemeral`:那个标记会被 daemon 启动清扫**删掉**,
|
|
8224
|
-
* 而扫描会话要跟单据一样长期留着(人隔天回来还要翻管家当时是怎么判的、还要接着追问)。
|
|
8225
|
-
*/
|
|
7922
|
+
/** Scan-session grouping, retained with deployment history. Do not reuse ephemeral: startup cleanup would delete evidence needed later. */
|
|
8226
7923
|
deployId: external_exports.string().min(1).optional(),
|
|
8227
7924
|
// owner-mode persona session 身份标识;UI 用它在 SessionList 过滤 + jump,
|
|
8228
7925
|
// daemon 用它做 idempotent dedupe + spawn 时派生 ctx.personaMode='owner'
|
|
@@ -8233,103 +7930,25 @@ var init_schemas = __esm({
|
|
|
8233
7930
|
* 本机资源指针——mirror 上与 ownerPersonaId 同地点 strip(#750 防线)。
|
|
8234
7931
|
*/
|
|
8235
7932
|
projectPath: external_exports.string().min(1).optional(),
|
|
8236
|
-
//
|
|
8237
|
-
// 派生 sessionId 是单向 hash `${personaId}-${tokenHash12}-${chatHash8}`,必须保留原始 chatId
|
|
8238
|
-
// 才能让 alice 重连同一 sub-session(持久化在 alice localStorage 之外,由 daemon push 同步)。
|
|
8239
|
-
// optional 是因为 owner-mode session 不带;只要写入就是 listener-mode 必填三元组。
|
|
7933
|
+
// Legacy chat identifier retained in stored session shape; current bot routing uses larkChatId.
|
|
8240
7934
|
chatId: external_exports.string().min(1).optional(),
|
|
8241
|
-
/**
|
|
8242
|
-
* 创建该 session 的设备 id(决策 #16:从 ctx.principal.id 派生 = deviceId)。
|
|
8243
|
-
* UI 端 self 判定收敛为单一比较 `!creatorPrincipalId || creatorPrincipalId === 本机 deviceId`
|
|
8244
|
-
* → 「我自己创建」vs「别的设备接入创建」。MyPersonas 二级分组按 creatorPrincipalId(设备)
|
|
8245
|
-
* 分桶、按 contact(deviceId→ownerName) 反查显示名。
|
|
8246
|
-
*
|
|
8247
|
-
* 命名保留 creatorPrincipalId(命名债,语义已是 creatorDeviceId);值由 connect token
|
|
8248
|
-
* subDevice 背书(guest ctx)或本机 deviceId(owner ctx)。optional 兼容老 session。
|
|
8249
|
-
*
|
|
8250
|
-
* 决策 #16 删除了 creatorOwnerPrincipalId(personal token #735 遗留)——当年 guest 的
|
|
8251
|
-
* principal.id 都是同张 cap.id 区分不了人才加它;现在 principal.id = 设备 deviceId、
|
|
8252
|
-
* 归属人由 contact(deviceId→ownerId) 反查,该字段理由消失。
|
|
8253
|
-
*/
|
|
7935
|
+
/** Creator routing identity, stamped at creation. Missing on old records means local-self fallback. Connect guests use signed device identity; this is distinct from human attribution. */
|
|
8254
7936
|
creatorPrincipalId: external_exports.string().min(1).optional(),
|
|
8255
|
-
/**
|
|
8256
|
-
* 创建该 session 的连接者飞书 displayName(一次性 stamp)。session:create handler
|
|
8257
|
-
* 从 ctx.principal.displayName 传入;newRunner 时读这个字段派生 SubSessionMeta.callerDisplayName,
|
|
8258
|
-
* 注入到 cc system prompt 引导自我介绍(见 connection-prompt.ts)。
|
|
8259
|
-
* Optional 兼容老 SessionFile——newRunner 读不到时 fallback ContactStore 反查(guest)
|
|
8260
|
-
* 或 deps.ownerDisplayName(owner)。
|
|
8261
|
-
*/
|
|
7937
|
+
/** Creator display-name snapshot for the connection prompt; missing on old records falls back to current owner/contact attribution. */
|
|
8262
7938
|
creatorDisplayName: external_exports.string().min(1).optional(),
|
|
8263
|
-
/**
|
|
8264
|
-
* 创建该 session 的连接者飞书 union_id(一次性 stamp,T-31 persona prompt injection)。
|
|
8265
|
-
* session:create / dispatch spawnB handler 从 ctx.principal.feishuUnionId(guest 场景来自
|
|
8266
|
-
* connect token subOwner)或 deps.ownerFeishuUnionId(owner 场景)传入;newRunner 时读这个
|
|
8267
|
-
* 字段派生 SubSessionMeta.callerFeishuUnionId 注入 cc system prompt「身份卡」的 union_id 行。
|
|
8268
|
-
* Optional:老 SessionFile / owner 未登飞书路径缺省,缺省时 prompt 不渲染该行(向后兼容)。
|
|
8269
|
-
*/
|
|
7939
|
+
/** Creator identity attribution stamped for the connection prompt. Missing on older/unattributed sessions omits that prompt row. */
|
|
8270
7940
|
creatorFeishuUnionId: external_exports.string().min(1).optional(),
|
|
8271
|
-
/**
|
|
8272
|
-
* Mirror peer sessions (2026-05-26): mirror 标识。**我在朋友 daemon 上创建的 session**,
|
|
8273
|
-
* 远端是 source of truth,本机是持久化副本,方便 sidebar 跨设备聚合。
|
|
8274
|
-
* 字段值 = 远端 daemon 的 deviceId(决策 #16:朋友设备标识,即我接入的那台机器)。
|
|
8275
|
-
*
|
|
8276
|
-
* 语义关键澄清:mirror **不是朋友创建给我的会话**——它是我自己的会话,只是在朋友
|
|
8277
|
-
* 的机器上跑。sidebar chip 显示 `@<朋友>` 暗示「位置」,不是「来源」。
|
|
8278
|
-
*
|
|
8279
|
-
* - undefined / 缺省 → 本机 native session(在我自己机器上跑)
|
|
8280
|
-
* - 非空 → 我的会话在该 deviceId 朋友的 daemon 上跑,本机为持久化副本
|
|
8281
|
-
*
|
|
8282
|
-
* 四类 session 取值矩阵(决策 #16 deviceId 版,creatorOwnerPrincipalId 列已删):
|
|
8283
|
-
* | 形态 | creatorPrincipalId(=设备) | originOwnerPrincipalId(=朋友设备) |
|
|
8284
|
-
* |-------------------------------------|--------------------------|----------------------------------|
|
|
8285
|
-
* | 本机 owner 自创 | 本机 deviceId | undefined |
|
|
8286
|
-
* | 本机被别的设备接入创建 | 接入者 deviceId(token 背书) | undefined |
|
|
8287
|
-
* | 我在朋友 daemon 上创建(mirror 回本机) | 我的 deviceId | 朋友 deviceId |
|
|
8288
|
-
*
|
|
8289
|
-
* UI 用法:
|
|
8290
|
-
* - sidebar Active/Pinned/Recents 过滤:`origin 非空 → 一律视作 self`(绕过 #739 隔离)
|
|
8291
|
-
* - 行内显示 `@<朋友>` chip 暗示位置
|
|
8292
|
-
* - 进 ChatPanel 时按 origin 路由到 `pool.ensureRemoteForOwner(origin)` 走远端(按 deviceId)
|
|
8293
|
-
*
|
|
8294
|
-
* daemon 用法:
|
|
8295
|
-
* - `peerSession:upsert` / `peerSession:remove` 一族 RPC 专管 mirror 写删;
|
|
8296
|
-
* daemon dumb write/delete,**不广播任何 push 帧**,UI 是写入唯一源
|
|
8297
|
-
* - native `session:*` handler 完全不感知 mirror(UI 操作 mirror 不走 native 路径)
|
|
8298
|
-
*/
|
|
7941
|
+
/** Remote device hosting the caller's mirrored session. The remote daemon is authoritative; this record is local sidebar storage. UI routes operations remotely and treats mirror as self; mirror upsert/remove do not broadcast native-session frames. */
|
|
8299
7942
|
originOwnerPrincipalId: external_exports.string().min(1).optional(),
|
|
8300
|
-
/**
|
|
8301
|
-
* Mirror 的 persona 身份(2026-07-17 android-dispatch-task spec §3.2):
|
|
8302
|
-
* 该 mirror 在朋友 daemon 上是用哪个 persona 创建的(朋友侧的 persona id)。
|
|
8303
|
-
* 与 originOwnerPrincipalId 平行的「位置」语义——**不参与**本机 persona 反查
|
|
8304
|
-
* (那是 ownerPersonaId 的职责,mirror 上按 #750 防线 strip)。
|
|
8305
|
-
* 消费方:mobile picker「借的」组按 (originOwnerPrincipalId, originOwnerPersonaId) 去重。
|
|
8306
|
-
* 写入方:ui toMirrorSessionFile / mobile buildMirrorUpsertArgs 在 strip ownerPersonaId 时 stamp。
|
|
8307
|
-
*/
|
|
7943
|
+
/** Persona id at the remote hosting device. Used with originOwnerPrincipalId for remote picker grouping, never for local persona lookup. */
|
|
8308
7944
|
originOwnerPersonaId: external_exports.string().min(1).optional(),
|
|
8309
|
-
/**
|
|
8310
|
-
* Dispatch 派生的 session 在 create 时记录源 session 的 sessionId(稳定 key);
|
|
8311
|
-
* sidebar 据此把 dispatch 出来的 sub-session 折叠在源 session 下。非 dispatch session 缺省。
|
|
8312
|
-
* mirror SessionFile 上必须 strip(朋友 daemon 上的 sessionId 在本机无意义,会让本机
|
|
8313
|
-
* 反查路径走错;按 #750 一族防线处理,跟 ownerPersonaId 同地点 strip)。
|
|
8314
|
-
*/
|
|
7945
|
+
/** Dispatch source session for local tree grouping. Strip on mirrors because remote session ids cannot link into the local tree. */
|
|
8315
7946
|
dispatchedFromSessionId: external_exports.string().min(1).optional(),
|
|
8316
|
-
/**
|
|
8317
|
-
* Shift fire 派生的 session 记录调 addShift 的 source cc session id(稳定 key);
|
|
8318
|
-
* sidebar 据此把 fire 出的 session 折叠在 source session 下,跟 dispatchedFromSessionId
|
|
8319
|
-
* 平行但渲染不同 icon(AlarmClock 区别于 dispatch 的 CornerDownRight),让 owner 一眼
|
|
8320
|
-
* 分清"派生于人为委派"还是"派生于定时唤起"。非 shift fire session 缺省。
|
|
8321
|
-
* 跟 dispatchedFromSessionId / forkedFromSessionId 三者互斥(同一 session 不会兼任)。
|
|
8322
|
-
* mirror SessionFile 同 dispatchedFromSessionId 处理(shift fire 本地概念,理论上不会
|
|
8323
|
-
* mirror,留 strip 防御)。
|
|
8324
|
-
*/
|
|
7947
|
+
/** Scheduler source session, distinct from dispatch/fork ancestry. Strip on mirrors because this linkage is local-only. */
|
|
8325
7948
|
shiftFiredFromSessionId: external_exports.string().min(1).optional(),
|
|
8326
|
-
/**
|
|
8327
|
-
* LarkBotConnector(spec 2026-08-05):该 session 是某飞书群的专属会话。
|
|
8328
|
-
* larkChatId = 飞书群 chat_id(oc_ 前缀),与 persona-guest scope 的 capId `lark-<chat_id>` 对应;
|
|
8329
|
-
* 由 daemon lark-bot-connector 模块写入,UI 只读(sidebar 按此字段隐藏,Drawer 群清单按此聚合)。
|
|
8330
|
-
*/
|
|
7949
|
+
/** Bot session key, potentially virtual topic/speaker or document key, not necessarily a real IM chat id. UI hides these sessions from ordinary recent-session lists. */
|
|
8331
7950
|
larkChatId: external_exports.string().min(1).optional(),
|
|
8332
|
-
/**
|
|
7951
|
+
/** Session display-name snapshot, including topic/speaker where appropriate; updated on later events. */
|
|
8333
7952
|
larkChatName: external_exports.string().min(1).optional(),
|
|
8334
7953
|
/** lark 会话类型(spec 2026-08-06 P9):p2p 与群的 chat_id 形态无法区分,UI 文案分支需要。可选=兼容老数据(缺省按 group 展示) */
|
|
8335
7954
|
larkChatType: external_exports.enum(["p2p", "group"]).optional(),
|
|
@@ -8358,7 +7977,6 @@ var init_schemas = __esm({
|
|
|
8358
7977
|
oldLines: external_exports.number().int().nonnegative(),
|
|
8359
7978
|
newStart: external_exports.number().int().nonnegative(),
|
|
8360
7979
|
newLines: external_exports.number().int().nonnegative(),
|
|
8361
|
-
// 每行以 '+' / '-' / ' ' 开头(diff npm 包的标准 hunk lines)
|
|
8362
7980
|
lines: external_exports.array(external_exports.string())
|
|
8363
7981
|
});
|
|
8364
7982
|
ToolResultExtraSchema = external_exports.object({
|
|
@@ -8388,7 +8006,6 @@ var init_schemas = __esm({
|
|
|
8388
8006
|
});
|
|
8389
8007
|
AskQuestionItemSchema = external_exports.object({
|
|
8390
8008
|
question: external_exports.string().min(1),
|
|
8391
|
-
// multiSelect 必填(boolean 不给默认值):daemon emit 时必须显式传,避免 UI 端漂移
|
|
8392
8009
|
multiSelect: external_exports.boolean(),
|
|
8393
8010
|
options: external_exports.array(AskQuestionOptionSchema).min(1)
|
|
8394
8011
|
});
|
|
@@ -8415,13 +8032,7 @@ var init_schemas = __esm({
|
|
|
8415
8032
|
// 同一文本块的多次 stream_event 增量带相同 partialId;语义同 thinking
|
|
8416
8033
|
partialId: external_exports.string().optional()
|
|
8417
8034
|
}),
|
|
8418
|
-
//
|
|
8419
|
-
// meta 非空表示是 CC 注入的系统消息(task-notification / slash-command / system-reminder 等),
|
|
8420
|
-
// UI 据此走低视觉权重渲染或子 agent 通知卡。
|
|
8421
|
-
//
|
|
8422
|
-
// sender(2026-07-08 spec §4.1):daemon 'send' 命令 synth 的 user_text 携带发起人 SenderInfo;
|
|
8423
|
-
// observer 回灌路径(cc jsonl)永远不带 —— cc 自己写盘不知道 daemon 层身份。
|
|
8424
|
-
// UI 端 sender 缺省 → 不渲染 displayName label;有 → 气泡下方渲染。
|
|
8035
|
+
// Realtime user events carry daemon attribution. Engine transcripts omit structured sender, so history reconstructs persisted attribution from text.
|
|
8425
8036
|
external_exports.object({
|
|
8426
8037
|
...ParsedEventBase,
|
|
8427
8038
|
kind: external_exports.literal("user_text"),
|
|
@@ -8436,7 +8047,6 @@ var init_schemas = __esm({
|
|
|
8436
8047
|
kind: external_exports.literal("tool_call"),
|
|
8437
8048
|
toolUseId: external_exports.string(),
|
|
8438
8049
|
tool: external_exports.string(),
|
|
8439
|
-
// 等于 session.tool;UI 按 {toolKind, tool} 分派渲染。additive 可选,旧 adapter 不填也能解析
|
|
8440
8050
|
toolKind: external_exports.string().optional(),
|
|
8441
8051
|
input: external_exports.unknown()
|
|
8442
8052
|
}),
|
|
@@ -8451,11 +8061,7 @@ var init_schemas = __esm({
|
|
|
8451
8061
|
toolResultExtra: ToolResultExtraSchema.optional(),
|
|
8452
8062
|
// 精确指向发起 tool 的 assistant 消息顶层 uuid;toolUseId 已够 UI 聚合,这个仅作调试辅助
|
|
8453
8063
|
sourceToolAssistantUUID: external_exports.string().optional(),
|
|
8454
|
-
//
|
|
8455
|
-
// CC 用户答完后把 answers 写在 user 帧顶层 toolUseResult.answers,daemon parser
|
|
8456
|
-
// 抽到这里。UI eventsToLines 用 toolUseId 配对到 question-card line,把卡片转为
|
|
8457
|
-
// 只读态显示用户的答案。仅 AskUserQuestion 配对的 tool_result 行携带;普通工具不带。
|
|
8458
|
-
// 等价于 HistoryMessage.askQuestionAnswers(历史回放路径),两端字段名对齐。
|
|
8064
|
+
// Question answers pair tool results to their inline question-card anchor; history carries the equivalent answer projection.
|
|
8459
8065
|
askQuestionAnswers: external_exports.record(external_exports.string(), external_exports.string()).optional()
|
|
8460
8066
|
}),
|
|
8461
8067
|
// CC 命中项目记忆(`type:"attachment"`,`attachment.type:"memories"`)的独立事件;
|
|
@@ -8477,35 +8083,20 @@ var init_schemas = __esm({
|
|
|
8477
8083
|
// 选项 ii)。老 daemon / legacy CC 不发该字段时保持向后兼容(optional)。
|
|
8478
8084
|
toolUseId: external_exports.string().optional()
|
|
8479
8085
|
}),
|
|
8480
|
-
//
|
|
8481
|
-
// daemon parser 把 assistant tool_use AskUserQuestion 翻译成本 kind(不是 permission_request、
|
|
8482
|
-
// 也不是 tool_call),让 UI 在事件流自然位置渲染问答卡(紧跟 assistant 消息 / 在后续消息之前),
|
|
8483
|
-
// 而不是被强行挂在末尾。permission_request 帧仍正常下发驱动 pendingQuestions 状态机,
|
|
8484
|
-
// 但只走 wire 帧不入 ring buffer——状态由 daemon pendingQuestions 持有,
|
|
8485
|
-
// 渲染锚点由本 kind 的 ParsedEvent 提供。
|
|
8486
|
-
// 历史回放:history-to-lines.ts 在 jsonl tool_use AskUserQuestion 处插入对应 'question-card' line,
|
|
8487
|
-
// 配对的 tool_result 携带 toolUseResult.answers,已答完的展示只读态。
|
|
8086
|
+
// Inline question anchor determines card position. Pending-question wire state remains daemon-owned; answered tool results turn the same card read-only.
|
|
8488
8087
|
external_exports.object({
|
|
8489
8088
|
...ParsedEventBase,
|
|
8490
8089
|
kind: external_exports.literal("ask_user_question"),
|
|
8491
8090
|
toolUseId: external_exports.string(),
|
|
8492
8091
|
questions: external_exports.array(AskQuestionItemSchema)
|
|
8493
8092
|
}),
|
|
8494
|
-
//
|
|
8495
|
-
// UI useChatPanelState 收 "Claude is working" spinner(lastEventKind===turn_end)。
|
|
8496
|
-
// 三个产生来源:
|
|
8497
|
-
// ① SDK 模式 stream-json `result` 帧(claude.ts)—— stdout 最后一帧,可靠
|
|
8498
|
-
// ② TUI 模式 observer 读 JSONL `system/turn_duration` 行(claude-tui.ts parseLine)
|
|
8499
|
-
// ⚠️ CC 写此行早于该轮尾段正文落盘,可能被尾随 text 覆盖 lastEventKind(③ 存在的原因)
|
|
8500
|
-
// ③ TUI 模式 gate 关闭期被 drop 的 ② 信号,屏幕稳定后由 manager.notifyScreenIdle flush
|
|
8501
|
-
// 注入(PR #962 双源确认模型;旧 dispatchTurnIdle 已拆除)—— 排在 buffer 末条
|
|
8093
|
+
// End of one response turn. Engine adapters and TUI idle confirmation supply this event; reducer closes the turn and UI consumes status.
|
|
8502
8094
|
external_exports.object({
|
|
8503
8095
|
...ParsedEventBase,
|
|
8504
8096
|
kind: external_exports.literal("turn_end"),
|
|
8505
8097
|
// 本轮耗时(ms)。SDK=result.duration_ms;TUI=turn_duration.durationMs;缺失时 reducer 按 turnStartedAt 兜底
|
|
8506
8098
|
durationMs: external_exports.number().int().nonnegative().optional(),
|
|
8507
8099
|
reason: external_exports.enum(TURN_END_REASONS).optional(),
|
|
8508
|
-
/** Codex 完成轮次的原生 id;用于在该轮末条回复上显示 Fork / Quick Ask。 */
|
|
8509
8100
|
turnId: external_exports.string().min(1).optional()
|
|
8510
8101
|
}),
|
|
8511
8102
|
// SDK system 帧 task_started / task_progress / task_notification 的归一化产物:
|
|
@@ -8538,10 +8129,7 @@ var init_schemas = __esm({
|
|
|
8538
8129
|
kind: external_exports.literal("meta_update"),
|
|
8539
8130
|
patch: SessionMetaSchema
|
|
8540
8131
|
}),
|
|
8541
|
-
//
|
|
8542
|
-
// metaSource='cc' → CC 注入的系统消息(默认低视觉权重渲染)
|
|
8543
|
-
// metaSource='owner' → 老板通过 persona:appendOwnerMessage 插入的话(蓝色气泡)
|
|
8544
|
-
// 缺省视为 'cc';现有非 persona 场景保持兼容(不需要 owner 时不下发该字段)
|
|
8132
|
+
// metaSource selects engine-injected versus owner-authored system presentation; missing defaults to engine source.
|
|
8545
8133
|
external_exports.object({
|
|
8546
8134
|
...ParsedEventBase,
|
|
8547
8135
|
kind: external_exports.literal("meta-text"),
|
|
@@ -8554,28 +8142,16 @@ var init_schemas = __esm({
|
|
|
8554
8142
|
cwd: external_exports.string().min(1).optional(),
|
|
8555
8143
|
tool: external_exports.string().optional(),
|
|
8556
8144
|
label: external_exports.string().optional(),
|
|
8557
|
-
// AI 标题的创建期占位(UI 传首行截断);正式值由 daemon 经 meta_update 投递,
|
|
8558
|
-
// session:update 不接受此字段(用户改名走 label)
|
|
8559
8145
|
aiLabel: external_exports.string().optional(),
|
|
8560
8146
|
model: external_exports.string().optional(),
|
|
8561
8147
|
permissionMode: external_exports.string().optional(),
|
|
8562
8148
|
effort: external_exports.string().optional(),
|
|
8563
|
-
// 用户在 NewSessionDialog 选择的 icon 标识;UI 端做 enum 兜底
|
|
8564
8149
|
iconKey: external_exports.string().optional(),
|
|
8565
|
-
// session:fork 派生 session 时由 UI 传入源 sessionId,daemon 写到 SessionFile.forkedFromSessionId
|
|
8566
8150
|
forkedFromSessionId: external_exports.string().min(1).optional(),
|
|
8567
|
-
// 快速问答:创建时打 ephemeral 标记,daemon 写到 SessionFile.ephemeral
|
|
8568
8151
|
ephemeral: external_exports.boolean().optional(),
|
|
8569
|
-
//
|
|
8570
|
-
// 且按 ownerPersonaId 做 idempotent dedupe(每 persona 永远只有一个 owner session)
|
|
8152
|
+
// Persona context derives cwd and spawn defaults; missing cwd is allowed when persona identity determines it.
|
|
8571
8153
|
ownerPersonaId: external_exports.string().min(1).optional(),
|
|
8572
|
-
/**
|
|
8573
|
-
* 新建会话选择的 project(spec 2026-08-17-projects-sidebar §4.2):
|
|
8574
|
-
* - 单独出现:daemon 以它为 cwd(等价传 cwd)
|
|
8575
|
-
* - 与 ownerPersonaId 同时出现:cwd 仍派生为 persona 目录,project 目录经
|
|
8576
|
-
* addDirs(CC --add-dir)/ codexSandbox.writableRoots(codex guest)挂载,
|
|
8577
|
-
* 并写入 SessionFile.projectPath 供归组
|
|
8578
|
-
*/
|
|
8154
|
+
/** Alone, projectPath supplies cwd. With a persona, retain persona cwd and attach the project for owner access/grouping. */
|
|
8579
8155
|
projectPath: external_exports.string().min(1).optional()
|
|
8580
8156
|
}).refine((args) => args.cwd != null || args.ownerPersonaId != null || args.projectPath != null, {
|
|
8581
8157
|
message: "cwd / ownerPersonaId / projectPath \u81F3\u5C11\u4F20\u4E00\u4E2A"
|
|
@@ -8583,7 +8159,6 @@ var init_schemas = __esm({
|
|
|
8583
8159
|
ProjectEntry = external_exports.object({
|
|
8584
8160
|
/** 归一化绝对路径(daemon 侧 ~ 展开 + resolve) */
|
|
8585
8161
|
path: external_exports.string().min(1),
|
|
8586
|
-
/** 展示名,缺省 basename */
|
|
8587
8162
|
name: external_exports.string().min(1),
|
|
8588
8163
|
createdAt: external_exports.string().datetime(),
|
|
8589
8164
|
/** 置顶时间(ms epoch,与 SessionFile.pinnedAt 同语义)。clawd-local,不同步 Codex */
|
|
@@ -8591,7 +8166,6 @@ var init_schemas = __esm({
|
|
|
8591
8166
|
});
|
|
8592
8167
|
ProjectPatchArgs = external_exports.object({
|
|
8593
8168
|
path: external_exports.string().min(1),
|
|
8594
|
-
/** number = 置顶(ms epoch);null = 取消置顶 */
|
|
8595
8169
|
pinnedAt: external_exports.number().nullable()
|
|
8596
8170
|
});
|
|
8597
8171
|
ProjectRevealArgs = external_exports.object({
|
|
@@ -8621,22 +8195,10 @@ var init_schemas = __esm({
|
|
|
8621
8195
|
SessionListArgs = external_exports.object({
|
|
8622
8196
|
/** true = 只返回 mirror(originOwnerPrincipalId 非空,即「我在朋友 daemon 上创建」的本机存证)。 */
|
|
8623
8197
|
originOnly: external_exports.boolean().optional(),
|
|
8624
|
-
/**
|
|
8625
|
-
* 按 own persona 窄化(2026-07-21 self-persona-sessions 追加):只留
|
|
8626
|
-
* `s.ownerPersonaId === args.ownerPersonaId` 的 sessions。
|
|
8627
|
-
* - mirror 行(`ownerPersonaId` 恒 null)天然排除
|
|
8628
|
-
* - 与 `originOnly=true` 同传恒返 0
|
|
8629
|
-
* - 老 daemon 不识别时忽略字段返回全量,客户端消费侧本就自带过滤/截断,天然向后兼容
|
|
8630
|
-
*/
|
|
8198
|
+
/** Filter by local ownerPersonaId. Mirrors have this local pointer stripped; originOnly with this filter therefore produces no rows. */
|
|
8631
8199
|
ownerPersonaId: external_exports.string().min(1).optional(),
|
|
8632
|
-
/** 按 updatedAt desc(list 的既有排序)截断前 N 条。 */
|
|
8633
8200
|
limit: external_exports.number().int().positive().optional(),
|
|
8634
|
-
/**
|
|
8635
|
-
* 分页起点(2026-07-18 android-tasks-tab 迭代追加):originOnly / ownerPersonaId 过滤后、
|
|
8636
|
-
* limit 截断前跳过前 N 条。与 limit 组合 = updatedAt desc 序上的一页。响应带 total
|
|
8637
|
-
* (过滤后总数,见 handler)供客户端判 hasMore。老 daemon 不识别时忽略 → 回全量,
|
|
8638
|
-
* 客户端按全量兼容。
|
|
8639
|
-
*/
|
|
8201
|
+
/** Offset applies after filtering and before limit. Response total counts the filtered set; older daemons can return full data for client fallback. */
|
|
8640
8202
|
offset: external_exports.number().int().nonnegative().optional()
|
|
8641
8203
|
});
|
|
8642
8204
|
SessionIdArgs = external_exports.object({ sessionId: external_exports.string().min(1) });
|
|
@@ -8648,12 +8210,8 @@ var init_schemas = __esm({
|
|
|
8648
8210
|
permissionMode: external_exports.string().optional(),
|
|
8649
8211
|
effort: external_exports.string().optional(),
|
|
8650
8212
|
cwd: external_exports.string().optional(),
|
|
8651
|
-
// 用户在 Edit modal 选择的 icon 标识;UI 端做 enum 兜底
|
|
8652
8213
|
iconKey: external_exports.string().optional(),
|
|
8653
|
-
//
|
|
8654
|
-
// z.literal(false) 让「不可逆」这条产品语义与协议约束是同一件事:反向标记会让会话
|
|
8655
|
-
// 从所有 sidebar 消费者视野消失(App.visibleSessions / buildSessionTree),用户看不见
|
|
8656
|
-
// 也删不掉,再被 sweepEphemeralOnStartup 静默删除——数据丢失级别的口子。
|
|
8214
|
+
// Promotion can only remove ephemeral status; adding it later would hide retained data and make startup cleanup delete it.
|
|
8657
8215
|
ephemeral: external_exports.literal(false).optional()
|
|
8658
8216
|
})
|
|
8659
8217
|
});
|
|
@@ -8666,11 +8224,7 @@ var init_schemas = __esm({
|
|
|
8666
8224
|
SessionSendArgs = external_exports.object({
|
|
8667
8225
|
sessionId: external_exports.string().min(1),
|
|
8668
8226
|
text: external_exports.string(),
|
|
8669
|
-
/**
|
|
8670
|
-
* turn 在跑时是入队还是直发。**入队与否由调用方显式声明,daemon 不按来源猜**:
|
|
8671
|
-
* UI 输入框传 true;IM / 飞书 / dispatch 注入不传 = 维持原状直发(老板 2026-08-31:
|
|
8672
|
-
* 其他地方先不加队列)。缺省 false → 行为与本字段引入前逐字一致。
|
|
8673
|
-
*/
|
|
8227
|
+
/** Queueing is caller opt-in, not inferred from origin. Missing/false preserves direct delivery for existing callers. */
|
|
8674
8228
|
queueWhileRunning: external_exports.boolean().optional()
|
|
8675
8229
|
});
|
|
8676
8230
|
SessionDequeueArgs = external_exports.object({
|
|
@@ -8724,21 +8278,11 @@ var init_schemas = __esm({
|
|
|
8724
8278
|
SessionForkArgs = external_exports.object({
|
|
8725
8279
|
cwd: external_exports.string().min(1),
|
|
8726
8280
|
toolSessionId: external_exports.string().min(1),
|
|
8727
|
-
/** Claude 消息 UUID;Codex 改用 turnId。 */
|
|
8728
8281
|
messageUuid: external_exports.string().min(1).optional(),
|
|
8729
|
-
/** Codex 原生已完成 turn ID。 */
|
|
8730
8282
|
turnId: external_exports.string().min(1).optional(),
|
|
8731
8283
|
/** 源 clawd session ID;Codex 必填,handler 用它校验 thread 归属。 */
|
|
8732
8284
|
sourceSessionId: external_exports.string().min(1).optional(),
|
|
8733
|
-
/**
|
|
8734
|
-
* 派生 jsonl 的落点 cwd(缺省 = 源文件所在目录,保持老行为)。
|
|
8735
|
-
*
|
|
8736
|
-
* 必须传的场景:调用方随后要用这条 transcript 建 session,而那个 session 的 cwd 与
|
|
8737
|
-
* 源 session 不同——最典型是 persona session(daemon 按 ownerPersonaId 把 cwd 强制
|
|
8738
|
-
* 派生成 persona 根)。CC `--resume` 只在**当前 cwd 的** projects 目录里找 session,
|
|
8739
|
-
* 落点与目标 cwd 分家 = 历史读不到 + resume 接不上(bug: quick-ask-blank-after-worktree)。
|
|
8740
|
-
* 正确编排:先 sessionCreate 拿到 daemon 定的真实 cwd,再以它作 targetCwd fork。
|
|
8741
|
-
*/
|
|
8285
|
+
/** Fork into the actual target cwd when it differs from the source: Claude resumes only from the current cwd's transcript directory. Create the target session first to obtain that derived cwd. */
|
|
8742
8286
|
targetCwd: external_exports.string().min(1).optional()
|
|
8743
8287
|
}).refine((args) => Boolean(args.messageUuid || args.turnId), "messageUuid or turnId required");
|
|
8744
8288
|
SessionForkResponseSchema = external_exports.object({
|
|
@@ -8774,14 +8318,7 @@ var init_schemas = __esm({
|
|
|
8774
8318
|
offset: external_exports.number().int().nonnegative().optional(),
|
|
8775
8319
|
/** 云历史分页快照标识;续页原样带回,避免多条链交错时混读。 */
|
|
8776
8320
|
snapshotId: external_exports.string().uuid().optional(),
|
|
8777
|
-
/**
|
|
8778
|
-
* 瘦身投影(2026-07-18 android-tasks-tab 迭代,v2 语义):true = daemon 裁掉窄消费面客户端
|
|
8779
|
-
* (mobile)不渲染的大字段(input / output / raw / toolResultExtra / memories /
|
|
8780
|
-
* sourceToolAssistantUUID),meta 注入行 text 截 500 字符。**例外:出错的工具调用全量保留**——
|
|
8781
|
-
* 本页内 error 非空的 tool_result 保留 output,且其同 toolUseId 的 tool_call 保留 input
|
|
8782
|
-
* (用户要能看清「啥错了」;老板 2026-07-18 拍板:普通工具调用手机不渲染,错误全量展示)。
|
|
8783
|
-
* desktop 渲染 tool card 需全文,不用此参数。老 daemon 不识别 → 回全量,客户端向后兼容。
|
|
8784
|
-
*/
|
|
8321
|
+
/** Slim drops large non-rendered fields and truncates metadata text. Failed tool results keep output and page-local matching calls keep input, so error diagnosis remains possible. */
|
|
8785
8322
|
slim: external_exports.boolean().optional()
|
|
8786
8323
|
});
|
|
8787
8324
|
HistorySubagentsArgs = external_exports.object({
|
|
@@ -8794,7 +8331,6 @@ var init_schemas = __esm({
|
|
|
8794
8331
|
subagentId: external_exports.string().min(1)
|
|
8795
8332
|
});
|
|
8796
8333
|
WorkspaceListArgs = external_exports.object({
|
|
8797
|
-
// cwd 缺省则 daemon 兜底 os.homedir();UI 的 DirBrowser 首次浏览就走这条路径
|
|
8798
8334
|
cwd: external_exports.string().min(1).optional(),
|
|
8799
8335
|
path: external_exports.string().optional(),
|
|
8800
8336
|
showHidden: external_exports.boolean().optional()
|
|
@@ -8845,8 +8381,6 @@ var init_schemas = __esm({
|
|
|
8845
8381
|
});
|
|
8846
8382
|
SessionSearchMatchSchema = external_exports.object({
|
|
8847
8383
|
sessionId: external_exports.string().min(1),
|
|
8848
|
-
// 命中处上下文片段,已单行化。**不回高亮 offset**:调用方手里有 query,
|
|
8849
|
-
// 自己在 snippet 里 indexOf 就能高亮,wire 上少一组坐标少一处漂
|
|
8850
8384
|
snippet: external_exports.string()
|
|
8851
8385
|
});
|
|
8852
8386
|
SessionSearchContentResponseSchema = external_exports.object({
|
|
@@ -8890,14 +8424,12 @@ var init_schemas = __esm({
|
|
|
8890
8424
|
sessionId: external_exports.string().min(1),
|
|
8891
8425
|
toolUseId: external_exports.string().min(1),
|
|
8892
8426
|
requestId: external_exports.string().min(1),
|
|
8893
|
-
// UI 单卡承载多 question;空数组语义无效,schema 必拒
|
|
8894
8427
|
questions: external_exports.array(AskQuestionItemSchema).min(1)
|
|
8895
8428
|
});
|
|
8896
8429
|
SessionQuestionClearedFrameSchema = external_exports.object({
|
|
8897
8430
|
type: external_exports.literal("session:question:cleared"),
|
|
8898
8431
|
sessionId: external_exports.string().min(1),
|
|
8899
8432
|
toolUseId: external_exports.string().min(1),
|
|
8900
|
-
// record<string,string>:与 AnswerQuestionArgs.answers 同形状(question text → label)
|
|
8901
8433
|
answers: external_exports.record(external_exports.string(), external_exports.string()).optional()
|
|
8902
8434
|
});
|
|
8903
8435
|
AnswerQuestionArgs = external_exports.object({
|
|
@@ -8922,15 +8454,7 @@ var init_schemas = __esm({
|
|
|
8922
8454
|
type: external_exports.literal("auth"),
|
|
8923
8455
|
token: external_exports.string().min(1),
|
|
8924
8456
|
scheme: external_exports.literal("bearer").optional(),
|
|
8925
|
-
/**
|
|
8926
|
-
* 决策 #16:自报身份字段(selfPrincipalId / selfDisplayName / selfDeviceId)全部删除——
|
|
8927
|
-
* guest 的设备 id / 人 id / 来源全部由 connect token 的 subDevice/subOwner/provider 签名背书
|
|
8928
|
-
* (server 签、目标设备本地验签,不可伪造),不再接受任何自报身份。
|
|
8929
|
-
*
|
|
8930
|
-
* selfUrl 已废弃(spec 2026-07-22 双向确认建联):可达地址自报移入 contact:handshake
|
|
8931
|
-
* args——auth 层回归纯验票。旧客户端发来的 selfUrl 被 strip(zod 默认),无害;
|
|
8932
|
-
* 旧客户端本身由 whoami 的 connect-token 拒绝路径拦截(CLIENT_OUTDATED)。
|
|
8933
|
-
*/
|
|
8457
|
+
/** Guest identity comes from signed token claims. Legacy self-reported fields and selfUrl are stripped; reachability belongs to contact:handshake. */
|
|
8934
8458
|
/**
|
|
8935
8459
|
* 客户端自报的 user-agent 性质标签(诊断/展示用)。
|
|
8936
8460
|
* 「自报事实」,非身份(决策 #16 边界):不参与鉴权、不参与路由、
|
|
@@ -8986,15 +8510,8 @@ var init_schemas = __esm({
|
|
|
8986
8510
|
os: external_exports.string(),
|
|
8987
8511
|
tools: external_exports.array(external_exports.object({ id: external_exports.string(), available: external_exports.boolean() })),
|
|
8988
8512
|
runningSessions: external_exports.array(InfoRunningSessionSchema),
|
|
8989
|
-
|
|
8990
|
-
// PR 1 阶段标 optional(daemon 还没下发,旧 daemon info 响应不带这五个字段);
|
|
8991
|
-
// PR 2 daemon 实现 single HTTP server + auth-context 后,daemon 必返这五个字段,
|
|
8992
|
-
// 届时可考虑改成 required。spec §11 第 3 条:"tokenRole 是协议字段,daemon 查 token
|
|
8993
|
-
// 表后显式下发,UI 不自行推导"——所以 UI 一律读这里,禁止本地推导。
|
|
8994
|
-
/** 'owner' = 拿到 owner-token 的连接(不限来源 IP)。来源 TOKEN_ROLES(spec §11 #1 中央真理源)。
|
|
8995
|
-
* 历史 'personal' role(persona file-sharing token)在 2026-05-21 删除;tokenPersonaId 一同移除。 */
|
|
8513
|
+
/** File-token role supplied by daemon; derive accepted values from the central TOKEN_ROLES array. */
|
|
8996
8514
|
tokenRole: external_exports.enum(TOKEN_ROLES).optional(),
|
|
8997
|
-
/** socket.remoteAddress 是否落在 127.0.0.1 / ::1;本期无 RPC 消费,保留协议槽位给未来 "Reveal in Finder" 等本机动作 */
|
|
8998
8515
|
isLoopback: external_exports.boolean().optional(),
|
|
8999
8516
|
/** UI 拼 file-sharing HTTP 路由的前缀(http(s)://host:port),不含尾斜杠;frpc 反代时返反代地址 */
|
|
9000
8517
|
httpBaseUrl: external_exports.string().optional(),
|
|
@@ -9248,7 +8765,6 @@ var init_methods = __esm({
|
|
|
9248
8765
|
summary: "\u8BFB\u6587\u4EF6\u5185\u5BB9\uFF1Bguest \u8DEF\u5F84\u8FB9\u754C\u4E0E workspace:list \u540C",
|
|
9249
8766
|
args: WorkspaceReadArgs
|
|
9250
8767
|
},
|
|
9251
|
-
// ---- deploy:*(persona 上云:persona 目录 = git 仓库工作树,发布 = 推送触发仓库流水线)----
|
|
9252
8768
|
"deploy:get": {
|
|
9253
8769
|
summary: "\u4E0A\u4E91\u9875\u9762\u8981\u7684\u5168\u90E8\uFF1A\u4ED3\u5E93\u7ED1\u5B9A\u72B6\u6001\uFF08git \u73B0\u7B97\uFF09\u3001\u626B\u63CF\u5386\u53F2\uFF08\u6BCF\u6761\u5E26\u6E05\u5355\u5FEB\u7167\uFF09\u3001\u53D1\u5E03\u5386\u53F2\uFF08\u6700\u540E\u4E00\u6761 = \u7EBF\u4E0A\u6B63\u8DD1\u7684\uFF09\u3001\u4ED3\u5E93 Secrets \u91CC\u5404\u53D8\u91CF\u540D\u586B\u8FC7\u6CA1\u6709\uFF08**\u53EA\u6709\u540D\u5B57\u548C\u65F6\u95F4\uFF0C\u6CA1\u6709\u503C**\uFF09\u3001\u4E00\u6761\u6D41\u6C34\u7EBF\u72B6\u6001\uFF08\u7F3A\u7701\u4ED3\u5E93\u6700\u65B0\u90A3\u6761\uFF1B`releaseHead` \u6307\u540D\u770B\u54EA\u6B21\u53D1\u5E03\u7684\uFF09\uFF1Bowner-only",
|
|
9254
8770
|
args: DeployGetArgsSchema
|
|
@@ -9292,7 +8808,6 @@ var init_methods = __esm({
|
|
|
9292
8808
|
summary: "\u4E0B\u7EBF\uFF1A\u89E6\u53D1 persona \u4ED3\u5E93\u91CC\u7684 clawd-unpublish.yml\uFF08workflow_dispatch\uFF09\uFF0C\u7531\u6D41\u6C34\u7EBF\u5728 gateway \u64A4\u6389\u6B63\u5F0F\u767B\u8BB0\u2014\u2014\u522B\u4EBA\u770B\u4E0D\u5230\u3001\u7528\u4E0D\u4E86\uFF1B\u4ED3\u5E93 / \u955C\u50CF / \u672C\u673A\u4E0A\u4E91\u8BB0\u5F55\u90FD\u4E0D\u52A8\uFF0C\u91CD\u65B0\u53D1\u5E03\u5C31\u56DE\u6765\u3002\u6700\u540E\u4E00\u6B21\u53D1\u5E03\u7684 commit \u91CC\u6CA1\u6709\u8FD9\u4EFD workflow\uFF08\u65E7\u6A21\u677F\uFF09\u65F6\u62D2\uFF0C\u8981\u5148\u91CD\u65B0\u53D1\u5E03\u4E00\u6B21\u3002\u56DE { before }\uFF1A\u89E6\u53D1\u4E4B\u524D\u73B0\u67E5\u7684\u6700\u65B0\u4E0B\u7EBF run id\uFF08\u6CA1\u4E0B\u7EBF\u8FC7 = null\uFF09\uFF0C\u9875\u9762\u62FF\u5B83\u8BA4\u65B0 run\uFF1Bowner-only\u3001human-only",
|
|
9293
8809
|
args: DeployPersonaArgsSchema
|
|
9294
8810
|
},
|
|
9295
|
-
// ---- githubAuth:*(本机 GitHub 身份,机器级;spec 2026-09-15-github-auth-unify-design §4.3)----
|
|
9296
8811
|
"githubAuth:get": {
|
|
9297
8812
|
summary: "\u672C\u673A\u7684 GitHub \u8EAB\u4EFD\uFF1Aauthorized\uFF08@login\uFF0C\u6765\u81EA git \u51ED\u636E\u5B58\u50A8 / \u672C\u673A\u51ED\u636E\u8868\uFF09/ missing / invalid\uFF08GitHub \u4E0D\u8BA4\uFF09/ pending\uFF08\u7B49 GitHub \u8DF3\u56DE\u4E2D\u5FC3\uFF09\u3002\u8BBE\u7F6E\u9875\u300C\u8D26\u53F7\u300D\u3001\u4E0A\u4E91\u9875\u9876\u4E0A\u90A3\u5F20\u5361\u3001PR \u6536\u4EF6\u7BB1\u90FD\u770B\u5B83\uFF1Bpending \u671F\u9875\u9762\u6BCF 2 \u79D2\u8F6E\u8BE2\uFF0Cdaemon \u987A\u624B\u5411\u4E2D\u5FC3\u9886 token\u3002\u65E0\u53C2\u6570",
|
|
9298
8813
|
args: NO_ARGS
|
|
@@ -9305,7 +8820,6 @@ var init_methods = __esm({
|
|
|
9305
8820
|
summary: "\u628A\u7528\u6237\u7C98\u7684 GitHub token \u5B58\u6210\u672C\u673A\u8EAB\u4EFD\uFF08\u70B9\u94FE\u63A5\u8D70\u4E0D\u901A\u65F6\u7684\u9000\u8DEF\uFF09\uFF1A\u5148 GET /user \u9A8C\u8BC1\uFF0C\u518D git credential approve \u4EA4\u7ED9 git \u7684 helper \u5B58\uFF08git \u6CA1\u914D helper \u65F6\u9000\u5230\u672C\u673A\u51ED\u636E\u8868 secrets/github.env\uFF09\u3002\u5165\u53C2\u4E0D\u8FDB\u65E5\u5FD7\u3001\u54CD\u5E94\u4E0D\u56DE\u663E token\uFF1Bowner-only",
|
|
9306
8821
|
args: GithubAuthAuthorizeArgsSchema
|
|
9307
8822
|
},
|
|
9308
|
-
// ---- cloud:connectors:*(云 persona 卡片上的连接器;design 2026-09-11-mcp-connector §3.8)----
|
|
9309
8823
|
"cloud:connectors:list": {
|
|
9310
8824
|
summary: "\u4E91\u4E0A persona \u5361\u7247\u4E0A\u7684\u8FDE\u63A5\u5668\u533A\uFF1A\u8FD9\u4E2A persona \u58F0\u660E\u7684\u3001**\u8981\u4F7F\u7528\u8005\u672C\u4EBA\u6388\u6743\u7684**\u8FDE\u63A5\u5668\u5404\u81EA\u8FDE\u6CA1\u8FDE\u4E0A\uFF08connected / missing / reauth\uFF09\u3001\u6309\u94AE\u662F\u5173\u8054\uFF08OAuth\uFF0C\u5E26\u73B0\u7B97\u7684 authUrl\uFF09\u8FD8\u662F\u7C98\u8D34 API key\uFF08\u5E26\u8981\u586B\u7684\u53D8\u91CF\uFF09\u3002daemon \u62FF gateway \u767B\u8BB0\u884C + \u672C\u673A\u767B\u5F55\u51ED\u8BC1\u5411\u4E2D\u5FC3\u4EE3\u67E5\uFF0C\u51ED\u8BC1\u4E0D\u51FA ws\uFF1Bowner-only",
|
|
9311
8825
|
args: CloudConnectorsListArgsSchema
|
|
@@ -9314,12 +8828,10 @@ var init_methods = __esm({
|
|
|
9314
8828
|
summary: "\u586B\u5199\u578B\u8FDE\u63A5\u5668\uFF1A\u628A\u4F7F\u7528\u8005\u672C\u4EBA\u7684\u51ED\u636E\u503C\u4EA4\u7ED9\u4E2D\u5FC3\u52A0\u5BC6\u5165\u5E93\uFF08\u4E00\u6B21\u586B\u5199\u5168\u5C40\u5171\u7528\uFF09\u3002**\u8FD9\u662F\u503C\u552F\u4E00\u7684\u5165\u53E3**\uFF1A\u5165\u53C2\u4E0D\u8FDB\u65E5\u5FD7\u3001\u54CD\u5E94\u4E0D\u56DE\u663E\u3001\u672C\u673A\u4E0D\u843D\u76D8\uFF1B\u683C\u540D\u5FC5\u987B\u662F\u8BE5\u8FDE\u63A5\u5668\u58F0\u660E\u8FC7\u7684\uFF1Bowner-only",
|
|
9315
8829
|
args: CloudConnectorFillArgsSchema
|
|
9316
8830
|
},
|
|
9317
|
-
// ---- cloud:volume:get(云上会话聊天页顶栏的云盘角标;design 2026-09-19-cloud-volume-usage §3.3)----
|
|
9318
8831
|
"cloud:volume:get": {
|
|
9319
8832
|
summary: "\u8C03\u7528\u65B9\u81EA\u5DF1\u90A3\u5757\u4E91\u76D8\u4E0A\u4E00\u8F6E\u672B\u7684\u7528\u91CF\uFF08usedBytes / sizeBytes / measuredAt\uFF09\u3002\u6309\u4EBA\u4E0D\u6309 persona\uFF1A\u4E91\u4E0A\u6240\u6709 persona \u5171\u7528\u4E00\u5757\u76D8\u3002daemon \u62FF\u672C\u673A api key \u95EE gateway /me/volume\uFF1B\u6CA1\u767B\u5F55 / \u6CA1 key / gateway \u4E0D\u901A / \u6CA1\u8DD1\u8FC7\u4EFB\u52A1\u4E00\u5F8B\u56DE volume:null\uFF08\u89D2\u6807\u4E0D\u6E32\uFF09\uFF0C\u4E0D\u629B\uFF1Bowner-only\u3002\u65E0\u53C2\u6570\uFF08\u67E5\u7684\u662F\u8C03\u7528\u65B9\u81EA\u5DF1\u7684\u76D8\uFF09",
|
|
9320
8833
|
args: NO_ARGS
|
|
9321
8834
|
},
|
|
9322
|
-
// ---- dashboard:*(设置 → Dashboard 专区;design 2026-09-23-cloud-persona-metrics §3.8)----
|
|
9323
8835
|
"dashboard:openUrl": {
|
|
9324
8836
|
summary: "\u7B7E\u4E00\u4E2A\u770B\u677F\u9875\u9762\u7684\u6D4F\u89C8\u5668\u94FE\u63A5\uFF08\u73B0\u5728\u53EA\u6709 cloud-persona-metrics\uFF1A\u4E91 persona \u8FD0\u884C\u72B6\u51B5\uFF09\u3002\u94FE\u63A5\u8D70\u672C\u673A tunnel \u516C\u7F51\u5730\u5740\u300124 \u5C0F\u65F6\u540E\u8FC7\u671F\uFF0C\u9875\u9762\u4E0E\u5B83\u7684\u6570\u636E\u63A5\u53E3\u90FD\u51ED\u94FE\u63A5\u4E0A\u7684\u7B7E\u540D\u653E\u884C\uFF1B\u6570\u636E\u7531\u672C\u673A daemon \u62FF\u672C\u673A api key \u5411\u4E91\u4E0A gateway \u4EE3\u53D6\u3002tunnel \u6CA1\u8FDE\u4E0A\u62A5 TUNNEL_UNAVAILABLE\uFF1Bowner-only",
|
|
9325
8837
|
args: DashboardOpenUrlArgs
|
|
@@ -9328,7 +8840,6 @@ var init_methods = __esm({
|
|
|
9328
8840
|
summary: "\u5217\u51FA owner \u7684\u5168\u90E8 shift\uFF08\u6392\u73ED\uFF09\uFF0C\u7ED9 UI \u65E5\u5386\u6E32\u67D3\u7528\uFF1Bowner-only",
|
|
9329
8841
|
args: NO_ARGS
|
|
9330
8842
|
},
|
|
9331
|
-
// ---- channel:*(Channel × Master:话题的聚合,纯组织单位)----
|
|
9332
8843
|
"channel:list": {
|
|
9333
8844
|
summary: "\u5217\u51FA\u5168\u90E8\u9891\u9053\uFF08Channel \xD7 Master \u7684\u7EC4\u7EC7\u5355\u4F4D\uFF1A\u6210\u5458\u540D\u5355 + \u8BDD\u9898\u7684\u805A\u5408\uFF09\uFF1Bowner-only\u3002\u65E0\u53C2\u6570",
|
|
9334
8845
|
args: NO_ARGS
|
|
@@ -9345,7 +8856,6 @@ var init_methods = __esm({
|
|
|
9345
8856
|
summary: "\u5220\u9891\u9053\uFF1B\u5176\u4E0B\u4ECD\u6709\u672A\u7ED3\u675F\u8BDD\u9898\u65F6\u62D2\u7EDD\uFF08fail-fast\uFF0C\u4E0D\u7EA7\u8054\u5220\u8BDD\u9898\uFF09",
|
|
9346
8857
|
args: ChannelDeleteArgsSchema
|
|
9347
8858
|
},
|
|
9348
|
-
// ---- topic:*(话题:一个任务的容器;一个话题 = 一个 Master session)----
|
|
9349
8859
|
"topic:list": {
|
|
9350
8860
|
summary: "\u5217\u67D0\u9891\u9053\u4E0B\u5168\u90E8\u8BDD\u9898\uFF08\u542B\u72B6\u6001\uFF0C\u4F9B\u9891\u9053 flyout \u5217\u8868\uFF09",
|
|
9351
8861
|
args: TopicListArgsSchema
|
|
@@ -9377,7 +8887,6 @@ var init_methods = __esm({
|
|
|
9377
8887
|
summary: "\u5224\u67D0\u6761\u7EBF\u5408\u683C\uFF1A\u7F6E accepted \u5E76\u540C\u6B65\u843D\u4E00\u6761\u6D41\u6C34\uFF1Breason \u4E0E watcherVerdict \u5FC5\u586B\uFF08\u4E0E\u6253\u56DE\u5BF9\u79F0\uFF0C\u53EA\u8BB0\u6253\u56DE\u7B49\u4E8E\u53EA\u8BB0\u5206\u5B50\uFF09\u3002watcherVerdict=fail \u65F6\u5FC5\u987B\u7ED9 overrideReason\uFF0Cdaemon \u5355\u72EC\u8BA1\u63A8\u7FFB\u6B21\u6570\u3002\u8FD4\u56DE acceptedLines / totalLines / allAccepted / overrides / needsOwnerAttention\uFF0C\u6536\u655B\u65F6\u673A\u7531 daemon \u6570\u4E0D\u7531 Master \u76D8",
|
|
9378
8888
|
args: TopicAcceptArgsSchema,
|
|
9379
8889
|
argsConstraints: {
|
|
9380
|
-
// watcherVerdict=fail 时 overrideReason 必填(.refine 的谓词不可序列化,这里显式声明)
|
|
9381
8890
|
if: { properties: { watcherVerdict: { const: "fail" } }, required: ["watcherVerdict"] },
|
|
9382
8891
|
then: { required: ["overrideReason"] }
|
|
9383
8892
|
}
|
|
@@ -9394,7 +8903,6 @@ var init_methods = __esm({
|
|
|
9394
8903
|
summary: "\u5220\u8BDD\u9898\uFF1A\u8FDE\u5E26\u5220\u8FD9\u4E2A\u8BDD\u9898\u540D\u4E0B\u7684\u5168\u90E8\u4F1A\u8BDD\u2014\u2014\u5B83\u7684 Master session\uFF081:1\uFF09+ \u5B83\u6D3E\u51FA\u53BB\u7684\u6BCF\u4E00\u6761 worker session\uFF1Bdispatch \u53F0\u8D26\u884C\u4FDD\u7559\u3002\u4F1A\u8BDD\u5DF2\u4E0D\u5728\u65F6\u8BDD\u9898\u7167\u5220\uFF0CMaster \u90A3\u6761\u56DE removedMasterSessionId: null\uFF0Cworker \u90A3\u4E9B\u4E0D\u8FDB removedWorkerSessionIds",
|
|
9395
8904
|
args: TopicDeleteArgsSchema
|
|
9396
8905
|
},
|
|
9397
|
-
// ---- master:*(授权查表,Master 经 clawd-rpc 桥调)----
|
|
9398
8906
|
"master:authorityCheck": {
|
|
9399
8907
|
summary: "\u67E5 Master \u7684\u6388\u6743\u8BBE\u5B9A\uFF08\u5168\u5C40\u4E00\u5957\uFF09\uFF1A\u8FD4\u56DE ask / allow / deny + source\u3002**\u8BBE\u5B9A\u6587\u4EF6\u4E0D\u5B58\u5728 \u2192 \u7528\u51FA\u5382\u9ED8\u8BA4\u89C4\u5219\u8868**\uFF08source=default\uFF0C\u5F53\u524D\u56DB\u6761\u5BF9\u5916\u4E0D\u53EF\u9006\u52A8\u4F5C\u5168\u662F ask\uFF0C\u522B\u628A\u5B83\u5F53\u5F02\u5E38\u515C\u5E95\uFF09\uFF1B\u8BFB\u574F / \u7ED3\u6784\u4E0D\u5BF9 / action \u62FC\u9519 / id \u672A\u767B\u8BB0 \u2192 **fail-safe \u88C1\u51B3 ask**\uFF08source=fallback:<\u539F\u56E0>\uFF0C\u4E0D\u9759\u9ED8\u541E\uFF09",
|
|
9400
8908
|
args: AuthorityCheckArgsSchema
|
|
@@ -9759,20 +9267,15 @@ var init_methods = __esm({
|
|
|
9759
9267
|
RPC_METHOD_STATUSES = ["allowed", "conditional", "denied"];
|
|
9760
9268
|
RpcMethodStatusSchema = external_exports.enum(RPC_METHOD_STATUSES);
|
|
9761
9269
|
RpcMethodInfoSchema = external_exports.object({
|
|
9762
|
-
/** canonical method 名,直接可作为 clawd-rpc call 的 method 参数 */
|
|
9763
9270
|
name: external_exports.string().min(1),
|
|
9764
9271
|
status: RpcMethodStatusSchema,
|
|
9765
|
-
/** 该 method 干什么,取自 {@link METHOD_DOCS} 的 summary */
|
|
9766
9272
|
description: external_exports.string().min(1),
|
|
9767
9273
|
/**
|
|
9768
9274
|
* 参数字段,从 {@link METHOD_DOCS} 的 args schema **反射**得来(不手写)。
|
|
9769
9275
|
* `null` = 该 method 还没有中央 zod schema(欠账,见 RpcMethodDoc),调用方需自行试探。
|
|
9770
9276
|
*/
|
|
9771
9277
|
args: external_exports.array(external_exports.object({ name: external_exports.string(), required: external_exports.boolean() })).nullable(),
|
|
9772
|
-
/**
|
|
9773
|
-
* 完整 args JSON Schema(draft-07),来自 {@link methodArgsSchema}。
|
|
9774
|
-
* **只在按 method 单查时下发**——全量目录带上 105 份 schema 会吃掉消费方一大块 context。
|
|
9775
|
-
*/
|
|
9278
|
+
/** Full JSON Schema is included only for a method-specific query to bound the directory response size. */
|
|
9776
9279
|
argsSchema: external_exports.record(external_exports.unknown()).optional(),
|
|
9777
9280
|
/**
|
|
9778
9281
|
* `denied` 必带:daemon error code(`UNAUTHORIZED` / `FEISHU_LOGIN_REQUIRED` /
|
|
@@ -9786,7 +9289,6 @@ var init_methods = __esm({
|
|
|
9786
9289
|
});
|
|
9787
9290
|
MetaMethodsResultSchema = external_exports.object({
|
|
9788
9291
|
type: external_exports.literal("meta:methods"),
|
|
9789
|
-
/** METHOD_NAMES 全集,顺序与之一致 */
|
|
9790
9292
|
methods: external_exports.array(RpcMethodInfoSchema)
|
|
9791
9293
|
});
|
|
9792
9294
|
zodToJsonSchemaMono = zodToJsonSchema;
|
|
@@ -9816,11 +9318,8 @@ var init_errors2 = __esm({
|
|
|
9816
9318
|
UNAUTHORIZED: "UNAUTHORIZED",
|
|
9817
9319
|
FORBIDDEN: "FORBIDDEN",
|
|
9818
9320
|
INVALID_PARAM: "INVALID_PARAM",
|
|
9819
|
-
//
|
|
9820
|
-
// daemon 进程身份未激活为飞书 unionId 时,FEISHU_GATED_METHODS 内的 RPC 一律返回此错误;
|
|
9821
|
-
// UI 据此显示登录引导(区别于 UNAUTHORIZED 的越权语义)。
|
|
9321
|
+
// Missing active login is distinct from permission denial; UI presents the login path for gated remote methods.
|
|
9822
9322
|
FEISHU_LOGIN_REQUIRED: "FEISHU_LOGIN_REQUIRED",
|
|
9823
|
-
// 2026-07-02 unified people sidebar: contact:pin 时 deviceId 不在 store(可能被别处删了)
|
|
9824
9323
|
CONTACT_NOT_FOUND: "CONTACT_NOT_FOUND",
|
|
9825
9324
|
// 统一 RPC 鉴权(spec 2026-07-28):personaDispatch:get 查不到台账行,或 guest ctx
|
|
9826
9325
|
// 查非自己发起的行(越权与未知 id 同码,不泄露存在性)。A 端轮询遇此码立即判失败。
|
|
@@ -9861,7 +9360,6 @@ var init_cloud_volume_schemas = __esm({
|
|
|
9861
9360
|
"use strict";
|
|
9862
9361
|
init_zod();
|
|
9863
9362
|
CloudVolumeUsageSchema = external_exports.object({
|
|
9864
|
-
/** 已用字节(df 的 Used 列) */
|
|
9865
9363
|
usedBytes: external_exports.number().int().nonnegative(),
|
|
9866
9364
|
/** 盘的总字节(df 的 1B-blocks 列;ext4 预留之后比 PVC 的 5Gi 略小) */
|
|
9867
9365
|
sizeBytes: external_exports.number().int().positive(),
|
|
@@ -49830,7 +49328,6 @@ var ProjectsStore = class {
|
|
|
49830
49328
|
}
|
|
49831
49329
|
opts;
|
|
49832
49330
|
entries = [];
|
|
49833
|
-
/** 见 ProjectAnnotation;与 entries 一起持久化在 projects.json */
|
|
49834
49331
|
annotations = {};
|
|
49835
49332
|
load() {
|
|
49836
49333
|
let raw;
|
|
@@ -49871,13 +49368,8 @@ var ProjectsStore = class {
|
|
|
49871
49368
|
return [...this.entries];
|
|
49872
49369
|
}
|
|
49873
49370
|
/**
|
|
49874
|
-
*
|
|
49875
|
-
* Codex
|
|
49876
|
-
* (它的 name 是用户在 Codex 里改过的,更权威)。
|
|
49877
|
-
*
|
|
49878
|
-
* 本地叠加层承接「clawd 新建、Codex 还没收录」的 project —— clawd 写不了 Codex registry
|
|
49879
|
-
* (§5 R2:Desktop 无 watcher 且写入基于内存快照),新建的必须有地方落,否则自己列表里
|
|
49880
|
-
* 都看不见。
|
|
49371
|
+
* Merge by normalized path, applying path rules to both sources and the createdAt gate only to Codex.
|
|
49372
|
+
* Prefer Codex labels; keep Clawd-created entries locally because Codex state must remain read-only.
|
|
49881
49373
|
*/
|
|
49882
49374
|
listMerged() {
|
|
49883
49375
|
const byPath = /* @__PURE__ */ new Map();
|
|
@@ -49912,7 +49404,7 @@ var ProjectsStore = class {
|
|
|
49912
49404
|
(c2) => this.normalize(c2.path) === abs
|
|
49913
49405
|
);
|
|
49914
49406
|
}
|
|
49915
|
-
/**
|
|
49407
|
+
/** Set a local pin, or clear it with null; false if neither source contains the path. */
|
|
49916
49408
|
patch(rawPath, fields) {
|
|
49917
49409
|
const abs = this.normalize(rawPath);
|
|
49918
49410
|
const row = this.entries.find((e) => e.path === abs);
|
|
@@ -49927,7 +49419,7 @@ var ProjectsStore = class {
|
|
|
49927
49419
|
if (entry) this.save();
|
|
49928
49420
|
return entry;
|
|
49929
49421
|
}
|
|
49930
|
-
/** upsert
|
|
49422
|
+
/** In-memory upsert; return the existing entry unchanged to preserve idempotence. */
|
|
49931
49423
|
add(rawPath, name) {
|
|
49932
49424
|
const abs = this.normalize(rawPath);
|
|
49933
49425
|
if (!this.isProjectablePath(abs)) return null;
|
|
@@ -51085,13 +50577,7 @@ var SkillsScanner = class {
|
|
|
51085
50577
|
this.home = opts.home ?? import_node_os7.default.homedir();
|
|
51086
50578
|
this.extraPluginRoots = opts.extraPluginRoots ?? [];
|
|
51087
50579
|
}
|
|
51088
|
-
/**
|
|
51089
|
-
* 对齐 cc-direct(openclaw-plugin-clawos/src/claude-code/skills-scanner.ts):
|
|
51090
|
-
* 1. global: ~/.claude/skills + ~/.claude/commands
|
|
51091
|
-
* 2. project: <cwd>/.claude/skills + <cwd>/.claude/commands
|
|
51092
|
-
* 3. plugin: installed_plugins.json 里每个 plugin 的 skills + commands
|
|
51093
|
-
* 顺序 global → project → plugin,同名去重(先到先得)
|
|
51094
|
-
*/
|
|
50580
|
+
/** First-seen names win: builtins, global sources, project sources, then plugins. */
|
|
51095
50581
|
list(args) {
|
|
51096
50582
|
const seen = /* @__PURE__ */ new Set();
|
|
51097
50583
|
const builtinBlock = [];
|
|
@@ -53623,6 +53109,8 @@ function driveCodexThread(opts) {
|
|
|
53623
53109
|
})().catch((e) => sink.onError(`codex app-server init failed: ${e.message}`));
|
|
53624
53110
|
const session = {
|
|
53625
53111
|
startTurn(text) {
|
|
53112
|
+
const model = opts.turnModel?.();
|
|
53113
|
+
const modelSelected = opts.onTurnModelSelected?.();
|
|
53626
53114
|
void ready.then(() => {
|
|
53627
53115
|
if (threadId) {
|
|
53628
53116
|
const opened = !turnOpen;
|
|
@@ -53635,7 +53123,10 @@ function driveCodexThread(opts) {
|
|
|
53635
53123
|
threadId,
|
|
53636
53124
|
input: turnStartInput(text, opts.imageInput),
|
|
53637
53125
|
// Wire field is effort (v2 already used it before 0.138); unknown reasoningEffort is silently ignored.
|
|
53638
|
-
...effort ? { effort } : {}
|
|
53126
|
+
...effort ? { effort } : {},
|
|
53127
|
+
...model ? { model } : {}
|
|
53128
|
+
}).then(() => {
|
|
53129
|
+
if (model) modelSelected?.(model);
|
|
53639
53130
|
}).catch((e) => {
|
|
53640
53131
|
if (exited) return;
|
|
53641
53132
|
sink.pushEvents([
|
|
@@ -53825,7 +53316,6 @@ function warmCodexFeatureCache() {
|
|
|
53825
53316
|
var import_node_path21 = __toESM(require("path"), 1);
|
|
53826
53317
|
var TEXT_PLAIN = "text/plain; charset=utf-8";
|
|
53827
53318
|
var EXT_TO_NATIVE_MIME = {
|
|
53828
|
-
// 图片
|
|
53829
53319
|
".png": "image/png",
|
|
53830
53320
|
".jpg": "image/jpeg",
|
|
53831
53321
|
".jpeg": "image/jpeg",
|
|
@@ -53835,11 +53325,9 @@ var EXT_TO_NATIVE_MIME = {
|
|
|
53835
53325
|
".bmp": "image/bmp",
|
|
53836
53326
|
".ico": "image/x-icon",
|
|
53837
53327
|
".avif": "image/avif",
|
|
53838
|
-
// 文档 / 富文本
|
|
53839
53328
|
".pdf": "application/pdf",
|
|
53840
53329
|
".html": "text/html; charset=utf-8",
|
|
53841
53330
|
".htm": "text/html; charset=utf-8",
|
|
53842
|
-
// 视频 / 音频
|
|
53843
53331
|
".mp4": "video/mp4",
|
|
53844
53332
|
".webm": "video/webm",
|
|
53845
53333
|
".mov": "video/quicktime",
|
|
@@ -53847,7 +53335,7 @@ var EXT_TO_NATIVE_MIME = {
|
|
|
53847
53335
|
".wav": "audio/wav",
|
|
53848
53336
|
".ogg": "audio/ogg",
|
|
53849
53337
|
".flac": "audio/flac",
|
|
53850
|
-
//
|
|
53338
|
+
// Binary formats must not be served as plain text.
|
|
53851
53339
|
".zip": "application/zip",
|
|
53852
53340
|
".gz": "application/gzip",
|
|
53853
53341
|
".tar": "application/x-tar",
|
|
@@ -53855,14 +53343,12 @@ var EXT_TO_NATIVE_MIME = {
|
|
|
53855
53343
|
".rar": "application/x-rar-compressed"
|
|
53856
53344
|
};
|
|
53857
53345
|
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
53858
|
-
// 文档
|
|
53859
53346
|
".md",
|
|
53860
53347
|
".markdown",
|
|
53861
53348
|
".rst",
|
|
53862
53349
|
".adoc",
|
|
53863
53350
|
".txt",
|
|
53864
53351
|
".log",
|
|
53865
|
-
// 通用结构化
|
|
53866
53352
|
".json",
|
|
53867
53353
|
".jsonc",
|
|
53868
53354
|
".json5",
|
|
@@ -53876,7 +53362,6 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
53876
53362
|
".conf",
|
|
53877
53363
|
".cfg",
|
|
53878
53364
|
".env",
|
|
53879
|
-
// 前端
|
|
53880
53365
|
".js",
|
|
53881
53366
|
".jsx",
|
|
53882
53367
|
".mjs",
|
|
@@ -53891,7 +53376,6 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
53891
53376
|
".svelte",
|
|
53892
53377
|
".graphql",
|
|
53893
53378
|
".gql",
|
|
53894
|
-
// 后端 / 各语言
|
|
53895
53379
|
".py",
|
|
53896
53380
|
".rb",
|
|
53897
53381
|
".go",
|
|
@@ -53919,7 +53403,6 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
53919
53403
|
".bash",
|
|
53920
53404
|
".zsh",
|
|
53921
53405
|
".fish",
|
|
53922
|
-
// 其他
|
|
53923
53406
|
".sql",
|
|
53924
53407
|
".proto",
|
|
53925
53408
|
".dockerfile",
|
|
@@ -61790,7 +61273,7 @@ function buildDeployHandlers(deps) {
|
|
|
61790
61273
|
const released = file.releases.at(-1);
|
|
61791
61274
|
const releasedEntry = released ? file.scans.find((s2) => s2.id === released.scanId) : void 0;
|
|
61792
61275
|
const scanBase = pickScanBase({
|
|
61793
|
-
//
|
|
61276
|
+
// A released record may contain an incomplete manifest if its scan was interrupted.
|
|
61794
61277
|
hasUsableManifest: releasedEntry?.status === "done",
|
|
61795
61278
|
releasedHead: await hasCommit(deps.exec, dir, released?.head) ? released?.head : void 0,
|
|
61796
61279
|
...want ? { want } : {}
|
|
@@ -61805,7 +61288,7 @@ function buildDeployHandlers(deps) {
|
|
|
61805
61288
|
status: "scanning",
|
|
61806
61289
|
manifest: { ...seed, connectors: mcp.readable ? mergeScannedConnectors(seed.connectors, mcp.connectors) : seed.connectors },
|
|
61807
61290
|
evidence: [...carriedEvidence.filter((e) => !mcpTargets.has(e.target)), ...mcp.evidence.map(redactEvidence)],
|
|
61808
|
-
// notes
|
|
61291
|
+
// Notes describe this scan's unresolved work; do not carry old notes into a new scan.
|
|
61809
61292
|
notes: mcp.warnings.map((w3) => `${MCP_SCAN_NOTE_PREFIX}${w3}`),
|
|
61810
61293
|
startedAt: deps.now(),
|
|
61811
61294
|
head: contentHead,
|
|
@@ -62000,8 +61483,7 @@ function buildDeployHandlers(deps) {
|
|
|
62000
61483
|
...scan,
|
|
62001
61484
|
status: "done",
|
|
62002
61485
|
finishedAt: deps.now(),
|
|
62003
|
-
//
|
|
62004
|
-
//(带 MCP_SCAN_NOTE_PREFIX)留着——butler 不知道那些,它的那份不该把它们冲掉
|
|
61486
|
+
// Redact free-text notes and preserve scanner warnings that the butler does not know about.
|
|
62005
61487
|
notes: [...scan.notes.filter((n2) => n2.startsWith(MCP_SCAN_NOTE_PREFIX)), ...args.notes.map(redactSecretValues)],
|
|
62006
61488
|
error: void 0
|
|
62007
61489
|
}));
|
|
@@ -63100,7 +62582,6 @@ function buildLarkBotHandlers(deps) {
|
|
|
63100
62582
|
state: "bound",
|
|
63101
62583
|
appId: meta.appId,
|
|
63102
62584
|
botName: meta.botName,
|
|
63103
|
-
// 取消了就是没升成,提示条留着
|
|
63104
62585
|
needsUpgrade: true,
|
|
63105
62586
|
groups: groupsOf(personaId2)
|
|
63106
62587
|
} : { state: "unbound", groups: [] }
|
|
@@ -63204,7 +62685,7 @@ function buildLarkBotHandlers(deps) {
|
|
|
63204
62685
|
botName: cloudBinding.botName,
|
|
63205
62686
|
...upgradingQr ? { needsUpgrade: true, upgradeQrUrl: upgradingQr } : {},
|
|
63206
62687
|
...broken2 ? { brokenReason: cloudBinding.brokenReason ?? "unknown" } : {},
|
|
63207
|
-
//
|
|
62688
|
+
// The cloud reads granted scopes; forward its upgrade decision.
|
|
63208
62689
|
...cloudBinding.needsUpgrade ? { needsUpgrade: true } : {},
|
|
63209
62690
|
groups: groupsOf(personaId2)
|
|
63210
62691
|
};
|
|
@@ -63234,7 +62715,7 @@ function buildLarkBotHandlers(deps) {
|
|
|
63234
62715
|
botName: cloudBinding?.botName ?? meta.botName,
|
|
63235
62716
|
...upgradingQr ? { needsUpgrade: true, upgradeQrUrl: upgradingQr } : {},
|
|
63236
62717
|
...broken ? { brokenReason: cloudBinding?.brokenReason ?? "unknown" } : {},
|
|
63237
|
-
//
|
|
62718
|
+
// Without cloud scope facts, avoid guessing that authorization needs upgrading.
|
|
63238
62719
|
...cloudBinding?.needsUpgrade ? { needsUpgrade: true } : {},
|
|
63239
62720
|
groups: groupsOf(personaId2)
|
|
63240
62721
|
};
|
|
@@ -63822,7 +63303,6 @@ ${formatLarkSpeakerReminder(speaker)}`
|
|
|
63822
63303
|
appId: last.appId,
|
|
63823
63304
|
// 回复走真身:chatId 自 spec 2026-09-10 起是虚拟三段式,只作会话 key 用
|
|
63824
63305
|
chatId: last.realChatId ?? last.chatId,
|
|
63825
|
-
// envs 已过滤保证锚点非空
|
|
63826
63306
|
replyToMessageId: replyAnchorOf(last),
|
|
63827
63307
|
text: replyText,
|
|
63828
63308
|
eventId: last.eventId,
|
|
@@ -64116,12 +63596,10 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
64116
63596
|
args.dispatchId,
|
|
64117
63597
|
args.prompt,
|
|
64118
63598
|
nextRound,
|
|
64119
|
-
//
|
|
64120
|
-
// sourcePersonaId 有写入点之前落的,那些行只能靠现查补上。
|
|
63599
|
+
// Legacy ledger rows may lack attribution; fall back to a current session lookup.
|
|
64121
63600
|
rec6.sourcePersonaId ?? deps.getSessionPersonaId?.(sourceSessionId)
|
|
64122
63601
|
),
|
|
64123
|
-
//
|
|
64124
|
-
// history 侧靠 classifyUserMessage 从信封解出同值。
|
|
63602
|
+
// Realtime metadata and history envelope classification both use dispatch-task.
|
|
64125
63603
|
"dispatch-task"
|
|
64126
63604
|
);
|
|
64127
63605
|
if (!ok) {
|
|
@@ -64186,8 +63664,6 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
64186
63664
|
sourceSessionId,
|
|
64187
63665
|
targetPersona,
|
|
64188
63666
|
...sourcePersonaId ? { sourcePersonaId } : {},
|
|
64189
|
-
// A 端台账记「我派给了谁的机器」:worker 会话在对端,本机点不进去,卡片的归属带
|
|
64190
|
-
// 靠这个字段说出「在哪台机器上」而不是干瞪眼(design §4 跨设备那一行)。
|
|
64191
63667
|
targetDeviceId: args.targetDeviceId,
|
|
64192
63668
|
taskText: args.prompt,
|
|
64193
63669
|
...args.brief ? { taskBrief: args.brief } : {},
|
|
@@ -64241,11 +63717,8 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
64241
63717
|
...args.brief ? { brief: args.brief } : {},
|
|
64242
63718
|
guestPrincipalId: ctx.principal.id,
|
|
64243
63719
|
guestDisplayName: ctx.principal.displayName,
|
|
64244
|
-
// T-31: 把 A 的飞书 union_id 透传给 B(connectGuestContext 已把它挂在 principal.feishuUnionId)
|
|
64245
63720
|
...ctx.principal.feishuUnionId ? { guestFeishuUnionId: ctx.principal.feishuUnionId } : {},
|
|
64246
63721
|
...args.model ? { model: args.model } : {},
|
|
64247
|
-
// butler 顶部入口:peer daemon 若在 forwardToPeer 里传了 sessionLabel(当前
|
|
64248
|
-
// 未接线,作 forward-compat 预留),这里也透给 spawnB
|
|
64249
63722
|
...args.sessionLabel ? { label: args.sessionLabel } : {}
|
|
64250
63723
|
}).then(() => logger?.info("dispatch.spawnB.ok", { dispatchId: dispatchId2 })).catch((err) => {
|
|
64251
63724
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -64262,8 +63735,6 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
64262
63735
|
const { dispatchId } = mgr.start({
|
|
64263
63736
|
sourceSessionId,
|
|
64264
63737
|
targetPersona,
|
|
64265
|
-
// 「这活谁派的」的真源。schema 里早就有这个字段,本次之前没有任何写入点——
|
|
64266
|
-
// 于是 worker 会话的 dispatch 卡片永远查不到派活方(归属带只能退回信封)。
|
|
64267
63738
|
...sourcePersonaId ? { sourcePersonaId } : {},
|
|
64268
63739
|
taskText: args.prompt,
|
|
64269
63740
|
...args.brief ? { taskBrief: args.brief } : {},
|
|
@@ -64282,9 +63753,7 @@ function buildPersonaDispatchHandlers(deps) {
|
|
|
64282
63753
|
prompt: args.prompt,
|
|
64283
63754
|
...args.brief ? { brief: args.brief } : {},
|
|
64284
63755
|
...args.model ? { model: args.model } : {},
|
|
64285
|
-
// 一次性 engine override(spec 2026-08-24):只有本机 owner 入口能带(guest 已在上方拒)
|
|
64286
63756
|
...args.tool ? { tool: args.tool } : {},
|
|
64287
|
-
// butler 顶部入口(spec 2026-07-27):sessionLabel 落到 SessionFile.label 让 sidebar 显示
|
|
64288
63757
|
...args.sessionLabel ? { label: args.sessionLabel } : {}
|
|
64289
63758
|
}).then(() => {
|
|
64290
63759
|
logger?.info("dispatch.spawnB.ok", { dispatchId });
|
|
@@ -64527,9 +63996,7 @@ var GATEWAY_ROUTES = {
|
|
|
64527
63996
|
turn: (personaId2) => `/p/${encodeURIComponent(personaId2)}/v1/chat/completions`,
|
|
64528
63997
|
turnCommand: (personaId2, turnId) => `/p/${encodeURIComponent(personaId2)}/turns/${encodeURIComponent(turnId)}/commands`,
|
|
64529
63998
|
conversationCancel: (personaId2, conversationId) => `/p/${encodeURIComponent(personaId2)}/conversations/${encodeURIComponent(conversationId)}:cancel`,
|
|
64530
|
-
// 调用方自己那块盘的用量(design 2026-09-19-cloud-volume-usage §3.3):同样按人、不挂 persona
|
|
64531
63999
|
volume: () => "/me/volume",
|
|
64532
|
-
// 云 persona 运行看板的数据(design 2026-09-23-cloud-persona-metrics §3.6):只给发布者与管理员(gateway 问中心,design 2026-09-24-cloud-persona-conversations §3.2)
|
|
64533
64000
|
metrics: (personaId2) => `/metrics/${encodeURIComponent(personaId2)}`
|
|
64534
64001
|
};
|
|
64535
64002
|
var METRICS_TIMEOUT_MS = 3e4;
|
|
@@ -64562,11 +64029,7 @@ var CloudGatewayClient = class {
|
|
|
64562
64029
|
}
|
|
64563
64030
|
deps;
|
|
64564
64031
|
fetchImpl;
|
|
64565
|
-
/**
|
|
64566
|
-
* gateway 在哪 + 这个人有没有 key。要登录、要中心配了地址;**不要求有 key**——
|
|
64567
|
-
* 登记表是全局的、/healthz 不看票,列表只需要地址。key 到开会话才要(见 credentials)。
|
|
64568
|
-
* 曾把两样绑在一起:没生成 key 的用户连列表都看不到,也就永远不知道要去设置页生成一把(2026-09-16 修)。
|
|
64569
|
-
*/
|
|
64032
|
+
/** Listing requires login and a gateway address, not an issued key; only session calls require the key. */
|
|
64570
64033
|
async locate() {
|
|
64571
64034
|
const loginPass = this.deps.getLoginPass();
|
|
64572
64035
|
if (!loginPass) throw new ClawdError(ERROR_CODES.FEISHU_LOGIN_REQUIRED, "\u8FD8\u6CA1\u767B\u5F55\u98DE\u4E66\uFF0C\u4E91\u4E0A persona \u7528\u4E0D\u4E86");
|
|
@@ -64580,15 +64043,11 @@ var CloudGatewayClient = class {
|
|
|
64580
64043
|
if (!apiKey) throw new CloudApiKeyRequiredError();
|
|
64581
64044
|
return { apiKey, gatewayUrl };
|
|
64582
64045
|
}
|
|
64583
|
-
/**
|
|
64584
|
-
* `healthzTimeoutMs`:只给 /healthz 那一趟定上限,**问到地址之后才开始计**(上云页的线上状态用;design 2026-09-24-deploy-unpublish)。
|
|
64585
|
-
* 中心那一跳(locate)有它自己的上限,不归这里管。不给 = 同以前,不设
|
|
64586
|
-
*/
|
|
64046
|
+
/** Health timeout starts after address lookup; the central request has its own deadline. */
|
|
64587
64047
|
async listPersonas(opts = {}) {
|
|
64588
64048
|
const { gatewayUrl } = await this.locate();
|
|
64589
64049
|
return this.fetchDeployments(gatewayUrl, opts.healthzTimeoutMs ? AbortSignal.timeout(opts.healthzTimeoutMs) : void 0);
|
|
64590
64050
|
}
|
|
64591
|
-
/** /healthz 的登记表 → official 行。gatewayUrl 由调用方定:listPersonas 从中心拿,personaConnectors 用 persona 文件里的 */
|
|
64592
64051
|
async fetchDeployments(gatewayUrl, signal) {
|
|
64593
64052
|
const res = await this.fetchImpl(gatewayUrl + GATEWAY_ROUTES.healthz(), ...signal ? [{ signal }] : []);
|
|
64594
64053
|
if (!res.ok) throw new Error(`gateway /healthz \u56DE ${res.status}`);
|
|
@@ -64610,17 +64069,12 @@ var CloudGatewayClient = class {
|
|
|
64610
64069
|
...typeof d2.model === "string" && d2.model ? { model: d2.model } : {},
|
|
64611
64070
|
...typeof d2.effort === "string" && d2.effort ? { effort: d2.effort } : {},
|
|
64612
64071
|
...typeof d2.registeredAt === "number" ? { registeredAt: d2.registeredAt } : {},
|
|
64613
|
-
// 形状不对的条目丢掉不抛:老 gateway 的登记行是名字数组,那种就是「没有连接器」
|
|
64614
64072
|
...Array.isArray(d2.connectors) ? { connectors: d2.connectors.map((c2) => EnvConnectorSchema.safeParse(c2)).flatMap((p3) => p3.success ? [p3.data] : []) } : {}
|
|
64615
64073
|
});
|
|
64616
64074
|
}
|
|
64617
64075
|
return out;
|
|
64618
64076
|
}
|
|
64619
|
-
/**
|
|
64620
|
-
* gateway 登记行上这个 persona(gateway 名)装了哪些连接器;没这行 = 空。
|
|
64621
|
-
* gatewayUrl 从 persona 文件的 cloud.gatewayUrl 来,**不经 locate()**:那一跳只为拿同一个地址,
|
|
64622
|
-
* 远端网络下白等 ~1s(design 2026-09-19-cloud-connectors-skip-locate)。/healthz 不看票,这里也不要求登录。
|
|
64623
|
-
*/
|
|
64077
|
+
/** Read connectors from the persona gateway address, avoiding a redundant central lookup. */
|
|
64624
64078
|
async personaConnectors(gatewayPersonaId, gatewayUrl) {
|
|
64625
64079
|
const list = await this.fetchDeployments(trimSlash(gatewayUrl));
|
|
64626
64080
|
return list.find((p3) => p3.personaId === gatewayPersonaId)?.connectors ?? [];
|
|
@@ -64651,11 +64105,7 @@ var CloudGatewayClient = class {
|
|
|
64651
64105
|
});
|
|
64652
64106
|
if (!res.ok) throw new Error(`gateway conversation cancel returned ${res.status}`);
|
|
64653
64107
|
}
|
|
64654
|
-
/**
|
|
64655
|
-
* 看板数据(design 2026-09-23-cloud-persona-metrics §3.8):GET /metrics/<persona>?window&entry,**原样回**状态码与 body——
|
|
64656
|
-
* 形状真源在 gateway 的 cloud-persona-metrics,daemon 只是替浏览器拿本机 api key 转一跳。拿不到 key / 连不上 / 超时 → 抛(路由折成 502)。
|
|
64657
|
-
* 超时是必须的:页面上一次没回来就不发下一次刷新,gateway 挂住不回会让页面永远停在旧数
|
|
64658
|
-
*/
|
|
64108
|
+
/** Proxy dashboard status/body with in-memory credentials. Bound request time so polling cannot hang indefinitely. */
|
|
64659
64109
|
async fetchMetrics(q3) {
|
|
64660
64110
|
const { apiKey, gatewayUrl } = await this.credentials();
|
|
64661
64111
|
const qs = new URLSearchParams();
|
|
@@ -64691,10 +64141,6 @@ var CloudGatewayClient = class {
|
|
|
64691
64141
|
throw e;
|
|
64692
64142
|
}
|
|
64693
64143
|
}
|
|
64694
|
-
/**
|
|
64695
|
-
* 这个人云盘上一轮末的用量(design 2026-09-19-cloud-volume-usage §3.3)。gateway 回 `volume: null` = 还没跑过任务(没有盘)。
|
|
64696
|
-
* 没登录 / 没 key 走 credentials() 的抛法,调用方(cloud:volume:get 的 handler)把它们和网络错一并折成 null——角标不该弹错。
|
|
64697
|
-
*/
|
|
64698
64144
|
async fetchVolume() {
|
|
64699
64145
|
const { apiKey, gatewayUrl } = await this.credentials();
|
|
64700
64146
|
const res = await this.fetchImpl(gatewayUrl + GATEWAY_ROUTES.volume(), {
|
|
@@ -66865,9 +66311,7 @@ var LocalWsServer = class {
|
|
|
66865
66311
|
httpServer = null;
|
|
66866
66312
|
frameHandler = null;
|
|
66867
66313
|
clients = /* @__PURE__ */ new Map();
|
|
66868
|
-
//
|
|
66869
|
-
// 同一 guest 可能多端同时连(多设备 / 多 tab)。
|
|
66870
|
-
// Phase 3 (决策 #14): 索引键从 ctx.capabilityId 改为 guest principal.id(两者恒等,纯正名)。
|
|
66314
|
+
// Guest principal ID indexes all of its active connections for revocation.
|
|
66871
66315
|
guestIdToClients = /* @__PURE__ */ new Map();
|
|
66872
66316
|
logger;
|
|
66873
66317
|
pingIntervalMs;
|
|
@@ -66913,7 +66357,7 @@ var LocalWsServer = class {
|
|
|
66913
66357
|
this.wss = null;
|
|
66914
66358
|
this.httpServer = null;
|
|
66915
66359
|
}
|
|
66916
|
-
/**
|
|
66360
|
+
/** Let the injected HTTP router respond, otherwise return 404. */
|
|
66917
66361
|
async handleHttpRequest(req, res) {
|
|
66918
66362
|
const handler = this.opts.httpRequestHandler;
|
|
66919
66363
|
if (handler) {
|
|
@@ -66959,9 +66403,7 @@ var LocalWsServer = class {
|
|
|
66959
66403
|
this.safeSend(c2.ws, frame);
|
|
66960
66404
|
}
|
|
66961
66405
|
}
|
|
66962
|
-
//
|
|
66963
|
-
// contact:pinned / friend:reverseTokenOffered 等
|
|
66964
|
-
// owner-only push frame——guest 无需消费。noAuth localhost ws 无 ctx,视作 owner。
|
|
66406
|
+
// Owner-only pushes exclude guests; null-context exempt connections use the owner fallback.
|
|
66965
66407
|
broadcastToOwners(frame) {
|
|
66966
66408
|
const gate = this.opts.authGate;
|
|
66967
66409
|
for (const c2 of this.clients.values()) {
|
|
@@ -66976,7 +66418,7 @@ var LocalWsServer = class {
|
|
|
66976
66418
|
}
|
|
66977
66419
|
return null;
|
|
66978
66420
|
}
|
|
66979
|
-
// AuthGate
|
|
66421
|
+
// AuthGate closes clients by ID.
|
|
66980
66422
|
closeClient(clientId, code, reason) {
|
|
66981
66423
|
const c2 = this.clients.get(clientId);
|
|
66982
66424
|
if (!c2) return;
|
|
@@ -66991,8 +66433,7 @@ var LocalWsServer = class {
|
|
|
66991
66433
|
if (!c2) return;
|
|
66992
66434
|
this.safeSend(c2.ws, frame);
|
|
66993
66435
|
}
|
|
66994
|
-
//
|
|
66995
|
-
// 绑到 client;guest 连接同时进 guest 索引(撤销级联用)。
|
|
66436
|
+
// Bind the authenticated context and index guests for revocation.
|
|
66996
66437
|
attachClientContext(clientId, ctx) {
|
|
66997
66438
|
const c2 = this.clients.get(clientId);
|
|
66998
66439
|
if (!c2) return;
|
|
@@ -67016,14 +66457,12 @@ var LocalWsServer = class {
|
|
|
67016
66457
|
set.add(clientId);
|
|
67017
66458
|
}
|
|
67018
66459
|
}
|
|
67019
|
-
//
|
|
66460
|
+
// null means unbound context, including exempt owner connections.
|
|
67020
66461
|
getClientContext(clientId) {
|
|
67021
66462
|
return this.clients.get(clientId)?.ctx ?? null;
|
|
67022
66463
|
}
|
|
67023
|
-
//
|
|
67024
|
-
//
|
|
67025
|
-
// close code 4408(非 4401!4401 = token 撤销,客户端会停止重连):
|
|
67026
|
-
// 客户端按普通断连处理 → 自动重连 → 新身份 ctx。
|
|
66464
|
+
// Identity changes require reconnecting to rebuild bound contexts. Use 4408: clients reconnect,
|
|
66465
|
+
// whereas 4401 is an auth rejection that stops reconnection.
|
|
67027
66466
|
closeAllClients(code = 4408, reason = "IDENTITY_SWITCHED") {
|
|
67028
66467
|
const ids = [...this.clients.keys()];
|
|
67029
66468
|
for (const id of ids) {
|
|
@@ -67216,14 +66655,10 @@ var LocalWsServer = class {
|
|
|
67216
66655
|
this.logger?.warn("safeSend failed", { err: err.message });
|
|
67217
66656
|
}
|
|
67218
66657
|
}
|
|
67219
|
-
// 测试辅助
|
|
67220
66658
|
clientCount() {
|
|
67221
66659
|
return this.clients.size;
|
|
67222
66660
|
}
|
|
67223
|
-
//
|
|
67224
|
-
// principal.kind==='guest'(connect/visitor 票种)不列——不是"我的设备",也不向 UI
|
|
67225
|
-
// 泄露 guest 信息;ctx===null 是 noAuth localhost 路径(dispatcher 按 owner fallback
|
|
67226
|
-
// 处理),视作 owner 列入。
|
|
66661
|
+
// Exclude guest contexts from owner connection snapshots. Null contexts follow the exempt owner fallback.
|
|
67227
66662
|
listOwnerConnections() {
|
|
67228
66663
|
const out = [];
|
|
67229
66664
|
for (const [id, c2] of this.clients) {
|
|
@@ -67280,12 +66715,8 @@ var AuthGate = class {
|
|
|
67280
66715
|
isAuthed(handleId) {
|
|
67281
66716
|
return this.states.get(handleId)?.authed ?? false;
|
|
67282
66717
|
}
|
|
67283
|
-
//
|
|
67284
|
-
//
|
|
67285
|
-
// - reject:未通过 auth 但帧不是 auth 帧,调用方应放弃此帧(gate 已关连接 / 验证进行中)
|
|
67286
|
-
// - pass:已 authed,调用方继续走业务路由
|
|
67287
|
-
//
|
|
67288
|
-
// Phase 2:async(authenticate 注入路径走中心 server introspect HTTP 调用)。
|
|
66718
|
+
// consumed: auth frame handled; reject: discard the frame; pass: continue business routing.
|
|
66719
|
+
// Pending authentication rejects extra frames without closing the connection.
|
|
67289
66720
|
async handleFrame(handle, frame) {
|
|
67290
66721
|
const st2 = this.states.get(handle.id);
|
|
67291
66722
|
if (!st2) return "reject";
|
|
@@ -67371,10 +66802,7 @@ var AuthContextResolver = class {
|
|
|
67371
66802
|
this.opts = opts;
|
|
67372
66803
|
}
|
|
67373
66804
|
opts;
|
|
67374
|
-
/**
|
|
67375
|
-
* 从 `Authorization` 头解析。null = 无凭证 / 不识别,调用方一律 401。
|
|
67376
|
-
* remoteAddress 用于 isLoopback 判定(loopback = 127.0.0.1 / ::1 / ::ffff:127.0.0.1)。
|
|
67377
|
-
*/
|
|
66805
|
+
/** Returns null for missing or unrecognized Bearer credentials; callers respond with 401. */
|
|
67378
66806
|
resolveFromHeader(authHeader, remoteAddress) {
|
|
67379
66807
|
const token = parseBearer(authHeader);
|
|
67380
66808
|
if (!token) return null;
|
|
@@ -67705,7 +67133,7 @@ var ContactStore = class {
|
|
|
67705
67133
|
has(deviceId) {
|
|
67706
67134
|
return this.contacts.has(deviceId);
|
|
67707
67135
|
}
|
|
67708
|
-
/**
|
|
67136
|
+
/** Person-level display/grouping view; routing remains device-based. */
|
|
67709
67137
|
findByOwner(ownerId, provider) {
|
|
67710
67138
|
return this.list().filter((c2) => c2.ownerId === ownerId && c2.provider === provider);
|
|
67711
67139
|
}
|
|
@@ -67713,17 +67141,13 @@ var ContactStore = class {
|
|
|
67713
67141
|
this.contacts.set(contact.deviceId, contact);
|
|
67714
67142
|
this.flush();
|
|
67715
67143
|
}
|
|
67716
|
-
/**
|
|
67144
|
+
/** Remove one device and flush if found; return whether it existed. */
|
|
67717
67145
|
removeByDeviceId(deviceId) {
|
|
67718
67146
|
const existed = this.contacts.delete(deviceId);
|
|
67719
67147
|
if (existed) this.flush();
|
|
67720
67148
|
return existed;
|
|
67721
67149
|
}
|
|
67722
|
-
/**
|
|
67723
|
-
* 更新单条 contact 的 pinnedAt(对齐 session:pin pattern,2026-07-02 unified people sidebar)。
|
|
67724
|
-
* @param pinnedAt null = 未 pin;number = pin 时间戳(daemon 侧 Date.now())
|
|
67725
|
-
* @returns 是否命中:deviceId 不存在返 false,调用方按需 error/broadcast;命中即 flush。
|
|
67726
|
-
*/
|
|
67150
|
+
/** Set the pin timestamp, or unpin with null; flush if found, otherwise return false. */
|
|
67727
67151
|
setPin(deviceId, pinnedAt) {
|
|
67728
67152
|
const existing = this.contacts.get(deviceId);
|
|
67729
67153
|
if (!existing) return false;
|
|
@@ -67731,10 +67155,7 @@ var ContactStore = class {
|
|
|
67731
67155
|
this.flush();
|
|
67732
67156
|
return true;
|
|
67733
67157
|
}
|
|
67734
|
-
/**
|
|
67735
|
-
* 更新单条 contact 的远程访问授权开关(对齐 setPin pattern)。
|
|
67736
|
-
* @returns 是否命中:deviceId 不存在返 false;命中即 flush.
|
|
67737
|
-
*/
|
|
67158
|
+
/** Update access authorization and flush if found; otherwise return false. */
|
|
67738
67159
|
setRemoteAccess(deviceId, opts) {
|
|
67739
67160
|
const existing = this.contacts.get(deviceId);
|
|
67740
67161
|
if (!existing) return false;
|
|
@@ -67745,11 +67166,7 @@ var ContactStore = class {
|
|
|
67745
67166
|
this.flush();
|
|
67746
67167
|
return true;
|
|
67747
67168
|
}
|
|
67748
|
-
/**
|
|
67749
|
-
* 更新单条 contact 的备注(2026-08-31,对齐 setPin pattern)。
|
|
67750
|
-
* @param note 空串 = 清空备注
|
|
67751
|
-
* @returns 是否命中:deviceId 不存在返 false;命中即 flush.
|
|
67752
|
-
*/
|
|
67169
|
+
/** Set the local note (empty string clears it); flush if found, otherwise return false. */
|
|
67753
67170
|
setNote(deviceId, note) {
|
|
67754
67171
|
const existing = this.contacts.get(deviceId);
|
|
67755
67172
|
if (!existing) return false;
|
|
@@ -68100,7 +67517,6 @@ async function writeUploadedAttachment(args) {
|
|
|
68100
67517
|
const relPath = absPath;
|
|
68101
67518
|
args.groupFileStore.upsert(args.scope, args.sessionId, {
|
|
68102
67519
|
relPath: absPath,
|
|
68103
|
-
// 存绝对路径,与现有 agent 入清单的形态一致(attachment.ts:144 双形态兼容)
|
|
68104
67520
|
from: "user-upload",
|
|
68105
67521
|
label: args.fileName,
|
|
68106
67522
|
size: actualSize,
|
|
@@ -68796,7 +68212,7 @@ function streamFile(res, absPath, logger) {
|
|
|
68796
68212
|
"Content-Type": mime,
|
|
68797
68213
|
"Content-Length": String(stat.size),
|
|
68798
68214
|
"Content-Disposition": `inline; filename*=UTF-8''${encodeURIComponent(basename2)}`,
|
|
68799
|
-
//
|
|
68215
|
+
// Prevent MIME sniffing; explicit HTML MIME remains renderable.
|
|
68800
68216
|
"X-Content-Type-Options": "nosniff"
|
|
68801
68217
|
});
|
|
68802
68218
|
const stream = import_node_fs48.default.createReadStream(absPath);
|
|
@@ -68900,14 +68316,12 @@ var GroupFileStore = class {
|
|
|
68900
68316
|
rootForScope(scope) {
|
|
68901
68317
|
return import_node_path55.default.join(this.dataDir, "sessions", ...scopeSubPath(scope).map(safeFileName));
|
|
68902
68318
|
}
|
|
68903
|
-
/** 与 SessionStore.filePath 平级,扩展名 .group-files.json */
|
|
68904
68319
|
filePath(scope, sessionId) {
|
|
68905
68320
|
return import_node_path55.default.join(this.rootForScope(scope), `${safeFileName(sessionId)}.group-files.json`);
|
|
68906
68321
|
}
|
|
68907
68322
|
cacheKey(scope, sessionId) {
|
|
68908
68323
|
return scope.kind === "default" ? `default::${sessionId}` : `persona:${scope.personaId}:${scope.mode}::${sessionId}`;
|
|
68909
68324
|
}
|
|
68910
|
-
/** 从磁盘读一份;不存在 → 空数组;schema 不匹配的条目 → 跳过(防腐) */
|
|
68911
68325
|
readFile(scope, sessionId) {
|
|
68912
68326
|
const file = this.filePath(scope, sessionId);
|
|
68913
68327
|
try {
|
|
@@ -68942,7 +68356,6 @@ var GroupFileStore = class {
|
|
|
68942
68356
|
import_node_fs50.default.writeFileSync(tmp, JSON.stringify(entries, null, 2), { mode: 384 });
|
|
68943
68357
|
import_node_fs50.default.renameSync(tmp, file);
|
|
68944
68358
|
}
|
|
68945
|
-
/** 拉一份当前 session 的清单。读盘 → cache;之后调用复用 cache */
|
|
68946
68359
|
list(scope, sessionId) {
|
|
68947
68360
|
const key = this.cacheKey(scope, sessionId);
|
|
68948
68361
|
const cached = this.cache.get(key);
|
|
@@ -68951,14 +68364,7 @@ var GroupFileStore = class {
|
|
|
68951
68364
|
this.cache.set(key, { entries });
|
|
68952
68365
|
return entries;
|
|
68953
68366
|
}
|
|
68954
|
-
/**
|
|
68955
|
-
* upsert:
|
|
68956
|
-
* - 同 relPath 已存在 → 更新 lastEditedAt + clear stale;不动 from / addedAt / id
|
|
68957
|
-
* (保留首次入群人 / 入群时刻)
|
|
68958
|
-
* - 不存在 → append,id 派生稳定 uuid,addedAt = now
|
|
68959
|
-
*
|
|
68960
|
-
* 返回最新 entry(caller 可用来 broadcast 通知)。
|
|
68961
|
-
*/
|
|
68367
|
+
/** Existing paths retain their id, original contributor, label and addedAt; edits update lastEditedAt and clear stale. */
|
|
68962
68368
|
upsert(scope, sessionId, input, now = Date.now()) {
|
|
68963
68369
|
const entries = this.list(scope, sessionId).slice();
|
|
68964
68370
|
const idx = entries.findIndex((e) => e.relPath === input.relPath);
|
|
@@ -68971,7 +68377,6 @@ var GroupFileStore = class {
|
|
|
68971
68377
|
mime: input.mime,
|
|
68972
68378
|
lastEditedAt: now,
|
|
68973
68379
|
stale: false
|
|
68974
|
-
// label 不在 upsert 路径覆盖(owner +Add 走另一条路径)
|
|
68975
68380
|
};
|
|
68976
68381
|
entries[idx] = next;
|
|
68977
68382
|
} else {
|
|
@@ -68983,7 +68388,6 @@ var GroupFileStore = class {
|
|
|
68983
68388
|
size: input.size,
|
|
68984
68389
|
mime: input.mime,
|
|
68985
68390
|
addedAt: now
|
|
68986
|
-
// agent 第一次 upsert 时不写 lastEditedAt(语义上 = 首次"编辑" = addedAt)
|
|
68987
68391
|
};
|
|
68988
68392
|
entries.push(next);
|
|
68989
68393
|
}
|
|
@@ -68991,13 +68395,7 @@ var GroupFileStore = class {
|
|
|
68991
68395
|
this.cache.set(this.cacheKey(scope, sessionId), { entries });
|
|
68992
68396
|
return next;
|
|
68993
68397
|
}
|
|
68994
|
-
/**
|
|
68995
|
-
* 标记一个 relPath stale(agent rm / mv 后文件不在)。
|
|
68996
|
-
* - 命中 → stale=true,UI 灰显
|
|
68997
|
-
* - 未命中 → noop(不需要为不在群里的文件创建 stale 条目)
|
|
68998
|
-
*
|
|
68999
|
-
* 注:spec §6 "Bash 命令是 rm 形态"启发式不强求 — runner 暂不调,留给后续优化
|
|
69000
|
-
*/
|
|
68398
|
+
/** Mark missing files stale without creating an entry. Removal of automatically added files should retain this placeholder. */
|
|
69001
68399
|
markStale(scope, sessionId, relPath) {
|
|
69002
68400
|
const entries = this.list(scope, sessionId).slice();
|
|
69003
68401
|
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
@@ -69007,13 +68405,7 @@ var GroupFileStore = class {
|
|
|
69007
68405
|
this.writeFile(scope, sessionId, entries);
|
|
69008
68406
|
this.cache.set(this.cacheKey(scope, sessionId), { entries });
|
|
69009
68407
|
}
|
|
69010
|
-
/**
|
|
69011
|
-
* 真删一条群文件条目(用于 owner 撤销自己 +Add 的入群操作)。
|
|
69012
|
-
* agent 自动入群的不应走这条 —— 用 markStale 表达"文件不在了"语义;
|
|
69013
|
-
* owner 手动加错了,应该能彻底从列表移除而不是留个 stale 占位。
|
|
69014
|
-
*
|
|
69015
|
-
* 返回值:true=命中并删除;false=relPath 不在群里。
|
|
69016
|
-
*/
|
|
68408
|
+
/** Remove a manually added entry entirely; use markStale for missing automatically added files. */
|
|
69017
68409
|
remove(scope, sessionId, relPath) {
|
|
69018
68410
|
const entries = this.list(scope, sessionId).slice();
|
|
69019
68411
|
const idx = entries.findIndex((e) => e.relPath === relPath);
|
|
@@ -69023,13 +68415,7 @@ var GroupFileStore = class {
|
|
|
69023
68415
|
this.cache.set(this.cacheKey(scope, sessionId), { entries });
|
|
69024
68416
|
return true;
|
|
69025
68417
|
}
|
|
69026
|
-
/**
|
|
69027
|
-
* 会话删除时的收尾:整份清单文件删掉、cache 一起清。
|
|
69028
|
-
*
|
|
69029
|
-
* 清单跟 SessionFile 不在一个目录(这里在 <dataDir>/sessions/…,persona 会话的 SessionFile
|
|
69030
|
-
* 在 persona 目录下),SessionStore.delete 带不走它——不收这一下就留一个孤儿文件。
|
|
69031
|
-
* 没清单的会话(绝大多数)是 noop。
|
|
69032
|
-
*/
|
|
68418
|
+
/** Delete the separate manifest and cache; deleting a persona session file does not remove this store. */
|
|
69033
68419
|
deleteSession(scope, sessionId) {
|
|
69034
68420
|
try {
|
|
69035
68421
|
import_node_fs50.default.unlinkSync(this.filePath(scope, sessionId));
|
|
@@ -69038,12 +68424,7 @@ var GroupFileStore = class {
|
|
|
69038
68424
|
}
|
|
69039
68425
|
this.cache.delete(this.cacheKey(scope, sessionId));
|
|
69040
68426
|
}
|
|
69041
|
-
/**
|
|
69042
|
-
* persona 删除时的收尾:它名下(owner + 每个 guest capId)的清单整棵树删掉,cache 一起清。
|
|
69043
|
-
*
|
|
69044
|
-
* persona 目录 `rm -rf` 带不走这棵树——清单在 `<dataDir>/sessions/<personaId>/…`,
|
|
69045
|
-
* 不在 persona 目录里(见文件头的路径约定)。没树的 persona 是 noop。
|
|
69046
|
-
*/
|
|
68427
|
+
/** Remove all manifests and cache entries for this persona; these files live outside the persona directory. */
|
|
69047
68428
|
deletePersona(personaId2) {
|
|
69048
68429
|
import_node_fs50.default.rmSync(import_node_path55.default.join(this.dataDir, "sessions", safeFileName(personaId2)), { recursive: true, force: true });
|
|
69049
68430
|
const prefix = `persona:${personaId2}:`;
|
|
@@ -69504,7 +68885,6 @@ var TunnelManager = class {
|
|
|
69504
68885
|
ttlMs;
|
|
69505
68886
|
startupTimeoutMs;
|
|
69506
68887
|
exitHookInstalled = false;
|
|
69507
|
-
// 标记是否在主动 stop 中:true 时不要把 frpc 退出 emit 成 onFrpcExit
|
|
69508
68888
|
stopping = false;
|
|
69509
68889
|
get url() {
|
|
69510
68890
|
return this.current?.url ?? null;
|
|
@@ -69576,8 +68956,7 @@ var TunnelManager = class {
|
|
|
69576
68956
|
});
|
|
69577
68957
|
clearFrpcPid(this.deps.dataDir);
|
|
69578
68958
|
}
|
|
69579
|
-
//
|
|
69580
|
-
// async stop() 跑不完时的兜底,避免留下孤儿 frpc。
|
|
68959
|
+
// Synchronous fallback when process exit/crash prevents awaiting stop().
|
|
69581
68960
|
killSync() {
|
|
69582
68961
|
const proc = this.proc;
|
|
69583
68962
|
this.proc = null;
|
|
@@ -70280,19 +69659,15 @@ function createConnectToDevice(deps) {
|
|
|
70280
69659
|
deviceId: args.deviceId,
|
|
70281
69660
|
ownerId: hs.ownerId,
|
|
70282
69661
|
provider: hs.provider,
|
|
70283
|
-
//
|
|
70284
|
-
// 如"公共1");"输入标识"连接不带 name → 回落 handshake 报的对方 owner 真名。
|
|
70285
|
-
// 归属人身份不丢(ownerId/provider 仍来自 handshake 背书)。
|
|
69662
|
+
// Directory names override handshake names without changing token-backed owner identity.
|
|
70286
69663
|
displayName: args.name || hs.displayName || args.deviceId,
|
|
70287
69664
|
remoteUrl: url,
|
|
70288
|
-
//
|
|
70289
|
-
// 过期后对方拒 TOKEN_EXPIRED → 重新 device:connect 换新票)
|
|
69665
|
+
// Reuse the signed token for reconnects; exchange a new one when it expires.
|
|
70290
69666
|
connectToken: exchanged.token,
|
|
70291
69667
|
grants: hs.grants,
|
|
70292
69668
|
addedAt: now(),
|
|
70293
69669
|
pinnedAt: existing?.pinnedAt ?? null,
|
|
70294
69670
|
remoteAccessAllowed: existing?.remoteAccessAllowed ?? false,
|
|
70295
|
-
// 备注是 owner 已配置状态,重复建联 upsert 必须保留(同 pinnedAt / remoteAccessAllowed)
|
|
70296
69671
|
note: existing?.note ?? ""
|
|
70297
69672
|
};
|
|
70298
69673
|
deps.store.upsert(contact);
|
|
@@ -72225,7 +71600,7 @@ function buildEngineHandlers(deps, overrides = {}) {
|
|
|
72225
71600
|
const probes = {
|
|
72226
71601
|
claude: () => queryClaudeAuthStatus(),
|
|
72227
71602
|
codex: () => queryCodexAccount({ logger: deps.logger }),
|
|
72228
|
-
//
|
|
71603
|
+
// Reuse capability discovery instead of spawning another pi process.
|
|
72229
71604
|
pi: async () => buildPiAccount((await deps.manager.getCapabilities("pi")).models, readPiCredentials(piAgentDir())),
|
|
72230
71605
|
...overrides.probes
|
|
72231
71606
|
};
|
|
@@ -72532,7 +71907,6 @@ function upsertPeerContact(args) {
|
|
|
72532
71907
|
addedAt: now(),
|
|
72533
71908
|
pinnedAt: existing?.pinnedAt ?? null,
|
|
72534
71909
|
remoteAccessAllowed: existing?.remoteAccessAllowed ?? false,
|
|
72535
|
-
// 备注是 owner 已配置状态,重复建联 upsert 必须保留(同 pinnedAt / remoteAccessAllowed)
|
|
72536
71910
|
note: existing?.note ?? ""
|
|
72537
71911
|
};
|
|
72538
71912
|
args.store.upsert(base);
|
|
@@ -72809,7 +72183,6 @@ function buildFeishuAuthHandlers(deps) {
|
|
|
72809
72183
|
response: {
|
|
72810
72184
|
type: "auth:getIdentity:ok",
|
|
72811
72185
|
identity: record4 ? record4.identity : null,
|
|
72812
|
-
// 决策 #15:设备属性,与登录态无关——UI"分享我的 Hub 标识"用
|
|
72813
72186
|
deviceId: deps.deviceId
|
|
72814
72187
|
}
|
|
72815
72188
|
};
|
|
@@ -72979,42 +72352,25 @@ var ADMIN_ANY = {
|
|
|
72979
72352
|
};
|
|
72980
72353
|
var CAPABILITY_SCOPED = { kind: "capability-scoped" };
|
|
72981
72354
|
var METHOD_GRANT_MAP = {
|
|
72982
|
-
// ---- public(meta-only,guest 也能调) ----
|
|
72983
72355
|
"info": { kind: "public" },
|
|
72984
72356
|
"ping": { kind: "public" },
|
|
72985
|
-
// v2 Phase 6: whoami 是身份反查,任何 authed connection (owner / guest) 都能调;
|
|
72986
|
-
// handler 内部按 ctx.principal.kind 决定返回 "合成 owner self capability" 还是
|
|
72987
|
-
// "查 capabilityManager 拿 caller 的 guest capability"。
|
|
72988
72357
|
"whoami": { kind: "public" },
|
|
72989
|
-
//
|
|
72990
|
-
// 返回全集而非只返可调的——guest 看到 denied 条目才知道边界在哪,否则只能反复试错;
|
|
72991
|
-
// method 名本身不是秘密(protocol/src/methods.ts 就是公开源),泄漏面为零。
|
|
72358
|
+
// Introspection includes denied methods so callers can discover their authorization boundaries.
|
|
72992
72359
|
"meta:methods": { kind: "public" },
|
|
72993
|
-
//
|
|
72994
|
-
// 三条都 'public' — 能连上 = 已通过本地验票. handler 内额外校验 guest 的
|
|
72995
|
-
// args.peerDeviceId 与 ctx.principal.id(=deviceId) 一致, 防 guest 越权操作别的 DM 桶.
|
|
72360
|
+
// Public inbox handlers still bind guest access to the authenticated peer device, not arbitrary DM buckets.
|
|
72996
72361
|
"inbox:list": { kind: "public" },
|
|
72997
72362
|
"inbox:markRead": { kind: "public" },
|
|
72998
72363
|
"inbox:postMessage": { kind: "public" },
|
|
72999
|
-
// ---- contact:* (联系人列表) ----
|
|
73000
|
-
// 飞书统一身份 Phase 3 (决策 #14): received-capability:* 正名 contact:*。
|
|
73001
|
-
// 写入路径在 device:connect / 自动反向;list / remove 作联系人读 / 删,owner-only。
|
|
73002
72364
|
"contact:list": ADMIN_ANY,
|
|
73003
72365
|
"contact:pin": ADMIN_ANY,
|
|
73004
72366
|
"contact:setNote": ADMIN_ANY,
|
|
73005
72367
|
"org:peers": ADMIN_ANY,
|
|
73006
72368
|
"contact:remove": ADMIN_ANY,
|
|
73007
|
-
// contact:setRemoteAccess (owner UI 配远程访问授权):ADMIN_ANY
|
|
73008
|
-
// (对齐 inbox:postMessage 的"能连上=有 auth,业务在 handler 校"模式)
|
|
73009
72369
|
"contact:setRemoteAccess": ADMIN_ANY,
|
|
73010
|
-
// contact
|
|
73011
|
-
// (visitor / owner 不允许触发反向落库)。
|
|
72370
|
+
// Only verified connect identities may trigger reverse contact persistence; the handler rejects owners/visitors.
|
|
73012
72371
|
"contact:handshake": { kind: "public" },
|
|
73013
|
-
// ---- visitor:* (访客名单,owner-only) ----
|
|
73014
|
-
// owner 看完整访客名单(含没开会话的);guest 不可调(handler 内再 assertOwner 兜底)。
|
|
73015
72372
|
"visitor:list": ADMIN_ANY,
|
|
73016
|
-
//
|
|
73017
|
-
// guest 不该看见更不该动。
|
|
72373
|
+
// Deployment handlers use owner credentials and can publish resources; exclude guests.
|
|
73018
72374
|
"deploy:get": ADMIN_ANY,
|
|
73019
72375
|
"deploy:scan": ADMIN_ANY,
|
|
73020
72376
|
"deploy:patch": ADMIN_ANY,
|
|
@@ -73025,17 +72381,12 @@ var METHOD_GRANT_MAP = {
|
|
|
73025
72381
|
"deploy:release": ADMIN_ANY,
|
|
73026
72382
|
"deploy:rerunJob": ADMIN_ANY,
|
|
73027
72383
|
"deploy:unpublish": ADMIN_ANY,
|
|
73028
|
-
//
|
|
73029
|
-
// 拿本机登录凭证向中心代查 / 代填使用者本人的凭据,guest 一律够不着
|
|
72384
|
+
// Connector handlers use the owner login to fetch or fill credentials; exclude guests.
|
|
73030
72385
|
"cloud:connectors:list": ADMIN_ANY,
|
|
73031
|
-
// 设置 → Dashboard:签看板链接(design 2026-09-23-cloud-persona-metrics §3.8)
|
|
73032
72386
|
"dashboard:openUrl": ADMIN_ANY,
|
|
73033
72387
|
"cloud:connectors:fill": ADMIN_ANY,
|
|
73034
|
-
// cloud:volume:get(云上会话顶栏的云盘角标;design 2026-09-19-cloud-volume-usage):本机 api key 问 gateway,guest 够不着
|
|
73035
72388
|
"cloud:volume:get": ADMIN_ANY,
|
|
73036
|
-
//
|
|
73037
|
-
// dispatcher 不验资源,handler 内按 ctx + frame.args 反查 ownerPersonaId 调 assertGrant。
|
|
73038
|
-
// owner 自动通过(ctx 自带 '*':'admin' grant 一切 match);guest 在被授权 persona 内可调。
|
|
72389
|
+
// Session handlers resolve the target and enforce guest access; the dispatcher cannot authorize from method alone.
|
|
73039
72390
|
"session:create": CAPABILITY_SCOPED,
|
|
73040
72391
|
"session:importExternal": CAPABILITY_SCOPED,
|
|
73041
72392
|
"session:list": CAPABILITY_SCOPED,
|
|
@@ -73043,11 +72394,8 @@ var METHOD_GRANT_MAP = {
|
|
|
73043
72394
|
"session:update": CAPABILITY_SCOPED,
|
|
73044
72395
|
"session:delete": CAPABILITY_SCOPED,
|
|
73045
72396
|
"session:send": CAPABILITY_SCOPED,
|
|
73046
|
-
// 撤排队消息:能发就能撤(spec 2026-08-31-chat-input-queue-design)
|
|
73047
72397
|
"session:dequeue": CAPABILITY_SCOPED,
|
|
73048
|
-
//
|
|
73049
|
-
// WS path 的隐私边界(防 guest 猜 sid 枚举 owner feishuUnionId)由 handler 内 ensureSessionAccess
|
|
73050
|
-
// 显式兜底——见 handlers/session.ts:currentTurnSender。dispatcher 'public' ≠ handler 免检。
|
|
72398
|
+
// Public here does not bypass session access checks: currentTurnSender must not expose another session's identity.
|
|
73051
72399
|
"session:currentTurnSender": { kind: "public" },
|
|
73052
72400
|
"session:stop": CAPABILITY_SCOPED,
|
|
73053
72401
|
"session:interrupt": CAPABILITY_SCOPED,
|
|
@@ -73064,39 +72412,26 @@ var METHOD_GRANT_MAP = {
|
|
|
73064
72412
|
"session:pin": CAPABILITY_SCOPED,
|
|
73065
72413
|
"session:archive": CAPABILITY_SCOPED,
|
|
73066
72414
|
"session:markRead": CAPABILITY_SCOPED,
|
|
73067
|
-
//
|
|
73068
|
-
// handler 内仍逐条 ensureSessionAccess,可见面不因 sessionIds 由客户端给定而放大
|
|
72415
|
+
// Search checks access to each session; caller-supplied IDs must not broaden visibility.
|
|
73069
72416
|
"session:searchContent": CAPABILITY_SCOPED,
|
|
73070
72417
|
"session:reorderPins": ADMIN_ANY,
|
|
73071
|
-
//
|
|
73072
|
-
// mirror peer sessions (2026-05-26):owner-only — guest 不应能写/删本机 mirror SessionFile
|
|
73073
|
-
// (会绕过 #739 隔离规则把伪造身份的 SessionFile 写进 default store)。handler 端内部 ctx
|
|
73074
|
-
// 校验,dispatcher 层一刀切 admin 也兜底。
|
|
72418
|
+
// Only owners may write peer-session mirrors; forged SessionFiles could bypass guest-store isolation.
|
|
73075
72419
|
"peerSession:upsert": ADMIN_ANY,
|
|
73076
72420
|
"peerSession:remove": ADMIN_ANY,
|
|
73077
72421
|
"permission:respond": CAPABILITY_SCOPED,
|
|
73078
72422
|
"session:answerQuestion": CAPABILITY_SCOPED,
|
|
73079
72423
|
"session:cancelQuestion": CAPABILITY_SCOPED,
|
|
73080
|
-
//
|
|
73081
|
-
// attachment / skills / agents / git 元信息,下沉到 capability-scoped;handler 内
|
|
73082
|
-
// 用 isGuestPathAllowed (persona-paths.ts) 限制路径必须在 personaRoot/<allowed-pid>/** 下,
|
|
73083
|
-
// 防止跨 persona 越权读 owner 私有文件。
|
|
72424
|
+
// Guest filesystem handlers enforce lexical persona or caller user-directory boundaries.
|
|
73084
72425
|
"history:projects": CAPABILITY_SCOPED,
|
|
73085
72426
|
"history:list": CAPABILITY_SCOPED,
|
|
73086
|
-
//
|
|
73087
|
-
// 创建的 session 历史(chat view 加载历史消息走这条),所以下沉到 capability-scoped;
|
|
73088
|
-
// handler 内按 ctx.principal.id === file.creatorPrincipalId 拦越权。
|
|
72427
|
+
// History handlers enforce session ownership before exposing transcript content.
|
|
73089
72428
|
"history:read": CAPABILITY_SCOPED,
|
|
73090
72429
|
"history:subagents": CAPABILITY_SCOPED,
|
|
73091
72430
|
"history:subagent-read": CAPABILITY_SCOPED,
|
|
73092
72431
|
"history:recentDirs": CAPABILITY_SCOPED,
|
|
73093
72432
|
"workspace:list": CAPABILITY_SCOPED,
|
|
73094
72433
|
"workspace:read": CAPABILITY_SCOPED,
|
|
73095
|
-
// ---- shift:* (排班 mock,owner-only 读) ----
|
|
73096
|
-
// mock 阶段只 owner 用;接 guest 后再考虑分级
|
|
73097
72434
|
"shift:list": ADMIN_ANY,
|
|
73098
|
-
// channel:* / topic:*(Channel × Master):owner 本机资源,owner-only 一刀切。
|
|
73099
|
-
// Master 是 owner-scope persona session,经 clawd-rpc 桥调,同样落在 ADMIN_ANY。
|
|
73100
72435
|
"channel:list": ADMIN_ANY,
|
|
73101
72436
|
"channel:create": ADMIN_ANY,
|
|
73102
72437
|
"channel:update": ADMIN_ANY,
|
|
@@ -73112,16 +72447,14 @@ var METHOD_GRANT_MAP = {
|
|
|
73112
72447
|
"topic:rename": ADMIN_ANY,
|
|
73113
72448
|
"topic:delete": ADMIN_ANY,
|
|
73114
72449
|
"master:authorityCheck": ADMIN_ANY,
|
|
73115
|
-
// project:*(sidebar 项目分区,spec 2026-08-17):registry 是 owner 本机资源,owner-only 一刀切
|
|
73116
72450
|
"project:list": ADMIN_ANY,
|
|
73117
72451
|
"project:create": ADMIN_ANY,
|
|
73118
72452
|
"project:patch": ADMIN_ANY,
|
|
73119
72453
|
"project:reveal": ADMIN_ANY,
|
|
73120
|
-
// githubAuth:*(本机 GitHub 身份,机器级;spec 2026-09-15-github-auth-unify):关联 / 粘 token 只认人手,guest 够不着
|
|
73121
72454
|
"githubAuth:get": ADMIN_ANY,
|
|
73122
72455
|
"githubAuth:oauthStart": ADMIN_ANY,
|
|
73123
72456
|
"githubAuth:authorize": ADMIN_ANY,
|
|
73124
|
-
//
|
|
72457
|
+
// GitHub operations use the machine identity and credentials; exclude guests.
|
|
73125
72458
|
"github:status": ADMIN_ANY,
|
|
73126
72459
|
"github:prList": ADMIN_ANY,
|
|
73127
72460
|
"github:prDetail": ADMIN_ANY,
|
|
@@ -73130,16 +72463,12 @@ var METHOD_GRANT_MAP = {
|
|
|
73130
72463
|
"github:prSetReady": ADMIN_ANY,
|
|
73131
72464
|
"github:collaborators": ADMIN_ANY,
|
|
73132
72465
|
"github:prRequestReviewers": ADMIN_ANY,
|
|
73133
|
-
//
|
|
73134
|
-
// 统一 RPC 鉴权(spec 2026-07-28):原 /api/rpc 白名单 method 收编。调用方 = 本机
|
|
73135
|
-
// MCP server 持 owner token → ADMIN_ANY 即「owner token 即可」;业务归因(sendDm 的
|
|
73136
|
-
// sender、shift 的 personaId)在 handler 内按 body 字段处理,与鉴权无关。
|
|
72466
|
+
// These programmatic entry points require the owner token; business attribution is handled separately.
|
|
73137
72467
|
"shift-internal:add": ADMIN_ANY,
|
|
73138
72468
|
"shift-internal:list": ADMIN_ANY,
|
|
73139
72469
|
"shift-internal:cancel": ADMIN_ANY,
|
|
73140
72470
|
"shift-internal:update": ADMIN_ANY,
|
|
73141
72471
|
"shift-internal:history": ADMIN_ANY,
|
|
73142
|
-
// ---- larkBot:* (LarkBotConnector 绑定/状态,owner-only) ----
|
|
73143
72472
|
"larkBot:provision": ADMIN_ANY,
|
|
73144
72473
|
"larkBot:provisionCancel": ADMIN_ANY,
|
|
73145
72474
|
"larkBot:bindManual": ADMIN_ANY,
|
|
@@ -73152,7 +72481,6 @@ var METHOD_GRANT_MAP = {
|
|
|
73152
72481
|
"cloud:larkBot:unbind": ADMIN_ANY,
|
|
73153
72482
|
"cloud:larkBot:status": ADMIN_ANY,
|
|
73154
72483
|
"cloud:larkBot:upgrade": ADMIN_ANY,
|
|
73155
|
-
// ---- onboarding:* / tools:probe / session:externalScan(首启 wizard,owner-only)----
|
|
73156
72484
|
"onboarding:complete": ADMIN_ANY,
|
|
73157
72485
|
"onboarding:iosInterest": ADMIN_ANY,
|
|
73158
72486
|
"tools:probe": ADMIN_ANY,
|
|
@@ -73160,8 +72488,7 @@ var METHOD_GRANT_MAP = {
|
|
|
73160
72488
|
"inbox:sendDm": ADMIN_ANY,
|
|
73161
72489
|
"personaDispatch:complete": ADMIN_ANY,
|
|
73162
72490
|
"personaDispatch:list": ADMIN_ANY,
|
|
73163
|
-
//
|
|
73164
|
-
// 台账行 sourceSessionId === ctx.principal.id,防 guest 查别人的 dispatch。
|
|
72491
|
+
// Remote outcome polling binds ledger sourceSessionId to the guest principal, preventing access to another caller's dispatch.
|
|
73165
72492
|
"personaDispatch:get": CAPABILITY_SCOPED,
|
|
73166
72493
|
"skills:list": CAPABILITY_SCOPED,
|
|
73167
72494
|
"agents:list": CAPABILITY_SCOPED,
|
|
@@ -73169,71 +72496,50 @@ var METHOD_GRANT_MAP = {
|
|
|
73169
72496
|
"git:branch": CAPABILITY_SCOPED,
|
|
73170
72497
|
"git:branches": CAPABILITY_SCOPED,
|
|
73171
72498
|
"capabilities:get": ADMIN_ANY,
|
|
73172
|
-
// engine:status(设置 → 账号「引擎」,spec 2026-09-17-engine-status):回账号邮箱 / 订阅档,与 githubAuth:get 同档,guest 够不着
|
|
73173
72499
|
"engine:status": ADMIN_ANY,
|
|
73174
|
-
// engine:connectPool(接入号池,spec 2026-09-17-onboarding-pool-key):改本机 ~/.codex/config.toml,owner-only
|
|
73175
72500
|
"engine:connectPool": ADMIN_ANY,
|
|
73176
72501
|
"subscriptionChannel:get": ADMIN_ANY,
|
|
73177
72502
|
"subscriptionChannel:save": ADMIN_ANY,
|
|
73178
72503
|
"subscriptionChannel:delete": ADMIN_ANY,
|
|
73179
72504
|
"subscriptionChannel:activate": ADMIN_ANY,
|
|
73180
72505
|
"persona:create": ADMIN_ANY,
|
|
73181
|
-
// persona
|
|
73182
|
-
// (list 用于 People 面板展开对方分享的 personas;get 用于 guest 视角下
|
|
73183
|
-
// PersonaDrawer 展示 persona 详情 readOnly),handler 端按 ctx + persona.public
|
|
73184
|
-
// + assertGrant 过滤,两者口径一致。
|
|
73185
|
-
// persona:update / persona:delete 保持 ADMIN_ANY:UI 端 guest viewerRole 下
|
|
73186
|
-
// 已干掉编辑入口,dispatcher 这层是双保险——非法 RPC 401 兜底。
|
|
72506
|
+
// Guest persona reads filter visibility and grants in the handler; mutation remains owner-only.
|
|
73187
72507
|
"persona:list": CAPABILITY_SCOPED,
|
|
73188
72508
|
"persona:get": CAPABILITY_SCOPED,
|
|
73189
72509
|
"persona:update": ADMIN_ANY,
|
|
73190
72510
|
"persona:delete": ADMIN_ANY,
|
|
73191
|
-
//
|
|
73192
|
-
// run handler 内做 persona.public + assertGrant(send) 双重校验(guest grants 是 wildcard,
|
|
73193
|
-
// 单 assertGrant 不够)。本地 MCP server 经 /rpc + Bearer 进来,同过此表(owner 恒过)。
|
|
72511
|
+
// Remote dispatch requires both public visibility and send grants; wildcard grants alone are insufficient.
|
|
73194
72512
|
"personaDispatch:run": CAPABILITY_SCOPED,
|
|
73195
|
-
//
|
|
73196
|
-
// (ensureGuest + remoteAccessAllowed),跟 inbox:postMessage 同模式。
|
|
72513
|
+
// The handler requires a guest context and remoteAccessAllowed for local peer execution.
|
|
73197
72514
|
"peerExec:run": { kind: "public" },
|
|
73198
|
-
// PTY
|
|
72515
|
+
// PTY handlers enforce the same session access boundary as other session RPCs.
|
|
73199
72516
|
"session:pty:input": CAPABILITY_SCOPED,
|
|
73200
72517
|
"session:pty:resize": CAPABILITY_SCOPED,
|
|
73201
72518
|
"session:pty:snapshot": CAPABILITY_SCOPED,
|
|
73202
|
-
//
|
|
73203
|
-
//
|
|
73204
|
-
// 自己被授权 persona 下的 session 文件。HTTP Bearer 仍然只识别 owner,所以 guest
|
|
73205
|
-
// 拿到 signed URL 后还要 HTTP 端校验通过才能取到文件——这里 RPC 层 ACL 是第一道。
|
|
72519
|
+
// Attachment handlers enforce scope/path checks before signing or changing group entries.
|
|
72520
|
+
// Serving signed URLs relies on their HMAC, not on Bearer authentication.
|
|
73206
72521
|
"attachment.signUrl": CAPABILITY_SCOPED,
|
|
73207
72522
|
"attachment.groupAdd": CAPABILITY_SCOPED,
|
|
73208
72523
|
"attachment.groupRemove": CAPABILITY_SCOPED,
|
|
73209
72524
|
"attachment.groupList": CAPABILITY_SCOPED,
|
|
73210
|
-
// ---- extension runtime(owner-only;handler 内再 assertOwner 兜底) ----
|
|
73211
72525
|
"extension.list": ADMIN_ANY,
|
|
73212
72526
|
"extension.open": ADMIN_ANY,
|
|
73213
72527
|
"extension.close": ADMIN_ANY,
|
|
73214
72528
|
"extension.uninstall": ADMIN_ANY,
|
|
73215
|
-
// ---- extension sharing v1(spec 2026-05-28-clawd-extension-sharing-design) ----
|
|
73216
|
-
// owner-side:owner 自己管理 publish 状态
|
|
73217
72529
|
"extension.publish": ADMIN_ANY,
|
|
73218
72530
|
"extension.unpublish": ADMIN_ANY,
|
|
73219
72531
|
"extension.publish:status": ADMIN_ANY,
|
|
73220
|
-
// cross-tunnel:guest 从 owner 拉 published list + bundle
|
|
73221
72532
|
"extension.list-published": CAPABILITY_SCOPED,
|
|
73222
72533
|
"extension.fetchBundle": CAPABILITY_SCOPED,
|
|
73223
|
-
//
|
|
72534
|
+
// Installation runs on the caller's own daemon and creates no persistent channel subscription.
|
|
73224
72535
|
"extension.install-from-channel": ADMIN_ANY,
|
|
73225
72536
|
"extension.update-from-channel": ADMIN_ANY,
|
|
73226
|
-
// ---- auth:* 飞书统一身份 Phase 1(owner-only) ----
|
|
73227
|
-
// 登录/登出/查身份都是 owner 本机操作,guest 不可调
|
|
73228
72537
|
"auth:login:start": ADMIN_ANY,
|
|
73229
72538
|
"auth:getIdentity": ADMIN_ANY,
|
|
73230
72539
|
"auth:logout": ADMIN_ANY,
|
|
73231
|
-
// 云 persona 调用 key:本机主人自己的东西,guest 一律够不着
|
|
73232
72540
|
"auth:apiKey:get": ADMIN_ANY,
|
|
73233
72541
|
"auth:apiKey:regenerate": ADMIN_ANY,
|
|
73234
72542
|
"auth:apiKey:revoke": ADMIN_ANY,
|
|
73235
|
-
// ---- device:* 设备目录 + 连接(决策 #15,owner-only) ----
|
|
73236
|
-
// 设备目录 / 连接都是 owner 本机操作(用本机登录票找中心 server),guest 不可调
|
|
73237
72543
|
"device:list": ADMIN_ANY,
|
|
73238
72544
|
"device:connect": ADMIN_ANY,
|
|
73239
72545
|
"device:connections": ADMIN_ANY
|
|
@@ -73289,7 +72595,7 @@ function computeMethodAccess(args) {
|
|
|
73289
72595
|
}
|
|
73290
72596
|
|
|
73291
72597
|
// src/version.ts
|
|
73292
|
-
var version = "0.2.
|
|
72598
|
+
var version = "0.2.622".length > 0 ? "0.2.622" : "dev";
|
|
73293
72599
|
|
|
73294
72600
|
// src/cli-probe/probe.ts
|
|
73295
72601
|
var fs84 = __toESM(require("fs"), 1);
|
|
@@ -73365,8 +72671,6 @@ function buildReadyFrame(deps, client) {
|
|
|
73365
72671
|
mode: deps.mode,
|
|
73366
72672
|
daemonSource,
|
|
73367
72673
|
globalCliVersion,
|
|
73368
|
-
// 连接身份下发(UI 据此挑历史加载策略)。noAuth 本机直连 / 老 fixture 不带 ctx 时
|
|
73369
|
-
// 一律 owner —— 那条路径本来就只有本机 UI 走得到。
|
|
73370
72674
|
principalKind: client?.principalKind ?? "owner",
|
|
73371
72675
|
...fileSharing,
|
|
73372
72676
|
...logShipping ? { logShipping } : {},
|
|
@@ -73394,8 +72698,7 @@ function buildMetaMethodsHandler(deps) {
|
|
|
73394
72698
|
grants,
|
|
73395
72699
|
feishuActive: deps.feishuActive(),
|
|
73396
72700
|
isImplemented: deps.isImplemented,
|
|
73397
|
-
//
|
|
73398
|
-
// 就是 agent,让它一开始就看不见,省掉「先发现再撞墙」那一轮。
|
|
72701
|
+
// Programmatic callers must see human-only methods as denied.
|
|
73399
72702
|
transport: client.transport
|
|
73400
72703
|
});
|
|
73401
72704
|
if (method !== void 0) {
|
|
@@ -74392,7 +73695,6 @@ function buildChannelHandlers(deps) {
|
|
|
74392
73695
|
const args = ChannelUpdateArgsSchema.parse(frame);
|
|
74393
73696
|
const channel = deps.channelStore.update(args.channelId, {
|
|
74394
73697
|
...args.name !== void 0 ? { name: args.name } : {},
|
|
74395
|
-
// 改名单同样过 withMaster:编辑路径把 Master 摘掉,效果跟建一个没 Master 的频道一样
|
|
74396
73698
|
...args.memberPersonas !== void 0 ? { memberPersonas: withMaster(args.memberPersonas) } : {},
|
|
74397
73699
|
...args.memberContactDeviceIds !== void 0 ? { memberContactDeviceIds: args.memberContactDeviceIds } : {}
|
|
74398
73700
|
});
|
|
@@ -74440,7 +73742,7 @@ function joinLines(records, lineMeta) {
|
|
|
74440
73742
|
acceptance: Array.isArray(meta.acceptance) ? meta.acceptance.map(String) : [],
|
|
74441
73743
|
rejectCount: lineMeta[meta.lineId]?.rejectCount ?? 0,
|
|
74442
73744
|
accepted: lineMeta[meta.lineId]?.accepted ?? false,
|
|
74443
|
-
//
|
|
73745
|
+
// Pure projection: only the store assigns ordinals through linesOf.
|
|
74444
73746
|
ordinal: lineMeta[meta.lineId]?.ordinal ?? null
|
|
74445
73747
|
});
|
|
74446
73748
|
}
|
|
@@ -74873,7 +74175,7 @@ function buildGithubHandlers(deps) {
|
|
|
74873
74175
|
"-f",
|
|
74874
74176
|
`searchQuery=${buildPrSearchQuery(role)}`,
|
|
74875
74177
|
"-F",
|
|
74876
|
-
//
|
|
74178
|
+
// Return the 50 most recently updated results; this endpoint does not paginate.
|
|
74877
74179
|
"first=50"
|
|
74878
74180
|
]);
|
|
74879
74181
|
const prs = parsePrListResponse(JSON.parse(stdout));
|
|
@@ -75232,8 +74534,6 @@ function buildMethodHandlers(deps) {
|
|
|
75232
74534
|
store: deps.contactStore,
|
|
75233
74535
|
broadcast: deps.broadcastToOwners
|
|
75234
74536
|
}),
|
|
75235
|
-
// 跨设备 exec(spec 2026-07-16):A 侧带 peerDeviceId → forwardPeerExecToPeer;
|
|
75236
|
-
// B 侧无 peerDeviceId → 本地 runCommand(handler 判 remoteAccessAllowed)。
|
|
75237
74537
|
...buildPeerExecHandlers({
|
|
75238
74538
|
store: deps.contactStore,
|
|
75239
74539
|
forwardToPeer: (a2) => forwardPeerExecToPeer(a2)
|
|
@@ -75260,7 +74560,7 @@ function buildMethodHandlers(deps) {
|
|
|
75260
74560
|
...buildChannelHandlers({
|
|
75261
74561
|
channelStore: deps.channelStore,
|
|
75262
74562
|
topicStore: deps.topicStore,
|
|
75263
|
-
//
|
|
74563
|
+
// Owner-only resources also require owner-only broadcasts.
|
|
75264
74564
|
broadcast: deps.broadcastToOwners
|
|
75265
74565
|
}),
|
|
75266
74566
|
...buildMasterHandlers({
|
|
@@ -75272,7 +74572,6 @@ function buildMethodHandlers(deps) {
|
|
|
75272
74572
|
dispatchStore: deps.dispatchStore,
|
|
75273
74573
|
createMasterSession: deps.createMasterSession,
|
|
75274
74574
|
deleteSession: deps.deleteSession,
|
|
75275
|
-
// 与 channel:* 同一个 callback:频道与话题共用 channel:changed 一帧
|
|
75276
74575
|
broadcast: deps.broadcastToOwners,
|
|
75277
74576
|
logger: deps.logger
|
|
75278
74577
|
}),
|
|
@@ -75679,12 +74978,10 @@ var PublishedChannelStore = class {
|
|
|
75679
74978
|
}
|
|
75680
74979
|
this.loaded = true;
|
|
75681
74980
|
}
|
|
75682
|
-
/** Returns the head snapshot, or null when unpublished / unknown extId. */
|
|
75683
74981
|
get(extId) {
|
|
75684
74982
|
this.assertLoaded();
|
|
75685
74983
|
return this.channels.get(extId) ?? null;
|
|
75686
74984
|
}
|
|
75687
|
-
/** Returns all extIds with a head (i.e. currently published). */
|
|
75688
74985
|
list() {
|
|
75689
74986
|
this.assertLoaded();
|
|
75690
74987
|
const out = [];
|
|
@@ -75741,7 +75038,6 @@ var BundleCache = class {
|
|
|
75741
75038
|
await import_promises21.default.writeFile(tmp, buffer, { mode: 384 });
|
|
75742
75039
|
await import_promises21.default.rename(tmp, file);
|
|
75743
75040
|
}
|
|
75744
|
-
/** Returns the bundle bytes, or null when the file doesn't exist. */
|
|
75745
75041
|
async read(snapshotHash) {
|
|
75746
75042
|
const file = import_node_path84.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
75747
75043
|
try {
|
|
@@ -75751,7 +75047,6 @@ var BundleCache = class {
|
|
|
75751
75047
|
throw e;
|
|
75752
75048
|
}
|
|
75753
75049
|
}
|
|
75754
|
-
/** Idempotent — missing file is not an error. */
|
|
75755
75050
|
async delete(snapshotHash) {
|
|
75756
75051
|
const file = import_node_path84.default.join(this.rootDir, `${snapshotHash}.zip`);
|
|
75757
75052
|
await import_promises21.default.rm(file, { force: true });
|
|
@@ -75854,18 +75149,13 @@ async function startDaemon(config) {
|
|
|
75854
75149
|
expectedDeviceId: authFile.deviceId
|
|
75855
75150
|
});
|
|
75856
75151
|
},
|
|
75857
|
-
//
|
|
75858
|
-
// "持票人还在职吗"。token 不再过期,撤销全靠它。端点无鉴权——不带本机登录票,
|
|
75859
|
-
// 本机没登录也能验别人的票。
|
|
75152
|
+
// Signed tokens establish issuer identity; the central check determines whether that principal is still active.
|
|
75860
75153
|
checkPrincipalActive: async (provider, ownerId2) => {
|
|
75861
75154
|
const r2 = await centralPrincipalStatus({
|
|
75862
75155
|
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
75863
75156
|
provider,
|
|
75864
75157
|
ownerId: ownerId2,
|
|
75865
|
-
//
|
|
75866
|
-
// st.authed、**不看 st.pending**,所以 authenticate 跑太久会被它 1008 关掉——
|
|
75867
|
-
// 那是"连接被关",不是我们要的 fail-open 放行。centralRequest 默认 15s 远超
|
|
75868
|
-
// 预算,这里压到 3s:中心慢时先抛超时 → 走 fail-open → 放行 + warn。
|
|
75158
|
+
// Finish revocation lookup before the WS authentication timer expires, allowing lookup failures to follow the documented fail-open path.
|
|
75869
75159
|
timeoutMs: 3e3
|
|
75870
75160
|
});
|
|
75871
75161
|
return r2.active;
|
|
@@ -75886,13 +75176,8 @@ async function startDaemon(config) {
|
|
|
75886
75176
|
let wsServer = null;
|
|
75887
75177
|
const authGate = authMode === "first-message" ? new AuthGate({
|
|
75888
75178
|
shouldEnforce: buildShouldEnforce({ tunnel: config.tunnel }),
|
|
75889
|
-
// Task 1.7:authenticate 注入路径替代 expectedToken 单 token 比对。
|
|
75890
|
-
// owner 路径 constantTimeEqual 防侧信道;guest 路径走 verifyConnectToken (Ed25519).
|
|
75891
75179
|
expectedToken: resolvedAuthToken,
|
|
75892
|
-
//
|
|
75893
|
-
// 本地验签(Ed25519 公钥 + aud === 本机 deviceId + 未过期)+ 中心撤销查询
|
|
75894
|
-
// (spec 2026-08-28,查不通则 fail-open 放行并 warn)。
|
|
75895
|
-
// 反向落库不再挂在验票副作用上——落库唯一路径 = contact:handshake handler。
|
|
75180
|
+
// Authenticate without storing contacts. Only contact:handshake performs reverse storage.
|
|
75896
75181
|
authenticate: async (t2) => authenticate(t2, buildConnectAuthDeps()),
|
|
75897
75182
|
onAuthed: (h2, ctx) => wsServer?.attachClientContext(h2.id, ctx),
|
|
75898
75183
|
buildOwnerContext: () => ownerContext(ownerPrincipalId, ownerDisplayName, ownerFeishuUnionId),
|
|
@@ -76033,9 +75318,7 @@ async function startDaemon(config) {
|
|
|
76033
75318
|
const mcpDaemonUrl = `http://127.0.0.1:${config.port}`;
|
|
76034
75319
|
const dispatchServerCandidates = [
|
|
76035
75320
|
import_node_path85.default.join(here, "dispatch", "mcp-server.cjs"),
|
|
76036
|
-
// 生产 dist/index → dist/dispatch/mcp-server.cjs
|
|
76037
75321
|
import_node_path85.default.join(here, "..", "dist", "dispatch", "mcp-server.cjs")
|
|
76038
|
-
// dev tsx src/index → ../dist/dispatch/mcp-server.cjs
|
|
76039
75322
|
];
|
|
76040
75323
|
const dispatchServerScriptPath = dispatchServerCandidates.find((p3) => import_node_fs66.default.existsSync(p3));
|
|
76041
75324
|
if (dispatchServerScriptPath) {
|
|
@@ -76198,54 +75481,31 @@ async function startDaemon(config) {
|
|
|
76198
75481
|
});
|
|
76199
75482
|
const manager = new SessionManager({
|
|
76200
75483
|
store,
|
|
76201
|
-
// Phase 2 (capability platform plan §1): factory 注入后 manager.storeFor 走
|
|
76202
|
-
// 新布局派生 (sessions/* + personas/<pid>/.clawd/sessions/owner/*)
|
|
76203
75484
|
storeFactory: sessionStoreFactory,
|
|
76204
75485
|
logger,
|
|
76205
|
-
// 取证 probe(可选,CLAWD_SCREEN_IDLE_PROBE=1 时启用):manager turn_end 判定链
|
|
76206
|
-
// 的所有决策点打到独立文件,跟 adapter 的 observeScreenIdle probe 共用同一份 file logger,
|
|
76207
|
-
// 便于 grep sessionId 时 tui 层 + manager 层交叉时序都在同一文件里
|
|
76208
75486
|
...screenIdleProbeLogger ? { screenIdleProbeLogger } : {},
|
|
76209
75487
|
getAdapter,
|
|
76210
75488
|
historyReader: history,
|
|
76211
75489
|
dataDir: config.dataDir,
|
|
76212
75490
|
personaRoot: import_node_path85.default.join(config.dataDir, "personas"),
|
|
76213
|
-
//
|
|
76214
|
-
// personaRegistry 声明在 manager 之后(同作用域 const,调用时机在启动完成后,无 TDZ 风险)。
|
|
75491
|
+
// Registry defaults are read only after startup, when the later registry binding is initialized.
|
|
76215
75492
|
getPersonaDefaults: (personaId2) => personaRegistry.get(personaId2),
|
|
76216
75493
|
usersRoot,
|
|
76217
75494
|
personaStore,
|
|
76218
75495
|
ownerDisplayName,
|
|
76219
75496
|
ownerPrincipalId,
|
|
76220
75497
|
ownerFeishuUnionId,
|
|
76221
|
-
// T-37 owner session 闲置回收兜底上限(默认 6h,config.json / env 可调,设 0 关闭)。
|
|
76222
|
-
// guest 的 10min 不走这条——它是 session/scope.ts 的固定常量。
|
|
76223
75498
|
ownerIdleKillDelayMs: config.ownerIdleKillDelayMs,
|
|
76224
75499
|
contactStore,
|
|
76225
75500
|
mode: config.mode,
|
|
76226
|
-
//
|
|
76227
|
-
// MCP server 用它拼 /rpc/<method>(Bearer CLAWD_DAEMON_TOKEN)调本机 daemon。
|
|
76228
|
-
// 127.0.0.1(不是 config.host)—— cc 跑在本机,loopback 最稳;外部访问限制 + http-router
|
|
76229
|
-
// 的 isLoopback 兜底已确保安全。
|
|
75501
|
+
// Local MCP traffic uses loopback independently of the externally configured listen host.
|
|
76230
75502
|
getDaemonUrl: () => `http://127.0.0.1:${config.port}`,
|
|
76231
|
-
// clawd 管家 (spec 2026-07-22-clawd-butler-persona §3.1):cc 子进程 env CLAWOS_API 闭包来源。
|
|
76232
|
-
// clawd 管家 skill 用它 fetch 云端 md 文档(`${CLAWOS_API}/docs/**`)。非 tunnel 模式
|
|
76233
|
-
// config.clawosApi 为 null → fallback 到 DEFAULT_CLAWOS_API(api.clawos.chat),保证注入
|
|
76234
|
-
// 的 env 一定非空;skill 拿到但云端不可达时走 §3.4 明说降级。
|
|
76235
75503
|
getClawosApi: () => config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
76236
|
-
// Persona dispatch (Task 8): manager.createDispatchedSession 用 personaDispatchManager
|
|
76237
|
-
// registerBSession;reducer 通过闭包反查 dispatchId 注 CLAWD_DISPATCH_ID env。
|
|
76238
75504
|
personaDispatchManager,
|
|
76239
|
-
// Dispatch 结果投递:deliverPending 查台账 + turn-end sweep(spec 2026-07-20 §5)。
|
|
76240
75505
|
dispatchStore,
|
|
76241
|
-
// daemon 级 MCP server 登记表(dispatch / ticket / shift / inbox / ...,上方各 writer
|
|
76242
|
-
// 块写盘成功即登记);reducer 透传到 SpawnContext.mcpConfigs,cc spawn 逐项 --mcp-config。
|
|
76243
75506
|
mcpConfigs,
|
|
76244
|
-
//
|
|
76245
|
-
// 产出拼进 ctx.mcpConfigs。**不在 reducer 里**——渲染要读写 fs,那边是纯函数。
|
|
75507
|
+
// Render MCP configs outside the pure reducer because rendering performs filesystem IO.
|
|
76246
75508
|
resolvePersonaMcpConfig,
|
|
76247
|
-
// 会话标题自产(首条 send 并行 fire,沿用会话引擎和模型)。
|
|
76248
|
-
// 不注入 = 功能关(manager 测试默认关闭防烧真调用),这里是唯一的生产装配点。
|
|
76249
75509
|
generateTitle: (file, firstMessage, signal) => generateSessionTitle(file, firstMessage, {
|
|
76250
75510
|
logger,
|
|
76251
75511
|
signal,
|
|
@@ -76265,9 +75525,7 @@ async function startDaemon(config) {
|
|
|
76265
75525
|
}
|
|
76266
75526
|
transport?.broadcastToSession(sid, frame);
|
|
76267
75527
|
},
|
|
76268
|
-
//
|
|
76269
|
-
// 闭包 stat + mime 写入群清单。stat 失败不阻塞主流程(log warn + 跳过本条),
|
|
76270
|
-
// 文件可能 agent 写完又被自己删(罕见),用 size=0 / fallback mime 兜底。
|
|
75528
|
+
// File edits update the group manifest without making optional stat/manifest failures fail the engine turn.
|
|
76271
75529
|
attachmentGroup: {
|
|
76272
75530
|
onFileEdit: (input) => {
|
|
76273
75531
|
const absPath = import_node_path85.default.isAbsolute(input.relPath) ? input.relPath : import_node_path85.default.join(input.cwd, input.relPath);
|
|
@@ -76320,10 +75578,7 @@ async function startDaemon(config) {
|
|
|
76320
75578
|
logger
|
|
76321
75579
|
});
|
|
76322
75580
|
const observer = new SessionObserver({
|
|
76323
|
-
// TUI
|
|
76324
|
-
// 永远 undefined,子 agent 调用过程没有抽屉入口。开 meta observer 后从 subagents 目录
|
|
76325
|
-
// 新出现的 agent-*.meta.json 派生 subagent_progress(started) 补这块缺口。SDK 模式仍走
|
|
76326
|
-
// task_started 路径,避免双 emit 让 ring buffer 双帧。
|
|
75581
|
+
// TUI lacks SDK task_started frames, so enable metadata observation only for TUI to avoid duplicate subagent progress.
|
|
76327
75582
|
enableSubagentMetaObserver: config.mode === "tui",
|
|
76328
75583
|
onEvent: ({ sessionId, events }) => {
|
|
76329
75584
|
manager.feedObserverEvents(sessionId, events);
|
|
@@ -76348,9 +75603,7 @@ async function startDaemon(config) {
|
|
|
76348
75603
|
onUserMessageObserved: (sessionId, realUuid, text) => {
|
|
76349
75604
|
manager.recordRealUserUuid({ sessionId, realUuid, text });
|
|
76350
75605
|
},
|
|
76351
|
-
//
|
|
76352
|
-
// (persist + session:info,不 kill),fork / respawn / UI 缓存随之对齐。
|
|
76353
|
-
// cwd 可能两段式到达(挪走瞬间新 cwd 行未落盘 → 先 null 后补),回调幂等
|
|
75606
|
+
// Relocated transcripts may reveal cwd later; persist each correction without killing the process so fork/resume/UI agree.
|
|
76354
75607
|
onJsonlRelocated: (sessionId, { filePath, cwd }) => {
|
|
76355
75608
|
logger.info("observer jsonl relocated", { sessionId, filePath, cwd });
|
|
76356
75609
|
if (cwd) manager.syncSessionCwd(sessionId, cwd);
|
|
@@ -76368,11 +75621,8 @@ async function startDaemon(config) {
|
|
|
76368
75621
|
onPtyReplayRegister: (tsid, replay) => manager.registerPtyReplay(tsid, replay),
|
|
76369
75622
|
onSurfaceRegister: (tsid, surface) => manager.registerSurface(tsid, surface),
|
|
76370
75623
|
onSurfaceUnregister: (tsid) => manager.unregisterSurface(tsid),
|
|
76371
|
-
// ReadyGate v2:ReadyDetector emit ready 时投递 reducer 'ready-detected' input
|
|
76372
75624
|
onReady: (tsid) => manager.dispatchReadyDetected(tsid),
|
|
76373
|
-
// 屏幕真稳定 5s 的一次性信号 → manager 查 pending turn_duration 并 flush 成 turn_end
|
|
76374
75625
|
onScreenIdle: (tsid) => manager.notifyScreenIdle(tsid),
|
|
76375
|
-
// 取证 probe(默认无条件启用;见 createFileOnlyLogger)
|
|
76376
75626
|
screenIdleProbeLogger
|
|
76377
75627
|
}) : new ClaudeAdapter({ logger, historyReader: new ClaudeHistoryReader() });
|
|
76378
75628
|
const cloudGateway = new CloudGatewayClient({
|
|
@@ -76411,7 +75661,6 @@ async function startDaemon(config) {
|
|
|
76411
75661
|
sharedHome: import_node_path85.default.join(import_node_os31.default.homedir(), ".codex"),
|
|
76412
75662
|
logger
|
|
76413
75663
|
}),
|
|
76414
|
-
// projectPaths 每次调用现取 —— 用户新建 project 后立即生效,不用重启 daemon
|
|
76415
75664
|
codexHomeFor: (cwd) => resolveCodexHome({
|
|
76416
75665
|
cwd,
|
|
76417
75666
|
personaRoot: import_node_path85.default.join(config.dataDir, "personas"),
|
|
@@ -76422,7 +75671,6 @@ async function startDaemon(config) {
|
|
|
76422
75671
|
isolatedHome: codexIsolatedHome
|
|
76423
75672
|
}),
|
|
76424
75673
|
codexHomeForThread: (threadId) => locateCodexThread(codexRolloutRoots(config.dataDir), threadId)?.home,
|
|
76425
|
-
// 附件图片验签:与 http-router /files 路由同一把 secret
|
|
76426
75674
|
getSignSecret: () => authFile.token
|
|
76427
75675
|
})
|
|
76428
75676
|
);
|
|
@@ -76548,10 +75796,7 @@ async function startDaemon(config) {
|
|
|
76548
75796
|
}),
|
|
76549
75797
|
connectToDevice: createConnectToDevice(feishuDeviceDeps),
|
|
76550
75798
|
log: (msg, meta) => logger.info(msg, meta),
|
|
76551
|
-
// 进度上报:串行建联 20+ 台要几十秒,UI 靠这个帧渲染进度条
|
|
76552
75799
|
onProgress: (progress) => wsServer?.broadcastToOwners({ type: "org-mesh:progress", ...progress }),
|
|
76553
|
-
// 已有联系人的地址被中心最新 url 刷新 → 复用 contact:added(UI reducer 按 deviceId upsert,
|
|
76554
|
-
// auto-reverse 重握手也发这一帧),不新增帧
|
|
76555
75800
|
onContactUpdated: (contact) => wsServer?.broadcastToOwners({ type: "contact:added", contact })
|
|
76556
75801
|
});
|
|
76557
75802
|
} catch (err) {
|
|
@@ -76603,11 +75848,8 @@ async function startDaemon(config) {
|
|
|
76603
75848
|
},
|
|
76604
75849
|
connectRemote,
|
|
76605
75850
|
broadcast: (frame) => wsServer?.broadcastToOwners(frame),
|
|
76606
|
-
//
|
|
76607
|
-
// 透传让对方反向加我为联系人。无 tunnel(仅本机 ws)→ null,对方不自动反向(不造假数据)。
|
|
76608
|
-
// 决策 #16:身份不自报——本机 deviceId/ownerId/provider 由 exchange 签进 token 背书。
|
|
75851
|
+
// The peer address is the public tunnel URL; device and owner identity are signed token claims, not self-reported fields.
|
|
76609
75852
|
selfUrl: () => currentTunnelUrl,
|
|
76610
|
-
// device:connections 数据源。惰性闭包:wsServer 晚于本 deps 赋值,请求时才解引用。
|
|
76611
75853
|
listOwnerConnections: () => wsServer?.listOwnerConnections() ?? []
|
|
76612
75854
|
};
|
|
76613
75855
|
const loginFlow = new LoginFlow({
|
|
@@ -76707,17 +75949,14 @@ async function startDaemon(config) {
|
|
|
76707
75949
|
// dispatch / shift-internal 两组,只看 buildMethodHandlers 内部表会误报死方法)
|
|
76708
75950
|
feishuActive: () => feishuActive,
|
|
76709
75951
|
getAllHandlers: () => handlers,
|
|
76710
|
-
// onboarding 判定(info 帧字段)+ importExternal 台账落点
|
|
76711
75952
|
dataDir: config.dataDir,
|
|
76712
75953
|
manager,
|
|
76713
75954
|
projects: projectsStore,
|
|
76714
75955
|
workspace,
|
|
76715
75956
|
skills,
|
|
76716
75957
|
agents,
|
|
76717
|
-
// skills:list 按 tool 路由:codex → app-server skills/list;缺省 claude → SkillsScanner(同步包成 Promise)。
|
|
76718
75958
|
getSkillsForTool: (tool, cwd) => tool === "codex" ? listCodexSkills(cwd) : isCloudTool(tool) ? Promise.resolve([]) : Promise.resolve(skills.list({ cwd })),
|
|
76719
75959
|
history,
|
|
76720
|
-
// history:read 按 session.tool 路由:codex → adapter.historyReader(CodexHistoryReader);缺省 claude。
|
|
76721
75960
|
getHistoryReader: (tool) => getAdapter(tool).historyReader ?? history,
|
|
76722
75961
|
hydrateCodexResolvedMeta,
|
|
76723
75962
|
observer,
|
|
@@ -76727,54 +75966,30 @@ async function startDaemon(config) {
|
|
|
76727
75966
|
personaRegistry,
|
|
76728
75967
|
refreshCloudPersonas,
|
|
76729
75968
|
getTunnelUrl: () => currentTunnelUrl,
|
|
76730
|
-
// ready / info 帧的 mode = daemon CC spawn 模式('sdk' | 'tui')。UI 据此挂 XtermPanel +
|
|
76731
|
-
// 订阅 session:pty / session:control;业务帧名两种 mode 完全一致,UI 业务订阅代码不变
|
|
76732
75969
|
mode: config.mode,
|
|
76733
|
-
//
|
|
76734
|
-
// PR 2 阶段 httpToken 复用 owner WS token;noAuth 模式下为 null(UI 看到无 httpToken
|
|
76735
|
-
// 时禁用文件 GET/POST,保持 1.0 行为兼容)。
|
|
75970
|
+
// UI receives the resolved HTTP Bearer token; WS loopback exemption does not disable HTTP authentication.
|
|
76736
75971
|
getHttpBaseUrl,
|
|
76737
75972
|
httpToken: resolvedAuthToken,
|
|
76738
|
-
//
|
|
76739
|
-
// 根据 sessionId 反查 scope 写盘。
|
|
76740
|
-
//
|
|
76741
|
-
// sessionStore / getSessionScope 都走 manager 的跨 scope 公开 API(findOwnedSession /
|
|
76742
|
-
// findOwnedSessionScope)—— 否则 default-only SessionStore 找不到 persona owner-mode
|
|
76743
|
-
// session,attachment 整套 RPC 对 persona session 都会报 "session not found"。
|
|
76744
|
-
// 详见 fix(daemon) #703:file-sharing v1 预存 wiring bug,#701 把 desktop 默认 --tunnel
|
|
76745
|
-
// 后首次让 desktop 用户用上 file-sharing 才被暴露。
|
|
75973
|
+
// Resolve sessions across scopes; the bare store cannot locate persona-session attachment manifests.
|
|
76746
75974
|
attachment: {
|
|
76747
75975
|
groupFileStore,
|
|
76748
75976
|
sessionStore: { read: (sid) => manager.findOwnedSession(sid) },
|
|
76749
75977
|
getHttpBaseUrl,
|
|
76750
|
-
// HMAC sign secret = auth.json 唯一 token(authFile 恒非空,startDaemon 开头加载)。
|
|
76751
75978
|
getSignSecret: () => authFile.token,
|
|
76752
|
-
// group RPC + sign 都用:根据 sessionId 反查 scope。owner-mode persona session 走
|
|
76753
|
-
// 'persona/<pid>/owner',default 走 'default'。
|
|
76754
75979
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
76755
|
-
//
|
|
75980
|
+
// Guest attachment paths are guarded against persona and caller work roots.
|
|
76756
75981
|
personaRoot: import_node_path85.default.join(config.dataDir, "personas"),
|
|
76757
75982
|
usersRoot
|
|
76758
75983
|
},
|
|
76759
|
-
// workspace/git/history/skills/agents handler 共用的 guest path guard 锚点
|
|
76760
75984
|
personaRoot: import_node_path85.default.join(config.dataDir, "personas"),
|
|
76761
|
-
// v2 多人 persona 隔离:handler 派生 guest user-dir 放行
|
|
76762
75985
|
usersRoot,
|
|
76763
|
-
// v2 Phase 6: whoami handler 装在 owner principal.displayName + persona 解析
|
|
76764
75986
|
getOwnerIdentity,
|
|
76765
|
-
// 决策 #16: whoami owner.id = 本机 deviceId(路由主键,const,热切换不变)
|
|
76766
75987
|
ownerPrincipalId,
|
|
76767
75988
|
personaStore,
|
|
76768
|
-
// capability platform v3 inbox: handler 接 manager (post/list/markRead),
|
|
76769
|
-
// cascade 接 store (list 统计 deletedInboxEvents).
|
|
76770
75989
|
inboxManager,
|
|
76771
75990
|
inboxStore,
|
|
76772
|
-
// 联系人列表 store(device:connect 出站 / contact:handshake 入站落同一 store)
|
|
76773
75991
|
contactStore,
|
|
76774
|
-
//
|
|
76775
|
-
// 用本机登录票换 aud=对方设备 的票,现读 ownerIdentityStore(热切换后用新身份)。
|
|
76776
|
-
// 契约(ContactHandlerDeps.exchangeToken):null = 本机登录问题(未登录),
|
|
76777
|
-
// 其他失败原样抛给 handler——两种都会让握手硬失败,只是给对端的错误文案不同。
|
|
75992
|
+
// Exchange with the current login. Missing login returns null; exchange errors propagate and prevent this handshake's local store update.
|
|
76778
75993
|
exchangeToken: async (targetDeviceId) => {
|
|
76779
75994
|
const record4 = ownerIdentityStore.read();
|
|
76780
75995
|
if (!record4) return null;
|
|
@@ -76783,9 +75998,7 @@ async function startDaemon(config) {
|
|
|
76783
75998
|
api: config.clawosApi ?? DEFAULT_CLAWOS_API,
|
|
76784
75999
|
loginPass: record4.loginPass,
|
|
76785
76000
|
deviceId: targetDeviceId,
|
|
76786
|
-
//
|
|
76787
|
-
// 必须在其内失败/成功,否则对端先超时不落库、本机后换票成功落库 → 反向半连,
|
|
76788
|
-
// 破坏「两侧都不落库」不变量。4s 留 1s 余量给 ws 往返
|
|
76001
|
+
// Bound exchange time below the peer RPC deadline to reduce acknowledged/local-store divergence; the handshake is not a distributed transaction.
|
|
76789
76002
|
timeoutMs: 4e3
|
|
76790
76003
|
});
|
|
76791
76004
|
return r2.token;
|
|
@@ -76798,29 +76011,17 @@ async function startDaemon(config) {
|
|
|
76798
76011
|
throw e;
|
|
76799
76012
|
}
|
|
76800
76013
|
},
|
|
76801
|
-
// inbox:sendDm 用:sessionId → session 出身(复用 attachment 同款 findOwnedSessionScope)
|
|
76802
76014
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
76803
|
-
// contact:removed broadcast;复用 capability:tokenIssued 同款通路
|
|
76804
76015
|
broadcastToOwners: (frame) => wsServer?.broadcastToOwners(frame),
|
|
76805
|
-
// extension runtime (v1: local-mode only). Instance owned by daemon top
|
|
76806
|
-
// level so stopAll() runs in the shutdown hook below.
|
|
76807
76016
|
extensionRuntime,
|
|
76808
|
-
// extension sharing v1 — owner-side published-channels store.
|
|
76809
76017
|
publishedChannelStore,
|
|
76810
76018
|
bundleCache,
|
|
76811
|
-
// 飞书统一身份 Phase 1:登录 RPC handlers(auth:login:start / getIdentity / logout)
|
|
76812
|
-
// deviceId(决策 #15):getIdentity 返回给 UI 拼"我的设备标识"
|
|
76813
76019
|
feishuAuth: {
|
|
76814
76020
|
loginFlow,
|
|
76815
76021
|
ownerIdentityStore,
|
|
76816
76022
|
deviceId: authFile.deviceId,
|
|
76817
76023
|
serverKeyStore,
|
|
76818
|
-
//
|
|
76819
|
-
// 核心是 feishuActive 翻 false——否则 dispatcher 飞书 gate(下方 feishuActive 判定,每帧
|
|
76820
|
-
// 现读 let binding)退登后仍放行 gated 方法。一并把归属人重置回未登录初值 + 重建 handlers
|
|
76821
|
-
// (deps 持 ownerId/displayName 值快照,whoami/出站自报身份用),与登录置位三件套对称。
|
|
76822
|
-
// 不踢连:踢连是登录热切换换身份 ctx 的需要;退登只是降级,gate 每帧现读即时生效,
|
|
76823
|
-
// 既有连接继续用,避免改动连接生命周期 / wire 契约。
|
|
76024
|
+
// Logout resets the live identity and gate. Existing connections remain; getters immediately reflect the reset without rebuilding handlers.
|
|
76824
76025
|
onLogout: () => {
|
|
76825
76026
|
feishuActive = false;
|
|
76826
76027
|
ownerId = "";
|
|
@@ -76846,13 +76047,11 @@ async function startDaemon(config) {
|
|
|
76846
76047
|
fill: (args) => centralConnectorFill({ api: config.clawosApi ?? DEFAULT_CLAWOS_API, ...args })
|
|
76847
76048
|
}
|
|
76848
76049
|
},
|
|
76849
|
-
// 云盘角标(design 2026-09-19-cloud-volume-usage):拿不到一律 null,handler 自己折
|
|
76850
76050
|
cloudVolume: {
|
|
76851
76051
|
getLoginPass: () => ownerIdentityStore.read()?.loginPass ?? null,
|
|
76852
76052
|
fetchVolume: () => cloudGateway.fetchVolume(),
|
|
76853
76053
|
logger
|
|
76854
76054
|
},
|
|
76855
|
-
// 设置 → Dashboard(design 2026-09-23-cloud-persona-metrics §3.8):链接走 tunnel 公网地址(D2),签名与附件同一把 secret、不同域
|
|
76856
76055
|
dashboard: {
|
|
76857
76056
|
getHttpBaseUrl,
|
|
76858
76057
|
getSignSecret: () => authFile.token
|
|
@@ -76863,26 +76062,18 @@ async function startDaemon(config) {
|
|
|
76863
76062
|
// 这里的 FEISHU_LOGIN_REQUIRED 抛错只是防御纵深。
|
|
76864
76063
|
feishuDevice: feishuDeviceDeps,
|
|
76865
76064
|
logger,
|
|
76866
|
-
// shift v1:shift:list 桥接 handler 透传 ShiftStore 单例(同实例也喂给
|
|
76867
|
-
// shift-internal:* MCP loopback handler)。owner 过滤用 ownerPrincipalId(跨
|
|
76868
|
-
// daemon 稳定身份),跟我们 ShiftStore boot 时用的 provider 一致。
|
|
76869
76065
|
shiftStore,
|
|
76870
76066
|
shiftOwnerIdProvider: () => ownerPrincipalId,
|
|
76871
76067
|
// PR 收件箱:github:* handler spawn 本机 gh CLI,凭据是 clawd 的 GitHub 身份(GH_TOKEN),没有才是 gh 自己的登录态
|
|
76872
76068
|
ghRunner: createGhRunner({ token: () => githubIdentity.cached()?.token }),
|
|
76873
76069
|
githubIdentity,
|
|
76874
|
-
// persona web 分享:visitor:list owner RPC 读访客名单(同 exchange upsert 的实例)
|
|
76875
76070
|
visitorStore
|
|
76876
76071
|
})
|
|
76877
76072
|
};
|
|
76878
76073
|
const dispatchHandlers = buildPersonaDispatchHandlers({
|
|
76879
76074
|
personaDispatchManager,
|
|
76880
76075
|
store: dispatchStore,
|
|
76881
|
-
//
|
|
76882
|
-
// 不存在都返回 undefined,由 handler 判成「话题不存在」拒掉(编 topicId 绕不过去)。
|
|
76883
|
-
//
|
|
76884
|
-
// 只给 persona 名单:memberContactDeviceIds 是「可发 DM」的人类名单,不参与派活判定。
|
|
76885
|
-
// 在这里一并带上它,下一个读代码的人会以为它也是闸的一部分(P1 接 DM 时再按需要加)。
|
|
76076
|
+
// Only persona members authorize task dispatch; human DM members are not part of this gate. Missing channel/topic must reject.
|
|
76886
76077
|
getTopicMembership: (topicId) => {
|
|
76887
76078
|
const t2 = topicStore.get(topicId);
|
|
76888
76079
|
if (!t2) return void 0;
|
|
@@ -76890,10 +76081,7 @@ async function startDaemon(config) {
|
|
|
76890
76081
|
if (!ch) return void 0;
|
|
76891
76082
|
return { personas: [...ch.memberPersonas] };
|
|
76892
76083
|
},
|
|
76893
|
-
// 续派(spec 2026-07-21):任务包注入 worker session,复用投递同款注入路径。
|
|
76894
76084
|
sendFollowUp: (workerSessionId, text, meta) => manager.injectIntoSession(workerSessionId, text, meta),
|
|
76895
|
-
// 「这活是谁派的」:台账 sourcePersonaId + 续派包信封 from 都吃它。与首发任务包在
|
|
76896
|
-
// SessionManager 那侧的反查(buildDispatchTaskPack 的 from)是同一个字段、同一个查法。
|
|
76897
76085
|
getSessionPersonaId: (sessionId) => manager.findOwnedSession(sessionId)?.ownerPersonaId,
|
|
76898
76086
|
logger,
|
|
76899
76087
|
spawnB: async (args) => {
|
|
@@ -76915,22 +76103,15 @@ async function startDaemon(config) {
|
|
|
76915
76103
|
targetPersona: args.targetPersona,
|
|
76916
76104
|
prompt: args.prompt,
|
|
76917
76105
|
sourceJsonlPath,
|
|
76918
|
-
// 派活方写的 2-3 句摘要 → 任务包信封 <brief>(卡片头常显)
|
|
76919
76106
|
...args.brief ? { brief: args.brief } : {},
|
|
76920
|
-
// B 角色(跨设备):非空 → guest scope + creatorPrincipalId/creatorDisplayName = A。
|
|
76921
76107
|
guestPrincipalId: args.guestPrincipalId,
|
|
76922
76108
|
guestDisplayName: args.guestDisplayName,
|
|
76923
|
-
// T-31: 把 A 的飞书 union_id stamp 到 B SessionFile,供 cc prompt 身份卡 union_id 行
|
|
76924
76109
|
guestFeishuUnionId: args.guestFeishuUnionId,
|
|
76925
|
-
// T-20: 可选 model override 透到 SessionFile.model
|
|
76926
76110
|
...args.model ? { model: args.model } : {},
|
|
76927
|
-
// 一次性 engine override(spec 2026-08-24)透到 SessionFile.tool
|
|
76928
76111
|
...args.tool ? { tool: args.tool } : {},
|
|
76929
|
-
// butler 顶部入口(spec 2026-07-27):可选 label 透到 SessionFile.label(sidebar 显示)
|
|
76930
76112
|
...args.label ? { label: args.label } : {}
|
|
76931
76113
|
});
|
|
76932
76114
|
},
|
|
76933
|
-
// A 角色:从 ContactStore 取 peer 可达 URL + connect token,转发到对端 daemon /rpc。
|
|
76934
76115
|
forwardToPeer: async ({ targetDeviceId, targetPersona, prompt, model }) => {
|
|
76935
76116
|
const contact = contactStore.get(targetDeviceId);
|
|
76936
76117
|
if (!contact || !contact.remoteUrl || !contact.connectToken) {
|
|
@@ -76943,15 +76124,11 @@ async function startDaemon(config) {
|
|
|
76943
76124
|
contact: { remoteUrl: contact.remoteUrl, connectToken: contact.connectToken },
|
|
76944
76125
|
targetPersona,
|
|
76945
76126
|
prompt,
|
|
76946
|
-
// T-20: 跨设备也透 model,让远端 daemon 用同一 claude 模型 spawn B
|
|
76947
76127
|
...model ? { model } : {}
|
|
76948
76128
|
});
|
|
76949
76129
|
},
|
|
76950
|
-
// B 角色:判断 targetPersona 是否 public(跨设备授权边界,private 拒)。
|
|
76951
76130
|
getPersonaPublic: (personaId2) => personaRegistry.get(personaId2)?.public ?? false,
|
|
76952
|
-
// model 校验按 target persona 绑定 tool 选来源(claude / codex 模型集不同)
|
|
76953
76131
|
getPersonaDefaults: (personaId2) => personaRegistry.get(personaId2),
|
|
76954
|
-
// codex 模型表是运行时查 model/list 来的,校验必须问 capabilities(manager 侧有进程级缓存)
|
|
76955
76132
|
getCapabilities: (tool) => manager.getCapabilities(tool)
|
|
76956
76133
|
});
|
|
76957
76134
|
Object.assign(handlers, dispatchHandlers);
|
|
@@ -76981,7 +76158,6 @@ async function startDaemon(config) {
|
|
|
76981
76158
|
channelStore: subscriptionChannelStore,
|
|
76982
76159
|
hasPersona: (personaId2) => personaStore.has(personaId2),
|
|
76983
76160
|
personaDir: (personaId2) => personaStore.personaDirPath(personaId2),
|
|
76984
|
-
// 发布前落闸要看它现在配的引擎和模型(云上只跑 codex / pi,且模型跟着这次发布走)
|
|
76985
76161
|
personaEngine: (personaId2) => {
|
|
76986
76162
|
const p3 = personaRegistry.get(personaId2);
|
|
76987
76163
|
return p3 ? { ...p3.tool ? { tool: p3.tool } : {}, ...p3.model ? { model: p3.model } : {} } : void 0;
|
|
@@ -76997,12 +76173,10 @@ async function startDaemon(config) {
|
|
|
76997
76173
|
return probeLocalVersion(runCommand, pkg);
|
|
76998
76174
|
},
|
|
76999
76175
|
fetchArtifact: downloadArtifact(runCommand),
|
|
77000
|
-
// apt 条目的 version 按底座 Debian 仓库定(不是本机):一次出网查 madison,只要元数据不下载
|
|
77001
76176
|
debianPackage: lookupDebianPackage(),
|
|
77002
76177
|
githubIdentity,
|
|
77003
76178
|
githubApi: (token) => createGithubApi({ token }),
|
|
77004
76179
|
org: PERSONA_REPO_ORG,
|
|
77005
|
-
// 发布者认领(design 2026-09-24-cloud-persona-conversations §3.1):身份由中心从登录票里取;409 = 名字是别人的
|
|
77006
76180
|
claimPublisher: async (personaId2) => {
|
|
77007
76181
|
const loginPass = ownerIdentityStore.read()?.loginPass;
|
|
77008
76182
|
if (!loginPass) throw new Error("\u8FD8\u6CA1\u767B\u5F55 clawd\uFF1A\u5148\u5728\u8BBE\u7F6E\u91CC\u767B\u5F55\u98DE\u4E66\uFF0C\u518D\u53D1\u5E03");
|
|
@@ -77074,7 +76248,6 @@ async function startDaemon(config) {
|
|
|
77074
76248
|
cloud: larkBotCloud,
|
|
77075
76249
|
store: larkBotStore,
|
|
77076
76250
|
fetchResource: (appId, messageId, fileKey, maxBytes, type) => larkBotCloud.fetchResource(appId, messageId, fileKey, maxBytes, type),
|
|
77077
|
-
// 图片落 guest userWorkDir 下(capId = lark-<chat_id>):CC 经 --add-dir + allowRead carve 可 Read
|
|
77078
76251
|
mediaRootFor: (chatId) => import_node_path85.default.join(deriveUserWorkDir(`lark-${chatId}`, usersRoot), "lark-media"),
|
|
77079
76252
|
isShuttingDown: () => larkDraining,
|
|
77080
76253
|
logger: {
|
|
@@ -77157,7 +76330,6 @@ async function startDaemon(config) {
|
|
|
77157
76330
|
feishuActive: () => feishuActive
|
|
77158
76331
|
});
|
|
77159
76332
|
},
|
|
77160
|
-
// mutating RPC 经 HTTP 调用时 handler broadcast 帧的外推(http-router 在 response 送出后调)
|
|
77161
76333
|
fanOutBroadcast
|
|
77162
76334
|
};
|
|
77163
76335
|
const viewerAssetLoader = tryLoadViewerAssets(logger);
|
|
@@ -77186,17 +76358,11 @@ async function startDaemon(config) {
|
|
|
77186
76358
|
authedRpc,
|
|
77187
76359
|
...viewerAssetLoader ? { viewerAssetLoader } : {},
|
|
77188
76360
|
dashboardRoutes,
|
|
77189
|
-
// persona web 分享:访客换 token(POST /share/exchange)+ guest SPA serve(GET /s)
|
|
77190
76361
|
visitorExchange: exchangeVisitorToken,
|
|
77191
76362
|
...shareUiAssetLoader ? { shareUiAssetLoader } : {},
|
|
77192
|
-
// LarkBotConnector 事件入口(云端签名即凭证,无 Bearer)
|
|
77193
76363
|
larkBotEvent: larkBotEventEndpoint,
|
|
77194
|
-
// /files HMAC verify 用 auth.json token(与 attachment.signUrl 同源)。
|
|
77195
76364
|
getSignSecret: () => authFile.token,
|
|
77196
|
-
//
|
|
77197
|
-
// scope 切换:persona 会话 → 连接者 userWorkDir(与 connection-prompt 宣称的工作目录一致,
|
|
77198
|
-
// guest 对 persona 目录的读权不再连带看到 owner 上传的附件);非 persona 会话 → session.cwd
|
|
77199
|
-
// (项目目录本就是用户地盘)。签名 secret 与 /files HMAC 同源;tunnel 未启时返 503。
|
|
76365
|
+
// Persona uploads use the caller work directory so guests cannot see owner uploads through persona read access. Other sessions use their cwd.
|
|
77200
76366
|
attachmentUpload: {
|
|
77201
76367
|
groupFileStore,
|
|
77202
76368
|
getSessionScope: (sid) => manager.findOwnedSessionScope(sid),
|
|
@@ -77207,14 +76373,8 @@ async function startDaemon(config) {
|
|
|
77207
76373
|
},
|
|
77208
76374
|
getHttpBaseUrl: () => currentTunnelUrl ? getHttpBaseUrl() : null
|
|
77209
76375
|
},
|
|
77210
|
-
// POST /extensions/import lands in ~/.clawd/extensions/<id>/
|
|
77211
76376
|
extensionsRoot: () => extensionsRoot(),
|
|
77212
|
-
//
|
|
77213
|
-
// 1. 进程内实时切换身份(let binding + 值快照持有者重建)
|
|
77214
|
-
// 2. 广播 auth:login:done 给在线 owner 连接(UI 即时反馈)
|
|
77215
|
-
// 3. 踢掉所有 ws 连接(在线连接的 ctx 是旧身份)→ UI 自动重连 → 新身份 ctx →
|
|
77216
|
-
// People 立即解锁,不需要重启 daemon
|
|
77217
|
-
// 失败则广播 auth:login:failed。
|
|
76377
|
+
// On login, update live identity, notify owner clients, then reconnect WS clients to rebuild cached contexts.
|
|
77218
76378
|
feishuLogin: {
|
|
77219
76379
|
handleLoginCallback: async (params) => {
|
|
77220
76380
|
const result = await loginFlow.handleCallback(params);
|
|
@@ -77255,16 +76415,13 @@ async function startDaemon(config) {
|
|
|
77255
76415
|
{
|
|
77256
76416
|
manager,
|
|
77257
76417
|
getAdapter,
|
|
77258
|
-
// onboarding 判定:ready 帧携带 profile.json 的 onboardingCompletedAt
|
|
77259
76418
|
dataDir: config.dataDir,
|
|
77260
76419
|
getTunnelUrl: () => currentTunnelUrl,
|
|
77261
76420
|
getLogShipping: () => config.logShipping ? {
|
|
77262
76421
|
endpoint: config.logShipping.endpoint,
|
|
77263
76422
|
sampling: config.logShipping.sampling
|
|
77264
76423
|
} : null,
|
|
77265
|
-
// ready 帧 mode = daemon CC spawn 模式('sdk' | 'tui');UI 用它挂 XtermPanel
|
|
77266
76424
|
mode: config.mode,
|
|
77267
|
-
// file-sharing 字段:httpBaseUrl 跟 tunnel 状态走;httpToken 复用 owner WS token
|
|
77268
76425
|
getHttpBaseUrl,
|
|
77269
76426
|
httpToken: resolvedAuthToken
|
|
77270
76427
|
},
|
|
@@ -77272,22 +76429,14 @@ async function startDaemon(config) {
|
|
|
77272
76429
|
),
|
|
77273
76430
|
protocolVersion: PROTOCOL_VERSION,
|
|
77274
76431
|
authGate: authGate ?? void 0,
|
|
77275
|
-
//
|
|
77276
|
-
// noAuth localhost daemon 上访客 token 若命中 → attach guest ctx,否则 fallback owner
|
|
77277
|
-
// (非 persona-web 场景静默 return null;tunnel 模式走 AuthGate 不经此路)。参数名
|
|
77278
|
-
// tryVerifyCapabilityToken 不再准确,跨 local-ws-server 改名属 followup 清理。
|
|
76432
|
+
// Loopback visitor tokens still attach guest context; absent visitor context follows the local owner fallback.
|
|
77279
76433
|
tryVerifyCapabilityToken: (token) => {
|
|
77280
76434
|
const vis = verifyVisitorToken(authFile.token, token, Math.floor(Date.now() / 1e3));
|
|
77281
76435
|
if (vis.ok) return visitorGuestContext(vis.payload);
|
|
77282
76436
|
return null;
|
|
77283
76437
|
},
|
|
77284
|
-
// file-sharing HTTP 路由复用 daemon 同端口(spec §5 第 3 条);router 自己处理 auth + 404
|
|
77285
76438
|
httpRequestHandler: httpRouter,
|
|
77286
|
-
//
|
|
77287
|
-
// daemon 是 pendingQuestions 的唯一 source of truth;新 client 接入 / 刷新页面时
|
|
77288
|
-
// 把当前所有未决 question 以 session:question 帧定向回放,让 UI 不再误显示 Ended。
|
|
77289
|
-
// 形状与 reducer permission_request 分支构造的 session:question 帧一致;UI 不区分
|
|
77290
|
-
// 首播 vs 重放(last-write-wins,详见 protocol/events.ts JSDoc)。
|
|
76439
|
+
// Replay daemon-owned pending questions on subscribe. Consumers treat replay and first delivery identically.
|
|
77291
76440
|
onSubscribe: (client, sessionId) => {
|
|
77292
76441
|
const runner = manager.getActive(sessionId);
|
|
77293
76442
|
if (!runner) return;
|
|
@@ -77454,8 +76603,6 @@ ${bar}
|
|
|
77454
76603
|
scope,
|
|
77455
76604
|
sessionId
|
|
77456
76605
|
})),
|
|
77457
|
-
// 双根扫描(spec 2026-08-06 §5):legacy cwd 根(迁移前落盘点,30 天自然老化)+
|
|
77458
|
-
// userWorkDir 根(迁移后 upload 落盘点,与 getAttachmentBaseDir 同源派生)
|
|
77459
76606
|
getSessionCwd: (sid) => manager.findOwnedSession(sid)?.cwd ?? null,
|
|
77460
76607
|
getUserWorkDir: attachmentUserWorkDir,
|
|
77461
76608
|
logger
|