@ours.network/fleet 1.1.5 → 1.2.0-nightly.10
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 +163 -21
- package/dist/agent-ours/bridge.d.ts +3 -0
- package/dist/agent-ours/bridge.js +178 -0
- package/dist/agent-ours/controller.d.ts +14 -0
- package/dist/agent-ours/controller.js +60 -0
- package/dist/agent-ours/file-rpc.d.ts +23 -0
- package/dist/agent-ours/file-rpc.js +123 -0
- package/dist/agent-ours/harness.d.ts +14 -0
- package/dist/agent-ours/harness.js +64 -0
- package/dist/agent-ours/mcp-endpoint.d.ts +15 -0
- package/dist/agent-ours/mcp-endpoint.js +111 -0
- package/dist/agent-ours/runtime.d.ts +53 -0
- package/dist/agent-ours/runtime.js +247 -0
- package/dist/agent-ours/service.d.ts +23 -0
- package/dist/agent-ours/service.js +364 -0
- package/dist/agent-ours/state.d.ts +45 -0
- package/dist/agent-ours/state.js +95 -0
- package/dist/agent-ours/wire.d.ts +14 -0
- package/dist/agent-ours/wire.js +55 -0
- package/dist/agent-recovery-gate.js +1 -1
- package/dist/application/legacy-supervisor-identity.d.ts +7 -0
- package/dist/application/legacy-supervisor-identity.js +43 -0
- package/dist/application/supervisor-ours-tools.d.ts +159 -0
- package/dist/application/supervisor-ours-tools.js +121 -0
- package/dist/application/task-room-service.js +55 -19
- package/dist/briefing.js +10 -59
- package/dist/build-info.json +6 -5
- package/dist/capabilities.d.ts +2 -1
- package/dist/capabilities.js +2 -0
- package/dist/cli.js +75 -8
- package/dist/client-profile.d.ts +18 -0
- package/dist/client-profile.js +130 -0
- package/dist/config.d.ts +1 -0
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +13 -21
- package/dist/daemon-recovery.d.ts +2 -0
- package/dist/daemon-recovery.js +53 -2
- package/dist/docs.d.ts +3 -3
- package/dist/docs.js +32 -23
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +70 -14
- package/dist/fleet-command-audit.js +3 -2
- package/dist/harness/agent-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +4 -2
- package/dist/harness/codex-session.js +9 -2
- package/dist/harness/codex.d.ts +3 -2
- package/dist/harness/codex.js +8 -3
- package/dist/harness/hermes-session.js +7 -1
- package/dist/init-wizard.d.ts +11 -6
- package/dist/init-wizard.js +33 -0
- package/dist/monitor.d.ts +34 -3
- package/dist/monitor.js +174 -74
- package/dist/owner-channel/channel.d.ts +3 -0
- package/dist/owner-channel/channel.js +28 -6
- package/dist/owner-channel/ours-client.d.ts +12 -5
- package/dist/owner-channel/ours-client.js +48 -12
- package/dist/rooms-tasks/archived-absence.d.ts +8 -0
- package/dist/rooms-tasks/archived-absence.js +48 -0
- package/dist/rooms-tasks/cli.js +1 -1
- package/dist/rooms-tasks/close.d.ts +3 -0
- package/dist/rooms-tasks/close.js +95 -17
- package/dist/rooms-tasks/cowork-adapter.d.ts +4 -0
- package/dist/rooms-tasks/cowork-adapter.js +10 -2
- package/dist/rooms-tasks/cowork-http.d.ts +3 -0
- package/dist/rooms-tasks/cowork-http.js +70 -0
- package/dist/rooms-tasks/deletion.js +40 -3
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +2 -0
- package/dist/rooms-tasks/provision.d.ts +11 -0
- package/dist/rooms-tasks/provision.js +138 -26
- package/dist/rooms-tasks/room-state.js +13 -0
- package/dist/rooms-tasks/task-state.d.ts +13 -3
- package/dist/rooms-tasks/task-state.js +42 -6
- package/dist/rooms-tasks/types.d.ts +12 -0
- package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
- package/dist/rooms-tasks/workspace-artifacts.js +93 -0
- package/dist/rooms-tasks/workspace.d.ts +26 -0
- package/dist/rooms-tasks/workspace.js +195 -0
- package/dist/runner.d.ts +14 -0
- package/dist/runner.js +540 -494
- package/dist/session/codex-app-server-transport.js +1 -1
- package/dist/session/codex-app-server.js +31 -5
- package/dist/spawn.js +19 -16
- package/dist/startup-readiness.d.ts +13 -0
- package/dist/startup-readiness.js +38 -0
- package/dist/supervisor/launchd.js +8 -1
- package/dist/supervisor/systemd.js +5 -0
- package/dist/temp-supervisor-recovery.d.ts +3 -0
- package/dist/temp-supervisor-recovery.js +46 -0
- package/dist/watchdog/briefing.js +3 -16
- package/dist/watchdog/run.js +19 -5
- package/dist/web/server.d.ts +2 -0
- package/dist/web/server.js +18 -1
- package/docs/task-workspaces.md +98 -0
- package/package.json +8 -4
- package/presets/fleet/roles/Coordinator.yaml +22 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { lstatSync, mkdirSync, readdirSync, readFileSync, renameSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
3
|
+
import { parse } from 'yaml';
|
|
4
|
+
import { stateRoot } from '../paths.js';
|
|
5
|
+
import { assertSafeAncestors, auditWorkspaceGit, validateWorkspace, ensureWorkspace, WorkspaceError } from './workspace.js';
|
|
6
|
+
/** Runtime control state stays in Fleet's live roster and recovery store. Its
|
|
7
|
+
* durable launch descriptor, not a name prefix or caller-supplied path, proves
|
|
8
|
+
* artifact ownership. Audit receipts/journals intentionally outlive deletion.
|
|
9
|
+
*/
|
|
10
|
+
export function collectWorkspaceArchives(workspace) {
|
|
11
|
+
validateWorkspace(workspace);
|
|
12
|
+
const root = join(stateRoot(), 'recovery', 'temporary');
|
|
13
|
+
assertSafeAncestors(root);
|
|
14
|
+
let entries;
|
|
15
|
+
try {
|
|
16
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
if (e.code === 'ENOENT')
|
|
20
|
+
return;
|
|
21
|
+
throw e;
|
|
22
|
+
}
|
|
23
|
+
const owned = [];
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
26
|
+
continue;
|
|
27
|
+
const path = join(root, entry.name);
|
|
28
|
+
const roleFile = join(path, 'role.yaml');
|
|
29
|
+
let role;
|
|
30
|
+
try {
|
|
31
|
+
const s = lstatSync(roleFile);
|
|
32
|
+
if (!s.isFile() || s.isSymbolicLink())
|
|
33
|
+
continue;
|
|
34
|
+
role = parse(readFileSync(roleFile, 'utf8'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
continue;
|
|
38
|
+
} // no ownership proof: never adopt a foreign archive
|
|
39
|
+
const recorded = role?.roomMemberStartup?.workspace;
|
|
40
|
+
if (recorded?.owner !== workspace.owner || recorded.id !== workspace.id)
|
|
41
|
+
continue;
|
|
42
|
+
if (JSON.stringify(recorded) !== JSON.stringify(workspace))
|
|
43
|
+
throw new WorkspaceError('workspace archive ownership mismatch');
|
|
44
|
+
assertSafeAncestors(path);
|
|
45
|
+
const termination = join(path, 'termination.jsonl');
|
|
46
|
+
if (!lstatSync(termination).isFile() || lstatSync(termination).isSymbolicLink())
|
|
47
|
+
throw new WorkspaceError('unsafe workspace archive termination evidence');
|
|
48
|
+
const supervisorPath = join(path, '.temp-supervisor.json');
|
|
49
|
+
let supervisor;
|
|
50
|
+
try {
|
|
51
|
+
if (!lstatSync(supervisorPath).isFile() || lstatSync(supervisorPath).isSymbolicLink())
|
|
52
|
+
throw new WorkspaceError('unsafe workspace archive supervisor evidence');
|
|
53
|
+
supervisor = JSON.parse(readFileSync(supervisorPath, 'utf8'));
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
if (e.code !== 'ENOENT')
|
|
57
|
+
throw e;
|
|
58
|
+
}
|
|
59
|
+
if (supervisor && (supervisor.role !== role.name || !supervisor.launchId))
|
|
60
|
+
throw new WorkspaceError('workspace archive launch ownership mismatch');
|
|
61
|
+
const terminated = readFileSync(termination, 'utf8').trim().split('\n').some(line => {
|
|
62
|
+
const value = JSON.parse(line);
|
|
63
|
+
return value.version === 1 && value.role === role.name
|
|
64
|
+
&& value.launchId === supervisor?.launchId
|
|
65
|
+
&& ['retired', 'reclaimed', 'failed'].includes(value.outcome);
|
|
66
|
+
});
|
|
67
|
+
if (!terminated || entry.name.endsWith('.retiring'))
|
|
68
|
+
throw new WorkspaceError('workspace archive retirement is incomplete');
|
|
69
|
+
auditWorkspaceGit(path);
|
|
70
|
+
owned.push(path);
|
|
71
|
+
}
|
|
72
|
+
// Preflight the entire set before mutation; task/room record remains until all succeed.
|
|
73
|
+
if (!owned.length)
|
|
74
|
+
return;
|
|
75
|
+
ensureWorkspace(workspace);
|
|
76
|
+
const destination = join(workspace.path, '.fleet-retired-agents');
|
|
77
|
+
assertSafeAncestors(destination);
|
|
78
|
+
mkdirSync(destination, { recursive: true, mode: 0o700 });
|
|
79
|
+
for (const path of owned) {
|
|
80
|
+
const target = join(destination, basename(path));
|
|
81
|
+
try {
|
|
82
|
+
lstatSync(target);
|
|
83
|
+
throw new WorkspaceError('archive collection destination exists');
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
if (e.code !== 'ENOENT')
|
|
87
|
+
throw e;
|
|
88
|
+
}
|
|
89
|
+
// Atomic transfer before workspace deletion: a crash never loses ownership
|
|
90
|
+
// evidence halfway through recursively removing a global recovery archive.
|
|
91
|
+
renameSync(path, target);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Persisted before allocation. Never infer ownership from an Agent's cwd. */
|
|
2
|
+
export interface OwnedWorkspace {
|
|
3
|
+
version: 1;
|
|
4
|
+
owner: 'task' | 'room';
|
|
5
|
+
id: string;
|
|
6
|
+
path: string;
|
|
7
|
+
token: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class WorkspaceError extends Error {
|
|
10
|
+
}
|
|
11
|
+
export declare function assertSafeAncestors(path: string): void;
|
|
12
|
+
export declare function planWorkspace(owner: OwnedWorkspace['owner'], id: string): OwnedWorkspace;
|
|
13
|
+
export declare function validateWorkspace(w: OwnedWorkspace, owner?: "task" | "room", id?: string): void;
|
|
14
|
+
/** Atomic directory publication; retry uses the durable token, never adopts another folder. */
|
|
15
|
+
export declare function ensureWorkspace(w: OwnedWorkspace): string;
|
|
16
|
+
/** Audit both directions of Git worktree registration, without executing repository config/hooks.
|
|
17
|
+
* All registrations and common dirs must be inside this workspace. Removing the complete
|
|
18
|
+
* workspace then removes their registries atomically with the owned repositories; never prune.
|
|
19
|
+
*/
|
|
20
|
+
export declare function auditWorkspaceGit(path: string): void;
|
|
21
|
+
/** Called only after all managed writers retire, under the owner's lifecycle lock.
|
|
22
|
+
* The tombstone is derived from the durable unguessable token. A crash during rm
|
|
23
|
+
* resumes that exact tombstone even if rm already removed its marker.
|
|
24
|
+
*/
|
|
25
|
+
export declare function assertWorkspaceDeletable(w: OwnedWorkspace, owner: OwnedWorkspace['owner'], id: string): void;
|
|
26
|
+
export declare function deleteWorkspace(w: OwnedWorkspace, owner: OwnedWorkspace['owner'], id: string): void;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { stateRoot } from '../paths.js';
|
|
5
|
+
const MARKER = '.fleet-workspace.json';
|
|
6
|
+
export class WorkspaceError extends Error {
|
|
7
|
+
}
|
|
8
|
+
function fail(message) { throw new WorkspaceError(`workspace: ${message}`); }
|
|
9
|
+
function stat(path) {
|
|
10
|
+
try {
|
|
11
|
+
return lstatSync(path);
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
if (e.code === 'ENOENT')
|
|
15
|
+
return undefined;
|
|
16
|
+
throw e;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function assertSafeAncestors(path) {
|
|
20
|
+
const absolute = resolve(path);
|
|
21
|
+
let part = absolute;
|
|
22
|
+
for (;;) {
|
|
23
|
+
const s = stat(part);
|
|
24
|
+
if (s && (s.isSymbolicLink() || !s.isDirectory()))
|
|
25
|
+
fail(`unsafe directory ${part}`);
|
|
26
|
+
const parent = dirname(part);
|
|
27
|
+
if (parent === part)
|
|
28
|
+
break;
|
|
29
|
+
part = parent;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function root(owner) {
|
|
33
|
+
return resolve(stateRoot(), 'workspaces', owner === 'task' ? 'tasks' : 'rooms');
|
|
34
|
+
}
|
|
35
|
+
export function planWorkspace(owner, id) {
|
|
36
|
+
if (!/^[a-zA-Z0-9_-]{1,80}$/.test(id))
|
|
37
|
+
fail('invalid owner ID');
|
|
38
|
+
return { version: 1, owner, id, path: join(root(owner), id), token: randomUUID() };
|
|
39
|
+
}
|
|
40
|
+
export function validateWorkspace(w, owner = w.owner, id = w.id) {
|
|
41
|
+
if (w.version !== 1 || !['task', 'room'].includes(w.owner) || w.owner !== owner || w.id !== id
|
|
42
|
+
|| !/^[a-zA-Z0-9_-]{1,80}$/.test(w.id)
|
|
43
|
+
|| !/^[a-f0-9-]{36}$/.test(w.token) || !isAbsolute(w.path)
|
|
44
|
+
|| w.path !== join(root(w.owner), w.id))
|
|
45
|
+
fail('invalid recorded ownership/path');
|
|
46
|
+
assertSafeAncestors(dirname(w.path));
|
|
47
|
+
}
|
|
48
|
+
function marked(path, w) {
|
|
49
|
+
assertSafeAncestors(path);
|
|
50
|
+
const marker = join(path, MARKER);
|
|
51
|
+
if (!stat(marker)?.isFile() || stat(marker)?.isSymbolicLink())
|
|
52
|
+
fail('missing/unsafe ownership marker');
|
|
53
|
+
const actual = JSON.parse(readFileSync(marker, 'utf8'));
|
|
54
|
+
if (JSON.stringify(actual) !== JSON.stringify(w))
|
|
55
|
+
fail('ownership marker mismatch');
|
|
56
|
+
}
|
|
57
|
+
function staging(w, phase) {
|
|
58
|
+
return join(dirname(w.path), `.${w.id}.${w.token}.${phase}`);
|
|
59
|
+
}
|
|
60
|
+
/** Atomic directory publication; retry uses the durable token, never adopts another folder. */
|
|
61
|
+
export function ensureWorkspace(w) {
|
|
62
|
+
validateWorkspace(w);
|
|
63
|
+
if (stat(staging(w, 'deleting')))
|
|
64
|
+
fail('deletion already started');
|
|
65
|
+
if (stat(w.path)) {
|
|
66
|
+
marked(w.path, w);
|
|
67
|
+
return w.path;
|
|
68
|
+
}
|
|
69
|
+
mkdirSync(dirname(w.path), { recursive: true, mode: 0o700 });
|
|
70
|
+
assertSafeAncestors(dirname(w.path));
|
|
71
|
+
const stage = staging(w, 'provisioning');
|
|
72
|
+
if (!stat(stage))
|
|
73
|
+
mkdirSync(stage, { mode: 0o700 });
|
|
74
|
+
assertSafeAncestors(stage);
|
|
75
|
+
if (readdirSync(stage).length === 0)
|
|
76
|
+
writeFileSync(join(stage, MARKER), JSON.stringify(w), { flag: 'wx', mode: 0o600 });
|
|
77
|
+
marked(stage, w);
|
|
78
|
+
try {
|
|
79
|
+
renameSync(stage, w.path);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
// Another allocator may have published this exact durable intent.
|
|
83
|
+
if (!stat(w.path))
|
|
84
|
+
throw error;
|
|
85
|
+
marked(w.path, w);
|
|
86
|
+
}
|
|
87
|
+
return w.path;
|
|
88
|
+
}
|
|
89
|
+
function inside(base, path) {
|
|
90
|
+
const rel = relative(base, path);
|
|
91
|
+
return rel !== '' && rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
|
|
92
|
+
}
|
|
93
|
+
function ownedPointer(base, from, value) {
|
|
94
|
+
if (!value.trim() || /[\r\n]/.test(value.trim()))
|
|
95
|
+
fail('malformed git pointer');
|
|
96
|
+
const target = resolve(from, value.trim());
|
|
97
|
+
if (!inside(base, target))
|
|
98
|
+
fail(`foreign git metadata ${target}`);
|
|
99
|
+
assertSafeAncestors(dirname(target));
|
|
100
|
+
if (stat(target)?.isSymbolicLink())
|
|
101
|
+
fail(`symlink git metadata ${target}`);
|
|
102
|
+
return target;
|
|
103
|
+
}
|
|
104
|
+
/** Audit both directions of Git worktree registration, without executing repository config/hooks.
|
|
105
|
+
* All registrations and common dirs must be inside this workspace. Removing the complete
|
|
106
|
+
* workspace then removes their registries atomically with the owned repositories; never prune.
|
|
107
|
+
*/
|
|
108
|
+
export function auditWorkspaceGit(path) {
|
|
109
|
+
const device = lstatSync(path).dev;
|
|
110
|
+
const visit = (dir, admin = false) => {
|
|
111
|
+
admin ||= Boolean(stat(join(dir, 'HEAD')) && (stat(join(dir, 'objects')) || stat(join(dir, 'commondir'))));
|
|
112
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
113
|
+
const p = join(dir, entry.name);
|
|
114
|
+
if (entry.isSymbolicLink()) {
|
|
115
|
+
if (admin || entry.name === '.git' || entry.name === 'commondir' || entry.name === 'gitdir')
|
|
116
|
+
fail(`symlink git metadata ${p}`);
|
|
117
|
+
continue; // rm unlinks ordinary artifact symlinks, never follows them.
|
|
118
|
+
}
|
|
119
|
+
if (entry.isDirectory()) {
|
|
120
|
+
if (lstatSync(p).dev !== device)
|
|
121
|
+
fail(`foreign mounted directory ${p}`);
|
|
122
|
+
visit(p, admin || entry.name === '.git');
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!entry.isFile())
|
|
126
|
+
continue; // sockets/FIFOs in retired runtime state are unlinked, never opened
|
|
127
|
+
if (entry.name === '.git') {
|
|
128
|
+
const value = readFileSync(p, 'utf8').trim();
|
|
129
|
+
if (!value.startsWith('gitdir: '))
|
|
130
|
+
fail(`invalid git file ${p}`);
|
|
131
|
+
const gitdir = ownedPointer(path, dir, value.slice(8));
|
|
132
|
+
if (!stat(gitdir)?.isDirectory())
|
|
133
|
+
fail(`missing git directory ${gitdir}`);
|
|
134
|
+
}
|
|
135
|
+
// Git admin files, including bare repositories and submodules.
|
|
136
|
+
if (entry.name === 'commondir' && (admin || stat(join(dir, 'HEAD')))) {
|
|
137
|
+
const common = ownedPointer(path, dir, readFileSync(p, 'utf8'));
|
|
138
|
+
if (!stat(common)?.isDirectory())
|
|
139
|
+
fail(`invalid git common directory ${common}`);
|
|
140
|
+
}
|
|
141
|
+
if (entry.name === 'gitdir' && (admin || stat(join(dir, 'commondir')))) {
|
|
142
|
+
const target = ownedPointer(path, dir, readFileSync(p, 'utf8'));
|
|
143
|
+
if (stat(target) && !stat(target)?.isFile())
|
|
144
|
+
fail(`invalid worktree backlink ${target}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
visit(path);
|
|
149
|
+
}
|
|
150
|
+
/** Called only after all managed writers retire, under the owner's lifecycle lock.
|
|
151
|
+
* The tombstone is derived from the durable unguessable token. A crash during rm
|
|
152
|
+
* resumes that exact tombstone even if rm already removed its marker.
|
|
153
|
+
*/
|
|
154
|
+
export function assertWorkspaceDeletable(w, owner, id) {
|
|
155
|
+
validateWorkspace(w, owner, id);
|
|
156
|
+
if (stat(w.path)) {
|
|
157
|
+
if (stat(staging(w, 'deleting')))
|
|
158
|
+
fail('both live and deleting workspace exist');
|
|
159
|
+
marked(w.path, w);
|
|
160
|
+
auditWorkspaceGit(w.path);
|
|
161
|
+
}
|
|
162
|
+
const pending = staging(w, 'provisioning');
|
|
163
|
+
if (stat(pending)) {
|
|
164
|
+
assertSafeAncestors(pending);
|
|
165
|
+
if (readdirSync(pending).length)
|
|
166
|
+
marked(pending, w);
|
|
167
|
+
if (readdirSync(pending).some(name => name !== MARKER))
|
|
168
|
+
fail('unexpected provisioning artifacts');
|
|
169
|
+
}
|
|
170
|
+
if (stat(staging(w, 'deleting')))
|
|
171
|
+
assertSafeAncestors(staging(w, 'deleting'));
|
|
172
|
+
}
|
|
173
|
+
export function deleteWorkspace(w, owner, id) {
|
|
174
|
+
assertWorkspaceDeletable(w, owner, id);
|
|
175
|
+
const tomb = staging(w, 'deleting');
|
|
176
|
+
const pending = staging(w, 'provisioning');
|
|
177
|
+
if (stat(w.path)) {
|
|
178
|
+
if (stat(tomb))
|
|
179
|
+
fail('both live and deleting workspace exist');
|
|
180
|
+
marked(w.path, w);
|
|
181
|
+
auditWorkspaceGit(w.path);
|
|
182
|
+
renameSync(w.path, tomb);
|
|
183
|
+
}
|
|
184
|
+
if (stat(pending)) {
|
|
185
|
+
if (readdirSync(pending).length)
|
|
186
|
+
marked(pending, w);
|
|
187
|
+
if (readdirSync(pending).some(name => name !== MARKER))
|
|
188
|
+
fail('unexpected provisioning artifacts');
|
|
189
|
+
rmSync(pending, { recursive: true });
|
|
190
|
+
}
|
|
191
|
+
if (stat(tomb)) {
|
|
192
|
+
assertSafeAncestors(tomb);
|
|
193
|
+
rmSync(tomb, { recursive: true });
|
|
194
|
+
}
|
|
195
|
+
}
|
package/dist/runner.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { prepareManagedAgent, releaseManagedAgent } from './agent-ours/service.js';
|
|
1
2
|
import { type ResolvedRole } from './config.js';
|
|
2
3
|
import { type MonitorHandle, type MonitorOpts, type FetchLike } from './monitor.js';
|
|
3
4
|
import { type DaemonGenerationProbe } from './daemon-recovery.js';
|
|
@@ -9,6 +10,8 @@ import { type OwnerChannelHandle, type OwnerChannelOptions } from './owner-chann
|
|
|
9
10
|
import { type OwnerBinderLease } from './owner-channel/binder.js';
|
|
10
11
|
import { ScheduledLoopManager, type ScheduledLoopManagerHandle } from './loops/manager.js';
|
|
11
12
|
export interface RunnerDeps {
|
|
13
|
+
prepareAgentOurs: typeof prepareManagedAgent;
|
|
14
|
+
releaseAgentOurs: typeof releaseManagedAgent;
|
|
12
15
|
exec: Exec;
|
|
13
16
|
cpuDelegated(): boolean;
|
|
14
17
|
isAlive(pid: number): boolean;
|
|
@@ -146,6 +149,16 @@ export declare const START_STAGGER_FILE = ".start-stagger-ms";
|
|
|
146
149
|
export declare function reserveLaunchSlot(root: string, staggerMs: number, deps: Pick<RunnerDeps, 'now' | 'sleep' | 'log'>): Promise<number>;
|
|
147
150
|
/** Read a temp role's config snapshot written by spawnTemp. */
|
|
148
151
|
export declare function loadTempRole(name: string): ResolvedRole;
|
|
152
|
+
/**
|
|
153
|
+
* Fall back to the config path applyRole() recorded at the last up/restart/spawn
|
|
154
|
+
* for this role. systemd's shared unit template (`ours-fleet-agent@.service`)
|
|
155
|
+
* execs `_run <name>` with no -c on every restart, so a role brought up from a
|
|
156
|
+
* non-default config (`ours-fleet up -c custom.yaml`) would otherwise silently
|
|
157
|
+
* resolve against the default ~/fleet.yaml on its very first restart and fail
|
|
158
|
+
* with "no such role". An empty/missing marker means "use the default", same as
|
|
159
|
+
* no -c was ever given.
|
|
160
|
+
*/
|
|
161
|
+
export declare function resolveConfigPath(dir: string, explicit?: string): string | undefined;
|
|
149
162
|
/** What one child session did, so the supervising loop can decide what follows. */
|
|
150
163
|
export interface AttemptResult {
|
|
151
164
|
elapsedSecs: number;
|
|
@@ -168,6 +181,7 @@ export declare function runOnce(name: string, opts?: {
|
|
|
168
181
|
temp?: boolean;
|
|
169
182
|
configPath?: string;
|
|
170
183
|
allowResumeRotation?: boolean;
|
|
184
|
+
identityLifetime?: 'permanent' | 'temporary';
|
|
171
185
|
}, partialDeps?: Partial<RunnerDeps>): Promise<AttemptResult>;
|
|
172
186
|
/**
|
|
173
187
|
* The persistent supervisor for one permanent role: run child sessions in a
|