@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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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)
|
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.84",
|
|
3
|
+
"buildId": "a63a3595510b",
|
|
4
|
+
"commit": "6db38803d52345eada7d87cc364a8a98d1144666",
|
|
5
5
|
"dirty": true,
|
|
6
|
-
"builtAt": "2026-10-
|
|
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.
|
|
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",
|