@ours.network/fleet 0.19.0-nightly.13 → 0.19.0-nightly.15

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.
Files changed (93) hide show
  1. package/README.md +66 -11
  2. package/dist/application/fleet-query-service.d.ts +1 -1
  3. package/dist/application/fleet-query-service.js +12 -0
  4. package/dist/application/role-creation-service.js +3 -1
  5. package/dist/application/types.d.ts +11 -0
  6. package/dist/briefing.d.ts +1 -1
  7. package/dist/briefing.js +11 -4
  8. package/dist/build-info.json +4 -4
  9. package/dist/cli.js +41 -11
  10. package/dist/config.d.ts +10 -7
  11. package/dist/config.js +32 -18
  12. package/dist/creation.d.ts +1 -1
  13. package/dist/docs.d.ts +2 -2
  14. package/dist/docs.js +96 -24
  15. package/dist/doctor.js +10 -10
  16. package/dist/duration.js +1 -1
  17. package/dist/fleet-proxy.d.ts +5 -0
  18. package/dist/harness/acp-agent.js +11 -6
  19. package/dist/harness/claude-code.js +199 -8
  20. package/dist/harness/codex.d.ts +4 -1
  21. package/dist/harness/codex.js +70 -13
  22. package/dist/harness/types.d.ts +55 -6
  23. package/dist/isolation/bubblewrap.d.ts +4 -4
  24. package/dist/isolation/bubblewrap.js +4 -4
  25. package/dist/isolation/policy.d.ts +1 -1
  26. package/dist/isolation/policy.js +2 -2
  27. package/dist/isolation/registry.d.ts +3 -3
  28. package/dist/isolation/registry.js +5 -5
  29. package/dist/isolation/resources.d.ts +1 -1
  30. package/dist/isolation/resources.js +1 -1
  31. package/dist/isolation/types.d.ts +3 -3
  32. package/dist/loops/manager.d.ts +30 -1
  33. package/dist/loops/manager.js +69 -6
  34. package/dist/loops/state.d.ts +18 -0
  35. package/dist/loops/state.js +4 -0
  36. package/dist/model-env.d.ts +71 -0
  37. package/dist/model-env.js +106 -0
  38. package/dist/monitor.d.ts +2 -2
  39. package/dist/monitor.js +4 -4
  40. package/dist/ops.d.ts +2 -2
  41. package/dist/ops.js +9 -9
  42. package/dist/owner-channel/channel.d.ts +10 -0
  43. package/dist/owner-channel/channel.js +65 -26
  44. package/dist/owner-channel/commands.js +198 -87
  45. package/dist/owner-channel/notices.d.ts +7 -0
  46. package/dist/owner-channel/notices.js +9 -0
  47. package/dist/rooms-tasks/cli.js +453 -192
  48. package/dist/rooms-tasks/markdown.d.ts +49 -0
  49. package/dist/rooms-tasks/markdown.js +206 -0
  50. package/dist/rooms-tasks/provision.js +1 -1
  51. package/dist/runner.d.ts +48 -0
  52. package/dist/runner.js +252 -100
  53. package/dist/session/acp.d.ts +106 -2
  54. package/dist/session/acp.js +217 -14
  55. package/dist/session/activity.d.ts +31 -0
  56. package/dist/session/activity.js +48 -0
  57. package/dist/session/control.d.ts +1 -1
  58. package/dist/session/conversation-normalizer.d.ts +7 -1
  59. package/dist/session/conversation-normalizer.js +154 -11
  60. package/dist/session/conversation-store.js +1 -1
  61. package/dist/session/conversation-types.d.ts +25 -6
  62. package/dist/session/types.d.ts +35 -0
  63. package/dist/spawn.d.ts +1 -1
  64. package/dist/spawn.js +38 -26
  65. package/dist/supervisor/launchd.d.ts +2 -2
  66. package/dist/supervisor/launchd.js +4 -4
  67. package/dist/supervisor/systemd.js +4 -4
  68. package/dist/supervisor/types.d.ts +1 -1
  69. package/dist/watchdog/alerts.d.ts +2 -2
  70. package/dist/watchdog/alerts.js +2 -2
  71. package/dist/watchdog/briefing.d.ts +5 -5
  72. package/dist/watchdog/briefing.js +13 -6
  73. package/dist/watchdog/config.js +1 -1
  74. package/dist/watchdog/query.d.ts +2 -2
  75. package/dist/watchdog/query.js +2 -2
  76. package/dist/watchdog/report.js +2 -2
  77. package/dist/watchdog/run.d.ts +2 -2
  78. package/dist/watchdog/run.js +6 -6
  79. package/dist/watchdog/scheduler.d.ts +7 -7
  80. package/dist/watchdog/scheduler.js +10 -10
  81. package/dist/watchdog/service.d.ts +4 -4
  82. package/dist/watchdog/service.js +5 -6
  83. package/dist/watchdog/store.d.ts +4 -4
  84. package/dist/watchdog/store.js +4 -4
  85. package/dist/web/runtime.js +1 -1
  86. package/dist/web/server.js +1 -1
  87. package/dist/web/topology-promote.js +1 -1
  88. package/dist/web-app/assets/{TerminalView-BAVk1Bot.js → TerminalView-C_G1ID2P.js} +1 -1
  89. package/dist/web-app/assets/{index-C3S-xFRU.js → index-BCBK78hw.js} +5 -5
  90. package/dist/web-app/index.html +1 -1
  91. package/dist/worklog.d.ts +7 -1
  92. package/dist/worklog.js +191 -39
  93. package/package.json +1 -1
@@ -0,0 +1,49 @@
1
+ import type { RoomOrchestrationState, TaskState } from './types.js';
2
+ export declare const MARKDOWN_MAX_CODE_POINTS = 3500;
3
+ export declare const MARKDOWN_MAX_BYTES = 12000;
4
+ export interface StatusPresentation {
5
+ icon: string;
6
+ word: string;
7
+ }
8
+ export type MarkdownField = {
9
+ label: string;
10
+ value: unknown;
11
+ kind?: 'prose' | 'code' | 'markdown';
12
+ multiline?: boolean;
13
+ };
14
+ export type MarkdownSection = {
15
+ heading?: string;
16
+ items?: unknown[];
17
+ markdownItems?: string[];
18
+ prose?: unknown;
19
+ };
20
+ export declare function withinMarkdownBounds(value: string): boolean;
21
+ /** Escaped single-line prose suitable for a field or list item. */
22
+ export declare function markdownProse(value: unknown): string;
23
+ /** Escaped heading text. Newlines and heading/list syntax cannot escape the heading. */
24
+ export declare function markdownHeading(value: unknown): string;
25
+ /** Escaped multiline prose. Intended line breaks remain line breaks. */
26
+ export declare function markdownMultiline(value: unknown): string;
27
+ /** CommonMark code span with a delimiter longer than every run in the value. */
28
+ export declare function markdownCode(value: unknown): string;
29
+ export declare function taskStatus(value: TaskState | string): string;
30
+ export declare function roomStatus(value: RoomOrchestrationState | string): string;
31
+ export declare function renderMarkdownResult(input: {
32
+ icon: string;
33
+ title: string;
34
+ fields?: MarkdownField[];
35
+ sections?: MarkdownSection[];
36
+ }): string;
37
+ export declare function renderMarkdownList(input: {
38
+ icon: string;
39
+ title: string;
40
+ empty: string;
41
+ records: string[];
42
+ }): string;
43
+ export type FailureKind = 'usage' | 'not_found' | 'conflict' | 'state' | 'pending' | 'unexpected';
44
+ export declare function renderMarkdownFailure(input: {
45
+ kind: FailureKind;
46
+ subject: string;
47
+ detail?: unknown;
48
+ action: string;
49
+ }): string;
@@ -0,0 +1,206 @@
1
+ import { Buffer } from 'node:buffer';
2
+ export const MARKDOWN_MAX_CODE_POINTS = 3_500;
3
+ export const MARKDOWN_MAX_BYTES = 12_000;
4
+ const FIELD_MAX_CODE_POINTS = 900;
5
+ const FIELD_MAX_BYTES = 3_000;
6
+ const ID_MAX_CODE_POINTS = 160;
7
+ const ID_MAX_BYTES = 640;
8
+ const TRUNCATED = '_… output truncated._';
9
+ const SPOOFING_CONTROLS = /[\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff]/g;
10
+ const SINGLE_LINE_CONTROLS = /[\u0000-\u001f\u007f-\u009f]/g;
11
+ const MULTILINE_CONTROLS = /[\u0000-\u0009\u000b-\u001f\u007f-\u009f]/g;
12
+ const PROSE_PUNCTUATION = /([\\`*_[\]<>])/g;
13
+ const HEADING_PUNCTUATION = /([\\`*_[\]<>#])/g;
14
+ const TASK_STATUS = {
15
+ backlog: { icon: '⏸️', word: 'Backlog' },
16
+ provisioning: { icon: '⏳', word: 'Provisioning' },
17
+ active: { icon: '🟢', word: 'Active' },
18
+ review: { icon: '🟡', word: 'Review' },
19
+ done: { icon: '✅', word: 'Done' },
20
+ cancelled: { icon: '🚫', word: 'Cancelled' },
21
+ failed: { icon: '❌', word: 'Failed' },
22
+ };
23
+ const ROOM_STATUS = {
24
+ provisioning: { icon: '⏳', word: 'Provisioning' },
25
+ active: { icon: '🟢', word: 'Active' },
26
+ closing: { icon: '⏳', word: 'Closing' },
27
+ closed: { icon: '🔒', word: 'Closed' },
28
+ };
29
+ const codePoints = (value) => Array.from(value).length;
30
+ const bytes = (value) => Buffer.byteLength(value, 'utf8');
31
+ export function withinMarkdownBounds(value) {
32
+ return codePoints(value) <= MARKDOWN_MAX_CODE_POINTS && bytes(value) <= MARKDOWN_MAX_BYTES;
33
+ }
34
+ function normalize(value, multiline) {
35
+ let text = String(value ?? '').replace(/\r\n?/g, '\n').replace(SPOOFING_CONTROLS, '�');
36
+ if (multiline)
37
+ text = text.replace(MULTILINE_CONTROLS, '�');
38
+ else
39
+ text = text.replace(SINGLE_LINE_CONTROLS, ' ').replace(/\n+/g, ' ');
40
+ return text;
41
+ }
42
+ function boundPlain(value, maxPoints, maxBytes) {
43
+ if (codePoints(value) <= maxPoints && bytes(value) <= maxBytes)
44
+ return value;
45
+ const suffix = '…';
46
+ let out = '';
47
+ for (const point of value) {
48
+ const next = out + point + suffix;
49
+ if (codePoints(next) > maxPoints || bytes(next) > maxBytes)
50
+ break;
51
+ out += point;
52
+ }
53
+ return out.replace(/\s+$/u, '') + suffix;
54
+ }
55
+ /** Escaped single-line prose suitable for a field or list item. */
56
+ export function markdownProse(value) {
57
+ return boundPlain(normalize(value, false).trim(), FIELD_MAX_CODE_POINTS, FIELD_MAX_BYTES)
58
+ .replace(PROSE_PUNCTUATION, '\\$1');
59
+ }
60
+ /** Escaped heading text. Newlines and heading/list syntax cannot escape the heading. */
61
+ export function markdownHeading(value) {
62
+ return boundPlain(normalize(value, false).trim(), 160, 640)
63
+ .replace(HEADING_PUNCTUATION, '\\$1');
64
+ }
65
+ /** Escaped multiline prose. Intended line breaks remain line breaks. */
66
+ export function markdownMultiline(value) {
67
+ const bounded = boundPlain(normalize(value, true).trim(), FIELD_MAX_CODE_POINTS, FIELD_MAX_BYTES);
68
+ return bounded.split('\n').map(line => {
69
+ const escaped = line.replace(PROSE_PUNCTUATION, '\\$1');
70
+ // A multiline value is commonly nested inside a block quote. Escape every
71
+ // still-active line-leading block marker so user text cannot create nested
72
+ // headings, lists, quotes, thematic breaks, or tilde fences.
73
+ return escaped
74
+ .replace(/^([ \t]*)(#{1,6}|[-+>])(?=[ \t]|$)/u, '$1\\$2')
75
+ .replace(/^([ \t]*)(\d{1,9})([.)])(?=[ \t]|$)/u, '$1$2\\$3')
76
+ .replace(/^([ \t]*)(-{3,})(?=[ \t]*$)/u, '$1\\$2')
77
+ .replace(/^([ \t]*)(~{3,})/u, '$1\\$2');
78
+ }).join('\n');
79
+ }
80
+ /** CommonMark code span with a delimiter longer than every run in the value. */
81
+ export function markdownCode(value) {
82
+ const normalized = normalize(value, false);
83
+ const text = boundPlain(normalized || '—', ID_MAX_CODE_POINTS, ID_MAX_BYTES);
84
+ const longest = Math.max(0, ...[...text.matchAll(/`+/g)].map(match => match[0].length));
85
+ const fence = '`'.repeat(longest + 1);
86
+ const pad = /^`|`$/.test(text) || (/^ | $/.test(text) && !/^ +$/.test(text));
87
+ return `${fence}${pad ? ' ' : ''}${text}${pad ? ' ' : ''}${fence}`;
88
+ }
89
+ export function taskStatus(value) {
90
+ const status = TASK_STATUS[value];
91
+ return status ? `${status.icon} ${status.word}` : `⚪ Unknown (${markdownProse(value)})`;
92
+ }
93
+ export function roomStatus(value) {
94
+ const status = ROOM_STATUS[value];
95
+ return status ? `${status.icon} ${status.word}` : `⚪ Unknown (${markdownProse(value)})`;
96
+ }
97
+ function addBlock(blocks, block) {
98
+ const candidate = [...blocks, block, TRUNCATED].join('\n\n');
99
+ if (!withinMarkdownBounds(candidate))
100
+ return false;
101
+ blocks.push(block);
102
+ return true;
103
+ }
104
+ function finalize(blocks, truncated) {
105
+ if (truncated)
106
+ blocks.push(TRUNCATED);
107
+ const output = blocks.join('\n\n');
108
+ if (!withinMarkdownBounds(output))
109
+ throw new Error('Markdown result exceeded its transport bounds');
110
+ return output;
111
+ }
112
+ export function renderMarkdownResult(input) {
113
+ const blocks = [`## ${input.icon} ${markdownHeading(input.title)}`];
114
+ let truncated = false;
115
+ for (const field of input.fields ?? []) {
116
+ const label = markdownHeading(field.label);
117
+ const value = field.kind === 'code' ? markdownCode(field.value)
118
+ : field.kind === 'markdown' ? String(field.value)
119
+ : field.multiline ? markdownMultiline(field.value) : markdownProse(field.value);
120
+ const rendered = field.multiline
121
+ ? `**${label}:**\n\n${value.split('\n').map(line => `> ${line}`).join('\n')}`
122
+ : `- **${label}:** ${value || '—'}`;
123
+ if (!addBlock(blocks, rendered)) {
124
+ truncated = true;
125
+ break;
126
+ }
127
+ }
128
+ if (!truncated)
129
+ for (const section of input.sections ?? []) {
130
+ const lines = [];
131
+ if (section.heading)
132
+ lines.push(`### ${markdownHeading(section.heading)}`);
133
+ if (section.prose !== undefined)
134
+ lines.push(markdownMultiline(section.prose));
135
+ const items = [
136
+ ...(section.items ?? []).map(markdownProse),
137
+ ...(section.markdownItems ?? []),
138
+ ];
139
+ let admitted = 0;
140
+ for (const item of items) {
141
+ const next = [...lines, `- ${item}`];
142
+ const remaining = items.length - admitted - 1;
143
+ const omission = remaining
144
+ ? `- _${remaining} more ${remaining === 1 ? 'result' : 'results'} omitted\\._` : undefined;
145
+ const candidate = [...blocks, [...next, ...(omission ? [omission] : [])].join('\n'), TRUNCATED]
146
+ .join('\n\n');
147
+ if (!withinMarkdownBounds(candidate))
148
+ break;
149
+ lines.push(`- ${item}`);
150
+ admitted++;
151
+ }
152
+ if (admitted < items.length) {
153
+ const omitted = items.length - admitted;
154
+ lines.push(`- _${omitted} more ${omitted === 1 ? 'result' : 'results'} omitted\\._`);
155
+ }
156
+ if (!addBlock(blocks, lines.join('\n'))) {
157
+ truncated = true;
158
+ break;
159
+ }
160
+ }
161
+ return finalize(blocks, truncated);
162
+ }
163
+ export function renderMarkdownList(input) {
164
+ const blocks = [`## ${input.icon} ${markdownHeading(input.title)}`];
165
+ if (!input.records.length)
166
+ return finalize([...blocks, markdownProse(input.empty)], false);
167
+ let admitted = 0;
168
+ for (const record of input.records) {
169
+ const remaining = input.records.length - admitted - 1;
170
+ const rendered = `- ${record}`;
171
+ const omission = remaining
172
+ ? `- _${remaining} more ${remaining === 1 ? 'result' : 'results'} omitted\\._` : '';
173
+ const candidate = [...blocks, rendered, ...(omission ? [omission] : [])].join('\n\n');
174
+ if (!withinMarkdownBounds(candidate))
175
+ break;
176
+ blocks.push(rendered);
177
+ admitted++;
178
+ }
179
+ if (admitted < input.records.length) {
180
+ const omitted = input.records.length - admitted;
181
+ const notice = `- _${omitted} more ${omitted === 1 ? 'result' : 'results'} omitted\\._`;
182
+ if (!addBlock(blocks, notice))
183
+ return finalize(blocks, true);
184
+ }
185
+ return finalize(blocks, false);
186
+ }
187
+ export function renderMarkdownFailure(input) {
188
+ const titles = {
189
+ usage: 'Invalid command',
190
+ not_found: 'Not found',
191
+ conflict: 'Conflict',
192
+ state: 'Action not allowed',
193
+ pending: 'Action still pending',
194
+ unexpected: 'Command failed',
195
+ };
196
+ return renderMarkdownResult({
197
+ icon: input.kind === 'pending' ? '⏳' : '⚠️',
198
+ title: titles[input.kind],
199
+ fields: [
200
+ { label: 'Command', value: input.subject, kind: 'code' },
201
+ ...(input.detail !== undefined && input.kind !== 'unexpected'
202
+ ? [{ label: 'Reason', value: input.detail }] : []),
203
+ ],
204
+ sections: [{ heading: 'Next step', items: [input.action] }],
205
+ });
206
+ }
@@ -245,7 +245,7 @@ export async function provisionMembers(input) {
245
245
  updateMemberSeats(roomId, seats);
246
246
  }
247
247
  else {
248
- // Phase 4: create_members
248
+ // create_members saga phase
249
249
  advanceSaga(roomId, 'create_members', 3);
250
250
  try {
251
251
  for (const planned of plan) {
package/dist/runner.d.ts CHANGED
@@ -35,6 +35,15 @@ export interface RunnerDeps {
35
35
  }
36
36
  /** Environment injected only into the managed harness process. */
37
37
  export declare function managedFleetProxyEnv(role: ResolvedRole, stateDir: string): Record<string, string>;
38
+ /**
39
+ * The environment a managed harness child actually receives, checked at the one
40
+ * point where it is composed. `role.env` deliberately wins over harness prep,
41
+ * which is exactly how a stale fleet-wide model pin used to outrank the model
42
+ * the role was spawned with — so the model pin is verified here rather than
43
+ * trusted, and a disagreement stops the launch instead of being reported as a
44
+ * success (see src/model-env.ts).
45
+ */
46
+ export declare function harnessChildEnv(role: ResolvedRole, launchEnv: Record<string, string> | undefined, stateDir: string): Record<string, string>;
38
47
  /**
39
48
  * Record who owns wake delivery for this run. Returning true means a fleet
40
49
  * monitor is taking ownership back from a native harness and must start at the
@@ -61,6 +70,23 @@ export declare function readExitRecord(path: string): ExitRecord | null;
61
70
  export declare const RESTART_LEDGER_FILE = ".restart-ledger.json";
62
71
  /** Consecutive immediate failures tolerated before the agent is held down. */
63
72
  export declare const RESTART_FAIL_THRESHOLD = 5;
73
+ /**
74
+ * How the previous supervisor process ended.
75
+ *
76
+ * `abrupt` is the case the ledger used to miss entirely: an OOM-kill or any
77
+ * other external signal takes the supervisor down before it can write anything,
78
+ * the service manager restarts the unit, and every durable indicator still
79
+ * describes the run that died. A health check reading them reported "no
80
+ * restarts" for a role that had died and come back.
81
+ */
82
+ export interface TerminationRecord {
83
+ class: 'clean' | 'abrupt' | 'unknown';
84
+ detail: string;
85
+ /** When the SURVIVING process observed it, not when it happened. */
86
+ observedAt: string;
87
+ /** Start time of the run that ended, when it was recorded. */
88
+ runStartedAt?: string;
89
+ }
64
90
  export interface RestartLedger {
65
91
  version: 1;
66
92
  consecutiveImmediateFailures: number;
@@ -72,7 +98,29 @@ export interface RestartLedger {
72
98
  updatedAt: string;
73
99
  /** When the circuit opened, for the held-down status line. */
74
100
  openedAt?: string;
101
+ /** How the previous supervisor process ended, including abnormal exits. */
102
+ lastTermination?: TerminationRecord;
103
+ /** Supervisor processes that died without closing their run marker. */
104
+ abruptTerminations?: number;
105
+ /** Start of the supervisor run that owns this state directory now. */
106
+ supervisorStartedAt?: string;
75
107
  }
108
+ /**
109
+ * Carried across a supervisor process's life so its successor can tell an
110
+ * orderly exit from a kill. Present on disk == "a supervisor believed it was
111
+ * running"; the next start finding one that is not its own is proof the
112
+ * previous process died without getting to write anything.
113
+ */
114
+ export declare const RUN_MARKER_FILE = ".supervisor-run.json";
115
+ /**
116
+ * Claim this state directory for the current supervisor process and report how
117
+ * the previous one ended. Runs BEFORE the first attempt, which is the whole
118
+ * point: after an abrupt kill nothing else writes until an attempt finishes,
119
+ * and an attempt can take minutes.
120
+ */
121
+ export declare function claimSupervisorRun(dir: string, startedAt: string, pid?: number): TerminationRecord;
122
+ /** Orderly exit: the successor must not read this run as a kill. */
123
+ export declare function releaseSupervisorRun(dir: string): void;
76
124
  /** Bounded exponential backoff for the nth consecutive immediate failure. */
77
125
  export declare function backoffFor(consecutiveFailures: number): number;
78
126
  /** Read a role's restart ledger; a missing or corrupt one starts clean. */