@ours.network/fleet 1.2.0-nightly.83 → 1.2.0-nightly.84

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.
@@ -1,5 +1,5 @@
1
1
  import { createReadStream } from 'node:fs';
2
- import { open, readFile, readdir } from 'node:fs/promises';
2
+ import { open, readFile, readdir, stat } from 'node:fs/promises';
3
3
  import { join } from 'node:path';
4
4
  import { createInterface } from 'node:readline';
5
5
  const HISTORICAL_KINDS = ['file.attached', 'fleet.task_created', 'prompt.admitted', 'message.chunk', 'message.replace', 'tool.upsert', 'tool.content_chunk', 'turn.completed'];
@@ -40,8 +40,15 @@ async function readLinesBackward(path, visit, signal) {
40
40
  await file.close();
41
41
  }
42
42
  }
43
+ // Rotated segments never change, so their last cursor is reused until the file itself changes.
44
+ const lastSeqs = new Map();
43
45
  /** The last event cursor in a segment, read from its tail; undefined when none is readable. */
44
46
  async function lastSeq(path) {
47
+ const info = await stat(path).catch(() => undefined);
48
+ const key = info && `${info.dev}:${info.ino}:${info.size}:${info.mtimeMs}`;
49
+ const known = key ? lastSeqs.get(path) : undefined;
50
+ if (key && known?.key === key)
51
+ return known.seq;
45
52
  let seq;
46
53
  await readLinesBackward(path, line => { try {
47
54
  const value = Number(JSON.parse(line).seq);
@@ -51,6 +58,12 @@ async function lastSeq(path) {
51
58
  }
52
59
  }
53
60
  catch { } return true; });
61
+ if (key) {
62
+ lastSeqs.delete(path);
63
+ lastSeqs.set(path, { key, seq });
64
+ if (lastSeqs.size > 4096)
65
+ lastSeqs.delete(lastSeqs.keys().next().value);
66
+ }
54
67
  return seq;
55
68
  }
56
69
  /**
@@ -16,6 +16,8 @@ export declare class RoleRepository {
16
16
  constructor(options?: RoleRepositoryOptions);
17
17
  list(strict?: boolean): Promise<RoleRecord[]>;
18
18
  get(id: string): Promise<RoleRecord | undefined>;
19
+ private scan;
20
+ private record;
19
21
  stateDir(record: RoleRecord): string | undefined;
20
22
  private detect;
21
23
  }
@@ -104,6 +104,20 @@ export class RoleRepository {
104
104
  this.options = options;
105
105
  }
106
106
  async list(strict = false) {
107
+ const scan = this.scan(strict);
108
+ const names = [...new Set([...scan.configuredByName.keys(), ...scan.permanent, ...scan.temporary])].sort();
109
+ return boundedMap(names, this.options.concurrency ?? 8, name => this.record(scan, name, strict));
110
+ }
111
+ // One role's record is built from its own state and probe; the other roles are never probed.
112
+ async get(id) {
113
+ if (!ROLE_NAME_RE.test(id))
114
+ return undefined;
115
+ const scan = this.scan(false);
116
+ if (!scan.configuredByName.has(id) && !scan.permanent.has(id) && !scan.temporary.has(id))
117
+ return undefined;
118
+ return this.record(scan, id, false);
119
+ }
120
+ scan(strict) {
107
121
  const permanentRoot = this.options.permanentRoot ?? agentsRoot();
108
122
  const temporaryRoot = this.options.temporaryRoot ?? tmpRoot();
109
123
  let configured = [];
@@ -116,60 +130,58 @@ export class RoleRepository {
116
130
  throw error;
117
131
  configProblem = { code: 'config_invalid', severity: 'error', source: 'config', detail: error.message };
118
132
  }
119
- const permanent = new Set(safeDirs(permanentRoot, strict));
120
- const temporary = new Set(safeDirs(temporaryRoot, strict));
121
- const configuredByName = new Map(configured.map(role => [role.name, role]));
122
- const names = [...new Set([...configuredByName.keys(), ...permanent, ...temporary])].sort();
123
- return boundedMap(names, this.options.concurrency ?? 8, async (name) => {
124
- const config = configuredByName.get(name);
125
- const inPermanent = permanent.has(name);
126
- const inTemporary = temporary.has(name);
127
- const problems = configProblem ? [configProblem] : [];
128
- let tempRole;
129
- let stateHealth = inPermanent || inTemporary ? 'present' : 'missing';
130
- if (inTemporary) {
131
- const read = snapshot(join(temporaryRoot, name, 'role.yaml'), name);
132
- tempRole = read.role;
133
- if (strict && read.problem)
134
- throw new Error('temporary role snapshot unavailable');
135
- if (read.problem) {
136
- problems.push(read.problem);
137
- stateHealth = 'corrupt';
138
- }
139
- }
140
- if (inPermanent && inTemporary)
141
- problems.push({ code: 'duplicate_state', severity: 'error', detail: 'both permanent and temporary state exist' });
142
- const intended = config?.session ?? tempRole?.session;
143
- const detected = await this.detect(name, intended);
144
- if (intended && detected !== 'none' && detected !== intended && detected !== 'ambiguous')
145
- problems.push({
146
- code: 'backend_mismatch', severity: 'warning',
147
- detail: `configured ${intended}, detected ${detected}`,
148
- });
149
- const stateRef = inTemporary ? { lifetime: 'temporary' }
150
- : inPermanent ? { lifetime: 'permanent' } : undefined;
151
- const tempStateDir = inTemporary ? join(temporaryRoot, name) : undefined;
152
- const taskId = !config && tempStateDir ? retainedTaskId(tempStateDir, name) : undefined;
153
- return {
154
- id: name,
155
- lifetime: config ? 'permanent' : inTemporary ? 'temporary' : 'orphan',
156
- configured: Boolean(config),
157
- ...(taskId ? { taskId } : {}),
158
- config: config ? view(config) : tempRole ? view(tempRole) : undefined,
159
- stateRef,
160
- stateHealth,
161
- configuredBackend: intended,
162
- detectedBackend: detected,
163
- compatibility: { compatible: true },
164
- problems,
165
- lineage: tempStateDir ? readSpawnLineage(tempStateDir) : undefined,
166
- };
167
- });
133
+ return {
134
+ temporaryRoot, configProblem,
135
+ permanent: new Set(safeDirs(permanentRoot, strict)),
136
+ temporary: new Set(safeDirs(temporaryRoot, strict)),
137
+ configuredByName: new Map(configured.map(role => [role.name, role])),
138
+ };
168
139
  }
169
- async get(id) {
170
- if (!ROLE_NAME_RE.test(id))
171
- return undefined;
172
- return (await this.list()).find(role => role.id === id);
140
+ async record(scan, name, strict) {
141
+ const { temporaryRoot, configProblem } = scan;
142
+ const config = scan.configuredByName.get(name);
143
+ const inPermanent = scan.permanent.has(name);
144
+ const inTemporary = scan.temporary.has(name);
145
+ const problems = configProblem ? [configProblem] : [];
146
+ let tempRole;
147
+ let stateHealth = inPermanent || inTemporary ? 'present' : 'missing';
148
+ if (inTemporary) {
149
+ const read = snapshot(join(temporaryRoot, name, 'role.yaml'), name);
150
+ tempRole = read.role;
151
+ if (strict && read.problem)
152
+ throw new Error('temporary role snapshot unavailable');
153
+ if (read.problem) {
154
+ problems.push(read.problem);
155
+ stateHealth = 'corrupt';
156
+ }
157
+ }
158
+ if (inPermanent && inTemporary)
159
+ problems.push({ code: 'duplicate_state', severity: 'error', detail: 'both permanent and temporary state exist' });
160
+ const intended = config?.session ?? tempRole?.session;
161
+ const detected = await this.detect(name, intended);
162
+ if (intended && detected !== 'none' && detected !== intended && detected !== 'ambiguous')
163
+ problems.push({
164
+ code: 'backend_mismatch', severity: 'warning',
165
+ detail: `configured ${intended}, detected ${detected}`,
166
+ });
167
+ const stateRef = inTemporary ? { lifetime: 'temporary' }
168
+ : inPermanent ? { lifetime: 'permanent' } : undefined;
169
+ const tempStateDir = inTemporary ? join(temporaryRoot, name) : undefined;
170
+ const taskId = !config && tempStateDir ? retainedTaskId(tempStateDir, name) : undefined;
171
+ return {
172
+ id: name,
173
+ lifetime: config ? 'permanent' : inTemporary ? 'temporary' : 'orphan',
174
+ configured: Boolean(config),
175
+ ...(taskId ? { taskId } : {}),
176
+ config: config ? view(config) : tempRole ? view(tempRole) : undefined,
177
+ stateRef,
178
+ stateHealth,
179
+ configuredBackend: intended,
180
+ detectedBackend: detected,
181
+ compatibility: { compatible: true },
182
+ problems,
183
+ lineage: tempStateDir ? readSpawnLineage(tempStateDir) : undefined,
184
+ };
173
185
  }
174
186
  stateDir(record) {
175
187
  if (!record.stateRef)
@@ -1,9 +1,9 @@
1
1
  {
2
- "version": "1.2.0-nightly.83",
3
- "buildId": "4171a7e61e16",
4
- "commit": "dcb322603476266ea118965f0f8b5ba4e2017733",
2
+ "version": "1.2.0-nightly.84",
3
+ "buildId": "a63a3595510b",
4
+ "commit": "6db38803d52345eada7d87cc364a8a98d1144666",
5
5
  "dirty": true,
6
- "builtAt": "2026-10-20T18:25:32.708Z",
6
+ "builtAt": "2026-10-20T19:57:42.666Z",
7
7
  "capabilities": [
8
8
  "service.refresh-v1",
9
9
  "managed-cli.codex-reasoning-effort-v1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "1.2.0-nightly.83",
3
+ "version": "1.2.0-nightly.84",
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",