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