@ours.network/fleet 0.10.0 → 0.10.1
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/monitor.d.ts +11 -3
- package/dist/monitor.js +7 -3
- package/dist/runner.d.ts +7 -0
- package/dist/runner.js +33 -5
- package/package.json +1 -1
package/dist/monitor.d.ts
CHANGED
|
@@ -142,7 +142,9 @@ export interface MonitorOpts {
|
|
|
142
142
|
}
|
|
143
143
|
/** The lifecycle surface the runner drives: prime pre-launch, run, stop on pid death. */
|
|
144
144
|
export interface MonitorHandle {
|
|
145
|
-
prime(
|
|
145
|
+
prime(options?: {
|
|
146
|
+
resetCursor?: boolean;
|
|
147
|
+
}): Promise<void>;
|
|
146
148
|
run(pid: number): Promise<void>;
|
|
147
149
|
stop(): void;
|
|
148
150
|
}
|
|
@@ -167,8 +169,14 @@ export declare class Monitor {
|
|
|
167
169
|
/** Active degradations, keyed by cause. Empty means armed. */
|
|
168
170
|
private readonly causes;
|
|
169
171
|
constructor(o: MonitorOpts);
|
|
170
|
-
/**
|
|
171
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Resume the last delivered cursor during ordinary fleet-owned restarts.
|
|
174
|
+
* A native→fleet ownership transition resets at stream tip because the native
|
|
175
|
+
* owner was responsible for arrivals while the supervisor was inactive.
|
|
176
|
+
*/
|
|
177
|
+
prime(options?: {
|
|
178
|
+
resetCursor?: boolean;
|
|
179
|
+
}): Promise<void>;
|
|
172
180
|
/** Long-poll → filter → coalesce → inject, until the pane pid dies or stop(). */
|
|
173
181
|
run(pid: number): Promise<void>;
|
|
174
182
|
stop(): void;
|
package/dist/monitor.js
CHANGED
|
@@ -336,9 +336,13 @@ export class Monitor {
|
|
|
336
336
|
const n = o.cfg.turn_fail_threshold;
|
|
337
337
|
this.turnFailThreshold = typeof n === 'number' && n >= 1 ? n : DEFAULT_TURN_FAIL_THRESHOLD;
|
|
338
338
|
}
|
|
339
|
-
/**
|
|
340
|
-
|
|
341
|
-
|
|
339
|
+
/**
|
|
340
|
+
* Resume the last delivered cursor during ordinary fleet-owned restarts.
|
|
341
|
+
* A native→fleet ownership transition resets at stream tip because the native
|
|
342
|
+
* owner was responsible for arrivals while the supervisor was inactive.
|
|
343
|
+
*/
|
|
344
|
+
async prime(options = {}) {
|
|
345
|
+
const persisted = options.resetCursor ? null : this.readPersistedCursor();
|
|
342
346
|
if (persisted !== null) {
|
|
343
347
|
this.cursor = persisted;
|
|
344
348
|
this.deliveredCursor = persisted;
|
package/dist/runner.d.ts
CHANGED
|
@@ -19,6 +19,13 @@ export interface RunnerDeps {
|
|
|
19
19
|
/** Lets a test (or a shutdown path) end the supervised restart loop. */
|
|
20
20
|
shouldStop?(): boolean;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Record who owns wake delivery for this run. Returning true means a fleet
|
|
24
|
+
* monitor is taking ownership back from a native harness and must start at the
|
|
25
|
+
* current stream tip rather than replay notifications the native owner was
|
|
26
|
+
* responsible for.
|
|
27
|
+
*/
|
|
28
|
+
export declare function recordMonitorOwner(dir: string, owner: 'fleet' | 'native'): boolean;
|
|
22
29
|
/**
|
|
23
30
|
* Compose the tmux pane shell command: env prefix + argv + exit-status capture.
|
|
24
31
|
* `paneArgv` defaults to `launch.argv`; when isolation is active the caller passes
|
package/dist/runner.js
CHANGED
|
@@ -32,6 +32,24 @@ const defaultDeps = () => ({
|
|
|
32
32
|
fetch: (url, init) => globalThis.fetch(url, init),
|
|
33
33
|
createMonitor: opts => createMonitor(opts),
|
|
34
34
|
});
|
|
35
|
+
const MONITOR_OWNER_FILE = '.monitor-owner';
|
|
36
|
+
/**
|
|
37
|
+
* Record who owns wake delivery for this run. Returning true means a fleet
|
|
38
|
+
* monitor is taking ownership back from a native harness and must start at the
|
|
39
|
+
* current stream tip rather than replay notifications the native owner was
|
|
40
|
+
* responsible for.
|
|
41
|
+
*/
|
|
42
|
+
export function recordMonitorOwner(dir, owner) {
|
|
43
|
+
let previous = null;
|
|
44
|
+
try {
|
|
45
|
+
const path = join(dir, MONITOR_OWNER_FILE);
|
|
46
|
+
if (existsSync(path))
|
|
47
|
+
previous = readFileSync(path, 'utf8').trim();
|
|
48
|
+
writeFileSync(path, `${owner}\n`);
|
|
49
|
+
}
|
|
50
|
+
catch { /* ownership diagnostics must never take the role down */ }
|
|
51
|
+
return owner === 'fleet' && previous === 'native';
|
|
52
|
+
}
|
|
35
53
|
/**
|
|
36
54
|
* Compose the tmux pane shell command: env prefix + argv + exit-status capture.
|
|
37
55
|
* `paneArgv` defaults to `launch.argv`; when isolation is active the caller passes
|
|
@@ -334,18 +352,21 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
334
352
|
// leave wake ownership to the harness. Temp snapshots predating `monitor:` are
|
|
335
353
|
// treated as native (monitor may be undefined on an old role.yaml).
|
|
336
354
|
const resolvedMonitorDeps = monitorDeps(deps, role.env);
|
|
337
|
-
const
|
|
355
|
+
const monitorOwner = role.monitor?.mode === 'fleet' ? 'fleet' : 'native';
|
|
356
|
+
const resetMonitorCursor = recordMonitorOwner(dir, monitorOwner);
|
|
357
|
+
const monitor = monitorOwner === 'fleet' ? deps.createMonitor({
|
|
338
358
|
name, identity: role.identity, agentDir: dir, cfg: role.monitor,
|
|
339
359
|
deps: resolvedMonitorDeps,
|
|
340
360
|
}) : null;
|
|
341
361
|
if (monitor)
|
|
342
|
-
await monitor.prime();
|
|
362
|
+
await monitor.prime({ resetCursor: resetMonitorCursor });
|
|
343
363
|
rmSync(exitFile, { force: true });
|
|
344
364
|
let pid;
|
|
345
365
|
let sessionHandle;
|
|
346
366
|
let acpSession;
|
|
347
367
|
let control;
|
|
348
368
|
let monitorLoop;
|
|
369
|
+
let acpStartupComplete = false;
|
|
349
370
|
if (sessionBackend === 'acp') {
|
|
350
371
|
const perms = role.permissions ?? resolvePermissions(undefined, undefined);
|
|
351
372
|
// Say once, at startup, that this role will decide permission requests by
|
|
@@ -373,7 +394,12 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
373
394
|
// refusal or a cancellation reached the agent and was not acted on, so
|
|
374
395
|
// the monitor must keep its cursor and try again.
|
|
375
396
|
submit: async (text, options) => {
|
|
376
|
-
|
|
397
|
+
// Cancelling the runner-owned startup prompt makes startup look failed
|
|
398
|
+
// and closes the session before the wake turn can run. During startup,
|
|
399
|
+
// steer into the live turn instead; after it completes, honor the
|
|
400
|
+
// configured interrupt policy normally.
|
|
401
|
+
const interrupt = options?.interrupt === true && acpStartupComplete;
|
|
402
|
+
const result = await acpSession.submitPrompt(text, { ...options, interrupt, steer: true });
|
|
377
403
|
const steered = result.accepted
|
|
378
404
|
&& (result.detail === 'injected' || result.detail === 'startedNewTurn');
|
|
379
405
|
return {
|
|
@@ -390,8 +416,9 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
390
416
|
// startup prompt and then refuses it has not started; logging the role as
|
|
391
417
|
// up would hide a role that never read its briefing.
|
|
392
418
|
const starting = acpSession.submitPrompt(firstPrompt);
|
|
393
|
-
//
|
|
394
|
-
//
|
|
419
|
+
// Monitoring starts immediately. The delivery adapter above downgrades
|
|
420
|
+
// interruption to steering until this startup turn reaches a terminal
|
|
421
|
+
// success, so there is neither a deaf gap nor a boot-cancellation loop.
|
|
395
422
|
monitorLoop = monitor?.run(pid);
|
|
396
423
|
const started = await starting;
|
|
397
424
|
if (!started.succeeded) {
|
|
@@ -401,6 +428,7 @@ export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
|
401
428
|
throw new Error(`[${name}] ACP startup prompt ${started.outcome}` +
|
|
402
429
|
`${started.detail ? `: ${started.detail}` : ''}`);
|
|
403
430
|
}
|
|
431
|
+
acpStartupComplete = true;
|
|
404
432
|
}
|
|
405
433
|
else {
|
|
406
434
|
await deps.tmux.kill(name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, tmux or ACP sessions, supervision, and ours.network messaging.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|