@capybara-hq/kernel 0.1.0-rc.25 → 0.1.0-rc.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/outbox-dead.service.d.ts +7 -3
- package/dist/admin/outbox-dead.service.js +17 -6
- package/dist/admin/overview.service.d.ts +6 -6
- package/dist/admin/overview.service.js +7 -7
- package/dist/background/wake-rules.d.ts +5 -0
- package/dist/background/wake-rules.js +16 -5
- package/dist/catalog/catalog-content.d.ts +6 -1
- package/dist/catalog/catalog-content.js +2 -1
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-catalog.service.js +5 -2
- package/dist/catalog/plugin-install.d.ts +5 -0
- package/dist/catalog/plugin-install.js +37 -1
- package/dist/channels/channels.module.d.ts +9 -8
- package/dist/channels/channels.module.js +11 -10
- package/dist/channels/connections.service.d.ts +3 -3
- package/dist/channels/connections.service.js +6 -6
- package/dist/channels/deliver-job.d.ts +10 -3
- package/dist/channels/deliver-job.js +7 -3
- package/dist/channels/jobs.d.ts +11 -4
- package/dist/channels/jobs.js +18 -3
- package/dist/channels/outbound.d.ts +5 -2
- package/dist/channels/outbound.js +9 -2
- package/dist/conversations/conversations.controller.d.ts +4 -2
- package/dist/conversations/conversations.controller.js +4 -2
- package/dist/conversations/conversations.service.d.ts +37 -4
- package/dist/conversations/conversations.service.js +77 -20
- package/dist/conversations/events.d.ts +6 -0
- package/dist/conversations/events.js +6 -0
- package/dist/db/kernel-schema.d.ts +10 -1
- package/dist/devices/devices.service.d.ts +14 -6
- package/dist/devices/devices.service.js +20 -7
- package/dist/evals/evals.module.js +5 -1
- package/dist/evals/jobs.d.ts +14 -6
- package/dist/evals/jobs.js +21 -19
- package/dist/evals/runs.service.d.ts +5 -6
- package/dist/evals/runs.service.js +11 -33
- package/dist/infra/outbox/outbox.service.d.ts +6 -0
- package/dist/infra/outbox/outbox.service.js +51 -17
- package/dist/infra/outbox/registry.d.ts +52 -0
- package/dist/infra/outbox/registry.js +57 -0
- package/dist/local-devices/device-policy.service.d.ts +131 -26
- package/dist/local-devices/device-policy.service.js +341 -145
- package/dist/local-devices/machines.d.ts +17 -2
- package/dist/local-devices/machines.js +66 -5
- package/dist/local-devices/policy-op-jobs.d.ts +15 -0
- package/dist/local-devices/policy-op-jobs.js +12 -0
- package/dist/local-devices/policy-summary.d.ts +36 -3
- package/dist/local-devices/policy-summary.js +47 -1
- package/dist/orgs/orgs.service.js +4 -1
- package/dist/overrides.d.ts +4 -0
- package/dist/overrides.js +2 -0
- package/dist/platform/ops/conversations.ops.js +1 -1
- package/dist/runtime/maintenance.d.ts +6 -3
- package/dist/runtime/maintenance.js +21 -7
- package/dist/runtime/plugin-context.service.d.ts +9 -3
- package/dist/runtime/plugin-context.service.js +17 -5
- package/dist/runtime/run-context.service.d.ts +7 -3
- package/dist/runtime/run-context.service.js +12 -2
- package/dist/runtime/run-finish.d.ts +2 -2
- package/dist/runtime/run-finish.js +11 -6
- package/dist/runtime/run-jobs.d.ts +21 -4
- package/dist/runtime/run-jobs.js +29 -6
- package/dist/runtime/run-orphan-check.job.d.ts +14 -11
- package/dist/runtime/run-orphan-check.job.js +16 -11
- package/dist/runtime/run-queue.d.ts +23 -31
- package/dist/runtime/run-queue.js +62 -64
- package/dist/runtime/run-status.d.ts +12 -0
- package/dist/runtime/run-status.js +21 -0
- package/dist/runtime/run-worker.d.ts +10 -2
- package/dist/runtime/run-worker.js +44 -19
- package/dist/runtime/step-log.d.ts +6 -5
- package/dist/runtime/step-log.js +6 -5
- package/dist/runtime/usage-report.d.ts +3 -1
- package/dist/runtime/usage-report.js +4 -2
- package/dist/runtime/waits.d.ts +16 -6
- package/dist/runtime/waits.js +37 -15
- package/dist/schedules/scan.job.d.ts +6 -6
- package/dist/schedules/scan.job.js +6 -6
- package/dist/skills/catalog.d.ts +7 -3
- package/dist/skills/catalog.js +12 -2
- package/dist/tools/approvals.service.d.ts +96 -19
- package/dist/tools/approvals.service.js +300 -67
- package/dist/tools/builtins/sandbox/task-output.js +21 -6
- package/dist/tools/connectors.service.js +0 -4
- package/dist/tools/device-op-resolver.d.ts +40 -0
- package/dist/tools/device-op-resolver.js +121 -0
- package/dist/tools/network-asks.d.ts +1 -1
- package/dist/tools/network-asks.js +4 -3
- package/dist/tools/tools.module.js +4 -0
- package/package.json +2 -2
- package/src/migrations/0125_conversation_tree_stopped_at.sql +5 -0
|
@@ -50,8 +50,9 @@ export declare class OutboxDeadService {
|
|
|
50
50
|
* the fast path after the commit; the relay delivers it otherwise. When the same (queue, job id)
|
|
51
51
|
* is owed by a pending row already, the dead row is deleted instead (`already_owed`). A pending
|
|
52
52
|
* row added between the check and the update fails the update on the unique index: the retry
|
|
53
|
-
* then runs once more and takes the `already_owed` branch
|
|
54
|
-
*
|
|
53
|
+
* then runs once more and takes the `already_owed` branch; a second conflict on that run (the
|
|
54
|
+
* job owed, delivered and owed again meanwhile) is `conflict` (409), the dead row left as it was.
|
|
55
|
+
* `outbox.not_found` (404) when no dead row has the id.
|
|
55
56
|
*/
|
|
56
57
|
retry(id: string, actor: WriteActor, detail?: Record<string, unknown>): Promise<{
|
|
57
58
|
outcome: OutboxRetryOutcome;
|
|
@@ -61,6 +62,9 @@ export declare class OutboxDeadService {
|
|
|
61
62
|
discard(id: string, actor: WriteActor, detail?: Record<string, unknown>): Promise<void>;
|
|
62
63
|
/** The dead row `id`, locked for the transaction; `outbox.not_found` when it is not dead. */
|
|
63
64
|
private lockDead;
|
|
64
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* A platform-level audit event (no org: the outbox is the deployment's), naming the job. The
|
|
67
|
+
* caller's detail (`via`, `operation`) comes first, so it cannot override the row's fields.
|
|
68
|
+
*/
|
|
65
69
|
private audit;
|
|
66
70
|
}
|
|
@@ -89,8 +89,9 @@ let OutboxDeadService = class OutboxDeadService {
|
|
|
89
89
|
* the fast path after the commit; the relay delivers it otherwise. When the same (queue, job id)
|
|
90
90
|
* is owed by a pending row already, the dead row is deleted instead (`already_owed`). A pending
|
|
91
91
|
* row added between the check and the update fails the update on the unique index: the retry
|
|
92
|
-
* then runs once more and takes the `already_owed` branch
|
|
93
|
-
*
|
|
92
|
+
* then runs once more and takes the `already_owed` branch; a second conflict on that run (the
|
|
93
|
+
* job owed, delivered and owed again meanwhile) is `conflict` (409), the dead row left as it was.
|
|
94
|
+
* `outbox.not_found` (404) when no dead row has the id.
|
|
94
95
|
*/
|
|
95
96
|
async retry(id, actor, detail = {}) {
|
|
96
97
|
try {
|
|
@@ -99,7 +100,14 @@ let OutboxDeadService = class OutboxDeadService {
|
|
|
99
100
|
catch (e) {
|
|
100
101
|
if (!isPendingConflict(e))
|
|
101
102
|
throw e;
|
|
102
|
-
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
return await this.retryOnce(id, actor, detail);
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
if (!isPendingConflict(e))
|
|
109
|
+
throw e;
|
|
110
|
+
throw new ApiError("conflict", `The job of dead outbox row ${id} was owed again while it was retried; list the dead rows with GET /api/v1/admin/outbox/dead and retry or discard it again`);
|
|
103
111
|
}
|
|
104
112
|
}
|
|
105
113
|
async retryOnce(id, actor, detail) {
|
|
@@ -125,7 +133,7 @@ let OutboxDeadService = class OutboxDeadService {
|
|
|
125
133
|
outcome = "retried";
|
|
126
134
|
afterCommit(() => this.fastPath([id]));
|
|
127
135
|
}
|
|
128
|
-
await this.audit("outbox.retried", id, row, actor, {
|
|
136
|
+
await this.audit("outbox.retried", id, row, actor, { ...detail, outcome });
|
|
129
137
|
return { outcome };
|
|
130
138
|
}
|
|
131
139
|
/** Deletes a dead row: its job is never delivered. `outbox.not_found` (404) when none. */
|
|
@@ -147,7 +155,10 @@ let OutboxDeadService = class OutboxDeadService {
|
|
|
147
155
|
throw notFound(id);
|
|
148
156
|
return row;
|
|
149
157
|
}
|
|
150
|
-
/**
|
|
158
|
+
/**
|
|
159
|
+
* A platform-level audit event (no org: the outbox is the deployment's), naming the job. The
|
|
160
|
+
* caller's detail (`via`, `operation`) comes first, so it cannot override the row's fields.
|
|
161
|
+
*/
|
|
151
162
|
audit(action, id, row, actor, detail) {
|
|
152
163
|
return audit(this.db.db, {
|
|
153
164
|
actorAccountId: actor.accountId,
|
|
@@ -155,11 +166,11 @@ let OutboxDeadService = class OutboxDeadService {
|
|
|
155
166
|
action,
|
|
156
167
|
target: id,
|
|
157
168
|
detail: {
|
|
169
|
+
...detail,
|
|
158
170
|
queue: row.queue,
|
|
159
171
|
jobName: row.job_name,
|
|
160
172
|
jobId: row.job_id,
|
|
161
173
|
...(row.org_id !== null && { orgId: row.org_id }),
|
|
162
|
-
...detail,
|
|
163
174
|
},
|
|
164
175
|
});
|
|
165
176
|
}
|
|
@@ -107,11 +107,11 @@ export declare function extensionReason(e: Pick<AdminExtensionView, "status" | "
|
|
|
107
107
|
/** The problem of a provider in the platform view, or null (`org_configured` is not one). */
|
|
108
108
|
export declare function providerProblem(p: ProviderStatus): AttentionProviderProblem | null;
|
|
109
109
|
/**
|
|
110
|
-
* The admin overview (spec §4.1, §5.2): its numbers and the 待处理 lists, read live from the
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* `unavailable`. Reads are not audited (decision D5) and not cached further
|
|
114
|
-
* or already Redis-cached).
|
|
110
|
+
* The admin overview (spec §4.1, §5.2): its numbers and the 待处理 lists, read live from the sources
|
|
111
|
+
* the admin pages read (`ExtensionAdmin`, the catalog, the provider registry's cached lists, the
|
|
112
|
+
* dead outbox rows, the run queue's lost-jobs canary). Each source is read on its own: a failing
|
|
113
|
+
* one is reported in `unavailable`. Reads are not audited (decision D5) and not cached further
|
|
114
|
+
* (every source is PG or already Redis-cached).
|
|
115
115
|
*/
|
|
116
116
|
export declare class AdminOverviewService {
|
|
117
117
|
private readonly extensions;
|
|
@@ -136,7 +136,7 @@ export declare class AdminOverviewService {
|
|
|
136
136
|
/**
|
|
137
137
|
* The extensions needing a look, but not one pending approval whose plugin waits in the platform
|
|
138
138
|
* inbox (a permission confirmation or an org upload: the inbox already lists and counts it). A
|
|
139
|
-
* config-declared extension has no catalog item and stays. Without the catalog
|
|
139
|
+
* config-declared extension has no catalog item and stays. Without the catalog none is left out.
|
|
140
140
|
*/
|
|
141
141
|
private extensionRows;
|
|
142
142
|
/** Every live catalog item, page by page (the catalog lists by name). */
|
|
@@ -53,11 +53,11 @@ export function providerProblem(p) {
|
|
|
53
53
|
return p.models.some((m) => m.kind === "chat" && m.disabled !== true) ? null : "no_chat_model";
|
|
54
54
|
}
|
|
55
55
|
/**
|
|
56
|
-
* The admin overview (spec §4.1, §5.2): its numbers and the 待处理 lists, read live from the
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* `unavailable`. Reads are not audited (decision D5) and not cached further
|
|
60
|
-
* or already Redis-cached).
|
|
56
|
+
* The admin overview (spec §4.1, §5.2): its numbers and the 待处理 lists, read live from the sources
|
|
57
|
+
* the admin pages read (`ExtensionAdmin`, the catalog, the provider registry's cached lists, the
|
|
58
|
+
* dead outbox rows, the run queue's lost-jobs canary). Each source is read on its own: a failing
|
|
59
|
+
* one is reported in `unavailable`. Reads are not audited (decision D5) and not cached further
|
|
60
|
+
* (every source is PG or already Redis-cached).
|
|
61
61
|
*/
|
|
62
62
|
let AdminOverviewService = class AdminOverviewService {
|
|
63
63
|
extensions;
|
|
@@ -164,13 +164,13 @@ let AdminOverviewService = class AdminOverviewService {
|
|
|
164
164
|
/**
|
|
165
165
|
* The extensions needing a look, but not one pending approval whose plugin waits in the platform
|
|
166
166
|
* inbox (a permission confirmation or an org upload: the inbox already lists and counts it). A
|
|
167
|
-
* config-declared extension has no catalog item and stays. Without the catalog
|
|
167
|
+
* config-declared extension has no catalog item and stays. Without the catalog none is left out.
|
|
168
168
|
*/
|
|
169
169
|
async extensionRows(items) {
|
|
170
170
|
const [views, catalog] = await Promise.all([this.extensions.list(), items.catch(() => [])]);
|
|
171
171
|
const inInbox = new Set(catalog.filter((i) => i.pending).map((i) => i.name));
|
|
172
172
|
return views.flatMap((v) => {
|
|
173
|
-
// every status of extensionNeedsAction has a reason,
|
|
173
|
+
// every status of extensionNeedsAction has a reason, as has enabled with an invalid config
|
|
174
174
|
const reason = extensionReason(v);
|
|
175
175
|
if (reason === null)
|
|
176
176
|
return [];
|
|
@@ -54,6 +54,11 @@ export type WakeDecision = {
|
|
|
54
54
|
* - the wake setting is on (the org's override, else `deploymentOn`);
|
|
55
55
|
* - the root's queue is not paused and the root is not deleted (a paused root holds the wakes of
|
|
56
56
|
* the whole tree, agent map spec §5.3);
|
|
57
|
+
* - for a `subagent_delivery` to the root: the root's tree is not stopped (`tree_stopped_at`, set by
|
|
58
|
+
* a person's stop of the root and cleared by the root's next run; agent map spec §6, changed in
|
|
59
|
+
* acceptance 2026-11-13): no sub-agent's finish or report, whenever its child finished, wakes a
|
|
60
|
+
* root a person stopped, the delivery waits for the next run. A sub-agent of a stopped tree is
|
|
61
|
+
* never woken here (`RunQueue.childWakeIn` refuses it first);
|
|
57
62
|
* - the event's trigger account can still send to the root (`canSend`, which the caller binds to
|
|
58
63
|
* the root);
|
|
59
64
|
* - no other wake of this conversation started in the last {@link WAKE_WINDOW_MS} (else `later`).
|
|
@@ -38,6 +38,11 @@ const WAKE_EVENT_TYPES = ["background_task_finished", "subagent_delivery"];
|
|
|
38
38
|
* - the wake setting is on (the org's override, else `deploymentOn`);
|
|
39
39
|
* - the root's queue is not paused and the root is not deleted (a paused root holds the wakes of
|
|
40
40
|
* the whole tree, agent map spec §5.3);
|
|
41
|
+
* - for a `subagent_delivery` to the root: the root's tree is not stopped (`tree_stopped_at`, set by
|
|
42
|
+
* a person's stop of the root and cleared by the root's next run; agent map spec §6, changed in
|
|
43
|
+
* acceptance 2026-11-13): no sub-agent's finish or report, whenever its child finished, wakes a
|
|
44
|
+
* root a person stopped, the delivery waits for the next run. A sub-agent of a stopped tree is
|
|
45
|
+
* never woken here (`RunQueue.childWakeIn` refuses it first);
|
|
41
46
|
* - the event's trigger account can still send to the root (`canSend`, which the caller binds to
|
|
42
47
|
* the root);
|
|
43
48
|
* - no other wake of this conversation started in the last {@link WAKE_WINDOW_MS} (else `later`).
|
|
@@ -68,21 +73,27 @@ export async function wakeDecisionIn(t, conv, consumed, deploymentOn, canSend) {
|
|
|
68
73
|
.orderBy("e.seq", "desc")
|
|
69
74
|
.limit(CANDIDATES)
|
|
70
75
|
.execute();
|
|
71
|
-
|
|
72
|
-
.sort((a, b) => Number(b.seq) - Number(a.seq))
|
|
73
|
-
.slice(0, CANDIDATES);
|
|
74
|
-
if (candidates.length === 0)
|
|
76
|
+
if (finishes.length === 0 && deliveries.length === 0)
|
|
75
77
|
return null;
|
|
76
78
|
const org = (await readOrgSettings(t, conv.orgId)).wakeOnBackgroundTask;
|
|
77
79
|
if (!effectiveWakeOnBackgroundTask(deploymentOn, org))
|
|
78
80
|
return null;
|
|
81
|
+
// read under the root's row lock when `conv` is the root (`RunQueue.ensureRunIn` holds it), so a
|
|
82
|
+
// person's stop of the tree (which writes `tree_stopped_at` on that row) is seen by every wake
|
|
83
|
+
// decided after its commit
|
|
79
84
|
const root = await t
|
|
80
85
|
.selectFrom("conversations")
|
|
81
|
-
.select(["queue_paused", "deleted_at"])
|
|
86
|
+
.select(["queue_paused", "deleted_at", "tree_stopped_at"])
|
|
82
87
|
.where("id", "=", conv.rootId)
|
|
83
88
|
.executeTakeFirst();
|
|
84
89
|
if (!root || root.queue_paused || root.deleted_at !== null)
|
|
85
90
|
return null;
|
|
91
|
+
const treeStopped = root.tree_stopped_at !== null;
|
|
92
|
+
const candidates = [...finishes, ...(treeStopped ? [] : deliveries)]
|
|
93
|
+
.sort((a, b) => Number(b.seq) - Number(a.seq))
|
|
94
|
+
.slice(0, CANDIDATES);
|
|
95
|
+
if (candidates.length === 0)
|
|
96
|
+
return null;
|
|
86
97
|
let boundary;
|
|
87
98
|
for (const c of candidates) {
|
|
88
99
|
if (await canSend(c.account)) {
|
|
@@ -77,6 +77,11 @@ export interface OrgCatalogTemplate {
|
|
|
77
77
|
* install wizard does not ask for it (an org admin sets it up when switching it on).
|
|
78
78
|
*/
|
|
79
79
|
defaultOff: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* The template is on in this org right now (a default-off one an admin switched on): the install
|
|
82
|
+
* asks for its input too, since the install is what creates its connector.
|
|
83
|
+
*/
|
|
84
|
+
on: boolean;
|
|
80
85
|
/**
|
|
81
86
|
* The template's setup guide in the item's latest version (`GET …/catalog/:itemId/guides/
|
|
82
87
|
* :guideId`), null without one (spec 2026-11-09-setup-guides-v2-design.md §9.3).
|
|
@@ -118,7 +123,7 @@ export interface OrgCatalogDetail {
|
|
|
118
123
|
* A connector template as an org sees it (the install wizard and the upgrade preview's inputs):
|
|
119
124
|
* presets and editable paths, no credentials.
|
|
120
125
|
*/
|
|
121
|
-
export declare function orgTemplateView(id: string, file: ConnectorTemplateFile, guide?: OrgCatalogTemplate["guide"]): OrgCatalogTemplate;
|
|
126
|
+
export declare function orgTemplateView(id: string, file: ConnectorTemplateFile, guide?: OrgCatalogTemplate["guide"], on?: boolean): OrgCatalogTemplate;
|
|
122
127
|
/** An item's icon (the latest version's plugin icon). */
|
|
123
128
|
export interface CatalogIcon {
|
|
124
129
|
data: Uint8Array;
|
|
@@ -16,7 +16,7 @@ import { satisfiesRange } from "./versions.js";
|
|
|
16
16
|
* A connector template as an org sees it (the install wizard and the upgrade preview's inputs):
|
|
17
17
|
* presets and editable paths, no credentials.
|
|
18
18
|
*/
|
|
19
|
-
export function orgTemplateView(id, file, guide = null) {
|
|
19
|
+
export function orgTemplateView(id, file, guide = null, on = true) {
|
|
20
20
|
const t = file.template;
|
|
21
21
|
return {
|
|
22
22
|
id,
|
|
@@ -30,6 +30,7 @@ export function orgTemplateView(id, file, guide = null) {
|
|
|
30
30
|
ownership: t.ownership ??
|
|
31
31
|
(typeof t.auth.ownership === "string" ? t.auth.ownership : null),
|
|
32
32
|
defaultOff: file.defaultEnabled === false,
|
|
33
|
+
on,
|
|
33
34
|
guide,
|
|
34
35
|
};
|
|
35
36
|
}
|
|
@@ -188,7 +188,7 @@ export declare function headsToResolve(entries: readonly MarketplaceEntry[], imp
|
|
|
188
188
|
/** The host versions (an entry's `minKernel` is judged against them). */
|
|
189
189
|
export declare const HOST_VERSIONS: {
|
|
190
190
|
readonly kernel: string;
|
|
191
|
-
readonly sdk: "0.1.0-rc.
|
|
191
|
+
readonly sdk: "0.1.0-rc.20";
|
|
192
192
|
};
|
|
193
193
|
/** Whether this deployment satisfies the entry's `minKernel`. */
|
|
194
194
|
export declare const entryCompatible: (e: Pick<MarketplaceEntry, "minKernel">) => boolean;
|
|
@@ -273,6 +273,9 @@ let OrgCatalogService = class OrgCatalogService {
|
|
|
273
273
|
throw itemNotFound();
|
|
274
274
|
const m = v.manifest;
|
|
275
275
|
const templates = await readCatalogTemplates(this.storage, v);
|
|
276
|
+
// Which templates are on in this org now: the install asks for the input of those, a
|
|
277
|
+
// default-off one an admin switched on included
|
|
278
|
+
const on = await connectorTemplatesOn(this.db, orgId, itemId, templates.map((t) => t.id));
|
|
276
279
|
// where each template's page finds its setup guide (spec 2026-11-09-setup-guides-v2 §9.3)
|
|
277
280
|
const guideOf = (templateId) => {
|
|
278
281
|
const g = templateGuideOf(m, templateId);
|
|
@@ -290,7 +293,7 @@ let OrgCatalogService = class OrgCatalogService {
|
|
|
290
293
|
item,
|
|
291
294
|
hasIcon: m.icon !== undefined,
|
|
292
295
|
extension: m.extension ? m.name : null,
|
|
293
|
-
templates: templates.map((t) => orgTemplateView(t.id, t.file, guideOf(t.id))),
|
|
296
|
+
templates: templates.map((t) => orgTemplateView(t.id, t.file, guideOf(t.id), on.has(t.id))),
|
|
294
297
|
dependencies: [
|
|
295
298
|
...(m.dependencies ?? []).map((d) => ({ name: d.name, range: d.version })),
|
|
296
299
|
...implied.map((d) => ({ name: d.name, range: d.range, implied: true })),
|
|
@@ -298,7 +301,7 @@ let OrgCatalogService = class OrgCatalogService {
|
|
|
298
301
|
skills: (m.skills ?? []).map((s) => packagePath(s.path)),
|
|
299
302
|
agents: (m.agents ?? []).map((a) => packagePath(a.path)),
|
|
300
303
|
missingTemplates: item.installed !== null && item.state === "needs_config"
|
|
301
|
-
? ((await this.pluginInstaller.missingConnectors(orgId, itemId))?.templates ?? []).map((t) => orgTemplateView(t.id, t.file, guideOf(t.id)))
|
|
304
|
+
? ((await this.pluginInstaller.missingConnectors(orgId, itemId))?.templates ?? []).map((t) => orgTemplateView(t.id, t.file, guideOf(t.id), true))
|
|
302
305
|
: [],
|
|
303
306
|
};
|
|
304
307
|
}
|
|
@@ -540,6 +540,11 @@ export declare class PluginInstaller {
|
|
|
540
540
|
file: ConnectorTemplateFile;
|
|
541
541
|
}[];
|
|
542
542
|
} | undefined>;
|
|
543
|
+
/**
|
|
544
|
+
* The org's live connector made from one of the plugin's connector templates, if any: an
|
|
545
|
+
* uninstall keeps a connector its agents use, and the plugin's next switch-on reuses it.
|
|
546
|
+
*/
|
|
547
|
+
private liveTemplateConnector;
|
|
543
548
|
/**
|
|
544
549
|
* Creates the connectors an installed plugin is missing (ruling P2-10, `POST …/catalog/:itemId/
|
|
545
550
|
* connectors`): `input.connectors` names some of {@link missingConnectors}' templates with the
|
|
@@ -484,6 +484,10 @@ let PluginInstaller = class PluginInstaller {
|
|
|
484
484
|
// The kernel installs only what needs no org input (the scope sync's `needs_config` rule)
|
|
485
485
|
if (actor === null)
|
|
486
486
|
throw systemNeedsInput(manifest.name);
|
|
487
|
+
// An earlier switch-off kept this template's connector: the install takes it back, so its
|
|
488
|
+
// input is not asked for here (the connector keeps its endpoint and credentials)
|
|
489
|
+
if ((await this.liveTemplateConnector(orgId, itemId, ct.id)) !== undefined)
|
|
490
|
+
continue;
|
|
487
491
|
connectors.set(ct.id, this.planConnector(actor, { itemId, version, name: manifest.name }, ct, input, at, approvedBy));
|
|
488
492
|
}
|
|
489
493
|
}
|
|
@@ -743,9 +747,24 @@ let PluginInstaller = class PluginInstaller {
|
|
|
743
747
|
}
|
|
744
748
|
step = "connectors";
|
|
745
749
|
for (const ct of plan.templates) {
|
|
750
|
+
// An earlier switch-off kept this template's connector (its agents use it): the plugin
|
|
751
|
+
// takes it back instead of creating a second one, whose default slug the first holds
|
|
752
|
+
const existing = await this.liveTemplateConnector(orgId, itemId, ct.id);
|
|
753
|
+
if (existing !== undefined) {
|
|
754
|
+
await record({
|
|
755
|
+
kind: "connector",
|
|
756
|
+
ref: existing,
|
|
757
|
+
created: false,
|
|
758
|
+
skillVersion: null,
|
|
759
|
+
templateId: ct.id,
|
|
760
|
+
});
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
746
763
|
const ci = plan.connectors.get(ct.id);
|
|
764
|
+
// No input was needed (a live connector was taken back above, or the template was skipped
|
|
765
|
+
// at plan time): nothing to create
|
|
747
766
|
if (!ci)
|
|
748
|
-
|
|
767
|
+
continue;
|
|
749
768
|
if (actor === null)
|
|
750
769
|
throw systemNeedsInput(manifest.name);
|
|
751
770
|
const r = await this.connectors.create(orgId, actor, ci, opts.actorTokenId);
|
|
@@ -1657,6 +1676,23 @@ let PluginInstaller = class PluginInstaller {
|
|
|
1657
1676
|
templates: all.filter((t) => on.has(t.id) && !made.has(t.id)),
|
|
1658
1677
|
};
|
|
1659
1678
|
}
|
|
1679
|
+
/**
|
|
1680
|
+
* The org's live connector made from one of the plugin's connector templates, if any: an
|
|
1681
|
+
* uninstall keeps a connector its agents use, and the plugin's next switch-on reuses it.
|
|
1682
|
+
*/
|
|
1683
|
+
async liveTemplateConnector(orgId, itemId, templateId) {
|
|
1684
|
+
const row = await this.db
|
|
1685
|
+
.selectFrom("connectors as c")
|
|
1686
|
+
.innerJoin("platform_connector_templates as p", "p.id", "c.platform_template_id")
|
|
1687
|
+
.select("c.id")
|
|
1688
|
+
.where("c.org_id", "=", orgId)
|
|
1689
|
+
.where("c.deleted_at", "is", null)
|
|
1690
|
+
.where("p.catalog_item_id", "=", itemId)
|
|
1691
|
+
.where("p.template_id", "=", templateId)
|
|
1692
|
+
.orderBy("c.created_at")
|
|
1693
|
+
.executeTakeFirst();
|
|
1694
|
+
return row?.id;
|
|
1695
|
+
}
|
|
1660
1696
|
/**
|
|
1661
1697
|
* Creates the connectors an installed plugin is missing (ruling P2-10, `POST …/catalog/:itemId/
|
|
1662
1698
|
* connectors`): `input.connectors` names some of {@link missingConnectors}' templates with the
|
|
@@ -9,14 +9,15 @@ import { ChannelOutbound } from "./outbound.js";
|
|
|
9
9
|
* Channels on every role: the org's channel connections, inbound (dedupe and enqueue; the inbound
|
|
10
10
|
* jobs), the outbound delivery, the producer of the channel queue `cap-kernel-channels`, the
|
|
11
11
|
* anonymous guests. The roles' parts are the role modules': the queue's consumer
|
|
12
|
-
* ({@link ChannelWorkersModule}) and the gateway check job are the worker role's, the
|
|
13
|
-
* supervisor and the webhook inbound the gateway role's (`GatewayModule`).
|
|
12
|
+
* ({@link ChannelWorkersModule}) and the gateway check job are the worker role's, the
|
|
13
|
+
* long-connection supervisor and the webhook inbound the gateway role's (`GatewayModule`).
|
|
14
14
|
*/
|
|
15
15
|
export declare class ChannelsModule {
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* The consumer of `cap-kernel-channels`: deliveries (`DELIVER_JOB`, owed through the outbox or
|
|
19
|
-
* hinted) and inbound jobs (`INBOUND_JOB`),
|
|
19
|
+
* hinted), a connection's catch-up (`RESUME_JOB`, owed) and inbound jobs (`INBOUND_JOB`), all
|
|
20
|
+
* handled by {@link ChannelOutbound}.
|
|
20
21
|
*/
|
|
21
22
|
export declare class ChannelsProcessor extends WorkerHost<Worker<ChannelJobData>> {
|
|
22
23
|
private readonly outbound;
|
|
@@ -32,11 +33,11 @@ export declare class ChannelsProcessor extends WorkerHost<Worker<ChannelJobData>
|
|
|
32
33
|
*/
|
|
33
34
|
export declare function channelsProcessorOptions(config: Pick<KernelConfig, "runtime" | "channels">): ProcessorWorkerOptions;
|
|
34
35
|
/**
|
|
35
|
-
* The consumer of the channel queue (concurrency `channels.deliveryConcurrency`, the run queue's
|
|
36
|
-
* lock and stalled detection, the delivery backoff). Imported only by the worker role
|
|
37
|
-
* (`WorkerModule`), so other roles get no processor and no BullMQ worker. It never starts by
|
|
38
|
-
* the worker role (`WorkerRole`) starts it after the extension load;
|
|
39
|
-
* on stop.
|
|
36
|
+
* The consumer of the channel queue (concurrency `channels.deliveryConcurrency`, the run queue's
|
|
37
|
+
* job lock and stalled detection, the delivery backoff). Imported only by the worker role
|
|
38
|
+
* (`WorkerModule`), so other roles get no processor and no BullMQ worker. It never starts by
|
|
39
|
+
* itself: the worker role (`WorkerRole`) starts it after the extension load;
|
|
40
|
+
* `ChannelOutbound.close` closes it on stop.
|
|
40
41
|
*/
|
|
41
42
|
export declare class ChannelWorkersModule {
|
|
42
43
|
static forRoot(config: KernelConfig): DynamicModule;
|
|
@@ -26,7 +26,7 @@ import { SchedulesModule } from "../schedules/schedules.module.js";
|
|
|
26
26
|
import { ToolsModule } from "../tools/tools.module.js";
|
|
27
27
|
import { ChannelConnections } from "./connections.service.js";
|
|
28
28
|
import { ChannelInbound } from "./inbound.js";
|
|
29
|
-
import { CHANNEL_QUEUE_NAME, ChannelJobs, DELIVER_JOB, INBOUND_JOB, } from "./jobs.js";
|
|
29
|
+
import { CHANNEL_QUEUE_NAME, ChannelJobs, DELIVER_JOB, INBOUND_JOB, RESUME_JOB, } from "./jobs.js";
|
|
30
30
|
import { ChannelOutbound, deliverBackoff } from "./outbound.js";
|
|
31
31
|
import { AnonymousGuestLock, AnonymousGuests } from "./threads.js";
|
|
32
32
|
/** The channel queue's registration (its producer here; the admin health reads its counts). */
|
|
@@ -35,8 +35,8 @@ const CHANNEL_QUEUE_MODULE = QueueModule.registerQueue(CHANNEL_QUEUE_NAME);
|
|
|
35
35
|
* Channels on every role: the org's channel connections, inbound (dedupe and enqueue; the inbound
|
|
36
36
|
* jobs), the outbound delivery, the producer of the channel queue `cap-kernel-channels`, the
|
|
37
37
|
* anonymous guests. The roles' parts are the role modules': the queue's consumer
|
|
38
|
-
* ({@link ChannelWorkersModule}) and the gateway check job are the worker role's, the
|
|
39
|
-
* supervisor and the webhook inbound the gateway role's (`GatewayModule`).
|
|
38
|
+
* ({@link ChannelWorkersModule}) and the gateway check job are the worker role's, the
|
|
39
|
+
* long-connection supervisor and the webhook inbound the gateway role's (`GatewayModule`).
|
|
40
40
|
*/
|
|
41
41
|
let ChannelsModule = class ChannelsModule {
|
|
42
42
|
};
|
|
@@ -77,7 +77,8 @@ ChannelsModule = __decorate([
|
|
|
77
77
|
export { ChannelsModule };
|
|
78
78
|
/**
|
|
79
79
|
* The consumer of `cap-kernel-channels`: deliveries (`DELIVER_JOB`, owed through the outbox or
|
|
80
|
-
* hinted) and inbound jobs (`INBOUND_JOB`),
|
|
80
|
+
* hinted), a connection's catch-up (`RESUME_JOB`, owed) and inbound jobs (`INBOUND_JOB`), all
|
|
81
|
+
* handled by {@link ChannelOutbound}.
|
|
81
82
|
*/
|
|
82
83
|
let ChannelsProcessor = class ChannelsProcessor extends WorkerHost {
|
|
83
84
|
outbound;
|
|
@@ -86,7 +87,7 @@ let ChannelsProcessor = class ChannelsProcessor extends WorkerHost {
|
|
|
86
87
|
this.outbound = outbound;
|
|
87
88
|
}
|
|
88
89
|
process(job, token) {
|
|
89
|
-
if (job.name !== DELIVER_JOB && job.name !== INBOUND_JOB) {
|
|
90
|
+
if (job.name !== DELIVER_JOB && job.name !== INBOUND_JOB && job.name !== RESUME_JOB) {
|
|
90
91
|
return Promise.reject(new UnrecoverableError(`unknown channel job "${job.name}"`));
|
|
91
92
|
}
|
|
92
93
|
return runJob(CHANNEL_QUEUE_NAME, job, () => this.outbound.process(job, token));
|
|
@@ -124,11 +125,11 @@ export function channelsProcessorOptions(config) {
|
|
|
124
125
|
};
|
|
125
126
|
}
|
|
126
127
|
/**
|
|
127
|
-
* The consumer of the channel queue (concurrency `channels.deliveryConcurrency`, the run queue's
|
|
128
|
-
* lock and stalled detection, the delivery backoff). Imported only by the worker role
|
|
129
|
-
* (`WorkerModule`), so other roles get no processor and no BullMQ worker. It never starts by
|
|
130
|
-
* the worker role (`WorkerRole`) starts it after the extension load;
|
|
131
|
-
* on stop.
|
|
128
|
+
* The consumer of the channel queue (concurrency `channels.deliveryConcurrency`, the run queue's
|
|
129
|
+
* job lock and stalled detection, the delivery backoff). Imported only by the worker role
|
|
130
|
+
* (`WorkerModule`), so other roles get no processor and no BullMQ worker. It never starts by
|
|
131
|
+
* itself: the worker role (`WorkerRole`) starts it after the extension load;
|
|
132
|
+
* `ChannelOutbound.close` closes it on stop.
|
|
132
133
|
*/
|
|
133
134
|
let ChannelWorkersModule = ChannelWorkersModule_1 = class ChannelWorkersModule {
|
|
134
135
|
static forRoot(config) {
|
|
@@ -123,7 +123,7 @@ export declare class ChannelConnections implements OnApplicationShutdown {
|
|
|
123
123
|
private readonly registry;
|
|
124
124
|
private readonly bus;
|
|
125
125
|
private readonly redis;
|
|
126
|
-
/** A connection that can deliver again
|
|
126
|
+
/** A connection that can deliver again owes its conversations' catch-up (`oweResume`). */
|
|
127
127
|
private readonly jobs;
|
|
128
128
|
/** `channels.webhookBaseUrl`: without it webhook mode is unavailable. */
|
|
129
129
|
private readonly config;
|
|
@@ -138,8 +138,8 @@ export declare class ChannelConnections implements OnApplicationShutdown {
|
|
|
138
138
|
constructor(db: KernelDb,
|
|
139
139
|
/** The org row lock (orgs/org-lock.ts): joins the transaction of the write. */
|
|
140
140
|
orgLock: Pick<OrgLock, "lockAndAuthorize">, keyring: Keyring, registry: Pick<ExtensionRegistry, "list">, bus: ExtensionContext["bus"], redis: Pick<Redis, "del">,
|
|
141
|
-
/** A connection that can deliver again
|
|
142
|
-
jobs: Pick<ChannelJobs, "
|
|
141
|
+
/** A connection that can deliver again owes its conversations' catch-up (`oweResume`). */
|
|
142
|
+
jobs: Pick<ChannelJobs, "oweResume">,
|
|
143
143
|
/** `channels.webhookBaseUrl`: without it webhook mode is unavailable. */
|
|
144
144
|
config: Pick<KernelConfigService, "get">, events: Pick<KernelEvents, "on">,
|
|
145
145
|
/** Secret values handled here (written or decrypted) are redacted from the logs (`addOrgSecrets`). */
|
|
@@ -154,7 +154,7 @@ let ChannelConnections = class ChannelConnections {
|
|
|
154
154
|
constructor(db,
|
|
155
155
|
/** The org row lock (orgs/org-lock.ts): joins the transaction of the write. */
|
|
156
156
|
orgLock, keyring, registry, bus, redis,
|
|
157
|
-
/** A connection that can deliver again
|
|
157
|
+
/** A connection that can deliver again owes its conversations' catch-up (`oweResume`). */
|
|
158
158
|
jobs,
|
|
159
159
|
/** `channels.webhookBaseUrl`: without it webhook mode is unavailable. */
|
|
160
160
|
config, events,
|
|
@@ -546,11 +546,6 @@ let ChannelConnections = class ChannelConnections {
|
|
|
546
546
|
}
|
|
547
547
|
// A mode switch also reconnects: the holder drops the long connection when going to webhook (F9)
|
|
548
548
|
await this.changed(id, rotated !== undefined || patch.status !== undefined || modeChanged);
|
|
549
|
-
if ((rotated || patch.status === "active") && row.status === "active") {
|
|
550
|
-
await this.jobs
|
|
551
|
-
.resumeConnection(id)
|
|
552
|
-
.catch((err) => this.log.warn("cannot resume channel deliveries", { err }));
|
|
553
|
-
}
|
|
554
549
|
return { channel: await this.viewOf(row), verifySkipped };
|
|
555
550
|
}
|
|
556
551
|
/** The rows of {@link update}, in their own transaction (the caller publishes after the commit). */
|
|
@@ -592,6 +587,11 @@ let ChannelConnections = class ChannelConnections {
|
|
|
592
587
|
.executeTakeFirst();
|
|
593
588
|
if (!updated)
|
|
594
589
|
throw notFound();
|
|
590
|
+
// can deliver again (re-enabled, or new credentials while active): the conversations that fell
|
|
591
|
+
// behind meanwhile are caught up by a job owed here (transactional outbox spec §3.1)
|
|
592
|
+
if ((rotated || patch.status === "active") && updated.status === "active") {
|
|
593
|
+
await this.jobs.oweResume(id, orgId, updated.updated_at);
|
|
594
|
+
}
|
|
595
595
|
await audit(this.db.db, {
|
|
596
596
|
actorAccountId,
|
|
597
597
|
...(actorTokenId !== undefined && { actorTokenId }),
|
|
@@ -4,15 +4,22 @@ import type { OutboxService } from "../infra/outbox/outbox.service.js";
|
|
|
4
4
|
* with no BullMQ and no Nest class, so the event log can owe it without importing the channels
|
|
5
5
|
* domain. `jobs.ts` (the producer) re-exports these names.
|
|
6
6
|
*/
|
|
7
|
-
/**
|
|
7
|
+
/** The channel queue (outbound deliveries and inbound jobs; no `:`), consumed only by workers. */
|
|
8
8
|
export declare const CHANNEL_QUEUE_NAME = "cap-kernel-channels";
|
|
9
|
-
/**
|
|
9
|
+
/** The delivery job's name. */
|
|
10
10
|
export declare const DELIVER_JOB = "deliver";
|
|
11
|
-
/**
|
|
11
|
+
/** The delivery job id (no `:`): one job per conversation and log position. */
|
|
12
12
|
export declare const deliverJobId: (conversationId: string, seq: number) => string;
|
|
13
13
|
export interface DeliverJobData {
|
|
14
14
|
conversationId: string;
|
|
15
15
|
}
|
|
16
|
+
/** The catch-up job of a connection that can deliver again (re-enabled, credentials rotated). */
|
|
17
|
+
export declare const RESUME_JOB = "channel-resume";
|
|
18
|
+
/** Its job id: one per connection and update (`updated_at` in ms; no `:`). */
|
|
19
|
+
export declare const resumeJobId: (connectionId: string, at: Date) => string;
|
|
20
|
+
export interface ResumeJobData {
|
|
21
|
+
connectionId: string;
|
|
22
|
+
}
|
|
16
23
|
/**
|
|
17
24
|
* Owes the conversation's delivery job for the log position `seq` in the transaction in context:
|
|
18
25
|
* the caller knows the conversation has a channel target. Shares the job id of
|
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
* with no BullMQ and no Nest class, so the event log can owe it without importing the channels
|
|
4
4
|
* domain. `jobs.ts` (the producer) re-exports these names.
|
|
5
5
|
*/
|
|
6
|
-
/**
|
|
6
|
+
/** The channel queue (outbound deliveries and inbound jobs; no `:`), consumed only by workers. */
|
|
7
7
|
export const CHANNEL_QUEUE_NAME = "cap-kernel-channels";
|
|
8
|
-
/**
|
|
8
|
+
/** The delivery job's name. */
|
|
9
9
|
export const DELIVER_JOB = "deliver";
|
|
10
|
-
/**
|
|
10
|
+
/** The delivery job id (no `:`): one job per conversation and log position. */
|
|
11
11
|
export const deliverJobId = (conversationId, seq) => `deliver-${conversationId}-${seq}`;
|
|
12
|
+
/** The catch-up job of a connection that can deliver again (re-enabled, credentials rotated). */
|
|
13
|
+
export const RESUME_JOB = "channel-resume";
|
|
14
|
+
/** Its job id: one per connection and update (`updated_at` in ms; no `:`). */
|
|
15
|
+
export const resumeJobId = (connectionId, at) => `channel-resume-${connectionId}-${at.getTime()}`;
|
|
12
16
|
/**
|
|
13
17
|
* Owes the conversation's delivery job for the log position `seq` in the transaction in context:
|
|
14
18
|
* the caller knows the conversation has a channel target. Shares the job id of
|
package/dist/channels/jobs.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { KernelDb } from "../infra/database/kernel-db.js";
|
|
|
5
5
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
6
6
|
import { KernelEvents } from "../infra/events/kernel-events.js";
|
|
7
7
|
import { OutboxService } from "../infra/outbox/outbox.service.js";
|
|
8
|
-
import { type DeliverJobData } from "./deliver-job.js";
|
|
9
|
-
export { CHANNEL_QUEUE_NAME, DELIVER_JOB, type DeliverJobData, deliverJobId, oweDelivery, } from "./deliver-job.js";
|
|
8
|
+
import { type DeliverJobData, type ResumeJobData } from "./deliver-job.js";
|
|
9
|
+
export { CHANNEL_QUEUE_NAME, DELIVER_JOB, type DeliverJobData, deliverJobId, oweDelivery, RESUME_JOB, type ResumeJobData, resumeJobId, } from "./deliver-job.js";
|
|
10
10
|
/**
|
|
11
11
|
* 结束后复核排的任务 id:每次唯一(带随机后缀,不含 `:`)。不能复用 `deliver-<会话>-<seq>`——同 id 的任务
|
|
12
12
|
* 可能正因锁被占而即将直接结束,BullMQ 会把新任务去重进它,于是没人投递。
|
|
@@ -24,7 +24,7 @@ export interface InboundJobData {
|
|
|
24
24
|
connectionId: string;
|
|
25
25
|
event: InboundEvent;
|
|
26
26
|
}
|
|
27
|
-
export type ChannelJobData = DeliverJobData | InboundJobData;
|
|
27
|
+
export type ChannelJobData = DeliverJobData | InboundJobData | ResumeJobData;
|
|
28
28
|
/** 入站任务名。 */
|
|
29
29
|
export declare const INBOUND_JOB = "inbound";
|
|
30
30
|
/**
|
|
@@ -86,7 +86,14 @@ export declare class ChannelJobs implements OnApplicationShutdown {
|
|
|
86
86
|
*/
|
|
87
87
|
enqueueInbound(connectionId: string, event: InboundEvent, jobId: string): Promise<void>;
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
89
|
+
* Owes the catch-up of a connection that can deliver again in the caller's transaction (the
|
|
90
|
+
* update that re-enabled it or rotated its credentials while active): the deliveries that ran
|
|
91
|
+
* while it could not deliver have ended, and nothing else finds the conversations lagging the log.
|
|
92
|
+
* `at` is the connection row's new `updated_at`.
|
|
93
|
+
*/
|
|
94
|
+
oweResume(connectionId: string, orgId: string, at: Date): Promise<boolean>;
|
|
95
|
+
/**
|
|
96
|
+
* The {@link RESUME_JOB} job: 连接重新可投递(轮换了凭据 / 重新启用):给该连接投递进度落后于会话日志的会话各排一个出站任务,
|
|
90
97
|
* 补投停用或凭据失效期间落下的内容(不必等下一条消息)。
|
|
91
98
|
*/
|
|
92
99
|
resumeConnection(connectionId: string): Promise<void>;
|
package/dist/channels/jobs.js
CHANGED
|
@@ -17,8 +17,8 @@ import { KernelEvents } from "../infra/events/kernel-events.js";
|
|
|
17
17
|
import { OutboxService } from "../infra/outbox/outbox.service.js";
|
|
18
18
|
import { OUTBOX_JOBS } from "../infra/outbox/registry.js";
|
|
19
19
|
import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
20
|
-
import { CHANNEL_QUEUE_NAME, DELIVER_JOB, deliverJobId, oweDelivery, } from "./deliver-job.js";
|
|
21
|
-
export { CHANNEL_QUEUE_NAME, DELIVER_JOB, deliverJobId, oweDelivery, } from "./deliver-job.js";
|
|
20
|
+
import { CHANNEL_QUEUE_NAME, DELIVER_JOB, deliverJobId, oweDelivery, RESUME_JOB, resumeJobId, } from "./deliver-job.js";
|
|
21
|
+
export { CHANNEL_QUEUE_NAME, DELIVER_JOB, deliverJobId, oweDelivery, RESUME_JOB, resumeJobId, } from "./deliver-job.js";
|
|
22
22
|
/**
|
|
23
23
|
* 结束后复核排的任务 id:每次唯一(带随机后缀,不含 `:`)。不能复用 `deliver-<会话>-<seq>`——同 id 的任务
|
|
24
24
|
* 可能正因锁被占而即将直接结束,BullMQ 会把新任务去重进它,于是没人投递。
|
|
@@ -160,7 +160,22 @@ let ChannelJobs = class ChannelJobs {
|
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
162
|
/**
|
|
163
|
-
*
|
|
163
|
+
* Owes the catch-up of a connection that can deliver again in the caller's transaction (the
|
|
164
|
+
* update that re-enabled it or rotated its credentials while active): the deliveries that ran
|
|
165
|
+
* while it could not deliver have ended, and nothing else finds the conversations lagging the log.
|
|
166
|
+
* `at` is the connection row's new `updated_at`.
|
|
167
|
+
*/
|
|
168
|
+
oweResume(connectionId, orgId, at) {
|
|
169
|
+
return this.outbox.add({
|
|
170
|
+
queue: CHANNEL_QUEUE_NAME,
|
|
171
|
+
name: RESUME_JOB,
|
|
172
|
+
jobId: resumeJobId(connectionId, at),
|
|
173
|
+
data: { connectionId },
|
|
174
|
+
orgId,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The {@link RESUME_JOB} job: 连接重新可投递(轮换了凭据 / 重新启用):给该连接投递进度落后于会话日志的会话各排一个出站任务,
|
|
164
179
|
* 补投停用或凭据失效期间落下的内容(不必等下一条消息)。
|
|
165
180
|
*/
|
|
166
181
|
async resumeConnection(connectionId) {
|