@ours.network/fleet 1.2.0-nightly.81 → 1.2.0-nightly.83
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.
|
@@ -25,9 +25,6 @@ export declare function resumedConversationPage(stateDir: string, request: {
|
|
|
25
25
|
}, live: ConversationPageView, expectedSessionId: string): Promise<ConversationPageView>;
|
|
26
26
|
/** Download authorization requires an actual published attachment, never an orphan copy. */
|
|
27
27
|
export declare function conversationHasAttachment(stateDir: string, attachment: import('../file-delivery/types.js').DeliveredFile): Promise<boolean>;
|
|
28
|
-
/** Passive, bounded last assistant text for directory previews. Never opens the
|
|
29
|
-
* ledger writer/recovery path or acquires a session/controller. An incomplete
|
|
30
|
-
* newest append is not committed yet; completed malformed records still fail. */
|
|
31
28
|
export declare function conversationPreview(stateDir: string, signal: AbortSignal): Promise<{
|
|
32
29
|
text: string;
|
|
33
30
|
at: string;
|
|
@@ -230,6 +230,60 @@ export async function conversationHasAttachment(stateDir, attachment) {
|
|
|
230
230
|
/** Passive, bounded last assistant text for directory previews. Never opens the
|
|
231
231
|
* ledger writer/recovery path or acquires a session/controller. An incomplete
|
|
232
232
|
* newest append is not committed yet; completed malformed records still fail. */
|
|
233
|
+
/** Bytes of a segment's tail a preview may consider, and the newest events it reads. */
|
|
234
|
+
const PREVIEW_WINDOW_BYTES = 2 * 1024 * 1024;
|
|
235
|
+
const PREVIEW_EVENTS = 200;
|
|
236
|
+
/**
|
|
237
|
+
* Newest-first completed events from the last PREVIEW_WINDOW_BYTES of one
|
|
238
|
+
* segment, read backwards in chunks until `need` events are found. A record
|
|
239
|
+
* cut by the window start is not complete and is skipped. Only the newest
|
|
240
|
+
* segment can have an in-flight append; elsewhere a partial record fails.
|
|
241
|
+
*/
|
|
242
|
+
async function previewSegmentEvents(path, newest, need, signal) {
|
|
243
|
+
const events = [];
|
|
244
|
+
const file = await open(path, 'r');
|
|
245
|
+
try {
|
|
246
|
+
const size = (await file.stat()).size, floor = Math.max(0, size - PREVIEW_WINDOW_BYTES);
|
|
247
|
+
let position = size, carry = Buffer.alloc(0), trailing = true;
|
|
248
|
+
const take = (line) => {
|
|
249
|
+
const event = JSON.parse(line);
|
|
250
|
+
if (event.schemaVersion !== 1 || !Number.isSafeInteger(event.seq))
|
|
251
|
+
throw Error('preview ledger invalid');
|
|
252
|
+
events.push(event);
|
|
253
|
+
};
|
|
254
|
+
while (position > floor && events.length < need) {
|
|
255
|
+
signal.throwIfAborted();
|
|
256
|
+
const length = Math.min(64 * 1024, position - floor);
|
|
257
|
+
position -= length;
|
|
258
|
+
const chunk = Buffer.alloc(length);
|
|
259
|
+
await file.read(chunk, 0, length, position);
|
|
260
|
+
const buffer = Buffer.concat([chunk, carry]);
|
|
261
|
+
let end = buffer.length;
|
|
262
|
+
for (let i = buffer.length - 1; i >= 0 && events.length < need; i--) {
|
|
263
|
+
if (buffer[i] !== 10)
|
|
264
|
+
continue;
|
|
265
|
+
if (trailing) {
|
|
266
|
+
if (end > i + 1 && !newest)
|
|
267
|
+
throw Error('preview ledger has partial record');
|
|
268
|
+
trailing = false;
|
|
269
|
+
}
|
|
270
|
+
else
|
|
271
|
+
take(buffer.toString('utf8', i + 1, end));
|
|
272
|
+
end = i;
|
|
273
|
+
}
|
|
274
|
+
carry = buffer.subarray(0, end);
|
|
275
|
+
}
|
|
276
|
+
if (trailing && position === floor && (!newest || floor > 0) && size > 0)
|
|
277
|
+
throw Error('preview ledger has partial record');
|
|
278
|
+
// At the file start the remaining bytes are the first complete record.
|
|
279
|
+
if (!trailing && floor === 0 && position === 0 && events.length < need)
|
|
280
|
+
take(carry.toString('utf8'));
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
await file.close();
|
|
284
|
+
}
|
|
285
|
+
return events;
|
|
286
|
+
}
|
|
233
287
|
export async function conversationPreview(stateDir, signal) {
|
|
234
288
|
signal.throwIfAborted();
|
|
235
289
|
const dir = join(stateDir, '.conversation');
|
|
@@ -245,30 +299,8 @@ export async function conversationPreview(stateDir, signal) {
|
|
|
245
299
|
const events = [];
|
|
246
300
|
for (const name of files) {
|
|
247
301
|
signal.throwIfAborted();
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const size = (await file.stat()).size, length = Math.min(size, 2 * 1024 * 1024), buffer = Buffer.alloc(length);
|
|
251
|
-
await file.read(buffer, 0, length, size - length);
|
|
252
|
-
const completeEnd = buffer.lastIndexOf(10) + 1;
|
|
253
|
-
// Only the newest segment can have an in-flight append. If its tail
|
|
254
|
-
// fills the bounded window, we cannot establish the previous preview.
|
|
255
|
-
if (completeEnd < length && (name !== files[0] || (size > length && completeEnd === 0)))
|
|
256
|
-
throw Error('preview ledger has partial record');
|
|
257
|
-
const lines = buffer.toString('utf8', 0, completeEnd).split('\n');
|
|
258
|
-
lines.pop();
|
|
259
|
-
if (size > length)
|
|
260
|
-
lines.shift();
|
|
261
|
-
for (let i = lines.length - 1; i >= 0 && events.length < 200; i--) {
|
|
262
|
-
const event = JSON.parse(lines[i]);
|
|
263
|
-
if (event.schemaVersion !== 1 || !Number.isSafeInteger(event.seq))
|
|
264
|
-
throw Error('preview ledger invalid');
|
|
265
|
-
events.push(event);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
finally {
|
|
269
|
-
await file.close();
|
|
270
|
-
}
|
|
271
|
-
if (events.length >= 200)
|
|
302
|
+
events.push(...await previewSegmentEvents(join(dir, name), name === files[0], PREVIEW_EVENTS - events.length, signal));
|
|
303
|
+
if (events.length >= PREVIEW_EVENTS)
|
|
272
304
|
break;
|
|
273
305
|
}
|
|
274
306
|
signal.throwIfAborted();
|
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.2.0-nightly.
|
|
3
|
-
"buildId": "
|
|
4
|
-
"commit": "
|
|
2
|
+
"version": "1.2.0-nightly.83",
|
|
3
|
+
"buildId": "4171a7e61e16",
|
|
4
|
+
"commit": "dcb322603476266ea118965f0f8b5ba4e2017733",
|
|
5
5
|
"dirty": true,
|
|
6
|
-
"builtAt": "2026-10-
|
|
6
|
+
"builtAt": "2026-10-20T18:25:32.708Z",
|
|
7
7
|
"capabilities": [
|
|
8
8
|
"service.refresh-v1",
|
|
9
9
|
"managed-cli.codex-reasoning-effort-v1",
|
|
@@ -62,6 +62,8 @@ export interface ResourceWireFrame {
|
|
|
62
62
|
export declare const RESOURCE_FRAME_BYTES: number;
|
|
63
63
|
export declare const RESOURCE_PENDING_BYTES: number;
|
|
64
64
|
export declare const RESOURCE_ASSEMBLY_BYTES: number;
|
|
65
|
+
/** Minimum spacing of activity-only follow-up captures of one view. */
|
|
66
|
+
export declare const ACTIVITY_CAPTURE_INTERVAL_MS = 1000;
|
|
65
67
|
/** Lazily yield byte chunks so backpressure never queues an entire large record.
|
|
66
68
|
* Base64 prevents splitting a Unicode scalar from corrupting JSON/SSE parsing. */
|
|
67
69
|
export declare function resourceFrames(envelope: ResourceEnvelope): Generator<ResourceWireFrame>;
|
|
@@ -109,9 +111,10 @@ export declare class FleetResourceViews {
|
|
|
109
111
|
private scope;
|
|
110
112
|
private project;
|
|
111
113
|
private configurationInputs;
|
|
114
|
+
private activityCaptureIntervalMs;
|
|
112
115
|
readonly producerGeneration: `${string}-${string}-${string}-${string}-${string}`;
|
|
113
116
|
private connections;
|
|
114
|
-
constructor(scope: FleetResourceScope, project: (descriptor: ResourceDescriptor, signal: AbortSignal, owner: string) => Promise<unknown>, configurationInputs?: () => readonly string[]);
|
|
117
|
+
constructor(scope: FleetResourceScope, project: (descriptor: ResourceDescriptor, signal: AbortSignal, owner: string) => Promise<unknown>, configurationInputs?: () => readonly string[], activityCaptureIntervalMs?: number);
|
|
115
118
|
open(response: ServerResponse, owner: string, authorized: () => boolean): {
|
|
116
119
|
connectionId: `${string}-${string}-${string}-${string}-${string}`;
|
|
117
120
|
close: () => void;
|
|
@@ -23,6 +23,8 @@ export const RESOURCE_FRAME_BYTES = 512 * 1024;
|
|
|
23
23
|
export const RESOURCE_PENDING_BYTES = 8 * 1024 * 1024;
|
|
24
24
|
export const RESOURCE_ASSEMBLY_BYTES = 16 * 1024 * 1024;
|
|
25
25
|
const CHUNK_BYTES = 256 * 1024;
|
|
26
|
+
/** Minimum spacing of activity-only follow-up captures of one view. */
|
|
27
|
+
export const ACTIVITY_CAPTURE_INTERVAL_MS = 1000;
|
|
26
28
|
/** Lazily yield byte chunks so backpressure never queues an entire large record.
|
|
27
29
|
* Base64 prevents splitting a Unicode scalar from corrupting JSON/SSE parsing. */
|
|
28
30
|
export function* resourceFrames(envelope) {
|
|
@@ -187,12 +189,14 @@ export class FleetResourceViews {
|
|
|
187
189
|
scope;
|
|
188
190
|
project;
|
|
189
191
|
configurationInputs;
|
|
192
|
+
activityCaptureIntervalMs;
|
|
190
193
|
producerGeneration = randomUUID();
|
|
191
194
|
connections = new Map();
|
|
192
|
-
constructor(scope, project, configurationInputs = () => []) {
|
|
195
|
+
constructor(scope, project, configurationInputs = () => [], activityCaptureIntervalMs = ACTIVITY_CAPTURE_INTERVAL_MS) {
|
|
193
196
|
this.scope = scope;
|
|
194
197
|
this.project = project;
|
|
195
198
|
this.configurationInputs = configurationInputs;
|
|
199
|
+
this.activityCaptureIntervalMs = activityCaptureIntervalMs;
|
|
196
200
|
}
|
|
197
201
|
open(response, owner, authorized) {
|
|
198
202
|
const id = randomUUID(), controller = new AbortController();
|
|
@@ -334,7 +338,7 @@ export class FleetResourceViews {
|
|
|
334
338
|
return connection;
|
|
335
339
|
}
|
|
336
340
|
current(connection, view) { return !connection.controller.signal.aborted && !view.controller.signal.aborted && connection.views.get(view.descriptor.viewId) === view; }
|
|
337
|
-
retire(view) { view.controller.abort(); view.rejectInitial(new ResourceBindingError(409, 'resource binding retired')); }
|
|
341
|
+
retire(view) { clearTimeout(view.activityTimer); view.activityTimer = undefined; view.controller.abort(); view.rejectInitial(new ResourceBindingError(409, 'resource binding retired')); }
|
|
338
342
|
emit(connection, view, kind, payload, source) {
|
|
339
343
|
return connection.output.send({ protocolVersion: 1, producerGeneration: this.producerGeneration, viewId: view.descriptor.viewId, subscriptionEpoch: view.epoch, baselineId: view.baselineId, sequence: view.sequence + 1, revision: view.revision, kind, payload, ...(source ? { source } : {}) }, () => { view.sequence++; if (source || kind === 'ready')
|
|
340
344
|
view.qualifiedAt = Date.now(); });
|
|
@@ -388,10 +392,10 @@ export class FleetResourceViews {
|
|
|
388
392
|
return false;
|
|
389
393
|
// Only classify known activity writes under the configured role roots.
|
|
390
394
|
// Parent/unknown events and every other file retain full invalidation.
|
|
391
|
-
// The canonical web producer atomically replaces hashed checkpoint files
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
if (
|
|
395
|
+
// The canonical web producer atomically replaces hashed checkpoint files,
|
|
396
|
+
// once per ledger advance of any role. No projection reads these delivery
|
|
397
|
+
// acknowledgements, so they neither fence a capture nor wake a follow-up.
|
|
398
|
+
if (/^[a-f0-9]{64}\.json$/.test(relative(resolve(stateRoot(), 'web', 'notification-outboxes'), path)))
|
|
395
399
|
return false;
|
|
396
400
|
for (const root of roots) {
|
|
397
401
|
const parts = relative(resolve(root), path).split(sep);
|
|
@@ -421,9 +425,28 @@ export class FleetResourceViews {
|
|
|
421
425
|
}
|
|
422
426
|
return true;
|
|
423
427
|
}
|
|
424
|
-
schedule(connection, view) {
|
|
428
|
+
schedule(connection, view, due = false) {
|
|
425
429
|
if (!this.current(connection, view) || view.failed)
|
|
426
430
|
return;
|
|
431
|
+
// Agent activity (ledger appends, status files) changes previews and status
|
|
432
|
+
// continuously while an agent works. Without a structural change since the
|
|
433
|
+
// latest capture began, coalesce those wakes into one trailing capture per
|
|
434
|
+
// interval instead of re-projecting every role after every append.
|
|
435
|
+
const wait = !due && view.captureSource !== view.source && view.capturedAt !== undefined
|
|
436
|
+
&& view.captureSource.snapshot().position === view.capturedStructure
|
|
437
|
+
? this.activityCaptureIntervalMs - (Date.now() - view.capturedAt) : 0;
|
|
438
|
+
if (wait > 0) {
|
|
439
|
+
view.scheduledPosition = view.source.snapshot().position;
|
|
440
|
+
view.dirty = true;
|
|
441
|
+
if (!view.running && !view.activityTimer) {
|
|
442
|
+
// The trailing capture runs when its timer fires, whatever the clock reads.
|
|
443
|
+
view.activityTimer = setTimeout(() => { view.activityTimer = undefined; this.schedule(connection, view, true); }, Math.min(wait, this.activityCaptureIntervalMs));
|
|
444
|
+
view.activityTimer.unref?.();
|
|
445
|
+
}
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
clearTimeout(view.activityTimer);
|
|
449
|
+
view.activityTimer = undefined;
|
|
427
450
|
view.scheduledPosition = view.source.snapshot().position;
|
|
428
451
|
view.dirty = true;
|
|
429
452
|
if (view.running)
|
|
@@ -446,6 +469,8 @@ export class FleetResourceViews {
|
|
|
446
469
|
if (!view.dirty || !this.current(connection, view))
|
|
447
470
|
return;
|
|
448
471
|
view.dirty = false;
|
|
472
|
+
view.capturedAt = Date.now();
|
|
473
|
+
view.capturedStructure = view.captureSource.snapshot().position;
|
|
449
474
|
let payload, source;
|
|
450
475
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
451
476
|
view.controller.signal.throwIfAborted();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "1.2.0-nightly.
|
|
3
|
+
"version": "1.2.0-nightly.83",
|
|
4
4
|
"description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, managed native/ACP sessions, supervision, and ours.network messaging.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "FSL-1.1-Apache-2.0",
|