@ours.network/fleet 1.2.0-nightly.1 → 1.2.0-nightly.11

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.
Files changed (90) hide show
  1. package/README.md +110 -21
  2. package/dist/agent-ours/bridge.d.ts +3 -0
  3. package/dist/agent-ours/bridge.js +178 -0
  4. package/dist/agent-ours/controller.d.ts +14 -0
  5. package/dist/agent-ours/controller.js +60 -0
  6. package/dist/agent-ours/file-rpc.d.ts +23 -0
  7. package/dist/agent-ours/file-rpc.js +123 -0
  8. package/dist/agent-ours/harness.d.ts +14 -0
  9. package/dist/agent-ours/harness.js +64 -0
  10. package/dist/agent-ours/mcp-endpoint.d.ts +15 -0
  11. package/dist/agent-ours/mcp-endpoint.js +111 -0
  12. package/dist/agent-ours/runtime.d.ts +53 -0
  13. package/dist/agent-ours/runtime.js +247 -0
  14. package/dist/agent-ours/service.d.ts +23 -0
  15. package/dist/agent-ours/service.js +364 -0
  16. package/dist/agent-ours/state.d.ts +45 -0
  17. package/dist/agent-ours/state.js +95 -0
  18. package/dist/agent-ours/wire.d.ts +14 -0
  19. package/dist/agent-ours/wire.js +55 -0
  20. package/dist/agent-recovery-gate.js +1 -1
  21. package/dist/application/legacy-supervisor-identity.d.ts +7 -0
  22. package/dist/application/legacy-supervisor-identity.js +43 -0
  23. package/dist/application/supervisor-ours-tools.d.ts +159 -0
  24. package/dist/application/supervisor-ours-tools.js +121 -0
  25. package/dist/application/task-room-service.d.ts +6 -2
  26. package/dist/application/task-room-service.js +78 -21
  27. package/dist/briefing.js +10 -59
  28. package/dist/build-info.json +5 -4
  29. package/dist/capabilities.d.ts +2 -1
  30. package/dist/capabilities.js +2 -0
  31. package/dist/cli.js +63 -2
  32. package/dist/client-profile.d.ts +1 -0
  33. package/dist/client-profile.js +18 -3
  34. package/dist/config.d.ts +1 -0
  35. package/dist/creation.d.ts +1 -1
  36. package/dist/creation.js +3 -14
  37. package/dist/docs.d.ts +3 -3
  38. package/dist/docs.js +32 -23
  39. package/dist/doctor.d.ts +1 -1
  40. package/dist/doctor.js +29 -10
  41. package/dist/fleet-command-audit.js +3 -2
  42. package/dist/harness/agent-session.d.ts +2 -0
  43. package/dist/harness/claude-code-session.js +4 -2
  44. package/dist/harness/codex-session.js +9 -2
  45. package/dist/harness/codex.d.ts +3 -2
  46. package/dist/harness/codex.js +4 -3
  47. package/dist/harness/hermes-session.js +7 -1
  48. package/dist/owner-channel/channel.d.ts +3 -0
  49. package/dist/owner-channel/channel.js +24 -4
  50. package/dist/owner-channel/commands.js +5 -0
  51. package/dist/rooms-tasks/archived-absence.d.ts +8 -0
  52. package/dist/rooms-tasks/archived-absence.js +48 -0
  53. package/dist/rooms-tasks/cli.js +6 -1
  54. package/dist/rooms-tasks/close.d.ts +3 -0
  55. package/dist/rooms-tasks/close.js +95 -17
  56. package/dist/rooms-tasks/cowork-adapter.d.ts +4 -0
  57. package/dist/rooms-tasks/cowork-adapter.js +10 -2
  58. package/dist/rooms-tasks/cowork-http.d.ts +3 -0
  59. package/dist/rooms-tasks/cowork-http.js +70 -0
  60. package/dist/rooms-tasks/deletion.js +40 -3
  61. package/dist/rooms-tasks/live-readiness.d.ts +18 -0
  62. package/dist/rooms-tasks/live-readiness.js +75 -0
  63. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  64. package/dist/rooms-tasks/member-startup.js +2 -0
  65. package/dist/rooms-tasks/provision.d.ts +11 -0
  66. package/dist/rooms-tasks/provision.js +138 -26
  67. package/dist/rooms-tasks/room-state.js +13 -0
  68. package/dist/rooms-tasks/task-state.d.ts +13 -3
  69. package/dist/rooms-tasks/task-state.js +42 -6
  70. package/dist/rooms-tasks/types.d.ts +12 -0
  71. package/dist/rooms-tasks/workspace-artifacts.d.ts +6 -0
  72. package/dist/rooms-tasks/workspace-artifacts.js +93 -0
  73. package/dist/rooms-tasks/workspace.d.ts +26 -0
  74. package/dist/rooms-tasks/workspace.js +195 -0
  75. package/dist/runner.d.ts +14 -0
  76. package/dist/runner.js +527 -495
  77. package/dist/session/codex-app-server-transport.js +1 -1
  78. package/dist/spawn.js +19 -16
  79. package/dist/startup-readiness.d.ts +13 -0
  80. package/dist/startup-readiness.js +38 -0
  81. package/dist/supervisor/systemd.js +5 -0
  82. package/dist/temp-supervisor-recovery.d.ts +3 -0
  83. package/dist/temp-supervisor-recovery.js +46 -0
  84. package/dist/watchdog/briefing.js +3 -16
  85. package/dist/watchdog/run.js +19 -5
  86. package/dist/web/server.d.ts +2 -0
  87. package/dist/web/server.js +18 -1
  88. package/docs/task-workspaces.md +98 -0
  89. package/package.json +6 -3
  90. package/presets/fleet/roles/Coordinator.yaml +22 -0
@@ -0,0 +1,15 @@
1
+ import type { ApplicationIdentityStore } from '@ours.network/mcp/application-identities';
2
+ import type { OursClient } from '@ours.network/sdk/client';
3
+ import type { AgentOursRuntime } from './runtime.js';
4
+ export interface EndpointOptions {
5
+ socket: string;
6
+ capability: string;
7
+ generation: number;
8
+ runtime: AgentOursRuntime;
9
+ client: OursClient;
10
+ identities: ApplicationIdentityStore;
11
+ remoteDaemonFiles: boolean;
12
+ }
13
+ export declare function startMcpEndpoint(options: EndpointOptions): Promise<{
14
+ close(): Promise<void>;
15
+ }>;
@@ -0,0 +1,111 @@
1
+ import { createServer } from 'node:net';
2
+ import { chmod, unlink } from 'node:fs/promises';
3
+ import { timingSafeEqual } from 'node:crypto';
4
+ import { createManagedOursMcpServer } from '@ours.network/mcp/server';
5
+ import { JSONRPCMessageSchema } from '@modelcontextprotocol/sdk/types.js';
6
+ import { FileCallbacks } from './file-rpc.js';
7
+ import { Wire } from './wire.js';
8
+ export async function startMcpEndpoint(options) {
9
+ const sockets = new Set();
10
+ const server = createServer((socket) => {
11
+ if (sockets.size >= 4) {
12
+ socket.destroy();
13
+ return;
14
+ }
15
+ const wire = new Wire(socket);
16
+ sockets.add(wire);
17
+ const files = new FileCallbacks(wire);
18
+ let authenticated = false;
19
+ let inFlight = 0;
20
+ const ids = new Set();
21
+ const deadline = setTimeout(() => wire.close(), 5000);
22
+ deadline.unref();
23
+ const transport = {
24
+ start: async () => { },
25
+ send: async (message) => {
26
+ if ('id' in message &&
27
+ message.id !== undefined &&
28
+ ('result' in message || 'error' in message))
29
+ ids.delete(message.id);
30
+ await wire.send({ kind: 'mcp', value: message });
31
+ },
32
+ close: async () => wire.close(),
33
+ };
34
+ const mcp = createManagedOursMcpServer(options.client, 'managed-v1', options.identities, {
35
+ remoteDaemonFiles: options.remoteDaemonFiles,
36
+ fileContext: files,
37
+ admit: async () => {
38
+ if (!authenticated || inFlight >= 32)
39
+ throw Error('MCP_ADMISSION_LIMIT');
40
+ inFlight++;
41
+ try {
42
+ const release = await options.runtime.admit();
43
+ return () => {
44
+ inFlight--;
45
+ release();
46
+ };
47
+ }
48
+ catch (error) {
49
+ inFlight--;
50
+ throw error;
51
+ }
52
+ },
53
+ });
54
+ wire.onClose = () => {
55
+ clearTimeout(deadline);
56
+ sockets.delete(wire);
57
+ files.close();
58
+ transport.onclose?.();
59
+ };
60
+ wire.onFrame = (frame) => {
61
+ if (!authenticated) {
62
+ const actual = typeof frame?.capability === 'string' ? Buffer.from(frame.capability) : Buffer.alloc(0);
63
+ const expected = Buffer.from(options.capability);
64
+ if (frame?.kind !== 'hello' ||
65
+ frame.generation !== options.generation ||
66
+ actual.length !== expected.length ||
67
+ !timingSafeEqual(actual, expected))
68
+ throw Error('UNAUTHORIZED_BRIDGE');
69
+ authenticated = true;
70
+ clearTimeout(deadline);
71
+ void mcp
72
+ .connect(transport)
73
+ .then(() => wire.send({ kind: 'ready' }))
74
+ .catch(() => wire.close());
75
+ return;
76
+ }
77
+ if (frame?.kind === 'fs-result') {
78
+ files.receive(frame);
79
+ return;
80
+ }
81
+ if (frame?.kind !== 'mcp')
82
+ throw Error('UNKNOWN_BRIDGE_OPERATION');
83
+ const message = JSONRPCMessageSchema.parse(frame.value);
84
+ if ('method' in message && 'id' in message) {
85
+ if (ids.has(message.id) || ids.size >= 64)
86
+ throw Error('MCP_REQUEST_LIMIT');
87
+ ids.add(message.id);
88
+ }
89
+ transport.onmessage?.(message);
90
+ };
91
+ });
92
+ await new Promise((resolve, reject) => {
93
+ server.once('error', reject);
94
+ server.listen(options.socket, () => {
95
+ server.removeListener('error', reject);
96
+ resolve();
97
+ });
98
+ });
99
+ await chmod(options.socket, 0o600);
100
+ return {
101
+ close: async () => {
102
+ for (const socket of sockets)
103
+ socket.close();
104
+ await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
105
+ await unlink(options.socket).catch((error) => {
106
+ if (error.code !== 'ENOENT')
107
+ throw error;
108
+ });
109
+ },
110
+ };
111
+ }
@@ -0,0 +1,53 @@
1
+ import type { OursClient } from '@ours.network/sdk/client';
2
+ import { RuntimeJournal, type RuntimeState } from './state.js';
3
+ export interface AssignedIdentity {
4
+ instance: string;
5
+ generation: number;
6
+ daemon: string;
7
+ name: string;
8
+ lifetime: 'permanent' | 'temporary';
9
+ action: string;
10
+ expectedCid?: string;
11
+ allowCreate: boolean;
12
+ bio: string;
13
+ }
14
+ export interface RoomAdmission {
15
+ id: string;
16
+ cid: string;
17
+ seat: string;
18
+ action: string;
19
+ /** The secret stays in trusted storage and is never returned to the child. */
20
+ redeem(client: OursClient): Promise<{
21
+ cid: string;
22
+ }>;
23
+ observe(agentCid: string): Promise<'pending' | 'established' | 'mismatch'>;
24
+ discardSecret(): Promise<void>;
25
+ }
26
+ export interface RuntimeDependencies {
27
+ journal: RuntimeJournal;
28
+ /** Client is already attached to the pinned daemon with its durable external owner. */
29
+ client: OursClient;
30
+ /** Checked before every operation; tied to the cross-process controller lock. */
31
+ assertFence(): void;
32
+ sleep(ms: number): Promise<void>;
33
+ now(): number;
34
+ }
35
+ /** A bridge never owns this object or its client; EOF never releases its lease. */
36
+ export declare class AgentOursRuntime {
37
+ readonly assignment: AssignedIdentity;
38
+ private readonly deps;
39
+ private inFlight;
40
+ private drain?;
41
+ private accepting;
42
+ private state;
43
+ constructor(assignment: AssignedIdentity, deps: RuntimeDependencies);
44
+ get snapshot(): Readonly<RuntimeState>;
45
+ private transition;
46
+ private verify;
47
+ prepare(room?: RoomAdmission, timeoutMs?: number): Promise<void>;
48
+ /** Admission covers SDK calls, response rendering and agent filesystem callbacks. */
49
+ admit(): Promise<() => void>;
50
+ startHarness<T>(start: () => Promise<T>): Promise<T>;
51
+ suspend(): Promise<void>;
52
+ terminal(): Promise<void>;
53
+ }
@@ -0,0 +1,247 @@
1
+ /** A bridge never owns this object or its client; EOF never releases its lease. */
2
+ export class AgentOursRuntime {
3
+ assignment;
4
+ deps;
5
+ inFlight = 0;
6
+ drain;
7
+ accepting = false;
8
+ state;
9
+ constructor(assignment, deps) {
10
+ this.assignment = assignment;
11
+ this.deps = deps;
12
+ const existing = deps.journal.read();
13
+ if (existing) {
14
+ if (existing.instance !== assignment.instance ||
15
+ existing.daemon !== assignment.daemon ||
16
+ existing.name !== assignment.name ||
17
+ existing.lifetime !== assignment.lifetime ||
18
+ existing.generation !== assignment.generation ||
19
+ existing.action !== assignment.action ||
20
+ (assignment.expectedCid !== undefined &&
21
+ existing.cid !== undefined &&
22
+ existing.cid !== assignment.expectedCid))
23
+ throw Error('RUNTIME_ASSIGNMENT_MISMATCH');
24
+ this.state = existing;
25
+ }
26
+ else {
27
+ // Deliberately serialize only the public ownership facts, never credentials.
28
+ const { instance, generation, daemon, name, lifetime, action } = assignment;
29
+ this.state = {
30
+ version: 1,
31
+ instance,
32
+ generation,
33
+ daemon,
34
+ name,
35
+ lifetime,
36
+ action,
37
+ phase: 'PREPARING',
38
+ revision: 0,
39
+ updatedAt: '',
40
+ };
41
+ deps.assertFence();
42
+ deps.journal.commit(this.state);
43
+ this.state = deps.journal.read();
44
+ }
45
+ }
46
+ get snapshot() {
47
+ return structuredClone(this.state);
48
+ }
49
+ transition(phase, patch = {}) {
50
+ this.deps.assertFence();
51
+ this.deps.journal.commit({ ...this.state, ...patch, phase }, this.state.revision);
52
+ this.state = this.deps.journal.read();
53
+ }
54
+ async verify() {
55
+ this.deps.assertFence();
56
+ if (this.assignment.expectedCid && this.state.cid !== this.assignment.expectedCid)
57
+ throw Error('PINNED_CID_MISMATCH');
58
+ const actual = await this.deps.client.currentIdentity();
59
+ if (actual.cid !== this.state.cid ||
60
+ actual.name !== this.state.name ||
61
+ actual.temporary !== (this.state.lifetime === 'temporary') ||
62
+ actual.isRoot)
63
+ throw Error('IDENTITY_PROOF_MISMATCH');
64
+ this.deps.assertFence();
65
+ }
66
+ async prepare(room, timeoutMs = 30_000) {
67
+ if (['TERMINAL_INTENT', 'CLEANUP_PENDING', 'RELEASED', 'FAILED', 'QUIESCING'].includes(this.state.phase))
68
+ throw Error('RUNTIME_NOT_RESUMABLE');
69
+ this.deps.assertFence();
70
+ if (!this.state.cid) {
71
+ // An existing PREPARING journal is an uncertain create, never evidence of absence.
72
+ if (this.state.phase === 'RECOVERING')
73
+ throw Error('UNCERTAIN_PROVISIONING');
74
+ const rows = await this.deps.client.listIdentities();
75
+ const row = rows.find((r) => r.name === this.state.name);
76
+ if (row) {
77
+ if (!('cid' in row) ||
78
+ this.state.lifetime === 'temporary' ||
79
+ !this.assignment.expectedCid ||
80
+ row.cid !== this.assignment.expectedCid ||
81
+ row.kind === 'root' ||
82
+ row.temp)
83
+ throw Error('IDENTITY_COLLISION');
84
+ this.transition('RECOVERING');
85
+ await this.deps.client.chooseIdentity({ name: this.state.name, force: false });
86
+ this.transition('OWNED', { cid: row.cid });
87
+ }
88
+ else {
89
+ if (!this.assignment.allowCreate || this.assignment.expectedCid)
90
+ throw Error('IDENTITY_ABSENT');
91
+ // Persist uncertainty before the non-idempotent call. Crash recovery requires owner proof.
92
+ this.transition('RECOVERING');
93
+ const args = {
94
+ name: this.state.name,
95
+ bio: this.assignment.bio,
96
+ exposeLocal: false,
97
+ localAutoAccept: true,
98
+ };
99
+ const created = this.state.lifetime === 'temporary'
100
+ ? await this.deps.client.createTemporaryIdentity(args)
101
+ : await this.deps.client.createIdentity(args);
102
+ if (created.hierarchy !== 'role')
103
+ throw Error('ROOT_PROVISIONING_FORBIDDEN');
104
+ this.transition('OWNED', { cid: created.info.cid });
105
+ }
106
+ }
107
+ await this.verify();
108
+ if (!room && (this.state.room || this.state.admissionIntent))
109
+ throw Error('ROOM_DESCRIPTOR_REQUIRED');
110
+ if (room) {
111
+ if (this.state.room &&
112
+ (this.state.room.id !== room.id ||
113
+ this.state.room.cid !== room.cid ||
114
+ this.state.room.seat !== room.seat ||
115
+ this.state.room.agentCid !== this.state.cid ||
116
+ this.state.room.action !== room.action))
117
+ throw Error('ROOM_ASSIGNMENT_MISMATCH');
118
+ const intent = this.state.admissionIntent;
119
+ if (intent &&
120
+ (intent.id !== room.id ||
121
+ intent.cid !== room.cid ||
122
+ intent.seat !== room.seat ||
123
+ intent.action !== room.action ||
124
+ intent.agentCid !== this.state.cid))
125
+ throw Error('ROOM_INTENT_MISMATCH');
126
+ if (!this.state.room && !intent) {
127
+ // A permanent identity may already occupy its seat after a clean supervisor stop.
128
+ const existing = await room.observe(this.state.cid);
129
+ if (existing === 'mismatch') {
130
+ this.transition('FAILED');
131
+ throw Error('WRONG_ROOM_SEAT');
132
+ }
133
+ this.transition('ROOM_PENDING', {
134
+ admissionIntent: {
135
+ id: room.id,
136
+ cid: room.cid,
137
+ seat: room.seat,
138
+ action: room.action,
139
+ agentCid: this.state.cid,
140
+ },
141
+ });
142
+ if (existing !== 'established') {
143
+ const result = await room.redeem(this.deps.client);
144
+ if (result.cid !== room.cid) {
145
+ this.transition('FAILED');
146
+ throw Error('WRONG_ROOM_CID');
147
+ }
148
+ }
149
+ }
150
+ const deadline = this.deps.now() + timeoutMs;
151
+ for (;;) {
152
+ this.deps.assertFence();
153
+ const observation = await room.observe(this.state.cid);
154
+ if (observation === 'mismatch') {
155
+ this.transition('FAILED');
156
+ throw Error('WRONG_ROOM_SEAT');
157
+ }
158
+ if (observation === 'established')
159
+ break;
160
+ if (this.deps.now() >= deadline)
161
+ throw Error('ROOM_PENDING');
162
+ await this.deps.sleep(Math.min(250, deadline - this.deps.now()));
163
+ }
164
+ this.transition('READY', {
165
+ room: {
166
+ id: room.id,
167
+ cid: room.cid,
168
+ seat: room.seat,
169
+ agentCid: this.state.cid,
170
+ action: room.action,
171
+ },
172
+ });
173
+ await room.discardSecret();
174
+ }
175
+ else
176
+ this.transition('READY');
177
+ this.accepting = true;
178
+ }
179
+ /** Admission covers SDK calls, response rendering and agent filesystem callbacks. */
180
+ async admit() {
181
+ this.deps.assertFence();
182
+ if (!this.accepting || !['READY', 'SERVING'].includes(this.state.phase))
183
+ throw Error('RUNTIME_NOT_READY');
184
+ this.inFlight++;
185
+ let released = false;
186
+ const release = () => {
187
+ if (released)
188
+ return;
189
+ released = true;
190
+ if (--this.inFlight === 0)
191
+ this.drain?.();
192
+ };
193
+ try {
194
+ await this.verify();
195
+ if (!this.accepting)
196
+ throw Error('RUNTIME_QUIESCING');
197
+ return release;
198
+ }
199
+ catch (error) {
200
+ release();
201
+ throw error;
202
+ }
203
+ }
204
+ async startHarness(start) {
205
+ if (!this.accepting || this.state.phase !== 'READY')
206
+ throw Error('RUNTIME_NOT_READY');
207
+ await this.verify();
208
+ this.transition('SERVING');
209
+ return start();
210
+ }
211
+ async suspend() {
212
+ this.accepting = false;
213
+ if (this.inFlight)
214
+ await new Promise((resolve) => {
215
+ this.drain = resolve;
216
+ });
217
+ this.transition('RECOVERING');
218
+ }
219
+ async terminal() {
220
+ this.accepting = false;
221
+ if (this.state.phase === 'RELEASED')
222
+ return;
223
+ if (!['TERMINAL_INTENT', 'CLEANUP_PENDING'].includes(this.state.phase))
224
+ this.transition('QUIESCING');
225
+ if (this.inFlight)
226
+ await new Promise((resolve) => {
227
+ this.drain = resolve;
228
+ });
229
+ this.transition('TERMINAL_INTENT');
230
+ try {
231
+ this.deps.assertFence();
232
+ const ack = await this.deps.client.releaseLease();
233
+ if (!ack ||
234
+ !Array.isArray(ack.released) ||
235
+ !Array.isArray(ack.closed) ||
236
+ [...ack.released, ...ack.closed].some((name) => typeof name !== 'string') ||
237
+ [ack.attempted, ack.notified, ack.failed].some((n) => !Number.isSafeInteger(n) || n < 0) ||
238
+ ack.notified + ack.failed !== ack.attempted)
239
+ throw Error('INVALID_RELEASE_ACK');
240
+ this.transition('RELEASED', { releaseAck: ack });
241
+ }
242
+ catch (error) {
243
+ this.transition('CLEANUP_PENDING');
244
+ throw error;
245
+ }
246
+ }
247
+ }
@@ -0,0 +1,23 @@
1
+ import type { ResolvedRole } from '../config.js';
2
+ import { AgentOursRuntime } from './runtime.js';
3
+ export declare const privateRuntimeRoot: () => string;
4
+ export interface ManagedAgentService {
5
+ runtime: AgentOursRuntime;
6
+ descriptor: string;
7
+ privatePaths: string[];
8
+ close(terminal: boolean): Promise<void>;
9
+ }
10
+ /** Only trusted launch orchestration may write this descriptor, never the child. */
11
+ export declare function storeRoomSecret(role: ResolvedRole): void;
12
+ export declare function storeTemporaryLaunch(role: ResolvedRole, action: string): void;
13
+ export declare function prepareManagedAgent(role: ResolvedRole, stateDir: string, temporary: boolean): Promise<ManagedAgentService>;
14
+ /** Explicit trusted inventory migration: read and pin existing permanent CID, never bind. */
15
+ export declare function preparePermanentAssignment(role: ResolvedRole): Promise<'verified' | 'unverified'>;
16
+ export declare function readRoomReadiness(roomCid: string, name: string): {
17
+ room: string;
18
+ invite: string;
19
+ cid: string;
20
+ generation: number;
21
+ } | undefined;
22
+ /** Explicit logical-instance termination, independent of harness/bridge transport. */
23
+ export declare function releaseManagedAgent(role: ResolvedRole): Promise<void>;