@clinebot/core 0.0.36 → 0.0.37
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/dist/ClineCore.d.ts +312 -3
- package/dist/ClineCore.d.ts.map +1 -1
- package/dist/account/cline-account-service.d.ts.map +1 -1
- package/dist/cron/cron-event-ingress.d.ts +38 -0
- package/dist/cron/cron-event-ingress.d.ts.map +1 -0
- package/dist/cron/cron-materializer.d.ts +36 -0
- package/dist/cron/cron-materializer.d.ts.map +1 -0
- package/dist/cron/cron-reconciler.d.ts +62 -0
- package/dist/cron/cron-reconciler.d.ts.map +1 -0
- package/dist/cron/cron-report-writer.d.ts +41 -0
- package/dist/cron/cron-report-writer.d.ts.map +1 -0
- package/dist/cron/cron-runner.d.ts +43 -0
- package/dist/cron/cron-runner.d.ts.map +1 -0
- package/dist/cron/cron-schema.d.ts +3 -0
- package/dist/cron/cron-schema.d.ts.map +1 -0
- package/dist/cron/cron-service.d.ts +57 -0
- package/dist/cron/cron-service.d.ts.map +1 -0
- package/dist/cron/cron-spec-parser.d.ts +27 -0
- package/dist/cron/cron-spec-parser.d.ts.map +1 -0
- package/dist/cron/cron-watcher.d.ts +23 -0
- package/dist/cron/cron-watcher.d.ts.map +1 -0
- package/dist/cron/scheduler.d.ts +3 -1
- package/dist/cron/scheduler.d.ts.map +1 -1
- package/dist/cron/sqlite-cron-store.d.ts +230 -0
- package/dist/cron/sqlite-cron-store.d.ts.map +1 -0
- package/dist/extensions/plugin/plugin-config-loader.d.ts +7 -1
- package/dist/extensions/plugin/plugin-config-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-loader.d.ts +10 -6
- package/dist/extensions/plugin/plugin-loader.d.ts.map +1 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts +7 -1
- package/dist/extensions/plugin/plugin-sandbox.d.ts.map +1 -1
- package/dist/extensions/plugin-sandbox-bootstrap.js +236 -275
- package/dist/extensions/tools/constants.d.ts +1 -0
- package/dist/extensions/tools/constants.d.ts.map +1 -1
- package/dist/extensions/tools/definitions.d.ts +2 -3
- package/dist/extensions/tools/definitions.d.ts.map +1 -1
- package/dist/extensions/tools/executors/editor.d.ts.map +1 -1
- package/dist/extensions/tools/helpers.d.ts +1 -0
- package/dist/extensions/tools/helpers.d.ts.map +1 -1
- package/dist/extensions/tools/index.d.ts +1 -2
- package/dist/extensions/tools/index.d.ts.map +1 -1
- package/dist/extensions/tools/presets.d.ts +1 -1
- package/dist/extensions/tools/schemas.d.ts +25 -3
- package/dist/extensions/tools/schemas.d.ts.map +1 -1
- package/dist/extensions/tools/team/delegated-agent.d.ts +2 -2
- package/dist/extensions/tools/team/delegated-agent.d.ts.map +1 -1
- package/dist/extensions/tools/team/multi-agent.d.ts +7 -3
- package/dist/extensions/tools/team/multi-agent.d.ts.map +1 -1
- package/dist/extensions/tools/team/team-tools.d.ts.map +1 -1
- package/dist/extensions/tools/types.d.ts +0 -5
- package/dist/extensions/tools/types.d.ts.map +1 -1
- package/dist/hooks/hook-bridge.d.ts +118 -0
- package/dist/hooks/hook-bridge.d.ts.map +1 -0
- package/dist/hooks/hook-file-hooks.d.ts +2 -1
- package/dist/hooks/hook-file-hooks.d.ts.map +1 -1
- package/dist/hooks/hook-registry.d.ts +16 -0
- package/dist/hooks/hook-registry.d.ts.map +1 -0
- package/dist/hub/browser-websocket.d.ts.map +1 -1
- package/dist/hub/client.d.ts +7 -1
- package/dist/hub/client.d.ts.map +1 -1
- package/dist/hub/daemon-entry.js +721 -461
- package/dist/hub/daemon.d.ts.map +1 -1
- package/dist/hub/defaults.d.ts +8 -4
- package/dist/hub/defaults.d.ts.map +1 -1
- package/dist/hub/index.js +665 -415
- package/dist/hub/runtime-handlers.d.ts.map +1 -1
- package/dist/hub/server.d.ts +18 -0
- package/dist/hub/server.d.ts.map +1 -1
- package/dist/hub/session-client.d.ts +3 -0
- package/dist/hub/session-client.d.ts.map +1 -1
- package/dist/hub/start-shared-server.d.ts.map +1 -1
- package/dist/hub/ui-client.d.ts +1 -0
- package/dist/hub/ui-client.d.ts.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +756 -467
- package/dist/llms/cline-recommended-models.d.ts +20 -0
- package/dist/llms/cline-recommended-models.d.ts.map +1 -0
- package/dist/llms/handler-factory.d.ts +16 -0
- package/dist/llms/handler-factory.d.ts.map +1 -0
- package/dist/llms/provider-defaults.d.ts.map +1 -1
- package/dist/llms/provider-settings.d.ts +45 -2
- package/dist/llms/provider-settings.d.ts.map +1 -1
- package/dist/llms/runtime-registry.d.ts.map +1 -1
- package/dist/runtime/agent-config-adapter.d.ts +148 -0
- package/dist/runtime/agent-config-adapter.d.ts.map +1 -0
- package/dist/runtime/agent-runtime-config-builder.d.ts +96 -0
- package/dist/runtime/agent-runtime-config-builder.d.ts.map +1 -0
- package/dist/runtime/history.d.ts +6 -0
- package/dist/runtime/history.d.ts.map +1 -1
- package/dist/runtime/host.d.ts.map +1 -1
- package/dist/runtime/loop-detection.d.ts +59 -0
- package/dist/runtime/loop-detection.d.ts.map +1 -0
- package/dist/runtime/mistake-tracker.d.ts +69 -0
- package/dist/runtime/mistake-tracker.d.ts.map +1 -0
- package/dist/runtime/runtime-builder.d.ts.map +1 -1
- package/dist/runtime/runtime-event-adapter.d.ts +102 -0
- package/dist/runtime/runtime-event-adapter.d.ts.map +1 -0
- package/dist/runtime/runtime-host.d.ts +28 -3
- package/dist/runtime/runtime-host.d.ts.map +1 -1
- package/dist/runtime/session-runtime-orchestrator.d.ts +261 -0
- package/dist/runtime/session-runtime-orchestrator.d.ts.map +1 -0
- package/dist/runtime/session-runtime.d.ts +16 -3
- package/dist/runtime/session-runtime.d.ts.map +1 -1
- package/dist/runtime/user-input-builder.d.ts +24 -0
- package/dist/runtime/user-input-builder.d.ts.map +1 -0
- package/dist/services/index.js +28 -0
- package/dist/services/local-runtime-bootstrap.d.ts.map +1 -1
- package/dist/services/plugin-tools.d.ts.map +1 -1
- package/dist/services/providers/local-provider-registry.d.ts +197 -21
- package/dist/services/providers/local-provider-registry.d.ts.map +1 -1
- package/dist/services/providers/local-provider-service.d.ts +3 -1
- package/dist/services/providers/local-provider-service.d.ts.map +1 -1
- package/dist/services/session-data.d.ts.map +1 -1
- package/dist/services/session-telemetry.d.ts +7 -2
- package/dist/services/session-telemetry.d.ts.map +1 -1
- package/dist/services/storage/file-team-store.d.ts.map +1 -1
- package/dist/services/storage/provider-settings-legacy-migration.d.ts.map +1 -1
- package/dist/services/storage/provider-settings-manager.d.ts +1 -0
- package/dist/services/storage/provider-settings-manager.d.ts.map +1 -1
- package/dist/services/storage/sqlite-team-store.d.ts.map +1 -1
- package/dist/session/conversation-store.d.ts +30 -0
- package/dist/session/conversation-store.d.ts.map +1 -0
- package/dist/session/message-builder.d.ts +65 -0
- package/dist/session/message-builder.d.ts.map +1 -0
- package/dist/session/session-manifest.d.ts +1 -1
- package/dist/transports/hub.d.ts +14 -3
- package/dist/transports/hub.d.ts.map +1 -1
- package/dist/transports/local.d.ts +14 -4
- package/dist/transports/local.d.ts.map +1 -1
- package/dist/transports/remote.d.ts.map +1 -1
- package/dist/types/chat-schema.d.ts +5 -5
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/events.d.ts +7 -6
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/provider-settings.d.ts +2 -2
- package/dist/types/provider-settings.d.ts.map +1 -1
- package/dist/types/session.d.ts +5 -2
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types.d.ts +4 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/ClineCore.ts +691 -6
- package/src/account/cline-account-service.ts +44 -6
- package/src/cron/cron-event-ingress.ts +357 -0
- package/src/cron/cron-materializer.ts +97 -0
- package/src/cron/cron-reconciler.ts +241 -0
- package/src/cron/cron-report-writer.ts +153 -0
- package/src/cron/cron-runner.ts +495 -0
- package/src/cron/cron-schema.ts +127 -0
- package/src/cron/cron-service.ts +163 -0
- package/src/cron/cron-spec-parser.ts +489 -0
- package/src/cron/cron-watcher.ts +102 -0
- package/src/cron/index.ts +10 -0
- package/src/cron/scheduler.ts +141 -6
- package/src/cron/sqlite-cron-store.ts +1286 -0
- package/src/extensions/plugin/plugin-config-loader.ts +21 -1
- package/src/extensions/plugin/plugin-loader.ts +25 -9
- package/src/extensions/plugin/plugin-sandbox-bootstrap.ts +151 -1
- package/src/extensions/plugin/plugin-sandbox.ts +131 -7
- package/src/extensions/tools/constants.ts +2 -0
- package/src/extensions/tools/definitions.ts +31 -22
- package/src/extensions/tools/executors/editor.ts +4 -3
- package/src/extensions/tools/helpers.ts +24 -0
- package/src/extensions/tools/index.ts +1 -2
- package/src/extensions/tools/presets.ts +1 -1
- package/src/extensions/tools/schemas.ts +13 -18
- package/src/extensions/tools/team/delegated-agent.ts +8 -3
- package/src/extensions/tools/team/multi-agent.ts +135 -19
- package/src/extensions/tools/team/team-tools.ts +151 -91
- package/src/extensions/tools/types.ts +0 -6
- package/src/hooks/hook-bridge.ts +489 -0
- package/src/hooks/hook-file-hooks.ts +58 -3
- package/src/hooks/hook-registry.ts +257 -0
- package/src/hub/browser-websocket.ts +26 -4
- package/src/hub/client.ts +72 -13
- package/src/hub/daemon-entry.ts +35 -0
- package/src/hub/daemon.ts +117 -14
- package/src/hub/defaults.ts +39 -12
- package/src/hub/runtime-handlers.ts +4 -3
- package/src/hub/server.ts +506 -77
- package/src/hub/session-client.ts +43 -1
- package/src/hub/start-shared-server.ts +3 -0
- package/src/hub/ui-client.ts +4 -0
- package/src/index.ts +46 -1
- package/src/llms/cline-recommended-models.ts +167 -0
- package/src/llms/handler-factory.ts +56 -0
- package/src/llms/provider-defaults.ts +17 -1
- package/src/llms/provider-settings.ts +48 -1
- package/src/llms/runtime-registry.ts +1 -0
- package/src/runtime/agent-config-adapter.ts +636 -0
- package/src/runtime/agent-runtime-config-builder.ts +205 -0
- package/src/runtime/error-feedback.ts +142 -0
- package/src/runtime/history.ts +137 -0
- package/src/runtime/host.ts +22 -0
- package/src/runtime/loop-detection.ts +162 -0
- package/src/runtime/mistake-tracker.ts +221 -0
- package/src/runtime/runtime-builder.ts +61 -5
- package/src/runtime/runtime-event-adapter.ts +412 -0
- package/src/runtime/runtime-host.ts +45 -1
- package/src/runtime/session-runtime-orchestrator.ts +1253 -0
- package/src/runtime/session-runtime.ts +16 -2
- package/src/runtime/user-input-builder.ts +167 -0
- package/src/services/local-runtime-bootstrap.ts +128 -22
- package/src/services/plugin-tools.ts +1 -0
- package/src/services/providers/local-provider-registry.ts +273 -57
- package/src/services/providers/local-provider-service.ts +67 -7
- package/src/services/session-data.ts +16 -14
- package/src/services/session-telemetry.ts +6 -15
- package/src/services/storage/file-team-store.ts +1 -5
- package/src/services/storage/provider-settings-legacy-migration.ts +8 -47
- package/src/services/storage/provider-settings-manager.ts +16 -1
- package/src/services/storage/sqlite-team-store.ts +1 -5
- package/src/session/conversation-store.ts +77 -0
- package/src/session/message-builder.ts +941 -0
- package/src/transports/hub.ts +458 -33
- package/src/transports/local.ts +296 -65
- package/src/transports/remote.ts +1 -0
- package/src/types/config.ts +9 -0
- package/src/types/events.ts +8 -6
- package/src/types/index.ts +3 -0
- package/src/types/provider-settings.ts +8 -1
- package/src/types/session.ts +5 -2
- package/src/types.ts +15 -1
- package/dist/cron/index.d.ts +0 -6
- package/dist/cron/index.d.ts.map +0 -1
- package/dist/services/telemetry/index.js +0 -28
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { relative } from "node:path";
|
|
3
|
+
import type { CronSpecParseResult } from "@clinebot/shared";
|
|
4
|
+
import {
|
|
5
|
+
type ResolveCronSpecsDirOptions,
|
|
6
|
+
resolveCronSpecsDir,
|
|
7
|
+
} from "@clinebot/shared/storage";
|
|
8
|
+
import { type ParseCronSpecInput, parseCronSpecFile } from "./cron-spec-parser";
|
|
9
|
+
import { getNextCronTime } from "./scheduler";
|
|
10
|
+
import type {
|
|
11
|
+
CronSpecRecord,
|
|
12
|
+
SqliteCronStore,
|
|
13
|
+
UpsertSpecResult,
|
|
14
|
+
} from "./sqlite-cron-store";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Scan the configured cron specs directory on disk, parse every file, and
|
|
18
|
+
* upsert spec state into the cron DB. This is the startup source of truth:
|
|
19
|
+
* watcher events are triggers to re-run reconciliation for one file, not a
|
|
20
|
+
* replacement.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export interface CronReconcilerOptions {
|
|
24
|
+
store: SqliteCronStore;
|
|
25
|
+
/**
|
|
26
|
+
* Cron spec source location. Defaults to global `~/.cline/cron`.
|
|
27
|
+
* Pass `{ scope: "workspace", workspaceRoot }` later to enable
|
|
28
|
+
* workspace-level cron sources without changing reconciler internals.
|
|
29
|
+
*/
|
|
30
|
+
specs?: ResolveCronSpecsDirOptions;
|
|
31
|
+
/** @deprecated Use `specs: { scope: "workspace", workspaceRoot }`. */
|
|
32
|
+
workspaceRoot?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ReconcileChange {
|
|
36
|
+
relativePath: string;
|
|
37
|
+
result: UpsertSpecResult;
|
|
38
|
+
parse: CronSpecParseResult;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ReconcileSummary {
|
|
42
|
+
scanned: number;
|
|
43
|
+
upserted: number;
|
|
44
|
+
invalidParses: number;
|
|
45
|
+
removed: number;
|
|
46
|
+
changes: ReconcileChange[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function toPosixRelative(fromDir: string, absolutePath: string): string {
|
|
50
|
+
return relative(fromDir, absolutePath).replace(/\\/g, "/");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function walk(dir: string): string[] {
|
|
54
|
+
if (!existsSync(dir)) return [];
|
|
55
|
+
const result: string[] = [];
|
|
56
|
+
const stack: string[] = [dir];
|
|
57
|
+
while (stack.length > 0) {
|
|
58
|
+
const current = stack.pop();
|
|
59
|
+
if (!current) continue;
|
|
60
|
+
let entries: import("node:fs").Dirent[];
|
|
61
|
+
try {
|
|
62
|
+
entries = readdirSync(current, { withFileTypes: true });
|
|
63
|
+
} catch {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
const full = `${current}/${entry.name}`;
|
|
68
|
+
if (entry.isDirectory()) {
|
|
69
|
+
// Skip the generated reports subdirectory — not spec sources.
|
|
70
|
+
if (entry.name === "reports") continue;
|
|
71
|
+
stack.push(full);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (!entry.isFile()) continue;
|
|
75
|
+
if (!entry.name.endsWith(".md")) continue;
|
|
76
|
+
result.push(full);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export class CronReconciler {
|
|
83
|
+
private readonly store: SqliteCronStore;
|
|
84
|
+
private readonly cronDir: string;
|
|
85
|
+
|
|
86
|
+
constructor(options: CronReconcilerOptions) {
|
|
87
|
+
this.store = options.store;
|
|
88
|
+
this.cronDir = resolveCronSpecsDir(
|
|
89
|
+
options.specs ??
|
|
90
|
+
(options.workspaceRoot
|
|
91
|
+
? { scope: "workspace", workspaceRoot: options.workspaceRoot }
|
|
92
|
+
: undefined),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public getCronDir(): string {
|
|
97
|
+
return this.cronDir;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Reconcile every file under the cron specs directory into the DB and mark specs
|
|
102
|
+
* whose source files no longer exist as `removed=1`.
|
|
103
|
+
*/
|
|
104
|
+
public async reconcileAll(): Promise<ReconcileSummary> {
|
|
105
|
+
const summary: ReconcileSummary = {
|
|
106
|
+
scanned: 0,
|
|
107
|
+
upserted: 0,
|
|
108
|
+
invalidParses: 0,
|
|
109
|
+
removed: 0,
|
|
110
|
+
changes: [],
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const files = walk(this.cronDir);
|
|
114
|
+
const seenPaths = new Set<string>();
|
|
115
|
+
|
|
116
|
+
for (const abs of files) {
|
|
117
|
+
const rel = toPosixRelative(this.cronDir, abs);
|
|
118
|
+
seenPaths.add(rel);
|
|
119
|
+
summary.scanned += 1;
|
|
120
|
+
const change = await this.reconcileFile(rel, abs);
|
|
121
|
+
if (change) {
|
|
122
|
+
summary.changes.push(change);
|
|
123
|
+
summary.upserted += 1;
|
|
124
|
+
if (change.parse.error) summary.invalidParses += 1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const existing = this.store.listSpecs({
|
|
129
|
+
includeRemoved: false,
|
|
130
|
+
limit: 10_000,
|
|
131
|
+
});
|
|
132
|
+
for (const spec of existing) {
|
|
133
|
+
if (!seenPaths.has(spec.sourcePath)) {
|
|
134
|
+
this.handleFileDeleted(spec);
|
|
135
|
+
summary.removed += 1;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.refreshScheduleNextRunAt();
|
|
140
|
+
|
|
141
|
+
return summary;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Reconcile a single file (absolute path). `relativePath` is expected to
|
|
146
|
+
* be POSIX-relative to the cron specs directory. Returns the reconciliation change
|
|
147
|
+
* or undefined if the file could not be read.
|
|
148
|
+
*/
|
|
149
|
+
public async reconcileFile(
|
|
150
|
+
relativePath: string,
|
|
151
|
+
absolutePath: string,
|
|
152
|
+
): Promise<ReconcileChange | undefined> {
|
|
153
|
+
const existing = this.store.getSpecBySourcePath(relativePath);
|
|
154
|
+
let raw: string;
|
|
155
|
+
let mtimeMs: number | undefined;
|
|
156
|
+
try {
|
|
157
|
+
raw = readFileSync(absolutePath, "utf8");
|
|
158
|
+
mtimeMs = statSync(absolutePath).mtimeMs;
|
|
159
|
+
} catch {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
const input: ParseCronSpecInput = { relativePath, raw };
|
|
163
|
+
const parse = parseCronSpecFile(input);
|
|
164
|
+
const result = this.store.upsertSpec({
|
|
165
|
+
externalId: parse.externalId,
|
|
166
|
+
sourcePath: relativePath,
|
|
167
|
+
triggerKind: parse.triggerKind,
|
|
168
|
+
sourceMtimeMs: mtimeMs,
|
|
169
|
+
sourceHash: parse.contentHash,
|
|
170
|
+
parseStatus: parse.error ? "invalid" : "valid",
|
|
171
|
+
parseError: parse.error,
|
|
172
|
+
spec: parse.spec,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// For valid schedule specs, compute next_run_at if missing or reset
|
|
176
|
+
// when schedule_expr changed.
|
|
177
|
+
if (
|
|
178
|
+
!parse.error &&
|
|
179
|
+
parse.triggerKind === "schedule" &&
|
|
180
|
+
result.record.enabled
|
|
181
|
+
) {
|
|
182
|
+
this.applyScheduleNextRunAt(result.record, {
|
|
183
|
+
forceReset:
|
|
184
|
+
!existing ||
|
|
185
|
+
existing.removed ||
|
|
186
|
+
!existing.enabled ||
|
|
187
|
+
existing.scheduleExpr !== result.record.scheduleExpr ||
|
|
188
|
+
existing.timezone !== result.record.timezone,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { relativePath, result, parse };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Handle a file that disappeared from disk between reconciliations.
|
|
197
|
+
* Marks the spec as removed and cancels any queued runs for it.
|
|
198
|
+
*/
|
|
199
|
+
public handleFileDeleted(spec: CronSpecRecord): void {
|
|
200
|
+
this.store.markSpecRemoved(spec.specId);
|
|
201
|
+
this.store.cancelQueuedRunsForSpec(spec.specId);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Refresh next_run_at for every enabled schedule spec.
|
|
206
|
+
* Used at startup to handle the "one overdue catch-up on startup then
|
|
207
|
+
* advance to next slot" policy.
|
|
208
|
+
*/
|
|
209
|
+
public refreshScheduleNextRunAt(): void {
|
|
210
|
+
const schedules = this.store.listSpecs({
|
|
211
|
+
triggerKind: "schedule",
|
|
212
|
+
enabled: true,
|
|
213
|
+
parseStatus: "valid",
|
|
214
|
+
});
|
|
215
|
+
for (const spec of schedules) {
|
|
216
|
+
this.applyScheduleNextRunAt(spec, { forceReset: false });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private applyScheduleNextRunAt(
|
|
221
|
+
spec: CronSpecRecord,
|
|
222
|
+
options: { forceReset: boolean },
|
|
223
|
+
): void {
|
|
224
|
+
if (!spec.scheduleExpr) return;
|
|
225
|
+
if (!options.forceReset && spec.nextRunAt) return;
|
|
226
|
+
try {
|
|
227
|
+
const now = Date.now();
|
|
228
|
+
const base = spec.lastRunAt
|
|
229
|
+
? Math.max(now, new Date(spec.lastRunAt).getTime())
|
|
230
|
+
: now;
|
|
231
|
+
const nextMs = getNextCronTime(spec.scheduleExpr, base, spec.timezone);
|
|
232
|
+
const nextIso = new Date(nextMs).toISOString();
|
|
233
|
+
if (spec.nextRunAt !== nextIso) {
|
|
234
|
+
this.store.updateSpecNextRunAt(spec.specId, nextIso);
|
|
235
|
+
}
|
|
236
|
+
} catch {
|
|
237
|
+
// Invalid cron pattern — leave next_run_at as is; the upsert's
|
|
238
|
+
// parse_status already reflects the spec correctness.
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
type ResolveCronSpecsDirOptions,
|
|
5
|
+
resolveCronReportsDir,
|
|
6
|
+
} from "@clinebot/shared/storage";
|
|
7
|
+
import type {
|
|
8
|
+
CronEventLogRecord,
|
|
9
|
+
CronRunRecord,
|
|
10
|
+
CronSpecRecord,
|
|
11
|
+
} from "./sqlite-cron-store";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Writes a markdown report for a completed or failed cron run.
|
|
15
|
+
* Reports live under `<cron-specs-dir>/reports/<run-id>.md`.
|
|
16
|
+
* By default that is `~/.cline/cron/reports/<run-id>.md`.
|
|
17
|
+
* and are derived artifacts — the database is still the operational source
|
|
18
|
+
* of truth.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export interface CronRunReportData {
|
|
22
|
+
finalText?: string;
|
|
23
|
+
usage?: {
|
|
24
|
+
inputTokens?: number;
|
|
25
|
+
outputTokens?: number;
|
|
26
|
+
cacheReadTokens?: number;
|
|
27
|
+
cacheWriteTokens?: number;
|
|
28
|
+
totalCost?: number;
|
|
29
|
+
};
|
|
30
|
+
toolCalls?: Array<{ name: string; error?: string; durationMs?: number }>;
|
|
31
|
+
durationMs?: number;
|
|
32
|
+
error?: string;
|
|
33
|
+
triggerEvent?: CronEventLogRecord;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface WriteReportOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Cron spec source/report location. Defaults to global `~/.cline/cron`.
|
|
39
|
+
* Pass `{ scope: "workspace", workspaceRoot }` to write reports beside a
|
|
40
|
+
* future workspace-scoped cron specs directory.
|
|
41
|
+
*/
|
|
42
|
+
specs?: ResolveCronSpecsDirOptions;
|
|
43
|
+
workspaceRoot: string;
|
|
44
|
+
run: CronRunRecord;
|
|
45
|
+
spec: CronSpecRecord;
|
|
46
|
+
data: CronRunReportData;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function escapeYamlString(value: string): string {
|
|
50
|
+
// Minimal escaping: wrap in quotes only when needed.
|
|
51
|
+
if (/[:#\n]/.test(value) || value.includes('"')) {
|
|
52
|
+
return JSON.stringify(value);
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function yamlEntry(key: string, value: string | undefined): string | undefined {
|
|
58
|
+
if (value === undefined || value === null) return undefined;
|
|
59
|
+
return `${key}: ${escapeYamlString(value)}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function buildFrontmatter(
|
|
63
|
+
run: CronRunRecord,
|
|
64
|
+
spec: CronSpecRecord,
|
|
65
|
+
triggerEvent?: CronEventLogRecord,
|
|
66
|
+
): string {
|
|
67
|
+
const entries: string[] = [
|
|
68
|
+
`runId: ${escapeYamlString(run.runId)}`,
|
|
69
|
+
`specId: ${escapeYamlString(spec.specId)}`,
|
|
70
|
+
`externalId: ${escapeYamlString(spec.externalId)}`,
|
|
71
|
+
`title: ${escapeYamlString(spec.title)}`,
|
|
72
|
+
`triggerKind: ${escapeYamlString(run.triggerKind)}`,
|
|
73
|
+
`status: ${escapeYamlString(run.status)}`,
|
|
74
|
+
`sourcePath: ${escapeYamlString(spec.sourcePath)}`,
|
|
75
|
+
];
|
|
76
|
+
const optional = [
|
|
77
|
+
yamlEntry("sessionId", run.sessionId),
|
|
78
|
+
yamlEntry("startedAt", run.startedAt),
|
|
79
|
+
yamlEntry("completedAt", run.completedAt),
|
|
80
|
+
yamlEntry("triggerEventId", run.triggerEventId),
|
|
81
|
+
yamlEntry("triggerEventType", triggerEvent?.eventType),
|
|
82
|
+
yamlEntry("triggerEventSource", triggerEvent?.source),
|
|
83
|
+
yamlEntry("triggerEventSubject", triggerEvent?.subject),
|
|
84
|
+
];
|
|
85
|
+
for (const entry of optional) if (entry) entries.push(entry);
|
|
86
|
+
return `---\n${entries.join("\n")}\n---\n`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildBody(data: CronRunReportData): string {
|
|
90
|
+
const sections: string[] = [];
|
|
91
|
+
if (data.triggerEvent) {
|
|
92
|
+
const event = data.triggerEvent;
|
|
93
|
+
const lines = [
|
|
94
|
+
`- eventId: ${event.eventId}`,
|
|
95
|
+
`- eventType: ${event.eventType}`,
|
|
96
|
+
`- source: ${event.source}`,
|
|
97
|
+
event.subject ? `- subject: ${event.subject}` : "",
|
|
98
|
+
`- occurredAt: ${event.occurredAt}`,
|
|
99
|
+
event.dedupeKey ? `- dedupeKey: ${event.dedupeKey}` : "",
|
|
100
|
+
event.attributes
|
|
101
|
+
? `- attributes: ${JSON.stringify(event.attributes)}`
|
|
102
|
+
: "",
|
|
103
|
+
].filter((line) => line.length > 0);
|
|
104
|
+
sections.push(`## Trigger Event\n\n${lines.join("\n")}\n`);
|
|
105
|
+
}
|
|
106
|
+
if (data.error) {
|
|
107
|
+
sections.push(`## Error\n\n${data.error}\n`);
|
|
108
|
+
}
|
|
109
|
+
if (data.finalText && data.finalText.trim().length > 0) {
|
|
110
|
+
sections.push(`## Summary\n\n${data.finalText.trim()}\n`);
|
|
111
|
+
}
|
|
112
|
+
if (data.usage) {
|
|
113
|
+
const u = data.usage;
|
|
114
|
+
const lines = [
|
|
115
|
+
u.inputTokens !== undefined ? `- inputTokens: ${u.inputTokens}` : "",
|
|
116
|
+
u.outputTokens !== undefined ? `- outputTokens: ${u.outputTokens}` : "",
|
|
117
|
+
u.cacheReadTokens !== undefined
|
|
118
|
+
? `- cacheReadTokens: ${u.cacheReadTokens}`
|
|
119
|
+
: "",
|
|
120
|
+
u.cacheWriteTokens !== undefined
|
|
121
|
+
? `- cacheWriteTokens: ${u.cacheWriteTokens}`
|
|
122
|
+
: "",
|
|
123
|
+
u.totalCost !== undefined ? `- totalCostUsd: ${u.totalCost}` : "",
|
|
124
|
+
data.durationMs !== undefined ? `- durationMs: ${data.durationMs}` : "",
|
|
125
|
+
].filter((line) => line.length > 0);
|
|
126
|
+
if (lines.length > 0) {
|
|
127
|
+
sections.push(`## Usage\n\n${lines.join("\n")}\n`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (data.toolCalls && data.toolCalls.length > 0) {
|
|
131
|
+
const bullets = data.toolCalls.map((call) => {
|
|
132
|
+
const parts = [`- ${call.name}`];
|
|
133
|
+
if (call.durationMs !== undefined) parts.push(`(${call.durationMs}ms)`);
|
|
134
|
+
if (call.error) parts.push(`error: ${call.error}`);
|
|
135
|
+
return parts.join(" ");
|
|
136
|
+
});
|
|
137
|
+
sections.push(`## Tool Calls\n\n${bullets.join("\n")}\n`);
|
|
138
|
+
}
|
|
139
|
+
return sections.join("\n");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function writeCronRunReport(options: WriteReportOptions): string {
|
|
143
|
+
const dir = resolveCronReportsDir(options.specs);
|
|
144
|
+
mkdirSync(dir, { recursive: true });
|
|
145
|
+
const path = join(dir, `${options.run.runId}.md`);
|
|
146
|
+
const content = `${buildFrontmatter(
|
|
147
|
+
options.run,
|
|
148
|
+
options.spec,
|
|
149
|
+
options.data.triggerEvent,
|
|
150
|
+
)}\n${buildBody(options.data)}`;
|
|
151
|
+
writeFileSync(path, content, "utf8");
|
|
152
|
+
return path;
|
|
153
|
+
}
|