@oharness/engine 0.5.1 → 0.7.0
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/agent/agent.d.ts +12 -0
- package/dist/agent/agent.js +3 -0
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/memory-backup.d.ts +2 -0
- package/dist/agent/memory-backup.js +9 -1
- package/dist/agent/memory-backup.js.map +1 -1
- package/dist/agent/memory-limits.js +8 -1
- package/dist/agent/memory-limits.js.map +1 -1
- package/dist/agent/memory-usage.d.ts +1 -1
- package/dist/agent/memory-usage.js +2 -3
- package/dist/agent/memory-usage.js.map +1 -1
- package/dist/agent/memory.d.ts +3 -0
- package/dist/agent/memory.js +6 -18
- package/dist/agent/memory.js.map +1 -1
- package/dist/agent/session.d.ts +9 -0
- package/dist/agent/session.js +14 -2
- package/dist/agent/session.js.map +1 -1
- package/dist/agent/settings.js +3 -2
- package/dist/agent/settings.js.map +1 -1
- package/dist/agent/store.d.ts +45 -4
- package/dist/agent/store.js +198 -25
- package/dist/agent/store.js.map +1 -1
- package/dist/artifacts/files.d.ts +52 -0
- package/dist/artifacts/files.js +169 -0
- package/dist/artifacts/files.js.map +1 -0
- package/dist/auth/store.js +3 -2
- package/dist/auth/store.js.map +1 -1
- package/dist/config-files.js +3 -2
- package/dist/config-files.js.map +1 -1
- package/dist/config.d.ts +6 -1
- package/dist/config.js +10 -2
- package/dist/config.js.map +1 -1
- package/dist/harness.d.ts +62 -1
- package/dist/harness.js +174 -12
- package/dist/harness.js.map +1 -1
- package/dist/paths.d.ts +5 -0
- package/dist/paths.js +21 -3
- package/dist/paths.js.map +1 -1
- package/dist/providers/builtin.d.ts +2 -0
- package/dist/providers/builtin.js +38 -24
- package/dist/providers/builtin.js.map +1 -1
- package/dist/providers/catalog.d.ts +5 -0
- package/dist/providers/catalog.js +32 -1
- package/dist/providers/catalog.js.map +1 -1
- package/dist/providers/catalog.json +241 -123
- package/dist/providers/discovery.js +17 -0
- package/dist/providers/discovery.js.map +1 -1
- package/dist/providers/models-dev.d.ts +89 -0
- package/dist/providers/models-dev.js +160 -0
- package/dist/providers/models-dev.js.map +1 -0
- package/dist/providers/models-dev.json +821 -0
- package/dist/providers/registry.d.ts +12 -0
- package/dist/providers/registry.js +33 -1
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/types.d.ts +21 -0
- package/dist/providers/types.js +16 -0
- package/dist/providers/types.js.map +1 -1
- package/dist/runtime-plugins/builtin/memory-migrate.d.ts +5 -21
- package/dist/runtime-plugins/builtin/memory-migrate.js +5 -125
- package/dist/runtime-plugins/builtin/memory-migrate.js.map +1 -1
- package/dist/runtime-plugins/builtin/memory.d.ts +3 -0
- package/dist/runtime-plugins/builtin/memory.js +12 -10
- package/dist/runtime-plugins/builtin/memory.js.map +1 -1
- package/dist/schedule/clock.d.ts +76 -0
- package/dist/schedule/clock.js +147 -0
- package/dist/schedule/clock.js.map +1 -0
- package/dist/schedule/cron.d.ts +9 -0
- package/dist/schedule/cron.js +18 -0
- package/dist/schedule/cron.js.map +1 -1
- package/dist/schedule/lock.d.ts +38 -0
- package/dist/schedule/lock.js +100 -0
- package/dist/schedule/lock.js.map +1 -0
- package/dist/schedule/runner.d.ts +67 -0
- package/dist/schedule/runner.js +100 -0
- package/dist/schedule/runner.js.map +1 -0
- package/dist/schedule/scheduler.d.ts +56 -15
- package/dist/schedule/scheduler.js +80 -107
- package/dist/schedule/scheduler.js.map +1 -1
- package/dist/storage/kv.js +3 -2
- package/dist/storage/kv.js.map +1 -1
- package/dist/storage/project.d.ts +26 -0
- package/dist/storage/project.js +74 -15
- package/dist/storage/project.js.map +1 -1
- package/dist/tools/builtin/routine.d.ts +31 -0
- package/dist/tools/builtin/routine.js +70 -0
- package/dist/tools/builtin/routine.js.map +1 -0
- package/dist/util/fs.d.ts +6 -0
- package/dist/util/fs.js +22 -0
- package/dist/util/fs.js.map +1 -0
- package/package.json +3 -3
|
@@ -3,15 +3,37 @@ import { appendFile, mkdir, readFile } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { ConfigError, errorMessage } from "../core/errors.js";
|
|
5
5
|
import { messageText } from "../core/types.js";
|
|
6
|
-
import { createLogger } from "../util/logger.js";
|
|
7
6
|
import { parseJson } from "../util/json.js";
|
|
8
7
|
import { nextRun, parseCron } from "./cron.js";
|
|
9
8
|
const MAX_OUTPUT_CHARS = 4_000;
|
|
10
9
|
/**
|
|
11
|
-
*
|
|
10
|
+
* Minutes a routine starts after its scheduled time, the same every time for
|
|
11
|
+
* the same routine.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* a
|
|
13
|
+
* "Every day at 9" is what most people write, and every copy firing at 9:00:00
|
|
14
|
+
* is a queue at the gateway at 9:00:00. A few minutes, fixed by the name so a
|
|
15
|
+
* routine keeps its own slot rather than wandering, and none at all for one
|
|
16
|
+
* that fires more than once an hour, where a few minutes is most of its
|
|
17
|
+
* interval.
|
|
18
|
+
*/
|
|
19
|
+
export function staggerMinutes(def, fields) {
|
|
20
|
+
if (fields.minute.size !== 1)
|
|
21
|
+
return 0;
|
|
22
|
+
let hash = 2166136261;
|
|
23
|
+
for (const char of def.name) {
|
|
24
|
+
hash ^= char.charCodeAt(0);
|
|
25
|
+
hash = Math.imul(hash, 16777619);
|
|
26
|
+
}
|
|
27
|
+
return (hash >>> 0) % 5;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Holds routines, runs them, and keeps their history.
|
|
31
|
+
*
|
|
32
|
+
* It does not watch the clock: RoutineClock does, for every project at once,
|
|
33
|
+
* and asks the scheduler of each routine's own project to run it.
|
|
34
|
+
*
|
|
35
|
+
* Each run gets its own session rather than appending to a long-lived one: a
|
|
36
|
+
* nightly job that shares context with every previous night would compact
|
|
15
37
|
* itself into uselessness within a week, and a failure would poison every
|
|
16
38
|
* subsequent run.
|
|
17
39
|
*/
|
|
@@ -20,8 +42,6 @@ export class Scheduler {
|
|
|
20
42
|
logger;
|
|
21
43
|
schedules = new Map();
|
|
22
44
|
runDir;
|
|
23
|
-
timer;
|
|
24
|
-
running = false;
|
|
25
45
|
/** Names currently executing, so a slow job cannot overlap itself. */
|
|
26
46
|
inFlight = new Set();
|
|
27
47
|
constructor(harness, definitions = [], opts = {}) {
|
|
@@ -50,11 +70,14 @@ export class Scheduler {
|
|
|
50
70
|
const out = [];
|
|
51
71
|
for (const { def, fields } of this.schedules.values()) {
|
|
52
72
|
const enabled = def.enabled !== false;
|
|
73
|
+
const next = enabled ? nextRun(fields)?.getTime() : undefined;
|
|
53
74
|
out.push({
|
|
54
75
|
name: def.name,
|
|
55
76
|
cron: def.cron,
|
|
56
77
|
enabled,
|
|
57
|
-
|
|
78
|
+
// When it will actually start, stagger included: the panel saying
|
|
79
|
+
// 9:00 for a run that begins at 9:03 is a small lie told daily.
|
|
80
|
+
nextRunAt: next === undefined ? undefined : next + staggerMinutes(def, fields) * 60_000,
|
|
58
81
|
lastRun: await this.lastRun(def.name),
|
|
59
82
|
});
|
|
60
83
|
}
|
|
@@ -73,62 +96,31 @@ export class Scheduler {
|
|
|
73
96
|
return this.execute(entry.def);
|
|
74
97
|
}
|
|
75
98
|
/**
|
|
76
|
-
*
|
|
99
|
+
* Run a definition in this harness, whether or not this scheduler holds it.
|
|
77
100
|
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
101
|
+
* For the clock, which watches routines across projects: it decides what is
|
|
102
|
+
* due, and each routine runs in the harness of the directory it names — the
|
|
103
|
+
* project's instructions, MCP servers and permission rules — rather than in
|
|
104
|
+
* whichever project happened to notice the minute arrive.
|
|
81
105
|
*/
|
|
82
|
-
|
|
83
|
-
if (this.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
let lastMinute = -1;
|
|
87
|
-
const tick = () => {
|
|
88
|
-
const now = new Date();
|
|
89
|
-
const minute = Math.floor(now.getTime() / 60_000);
|
|
90
|
-
// Only evaluate once per wall-clock minute, however often we wake.
|
|
91
|
-
if (minute === lastMinute)
|
|
92
|
-
return;
|
|
93
|
-
lastMinute = minute;
|
|
94
|
-
for (const { def, fields } of this.schedules.values()) {
|
|
95
|
-
if (def.enabled === false)
|
|
96
|
-
continue;
|
|
97
|
-
if (!matchesNow(fields, now))
|
|
98
|
-
continue;
|
|
99
|
-
if (this.inFlight.has(def.name)) {
|
|
100
|
-
// A run still going when the next occurrence arrives means the job
|
|
101
|
-
// is slower than its interval; skipping is safer than piling up.
|
|
102
|
-
this.logger.warn("skipping overlapping run", { schedule: def.name });
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
void this.execute(def);
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
this.timer = setInterval(tick, 30_000);
|
|
109
|
-
// Do not hold the process open on our account; the CLI daemon decides.
|
|
110
|
-
this.timer.unref?.();
|
|
111
|
-
tick();
|
|
112
|
-
}
|
|
113
|
-
stop() {
|
|
114
|
-
this.running = false;
|
|
115
|
-
if (this.timer)
|
|
116
|
-
clearInterval(this.timer);
|
|
117
|
-
this.timer = undefined;
|
|
118
|
-
}
|
|
119
|
-
/** Resolves when every in-flight run has finished. */
|
|
120
|
-
async drain() {
|
|
121
|
-
while (this.inFlight.size > 0) {
|
|
122
|
-
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
106
|
+
async runDefinition(def, meta = {}) {
|
|
107
|
+
if (this.inFlight.has(def.name)) {
|
|
108
|
+
this.logger.warn("skipping overlapping run", { schedule: def.name });
|
|
109
|
+
return undefined;
|
|
123
110
|
}
|
|
111
|
+
return this.execute(def, meta);
|
|
124
112
|
}
|
|
125
|
-
|
|
113
|
+
isRunning(name) {
|
|
114
|
+
return this.inFlight.has(name);
|
|
115
|
+
}
|
|
116
|
+
async execute(def, meta = {}) {
|
|
126
117
|
this.inFlight.add(def.name);
|
|
127
118
|
const startedAt = Date.now();
|
|
128
|
-
this.logger.info("running schedule", { schedule: def.name });
|
|
119
|
+
this.logger.info("running schedule", { schedule: def.name, ...meta });
|
|
129
120
|
const session = this.harness.createSession({
|
|
130
121
|
model: def.model,
|
|
131
|
-
title:
|
|
122
|
+
title: def.name,
|
|
123
|
+
routine: def.name,
|
|
132
124
|
});
|
|
133
125
|
const agent = this.harness.agent(session, {
|
|
134
126
|
// Nobody is at the keyboard to approve anything.
|
|
@@ -168,70 +160,51 @@ export class Scheduler {
|
|
|
168
160
|
costUsd: session.costUsd,
|
|
169
161
|
output: output.slice(0, MAX_OUTPUT_CHARS),
|
|
170
162
|
...(error ? { error } : {}),
|
|
163
|
+
...(meta.scheduledFor !== undefined ? { scheduledFor: meta.scheduledFor } : {}),
|
|
164
|
+
...(meta.catchUp ? { catchUp: true } : {}),
|
|
171
165
|
};
|
|
172
|
-
await this.
|
|
166
|
+
await appendRun(this.runDir, run).catch((err) => this.logger.warn("could not record the run", { error: String(err) }));
|
|
173
167
|
return run;
|
|
174
168
|
}
|
|
175
|
-
async recordRun(run) {
|
|
176
|
-
await mkdir(this.runDir, { recursive: true });
|
|
177
|
-
await appendFile(path.join(this.runDir, `${safeName(run.schedule)}.jsonl`), `${JSON.stringify(run)}\n`, "utf8");
|
|
178
|
-
}
|
|
179
169
|
async lastRun(name) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
catch {
|
|
185
|
-
return undefined;
|
|
186
|
-
}
|
|
187
|
-
const lines = raw.split("\n").filter((line) => line.trim() !== "");
|
|
188
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
189
|
-
const parsed = parseJson(lines[i]);
|
|
190
|
-
if (parsed.ok)
|
|
191
|
-
return parsed.value;
|
|
170
|
+
const runs = await readRuns(this.runDir, name);
|
|
171
|
+
for (let i = runs.length - 1; i >= 0; i--) {
|
|
172
|
+
if (!runs[i].skipped)
|
|
173
|
+
return runs[i];
|
|
192
174
|
}
|
|
193
175
|
return undefined;
|
|
194
176
|
}
|
|
195
|
-
/** Past runs, newest first. */
|
|
177
|
+
/** Past runs and skipped times, newest first. */
|
|
196
178
|
async history(name, limit = 20) {
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
raw = await readFile(path.join(this.runDir, `${safeName(name)}.jsonl`), "utf8");
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
return [];
|
|
203
|
-
}
|
|
204
|
-
const runs = [];
|
|
205
|
-
for (const line of raw.split("\n")) {
|
|
206
|
-
if (line.trim() === "")
|
|
207
|
-
continue;
|
|
208
|
-
const parsed = parseJson(line);
|
|
209
|
-
if (parsed.ok)
|
|
210
|
-
runs.push(parsed.value);
|
|
211
|
-
}
|
|
212
|
-
return runs.reverse().slice(0, limit);
|
|
179
|
+
return (await readRuns(this.runDir, name)).reverse().slice(0, limit);
|
|
213
180
|
}
|
|
214
181
|
}
|
|
182
|
+
/** Add one line to a routine's history. */
|
|
183
|
+
export async function appendRun(runDir, run) {
|
|
184
|
+
await mkdir(runDir, { recursive: true });
|
|
185
|
+
await appendFile(path.join(runDir, `${safeName(run.schedule)}.jsonl`), `${JSON.stringify(run)}\n`, "utf8");
|
|
186
|
+
}
|
|
187
|
+
/** A routine's history, oldest first. Lines that do not parse are passed over. */
|
|
188
|
+
export async function readRuns(runDir, name) {
|
|
189
|
+
let raw;
|
|
190
|
+
try {
|
|
191
|
+
raw = await readFile(path.join(runDir, `${safeName(name)}.jsonl`), "utf8");
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
const runs = [];
|
|
197
|
+
for (const line of raw.split("\n")) {
|
|
198
|
+
if (line.trim() === "")
|
|
199
|
+
continue;
|
|
200
|
+
const parsed = parseJson(line);
|
|
201
|
+
if (parsed.ok)
|
|
202
|
+
runs.push(parsed.value);
|
|
203
|
+
}
|
|
204
|
+
return runs;
|
|
205
|
+
}
|
|
215
206
|
/** A schedule name becomes a filename, so keep it to safe characters. */
|
|
216
207
|
function safeName(name) {
|
|
217
208
|
return name.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
218
209
|
}
|
|
219
|
-
function matchesNow(fields, now) {
|
|
220
|
-
// `matches` is minute-granular and `now` carries seconds; zero them so a
|
|
221
|
-
// tick at 12:00:37 still counts as 12:00.
|
|
222
|
-
const minute = new Date(now.getTime());
|
|
223
|
-
minute.setSeconds(0, 0);
|
|
224
|
-
return matchesMinute(fields, minute);
|
|
225
|
-
}
|
|
226
|
-
function matchesMinute(fields, date) {
|
|
227
|
-
if (!fields.minute.has(date.getMinutes()))
|
|
228
|
-
return false;
|
|
229
|
-
if (!fields.hour.has(date.getHours()))
|
|
230
|
-
return false;
|
|
231
|
-
if (!fields.month.has(date.getMonth() + 1))
|
|
232
|
-
return false;
|
|
233
|
-
const dom = fields.dayOfMonth.has(date.getDate());
|
|
234
|
-
const dow = fields.dayOfWeek.has(date.getDay());
|
|
235
|
-
return fields.dayFieldsAreOr ? dom || dow : dom && dow;
|
|
236
|
-
}
|
|
237
210
|
//# sourceMappingURL=scheduler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/schedule/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/schedule/scheduler.ts"],"names":[],"mappings":"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"}
|
package/dist/storage/kv.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { appendFile, mkdir, open, readFile, readdir,
|
|
1
|
+
import { appendFile, mkdir, open, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parseJson } from "../util/json.js";
|
|
4
|
+
import { renameWithRetry } from "../util/fs.js";
|
|
4
5
|
/**
|
|
5
6
|
* A directory of JSON files, addressed by key.
|
|
6
7
|
*
|
|
@@ -50,7 +51,7 @@ export class JsonStore {
|
|
|
50
51
|
await mkdir(path.dirname(target), { recursive: true });
|
|
51
52
|
const temp = `${target}.${process.pid}.${(tempSeq += 1)}.tmp`;
|
|
52
53
|
await writeFile(temp, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
53
|
-
await
|
|
54
|
+
await renameWithRetry(temp, target);
|
|
54
55
|
}
|
|
55
56
|
async remove(key) {
|
|
56
57
|
await rm(this.file(key), { force: true });
|
package/dist/storage/kv.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kv.js","sourceRoot":"","sources":["../../src/storage/kv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"kv.js","sourceRoot":"","sources":["../../src/storage/kv.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The identity of a project, as something other than its path.
|
|
3
|
+
*
|
|
4
|
+
* Sessions have to belong to something stable. A working directory is not:
|
|
5
|
+
* clones land in different places, worktrees multiply, and a renamed folder
|
|
6
|
+
* would orphan every transcript recorded under the old name — which is exactly
|
|
7
|
+
* how a session list "loses" work that is still on disk.
|
|
8
|
+
*
|
|
9
|
+
* Registered projects carry that identity in `.oharness/project.json`. Git
|
|
10
|
+
* repositories use their root commit when the manifest is first created, so
|
|
11
|
+
* separately opened clones still meet as one project. Other directories get
|
|
12
|
+
* a random id. The path hash remains only as a read-only fallback for callers
|
|
13
|
+
* that inspect a directory before it has been registered.
|
|
14
|
+
*/
|
|
15
|
+
export interface ProjectManifest {
|
|
16
|
+
version: 1;
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
createdAt: number;
|
|
20
|
+
}
|
|
1
21
|
export declare function projectId(cwd: string): Promise<string>;
|
|
22
|
+
/** Read the small, non-executable identity document carried by a project. */
|
|
23
|
+
export declare function readProjectManifest(cwd: string): Promise<ProjectManifest | undefined>;
|
|
24
|
+
/** Ensure a registered project has an identity that survives moves and exports. */
|
|
25
|
+
export declare function ensureProjectManifest(cwd: string): Promise<ProjectManifest>;
|
|
26
|
+
/** Change only the display name; identity and creation time never change. */
|
|
27
|
+
export declare function renameProjectManifest(cwd: string, name: string): Promise<ProjectManifest>;
|
|
2
28
|
export interface ProjectRecord {
|
|
3
29
|
id: string;
|
|
4
30
|
vcs: "git" | "none";
|
package/dist/storage/project.js
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { realpath } from "node:fs/promises";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdir, readFile, realpath, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* clones land in different places, worktrees multiply, and a renamed folder
|
|
10
|
-
* would orphan every transcript recorded under the old name — which is exactly
|
|
11
|
-
* how a session list "loses" work that is still on disk.
|
|
12
|
-
*
|
|
13
|
-
* So a git repository is identified by its root commit, which every clone and
|
|
14
|
-
* every worktree of that repository shares and which nothing can rename.
|
|
15
|
-
* Anything else falls back to a hash of the resolved path, which at least
|
|
16
|
-
* survives symlinks and trailing slashes.
|
|
17
|
-
*/
|
|
5
|
+
import { projectPath } from "../paths.js";
|
|
6
|
+
import { renameWithRetry } from "../util/fs.js";
|
|
7
|
+
const MANIFEST = "project.json";
|
|
8
|
+
const PROJECT_ID = /^(?:project_[a-f0-9]{32}|[a-f0-9]{40,64})$/;
|
|
18
9
|
const cache = new Map();
|
|
19
10
|
export function projectId(cwd) {
|
|
20
11
|
const key = path.resolve(cwd);
|
|
@@ -26,12 +17,80 @@ export function projectId(cwd) {
|
|
|
26
17
|
return pending;
|
|
27
18
|
}
|
|
28
19
|
async function resolve(cwd) {
|
|
20
|
+
const manifest = await readProjectManifest(cwd);
|
|
21
|
+
if (manifest)
|
|
22
|
+
return manifest.id;
|
|
29
23
|
const root = await rootCommit(cwd);
|
|
30
24
|
if (root)
|
|
31
25
|
return root;
|
|
32
26
|
const real = await realpath(cwd).catch(() => cwd);
|
|
33
27
|
return `dir-${createHash("sha256").update(real).digest("hex").slice(0, 16)}`;
|
|
34
28
|
}
|
|
29
|
+
/** Read the small, non-executable identity document carried by a project. */
|
|
30
|
+
export async function readProjectManifest(cwd) {
|
|
31
|
+
let value;
|
|
32
|
+
try {
|
|
33
|
+
value = JSON.parse(await readFile(projectPath(cwd, MANIFEST), "utf8"));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
39
|
+
return undefined;
|
|
40
|
+
const one = value;
|
|
41
|
+
if (one.version !== 1 || typeof one.id !== "string" || !PROJECT_ID.test(one.id))
|
|
42
|
+
return undefined;
|
|
43
|
+
if (typeof one.name !== "string" || !one.name.trim() || !Number.isSafeInteger(one.createdAt))
|
|
44
|
+
return undefined;
|
|
45
|
+
return { version: 1, id: one.id, name: one.name.trim(), createdAt: one.createdAt };
|
|
46
|
+
}
|
|
47
|
+
/** Ensure a registered project has an identity that survives moves and exports. */
|
|
48
|
+
export async function ensureProjectManifest(cwd) {
|
|
49
|
+
const resolved = path.resolve(cwd);
|
|
50
|
+
await Promise.all([
|
|
51
|
+
mkdir(path.join(resolved, "MEMORY"), { recursive: true }),
|
|
52
|
+
mkdir(path.join(resolved, "ARTIFACTS"), { recursive: true }),
|
|
53
|
+
]);
|
|
54
|
+
const existing = await readProjectManifest(resolved);
|
|
55
|
+
if (existing) {
|
|
56
|
+
cache.set(resolved, Promise.resolve(existing.id));
|
|
57
|
+
return existing;
|
|
58
|
+
}
|
|
59
|
+
const root = await rootCommit(resolved);
|
|
60
|
+
const manifest = {
|
|
61
|
+
version: 1,
|
|
62
|
+
id: root ?? `project_${randomUUID().replaceAll("-", "")}`,
|
|
63
|
+
name: path.basename(resolved) || resolved,
|
|
64
|
+
createdAt: Date.now(),
|
|
65
|
+
};
|
|
66
|
+
await writeManifest(resolved, manifest);
|
|
67
|
+
cache.set(resolved, Promise.resolve(manifest.id));
|
|
68
|
+
return manifest;
|
|
69
|
+
}
|
|
70
|
+
/** Change only the display name; identity and creation time never change. */
|
|
71
|
+
export async function renameProjectManifest(cwd, name) {
|
|
72
|
+
const current = await ensureProjectManifest(cwd);
|
|
73
|
+
const next = { ...current, name: name.trim() };
|
|
74
|
+
if (!next.name)
|
|
75
|
+
throw new Error("project name is required");
|
|
76
|
+
await writeManifest(cwd, next);
|
|
77
|
+
return next;
|
|
78
|
+
}
|
|
79
|
+
function asyncTemp(file) {
|
|
80
|
+
return `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
81
|
+
}
|
|
82
|
+
async function writeManifest(cwd, manifest) {
|
|
83
|
+
const file = projectPath(cwd, MANIFEST);
|
|
84
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
85
|
+
const temp = asyncTemp(file);
|
|
86
|
+
try {
|
|
87
|
+
await writeFile(temp, `${JSON.stringify(manifest, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
88
|
+
await renameWithRetry(temp, file);
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
await rm(temp, { force: true });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
35
94
|
/**
|
|
36
95
|
* The repository's first commit.
|
|
37
96
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/storage/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/storage/project.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Tool } from "../types.ts";
|
|
2
|
+
/** One routine as the tool reports it. */
|
|
3
|
+
export interface RoutineSummary {
|
|
4
|
+
name: string;
|
|
5
|
+
cron: string;
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
nextRunAt?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* What the routine tool needs from the harness. An interface so the tool
|
|
11
|
+
* layer never imports the harness, which imports tools.
|
|
12
|
+
*/
|
|
13
|
+
export interface RoutineHost {
|
|
14
|
+
list(): Promise<RoutineSummary[]>;
|
|
15
|
+
add(input: {
|
|
16
|
+
name: string;
|
|
17
|
+
cron: string;
|
|
18
|
+
prompt: string;
|
|
19
|
+
cwd: string;
|
|
20
|
+
}): Promise<unknown>;
|
|
21
|
+
remove(name: string): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create, list and delete routines — prompts that run on a schedule.
|
|
25
|
+
*
|
|
26
|
+
* A tool rather than "edit the config file": the file the agent would reach
|
|
27
|
+
* for is the project's, which is the repository's and fires on every clone,
|
|
28
|
+
* and a hand-written cron nobody parsed is found broken at 9am, not now. This
|
|
29
|
+
* writes the user's own config and refuses an expression that does not parse.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createRoutineTool(host: RoutineHost): Tool;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { errorMessage } from "../../core/errors.js";
|
|
3
|
+
import { defineTool } from "../define.js";
|
|
4
|
+
import { toolError, toolText } from "../types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Create, list and delete routines — prompts that run on a schedule.
|
|
7
|
+
*
|
|
8
|
+
* A tool rather than "edit the config file": the file the agent would reach
|
|
9
|
+
* for is the project's, which is the repository's and fires on every clone,
|
|
10
|
+
* and a hand-written cron nobody parsed is found broken at 9am, not now. This
|
|
11
|
+
* writes the user's own config and refuses an expression that does not parse.
|
|
12
|
+
*/
|
|
13
|
+
export function createRoutineTool(host) {
|
|
14
|
+
return defineTool({
|
|
15
|
+
name: "routine",
|
|
16
|
+
description: [
|
|
17
|
+
"Manage routines: prompts that run by themselves on a schedule, each in a fresh session in this project.",
|
|
18
|
+
"Use when the user asks for something to happen regularly or at a set time (\"every weekday at 9\", \"each Monday morning\").",
|
|
19
|
+
"`add` takes a short kebab-case name, a five-field cron in the user's local time (minute hour day-of-month month day-of-week; `0 9 * * 1-5` is weekdays at 9:00), and the prompt each run sends.",
|
|
20
|
+
"Translate the user's words into the cron yourself; ask only if the time is genuinely unclear.",
|
|
21
|
+
"Write the prompt to stand on its own: the run has no memory of this conversation and nobody is there to answer questions or approve anything.",
|
|
22
|
+
"After adding, tell the user in plain words when it will run.",
|
|
23
|
+
"It runs in this session's project unless `cwd` names another one.",
|
|
24
|
+
"`list` shows existing routines; `remove` deletes one by name.",
|
|
25
|
+
].join(" "),
|
|
26
|
+
parameters: z.object({
|
|
27
|
+
action: z.enum(["add", "list", "remove"]),
|
|
28
|
+
name: z.string().optional().describe("Routine name. Required for add and remove."),
|
|
29
|
+
cron: z.string().optional().describe("Five-field cron, local time. Required for add."),
|
|
30
|
+
prompt: z.string().optional().describe("What each run asks. Required for add."),
|
|
31
|
+
cwd: z
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe("Absolute path of the project directory the routine runs in. Defaults to this session's project; set it only when the user names another project."),
|
|
35
|
+
}),
|
|
36
|
+
renderTitle: (input) => `Routine(${input.action}${input.name ? " " + input.name : ""})`,
|
|
37
|
+
async execute(input, ctx) {
|
|
38
|
+
try {
|
|
39
|
+
if (input.action === "list") {
|
|
40
|
+
const routines = await host.list();
|
|
41
|
+
if (!routines.length)
|
|
42
|
+
return toolText("No routines yet.");
|
|
43
|
+
return toolText(routines
|
|
44
|
+
.map((one) => `${one.name} ${one.cron}` +
|
|
45
|
+
(one.enabled ? "" : " (disabled)") +
|
|
46
|
+
(one.nextRunAt ? ` next ${new Date(one.nextRunAt).toString()}` : ""))
|
|
47
|
+
.join("\n"));
|
|
48
|
+
}
|
|
49
|
+
if (!input.name)
|
|
50
|
+
return toolError(`${input.action} needs a name`);
|
|
51
|
+
if (input.action === "remove") {
|
|
52
|
+
await host.remove(input.name);
|
|
53
|
+
return toolText(`Removed routine "${input.name}".`);
|
|
54
|
+
}
|
|
55
|
+
if (!input.cron || !input.prompt)
|
|
56
|
+
return toolError("add needs a name, a cron and a prompt");
|
|
57
|
+
await host.add({ name: input.name, cron: input.cron, prompt: input.prompt, cwd: input.cwd ?? ctx.cwd });
|
|
58
|
+
const added = (await host.list()).find((one) => one.name === input.name);
|
|
59
|
+
const next = added?.nextRunAt ? ` Next run: ${new Date(added.nextRunAt).toString()}.` : "";
|
|
60
|
+
return toolText(`Added routine "${input.name}" (${input.cron}).${next} ` +
|
|
61
|
+
"It fires while the OHarness desktop app or the TUI is open, or while `oharness schedule daemon` runs; " +
|
|
62
|
+
"a time missed while none was running is caught up once when one next starts. Say this if the user may not know it.", { routine: input.name });
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
return toolError(errorMessage(err));
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=routine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routine.js","sourceRoot":"","sources":["../../../src/tools/builtin/routine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rename a file or directory, tolerating the short locks produced by Windows
|
|
3
|
+
* Defender, Search Indexer and editors. POSIX failures are returned at once;
|
|
4
|
+
* retrying them would only hide a real error.
|
|
5
|
+
*/
|
|
6
|
+
export declare function renameWithRetry(from: string, to: string): Promise<void>;
|
package/dist/util/fs.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { rename } from "node:fs/promises";
|
|
2
|
+
const WINDOWS_RENAME_ERRORS = new Set(["EPERM", "EBUSY", "EACCES"]);
|
|
3
|
+
/**
|
|
4
|
+
* Rename a file or directory, tolerating the short locks produced by Windows
|
|
5
|
+
* Defender, Search Indexer and editors. POSIX failures are returned at once;
|
|
6
|
+
* retrying them would only hide a real error.
|
|
7
|
+
*/
|
|
8
|
+
export async function renameWithRetry(from, to) {
|
|
9
|
+
for (let attempt = 0;; attempt++) {
|
|
10
|
+
try {
|
|
11
|
+
await rename(from, to);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
const code = error.code ?? "";
|
|
16
|
+
if (process.platform !== "win32" || attempt >= 5 || !WINDOWS_RENAME_ERRORS.has(code))
|
|
17
|
+
throw error;
|
|
18
|
+
await new Promise((resolve) => setTimeout(resolve, 25 * (attempt + 1)));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=fs.js.map
|