@ours.network/fleet 1.1.0-nightly.8 → 1.1.0
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 +231 -46
- package/dist/application/capabilities.js +18 -6
- package/dist/application/fleet-query-service.js +7 -4
- package/dist/application/role-command-service.js +9 -0
- package/dist/application/role-creation-service.d.ts +4 -4
- package/dist/application/role-creation-service.js +9 -2
- package/dist/application/role-repository.d.ts +4 -2
- package/dist/application/role-repository.js +6 -3
- package/dist/application/session-control.d.ts +3 -1
- package/dist/application/session-control.js +4 -2
- package/dist/application/task-room-service.d.ts +92 -39
- package/dist/application/task-room-service.js +579 -173
- package/dist/briefing.js +85 -28
- package/dist/build-info.json +5 -5
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +10 -0
- package/dist/cli.js +130 -55
- package/dist/config.d.ts +38 -3
- package/dist/config.js +193 -28
- package/dist/creation.d.ts +1 -1
- package/dist/creation.js +7 -4
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +193 -55
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +40 -4
- package/dist/fleet-command-audit.d.ts +59 -9
- package/dist/fleet-command-audit.js +426 -63
- package/dist/fleet-proxy.d.ts +5 -2
- package/dist/harness/claude-code-session.js +2 -1
- package/dist/harness/codex-app-server-proxy.d.ts +40 -0
- package/dist/harness/codex-app-server-proxy.js +348 -14
- package/dist/harness/codex-session.d.ts +9 -2
- package/dist/harness/codex-session.js +18 -2
- package/dist/harness/codex.d.ts +5 -1
- package/dist/harness/codex.js +104 -10
- package/dist/harness/types.d.ts +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/init-guidance.d.ts +1 -1
- package/dist/init-guidance.js +1 -1
- package/dist/init-wizard.d.ts +98 -0
- package/dist/init-wizard.js +631 -0
- package/dist/lifecycle-summary.d.ts +134 -0
- package/dist/lifecycle-summary.js +179 -0
- package/dist/loops/config.d.ts +10 -1
- package/dist/loops/config.js +38 -5
- package/dist/model-recovery.d.ts +1 -1
- package/dist/ops.js +2 -17
- package/dist/owner-channel/channel.d.ts +21 -5
- package/dist/owner-channel/channel.js +312 -138
- package/dist/owner-channel/commands.d.ts +28 -6
- package/dist/owner-channel/commands.js +109 -27
- package/dist/owner-channel/lifecycle-outbox.d.ts +24 -0
- package/dist/owner-channel/lifecycle-outbox.js +87 -0
- package/dist/owner-channel/ours-client.d.ts +19 -0
- package/dist/owner-channel/ours-client.js +3 -0
- package/dist/owner-channel/state.d.ts +2 -0
- package/dist/owner-channel/state.js +8 -2
- package/dist/permissions.js +1 -1
- package/dist/preset-migration.d.ts +42 -0
- package/dist/preset-migration.js +450 -0
- package/dist/reports/artifact.d.ts +10 -0
- package/dist/reports/artifact.js +32 -0
- package/dist/reports/index.d.ts +5 -0
- package/dist/reports/index.js +5 -0
- package/dist/reports/render.d.ts +6 -0
- package/dist/reports/render.js +240 -0
- package/dist/reports/service.d.ts +42 -0
- package/dist/reports/service.js +57 -0
- package/dist/reports/tasks.d.ts +52 -0
- package/dist/reports/tasks.js +177 -0
- package/dist/reports/types.d.ts +315 -0
- package/dist/reports/types.js +6 -0
- package/dist/resolved-plan.js +13 -1
- package/dist/rooms-tasks/cli.d.ts +2 -0
- package/dist/rooms-tasks/cli.js +434 -279
- package/dist/rooms-tasks/close.d.ts +9 -1
- package/dist/rooms-tasks/close.js +23 -7
- package/dist/rooms-tasks/config.js +16 -25
- package/dist/rooms-tasks/cowork-adapter.d.ts +5 -0
- package/dist/rooms-tasks/cowork-adapter.js +13 -0
- package/dist/rooms-tasks/deletion.d.ts +51 -0
- package/dist/rooms-tasks/deletion.js +217 -0
- package/dist/rooms-tasks/external-worker.d.ts +8 -0
- package/dist/rooms-tasks/external-worker.js +38 -0
- package/dist/rooms-tasks/launch-snapshot.d.ts +19 -0
- package/dist/rooms-tasks/launch-snapshot.js +181 -0
- package/dist/rooms-tasks/member-overrides.d.ts +30 -0
- package/dist/rooms-tasks/member-overrides.js +204 -0
- package/dist/rooms-tasks/member-startup.d.ts +1 -0
- package/dist/rooms-tasks/member-startup.js +9 -2
- package/dist/rooms-tasks/provision.js +146 -56
- package/dist/rooms-tasks/room-state.d.ts +1 -0
- package/dist/rooms-tasks/room-state.js +12 -1
- package/dist/rooms-tasks/task-room-name.d.ts +12 -0
- package/dist/rooms-tasks/task-room-name.js +61 -0
- package/dist/rooms-tasks/task-state.d.ts +115 -3
- package/dist/rooms-tasks/task-state.js +361 -11
- package/dist/rooms-tasks/templates.d.ts +4 -1
- package/dist/rooms-tasks/templates.js +34 -3
- package/dist/rooms-tasks/terminal.d.ts +3 -0
- package/dist/rooms-tasks/terminal.js +10 -4
- package/dist/rooms-tasks/types.d.ts +92 -7
- package/dist/rooms-tasks/types.js +14 -1
- package/dist/runner.d.ts +3 -0
- package/dist/runner.js +92 -33
- package/dist/session/acp.d.ts +4 -1
- package/dist/session/acp.js +29 -13
- package/dist/session/arbiter.d.ts +2 -1
- package/dist/session/arbiter.js +2 -0
- package/dist/session/codex-app-server-transport.d.ts +53 -0
- package/dist/session/codex-app-server-transport.js +184 -0
- package/dist/session/codex-app-server.d.ts +122 -0
- package/dist/session/codex-app-server.js +1031 -0
- package/dist/session/control.d.ts +2 -2
- package/dist/session/control.js +18 -2
- package/dist/session/conversation-types.d.ts +4 -4
- package/dist/session/types.d.ts +24 -6
- package/dist/session/types.js +28 -0
- package/dist/spawn.d.ts +9 -0
- package/dist/spawn.js +77 -9
- package/dist/web/fleet-config-service.d.ts +1 -0
- package/dist/web/fleet-config-service.js +25 -7
- package/dist/web/runtime.js +11 -6
- package/dist/web/server.js +28 -1
- package/dist/web-app/assets/{index-BbTc5KtZ.js → index-C0h3ALvs.js} +8 -8
- package/dist/web-app/index.html +1 -1
- package/package.json +4 -4
- package/presets/brain-catalog.json +17 -0
- package/presets/fleet/{agents → agent_templates}/Critic.yaml +2 -0
- package/presets/fleet/{agents/Agent.yaml → agent_templates/Developer.yaml} +3 -1
- package/presets/fleet/agent_templates/LocalCoordinator.yaml +50 -0
- package/presets/fleet/agents/{Developer.yaml → FleetCoordinator.yaml} +1 -1
- package/presets/fleet/brains/claude-fable-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-fable-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-opus-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-high.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-low.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-max.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-medium.yaml +5 -0
- package/presets/fleet/brains/claude-sonnet-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-3-codex-spark-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-mini-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-4-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-5-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-luna-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-sol-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-5-6-terra-xhigh.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-high.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-low.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-max.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-medium.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-ultra.yaml +5 -0
- package/presets/fleet/brains/codex-gpt-6-astra-xhigh.yaml +5 -0
- package/presets/fleet/roles/Coordinator.yaml +37 -0
- package/presets/fleet/roles/Critic.yaml +23 -1
- package/presets/fleet/roles/Developer.yaml +27 -5
- package/presets/fleet/roles/LocalCoordinator.yaml +35 -0
- package/presets/fleet/room_templates/pair.yaml +9 -7
- package/presets/fleet/room_templates/single.yaml +5 -4
- package/presets/fleet/room_templates/team.yaml +12 -8
- package/presets/manifest.json +2 -1
- package/presets/fleet/agents/Architect.yaml +0 -3
- package/presets/fleet/agents/Secretary.yaml +0 -3
- package/presets/fleet/agents/Tester.yaml +0 -3
- package/presets/fleet/roles/Agent.yaml +0 -6
- package/presets/fleet/roles/Architect.yaml +0 -6
- package/presets/fleet/roles/Secretary.yaml +0 -6
- package/presets/fleet/roles/Tester.yaml +0 -6
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { Buffer } from 'node:buffer';
|
|
3
4
|
import { replaceFileAtomically } from './atomic-file.js';
|
|
4
5
|
import { isSensitiveConfigKey } from './sensitive-config.js';
|
|
6
|
+
import { mandatoryConfigurationFits } from './lifecycle-summary.js';
|
|
7
|
+
import { MARKDOWN_MAX_BYTES, MARKDOWN_MAX_CODE_POINTS, markdownCode, markdownProse, } from './rooms-tasks/markdown.js';
|
|
8
|
+
const SESSION_BACKENDS = new Set(['acp', 'codex-app-server']);
|
|
5
9
|
export class FleetCliExit extends Error {
|
|
6
10
|
exitCode;
|
|
7
11
|
outcomeClass;
|
|
@@ -13,15 +17,91 @@ export class FleetCliExit extends Error {
|
|
|
13
17
|
this.effect = effect;
|
|
14
18
|
}
|
|
15
19
|
}
|
|
20
|
+
/** Commands are Owner-silent unless they record one of these semantic lifecycle kinds. */
|
|
21
|
+
export const FLEET_LIFECYCLE_EVENT_KINDS = Object.freeze([
|
|
22
|
+
'agent_started', 'task', 'room', 'lifecycle_failure',
|
|
23
|
+
]);
|
|
24
|
+
/** Stable semantic dedupe basis: resource kind + stable resource ID + transition/action ID. */
|
|
25
|
+
export function lifecycleEventDigestBasis(value) {
|
|
26
|
+
const resourceKind = value.kind === 'lifecycle_failure' ? value.resource : value.kind;
|
|
27
|
+
return `${resourceKind}\0${value.id}\0${value.eventId}`;
|
|
28
|
+
}
|
|
16
29
|
let collection;
|
|
17
|
-
export function beginFleetAuditCollection() { collection = { resourceIds: {} }; }
|
|
30
|
+
export function beginFleetAuditCollection() { collection = { resourceIds: {}, presentations: [] }; }
|
|
31
|
+
let lifecycleCheckpoint;
|
|
32
|
+
export function setFleetAuditLifecycleCheckpoint(checkpoint) { lifecycleCheckpoint = checkpoint; }
|
|
33
|
+
/**
|
|
34
|
+
* Deliver already-recorded lifecycle events while a managed command is still
|
|
35
|
+
* running. On a failed checkpoint the events remain in the final audit batch,
|
|
36
|
+
* so a temporary Owner-channel outage loses timing, never the notice itself.
|
|
37
|
+
*/
|
|
38
|
+
export async function checkpointFleetAuditPresentations() {
|
|
39
|
+
if (!collection || !lifecycleCheckpoint || !collection.presentations.length)
|
|
40
|
+
return;
|
|
41
|
+
const pending = collection.presentations.splice(0);
|
|
42
|
+
try {
|
|
43
|
+
await lifecycleCheckpoint(structuredClone(pending));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
collection.presentations.unshift(...pending);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
18
49
|
export function recordFleetAuditResource(kind, id) {
|
|
19
50
|
if (id && collection)
|
|
20
51
|
collection.resourceIds[kind] = id;
|
|
21
52
|
}
|
|
53
|
+
const PRESENTATION_LABEL_MAX_CODE_POINTS = 160;
|
|
54
|
+
const PRESENTATION_LABEL_MAX_BYTES = 640;
|
|
55
|
+
const PRESENTATION_LABEL_CONTROLS = /[\u0000-\u001f\u007f-\u009f]/gu;
|
|
56
|
+
const PRESENTATION_LABEL_SPOOFING = /[\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff]/gu;
|
|
57
|
+
/** Immutable, single-line human label captured before mutable Task/Room state can disappear. */
|
|
58
|
+
export function fleetPresentationLabel(value) {
|
|
59
|
+
const normalized = String(value ?? '')
|
|
60
|
+
.replace(/\r\n?/gu, '\n')
|
|
61
|
+
.replace(PRESENTATION_LABEL_SPOOFING, '�')
|
|
62
|
+
.replace(PRESENTATION_LABEL_CONTROLS, ' ')
|
|
63
|
+
.replace(/\s+/gu, ' ')
|
|
64
|
+
.trim();
|
|
65
|
+
if (!normalized)
|
|
66
|
+
return undefined;
|
|
67
|
+
if (Array.from(normalized).length <= PRESENTATION_LABEL_MAX_CODE_POINTS
|
|
68
|
+
&& Buffer.byteLength(normalized, 'utf8') <= PRESENTATION_LABEL_MAX_BYTES)
|
|
69
|
+
return normalized;
|
|
70
|
+
let bounded = '';
|
|
71
|
+
for (const point of normalized) {
|
|
72
|
+
const candidate = `${bounded}${point}…`;
|
|
73
|
+
if (Array.from(candidate).length > PRESENTATION_LABEL_MAX_CODE_POINTS
|
|
74
|
+
|| Buffer.byteLength(candidate, 'utf8') > PRESENTATION_LABEL_MAX_BYTES)
|
|
75
|
+
break;
|
|
76
|
+
bounded += point;
|
|
77
|
+
}
|
|
78
|
+
return `${bounded.replace(/\s+$/u, '')}…`;
|
|
79
|
+
}
|
|
22
80
|
export function recordFleetAuditPresentation(value) {
|
|
23
|
-
if (collection)
|
|
24
|
-
|
|
81
|
+
if (collection && FLEET_LIFECYCLE_EVENT_KINDS.includes(value.kind)) {
|
|
82
|
+
const basis = value.eventId ?? (value.kind === 'agent_started' ? value.actionId
|
|
83
|
+
: value.kind === 'task' || value.kind === 'room'
|
|
84
|
+
? `${value.operation}:${value.revision ?? `${value.previousState ?? 'none'}:${value.newState}`}`
|
|
85
|
+
: `${value.category}:${value.state}`);
|
|
86
|
+
const labels = value.kind === 'task'
|
|
87
|
+
? { title: fleetPresentationLabel(value.title), roomName: fleetPresentationLabel(value.roomName),
|
|
88
|
+
reason: fleetPresentationLabel(value.reason) }
|
|
89
|
+
: value.kind === 'room'
|
|
90
|
+
? { name: fleetPresentationLabel(value.name) }
|
|
91
|
+
: value.kind === 'lifecycle_failure'
|
|
92
|
+
? { label: fleetPresentationLabel(value.label) }
|
|
93
|
+
: {};
|
|
94
|
+
const presentation = structuredClone({ ...value, ...labels, eventId: basis });
|
|
95
|
+
const digest = lifecycleEventDigestBasis(presentation);
|
|
96
|
+
if (!collection.presentations.some(existing => lifecycleEventDigestBasis(existing) === digest))
|
|
97
|
+
collection.presentations.push(presentation);
|
|
98
|
+
if (value.kind === 'task')
|
|
99
|
+
collection.resourceIds.task = value.id;
|
|
100
|
+
if (value.kind === 'room')
|
|
101
|
+
collection.resourceIds.room = value.id;
|
|
102
|
+
if (value.kind === 'agent_started')
|
|
103
|
+
collection.resourceIds.agent = value.id;
|
|
104
|
+
}
|
|
25
105
|
}
|
|
26
106
|
export function recordFleetAuditFailure(failure) { if (collection)
|
|
27
107
|
collection.failure = failure; }
|
|
@@ -30,7 +110,7 @@ export function consumeFleetAuditCollection() {
|
|
|
30
110
|
collection = undefined;
|
|
31
111
|
return {
|
|
32
112
|
...(current && Object.keys(current.resourceIds).length ? { resourceIds: current.resourceIds } : {}),
|
|
33
|
-
...(current?.
|
|
113
|
+
...(current?.presentations.length ? { presentations: current.presentations } : {}),
|
|
34
114
|
...(current?.failure ? { failure: current.failure } : {}),
|
|
35
115
|
};
|
|
36
116
|
}
|
|
@@ -39,16 +119,19 @@ const AGENT_SURFACES = {
|
|
|
39
119
|
spawn: new Set(['<none>']),
|
|
40
120
|
template: new Set(['list', 'show', 'validate']),
|
|
41
121
|
task: new Set(['create', 'list', 'lists', 'list-create', 'list-rename', 'list-delete', 'move',
|
|
42
|
-
'show', 'start', 'block', 'unblock', 'review', 'done', 'cancel', 'delete', '
|
|
43
|
-
room: new Set(['create', 'list', 'show', 'open', 'members', 'delete', 'close'
|
|
122
|
+
'show', 'start', 'block', 'unblock', 'review', 'done', 'cancel', 'delete', 'work', 'finish']),
|
|
123
|
+
room: new Set(['create', 'list', 'show', 'open', 'members', 'delete', 'close']),
|
|
44
124
|
};
|
|
45
125
|
export const fleetProxyCommandInventory = Object.freeze(Object.fromEntries(Object.entries(AGENT_SURFACES).map(([surface, commands]) => [surface, [...commands]])));
|
|
46
126
|
const DENIED = new Set([
|
|
47
127
|
'up', 'down', 'restart', 'force-restart', 'attach', 'send', 'loops', 'owner-channel',
|
|
48
|
-
'watchdog-run', 'watchdog-report', 'rm', 'init', 'web',
|
|
128
|
+
'watchdog-run', 'watchdog-report', 'rm', 'init', 'migrate-agent-templates', 'migrate-role-defaults', 'web',
|
|
49
129
|
]);
|
|
50
130
|
export const fleetProxyTopLevelInventory = Object.freeze({
|
|
51
|
-
safeRead: [...SAFE_READ], agent: Object.keys(AGENT_SURFACES),
|
|
131
|
+
safeRead: [...SAFE_READ], agent: Object.keys(AGENT_SURFACES), public: [
|
|
132
|
+
'up', 'down', 'restart', 'force-restart', 'attach', 'send', 'loops', 'owner-channel',
|
|
133
|
+
'watchdog-run', 'watchdog-report', 'rm', 'init', 'web',
|
|
134
|
+
], denied: [...DENIED],
|
|
52
135
|
hidden: ['_run', '_run-temp', '_run-watchdog', '_run-watchdogs'], aliases: ['man'],
|
|
53
136
|
});
|
|
54
137
|
const globalValueOptions = new Set(['-c', '--configuration']);
|
|
@@ -92,18 +175,19 @@ export function classifyFleetArgv(argv) {
|
|
|
92
175
|
if (sub.startsWith('_'))
|
|
93
176
|
return { command: `${command} ${sub}`, route: 'supervisor-proxy/internal', decision: 'deny' };
|
|
94
177
|
if (command !== 'spawn' && !AGENT_SURFACES[command].has(sub))
|
|
95
|
-
return { command: `${command} ${sub}`, route:
|
|
178
|
+
return { command: `${command} ${sub}`, route: `supervisor-proxy/${command}`, decision: 'allow' };
|
|
96
179
|
return { command: sub.startsWith('-') ? command : `${command} ${sub}`,
|
|
97
180
|
route: `supervisor-proxy/${command}`, decision: 'allow' };
|
|
98
181
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
182
|
+
// The proxy is attribution and routing, not a product permission boundary.
|
|
183
|
+
// Commander validates every public command. Hidden process entry points above
|
|
184
|
+
// remain guarded because recursively invoking them breaks the supervisor protocol.
|
|
185
|
+
return { command, route: 'supervisor-proxy/public', decision: 'allow' };
|
|
102
186
|
}
|
|
103
187
|
const marker = (value) => value === '' ? '[REDACTED:empty]' : '[REDACTED:value]';
|
|
104
188
|
const sensitiveValueFlags = new Set([
|
|
105
189
|
'--identity', '--invite', '--token', '--api-token', '--password', '--password-file',
|
|
106
|
-
'--env', '--brief', '--brief-file', '--bio-file', '--persona-file', '--isolation-file',
|
|
190
|
+
'--env', '--brief', '--brief-file', '--bio-file', '--persona-file', '--isolation-file', '--loops-file',
|
|
107
191
|
'--configuration', '-c', '--public-invite', '--public-invite-file', '--invite-file',
|
|
108
192
|
'--summary-file', '--text', '--message', '--summary', '--reason', '--goal', '--cwd',
|
|
109
193
|
'--identity-cid', '--owner-cid', '--contact-cid', '--codex-config', '--add-dir',
|
|
@@ -251,7 +335,7 @@ export class FleetCommandAuditStore {
|
|
|
251
335
|
const prior = { class: attempt.outcome.class, effect: attempt.outcome.effect,
|
|
252
336
|
...(attempt.outcome.exitCode === undefined ? {} : { exitCode: attempt.outcome.exitCode }),
|
|
253
337
|
...(attempt.outcome.resourceIds ? { resourceIds: attempt.outcome.resourceIds } : {}),
|
|
254
|
-
...(attempt.outcome.
|
|
338
|
+
...(attempt.outcome.presentations ? { presentations: attempt.outcome.presentations } : {}) };
|
|
255
339
|
if (JSON.stringify(prior) !== JSON.stringify(outcome))
|
|
256
340
|
throw new Error('conflicting fleet command outcome for existing correlation');
|
|
257
341
|
}
|
|
@@ -293,7 +377,7 @@ export function validateFleetAuditBegin(value) {
|
|
|
293
377
|
}
|
|
294
378
|
export function validateFleetAuditFinish(value) {
|
|
295
379
|
const input = value;
|
|
296
|
-
const allowed = ['correlationId', 'class', 'exitCode', 'effect', 'resourceIds', '
|
|
380
|
+
const allowed = ['correlationId', 'class', 'exitCode', 'effect', 'resourceIds', 'presentations'];
|
|
297
381
|
const resources = input?.resourceIds;
|
|
298
382
|
if (!input || Object.keys(input).some(key => !allowed.includes(key))
|
|
299
383
|
|| typeof input.correlationId !== 'string' || !UUID.test(input.correlationId)
|
|
@@ -302,74 +386,353 @@ export function validateFleetAuditFinish(value) {
|
|
|
302
386
|
|| (resources !== undefined && (!resources || typeof resources !== 'object' || Array.isArray(resources)
|
|
303
387
|
|| Object.entries(resources).some(([key, id]) => !['agent', 'task', 'room'].includes(key)
|
|
304
388
|
|| typeof id !== 'string' || !SAFE_RESOURCE_ID.test(id))))
|
|
305
|
-
|| (input.
|
|
389
|
+
|| (input.presentations !== undefined && (!Array.isArray(input.presentations)
|
|
390
|
+
|| input.presentations.length > 128 || !input.presentations.every(validPresentation))))
|
|
306
391
|
throw new Error('invalid fleet audit finish fields');
|
|
307
392
|
}
|
|
393
|
+
const APPROVAL_MODES = new Set(['ask', 'auto', 'allow', 'deny']);
|
|
394
|
+
const FILESYSTEM_MODES = new Set(['read-only', 'workspace', 'unrestricted']);
|
|
395
|
+
const UNATTENDED_MODES = new Set(['deny', 'wait']);
|
|
396
|
+
const FLEET_PERMISSION_MODES = new Set(['ask', 'auto', 'allow']);
|
|
397
|
+
const PRESENTATION_TEXT = /[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/u;
|
|
398
|
+
/**
|
|
399
|
+
* Exact-key validation of a current-version launch configuration. A partial
|
|
400
|
+
* v1 configuration is rejected; a legacy presentation simply omits the field.
|
|
401
|
+
*/
|
|
402
|
+
function validConfiguration(value) {
|
|
403
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
404
|
+
return false;
|
|
405
|
+
const c = value;
|
|
406
|
+
const text = (v, max = 256) => typeof v === 'string' && v.length <= max
|
|
407
|
+
&& !PRESENTATION_TEXT.test(v);
|
|
408
|
+
const exact = (record, keys) => Object.keys(record).every(key => keys.includes(key));
|
|
409
|
+
const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
410
|
+
const origin = (v) => {
|
|
411
|
+
if (!record(v))
|
|
412
|
+
return false;
|
|
413
|
+
if (v.kind === 'named')
|
|
414
|
+
return exact(v, ['kind', 'ref']) && text(v.ref, 160);
|
|
415
|
+
if (v.kind === 'inline')
|
|
416
|
+
return exact(v, ['kind', 'fingerprint'])
|
|
417
|
+
&& (v.fingerprint === undefined || (typeof v.fingerprint === 'string' && /^[a-f0-9]{12}$/.test(v.fingerprint)));
|
|
418
|
+
return v.kind === 'unknown' && exact(v, ['kind']);
|
|
419
|
+
};
|
|
420
|
+
const count = (v) => v === undefined
|
|
421
|
+
|| (Number.isSafeInteger(v) && Number(v) >= 0 && Number(v) <= 4_096);
|
|
422
|
+
return exact(c, ['version', 'template', 'role', 'brain', 'harness', 'session', 'model',
|
|
423
|
+
'effort', 'mission', 'approval', 'filesystem', 'unattended', 'permissionMode', 'monitor', 'isolation', 'loops'])
|
|
424
|
+
&& c.version === 1 && origin(c.role) && origin(c.brain)
|
|
425
|
+
&& text(c.harness, 64) && SESSION_BACKENDS.has(c.session)
|
|
426
|
+
&& (c.model === null || text(c.model, 160))
|
|
427
|
+
&& (c.template === undefined || text(c.template, 160))
|
|
428
|
+
&& (c.effort === undefined || text(c.effort, 32))
|
|
429
|
+
// The builder caps the mission label at 80 code points; enforce the same
|
|
430
|
+
// invariant here so the per-line budget proof holds for wire input too.
|
|
431
|
+
&& (c.mission === undefined || (text(c.mission) && Array.from(String(c.mission)).length <= 80))
|
|
432
|
+
&& APPROVAL_MODES.has(String(c.approval))
|
|
433
|
+
&& FILESYSTEM_MODES.has(String(c.filesystem))
|
|
434
|
+
&& UNATTENDED_MODES.has(String(c.unattended))
|
|
435
|
+
&& record(c.permissionMode)
|
|
436
|
+
&& exact(c.permissionMode, ['fleetMode', 'nativeMode'])
|
|
437
|
+
&& FLEET_PERMISSION_MODES.has(String(c.permissionMode.fleetMode))
|
|
438
|
+
&& text(c.permissionMode.nativeMode, 160)
|
|
439
|
+
&& record(c.monitor) && exact(c.monitor, ['mode', 'interrupt'])
|
|
440
|
+
&& ['fleet', 'native'].includes(String(c.monitor.mode))
|
|
441
|
+
&& (typeof c.monitor.interrupt === 'boolean' || c.monitor.interrupt === 'after_tool')
|
|
442
|
+
&& (c.isolation === undefined || (record(c.isolation)
|
|
443
|
+
&& exact(c.isolation, ['requested', 'on_unavailable', 'network', 'read_mounts', 'write_mounts'])
|
|
444
|
+
&& ['auto', 'bubblewrap', 'podman', 'none'].includes(String(c.isolation.requested))
|
|
445
|
+
&& (c.isolation.on_unavailable === undefined || ['warn', 'strict'].includes(String(c.isolation.on_unavailable)))
|
|
446
|
+
&& (c.isolation.network === undefined || ['broker', 'deny', 'allow', 'allowlist'].includes(String(c.isolation.network)))
|
|
447
|
+
&& count(c.isolation.read_mounts) && count(c.isolation.write_mounts)))
|
|
448
|
+
&& (c.loops === undefined || (record(c.loops)
|
|
449
|
+
&& exact(c.loops, ['source', 'policy', 'entries'])
|
|
450
|
+
&& ['agent-template', 'cli', 'omitted'].includes(String(c.loops.source))
|
|
451
|
+
&& c.loops.policy === 'skip-if-busy'
|
|
452
|
+
&& Array.isArray(c.loops.entries) && c.loops.entries.length <= 64
|
|
453
|
+
&& c.loops.entries.every(entry => record(entry)
|
|
454
|
+
&& exact(entry, ['name', 'enabled', 'intervalMs', 'initialDelayMs', 'jitterMs', 'prompt'])
|
|
455
|
+
&& text(entry.name, 64) && typeof entry.enabled === 'boolean'
|
|
456
|
+
&& [entry.intervalMs, entry.initialDelayMs, entry.jitterMs].every(Number.isSafeInteger)
|
|
457
|
+
&& record(entry.prompt) && exact(entry.prompt, ['bytes', 'sha256'])
|
|
458
|
+
&& Number.isSafeInteger(entry.prompt.bytes) && Number(entry.prompt.bytes) >= 0
|
|
459
|
+
&& typeof entry.prompt.sha256 === 'string' && /^[a-f0-9]{64}$/.test(entry.prompt.sha256))))
|
|
460
|
+
// Escaping and code-fence growth can push a per-field-valid configuration
|
|
461
|
+
// past the line budget; a v1 configuration whose complete mandatory
|
|
462
|
+
// rendering cannot fit is invalid, never silently trimmed.
|
|
463
|
+
&& mandatoryConfigurationFits(c);
|
|
464
|
+
}
|
|
308
465
|
function validPresentation(value) {
|
|
309
466
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
310
467
|
return false;
|
|
311
468
|
const p = value;
|
|
469
|
+
const configured = (record) => record.configuration === undefined || validConfiguration(record.configuration);
|
|
312
470
|
const safe = (v) => typeof v === 'string' && SAFE_RESOURCE_ID.test(v);
|
|
313
471
|
const text = (v) => typeof v === 'string' && v.length <= 256
|
|
314
472
|
&& !/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/u.test(v);
|
|
473
|
+
const presentationLabel = (v) => typeof v === 'string'
|
|
474
|
+
&& fleetPresentationLabel(v) === v;
|
|
475
|
+
const exact = (record, keys) => Object.keys(record).every(key => keys.includes(key));
|
|
476
|
+
const transition = (allowed) => typeof p.operation === 'string' && typeof p.newState === 'string'
|
|
477
|
+
&& allowed[p.operation]?.includes(`${String(p.previousState ?? 'none')}→${p.newState}`);
|
|
315
478
|
if (p.kind === 'agent_started')
|
|
316
|
-
return
|
|
317
|
-
|
|
479
|
+
return exact(p, ['kind', 'eventId', 'id', 'name', 'lifetime', 'brain', 'role',
|
|
480
|
+
'harness', 'session', 'model', 'permissions', 'parent', 'actionId', 'inherited', 'configuration'])
|
|
481
|
+
&& safe(p.eventId) && safe(p.id) && safe(p.name) && ['permanent', 'temporary'].includes(String(p.lifetime))
|
|
482
|
+
&& text(p.brain) && text(p.role) && safe(p.harness)
|
|
483
|
+
&& SESSION_BACKENDS.has(p.session) && safe(p.parent) && safe(p.actionId)
|
|
318
484
|
&& (p.model === undefined || text(p.model)) && (p.permissions === undefined || text(p.permissions))
|
|
319
|
-
&& Array.isArray(p.inherited) && p.inherited.every(text);
|
|
485
|
+
&& Array.isArray(p.inherited) && p.inherited.every(text) && configured(p);
|
|
320
486
|
if (p.kind === 'task')
|
|
321
|
-
return
|
|
322
|
-
|
|
487
|
+
return exact(p, ['kind', 'operation', 'eventId', 'id', 'title', 'previousState',
|
|
488
|
+
'newState', 'template', 'roomId', 'roomName', 'reason', 'revision', 'list', 'agents'])
|
|
489
|
+
&& ['create', 'start', 'work', 'block', 'unblock', 'review', 'done',
|
|
490
|
+
'cancel', 'finish', 'delete', 'settling'].includes(String(p.operation)) && safe(p.eventId) && safe(p.id) && safe(p.newState)
|
|
491
|
+
&& transition({ create: ['none→backlog', 'none→provisioning', 'none→active'],
|
|
492
|
+
start: ['backlog→provisioning'], work: ['provisioning→active'],
|
|
493
|
+
block: ['backlog→backlog', 'provisioning→provisioning', 'active→active', 'review→review'],
|
|
494
|
+
unblock: ['backlog→backlog', 'provisioning→provisioning', 'active→active', 'review→review'],
|
|
495
|
+
review: ['active→review'], done: ['review→done'], finish: ['active→done', 'review→done'],
|
|
496
|
+
cancel: ['backlog→cancelled', 'provisioning→cancelled', 'active→cancelled', 'review→cancelled'],
|
|
497
|
+
delete: [
|
|
498
|
+
'backlog→deleting', 'provisioning→deleting', 'active→deleting', 'review→deleting',
|
|
499
|
+
'done→deleting', 'cancelled→deleting', 'failed→deleting',
|
|
500
|
+
'backlog→deleted', 'provisioning→deleted', 'active→deleted', 'review→deleted',
|
|
501
|
+
'done→deleted', 'cancelled→deleted', 'failed→deleted',
|
|
502
|
+
], settling: ['active→active', 'review→review', 'backlog→backlog',
|
|
503
|
+
'provisioning→provisioning'] })
|
|
504
|
+
&& (p.title === undefined || presentationLabel(p.title))
|
|
505
|
+
&& (p.roomName === undefined || presentationLabel(p.roomName))
|
|
506
|
+
&& (p.reason === undefined || presentationLabel(p.reason))
|
|
507
|
+
&& (p.previousState === undefined || safe(p.previousState))
|
|
323
508
|
&& (p.template === undefined || text(p.template)) && (p.roomId === undefined || safe(p.roomId))
|
|
509
|
+
&& (p.list === undefined || text(p.list)) && (p.revision === undefined || text(p.revision))
|
|
324
510
|
&& Array.isArray(p.agents) && p.agents.length <= 64
|
|
325
|
-
&& p.agents.every(a => a && typeof a === 'object'
|
|
511
|
+
&& p.agents.every(a => a && typeof a === 'object'
|
|
512
|
+
&& exact(a, ['name', 'brain', 'role', 'permissions', 'configuration'])
|
|
513
|
+
&& safe(a.name)
|
|
326
514
|
&& text(a.role)
|
|
327
|
-
&& (a.brain === undefined || text(a.brain))
|
|
515
|
+
&& (a.brain === undefined || text(a.brain))
|
|
516
|
+
&& (a.permissions === undefined || text(a.permissions))
|
|
517
|
+
&& configured(a));
|
|
328
518
|
if (p.kind === 'room')
|
|
329
|
-
return
|
|
519
|
+
return exact(p, ['kind', 'operation', 'eventId', 'id', 'previousState', 'newState',
|
|
520
|
+
'revision', 'name', 'template', 'taskId', 'anonymous', 'memberCount', 'ownerAttached', 'participants'])
|
|
521
|
+
&& ['create', 'activate', 'close', 'delete'].includes(String(p.operation))
|
|
522
|
+
&& safe(p.eventId) && safe(p.id) && safe(p.newState)
|
|
523
|
+
&& transition({ create: ['none→provisioning'], activate: ['provisioning→active'],
|
|
524
|
+
close: ['active→closing', 'provisioning→closing'],
|
|
525
|
+
delete: ['closing→deleted'] })
|
|
330
526
|
&& (p.previousState === undefined || safe(p.previousState))
|
|
331
|
-
&& (p.template === undefined || text(p.template))
|
|
527
|
+
&& (p.name === undefined || presentationLabel(p.name)) && (p.template === undefined || text(p.template))
|
|
528
|
+
&& (p.taskId === undefined || safe(p.taskId)) && (p.revision === undefined || text(p.revision))
|
|
529
|
+
&& (p.anonymous === undefined || typeof p.anonymous === 'boolean')
|
|
530
|
+
&& (p.ownerAttached === undefined || typeof p.ownerAttached === 'boolean')
|
|
531
|
+
&& (p.memberCount === undefined || (Number.isSafeInteger(p.memberCount)
|
|
532
|
+
&& Number(p.memberCount) >= 0 && Number(p.memberCount) <= 64))
|
|
332
533
|
&& Array.isArray(p.participants) && p.participants.length <= 64
|
|
333
|
-
&& p.participants.every(a => a && typeof a === 'object'
|
|
334
|
-
&&
|
|
534
|
+
&& p.participants.every(a => a && typeof a === 'object'
|
|
535
|
+
&& exact(a, ['name', 'id', 'brain', 'role', 'permissions', 'configuration'])
|
|
536
|
+
&& safe(a.name)
|
|
537
|
+
&& text(a.role)
|
|
538
|
+
&& (a.id === undefined || safe(a.id))
|
|
539
|
+
&& (a.brain === undefined || text(a.brain))
|
|
540
|
+
&& (a.permissions === undefined || text(a.permissions))
|
|
541
|
+
&& configured(a));
|
|
542
|
+
if (p.kind === 'lifecycle_failure')
|
|
543
|
+
return exact(p, ['kind', 'eventId', 'resource', 'id', 'label', 'state', 'category'])
|
|
544
|
+
&& ['Agent', 'Task', 'Room'].includes(String(p.resource))
|
|
545
|
+
&& safe(p.eventId) && safe(p.id) && safe(p.state)
|
|
546
|
+
&& (p.label === undefined || presentationLabel(p.label))
|
|
547
|
+
&& ['provision_failed', 'provision_pending', 'readiness_failed', 'settlement_failed',
|
|
548
|
+
'settlement_pending', 'cleanup_failed', 'cleanup_pending'].includes(String(p.category));
|
|
335
549
|
return false;
|
|
336
550
|
}
|
|
337
|
-
export function
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
551
|
+
export function validateFleetAuditPresentations(value) {
|
|
552
|
+
if (!Array.isArray(value) || value.length < 1 || value.length > 128
|
|
553
|
+
|| !value.every(validPresentation))
|
|
554
|
+
throw new Error('invalid Fleet lifecycle presentations');
|
|
555
|
+
}
|
|
556
|
+
const messageCodePoints = (value) => Array.from(value).length;
|
|
557
|
+
const messageBytes = (value) => Buffer.byteLength(value, 'utf8');
|
|
558
|
+
const withinMessageBounds = (value) => messageCodePoints(value) <= MARKDOWN_MAX_CODE_POINTS && messageBytes(value) <= MARKDOWN_MAX_BYTES;
|
|
559
|
+
function titleCase(value) {
|
|
560
|
+
return value ? `${value[0].toUpperCase()}${value.slice(1)}` : value;
|
|
561
|
+
}
|
|
562
|
+
function commaInteger(value) {
|
|
563
|
+
return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
564
|
+
}
|
|
565
|
+
function renderStructuredMessage(headline, fields, action) {
|
|
566
|
+
return `${headline}`
|
|
567
|
+
+ fields.map(([label, value]) => `\n- **${label}:** ${value}`).join('')
|
|
568
|
+
+ (action ? `\n\n${action}` : '');
|
|
569
|
+
}
|
|
570
|
+
function structuredMessage(headline, fields, action) {
|
|
571
|
+
const visible = fields.filter((field) => field[1] !== undefined);
|
|
572
|
+
const render = (selected) => renderStructuredMessage(headline, selected, action);
|
|
573
|
+
const complete = render(visible);
|
|
574
|
+
if (withinMessageBounds(complete))
|
|
575
|
+
return complete;
|
|
576
|
+
const marker = ['Details omitted', 'Additional lifecycle details'];
|
|
577
|
+
const selected = [];
|
|
578
|
+
for (const field of visible) {
|
|
579
|
+
if (!withinMessageBounds(render([...selected, field, marker])))
|
|
580
|
+
break;
|
|
581
|
+
selected.push(field);
|
|
582
|
+
}
|
|
583
|
+
const bounded = render([...selected, marker]);
|
|
584
|
+
if (!withinMessageBounds(bounded))
|
|
585
|
+
throw new Error('mandatory lifecycle presentation exceeds message bounds');
|
|
586
|
+
return bounded;
|
|
587
|
+
}
|
|
588
|
+
function originFields(label, origin) {
|
|
589
|
+
if (origin.kind === 'named')
|
|
590
|
+
return [[label, `Preset ${markdownCode(origin.ref)}`]];
|
|
591
|
+
if (origin.kind === 'inline')
|
|
592
|
+
return [
|
|
593
|
+
[label, 'Inline definition'],
|
|
594
|
+
...(origin.fingerprint ? [[`${label} fingerprint`, markdownCode(origin.fingerprint)]] : []),
|
|
595
|
+
];
|
|
596
|
+
return [[label, 'Unknown']];
|
|
342
597
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
throw new Error('fleet command audit outcome is absent');
|
|
346
|
-
return ['🧾 Fleet command outcome', `Correlation: ${attempt.correlationId}`, `Agent: ${attempt.caller}`,
|
|
347
|
-
`Route: ${attempt.classification.route}`, `Fleet action: ${attempt.classification.command}`,
|
|
348
|
-
`Result: ${attempt.outcome.class}`, `Effect: ${attempt.outcome.effect}`,
|
|
349
|
-
...(attempt.outcome.exitCode === undefined ? [] : [`Exit: ${attempt.outcome.exitCode}`]),
|
|
350
|
-
...Object.entries(attempt.outcome.resourceIds ?? {}).sort(([a], [b]) => a.localeCompare(b))
|
|
351
|
-
.map(([kind, id]) => `${kind[0]?.toUpperCase()}${kind.slice(1)} ID: ${id}`),
|
|
352
|
-
`Completed: ${attempt.outcome.completedAt}`, `Raw argv (redacted): ${JSON.stringify(attempt.argv)}`,
|
|
353
|
-
...renderPresentation(attempt.outcome.presentation)].join('\n');
|
|
598
|
+
function interruptSummary(value) {
|
|
599
|
+
return value === false ? 'off' : value === true ? 'on' : markdownProse(value);
|
|
354
600
|
}
|
|
355
|
-
function
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
601
|
+
function agentConfigurationFieldGroups(configuration) {
|
|
602
|
+
const optional = [
|
|
603
|
+
[['Monitor mode', markdownProse(configuration.monitor.mode)],
|
|
604
|
+
['Interrupt', interruptSummary(configuration.monitor.interrupt)]],
|
|
605
|
+
];
|
|
606
|
+
if (configuration.isolation)
|
|
607
|
+
optional.push([
|
|
608
|
+
['Isolation', markdownProse(configuration.isolation.requested)],
|
|
609
|
+
['Network', configuration.isolation.network ? markdownProse(configuration.isolation.network) : undefined],
|
|
610
|
+
['If unavailable', configuration.isolation.on_unavailable
|
|
611
|
+
? configuration.isolation.on_unavailable === 'strict' ? 'Fail' : 'Warn' : undefined],
|
|
612
|
+
['Read-only mounts', String(configuration.isolation.read_mounts ?? 0)],
|
|
613
|
+
['Read-write mounts', String(configuration.isolation.write_mounts ?? 0)],
|
|
614
|
+
]);
|
|
615
|
+
for (const entry of configuration.loops?.entries ?? [])
|
|
616
|
+
optional.push([
|
|
617
|
+
['Loop', markdownCode(entry.name)],
|
|
618
|
+
['Loop status', entry.enabled ? 'Enabled' : 'Disabled'],
|
|
619
|
+
['Loop interval', `${commaInteger(entry.intervalMs)} ms`],
|
|
620
|
+
['Loop delay', `${commaInteger(entry.initialDelayMs)} ms`],
|
|
621
|
+
['Loop jitter', `${commaInteger(entry.jitterMs)} ms`],
|
|
622
|
+
['Loop prompt size', `${entry.prompt.bytes} B`],
|
|
623
|
+
['Loop prompt hash', markdownCode(entry.prompt.sha256.slice(0, 12))],
|
|
624
|
+
]);
|
|
625
|
+
return { mandatory: [
|
|
626
|
+
['Template', configuration.template ? markdownCode(configuration.template) : undefined],
|
|
627
|
+
...originFields('Role', configuration.role),
|
|
628
|
+
['Mission', configuration.mission ? `“${markdownProse(configuration.mission)}”` : undefined],
|
|
629
|
+
...originFields('Brain', configuration.brain),
|
|
630
|
+
['Harness', markdownCode(configuration.harness)],
|
|
631
|
+
['Model', configuration.model === null ? 'Harness default' : markdownCode(configuration.model)],
|
|
632
|
+
['Effort', configuration.effort ? markdownProse(configuration.effort) : undefined],
|
|
633
|
+
['Approval', markdownProse(configuration.approval)],
|
|
634
|
+
['Filesystem', markdownProse(configuration.filesystem)],
|
|
635
|
+
['Wait', markdownProse(configuration.unattended)],
|
|
636
|
+
['Fleet mode', markdownProse(configuration.permissionMode.fleetMode)],
|
|
637
|
+
['Native mode', markdownProse(configuration.permissionMode.nativeMode)],
|
|
638
|
+
], optional, tail: [
|
|
639
|
+
['Loop policy', configuration.loops ? 'Skip if busy' : undefined],
|
|
640
|
+
['Loop source', configuration.loops
|
|
641
|
+
? markdownProse(configuration.loops.source.replace(/-/g, ' ')) : undefined],
|
|
642
|
+
] };
|
|
643
|
+
}
|
|
644
|
+
function boundedAgentMessage(headline, prefix, optionalGroups, suffix, action) {
|
|
645
|
+
const visible = (fields) => fields.filter((field) => field[1] !== undefined);
|
|
646
|
+
const requiredPrefix = visible(prefix);
|
|
647
|
+
const requiredSuffix = visible(suffix);
|
|
648
|
+
const groups = optionalGroups.map(visible).filter(group => group.length > 0);
|
|
649
|
+
const marker = ['Details omitted', 'Additional optional Agent configuration'];
|
|
650
|
+
const render = (optional, omitted) => renderStructuredMessage(headline, [...requiredPrefix, ...optional, ...(omitted ? [marker] : []), ...requiredSuffix], action);
|
|
651
|
+
const all = groups.flat();
|
|
652
|
+
const complete = render(all, false);
|
|
653
|
+
if (withinMessageBounds(complete))
|
|
654
|
+
return complete;
|
|
655
|
+
const included = [];
|
|
656
|
+
for (const group of groups) {
|
|
657
|
+
const candidate = render([...included, ...group], true);
|
|
658
|
+
if (!withinMessageBounds(candidate))
|
|
659
|
+
break;
|
|
660
|
+
included.push(...group);
|
|
661
|
+
}
|
|
662
|
+
const bounded = render(included, true);
|
|
663
|
+
if (!withinMessageBounds(bounded))
|
|
664
|
+
throw new Error('mandatory Agent presentation exceeds message bounds');
|
|
665
|
+
return bounded;
|
|
666
|
+
}
|
|
667
|
+
/** Compact Owner presentation. It intentionally has no command, argv, environment, or correlation data. */
|
|
668
|
+
export function renderFleetLifecycleEvent(value) {
|
|
669
|
+
if (value.kind === 'lifecycle_failure') {
|
|
670
|
+
const label = markdownProse(value.label ?? value.id);
|
|
671
|
+
if (value.category === 'provision_failed')
|
|
672
|
+
return structuredMessage(`⚠️ Couldn’t prepare ${value.resource} “${label}”`, [['Status', 'Provisioning failed'], ['Resource', value.resource], [`${value.resource} ID`, markdownCode(value.id)]], `**Next:** Check the Fleet service logs, correct the configuration, then run the same ${value.resource === 'Room' ? 'create' : 'start'} command again.`);
|
|
673
|
+
if (value.category === 'provision_pending')
|
|
674
|
+
return structuredMessage(`⏳ ${value.resource} “${label}” is still getting ready`, [['Status', 'Waiting for Agents'], ['Resource', value.resource], [`${value.resource} ID`, markdownCode(value.id)]], value.resource === 'Task'
|
|
675
|
+
? `**Next:** Check member readiness, then run ${markdownCode(`task start ${value.id}`)}.`
|
|
676
|
+
: '**Next:** Check member readiness, then repeat the same Room command.');
|
|
677
|
+
if (value.category === 'readiness_failed')
|
|
678
|
+
return structuredMessage(`⚠️ Agent didn’t become ready: ${label}`, [['Status', 'Readiness failed'], ['Agent ID', markdownCode(value.id)]], '**Next:** Check the Agent log, correct its configuration, then create it again.');
|
|
679
|
+
if (value.resource === 'Task')
|
|
680
|
+
return structuredMessage(`⚠️ Task cleanup is incomplete: ${label}`, [['Status', 'Finish pending'], ['Task ID', markdownCode(value.id)]], '**Next:** Check the Fleet service logs, then repeat the same `done`, `finish`, or `cancel` command.');
|
|
681
|
+
return structuredMessage(`⚠️ Room cleanup is incomplete: ${label}`, [['Status', 'Deletion pending'], ['Room ID', markdownCode(value.id)]], '**Next:** Check the Fleet service logs, then repeat the same confirmed Room delete command.');
|
|
682
|
+
}
|
|
683
|
+
if (value.kind === 'agent_started') {
|
|
684
|
+
const prefix = [
|
|
685
|
+
['Started by', markdownProse(value.parent)],
|
|
686
|
+
['Lifetime', titleCase(value.lifetime)],
|
|
687
|
+
];
|
|
688
|
+
if (value.configuration) {
|
|
689
|
+
const configuration = agentConfigurationFieldGroups(value.configuration);
|
|
690
|
+
return boundedAgentMessage(`🚀 Agent launched: ${markdownProse(value.name)}`, [...prefix, ...configuration.mandatory], configuration.optional, [...configuration.tail, ['Agent ID', markdownCode(value.id)]], 'Launch was accepted; harness and room-seat readiness converge separately.');
|
|
691
|
+
}
|
|
692
|
+
return structuredMessage(`🚀 Agent launched: ${markdownProse(value.name)}`, [...prefix,
|
|
693
|
+
['Resolved details', 'Unavailable'],
|
|
694
|
+
['Role', markdownProse(value.role)],
|
|
695
|
+
['Brain', markdownProse(value.brain)],
|
|
696
|
+
...(value.permissions ? [['Permissions', markdownProse(value.permissions)]] : []),
|
|
697
|
+
['Agent ID', markdownCode(value.id)]], 'Launch was accepted; harness and room-seat readiness converge separately.');
|
|
698
|
+
}
|
|
699
|
+
if (value.kind === 'task') {
|
|
700
|
+
const title = markdownProse(value.title ?? value.id);
|
|
701
|
+
const id = markdownCode(value.id);
|
|
702
|
+
const list = value.list ? markdownProse(value.list) : undefined;
|
|
703
|
+
if (value.operation === 'create' && value.newState === 'backlog')
|
|
704
|
+
return structuredMessage('✅ Task added to Backlog', [['Task', `“${title}”`], ['Task ID', id], ['List', list],
|
|
705
|
+
['Template', value.template ? markdownCode(value.template) : undefined]]);
|
|
706
|
+
if (value.operation === 'create' || value.operation === 'start' || value.operation === 'work')
|
|
707
|
+
return structuredMessage(`🚀 Task started: ${title}`, [['Status', 'Active'], ['List', list], ['Room', value.roomId
|
|
708
|
+
? markdownProse(value.roomName ?? value.roomId) : undefined],
|
|
709
|
+
['Room ID', value.roomId ? markdownCode(value.roomId) : undefined],
|
|
710
|
+
['Team', `${value.agents.length} Agent${value.agents.length === 1 ? '' : 's'} ready`], ['Task ID', id]], 'The task room and its Agents are ready to work.');
|
|
711
|
+
if (value.operation === 'block')
|
|
712
|
+
return structuredMessage(`⛔ Task blocked: ${title}`, [['Status', titleCase(value.newState)], ['Blocked', 'Yes'],
|
|
713
|
+
['Reason', value.reason ? markdownProse(value.reason) : undefined], ['Task ID', id]]);
|
|
714
|
+
if (value.operation === 'unblock')
|
|
715
|
+
return structuredMessage(`✅ Task unblocked: ${title}`, [['Status', titleCase(value.newState)], ['Task ID', id]]);
|
|
716
|
+
if (value.operation === 'review')
|
|
717
|
+
return structuredMessage(`🔎 Task ready for review: ${title}`, [['List', list], ['Task ID', id]]);
|
|
718
|
+
if (value.operation === 'done' || value.operation === 'finish')
|
|
719
|
+
return structuredMessage(`🎉 Task completed: ${title}`, [['Status', 'Done'], ['List', list], ['Task ID', id]], 'The task room and temporary Agents have been cleaned up.');
|
|
720
|
+
if (value.operation === 'cancel')
|
|
721
|
+
return structuredMessage(`🚫 Task cancelled: ${title}`, [['Status', 'Cancelled'], ['List', list], ['Task ID', id]], 'The task room and temporary Agents have been cleaned up.');
|
|
722
|
+
if (value.operation === 'delete' && value.newState === 'deleting')
|
|
723
|
+
return structuredMessage(`🗑️ Task deletion started: ${title}`, [['Current status', 'Deleting'], ['Task ID', id]], 'Fleet is removing the linked room and temporary Agents in the background.');
|
|
724
|
+
if (value.operation === 'delete')
|
|
725
|
+
return structuredMessage(`🗑️ Task deleted: ${title}`, [['Task ID', id]], 'Temporary Agents were cleaned up.');
|
|
726
|
+
return structuredMessage(`⚠️ Task cleanup is incomplete: ${title}`, [['Status', 'Finish pending'], ['Task ID', id]], '**Next:** Check the Fleet service logs, then repeat the same `done`, `finish`, or `cancel` command.');
|
|
727
|
+
}
|
|
728
|
+
const name = markdownProse(value.name ?? value.id);
|
|
729
|
+
if (value.operation === 'activate')
|
|
730
|
+
return structuredMessage(`🏠 Room ready: ${name}`, [['Status', 'Active'], ['Template', value.template ? markdownCode(value.template) : undefined],
|
|
731
|
+
['Team', `${value.memberCount ?? value.participants.length} Agent${(value.memberCount ?? value.participants.length) === 1 ? '' : 's'} ready`],
|
|
732
|
+
['Room ID', markdownCode(value.id)]]);
|
|
733
|
+
if (value.operation === 'delete')
|
|
734
|
+
return structuredMessage(`🗑️ Room deleted: ${name}`, [['Room ID', markdownCode(value.id)]], 'Temporary Agents were cleaned up.');
|
|
735
|
+
if (value.operation === 'close')
|
|
736
|
+
return structuredMessage(`🗑️ Room deletion started: ${name}`, [['Current status', 'Deleting'], ['Room ID', markdownCode(value.id)]]);
|
|
737
|
+
return structuredMessage(`⏳ Room “${name}” is still getting ready`, [['Status', 'Waiting for Agents'], ['Resource', 'Room'], ['Room ID', markdownCode(value.id)]], '**Next:** Check member readiness, then repeat the same Room command.');
|
|
375
738
|
}
|
package/dist/fleet-proxy.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { MonitorConfig, ResolvedRole } from './config.js';
|
|
1
|
+
import type { MonitorConfig, ResolvedRole, SessionBackendId } from './config.js';
|
|
2
|
+
import type { AgentLaunchConfiguration } from './lifecycle-summary.js';
|
|
2
3
|
import type { SpawnOpts } from './spawn.js';
|
|
3
4
|
/** Present only inside a managed role process. The CLI treats it as a routing hint, not authority. */
|
|
4
5
|
export declare const FLEET_PROXY_STATE_DIR_ENV = "OURS_FLEET_PROXY_STATE_DIR";
|
|
@@ -9,7 +10,7 @@ export interface ManagedFleetSpawnResult {
|
|
|
9
10
|
lifetime: 'permanent' | 'temporary';
|
|
10
11
|
statePath: string;
|
|
11
12
|
harness: string;
|
|
12
|
-
session:
|
|
13
|
+
session: SessionBackendId;
|
|
13
14
|
model?: string;
|
|
14
15
|
monitor: Pick<MonitorConfig, 'mode' | 'interrupt'>;
|
|
15
16
|
/** Adapter-resolved portable policy and exact native runtime mode. */
|
|
@@ -21,6 +22,8 @@ export interface ManagedFleetSpawnResult {
|
|
|
21
22
|
creationActionId: string;
|
|
22
23
|
brainSummary: string;
|
|
23
24
|
roleSummary: string;
|
|
25
|
+
/** Launch configuration captured from the exact resolved role; absent only from pre-upgrade daemons. */
|
|
26
|
+
configuration?: AgentLaunchConfiguration;
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
29
|
* Fill only omitted spawn settings from the live caller. Explicit agent choices
|
|
@@ -21,7 +21,8 @@ export class ClaudeCodeAgentSessionAdapter {
|
|
|
21
21
|
start(options) {
|
|
22
22
|
const { role, prep, launch } = options;
|
|
23
23
|
return this.transport({
|
|
24
|
-
name: role.name,
|
|
24
|
+
name: role.name, harness: role.harness,
|
|
25
|
+
argv: launch.argv, cwd: options.cwd, env: launch.env,
|
|
25
26
|
stateDir: options.stateDir, mode: options.mode, permissions: options.permissions,
|
|
26
27
|
modeId: this.strategy.permissionModeId(role),
|
|
27
28
|
mcpServers: this.strategy.mcpServers(role),
|