@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
package/README.md
CHANGED
|
@@ -1853,7 +1853,7 @@ and member status and choose supported recovery. These checks do not restart age
|
|
|
1853
1853
|
recreate identities, redeem invites, or rewrite lifecycle state. Temporary room agents
|
|
1854
1854
|
remain transient across host reboot; retained launch records do not restore them.
|
|
1855
1855
|
|
|
1856
|
-
See [
|
|
1856
|
+
See [models, efforts and Brains](presets/README.md) for the adapter model catalog, the three purpose Brains setup creates, and runtime requirements.
|
|
1857
1857
|
|
|
1858
1858
|
### YAML room layouts
|
|
1859
1859
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { PrereqCheck } from '../harness/types.js';
|
|
2
|
+
import type { OfferedModels } from '../subscriptions/cli.js';
|
|
3
|
+
/**
|
|
4
|
+
* A selectable model. `efforts: null` means the efforts are unknown (a model the
|
|
5
|
+
* adapter does not maintain); `[]` means the model is known to have no effort setting.
|
|
6
|
+
*/
|
|
7
|
+
export interface CapabilityModel {
|
|
8
|
+
id: string;
|
|
9
|
+
label: string;
|
|
10
|
+
efforts: string[] | null;
|
|
11
|
+
available: boolean;
|
|
12
|
+
reason?: string;
|
|
13
|
+
source: 'adapter' | 'runtime-discovery';
|
|
14
|
+
}
|
|
15
|
+
export interface HarnessCapability {
|
|
16
|
+
harness: string;
|
|
17
|
+
ready: boolean;
|
|
18
|
+
checks: PrereqCheck[];
|
|
19
|
+
sessions: string[];
|
|
20
|
+
modelRequired: boolean;
|
|
21
|
+
customModels: boolean;
|
|
22
|
+
models: CapabilityModel[];
|
|
23
|
+
discovery?: {
|
|
24
|
+
state: 'known';
|
|
25
|
+
} | {
|
|
26
|
+
state: 'unknown';
|
|
27
|
+
reason: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface HarnessCapabilitiesReport {
|
|
31
|
+
harnesses: HarnessCapability[];
|
|
32
|
+
}
|
|
33
|
+
export interface HarnessCapabilityDeps {
|
|
34
|
+
harnesses?: string[];
|
|
35
|
+
codexModels?: () => Promise<OfferedModels>;
|
|
36
|
+
/** Skip executable checks when only model facts are needed. */
|
|
37
|
+
checkReadiness?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** Each production adapter's maintained models, narrowed by what the active Codex account offers. */
|
|
40
|
+
export declare function harnessCapabilities(deps?: HarnessCapabilityDeps): Promise<HarnessCapabilitiesReport>;
|
|
41
|
+
export interface BrainChoice {
|
|
42
|
+
harness: string;
|
|
43
|
+
session?: string;
|
|
44
|
+
model?: string | null;
|
|
45
|
+
effort?: string | null;
|
|
46
|
+
}
|
|
47
|
+
export type BrainChoiceCheck = {
|
|
48
|
+
ok: true;
|
|
49
|
+
model?: CapabilityModel;
|
|
50
|
+
} | {
|
|
51
|
+
ok: false;
|
|
52
|
+
code: string;
|
|
53
|
+
message: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Strict check of a new harness/model/effort choice. Nothing is substituted: an
|
|
57
|
+
* effort is kept only when the chosen model supports it; an omitted effort keeps
|
|
58
|
+
* the harness default. Existing Brains are resolved by their adapter as before.
|
|
59
|
+
*/
|
|
60
|
+
export declare function validateBrainChoice(report: HarnessCapabilitiesReport, choice: BrainChoice): BrainChoiceCheck;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { getAdapter, knownAdapters, productionAdapters } from '../harness/registry.js';
|
|
2
|
+
/** Each production adapter's maintained models, narrowed by what the active Codex account offers. */
|
|
3
|
+
export async function harnessCapabilities(deps = {}) {
|
|
4
|
+
// Only harnesses whose adapter is registered in this process can report facts.
|
|
5
|
+
const ids = (deps.harnesses ?? productionAdapters()).filter(id => knownAdapters().includes(id));
|
|
6
|
+
const harnesses = await Promise.all(ids.map(async (id) => {
|
|
7
|
+
const adapter = getAdapter(id);
|
|
8
|
+
const capabilities = adapter.modelCapabilities?.();
|
|
9
|
+
if (!capabilities)
|
|
10
|
+
return undefined;
|
|
11
|
+
let ready = true, checks = [];
|
|
12
|
+
if (deps.checkReadiness !== false) {
|
|
13
|
+
try {
|
|
14
|
+
({ ok: ready, checks } = await adapter.checkPrereqs());
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
ready = false;
|
|
18
|
+
checks = [{ name: 'availability', ok: false, detail: 'Harness checks unavailable' }];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
let models = capabilities.models.map(model => ({
|
|
22
|
+
id: model.id, label: model.label, efforts: [...model.efforts], available: true, source: 'adapter',
|
|
23
|
+
}));
|
|
24
|
+
let discovery;
|
|
25
|
+
if (id === 'codex' && deps.codexModels) {
|
|
26
|
+
const offered = await deps.codexModels();
|
|
27
|
+
discovery = offered.state === 'known' ? { state: 'known' } : { state: 'unknown', reason: offered.reason };
|
|
28
|
+
if (offered.state === 'known') {
|
|
29
|
+
models = models.map(model => offered.models.includes(model.id) ? model
|
|
30
|
+
: { ...model, available: false, reason: 'The active Codex account does not offer this model.' });
|
|
31
|
+
// Offered models the adapter does not maintain: selectable, but their efforts are not known.
|
|
32
|
+
for (const extra of offered.models.filter(model => !models.some(known => known.id === model)))
|
|
33
|
+
models.push({ id: extra, label: extra, efforts: null, available: true, source: 'runtime-discovery' });
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
models = models.map(model => ({ ...model, available: false,
|
|
37
|
+
reason: `Codex could not report the models offered to the active account: ${offered.reason}` }));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { harness: id, ready, checks, sessions: [...capabilities.sessions], modelRequired: capabilities.modelRequired,
|
|
41
|
+
customModels: capabilities.customModels, models, ...(discovery ? { discovery } : {}) };
|
|
42
|
+
}));
|
|
43
|
+
return { harnesses: harnesses.filter((item) => item !== undefined) };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Strict check of a new harness/model/effort choice. Nothing is substituted: an
|
|
47
|
+
* effort is kept only when the chosen model supports it; an omitted effort keeps
|
|
48
|
+
* the harness default. Existing Brains are resolved by their adapter as before.
|
|
49
|
+
*/
|
|
50
|
+
export function validateBrainChoice(report, choice) {
|
|
51
|
+
const harness = report.harnesses.find(item => item.harness === choice.harness);
|
|
52
|
+
if (!harness)
|
|
53
|
+
return { ok: false, code: 'harness_unsupported',
|
|
54
|
+
message: `Harness '${String(choice.harness)}' is not available; choose one of: ${report.harnesses.map(item => item.harness).join(', ')}` };
|
|
55
|
+
if (choice.session !== undefined && !harness.sessions.includes(choice.session))
|
|
56
|
+
return { ok: false, code: 'session_unsupported',
|
|
57
|
+
message: `${harness.harness} does not support session '${choice.session}'; choose one of: ${harness.sessions.join(', ')}` };
|
|
58
|
+
const effort = choice.effort ?? undefined;
|
|
59
|
+
if (effort !== undefined && (typeof effort !== 'string' || !effort))
|
|
60
|
+
return { ok: false, code: 'effort_invalid', message: 'Effort must be a non-empty string or omitted' };
|
|
61
|
+
const modelId = choice.model ?? undefined;
|
|
62
|
+
if (modelId === undefined) {
|
|
63
|
+
if (harness.modelRequired)
|
|
64
|
+
return { ok: false, code: 'model_required', message: `${harness.harness} requires an explicit model` };
|
|
65
|
+
if (effort !== undefined)
|
|
66
|
+
return { ok: false, code: 'effort_unknown',
|
|
67
|
+
message: `The ${harness.harness} default model has no known effort list; choose a model to set effort, or leave effort at the harness default` };
|
|
68
|
+
return { ok: true };
|
|
69
|
+
}
|
|
70
|
+
if (typeof modelId !== 'string' || !modelId.trim())
|
|
71
|
+
return { ok: false, code: 'model_invalid', message: 'Model must be a non-empty string' };
|
|
72
|
+
const model = harness.models.find(item => item.id === modelId);
|
|
73
|
+
if (!model) {
|
|
74
|
+
if (!harness.customModels)
|
|
75
|
+
return { ok: false, code: 'model_unsupported',
|
|
76
|
+
message: `${harness.harness} does not support model '${modelId}'` };
|
|
77
|
+
if (effort !== undefined)
|
|
78
|
+
return { ok: false, code: 'effort_unknown',
|
|
79
|
+
message: `Efforts for '${modelId}' are unknown to ${harness.harness}; leave effort at the harness default or choose a listed model` };
|
|
80
|
+
return { ok: true };
|
|
81
|
+
}
|
|
82
|
+
if (!model.available)
|
|
83
|
+
return { ok: false, code: 'model_unavailable', message: `${model.label}: ${model.reason ?? 'unavailable'}` };
|
|
84
|
+
if (effort === undefined)
|
|
85
|
+
return { ok: true, model };
|
|
86
|
+
if (model.efforts === null)
|
|
87
|
+
return { ok: false, code: 'effort_unknown',
|
|
88
|
+
message: `Efforts for ${model.label} are unknown; leave effort at the harness default` };
|
|
89
|
+
if (!model.efforts.length)
|
|
90
|
+
return { ok: false, code: 'effort_unsupported',
|
|
91
|
+
message: `${model.label} has no effort setting; remove the effort` };
|
|
92
|
+
if (!model.efforts.includes(effort))
|
|
93
|
+
return { ok: false, code: 'effort_unsupported',
|
|
94
|
+
message: `${model.label} does not support effort '${effort}'; choose one of: ${model.efforts.join(', ')}` };
|
|
95
|
+
return { ok: true, model };
|
|
96
|
+
}
|
|
@@ -10,6 +10,23 @@ export interface HarnessModelCatalog {
|
|
|
10
10
|
models: HarnessModelOption[];
|
|
11
11
|
warnings: string[];
|
|
12
12
|
}
|
|
13
|
+
/** One model a harness adapter knows, with the exact efforts it supports ([] = no effort setting). */
|
|
14
|
+
export interface PackagedModel {
|
|
15
|
+
harness: 'codex' | 'claude-code';
|
|
16
|
+
session: 'acp';
|
|
17
|
+
model: string;
|
|
18
|
+
label: string;
|
|
19
|
+
efforts: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface PackagedModelCatalog {
|
|
22
|
+
schema_version: 2;
|
|
23
|
+
catalog_revision: number;
|
|
24
|
+
models: PackagedModel[];
|
|
25
|
+
}
|
|
26
|
+
/** Validate the packaged model catalog maintained with the Claude Code and Codex adapters. */
|
|
27
|
+
export declare function validatePackagedModelCatalog(value: unknown, path?: string): PackagedModelCatalog;
|
|
28
|
+
/** The packaged catalog, optionally limited to one harness. */
|
|
29
|
+
export declare function packagedModelCatalog(harness?: PackagedModel['harness']): PackagedModel[];
|
|
13
30
|
/** Read Codex's locally materialized `model/list` result; never invent fallback aliases. */
|
|
14
31
|
export declare function codexModelCatalog(path?: string): HarnessModelCatalog;
|
|
15
32
|
/**
|
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
import { readFileSync, statSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
+
import { packagedPresetRoot } from '../preset-bootstrap.js';
|
|
5
|
+
const KNOWN_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max', 'ultra'];
|
|
6
|
+
/** Validate the packaged model catalog maintained with the Claude Code and Codex adapters. */
|
|
7
|
+
export function validatePackagedModelCatalog(value, path = 'packaged model catalog') {
|
|
8
|
+
const catalog = value;
|
|
9
|
+
if (!catalog || catalog.schema_version !== 2 || !Number.isInteger(catalog.catalog_revision)
|
|
10
|
+
|| !Array.isArray(catalog.models) || catalog.models.length === 0)
|
|
11
|
+
throw new Error(`invalid ${path}`);
|
|
12
|
+
const tuples = new Set();
|
|
13
|
+
for (const model of catalog.models) {
|
|
14
|
+
if (!model || !['codex', 'claude-code'].includes(model.harness) || model.session !== 'acp'
|
|
15
|
+
|| typeof model.model !== 'string' || !model.model
|
|
16
|
+
|| typeof model.label !== 'string' || !model.label.trim()
|
|
17
|
+
|| !Array.isArray(model.efforts) || new Set(model.efforts).size !== model.efforts.length
|
|
18
|
+
|| model.efforts.some(effort => !KNOWN_EFFORTS.includes(effort)))
|
|
19
|
+
throw new Error(`unsupported model state in ${path}: ${String(model?.model)}`);
|
|
20
|
+
const tuple = `${model.harness}\0${model.session}\0${model.model}`;
|
|
21
|
+
if (tuples.has(tuple))
|
|
22
|
+
throw new Error(`duplicate supported model in ${path}: ${model.model}`);
|
|
23
|
+
tuples.add(tuple);
|
|
24
|
+
}
|
|
25
|
+
return catalog;
|
|
26
|
+
}
|
|
27
|
+
/** The packaged catalog, optionally limited to one harness. */
|
|
28
|
+
export function packagedModelCatalog(harness) {
|
|
29
|
+
const path = join(packagedPresetRoot(), 'brain-catalog.json');
|
|
30
|
+
const models = validatePackagedModelCatalog(JSON.parse(readFileSync(path, 'utf8')), path).models;
|
|
31
|
+
return harness ? models.filter(model => model.harness === harness) : models;
|
|
32
|
+
}
|
|
4
33
|
const validId = (value) => typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._[\]-]{0,127}$/.test(value);
|
|
5
34
|
/** Read Codex's locally materialized `model/list` result; never invent fallback aliases. */
|
|
6
35
|
export function codexModelCatalog(path = join(homedir(), '.codex', 'models_cache.json')) {
|
|
@@ -45,16 +74,10 @@ export function codexModelCatalog(path = join(homedir(), '.codex', 'models_cache
|
|
|
45
74
|
* not a claim about account entitlement (which Claude validates at launch).
|
|
46
75
|
*/
|
|
47
76
|
export function claudeModelCatalog() {
|
|
48
|
-
const reasoningEfforts = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
49
77
|
return {
|
|
50
|
-
models:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
{ id: 'claude-haiku-4-5-20251001', label: 'Claude Haiku 4.5', reasoningEfforts: [], source: 'claude-adapter-2.1' },
|
|
54
|
-
{ id: 'claude-fable-5', label: 'Claude Fable 5', reasoningEfforts, source: 'claude-adapter-2.1' },
|
|
55
|
-
{ id: 'claude-opus-5', label: 'Claude Opus 5', reasoningEfforts, source: 'claude-adapter-2.1' },
|
|
56
|
-
{ id: 'claude-sonnet-5', label: 'Claude Sonnet 5', reasoningEfforts, source: 'claude-adapter-2.1' },
|
|
57
|
-
],
|
|
78
|
+
models: packagedModelCatalog('claude-code').map(model => ({
|
|
79
|
+
id: model.model, label: model.label, reasoningEfforts: model.efforts, source: 'claude-adapter-2.1',
|
|
80
|
+
})),
|
|
58
81
|
warnings: ['Claude Code exposes no typed model-list endpoint; choices require a current Claude runtime (verified with 2.1.281); older bundled SDKs need CLAUDE_CODE_EXECUTABLE. Entitlement is validated at launch.'],
|
|
59
82
|
};
|
|
60
83
|
}
|
|
@@ -5,7 +5,7 @@ export declare const PROVENANCE_KINDS: {
|
|
|
5
5
|
readonly layout: "room_layouts";
|
|
6
6
|
};
|
|
7
7
|
export type ProvenanceKind = keyof typeof PROVENANCE_KINDS;
|
|
8
|
-
export type ProvenanceStatus = 'predefined' | 'changed' | 'custom';
|
|
8
|
+
export type ProvenanceStatus = 'predefined' | 'changed' | 'custom' | 'legacy';
|
|
9
9
|
export interface ProvenanceEntry {
|
|
10
10
|
status: ProvenanceStatus;
|
|
11
11
|
preset?: string;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/** Where each configured definition came from, compared with the packaged presets.
|
|
2
2
|
* A definition is `predefined` when it has the same value as the shipped preset of
|
|
3
3
|
* the same name, `changed` when a preset of that name exists but the value differs,
|
|
4
|
-
* and `custom` otherwise.
|
|
4
|
+
* and `custom` otherwise. A Brain whose name and value match a preset that earlier
|
|
5
|
+
* releases generated for every model and effort is `legacy`; one of such a name
|
|
6
|
+
* with another value is `changed`. Values are compared after parsing, so the formatting the
|
|
5
7
|
* configuration editor writes does not count as a change. Preset text is returned
|
|
6
8
|
* so users can see exactly what ships; it contains no host data.
|
|
7
9
|
*/
|
|
@@ -29,6 +31,14 @@ function yamlFiles(dir) {
|
|
|
29
31
|
}
|
|
30
32
|
return files;
|
|
31
33
|
}
|
|
34
|
+
/** Brain presets earlier releases generated per model and effort; no longer shipped or installed. */
|
|
35
|
+
function legacyBrains(presetRoot) {
|
|
36
|
+
const path = join(presetRoot, '..', 'legacy', 'generated-brains-rev4.json');
|
|
37
|
+
if (!existsSync(path))
|
|
38
|
+
return new Map();
|
|
39
|
+
const { brains } = JSON.parse(readFileSync(path, 'utf8'));
|
|
40
|
+
return new Map(Object.entries(brains).map(([name, value]) => [name, Object.entries(value).map(([key, item]) => `${key}: ${item}`).join('\n') + '\n']));
|
|
41
|
+
}
|
|
32
42
|
const parsed = (text) => { try {
|
|
33
43
|
return parse(text);
|
|
34
44
|
}
|
|
@@ -47,10 +57,15 @@ export class PresetProvenance {
|
|
|
47
57
|
const result = {};
|
|
48
58
|
for (const [kind, dir] of Object.entries(PROVENANCE_KINDS)) {
|
|
49
59
|
const presets = yamlFiles(join(this.presetRoot, dir)), installed = yamlFiles(join(split, dir));
|
|
60
|
+
const legacy = kind === 'brain' ? legacyBrains(this.presetRoot) : new Map();
|
|
50
61
|
result[kind] = Object.fromEntries([...installed].map(([name, text]) => {
|
|
51
62
|
const preset = presets.get(name);
|
|
52
|
-
if (preset === undefined)
|
|
53
|
-
|
|
63
|
+
if (preset === undefined) {
|
|
64
|
+
const old = legacy.get(name);
|
|
65
|
+
if (old === undefined)
|
|
66
|
+
return [name, { status: 'custom' }];
|
|
67
|
+
return [name, { status: isDeepStrictEqual(parsed(text), parsed(old)) ? 'legacy' : 'changed', preset: old }];
|
|
68
|
+
}
|
|
54
69
|
return [name, { status: isDeepStrictEqual(parsed(text), parsed(preset)) ? 'predefined' : 'changed', preset }];
|
|
55
70
|
}));
|
|
56
71
|
}
|
|
@@ -145,7 +145,9 @@ export declare class RoleCreationService {
|
|
|
145
145
|
private launchSync;
|
|
146
146
|
capabilities(): Promise<CreationCapabilities>;
|
|
147
147
|
preview(input: CreateRoleSessionRequest): Promise<CreationPreview>;
|
|
148
|
-
create(input: CreateRoleSessionRequest, previewHash: string, idempotencyKey: string, browserSession: string
|
|
148
|
+
create(input: CreateRoleSessionRequest, previewHash: string, idempotencyKey: string, browserSession: string,
|
|
149
|
+
/** Checks a new request only; a retry of an accepted request returns its action. */
|
|
150
|
+
checkNewRequest?: (input: CreateRoleSessionRequest) => Promise<void>): Promise<CreationAction>;
|
|
149
151
|
get(actionId: string): CreationAction | undefined;
|
|
150
152
|
private run;
|
|
151
153
|
private waitForReady;
|
|
@@ -184,7 +184,9 @@ export class RoleCreationService {
|
|
|
184
184
|
identityBootstrap, previewHash,
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
|
-
async create(input, previewHash, idempotencyKey, browserSession
|
|
187
|
+
async create(input, previewHash, idempotencyKey, browserSession,
|
|
188
|
+
/** Checks a new request only; a retry of an accepted request returns its action. */
|
|
189
|
+
checkNewRequest) {
|
|
188
190
|
if (!/^[A-Za-z0-9_-]{22,256}$/.test(idempotencyKey))
|
|
189
191
|
throw new FleetError('invalid_request', 'Idempotency-Key must contain at least 128 bits');
|
|
190
192
|
const requestHash = hash(this.validate(input));
|
|
@@ -195,6 +197,7 @@ export class RoleCreationService {
|
|
|
195
197
|
throw new FleetError('idempotency_conflict', 'Idempotency-Key was already used for another request');
|
|
196
198
|
return this.actions.get(existing.actionId);
|
|
197
199
|
}
|
|
200
|
+
await checkNewRequest?.(input);
|
|
198
201
|
const preview = await this.preview(input);
|
|
199
202
|
if (preview.previewHash !== previewHash)
|
|
200
203
|
throw new FleetError('stale_state', 'preview is stale; review the effective plan again');
|
|
@@ -114,7 +114,7 @@ export declare class TaskRoomApplicationService {
|
|
|
114
114
|
private readonly configurationPath?;
|
|
115
115
|
private readonly deps;
|
|
116
116
|
constructor(configurationPath?: string | undefined, deps?: TaskRoomServiceDeps);
|
|
117
|
-
addMember(taskId: string, request: AddMemberRequest): Promise<{
|
|
117
|
+
addMember(taskId: string, request: AddMemberRequest, checkNewRequest?: (request: AddMemberRequest) => Promise<void>): Promise<{
|
|
118
118
|
requestId: string;
|
|
119
119
|
taskId: string;
|
|
120
120
|
roleId: string;
|
|
@@ -201,10 +201,21 @@ export declare class TaskRoomApplicationService {
|
|
|
201
201
|
list: TaskListRecord;
|
|
202
202
|
tasks: TaskRecord[];
|
|
203
203
|
}>;
|
|
204
|
+
/** Run the one-time mission key migration now (service start, `task migrate-keys`). */
|
|
205
|
+
prepareMissionKeys(): Promise<void>;
|
|
206
|
+
/** One-time key migration under the list lock, honoring `tasks.default_prefix`. */
|
|
207
|
+
private ensureMissionKeys;
|
|
204
208
|
createTaskList(input: {
|
|
205
209
|
actor: TaskRoomActor;
|
|
206
210
|
name: string;
|
|
211
|
+
prefix?: string;
|
|
207
212
|
}): Promise<TaskListRecord>;
|
|
213
|
+
setTaskListPrefix(input: {
|
|
214
|
+
actor: TaskRoomActor;
|
|
215
|
+
name: string;
|
|
216
|
+
prefix: string;
|
|
217
|
+
}): Promise<TaskListRecord>;
|
|
218
|
+
suggestTaskListPrefix(name: string): Promise<string>;
|
|
208
219
|
renameTaskList(input: {
|
|
209
220
|
actor: TaskRoomActor;
|
|
210
221
|
name: string;
|
|
@@ -7,8 +7,8 @@ import { ConfigError, loadConfig } from '../config.js';
|
|
|
7
7
|
import { CoworkProtocolError, CoworkUnavailableError, createCoworkAdapter, } from '../rooms-tasks/cowork-adapter.js';
|
|
8
8
|
import { getBinPath, provisionMembers } from '../rooms-tasks/provision.js';
|
|
9
9
|
import { activateRoom, advanceSaga, createRoomRecord, getRoomRecord, listRoomRecords, setOwnerSeat, setSagaError, } from '../rooms-tasks/room-state.js';
|
|
10
|
-
import { activateTask, blockTask as persistBlockTask, createTask as persistTask, getDeletingTask, getTask as readTask, listTasks as readTasks, taskDeletionState, moveTaskToList, reviewTask as persistReviewTask, startTask as transitionTask, TaskStateError, unblockTask as persistUnblockTask, updateTaskRoom, updateTaskTemplate, updateTaskExecutionPlan, updateTaskBrief, updateTaskLayout, } from '../rooms-tasks/task-state.js';
|
|
11
|
-
import { createTaskListLocked, DEFAULT_TASK_LIST_ID, deleteTaskListRecordLocked, readTaskLists, renameTaskListLocked, resolveTaskList, TaskListError, withTaskListsLock, } from '../rooms-tasks/task-lists.js';
|
|
10
|
+
import { activateTask, blockTask as persistBlockTask, createTask as persistTask, getDeletingTask, getTask as readTask, listTasks as readTasks, taskDeletionState, migrateMissionKeysLocked, moveTaskToList, reviewTask as persistReviewTask, startTask as transitionTask, TaskStateError, unblockTask as persistUnblockTask, updateTaskRoom, updateTaskTemplate, updateTaskExecutionPlan, updateTaskBrief, updateTaskLayout, } from '../rooms-tasks/task-state.js';
|
|
11
|
+
import { createTaskListLocked, DEFAULT_TASK_LIST_ID, deleteTaskListRecordLocked, readTaskLists, renameTaskListLocked, resolveTaskList, setTaskListPrefixLocked, suggestTaskListPrefix, TaskListError, withTaskListsLock, } from '../rooms-tasks/task-lists.js';
|
|
12
12
|
import { hashTemplate, listTemplates, resolveTemplate, sealTemplateSnapshot, snapshotTemplate } from '../rooms-tasks/templates.js';
|
|
13
13
|
import { acquireLaunchSnapshotLock, releaseLaunchSnapshot } from '../rooms-tasks/launch-snapshot.js';
|
|
14
14
|
import { hashMemberOverrides, prepareExecutionPlan } from '../rooms-tasks/member-overrides.js';
|
|
@@ -76,11 +76,11 @@ export class TaskRoomApplicationService {
|
|
|
76
76
|
this.configurationPath = configurationPath;
|
|
77
77
|
this.deps = deps;
|
|
78
78
|
}
|
|
79
|
-
async addMember(taskId, request) {
|
|
79
|
+
async addMember(taskId, request, checkNewRequest) {
|
|
80
80
|
const { requestMemberAddition } = await import('../rooms-tasks/add-member.js');
|
|
81
81
|
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
82
82
|
return requestMemberAddition({ taskId, request, cfg, configPath: this.configurationPath,
|
|
83
|
-
cowork: readTask(taskId).layout ? undefined : (this.deps.cowork ?? createCoworkAdapter)(cfg), binPath: (this.deps.binPath ?? getBinPath)(), provision: this.deps.provisionMembers, layouts: this.layouts() });
|
|
83
|
+
cowork: readTask(taskId).layout ? undefined : (this.deps.cowork ?? createCoworkAdapter)(cfg), binPath: (this.deps.binPath ?? getBinPath)(), provision: this.deps.provisionMembers, layouts: this.layouts(), checkNewRequest });
|
|
84
84
|
}
|
|
85
85
|
async memberAddition(taskId, requestId) { const { memberAddition } = await import('../rooms-tasks/add-member.js'); return memberAddition(taskId, requestId); }
|
|
86
86
|
layouts() {
|
|
@@ -100,7 +100,7 @@ export class TaskRoomApplicationService {
|
|
|
100
100
|
const task = await withTaskListsLock(() => persistTask({
|
|
101
101
|
title: request.title, brief, brief_file: request.briefFile, layout,
|
|
102
102
|
origin: request.origin, idempotency_key: request.idempotencyKey,
|
|
103
|
-
start: !request.backlog,
|
|
103
|
+
start: !request.backlog, list: request.list ?? 'default',
|
|
104
104
|
}));
|
|
105
105
|
recordFleetAuditPresentation({ kind: 'task', operation: 'create', id: task.task_id,
|
|
106
106
|
title: task.title, previousState: 'none', newState: task.state, revision: task.created_at,
|
|
@@ -200,6 +200,7 @@ export class TaskRoomApplicationService {
|
|
|
200
200
|
return this.layouts().view(task);
|
|
201
201
|
}
|
|
202
202
|
async createTask(request) {
|
|
203
|
+
await this.ensureMissionKeys();
|
|
203
204
|
if (request.layout !== undefined)
|
|
204
205
|
return this.createLayoutTask(request);
|
|
205
206
|
const cfg = (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath);
|
|
@@ -229,15 +230,12 @@ export class TaskRoomApplicationService {
|
|
|
229
230
|
plan_hash: preparedPlan.planHash };
|
|
230
231
|
}
|
|
231
232
|
const ref = template && { name: template.name, version: template.version, content_hash: template.content_hash };
|
|
232
|
-
task = await withTaskListsLock(() => {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
start: !request.backlog, no_room: request.noRoom, listId: list.list_id,
|
|
239
|
-
});
|
|
240
|
-
});
|
|
233
|
+
task = await withTaskListsLock(() => persistTask({
|
|
234
|
+
title: request.title, brief, brief_file: request.briefFile, template: ref,
|
|
235
|
+
execution_plan: executionPlan,
|
|
236
|
+
origin: request.origin, idempotency_key: request.idempotencyKey,
|
|
237
|
+
start: !request.backlog, no_room: request.noRoom, list: request.list ?? 'default',
|
|
238
|
+
}));
|
|
241
239
|
}
|
|
242
240
|
catch (error) {
|
|
243
241
|
unlock?.();
|
|
@@ -317,8 +315,31 @@ export class TaskRoomApplicationService {
|
|
|
317
315
|
const lists = filter?.list ? [resolveTaskList(filter.list)] : readTaskLists();
|
|
318
316
|
return lists.map(list => ({ list, tasks: tasks.filter(task => task.list_id === list.list_id) }));
|
|
319
317
|
}
|
|
318
|
+
/** Run the one-time mission key migration now (service start, `task migrate-keys`). */
|
|
319
|
+
prepareMissionKeys() { return this.ensureMissionKeys(); }
|
|
320
|
+
/** One-time key migration under the list lock, honoring `tasks.default_prefix`. */
|
|
321
|
+
ensureMissionKeys() {
|
|
322
|
+
const configured = (() => {
|
|
323
|
+
try {
|
|
324
|
+
return (this.deps.loadConfiguration ?? loadConfig)(this.configurationPath).tasks?.default_prefix;
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
})();
|
|
330
|
+
return withTaskListsLock(() => migrateMissionKeysLocked(configured));
|
|
331
|
+
}
|
|
320
332
|
async createTaskList(input) {
|
|
321
|
-
|
|
333
|
+
await this.ensureMissionKeys();
|
|
334
|
+
return withTaskListsLock(() => createTaskListLocked(input.name, input.prefix));
|
|
335
|
+
}
|
|
336
|
+
async setTaskListPrefix(input) {
|
|
337
|
+
await this.ensureMissionKeys();
|
|
338
|
+
return withTaskListsLock(() => setTaskListPrefixLocked(input.name, input.prefix));
|
|
339
|
+
}
|
|
340
|
+
async suggestTaskListPrefix(name) {
|
|
341
|
+
await this.ensureMissionKeys();
|
|
342
|
+
return suggestTaskListPrefix(name);
|
|
322
343
|
}
|
|
323
344
|
async renameTaskList(input) {
|
|
324
345
|
return withTaskListsLock(() => renameTaskListLocked(input.name, input.newName));
|
|
@@ -1125,7 +1146,9 @@ export class TaskRoomApplicationService {
|
|
|
1125
1146
|
throw new ConfigError('rooms: configuration is required before creating or querying rooms');
|
|
1126
1147
|
return createCoworkAdapter({ configPath: cfg.rooms.cowork?.config });
|
|
1127
1148
|
})();
|
|
1128
|
-
|
|
1149
|
+
// Only tasks keyed at creation lead with the key: a room create interrupted across the
|
|
1150
|
+
// upgrade must keep the name its earlier attempt used.
|
|
1151
|
+
const roomName = task.task_id ? deriveTaskRoomName(task.title, task.task_id, task.key && !task.key_from_migration ? task.key : undefined) : task.title;
|
|
1129
1152
|
const unlockSnapshot = template ? acquireLaunchSnapshotLock() : undefined;
|
|
1130
1153
|
let launchTemplate;
|
|
1131
1154
|
let room;
|
package/dist/atomic-file.d.ts
CHANGED
|
@@ -47,6 +47,8 @@ export interface ReplaceOptions {
|
|
|
47
47
|
*/
|
|
48
48
|
durable?: boolean;
|
|
49
49
|
}
|
|
50
|
+
/** Make completed renames in `dir` durable; throws where the platform cannot. */
|
|
51
|
+
export declare function fsyncDirectory(dir: string): void;
|
|
50
52
|
/**
|
|
51
53
|
* Run `fn` holding a cross-process lock. A fully populated private claim directory
|
|
52
54
|
* is atomically renamed to the canonical path, so contenders never observe partial
|
package/dist/atomic-file.js
CHANGED
|
@@ -19,7 +19,8 @@ export class FileLockOwnerUnverifiableError extends Error {
|
|
|
19
19
|
this.pid = pid;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
|
|
22
|
+
/** Make completed renames in `dir` durable; throws where the platform cannot. */
|
|
23
|
+
export function fsyncDirectory(dir) {
|
|
23
24
|
const dirFd = openSync(dir, 'r');
|
|
24
25
|
try {
|
|
25
26
|
fsyncSync(dirFd);
|
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.
|
|
3
|
-
"buildId": "
|
|
4
|
-
"commit": "
|
|
2
|
+
"version": "2.3.1",
|
|
3
|
+
"buildId": "2fbb7f11f70a",
|
|
4
|
+
"commit": "f7b1dda8a2cb911367ce11617ef38759ba78c5e6",
|
|
5
5
|
"dirty": false,
|
|
6
|
-
"builtAt": "2026-10-
|
|
6
|
+
"builtAt": "2026-10-30T19:03:10.573Z",
|
|
7
7
|
"capabilities": [
|
|
8
8
|
"service.refresh-v1",
|
|
9
9
|
"managed-cli.codex-reasoning-effort-v1",
|
package/dist/config.d.ts
CHANGED
|
@@ -272,6 +272,12 @@ export interface FleetConfig {
|
|
|
272
272
|
agentDefinitions?: Record<string, AgentDefinition>;
|
|
273
273
|
/** Canonical inert templates keyed by stable template ID. */
|
|
274
274
|
agentTemplates?: Record<string, AgentTemplateDefinition>;
|
|
275
|
+
/**
|
|
276
|
+
* Named Role selected by each Agent Template (`role: { ref: X }`), captured
|
|
277
|
+
* before roles are flattened to `inline`. Naming provenance only: it is kept
|
|
278
|
+
* outside the definitions so launch fingerprints are unchanged.
|
|
279
|
+
*/
|
|
280
|
+
agentTemplateRoleRefs?: Record<string, string>;
|
|
275
281
|
rolePresets?: Record<string, Record<string, unknown>>;
|
|
276
282
|
brainPresets?: Record<string, Record<string, unknown>>;
|
|
277
283
|
/** Required launch-boundary resolver for effective temporary Agent definitions. */
|
package/dist/config.js
CHANGED
|
@@ -427,7 +427,7 @@ function mergeTemplateDefinition(template, overrides, file, id) {
|
|
|
427
427
|
result[key] = merge(result[key], value, key);
|
|
428
428
|
return result;
|
|
429
429
|
}
|
|
430
|
-
function composeSplitRoles(base, baseDoc, yamlMode, diagnostics, files, additionalAgent, templateSink = {}, roleSink = {}, brainSink = {}) {
|
|
430
|
+
function composeSplitRoles(base, baseDoc, yamlMode, diagnostics, files, additionalAgent, templateSink = {}, roleSink = {}, brainSink = {}, roleRefSink = {}) {
|
|
431
431
|
if (baseDoc.api_version !== V2_API_VERSION)
|
|
432
432
|
throw new ConfigError(`${base}: legacy fleet configuration is unsupported; run 'ours-fleet doctor' and rewrite it as ${V2_API_VERSION}`);
|
|
433
433
|
if ('roles' in baseDoc)
|
|
@@ -472,6 +472,8 @@ function composeSplitRoles(base, baseDoc, yamlMode, diagnostics, files, addition
|
|
|
472
472
|
validateBrainValue(templateBrain.value, templateBrain.file, templateBrain.pointer);
|
|
473
473
|
validateAgentScalars(Object.fromEntries(Object.entries(value).filter(([key]) => key !== 'role' && key !== 'brain')), template.file, id);
|
|
474
474
|
canonicalTemplates.set(id, { file: template.file, value });
|
|
475
|
+
if (isPlainObject(value.role) && typeof value.role.ref === 'string')
|
|
476
|
+
roleRefSink[id] = value.role.ref;
|
|
475
477
|
const operational = deepSubStrict(Object.fromEntries(Object.entries(value).filter(([key]) => key !== 'role' && key !== 'brain')), vars, template.file, `/agent_templates/${id}`);
|
|
476
478
|
templateSink[id] = {
|
|
477
479
|
role: { inline: deepSubStrict(templateRole.value, vars, templateRole.file, templateRole.pointer) },
|
|
@@ -614,6 +616,7 @@ export function loadConfig(configPath, options = {}) {
|
|
|
614
616
|
const files = [];
|
|
615
617
|
const diagnostics = [];
|
|
616
618
|
const agentTemplates = {};
|
|
619
|
+
const agentTemplateRoleRefs = {};
|
|
617
620
|
const rolePresets = {};
|
|
618
621
|
const brainPresets = {};
|
|
619
622
|
let docs = [];
|
|
@@ -634,7 +637,7 @@ export function loadConfig(configPath, options = {}) {
|
|
|
634
637
|
const dd = fleetDDir();
|
|
635
638
|
if (existsSync(dd) && readdirSync(dd).some(f => f.endsWith('.yaml') || f.endsWith('.yml')))
|
|
636
639
|
throw new ConfigError(`${dd}: legacy fleet.d configuration is unsupported; run 'ours-fleet doctor' and move agents to ${splitRootFor(base)}/agents/`);
|
|
637
|
-
docs = [{ file: base, doc: manifestDoc }, ...composeSplitRoles(base, manifestDoc, options.yamlMode ?? 'compat', diagnostics, files, options.additionalAgent, agentTemplates, rolePresets, brainPresets)];
|
|
640
|
+
docs = [{ file: base, doc: manifestDoc }, ...composeSplitRoles(base, manifestDoc, options.yamlMode ?? 'compat', diagnostics, files, options.additionalAgent, agentTemplates, rolePresets, brainPresets, agentTemplateRoleRefs)];
|
|
638
641
|
const baseDoc = manifestDoc;
|
|
639
642
|
const vars = (baseDoc.vars ?? {});
|
|
640
643
|
const defaults = (baseDoc.defaults ?? {});
|
|
@@ -822,7 +825,7 @@ export function loadConfig(configPath, options = {}) {
|
|
|
822
825
|
}
|
|
823
826
|
}
|
|
824
827
|
const result = {
|
|
825
|
-
roles, agentDefinitions, agentTemplates, rolePresets, brainPresets, vars, defaults, files, startStaggerMs, diagnostics, watchdogs,
|
|
828
|
+
roles, agentDefinitions, agentTemplates, agentTemplateRoleRefs, rolePresets, brainPresets, vars, defaults, files, startStaggerMs, diagnostics, watchdogs,
|
|
826
829
|
loops: resolvedLoops.loops,
|
|
827
830
|
rooms, roomTemplates, tasks, ownerInviteFingerprint,
|
|
828
831
|
configMode: 'split-v2',
|