@capybara-hq/kernel 0.1.0-rc.24 → 0.1.0-rc.26
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/admin.module.js +2 -0
- package/dist/admin/health.service.d.ts +2 -2
- package/dist/admin/health.service.js +6 -2
- 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/agents/agents.module.js +3 -0
- package/dist/agents/agents.service.d.ts +6 -1
- package/dist/agents/agents.service.js +11 -2
- package/dist/background/wake-rules.d.ts +5 -0
- package/dist/background/wake-rules.js +16 -5
- package/dist/catalog/catalog-import.module.js +0 -4
- package/dist/catalog/catalog-scope.service.d.ts +63 -26
- package/dist/catalog/catalog-scope.service.js +146 -71
- package/dist/catalog/catalog.module.js +4 -3
- package/dist/catalog/catalog.service.d.ts +11 -4
- package/dist/catalog/catalog.service.js +21 -5
- package/dist/catalog/cli.d.ts +4 -3
- package/dist/catalog/cli.js +8 -13
- package/dist/catalog/job-ids.d.ts +8 -20
- package/dist/catalog/job-ids.js +11 -16
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-plugin-uploads.d.ts +11 -4
- package/dist/catalog/org-plugin-uploads.js +21 -6
- package/dist/catalog/owned-items.d.ts +6 -13
- package/dist/catalog/owned-items.js +11 -29
- package/dist/catalog/owned-plugin-cleanup.d.ts +8 -17
- package/dist/catalog/owned-plugin-cleanup.js +12 -66
- package/dist/catalog/platform-components.d.ts +56 -34
- package/dist/catalog/platform-components.js +68 -78
- package/dist/catalog/plugin-import.d.ts +28 -25
- package/dist/catalog/plugin-import.js +90 -76
- package/dist/catalog/plugin-upgrade.d.ts +4 -4
- package/dist/catalog/plugin-upgrade.js +7 -19
- package/dist/catalog/scope-jobs.d.ts +59 -0
- package/dist/catalog/scope-jobs.js +65 -0
- package/dist/catalog/scope-sync-workers.d.ts +22 -0
- package/dist/catalog/scope-sync-workers.js +58 -2
- package/dist/catalog/scope-sync.d.ts +40 -65
- package/dist/catalog/scope-sync.js +114 -122
- 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/inbound.js +2 -2
- 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/channels/threads.d.ts +4 -3
- package/dist/channels/threads.js +4 -3
- package/dist/conversations/conversations.controller.d.ts +4 -2
- package/dist/conversations/conversations.controller.js +8 -4
- package/dist/conversations/conversations.service.d.ts +54 -9
- package/dist/conversations/conversations.service.js +94 -25
- package/dist/conversations/events.d.ts +27 -0
- package/dist/conversations/events.js +30 -1
- package/dist/conversations/source.d.ts +10 -0
- package/dist/conversations/source.js +17 -0
- package/dist/conversations/tree.d.ts +10 -0
- package/dist/conversations/tree.js +35 -0
- package/dist/db/kernel-schema.d.ts +19 -15
- package/dist/devices/devices.service.d.ts +25 -14
- package/dist/devices/devices.service.js +36 -30
- 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/run-replay.d.ts +13 -2
- package/dist/evals/run-replay.js +28 -1
- package/dist/evals/runs.service.d.ts +5 -6
- package/dist/evals/runs.service.js +11 -33
- package/dist/evals/snapshot.d.ts +7 -1
- package/dist/evals/snapshot.js +2 -1
- package/dist/extensions/hooks.d.ts +22 -3
- package/dist/extensions/hooks.js +15 -1
- package/dist/extensions/lifecycle-hooks.d.ts +99 -0
- package/dist/extensions/lifecycle-hooks.js +273 -0
- package/dist/extensions/org-config.d.ts +13 -1
- package/dist/extensions/org-config.js +19 -2
- package/dist/extensions/permission-view.d.ts +9 -5
- package/dist/extensions/permission-view.js +9 -4
- package/dist/extensions/point-defaults.d.ts +28 -1
- package/dist/extensions/point-defaults.js +44 -13
- package/dist/inbox/inbox.service.d.ts +6 -20
- package/dist/inbox/inbox.service.js +0 -3
- package/dist/inbox/org-notices.d.ts +1 -1
- package/dist/inbox/platform-inbox.service.d.ts +3 -32
- package/dist/inbox/platform-inbox.service.js +5 -54
- 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 +176 -0
- package/dist/infra/outbox/registry.js +183 -0
- package/dist/infra/queue/queue-names.d.ts +1 -1
- package/dist/infra/queue/queue-names.js +1 -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/directory-sync.js +1 -1
- package/dist/orgs/members.service.js +1 -1
- package/dist/orgs/offboarding.d.ts +50 -57
- package/dist/orgs/offboarding.js +117 -162
- package/dist/orgs/orgs.module.js +3 -0
- package/dist/orgs/orgs.service.d.ts +11 -5
- package/dist/orgs/orgs.service.js +19 -7
- package/dist/overrides.d.ts +7 -0
- package/dist/overrides.js +4 -0
- package/dist/platform/ops/conversations.ops.js +1 -1
- package/dist/platform/ops/inbox.ops.js +4 -20
- package/dist/platform/ops/platform.ops.d.ts +1 -0
- package/dist/platform/ops/platform.ops.js +6 -3
- package/dist/roles/worker-role.d.ts +10 -5
- package/dist/roles/worker-role.js +19 -9
- package/dist/roles/worker.module.js +2 -4
- package/dist/runtime/context/build.d.ts +3 -0
- package/dist/runtime/context/build.js +15 -0
- package/dist/runtime/context/plugin-context.d.ts +20 -0
- package/dist/runtime/context/plugin-context.js +30 -0
- package/dist/runtime/langgraph-loop.js +4 -0
- package/dist/runtime/loop.d.ts +8 -0
- package/dist/runtime/maintenance.d.ts +9 -4
- package/dist/runtime/maintenance.js +52 -13
- package/dist/runtime/model-call.d.ts +6 -1
- package/dist/runtime/model-call.js +1 -0
- package/dist/runtime/plugin-context.service.d.ts +75 -0
- package/dist/runtime/plugin-context.service.js +220 -0
- package/dist/runtime/run-context.service.d.ts +7 -3
- package/dist/runtime/run-context.service.js +12 -2
- package/dist/runtime/run-control.d.ts +6 -1
- package/dist/runtime/run-control.js +13 -0
- 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 +29 -24
- package/dist/runtime/run-queue.js +118 -57
- package/dist/runtime/run-status.d.ts +12 -0
- package/dist/runtime/run-status.js +21 -0
- package/dist/runtime/run-worker.d.ts +32 -3
- package/dist/runtime/run-worker.js +143 -21
- package/dist/runtime/runtime.module.js +2 -0
- package/dist/runtime/step-log.d.ts +6 -5
- package/dist/runtime/step-log.js +6 -5
- package/dist/runtime/subagents/dispatch.js +1 -0
- package/dist/runtime/subagents/skill-fork.js +3 -0
- 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/sandbox/jobs.d.ts +20 -10
- package/dist/sandbox/jobs.js +23 -17
- package/dist/schedules/fire.d.ts +25 -4
- package/dist/schedules/fire.js +87 -13
- package/dist/schedules/job-ids.d.ts +66 -17
- package/dist/schedules/job-ids.js +81 -13
- package/dist/schedules/run-tracker.js +2 -1
- package/dist/schedules/scan.job.d.ts +16 -35
- package/dist/schedules/scan.job.js +38 -147
- package/dist/schedules/scheduled-tasks.service.d.ts +6 -6
- package/dist/schedules/scheduled-tasks.service.js +17 -14
- package/dist/schedules/schedules.module.js +0 -4
- package/dist/skills/catalog.d.ts +7 -3
- package/dist/skills/catalog.js +12 -2
- package/dist/skills/changes.controller.d.ts +1 -1
- package/dist/skills/changes.controller.js +1 -1
- package/dist/tools/approvals.service.d.ts +111 -23
- package/dist/tools/approvals.service.js +326 -77
- package/dist/tools/builtins/sandbox/task-output.js +21 -6
- 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/pipeline.d.ts +11 -2
- package/dist/tools/pipeline.js +5 -0
- package/dist/tools/questions.service.d.ts +15 -4
- package/dist/tools/questions.service.js +21 -19
- package/dist/tools/registry.d.ts +8 -1
- package/dist/tools/registry.js +9 -0
- package/dist/tools/tools.module.js +4 -0
- package/package.json +3 -3
- package/src/migrations/0121_drop_platform_migration_runs.sql +12 -0
- package/src/migrations/0122_drop_scheduled_task_runs_rechecked_at.sql +5 -0
- package/src/migrations/0123_pgvector.sql +27 -0
- package/src/migrations/0124_conversation_source.sql +36 -0
- package/src/migrations/0125_conversation_tree_stopped_at.sql +5 -0
- package/dist/catalog/platform-migration.d.ts +0 -115
- package/dist/catalog/platform-migration.js +0 -583
- package/dist/infra/queue/add-job-once.d.ts +0 -14
- package/dist/infra/queue/add-job-once.js +0 -16
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DOCUMENT_MEDIA_TYPES, hasControlChars, IMAGE_MEDIA_TYPES, isAttachmentId, isSkillName, MEDIA_NAME_MAX_CHARS, } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import { CONTEXT_LIMITS, DOCUMENT_MEDIA_TYPES, hasControlChars, IMAGE_MEDIA_TYPES, isAttachmentId, isSkillName, MEDIA_NAME_MAX_CHARS, } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { AgentDefinitionSchema, DisabledSkillRef, DisabledToolRef, ModelRef, } from "../agents/definition.js";
|
|
4
4
|
import { ORG_ROLES } from "../orgs/roles.js";
|
|
@@ -228,6 +228,22 @@ export const RunContextSchema = z.object({
|
|
|
228
228
|
.optional(),
|
|
229
229
|
});
|
|
230
230
|
const ev = (type, payload) => z.object({ type: z.literal(type), payload });
|
|
231
|
+
/**
|
|
232
|
+
* The most UTF-8 bytes a `plugin_context` event's text may hold (memory spec §6.1): the providers'
|
|
233
|
+
* limit, so the log takes every text a provider may return.
|
|
234
|
+
*/
|
|
235
|
+
export const PLUGIN_CONTEXT_MAX_BYTES = CONTEXT_LIMITS.maxBytes;
|
|
236
|
+
/**
|
|
237
|
+
* `plugin_context` (memory spec §6.1): the text a `context@1` provider added at a run start. Empty
|
|
238
|
+
* text means the provider has nothing (any more); `hash` is the sha256 (hex) of `text`.
|
|
239
|
+
*/
|
|
240
|
+
export const PluginContextSchema = z.object({
|
|
241
|
+
plugin: z.string().min(1).max(64),
|
|
242
|
+
text: z.string().refine((t) => Buffer.byteLength(t, "utf8") <= PLUGIN_CONTEXT_MAX_BYTES, {
|
|
243
|
+
message: `at most ${PLUGIN_CONTEXT_MAX_BYTES} UTF-8 bytes`,
|
|
244
|
+
}),
|
|
245
|
+
hash: z.string().regex(/^[0-9a-f]{64}$/),
|
|
246
|
+
});
|
|
231
247
|
/**
|
|
232
248
|
* An agent definition frozen into a log (`subagent_started`): the current schema, with the
|
|
233
249
|
* selected and pinned skills of older logs still accepted (skills default-on spec §4: history
|
|
@@ -485,6 +501,13 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
|
|
|
485
501
|
* system message (`runContextText`); a summary covering it restores the current values.
|
|
486
502
|
*/
|
|
487
503
|
ev("run_context", RunContextSchema),
|
|
504
|
+
/**
|
|
505
|
+
* Text a `context@1` provider added at a run start (memory spec §6.1), written only when its
|
|
506
|
+
* hash differs from the plugin's previous one in the conversation. **Model-visible**: rendered
|
|
507
|
+
* at its position as a system message (`pluginContextText`, nothing for empty text); a summary
|
|
508
|
+
* covering it is followed by the latest non-empty one per plugin again.
|
|
509
|
+
*/
|
|
510
|
+
ev("plugin_context", PluginContextSchema),
|
|
488
511
|
/**
|
|
489
512
|
* The step's resolved tool set differs from the one the conversation's previous model request
|
|
490
513
|
* offered (`toolSetChange`): tool names as the model sees them, sorted. Written before that
|
|
@@ -599,6 +622,12 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
|
|
|
599
622
|
*/
|
|
600
623
|
reason: z.string().max(APPROVAL_DENY_REASON_MAX_CHARS).optional(),
|
|
601
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() })),
|
|
602
631
|
/**
|
|
603
632
|
* 工具调用需要用户输入(`AskUserQuestion`、插件工具的 `input_required`、MCP elicitation):输入请求已建,
|
|
604
633
|
* run 暂停等待回答。界面据此显示表单卡片(不进模型请求)。
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ConversationSource } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
import type { KernelSchemaDb } from "../db/kernel-schema.js";
|
|
3
|
+
/**
|
|
4
|
+
* Where a conversation comes from (memory plugin spec §6.2), stored on its row at creation
|
|
5
|
+
* (`conversations.source`, migration 0124) and never changed. A missing conversation throws
|
|
6
|
+
* rather than guessing a source (a guess could offer a group chat what only a DM may see).
|
|
7
|
+
*/
|
|
8
|
+
export declare function conversationSourceOf(db: Pick<KernelSchemaDb, "selectFrom">, conversationId: string): Promise<ConversationSource>;
|
|
9
|
+
/** The source of a conversation created for a channel chat of `chatType`. */
|
|
10
|
+
export declare const channelSource: (chatType: "dm" | "group") => ConversationSource;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a conversation comes from (memory plugin spec §6.2), stored on its row at creation
|
|
3
|
+
* (`conversations.source`, migration 0124) and never changed. A missing conversation throws
|
|
4
|
+
* rather than guessing a source (a guess could offer a group chat what only a DM may see).
|
|
5
|
+
*/
|
|
6
|
+
export async function conversationSourceOf(db, conversationId) {
|
|
7
|
+
const row = await db
|
|
8
|
+
.selectFrom("conversations")
|
|
9
|
+
.select("source")
|
|
10
|
+
.where("id", "=", conversationId)
|
|
11
|
+
.executeTakeFirst();
|
|
12
|
+
if (row === undefined)
|
|
13
|
+
throw new Error(`conversation ${conversationId} not found`);
|
|
14
|
+
return row.source;
|
|
15
|
+
}
|
|
16
|
+
/** The source of a conversation created for a channel chat of `chatType`. */
|
|
17
|
+
export const channelSource = (chatType) => chatType === "dm" ? "channel_dm" : "channel_group";
|
|
@@ -15,3 +15,13 @@ export declare function conversationTree(db: KernelSchemaDb, conversationId: str
|
|
|
15
15
|
* `conversations.parent_conversation_id`. Empty for a root or an unknown id.
|
|
16
16
|
*/
|
|
17
17
|
export declare function conversationAncestors(db: KernelSchemaDb, conversationId: string): Promise<string[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Locks the conversations `ids` and every ancestor of theirs (`FOR NO KEY UPDATE`, in the caller's
|
|
20
|
+
* transaction) tree by tree, root first: the trees in root id order, within a tree by level below
|
|
21
|
+
* the root, then id. A reply locks its root conversation and only then the sub-agents' below it
|
|
22
|
+
* (`ConversationsService.skipWaitsIn`), and a dispatch locks a parent before its child; a locker
|
|
23
|
+
* of conversations in plain id order could take a child before its root and wait for the root
|
|
24
|
+
* while such a path holds the root and waits for the child (40P01). Returns the ids locked (the
|
|
25
|
+
* requested ones and their ancestors); an unknown id is skipped.
|
|
26
|
+
*/
|
|
27
|
+
export declare function lockConversationTrees(db: KernelSchemaDb, ids: readonly string[]): Promise<string[]>;
|
|
@@ -40,3 +40,38 @@ export async function conversationAncestors(db, conversationId) {
|
|
|
40
40
|
SELECT id FROM up WHERE level > 0 ORDER BY level`.execute(db);
|
|
41
41
|
return r.rows.map((row) => row.id);
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Locks the conversations `ids` and every ancestor of theirs (`FOR NO KEY UPDATE`, in the caller's
|
|
45
|
+
* transaction) tree by tree, root first: the trees in root id order, within a tree by level below
|
|
46
|
+
* the root, then id. A reply locks its root conversation and only then the sub-agents' below it
|
|
47
|
+
* (`ConversationsService.skipWaitsIn`), and a dispatch locks a parent before its child; a locker
|
|
48
|
+
* of conversations in plain id order could take a child before its root and wait for the root
|
|
49
|
+
* while such a path holds the root and waits for the child (40P01). Returns the ids locked (the
|
|
50
|
+
* requested ones and their ancestors); an unknown id is skipped.
|
|
51
|
+
*/
|
|
52
|
+
export async function lockConversationTrees(db, ids) {
|
|
53
|
+
const wanted = [...new Set(ids)];
|
|
54
|
+
if (wanted.length === 0)
|
|
55
|
+
return [];
|
|
56
|
+
const r = await sql `
|
|
57
|
+
WITH RECURSIVE up(start, id, parent, level) AS (
|
|
58
|
+
SELECT c.id, c.id, c.parent_conversation_id, 0 FROM capybara.conversations c
|
|
59
|
+
WHERE c.id = ANY(${wanted}::text[])
|
|
60
|
+
UNION ALL
|
|
61
|
+
SELECT up.start, c.id, c.parent_conversation_id, up.level + 1
|
|
62
|
+
FROM capybara.conversations c JOIN up ON c.id = up.parent
|
|
63
|
+
WHERE up.level < ${TREE_MAX_LEVELS}
|
|
64
|
+
),
|
|
65
|
+
tops AS (
|
|
66
|
+
SELECT DISTINCT ON (start) start, id AS root, level AS height
|
|
67
|
+
FROM up ORDER BY start, level DESC
|
|
68
|
+
),
|
|
69
|
+
nodes AS (
|
|
70
|
+
SELECT DISTINCT up.id, tops.root, tops.height - up.level AS depth
|
|
71
|
+
FROM up JOIN tops ON tops.start = up.start
|
|
72
|
+
)
|
|
73
|
+
SELECT c.id FROM capybara.conversations c JOIN nodes n ON n.id = c.id
|
|
74
|
+
ORDER BY n.root, n.depth, n.id
|
|
75
|
+
FOR NO KEY UPDATE OF c`.execute(db);
|
|
76
|
+
return r.rows.map((row) => row.id);
|
|
77
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FileSystemHandle, ParsedTheme, SandboxTier, SkillInvocation } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import type { ConversationSource, FileSystemHandle, ParsedTheme, SandboxTier, SkillInvocation } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import type { ColumnType, Generated, Kysely } from "kysely";
|
|
3
3
|
import type { ModelRef } from "../agents/definition.js";
|
|
4
4
|
import type { ScheduleChat, ScheduleMarker, ScheduleMode, SchedulePauseReason, ScheduleStatus, ScheduleTrigger, TaskRunSkipReason, TaskRunStatus } from "../schedules/types.js";
|
|
@@ -263,6 +263,16 @@ export interface ConversationsTable {
|
|
|
263
263
|
device_id: ColumnType<string | null, string | null | undefined, string | null>;
|
|
264
264
|
/** 0113: the scheduled task that created it (`new` mode); null otherwise. */
|
|
265
265
|
scheduled_task_id: ColumnType<string | null, string | null | undefined, string | null>;
|
|
266
|
+
/**
|
|
267
|
+
* 0124: where the conversation comes from, set at creation and never changed. Required on insert
|
|
268
|
+
* (the column's `web` default is not relied on: a forgotten source must not read as web).
|
|
269
|
+
*/
|
|
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>;
|
|
266
276
|
}
|
|
267
277
|
/** 0060: an org's order of file-preview@1 previewers (UI refresh B Task 11). */
|
|
268
278
|
export interface FilePreviewersTable {
|
|
@@ -340,7 +350,11 @@ export interface RunsTable {
|
|
|
340
350
|
waiting_since: TsNullable;
|
|
341
351
|
/** 0012:累计等待时长(毫秒),不计入运行时长。 */
|
|
342
352
|
waited_ms: Int8;
|
|
343
|
-
/**
|
|
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
|
+
*/
|
|
344
358
|
job_id: string | null;
|
|
345
359
|
/** 0012:最近一次(续跑)回到 queued 的时刻。 */
|
|
346
360
|
queued_at: TsNullable;
|
|
@@ -1224,8 +1238,6 @@ export interface ScheduledTaskRunsTable {
|
|
|
1224
1238
|
skip_reason: ColumnType<TaskRunSkipReason | null, TaskRunSkipReason | null | undefined, TaskRunSkipReason | null>;
|
|
1225
1239
|
error: NullableText;
|
|
1226
1240
|
missing_grants: ColumnType<string[], string | undefined, string>;
|
|
1227
|
-
/** When the scanner's repair last found this started run still in flight. */
|
|
1228
|
-
rechecked_at: NullableTs;
|
|
1229
1241
|
}
|
|
1230
1242
|
/** 0113: a scheduled task's remembered grants (same shape as `conversation_tool_grants`). */
|
|
1231
1243
|
export interface ScheduledTaskGrantsTable {
|
|
@@ -1333,7 +1345,6 @@ export interface KernelDatabase {
|
|
|
1333
1345
|
plugin_install_components: PluginInstallComponentsTable;
|
|
1334
1346
|
extension_catalog_sources: ExtensionCatalogSourcesTable;
|
|
1335
1347
|
bootstrap_runs: BootstrapRunsTable;
|
|
1336
|
-
platform_migration_runs: PlatformMigrationRunsTable;
|
|
1337
1348
|
org_notices: OrgNoticesTable;
|
|
1338
1349
|
platform_notices: PlatformNoticesTable;
|
|
1339
1350
|
plugin_scope_sync_failures: PluginScopeSyncFailuresTable;
|
|
@@ -1397,21 +1408,14 @@ export interface ExtensionClientReportsTable {
|
|
|
1397
1408
|
last_at: Ts;
|
|
1398
1409
|
last_message: string;
|
|
1399
1410
|
}
|
|
1400
|
-
/**
|
|
1401
|
-
export
|
|
1402
|
-
id: string;
|
|
1403
|
-
started_at: Ts;
|
|
1404
|
-
finished_at: TsNullable;
|
|
1405
|
-
stats: Json<Record<string, unknown>>;
|
|
1406
|
-
}
|
|
1407
|
-
/** 0082: the kinds of {@link OrgNoticesTable}. */
|
|
1408
|
-
export type OrgNoticeKind = "skill_renamed" | "item_shared" | "shared_item_updated" | "upload_confirmed" | "upload_rejected";
|
|
1411
|
+
/** 0084 (0121 dropped `skill_renamed`): the kinds of {@link OrgNoticesTable}. */
|
|
1412
|
+
export type OrgNoticeKind = "item_shared" | "shared_item_updated" | "upload_confirmed" | "upload_rejected";
|
|
1409
1413
|
/** 0082: a notice for an org's admins (an inbox item), until one of them dismisses it. */
|
|
1410
1414
|
export interface OrgNoticesTable {
|
|
1411
1415
|
id: string;
|
|
1412
1416
|
org_id: string;
|
|
1413
1417
|
kind: OrgNoticeKind;
|
|
1414
|
-
/**
|
|
1418
|
+
/** The kind's shape (`OrgNoticeDetails` in `inbox/org-notices.ts`). */
|
|
1415
1419
|
detail: Json<Record<string, unknown>>;
|
|
1416
1420
|
created_at: Ts;
|
|
1417
1421
|
dismissed_at: TsNullable;
|
|
@@ -4,9 +4,9 @@ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
|
|
|
4
4
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
5
5
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
6
6
|
import { LockService } from "../infra/lock/lock.service.js";
|
|
7
|
+
import { OutboxService } from "../infra/outbox/outbox.service.js";
|
|
7
8
|
import { DevicePolicyService } from "../local-devices/device-policy.service.js";
|
|
8
9
|
import { MachinesService } from "../local-devices/machines.js";
|
|
9
|
-
import type { KernelLogger } from "../log.js";
|
|
10
10
|
import type { DeviceSandboxLevel } from "../runtime/context/device-paths.js";
|
|
11
11
|
import { SandboxJobs } from "../sandbox/jobs.js";
|
|
12
12
|
import { SandboxProviders } from "../sandbox/providers.js";
|
|
@@ -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 {
|
|
@@ -181,8 +188,8 @@ export interface DeviceList {
|
|
|
181
188
|
* Personal devices (spec 2026-11-07-environments-design §4.2 items 7, 12, 13; §6; §9):
|
|
182
189
|
* cloud ones on the deployment's sandbox provider and `device` ones on a connected machine; a
|
|
183
190
|
* person's devices in an org, visible and usable only by their owner (anyone else gets
|
|
184
|
-
* `device.not_found`). Deleting one soft-deletes it and
|
|
185
|
-
* `sandbox-destroy-for` job
|
|
191
|
+
* `device.not_found`). Deleting one soft-deletes it and owes the destruction of its instance and
|
|
192
|
+
* files (a `sandbox-destroy-for` job) in the same transaction.
|
|
186
193
|
*/
|
|
187
194
|
export declare class DevicesService {
|
|
188
195
|
private readonly db;
|
|
@@ -191,14 +198,17 @@ export declare class DevicesService {
|
|
|
191
198
|
private readonly providers;
|
|
192
199
|
private readonly sandbox;
|
|
193
200
|
private readonly sandboxJobs;
|
|
201
|
+
/** Owes a deleted device's destruction in the delete's transaction. */
|
|
202
|
+
private readonly outbox;
|
|
194
203
|
private readonly access;
|
|
195
204
|
private readonly displayNames;
|
|
196
|
-
private readonly log;
|
|
197
205
|
/** Reads the device relay's lease (`online`). */
|
|
198
206
|
private readonly locks;
|
|
199
207
|
private readonly machines;
|
|
200
208
|
private readonly policies;
|
|
201
|
-
constructor(db: KernelDb, txHost: TransactionHost, config: Pick<KernelConfigService, "get">, providers: Pick<SandboxProviders, "cloud" | "forName">, sandbox: Pick<SandboxService, "deviceLimits" | "limitsState">, sandboxJobs: Pick<SandboxJobs, "
|
|
209
|
+
constructor(db: KernelDb, txHost: TransactionHost, config: Pick<KernelConfigService, "get">, providers: Pick<SandboxProviders, "cloud" | "forName">, sandbox: Pick<SandboxService, "deviceLimits" | "limitsState">, sandboxJobs: Pick<SandboxJobs, "resetFiles">,
|
|
210
|
+
/** Owes a deleted device's destruction in the delete's transaction. */
|
|
211
|
+
outbox: Pick<OutboxService, "add">, access: Pick<DeviceAccess, "usable">, displayNames: Pick<ExtensionDisplayNames, "many">,
|
|
202
212
|
/** Reads the device relay's lease (`online`). */
|
|
203
213
|
locks: Pick<LockService, "holderOf">, machines: Pick<MachinesService, "revoke" | "revokeActive">, policies: Pick<DevicePolicyService, "apply" | "summary">);
|
|
204
214
|
/**
|
|
@@ -284,9 +294,9 @@ export declare class DevicesService {
|
|
|
284
294
|
/** Renames the device locked by {@link lockOwn} (`before`); see {@link rename}. */
|
|
285
295
|
private renameLocked;
|
|
286
296
|
/**
|
|
287
|
-
* Deletes one of the person's live devices: soft delete, its IM default row cleared, and
|
|
288
|
-
*
|
|
289
|
-
* destroys the instance and the files). Conversations bound to it keep pointing at it; their
|
|
297
|
+
* Deletes one of the person's live devices: soft delete, its IM default row cleared, and a
|
|
298
|
+
* `sandbox-destroy-for` job owed in the same transaction (`device-destroy-<deviceId>`; the
|
|
299
|
+
* worker waits for running commands, then destroys the instance and the files). Conversations bound to it keep pointing at it; their
|
|
290
300
|
* sandbox tools then fail with `device.not_found`. A local device's machine is
|
|
291
301
|
* revoked in the same transaction; after the commit it is told `stop { reason: "deleted" }`
|
|
292
302
|
* (while connected) and its connection is closed.
|
|
@@ -306,7 +316,7 @@ export declare class DevicesService {
|
|
|
306
316
|
* refused up front with `device.policy_refused` when the machine last reported
|
|
307
317
|
* `remoteGrants: "off"` (otherwise the machine decides); narrowing is always sent, and
|
|
308
318
|
* `isolation` is only ever `sandbox`. The machine offline → `device.offline`; a cloud device or a
|
|
309
|
-
* program without the `
|
|
319
|
+
* program without the `policyOpId` feature → `device.policy_unsupported`; the machine's refusal →
|
|
310
320
|
* `device.policy_refused` with its message. Callers are human sessions only (no personal access
|
|
311
321
|
* token, no service token, no Platform op).
|
|
312
322
|
*/
|
|
@@ -391,8 +401,9 @@ export declare class DevicesService {
|
|
|
391
401
|
private views;
|
|
392
402
|
/**
|
|
393
403
|
* The last {@link RECENT_POLICY_CHANGES} sandbox changes of each device in the org (audit rows
|
|
394
|
-
* `device.policy_changed`, `device.
|
|
395
|
-
* 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.
|
|
396
407
|
*/
|
|
397
408
|
private recentSandboxChanges;
|
|
398
409
|
/**
|
|
@@ -15,18 +15,17 @@ import { ApiError } from "../errors.js";
|
|
|
15
15
|
import { ExtensionDisplayNames } from "../extensions/display-name.js";
|
|
16
16
|
import { audit } from "../identity/audit.js";
|
|
17
17
|
import { KernelConfigService } from "../infra/config/kernel-config.service.js";
|
|
18
|
-
import { afterCommit } from "../infra/database/after-commit.js";
|
|
19
18
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
20
19
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
21
20
|
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
22
21
|
import { LockService } from "../infra/lock/lock.service.js";
|
|
23
|
-
import {
|
|
22
|
+
import { OutboxService } from "../infra/outbox/outbox.service.js";
|
|
24
23
|
import { DevicePolicyService } from "../local-devices/device-policy.service.js";
|
|
25
24
|
import { deviceLeaseKey } from "../local-devices/keys.js";
|
|
26
25
|
import { MachinesService } from "../local-devices/machines.js";
|
|
27
26
|
import { deviceFeaturesOf, localPolicyOf } from "../local-devices/policy-summary.js";
|
|
28
27
|
import { readOrgSettings } from "../orgs/settings.service.js";
|
|
29
|
-
import { SandboxJobs } from "../sandbox/jobs.js";
|
|
28
|
+
import { oweDeviceDestroy, SandboxJobs } from "../sandbox/jobs.js";
|
|
30
29
|
import { isInternalProvider, LOCAL_PROVIDER, SandboxProviders } from "../sandbox/providers.js";
|
|
31
30
|
import { deviceNotFound, SandboxService } from "../sandbox/sandbox.service.js";
|
|
32
31
|
import { effectiveSandboxPolicy, readDeploymentSandboxPolicy, } from "../sandbox/tiers.js";
|
|
@@ -43,8 +42,8 @@ const nameTaken = () => new ApiError("device.name_taken", "You already have a de
|
|
|
43
42
|
* Personal devices (spec 2026-11-07-environments-design §4.2 items 7, 12, 13; §6; §9):
|
|
44
43
|
* cloud ones on the deployment's sandbox provider and `device` ones on a connected machine; a
|
|
45
44
|
* person's devices in an org, visible and usable only by their owner (anyone else gets
|
|
46
|
-
* `device.not_found`). Deleting one soft-deletes it and
|
|
47
|
-
* `sandbox-destroy-for` job
|
|
45
|
+
* `device.not_found`). Deleting one soft-deletes it and owes the destruction of its instance and
|
|
46
|
+
* files (a `sandbox-destroy-for` job) in the same transaction.
|
|
48
47
|
*/
|
|
49
48
|
let DevicesService = class DevicesService {
|
|
50
49
|
db;
|
|
@@ -53,13 +52,15 @@ let DevicesService = class DevicesService {
|
|
|
53
52
|
providers;
|
|
54
53
|
sandbox;
|
|
55
54
|
sandboxJobs;
|
|
55
|
+
outbox;
|
|
56
56
|
access;
|
|
57
57
|
displayNames;
|
|
58
|
-
log;
|
|
59
58
|
locks;
|
|
60
59
|
machines;
|
|
61
60
|
policies;
|
|
62
|
-
constructor(db, txHost, config, providers, sandbox, sandboxJobs,
|
|
61
|
+
constructor(db, txHost, config, providers, sandbox, sandboxJobs,
|
|
62
|
+
/** Owes a deleted device's destruction in the delete's transaction. */
|
|
63
|
+
outbox, access, displayNames,
|
|
63
64
|
/** Reads the device relay's lease (`online`). */
|
|
64
65
|
locks, machines, policies) {
|
|
65
66
|
this.db = db;
|
|
@@ -68,9 +69,9 @@ let DevicesService = class DevicesService {
|
|
|
68
69
|
this.providers = providers;
|
|
69
70
|
this.sandbox = sandbox;
|
|
70
71
|
this.sandboxJobs = sandboxJobs;
|
|
72
|
+
this.outbox = outbox;
|
|
71
73
|
this.access = access;
|
|
72
74
|
this.displayNames = displayNames;
|
|
73
|
-
this.log = log;
|
|
74
75
|
this.locks = locks;
|
|
75
76
|
this.machines = machines;
|
|
76
77
|
this.policies = policies;
|
|
@@ -388,9 +389,9 @@ let DevicesService = class DevicesService {
|
|
|
388
389
|
});
|
|
389
390
|
}
|
|
390
391
|
/**
|
|
391
|
-
* Deletes one of the person's live devices: soft delete, its IM default row cleared, and
|
|
392
|
-
*
|
|
393
|
-
* destroys the instance and the files). Conversations bound to it keep pointing at it; their
|
|
392
|
+
* Deletes one of the person's live devices: soft delete, its IM default row cleared, and a
|
|
393
|
+
* `sandbox-destroy-for` job owed in the same transaction (`device-destroy-<deviceId>`; the
|
|
394
|
+
* worker waits for running commands, then destroys the instance and the files). Conversations bound to it keep pointing at it; their
|
|
394
395
|
* sandbox tools then fail with `device.not_found`. A local device's machine is
|
|
395
396
|
* revoked in the same transaction; after the commit it is told `stop { reason: "deleted" }`
|
|
396
397
|
* (while connected) and its connection is closed.
|
|
@@ -417,15 +418,7 @@ let DevicesService = class DevicesService {
|
|
|
417
418
|
target: id,
|
|
418
419
|
detail: { name: before.name, isDefault: before.is_default, ...auditDetail },
|
|
419
420
|
});
|
|
420
|
-
|
|
421
|
-
try {
|
|
422
|
-
await this.sandboxJobs.destroy([id]);
|
|
423
|
-
}
|
|
424
|
-
catch (err) {
|
|
425
|
-
// the hourly destruction sweep destroys deleted devices too
|
|
426
|
-
this.log.warn("cannot schedule device destruction", { deviceId: id, err });
|
|
427
|
-
}
|
|
428
|
-
});
|
|
421
|
+
await oweDeviceDestroy(this.outbox, id, orgId);
|
|
429
422
|
}
|
|
430
423
|
/**
|
|
431
424
|
* Disconnects the machine of one of the person's own local devices (it must sign in
|
|
@@ -443,7 +436,7 @@ let DevicesService = class DevicesService {
|
|
|
443
436
|
* refused up front with `device.policy_refused` when the machine last reported
|
|
444
437
|
* `remoteGrants: "off"` (otherwise the machine decides); narrowing is always sent, and
|
|
445
438
|
* `isolation` is only ever `sandbox`. The machine offline → `device.offline`; a cloud device or a
|
|
446
|
-
* program without the `
|
|
439
|
+
* program without the `policyOpId` feature → `device.policy_unsupported`; the machine's refusal →
|
|
447
440
|
* `device.policy_refused` with its message. Callers are human sessions only (no personal access
|
|
448
441
|
* token, no service token, no Platform op).
|
|
449
442
|
*/
|
|
@@ -459,7 +452,7 @@ let DevicesService = class DevicesService {
|
|
|
459
452
|
const widens = (change.add?.length ?? 0) > 0 || (known !== null && tierWidens(known.tier, change.tier));
|
|
460
453
|
// only a known "off" is refused here; an unknown summary, a machine that cannot take the op
|
|
461
454
|
// or none at all are left to apply (the machine decides, or unsupported / offline)
|
|
462
|
-
const canApply = summary?.features.includes("
|
|
455
|
+
const canApply = summary?.features.includes("policyOpId") === true;
|
|
463
456
|
if (widens && canApply && known?.remoteGrants === "off") {
|
|
464
457
|
throw new ApiError("device.policy_refused", remoteGrantsOffMessage(change));
|
|
465
458
|
}
|
|
@@ -900,8 +893,9 @@ let DevicesService = class DevicesService {
|
|
|
900
893
|
}
|
|
901
894
|
/**
|
|
902
895
|
* The last {@link RECENT_POLICY_CHANGES} sandbox changes of each device in the org (audit rows
|
|
903
|
-
* `device.policy_changed`, `device.
|
|
904
|
-
* 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.
|
|
905
899
|
*/
|
|
906
900
|
async recentSandboxChanges(orgId, deviceIds) {
|
|
907
901
|
const out = new Map();
|
|
@@ -1021,9 +1015,9 @@ DevicesService = __decorate([
|
|
|
1021
1015
|
__param(3, Inject(SandboxProviders)),
|
|
1022
1016
|
__param(4, Inject(SandboxService)),
|
|
1023
1017
|
__param(5, Inject(SandboxJobs)),
|
|
1024
|
-
__param(6, Inject(
|
|
1025
|
-
__param(7, Inject(
|
|
1026
|
-
__param(8, Inject(
|
|
1018
|
+
__param(6, Inject(OutboxService)),
|
|
1019
|
+
__param(7, Inject(DeviceAccess)),
|
|
1020
|
+
__param(8, Inject(ExtensionDisplayNames)),
|
|
1027
1021
|
__param(9, Inject(LockService)),
|
|
1028
1022
|
__param(10, Inject(MachinesService)),
|
|
1029
1023
|
__param(11, Inject(DevicePolicyService))
|
|
@@ -1047,6 +1041,8 @@ const limitsView = (l) => ({
|
|
|
1047
1041
|
/** The audit actions a device's recent sandbox changes list. */
|
|
1048
1042
|
const SANDBOX_CHANGE_ACTIONS = [
|
|
1049
1043
|
"device.policy_changed",
|
|
1044
|
+
"device.policy_unknown",
|
|
1045
|
+
"device.policy_failed",
|
|
1050
1046
|
"device.tier_changed",
|
|
1051
1047
|
"device.isolation_reported",
|
|
1052
1048
|
];
|
|
@@ -1066,10 +1062,16 @@ function sandboxChangeOf(action, at, d, by) {
|
|
|
1066
1062
|
by,
|
|
1067
1063
|
};
|
|
1068
1064
|
if (action === "device.tier_changed") {
|
|
1069
|
-
return {
|
|
1065
|
+
return {
|
|
1066
|
+
...base,
|
|
1067
|
+
kind: "tier",
|
|
1068
|
+
tier: { to: tierOrNull(d.to) },
|
|
1069
|
+
via: "web",
|
|
1070
|
+
outcome: "applied",
|
|
1071
|
+
};
|
|
1070
1072
|
}
|
|
1071
1073
|
if (action === "device.isolation_reported") {
|
|
1072
|
-
return { ...base, kind: "isolation", isolation, via: "device",
|
|
1074
|
+
return { ...base, kind: "isolation", isolation, via: "device", outcome: "applied" };
|
|
1073
1075
|
}
|
|
1074
1076
|
return {
|
|
1075
1077
|
...base,
|
|
@@ -1086,7 +1088,11 @@ function sandboxChangeOf(action, at, d, by) {
|
|
|
1086
1088
|
: null,
|
|
1087
1089
|
remoteGrants: d.remoteGrants === "owner" || d.remoteGrants === "off" ? d.remoteGrants : null,
|
|
1088
1090
|
via: d.via === "im" ? "im" : d.via === "device" ? "device" : "web",
|
|
1089
|
-
|
|
1091
|
+
outcome: action === "device.policy_unknown"
|
|
1092
|
+
? "unknown"
|
|
1093
|
+
: action === "device.policy_failed"
|
|
1094
|
+
? "failed"
|
|
1095
|
+
: "applied",
|
|
1090
1096
|
};
|
|
1091
1097
|
}
|
|
1092
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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ContentBlock, type Logger, type ModelUsage, type StopReason } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import { type ContentBlock, type ConversationSource, type Logger, type ModelUsage, type StopReason } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { type ModelRef } from "../agents/definition.js";
|
|
3
3
|
import { EventLog } from "../conversations/event-log.js";
|
|
4
4
|
import type { KernelSchemaDb } from "../db/kernel-schema.js";
|
|
@@ -98,6 +98,8 @@ export interface ReplaySeed {
|
|
|
98
98
|
evalRunId: string;
|
|
99
99
|
orgId: string;
|
|
100
100
|
agentId: string;
|
|
101
|
+
/** The original conversation's source: the replay offers the tools of this source. */
|
|
102
|
+
source: ConversationSource;
|
|
101
103
|
accountId: string;
|
|
102
104
|
conversationId: string;
|
|
103
105
|
runId: string;
|
|
@@ -121,11 +123,20 @@ export interface ReplaySeed {
|
|
|
121
123
|
runContext: {
|
|
122
124
|
payload: unknown;
|
|
123
125
|
} | undefined;
|
|
126
|
+
/**
|
|
127
|
+
* The original run's `plugin_context` events (memory spec §6.1), in log order, copied as is:
|
|
128
|
+
* replays never call context providers.
|
|
129
|
+
*/
|
|
130
|
+
pluginContexts: {
|
|
131
|
+
payload: {
|
|
132
|
+
plugin: string;
|
|
133
|
+
};
|
|
134
|
+
}[];
|
|
124
135
|
}
|
|
125
136
|
/**
|
|
126
137
|
* The seed of a full-run replay (C6): the `kind = 'eval'` conversation with the events before the
|
|
127
138
|
* original run at their original seqs, the run row and its `run_started` (and the original
|
|
128
|
-
* `skills_catalog`), in one transaction of its own.
|
|
139
|
+
* `skills_catalog`, `run_context` and `plugin_context` events), in one transaction of its own.
|
|
129
140
|
*/
|
|
130
141
|
export declare class EvalReplaySeeder {
|
|
131
142
|
private readonly db;
|