@dobby.ai/dobby 0.1.1 → 0.1.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 (136) hide show
  1. package/README.md +20 -7
  2. package/dist/src/agent/event-forwarder.js +185 -16
  3. package/dist/src/cli/commands/cron.js +39 -35
  4. package/dist/src/cli/program.js +0 -6
  5. package/dist/src/core/types.js +2 -0
  6. package/dist/src/cron/config.js +2 -2
  7. package/dist/src/cron/service.js +87 -23
  8. package/dist/src/cron/store.js +1 -1
  9. package/package.json +9 -3
  10. package/.env.example +0 -8
  11. package/AGENTS.md +0 -267
  12. package/ROADMAP.md +0 -34
  13. package/config/cron.example.json +0 -9
  14. package/config/gateway.example.json +0 -132
  15. package/dist/plugins/connector-discord/src/mapper.js +0 -75
  16. package/dist/src/cli/tests/config-command.test.js +0 -42
  17. package/dist/src/cli/tests/config-io.test.js +0 -64
  18. package/dist/src/cli/tests/config-mutators.test.js +0 -47
  19. package/dist/src/cli/tests/discord-mapper.test.js +0 -90
  20. package/dist/src/cli/tests/doctor.test.js +0 -252
  21. package/dist/src/cli/tests/init-catalog.test.js +0 -134
  22. package/dist/src/cli/tests/program-options.test.js +0 -78
  23. package/dist/src/cli/tests/routing-config.test.js +0 -254
  24. package/dist/src/core/tests/control-command.test.js +0 -17
  25. package/dist/src/core/tests/runtime-registry.test.js +0 -116
  26. package/dist/src/core/tests/typing-controller.test.js +0 -103
  27. package/docs/BOXLITE_SANDBOX_FEASIBILITY.md +0 -175
  28. package/docs/CRON_SCHEDULER_DESIGN.md +0 -374
  29. package/docs/DOCKER_SANDBOX_vs_BOXLITE.md +0 -77
  30. package/docs/EXTENSION_SYSTEM_ARCHITECTURE.md +0 -119
  31. package/docs/MVP.md +0 -135
  32. package/docs/RUNBOOK.md +0 -243
  33. package/docs/TEAMWORK_HANDOFF_DESIGN.md +0 -440
  34. package/plugins/connector-discord/dobby.manifest.json +0 -18
  35. package/plugins/connector-discord/index.js +0 -1
  36. package/plugins/connector-discord/package-lock.json +0 -360
  37. package/plugins/connector-discord/package.json +0 -38
  38. package/plugins/connector-discord/src/connector.ts +0 -345
  39. package/plugins/connector-discord/src/contribution.ts +0 -21
  40. package/plugins/connector-discord/src/mapper.ts +0 -101
  41. package/plugins/connector-discord/tsconfig.json +0 -19
  42. package/plugins/connector-feishu/dobby.manifest.json +0 -18
  43. package/plugins/connector-feishu/index.js +0 -1
  44. package/plugins/connector-feishu/package-lock.json +0 -618
  45. package/plugins/connector-feishu/package.json +0 -38
  46. package/plugins/connector-feishu/src/connector.ts +0 -343
  47. package/plugins/connector-feishu/src/contribution.ts +0 -26
  48. package/plugins/connector-feishu/src/mapper.ts +0 -401
  49. package/plugins/connector-feishu/tsconfig.json +0 -19
  50. package/plugins/plugin-sdk/index.d.ts +0 -261
  51. package/plugins/plugin-sdk/index.js +0 -1
  52. package/plugins/plugin-sdk/package-lock.json +0 -12
  53. package/plugins/plugin-sdk/package.json +0 -22
  54. package/plugins/provider-claude/dobby.manifest.json +0 -17
  55. package/plugins/provider-claude/index.js +0 -1
  56. package/plugins/provider-claude/package-lock.json +0 -3398
  57. package/plugins/provider-claude/package.json +0 -39
  58. package/plugins/provider-claude/src/contribution.ts +0 -1018
  59. package/plugins/provider-claude/tsconfig.json +0 -19
  60. package/plugins/provider-claude-cli/dobby.manifest.json +0 -17
  61. package/plugins/provider-claude-cli/index.js +0 -1
  62. package/plugins/provider-claude-cli/package-lock.json +0 -2898
  63. package/plugins/provider-claude-cli/package.json +0 -38
  64. package/plugins/provider-claude-cli/src/contribution.ts +0 -1673
  65. package/plugins/provider-claude-cli/tsconfig.json +0 -19
  66. package/plugins/provider-pi/dobby.manifest.json +0 -17
  67. package/plugins/provider-pi/index.js +0 -1
  68. package/plugins/provider-pi/package-lock.json +0 -3877
  69. package/plugins/provider-pi/package.json +0 -40
  70. package/plugins/provider-pi/src/contribution.ts +0 -606
  71. package/plugins/provider-pi/tsconfig.json +0 -19
  72. package/plugins/sandbox-core/boxlite.js +0 -1
  73. package/plugins/sandbox-core/dobby.manifest.json +0 -17
  74. package/plugins/sandbox-core/docker.js +0 -1
  75. package/plugins/sandbox-core/package-lock.json +0 -136
  76. package/plugins/sandbox-core/package.json +0 -39
  77. package/plugins/sandbox-core/src/boxlite-context.ts +0 -2
  78. package/plugins/sandbox-core/src/boxlite-contribution.ts +0 -53
  79. package/plugins/sandbox-core/src/boxlite-executor.ts +0 -911
  80. package/plugins/sandbox-core/src/docker-contribution.ts +0 -43
  81. package/plugins/sandbox-core/src/docker-executor.ts +0 -217
  82. package/plugins/sandbox-core/tsconfig.json +0 -19
  83. package/scripts/local-extensions.mjs +0 -168
  84. package/src/agent/event-forwarder.ts +0 -414
  85. package/src/cli/commands/config.ts +0 -328
  86. package/src/cli/commands/configure.ts +0 -92
  87. package/src/cli/commands/cron.ts +0 -410
  88. package/src/cli/commands/doctor.ts +0 -331
  89. package/src/cli/commands/extension.ts +0 -207
  90. package/src/cli/commands/init.ts +0 -211
  91. package/src/cli/commands/start.ts +0 -223
  92. package/src/cli/commands/topology.ts +0 -415
  93. package/src/cli/index.ts +0 -9
  94. package/src/cli/program.ts +0 -314
  95. package/src/cli/shared/config-io.ts +0 -245
  96. package/src/cli/shared/config-mutators.ts +0 -470
  97. package/src/cli/shared/config-schema.ts +0 -228
  98. package/src/cli/shared/config-types.ts +0 -129
  99. package/src/cli/shared/configure-sections.ts +0 -595
  100. package/src/cli/shared/discord-config.ts +0 -14
  101. package/src/cli/shared/init-catalog.ts +0 -249
  102. package/src/cli/shared/local-extension-specs.ts +0 -108
  103. package/src/cli/shared/runtime.ts +0 -33
  104. package/src/cli/shared/schema-prompts.ts +0 -443
  105. package/src/cli/tests/config-command.test.ts +0 -56
  106. package/src/cli/tests/config-io.test.ts +0 -92
  107. package/src/cli/tests/config-mutators.test.ts +0 -59
  108. package/src/cli/tests/discord-mapper.test.ts +0 -128
  109. package/src/cli/tests/doctor.test.ts +0 -269
  110. package/src/cli/tests/init-catalog.test.ts +0 -144
  111. package/src/cli/tests/program-options.test.ts +0 -95
  112. package/src/cli/tests/routing-config.test.ts +0 -281
  113. package/src/core/control-command.ts +0 -12
  114. package/src/core/dedup-store.ts +0 -103
  115. package/src/core/gateway.ts +0 -609
  116. package/src/core/routing.ts +0 -404
  117. package/src/core/runtime-registry.ts +0 -141
  118. package/src/core/tests/control-command.test.ts +0 -20
  119. package/src/core/tests/runtime-registry.test.ts +0 -140
  120. package/src/core/tests/typing-controller.test.ts +0 -129
  121. package/src/core/types.ts +0 -324
  122. package/src/core/typing-controller.ts +0 -119
  123. package/src/cron/config.ts +0 -154
  124. package/src/cron/schedule.ts +0 -61
  125. package/src/cron/service.ts +0 -249
  126. package/src/cron/store.ts +0 -155
  127. package/src/cron/types.ts +0 -60
  128. package/src/extension/loader.ts +0 -145
  129. package/src/extension/manager.ts +0 -355
  130. package/src/extension/manifest.ts +0 -26
  131. package/src/extension/registry.ts +0 -229
  132. package/src/main.ts +0 -8
  133. package/src/sandbox/executor.ts +0 -44
  134. package/src/sandbox/host-executor.ts +0 -118
  135. package/src/shared/dobby-repo.ts +0 -48
  136. package/tsconfig.json +0 -18
@@ -1,103 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { setTimeout as delay } from "node:timers/promises";
3
- import test from "node:test";
4
- import { createTypingKeepAliveController } from "../typing-controller.js";
5
- function createLogger() {
6
- return {
7
- error() { },
8
- warn() { },
9
- info() { },
10
- debug() { },
11
- };
12
- }
13
- function createInbound() {
14
- return {
15
- connectorId: "discord.main",
16
- platform: "discord",
17
- accountId: "discord.main",
18
- source: {
19
- type: "channel",
20
- id: "123",
21
- },
22
- chatId: "123",
23
- messageId: "m-1",
24
- userId: "u-1",
25
- userName: "tester",
26
- text: "hello",
27
- attachments: [],
28
- timestampMs: Date.now(),
29
- raw: {},
30
- isDirectMessage: false,
31
- mentionedBot: true,
32
- };
33
- }
34
- function createConnector(sentTyping) {
35
- return {
36
- id: "discord.main",
37
- platform: "discord",
38
- name: "discord",
39
- capabilities: {
40
- updateStrategy: "edit",
41
- supportedSources: ["channel"],
42
- supportsThread: true,
43
- supportsTyping: true,
44
- supportsFileUpload: true,
45
- maxTextLength: 2000,
46
- },
47
- async start() { },
48
- async send() {
49
- return { messageId: "reply-1" };
50
- },
51
- async sendTyping() {
52
- sentTyping.push(Date.now());
53
- },
54
- async stop() { },
55
- };
56
- }
57
- test("typing is sent before the first visible output when response is slow", async () => {
58
- const sentTyping = [];
59
- const controller = createTypingKeepAliveController(createConnector(sentTyping), createInbound(), createLogger(), {
60
- initialDelayMs: 0,
61
- keepaliveIntervalMs: 200,
62
- });
63
- try {
64
- await controller.prime();
65
- assert.equal(sentTyping.length, 1);
66
- }
67
- finally {
68
- controller.stop();
69
- }
70
- });
71
- test("visible output before prime suppresses typing", async () => {
72
- const sentTyping = [];
73
- const controller = createTypingKeepAliveController(createConnector(sentTyping), createInbound(), createLogger(), {
74
- initialDelayMs: 0,
75
- keepaliveIntervalMs: 200,
76
- });
77
- try {
78
- controller.markVisibleOutput();
79
- await controller.prime();
80
- assert.deepEqual(sentTyping, []);
81
- }
82
- finally {
83
- controller.stop();
84
- }
85
- });
86
- test("visible output stops further typing keepalive", async () => {
87
- const sentTyping = [];
88
- const controller = createTypingKeepAliveController(createConnector(sentTyping), createInbound(), createLogger(), {
89
- initialDelayMs: 0,
90
- keepaliveIntervalMs: 15,
91
- });
92
- try {
93
- await controller.prime();
94
- const beforeVisibleOutput = sentTyping.length;
95
- assert.equal(beforeVisibleOutput > 0, true);
96
- controller.markVisibleOutput();
97
- await delay(30);
98
- assert.equal(sentTyping.length, beforeVisibleOutput);
99
- }
100
- finally {
101
- controller.stop();
102
- }
103
- });
@@ -1,175 +0,0 @@
1
- # BoxLite 作为 Sandbox 的可行性评估(dobby)
2
-
3
- 更新时间:2026-02-17
4
- 适用版本:`dobby` 当前 `main`、`@boxlite-ai/boxlite@0.2.11`
5
-
6
- ## 1. 评估目标
7
-
8
- 评估 `BoxLite` 是否可作为本项目当前 `sandbox` 后端(替代/补充 `host` 与 `docker`),并给出可执行的落地建议与风险边界。
9
-
10
- 本项目当前相关代码入口:
11
-
12
- - `src/sandbox/executor.ts`:定义统一 `Executor` 接口,当前仅实现 `host`/`docker`,`boxlite` 分支抛错。
13
- - `src/core/types.ts`:`SandboxConfig` 已包含 `backend: "boxlite"`。
14
- - `src/core/routing.ts`:配置 schema 已支持 `sandbox.backend = "boxlite"` 和 `boxlite.workspaceRoot`。
15
- - `src/agent/session-factory.ts`:工具调用统一走 `executor.exec(command, cwd, { timeoutSeconds, signal, env })`。
16
-
17
- ## 2. 结论摘要
18
-
19
- ### 2.1 总结论
20
-
21
- `BoxLite` 作为本项目 sandbox **可行**,建议先走“本地嵌入式 BoxLite”路线,不建议当前阶段优先接入云端 REST/BoxRun 形态。
22
-
23
- ### 2.2 量化判断
24
-
25
- - 本地嵌入式 BoxLite 可行度:`高(7.5/10)`
26
- - 云端 REST/BoxRun API 可行度:`中(4.5/10)`
27
-
28
- ### 2.3 关键判断依据
29
-
30
- - 架构对齐:本项目已预留 `boxlite` 配置分支,改造点集中在 `Executor` 实现层。
31
- - 本机实测:在 macOS ARM64 环境中可成功执行 BoxLite 命令。
32
- - 风险可控:主要风险集中在 Node 绑定中断语义与安装稳定性,可通过工程策略绕开。
33
-
34
- ## 3. 能力对齐分析
35
-
36
- ### 3.1 本项目 `Executor` 需要的能力
37
-
38
- 统一接口需求(来自 `src/sandbox/executor.ts`):
39
-
40
- - 输入:`command`, `cwd`, `timeoutSeconds`, `signal`, `env`
41
- - 输出:`stdout`, `stderr`, `code`, `killed`
42
-
43
- 运行语义要求(由 `src/agent/session-factory.ts` 触发):
44
-
45
- - 可在指定工作目录执行 shell 命令
46
- - 支持超时与中断
47
- - 支持环境变量注入
48
- - 输出可回传给上层工具调用
49
-
50
- ### 3.2 BoxLite 可提供的能力
51
-
52
- 从 BoxLite Node SDK/源码可确认:
53
-
54
- - 支持创建隔离 box(micro-VM + OCI image)
55
- - 支持执行命令、获取 stdout/stderr、返回退出码
56
- - 支持 box 级 `workingDir`、`env`、`volumes`、资源限制
57
- - 支持 execution 级 `kill()`(但见风险项)
58
- - 支持 box 级 `stop()`(可用于强制回收)
59
-
60
- 结论:功能面可以覆盖 `Executor` 主需求。
61
-
62
- ## 4. 本机 PoC 实测结果(关键证据)
63
-
64
- 测试环境:
65
-
66
- - OS:macOS 26.3, ARM64
67
- - Docker:`28.5.2`(daemon 可访问)
68
- - Node:`v22.16.0`
69
-
70
- ### 4.1 基础执行可用
71
-
72
- 在临时目录安装并执行:
73
-
74
- - `SimpleBox(image=alpine:latest)` 执行 `echo boxlite-ok` 成功
75
- - 返回:`exitCode=0`, `stdout=boxlite-ok`
76
-
77
- ### 4.2 性能观察
78
-
79
- 同一 box 内连续两条命令:
80
-
81
- - 第一条:约 `172ms`
82
- - 第二条:约 `1ms`
83
-
84
- 说明:复用同一 box 时,命令执行开销很低;启动成本主要在 box 首次创建阶段。
85
-
86
- ### 4.3 中断语义风险(重要)
87
-
88
- 在 Node 绑定实测中,出现以下行为:
89
-
90
- - `execution.wait()` 进行中调用 `execution.kill()`,多次出现 `Failed to send signal`,长命令未按预期被 kill。
91
- - 改用 `box.stop()` 可以中断正在执行的长命令(测试中约 1.4s 退出,`exitCode=-1`)。
92
-
93
- 结论:当前版本下应优先采用“box 级停止/重建”实现超时与 abort,不能假设 `execution.kill()` 始终可用。
94
-
95
- ### 4.4 安装稳定性风险
96
-
97
- 发现 npm optional dependency 场景下,`@boxlite-ai/boxlite` 可能找不到平台原生包(例如 `@boxlite-ai/boxlite-darwin-arm64`),会导致 `Cannot find native binding`。
98
-
99
- 结论:集成时需要明确安装/校验策略,避免运行时才暴露问题。
100
-
101
- ## 5. 主要风险与影响评估
102
-
103
- | 风险项 | 级别 | 影响 | 缓解建议 |
104
- |---|---:|---|---|
105
- | `execution.kill()` 在 wait 期间不稳定 | 高 | `stop/abort` 不可靠,可能产生僵尸执行 | 使用 `box.stop()` + 按策略重建 box;避免并发共享 box |
106
- | 原生包 optional dependency 安装不稳定 | 中 | 启动时报 native binding 缺失 | 启动前做依赖自检;CI/部署显式校验平台包 |
107
- | 文档/API 形态存在演进(本地 SDK vs Cloud API) | 中 | 误选技术路径导致返工 | 当前阶段锁定本地 Node SDK 接口,Cloud API 作为后续 |
108
- | box 级 stop 可能“连坐” | 中 | 若共享 box,会影响同 box 其他任务 | 采用每会话/每route隔离策略,或严格串行 |
109
-
110
- ## 6. 建议落地方案(当前项目)
111
-
112
- ### 6.1 推荐路线
113
-
114
- 优先实现 `BoxliteExecutor`(本地模式),保留 `DockerExecutor` 作为回退后端。
115
-
116
- ### 6.2 设计建议
117
-
118
- - 执行模型:
119
- - 建议“每会话一个 box”或“每 route 一个 box + 严格串行”。
120
- - 不建议多会话高并发共享单 box(会放大 stop 连坐影响)。
121
- - cwd 语义:
122
- - 保持与 `DockerExecutor` 一致,使用 `sh -lc "cd ... && <command>"` 语义对齐现有行为。
123
- - 或将 `workingDir` 固定为 `/workspace` 并通过 volume 映射 route `projectRoot`。
124
- - 超时/中断:
125
- - 先实现“外层 timeout -> box.stop() -> 标记 killed -> 按需重建 box”。
126
- - 将 `execution.kill()` 作为可选优化,不作为正确性前提。
127
- - 安全边界:
128
- - 保留现有 `projectRoot` 路径校验策略(不放宽)。
129
- - 映射目录最小化,必要时只挂载 route 对应工作目录。
130
-
131
- ### 6.3 分阶段实施
132
-
133
- 1. `P0`:新增 `src/sandbox/boxlite-executor.ts`,接入 `createExecutor` 分支。
134
- 2. `P1`:补充配置校验与启动前自检(原生 binding、镜像可用、workspace 映射)。
135
- 3. `P2`:实现 timeout/abort 的 box-stop 路径,保证语义闭环。
136
- 4. `P3`:压测与回归(串行执行、stop 指令、错误回写、重启恢复)。
137
-
138
- ## 7. 作为决策依据的“准入门槛”
139
-
140
- 将以下项全部满足后,才建议把 `boxlite` 作为默认 sandbox:
141
-
142
- 1. `npm run check` 与 `npm run build` 均通过。
143
- 2. 真实 Discord 链路下,`@bot` 消息可稳定得到响应。
144
- 3. `stop` 指令可在可接受时延内中断执行(建议 < 3s)。
145
- 4. 超时任务不会残留活跃执行(无僵尸进程累积)。
146
- 5. 关键错误可观测(日志中可区分运行错误、超时中断、环境故障)。
147
-
148
- ## 8. 参考来源
149
-
150
- 官方/源码(primary sources):
151
-
152
- - BoxLite 主仓库 README
153
- https://github.com/boxlite-ai/boxlite/blob/main/README.md
154
- - Node SDK 包定义
155
- https://github.com/boxlite-ai/boxlite/blob/main/sdks/node/package.json
156
- - Node `SimpleBox` 实现
157
- https://github.com/boxlite-ai/boxlite/blob/main/sdks/node/lib/simplebox.ts
158
- - Node 执行控制实现(`wait/kill`)
159
- https://github.com/boxlite-ai/boxlite/blob/main/sdks/node/src/exec.rs
160
- - Node API Reference
161
- https://github.com/boxlite-ai/boxlite/blob/main/docs/reference/nodejs/README.md
162
- - AI Agent Integration Guide
163
- https://github.com/boxlite-ai/boxlite/blob/main/docs/guides/ai-agent-integration.md
164
- - Cloud Sandbox OpenAPI
165
- https://github.com/boxlite-ai/boxlite/blob/main/openapi/rest-sandbox-open-api.yaml
166
- - Reference Server 说明
167
- https://github.com/boxlite-ai/boxlite/blob/main/openapi/reference-server/README.md
168
-
169
- 本仓库对接点:
170
-
171
- - `src/sandbox/executor.ts`
172
- - `src/core/types.ts`
173
- - `src/core/routing.ts`
174
- - `src/agent/session-factory.ts`
175
- - `src/sandbox/docker-executor.ts`
@@ -1,374 +0,0 @@
1
- # dobby 定时任务技术方案(Draft)
2
-
3
- > 状态:Draft / 待 Review
4
- > 日期:2026-03-05
5
- > 目标:在当前 dobby 架构下增加“可调度任务”,并将执行结果回传到对应 connector 的目标会话(channel/thread/DM 等)。
6
-
7
- ---
8
-
9
- ## 1. 背景与目标
10
-
11
- 当前 `dobby` 的执行入口是「连接器入站消息」(例如 Discord `messageCreate` 事件)触发 Gateway 流程。项目尚无“后台定时触发”能力。
12
-
13
- 本设计希望实现:
14
-
15
- 1. 支持一次性与周期性任务(at/interval/cron)。
16
- 2. 到点自动执行任务(调用现有 provider/sandbox 路由链路)。
17
- 3. 结果自动回传到目标 connector 会话(channel/thread/DM)。
18
- 4. 与现有架构保持一致:**不破坏扩展系统边界,不引入双执行链路**。
19
- 5. 默认不复用聊天会话状态(no shared conversation memory),避免 Cron 与日常对话互相污染。
20
-
21
- ---
22
-
23
- ## 2. 现状(dobby)
24
-
25
- ### 2.1 当前消息主链路
26
-
27
- - 以 Discord connector 为例:`messageCreate` -> `emitInbound` -> `Gateway.handleInbound`。
28
- - Gateway 内已具备:
29
- - 去重(dedup)
30
- - 路由解析(route)
31
- - provider runtime 复用与串行执行(conversation 级)
32
- - 流式事件转发与最终消息更新(`_Thinking..._` + delta + finalize)
33
-
34
- ### 2.2 关键约束
35
-
36
- - 目前没有 scheduler 生命周期管理。
37
- - 扩展系统只有 `provider | connector | sandbox` 三类 contribution。
38
- - 现有 `RuntimeRegistry` 的“串行队列模型”可借鉴用于 Cron 并发控制;但 scheduled 默认不走 `getOrCreate` 的长期会话复用路径。
39
-
40
- ---
41
-
42
- ## 3. 外部项目调研结论
43
-
44
- ### 3.1 openclaw(建议重点借鉴)
45
-
46
- openclaw 的 cron 子系统较完整,值得借鉴的点:
47
-
48
- 1. **任务模型清晰**:`schedule + payload + delivery + state`。
49
- 2. **调度鲁棒性**:
50
- - 启动恢复(stale running 标记清理、missed jobs 补跑)
51
- - 单任务超时保护
52
- - 连续失败退避(backoff)
53
- - 并发上限
54
- 3. **执行与投递分离**:
55
- - 先跑任务,再统一走 delivery dispatch(announce/direct/webhook)
56
- 4. **可观测性较强**:
57
- - 事件广播(started/finished/removed)
58
- - run log 追加记录
59
-
60
- ### 3.2 nanoclaw(可局部借鉴)
61
-
62
- nanoclaw 优点:
63
-
64
- 1. 结构直接(`scheduled_tasks + task_run_logs`)。
65
- 2. IPC + tool 的权限模型清晰(main 可跨组,普通组仅限本组)。
66
- 3. 实现成本低,便于快速起步。
67
-
68
- nanoclaw 的不适配点:
69
-
70
- - 定时任务结果默认不自动回用户,通常需要任务内显式调用 `send_message`。
71
- - 这会造成“任务执行完成但用户看不到结果”的产品风险,不符合 dobby 期望。
72
-
73
- ---
74
-
75
- ## 4. 推荐方案(dobby)
76
-
77
- ### 4.1 核心决策
78
-
79
- 采用 **Host 内置 CronService + 复用 Gateway 入站执行链路**。
80
-
81
- 简述:到点后构造一条 synthetic inbound message,交给 Gateway 执行,让现有 `EventForwarder` + connector `send` 流程完成回传。
82
-
83
- 这样得到的收益:
84
-
85
- 1. 无需维护第二套“任务执行 -> 消息发送”逻辑。
86
- 2. 自动复用现有流式输出、tool 状态、错误回写。
87
- 3. 会话串行、stop/abort 语义更一致。
88
- 4. 设计与当前架构耦合最小。
89
-
90
- ### 4.2 Connector 无关原则
91
-
92
- Cron 设计应保持 connector-agnostic,避免绑定 Discord 语义:
93
-
94
- 1. Scheduler 的唯一执行入口是 `Gateway.handleScheduled(...)`(synthetic inbound),不直接依赖 Discord SDK 细节。
95
- 2. v1 将任务目标定义为**结果回传通道**(`delivery`),字段先收敛为 `connectorId + routeId + channelId (+threadId)`。
96
- 3. 每个 connector 的文本长度、`updateStrategy`(`edit/final_only/append`)、线程能力等差异,继续由 `EventForwarder + connector plugin` 处理。
97
- 4. 文档与实现里提到 Discord 的地方都视为“示例 connector”,不是 Cron 的架构前提。
98
-
99
- ### 4.3 配置边界(采纳建议)
100
-
101
- 建议将 Cron 配置从 `gateway.json` 中拆出,使用独立配置文件:
102
-
103
- 1. `gateway.json` 继续只描述 gateway 核心(routes/bindings/providers/connectors/sandboxes/data)。
104
- 2. `cron.json` 独立描述调度器参数(enabled/store/poll/concurrency/recovery)。
105
- 3. CronService 启动时同时读取:
106
- - gateway 配置(用于 route/connector 运行时校验)
107
- - cron 配置(用于调度行为控制)
108
- 4. 这样可以降低配置耦合,后续也更容易把 Cron 演进为独立 sidecar。
109
-
110
- ### 4.4 会话策略(采纳建议)
111
-
112
- 对于周期性任务,默认采用 **stateless run**:
113
-
114
- 1. Cron 任务只“借用 connector 通道回消息”,不与该通道日常对话共享会话记忆。
115
- 2. 每次触发视为独立执行,不要求保留/恢复该频道的 conversation 状态。
116
- 3. 如后续有强需求,再支持显式开启 `shared-session`(可选项,默认关闭)。
117
-
118
- ### 4.5 术语澄清(本方案关键)
119
-
120
- 为避免“runtime/session”语义混淆,约定如下:
121
-
122
- 1. **Runtime(执行实例)**:provider 返回的单次执行对象(`prompt/subscribe/abort/dispose`)。
123
- 2. **Conversation session(会话状态)**:跨多轮消息复用的上下文状态(内存或持久化)。
124
- 3. **no shared conversation memory** 的含义是:**不复用会话状态**;并不等同于“完全不创建 runtime”。
125
- 4. 因此,Cron 默认形态是:**每个 run 创建临时 runtime,执行后立即释放**(transient runtime)。
126
-
127
- ---
128
-
129
- ## 5. 架构草图
130
-
131
- ```mermaid
132
- flowchart TD
133
- A["CronService timer tick"] --> B{"Job due?"}
134
- B -- "No" --> A
135
- B -- "Yes" --> C["Build synthetic InboundEnvelope"]
136
- C --> D["Gateway.handleScheduled(...)"]
137
- D --> E["Gateway.processMessage(...)"]
138
- E --> F["Provider runtime prompt(...)"]
139
- F --> G["EventForwarder stream/update/finalize"]
140
- G --> H["Connector plugin send/update"]
141
- H --> I["Persist job state + run log"]
142
- I --> A
143
- ```
144
-
145
- ---
146
-
147
- ## 6. 数据模型(Draft)
148
-
149
- 建议使用独立 `cron.json` 与任务存储文件。
150
-
151
- ### 6.1 cron.json 配置草案(独立于 gateway.json)
152
-
153
- ```json
154
- {
155
- "enabled": true,
156
- "storeFile": "./data/state/cron-jobs.json",
157
- "runLogFile": "./data/state/cron-runs.jsonl",
158
- "pollIntervalMs": 10000,
159
- "maxConcurrentRuns": 1,
160
- "runMissedOnStartup": true,
161
- "jobTimeoutMs": 600000
162
- }
163
- ```
164
-
165
- 建议的 cron 配置路径优先级:
166
-
167
- 1. `DOBBY_CRON_CONFIG_PATH`
168
- 2. `<gateway-config-dir>/cron.json`
169
- 3. `<data.rootDir>/state/cron.config.json`(自动初始化)
170
-
171
- ### 6.2 任务模型草案(TypeScript)
172
-
173
- ```ts
174
- type JobSchedule =
175
- | { kind: "at"; at: string } // ISO 时间
176
- | { kind: "every"; everyMs: number }
177
- | { kind: "cron"; expr: string; tz?: string };
178
-
179
- type JobDelivery = {
180
- connectorId: string; // connector instance id (e.g. discord.main)
181
- routeId: string; // existing routes.items key
182
- channelId: string; // result channel/chat id
183
- threadId?: string;
184
- };
185
-
186
- type ScheduledJob = {
187
- id: string;
188
- name: string;
189
- enabled: boolean;
190
- schedule: JobSchedule;
191
- sessionPolicy?: "stateless" | "shared-session"; // default: stateless
192
- prompt: string;
193
- delivery: JobDelivery;
194
- createdAtMs: number;
195
- updatedAtMs: number;
196
- state: {
197
- nextRunAtMs?: number;
198
- runningAtMs?: number;
199
- lastRunAtMs?: number;
200
- lastStatus?: "ok" | "error" | "skipped";
201
- lastError?: string;
202
- consecutiveErrors?: number;
203
- };
204
- };
205
- ```
206
-
207
- ---
208
-
209
- ## 7. 执行链路设计细节
210
-
211
- ### 7.1 Synthetic InboundEnvelope
212
-
213
- 到点执行时构造:
214
-
215
- - `messageId = "cron:<jobId>:<ts>"`(天然唯一,且纳入 dedup key)
216
- - `userId = "cron"`
217
- - `text = job.prompt`
218
- - `attachments = []`
219
- - `mentionedBot = true`(规避 `mentions="required"` 在群聊时被忽略)
220
- - `source = { type: "chat", id: job.delivery.channelId }`
221
- - `chatId = job.delivery.channelId`
222
- - `threadId = job.delivery.threadId`(若有)
223
- - `routeIdOverride = job.delivery.routeId`
224
- - `conversationKey` 使用 run 级唯一值(如 `cron:<jobId>:<runTs>`),不复用频道常规会话 key
225
-
226
- 然后交给 Gateway 处理(建议新增 `handleScheduled` 包装入口,内部复用现有流程)。
227
-
228
- ### 7.2 Stateless 执行实现建议
229
-
230
- 为满足“无需额外 conversation 管理”的目标,建议:
231
-
232
- 1. Scheduler 路径不要走现有 `RuntimeRegistry.getOrCreate(convKey)` 的长期复用语义。
233
- 2. 每次任务触发都创建一次临时 runtime(transient),执行完成后立即 `dispose`。
234
- 3. provider 侧增加 `sessionPolicy` 提示(`ephemeral`),避免读取/写回持久会话文件。
235
- 4. outbound 仍走 connector send(并按 `updateStrategy` 选择 create/update),因此用户看到的仍是同一 channel/thread 回传。
236
- 5. 结论:**不共享会话状态 != 不创建 runtime**;只是 runtime 生命周期收敛到单次 run。
237
-
238
- ### 7.3 为什么不单独“直连 provider + 自己 send 某个 connector”
239
-
240
- 不建议。会引入第二套:
241
-
242
- - thinking 占位逻辑
243
- - streaming 分片逻辑
244
- - tool 事件消息策略
245
- - 错误回写策略
246
-
247
- 这会在长期演进时造成行为漂移和维护成本上升。
248
-
249
- ---
250
-
251
- ## 8. 调度鲁棒性策略(v1)
252
-
253
- v1 建议实现以下最小鲁棒性(借鉴 openclaw):
254
-
255
- 1. 启动时清理 stale `runningAtMs`。
256
- 2. `runMissedOnStartup=true` 时补跑过期任务(每任务仅补一次)。
257
- 3. 单任务执行超时(默认 10 分钟,可配置)。
258
- 4. 连续错误指数退避(例如 30s/60s/5m/15m)。
259
- 5. 定时器最迟 60s 唤醒一次,避免 wall clock 漂移造成长期不触发。
260
- 6. 语义采用 **best-effort at-least-once**,v1 不承诺严格 exactly-once。
261
-
262
- ---
263
-
264
- ## 9. CLI 草案(先运维,再 Agent 自助)
265
-
266
- 先做运维 CLI,避免一开始引入 provider tool 改造:
267
-
268
- 1. `dobby cron add`
269
- 2. `dobby cron list`
270
- 3. `dobby cron status`
271
- 4. `dobby cron run <id>`
272
- 5. `dobby cron update <id>`
273
- 6. `dobby cron remove <id>`
274
- 7. `dobby cron pause/resume <id>`
275
-
276
- 后续再考虑提供 provider 侧 scheduling tools(供 Agent 在对话中创建任务)。
277
-
278
- ---
279
-
280
- ## 10. 拟改动文件清单(落地方向)
281
-
282
- ### 10.1 配置与类型
283
-
284
- - `src/cron/types.ts`(新增 cron config / job types)
285
- - `src/cron/config.ts`(cron config schema + load + normalize)
286
- - `config/cron.example.json`(新增 cron 示例配置)
287
- - `src/core/types.ts`(ProviderRuntimeCreateOptions 增加可选 `sessionPolicy`)
288
-
289
- ### 10.2 调度核心(新增)
290
-
291
- - `src/cron/schedule.ts`
292
- - `src/cron/store.ts`
293
- - `src/cron/service.ts`
294
-
295
- ### 10.3 启动接线
296
-
297
- - `src/cli/commands/start.ts`
298
- - 加载独立 cron 配置并初始化 CronService
299
- - 在 shutdown 里 stop CronService
300
-
301
- ### 10.4 Gateway 执行入口
302
-
303
- - `src/core/gateway.ts`
304
- - 增加 `handleScheduled(job, syntheticInbound)` 或等价方法
305
- - 复用 `processMessage`,保持执行行为一致
306
- - 新增“单次 runtime 执行后即释放”的 scheduled path(不经长期 registry)
307
-
308
- ### 10.5 CLI
309
-
310
- - `src/cli/program.ts`
311
- - `src/cli/commands/cron.ts`(新增)
312
- - 支持 `--cron-config` 可选参数(覆盖默认 cron 配置路径)
313
-
314
- ---
315
-
316
- ## 11. 分阶段落地建议
317
-
318
- ### Phase 1(最小可用)
319
-
320
- - JSON store + timer loop + `at/every/cron`
321
- - synthetic inbound 执行
322
- - 自动回传到任务绑定的 connector 会话
323
- - 默认 stateless(单次 runtime,不共享聊天会话)
324
- - CLI: add/list/run/remove
325
-
326
- ### Phase 2(可运营)
327
-
328
- - pause/resume/update/status
329
- - run log(`data/state/cron-runs.jsonl`)
330
- - 启动补跑 + backoff + timeout
331
-
332
- ### Phase 3(体验增强)
333
-
334
- - provider 工具化(对话内创建任务)
335
- - topic/thread 更细粒度 delivery 策略
336
- - 可视化状态面板(未来)
337
-
338
- ---
339
-
340
- ## 12. 验收标准(Draft)
341
-
342
- 1. 到点任务可触发并在目标 connector 会话看到回复(例如 Discord channel/thread)。
343
- 2. 回复包含流式更新(不是只在结束时一次性发送)。
344
- 3. 任务执行失败时能在对应 connector 会话看到错误信息。
345
- 4. 进程重启后可继续调度并恢复执行;在异常边界允许少量重复触发(best-effort)。
346
- 5. Cron 引入后不改变现有用户消息会话的串行语义(两者隔离、互不污染)。
347
- 6. 默认模式下,Cron 任务不会读取到该频道既有会话上下文(stateless 隔离)。
348
- 7. 架构上只保留一条执行链路:`CronService -> Gateway.handleScheduled -> provider runtime -> EventForwarder -> connector`。
349
-
350
- ---
351
-
352
- ## 13. 风险与折中
353
-
354
- 1. **会话语义误解风险**:容易把“stateless”理解为“完全不创建 runtime”,造成实现偏差。
355
- - 缓解:统一术语,明确“默认是 transient runtime,不复用 conversation memory”。
356
- 2. **mentions="required"**:定时任务不是用户 @bot 消息,需显式标记 `mentionedBot=true` 或在调度入口跳过该限制。
357
- 3. **时间语义**:cron 时区必须显式策略(默认系统时区,支持 per-job tz)。
358
- 4. **重复触发**:在崩溃恢复等异常边界可能出现少量重复触发(best-effort at-least-once)。
359
- - 缓解:通过 `runningAtMs` + 持久化时序控制尽量降低重复概率。
360
- 5. **Connector 能力差异**:不同 connector 的 edit/thread/file 能力不同,需严格走插件能力判断与降级策略。
361
- 6. **配置漂移**:cron 配置与 gateway 配置分离后,需在 `cron add/run` 时做 route/connector 存在性校验,避免引用失效。
362
- 7. **停止语义差异**:stateless run 不共享聊天 conversation key,`stop` 需通过 cron 管理命令或 job-run 级 abort 实现。
363
-
364
- ---
365
-
366
- ## 14. 最终建议
367
-
368
- 在 dobby 当前架构下,优先采用:
369
-
370
- - **openclaw 风格的鲁棒调度内核**
371
- - **dobby 现有 Gateway 执行链路复用**
372
- - **避免 nanoclaw 的“任务需显式 send_message 才回用户”模式**
373
-
374
- 该方案在工程复杂度、行为一致性、后续演进空间三者之间平衡最好。