@ours.network/fleet 0.9.5 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -0
- package/dist/atomic-file.d.ts +30 -0
- package/dist/atomic-file.js +86 -0
- package/dist/briefing.d.ts +6 -0
- package/dist/briefing.js +41 -11
- package/dist/cli.js +95 -21
- package/dist/config.d.ts +15 -1
- package/dist/config.js +47 -2
- package/dist/creation.d.ts +179 -0
- package/dist/creation.js +254 -0
- package/dist/docs.d.ts +28 -1
- package/dist/docs.js +132 -0
- package/dist/doctor.js +74 -16
- package/dist/harness/claude-code.d.ts +39 -3
- package/dist/harness/claude-code.js +126 -24
- package/dist/harness/codex.d.ts +7 -1
- package/dist/harness/codex.js +57 -10
- package/dist/harness/registry.d.ts +2 -0
- package/dist/harness/registry.js +19 -0
- package/dist/harness/types.d.ts +50 -3
- package/dist/isolation/bubblewrap.js +7 -1
- package/dist/isolation/policy.d.ts +34 -5
- package/dist/isolation/policy.js +114 -7
- package/dist/isolation/resources.d.ts +6 -3
- package/dist/isolation/resources.js +6 -3
- package/dist/isolation/types.d.ts +19 -1
- package/dist/monitor.d.ts +30 -3
- package/dist/monitor.js +63 -25
- package/dist/ops.d.ts +15 -2
- package/dist/ops.js +32 -9
- package/dist/permissions.d.ts +70 -0
- package/dist/permissions.js +97 -0
- package/dist/runner.d.ts +65 -2
- package/dist/runner.js +239 -19
- package/dist/session/acp.d.ts +22 -1
- package/dist/session/acp.js +110 -26
- package/dist/session/control.d.ts +49 -1
- package/dist/session/control.js +116 -12
- package/dist/session/tmux.d.ts +8 -1
- package/dist/session/tmux.js +34 -4
- package/dist/session/types.d.ts +92 -1
- package/dist/session/types.js +42 -1
- package/dist/spawn.d.ts +27 -2
- package/dist/spawn.js +153 -15
- package/dist/supervisor/launchd.d.ts +50 -0
- package/dist/supervisor/launchd.js +121 -4
- package/dist/supervisor/none.js +22 -4
- package/dist/supervisor/systemd.d.ts +8 -1
- package/dist/supervisor/systemd.js +94 -4
- package/dist/supervisor/types.d.ts +36 -3
- package/dist/tmux.d.ts +34 -2
- package/dist/tmux.js +48 -11
- package/package.json +1 -1
|
@@ -1,14 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed liveness verdict. `stopped` is a *definite* stop — the only state that
|
|
3
|
+
* lets a caller discard session context. `unknown` means the probe itself did
|
|
4
|
+
* not answer (bus unreachable, missing binary, unrecognised state) and must be
|
|
5
|
+
* treated as "may still be running".
|
|
6
|
+
*/
|
|
7
|
+
export type LivenessState = 'running' | 'stopped' | 'unknown';
|
|
8
|
+
export interface Liveness {
|
|
9
|
+
state: LivenessState;
|
|
10
|
+
/** The backend's own words: native state tokens, or the probe failure. */
|
|
11
|
+
detail: string;
|
|
12
|
+
}
|
|
13
|
+
/** Whether `install` had to create the registration, or found it already there. */
|
|
14
|
+
export interface InstallOutcome {
|
|
15
|
+
created: boolean;
|
|
16
|
+
detail: string;
|
|
17
|
+
}
|
|
18
|
+
export interface UninstallOutcome {
|
|
19
|
+
removed: boolean;
|
|
20
|
+
detail: string;
|
|
21
|
+
}
|
|
1
22
|
export interface SupervisorBackend {
|
|
2
23
|
id: 'systemd' | 'launchd' | 'none';
|
|
3
24
|
/** One-time host setup (unit template / dirs / linger). Returns human-readable messages. */
|
|
4
25
|
init(binPath: string): Promise<string[]>;
|
|
5
|
-
/**
|
|
6
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Ensure the role's unit exists and is enabled + started. Idempotent, and
|
|
28
|
+
* EXPLICIT about whether it created the registration: rollback may only
|
|
29
|
+
* remove what this transaction made, so "did I create this?" has to be
|
|
30
|
+
* answerable rather than assumed (6.2).
|
|
31
|
+
*/
|
|
32
|
+
install(name: string, binPath: string): Promise<InstallOutcome>;
|
|
7
33
|
start(name: string): Promise<void>;
|
|
8
34
|
stop(name: string): Promise<void>;
|
|
9
35
|
restart(name: string): Promise<void>;
|
|
10
36
|
status(name: string): Promise<string>;
|
|
11
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Classify the role's liveness from the backend's own native result — never
|
|
39
|
+
* by matching prose in `status()`. Implementations must not throw: a failed
|
|
40
|
+
* probe is `unknown` with the failure in `detail`.
|
|
41
|
+
*/
|
|
42
|
+
liveness(name: string): Promise<Liveness>;
|
|
43
|
+
/** Remove the registration. Idempotent; reports whether anything was there. */
|
|
44
|
+
uninstall(name: string): Promise<UninstallOutcome>;
|
|
12
45
|
/** Command the CLI execs (stdio inherited) to show logs. */
|
|
13
46
|
logsArgs(name: string, follow: boolean): {
|
|
14
47
|
cmd: string;
|
package/dist/tmux.d.ts
CHANGED
|
@@ -1,14 +1,46 @@
|
|
|
1
1
|
import { type Exec } from './exec.js';
|
|
2
|
+
/** Socket-name prefix for every tmux server this fleet starts. */
|
|
3
|
+
export declare const TMUX_SOCKET_PREFIX = "ours-fleet-";
|
|
4
|
+
/**
|
|
5
|
+
* The tmux socket a session lives on — ONE SERVER PER SESSION (#32).
|
|
6
|
+
*
|
|
7
|
+
* Without `-L`, every role's pane lands on the single default tmux server. That
|
|
8
|
+
* server is a child of whichever role happened to start it first, so it sits in
|
|
9
|
+
* that role's unit cgroup: stopping that one unit takes down every other role's
|
|
10
|
+
* pane with it. A per-session socket puts each role's server in its own unit,
|
|
11
|
+
* which is what makes `stop` local to the role being stopped.
|
|
12
|
+
*
|
|
13
|
+
* Role names are `[A-Za-z0-9_-]+` (config.ts), so this is always a usable
|
|
14
|
+
* socket filename.
|
|
15
|
+
*/
|
|
16
|
+
export declare const tmuxSocket: (session: string) => string;
|
|
17
|
+
/**
|
|
18
|
+
* Address a tmux command at that session's own server. EVERY tmux invocation in
|
|
19
|
+
* this repository must go through here — one that forgets `-L` silently talks to
|
|
20
|
+
* the shared default server and re-creates #32.
|
|
21
|
+
*/
|
|
22
|
+
export declare const tmuxArgs: (session: string, args: string[]) => string[];
|
|
2
23
|
/** Thin tmux wrapper; all session handling in the core goes through this. */
|
|
3
24
|
export declare class Tmux {
|
|
4
25
|
private exec;
|
|
5
26
|
constructor(exec?: Exec);
|
|
6
27
|
has(name: string): Promise<boolean>;
|
|
7
28
|
newSession(name: string, cwd: string, shellCommand: string): Promise<void>;
|
|
8
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Best-effort kill. Returns whether a session was actually there to destroy —
|
|
31
|
+
* the caller needs that to tell "we tore this session down" apart from "the
|
|
32
|
+
* program inside it exited on its own".
|
|
33
|
+
*/
|
|
34
|
+
kill(name: string): Promise<boolean>;
|
|
9
35
|
capture(name: string, lines?: number): Promise<string>;
|
|
10
36
|
panePid(name: string): Promise<number | null>;
|
|
11
|
-
|
|
37
|
+
/**
|
|
38
|
+
* List the live sessions among `names`, asking each server in turn.
|
|
39
|
+
* There is no fleet-wide `tmux ls` any more: a session per server means the
|
|
40
|
+
* caller says which sessions to ask about. A server that is not running
|
|
41
|
+
* answers non-zero and contributes nothing.
|
|
42
|
+
*/
|
|
43
|
+
list(names: readonly string[]): Promise<string>;
|
|
12
44
|
sendText(name: string, text: string): Promise<void>;
|
|
13
45
|
sendKey(name: string, key: string): Promise<void>;
|
|
14
46
|
}
|
package/dist/tmux.js
CHANGED
|
@@ -1,4 +1,25 @@
|
|
|
1
1
|
import { realExec } from './exec.js';
|
|
2
|
+
/** Socket-name prefix for every tmux server this fleet starts. */
|
|
3
|
+
export const TMUX_SOCKET_PREFIX = 'ours-fleet-';
|
|
4
|
+
/**
|
|
5
|
+
* The tmux socket a session lives on — ONE SERVER PER SESSION (#32).
|
|
6
|
+
*
|
|
7
|
+
* Without `-L`, every role's pane lands on the single default tmux server. That
|
|
8
|
+
* server is a child of whichever role happened to start it first, so it sits in
|
|
9
|
+
* that role's unit cgroup: stopping that one unit takes down every other role's
|
|
10
|
+
* pane with it. A per-session socket puts each role's server in its own unit,
|
|
11
|
+
* which is what makes `stop` local to the role being stopped.
|
|
12
|
+
*
|
|
13
|
+
* Role names are `[A-Za-z0-9_-]+` (config.ts), so this is always a usable
|
|
14
|
+
* socket filename.
|
|
15
|
+
*/
|
|
16
|
+
export const tmuxSocket = (session) => `${TMUX_SOCKET_PREFIX}${session}`;
|
|
17
|
+
/**
|
|
18
|
+
* Address a tmux command at that session's own server. EVERY tmux invocation in
|
|
19
|
+
* this repository must go through here — one that forgets `-L` silently talks to
|
|
20
|
+
* the shared default server and re-creates #32.
|
|
21
|
+
*/
|
|
22
|
+
export const tmuxArgs = (session, args) => ['-L', tmuxSocket(session), ...args];
|
|
2
23
|
/** Thin tmux wrapper; all session handling in the core goes through this. */
|
|
3
24
|
export class Tmux {
|
|
4
25
|
exec;
|
|
@@ -6,44 +27,60 @@ export class Tmux {
|
|
|
6
27
|
this.exec = exec;
|
|
7
28
|
}
|
|
8
29
|
async has(name) {
|
|
9
|
-
return (await this.exec('tmux', ['has-session', '-t', name])).code === 0;
|
|
30
|
+
return (await this.exec('tmux', tmuxArgs(name, ['has-session', '-t', name]))).code === 0;
|
|
10
31
|
}
|
|
11
32
|
async newSession(name, cwd, shellCommand) {
|
|
12
|
-
const r = await this.exec('tmux', ['new-session', '-d', '-s', name, '-c', cwd, shellCommand]);
|
|
33
|
+
const r = await this.exec('tmux', tmuxArgs(name, ['new-session', '-d', '-s', name, '-c', cwd, shellCommand]));
|
|
13
34
|
if (r.code !== 0)
|
|
14
35
|
throw new Error(`tmux new-session '${name}' failed (${r.code}): ${r.stderr.trim()}`);
|
|
15
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Best-effort kill. Returns whether a session was actually there to destroy —
|
|
39
|
+
* the caller needs that to tell "we tore this session down" apart from "the
|
|
40
|
+
* program inside it exited on its own".
|
|
41
|
+
*/
|
|
16
42
|
async kill(name) {
|
|
17
|
-
await this.exec('tmux', ['kill-session', '-t', name])
|
|
43
|
+
return (await this.exec('tmux', tmuxArgs(name, ['kill-session', '-t', name]))).code === 0;
|
|
18
44
|
}
|
|
19
45
|
async capture(name, lines = 40) {
|
|
20
|
-
const r = await this.exec('tmux', ['capture-pane', '-t', name, '-p']);
|
|
46
|
+
const r = await this.exec('tmux', tmuxArgs(name, ['capture-pane', '-t', name, '-p']));
|
|
21
47
|
if (r.code !== 0)
|
|
22
48
|
throw new Error(`tmux capture-pane '${name}' failed: ${r.stderr.trim()}`);
|
|
23
49
|
const all = r.stdout.replace(/\n+$/, '').split('\n');
|
|
24
50
|
return all.slice(-lines).join('\n');
|
|
25
51
|
}
|
|
26
52
|
async panePid(name) {
|
|
27
|
-
const r = await this.exec('tmux', ['list-panes', '-t', name, '-F', '#{pane_pid}']);
|
|
53
|
+
const r = await this.exec('tmux', tmuxArgs(name, ['list-panes', '-t', name, '-F', '#{pane_pid}']));
|
|
28
54
|
if (r.code !== 0)
|
|
29
55
|
return null;
|
|
30
56
|
const pid = parseInt(r.stdout.trim().split('\n')[0], 10);
|
|
31
57
|
return Number.isFinite(pid) ? pid : null;
|
|
32
58
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
59
|
+
/**
|
|
60
|
+
* List the live sessions among `names`, asking each server in turn.
|
|
61
|
+
* There is no fleet-wide `tmux ls` any more: a session per server means the
|
|
62
|
+
* caller says which sessions to ask about. A server that is not running
|
|
63
|
+
* answers non-zero and contributes nothing.
|
|
64
|
+
*/
|
|
65
|
+
async list(names) {
|
|
66
|
+
const lines = [];
|
|
67
|
+
for (const name of names) {
|
|
68
|
+
const r = await this.exec('tmux', tmuxArgs(name, ['ls']));
|
|
69
|
+
if (r.code === 0 && r.stdout.trim())
|
|
70
|
+
lines.push(r.stdout.trimEnd());
|
|
71
|
+
}
|
|
72
|
+
return lines.join('\n');
|
|
36
73
|
}
|
|
37
74
|
async sendText(name, text) {
|
|
38
|
-
let r = await this.exec('tmux', ['send-keys', '-t', name, '-l', text]);
|
|
75
|
+
let r = await this.exec('tmux', tmuxArgs(name, ['send-keys', '-t', name, '-l', text]));
|
|
39
76
|
if (r.code !== 0)
|
|
40
77
|
throw new Error(`tmux send-keys '${name}' failed: ${r.stderr.trim()}`);
|
|
41
|
-
r = await this.exec('tmux', ['send-keys', '-t', name, 'Enter']);
|
|
78
|
+
r = await this.exec('tmux', tmuxArgs(name, ['send-keys', '-t', name, 'Enter']));
|
|
42
79
|
if (r.code !== 0)
|
|
43
80
|
throw new Error(`tmux send-keys Enter '${name}' failed: ${r.stderr.trim()}`);
|
|
44
81
|
}
|
|
45
82
|
async sendKey(name, key) {
|
|
46
|
-
const r = await this.exec('tmux', ['send-keys', '-t', name, key]);
|
|
83
|
+
const r = await this.exec('tmux', tmuxArgs(name, ['send-keys', '-t', name, key]));
|
|
47
84
|
if (r.code !== 0)
|
|
48
85
|
throw new Error(`tmux send-keys '${name}' failed: ${r.stderr.trim()}`);
|
|
49
86
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.7",
|
|
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",
|