@capybara-hq/kernel 0.1.0-rc.23 → 0.1.0-rc.24
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/bootstrap.json +25 -25
- package/dist/admin/admin.module.d.ts +2 -1
- package/dist/admin/admin.module.js +7 -3
- package/dist/admin/outbox-dead.controller.d.ts +23 -0
- package/dist/admin/outbox-dead.controller.js +62 -0
- package/dist/admin/outbox-dead.schemas.d.ts +8 -0
- package/dist/admin/outbox-dead.schemas.js +18 -0
- package/dist/admin/outbox-dead.service.d.ts +66 -0
- package/dist/admin/outbox-dead.service.js +179 -0
- package/dist/admin/overview.service.d.ts +40 -5
- package/dist/admin/overview.service.js +41 -7
- package/dist/agents/from-template.js +1 -1
- package/dist/background/finish.d.ts +5 -2
- package/dist/background/finish.js +14 -3
- package/dist/background/launch.d.ts +2 -1
- package/dist/background/launch.js +5 -2
- package/dist/background/store.d.ts +8 -0
- package/dist/background/store.js +24 -0
- package/dist/catalog/admin-catalog.controller.d.ts +21 -2
- package/dist/catalog/admin-catalog.controller.js +45 -6
- package/dist/catalog/admin-catalog.schemas.d.ts +5 -0
- package/dist/catalog/admin-catalog.schemas.js +8 -0
- package/dist/catalog/catalog.module.d.ts +2 -1
- package/dist/catalog/catalog.module.js +5 -1
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/platform-import-update.d.ts +50 -0
- package/dist/catalog/platform-import-update.js +116 -0
- package/dist/catalog/plugin-import.d.ts +36 -1
- package/dist/catalog/plugin-import.js +23 -1
- package/dist/catalog/plugin-upgrade.d.ts +18 -0
- package/dist/catalog/plugin-upgrade.js +19 -0
- package/dist/channels/channels.module.d.ts +5 -2
- package/dist/channels/channels.module.js +9 -2
- package/dist/channels/deliver-job.d.ts +25 -0
- package/dist/channels/deliver-job.js +25 -0
- package/dist/channels/inbound.d.ts +1 -1
- package/dist/channels/inbound.js +2 -15
- package/dist/channels/jobs.d.ts +28 -18
- package/dist/channels/jobs.js +63 -26
- package/dist/config/schema.d.ts +0 -3
- package/dist/config/schema.js +11 -20
- package/dist/conversations/agent-tree.d.ts +16 -0
- package/dist/conversations/agent-tree.js +36 -0
- package/dist/conversations/conversations.service.d.ts +44 -14
- package/dist/conversations/conversations.service.js +52 -38
- package/dist/conversations/event-log.d.ts +7 -10
- package/dist/conversations/event-log.js +24 -15
- package/dist/conversations/events.js +2 -1
- package/dist/conversations/queue.service.d.ts +8 -3
- package/dist/conversations/queue.service.js +37 -13
- package/dist/conversations/titles.d.ts +21 -8
- package/dist/conversations/titles.js +42 -15
- package/dist/db/kernel-schema.d.ts +26 -8
- package/dist/devices/devices.service.d.ts +12 -3
- package/dist/devices/devices.service.js +18 -3
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +11 -0
- package/dist/evals/run-replay.js +14 -8
- package/dist/extensions/extensions.module.js +3 -0
- package/dist/extensions/lifecycle.d.ts +6 -1
- package/dist/extensions/lifecycle.js +9 -2
- package/dist/extensions/usage-subscribers.d.ts +43 -0
- package/dist/extensions/usage-subscribers.js +108 -0
- package/dist/http/api.module.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/infra/cli/kernel-cli.module.js +5 -0
- package/dist/infra/events/kernel-events.d.ts +9 -35
- package/dist/infra/outbox/outbox-relay.module.d.ts +6 -0
- package/dist/infra/outbox/outbox-relay.module.js +18 -0
- package/dist/infra/outbox/outbox.dispatcher.d.ts +103 -0
- package/dist/infra/outbox/outbox.dispatcher.js +310 -0
- package/dist/infra/outbox/outbox.module.d.ts +24 -0
- package/dist/infra/outbox/outbox.module.js +50 -0
- package/dist/infra/outbox/outbox.relay.d.ts +31 -0
- package/dist/infra/outbox/outbox.relay.js +76 -0
- package/dist/infra/outbox/outbox.service.d.ts +51 -0
- package/dist/infra/outbox/outbox.service.js +131 -0
- package/dist/infra/outbox/registry.d.ts +120 -0
- package/dist/infra/outbox/registry.js +108 -0
- package/dist/infra/queue/queue-names.d.ts +8 -0
- package/dist/infra/queue/queue-names.js +16 -0
- package/dist/kernel.module.js +3 -0
- package/dist/local-devices/device-policy.service.d.ts +4 -0
- package/dist/local-devices/device-policy.service.js +61 -3
- package/dist/local-devices/machines.d.ts +13 -5
- package/dist/local-devices/machines.js +45 -7
- package/dist/local-devices/policy-summary.d.ts +44 -1
- package/dist/local-devices/policy-summary.js +69 -0
- package/dist/models/embeddings.d.ts +9 -9
- package/dist/models/embeddings.js +11 -18
- package/dist/models/models.module.js +3 -0
- package/dist/orgs/orgs.service.js +7 -0
- package/dist/overrides.d.ts +20 -3
- package/dist/overrides.js +10 -2
- package/dist/platform/exclusions.js +5 -0
- package/dist/platform/ops/admin.ops.js +18 -2
- package/dist/platform/ops/catalog-admin.ops.d.ts +3 -1
- package/dist/platform/ops/catalog-admin.ops.js +32 -4
- package/dist/platform/ops/outbox.ops.d.ts +21 -0
- package/dist/platform/ops/outbox.ops.js +125 -0
- package/dist/platform/ops/schedules.ops.js +40 -1
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +3 -0
- package/dist/platform/route-schemas.js +3 -0
- package/dist/roles/worker.module.js +9 -4
- package/dist/runtime/langgraph-loop.js +13 -5
- package/dist/runtime/limits.d.ts +15 -0
- package/dist/runtime/limits.js +25 -0
- package/dist/runtime/loop.d.ts +8 -3
- package/dist/runtime/loop.js +2 -1
- package/dist/runtime/maintenance.d.ts +4 -2
- package/dist/runtime/maintenance.js +17 -2
- package/dist/runtime/model-call.d.ts +14 -16
- package/dist/runtime/model-call.js +14 -24
- package/dist/runtime/queue-lost-jobs.d.ts +24 -0
- package/dist/runtime/queue-lost-jobs.js +51 -0
- package/dist/runtime/run-control.d.ts +42 -5
- package/dist/runtime/run-control.js +66 -12
- package/dist/runtime/run-finish.d.ts +39 -12
- package/dist/runtime/run-finish.js +97 -14
- package/dist/runtime/run-jobs.d.ts +95 -13
- package/dist/runtime/run-jobs.js +199 -28
- package/dist/runtime/run-orphan-check.job.d.ts +142 -0
- package/dist/runtime/run-orphan-check.job.js +339 -0
- package/dist/runtime/run-processors.d.ts +16 -9
- package/dist/runtime/run-processors.js +1 -1
- package/dist/runtime/run-queue.d.ts +64 -55
- package/dist/runtime/run-queue.js +147 -284
- package/dist/runtime/run-steps.d.ts +1 -2
- package/dist/runtime/run-steps.js +8 -11
- package/dist/runtime/run-worker.d.ts +6 -17
- package/dist/runtime/run-worker.js +32 -34
- package/dist/runtime/runtime.module.d.ts +2 -3
- package/dist/runtime/runtime.module.js +5 -6
- package/dist/runtime/step-log.d.ts +16 -4
- package/dist/runtime/step-log.js +26 -6
- package/dist/runtime/stream.controller.d.ts +2 -4
- package/dist/runtime/stream.controller.js +5 -11
- package/dist/runtime/subagents/child.js +1 -1
- package/dist/runtime/subagents/definition.d.ts +2 -1
- package/dist/runtime/subagents/definition.js +4 -2
- package/dist/runtime/subagents/deliveries.d.ts +6 -3
- package/dist/runtime/subagents/deliveries.js +16 -7
- package/dist/runtime/subagents/dispatch.d.ts +12 -12
- package/dist/runtime/subagents/dispatch.js +13 -38
- package/dist/runtime/subagents/messages.d.ts +5 -1
- package/dist/runtime/subagents/messages.js +11 -3
- package/dist/runtime/subagents/skill-fork.d.ts +3 -3
- package/dist/runtime/subagents/skill-fork.js +5 -5
- package/dist/runtime/subagents/subagents.service.js +1 -1
- package/dist/runtime/subagents/tree-jobs.d.ts +61 -0
- package/dist/runtime/subagents/tree-jobs.js +86 -0
- package/dist/runtime/usage-report-jobs.d.ts +23 -48
- package/dist/runtime/usage-report-jobs.js +67 -119
- package/dist/runtime/usage-report.d.ts +28 -77
- package/dist/runtime/usage-report.js +35 -213
- package/dist/runtime/waits.d.ts +48 -198
- package/dist/runtime/waits.js +103 -805
- package/dist/sandbox/sandbox.service.d.ts +17 -4
- package/dist/sandbox/sandbox.service.js +55 -14
- package/dist/schedules/calendar.d.ts +134 -0
- package/dist/schedules/calendar.js +410 -0
- package/dist/schedules/fire.d.ts +1 -3
- package/dist/schedules/fire.js +5 -10
- package/dist/schedules/run-tracker.d.ts +0 -5
- package/dist/schedules/run-tracker.js +0 -13
- package/dist/schedules/scan.job.js +15 -20
- package/dist/schedules/schedule-math.d.ts +42 -0
- package/dist/schedules/schedule-math.js +52 -0
- package/dist/schedules/scheduled-tasks.service.d.ts +16 -0
- package/dist/schedules/scheduled-tasks.service.js +53 -0
- package/dist/schedules/schedules.controller.d.ts +5 -1
- package/dist/schedules/schedules.controller.js +15 -1
- package/dist/schedules/schedules.schemas.d.ts +35 -0
- package/dist/schedules/schedules.schemas.js +27 -0
- package/dist/schedules/store.d.ts +5 -0
- package/dist/schedules/store.js +20 -0
- package/dist/tools/approvals.service.d.ts +26 -7
- package/dist/tools/approvals.service.js +88 -10
- package/dist/tools/builtin-names.d.ts +5 -3
- package/dist/tools/builtin-names.js +12 -4
- package/dist/tools/builtins/builtin-tools.d.ts +1 -2
- package/dist/tools/builtins/builtin-tools.js +21 -17
- package/dist/tools/builtins/sandbox/bash.js +9 -3
- package/dist/tools/builtins/sandbox/common.d.ts +18 -1
- package/dist/tools/builtins/sandbox/common.js +14 -1
- package/dist/tools/builtins/sandbox/index.d.ts +2 -1
- package/dist/tools/builtins/sandbox/index.js +4 -1
- package/dist/tools/builtins/sandbox/task-list.d.ts +12 -0
- package/dist/tools/builtins/sandbox/task-list.js +130 -0
- package/dist/tools/builtins/sandbox/task-output.d.ts +8 -1
- package/dist/tools/builtins/sandbox/task-output.js +21 -3
- package/dist/tools/builtins/web/extract.d.ts +2 -4
- package/dist/tools/builtins/web/extract.js +2 -5
- package/dist/tools/pipeline.d.ts +1 -1
- package/dist/tools/pipeline.js +1 -1
- package/dist/tools/questions.service.d.ts +15 -6
- package/dist/tools/questions.service.js +20 -8
- package/dist/tools/registry.d.ts +4 -2
- package/dist/tools/registry.js +4 -3
- package/dist/tools/tools.module.js +4 -3
- package/package.json +3 -3
- package/src/migrations/0118_device_policy_ops.sql +6 -0
- package/src/migrations/0119_outbox.sql +22 -0
- package/src/migrations/0120_drop_usage_report_sweep.sql +7 -0
|
@@ -22,7 +22,7 @@ import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
|
22
22
|
import { ModelsModule } from "../models/models.module.js";
|
|
23
23
|
import { ModelPurposesService, PurposeAttemptError } from "../models/purposes.js";
|
|
24
24
|
import { fireObserve, hookDeniedAudit, hookDeniedEvent, hookRequestOf, } from "../runtime/hook-calls.js";
|
|
25
|
-
import {
|
|
25
|
+
import { UsageReportJobs, UsageReportJobsModule } from "../runtime/usage-report-jobs.js";
|
|
26
26
|
import { HIDDEN_FORMAT_RE } from "../text.js";
|
|
27
27
|
import { EventLog, EventLogModule } from "./event-log.js";
|
|
28
28
|
import { typedInvocation } from "./events.js";
|
|
@@ -275,10 +275,8 @@ export async function runTitleJob(d, conversationId, attempt) {
|
|
|
275
275
|
});
|
|
276
276
|
}
|
|
277
277
|
const { written, usage } = await d.writes.writeResult(conversationId, attempt, out, title);
|
|
278
|
-
if (usage)
|
|
278
|
+
if (usage)
|
|
279
279
|
await d.eventLog.notify(conversationId, usage.seq);
|
|
280
|
-
void d.usageReport?.enqueue(conversationId, usage.seq);
|
|
281
|
-
}
|
|
282
280
|
if (written)
|
|
283
281
|
await d.eventLog.publishChanged(conversationId, "title");
|
|
284
282
|
}
|
|
@@ -291,9 +289,9 @@ const usageEvent = (attempt, u) => ({
|
|
|
291
289
|
payload: { step: 0, provider: u.provider, model: u.model, ...u.usage, purpose: "title" },
|
|
292
290
|
});
|
|
293
291
|
async function logUsage(d, conversationId, attempt, u) {
|
|
294
|
-
const
|
|
292
|
+
const ev = await d.writes.writeUsage(conversationId, attempt, u);
|
|
295
293
|
if (ev)
|
|
296
|
-
|
|
294
|
+
await d.eventLog.notify(conversationId, ev.seq);
|
|
297
295
|
}
|
|
298
296
|
/** Returns a claimed attempt (shutdown before the call was sent). */
|
|
299
297
|
async function giveBack(db, conversationId, attempt) {
|
|
@@ -383,17 +381,19 @@ let TitleGenerator = class TitleGenerator {
|
|
|
383
381
|
eventLog;
|
|
384
382
|
purposes;
|
|
385
383
|
host;
|
|
386
|
-
|
|
384
|
+
usage;
|
|
387
385
|
log;
|
|
388
386
|
txHost;
|
|
389
387
|
constructor(kdb, eventLog, purposes,
|
|
390
388
|
/** The title call fires the plugin hooks (`model.request`, `model.response`). */
|
|
391
|
-
host,
|
|
389
|
+
host,
|
|
390
|
+
/** The title call's `usage.report` delivery, owed with its usage event. */
|
|
391
|
+
usage, log, txHost) {
|
|
392
392
|
this.kdb = kdb;
|
|
393
393
|
this.eventLog = eventLog;
|
|
394
394
|
this.purposes = purposes;
|
|
395
395
|
this.host = host;
|
|
396
|
-
this.
|
|
396
|
+
this.usage = usage;
|
|
397
397
|
this.log = log;
|
|
398
398
|
this.txHost = txHost;
|
|
399
399
|
}
|
|
@@ -409,21 +409,20 @@ let TitleGenerator = class TitleGenerator {
|
|
|
409
409
|
purposes: this.purposes,
|
|
410
410
|
log: this.log,
|
|
411
411
|
...(hooks !== undefined && { hooks }),
|
|
412
|
-
...(this.usageReport !== undefined && { usageReport: this.usageReport }),
|
|
413
412
|
...(signal !== undefined && { signal }),
|
|
414
413
|
writes: this,
|
|
415
414
|
}, conversationId, attempt);
|
|
416
415
|
}
|
|
417
416
|
/**
|
|
418
|
-
* Appends the call's `usage` event and writes the title while
|
|
419
|
-
* wins), in one transaction.
|
|
417
|
+
* Appends the call's `usage` event (owing its `usage.report` delivery) and writes the title while
|
|
418
|
+
* `title_source IS NULL` (a rename wins), in one transaction.
|
|
420
419
|
*/
|
|
421
420
|
async writeResult(conversationId, attempt, out, title) {
|
|
422
421
|
// Append first: it locks the conversation row FOR UPDATE before this transaction touches it.
|
|
423
422
|
// Updating the title first held the row FOR NO KEY UPDATE, which lets in another
|
|
424
423
|
// transaction's foreign key check (FOR KEY SHARE, e.g. inserting an approval); the append's
|
|
425
424
|
// FOR UPDATE then waited for that one while it waited for the title row: 40P01
|
|
426
|
-
const
|
|
425
|
+
const ev = await this.appendUsage(conversationId, attempt, out);
|
|
427
426
|
const row = title === null
|
|
428
427
|
? undefined
|
|
429
428
|
: await this.kdb.db
|
|
@@ -435,6 +434,22 @@ let TitleGenerator = class TitleGenerator {
|
|
|
435
434
|
.executeTakeFirst();
|
|
436
435
|
return { written: row !== undefined, usage: ev };
|
|
437
436
|
}
|
|
437
|
+
/**
|
|
438
|
+
* Appends the usage event of a call that failed after it was sent (billed, so reported) and owes
|
|
439
|
+
* its `usage.report` delivery, in one transaction.
|
|
440
|
+
*/
|
|
441
|
+
async writeUsage(conversationId, attempt, u) {
|
|
442
|
+
return this.appendUsage(conversationId, attempt, u);
|
|
443
|
+
}
|
|
444
|
+
/** The usage event and its owed delivery (only when this call wrote it, not on a retry's hit). */
|
|
445
|
+
async appendUsage(conversationId, attempt, u) {
|
|
446
|
+
const { events, fresh } = await this.eventLog.appendInReport(conversationId, [
|
|
447
|
+
usageEvent(attempt, u),
|
|
448
|
+
]);
|
|
449
|
+
if (fresh[0])
|
|
450
|
+
await this.usage.oweEvents(conversationId, events);
|
|
451
|
+
return events[0];
|
|
452
|
+
}
|
|
438
453
|
/** The `hook.denied` audit row of a denied title call. */
|
|
439
454
|
async auditDenied(entry) {
|
|
440
455
|
await audit(this.kdb.db, entry);
|
|
@@ -443,6 +458,12 @@ let TitleGenerator = class TitleGenerator {
|
|
|
443
458
|
__decorate([
|
|
444
459
|
Transactional({ propagation: "requires_new" })
|
|
445
460
|
], TitleGenerator.prototype, "writeResult", null);
|
|
461
|
+
__decorate([
|
|
462
|
+
Transactional({ propagation: "requires_new" })
|
|
463
|
+
], TitleGenerator.prototype, "writeUsage", null);
|
|
464
|
+
__decorate([
|
|
465
|
+
Transactional({ propagation: "mandatory" })
|
|
466
|
+
], TitleGenerator.prototype, "appendUsage", null);
|
|
446
467
|
__decorate([
|
|
447
468
|
Transactional({ propagation: "requires_new" })
|
|
448
469
|
], TitleGenerator.prototype, "auditDenied", null);
|
|
@@ -452,7 +473,7 @@ TitleGenerator = __decorate([
|
|
|
452
473
|
__param(1, Inject(EventLog)),
|
|
453
474
|
__param(2, Inject(ModelPurposesService)),
|
|
454
475
|
__param(3, Inject(ExtensionHost)),
|
|
455
|
-
__param(4, Inject(
|
|
476
|
+
__param(4, Inject(UsageReportJobs)),
|
|
456
477
|
__param(5, Inject(KERNEL_LOGGER)),
|
|
457
478
|
__param(6, Inject(TransactionHost))
|
|
458
479
|
], TitleGenerator);
|
|
@@ -535,7 +556,13 @@ let TitlesModule = class TitlesModule {
|
|
|
535
556
|
};
|
|
536
557
|
TitlesModule = __decorate([
|
|
537
558
|
Module({
|
|
538
|
-
imports: [
|
|
559
|
+
imports: [
|
|
560
|
+
EventLogModule,
|
|
561
|
+
ModelsModule,
|
|
562
|
+
ExtensionsModule,
|
|
563
|
+
UsageReportJobsModule,
|
|
564
|
+
TITLE_QUEUE_MODULE,
|
|
565
|
+
],
|
|
539
566
|
providers: [TitleGenerator, TitleQueue],
|
|
540
567
|
exports: [TitleQueue, TITLE_QUEUE_MODULE],
|
|
541
568
|
})
|
|
@@ -939,6 +939,8 @@ export interface DeviceMachinesTable {
|
|
|
939
939
|
features: ColumnType<string[], string[] | undefined, string[]>;
|
|
940
940
|
/** 0114: the program's local network policy summary (hello, refreshed by each `policy` op). */
|
|
941
941
|
local_policy: ColumnType<unknown | null, string | null | undefined, string | null>;
|
|
942
|
+
/** 0118: the `policy` ops sent whose answer is not recorded yet (`PendingPolicyOp[]`). */
|
|
943
|
+
pending_policy_ops: ColumnType<unknown, string | undefined, string>;
|
|
942
944
|
}
|
|
943
945
|
/** 0088_environments.sql, renamed in 0105: the device a member's new conversations use by default. */
|
|
944
946
|
export interface DeviceDefaultsTable {
|
|
@@ -1156,12 +1158,6 @@ export interface ExtensionReplicaStatusTable {
|
|
|
1156
1158
|
http_replica_id: string | null;
|
|
1157
1159
|
heartbeat_at: Ts;
|
|
1158
1160
|
}
|
|
1159
|
-
/** 0040: the usage.report sweeper's watermark (a single row, id = 1). */
|
|
1160
|
-
export interface UsageReportSweepTable {
|
|
1161
|
-
id: ColumnType<number, number | undefined, never>;
|
|
1162
|
-
watermark: Ts;
|
|
1163
|
-
updated_at: ColumnType<Date, Date | string | undefined, Date | string>;
|
|
1164
|
-
}
|
|
1165
1161
|
/** A model disabled on the platform (`org_id` null) or in one org (0089, spec B §5.3). */
|
|
1166
1162
|
export interface ModelDisabledTable {
|
|
1167
1163
|
org_id: string | null;
|
|
@@ -1181,7 +1177,7 @@ export interface ModelUsageTable {
|
|
|
1181
1177
|
input_tokens: number;
|
|
1182
1178
|
occurred_at: Ts;
|
|
1183
1179
|
reported_at: TsNullable;
|
|
1184
|
-
/** Set when the row's `usage.report` job gave up after its last attempt
|
|
1180
|
+
/** Set when the row's `usage.report` job gave up after its last attempt. */
|
|
1185
1181
|
report_failed_at: TsNullable;
|
|
1186
1182
|
}
|
|
1187
1183
|
type NullableText = ColumnType<string | null, string | null | undefined, string | null>;
|
|
@@ -1310,7 +1306,6 @@ export interface KernelDatabase {
|
|
|
1310
1306
|
extension_blobs: ExtensionBlobsTable;
|
|
1311
1307
|
extension_installs: ExtensionInstallsTable;
|
|
1312
1308
|
extension_replica_status: ExtensionReplicaStatusTable;
|
|
1313
|
-
usage_report_sweep: UsageReportSweepTable;
|
|
1314
1309
|
extension_name_owners: ExtensionNameOwnersTable;
|
|
1315
1310
|
extension_deployment_configs: ExtensionDeploymentConfigsTable;
|
|
1316
1311
|
catalog_items: CatalogItemsTable;
|
|
@@ -1344,6 +1339,29 @@ export interface KernelDatabase {
|
|
|
1344
1339
|
plugin_scope_sync_failures: PluginScopeSyncFailuresTable;
|
|
1345
1340
|
extension_client_builds: ExtensionClientBuildsTable;
|
|
1346
1341
|
extension_client_reports: ExtensionClientReportsTable;
|
|
1342
|
+
outbox: OutboxTable;
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* 0119: the transactional outbox (transactional outbox spec §4). A row is a BullMQ job owed by a
|
|
1346
|
+
* committed transaction; deleted on delivery, so only dead rows (`dead_at` set) stay.
|
|
1347
|
+
*/
|
|
1348
|
+
export interface OutboxTable {
|
|
1349
|
+
/** bigint identity; the pg driver reads it as a string. */
|
|
1350
|
+
id: Generated<string>;
|
|
1351
|
+
queue: string;
|
|
1352
|
+
job_name: string;
|
|
1353
|
+
/** Unique per queue: a second add of the same job before delivery is a no-op. */
|
|
1354
|
+
job_id: string;
|
|
1355
|
+
data: Json;
|
|
1356
|
+
/** The job's delay target; null runs it now. */
|
|
1357
|
+
run_at: Date | null;
|
|
1358
|
+
/** Null for platform work. */
|
|
1359
|
+
org_id: string | null;
|
|
1360
|
+
attempts: Generated<number>;
|
|
1361
|
+
next_attempt_at: Generated<Date>;
|
|
1362
|
+
last_error: string | null;
|
|
1363
|
+
dead_at: Date | null;
|
|
1364
|
+
created_at: Generated<Date>;
|
|
1347
1365
|
}
|
|
1348
1366
|
/**
|
|
1349
1367
|
* 0107: the client files of an extension package (client extensions spec §9.1), keyed by the
|
|
@@ -111,7 +111,8 @@ export interface DeviceSandboxView {
|
|
|
111
111
|
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
|
-
* - `policy` (`device.policy_changed`): a local policy change sent to the machine
|
|
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
116
|
* - `tier` (`device.tier_changed`): a cloud device's tier set by its owner;
|
|
116
117
|
* - `isolation` (`device.isolation_reported`): the machine reported running without a sandbox,
|
|
117
118
|
* or with one again.
|
|
@@ -122,15 +123,23 @@ export interface DeviceSandboxChangeView {
|
|
|
122
123
|
/** Hosts added to / removed from the local allowlist (`policy` only; else empty). */
|
|
123
124
|
add: string[];
|
|
124
125
|
remove: string[];
|
|
125
|
-
/**
|
|
126
|
+
/**
|
|
127
|
+
* The tier set (`to: null`: follow the org's default; `from`, the tier before, only for a change
|
|
128
|
+
* made on the machine); null when the tier did not change.
|
|
129
|
+
*/
|
|
126
130
|
tier: {
|
|
131
|
+
from?: SandboxTier | null;
|
|
127
132
|
to: SandboxTier | null;
|
|
128
133
|
} | null;
|
|
129
134
|
/** The isolation set or reported; null when it did not change. */
|
|
130
135
|
isolation: "sandbox" | "none" | null;
|
|
136
|
+
/** The localhost access now (a change made on the machine only); null when it did not change. */
|
|
137
|
+
localhost: DeviceLocalPolicySummary["localhost"] | null;
|
|
138
|
+
/** Whether the machine takes the platform's changes now (made on the machine); null: unchanged. */
|
|
139
|
+
remoteGrants: DeviceLocalPolicySummary["remoteGrants"] | null;
|
|
131
140
|
/** The deciding account's display name (null when the account is gone). */
|
|
132
141
|
by: string | null;
|
|
133
|
-
/** `device`: reported
|
|
142
|
+
/** `device`: made or reported on the machine itself (a `policy` change, an `isolation`). */
|
|
134
143
|
via: "web" | "im" | "device";
|
|
135
144
|
/** The machine answered with the policy as written (false: the answer never came). */
|
|
136
145
|
confirmed: boolean;
|
|
@@ -1055,7 +1055,16 @@ const tierOrNull = (v) => v === "strict" || v === "dev" || v === "open" ? v : nu
|
|
|
1055
1055
|
function sandboxChangeOf(action, at, d, by) {
|
|
1056
1056
|
const hosts = (v) => Array.isArray(v) ? v.filter((h) => typeof h === "string") : [];
|
|
1057
1057
|
const isolation = d.isolation === "sandbox" || d.isolation === "none" ? d.isolation : null;
|
|
1058
|
-
const base = {
|
|
1058
|
+
const base = {
|
|
1059
|
+
at: at.toISOString(),
|
|
1060
|
+
add: [],
|
|
1061
|
+
remove: [],
|
|
1062
|
+
tier: null,
|
|
1063
|
+
isolation: null,
|
|
1064
|
+
localhost: null,
|
|
1065
|
+
remoteGrants: null,
|
|
1066
|
+
by,
|
|
1067
|
+
};
|
|
1059
1068
|
if (action === "device.tier_changed") {
|
|
1060
1069
|
return { ...base, kind: "tier", tier: { to: tierOrNull(d.to) }, via: "web", confirmed: true };
|
|
1061
1070
|
}
|
|
@@ -1068,9 +1077,15 @@ function sandboxChangeOf(action, at, d, by) {
|
|
|
1068
1077
|
add: hosts(d.add),
|
|
1069
1078
|
remove: hosts(d.remove),
|
|
1070
1079
|
// the key is there only when the change set the tier (null: follow the org's default)
|
|
1071
|
-
tier: "tier" in d
|
|
1080
|
+
tier: "tier" in d
|
|
1081
|
+
? { ...("tierFrom" in d && { from: tierOrNull(d.tierFrom) }), to: tierOrNull(d.tier) }
|
|
1082
|
+
: null,
|
|
1072
1083
|
isolation,
|
|
1073
|
-
|
|
1084
|
+
localhost: d.localhost === "on" || d.localhost === "off-proxy-detected" || d.localhost === "off"
|
|
1085
|
+
? d.localhost
|
|
1086
|
+
: null,
|
|
1087
|
+
remoteGrants: d.remoteGrants === "owner" || d.remoteGrants === "off" ? d.remoteGrants : null,
|
|
1088
|
+
via: d.via === "im" ? "im" : d.via === "device" ? "device" : "web",
|
|
1074
1089
|
confirmed: d.confirmed === true,
|
|
1075
1090
|
};
|
|
1076
1091
|
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -225,10 +225,17 @@ export declare const ERROR_CODES: {
|
|
|
225
225
|
readonly "plugin.upgrade_stale": 409;
|
|
226
226
|
readonly "plugin.upgrade_target_invalid": 409;
|
|
227
227
|
readonly "plugin.upgrade_failed": 502;
|
|
228
|
+
/**
|
|
229
|
+
* The platform's import and update imported the version but the update failed: the platform
|
|
230
|
+
* stays on its version, the imported version is in the catalog (retry with the plain update).
|
|
231
|
+
*/
|
|
232
|
+
readonly "plugin.imported_not_updated": 409;
|
|
228
233
|
readonly "setup.already_completed": 409;
|
|
229
234
|
readonly "setup.token_invalid": 403;
|
|
230
235
|
/** The admin overview's seven-day usage aggregate ran past its statement timeout (spec §5.5). */
|
|
231
236
|
readonly "admin.usage_unavailable": 503;
|
|
237
|
+
/** No dead outbox row has this id (delivered, discarded, retried, or never dead). */
|
|
238
|
+
readonly "outbox.not_found": 404;
|
|
232
239
|
readonly "agent.not_found": 404;
|
|
233
240
|
readonly "agent.version_conflict": 409;
|
|
234
241
|
/** The profile was changed since the client read it (`expectedUpdatedAt` is stale). */
|
|
@@ -319,6 +326,10 @@ export declare const ERROR_CODES: {
|
|
|
319
326
|
readonly "attachment.too_many_pending": 429;
|
|
320
327
|
readonly "sandbox.unavailable": 503;
|
|
321
328
|
readonly "sandbox.busy": 409;
|
|
329
|
+
/** A cloud sandbox provider refused the device's tier (its config refuses open egress). */
|
|
330
|
+
readonly "sandbox.tier_refused": 409;
|
|
331
|
+
/** A cloud sandbox provider failed to create a device's sandbox for another reason. */
|
|
332
|
+
readonly "sandbox.create_failed": 502;
|
|
322
333
|
/** A sandbox file path the kernel reads is not absolute or not under the sandbox roots. */
|
|
323
334
|
readonly "sandbox.file_invalid_path": 422;
|
|
324
335
|
/** No file at the sandbox path the kernel reads. */
|
package/dist/errors.js
CHANGED
|
@@ -225,10 +225,17 @@ export const ERROR_CODES = {
|
|
|
225
225
|
"plugin.upgrade_stale": 409,
|
|
226
226
|
"plugin.upgrade_target_invalid": 409,
|
|
227
227
|
"plugin.upgrade_failed": 502,
|
|
228
|
+
/**
|
|
229
|
+
* The platform's import and update imported the version but the update failed: the platform
|
|
230
|
+
* stays on its version, the imported version is in the catalog (retry with the plain update).
|
|
231
|
+
*/
|
|
232
|
+
"plugin.imported_not_updated": 409,
|
|
228
233
|
"setup.already_completed": 409,
|
|
229
234
|
"setup.token_invalid": 403,
|
|
230
235
|
/** The admin overview's seven-day usage aggregate ran past its statement timeout (spec §5.5). */
|
|
231
236
|
"admin.usage_unavailable": 503,
|
|
237
|
+
/** No dead outbox row has this id (delivered, discarded, retried, or never dead). */
|
|
238
|
+
"outbox.not_found": 404,
|
|
232
239
|
"agent.not_found": 404,
|
|
233
240
|
"agent.version_conflict": 409,
|
|
234
241
|
/** The profile was changed since the client read it (`expectedUpdatedAt` is stale). */
|
|
@@ -319,6 +326,10 @@ export const ERROR_CODES = {
|
|
|
319
326
|
"attachment.too_many_pending": 429,
|
|
320
327
|
"sandbox.unavailable": 503,
|
|
321
328
|
"sandbox.busy": 409,
|
|
329
|
+
/** A cloud sandbox provider refused the device's tier (its config refuses open egress). */
|
|
330
|
+
"sandbox.tier_refused": 409,
|
|
331
|
+
/** A cloud sandbox provider failed to create a device's sandbox for another reason. */
|
|
332
|
+
"sandbox.create_failed": 502,
|
|
322
333
|
/** A sandbox file path the kernel reads is not absolute or not under the sandbox roots. */
|
|
323
334
|
"sandbox.file_invalid_path": 422,
|
|
324
335
|
/** No file at the sandbox path the kernel reads. */
|
package/dist/evals/run-replay.js
CHANGED
|
@@ -19,6 +19,7 @@ import { KernelDb } from "../infra/database/kernel-db.js";
|
|
|
19
19
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
20
20
|
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
21
21
|
import { estimateTokens } from "../runtime/context/tokens.js";
|
|
22
|
+
import { runDeadline, tightestLimit } from "../runtime/limits.js";
|
|
22
23
|
import { RunError } from "../runtime/model-call.js";
|
|
23
24
|
import { runContextIdemKey } from "../runtime/run-context.service.js";
|
|
24
25
|
import { recordedSkillForks } from "../runtime/subagents/skill-fork.js";
|
|
@@ -526,13 +527,18 @@ export async function replayRun(d, s, candidate, evalRunId, signal) {
|
|
|
526
527
|
return out;
|
|
527
528
|
};
|
|
528
529
|
const runtime = d.config.get().runtime;
|
|
529
|
-
|
|
530
|
+
// 0 in the config or the logged child limit = no limit there
|
|
531
|
+
const maxSteps = tightestLimit(subagent?.maxSteps, runtime.maxSteps);
|
|
532
|
+
// `runtime.maxRunSeconds` 0: no deadline and no timer
|
|
533
|
+
const deadline = runDeadline(Date.now(), runtime.maxRunSeconds);
|
|
530
534
|
let deadlineHit = false;
|
|
531
|
-
const timer =
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
535
|
+
const timer = deadline === undefined
|
|
536
|
+
? undefined
|
|
537
|
+
: setTimeout(() => {
|
|
538
|
+
deadlineHit = true;
|
|
539
|
+
stopper.abort();
|
|
540
|
+
}, Math.max(0, deadline - Date.now()));
|
|
541
|
+
timer?.unref();
|
|
536
542
|
// a fork invocation's report is the recorded one: a replay never dispatches a sub-agent (A5)
|
|
537
543
|
const skillForks = recordedSkillForks(eventLog, s.events.filter((e) => e.runId === s.runId));
|
|
538
544
|
const loop = d.loop({ toolPipeline: pipeline, registry, skillForks, ...(media && { media }) });
|
|
@@ -551,8 +557,8 @@ export async function replayRun(d, s, candidate, evalRunId, signal) {
|
|
|
551
557
|
sink: () => { },
|
|
552
558
|
pendingAbsorbable,
|
|
553
559
|
limits: {
|
|
554
|
-
maxSteps
|
|
555
|
-
deadline,
|
|
560
|
+
...(maxSteps !== undefined && { maxSteps }),
|
|
561
|
+
...(deadline !== undefined && { deadline }),
|
|
556
562
|
},
|
|
557
563
|
// replays never fire plugin hooks (the loop deps omit them too, P-18)
|
|
558
564
|
conversationKind: "eval",
|
|
@@ -28,6 +28,7 @@ import { SingleProviderGuard } from "./single-provider.js";
|
|
|
28
28
|
import { ReplicaStatusStore } from "./sources.js";
|
|
29
29
|
import { ExtensionStateModule } from "./state.module.js";
|
|
30
30
|
import { ExtensionAvailability } from "./unavailable.js";
|
|
31
|
+
import { UsageSubscribersModule } from "./usage-subscribers.js";
|
|
31
32
|
/**
|
|
32
33
|
* The extensions domain: the extension host (it builds each `ctx` from the injected kernel
|
|
33
34
|
* services), the package store and install records (the desired state the host converges to, one
|
|
@@ -48,6 +49,8 @@ ExtensionsModule = __decorate([
|
|
|
48
49
|
ExtensionStateModule,
|
|
49
50
|
// ctx.models: the embeddings broker (models domain, below the host through ExtensionStateModule)
|
|
50
51
|
ModelsModule,
|
|
52
|
+
// the lifecycle clears the `usage.report` subscriber cache on PLUGINS_CHANGED
|
|
53
|
+
UsageSubscribersModule,
|
|
51
54
|
],
|
|
52
55
|
providers: [
|
|
53
56
|
PluginInstallLock,
|
|
@@ -7,6 +7,7 @@ import { ExtensionInstalls } from "./installs.js";
|
|
|
7
7
|
import { PLUGINS_CHANGED_TOPIC, PLUGINS_RECONCILE_JOB, PLUGINS_RECONCILE_TOPIC, STALE_RECONCILE_PERIODS } from "./lifecycle-topics.js";
|
|
8
8
|
import { PLUGINS_MIGRATED_TOPIC } from "./migrate-lock.js";
|
|
9
9
|
import { ReplicaStatusStore } from "./sources.js";
|
|
10
|
+
import { UsageSubscribers } from "./usage-subscribers.js";
|
|
10
11
|
export { PLUGINS_CHANGED_TOPIC, PLUGINS_MIGRATED_TOPIC, PLUGINS_RECONCILE_JOB, PLUGINS_RECONCILE_TOPIC, STALE_RECONCILE_PERIODS, };
|
|
11
12
|
/**
|
|
12
13
|
* 每副本一个:把本副本的插件加载状态收敛到 PG 期望状态(C16)。
|
|
@@ -24,6 +25,8 @@ export declare class ExtensionLifecycle {
|
|
|
24
25
|
private readonly installs;
|
|
25
26
|
private readonly config;
|
|
26
27
|
private readonly log;
|
|
28
|
+
/** "Does any plugin subscribe to usage.report": its cached answer is cleared on PLUGINS_CHANGED. */
|
|
29
|
+
private readonly usageSubscribers;
|
|
27
30
|
private running;
|
|
28
31
|
private queued;
|
|
29
32
|
private unsubs;
|
|
@@ -32,7 +35,9 @@ export declare class ExtensionLifecycle {
|
|
|
32
35
|
private stopped;
|
|
33
36
|
constructor(host: ExtensionHost, bus: Pick<ExtensionContext["bus"], "publish" | "subscribe">, status: ReplicaStatusStore,
|
|
34
37
|
/** Every install record's name (any desired state): prunes status rows this replica no longer knows. */
|
|
35
|
-
installs: ExtensionInstalls, config: KernelConfigService, log: KernelLogger
|
|
38
|
+
installs: ExtensionInstalls, config: KernelConfigService, log: KernelLogger,
|
|
39
|
+
/** "Does any plugin subscribe to usage.report": its cached answer is cleared on PLUGINS_CHANGED. */
|
|
40
|
+
usageSubscribers: Pick<UsageSubscribers, "invalidate">);
|
|
36
41
|
/** 订阅三个主题(订阅失败只记 error:周期任务的消息同样收不到时,下次启动再来)。 */
|
|
37
42
|
start(): Promise<void>;
|
|
38
43
|
/**
|
|
@@ -17,6 +17,7 @@ import { ExtensionInstalls } from "./installs.js";
|
|
|
17
17
|
import { PLUGIN_DEPLOYMENT_CONFIG_TOPIC, PLUGINS_CHANGED_TOPIC, PLUGINS_RECONCILE_JOB, PLUGINS_RECONCILE_TOPIC, STALE_RECONCILE_PERIODS, } from "./lifecycle-topics.js";
|
|
18
18
|
import { PLUGINS_MIGRATED_TOPIC } from "./migrate-lock.js";
|
|
19
19
|
import { ReplicaStatusStore } from "./sources.js";
|
|
20
|
+
import { UsageSubscribers } from "./usage-subscribers.js";
|
|
20
21
|
export { PLUGINS_CHANGED_TOPIC, PLUGINS_MIGRATED_TOPIC, PLUGINS_RECONCILE_JOB, PLUGINS_RECONCILE_TOPIC, STALE_RECONCILE_PERIODS, };
|
|
21
22
|
/** 心跳超过这么多个周期的行直接删除(副本早已退出;读的一方本来就不计入)。 */
|
|
22
23
|
const PRUNE_RECONCILE_PERIODS = 10;
|
|
@@ -35,6 +36,7 @@ let ExtensionLifecycle = class ExtensionLifecycle {
|
|
|
35
36
|
installs;
|
|
36
37
|
config;
|
|
37
38
|
log;
|
|
39
|
+
usageSubscribers;
|
|
38
40
|
running;
|
|
39
41
|
queued;
|
|
40
42
|
unsubs = [];
|
|
@@ -43,13 +45,16 @@ let ExtensionLifecycle = class ExtensionLifecycle {
|
|
|
43
45
|
stopped = false;
|
|
44
46
|
constructor(host, bus, status,
|
|
45
47
|
/** Every install record's name (any desired state): prunes status rows this replica no longer knows. */
|
|
46
|
-
installs, config, log
|
|
48
|
+
installs, config, log,
|
|
49
|
+
/** "Does any plugin subscribe to usage.report": its cached answer is cleared on PLUGINS_CHANGED. */
|
|
50
|
+
usageSubscribers) {
|
|
47
51
|
this.host = host;
|
|
48
52
|
this.bus = bus;
|
|
49
53
|
this.status = status;
|
|
50
54
|
this.installs = installs;
|
|
51
55
|
this.config = config;
|
|
52
56
|
this.log = log;
|
|
57
|
+
this.usageSubscribers = usageSubscribers;
|
|
53
58
|
}
|
|
54
59
|
/** 订阅三个主题(订阅失败只记 error:周期任务的消息同样收不到时,下次启动再来)。 */
|
|
55
60
|
async start() {
|
|
@@ -65,6 +70,7 @@ let ExtensionLifecycle = class ExtensionLifecycle {
|
|
|
65
70
|
await on(PLUGINS_CHANGED_TOPIC, (p) => {
|
|
66
71
|
// 安装 / 卸载改变了「部署已知」的插件名:路由闸门不等 TTL
|
|
67
72
|
this.host.invalidateKnownNames();
|
|
73
|
+
this.usageSubscribers.invalidate();
|
|
68
74
|
this.trigger(`changed:${pluginOf(p)}`);
|
|
69
75
|
});
|
|
70
76
|
await on(PLUGINS_RECONCILE_TOPIC, () => this.trigger("periodic"));
|
|
@@ -176,7 +182,8 @@ ExtensionLifecycle = __decorate([
|
|
|
176
182
|
__param(2, Inject(ReplicaStatusStore)),
|
|
177
183
|
__param(3, Inject(ExtensionInstalls)),
|
|
178
184
|
__param(4, Inject(KernelConfigService)),
|
|
179
|
-
__param(5, Inject(KERNEL_LOGGER))
|
|
185
|
+
__param(5, Inject(KERNEL_LOGGER)),
|
|
186
|
+
__param(6, Inject(UsageSubscribers))
|
|
180
187
|
], ExtensionLifecycle);
|
|
181
188
|
export { ExtensionLifecycle };
|
|
182
189
|
const pluginOf = (p) => typeof p === "object" && p !== null && typeof p.plugin === "string"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { KernelSchemaDb } from "../db/kernel-schema.js";
|
|
2
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
3
|
+
/**
|
|
4
|
+
* Plugins that subscribe to `usage.report` according to deployment state: an approved permission
|
|
5
|
+
* snapshot that declares the `usage.report` hook, for a plugin the deployment still knows: an enabled
|
|
6
|
+
* install, or a config-declared plugin with a replica status row that is not `unloaded`. With `orgId`, only plugins
|
|
7
|
+
* the organization enabled (org config rows and required bootstrap plugins).
|
|
8
|
+
*/
|
|
9
|
+
export declare function queryUsageSubscribers(db: KernelSchemaDb, orgId?: string): Promise<Set<string>>;
|
|
10
|
+
/** How long one replica trusts its answer to "does any plugin subscribe" (spec §8.1). */
|
|
11
|
+
export declare const USAGE_SUBSCRIBERS_TTL_MS = 30000;
|
|
12
|
+
/**
|
|
13
|
+
* Test seam (`KernelOverride`): the clock {@link UsageSubscribers} ages its cached answer with
|
|
14
|
+
* (default `Date.now`); the kernel never provides it.
|
|
15
|
+
*/
|
|
16
|
+
export declare const USAGE_SUBSCRIBERS_CLOCK: unique symbol;
|
|
17
|
+
/**
|
|
18
|
+
* Whether the deployment has at least one `usage.report` subscriber ({@link queryUsageSubscribers}
|
|
19
|
+
* without an org), which decides whether a usage write owes a delivery (transactional outbox spec
|
|
20
|
+
* §8.1): a usage event written while no plugin subscribes is never reported, later subscribers
|
|
21
|
+
* included. The answer comes from PG, not from this replica's loaded handlers, so a replica that
|
|
22
|
+
* has no handler still owes the delivery another replica makes.
|
|
23
|
+
*
|
|
24
|
+
* Per-process memoisation of that PG read: kept {@link USAGE_SUBSCRIBERS_TTL_MS} and cleared on
|
|
25
|
+
* `PLUGINS_CHANGED` (`ExtensionLifecycle` calls {@link invalidate}), so an enabled subscriber is
|
|
26
|
+
* seen at once, or within 30 s when the bus message is lost. It holds no state another request or
|
|
27
|
+
* replica relies on.
|
|
28
|
+
*/
|
|
29
|
+
export declare class UsageSubscribers {
|
|
30
|
+
private readonly kdb;
|
|
31
|
+
private readonly now;
|
|
32
|
+
private cached;
|
|
33
|
+
/** Bumped by {@link invalidate}: a read that started before it does not refill the cache. */
|
|
34
|
+
private generation;
|
|
35
|
+
constructor(kdb: KernelDb, now?: () => number);
|
|
36
|
+
/** Whether any plugin subscribes (cached; the read joins the caller's transaction, if any). */
|
|
37
|
+
any(): Promise<boolean>;
|
|
38
|
+
/** Forgets the cached answer (a plugin was installed, enabled, upgraded or removed). */
|
|
39
|
+
invalidate(): void;
|
|
40
|
+
}
|
|
41
|
+
/** {@link UsageSubscribers}: below the extension host, so the model broker and the runtime use it. */
|
|
42
|
+
export declare class UsageSubscribersModule {
|
|
43
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { Inject, Injectable, Module, Optional } from "@nestjs/common";
|
|
11
|
+
import { sql } from "kysely";
|
|
12
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
13
|
+
import { enabledExtensionsQuery } from "./enabled.js";
|
|
14
|
+
/**
|
|
15
|
+
* Plugins that subscribe to `usage.report` according to deployment state: an approved permission
|
|
16
|
+
* snapshot that declares the `usage.report` hook, for a plugin the deployment still knows: an enabled
|
|
17
|
+
* install, or a config-declared plugin with a replica status row that is not `unloaded`. With `orgId`, only plugins
|
|
18
|
+
* the organization enabled (org config rows and required bootstrap plugins).
|
|
19
|
+
*/
|
|
20
|
+
export async function queryUsageSubscribers(db, orgId) {
|
|
21
|
+
let q = db
|
|
22
|
+
.selectFrom("extension_permission_snapshots as s")
|
|
23
|
+
.select("s.plugin")
|
|
24
|
+
.where(sql `s.permissions -> 'permissions' -> 'hooks' ? 'usage.report'`)
|
|
25
|
+
.where((eb) => eb.or([
|
|
26
|
+
eb.exists(eb
|
|
27
|
+
.selectFrom("extension_installs as i")
|
|
28
|
+
.select(sql `1`.as("one"))
|
|
29
|
+
.whereRef("i.name", "=", "s.plugin")
|
|
30
|
+
.where("i.desired_state", "=", "enabled")),
|
|
31
|
+
// config-declared plugins still declared (and not unloaded) on some replica; installs are
|
|
32
|
+
// covered by desired_state above (their replica rows linger as `unloaded` when disabled)
|
|
33
|
+
eb.exists(eb
|
|
34
|
+
.selectFrom("extension_replica_status as r")
|
|
35
|
+
.select(sql `1`.as("one"))
|
|
36
|
+
.whereRef("r.plugin", "=", "s.plugin")
|
|
37
|
+
.where("r.source", "=", "config")
|
|
38
|
+
.where("r.state", "<>", "unloaded")),
|
|
39
|
+
]));
|
|
40
|
+
if (orgId !== undefined) {
|
|
41
|
+
q = q.where("s.plugin", "in", enabledExtensionsQuery(db, orgId));
|
|
42
|
+
}
|
|
43
|
+
return new Set((await q.execute()).map((r) => r.plugin));
|
|
44
|
+
}
|
|
45
|
+
/** How long one replica trusts its answer to "does any plugin subscribe" (spec §8.1). */
|
|
46
|
+
export const USAGE_SUBSCRIBERS_TTL_MS = 30_000;
|
|
47
|
+
/**
|
|
48
|
+
* Test seam (`KernelOverride`): the clock {@link UsageSubscribers} ages its cached answer with
|
|
49
|
+
* (default `Date.now`); the kernel never provides it.
|
|
50
|
+
*/
|
|
51
|
+
export const USAGE_SUBSCRIBERS_CLOCK = Symbol("USAGE_SUBSCRIBERS_CLOCK");
|
|
52
|
+
/**
|
|
53
|
+
* Whether the deployment has at least one `usage.report` subscriber ({@link queryUsageSubscribers}
|
|
54
|
+
* without an org), which decides whether a usage write owes a delivery (transactional outbox spec
|
|
55
|
+
* §8.1): a usage event written while no plugin subscribes is never reported, later subscribers
|
|
56
|
+
* included. The answer comes from PG, not from this replica's loaded handlers, so a replica that
|
|
57
|
+
* has no handler still owes the delivery another replica makes.
|
|
58
|
+
*
|
|
59
|
+
* Per-process memoisation of that PG read: kept {@link USAGE_SUBSCRIBERS_TTL_MS} and cleared on
|
|
60
|
+
* `PLUGINS_CHANGED` (`ExtensionLifecycle` calls {@link invalidate}), so an enabled subscriber is
|
|
61
|
+
* seen at once, or within 30 s when the bus message is lost. It holds no state another request or
|
|
62
|
+
* replica relies on.
|
|
63
|
+
*/
|
|
64
|
+
let UsageSubscribers = class UsageSubscribers {
|
|
65
|
+
kdb;
|
|
66
|
+
now;
|
|
67
|
+
cached;
|
|
68
|
+
/** Bumped by {@link invalidate}: a read that started before it does not refill the cache. */
|
|
69
|
+
generation = 0;
|
|
70
|
+
constructor(kdb, now = Date.now) {
|
|
71
|
+
this.kdb = kdb;
|
|
72
|
+
this.now = now;
|
|
73
|
+
}
|
|
74
|
+
/** Whether any plugin subscribes (cached; the read joins the caller's transaction, if any). */
|
|
75
|
+
async any() {
|
|
76
|
+
const at = this.now();
|
|
77
|
+
const c = this.cached;
|
|
78
|
+
if (c !== undefined && at - c.at < USAGE_SUBSCRIBERS_TTL_MS)
|
|
79
|
+
return c.any;
|
|
80
|
+
const generation = this.generation;
|
|
81
|
+
const any = (await queryUsageSubscribers(this.kdb.db)).size > 0;
|
|
82
|
+
if (generation === this.generation)
|
|
83
|
+
this.cached = { any, at };
|
|
84
|
+
return any;
|
|
85
|
+
}
|
|
86
|
+
/** Forgets the cached answer (a plugin was installed, enabled, upgraded or removed). */
|
|
87
|
+
invalidate() {
|
|
88
|
+
this.generation++;
|
|
89
|
+
this.cached = undefined;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
UsageSubscribers = __decorate([
|
|
93
|
+
Injectable(),
|
|
94
|
+
__param(0, Inject(KernelDb)),
|
|
95
|
+
__param(1, Optional()),
|
|
96
|
+
__param(1, Inject(USAGE_SUBSCRIBERS_CLOCK))
|
|
97
|
+
], UsageSubscribers);
|
|
98
|
+
export { UsageSubscribers };
|
|
99
|
+
/** {@link UsageSubscribers}: below the extension host, so the model broker and the runtime use it. */
|
|
100
|
+
let UsageSubscribersModule = class UsageSubscribersModule {
|
|
101
|
+
};
|
|
102
|
+
UsageSubscribersModule = __decorate([
|
|
103
|
+
Module({
|
|
104
|
+
providers: [UsageSubscribers],
|
|
105
|
+
exports: [UsageSubscribers],
|
|
106
|
+
})
|
|
107
|
+
], UsageSubscribersModule);
|
|
108
|
+
export { UsageSubscribersModule };
|
package/dist/http/api.module.js
CHANGED
|
@@ -12,6 +12,7 @@ import { AdminAgentsController } from "../admin/admin-agents.controller.js";
|
|
|
12
12
|
import { AdminConnectorsController } from "../admin/admin-connectors.controller.js";
|
|
13
13
|
import { AdminAuditController } from "../admin/audit.controller.js";
|
|
14
14
|
import { AdminExtensionsController } from "../admin/extensions.controller.js";
|
|
15
|
+
import { OutboxDeadController } from "../admin/outbox-dead.controller.js";
|
|
15
16
|
import { AdminOverviewController } from "../admin/overview.controller.js";
|
|
16
17
|
import { SetupController } from "../admin/setup.controller.js";
|
|
17
18
|
import { AgentsController } from "../agents/agents.controller.js";
|
|
@@ -107,6 +108,7 @@ const CONTROLLERS = [
|
|
|
107
108
|
AdminConnectorsController,
|
|
108
109
|
AdminAuditController,
|
|
109
110
|
AdminOverviewController,
|
|
111
|
+
OutboxDeadController,
|
|
110
112
|
AdminExtensionsController,
|
|
111
113
|
AdminCatalogController,
|
|
112
114
|
AdminComponentsController,
|