@ours.network/fleet 1.1.0-nightly.2 → 1.1.0-nightly.21
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 +204 -55
- package/dist/agent-recovery-gate.d.ts +17 -0
- package/dist/agent-recovery-gate.js +135 -0
- package/dist/application/capabilities.js +18 -6
- package/dist/application/fleet-query-service.js +20 -5
- package/dist/application/role-command-service.js +9 -0
- package/dist/application/role-creation-service.d.ts +6 -25
- package/dist/application/role-creation-service.js +34 -72
- package/dist/application/role-repository.d.ts +4 -2
- package/dist/application/role-repository.js +6 -3
- package/dist/application/session-control.d.ts +3 -1
- package/dist/application/session-control.js +4 -2
- package/dist/application/task-room-service.d.ts +58 -6
- package/dist/application/task-room-service.js +435 -79
- package/dist/briefing.js +74 -24
- package/dist/build-info.json +4 -4
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +10 -0
- package/dist/cli.js +241 -76
- package/dist/config-yaml.d.ts +1 -1
- package/dist/config.d.ts +75 -3
- package/dist/config.js +274 -34
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +3 -2
- package/dist/daemon-recovery.d.ts +93 -0
- package/dist/daemon-recovery.js +328 -0
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +192 -66
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +54 -4
- package/dist/fleet-command-audit.d.ts +165 -0
- package/dist/fleet-command-audit.js +555 -0
- package/dist/fleet-proxy.d.ts +7 -2
- package/dist/fleet-proxy.js +30 -8
- package/dist/harness/agent-session.d.ts +5 -0
- package/dist/harness/claude-code-session.d.ts +2 -0
- package/dist/harness/claude-code-session.js +6 -1
- package/dist/harness/claude-code.js +2 -0
- package/dist/harness/codex-app-server-proxy.d.ts +40 -0
- package/dist/harness/codex-app-server-proxy.js +348 -14
- package/dist/harness/codex-session.d.ts +11 -2
- package/dist/harness/codex-session.js +22 -2
- package/dist/harness/codex.d.ts +5 -1
- package/dist/harness/codex.js +110 -10
- package/dist/harness/types.d.ts +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/init-guidance.d.ts +1 -1
- package/dist/init-guidance.js +4 -3
- package/dist/init-wizard.d.ts +98 -0
- package/dist/init-wizard.js +629 -0
- package/dist/lifecycle-summary.d.ts +134 -0
- package/dist/lifecycle-summary.js +179 -0
- package/dist/loops/config.d.ts +10 -1
- package/dist/loops/config.js +38 -5
- package/dist/model-env.d.ts +2 -2
- package/dist/model-env.js +1 -1
- package/dist/model-recovery.d.ts +1 -1
- package/dist/ops.js +2 -17
- package/dist/owner-channel/channel.d.ts +58 -0
- package/dist/owner-channel/channel.js +321 -42
- package/dist/owner-channel/commands.d.ts +6 -1
- package/dist/owner-channel/commands.js +34 -19
- package/dist/owner-channel/state.d.ts +2 -0
- package/dist/owner-channel/state.js +8 -2
- package/dist/permissions.js +1 -1
- package/dist/preset-bootstrap.d.ts +10 -0
- package/dist/preset-bootstrap.js +85 -0
- package/dist/preset-migration.d.ts +42 -0
- package/dist/preset-migration.js +414 -0
- package/dist/reports/artifact.d.ts +10 -0
- package/dist/reports/artifact.js +32 -0
- package/dist/reports/index.d.ts +5 -0
- package/dist/reports/index.js +5 -0
- package/dist/reports/render.d.ts +6 -0
- package/dist/reports/render.js +240 -0
- package/dist/reports/service.d.ts +42 -0
- package/dist/reports/service.js +57 -0
- package/dist/reports/tasks.d.ts +52 -0
- package/dist/reports/tasks.js +177 -0
- package/dist/reports/types.d.ts +315 -0
- package/dist/reports/types.js +6 -0
- package/dist/resolved-plan.js +13 -1
- package/dist/rooms-tasks/cli.d.ts +2 -0
- package/dist/rooms-tasks/cli.js +514 -75
- package/dist/rooms-tasks/close.d.ts +9 -1
- package/dist/rooms-tasks/close.js +20 -4
- package/dist/rooms-tasks/config.js +16 -14
- package/dist/rooms-tasks/cowork-adapter.d.ts +1 -0
- package/dist/rooms-tasks/cowork-adapter.js +6 -0
- package/dist/rooms-tasks/deletion.d.ts +51 -0
- package/dist/rooms-tasks/deletion.js +217 -0
- package/dist/rooms-tasks/external-worker.d.ts +1 -0
- package/dist/rooms-tasks/external-worker.js +14 -9
- package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
- package/dist/rooms-tasks/launch-snapshot.js +181 -0
- package/dist/rooms-tasks/member-overrides.d.ts +30 -0
- package/dist/rooms-tasks/member-overrides.js +204 -0
- package/dist/rooms-tasks/provision.js +184 -49
- package/dist/rooms-tasks/room-state.d.ts +1 -0
- package/dist/rooms-tasks/room-state.js +12 -1
- package/dist/rooms-tasks/task-state.d.ts +115 -3
- package/dist/rooms-tasks/task-state.js +361 -11
- package/dist/rooms-tasks/templates.d.ts +4 -2
- package/dist/rooms-tasks/templates.js +42 -78
- package/dist/rooms-tasks/terminal.d.ts +3 -0
- package/dist/rooms-tasks/terminal.js +7 -3
- package/dist/rooms-tasks/types.d.ts +96 -17
- package/dist/rooms-tasks/types.js +14 -4
- package/dist/runner.d.ts +12 -2
- package/dist/runner.js +124 -43
- package/dist/session/acp.d.ts +9 -1
- package/dist/session/acp.js +35 -9
- package/dist/session/arbiter.d.ts +2 -1
- package/dist/session/arbiter.js +2 -0
- package/dist/session/codex-app-server-transport.d.ts +53 -0
- package/dist/session/codex-app-server-transport.js +184 -0
- package/dist/session/codex-app-server.d.ts +122 -0
- package/dist/session/codex-app-server.js +1031 -0
- package/dist/session/control.d.ts +14 -1
- package/dist/session/control.js +38 -0
- package/dist/session/conversation-types.d.ts +4 -4
- package/dist/session/types.d.ts +24 -6
- package/dist/session/types.js +28 -0
- package/dist/spawn.d.ts +15 -31
- package/dist/spawn.js +117 -237
- package/dist/temp-lifecycle.d.ts +1 -1
- package/dist/watchdog/config.d.ts +10 -7
- package/dist/watchdog/config.js +29 -19
- package/dist/watchdog/run.js +4 -13
- package/dist/web/fleet-config-service.d.ts +1 -0
- package/dist/web/fleet-config-service.js +27 -8
- package/dist/web/runtime.js +11 -6
- package/dist/web/server.js +28 -1
- package/dist/web/topology-promote.js +8 -1
- package/dist/web-app/assets/index-C0h3ALvs.js +10 -0
- package/dist/web-app/index.html +1 -1
- package/package.json +3 -2
- package/presets/brain-catalog.json +16 -0
- package/presets/fleet/agent_templates/Critic.yaml +4 -0
- package/presets/fleet/agent_templates/Developer.yaml +4 -0
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +4 -0
- package/presets/fleet/agents/FleetCoordinator.yaml +3 -0
- package/presets/fleet/brains/claude-default.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +30 -0
- package/presets/fleet/roles/Critic.yaml +19 -0
- package/presets/fleet/roles/Developer.yaml +19 -0
- package/presets/fleet/roles/LocalCoordinator.yaml +25 -0
- package/presets/fleet/room_templates/pair.yaml +10 -0
- package/presets/fleet/room_templates/single.yaml +7 -0
- package/presets/fleet/room_templates/team.yaml +12 -0
- package/presets/fleet.yaml +7 -0
- package/presets/manifest.json +6 -0
- package/dist/web-app/assets/index-BbE9EX6n.js +0 -10
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
-
import { mkdir, readFile, readdir, rm } from 'node:fs/promises';
|
|
3
|
+
import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { canonicalCid } from '../config.js';
|
|
6
|
+
import { DAEMON_RECOVERY_DEADLINE_MS } from '../daemon-recovery.js';
|
|
6
7
|
import { replaceFileAtomically } from '../atomic-file.js';
|
|
7
8
|
import { TaskRoomApplicationService } from '../application/task-room-service.js';
|
|
8
9
|
import { RoleLifecycleService } from '../application/role-command-service.js';
|
|
@@ -10,10 +11,11 @@ import { RoleRepository } from '../application/role-repository.js';
|
|
|
10
11
|
import { FleetQueryService } from '../application/fleet-query-service.js';
|
|
11
12
|
import { interruptSession, queueSessionPrompt } from '../application/session-mutations.js';
|
|
12
13
|
import { pickBackend } from '../supervisor/index.js';
|
|
13
|
-
import { ACP_CANCEL_DEADLINE_EXCEEDED, SessionControlError, } from '../session/types.js';
|
|
14
|
+
import { ACP_CANCEL_DEADLINE_EXCEEDED, CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED, SessionControlError, } from '../session/types.js';
|
|
14
15
|
import { VERSION } from '../version.js';
|
|
15
16
|
import { renderMarkdownFailure, renderMarkdownResult, roomStatus, taskStatus, } from '../rooms-tasks/markdown.js';
|
|
16
17
|
import { dispatchOwnerCommand, fleetCliOps, isOwnerCommandText, } from './commands.js';
|
|
18
|
+
import { FleetCommandAuditStore, lifecycleEventDigestBasis, renderFleetLifecycleEvent, } from '../fleet-command-audit.js';
|
|
17
19
|
import { OURS_BOUND_ELSEWHERE, OursSdkClient, oursErrorCode, } from './ours-client.js';
|
|
18
20
|
import { ownerNotices, } from './notices.js';
|
|
19
21
|
import { DuplicateSendError, OwnerAuthorizationState, OwnerChannelState, OwnerConversationState, } from './state.js';
|
|
@@ -21,6 +23,24 @@ import { OwnerTaskState, ownerTaskAuditId, ownerTaskDigest, } from './tasks.js';
|
|
|
21
23
|
import { AttachmentRecoveryState, admitAttachments, cleanupAttachmentRoot, parseIncomingAttachments, parseRetrievedAttachments, prepareAttachmentDirectory, recoveredAttachment, removeRequestDirectory, safeField, validateAttachmentSelection, validateAttachmentRelaySelection, writeRecoveredAttachment, } from './attachments.js';
|
|
22
24
|
import { acquireOwnerBinderLease, OWNER_BIND_HANDOFF_TIMEOUT_MS, } from './binder.js';
|
|
23
25
|
import { MessageRecoveryState } from './message-recovery.js';
|
|
26
|
+
export const OWNER_RECOVERY_QUEUE_CAPACITY = 32;
|
|
27
|
+
export const OWNER_RECOVERY_DEGRADED = 'OWNER_RECOVERY_DEGRADED';
|
|
28
|
+
export class OwnerRecoveryDegradedError extends Error {
|
|
29
|
+
code = OWNER_RECOVERY_DEGRADED;
|
|
30
|
+
constructor() {
|
|
31
|
+
super('owner channel recovery is degraded');
|
|
32
|
+
this.name = 'OwnerRecoveryDegradedError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export class OwnerRecoveryTimeoutError extends Error {
|
|
36
|
+
stage;
|
|
37
|
+
code = OWNER_RECOVERY_DEGRADED;
|
|
38
|
+
constructor(stage) {
|
|
39
|
+
super(`owner channel recovery timed out at ${stage}`);
|
|
40
|
+
this.stage = stage;
|
|
41
|
+
this.name = 'OwnerRecoveryTimeoutError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
24
44
|
const OWNER_UPDATE_MIN_INTERVAL_MS = 5_000;
|
|
25
45
|
const OWNER_UPDATE_MAX_COUNT = 20;
|
|
26
46
|
const OWNER_UPDATE_MAX_CHARS = 280;
|
|
@@ -76,11 +96,22 @@ export class OwnerChannel {
|
|
|
76
96
|
completionTasks = new Set();
|
|
77
97
|
activeRequests = new Map();
|
|
78
98
|
managementTail = Promise.resolve();
|
|
99
|
+
recoveryEpoch;
|
|
100
|
+
recoveryTask;
|
|
101
|
+
recoveryToken = 0;
|
|
102
|
+
recoveryQueued = 0;
|
|
103
|
+
watchGeneration = 0;
|
|
104
|
+
/** A timed-out client operation that must settle before replacement is safe. */
|
|
105
|
+
recoveryQuiescence;
|
|
106
|
+
releaseSafe = true;
|
|
79
107
|
ready = false;
|
|
108
|
+
startedOnce = false;
|
|
109
|
+
shutdownDegraded = false;
|
|
80
110
|
binder;
|
|
81
111
|
binderOwnedInternally = false;
|
|
82
112
|
fleetOps;
|
|
83
113
|
prepareRestart;
|
|
114
|
+
commandAudits;
|
|
84
115
|
constructor(options) {
|
|
85
116
|
this.options = options;
|
|
86
117
|
this.client = options.client ?? new OursSdkClient(options.env, line => options.log(`[${options.role}] owner channel ${line}`));
|
|
@@ -96,6 +127,7 @@ export class OwnerChannel {
|
|
|
96
127
|
await lifecycle.prepareRestart({ roleIds: [role], mode });
|
|
97
128
|
});
|
|
98
129
|
this.state = new OwnerChannelState(join(options.stateDir, '.owner-channel-state.json'));
|
|
130
|
+
this.commandAudits = new FleetCommandAuditStore(join(options.stateDir, '.owner-channel-command-audit.json'));
|
|
99
131
|
this.authorizations = new OwnerAuthorizationState(join(options.stateDir, '.owner-channel-owners.json'), options.config.owners);
|
|
100
132
|
this.conversations = new OwnerConversationState(join(options.stateDir, '.owner-channel-conversations.json'));
|
|
101
133
|
this.tasks = new OwnerTaskState(join(options.stateDir, '.owner-channel-tasks.json'));
|
|
@@ -124,6 +156,7 @@ export class OwnerChannel {
|
|
|
124
156
|
}
|
|
125
157
|
async start() {
|
|
126
158
|
this.stopping = false;
|
|
159
|
+
this.shutdownDegraded = false;
|
|
127
160
|
this.binderOwnedInternally = !this.options.binderLease;
|
|
128
161
|
this.binder = this.options.binderLease ?? await acquireOwnerBinderLease(this.options.stateDir, this.options.role, this.options.config.identity, this.options.binderDeps);
|
|
129
162
|
try {
|
|
@@ -164,11 +197,12 @@ export class OwnerChannel {
|
|
|
164
197
|
this.attachmentRecovery.cleanup(Date.now(), this.attachmentConfig.retention_ms);
|
|
165
198
|
void cleanupAttachmentRoot(this.attachmentRoot, Date.now(), this.attachmentConfig.retention_ms).catch(error => this.logError('attachment crash cleanup failed', error));
|
|
166
199
|
}
|
|
200
|
+
this.startedOnce = true;
|
|
167
201
|
this.ready = true;
|
|
168
202
|
// Do not make role startup wait for an old owner request to finish a turn.
|
|
169
203
|
// watchLoop itself drains before every establishment, including this first
|
|
170
204
|
// one, so there is no drain-to-tip race.
|
|
171
|
-
this.watchTask = this.watchLoop();
|
|
205
|
+
this.watchTask = this.watchLoop(++this.watchGeneration);
|
|
172
206
|
}
|
|
173
207
|
drain() {
|
|
174
208
|
this.drainRequested = true;
|
|
@@ -183,46 +217,240 @@ export class OwnerChannel {
|
|
|
183
217
|
return this.drainTask;
|
|
184
218
|
}
|
|
185
219
|
async close() {
|
|
220
|
+
if (this.recoveryTask)
|
|
221
|
+
this.shutdownDegraded = true;
|
|
186
222
|
this.stopping = true;
|
|
187
223
|
this.ready = false;
|
|
188
|
-
this.
|
|
189
|
-
|
|
224
|
+
this.recoveryToken++;
|
|
225
|
+
this.watchGeneration++;
|
|
226
|
+
const staleWatch = this.watchTask;
|
|
190
227
|
this.watchTask = undefined;
|
|
191
|
-
|
|
228
|
+
this.watchAbort?.abort();
|
|
229
|
+
const now = this.options.recoveryDeps?.now ?? (() => Date.now());
|
|
230
|
+
const deadlineAt = now()
|
|
231
|
+
+ (this.options.recoveryDeps?.deadlineMs ?? DAEMON_RECOVERY_DEADLINE_MS);
|
|
232
|
+
let disposed = false;
|
|
192
233
|
try {
|
|
193
|
-
await this.
|
|
234
|
+
await this.recoveryStage('shutdown_quiescence', Promise.all([
|
|
235
|
+
staleWatch?.catch(error => this.logError('watch shutdown failed', error)),
|
|
236
|
+
this.drainTask?.catch(error => this.logError('drain shutdown failed', error)),
|
|
237
|
+
this.managementTail.then(() => undefined),
|
|
238
|
+
this.recoveryQuiescence,
|
|
239
|
+
]), deadlineAt);
|
|
240
|
+
await this.recoveryStage('shutdown_close', this.client.close(), deadlineAt);
|
|
241
|
+
disposed = true;
|
|
242
|
+
this.writeShutdownState('closed', 'OWNER_SHUTDOWN_CLOSED', now());
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
this.shutdownDegraded = true;
|
|
246
|
+
this.releaseSafe = false;
|
|
247
|
+
const stage = error instanceof OwnerRecoveryTimeoutError ? error.stage : 'shutdown_unknown';
|
|
248
|
+
this.writeShutdownState('degraded', `OWNER_${stage.toUpperCase()}_TIMEOUT`, now());
|
|
249
|
+
this.options.log(`[${this.options.role}] owner shutdown degraded stage=${stage}`);
|
|
194
250
|
}
|
|
195
251
|
finally {
|
|
196
|
-
if (this.binderOwnedInternally)
|
|
252
|
+
if (disposed && this.binderOwnedInternally)
|
|
197
253
|
this.binder?.release();
|
|
198
|
-
|
|
254
|
+
if (disposed)
|
|
255
|
+
this.binder = undefined;
|
|
199
256
|
}
|
|
200
257
|
}
|
|
258
|
+
binderReleaseSafe() { return this.releaseSafe; }
|
|
201
259
|
manage(request) {
|
|
202
|
-
|
|
203
|
-
this.managementTail = run.then(() => undefined, () => undefined);
|
|
204
|
-
return run;
|
|
260
|
+
return this.queueManagement(() => this.manageNow(request));
|
|
205
261
|
}
|
|
206
262
|
notifyFleetSpawn(event) {
|
|
207
|
-
|
|
263
|
+
return this.queueManagement(async () => {
|
|
208
264
|
if (!this.ready || this.stopping)
|
|
209
265
|
throw new Error('owner-channel MCP client is unavailable');
|
|
210
|
-
const model = event.model ? `, model ${event.model}` : '';
|
|
211
|
-
const monitorPolicy = event.monitor.interrupt === true
|
|
212
|
-
? ' with interruption'
|
|
213
|
-
: event.monitor.interrupt === 'after_tool' ? ' with after-tool steering' : '';
|
|
214
|
-
const monitor = `${event.monitor.mode} monitor${monitorPolicy}`;
|
|
215
266
|
const permission = event.permissionMode
|
|
216
|
-
?
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
267
|
+
? `${event.permissionMode.fleetMode}/${event.permissionMode.nativeMode}` : undefined;
|
|
268
|
+
await this.sendProactiveMessage(renderFleetLifecycleEvent({ kind: 'agent_started', eventId: event.creationActionId,
|
|
269
|
+
id: event.role, name: event.role,
|
|
270
|
+
lifetime: event.lifetime, brain: event.brainSummary, role: event.roleSummary,
|
|
271
|
+
harness: event.harness, session: event.session, model: event.model,
|
|
272
|
+
permissions: permission, parent: event.caller, actionId: event.creationActionId,
|
|
273
|
+
inherited: event.inherited,
|
|
274
|
+
...(event.configuration ? { configuration: event.configuration } : {}) }), `fleet-spawn\0${event.creationActionId}`, 0);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
beginFleetCommandAudit(requestId, argv) {
|
|
278
|
+
const run = this.managementTail.then(async () => {
|
|
279
|
+
let attempt = this.commandAudits.begin(requestId, this.options.role, argv);
|
|
280
|
+
if (attempt.invocation === 'delivered')
|
|
281
|
+
return attempt;
|
|
282
|
+
if (attempt.invocation === 'uncertain')
|
|
283
|
+
return attempt;
|
|
284
|
+
this.options.log(`[${this.options.role}] fleet proxy command ${attempt.correlationId} `
|
|
285
|
+
+ `route=${attempt.classification.route} decision=${attempt.classification.decision}`);
|
|
286
|
+
attempt = this.commandAudits.invocation(attempt.correlationId, this.options.role, 'delivered');
|
|
287
|
+
return attempt;
|
|
222
288
|
});
|
|
223
289
|
this.managementTail = run.then(() => undefined, () => undefined);
|
|
224
290
|
return run;
|
|
225
291
|
}
|
|
292
|
+
finishFleetCommandAudit(input) {
|
|
293
|
+
const run = this.managementTail.then(async () => {
|
|
294
|
+
let attempt = this.commandAudits.finish(input.correlationId, this.options.role, {
|
|
295
|
+
class: input.class, effect: input.effect,
|
|
296
|
+
...(input.exitCode === undefined ? {} : { exitCode: input.exitCode }),
|
|
297
|
+
...(input.resourceIds ? { resourceIds: input.resourceIds } : {}),
|
|
298
|
+
...(input.presentations ? { presentations: input.presentations } : {}),
|
|
299
|
+
});
|
|
300
|
+
if (attempt.outcome?.delivery === 'delivered')
|
|
301
|
+
return attempt;
|
|
302
|
+
if (attempt.outcome?.delivery === 'uncertain')
|
|
303
|
+
return attempt;
|
|
304
|
+
let uncertain = false;
|
|
305
|
+
try {
|
|
306
|
+
for (const event of attempt.outcome?.presentations ?? []) {
|
|
307
|
+
try {
|
|
308
|
+
await this.sendProactiveMessage(renderFleetLifecycleEvent(event), `fleet-lifecycle\0${lifecycleEventDigestBasis(event)}`, 0);
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
if (error instanceof DuplicateSendError) {
|
|
312
|
+
if (error.status !== 'delivered')
|
|
313
|
+
uncertain = true;
|
|
314
|
+
this.options.log(`[${this.options.role}] duplicate Fleet lifecycle event suppressed`);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
throw error;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
uncertain = true;
|
|
323
|
+
this.logError(`fleet lifecycle delivery uncertain correlation=${attempt.correlationId}`, error);
|
|
324
|
+
}
|
|
325
|
+
attempt = this.commandAudits.outcome(attempt.correlationId, this.options.role, uncertain ? 'uncertain' : 'delivered');
|
|
326
|
+
return attempt;
|
|
327
|
+
});
|
|
328
|
+
this.managementTail = run.then(() => undefined, () => undefined);
|
|
329
|
+
return run;
|
|
330
|
+
}
|
|
331
|
+
recover(epoch) {
|
|
332
|
+
if (this.stopping)
|
|
333
|
+
return Promise.reject(new Error('owner channel is stopping'));
|
|
334
|
+
if (this.recoveryEpoch === epoch && this.recoveryTask)
|
|
335
|
+
return this.recoveryTask;
|
|
336
|
+
this.recoveryEpoch = epoch;
|
|
337
|
+
this.ready = false;
|
|
338
|
+
const token = ++this.recoveryToken;
|
|
339
|
+
const watchGeneration = ++this.watchGeneration;
|
|
340
|
+
const staleWatch = this.watchTask;
|
|
341
|
+
this.watchTask = undefined;
|
|
342
|
+
this.watchAbort?.abort();
|
|
343
|
+
const superseded = () => this.stopping || token !== this.recoveryToken;
|
|
344
|
+
const now = this.options.recoveryDeps?.now ?? (() => Date.now());
|
|
345
|
+
const deadlineAt = now()
|
|
346
|
+
+ (this.options.recoveryDeps?.deadlineMs ?? DAEMON_RECOVERY_DEADLINE_MS);
|
|
347
|
+
const run = this.managementTail.then(async () => {
|
|
348
|
+
if (superseded())
|
|
349
|
+
throw new Error('owner recovery epoch superseded');
|
|
350
|
+
if (this.recoveryQuiescence) {
|
|
351
|
+
const debt = this.recoveryQuiescence;
|
|
352
|
+
await this.recoveryStage('prior_quiescence', debt, deadlineAt);
|
|
353
|
+
if (this.recoveryQuiescence === debt)
|
|
354
|
+
this.recoveryQuiescence = undefined;
|
|
355
|
+
}
|
|
356
|
+
// Quiesce every stale-client reader before replacing its transport. A
|
|
357
|
+
// watch can be inside the shared drain task when its abort lands.
|
|
358
|
+
await this.recoveryStage('stale_quiescence', Promise.all([
|
|
359
|
+
staleWatch?.catch(error => this.logError('stale watch shutdown failed', error)),
|
|
360
|
+
this.drainTask?.catch(error => this.logError('stale drain shutdown failed', error)),
|
|
361
|
+
]), deadlineAt);
|
|
362
|
+
if (superseded())
|
|
363
|
+
throw new Error('owner recovery epoch superseded');
|
|
364
|
+
await this.recoveryStage('close', this.client.close(), deadlineAt);
|
|
365
|
+
if (superseded())
|
|
366
|
+
throw new Error('owner recovery epoch superseded');
|
|
367
|
+
try {
|
|
368
|
+
await this.recoveryStage('start', this.client.start(), deadlineAt);
|
|
369
|
+
if (superseded())
|
|
370
|
+
throw new Error('owner recovery epoch superseded');
|
|
371
|
+
await this.recoveryStage('bind', this.client.bindIdentity(this.options.config.identity), deadlineAt);
|
|
372
|
+
if (superseded())
|
|
373
|
+
throw new Error('owner recovery epoch superseded');
|
|
374
|
+
// Journal-aware drain is the only operation allowed to mark owner mail
|
|
375
|
+
// read. The final listing is a typed, read-only channel probe.
|
|
376
|
+
await this.recoveryStage('drain', this.drain(), deadlineAt);
|
|
377
|
+
await this.recoveryStage('list', this.client.listIncomingMessages(), deadlineAt);
|
|
378
|
+
if (superseded())
|
|
379
|
+
throw new Error('owner recovery epoch superseded');
|
|
380
|
+
this.ready = true;
|
|
381
|
+
this.watchTask = this.watchLoop(watchGeneration);
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
this.ready = false;
|
|
385
|
+
if (token === this.recoveryToken)
|
|
386
|
+
this.recoveryToken++;
|
|
387
|
+
// A timed-out operation may still be using this client. Never overlap
|
|
388
|
+
// its disposal; the recorded quiescence debt gates the next retry.
|
|
389
|
+
if (!this.recoveryQuiescence) {
|
|
390
|
+
try {
|
|
391
|
+
await this.recoveryStage('close_after_failure', this.client.close(), deadlineAt);
|
|
392
|
+
}
|
|
393
|
+
catch (closeError) {
|
|
394
|
+
this.logError('recovery client close failed', closeError);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
this.managementTail = run.then(() => undefined, () => undefined);
|
|
401
|
+
const task = run.finally(() => {
|
|
402
|
+
if (this.recoveryTask === task)
|
|
403
|
+
this.recoveryTask = undefined;
|
|
404
|
+
});
|
|
405
|
+
this.recoveryTask = task;
|
|
406
|
+
return task;
|
|
407
|
+
}
|
|
408
|
+
recoveryStage(stage, operation, deadlineAt) {
|
|
409
|
+
const now = this.options.recoveryDeps?.now ?? (() => Date.now());
|
|
410
|
+
const remaining = Math.max(0, deadlineAt - now());
|
|
411
|
+
const setTimer = this.options.recoveryDeps?.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
412
|
+
const clearTimer = this.options.recoveryDeps?.clearTimer ?? (timer => clearTimeout(timer));
|
|
413
|
+
let timer;
|
|
414
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
415
|
+
timer = setTimer(() => reject(new OwnerRecoveryTimeoutError(stage)), remaining);
|
|
416
|
+
});
|
|
417
|
+
return Promise.race([operation, timeout]).catch(error => {
|
|
418
|
+
if (error instanceof OwnerRecoveryTimeoutError) {
|
|
419
|
+
const debt = operation.then(() => undefined, () => undefined);
|
|
420
|
+
this.recoveryQuiescence = debt;
|
|
421
|
+
}
|
|
422
|
+
throw error;
|
|
423
|
+
}).finally(() => clearTimer(timer));
|
|
424
|
+
}
|
|
425
|
+
writeShutdownState(state, reason, at) {
|
|
426
|
+
try {
|
|
427
|
+
replaceFileAtomically(join(this.options.stateDir, '.owner-channel-shutdown.json'), JSON.stringify({ version: 1, state, reason, at: new Date(at).toISOString() }, null, 2) + '\n');
|
|
428
|
+
}
|
|
429
|
+
catch (error) {
|
|
430
|
+
this.logError('shutdown status write failed', error);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
queueManagement(operation) {
|
|
434
|
+
const deferred = this.recoveryTask !== undefined && !this.ready;
|
|
435
|
+
if (!this.ready && !deferred)
|
|
436
|
+
return Promise.reject(!this.startedOnce || (this.stopping && !this.shutdownDegraded)
|
|
437
|
+
? new Error('owner-channel MCP client is unavailable')
|
|
438
|
+
: new OwnerRecoveryDegradedError());
|
|
439
|
+
if (deferred && this.recoveryQueued >= OWNER_RECOVERY_QUEUE_CAPACITY)
|
|
440
|
+
return Promise.reject(new Error('owner recovery queue is full'));
|
|
441
|
+
if (deferred)
|
|
442
|
+
this.recoveryQueued++;
|
|
443
|
+
const run = this.managementTail.then(() => {
|
|
444
|
+
if (!this.ready || this.stopping)
|
|
445
|
+
throw new OwnerRecoveryDegradedError();
|
|
446
|
+
return operation();
|
|
447
|
+
});
|
|
448
|
+
this.managementTail = run.then(() => undefined, () => undefined);
|
|
449
|
+
return run.finally(() => {
|
|
450
|
+
if (deferred)
|
|
451
|
+
this.recoveryQueued = Math.max(0, this.recoveryQueued - 1);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
226
454
|
async manageNow(request) {
|
|
227
455
|
if (!this.ready || this.stopping)
|
|
228
456
|
throw new Error('owner-channel MCP client is unavailable');
|
|
@@ -943,12 +1171,13 @@ export class OwnerChannel {
|
|
|
943
1171
|
catch (error) {
|
|
944
1172
|
await rm(outbox, { recursive: true, force: true });
|
|
945
1173
|
if (error instanceof SessionControlError
|
|
946
|
-
&& error.reasonCode === ACP_CANCEL_DEADLINE_EXCEEDED
|
|
1174
|
+
&& (error.reasonCode === ACP_CANCEL_DEADLINE_EXCEEDED
|
|
1175
|
+
|| error.reasonCode === CODEX_APP_SERVER_CANCEL_DEADLINE_EXCEEDED)) {
|
|
947
1176
|
// drainAll journaled this authenticated message before delivery. The
|
|
948
1177
|
// adapter generation is terminating, so leave the wire unhandled and
|
|
949
1178
|
// body-free: the resumed owner channel recovers it exactly once.
|
|
950
1179
|
this.options.log(`[${this.options.role}] owner request ${requestId.slice(0, 12)} `
|
|
951
|
-
+ `held for adapter resume reason=${
|
|
1180
|
+
+ `held for adapter resume reason=${error.reasonCode}`);
|
|
952
1181
|
return false;
|
|
953
1182
|
}
|
|
954
1183
|
this.logError('request delivery failed', error);
|
|
@@ -989,6 +1218,7 @@ export class OwnerChannel {
|
|
|
989
1218
|
*/
|
|
990
1219
|
async handleCommand(sender, text, wireId) {
|
|
991
1220
|
const ctx = {
|
|
1221
|
+
authenticatedCid: sender.id,
|
|
992
1222
|
role: this.options.role,
|
|
993
1223
|
harness: this.options.harness,
|
|
994
1224
|
version: VERSION,
|
|
@@ -1041,9 +1271,7 @@ export class OwnerChannel {
|
|
|
1041
1271
|
reviewTask: taskId => new TaskRoomApplicationService(this.options.configPath).reviewTask({
|
|
1042
1272
|
actor: { kind: 'authenticated_owner', surface: 'messenger', cid: sender.id }, taskId,
|
|
1043
1273
|
}),
|
|
1044
|
-
deleteTask: taskId =>
|
|
1045
|
-
actor: { kind: 'authenticated_owner', surface: 'messenger', cid: sender.id }, taskId,
|
|
1046
|
-
}),
|
|
1274
|
+
deleteTask: taskId => this.deleteTaskFromOwner(sender, taskId, wireId),
|
|
1047
1275
|
listRoomQueries: filter => new TaskRoomApplicationService(this.options.configPath).listRooms(filter),
|
|
1048
1276
|
getRoomQuery: id => new TaskRoomApplicationService(this.options.configPath).getRoomDetail(id),
|
|
1049
1277
|
listTemplateQueries: () => new TaskRoomApplicationService(this.options.configPath).listTemplates(),
|
|
@@ -1054,6 +1282,18 @@ export class OwnerChannel {
|
|
|
1054
1282
|
recentEvents: limit => this.options.session.eventsSince(0).slice(-limit),
|
|
1055
1283
|
readWorklogTail: maxChars => this.readWorklogTail(maxChars),
|
|
1056
1284
|
reply: async (replyText) => { await this.send(sender.id, replyText, wireId); },
|
|
1285
|
+
replyHtml: async (filename, html) => {
|
|
1286
|
+
const directory = join(this.options.stateDir, '.owner-channel-report-outbox', this.requestId(wireId));
|
|
1287
|
+
const path = join(directory, filename);
|
|
1288
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
1289
|
+
try {
|
|
1290
|
+
await writeFile(path, html, { flag: 'wx', mode: 0o600 });
|
|
1291
|
+
await this.client.sendFile({ contact: sender.id, path, filename, replyToWireId: wireId });
|
|
1292
|
+
}
|
|
1293
|
+
finally {
|
|
1294
|
+
await rm(directory, { recursive: true, force: true });
|
|
1295
|
+
}
|
|
1296
|
+
},
|
|
1057
1297
|
};
|
|
1058
1298
|
try {
|
|
1059
1299
|
await dispatchOwnerCommand(text, ctx);
|
|
@@ -1161,10 +1401,13 @@ export class OwnerChannel {
|
|
|
1161
1401
|
const app = new TaskRoomApplicationService(this.options.configPath);
|
|
1162
1402
|
const actor = { kind: 'authenticated_owner', surface: 'messenger', cid: sender.id };
|
|
1163
1403
|
const begin = await app.beginTaskRecovery({ actor, taskId });
|
|
1164
|
-
if (begin.kind
|
|
1404
|
+
if (begin.kind !== 'final') {
|
|
1405
|
+
const deleting = begin.kind === 'deletion_worker_required';
|
|
1165
1406
|
await this.send(sender.id, renderMarkdownFailure({
|
|
1166
1407
|
kind: 'pending', subject: `/task recover ${taskId}`,
|
|
1167
|
-
detail:
|
|
1408
|
+
detail: deleting
|
|
1409
|
+
? 'The task is pending deletion; recovery continues its cleanup.'
|
|
1410
|
+
: 'The recovery request was accepted and is still being settled.',
|
|
1168
1411
|
action: `Run /task recover ${taskId} if it remains pending.`,
|
|
1169
1412
|
}), wireId);
|
|
1170
1413
|
this.state.remember(wireId);
|
|
@@ -1172,9 +1415,13 @@ export class OwnerChannel {
|
|
|
1172
1415
|
await this.fleetOps.recoverTask(taskId);
|
|
1173
1416
|
}
|
|
1174
1417
|
catch (error) {
|
|
1175
|
-
|
|
1176
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1177
|
-
recoveryHint:
|
|
1418
|
+
const failure = {
|
|
1419
|
+
actor, taskId, error: error instanceof Error ? error.message : String(error),
|
|
1420
|
+
recoveryHint: deleting
|
|
1421
|
+
? `External delete worker failed to start. Retry /task delete ${taskId} ${taskId}.`
|
|
1422
|
+
: `External settle worker failed to start. Retry /task recover ${taskId}.`,
|
|
1423
|
+
};
|
|
1424
|
+
await (deleting ? app.recordDeletionError(failure) : app.recordSettlementError(failure));
|
|
1178
1425
|
throw error;
|
|
1179
1426
|
}
|
|
1180
1427
|
return;
|
|
@@ -1199,6 +1446,36 @@ export class OwnerChannel {
|
|
|
1199
1446
|
}), wireId);
|
|
1200
1447
|
this.state.remember(wireId);
|
|
1201
1448
|
}
|
|
1449
|
+
/** Accept a permanent any-state deletion, acknowledge it, then hand cleanup to a durable worker. */
|
|
1450
|
+
async deleteTaskFromOwner(sender, taskId, wireId) {
|
|
1451
|
+
const app = new TaskRoomApplicationService(this.options.configPath);
|
|
1452
|
+
const actor = { kind: 'authenticated_owner', surface: 'messenger', cid: sender.id };
|
|
1453
|
+
const accepted = await app.requestTaskDeletion({ actor, taskId });
|
|
1454
|
+
if (accepted.status === 'already_absent') {
|
|
1455
|
+
await this.send(sender.id, renderMarkdownResult({
|
|
1456
|
+
icon: '🗑️', title: 'Task already absent',
|
|
1457
|
+
fields: [{ label: 'ID', value: taskId, kind: 'code' }],
|
|
1458
|
+
}), wireId);
|
|
1459
|
+
this.state.remember(wireId);
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
await this.send(sender.id, renderMarkdownFailure({
|
|
1463
|
+
kind: 'pending', subject: `/task delete ${taskId} ${taskId}`,
|
|
1464
|
+
detail: 'The deletion was accepted; cleanup is settling in the background.',
|
|
1465
|
+
action: `Run /task recover ${taskId} or repeat /task delete ${taskId} ${taskId} if it remains pending.`,
|
|
1466
|
+
}), wireId);
|
|
1467
|
+
this.state.remember(wireId);
|
|
1468
|
+
try {
|
|
1469
|
+
await this.fleetOps.settleTaskDeletion(taskId);
|
|
1470
|
+
}
|
|
1471
|
+
catch (error) {
|
|
1472
|
+
await app.recordDeletionError({
|
|
1473
|
+
actor, taskId, error: error instanceof Error ? error.message : String(error),
|
|
1474
|
+
recoveryHint: `External delete worker failed to start. Retry /task delete ${taskId} ${taskId}.`,
|
|
1475
|
+
});
|
|
1476
|
+
throw error;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1202
1479
|
async terminalTaskFromOwner(sender, taskId, kind, outcome, wireId) {
|
|
1203
1480
|
const app = new TaskRoomApplicationService(this.options.configPath);
|
|
1204
1481
|
const actor = { kind: 'authenticated_owner', surface: 'messenger', cid: sender.id };
|
|
@@ -1686,7 +1963,8 @@ export class OwnerChannel {
|
|
|
1686
1963
|
startProgress(event);
|
|
1687
1964
|
// Automatic commentary is an ACP phase extension. Other backends and
|
|
1688
1965
|
// older adapters retain their established final-only behavior.
|
|
1689
|
-
if (this.options.session.
|
|
1966
|
+
if (this.options.session.capabilities?.messagePhases
|
|
1967
|
+
?? this.options.session.backend === 'acp')
|
|
1690
1968
|
acceptCommentary(event);
|
|
1691
1969
|
})
|
|
1692
1970
|
: () => undefined;
|
|
@@ -1728,7 +2006,8 @@ export class OwnerChannel {
|
|
|
1728
2006
|
enabled: this.commentsEnabled,
|
|
1729
2007
|
baseline: this.commentsBaseline,
|
|
1730
2008
|
// Only the ACP backend emits the phase marker commentary relaying needs.
|
|
1731
|
-
supported: this.options.session.
|
|
2009
|
+
supported: this.options.session.capabilities?.messagePhases
|
|
2010
|
+
?? this.options.session.backend === 'acp',
|
|
1732
2011
|
};
|
|
1733
2012
|
}
|
|
1734
2013
|
/** Model-authored commentary only; raw protocol/tool data never reaches here. */
|
|
@@ -1876,7 +2155,7 @@ export class OwnerChannel {
|
|
|
1876
2155
|
}
|
|
1877
2156
|
return undefined;
|
|
1878
2157
|
}
|
|
1879
|
-
async watchLoop() {
|
|
2158
|
+
async watchLoop(generation) {
|
|
1880
2159
|
const sleep = this.options.binderDeps?.sleep
|
|
1881
2160
|
?? (ms => new Promise(resolve => setTimeout(resolve, ms)));
|
|
1882
2161
|
const restored = this.readWatchState();
|
|
@@ -1885,7 +2164,7 @@ export class OwnerChannel {
|
|
|
1885
2164
|
state = this.writeWatchState(state, 'OWNER_WATCH_STATE_RECOVERED');
|
|
1886
2165
|
let delayMs = 1_000;
|
|
1887
2166
|
let attempts = 0;
|
|
1888
|
-
while (!this.stopping) {
|
|
2167
|
+
while (!this.stopping && generation === this.watchGeneration) {
|
|
1889
2168
|
const ctrl = new AbortController();
|
|
1890
2169
|
this.watchAbort = ctrl;
|
|
1891
2170
|
try {
|
|
@@ -1895,12 +2174,12 @@ export class OwnerChannel {
|
|
|
1895
2174
|
// fall into a gap. Persistent history plus fleet's body-free claim
|
|
1896
2175
|
// journal is authoritative; durable wire-ID dedupe makes hints harmless.
|
|
1897
2176
|
await this.drain();
|
|
1898
|
-
if (this.stopping)
|
|
2177
|
+
if (this.stopping || generation !== this.watchGeneration)
|
|
1899
2178
|
return;
|
|
1900
2179
|
state = this.writeWatchState(state, 'OWNER_WATCH_CONNECTING', { reconnected: attempts > 0 });
|
|
1901
2180
|
attempts++;
|
|
1902
2181
|
for await (const _event of this.client.watchNotifications(this.options.config.identity, { since: 0, signal: ctrl.signal })) {
|
|
1903
|
-
if (this.stopping)
|
|
2182
|
+
if (this.stopping || generation !== this.watchGeneration)
|
|
1904
2183
|
return;
|
|
1905
2184
|
state = this.writeWatchState(state, 'OWNER_WATCH_CONNECTED', { resetFailures: true });
|
|
1906
2185
|
delayMs = 1_000;
|
|
@@ -1909,11 +2188,11 @@ export class OwnerChannel {
|
|
|
1909
2188
|
// recorded before a managed request is dispatched.
|
|
1910
2189
|
await this.drain();
|
|
1911
2190
|
}
|
|
1912
|
-
if (!this.stopping)
|
|
2191
|
+
if (!this.stopping && generation === this.watchGeneration)
|
|
1913
2192
|
throw new Error('ours SDK notification stream ended');
|
|
1914
2193
|
}
|
|
1915
2194
|
catch (error) {
|
|
1916
|
-
if (this.stopping)
|
|
2195
|
+
if (this.stopping || generation !== this.watchGeneration)
|
|
1917
2196
|
return;
|
|
1918
2197
|
// SDK 2 deliberately hides transport status behind its typed stream.
|
|
1919
2198
|
// Do not parse error prose to rediscover it: every failure follows the
|
|
@@ -17,6 +17,8 @@ export interface OwnerFleetOps {
|
|
|
17
17
|
closeRoom(roomId: string): Promise<void>;
|
|
18
18
|
/** Resume a task terminal intent outside the caller role's supervisor lifecycle. */
|
|
19
19
|
settleTask(taskId: string): Promise<void>;
|
|
20
|
+
/** Settle an accepted task deletion outside the caller role's supervisor lifecycle. */
|
|
21
|
+
settleTaskDeletion(taskId: string): Promise<void>;
|
|
20
22
|
recoverTask(taskId: string): Promise<void>;
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
@@ -25,6 +27,7 @@ export interface OwnerFleetOps {
|
|
|
25
27
|
* cannot name another agent or another recipient.
|
|
26
28
|
*/
|
|
27
29
|
export interface OwnerCommandContext {
|
|
30
|
+
authenticatedCid?: string;
|
|
28
31
|
role: string;
|
|
29
32
|
/** Harness id of the role (e.g. 'claude-code', 'codex'); gates forwarding. */
|
|
30
33
|
harness: string;
|
|
@@ -83,7 +86,8 @@ export interface OwnerCommandContext {
|
|
|
83
86
|
blockTask(taskId: string, reason: string): TaskRecord;
|
|
84
87
|
unblockTask(taskId: string): TaskRecord;
|
|
85
88
|
reviewTask(taskId: string): TaskRecord;
|
|
86
|
-
|
|
89
|
+
/** Accept a permanent any-state deletion, acknowledge it, then launch the external delete worker. */
|
|
90
|
+
deleteTask(taskId: string): Promise<void>;
|
|
87
91
|
listRoomQueries(filter?: {
|
|
88
92
|
state?: 'active' | 'provisioning';
|
|
89
93
|
}): ReturnType<TaskRoomApplicationService['listRooms']>;
|
|
@@ -94,6 +98,7 @@ export interface OwnerCommandContext {
|
|
|
94
98
|
recentEvents(limit: number): SessionEvent[];
|
|
95
99
|
readWorklogTail(maxChars: number): Promise<string | undefined>;
|
|
96
100
|
reply(text: string): Promise<void>;
|
|
101
|
+
replyHtml(filename: string, html: string): Promise<void>;
|
|
97
102
|
}
|
|
98
103
|
export interface OwnerCommand {
|
|
99
104
|
/** Primary name without the leading slash. */
|