@ours.network/fleet 0.19.0-nightly.9 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -11
- package/dist/application/fleet-query-service.d.ts +1 -1
- package/dist/application/fleet-query-service.js +12 -0
- package/dist/application/role-creation-service.js +3 -1
- package/dist/application/types.d.ts +11 -0
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +103 -10
- package/dist/build-info.json +5 -5
- package/dist/cli.js +41 -11
- package/dist/config.d.ts +21 -7
- package/dist/config.js +32 -18
- package/dist/creation.d.ts +1 -1
- package/dist/docs.d.ts +2 -2
- package/dist/docs.js +117 -24
- package/dist/doctor.js +10 -10
- package/dist/duration.js +1 -1
- package/dist/fleet-proxy.d.ts +5 -0
- package/dist/harness/acp-agent.js +11 -6
- package/dist/harness/claude-code.js +201 -8
- package/dist/harness/codex.d.ts +4 -1
- package/dist/harness/codex.js +72 -13
- package/dist/harness/types.d.ts +57 -6
- package/dist/isolation/bubblewrap.d.ts +4 -4
- package/dist/isolation/bubblewrap.js +4 -4
- package/dist/isolation/policy.d.ts +1 -1
- package/dist/isolation/policy.js +2 -2
- package/dist/isolation/registry.d.ts +3 -3
- package/dist/isolation/registry.js +5 -5
- package/dist/isolation/resources.d.ts +1 -1
- package/dist/isolation/resources.js +1 -1
- package/dist/isolation/types.d.ts +3 -3
- package/dist/loops/manager.d.ts +30 -1
- package/dist/loops/manager.js +69 -6
- package/dist/loops/state.d.ts +18 -0
- package/dist/loops/state.js +4 -0
- package/dist/model-env.d.ts +71 -0
- package/dist/model-env.js +106 -0
- package/dist/monitor.d.ts +2 -2
- package/dist/monitor.js +4 -4
- package/dist/ops.d.ts +2 -2
- package/dist/ops.js +9 -9
- package/dist/owner-channel/channel.d.ts +17 -0
- package/dist/owner-channel/channel.js +104 -23
- package/dist/owner-channel/commands.d.ts +9 -0
- package/dist/owner-channel/commands.js +226 -99
- package/dist/owner-channel/notices.d.ts +7 -0
- package/dist/owner-channel/notices.js +9 -0
- package/dist/rooms-tasks/cli.js +731 -219
- package/dist/rooms-tasks/close.d.ts +35 -0
- package/dist/rooms-tasks/close.js +190 -0
- package/dist/rooms-tasks/cowork-adapter.d.ts +24 -0
- package/dist/rooms-tasks/cowork-adapter.js +120 -0
- package/dist/rooms-tasks/external-worker.d.ts +2 -0
- package/dist/rooms-tasks/external-worker.js +40 -0
- package/dist/rooms-tasks/index.d.ts +2 -0
- package/dist/rooms-tasks/index.js +2 -0
- package/dist/rooms-tasks/markdown.d.ts +49 -0
- package/dist/rooms-tasks/markdown.js +206 -0
- package/dist/rooms-tasks/member-startup.d.ts +51 -0
- package/dist/rooms-tasks/member-startup.js +151 -0
- package/dist/rooms-tasks/provision.d.ts +8 -0
- package/dist/rooms-tasks/provision.js +418 -97
- package/dist/rooms-tasks/room-state.d.ts +13 -1
- package/dist/rooms-tasks/room-state.js +149 -1
- package/dist/rooms-tasks/task-state.d.ts +13 -2
- package/dist/rooms-tasks/task-state.js +131 -3
- package/dist/rooms-tasks/terminal.d.ts +23 -0
- package/dist/rooms-tasks/terminal.js +56 -0
- package/dist/rooms-tasks/types.d.ts +108 -2
- package/dist/runner.d.ts +48 -0
- package/dist/runner.js +252 -100
- package/dist/session/acp.d.ts +106 -2
- package/dist/session/acp.js +217 -14
- package/dist/session/activity.d.ts +31 -0
- package/dist/session/activity.js +48 -0
- package/dist/session/control.d.ts +1 -1
- package/dist/session/conversation-normalizer.d.ts +7 -1
- package/dist/session/conversation-normalizer.js +154 -11
- package/dist/session/conversation-store.js +1 -1
- package/dist/session/conversation-types.d.ts +25 -6
- package/dist/session/types.d.ts +35 -0
- package/dist/spawn.d.ts +4 -2
- package/dist/spawn.js +39 -26
- package/dist/supervisor/launchd.d.ts +2 -2
- package/dist/supervisor/launchd.js +4 -4
- package/dist/supervisor/systemd.js +4 -4
- package/dist/supervisor/types.d.ts +1 -1
- package/dist/temp-lifecycle.d.ts +14 -2
- package/dist/temp-lifecycle.js +90 -0
- package/dist/watchdog/alerts.d.ts +2 -2
- package/dist/watchdog/alerts.js +2 -2
- package/dist/watchdog/briefing.d.ts +5 -5
- package/dist/watchdog/briefing.js +13 -6
- package/dist/watchdog/config.js +1 -1
- package/dist/watchdog/query.d.ts +2 -2
- package/dist/watchdog/query.js +2 -2
- package/dist/watchdog/report.js +2 -2
- package/dist/watchdog/run.d.ts +2 -2
- package/dist/watchdog/run.js +6 -6
- package/dist/watchdog/scheduler.d.ts +7 -7
- package/dist/watchdog/scheduler.js +10 -10
- package/dist/watchdog/service.d.ts +4 -4
- package/dist/watchdog/service.js +5 -6
- package/dist/watchdog/store.d.ts +4 -4
- package/dist/watchdog/store.js +4 -4
- package/dist/web/runtime.js +1 -1
- package/dist/web/server.js +1 -1
- package/dist/web/topology-promote.js +1 -1
- package/dist/web-app/assets/{TerminalView-BAVk1Bot.js → TerminalView-C_G1ID2P.js} +1 -1
- package/dist/web-app/assets/{index-C3S-xFRU.js → index-BCBK78hw.js} +5 -5
- package/dist/web-app/index.html +1 -1
- package/dist/worklog.d.ts +7 -1
- package/dist/worklog.js +191 -39
- package/package.json +1 -1
package/dist/ops.js
CHANGED
|
@@ -17,7 +17,7 @@ import { daemonIdentityProvisioner, reconcilePermanentRoleIdentities, } from './
|
|
|
17
17
|
/** Materialize a role's state dir from config: briefing + markers. Returns the dir. */
|
|
18
18
|
export function applyRole(role, opts = {}) {
|
|
19
19
|
const adapter = getAdapter(role.harness);
|
|
20
|
-
const errs = adapter.validateOptions(role.harness_options);
|
|
20
|
+
const errs = adapter.validateOptions(role.harness_options, role);
|
|
21
21
|
if (errs.length)
|
|
22
22
|
throw new Error(`role '${role.name}': ` + errs.map(e => `${e.path}: ${e.message}`).join('; '));
|
|
23
23
|
const dir = agentDir(role.name, opts.temp === true);
|
|
@@ -62,7 +62,7 @@ export async function up(cfg, names, deps, configPath, identityGuarantee) {
|
|
|
62
62
|
// guessing "stopped" from an unanswered probe silently discards a live
|
|
63
63
|
// conversation.
|
|
64
64
|
// An explicit operator `up` is the sanctioned way to release a held-down
|
|
65
|
-
// role: the still-alive runner polls this file and resumes
|
|
65
|
+
// role: the still-alive runner polls this file and resumes.
|
|
66
66
|
resetRestartLedger(dir);
|
|
67
67
|
const live = await deps.backend.liveness(role.name)
|
|
68
68
|
.catch(e => ({ state: 'unknown', detail: e instanceof Error ? e.message : String(e) }));
|
|
@@ -71,7 +71,7 @@ export async function up(cfg, names, deps, configPath, identityGuarantee) {
|
|
|
71
71
|
else if (live.state === 'unknown')
|
|
72
72
|
deps.log(` ! ${role.name}: liveness unknown, keeping session context — ${live.detail}`);
|
|
73
73
|
// Report what each install actually did, so a creation transaction can undo
|
|
74
|
-
// only the registrations IT made
|
|
74
|
+
// only the registrations IT made. Announced immediately as well as
|
|
75
75
|
// returned: a later role in this same loop can throw, and the registrations
|
|
76
76
|
// already made must still be undoable.
|
|
77
77
|
const outcome = { ...await deps.backend.install(role.name, deps.binPath), role: role.name };
|
|
@@ -94,7 +94,7 @@ function stableStringify(value) {
|
|
|
94
94
|
return v;
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
|
-
/**
|
|
97
|
+
/** SHA-256 over the resolved enabled-watchdog set, stable regardless of config source order. */
|
|
98
98
|
function watchdogFingerprint(enabled) {
|
|
99
99
|
const sorted = [...enabled].sort((a, b) => a.name.localeCompare(b.name));
|
|
100
100
|
return createHash('sha256').update(stableStringify(sorted)).digest('hex');
|
|
@@ -117,14 +117,14 @@ function writeFingerprint(fingerprint) {
|
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
119
119
|
* Install/start (or stop) the single supervised watchdog-scheduler process
|
|
120
|
-
*
|
|
120
|
+
* to match the config's enabled watchdogs. Runs on every `up`,
|
|
121
121
|
* including a named `up <Role>` — cheap and idempotent, and the alternative
|
|
122
122
|
* (only reconciling on a whole-fleet `up`) would leave a newly-enabled
|
|
123
123
|
* watchdog unscheduled until the next bare `up`. Never throws: a scheduler
|
|
124
124
|
* hiccup must not fail the role installs that already succeeded.
|
|
125
125
|
*
|
|
126
126
|
* Restarts ONLY when something the scheduler actually needs to pick up
|
|
127
|
-
* changed
|
|
127
|
+
* changed: an unconditional `restart()` on every `up` —
|
|
128
128
|
* including a named `up <SomeUnrelatedRole>` — interrupted whatever the
|
|
129
129
|
* scheduler was mid-run (a 15s stop timeout can kill a long check without a
|
|
130
130
|
* report). Two independent signals decide: `svc.install()`'s own `changed`
|
|
@@ -142,7 +142,7 @@ async function reconcileWatchdogScheduler(cfg, deps, configPath) {
|
|
|
142
142
|
return;
|
|
143
143
|
const enabled = cfg.watchdogs.filter(w => w.enabled);
|
|
144
144
|
try {
|
|
145
|
-
// Check supervised() before any stop
|
|
145
|
+
// Check supervised() before any stop: on an
|
|
146
146
|
// unsupervised platform/config, the scheduler was never installed, so
|
|
147
147
|
// stopping it is not just a no-op — on Linux, stop() throws for a unit
|
|
148
148
|
// that was never loaded. A watchdog-less fleet must not see that
|
|
@@ -162,7 +162,7 @@ async function reconcileWatchdogScheduler(cfg, deps, configPath) {
|
|
|
162
162
|
if (unitChanged || fingerprintChanged) {
|
|
163
163
|
// restart(), not start(): start() is a no-op on an already-active unit,
|
|
164
164
|
// so a config change (new/changed watchdogs) would never reach a
|
|
165
|
-
// scheduler that's already running
|
|
165
|
+
// scheduler that's already running.
|
|
166
166
|
await svc.restart();
|
|
167
167
|
deps.log(`↑ watchdogs scheduler (${enabled.map(w => w.name).join(', ')})`);
|
|
168
168
|
}
|
|
@@ -208,7 +208,7 @@ export async function down(cfg, names, deps) {
|
|
|
208
208
|
// Only a whole-fleet `down` (no names) stops the scheduler — stopping one
|
|
209
209
|
// named role is not a decision to stop watching the others (tolerate
|
|
210
210
|
// absence/errors: a never-installed scheduler must not fail `down`).
|
|
211
|
-
// supervised() gates the call itself
|
|
211
|
+
// supervised() gates the call itself: on an
|
|
212
212
|
// unsupervised platform/config there is nothing installed to stop, and
|
|
213
213
|
// Linux's stop() throws for a unit that was never loaded.
|
|
214
214
|
if (names.length === 0 && deps.watchdogService?.supervised()) {
|
|
@@ -217,6 +217,13 @@ export declare class OwnerChannel implements OwnerChannelHandle {
|
|
|
217
217
|
* CLI is asked to bounce this very process; neither can happen afterwards.
|
|
218
218
|
*/
|
|
219
219
|
private restartSelf;
|
|
220
|
+
/**
|
|
221
|
+
* The caller may itself be a room member. Persist and acknowledge acceptance
|
|
222
|
+
* before an external worker starts a saga that can retire this process.
|
|
223
|
+
*/
|
|
224
|
+
private closeRoomFromOwner;
|
|
225
|
+
/** Carry a task terminal request through a worker that survives this role. */
|
|
226
|
+
private terminalTaskFromOwner;
|
|
220
227
|
/** Code-point-safe tail of the worklog, or undefined when there is none. */
|
|
221
228
|
private readWorklogTail;
|
|
222
229
|
/** Bound harness-command output to a single outbound message. */
|
|
@@ -250,6 +257,16 @@ export declare class OwnerChannel implements OwnerChannelHandle {
|
|
|
250
257
|
private warnOwnerOfUnauthorizedSender;
|
|
251
258
|
private effectiveOwners;
|
|
252
259
|
private authorizationIntegrity;
|
|
260
|
+
/**
|
|
261
|
+
* Report what the session actually did with the prompt, not what the config
|
|
262
|
+
* asked for. `interrupt: true` used to be reported as "your request
|
|
263
|
+
* interrupted the previous task" unconditionally; the session now answers
|
|
264
|
+
* whether anything was cancelled, whether the request is queued behind
|
|
265
|
+
* earlier prompts, or whether it is held until the current task reaches a
|
|
266
|
+
* safe stopping point. Backends that report no delivery state keep the old
|
|
267
|
+
* queuedBehind-based wording.
|
|
268
|
+
*/
|
|
269
|
+
private acceptanceNotice;
|
|
253
270
|
private complete;
|
|
254
271
|
private commentsState;
|
|
255
272
|
/** Model-authored commentary only; raw protocol/tool data never reaches here. */
|
|
@@ -6,6 +6,7 @@ import { DEFAULT_OWNER_ATTACHMENT_MIME, canonicalCid, } from '../config.js';
|
|
|
6
6
|
import { replaceFileAtomically } from '../atomic-file.js';
|
|
7
7
|
import { ACP_CANCEL_DEADLINE_EXCEEDED, SessionControlError, interruptOutcome, } from '../session/types.js';
|
|
8
8
|
import { VERSION } from '../version.js';
|
|
9
|
+
import { renderMarkdownFailure, renderMarkdownResult, taskStatus, } from '../rooms-tasks/markdown.js';
|
|
9
10
|
import { dispatchOwnerCommand, fleetCliOps, isOwnerCommandText, } from './commands.js';
|
|
10
11
|
import { OURS_BOUND_ELSEWHERE, OursSdkClient, oursErrorCode, } from './ours-client.js';
|
|
11
12
|
import { ownerNotices, } from './notices.js';
|
|
@@ -195,10 +196,13 @@ export class OwnerChannel {
|
|
|
195
196
|
? ' with interruption'
|
|
196
197
|
: event.monitor.interrupt === 'after_tool' ? ' with after-tool steering' : '';
|
|
197
198
|
const monitor = `${event.monitor.mode} monitor${monitorPolicy}`;
|
|
199
|
+
const permission = event.permissionMode
|
|
200
|
+
? `; permission ${event.permissionMode.fleetMode}, native ${event.permissionMode.nativeMode}`
|
|
201
|
+
: '';
|
|
198
202
|
const inherited = event.inherited.length
|
|
199
203
|
? ` Supervisor inherited omitted defaults: ${event.inherited.join(', ')}.` : '';
|
|
200
204
|
await this.sendProactiveMessage(`🧑💻 ${event.caller} spawned ${event.lifetime} agent ${event.role} `
|
|
201
|
-
+ `(${event.harness}/${event.session}${model}; ${monitor}).${inherited}`, `fleet-spawn\0${event.creationActionId}`, 0);
|
|
205
|
+
+ `(${event.harness}/${event.session}${model}; ${monitor}${permission}).${inherited}`, `fleet-spawn\0${event.creationActionId}`, 0);
|
|
202
206
|
});
|
|
203
207
|
this.managementTail = run.then(() => undefined, () => undefined);
|
|
204
208
|
return run;
|
|
@@ -789,17 +793,13 @@ export class OwnerChannel {
|
|
|
789
793
|
outbox = this.outboxDir(originWireId);
|
|
790
794
|
await mkdir(outbox, { recursive: true, mode: 0o700 });
|
|
791
795
|
const activityCursor = this.latestEventSeq(this.options.session.eventsSince(0));
|
|
792
|
-
const queued = await this.options.session.queuePrompt(this.ownerAttachmentPrompt(sender, originWireId, requestId,
|
|
796
|
+
const queued = await this.options.session.queuePrompt(this.ownerAttachmentPrompt(sender, originWireId, requestId, admitted, group.caption), {
|
|
793
797
|
interrupt: this.options.config.interrupt,
|
|
794
798
|
...(this.options.config.interrupt ? { interruptSource: 'owner' } : {}),
|
|
795
799
|
origin: { kind: 'owner', requestId,
|
|
796
800
|
...(group.caption ? { displayText: String(group.caption.text ?? '') } : {}) },
|
|
797
801
|
});
|
|
798
|
-
const accepted = this.
|
|
799
|
-
? ownerNotices.receivedInterrupting()
|
|
800
|
-
: queued.queuedBehind > 0
|
|
801
|
-
? ownerNotices.receivedQueued(queued.queuedBehind)
|
|
802
|
-
: ownerNotices.receivedStarted();
|
|
802
|
+
const accepted = this.acceptanceNotice(queued);
|
|
803
803
|
handledWireIds.forEach(wire => this.inFlight.add(wire));
|
|
804
804
|
const receipt = this.send(sender.id, accepted, originWireId).then(() => undefined).catch(error => {
|
|
805
805
|
this.logError(`attachment request ${requestId.slice(0, 12)} acceptance notice failed`, error);
|
|
@@ -918,7 +918,7 @@ export class OwnerChannel {
|
|
|
918
918
|
let queued;
|
|
919
919
|
const activityCursor = this.latestEventSeq(this.options.session.eventsSince(0));
|
|
920
920
|
try {
|
|
921
|
-
queued = await this.options.session.queuePrompt(this.ownerPrompt(sender, text, wireId
|
|
921
|
+
queued = await this.options.session.queuePrompt(this.ownerPrompt(sender, text, wireId), {
|
|
922
922
|
interrupt: this.options.config.interrupt,
|
|
923
923
|
...(this.options.config.interrupt ? { interruptSource: 'owner' } : {}),
|
|
924
924
|
origin: { kind: 'owner', requestId, displayText: text },
|
|
@@ -940,14 +940,7 @@ export class OwnerChannel {
|
|
|
940
940
|
this.state.remember(wireId);
|
|
941
941
|
return true;
|
|
942
942
|
}
|
|
943
|
-
|
|
944
|
-
// accepted into the ACP queue. Never claim this request is running while
|
|
945
|
-
// the session itself says earlier work remains ahead of it.
|
|
946
|
-
const accepted = queued.queuedBehind > 0
|
|
947
|
-
? ownerNotices.receivedQueued(queued.queuedBehind)
|
|
948
|
-
: this.options.config.interrupt
|
|
949
|
-
? ownerNotices.receivedInterrupting()
|
|
950
|
-
: ownerNotices.receivedStarted();
|
|
943
|
+
const accepted = this.acceptanceNotice(queued);
|
|
951
944
|
this.inFlight.add(wireId);
|
|
952
945
|
const receipt = this.send(sender.id, accepted, wireId).then(() => undefined).catch(error => {
|
|
953
946
|
this.logError(`request ${requestId.slice(0, 12)} acceptance notice failed`, error);
|
|
@@ -996,6 +989,8 @@ export class OwnerChannel {
|
|
|
996
989
|
return this.commentsState();
|
|
997
990
|
},
|
|
998
991
|
fleetList: () => this.fleetOps.list(),
|
|
992
|
+
closeRoom: roomId => this.closeRoomFromOwner(sender, roomId, wireId),
|
|
993
|
+
terminalTask: (taskId, kind, outcome) => this.terminalTaskFromOwner(sender, taskId, kind, outcome, wireId),
|
|
999
994
|
recentEvents: limit => this.options.session.eventsSince(0).slice(-limit),
|
|
1000
995
|
readWorklogTail: maxChars => this.readWorklogTail(maxChars),
|
|
1001
996
|
reply: async (replyText) => { await this.send(sender.id, replyText, wireId); },
|
|
@@ -1046,6 +1041,60 @@ export class OwnerChannel {
|
|
|
1046
1041
|
this.options.log(`[${this.options.role}] owner requested ${command}`);
|
|
1047
1042
|
await this.fleetOps.restart(mode);
|
|
1048
1043
|
}
|
|
1044
|
+
/**
|
|
1045
|
+
* The caller may itself be a room member. Persist and acknowledge acceptance
|
|
1046
|
+
* before an external worker starts a saga that can retire this process.
|
|
1047
|
+
*/
|
|
1048
|
+
async closeRoomFromOwner(sender, roomId, wireId) {
|
|
1049
|
+
const { acceptManagedRoomClose, recordManagedRoomCloseError } = await import('../rooms-tasks/close.js');
|
|
1050
|
+
await acceptManagedRoomClose(roomId);
|
|
1051
|
+
await this.send(sender.id, renderMarkdownFailure({
|
|
1052
|
+
kind: 'pending', subject: `/room delete ${roomId} ${roomId}`,
|
|
1053
|
+
detail: 'The deletion request was accepted and is still being settled.',
|
|
1054
|
+
action: `Run /room recover ${roomId} if deletion remains pending.`,
|
|
1055
|
+
}), wireId);
|
|
1056
|
+
this.state.remember(wireId);
|
|
1057
|
+
try {
|
|
1058
|
+
await this.fleetOps.closeRoom(roomId);
|
|
1059
|
+
}
|
|
1060
|
+
catch (error) {
|
|
1061
|
+
await recordManagedRoomCloseError(roomId, error instanceof Error ? error.message : String(error), `External delete worker failed to start. Retry /room delete ${roomId} ${roomId}.`);
|
|
1062
|
+
throw error;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
/** Carry a task terminal request through a worker that survives this role. */
|
|
1066
|
+
async terminalTaskFromOwner(sender, taskId, kind, outcome, wireId) {
|
|
1067
|
+
const { getTask } = await import('../rooms-tasks/task-state.js');
|
|
1068
|
+
const { acceptTaskTerminalIntent, recordTaskTerminalIntentError, } = await import('../rooms-tasks/terminal.js');
|
|
1069
|
+
const task = getTask(taskId);
|
|
1070
|
+
const accepted = await acceptTaskTerminalIntent({
|
|
1071
|
+
taskId, kind, roomId: task.room_id, outcome,
|
|
1072
|
+
});
|
|
1073
|
+
if (accepted.terminal_intent?.status === 'settled') {
|
|
1074
|
+
await this.send(sender.id, renderMarkdownResult({
|
|
1075
|
+
icon: '📋', title: 'Task terminal action complete',
|
|
1076
|
+
fields: [
|
|
1077
|
+
{ label: 'ID', value: taskId, kind: 'code' },
|
|
1078
|
+
{ label: 'Status', value: taskStatus(accepted.state), kind: 'markdown' },
|
|
1079
|
+
],
|
|
1080
|
+
}), wireId);
|
|
1081
|
+
this.state.remember(wireId);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
await this.send(sender.id, renderMarkdownFailure({
|
|
1085
|
+
kind: 'pending', subject: `/task ${kind === 'done' ? 'done' : 'cancel'} ${taskId}`,
|
|
1086
|
+
detail: 'The terminal request was accepted and is still being settled.',
|
|
1087
|
+
action: `Run /task recover ${taskId} if it remains pending.`,
|
|
1088
|
+
}), wireId);
|
|
1089
|
+
this.state.remember(wireId);
|
|
1090
|
+
try {
|
|
1091
|
+
await this.fleetOps.settleTask(taskId);
|
|
1092
|
+
}
|
|
1093
|
+
catch (error) {
|
|
1094
|
+
await recordTaskTerminalIntentError(taskId, error instanceof Error ? error.message : String(error), `External settle worker failed to start. Retry the identical task command or run task recover ${taskId}.`);
|
|
1095
|
+
throw error;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1049
1098
|
/** Code-point-safe tail of the worklog, or undefined when there is none. */
|
|
1050
1099
|
async readWorklogTail(maxChars) {
|
|
1051
1100
|
try {
|
|
@@ -1332,6 +1381,32 @@ export class OwnerChannel {
|
|
|
1332
1381
|
authorizationIntegrity() {
|
|
1333
1382
|
return this.options.config.agent ? { ok: true } : this.authorizations.integrity();
|
|
1334
1383
|
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Report what the session actually did with the prompt, not what the config
|
|
1386
|
+
* asked for. `interrupt: true` used to be reported as "your request
|
|
1387
|
+
* interrupted the previous task" unconditionally; the session now answers
|
|
1388
|
+
* whether anything was cancelled, whether the request is queued behind
|
|
1389
|
+
* earlier prompts, or whether it is held until the current task reaches a
|
|
1390
|
+
* safe stopping point. Backends that report no delivery state keep the old
|
|
1391
|
+
* queuedBehind-based wording.
|
|
1392
|
+
*/
|
|
1393
|
+
acceptanceNotice(queued) {
|
|
1394
|
+
switch (queued.delivery) {
|
|
1395
|
+
case 'interrupted': return ownerNotices.receivedInterrupting();
|
|
1396
|
+
case 'deferred': return ownerNotices.receivedDeferred();
|
|
1397
|
+
case 'queued': return ownerNotices.receivedQueued(Math.max(1, queued.queuedBehind));
|
|
1398
|
+
case 'started': return ownerNotices.receivedStarted();
|
|
1399
|
+
default:
|
|
1400
|
+
// Interrupting the live turn does not remove prompts which were already
|
|
1401
|
+
// accepted into the ACP queue. Never claim this request is running while
|
|
1402
|
+
// the session itself says earlier work remains ahead of it.
|
|
1403
|
+
return queued.queuedBehind > 0
|
|
1404
|
+
? ownerNotices.receivedQueued(queued.queuedBehind)
|
|
1405
|
+
: this.options.config.interrupt
|
|
1406
|
+
? ownerNotices.receivedInterrupting()
|
|
1407
|
+
: ownerNotices.receivedStarted();
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1335
1410
|
async complete(active, outbox, queued, activityCursor) {
|
|
1336
1411
|
const progressMs = this.options.config.progress_interval_ms;
|
|
1337
1412
|
let lastSeq = activityCursor;
|
|
@@ -1532,7 +1607,7 @@ export class OwnerChannel {
|
|
|
1532
1607
|
throw new Error('commentary appears to contain secret, reasoning, or raw tool content');
|
|
1533
1608
|
return message;
|
|
1534
1609
|
}
|
|
1535
|
-
ownerAttachmentPrompt(sender, wireId, requestId,
|
|
1610
|
+
ownerAttachmentPrompt(sender, wireId, requestId, files, caption) {
|
|
1536
1611
|
const lines = [
|
|
1537
1612
|
'[fleet-owner]',
|
|
1538
1613
|
`Authenticated owner ${safeField(sender.name, 160)} (${sender.id}) sent owner-channel attachment request ${wireId}.`,
|
|
@@ -1556,10 +1631,13 @@ export class OwnerChannel {
|
|
|
1556
1631
|
lines.push(`- voice transcript status: ${transcription?.status ?? 'unavailable'}`, `- voice transcript fallback: audio path above; category ${transcription?.errorCategory ?? 'not_provided'}`);
|
|
1557
1632
|
}
|
|
1558
1633
|
}
|
|
1559
|
-
lines.push('Answer in your final assistant response; fleet routes it only to the authenticated sender and correlates it to the originating file wire.',
|
|
1634
|
+
lines.push('Answer in your final assistant response; fleet routes it only to the authenticated sender and correlates it to the originating file wire.',
|
|
1635
|
+
// send_file is the only delivery route an agent is given: a tool call either
|
|
1636
|
+
// delivers or reports an error, where a file written to disk does neither.
|
|
1637
|
+
'To send the owner a file — now or later in this turn — call ours `send_file`:', `contact: ${this.options.config.identity}`, 'and the path of the finished file. Fleet routes it to the authenticated owner.', 'A file written anywhere else is not delivered and nothing will report that it was not.', 'Use descriptive unique filenames. Send nothing the owner did not request or should not receive.');
|
|
1560
1638
|
return lines.join('\n');
|
|
1561
1639
|
}
|
|
1562
|
-
ownerPrompt(sender, text, wireId
|
|
1640
|
+
ownerPrompt(sender, text, wireId) {
|
|
1563
1641
|
return [
|
|
1564
1642
|
'[fleet-owner]',
|
|
1565
1643
|
`Authenticated owner ${sender.name} (${sender.id}) sent owner-channel message ${wireId}.`,
|
|
@@ -1573,10 +1651,13 @@ export class OwnerChannel {
|
|
|
1573
1651
|
] : [
|
|
1574
1652
|
'Managed-agent outbound relay is not configured; do not send intermediate or proactive owner-channel messages.',
|
|
1575
1653
|
]),
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
'
|
|
1579
|
-
|
|
1654
|
+
// send_file is the only delivery route an agent is given: a tool call either
|
|
1655
|
+
// delivers or reports an error, where a file written to disk does neither.
|
|
1656
|
+
'To send the owner a file — now or later in this turn — call ours `send_file`:',
|
|
1657
|
+
`contact: ${this.options.config.identity}`,
|
|
1658
|
+
'and the path of the finished file. Fleet routes it to the authenticated owner.',
|
|
1659
|
+
'A file written anywhere else is not delivered and nothing will report that it was not.',
|
|
1660
|
+
'Use descriptive unique filenames. Send nothing the owner did not request or should not receive.',
|
|
1580
1661
|
'',
|
|
1581
1662
|
text || '(empty message)',
|
|
1582
1663
|
].join('\n');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { InterruptOutcome, SessionEvent, SessionSnapshot } from '../session/types.js';
|
|
2
|
+
import type { TaskOutcome, TaskTerminalIntent } from '../rooms-tasks/types.js';
|
|
2
3
|
import { type OwnerCommentsState } from './notices.js';
|
|
3
4
|
/**
|
|
4
5
|
* Fleet-level effects a deterministic owner command may trigger. Production
|
|
@@ -10,6 +11,10 @@ export interface OwnerFleetOps {
|
|
|
10
11
|
restart(mode: 'keep' | 'fresh'): Promise<void>;
|
|
11
12
|
/** `ours-fleet ls` output. */
|
|
12
13
|
list(): Promise<string>;
|
|
14
|
+
/** Start a room-close worker outside the caller role's supervisor lifecycle. */
|
|
15
|
+
closeRoom(roomId: string): Promise<void>;
|
|
16
|
+
/** Resume a task terminal intent outside the caller role's supervisor lifecycle. */
|
|
17
|
+
settleTask(taskId: string): Promise<void>;
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* The narrow capability surface a command executor sees. Everything here is
|
|
@@ -40,6 +45,10 @@ export interface OwnerCommandContext {
|
|
|
40
45
|
*/
|
|
41
46
|
setComments(enabled: boolean): OwnerCommentsState;
|
|
42
47
|
fleetList(): Promise<string>;
|
|
48
|
+
/** Persist acceptance, acknowledge it, then launch the external close worker. */
|
|
49
|
+
closeRoom(roomId: string): Promise<void>;
|
|
50
|
+
/** Persist terminal intent, acknowledge it, then launch the external settle worker. */
|
|
51
|
+
terminalTask(taskId: string, kind: TaskTerminalIntent['kind'], outcome?: TaskOutcome): Promise<void>;
|
|
43
52
|
recentEvents(limit: number): SessionEvent[];
|
|
44
53
|
readWorklogTail(maxChars: number): Promise<string | undefined>;
|
|
45
54
|
reply(text: string): Promise<void>;
|