@ours.network/fleet 2.2.6 → 2.3.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.
- package/README.md +1 -1
- package/dist/application/harness-capabilities.d.ts +60 -0
- package/dist/application/harness-capabilities.js +96 -0
- package/dist/application/model-catalog.d.ts +17 -0
- package/dist/application/model-catalog.js +32 -9
- package/dist/application/preset-provenance.d.ts +1 -1
- package/dist/application/preset-provenance.js +18 -3
- package/dist/application/role-creation-service.d.ts +3 -1
- package/dist/application/role-creation-service.js +4 -1
- package/dist/application/task-room-service.d.ts +12 -1
- package/dist/application/task-room-service.js +39 -16
- package/dist/atomic-file.d.ts +2 -0
- package/dist/atomic-file.js +2 -1
- package/dist/build-info.json +4 -4
- package/dist/config.d.ts +6 -0
- package/dist/config.js +6 -3
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +37 -0
- package/dist/harness/claude-code.js +7 -0
- package/dist/harness/codex.js +7 -0
- package/dist/harness/hermes.js +2 -0
- package/dist/harness/types.d.ts +20 -0
- package/dist/init-wizard.d.ts +19 -12
- package/dist/init-wizard.js +122 -104
- package/dist/owner-channel/commands.js +25 -12
- package/dist/reports/tasks.js +4 -3
- package/dist/rooms-tasks/add-member.d.ts +15 -1
- package/dist/rooms-tasks/add-member.js +44 -10
- package/dist/rooms-tasks/cli.js +79 -5
- package/dist/rooms-tasks/config.js +11 -0
- package/dist/rooms-tasks/layout-member-retirement.d.ts +15 -0
- package/dist/rooms-tasks/layout-member-retirement.js +26 -1
- package/dist/rooms-tasks/layout-service.d.ts +2 -1
- package/dist/rooms-tasks/layout-service.js +10 -4
- package/dist/rooms-tasks/layout.d.ts +4 -2
- package/dist/rooms-tasks/layout.js +21 -4
- package/dist/rooms-tasks/member-naming.d.ts +43 -0
- package/dist/rooms-tasks/member-naming.js +186 -0
- package/dist/rooms-tasks/member-overrides.js +1 -0
- package/dist/rooms-tasks/mission-keys.d.ts +83 -0
- package/dist/rooms-tasks/mission-keys.js +250 -0
- package/dist/rooms-tasks/provision.d.ts +4 -0
- package/dist/rooms-tasks/provision.js +37 -3
- package/dist/rooms-tasks/task-lists.d.ts +18 -2
- package/dist/rooms-tasks/task-lists.js +60 -5
- package/dist/rooms-tasks/task-room-name.d.ts +7 -2
- package/dist/rooms-tasks/task-room-name.js +13 -4
- package/dist/rooms-tasks/task-state.d.ts +40 -0
- package/dist/rooms-tasks/task-state.js +288 -45
- package/dist/rooms-tasks/types.d.ts +35 -1
- package/dist/rooms-tasks/types.js +1 -1
- package/dist/web/runtime.js +4 -1
- package/dist/web/server.js +116 -50
- package/package.json +1 -1
- package/presets/README.md +38 -18
- package/presets/brain-catalog.json +19 -19
- package/presets/legacy/generated-brains-rev4.json +497 -0
- package/presets/manifest.json +1 -1
- package/presets/fleet/brains/claude-fable-5-1-high.yaml +0 -5
- package/presets/fleet/brains/claude-fable-5-1-low.yaml +0 -5
- package/presets/fleet/brains/claude-fable-5-1-max.yaml +0 -5
- package/presets/fleet/brains/claude-fable-5-1-medium.yaml +0 -5
- package/presets/fleet/brains/claude-fable-5-1-xhigh.yaml +0 -5
- package/presets/fleet/brains/claude-fable-5-high.yaml +0 -5
- package/presets/fleet/brains/claude-fable-5-low.yaml +0 -5
- package/presets/fleet/brains/claude-fable-5-max.yaml +0 -5
- package/presets/fleet/brains/claude-fable-5-medium.yaml +0 -5
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +0 -5
- package/presets/fleet/brains/claude-haiku-4-5-20251001-default.yaml +0 -4
- package/presets/fleet/brains/claude-opus-5-5-high.yaml +0 -5
- package/presets/fleet/brains/claude-opus-5-5-low.yaml +0 -5
- package/presets/fleet/brains/claude-opus-5-5-max.yaml +0 -5
- package/presets/fleet/brains/claude-opus-5-5-medium.yaml +0 -5
- package/presets/fleet/brains/claude-opus-5-5-xhigh.yaml +0 -5
- package/presets/fleet/brains/claude-opus-5-high.yaml +0 -5
- package/presets/fleet/brains/claude-opus-5-low.yaml +0 -5
- package/presets/fleet/brains/claude-opus-5-max.yaml +0 -5
- package/presets/fleet/brains/claude-opus-5-medium.yaml +0 -5
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +0 -5
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +0 -5
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +0 -5
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +0 -5
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +0 -5
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-1-sol-high.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-1-sol-low.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-1-sol-max.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-1-sol-medium.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-1-sol-ultra.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-1-sol-xhigh.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-luna-high.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-luna-low.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-luna-max.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-luna-medium.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-luna-xhigh.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-sol-high.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-sol-low.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-sol-max.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-sol-medium.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-sol-ultra.yaml +0 -5
- package/presets/fleet/brains/codex-gpt-6-sol-xhigh.yaml +0 -5
|
@@ -174,6 +174,15 @@ export interface TaskRecord {
|
|
|
174
174
|
notification_manifest?: import('./notification-manifest.js').TaskNotificationManifest;
|
|
175
175
|
workspace?: import('./workspace.js').OwnedWorkspace;
|
|
176
176
|
task_id: string;
|
|
177
|
+
/**
|
|
178
|
+
* Immutable human mission key (`MSN-1`), issued once from a durable
|
|
179
|
+
* per-prefix high-water mark. Absent only before the one-time migration.
|
|
180
|
+
*/
|
|
181
|
+
key?: string;
|
|
182
|
+
/** Set when the key was backfilled for a task that existed before mission keys. */
|
|
183
|
+
key_from_migration?: true;
|
|
184
|
+
/** Durable per-mission agent name allocations; absent until first allocation or migration. */
|
|
185
|
+
member_naming?: MemberNaming;
|
|
177
186
|
/** Stable organizational list identifier. Missing legacy values mean `default`. */
|
|
178
187
|
list_id: string;
|
|
179
188
|
/** Derived presentation field; repositories must never persist it. */
|
|
@@ -211,9 +220,30 @@ export interface TaskRecord {
|
|
|
211
220
|
terminal_intent?: TaskTerminalIntent;
|
|
212
221
|
deletion?: TaskDeletionIntent;
|
|
213
222
|
}
|
|
223
|
+
/**
|
|
224
|
+
* Role-scoped agent names: `<KEY>-<Stem>` for the first member of a stem,
|
|
225
|
+
* `<KEY>-<Stem>-<n>` (n >= 2) after it. Counters only grow; an allocation is
|
|
226
|
+
* reused by provisioning, recovery and restart, never by a replacement.
|
|
227
|
+
*/
|
|
228
|
+
export interface MemberNameAllocation {
|
|
229
|
+
name: string;
|
|
230
|
+
/** Counter bucket: the case-folded truncated stem. Absent on frozen legacy names. */
|
|
231
|
+
bucket?: string;
|
|
232
|
+
ordinal?: number;
|
|
233
|
+
label_source?: 'role_override' | 'template_ref' | 'room_role' | 'participant_key' | 'slot' | 'fallback';
|
|
234
|
+
/** A historical name kept exactly as launched before mission keys existed. */
|
|
235
|
+
frozen?: true;
|
|
236
|
+
}
|
|
237
|
+
export interface MemberNaming {
|
|
238
|
+
version: 2;
|
|
239
|
+
counters: Record<string, number>;
|
|
240
|
+
allocations: Record<string, MemberNameAllocation>;
|
|
241
|
+
}
|
|
214
242
|
export interface TaskListRecord {
|
|
215
243
|
list_id: string;
|
|
216
244
|
name: string;
|
|
245
|
+
/** Prefix for new mission keys; absent only before the one-time key migration. */
|
|
246
|
+
prefix?: string;
|
|
217
247
|
built_in: boolean;
|
|
218
248
|
created_at: string;
|
|
219
249
|
}
|
|
@@ -387,6 +417,8 @@ export interface TemplateSnapshotMember extends TemplateMemberSlot {
|
|
|
387
417
|
id: string;
|
|
388
418
|
hash: string;
|
|
389
419
|
};
|
|
420
|
+
/** Named Role of the member's Agent Template, sealed at plan time for agent naming. */
|
|
421
|
+
role_label?: string;
|
|
390
422
|
}
|
|
391
423
|
export interface TemplateSnapshot extends Omit<TemplateDefinition, 'members'> {
|
|
392
424
|
members: TemplateSnapshotMember[];
|
|
@@ -418,13 +450,15 @@ export interface TasksConfig {
|
|
|
418
450
|
create_mode?: 'start' | 'backlog';
|
|
419
451
|
close_room_on_done?: boolean;
|
|
420
452
|
retain_completed_for?: string;
|
|
453
|
+
/** Default list prefix (default MSN); may change only before the first mission key. */
|
|
454
|
+
default_prefix?: string;
|
|
421
455
|
}
|
|
422
456
|
export type RoomTemplatesConfig = Record<string, TemplateDefinition>;
|
|
423
457
|
export declare const ROOMS_KEYS: readonly ["cowork", "owner", "defaults"];
|
|
424
458
|
export declare const ROOMS_OWNER_KEYS: readonly ["provider", "public_invite", "public_invite_file", "expected_cid", "role"];
|
|
425
459
|
export declare const ROOMS_COWORK_KEYS: readonly ["config"];
|
|
426
460
|
export declare const ROOMS_DEFAULTS_KEYS: readonly ["template", "attach_owner", "close_when_task_done"];
|
|
427
|
-
export declare const TASKS_KEYS: readonly ["default_room_template", "create_mode", "close_room_on_done", "retain_completed_for"];
|
|
461
|
+
export declare const TASKS_KEYS: readonly ["default_room_template", "create_mode", "close_room_on_done", "retain_completed_for", "default_prefix"];
|
|
428
462
|
export declare const TEMPLATE_KEYS: readonly ["version", "description", "room", "contract", "members", "override_builtin"];
|
|
429
463
|
export declare const TEMPLATE_ROOM_KEYS: readonly ["quiet_membership", "anonymous"];
|
|
430
464
|
/** `agent` is accepted only so validation can emit its actionable migration error. */
|
|
@@ -23,7 +23,7 @@ export const ROOMS_KEYS = ['cowork', 'owner', 'defaults'];
|
|
|
23
23
|
export const ROOMS_OWNER_KEYS = ['provider', 'public_invite', 'public_invite_file', 'expected_cid', 'role'];
|
|
24
24
|
export const ROOMS_COWORK_KEYS = ['config'];
|
|
25
25
|
export const ROOMS_DEFAULTS_KEYS = ['template', 'attach_owner', 'close_when_task_done'];
|
|
26
|
-
export const TASKS_KEYS = ['default_room_template', 'create_mode', 'close_room_on_done', 'retain_completed_for'];
|
|
26
|
+
export const TASKS_KEYS = ['default_room_template', 'create_mode', 'close_room_on_done', 'retain_completed_for', 'default_prefix'];
|
|
27
27
|
export const TEMPLATE_KEYS = ['version', 'description', 'room', 'contract', 'members', 'override_builtin'];
|
|
28
28
|
export const TEMPLATE_ROOM_KEYS = ['quiet_membership', 'anonymous'];
|
|
29
29
|
/** `agent` is accepted only so validation can emit its actionable migration error. */
|
package/dist/web/runtime.js
CHANGED
|
@@ -193,13 +193,16 @@ export async function startWebConsole(options) {
|
|
|
193
193
|
const topologyPromote = new TopologyPromoteService({
|
|
194
194
|
drafts: topologyDrafts, configuration, topology: readTopology,
|
|
195
195
|
});
|
|
196
|
+
const taskRooms = new TaskRoomApplicationService(options.configPath, { binPath: () => options.binPath });
|
|
197
|
+
// One-time, lock-guarded mission key migration before serving; failure leaves it to the next mutation.
|
|
198
|
+
await taskRooms.prepareMissionKeys().catch(error => console.warn(`mission key migration deferred: ${error.message}`));
|
|
196
199
|
let server;
|
|
197
200
|
try {
|
|
198
201
|
server = await buildWebServer({
|
|
199
202
|
query, repository, logs, commands, creation, removal, audit, events, watchdogs, configuration, observeRole,
|
|
200
203
|
resourceScope: { configPath: configuration.path, subscribeMutations: listener => events.subscribeResources(listener) },
|
|
201
204
|
invalidateResourceCaches: () => { watchdogConfigProvider.invalidate(); watchdogFindings.invalidate(); },
|
|
202
|
-
taskRooms
|
|
205
|
+
taskRooms,
|
|
203
206
|
roomLayouts: new RoomLayoutDefinitions(options.configPath),
|
|
204
207
|
layoutBindings: new LayoutBindingGrants(new RoomLayoutService(options.configPath).supervisor()),
|
|
205
208
|
presetProvenance: new PresetProvenance(options.configPath),
|
package/dist/web/server.js
CHANGED
|
@@ -7,15 +7,13 @@ import { registerConversationStream } from './conversation-stream.js';
|
|
|
7
7
|
import { conversationHasAttachment } from '../application/conversation-history.js';
|
|
8
8
|
import { readDeliveredFile } from '../file-delivery/store.js';
|
|
9
9
|
import { WorkspaceDeviceAuthError } from './workspace-devices.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { validateCatalog } from '../init-wizard.js';
|
|
10
|
+
import { PURPOSE_BRAINS, PurposeBrainConflictError } from '../init-wizard.js';
|
|
11
|
+
import { harnessCapabilities, validateBrainChoice } from '../application/harness-capabilities.js';
|
|
13
12
|
import { getAdapter } from '../harness/registry.js';
|
|
14
13
|
import { storeAgentAttachment, attachmentPrompt, MAX_ATTACHMENT_BODY, readAgentAttachment, prepareAttachmentPresentation, presentAttachmentEvent } from './agent-attachments.js';
|
|
15
14
|
import { readChatIdle } from '../temp-idle.js';
|
|
16
15
|
import { agentDir } from '../paths.js';
|
|
17
16
|
import { SupervisorOursTools } from '../application/supervisor-ours-tools.js';
|
|
18
|
-
import { join } from 'node:path';
|
|
19
17
|
import Fastify from 'fastify';
|
|
20
18
|
import fastifyWebsocket from '@fastify/websocket';
|
|
21
19
|
import { createHmac, randomBytes } from 'node:crypto';
|
|
@@ -26,7 +24,8 @@ import { WebAuth } from './auth.js';
|
|
|
26
24
|
import { FleetEventBus } from './events.js';
|
|
27
25
|
import { ROLE_NAME_RE } from '../config.js';
|
|
28
26
|
import { TaskListError } from '../rooms-tasks/task-lists.js';
|
|
29
|
-
import { acknowledgeTaskProgress, acknowledgeTaskReview, contributionUnread, TaskStateError } from '../rooms-tasks/task-state.js';
|
|
27
|
+
import { acknowledgeTaskProgress, acknowledgeTaskReview, contributionUnread, resolveTaskRef, TaskStateError } from '../rooms-tasks/task-state.js';
|
|
28
|
+
import { MissionKeyError } from '../rooms-tasks/mission-keys.js';
|
|
30
29
|
import { appendTaskProgress, getTaskReview, taskProgressSnapshot, publicProgressEntry, publicReview, readTaskReviewArtifact, setTaskReviewMarkdown, taskContributionSummary, TaskReviewError, } from '../rooms-tasks/task-review.js';
|
|
31
30
|
import { isProvider } from '../subscriptions/store.js';
|
|
32
31
|
const ONBOARDING_WORK = ['coordination', 'development', 'review'];
|
|
@@ -214,12 +213,12 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
214
213
|
return { harness, ok: false, checks: [{ name: 'availability', ok: false, detail: 'Harness checks unavailable' }] };
|
|
215
214
|
}
|
|
216
215
|
}));
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
const payload = { harnesses, catalog
|
|
216
|
+
// The catalog is each adapter's maintained models, narrowed to what the active Codex account offers.
|
|
217
|
+
const capabilities = await harnessCapabilities({ harnesses: ['codex', 'claude-code'], codexModels: services.codexModels, checkReadiness: false });
|
|
218
|
+
const codex = capabilities.harnesses.find(item => item.harness === 'codex');
|
|
219
|
+
const catalog = capabilities.harnesses.flatMap(item => item.models.filter(model => model.available && model.efforts !== null)
|
|
220
|
+
.map(model => ({ harness: item.harness, session: 'acp', model: model.id, label: model.label, efforts: model.efforts })));
|
|
221
|
+
const payload = { harnesses, catalog, ...(codex?.discovery ? { codexModels: codex.discovery } : {}), providers: services.subscriptions ? await services.subscriptions.list() : [], coordinator: 'FleetCoordinator', configured: onboardingConfigured() };
|
|
223
222
|
if (revision === services.configuration?.read().revision) {
|
|
224
223
|
onboardingObserved = { owner: browser.id, revision, payload };
|
|
225
224
|
events.publish('onboarding.changed', {});
|
|
@@ -244,15 +243,25 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
244
243
|
if (!services.configuration || !services.onboardingSetup)
|
|
245
244
|
throw new FleetError('capability_unavailable', 'first-time Fleet setup is unavailable');
|
|
246
245
|
const requested = request.body?.models;
|
|
247
|
-
const
|
|
248
|
-
const supported = validateCatalog(JSON.parse(readFileSync(join(packagedPresetRoot(), 'brain-catalog.json'), 'utf8'))).models
|
|
249
|
-
.filter(model => model.harness !== 'codex' || !codexModels || (codexModels.state === 'known' && codexModels.models.includes(model.model)));
|
|
246
|
+
const capabilities = await harnessCapabilities({ harnesses: ['codex', 'claude-code'], codexModels: services.codexModels, checkReadiness: false });
|
|
250
247
|
const models = {};
|
|
251
248
|
for (const work of ONBOARDING_WORK) {
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
249
|
+
const choice = requested?.[work];
|
|
250
|
+
const label = PURPOSE_BRAINS[work].label;
|
|
251
|
+
if (!choice || typeof choice !== 'object' || !('effort' in choice))
|
|
252
|
+
throw new FleetError('invalid_request', `choose a harness, model and effort for the ${label} Brain`);
|
|
253
|
+
const { harness, model, effort } = choice;
|
|
254
|
+
if (typeof harness !== 'string' || typeof model !== 'string' || (effort !== null && typeof effort !== 'string'))
|
|
255
|
+
throw new FleetError('invalid_request', `choose a harness, model and effort for the ${label} Brain`);
|
|
256
|
+
const check = validateBrainChoice(capabilities, { harness, session: 'acp', model, effort });
|
|
257
|
+
// Setup offers the adapters' maintained models only, each with an explicit effort when it has one.
|
|
258
|
+
if (!check.ok)
|
|
259
|
+
throw new FleetError('invalid_request', `${label} Brain: ${check.message}`);
|
|
260
|
+
if (!check.model || check.model.source !== 'adapter')
|
|
261
|
+
throw new FleetError('invalid_request', `${label} Brain: choose a supported model`);
|
|
262
|
+
if (check.model.efforts?.length && effort === null)
|
|
263
|
+
throw new FleetError('invalid_request', `${label} Brain: choose an effort for ${check.model.label} (${check.model.efforts.join(', ')})`);
|
|
264
|
+
models[work] = { harness: harness, session: 'acp', model, effort: effort };
|
|
256
265
|
}
|
|
257
266
|
const chosen = Object.values(models);
|
|
258
267
|
const subscriptions = [...new Set(chosen.map(model => model.harness === 'codex' ? 'codex' : 'claude'))];
|
|
@@ -263,31 +272,49 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
263
272
|
if (onboardingConfigured())
|
|
264
273
|
throw new FleetError('conflict', 'Fleet is already set up; change its models in the configuration');
|
|
265
274
|
try {
|
|
266
|
-
await setup({ subscriptions, assignmentStrategy: oneModel ? 'one-model' : 'per-job', models
|
|
275
|
+
await setup({ subscriptions, assignmentStrategy: oneModel ? 'one-model' : 'per-job', models });
|
|
267
276
|
}
|
|
268
277
|
catch (error) {
|
|
278
|
+
if (error instanceof PurposeBrainConflictError)
|
|
279
|
+
throw new FleetError('conflict', error.message);
|
|
269
280
|
throw new FleetError('backend_failure', `Fleet setup did not complete: ${error.message}`);
|
|
270
281
|
}
|
|
271
|
-
// Setup preserves files it finds, so confirm
|
|
272
|
-
// The prepared agents reference packaged Brain presets, so resolve what each of them now points at:
|
|
273
|
-
// a preserved file of a packaged name may hold another model, session or reasoning than its name says.
|
|
282
|
+
// Setup preserves files it finds, so confirm each prepared agent now resolves to exactly the chosen triple.
|
|
274
283
|
const published = configuration.read(true).model, brains = published.brains ?? {};
|
|
275
284
|
const brainOf = (holder) => brains[String(holder?.brain?.ref ?? '')];
|
|
276
285
|
const assigned = { coordination: brainOf(published.agents.FleetCoordinator), development: brainOf(published.agent_templates?.Developer), review: brainOf(published.agent_templates?.Critic) };
|
|
277
286
|
for (const work of ONBOARDING_WORK)
|
|
278
|
-
if (assigned[work]?.harness !== models[work].harness || assigned[work]?.model !== models[work].model || assigned[work]?.session !== models[work].session || assigned[work]?.effort !==
|
|
287
|
+
if (assigned[work]?.harness !== models[work].harness || assigned[work]?.model !== models[work].model || assigned[work]?.session !== models[work].session || (assigned[work]?.effort ?? null) !== models[work].effort)
|
|
279
288
|
throw new FleetError('conflict', 'Fleet was set up with different models; review its configuration');
|
|
280
289
|
return { configured: true };
|
|
281
290
|
});
|
|
282
291
|
onboardingSetupQueue = run.catch(() => { });
|
|
283
292
|
return run;
|
|
284
293
|
});
|
|
294
|
+
app.get('/api/v1/harness-capabilities', async (request) => {
|
|
295
|
+
auth.authenticate(request);
|
|
296
|
+
return harnessCapabilities({ codexModels: services.codexModels });
|
|
297
|
+
});
|
|
285
298
|
app.get('/api/v1/creation-capabilities', async (request) => {
|
|
286
299
|
auth.authenticate(request);
|
|
287
300
|
return services.creation.capabilities();
|
|
288
301
|
});
|
|
302
|
+
// A new agent's own harness/model/effort must be one its adapter supports; saved Brains are used as they are.
|
|
303
|
+
const checkBrainChoice = async (brain) => {
|
|
304
|
+
if (!brain || typeof brain !== 'object')
|
|
305
|
+
return;
|
|
306
|
+
const value = brain;
|
|
307
|
+
const check = validateBrainChoice(await harnessCapabilities({ codexModels: services.codexModels, checkReadiness: false }), {
|
|
308
|
+
harness: String(value.harness), session: value.session,
|
|
309
|
+
model: value.model, effort: value.effort
|
|
310
|
+
});
|
|
311
|
+
if (!check.ok)
|
|
312
|
+
throw new FleetError('invalid_request', `Brain: ${check.message}`);
|
|
313
|
+
};
|
|
314
|
+
const checkInlineBrain = (creation) => checkBrainChoice(creation?.brain?.inline);
|
|
289
315
|
app.post('/api/v1/roles/preview', async (request) => {
|
|
290
316
|
auth.authenticate(request, true);
|
|
317
|
+
await checkInlineBrain(request.body);
|
|
291
318
|
return services.creation.preview(request.body);
|
|
292
319
|
});
|
|
293
320
|
app.post('/api/v1/roles', async (request, reply) => {
|
|
@@ -296,7 +323,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
296
323
|
const idempotencyKey = String(request.headers['idempotency-key'] ?? '');
|
|
297
324
|
if (!body?.request || !body.previewHash)
|
|
298
325
|
throw new FleetError('invalid_request', 'request and previewHash are required');
|
|
299
|
-
const action = await services.creation.create(body.request, body.previewHash, idempotencyKey, session.id);
|
|
326
|
+
const action = await services.creation.create(body.request, body.previewHash, idempotencyKey, session.id, checkInlineBrain);
|
|
300
327
|
events.publish('creation.changed', action, action.roleId);
|
|
301
328
|
reply.header('Location', `/api/v1/creation-actions/${encodeURIComponent(action.actionId)}`);
|
|
302
329
|
reply.code(202);
|
|
@@ -321,6 +348,8 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
321
348
|
? 'conflict' : 'invalid_request';
|
|
322
349
|
throw new FleetError(code, error.message);
|
|
323
350
|
}
|
|
351
|
+
if (error instanceof MissionKeyError)
|
|
352
|
+
throw new FleetError(error.code === 'invalid_prefix' ? 'invalid_request' : 'conflict', error.message, { details: { reason: error.code } });
|
|
324
353
|
if (error instanceof TaskStateError && 'accepted' in error && error.accepted === false)
|
|
325
354
|
return Promise.reject(Object.assign(new FleetError('invalid_request', error.message), { memberNotAccepted: true }));
|
|
326
355
|
if (error instanceof TaskStateError) {
|
|
@@ -329,6 +358,20 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
329
358
|
throw error;
|
|
330
359
|
}
|
|
331
360
|
};
|
|
361
|
+
/** Canonical task ID for a `:id` param holding an ID or a mission key; call only after authentication. */
|
|
362
|
+
const taskRef = (request, options = {}) => {
|
|
363
|
+
const ref = String(request.params.id ?? '');
|
|
364
|
+
try {
|
|
365
|
+
return resolveTaskRef(ref, options);
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
if (error instanceof MissionKeyError)
|
|
369
|
+
throw new FleetError('resource_not_found', error.message);
|
|
370
|
+
if (error instanceof TaskStateError)
|
|
371
|
+
throw new FleetError(error.message.startsWith('task not found:') ? 'resource_not_found' : 'invalid_request', error.message);
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
};
|
|
332
375
|
const reviewApi = async (fn) => taskApi(async () => {
|
|
333
376
|
try {
|
|
334
377
|
return await fn();
|
|
@@ -345,7 +388,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
345
388
|
});
|
|
346
389
|
/** Browser task payloads carry Progress/To review counts; bodies have their own routes. */
|
|
347
390
|
const wireTask = (task) => {
|
|
348
|
-
const { progress: _progress, review: _review, ...rest } = task;
|
|
391
|
+
const { progress: _progress, review: _review, member_naming: _naming, ...rest } = task;
|
|
349
392
|
return { ...rest, ...taskContributionSummary(task) };
|
|
350
393
|
};
|
|
351
394
|
const wireTaskResult = (result) => {
|
|
@@ -517,15 +560,29 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
517
560
|
});
|
|
518
561
|
app.post('/api/v1/task-lists', async (request, reply) => {
|
|
519
562
|
auth.authenticate(request, true);
|
|
520
|
-
const
|
|
521
|
-
const
|
|
563
|
+
const body = request.body;
|
|
564
|
+
const name = String(body?.name ?? '');
|
|
565
|
+
if (body?.prefix !== undefined && typeof body.prefix !== 'string')
|
|
566
|
+
throw new FleetError('invalid_request', 'prefix must be a string');
|
|
567
|
+
const list = await taskApi(() => requireTaskRooms().createTaskList({ actor: { kind: 'local_control', surface: 'web' }, name,
|
|
568
|
+
prefix: body?.prefix }));
|
|
522
569
|
reply.code(201);
|
|
523
570
|
return { list };
|
|
524
571
|
});
|
|
572
|
+
app.get('/api/v1/task-lists/prefix-suggestion', async (request) => {
|
|
573
|
+
auth.authenticate(request);
|
|
574
|
+
const name = String(request.query.name ?? '');
|
|
575
|
+
return { prefix: await taskApi(() => requireTaskRooms().suggestTaskListPrefix(name)) };
|
|
576
|
+
});
|
|
525
577
|
app.patch('/api/v1/task-lists/:name', async (request) => {
|
|
526
578
|
auth.authenticate(request, true);
|
|
527
579
|
const name = request.params.name;
|
|
528
|
-
const
|
|
580
|
+
const body = request.body;
|
|
581
|
+
if (body?.prefix !== undefined && typeof body.prefix !== 'string')
|
|
582
|
+
throw new FleetError('invalid_request', 'prefix must be a string');
|
|
583
|
+
if (body?.name === undefined && typeof body?.prefix === 'string')
|
|
584
|
+
return { list: await taskApi(() => requireTaskRooms().setTaskListPrefix({ actor: { kind: 'local_control', surface: 'web' }, name, prefix: body.prefix })) };
|
|
585
|
+
const newName = String(body?.name ?? '');
|
|
529
586
|
return { list: await taskApi(() => requireTaskRooms().renameTaskList({ actor: { kind: 'local_control', surface: 'web' }, name, newName })) };
|
|
530
587
|
});
|
|
531
588
|
app.delete('/api/v1/task-lists/:name', async (request) => {
|
|
@@ -540,11 +597,13 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
540
597
|
});
|
|
541
598
|
app.delete('/api/v1/tasks/:id', async (request, reply) => {
|
|
542
599
|
auth.authenticate(request, true);
|
|
543
|
-
const
|
|
600
|
+
const submitted = request.params.id;
|
|
544
601
|
const confirm = request.query.confirm;
|
|
545
|
-
// Exact-
|
|
546
|
-
if (confirm !==
|
|
547
|
-
throw new FleetError('invalid_request', 'confirm must exactly repeat the task id');
|
|
602
|
+
// Exact raw ref-twice confirmation is validated before existence or idempotency.
|
|
603
|
+
if (confirm !== submitted)
|
|
604
|
+
throw new FleetError('invalid_request', 'confirm must exactly repeat the task id or key');
|
|
605
|
+
// A deleted key still maps to its canonical ID, so a repeated DELETE stays idempotent.
|
|
606
|
+
const taskId = taskRef(request, { allowDeleted: true });
|
|
548
607
|
const accepted = await taskApi(() => requireTaskRooms().requestTaskDeletion({
|
|
549
608
|
actor: { kind: 'local_control', surface: 'web' }, taskId,
|
|
550
609
|
}));
|
|
@@ -559,7 +618,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
559
618
|
return {
|
|
560
619
|
task_id: taskId, accepted: true, deletion: 'pending',
|
|
561
620
|
...(outcome.error ? { error: outcome.error } : {}),
|
|
562
|
-
recovery: `Repeat DELETE /api/v1/tasks/${
|
|
621
|
+
recovery: `Repeat DELETE /api/v1/tasks/${submitted}?confirm=${submitted}.`,
|
|
563
622
|
};
|
|
564
623
|
});
|
|
565
624
|
app.post('/api/v1/tasks', async (request, reply) => {
|
|
@@ -588,7 +647,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
588
647
|
});
|
|
589
648
|
app.patch('/api/v1/tasks/:id/list', async (request) => {
|
|
590
649
|
auth.authenticate(request, true);
|
|
591
|
-
const taskId = request
|
|
650
|
+
const taskId = taskRef(request);
|
|
592
651
|
const list = String(request.body?.list ?? '');
|
|
593
652
|
return { task: wireTask(await taskApi(() => requireTaskRooms().moveTask({ actor: { kind: 'local_control', surface: 'web' }, taskId, list }))) };
|
|
594
653
|
});
|
|
@@ -598,17 +657,17 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
598
657
|
if (typeof body?.brief !== 'string' || typeof body.expectedBrief !== 'string' || body.brief.length > 100_000)
|
|
599
658
|
throw new FleetError('invalid_request', 'brief and expectedBrief must be strings; brief maximum is 100000 characters');
|
|
600
659
|
return { task: wireTask(await taskApi(() => requireTaskRooms().editTaskDescription({
|
|
601
|
-
actor: { kind: 'local_control', surface: 'web' }, taskId: request
|
|
660
|
+
actor: { kind: 'local_control', surface: 'web' }, taskId: taskRef(request),
|
|
602
661
|
brief: body.brief, expectedBrief: body.expectedBrief,
|
|
603
662
|
}))) };
|
|
604
663
|
});
|
|
605
664
|
app.get('/api/v1/tasks/:id', async (request) => {
|
|
606
665
|
auth.authenticate(request);
|
|
607
|
-
return wireTaskResult(await taskApi(() => requireTaskRooms().getTask(request
|
|
666
|
+
return wireTaskResult(await taskApi(() => requireTaskRooms().getTask(taskRef(request))));
|
|
608
667
|
});
|
|
609
668
|
app.get('/api/v1/tasks/:id/progress', async (request) => {
|
|
610
669
|
auth.authenticate(request);
|
|
611
|
-
return { progress: await reviewApi(() => taskProgressSnapshot(request
|
|
670
|
+
return { progress: await reviewApi(() => taskProgressSnapshot(taskRef(request))) };
|
|
612
671
|
});
|
|
613
672
|
app.post('/api/v1/tasks/:id/progress', async (request, reply) => {
|
|
614
673
|
auth.authenticate(request, true);
|
|
@@ -617,7 +676,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
617
676
|
const requestKey = typeof body?.requestKey === 'string' ? body.requestKey : typeof header === 'string' ? header : undefined;
|
|
618
677
|
if (typeof body?.text !== 'string' || (body.requestKey !== undefined && typeof body.requestKey !== 'string'))
|
|
619
678
|
throw new FleetError('invalid_request', 'text must be a string; requestKey, when given, a string');
|
|
620
|
-
const result = await reviewApi(() => appendTaskProgress(request
|
|
679
|
+
const result = await reviewApi(() => appendTaskProgress(taskRef(request), {
|
|
621
680
|
text: body.text, requestKey, author: { kind: 'local_control', surface: 'web' },
|
|
622
681
|
}));
|
|
623
682
|
if (!result.replayed)
|
|
@@ -630,7 +689,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
630
689
|
const ids = request.body?.ids;
|
|
631
690
|
if (!Array.isArray(ids) || ids.length > 200 || ids.some(id => typeof id !== 'string' || id.length > 64))
|
|
632
691
|
throw new FleetError('invalid_request', 'ids must be at most 200 progress entry IDs');
|
|
633
|
-
return { unread: await reviewApi(() => acknowledgeTaskProgress(request
|
|
692
|
+
return { unread: await reviewApi(() => acknowledgeTaskProgress(taskRef(request), ids)) };
|
|
634
693
|
});
|
|
635
694
|
/** Acknowledge the To review revision the browser fetched and rendered. */
|
|
636
695
|
app.post('/api/v1/tasks/:id/review/read', async (request) => {
|
|
@@ -638,11 +697,11 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
638
697
|
const revision = request.body?.revision;
|
|
639
698
|
if (!Number.isSafeInteger(revision) || revision < 0)
|
|
640
699
|
throw new FleetError('invalid_request', 'revision must be a non-negative integer');
|
|
641
|
-
return { unread: await reviewApi(() => acknowledgeTaskReview(request
|
|
700
|
+
return { unread: await reviewApi(() => acknowledgeTaskReview(taskRef(request), revision)) };
|
|
642
701
|
});
|
|
643
702
|
app.get('/api/v1/tasks/:id/review', async (request) => {
|
|
644
703
|
auth.authenticate(request);
|
|
645
|
-
const review = await reviewApi(() => getTaskReview(request
|
|
704
|
+
const review = await reviewApi(() => getTaskReview(taskRef(request)));
|
|
646
705
|
return { review: review ? publicReview(review) : null };
|
|
647
706
|
});
|
|
648
707
|
app.put('/api/v1/tasks/:id/review', async (request) => {
|
|
@@ -650,7 +709,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
650
709
|
const body = request.body;
|
|
651
710
|
if (typeof body?.markdown !== 'string' || typeof body.expectedMarkdown !== 'string')
|
|
652
711
|
throw new FleetError('invalid_request', 'markdown and expectedMarkdown must be strings');
|
|
653
|
-
const review = await reviewApi(() => setTaskReviewMarkdown(request
|
|
712
|
+
const review = await reviewApi(() => setTaskReviewMarkdown(taskRef(request), {
|
|
654
713
|
markdown: body.markdown, expectedMarkdown: body.expectedMarkdown,
|
|
655
714
|
author: { kind: 'local_control', surface: 'web' },
|
|
656
715
|
}));
|
|
@@ -658,7 +717,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
658
717
|
});
|
|
659
718
|
app.get('/api/v1/tasks/:id/review/artifacts/:artifactId', async (request, reply) => {
|
|
660
719
|
auth.authenticate(request);
|
|
661
|
-
const { artifact, bytes } = await reviewApi(() => readTaskReviewArtifact(request
|
|
720
|
+
const { artifact, bytes } = await reviewApi(() => readTaskReviewArtifact(taskRef(request), request.params.artifactId));
|
|
662
721
|
reply.header('Cache-Control', 'private, no-store');
|
|
663
722
|
reply.header('X-Content-Type-Options', 'nosniff');
|
|
664
723
|
reply.header('Content-Security-Policy', "default-src 'none'; sandbox");
|
|
@@ -669,7 +728,12 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
669
728
|
auth.authenticate(request, true);
|
|
670
729
|
const body = request.body;
|
|
671
730
|
try {
|
|
672
|
-
|
|
731
|
+
// A new manual harness/model/effort must be one the adapter supports; nothing is substituted.
|
|
732
|
+
// A retry of an accepted request returns its receipt even if the account's models changed since.
|
|
733
|
+
const result = await taskApi(() => requireTaskRooms().addMember(taskRef(request), body, async (member) => {
|
|
734
|
+
if (typeof member.brain === 'object')
|
|
735
|
+
await checkBrainChoice(member.brain.inline);
|
|
736
|
+
}));
|
|
673
737
|
reply.code(result.state === 'running' ? 202 : 200);
|
|
674
738
|
return result;
|
|
675
739
|
}
|
|
@@ -681,7 +745,8 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
681
745
|
});
|
|
682
746
|
app.get('/api/v1/tasks/:id/member-additions/:requestId', async (request) => {
|
|
683
747
|
auth.authenticate(request);
|
|
684
|
-
const {
|
|
748
|
+
const { requestId } = request.params;
|
|
749
|
+
const id = taskRef(request);
|
|
685
750
|
return taskApi(() => requireTaskRooms().memberAddition(id, requestId));
|
|
686
751
|
});
|
|
687
752
|
app.patch('/api/v1/tasks/:id/layout', async (request) => {
|
|
@@ -691,18 +756,19 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
691
756
|
if (!name(body?.layout) || !(body.expectedLayout === null || name(body.expectedLayout)))
|
|
692
757
|
throw new FleetError('invalid_request', 'layout must be a layout name; expectedLayout a layout name or null');
|
|
693
758
|
const task = await taskApi(() => requireTaskRooms().setTaskLayout({ actor: { kind: 'local_control', surface: 'web' },
|
|
694
|
-
taskId: request
|
|
759
|
+
taskId: taskRef(request), layout: body.layout, expectedLayout: body.expectedLayout }));
|
|
695
760
|
await audit.record({ requestId: request.id, browser: session.id, action: 'task.layout.set', result: 'succeeded' });
|
|
696
761
|
return { task: wireTask(requireTaskRooms().withLayoutRooms(task)) };
|
|
697
762
|
});
|
|
698
763
|
app.get('/api/v1/tasks/:id/layout', async (request) => {
|
|
699
764
|
auth.authenticate(request);
|
|
700
|
-
return { layout: await taskApi(async () => requireTaskRooms().taskLayout(request
|
|
765
|
+
return { layout: await taskApi(async () => requireTaskRooms().taskLayout(taskRef(request))) };
|
|
701
766
|
});
|
|
702
767
|
for (const operation of ['open', 'close']) {
|
|
703
768
|
app.post(`/api/v1/tasks/:id/layout/rooms/:room/${operation}`, async (request, reply) => {
|
|
704
769
|
const session = auth.authenticate(request, true);
|
|
705
|
-
const {
|
|
770
|
+
const { room } = request.params;
|
|
771
|
+
const id = taskRef(request);
|
|
706
772
|
const accepted = await taskApi(() => requireTaskRooms().launchTaskLayoutOperation({
|
|
707
773
|
actor: { kind: 'local_control', surface: 'web' }, taskId: id, room,
|
|
708
774
|
operation: operation === 'open' ? 'open' : 'close-room',
|
|
@@ -715,7 +781,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
715
781
|
app.post('/api/v1/tasks/:id/layout/cleanup', async (request, reply) => {
|
|
716
782
|
auth.authenticate(request, true);
|
|
717
783
|
const accepted = await taskApi(() => requireTaskRooms().retryTaskLayoutCleanup({
|
|
718
|
-
actor: { kind: 'local_control', surface: 'web' }, taskId: request
|
|
784
|
+
actor: { kind: 'local_control', surface: 'web' }, taskId: taskRef(request),
|
|
719
785
|
}));
|
|
720
786
|
reply.code(202);
|
|
721
787
|
return { operation: accepted };
|
|
@@ -761,7 +827,7 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
761
827
|
for (const action of ['start', 'block', 'unblock', 'review', 'finish', 'cancel']) {
|
|
762
828
|
app.post(`/api/v1/tasks/:id/${action}`, async (request, reply) => {
|
|
763
829
|
auth.authenticate(request, true);
|
|
764
|
-
const taskId = request
|
|
830
|
+
const taskId = taskRef(request);
|
|
765
831
|
const body = (request.body ?? {});
|
|
766
832
|
const input = { actor: { kind: 'local_control', surface: 'web' }, taskId };
|
|
767
833
|
const api = requireTaskRooms();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, managed native/ACP sessions, supervision, and ours.network messaging.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|
package/presets/README.md
CHANGED
|
@@ -1,26 +1,48 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Models, efforts and Brains
|
|
2
2
|
|
|
3
|
-
`brain-catalog.json` is the
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
`brain-catalog.json` is the model catalog maintained with the Claude Code and
|
|
4
|
+
Codex harness adapters: each exact model ID, its label, and the efforts it
|
|
5
|
+
supports (an empty `efforts` list means the model has no effort setting). The
|
|
6
|
+
adapters expose it through `modelCapabilities()`, and Fleet serves it, narrowed
|
|
7
|
+
to the models the active Codex account offers, at `GET /api/v1/harness-capabilities`.
|
|
8
|
+
Onboarding, Settings and agent launch flows use that one source. Hermes models
|
|
9
|
+
come from the provider configured in the role home, so Hermes lists none and
|
|
10
|
+
sets no effort.
|
|
11
|
+
|
|
12
|
+
Setup no longer installs a Brain per model and effort. It creates three
|
|
13
|
+
editable purpose Brains from the choices made during setup:
|
|
14
|
+
|
|
15
|
+
| Brain | Used by |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `development` | Developer, Engineer |
|
|
18
|
+
| `critic` | Critic |
|
|
19
|
+
| `coordination` | LocalCoordinator, FleetCoordinator |
|
|
20
|
+
|
|
21
|
+
Users change these or add more in Settings > Brains. An agent can also be
|
|
22
|
+
launched with a one-off harness, model and effort; that choice is kept with the
|
|
23
|
+
agent and saves no Brain.
|
|
24
|
+
|
|
25
|
+
Existing installations keep every Brain file and reference. The values of the
|
|
26
|
+
Brain presets earlier releases generated per model and effort are frozen in
|
|
27
|
+
`legacy/generated-brains-rev4.json`; configuration provenance reports an
|
|
28
|
+
untouched one as `legacy` and an edited one as `changed`. Nothing deletes them.
|
|
29
|
+
`fleet/brains/claude-default.yaml` (the Claude Code default model) still ships
|
|
30
|
+
for packaged templates and existing references; setup does not install it.
|
|
8
31
|
|
|
9
32
|
## GPT-6.1 Sol (added 2026-09-29)
|
|
10
33
|
|
|
11
|
-
The Codex
|
|
12
|
-
use the exact model ID `gpt-6.1-sol` with ACP sessions. The init wizard offers
|
|
34
|
+
The Codex catalog entry uses the exact model ID `gpt-6.1-sol` with ACP sessions. The init wizard offers
|
|
13
35
|
GPT-6.1 Sol for development, review, and coordination.
|
|
14
36
|
|
|
15
37
|
[Official OpenAI documentation](https://developers.openai.com/api/docs/models/gpt-6.1-sol)
|
|
16
38
|
supports low, medium, high, xhigh, and max API reasoning efforts. The local
|
|
17
39
|
Codex `models_cache.json` inspected on 2026-09-29 additionally advertises
|
|
18
|
-
`ultra`, a Codex harness mode.
|
|
40
|
+
`ultra`, a Codex harness mode. The catalog reflects that six-effort Codex
|
|
19
41
|
catalog; no inference or ACP launch was performed for this addition.
|
|
20
42
|
|
|
21
43
|
## Current model additions (verified 2026-09-24)
|
|
22
44
|
|
|
23
|
-
| Harness | Exact model ID |
|
|
45
|
+
| Harness | Exact model ID | Efforts |
|
|
24
46
|
| --- | --- | --- |
|
|
25
47
|
| Codex | `gpt-6-sol` | low, medium, high, xhigh, max, ultra |
|
|
26
48
|
| Codex | `gpt-6-luna` | low, medium, high, xhigh, max |
|
|
@@ -29,23 +51,21 @@ catalog; no inference or ACP launch was performed for this addition.
|
|
|
29
51
|
| Claude Code (retained) | `claude-sonnet-5` | low, medium, high, xhigh, max |
|
|
30
52
|
| Claude Code | `claude-haiku-4-5-20251001` | default, with no effort setting |
|
|
31
53
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
models supporting its Quick/Balanced/Thorough (low/medium/high) choices; use the
|
|
35
|
-
named Brain directly for Haiku, which does not support effort.
|
|
54
|
+
Setup asks for an effort only among the efforts a model supports; Haiku gets
|
|
55
|
+
none.
|
|
36
56
|
|
|
37
57
|
### Runtime requirements and evidence
|
|
38
58
|
|
|
39
59
|
Codex CLI **0.155.0** freshly queried through `app-server` `initialize` then
|
|
40
60
|
`model/list` advertised the GPT rows above. A previously populated
|
|
41
61
|
`~/.codex/models_cache.json` did not contain these models; refresh runtime discovery
|
|
42
|
-
before concluding a model is unavailable. `ours-fleet doctor` compares
|
|
62
|
+
before concluding a model is unavailable. `ours-fleet doctor` compares configured Codex Brains to
|
|
43
63
|
the local cache. The catalog is not an account entitlement guarantee.
|
|
44
64
|
|
|
45
65
|
The [Sol](https://developers.openai.com/api/docs/models/gpt-6-sol) and
|
|
46
66
|
[Luna](https://developers.openai.com/api/docs/models/gpt-6-luna) API model pages
|
|
47
67
|
also support `none`, but the verified Codex runtime does not advertise it.
|
|
48
|
-
Consequently
|
|
68
|
+
Consequently the catalog lists no `none` effort. Sol's `ultra` is an advertised Codex
|
|
49
69
|
harness mode, not an additional API reasoning level; Luna does not advertise it.
|
|
50
70
|
|
|
51
71
|
Claude Code **2.1.281** was verified using the Agent SDK `supportedModels()`
|
|
@@ -56,13 +76,13 @@ not asserted minimum versions. Provider references:
|
|
|
56
76
|
[model IDs](https://platform.claude.com/docs/en/models/overview) and
|
|
57
77
|
[Claude Code effort support](https://code.claude.com/docs/en/model-config#adjust-effort-level).
|
|
58
78
|
|
|
59
|
-
**Check the executable used by ACP before selecting the new Claude
|
|
79
|
+
**Check the executable used by ACP before selecting the new Claude models.**
|
|
60
80
|
The inspected `claude-agent-acp` **0.63.0** installation bundles Agent SDK
|
|
61
81
|
**0.3.220**, whose default executable reports older models. To use the verified
|
|
62
82
|
installed Claude CLI, set `CLAUDE_CODE_EXECUTABLE` to its absolute path in the
|
|
63
83
|
launch environment (for example `/usr/bin/claude` on the verification host).
|
|
64
84
|
Otherwise use a bundled SDK whose runtime advertises these models and efforts.
|
|
65
|
-
The
|
|
85
|
+
The catalog does not change your executable or restart agents. An older bundled
|
|
66
86
|
runtime alone is not evidence of support for the new models.
|
|
67
87
|
|
|
68
88
|
No-prompt ACP startup and `session/set_config_option` probes also verified all
|