@ours.network/fleet 1.0.3 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -9
- package/dist/application/errors.d.ts +1 -1
- 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 +262 -0
- package/dist/application/task-room-service.js +571 -0
- package/dist/atomic-file.d.ts +5 -3
- package/dist/atomic-file.js +32 -8
- package/dist/build-info.json +4 -4
- package/dist/cli.js +39 -15
- package/dist/config.d.ts +6 -3
- package/dist/config.js +0 -17
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +26 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/owner-channel/attachments.d.ts +2 -4
- package/dist/owner-channel/attachments.js +6 -39
- package/dist/owner-channel/channel.d.ts +5 -0
- package/dist/owner-channel/channel.js +159 -21
- package/dist/owner-channel/commands.d.ts +43 -1
- package/dist/owner-channel/commands.js +137 -130
- package/dist/rooms-tasks/cli.js +301 -463
- package/dist/rooms-tasks/task-lists.d.ts +19 -0
- package/dist/rooms-tasks/task-lists.js +96 -0
- package/dist/rooms-tasks/task-state.d.ts +3 -0
- package/dist/rooms-tasks/task-state.js +281 -175
- package/dist/rooms-tasks/types.d.ts +11 -1
- 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/runtime.js +2 -0
- package/dist/web/server.d.ts +2 -0
- package/dist/web/server.js +88 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -400,7 +400,6 @@ roles:
|
|
|
400
400
|
max_file_bytes: 10485760 # 10 MiB
|
|
401
401
|
max_request_bytes: 20971520 # 20 MiB total, and >= max_file_bytes
|
|
402
402
|
retention_ms: 86400000 # stale crash cleanup; 1 minute..30 days
|
|
403
|
-
allowed_mime: [application/pdf, text/plain, image/png, audio/ogg]
|
|
404
403
|
model: claude-fable-5 # launch on a specific model (pass-through id; default: launcher default)
|
|
405
404
|
mission: one line
|
|
406
405
|
persona: | # operating contract (published as persona)
|
|
@@ -967,20 +966,25 @@ message plaintext in fleet state.
|
|
|
967
966
|
Inbound owner attachments use the same authenticated-CID and exact-wire routing
|
|
968
967
|
boundary. Fleet first calls the metadata-only `listIncomingFiles`, groups a
|
|
969
968
|
file-only wake or a same-sender reply-linked text caption, and checks the enabled,
|
|
970
|
-
count,
|
|
971
|
-
|
|
969
|
+
count, per-file size, and total-size policy before retrieving any bytes. MIME values,
|
|
970
|
+
extensions, file categories, and declared-versus-detected mismatches are metadata,
|
|
971
|
+
never rejection criteria. It then calls selective `getFiles` only for the admitted wire IDs. An
|
|
972
972
|
unauthorized sender is ignored without retrieval or reply. A rejected authorized
|
|
973
973
|
request receives a bounded reason correlated to its file wire.
|
|
974
974
|
|
|
975
975
|
Retrieved files must be regular, non-symlink paths whose byte count and SHA-256
|
|
976
|
-
match typed SDK metadata. Fleet
|
|
977
|
-
|
|
976
|
+
match typed SDK metadata. Fleet detects recognizable content only to report metadata,
|
|
977
|
+
sanitizes traversal/control characters from names, and copies each
|
|
978
978
|
file into a random request-scoped directory at mode 0700 with files at mode 0600.
|
|
979
979
|
The `[fleet-owner]` turn receives only bounded metadata, the private local paths,
|
|
980
980
|
and an explicit daemon transcription result for voice messages. Successful
|
|
981
981
|
transcripts are included; `failed` and `unavailable` states are stated plainly so
|
|
982
982
|
the agent must use the audio path rather than inventing text.
|
|
983
983
|
|
|
984
|
+
The legacy `attachments.allowed_mime` key is accepted and ignored so existing
|
|
985
|
+
configurations continue to load. It is omitted from resolved configuration and
|
|
986
|
+
cannot affect attachment admission.
|
|
987
|
+
|
|
984
988
|
Request files are removed after final delivery and stale directories are removed
|
|
985
989
|
after `retention_ms`. A bounded mode-0600 recovery journal stores only owner CID
|
|
986
990
|
and wire routing metadata—never filenames, paths, captions, transcripts, or file
|
|
@@ -1002,11 +1006,11 @@ Managed-agent file egress is a separate trust direction. It retains the same
|
|
|
1002
1006
|
metadata provenance checks, count/per-file/request byte caps, regular-file and
|
|
1003
1007
|
no-symlink reads, actual byte-count/SHA-256 verification, filename sanitization,
|
|
1004
1008
|
private staging, fixed owner routing, and bounded body-free recovery metadata.
|
|
1005
|
-
It deliberately does not consult `attachments.enabled
|
|
1009
|
+
It deliberately does not consult `attachments.enabled`; MIME is
|
|
1006
1010
|
still detected during byte validation, but a declared-versus-detected mismatch is
|
|
1007
|
-
not
|
|
1008
|
-
binaries are therefore routable only
|
|
1009
|
-
|
|
1011
|
+
not a deny gate in either direction. Markdown, HTML, octet-stream, unknown extensions,
|
|
1012
|
+
and normal binaries are therefore routable only across their authenticated direction.
|
|
1013
|
+
Each outbound file
|
|
1010
1014
|
gets a durable pre-send marker: a failed/ambiguous send becomes `uncertain` and is
|
|
1011
1015
|
not blindly retried, while files not yet attempted remain recoverable after restart.
|
|
1012
1016
|
Logs contain counts and byte totals, never filenames or raw bytes.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type FleetErrorCode = 'role_not_found' | 'capability_unavailable' | 'invalid_request' | 'offline' | 'control_unavailable' | 'timeout' | 'rejected' | 'backend_failure' | 'conflict' | 'stale_state' | 'prerequisite_unavailable' | 'idempotency_conflict' | 'rollback_incomplete' | 'launched_unconfirmed' | 'unauthorized' | 'forbidden' | 'rate_limited' | 'incompatible_version' | 'internal';
|
|
1
|
+
export type FleetErrorCode = 'role_not_found' | 'resource_not_found' | 'capability_unavailable' | 'invalid_request' | 'offline' | 'control_unavailable' | 'timeout' | 'rejected' | 'backend_failure' | 'conflict' | 'stale_state' | 'prerequisite_unavailable' | 'idempotency_conflict' | 'rollback_incomplete' | 'launched_unconfirmed' | 'unauthorized' | 'forbidden' | 'rate_limited' | 'incompatible_version' | 'internal';
|
|
2
2
|
export interface FleetErrorShape {
|
|
3
3
|
code: FleetErrorCode;
|
|
4
4
|
message: string;
|
|
@@ -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';
|