@ours.network/fleet 0.7.0 → 0.7.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/README.md +6 -1
- package/dist/cli.js +3 -3
- package/dist/ops.d.ts +3 -2
- package/dist/ops.js +11 -4
- package/dist/runner.js +20 -2
- package/dist/spawn.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ The state dir contract:
|
|
|
62
62
|
| `briefing.md` | generated | rewritten on every `up`/`restart`; never hand-edit |
|
|
63
63
|
| `WORKLOG.md` | the agent | seeded empty, agent-appended; survives restarts |
|
|
64
64
|
| `ROUTINES.md` | operator / agent | **optional** recurring-work instructions; re-read at the start of every wake, hot-editable **without a restart**; absence means "no routines" |
|
|
65
|
-
| `.identity`, `.cwd`, `.session-id`, `.booted`, `.exit-status` | supervisor | dot-marker state — session resume and boot bookkeeping |
|
|
65
|
+
| `.identity`, `.cwd`, `.session-id`, `.booted`, `.exit-status`, `.config-path` | supervisor | dot-marker state — session resume and boot bookkeeping |
|
|
66
66
|
|
|
67
67
|
## Prerequisites
|
|
68
68
|
|
|
@@ -169,6 +169,11 @@ ours-fleet doctor [--harness H]
|
|
|
169
169
|
ours-fleet init
|
|
170
170
|
```
|
|
171
171
|
|
|
172
|
+
A permanent role brought up via `-c custom.yaml` remembers that file (`.config-path`
|
|
173
|
+
in its state dir) across supervisor-triggered restarts — systemd/launchd re-invoke the
|
|
174
|
+
agent process with no arguments, so without this the role would silently fall back to
|
|
175
|
+
the default `~/fleet.yaml` on its very first crash-restart and fail to resolve.
|
|
176
|
+
|
|
172
177
|
## fleet.yaml reference
|
|
173
178
|
|
|
174
179
|
```yaml
|
package/dist/cli.js
CHANGED
|
@@ -101,7 +101,7 @@ cOpt(program.command('config').description('validate + print the merged plan (no
|
|
|
101
101
|
cOpt(program.command('up [names...]').description('create/start every role (or just the named ones)'))
|
|
102
102
|
.action(async (names, opts) => {
|
|
103
103
|
try {
|
|
104
|
-
await up(loadConfig(opts.configuration), names, deps());
|
|
104
|
+
await up(loadConfig(opts.configuration), names, deps(), opts.configuration);
|
|
105
105
|
}
|
|
106
106
|
catch (e) {
|
|
107
107
|
die(e);
|
|
@@ -119,7 +119,7 @@ cOpt(program.command('down [names...]').description('stop roles'))
|
|
|
119
119
|
cOpt(program.command('restart [names...]').description('re-sync config + bounce, RESUMING context'))
|
|
120
120
|
.action(async (names, opts) => {
|
|
121
121
|
try {
|
|
122
|
-
await restartRoles(loadConfig(opts.configuration), names, deps(), 'keep');
|
|
122
|
+
await restartRoles(loadConfig(opts.configuration), names, deps(), 'keep', opts.configuration);
|
|
123
123
|
}
|
|
124
124
|
catch (e) {
|
|
125
125
|
die(e);
|
|
@@ -128,7 +128,7 @@ cOpt(program.command('restart [names...]').description('re-sync config + bounce,
|
|
|
128
128
|
cOpt(program.command('force-restart [names...]').description('re-sync + bounce FRESH (context wiped)'))
|
|
129
129
|
.action(async (names, opts) => {
|
|
130
130
|
try {
|
|
131
|
-
await restartRoles(loadConfig(opts.configuration), names, deps(), 'fresh');
|
|
131
|
+
await restartRoles(loadConfig(opts.configuration), names, deps(), 'fresh', opts.configuration);
|
|
132
132
|
}
|
|
133
133
|
catch (e) {
|
|
134
134
|
die(e);
|
package/dist/ops.d.ts
CHANGED
|
@@ -10,11 +10,12 @@ export interface OpsDeps {
|
|
|
10
10
|
export declare function applyRole(role: ResolvedRole, opts?: {
|
|
11
11
|
fresh?: boolean;
|
|
12
12
|
temp?: boolean;
|
|
13
|
+
configPath?: string;
|
|
13
14
|
}): string;
|
|
14
15
|
/** Create/start roles declaratively. Idempotent; active roles keep their context. */
|
|
15
|
-
export declare function up(cfg: FleetConfig, names: string[], deps: OpsDeps): Promise<void>;
|
|
16
|
+
export declare function up(cfg: FleetConfig, names: string[], deps: OpsDeps, configPath?: string): Promise<void>;
|
|
16
17
|
export declare function down(cfg: FleetConfig, names: string[], deps: OpsDeps): Promise<void>;
|
|
17
18
|
/** Re-sync from config + bounce. mode 'keep' resumes context; 'fresh' wipes it. */
|
|
18
|
-
export declare function restartRoles(cfg: FleetConfig, names: string[], deps: OpsDeps, mode: 'keep' | 'fresh'): Promise<void>;
|
|
19
|
+
export declare function restartRoles(cfg: FleetConfig, names: string[], deps: OpsDeps, mode: 'keep' | 'fresh', configPath?: string): Promise<void>;
|
|
19
20
|
/** Stop + forget a role: unit, state dir, and its fleet.d file when spawned. */
|
|
20
21
|
export declare function rmRole(cfg: FleetConfig, name: string, deps: OpsDeps): Promise<void>;
|
package/dist/ops.js
CHANGED
|
@@ -14,6 +14,13 @@ export function applyRole(role, opts = {}) {
|
|
|
14
14
|
throw new Error(`role '${role.name}': ` + errs.map(e => `${e.path}: ${e.message}`).join('; '));
|
|
15
15
|
const dir = agentDir(role.name, opts.temp === true);
|
|
16
16
|
mkdirSync(dir, { recursive: true });
|
|
17
|
+
// systemd's shared unit template invokes `_run <name>` on every restart with no
|
|
18
|
+
// -c (see supervisor/systemd.ts) — record which config this permanent role was
|
|
19
|
+
// brought up from so the supervised process can reload the SAME file instead of
|
|
20
|
+
// silently falling back to the default ~/fleet.yaml. Temp roles snapshot their
|
|
21
|
+
// whole resolved role into role.yaml instead and don't need this.
|
|
22
|
+
if (!opts.temp)
|
|
23
|
+
writeFileSync(join(dir, '.config-path'), (opts.configPath ?? '') + '\n');
|
|
17
24
|
writeFileSync(join(dir, '.identity'), role.identity + '\n');
|
|
18
25
|
if (role.cwd)
|
|
19
26
|
writeFileSync(join(dir, '.cwd'), role.cwd + '\n');
|
|
@@ -35,13 +42,13 @@ function selectRoles(cfg, names) {
|
|
|
35
42
|
return names.length ? names.map(n => findRole(cfg, n)) : cfg.roles;
|
|
36
43
|
}
|
|
37
44
|
/** Create/start roles declaratively. Idempotent; active roles keep their context. */
|
|
38
|
-
export async function up(cfg, names, deps) {
|
|
45
|
+
export async function up(cfg, names, deps, configPath) {
|
|
39
46
|
let first = true;
|
|
40
47
|
for (const role of selectRoles(cfg, names)) {
|
|
41
48
|
if (!first)
|
|
42
49
|
await deps.sleep(STAGGER_MS());
|
|
43
50
|
first = false;
|
|
44
|
-
const dir = applyRole(role);
|
|
51
|
+
const dir = applyRole(role, { configPath });
|
|
45
52
|
// If the role isn't running, boot fresh so it reads the briefing we just wrote.
|
|
46
53
|
const status = await deps.backend.status(role.name).catch(() => '');
|
|
47
54
|
if (!/running|active \(/.test(status))
|
|
@@ -62,13 +69,13 @@ export async function down(cfg, names, deps) {
|
|
|
62
69
|
}
|
|
63
70
|
}
|
|
64
71
|
/** Re-sync from config + bounce. mode 'keep' resumes context; 'fresh' wipes it. */
|
|
65
|
-
export async function restartRoles(cfg, names, deps, mode) {
|
|
72
|
+
export async function restartRoles(cfg, names, deps, mode, configPath) {
|
|
66
73
|
let first = true;
|
|
67
74
|
for (const role of selectRoles(cfg, names)) {
|
|
68
75
|
if (!first)
|
|
69
76
|
await deps.sleep(STAGGER_MS());
|
|
70
77
|
first = false;
|
|
71
|
-
applyRole(role, { fresh: mode === 'fresh' });
|
|
78
|
+
applyRole(role, { fresh: mode === 'fresh', configPath });
|
|
72
79
|
await deps.backend.restart(role.name);
|
|
73
80
|
deps.log(mode === 'fresh'
|
|
74
81
|
? `↻ ${role.name} — force-restarted (FRESH — context cleared, briefing reloaded)`
|
package/dist/runner.js
CHANGED
|
@@ -47,13 +47,31 @@ export function loadTempRole(name) {
|
|
|
47
47
|
role.__temp = true;
|
|
48
48
|
return role;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Fall back to the config path applyRole() recorded at the last up/restart/spawn
|
|
52
|
+
* for this role. systemd's shared unit template (`ours-fleet-agent@.service`)
|
|
53
|
+
* execs `_run <name>` with no -c on every restart, so a role brought up from a
|
|
54
|
+
* non-default config (`ours-fleet up -c custom.yaml`) would otherwise silently
|
|
55
|
+
* resolve against the default ~/fleet.yaml on its very first restart and fail
|
|
56
|
+
* with "no such role". An empty/missing marker means "use the default", same as
|
|
57
|
+
* no -c was ever given.
|
|
58
|
+
*/
|
|
59
|
+
function resolveConfigPath(dir, explicit) {
|
|
60
|
+
if (explicit)
|
|
61
|
+
return explicit;
|
|
62
|
+
const marker = join(dir, '.config-path');
|
|
63
|
+
if (!existsSync(marker))
|
|
64
|
+
return undefined;
|
|
65
|
+
return readFileSync(marker, 'utf8').trim() || undefined;
|
|
66
|
+
}
|
|
50
67
|
/** One supervised session lifecycle. The supervisor re-invokes us after we return. */
|
|
51
68
|
export async function runOnce(name, opts = {}, partialDeps = {}) {
|
|
52
69
|
const deps = { ...defaultDeps(), ...partialDeps };
|
|
53
70
|
const temp = opts.temp === true;
|
|
54
|
-
const role = temp ? loadTempRole(name) : findRole(loadConfig(opts.configPath), name);
|
|
55
|
-
const adapter = getAdapter(role.harness);
|
|
56
71
|
const dir = agentDir(name, temp);
|
|
72
|
+
const configPath = temp ? opts.configPath : resolveConfigPath(dir, opts.configPath);
|
|
73
|
+
const role = temp ? loadTempRole(name) : findRole(loadConfig(configPath), name);
|
|
74
|
+
const adapter = getAdapter(role.harness);
|
|
57
75
|
mkdirSync(dir, { recursive: true });
|
|
58
76
|
const sidFile = join(dir, '.session-id');
|
|
59
77
|
if (!existsSync(sidFile))
|
package/dist/spawn.js
CHANGED
|
@@ -61,7 +61,7 @@ export async function spawnPermanent(o, deps) {
|
|
|
61
61
|
writeFileSync(file, stringify({
|
|
62
62
|
roles: { [o.name]: roleFromOpts(o, cfg.defaults.harness) },
|
|
63
63
|
}));
|
|
64
|
-
await up(loadConfig(o.configPath), [o.name], deps);
|
|
64
|
+
await up(loadConfig(o.configPath), [o.name], deps, o.configPath);
|
|
65
65
|
return file;
|
|
66
66
|
}
|
|
67
67
|
const detachedSupervisor = (binPath, args, dir) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, tmux consoles, systemd/launchd supervision, ours.network messaging.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|