@ours.network/fleet 2.2.6 → 2.3.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.
Files changed (38) hide show
  1. package/dist/application/task-room-service.d.ts +11 -0
  2. package/dist/application/task-room-service.js +37 -14
  3. package/dist/atomic-file.d.ts +2 -0
  4. package/dist/atomic-file.js +2 -1
  5. package/dist/build-info.json +4 -4
  6. package/dist/config.d.ts +6 -0
  7. package/dist/config.js +6 -3
  8. package/dist/docs.d.ts +1 -1
  9. package/dist/docs.js +37 -0
  10. package/dist/owner-channel/commands.js +25 -12
  11. package/dist/reports/tasks.js +4 -3
  12. package/dist/rooms-tasks/add-member.js +17 -6
  13. package/dist/rooms-tasks/cli.js +79 -5
  14. package/dist/rooms-tasks/config.js +11 -0
  15. package/dist/rooms-tasks/layout-member-retirement.d.ts +15 -0
  16. package/dist/rooms-tasks/layout-member-retirement.js +26 -1
  17. package/dist/rooms-tasks/layout-service.d.ts +2 -1
  18. package/dist/rooms-tasks/layout-service.js +10 -4
  19. package/dist/rooms-tasks/layout.d.ts +4 -2
  20. package/dist/rooms-tasks/layout.js +21 -4
  21. package/dist/rooms-tasks/member-naming.d.ts +43 -0
  22. package/dist/rooms-tasks/member-naming.js +186 -0
  23. package/dist/rooms-tasks/member-overrides.js +1 -0
  24. package/dist/rooms-tasks/mission-keys.d.ts +83 -0
  25. package/dist/rooms-tasks/mission-keys.js +250 -0
  26. package/dist/rooms-tasks/provision.d.ts +4 -0
  27. package/dist/rooms-tasks/provision.js +37 -3
  28. package/dist/rooms-tasks/task-lists.d.ts +18 -2
  29. package/dist/rooms-tasks/task-lists.js +60 -5
  30. package/dist/rooms-tasks/task-room-name.d.ts +7 -2
  31. package/dist/rooms-tasks/task-room-name.js +13 -4
  32. package/dist/rooms-tasks/task-state.d.ts +40 -0
  33. package/dist/rooms-tasks/task-state.js +288 -45
  34. package/dist/rooms-tasks/types.d.ts +35 -1
  35. package/dist/rooms-tasks/types.js +1 -1
  36. package/dist/web/runtime.js +4 -1
  37. package/dist/web/server.js +62 -27
  38. package/package.json +1 -1
@@ -201,10 +201,21 @@ export declare class TaskRoomApplicationService {
201
201
  list: TaskListRecord;
202
202
  tasks: TaskRecord[];
203
203
  }>;
204
+ /** Run the one-time mission key migration now (service start, `task migrate-keys`). */
205
+ prepareMissionKeys(): Promise<void>;
206
+ /** One-time key migration under the list lock, honoring `tasks.default_prefix`. */
207
+ private ensureMissionKeys;
204
208
  createTaskList(input: {
205
209
  actor: TaskRoomActor;
206
210
  name: string;
211
+ prefix?: string;
207
212
  }): Promise<TaskListRecord>;
213
+ setTaskListPrefix(input: {
214
+ actor: TaskRoomActor;
215
+ name: string;
216
+ prefix: string;
217
+ }): Promise<TaskListRecord>;
218
+ suggestTaskListPrefix(name: string): Promise<string>;
208
219
  renameTaskList(input: {
209
220
  actor: TaskRoomActor;
210
221
  name: string;
@@ -7,8 +7,8 @@ import { ConfigError, loadConfig } from '../config.js';
7
7
  import { CoworkProtocolError, CoworkUnavailableError, createCoworkAdapter, } from '../rooms-tasks/cowork-adapter.js';
8
8
  import { getBinPath, provisionMembers } from '../rooms-tasks/provision.js';
9
9
  import { activateRoom, advanceSaga, createRoomRecord, getRoomRecord, listRoomRecords, setOwnerSeat, setSagaError, } from '../rooms-tasks/room-state.js';
10
- import { activateTask, blockTask as persistBlockTask, createTask as persistTask, getDeletingTask, getTask as readTask, listTasks as readTasks, taskDeletionState, moveTaskToList, reviewTask as persistReviewTask, startTask as transitionTask, TaskStateError, unblockTask as persistUnblockTask, updateTaskRoom, updateTaskTemplate, updateTaskExecutionPlan, updateTaskBrief, updateTaskLayout, } from '../rooms-tasks/task-state.js';
11
- import { createTaskListLocked, DEFAULT_TASK_LIST_ID, deleteTaskListRecordLocked, readTaskLists, renameTaskListLocked, resolveTaskList, TaskListError, withTaskListsLock, } from '../rooms-tasks/task-lists.js';
10
+ import { activateTask, blockTask as persistBlockTask, createTask as persistTask, getDeletingTask, getTask as readTask, listTasks as readTasks, taskDeletionState, migrateMissionKeysLocked, moveTaskToList, reviewTask as persistReviewTask, startTask as transitionTask, TaskStateError, unblockTask as persistUnblockTask, updateTaskRoom, updateTaskTemplate, updateTaskExecutionPlan, updateTaskBrief, updateTaskLayout, } from '../rooms-tasks/task-state.js';
11
+ import { createTaskListLocked, DEFAULT_TASK_LIST_ID, deleteTaskListRecordLocked, readTaskLists, renameTaskListLocked, resolveTaskList, setTaskListPrefixLocked, suggestTaskListPrefix, TaskListError, withTaskListsLock, } from '../rooms-tasks/task-lists.js';
12
12
  import { hashTemplate, listTemplates, resolveTemplate, sealTemplateSnapshot, snapshotTemplate } from '../rooms-tasks/templates.js';
13
13
  import { acquireLaunchSnapshotLock, releaseLaunchSnapshot } from '../rooms-tasks/launch-snapshot.js';
14
14
  import { hashMemberOverrides, prepareExecutionPlan } from '../rooms-tasks/member-overrides.js';
@@ -100,7 +100,7 @@ export class TaskRoomApplicationService {
100
100
  const task = await withTaskListsLock(() => persistTask({
101
101
  title: request.title, brief, brief_file: request.briefFile, layout,
102
102
  origin: request.origin, idempotency_key: request.idempotencyKey,
103
- start: !request.backlog, listId: resolveTaskList(request.list ?? 'default').list_id,
103
+ start: !request.backlog, list: request.list ?? 'default',
104
104
  }));
105
105
  recordFleetAuditPresentation({ kind: 'task', operation: 'create', id: task.task_id,
106
106
  title: task.title, previousState: 'none', newState: task.state, revision: task.created_at,
@@ -200,6 +200,7 @@ export class TaskRoomApplicationService {
200
200
  return this.layouts().view(task);
201
201
  }
202
202
  async createTask(request) {
203
+ await this.ensureMissionKeys();
203
204
  if (request.layout !== undefined)
204
205
  return this.createLayoutTask(request);
205
206
  const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
@@ -229,15 +230,12 @@ export class TaskRoomApplicationService {
229
230
  plan_hash: preparedPlan.planHash };
230
231
  }
231
232
  const ref = template && { name: template.name, version: template.version, content_hash: template.content_hash };
232
- task = await withTaskListsLock(() => {
233
- const list = resolveTaskList(request.list ?? 'default');
234
- return persistTask({
235
- title: request.title, brief, brief_file: request.briefFile, template: ref,
236
- execution_plan: executionPlan,
237
- origin: request.origin, idempotency_key: request.idempotencyKey,
238
- start: !request.backlog, no_room: request.noRoom, listId: list.list_id,
239
- });
240
- });
233
+ task = await withTaskListsLock(() => persistTask({
234
+ title: request.title, brief, brief_file: request.briefFile, template: ref,
235
+ execution_plan: executionPlan,
236
+ origin: request.origin, idempotency_key: request.idempotencyKey,
237
+ start: !request.backlog, no_room: request.noRoom, list: request.list ?? 'default',
238
+ }));
241
239
  }
242
240
  catch (error) {
243
241
  unlock?.();
@@ -317,8 +315,31 @@ export class TaskRoomApplicationService {
317
315
  const lists = filter?.list ? [resolveTaskList(filter.list)] : readTaskLists();
318
316
  return lists.map(list => ({ list, tasks: tasks.filter(task => task.list_id === list.list_id) }));
319
317
  }
318
+ /** Run the one-time mission key migration now (service start, `task migrate-keys`). */
319
+ prepareMissionKeys() { return this.ensureMissionKeys(); }
320
+ /** One-time key migration under the list lock, honoring `tasks.default_prefix`. */
321
+ ensureMissionKeys() {
322
+ const configured = (() => {
323
+ try {
324
+ return (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath).tasks?.default_prefix;
325
+ }
326
+ catch {
327
+ return undefined;
328
+ }
329
+ })();
330
+ return withTaskListsLock(() => migrateMissionKeysLocked(configured));
331
+ }
320
332
  async createTaskList(input) {
321
- return withTaskListsLock(() => createTaskListLocked(input.name));
333
+ await this.ensureMissionKeys();
334
+ return withTaskListsLock(() => createTaskListLocked(input.name, input.prefix));
335
+ }
336
+ async setTaskListPrefix(input) {
337
+ await this.ensureMissionKeys();
338
+ return withTaskListsLock(() => setTaskListPrefixLocked(input.name, input.prefix));
339
+ }
340
+ async suggestTaskListPrefix(name) {
341
+ await this.ensureMissionKeys();
342
+ return suggestTaskListPrefix(name);
322
343
  }
323
344
  async renameTaskList(input) {
324
345
  return withTaskListsLock(() => renameTaskListLocked(input.name, input.newName));
@@ -1125,7 +1146,9 @@ export class TaskRoomApplicationService {
1125
1146
  throw new ConfigError('rooms: configuration is required before creating or querying rooms');
1126
1147
  return createCoworkAdapter({ configPath: cfg.rooms.cowork?.config });
1127
1148
  })();
1128
- const roomName = task.task_id ? deriveTaskRoomName(task.title, task.task_id) : task.title;
1149
+ // Only tasks keyed at creation lead with the key: a room create interrupted across the
1150
+ // upgrade must keep the name its earlier attempt used.
1151
+ const roomName = task.task_id ? deriveTaskRoomName(task.title, task.task_id, task.key && !task.key_from_migration ? task.key : undefined) : task.title;
1129
1152
  const unlockSnapshot = template ? acquireLaunchSnapshotLock() : undefined;
1130
1153
  let launchTemplate;
1131
1154
  let room;
@@ -47,6 +47,8 @@ export interface ReplaceOptions {
47
47
  */
48
48
  durable?: boolean;
49
49
  }
50
+ /** Make completed renames in `dir` durable; throws where the platform cannot. */
51
+ export declare function fsyncDirectory(dir: string): void;
50
52
  /**
51
53
  * Run `fn` holding a cross-process lock. A fully populated private claim directory
52
54
  * is atomically renamed to the canonical path, so contenders never observe partial
@@ -19,7 +19,8 @@ export class FileLockOwnerUnverifiableError extends Error {
19
19
  this.pid = pid;
20
20
  }
21
21
  }
22
- function fsyncDirectory(dir) {
22
+ /** Make completed renames in `dir` durable; throws where the platform cannot. */
23
+ export function fsyncDirectory(dir) {
23
24
  const dirFd = openSync(dir, 'r');
24
25
  try {
25
26
  fsyncSync(dirFd);
@@ -1,9 +1,9 @@
1
1
  {
2
- "version": "2.2.6",
3
- "buildId": "ce1d73f64f81",
4
- "commit": "2f3d0b2224caee214965d30a2fbad2c985510b53",
2
+ "version": "2.3.0",
3
+ "buildId": "246fc0d15b6e",
4
+ "commit": "ce34aa31bf25ec6d0af430270b65f013e3681900",
5
5
  "dirty": false,
6
- "builtAt": "2026-10-30T14:29:44.502Z",
6
+ "builtAt": "2026-10-30T17:28:38.538Z",
7
7
  "capabilities": [
8
8
  "service.refresh-v1",
9
9
  "managed-cli.codex-reasoning-effort-v1",
package/dist/config.d.ts CHANGED
@@ -272,6 +272,12 @@ export interface FleetConfig {
272
272
  agentDefinitions?: Record<string, AgentDefinition>;
273
273
  /** Canonical inert templates keyed by stable template ID. */
274
274
  agentTemplates?: Record<string, AgentTemplateDefinition>;
275
+ /**
276
+ * Named Role selected by each Agent Template (`role: { ref: X }`), captured
277
+ * before roles are flattened to `inline`. Naming provenance only: it is kept
278
+ * outside the definitions so launch fingerprints are unchanged.
279
+ */
280
+ agentTemplateRoleRefs?: Record<string, string>;
275
281
  rolePresets?: Record<string, Record<string, unknown>>;
276
282
  brainPresets?: Record<string, Record<string, unknown>>;
277
283
  /** Required launch-boundary resolver for effective temporary Agent definitions. */
package/dist/config.js CHANGED
@@ -427,7 +427,7 @@ function mergeTemplateDefinition(template, overrides, file, id) {
427
427
  result[key] = merge(result[key], value, key);
428
428
  return result;
429
429
  }
430
- function composeSplitRoles(base, baseDoc, yamlMode, diagnostics, files, additionalAgent, templateSink = {}, roleSink = {}, brainSink = {}) {
430
+ function composeSplitRoles(base, baseDoc, yamlMode, diagnostics, files, additionalAgent, templateSink = {}, roleSink = {}, brainSink = {}, roleRefSink = {}) {
431
431
  if (baseDoc.api_version !== V2_API_VERSION)
432
432
  throw new ConfigError(`${base}: legacy fleet configuration is unsupported; run 'ours-fleet doctor' and rewrite it as ${V2_API_VERSION}`);
433
433
  if ('roles' in baseDoc)
@@ -472,6 +472,8 @@ function composeSplitRoles(base, baseDoc, yamlMode, diagnostics, files, addition
472
472
  validateBrainValue(templateBrain.value, templateBrain.file, templateBrain.pointer);
473
473
  validateAgentScalars(Object.fromEntries(Object.entries(value).filter(([key]) => key !== 'role' && key !== 'brain')), template.file, id);
474
474
  canonicalTemplates.set(id, { file: template.file, value });
475
+ if (isPlainObject(value.role) && typeof value.role.ref === 'string')
476
+ roleRefSink[id] = value.role.ref;
475
477
  const operational = deepSubStrict(Object.fromEntries(Object.entries(value).filter(([key]) => key !== 'role' && key !== 'brain')), vars, template.file, `/agent_templates/${id}`);
476
478
  templateSink[id] = {
477
479
  role: { inline: deepSubStrict(templateRole.value, vars, templateRole.file, templateRole.pointer) },
@@ -614,6 +616,7 @@ export function loadConfig(configPath, options = {}) {
614
616
  const files = [];
615
617
  const diagnostics = [];
616
618
  const agentTemplates = {};
619
+ const agentTemplateRoleRefs = {};
617
620
  const rolePresets = {};
618
621
  const brainPresets = {};
619
622
  let docs = [];
@@ -634,7 +637,7 @@ export function loadConfig(configPath, options = {}) {
634
637
  const dd = fleetDDir();
635
638
  if (existsSync(dd) && readdirSync(dd).some(f => f.endsWith('.yaml') || f.endsWith('.yml')))
636
639
  throw new ConfigError(`${dd}: legacy fleet.d configuration is unsupported; run 'ours-fleet doctor' and move agents to ${splitRootFor(base)}/agents/`);
637
- docs = [{ file: base, doc: manifestDoc }, ...composeSplitRoles(base, manifestDoc, options.yamlMode ?? 'compat', diagnostics, files, options.additionalAgent, agentTemplates, rolePresets, brainPresets)];
640
+ docs = [{ file: base, doc: manifestDoc }, ...composeSplitRoles(base, manifestDoc, options.yamlMode ?? 'compat', diagnostics, files, options.additionalAgent, agentTemplates, rolePresets, brainPresets, agentTemplateRoleRefs)];
638
641
  const baseDoc = manifestDoc;
639
642
  const vars = (baseDoc.vars ?? {});
640
643
  const defaults = (baseDoc.defaults ?? {});
@@ -822,7 +825,7 @@ export function loadConfig(configPath, options = {}) {
822
825
  }
823
826
  }
824
827
  const result = {
825
- roles, agentDefinitions, agentTemplates, rolePresets, brainPresets, vars, defaults, files, startStaggerMs, diagnostics, watchdogs,
828
+ roles, agentDefinitions, agentTemplates, agentTemplateRoleRefs, rolePresets, brainPresets, vars, defaults, files, startStaggerMs, diagnostics, watchdogs,
826
829
  loops: resolvedLoops.loops,
827
830
  rooms, roomTemplates, tasks, ownerInviteFingerprint,
828
831
  configMode: 'split-v2',