@nocobase/app-plugin-agents 1.0.0-beta.15 → 1.0.0-beta.17
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/CHANGELOG.md +62 -0
- package/README.md +12 -1
- package/dist/client/chat/api.d.ts +1 -1
- package/dist/client/chat/api.d.ts.map +1 -1
- package/dist/client/chat/api.js +2 -2
- package/dist/client/chat/api.js.map +1 -1
- package/dist/client/chat/keys.d.ts +1 -0
- package/dist/client/chat/keys.d.ts.map +1 -1
- package/dist/client/chat/keys.js +1 -0
- package/dist/client/chat/keys.js.map +1 -1
- package/dist/client/chat/provider.d.ts +10 -4
- package/dist/client/chat/provider.d.ts.map +1 -1
- package/dist/client/chat/provider.js +86 -15
- package/dist/client/chat/provider.js.map +1 -1
- package/dist/client/chat/use-chat.d.ts +3 -1
- package/dist/client/chat/use-chat.d.ts.map +1 -1
- package/dist/client/chat/use-chat.js +2 -2
- package/dist/client/chat/use-chat.js.map +1 -1
- package/dist/client/chat/use-conversation-model.d.ts +12 -0
- package/dist/client/chat/use-conversation-model.d.ts.map +1 -0
- package/dist/client/chat/use-conversation-model.js +64 -0
- package/dist/client/chat/use-conversation-model.js.map +1 -0
- package/dist/client/chat.d.ts +1 -0
- package/dist/client/chat.d.ts.map +1 -1
- package/dist/client/chat.js +1 -0
- package/dist/client/chat.js.map +1 -1
- package/dist/client/locales/de-DE.d.ts +5 -0
- package/dist/client/locales/de-DE.d.ts.map +1 -0
- package/dist/client/locales/de-DE.js +1219 -0
- package/dist/client/locales/de-DE.js.map +1 -0
- package/dist/client/locales/en-US.d.ts +8 -0
- package/dist/client/locales/en-US.d.ts.map +1 -1
- package/dist/client/locales/en-US.js +8 -0
- package/dist/client/locales/en-US.js.map +1 -1
- package/dist/client/locales/es-ES.d.ts +5 -0
- package/dist/client/locales/es-ES.d.ts.map +1 -0
- package/dist/client/locales/es-ES.js +1228 -0
- package/dist/client/locales/es-ES.js.map +1 -0
- package/dist/client/locales/fr-FR.d.ts +5 -0
- package/dist/client/locales/fr-FR.d.ts.map +1 -0
- package/dist/client/locales/fr-FR.js +1229 -0
- package/dist/client/locales/fr-FR.js.map +1 -0
- package/dist/client/locales/id-ID.d.ts +5 -0
- package/dist/client/locales/id-ID.d.ts.map +1 -0
- package/dist/client/locales/id-ID.js +1219 -0
- package/dist/client/locales/id-ID.js.map +1 -0
- package/dist/client/locales/index.d.ts.map +1 -1
- package/dist/client/locales/index.js +8 -0
- package/dist/client/locales/index.js.map +1 -1
- package/dist/client/locales/ja-JP.d.ts +5 -0
- package/dist/client/locales/ja-JP.d.ts.map +1 -0
- package/dist/client/locales/ja-JP.js +1219 -0
- package/dist/client/locales/ja-JP.js.map +1 -0
- package/dist/client/locales/pt-BR.d.ts +5 -0
- package/dist/client/locales/pt-BR.d.ts.map +1 -0
- package/dist/client/locales/pt-BR.js +1229 -0
- package/dist/client/locales/pt-BR.js.map +1 -0
- package/dist/client/locales/vi-VN.d.ts +5 -0
- package/dist/client/locales/vi-VN.d.ts.map +1 -0
- package/dist/client/locales/vi-VN.js +1219 -0
- package/dist/client/locales/vi-VN.js.map +1 -0
- package/dist/client/locales/zh-CN.d.ts.map +1 -1
- package/dist/client/locales/zh-CN.js +8 -0
- package/dist/client/locales/zh-CN.js.map +1 -1
- package/dist/client/locales/zh-TW.d.ts +5 -0
- package/dist/client/locales/zh-TW.d.ts.map +1 -0
- package/dist/client/locales/zh-TW.js +1219 -0
- package/dist/client/locales/zh-TW.js.map +1 -0
- package/dist/client/model-suggestion.d.ts +6 -0
- package/dist/client/model-suggestion.d.ts.map +1 -0
- package/dist/client/model-suggestion.js +25 -0
- package/dist/client/model-suggestion.js.map +1 -0
- package/dist/client/pages/agents/agent-dialog.d.ts.map +1 -1
- package/dist/client/pages/agents/agent-dialog.js +2 -9
- package/dist/client/pages/agents/agent-dialog.js.map +1 -1
- package/dist/client/pages/usage/date-range-picker.d.ts.map +1 -1
- package/dist/client/pages/usage/date-range-picker.js +14 -2
- package/dist/client/pages/usage/date-range-picker.js.map +1 -1
- package/dist/client/pages/usage/index.d.ts.map +1 -1
- package/dist/client/pages/usage/index.js +17 -5
- package/dist/client/pages/usage/index.js.map +1 -1
- package/dist/client/pages/usage/prices/model.d.ts +4 -4
- package/dist/client/pages/usage/prices/model.d.ts.map +1 -1
- package/dist/client/pages/usage/prices/model.js +2 -2
- package/dist/client/pages/usage/prices/model.js.map +1 -1
- package/dist/client/pages/usage/prices/price-cells.js +2 -2
- package/dist/client/pages/usage/prices/price-cells.js.map +1 -1
- package/dist/client/pages/usage/prices/prices-section.d.ts.map +1 -1
- package/dist/client/pages/usage/prices/prices-section.js +13 -13
- package/dist/client/pages/usage/prices/prices-section.js.map +1 -1
- package/dist/package.json +5 -2
- package/dist/server/cli/online-credential.d.ts +16 -0
- package/dist/server/cli/online-credential.d.ts.map +1 -0
- package/dist/server/cli/online-credential.js +51 -0
- package/dist/server/cli/online-credential.js.map +1 -0
- package/dist/server/cli/run-credential.d.ts +2 -0
- package/dist/server/cli/run-credential.d.ts.map +1 -1
- package/dist/server/cli/run-credential.js +12 -1
- package/dist/server/cli/run-credential.js.map +1 -1
- package/dist/server/cli/surface.js +2 -2
- package/dist/server/cli/surface.js.map +1 -1
- package/dist/server/composition.d.ts +2 -0
- package/dist/server/composition.d.ts.map +1 -1
- package/dist/server/composition.js +17 -3
- package/dist/server/composition.js.map +1 -1
- package/dist/server/core/brief/brief.js +1 -1
- package/dist/server/core/brief/brief.js.map +1 -1
- package/dist/server/core/conversations/chat-settings.d.ts +1 -0
- package/dist/server/core/conversations/chat-settings.d.ts.map +1 -1
- package/dist/server/core/conversations/chat-settings.js +71 -57
- package/dist/server/core/conversations/chat-settings.js.map +1 -1
- package/dist/server/core/conversations/conversation.service.d.ts.map +1 -1
- package/dist/server/core/conversations/conversation.service.js +3 -4
- package/dist/server/core/conversations/conversation.service.js.map +1 -1
- package/dist/server/core/reports/report.service.d.ts +1 -0
- package/dist/server/core/reports/report.service.d.ts.map +1 -1
- package/dist/server/core/reports/report.service.js +5 -2
- package/dist/server/core/reports/report.service.js.map +1 -1
- package/dist/server/core/runs/claim.d.ts +1 -1
- package/dist/server/core/runs/claim.d.ts.map +1 -1
- package/dist/server/core/runs/claim.js +337 -58
- package/dist/server/core/runs/claim.js.map +1 -1
- package/dist/server/core/runs/lifecycle.d.ts.map +1 -1
- package/dist/server/core/runs/lifecycle.js +4 -2
- package/dist/server/core/runs/lifecycle.js.map +1 -1
- package/dist/server/core/runs/runner-affinity.d.ts +6 -0
- package/dist/server/core/runs/runner-affinity.d.ts.map +1 -0
- package/dist/server/core/runs/runner-affinity.js +16 -0
- package/dist/server/core/runs/runner-affinity.js.map +1 -0
- package/dist/server/core/skills/skill.service.d.ts +2 -0
- package/dist/server/core/skills/skill.service.d.ts.map +1 -1
- package/dist/server/core/skills/skill.service.js +1 -0
- package/dist/server/core/skills/skill.service.js.map +1 -1
- package/dist/server/locales/de-DE.d.ts +5 -0
- package/dist/server/locales/de-DE.d.ts.map +1 -0
- package/dist/server/locales/de-DE.js +54 -0
- package/dist/server/locales/de-DE.js.map +1 -0
- package/dist/server/locales/es-ES.d.ts +5 -0
- package/dist/server/locales/es-ES.d.ts.map +1 -0
- package/dist/server/locales/es-ES.js +54 -0
- package/dist/server/locales/es-ES.js.map +1 -0
- package/dist/server/locales/fr-FR.d.ts +5 -0
- package/dist/server/locales/fr-FR.d.ts.map +1 -0
- package/dist/server/locales/fr-FR.js +54 -0
- package/dist/server/locales/fr-FR.js.map +1 -0
- package/dist/server/locales/id-ID.d.ts +5 -0
- package/dist/server/locales/id-ID.d.ts.map +1 -0
- package/dist/server/locales/id-ID.js +54 -0
- package/dist/server/locales/id-ID.js.map +1 -0
- package/dist/server/locales/index.d.ts.map +1 -1
- package/dist/server/locales/index.js +8 -0
- package/dist/server/locales/index.js.map +1 -1
- package/dist/server/locales/ja-JP.d.ts +5 -0
- package/dist/server/locales/ja-JP.d.ts.map +1 -0
- package/dist/server/locales/ja-JP.js +54 -0
- package/dist/server/locales/ja-JP.js.map +1 -0
- package/dist/server/locales/pt-BR.d.ts +5 -0
- package/dist/server/locales/pt-BR.d.ts.map +1 -0
- package/dist/server/locales/pt-BR.js +54 -0
- package/dist/server/locales/pt-BR.js.map +1 -0
- package/dist/server/locales/vi-VN.d.ts +5 -0
- package/dist/server/locales/vi-VN.d.ts.map +1 -0
- package/dist/server/locales/vi-VN.js +54 -0
- package/dist/server/locales/vi-VN.js.map +1 -0
- package/dist/server/locales/zh-TW.d.ts +5 -0
- package/dist/server/locales/zh-TW.d.ts.map +1 -0
- package/dist/server/locales/zh-TW.js +54 -0
- package/dist/server/locales/zh-TW.js.map +1 -0
- package/dist/server/online/agent/configuration.d.ts +2 -2
- package/dist/server/online/agent/configuration.d.ts.map +1 -1
- package/dist/server/online/agent/configuration.js +21 -7
- package/dist/server/online/agent/configuration.js.map +1 -1
- package/dist/server/online/agent/index.d.ts +2 -1
- package/dist/server/online/agent/index.d.ts.map +1 -1
- package/dist/server/online/agent/index.js.map +1 -1
- package/dist/server/online/agent/native/backend.d.ts +2 -0
- package/dist/server/online/agent/native/backend.d.ts.map +1 -1
- package/dist/server/online/agent/native/backend.js +14 -12
- package/dist/server/online/agent/native/backend.js.map +1 -1
- package/dist/server/online/agent/native/execution.d.ts +2 -0
- package/dist/server/online/agent/native/execution.d.ts.map +1 -1
- package/dist/server/online/agent/native/execution.js +51 -22
- package/dist/server/online/agent/native/execution.js.map +1 -1
- package/dist/server/online/agent/native/types.d.ts +6 -0
- package/dist/server/online/agent/native/types.d.ts.map +1 -1
- package/dist/server/online/agent/runner/backend.d.ts.map +1 -1
- package/dist/server/online/agent/runner/backend.js +4 -3
- package/dist/server/online/agent/runner/backend.js.map +1 -1
- package/dist/server/online/agent/service.d.ts +2 -0
- package/dist/server/online/agent/service.d.ts.map +1 -1
- package/dist/server/online/agent/service.js +3 -1
- package/dist/server/online/agent/service.js.map +1 -1
- package/dist/server/online/agent/types.d.ts +15 -1
- package/dist/server/online/agent/types.d.ts.map +1 -1
- package/dist/server/online/sandbox/cli-command.d.ts +6 -0
- package/dist/server/online/sandbox/cli-command.d.ts.map +1 -0
- package/dist/server/online/sandbox/cli-command.js +370 -0
- package/dist/server/online/sandbox/cli-command.js.map +1 -0
- package/dist/server/online/sandbox/execution.d.ts +25 -0
- package/dist/server/online/sandbox/execution.d.ts.map +1 -0
- package/dist/server/online/sandbox/execution.js +63 -0
- package/dist/server/online/sandbox/execution.js.map +1 -0
- package/dist/server/online/sandbox/filesystem.d.ts +8 -0
- package/dist/server/online/sandbox/filesystem.d.ts.map +1 -0
- package/dist/server/online/sandbox/filesystem.js +72 -0
- package/dist/server/online/sandbox/filesystem.js.map +1 -0
- package/dist/server/online/sandbox/limits.d.ts +4 -0
- package/dist/server/online/sandbox/limits.d.ts.map +1 -0
- package/dist/server/online/sandbox/limits.js +46 -0
- package/dist/server/online/sandbox/limits.js.map +1 -0
- package/dist/server/online/sandbox/provider.d.ts +9 -0
- package/dist/server/online/sandbox/provider.d.ts.map +1 -0
- package/dist/server/online/sandbox/provider.js +237 -0
- package/dist/server/online/sandbox/provider.js.map +1 -0
- package/dist/server/online/sandbox/skills.d.ts +14 -0
- package/dist/server/online/sandbox/skills.d.ts.map +1 -0
- package/dist/server/online/sandbox/skills.js +56 -0
- package/dist/server/online/sandbox/skills.js.map +1 -0
- package/dist/server/online/sandbox/tools.d.ts +3 -0
- package/dist/server/online/sandbox/tools.d.ts.map +1 -0
- package/dist/server/online/sandbox/tools.js +35 -0
- package/dist/server/online/sandbox/tools.js.map +1 -0
- package/dist/server/online/sandbox/types.d.ts +72 -0
- package/dist/server/online/sandbox/types.d.ts.map +1 -0
- package/dist/server/online/sandbox/types.js +2 -0
- package/dist/server/online/sandbox/types.js.map +1 -0
- package/dist/server/providers/agents.d.ts +5 -2
- package/dist/server/providers/agents.d.ts.map +1 -1
- package/dist/server/providers/agents.js +63 -2
- package/dist/server/providers/agents.js.map +1 -1
- package/dist/server/providers/authorization.d.ts +1 -1
- package/dist/server/providers/authorization.d.ts.map +1 -1
- package/dist/server/providers/authorization.js +3 -2
- package/dist/server/providers/authorization.js.map +1 -1
- package/dist/server/routes/chat.d.ts +1 -1
- package/dist/server/routes/chat.d.ts.map +1 -1
- package/dist/server/routes/chat.js +15 -6
- package/dist/server/routes/chat.js.map +1 -1
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +17 -2
- package/dist/server/routes/index.js.map +1 -1
- package/dist/server/routes/openapi.js +1 -1
- package/dist/server/routes/openapi.js.map +1 -1
- package/dist/server/routes/schemas.js +1 -1
- package/dist/server/routes/schemas.js.map +1 -1
- package/dist/server/runners/runner.service.d.ts +1 -1
- package/dist/server/runners/runner.service.d.ts.map +1 -1
- package/dist/server/runners/runner.service.js +5 -1
- package/dist/server/runners/runner.service.js.map +1 -1
- package/dist/server/runners/runner.store.d.ts.map +1 -1
- package/dist/server/runners/runner.store.js +3 -1
- package/dist/server/runners/runner.store.js.map +1 -1
- package/dist/server/vendor/THIRD_PARTY_LICENSES.txt +782 -0
- package/dist/server/vendor/just-bash.d.ts +137 -0
- package/dist/server/vendor/just-bash.js +2102 -0
- package/dist/shared/access.d.ts +1 -1
- package/dist/shared/access.d.ts.map +1 -1
- package/dist/shared/access.js +1 -1
- package/dist/shared/access.js.map +1 -1
- package/dist/shared/conversations.d.ts +2 -1
- package/dist/shared/conversations.d.ts.map +1 -1
- package/dist/shared/conversations.js.map +1 -1
- package/dist/shared/locales/access.en-US.js +3 -3
- package/dist/shared/locales/access.zh-CN.js +4 -4
- package/dist/shared/runners.d.ts +1 -1
- package/dist/shared/runners.d.ts.map +1 -1
- package/dist/shared/runners.js +2 -0
- package/dist/shared/runners.js.map +1 -1
- package/docs/native-sandbox.md +55 -0
- package/docs/ui-message-clients.md +10 -0
- package/package.json +17 -14
- package/skills/nocobase-app-plugin-agents/SKILL.md +1 -0
- package/skills/nocobase-app-plugin-agents/references/online-agent.md +2 -2
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { upgradeReservation } from './runner-affinity.js';
|
|
2
|
+
import { ACTIVE_JOB, jobsRepo } from '../../jobs/job.store.js';
|
|
1
3
|
/**
|
|
2
4
|
* Claiming: handing queued runs to a runner that fits them, within each agent's concurrency limit, one run per
|
|
3
5
|
* (agent, subject, thread) at a time, through the runners' one long-poll (`routes/runners/runner.ts`), which hands
|
|
@@ -6,7 +8,7 @@
|
|
|
6
8
|
* team or belongs to the person who woke the agent, its features cover what the run requires (worked out from the
|
|
7
9
|
* payload: checkouts, directories, skills, secrets), its owner's local policy (`Runner.policy`) lets it take the
|
|
8
10
|
* agent's runs on the run's subject and repositories, and it has a free slot. A working directory that is a directory
|
|
9
|
-
* on one runner pins the run to that runner
|
|
11
|
+
* on one runner pins the run to that runner; multiple runs may share that directory. A run's variables go to the runner that
|
|
10
12
|
* takes it, personal ones included, unless one of them is for team runners only (`teamRunnersOnly`): then only a team
|
|
11
13
|
* runner takes the run, and the variables that asked for one are noted on it (`teamOnlyVariables`), so its wait says
|
|
12
14
|
* which.
|
|
@@ -75,7 +77,7 @@ class AssemblyError extends Error {
|
|
|
75
77
|
this.name = 'AssemblyError';
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
|
-
/** The run does not fit this runner after all (its payload needs another runner
|
|
80
|
+
/** The run does not fit this runner after all (its payload needs another runner or unavailable capabilities). */
|
|
79
81
|
class NotForThisRunner extends Error {
|
|
80
82
|
constructor() {
|
|
81
83
|
super('The run does not fit this runner.');
|
|
@@ -224,20 +226,6 @@ function directoryKeyOf(runnerId, dirs) {
|
|
|
224
226
|
.map((dir) => `${runnerId}:${dir.path}`);
|
|
225
227
|
return paths.length > 0 ? paths.join(DIRECTORY_KEY_SEPARATOR) : null;
|
|
226
228
|
}
|
|
227
|
-
/** Whether another run held by the runner works in any of `key`'s directories. */
|
|
228
|
-
async function directoryBusy(conn, runnerId, runId, key) {
|
|
229
|
-
const wanted = new Set(key.split(DIRECTORY_KEY_SEPARATOR));
|
|
230
|
-
const held = await runsRepo(conn).findMany({
|
|
231
|
-
filter: (f) => f.and([
|
|
232
|
-
f.string('runnerId').eq(runnerId),
|
|
233
|
-
f.string('id').ne(runId),
|
|
234
|
-
f.or(ACTIVE.map((status) => f.string('status').eq(status))),
|
|
235
|
-
]),
|
|
236
|
-
});
|
|
237
|
-
return held.some((run) => (run.directoryKey ?? '')
|
|
238
|
-
.split(DIRECTORY_KEY_SEPARATOR)
|
|
239
|
-
.some((part) => part !== '' && wanted.has(part)));
|
|
240
|
-
}
|
|
241
229
|
/** Resolve omitted subject input in the claim transaction, keeping it in the audit history. */
|
|
242
230
|
async function settleClaimInputs(conn, pending, effective, now) {
|
|
243
231
|
const ids = new Set(effective.map((input) => input.id));
|
|
@@ -270,10 +258,16 @@ export function createClaimService(deps) {
|
|
|
270
258
|
(assembly.workspaceKey && !supportsWorkspaceKey(runner)))
|
|
271
259
|
return 'incompatible';
|
|
272
260
|
const { skills, variables } = await resources(agent, assembly);
|
|
261
|
+
return compatibleAssembly(run, agent, runner, assembly, skills.length, variables);
|
|
262
|
+
}
|
|
263
|
+
function compatibleAssembly(run, agent, runner, assembly, skillCount, variables) {
|
|
264
|
+
if (!isProtocolSupported(runner.protocolVersion) ||
|
|
265
|
+
(assembly.workspaceKey && !supportsWorkspaceKey(runner)))
|
|
266
|
+
return 'incompatible';
|
|
273
267
|
const names = variables.names;
|
|
274
268
|
if (!covers(runner.features, [
|
|
275
269
|
...run.requires,
|
|
276
|
-
...payloadRequires(payloadDirs(assembly.dirs),
|
|
270
|
+
...payloadRequires(payloadDirs(assembly.dirs), skillCount, names.length, Boolean(assembly.workspaceKey)),
|
|
277
271
|
]))
|
|
278
272
|
return 'incompatible';
|
|
279
273
|
if (!fitsActor(runner, agent, run.actorUserId, run.requires, clock.now().getTime()) ||
|
|
@@ -307,7 +301,8 @@ export function createClaimService(deps) {
|
|
|
307
301
|
const entries = [];
|
|
308
302
|
for (const entry of agent.modelEntries) {
|
|
309
303
|
const single = { ...agent, modelEntries: [entry] };
|
|
310
|
-
const
|
|
304
|
+
const reserved = upgradeReservation(run.executions ?? [], registered, now.getTime());
|
|
305
|
+
const candidates = registered.filter((runner) => runner.status !== 'revoked' && (!reserved || runner.id === reserved));
|
|
311
306
|
const connected = candidates.filter((runner) => (runner.status === 'online' ||
|
|
312
307
|
runner.status === 'upgrade_required') &&
|
|
313
308
|
runner.lastSeenAt !== null &&
|
|
@@ -321,7 +316,6 @@ export function createClaimService(deps) {
|
|
|
321
316
|
let unknown = false;
|
|
322
317
|
let incompatible = false;
|
|
323
318
|
let ready = false;
|
|
324
|
-
let directoryBlocked = false;
|
|
325
319
|
for (const runner of online) {
|
|
326
320
|
if (!isProtocolSupported(runner.protocolVersion) ||
|
|
327
321
|
runner.status === 'upgrade_required') {
|
|
@@ -377,16 +371,11 @@ export function createClaimService(deps) {
|
|
|
377
371
|
const held = await deps.slotsHeld(conn, runner.id);
|
|
378
372
|
const byTool = held.byTool ?? {};
|
|
379
373
|
const used = held.jobs + held.runs;
|
|
380
|
-
const directory = directoryKeyOf(runner.id, payloadDirs(assembly.dirs));
|
|
381
|
-
const blockedDirectory = directory !== null &&
|
|
382
|
-
(await directoryBusy(conn, runner.id, run.id, directory));
|
|
383
|
-
directoryBlocked ||= blockedDirectory;
|
|
384
374
|
const hasRoom = !runner.draining &&
|
|
385
375
|
(runner.globalFree ?? 1) > 0 &&
|
|
386
376
|
used < runner.slots &&
|
|
387
377
|
(byTool[entry.tool] ?? 0) < toolLimit(runner, entry.tool) &&
|
|
388
|
-
(runner.toolLoad?.[entry.tool]?.free ?? 1) > 0
|
|
389
|
-
!blockedDirectory;
|
|
378
|
+
(runner.toolLoad?.[entry.tool]?.free ?? 1) > 0;
|
|
390
379
|
ready ||= hasRoom && runner.globalFree !== undefined;
|
|
391
380
|
if (hasRoom && runner.globalFree === undefined)
|
|
392
381
|
unknown = true;
|
|
@@ -398,10 +387,10 @@ export function createClaimService(deps) {
|
|
|
398
387
|
? 'unknown'
|
|
399
388
|
: 'ready'
|
|
400
389
|
: runner.draining
|
|
401
|
-
? runner.draining
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
390
|
+
? runner.draining === 'diskLow'
|
|
391
|
+
? 'busy'
|
|
392
|
+
: runner.draining
|
|
393
|
+
: 'busy',
|
|
405
394
|
lastSeenAt: runner.lastSeenAt,
|
|
406
395
|
used,
|
|
407
396
|
limit: runner.slots,
|
|
@@ -426,8 +415,6 @@ export function createClaimService(deps) {
|
|
|
426
415
|
: connected.length
|
|
427
416
|
? 'toolNotInstalled'
|
|
428
417
|
: 'offline';
|
|
429
|
-
if (directoryBlocked && !ready)
|
|
430
|
-
reason = 'restricted';
|
|
431
418
|
if (!ready &&
|
|
432
419
|
compatible.length > 0 &&
|
|
433
420
|
loads.every((runner) => runner.reason === 'updatePending' || runner.reason === 'pruning'))
|
|
@@ -465,6 +452,126 @@ export function createClaimService(deps) {
|
|
|
465
452
|
entries,
|
|
466
453
|
};
|
|
467
454
|
}
|
|
455
|
+
/** Metadata only: batch new scopes, reuse common scopes across machines and visited candidates. */
|
|
456
|
+
function admissionResources(conn, agents = []) {
|
|
457
|
+
const variableScopes = new Set();
|
|
458
|
+
const skillScopes = new Set();
|
|
459
|
+
const variables = [];
|
|
460
|
+
const skills = [];
|
|
461
|
+
function missing(targets, seen) {
|
|
462
|
+
const fresh = new Map();
|
|
463
|
+
for (const target of targets) {
|
|
464
|
+
const key = JSON.stringify([target.scope, target.scopeId]);
|
|
465
|
+
if (!seen.has(key))
|
|
466
|
+
fresh.set(key, target);
|
|
467
|
+
}
|
|
468
|
+
for (const key of fresh.keys())
|
|
469
|
+
seen.add(key);
|
|
470
|
+
return [...fresh.values()];
|
|
471
|
+
}
|
|
472
|
+
return {
|
|
473
|
+
async load(checks) {
|
|
474
|
+
const vars = missing([
|
|
475
|
+
...checks.flatMap(({ agent, assembly }) => variableTargets(agent, assembly)),
|
|
476
|
+
...agents.map((agent) => ({ scope: 'agent', scopeId: agent.id })),
|
|
477
|
+
], variableScopes);
|
|
478
|
+
const attached = missing(checks.flatMap(({ agent, assembly }) => skillTargets(agent, assembly)), skillScopes);
|
|
479
|
+
if (vars.length)
|
|
480
|
+
variables.push(...(await deps.variables.admissionMetadata(conn, vars)));
|
|
481
|
+
if (attached.length)
|
|
482
|
+
skills.push(...(await deps.skills.admissionMetadata(conn, attached)));
|
|
483
|
+
},
|
|
484
|
+
get(agent, assembly) {
|
|
485
|
+
const merged = new Map();
|
|
486
|
+
for (const target of variableTargets(agent, assembly))
|
|
487
|
+
for (const row of variables)
|
|
488
|
+
if (row.scope === target.scope && row.scopeId === target.scopeId)
|
|
489
|
+
merged.set(row.name, row);
|
|
490
|
+
const targets = skillTargets(agent, assembly);
|
|
491
|
+
return {
|
|
492
|
+
skills: skills.filter((row) => targets.some((target) => target.scope === row.scope && target.scopeId === row.scopeId)),
|
|
493
|
+
variables: {
|
|
494
|
+
names: [...merged.values()]
|
|
495
|
+
.filter((row) => row.kind === 'value')
|
|
496
|
+
.map((row) => row.name),
|
|
497
|
+
teamOnly: [...merged.values()].filter((row) => row.kind === 'value' && row.teamRunnersOnly),
|
|
498
|
+
passthrough: [...merged.values()]
|
|
499
|
+
.filter((row) => row.kind === 'runner')
|
|
500
|
+
.map((row) => row.name),
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
/** Eligibility needs existence/compatibility, not occupancy, other models or a full wait projection. */
|
|
507
|
+
async function preferredModelAvailable(conn, run, agent, inputs, metadata, inventory) {
|
|
508
|
+
const now = clock.now().getTime();
|
|
509
|
+
const registered = await inventory();
|
|
510
|
+
const reserved = upgradeReservation(run.executions ?? [], registered, now);
|
|
511
|
+
const entry = agent.modelEntries[0];
|
|
512
|
+
const single = { ...agent, modelEntries: [entry] };
|
|
513
|
+
const binding = deps.subjects.get(run.subject.kind);
|
|
514
|
+
if (!binding)
|
|
515
|
+
throw new Error(`No context provider for ${run.subject.kind}.`);
|
|
516
|
+
const checks = [];
|
|
517
|
+
for (const runner of registered) {
|
|
518
|
+
if ((reserved && runner.id !== reserved) ||
|
|
519
|
+
runner.status !== 'online' ||
|
|
520
|
+
!runner.lastSeenAt ||
|
|
521
|
+
Date.parse(runner.lastSeenAt) + TIMINGS.offlineAfterMs <= now ||
|
|
522
|
+
!isProtocolSupported(runner.protocolVersion) ||
|
|
523
|
+
!runsEntry(runner, entry, now) ||
|
|
524
|
+
(runner.trust !== 'team' && runner.ownerUserId !== run.actorUserId))
|
|
525
|
+
continue;
|
|
526
|
+
const assembly = await binding.context
|
|
527
|
+
.assemble(conn, {
|
|
528
|
+
run,
|
|
529
|
+
agent,
|
|
530
|
+
runner,
|
|
531
|
+
inputs,
|
|
532
|
+
cli: deps.cli.name,
|
|
533
|
+
appName: deps.app.name,
|
|
534
|
+
})
|
|
535
|
+
.catch(() => null);
|
|
536
|
+
// The diagnostic path treats an unreadable context as unknown and waits for the preferred model.
|
|
537
|
+
if (!assembly)
|
|
538
|
+
return true;
|
|
539
|
+
checks.push({ agent: single, runner, assembly });
|
|
540
|
+
}
|
|
541
|
+
await metadata.load(checks);
|
|
542
|
+
for (const { runner, assembly } of checks) {
|
|
543
|
+
const { skills, variables } = metadata.get(single, assembly);
|
|
544
|
+
if (skills.some((skill) => skill.hash === null))
|
|
545
|
+
throw notFound('Skill version');
|
|
546
|
+
if (compatibleAssembly(run, single, runner, assembly, skills.length, variables) !== 'ready')
|
|
547
|
+
continue;
|
|
548
|
+
if (variables.passthrough.length && runner.variables == null)
|
|
549
|
+
return true;
|
|
550
|
+
if (variables.passthrough.some((name) => !runner.variables?.includes(name)))
|
|
551
|
+
continue;
|
|
552
|
+
return true;
|
|
553
|
+
}
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
/** The same ordered-model decision is used for dispatch and for immediate runner affinity. */
|
|
557
|
+
async function modelAdmission(conn, run, agent, entry, inputs, metadata = admissionResources(conn), inventory = () => deps.runners.all(conn)) {
|
|
558
|
+
if (agent.modelEntries.indexOf(entry) <= 0 || !deps.runners)
|
|
559
|
+
return undefined;
|
|
560
|
+
const policy = await deps.selectionPolicies?.forRun(conn, run);
|
|
561
|
+
if (!policy)
|
|
562
|
+
return undefined;
|
|
563
|
+
const elapsed = Math.max(0, (clock.now().getTime() - queueStart(run)) / 1000);
|
|
564
|
+
const preferredAvailable = await preferredModelAvailable(conn, run, agent, inputs, metadata, inventory);
|
|
565
|
+
if (preferredAvailable && elapsed < policy.fallbackAfterSeconds)
|
|
566
|
+
return false;
|
|
567
|
+
return {
|
|
568
|
+
reason: preferredAvailable ? 'preferredQueued' : 'preferredUnavailable',
|
|
569
|
+
preferredTool: agent.modelEntries[0].tool,
|
|
570
|
+
preferredModel: agent.modelEntries[0].model,
|
|
571
|
+
queueEnteredAt: new Date(queueStart(run)).toISOString(),
|
|
572
|
+
waitedSeconds: elapsed,
|
|
573
|
+
};
|
|
574
|
+
}
|
|
468
575
|
async function assemble({ conn }, run, agent, runner, inputs, leaseExpiresAt, prepared, entry, resources = claimResources(conn, run.id)) {
|
|
469
576
|
const binding = deps.subjects.get(run.subjectKind);
|
|
470
577
|
if (!binding)
|
|
@@ -494,9 +601,6 @@ export function createClaimService(deps) {
|
|
|
494
601
|
throw new NotForThisRunner();
|
|
495
602
|
const dirs = payloadDirs(assembly.dirs);
|
|
496
603
|
const directoryKey = directoryKeyOf(runner.id, dirs);
|
|
497
|
-
if (directoryKey !== null &&
|
|
498
|
-
(await directoryBusy(conn, runner.id, run.id, directoryKey)))
|
|
499
|
-
throw new NotForThisRunner();
|
|
500
604
|
const { skills, variables } = await resources(agent, assembly);
|
|
501
605
|
const names = variables.names;
|
|
502
606
|
const requires = cleanList([
|
|
@@ -982,27 +1086,10 @@ export function createClaimService(deps) {
|
|
|
982
1086
|
// Admission must precede preflight assembly: a missing or obsolete
|
|
983
1087
|
// subject is cancelled normally, rather than counted as setup failure.
|
|
984
1088
|
const resources = claimResources(conn, run.id);
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
selection?.entries[0]?.blockers?.includes('unknown') === true;
|
|
990
|
-
const chosenIndex = agent.modelEntries.indexOf(entry);
|
|
991
|
-
if (selection && chosenIndex > 0) {
|
|
992
|
-
// Throw to roll back the provisional dispatch; the original run keeps
|
|
993
|
-
// waiting for its first choice without consuming an attempt or token.
|
|
994
|
-
if (preferredAvailable && elapsed < selection.fallbackAfterSeconds)
|
|
995
|
-
throw new NotForThisRunner();
|
|
996
|
-
selectionSnapshot = {
|
|
997
|
-
reason: preferredAvailable
|
|
998
|
-
? 'preferredQueued'
|
|
999
|
-
: 'preferredUnavailable',
|
|
1000
|
-
preferredTool: agent.modelEntries[0].tool,
|
|
1001
|
-
preferredModel: agent.modelEntries[0].model,
|
|
1002
|
-
queueEnteredAt: selection.queueEnteredAt,
|
|
1003
|
-
waitedSeconds: elapsed,
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1089
|
+
const admission = await modelAdmission(conn, toRun(current), agent, entry, pending.map(toInput));
|
|
1090
|
+
if (admission === false)
|
|
1091
|
+
throw new NotForThisRunner();
|
|
1092
|
+
selectionSnapshot = admission;
|
|
1006
1093
|
assembled = await assemble(unit, run, agent, runner, pending.map(toInput), leaseExpiresAt, prepared, entry, resources);
|
|
1007
1094
|
}
|
|
1008
1095
|
catch (error) {
|
|
@@ -1118,9 +1205,9 @@ export function createClaimService(deps) {
|
|
|
1118
1205
|
});
|
|
1119
1206
|
}
|
|
1120
1207
|
/** Queued runs that may be claimed now, in claim order. */
|
|
1121
|
-
async function candidates() {
|
|
1208
|
+
async function candidates(runner) {
|
|
1122
1209
|
const now = clock.now();
|
|
1123
|
-
|
|
1210
|
+
const ordinary = await runsRepo(tx.read()).findMany({
|
|
1124
1211
|
filter: (f) => f.and([
|
|
1125
1212
|
f.string('status').eq('queued'),
|
|
1126
1213
|
f.or([
|
|
@@ -1135,15 +1222,198 @@ export function createClaimService(deps) {
|
|
|
1135
1222
|
],
|
|
1136
1223
|
limit: CANDIDATES,
|
|
1137
1224
|
});
|
|
1225
|
+
if (ordinary.length === 0)
|
|
1226
|
+
return [];
|
|
1227
|
+
// A separate scan prevents ordinary candidate pages from hiding upgrade resumptions.
|
|
1228
|
+
const recovery = [];
|
|
1229
|
+
for (let offset = 0;; offset += CANDIDATES) {
|
|
1230
|
+
const page = await runsRepo(tx.read()).findMany({
|
|
1231
|
+
filter: (f) => f.and([
|
|
1232
|
+
f.string('status').eq('queued'),
|
|
1233
|
+
f.string('failureReason').eq('runnerUpgrade'),
|
|
1234
|
+
f
|
|
1235
|
+
.date('queueEnteredAt')
|
|
1236
|
+
.after(new Date(now.getTime() - 5 * 60_000)),
|
|
1237
|
+
f.or([
|
|
1238
|
+
f.date('availableAt').empty(),
|
|
1239
|
+
f.date('availableAt').notAfter(now),
|
|
1240
|
+
]),
|
|
1241
|
+
]),
|
|
1242
|
+
sort: (sort) => [
|
|
1243
|
+
sort.field('queueEnteredAt').asc(),
|
|
1244
|
+
sort.field('id').asc(),
|
|
1245
|
+
],
|
|
1246
|
+
limit: CANDIDATES,
|
|
1247
|
+
offset,
|
|
1248
|
+
});
|
|
1249
|
+
recovery.push(...page.filter((run) => toExecutions(run.executionHistory).at(-1)?.runnerId === runner.id));
|
|
1250
|
+
if (page.length < CANDIDATES || recovery.length >= CANDIDATES)
|
|
1251
|
+
break;
|
|
1252
|
+
}
|
|
1253
|
+
return [
|
|
1254
|
+
...new Map([...recovery, ...ordinary].map((run) => [run.id, run])).values(),
|
|
1255
|
+
];
|
|
1256
|
+
}
|
|
1257
|
+
/** One batch for locations, one for machines, and occupancy batches only when a preference exists. */
|
|
1258
|
+
async function affinityBatch(queued, runner) {
|
|
1259
|
+
const conn = tx.read();
|
|
1260
|
+
if (queued.length === 0)
|
|
1261
|
+
return {
|
|
1262
|
+
machines: [runner],
|
|
1263
|
+
preferred: new Map(),
|
|
1264
|
+
held: [],
|
|
1265
|
+
jobs: [],
|
|
1266
|
+
};
|
|
1267
|
+
const locations = await sessionsRepo(conn).groupBy({
|
|
1268
|
+
by: ['runnerId', 'subjectKind', 'subjectId'],
|
|
1269
|
+
aggregate: (a) => ({ updatedAt: a.max('updatedAt') }),
|
|
1270
|
+
filter: (f) => f.or(queued.map((run) => f.and([
|
|
1271
|
+
f.string('subjectKind').eq(run.subjectKind),
|
|
1272
|
+
f.string('subjectId').eq(run.subjectId),
|
|
1273
|
+
]))),
|
|
1274
|
+
sort: (sort) => sort.field('updatedAt').desc(),
|
|
1275
|
+
});
|
|
1276
|
+
const activeLocations = await runsRepo(conn).groupBy({
|
|
1277
|
+
by: ['runnerId', 'subjectKind', 'subjectId'],
|
|
1278
|
+
aggregate: (a) => ({ updatedAt: a.max('updatedAt') }),
|
|
1279
|
+
filter: (f) => f.and([
|
|
1280
|
+
f.string('runnerId').notEmpty(),
|
|
1281
|
+
f.or(ACTIVE.map((status) => f.string('status').eq(status))),
|
|
1282
|
+
f.or(queued.map((run) => f.and([
|
|
1283
|
+
f.string('subjectKind').eq(run.subjectKind),
|
|
1284
|
+
f.string('subjectId').eq(run.subjectId),
|
|
1285
|
+
]))),
|
|
1286
|
+
]),
|
|
1287
|
+
});
|
|
1288
|
+
const machineIds = [
|
|
1289
|
+
...new Set([
|
|
1290
|
+
runner.id,
|
|
1291
|
+
...queued.map((run) => toExecutions(run.executionHistory).at(-1)?.runnerId),
|
|
1292
|
+
...locations.map((item) => item.runnerId),
|
|
1293
|
+
...activeLocations.map((item) => item.runnerId),
|
|
1294
|
+
].filter((id) => Boolean(id))),
|
|
1295
|
+
];
|
|
1296
|
+
const machines = deps.runners
|
|
1297
|
+
? await deps.runners.all(conn, machineIds)
|
|
1298
|
+
: [runner];
|
|
1299
|
+
const preferred = new Map();
|
|
1300
|
+
for (const run of queued) {
|
|
1301
|
+
const last = toExecutions(run.executionHistory).at(-1)?.runnerId;
|
|
1302
|
+
const current = activeLocations
|
|
1303
|
+
.filter((item) => item.subjectKind === run.subjectKind &&
|
|
1304
|
+
item.subjectId === run.subjectId)
|
|
1305
|
+
.sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)))[0]?.runnerId;
|
|
1306
|
+
const location = locations.find((item) => item.subjectKind === run.subjectKind &&
|
|
1307
|
+
item.subjectId === run.subjectId)?.runnerId;
|
|
1308
|
+
const machine = machines.find((item) => item.id === (last ?? current ?? location));
|
|
1309
|
+
if (machine)
|
|
1310
|
+
preferred.set(run.id, machine);
|
|
1311
|
+
}
|
|
1312
|
+
const ids = [...new Set([...preferred.values()].map((item) => item.id))];
|
|
1313
|
+
const held = ids.length === 0
|
|
1314
|
+
? []
|
|
1315
|
+
: await runsRepo(conn).groupBy({
|
|
1316
|
+
by: ['runnerId', 'tool'],
|
|
1317
|
+
aggregate: (a) => ({ count: a.count() }),
|
|
1318
|
+
filter: (f) => f.and([
|
|
1319
|
+
f.or(ids.map((id) => f.string('runnerId').eq(id))),
|
|
1320
|
+
f.or(ACTIVE.map((status) => f.string('status').eq(status))),
|
|
1321
|
+
]),
|
|
1322
|
+
});
|
|
1323
|
+
const jobs = ids.length === 0
|
|
1324
|
+
? []
|
|
1325
|
+
: await jobsRepo(conn).groupBy({
|
|
1326
|
+
by: ['runnerId'],
|
|
1327
|
+
aggregate: (a) => ({ count: a.count() }),
|
|
1328
|
+
filter: (f) => f.and([
|
|
1329
|
+
f.or(ids.map((id) => f.string('runnerId').eq(id))),
|
|
1330
|
+
f.or(ACTIVE_JOB.map((status) => f.string('status').eq(status))),
|
|
1331
|
+
]),
|
|
1332
|
+
});
|
|
1333
|
+
return { machines, preferred, held, jobs };
|
|
1334
|
+
}
|
|
1335
|
+
function hasImmediateRoom(run, agent, preferred, batch) {
|
|
1336
|
+
const now = clock.now().getTime();
|
|
1337
|
+
if (preferred.status !== 'online' ||
|
|
1338
|
+
!preferred.lastSeenAt ||
|
|
1339
|
+
Date.parse(preferred.lastSeenAt) + TIMINGS.offlineAfterMs <= now ||
|
|
1340
|
+
preferred.draining ||
|
|
1341
|
+
(preferred.globalFree ?? 1) <= 0 ||
|
|
1342
|
+
!fits(preferred, agent, run, now))
|
|
1343
|
+
return false;
|
|
1344
|
+
const held = batch.held.filter((item) => item.runnerId === preferred.id);
|
|
1345
|
+
if (held.reduce((count, item) => count + Number(item.count), 0) +
|
|
1346
|
+
batch.jobs
|
|
1347
|
+
.filter((item) => item.runnerId === preferred.id)
|
|
1348
|
+
.reduce((count, item) => count + Number(item.count), 0) >=
|
|
1349
|
+
preferred.slots)
|
|
1350
|
+
return false;
|
|
1351
|
+
if (!pickOpenEntry(preferred, agent, (tool) => held
|
|
1352
|
+
.filter((item) => item.tool === tool)
|
|
1353
|
+
.reduce((count, item) => count + Number(item.count), 0) <
|
|
1354
|
+
toolLimit(preferred, tool) &&
|
|
1355
|
+
(preferred.toolLoad?.[tool]?.free ?? 1) > 0, now))
|
|
1356
|
+
return false;
|
|
1357
|
+
return true;
|
|
1358
|
+
}
|
|
1359
|
+
/** Evaluate only the candidate reached in queue order, reusing this poll's batched capacity and metadata. */
|
|
1360
|
+
async function readyPreference(run, agent, batch, claimant, metadata, inventory) {
|
|
1361
|
+
const conn = tx.read();
|
|
1362
|
+
const runner = batch.preferred.get(run.id);
|
|
1363
|
+
if (!runner ||
|
|
1364
|
+
runner.id === claimant.id ||
|
|
1365
|
+
!fits(claimant, agent, run, clock.now().getTime()) ||
|
|
1366
|
+
upgradeReservation(toExecutions(run.executionHistory), batch.machines, clock.now().getTime()) ||
|
|
1367
|
+
!hasImmediateRoom(run, agent, runner, batch))
|
|
1368
|
+
return false;
|
|
1369
|
+
const binding = deps.subjects.get(run.subjectKind);
|
|
1370
|
+
if (!binding || !(await commentConsentFor(conn, runner, run)).allowed)
|
|
1371
|
+
return false;
|
|
1372
|
+
const assembly = await binding.context
|
|
1373
|
+
.assemble(conn, {
|
|
1374
|
+
run: toRun(run),
|
|
1375
|
+
agent,
|
|
1376
|
+
runner,
|
|
1377
|
+
inputs: [],
|
|
1378
|
+
cli: deps.cli.name,
|
|
1379
|
+
appName: deps.app.name,
|
|
1380
|
+
})
|
|
1381
|
+
.catch(() => null);
|
|
1382
|
+
if (!assembly)
|
|
1383
|
+
return false;
|
|
1384
|
+
await metadata.load([{ agent, assembly }]);
|
|
1385
|
+
const { skills, variables } = metadata.get(agent, assembly);
|
|
1386
|
+
if (!variables.passthrough.every((name) => runner.variables?.includes(name)) ||
|
|
1387
|
+
skills.some((row) => row.hash === null) ||
|
|
1388
|
+
compatibleAssembly(toRun(run), agent, runner, assembly, skills.length, variables) !== 'ready')
|
|
1389
|
+
return false;
|
|
1390
|
+
const entry = pickOpenEntry(runner, agent, (tool) => batch.held
|
|
1391
|
+
.filter((item) => item.runnerId === runner.id && item.tool === tool)
|
|
1392
|
+
.reduce((count, item) => count + Number(item.count), 0) <
|
|
1393
|
+
toolLimit(runner, tool) && (runner.toolLoad?.[tool]?.free ?? 1) > 0, clock.now().getTime());
|
|
1394
|
+
if (!entry)
|
|
1395
|
+
return false;
|
|
1396
|
+
if (agent.modelEntries.indexOf(entry) > 0) {
|
|
1397
|
+
const inputs = (await pendingInputs(conn, run.id)).map(toInput);
|
|
1398
|
+
if ((await modelAdmission(conn, toRun(run), agent, entry, inputs, metadata, inventory)) === false)
|
|
1399
|
+
return false;
|
|
1400
|
+
}
|
|
1401
|
+
return true;
|
|
1138
1402
|
}
|
|
1139
1403
|
async function claimOne(runner, seen, reported) {
|
|
1140
1404
|
if (runner.draining)
|
|
1141
1405
|
return null;
|
|
1142
1406
|
const conn = tx.read();
|
|
1143
|
-
const queued = await candidates();
|
|
1407
|
+
const queued = await candidates(runner);
|
|
1408
|
+
const affinity = await affinityBatch(queued, runner);
|
|
1144
1409
|
const agents = await findAgents(conn, queued
|
|
1145
1410
|
.filter((candidate) => !seen.has(candidate.id))
|
|
1146
1411
|
.map((candidate) => candidate.agentId));
|
|
1412
|
+
const metadata = admissionResources(conn, [...agents.values()]);
|
|
1413
|
+
let inventoryRead;
|
|
1414
|
+
const inventory = () => (inventoryRead ??= deps.runners
|
|
1415
|
+
? deps.runners.all(conn)
|
|
1416
|
+
: Promise.resolve([runner]));
|
|
1147
1417
|
const full = new Set();
|
|
1148
1418
|
for (const candidate of queued) {
|
|
1149
1419
|
if (seen.has(candidate.id) || full.has(candidate.agentId))
|
|
@@ -1154,6 +1424,15 @@ export function createClaimService(deps) {
|
|
|
1154
1424
|
(!deps.subjects.get(candidate.subjectKind)?.actsAs &&
|
|
1155
1425
|
!fits(runner, agent, candidate, clock.now().getTime())))
|
|
1156
1426
|
continue;
|
|
1427
|
+
const reserved = upgradeReservation(toExecutions(candidate.executionHistory), affinity.machines, clock.now().getTime());
|
|
1428
|
+
if (reserved && reserved !== runner.id)
|
|
1429
|
+
continue;
|
|
1430
|
+
const preferred = affinity.preferred.get(candidate.id);
|
|
1431
|
+
if (!reserved &&
|
|
1432
|
+
preferred &&
|
|
1433
|
+
preferred.id !== runner.id &&
|
|
1434
|
+
(await readyPreference(candidate, agent, affinity, runner, metadata, inventory)))
|
|
1435
|
+
continue;
|
|
1157
1436
|
// None of its tools has room on the runner's side: the runs behind it may still fit.
|
|
1158
1437
|
if (!pickOpenEntry(runner, agent, (tool) => reported.has(tool), clock.now().getTime()))
|
|
1159
1438
|
continue;
|