@clawos-dev/clawd 0.2.618 → 0.2.620
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 +9 -16
- package/dist/cli.cjs +153 -700
- package/dist/cloud-persona-conversations.html +65 -65
- package/dist/dispatch/mcp-server.cjs +9 -29
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/collect-files.mjs +6 -15
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/list-projects.mjs +1 -2
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/record-push.mjs +3 -5
- package/dist/persona-defaults/persona-app-builder/.claude/skills/app-builder-projects/scripts/render-sql.mjs +4 -6
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +1 -7
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/main.ts +3 -9
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/polyfill.ts +3 -9
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/server/src/table-name.ts +2 -5
- package/dist/persona-defaults/persona-app-builder/extension-kit/examples/nestjs-react/web/src/App.jsx +1 -8
- package/dist/persona-defaults/persona-app-builder/mcp/clawd-app-builder/mcp-server.cjs +9 -16
- package/dist/persona-defaults/persona-clawd-butler/.claude/skills/clawd-session-import/scripts/link-sessions.mjs +7 -23
- package/dist/persona-defaults/persona-clawd-butler/.claude/skills/scan-sessions-suggest-personas/scripts/extract-signals.mjs +6 -14
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/collect-files.mjs +6 -15
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/list-projects.mjs +1 -2
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/record-push.mjs +3 -5
- package/dist/persona-defaults/persona-dataclaw-builder/.claude/skills/app-builder-projects/scripts/render-sql.mjs +4 -6
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/app.module.ts +2 -6
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/dataclaw/dataclaw.client.ts +2 -4
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/dataclaw/dataclaw.controller.ts +1 -2
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/main.ts +4 -11
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/server/src/polyfill.ts +1 -5
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/api.js +1 -4
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/BarChart.jsx +0 -1
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/ResultCards.jsx +0 -1
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/components/ResultTable.jsx +0 -1
- package/dist/persona-defaults/persona-dataclaw-builder/extension-kit/examples/nestjs-react/web/src/enums.js +2 -3
- package/dist/persona-defaults/persona-dataclaw-builder/mcp/clawd-app-builder/mcp-server.cjs +9 -16
- package/dist/share-ui/assets/{guest-CAiFdsmV.js → guest-nVklLeTD.js} +62 -62
- package/dist/share-ui/guest.html +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -7835,7 +7835,8 @@ var init_persona_schemas = __esm({
|
|
|
7835
7835
|
size: external_exports.string().optional(),
|
|
7836
7836
|
/**
|
|
7837
7837
|
* 云上 task 运行模式下一个 task Pod 同时跑几个 task(design 2026-09-19-cloud-task-mode §3.6),没写按 4。
|
|
7838
|
-
*
|
|
7838
|
+
* 只有发布上云的流水线读它(cloud-host/pipeline/deploy-file.mjs 校验正整数、不合格拒发);本机跑不看。
|
|
7839
|
+
* 发布页的资源预估卡通过 persona:update 保存,下次发布生效。
|
|
7839
7840
|
*/
|
|
7840
7841
|
taskConcurrency: external_exports.number().int().positive().optional(),
|
|
7841
7842
|
createdAt: external_exports.number(),
|
|
@@ -7951,6 +7952,7 @@ var init_persona_schemas = __esm({
|
|
|
7951
7952
|
tool: external_exports.string().optional(),
|
|
7952
7953
|
// 云上任务 Pod 的规格档:只收四档之一(PersonaFile 层是 string,边界在这里收窄)
|
|
7953
7954
|
size: PersonaSizeSchema.optional(),
|
|
7955
|
+
taskConcurrency: PersonaFileSchema.shape.taskConcurrency,
|
|
7954
7956
|
personality: external_exports.string().optional(),
|
|
7955
7957
|
// 人格补充层正文(见 PersonaInfoResponseSchema.personalityLocal)。与 personality 各写
|
|
7956
7958
|
// 各层、互不影响;'' = 清空补充层。预装 persona 的编辑入口写的就是这个字段。
|
|
@@ -15654,18 +15656,9 @@ var init_claude_history = __esm({
|
|
|
15654
15656
|
this.fileHistoryRoot = import_node_path5.default.join(base, "file-history");
|
|
15655
15657
|
}
|
|
15656
15658
|
/**
|
|
15657
|
-
*
|
|
15658
|
-
*
|
|
15659
|
-
*
|
|
15660
|
-
* EnterWorktree)会把 `<tsid>.jsonl` 挪到新 hash 目录,而调用方手里的 cwd 可能仍是老值——
|
|
15661
|
-
* persona session 尤其顽固:fork 出的新 session 因带 ownerPersonaId 被 manager 强制
|
|
15662
|
-
* 改写 cwd 回 persona 根(session/manager.ts derivePersonaSpawnCwd),与 jsonl 实际
|
|
15663
|
-
* 落点(源文件所在目录,见 session/fork.ts)永久分家。
|
|
15664
|
-
* 拼不出路径时按 tsid 全局兜底(与 fork.ts / observer 同款),命中则用它所在目录。
|
|
15665
|
-
*
|
|
15666
|
-
* 兜底失败仍返回按 cwd 算的目录:下游 readJsonlLines / readdir 都容忍不存在,
|
|
15667
|
-
* 保持"查无此 session 返回空"的既有语义,不抛。
|
|
15668
|
-
* bug: quick-ask-blank-after-worktree(fork 出的快速问答面板整片空白)
|
|
15659
|
+
* Resolve the transcript directory, falling back to a global tsid search when cwd is stale.
|
|
15660
|
+
* EnterWorktree relocation and persona forks can leave cwd pointing at the original directory.
|
|
15661
|
+
* If no transcript is found, return the cwd-derived directory; readers tolerate it being absent.
|
|
15669
15662
|
*/
|
|
15670
15663
|
resolveSessionDir(cwd, toolSessionId) {
|
|
15671
15664
|
const byCwd = import_node_path5.default.join(this.projectsRoot, cwdToHashDir(cwd));
|
|
@@ -15674,14 +15667,8 @@ var init_claude_history = __esm({
|
|
|
15674
15667
|
return found ? import_node_path5.default.dirname(found) : byCwd;
|
|
15675
15668
|
}
|
|
15676
15669
|
/**
|
|
15677
|
-
*
|
|
15678
|
-
*
|
|
15679
|
-
* 对外开这一口是为了让 adapter 之外的消费者**不必自己拼路径**:
|
|
15680
|
-
* 「按 cwd 直取 → 按 tsid 全局兜底」这套 relocation 逻辑曾被抄成第二份,
|
|
15681
|
-
* 规则一改就静默漂移。路径编码(cwdToHashDir)与兜底顺序全仓只此一处。
|
|
15682
|
-
*
|
|
15683
|
-
* 返回 null 而不是不存在的路径:调用方要能区分「还没写盘」与「读到空 transcript」——
|
|
15684
|
-
* 后者会让核验把一条刚 spawn 的线判成假完成。
|
|
15670
|
+
* Resolve through the shared cwd/tsid relocation logic.
|
|
15671
|
+
* Return null before the file exists so callers can distinguish not-yet-written from empty history.
|
|
15685
15672
|
*/
|
|
15686
15673
|
resolveTranscriptFile(cwd, toolSessionId) {
|
|
15687
15674
|
const file = import_node_path5.default.join(this.resolveSessionDir(cwd, toolSessionId), `${toolSessionId}.jsonl`);
|
|
@@ -15970,9 +15957,8 @@ var init_claude_history = __esm({
|
|
|
15970
15957
|
return result;
|
|
15971
15958
|
}
|
|
15972
15959
|
/**
|
|
15973
|
-
*
|
|
15974
|
-
*
|
|
15975
|
-
* 该文件跳过而不是整体失败
|
|
15960
|
+
* Preview the rewind: current content is old, snapshot backup is new.
|
|
15961
|
+
* structuredPatch supplies hunks and +/- totals; unreadable files are skipped individually.
|
|
15976
15962
|
*/
|
|
15977
15963
|
computeRewindDiff(args) {
|
|
15978
15964
|
const snapshots = this.readFileHistorySnapshots({
|
|
@@ -16605,10 +16591,8 @@ var init_claude = __esm({
|
|
|
16605
16591
|
label: "Effort",
|
|
16606
16592
|
description: "Reasoning effort level",
|
|
16607
16593
|
scope: "tool-specific",
|
|
16608
|
-
// CC
|
|
16609
|
-
//
|
|
16610
|
-
// xhigh,但不传 --effort 时实际生效的是 ~/.claude/settings.json 的 effortLevel,
|
|
16611
|
-
// 而它的常见值就是 high;用 high 作 persona 补齐值对既有会话行为最中性。
|
|
16594
|
+
// CC 2.1.187 --effort values. Use high when backfilling persona defaults to match common
|
|
16595
|
+
// settings.json effortLevel; omitting --effort would instead follow that file.
|
|
16612
16596
|
options: [
|
|
16613
16597
|
{ value: "low", label: "Low" },
|
|
16614
16598
|
{ value: "medium", label: "Medium" },
|
|
@@ -16679,7 +16663,6 @@ var init_claude = __esm({
|
|
|
16679
16663
|
this.logger?.info("claude spawn", { cmd, args, cwd: ctx.cwd });
|
|
16680
16664
|
return child;
|
|
16681
16665
|
}
|
|
16682
|
-
// 暴露内部工具给测试直接校验 args 组合
|
|
16683
16666
|
buildSpawnArgs(ctx) {
|
|
16684
16667
|
return buildSpawnArgs(ctx);
|
|
16685
16668
|
}
|
|
@@ -45374,7 +45357,6 @@ var SessionStoreFactory = class {
|
|
|
45374
45357
|
dataDir;
|
|
45375
45358
|
bareStore = null;
|
|
45376
45359
|
vmOwnerStores = /* @__PURE__ */ new Map();
|
|
45377
|
-
// vmGuest 索引 key = `${pid}::${capId}`(按 cap.id 隔离,一张 cap 一个目录)
|
|
45378
45360
|
vmGuestStores = /* @__PURE__ */ new Map();
|
|
45379
45361
|
constructor(opts) {
|
|
45380
45362
|
this.dataDir = opts.dataDir;
|
|
@@ -45469,9 +45451,7 @@ function buildGuestSettingsV1() {
|
|
|
45469
45451
|
sandbox: {
|
|
45470
45452
|
enabled: true,
|
|
45471
45453
|
autoAllowBashIfSandboxed: true,
|
|
45472
|
-
// Bash 自动过沙箱;文件工具 dontAsk 自动拒(最严格只 Bash)
|
|
45473
45454
|
allowUnsandboxedCommands: false,
|
|
45474
|
-
// 关 dangerouslyDisableSandbox 逃逸口
|
|
45475
45455
|
filesystem: {
|
|
45476
45456
|
denyRead: ["~/"],
|
|
45477
45457
|
denyWrite: [],
|
|
@@ -45952,13 +45932,11 @@ function cloneState(s2) {
|
|
|
45952
45932
|
// 浅拷贝:下方 trackBackgroundTask 在 next 上原地增删,不能共享调用方数组(纯函数契约)
|
|
45953
45933
|
pendingBgTasks: s2.pendingBgTasks ? s2.pendingBgTasks.slice() : void 0,
|
|
45954
45934
|
stdinClosed: s2.stdinClosed,
|
|
45955
|
-
//
|
|
45956
|
-
// reduce 内 push/shift 在调用方原 state 上副作用(reducer 纯函数契约)
|
|
45935
|
+
// 队列须复制,避免 push / shift 修改调用方 state;基础类型可直接复用。
|
|
45957
45936
|
readyForSend: s2.readyForSend,
|
|
45958
45937
|
pendingSend: s2.pendingSend.slice(),
|
|
45959
45938
|
// 同理浅拷贝:入队 / 出队都在 next 上原地改
|
|
45960
45939
|
queuedSends: s2.queuedSends.slice(),
|
|
45961
|
-
// per-turn sender (spec §4.3):SenderInfo 只在 case 'send' 覆盖,其它 kind 直接复用引用
|
|
45962
45940
|
currentTurnSender: s2.currentTurnSender
|
|
45963
45941
|
};
|
|
45964
45942
|
}
|
|
@@ -46132,12 +46110,8 @@ function buildSpawnContext(state, deps) {
|
|
|
46132
46110
|
permissionMode: file.permissionMode,
|
|
46133
46111
|
effort: file.effort,
|
|
46134
46112
|
env,
|
|
46135
|
-
// Phase 2 capability platform (plan §3): personaRoot 透传给 buildSpawnArgs;
|
|
46136
|
-
// 内部 shouldSandbox(cwd, personaRoot) 决定是否注入 --settings sandbox-settings.
|
|
46137
46113
|
personaRoot: deps.personaRoot,
|
|
46138
46114
|
...mcpConfigs.length > 0 ? { mcpConfigs } : {},
|
|
46139
|
-
// shift v1:把 SessionFile.ownerPersonaId 透传到 SpawnContext;buildSpawnArgs 等下游
|
|
46140
|
-
// 不直接读这个字段(只读 env),但保留显式字段方便 daemon 内部诊断/日志。
|
|
46141
46115
|
personaId: personaId2
|
|
46142
46116
|
};
|
|
46143
46117
|
const meta = state.subSessionMeta;
|
|
@@ -46499,8 +46473,7 @@ function applyCommand(state, command, deps) {
|
|
|
46499
46473
|
});
|
|
46500
46474
|
return { state: next, effects };
|
|
46501
46475
|
}
|
|
46502
|
-
//
|
|
46503
|
-
// 中断也走这条(manager.interrupt 转发),"中断 = 队列作废"是老板拍的语义。
|
|
46476
|
+
// 中断也通过 dequeue 撤队列。
|
|
46504
46477
|
case "dequeue": {
|
|
46505
46478
|
if (next.queuedSends.length === 0) return { state: next, effects };
|
|
46506
46479
|
next.queuedSends = command.id ? next.queuedSends.filter((q3) => q3.id !== command.id) : [];
|
|
@@ -46802,11 +46775,7 @@ function reduceSessionInner(state, input, deps) {
|
|
|
46802
46775
|
const eventEffects = [
|
|
46803
46776
|
...pushEventToBufferInPlace(next, { kind: "error", message: input.message }, deps),
|
|
46804
46777
|
...pushEventToBufferInPlace(next, { kind: "turn_end" }, deps)
|
|
46805
|
-
//
|
|
46806
|
-
// jsonl 追加一行 turn_duration(runner.ts case 'append-turn-duration',resume 场景
|
|
46807
|
-
// toolSessionId 已 stamp 时会真落盘)。它的用途是"SDK 模式 CC 不写 turn 边界、
|
|
46808
|
-
// daemon 补写",而这里 CC 根本没跑过这一轮——补边界等于往历史里插一个空 turn,
|
|
46809
|
-
// 且不可回滚。proc-exit 的 interrupted 注入走同一条路但语义不同:那边 turn 真发生过。
|
|
46778
|
+
// 此轮未真正执行,不能向真实 CC jsonl 补空 turn 边界;与发生过执行的 proc-exit 中断不同。
|
|
46810
46779
|
].filter((e) => e.kind !== "append-turn-duration");
|
|
46811
46780
|
return {
|
|
46812
46781
|
state: next,
|
|
@@ -47191,28 +47160,19 @@ var SessionRunner = class {
|
|
|
47191
47160
|
// sub-session idle-kill timer ref;key = sessionId(实际同一 runner 只对应一个 session,
|
|
47192
47161
|
// 但 reducer Effect schema 带 sessionId 作为唯一键,对齐 schedule/cancel 配对)
|
|
47193
47162
|
idleKillTimers = /* @__PURE__ */ new Map();
|
|
47194
|
-
//
|
|
47195
|
-
// tool_use(kind='tool_call')与 tool_result 通过 toolUseId 配对。tool_use 提前到,
|
|
47196
|
-
// 等 tool_result 来时反查 path 调 onFileEdit。条目 in-flight 期间很少(个位数),
|
|
47197
|
-
// 不需要 LRU;session 退出时整个 runner 销毁自然清理。
|
|
47163
|
+
// 按 toolUseId 等待成功的编辑结果才回写文件清单;退出时清理,避免迟到结果重建已删清单。
|
|
47198
47164
|
pendingFileEdits = /* @__PURE__ */ new Map();
|
|
47199
47165
|
getState() {
|
|
47200
47166
|
return this.state;
|
|
47201
47167
|
}
|
|
47202
|
-
// 外部推送 input 到 reducer,然后执行产出的 effects
|
|
47203
47168
|
input(inputMsg) {
|
|
47204
47169
|
const adapter = this.hooks.adapter;
|
|
47205
47170
|
const deps = {
|
|
47206
|
-
// file-sharing (spec §6 PR 3):在 stdout-line 解析时同步观察 file-edit 工具事件,
|
|
47207
|
-
// 配对 tool_use ↔ tool_result 调 onFileEdit。原 reducer 路径不变。
|
|
47208
47171
|
parseLine: (l2) => {
|
|
47209
47172
|
const events = adapter.parseLine(l2);
|
|
47210
47173
|
if (this.hooks.onFileEdit) this.observeForFileEdit(events);
|
|
47211
47174
|
return events;
|
|
47212
47175
|
},
|
|
47213
|
-
// Persona dispatch (Task 9):原 in-place `@persona/<id>` 注入路径已删,cc 自己看到字面
|
|
47214
|
-
// token 会调 personaDispatch MCP tool 委派(dispatch.mcp.json 通过 cc --mcp-config 加载,
|
|
47215
|
-
// CLAUDE.md 教 cc 触发条件)。encodeStdin 现在是 adapter 的简单 passthrough。
|
|
47216
47176
|
encodeStdin: (t2, ctx) => adapter.encodeStdin(t2, ctx) ?? "",
|
|
47217
47177
|
bufferCap: this.hooks.bufferCap ?? 500,
|
|
47218
47178
|
now: this.hooks.now ?? Date.now,
|
|
@@ -47222,20 +47182,11 @@ var SessionRunner = class {
|
|
|
47222
47182
|
ownerDeviceId: this.hooks.ownerDeviceId,
|
|
47223
47183
|
ownerFeishuUnionId: this.hooks.ownerFeishuUnionId,
|
|
47224
47184
|
personaRoot: this.hooks.personaRoot,
|
|
47225
|
-
// 单栏 refactor (spec 2026-06-02 §5.1): cc 子进程 env 注入 CLAWD_DAEMON_URL,让 assistant
|
|
47226
|
-
// MCP server 拼 /rpc/<method>(Bearer)调 daemon。null = HTTP RPC 未启。
|
|
47227
47185
|
getDaemonUrl: this.hooks.getDaemonUrl,
|
|
47228
|
-
// clawd 管家 (spec 2026-07-22-clawd-butler-persona §3.1): cc 子进程 env 注入 CLAWOS_API,
|
|
47229
|
-
// 让 clawd 管家 skill fetch `${CLAWOS_API}/docs/**` 线上文档。
|
|
47230
47186
|
getClawosApi: this.hooks.getClawosApi,
|
|
47231
|
-
// daemon 级 MCP server 列表闭包透传;见 reducer.buildSpawnContext 派生
|
|
47232
|
-
// SpawnContext.mcpConfigs + 按 DISPATCH_MCP_ID 判断 dispatch 教学注入
|
|
47233
47187
|
getMcpConfigs: this.hooks.getMcpConfigs,
|
|
47234
|
-
// 人格补充层读闭包透传;reducer.buildSpawnContext 按 SessionFile.ownerPersonaId 查,
|
|
47235
|
-
// 有内容就作为最后一段追加进 extraSystemPrompt
|
|
47236
47188
|
readPersonalityLocal: this.hooks.readPersonalityLocal,
|
|
47237
47189
|
lookupDispatchByBSessionId: this.hooks.lookupDispatchByBSessionId,
|
|
47238
|
-
// ReadyGate v2:透传 mode 让 reducer send / ready-detected 分支决定走暂存队列还是直写
|
|
47239
47190
|
mode: this.hooks.mode,
|
|
47240
47191
|
// [RG-DBG] 注入 logger 让 reducer rgDbg 走 pino 进 clawd.log;定位完跟 rgDbg 一起删
|
|
47241
47192
|
logger: this.hooks.logger
|
|
@@ -47261,9 +47212,7 @@ var SessionRunner = class {
|
|
|
47261
47212
|
this.recoveringCloud = false;
|
|
47262
47213
|
}
|
|
47263
47214
|
}
|
|
47264
|
-
//
|
|
47265
|
-
// 真停下来再 ack 给前端,从而避免"删 worktree 时 CC 还活着持有文件锁"的 race。
|
|
47266
|
-
// 已经停止则立即 resolve;超时(默认 3000ms)抛错由上层透传成 RPC error
|
|
47215
|
+
// 等 procAlive=false 后返回,避免停止 RPC ack 后仍持文件锁;已停止立即 resolve,超时报错。
|
|
47267
47216
|
async waitUntilStopped(timeoutMs = DEFAULT_WAIT_STOP_TIMEOUT_MS) {
|
|
47268
47217
|
if (!this.state.procAlive) return;
|
|
47269
47218
|
return new Promise((resolve9, reject) => {
|
|
@@ -47321,15 +47270,8 @@ var SessionRunner = class {
|
|
|
47321
47270
|
if (this.state.procAlive) this.input({ kind: "proc-exit", code: null });
|
|
47322
47271
|
}
|
|
47323
47272
|
/**
|
|
47324
|
-
*
|
|
47325
|
-
*
|
|
47326
|
-
* 1) tool_call kind + tool ∈ FILE_EDIT_TOOLS → cache toolUseId → { tool, relPath }
|
|
47327
|
-
* relPath 从 input 里取(Write/Edit/MultiEdit 是 file_path,NotebookEdit 是 notebook_path)。
|
|
47328
|
-
* 2) tool_result kind + cache 命中 + 非 error → 调 onFileEdit + 删 cache
|
|
47329
|
-
* 3) tool_result kind + cache 命中 + 是 error → 删 cache,不调(spec 只要"成功"才入清单)
|
|
47330
|
-
* 4) tool_call 但 input 没有 path → 不 cache(防御)
|
|
47331
|
-
*
|
|
47332
|
-
* 不破坏 reducer 路径——纯只读扫描 events 数组。
|
|
47273
|
+
* 只读配对 tool_call / tool_result;有路径且成功的文件编辑调用 onFileEdit。
|
|
47274
|
+
* 错误结果只清 cache,缺路径不登记,不改变 reducer 事件流。
|
|
47333
47275
|
*/
|
|
47334
47276
|
observeForFileEdit(events) {
|
|
47335
47277
|
const cb = this.hooks.onFileEdit;
|
|
@@ -47365,9 +47307,7 @@ var SessionRunner = class {
|
|
|
47365
47307
|
}
|
|
47366
47308
|
}
|
|
47367
47309
|
}
|
|
47368
|
-
//
|
|
47369
|
-
// —— CC 侧会异步回写匹配 request_id 的 `control_response` 帧,
|
|
47370
|
-
// 在 stdout 行处理入口被 tryHandleControlResponse 拦截并 resolve pending
|
|
47310
|
+
// CC control_request 按 request_id 匹配异步 control_response;退出或超时拒绝等待者。
|
|
47371
47311
|
sendControlRequest(subtype, extraRequestFields = {}, opts = {}) {
|
|
47372
47312
|
const proc = this.proc;
|
|
47373
47313
|
if (!proc || !this.state.procAlive) {
|
|
@@ -47436,7 +47376,6 @@ var SessionRunner = class {
|
|
|
47436
47376
|
}
|
|
47437
47377
|
this.pendingControlRequests.clear();
|
|
47438
47378
|
}
|
|
47439
|
-
// 执行单个 effect。每种 effect 对应一次具体副作用,不跨种类组合
|
|
47440
47379
|
applyEffect(effect) {
|
|
47441
47380
|
switch (effect.kind) {
|
|
47442
47381
|
case "spawn":
|
|
@@ -47654,12 +47593,8 @@ var SessionRunner = class {
|
|
|
47654
47593
|
});
|
|
47655
47594
|
}
|
|
47656
47595
|
/**
|
|
47657
|
-
* stdin
|
|
47658
|
-
*
|
|
47659
|
-
* kill 再 spawn),这道闸兜的是 permission / 问答回写等异步路径:它们的 effect 可能在
|
|
47660
|
-
* turn_end 关 stdin 之后才被执行。
|
|
47661
|
-
*
|
|
47662
|
-
* recorder tap 收在这里,保证"写成功才录",录制流与真实字节流不分叉。
|
|
47596
|
+
* stdin 唯一入口:EOF 后拒绝权限 / 问答等迟到写入,避免 EPIPE。
|
|
47597
|
+
* 正常 send 由 reducer 先收旧进程再 spawn;仅成功写入才 tap recorder。
|
|
47663
47598
|
*/
|
|
47664
47599
|
writeStdin(payload) {
|
|
47665
47600
|
const stdin = this.proc?.stdin;
|
|
@@ -47674,10 +47609,8 @@ var SessionRunner = class {
|
|
|
47674
47609
|
this.recorder?.tapStdinWrite(payload);
|
|
47675
47610
|
return true;
|
|
47676
47611
|
}
|
|
47677
|
-
//
|
|
47678
|
-
//
|
|
47679
|
-
// 是因为 CC 在 streaming hang / 等 control_response / API 半连接 等场景下不响应 SIGTERM,
|
|
47680
|
-
// 直接 SIGKILL 一次到位。代价是 CC 来不及落盘当前轮 jsonl,但 observer 路径已经回灌过。
|
|
47612
|
+
// 按 reducer 指定信号 kill,不升级或另设策略;SIGKILL 避免 CC 挂起时不响应 SIGTERM。
|
|
47613
|
+
// 代价是当前轮 CC 尾部 jsonl 可能未落盘。
|
|
47681
47614
|
doKill(signal) {
|
|
47682
47615
|
if (!this.proc) return;
|
|
47683
47616
|
try {
|
|
@@ -47774,16 +47707,13 @@ function makeInitialState(file, subSessionMeta) {
|
|
|
47774
47707
|
procAlive: false,
|
|
47775
47708
|
seenUuids: [],
|
|
47776
47709
|
subSessionMeta,
|
|
47777
|
-
// 后台任务台账(close-stdin 判据);由 subagent_progress 事件流维护,spawn 时天然为空
|
|
47778
47710
|
pendingBgTasks: [],
|
|
47779
47711
|
stdinClosed: false,
|
|
47780
47712
|
// ReadyGate v2:默认值兼容 SDK happy path(直写 stdin,不走队列)。
|
|
47781
47713
|
// TUI 模式由 reducer send 分支按 deps.mode='tui' 主动翻 readyForSend=false
|
|
47782
47714
|
readyForSend: true,
|
|
47783
47715
|
pendingSend: [],
|
|
47784
|
-
// 输入框排队队列(spec 2026-08-31-chat-input-queue-design):初始空
|
|
47785
47716
|
queuedSends: [],
|
|
47786
|
-
// per-turn sender 2026-07-08 spec §4.3:初始 null;case 'send' 首次覆盖
|
|
47787
47717
|
currentTurnSender: null
|
|
47788
47718
|
};
|
|
47789
47719
|
}
|
|
@@ -47806,76 +47736,31 @@ var SessionManager = class {
|
|
|
47806
47736
|
// tool id → capabilities 快照(仅 capabilities:get RPC 缓存用;contextWindowSize 走 adapter.resolveContextWindow,无需缓存)。
|
|
47807
47737
|
// loadedAt 给 engine:status 显示「模型表上次刷新时间」;refresh 走 getCapabilities({ refresh:true }) 整条覆盖
|
|
47808
47738
|
capabilitiesCache = /* @__PURE__ */ new Map();
|
|
47809
|
-
//
|
|
47810
|
-
// synth uuid = daemon 在 reducer 'send' 命令里生成、写进 buffer 里 user_text 事件的 uuid
|
|
47811
|
-
// real uuid = CC 写 jsonl 时给 user 行分配的 uuid(也是 file-history-snapshot.messageId 的来源)
|
|
47812
|
-
// 由 observer 监听 jsonl user 行后调 recordRealUserUuid 建立映射;rewind 系列 RPC 在
|
|
47813
|
-
// 入参 / 出参做转译,保证 UI 看到的 uuid 始终是 events 流里的 synth uuid
|
|
47739
|
+
// observer 建立 synth user uuid → jsonl real uuid 映射;rewind 对外始终使用 UI 的 synth uuid。
|
|
47814
47740
|
realUuidBySynth = /* @__PURE__ */ new Map();
|
|
47815
|
-
//
|
|
47816
|
-
//
|
|
47817
|
-
//
|
|
47818
|
-
// 语义澄清:`turn_duration` 是 CC 报的原始信号("本轮 API 调用完了"),**不代表 turn 真的结束**
|
|
47819
|
-
//(背景 agent 可能还在跑)。屏幕 5s 稳定 + 无 popup 才是"确认信号"。
|
|
47820
|
-
// 两者 AND 后 daemon 才产生真正的 `turn_end` 事件送给 reducer。
|
|
47821
|
-
//
|
|
47822
|
-
// pending 不设截止时间:屏幕稳定的时机由 CC UI 决定,可能几秒也可能几分钟。观察者以
|
|
47823
|
-
// "屏幕真的稳定"作为触发点,signal-driven 而非 timer-driven。
|
|
47824
|
-
//
|
|
47825
|
-
// 清理点:newSession / stop / session-delete / stopAll。
|
|
47826
|
-
// sessionId → 被 gate drop 的 turn_end 携带的 durationMs(可能 undefined);flush 注入时回填
|
|
47741
|
+
// TUI turn_duration 必须与屏幕稳定、无 popup 同时成立才形成 turn_end。
|
|
47742
|
+
// gate 未开时暂存时长,等 notifyScreenIdle 确认;不设截止时间、不凭屏幕信号单独补 turn_end。
|
|
47743
|
+
// new / stop / delete / stopAll 清理。
|
|
47827
47744
|
pendingTurnDurationSignals = /* @__PURE__ */ new Map();
|
|
47828
|
-
//
|
|
47829
|
-
// default scope 直接复用 deps.store;persona scope(owner / listener)第一次访问时按需创建并缓存。
|
|
47830
|
-
// 取代旧的 storesByAgent —— agentId 概念由 SessionScope 取代,路径即身份,
|
|
47831
|
-
// SubSessionMeta 由 scope 派生(metaFromScope),不再需要运行时 cache 或 ownerPersonaId 兜底。
|
|
47745
|
+
// 无 storeFactory 时按 scope 缓存 SessionStore;default 复用 deps.store。
|
|
47832
47746
|
storesByScope = /* @__PURE__ */ new Map();
|
|
47833
|
-
// persona-bound
|
|
47834
|
-
// 透传 owner 路径白名单 EventFrame(routeFromRunner 决定哪些 type 进入),listener 端按
|
|
47835
|
-
// `frame.type` narrow 出 'session:event'(ParsedEvent)/ 'session:status'(procAlive 同步)等。
|
|
47836
|
-
// 这样 owner / listener 共用同一组事件字面量,新增跨端信号时只需在 fan-out 白名单里加 type。
|
|
47747
|
+
// persona-bound 订阅器与 wire 广播并行,信号类型由 routeFromRunner 的白名单限定。
|
|
47837
47748
|
eventSubscribers = /* @__PURE__ */ new Map();
|
|
47838
|
-
//
|
|
47839
|
-
// onPtyRegister callback 写入。handler 处理 tui:pty:input / tui:pty:resize 时
|
|
47840
|
-
// 走 getPtyBySessionId 反查,找到 transport 调 pty.write / pty.resize。
|
|
47841
|
-
// 进程 exit 时由 onPtyUnregister 清理;同一 toolSessionId 重 spawn 会覆盖
|
|
47749
|
+
// adapter 注册 toolSessionId → pty;exit 清理,respawn 覆盖,RPC 按 sessionId 反查。
|
|
47842
47750
|
ptyTransports = /* @__PURE__ */ new Map();
|
|
47843
|
-
//
|
|
47844
|
-
// onPtyReplayRegister callback 写入;闭包持引用 adapter 内部 @xterm/headless detector,
|
|
47845
|
-
// 调用时立即 serialize 当前 buffer + scrollback。
|
|
47846
|
-
// 新 client 通过 session:subscribe 接入时由 onSubscribe hook 取 getPtyReplay(sessionId)
|
|
47847
|
-
// 把屏幕快照定向 emit,解决"刷新页面 / 第二个 tab 接入时 xterm 白屏"
|
|
47751
|
+
// 当前屏幕快照闭包;新订阅者定向回放,避免刷新或新 tab 只收到增量而白屏。
|
|
47848
47752
|
ptyReplaysByToolSid = /* @__PURE__ */ new Map();
|
|
47849
|
-
//
|
|
47850
|
-
// observer 都从这一份 buffer 派生状态)。ClaudeTuiAdapter spawn 时通过 onSurfaceRegister
|
|
47851
|
-
// callback 写入;exit 时 onSurfaceUnregister 清理。
|
|
47852
|
-
// manager.resizePty 用此映射把 UI 的 session:pty:resize 同步到 surface 内部 buffer,
|
|
47853
|
-
// 保证 snapshot serialize 时 cursor positioning 与 UI xterm 实际尺寸一致。
|
|
47753
|
+
// pty 与 detector 共用的终端镜像;resize 必须同步,保证 snapshot 光标位置与 UI 一致。
|
|
47854
47754
|
surfacesByToolSid = /* @__PURE__ */ new Map();
|
|
47855
|
-
//
|
|
47856
|
-
//
|
|
47857
|
-
// 修「mount resize 早于 lazy spawn 被丢」与「respawn 回到 120×40 后无人补发」的尺寸错配。
|
|
47858
|
-
// 键用 sessionId 而非 toolSessionId:/clear 会 reassign tsid,sessionId 跨 respawn 稳定
|
|
47755
|
+
// 早于 lazy spawn 的 UI resize 先缓存,注册 pty / surface 时回放。
|
|
47756
|
+
// 用跨 respawn 稳定的 sessionId 作键,不能用 /clear 会变化的 toolSessionId。
|
|
47859
47757
|
lastUiSizeBySessionId = /* @__PURE__ */ new Map();
|
|
47860
|
-
//
|
|
47861
|
-
// observer.onEvent 要回灌 manager.feedObserverEvents,所以 observer 必须晚于 manager
|
|
47862
|
-
// 构造;反过来 manager 又要在 mode='tui' 下 spawn session 时 attach observer。setter 解循环
|
|
47758
|
+
// 通过 setter 注入 observer,避免 observer 回调依赖 manager 形成构造循环。
|
|
47863
47759
|
attachedObserver = null;
|
|
47864
47760
|
/** 仅 mode='tui' 走这条路径;调用方负责保证在使用前已 attach */
|
|
47865
47761
|
attachObserver(observer) {
|
|
47866
47762
|
this.attachedObserver = observer;
|
|
47867
47763
|
}
|
|
47868
|
-
// 按 scope 拿对应的 SessionStore.
|
|
47869
|
-
// Phase 2 (capability platform plan §1) 路由切到 SessionStoreFactory:
|
|
47870
|
-
// default → factory.forBare() <dataDir>/sessions/
|
|
47871
|
-
// persona/<pid>/owner → factory.forVmOwner(pid) <dataDir>/personas/<pid>/.clawd/sessions/owner/
|
|
47872
|
-
// persona/<pid>/guest → factory.forVmGuest(pid, capId) <dataDir>/personas/<pid>/.clawd/sessions/guests/<capId>/
|
|
47873
|
-
// 缺省 (无 factory 注入): 回退 dataDir+scope 老路径 (向后兼容旧 spec).
|
|
47874
|
-
/**
|
|
47875
|
-
* cwd 必须是本机真实目录——除非 adapter 是远端的(云上 persona,ToolAdapter.remote):那时 cwd 是
|
|
47876
|
-
* 「会话在哪跑」(gateway 上的地址),本机没有对应目录,stat 必然失败但那不是错。
|
|
47877
|
-
* 三个建会话入口(create / linkExisting / createFromExternal)共用这一处。
|
|
47878
|
-
*/
|
|
47879
47764
|
/**
|
|
47880
47765
|
* persona 会话的 cwd,四个建会话入口(create / dispatch / shift / lark)共用:本机 persona 是
|
|
47881
47766
|
* personaRoot/<id>,云上 persona 是 gateway 上的地址(PersonaSessionDefaults.cloud)。
|
|
@@ -47887,6 +47772,7 @@ var SessionManager = class {
|
|
|
47887
47772
|
defaults?.cloud
|
|
47888
47773
|
);
|
|
47889
47774
|
}
|
|
47775
|
+
/** 远端 adapter 的 cwd 属于 gateway;仅对本机 adapter 检查目录存在性。 */
|
|
47890
47776
|
assertLocalCwd(cwd, tool) {
|
|
47891
47777
|
if (this.deps.getAdapter(tool).remote) return;
|
|
47892
47778
|
try {
|
|
@@ -47895,6 +47781,7 @@ var SessionManager = class {
|
|
|
47895
47781
|
throw new ClawdError(ERROR_CODES.INVALID_CWD, `cwd not a directory: ${cwd}`);
|
|
47896
47782
|
}
|
|
47897
47783
|
}
|
|
47784
|
+
/** 优先按 factory 路由;缺省时普通 session 用 deps.store,其余按 dataDir + scope 缓存。 */
|
|
47898
47785
|
storeFor(scope) {
|
|
47899
47786
|
if (this.deps.storeFactory) {
|
|
47900
47787
|
if (scope.kind === "default") return this.deps.storeFactory.forBare();
|
|
@@ -47924,15 +47811,10 @@ var SessionManager = class {
|
|
|
47924
47811
|
capabilitiesLoadedAt(tool) {
|
|
47925
47812
|
return this.capabilitiesCache.get(tool)?.loadedAt;
|
|
47926
47813
|
}
|
|
47927
|
-
// 由 SessionFile 反推 scope 的 thin wrapper —— 决策矩阵收敛到 scope.scopeForFile
|
|
47928
|
-
// free function(scope.spec.ts transition 表驱动测试),manager 只负责注入 deps.
|
|
47929
47814
|
scopeForFile(file) {
|
|
47930
47815
|
return scopeForFile(file, this.deps.ownerPrincipalId);
|
|
47931
47816
|
}
|
|
47932
|
-
//
|
|
47933
|
-
// Phase 2 capability platform: 新布局下 persona 资产在 <dataDir>/personas/<pid>/,
|
|
47934
|
-
// 直接 readdir 这个目录拿所有 personaId. 老布局 (无 storeFactory) fallback 扫
|
|
47935
|
-
// <dataDir>/sessions/ 列子目录 (排除 'default').
|
|
47817
|
+
// 枚举 persona:有 factory 时扫 personas/,兼容模式扫 sessions/(排除 default)。
|
|
47936
47818
|
listPersonaIdsOnDisk() {
|
|
47937
47819
|
if (!this.deps.dataDir) return [];
|
|
47938
47820
|
const root = this.deps.storeFactory ? import_node_path9.default.join(this.deps.dataDir, "personas") : import_node_path9.default.join(this.deps.dataDir, "sessions");
|
|
@@ -47946,9 +47828,7 @@ var SessionManager = class {
|
|
|
47946
47828
|
}
|
|
47947
47829
|
return entries.filter((e) => e.isDirectory() && e.name !== "default").map((e) => e.name);
|
|
47948
47830
|
}
|
|
47949
|
-
//
|
|
47950
|
-
// 路径: <dataDir>/personas/<pid>/.clawd/sessions/guests/<capId>/
|
|
47951
|
-
// 只在 storeFactory 注入 (新布局) 下生效, 老布局无 guest 目录.
|
|
47831
|
+
// 仅 factory 布局枚举 persona 的 guests/<capId>/。
|
|
47952
47832
|
listGuestCapIdsForPersona(personaId2) {
|
|
47953
47833
|
if (!this.deps.dataDir || !this.deps.storeFactory) return [];
|
|
47954
47834
|
const root = import_node_path9.default.join(
|
|
@@ -47969,18 +47849,8 @@ var SessionManager = class {
|
|
|
47969
47849
|
}
|
|
47970
47850
|
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
47971
47851
|
}
|
|
47972
|
-
//
|
|
47973
|
-
//
|
|
47974
|
-
// 1) active runner(用户当前在用的 session):runner.state.file 是 SessionFile 内存权威副本,
|
|
47975
|
-
// 直接返回,零磁盘 I/O。覆盖 attachment / file-sharing 等"用户操作 → 紧接着 RPC"的
|
|
47976
|
-
// 绝大多数场景
|
|
47977
|
-
// 2) default scope 磁盘:inactive default session 命中
|
|
47978
|
-
// 3) 所有 persona owner 目录扫盘:inactive persona owner session 命中
|
|
47979
|
-
// 4) 每个 persona 的 guests/<capId>/ 扫盘:guest 通过我颁发 token 接进来创建的 session
|
|
47980
|
-
// listener sub-session 不走这条——transport 始终明确传 (personaId, sessionId)。
|
|
47981
|
-
//
|
|
47982
|
-
// 公开方法:attachment / file-sharing handler 通过 deps 闭包消费,不应直接持 SessionStore
|
|
47983
|
-
// (持 default-only store 是 file-sharing v1 的预存 bug 根因——见 fix #703)
|
|
47852
|
+
// sessionId 跨 scope 定位:优先 runner 的内存权威副本,再扫 default / persona owner / guest 存储。
|
|
47853
|
+
// 调用方通过 manager 闭包访问,不能持 default-only store 漏掉 persona 会话。
|
|
47984
47854
|
findOwnedSession(sessionId) {
|
|
47985
47855
|
const runner = this.runners.get(sessionId);
|
|
47986
47856
|
if (runner) return runner.getState().file;
|
|
@@ -47998,17 +47868,12 @@ var SessionManager = class {
|
|
|
47998
47868
|
}
|
|
47999
47869
|
return null;
|
|
48000
47870
|
}
|
|
48001
|
-
//
|
|
48002
|
-
// 集中到 manager(scope 单一来源),调用方拿 scope 不再自己拼 object 也不依赖
|
|
48003
|
-
// ownerPersonaId 字段语义。给 attachment handler 通过 deps.getSessionScope 闭包消费。
|
|
47871
|
+
// scope 的派生统一走 scopeForFile,调用方不自行按 ownerPersonaId 拼 scope。
|
|
48004
47872
|
findOwnedSessionScope(sessionId) {
|
|
48005
47873
|
const file = this.findOwnedSession(sessionId);
|
|
48006
47874
|
return file ? this.scopeForFile(file) : null;
|
|
48007
47875
|
}
|
|
48008
|
-
//
|
|
48009
|
-
// guest sessions 由 handlers/session.ts list 处按 ctx.principal.kind === 'guest' 做权限过滤
|
|
48010
|
-
// (creatorPrincipalId === capId), 这里仅负责数据源汇总.
|
|
48011
|
-
// listener sub-session 不走这条(listener 走 persona:listSubSessions RPC 单独入口)。
|
|
47876
|
+
// 汇总 default / persona owner / guest;这里只汇总,调用方 handler 负责按身份过滤。
|
|
48012
47877
|
listAllOwned() {
|
|
48013
47878
|
const out = [];
|
|
48014
47879
|
out.push(...this.deps.store.list());
|
|
@@ -48036,8 +47901,6 @@ var SessionManager = class {
|
|
|
48036
47901
|
cwd: file.cwd
|
|
48037
47902
|
}));
|
|
48038
47903
|
}
|
|
48039
|
-
// 写回 SessionFile,自动根据 ownerPersonaId / creatorPrincipalId 派生写入 scope.
|
|
48040
|
-
// 调用方原先都是 `deps.store.write(updated)`,全部走这里收口路由.
|
|
48041
47904
|
writeOwned(file) {
|
|
48042
47905
|
return this.storeFor(this.scopeForFile(file)).write(file);
|
|
48043
47906
|
}
|
|
@@ -48054,12 +47917,7 @@ var SessionManager = class {
|
|
|
48054
47917
|
}
|
|
48055
47918
|
return false;
|
|
48056
47919
|
}
|
|
48057
|
-
//
|
|
48058
|
-
// 经过 compressFrameForWire 后决定是 push collector 还是走 deps.broadcastFrame。
|
|
48059
|
-
// scope 决定两件事:SessionStore 路由 + SubSessionMeta 派生(metaFromScope)。
|
|
48060
|
-
// - default → 复用 deps.store,无 subSessionMeta(普通 session)
|
|
48061
|
-
// - persona/owner → personas/<pid>/.clawd/sessions/owner/,meta={idleKillDelayMs:<config 默认 6h>, personaMode:'owner'}
|
|
48062
|
-
// - persona/guest → personas/<pid>/.clawd/sessions/guests/<capId>/,meta={idleKillDelayMs:10min, personaMode:'guest', extraSettings:<sandbox>}
|
|
47920
|
+
// 按 scope 装配存储和运行时 meta;广播统一经 routeFromRunner 压缩和路由。
|
|
48063
47921
|
newRunner(file, scope) {
|
|
48064
47922
|
const adapter = this.deps.getAdapter(file.tool ?? "claude");
|
|
48065
47923
|
const store = this.storeFor(scope);
|
|
@@ -48123,36 +47981,19 @@ var SessionManager = class {
|
|
|
48123
47981
|
spawnOverride: this.deps.spawnOverride,
|
|
48124
47982
|
now: this.deps.now,
|
|
48125
47983
|
bufferCap: this.deps.bufferCap,
|
|
48126
|
-
// adapter 自己持有模型表 + 兜底逻辑(contains / opus-1M / [1m] 等),manager 不再 cache 转发
|
|
48127
47984
|
resolveContextWindow: (tool, modelId) => this.deps.getAdapter(tool).resolveContextWindow(modelId),
|
|
48128
47985
|
dataDir: this.deps.dataDir,
|
|
48129
|
-
// personaStore 不再透给 runner hooks(in-place mention 注入路径已删,runner 不用);
|
|
48130
|
-
// manager 自身的 sandbox 派生仍直接读 this.deps.personaStore。
|
|
48131
47986
|
ownerDisplayName: this.deps.ownerDisplayName,
|
|
48132
|
-
// T-31 persona prompt injection:owner 设备 id(= ownerPrincipalId)+ 飞书 union_id
|
|
48133
|
-
// 供 reducer 组 promptArgs 时作为「本 persona 的 owner」身份卡数据源(guest session 用)
|
|
48134
|
-
// 或「当前连接方」身份卡的兜底源(owner session 老路径未 stamp caller 时)。
|
|
48135
47987
|
ownerDeviceId: this.deps.ownerPrincipalId,
|
|
48136
47988
|
ownerFeishuUnionId: this.deps.ownerFeishuUnionId,
|
|
48137
|
-
// Phase 2 capability platform (plan §3): 透传 personaRoot, buildSpawnArgs 据
|
|
48138
|
-
// cwd 是否在 personaRoot 下自动决定是否注入 --settings sandbox-settings.json.
|
|
48139
47989
|
personaRoot: this.deps.personaRoot,
|
|
48140
|
-
// 单栏 refactor (spec 2026-06-02 §5.1): 透传 daemon HTTP RPC base URL 闭包,
|
|
48141
|
-
// reducer 把它注入 cc 子进程 env CLAWD_DAEMON_URL.
|
|
48142
47990
|
getDaemonUrl: this.deps.getDaemonUrl,
|
|
48143
|
-
// clawd 管家 (spec 2026-07-22-clawd-butler-persona §3.1): 透传 clawos 云 api base URL
|
|
48144
|
-
// 闭包, reducer 把它注入 cc 子进程 env CLAWOS_API, skill fetch 线上 md 文档用。
|
|
48145
47991
|
getClawosApi: this.deps.getClawosApi,
|
|
48146
|
-
// daemon 级 MCP server 列表闭包透传给 reducer,cc spawn 逐项加 --mcp-config flag;
|
|
48147
|
-
// B session 的 CLAWD_DISPATCH_ID env 由下方 lookupDispatchByBSessionId 反查闭包负责。
|
|
48148
47992
|
getMcpConfigs: () => this.deps.mcpConfigs ?? [],
|
|
48149
|
-
// persona 级凭据注入(M1b Step 5):闭包直接透传,manager 不碰凭据值本身。
|
|
48150
47993
|
resolvePersonaMcpConfig: this.deps.resolvePersonaMcpConfig,
|
|
48151
|
-
//
|
|
48152
|
-
// 递下去——in-place mention 注入那次的教训,见上面那条注释)。缺 store = 不注补充段。
|
|
47994
|
+
// 只传人格补充层读闭包,避免把整个 store 下沉;缺 store 不追加。
|
|
48153
47995
|
readPersonalityLocal: this.deps.personaStore ? (id) => this.deps.personaStore?.readPersonalityLocal(id) ?? null : void 0,
|
|
48154
47996
|
lookupDispatchByBSessionId: this.deps.personaDispatchManager ? (bSid) => this.deps.personaDispatchManager?.lookupDispatchByBSessionId(bSid) : void 0,
|
|
48155
|
-
// file-sharing (spec §6 PR 3):闭包 scope + sessionId,runner 只暴露 tool/relPath/cwd
|
|
48156
47997
|
onFileEdit: attachmentGroup ? (input) => attachmentGroup.onFileEdit({
|
|
48157
47998
|
scope,
|
|
48158
47999
|
sessionId: file.sessionId,
|
|
@@ -48160,8 +48001,6 @@ var SessionManager = class {
|
|
|
48160
48001
|
relPath: input.relPath,
|
|
48161
48002
|
cwd: input.cwd
|
|
48162
48003
|
}) : void 0,
|
|
48163
|
-
// ReadyGate v2:透传 daemon mode,runner 装配 ReducerDeps 时让 reducer 判断
|
|
48164
|
-
// send / ready-detected 分支走暂存队列还是直写
|
|
48165
48004
|
mode: this.deps.mode
|
|
48166
48005
|
});
|
|
48167
48006
|
if (this.deps.mode === "tui" && (file.tool ?? "claude") === "claude" && !file.toolSessionId) {
|
|
@@ -48467,25 +48306,8 @@ var SessionManager = class {
|
|
|
48467
48306
|
const file = this.getFile(args.sessionId);
|
|
48468
48307
|
return { response: file, broadcast: [] };
|
|
48469
48308
|
}
|
|
48470
|
-
// mirror
|
|
48471
|
-
//
|
|
48472
|
-
// 双写完成后通过 cache.setData 同步 sidebar 本地 state。
|
|
48473
|
-
//
|
|
48474
|
-
// 强制走 deps.store(即 default scope),不走 scopeForFile:mirror 物理落 default 即可。
|
|
48475
|
-
// 远端 creator / origin 字段原值保留(不被本机 ctx 派生覆写)。
|
|
48476
|
-
//
|
|
48477
|
-
// strip 远端 personaId / dispatchedFromSessionId / shiftFiredFromSessionId:mirror
|
|
48478
|
-
// SessionFile 上这些字段都指向朋友 daemon 上的本地 id,本机不持有对应 persona /
|
|
48479
|
-
// 源 session。留着会让按这些字段反查的本机消费者(MyPersonasSection 按 ownerPersonaId
|
|
48480
|
-
// 反查 persona、sidebar 按 dispatchedFromSessionId / shiftFiredFromSessionId 折叠
|
|
48481
|
-
// sub-session)走错分支。写入前清掉。#750 一族防线。
|
|
48482
|
-
//
|
|
48483
|
-
// shift fire 是本地 daemon scheduler 行为,理论上不产生 mirror SessionFile,但留 strip
|
|
48484
|
-
// 防御(同 dispatchedFromSessionId 处理姿势对齐)。
|
|
48485
|
-
//
|
|
48486
|
-
// 与 manager.create / manager.update 区别:
|
|
48487
|
-
// - 不走 newRunner / attachObserver / cwd 校验(mirror 是纯 metadata 层)
|
|
48488
|
-
// - 不发 session:info 帧(manager.create / .update 通过 reducer/wireCollector 发 push)
|
|
48309
|
+
// mirror 仅持久化远端元数据到 default store,不建 runner、校验 cwd 或广播;UI 自行更新缓存。
|
|
48310
|
+
// 保留远端 creator / origin,移除只在对端有效的 persona / dispatch / shift 关联,防本机错误归组。
|
|
48489
48311
|
mirrorUpsert(file) {
|
|
48490
48312
|
const {
|
|
48491
48313
|
ownerPersonaId: _strippedPid,
|
|
@@ -48495,9 +48317,7 @@ var SessionManager = class {
|
|
|
48495
48317
|
} = file;
|
|
48496
48318
|
return this.deps.store.write(rest);
|
|
48497
48319
|
}
|
|
48498
|
-
// mirror
|
|
48499
|
-
// 强制走 deps.store + strip ownerPersonaId),删也只删 default。idempotent:找不到
|
|
48500
|
-
// 不 throw(store.delete 内部 ENOENT 兜底返 false 不 throw)。**不广播 session:deleted**。
|
|
48320
|
+
// mirror 仅删 default store;不存在即 no-op,不广播删除帧。
|
|
48501
48321
|
mirrorRemove(sessionId) {
|
|
48502
48322
|
this.deps.store.delete(sessionId);
|
|
48503
48323
|
}
|
|
@@ -48598,16 +48418,8 @@ var SessionManager = class {
|
|
|
48598
48418
|
this.writeOwned({ ...existing, cwd, updatedAt: nowIso2(this.deps) });
|
|
48599
48419
|
}
|
|
48600
48420
|
/**
|
|
48601
|
-
*
|
|
48602
|
-
*
|
|
48603
|
-
* persist-file + session:info,与 CC 侧 `parseLine` 抽出来的 meta_update 完全同路。
|
|
48604
|
-
*
|
|
48605
|
-
* 与 `feedObserverEvents` 的区别:这不是 observer 回灌——不带 uuid、不参与 turn 判定,
|
|
48606
|
-
* 也不受那条 SDK 模式的 procAlive 守卫(守卫是为"daemon 自己在消费 stdout,observer 读到的
|
|
48607
|
-
* 是同一帧副本"设的;主动探测来的 meta 没有重复源,被吞掉就永远补不上)。
|
|
48608
|
-
*
|
|
48609
|
-
* 无 runner 时先 ensureSession(只建 state 容器不 spawn,同 session:observe 的前置),
|
|
48610
|
-
* 否则 meta_update 无处可落。幂等由 reducer 的 shallowEqMeta 保证;session 不存在静默返回。
|
|
48421
|
+
* 主动探测元信息走 meta_update,不带 uuid、不参与 turn 判定,也不受 observer 双源去重 gate。
|
|
48422
|
+
* 无 runner 时只建 state 容器;reducer 保证浅比较幂等,session 不存在则忽略。
|
|
48611
48423
|
*/
|
|
48612
48424
|
applyResolvedMeta(sessionId, patch) {
|
|
48613
48425
|
let runner = this.runners.get(sessionId);
|
|
@@ -48714,16 +48526,8 @@ var SessionManager = class {
|
|
|
48714
48526
|
return { response: { ok: true }, broadcast };
|
|
48715
48527
|
}
|
|
48716
48528
|
/**
|
|
48717
|
-
* 首条 send
|
|
48718
|
-
*
|
|
48719
|
-
* 「首轮即有」,daemon 用本会话选定的引擎和模型自己生成,与主 turn 并行,完成后经
|
|
48720
|
-
* manager.update 内部通道写 aiLabel(marker patch:不刷 updatedAt、广播 session:info)。
|
|
48721
|
-
*
|
|
48722
|
-
* 跳过条件:
|
|
48723
|
-
* - aiLabelGeneratedAt 已盖章(成功过;失败不盖章 → 下次 send 自然重试)
|
|
48724
|
-
* - label 非空(用户手输/dispatch 显式命名,显示优先级下 aiLabel 永远不可见,纯浪费)
|
|
48725
|
-
* - larkChatId 非空(lark 群会话显示群名是产品语义,AI 标题反而会遮住群名)
|
|
48726
|
-
* - 进程内已有同 session 的 in-flight 调用
|
|
48529
|
+
* 首条 send 并行生成标题,用 meta_update 写 aiLabel,不刷新活动时间。
|
|
48530
|
+
* 成功标记、用户 label、群聊名称或已有 in-flight 调用均跳过;失败不盖章,后续可重试。
|
|
48727
48531
|
*/
|
|
48728
48532
|
maybeGenerateTitle(file, text) {
|
|
48729
48533
|
const generate = this.deps.generateTitle;
|
|
@@ -48778,19 +48582,8 @@ var SessionManager = class {
|
|
|
48778
48582
|
});
|
|
48779
48583
|
}
|
|
48780
48584
|
/**
|
|
48781
|
-
*
|
|
48782
|
-
*
|
|
48783
|
-
* session 文件不存在或 send 抛错 → false(记 log,不 throw)。直接 runner.input
|
|
48784
|
-
* (无 collector),frames 经 routeFromRunner 正常广播上 wire。
|
|
48785
|
-
*
|
|
48786
|
-
* 来源分型(L2):`meta` 显式声明"这条注入是什么"('dispatch-task' / 'dispatch-result'),
|
|
48787
|
-
* reducer 原样 stamp 到 synth 的 user_text,与 history 侧 classifyUserMessage 从信封
|
|
48788
|
-
* 解出的值必须一致。
|
|
48789
|
-
*
|
|
48790
|
-
* sender 仍是 owner —— **sender 与 origin 是两个正交维度**:sender 回答"以谁的身份跑
|
|
48791
|
-
* 这一轮"(MCP endpoint / ticket api 主键要它,daemon 是代 owner 触发的,伪造 'persona'
|
|
48792
|
-
* 会污染下游主键),origin 回答"这条消息从哪来",后者由 meta + 信封表达。渲染层按
|
|
48793
|
-
* origin 分 owner / guest / persona 三档,persona 档正是 meta 命中 dispatch-* 这一支。
|
|
48585
|
+
* 以 owner 身份注入新 turn;无 session 或发送失败返回 false,帧走异步广播。
|
|
48586
|
+
* meta 来源分型须与历史信封分类一致。sender 表达执行身份,origin 表达消息来源,不能混用。
|
|
48794
48587
|
*/
|
|
48795
48588
|
injectIntoSession(sessionId, text, meta) {
|
|
48796
48589
|
if (!this.deps.ownerPrincipalId) {
|
|
@@ -48825,18 +48618,13 @@ var SessionManager = class {
|
|
|
48825
48618
|
}
|
|
48826
48619
|
}
|
|
48827
48620
|
/**
|
|
48828
|
-
*
|
|
48829
|
-
* handlers/session.ts:currentTurnSender 用(loopback HTTP RPC 绝对不能因不存在的 sid 崩)。
|
|
48830
|
-
* 2026-07-08 spec §4.6。
|
|
48621
|
+
* session 不存在返回 null;currentTurnSender 的 loopback RPC 依赖此安全查询。
|
|
48831
48622
|
*/
|
|
48832
48623
|
tryGetState(sessionId) {
|
|
48833
48624
|
const runner = this.runners.get(sessionId);
|
|
48834
48625
|
return runner?.getState() ?? null;
|
|
48835
48626
|
}
|
|
48836
|
-
//
|
|
48837
|
-
// proc.on('exit') 几乎瞬时触发;waitUntilStopped 的 3s 超时只在子进程已是 zombie 或
|
|
48838
|
-
// 极端 OS 异常时才会用上。这样修掉了"前端 sessionStop 还没等 CC 退就去删 worktree
|
|
48839
|
-
// 撞文件锁"的 race,同时也覆盖 CC streaming hang / 幽灵进程这类 SIGTERM 杀不动的 case
|
|
48627
|
+
// 等真实 proc-exit 再 ack,避免调用方开始删除 worktree 时进程仍持文件锁;超时向上报错。
|
|
48840
48628
|
async stop(args) {
|
|
48841
48629
|
const file = this.findOwnedSession(args.sessionId);
|
|
48842
48630
|
const runner = this.runners.get(args.sessionId);
|
|
@@ -48857,13 +48645,8 @@ var SessionManager = class {
|
|
|
48857
48645
|
await runner.waitUntilStopped();
|
|
48858
48646
|
return { response: { ok: true }, broadcast };
|
|
48859
48647
|
}
|
|
48860
|
-
//
|
|
48861
|
-
//
|
|
48862
|
-
// - TUI 模式(pty 已注册):直接走 stop 的 SIGKILL 路径。
|
|
48863
|
-
// 原本写 ESC(\x1b)给 pty 让 CC TUI 自己处理,但 CC TUI 的 ESC 语义随状态切换
|
|
48864
|
-
// (中断 turn / 清 prompt / 弹菜单),单次 ESC 经常只完成其中一步,用户得反复按。
|
|
48865
|
-
// 统一改成 kill 进程:行为可预测,下次 send 自动 respawn。
|
|
48866
|
-
// 失败情况(session 不存在 / 没活进程 / 超时 / 进程半路退出)都 throw ClawdError
|
|
48648
|
+
// SDK 发送 interrupt 控制帧;TUI 走 stop / SIGKILL(ESC 的行为取决于界面状态)。
|
|
48649
|
+
// session 不存在、无活进程、超时或中途退出时抛 ClawdError。
|
|
48867
48650
|
async interrupt(args) {
|
|
48868
48651
|
this.getFile(args.sessionId);
|
|
48869
48652
|
const runner = this.runners.get(args.sessionId);
|
|
@@ -49141,22 +48924,14 @@ var SessionManager = class {
|
|
|
49141
48924
|
return this.runners.get(sessionId);
|
|
49142
48925
|
}
|
|
49143
48926
|
/**
|
|
49144
|
-
*
|
|
49145
|
-
* 'stopping' 等都压平)。无 runner 返回 'idle'(没 spawn 过)。
|
|
49146
|
-
*
|
|
49147
|
-
* 用途:listener 端 subscribe 时补推一次当前 status —— sessionManager.subscribe 注册的
|
|
49148
|
-
* callback 只接未来 emit,不重放当前 status;不补推则 listener 进入时如果 sub-session
|
|
49149
|
-
* 已 running,永远拿不到 procAlive 信号。owner 路径在 LocalWsServer.onSubscribe hook
|
|
49150
|
-
* 重放 pendingQuestions(同一思路)。
|
|
48927
|
+
* 订阅只接未来帧,接入时需补推当前 wire status;running-idle 保留,无 runner 返回 idle。
|
|
49151
48928
|
*/
|
|
49152
48929
|
getCurrentStatus(sessionId) {
|
|
49153
48930
|
const runner = this.runners.get(sessionId);
|
|
49154
48931
|
if (!runner) return "idle";
|
|
49155
48932
|
return compressStatus(runner.getState().status);
|
|
49156
48933
|
}
|
|
49157
|
-
//
|
|
49158
|
-
// observer 喂事件路径只走 default + owner(普通 + owner persona session)
|
|
49159
|
-
// ——listener sub-session 的 observer 路径走 ensureRunnerForScope。
|
|
48934
|
+
// 仅创建 state 容器、不 spawn;由持久化 SessionFile 派生 scope。
|
|
49160
48935
|
ensureSession(file) {
|
|
49161
48936
|
let r2 = this.runners.get(file.sessionId);
|
|
49162
48937
|
if (r2) return r2;
|
|
@@ -49164,15 +48939,8 @@ var SessionManager = class {
|
|
|
49164
48939
|
this.runners.set(file.sessionId, r2);
|
|
49165
48940
|
return r2;
|
|
49166
48941
|
}
|
|
49167
|
-
//
|
|
49168
|
-
//
|
|
49169
|
-
// 区别于 owner / default 单 sessionId 入口,这些 API 接 SessionScope 参数 ——
|
|
49170
|
-
// 调用方(PersonaManager / observer 等)明确传 scope,由 storeFor 路由到正确目录:
|
|
49171
|
-
// - {kind:'persona', personaId, mode:'guest', capId} → guests/<capId>/,meta={idleKill:10min, sandbox}
|
|
49172
|
-
// - {kind:'persona', personaId, mode:'owner'} → owner/,meta={idleKill:<config 默认 6h>}
|
|
49173
|
-
//
|
|
49174
|
-
// SubSessionMeta 由 scope → metaFromScope 派生,不进 SessionFile schema。
|
|
49175
|
-
/** 按 scope 读取 SessionFile;不存在返回 null */
|
|
48942
|
+
// 显式 scope API
|
|
48943
|
+
// 按给定身份路由 store,运行时 meta 由 scope 派生,不写入 SessionFile。
|
|
49176
48944
|
readForScope(sessionId, scope) {
|
|
49177
48945
|
return this.storeFor(scope).read(sessionId);
|
|
49178
48946
|
}
|
|
@@ -49206,15 +48974,8 @@ var SessionManager = class {
|
|
|
49206
48974
|
return store.write(file);
|
|
49207
48975
|
}
|
|
49208
48976
|
/**
|
|
49209
|
-
*
|
|
49210
|
-
*
|
|
49211
|
-
* - 第一次访问:lazy ensureSession,buffer 为空 → 返回 []。
|
|
49212
|
-
* - 之前 send 过 / observer 喂过:buffer 已经有累积事件,直接返回。
|
|
49213
|
-
*
|
|
49214
|
-
* 不读 jsonl:
|
|
49215
|
-
* 1. observer 路径在 spawn 后会自动接管 jsonl 回灌,进 reducer buffer。
|
|
49216
|
-
* 2. 第一次握手时 sub-session 还没 spawn → toolSessionId 为空 → jsonl 不存在。
|
|
49217
|
-
* sessionFile 不存在抛 SESSION_NOT_FOUND;上层应先调 createForScope。
|
|
48977
|
+
* 从 reducer buffer 读取历史,与实时订阅保持同源;不读 jsonl。
|
|
48978
|
+
* 初次访问仅建 runner,buffer 可为空;session 不存在抛 SESSION_NOT_FOUND。
|
|
49218
48979
|
*/
|
|
49219
48980
|
readHistoryEventsForScope(sessionId, scope) {
|
|
49220
48981
|
const file = this.storeFor(scope).read(sessionId);
|
|
@@ -49228,20 +48989,8 @@ var SessionManager = class {
|
|
|
49228
48989
|
return runner.getState().buffer.map((e) => e.event);
|
|
49229
48990
|
}
|
|
49230
48991
|
/**
|
|
49231
|
-
*
|
|
49232
|
-
*
|
|
49233
|
-
* Why a new method instead of reusing `history:read` RPC handler:
|
|
49234
|
-
* - `history:read` 走 HandlerDeps.history.read(),读 CC 写盘的 jsonl 文件
|
|
49235
|
-
* - 本方法走 reducer buffer(与实时 `subscribe` 同一数据源)
|
|
49236
|
-
* 两条路径数据形态、时序、字段不同,不能共享 helper。persona-bound 客户端必须
|
|
49237
|
-
* 走 buffer 路径,才能保证首屏回放和实时帧无 gap、无重复。
|
|
49238
|
-
*
|
|
49239
|
-
* 切片语义:clip 安全边界,response.offset 保留请求值便于客户端识别越界请求。
|
|
49240
|
-
* - offset >= total → events: [], offset: 请求值, total: 当前 buffer 长度
|
|
49241
|
-
* - offset + limit > total → 返回 tail [offset, total),长度 = total - offset
|
|
49242
|
-
* - 正常 → 返回 [offset, offset + limit)
|
|
49243
|
-
*
|
|
49244
|
-
* SESSION_NOT_FOUND 行为继承 readHistoryEventsForScope(store.read 返回 null 即抛)。
|
|
48992
|
+
* 分页读取 reducer buffer;不能复用读取 jsonl 的 history:read,否则首屏与实时流可能错位。
|
|
48993
|
+
* 超出尾部返回空或剩余 tail,offset 保留请求值;不存在时抛 SESSION_NOT_FOUND。
|
|
49245
48994
|
*/
|
|
49246
48995
|
readHistoryPageForScope(sessionId, scope, limit, offset) {
|
|
49247
48996
|
const all = this.readHistoryEventsForScope(sessionId, scope);
|
|
@@ -49251,11 +49000,7 @@ var SessionManager = class {
|
|
|
49251
49000
|
return { events: all.slice(start, end), offset, total };
|
|
49252
49001
|
}
|
|
49253
49002
|
/**
|
|
49254
|
-
*
|
|
49255
|
-
* 与 owner 路径共享同一组字面量,listener callback 拿到的 frame.type 当前可能是
|
|
49256
|
-
* 'session:event'(ParsedEvent 流)或 'session:status'(procAlive)。
|
|
49257
|
-
* 返回 unsubscribe;不破坏现有 wire 广播路径——routeFromRunner 同时 fan-out 到
|
|
49258
|
-
* eventSubscribers 和 deps.broadcastFrame
|
|
49003
|
+
* 同步订阅 routeFromRunner 白名单帧并返回 unsubscribe;与 wire 广播并行,不重放当前状态。
|
|
49259
49004
|
*/
|
|
49260
49005
|
subscribe(sessionId, _scope, listener) {
|
|
49261
49006
|
let subs = this.eventSubscribers.get(sessionId);
|
|
@@ -49373,15 +49118,8 @@ var SessionManager = class {
|
|
|
49373
49118
|
return runner;
|
|
49374
49119
|
}
|
|
49375
49120
|
/**
|
|
49376
|
-
*
|
|
49377
|
-
*
|
|
49378
|
-
* - 复用 persona-owner 创建路径(cwd=personaDir、scope=persona-owner、不沙箱)
|
|
49379
|
-
* - SessionFile 上写 dispatchedFromSessionId 关联回 A(UI 折叠用;mirror 一侧 strip)
|
|
49380
|
-
* - 跟 PersonaDispatchManager 双向登记 dispatchId ↔ B sessionId,让后续 reducer
|
|
49381
|
-
* buildSpawnContext 能反查到 dispatchId 注 CLAWD_DISPATCH_ID env
|
|
49382
|
-
* - 投 inject-owner-text 任务包;包尾教 B 用 personaDispatchComplete tool 回传
|
|
49383
|
-
*
|
|
49384
|
-
* cwd 派生与 SessionManager.create 的 owner persona 路径一致(derivePersonaSpawnCwd)。
|
|
49121
|
+
* 创建 worker session 并通过 send 启动任务包;本地 owner、跨设备 guest。
|
|
49122
|
+
* 持久化来源关联,登记 dispatch ↔ session 供 env 注入;不能用只写 buffer 的 inject-owner-text。
|
|
49385
49123
|
*/
|
|
49386
49124
|
createDispatchedSession(args) {
|
|
49387
49125
|
if (!this.deps.personaRoot) {
|
|
@@ -49409,8 +49147,6 @@ var SessionManager = class {
|
|
|
49409
49147
|
const file = {
|
|
49410
49148
|
sessionId,
|
|
49411
49149
|
cwd,
|
|
49412
|
-
// dispatch B session 跟随 persona 绑定的 tool(codex persona 用 codex spawn);
|
|
49413
|
-
// UI 手建路径另允许通过 session:create 显式覆盖,dispatch 走 args.tool 一次性 override。
|
|
49414
49150
|
tool: effectiveTool,
|
|
49415
49151
|
ownerPersonaId: args.targetPersona,
|
|
49416
49152
|
dispatchedFromSessionId: args.sourceSessionId,
|
|
@@ -49420,16 +49156,11 @@ var SessionManager = class {
|
|
|
49420
49156
|
...args.guestPrincipalId ? {
|
|
49421
49157
|
creatorPrincipalId: args.guestPrincipalId,
|
|
49422
49158
|
...args.guestDisplayName ? { creatorDisplayName: args.guestDisplayName } : {},
|
|
49423
|
-
// T-31: A 的飞书 union_id stamp 到 B 的 SessionFile;guest meta 由 newRunner 派生
|
|
49424
49159
|
...args.guestFeishuUnionId ? { creatorFeishuUnionId: args.guestFeishuUnionId } : {}
|
|
49425
49160
|
} : {},
|
|
49426
|
-
// T-20 的 dispatch model override 优先;缺省回落 persona 默认值(spec 2026-08-12)。
|
|
49427
|
-
// effort 没有 override 参数(续派也不允许改),直接取 persona 默认值。
|
|
49428
|
-
// engine 被 override 掉时 persona 的 model / effort 一概不继承(见 engineOverridden)。
|
|
49429
49161
|
...personaDefaultFields(engineOverridden ? void 0 : personaDefaults, {
|
|
49430
49162
|
model: args.model
|
|
49431
49163
|
}),
|
|
49432
|
-
// butler 顶部入口:dispatch 传下来的 label 落盘,sidebar 显示(否则兜底 cwd-basename)
|
|
49433
49164
|
...args.label ? { label: args.label } : {},
|
|
49434
49165
|
createdAt: now,
|
|
49435
49166
|
updatedAt: now
|
|
@@ -49474,8 +49205,6 @@ var SessionManager = class {
|
|
|
49474
49205
|
}
|
|
49475
49206
|
runner.input({
|
|
49476
49207
|
kind: "command",
|
|
49477
|
-
// 来源分型(L2):realtime 路径显式带 meta,与 history 侧 classifyUserMessage
|
|
49478
|
-
// 从信封解出的值一致 —— 两路不一致会打歪 trimHistoryOverlap 的 anchor。
|
|
49479
49208
|
command: {
|
|
49480
49209
|
kind: "send",
|
|
49481
49210
|
text: taskPack,
|
|
@@ -49490,17 +49219,8 @@ var SessionManager = class {
|
|
|
49490
49219
|
return { sessionId };
|
|
49491
49220
|
}
|
|
49492
49221
|
/**
|
|
49493
|
-
*
|
|
49494
|
-
*
|
|
49495
|
-
* 返回 sessionId + ended Promise(cc 结束时 resolve)+ kill closure(timeout 兜底)。
|
|
49496
|
-
*
|
|
49497
|
-
* 跟 createDispatchedSession 的差异:
|
|
49498
|
-
* - 不带 dispatchId / source / task pack;prompt 直接当第一条 user message
|
|
49499
|
-
* - 不调 personaDispatchManager.registerBSession
|
|
49500
|
-
* - 多返一个 ended Promise,监听 session:status 帧翻译成 shift 业务终态 union
|
|
49501
|
-
* (ShiftSessionEnded:{ok:true} | {error:string}),不经 exit code 中转
|
|
49502
|
-
* - turn-idle 到达即视为业务完成 → settle {ok:true} + fire-and-forget kill cc
|
|
49503
|
-
* - kill 通过 runner.input({command:{kind:'stop'}}) 触发 SIGKILL → proc-exit
|
|
49222
|
+
* ShiftScheduler 启动一次性会话,以 prompt 首发,不登记 dispatch。
|
|
49223
|
+
* turn-idle 即完成并异步停止进程;返回业务 ended Promise 与供超时使用的 kill 闭包,不以 exit code 判成败。
|
|
49504
49224
|
*/
|
|
49505
49225
|
createShiftSession(args) {
|
|
49506
49226
|
if (!this.deps.personaRoot) {
|
|
@@ -49518,10 +49238,8 @@ var SessionManager = class {
|
|
|
49518
49238
|
const file = {
|
|
49519
49239
|
sessionId,
|
|
49520
49240
|
cwd,
|
|
49521
|
-
// shift 定时 spawn 同样跟随 persona 绑定 tool(对齐 dispatch B session)
|
|
49522
49241
|
tool: personaDefaults?.tool ?? "claude",
|
|
49523
49242
|
ownerPersonaId: args.targetPersona,
|
|
49524
|
-
// persona 绑定的 model / effort 快照进 SessionFile(spec 2026-08-12)
|
|
49525
49243
|
...personaDefaultFields(personaDefaults),
|
|
49526
49244
|
...args.sourceSessionId ? { shiftFiredFromSessionId: args.sourceSessionId } : {},
|
|
49527
49245
|
...args.deployId ? { deployId: args.deployId } : {},
|
|
@@ -49588,19 +49306,8 @@ var SessionManager = class {
|
|
|
49588
49306
|
return { sessionId, ended, kill: killImpl };
|
|
49589
49307
|
}
|
|
49590
49308
|
/**
|
|
49591
|
-
*
|
|
49592
|
-
*
|
|
49593
|
-
*
|
|
49594
|
-
* 跟 createShiftSession 的差异:
|
|
49595
|
-
* - **find-or-create**:按 capId `lark-<chatId>` 找现存 session,没有才建;daemon
|
|
49596
|
-
* `create` 的无 dedupe 语义不变——dedupe 是本方法私有实现。
|
|
49597
|
-
* 粒度由云端给的 chatId 决定,daemon 只当它是不透明 key:群消息是虚拟三段式
|
|
49598
|
-
* `<真实 chat_id>:<thread_id>:<open_id>`(spec 2026-09-10,一话题一人一条),
|
|
49599
|
-
* p2p 是裸 chat_id,文档评论是 `doc:<fileToken>`
|
|
49600
|
-
* - guest scope + 沙箱:群成员 query 不带 owner 权限;10min guest idle-kill + resume
|
|
49601
|
-
* - turn-idle 后**不 kill**(session 常驻,记忆即价值);TUI 下延迟 settleDelayMs 等
|
|
49602
|
-
* 尾随 text 落盘(schemas.ts turn_end 注释 ②)再读 buffer,SDK / codex 同步读
|
|
49603
|
-
* - 回复提取:extractLarkReplyText(最后一条 user_text 之后的 text 事件拼接)
|
|
49309
|
+
* 按 lark-<chatId> 复用 guest 沙箱会话;chatId 作为不透明 key,可含话题、用户或文档维度。
|
|
49310
|
+
* turn-idle 后提取最后一条 user_text 的回复,不主动 kill;TUI 的尾随 text 窗口见 settleDelayMs。
|
|
49604
49311
|
*/
|
|
49605
49312
|
runLarkChatTurn(args) {
|
|
49606
49313
|
if (!this.deps.personaRoot) {
|
|
@@ -49623,8 +49330,6 @@ var SessionManager = class {
|
|
|
49623
49330
|
cwd: this.personaCwdFor(args.personaId, personaDefaults),
|
|
49624
49331
|
tool: personaDefaults?.tool ?? "claude",
|
|
49625
49332
|
ownerPersonaId: args.personaId,
|
|
49626
|
-
// persona 绑定的 model / effort 快照进 SessionFile(spec 2026-08-12)——
|
|
49627
|
-
// 飞书里跟 persona 聊天用的模型,与 UI 里给它配的一致
|
|
49628
49333
|
...personaDefaultFields(personaDefaults),
|
|
49629
49334
|
// guest 三件套:creatorPrincipalId=capId(scopeForFile 回落防线)、
|
|
49630
49335
|
// creatorDisplayName(reducer guest 分支硬性要求,缺则 spawn 抛)
|
|
@@ -49728,12 +49433,8 @@ var SessionManager = class {
|
|
|
49728
49433
|
if (!runner) return;
|
|
49729
49434
|
runner.feedStdoutLine(line);
|
|
49730
49435
|
}
|
|
49731
|
-
// observer
|
|
49732
|
-
//
|
|
49733
|
-
// 双层防护对齐 openclaw-plugin-clawos:
|
|
49734
|
-
// 1. 主防御:runner.procAlive 时直接丢弃——daemon 自己在 spawn CC 并消费 stdout,
|
|
49735
|
-
// observer 读 JSONL 100% 是重复内容(来自同一逻辑帧),从源头不让它进 reducer。
|
|
49736
|
-
// 2. 兜底:reducer 的 batch uuid dedup 处理 managed 刚 stop / 切换的窗口期事件。
|
|
49436
|
+
// SDK 活进程已消费 stdout,observer 副本由 procAlive gate 丢弃;TUI 则由 observer 唯一供业务事件。
|
|
49437
|
+
// 切换窗口的重复帧由 reducer batch uuid 去重。
|
|
49737
49438
|
feedObserverEvents(sessionId, events) {
|
|
49738
49439
|
if (events.length === 0) return;
|
|
49739
49440
|
const runner = this.runners.get(sessionId);
|
|
@@ -49816,17 +49517,8 @@ var SessionManager = class {
|
|
|
49816
49517
|
runner.feedObserverEvents(feedEvents);
|
|
49817
49518
|
}
|
|
49818
49519
|
/**
|
|
49819
|
-
*
|
|
49820
|
-
*
|
|
49821
|
-
* 语义:屏幕真的稳定了 5s。查有没有 pending 的 turn_duration 信号:
|
|
49822
|
-
* - 有 → 之前收到的 turn_duration 被屏幕稳定**确认**了,flush 成 turn_end 进 reducer
|
|
49823
|
-
* - 无 → 屏幕稳定但从没收到 turn_duration → noop(不生成 turn_end;补偿路径的错误做法)
|
|
49824
|
-
*
|
|
49825
|
-
* pending 集合是**必要前提**:turn_end 只能来自"observer 收 turn_duration + 屏幕后续稳定"
|
|
49826
|
-
* 双源确认,任何一个缺少都不能 emit。这跟 PR #962 拆掉的 dispatchTurnIdle "屏幕静止就补
|
|
49827
|
-
* turn_end" 语义不同。
|
|
49828
|
-
*
|
|
49829
|
-
* 仅 TUI 模式;SDK / codex 没有屏幕信号也就不会触发本方法。
|
|
49520
|
+
* TUI 屏幕稳定时,仅 flush 已 pending 的 turn_duration;没有信号即 no-op。
|
|
49521
|
+
* 屏幕稳定自身不是 turn_end 证据,不能补造结束事件。
|
|
49830
49522
|
*/
|
|
49831
49523
|
notifyScreenIdle(toolSessionId) {
|
|
49832
49524
|
if (this.deps.mode !== "tui") return;
|
|
@@ -49911,8 +49603,7 @@ var SessionManager = class {
|
|
|
49911
49603
|
}
|
|
49912
49604
|
return { response: { ok: true }, broadcast };
|
|
49913
49605
|
}
|
|
49914
|
-
//
|
|
49915
|
-
// 先查 state.pendingPermissions:不存在 → 抛 PERMISSION_REQUEST_STALE
|
|
49606
|
+
// 无 runner 时权限请求已失效;已有 runner 且请求已被另一端回答则幂等 no-op。
|
|
49916
49607
|
respondPermission(args) {
|
|
49917
49608
|
const runner = this.runners.get(args.sessionId);
|
|
49918
49609
|
if (!runner) {
|
|
@@ -50005,7 +49696,6 @@ var SessionManager = class {
|
|
|
50005
49696
|
const size = sid ? this.lastUiSizeBySessionId.get(sid) : void 0;
|
|
50006
49697
|
if (size) surface.resize(size.cols, size.rows);
|
|
50007
49698
|
}
|
|
50008
|
-
/** spawn 进程 exit 时清理 */
|
|
50009
49699
|
unregisterSurface(toolSessionId) {
|
|
50010
49700
|
this.surfacesByToolSid.delete(toolSessionId);
|
|
50011
49701
|
}
|
|
@@ -50033,12 +49723,7 @@ var SessionManager = class {
|
|
|
50033
49723
|
return true;
|
|
50034
49724
|
}
|
|
50035
49725
|
/**
|
|
50036
|
-
*
|
|
50037
|
-
* seq 来自 adapter spawn 路径维护的 per-session counter,与 detector.parseSeq 同源。
|
|
50038
|
-
* UI 端 snapshot/incremental dedup 必须靠这个 seq。
|
|
50039
|
-
* 仅 mode='tui' 时发——SDK spawn 模式根本没 pty。
|
|
50040
|
-
* payload 永远只有 `{ sessionId, data, seq }`,data = base64 UTF-8 字节流。
|
|
50041
|
-
* 详见 protocol/src/events.ts session:pty JSDoc
|
|
49726
|
+
* TUI 增量 pty 字节流,seq 与 detector 共源,供 UI 与快照去重;载荷规则见 protocol/events.ts。
|
|
50042
49727
|
*/
|
|
50043
49728
|
emitPtyChunk(toolSessionId, b64, seq) {
|
|
50044
49729
|
if (this.deps.mode !== "tui") return;
|
|
@@ -50050,11 +49735,7 @@ var SessionManager = class {
|
|
|
50050
49735
|
);
|
|
50051
49736
|
}
|
|
50052
49737
|
/**
|
|
50053
|
-
*
|
|
50054
|
-
* visible=true → kind='popup_open' + popupKind
|
|
50055
|
-
* visible=false → kind='popup_close' + popupKind
|
|
50056
|
-
* 仅 mode='tui' 时发。新增 popup 类型只需扩 protocol POPUP_KIND_VALUES,不加帧名。
|
|
50057
|
-
* 详见 protocol/src/events.ts session:control JSDoc
|
|
49738
|
+
* TUI popup transition 复用 session:control;新 popup 类型扩 POPUP_KIND_VALUES,规则见 protocol/events.ts。
|
|
50058
49739
|
*/
|
|
50059
49740
|
emitPopupTransition(toolSessionId, popupKind, visible) {
|
|
50060
49741
|
if (this.deps.mode !== "tui") return;
|
|
@@ -50071,9 +49752,7 @@ var SessionManager = class {
|
|
|
50071
49752
|
);
|
|
50072
49753
|
}
|
|
50073
49754
|
/**
|
|
50074
|
-
*
|
|
50075
|
-
* quiet window 通过时投递 reducer 'ready-detected' input,触发 pendingSend 队列出队 flush。
|
|
50076
|
-
* 仅 mode='tui' 时启用;SDK 模式 reducer 内 early return 也无害(双层兜底)。
|
|
49755
|
+
* TUI ready 检测转发给 reducer,触发 pendingSend 出队;SDK 跳过。
|
|
50077
49756
|
*/
|
|
50078
49757
|
dispatchReadyDetected(toolSessionId) {
|
|
50079
49758
|
if (this.deps.mode !== "tui") {
|
|
@@ -50431,26 +50110,13 @@ var PersonaStore = class {
|
|
|
50431
50110
|
return path16.join(this.personaDir(personaId2), "AGENTS.md");
|
|
50432
50111
|
}
|
|
50433
50112
|
/**
|
|
50434
|
-
*
|
|
50435
|
-
*
|
|
50436
|
-
* 这份是 guest 沙箱的 **base 真源**(manager compose 读它当 base);guest 实际加载的是 manager
|
|
50437
|
-
* spawn 前 per-guest 动态拼到各自 session 目录的那份(base + 强制底座 + 本 guest userWorkDir carve)。
|
|
50113
|
+
* guest sandbox base 放 .clawd/,避免 CC project settings 自动加载。
|
|
50114
|
+
* manager 读取后叠加强制底座和 userWorkDir,以 inline settings 注入会话。
|
|
50438
50115
|
*/
|
|
50439
50116
|
sandboxSettingsPath(personaId2) {
|
|
50440
50117
|
return path16.join(this.personaDir(personaId2), ".clawd", "sandbox-settings.json");
|
|
50441
50118
|
}
|
|
50442
|
-
/**
|
|
50443
|
-
* 人格补充层落盘路径 —— `<personaDir>/.clawd/personality.local.md`。
|
|
50444
|
-
*
|
|
50445
|
-
* 位置不是随手挑的,两条既有约定叠出来的:`.clawd/` 是 daemon 声明「不动」的 persona 私有区
|
|
50446
|
-
* (不在 seed.ts 的 DAEMON_MANAGED_PATHS 里),`.local` 是仓内已成立的「本机个人 override
|
|
50447
|
-
* 永不被 OTA 覆盖」后缀。于是内置 persona 的 CLAUDE.md 继续被
|
|
50448
|
-
* refresh 覆盖成 bundle 新版、用户写的这层原样活着。
|
|
50449
|
-
*
|
|
50450
|
-
* 同 sandbox-settings.json 的理由:放 `.clawd/` 而非 persona 根目录,让 CC 的 project
|
|
50451
|
-
* setting source 自动发现扫不到——这层只经 SpawnContext.extraSystemPrompt 注入
|
|
50452
|
-
* (见 local-personality-prompt.ts),通道唯一,不会被当第二份 CLAUDE.md 重复加载。
|
|
50453
|
-
*/
|
|
50119
|
+
/** 用户人格补充层:OTA 不覆盖,只经 extraSystemPrompt 注入,避免重复加载人格。 */
|
|
50454
50120
|
personalityLocalPath(personaId2) {
|
|
50455
50121
|
return path16.join(this.personaDir(personaId2), ".clawd", "personality.local.md");
|
|
50456
50122
|
}
|
|
@@ -50472,10 +50138,7 @@ var PersonaStore = class {
|
|
|
50472
50138
|
if (!fs13.existsSync(p3)) return null;
|
|
50473
50139
|
return fs13.readFileSync(p3, "utf8");
|
|
50474
50140
|
}
|
|
50475
|
-
/**
|
|
50476
|
-
* 写人格补充层。**纯空白 = 清空**:删文件而不是留个空文件——「有没有补充」的唯一判据
|
|
50477
|
-
* 是文件在不在,留空文件会让下游多一种要判空的状态。persona 目录还没建时自建 `.clawd/`。
|
|
50478
|
-
*/
|
|
50141
|
+
/** 纯空白表示清空,删除文件;缺目录时创建 .clawd/。 */
|
|
50479
50142
|
writePersonalityLocal(personaId2, body) {
|
|
50480
50143
|
const p3 = this.personalityLocalPath(personaId2);
|
|
50481
50144
|
if (!body.trim()) {
|
|
@@ -50485,12 +50148,7 @@ var PersonaStore = class {
|
|
|
50485
50148
|
fs13.mkdirSync(path16.dirname(p3), { recursive: true });
|
|
50486
50149
|
this.atomicWrite(p3, body);
|
|
50487
50150
|
}
|
|
50488
|
-
/**
|
|
50489
|
-
* 历史 persona 补 AGENTS.md:人格双写镜像是 codex 适配后才加的,更早落盘的 persona 只有
|
|
50490
|
-
* CLAUDE.md。codex 原生读 cwd 的 AGENTS.md,缺失则切到 codex 后人格丢失。daemon 启动跑一次
|
|
50491
|
-
* migrate(见 seed.migrateAgentsMirror)。幂等:已有 AGENTS.md(含用户手改的)不覆盖;
|
|
50492
|
-
* 无 CLAUDE.md 不凭空造。返回是否实际补写。
|
|
50493
|
-
*/
|
|
50151
|
+
/** 补缺失的 AGENTS.md 镜像;已有文件(含用户编辑)不覆盖,无 CLAUDE.md 则不生成。 */
|
|
50494
50152
|
ensureAgentsMirror(personaId2) {
|
|
50495
50153
|
const claudeMd = this.claudeMdPath(personaId2);
|
|
50496
50154
|
const agentsMd = this.agentsMdPath(personaId2);
|
|
@@ -50500,11 +50158,8 @@ var PersonaStore = class {
|
|
|
50500
50158
|
return true;
|
|
50501
50159
|
}
|
|
50502
50160
|
/**
|
|
50503
|
-
*
|
|
50504
|
-
*
|
|
50505
|
-
* (enabled/denyRead/denyWrite/userWorkDir owner 削弱不了)→ 写 per-guest 文件给 CC。
|
|
50506
|
-
* 写入时强制注入本 persona 的**基线**(Edit-deny + allowRead,见 injectPersonaBaseline),
|
|
50507
|
-
* 让落盘 base 就是 source of truth(compose 不再运行时替换 permission deny / 注入 persona 读权)。
|
|
50161
|
+
* 覆盖 guest sandbox base,并注入 persona 的 Edit-deny、OS 与工具层读权。
|
|
50162
|
+
* manager 再叠不可削弱的强制底座和 userWorkDir,以 inline settings 注入 CC。
|
|
50508
50163
|
*/
|
|
50509
50164
|
writeSandboxSettings(personaId2, settings) {
|
|
50510
50165
|
this.atomicWrite(
|
|
@@ -50513,12 +50168,8 @@ var PersonaStore = class {
|
|
|
50513
50168
|
);
|
|
50514
50169
|
}
|
|
50515
50170
|
/**
|
|
50516
|
-
*
|
|
50517
|
-
*
|
|
50518
|
-
* 落盘即真源,compose 不再运行时注入——spec 2026-08-06-persona-workdir-discipline §1-§2);
|
|
50519
|
-
* `Read(~/<persona>/**)` union 进 permissions.allow——OS 沙箱层可读只放行了 Bash,CC 自带
|
|
50520
|
-
* Read 工具在最严格 base(dontAsk + 无 allow)下仍被拒,工具层与 OS 层必须同构放行
|
|
50521
|
-
* (guest「自由读 persona 目录、只写自己 userWorkDir」,spec 2026-08-06-lark-dm §P10 通用化)。
|
|
50171
|
+
* 去掉历史相对 Edit-deny,加入 persona 绝对路径的 Edit-deny。
|
|
50172
|
+
* 同时放行 OS allowRead 和工具层 Read(persona/**);只开 OS 读权不能放行 CC Read。
|
|
50522
50173
|
*/
|
|
50523
50174
|
injectPersonaBaseline(personaId2, settings) {
|
|
50524
50175
|
const dir = this.personaDir(personaId2);
|
|
@@ -50587,10 +50238,7 @@ var PersonaStore = class {
|
|
|
50587
50238
|
if (fs13.existsSync(agentsMd)) return fs13.readFileSync(agentsMd, "utf8");
|
|
50588
50239
|
return null;
|
|
50589
50240
|
}
|
|
50590
|
-
/**
|
|
50591
|
-
* 读 sandbox-settings.json 当前内容;文件不存在 / JSON 解析失败均返回 null。
|
|
50592
|
-
* owner 手改文件可能写出非法形状,daemon 不强校验,UI 拿到 null → 显示空状态。
|
|
50593
|
-
*/
|
|
50241
|
+
/** 缺失或读/解析失败返回 null;owner 手编的内容不强校验形状。 */
|
|
50594
50242
|
readSandboxSettings(personaId2) {
|
|
50595
50243
|
const p3 = this.sandboxSettingsPath(personaId2);
|
|
50596
50244
|
if (!fs13.existsSync(p3)) return null;
|
|
@@ -50611,17 +50259,7 @@ var PersonaStore = class {
|
|
|
50611
50259
|
claudeSettingsPath(personaId2) {
|
|
50612
50260
|
return path16.join(this.personaDir(personaId2), ".claude", "settings.json");
|
|
50613
50261
|
}
|
|
50614
|
-
/**
|
|
50615
|
-
* 读取 persona 的 `.claude/settings.json` 中 `enabledPlugins` map,把 value === true
|
|
50616
|
-
* 的 key 转成 `{ id }[]` 给 UI 做只读展示。
|
|
50617
|
-
*
|
|
50618
|
-
* 兜底范围(来自老板规则:不要无事实依据的兜底):
|
|
50619
|
-
* - 文件不存在 → [](plugin 未被启用是合法初始态)
|
|
50620
|
-
* - JSON 解析失败 → [](owner 手改文件,schema 漂移交给 UI 兜底)
|
|
50621
|
-
* - `enabledPlugins` 字段缺失 / 非 object → []
|
|
50622
|
-
* - 单 value 非 boolean → 跳过该 entry
|
|
50623
|
-
* 其他错误(fs 权限等)原样抛出,由上层 handler 转 ws error 帧。
|
|
50624
|
-
*/
|
|
50262
|
+
/** 取 enabledPlugins 中非空且 value === true 的 id;文件缺失、读/解析失败或字段非对象均返回 []。 */
|
|
50625
50263
|
readEnabledPlugins(personaId2) {
|
|
50626
50264
|
const p3 = this.claudeSettingsPath(personaId2);
|
|
50627
50265
|
if (!fs13.existsSync(p3)) return [];
|
|
@@ -50670,11 +50308,7 @@ var PersonaRegistry = class {
|
|
|
50670
50308
|
cache = /* @__PURE__ */ new Map();
|
|
50671
50309
|
/** 上一次从 gateway 拉到的云 persona;daemon 刚起还没拉到 = 空 */
|
|
50672
50310
|
cloud = [];
|
|
50673
|
-
/**
|
|
50674
|
-
* 从 store 全量重建缓存(boot 时 + 测试 setup 时),再合进云快照。
|
|
50675
|
-
* 单个 persona 读失败(损坏的 persona.json / schema 不匹配)不应阻塞 daemon 启动 ——
|
|
50676
|
-
* try/catch 隔离,让其它 persona 仍能加载。
|
|
50677
|
-
*/
|
|
50311
|
+
/** 重建时隔离单个人格的读取失败,避免阻塞其他 persona 加载。 */
|
|
50678
50312
|
reload() {
|
|
50679
50313
|
this.cache.clear();
|
|
50680
50314
|
for (const id of this.store.list()) {
|
|
@@ -50688,7 +50322,7 @@ var PersonaRegistry = class {
|
|
|
50688
50322
|
if (!this.cache.has(c2.personaId)) this.cache.set(c2.personaId, c2);
|
|
50689
50323
|
}
|
|
50690
50324
|
}
|
|
50691
|
-
/**
|
|
50325
|
+
/** 替换云快照并重建;返回被本机同名条目盖掉的云 personaId。 */
|
|
50692
50326
|
setCloudSnapshot(list) {
|
|
50693
50327
|
this.cloud = list.filter((p3) => p3.cloud);
|
|
50694
50328
|
this.reload();
|
|
@@ -50744,13 +50378,7 @@ var DEFAULT_PERSONAS = [
|
|
|
50744
50378
|
sandboxProfile: DEFAULT_BYPASS_PROFILE
|
|
50745
50379
|
},
|
|
50746
50380
|
{
|
|
50747
|
-
//
|
|
50748
|
-
// 新 personaId 独立于老 persona-clawd-helper(老 helper seed entry 已删;老用户机器上
|
|
50749
|
-
// 若有残留目录,是他们自留物,daemon 不再触碰)。bundle 里 CLAUDE.md 骨架 + 6 个
|
|
50750
|
-
// prebuild skill:新装用户走 copyBundleExtras,存量用户走 refreshDaemonManagedDirs
|
|
50751
|
-
// (`.claude/skills` 在 DAEMON_MANAGED_PATHS 里),都装到 `.claude/skills/`。
|
|
50752
|
-
// 前 5 个 skill fetch 用 daemon 注入的 env CLAWOS_API 拼 `${CLAWOS_API}/api/docs/**`
|
|
50753
|
-
// 拉线上手册;clawd-session-import 自带 scripts/link-sessions.mjs,只依赖 node 内置模块。
|
|
50381
|
+
// 管家沿用独立 id;旧 helper 残留不纳管,产品 skills 经 seed / refresh 安装。
|
|
50754
50382
|
personaId: "persona-clawd-butler",
|
|
50755
50383
|
label: "clawd \u7BA1\u5BB6",
|
|
50756
50384
|
model: "opus",
|
|
@@ -50769,22 +50397,16 @@ var DEFAULT_PERSONAS = [
|
|
|
50769
50397
|
sandboxProfile: DEFAULT_BYPASS_PROFILE
|
|
50770
50398
|
},
|
|
50771
50399
|
{
|
|
50772
|
-
// app-builder:全栈应用搭建师,绑 extension-kit 模板 + multi-project picker
|
|
50773
|
-
// spec: superpowers/specs/2026-06-01-app-builder-project-picker-design.md
|
|
50774
|
-
// superpowers/specs/2026-06-02-app-builder-single-pane-default-design.md
|
|
50775
50400
|
personaId: "persona-app-builder",
|
|
50776
50401
|
label: "App Builder",
|
|
50777
50402
|
model: "opus",
|
|
50778
50403
|
effort: "high",
|
|
50779
50404
|
iconKey: "assist",
|
|
50780
50405
|
public: false,
|
|
50781
|
-
//
|
|
50782
|
-
// + 出站全放。工具链只列精确目录(~/.nvm / pnpm store / ~/.npm),**不放宽 dot 目录**(~/.config 含
|
|
50783
|
-
// gh/gcloud 凭证、~/.local/bin 在 PATH 可被植入),避免 secrets 暴露 + 提权。mac/linux 两套 pnpm store 都列。
|
|
50406
|
+
// 工具链只放行精确目录,不放宽 ~/.config 或 ~/.local/bin,避免暴露凭据和可执行植入。
|
|
50784
50407
|
sandboxProfile: {
|
|
50785
|
-
//
|
|
50786
|
-
//
|
|
50787
|
-
// defaultMode bypassPermissions:开发流频繁用工具,免逐次审批;deny 规则仍生效、OS 沙箱照管(已验证)。
|
|
50408
|
+
// 增加通用文件/web 工具;OS carve 约束文件范围,.secrets 另拒读。
|
|
50409
|
+
// bypassPermissions 免逐次审批,显式 deny 与 OS 沙箱仍生效。
|
|
50788
50410
|
permissions: {
|
|
50789
50411
|
defaultMode: "bypassPermissions",
|
|
50790
50412
|
allow: ["Read", "Edit", "Write", "Glob", "Grep", "WebFetch", "WebSearch"]
|
|
@@ -50796,11 +50418,8 @@ var DEFAULT_PERSONAS = [
|
|
|
50796
50418
|
allowRead: ["~/.npmrc", "~/Library/Preferences/pnpm", "~/.nvm", "~/Library/pnpm", "~/.local/share/pnpm", "~/.local/state/pnpm", "~/.npm", "~/.pnpm-store"],
|
|
50797
50419
|
allowWrite: ["~/Library/pnpm", "~/.local/share/pnpm", "~/.local/state/pnpm", "~/.npm", "~/.pnpm-store"]
|
|
50798
50420
|
},
|
|
50799
|
-
//
|
|
50800
|
-
//
|
|
50801
|
-
// 用一组常见顶级域通配近似"全放行",几乎零维护即可覆盖 npm/pnpm/git 全生态(registry.npmjs.org→*.org、
|
|
50802
|
-
// npm.jsr.io→*.io、github/githubusercontent→*.com…)。代价:仍挡掉直连 IP 外联(正常包安装都走域名,
|
|
50803
|
-
// 反而是一道防裸 IP 外泄的兜底)。需要别的 TLD 往这里加。allowLocalBinding 控本机 bind,与出站正交。
|
|
50421
|
+
// CC allowedDomains 只支持具体域名或 *.后缀,'*' / 空列表不能用于全开。
|
|
50422
|
+
// TLD 通配覆盖常用包源,仍拒绝裸 IP;allowLocalBinding 与出站授权独立。
|
|
50804
50423
|
network: {
|
|
50805
50424
|
allowedDomains: ["*.com", "*.org", "*.io", "*.net", "*.dev", "*.app", "*.cn", "*.co", "*.chat", "*.ai"],
|
|
50806
50425
|
allowLocalBinding: true
|
|
@@ -50814,17 +50433,13 @@ var DEFAULT_PERSONAS = [
|
|
|
50814
50433
|
}
|
|
50815
50434
|
},
|
|
50816
50435
|
{
|
|
50817
|
-
// 数据查询搭建师:复用 app-builder 流水线生成 dataclaw 只读查询页
|
|
50818
|
-
// spec: clawd/superpowers/specs/2026-06-14-dataclaw-builder-design.md
|
|
50819
50436
|
personaId: "persona-dataclaw-builder",
|
|
50820
50437
|
label: "\u6570\u636E\u67E5\u8BE2\u642D\u5EFA\u5E08",
|
|
50821
50438
|
model: "opus",
|
|
50822
50439
|
effort: "high",
|
|
50823
50440
|
iconKey: "assist",
|
|
50824
50441
|
public: false,
|
|
50825
|
-
// 同 app-builder
|
|
50826
|
-
// + 出站 TLD 通配白名单。复用同一条流水线 → 同一套沙箱授权。
|
|
50827
|
-
// 注:不带 .secrets deny —— dataclaw-builder 的 apikey 走生成 app 的 server/.env,persona .secrets 已清空。
|
|
50442
|
+
// 同 app-builder 的路径与域名授权;apikey 在生成项目 server/.env,无需 persona .secrets carve。
|
|
50828
50443
|
sandboxProfile: {
|
|
50829
50444
|
permissions: {
|
|
50830
50445
|
defaultMode: "bypassPermissions",
|
|
@@ -50848,25 +50463,17 @@ var DEFAULT_PERSONAS = [
|
|
|
50848
50463
|
}
|
|
50849
50464
|
},
|
|
50850
50465
|
{
|
|
50851
|
-
//
|
|
50852
|
-
// bundle 含 frontend-slides skill(MIT,Zara Zhang),daemon ship 进 .claude/skills/
|
|
50466
|
+
// frontend-slides(MIT,Zara Zhang)不随 bundle 发布,由人格引导按需安装上游。
|
|
50853
50467
|
personaId: "persona-html-slides",
|
|
50854
50468
|
label: "HTML PPT \u5236\u4F5C\u5E08",
|
|
50855
50469
|
model: "opus",
|
|
50856
50470
|
effort: "high",
|
|
50857
50471
|
iconKey: "doc",
|
|
50858
50472
|
public: false,
|
|
50859
|
-
//
|
|
50860
|
-
//
|
|
50861
|
-
//
|
|
50862
|
-
//
|
|
50863
|
-
// specs/2026-09-18-aliyun-ak-leak-rotate-design.md;deploy-kit 下那份 .secrets/ 已随 design 2026-09-19-persona-mcp-single-source 删),
|
|
50864
|
-
// 不在 persona 目录另存一份。app-builder 的 FC 部署由 clawd-app-builder MCP server 起(沙箱外、AK 在它 env 里)故其 profile
|
|
50865
|
-
// 无需 carve 这个路径;html-slides 没有 daemon 侧流程、要在 cc 沙箱内直接 `set -a; . $HOME/.clawd/secrets/aliyun.env; set +a`
|
|
50866
|
-
// 后调 aliyun CLI(env 凭证绕过沙箱外的 ~/.aliyun/),所以这里**额外**把这个凭证文件加进 allowRead
|
|
50867
|
-
// (denyRead '~/' 之上的精确凿洞,单文件、最小暴露,同 ~/.npmrc 先例)。
|
|
50868
|
-
// excludedCommands: ['aliyun *'] —— 额外把 aliyun CLI 放沙箱外跑(逃出 seatbelt 直接用宿主凭证/网络),
|
|
50869
|
-
// 与上面沙箱内 env 凭证方案并存,按运行时实际命令前缀匹配生效。
|
|
50473
|
+
// 文件/web 工具与包管理器授权同 app-builder。
|
|
50474
|
+
// html-slides 在 CC 内调用阿里云,精确放行 ~/.clawd/secrets/aliyun.env;bundle 不带 AK。
|
|
50475
|
+
// app-builder 的部署则由沙箱外 MCP 持凭据执行,不需要该 carve。
|
|
50476
|
+
// excludedCommands 同时允许 aliyun 命令逃出 OS 沙箱使用宿主凭据/网络。
|
|
50870
50477
|
sandboxProfile: {
|
|
50871
50478
|
permissions: {
|
|
50872
50479
|
defaultMode: "bypassPermissions",
|
|
@@ -50891,12 +50498,7 @@ var DEFAULT_PERSONAS = [
|
|
|
50891
50498
|
}
|
|
50892
50499
|
},
|
|
50893
50500
|
{
|
|
50894
|
-
// Master
|
|
50895
|
-
// 代替老板操盘一群 worker persona:拆线 / 派活 / **验收 / 打回** / 收敛 / 上报。
|
|
50896
|
-
// 判断力靠 CLAUDE.md 人格 + .claude/agents/line-watcher.md(核验 subagent),
|
|
50897
|
-
// 纪律靠 daemon 侧 zod(`topic:*` 的 reason / acceptanceCriteria 非空在 RPC 入口拒,
|
|
50898
|
-
// 没有绕过路径)+ `master:authorityCheck` 查表。M1 那三个 bundle 内 .mjs 脚本已随
|
|
50899
|
-
// M2「MCP 化」整体拆除,别再照那套找。
|
|
50501
|
+
// Master 人格/line-watcher 提供判断;topic RPC schema 与 master:authorityCheck 约束操作。
|
|
50900
50502
|
personaId: "persona-master",
|
|
50901
50503
|
label: "Master",
|
|
50902
50504
|
model: "opus",
|
|
@@ -51523,14 +51125,7 @@ var PersonaManager = class {
|
|
|
51523
51125
|
this.deps = deps;
|
|
51524
51126
|
}
|
|
51525
51127
|
deps;
|
|
51526
|
-
/**
|
|
51527
|
-
* slug 是 persona 的身份,label 只是显示名——两者刻意分开:id 落进文件路径 /
|
|
51528
|
-
* 分享链接且创建后不可改,label 可中文、可随时改。此前 id 从 label 推导,
|
|
51529
|
-
* 非 ascii 被剥空后一律 fallback 成 `persona-persona`。
|
|
51530
|
-
*
|
|
51531
|
-
* 撞名直接抛错而非改名:id 由调用方显式指定,静默换成带后缀的 id 等于把
|
|
51532
|
-
* 用户的选择偷偷改掉。字符集校验在 PersonaCreateArgsSchema(路径穿越边界)。
|
|
51533
|
-
*/
|
|
51128
|
+
/** slug 是不可变的路径/分享身份,label 可改;撞名直接拒绝,路径字符由 RPC schema 校验。 */
|
|
51534
51129
|
create(args) {
|
|
51535
51130
|
const personaId2 = `persona-${args.slug}`;
|
|
51536
51131
|
if (this.deps.store.has(personaId2)) {
|
|
@@ -51541,10 +51136,7 @@ var PersonaManager = class {
|
|
|
51541
51136
|
personaId: personaId2,
|
|
51542
51137
|
label: args.label,
|
|
51543
51138
|
model: args.model,
|
|
51544
|
-
//
|
|
51545
|
-
// 那一档,但 persona:create 也可能来自 butler 的 persona-authoring skill 这类不带该
|
|
51546
|
-
// 字段的调用方——不兜的话要等下次 daemon 启动 backfill 才补上,中间建的 session 就漏了。
|
|
51547
|
-
// 取值理由与 backfillPersonaSessionDefaults 一致(见那里的注释)。
|
|
51139
|
+
// 创建时即补 high,避免不带 effort 的调用方建出缺档位会话;理由同 backfillPersonaSessionDefaults。
|
|
51548
51140
|
effort: args.effort ?? "high",
|
|
51549
51141
|
tool: args.tool,
|
|
51550
51142
|
public: args.public ?? false,
|
|
@@ -51598,10 +51190,7 @@ var PersonaManager = class {
|
|
|
51598
51190
|
listSkills(personaId2) {
|
|
51599
51191
|
return listSkillsForDir(this.deps.store.skillsDir(personaId2), "project");
|
|
51600
51192
|
}
|
|
51601
|
-
/**
|
|
51602
|
-
* 读取 persona `.claude/settings.json` 中已启用的插件列表(value === true 的 key)。
|
|
51603
|
-
* 文件不存在 / 解析失败 / 字段缺失 → 空数组。供 persona:get handler 拼响应。
|
|
51604
|
-
*/
|
|
51193
|
+
/** 已启用插件的读取与容错语义见 PersonaStore.readEnabledPlugins。 */
|
|
51605
51194
|
listEnabledPlugins(personaId2) {
|
|
51606
51195
|
return this.deps.store.readEnabledPlugins(personaId2);
|
|
51607
51196
|
}
|
|
@@ -51610,22 +51199,9 @@ var PersonaManager = class {
|
|
|
51610
51199
|
return this.deps.store.readCodexSandboxSettings(personaId2);
|
|
51611
51200
|
}
|
|
51612
51201
|
/**
|
|
51613
|
-
*
|
|
51614
|
-
*
|
|
51615
|
-
*
|
|
51616
|
-
* 手配的 writableRoots / denyRead(丢的是安全边界,且用户不会立刻发现)。
|
|
51617
|
-
* 不走 PersonaManager.update 是因为它管的是 PersonaFile meta + 人格两层文本,
|
|
51618
|
-
* 而这份配置是另一个文件、另一套 schema。
|
|
51619
|
-
*
|
|
51620
|
-
* **读不到时 base 用 CODEX_SANDBOX_DEFAULT 而不是 `{}`**:`{}` 会让这次「合并」实际写出一份
|
|
51621
|
-
* 只有 patch 字段的残缺配置(例如只有 `{network:true}`,没有包管理器缓存目录 → `npm install`
|
|
51622
|
-
* EPERM),而且从此 migrateCodexSandbox 见文件已存在就跳过,UI 上再也补不回来。
|
|
51623
|
-
* 正常路径下文件在 create 时就已物化,这条兜底只在文件被删 / 被改坏时触发。
|
|
51624
|
-
*
|
|
51625
|
-
* ⚠️ 已知限制:`CodexSandboxSettingsSchema` 是 `.strict()` 的,owner 手编时多写一个键
|
|
51626
|
-
* (注释键 / 历史字段)会让整份文件读成 null,于是这次合并把它替换成默认值 ⊕ patch。
|
|
51627
|
-
* 已有测试锁定该 strict 语义(store.spec「schema 校验失败 → 返回 null」),本次不动它——
|
|
51628
|
-
* 至少残缺配置不会再被写出去。要放宽得单独评估。
|
|
51202
|
+
* 合并局部 patch,保留未改的 writableRoots / denyRead;UI 联网开关仅传 network。
|
|
51203
|
+
* 缺失或无效时以完整 CODEX_SANDBOX_DEFAULT 为 base,避免写出缺缓存目录的配置。
|
|
51204
|
+
* 注意 strict schema:手编的未知键会使整份读成 null,本次合并将覆盖为默认值 ⊕ patch。
|
|
51629
51205
|
*/
|
|
51630
51206
|
mergeCodexSandbox(personaId2, patch) {
|
|
51631
51207
|
if (!this.deps.registry.get(personaId2)) throw new Error(`persona not found: ${personaId2}`);
|
|
@@ -51638,12 +51214,7 @@ var PersonaManager = class {
|
|
|
51638
51214
|
readSandboxSettings(personaId2) {
|
|
51639
51215
|
return this.deps.store.readSandboxSettings(personaId2);
|
|
51640
51216
|
}
|
|
51641
|
-
/**
|
|
51642
|
-
* 删除 persona。
|
|
51643
|
-
* PersonaStore.remove(personaId) 级联删整个 <personaRoot>/<personaId>/ 目录
|
|
51644
|
-
* (含 .clawd/sessions/owner/ 和 .clawd/sessions/guests/<capId>/),rmSync
|
|
51645
|
-
* recursive force 一锅清。目录外的那份(群文件清单)交给 onPersonaDeleted。
|
|
51646
|
-
*/
|
|
51217
|
+
/** 级联删 persona 目录(含 owner/guest 会话);目录外群文件清单交给 onPersonaDeleted。 */
|
|
51647
51218
|
delete(personaId2) {
|
|
51648
51219
|
this.deps.store.remove(personaId2);
|
|
51649
51220
|
this.deps.registry.remove(personaId2);
|
|
@@ -52425,7 +51996,6 @@ var CodexHistoryReader = class {
|
|
|
52425
51996
|
toolSessionId: f2.threadId,
|
|
52426
51997
|
summary: summary || "(\u7A7A\u4F1A\u8BDD)",
|
|
52427
51998
|
messageCount: 0,
|
|
52428
|
-
// 与此前 thread/list 一致:列表不数条数
|
|
52429
51999
|
updatedAt: new Date(f2.mtimeMs).toISOString()
|
|
52430
52000
|
};
|
|
52431
52001
|
});
|
|
@@ -53932,8 +53502,7 @@ function threadStartParams(ctx) {
|
|
|
53932
53502
|
// sandbox 字段**(避免 sandbox mode 覆盖 profile);owner / 普通会话 → danger-full-access
|
|
53933
53503
|
// (owner 全权,对齐 CC owner 无 OS 沙箱),不读 permissionMode(那是审批预设,进 approvalPolicy)。
|
|
53934
53504
|
...ctx.personaMode === "guest" ? {} : { sandbox: "danger-full-access" },
|
|
53935
|
-
//
|
|
53936
|
-
// 取代旧的"折进首条 turn 文本"(那个被当用户消息显示)。thread/start + thread/resume 都带,幂等。
|
|
53505
|
+
// Connection identity belongs in developerInstructions, outside user history; include on start/resume.
|
|
53937
53506
|
...ctx.extraSystemPrompt ? { developerInstructions: ctx.extraSystemPrompt } : {}
|
|
53938
53507
|
};
|
|
53939
53508
|
return params;
|
|
@@ -54065,8 +53634,7 @@ function driveCodexThread(opts) {
|
|
|
54065
53634
|
void client.request("turn/start", {
|
|
54066
53635
|
threadId,
|
|
54067
53636
|
input: turnStartInput(text, opts.imageInput),
|
|
54068
|
-
//
|
|
54069
|
-
// codex 静默忽略, reasoning effort 改了不生效。"override for this turn and subsequent turns"。
|
|
53637
|
+
// Wire field is effort (v2 already used it before 0.138); unknown reasoningEffort is silently ignored.
|
|
54070
53638
|
...effort ? { effort } : {}
|
|
54071
53639
|
}).catch((e) => {
|
|
54072
53640
|
if (exited) return;
|
|
@@ -54405,8 +53973,6 @@ function createCodexSession(ctx, sink, deps = {}) {
|
|
|
54405
53973
|
sink,
|
|
54406
53974
|
logger: deps.logger,
|
|
54407
53975
|
stream: stdioLineStream(proc),
|
|
54408
|
-
// 连接 intro(ctx.extraSystemPrompt,owner/guest 人称)经 threadStartParams 的
|
|
54409
|
-
// developerInstructions 传入(developer 级、不进消息流、不显示在 UI、跨 resume 幂等)。
|
|
54410
53976
|
threadParams: threadStartParams(ctx),
|
|
54411
53977
|
// persona 惯例装在 <cwd>/.claude/skills/,经 extraRoots 注入 discovery
|
|
54412
53978
|
// (skills:list RPC 侧 codex-skills.ts 同款注入,两处保持一致)。
|
|
@@ -54497,7 +54063,6 @@ var EFFORT_LABELS = {
|
|
|
54497
54063
|
};
|
|
54498
54064
|
var CODEX_DEFAULT_MODEL_ENTRY = {
|
|
54499
54065
|
id: "",
|
|
54500
|
-
// 不写 'Default'——说清楚是谁决定的(对齐 claude 侧「由 CC 决定」)
|
|
54501
54066
|
label: "\u7531 config.toml \u51B3\u5B9A",
|
|
54502
54067
|
description: "\u4E0D\u6307\u5B9A\u6A21\u578B\uFF0C\u7528 codex config.toml \u91CC\u914D\u7F6E\u7684\u90A3\u4E2A",
|
|
54503
54068
|
contextWindowSize: CODEX_CONTEXT_WINDOW,
|
|
@@ -54527,9 +54092,7 @@ function mapModelList(raw) {
|
|
|
54527
54092
|
...typeof e.description === "string" ? { description: e.description } : {},
|
|
54528
54093
|
contextWindowSize: CODEX_CONTEXT_WINDOW,
|
|
54529
54094
|
...efforts && efforts.length > 0 ? { efforts } : {},
|
|
54530
|
-
//
|
|
54531
|
-
// 空项删掉后它是「哪个是默认」的唯一权威来源——新建 persona 的预选、
|
|
54532
|
-
// backfill 补 codex model 都依赖它。
|
|
54095
|
+
// Preserve the engine's default for persona preselection/backfill.
|
|
54533
54096
|
...e.isDefault === true ? { default: true } : {},
|
|
54534
54097
|
...typeof e.defaultReasoningEffort === "string" ? { defaultEffort: e.defaultReasoningEffort } : {}
|
|
54535
54098
|
});
|
|
@@ -54679,7 +54242,6 @@ var CODEX_CAPABILITIES_BASE = {
|
|
|
54679
54242
|
tool: "codex",
|
|
54680
54243
|
toolSessionIdLabel: "Codex Thread ID",
|
|
54681
54244
|
permissionModes: CODEX_APPROVAL_PRESETS,
|
|
54682
|
-
// 本机 Codex 支持原生 child thread、已完成 turn 的 fork;rewind / TUI / observe / 文件分享仍关闭。
|
|
54683
54245
|
features: { rewind: false, subagents: true, tui: false, observe: false, fileSharing: false, fork: true },
|
|
54684
54246
|
configSchema: [
|
|
54685
54247
|
{
|
|
@@ -54688,7 +54250,6 @@ var CODEX_CAPABILITIES_BASE = {
|
|
|
54688
54250
|
label: "Model",
|
|
54689
54251
|
scope: "core",
|
|
54690
54252
|
options: [],
|
|
54691
|
-
// 运行时填充,见 buildCapabilities
|
|
54692
54253
|
default: ""
|
|
54693
54254
|
},
|
|
54694
54255
|
{
|
|
@@ -54704,7 +54265,6 @@ var CODEX_CAPABILITIES_BASE = {
|
|
|
54704
54265
|
type: "select",
|
|
54705
54266
|
label: "Reasoning effort",
|
|
54706
54267
|
scope: "tool-specific",
|
|
54707
|
-
// 渲染在 Advanced 区(与 claude effort 一致)
|
|
54708
54268
|
options: CODEX_EFFORTS,
|
|
54709
54269
|
default: "medium"
|
|
54710
54270
|
}
|
|
@@ -54735,7 +54295,7 @@ async function probeCodex(env = process.env) {
|
|
|
54735
54295
|
}
|
|
54736
54296
|
var CodexAdapter = class {
|
|
54737
54297
|
id = "codex";
|
|
54738
|
-
//
|
|
54298
|
+
// session.tool routes local history reads to the rollout reader.
|
|
54739
54299
|
historyReader;
|
|
54740
54300
|
probeCache = null;
|
|
54741
54301
|
logger;
|
|
@@ -54798,10 +54358,7 @@ var CodexAdapter = class {
|
|
|
54798
54358
|
return [];
|
|
54799
54359
|
}
|
|
54800
54360
|
}
|
|
54801
|
-
/**
|
|
54802
|
-
* provider 订阅侧全集。未配自定义 provider(官方用户)或任何失败都返回 []。
|
|
54803
|
-
* 见 codex-provider-models.ts —— model/list 只报 codex 内置型号,代理转发的模型全靠这里。
|
|
54804
|
-
*/
|
|
54361
|
+
/** Provider models: unconfigured returns []; failures return [] unless strict refresh requests a throw. */
|
|
54805
54362
|
async loadProviderModels(strict) {
|
|
54806
54363
|
try {
|
|
54807
54364
|
const raw = this.configQueryOverride ? await this.configQueryOverride() : await queryCodexAppServer(process.cwd(), "config/read", {}, {
|
|
@@ -55909,7 +55466,6 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
55909
55466
|
});
|
|
55910
55467
|
this.tuiOpts.onPopupTransition(ctx.toolSessionId, kind, visible);
|
|
55911
55468
|
},
|
|
55912
|
-
// popup_close 信号源:ready observer 扫到 ❯ + ─{20,} 输入框结构 → 输入框可见 → popup 已关
|
|
55913
55469
|
getInputFrameVisible: () => readyObserver.inputFrameVisible
|
|
55914
55470
|
});
|
|
55915
55471
|
const screenIdleObserver = observeScreenIdle(surface, {
|
|
@@ -55989,26 +55545,10 @@ var ClaudeTuiAdapter = class extends ClaudeAdapter {
|
|
|
55989
55545
|
});
|
|
55990
55546
|
return ptyChild;
|
|
55991
55547
|
}
|
|
55992
|
-
//
|
|
55993
|
-
//
|
|
55994
|
-
//
|
|
55995
|
-
//
|
|
55996
|
-
// 合法 JSON,父类正常解析返业务 ParsedEvent —— observer 是 TUI 模式业务事件的唯一来源
|
|
55997
|
-
//
|
|
55998
|
-
// **TUI user 行字符串 content 兼容**(CC v2.1.141 实测):xterm 手输的 user 行写盘形如
|
|
55999
|
-
// {"type":"user","message":{"role":"user","content":"老板手输的 prompt"},"uuid":"...",...}
|
|
56000
|
-
// content 是字符串而非数组。父类(SDK 模式)的 parseLine 在 `if (!Array.isArray(content))`
|
|
56001
|
-
// 处直接 return,user_text 永远进不了 reducer → MessageList 不显示用户消息。
|
|
56002
|
-
// 这里 override 检测字符串 content 的 user 行,包装成单元素 [{type:"text",text:...}]
|
|
56003
|
-
// 数组形式让父类正常走 user 分支产 user_text。其它形态(tool_result / array content /
|
|
56004
|
-
// 非 user 行)原样落父类。
|
|
56005
|
-
//
|
|
56006
|
-
// 关于 user_text dedup:观察者会抓到两类 user 行:
|
|
56007
|
-
// - 结构化输入路径 (session:send):daemon reducer 已 synthesize user_text 上屏;observer
|
|
56008
|
-
// 抓到的 jsonl real user 行 = 这次 synth 的副本,**必须丢**(不丢 UI 会重复上屏)
|
|
56009
|
-
// - xterm 输入路径 (session:pty:input):daemon 没 synth,observer 是唯一来源,**必须保留**
|
|
56010
|
-
// 这层判断需要 sessionId + realUuidBySynth map,parseLine 拿不到,统一由 manager.feedObserverEvents
|
|
56011
|
-
// 按 real uuid 是否已被映射到 synth 决定。这里只做基础 parse + JSON 校验
|
|
55548
|
+
// PTY stdout is ANSI and yields no business events; observer JSONL is the TUI event source.
|
|
55549
|
+
// TUI user rows can have string content (CC 2.1.141); wrap it for the parent's array parser.
|
|
55550
|
+
// Manager handles user_text dedup using the synth/real UUID mapping: session:send has a
|
|
55551
|
+
// synthesized copy, whereas xterm input has only the observer row.
|
|
56012
55552
|
parseLine(line) {
|
|
56013
55553
|
const trimmed = line.trim();
|
|
56014
55554
|
if (!trimmed || trimmed[0] !== "{") return super.parseLine(line);
|
|
@@ -56136,9 +55676,7 @@ var PersonaDispatchManager = class {
|
|
|
56136
55676
|
if (sourceSessionId) this.deps.onCompleted?.(sourceSessionId);
|
|
56137
55677
|
}
|
|
56138
55678
|
/**
|
|
56139
|
-
*
|
|
56140
|
-
* 保证 worker 进程死后 respawn 时 reducer lookupDispatchByBSessionId 仍注回同一
|
|
56141
|
-
* CLAWD_DISPATCH_ID env。台账行的复位由 handler 直接走 store(本方法只管内存态)。
|
|
55679
|
+
* 续派重建 worker 映射,确保 respawn 仍注入原 CLAWD_DISPATCH_ID;台账复位由 handler 负责。
|
|
56142
55680
|
*/
|
|
56143
55681
|
reopen(dispatchId, args) {
|
|
56144
55682
|
this.map.set(dispatchId, {
|
|
@@ -56339,17 +55877,9 @@ function createChannelStore(deps) {
|
|
|
56339
55877
|
...deps,
|
|
56340
55878
|
key: "channels",
|
|
56341
55879
|
version: 2,
|
|
56342
|
-
// v1
|
|
56343
|
-
//
|
|
56344
|
-
//
|
|
56345
|
-
//
|
|
56346
|
-
// 存量频道因此失去跨设备派活能力,这是有意的:要补偿就得在这里去拉对方 daemon 的
|
|
56347
|
-
// persona 列表(对方不一定在线),而「他家所有 persona 自动进名单」正是要废掉的
|
|
56348
|
-
// 那个隐含推导。老板重新勾一次即可。
|
|
56349
|
-
//
|
|
56350
|
-
// ⚠️ 写法注意(给下一次 v2→v3 的人):这里是**先解构后覆盖** —— `...rest` 会把
|
|
56351
|
-
// 旧行里的同名字段一起带进来,紧跟着的赋值再盖掉它。加新字段时确认清楚是想覆盖
|
|
56352
|
-
// 还是想保留,别无意中把 rest 里已有的值清空。
|
|
55880
|
+
// v1→v2 将 memberPersonaIds 转成 memberPersonas;保留 contact 设备名单供 DM 使用。
|
|
55881
|
+
// 不从设备名单隐式授权全部远端 persona:需用户明确重新选择,迁移不依赖远端在线。
|
|
55882
|
+
// rest 先展开,再覆盖迁移字段;扩充格式时注意保留已有字段。
|
|
56353
55883
|
migrate: (rows) => rows.map((c2) => {
|
|
56354
55884
|
const { memberPersonaIds, ...rest } = c2;
|
|
56355
55885
|
return {
|
|
@@ -56420,21 +55950,9 @@ function createTopicStore(deps) {
|
|
|
56420
55950
|
};
|
|
56421
55951
|
return {
|
|
56422
55952
|
/**
|
|
56423
|
-
*
|
|
56424
|
-
*
|
|
56425
|
-
*
|
|
56426
|
-
* 一、存量 `state: 'archived'` 归成 `'done'`。归档已改由该话题 Master 会话的 `archivedAt`
|
|
56427
|
-
* 承载(spec 2026-08-25-topic-archive-and-pin),`TOPIC_STATES` 随之退掉那一档。json-store
|
|
56428
|
-
* 读盘本身不过 zod,但 `topic:list` 的响应要过 `TopicListResultSchema.parse` —— 留着这个
|
|
56429
|
-
* 值会让**整个频道**的话题列表 parse 失败。
|
|
56430
|
-
*
|
|
56431
|
-
* 二、补/纠线号水位。放在读盘而不是发号时:水位一旦落后于已发出的号,下一条新线就会捡回
|
|
56432
|
-
* 一个已经写进流水的号,那是「留档被读坏」而不是「少个号」,越早接上越好。
|
|
56433
|
-
*
|
|
56434
|
-
* **补过就立刻固化**(这一条与 archived 那条不同,不能只留在内存里):水位不落盘的话,
|
|
56435
|
-
* 下次启动还得靠 lineMeta 重算,而 lineMeta 一旦丢了最大号那条,重算出来的水位就退,
|
|
56436
|
-
* 退了就把已经发出去的号又发一遍。不变式不能挂在「lineMeta 一直完好」这个外部事实上。
|
|
56437
|
-
* 水位已经合法的话题不算改动,不会因此每次启动都写一次盘。
|
|
55953
|
+
* archived 归一为 done,避免旧值使 topic:list 响应整体 parse 失败;归档标记在 Master 会话上。
|
|
55954
|
+
* 补正水位后 scheduleFlush,尽早持久化;否则重启时再从 lineMeta 推导可能因删线复用旧号。
|
|
55955
|
+
* 水位合法时不触发写盘;仅改 archived 时等待后续业务写操作带出。
|
|
56438
55956
|
*/
|
|
56439
55957
|
async load() {
|
|
56440
55958
|
await file.load();
|
|
@@ -56462,7 +55980,6 @@ function createTopicStore(deps) {
|
|
|
56462
55980
|
acceptanceCriteria: [],
|
|
56463
55981
|
activity: [],
|
|
56464
55982
|
lineMeta: {},
|
|
56465
|
-
// 新话题的第一条线拿 1
|
|
56466
55983
|
nextLineOrdinal: 1,
|
|
56467
55984
|
state: "planning",
|
|
56468
55985
|
createdAt: iso,
|
|
@@ -57443,7 +56960,7 @@ function sanitizeForQuote(def) {
|
|
|
57443
56960
|
return Object.fromEntries(
|
|
57444
56961
|
Object.entries(obj).map(([k3, v3]) => [
|
|
57445
56962
|
k3,
|
|
57446
|
-
//
|
|
56963
|
+
// 新正则避免全局 lastIndex 在 test / matchAll 间串扰。
|
|
57447
56964
|
typeof v3 === "string" && v3.length > 0 && !new RegExp(PLACEHOLDER2).test(v3) ? REDACTED : v3
|
|
57448
56965
|
])
|
|
57449
56966
|
);
|
|
@@ -57681,8 +57198,7 @@ var PackageFindingSchema = EnvPackageSchema.omit({ localVersion: true, artifact:
|
|
|
57681
57198
|
var FINDING_SCHEMAS = {
|
|
57682
57199
|
package: PackageFindingSchema,
|
|
57683
57200
|
secret: EnvSecretSchema,
|
|
57684
|
-
//
|
|
57685
|
-
//(handlers/deploy.ts 的 cliConnectorFromFinding)。MCP 连接器不经上报口,由 scanMcpConnectors 机械列出。
|
|
57201
|
+
// 仅收 CLI 连接器发现;MCP 由 scanMcpConnectors 从配置列出。
|
|
57686
57202
|
connector: CliConnectorFindingSchema
|
|
57687
57203
|
};
|
|
57688
57204
|
function parseFinding(kind, item) {
|
|
@@ -61606,13 +61122,11 @@ var L2 = [
|
|
|
61606
61122
|
"",
|
|
61607
61123
|
"jobs:",
|
|
61608
61124
|
" build:",
|
|
61609
|
-
//
|
|
61610
|
-
// 中文说明归 clawd 的上云页面,这里保持机器好认。
|
|
61125
|
+
// jobs API 只返 name 不返 key,故 name 必须用段 key。
|
|
61611
61126
|
" name: build",
|
|
61612
61127
|
" runs-on: ubuntu-latest",
|
|
61613
|
-
// ACR
|
|
61128
|
+
// ACR 账号内串行构建,需给其他镜像排队留时间。
|
|
61614
61129
|
" timeout-minutes: 40",
|
|
61615
|
-
// 没有 outputs:——smoke / deploy 段自己读契约、自己解析 digest(文件头)。
|
|
61616
61130
|
" steps:",
|
|
61617
61131
|
...CONTRACT_STEPS,
|
|
61618
61132
|
" - name: \u51ED\u636E\u9F50\u4E0D\u9F50\uFF08\u7F3A\u54EA\u51E0\u4E2A\u540D\u5B57\u4E00\u6B21\u5217\u5168\uFF09",
|
|
@@ -61649,7 +61163,7 @@ var L2 = [
|
|
|
61649
61163
|
" ALIBABA_CLOUD_ACCESS_KEY_ID: ${{ secrets.ALI_ACS_ACCESS_KEY_ID }}",
|
|
61650
61164
|
" ALIBABA_CLOUD_ACCESS_KEY_SECRET: ${{ secrets.ALI_ACS_ACCESS_KEY_SECRET }}",
|
|
61651
61165
|
" run: |",
|
|
61652
|
-
// pipefail
|
|
61166
|
+
// pipefail 让 ls-remote 失败正确传播,避免被 cut 的成功状态掩盖。
|
|
61653
61167
|
" set -eo pipefail",
|
|
61654
61168
|
' REPO="$ACR_NAMESPACE/${{ steps.plan.outputs.persona_id }}"',
|
|
61655
61169
|
' node clawos/cloud-host/pipeline/acr.mjs ensure-repo --repo "$REPO" \\',
|
|
@@ -61676,9 +61190,7 @@ var L2 = [
|
|
|
61676
61190
|
" - name: \u4E34\u65F6\u767B\u8BB0 \u2192 \u8D77\u4E00\u6B21 Pod \u2192 \u64A4\u9500",
|
|
61677
61191
|
" # \u767B\u8BB0\u6B63\u5F0F\u53EA\u662F\u5F80 gateway \u5199\u4E00\u884C\uFF0Cgateway \u4E0D\u78B0\u955C\u50CF\uFF1B\u96C6\u7FA4\u62C9\u955C\u50CF\u53D1\u751F\u5728\u7B2C\u4E00\u4E2A\u4EFB\u52A1\u5230\u6765\u65F6\u3002",
|
|
61678
61192
|
" # \u6CA1\u6709\u8FD9\u4E00\u6B65\uFF0C\u6D41\u6C34\u7EBF\u7EFF\u4E86\u53EA\u4EE3\u8868\u300C\u767B\u8BB0\u4E86\u300D\u3002\u955C\u50CF\u7528 digest\uFF08\u96C6\u7FA4 Job \u662F IfNotPresent\uFF0C\u79FB\u52A8 tag \u4F1A\u4E00\u76F4\u8DD1\u65E7\u7684\uFF09\u3002",
|
|
61679
|
-
//
|
|
61680
|
-
// 名字形状 deploy-file.mjs 已卡死(persona_id [a-z0-9-]、model / effort [A-Za-z0-9._/-](`/` 是 pi 的 provider/id,不是元字符)、size 四档枚举、task_concurrency 正整数、connectors 无单引号
|
|
61681
|
-
// 无换行),所以单引号包住直接拼进命令行是安全的。
|
|
61193
|
+
// smoke 与正式发布用同份模型/档位/连接器;deploy-file.mjs 拒绝单引号/换行等危险值,方可单引号拼参。
|
|
61682
61194
|
" env:",
|
|
61683
61195
|
" CLAWD_GATEWAY_URL: ${{ vars.CLAWD_GATEWAY_URL }}",
|
|
61684
61196
|
" CLAWD_GATEWAY_ADMIN_KEY: ${{ secrets.CLAWD_GATEWAY_ADMIN_KEY }}",
|
|
@@ -63026,13 +62538,11 @@ var SubscriptionChannelStore = class {
|
|
|
63026
62538
|
const data = this.readOrCreate();
|
|
63027
62539
|
return data.channels.find((c2) => c2.id === id) ?? null;
|
|
63028
62540
|
}
|
|
63029
|
-
/** 获取当前激活的通道实体 */
|
|
63030
62541
|
getActive() {
|
|
63031
62542
|
const data = this.readOrCreate();
|
|
63032
62543
|
if (!data.activeChannelId) return null;
|
|
63033
62544
|
return data.channels.find((c2) => c2.id === data.activeChannelId) ?? null;
|
|
63034
62545
|
}
|
|
63035
|
-
/** 保存(新增或更新)通道 */
|
|
63036
62546
|
save(input) {
|
|
63037
62547
|
const name = input.name.trim();
|
|
63038
62548
|
if (!name) throw new Error("\u901A\u9053\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
|
|
@@ -63080,7 +62590,6 @@ var SubscriptionChannelStore = class {
|
|
|
63080
62590
|
this.write(data);
|
|
63081
62591
|
return newChannel;
|
|
63082
62592
|
}
|
|
63083
|
-
/** 删除通道 */
|
|
63084
62593
|
delete(id) {
|
|
63085
62594
|
const data = this.readOrCreate();
|
|
63086
62595
|
const filtered = data.channels.filter((c2) => c2.id !== id);
|
|
@@ -63320,15 +62829,7 @@ function createGithubIdentity(deps) {
|
|
|
63320
62829
|
await adopt(token);
|
|
63321
62830
|
return status();
|
|
63322
62831
|
},
|
|
63323
|
-
|
|
63324
|
-
* 本机还没有身份时先问中心有没有本人现成的 token(换台机器授权过 / 上次跳回来了但 daemon 没轮到):
|
|
63325
|
-
* 有且 GitHub 认就直接领、不跳 GitHub。本机已有身份(能用的 = 用户要换一个;失效的 = 要重新授权,而中心那份就是这把死的)
|
|
63326
|
-
* 一律跳 GitHub 重来。进 pending 之后每次 status() 顺手问一次中心领没领到。
|
|
63327
|
-
*
|
|
63328
|
-
* 跳 GitHub 的那一次**直接回 pending 带链接**,不经 status():页面只在拿到 pending 时才开授权页
|
|
63329
|
-
* (ui hooks/useGithubAuth.ts),而 status() 在本机身份还能用时会照常显示它——「换一个」走 status() 就把链接吞了,
|
|
63330
|
-
* 用户点了没跳 GitHub、卡片直接显示已关联,中心那把死 token 永远换不掉(2026-09-17 实测)。之后的轮询仍走 status()。
|
|
63331
|
-
*/
|
|
62832
|
+
// 已有身份时重新授权;直接返回 pending 链接,不能经 status() 被旧的 authorized 状态遮掉。
|
|
63332
62833
|
async oauthStart(returnTo) {
|
|
63333
62834
|
const local = await refresh();
|
|
63334
62835
|
if (!local && await claimCenterToken()) return status();
|
|
@@ -64874,7 +64375,6 @@ async function forwardDispatchToPeer(args) {
|
|
|
64874
64375
|
body: JSON.stringify({
|
|
64875
64376
|
targetPersona: args.targetPersona,
|
|
64876
64377
|
prompt: args.prompt,
|
|
64877
|
-
// T-20: 条件展开防 undefined 污染 JSON。远端 daemon handler 会再校验一次 model 合法性。
|
|
64878
64378
|
...args.model ? { model: args.model } : {}
|
|
64879
64379
|
})
|
|
64880
64380
|
});
|
|
@@ -65671,7 +65171,7 @@ var PiRpcClient = class {
|
|
|
65671
65171
|
if (this.closed || this.inputClosed) return;
|
|
65672
65172
|
this.write(frame);
|
|
65673
65173
|
}
|
|
65674
|
-
/**
|
|
65174
|
+
/** Close the write end for Pi's graceful stdin-EOF shutdown. */
|
|
65675
65175
|
endInput() {
|
|
65676
65176
|
if (this.inputClosed) return;
|
|
65677
65177
|
this.inputClosed = true;
|
|
@@ -65964,7 +65464,6 @@ function drivePiSession(opts) {
|
|
|
65964
65464
|
void client.request({ type: "abort" }).catch(() => {
|
|
65965
65465
|
});
|
|
65966
65466
|
},
|
|
65967
|
-
// 信号由 runner 透传 reducer 的 kill effect;怎么落地由壳定(本机 endInput + 宽限 + kill;云上关流)
|
|
65968
65467
|
async stop(signal = "SIGTERM") {
|
|
65969
65468
|
await opts.kill(signal);
|
|
65970
65469
|
}
|
|
@@ -66079,7 +65578,7 @@ function mapPiModels(raw) {
|
|
|
66079
65578
|
const efforts = levels.length ? levels.map((v3) => PI_THINKING_EFFORTS.find((e) => e.value === v3) ?? { value: v3, label: v3 }) : m3.reasoning === true ? PI_THINKING_EFFORTS : [PI_THINKING_EFFORTS[0]];
|
|
66080
65579
|
out.push({
|
|
66081
65580
|
id: `${provider}/${id}`,
|
|
66082
|
-
//
|
|
65581
|
+
// Ids use provider/model, matching --model.
|
|
66083
65582
|
label: str2(m3.name) || id,
|
|
66084
65583
|
contextWindowSize: cw && cw > 0 ? cw : PI_DEFAULT_CONTEXT_WINDOW,
|
|
66085
65584
|
efforts,
|
|
@@ -66095,7 +65594,6 @@ function buildPiCapabilities(models) {
|
|
|
66095
65594
|
toolSessionIdLabel: "pi Session ID",
|
|
66096
65595
|
models,
|
|
66097
65596
|
permissionModes: [PI_FULL_ACCESS],
|
|
66098
|
-
// 无 file-history-snapshot / 无 pty / observer 是 CC jsonl 解析器 / 无文件分享 / fork 是 claude jsonl 专用 / 无子 agent
|
|
66099
65597
|
features: { rewind: false, subagents: false, tui: false, observe: false, fileSharing: false, fork: false },
|
|
66100
65598
|
configSchema: [
|
|
66101
65599
|
{
|
|
@@ -66291,7 +65789,6 @@ var CLOUD_PI_CAPABILITIES = {
|
|
|
66291
65789
|
toolSessionIdLabel: "pi Session ID",
|
|
66292
65790
|
models: [],
|
|
66293
65791
|
permissionModes: [CLOUD_APPROVAL2],
|
|
66294
|
-
// 无 rewind / 无子 agent(pi 核心没有)/ 无 pty / observer 是 CC jsonl 解析器 / 无文件分享 / fork 是 claude jsonl 专用
|
|
66295
65792
|
features: { rewind: false, subagents: false, tui: false, observe: false, fileSharing: false, fork: false },
|
|
66296
65793
|
configSchema: [
|
|
66297
65794
|
{
|
|
@@ -66784,8 +66281,7 @@ var SubagentMetaObserver = class {
|
|
|
66784
66281
|
w3.pollTimer = setInterval(() => this.scan(w3), 200);
|
|
66785
66282
|
return { dirPath };
|
|
66786
66283
|
}
|
|
66787
|
-
//
|
|
66788
|
-
// 失败就交给 pollTimer 200ms 兜底,目录一出现就被下次 poll 捡起来,那时也尝试 attach
|
|
66284
|
+
// 首次 subagents/ 目录通常还不存在;watch 失败后由 poll 重试 attach。
|
|
66789
66285
|
attachWatcher(w3) {
|
|
66790
66286
|
if (w3.watcher) return;
|
|
66791
66287
|
try {
|
|
@@ -66869,9 +66365,6 @@ var SessionObserver = class {
|
|
|
66869
66365
|
opts;
|
|
66870
66366
|
home;
|
|
66871
66367
|
watches = /* @__PURE__ */ new Map();
|
|
66872
|
-
// 子 observer:监听 <projectsRoot>/<hashDir>/<toolSessionId>/subagents/ 目录的 meta.json,
|
|
66873
|
-
// 仅 TUI 模式启用——SDK 模式靠 `system.task_started` 帧(claude.ts:466)emit subagent_progress
|
|
66874
|
-
// 已经能解锁 AgentCallLine 抽屉入口,再跑 meta watcher 会让 ring buffer 留双帧
|
|
66875
66368
|
metaObserver;
|
|
66876
66369
|
resolveJsonlPath(cwd, toolSessionId, override) {
|
|
66877
66370
|
if (override) return override;
|
|
@@ -66920,11 +66413,8 @@ var SessionObserver = class {
|
|
|
66920
66413
|
});
|
|
66921
66414
|
return { filePath };
|
|
66922
66415
|
}
|
|
66923
|
-
//
|
|
66924
|
-
//
|
|
66925
|
-
// parseLine 对每行都产一条 meta_update,N=200 行就是 200 条几乎相同的 patch。
|
|
66926
|
-
// reducer.shallowEqMeta 会 dedup 但 CPU + log 仍吵。改成同 patch 字段保留最新值合并成单条。
|
|
66927
|
-
// 异常静默吞,不阻塞 watcher 启动
|
|
66416
|
+
// 合并尾部 meta_update 为单条 patch,各字段取最后出现的值;逐行发送会造成重复 CPU/log。
|
|
66417
|
+
// 读取或解析失败不阻塞 watcher 启动。
|
|
66928
66418
|
hydrateMetaTail(w3, maxLines = 200) {
|
|
66929
66419
|
try {
|
|
66930
66420
|
const raw = import_node_fs45.default.readFileSync(w3.filePath, "utf8");
|
|
@@ -66993,10 +66483,8 @@ var SessionObserver = class {
|
|
|
66993
66483
|
}
|
|
66994
66484
|
if (w3.pendingCwdDerive) this.maybeDeriveCwd(w3);
|
|
66995
66485
|
}
|
|
66996
|
-
//
|
|
66997
|
-
//
|
|
66998
|
-
// 检测点唯一(poll ENOENT),fs.watch rename 事件也 funnel 到 poll,天然幂等;scan 加 1s
|
|
66999
|
-
// rate-limit 防 ENOENT 持续期(如 cleanupPeriodDays 真删除)反复 readdir 空转。
|
|
66486
|
+
// CC relocation 是 move,前缀不变:切 watcher 时保留 lastSize/buf 接续读,避免重复。
|
|
66487
|
+
// 文件变小由 poll 按截断处理,uuid dedup 兜底;扫描限频,避免真正删除后持续空转。
|
|
67000
66488
|
maybeRelocate(w3) {
|
|
67001
66489
|
const now = Date.now();
|
|
67002
66490
|
if (now - w3.lastRelocateScanMs < 1e3) return;
|
|
@@ -67043,9 +66531,7 @@ var SessionObserver = class {
|
|
|
67043
66531
|
toolSessionId: w3.toolSessionId
|
|
67044
66532
|
});
|
|
67045
66533
|
}
|
|
67046
|
-
//
|
|
67047
|
-
// 且 content 含 text part)时,抽出 real uuid + 第一段 text + timestamp 上报。
|
|
67048
|
-
// 不依赖 adapter——这是 CC JSONL 格式的兜底识别,与 observer 整体的 CC 耦合一致
|
|
66534
|
+
// CC JSONL 格式兜底,不依赖 adapter;只上报主链 user 文本的 real uuid、首段 text 和 timestamp。
|
|
67049
66535
|
maybeReportUserMessage(sessionId, line) {
|
|
67050
66536
|
const cb = this.opts.onUserMessageObserved;
|
|
67051
66537
|
if (!cb) return;
|
|
@@ -68023,11 +67509,7 @@ var InboxStore = class {
|
|
|
68023
67509
|
fs58.mkdirSync(this.dirPath(), { recursive: true });
|
|
68024
67510
|
}
|
|
68025
67511
|
dataDir;
|
|
68026
|
-
/**
|
|
68027
|
-
* 列出某 peer 的消息, 按 createdAt 升序.
|
|
68028
|
-
* sinceCreatedAt 缺省时返回全量; 提供时仅返回 createdAt > sinceCreatedAt 的增量.
|
|
68029
|
-
* 文件不存在时返回 [] (不抛, 不创建).
|
|
68030
|
-
*/
|
|
67512
|
+
/** sinceCreatedAt 缺省读全量,否则仅读时间戳更大的消息;读取失败返回空数组。 */
|
|
68031
67513
|
list(peerDeviceId, sinceCreatedAt) {
|
|
68032
67514
|
const file = this.filePath(peerDeviceId);
|
|
68033
67515
|
let raw;
|
|
@@ -68041,9 +67523,6 @@ var InboxStore = class {
|
|
|
68041
67523
|
if (sinceCreatedAt === void 0) return all;
|
|
68042
67524
|
return all.filter((m3) => m3.createdAt > sinceCreatedAt);
|
|
68043
67525
|
}
|
|
68044
|
-
/**
|
|
68045
|
-
* 列出所有 peer 的 peerDeviceId (扫目录所有 *.jsonl 文件名).
|
|
68046
|
-
*/
|
|
68047
67526
|
listAllPeerDeviceIds() {
|
|
68048
67527
|
const dir = this.dirPath();
|
|
68049
67528
|
let entries;
|
|
@@ -68055,10 +67534,7 @@ var InboxStore = class {
|
|
|
68055
67534
|
}
|
|
68056
67535
|
return entries.filter((name) => name.endsWith(".jsonl")).map((name) => name.slice(0, -".jsonl".length));
|
|
68057
67536
|
}
|
|
68058
|
-
/**
|
|
68059
|
-
* 追加一条消息,按 message.id 幂等去重。
|
|
68060
|
-
* 双投 + 重试场景下保护 — 同 id 已存在则 no-op,不重写不覆盖。
|
|
68061
|
-
*/
|
|
67537
|
+
/** 按 message.id 幂等追加;双投 / 重试不会重复写入或覆盖。 */
|
|
68062
67538
|
append(message) {
|
|
68063
67539
|
const existing = this.list(message.peerDeviceId);
|
|
68064
67540
|
if (existing.some((m3) => m3.id === message.id)) return;
|
|
@@ -68070,11 +67546,7 @@ var InboxStore = class {
|
|
|
68070
67546
|
} catch {
|
|
68071
67547
|
}
|
|
68072
67548
|
}
|
|
68073
|
-
/**
|
|
68074
|
-
* 把该 peer 所有 createdAt<=upToCreatedAt 且 sender!=readerDeviceId 的消息
|
|
68075
|
-
* readBy[readerDeviceId] 写成 upToCreatedAt. O(N) rewrite 整个文件.
|
|
68076
|
-
* 返写入了几条.
|
|
68077
|
-
*/
|
|
67549
|
+
/** 对方消息的 readBy 水位只升不降;整文件重写,返回实际修改条数。 */
|
|
68078
67550
|
markRead(peerDeviceId, readerDeviceId, upToCreatedAt) {
|
|
68079
67551
|
const messages = this.list(peerDeviceId);
|
|
68080
67552
|
let changed = 0;
|
|
@@ -68090,9 +67562,6 @@ var InboxStore = class {
|
|
|
68090
67562
|
this.rewriteFile(peerDeviceId, next);
|
|
68091
67563
|
return changed;
|
|
68092
67564
|
}
|
|
68093
|
-
/**
|
|
68094
|
-
* 删整个 peer 的 jsonl (peer:remove cascade). 不存在 no-op.
|
|
68095
|
-
*/
|
|
68096
67565
|
removeByPeerDeviceId(peerDeviceId) {
|
|
68097
67566
|
const file = this.filePath(peerDeviceId);
|
|
68098
67567
|
try {
|
|
@@ -68144,13 +67613,7 @@ var InboxManager = class {
|
|
|
68144
67613
|
}
|
|
68145
67614
|
store;
|
|
68146
67615
|
broadcast;
|
|
68147
|
-
/**
|
|
68148
|
-
* 写一条消息到 peer.jsonl + broadcast 给本机所有 owner ws。
|
|
68149
|
-
* 按 id 幂等:同 id 已落盘则 no-op(不广播、不重写)。
|
|
68150
|
-
*
|
|
68151
|
-
* caller 必须已通过 handler 鉴权
|
|
68152
|
-
* (owner ctx 必填 args.peerOwnerId;guest ctx 由 handler 反查 peer-store)。
|
|
68153
|
-
*/
|
|
67616
|
+
/** 按 id 幂等写入并广播;调用方须先鉴权并确定 peerDeviceId / senderDeviceId。 */
|
|
68154
67617
|
postMessage(args) {
|
|
68155
67618
|
const beforeLen = this.store.list(args.peerDeviceId).length;
|
|
68156
67619
|
const msg = {
|
|
@@ -68175,10 +67638,7 @@ var InboxManager = class {
|
|
|
68175
67638
|
list(peerDeviceId, sinceCreatedAt) {
|
|
68176
67639
|
return this.store.list(peerDeviceId, sinceCreatedAt);
|
|
68177
67640
|
}
|
|
68178
|
-
/**
|
|
68179
|
-
* 标已读. 返写入了几条 (sender !== self 且 createdAt <= upToCreatedAt 的消息).
|
|
68180
|
-
* broadcast 已更新 readBy 的每条消息 (单帧一消息, 复用 inbox:event 通道).
|
|
68181
|
-
*/
|
|
67641
|
+
/** 更新对方消息的已读水位,返回实际修改条数,并逐消息广播 readBy 变更。 */
|
|
68182
67642
|
markRead(args) {
|
|
68183
67643
|
const before = this.store.list(args.peerDeviceId);
|
|
68184
67644
|
const changed = this.store.markRead(args.peerDeviceId, args.readerDeviceId, args.upToCreatedAt);
|
|
@@ -68197,7 +67657,6 @@ var InboxManager = class {
|
|
|
68197
67657
|
}
|
|
68198
67658
|
return changed;
|
|
68199
67659
|
}
|
|
68200
|
-
/** peer:remove cascade. 删整个 peer 的 jsonl. */
|
|
68201
67660
|
removeChannel(peerDeviceId) {
|
|
68202
67661
|
this.store.removeByPeerDeviceId(peerDeviceId);
|
|
68203
67662
|
}
|
|
@@ -70864,7 +70323,6 @@ function buildDeviceHandlers(deps) {
|
|
|
70864
70323
|
return {
|
|
70865
70324
|
response: {
|
|
70866
70325
|
type: "device:connect:ok",
|
|
70867
|
-
// 返回连接的设备标识(= 请求的 args.deviceId)
|
|
70868
70326
|
deviceId: args.deviceId,
|
|
70869
70327
|
name: contact.displayName,
|
|
70870
70328
|
url: contact.remoteUrl
|
|
@@ -73737,12 +73195,7 @@ var METHOD_GRANT_MAP = {
|
|
|
73737
73195
|
// 跨设备 exec(spec 2026-07-16):public——"能连上=有 auth",业务在 handler 校
|
|
73738
73196
|
// (ensureGuest + remoteAccessAllowed),跟 inbox:postMessage 同模式。
|
|
73739
73197
|
"peerExec:run": { kind: "public" },
|
|
73740
|
-
//
|
|
73741
|
-
// snapshot/buffer/flush 修复):之前三件套 ADMIN_ANY 把 guest 拦在 dispatcher 401,
|
|
73742
|
-
// 导致 XtermPanel 重挂载拉 snapshot 在 guest 路径不生效,老的字节流断裂串扰回来。
|
|
73743
|
-
// 同一个 session 永远只有一个操作者(owner 不进 guest 创建的 session),并发字节流
|
|
73744
|
-
// 冲突不存在;guest 在 chat 模式下已能 session:send/interrupt,PTY 域不开放纯属
|
|
73745
|
-
// 历史遗留的不对称。handler 端通过 ensureSessionAccess 验权对齐其他 session:* RPC。
|
|
73198
|
+
// PTY 与其他 session RPC 一样,由 handler 通过 ensureSessionAccess 校验会话访问权限。
|
|
73746
73199
|
"session:pty:input": CAPABILITY_SCOPED,
|
|
73747
73200
|
"session:pty:resize": CAPABILITY_SCOPED,
|
|
73748
73201
|
"session:pty:snapshot": CAPABILITY_SCOPED,
|
|
@@ -73836,7 +73289,7 @@ function computeMethodAccess(args) {
|
|
|
73836
73289
|
}
|
|
73837
73290
|
|
|
73838
73291
|
// src/version.ts
|
|
73839
|
-
var version = "0.2.
|
|
73292
|
+
var version = "0.2.620".length > 0 ? "0.2.620" : "dev";
|
|
73840
73293
|
|
|
73841
73294
|
// src/cli-probe/probe.ts
|
|
73842
73295
|
var fs84 = __toESM(require("fs"), 1);
|