@dobby.ai/dobby 0.1.0 → 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.
- package/README.md +84 -39
- package/dist/src/agent/event-forwarder.js +185 -16
- package/dist/src/cli/commands/cron.js +39 -35
- package/dist/src/cli/commands/doctor.js +81 -2
- package/dist/src/cli/commands/extension.js +3 -1
- package/dist/src/cli/commands/init.js +43 -173
- package/dist/src/cli/commands/topology.js +38 -14
- package/dist/src/cli/program.js +15 -137
- package/dist/src/cli/shared/config-io.js +3 -31
- package/dist/src/cli/shared/config-mutators.js +33 -9
- package/dist/src/cli/shared/configure-sections.js +52 -12
- package/dist/src/cli/shared/init-catalog.js +89 -46
- package/dist/src/cli/shared/local-extension-specs.js +85 -0
- package/dist/src/cli/shared/schema-prompts.js +26 -2
- package/dist/src/core/gateway.js +3 -1
- package/dist/src/core/routing.js +53 -38
- package/dist/src/core/types.js +2 -0
- package/dist/src/cron/config.js +2 -2
- package/dist/src/cron/service.js +87 -23
- package/dist/src/cron/store.js +1 -1
- package/dist/src/main.js +0 -0
- package/dist/src/shared/dobby-repo.js +40 -0
- package/package.json +11 -4
- package/.env.example +0 -9
- package/AGENTS.md +0 -267
- package/ROADMAP.md +0 -34
- package/config/cron.example.json +0 -9
- package/config/gateway.example.json +0 -128
- package/config/models.custom.example.json +0 -27
- package/dist/src/agent/tests/event-forwarder.test.js +0 -113
- package/dist/src/cli/shared/config-path.js +0 -207
- package/dist/src/cli/shared/init-models-file.js +0 -65
- package/dist/src/cli/shared/presets.js +0 -86
- package/dist/src/cli/tests/config-command.test.js +0 -42
- package/dist/src/cli/tests/config-io.test.js +0 -64
- package/dist/src/cli/tests/config-mutators.test.js +0 -47
- package/dist/src/cli/tests/config-path.test.js +0 -21
- package/dist/src/cli/tests/discord-config.test.js +0 -23
- package/dist/src/cli/tests/doctor.test.js +0 -107
- package/dist/src/cli/tests/init-catalog.test.js +0 -87
- package/dist/src/cli/tests/presets.test.js +0 -41
- package/dist/src/cli/tests/program-options.test.js +0 -92
- package/dist/src/cli/tests/routing-config.test.js +0 -199
- package/dist/src/cli/tests/routing-legacy.test.js +0 -191
- package/dist/src/core/tests/control-command.test.js +0 -17
- package/dist/src/core/tests/gateway-update-strategy.test.js +0 -167
- package/dist/src/core/tests/runtime-registry.test.js +0 -116
- package/dist/src/core/tests/typing-controller.test.js +0 -103
- package/docs/BOXLITE_SANDBOX_FEASIBILITY.md +0 -175
- package/docs/CRON_SCHEDULER_DESIGN.md +0 -374
- package/docs/DOCKER_SANDBOX_vs_BOXLITE.md +0 -77
- package/docs/EXTENSION_SYSTEM_ARCHITECTURE.md +0 -119
- package/docs/MVP.md +0 -135
- package/docs/RUNBOOK.md +0 -242
- package/docs/TEAMWORK_HANDOFF_DESIGN.md +0 -440
- package/plugins/connector-discord/dobby.manifest.json +0 -18
- package/plugins/connector-discord/index.js +0 -1
- package/plugins/connector-discord/package-lock.json +0 -360
- package/plugins/connector-discord/package.json +0 -38
- package/plugins/connector-discord/src/connector.ts +0 -350
- package/plugins/connector-discord/src/contribution.ts +0 -21
- package/plugins/connector-discord/src/mapper.ts +0 -102
- package/plugins/connector-discord/tsconfig.json +0 -19
- package/plugins/connector-feishu/dobby.manifest.json +0 -18
- package/plugins/connector-feishu/index.js +0 -1
- package/plugins/connector-feishu/package-lock.json +0 -618
- package/plugins/connector-feishu/package.json +0 -38
- package/plugins/connector-feishu/src/connector.ts +0 -343
- package/plugins/connector-feishu/src/contribution.ts +0 -26
- package/plugins/connector-feishu/src/mapper.ts +0 -401
- package/plugins/connector-feishu/tsconfig.json +0 -19
- package/plugins/plugin-sdk/index.d.ts +0 -261
- package/plugins/plugin-sdk/index.js +0 -1
- package/plugins/plugin-sdk/package-lock.json +0 -12
- package/plugins/plugin-sdk/package.json +0 -22
- package/plugins/provider-claude/dobby.manifest.json +0 -17
- package/plugins/provider-claude/index.js +0 -1
- package/plugins/provider-claude/package-lock.json +0 -3398
- package/plugins/provider-claude/package.json +0 -39
- package/plugins/provider-claude/src/contribution.ts +0 -1018
- package/plugins/provider-claude/tsconfig.json +0 -19
- package/plugins/provider-claude-cli/dobby.manifest.json +0 -17
- package/plugins/provider-claude-cli/index.js +0 -1
- package/plugins/provider-claude-cli/package-lock.json +0 -2898
- package/plugins/provider-claude-cli/package.json +0 -38
- package/plugins/provider-claude-cli/src/contribution.ts +0 -1673
- package/plugins/provider-claude-cli/tsconfig.json +0 -19
- package/plugins/provider-pi/dobby.manifest.json +0 -17
- package/plugins/provider-pi/index.js +0 -1
- package/plugins/provider-pi/package-lock.json +0 -3877
- package/plugins/provider-pi/package.json +0 -40
- package/plugins/provider-pi/src/contribution.ts +0 -476
- package/plugins/provider-pi/tsconfig.json +0 -19
- package/plugins/sandbox-core/boxlite.js +0 -1
- package/plugins/sandbox-core/dobby.manifest.json +0 -17
- package/plugins/sandbox-core/docker.js +0 -1
- package/plugins/sandbox-core/package-lock.json +0 -136
- package/plugins/sandbox-core/package.json +0 -39
- package/plugins/sandbox-core/src/boxlite-context.ts +0 -2
- package/plugins/sandbox-core/src/boxlite-contribution.ts +0 -53
- package/plugins/sandbox-core/src/boxlite-executor.ts +0 -911
- package/plugins/sandbox-core/src/docker-contribution.ts +0 -43
- package/plugins/sandbox-core/src/docker-executor.ts +0 -217
- package/plugins/sandbox-core/tsconfig.json +0 -19
- package/scripts/local-extensions.mjs +0 -168
- package/src/agent/event-forwarder.ts +0 -414
- package/src/cli/commands/config.ts +0 -328
- package/src/cli/commands/configure.ts +0 -92
- package/src/cli/commands/cron.ts +0 -410
- package/src/cli/commands/doctor.ts +0 -230
- package/src/cli/commands/extension.ts +0 -205
- package/src/cli/commands/init.ts +0 -396
- package/src/cli/commands/start.ts +0 -223
- package/src/cli/commands/topology.ts +0 -383
- package/src/cli/index.ts +0 -9
- package/src/cli/program.ts +0 -465
- package/src/cli/shared/config-io.ts +0 -277
- package/src/cli/shared/config-mutators.ts +0 -440
- package/src/cli/shared/config-schema.ts +0 -228
- package/src/cli/shared/config-types.ts +0 -121
- package/src/cli/shared/configure-sections.ts +0 -551
- package/src/cli/shared/discord-config.ts +0 -14
- package/src/cli/shared/init-catalog.ts +0 -189
- package/src/cli/shared/init-models-file.ts +0 -77
- package/src/cli/shared/runtime.ts +0 -33
- package/src/cli/shared/schema-prompts.ts +0 -414
- package/src/cli/tests/config-command.test.ts +0 -56
- package/src/cli/tests/config-io.test.ts +0 -92
- package/src/cli/tests/config-mutators.test.ts +0 -59
- package/src/cli/tests/doctor.test.ts +0 -120
- package/src/cli/tests/init-catalog.test.ts +0 -96
- package/src/cli/tests/program-options.test.ts +0 -113
- package/src/cli/tests/routing-config.test.ts +0 -209
- package/src/core/control-command.ts +0 -12
- package/src/core/dedup-store.ts +0 -103
- package/src/core/gateway.ts +0 -607
- package/src/core/routing.ts +0 -379
- package/src/core/runtime-registry.ts +0 -141
- package/src/core/tests/control-command.test.ts +0 -20
- package/src/core/tests/runtime-registry.test.ts +0 -140
- package/src/core/tests/typing-controller.test.ts +0 -129
- package/src/core/types.ts +0 -318
- package/src/core/typing-controller.ts +0 -119
- package/src/cron/config.ts +0 -154
- package/src/cron/schedule.ts +0 -61
- package/src/cron/service.ts +0 -249
- package/src/cron/store.ts +0 -155
- package/src/cron/types.ts +0 -60
- package/src/extension/loader.ts +0 -145
- package/src/extension/manager.ts +0 -355
- package/src/extension/manifest.ts +0 -26
- package/src/extension/registry.ts +0 -229
- package/src/main.ts +0 -8
- package/src/sandbox/executor.ts +0 -44
- package/src/sandbox/host-executor.ts +0 -118
- package/tsconfig.json +0 -18
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ Discord-first 本地 Agent Gateway。宿主只负责 CLI、网关主流程、扩
|
|
|
7
7
|
- `@dobby.ai/connector-discord`
|
|
8
8
|
- `@dobby.ai/connector-feishu`
|
|
9
9
|
- `@dobby.ai/provider-pi`
|
|
10
|
+
- `@dobby.ai/provider-codex-cli`
|
|
10
11
|
- `@dobby.ai/provider-claude-cli`
|
|
11
12
|
- `@dobby.ai/provider-claude`
|
|
12
13
|
- `@dobby.ai/sandbox-core`
|
|
@@ -17,14 +18,14 @@ Discord-first 本地 Agent Gateway。宿主只负责 CLI、网关主流程、扩
|
|
|
17
18
|
|
|
18
19
|
- connector source -> binding -> route -> provider / sandbox
|
|
19
20
|
- Discord 频道 / 线程接入;线程消息继续按父频道命中 binding
|
|
20
|
-
- Feishu 长连接消息接入(self-built app
|
|
21
|
+
- Feishu 长连接消息接入(self-built app)
|
|
21
22
|
- Feishu 出站支持普通文本和 Markdown 卡片;默认群内直发,不走 reply thread
|
|
22
23
|
- conversation 级 runtime 复用与串行化
|
|
23
24
|
- 扩展 store 安装、启用、列举与 schema 驱动配置
|
|
24
25
|
- Discord 流式回复、typing、附件下载与图片输入
|
|
25
26
|
- cron 调度:一次性、固定间隔、cron expression
|
|
26
|
-
- 交互式初始化:`dobby init
|
|
27
|
-
-
|
|
27
|
+
- 交互式初始化:`dobby init`(支持多 provider / 多 connector starter)
|
|
28
|
+
- 配置检查与 schema inspect:`dobby config show|list|schema`
|
|
28
29
|
- 诊断与保守修复:`dobby doctor [--fix]`
|
|
29
30
|
|
|
30
31
|
## 架构概览
|
|
@@ -59,7 +60,7 @@ Discord / Cron
|
|
|
59
60
|
- npm
|
|
60
61
|
- 对应 provider / connector 的外部运行条件
|
|
61
62
|
- 例如 Discord bot token
|
|
62
|
-
- Claude CLI 或 Claude Agent SDK 所需认证
|
|
63
|
+
- Codex CLI、Claude CLI 或 Claude Agent SDK 所需认证
|
|
63
64
|
- 可选的 Docker / Boxlite 运行环境
|
|
64
65
|
|
|
65
66
|
## 快速开始
|
|
@@ -76,7 +77,7 @@ npm install
|
|
|
76
77
|
npm run build
|
|
77
78
|
```
|
|
78
79
|
|
|
79
|
-
3.
|
|
80
|
+
3. 初始化模板配置
|
|
80
81
|
|
|
81
82
|
```bash
|
|
82
83
|
npm run start -- init
|
|
@@ -84,21 +85,41 @@ npm run start -- init
|
|
|
84
85
|
|
|
85
86
|
`init` 会做这些事情:
|
|
86
87
|
|
|
87
|
-
- 交互选择 provider 和 connector
|
|
88
|
+
- 交互选择 provider 和 connector(均可多选)
|
|
88
89
|
- 自动安装所选扩展到运行时 extension store
|
|
89
|
-
-
|
|
90
|
+
- 写入一份带占位符的 `gateway.json` 模板
|
|
91
|
+
- 把 `routes.default.projectRoot` 设为当前工作目录
|
|
92
|
+
- 为 direct message 生成 `bindings.default`,回落到默认 route
|
|
93
|
+
- 为每个所选 connector 生成一个默认 binding 到同一条 route
|
|
90
94
|
- 生成 `gateway.json`
|
|
91
|
-
-
|
|
95
|
+
- `provider.pi` 默认写入最小 inline 配置,不再依赖 `models.custom.json`
|
|
92
96
|
|
|
93
|
-
说明:当前 `init`
|
|
97
|
+
说明:当前 `init` 内建这些 starter 选择:
|
|
94
98
|
|
|
95
|
-
|
|
99
|
+
- provider:`provider.pi`、`provider.claude-cli`
|
|
100
|
+
- connector:`connector.discord`、`connector.feishu`
|
|
101
|
+
|
|
102
|
+
4. 编辑 `gateway.json`
|
|
103
|
+
|
|
104
|
+
把 `REPLACE_WITH_*` / `YOUR_*` 占位值替换成你的真实配置,例如:
|
|
105
|
+
|
|
106
|
+
- `connectors.items[*]` 中的 token / appId / appSecret
|
|
107
|
+
- `bindings.items[*].source.id`
|
|
108
|
+
- `routes.items[*].projectRoot`(如需覆盖默认 project root)
|
|
109
|
+
|
|
110
|
+
5. 运行诊断
|
|
96
111
|
|
|
97
112
|
```bash
|
|
98
113
|
npm run start -- doctor
|
|
99
114
|
```
|
|
100
115
|
|
|
101
|
-
|
|
116
|
+
`doctor` 会同时检查:
|
|
117
|
+
|
|
118
|
+
- 配置结构 / 引用关系
|
|
119
|
+
- 缺失的扩展安装
|
|
120
|
+
- `REPLACE_WITH_*` / `YOUR_*` 这类 init 占位值是否还未替换
|
|
121
|
+
|
|
122
|
+
6. 启动网关
|
|
102
123
|
|
|
103
124
|
```bash
|
|
104
125
|
npm run start --
|
|
@@ -107,7 +128,9 @@ npm run start --
|
|
|
107
128
|
说明:
|
|
108
129
|
|
|
109
130
|
- `dobby` 无子命令时,默认等价于 `dobby start`
|
|
131
|
+
- `dobby --version` 可直接查看当前 CLI 版本
|
|
110
132
|
- 在仓库内直接运行时,CLI 会自动使用 `./config/gateway.json`
|
|
133
|
+
- 在仓库内执行 `init` / `extension install` 时,会优先安装 `plugins/*` 的本地构建产物
|
|
111
134
|
- 也可以通过环境变量覆盖配置路径:
|
|
112
135
|
|
|
113
136
|
```bash
|
|
@@ -152,33 +175,23 @@ cron 配置路径优先级:
|
|
|
152
175
|
顶层命令:
|
|
153
176
|
|
|
154
177
|
```bash
|
|
178
|
+
dobby --version
|
|
155
179
|
dobby start
|
|
156
180
|
dobby init
|
|
157
|
-
dobby configure
|
|
158
181
|
dobby doctor [--fix]
|
|
159
182
|
```
|
|
160
183
|
|
|
161
|
-
|
|
184
|
+
配置检查:
|
|
162
185
|
|
|
163
186
|
```bash
|
|
164
187
|
dobby config show [section] [--json]
|
|
165
188
|
dobby config list [section] [--json]
|
|
166
|
-
dobby config edit [--section provider|connector|route|binding]
|
|
167
189
|
dobby config schema list [--json]
|
|
168
190
|
dobby config schema show <contributionId> [--json]
|
|
169
|
-
|
|
170
|
-
dobby bot list [--json]
|
|
171
|
-
dobby bot set <connectorId> [--name <name>] [--token <token>]
|
|
172
|
-
|
|
173
|
-
dobby binding list [--connector <id>] [--json]
|
|
174
|
-
dobby binding set <bindingId> --connector <id> --source-type channel|chat --source-id <id> --route <id>
|
|
175
|
-
dobby binding remove <bindingId>
|
|
176
|
-
|
|
177
|
-
dobby route list [--json]
|
|
178
|
-
dobby route set <routeId> [--project-root <path>] [--tools full|readonly] [--provider <id>] [--sandbox <id>] [--mentions required|optional]
|
|
179
|
-
dobby route remove <routeId> [--cascade-bindings]
|
|
180
191
|
```
|
|
181
192
|
|
|
193
|
+
配置变更建议直接编辑 `gateway.json`,再通过 `dobby doctor` 或 `dobby start` 做校验。
|
|
194
|
+
|
|
182
195
|
扩展管理:
|
|
183
196
|
|
|
184
197
|
```bash
|
|
@@ -221,25 +234,40 @@ dobby cron remove <jobId>
|
|
|
221
234
|
- 默认 provider instance ID
|
|
222
235
|
- `providers.items[*].type` / `connectors.items[*].type` / `sandboxes.items[*].type`
|
|
223
236
|
- 指向某个 contribution,实例配置直接内联在对象里
|
|
224
|
-
- `routes.
|
|
225
|
-
- 统一提供 route 默认的 `provider`、`sandbox`、`tools`、`mentions`
|
|
237
|
+
- `routes.default`
|
|
238
|
+
- 统一提供 route 默认的 `projectRoot`、`provider`、`sandbox`、`tools`、`mentions`
|
|
226
239
|
- `routes.items[*]`
|
|
227
|
-
- route 是可复用的执行 profile
|
|
240
|
+
- route 是可复用的执行 profile,可继承默认 `projectRoot`,并按需覆盖 `provider`、`sandbox`、`tools`、`mentions`、`systemPromptFile`
|
|
241
|
+
- `bindings.default`
|
|
242
|
+
- direct message 未命中显式 binding 时使用的默认 route fallback
|
|
228
243
|
- `bindings.items[*]`
|
|
229
244
|
- `(connector, source.type, source.id) -> route` 的入口绑定
|
|
230
245
|
- `sandboxes.default`
|
|
231
246
|
- 未指定时默认使用 `host.builtin`
|
|
232
|
-
- 未匹配 binding
|
|
233
|
-
|
|
234
|
-
当前代码还保留但未真正生效的字段:
|
|
235
|
-
|
|
236
|
-
- cron job 的 `sessionPolicy`
|
|
247
|
+
- 未匹配 binding 的入站消息会被直接忽略;仅 direct message 可回落到 `bindings.default`
|
|
237
248
|
|
|
238
249
|
示例配置:
|
|
239
250
|
|
|
240
251
|
- gateway:[`config/gateway.example.json`](config/gateway.example.json)
|
|
241
252
|
- cron:[`config/cron.example.json`](config/cron.example.json)
|
|
242
|
-
|
|
253
|
+
|
|
254
|
+
`provider.pi` 现在使用 inline custom provider 配置。最小常用字段是:
|
|
255
|
+
|
|
256
|
+
- `model`
|
|
257
|
+
- `baseUrl`
|
|
258
|
+
- `apiKey`
|
|
259
|
+
|
|
260
|
+
这些字段默认自动补齐:
|
|
261
|
+
|
|
262
|
+
- `provider = "custom-openai"`
|
|
263
|
+
- `api = "openai-completions"`
|
|
264
|
+
- `authHeader = false`
|
|
265
|
+
- `thinkingLevel = "off"`
|
|
266
|
+
- `models = [{ id: model }]`
|
|
267
|
+
|
|
268
|
+
只有在你需要多模型元数据或覆盖能力参数时,才需要手工展开 `models`。
|
|
269
|
+
|
|
270
|
+
`apiKey` 支持直接写 literal,也支持写环境变量名,由 `pi` 的 `AuthStorage` / `ModelRegistry` 按上游规则解析。
|
|
243
271
|
|
|
244
272
|
## 扩展包与 contribution
|
|
245
273
|
|
|
@@ -247,6 +275,7 @@ dobby cron remove <jobId>
|
|
|
247
275
|
|
|
248
276
|
- `connector.discord`
|
|
249
277
|
- `provider.pi`
|
|
278
|
+
- `provider.codex-cli`
|
|
250
279
|
- `provider.claude-cli`
|
|
251
280
|
- `provider.claude`
|
|
252
281
|
- `sandbox.boxlite`
|
|
@@ -255,15 +284,30 @@ dobby cron remove <jobId>
|
|
|
255
284
|
`dobby init` 当前只内建这些 starter 选择:
|
|
256
285
|
|
|
257
286
|
- provider:`provider.pi`、`provider.claude-cli`
|
|
258
|
-
- connector:`connector.discord`
|
|
287
|
+
- connector:`connector.discord`、`connector.feishu`
|
|
259
288
|
|
|
260
|
-
`provider.claude` 与 sandbox 相关扩展需要手工安装和配置,例如:
|
|
289
|
+
`provider.codex-cli`、`provider.claude` 与 sandbox 相关扩展需要手工安装和配置,例如:
|
|
261
290
|
|
|
262
291
|
```bash
|
|
292
|
+
npm run start -- extension install @dobby.ai/provider-codex-cli --enable
|
|
263
293
|
npm run start -- extension install @dobby.ai/provider-claude --enable
|
|
264
294
|
npm run start -- extension install @dobby.ai/sandbox-core --enable
|
|
265
295
|
```
|
|
266
296
|
|
|
297
|
+
若使用 `provider.codex-cli`,启动前建议检查:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
codex --version
|
|
301
|
+
codex login status
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
最小配置字段:
|
|
305
|
+
|
|
306
|
+
- `command`(默认 `codex`)
|
|
307
|
+
- `commandArgs`(默认 `[]`)
|
|
308
|
+
- `model`(可选;未设置时使用 Codex CLI / 本地配置默认模型)
|
|
309
|
+
- `skipGitRepoCheck`(默认 `false`)
|
|
310
|
+
|
|
267
311
|
`--enable` 的行为:
|
|
268
312
|
|
|
269
313
|
- 把包写入 `extensions.allowList`
|
|
@@ -292,13 +336,14 @@ npm run start -- cron add daily-report \
|
|
|
292
336
|
|
|
293
337
|
说明:
|
|
294
338
|
|
|
295
|
-
- `cron run <jobId>`
|
|
339
|
+
- `cron run <jobId>` 会额外排队一次立即执行,不会恢复 paused 状态,也不会改写原有 `nextRunAtMs`
|
|
296
340
|
- 需要已有一个正在运行的 `dobby start`
|
|
297
341
|
- 当前 scheduled run 一律按 stateless / ephemeral 执行
|
|
298
342
|
|
|
299
343
|
## Discord 连接器的当前行为
|
|
300
344
|
|
|
301
|
-
-
|
|
345
|
+
- guild channel 仍按显式 binding 匹配
|
|
346
|
+
- DM 可通过 `bindings.default` 回落到默认 route
|
|
302
347
|
- 线程消息使用父频道 ID 做 binding 查找
|
|
303
348
|
- 会自动下载附件到本地
|
|
304
349
|
- 图片会作为 image input 传给 provider
|
|
@@ -372,7 +417,7 @@ npm run plugins:build
|
|
|
372
417
|
|
|
373
418
|
- `npm run dev:local` 与 `npm run start:local` 会尝试读取 `.env`
|
|
374
419
|
- 普通 `npm run start -- ...` 不会自动载入 `.env`
|
|
375
|
-
-
|
|
420
|
+
- `dobby init` 生成的是模板配置;运行前先替换 `gateway.json` 中的 placeholder
|
|
376
421
|
|
|
377
422
|
## 相关文档
|
|
378
423
|
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { OUTBOUND_MESSAGE_KIND_METADATA_KEY, OUTBOUND_MESSAGE_KIND_PROGRESS, } from "../core/types.js";
|
|
2
|
+
const DEFAULT_PROGRESS_DEBOUNCE_MS = 150;
|
|
3
|
+
const DEFAULT_LONG_PROGRESS_MS = 10_000;
|
|
4
|
+
const WORKING_LOCALLY_TEXT = "Working locally...";
|
|
5
|
+
const STILL_WORKING_LOCALLY_TEXT = "Still working locally...";
|
|
6
|
+
const WORKING_WITH_TOOLS_TEXT = "Working with tools...";
|
|
7
|
+
const STILL_WORKING_WITH_TOOLS_TEXT = "Still working with tools...";
|
|
8
|
+
const RECOVERING_FROM_TOOL_ISSUE_TEXT = "Recovering from a tool issue...";
|
|
1
9
|
function truncate(text, max) {
|
|
2
10
|
if (max === undefined)
|
|
3
11
|
return text;
|
|
@@ -57,22 +65,37 @@ export class EventForwarder {
|
|
|
57
65
|
appendEmittedText = "";
|
|
58
66
|
pendingFlush = null;
|
|
59
67
|
flushSerial = Promise.resolve();
|
|
68
|
+
progressSerial = Promise.resolve();
|
|
60
69
|
pendingOps = [];
|
|
61
70
|
updateIntervalMs;
|
|
71
|
+
progressDebounceMs;
|
|
72
|
+
longProgressMs;
|
|
62
73
|
toolMessageMode;
|
|
63
74
|
maxTextLength;
|
|
64
75
|
onOutboundActivity;
|
|
65
76
|
updateStrategy;
|
|
77
|
+
progressUpdateStrategy;
|
|
66
78
|
lastEditPrimaryText = null;
|
|
79
|
+
progressMessageId = null;
|
|
80
|
+
lastProgressMessageText = null;
|
|
81
|
+
pendingProgressText = null;
|
|
82
|
+
pendingProgressFlush = null;
|
|
83
|
+
hasQueuedProgressMessage = false;
|
|
84
|
+
longProgressTimer = null;
|
|
85
|
+
activeWorkPhase = null;
|
|
67
86
|
constructor(connector, inbound, rootMessageId, logger, options = {}) {
|
|
68
87
|
this.connector = connector;
|
|
69
88
|
this.inbound = inbound;
|
|
70
89
|
this.logger = logger;
|
|
71
90
|
this.rootMessageId = rootMessageId;
|
|
72
91
|
this.updateIntervalMs = options.updateIntervalMs ?? 400;
|
|
92
|
+
this.progressDebounceMs = options.progressDebounceMs ?? DEFAULT_PROGRESS_DEBOUNCE_MS;
|
|
93
|
+
this.longProgressMs = options.longProgressMs ?? DEFAULT_LONG_PROGRESS_MS;
|
|
73
94
|
this.toolMessageMode = options.toolMessageMode ?? "none";
|
|
74
95
|
this.onOutboundActivity = options.onOutboundActivity;
|
|
75
96
|
this.updateStrategy = this.connector.capabilities.updateStrategy;
|
|
97
|
+
this.progressUpdateStrategy = this.connector.capabilities.progressUpdateStrategy
|
|
98
|
+
?? (this.updateStrategy === "edit" ? "edit" : "create");
|
|
76
99
|
const capabilityMaxTextLength = this.connector.capabilities.maxTextLength;
|
|
77
100
|
this.maxTextLength = typeof capabilityMaxTextLength === "number" && capabilityMaxTextLength > 0
|
|
78
101
|
? capabilityMaxTextLength
|
|
@@ -98,13 +121,20 @@ export class EventForwarder {
|
|
|
98
121
|
}
|
|
99
122
|
return;
|
|
100
123
|
}
|
|
124
|
+
if (event.type === "command_start") {
|
|
125
|
+
this.enterWorkPhase("local");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
101
128
|
if (event.type === "tool_start") {
|
|
102
129
|
this.logger.info({
|
|
103
130
|
toolName: event.toolName,
|
|
104
131
|
conversation: `${this.inbound.platform}:${this.inbound.accountId}:${this.inbound.chatId}:${this.inbound.threadId ?? "root"}`,
|
|
105
132
|
}, "Tool execution started");
|
|
106
|
-
if (this.
|
|
107
|
-
this.
|
|
133
|
+
if (this.toolMessageMode === "all") {
|
|
134
|
+
this.enqueueSideMessage(this.renderToolStartMessage(event.toolName));
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
this.enterWorkPhase("tool");
|
|
108
138
|
}
|
|
109
139
|
return;
|
|
110
140
|
}
|
|
@@ -115,26 +145,21 @@ export class EventForwarder {
|
|
|
115
145
|
isError: event.isError,
|
|
116
146
|
conversation: `${this.inbound.platform}:${this.inbound.accountId}:${this.inbound.chatId}:${this.inbound.threadId ?? "root"}`,
|
|
117
147
|
}, event.isError ? "Tool execution finished with error" : "Tool execution finished");
|
|
118
|
-
if (this.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const availableSummaryLength = this.maxTextLength === undefined
|
|
124
|
-
? undefined
|
|
125
|
-
: Math.max(0, this.maxTextLength - header.length - footer.length);
|
|
126
|
-
this.enqueueSend(`${header}${truncate(summary, availableSummaryLength)}${footer}`);
|
|
148
|
+
if ((this.toolMessageMode === "all" || (this.toolMessageMode === "errors" && event.isError))) {
|
|
149
|
+
this.enqueueSideMessage(this.renderToolEndMessage(event.toolName, event.isError, summary));
|
|
150
|
+
}
|
|
151
|
+
else if (event.isError) {
|
|
152
|
+
this.setProgressMessage(RECOVERING_FROM_TOOL_ISSUE_TEXT);
|
|
127
153
|
}
|
|
128
154
|
return;
|
|
129
155
|
}
|
|
130
156
|
if (event.type === "status") {
|
|
131
|
-
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
this.enqueueSend(`_${event.message}_`);
|
|
157
|
+
this.setProgressMessage(event.message);
|
|
135
158
|
}
|
|
136
159
|
};
|
|
137
160
|
async finalize() {
|
|
161
|
+
this.clearLongProgressTimer();
|
|
162
|
+
await this.flushProgressUpdates();
|
|
138
163
|
if (this.updateStrategy === "final_only") {
|
|
139
164
|
await this.finalizeFinalOnly();
|
|
140
165
|
await Promise.allSettled(this.pendingOps);
|
|
@@ -173,6 +198,7 @@ export class EventForwarder {
|
|
|
173
198
|
return;
|
|
174
199
|
}
|
|
175
200
|
try {
|
|
201
|
+
await this.flushProgressUpdates();
|
|
176
202
|
if (this.updateStrategy === "append") {
|
|
177
203
|
await this.flushAppendProgress();
|
|
178
204
|
return;
|
|
@@ -198,13 +224,113 @@ export class EventForwarder {
|
|
|
198
224
|
});
|
|
199
225
|
await run;
|
|
200
226
|
}
|
|
201
|
-
|
|
227
|
+
enqueueSideMessage(text) {
|
|
228
|
+
this.enqueueSendWithMetadata(text, this.progressMessageMetadata());
|
|
229
|
+
}
|
|
230
|
+
setProgressMessage(message) {
|
|
231
|
+
this.activeWorkPhase = null;
|
|
232
|
+
this.clearLongProgressTimer();
|
|
233
|
+
this.scheduleProgressUpdate(this.renderStatusMessage(message));
|
|
234
|
+
}
|
|
235
|
+
enterWorkPhase(phase) {
|
|
236
|
+
if (this.activeWorkPhase === "local" && phase === "tool") {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (this.activeWorkPhase === phase) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
this.activeWorkPhase = phase;
|
|
243
|
+
this.scheduleProgressUpdate(this.renderWorkPhaseMessage(phase, false));
|
|
244
|
+
this.scheduleLongProgressUpdate(phase);
|
|
245
|
+
}
|
|
246
|
+
scheduleProgressUpdate(text) {
|
|
247
|
+
const shouldCreateImmediately = !this.hasQueuedProgressMessage;
|
|
248
|
+
this.hasQueuedProgressMessage = true;
|
|
249
|
+
this.pendingProgressText = text;
|
|
250
|
+
if (shouldCreateImmediately) {
|
|
251
|
+
void this.flushProgressNow();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (this.pendingProgressFlush) {
|
|
255
|
+
clearTimeout(this.pendingProgressFlush);
|
|
256
|
+
}
|
|
257
|
+
this.pendingProgressFlush = setTimeout(() => {
|
|
258
|
+
void this.flushProgressNow();
|
|
259
|
+
}, this.progressDebounceMs);
|
|
260
|
+
}
|
|
261
|
+
async flushProgressNow() {
|
|
262
|
+
if (this.pendingProgressFlush) {
|
|
263
|
+
clearTimeout(this.pendingProgressFlush);
|
|
264
|
+
this.pendingProgressFlush = null;
|
|
265
|
+
}
|
|
266
|
+
const text = this.pendingProgressText;
|
|
267
|
+
if (text === null) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
this.pendingProgressText = null;
|
|
271
|
+
const metadata = this.progressMessageMetadata();
|
|
272
|
+
const run = this.progressSerial.then(async () => {
|
|
273
|
+
if (this.lastProgressMessageText === text) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
if (this.canEditProgressMessage() && this.progressMessageId) {
|
|
278
|
+
await this.connector.send({
|
|
279
|
+
...this.baseEnvelope(),
|
|
280
|
+
mode: "update",
|
|
281
|
+
targetMessageId: this.progressMessageId,
|
|
282
|
+
text,
|
|
283
|
+
metadata,
|
|
284
|
+
});
|
|
285
|
+
this.lastProgressMessageText = text;
|
|
286
|
+
this.noteOutboundActivity();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const created = await this.connector.send({
|
|
290
|
+
...this.baseEnvelope(),
|
|
291
|
+
mode: "create",
|
|
292
|
+
text,
|
|
293
|
+
metadata,
|
|
294
|
+
});
|
|
295
|
+
this.progressMessageId = this.canEditProgressMessage() ? (created.messageId ?? this.progressMessageId) : null;
|
|
296
|
+
this.lastProgressMessageText = text;
|
|
297
|
+
this.noteOutboundActivity();
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
this.logger.warn({
|
|
301
|
+
err: error,
|
|
302
|
+
connectorId: this.inbound.connectorId,
|
|
303
|
+
chatId: this.inbound.chatId,
|
|
304
|
+
progressMessageId: this.progressMessageId,
|
|
305
|
+
}, "Failed to send or update progress message");
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
this.progressSerial = run.catch(() => {
|
|
309
|
+
// keep chain alive for future progress updates
|
|
310
|
+
});
|
|
311
|
+
this.pendingOps.push(run);
|
|
312
|
+
await run;
|
|
313
|
+
}
|
|
314
|
+
scheduleLongProgressUpdate(phase) {
|
|
315
|
+
this.clearLongProgressTimer();
|
|
316
|
+
if (this.longProgressMs <= 0) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
this.longProgressTimer = setTimeout(() => {
|
|
320
|
+
if (this.activeWorkPhase !== phase) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
this.scheduleProgressUpdate(this.renderWorkPhaseMessage(phase, true));
|
|
324
|
+
}, this.longProgressMs);
|
|
325
|
+
}
|
|
326
|
+
enqueueSendWithMetadata(text, metadata) {
|
|
202
327
|
const promise = this.connector
|
|
203
328
|
.send({
|
|
204
329
|
...this.baseEnvelope(),
|
|
205
330
|
mode: "create",
|
|
206
331
|
...(this.rootMessageId ? { replyToMessageId: this.rootMessageId } : {}),
|
|
207
332
|
text,
|
|
333
|
+
...(metadata ? { metadata } : {}),
|
|
208
334
|
})
|
|
209
335
|
.then(() => {
|
|
210
336
|
this.noteOutboundActivity();
|
|
@@ -219,6 +345,49 @@ export class EventForwarder {
|
|
|
219
345
|
});
|
|
220
346
|
this.pendingOps.push(promise);
|
|
221
347
|
}
|
|
348
|
+
canEditProgressMessage() {
|
|
349
|
+
return this.progressUpdateStrategy === "edit";
|
|
350
|
+
}
|
|
351
|
+
progressMessageMetadata() {
|
|
352
|
+
return {
|
|
353
|
+
[OUTBOUND_MESSAGE_KIND_METADATA_KEY]: OUTBOUND_MESSAGE_KIND_PROGRESS,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
renderStatusMessage(message) {
|
|
357
|
+
return truncate(message, this.maxTextLength);
|
|
358
|
+
}
|
|
359
|
+
renderWorkPhaseMessage(phase, isLongRunning) {
|
|
360
|
+
if (phase === "local") {
|
|
361
|
+
return truncate(isLongRunning ? STILL_WORKING_LOCALLY_TEXT : WORKING_LOCALLY_TEXT, this.maxTextLength);
|
|
362
|
+
}
|
|
363
|
+
return truncate(isLongRunning ? STILL_WORKING_WITH_TOOLS_TEXT : WORKING_WITH_TOOLS_TEXT, this.maxTextLength);
|
|
364
|
+
}
|
|
365
|
+
renderToolStartMessage(toolName) {
|
|
366
|
+
return truncate(`Running tool: ${toolName}`, this.maxTextLength);
|
|
367
|
+
}
|
|
368
|
+
renderToolEndMessage(toolName, isError, summary) {
|
|
369
|
+
const prefix = isError ? "ERR" : "OK";
|
|
370
|
+
const body = summary.trim().length > 0 ? `${prefix} ${toolName}\n${summary}` : `${prefix} ${toolName}`;
|
|
371
|
+
return truncate(body, this.maxTextLength);
|
|
372
|
+
}
|
|
373
|
+
async flushProgressUpdates() {
|
|
374
|
+
if (this.pendingProgressText !== null) {
|
|
375
|
+
await this.flushProgressNow();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
await this.progressSerial;
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// progress message failures are already logged when they happen
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
clearLongProgressTimer() {
|
|
386
|
+
if (this.longProgressTimer) {
|
|
387
|
+
clearTimeout(this.longProgressTimer);
|
|
388
|
+
this.longProgressTimer = null;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
222
391
|
async sendEditPrimary(text) {
|
|
223
392
|
if (this.rootMessageId && this.lastEditPrimaryText === text) {
|
|
224
393
|
return;
|
|
@@ -36,15 +36,6 @@ function parseSchedule(input) {
|
|
|
36
36
|
...(input.tz ? { tz: input.tz } : {}),
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
function parseSessionPolicy(value) {
|
|
40
|
-
if (value === undefined) {
|
|
41
|
-
return undefined;
|
|
42
|
-
}
|
|
43
|
-
if (value === "stateless" || value === "shared-session") {
|
|
44
|
-
return value;
|
|
45
|
-
}
|
|
46
|
-
throw new Error(`Invalid session policy '${value}'. Expected 'stateless' or 'shared-session'.`);
|
|
47
|
-
}
|
|
48
39
|
function assertDeliveryReferences(config, input) {
|
|
49
40
|
if (!config.connectors.items[input.connectorId]) {
|
|
50
41
|
throw new Error(`Unknown connectorId '${input.connectorId}'`);
|
|
@@ -86,13 +77,11 @@ export async function runCronAddCommand(options) {
|
|
|
86
77
|
const now = Date.now();
|
|
87
78
|
const nextRunAtMs = computeInitialNextRunAtMs(schedule, now);
|
|
88
79
|
const id = `${slugify(options.name)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
89
|
-
const sessionPolicy = parseSessionPolicy(options.sessionPolicy) ?? "stateless";
|
|
90
80
|
const job = {
|
|
91
81
|
id,
|
|
92
82
|
name: options.name,
|
|
93
83
|
enabled: true,
|
|
94
84
|
schedule,
|
|
95
|
-
sessionPolicy,
|
|
96
85
|
prompt: options.prompt,
|
|
97
86
|
delivery: {
|
|
98
87
|
connectorId: options.connectorId,
|
|
@@ -132,6 +121,9 @@ export async function runCronListCommand(options) {
|
|
|
132
121
|
console.log(`- ${job.id} [${job.enabled ? "enabled" : "paused"}] ${job.name}`);
|
|
133
122
|
console.log(` schedule=${schedule}`);
|
|
134
123
|
console.log(` next=${next} last=${last} status=${job.state.lastStatus ?? "-"}`);
|
|
124
|
+
if (job.state.manualRunRequestedAtMs !== undefined) {
|
|
125
|
+
console.log(` manualRun=${new Date(job.state.manualRunRequestedAtMs).toISOString()}`);
|
|
126
|
+
}
|
|
135
127
|
console.log(` delivery=${job.delivery.connectorId}/${job.delivery.routeId}/${job.delivery.channelId}`);
|
|
136
128
|
}
|
|
137
129
|
}
|
|
@@ -154,10 +146,12 @@ export async function runCronStatusCommand(options) {
|
|
|
154
146
|
console.log(`- name: ${target.name}`);
|
|
155
147
|
console.log(`- enabled: ${target.enabled}`);
|
|
156
148
|
console.log(`- schedule: ${describeSchedule(target.schedule)}`);
|
|
157
|
-
console.log(`- sessionPolicy: ${target.sessionPolicy ?? "stateless"}`);
|
|
158
149
|
console.log(`- nextRun: ${target.state.nextRunAtMs ? new Date(target.state.nextRunAtMs).toISOString() : "-"}`);
|
|
159
150
|
console.log(`- lastRun: ${target.state.lastRunAtMs ? new Date(target.state.lastRunAtMs).toISOString() : "-"}`);
|
|
160
151
|
console.log(`- lastStatus: ${target.state.lastStatus ?? "-"}`);
|
|
152
|
+
if (target.state.manualRunRequestedAtMs !== undefined) {
|
|
153
|
+
console.log(`- manualRunQueuedAt: ${new Date(target.state.manualRunRequestedAtMs).toISOString()}`);
|
|
154
|
+
}
|
|
161
155
|
if (target.state.lastError) {
|
|
162
156
|
console.log(`- lastError: ${target.state.lastError}`);
|
|
163
157
|
}
|
|
@@ -173,28 +167,23 @@ export async function runCronRunCommand(options) {
|
|
|
173
167
|
const now = Date.now();
|
|
174
168
|
await context.store.updateJob(options.jobId, (current) => ({
|
|
175
169
|
...current,
|
|
176
|
-
enabled: true,
|
|
177
170
|
updatedAtMs: now,
|
|
178
171
|
state: {
|
|
179
172
|
...current.state,
|
|
180
|
-
|
|
173
|
+
manualRunRequestedAtMs: current.state.manualRunRequestedAtMs ?? now,
|
|
181
174
|
},
|
|
182
175
|
}));
|
|
183
|
-
console.log(`
|
|
176
|
+
console.log(`Queued one manual run for cron job ${options.jobId}.`);
|
|
177
|
+
console.log("It will execute once without changing the job's enabled state or next scheduled run.");
|
|
184
178
|
console.log("Ensure the gateway process is running for execution.");
|
|
185
179
|
}
|
|
186
180
|
export async function runCronUpdateCommand(options) {
|
|
187
181
|
const context = await loadCronContext(options.cronConfigPath ? { cronConfigPath: options.cronConfigPath } : undefined);
|
|
188
182
|
const now = Date.now();
|
|
189
|
-
const hasScheduleUpdate = options.at !== undefined
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
...(options.everyMs !== undefined ? { everyMs: options.everyMs } : {}),
|
|
194
|
-
...(options.cronExpr ? { cronExpr: options.cronExpr } : {}),
|
|
195
|
-
...(options.tz ? { tz: options.tz } : {}),
|
|
196
|
-
})
|
|
197
|
-
: null;
|
|
183
|
+
const hasScheduleUpdate = options.at !== undefined
|
|
184
|
+
|| options.everyMs !== undefined
|
|
185
|
+
|| options.cronExpr !== undefined
|
|
186
|
+
|| options.tz !== undefined;
|
|
198
187
|
await context.store.updateJob(options.jobId, (current) => {
|
|
199
188
|
const updatedDelivery = {
|
|
200
189
|
connectorId: options.connectorId ?? current.delivery.connectorId,
|
|
@@ -209,14 +198,37 @@ export async function runCronUpdateCommand(options) {
|
|
|
209
198
|
connectorId: updatedDelivery.connectorId,
|
|
210
199
|
routeId: updatedDelivery.routeId,
|
|
211
200
|
});
|
|
212
|
-
|
|
213
|
-
|
|
201
|
+
let schedule = current.schedule;
|
|
202
|
+
if (hasScheduleUpdate) {
|
|
203
|
+
const hasExplicitScheduleVariant = options.at !== undefined
|
|
204
|
+
|| options.everyMs !== undefined
|
|
205
|
+
|| options.cronExpr !== undefined;
|
|
206
|
+
if (hasExplicitScheduleVariant) {
|
|
207
|
+
schedule = parseSchedule({
|
|
208
|
+
...(options.at ? { at: options.at } : {}),
|
|
209
|
+
...(options.everyMs !== undefined ? { everyMs: options.everyMs } : {}),
|
|
210
|
+
...(options.cronExpr ? { cronExpr: options.cronExpr } : {}),
|
|
211
|
+
...(options.tz ? { tz: options.tz } : {}),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
else if (options.tz !== undefined) {
|
|
215
|
+
if (current.schedule.kind !== "cron") {
|
|
216
|
+
throw new Error("--tz can only be updated for cron schedules or together with --cron");
|
|
217
|
+
}
|
|
218
|
+
schedule = {
|
|
219
|
+
kind: "cron",
|
|
220
|
+
expr: current.schedule.expr,
|
|
221
|
+
...(options.tz ? { tz: options.tz } : {}),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const nextRunAtMs = hasScheduleUpdate
|
|
214
226
|
? computeInitialNextRunAtMs(schedule, now)
|
|
215
227
|
: current.state.nextRunAtMs;
|
|
216
228
|
const nextState = {
|
|
217
229
|
...current.state,
|
|
218
230
|
};
|
|
219
|
-
if (
|
|
231
|
+
if (hasScheduleUpdate) {
|
|
220
232
|
if (nextRunAtMs === undefined) {
|
|
221
233
|
delete nextState.nextRunAtMs;
|
|
222
234
|
}
|
|
@@ -224,7 +236,6 @@ export async function runCronUpdateCommand(options) {
|
|
|
224
236
|
nextState.nextRunAtMs = nextRunAtMs;
|
|
225
237
|
}
|
|
226
238
|
}
|
|
227
|
-
const parsedSessionPolicy = parseSessionPolicy(options.sessionPolicy);
|
|
228
239
|
const nextJob = {
|
|
229
240
|
...current,
|
|
230
241
|
name: options.name ?? current.name,
|
|
@@ -234,13 +245,6 @@ export async function runCronUpdateCommand(options) {
|
|
|
234
245
|
updatedAtMs: now,
|
|
235
246
|
state: nextState,
|
|
236
247
|
};
|
|
237
|
-
const nextSessionPolicy = parsedSessionPolicy ?? current.sessionPolicy;
|
|
238
|
-
if (nextSessionPolicy !== undefined) {
|
|
239
|
-
nextJob.sessionPolicy = nextSessionPolicy;
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
delete nextJob.sessionPolicy;
|
|
243
|
-
}
|
|
244
248
|
return nextJob;
|
|
245
249
|
});
|
|
246
250
|
console.log(`Updated cron job ${options.jobId}`);
|