@ours.network/fleet 1.0.3 → 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.
- package/dist/application/role-command-service.d.ts +29 -1
- package/dist/application/role-command-service.js +41 -2
- package/dist/application/role-creation-service.d.ts +32 -1
- package/dist/application/role-creation-service.js +56 -10
- package/dist/application/role-removal-service.d.ts +19 -0
- package/dist/application/role-removal-service.js +13 -3
- package/dist/application/session-mutations.d.ts +7 -0
- package/dist/application/session-mutations.js +8 -0
- package/dist/application/task-room-service.d.ts +227 -0
- package/dist/application/task-room-service.js +529 -0
- package/dist/build-info.json +4 -4
- package/dist/cli.js +39 -15
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/owner-channel/channel.d.ts +5 -0
- package/dist/owner-channel/channel.js +143 -18
- package/dist/owner-channel/commands.d.ts +26 -1
- package/dist/owner-channel/commands.js +66 -128
- package/dist/rooms-tasks/cli.js +181 -462
- package/dist/runner.js +10 -33
- package/dist/session/control.d.ts +10 -1
- package/dist/session/control.js +22 -21
- package/dist/watchdog/query.d.ts +2 -0
- package/dist/watchdog/query.js +7 -3
- package/dist/web/server.js +2 -2
- package/package.json +1 -1
|
@@ -1,7 +1,32 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
39
|
+
if (options.journal !== false)
|
|
40
|
+
mkdirSync(this.journalDir, { recursive: true, mode: 0o700 });
|
|
37
41
|
this.identityProvisioner = options.identityProvisioner ?? daemonIdentityProvisioner();
|
|
38
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
58
|
-
const preview = this.
|
|
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
|
+
}
|