@ours.network/fleet 1.1.0-nightly.9 → 1.1.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.
Files changed (202) hide show
  1. package/README.md +285 -46
  2. package/dist/application/capabilities.js +18 -6
  3. package/dist/application/fleet-query-service.js +7 -4
  4. package/dist/application/role-command-service.js +9 -0
  5. package/dist/application/role-creation-service.d.ts +4 -4
  6. package/dist/application/role-creation-service.js +8 -1
  7. package/dist/application/role-repository.d.ts +4 -2
  8. package/dist/application/role-repository.js +6 -3
  9. package/dist/application/session-control.d.ts +3 -1
  10. package/dist/application/session-control.js +4 -2
  11. package/dist/application/task-room-service.d.ts +92 -39
  12. package/dist/application/task-room-service.js +562 -289
  13. package/dist/briefing.js +79 -22
  14. package/dist/build-info.json +5 -5
  15. package/dist/canonical-json.d.ts +2 -0
  16. package/dist/canonical-json.js +10 -0
  17. package/dist/cli.js +129 -47
  18. package/dist/config.d.ts +42 -3
  19. package/dist/config.js +202 -29
  20. package/dist/creation.d.ts +1 -1
  21. package/dist/creation.js +7 -4
  22. package/dist/docs.d.ts +1 -1
  23. package/dist/docs.js +195 -41
  24. package/dist/doctor.d.ts +2 -0
  25. package/dist/doctor.js +40 -4
  26. package/dist/fleet-command-audit.d.ts +22 -1
  27. package/dist/fleet-command-audit.js +355 -50
  28. package/dist/fleet-proxy.d.ts +5 -2
  29. package/dist/harness/claude-code-session.js +5 -1
  30. package/dist/harness/codex-app-server-proxy.d.ts +40 -0
  31. package/dist/harness/codex-app-server-proxy.js +348 -14
  32. package/dist/harness/codex-session.d.ts +9 -2
  33. package/dist/harness/codex-session.js +21 -2
  34. package/dist/harness/codex.d.ts +5 -1
  35. package/dist/harness/codex.js +104 -10
  36. package/dist/harness/types.d.ts +1 -1
  37. package/dist/index.d.ts +4 -3
  38. package/dist/index.js +3 -2
  39. package/dist/init-guidance.d.ts +1 -1
  40. package/dist/init-guidance.js +1 -1
  41. package/dist/init-wizard.d.ts +98 -0
  42. package/dist/init-wizard.js +631 -0
  43. package/dist/lifecycle-summary.d.ts +134 -0
  44. package/dist/lifecycle-summary.js +179 -0
  45. package/dist/loops/config.d.ts +10 -1
  46. package/dist/loops/config.js +38 -5
  47. package/dist/model-recovery.d.ts +1 -1
  48. package/dist/ops.js +2 -17
  49. package/dist/owner-channel/channel.d.ts +19 -3
  50. package/dist/owner-channel/channel.js +301 -120
  51. package/dist/owner-channel/commands.d.ts +28 -6
  52. package/dist/owner-channel/commands.js +109 -27
  53. package/dist/owner-channel/lifecycle-outbox.d.ts +24 -0
  54. package/dist/owner-channel/lifecycle-outbox.js +87 -0
  55. package/dist/owner-channel/ours-client.d.ts +19 -0
  56. package/dist/owner-channel/ours-client.js +3 -0
  57. package/dist/permissions.js +1 -1
  58. package/dist/preset-migration.d.ts +42 -0
  59. package/dist/preset-migration.js +450 -0
  60. package/dist/reports/artifact.d.ts +10 -0
  61. package/dist/reports/artifact.js +32 -0
  62. package/dist/reports/index.d.ts +5 -0
  63. package/dist/reports/index.js +5 -0
  64. package/dist/reports/render.d.ts +6 -0
  65. package/dist/reports/render.js +240 -0
  66. package/dist/reports/service.d.ts +42 -0
  67. package/dist/reports/service.js +57 -0
  68. package/dist/reports/tasks.d.ts +52 -0
  69. package/dist/reports/tasks.js +177 -0
  70. package/dist/reports/types.d.ts +315 -0
  71. package/dist/reports/types.js +6 -0
  72. package/dist/resolved-plan.js +13 -1
  73. package/dist/rooms-tasks/cli.d.ts +2 -0
  74. package/dist/rooms-tasks/cli.js +361 -285
  75. package/dist/rooms-tasks/close.d.ts +9 -1
  76. package/dist/rooms-tasks/close.js +23 -7
  77. package/dist/rooms-tasks/config.js +16 -25
  78. package/dist/rooms-tasks/cowork-adapter.d.ts +5 -0
  79. package/dist/rooms-tasks/cowork-adapter.js +13 -0
  80. package/dist/rooms-tasks/deletion.d.ts +51 -0
  81. package/dist/rooms-tasks/deletion.js +217 -0
  82. package/dist/rooms-tasks/external-worker.d.ts +8 -0
  83. package/dist/rooms-tasks/external-worker.js +38 -0
  84. package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
  85. package/dist/rooms-tasks/launch-snapshot.js +181 -0
  86. package/dist/rooms-tasks/member-overrides.d.ts +30 -0
  87. package/dist/rooms-tasks/member-overrides.js +204 -0
  88. package/dist/rooms-tasks/member-startup.d.ts +1 -0
  89. package/dist/rooms-tasks/member-startup.js +9 -2
  90. package/dist/rooms-tasks/provision.js +146 -83
  91. package/dist/rooms-tasks/room-state.d.ts +1 -0
  92. package/dist/rooms-tasks/room-state.js +12 -1
  93. package/dist/rooms-tasks/task-room-name.d.ts +12 -0
  94. package/dist/rooms-tasks/task-room-name.js +61 -0
  95. package/dist/rooms-tasks/task-state.d.ts +115 -3
  96. package/dist/rooms-tasks/task-state.js +361 -11
  97. package/dist/rooms-tasks/templates.d.ts +4 -1
  98. package/dist/rooms-tasks/templates.js +34 -3
  99. package/dist/rooms-tasks/terminal.d.ts +3 -0
  100. package/dist/rooms-tasks/terminal.js +10 -4
  101. package/dist/rooms-tasks/types.d.ts +92 -7
  102. package/dist/rooms-tasks/types.js +14 -1
  103. package/dist/runner.d.ts +3 -0
  104. package/dist/runner.js +97 -47
  105. package/dist/session/acp.d.ts +27 -1
  106. package/dist/session/acp.js +307 -17
  107. package/dist/session/arbiter.d.ts +2 -1
  108. package/dist/session/arbiter.js +2 -0
  109. package/dist/session/codex-app-server-transport.d.ts +53 -0
  110. package/dist/session/codex-app-server-transport.js +184 -0
  111. package/dist/session/codex-app-server.d.ts +122 -0
  112. package/dist/session/codex-app-server.js +1031 -0
  113. package/dist/session/control.d.ts +1 -1
  114. package/dist/session/control.js +17 -1
  115. package/dist/session/conversation-types.d.ts +4 -4
  116. package/dist/session/stall-watchdog.d.ts +60 -0
  117. package/dist/session/stall-watchdog.js +210 -0
  118. package/dist/session/types.d.ts +29 -8
  119. package/dist/session/types.js +28 -0
  120. package/dist/spawn.d.ts +9 -0
  121. package/dist/spawn.js +77 -9
  122. package/dist/web/fleet-config-service.d.ts +1 -0
  123. package/dist/web/fleet-config-service.js +25 -7
  124. package/dist/web/runtime.js +11 -6
  125. package/dist/web/server.js +28 -1
  126. package/dist/web-app/assets/{index-BbTc5KtZ.js → index-C0h3ALvs.js} +8 -8
  127. package/dist/web-app/index.html +1 -1
  128. package/package.json +4 -4
  129. package/presets/brain-catalog.json +17 -0
  130. package/presets/fleet/{agents → agent_templates}/Critic.yaml +2 -0
  131. package/presets/fleet/{agents/Agent.yaml → agent_templates/Developer.yaml} +3 -1
  132. package/presets/fleet/agent_templates/LocalCoordinator.yaml +50 -0
  133. package/presets/fleet/agents/{Developer.yaml → FleetCoordinator.yaml} +1 -1
  134. package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
  135. package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
  136. package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
  137. package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
  138. package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
  139. package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
  140. package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
  141. package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
  142. package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
  143. package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
  144. package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
  145. package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
  146. package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
  147. package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
  148. package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
  149. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
  150. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
  151. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
  152. package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
  153. package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
  154. package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
  155. package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
  156. package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
  157. package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
  158. package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
  159. package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
  160. package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
  161. package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
  162. package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
  163. package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
  164. package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
  165. package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
  166. package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
  167. package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
  168. package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
  169. package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
  170. package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
  171. package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
  172. package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
  173. package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
  174. package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
  175. package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
  176. package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
  177. package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
  178. package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
  179. package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
  180. package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
  181. package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
  182. package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +5 -0
  183. package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +5 -0
  184. package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +5 -0
  185. package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +5 -0
  186. package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +5 -0
  187. package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +5 -0
  188. package/presets/fleet/roles/Coordinator.yaml +37 -0
  189. package/presets/fleet/roles/Critic.yaml +23 -1
  190. package/presets/fleet/roles/Developer.yaml +27 -5
  191. package/presets/fleet/roles/LocalCoordinator.yaml +35 -0
  192. package/presets/fleet/room_templates/pair.yaml +9 -7
  193. package/presets/fleet/room_templates/single.yaml +5 -4
  194. package/presets/fleet/room_templates/team.yaml +12 -8
  195. package/presets/manifest.json +2 -1
  196. package/presets/fleet/agents/Architect.yaml +0 -3
  197. package/presets/fleet/agents/Secretary.yaml +0 -3
  198. package/presets/fleet/agents/Tester.yaml +0 -3
  199. package/presets/fleet/roles/Agent.yaml +0 -6
  200. package/presets/fleet/roles/Architect.yaml +0 -6
  201. package/presets/fleet/roles/Secretary.yaml +0 -6
  202. package/presets/fleet/roles/Tester.yaml +0 -6
@@ -1,7 +1,7 @@
1
1
  import type { InterruptOutcome, SessionEvent, SessionSnapshot } from '../session/types.js';
2
2
  import type { RoomOrchestrationRecord, TaskOutcome, TaskRecord, TaskTerminalIntent, TaskListRecord } from '../rooms-tasks/types.js';
3
3
  import { type OwnerCommentsState } from './notices.js';
4
- import type { CreateRoomRequest, CreateTaskRequest, TaskRoomApplicationService } from '../application/task-room-service.js';
4
+ import type { CreateRoomRequest, CreateTaskRequest, TaskProvisioningOutcome, TaskRoomApplicationService } from '../application/task-room-service.js';
5
5
  import type { TaskState } from '../rooms-tasks/types.js';
6
6
  /**
7
7
  * Fleet-level effects a deterministic owner command may trigger. Production
@@ -17,7 +17,10 @@ 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
- recoverTask(taskId: string): Promise<void>;
20
+ /** Settle an accepted task deletion outside the caller role's supervisor lifecycle. */
21
+ settleTaskDeletion(taskId: string): Promise<void>;
22
+ /** Continue an accepted provisioning saga until convergence. */
23
+ provisionTask(taskId: string): Promise<void>;
21
24
  }
22
25
  /**
23
26
  * The narrow capability surface a command executor sees. Everything here is
@@ -25,6 +28,7 @@ export interface OwnerFleetOps {
25
28
  * cannot name another agent or another recipient.
26
29
  */
27
30
  export interface OwnerCommandContext {
31
+ authenticatedCid?: string;
28
32
  role: string;
29
33
  /** Harness id of the role (e.g. 'claude-code', 'codex'); gates forwarding. */
30
34
  harness: string;
@@ -50,12 +54,11 @@ export interface OwnerCommandContext {
50
54
  fleetList(): Promise<string>;
51
55
  /** Persist acceptance, acknowledge it, then launch the external close worker. */
52
56
  closeRoom(roomId: string): Promise<void>;
53
- recoverRoom(roomId: string): Promise<void>;
54
57
  /** Persist terminal intent, acknowledge it, then launch the external settle worker. */
55
58
  terminalTask(taskId: string, kind: TaskTerminalIntent['kind'], outcome?: TaskOutcome): Promise<void>;
56
- recoverTask(taskId: string): Promise<void>;
57
59
  createTask(input: Omit<CreateTaskRequest, 'actor'>): Promise<TaskRecord>;
58
- startTask(taskId: string): Promise<TaskRecord>;
60
+ startTask(taskId: string): Promise<TaskProvisioningOutcome | TaskRecord>;
61
+ taskProvisioningOutcome?(taskId: string): TaskProvisioningOutcome;
59
62
  listTasks(filter?: {
60
63
  state?: TaskState | TaskState[];
61
64
  list?: 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
- deleteTask(taskId: string): boolean;
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. */
@@ -105,6 +110,11 @@ export interface OwnerCommand {
105
110
  summary: string;
106
111
  execute(ctx: OwnerCommandContext, args: string): Promise<void>;
107
112
  }
113
+ export interface OwnerTypedCommandDefinition {
114
+ name: string;
115
+ description: string;
116
+ input_schema: Record<string, unknown>;
117
+ }
108
118
  /**
109
119
  * Commands each harness's bundled ACP adapter verifiably executes locally,
110
120
  * pinned by test/acp-adapter-commands.test.ts against the shipped adapter
@@ -121,6 +131,18 @@ export declare const HARNESS_LOCAL_COMMANDS: Record<string, readonly string[]>;
121
131
  * registration step for a new command.
122
132
  */
123
133
  export declare const ownerCommands: OwnerCommand[];
134
+ /**
135
+ * Project the slash-command registry into the SDK catalog. Only primary names
136
+ * are advertised: aliases remain accepted by the slash dispatcher without
137
+ * cluttering the typed menu with duplicate actions.
138
+ *
139
+ * Typed arguments deliberately stay as the exact text following the command
140
+ * name. That adapter is what lets typed and slash invocations share every
141
+ * existing parser, usage error, lifecycle guard, and result path.
142
+ */
143
+ export declare function ownerTypedCommandCatalog(): OwnerTypedCommandDefinition[];
144
+ /** Convert one typed invocation back into the canonical slash input. */
145
+ export declare function ownerTypedCommandText(name: string, input: unknown): string;
124
146
  /** Trimmed slash-prefixed text is a command attempt and is never forwarded. */
125
147
  export declare const isOwnerCommandText: (text: string) => boolean;
126
148
  export declare function ownerCommandHelp(error?: string): string;
@@ -6,6 +6,16 @@ import { markdownCode, markdownProse, renderMarkdownFailure, renderMarkdownList,
6
6
  import { OWNER_COMMENT_LABEL, ownerNotices } from './notices.js';
7
7
  import { TaskRoomApplicationError } from '../application/task-room-service.js';
8
8
  import { TaskListError } from '../rooms-tasks/task-lists.js';
9
+ import { createTasksReport } from '../reports/index.js';
10
+ async function ownerTaskReport(ctx) {
11
+ return createTasksReport({
12
+ viewer: { surface: 'messenger', authenticatedCid: ctx.authenticatedCid ?? 'owner-command-context', roomCids: [] },
13
+ collect: () => {
14
+ return { lists: ctx.listTaskLists(), tasks: ctx.listTasks() };
15
+ },
16
+ source: { name: 'ours-fleet', version: ctx.version },
17
+ });
18
+ }
9
19
  /** A malformed invocation; the dispatcher answers it with annotated help. */
10
20
  class OwnerCommandUsageError extends Error {
11
21
  }
@@ -26,7 +36,7 @@ function isKnownOwnerTaskState(message) {
26
36
  /^task is not blocked$/u,
27
37
  new RegExp(`^task ${SAFE_ID_WORD} already has a conflicting '(?:done|cancelled)' terminal intent$`, 'u'),
28
38
  new RegExp(`^task ${SAFE_ID_WORD} is already in terminal state '${TASK_STATE_WORD}'$`, 'u'),
29
- new RegExp(`^cannot delete a '${TASK_STATE_WORD}' task; only 'done' tasks can be deleted$`, 'u'),
39
+ new RegExp(`^task ${SAFE_ID_WORD} is pending deletion; run 'ours-fleet task delete ${SAFE_ID_WORD} ${SAFE_ID_WORD}' to retry cleanup$`, 'u'),
30
40
  ].some(pattern => pattern.test(message));
31
41
  }
32
42
  function isKnownOwnerRoomState(message) {
@@ -96,6 +106,22 @@ const taskAction = (title, task, fields = []) => renderMarkdownResult({
96
106
  ...fields,
97
107
  ],
98
108
  });
109
+ const taskProvisioningAction = (outcome) => {
110
+ if (outcome.kind === 'failed')
111
+ return renderMarkdownFailure({
112
+ kind: 'state', subject: `/task start ${outcome.task.task_id}`,
113
+ detail: outcome.blocker ?? 'Provisioning reached a terminal failure.',
114
+ action: outcome.next_action ?? `Run /task start ${outcome.task.task_id}.`,
115
+ });
116
+ return taskAction(outcome.kind === 'ready' ? 'Room provisioning complete'
117
+ : outcome.next_action ? 'Room provisioning needs attention' : 'Room provisioning continues', outcome.task, [
118
+ ...(outcome.room ? [{ label: 'Room', value: `${outcome.room.room_id} — ${outcome.room.room_name}` }] : []),
119
+ ...(outcome.launch.template ? [{ label: 'Template', value: outcome.launch.template, kind: 'code' }] : []),
120
+ { label: 'Members', value: `${outcome.members.active}/${outcome.members.expected} active; ${outcome.members.launched}/${outcome.members.expected} launched` },
121
+ ...(outcome.blocker ? [{ label: 'Blocker', value: outcome.blocker }] : []),
122
+ ...(outcome.next_action ? [{ label: 'Next action', value: outcome.next_action }] : []),
123
+ ]);
124
+ };
99
125
  const roomAction = (title, room, fields = []) => renderMarkdownResult({
100
126
  icon: '🏠', title,
101
127
  fields: [
@@ -242,10 +268,18 @@ export const ownerCommands = [
242
268
  execute: noArgs('/version', async (ctx) => ctx.reply(`ℹ️ ours-fleet ${ctx.version}`)),
243
269
  },
244
270
  {
245
- name: 'tasks', usage: '/tasks [state]',
246
- summary: 'list tasks (optionally filter by state)',
271
+ name: 'tasks', usage: '/tasks [state|html]',
272
+ summary: 'list tasks, or attach the HTML report with /tasks html',
247
273
  execute: async (ctx, args) => {
248
274
  const stateFilter = args?.trim() || undefined;
275
+ if (stateFilter === 'html') {
276
+ const artifact = await ownerTaskReport(ctx);
277
+ await ctx.replyHtml(artifact.metadata.filename, artifact.html);
278
+ await ctx.reply(`HTML report attached: ${artifact.metadata.filename}`);
279
+ return;
280
+ }
281
+ if (stateFilter && /\s/u.test(stateFilter))
282
+ throw new OwnerCommandUsageError('usage: /tasks [state|html]');
249
283
  const tasks = ctx.listTasks(stateFilter && stateFilter !== 'all' ? { state: stateFilter } : undefined);
250
284
  await ctx.reply(renderMarkdownList({
251
285
  icon: '📋', title: 'Tasks', empty: 'No tasks found.',
@@ -255,7 +289,7 @@ export const ownerCommands = [
255
289
  },
256
290
  {
257
291
  name: 'task',
258
- usage: '/task <create|list|show|start|block|unblock|review|done|cancel|delete|recover> ...',
292
+ usage: '/task <create|list|show|start|block|unblock|review|done|cancel|delete> ...',
259
293
  summary: 'task lifecycle subcommands',
260
294
  execute: async (ctx, args) => {
261
295
  if (!args)
@@ -301,6 +335,12 @@ export const ownerCommands = [
301
335
  title: titleParts.join(' '), origin: { type: 'owner_channel' },
302
336
  backlog, template, noRoom, brief: trailingLines, list,
303
337
  });
338
+ const provisioning = !backlog && !noRoom && t.room_id
339
+ ? ctx.taskProvisioningOutcome?.(t.task_id) : undefined;
340
+ if (provisioning) {
341
+ await ctx.reply(taskProvisioningAction(provisioning));
342
+ break;
343
+ }
304
344
  await ctx.reply(taskAction('Task created', t, [
305
345
  { label: 'Title', value: t.title },
306
346
  ...(template ? [{ label: 'Template', value: template, kind: 'code' }] : []),
@@ -309,6 +349,9 @@ export const ownerCommands = [
309
349
  break;
310
350
  }
311
351
  case 'list': {
352
+ if (rest.some(value => value.startsWith('--')
353
+ && value !== '--group-by-list' && !value.startsWith('--list=')))
354
+ throw new OwnerCommandUsageError('usage: /task list [state] [--list=<name>] [--group-by-list]');
312
355
  const listFlag = rest.find(r => r.startsWith('--list='));
313
356
  const list = trailingLines ?? listFlag?.slice('--list='.length);
314
357
  const grouped = rest.includes('--group-by-list');
@@ -345,6 +388,8 @@ export const ownerCommands = [
345
388
  break;
346
389
  }
347
390
  case 'lists': {
391
+ if (rest.length || trailingLines)
392
+ throw new OwnerCommandUsageError('usage: /task lists');
348
393
  const lists = ctx.listTaskLists();
349
394
  await ctx.reply(renderMarkdownList({ icon: '📚', title: 'Task lists', empty: 'No task lists found.',
350
395
  records: lists.map(list => `${markdownCode(list.name)}${list.built_in ? ' — built-in' : ''}`) }));
@@ -388,7 +433,7 @@ export const ownerCommands = [
388
433
  break;
389
434
  }
390
435
  case 'show': {
391
- if (!rest[0])
436
+ if (rest.length !== 1 || rest[0].startsWith('--'))
392
437
  throw new OwnerCommandUsageError('usage: /task show <id>');
393
438
  await showTask(rest[0]);
394
439
  break;
@@ -396,8 +441,9 @@ export const ownerCommands = [
396
441
  case 'start': {
397
442
  if (!rest[0])
398
443
  throw new OwnerCommandUsageError('usage: /task start <id>');
399
- const t = await ctx.startTask(rest[0]);
400
- await ctx.reply(taskAction('Task started', t));
444
+ const started = await ctx.startTask(rest[0]);
445
+ await ctx.reply('kind' in started
446
+ ? taskProvisioningAction(started) : taskAction('Task started', started));
401
447
  break;
402
448
  }
403
449
  case 'block': {
@@ -438,21 +484,13 @@ export const ownerCommands = [
438
484
  case 'delete': {
439
485
  if (rest.length !== 2 || rest[0] !== rest[1])
440
486
  throw new OwnerCommandUsageError('destructive: /task delete <id> <id> — provide the task ID twice');
441
- const deleted = ctx.deleteTask(rest[0]);
442
- await ctx.reply(renderMarkdownResult({
443
- icon: '🗑️', title: deleted ? 'Task deleted' : 'Task already absent',
444
- fields: [{ label: 'ID', value: rest[0], kind: 'code' }],
445
- }));
446
- break;
447
- }
448
- case 'recover': {
449
- if (!rest[0])
450
- throw new OwnerCommandUsageError('usage: /task recover <id>');
451
- await ctx.recoverTask(rest[0]);
487
+ await ctx.deleteTask(rest[0]);
452
488
  break;
453
489
  }
454
490
  default:
455
491
  // bare /task <id> → show
492
+ if (sub === 'recover' || sub === 'await' || rest.length)
493
+ throw new OwnerCommandUsageError(`unknown task subcommand: ${sub}`);
456
494
  await showTask(sub);
457
495
  }
458
496
  }
@@ -482,7 +520,7 @@ export const ownerCommands = [
482
520
  },
483
521
  {
484
522
  name: 'room',
485
- usage: '/room <create|list|show|delete|close|recover> ...',
523
+ usage: '/room <create|list|show|delete|close> ...',
486
524
  summary: 'room lifecycle subcommands',
487
525
  execute: async (ctx, args) => {
488
526
  if (!args)
@@ -567,14 +605,10 @@ export const ownerCommands = [
567
605
  await ctx.closeRoom(rest[0]);
568
606
  break;
569
607
  }
570
- case 'recover': {
571
- if (!rest[0])
572
- throw new OwnerCommandUsageError('usage: /room recover <id>');
573
- await ctx.recoverRoom(rest[0]);
574
- break;
575
- }
576
608
  default:
577
609
  // bare /room <id> → show
610
+ if (sub === 'recover' || rest.length)
611
+ throw new OwnerCommandUsageError(`unknown room subcommand: ${sub}`);
578
612
  await showRoom(sub);
579
613
  }
580
614
  }
@@ -628,7 +662,7 @@ export const ownerCommands = [
628
662
  ...(t.contract ? [`Contract: ${t.contract}`] : []),
629
663
  'Members:',
630
664
  ...t.members.map(m => ` ${m.slot} (${m.role}) ×${m.count} → `
631
- + `${'ref' in m.agent ? `Agent ref: ${m.agent.ref}` : 'inline Agent'}`),
665
+ + `Agent Template: ${m.agent_template}`),
632
666
  ];
633
667
  await ctx.reply(lines.join('\n'));
634
668
  };
@@ -654,6 +688,53 @@ export const ownerCommands = [
654
688
  },
655
689
  },
656
690
  ];
691
+ /**
692
+ * Project the slash-command registry into the SDK catalog. Only primary names
693
+ * are advertised: aliases remain accepted by the slash dispatcher without
694
+ * cluttering the typed menu with duplicate actions.
695
+ *
696
+ * Typed arguments deliberately stay as the exact text following the command
697
+ * name. That adapter is what lets typed and slash invocations share every
698
+ * existing parser, usage error, lifecycle guard, and result path.
699
+ */
700
+ export function ownerTypedCommandCatalog() {
701
+ return ownerCommands.map(command => {
702
+ const usage = command.usage ?? `/${command.name}`;
703
+ const suffix = usage.slice(usage.indexOf(' ') + 1);
704
+ const acceptsArguments = usage.includes(' ');
705
+ const requiresArguments = acceptsArguments && !suffix.startsWith('[');
706
+ return {
707
+ name: command.name,
708
+ description: `${command.summary} (${usage})`,
709
+ input_schema: {
710
+ type: 'object',
711
+ properties: acceptsArguments ? {
712
+ arguments: {
713
+ type: 'string', title: 'Arguments',
714
+ description: `Text after /${command.name}. Usage: ${usage}`,
715
+ },
716
+ } : {},
717
+ ...(requiresArguments ? { required: ['arguments'] } : {}),
718
+ },
719
+ };
720
+ });
721
+ }
722
+ /** Convert one typed invocation back into the canonical slash input. */
723
+ export function ownerTypedCommandText(name, input) {
724
+ const command = ownerCommands.find(entry => entry.name === name);
725
+ if (!command)
726
+ throw new Error(`unknown typed owner command: ${name}`);
727
+ if (!input || typeof input !== 'object' || Array.isArray(input))
728
+ throw new Error('typed owner command arguments must be an object');
729
+ const record = input;
730
+ if (Object.keys(record).some(key => key !== 'arguments'))
731
+ throw new Error('typed owner command arguments contain an unknown field');
732
+ const value = record.arguments;
733
+ if (value !== undefined && typeof value !== 'string')
734
+ throw new Error('typed owner command arguments must be text');
735
+ const args = value?.trim() ?? '';
736
+ return `/${command.name}${args ? ` ${args}` : ''}`;
737
+ }
657
738
  /** Trimmed slash-prefixed text is a command attempt and is never forwarded. */
658
739
  export const isOwnerCommandText = (text) => text.trim().startsWith('/');
659
740
  export function ownerCommandHelp(error) {
@@ -711,6 +792,7 @@ export function fleetCliOps(role, configPath) {
711
792
  }),
712
793
  closeRoom: roomId => launchFleetWorker(['room', '_delete', roomId], `room-delete-${roomId}`, configPath),
713
794
  settleTask: taskId => launchFleetWorker(['task', '_settle', taskId], `task-settle-${taskId}`, configPath),
714
- recoverTask: taskId => launchFleetWorker(['task', '_recover', taskId], `task-recover-${taskId}`, configPath),
795
+ settleTaskDeletion: taskId => launchFleetWorker(['task', '_settle_delete', taskId], `task-delete-${taskId}`, configPath),
796
+ provisionTask: taskId => launchFleetWorker(['task', '_provision', taskId], `task-provision-${taskId}`, configPath),
715
797
  };
716
798
  }
@@ -0,0 +1,24 @@
1
+ import { type FleetAuditPresentation } from '../fleet-command-audit.js';
2
+ type LifecycleDelivery = 'pending' | 'delivered' | 'uncertain';
3
+ interface LifecycleOutboxEntry {
4
+ digest: string;
5
+ presentation: FleetAuditPresentation;
6
+ delivery: LifecycleDelivery;
7
+ }
8
+ /** Durable lifecycle acceptance queue; plaintext is limited to validated concise presentations. */
9
+ export declare class FleetLifecycleOutbox {
10
+ private readonly path;
11
+ private entries;
12
+ private corruptReason?;
13
+ constructor(path: string);
14
+ integrity(): {
15
+ ok: boolean;
16
+ error?: string;
17
+ };
18
+ enqueue(presentations: FleetAuditPresentation[]): void;
19
+ pending(): readonly LifecycleOutboxEntry[];
20
+ finish(digest: string, delivery: Exclude<LifecycleDelivery, 'pending'>): void;
21
+ private assertHealthy;
22
+ private persist;
23
+ }
24
+ export {};
@@ -0,0 +1,87 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { chmodSync, existsSync, readFileSync } from 'node:fs';
3
+ import { replaceFileAtomically } from '../atomic-file.js';
4
+ import { lifecycleEventDigestBasis, validateFleetAuditPresentations, } from '../fleet-command-audit.js';
5
+ const DIGEST = /^[a-f0-9]{64}$/u;
6
+ const LIMIT = 5_000;
7
+ /** Durable lifecycle acceptance queue; plaintext is limited to validated concise presentations. */
8
+ export class FleetLifecycleOutbox {
9
+ path;
10
+ entries = [];
11
+ corruptReason;
12
+ constructor(path) {
13
+ this.path = path;
14
+ if (!existsSync(path))
15
+ return;
16
+ try {
17
+ const value = JSON.parse(readFileSync(path, 'utf8'));
18
+ if (value.version !== 1 || !Array.isArray(value.entries) || value.entries.length > LIMIT)
19
+ throw new Error('invalid lifecycle outbox');
20
+ const digests = new Set();
21
+ for (const entry of value.entries) {
22
+ validateFleetAuditPresentations([entry?.presentation]);
23
+ if (!DIGEST.test(entry.digest)
24
+ || !['pending', 'delivered', 'uncertain'].includes(entry.delivery)
25
+ || digests.has(entry.digest))
26
+ throw new Error('invalid lifecycle outbox entry');
27
+ digests.add(entry.digest);
28
+ }
29
+ this.entries = structuredClone(value.entries);
30
+ chmodSync(path, 0o600);
31
+ }
32
+ catch {
33
+ this.corruptReason = 'invalid persisted Fleet lifecycle outbox';
34
+ this.entries = [];
35
+ try {
36
+ chmodSync(path, 0o600);
37
+ }
38
+ catch { /* remain fail-closed */ }
39
+ }
40
+ }
41
+ integrity() {
42
+ return this.corruptReason ? { ok: false, error: this.corruptReason } : { ok: true };
43
+ }
44
+ enqueue(presentations) {
45
+ this.assertHealthy();
46
+ validateFleetAuditPresentations(presentations);
47
+ let changed = false;
48
+ for (const presentation of presentations) {
49
+ const digest = createHash('sha256').update(lifecycleEventDigestBasis(presentation)).digest('hex');
50
+ if (this.entries.some(entry => entry.digest === digest))
51
+ continue;
52
+ if (this.entries.length >= LIMIT) {
53
+ const settled = this.entries.findIndex(entry => entry.delivery !== 'pending');
54
+ if (settled < 0)
55
+ throw new Error(`Fleet lifecycle outbox is limited to ${LIMIT} pending events`);
56
+ this.entries.splice(settled, 1);
57
+ }
58
+ this.entries.push({ digest, presentation: structuredClone(presentation), delivery: 'pending' });
59
+ changed = true;
60
+ }
61
+ if (changed)
62
+ this.persist();
63
+ }
64
+ pending() {
65
+ this.assertHealthy();
66
+ return this.entries.filter(entry => entry.delivery === 'pending').map(entry => structuredClone(entry));
67
+ }
68
+ finish(digest, delivery) {
69
+ this.assertHealthy();
70
+ const entry = this.entries.find(candidate => candidate.digest === digest);
71
+ if (!entry)
72
+ throw new Error('unknown Fleet lifecycle outbox entry');
73
+ if (entry.delivery === delivery)
74
+ return;
75
+ if (entry.delivery !== 'pending')
76
+ throw new Error('conflicting Fleet lifecycle delivery state');
77
+ entry.delivery = delivery;
78
+ this.persist();
79
+ }
80
+ assertHealthy() {
81
+ if (this.corruptReason)
82
+ throw new Error(this.corruptReason);
83
+ }
84
+ persist() {
85
+ replaceFileAtomically(this.path, `${JSON.stringify({ version: 1, entries: this.entries })}\n`, 0o600);
86
+ }
87
+ }
@@ -23,6 +23,22 @@ export type OursRetrievedFiles = Res<'getFiles'>;
23
23
  export type OursRetrievedFile = OursRetrievedFiles['files'][number];
24
24
  export type OursHistoryFile = NonNullable<Res<'getFileInfo'>>;
25
25
  export type OursNotificationEvent = NotificationEvent;
26
+ export type OursJsonValue = null | boolean | number | string | OursJsonValue[] | {
27
+ [key: string]: OursJsonValue;
28
+ };
29
+ export interface OursCommandContext {
30
+ readonly sender_cid: string;
31
+ readonly sender_name: string;
32
+ readonly request_wire_id: string;
33
+ }
34
+ export interface OursRegisteredCommand {
35
+ name: string;
36
+ description?: string;
37
+ input_schema: {
38
+ [key: string]: OursJsonValue;
39
+ };
40
+ handler(argumentsValue: OursJsonValue, context: Readonly<OursCommandContext>): OursJsonValue | Promise<OursJsonValue>;
41
+ }
26
42
  export declare class OursWatchDeadlineError extends OursDaemonError {
27
43
  }
28
44
  /**
@@ -41,6 +57,8 @@ export interface OursOps {
41
57
  start(): Promise<void>;
42
58
  /** Bind this session's identity. Throws `BOUND_ELSEWHERE` when it is held live. */
43
59
  bindIdentity(name: string): Promise<void>;
60
+ /** Replace the bound identity's advertised typed-command catalog and handlers. */
61
+ registerCommands(commands: OursRegisteredCommand[]): Promise<void>;
44
62
  listContacts(): Promise<OursContactsView>;
45
63
  generateInvite(name?: string): Promise<OursInviteResult>;
46
64
  addContact(a: {
@@ -114,6 +132,7 @@ export declare class OursSdkClient implements OursOps {
114
132
  constructor(env?: Record<string, string>, log?: (line: string) => void, deps?: OursSdkClientDeps);
115
133
  start(): Promise<void>;
116
134
  bindIdentity(name: string): Promise<void>;
135
+ registerCommands(commands: OursRegisteredCommand[]): Promise<void>;
117
136
  listContacts(): Promise<OursContactsView>;
118
137
  generateInvite(name?: string): Promise<OursInviteResult>;
119
138
  addContact(a: {
@@ -140,6 +140,9 @@ export class OursSdkClient {
140
140
  // from an identity, it waits for the bounded handoff window and then fails.
141
141
  await this.ops().chooseIdentity({ name, force: false });
142
142
  }
143
+ async registerCommands(commands) {
144
+ await this.ops().registerCommands(commands);
145
+ }
143
146
  async listContacts() {
144
147
  return this.ops().listContacts();
145
148
  }
@@ -85,7 +85,7 @@ export function analyzeRolePermissions(role) {
85
85
  translation = { ...translation, capabilities: inspection.capabilities };
86
86
  }
87
87
  const conflicts = neutral.supported
88
- ? findConflicts(role, neutral.native, adapter.nativePermissionOverrides(role.harness_options))
88
+ ? findConflicts(role, neutral.native, adapter.nativePermissionOverrides(role.harness_options, role))
89
89
  : [];
90
90
  const floor = checkUnattendedFloor(translation.capabilities, requiredUnattendedFloor(role));
91
91
  const floorSeverity = role.permissions.unattended === 'deny' ? 'fail' : 'warn';
@@ -0,0 +1,42 @@
1
+ import { renameSync } from 'node:fs';
2
+ export declare const legacyStarterMigrationManifest: Readonly<{
3
+ schemaVersion: 1;
4
+ presetRevision: 2;
5
+ fingerprints: Readonly<Record<string, string>>;
6
+ }>;
7
+ export interface PresetMigrationResult {
8
+ write: boolean;
9
+ root: string;
10
+ stagingPath: string;
11
+ backupPath: string;
12
+ moves: Array<{
13
+ from: string;
14
+ to: string;
15
+ }>;
16
+ additions: string[];
17
+ }
18
+ export interface RoleDefaultMigrationResult {
19
+ write: boolean;
20
+ root: string;
21
+ stagingPath: string;
22
+ backupPath: string;
23
+ removals: string[];
24
+ replacements: string[];
25
+ additions: string[];
26
+ preserved: string[];
27
+ }
28
+ interface MigrationHooks {
29
+ rename?: typeof renameSync;
30
+ nonce?: string;
31
+ beforeLockClaim?: () => void;
32
+ beforeRoleDefaultPublish?: () => void;
33
+ }
34
+ /** Explicit adoption of exact known packaged role defaults; customized files are never changed. */
35
+ export declare function migratePackagedRoleDefaults(configuration: string, options?: {
36
+ write?: boolean;
37
+ }, hooks?: MigrationHooks): RoleDefaultMigrationResult;
38
+ /** Explicit safe migration of the exact revision-2 six-worker starter set. Dry-run unless write=true. */
39
+ export declare function migrateLegacyStarterPresets(configuration: string, options?: {
40
+ write?: boolean;
41
+ }, hooks?: MigrationHooks): PresetMigrationResult;
42
+ export {};