@ours.network/fleet 2.0.3 → 2.1.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 +10 -1
- package/dist/application/fleet-query-service.js +2 -0
- package/dist/application/room-layout-definitions.js +5 -0
- package/dist/application/task-layouts.d.ts +10 -1
- package/dist/application/task-layouts.js +53 -5
- package/dist/application/task-room-service.d.ts +5 -1
- package/dist/application/task-room-service.js +15 -6
- package/dist/application/types.d.ts +2 -0
- package/dist/build-info.json +4 -4
- package/dist/config.d.ts +0 -2
- package/dist/config.js +4 -6
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +5 -1
- package/dist/owner-channel/channel.d.ts +2 -3
- package/dist/owner-channel/channel.js +6 -78
- package/dist/owner-channel/notices.d.ts +0 -2
- package/dist/owner-channel/notices.js +0 -25
- package/dist/owner-channel/ours-client.d.ts +4 -0
- package/dist/owner-channel/ours-client.js +7 -1
- package/dist/rooms-tasks/add-member.d.ts +3 -1
- package/dist/rooms-tasks/add-member.js +40 -0
- package/dist/rooms-tasks/cli.js +2 -2
- package/dist/rooms-tasks/empty-layout.d.ts +5 -0
- package/dist/rooms-tasks/empty-layout.js +8 -0
- package/dist/rooms-tasks/layout-config.js +2 -2
- package/dist/rooms-tasks/layout-service.d.ts +1 -1
- package/dist/rooms-tasks/layout-service.js +5 -2
- package/dist/rooms-tasks/layout.d.ts +18 -1
- package/dist/rooms-tasks/layout.js +40 -6
- package/dist/session/control.js +1 -0
- package/dist/web/server.js +7 -5
- package/docs/room-layouts.md +34 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -485,7 +485,6 @@ Additional operational field inventory (schematic, not one YAML document):
|
|
|
485
485
|
owners: [owner-contact-cid] # authenticated ours contact IDs, never display names
|
|
486
486
|
agent: managed-agent-cid # exact role CID allowed to relay messages/files outward
|
|
487
487
|
interrupt: false # false queues; true cancels current work first
|
|
488
|
-
progress_interval_ms: 30000 # fleet-generated progress notices; 0 disables
|
|
489
488
|
comments: true # relay live "🟡 Live update:" structured commentary (default true);
|
|
490
489
|
# restart baseline for /comments on|off
|
|
491
490
|
attachments: # secure inbound documents, images, and voice
|
|
@@ -1962,3 +1961,13 @@ App released. The currently pinned Fleet nightly.41 does not support v2 grants.
|
|
|
1962
1961
|
These source changes do not publish, merge, or deploy any component.
|
|
1963
1962
|
|
|
1964
1963
|
See [ACP file sending](docs/acp-files.md) for the same-chat `send_file` flow, available to every Agent by default, and its supported sessions.
|
|
1964
|
+
|
|
1965
|
+
### Owner-channel activity display
|
|
1966
|
+
|
|
1967
|
+
Routine tool activity is shown as one updating status in the app, outside message
|
|
1968
|
+
history. Fleet no longer sends periodic tool-counter messages. The
|
|
1969
|
+
`owner_channel.progress_interval_ms` setting has been removed; remove it from
|
|
1970
|
+
existing role/default configuration when applying this change. Meaningful agent
|
|
1971
|
+
commentary, attention requests and final answers continue to be delivered.
|
|
1972
|
+
The app associates activity with the channel's bound CID, never a display name.
|
|
1973
|
+
It shows unavailable status when the directory observation disconnects.
|
|
@@ -165,6 +165,8 @@ export class FleetQueryService {
|
|
|
165
165
|
return {
|
|
166
166
|
backend: intended, reachability: snapshot.alive ? 'online' : 'offline',
|
|
167
167
|
readiness: snapshot.readiness, evidence: 'authoritative',
|
|
168
|
+
...(typeof snapshot.ownerChannelCid === 'string' && /^[a-f0-9]{64}$/i.test(snapshot.ownerChannelCid)
|
|
169
|
+
? { ownerChannelCid: snapshot.ownerChannelCid.toLowerCase() } : {}),
|
|
168
170
|
sessionId: snapshot.sessionId, lastError: snapshot.lastError,
|
|
169
171
|
pendingPermissionId: snapshot.pendingPermissionId,
|
|
170
172
|
protocolVersion: snapshot.protocolVersion, features: snapshot.features,
|
|
@@ -14,6 +14,7 @@ import { loadConfig, splitRootFor } from '../config.js';
|
|
|
14
14
|
import { assertLayoutFile, validLayoutKey, validateRoomLayout } from '../rooms-tasks/layout-config.js';
|
|
15
15
|
import { FleetError } from './errors.js';
|
|
16
16
|
import { layoutDefinitionHash } from './task-layouts.js';
|
|
17
|
+
import { EMPTY_TASK_LAYOUT } from '../rooms-tasks/empty-layout.js';
|
|
17
18
|
export const ABSENT_REVISION = 'absent';
|
|
18
19
|
const revisionOf = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
19
20
|
export class RoomLayoutDefinitions {
|
|
@@ -25,6 +26,8 @@ export class RoomLayoutDefinitions {
|
|
|
25
26
|
}
|
|
26
27
|
root() { return join(splitRootFor(this.configPath ?? defaultConfigPath()), 'room_layouts'); }
|
|
27
28
|
pathFor(name) {
|
|
29
|
+
if (name === EMPTY_TASK_LAYOUT)
|
|
30
|
+
throw new FleetError('invalid_request', 'the built-in empty layout cannot be edited or deleted');
|
|
28
31
|
if (!validLayoutKey(name))
|
|
29
32
|
throw new FleetError('invalid_request', 'layout name must start with a letter and use letters, digits, - or _');
|
|
30
33
|
return join(this.root(), `${name}.yaml`);
|
|
@@ -68,6 +71,8 @@ export class RoomLayoutDefinitions {
|
|
|
68
71
|
return readdirSync(root).filter(file => /\.ya?ml$/.test(file)).sort().map(file => {
|
|
69
72
|
const name = basename(file, extname(file)), path = join(root, file);
|
|
70
73
|
try {
|
|
74
|
+
if (name === EMPTY_TASK_LAYOUT)
|
|
75
|
+
throw Error('reserved built-in layout name');
|
|
71
76
|
if (!validLayoutKey(name))
|
|
72
77
|
throw Error(`invalid room layout id: ${name}`);
|
|
73
78
|
assertLayoutFile(path);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type RoomLayoutDefinition } from '../rooms-tasks/layout-config.js';
|
|
2
2
|
import { RoomLayoutService } from '../rooms-tasks/layout-service.js';
|
|
3
3
|
import type { TaskLayoutLink, TaskRecord } from '../rooms-tasks/types.js';
|
|
4
|
-
export type TaskLayoutOperation = 'open' | 'close-room' | 'close';
|
|
4
|
+
export type TaskLayoutOperation = 'start' | 'open' | 'close-room' | 'close';
|
|
5
5
|
export interface TaskLayoutOperationRecord {
|
|
6
6
|
/** Only the worker holding the current ID may update the record. */
|
|
7
7
|
id: string;
|
|
@@ -73,6 +73,15 @@ export declare class TaskLayouts {
|
|
|
73
73
|
runExists(link: TaskLayoutLink): boolean;
|
|
74
74
|
/** Idempotent: reuse a run whose provenance matches this task, or snapshot one from the pinned source. */
|
|
75
75
|
ensureRun(task: TaskRecord, precondition?: (current: TaskRecord) => void): Promise<void>;
|
|
76
|
+
/** Starting a mission initializes every room and its required agents before Active. */
|
|
77
|
+
initializeRooms(task: TaskRecord): Promise<void>;
|
|
78
|
+
addMember(task: TaskRecord, input: {
|
|
79
|
+
key: string;
|
|
80
|
+
role: string;
|
|
81
|
+
requestId: string;
|
|
82
|
+
hash: string;
|
|
83
|
+
definition: import('../config.js').AgentTemplateDefinition;
|
|
84
|
+
}): Promise<void>;
|
|
76
85
|
private verifyProvenance;
|
|
77
86
|
readOperation(runId: string): TaskLayoutOperationRecord | undefined;
|
|
78
87
|
/** Update only the still-current operation; a superseded or forgotten one is never resurrected. */
|
|
@@ -11,7 +11,7 @@ import { join } from 'node:path';
|
|
|
11
11
|
import { canonicalJson } from '../canonical-json.js';
|
|
12
12
|
import { replaceFileAtomically, withFileLock } from '../atomic-file.js';
|
|
13
13
|
import { assertLayoutFile } from '../rooms-tasks/layout-config.js';
|
|
14
|
-
import { getTask as readTask } from '../rooms-tasks/task-state.js';
|
|
14
|
+
import { getTask as readTask, activateTask, blockTask } from '../rooms-tasks/task-state.js';
|
|
15
15
|
import { stateRoot } from '../paths.js';
|
|
16
16
|
import { launchFleetWorker } from '../rooms-tasks/external-worker.js';
|
|
17
17
|
import { layoutSupervisorId } from '../rooms-tasks/layout-control.js';
|
|
@@ -20,6 +20,7 @@ import { FleetError } from './errors.js';
|
|
|
20
20
|
import { ownedLayoutRetirementSeats } from '../rooms-tasks/layout-member-retirement.js';
|
|
21
21
|
import { eraseMemberArtifacts } from '../rooms-tasks/erasure.js';
|
|
22
22
|
import { assertTaskRegistrationsAbsent } from '../supervisor/catalog.js';
|
|
23
|
+
import { EMPTY_TASK_LAYOUT, EMPTY_TASK_ROOM } from '../rooms-tasks/empty-layout.js';
|
|
23
24
|
/** Hash of the authorable definition only (never the source file path). */
|
|
24
25
|
export function layoutDefinitionHash(definition) {
|
|
25
26
|
const { version, description, participants, rooms } = definition;
|
|
@@ -95,12 +96,35 @@ export class TaskLayouts {
|
|
|
95
96
|
if (layoutDefinitionHash(source) !== link.definition_hash)
|
|
96
97
|
throw Error(`room layout ${link.name} changed before this task's layout run was created; create a new task to use the edited layout`);
|
|
97
98
|
const scoped = taskScopedDefinition(structuredClone(source), current);
|
|
99
|
+
if (link.name === EMPTY_TASK_LAYOUT)
|
|
100
|
+
scoped.rooms[EMPTY_TASK_ROOM].goal = current.brief?.trim() || current.title;
|
|
98
101
|
const provenance = { task_id: current.task_id, source_hash: link.definition_hash, scoped_hash: layoutDefinitionHash(scoped) };
|
|
99
102
|
// Provenance precedes the run: a crash in between is re-derived from the same pinned source.
|
|
100
103
|
replaceFileAtomically(this.provenancePath(link.run_id), JSON.stringify(provenance), 0o600);
|
|
101
104
|
await service.create(link.name, {}, link.run_id, { source: scoped, cwd: current.workspace?.path });
|
|
102
105
|
});
|
|
103
106
|
}
|
|
107
|
+
/** Starting a mission initializes every room and its required agents before Active. */
|
|
108
|
+
async initializeRooms(task) {
|
|
109
|
+
await this.withTaskLock(task.layout.run_id, async () => {
|
|
110
|
+
const current = readTask(task.task_id);
|
|
111
|
+
if (current.deletion?.status === 'pending' || current.terminal_intent || !['provisioning', 'active'].includes(current.state))
|
|
112
|
+
throw new FleetError('conflict', 'task is no longer starting; its room cannot be opened');
|
|
113
|
+
this.verifyProvenance(current);
|
|
114
|
+
const engine = this.service().open(task.layout.run_id);
|
|
115
|
+
for (const key of Object.keys(engine.snapshot().definition.rooms))
|
|
116
|
+
await engine.activate(layoutSupervisorId(), key);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async addMember(task, input) {
|
|
120
|
+
const link = task.layout;
|
|
121
|
+
await this.withTaskLock(link.run_id, async () => {
|
|
122
|
+
const current = readTask(task.task_id);
|
|
123
|
+
assertLayoutOpenable(current);
|
|
124
|
+
this.verifyProvenance(current);
|
|
125
|
+
await this.service().open(link.run_id).addMember(layoutSupervisorId(), { ...input, room: EMPTY_TASK_ROOM });
|
|
126
|
+
});
|
|
127
|
+
}
|
|
104
128
|
verifyProvenance(task) {
|
|
105
129
|
const link = task.layout, path = this.provenancePath(link.run_id);
|
|
106
130
|
let provenance;
|
|
@@ -151,15 +175,18 @@ export class TaskLayouts {
|
|
|
151
175
|
...(state.uncertain ? { uncertain: state.uncertain } : {}),
|
|
152
176
|
rooms: Object.entries(state.definition.rooms).map(([key, spec]) => {
|
|
153
177
|
const room = state.rooms[key];
|
|
178
|
+
const additions = Object.entries(state.additional_members ?? {}).filter(([, member]) => member.room === key);
|
|
179
|
+
const roles = { ...spec.roles, ...Object.fromEntries(additions.map(([name, member]) => [name, member.role])) };
|
|
154
180
|
return {
|
|
155
|
-
key, goal: spec.goal, members: [...spec.members
|
|
181
|
+
key, goal: spec.goal, members: [...spec.members, ...additions.map(([name]) => name)],
|
|
182
|
+
...(Object.keys(roles).length ? { roles } : {}),
|
|
156
183
|
state: room?.state ?? 'declared', ready: [...(room?.ready ?? [])],
|
|
157
184
|
...(room?.native ? { room_id: room.native.room_id, identity_cid: room.native.identity_cid } : {}),
|
|
158
185
|
};
|
|
159
186
|
}),
|
|
160
187
|
participants: Object.entries(state.participants).map(([key, p]) => ({
|
|
161
188
|
key, ...(p.participant ? { participant: p.participant, room: p.room } : {}),
|
|
162
|
-
agent_template: state.definition.participants[p.participant ?? key]?.agent_template, owned: p.owned, retired: Boolean(p.retired),
|
|
189
|
+
agent_template: state.definition.participants[p.participant ?? key]?.agent_template ?? state.additional_members?.[key]?.template, owned: p.owned, retired: Boolean(p.retired),
|
|
163
190
|
...(p.instance?.agent ? { agent: p.instance.agent } : {}), ...(p.instance?.cid ? { cid: p.instance.cid } : {}),
|
|
164
191
|
})),
|
|
165
192
|
};
|
|
@@ -169,7 +196,7 @@ export class TaskLayouts {
|
|
|
169
196
|
const link = task.layout;
|
|
170
197
|
if (!link)
|
|
171
198
|
throw Error(`task ${task.task_id} has no layout`);
|
|
172
|
-
if (
|
|
199
|
+
if (!['start', 'close'].includes(operation) && (!room || !ROOM_KEY.test(room)))
|
|
173
200
|
throw new FleetError('invalid_request', 'a valid room key is required');
|
|
174
201
|
if (room) {
|
|
175
202
|
const definition = this.runExists(link) ? this.service().open(link.run_id, true).snapshot().definition : undefined;
|
|
@@ -177,13 +204,20 @@ export class TaskLayouts {
|
|
|
177
204
|
throw new FleetError('resource_not_found', `unknown layout room: ${room}`);
|
|
178
205
|
}
|
|
179
206
|
const record = { id: randomUUID(), operation, ...(room ? { room } : {}), status: 'launching' };
|
|
180
|
-
|
|
207
|
+
let accepted = false;
|
|
208
|
+
const selected = await withFileLock(this.operationPath(link.run_id) + '.lock', () => {
|
|
181
209
|
const current = this.readOperation(link.run_id);
|
|
182
210
|
// Full close always proceeds: terminal cleanup must not wait behind an open; the engine lock serializes them.
|
|
211
|
+
if (operation === 'start' && current?.operation === 'start' && ['launching', 'running'].includes(current.status) && Date.now() - Date.parse(current.updated_at) < 10 * 60_000)
|
|
212
|
+
return current;
|
|
183
213
|
if (operation !== 'close' && current && ['launching', 'running'].includes(current.status) && Date.now() - Date.parse(current.updated_at) < 10 * 60_000)
|
|
184
214
|
throw new FleetError('conflict', `layout operation already in progress: ${current.operation}${current.room ? ' ' + current.room : ''}`);
|
|
185
215
|
this.writeOperation(link.run_id, record);
|
|
216
|
+
accepted = true;
|
|
217
|
+
return record;
|
|
186
218
|
});
|
|
219
|
+
if (!accepted)
|
|
220
|
+
return selected;
|
|
187
221
|
const args = ['task', '_layout', task.task_id, operation, ...(room ? [room] : []), '--operation-id', record.id];
|
|
188
222
|
const launch = this.deps.launch
|
|
189
223
|
?? ((cli, name) => launchFleetWorker(cli, name, this.configPath, this.deps.binPath?.()));
|
|
@@ -218,6 +252,20 @@ export class TaskLayouts {
|
|
|
218
252
|
}
|
|
219
253
|
async perform(task, operation, room) {
|
|
220
254
|
const link = task.layout;
|
|
255
|
+
if (operation === 'start') {
|
|
256
|
+
try {
|
|
257
|
+
await this.initializeRooms(task);
|
|
258
|
+
if (readTask(task.task_id).state !== 'active')
|
|
259
|
+
activateTask(task.task_id);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
const current = readTask(task.task_id);
|
|
263
|
+
if (current.state === 'provisioning' && !current.terminal_intent && current.deletion?.status !== 'pending')
|
|
264
|
+
blockTask(task.task_id, `layout startup failed: ${error.message}`);
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
221
269
|
if (operation === 'open') {
|
|
222
270
|
// Re-read under the task-layout lock: a deletion or terminal action accepted after HTTP admission wins.
|
|
223
271
|
await this.ensureRun(task, assertLayoutOpenable);
|
|
@@ -79,6 +79,8 @@ export interface CreateTaskRequest {
|
|
|
79
79
|
anonymous?: boolean;
|
|
80
80
|
/** Room Layout name: the task's rooms and participants come from a layout run. */
|
|
81
81
|
layout?: string;
|
|
82
|
+
/** HTTP acceptance returns while a detached worker initializes the layout. */
|
|
83
|
+
deferLayoutStart?: boolean;
|
|
82
84
|
}
|
|
83
85
|
export interface CreateRoomRequest {
|
|
84
86
|
actor: TaskRoomActor;
|
|
@@ -167,7 +169,7 @@ export declare class TaskRoomApplicationService {
|
|
|
167
169
|
runTaskLayoutOperation(input: {
|
|
168
170
|
actor: TaskRoomActor;
|
|
169
171
|
taskId: string;
|
|
170
|
-
operation: 'open' | 'close-room' | 'close';
|
|
172
|
+
operation: 'start' | 'open' | 'close-room' | 'close';
|
|
171
173
|
room?: string;
|
|
172
174
|
operationId?: string;
|
|
173
175
|
}): Promise<import("./task-layouts.js").TaskLayoutView>;
|
|
@@ -179,6 +181,7 @@ export declare class TaskRoomApplicationService {
|
|
|
179
181
|
template?: string;
|
|
180
182
|
members?: MemberOverrides;
|
|
181
183
|
anonymous?: boolean;
|
|
184
|
+
deferLayoutStart?: boolean;
|
|
182
185
|
}): Promise<TaskRecord>;
|
|
183
186
|
listTasks(filter?: {
|
|
184
187
|
state?: TaskState | TaskState[];
|
|
@@ -404,6 +407,7 @@ export declare class TaskRoomApplicationService {
|
|
|
404
407
|
template?: string;
|
|
405
408
|
members?: MemberOverrides;
|
|
406
409
|
anonymous?: boolean;
|
|
410
|
+
deferLayoutStart?: boolean;
|
|
407
411
|
}): Promise<{
|
|
408
412
|
task: TaskRecord;
|
|
409
413
|
status: 'ready' | 'already_active' | 'in_progress';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TaskLayouts, assertLayoutOpenable, writeTaskBrief } from './task-layouts.js';
|
|
2
|
+
import { EMPTY_TASK_LAYOUT } from '../rooms-tasks/empty-layout.js';
|
|
2
3
|
import { FleetError } from './errors.js';
|
|
3
4
|
import { readFileSync } from 'node:fs';
|
|
4
5
|
import { taskLiveReadiness } from '../rooms-tasks/live-readiness.js';
|
|
@@ -78,7 +79,7 @@ export class TaskRoomApplicationService {
|
|
|
78
79
|
const { requestMemberAddition } = await import('../rooms-tasks/add-member.js');
|
|
79
80
|
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
80
81
|
return requestMemberAddition({ taskId, request, cfg, configPath: this.configurationPath,
|
|
81
|
-
cowork: (this.deps.cowork ?? createCoworkAdapter)(cfg), binPath: (this.deps.binPath ?? getBinPath)(), provision: this.deps.provisionMembers });
|
|
82
|
+
cowork: readTask(taskId).layout ? undefined : (this.deps.cowork ?? createCoworkAdapter)(cfg), binPath: (this.deps.binPath ?? getBinPath)(), provision: this.deps.provisionMembers, layouts: this.layouts() });
|
|
82
83
|
}
|
|
83
84
|
async memberAddition(taskId, requestId) { const { memberAddition } = await import('../rooms-tasks/add-member.js'); return memberAddition(taskId, requestId); }
|
|
84
85
|
layouts() {
|
|
@@ -103,10 +104,10 @@ export class TaskRoomApplicationService {
|
|
|
103
104
|
recordFleetAuditPresentation({ kind: 'task', operation: 'create', id: task.task_id,
|
|
104
105
|
title: task.title, previousState: 'none', newState: task.state, revision: task.created_at,
|
|
105
106
|
list: task.list_name ?? 'default', roomId: undefined, template: `layout:${layout.name}`, agents: [] });
|
|
106
|
-
return task.state === 'provisioning' ? (await this.ensureLayoutWork(task)).task : task;
|
|
107
|
+
return task.state === 'provisioning' ? (await this.ensureLayoutWork(task, request.deferLayoutStart)).task : task;
|
|
107
108
|
}
|
|
108
109
|
/** Layout tasks never provision the legacy single room; their run is a local snapshot. */
|
|
109
|
-
async ensureLayoutWork(task) {
|
|
110
|
+
async ensureLayoutWork(task, defer = false) {
|
|
110
111
|
if (TASK_TERMINAL_STATES.includes(task.state))
|
|
111
112
|
throw new TaskRoomApplicationError('task_terminal', 'task terminal', { task: task.task_id, state: task.state });
|
|
112
113
|
if (task.state === 'active' || task.state === 'review')
|
|
@@ -119,6 +120,11 @@ export class TaskRoomApplicationService {
|
|
|
119
120
|
}
|
|
120
121
|
try {
|
|
121
122
|
await this.layouts().ensureRun(task);
|
|
123
|
+
if (defer) {
|
|
124
|
+
await this.layouts().launch(task, 'start');
|
|
125
|
+
return { task: readTask(task.task_id), status: 'in_progress' };
|
|
126
|
+
}
|
|
127
|
+
await this.layouts().initializeRooms(task);
|
|
122
128
|
}
|
|
123
129
|
catch (error) {
|
|
124
130
|
const reason = `layout run could not be created: ${error.message}`;
|
|
@@ -362,10 +368,11 @@ export class TaskRoomApplicationService {
|
|
|
362
368
|
const blocker = task.outcome?.summary ?? task.blocked?.reason;
|
|
363
369
|
return {
|
|
364
370
|
kind: failed ? 'failed' : ready ? 'ready' : 'in_progress', task, room: undefined,
|
|
365
|
-
launch: { template: `layout:${task.layout.name}`, anonymous: false,
|
|
371
|
+
launch: { template: `layout:${task.layout.name}`, anonymous: false,
|
|
372
|
+
owner_attached: task.layout.name === EMPTY_TASK_LAYOUT && this.layouts().view(task)?.rooms.some(room => room.state === 'active') === true },
|
|
366
373
|
members: { expected: 0, active: 0, launched: 0 },
|
|
367
374
|
...(blocker ? { blocker } : {}),
|
|
368
|
-
...(!ready && !failed ? { next_action: `Correct the blocker, then run ours-fleet task start ${task.task_id}.` } : {}),
|
|
375
|
+
...(!ready && !failed && blocker ? { next_action: `Correct the blocker, then run ours-fleet task start ${task.task_id}.` } : {}),
|
|
369
376
|
};
|
|
370
377
|
}
|
|
371
378
|
const room = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
@@ -584,6 +591,8 @@ export class TaskRoomApplicationService {
|
|
|
584
591
|
}
|
|
585
592
|
async launchTaskProvisioning(taskId) {
|
|
586
593
|
const outcome = this.taskProvisioningOutcome(taskId);
|
|
594
|
+
if (outcome.task.layout)
|
|
595
|
+
return outcome; // layout startup already has its own durable operation/worker
|
|
587
596
|
if (outcome.kind === 'in_progress' && !outcome.next_action) {
|
|
588
597
|
await launchFleetWorker(['task', '_provision', taskId], `task-provision-${taskId}`, this.configurationPath, this.deps.binPath?.());
|
|
589
598
|
}
|
|
@@ -882,7 +891,7 @@ export class TaskRoomApplicationService {
|
|
|
882
891
|
if (task.layout) {
|
|
883
892
|
if (input.template || input.members || input.anonymous !== undefined)
|
|
884
893
|
throw new FleetError('invalid_request', 'a layout task cannot take a room template or member overrides');
|
|
885
|
-
return this.ensureLayoutWork(task);
|
|
894
|
+
return this.ensureLayoutWork(task, input.deferLayoutStart);
|
|
886
895
|
}
|
|
887
896
|
const recordedRoom = task.room_id ? getRoomRecord(task.room_id) : undefined;
|
|
888
897
|
const recordedPolicy = storedRoomLaunchPolicy(recordedRoom?.room_policy ?? task.execution_plan?.room_policy);
|
|
@@ -62,6 +62,8 @@ export interface RoleStatus {
|
|
|
62
62
|
detail: string;
|
|
63
63
|
};
|
|
64
64
|
session: {
|
|
65
|
+
/** Supervisor-bound owner mailbox, distinct from the managed agent identity. */
|
|
66
|
+
ownerChannelCid?: string;
|
|
65
67
|
backend: SessionBackendId | 'unknown';
|
|
66
68
|
reachability: 'online' | 'offline' | 'unavailable' | 'unknown';
|
|
67
69
|
readiness: 'starting' | 'idle' | 'running' | 'awaiting_permission' | 'failed' | 'unknown';
|
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.0
|
|
3
|
-
"buildId": "
|
|
4
|
-
"commit": "
|
|
2
|
+
"version": "2.1.0",
|
|
3
|
+
"buildId": "697e8bf9e53c",
|
|
4
|
+
"commit": "2805e0e84c2a3bad05efdbe895fd8fb6e9d2aff4",
|
|
5
5
|
"dirty": false,
|
|
6
|
-
"builtAt": "2026-10-
|
|
6
|
+
"builtAt": "2026-10-27T12:22:22.859Z",
|
|
7
7
|
"capabilities": [
|
|
8
8
|
"service.refresh-v1",
|
|
9
9
|
"managed-cli.codex-reasoning-effort-v1",
|
package/dist/config.d.ts
CHANGED
|
@@ -101,8 +101,6 @@ export interface OwnerChannelConfig {
|
|
|
101
101
|
agent?: string;
|
|
102
102
|
/** Cancel active work before each owner request instead of queueing it. */
|
|
103
103
|
interrupt: boolean;
|
|
104
|
-
/** Deterministic in-progress notice interval; 0 disables progress notices. */
|
|
105
|
-
progress_interval_ms: number;
|
|
106
104
|
/**
|
|
107
105
|
* Relay the agent's live ACP commentary to the owner while a turn runs.
|
|
108
106
|
* This is the RESTART BASELINE only: `/comments on|off` changes the running
|
package/dist/config.js
CHANGED
|
@@ -882,8 +882,11 @@ export function resolveOwnerChannelConfig(defaults, role, session, file = 'confi
|
|
|
882
882
|
...(role ?? {}),
|
|
883
883
|
};
|
|
884
884
|
const allowed = [
|
|
885
|
-
'identity', 'owners', 'agent', 'interrupt', '
|
|
885
|
+
'identity', 'owners', 'agent', 'interrupt', 'comments', 'attachments',
|
|
886
886
|
];
|
|
887
|
+
if ('progress_interval_ms' in merged)
|
|
888
|
+
throw new ConfigError(`${file}: role '${name}' owner_channel.progress_interval_ms has been removed; delete it. `
|
|
889
|
+
+ 'Routine tool activity is shown as one status in the app instead of periodic chat messages');
|
|
887
890
|
const bad = Object.keys(merged).filter(key => !allowed.includes(key));
|
|
888
891
|
if (bad.length)
|
|
889
892
|
throw new ConfigError(`${file}: role '${name}' owner_channel: unknown key(s) ${bad.join(', ')}`);
|
|
@@ -905,10 +908,6 @@ export function resolveOwnerChannelConfig(defaults, role, session, file = 'confi
|
|
|
905
908
|
throw new ConfigError(`${file}: role '${name}' owner_channel.owners must contain exact 64-hex CIDs when agent relay is configured`);
|
|
906
909
|
if (merged.interrupt !== undefined && typeof merged.interrupt !== 'boolean')
|
|
907
910
|
throw new ConfigError(`${file}: role '${name}' owner_channel.interrupt must be true or false`);
|
|
908
|
-
if (merged.progress_interval_ms !== undefined
|
|
909
|
-
&& (typeof merged.progress_interval_ms !== 'number'
|
|
910
|
-
|| !Number.isFinite(merged.progress_interval_ms) || merged.progress_interval_ms < 0))
|
|
911
|
-
throw new ConfigError(`${file}: role '${name}' owner_channel.progress_interval_ms must be a non-negative number`);
|
|
912
911
|
if (merged.comments !== undefined && typeof merged.comments !== 'boolean')
|
|
913
912
|
throw new ConfigError(`${file}: role '${name}' owner_channel.comments must be true or false`);
|
|
914
913
|
if (defaultInput.attachments !== undefined && !isPlainObject(defaultInput.attachments))
|
|
@@ -949,7 +948,6 @@ export function resolveOwnerChannelConfig(defaults, role, session, file = 'confi
|
|
|
949
948
|
owners,
|
|
950
949
|
...(agent ? { agent } : {}),
|
|
951
950
|
interrupt: merged.interrupt ?? false,
|
|
952
|
-
progress_interval_ms: merged.progress_interval_ms ?? 30_000,
|
|
953
951
|
// Default true preserves the established live-commentary behavior; an
|
|
954
952
|
// upgrade never silently goes quiet on an owner who relied on it.
|
|
955
953
|
comments: merged.comments ?? true,
|