@capybara-hq/kernel 0.1.0-rc.24 → 0.1.0-rc.25
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/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/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/inbound.js +2 -2
- package/dist/channels/threads.d.ts +4 -3
- package/dist/channels/threads.js +4 -3
- package/dist/conversations/conversations.controller.js +4 -2
- package/dist/conversations/conversations.service.d.ts +17 -5
- package/dist/conversations/conversations.service.js +17 -5
- package/dist/conversations/events.d.ts +21 -0
- package/dist/conversations/events.js +24 -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 +9 -14
- package/dist/devices/devices.service.d.ts +11 -8
- package/dist/devices/devices.service.js +16 -23
- package/dist/evals/run-replay.d.ts +13 -2
- package/dist/evals/run-replay.js +28 -1
- 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/registry.d.ts +124 -0
- package/dist/infra/outbox/registry.js +126 -0
- package/dist/infra/queue/queue-names.d.ts +1 -1
- package/dist/infra/queue/queue-names.js +1 -0
- 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 +15 -6
- package/dist/overrides.d.ts +3 -0
- package/dist/overrides.js +2 -0
- 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 +5 -3
- package/dist/runtime/maintenance.js +36 -11
- 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 +69 -0
- package/dist/runtime/plugin-context.service.js +208 -0
- package/dist/runtime/run-control.d.ts +6 -1
- package/dist/runtime/run-control.js +13 -0
- package/dist/runtime/run-queue.d.ts +13 -0
- package/dist/runtime/run-queue.js +63 -0
- package/dist/runtime/run-worker.d.ts +22 -1
- package/dist/runtime/run-worker.js +107 -10
- package/dist/runtime/runtime.module.js +2 -0
- package/dist/runtime/subagents/dispatch.js +1 -0
- package/dist/runtime/subagents/skill-fork.js +3 -0
- 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 +13 -32
- package/dist/schedules/scan.job.js +35 -144
- 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/changes.controller.d.ts +1 -1
- package/dist/skills/changes.controller.js +1 -1
- package/dist/tools/approvals.service.d.ts +15 -4
- package/dist/tools/approvals.service.js +26 -10
- 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/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/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
|
@@ -9,7 +9,6 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
9
9
|
};
|
|
10
10
|
import { randomUUID } from "node:crypto";
|
|
11
11
|
import { CHANNEL_EXTENSION_POINT } from "@capybara-hq/plugin-sdk";
|
|
12
|
-
import { getQueueToken } from "@nestjs/bullmq";
|
|
13
12
|
import { Inject, Injectable } from "@nestjs/common";
|
|
14
13
|
import { AgentLookup } from "../agents/agent-lookup.js";
|
|
15
14
|
import { ConversationAccess } from "../conversations/access.js";
|
|
@@ -18,16 +17,15 @@ import { ApiError } from "../errors.js";
|
|
|
18
17
|
import { ExtensionRegistry } from "../extensions/registry.js";
|
|
19
18
|
import { audit } from "../identity/audit.js";
|
|
20
19
|
import { KernelConfigService } from "../infra/config/kernel-config.service.js";
|
|
21
|
-
import { afterCommit } from "../infra/database/after-commit.js";
|
|
22
20
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
23
21
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
24
22
|
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
23
|
+
import { OutboxService } from "../infra/outbox/outbox.service.js";
|
|
25
24
|
import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
26
25
|
import { DevicePolicyService } from "../local-devices/device-policy.service.js";
|
|
27
26
|
import { orgAllows } from "../local-devices/network-grants.js";
|
|
28
27
|
import { OrgLock } from "../orgs/org-lock.js";
|
|
29
28
|
import { can, roleAtLeast } from "../orgs/roles.js";
|
|
30
|
-
import { MAINTENANCE_QUEUE_NAME } from "../runtime/maintenance-queue.js";
|
|
31
29
|
import { ProviderRegistry } from "../runtime/providers.js";
|
|
32
30
|
import { SandboxService } from "../sandbox/sandbox.service.js";
|
|
33
31
|
import { isTimeZone } from "../time-zone.js";
|
|
@@ -36,7 +34,7 @@ import { assembleCalendar, CALENDAR_LIMITS, CALENDAR_RANGE_DAYS, CALENDAR_TASK_R
|
|
|
36
34
|
import { readScheduleSettings } from "./effective-settings.js";
|
|
37
35
|
import { grantNotAllowed, scheduleDisabled, scheduleForbidden, scheduleNotFound, targetInvalid, } from "./errors.js";
|
|
38
36
|
import { grantRefusal, grantRefusalOf, isNetworkGrantKey, isPolicyGrantKey, isTierGrantKey, NETWORK_GRANT_PREFIX, NETWORK_GRANT_SPEC_HASH, networkGrantHost, networkGrantRefusal, TIER_GRANT_SPEC_HASH, tierGrantRefusal, } from "./grant-rules.js";
|
|
39
|
-
import {
|
|
37
|
+
import { oweSchedFire, oweSchedSettleUnfired } from "./job-ids.js";
|
|
40
38
|
import { pauseTaskIn } from "./pause.js";
|
|
41
39
|
import { jitterForGap, minGapMinutes, nextTick, validateTrigger } from "./schedule-math.js";
|
|
42
40
|
import { ScheduledTaskStore } from "./store.js";
|
|
@@ -75,14 +73,14 @@ let ScheduledTasksService = class ScheduledTasksService {
|
|
|
75
73
|
extensions;
|
|
76
74
|
providers;
|
|
77
75
|
log;
|
|
78
|
-
|
|
76
|
+
outbox;
|
|
79
77
|
sandbox;
|
|
80
78
|
devicePolicy;
|
|
81
79
|
constructor(db, txHost, config, store, orgLock, agents, access, tools, extensions,
|
|
82
80
|
/** A task's model must resolve for the org and declare its effort (as a message's switch). */
|
|
83
81
|
providers, log,
|
|
84
|
-
/** "Run now"
|
|
85
|
-
|
|
82
|
+
/** "Run now" owes the fire job (consumed by the worker role). */
|
|
83
|
+
outbox,
|
|
86
84
|
/** Network grants: the org's effective egress (network ask-to-allow spec §8.1). */
|
|
87
85
|
sandbox,
|
|
88
86
|
/** Network grants: the task's device's local policy summary (`remoteGrants`). */
|
|
@@ -98,7 +96,7 @@ let ScheduledTasksService = class ScheduledTasksService {
|
|
|
98
96
|
this.extensions = extensions;
|
|
99
97
|
this.providers = providers;
|
|
100
98
|
this.log = log;
|
|
101
|
-
this.
|
|
99
|
+
this.outbox = outbox;
|
|
102
100
|
this.sandbox = sandbox;
|
|
103
101
|
this.devicePolicy = devicePolicy;
|
|
104
102
|
}
|
|
@@ -525,6 +523,8 @@ let ScheduledTasksService = class ScheduledTasksService {
|
|
|
525
523
|
disabledTools: d.disabledTools ?? [],
|
|
526
524
|
subagentTools: d.subagentTools,
|
|
527
525
|
subagents: d.subagents,
|
|
526
|
+
// no conversation at hand: a web one's tools; a run elsewhere still refuses by spec.sources
|
|
527
|
+
source: "web",
|
|
528
528
|
});
|
|
529
529
|
const ctx = {
|
|
530
530
|
role,
|
|
@@ -789,6 +789,8 @@ let ScheduledTasksService = class ScheduledTasksService {
|
|
|
789
789
|
disabledTools: d.disabledTools ?? [],
|
|
790
790
|
subagentTools: d.subagentTools,
|
|
791
791
|
subagents: d.subagents,
|
|
792
|
+
// no conversation at hand: a web one's tools; a run elsewhere still refuses by spec.sources
|
|
793
|
+
source: "web",
|
|
792
794
|
});
|
|
793
795
|
return new Map(tools.map((x) => [
|
|
794
796
|
x.key,
|
|
@@ -820,7 +822,7 @@ let ScheduledTasksService = class ScheduledTasksService {
|
|
|
820
822
|
/**
|
|
821
823
|
* "Run now" (spec §6), creator only: a task run for the request time, outside the schedule (no
|
|
822
824
|
* unique-key conflict with a tick, and `next_run_at` does not move), fired at once through the
|
|
823
|
-
* fire job
|
|
825
|
+
* fire job its transaction owes. `trial`: an attended trial run whose approvals become the task's
|
|
824
826
|
* grants (§7). Counted in the rate limit like any run. An ended task cannot run; a paused one
|
|
825
827
|
* can (only its creator gets here: the run is `manual`, which the fire's paused check lets by).
|
|
826
828
|
*/
|
|
@@ -850,10 +852,9 @@ let ScheduledTasksService = class ScheduledTasksService {
|
|
|
850
852
|
target: task.id,
|
|
851
853
|
detail: { taskRunId: run.id, trial: opts.trial },
|
|
852
854
|
});
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
});
|
|
855
|
+
await oweSchedFire(this.outbox, run.id, orgId);
|
|
856
|
+
// a fire job that fails for good before its claim leaves the run to this check (`removed`)
|
|
857
|
+
await oweSchedSettleUnfired(this.outbox, run.id, orgId, run.scheduledFor, null);
|
|
857
858
|
return run;
|
|
858
859
|
}
|
|
859
860
|
/** The live role of a member of the org; not a member → `schedule.not_found`. */
|
|
@@ -1083,6 +1084,8 @@ let ScheduledTasksService = class ScheduledTasksService {
|
|
|
1083
1084
|
disabledTools: d.disabledTools ?? [],
|
|
1084
1085
|
subagentTools: d.subagentTools,
|
|
1085
1086
|
subagents: d.subagents,
|
|
1087
|
+
// no conversation at hand: a web one's tools; a run elsewhere still refuses by spec.sources
|
|
1088
|
+
source: "web",
|
|
1086
1089
|
});
|
|
1087
1090
|
for (const g of toolGrants) {
|
|
1088
1091
|
const tool = tools.find((x) => x.key === g.toolKey);
|
|
@@ -1168,7 +1171,7 @@ ScheduledTasksService = __decorate([
|
|
|
1168
1171
|
__param(8, Inject(ExtensionRegistry)),
|
|
1169
1172
|
__param(9, Inject(ProviderRegistry)),
|
|
1170
1173
|
__param(10, Inject(KERNEL_LOGGER)),
|
|
1171
|
-
__param(11, Inject(
|
|
1174
|
+
__param(11, Inject(OutboxService)),
|
|
1172
1175
|
__param(12, Inject(SandboxService)),
|
|
1173
1176
|
__param(13, Inject(DevicePolicyService))
|
|
1174
1177
|
], ScheduledTasksService);
|
|
@@ -11,10 +11,8 @@ import { Inject, Module } from "@nestjs/common";
|
|
|
11
11
|
import { AgentLookupModule } from "../agents/agent-lookup.js";
|
|
12
12
|
import { ConversationAccessModule } from "../conversations/access.js";
|
|
13
13
|
import { ExtensionRegistryModule } from "../extensions/registry.js";
|
|
14
|
-
import { QueueModule } from "../infra/queue/queue.module.js";
|
|
15
14
|
import { LocalDevicesModule } from "../local-devices/local-devices.module.js";
|
|
16
15
|
import { OrgLockModule } from "../orgs/org-lock.js";
|
|
17
|
-
import { MAINTENANCE_QUEUE_NAME } from "../runtime/maintenance-queue.js";
|
|
18
16
|
import { ProvidersModule } from "../runtime/providers.js";
|
|
19
17
|
import { SandboxModule } from "../sandbox/sandbox.module.js";
|
|
20
18
|
import { ScheduleToolsPort } from "../tools/builtins/schedules.js";
|
|
@@ -53,8 +51,6 @@ SchedulesModule = __decorate([
|
|
|
53
51
|
// network grants: the org's effective egress and the task device's policy summary
|
|
54
52
|
SandboxModule,
|
|
55
53
|
LocalDevicesModule,
|
|
56
|
-
// "run now" enqueues the fire job
|
|
57
|
-
QueueModule.registerQueue(MAINTENANCE_QUEUE_NAME),
|
|
58
54
|
],
|
|
59
55
|
providers: [ScheduledTasksService],
|
|
60
56
|
exports: [ScheduledTasksService, ScheduledTaskStoreModule],
|
|
@@ -40,7 +40,7 @@ export declare class InboxController {
|
|
|
40
40
|
private readonly inbox;
|
|
41
41
|
constructor(inbox: InboxService);
|
|
42
42
|
list(principal: Principal, org: OrgContext): Promise<InboxView>;
|
|
43
|
-
/** An org admin dismisses an org notice
|
|
43
|
+
/** An org admin dismisses an org notice -> 204. */
|
|
44
44
|
dismissNotice(principal: Principal, org: OrgContext, params: z.infer<typeof NoticeParamsSchema>): Promise<void>;
|
|
45
45
|
}
|
|
46
46
|
export {};
|
|
@@ -86,7 +86,7 @@ let InboxController = class InboxController {
|
|
|
86
86
|
const p = requireUser(principal);
|
|
87
87
|
return this.inbox.list(org.orgId, p.account.id);
|
|
88
88
|
}
|
|
89
|
-
/** An org admin dismisses an org notice
|
|
89
|
+
/** An org admin dismisses an org notice -> 204. */
|
|
90
90
|
async dismissNotice(principal, org, params) {
|
|
91
91
|
const p = requireUser(principal);
|
|
92
92
|
await this.inbox.dismissNotice(org.orgId, p.account.id, params.id);
|
|
@@ -338,11 +338,22 @@ export declare class ApprovalsService {
|
|
|
338
338
|
*/
|
|
339
339
|
cancelPendingIn(runId: string): Promise<ApprovalRecord[]>;
|
|
340
340
|
/**
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
341
|
+
* The conversations of `orgId` holding a pending approval `accountId` is asked to decide
|
|
342
|
+
* (`requested_for`), read without locks: offboarding locks them, by tree root first
|
|
343
|
+
* (`lockConversationTrees`), before {@link cancelPendingFor}.
|
|
344
344
|
*/
|
|
345
|
-
|
|
345
|
+
pendingConversationsFor(orgId: string, accountId: string): Promise<string[]>;
|
|
346
|
+
/**
|
|
347
|
+
* In the caller's transaction (`mandatory`; offboarding): cancels every pending approval of
|
|
348
|
+
* `orgId` that `accountId` is asked to decide (`requested_for`) in `conversationIds`, the
|
|
349
|
+
* conversations the caller already locked (by tree root first, `lockConversationTrees`; lock
|
|
350
|
+
* order: conversations, then approval rows), along the same path as {@link cancelPendingIn}:
|
|
351
|
+
* `cancelled`, `approval_decided{cancelled}` with no decider. An approval asked meanwhile in
|
|
352
|
+
* another conversation is left to its run's stop, so no conversation is locked after a row.
|
|
353
|
+
* Returns the cancelled records and the highest seq written per conversation; the caller
|
|
354
|
+
* publishes after the commit.
|
|
355
|
+
*/
|
|
356
|
+
cancelPendingFor(orgId: string, accountId: string, conversationIds: readonly string[]): Promise<{
|
|
346
357
|
records: ApprovalRecord[];
|
|
347
358
|
seqs: Map<string, number>;
|
|
348
359
|
}>;
|
|
@@ -13,6 +13,7 @@ import { Inject, Injectable } from "@nestjs/common";
|
|
|
13
13
|
import { sql } from "kysely";
|
|
14
14
|
import { EventLog } from "../conversations/event-log.js";
|
|
15
15
|
import { NETWORK_ASK_MAX_HOSTS, NETWORK_HOST_MAX_CHARS, } from "../conversations/events.js";
|
|
16
|
+
import { conversationSourceOf } from "../conversations/source.js";
|
|
16
17
|
import { ApiError } from "../errors.js";
|
|
17
18
|
import { audit } from "../identity/audit.js";
|
|
18
19
|
import { KernelConfigService } from "../infra/config/kernel-config.service.js";
|
|
@@ -814,6 +815,8 @@ let ApprovalsService = class ApprovalsService {
|
|
|
814
815
|
disabledTools: def?.disabledTools ?? [],
|
|
815
816
|
subagentTools: def?.subagentTools,
|
|
816
817
|
subagents: def?.subagents,
|
|
818
|
+
// the tools of the trial run's conversation, where the approval was asked
|
|
819
|
+
source: await conversationSourceOf(t, a.conversationId),
|
|
817
820
|
});
|
|
818
821
|
const tool = tools.find((x) => x.key === a.toolKey);
|
|
819
822
|
const { forbiddenTools } = await readScheduleSettings(t, this.config, a.orgId);
|
|
@@ -975,21 +978,34 @@ let ApprovalsService = class ApprovalsService {
|
|
|
975
978
|
return out;
|
|
976
979
|
}
|
|
977
980
|
/**
|
|
978
|
-
*
|
|
979
|
-
*
|
|
980
|
-
*
|
|
981
|
+
* The conversations of `orgId` holding a pending approval `accountId` is asked to decide
|
|
982
|
+
* (`requested_for`), read without locks: offboarding locks them, by tree root first
|
|
983
|
+
* (`lockConversationTrees`), before {@link cancelPendingFor}.
|
|
981
984
|
*/
|
|
982
|
-
async
|
|
983
|
-
const
|
|
984
|
-
// the conversations first, in id order (lock order: conversation, then approval rows)
|
|
985
|
-
const convs = await this.lockConversations(t
|
|
985
|
+
async pendingConversationsFor(orgId, accountId) {
|
|
986
|
+
const rows = await this.db
|
|
986
987
|
.selectFrom("approvals")
|
|
987
988
|
.select("conversation_id")
|
|
989
|
+
.distinct()
|
|
988
990
|
.where("org_id", "=", orgId)
|
|
989
991
|
.where("requested_for", "=", accountId)
|
|
990
|
-
.where("status", "=", "pending")
|
|
991
|
-
|
|
992
|
-
|
|
992
|
+
.where("status", "=", "pending")
|
|
993
|
+
.execute();
|
|
994
|
+
return rows.map((r) => r.conversation_id);
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* In the caller's transaction (`mandatory`; offboarding): cancels every pending approval of
|
|
998
|
+
* `orgId` that `accountId` is asked to decide (`requested_for`) in `conversationIds`, the
|
|
999
|
+
* conversations the caller already locked (by tree root first, `lockConversationTrees`; lock
|
|
1000
|
+
* order: conversations, then approval rows), along the same path as {@link cancelPendingIn}:
|
|
1001
|
+
* `cancelled`, `approval_decided{cancelled}` with no decider. An approval asked meanwhile in
|
|
1002
|
+
* another conversation is left to its run's stop, so no conversation is locked after a row.
|
|
1003
|
+
* Returns the cancelled records and the highest seq written per conversation; the caller
|
|
1004
|
+
* publishes after the commit.
|
|
1005
|
+
*/
|
|
1006
|
+
async cancelPendingFor(orgId, accountId, conversationIds) {
|
|
1007
|
+
const t = this.db.db;
|
|
1008
|
+
const convs = [...conversationIds];
|
|
993
1009
|
if (convs.length === 0)
|
|
994
1010
|
return { records: [], seqs: new Map() };
|
|
995
1011
|
const rows = await t
|
package/dist/tools/pipeline.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type FormAnswer, type InputForm, type Logger } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import { type ConversationSource, type FormAnswer, type InputForm, type Logger } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import type { AgentDefinition } from "../agents/definition.js";
|
|
3
3
|
import { EventLog } from "../conversations/event-log.js";
|
|
4
4
|
import type { KernelSchemaDb } from "../db/kernel-schema.js";
|
|
@@ -222,8 +222,17 @@ export interface ToolStepContext {
|
|
|
222
222
|
/** 运行所用的 agent 版本(审计链记录)。 */
|
|
223
223
|
agentVersion: number;
|
|
224
224
|
agent: AgentDefinition;
|
|
225
|
-
/**
|
|
225
|
+
/** The run's trigger account (personal credentials, the audit actor). */
|
|
226
226
|
accountId: string;
|
|
227
|
+
/**
|
|
228
|
+
* The conversation's source (memory plugin spec §6.2): extension tools limited by
|
|
229
|
+
* `spec.sources` are resolved with it, and plugin handlers receive it.
|
|
230
|
+
*/
|
|
231
|
+
source: ConversationSource;
|
|
232
|
+
/** The conversation's owner (`conversations.account_id`), given to plugin handlers. */
|
|
233
|
+
ownerAccountId: string;
|
|
234
|
+
/** The trigger account's current org role, given to plugin handlers. */
|
|
235
|
+
role: OrgRole;
|
|
227
236
|
/** run 的中止信号(用户中止 / 截止时间)。 */
|
|
228
237
|
signal: AbortSignal;
|
|
229
238
|
/** run 的截止时间(epoch 毫秒);传给内置工具(沙箱命令据此收紧超时,Ruling M20)。 */
|
package/dist/tools/pipeline.js
CHANGED
|
@@ -314,6 +314,7 @@ let ToolPipeline = class ToolPipeline {
|
|
|
314
314
|
forkChild: ctx.parent?.forSkill === true,
|
|
315
315
|
sandboxConversationId: ctx.parent?.rootConversationId ?? ctx.conversationId,
|
|
316
316
|
replay: ctx.conversationKind === "eval",
|
|
317
|
+
source: ctx.source,
|
|
317
318
|
});
|
|
318
319
|
}
|
|
319
320
|
return resolved;
|
|
@@ -1001,6 +1002,10 @@ let ToolPipeline = class ToolPipeline {
|
|
|
1001
1002
|
conversationId: ctx.conversationId,
|
|
1002
1003
|
callId,
|
|
1003
1004
|
...(resume !== undefined && { resume }),
|
|
1005
|
+
// who and where (memory plugin spec §6.2): `accountId` stays the trigger account
|
|
1006
|
+
source: ctx.source,
|
|
1007
|
+
ownerAccountId: ctx.ownerAccountId,
|
|
1008
|
+
role: ctx.role,
|
|
1004
1009
|
...(tool.source.kind === "builtin" && stepTools !== undefined && { tools: stepTools }),
|
|
1005
1010
|
...(tool.source.kind === "builtin" && { runId: ctx.runId }),
|
|
1006
1011
|
...(tool.source.kind === "builtin" &&
|
|
@@ -105,11 +105,22 @@ export declare class QuestionsService {
|
|
|
105
105
|
*/
|
|
106
106
|
cancelPendingIn(runId: string): Promise<QuestionRecord[]>;
|
|
107
107
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
108
|
+
* The conversations of `orgId` holding a pending question of a run `accountId` started (only the
|
|
109
|
+
* run's starter answers), read without locks: offboarding locks them, by tree root first
|
|
110
|
+
* (`lockConversationTrees`), before {@link cancelPendingFor}.
|
|
111
111
|
*/
|
|
112
|
-
|
|
112
|
+
pendingConversationsFor(orgId: string, accountId: string): Promise<string[]>;
|
|
113
|
+
/**
|
|
114
|
+
* In the caller's transaction (`mandatory`; offboarding): cancels every pending question of a run
|
|
115
|
+
* `accountId` started in `orgId` within `conversationIds`, the conversations the caller already
|
|
116
|
+
* locked (by tree root first, `lockConversationTrees`; lock order D7: conversation, then question
|
|
117
|
+
* rows), along the same path as {@link cancelPendingIn}: `cancelled`,
|
|
118
|
+
* `question_answered{cancelled}`. A question asked meanwhile in another conversation waits for
|
|
119
|
+
* its run's stop (offboarding owes it), so no conversation is locked after a row. Returns the
|
|
120
|
+
* cancelled records and the highest seq written per conversation; the caller publishes after the
|
|
121
|
+
* commit.
|
|
122
|
+
*/
|
|
123
|
+
cancelPendingFor(orgId: string, accountId: string, conversationIds: readonly string[]): Promise<{
|
|
113
124
|
records: QuestionRecord[];
|
|
114
125
|
seqs: Map<string, number>;
|
|
115
126
|
}>;
|
|
@@ -324,14 +324,12 @@ let QuestionsService = class QuestionsService {
|
|
|
324
324
|
return out;
|
|
325
325
|
}
|
|
326
326
|
/**
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
327
|
+
* The conversations of `orgId` holding a pending question of a run `accountId` started (only the
|
|
328
|
+
* run's starter answers), read without locks: offboarding locks them, by tree root first
|
|
329
|
+
* (`lockConversationTrees`), before {@link cancelPendingFor}.
|
|
330
330
|
*/
|
|
331
|
-
async
|
|
332
|
-
const
|
|
333
|
-
// the conversations first, in id order (lock order D7: conversation, then question rows)
|
|
334
|
-
const convs = await t
|
|
331
|
+
async pendingConversationsFor(orgId, accountId) {
|
|
332
|
+
const rows = await this.db
|
|
335
333
|
.selectFrom("questions")
|
|
336
334
|
.select("conversation_id")
|
|
337
335
|
.distinct()
|
|
@@ -339,24 +337,28 @@ let QuestionsService = class QuestionsService {
|
|
|
339
337
|
.where("status", "=", "pending")
|
|
340
338
|
.where("run_id", "in", (eb) => eb.selectFrom("runs").select("id").where("trigger_account_id", "=", accountId))
|
|
341
339
|
.execute();
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
340
|
+
return rows.map((r) => r.conversation_id);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* In the caller's transaction (`mandatory`; offboarding): cancels every pending question of a run
|
|
344
|
+
* `accountId` started in `orgId` within `conversationIds`, the conversations the caller already
|
|
345
|
+
* locked (by tree root first, `lockConversationTrees`; lock order D7: conversation, then question
|
|
346
|
+
* rows), along the same path as {@link cancelPendingIn}: `cancelled`,
|
|
347
|
+
* `question_answered{cancelled}`. A question asked meanwhile in another conversation waits for
|
|
348
|
+
* its run's stop (offboarding owes it), so no conversation is locked after a row. Returns the
|
|
349
|
+
* cancelled records and the highest seq written per conversation; the caller publishes after the
|
|
350
|
+
* commit.
|
|
351
|
+
*/
|
|
352
|
+
async cancelPendingFor(orgId, accountId, conversationIds) {
|
|
353
|
+
const t = this.db.db;
|
|
354
|
+
const convs = [...conversationIds];
|
|
353
355
|
if (convs.length === 0)
|
|
354
356
|
return { records: [], seqs: new Map() };
|
|
355
357
|
const rows = await t
|
|
356
358
|
.selectFrom("questions")
|
|
357
359
|
.selectAll()
|
|
358
360
|
.where("org_id", "=", orgId)
|
|
359
|
-
.where("conversation_id", "in", convs
|
|
361
|
+
.where("conversation_id", "in", convs)
|
|
360
362
|
.where("status", "=", "pending")
|
|
361
363
|
.where("run_id", "in", (eb) => eb.selectFrom("runs").select("id").where("trigger_account_id", "=", accountId))
|
|
362
364
|
.orderBy("created_at")
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Connector, HookParentScope, InputRequired, NetworkBlockedHost, ToolAnnotations, ToolCallContext, ToolResult, ToolSpec } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import type { Connector, ConversationSource, HookParentScope, InputRequired, NetworkBlockedHost, ToolAnnotations, ToolCallContext, ToolResult, ToolSpec } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import { type AgentDefinition, type DisabledToolRef, type SubagentTools } from "../agents/definition.js";
|
|
3
3
|
import type { ToolView } from "../conversations/events.js";
|
|
4
4
|
import { EffectiveConfigs } from "../extensions/effective-config.js";
|
|
@@ -213,6 +213,8 @@ export declare function withoutDisplayFields<T>(spec: T): T;
|
|
|
213
213
|
export declare function specHash(spec: unknown): string;
|
|
214
214
|
/** 由注解推导默认策略:只读 → allow;其余(破坏性 / 无注解)→ ask。 */
|
|
215
215
|
export declare function policyFromAnnotations(a: ToolAnnotations | undefined): "allow" | "ask";
|
|
216
|
+
/** Why an extension tool limited by `spec.sources` is not offered in a `source` conversation. */
|
|
217
|
+
export declare const sourceHiddenReason: (source: ConversationSource) => string;
|
|
216
218
|
/** `forAgent` 的结果。 */
|
|
217
219
|
export interface AgentTools {
|
|
218
220
|
tools: ResolvedTool[];
|
|
@@ -362,6 +364,11 @@ export declare class ToolRegistry {
|
|
|
362
364
|
sandboxConversationId?: string;
|
|
363
365
|
/** An eval replay's listing: its calls are never executed (the sandbox tools are listed). */
|
|
364
366
|
replay?: boolean;
|
|
367
|
+
/**
|
|
368
|
+
* The conversation's source (memory plugin spec §6.2): an extension tool whose
|
|
369
|
+
* `spec.sources` leaves it out is hidden. Required, so nothing defaults to the widest.
|
|
370
|
+
*/
|
|
371
|
+
source: ConversationSource;
|
|
365
372
|
}): Promise<AgentTools>;
|
|
366
373
|
/** 是否启用 `ToolSearch`(见 {@link forAgent});先比阈值,超过才读组织设置。 */
|
|
367
374
|
private searchActive;
|
package/dist/tools/registry.js
CHANGED
|
@@ -59,6 +59,8 @@ export function specHash(spec) {
|
|
|
59
59
|
export function policyFromAnnotations(a) {
|
|
60
60
|
return a?.readOnlyHint === true ? "allow" : "ask";
|
|
61
61
|
}
|
|
62
|
+
/** Why an extension tool limited by `spec.sources` is not offered in a `source` conversation. */
|
|
63
|
+
export const sourceHiddenReason = (source) => `Not available in ${source} conversations`;
|
|
62
64
|
// `ToolSearch` is added by the registry by org setting and threshold, `Skill` when the log has a
|
|
63
65
|
// skills catalog snapshot; neither comes from agent definitions (`./builtin-names.ts`).
|
|
64
66
|
/** 工具定义(模型可见三项)的估算 token 数(与请求估算同一口径)。 */
|
|
@@ -348,6 +350,7 @@ let ToolRegistry = class ToolRegistry {
|
|
|
348
350
|
}
|
|
349
351
|
async resolve(orgId, def) {
|
|
350
352
|
const disabled = def.disabledTools ?? [];
|
|
353
|
+
const { source } = def;
|
|
351
354
|
const scope = {
|
|
352
355
|
orgId,
|
|
353
356
|
...(def.sandboxConversationId !== undefined && {
|
|
@@ -435,6 +438,12 @@ let ToolRegistry = class ToolRegistry {
|
|
|
435
438
|
hidden.push({ ref: key, reason: toolTurnedOffReason(key) });
|
|
436
439
|
continue;
|
|
437
440
|
}
|
|
441
|
+
// offered only in some conversation sources; the source never changes, so this also
|
|
442
|
+
// refuses a resumed call (the pipeline resolves the step's tools with the same source)
|
|
443
|
+
if (t.spec.sources !== undefined && !t.spec.sources.includes(source)) {
|
|
444
|
+
hidden.push({ ref: key, reason: sourceHiddenReason(source) });
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
438
447
|
if (!(await configuredIn(t.plugin))) {
|
|
439
448
|
hidden.push({ ref: key, reason: `Plugin ${t.plugin} ${NEEDS_CONFIG_REASON}` });
|
|
440
449
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capybara-hq/kernel",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.25",
|
|
4
4
|
"description": "Capybara kernel: config, database, plugin host and HTTP API of the Capybara agent platform",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"yaml": "^2.9.1",
|
|
66
66
|
"yauzl": "3.4.0",
|
|
67
67
|
"zod": "^4.6.7",
|
|
68
|
-
"@capybara-hq/plugin-sdk": "^0.1.0-rc.
|
|
68
|
+
"@capybara-hq/plugin-sdk": "^0.1.0-rc.19"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@langchain/langgraph-checkpoint-validation": "^1.2.0",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@types/ws": "^8.18.2",
|
|
75
75
|
"@types/yauzl": "3.4.0",
|
|
76
76
|
"ws": "^8.22.0",
|
|
77
|
-
"@capybara-hq/testing": "0.1.0-rc.
|
|
77
|
+
"@capybara-hq/testing": "0.1.0-rc.17"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
80
|
"build": "tsc -b"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- 0121, transactional outbox spec §8.2: the platform components migration job is gone (an import
|
|
2
|
+
-- writes its platform components in its transaction), so nothing writes its runs table any more and
|
|
3
|
+
-- the platform inbox no longer shows a run; the org notice kind `skill_renamed`, which only that job
|
|
4
|
+
-- wrote, goes with it.
|
|
5
|
+
-- capybara:allow-destructive the job that wrote this table is removed in this release and nothing reads it (project not released, no compatibility layer)
|
|
6
|
+
DROP TABLE IF EXISTS platform_migration_runs;
|
|
7
|
+
-- capybara:allow-destructive notices of a kind only the removed job wrote; the inbox no longer renders the kind (project not released)
|
|
8
|
+
DELETE FROM org_notices WHERE kind = 'skill_renamed';
|
|
9
|
+
-- capybara:allow-destructive the check is added back without `skill_renamed` in the next statement; no row has that kind any more
|
|
10
|
+
ALTER TABLE org_notices DROP CONSTRAINT IF EXISTS org_notices_kind_check;
|
|
11
|
+
ALTER TABLE org_notices ADD CONSTRAINT org_notices_kind_check
|
|
12
|
+
CHECK (kind IN ('item_shared', 'shared_item_updated', 'upload_confirmed', 'upload_rejected'));
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
-- 0122, transactional outbox spec §8.1 / §8.2: the transaction that writes a pending task run owes
|
|
2
|
+
-- its fire job in the outbox, so the scheduled task scan's repair is gone, and with it the column
|
|
3
|
+
-- that rotated the repair's rechecks of started runs.
|
|
4
|
+
-- capybara:allow-destructive the repair that read and wrote this column is removed in this release and nothing else reads it (project not released, no compatibility layer)
|
|
5
|
+
ALTER TABLE scheduled_task_runs DROP COLUMN IF EXISTS rechecked_at;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- pgvector, when the server offers it (memory plugin spec §6.4). Plugins store embeddings in a
|
|
2
|
+
-- `vector` column when the type is visible from their search_path (`plugin_<name>, public`) and fall
|
|
3
|
+
-- back to `real[]` otherwise, so pgvector is recommended, not required:
|
|
4
|
+
-- - not offered by the server (no row in pg_available_extensions): nothing to do;
|
|
5
|
+
-- - already installed, in whatever schema a DBA put it: left where it is;
|
|
6
|
+
-- - refused (insufficient_privilege: the migrating role may not create it; feature_not_supported:
|
|
7
|
+
-- a managed server lists it but its vendor allow-list refuses it, e.g. Azure Flexible Server):
|
|
8
|
+
-- a notice naming the SQLSTATE, the migration succeeds. Any other error still fails it.
|
|
9
|
+
-- `SCHEMA public` matters: the migrator's search_path is `capybara, public`, and a bare
|
|
10
|
+
-- CREATE EXTENSION would put the type and `<=>` in `capybara`, invisible to plugin schemas.
|
|
11
|
+
-- capybara:allow-destructive not destructive: a DO block is the only way to make CREATE EXTENSION conditional on availability and to turn a refused CREATE EXTENSION into a notice
|
|
12
|
+
DO $$
|
|
13
|
+
BEGIN
|
|
14
|
+
IF NOT EXISTS (SELECT 1 FROM pg_available_extensions WHERE name = 'vector') THEN
|
|
15
|
+
RAISE NOTICE 'pgvector is not available on this server; plugins fall back to real[] embeddings';
|
|
16
|
+
RETURN;
|
|
17
|
+
END IF;
|
|
18
|
+
IF EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'vector') THEN
|
|
19
|
+
RETURN;
|
|
20
|
+
END IF;
|
|
21
|
+
BEGIN
|
|
22
|
+
EXECUTE 'CREATE EXTENSION IF NOT EXISTS vector SCHEMA public';
|
|
23
|
+
EXCEPTION WHEN insufficient_privilege OR feature_not_supported THEN
|
|
24
|
+
RAISE NOTICE 'pgvector not installed: CREATE EXTENSION was refused (SQLSTATE %: %); plugins fall back to real[] embeddings', SQLSTATE, SQLERRM;
|
|
25
|
+
END;
|
|
26
|
+
END
|
|
27
|
+
$$;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
-- Memory plugin spec §6.2: where a conversation comes from (web, channel_dm, channel_group,
|
|
2
|
+
-- subagent), set once at creation and never changed; extension tools and context@1 providers are
|
|
3
|
+
-- offered by it.
|
|
4
|
+
-- The column is added with a constant default, so existing rows read 'web' without a rewrite.
|
|
5
|
+
-- The backfill then sets the other sources, each UPDATE touching only rows still at 'web', so a
|
|
6
|
+
-- re-run updates nothing; later UPDATEs skip rows an earlier one set:
|
|
7
|
+
-- 1. kind 'subagent' -> subagent;
|
|
8
|
+
-- 2. a channel thread row of any archive state (DM /new archives the row, the conversation stays
|
|
9
|
+
-- a DM's): only 'dm:' keys -> channel_dm, any other key -> channel_group (a conversation with
|
|
10
|
+
-- both is a group's: the safer of the two);
|
|
11
|
+
-- 3. a scheduled group run whose thread is still to bind at its first post (no thread row yet):
|
|
12
|
+
-- its delivery's target chat type.
|
|
13
|
+
-- The rest stays web. The CHECK is added last (dropped first, so a re-run re-adds it).
|
|
14
|
+
ALTER TABLE conversations ADD COLUMN IF NOT EXISTS source text NOT NULL DEFAULT 'web';
|
|
15
|
+
|
|
16
|
+
UPDATE conversations SET source = 'subagent'
|
|
17
|
+
WHERE kind = 'subagent' AND source = 'web';
|
|
18
|
+
|
|
19
|
+
UPDATE conversations c
|
|
20
|
+
SET source = CASE WHEN t.dm THEN 'channel_dm' ELSE 'channel_group' END
|
|
21
|
+
FROM (
|
|
22
|
+
SELECT conversation_id, bool_and(thread_key LIKE 'dm:%') AS dm
|
|
23
|
+
FROM channel_threads
|
|
24
|
+
GROUP BY conversation_id
|
|
25
|
+
) t
|
|
26
|
+
WHERE t.conversation_id = c.id AND c.source = 'web';
|
|
27
|
+
|
|
28
|
+
UPDATE conversations c
|
|
29
|
+
SET source = CASE WHEN d.target->>'chatType' = 'dm' THEN 'channel_dm' ELSE 'channel_group' END
|
|
30
|
+
FROM channel_deliveries d
|
|
31
|
+
WHERE d.conversation_id = c.id AND d.target->>'chatType' IN ('dm', 'group') AND c.source = 'web';
|
|
32
|
+
|
|
33
|
+
-- capybara:allow-destructive the CHECK is re-added unchanged right below (a re-run of this file)
|
|
34
|
+
ALTER TABLE conversations DROP CONSTRAINT IF EXISTS conversations_source_check;
|
|
35
|
+
ALTER TABLE conversations ADD CONSTRAINT conversations_source_check
|
|
36
|
+
CHECK (source IN ('web', 'channel_dm', 'channel_group', 'subagent'));
|