@margay/ccl-core 1.5.0-alpha.0 → 1.5.1-alpha.2

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.
Files changed (94) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +95197 -74935
  3. package/dist/sdk/background.d.ts +48 -0
  4. package/dist/sdk/background.js +87 -0
  5. package/dist/sdk/backgroundRunTypes.d.ts +101 -0
  6. package/dist/sdk/backgroundRunTypes.js +1 -0
  7. package/dist/sdk/commandAdapter.d.ts +77 -0
  8. package/dist/sdk/commandAdapter.js +180 -0
  9. package/dist/sdk/commandProcess.d.ts +27 -0
  10. package/dist/sdk/commandProcess.js +130 -0
  11. package/dist/sdk/executionGuidance.d.ts +7 -0
  12. package/dist/sdk/executionGuidance.js +37 -0
  13. package/dist/sdk/index.d.ts +16 -1
  14. package/dist/sdk/index.js +10 -0
  15. package/dist/sdk/inputProvenance.d.ts +13 -0
  16. package/dist/sdk/inputProvenance.js +26 -0
  17. package/dist/sdk/mcpServer.d.ts +9 -5
  18. package/dist/sdk/mcpServer.js +51 -9
  19. package/dist/sdk/operations/command.d.ts +24 -0
  20. package/dist/sdk/operations/command.js +48 -0
  21. package/dist/sdk/operations/host.d.ts +3 -0
  22. package/dist/sdk/operations/host.js +376 -0
  23. package/dist/sdk/operations/json.d.ts +6 -0
  24. package/dist/sdk/operations/json.js +24 -0
  25. package/dist/sdk/operations/reference.d.ts +15 -0
  26. package/dist/sdk/operations/reference.js +19 -0
  27. package/dist/sdk/operations/store.d.ts +26 -0
  28. package/dist/sdk/operations/store.js +300 -0
  29. package/dist/sdk/operations/types.d.ts +171 -0
  30. package/dist/sdk/operations/types.js +8 -0
  31. package/dist/sdk/pendingApprovalIdentity.d.ts +35 -0
  32. package/dist/sdk/pendingApprovalIdentity.js +27 -0
  33. package/dist/sdk/permissionDelegation.d.ts +19 -0
  34. package/dist/sdk/permissionDelegation.js +11 -0
  35. package/dist/sdk/query.d.ts +4 -2
  36. package/dist/sdk/query.js +236 -56
  37. package/dist/sdk/queryInput.js +7 -0
  38. package/dist/sdk/queryLifecycle.d.ts +19 -0
  39. package/dist/sdk/queryLifecycle.js +93 -0
  40. package/dist/sdk/queryLifecycleTypes.d.ts +160 -0
  41. package/dist/sdk/queryLifecycleTypes.js +47 -0
  42. package/dist/sdk/runtimeResources.d.ts +197 -0
  43. package/dist/sdk/runtimeResources.js +36 -0
  44. package/dist/sdk/scheduledOccurrenceTypes.d.ts +103 -0
  45. package/dist/sdk/scheduledOccurrenceTypes.js +12 -0
  46. package/dist/sdk/session.d.ts +4 -3
  47. package/dist/sdk/session.js +19 -14
  48. package/dist/sdk/types.d.ts +21 -2
  49. package/docs/README.md +5 -0
  50. package/docs/background-runs.md +229 -0
  51. package/docs/ccl-duo-agent.md +1 -4
  52. package/docs/ccl-ide-usage.md +7 -70
  53. package/docs/cli-installation.md +37 -0
  54. package/docs/command-tool-recovery.md +62 -0
  55. package/docs/dev-quickstart.md +29 -7
  56. package/docs/error-recovery-usage.md +30 -2
  57. package/docs/features/business-facts.md +2 -4
  58. package/docs/hook-input-provenance.md +120 -0
  59. package/docs/index.md +30 -96
  60. package/docs/local-analytics-quickstart.md +11 -28
  61. package/docs/mcp-tool-recovery.md +82 -0
  62. package/docs/operations-client-integration.md +1 -7
  63. package/docs/peer-development-review.md +0 -1
  64. package/docs/releases/1.5.0-alpha.3.md +17 -0
  65. package/docs/releases/1.5.0-alpha.4.md +16 -0
  66. package/docs/releases/1.5.0-alpha.5.md +26 -0
  67. package/docs/releases/1.5.0-alpha.6.md +15 -0
  68. package/docs/releases/1.5.1-alpha.1.md +11 -0
  69. package/docs/releases/1.5.1-alpha.2.md +17 -0
  70. package/docs/releases/1.5.1-alpha.md +22 -0
  71. package/docs/runtime-resources.md +137 -0
  72. package/docs/self-hosted-product-events.md +6 -35
  73. package/docs/shared-skill-resources.md +50 -0
  74. package/examples/background-runs/agents/background-reviewer.md +6 -0
  75. package/examples/background-runs/agents/background-worker.md +6 -0
  76. package/examples/background-runs/worker-reviewer.mjs +42 -0
  77. package/examples/command-recovery/durable-server.mjs +44 -0
  78. package/examples/command-recovery/receipt-command.mjs +40 -0
  79. package/examples/runtime-resources/catalog.ts +68 -0
  80. package/package.json +25 -14
  81. package/sdk/API.md +236 -1
  82. package/sdk/README.md +6 -0
  83. package/docs/agent-owned-error-recovery-ux-2026-10-04.md +0 -146
  84. package/docs/analytics-capabilities-2026-10-08.md +0 -223
  85. package/docs/releases/1.4.5-rc.1.md +0 -39
  86. package/docs/releases/1.4.5-rc.10.md +0 -50
  87. package/docs/releases/1.4.5-rc.2.md +0 -26
  88. package/docs/releases/1.4.5-rc.3.md +0 -9
  89. package/docs/releases/1.4.5-rc.6.md +0 -11
  90. package/docs/releases/1.4.5-rc.7.md +0 -14
  91. package/docs/releases/1.4.5-rc.8.md +0 -18
  92. package/docs/releases/1.4.5-rc.9.md +0 -22
  93. package/docs/releases/1.4.6-beta.0.md +0 -54
  94. package/docs/releases/1.5.0-alpha.0.md +0 -44
@@ -71,6 +71,10 @@ export type QueryOptions = {
71
71
  * deny, the tool does not execute.
72
72
  */
73
73
  canUseTool?: CanUseToolFn;
74
+ /** Default review preserves SDK host review of ambient allows; ask_only trusts local policy. */
75
+ permissionCallbackMode?: 'review' | 'ask_only';
76
+ /** Host-supplied task grants, applied before any input/resume. Never model-supplied. */
77
+ permissionDelegation?: import('./permissionDelegation.js').PermissionDelegation;
74
78
  /**
75
79
  * Milliseconds before an unresolved canUseTool callback is treated as a
76
80
  * deny (fail-closed). Default 300_000. Pass 0 to disable the timeout
@@ -234,6 +238,7 @@ export type LifecycleHooks = {
234
238
  toolName?: string;
235
239
  content: unknown;
236
240
  isError: boolean;
241
+ operationReference?: import('./operations/reference.js').OperationReference;
237
242
  sessionId?: string;
238
243
  }) => void | Promise<void>;
239
244
  /**
@@ -261,16 +266,21 @@ export type PendingApproval = {
261
266
  toolName: string;
262
267
  input: Record<string, unknown>;
263
268
  createdAt: string;
269
+ /** New suspension requests are resumable only after the CLI confirms durable preservation. */
270
+ suspension?: 'requested' | 'confirmed';
264
271
  };
265
272
  /** Host decision applied on resume for a recorded pending approval. */
266
273
  export type PendingDecision = {
274
+ /** Bind a durable decision to the original approval and enforce matching before CLI tool execution. */
275
+ expectedApproval?: import('./pendingApprovalIdentity.js').PendingApprovalIdentity;
276
+ } & ({
267
277
  behavior: 'allow';
268
- /** Override the approved input; defaults to the recorded pending input. */
278
+ /** Override the approved input in legacy mode; strict expectedApproval permits only identical input. */
269
279
  updatedInput?: Record<string, unknown>;
270
280
  } | {
271
281
  behavior: 'deny';
272
282
  message?: string;
273
- };
283
+ });
274
284
  export type AccumulatedUsage = {
275
285
  queries: number;
276
286
  inputTokens: number;
@@ -311,6 +321,8 @@ export type SDKUserMessage = {
311
321
  uuid?: string;
312
322
  parent_tool_use_id?: null;
313
323
  session_id?: string;
324
+ isSynthetic?: boolean;
325
+ input_provenance?: import('./inputProvenance.js').InputProvenance;
314
326
  };
315
327
  export type SDKEvent = SDKEventPayload & {
316
328
  turnId?: string;
@@ -349,6 +361,10 @@ export type SessionLifecycleEvent = {
349
361
  reason: string;
350
362
  });
351
363
  type SDKEventPayload = {
364
+ type: 'query_lifecycle';
365
+ lifecycle: import('./queryLifecycleTypes.js').QueryLifecycleObservation;
366
+ sessionId: string;
367
+ } | {
352
368
  type: 'assistant_text';
353
369
  text: string;
354
370
  sessionId?: string;
@@ -371,6 +387,7 @@ type SDKEventPayload = {
371
387
  toolUseId: string;
372
388
  content: unknown;
373
389
  isError: boolean;
390
+ operationReference?: import('./operations/reference.js').OperationReference;
374
391
  sessionId?: string;
375
392
  } | {
376
393
  type: 'permission_request';
@@ -444,6 +461,8 @@ type SDKEventPayload = {
444
461
  * SDKControlInitializeResponseSchema in entrypoints/sdk/controlSchemas.ts.
445
462
  */
446
463
  export type InitializationResult = {
464
+ /** Fresh on initialize/reinitialize; descriptive metadata, never a permission grant. */
465
+ runtime_resources?: import('./runtimeResources.js').RuntimeResources;
447
466
  commands?: unknown[];
448
467
  agents?: unknown[];
449
468
  models?: unknown[];
package/docs/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # CCL / Margay documentation
2
+
3
+ Start with the [installation guide](cli-installation.md), [quick start](../QUICKSTART.md), or [documentation index](index.md).
4
+
5
+ For integrations, see the [SDK guide](../sdk/README.md), [API contract](../sdk/API.md), [runtime resources](runtime-resources.md), and [shared skills](shared-skill-resources.md).
@@ -0,0 +1,229 @@
1
+ # 持久后台任务(1.5.1-alpha)
2
+
3
+ `BackgroundRun` 让 assistant 明确委派独立后台任务,接受后释放前台。普通短工具、当前会话中的 Agent/Bash 后台任务仍可沿用原接口,但不据此承诺它们能跨宿主退出持续运行。此文描述 `1.5.1-alpha` 接口,不能使用旧 npm alpha 代替。Node 要求为 `>=22.19.0`;已有验证覆盖 macOS arm64 的 Node 22.19.0/24.20.0,事件分页与连续前台交互的原验收使用 Node 24.20.0。安装时固定 `@margay/ccl-core@1.5.1-alpha`,使用该包的 CLI **和配套 SDK**;发行可用性以 npm registry 为准。升级不会替换运行中进程的代码,旧会话与 daemon 须在确认已有任务状态后正常退出并重启。遇到旧 owner 不支持提交时会明确拒绝,不自动杀掉或接管它。
4
+
5
+ ## 用户与 assistant 的分工
6
+
7
+ 宿主保存接受回执、会话身份、执行事件、权限、取消请求和结果。Pepper 等 assistant 继续负责业务任务版本、来源、时限、成果验收和对外送达。收到 runId 可以回复“已接受”;不能据此回复“业务已完成”。
8
+
9
+ 模型的共享执行指引覆盖默认、自定义及子 agent 提示:需要立即依赖结果时同步执行,独立且已授权的工作可移交后台,让前台继续响应。用户明确要求等待最终结果时,不能仅用接受回执结束任务。`BackgroundRun` 也适用于前端仍打开的情况;提交方应传入任务范围、必要规则和验收条件。会话内 Agent/Bash 后台与持久后台的生命周期不同。完成通知依赖仍活跃的接收通道,没有通知不能推断任务仍在运行或已经完成;没有可用通道时保留原标识,通过已有查询入口取回结果。这些是选择和交付指引,不会改变实际工具权限、agent 默认模式或自动后台阈值。
10
+
11
+ 实际模型/工具 CLI 自己持执行锁并保存事件,daemon 只负责原有调度。关闭前台或停止 daemon 不会取消独立执行者。执行者自身崩溃后不自动重跑整个 agent;按原 runId 查询,使用返回的 `operationReferences` 核对自定义工具效果。恢复不要求原聊天还活着,也不意味着允许重放未知写入。
12
+
13
+ 当前 TUI 的 Ctrl+B 可处理已有前台工具任务,不能把整个活跃主会话转成持久后台 run。需要释放前台并保留独立执行、查询与恢复能力时,使用 `BackgroundRun` 或本文 SDK 接口。会话内任务的停止状态和持久 run 的状态属于不同接口,不应互换状态值。
14
+
15
+ 对宿主已捕获的 MCP/外部 Agent CLI 调用,先接受 operation,再把其引用持久保存到后台 run,之后才允许派发。关联保存失败会阻止工具执行;返回后的引用更新不倒退版本。执行器在工具返回前崩溃,也可从原 runId 找到该 operation;是否能核对远端效果仍取决于注册的 query/replay 能力,不能自动重跑未知写入。旧开发版本缺失的历史关联无法自动补全,空数组不证明未执行。
16
+
17
+ ## SDK
18
+
19
+ ```js
20
+ import { createBackgroundClient } from '@margay/ccl-core/sdk'
21
+
22
+ const background = createBackgroundClient({
23
+ cwd: '/absolute/task/project',
24
+ cliPath: '/absolute/path/to/ccl/dist/cli.js',
25
+ // execPath: '/path/to/node',
26
+ launch: { model: 'your-model' },
27
+ })
28
+ const run = await background.submitRun({
29
+ requestKey: 'business-task-id:version-1',
30
+ spec: { prompt: '执行明确且有界的任务,保存真实成果', maxTurns: 12 },
31
+ })
32
+ // 此时可立即回应用户并处理下一条输入。
33
+ const view = await background.getRun({ runId: run.runId })
34
+ if (view.status === 'found') console.log(view.snapshot, view.observation)
35
+
36
+ // 演示只读观察,不是可靠业务送达。真实消费者应在处理成功后持久保存游标。
37
+ let cursor
38
+ let page
39
+ do {
40
+ page = await background.readRunEvents({ runId: run.runId, cursor, limit: 100 })
41
+ for (const event of page.events) console.log(event)
42
+ cursor = page.nextCursor
43
+ } while (page.hasMore)
44
+ ```
45
+
46
+ 该入口由本版本 package.json 的 ./sdk export 导出。源码示例见 `examples/background-runs/worker-reviewer.mjs`:worker 和 reviewer 用不同 agent 定义、不同会话、各自预算;reviewer 必须读取真实文件。示例只返回结果,不自动发送业务消息,也不把 reviewer 的自述当作独立事实验证。
47
+
48
+ 同作用域同键同内容接受一次。丢失 ACK 时用原 requestKey 查询;不要生成新键重派。异内容/异执行配置冲突必须核对。源码或 agent 定义改变不能静默继续原任务。凭据值不参与执行配置身份,目标与定义仍参与绑定。仅存在于调用进程中的 JS 权限回调、SDK MCP 内存服务不能变成耐崩溃后台服务;使用独立 stdio/HTTP MCP 服务配置。
49
+
50
+ assistant 在第一次提交前应持久保存原 `cwd`(项目作用域)、`requestKey`、任务内容/版本和授权来源;收到回执后保存 `runId/sessionId`。宿主不会从 Pepper 的业务账本或记忆推断这些字段。SDK `ack_unknown`、`invalid_response` 或连接中断表示回执不确定,不代表任务没被接受。先在**同一项目**用原键 `getRun`;返回 `not_found` 时核对项目、记录是否被移动/删除及请求是否仍在途中。只能用原键原内容重试接受,不能换键绕过不确定状态。记录丢失时没有“查询为空即可安全重跑”的保证;配置冲突、历史损坏、未知格式应保留错误并核对,不能重建空库掩盖它们。
51
+
52
+ 每次提交经私有本机连接交接当前启动配置与凭据,排队任务分别保存内存上下文,不借用其它任务的 token。当前支持 `CCL_GATEWAY_KEY`、`CCL_API_KEY`、`ANTHROPIC_API_KEY`、`ANTHROPIC_AUTH_TOKEN`、`CLAUDE_CODE_OAUTH_TOKEN`,以及 `--settings` / `--mcp-config` 中的配置;兼容变量沿现有读取路径生效,不重新启用已禁用的官方直连入口。文件型凭据由执行者按原适配器机制读取。调用方的任意自定义环境变量不会自动同步;MCP 使用显式配置。凭据文件描述符不支持跨独立进程继承,会明确拒绝。
53
+
54
+ 磁盘只保存无密钥的恢复来源描述。owner 重启后,排队任务从匹配的启动上下文或原配置文件恢复;若临时凭据已丢失,任务仍为 `queued`,并报告 `dispatchBlockedReason: context_unavailable`。用原 requestKey 和相同任务内容重新提交可补回上下文,保留原 runId/sessionId。已经开始的任务不会因此重派或更换凭据。
55
+
56
+ 指定 `spec.agent` 的后台任务必须找到该 agent 定义,否则在首次模型派发前失败。主 agent 的工具池和 `Agent(允许的子类型)` 限制共同生效,包括对子 agent、外部 CLI 与 fork 的实际分派检查。
57
+
58
+ 前台处于 Plan 模式时,`BackgroundRun` 工具由宿主把 `permissionMode: 'plan'` 写入任务 spec;后台 worker 保留这个模式。该字段不暴露给模型选择或删除。SDK/CLI 调用方也可在 `spec` 中显式指定 `permissionMode: 'plan'`;这里只接受 plan,省略时保持原有 default 行为,不能用它指定 bypassPermissions 等模式。Plan 沿用现有权限规则,并非新增一条禁止所有写入的硬性规则。限制参与原请求的不可变身份;同键改变限制会冲突,旧记录不会自动升级。遇到冲突应先查询原任务及工具效果,不得通过换键重派来绕过不确定状态。
59
+
60
+ ## 状态、事件和权限
61
+
62
+ `getRun` 返回 `snapshot`、`observation`、`allowedActions`。snapshot 是最后保存的事实;observation 是本次本地检查。执行锁 held 只表示持有执行权,不证明正在进展,更不证明远端任务仍活着。free 也不证明外部副作用停止。外部当前状态默认 unknown。
63
+
64
+ 后台 worker 保存 `system/error_diagnostic` 事件,并在 `snapshot.latestDiagnostic` 返回最近捕获的有效恢复诊断(旧记录或尚无诊断时省略)。它描述一次请求,不替代任务 `state`:例如此前工具已保存成功,下一次模型响应流中断时,诊断仍可能是 `toolState: 'not_started'`。这不表示此前工具没执行,应同时核对原事件与 `operationReferences`。后续成功或失败不会抹掉这份历史诊断;`recovery.phase: 'recovering'` 也不证明执行者现在仍活着。消费者应结合终态和当前 observation 显示结果,执行者崩溃且缺少终态时报告未知,不自动重跑整个任务。
65
+
66
+ - `queued/starting/running/waiting_permission`:已接受、开始或等待;不是业务完成。
67
+ - `succeeded`:模型循环正常结束,未记录工具错误或权限拒绝;业务成果仍需验收。
68
+ - `completed_with_issues`:模型正常结束,但存在工具错误或权限拒绝。原 SDK result、错误和拒绝均保留。
69
+ - `failed/interrupted`:失败或没有完整终态;不自动重放未知执行。
70
+ - `cancelled`:队列内尚未执行的任务被取消。运行中的任务先记录 cancellation=requested;晚到真实结果不会被取消请求覆盖。
71
+
72
+ pending permission 保存原 toolUseId、工具、输入、assistant 和会话。批准必须指定当前 revision 和稳定 actionId,不能更换输入:
73
+
74
+ ```js
75
+ await background.approveRun({
76
+ runId, expectedRevision, actionId: 'explicit-approval-1',
77
+ toolUseId, decision: 'allow',
78
+ })
79
+ await background.cancelRun({ runId, expectedRevision, actionId: 'cancel-1' })
80
+ ```
81
+
82
+ 批准不重启已死亡的执行者,也不覆盖当前拒绝规则。`BackgroundRun` 模型工具允许显式提交、查询、读事件、取消和请求批准;allow 批准仍走前台权限确认。
83
+
84
+ 事件按 runId/generation/sequence 标识,支持至少一次补收。TUI 订阅实际提交到该会话的 run;使用稳定通知 ID,需要投递的原消息保存进会话后才推进游标。读取 get/events 或 TaskOutput 不会提前吞掉终态通知。前台不在时事件保留;重新打开原会话可以补收。
85
+
86
+ 权限通知在投递前和队列实际消费时核对原 run/generation/toolUseId 的当前保存状态。已经批准、拒绝、取消或进入终态的旧请求不再要求重复决定;仅通知订阅游标越过该过期事件,完整历史保持可查询,后续结果继续投递。仍未决的通知携带精确 pendingPermission、当前 revision 和检查时间;这不是远端活性保证,询问或批准前仍应查询原状态,批准继续使用原 expectedRevision/actionId。读取失败、身份不匹配或记录缺失不会被当作已解决:通知报错并保留游标,恢复后可重投。已经送进模型的输入无法追溯撤回,不据旧快照重复批准工具。
87
+
88
+ `readRunEvents` / CLI `events` / 模型工具 `events` 接受 `cursor` 和 `limit`,返回 `{events, nextCursor, hasMore}`。`limit` 默认 100、范围 1–1000 的整数;普通页累计序列化事件以约 1 MiB 为目标,首条超过目标仍整条返回。`nextCursor` 只越过本页实际返回的事件;空尾页保留同一游标,`hasMore=false` 表示该次读取时已到尾部,后续仍可能追加。根据业务需要继续读 `hasMore` 页面,不必每轮扫描完整历史;保存不透明游标,不自行修改或跨 run/项目复用。缺行、校验损坏、错 generation 或未来游标会明确失败,不静默跳过。
89
+
90
+ 可靠消费者先处理事件,再持久保存 `nextCursor`。处理后、存游标前崩溃会重复收到事件,因此业务处理/送达应以事件身份或原业务键去重;宿主事件已保存不证明消息已送到外部用户。SDK 仍保留单次响应 32 MiB 保护:先用 `limit: 1` 缩小页面;若**单条完整事件**也超限,使用下面的 CLI 文件路径读取原记录,不提交新任务,也不把失败游标记为已消费。模型工具沿现有大结果文件机制返回完整原结果的文件引用。
91
+
92
+ ## CLI 与存储边界
93
+
94
+ `ccl daemon submit|get|events|cancel-run|approve-run --request-stdin --cwd PROJECT` 从 stdin 接收 JSON。submit 可启动缺失的兼容 daemon;读取不会启动进程。旧活 owner 配置不匹配时明确拒绝,不暗中替换。daemon stop 停调度;取消某一 run 使用 cancel-run。
95
+
96
+ | 目的 | SDK | CLI 子命令 | `BackgroundRun` 模型工具 action |
97
+ | --- | --- | --- | --- |
98
+ | 持久接受 | `submitRun` | `submit` | `submit` |
99
+ | 查询原记录 | `getRun` | `get` | `get` |
100
+ | 分页补收 | `readRunEvents` | `events` | `events` |
101
+ | 请求取消 | `cancelRun` | `cancel-run` | `cancel` |
102
+ | 决定原权限请求 | `approveRun` | `approve-run` | `approve` |
103
+
104
+ SDK/CLI 请求使用 `requestKey/runId/expectedRevision/actionId/toolUseId`;模型工具分别用 `request_key/run_id/expected_revision/action_id/tool_use_id`。`cursor/limit/decision/spec` 同名,`spec.maxTurns` 等嵌套字段也仍是 camelCase。查询二选一提供原 `runId` 或 `requestKey`。模型工具显式提交即选择持久后台;宿主不猜测 assistant 的业务任务应前台还是后台。SDK 返回原结果;CLI 用 `{ok:true,result:...}` 包装,错误为 `{ok:false,code,error}`。
105
+
106
+ 以下文件由调用方准备,`original-events-request.json` 内容例如 `{"requestKey":"business-task-id:version-1","limit":1}`;续读时加入上次成功处理后的 `cursor`。输出文件应放在任务自有目录,包含工具数据,按其敏感程度保存。读取与重定向不启动执行器,也不消费通知:
107
+
108
+ ```sh
109
+ ccl daemon get --request-stdin --cwd /absolute/task/project < original-get-request.json
110
+ ccl daemon events --request-stdin --cwd /absolute/task/project < original-events-request.json > original-events-page.json
111
+ ```
112
+
113
+ `original-get-request.json` 只需 `{"requestKey":"business-task-id:version-1"}`。提交 JSON 示例为 `{"requestKey":"business-task-id:version-1","spec":{"prompt":"执行已授权的有界任务","maxTurns":12}}`,传给 `daemon submit`;不要在 shell 命令行拼接敏感输入。SDK 的 `launch` 配置由客户端随提交透传;CLI 可在提交 JSON 中给出相同 `launch` 对象。
114
+
115
+ 等待权限时向用户展示原工具及原输入;没有明确允许就不批准。取消请求也不能证明远端效果已停止。执行器死亡后先看原 `operationReferences`,再经 [Operation/MCP 恢复入口](mcp-tool-recovery.md) 查询原效果;是否允许重放取决于已注册契约和当前权限。worker 与 reviewer 使用不同任务键、agent 定义、会话及各自预算;示例中的等待循环只演示编排,前台服务不应 `await` 整条 worker/reviewer 链才处理下一条消息。assistant 收到接受回执即可回应,后台监督逻辑另外补收事件并验收,成功送达后再更新自己的业务记录。
116
+
117
+ 项目 `.ccl/daemon/submissions/runs.sqlite` 保存事务事实,独立 executor.sqlite 保存执行互斥。读取损坏记录、未知格式、热日志时不重建空历史;owner 维护路径可恢复已提交事务。未发布 JSONL 草稿明确报告 legacy,不自动重派。初版不自动清除记录与事件,磁盘占用会增长;不存在自动 expired 或“没查到就一定没执行”的保证。
118
+
119
+ Windows、断电恢复以及完整 #55 冻结案例的验收结果以本轮报告为准;不要把本说明或计划审核视为已经通过。
120
+
121
+ ## 独立 reviewer:取得原生证据
122
+
123
+ `worker-reviewer.mjs` 中的 reviewer 是独立主会话,有自己的 `runId/sessionId`,不是主助手的子 agent;不要为它合成 `SubagentStop`。使用原项目的 `getRun({runId})` 和 `readRunEvents({runId,cursor,limit})`,或上面的 CLI `get/events`。`sessionId` 用于核对返回身份,不是这些查询的选择参数,也不应用它猜测内部 transcript 路径。公共事件接口不要求等待 transcript flush。
124
+
125
+ 以下字段可用于审核;事件的原生消息在 `event.data`,SDK 类型为 `unknown`,消费者应先检查对象、消息类型和数组结构。所有身份都应与原接受回执匹配。
126
+
127
+ | 需要核对的事实 | 保存位置 | 不能据此推断 |
128
+ | --- | --- | --- |
129
+ | 接受的任务和身份 | `snapshot.spec`、`specHash`、`configId`、`runId/sessionId/generation`;`accepted` 事件也保存快照 | `spec` 不包含文件内容或完整拼装请求 |
130
+ | 运行初始化 | `sdk_system`,`data.subtype === 'init'`,包括 `session_id/model/tools` 等实际初始化字段 | 工具名称列表不是最终每次请求的完整工具 schema |
131
+ | 服务返回的模型标签 | `sdk_assistant` 的 `data.message.model`;按消息保留,不假设整轮只有一个模型 | 标签不是上游权重的独立证明,正常事件不保证有部署响应头 |
132
+ | 实际工具调用 | `sdk_assistant` 的 `data.message.content` 中 `type: 'tool_use'` 块,含 `id/name/input` | 调用被提出不表示执行成功 |
133
+ | 实际工具返回 | `sdk_user` 的 `data.message.content` 中 `type: 'tool_result'` 块,以 `tool_use_id` 对应调用的 `id` | 返回内容不保证文件读全、外部效果完成或业务审核正确 |
134
+ | 主循环结束 | `sdk_result` 的原生结果、`session_id/uuid/is_error`,以及同一事件的 snapshot | `succeeded` 不是业务验收或外部送达回执 |
135
+
136
+ 例如核对 reviewer 是否读过指定材料:先定位它的原 run 和 session,再按 sequence 读取原生调用;检查 Read 的具体路径、范围及相应 tool_result 的实际内容和错误。只看到工具名、模型声称“已读”、空返回或文件引用,都不足以认定所需内容已被完整读取。大结果可能按工具原有机制截断或返回文件引用;引用文件的存在性、保留时间和访问权限不因事件保存而自动得到保证。若调查涉及可恢复外部工具效果,沿原 `operationReferences` 查询其恢复契约;工具返回和模型终答不能替代原操作状态核查。
137
+
138
+ 原生 envelope 在输出前进入后台事实库;每次保存把事件和对应快照放在同一事务中。消费到匹配 run/generation/session 的 `sdk_result`,可定位该主循环已保存的结果边界,之前的工具事件仍需检查错误、拒绝及缺失返回。之后仍可能追加 `executor_exit` 等事件;不能把 `hasMore=false` 当永久 EOF,也不能把主循环结束当所有外部效果或其它任务已结束。崩溃缺少 sdk_result 时保留“不完整/未知”;配置失败等终态也可能没有正常模型结果。`completed_with_issues` 必须保留问题,不能折算为审核通过。
139
+
140
+ ### “冻结输入”的实际范围
141
+
142
+ 宿主不可变保存的是被接受的 `spec.prompt`、可选 `spec.systemPrompt` 和其余 spec 字段。`configId` 绑定执行代码和配置摘要,用于拒绝同键不同执行配置;它不是可还原的输入归档。当前没有公共保证可取得每次发往模型的完整拼装 system/skills/工具 schema、所有服务部署响应头,或任务引用文件在执行前的原始字节。内部 transcript 也没有在此定义完整请求快照及 flush 契约。
143
+
144
+ 如果审核要求比较执行前后的业务材料,调用方需要在授权范围内保留有版本的材料与必要基线,并把明确位置交给任务。缺少基线应报告该历史主张未证实,不能当成材料被改写的证据。完整请求审计属于尚未提供的能力,不能用任务 spec 或配置哈希冒充;以上原生事件仍可用于核对已发生的调用、实际返回和模型结果。
145
+
146
+ ## 编排调用方退出后的续办
147
+
148
+ 现有持久机制保存和执行已经接受的有界任务,没有通用的持久 continuation 或多阶段流程引擎。示例的 worker 完成等待、reviewer 提交和最终输出由运行该示例的进程驱动。该进程退出时,已启动的 worker 可以继续;尚未提交的 reviewer、后续修正和业务送达不会因此自动启动。
149
+
150
+ assistant 的恢复流程应基于自己的持久阶段记录:
151
+
152
+ 1. 提交前保存原项目、阶段任务键、原 spec、执行配置版本及授权来源;回执后保存 run/session 身份。阶段记录不应只存在于宿主记忆或调用进程内存。
153
+ 2. 重启先查原键,补读并处理事件,依据原工具效果和成果决定该阶段是否满足继续条件。审核可以诊断失败的 worker,但模型运行成功不是业务通过;需要权限或效果未知时不能直接推进修正、重放或送达。
154
+ 3. 只有下一阶段尚未接受且继续条件成立时,按已保存的下一阶段键及输入提交。下一阶段提交 ACK 丢失也先按它的原键查询。修正阶段有自己的明确任务和预算,不能把相同失败任务换成子任务以无限重试。
155
+ 4. 处理事件后保存游标;处理后存游标前崩溃会再次收到同一事件,按 run/generation/sequence 或原业务键去重。外部消息送达由消费者保存实际回执,不能拿事件读取成功代替送达成功。
156
+
157
+ 同键重接不仅要求 spec 相同,还要求 `configId` 匹配。它包含规范化项目、CLI 的实际路径和代码哈希、launch、选定环境配置及定义文件摘要;例如更换安装前缀、模型路由或 MCP 配置可能造成冲突。不是所有环境变量都参与绑定,凭据值也不参与;HOME 的变化可能通过配置文件来源影响绑定,不能笼统认为任何 HOME 或任意环境变量变化都会冲突。保存 configId 可帮助核对,但不能把它作为参数强制宿主接受不同配置。
158
+
159
+ 升级后仍可先通过 get/events 查旧记录;这不要求用新配置重新 submit。需要补回排队任务上下文时,必须满足原绑定,或明确处理配置冲突;无法恢复原配置就报告阻塞,不得换键绕过未知执行。简单重新运行示例不是通用恢复器:脚本或配置版本、任务路径和 reviewer 输入改变都可能导致同键冲突。消费者负责保存并恢复原阶段输入,不应根据变动后的快照随意重建已经接受的请求。
160
+
161
+ 固定源码构建和配套 CLI/SDK 的隔离使用见 [开发指南](dev-quickstart.md)。同一版本字符串不能证明两个安装的内容相同;接入验证需记录提交及构建工件哈希。
162
+
163
+ ### Per-request execution observations
164
+
165
+ CCL adds current execution observations when assembling a model request, after filtering tool schemas. This is separate from the shared foreground/background policy and from inherited agent prompts. The observations identify the main assistant, a durable worker, or a subagent; the current permission mode; the exposed execution tools and background parameters; and two distinct receiving paths:
166
+
167
+ - The durable watcher reports polling, unavailable after a poll/queue error, or stopped. Polling is not a receipt or a promise of future delivery.
168
+ - The session-local path reports a mounted TUI consumer, the current headless turn's owned-task drain, or a subagent's addressed queue during its query. A closed stdin can stop the durable watcher while the current turn still waits for its local tasks.
169
+
170
+ A worker's output is retained under its original background run. Its child agents have their own query scope and do not inherit the foreground receiver. Auxiliary requests and missing producers report unknown; business delivery remains unknown because this observation does not introduce a consumer acknowledgment protocol. Final tool-schema exposure is not authorization, and deferred schemas still require discovery. Host retries re-read observations; no observation guarantees that a process or channel remains alive afterward. SDK `createBackgroundClient` operations that make no model request return their existing saved-state/event responses without a model prompt.
171
+
172
+
173
+ ## 周期任务的权限与结果交接(自 1.5.1-alpha.2 起)
174
+
175
+ 本节描述 1.5.1-alpha.2 的行为;更早版本不包含完整的交接与恢复协议。`CronCreate` 的后台调度与 `BackgroundRun submit` 共用 daemon,但前者按每次触发保存 **occurrence**,后者保存 **run**;两者的 ID、事件游标和审批参数不能互换。
176
+
177
+ 创建 `execution: "background"` 的 schedule 时,宿主保存创建会话的有效权限模式与可委派的 CLI/session 规则,绑定这一项任务和原会话。执行时仍加载当前持久配置与拒绝规则,不把共享 daemon 全局提权。command/skill 临时许可、单次批准和不可序列化的 SDK 回调不升级为长期许可;创建回执列出 `permissionHandoff.omittedSources`。普通 SDK 的权限回调契约不变;调度执行器使用专用的 `ask_only` 路由,自动允许的动作直接执行,真正需要决定的调用才进入 `waiting_permission`。
178
+
179
+ 创建结果区分 `background_ready`(owner 已就绪)、`saved_not_running`(定义保存但 owner 未就绪)、权限交接模式/revision 和 `notification.receiver`。就绪不保证业务完成或所有未来动作都已授权。已有任务的 `operation: "handoff"` 仅重试启动,不改变授权。用户明确要求更新授权或接收会话时,调用 `CronCreate({operation: "reauthorize", taskId})`,经现有权限检查捕获当前宿主上下文;模型不能直接填权限或伪造 source session。新 revision 只影响尚未接受的未来触发,旧 pending 保持原审批身份,新触发不会合并到旧 revision 的 pending。没有 delegation 的旧任务明确显示 `legacy/no_delegation`,不会从新会话静默猜测权限。
180
+
181
+ ### 查询、决定与取消
182
+
183
+ ```js
184
+ const background = createBackgroundClient({ cwd: project, cliPath })
185
+ const occurrences = await background.listOccurrences({ taskId })
186
+ const view = await background.getOccurrence({ occurrenceId })
187
+ if (view.status === 'found' && view.snapshot.execution === 'waiting_permission') {
188
+ const pending = view.snapshot.pendingPermission
189
+ // 先展示 pending.approval.toolName / input,并取得明确适用的决定。
190
+ // 没有可核实的 pending 身份时不能猜测或自动批准。
191
+ if (pending && explicitUserDecision) {
192
+ await background.approveOccurrence({
193
+ occurrenceId,
194
+ expectedRevision: pending.revision,
195
+ requestId: pending.approval.requestId,
196
+ toolUseId: pending.approval.toolUseId,
197
+ actionId: savedApprovalActionId,
198
+ decision: explicitUserDecision, // 'allow' 或 'deny'
199
+ })
200
+ }
201
+ }
202
+ const page = await background.readOccurrenceEvents({ occurrenceId, afterSequence: savedSequence, limit: 100 })
203
+ // 实际处理 page.events 后再保存 page.nextSequence;按事件身份去重。
204
+ ```
205
+
206
+ 批准使用 **pendingPermission.revision**,取消使用查询时的 **snapshot.revision**:`cancelOccurrence({occurrenceId, expectedRevision, actionId})`。actionId 由调用方自行生成并持久保存,get 不签发它,也不需要用户填写。相同 actionId 和相同原请求可查询原收据;超时后先读原 occurrence,不换 actionId 盲重试。冲突决定、过期 revision 或不匹配调用被拒绝。取消一轮不删除 recurring 定义;停止未来调度使用 `CronDelete`。取消不能撤回外部已发生的效果,活跃执行退出时可能报告 `interrupted`,不能把它解释为远端操作从未发生。
207
+
208
+ CLI 使用 `ccl daemon <子命令> --request-stdin --cwd PROJECT`,stdin 中为对应 SDK 请求 JSON:
209
+
210
+ | SDK | CLI 子命令 |
211
+ | --- | --- |
212
+ | `listOccurrences` | `list-occurrences` |
213
+ | `getOccurrence` | `get-occurrence` |
214
+ | `readOccurrenceEvents` | `occurrence-events` |
215
+ | `approveOccurrence` | `approve-occurrence` |
216
+ | `cancelOccurrence` | `cancel-occurrence` |
217
+
218
+ 只读查询不启动 owner,也不消费通知;决定和取消需要兼容的运行 owner。旧 `daemon approve <id> allow` 无精确身份,返回迁移错误。模型工具用 `BackgroundRun` 的 `occurrence_id` 配合 `get/events/approve/cancel`;批准字段为 `expected_revision/request_id/tool_use_id/action_id/decision`,事件使用 `after_sequence`,不用 run 的 opaque `cursor`。模型收到宿主通知不等于用户授权,allow 仍遵循前台权限确认或明确适用策略。
219
+
220
+ ### 前端体验与故障恢复
221
+
222
+ - TUI 与持续运行的 SDK/headless 主循环:原会话收到 `scheduled-task` 宿主事件,主助手解释具体待批工具、当前结果或失败。此事件不冒充新的用户指令。SDK 的自主通知结果不绑定到上一条用户输入的 turn UUID。
223
+ - 已退出的一次性 headless:不承诺向已关闭的输出流推送。消费者可通过上述 SDK/CLI 查询和处理结构化 pending;恢复原会话时补收未投递消息。Pepper 等 assistant 负责自己的外部消息渠道及送达回执。
224
+ - 接收者离线、入队失败或入队后尚未写入 transcript:保留通知游标,补投稳定 UUID。只有原会话持久保存的通知消息才算宿主接收回执;get/events 和队列操作记录都不算。已保存不代表模型已解释或 IM 已送达。
225
+ - 同一待批调用跨周期只提示一次,查询及实际消费时显示最新合并次数。投递前、队列消费前重查状态,已经批准、拒绝、取消或结束的旧审批不再提问;历史事件仍可读。状态读取失败不会冒充“已解决”。
226
+ - 等待批准时 owner 重启:保留经确认尚未执行的原调用,可在明确批准后恢复原 session。恢复严格核对工具与完整输入,不因同名工具就套用旧批准。
227
+ - 决定和恢复 intent 已保存后崩溃:重启标记 `interrupted`,不自动再执行。先核查原收据、工具结果及适用的 Operation 恢复契约;宿主不保证任意外部系统 exactly-once。
228
+
229
+ `execution: succeeded` 只说明本轮模型执行正常结束,业务结果仍需调用方验收。`delivery` 是原调度交付状态,不等于上述 session 通知游标或外部消息送达回执。实现与实测边界见 [1.5.1-alpha.2 版本说明](releases/1.5.1-alpha.2.md)。
@@ -2,7 +2,6 @@
2
2
 
3
3
  > npm 包的公开命令为 `margay`。下文带日期、提交号和本机 `~/.local/bin/ccl` 的记录属于历史验收;该本机入口不是 npm 安装保证提供的命令,也不代表当前候选已通过回归。
4
4
 
5
- > 本文描述截至 2026-09-21 已审核的候选提交 `1387fdae`。npm `next` 仍是更早的 `1.4.1-beta-duo.0`,不包含此后的修复;版本号本身不能确认本机 CLI 身份。历史验收记录见源码检出中的 `docs/ccl-duo-ux-validation.md`(不随 npm 包分发)。
6
5
 
7
6
  > 2026-09-21 的 Buddy 化简与 Duo 生命周期修复已完成代码复核和干净检出核验,仍非 npm 发版验收。RC8/RC10/C790 保持 HOLD。
8
7
 
@@ -38,7 +37,7 @@ Duo 让一个 Agent 执行任务,另一个独立检查结果。双方各有上
38
37
 
39
38
  两个角色必须解析为不同的具体模型 ID;`auto`、`default` 等选择项会在发出模型请求前解析。若没有两种可用模型或模型验证失败,原任务保留在待命状态并显示原因,可调整配置后重试。
40
39
 
41
- 2026-10-11 本地修复候选支持带引号的模型参数,例如 `/duo --reviewer-model "deepseek-v4-pro"`。网关目录明确登记别名和对应具体模型时,启动前会解析为具体模型;两种别名指向同一模型仍不能组成双模型审核。旧网关没有映射时不会猜测模型身份。若缺少可用审核 agent,按提示在 `/agents` 中启用 `code-reviewer` 或 `general-purpose` 后重试。此候选尚未发布。
40
+ 支持带引号的模型参数,例如 `/duo --reviewer-model "deepseek-v4-pro"`。网关目录明确登记别名和对应具体模型时,启动前会解析为具体模型;两种别名指向同一模型仍不能组成双模型审核。旧网关没有映射时不会猜测模型身份。若缺少可用审核 agent,按提示在 `/agents` 中启用 `code-reviewer` 或 `general-purpose` 后重试。
42
41
 
43
42
  ## 看懂角色状态
44
43
 
@@ -137,5 +136,3 @@ WS_NO_BUFFER_UTIL=1 ~/.local/bin/ccl --ide
137
136
  `/ide` 接通编辑器能力,聊天仍在 CCL 终端。Claude Code、Codex 各自有官方原生聊天扩展;使用它们的聊天面板运行 CCL,以及迁移当前 Duo 会话,均尚未验证。专用 Duo 面板、tmux 中的完整版 RLL 和 SDK 多用户服务另行处理。
138
137
 
139
138
  普通 `/buddy` 先创建或复用 Team,再按返回的实际团队名启动 Agent;`/buddy --peer-review` 仍需固定审核模型和开发目标。两条 Buddy 路径与 Duo 的完成条件不同,详见[轻量开发审查说明](peer-development-review.md)。
140
-
141
- 实现范围与历史测试合同见源码检出中的 `docs/ccl-duo-ux-tdd-plan.md` 和 `docs/ccl-duo-ux-validation.md`(不随 npm 包分发)。当前已审核候选通过 4,081 项源码测试、19 项跳过、102 项 Duo 定向测试、类型检查、36 项构建门禁与 5 项 smoke;隔离安装包的有头终端场景分别通过完整审核与接受 15/15 步、下一任务单模型答复未完成负例 18/18 步。它们使用确定性 provider,不代表真实模型判断质量或发版通过。
@@ -1,75 +1,12 @@
1
1
  # 在 VS Code 中使用 CCL 与 Duo
2
2
 
3
- 更新:2026-09-17。本机安装构建:`7a75f333`(版本显示 `1.4.0-beta.0`)。当前提供终端界面与 IDE 连接;尚未验证用原生聊天面板运行 CCL/Duo。
3
+ CCL 的 `/ide` 连接编辑器服务,聊天仍在终端中进行。原生聊天面板、Trae 和跨客户端会话迁移需要分别验证。
4
4
 
5
- ## 当前验证到了哪里
5
+ 1. 在 VS Code 打开与 CCL 相同的项目。
6
+ 2. 在集成终端运行 `ccl --ide`,或在已有 CCL 会话执行 `/ide` 并选择编辑器。
7
+ 3. 如果该编辑器已被另一个 CCL 占用,先在原会话的 `/ide` 菜单断开连接。
8
+ 4. 需要 Duo 时,在同一终端会话执行 `/duo-agent`。模型与审核流程见 [Duo 指南](ccl-duo-agent.md)。
6
9
 
7
- | 路径 | 状态 |
8
- |---|---|
9
- | 已安装 CCL 的终端 Duo | 三项 wezterm-gui 有头场景通过,详见[验收记录](ccl-duo-ux-validation.md) |
10
- | CCL → VS Code 编辑器连接 | 加临时环境变量后,已验证握手、服务能力及实际 TCP 连接 |
11
- | 默认 CCL → VS Code 连接 | 已复现 `bufferUtil.mask is not a function`,尚未修复 |
12
- | 在 VS Code 集成终端启动同一 CCL | 可按下文操作;本轮实际连接客户端运行在外部 WezTerm,未独立完成集成终端中的 Duo 全流程验收 |
13
- | Trae 连接 | 未实测,不由 VS Code 结果推定兼容 |
14
- | CCL/Duo 原生聊天面板、终端会话迁移 | 尚未验证,不承诺直接切换或保留正在运行的 Duo 状态 |
10
+ 编辑器服务与 CLI 必须使用一致的配置目录。CCL 默认发现 `~/.ccl/ide` 下的服务;使用官方扩展时,按扩展支持的配置方式让其使用相同目录。自定义 `CCL_CONFIG_DIR` 后也应保持一致,不要复制锁文件或凭据来拼接连接。
15
11
 
16
- ## 在已打开的测试 VS Code 窗口使用
17
-
18
- 1. 打开与 CCL 相同的项目目录。
19
- 2. 如果另一个 CCL 已连接该 VS Code,先在那个 CCL 的 `/ide` 菜单选择 `None`,或退出该 CCL。当前界面提示同一 VS Code 一次连接一个 CCL。
20
- 3. 在 VS Code 菜单中选择 **终端 → 新建终端**,运行:
21
-
22
- ```sh
23
- WS_NO_BUFFER_UTIL=1 ~/.local/bin/ccl --ide
24
- ```
25
-
26
- 4. 若需要手动选择连接,在 CCL 内执行 `/ide` 并选中 Visual Studio Code。自动连接设置可按个人偏好选择。
27
- 5. 在同一 CCL 界面进入 Duo:
28
-
29
- ```text
30
- /model
31
- /duo-agent
32
- /duo --reviewer-model <固定模型ID>
33
- ```
34
-
35
- `/model` 选择执行模型,最后一行配置审核模型,然后直接输入任务。双方可用不同模型。暂停、恢复及反审规则见 [Duo 使用说明](ccl-duo-agent.md)。
36
-
37
- `WS_NO_BUFFER_UTIL=1` 让本次进程使用 WebSocket 的 JavaScript 实现,绕过已复现的可选原生模块错误。它不会永久修改环境或安装包;Gateway 不需要升级。
38
-
39
- ## 扩展与配置目录
40
-
41
- 本次连接使用官方 `anthropic.claude-code` 扩展 2.1.274 的 IDE 服务。CCL 默认在 `~/.ccl/ide` 发现服务,扩展需要随 IDE 启动时取得 `CLAUDE_CONFIG_DIR=~/.ccl`。如果自定义了 `CCL_CONFIG_DIR`,应使用那个实际目录。不要通过复制锁文件、token 或修改原始会话来拼接连接。
42
-
43
- 为保留日常 VS Code 配置,本次测试使用独立目录:
44
-
45
- - IDE 用户数据:`~/.local/share/ccl/ide-vscode-session/user-data`
46
- - 扩展:`~/.local/share/ccl/ide-vscode-session/extensions`
47
-
48
- 这两个目录中已完成扩展安装。重新打开同一个测试配置时,先进入项目目录,再执行:
49
-
50
- ```sh
51
- CLAUDE_CONFIG_DIR="$HOME/.ccl" CCL_CONFIG_DIR="$HOME/.ccl" \
52
- "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code" \
53
- --new-window \
54
- --user-data-dir "$HOME/.local/share/ccl/ide-vscode-session/user-data" \
55
- --extensions-dir "$HOME/.local/share/ccl/ide-vscode-session/extensions" \
56
- "$PWD"
57
- ```
58
-
59
- 已运行的 IDE 进程可能沿用原环境;发现服务目录不一致时,先保存文件并关闭这个测试配置的窗口,再用上面的命令启动。无需关闭其他日常 VS Code 窗口。
60
-
61
- ## 终端聊天与原生聊天面板
62
-
63
- `/ide` 接通编辑器服务,不会把终端聊天界面转换成图形聊天面板。
64
-
65
- - **Claude Code 官方扩展**有原生聊天面板,并与官方 Claude CLI 共享会话历史。这是官方 Claude Code 的能力,不能直接推定 CCL 的自定义 Duo 状态同样兼容。[官方文档](https://code.claude.com/docs/en/vs-code)
66
- - **Codex 官方扩展**有侧边栏聊天、文件/选区上下文和修改审查界面。[OpenAI 官方文档](https://developers.openai.com/codex/ide/)
67
- - 本机 Claude 扩展提供 `claudeCode.claudeProcessWrapper` 启动入口设置,但它是否足以接入 CCL、多模型与 Duo 的输入/暂停/挑战/恢复,需要单独验证。当前没有将原生面板后端切换为 CCL。
68
-
69
- 后续可先验证复用已有面板的可行性,再判断是否需要专用扩展;这不是已交付功能。当前也没有把 CCL 会话复制进官方 Claude 或 Codex 的历史记录。
70
-
71
- ## 证据与未完成项
72
-
73
- 原 RLL 工作目录的 `.dual-agent/test-evidence/ccl-ide-manual-connection/` 保存默认失败和临时处理后的成功日志。成功握手耗时 25ms,服务为 `Claude Code VSCode MCP` 2.1.274,返回 `hasTools=true`;未逐个执行 IDE 工具或发送真实模型任务。
74
-
75
- 待处理:默认 WebSocket `bufferutil` 兼容问题的产品修复、集成终端实际 Duo 流程验收、现有原生面板接 CCL 的可行性验证。Gateway/Margay 未因本次连接测试修改。
12
+ 已启动的编辑器可能仍使用旧环境;保存工作后重启相应编辑器窗口,再检查 `/ide` 状态。连接编辑器不等于迁移聊天历史或 Duo 状态。
@@ -81,6 +81,43 @@ disabled; this verifies CLI installation and startup, not native dependency or
81
81
  model behavior. It currently runs on POSIX; Windows `.cmd`/PowerShell launchers
82
82
  require a Windows run. It cleans its temporary installations even on failure.
83
83
 
84
+ ## Isolating credentials on macOS
85
+
86
+ Set an absolute, private configuration directory **before starting** each process:
87
+
88
+ ```sh
89
+ CCL_CONFIG_DIR=/absolute/path/to/isolated-config margay
90
+ ```
91
+
92
+ `CCL_CONFIG_DIR` takes precedence over the compatible `CLAUDE_CONFIG_DIR` variable.
93
+ Use different directories for independent accounts; the same directory intentionally
94
+ shares credentials and OAuth refresh locks. Set a separate `HOME` and temporary
95
+ directory too when testing, since other programs may store files outside CCL's config.
96
+ Do not change these variables inside an already running process: its caches and
97
+ background work remain bound to its startup configuration.
98
+
99
+ macOS Keychain belongs to the OS user, so changing `HOME` alone does not create a
100
+ separate Keychain. Before the #46 fix, CCL's underlying OAuth/legacy-key naming
101
+ helper used a shared service name whenever both config variables were absent,
102
+ even if `HOME` differed. The packaged CLI already supplies a default
103
+ `CLAUDE_CONFIG_DIR` at startup and did not reproduce that collision in the isolated
104
+ CLI test; callers bypassing that startup wrapper must not rely on that protection.
105
+
106
+ With the #46 fix, the underlying helper scopes an overridden HOME's config path
107
+ the same way as an explicit directory. Existing explicit-directory names remain
108
+ unchanged. The unscoped legacy service is preserved only for an implicit config
109
+ under the OS account's ordinary home; the CLI's existing scoped default is also
110
+ unchanged. If the OS home cannot be determined, the helper uses a scoped name.
111
+ It never copies or clears a shared credential to populate a new isolated scope.
112
+ An environment that previously relied on the accidental shared credential may
113
+ need to authenticate separately in its own directory.
114
+
115
+ This describes the local #46 source change; use a candidate/release containing
116
+ that change before relying on the new HOME-only behavior. The Margay website
117
+ account vault uses its own configuration-scoped references, and `margay auth logout`
118
+ targets that website account, not the old OAuth Keychain. These tests use an
119
+ injected fake vault and OAuth receiver; they do not log out a real account.
120
+
84
121
  Use the final tarball as the test input. A metadata edit after publishing rc.9
85
122
  does not change rc.9 on npm; allocate a new version, rebuild, pack and verify
86
123
  before publishing the corrected release.
@@ -0,0 +1,62 @@
1
+ # 【公开】自定义命令执行与回执查询(SDK)
2
+
3
+ `createCommandAdapter` 供 assistant/宿主把固定 CLI 包装成可核对结果的工具。它通过 `@margay/ccl-core/sdk` 导出。模型提供业务 input;可信宿主提供 executable、参数、环境、目标身份和权限回调。不要让模型替换这些配置。
4
+
5
+ ```ts
6
+ import { createCommandAdapter } from '@margay/ccl-core/sdk'
7
+
8
+ const adapter = createCommandAdapter({
9
+ adapterId: 'orders', adapterVersion: '1', targetIdentity: 'tenant-42',
10
+ execute: { cmd: '/opt/orders-wrapper', args: ['execute'], timeoutMs: 30_000 },
11
+ query: { cmd: '/opt/orders-wrapper', args: ['query'], timeoutMs: 5_000 },
12
+ })
13
+ const operation = adapter.prepare({ orderId: 'example' }, { requestKey: 'host-action-123' })
14
+ const execution = await adapter.execute(operation, { signal })
15
+ // Keep operation.operationId / requestKey. Unknown does not mean that nothing happened.
16
+ const recovered = await adapter.queryOutcome(operation, { signal })
17
+ ```
18
+
19
+ `createCommandAdapter` 本身是一套活进程适配接口。同一个实例会把重复 requestKey 关联到原 operation,同键不同输入拒绝。输入和操作绑定是冻结的快照;同一 operation 多次 execute 返回原执行,不会重新派发。跨进程恢复使用下述 `createOperationHost` 和 `createDurableCommandAdapter`,不能靠活进程示例的内存 map 实现。这些接口适用于 `1.5.1-alpha`;持久恢复实现已完成限定范围的独立代码审核。独立后台执行入口及其已知边界见 [BackgroundRun](background-runs.md);不能据此声称 Pepper 已完成新接口接入。
20
+
21
+ 包装命令从 stdin 读取一个 JSON 对象,字段包含 schemaVersion=1、operationId、requestKey、adapterId、adapterVersion、targetIdentity、bindingDigest、inputDigest、attemptId、createdAt 和 input。stdout 必须是一个 JSON 回执;日志写 stderr。响应必须原样带回 schemaVersion 以及 operationId/requestKey/adapterId/adapterVersion/targetIdentity/bindingDigest/inputDigest,另附:
22
+
23
+ - `status: "applied", receipt: <非 null 的 JSON>, result?: <JSON>`:适配器确认效果。它不代表用户业务目标已验收。
24
+ - `status: "in_progress", handle: "原外部任务句柄"`:继续查询原任务;不要重新派发。
25
+ - `status: "unknown", reason: "原因"`:目前无法确认效果。查询无记录不能证明旧请求不会晚到。
26
+
27
+ 非零退出、超时、取消、输出截断、错误绑定、无回执和泛化的 `not_applied` 都不会成为 applied。没有配置 query 时返回 unsupported,process=null,表示没有启动查询进程。查询命令的只读保证和回执真实性由可信适配器维护者承担;CCL 校验关联信息,不能仅凭命令自称无副作用就证明它安全。
28
+
29
+ 显式 `retry(operation, { signal, authorize })` 还要求配置 `idempotency: { scope, retentionMs }` 和 `verifyIdentity(operation): Promise<boolean>`。前者必须来自外部服务真实的去重契约;后者检查真实账户/目标/版本,不能只重复比较配置字符串。retry 在权限确认后核对身份、环境绑定、有效期,复用原 requestKey,拒绝并发恢复。查询已经查回 applied 时返回该回执,不再执行写入。凭据只留在宿主配置/进程环境中,operation 使用绑定摘要;环境变化会拒绝继续使用旧绑定,不能静默切换身份。
30
+
31
+ `runCommandProcess`(也经 SDK 导出)提供一次不经 shell 的进程调用:真实 stdin、stdout/stderr、exit_code、启动/取消/超时事实。超时和取消只终止本次拥有的本地进程组;SIGTERM 后升级 SIGKILL。原始进程层只能说明 not_dispatched 或 unknown,不能证明远程业务已取消。同步调用不允许通过遗留后代进程冒充持久后台任务。
32
+
33
+ 源码仓库中的 `examples/command-recovery/server.ts`(不随 npm 包分发)演示通过 `createSdkMcpServer/tool` 注册 execute/query,真实 MCP 的 `extra.signal` 传给适配器。随包的 `receipt-command.mjs` 用本地文件演示效果已保存但回执丢失后的查询;它使用独占目录防重复写入。中途只留下目录、没有完整回执时保持 unknown,不自动覆盖重做。示例不是生产事务服务或持久任务管理器;随包的持久示例是下述 `durable-server.mjs`。
34
+
35
+ ## 操作级持久恢复(C3)
36
+
37
+ `createOperationHost({ storeDir, scope, resolveAdapter, authorize })` 使用宿主指定的私有目录和稳定工作区作用域保存记录。`resolveAdapter` 只能解析可信注册;`authorize` 必须检查当前调用者权限。`createDurableCommandAdapter` 把固定命令及回执查询接入这个宿主。它要求真实 `verifyIdentity` 返回当前目标身份与适配器版本,默认不继承整个进程环境。
38
+
39
+ `submitOperation` 只持久接受;`executeOperation` 才执行。未知结果后使用 `reconcileOperation` 查询原效果,或在原服务有实际幂等约定时显式 `resumeOperation`。`getOperation` 和 `readOperationEvents` 只读已提交记录;无需连接原服务便可读取已保存结果,但仍检查当前读取权限。写恢复携带最近的 `expectedRevision` 和唯一 `actionId`,冲突后先读原操作,不换 requestKey 重建任务。
40
+
41
+ `examples/command-recovery/durable-server.mjs` 是可重建的 SDK MCP 示例:宿主传入同一个存储目录和当前权限回调,每次启动重新建立实例;execute/get/query 共用操作记录。示例读取真实本地目录身份,不能把它的本地身份查询照搬成远端账户认证。它保留操作 ID、原输入和回执,不用内存 map 作为权威记录。旧 `server.ts` 仍演示基础活进程接口。
42
+
43
+ ```js
44
+ import { durableCommandRecoveryServer } from './examples/command-recovery/durable-server.mjs'
45
+
46
+ const server = durableCommandRecoveryServer('/private/assistant/command-state',
47
+ request => checkCurrentPermission(request))
48
+ // Register server with the existing SDK MCP host. On restart, rebuild it with
49
+ // the same directory and a fresh permission callback, then use get/query.
50
+ ```
51
+
52
+ CLI `ccl operations get/events/reconcile/resume/cancel` 和模型侧 `Operation` 工具使用同一个核心。CLI 的恢复动作沿当前原工具权限;非交互请求需要批准时返回 waiting_permission。MCP 注册与原任务句柄映射见 [MCP 恢复说明](mcp-tool-recovery.md)。恢复会话会补回原协议结果,或明确标为 `verified_operation_recovery` 的已核验结果;不会自动重新派发未知写操作。
53
+
54
+ assistant 要把一整段耗时工作交给独立执行者时,使用 [持久后台入口](background-runs.md),而不是把 `executeOperation` 的未知结果包装成一个新业务任务。后台原 run 的 `operationReferences` 指向这些原操作;恢复时先读取当前 revision,再按契约查询/请求恢复。run 的事件游标与 operation 的事件游标是不同协议,不能互换;此处的 `operations events` 不因此获得 `BackgroundRun` 的 `limit/hasMore` 接口。前台结束不要求立即重放,原执行者可能仍在运行;若它已退出,也只能依据真实回执和服务幂等契约继续。
55
+
56
+ ## 开发验证
57
+
58
+ 普通 `npm test` 不包含依赖构建产物的进程用例。先在隔离快照构建 CLI/SDK,再用 `npm run test:operations` 显式执行;设置 `CCL_TEST_COMPILED_CLI` 指向该快照 `dist/cli.js`,`CCL_TEST_COMPILED_SDK` 指向 `dist/sdk`。快照须包含 `examples/command-recovery`。未提供构建路径时明确失败,不静默跳过。
59
+
60
+ 运行时使用该次测试独立的 HOME、CCL_CONFIG_DIR/CLAUDE_CONFIG_DIR、TMPDIR,去掉 RL_/TMUX/WEZTERM 等会话指向及真实网关凭据;用例只创建临时目录、回环端口和自身子进程,结束后清理。不要指向用户的配置或全局安装。
61
+
62
+ 已执行的 macOS 故障测试包括真实 SIGKILL/SIGSTOP、回执丢失后的跨进程查询和严格只读 hot journal 路径。SIGKILL 不等于断电保证;Windows 的进程树终止与存储恢复未实测。后台生命周期的五类崩溃窗口已另行验证,使用边界见 [后台任务说明](background-runs.md);Pepper 采用新后台接口的真实业务案例仍未验收。
@@ -1,14 +1,23 @@
1
1
  # CCL Developer Quick Start
2
2
 
3
- This guide is for developers validating a source checkout of CCL `1.4.5-rc.1`. It is
3
+ This guide is for developers validating a source checkout of CCL. It is
4
4
  not a place to store shared credentials or production gateway details.
5
5
 
6
- ## 1. Install Dependencies
6
+ ## 1. Select a Checkout and Install Dependencies
7
+
8
+ Use Node.js **22.19.0 or newer** and Bun on PATH. The build script uses Bun;
9
+ Node runs the resulting CLI and SDK. For a fixed background-recovery baseline,
10
+ use commit `1890bf74c31372073b0cbdbcbdb59870a532989d` (package version
11
+ `1.5.1-alpha`). This source reference does not imply that version is published
12
+ on npm. Clone into a separate directory for validation:
7
13
 
8
14
  ```bash
9
- git clone https://github.com/YW1975/ccl.git
10
- cd ccl
11
- npm install
15
+ git clone https://github.com/YW1975/ccl.git ccl-validation
16
+ cd ccl-validation
17
+ git checkout --detach 1890bf74c31372073b0cbdbcbdb59870a532989d
18
+ node --version
19
+ bun --version
20
+ npm ci
12
21
  ```
13
22
 
14
23
  ## 2. Build And Smoke Check
@@ -19,12 +28,25 @@ node dist/cli.js --version
19
28
  node dist/cli.js --help
20
29
  ```
21
30
 
22
- Expected version output:
31
+ The CLI version must match the selected checkout's `package.json`:
23
32
 
24
33
  ```text
25
- 1.4.5-rc.1 (Margay)
34
+ 1.5.1-alpha (Margay)
26
35
  ```
27
36
 
37
+ Keep CLI and SDK from this same build: import `dist/sdk/index.js` and pass
38
+ the absolute `dist/cli.js` path as the SDK client's `cliPath`. Do not pair this
39
+ SDK with an older global CLI merely because both commands start successfully.
40
+ Record the commit and SHA-256 of the CLI and SDK files used for verification.
41
+ An isolated checkout build does not update a global installation or running session.
42
+
43
+ For runtime validation, use a task-owned project, HOME, config and temporary
44
+ directories, with only the credentials and services authorized for that test.
45
+ Do not point a crash/restart test at the daemon or project used by a working
46
+ assistant. Reuse the original project and task identities for recovery within
47
+ that test. See [background runs](background-runs.md) for acceptance, events,
48
+ configuration conflicts and the caller's responsibility for later stages.
49
+
28
50
  ## 3. Optional Gateway Setup
29
51
 
30
52
  Start the built CLI, then configure through its TUI: