@ours.network/fleet 1.1.0-nightly.9 → 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 +8 -1
- 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 +562 -289
- package/dist/briefing.js +79 -22
- 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 +129 -47
- 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 +181 -41
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +40 -4
- package/dist/fleet-command-audit.d.ts +22 -1
- package/dist/fleet-command-audit.js +355 -50
- 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 +19 -3
- package/dist/owner-channel/channel.js +301 -120
- 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/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 +361 -285
- 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 -83
- 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 +91 -44
- 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 +1 -1
- package/dist/session/control.js +17 -1
- 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;
|
|
@@ -24,17 +28,73 @@ export function lifecycleEventDigestBasis(value) {
|
|
|
24
28
|
}
|
|
25
29
|
let collection;
|
|
26
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
|
+
}
|
|
27
49
|
export function recordFleetAuditResource(kind, id) {
|
|
28
50
|
if (id && collection)
|
|
29
51
|
collection.resourceIds[kind] = id;
|
|
30
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
|
+
}
|
|
31
80
|
export function recordFleetAuditPresentation(value) {
|
|
32
81
|
if (collection && FLEET_LIFECYCLE_EVENT_KINDS.includes(value.kind)) {
|
|
33
82
|
const basis = value.eventId ?? (value.kind === 'agent_started' ? value.actionId
|
|
34
83
|
: value.kind === 'task' || value.kind === 'room'
|
|
35
84
|
? `${value.operation}:${value.revision ?? `${value.previousState ?? 'none'}:${value.newState}`}`
|
|
36
85
|
: `${value.category}:${value.state}`);
|
|
37
|
-
|
|
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);
|
|
38
98
|
if (value.kind === 'task')
|
|
39
99
|
collection.resourceIds.task = value.id;
|
|
40
100
|
if (value.kind === 'room')
|
|
@@ -59,15 +119,19 @@ const AGENT_SURFACES = {
|
|
|
59
119
|
spawn: new Set(['<none>']),
|
|
60
120
|
template: new Set(['list', 'show', 'validate']),
|
|
61
121
|
task: new Set(['create', 'list', 'lists', 'list-create', 'list-rename', 'list-delete', 'move',
|
|
62
|
-
'show', 'start', 'block', 'unblock', 'review', 'done', 'cancel', 'delete', '
|
|
63
|
-
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']),
|
|
64
124
|
};
|
|
65
125
|
export const fleetProxyCommandInventory = Object.freeze(Object.fromEntries(Object.entries(AGENT_SURFACES).map(([surface, commands]) => [surface, [...commands]])));
|
|
126
|
+
const DENIED = new Set([
|
|
127
|
+
'up', 'down', 'restart', 'force-restart', 'attach', 'send', 'loops', 'owner-channel',
|
|
128
|
+
'watchdog-run', 'watchdog-report', 'rm', 'init', 'migrate-agent-templates', 'migrate-role-defaults', 'web',
|
|
129
|
+
]);
|
|
66
130
|
export const fleetProxyTopLevelInventory = Object.freeze({
|
|
67
131
|
safeRead: [...SAFE_READ], agent: Object.keys(AGENT_SURFACES), public: [
|
|
68
132
|
'up', 'down', 'restart', 'force-restart', 'attach', 'send', 'loops', 'owner-channel',
|
|
69
133
|
'watchdog-run', 'watchdog-report', 'rm', 'init', 'web',
|
|
70
|
-
], denied: [],
|
|
134
|
+
], denied: [...DENIED],
|
|
71
135
|
hidden: ['_run', '_run-temp', '_run-watchdog', '_run-watchdogs'], aliases: ['man'],
|
|
72
136
|
});
|
|
73
137
|
const globalValueOptions = new Set(['-c', '--configuration']);
|
|
@@ -123,7 +187,7 @@ export function classifyFleetArgv(argv) {
|
|
|
123
187
|
const marker = (value) => value === '' ? '[REDACTED:empty]' : '[REDACTED:value]';
|
|
124
188
|
const sensitiveValueFlags = new Set([
|
|
125
189
|
'--identity', '--invite', '--token', '--api-token', '--password', '--password-file',
|
|
126
|
-
'--env', '--brief', '--brief-file', '--bio-file', '--persona-file', '--isolation-file',
|
|
190
|
+
'--env', '--brief', '--brief-file', '--bio-file', '--persona-file', '--isolation-file', '--loops-file',
|
|
127
191
|
'--configuration', '-c', '--public-invite', '--public-invite-file', '--invite-file',
|
|
128
192
|
'--summary-file', '--text', '--message', '--summary', '--reason', '--goal', '--cwd',
|
|
129
193
|
'--identity-cid', '--owner-cid', '--contact-cid', '--codex-config', '--add-dir',
|
|
@@ -326,26 +390,102 @@ export function validateFleetAuditFinish(value) {
|
|
|
326
390
|
|| input.presentations.length > 128 || !input.presentations.every(validPresentation))))
|
|
327
391
|
throw new Error('invalid fleet audit finish fields');
|
|
328
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
|
+
}
|
|
329
465
|
function validPresentation(value) {
|
|
330
466
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
331
467
|
return false;
|
|
332
468
|
const p = value;
|
|
469
|
+
const configured = (record) => record.configuration === undefined || validConfiguration(record.configuration);
|
|
333
470
|
const safe = (v) => typeof v === 'string' && SAFE_RESOURCE_ID.test(v);
|
|
334
471
|
const text = (v) => typeof v === 'string' && v.length <= 256
|
|
335
472
|
&& !/[\u0000-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/u.test(v);
|
|
473
|
+
const presentationLabel = (v) => typeof v === 'string'
|
|
474
|
+
&& fleetPresentationLabel(v) === v;
|
|
336
475
|
const exact = (record, keys) => Object.keys(record).every(key => keys.includes(key));
|
|
337
476
|
const transition = (allowed) => typeof p.operation === 'string' && typeof p.newState === 'string'
|
|
338
477
|
&& allowed[p.operation]?.includes(`${String(p.previousState ?? 'none')}→${p.newState}`);
|
|
339
478
|
if (p.kind === 'agent_started')
|
|
340
479
|
return exact(p, ['kind', 'eventId', 'id', 'name', 'lifetime', 'brain', 'role',
|
|
341
|
-
'harness', 'session', 'model', 'permissions', 'parent', 'actionId', 'inherited'])
|
|
480
|
+
'harness', 'session', 'model', 'permissions', 'parent', 'actionId', 'inherited', 'configuration'])
|
|
342
481
|
&& safe(p.eventId) && safe(p.id) && safe(p.name) && ['permanent', 'temporary'].includes(String(p.lifetime))
|
|
343
|
-
&& text(p.brain) && text(p.role) && safe(p.harness)
|
|
482
|
+
&& text(p.brain) && text(p.role) && safe(p.harness)
|
|
483
|
+
&& SESSION_BACKENDS.has(p.session) && safe(p.parent) && safe(p.actionId)
|
|
344
484
|
&& (p.model === undefined || text(p.model)) && (p.permissions === undefined || text(p.permissions))
|
|
345
|
-
&& Array.isArray(p.inherited) && p.inherited.every(text);
|
|
485
|
+
&& Array.isArray(p.inherited) && p.inherited.every(text) && configured(p);
|
|
346
486
|
if (p.kind === 'task')
|
|
347
487
|
return exact(p, ['kind', 'operation', 'eventId', 'id', 'title', 'previousState',
|
|
348
|
-
'newState', 'template', 'roomId', 'revision', 'list', 'agents'])
|
|
488
|
+
'newState', 'template', 'roomId', 'roomName', 'reason', 'revision', 'list', 'agents'])
|
|
349
489
|
&& ['create', 'start', 'work', 'block', 'unblock', 'review', 'done',
|
|
350
490
|
'cancel', 'finish', 'delete', 'settling'].includes(String(p.operation)) && safe(p.eventId) && safe(p.id) && safe(p.newState)
|
|
351
491
|
&& transition({ create: ['none→backlog', 'none→provisioning', 'none→active'],
|
|
@@ -354,80 +494,245 @@ function validPresentation(value) {
|
|
|
354
494
|
unblock: ['backlog→backlog', 'provisioning→provisioning', 'active→active', 'review→review'],
|
|
355
495
|
review: ['active→review'], done: ['review→done'], finish: ['active→done', 'review→done'],
|
|
356
496
|
cancel: ['backlog→cancelled', 'provisioning→cancelled', 'active→cancelled', 'review→cancelled'],
|
|
357
|
-
delete: [
|
|
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',
|
|
358
503
|
'provisioning→provisioning'] })
|
|
359
|
-
&& (p.title === undefined ||
|
|
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))
|
|
360
508
|
&& (p.template === undefined || text(p.template)) && (p.roomId === undefined || safe(p.roomId))
|
|
361
509
|
&& (p.list === undefined || text(p.list)) && (p.revision === undefined || text(p.revision))
|
|
362
510
|
&& Array.isArray(p.agents) && p.agents.length <= 64
|
|
363
511
|
&& p.agents.every(a => a && typeof a === 'object'
|
|
364
|
-
&& exact(a, ['name', 'brain', 'role', 'permissions'])
|
|
512
|
+
&& exact(a, ['name', 'brain', 'role', 'permissions', 'configuration'])
|
|
365
513
|
&& safe(a.name)
|
|
366
514
|
&& text(a.role)
|
|
367
515
|
&& (a.brain === undefined || text(a.brain))
|
|
368
|
-
&& (a.permissions === undefined || text(a.permissions))
|
|
516
|
+
&& (a.permissions === undefined || text(a.permissions))
|
|
517
|
+
&& configured(a));
|
|
369
518
|
if (p.kind === 'room')
|
|
370
519
|
return exact(p, ['kind', 'operation', 'eventId', 'id', 'previousState', 'newState',
|
|
371
|
-
'revision', 'name', 'template', 'taskId', 'participants'])
|
|
520
|
+
'revision', 'name', 'template', 'taskId', 'anonymous', 'memberCount', 'ownerAttached', 'participants'])
|
|
372
521
|
&& ['create', 'activate', 'close', 'delete'].includes(String(p.operation))
|
|
373
522
|
&& safe(p.eventId) && safe(p.id) && safe(p.newState)
|
|
374
523
|
&& transition({ create: ['none→provisioning'], activate: ['provisioning→active'],
|
|
375
524
|
close: ['active→closing', 'provisioning→closing'],
|
|
376
525
|
delete: ['closing→deleted'] })
|
|
377
526
|
&& (p.previousState === undefined || safe(p.previousState))
|
|
378
|
-
&& (p.name === undefined ||
|
|
527
|
+
&& (p.name === undefined || presentationLabel(p.name)) && (p.template === undefined || text(p.template))
|
|
379
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))
|
|
380
533
|
&& Array.isArray(p.participants) && p.participants.length <= 64
|
|
381
534
|
&& p.participants.every(a => a && typeof a === 'object'
|
|
382
|
-
&& exact(a, ['name', 'id', 'brain', 'role', 'permissions'])
|
|
535
|
+
&& exact(a, ['name', 'id', 'brain', 'role', 'permissions', 'configuration'])
|
|
383
536
|
&& safe(a.name)
|
|
384
537
|
&& text(a.role)
|
|
385
538
|
&& (a.id === undefined || safe(a.id))
|
|
386
539
|
&& (a.brain === undefined || text(a.brain))
|
|
387
|
-
&& (a.permissions === undefined || text(a.permissions))
|
|
540
|
+
&& (a.permissions === undefined || text(a.permissions))
|
|
541
|
+
&& configured(a));
|
|
388
542
|
if (p.kind === 'lifecycle_failure')
|
|
389
|
-
return exact(p, ['kind', 'eventId', 'resource', 'id', 'state', 'category'])
|
|
543
|
+
return exact(p, ['kind', 'eventId', 'resource', 'id', 'label', 'state', 'category'])
|
|
390
544
|
&& ['Agent', 'Task', 'Room'].includes(String(p.resource))
|
|
391
|
-
&& safe(p.eventId) && safe(p.id) && safe(p.state)
|
|
392
|
-
|
|
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));
|
|
393
549
|
return false;
|
|
394
550
|
}
|
|
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']];
|
|
597
|
+
}
|
|
598
|
+
function interruptSummary(value) {
|
|
599
|
+
return value === false ? 'off' : value === true ? 'on' : markdownProse(value);
|
|
600
|
+
}
|
|
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
|
+
}
|
|
395
667
|
/** Compact Owner presentation. It intentionally has no command, argv, environment, or correlation data. */
|
|
396
668
|
export function renderFleetLifecycleEvent(value) {
|
|
397
669
|
if (value.kind === 'lifecycle_failure') {
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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.');
|
|
410
682
|
}
|
|
411
683
|
if (value.kind === 'agent_started') {
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
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.');
|
|
415
698
|
}
|
|
416
699
|
if (value.kind === 'task') {
|
|
417
|
-
const title = value.title
|
|
418
|
-
const
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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.');
|
|
425
727
|
}
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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.');
|
|
433
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),
|
|
@@ -1,4 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
interface TerminalRecoveryOptions {
|
|
3
|
+
sendToCodex(line: string): void;
|
|
4
|
+
emitToClient(line: string): void;
|
|
5
|
+
log(line: string): void;
|
|
6
|
+
quietMs?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Reconcile Codex's missing terminal notification without guessing from a
|
|
10
|
+
* wall-clock timeout. A candidate needs the exact turn's authoritative final
|
|
11
|
+
* assistant item, no live items or server requests, and a quiet period. The
|
|
12
|
+
* proxy then asks app-server for the current thread snapshot. Only a terminal
|
|
13
|
+
* stored turn, or an idle thread containing that exact turn, may synthesize the
|
|
14
|
+
* missing notification.
|
|
15
|
+
*
|
|
16
|
+
* This lives in the authenticated bundled-Codex proxy, not the generic ACP
|
|
17
|
+
* session: only here are App Server thread/turn IDs and item lifecycles visible.
|
|
18
|
+
*/
|
|
19
|
+
export declare class CodexTerminalRecovery {
|
|
20
|
+
private readonly options;
|
|
21
|
+
private readonly turns;
|
|
22
|
+
private readonly pendingTurnStarts;
|
|
23
|
+
private readonly serverRequests;
|
|
24
|
+
private readonly reconcileRequests;
|
|
25
|
+
private readonly inferredTurns;
|
|
26
|
+
private reconcileSequence;
|
|
27
|
+
constructor(options: TerminalRecoveryOptions);
|
|
28
|
+
observeClientLine(line: string): void;
|
|
29
|
+
/** Return false when an internal response or late duplicate was consumed. */
|
|
30
|
+
observeServerLine(line: string): boolean;
|
|
31
|
+
close(): void;
|
|
32
|
+
private parse;
|
|
33
|
+
private ensureTurn;
|
|
34
|
+
private candidate;
|
|
35
|
+
private cancelReconciliation;
|
|
36
|
+
private scheduleReconciliation;
|
|
37
|
+
private reconcile;
|
|
38
|
+
private handleReconcileResponse;
|
|
39
|
+
private finishTurn;
|
|
40
|
+
}
|
|
2
41
|
/** Transform one app-server NDJSON request; malformed input passes through. */
|
|
3
42
|
export declare function rewriteCodexAppServerRequest(line: string, approval: string, expectedSandbox: string, disableInheritedMcp?: boolean): string;
|
|
4
43
|
export declare function runCodexAppServerProxy(): void;
|
|
44
|
+
export {};
|