@capybara-hq/kernel 0.1.0-rc.25 → 0.1.0-rc.27
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/admin/outbox-dead.service.d.ts +7 -3
- package/dist/admin/outbox-dead.service.js +17 -6
- package/dist/admin/overview.service.d.ts +6 -6
- package/dist/admin/overview.service.js +7 -7
- package/dist/background/wake-rules.d.ts +5 -0
- package/dist/background/wake-rules.js +16 -5
- package/dist/catalog/catalog-content.d.ts +6 -1
- package/dist/catalog/catalog-content.js +2 -1
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-catalog.service.js +5 -2
- package/dist/catalog/plugin-install.d.ts +5 -0
- package/dist/catalog/plugin-install.js +37 -1
- package/dist/channels/channels.module.d.ts +9 -8
- package/dist/channels/channels.module.js +11 -10
- package/dist/channels/connections.service.d.ts +3 -3
- package/dist/channels/connections.service.js +6 -6
- package/dist/channels/deliver-job.d.ts +10 -3
- package/dist/channels/deliver-job.js +7 -3
- package/dist/channels/jobs.d.ts +11 -4
- package/dist/channels/jobs.js +18 -3
- package/dist/channels/outbound.d.ts +5 -2
- package/dist/channels/outbound.js +9 -2
- package/dist/conversations/conversations.controller.d.ts +4 -2
- package/dist/conversations/conversations.controller.js +4 -2
- package/dist/conversations/conversations.service.d.ts +37 -4
- package/dist/conversations/conversations.service.js +77 -20
- package/dist/conversations/events.d.ts +6 -0
- package/dist/conversations/events.js +6 -0
- package/dist/db/kernel-schema.d.ts +10 -1
- package/dist/devices/devices.service.d.ts +14 -6
- package/dist/devices/devices.service.js +20 -7
- package/dist/evals/evals.module.js +5 -1
- package/dist/evals/jobs.d.ts +14 -6
- package/dist/evals/jobs.js +21 -19
- package/dist/evals/runs.service.d.ts +5 -6
- package/dist/evals/runs.service.js +11 -33
- package/dist/infra/outbox/outbox.service.d.ts +6 -0
- package/dist/infra/outbox/outbox.service.js +51 -17
- package/dist/infra/outbox/registry.d.ts +52 -0
- package/dist/infra/outbox/registry.js +57 -0
- package/dist/local-devices/device-policy.service.d.ts +131 -26
- package/dist/local-devices/device-policy.service.js +341 -145
- package/dist/local-devices/machines.d.ts +17 -2
- package/dist/local-devices/machines.js +66 -5
- package/dist/local-devices/policy-op-jobs.d.ts +15 -0
- package/dist/local-devices/policy-op-jobs.js +12 -0
- package/dist/local-devices/policy-summary.d.ts +36 -3
- package/dist/local-devices/policy-summary.js +47 -1
- package/dist/orgs/orgs.service.js +4 -1
- package/dist/overrides.d.ts +4 -0
- package/dist/overrides.js +2 -0
- package/dist/platform/ops/conversations.ops.js +1 -1
- package/dist/runtime/maintenance.d.ts +6 -3
- package/dist/runtime/maintenance.js +21 -7
- package/dist/runtime/plugin-context.service.d.ts +9 -3
- package/dist/runtime/plugin-context.service.js +17 -5
- package/dist/runtime/run-context.service.d.ts +7 -3
- package/dist/runtime/run-context.service.js +12 -2
- package/dist/runtime/run-finish.d.ts +2 -2
- package/dist/runtime/run-finish.js +11 -6
- package/dist/runtime/run-jobs.d.ts +21 -4
- package/dist/runtime/run-jobs.js +29 -6
- package/dist/runtime/run-orphan-check.job.d.ts +14 -11
- package/dist/runtime/run-orphan-check.job.js +16 -11
- package/dist/runtime/run-queue.d.ts +23 -31
- package/dist/runtime/run-queue.js +62 -64
- package/dist/runtime/run-status.d.ts +12 -0
- package/dist/runtime/run-status.js +21 -0
- package/dist/runtime/run-worker.d.ts +10 -2
- package/dist/runtime/run-worker.js +44 -19
- package/dist/runtime/step-log.d.ts +6 -5
- package/dist/runtime/step-log.js +6 -5
- package/dist/runtime/usage-report.d.ts +3 -1
- package/dist/runtime/usage-report.js +4 -2
- package/dist/runtime/waits.d.ts +16 -6
- package/dist/runtime/waits.js +37 -15
- package/dist/schedules/scan.job.d.ts +6 -6
- package/dist/schedules/scan.job.js +6 -6
- package/dist/skills/catalog.d.ts +7 -3
- package/dist/skills/catalog.js +12 -2
- package/dist/tools/approvals.service.d.ts +96 -19
- package/dist/tools/approvals.service.js +300 -67
- package/dist/tools/builtins/sandbox/task-output.js +21 -6
- package/dist/tools/connectors.service.js +0 -4
- package/dist/tools/device-op-resolver.d.ts +40 -0
- package/dist/tools/device-op-resolver.js +121 -0
- package/dist/tools/network-asks.d.ts +1 -1
- package/dist/tools/network-asks.js +4 -3
- package/dist/tools/tools.module.js +4 -0
- package/package.json +2 -2
- package/src/migrations/0125_conversation_tree_stopped_at.sql +5 -0
|
@@ -97,7 +97,7 @@ export declare class ChannelOutbound {
|
|
|
97
97
|
/** The conversation lock provider; delivery takes it under its own key prefix. */
|
|
98
98
|
lock: Pick<ConversationLock, "scoped">, connections: Pick<ChannelConnections, "outbound">, approvals: Pick<ApprovalsService, "describe">, questions: Pick<QuestionsService, "forCall">, registry: Pick<ToolRegistry, "builtinSpecs" | "extensionToolSpecs">,
|
|
99
99
|
/** 入站任务的处理(worker 上执行 `ChannelInbound.handle`);失败抛错由 BullMQ 重试。 */
|
|
100
|
-
inbound: Pick<ChannelInbound, "handle">, jobs: Pick<ChannelJobs, "enqueue">, config: Pick<KernelConfigService, "get">, log: Pick<Logger, "error" | "warn" | "info">, txHost: TransactionHost,
|
|
100
|
+
inbound: Pick<ChannelInbound, "handle">, jobs: Pick<ChannelJobs, "enqueue" | "resumeConnection">, config: Pick<KernelConfigService, "get">, log: Pick<Logger, "error" | "warn" | "info">, txHost: TransactionHost,
|
|
101
101
|
/** Pauses the task of a scheduled run whose chat is gone (scheduled tasks spec §8). */
|
|
102
102
|
schedules: Pick<ScheduledTasksService, "pauseFor">);
|
|
103
103
|
/**
|
|
@@ -112,7 +112,10 @@ export declare class ChannelOutbound {
|
|
|
112
112
|
/** Stops: the jobs in flight are handed back at their next checkpoint, the worker closes. */
|
|
113
113
|
close(): Promise<void>;
|
|
114
114
|
private requeue;
|
|
115
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* One job of the channel queue (the `@Processor` calls it): a delivery, an inbound event or a
|
|
117
|
+
* connection's catch-up.
|
|
118
|
+
*/
|
|
116
119
|
process(job: Job<ChannelJobData>, token: string | undefined): Promise<void>;
|
|
117
120
|
/** 本副本没有加载连接的插件:推迟重试(不消耗重试次数),排队超过上限才放弃。 */
|
|
118
121
|
private pluginPending;
|
|
@@ -28,7 +28,7 @@ import { ToolRegistry } from "../tools/registry.js";
|
|
|
28
28
|
import { approvalCard, cardMessage, connectUrl, conversationUrl, questionCard, webBase, } from "./cards.js";
|
|
29
29
|
import { ChannelConnections } from "./connections.service.js";
|
|
30
30
|
import { ChannelInbound } from "./inbound.js";
|
|
31
|
-
import { ChannelJobs, INBOUND_JOB, idempotencyKey, } from "./jobs.js";
|
|
31
|
+
import { ChannelJobs, INBOUND_JOB, idempotencyKey, RESUME_JOB, } from "./jobs.js";
|
|
32
32
|
import { phrase } from "./phrases.js";
|
|
33
33
|
import { insertThread } from "./threads.js";
|
|
34
34
|
// The queue, job names, ids and priorities live with the producer (jobs.ts)
|
|
@@ -233,12 +233,19 @@ let ChannelOutbound = class ChannelOutbound {
|
|
|
233
233
|
await job.moveToDelayed(Date.now() + delayMs, token);
|
|
234
234
|
throw new DelayedError();
|
|
235
235
|
}
|
|
236
|
-
/**
|
|
236
|
+
/**
|
|
237
|
+
* One job of the channel queue (the `@Processor` calls it): a delivery, an inbound event or a
|
|
238
|
+
* connection's catch-up.
|
|
239
|
+
*/
|
|
237
240
|
async process(job, token) {
|
|
238
241
|
if (this.stopping.signal.aborted)
|
|
239
242
|
return this.requeue(job, token, 0);
|
|
240
243
|
if (job.name === INBOUND_JOB)
|
|
241
244
|
return this.processInbound(job, token);
|
|
245
|
+
// a connection's catch-up: a failed add fails the job, which BullMQ retries
|
|
246
|
+
if (job.name === RESUME_JOB) {
|
|
247
|
+
return this.jobs.resumeConnection(job.data.connectionId);
|
|
248
|
+
}
|
|
242
249
|
return this.deliver(job, token);
|
|
243
250
|
}
|
|
244
251
|
/** 本副本没有加载连接的插件:推迟重试(不消耗重试次数),排队超过上限才放弃。 */
|
|
@@ -312,8 +312,10 @@ export declare class ConversationsController {
|
|
|
312
312
|
}>;
|
|
313
313
|
/**
|
|
314
314
|
* Stops the current run (the conversation's owner): 202 `{ runId }`; nothing active → 409
|
|
315
|
-
* `run.not_active`. On a root conversation
|
|
316
|
-
*
|
|
315
|
+
* `run.not_active`. On a root conversation every active sub-agent of its tree stops with its
|
|
316
|
+
* run, and nothing they deliver starts the root again until its next run (agent map spec §6,
|
|
317
|
+
* changed in acceptance 2026-11-13); `runId` is the stopped run (the latest when only sub-agents
|
|
318
|
+
* ran). On a sub-agent's conversation (the root's owner, at any depth) the
|
|
317
319
|
* sub-agent stops with every sub-agent below it, and `runId` is the sub-agent's latest run,
|
|
318
320
|
* which may already be finished when only its descendants were working (agent map spec §6).
|
|
319
321
|
*/
|
|
@@ -456,8 +456,10 @@ let ConversationsController = class ConversationsController {
|
|
|
456
456
|
}
|
|
457
457
|
/**
|
|
458
458
|
* Stops the current run (the conversation's owner): 202 `{ runId }`; nothing active → 409
|
|
459
|
-
* `run.not_active`. On a root conversation
|
|
460
|
-
*
|
|
459
|
+
* `run.not_active`. On a root conversation every active sub-agent of its tree stops with its
|
|
460
|
+
* run, and nothing they deliver starts the root again until its next run (agent map spec §6,
|
|
461
|
+
* changed in acceptance 2026-11-13); `runId` is the stopped run (the latest when only sub-agents
|
|
462
|
+
* ran). On a sub-agent's conversation (the root's owner, at any depth) the
|
|
461
463
|
* sub-agent stops with every sub-agent below it, and `runId` is the sub-agent's latest run,
|
|
462
464
|
* which may already be finished when only its descendants were working (agent map spec §6).
|
|
463
465
|
*/
|
|
@@ -537,10 +537,43 @@ export declare class ConversationsService {
|
|
|
537
537
|
* 等人处理的运行没有 worker:直接取消等待并写终态。其余经 Redis 广播,运行所在的任意副本都会收到;
|
|
538
538
|
* 运行已是孤儿(没有任何 worker 在执行)时直接写终态。
|
|
539
539
|
*
|
|
540
|
-
* A root conversation's stop stops
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
540
|
+
* A root conversation's stop stops everything below it too (agent map spec §6, changed in
|
|
541
|
+
* acceptance 2026-11-13: after a person stops, the conversation stays stopped). In order:
|
|
542
|
+
* 1. `tree_stopped_at` is set on the root under its row lock: by the stopped run's terminal
|
|
543
|
+
* transaction (a waiting, queued or orphaned run), before the stop is asked for (a run a
|
|
544
|
+
* worker holds, which is active until then), or on its own (nothing of the root ran). From
|
|
545
|
+
* then on no sub-agent delivery wakes the root (`wakeDecisionIn`) and no
|
|
546
|
+
* sub-agent of the tree starts a run by a wake or a parent's message (`RunQueue.childWakeIn`),
|
|
547
|
+
* until the root next starts a run (a person's message, the queue, a background Bash task's
|
|
548
|
+
* finish), which clears it. A child that finishes just before or after the stop, `completed`
|
|
549
|
+
* or `aborted`, thus never wakes the root; its delivery stays in the log and reaches the
|
|
550
|
+
* model with the next run;
|
|
551
|
+
* 2. (with 1) the root's own active run stops as before ({@link abortRootRun});
|
|
552
|
+
* 3. every active sub-agent run of the tree stops (`RunControl.stopTree`, as the agent map's stop
|
|
553
|
+
* and the deletion do), with the background tasks of every sub-agent. A sub-agent the root's
|
|
554
|
+
* run dispatched after that read is stopped when the root run ends
|
|
555
|
+
* (`Waits.stopDescendantsOfStopped`).
|
|
556
|
+
* The root's own background Bash tasks keep running: they are the conversation's tasks, stopped
|
|
557
|
+
* with `TaskStop` or the task list. `run.not_active` when neither the root nor its tree had an
|
|
558
|
+
* active run (the stop still holds the tree's wakes); else the root's run id (its latest when only
|
|
559
|
+
* sub-agents ran).
|
|
560
|
+
*
|
|
561
|
+
* A sub-agent's conversation (its owner, from the agent map) stops that sub-agent with every
|
|
562
|
+
* descendant (`RunControl.stopTree`); `run.not_active` when nothing in that subtree runs, else
|
|
563
|
+
* the sub-agent's latest run id. Its `aborted` finish never wakes its parent.
|
|
544
564
|
*/
|
|
545
565
|
abort(orgId: string, conversationId: string, accountId: string): Promise<string>;
|
|
566
|
+
/**
|
|
567
|
+
* Marks a person's stop of the root's tree (`tree_stopped_at`, see {@link abort}); committed
|
|
568
|
+
* before any stop, so every wake decided after it (under the root's row lock) sees it.
|
|
569
|
+
*/
|
|
570
|
+
private holdTree;
|
|
571
|
+
/**
|
|
572
|
+
* Stops a root conversation's own active run (queued / running / waiting) and returns its id;
|
|
573
|
+
* null when none is active. Its sub-agents are the caller's. The tree's hold
|
|
574
|
+
* (`tree_stopped_at`) is written by the stopped run's terminal transaction (`RunFinish.finishIn`
|
|
575
|
+
* for a run a person stopped), in the lock order every stop and decision takes (conversation,
|
|
576
|
+
* decided row, run); a run a worker holds is marked before its stop is asked for as well.
|
|
577
|
+
*/
|
|
578
|
+
private abortRootRun;
|
|
546
579
|
}
|
|
@@ -342,13 +342,11 @@ let ConversationsService = class ConversationsService {
|
|
|
342
342
|
* log, runs and trajectory stay (super admins still see them).
|
|
343
343
|
*/
|
|
344
344
|
async remove(orgId, conversationId, accountId) {
|
|
345
|
-
await this.writable(orgId, conversationId, accountId);
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (!(e instanceof ApiError && e.code === "run.not_active"))
|
|
351
|
-
throw e;
|
|
345
|
+
const conv = await this.writable(orgId, conversationId, accountId);
|
|
346
|
+
// the root's own run only: the tree stops below, after the mark (a sub-agent's conversation
|
|
347
|
+
// is read-only, so `writable` refused it)
|
|
348
|
+
if (conv.parentConversationId === null) {
|
|
349
|
+
await this.abortRootRun(conversationId, accountId);
|
|
352
350
|
}
|
|
353
351
|
await this.markDeleted(orgId, conversationId, accountId);
|
|
354
352
|
// after the mark, so no finish or delivery of the stops below can start a run on the deleted
|
|
@@ -981,25 +979,77 @@ let ConversationsService = class ConversationsService {
|
|
|
981
979
|
* 等人处理的运行没有 worker:直接取消等待并写终态。其余经 Redis 广播,运行所在的任意副本都会收到;
|
|
982
980
|
* 运行已是孤儿(没有任何 worker 在执行)时直接写终态。
|
|
983
981
|
*
|
|
984
|
-
* A root conversation's stop stops
|
|
985
|
-
*
|
|
986
|
-
*
|
|
987
|
-
*
|
|
982
|
+
* A root conversation's stop stops everything below it too (agent map spec §6, changed in
|
|
983
|
+
* acceptance 2026-11-13: after a person stops, the conversation stays stopped). In order:
|
|
984
|
+
* 1. `tree_stopped_at` is set on the root under its row lock: by the stopped run's terminal
|
|
985
|
+
* transaction (a waiting, queued or orphaned run), before the stop is asked for (a run a
|
|
986
|
+
* worker holds, which is active until then), or on its own (nothing of the root ran). From
|
|
987
|
+
* then on no sub-agent delivery wakes the root (`wakeDecisionIn`) and no
|
|
988
|
+
* sub-agent of the tree starts a run by a wake or a parent's message (`RunQueue.childWakeIn`),
|
|
989
|
+
* until the root next starts a run (a person's message, the queue, a background Bash task's
|
|
990
|
+
* finish), which clears it. A child that finishes just before or after the stop, `completed`
|
|
991
|
+
* or `aborted`, thus never wakes the root; its delivery stays in the log and reaches the
|
|
992
|
+
* model with the next run;
|
|
993
|
+
* 2. (with 1) the root's own active run stops as before ({@link abortRootRun});
|
|
994
|
+
* 3. every active sub-agent run of the tree stops (`RunControl.stopTree`, as the agent map's stop
|
|
995
|
+
* and the deletion do), with the background tasks of every sub-agent. A sub-agent the root's
|
|
996
|
+
* run dispatched after that read is stopped when the root run ends
|
|
997
|
+
* (`Waits.stopDescendantsOfStopped`).
|
|
998
|
+
* The root's own background Bash tasks keep running: they are the conversation's tasks, stopped
|
|
999
|
+
* with `TaskStop` or the task list. `run.not_active` when neither the root nor its tree had an
|
|
1000
|
+
* active run (the stop still holds the tree's wakes); else the root's run id (its latest when only
|
|
1001
|
+
* sub-agents ran).
|
|
1002
|
+
*
|
|
1003
|
+
* A sub-agent's conversation (its owner, from the agent map) stops that sub-agent with every
|
|
1004
|
+
* descendant (`RunControl.stopTree`); `run.not_active` when nothing in that subtree runs, else
|
|
1005
|
+
* the sub-agent's latest run id. Its `aborted` finish never wakes its parent.
|
|
988
1006
|
*/
|
|
989
1007
|
async abort(orgId, conversationId, accountId) {
|
|
990
1008
|
const conv = await this.get(orgId, conversationId, accountId);
|
|
1009
|
+
const latest = () => this.db
|
|
1010
|
+
.selectFrom("runs")
|
|
1011
|
+
.select("id")
|
|
1012
|
+
.where("conversation_id", "=", conversationId)
|
|
1013
|
+
.orderBy("created_at", "desc")
|
|
1014
|
+
.executeTakeFirst();
|
|
991
1015
|
if (conv.parentConversationId !== null) {
|
|
992
|
-
const
|
|
993
|
-
.selectFrom("runs")
|
|
994
|
-
.select("id")
|
|
995
|
-
.where("conversation_id", "=", conversationId)
|
|
996
|
-
.orderBy("created_at", "desc")
|
|
997
|
-
.executeTakeFirst();
|
|
1016
|
+
const last = await latest();
|
|
998
1017
|
const n = await this.runs.stopTree(conversationId, accountId);
|
|
999
|
-
if (n === 0 || !
|
|
1018
|
+
if (n === 0 || !last)
|
|
1000
1019
|
throw new ApiError("run.not_active", "No active run to abort");
|
|
1001
|
-
return
|
|
1020
|
+
return last.id;
|
|
1002
1021
|
}
|
|
1022
|
+
const stopped = await this.abortRootRun(conversationId, accountId);
|
|
1023
|
+
// nothing of the root's ran: the hold is the stop's own write
|
|
1024
|
+
if (stopped === null)
|
|
1025
|
+
await this.holdTree(conversationId);
|
|
1026
|
+
const n = await this.runs.stopTree(conversationId, accountId, { includeSelf: false });
|
|
1027
|
+
if (stopped !== null)
|
|
1028
|
+
return stopped;
|
|
1029
|
+
const last = await latest();
|
|
1030
|
+
if (n === 0 || !last)
|
|
1031
|
+
throw new ApiError("run.not_active", "No active run to abort");
|
|
1032
|
+
return last.id;
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Marks a person's stop of the root's tree (`tree_stopped_at`, see {@link abort}); committed
|
|
1036
|
+
* before any stop, so every wake decided after it (under the root's row lock) sees it.
|
|
1037
|
+
*/
|
|
1038
|
+
async holdTree(conversationId) {
|
|
1039
|
+
await this.db
|
|
1040
|
+
.updateTable("conversations")
|
|
1041
|
+
.set({ tree_stopped_at: sql `now()` })
|
|
1042
|
+
.where("id", "=", conversationId)
|
|
1043
|
+
.execute();
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Stops a root conversation's own active run (queued / running / waiting) and returns its id;
|
|
1047
|
+
* null when none is active. Its sub-agents are the caller's. The tree's hold
|
|
1048
|
+
* (`tree_stopped_at`) is written by the stopped run's terminal transaction (`RunFinish.finishIn`
|
|
1049
|
+
* for a run a person stopped), in the lock order every stop and decision takes (conversation,
|
|
1050
|
+
* decided row, run); a run a worker holds is marked before its stop is asked for as well.
|
|
1051
|
+
*/
|
|
1052
|
+
async abortRootRun(conversationId, accountId) {
|
|
1003
1053
|
const run = await this.db
|
|
1004
1054
|
.selectFrom("runs")
|
|
1005
1055
|
.select(["id", "status"])
|
|
@@ -1008,7 +1058,7 @@ let ConversationsService = class ConversationsService {
|
|
|
1008
1058
|
.orderBy("created_at", "desc")
|
|
1009
1059
|
.executeTakeFirst();
|
|
1010
1060
|
if (!run)
|
|
1011
|
-
|
|
1061
|
+
return null;
|
|
1012
1062
|
const runs = this.runs;
|
|
1013
1063
|
// 等待中:直接结束;没成功(刚被续跑、已回到 queued)则按下面的常规路径中止。
|
|
1014
1064
|
// the stop's terminal transaction owed the ensure-run backstop; the direct call starts what
|
|
@@ -1031,6 +1081,10 @@ let ConversationsService = class ConversationsService {
|
|
|
1031
1081
|
await runs.ensureRunAfterCommit(conversationId);
|
|
1032
1082
|
return run.id;
|
|
1033
1083
|
}
|
|
1084
|
+
// a run a worker holds ends later: the hold is written now, before the stop is asked for,
|
|
1085
|
+
// while the run is still active (no wake can start the root before it), and again by its
|
|
1086
|
+
// terminal transaction
|
|
1087
|
+
await this.holdTree(conversationId);
|
|
1034
1088
|
await runs.requestAbort(run.id, accountId);
|
|
1035
1089
|
return run.id;
|
|
1036
1090
|
}
|
|
@@ -1053,6 +1107,9 @@ __decorate([
|
|
|
1053
1107
|
__decorate([
|
|
1054
1108
|
Transactional({ propagation: "mandatory" })
|
|
1055
1109
|
], ConversationsService.prototype, "bindIn", null);
|
|
1110
|
+
__decorate([
|
|
1111
|
+
Transactional()
|
|
1112
|
+
], ConversationsService.prototype, "holdTree", null);
|
|
1056
1113
|
ConversationsService = __decorate([
|
|
1057
1114
|
Injectable(),
|
|
1058
1115
|
__param(0, Inject(KernelDb)),
|
|
@@ -737,6 +737,12 @@ export declare const ConversationEventSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
737
737
|
byAccountId: z.ZodNullable<z.ZodString>;
|
|
738
738
|
reason: z.ZodOptional<z.ZodString>;
|
|
739
739
|
}, z.core.$strip>;
|
|
740
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
741
|
+
type: z.ZodLiteral<"approval_updated">;
|
|
742
|
+
payload: z.ZodObject<{
|
|
743
|
+
approvalId: z.ZodString;
|
|
744
|
+
callId: z.ZodString;
|
|
745
|
+
}, z.core.$strip>;
|
|
740
746
|
}, z.core.$strip>, z.ZodObject<{
|
|
741
747
|
type: z.ZodLiteral<"question_asked">;
|
|
742
748
|
payload: z.ZodObject<{
|
|
@@ -622,6 +622,12 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
|
|
|
622
622
|
*/
|
|
623
623
|
reason: z.string().max(APPROVAL_DENY_REASON_MAX_CHARS).optional(),
|
|
624
624
|
})),
|
|
625
|
+
/**
|
|
626
|
+
* A still-pending approval changed without a decision: a network ask's 「这台设备一直允许」 whose
|
|
627
|
+
* device change was not applied or not confirmed (`detail.attempt`, transactional outbox spec
|
|
628
|
+
* §7). Display only, never model-visible: the page reads the approval again.
|
|
629
|
+
*/
|
|
630
|
+
ev("approval_updated", z.object({ approvalId: z.string(), callId: z.string() })),
|
|
625
631
|
/**
|
|
626
632
|
* 工具调用需要用户输入(`AskUserQuestion`、插件工具的 `input_required`、MCP elicitation):输入请求已建,
|
|
627
633
|
* run 暂停等待回答。界面据此显示表单卡片(不进模型请求)。
|
|
@@ -268,6 +268,11 @@ export interface ConversationsTable {
|
|
|
268
268
|
* (the column's `web` default is not relied on: a forgotten source must not read as web).
|
|
269
269
|
*/
|
|
270
270
|
source: ColumnType<ConversationSource, ConversationSource, never>;
|
|
271
|
+
/**
|
|
272
|
+
* 0125: when a person stopped this root conversation and its sub-agent tree; cleared when the
|
|
273
|
+
* root next starts a run. While set the tree's wakes are held (agent map spec §6).
|
|
274
|
+
*/
|
|
275
|
+
tree_stopped_at: ColumnType<Date | null, Date | string | null | undefined, Date | string | null>;
|
|
271
276
|
}
|
|
272
277
|
/** 0060: an org's order of file-preview@1 previewers (UI refresh B Task 11). */
|
|
273
278
|
export interface FilePreviewersTable {
|
|
@@ -345,7 +350,11 @@ export interface RunsTable {
|
|
|
345
350
|
waiting_since: TsNullable;
|
|
346
351
|
/** 0012:累计等待时长(毫秒),不计入运行时长。 */
|
|
347
352
|
waited_ms: Int8;
|
|
348
|
-
/**
|
|
353
|
+
/**
|
|
354
|
+
* 0012: the run's job id when it is not the trigger's `runJobId`: a resume's
|
|
355
|
+
* (`resumeJobId`), or `<runJobId>-<n>` for a later run of the same trigger (R34); null: the
|
|
356
|
+
* trigger's first run, under `runJobId`.
|
|
357
|
+
*/
|
|
349
358
|
job_id: string | null;
|
|
350
359
|
/** 0012:最近一次(续跑)回到 queued 的时刻。 */
|
|
351
360
|
queued_at: TsNullable;
|
|
@@ -112,7 +112,8 @@ export declare const RECENT_POLICY_CHANGES = 10;
|
|
|
112
112
|
/**
|
|
113
113
|
* One sandbox change of a device, as its owner sees it on the device page (§10.4):
|
|
114
114
|
* - `policy` (`device.policy_changed`): a local policy change sent to the machine (`web`, `im`),
|
|
115
|
-
* or made on the machine by its owner (`device`, `capybara-device policy ...`);
|
|
115
|
+
* or made on the machine by its owner (`device`, `capybara-device policy ...`); one sent whose
|
|
116
|
+
* answer never came (`device.policy_unknown`) or that failed (`device.policy_failed`) too;
|
|
116
117
|
* - `tier` (`device.tier_changed`): a cloud device's tier set by its owner;
|
|
117
118
|
* - `isolation` (`device.isolation_reported`): the machine reported running without a sandbox,
|
|
118
119
|
* or with one again.
|
|
@@ -141,8 +142,14 @@ export interface DeviceSandboxChangeView {
|
|
|
141
142
|
by: string | null;
|
|
142
143
|
/** `device`: made or reported on the machine itself (a `policy` change, an `isolation`). */
|
|
143
144
|
via: "web" | "im" | "device";
|
|
144
|
-
/**
|
|
145
|
-
|
|
145
|
+
/**
|
|
146
|
+
* What became of a `policy` change sent to the machine: `applied` (also every change made or
|
|
147
|
+
* reported on the machine, and every tier / isolation row); `unknown`, its answer never came
|
|
148
|
+
* (`device.policy_unknown`: an `applied` row follows when the machine reports it); `failed`,
|
|
149
|
+
* the machine refused it, never received it or stopped while applying it
|
|
150
|
+
* (`device.policy_failed`).
|
|
151
|
+
*/
|
|
152
|
+
outcome: "applied" | "unknown" | "failed";
|
|
146
153
|
}
|
|
147
154
|
/** A change of a local device's policy by its owner (`POST /devices/:id/policy`). */
|
|
148
155
|
export interface DevicePolicyChange {
|
|
@@ -309,7 +316,7 @@ export declare class DevicesService {
|
|
|
309
316
|
* refused up front with `device.policy_refused` when the machine last reported
|
|
310
317
|
* `remoteGrants: "off"` (otherwise the machine decides); narrowing is always sent, and
|
|
311
318
|
* `isolation` is only ever `sandbox`. The machine offline → `device.offline`; a cloud device or a
|
|
312
|
-
* program without the `
|
|
319
|
+
* program without the `policyOpId` feature → `device.policy_unsupported`; the machine's refusal →
|
|
313
320
|
* `device.policy_refused` with its message. Callers are human sessions only (no personal access
|
|
314
321
|
* token, no service token, no Platform op).
|
|
315
322
|
*/
|
|
@@ -394,8 +401,9 @@ export declare class DevicesService {
|
|
|
394
401
|
private views;
|
|
395
402
|
/**
|
|
396
403
|
* The last {@link RECENT_POLICY_CHANGES} sandbox changes of each device in the org (audit rows
|
|
397
|
-
* `device.policy_changed`, `device.
|
|
398
|
-
* with the deciding account's
|
|
404
|
+
* `device.policy_changed`, `device.policy_unknown`, `device.policy_failed`,
|
|
405
|
+
* `device.tier_changed`, `device.isolation_reported`), newest first, with the deciding account's
|
|
406
|
+
* display name.
|
|
399
407
|
*/
|
|
400
408
|
private recentSandboxChanges;
|
|
401
409
|
/**
|
|
@@ -436,7 +436,7 @@ let DevicesService = class DevicesService {
|
|
|
436
436
|
* refused up front with `device.policy_refused` when the machine last reported
|
|
437
437
|
* `remoteGrants: "off"` (otherwise the machine decides); narrowing is always sent, and
|
|
438
438
|
* `isolation` is only ever `sandbox`. The machine offline → `device.offline`; a cloud device or a
|
|
439
|
-
* program without the `
|
|
439
|
+
* program without the `policyOpId` feature → `device.policy_unsupported`; the machine's refusal →
|
|
440
440
|
* `device.policy_refused` with its message. Callers are human sessions only (no personal access
|
|
441
441
|
* token, no service token, no Platform op).
|
|
442
442
|
*/
|
|
@@ -452,7 +452,7 @@ let DevicesService = class DevicesService {
|
|
|
452
452
|
const widens = (change.add?.length ?? 0) > 0 || (known !== null && tierWidens(known.tier, change.tier));
|
|
453
453
|
// only a known "off" is refused here; an unknown summary, a machine that cannot take the op
|
|
454
454
|
// or none at all are left to apply (the machine decides, or unsupported / offline)
|
|
455
|
-
const canApply = summary?.features.includes("
|
|
455
|
+
const canApply = summary?.features.includes("policyOpId") === true;
|
|
456
456
|
if (widens && canApply && known?.remoteGrants === "off") {
|
|
457
457
|
throw new ApiError("device.policy_refused", remoteGrantsOffMessage(change));
|
|
458
458
|
}
|
|
@@ -893,8 +893,9 @@ let DevicesService = class DevicesService {
|
|
|
893
893
|
}
|
|
894
894
|
/**
|
|
895
895
|
* The last {@link RECENT_POLICY_CHANGES} sandbox changes of each device in the org (audit rows
|
|
896
|
-
* `device.policy_changed`, `device.
|
|
897
|
-
* with the deciding account's
|
|
896
|
+
* `device.policy_changed`, `device.policy_unknown`, `device.policy_failed`,
|
|
897
|
+
* `device.tier_changed`, `device.isolation_reported`), newest first, with the deciding account's
|
|
898
|
+
* display name.
|
|
898
899
|
*/
|
|
899
900
|
async recentSandboxChanges(orgId, deviceIds) {
|
|
900
901
|
const out = new Map();
|
|
@@ -1040,6 +1041,8 @@ const limitsView = (l) => ({
|
|
|
1040
1041
|
/** The audit actions a device's recent sandbox changes list. */
|
|
1041
1042
|
const SANDBOX_CHANGE_ACTIONS = [
|
|
1042
1043
|
"device.policy_changed",
|
|
1044
|
+
"device.policy_unknown",
|
|
1045
|
+
"device.policy_failed",
|
|
1043
1046
|
"device.tier_changed",
|
|
1044
1047
|
"device.isolation_reported",
|
|
1045
1048
|
];
|
|
@@ -1059,10 +1062,16 @@ function sandboxChangeOf(action, at, d, by) {
|
|
|
1059
1062
|
by,
|
|
1060
1063
|
};
|
|
1061
1064
|
if (action === "device.tier_changed") {
|
|
1062
|
-
return {
|
|
1065
|
+
return {
|
|
1066
|
+
...base,
|
|
1067
|
+
kind: "tier",
|
|
1068
|
+
tier: { to: tierOrNull(d.to) },
|
|
1069
|
+
via: "web",
|
|
1070
|
+
outcome: "applied",
|
|
1071
|
+
};
|
|
1063
1072
|
}
|
|
1064
1073
|
if (action === "device.isolation_reported") {
|
|
1065
|
-
return { ...base, kind: "isolation", isolation, via: "device",
|
|
1074
|
+
return { ...base, kind: "isolation", isolation, via: "device", outcome: "applied" };
|
|
1066
1075
|
}
|
|
1067
1076
|
return {
|
|
1068
1077
|
...base,
|
|
@@ -1079,7 +1088,11 @@ function sandboxChangeOf(action, at, d, by) {
|
|
|
1079
1088
|
: null,
|
|
1080
1089
|
remoteGrants: d.remoteGrants === "owner" || d.remoteGrants === "off" ? d.remoteGrants : null,
|
|
1081
1090
|
via: d.via === "im" ? "im" : d.via === "device" ? "device" : "web",
|
|
1082
|
-
|
|
1091
|
+
outcome: action === "device.policy_unknown"
|
|
1092
|
+
? "unknown"
|
|
1093
|
+
: action === "device.policy_failed"
|
|
1094
|
+
? "failed"
|
|
1095
|
+
: "applied",
|
|
1083
1096
|
};
|
|
1084
1097
|
}
|
|
1085
1098
|
/**
|
|
@@ -21,7 +21,7 @@ import { OrgLockModule } from "../orgs/org-lock.js";
|
|
|
21
21
|
import { ProvidersModule } from "../runtime/providers.js";
|
|
22
22
|
import { RuntimeModule } from "../runtime/runtime.module.js";
|
|
23
23
|
import { EvalBudgetModule } from "./budget.js";
|
|
24
|
-
import { EVAL_QUEUE_NAME, EvalJobs } from "./jobs.js";
|
|
24
|
+
import { EVAL_CASE_JOB, EVAL_QUEUE_NAME, EvalJobs } from "./jobs.js";
|
|
25
25
|
import { EvalReplaySeeder } from "./run-replay.js";
|
|
26
26
|
import { EvalRunsService } from "./runs.service.js";
|
|
27
27
|
import { EvalSetsService } from "./sets.service.js";
|
|
@@ -66,6 +66,10 @@ let EvalsProcessor = class EvalsProcessor extends WorkerHost {
|
|
|
66
66
|
this.evals = evals;
|
|
67
67
|
}
|
|
68
68
|
process(job, token) {
|
|
69
|
+
// the queue's one job name, owed by an eval run's start
|
|
70
|
+
if (job.name !== EVAL_CASE_JOB) {
|
|
71
|
+
return Promise.reject(new Error(`unknown eval job "${job.name}"`));
|
|
72
|
+
}
|
|
69
73
|
return runJob(EVAL_QUEUE_NAME, job, () => this.evals.process(job, token));
|
|
70
74
|
}
|
|
71
75
|
failed(job, err) {
|
package/dist/evals/jobs.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { OutboxService } from "../infra/outbox/outbox.service.js";
|
|
2
2
|
/** 评测队列的 BullMQ 队列名(不含 `:`,C7)。 */
|
|
3
3
|
export declare const EVAL_QUEUE_NAME = "cap-kernel-evals";
|
|
4
|
+
/** The one job name on {@link EVAL_QUEUE_NAME}: one case of an eval run. */
|
|
5
|
+
export declare const EVAL_CASE_JOB = "eval-case";
|
|
4
6
|
/** 每个用例一个任务(C7;不含 `:`)。 */
|
|
5
7
|
export declare const evalJobId: (runId: string, caseId: string) => string;
|
|
6
8
|
/** 评测任务的数据。 */
|
|
@@ -10,11 +12,17 @@ export interface EvalJobData {
|
|
|
10
12
|
orgId: string;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
|
-
* The producer of `cap-kernel-evals
|
|
14
|
-
*
|
|
15
|
+
* The producer of `cap-kernel-evals`: owes one {@link EVAL_CASE_JOB} per case (under
|
|
16
|
+
* {@link evalJobId}) in the transaction that writes the run and its pending results (transactional
|
|
17
|
+
* outbox spec §3), so a run never waits for jobs that were never added. The eval worker consumes
|
|
18
|
+
* them on the worker role. A leaf module: the runs service and the worker import it.
|
|
15
19
|
*/
|
|
16
20
|
export declare class EvalJobs {
|
|
17
|
-
private readonly
|
|
18
|
-
constructor(
|
|
19
|
-
|
|
21
|
+
private readonly outbox;
|
|
22
|
+
constructor(outbox: Pick<OutboxService, "addMany">);
|
|
23
|
+
/**
|
|
24
|
+
* Owes the case jobs in the caller's transaction (throws outside one), in one insert: a set has
|
|
25
|
+
* up to `evals.maxCasesPerSet` cases, written while the start holds the org lock.
|
|
26
|
+
*/
|
|
27
|
+
owe(jobs: readonly EvalJobData[]): Promise<void>;
|
|
20
28
|
}
|
package/dist/evals/jobs.js
CHANGED
|
@@ -7,39 +7,41 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
8
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
9
|
};
|
|
10
|
-
import { getQueueToken } from "@nestjs/bullmq";
|
|
11
10
|
import { Inject, Injectable } from "@nestjs/common";
|
|
11
|
+
import { OutboxService } from "../infra/outbox/outbox.service.js";
|
|
12
12
|
/** 评测队列的 BullMQ 队列名(不含 `:`,C7)。 */
|
|
13
13
|
export const EVAL_QUEUE_NAME = "cap-kernel-evals";
|
|
14
|
+
/** The one job name on {@link EVAL_QUEUE_NAME}: one case of an eval run. */
|
|
15
|
+
export const EVAL_CASE_JOB = "eval-case";
|
|
14
16
|
/** 每个用例一个任务(C7;不含 `:`)。 */
|
|
15
17
|
export const evalJobId = (runId, caseId) => `eval-${runId}-${caseId}`;
|
|
16
18
|
/**
|
|
17
|
-
* The producer of `cap-kernel-evals
|
|
18
|
-
*
|
|
19
|
+
* The producer of `cap-kernel-evals`: owes one {@link EVAL_CASE_JOB} per case (under
|
|
20
|
+
* {@link evalJobId}) in the transaction that writes the run and its pending results (transactional
|
|
21
|
+
* outbox spec §3), so a run never waits for jobs that were never added. The eval worker consumes
|
|
22
|
+
* them on the worker role. A leaf module: the runs service and the worker import it.
|
|
19
23
|
*/
|
|
20
24
|
let EvalJobs = class EvalJobs {
|
|
21
|
-
|
|
22
|
-
constructor(
|
|
23
|
-
this.
|
|
25
|
+
outbox;
|
|
26
|
+
constructor(outbox) {
|
|
27
|
+
this.outbox = outbox;
|
|
24
28
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Owes the case jobs in the caller's transaction (throws outside one), in one insert: a set has
|
|
31
|
+
* up to `evals.maxCasesPerSet` cases, written while the start holds the org lock.
|
|
32
|
+
*/
|
|
33
|
+
async owe(jobs) {
|
|
34
|
+
await this.outbox.addMany(jobs.map((data) => ({
|
|
35
|
+
queue: EVAL_QUEUE_NAME,
|
|
36
|
+
name: EVAL_CASE_JOB,
|
|
37
|
+
jobId: evalJobId(data.runId, data.caseId),
|
|
28
38
|
data,
|
|
29
|
-
|
|
30
|
-
jobId: evalJobId(data.runId, data.caseId),
|
|
31
|
-
// 处理函数外的暂时故障(PG / Redis):指数退避重试,不让整个用例一次失败。
|
|
32
|
-
// 结果按认领号写入,重做不会重复写结果
|
|
33
|
-
attempts: 3,
|
|
34
|
-
backoff: { type: "exponential", delay: 1000 },
|
|
35
|
-
removeOnComplete: true,
|
|
36
|
-
removeOnFail: 1000,
|
|
37
|
-
},
|
|
39
|
+
orgId: data.orgId,
|
|
38
40
|
})));
|
|
39
41
|
}
|
|
40
42
|
};
|
|
41
43
|
EvalJobs = __decorate([
|
|
42
44
|
Injectable(),
|
|
43
|
-
__param(0, Inject(
|
|
45
|
+
__param(0, Inject(OutboxService))
|
|
44
46
|
], EvalJobs);
|
|
45
47
|
export { EvalJobs };
|
|
@@ -145,7 +145,7 @@ export declare const RESULTS_ASOF_OVERLAP_MS = 15000;
|
|
|
145
145
|
export declare class EvalRunsService {
|
|
146
146
|
readonly txHost: TransactionHost;
|
|
147
147
|
private readonly d;
|
|
148
|
-
constructor(kdb: KernelDb, orgLock: OrgLock, locks: LockService, config: KernelConfigService, registry: ProviderRegistry, prices: PricesService, settings: SettingsService,
|
|
148
|
+
constructor(kdb: KernelDb, orgLock: OrgLock, locks: LockService, config: KernelConfigService, registry: ProviderRegistry, prices: PricesService, settings: SettingsService, jobs: EvalJobs, budget: EvalBudget, purposes: ModelPurposesService, txHost: TransactionHost);
|
|
149
149
|
private liveSet;
|
|
150
150
|
/**
|
|
151
151
|
* 解析模型;不存在 / 不可用 → `eval.candidate_invalid`(供应商列表暂时失败照常抛出)。
|
|
@@ -187,8 +187,9 @@ export declare class EvalRunsService {
|
|
|
187
187
|
private tokenCaps;
|
|
188
188
|
/**
|
|
189
189
|
* 启动:校验 token 上限([1, `evals.maxTokenCap`],缺省 `evals.defaultTokenCap`)、候选、评委用途;
|
|
190
|
-
* 没有可回放的 → `eval.nothing_to_replay
|
|
191
|
-
*
|
|
190
|
+
* 没有可回放的 → `eval.nothing_to_replay`。One transaction writes the run row and its pending
|
|
191
|
+
* results and owes one case job per valid case (transactional outbox spec §3): the outbox adds
|
|
192
|
+
* them after the commit, so a run never waits for a job that was never added.
|
|
192
193
|
*/
|
|
193
194
|
start(orgId: string, actor: string, setId: string, input: {
|
|
194
195
|
mode: "step" | "run";
|
|
@@ -196,10 +197,8 @@ export declare class EvalRunsService {
|
|
|
196
197
|
judge: boolean;
|
|
197
198
|
tokenCap?: number;
|
|
198
199
|
}, actorTokenId?: string, auditDetail?: Record<string, unknown>): Promise<EvalRunView>;
|
|
199
|
-
/** The run row and its
|
|
200
|
+
/** The run row, its pending results and its owed case jobs, in a transaction of their own. */
|
|
200
201
|
private startIn;
|
|
201
|
-
/** The enqueue failed: the run is `failed`, its pending results `skipped`. */
|
|
202
|
-
private failStartIn;
|
|
203
202
|
/** 各运行的汇总(只算已结束的)。 */
|
|
204
203
|
private summaries;
|
|
205
204
|
private static toView;
|