@ours.network/fleet 0.13.2 → 0.14.0
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 +93 -60
- package/dist/briefing.js +15 -12
- package/dist/config.d.ts +10 -0
- package/dist/config.js +21 -2
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +35 -45
- package/dist/loops/manager.d.ts +1 -0
- package/dist/loops/manager.js +23 -1
- package/dist/owner-channel/channel.d.ts +52 -0
- package/dist/owner-channel/channel.js +403 -62
- package/dist/owner-channel/commands.d.ts +79 -0
- package/dist/owner-channel/commands.js +183 -0
- package/dist/owner-channel/notices.d.ts +7 -0
- package/dist/owner-channel/notices.js +20 -0
- package/dist/owner-channel/state.d.ts +45 -0
- package/dist/owner-channel/state.js +229 -19
- package/dist/owner-channel/tasks.js +7 -4
- package/dist/runner.js +2 -0
- package/dist/session/acp.d.ts +3 -0
- package/dist/session/acp.js +24 -1
- package/package.json +1 -1
package/dist/loops/manager.js
CHANGED
|
@@ -7,6 +7,7 @@ export class ScheduledLoopManager {
|
|
|
7
7
|
definitions = new Map();
|
|
8
8
|
store;
|
|
9
9
|
timer;
|
|
10
|
+
runTimeouts = new Set();
|
|
10
11
|
stopping = false;
|
|
11
12
|
constructor(role, definitions, stateDir, arbiter, deps) {
|
|
12
13
|
this.role = role;
|
|
@@ -27,6 +28,9 @@ export class ScheduledLoopManager {
|
|
|
27
28
|
if (this.timer !== undefined)
|
|
28
29
|
this.deps.clearTimer(this.timer);
|
|
29
30
|
this.timer = undefined;
|
|
31
|
+
for (const timeout of this.runTimeouts)
|
|
32
|
+
this.deps.clearTimer(timeout);
|
|
33
|
+
this.runTimeouts.clear();
|
|
30
34
|
this.store.state.clock.lastWallMs = this.deps.now();
|
|
31
35
|
this.store.persist();
|
|
32
36
|
}
|
|
@@ -140,7 +144,25 @@ export class ScheduledLoopManager {
|
|
|
140
144
|
return { state: 'unavailable', runId };
|
|
141
145
|
}
|
|
142
146
|
this.deps.log(`[${this.role}] loop ${definition.name} started run=${runId.slice(0, 11)} scheduled=${new Date(scheduledAt).toISOString()}`);
|
|
143
|
-
|
|
147
|
+
// Maintenance is best-effort and must never monopolize a persistent role.
|
|
148
|
+
// A cancellation which the ACP adapter ignores is escalated by AcpSession
|
|
149
|
+
// into an adapter restart, preserving the session and deferred owner mail.
|
|
150
|
+
const timeoutMs = Math.max(60_000, Math.min(5 * 60_000, Math.floor(definition.intervalMs / 2)));
|
|
151
|
+
const timeout = this.deps.setTimer(() => {
|
|
152
|
+
this.runTimeouts.delete(timeout);
|
|
153
|
+
if (state.activeRunId !== runId || this.stopping)
|
|
154
|
+
return;
|
|
155
|
+
this.deps.log(`[${this.role}] loop ${definition.name} timed out run=${runId.slice(0, 11)} after ${timeoutMs}ms; cancelling`);
|
|
156
|
+
void this.arbiter.interrupt('scheduled-loop').catch(error => {
|
|
157
|
+
this.deps.log(`[${this.role}] loop ${definition.name} timeout cancellation failed: ${error?.name ?? 'Error'}`);
|
|
158
|
+
});
|
|
159
|
+
}, timeoutMs);
|
|
160
|
+
this.runTimeouts.add(timeout);
|
|
161
|
+
void result.queued.completion.then(turn => {
|
|
162
|
+
this.deps.clearTimer(timeout);
|
|
163
|
+
this.runTimeouts.delete(timeout);
|
|
164
|
+
this.finish(definition, state, runId, turn);
|
|
165
|
+
});
|
|
144
166
|
return { state: 'started', runId };
|
|
145
167
|
}
|
|
146
168
|
finish(definition, state, runId, result) {
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { type ChildProcessWithoutNullStreams } from 'node:child_process';
|
|
2
2
|
import { type OwnerChannelConfig } from '../config.js';
|
|
3
3
|
import type { SessionHandle } from '../session/types.js';
|
|
4
|
+
import { type OwnerFleetOps } from './commands.js';
|
|
4
5
|
import { type OursToolClient } from './mcp.js';
|
|
5
6
|
import { type OwnerUpdatePhase } from './notices.js';
|
|
6
7
|
import { type OwnerEntry } from './state.js';
|
|
7
8
|
import { type OwnerTaskPhase } from './tasks.js';
|
|
8
9
|
export interface OwnerChannelOptions {
|
|
9
10
|
role: string;
|
|
11
|
+
/** Harness id of the role (e.g. 'claude-code', 'codex'); gates which slash commands may be forwarded. */
|
|
12
|
+
harness: string;
|
|
10
13
|
config: OwnerChannelConfig;
|
|
11
14
|
session: SessionHandle;
|
|
12
15
|
stateDir: string;
|
|
@@ -16,6 +19,10 @@ export interface OwnerChannelOptions {
|
|
|
16
19
|
client?: OursToolClient;
|
|
17
20
|
/** Test seam; production uses `ours-mcp watch <identity>`. */
|
|
18
21
|
watch?: (identity: string) => ChildProcessWithoutNullStreams;
|
|
22
|
+
/** Test seam; production uses the detached ours-fleet CLI (`fleetCliOps`). */
|
|
23
|
+
fleet?: OwnerFleetOps;
|
|
24
|
+
/** Forwarded to fleet CLI invocations spawned for owner commands. */
|
|
25
|
+
configPath?: string;
|
|
19
26
|
}
|
|
20
27
|
export interface OwnerChannelHandle {
|
|
21
28
|
start(): Promise<void>;
|
|
@@ -109,6 +116,7 @@ export declare class OwnerChannel implements OwnerChannelHandle {
|
|
|
109
116
|
private readonly client;
|
|
110
117
|
private readonly state;
|
|
111
118
|
private readonly authorizations;
|
|
119
|
+
private readonly conversations;
|
|
112
120
|
private readonly tasks;
|
|
113
121
|
private readonly attachmentRecovery;
|
|
114
122
|
private readonly attachmentConfig;
|
|
@@ -118,6 +126,8 @@ export declare class OwnerChannel implements OwnerChannelHandle {
|
|
|
118
126
|
* (a crash must replay them) but must not be queued twice while live.
|
|
119
127
|
*/
|
|
120
128
|
private readonly inFlight;
|
|
129
|
+
/** Wires already NACKed to the managed agent, so a deferred replay stays quiet. */
|
|
130
|
+
private readonly relayNacks;
|
|
121
131
|
private stopping;
|
|
122
132
|
private watchProcess?;
|
|
123
133
|
private watchTask?;
|
|
@@ -127,6 +137,7 @@ export declare class OwnerChannel implements OwnerChannelHandle {
|
|
|
127
137
|
private readonly activeRequests;
|
|
128
138
|
private managementTail;
|
|
129
139
|
private ready;
|
|
140
|
+
private readonly fleetOps;
|
|
130
141
|
constructor(options: OwnerChannelOptions);
|
|
131
142
|
start(): Promise<void>;
|
|
132
143
|
drain(): Promise<void>;
|
|
@@ -139,14 +150,55 @@ export declare class OwnerChannel implements OwnerChannelHandle {
|
|
|
139
150
|
private assertLabel;
|
|
140
151
|
private safeMetadata;
|
|
141
152
|
private sendOwnerUpdate;
|
|
153
|
+
private sendProactiveMessage;
|
|
142
154
|
private openOwnerTask;
|
|
143
155
|
private sendOwnerTaskReport;
|
|
144
156
|
private safeOwnerUpdate;
|
|
145
157
|
private safeTaskReport;
|
|
158
|
+
private safeProactiveMessage;
|
|
146
159
|
private drainAll;
|
|
147
160
|
private attachmentGroups;
|
|
148
161
|
private handleAttachmentGroup;
|
|
149
162
|
private handle;
|
|
163
|
+
/**
|
|
164
|
+
* Deterministic command path: the message never becomes an agent prompt.
|
|
165
|
+
* Authorization already happened — the managed-agent relay branch and the
|
|
166
|
+
* owner-CID check in handle() both run before dispatch, so only an
|
|
167
|
+
* authenticated owner reaches this: neither ordinary peers nor the managed
|
|
168
|
+
* agent itself can execute /force-restart, /model, or any other command.
|
|
169
|
+
*/
|
|
170
|
+
private handleCommand;
|
|
171
|
+
/** Queue raw slash text to the harness and report the turn's outcome. */
|
|
172
|
+
private runHarnessCommand;
|
|
173
|
+
/**
|
|
174
|
+
* Confirmation and the durable wire record must both land BEFORE the fleet
|
|
175
|
+
* CLI is asked to bounce this very process; neither can happen afterwards.
|
|
176
|
+
*/
|
|
177
|
+
private restartSelf;
|
|
178
|
+
/** Code-point-safe tail of the worklog, or undefined when there is none. */
|
|
179
|
+
private readWorklogTail;
|
|
180
|
+
/** Bound harness-command output to a single outbound message. */
|
|
181
|
+
private commandOutput;
|
|
182
|
+
private acceptedSender;
|
|
183
|
+
private isAgentSender;
|
|
184
|
+
private isEffectiveOwner;
|
|
185
|
+
private relayManagedAgentMessage;
|
|
186
|
+
/**
|
|
187
|
+
* One bounded NACK per wire: an unroutable or refused relay must be visible
|
|
188
|
+
* to the authenticated agent, while its deferred replays stay quiet. NACK
|
|
189
|
+
* delivery is best-effort — it must never make the failure worse.
|
|
190
|
+
*/
|
|
191
|
+
private nackManagedAgent;
|
|
192
|
+
/**
|
|
193
|
+
* Daemon contact resolution is case-exact, so a canonical config CID picked
|
|
194
|
+
* by the sole-owner fallback is translated to the daemon-known contact form
|
|
195
|
+
* when one exists. Last-inbound routes already carry the daemon form.
|
|
196
|
+
*/
|
|
197
|
+
private routableContact;
|
|
198
|
+
private safeRelayMessage;
|
|
199
|
+
private warnOwnerOfUnauthorizedSender;
|
|
200
|
+
private effectiveOwners;
|
|
201
|
+
private authorizationIntegrity;
|
|
150
202
|
private complete;
|
|
151
203
|
private ownerAttachmentPrompt;
|
|
152
204
|
private ownerPrompt;
|