@zq-silk/yui 0.6.5 → 0.6.6
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/controller/agentRuntimeObserver.js +76 -16
- package/dist/controller/controller.js +398 -76
- package/dist/controller/fileSchedulerStoreAdapter.js +60 -51
- package/dist/controller/jobSupervisor.js +2 -16
- package/dist/controller/resourceInventoryLinux.js +73 -20
- package/dist/controller/runtime.js +1 -0
- package/dist/controller/runtimeEventProcessor.js +171 -31
- package/dist/coordination/keyedWorkQueue.js +189 -0
- package/dist/runtime/runtimeSessionCandidate.js +43 -0
- package/dist/scheduler/activeRoleRunDelivery.js +2 -4
- package/dist/scheduler/activeTaskProgress.js +2 -4
- package/dist/scheduler/leaderWakeupProcessor.js +3 -1
- package/dist/scheduler/ports.js +35 -2
- package/dist/scheduler/roleRunLiveness.js +44 -22
- package/dist/scheduler/roleRunStall.js +49 -25
- package/dist/storage/sqliteSchema.js +363 -14
- package/dist/storage/sqliteStore.js +403 -30
- package/dist/storage/storeRpc.js +5 -0
- package/dist/storage/taskStore.js +104 -6
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
3
3
|
import { createRuntimeObservation, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
4
|
+
const DEFAULT_MAX_CONCURRENT_SAMPLES = 8;
|
|
5
|
+
const MAX_CONCURRENT_SAMPLES = 64;
|
|
4
6
|
/**
|
|
5
7
|
* Controller-owned, provider-independent sampler. Drivers own source parsing;
|
|
6
8
|
* this component owns active-Run discovery, cursor lifetime, canonical event
|
|
@@ -11,11 +13,13 @@ export class AgentRuntimeObserver {
|
|
|
11
13
|
inbox;
|
|
12
14
|
drivers;
|
|
13
15
|
#states = new Map();
|
|
16
|
+
#maxConcurrentSamples;
|
|
14
17
|
#sequence = 0;
|
|
15
|
-
constructor(store, inbox, drivers = builtinAgentDriverRegistry()) {
|
|
18
|
+
constructor(store, inbox, drivers = builtinAgentDriverRegistry(), options = {}) {
|
|
16
19
|
this.store = store;
|
|
17
20
|
this.inbox = inbox;
|
|
18
21
|
this.drivers = drivers;
|
|
22
|
+
this.#maxConcurrentSamples = sampleConcurrency(options.maxConcurrentSamples);
|
|
19
23
|
}
|
|
20
24
|
async sample(now = new Date()) {
|
|
21
25
|
const active = this.activeSources();
|
|
@@ -25,7 +29,9 @@ export class AgentRuntimeObserver {
|
|
|
25
29
|
this.#states.delete(key);
|
|
26
30
|
}
|
|
27
31
|
const dirty = new Set();
|
|
28
|
-
|
|
32
|
+
const sequenceBase = this.#sequence;
|
|
33
|
+
this.#sequence += active.length;
|
|
34
|
+
await forEachConcurrent(active, this.#maxConcurrentSamples, async ({ key, fence, source, freshSession, persistedState }, index) => {
|
|
29
35
|
const existingState = this.#states.get(key);
|
|
30
36
|
// Cursor state is intentionally process-local, but the latest canonical
|
|
31
37
|
// usage/activity baseline is durable. Rehydrate it after Controller
|
|
@@ -49,7 +55,10 @@ export class AgentRuntimeObserver {
|
|
|
49
55
|
}
|
|
50
56
|
state.cursor = sample.cursor;
|
|
51
57
|
const at = now.toISOString();
|
|
52
|
-
|
|
58
|
+
// Reserve sequence numbers in source-key order before sampling. Provider
|
|
59
|
+
// latency can change completion order without changing observation
|
|
60
|
+
// identity or the canonical sequence assigned to a source.
|
|
61
|
+
const sequence = sequenceBase + index;
|
|
53
62
|
if (existingState === undefined && freshSession && state.usage === undefined) {
|
|
54
63
|
const zero = Object.freeze({ inputTokens: 0, outputTokens: 0 });
|
|
55
64
|
this.inbox.enqueueObservation(createRuntimeObservation({
|
|
@@ -114,28 +123,52 @@ export class AgentRuntimeObserver {
|
|
|
114
123
|
if (sample.activityId !== undefined)
|
|
115
124
|
state.activityId = sample.activityId;
|
|
116
125
|
this.#states.set(key, state);
|
|
117
|
-
})
|
|
118
|
-
return Object.freeze([...dirty].sort());
|
|
126
|
+
});
|
|
127
|
+
return Object.freeze([...dirty].sort(numericCompare));
|
|
119
128
|
}
|
|
120
129
|
activeSources() {
|
|
121
130
|
const result = [];
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
const indexedTaskIds = this.store.listActiveTaskIds?.();
|
|
132
|
+
// FileTaskStore remains a development/compatibility fallback. The normal
|
|
133
|
+
// Controller store is SQLite and must discover only its indexed hot set.
|
|
134
|
+
const activeTasks = indexedTaskIds === undefined
|
|
135
|
+
? this.store.listTasks().filter((task) => task.status === "active")
|
|
136
|
+
: [...new Set(indexedTaskIds)]
|
|
137
|
+
.sort(numericCompare)
|
|
138
|
+
.map((taskId) => this.store.getTask(taskId))
|
|
139
|
+
.filter((task) => (task !== null && task.status === "active"));
|
|
140
|
+
for (const task of activeTasks) {
|
|
141
|
+
// A Task still incurs one O(E) event projection. Group those observations
|
|
142
|
+
// by Run and sort each group once so every active Run can reuse the same
|
|
143
|
+
// ordered slice instead of repeatedly filtering/sorting all E events.
|
|
144
|
+
const observationsByRunId = new Map();
|
|
145
|
+
for (const event of this.store.listEvents(task.id)) {
|
|
146
|
+
const observation = runtimeObservationFromTaskEvent(event);
|
|
147
|
+
const runId = observation?.fence.runId;
|
|
148
|
+
if (observation === null || runId === undefined)
|
|
149
|
+
continue;
|
|
150
|
+
const grouped = observationsByRunId.get(runId);
|
|
151
|
+
if (grouped === undefined) {
|
|
152
|
+
observationsByRunId.set(runId, [observation]);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
grouped.push(observation);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const observations of observationsByRunId.values()) {
|
|
159
|
+
observations.sort(compareObservations);
|
|
160
|
+
}
|
|
128
161
|
for (const run of this.store.listAgentRuns(task.id)) {
|
|
129
162
|
if (run.status !== "active"
|
|
130
163
|
|| this.store.getActiveAgentRun(task.id, run.roleName)?.id !== run.id)
|
|
131
164
|
continue;
|
|
165
|
+
const observations = observationsByRunId.get(run.id) ?? [];
|
|
132
166
|
const accepted = observations
|
|
133
167
|
.filter((observation) => observation.kind === "turn.accepted"
|
|
134
168
|
&& observation.fence.runId === run.id
|
|
135
169
|
&& observation.fence.roleName === run.roleName
|
|
136
170
|
&& observation.fence.agentId === run.effective.agentId
|
|
137
171
|
&& observation.payload.observerSource !== undefined)
|
|
138
|
-
.sort(compareObservations)
|
|
139
172
|
.at(-1);
|
|
140
173
|
const source = accepted?.payload.observerSource;
|
|
141
174
|
if (accepted === undefined || source === undefined
|
|
@@ -150,19 +183,23 @@ export class AgentRuntimeObserver {
|
|
|
150
183
|
}
|
|
151
184
|
const fence = accepted.fence;
|
|
152
185
|
const exact = observations
|
|
153
|
-
.filter((observation) => runtimeObservationFenceMatches(fence, observation.fence))
|
|
154
|
-
.sort(compareObservations);
|
|
186
|
+
.filter((observation) => runtimeObservationFenceMatches(fence, observation.fence));
|
|
155
187
|
const persistedUsage = exact.filter((observation) => (observation.kind === "activity.observed"
|
|
156
188
|
&& observation.payload.usage !== undefined)).at(-1);
|
|
157
189
|
const persistedHealth = exact.filter((observation) => (observation.kind === "observer.health"
|
|
158
190
|
&& observation.payload.sourceId === source.sourceId)).at(-1);
|
|
159
191
|
result.push(Object.freeze({
|
|
160
192
|
key: JSON.stringify([
|
|
193
|
+
fence.taskId,
|
|
194
|
+
fence.roleName,
|
|
195
|
+
fence.runId,
|
|
196
|
+
fence.agentId,
|
|
161
197
|
fence.driverId,
|
|
198
|
+
fence.launchId,
|
|
162
199
|
fence.sessionGenerationId,
|
|
163
200
|
fence.nativeSessionId,
|
|
164
201
|
fence.nativeTurnId,
|
|
165
|
-
fence.
|
|
202
|
+
fence.receiptId,
|
|
166
203
|
source.sourceId
|
|
167
204
|
]),
|
|
168
205
|
fence,
|
|
@@ -187,8 +224,31 @@ export class AgentRuntimeObserver {
|
|
|
187
224
|
}));
|
|
188
225
|
}
|
|
189
226
|
}
|
|
190
|
-
return result;
|
|
227
|
+
return Object.freeze(result.sort((left, right) => numericCompare(left.key, right.key)));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async function forEachConcurrent(values, limit, visit) {
|
|
231
|
+
let nextIndex = 0;
|
|
232
|
+
const workers = Math.min(limit, values.length);
|
|
233
|
+
await Promise.all(Array.from({ length: workers }, async () => {
|
|
234
|
+
while (nextIndex < values.length) {
|
|
235
|
+
const index = nextIndex;
|
|
236
|
+
nextIndex += 1;
|
|
237
|
+
await visit(values[index], index);
|
|
238
|
+
}
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
241
|
+
function sampleConcurrency(value) {
|
|
242
|
+
const resolved = value ?? DEFAULT_MAX_CONCURRENT_SAMPLES;
|
|
243
|
+
if (!Number.isSafeInteger(resolved)
|
|
244
|
+
|| resolved < 1
|
|
245
|
+
|| resolved > MAX_CONCURRENT_SAMPLES) {
|
|
246
|
+
throw new Error(`Agent runtime observer maxConcurrentSamples must be between 1 and ${MAX_CONCURRENT_SAMPLES}.`);
|
|
191
247
|
}
|
|
248
|
+
return resolved;
|
|
249
|
+
}
|
|
250
|
+
function numericCompare(left, right) {
|
|
251
|
+
return left.localeCompare(right, undefined, { numeric: true });
|
|
192
252
|
}
|
|
193
253
|
function compareObservations(left, right) {
|
|
194
254
|
return left.receivedAt.localeCompare(right.receivedAt)
|