@nocobase/app-plugin-agents 2.0.0-beta.5 → 2.0.0-beta.7
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 +59 -0
- package/README.md +8 -2
- package/dist/client/chat/api.d.ts +6 -1
- package/dist/client/chat/api.d.ts.map +1 -1
- package/dist/client/chat/api.js +18 -0
- package/dist/client/chat/api.js.map +1 -1
- package/dist/client/chat/controller.d.ts.map +1 -1
- package/dist/client/chat/controller.js +5 -2
- package/dist/client/chat/controller.js.map +1 -1
- package/dist/client/chat/notices.d.ts +12 -0
- package/dist/client/chat/notices.d.ts.map +1 -0
- package/dist/client/chat/notices.js +15 -0
- package/dist/client/chat/notices.js.map +1 -0
- package/dist/client/chat/submissions.d.ts +16 -0
- package/dist/client/chat/submissions.d.ts.map +1 -1
- package/dist/client/chat/submissions.js +22 -0
- package/dist/client/chat/submissions.js.map +1 -1
- 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/components/route-child-page.d.ts +1 -1
- package/dist/client/components/route-child-page.d.ts.map +1 -1
- package/dist/client/components/route-child-page.js +18 -2
- package/dist/client/components/route-child-page.js.map +1 -1
- package/dist/client/components/route-overlay.d.ts +4 -1
- package/dist/client/components/route-overlay.d.ts.map +1 -1
- package/dist/client/components/route-overlay.js +32 -3
- package/dist/client/components/route-overlay.js.map +1 -1
- package/dist/client/components/ui/alert-dialog.d.ts +3 -1
- package/dist/client/components/ui/alert-dialog.d.ts.map +1 -1
- package/dist/client/components/ui/alert-dialog.js +2 -2
- package/dist/client/components/ui/alert-dialog.js.map +1 -1
- package/dist/client/components/unsaved-changes.d.ts.map +1 -1
- package/dist/client/components/unsaved-changes.js +17 -4
- package/dist/client/components/unsaved-changes.js.map +1 -1
- package/dist/client/components/use-route-overlay.d.ts +2 -9
- package/dist/client/components/use-route-overlay.d.ts.map +1 -1
- package/dist/client/components/use-route-overlay.js +5 -3
- package/dist/client/components/use-route-overlay.js.map +1 -1
- package/dist/client/locales/de-DE.d.ts.map +1 -1
- package/dist/client/locales/de-DE.js +134 -109
- package/dist/client/locales/de-DE.js.map +1 -1
- package/dist/client/locales/en-US.d.ts +1 -0
- package/dist/client/locales/en-US.d.ts.map +1 -1
- package/dist/client/locales/en-US.js +3 -2
- package/dist/client/locales/en-US.js.map +1 -1
- package/dist/client/locales/es-ES.d.ts.map +1 -1
- package/dist/client/locales/es-ES.js +211 -186
- package/dist/client/locales/es-ES.js.map +1 -1
- package/dist/client/locales/fr-FR.d.ts.map +1 -1
- package/dist/client/locales/fr-FR.js +143 -118
- package/dist/client/locales/fr-FR.js.map +1 -1
- package/dist/client/locales/id-ID.d.ts.map +1 -1
- package/dist/client/locales/id-ID.js +132 -107
- package/dist/client/locales/id-ID.js.map +1 -1
- package/dist/client/locales/index.d.ts.map +1 -1
- package/dist/client/locales/index.js +2 -0
- package/dist/client/locales/index.js.map +1 -1
- package/dist/client/locales/ja-JP.d.ts.map +1 -1
- package/dist/client/locales/ja-JP.js +215 -190
- package/dist/client/locales/ja-JP.js.map +1 -1
- package/dist/client/locales/ko-KR.d.ts +5 -0
- package/dist/client/locales/ko-KR.d.ts.map +1 -0
- package/dist/client/locales/ko-KR.js +1311 -0
- package/dist/client/locales/ko-KR.js.map +1 -0
- package/dist/client/locales/pt-BR.d.ts.map +1 -1
- package/dist/client/locales/pt-BR.js +205 -180
- package/dist/client/locales/pt-BR.js.map +1 -1
- package/dist/client/locales/ru-RU.d.ts +5 -0
- package/dist/client/locales/ru-RU.d.ts.map +1 -0
- package/dist/client/locales/ru-RU.js +1341 -0
- package/dist/client/locales/ru-RU.js.map +1 -0
- package/dist/client/locales/runtimes.en-US.d.ts +1 -0
- package/dist/client/locales/runtimes.en-US.d.ts.map +1 -1
- package/dist/client/locales/runtimes.en-US.js +3 -2
- package/dist/client/locales/runtimes.en-US.js.map +1 -1
- package/dist/client/locales/runtimes.zh-CN.d.ts.map +1 -1
- package/dist/client/locales/runtimes.zh-CN.js +3 -2
- package/dist/client/locales/runtimes.zh-CN.js.map +1 -1
- package/dist/client/locales/vi-VN.d.ts.map +1 -1
- package/dist/client/locales/vi-VN.js +171 -146
- package/dist/client/locales/vi-VN.js.map +1 -1
- package/dist/client/locales/zh-CN.d.ts.map +1 -1
- package/dist/client/locales/zh-CN.js +3 -2
- package/dist/client/locales/zh-CN.js.map +1 -1
- package/dist/client/locales/zh-TW.d.ts.map +1 -1
- package/dist/client/locales/zh-TW.js +148 -123
- package/dist/client/locales/zh-TW.js.map +1 -1
- package/dist/client/pages/runtimes/quota.d.ts +3 -2
- package/dist/client/pages/runtimes/quota.d.ts.map +1 -1
- package/dist/client/pages/runtimes/quota.js +8 -3
- package/dist/client/pages/runtimes/quota.js.map +1 -1
- package/dist/client/pages/runtimes/runner-tools.d.ts.map +1 -1
- package/dist/client/pages/runtimes/runner-tools.js +7 -16
- package/dist/client/pages/runtimes/runner-tools.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 +3 -1
- package/dist/client/pages/usage/date-range-picker.js.map +1 -1
- package/dist/client/routes.d.ts.map +1 -1
- package/dist/client/routes.js +7 -0
- package/dist/client/routes.js.map +1 -1
- package/dist/database/migrations/.manifest.json +12 -0
- package/dist/database/migrations/202611070001_ag_run_runner_pin.d.ts +4 -0
- package/dist/database/migrations/202611070001_ag_run_runner_pin.d.ts.map +1 -0
- package/dist/database/migrations/202611070001_ag_run_runner_pin.js +17 -0
- package/dist/database/migrations/202611070001_ag_run_runner_pin.js.map +1 -0
- package/dist/database/migrations/202611080001_ag_conversation_shares.d.ts +4 -0
- package/dist/database/migrations/202611080001_ag_conversation_shares.d.ts.map +1 -0
- package/dist/database/migrations/202611080001_ag_conversation_shares.js +25 -0
- package/dist/database/migrations/202611080001_ag_conversation_shares.js.map +1 -0
- package/dist/database/migrations/202611080002_ag_run_failure_params.d.ts +4 -0
- package/dist/database/migrations/202611080002_ag_run_failure_params.d.ts.map +1 -0
- package/dist/database/migrations/202611080002_ag_run_failure_params.js +17 -0
- package/dist/database/migrations/202611080002_ag_run_failure_params.js.map +1 -0
- package/dist/package.json +1 -1
- package/dist/server/composition.d.ts +2 -0
- package/dist/server/composition.d.ts.map +1 -1
- package/dist/server/composition.js +2 -1
- package/dist/server/composition.js.map +1 -1
- package/dist/server/core/agents/agent.service.d.ts +2 -1
- package/dist/server/core/agents/agent.service.d.ts.map +1 -1
- package/dist/server/core/agents/agent.service.js.map +1 -1
- package/dist/server/core/agents/agent.store.d.ts +2 -2
- package/dist/server/core/agents/agent.store.d.ts.map +1 -1
- package/dist/server/core/agents/agent.store.js +8 -2
- package/dist/server/core/agents/agent.store.js.map +1 -1
- package/dist/server/core/brief/brief.d.ts +10 -5
- package/dist/server/core/brief/brief.d.ts.map +1 -1
- package/dist/server/core/brief/brief.js +36 -13
- package/dist/server/core/brief/brief.js.map +1 -1
- package/dist/server/core/brief/index.d.ts +1 -1
- package/dist/server/core/brief/index.d.ts.map +1 -1
- package/dist/server/core/brief/index.js +1 -1
- package/dist/server/core/brief/index.js.map +1 -1
- package/dist/server/core/conversations/attachments.d.ts +0 -11
- package/dist/server/core/conversations/attachments.d.ts.map +1 -1
- package/dist/server/core/conversations/attachments.js +25 -5
- package/dist/server/core/conversations/attachments.js.map +1 -1
- package/dist/server/core/conversations/conversation.service.d.ts +2 -0
- package/dist/server/core/conversations/conversation.service.d.ts.map +1 -1
- package/dist/server/core/conversations/conversation.service.js +130 -1
- package/dist/server/core/conversations/conversation.service.js.map +1 -1
- package/dist/server/core/conversations/index.d.ts +1 -0
- package/dist/server/core/conversations/index.d.ts.map +1 -1
- package/dist/server/core/conversations/links.d.ts.map +1 -1
- package/dist/server/core/conversations/links.js +10 -5
- package/dist/server/core/conversations/links.js.map +1 -1
- package/dist/server/core/conversations/ports.d.ts +6 -0
- package/dist/server/core/conversations/ports.d.ts.map +1 -1
- package/dist/server/core/conversations/shares.d.ts +38 -0
- package/dist/server/core/conversations/shares.d.ts.map +1 -0
- package/dist/server/core/conversations/shares.js +189 -0
- package/dist/server/core/conversations/shares.js.map +1 -0
- package/dist/server/core/runs/claim.d.ts +13 -3
- package/dist/server/core/runs/claim.d.ts.map +1 -1
- package/dist/server/core/runs/claim.js +203 -36
- package/dist/server/core/runs/claim.js.map +1 -1
- package/dist/server/core/runs/extensions.d.ts +4 -1
- package/dist/server/core/runs/extensions.d.ts.map +1 -1
- package/dist/server/core/runs/extensions.js +3 -1
- package/dist/server/core/runs/extensions.js.map +1 -1
- package/dist/server/core/runs/git-credentials.d.ts.map +1 -1
- package/dist/server/core/runs/git-credentials.js +24 -2
- package/dist/server/core/runs/git-credentials.js.map +1 -1
- package/dist/server/core/runs/identity.d.ts.map +1 -1
- package/dist/server/core/runs/identity.js +9 -0
- package/dist/server/core/runs/identity.js.map +1 -1
- package/dist/server/core/runs/lifecycle.d.ts.map +1 -1
- package/dist/server/core/runs/lifecycle.js +14 -0
- package/dist/server/core/runs/lifecycle.js.map +1 -1
- package/dist/server/core/runs/ports.d.ts +7 -0
- package/dist/server/core/runs/ports.d.ts.map +1 -1
- package/dist/server/core/runs/ports.js.map +1 -1
- package/dist/server/core/runs/preview.d.ts +1 -4
- package/dist/server/core/runs/preview.d.ts.map +1 -1
- package/dist/server/core/runs/preview.js +10 -5
- package/dist/server/core/runs/preview.js.map +1 -1
- package/dist/server/core/runs/quota-wait.d.ts +7 -0
- package/dist/server/core/runs/quota-wait.d.ts.map +1 -0
- package/dist/server/core/runs/quota-wait.js +56 -0
- package/dist/server/core/runs/quota-wait.js.map +1 -0
- package/dist/server/core/runs/run-data-purge.d.ts.map +1 -1
- package/dist/server/core/runs/run-data-purge.js +1 -0
- package/dist/server/core/runs/run-data-purge.js.map +1 -1
- package/dist/server/core/runs/run.service.d.ts +13 -0
- package/dist/server/core/runs/run.service.d.ts.map +1 -1
- package/dist/server/core/runs/run.service.js +84 -3
- package/dist/server/core/runs/run.service.js.map +1 -1
- package/dist/server/core/runs/run.store.d.ts +2 -0
- package/dist/server/core/runs/run.store.d.ts.map +1 -1
- package/dist/server/core/runs/run.store.js +3 -0
- package/dist/server/core/runs/run.store.js.map +1 -1
- package/dist/server/core/runs/sweeper.d.ts.map +1 -1
- package/dist/server/core/runs/sweeper.js +2 -0
- package/dist/server/core/runs/sweeper.js.map +1 -1
- package/dist/server/core/runs/transitions.d.ts +1 -0
- package/dist/server/core/runs/transitions.d.ts.map +1 -1
- package/dist/server/core/runs/transitions.js +15 -1
- package/dist/server/core/runs/transitions.js.map +1 -1
- package/dist/server/core/runs/workload.d.ts +1 -1
- package/dist/server/core/runs/workload.d.ts.map +1 -1
- package/dist/server/core/runs/workload.js +19 -4
- package/dist/server/core/runs/workload.js.map +1 -1
- package/dist/server/kernel/clock.d.ts +2 -0
- package/dist/server/kernel/clock.d.ts.map +1 -1
- package/dist/server/kernel/clock.js +14 -0
- package/dist/server/kernel/clock.js.map +1 -1
- package/dist/server/kernel/events.d.ts +4 -1
- package/dist/server/kernel/events.d.ts.map +1 -1
- package/dist/server/kernel/events.js.map +1 -1
- package/dist/server/locales/de-DE.js +3 -3
- package/dist/server/locales/de-DE.js.map +1 -1
- package/dist/server/locales/es-ES.js +6 -6
- package/dist/server/locales/es-ES.js.map +1 -1
- package/dist/server/locales/fr-FR.js +4 -4
- package/dist/server/locales/fr-FR.js.map +1 -1
- package/dist/server/locales/id-ID.js +5 -5
- package/dist/server/locales/id-ID.js.map +1 -1
- package/dist/server/locales/index.d.ts.map +1 -1
- package/dist/server/locales/index.js +2 -0
- package/dist/server/locales/index.js.map +1 -1
- package/dist/server/locales/ja-JP.d.ts.map +1 -1
- package/dist/server/locales/ja-JP.js +6 -6
- package/dist/server/locales/ja-JP.js.map +1 -1
- package/dist/server/locales/ko-KR.d.ts +5 -0
- package/dist/server/locales/ko-KR.d.ts.map +1 -0
- package/dist/server/locales/ko-KR.js +59 -0
- package/dist/server/locales/ko-KR.js.map +1 -0
- package/dist/server/locales/pt-BR.js +6 -6
- package/dist/server/locales/pt-BR.js.map +1 -1
- package/dist/server/locales/ru-RU.d.ts +5 -0
- package/dist/server/locales/ru-RU.d.ts.map +1 -0
- package/dist/server/locales/ru-RU.js +59 -0
- package/dist/server/locales/ru-RU.js.map +1 -0
- package/dist/server/locales/vi-VN.js +6 -6
- package/dist/server/locales/vi-VN.js.map +1 -1
- package/dist/server/locales/zh-TW.d.ts.map +1 -1
- package/dist/server/locales/zh-TW.js +6 -6
- package/dist/server/locales/zh-TW.js.map +1 -1
- package/dist/server/online/agent/persistence/store.d.ts.map +1 -1
- package/dist/server/online/agent/persistence/store.js +3 -0
- package/dist/server/online/agent/persistence/store.js.map +1 -1
- package/dist/server/online/agent/runner/projection.d.ts +1 -1
- package/dist/server/online/agent/runner/projection.d.ts.map +1 -1
- package/dist/server/online/agent/runner/projection.js +8 -1
- package/dist/server/online/agent/runner/projection.js.map +1 -1
- package/dist/server/routes/admin.d.ts.map +1 -1
- package/dist/server/routes/admin.js +27 -1
- package/dist/server/routes/admin.js.map +1 -1
- package/dist/server/routes/chat.d.ts.map +1 -1
- package/dist/server/routes/chat.js +91 -11
- package/dist/server/routes/chat.js.map +1 -1
- package/dist/server/routes/runners/admin.d.ts.map +1 -1
- package/dist/server/routes/runners/admin.js +3 -3
- package/dist/server/routes/runners/admin.js.map +1 -1
- package/dist/server/routes/runners/runner.d.ts.map +1 -1
- package/dist/server/routes/runners/runner.js +13 -22
- package/dist/server/routes/runners/runner.js.map +1 -1
- package/dist/server/routes/schemas.d.ts +4 -0
- package/dist/server/routes/schemas.d.ts.map +1 -1
- package/dist/server/routes/schemas.js +23 -5
- package/dist/server/routes/schemas.js.map +1 -1
- package/dist/server/runners/runner.service.d.ts.map +1 -1
- package/dist/server/runners/runner.service.js +17 -5
- package/dist/server/runners/runner.service.js.map +1 -1
- package/dist/shared/briefs.d.ts +2 -6
- package/dist/shared/briefs.d.ts.map +1 -1
- package/dist/shared/conversations.d.ts +17 -0
- package/dist/shared/conversations.d.ts.map +1 -1
- package/dist/shared/conversations.js.map +1 -1
- package/dist/shared/runs.d.ts +4 -0
- package/dist/shared/runs.d.ts.map +1 -1
- package/dist/shared/runs.js.map +1 -1
- package/docs/conversation-sharing.md +55 -0
- package/package.json +13 -13
- package/skills/nocobase-app-plugin-agents/references/runners.md +1 -1
|
@@ -19,6 +19,12 @@ import { ACTIVE_JOB, jobsRepo } from '../../jobs/job.store.js';
|
|
|
19
19
|
* agent's entries whose tool the runner runs and has room for, so an agent listing two tools runs with the second while
|
|
20
20
|
* the first is full; a run none of whose tools has room is passed over for the runs behind it.
|
|
21
21
|
*
|
|
22
|
+
* Runners share a queue in proportion to each tool's limit, not by who asks first: before it locks anything, a claim
|
|
23
|
+
* waits until the run has been queued for the share of the tool's limit the runner already runs, times one second
|
|
24
|
+
* (`claimPace`). The runners' route hands out one run per request and the runner asks again at once, so of several
|
|
25
|
+
* runners woken for the same runs, the least busy with each tool takes the next one. A request that ends while it
|
|
26
|
+
* waits takes nothing.
|
|
27
|
+
*
|
|
22
28
|
* Variables are checked and opened in the claim's own transaction, on its connection: which ones the assembled payload
|
|
23
29
|
* gets, whether any is for shared runners only, and their values, so what the runner receives is what was checked. The
|
|
24
30
|
* names taken from the runner go in `workspace.passthrough`, without values.
|
|
@@ -48,7 +54,7 @@ import { ANNOUNCED_FAILURES, isProtocolSupported, policyAllowsAgent, policyAllow
|
|
|
48
54
|
import { chatEffortCapabilities, quotaForAgent, runnerLowOnDisk, runsEntry, toolLimit, supportsWorkspaceKey, } from '../../../shared/runners.js';
|
|
49
55
|
import { queueStart } from './selection.js';
|
|
50
56
|
import { createHash } from 'node:crypto';
|
|
51
|
-
import { later } from '../../kernel/clock.js';
|
|
57
|
+
import { later, pause } from '../../kernel/clock.js';
|
|
52
58
|
import { notFound } from '../../kernel/errors.js';
|
|
53
59
|
import { runSecretsKey } from '../../kernel/redaction.js';
|
|
54
60
|
import { cleanList, covers, jsonObject } from '../../kernel/values.js';
|
|
@@ -57,7 +63,7 @@ import { joinBrief, renderBrief } from '../brief/index.js';
|
|
|
57
63
|
import { cliPackageFor } from './cli-package.js';
|
|
58
64
|
import { executionHistory } from './execution.js';
|
|
59
65
|
import { describeVariables, variableRefs, } from '../variables/index.js';
|
|
60
|
-
import { mountsFor, prepareExtensions, repoAccessFor, responsibleOf, sessionKeyOf, withSections, } from './extensions.js';
|
|
66
|
+
import { mountsFor, prepareExtensions, repoAccessFor, RepoAccessError, responsibleOf, sessionKeyOf, withSections, } from './extensions.js';
|
|
61
67
|
import { MAX_CLAIM_FAILURES, toolPolicyFor } from './policy.js';
|
|
62
68
|
import { mintRunToken } from './run-tokens.js';
|
|
63
69
|
import { ACTIVE, countActive, eventsRepo, findRunRecord, markDelivered, pendingInputs, runsOfKey, runsRepo, inputsRepo, sessionsRepo, toInput, toExecutions, toRun, } from './run.store.js';
|
|
@@ -71,6 +77,8 @@ import { consumeReset, saveBrief } from './workspace.store.js';
|
|
|
71
77
|
const CANDIDATES = 50;
|
|
72
78
|
/** The initial page plus at most two further pages per claim request, shared by all free slots. */
|
|
73
79
|
const CANDIDATE_PAGES = 3;
|
|
80
|
+
/** The longest a claim waits its turn: what a runner waits for a run when it already runs its tool at the limit. */
|
|
81
|
+
const PACE_MS = 1000;
|
|
74
82
|
export function createClaimScan() {
|
|
75
83
|
return { remainingPages: CANDIDATE_PAGES };
|
|
76
84
|
}
|
|
@@ -109,6 +117,22 @@ function toolRoom(tools) {
|
|
|
109
117
|
},
|
|
110
118
|
};
|
|
111
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* How long `runner` waits before it takes `run` with `tool`, at `now`: the share of the tool's limit it already runs
|
|
122
|
+
* (`toolLimit`), of the busier of what this application counts (`byTool`) and what the runner last reported
|
|
123
|
+
* (`Runner.toolLoad`), times one second, counted from when the run entered the queue (`queueStart`). Several runners
|
|
124
|
+
* woken for one run take it in order of how busy each is with its tool, whichever asked first and however many runs
|
|
125
|
+
* each took before; an idle runner, and work queued longer than its share, wait nothing.
|
|
126
|
+
*/
|
|
127
|
+
export function claimPace(runner, tool, byTool, run, now) {
|
|
128
|
+
const limit = toolLimit(runner, tool);
|
|
129
|
+
if (limit <= 0)
|
|
130
|
+
return 0;
|
|
131
|
+
const load = runner.toolLoad?.[tool];
|
|
132
|
+
const used = Math.max(byTool[tool] ?? 0, load ? load.slots - load.free : 0);
|
|
133
|
+
const share = (Math.min(used, limit) / limit) * PACE_MS;
|
|
134
|
+
return Math.min(share, Math.max(0, queueStart(run) + share - now));
|
|
135
|
+
}
|
|
112
136
|
/**
|
|
113
137
|
* The entry `runner` takes a run of `agent` with: the first whose tool it has enabled, installed and signed in, with
|
|
114
138
|
* the entry's model among those it reports (`runsEntry`). Null when it has none of them.
|
|
@@ -146,9 +170,24 @@ export function fitsActor(runner, agent, actorUserId, requires, now) {
|
|
|
146
170
|
return false;
|
|
147
171
|
return runner.trust === 'team' || runner.ownerUserId === actorUserId;
|
|
148
172
|
}
|
|
173
|
+
/** A strict continuation keeps the tool/model that owns its original session. */
|
|
174
|
+
function quotaAgent(run, agent) {
|
|
175
|
+
if (!run.runnerPinId || run.freshSession)
|
|
176
|
+
return agent;
|
|
177
|
+
const last = toExecutions(run.executionHistory).at(-1);
|
|
178
|
+
const tool = last?.tool ?? run.tool;
|
|
179
|
+
const model = last ? last.model : run.model;
|
|
180
|
+
return tool
|
|
181
|
+
? {
|
|
182
|
+
...agent,
|
|
183
|
+
modelEntries: agent.modelEntries.filter((entry) => entry.tool === tool && entry.model === model),
|
|
184
|
+
}
|
|
185
|
+
: agent;
|
|
186
|
+
}
|
|
149
187
|
/** Whether `runner` may run `run` of `agent`, before any lock is taken and before its payload is known. */
|
|
150
188
|
export function fits(runner, agent, run, now) {
|
|
151
|
-
return
|
|
189
|
+
return ((!run.runnerPinId || run.runnerPinId === runner.id) &&
|
|
190
|
+
fitsActor(runner, quotaAgent(run, agent), run.actorUserId, toRun(run).requires, now));
|
|
152
191
|
}
|
|
153
192
|
/** The runner features a payload needs. */
|
|
154
193
|
export function payloadRequires(dirs, skills, variables, workspaceKey = false) {
|
|
@@ -185,7 +224,7 @@ export function skillTargets(_agent, assembly) {
|
|
|
185
224
|
...assembly.dirs.flatMap((dir) => dir.scopeId ? [{ scope: 'workdir', scopeId: dir.scopeId }] : []),
|
|
186
225
|
];
|
|
187
226
|
}
|
|
188
|
-
function fingerprint(agent, entry, layers, skills) {
|
|
227
|
+
function fingerprint(agent, entry, layers, skills, readOnly) {
|
|
189
228
|
return createHash('sha256')
|
|
190
229
|
.update(JSON.stringify([
|
|
191
230
|
entry.tool,
|
|
@@ -194,12 +233,16 @@ function fingerprint(agent, entry, layers, skills) {
|
|
|
194
233
|
layers.system,
|
|
195
234
|
layers.agent,
|
|
196
235
|
skills.map((skill) => [skill.slug, skill.hash]),
|
|
236
|
+
// Keep existing writable fingerprints unchanged across upgrades.
|
|
237
|
+
...(readOnly ? [{ readOnly: true }] : []),
|
|
197
238
|
]))
|
|
198
239
|
.digest('hex');
|
|
199
240
|
}
|
|
200
241
|
/** The brief for `assembly`, with the skills a run would get. */
|
|
201
242
|
export function briefOf(agent, assembly, skills, cli, appName) {
|
|
202
|
-
|
|
243
|
+
// Custom system prompts (such as conversations) keep their own layout.
|
|
244
|
+
const stablePrefix = assembly.briefLayout === 'stable-prefix' && assembly.system === undefined;
|
|
245
|
+
const input = {
|
|
203
246
|
agentName: agent.name,
|
|
204
247
|
subject: { noun: assembly.subject.noun, key: assembly.subject.key },
|
|
205
248
|
task: assembly.task,
|
|
@@ -210,14 +253,20 @@ export function briefOf(agent, assembly, skills, cli, appName) {
|
|
|
210
253
|
cli,
|
|
211
254
|
appName,
|
|
212
255
|
...(assembly.guidance ? { guidance: assembly.guidance } : {}),
|
|
213
|
-
}
|
|
256
|
+
};
|
|
257
|
+
const rendered = renderBrief(input);
|
|
214
258
|
const layers = assembly.system === undefined
|
|
215
259
|
? rendered
|
|
216
260
|
: { ...rendered, system: assembly.system };
|
|
217
261
|
const turn = assembly.turn.previousSummary
|
|
218
262
|
? `${assembly.turn.prompt}\n\nWhat earlier runs on this already did:\n\n${assembly.turn.previousSummary}`
|
|
219
263
|
: assembly.turn.prompt;
|
|
220
|
-
|
|
264
|
+
// Keep the logical layers (and their session fingerprint) unchanged. Only the outgoing text is reordered,
|
|
265
|
+
// so an ordinary follow-up can still resume the session created by an independent review.
|
|
266
|
+
const prompt = stablePrefix
|
|
267
|
+
? joinBrief(renderBrief({ ...input, stablePrefix: true }), true)
|
|
268
|
+
: joinBrief(layers);
|
|
269
|
+
return { layers, turn, prompt };
|
|
221
270
|
}
|
|
222
271
|
/** Whether `runner` asks for repository credentials on demand rather than taking them with the claim. */
|
|
223
272
|
export function asksCredentialsOnDemand(runner) {
|
|
@@ -684,6 +733,7 @@ export function createClaimService(deps) {
|
|
|
684
733
|
const token = await mintRunToken(conn, deps, run, runner.id);
|
|
685
734
|
const git = await repoAccessFor(conn, deps.repoAccess, {
|
|
686
735
|
claim,
|
|
736
|
+
readOnly: assembly.readOnly === true,
|
|
687
737
|
repos: dirs.filter((dir) => dir.kind === 'repo'),
|
|
688
738
|
onDemand: asksCredentialsOnDemand(runner),
|
|
689
739
|
tools: wrapsHostTools(runner),
|
|
@@ -700,17 +750,20 @@ export function createClaimService(deps) {
|
|
|
700
750
|
});
|
|
701
751
|
const render = async () => briefOf(agent, await withSections(conn, deps.sections, effectiveClaim, assembly, prepared), skills, deps.cli.name, deps.app.name);
|
|
702
752
|
let brief = await render();
|
|
703
|
-
const briefFingerprint = fingerprint(agent, entry, brief.layers, skills);
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
753
|
+
const briefFingerprint = fingerprint(agent, entry, brief.layers, skills, assembly.readOnly === true);
|
|
754
|
+
const strict = Boolean(run.runnerPinId && !run.freshSession);
|
|
755
|
+
const resume = strict
|
|
756
|
+
? Boolean(run.sessionId)
|
|
757
|
+
: !run.freshSession &&
|
|
758
|
+
binding.sessionPolicy?.(effectiveClaim) !== 'fresh' &&
|
|
759
|
+
!clean &&
|
|
760
|
+
session &&
|
|
761
|
+
!session.poisoned &&
|
|
762
|
+
session.fingerprint === briefFingerprint;
|
|
710
763
|
// A kept session that cannot be resumed: the new one is told what came before, as a first one is. The history
|
|
711
764
|
// is outside the fingerprint, so it does not change.
|
|
712
765
|
let recoveryContext;
|
|
713
|
-
if (session) {
|
|
766
|
+
if (session && !strict) {
|
|
714
767
|
const fresh = await binding.context.assemble(conn, { ...claim, freshSession: true }, prepared.get('subject'));
|
|
715
768
|
if (resume) {
|
|
716
769
|
// Should the kept session be gone on the runner, the new one hears what came before from this.
|
|
@@ -744,6 +797,11 @@ export function createClaimService(deps) {
|
|
|
744
797
|
'The previous attempt was interrupted by a runner upgrade. Continue from the current state.',
|
|
745
798
|
]
|
|
746
799
|
: []),
|
|
800
|
+
...(strict
|
|
801
|
+
? [
|
|
802
|
+
'Quota recovery: continue the interrupted step from the existing session, progress and files.',
|
|
803
|
+
]
|
|
804
|
+
: []),
|
|
747
805
|
...resumeNotes,
|
|
748
806
|
...(sessionNote ? [sessionNote] : []),
|
|
749
807
|
].join('\n\n');
|
|
@@ -756,6 +814,7 @@ export function createClaimService(deps) {
|
|
|
756
814
|
createdAt: nowText,
|
|
757
815
|
});
|
|
758
816
|
const payload = {
|
|
817
|
+
...(strict ? { session: { strict: true } } : {}),
|
|
759
818
|
...(run.subjectKind === 'conversation' &&
|
|
760
819
|
runner.features.includes('outputBlocks')
|
|
761
820
|
? { outputBlocks: true }
|
|
@@ -786,8 +845,14 @@ export function createClaimService(deps) {
|
|
|
786
845
|
prompt: {
|
|
787
846
|
system: brief.prompt,
|
|
788
847
|
turn,
|
|
789
|
-
session: resume ? 'resume' : 'fresh',
|
|
790
|
-
...(
|
|
848
|
+
session: strict || resume ? 'resume' : 'fresh',
|
|
849
|
+
...(strict
|
|
850
|
+
? run.sessionId
|
|
851
|
+
? { resumeSessionId: run.sessionId }
|
|
852
|
+
: {}
|
|
853
|
+
: resume && session
|
|
854
|
+
? { resumeSessionId: session.sessionId }
|
|
855
|
+
: {}),
|
|
791
856
|
...(resume && recoveryContext ? { recoveryContext } : {}),
|
|
792
857
|
},
|
|
793
858
|
inputs,
|
|
@@ -1051,7 +1116,9 @@ export function createClaimService(deps) {
|
|
|
1051
1116
|
const pinned = await pin.runnerOf(conn, toRun(run));
|
|
1052
1117
|
return pinned === null || pinned === runner.id;
|
|
1053
1118
|
}
|
|
1054
|
-
async function attempt(runner, agent, candidate, reported) {
|
|
1119
|
+
async function attempt(runner, agent, candidate, reported, pass) {
|
|
1120
|
+
if (candidate.runnerPinId && candidate.runnerPinId !== runner.id)
|
|
1121
|
+
return { kind: 'skipped' };
|
|
1055
1122
|
const conn = tx.read();
|
|
1056
1123
|
// Rules that do not depend on who the run acts as come first, and settle the rest of the pass: an agent at its
|
|
1057
1124
|
// limit is checked once per poll, not once per queued run.
|
|
@@ -1079,6 +1146,17 @@ export function createClaimService(deps) {
|
|
|
1079
1146
|
const consent = await commentConsentFor(conn, runner, candidate);
|
|
1080
1147
|
if (!consent.allowed)
|
|
1081
1148
|
return { kind: 'skipped' };
|
|
1149
|
+
// Wait this runner's turn for the run, after every read-only rule and before any lock or write. The transaction
|
|
1150
|
+
// checks again whether the run is still queued and which tool has room.
|
|
1151
|
+
const paced = pickOpenEntry(runner, agent, (tool) => (pass.byTool[tool] ?? 0) < toolLimit(runner, tool) &&
|
|
1152
|
+
reported.has(tool), clock.now().getTime());
|
|
1153
|
+
if (!paced)
|
|
1154
|
+
return { kind: 'skipped' };
|
|
1155
|
+
const wait = claimPace(runner, paced.tool, pass.byTool, candidate, clock.now().getTime());
|
|
1156
|
+
if (wait > 0)
|
|
1157
|
+
await (deps.pause ?? pause)(wait, pass.signal);
|
|
1158
|
+
if (pass.signal?.aborted)
|
|
1159
|
+
return { kind: 'aborted' };
|
|
1082
1160
|
// Read once, before the transaction, and handed to every repository access hook of this claim. A failed read
|
|
1083
1161
|
// leaves the run queued for the next poll rather than letting a provider pick an identity without it.
|
|
1084
1162
|
let responsibleUserId = null;
|
|
@@ -1154,6 +1232,8 @@ export function createClaimService(deps) {
|
|
|
1154
1232
|
status: 'dispatched',
|
|
1155
1233
|
failureReason: null,
|
|
1156
1234
|
failureDetail: null,
|
|
1235
|
+
failureParams: null,
|
|
1236
|
+
claimFailures: 0,
|
|
1157
1237
|
runnerId: runner.id,
|
|
1158
1238
|
// Brief providers observe this reservation inside the claim transaction.
|
|
1159
1239
|
// Replace a previous attempt's tool before computing the roster.
|
|
@@ -1241,7 +1321,16 @@ export function createClaimService(deps) {
|
|
|
1241
1321
|
: null,
|
|
1242
1322
|
},
|
|
1243
1323
|
});
|
|
1244
|
-
|
|
1324
|
+
if (current.runnerPinId && !current.freshSession)
|
|
1325
|
+
await deps.subjects
|
|
1326
|
+
.get(run.subjectKind)
|
|
1327
|
+
?.sink?.onQuotaResumed?.(unit, toRun(run));
|
|
1328
|
+
unit.emit({
|
|
1329
|
+
type: 'run.changed',
|
|
1330
|
+
...(current.runnerPinId ? { quotaWait: false } : {}),
|
|
1331
|
+
runId: run.id,
|
|
1332
|
+
status: 'dispatched',
|
|
1333
|
+
});
|
|
1245
1334
|
if (variableRefs(candidate.teamOnlyVariables).length > 0)
|
|
1246
1335
|
unit.emit({
|
|
1247
1336
|
type: 'notice.cleared',
|
|
@@ -1284,28 +1373,98 @@ export function createClaimService(deps) {
|
|
|
1284
1373
|
}
|
|
1285
1374
|
}
|
|
1286
1375
|
}
|
|
1287
|
-
/**
|
|
1288
|
-
async function recordFailure(
|
|
1289
|
-
deps.onClaimFailure?.(
|
|
1376
|
+
/** Repository failures have their own budget; ordinary assembly failures keep the existing three-try limit. */
|
|
1377
|
+
async function recordFailure(candidate, error) {
|
|
1378
|
+
deps.onClaimFailure?.(candidate.id, error.cause);
|
|
1290
1379
|
await tx.run(async (unit) => {
|
|
1291
|
-
const run = await findRunRecord(unit.conn,
|
|
1292
|
-
if (!run ||
|
|
1380
|
+
const run = await findRunRecord(unit.conn, candidate.id);
|
|
1381
|
+
if (!run ||
|
|
1382
|
+
run.status !== 'queued' ||
|
|
1383
|
+
run.revision !== candidate.revision ||
|
|
1384
|
+
Number(run.attempt) !== Number(candidate.attempt) ||
|
|
1385
|
+
run.actorUserId !== candidate.actorUserId ||
|
|
1386
|
+
!(await queuedIdentityCurrent(unit.conn, deps, run)))
|
|
1387
|
+
return;
|
|
1388
|
+
const previous = jsonObject(run.failureParams);
|
|
1389
|
+
const cause = error.cause;
|
|
1390
|
+
if (cause instanceof RepoAccessError) {
|
|
1391
|
+
const now = clock.now().getTime();
|
|
1392
|
+
const repoFailures = Number(previous.repoFailures ?? 0) + 1;
|
|
1393
|
+
const first = typeof previous.repoFirstFailureAt === 'string'
|
|
1394
|
+
? Date.parse(previous.repoFirstFailureAt)
|
|
1395
|
+
: now;
|
|
1396
|
+
const retryAt = typeof cause.params.retryAt === 'string'
|
|
1397
|
+
? Date.parse(cause.params.retryAt)
|
|
1398
|
+
: NaN;
|
|
1399
|
+
const delays = [1, 2, 4, 8, 15];
|
|
1400
|
+
const next = Math.max(now + (delays[repoFailures - 1] ?? 15) * 60_000, Number.isFinite(retryAt) ? retryAt : 0);
|
|
1401
|
+
const params = {
|
|
1402
|
+
...cause.params,
|
|
1403
|
+
repoFailures,
|
|
1404
|
+
repoFirstFailureAt: new Date(first).toISOString(),
|
|
1405
|
+
};
|
|
1406
|
+
const reason = cause.kind === 'denied'
|
|
1407
|
+
? 'repoAccessDenied'
|
|
1408
|
+
: 'repoAccessUnavailable';
|
|
1409
|
+
if (cause.kind === 'denied' ||
|
|
1410
|
+
repoFailures >= 6 ||
|
|
1411
|
+
next > first + 60 * 60_000) {
|
|
1412
|
+
await finishRun(unit, deps, run, {
|
|
1413
|
+
status: 'failed',
|
|
1414
|
+
reason,
|
|
1415
|
+
detail: cause.message,
|
|
1416
|
+
failureParams: params,
|
|
1417
|
+
}, true);
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
const availableAt = new Date(next).toISOString();
|
|
1421
|
+
const changed = await runsRepo(unit.conn).updateMany({
|
|
1422
|
+
filter: {
|
|
1423
|
+
id: run.id,
|
|
1424
|
+
status: 'queued',
|
|
1425
|
+
revision: run.revision,
|
|
1426
|
+
attempt: Number(run.attempt),
|
|
1427
|
+
actorUserId: run.actorUserId,
|
|
1428
|
+
},
|
|
1429
|
+
values: {
|
|
1430
|
+
availableAt,
|
|
1431
|
+
failureReason: reason,
|
|
1432
|
+
failureDetail: cause.message.slice(0, 10_000),
|
|
1433
|
+
failureParams: params,
|
|
1434
|
+
updatedAt: clock.now().toISOString(),
|
|
1435
|
+
},
|
|
1436
|
+
});
|
|
1437
|
+
if (changed.updatedCount > 0) {
|
|
1438
|
+
unit.emit({ type: 'run.changed', runId: run.id, status: 'queued' });
|
|
1439
|
+
unit.emit({ type: 'run.requeued', runId: run.id, availableAt });
|
|
1440
|
+
}
|
|
1293
1441
|
return;
|
|
1442
|
+
}
|
|
1294
1443
|
const failures = Number(run.claimFailures) + 1;
|
|
1444
|
+
// Preserve only the independent repository retry budget across an unrelated preparation failure.
|
|
1445
|
+
const budget = previous.repoFailures
|
|
1446
|
+
? {
|
|
1447
|
+
repoFailures: previous.repoFailures,
|
|
1448
|
+
repoFirstFailureAt: previous.repoFirstFailureAt,
|
|
1449
|
+
repoAccess: previous.repoAccess ?? previous,
|
|
1450
|
+
}
|
|
1451
|
+
: null;
|
|
1295
1452
|
if (failures >= MAX_CLAIM_FAILURES) {
|
|
1296
1453
|
await finishRun(unit, deps, run, {
|
|
1297
1454
|
status: 'failed',
|
|
1298
1455
|
reason: 'setupFailed',
|
|
1299
1456
|
detail: error.message,
|
|
1300
|
-
|
|
1457
|
+
failureParams: null,
|
|
1458
|
+
}, true);
|
|
1301
1459
|
return;
|
|
1302
1460
|
}
|
|
1303
1461
|
await runsRepo(unit.conn).updateMany({
|
|
1304
|
-
filter: { id:
|
|
1462
|
+
filter: { id: run.id, status: 'queued', revision: run.revision },
|
|
1305
1463
|
values: {
|
|
1306
1464
|
claimFailures: failures,
|
|
1307
1465
|
failureReason: 'setupFailed',
|
|
1308
1466
|
failureDetail: error.message.slice(0, 10_000),
|
|
1467
|
+
failureParams: budget,
|
|
1309
1468
|
},
|
|
1310
1469
|
});
|
|
1311
1470
|
});
|
|
@@ -1529,8 +1688,8 @@ export function createClaimService(deps) {
|
|
|
1529
1688
|
}
|
|
1530
1689
|
return true;
|
|
1531
1690
|
}
|
|
1532
|
-
/** False stops the whole request when a transactional capacity check finds the runner full. */
|
|
1533
|
-
async function claimPage(runner, seen, reported, queued, full) {
|
|
1691
|
+
/** False stops the whole request when a transactional capacity check finds the runner full, or the request ended. */
|
|
1692
|
+
async function claimPage(runner, seen, reported, queued, full, pass) {
|
|
1534
1693
|
const conn = tx.read();
|
|
1535
1694
|
const affinity = await affinityBatch(queued, runner);
|
|
1536
1695
|
const agents = await findAgents(conn, queued
|
|
@@ -1542,10 +1701,13 @@ export function createClaimService(deps) {
|
|
|
1542
1701
|
? deps.runners.all(conn)
|
|
1543
1702
|
: Promise.resolve([runner]));
|
|
1544
1703
|
for (const candidate of queued) {
|
|
1545
|
-
if (seen.has(candidate.id) ||
|
|
1704
|
+
if (seen.has(candidate.id) ||
|
|
1705
|
+
full.has(candidate.agentId) ||
|
|
1706
|
+
(candidate.runnerPinId && candidate.runnerPinId !== runner.id))
|
|
1546
1707
|
continue;
|
|
1547
1708
|
seen.add(candidate.id);
|
|
1548
|
-
const
|
|
1709
|
+
const configured = agents.get(candidate.agentId);
|
|
1710
|
+
const agent = configured ? quotaAgent(candidate, configured) : undefined;
|
|
1549
1711
|
if (!agent ||
|
|
1550
1712
|
(!deps.subjects.get(candidate.subjectKind)?.actsAs &&
|
|
1551
1713
|
!fits(runner, agent, candidate, clock.now().getTime())))
|
|
@@ -1555,7 +1717,8 @@ export function createClaimService(deps) {
|
|
|
1555
1717
|
continue;
|
|
1556
1718
|
// Work bound to another runner waits for it; work bound here takes no other preference.
|
|
1557
1719
|
const pin = deps.subjects.get(candidate.subjectKind)?.runnerPin;
|
|
1558
|
-
const pinned =
|
|
1720
|
+
const pinned = candidate.runnerPinId ??
|
|
1721
|
+
(pin ? await pin.runnerOf(conn, toRun(candidate)) : null);
|
|
1559
1722
|
if (pinned !== null && pinned !== runner.id)
|
|
1560
1723
|
continue;
|
|
1561
1724
|
const preferred = affinity.preferred.get(candidate.id);
|
|
@@ -1569,9 +1732,11 @@ export function createClaimService(deps) {
|
|
|
1569
1732
|
if (!pickOpenEntry(runner, agent, (tool) => reported.has(tool), clock.now().getTime()))
|
|
1570
1733
|
continue;
|
|
1571
1734
|
try {
|
|
1572
|
-
const result = await attempt(runner, agent, candidate, reported);
|
|
1735
|
+
const result = await attempt(runner, agent, candidate, reported, pass);
|
|
1573
1736
|
if (result.kind === 'claimed')
|
|
1574
1737
|
return result.payload;
|
|
1738
|
+
if (result.kind === 'aborted')
|
|
1739
|
+
return false;
|
|
1575
1740
|
if (result.kind === 'full') {
|
|
1576
1741
|
if (result.scope === 'runner')
|
|
1577
1742
|
return false;
|
|
@@ -1586,7 +1751,7 @@ export function createClaimService(deps) {
|
|
|
1586
1751
|
continue;
|
|
1587
1752
|
if (!(error instanceof AssemblyError))
|
|
1588
1753
|
throw error;
|
|
1589
|
-
await recordFailure(candidate
|
|
1754
|
+
await recordFailure(candidate, error);
|
|
1590
1755
|
}
|
|
1591
1756
|
}
|
|
1592
1757
|
return null;
|
|
@@ -1633,7 +1798,7 @@ export function createClaimService(deps) {
|
|
|
1633
1798
|
const agent = await findAgent(conn, run.agentId);
|
|
1634
1799
|
return agent ? selectionFacts(conn, toRun(run), agent, false) : null;
|
|
1635
1800
|
},
|
|
1636
|
-
async claim(runner, free, tools, scan = createClaimScan()) {
|
|
1801
|
+
async claim(runner, free, tools, scan = createClaimScan(), signal) {
|
|
1637
1802
|
if (scan.remainingPages <= 0 ||
|
|
1638
1803
|
runner.status !== 'online' ||
|
|
1639
1804
|
runner.draining ||
|
|
@@ -1646,8 +1811,10 @@ export function createClaimService(deps) {
|
|
|
1646
1811
|
const pages = candidates(runner, scan);
|
|
1647
1812
|
let queued = [];
|
|
1648
1813
|
while (payloads.length < limit) {
|
|
1649
|
-
|
|
1814
|
+
const held = await deps.slotsHeld(tx.read(), runner.id);
|
|
1815
|
+
if (held.runs + held.jobs >= runner.slots)
|
|
1650
1816
|
break;
|
|
1817
|
+
const pass = { byTool: held.byTool ?? {}, signal };
|
|
1651
1818
|
const full = new Set();
|
|
1652
1819
|
let payload;
|
|
1653
1820
|
do {
|
|
@@ -1657,7 +1824,7 @@ export function createClaimService(deps) {
|
|
|
1657
1824
|
return payloads;
|
|
1658
1825
|
queued = page.value;
|
|
1659
1826
|
}
|
|
1660
|
-
payload = await claimPage(runner, seen, reported, queued, full);
|
|
1827
|
+
payload = await claimPage(runner, seen, reported, queued, full, pass);
|
|
1661
1828
|
if (payload === false)
|
|
1662
1829
|
return payloads;
|
|
1663
1830
|
if (!payload)
|