@ours.network/fleet 1.0.2 → 1.0.4

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 (36) hide show
  1. package/README.md +4 -4
  2. package/dist/application/role-command-service.d.ts +29 -1
  3. package/dist/application/role-command-service.js +41 -2
  4. package/dist/application/role-creation-service.d.ts +32 -1
  5. package/dist/application/role-creation-service.js +56 -10
  6. package/dist/application/role-removal-service.d.ts +19 -0
  7. package/dist/application/role-removal-service.js +13 -3
  8. package/dist/application/session-mutations.d.ts +7 -0
  9. package/dist/application/session-mutations.js +8 -0
  10. package/dist/application/task-room-service.d.ts +227 -0
  11. package/dist/application/task-room-service.js +529 -0
  12. package/dist/build-info.json +4 -4
  13. package/dist/cli.js +39 -15
  14. package/dist/docs.d.ts +1 -1
  15. package/dist/docs.js +7 -6
  16. package/dist/harness/codex-app-server-proxy.d.ts +1 -1
  17. package/dist/harness/codex-app-server-proxy.js +23 -9
  18. package/dist/harness/types.d.ts +4 -2
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/owner-channel/channel.d.ts +5 -0
  22. package/dist/owner-channel/channel.js +143 -18
  23. package/dist/owner-channel/commands.d.ts +26 -1
  24. package/dist/owner-channel/commands.js +66 -128
  25. package/dist/rooms-tasks/cli.js +181 -462
  26. package/dist/rooms-tasks/provision.js +71 -8
  27. package/dist/rooms-tasks/types.d.ts +2 -0
  28. package/dist/runner.js +19 -44
  29. package/dist/session/acp.d.ts +8 -3
  30. package/dist/session/acp.js +24 -8
  31. package/dist/session/control.d.ts +10 -1
  32. package/dist/session/control.js +22 -21
  33. package/dist/watchdog/query.d.ts +2 -0
  34. package/dist/watchdog/query.js +7 -3
  35. package/dist/web/server.js +2 -2
  36. package/package.json +1 -1
package/README.md CHANGED
@@ -454,10 +454,10 @@ role's `harness_options`, so a fleet can set common Codex permission/profile def
454
454
  and override individual keys per role. `monitor` merges the same way — a role block
455
455
  overrides `defaults.monitor` key-by-key.
456
456
 
457
- Every supervised role is a client of the operator-configured ours daemon. Fleet
458
- forces `OURS_AUTOSTART=0` in both tmux and ACP harness processes, after role environment
459
- overlays, so `env.OURS_AUTOSTART` cannot transfer shared daemon lifecycle ownership to an
460
- agent. Operators and explicit installer/setup flows remain responsible for starting it.
457
+ Every supervised role is a client of the operator-configured ours daemon. Fleet strips the
458
+ obsolete, presence-sensitive `OURS_AUTOSTART` variable from both tmux and ACP harness
459
+ processes. `ours-mcp proxy` is client-only and never starts a daemon; operators and explicit
460
+ installer/setup flows remain responsible for starting it.
461
461
 
462
462
  ### Rooms and tasks
463
463
 
@@ -1,7 +1,32 @@
1
- import { type OpsDeps } from '../ops.js';
1
+ import { loadConfig } from '../config.js';
2
+ import { restartRoles, type OpsDeps } from '../ops.js';
2
3
  import { FleetError } from './errors.js';
3
4
  import { RoleRepository } from './role-repository.js';
4
5
  import type { RoleStatus } from './types.js';
6
+ export interface RestartBatchPlan {
7
+ readonly roleIds: readonly string[];
8
+ readonly mode: 'keep' | 'fresh';
9
+ readonly config: ReturnType<typeof loadConfig>;
10
+ readonly configPath?: string;
11
+ }
12
+ /** Shared restart kernel. Receipt policy and surface rendering stay with callers. */
13
+ export declare class RoleLifecycleService {
14
+ private readonly options;
15
+ constructor(options: RoleCommandOptions);
16
+ prepareRestart(input: {
17
+ roleIds: string[];
18
+ mode: 'keep' | 'fresh';
19
+ config?: ReturnType<typeof loadConfig>;
20
+ }): Promise<RestartBatchPlan>;
21
+ executeRestart(plan: RestartBatchPlan): Promise<void>;
22
+ status(roleId: string): Promise<RoleStatus>;
23
+ }
24
+ /** Adapter-facing batch entry point used by CLI and detached Messenger workers. */
25
+ export declare function executeRestartBatch(lifecycle: Pick<RoleLifecycleService, 'prepareRestart' | 'executeRestart'>, input: {
26
+ roleIds: string[];
27
+ mode: 'keep' | 'fresh';
28
+ config?: ReturnType<typeof loadConfig>;
29
+ }): Promise<RestartBatchPlan>;
5
30
  export type LifecycleAction = 'start' | 'stop' | 'restart_resume' | 'restart_fresh';
6
31
  export interface CommandReceipt {
7
32
  actionId: string;
@@ -19,11 +44,14 @@ export interface RoleCommandOptions {
19
44
  configPath?: string;
20
45
  status(roleId: string): Promise<RoleStatus>;
21
46
  onProgress?: (receipt: CommandReceipt) => void;
47
+ restart?: typeof restartRoles;
48
+ lifecycle?: RoleLifecycleService;
22
49
  }
23
50
  export declare class RoleCommandService {
24
51
  private readonly options;
25
52
  private readonly receipts;
26
53
  private readonly locks;
54
+ readonly lifecycle: RoleLifecycleService;
27
55
  constructor(options: RoleCommandOptions);
28
56
  execute(input: {
29
57
  roleId: string;
@@ -2,12 +2,46 @@ import { randomUUID } from 'node:crypto';
2
2
  import { loadConfig } from '../config.js';
3
3
  import { up, restartRoles } from '../ops.js';
4
4
  import { FleetError, normalizeError } from './errors.js';
5
+ /** Shared restart kernel. Receipt policy and surface rendering stay with callers. */
6
+ export class RoleLifecycleService {
7
+ options;
8
+ constructor(options) {
9
+ this.options = options;
10
+ }
11
+ async prepareRestart(input) {
12
+ const config = input.config ?? loadConfig(this.options.configPath);
13
+ const selected = input.roleIds.length ? input.roleIds : config.roles.map(role => role.name);
14
+ for (const roleId of selected) {
15
+ const role = await this.options.repository.get(roleId);
16
+ if (!role)
17
+ throw new FleetError('role_not_found', `no such role '${roleId}'`);
18
+ if (role.lifetime !== 'permanent')
19
+ throw new FleetError('capability_unavailable', 'lifecycle is unavailable for temporary/orphan roles');
20
+ }
21
+ return Object.freeze({
22
+ roleIds: Object.freeze([...input.roleIds]), mode: input.mode, config,
23
+ ...(this.options.configPath ? { configPath: this.options.configPath } : {}),
24
+ });
25
+ }
26
+ async executeRestart(plan) {
27
+ await (this.options.restart ?? restartRoles)(plan.config, [...plan.roleIds], this.options.ops, plan.mode, plan.configPath);
28
+ }
29
+ status(roleId) { return this.options.status(roleId); }
30
+ }
31
+ /** Adapter-facing batch entry point used by CLI and detached Messenger workers. */
32
+ export async function executeRestartBatch(lifecycle, input) {
33
+ const plan = await lifecycle.prepareRestart(input);
34
+ await lifecycle.executeRestart(plan);
35
+ return plan;
36
+ }
5
37
  export class RoleCommandService {
6
38
  options;
7
39
  receipts = new Map();
8
40
  locks = new Map();
41
+ lifecycle;
9
42
  constructor(options) {
10
43
  this.options = options;
44
+ this.lifecycle = options.lifecycle ?? new RoleLifecycleService(options);
11
45
  }
12
46
  async execute(input) {
13
47
  const actionId = input.actionId ?? randomUUID();
@@ -42,8 +76,13 @@ export class RoleCommandService {
42
76
  await up(config, [receipt.roleId], this.options.ops, this.options.configPath);
43
77
  else if (receipt.action === 'stop')
44
78
  await this.options.ops.backend.stop(receipt.roleId);
45
- else
46
- await restartRoles(config, [receipt.roleId], this.options.ops, receipt.action === 'restart_fresh' ? 'fresh' : 'keep', this.options.configPath);
79
+ else {
80
+ const plan = await this.lifecycle.prepareRestart({
81
+ roleIds: [receipt.roleId],
82
+ mode: receipt.action === 'restart_fresh' ? 'fresh' : 'keep', config,
83
+ });
84
+ await this.lifecycle.executeRestart(plan);
85
+ }
47
86
  try {
48
87
  const status = await this.options.status(receipt.roleId);
49
88
  receipt.postcondition = { overall: status.overall, observedAt: status.observedAt };
@@ -1,8 +1,10 @@
1
1
  import { type CommonPermissions, type MonitorConfig, type MonitorInterrupt, type NotifyEventType } from '../config.js';
2
2
  import { type IdentityProvisioner } from '../creation.js';
3
- import { type SupervisorLauncher } from '../spawn.js';
3
+ import { spawnDryRun, type SpawnOpts, type SupervisorLauncher } from '../spawn.js';
4
4
  import type { OpsDeps } from '../ops.js';
5
+ import type { ResolvedRole } from '../config.js';
5
6
  import { FleetError } from './errors.js';
7
+ import { type ManagedFleetSpawnResult } from '../fleet-proxy.js';
6
8
  import { type HarnessModelCatalog, type HarnessModelOption } from './model-catalog.js';
7
9
  export interface CreateRoleSessionRequest {
8
10
  name: string;
@@ -122,7 +124,20 @@ export interface RoleCreationServiceOptions {
122
124
  probeReady?: (name: string, session: 'acp' | 'tmux') => Promise<'ready' | 'attention' | 'unknown'>;
123
125
  onProgress?: (action: CreationAction) => void;
124
126
  modelCatalogs?: Partial<Record<'codex' | 'claude-code', () => HarnessModelCatalog>>;
127
+ /** Direct/managed callers must not create or restore the web action journal. */
128
+ journal?: boolean;
125
129
  }
130
+ export type CreationPlan = {
131
+ origin: 'direct';
132
+ options: SpawnOpts;
133
+ preview: ReturnType<typeof spawnDryRun>;
134
+ } | {
135
+ origin: 'managed';
136
+ options: SpawnOpts;
137
+ preview: ReturnType<typeof spawnDryRun>;
138
+ caller: string;
139
+ inherited: string[];
140
+ };
126
141
  export declare class RoleCreationService {
127
142
  private readonly options;
128
143
  private readonly actions;
@@ -131,6 +146,22 @@ export declare class RoleCreationService {
131
146
  private readonly journalDir;
132
147
  private readonly identityProvisioner;
133
148
  constructor(options: RoleCreationServiceOptions);
149
+ previewSpawn(input: {
150
+ origin: 'direct';
151
+ options: SpawnOpts;
152
+ } | {
153
+ origin: 'managed';
154
+ caller: ResolvedRole;
155
+ options: SpawnOpts;
156
+ }): CreationPlan;
157
+ createDirect(options: SpawnOpts): Promise<{
158
+ plan: Extract<CreationPlan, {
159
+ origin: 'direct';
160
+ }>;
161
+ statePath: string;
162
+ }>;
163
+ createManaged(caller: ResolvedRole, requested: SpawnOpts): Promise<ManagedFleetSpawnResult>;
164
+ private launchSync;
134
165
  capabilities(): Promise<CreationCapabilities>;
135
166
  preview(input: CreateRoleSessionRequest): Promise<CreationPreview>;
136
167
  create(input: CreateRoleSessionRequest, previewHash: string, idempotencyKey: string, browserSession: string): Promise<CreationAction>;
@@ -5,8 +5,11 @@ import { loadConfig, NOTIFY_EVENT_TYPES, resolveMonitorConfig, resolveRoleModel,
5
5
  import { daemonIdentityProvisioner, } from '../creation.js';
6
6
  import { replaceFileAtomically } from '../atomic-file.js';
7
7
  import { stateRoot } from '../paths.js';
8
- import { buildRoleConfig, spawnPermanent, spawnTemp, validateSpawnOpts, } from '../spawn.js';
8
+ import { buildRoleConfig, spawnDryRun, spawnPermanent, spawnTemp, validateSpawnOpts, } from '../spawn.js';
9
9
  import { FleetError, normalizeError } from './errors.js';
10
+ import { inheritCallerSpawnDefaults } from '../fleet-proxy.js';
11
+ import { effectivePermissionMode } from '../permissions.js';
12
+ import { effectiveRoleModel } from '../model-env.js';
10
13
  import { claudeModelCatalog, codexModelCatalog, } from './model-catalog.js';
11
14
  const canonical = (value) => {
12
15
  if (Array.isArray(value))
@@ -33,9 +36,46 @@ export class RoleCreationService {
33
36
  constructor(options) {
34
37
  this.options = options;
35
38
  this.journalDir = options.journalDir ?? `${stateRoot()}/web/creation-actions`;
36
- mkdirSync(this.journalDir, { recursive: true, mode: 0o700 });
39
+ if (options.journal !== false)
40
+ mkdirSync(this.journalDir, { recursive: true, mode: 0o700 });
37
41
  this.identityProvisioner = options.identityProvisioner ?? daemonIdentityProvisioner();
38
- this.restore();
42
+ if (options.journal !== false)
43
+ this.restore();
44
+ }
45
+ previewSpawn(input) {
46
+ if (input.origin === 'direct') {
47
+ const options = { ...input.options, surface: 'cli' };
48
+ return { origin: 'direct', options, preview: spawnDryRun(options) };
49
+ }
50
+ const inherited = inheritCallerSpawnDefaults(input.caller, input.options, this.options.configPath);
51
+ const options = { ...inherited.options, surface: 'agent',
52
+ callerRole: input.caller.name };
53
+ return { origin: 'managed', options, caller: input.caller.name,
54
+ inherited: inherited.inherited, preview: spawnDryRun(options) };
55
+ }
56
+ async createDirect(options) {
57
+ const plan = this.previewSpawn({ origin: 'direct', options });
58
+ return { plan, statePath: await this.launchSync(plan.options) };
59
+ }
60
+ async createManaged(caller, requested) {
61
+ const plan = this.previewSpawn({ origin: 'managed', caller, options: requested });
62
+ const creationActionId = randomUUID();
63
+ plan.options.creationActionId = creationActionId;
64
+ const statePath = await this.launchSync(plan.options);
65
+ return {
66
+ caller: plan.caller, role: plan.options.name,
67
+ lifetime: plan.options.temp ? 'temporary' : 'permanent', statePath,
68
+ harness: plan.preview.resolvedRole.harness, session: plan.preview.resolvedRole.session,
69
+ ...(effectiveRoleModel(plan.preview.resolvedRole) ? { model: effectiveRoleModel(plan.preview.resolvedRole) } : {}),
70
+ monitor: { mode: plan.preview.resolvedRole.monitor.mode, interrupt: plan.preview.resolvedRole.monitor.interrupt },
71
+ permissionMode: effectivePermissionMode(plan.preview.resolvedRole), inherited: plan.inherited,
72
+ creationActionId,
73
+ };
74
+ }
75
+ launchSync(options, creation) {
76
+ return options.temp
77
+ ? spawnTemp(options, this.options.binPath, this.options.tempLauncher, creation)
78
+ : spawnPermanent(options, this.options.ops, creation);
39
79
  }
40
80
  async capabilities() {
41
81
  const reasons = [];
@@ -216,12 +256,7 @@ export class RoleCreationService {
216
256
  : { exists: name => this.identityProvisioner.exists(name) },
217
257
  onStage: (stage, evidence) => this.coreStage(action, stage, evidence),
218
258
  };
219
- if (preview.effective.lifetime === 'permanent') {
220
- await spawnPermanent(this.spawnOptions(preview.request, action.actionId), this.options.ops, creation);
221
- }
222
- else {
223
- await spawnTemp(this.spawnOptions(preview.request, action.actionId), this.options.binPath, this.options.tempLauncher, creation);
224
- }
259
+ await this.launchSync(this.spawnOptions(preview.request, action.actionId), creation);
225
260
  this.stage(action, 'launched', 'launch accepted; readiness not yet confirmed');
226
261
  this.stage(action, 'identity_bootstrap_pending', preview.effective.lifetime === 'permanent'
227
262
  ? 'fleet established the permanent identity; the harness must bind it from the generated briefing'
@@ -262,6 +297,16 @@ export class RoleCreationService {
262
297
  return 'unknown';
263
298
  }
264
299
  validate(input) {
300
+ const allowed = new Set([
301
+ 'name', 'harness', 'model', 'reasoningEffort', 'session', 'cwd', 'lifetime', 'mission',
302
+ 'coordinator', 'permissions', 'bio', 'persona', 'monitor', 'openAfterCreate',
303
+ 'highRiskAcknowledged', 'reuseExistingIdentityAcknowledged',
304
+ 'unverifiedIdentityAcknowledged',
305
+ ]);
306
+ const unsupported = Object.keys(input)
307
+ .filter(key => !allowed.has(key));
308
+ if (unsupported.length)
309
+ throw new FleetError('invalid_request', `unsupported web creation field: ${unsupported[0]}`);
265
310
  if (!ROLE_NAME_RE.test(input.name))
266
311
  throw new FleetError('invalid_request', 'invalid role name');
267
312
  if (!['codex', 'claude-code'].includes(input.harness))
@@ -316,7 +361,8 @@ export class RoleCreationService {
316
361
  }
317
362
  spawnOptions(request, creationActionId) {
318
363
  return {
319
- name: request.name, identity: request.name, harness: request.harness,
364
+ name: request.name, temp: request.lifetime === 'temporary',
365
+ identity: request.name, harness: request.harness,
320
366
  model: request.model, session: request.session, cwd: request.cwd,
321
367
  reasoningEffort: request.reasoningEffort,
322
368
  mission: request.mission, coordinator: request.coordinator,
@@ -19,7 +19,26 @@ export declare class RoleRemovalService {
19
19
  ops: OpsDeps;
20
20
  currentControlRole?: string;
21
21
  });
22
+ removeDirect(input: {
23
+ actor: {
24
+ kind: 'local_control';
25
+ surface: 'cli';
26
+ };
27
+ role: string;
28
+ }): Promise<void>;
29
+ previewWeb(requested: string): RoleRemovalPreview;
30
+ removeWeb(input: {
31
+ role: string;
32
+ confirmation?: string;
33
+ confirmed?: boolean;
34
+ coordinatorAcknowledged?: boolean;
35
+ }): Promise<RoleRemovalPreview & {
36
+ removed: true;
37
+ recoveryPath: string;
38
+ }>;
39
+ /** @deprecated Internal compatibility aliases; surface adapters use explicit policies above. */
22
40
  preview(requested: string): RoleRemovalPreview;
41
+ /** @deprecated Internal compatibility aliases; surface adapters use explicit policies above. */
23
42
  remove(input: {
24
43
  role: string;
25
44
  confirmation?: string;
@@ -9,7 +9,11 @@ export class RoleRemovalService {
9
9
  constructor(options) {
10
10
  this.options = options;
11
11
  }
12
- preview(requested) {
12
+ async removeDirect(input) {
13
+ const cfg = loadConfig(this.options.configPath);
14
+ await rmRole(cfg, input.role, this.options.ops);
15
+ }
16
+ previewWeb(requested) {
13
17
  if (!ROLE_NAME_RE.test(requested))
14
18
  throw new FleetError('invalid_request', 'invalid role name');
15
19
  const cfg = loadConfig(this.options.configPath);
@@ -54,8 +58,8 @@ export class RoleRemovalService {
54
58
  recovery: { available: true, detail: 'State and spawned configuration are copied to the local removal archive before deletion.' },
55
59
  };
56
60
  }
57
- async remove(input) {
58
- const preview = this.preview(input.role);
61
+ async removeWeb(input) {
62
+ const preview = this.previewWeb(input.role);
59
63
  if (preview.selfProtected)
60
64
  throw new FleetError('forbidden', 'the current control role cannot remove itself from its own web session');
61
65
  if (preview.coordinatorProtection && !input.coordinatorAcknowledged)
@@ -84,4 +88,10 @@ export class RoleRemovalService {
84
88
  }
85
89
  return { ...preview, removed: true, recoveryPath };
86
90
  }
91
+ /** @deprecated Internal compatibility aliases; surface adapters use explicit policies above. */
92
+ preview(requested) { return this.previewWeb(requested); }
93
+ /** @deprecated Internal compatibility aliases; surface adapters use explicit policies above. */
94
+ remove(input) {
95
+ return this.removeWeb(input);
96
+ }
87
97
  }
@@ -0,0 +1,7 @@
1
+ import type { InterruptOutcome, QueuedPrompt, SessionHandle, SubmitPromptOptions, TurnCancellationSource } from '../session/types.js';
2
+ /** Thin in-process session mutation kernel. Callers retain every policy decision. */
3
+ export declare const queueSessionPrompt: (session: SessionHandle, text: string, options?: SubmitPromptOptions) => Promise<QueuedPrompt>;
4
+ export declare const interruptSession: (session: SessionHandle, source: TurnCancellationSource) => Promise<InterruptOutcome>;
5
+ export declare const respondSessionPermission: (session: SessionHandle, permissionId: string, optionId: string) => boolean;
6
+ export type GenerationPermissionResult = 'accepted' | 'stale' | 'unavailable';
7
+ export declare const respondSessionPermissionV2: (session: SessionHandle, permissionId: string, optionId: string, sessionGeneration: string) => GenerationPermissionResult;
@@ -0,0 +1,8 @@
1
+ import { interruptOutcome } from '../session/types.js';
2
+ /** Thin in-process session mutation kernel. Callers retain every policy decision. */
3
+ export const queueSessionPrompt = (session, text, options) => session.queuePrompt(text, options);
4
+ export const interruptSession = async (session, source) => interruptOutcome(await session.interrupt(source));
5
+ export const respondSessionPermission = (session, permissionId, optionId) => session.respondPermission(permissionId, optionId);
6
+ export const respondSessionPermissionV2 = (session, permissionId, optionId, sessionGeneration) => session.respondPermissionV2
7
+ ? session.respondPermissionV2(permissionId, optionId, sessionGeneration)
8
+ : 'unavailable';
@@ -0,0 +1,227 @@
1
+ import { type FleetConfig } from '../config.js';
2
+ import { type CoworkAdapter } from '../rooms-tasks/cowork-adapter.js';
3
+ import { provisionMembers } from '../rooms-tasks/provision.js';
4
+ import type { RoomOrchestrationRecord, TaskOrigin, TaskOutcome, TaskRecord, TaskState } from '../rooms-tasks/types.js';
5
+ export type TaskRoomActor = {
6
+ kind: 'local_control';
7
+ surface: 'cli';
8
+ } | {
9
+ kind: 'authenticated_owner';
10
+ surface: 'messenger';
11
+ cid: string;
12
+ } | {
13
+ kind: 'internal_worker';
14
+ surface: 'cli';
15
+ };
16
+ export interface TaskSettlementPlan {
17
+ task: TaskRecord;
18
+ settlementRequired: boolean;
19
+ }
20
+ export type TaskRecoveryIssue = {
21
+ code: 'terminal_pending' | 'waiting_cowork' | 'waiting_owner_invite' | 'owner_cid_mismatch' | 'waiting_seats' | 'provisioning_resumed';
22
+ } | {
23
+ code: 'member_failed';
24
+ stepIndex: number;
25
+ } | {
26
+ code: 'resume_failed';
27
+ error: string;
28
+ };
29
+ export interface TaskRecoveryResult {
30
+ kind: 'provisioning_resumed' | 'provisioning_resume_failed' | 'provisioning_non_resumable' | 'terminal' | 'no_op';
31
+ task: TaskRecord;
32
+ room: RoomOrchestrationRecord | undefined;
33
+ issues: TaskRecoveryIssue[];
34
+ reason?: 'missing_room' | 'missing_durable_template' | 'non_resumable_phase';
35
+ }
36
+ export type TaskRecoveryBegin = {
37
+ kind: 'terminal_worker_required';
38
+ taskId: string;
39
+ } | {
40
+ kind: 'final';
41
+ result: TaskRecoveryResult;
42
+ };
43
+ export declare class TaskRoomApplicationError extends Error {
44
+ readonly code: 'template_not_found' | 'task_template_drift' | 'template_mismatch' | 'task_terminal' | 'task_terminal_already' | 'task_non_resumable' | 'room_not_found' | 'room_record_not_found';
45
+ readonly fields: Readonly<Record<string, string>>;
46
+ constructor(code: 'template_not_found' | 'task_template_drift' | 'template_mismatch' | 'task_terminal' | 'task_terminal_already' | 'task_non_resumable' | 'room_not_found' | 'room_record_not_found', message: string, fields?: Readonly<Record<string, string>>);
47
+ }
48
+ export interface CreateTaskRequest {
49
+ actor: TaskRoomActor;
50
+ title: string;
51
+ brief?: string;
52
+ briefFile?: string;
53
+ template?: string;
54
+ backlog?: boolean;
55
+ noRoom?: boolean;
56
+ idempotencyKey?: string;
57
+ origin: TaskOrigin;
58
+ }
59
+ export interface CreateRoomRequest {
60
+ actor: TaskRoomActor;
61
+ name: string;
62
+ template?: string;
63
+ goal?: string;
64
+ brief?: string;
65
+ briefFile?: string;
66
+ }
67
+ export interface TaskRoomServiceDeps {
68
+ loadConfiguration?(path?: string): FleetConfig;
69
+ cowork?(config: FleetConfig): CoworkAdapter;
70
+ binPath?(): string;
71
+ provisionMembers?: typeof provisionMembers;
72
+ }
73
+ /** Exact extraction of the previously CLI-owned task create/start behavior. */
74
+ export declare class TaskRoomApplicationService {
75
+ private readonly configurationPath?;
76
+ private readonly deps;
77
+ private recovery?;
78
+ constructor(configurationPath?: string | undefined, deps?: TaskRoomServiceDeps);
79
+ createTask(request: CreateTaskRequest): Promise<TaskRecord>;
80
+ createRoom(request: CreateRoomRequest): Promise<RoomOrchestrationRecord>;
81
+ startTask(input: {
82
+ actor: TaskRoomActor;
83
+ taskId: string;
84
+ }): Promise<TaskRecord>;
85
+ listTasks(filter?: {
86
+ state?: TaskState | TaskState[];
87
+ }): TaskRecord[];
88
+ getTask(taskId: string): {
89
+ task: TaskRecord;
90
+ orchestration: RoomOrchestrationRecord | undefined;
91
+ };
92
+ blockTask(input: {
93
+ actor: TaskRoomActor;
94
+ taskId: string;
95
+ reason: string;
96
+ }): TaskRecord;
97
+ unblockTask(input: {
98
+ actor: TaskRoomActor;
99
+ taskId: string;
100
+ }): TaskRecord;
101
+ reviewTask(input: {
102
+ actor: TaskRoomActor;
103
+ taskId: string;
104
+ }): TaskRecord;
105
+ deleteTask(input: {
106
+ actor: TaskRoomActor;
107
+ taskId: string;
108
+ }): boolean;
109
+ completeTask(input: {
110
+ actor: TaskRoomActor;
111
+ taskId: string;
112
+ outcome?: TaskOutcome;
113
+ }): Promise<TaskSettlementPlan>;
114
+ cancelTask(input: {
115
+ actor: TaskRoomActor;
116
+ taskId: string;
117
+ }): Promise<TaskSettlementPlan>;
118
+ settleTask(input: {
119
+ actor: {
120
+ kind: 'internal_worker';
121
+ surface: 'cli';
122
+ };
123
+ taskId: string;
124
+ }): Promise<TaskRecord>;
125
+ recordSettlementError(input: {
126
+ actor: TaskRoomActor;
127
+ taskId: string;
128
+ error: string;
129
+ recoveryHint: string;
130
+ }): Promise<TaskRecord>;
131
+ beginTaskRecovery(input: {
132
+ actor: TaskRoomActor;
133
+ taskId: string;
134
+ }): Promise<TaskRecoveryBegin>;
135
+ continueTaskRecovery(input: {
136
+ actor: TaskRoomActor;
137
+ taskId: string;
138
+ terminalTimedOut: boolean;
139
+ }): Promise<TaskRecoveryResult>;
140
+ private acceptTerminal;
141
+ listTemplates(): import("../rooms-tasks/types.js").TemplateDefinition[];
142
+ getTemplate(name: string): {
143
+ content_hash: string;
144
+ name: string;
145
+ version: number;
146
+ description: string;
147
+ builtin?: boolean;
148
+ room?: import("../rooms-tasks/types.js").TemplateRoomConfig;
149
+ contract?: string;
150
+ members: import("../rooms-tasks/types.js").TemplateMemberSlot[];
151
+ };
152
+ validateTemplates(): {
153
+ template: string;
154
+ issues: string[];
155
+ }[];
156
+ listRooms(filter?: {
157
+ state?: 'active' | 'provisioning';
158
+ }): Promise<{
159
+ orchestration: RoomOrchestrationRecord | null;
160
+ room_id: string;
161
+ identity_name: string;
162
+ identity_cid: string;
163
+ room_name: string;
164
+ state: "provisioning" | "active" | "closing" | "closed";
165
+ seats: import("../rooms-tasks/cowork-adapter.js").CoworkSeatInfo[];
166
+ goal?: string;
167
+ briefing?: string;
168
+ role_briefings: Record<string, import("../rooms-tasks/cowork-adapter.js").CoworkRoleBriefingInfo>;
169
+ }[]>;
170
+ getRoomDetail(id: string): Promise<{
171
+ room: import("../rooms-tasks/cowork-adapter.js").CoworkRoomInfo;
172
+ orchestration: RoomOrchestrationRecord | undefined;
173
+ }>;
174
+ getRoomMembers(id: string): Promise<{
175
+ room: import("../rooms-tasks/cowork-adapter.js").CoworkRoomInfo;
176
+ orchestration: RoomOrchestrationRecord | undefined;
177
+ members: import("../rooms-tasks/cowork-adapter.js").CoworkSeatInfo[];
178
+ }>;
179
+ requestRoomDeletion(input: {
180
+ actor: TaskRoomActor;
181
+ roomId: string;
182
+ }): Promise<{
183
+ room: RoomOrchestrationRecord;
184
+ settlementRequired: true;
185
+ }>;
186
+ settleRoomDeletion(input: {
187
+ actor: {
188
+ kind: 'internal_worker';
189
+ surface: 'cli';
190
+ };
191
+ roomId: string;
192
+ }): Promise<import("../rooms-tasks/close.js").ManagedRoomDeleteResult>;
193
+ recordRoomSettlementError(input: {
194
+ actor: TaskRoomActor;
195
+ roomId: string;
196
+ error: string;
197
+ recoveryHint: string;
198
+ }): Promise<RoomOrchestrationRecord>;
199
+ recoverRoom(input: {
200
+ actor: TaskRoomActor;
201
+ roomId: string;
202
+ }): Promise<{
203
+ kind: 'deletion_worker_required';
204
+ roomId: string;
205
+ } | {
206
+ kind: 'recovered' | 'provisioning_resumed' | 'provisioning_resume_failed';
207
+ room: Awaited<ReturnType<CoworkAdapter['recoverRoom']>>;
208
+ orchestration: RoomOrchestrationRecord | undefined;
209
+ issues: string[];
210
+ }>;
211
+ finishTask(input: {
212
+ actor: TaskRoomActor;
213
+ taskId: string;
214
+ outcome?: TaskOutcome;
215
+ }): Promise<TaskSettlementPlan>;
216
+ ensureTaskWork(input: {
217
+ actor: TaskRoomActor;
218
+ taskId: string;
219
+ template?: string;
220
+ }): Promise<{
221
+ task: TaskRecord;
222
+ status: 'ready' | 'already_active';
223
+ }>;
224
+ private createTemplate;
225
+ private existingTemplate;
226
+ private provisionRoom;
227
+ }