@clinebot/core 0.0.35 → 0.0.36
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 +1 -2
- package/dist/ClineCore.d.ts +53 -39
- package/dist/ClineCore.d.ts.map +1 -1
- package/dist/account/index.d.ts +1 -1
- package/dist/account/index.d.ts.map +1 -1
- package/dist/account/rpc.d.ts +6 -6
- package/dist/account/rpc.d.ts.map +1 -1
- package/dist/cron/index.d.ts +6 -0
- package/dist/cron/index.d.ts.map +1 -0
- package/dist/cron/resource-limiter.d.ts +9 -0
- package/dist/cron/resource-limiter.d.ts.map +1 -0
- package/dist/cron/schedule-command-service.d.ts +10 -0
- package/dist/cron/schedule-command-service.d.ts.map +1 -0
- package/dist/cron/schedule-service.d.ts +100 -0
- package/dist/cron/schedule-service.d.ts.map +1 -0
- package/dist/cron/scheduler.d.ts +66 -0
- package/dist/cron/scheduler.d.ts.map +1 -0
- package/dist/cron/sqlite-schedule-store.d.ts +52 -0
- package/dist/cron/sqlite-schedule-store.d.ts.map +1 -0
- package/dist/extensions/config/agent-config-loader.d.ts +4 -3
- package/dist/extensions/config/agent-config-loader.d.ts.map +1 -1
- package/dist/extensions/config/runtime-commands.d.ts +1 -0
- package/dist/extensions/config/runtime-commands.d.ts.map +1 -1
- package/dist/extensions/config/user-instruction-config-loader.d.ts +1 -0
- package/dist/extensions/config/user-instruction-config-loader.d.ts.map +1 -1
- package/dist/extensions/context/agentic-compaction.d.ts +2 -2
- package/dist/extensions/context/agentic-compaction.d.ts.map +1 -1
- package/dist/extensions/context/compaction-shared.d.ts +5 -4
- package/dist/extensions/context/compaction-shared.d.ts.map +1 -1
- package/dist/extensions/context/compaction.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-config-loader.d.ts +9 -2
- package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-loader.d.ts +5 -3
- package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-module-import.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts +15 -2
- package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-targeting.d.ts +7 -0
- package/dist/extensions/plugin/plugin-targeting.d.ts.map +1 -0
- package/dist/extensions/plugin-sandbox-bootstrap.js +211 -211
- package/dist/extensions/tools/definitions.d.ts +1 -1
- package/dist/extensions/tools/definitions.d.ts.map +1 -1
- package/dist/extensions/tools/executors/apply-patch.d.ts +3 -1
- package/dist/extensions/tools/executors/apply-patch.d.ts.map +1 -1
- package/dist/extensions/tools/executors/search.d.ts +1 -1
- package/dist/extensions/tools/executors/search.d.ts.map +1 -1
- package/dist/extensions/tools/index.d.ts +2 -0
- package/dist/extensions/tools/index.d.ts.map +1 -1
- package/dist/extensions/tools/presets.d.ts +26 -43
- package/dist/extensions/tools/presets.d.ts.map +1 -1
- package/dist/extensions/tools/runtime.d.ts +25 -0
- package/dist/extensions/tools/runtime.d.ts.map +1 -0
- package/dist/extensions/tools/schemas.d.ts.map +1 -1
- package/dist/extensions/tools/team/team-tools.d.ts +1 -0
- package/dist/extensions/tools/team/team-tools.d.ts.map +1 -1
- package/dist/hooks/hook-file-hooks.d.ts +4 -1
- package/dist/hooks/hook-file-hooks.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/subprocess.d.ts +8 -1
- package/dist/hooks/subprocess.d.ts.map +1 -1
- package/dist/hub/browser-websocket.d.ts +18 -0
- package/dist/hub/browser-websocket.d.ts.map +1 -0
- package/dist/hub/client.d.ts +45 -0
- package/dist/hub/client.d.ts.map +1 -0
- package/dist/hub/connect.d.ts +15 -0
- package/dist/hub/connect.d.ts.map +1 -0
- package/dist/hub/daemon-entry.d.ts +2 -0
- package/dist/hub/daemon-entry.d.ts.map +1 -0
- package/dist/hub/daemon-entry.js +1045 -0
- package/dist/hub/daemon.d.ts +5 -0
- package/dist/hub/daemon.d.ts.map +1 -0
- package/dist/hub/defaults.d.ts +13 -0
- package/dist/hub/defaults.d.ts.map +1 -0
- package/dist/hub/discovery.d.ts +29 -0
- package/dist/hub/discovery.d.ts.map +1 -0
- package/dist/hub/index.d.ts +15 -0
- package/dist/hub/index.d.ts.map +1 -0
- package/dist/hub/index.js +1044 -0
- package/dist/hub/native-transport.d.ts +17 -0
- package/dist/hub/native-transport.d.ts.map +1 -0
- package/dist/hub/runtime-handlers.d.ts +11 -0
- package/dist/hub/runtime-handlers.d.ts.map +1 -0
- package/dist/hub/server.d.ts +86 -0
- package/dist/hub/server.d.ts.map +1 -0
- package/dist/hub/session-client.d.ts +87 -0
- package/dist/hub/session-client.d.ts.map +1 -0
- package/dist/hub/start-shared-server.d.ts +19 -0
- package/dist/hub/start-shared-server.d.ts.map +1 -0
- package/dist/hub/transport.d.ts +8 -0
- package/dist/hub/transport.d.ts.map +1 -0
- package/dist/hub/ui-client.d.ts +44 -0
- package/dist/hub/ui-client.d.ts.map +1 -0
- package/dist/hub/workspace.d.ts +4 -0
- package/dist/hub/workspace.d.ts.map +1 -0
- package/dist/index.d.ts +26 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +498 -476
- package/dist/llms/configured-provider-registry.d.ts +28 -0
- package/dist/llms/configured-provider-registry.d.ts.map +1 -0
- package/dist/llms/provider-defaults.d.ts +27 -0
- package/dist/llms/provider-defaults.d.ts.map +1 -0
- package/dist/llms/provider-settings.d.ts +202 -0
- package/dist/llms/provider-settings.d.ts.map +1 -0
- package/dist/llms/runtime-config.d.ts +4 -0
- package/dist/llms/runtime-config.d.ts.map +1 -0
- package/dist/llms/runtime-registry.d.ts +20 -0
- package/dist/llms/runtime-registry.d.ts.map +1 -0
- package/dist/llms/runtime-types.d.ts +85 -0
- package/dist/llms/runtime-types.d.ts.map +1 -0
- package/dist/runtime/host.d.ts +1 -2
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/rules.d.ts +1 -0
- package/dist/runtime/rules.d.ts.map +1 -1
- package/dist/runtime/runtime-builder.d.ts.map +1 -1
- package/dist/runtime/runtime-host.d.ts +22 -24
- package/dist/runtime/runtime-host.d.ts.map +1 -1
- package/dist/runtime/runtime-oauth-token-manager.d.ts.map +1 -1
- package/dist/runtime/session-runtime.d.ts +1 -19
- package/dist/runtime/session-runtime.d.ts.map +1 -1
- package/dist/services/global-settings.d.ts +12 -0
- package/dist/services/global-settings.d.ts.map +1 -0
- package/dist/services/local-runtime-bootstrap.d.ts +9 -3
- package/dist/services/local-runtime-bootstrap.d.ts.map +1 -1
- package/dist/services/plugin-tools.d.ts +16 -0
- package/dist/services/plugin-tools.d.ts.map +1 -0
- package/dist/services/providers/local-provider-registry.d.ts +4 -4
- package/dist/services/providers/local-provider-registry.d.ts.map +1 -1
- package/dist/services/providers/local-provider-service.d.ts +13 -13
- package/dist/services/providers/local-provider-service.d.ts.map +1 -1
- package/dist/services/session-data.d.ts +1 -1
- package/dist/services/session-data.d.ts.map +1 -1
- package/dist/services/storage/provider-settings-legacy-migration.d.ts +1 -1
- package/dist/services/storage/provider-settings-legacy-migration.d.ts.map +1 -1
- package/dist/services/telemetry/index.js +28 -15
- package/dist/services/workspace-manifest.d.ts +11 -0
- package/dist/services/workspace-manifest.d.ts.map +1 -1
- package/dist/session/persistence-service.d.ts +11 -23
- package/dist/session/persistence-service.d.ts.map +1 -1
- package/dist/session/session-manifest-store.d.ts +22 -0
- package/dist/session/session-manifest-store.d.ts.map +1 -0
- package/dist/session/session-row.d.ts +93 -0
- package/dist/session/session-row.d.ts.map +1 -0
- package/dist/session/session-service.d.ts +2 -102
- package/dist/session/session-service.d.ts.map +1 -1
- package/dist/session/subagent-session-manager.d.ts +36 -0
- package/dist/session/subagent-session-manager.d.ts.map +1 -0
- package/dist/session/team-persistence-store.d.ts +24 -0
- package/dist/session/team-persistence-store.d.ts.map +1 -0
- package/dist/transports/hub.d.ts +47 -0
- package/dist/transports/hub.d.ts.map +1 -0
- package/dist/transports/local.d.ts +10 -6
- package/dist/transports/local.d.ts.map +1 -1
- package/dist/transports/remote.d.ts +10 -0
- package/dist/transports/remote.d.ts.map +1 -0
- package/dist/transports/runtime-host-support.d.ts +3 -2
- package/dist/transports/runtime-host-support.d.ts.map +1 -1
- package/dist/types/chat-schema.d.ts +10 -12
- package/dist/types/chat-schema.d.ts.map +1 -1
- package/dist/types/config.d.ts +8 -7
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/provider-settings.d.ts +4 -5
- package/dist/types/provider-settings.d.ts.map +1 -1
- package/dist/types/session.d.ts +2 -1
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types.d.ts +8 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +20 -6
- package/src/ClineCore.ts +68 -40
- package/src/account/index.ts +3 -3
- package/src/account/rpc.ts +12 -12
- package/src/cron/index.ts +5 -0
- package/src/cron/resource-limiter.ts +46 -0
- package/src/cron/schedule-command-service.ts +193 -0
- package/src/cron/schedule-service.ts +703 -0
- package/src/cron/scheduler.ts +637 -0
- package/src/cron/sqlite-schedule-store.ts +708 -0
- package/src/extensions/config/agent-config-loader.ts +17 -7
- package/src/extensions/config/runtime-commands.ts +6 -0
- package/src/extensions/config/user-instruction-config-loader.ts +1 -0
- package/src/extensions/context/agentic-compaction.ts +3 -3
- package/src/extensions/context/basic-compaction.ts +2 -2
- package/src/extensions/context/compaction-shared.ts +5 -4
- package/src/extensions/context/compaction.ts +3 -3
- package/src/extensions/plugin/plugin-config-loader.ts +17 -2
- package/src/extensions/plugin/plugin-loader.ts +48 -4
- package/src/extensions/plugin/plugin-module-import.ts +0 -2
- package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +93 -39
- package/src/extensions/plugin/plugin-sandbox.ts +47 -27
- package/src/extensions/plugin/plugin-targeting.ts +32 -0
- package/src/extensions/tools/definitions.ts +30 -49
- package/src/extensions/tools/executors/apply-patch.ts +69 -80
- package/src/extensions/tools/executors/search.ts +195 -3
- package/src/extensions/tools/index.ts +10 -0
- package/src/extensions/tools/presets.ts +31 -46
- package/src/extensions/tools/runtime.ts +261 -0
- package/src/extensions/tools/schemas.ts +4 -2
- package/src/extensions/tools/team/team-tools.ts +21 -0
- package/src/hooks/hook-file-hooks.ts +8 -2
- package/src/hooks/index.ts +0 -7
- package/src/hooks/subprocess-runner.ts +1 -1
- package/src/hooks/subprocess.ts +9 -0
- package/src/hub/browser-websocket.ts +137 -0
- package/src/hub/client.ts +574 -0
- package/src/hub/connect.ts +156 -0
- package/src/hub/daemon-entry.ts +87 -0
- package/src/hub/daemon.ts +181 -0
- package/src/hub/defaults.ts +43 -0
- package/src/hub/discovery.ts +247 -0
- package/src/hub/index.ts +14 -0
- package/src/hub/native-transport.ts +31 -0
- package/src/hub/runtime-handlers.ts +140 -0
- package/src/hub/server.ts +1888 -0
- package/src/hub/session-client.ts +460 -0
- package/src/hub/start-shared-server.ts +58 -0
- package/src/hub/transport.ts +14 -0
- package/src/hub/ui-client.ts +122 -0
- package/src/hub/workspace.ts +19 -0
- package/src/index.ts +124 -68
- package/src/llms/configured-provider-registry.ts +193 -0
- package/src/llms/provider-defaults.ts +637 -0
- package/src/llms/provider-settings.ts +263 -0
- package/src/llms/runtime-config.ts +43 -0
- package/src/llms/runtime-registry.ts +171 -0
- package/src/llms/runtime-types.ts +121 -0
- package/src/runtime/host.ts +107 -269
- package/src/runtime/index.ts +1 -0
- package/src/runtime/rules.ts +12 -0
- package/src/runtime/runtime-builder.ts +24 -8
- package/src/runtime/runtime-host.ts +89 -61
- package/src/runtime/runtime-oauth-token-manager.ts +11 -15
- package/src/runtime/session-runtime.ts +0 -24
- package/src/services/global-settings.ts +122 -0
- package/src/services/local-runtime-bootstrap.ts +51 -13
- package/src/services/plugin-tools.ts +85 -0
- package/src/services/providers/local-provider-registry.ts +6 -6
- package/src/services/providers/local-provider-service.ts +42 -37
- package/src/services/session-data.ts +15 -9
- package/src/services/storage/provider-settings-legacy-migration.ts +6 -4
- package/src/services/storage/provider-settings-manager.ts +1 -1
- package/src/services/workspace-manifest.ts +18 -0
- package/src/session/file-session-service.ts +1 -1
- package/src/session/index.ts +6 -27
- package/src/session/persistence-service.ts +119 -504
- package/src/session/session-manifest-store.ts +158 -0
- package/src/session/session-row.ts +199 -0
- package/src/session/session-service.ts +17 -376
- package/src/session/session-team-coordination.ts +1 -1
- package/src/session/subagent-session-manager.ts +397 -0
- package/src/session/team-persistence-store.ts +176 -0
- package/src/transports/hub.ts +656 -0
- package/src/transports/local.ts +135 -40
- package/src/transports/remote.ts +26 -0
- package/src/transports/runtime-host-support.ts +63 -9
- package/src/types/chat-schema.ts +4 -5
- package/src/types/config.ts +8 -7
- package/src/types/provider-settings.ts +11 -7
- package/src/types/session.ts +2 -4
- package/src/types.ts +27 -1
- package/dist/hooks/persistent.d.ts +0 -64
- package/dist/hooks/persistent.d.ts.map +0 -1
- package/dist/runtime/rpc-runtime-ensure.d.ts +0 -65
- package/dist/runtime/rpc-runtime-ensure.d.ts.map +0 -1
- package/dist/runtime/rpc-spawn-lease.d.ts +0 -8
- package/dist/runtime/rpc-spawn-lease.d.ts.map +0 -1
- package/dist/session/rpc-session-service.d.ts +0 -16
- package/dist/session/rpc-session-service.d.ts.map +0 -1
- package/dist/session/sqlite-rpc-session-backend.d.ts +0 -31
- package/dist/session/sqlite-rpc-session-backend.d.ts.map +0 -1
- package/dist/transports/rpc.d.ts +0 -51
- package/dist/transports/rpc.d.ts.map +0 -1
- package/src/ClineCore.test.ts +0 -226
- package/src/account/cline-account-service.test.ts +0 -185
- package/src/account/featurebase-token.test.ts +0 -175
- package/src/account/rpc.test.ts +0 -63
- package/src/auth/bounded-ttl-cache.test.ts +0 -38
- package/src/auth/client.test.ts +0 -69
- package/src/auth/cline.test.ts +0 -267
- package/src/auth/codex.test.ts +0 -170
- package/src/auth/oca.test.ts +0 -340
- package/src/auth/server.test.ts +0 -287
- package/src/auth/utils.test.ts +0 -128
- package/src/extensions/config/agent-config-loader.test.ts +0 -236
- package/src/extensions/config/hooks-config-loader.test.ts +0 -20
- package/src/extensions/config/runtime-commands.test.ts +0 -115
- package/src/extensions/config/unified-config-file-watcher.test.ts +0 -196
- package/src/extensions/config/user-instruction-config-loader.test.ts +0 -246
- package/src/extensions/context/compaction.test.ts +0 -483
- package/src/extensions/mcp/config-loader.test.ts +0 -238
- package/src/extensions/mcp/manager.test.ts +0 -105
- package/src/extensions/plugin/plugin-config-loader.test.ts +0 -184
- package/src/extensions/plugin/plugin-loader.test.ts +0 -292
- package/src/extensions/plugin/plugin-sandbox.test.ts +0 -423
- package/src/extensions/tools/definitions.test.ts +0 -780
- package/src/extensions/tools/executors/bash.test.ts +0 -87
- package/src/extensions/tools/executors/editor.test.ts +0 -35
- package/src/extensions/tools/executors/file-read.test.ts +0 -125
- package/src/extensions/tools/model-tool-routing.test.ts +0 -86
- package/src/extensions/tools/presets.test.ts +0 -70
- package/src/extensions/tools/team/multi-agent.lifecycle.test.ts +0 -455
- package/src/extensions/tools/team/spawn-agent-tool.test.ts +0 -381
- package/src/extensions/tools/team/team-tools.test.ts +0 -918
- package/src/hooks/checkpoint-hooks.test.ts +0 -168
- package/src/hooks/hook-file-hooks.test.ts +0 -311
- package/src/hooks/persistent.ts +0 -661
- package/src/runtime/history.test.ts +0 -114
- package/src/runtime/host.test.ts +0 -230
- package/src/runtime/rpc-runtime-ensure.test.ts +0 -123
- package/src/runtime/rpc-runtime-ensure.ts +0 -659
- package/src/runtime/rpc-spawn-lease.test.ts +0 -81
- package/src/runtime/rpc-spawn-lease.ts +0 -156
- package/src/runtime/runtime-builder.team-persistence.test.ts +0 -245
- package/src/runtime/runtime-builder.test.ts +0 -615
- package/src/runtime/runtime-oauth-token-manager.test.ts +0 -137
- package/src/runtime/runtime-parity.test.ts +0 -143
- package/src/services/providers/local-provider-service.test.ts +0 -1062
- package/src/services/session-data.test.ts +0 -160
- package/src/services/storage/provider-settings-legacy-migration.test.ts +0 -424
- package/src/services/storage/provider-settings-manager.test.ts +0 -191
- package/src/services/telemetry/OpenTelemetryAdapter.test.ts +0 -157
- package/src/services/telemetry/OpenTelemetryProvider.test.ts +0 -326
- package/src/services/telemetry/TelemetryLoggerSink.test.ts +0 -42
- package/src/services/telemetry/TelemetryService.test.ts +0 -134
- package/src/services/telemetry/distinct-id.test.ts +0 -57
- package/src/services/workspace/file-indexer.d.ts +0 -11
- package/src/services/workspace/file-indexer.test.ts +0 -156
- package/src/services/workspace/mention-enricher.test.ts +0 -106
- package/src/session/persistence-service.test.ts +0 -300
- package/src/session/rpc-session-service.ts +0 -114
- package/src/session/session-service.team-persistence.test.ts +0 -48
- package/src/session/sqlite-rpc-session-backend.ts +0 -301
- package/src/transports/local.e2e.test.ts +0 -380
- package/src/transports/local.test.ts +0 -2559
- package/src/transports/rpc.test.ts +0 -82
- package/src/transports/rpc.ts +0 -665
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type {
|
|
3
|
+
BasicLogger,
|
|
4
|
+
ChatRunTurnRequest,
|
|
5
|
+
ChatStartSessionArtifacts,
|
|
6
|
+
ChatStartSessionRequest,
|
|
7
|
+
HubScheduleCreateInput,
|
|
8
|
+
HubScheduleUpdateInput,
|
|
9
|
+
ScheduleExecutionRecord,
|
|
10
|
+
ScheduleExecutionStatus,
|
|
11
|
+
ScheduleRecord,
|
|
12
|
+
} from "@clinebot/shared";
|
|
13
|
+
import { nowIso } from "@clinebot/shared/db";
|
|
14
|
+
import { ResourceLimiter } from "./resource-limiter";
|
|
15
|
+
import { validateCronPattern } from "./scheduler";
|
|
16
|
+
import {
|
|
17
|
+
type ListScheduleExecutionsOptions,
|
|
18
|
+
type ListSchedulesOptions,
|
|
19
|
+
type ScheduleClaimRecord,
|
|
20
|
+
type ScheduleExecutionStats,
|
|
21
|
+
SqliteHubScheduleStore,
|
|
22
|
+
} from "./sqlite-schedule-store";
|
|
23
|
+
|
|
24
|
+
function toErrorMessage(error: unknown): string {
|
|
25
|
+
return error instanceof Error ? error.message : String(error);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type HubScheduleTurnResult = {
|
|
29
|
+
text: string;
|
|
30
|
+
usage?: {
|
|
31
|
+
inputTokens?: number;
|
|
32
|
+
outputTokens?: number;
|
|
33
|
+
cacheReadTokens?: number;
|
|
34
|
+
cacheWriteTokens?: number;
|
|
35
|
+
totalCost?: number;
|
|
36
|
+
};
|
|
37
|
+
inputTokens?: number;
|
|
38
|
+
outputTokens?: number;
|
|
39
|
+
iterations?: number;
|
|
40
|
+
finishReason?: string;
|
|
41
|
+
messages?: unknown[];
|
|
42
|
+
toolCalls?: Array<{
|
|
43
|
+
name: string;
|
|
44
|
+
input?: unknown;
|
|
45
|
+
output?: unknown;
|
|
46
|
+
error?: string;
|
|
47
|
+
durationMs?: number;
|
|
48
|
+
}>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function parseTurnMetrics(result: HubScheduleTurnResult): {
|
|
52
|
+
iterations?: number;
|
|
53
|
+
tokensUsed?: number;
|
|
54
|
+
costUsd?: number;
|
|
55
|
+
} {
|
|
56
|
+
const inputTokens =
|
|
57
|
+
typeof result.inputTokens === "number" ? result.inputTokens : undefined;
|
|
58
|
+
const outputTokens =
|
|
59
|
+
typeof result.outputTokens === "number" ? result.outputTokens : undefined;
|
|
60
|
+
return {
|
|
61
|
+
iterations:
|
|
62
|
+
typeof result.iterations === "number" ? result.iterations : undefined,
|
|
63
|
+
tokensUsed:
|
|
64
|
+
inputTokens !== undefined && outputTokens !== undefined
|
|
65
|
+
? inputTokens + outputTokens
|
|
66
|
+
: undefined,
|
|
67
|
+
costUsd:
|
|
68
|
+
typeof result.usage?.totalCost === "number"
|
|
69
|
+
? result.usage.totalCost
|
|
70
|
+
: undefined,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface AggregatedTurnMetrics {
|
|
75
|
+
iterations?: number;
|
|
76
|
+
tokensUsed?: number;
|
|
77
|
+
costUsd?: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function addTurnMetrics(
|
|
81
|
+
current: AggregatedTurnMetrics,
|
|
82
|
+
result: HubScheduleTurnResult,
|
|
83
|
+
): AggregatedTurnMetrics {
|
|
84
|
+
const next = { ...current };
|
|
85
|
+
const metrics = parseTurnMetrics(result);
|
|
86
|
+
if (typeof metrics.iterations === "number") {
|
|
87
|
+
next.iterations = (next.iterations ?? 0) + metrics.iterations;
|
|
88
|
+
}
|
|
89
|
+
if (typeof metrics.tokensUsed === "number") {
|
|
90
|
+
next.tokensUsed = (next.tokensUsed ?? 0) + metrics.tokensUsed;
|
|
91
|
+
}
|
|
92
|
+
if (typeof metrics.costUsd === "number") {
|
|
93
|
+
next.costUsd = (next.costUsd ?? 0) + metrics.costUsd;
|
|
94
|
+
}
|
|
95
|
+
return next;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function optionsOrDefault(value: number | undefined, fallback: number): number {
|
|
99
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function asPositiveSeconds(value: unknown, fallback: number): number {
|
|
103
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
104
|
+
return fallback;
|
|
105
|
+
}
|
|
106
|
+
return Math.max(1, Math.floor(value));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const DEFAULT_AUTONOMOUS_IDLE_TIMEOUT_SECONDS = 60;
|
|
110
|
+
const DEFAULT_AUTONOMOUS_POLL_INTERVAL_SECONDS = 5;
|
|
111
|
+
const AUTONOMOUS_IDLE_NOOP_TOKEN = "<idle-noop/>";
|
|
112
|
+
|
|
113
|
+
export interface ScheduleAutonomousOptions {
|
|
114
|
+
enabled?: boolean;
|
|
115
|
+
idleTimeoutSeconds?: number;
|
|
116
|
+
pollIntervalSeconds?: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getAutonomousOptions(
|
|
120
|
+
schedule: ScheduleRecord,
|
|
121
|
+
): ScheduleAutonomousOptions | undefined {
|
|
122
|
+
const metadata = schedule.metadata;
|
|
123
|
+
if (!metadata || typeof metadata !== "object") {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
const raw =
|
|
127
|
+
metadata.autonomous &&
|
|
128
|
+
typeof metadata.autonomous === "object" &&
|
|
129
|
+
!Array.isArray(metadata.autonomous)
|
|
130
|
+
? (metadata.autonomous as Record<string, unknown>)
|
|
131
|
+
: undefined;
|
|
132
|
+
if (!raw || raw.enabled !== true) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
enabled: true,
|
|
137
|
+
idleTimeoutSeconds: asPositiveSeconds(
|
|
138
|
+
raw.idleTimeoutSeconds,
|
|
139
|
+
DEFAULT_AUTONOMOUS_IDLE_TIMEOUT_SECONDS,
|
|
140
|
+
),
|
|
141
|
+
pollIntervalSeconds: asPositiveSeconds(
|
|
142
|
+
raw.pollIntervalSeconds,
|
|
143
|
+
DEFAULT_AUTONOMOUS_POLL_INTERVAL_SECONDS,
|
|
144
|
+
),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function buildSchedulePrompt(
|
|
149
|
+
schedule: ScheduleRecord,
|
|
150
|
+
autonomous: ScheduleAutonomousOptions | undefined,
|
|
151
|
+
): string {
|
|
152
|
+
if (!autonomous?.enabled) {
|
|
153
|
+
return schedule.prompt;
|
|
154
|
+
}
|
|
155
|
+
return `${schedule.prompt}
|
|
156
|
+
|
|
157
|
+
When you finish the immediate scheduled work, remain available for autonomous follow-up. During idle polling, inspect team mailbox and team tasks. Use team_task with action="list" to find ready unassigned work, claim it with team_task and action="claim", and resume execution when work exists. Reply exactly ${AUTONOMOUS_IDLE_NOOP_TOKEN} only when the poll finds no actionable work.`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function buildAutonomousPollPrompt(
|
|
161
|
+
autonomous: ScheduleAutonomousOptions,
|
|
162
|
+
): string {
|
|
163
|
+
return `Autonomous idle poll. Check team_read_mailbox for unread messages and use team_task with action="list" to find ready unassigned tasks. Claim and execute one task if actionable work exists. If there is nothing to do right now, reply exactly ${AUTONOMOUS_IDLE_NOOP_TOKEN} and nothing else. Poll cadence is ${autonomous.pollIntervalSeconds}s and the idle shutdown window is ${autonomous.idleTimeoutSeconds}s.`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function isAutonomousNoop(result: HubScheduleTurnResult): boolean {
|
|
167
|
+
return result.text?.trim() === AUTONOMOUS_IDLE_NOOP_TOKEN;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function sleep(ms: number): Promise<void> {
|
|
171
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
class TimeoutError extends Error {
|
|
175
|
+
constructor(message: string) {
|
|
176
|
+
super(message);
|
|
177
|
+
this.name = "TimeoutError";
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function withTimeout<T>(
|
|
182
|
+
promise: Promise<T>,
|
|
183
|
+
timeoutMs: number,
|
|
184
|
+
): Promise<T> {
|
|
185
|
+
if (timeoutMs <= 0) {
|
|
186
|
+
return await promise;
|
|
187
|
+
}
|
|
188
|
+
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
189
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
190
|
+
timeoutHandle = setTimeout(() => {
|
|
191
|
+
reject(new TimeoutError("scheduled execution timed out"));
|
|
192
|
+
}, timeoutMs);
|
|
193
|
+
});
|
|
194
|
+
try {
|
|
195
|
+
return await Promise.race([promise, timeoutPromise]);
|
|
196
|
+
} finally {
|
|
197
|
+
if (timeoutHandle) {
|
|
198
|
+
clearTimeout(timeoutHandle);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface HubScheduleRuntimeHandlers {
|
|
204
|
+
startSession(request: ChatStartSessionRequest): Promise<{
|
|
205
|
+
sessionId: string;
|
|
206
|
+
startResult?: ChatStartSessionArtifacts;
|
|
207
|
+
}>;
|
|
208
|
+
sendSession(
|
|
209
|
+
sessionId: string,
|
|
210
|
+
request: ChatRunTurnRequest,
|
|
211
|
+
): Promise<{
|
|
212
|
+
result: HubScheduleTurnResult;
|
|
213
|
+
}>;
|
|
214
|
+
abortSession(sessionId: string): Promise<{ applied: boolean }>;
|
|
215
|
+
stopSession(sessionId: string): Promise<{ applied: boolean }>;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface ActiveScheduledExecution {
|
|
219
|
+
executionId: string;
|
|
220
|
+
scheduleId: string;
|
|
221
|
+
sessionId: string;
|
|
222
|
+
startedAt: string;
|
|
223
|
+
timeoutAt?: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface HubScheduleServiceOptions {
|
|
227
|
+
runtimeHandlers: HubScheduleRuntimeHandlers;
|
|
228
|
+
eventPublisher?: (eventType: string, payload: unknown) => void;
|
|
229
|
+
logger?: BasicLogger;
|
|
230
|
+
sessionsDbPath?: string;
|
|
231
|
+
pollIntervalMs?: number;
|
|
232
|
+
globalMaxConcurrency?: number;
|
|
233
|
+
claimLeaseSeconds?: number;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export class HubScheduleService {
|
|
237
|
+
private readonly store: SqliteHubScheduleStore;
|
|
238
|
+
private readonly resourceLimiter: ResourceLimiter;
|
|
239
|
+
private readonly options: HubScheduleServiceOptions;
|
|
240
|
+
private readonly claimLeaseMs: number;
|
|
241
|
+
private readonly activeExecutions = new Map<
|
|
242
|
+
string,
|
|
243
|
+
ActiveScheduledExecution
|
|
244
|
+
>();
|
|
245
|
+
private timer: ReturnType<typeof setInterval> | undefined;
|
|
246
|
+
private started = false;
|
|
247
|
+
private ticking = false;
|
|
248
|
+
private disposed = false;
|
|
249
|
+
|
|
250
|
+
constructor(options: HubScheduleServiceOptions) {
|
|
251
|
+
this.options = options;
|
|
252
|
+
this.store = new SqliteHubScheduleStore({
|
|
253
|
+
sessionsDbPath: options.sessionsDbPath,
|
|
254
|
+
});
|
|
255
|
+
this.resourceLimiter = new ResourceLimiter(
|
|
256
|
+
options.globalMaxConcurrency ?? 10,
|
|
257
|
+
);
|
|
258
|
+
this.claimLeaseMs = Math.max(
|
|
259
|
+
5_000,
|
|
260
|
+
optionsOrDefault(options.claimLeaseSeconds, 90) * 1000,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
public async start(): Promise<void> {
|
|
265
|
+
if (this.disposed) {
|
|
266
|
+
throw new Error("HubScheduleService has been disposed");
|
|
267
|
+
}
|
|
268
|
+
if (this.started) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
this.started = true;
|
|
272
|
+
const intervalMs = Math.max(
|
|
273
|
+
5_000,
|
|
274
|
+
optionsOrDefault(this.options.pollIntervalMs, 30_000),
|
|
275
|
+
);
|
|
276
|
+
this.options.logger?.log("hub.schedule.started", {
|
|
277
|
+
pollIntervalMs: intervalMs,
|
|
278
|
+
});
|
|
279
|
+
await this.tick();
|
|
280
|
+
this.timer = setInterval(() => {
|
|
281
|
+
void this.tick();
|
|
282
|
+
}, intervalMs);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
public async stop(): Promise<void> {
|
|
286
|
+
const wasStarted = this.started;
|
|
287
|
+
this.started = false;
|
|
288
|
+
if (this.timer) {
|
|
289
|
+
clearInterval(this.timer);
|
|
290
|
+
this.timer = undefined;
|
|
291
|
+
}
|
|
292
|
+
if (wasStarted) {
|
|
293
|
+
this.options.logger?.log("hub.schedule.stopped", {});
|
|
294
|
+
}
|
|
295
|
+
const active = Array.from(this.activeExecutions.values());
|
|
296
|
+
await Promise.all(
|
|
297
|
+
active.map(async (execution) => {
|
|
298
|
+
try {
|
|
299
|
+
await this.options.runtimeHandlers.abortSession(execution.sessionId);
|
|
300
|
+
} catch {
|
|
301
|
+
// best effort
|
|
302
|
+
}
|
|
303
|
+
}),
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
public async dispose(): Promise<void> {
|
|
308
|
+
if (this.disposed) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
this.disposed = true;
|
|
312
|
+
await this.stop();
|
|
313
|
+
this.store.close();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
public createSchedule(input: HubScheduleCreateInput): ScheduleRecord {
|
|
317
|
+
validateCronPattern(input.cronPattern);
|
|
318
|
+
if (!input.workspaceRoot?.trim()) {
|
|
319
|
+
throw new Error("workspaceRoot is required for schedules");
|
|
320
|
+
}
|
|
321
|
+
return this.store.createSchedule(input);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
public getSchedule(scheduleId: string): ScheduleRecord | undefined {
|
|
325
|
+
return this.store.getSchedule(scheduleId);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
public listSchedules(options: ListSchedulesOptions = {}): ScheduleRecord[] {
|
|
329
|
+
return this.store.listSchedules(options);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
public updateSchedule(
|
|
333
|
+
scheduleId: string,
|
|
334
|
+
updates: HubScheduleUpdateInput,
|
|
335
|
+
): ScheduleRecord | undefined {
|
|
336
|
+
if (updates.cronPattern !== undefined) {
|
|
337
|
+
validateCronPattern(updates.cronPattern);
|
|
338
|
+
}
|
|
339
|
+
const current = this.store.getSchedule(scheduleId);
|
|
340
|
+
if (!current) {
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
const nextWorkspaceRoot =
|
|
344
|
+
updates.workspaceRoot !== undefined
|
|
345
|
+
? updates.workspaceRoot.trim()
|
|
346
|
+
: current.workspaceRoot;
|
|
347
|
+
const nextEnabled = updates.enabled ?? current.enabled;
|
|
348
|
+
if (nextEnabled && !nextWorkspaceRoot) {
|
|
349
|
+
throw new Error("workspaceRoot is required for enabled schedules");
|
|
350
|
+
}
|
|
351
|
+
return this.store.updateSchedule(scheduleId, {
|
|
352
|
+
...updates,
|
|
353
|
+
scheduleId,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
public deleteSchedule(scheduleId: string): boolean {
|
|
358
|
+
return this.store.deleteSchedule(scheduleId);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
public pauseSchedule(scheduleId: string): ScheduleRecord | undefined {
|
|
362
|
+
return this.updateSchedule(scheduleId, { scheduleId, enabled: false });
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
public resumeSchedule(scheduleId: string): ScheduleRecord | undefined {
|
|
366
|
+
return this.updateSchedule(scheduleId, { scheduleId, enabled: true });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
public async triggerScheduleNow(
|
|
370
|
+
scheduleId: string,
|
|
371
|
+
): Promise<ScheduleExecutionRecord | undefined> {
|
|
372
|
+
const schedule = this.store.getSchedule(scheduleId);
|
|
373
|
+
if (!schedule) {
|
|
374
|
+
return undefined;
|
|
375
|
+
}
|
|
376
|
+
return await this.executeSchedule(schedule, nowIso(), "manual");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
public listScheduleExecutions(
|
|
380
|
+
options: ListScheduleExecutionsOptions,
|
|
381
|
+
): ScheduleExecutionRecord[] {
|
|
382
|
+
return this.store.listExecutions(options);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
public getScheduleStats(scheduleId: string): ScheduleExecutionStats {
|
|
386
|
+
return this.store.getExecutionStats(scheduleId);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
public getActiveExecutions(): ActiveScheduledExecution[] {
|
|
390
|
+
return Array.from(this.activeExecutions.values());
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
public getUpcomingRuns(limit = 20): Array<{
|
|
394
|
+
scheduleId: string;
|
|
395
|
+
name: string;
|
|
396
|
+
nextRunAt: string;
|
|
397
|
+
}> {
|
|
398
|
+
return this.store.listUpcomingRuns(limit);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private async tick(): Promise<void> {
|
|
402
|
+
if (this.ticking) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
this.ticking = true;
|
|
406
|
+
try {
|
|
407
|
+
const claims = this.store.claimDueSchedules(nowIso(), this.claimLeaseMs);
|
|
408
|
+
await Promise.allSettled(
|
|
409
|
+
claims.map((claim) => this.executeClaimedSchedule(claim)),
|
|
410
|
+
);
|
|
411
|
+
} catch (error) {
|
|
412
|
+
const logger = this.options.logger;
|
|
413
|
+
if (logger) {
|
|
414
|
+
if (logger.error) {
|
|
415
|
+
logger.error("hub.schedule.tick.failed", { error });
|
|
416
|
+
} else {
|
|
417
|
+
logger.log("hub.schedule.tick.failed", {
|
|
418
|
+
error,
|
|
419
|
+
severity: "error",
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
} finally {
|
|
424
|
+
this.ticking = false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private async executeClaimedSchedule(
|
|
429
|
+
claim: ScheduleClaimRecord,
|
|
430
|
+
): Promise<void> {
|
|
431
|
+
const releaseLeaseHeartbeat = this.startClaimLeaseHeartbeat(claim);
|
|
432
|
+
try {
|
|
433
|
+
const result = await this.executeSchedule(
|
|
434
|
+
claim.schedule,
|
|
435
|
+
new Date(claim.triggeredAt).toISOString(),
|
|
436
|
+
"scheduled",
|
|
437
|
+
);
|
|
438
|
+
const completedAt = result.startedAt ?? claim.triggeredAt;
|
|
439
|
+
this.store.completeScheduleClaim(
|
|
440
|
+
claim.schedule.scheduleId,
|
|
441
|
+
claim.claimToken,
|
|
442
|
+
new Date(completedAt).toISOString(),
|
|
443
|
+
);
|
|
444
|
+
} finally {
|
|
445
|
+
releaseLeaseHeartbeat();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private startClaimLeaseHeartbeat(claim: ScheduleClaimRecord): () => void {
|
|
450
|
+
const heartbeatMs = Math.max(1_000, Math.floor(this.claimLeaseMs / 2));
|
|
451
|
+
const interval = setInterval(() => {
|
|
452
|
+
const leaseUntilAt = new Date(
|
|
453
|
+
Date.now() + this.claimLeaseMs,
|
|
454
|
+
).toISOString();
|
|
455
|
+
const renewed = this.store.renewScheduleClaim(
|
|
456
|
+
claim.schedule.scheduleId,
|
|
457
|
+
claim.claimToken,
|
|
458
|
+
leaseUntilAt,
|
|
459
|
+
);
|
|
460
|
+
if (!renewed) {
|
|
461
|
+
clearInterval(interval);
|
|
462
|
+
}
|
|
463
|
+
}, heartbeatMs);
|
|
464
|
+
return () => clearInterval(interval);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
private buildStartRequest(schedule: ScheduleRecord): ChatStartSessionRequest {
|
|
468
|
+
const workspaceRoot = schedule.workspaceRoot.trim();
|
|
469
|
+
const provider = schedule.modelSelection?.providerId?.trim();
|
|
470
|
+
const model = schedule.modelSelection?.modelId?.trim();
|
|
471
|
+
if (!workspaceRoot) {
|
|
472
|
+
throw new Error("schedule requires workspaceRoot");
|
|
473
|
+
}
|
|
474
|
+
if (!provider || !model) {
|
|
475
|
+
throw new Error(
|
|
476
|
+
"schedule requires modelSelection.providerId and modelSelection.modelId",
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
workspaceRoot,
|
|
481
|
+
cwd: schedule.cwd?.trim() || workspaceRoot,
|
|
482
|
+
provider,
|
|
483
|
+
model,
|
|
484
|
+
mode:
|
|
485
|
+
schedule.mode === "plan"
|
|
486
|
+
? "plan"
|
|
487
|
+
: schedule.mode === "yolo"
|
|
488
|
+
? "yolo"
|
|
489
|
+
: "act",
|
|
490
|
+
systemPrompt: schedule.systemPrompt,
|
|
491
|
+
maxIterations: schedule.maxIterations,
|
|
492
|
+
enableTools: schedule.runtimeOptions?.enableTools ?? true,
|
|
493
|
+
enableSpawn: schedule.runtimeOptions?.enableSpawn ?? true,
|
|
494
|
+
enableTeams: schedule.runtimeOptions?.enableTeams ?? true,
|
|
495
|
+
autoApproveTools: schedule.runtimeOptions?.autoApproveTools ?? true,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
private async executeSchedule(
|
|
500
|
+
schedule: ScheduleRecord,
|
|
501
|
+
triggeredAt: string,
|
|
502
|
+
trigger: "scheduled" | "manual",
|
|
503
|
+
): Promise<ScheduleExecutionRecord> {
|
|
504
|
+
const executionId = `exec_${randomUUID()}`;
|
|
505
|
+
const triggeredAtMs = new Date(triggeredAt).getTime();
|
|
506
|
+
const pending: ScheduleExecutionRecord = {
|
|
507
|
+
executionId,
|
|
508
|
+
scheduleId: schedule.scheduleId,
|
|
509
|
+
triggeredAt: triggeredAtMs,
|
|
510
|
+
status: "pending",
|
|
511
|
+
};
|
|
512
|
+
this.store.recordExecution(pending);
|
|
513
|
+
|
|
514
|
+
const acquired = this.resourceLimiter.acquire(
|
|
515
|
+
schedule.scheduleId,
|
|
516
|
+
executionId,
|
|
517
|
+
schedule.maxParallel ?? 1,
|
|
518
|
+
);
|
|
519
|
+
if (!acquired) {
|
|
520
|
+
const skipped: ScheduleExecutionRecord = {
|
|
521
|
+
...pending,
|
|
522
|
+
status: "failed",
|
|
523
|
+
endedAt: Date.now(),
|
|
524
|
+
errorMessage: "concurrency limit reached",
|
|
525
|
+
};
|
|
526
|
+
this.store.recordExecution(skipped);
|
|
527
|
+
return skipped;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
let sessionId: string | undefined;
|
|
531
|
+
let startedAt: number | undefined;
|
|
532
|
+
let timeoutAt: string | undefined;
|
|
533
|
+
let executionDeadlineMs: number | undefined;
|
|
534
|
+
|
|
535
|
+
try {
|
|
536
|
+
const startRequest = this.buildStartRequest(schedule);
|
|
537
|
+
const startResponse =
|
|
538
|
+
await this.options.runtimeHandlers.startSession(startRequest);
|
|
539
|
+
sessionId = startResponse.sessionId.trim();
|
|
540
|
+
if (!sessionId) {
|
|
541
|
+
throw new Error("runtime start returned empty sessionId");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
startedAt = Date.now();
|
|
545
|
+
timeoutAt =
|
|
546
|
+
typeof schedule.timeoutSeconds === "number" &&
|
|
547
|
+
schedule.timeoutSeconds > 0
|
|
548
|
+
? new Date(startedAt + schedule.timeoutSeconds * 1000).toISOString()
|
|
549
|
+
: undefined;
|
|
550
|
+
executionDeadlineMs = timeoutAt
|
|
551
|
+
? new Date(timeoutAt).getTime()
|
|
552
|
+
: undefined;
|
|
553
|
+
|
|
554
|
+
const runningState: ScheduleExecutionRecord = {
|
|
555
|
+
...pending,
|
|
556
|
+
sessionId,
|
|
557
|
+
startedAt,
|
|
558
|
+
status: "running",
|
|
559
|
+
};
|
|
560
|
+
this.store.recordExecution(runningState);
|
|
561
|
+
this.activeExecutions.set(executionId, {
|
|
562
|
+
executionId,
|
|
563
|
+
scheduleId: schedule.scheduleId,
|
|
564
|
+
sessionId,
|
|
565
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
566
|
+
timeoutAt,
|
|
567
|
+
});
|
|
568
|
+
this.publishEvent("schedule.execution.started", {
|
|
569
|
+
scheduleId: schedule.scheduleId,
|
|
570
|
+
executionId,
|
|
571
|
+
sessionId,
|
|
572
|
+
trigger,
|
|
573
|
+
triggeredAt,
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
const turnRequest: ChatRunTurnRequest = {
|
|
577
|
+
config: startRequest,
|
|
578
|
+
prompt: buildSchedulePrompt(schedule, getAutonomousOptions(schedule)),
|
|
579
|
+
};
|
|
580
|
+
const sendTurn = async (
|
|
581
|
+
request: ChatRunTurnRequest,
|
|
582
|
+
): Promise<HubScheduleTurnResult> => {
|
|
583
|
+
const sendPromise = this.options.runtimeHandlers.sendSession(
|
|
584
|
+
sessionId!,
|
|
585
|
+
request,
|
|
586
|
+
);
|
|
587
|
+
const timeoutMs = executionDeadlineMs
|
|
588
|
+
? Math.max(1, executionDeadlineMs - Date.now())
|
|
589
|
+
: 0;
|
|
590
|
+
const sendResult = await withTimeout(sendPromise, timeoutMs);
|
|
591
|
+
return sendResult.result;
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
let metrics = addTurnMetrics({}, await sendTurn(turnRequest));
|
|
595
|
+
const autonomous = getAutonomousOptions(schedule);
|
|
596
|
+
if (autonomous?.enabled) {
|
|
597
|
+
metrics = await this.runAutonomousIdleLoop({
|
|
598
|
+
sessionId,
|
|
599
|
+
startRequest,
|
|
600
|
+
autonomous,
|
|
601
|
+
metrics,
|
|
602
|
+
sendTurn,
|
|
603
|
+
executionDeadlineMs,
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const completed: ScheduleExecutionRecord = {
|
|
608
|
+
...runningState,
|
|
609
|
+
status: "success",
|
|
610
|
+
endedAt: Date.now(),
|
|
611
|
+
iterations: metrics.iterations,
|
|
612
|
+
tokensUsed: metrics.tokensUsed,
|
|
613
|
+
costUsd: metrics.costUsd,
|
|
614
|
+
};
|
|
615
|
+
this.store.recordExecution(completed);
|
|
616
|
+
this.publishEvent("schedule.execution.completed", {
|
|
617
|
+
scheduleId: schedule.scheduleId,
|
|
618
|
+
executionId,
|
|
619
|
+
sessionId,
|
|
620
|
+
status: completed.status,
|
|
621
|
+
durationMs:
|
|
622
|
+
completed.startedAt && completed.endedAt
|
|
623
|
+
? completed.endedAt - completed.startedAt
|
|
624
|
+
: undefined,
|
|
625
|
+
});
|
|
626
|
+
return completed;
|
|
627
|
+
} catch (error) {
|
|
628
|
+
const status: ScheduleExecutionStatus =
|
|
629
|
+
error instanceof TimeoutError ? "timeout" : "failed";
|
|
630
|
+
if (sessionId && status === "timeout") {
|
|
631
|
+
try {
|
|
632
|
+
await this.options.runtimeHandlers.abortSession(sessionId);
|
|
633
|
+
} catch {
|
|
634
|
+
// best effort
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const failed: ScheduleExecutionRecord = {
|
|
638
|
+
executionId,
|
|
639
|
+
scheduleId: schedule.scheduleId,
|
|
640
|
+
sessionId,
|
|
641
|
+
triggeredAt: triggeredAtMs,
|
|
642
|
+
startedAt,
|
|
643
|
+
endedAt: Date.now(),
|
|
644
|
+
status,
|
|
645
|
+
errorMessage: toErrorMessage(error),
|
|
646
|
+
};
|
|
647
|
+
this.store.recordExecution(failed);
|
|
648
|
+
this.publishEvent("schedule.execution.completed", {
|
|
649
|
+
scheduleId: schedule.scheduleId,
|
|
650
|
+
executionId,
|
|
651
|
+
sessionId,
|
|
652
|
+
status: failed.status,
|
|
653
|
+
errorMessage: failed.errorMessage,
|
|
654
|
+
});
|
|
655
|
+
return failed;
|
|
656
|
+
} finally {
|
|
657
|
+
if (sessionId) {
|
|
658
|
+
try {
|
|
659
|
+
await this.options.runtimeHandlers.stopSession(sessionId);
|
|
660
|
+
} catch {
|
|
661
|
+
// best effort
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
this.activeExecutions.delete(executionId);
|
|
665
|
+
this.resourceLimiter.release(schedule.scheduleId, executionId);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
private publishEvent(eventType: string, payload: unknown): void {
|
|
670
|
+
this.options.eventPublisher?.(eventType, payload);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
private async runAutonomousIdleLoop(options: {
|
|
674
|
+
sessionId: string;
|
|
675
|
+
startRequest: ChatStartSessionRequest;
|
|
676
|
+
autonomous: ScheduleAutonomousOptions;
|
|
677
|
+
metrics: AggregatedTurnMetrics;
|
|
678
|
+
sendTurn: (request: ChatRunTurnRequest) => Promise<HubScheduleTurnResult>;
|
|
679
|
+
executionDeadlineMs?: number;
|
|
680
|
+
}): Promise<AggregatedTurnMetrics> {
|
|
681
|
+
let metrics = options.metrics;
|
|
682
|
+
const idleDeadline =
|
|
683
|
+
Date.now() + options.autonomous.idleTimeoutSeconds! * 1000;
|
|
684
|
+
while (Date.now() < idleDeadline) {
|
|
685
|
+
if (
|
|
686
|
+
options.executionDeadlineMs !== undefined &&
|
|
687
|
+
Date.now() >= options.executionDeadlineMs
|
|
688
|
+
) {
|
|
689
|
+
throw new TimeoutError("scheduled execution timed out");
|
|
690
|
+
}
|
|
691
|
+
await sleep(options.autonomous.pollIntervalSeconds! * 1000);
|
|
692
|
+
const result = await options.sendTurn({
|
|
693
|
+
config: options.startRequest,
|
|
694
|
+
prompt: buildAutonomousPollPrompt(options.autonomous),
|
|
695
|
+
});
|
|
696
|
+
metrics = addTurnMetrics(metrics, result);
|
|
697
|
+
if (isAutonomousNoop(result)) {
|
|
698
|
+
break;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return metrics;
|
|
702
|
+
}
|
|
703
|
+
}
|