@ours.network/fleet 2.3.1 → 2.3.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.
- package/dist/build-info.json +4 -4
- package/dist/rooms-tasks/task-state.js +7 -2
- package/dist/web/fleet-config-service.d.ts +8 -0
- package/dist/web/fleet-config-service.js +23 -8
- package/dist/web/resource-views.d.ts +2 -0
- package/dist/web/resource-views.js +26 -15
- package/dist/web/server.js +3 -0
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.3.
|
|
3
|
-
"buildId": "
|
|
4
|
-
"commit": "
|
|
2
|
+
"version": "2.3.2",
|
|
3
|
+
"buildId": "597fdbc5f69f",
|
|
4
|
+
"commit": "31b6aae8610ef1e4d74212cb06d9b03379a8eb40",
|
|
5
5
|
"dirty": false,
|
|
6
|
-
"builtAt": "2026-10-
|
|
6
|
+
"builtAt": "2026-10-31T06:49:15.117Z",
|
|
7
7
|
"capabilities": [
|
|
8
8
|
"service.refresh-v1",
|
|
9
9
|
"managed-cli.codex-reasoning-effort-v1",
|
|
@@ -179,7 +179,7 @@ function writeTask(record, options = {}) {
|
|
|
179
179
|
const next = status(record), before = previous ? status(previous) : undefined;
|
|
180
180
|
pending.push({ eventId: `task:${record.task_id}:${randomUUID()}`, taskId: record.task_id,
|
|
181
181
|
title: `Task ${!previous ? 'created' : 'updated'}: ${taskLabel(record)}`.slice(0, 160),
|
|
182
|
-
body:
|
|
182
|
+
body: taskSentence(record, `${before ? `${before} → ` : 'created in '}${next}.`),
|
|
183
183
|
target: { kind: 'task', taskId: record.task_id }, delivery: 'push-only' });
|
|
184
184
|
}
|
|
185
185
|
pending.push(...contributionEvents(previous, record));
|
|
@@ -196,6 +196,11 @@ function writeTask(record, options = {}) {
|
|
|
196
196
|
}
|
|
197
197
|
/** Human label for notification titles: the mission key, when issued, precedes the title. */
|
|
198
198
|
const taskLabel = (record) => record.key ? `${record.key} · ${record.title}` : record.title;
|
|
199
|
+
/**
|
|
200
|
+
* Notification body naming the task by its mission key. The opaque task ID stays in routing and
|
|
201
|
+
* event fields only; a task without a key relies on its title, which the notification title shows.
|
|
202
|
+
*/
|
|
203
|
+
const taskSentence = (record, text) => record.key ? `Task ${record.key}: ${text}` : text[0].toUpperCase() + text.slice(1);
|
|
199
204
|
/** Packages written before revisions existed count as revision 1; their next change becomes 2. No package is 0. */
|
|
200
205
|
export const reviewRevision = (review) => review ? review.revision ?? 1 : 0;
|
|
201
206
|
const contributionsChanged = (previous, record) => (record.progress ?? []).some(entry => !(previous.progress ?? []).some(old => old.id === entry.id))
|
|
@@ -214,7 +219,7 @@ function contributionEvents(previous, record) {
|
|
|
214
219
|
const revision = reviewRevision(record.review);
|
|
215
220
|
if (revision > reviewRevision(previous.review))
|
|
216
221
|
events.push({ eventId: `task:${id}:review:${revision}`, taskId: id, title: `To review updated: ${taskLabel(record)}`.slice(0, 160),
|
|
217
|
-
body:
|
|
222
|
+
body: taskSentence(record, 'the review package changed.'), target: { kind: 'task', taskId: id }, delivery: 'push-only' });
|
|
218
223
|
return events;
|
|
219
224
|
}
|
|
220
225
|
/** Item-based Progress unread and revision-based To review unread. An absent read state means nothing is unread. */
|
|
@@ -50,6 +50,14 @@ export declare class FleetConfigService {
|
|
|
50
50
|
private validationInputs;
|
|
51
51
|
/** Cached during canonical reads; observer filtering must never parse YAML. */
|
|
52
52
|
resourceValidationInputs(): readonly string[];
|
|
53
|
+
/**
|
|
54
|
+
* Record the manifest's external file references before a projection that loads
|
|
55
|
+
* the configuration, so a reference anywhere (including a task workspace) fences
|
|
56
|
+
* that capture even before the configuration itself has been read. A manifest that
|
|
57
|
+
* cannot be read keeps the last recorded inputs; the projection reports its error.
|
|
58
|
+
*/
|
|
59
|
+
observeValidationInputs(): void;
|
|
60
|
+
private recordValidationInputs;
|
|
53
61
|
constructor(options?: FleetConfigServiceOptions);
|
|
54
62
|
read(includeDefinitions?: boolean): ConfigReadResult;
|
|
55
63
|
preview(baseRevision: string, input: unknown): Promise<ConfigPreviewResult>;
|
|
@@ -21,6 +21,28 @@ export class FleetConfigService {
|
|
|
21
21
|
validationInputs = [];
|
|
22
22
|
/** Cached during canonical reads; observer filtering must never parse YAML. */
|
|
23
23
|
resourceValidationInputs() { return this.validationInputs; }
|
|
24
|
+
/**
|
|
25
|
+
* Record the manifest's external file references before a projection that loads
|
|
26
|
+
* the configuration, so a reference anywhere (including a task workspace) fences
|
|
27
|
+
* that capture even before the configuration itself has been read. A manifest that
|
|
28
|
+
* cannot be read keeps the last recorded inputs; the projection reports its error.
|
|
29
|
+
*/
|
|
30
|
+
observeValidationInputs() {
|
|
31
|
+
try {
|
|
32
|
+
this.recordValidationInputs(parse(trustedFile(this.path), { prettyErrors: false }));
|
|
33
|
+
}
|
|
34
|
+
catch { /* unchanged */ }
|
|
35
|
+
}
|
|
36
|
+
recordValidationInputs(parsed) {
|
|
37
|
+
const record = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
38
|
+
const manifest = record(parsed);
|
|
39
|
+
const vars = record(manifest.vars);
|
|
40
|
+
const expand = (value) => value.replace(/\$\{(\w+)\}/g, (match, key) => key in vars ? String(vars[key]) : match);
|
|
41
|
+
const invite = record(record(manifest.rooms).owner).public_invite_file;
|
|
42
|
+
const prompts = Object.values(record(manifest.watchdogs)).map(value => record(value).prompt_file);
|
|
43
|
+
this.validationInputs = [...(typeof invite === 'string' ? [expand(expand(invite))] : []),
|
|
44
|
+
...prompts.filter((value) => typeof value === 'string').map(expand)].map(value => resolve(value));
|
|
45
|
+
}
|
|
24
46
|
constructor(options = {}) {
|
|
25
47
|
this.path = options.configPath ?? defaultConfigPath();
|
|
26
48
|
this.preflight = options.preflight ?? (async () => emptyReport());
|
|
@@ -30,16 +52,9 @@ export class FleetConfigService {
|
|
|
30
52
|
const sources = readSources(this.path);
|
|
31
53
|
let raw;
|
|
32
54
|
try {
|
|
33
|
-
const manifest = parse(sources.documents.get(basename(sources.manifest)), { prettyErrors: false });
|
|
34
|
-
const record = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
35
|
-
const vars = record(manifest.vars);
|
|
36
|
-
const expand = (value) => value.replace(/\$\{(\w+)\}/g, (match, key) => key in vars ? String(vars[key]) : match);
|
|
37
|
-
const invite = record(record(manifest.rooms).owner).public_invite_file;
|
|
38
|
-
const prompts = Object.values(record(manifest.watchdogs)).map(value => record(value).prompt_file);
|
|
39
55
|
// Record even invalid/missing references before validation, so repairing an
|
|
40
56
|
// already observed dependency can never retain a certified stale capture.
|
|
41
|
-
this.
|
|
42
|
-
...prompts.filter((value) => typeof value === 'string').map(expand)].map(value => resolve(value));
|
|
57
|
+
this.recordValidationInputs(parse(sources.documents.get(basename(sources.manifest)), { prettyErrors: false }));
|
|
43
58
|
loadConfig(this.path, { yamlMode: 'strict' });
|
|
44
59
|
raw = modelFrom(sources, includeDefinitions);
|
|
45
60
|
}
|
|
@@ -135,6 +135,8 @@ export declare class FleetResourceViews {
|
|
|
135
135
|
private coverage;
|
|
136
136
|
/** Certify each view's last projected source; never a position its capture did not read. */
|
|
137
137
|
private cover;
|
|
138
|
+
/** Whether `path` is, contains or lies within the manifest, its split definitions or another configuration input. */
|
|
139
|
+
private configurationDependency;
|
|
138
140
|
private affects;
|
|
139
141
|
private schedule;
|
|
140
142
|
private refresh;
|
|
@@ -364,22 +364,25 @@ export class FleetResourceViews {
|
|
|
364
364
|
view.qualifiedAt = now;
|
|
365
365
|
} });
|
|
366
366
|
}
|
|
367
|
+
/** Whether `path` is, contains or lies within the manifest, its split definitions or another configuration input. */
|
|
368
|
+
configurationDependency(path) {
|
|
369
|
+
const manifest = resolve(this.scope.configPath ?? defaultConfigPath());
|
|
370
|
+
const root = resolve(splitRootFor(manifest)), changed = resolve(path);
|
|
371
|
+
const within = (candidate, parent) => {
|
|
372
|
+
const local = relative(parent, candidate);
|
|
373
|
+
return local === '' || (local !== '..' && !local.startsWith('..' + sep));
|
|
374
|
+
};
|
|
375
|
+
// Configuration and provenance read the manifest and split definitions;
|
|
376
|
+
// loadConfig also rejects legacy YAML files in the global fleet.d root.
|
|
377
|
+
// Ancestor replacement/unknown directory wakes remain conservative.
|
|
378
|
+
const legacy = resolve(fleetDDir());
|
|
379
|
+
return within(changed, root) || within(root, changed) || within(manifest, changed)
|
|
380
|
+
|| within(changed, legacy) || within(legacy, changed)
|
|
381
|
+
|| this.configurationInputs().some(input => within(resolve(input), changed));
|
|
382
|
+
}
|
|
367
383
|
affects(descriptor, path, structural = false) {
|
|
368
|
-
if (descriptor.resource === 'configuration')
|
|
369
|
-
|
|
370
|
-
const root = resolve(splitRootFor(manifest)), changed = resolve(path);
|
|
371
|
-
const within = (candidate, parent) => {
|
|
372
|
-
const local = relative(parent, candidate);
|
|
373
|
-
return local === '' || (local !== '..' && !local.startsWith('..' + sep));
|
|
374
|
-
};
|
|
375
|
-
// Configuration and provenance read the manifest and split definitions;
|
|
376
|
-
// loadConfig also rejects legacy YAML files in the global fleet.d root.
|
|
377
|
-
// Ancestor replacement/unknown directory wakes remain conservative.
|
|
378
|
-
const legacy = resolve(fleetDDir());
|
|
379
|
-
return within(changed, root) || within(root, changed) || within(manifest, changed)
|
|
380
|
-
|| within(changed, legacy) || within(legacy, changed)
|
|
381
|
-
|| this.configurationInputs().some(input => within(resolve(input), changed));
|
|
382
|
-
}
|
|
384
|
+
if (descriptor.resource === 'configuration')
|
|
385
|
+
return this.configurationDependency(path);
|
|
383
386
|
// An explicitly selected manifest may use any pathname, including one
|
|
384
387
|
// otherwise reserved for an independent task receipt.
|
|
385
388
|
if (this.scope.configPath && resolve(path) === resolve(this.scope.configPath))
|
|
@@ -394,6 +397,14 @@ export class FleetResourceViews {
|
|
|
394
397
|
if (roleProjection && !inRoleRoot && (/^(?:history\.jsonl|models_cache\.json|(?:thread_history|state|logs|queue|goals)_\d+\.sqlite(?:-wal|-shm|-journal)?)$/.test(relative(resolve(homedir(), '.codex'), path))
|
|
395
398
|
|| /^(?:workspace-devices\.db(?:-wal|-shm|-journal)?|audit[/\\]audit\.jsonl)$/.test(relative(resolve(stateRoot(), 'web'), path))))
|
|
396
399
|
return false;
|
|
400
|
+
// Task and room workspaces hold their agents' own execution artifacts (checkouts,
|
|
401
|
+
// builds, test logs); role projections read nothing there except declared role
|
|
402
|
+
// roots and configuration (manifest, split definitions), which stay fenced. Working
|
|
403
|
+
// agents write there continuously, which otherwise fences every directory capture
|
|
404
|
+
// until it resyncs. The workspace directories themselves and their ancestors remain full wakes.
|
|
405
|
+
if (roleProjection && !inRoleRoot && /^(?:tasks|rooms)[/\\][^/\\]+[/\\]/.test(relative(resolve(stateRoot(), 'workspaces'), path))
|
|
406
|
+
&& !this.configurationDependency(path))
|
|
407
|
+
return false;
|
|
397
408
|
// Preview selection reads role config/state and conversation ledgers, never
|
|
398
409
|
// task layout run/operation/brief receipts. A worker may commit several of
|
|
399
410
|
// these while that independent canonical preview read is in flight.
|
package/dist/web/server.js
CHANGED
|
@@ -424,6 +424,9 @@ export async function buildWebServer(services, boundary, options = {}) {
|
|
|
424
424
|
const project = async (descriptor, signal, owner = '') => {
|
|
425
425
|
signal.throwIfAborted();
|
|
426
426
|
services.invalidateResourceCaches?.();
|
|
427
|
+
// Role projections load the configuration: fence this capture on its external inputs too.
|
|
428
|
+
if (['directory', 'roles', 'previews'].includes(descriptor.resource))
|
|
429
|
+
services.configuration?.observeValidationInputs();
|
|
427
430
|
switch (descriptor.resource) {
|
|
428
431
|
case 'task-layout':
|
|
429
432
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/fleet",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
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",
|