@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.
- package/README.md +20 -7
- package/dist/src/agent/event-forwarder.js +185 -16
- package/dist/src/cli/commands/cron.js +39 -35
- package/dist/src/cli/program.js +0 -6
- 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/package.json +9 -3
- package/.env.example +0 -8
- package/AGENTS.md +0 -267
- package/ROADMAP.md +0 -34
- package/config/cron.example.json +0 -9
- package/config/gateway.example.json +0 -132
- package/dist/plugins/connector-discord/src/mapper.js +0 -75
- 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/discord-mapper.test.js +0 -90
- package/dist/src/cli/tests/doctor.test.js +0 -252
- package/dist/src/cli/tests/init-catalog.test.js +0 -134
- package/dist/src/cli/tests/program-options.test.js +0 -78
- package/dist/src/cli/tests/routing-config.test.js +0 -254
- package/dist/src/core/tests/control-command.test.js +0 -17
- 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 -243
- 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 -345
- package/plugins/connector-discord/src/contribution.ts +0 -21
- package/plugins/connector-discord/src/mapper.ts +0 -101
- 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 -606
- 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 -331
- package/src/cli/commands/extension.ts +0 -207
- package/src/cli/commands/init.ts +0 -211
- package/src/cli/commands/start.ts +0 -223
- package/src/cli/commands/topology.ts +0 -415
- package/src/cli/index.ts +0 -9
- package/src/cli/program.ts +0 -314
- package/src/cli/shared/config-io.ts +0 -245
- package/src/cli/shared/config-mutators.ts +0 -470
- package/src/cli/shared/config-schema.ts +0 -228
- package/src/cli/shared/config-types.ts +0 -129
- package/src/cli/shared/configure-sections.ts +0 -595
- package/src/cli/shared/discord-config.ts +0 -14
- package/src/cli/shared/init-catalog.ts +0 -249
- package/src/cli/shared/local-extension-specs.ts +0 -108
- package/src/cli/shared/runtime.ts +0 -33
- package/src/cli/shared/schema-prompts.ts +0 -443
- 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/discord-mapper.test.ts +0 -128
- package/src/cli/tests/doctor.test.ts +0 -269
- package/src/cli/tests/init-catalog.test.ts +0 -144
- package/src/cli/tests/program-options.test.ts +0 -95
- package/src/cli/tests/routing-config.test.ts +0 -281
- package/src/core/control-command.ts +0 -12
- package/src/core/dedup-store.ts +0 -103
- package/src/core/gateway.ts +0 -609
- package/src/core/routing.ts +0 -404
- 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 -324
- 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/src/shared/dobby-repo.ts +0 -48
- 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`
|
|
@@ -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
|
## 快速开始
|
|
@@ -245,10 +246,6 @@ dobby cron remove <jobId>
|
|
|
245
246
|
- 未指定时默认使用 `host.builtin`
|
|
246
247
|
- 未匹配 binding 的入站消息会被直接忽略;仅 direct message 可回落到 `bindings.default`
|
|
247
248
|
|
|
248
|
-
当前代码还保留但未真正生效的字段:
|
|
249
|
-
|
|
250
|
-
- cron job 的 `sessionPolicy`
|
|
251
|
-
|
|
252
249
|
示例配置:
|
|
253
250
|
|
|
254
251
|
- gateway:[`config/gateway.example.json`](config/gateway.example.json)
|
|
@@ -278,6 +275,7 @@ dobby cron remove <jobId>
|
|
|
278
275
|
|
|
279
276
|
- `connector.discord`
|
|
280
277
|
- `provider.pi`
|
|
278
|
+
- `provider.codex-cli`
|
|
281
279
|
- `provider.claude-cli`
|
|
282
280
|
- `provider.claude`
|
|
283
281
|
- `sandbox.boxlite`
|
|
@@ -288,13 +286,28 @@ dobby cron remove <jobId>
|
|
|
288
286
|
- provider:`provider.pi`、`provider.claude-cli`
|
|
289
287
|
- connector:`connector.discord`、`connector.feishu`
|
|
290
288
|
|
|
291
|
-
`provider.claude` 与 sandbox 相关扩展需要手工安装和配置,例如:
|
|
289
|
+
`provider.codex-cli`、`provider.claude` 与 sandbox 相关扩展需要手工安装和配置,例如:
|
|
292
290
|
|
|
293
291
|
```bash
|
|
292
|
+
npm run start -- extension install @dobby.ai/provider-codex-cli --enable
|
|
294
293
|
npm run start -- extension install @dobby.ai/provider-claude --enable
|
|
295
294
|
npm run start -- extension install @dobby.ai/sandbox-core --enable
|
|
296
295
|
```
|
|
297
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
|
+
|
|
298
311
|
`--enable` 的行为:
|
|
299
312
|
|
|
300
313
|
- 把包写入 `extensions.allowList`
|
|
@@ -323,7 +336,7 @@ npm run start -- cron add daily-report \
|
|
|
323
336
|
|
|
324
337
|
说明:
|
|
325
338
|
|
|
326
|
-
- `cron run <jobId>`
|
|
339
|
+
- `cron run <jobId>` 会额外排队一次立即执行,不会恢复 paused 状态,也不会改写原有 `nextRunAtMs`
|
|
327
340
|
- 需要已有一个正在运行的 `dobby start`
|
|
328
341
|
- 当前 scheduled run 一律按 stateless / ephemeral 执行
|
|
329
342
|
|
|
@@ -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}`);
|
package/dist/src/cli/program.js
CHANGED
|
@@ -137,7 +137,6 @@ export function buildProgram() {
|
|
|
137
137
|
.requiredOption("--route <id>", "Route ID")
|
|
138
138
|
.requiredOption("--channel <id>", "Delivery channel/chat ID")
|
|
139
139
|
.option("--thread <id>", "Delivery thread ID")
|
|
140
|
-
.option("--session-policy <policy>", "Session policy: stateless|shared-session", "stateless")
|
|
141
140
|
.option("--at <iso>", "Run once at ISO timestamp")
|
|
142
141
|
.option("--every-ms <ms>", "Run at fixed interval in milliseconds")
|
|
143
142
|
.option("--cron <expr>", "Cron expression")
|
|
@@ -152,7 +151,6 @@ export function buildProgram() {
|
|
|
152
151
|
routeId: opts.route,
|
|
153
152
|
channelId: opts.channel,
|
|
154
153
|
...(typeof opts.thread === "string" ? { threadId: opts.thread } : {}),
|
|
155
|
-
sessionPolicy: opts.sessionPolicy,
|
|
156
154
|
...(typeof opts.at === "string" ? { at: opts.at } : {}),
|
|
157
155
|
...(parsedEveryMs !== null && Number.isFinite(parsedEveryMs) ? { everyMs: parsedEveryMs } : {}),
|
|
158
156
|
...(typeof opts.cron === "string" ? { cronExpr: opts.cron } : {}),
|
|
@@ -206,7 +204,6 @@ export function buildProgram() {
|
|
|
206
204
|
.option("--channel <id>", "Delivery channel/chat ID")
|
|
207
205
|
.option("--thread <id>", "Delivery thread ID")
|
|
208
206
|
.option("--clear-thread", "Unset delivery thread", false)
|
|
209
|
-
.option("--session-policy <policy>", "Session policy: stateless|shared-session")
|
|
210
207
|
.option("--at <iso>", "Run once at ISO timestamp")
|
|
211
208
|
.option("--every-ms <ms>", "Run at fixed interval in milliseconds")
|
|
212
209
|
.option("--cron <expr>", "Cron expression")
|
|
@@ -223,9 +220,6 @@ export function buildProgram() {
|
|
|
223
220
|
...(typeof opts.channel === "string" ? { channelId: opts.channel } : {}),
|
|
224
221
|
...(typeof opts.thread === "string" ? { threadId: opts.thread } : {}),
|
|
225
222
|
clearThread: Boolean(opts.clearThread),
|
|
226
|
-
...(typeof opts.sessionPolicy === "string"
|
|
227
|
-
? { sessionPolicy: opts.sessionPolicy }
|
|
228
|
-
: {}),
|
|
229
223
|
...(typeof opts.at === "string" ? { at: opts.at } : {}),
|
|
230
224
|
...(parsedEveryMs !== null && Number.isFinite(parsedEveryMs) ? { everyMs: parsedEveryMs } : {}),
|
|
231
225
|
...(typeof opts.cron === "string" ? { cronExpr: opts.cron } : {}),
|
package/dist/src/core/types.js
CHANGED
package/dist/src/cron/config.js
CHANGED
|
@@ -7,7 +7,7 @@ const rawCronConfigSchema = z.object({
|
|
|
7
7
|
storeFile: z.string().min(1).optional(),
|
|
8
8
|
runLogFile: z.string().min(1).optional(),
|
|
9
9
|
pollIntervalMs: z.number().int().positive().default(10_000),
|
|
10
|
-
maxConcurrentRuns: z.number().int().positive().default(
|
|
10
|
+
maxConcurrentRuns: z.number().int().positive().default(2),
|
|
11
11
|
runMissedOnStartup: z.boolean().default(true),
|
|
12
12
|
jobTimeoutMs: z.number().int().positive().default(10 * 60 * 1000),
|
|
13
13
|
});
|
|
@@ -45,7 +45,7 @@ function defaultCronConfigPayload() {
|
|
|
45
45
|
storeFile: "./data/state/cron-jobs.json",
|
|
46
46
|
runLogFile: "./data/state/cron-runs.jsonl",
|
|
47
47
|
pollIntervalMs: 10_000,
|
|
48
|
-
maxConcurrentRuns:
|
|
48
|
+
maxConcurrentRuns: 2,
|
|
49
49
|
runMissedOnStartup: true,
|
|
50
50
|
jobTimeoutMs: 10 * 60 * 1000,
|
|
51
51
|
};
|