bareloop 0.42.0 → 0.43.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/CHANGELOG.md +46 -0
- package/bareloop.context.md +53 -30
- package/package.json +1 -1
- package/src/authorjob.js +6 -0
- package/src/declaredclose.js +2 -2
- package/src/interviewrun.js +0 -1
- package/src/job.js +38 -11
- package/src/panel/authorroutes.js +107 -33
- package/src/panel/authorsession.js +59 -27
- package/src/panel/index.html +813 -484
- package/src/panel/runroutes.js +163 -5
- package/src/panel/scheduler.js +156 -0
- package/src/panel/schedules.js +667 -0
- package/src/panel/schedulestart.js +243 -0
- package/src/panel/server.js +451 -48
- package/src/planrun.js +39 -7
- package/src/providerrows.js +3 -3
- package/src/reviewdoor.js +37 -0
- package/src/runlist.js +35 -0
- package/src/schedulewords.js +296 -0
- package/src/trigger.js +48 -0
- package/src/triggerclock.js +127 -0
- package/src/triggerfiles.js +215 -0
- package/src/triggerledger.js +79 -0
- package/src/userrun.js +6 -7
- package/types/declaredclose.d.ts +1 -1
- package/types/job.d.ts +8 -1
- package/types/panel/authorroutes.d.ts +34 -1
- package/types/panel/authorsession.d.ts +37 -12
- package/types/panel/runroutes.d.ts +32 -1
- package/types/panel/scheduler.d.ts +17 -0
- package/types/panel/schedules.d.ts +412 -0
- package/types/panel/schedulestart.d.ts +106 -0
- package/types/panel/server.d.ts +143 -14
- package/types/planrun.d.ts +2 -0
- package/types/providerrows.d.ts +2 -2
- package/types/reviewdoor.d.ts +27 -0
- package/types/runlist.d.ts +20 -0
- package/types/schedulewords.d.ts +135 -0
- package/types/trigger.d.ts +37 -0
- package/types/triggerclock.d.ts +52 -0
- package/types/triggerfiles.d.ts +93 -0
- package/types/triggerledger.d.ts +35 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The spec one fire runs: the signed schedule's spec; for a file cause, Inputs line 1 is the arriving file. Every other
|
|
3
|
+
* field must be deep-equal and the workflow key unchanged (F.4): the person signed the SCHEDULE, each fire's hash is
|
|
4
|
+
* derived by code. A clock fire's spec equals the signed spec byte for byte (same hash).
|
|
5
|
+
* @param {{spec: any, specHash: string, workflowKey: string}} schedule
|
|
6
|
+
* @param {{kind: 'clock'}|{kind: 'file', path: string}} cause
|
|
7
|
+
* @returns {{ok: true, spec: any, hash: string}|{ok: false, why: string}}
|
|
8
|
+
*/
|
|
9
|
+
export function perFireSpec(schedule: {
|
|
10
|
+
spec: any;
|
|
11
|
+
specHash: string;
|
|
12
|
+
workflowKey: string;
|
|
13
|
+
}, cause: {
|
|
14
|
+
kind: "clock";
|
|
15
|
+
} | {
|
|
16
|
+
kind: "file";
|
|
17
|
+
path: string;
|
|
18
|
+
}): {
|
|
19
|
+
ok: true;
|
|
20
|
+
spec: any;
|
|
21
|
+
hash: string;
|
|
22
|
+
} | {
|
|
23
|
+
ok: false;
|
|
24
|
+
why: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* @param {{
|
|
28
|
+
* door: ReturnType<typeof import('./schedules.js').createScheduleDoor>,
|
|
29
|
+
* startSession: ReturnType<typeof import('./authorroutes.js').createAuthorRoutes>['startSession'],
|
|
30
|
+
* envNow: () => Record<string, string|undefined>,
|
|
31
|
+
* spawnFn: Function, bareloopBin: string, sessionsRoot?: string, home?: string,
|
|
32
|
+
* rowsFor: () => readonly import('../providerrows.js').KeyRow[],
|
|
33
|
+
* startFrom: (runid: string) => any,
|
|
34
|
+
* now?: () => number, prepareTimeoutMs?: number, pollMs?: number, childGraceMs?: number,
|
|
35
|
+
* forgetSession?: (id: string) => void, ranSince?: (job: string, sinceMs: number) => boolean,
|
|
36
|
+
* }} deps
|
|
37
|
+
*/
|
|
38
|
+
export function createStarter(deps: {
|
|
39
|
+
door: ReturnType<typeof import("./schedules.js").createScheduleDoor>;
|
|
40
|
+
startSession: ReturnType<typeof import("./authorroutes.js").createAuthorRoutes>["startSession"];
|
|
41
|
+
envNow: () => Record<string, string | undefined>;
|
|
42
|
+
spawnFn: Function;
|
|
43
|
+
bareloopBin: string;
|
|
44
|
+
sessionsRoot?: string;
|
|
45
|
+
home?: string;
|
|
46
|
+
rowsFor: () => readonly import("../providerrows.js").KeyRow[];
|
|
47
|
+
startFrom: (runid: string) => any;
|
|
48
|
+
now?: () => number;
|
|
49
|
+
prepareTimeoutMs?: number;
|
|
50
|
+
pollMs?: number;
|
|
51
|
+
childGraceMs?: number;
|
|
52
|
+
forgetSession?: (id: string) => void;
|
|
53
|
+
ranSince?: (job: string, sinceMs: number) => boolean;
|
|
54
|
+
}): {
|
|
55
|
+
/**
|
|
56
|
+
* Cheap, $0, no copy, no session. `{ok: true}` or `{ok: false, why}` (words).
|
|
57
|
+
* @param {string} job
|
|
58
|
+
* @param {{kind: 'clock'} | {kind: 'file', path: string, sha: string}} cause
|
|
59
|
+
* @returns {{ok: true}|{ok: false, why: string}}
|
|
60
|
+
*/
|
|
61
|
+
wouldAccept(job: string, cause: {
|
|
62
|
+
kind: "clock";
|
|
63
|
+
} | {
|
|
64
|
+
kind: "file";
|
|
65
|
+
path: string;
|
|
66
|
+
sha: string;
|
|
67
|
+
}): {
|
|
68
|
+
ok: true;
|
|
69
|
+
} | {
|
|
70
|
+
ok: false;
|
|
71
|
+
why: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Start the run. Never throws. `consumed` says whether `beforeSpawn` ran, so the caller knows whether a `released` row is owed.
|
|
75
|
+
* @param {string} job
|
|
76
|
+
* @param {{kind: 'clock', slot: number, catchUp: boolean, missed: number} | {kind: 'file', path: string, sha: string}} cause
|
|
77
|
+
* @param {{beforeSpawn?: () => void}} [hooks]
|
|
78
|
+
* @returns {Promise<{ok: true, sessionId: string, specHash: string, consumed: true}|{ok: false, why: string, consumed: boolean, installNeeded?: true}>}
|
|
79
|
+
*/
|
|
80
|
+
start(job: string, cause: {
|
|
81
|
+
kind: "clock";
|
|
82
|
+
slot: number;
|
|
83
|
+
catchUp: boolean;
|
|
84
|
+
missed: number;
|
|
85
|
+
} | {
|
|
86
|
+
kind: "file";
|
|
87
|
+
path: string;
|
|
88
|
+
sha: string;
|
|
89
|
+
}, hooks?: {
|
|
90
|
+
beforeSpawn?: () => void;
|
|
91
|
+
}): Promise<{
|
|
92
|
+
ok: true;
|
|
93
|
+
sessionId: string;
|
|
94
|
+
specHash: string;
|
|
95
|
+
consumed: true;
|
|
96
|
+
} | {
|
|
97
|
+
ok: false;
|
|
98
|
+
why: string;
|
|
99
|
+
consumed: boolean;
|
|
100
|
+
installNeeded?: true;
|
|
101
|
+
}>;
|
|
102
|
+
};
|
|
103
|
+
/** how long a headless session may take to reach `prepared` (copy, install, signing gates) */
|
|
104
|
+
export const PREPARE_TIMEOUT_MS: number;
|
|
105
|
+
/** how long the starter listens for a child that refuses at once (run-u checks its spec and approve hash first thing) */
|
|
106
|
+
export const CHILD_GRACE_MS: 1500;
|
package/types/panel/server.d.ts
CHANGED
|
@@ -116,7 +116,7 @@ export function legDividersFor(spineRecords: any[], parts: any[]): {
|
|
|
116
116
|
*
|
|
117
117
|
* The table (outcome → reason / next / buttons) is PANEL-BUILD.md P5 item 1. The
|
|
118
118
|
* Resume button appears only when `o.resume` says the engine would accept it; "Reuse workflow"
|
|
119
|
-
* (replaces P5 item 3, 2026-10-03
|
|
119
|
+
* (replaces P5 item 3, 2026-10-03; one door on every ending since 2026-10-15) starts a NEW run.
|
|
120
120
|
* @param {{outcome: string|null, stopReason: string|null, spentUsd: number|null, budgetUsd: number|null, lastEscalation?: any}} summary
|
|
121
121
|
* @param {{died: boolean, lastThing: string|null}} death
|
|
122
122
|
* @param {{resume?: {ok: boolean, why?: string}|null, destinationRefused?: string|null, moneyHalt?: boolean,
|
|
@@ -155,6 +155,28 @@ export function endedFor(summary: {
|
|
|
155
155
|
label: string;
|
|
156
156
|
}[];
|
|
157
157
|
} | null;
|
|
158
|
+
/**
|
|
159
|
+
* RULED 2026-10-15 (Remove from Needs you): the ONE place a run's Needs-you state is decided. `Accept` (a live door) is never removable and never ages out.
|
|
160
|
+
* A `Resume` tag leaves when the person removed it AFTER (or at) the stop it is for — a removal covers only the ending it was made on, so a later stop
|
|
161
|
+
* brings the run back — or after `needsYouDays`. `removedMs` is the newest removal that covers the current ending (or null), `expired` is true when only
|
|
162
|
+
* the age rule took a Resume run out.
|
|
163
|
+
* @param {{live: boolean, records: any[], resume: {ok?: boolean}|null|undefined, stoppedAt: string|null, nowMs: number, removedMs?: number}} f
|
|
164
|
+
* @returns {{needs: 'Accept'|'Resume'|null, removedMs: number|null, expired: boolean}}
|
|
165
|
+
*/
|
|
166
|
+
export function needsStateFor(f: {
|
|
167
|
+
live: boolean;
|
|
168
|
+
records: any[];
|
|
169
|
+
resume: {
|
|
170
|
+
ok?: boolean;
|
|
171
|
+
} | null | undefined;
|
|
172
|
+
stoppedAt: string | null;
|
|
173
|
+
nowMs: number;
|
|
174
|
+
removedMs?: number;
|
|
175
|
+
}): {
|
|
176
|
+
needs: "Accept" | "Resume" | null;
|
|
177
|
+
removedMs: number | null;
|
|
178
|
+
expired: boolean;
|
|
179
|
+
};
|
|
158
180
|
/**
|
|
159
181
|
* The Resume route's reader (P5 item 2): the listed row plus the resume plan, from the
|
|
160
182
|
* ONE {@link resumePlanFor} the Ended block asks. `null` when the runid is not listed
|
|
@@ -184,6 +206,43 @@ export function getStopContext(runid: string, opts?: {
|
|
|
184
206
|
live: boolean;
|
|
185
207
|
spineExists: boolean;
|
|
186
208
|
} | null;
|
|
209
|
+
/**
|
|
210
|
+
* The Remove-from-Needs-you route's reader (RULED 2026-10-15): the listed row and the Needs-you tag the runs list shows for it right now
|
|
211
|
+
* (`summarizeRow`, the one rule). `null` when the runid is not listed.
|
|
212
|
+
* @param {string} runid
|
|
213
|
+
* @param {{ home?: string, now?: number }} [opts]
|
|
214
|
+
* @returns {{row: any, needs: 'Accept'|'Resume'|null}|null}
|
|
215
|
+
*/
|
|
216
|
+
export function getNeedsContext(runid: string, opts?: {
|
|
217
|
+
home?: string;
|
|
218
|
+
now?: number;
|
|
219
|
+
}): {
|
|
220
|
+
row: any;
|
|
221
|
+
needs: "Accept" | "Resume" | null;
|
|
222
|
+
} | null;
|
|
223
|
+
/**
|
|
224
|
+
* The ONE rule for a run's Needs-you tag (P9 v2 E): `Resume` when the engine would accept a resume (the SAME `resumePlanFor` answer the Run
|
|
225
|
+
* tab's Resume card shows, already asked once per row by `endedForRow`), until the run has been stopped `needsYouDays` (exactly that old is out;
|
|
226
|
+
* no readable stop time stays in). `Accept` comes from {@link doorWaiting} (T Part 8), asked in `summarizeRow`, never here.
|
|
227
|
+
* @param {{ok?: boolean}|null|undefined} resume @param {string|null} stoppedAt @param {number} nowMs
|
|
228
|
+
* @returns {'Accept'|'Resume'|null}
|
|
229
|
+
*/
|
|
230
|
+
export function needsTag(resume: {
|
|
231
|
+
ok?: boolean;
|
|
232
|
+
} | null | undefined, stoppedAt: string | null, nowMs: number): "Accept" | "Resume" | null;
|
|
233
|
+
/**
|
|
234
|
+
* One entry per job, newest activity first: `headRunid` is the job's newest run BY TIME (never "the first row in server order"), `activityMs` the
|
|
235
|
+
* newest KNOWN time over its runs (`stoppedAt`, else `at`), `quiet` only when that time is known and older than the quiet limit. A job with a live
|
|
236
|
+
* run is never quiet; an unknown age is never guessed into quiet.
|
|
237
|
+
* @param {any[]} rows `listRuns` rows @param {number} nowMs
|
|
238
|
+
* @returns {{job: string, headRunid: string|null, quiet: boolean, activityMs: number|null}[]}
|
|
239
|
+
*/
|
|
240
|
+
export function summarizeJobs(rows: any[], nowMs: number): {
|
|
241
|
+
job: string;
|
|
242
|
+
headRunid: string | null;
|
|
243
|
+
quiet: boolean;
|
|
244
|
+
activityMs: number | null;
|
|
245
|
+
}[];
|
|
187
246
|
/**
|
|
188
247
|
* `GET /api/runs` — every listed run, NEWEST FIRST BY `at` (never by file/
|
|
189
248
|
* append order — a backfill scan appends rows in sorted-PATH order, which
|
|
@@ -197,12 +256,53 @@ export function getStopContext(runid: string, opts?: {
|
|
|
197
256
|
* SAME payload by job client-side (`groupRunsByJob` in `index.html`) rather
|
|
198
257
|
* than a second server-side endpoint, so the glyph/checkType mapping is
|
|
199
258
|
* still computed in exactly one place.
|
|
200
|
-
* @param {{ home?: string }} [opts]
|
|
259
|
+
* @param {{ home?: string, now?: number }} [opts] `now` (ms) is injectable for the Needs-you age rule
|
|
201
260
|
* @returns {any[]}
|
|
202
261
|
*/
|
|
203
262
|
export function listRuns(opts?: {
|
|
204
263
|
home?: string;
|
|
264
|
+
now?: number;
|
|
205
265
|
}): any[];
|
|
266
|
+
/**
|
|
267
|
+
* `GET /api/runs` (P9 v2 F): `{runs, jobs, limits}`. The server decides every state the left list draws — `live`, `needs`, each job's head run and
|
|
268
|
+
* quiet flag, the limits — and the page works out nothing. `scheduled` is the triggers piece (PRD v1.99, T Part 7).
|
|
269
|
+
* @param {{ home?: string, now?: number, schedules?: ReturnType<typeof createScheduleDoor> }} [opts]
|
|
270
|
+
*/
|
|
271
|
+
export function runsListPayload(opts?: {
|
|
272
|
+
home?: string;
|
|
273
|
+
now?: number;
|
|
274
|
+
schedules?: ReturnType<typeof createScheduleDoor>;
|
|
275
|
+
}): {
|
|
276
|
+
runs: any[];
|
|
277
|
+
jobs: {
|
|
278
|
+
job: string;
|
|
279
|
+
headRunid: string | null;
|
|
280
|
+
quiet: boolean;
|
|
281
|
+
activityMs: number | null;
|
|
282
|
+
}[];
|
|
283
|
+
scheduled: any[];
|
|
284
|
+
gaveUp: any[];
|
|
285
|
+
limits: Readonly<{
|
|
286
|
+
quietDays: 30;
|
|
287
|
+
olderRuns: 7;
|
|
288
|
+
needsYouDays: 7;
|
|
289
|
+
}>;
|
|
290
|
+
};
|
|
291
|
+
/**
|
|
292
|
+
* T Part 8 — what the Accept route needs: the run's row, whether its runner is alive, whether it waits at its review door
|
|
293
|
+
* ({@link doorWaiting}), and the signed spec it was judged by (the file and hash `run-u --door --approve` takes).
|
|
294
|
+
* @param {string} runid @param {{ home?: string }} opts
|
|
295
|
+
* @returns {{row: any, live: boolean, waiting: boolean, specPath: string|null, specHash: string|null}|null}
|
|
296
|
+
*/
|
|
297
|
+
export function getAcceptContext(runid: string, opts?: {
|
|
298
|
+
home?: string;
|
|
299
|
+
}): {
|
|
300
|
+
row: any;
|
|
301
|
+
live: boolean;
|
|
302
|
+
waiting: boolean;
|
|
303
|
+
specPath: string | null;
|
|
304
|
+
specHash: string | null;
|
|
305
|
+
} | null;
|
|
206
306
|
/**
|
|
207
307
|
* `GET /api/runs/:runid` — the full replay for the Run tab: steps (or
|
|
208
308
|
* iterations), counters, summary-box fields. Looks `runid` up in the run
|
|
@@ -210,11 +310,12 @@ export function listRuns(opts?: {
|
|
|
210
310
|
* header); `null` when the runid is not in the list at all (caller renders
|
|
211
311
|
* 404).
|
|
212
312
|
* @param {string} runid
|
|
213
|
-
* @param {{ home?: string }} [opts]
|
|
313
|
+
* @param {{ home?: string, schedules?: ReturnType<typeof createScheduleDoor> }} [opts]
|
|
214
314
|
* @returns {any|null}
|
|
215
315
|
*/
|
|
216
316
|
export function getRunDetail(runid: string, opts?: {
|
|
217
317
|
home?: string;
|
|
318
|
+
schedules?: ReturnType<typeof createScheduleDoor>;
|
|
218
319
|
}): any | null;
|
|
219
320
|
/**
|
|
220
321
|
* `GET /api/runs/:runid/audit` — the Audit tab's rows, off the run's own
|
|
@@ -343,11 +444,12 @@ export function inputsTextFromSpec(spec: any, source: string): string;
|
|
|
343
444
|
* from the resolved SPEC only (a) (b) (c) — a job-start record and `none()`
|
|
344
445
|
* carry no close/writeScope/tools at all, so those stay `'not recorded'`.
|
|
345
446
|
* @param {string} runid
|
|
346
|
-
* @param {{ home?: string }} [opts]
|
|
447
|
+
* @param {{ home?: string, schedules?: ReturnType<typeof createScheduleDoor> }} [opts]
|
|
347
448
|
* @returns {any|null}
|
|
348
449
|
*/
|
|
349
450
|
export function getRunJob(runid: string, opts?: {
|
|
350
451
|
home?: string;
|
|
452
|
+
schedules?: ReturnType<typeof createScheduleDoor>;
|
|
351
453
|
}): any | null;
|
|
352
454
|
/**
|
|
353
455
|
* The track record of a WORKFLOW: every LISTED run whose signed spec (the one its latest leg ran under) has this
|
|
@@ -395,14 +497,14 @@ export function reuseLine(record: {
|
|
|
395
497
|
} | null): string;
|
|
396
498
|
/**
|
|
397
499
|
* `start-from` (the route keeps its name) for one listed run: the card prefilled from the run's SIGNED job, which
|
|
398
|
-
*
|
|
500
|
+
* the track record (v1.101: nothing is locked, every box is open). The job boxes carry the signed values, read by the
|
|
399
501
|
* SAME readers the Job tab uses (`successFromSpec`/`guardrailsFromSpec`); the open four — Source, Destination, the
|
|
400
502
|
* $ cap and the Time cap — start at what the run had. `ok:false` when the run's signed job is not on disk (nothing
|
|
401
503
|
* to copy: change the job: Clear the card). `null` when the runid is not listed.
|
|
402
504
|
* @param {string} runid
|
|
403
505
|
* @param {{ home?: string, model?: string, provider?: string }} [opts] `model` = the Model box's current Name (the estimate counts only runs on that worker); absent = the card's own Model
|
|
404
506
|
* @returns {{ok: true, origin: {runid: string, job: string}, outcome: string|null, card: Record<string, any>, from: 'signed job',
|
|
405
|
-
*
|
|
507
|
+
* note: string|null,
|
|
406
508
|
* specHash: string, workflowKey: string, spec: any, specPath: string, specNamesModel: boolean,
|
|
407
509
|
* specWorker: {provider: string, endpoint: string, label: string},
|
|
408
510
|
* trackRecord: ReturnType<typeof trackRecordFor>, line: string}|{ok: false, error: string}|null}
|
|
@@ -420,8 +522,6 @@ export function getStartFrom(runid: string, opts?: {
|
|
|
420
522
|
outcome: string | null;
|
|
421
523
|
card: Record<string, any>;
|
|
422
524
|
from: "signed job";
|
|
423
|
-
locked: readonly string[];
|
|
424
|
-
open: readonly string[];
|
|
425
525
|
note: string | null;
|
|
426
526
|
specHash: string;
|
|
427
527
|
workflowKey: string;
|
|
@@ -439,6 +539,21 @@ export function getStartFrom(runid: string, opts?: {
|
|
|
439
539
|
ok: false;
|
|
440
540
|
error: string;
|
|
441
541
|
} | null;
|
|
542
|
+
/**
|
|
543
|
+
* T Part 3: a job's listed runs, newest first, each with its signed spec (null when not on disk), its terminal outcome and
|
|
544
|
+
* its parsed spine. The schedule door's gate (`greenFor`) reads this; nothing here builds a run context.
|
|
545
|
+
* @param {string} job
|
|
546
|
+
* @param {{ home?: string }} [opts]
|
|
547
|
+
* @returns {{runid: string, outcome: string|null, spec: any, records: any[]}[]}
|
|
548
|
+
*/
|
|
549
|
+
export function jobRunsOf(job: string, opts?: {
|
|
550
|
+
home?: string;
|
|
551
|
+
}): {
|
|
552
|
+
runid: string;
|
|
553
|
+
outcome: string | null;
|
|
554
|
+
spec: any;
|
|
555
|
+
records: any[];
|
|
556
|
+
}[];
|
|
442
557
|
/**
|
|
443
558
|
* The Reuse workflow picker's list: one entry per JOB (same-job identity = `workflowKey`, the one `getStartFrom` uses)
|
|
444
559
|
* that has at least one GREEN local run, newest green first. `runid` = that job's newest green run, so the page hands
|
|
@@ -469,7 +584,7 @@ export function listReuseJobs(opts?: {
|
|
|
469
584
|
* @param {string} id
|
|
470
585
|
* @param {{ home?: string, model?: string, provider?: string }} [opts] `model` as for {@link getStartFrom}
|
|
471
586
|
* @returns {{ok: true, origin: {runid: null, job: string, importId: string}, card: Record<string, any>, from: 'imported job',
|
|
472
|
-
*
|
|
587
|
+
* note: string|null, specHash: string, workflowKey: string, spec: any,
|
|
473
588
|
* specPath: null, trackRecord: ReturnType<typeof trackRecordFor>, line: string}|{ok: false, error: string}|null}
|
|
474
589
|
*/
|
|
475
590
|
export function getStartFromImport(id: string, opts?: {
|
|
@@ -485,8 +600,6 @@ export function getStartFromImport(id: string, opts?: {
|
|
|
485
600
|
};
|
|
486
601
|
card: Record<string, any>;
|
|
487
602
|
from: "imported job";
|
|
488
|
-
locked: readonly string[];
|
|
489
|
-
open: readonly string[];
|
|
490
603
|
note: string | null;
|
|
491
604
|
specHash: string;
|
|
492
605
|
workflowKey: string;
|
|
@@ -514,7 +627,7 @@ export function getStartFromImport(id: string, opts?: {
|
|
|
514
627
|
* only `createPanelServer` below always supplies one.
|
|
515
628
|
* @param {import('node:http').IncomingMessage} req
|
|
516
629
|
* @param {import('node:http').ServerResponse} res
|
|
517
|
-
* @param {{ home?: string, packageJsonPath?: string, port: number, token?: string, authorRoutes?: ReturnType<typeof createAuthorRoutes>, settingsRoutes?: ReturnType<typeof createSettingsRoutes>, runRoutes?: ReturnType<typeof createRunRoutes>, importRoutes?: ReturnType<typeof createImportRoutes> }} opts
|
|
630
|
+
* @param {{ home?: string, packageJsonPath?: string, port: number, token?: string, authorRoutes?: ReturnType<typeof createAuthorRoutes>, settingsRoutes?: ReturnType<typeof createSettingsRoutes>, runRoutes?: ReturnType<typeof createRunRoutes>, importRoutes?: ReturnType<typeof createImportRoutes>, schedules?: ReturnType<typeof createScheduleDoor> }} opts
|
|
518
631
|
*/
|
|
519
632
|
export function handleRequest(req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, opts: {
|
|
520
633
|
home?: string;
|
|
@@ -525,6 +638,7 @@ export function handleRequest(req: import("node:http").IncomingMessage, res: imp
|
|
|
525
638
|
settingsRoutes?: ReturnType<typeof createSettingsRoutes>;
|
|
526
639
|
runRoutes?: ReturnType<typeof createRunRoutes>;
|
|
527
640
|
importRoutes?: ReturnType<typeof createImportRoutes>;
|
|
641
|
+
schedules?: ReturnType<typeof createScheduleDoor>;
|
|
528
642
|
}): void;
|
|
529
643
|
/**
|
|
530
644
|
* Start the panel server. Binds `127.0.0.1` ONLY. A taken port is a LOUD,
|
|
@@ -545,8 +659,9 @@ export function handleRequest(req: import("node:http").IncomingMessage, res: imp
|
|
|
545
659
|
* @param {{ port?: number, home?: string, env?: Record<string,string|undefined>,
|
|
546
660
|
* sessionsRoot?: string, spawnFn?: (...a: any[]) => any, bareloopBin?: string,
|
|
547
661
|
* fetchImpl?: typeof fetch, settleMs?: number, userHome?: string,
|
|
548
|
-
* openFolderSpawn?: (cmd: string, args: string[], o: object) => any, platform?: string, packageJsonPath?: string
|
|
549
|
-
*
|
|
662
|
+
* openFolderSpawn?: (cmd: string, args: string[], o: object) => any, platform?: string, packageJsonPath?: string,
|
|
663
|
+
* scheduleNow?: () => number, sessionDeps?: Record<string, any>, schedulerIntervalMs?: number, acceptWaitMs?: number }} [opts] `scheduleNow`, `sessionDeps` and `schedulerIntervalMs` are TEST SEAMS (the schedule clock; createSession's stubs; the scheduler's timer, 0 = none)
|
|
664
|
+
* @returns {Promise<{ server: import('node:http').Server, port: number, token: string, schedules: ReturnType<typeof createScheduleDoor>, scheduler: ReturnType<typeof createScheduler>, close: () => Promise<void> }>}
|
|
550
665
|
*/
|
|
551
666
|
export function createPanelServer(opts?: {
|
|
552
667
|
port?: number;
|
|
@@ -561,10 +676,16 @@ export function createPanelServer(opts?: {
|
|
|
561
676
|
openFolderSpawn?: (cmd: string, args: string[], o: object) => any;
|
|
562
677
|
platform?: string;
|
|
563
678
|
packageJsonPath?: string;
|
|
679
|
+
scheduleNow?: () => number;
|
|
680
|
+
sessionDeps?: Record<string, any>;
|
|
681
|
+
schedulerIntervalMs?: number;
|
|
682
|
+
acceptWaitMs?: number;
|
|
564
683
|
}): Promise<{
|
|
565
684
|
server: import("node:http").Server;
|
|
566
685
|
port: number;
|
|
567
686
|
token: string;
|
|
687
|
+
schedules: ReturnType<typeof createScheduleDoor>;
|
|
688
|
+
scheduler: ReturnType<typeof createScheduler>;
|
|
568
689
|
close: () => Promise<void>;
|
|
569
690
|
}>;
|
|
570
691
|
/**
|
|
@@ -604,13 +725,21 @@ export const DEFAULT_PORT: 4700;
|
|
|
604
725
|
*/
|
|
605
726
|
export const SOFTGREEN_ADMITTED_ISO: "2026-08-18T00:00:00.000Z";
|
|
606
727
|
export { DIED_MTIME_MS };
|
|
728
|
+
/** The named limits of the runs list, in ONE place (P9 v2 D.13): quiet after 30 days, 7 older runs shown, a Resume tag ages out after 7 days. Sent in `GET /api/runs`; the page reads them from there. */
|
|
729
|
+
export const RUNS_LIST_LIMITS: Readonly<{
|
|
730
|
+
quietDays: 30;
|
|
731
|
+
olderRuns: 7;
|
|
732
|
+
needsYouDays: 7;
|
|
733
|
+
}>;
|
|
607
734
|
/** The default/max page size for {@link getRunRounds} — tighten-only,
|
|
608
735
|
* named so a future change is a deliberate, visible edit (item 5,
|
|
609
736
|
* 2026-09-25). */
|
|
610
737
|
export const ROUNDS_PAGE_DEFAULT: 50;
|
|
611
738
|
export const ROUNDS_PAGE_MAX: 200;
|
|
739
|
+
import { createScheduleDoor } from './schedules.js';
|
|
612
740
|
import { createAuthorRoutes } from './authorroutes.js';
|
|
613
741
|
import { createSettingsRoutes } from './settingsroutes.js';
|
|
614
742
|
import { createRunRoutes } from './runroutes.js';
|
|
615
743
|
import { createImportRoutes } from './importroutes.js';
|
|
744
|
+
import { createScheduler } from './scheduler.js';
|
|
616
745
|
import { DIED_MTIME_MS } from '../runlist.js';
|
package/types/planrun.d.ts
CHANGED
|
@@ -523,6 +523,8 @@ export const FORBIDDEN_WRITE_SEGMENT_PATTERN: RegExp;
|
|
|
523
523
|
export const BOUND_REASON_MAX: 200;
|
|
524
524
|
/** the recorded reason for a step attempt whose round was cut at the output cap (F122 2A b) */
|
|
525
525
|
export const TRUNCATED_REASON: "output cut at the token limit (max_tokens)";
|
|
526
|
+
/** the plain sentence for a replan answer cut off at its length limit (hamr 2026-10-16, run mv9gm796) */
|
|
527
|
+
export const REPLAN_CUT_WORDS: "The replan answer was cut off at its length limit, so no new plan came back.";
|
|
526
528
|
export type CategorizedError = Error & {
|
|
527
529
|
category?: string;
|
|
528
530
|
lib?: string;
|
package/types/providerrows.d.ts
CHANGED
|
@@ -251,8 +251,8 @@ export function judgeRowFor(rows: readonly KeyRow[], judge: {
|
|
|
251
251
|
* the worker's own provider it calls the worker's endpoint, so a row on that endpoint with a line
|
|
252
252
|
* NAMED for the judge's model prices it ({@link ratesFor}); a judge model that is absent or names
|
|
253
253
|
* no line there keeps the worker's price (the worker's row, or none = the guess). A different
|
|
254
|
-
* provider resolves with no endpoint. The judge's KEY is chosen elsewhere (`
|
|
255
|
-
*
|
|
254
|
+
* provider resolves with no endpoint. The judge's KEY is chosen elsewhere (`judgeRowFor`:
|
|
255
|
+
* the worker's own row when the judge shares its provider), so the two can come from different rows.
|
|
256
256
|
* @param {{ provider: string, model?: string|null }} judge
|
|
257
257
|
* @param {string|null|undefined} workerProvider
|
|
258
258
|
* @param {string|null|undefined} workerBaseUrl
|
package/types/reviewdoor.d.ts
CHANGED
|
@@ -4,6 +4,33 @@
|
|
|
4
4
|
* @param {any[]} events the run's spine, parsed
|
|
5
5
|
* @returns {any|null} */
|
|
6
6
|
export function doorRecordOf(events: any[]): any | null;
|
|
7
|
+
/**
|
|
8
|
+
* Was this run's door ACCEPTED, honoured? A `door-decision` record with `decision: 'accept'` and `honoured: true`
|
|
9
|
+
* (written by {@link answerReviewDoor}, whichever surface asked: the CLI, or the panel's Accept). Pure, off the
|
|
10
|
+
* run's spine. A refused accept (`honoured: false`) does not count.
|
|
11
|
+
* @param {any[]} events the run's spine, parsed
|
|
12
|
+
* @returns {boolean}
|
|
13
|
+
*/
|
|
14
|
+
export function doorAccepted(events: any[]): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* T D.2 — a SCHEDULED run's green is held until the person accepts it (T10): a run that carries a `trigger.json` (a scheduled
|
|
17
|
+
* start wrote it) and holds no honoured accept. One shared predicate: `getStartFrom` reads its outcome as `'green-held'` (so
|
|
18
|
+
* the Reuse start refuses it unchanged), `listReuseJobs` skips it, and the schedule gate does not count it. A hand run is never held.
|
|
19
|
+
* @param {any[]} events the run's spine, parsed @param {any} triggerRec the run's `trigger.json`, or null
|
|
20
|
+
* @returns {boolean}
|
|
21
|
+
*/
|
|
22
|
+
export function heldScheduled(events: any[], triggerRec: any): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Is this run WAITING for the person's Accept? Its spine holds a door record, the door has not expired ({@link doorAgeGate}),
|
|
25
|
+
* and no honoured `accept` or `rerun` decision follows it (a `pause` keeps the same three doors open). One owner: the runs
|
|
26
|
+
* list's Needs-you `Accept` tag, the Run tab's Accept button and the scheduled NEXT line all read this.
|
|
27
|
+
* @param {any[]} events the run's spine, parsed
|
|
28
|
+
* @param {{now?: () => number}} [opts]
|
|
29
|
+
* @returns {boolean}
|
|
30
|
+
*/
|
|
31
|
+
export function doorWaiting(events: any[], opts?: {
|
|
32
|
+
now?: () => number;
|
|
33
|
+
}): boolean;
|
|
7
34
|
/**
|
|
8
35
|
* IS THE DOOR STILL OPEN? The same 60-day TTL a hitl checkpoint keeps
|
|
9
36
|
* (`PAUSE_TTL_MS`) and deliberately the same NUMBER — the door replaced `cancel`
|
package/types/runlist.d.ts
CHANGED
|
@@ -67,6 +67,26 @@ export function appendRun(row: RunRow, opts?: {
|
|
|
67
67
|
export function appendRunEvent(event: RunEvent, opts?: {
|
|
68
68
|
home?: string;
|
|
69
69
|
}): void;
|
|
70
|
+
/**
|
|
71
|
+
* RULED 2026-10-15 ("Remove from Needs you"): the person took a stopped run's card out of the Needs-you list. ONE row
|
|
72
|
+
* `{runid, type:'needs-removed', at}` appended beside the run rows; it is not a run, a claim or an event ({@link readRunList}
|
|
73
|
+
* ignores it, so the monthly limit, the run counter and the scheduler never see it). Nothing is rewritten.
|
|
74
|
+
* @param {string} runid
|
|
75
|
+
* @param {{ home?: string, now?: () => number }} [opts]
|
|
76
|
+
* @returns {string} the `at` written
|
|
77
|
+
*/
|
|
78
|
+
export function appendNeedsRemoved(runid: string, opts?: {
|
|
79
|
+
home?: string;
|
|
80
|
+
now?: () => number;
|
|
81
|
+
}): string;
|
|
82
|
+
/**
|
|
83
|
+
* The newest `needs-removed` time (ms) per run id. An absent file is an empty map; a row with no readable time is not a removal.
|
|
84
|
+
* @param {{ home?: string }} [opts]
|
|
85
|
+
* @returns {Map<string, number>}
|
|
86
|
+
*/
|
|
87
|
+
export function readNeedsRemovals(opts?: {
|
|
88
|
+
home?: string;
|
|
89
|
+
}): Map<string, number>;
|
|
70
90
|
/**
|
|
71
91
|
* @typedef {{ type: 'leg-start', runid: string, leg: number, pid?: number, capUsd?: number, at: string }} LegStart
|
|
72
92
|
* A resumed run's NEW claim (P5-R, hamr 2026-10-02: one run, one id, one row): appended beside the run's row
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What starts the job, in words. `null` (by hand) has no sentence.
|
|
3
|
+
* @param {Trigger|null|undefined} t @param {string} [home]
|
|
4
|
+
* @returns {string|null}
|
|
5
|
+
*/
|
|
6
|
+
export function scheduledWords(t: Trigger | null | undefined, home?: string): string | null;
|
|
7
|
+
/** "Mon 13 Oct, 09:00" — local time, fixed English names (never the machine's locale). @param {number} ms */
|
|
8
|
+
export function slotWords(ms: number): string;
|
|
9
|
+
/**
|
|
10
|
+
* The Scheduled card's first line: "today 17:00", "tomorrow 09:00", else the date the NEXT line already says
|
|
11
|
+
* ("Tue 20 Oct, 09:05"). Local time, by calendar day. @param {number} ms the next run @param {number} now
|
|
12
|
+
*/
|
|
13
|
+
export function nextTimeWords(ms: number, now: number): string;
|
|
14
|
+
/** "next in 20m" / "next in 3h" / "next in 3d" (whole units, rounded down, never "0m"). @param {number} ms @param {number} now */
|
|
15
|
+
export function nextInWords(ms: number, now: number): string;
|
|
16
|
+
/**
|
|
17
|
+
* NEXT of a scheduled job: one sentence, first match wins.
|
|
18
|
+
* 1. a run of this job is waiting for the person's Accept (the next scheduled run does NOT wait for it, T7);
|
|
19
|
+
* 2. the latest outcome this month is `monthly-exhausted` (amounts from the row), RED;
|
|
20
|
+
* 3. a clock kind with a next slot; 4. a file kind that is armed; 5. not armed yet.
|
|
21
|
+
* A last outcome of `refused` goes in front of 3-5, so a trigger that cannot start is never silent.
|
|
22
|
+
* `red` is the typed flag for "the person must act": the page paints it and never guesses from the text.
|
|
23
|
+
* @param {{trigger: Trigger, armed: boolean, nextSlotMs: number|null, lastOutcome: any, waitingRunNo: number|null, now: number, retryInMs?: number|null, gaveUp?: any}} f
|
|
24
|
+
* `lastOutcome` = the newest outcome row of the CURRENT signing (`kind`, `at` in ms, `why`, `leftUsd`, `needUsd`, `atLeast`)
|
|
25
|
+
* @returns {{text: string, red: boolean}}
|
|
26
|
+
*/
|
|
27
|
+
export function scheduledNextFor(f: {
|
|
28
|
+
trigger: Trigger;
|
|
29
|
+
armed: boolean;
|
|
30
|
+
nextSlotMs: number | null;
|
|
31
|
+
lastOutcome: any;
|
|
32
|
+
waitingRunNo: number | null;
|
|
33
|
+
now: number;
|
|
34
|
+
retryInMs?: number | null;
|
|
35
|
+
gaveUp?: any;
|
|
36
|
+
}): {
|
|
37
|
+
text: string;
|
|
38
|
+
red: boolean;
|
|
39
|
+
};
|
|
40
|
+
/** Is `atMs` in the same local calendar month as `nowMs`? A non-number is never. @param {unknown} atMs @param {number} nowMs */
|
|
41
|
+
export function sameMonthAs(atMs: unknown, nowMs: number): boolean;
|
|
42
|
+
/** RULED 2026-10-15: the red NEXT of a schedule that gave up. @param {unknown} why the refusal's own words */
|
|
43
|
+
export function gaveUpNextWords(why: unknown): string;
|
|
44
|
+
/** The give-up date on the Needs-you card, local `MM/DD HH:MM`. @param {number} ms */
|
|
45
|
+
export function gaveUpDateWords(ms: number): string;
|
|
46
|
+
/** RULED 2026-10-15: after an install-needed refusal, the wait that is left, in whole minutes rounded up; '' when no wait. @param {number|null|undefined} ms */
|
|
47
|
+
export function retryWords(ms: number | null | undefined): string;
|
|
48
|
+
/** The fixed catch-up sentence from a run's `trigger.json`. @param {any} rec */
|
|
49
|
+
export function catchUpWords(rec: any): "This run was a catch-up: it started more than 2 minutes after its set time (the panel was off)." | null;
|
|
50
|
+
/**
|
|
51
|
+
* A spawn error AFTER the consuming row: `tries` releases so far, `max` the bound. Under the bound it will be tried again;
|
|
52
|
+
* at the bound it stays consumed and says so. @param {string} why @param {number} tries @param {number} max
|
|
53
|
+
*/
|
|
54
|
+
export function lateRefusalWords(why: string, tries: number, max: number): string;
|
|
55
|
+
/** "Thu 10/16 08:15" — local time, fixed English day names. @param {number} ms */
|
|
56
|
+
export function nextSlotShort(ms: number): string;
|
|
57
|
+
/**
|
|
58
|
+
* Job tab, "Started": how the run began, then its local start time. `rec` = the run's `trigger.json` (null on a hand run).
|
|
59
|
+
* @param {any} rec @param {number|null} atMs the run's start
|
|
60
|
+
*/
|
|
61
|
+
export function startedWords(rec: any, atMs: number | null): string;
|
|
62
|
+
/**
|
|
63
|
+
* Audit, first row of a run a schedule started. @param {any} rec `trigger.json` @param {number|null} originNo the origin run's number
|
|
64
|
+
*/
|
|
65
|
+
export function startedAuditWords(rec: any, originNo: number | null): string;
|
|
66
|
+
/**
|
|
67
|
+
* A fire that started nothing, in plain words. `short` = the SCHEDULE row's `last` reason; else the Audit group's line.
|
|
68
|
+
* @param {any} row a ledger row (`busy` | `monthly-exhausted` | `refused` | `gave-up`) @param {boolean} [short]
|
|
69
|
+
*/
|
|
70
|
+
export function skippedWords(row: any, short?: boolean): string;
|
|
71
|
+
/**
|
|
72
|
+
* Job tab, "Schedule": the job's schedule NOW and what it is bound to. The first part IS the Run tab's line ({@link scheduledWords}),
|
|
73
|
+
* first letter lowered, so the two can never disagree.
|
|
74
|
+
* @param {{trigger: Trigger|null, originNo: number|null, model: string|null, destination: string|null, home?: string}} f
|
|
75
|
+
*/
|
|
76
|
+
export function jobScheduleWords(f: {
|
|
77
|
+
trigger: Trigger | null;
|
|
78
|
+
originNo: number | null;
|
|
79
|
+
model: string | null;
|
|
80
|
+
destination: string | null;
|
|
81
|
+
home?: string;
|
|
82
|
+
}): string;
|
|
83
|
+
/** RULED 2026-10-15: the SCHEDULE row's `next` while an install-needed refusal is waiting to be tried again. @param {number} ms */
|
|
84
|
+
export function retryNextWords(ms: number): string;
|
|
85
|
+
/**
|
|
86
|
+
* RULED 2026-10-15: the sign on the head of the Audit's `schedule` section. ONE small rule (hamr may still adjust it): [▶] only while a
|
|
87
|
+
* run of the job is running (as everywhere in the panel), [✗] when the SCHEDULE row is red, else [·] (on, waiting).
|
|
88
|
+
* @param {{running: boolean, red: boolean}} f @returns {{sign: string}}
|
|
89
|
+
*/
|
|
90
|
+
export function scheduleSignFor(f: {
|
|
91
|
+
running: boolean;
|
|
92
|
+
red: boolean;
|
|
93
|
+
}): {
|
|
94
|
+
sign: string;
|
|
95
|
+
};
|
|
96
|
+
/** Audit schedule section, the `this run` words of a run the schedule started (the head already says which schedule and from where). @param {any} rec `trigger.json` */
|
|
97
|
+
export function sectionThisRunWords(rec: any): string;
|
|
98
|
+
/** Audit schedule section, folded tail: `1 try started nothing` / `N tries started nothing`; '' for none. @param {number} n */
|
|
99
|
+
export function triesTailWords(n: number): string;
|
|
100
|
+
/**
|
|
101
|
+
* Every schedule button/word the page shows lives here, so a reword is ONE edit (hamr will reword on his walk).
|
|
102
|
+
* The server sends this table with the run detail and the schedule card; the page reads every label from it.
|
|
103
|
+
*/
|
|
104
|
+
export const SCHEDULE_WORDS: Readonly<{
|
|
105
|
+
byHand: "By hand";
|
|
106
|
+
cancelSchedule: "Cancel schedule";
|
|
107
|
+
cancelWords: "Cancel schedule";
|
|
108
|
+
scheduleRun: "Schedule run";
|
|
109
|
+
cancelModify: "Cancel / modify schedule";
|
|
110
|
+
signSchedule: "Sign schedule";
|
|
111
|
+
nothingChanged: "Nothing changed yet. This card only sets or adjusts the schedule; it never runs the job.";
|
|
112
|
+
confirmPrefix: "Confirm: ";
|
|
113
|
+
whenLabel: "When";
|
|
114
|
+
nowPrefix: "Now: ";
|
|
115
|
+
scheduledLabel: "SCHEDULE";
|
|
116
|
+
nextLabel: "NEXT";
|
|
117
|
+
options: Readonly<{
|
|
118
|
+
hand: "By hand";
|
|
119
|
+
daily: "Every day at HH:MM";
|
|
120
|
+
weekdays: "Weekdays at HH:MM";
|
|
121
|
+
weekly: "Every <day> at HH:MM";
|
|
122
|
+
'file-updated': "When <file> is updated";
|
|
123
|
+
'new-file': "When a new file arrives in <folder>";
|
|
124
|
+
}>;
|
|
125
|
+
gaveUpPill: "Check";
|
|
126
|
+
problemNext: "next: fix it in Cancel / modify schedule";
|
|
127
|
+
monthNext: "next: raise the limit in Settings, or it starts on the 1st";
|
|
128
|
+
acceptWaiting: "waiting for your Accept (the next run does not wait)";
|
|
129
|
+
scheduleGroup: "schedule";
|
|
130
|
+
notGreen: "This job has not had a good run yet. Run it by hand and get it right first; then you can set when it runs. A rubric job also needs your Accept once. Nothing was changed.";
|
|
131
|
+
stale: "What you were shown is not what is on the card now. Nothing was signed. Look again, then sign.";
|
|
132
|
+
}>;
|
|
133
|
+
export function dateTimeWords(ms: number): string;
|
|
134
|
+
export function lowerFirst(s: string): string;
|
|
135
|
+
export type Trigger = import("./trigger.js").Trigger;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only way a trigger enters the system. Returns the typed trigger, or says what to fix (and which field).
|
|
3
|
+
* @param {unknown} t
|
|
4
|
+
* @returns {{ok: true, trigger: Trigger} | {ok: false, say: string, code: 'invalid-value'|'unknown-field', path: string}}
|
|
5
|
+
*/
|
|
6
|
+
export function validateTrigger(t: unknown): {
|
|
7
|
+
ok: true;
|
|
8
|
+
trigger: Trigger;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
say: string;
|
|
12
|
+
code: "invalid-value" | "unknown-field";
|
|
13
|
+
path: string;
|
|
14
|
+
};
|
|
15
|
+
/** @typedef {import('./triggerclock.js').Schedule | {kind:'new-file', folder:string} | {kind:'file-updated', path:string}} Trigger */
|
|
16
|
+
export const TRIGGER_KINDS: readonly string[];
|
|
17
|
+
export const CLOCK_KINDS: readonly string[];
|
|
18
|
+
export const FILE_KINDS: readonly string[];
|
|
19
|
+
export function isClockTrigger(t: unknown): t is {
|
|
20
|
+
kind: "daily" | "weekdays" | "weekly";
|
|
21
|
+
hour: number;
|
|
22
|
+
minute: number;
|
|
23
|
+
};
|
|
24
|
+
export function isFileTrigger(t: unknown): t is {
|
|
25
|
+
kind: "new-file";
|
|
26
|
+
folder: string;
|
|
27
|
+
} | {
|
|
28
|
+
kind: "file-updated";
|
|
29
|
+
path: string;
|
|
30
|
+
};
|
|
31
|
+
export type Trigger = import("./triggerclock.js").Schedule | {
|
|
32
|
+
kind: "new-file";
|
|
33
|
+
folder: string;
|
|
34
|
+
} | {
|
|
35
|
+
kind: "file-updated";
|
|
36
|
+
path: string;
|
|
37
|
+
};
|