@ours.network/fleet 0.18.1 → 0.19.0-nightly.2

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 (79) hide show
  1. package/README.md +57 -91
  2. package/dist/application/fleet-query-service.js +0 -12
  3. package/dist/application/role-creation-service.js +10 -7
  4. package/dist/application/types.d.ts +0 -11
  5. package/dist/briefing.js +6 -15
  6. package/dist/build-info.json +5 -5
  7. package/dist/cli.js +12 -37
  8. package/dist/config.d.ts +8 -6
  9. package/dist/config.js +46 -30
  10. package/dist/creation.d.ts +38 -22
  11. package/dist/creation.js +111 -24
  12. package/dist/docs.d.ts +1 -1
  13. package/dist/docs.js +44 -95
  14. package/dist/doctor.d.ts +5 -1
  15. package/dist/doctor.js +18 -11
  16. package/dist/fleet-proxy.d.ts +0 -5
  17. package/dist/harness/acp-agent.js +6 -11
  18. package/dist/harness/claude-code.js +11 -200
  19. package/dist/harness/codex.d.ts +1 -4
  20. package/dist/harness/codex.js +12 -70
  21. package/dist/harness/types.d.ts +4 -54
  22. package/dist/loops/manager.d.ts +1 -30
  23. package/dist/loops/manager.js +6 -69
  24. package/dist/loops/state.d.ts +0 -18
  25. package/dist/loops/state.js +0 -4
  26. package/dist/monitor.js +1 -1
  27. package/dist/ops.d.ts +3 -0
  28. package/dist/ops.js +8 -3
  29. package/dist/owner-channel/attachments.d.ts +25 -2
  30. package/dist/owner-channel/attachments.js +61 -5
  31. package/dist/owner-channel/channel.d.ts +29 -30
  32. package/dist/owner-channel/channel.js +291 -291
  33. package/dist/owner-channel/commands.js +89 -0
  34. package/dist/owner-channel/message-recovery.d.ts +25 -0
  35. package/dist/owner-channel/message-recovery.js +114 -0
  36. package/dist/owner-channel/notices.d.ts +0 -7
  37. package/dist/owner-channel/notices.js +0 -9
  38. package/dist/owner-channel/ours-client.d.ts +148 -0
  39. package/dist/owner-channel/ours-client.js +231 -0
  40. package/dist/rooms-tasks/cli.d.ts +4 -0
  41. package/dist/rooms-tasks/cli.js +565 -0
  42. package/dist/rooms-tasks/config.d.ts +15 -0
  43. package/dist/rooms-tasks/config.js +171 -0
  44. package/dist/rooms-tasks/cowork-adapter.d.ts +80 -0
  45. package/dist/rooms-tasks/cowork-adapter.js +48 -0
  46. package/dist/rooms-tasks/index.d.ts +7 -0
  47. package/dist/rooms-tasks/index.js +7 -0
  48. package/dist/rooms-tasks/room-state.d.ts +22 -0
  49. package/dist/rooms-tasks/room-state.js +117 -0
  50. package/dist/rooms-tasks/task-state.d.ts +31 -0
  51. package/dist/rooms-tasks/task-state.js +173 -0
  52. package/dist/rooms-tasks/templates.d.ts +6 -0
  53. package/dist/rooms-tasks/templates.js +80 -0
  54. package/dist/rooms-tasks/types.d.ts +153 -0
  55. package/dist/rooms-tasks/types.js +20 -0
  56. package/dist/runner.d.ts +0 -48
  57. package/dist/runner.js +94 -236
  58. package/dist/session/acp.d.ts +0 -104
  59. package/dist/session/acp.js +10 -213
  60. package/dist/session/conversation-normalizer.d.ts +0 -6
  61. package/dist/session/conversation-normalizer.js +10 -153
  62. package/dist/session/conversation-types.d.ts +4 -23
  63. package/dist/session/types.d.ts +0 -35
  64. package/dist/spawn.js +26 -33
  65. package/dist/supervisor/systemd.js +29 -2
  66. package/dist/watchdog/briefing.js +0 -7
  67. package/dist/watchdog/run.js +3 -3
  68. package/dist/web-app/assets/{TerminalView-C_G1ID2P.js → TerminalView-BAVk1Bot.js} +1 -1
  69. package/dist/web-app/assets/{index-BCBK78hw.js → index-C3S-xFRU.js} +5 -5
  70. package/dist/web-app/index.html +1 -1
  71. package/dist/worklog.d.ts +1 -7
  72. package/dist/worklog.js +39 -191
  73. package/package.json +3 -1
  74. package/dist/model-env.d.ts +0 -71
  75. package/dist/model-env.js +0 -106
  76. package/dist/owner-channel/mcp.d.ts +0 -24
  77. package/dist/owner-channel/mcp.js +0 -145
  78. package/dist/session/activity.d.ts +0 -31
  79. package/dist/session/activity.js +0 -48
@@ -6,8 +6,14 @@ import { replaceFileAtomically } from '../atomic-file.js';
6
6
  const WIRE = /^[A-Fa-f0-9]{64}$/;
7
7
  const CID = /^[A-Fa-f0-9]{64}$/;
8
8
  const MAX_PENDING_REQUESTS = 32;
9
+ /**
10
+ * Admit the daemon's file listing. The rows are typed now, but every field is
11
+ * still re-validated here: sender CID, wire id, sizes and ids all cross the
12
+ * trust boundary and decide routing, so a daemon-side shape change must drop a
13
+ * row rather than produce a half-built attachment.
14
+ */
9
15
  export function parseIncomingAttachments(raw) {
10
- const values = raw?.files;
16
+ const values = raw;
11
17
  if (!Array.isArray(values))
12
18
  return [];
13
19
  const out = [];
@@ -40,12 +46,12 @@ export function parseIncomingAttachments(raw) {
40
46
  export function parseRetrievedAttachments(raw, expected, recovered = false) {
41
47
  const values = raw?.files;
42
48
  if (!Array.isArray(values) || values.length !== expected.length)
43
- throw new Error('ours-mcp returned an incomplete selected attachment set');
49
+ throw new Error('the ours daemon returned an incomplete selected attachment set');
44
50
  const byWire = new Map(expected.map(file => [file.wireId, file]));
45
51
  const out = [];
46
52
  for (const value of values) {
47
53
  if (!value || typeof value !== 'object')
48
- throw new Error('ours-mcp returned invalid attachment metadata');
54
+ throw new Error('the ours daemon returned invalid attachment metadata');
49
55
  const file = value;
50
56
  const wireId = String(file.wire_id ?? '');
51
57
  const listed = byWire.get(wireId);
@@ -58,7 +64,7 @@ export function parseRetrievedAttachments(raw, expected, recovered = false) {
58
64
  || !Number.isSafeInteger(size) || size !== listed.size || mime !== listed.mime
59
65
  || kind !== listed.kind || !/^[a-f0-9]{64}$/.test(sha256)
60
66
  || typeof file.path !== 'string' || !file.path)
61
- throw new Error('ours-mcp selected attachment provenance or integrity metadata mismatched');
67
+ throw new Error('selected attachment provenance or integrity metadata mismatched');
62
68
  out.push({
63
69
  ...listed, filename: safeField(file.filename, 255), mime, size, path: file.path, sha256, kind,
64
70
  ...(recovered ? {} : parseTranscription(file.transcription, wireId)),
@@ -66,7 +72,7 @@ export function parseRetrievedAttachments(raw, expected, recovered = false) {
66
72
  byWire.delete(wireId);
67
73
  }
68
74
  if (byWire.size)
69
- throw new Error('ours-mcp omitted a selected attachment');
75
+ throw new Error('the ours daemon omitted a selected attachment');
70
76
  return out;
71
77
  }
72
78
  function parseTranscription(value, wireId) {
@@ -219,6 +225,56 @@ export async function admitAttachments(files, dir, config, options = {}) {
219
225
  }
220
226
  return admitted;
221
227
  }
228
+ /**
229
+ * Land crash-recovered file bytes inside an already-prepared request directory.
230
+ *
231
+ * The MCP path handed the daemon a `dest_path` and let its connector write the
232
+ * file. Nothing writes on our behalf any more, so this owns both halves of that
233
+ * contract: the destination is DERIVED from a validated wire id inside `dir`
234
+ * rather than accepted from a caller, and the file is published by link-after-
235
+ * fsync, so a crash or a short write can never leave a partial file where the
236
+ * admission step would read it as complete.
237
+ */
238
+ export async function writeRecoveredAttachment(dir, wireId, bytes, deps = {}) {
239
+ if (!WIRE.test(wireId))
240
+ throw new Error('recovered attachment wire id is not a 64-hex value');
241
+ const dirStat = await lstat(dir);
242
+ if (!dirStat.isDirectory() || dirStat.isSymbolicLink())
243
+ throw new Error('recovered attachment directory is not a safe directory');
244
+ const write = deps.write
245
+ ?? ((handle, buffer, offset) => handle.write(buffer, offset, buffer.length - offset)
246
+ .then(result => result.bytesWritten));
247
+ const finalPath = join(dir, `.recovered-${wireId}-${randomUUID()}`);
248
+ const tmp = join(dir, `.${basename(finalPath)}.${randomUUID()}.tmp`);
249
+ const handle = await open(tmp, 'wx', 0o600);
250
+ try {
251
+ for (let written = 0; written < bytes.length;) {
252
+ const advanced = await write(handle, bytes, written);
253
+ if (advanced <= 0)
254
+ throw new Error(`recovered attachment write made no progress at byte ${written}`);
255
+ written += advanced;
256
+ }
257
+ await handle.sync();
258
+ }
259
+ catch (error) {
260
+ await handle.close().catch(() => undefined);
261
+ await rm(tmp, { force: true });
262
+ throw error;
263
+ }
264
+ await handle.close();
265
+ // link publishes the finished bytes under a name that never existed in a
266
+ // partial state; the temp is only ever removed after it succeeded.
267
+ try {
268
+ await link(tmp, finalPath);
269
+ }
270
+ catch (error) {
271
+ await rm(tmp, { force: true });
272
+ throw error;
273
+ }
274
+ await rm(tmp, { force: true });
275
+ await chmod(finalPath, 0o600);
276
+ return finalPath;
277
+ }
222
278
  export async function recoveredAttachment(file, path) {
223
279
  const stat = await lstat(path);
224
280
  if (!stat.isFile() || stat.isSymbolicLink() || stat.size !== file.size)
@@ -1,10 +1,8 @@
1
- import { type ChildProcessWithoutNullStreams } from 'node:child_process';
2
1
  import { type OwnerChannelConfig } from '../config.js';
3
- import { type FetchLike } from '../monitor.js';
4
2
  import { type SessionHandle } from '../session/types.js';
5
3
  import { type OwnerFleetOps } from './commands.js';
6
4
  import type { ManagedFleetSpawnResult } from '../fleet-proxy.js';
7
- import { type OursToolClient } from './mcp.js';
5
+ import { type OursOps } from './ours-client.js';
8
6
  import { type OwnerUpdatePhase } from './notices.js';
9
7
  import { type OwnerEntry } from './state.js';
10
8
  import { type OwnerTaskPhase } from './tasks.js';
@@ -17,15 +15,8 @@ export interface OwnerChannelOptions {
17
15
  session: SessionHandle;
18
16
  stateDir: string;
19
17
  env?: Record<string, string>;
20
- command?: string;
21
18
  log(line: string): void;
22
- client?: OursToolClient;
23
- /** Legacy child-process test seam; production uses the direct notification API. */
24
- watch?: (identity: string) => ChildProcessWithoutNullStreams;
25
- /** Test seam for the production direct notification long-poll. */
26
- watchFetch?: FetchLike;
27
- /** Test seam for the long-poll stall bound; production uses OWNER_WATCH_STALL_MS. */
28
- watchStallMs?: number;
19
+ client?: OursOps;
29
20
  /** Test seam; production uses the detached ours-fleet CLI (`fleetCliOps`). */
30
21
  fleet?: OwnerFleetOps;
31
22
  /** Forwarded to fleet CLI invocations spawned for owner commands. */
@@ -118,7 +109,13 @@ export type { OwnerUpdatePhase } from './notices.js';
118
109
  export interface OwnerContact {
119
110
  cid: string;
120
111
  name: string;
112
+ /** Structural, from which daemon collection the row came: established or pending. */
121
113
  status: string;
114
+ /**
115
+ * Retained for the `ours-fleet owner contact list` column. The daemon's typed
116
+ * contact view has no such field, so it is always absent; it is not inferred
117
+ * from anything a contact controls.
118
+ */
122
119
  kind?: string;
123
120
  human?: {
124
121
  cid?: string;
@@ -136,6 +133,7 @@ export declare class OwnerChannel implements OwnerChannelHandle {
136
133
  private readonly authorizations;
137
134
  private readonly conversations;
138
135
  private readonly tasks;
136
+ private readonly messageRecovery;
139
137
  private readonly attachmentRecovery;
140
138
  private readonly attachmentConfig;
141
139
  private readonly attachmentRoot;
@@ -144,7 +142,7 @@ export declare class OwnerChannel implements OwnerChannelHandle {
144
142
  * (a crash must replay them) but must not be queued twice while live.
145
143
  */
146
144
  private readonly inFlight;
147
- /** Wires already NACKed to the managed agent, so a deferred replay stays quiet. */
145
+ /** Wires already NACKed to the managed agent, so a history replay stays quiet. */
148
146
  private readonly relayNacks;
149
147
  /**
150
148
  * fleet.yaml declares the restart baseline; `/comments on|off` changes only
@@ -155,7 +153,6 @@ export declare class OwnerChannel implements OwnerChannelHandle {
155
153
  private readonly commentsBaseline;
156
154
  private commentsEnabled;
157
155
  private stopping;
158
- private watchProcess?;
159
156
  private watchTask?;
160
157
  private watchAbort?;
161
158
  private drainTask?;
@@ -174,6 +171,12 @@ export declare class OwnerChannel implements OwnerChannelHandle {
174
171
  manage(request: OwnerChannelManagementRequest): Promise<OwnerChannelManagementResult>;
175
172
  notifyFleetSpawn(event: ManagedFleetSpawnResult): Promise<void>;
176
173
  private manageNow;
174
+ /**
175
+ * The daemon reports established contacts and pending introductions as two
176
+ * separate collections, so the status is structural rather than a word parsed
177
+ * out of a rendered line. Nothing here can be spoofed by a contact's own
178
+ * display name.
179
+ */
177
180
  private contacts;
178
181
  private contact;
179
182
  private assertCid;
@@ -187,6 +190,15 @@ export declare class OwnerChannel implements OwnerChannelHandle {
187
190
  private safeTaskReport;
188
191
  private safeProactiveMessage;
189
192
  private drainAll;
193
+ /**
194
+ * Claim the exact oldest unread SQLite batch before marking it read.
195
+ * The journal contains only wire IDs and sequence numbers; bodies remain in
196
+ * the daemon's persistent history and are recovered with getHistoryItem.
197
+ */
198
+ private claimMessages;
199
+ private messageClaim;
200
+ private historyMessage;
201
+ private attachmentMetadata;
190
202
  private attachmentGroups;
191
203
  private handleAttachmentGroup;
192
204
  private handle;
@@ -224,7 +236,7 @@ export declare class OwnerChannel implements OwnerChannelHandle {
224
236
  private managedAttachmentReplyWire;
225
237
  /**
226
238
  * One bounded NACK per wire: an unroutable or refused relay must be visible
227
- * to the authenticated agent, while its deferred replays stay quiet. NACK
239
+ * to the authenticated agent, while its history replays stay quiet. NACK
228
240
  * delivery is best-effort — it must never make the failure worse.
229
241
  */
230
242
  private nackManagedAgent;
@@ -238,16 +250,6 @@ export declare class OwnerChannel implements OwnerChannelHandle {
238
250
  private warnOwnerOfUnauthorizedSender;
239
251
  private effectiveOwners;
240
252
  private authorizationIntegrity;
241
- /**
242
- * Report what the session actually did with the prompt, not what the config
243
- * asked for. `interrupt: true` used to be reported as "your request
244
- * interrupted the previous task" unconditionally; the session now answers
245
- * whether anything was cancelled, whether the request is queued behind
246
- * earlier prompts, or whether it is held until the current task reaches a
247
- * safe stopping point. Backends that report no delivery state keep the old
248
- * queuedBehind-based wording.
249
- */
250
- private acceptanceNotice;
251
253
  private complete;
252
254
  private commentsState;
253
255
  /** Model-authored commentary only; raw protocol/tool data never reaches here. */
@@ -267,15 +269,12 @@ export declare class OwnerChannel implements OwnerChannelHandle {
267
269
  private progressPhase;
268
270
  private watchLoop;
269
271
  /**
270
- * `recovered` distinguishes a first-ever start (no state, nothing lost) from a
271
- * cursor we HAD and can no longer read. Only the latter is a recovery, and the
272
- * caller needs to know because the reason it reports is the only evidence a
273
- * durable cursor was ever lost.
272
+ * `recovered` distinguishes a first-ever start from unreadable persisted
273
+ * diagnostics. Notification correctness does not depend on this state: every
274
+ * establishment drains and then replays SDK hints from offset zero.
274
275
  */
275
276
  private readWatchState;
276
277
  private writeWatchState;
277
- /** Compatibility path for injected child-process tests; production is direct. */
278
- private legacyWatchLoop;
279
278
  private errorText;
280
279
  private logError;
281
280
  }