@capybara-hq/kernel 0.1.0-rc.21 → 0.1.0-rc.22
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/agents/admin-agents.d.ts +16 -3
- package/dist/agents/admin-agents.js +65 -7
- package/dist/agents/agents.schemas.d.ts +24 -6
- package/dist/agents/agents.service.d.ts +4 -3
- package/dist/agents/agents.service.js +39 -26
- package/dist/agents/definition.d.ts +44 -6
- package/dist/agents/definition.js +86 -18
- package/dist/agents/from-template.d.ts +6 -3
- package/dist/agents/from-template.js +57 -44
- package/dist/agents/org-templates.js +97 -41
- package/dist/agents/templates.d.ts +6 -0
- package/dist/agents/templates.js +15 -1
- package/dist/catalog/admin-catalog.controller.d.ts +2 -4
- package/dist/catalog/admin-catalog.controller.js +2 -6
- package/dist/catalog/catalog.module.js +3 -0
- package/dist/catalog/claude/report.d.ts +9 -2
- package/dist/catalog/claude/report.js +1 -0
- package/dist/catalog/claude/translate.js +1 -1
- package/dist/catalog/component-promotion.js +1 -2
- package/dist/catalog/component-state.d.ts +13 -1
- package/dist/catalog/component-state.js +14 -5
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/marketplaces.service.js +2 -1
- package/dist/catalog/org-catalog.controller.d.ts +13 -3
- package/dist/catalog/org-catalog.controller.js +26 -4
- package/dist/catalog/org-catalog.schemas.d.ts +13 -0
- package/dist/catalog/org-catalog.schemas.js +34 -0
- package/dist/catalog/org-plugin-uploads.controller.js +0 -1
- package/dist/catalog/org-plugin-uploads.d.ts +0 -1
- package/dist/catalog/org-plugin-uploads.js +0 -3
- package/dist/catalog/org-plugin-uploads.schemas.d.ts +0 -1
- package/dist/catalog/org-plugin-uploads.schemas.js +0 -2
- package/dist/catalog/platform-components.d.ts +0 -1
- package/dist/catalog/platform-components.js +3 -8
- package/dist/catalog/plugin-import.d.ts +0 -8
- package/dist/catalog/plugin-import.js +4 -35
- package/dist/catalog/plugin-install.d.ts +2 -2
- package/dist/catalog/plugin-install.js +2 -2
- package/dist/catalog/plugin-package.d.ts +7 -0
- package/dist/catalog/plugin-package.js +27 -1
- package/dist/catalog/plugin-upgrade.d.ts +5 -6
- package/dist/catalog/plugin-upgrade.js +5 -23
- package/dist/catalog/requests.service.d.ts +66 -10
- package/dist/catalog/requests.service.js +196 -15
- package/dist/channels/actions.d.ts +2 -0
- package/dist/channels/actions.js +19 -4
- package/dist/channels/cards.d.ts +16 -2
- package/dist/channels/cards.js +40 -7
- package/dist/channels/channels.module.js +3 -0
- package/dist/channels/outbound.d.ts +22 -1
- package/dist/channels/outbound.js +116 -7
- package/dist/channels/phrases.d.ts +25 -0
- package/dist/channels/phrases.js +19 -0
- package/dist/channels/threads.d.ts +12 -0
- package/dist/channels/threads.js +34 -0
- package/dist/config/schema.d.ts +23 -1
- package/dist/config/schema.js +105 -5
- package/dist/conversations/conversations.service.d.ts +42 -9
- package/dist/conversations/conversations.service.js +57 -25
- package/dist/conversations/events.d.ts +106 -11
- package/dist/conversations/events.js +121 -18
- package/dist/conversations/message-context.d.ts +6 -3
- package/dist/conversations/message-context.js +14 -10
- package/dist/conversations/queue.d.ts +15 -2
- package/dist/conversations/queue.js +22 -3
- package/dist/conversations/queue.service.d.ts +12 -6
- package/dist/conversations/queue.service.js +36 -13
- package/dist/db/kernel-schema.d.ts +105 -3
- package/dist/devices/conversation-devices.d.ts +8 -1
- package/dist/devices/conversation-devices.js +8 -0
- package/dist/devices/devices.controller.d.ts +21 -4
- package/dist/devices/devices.controller.js +46 -8
- package/dist/devices/devices.schemas.d.ts +9 -1
- package/dist/devices/devices.schemas.js +11 -3
- package/dist/devices/devices.service.d.ts +176 -4
- package/dist/devices/devices.service.js +341 -6
- package/dist/devices/dto.d.ts +56 -3
- package/dist/devices/dto.js +51 -1
- package/dist/devices/public-suffix.d.ts +6 -0
- package/dist/devices/public-suffix.js +79 -0
- package/dist/errors.d.ts +46 -5
- package/dist/errors.js +46 -5
- package/dist/evals/reconstruct.d.ts +18 -13
- package/dist/evals/reconstruct.js +50 -36
- package/dist/evals/recorded-tools.d.ts +7 -7
- package/dist/evals/recorded-tools.js +4 -3
- package/dist/evals/trajectory.d.ts +6 -1
- package/dist/evals/trajectory.js +7 -0
- package/dist/extensions/package.js +2 -2
- package/dist/http/api.module.js +5 -0
- package/dist/inbox/inbox.module.js +2 -1
- package/dist/inbox/inbox.service.d.ts +19 -4
- package/dist/inbox/inbox.service.js +14 -3
- package/dist/infra/config/kernel-config.service.js +11 -0
- package/dist/infra/events/kernel-events.d.ts +2 -1
- package/dist/local-devices/device-policy.service.d.ts +65 -0
- package/dist/local-devices/device-policy.service.js +217 -0
- package/dist/local-devices/local-device-gateway.js +4 -1
- package/dist/local-devices/local-device-provider.d.ts +40 -3
- package/dist/local-devices/local-device-provider.js +109 -7
- package/dist/local-devices/local-devices.module.d.ts +2 -1
- package/dist/local-devices/local-devices.module.js +19 -3
- package/dist/local-devices/machines.d.ts +14 -3
- package/dist/local-devices/machines.js +41 -3
- package/dist/local-devices/network-grants.d.ts +104 -0
- package/dist/local-devices/network-grants.js +288 -0
- package/dist/local-devices/policy-summary.d.ts +20 -0
- package/dist/local-devices/policy-summary.js +63 -0
- package/dist/orgs/orgs.module.js +4 -10
- package/dist/orgs/roles.d.ts +1 -1
- package/dist/orgs/roles.js +4 -0
- package/dist/orgs/settings.controller.js +2 -1
- package/dist/orgs/settings.service.d.ts +164 -12
- package/dist/orgs/settings.service.js +212 -24
- package/dist/platform/exclusions.js +11 -1
- package/dist/platform/operation.js +4 -0
- package/dist/platform/ops/admin-items.ops.js +1 -1
- package/dist/platform/ops/agent-skills.ops.js +2 -2
- package/dist/platform/ops/agent-templates.ops.js +2 -2
- package/dist/platform/ops/agents.ops.js +11 -15
- package/dist/platform/ops/devices.ops.js +3 -1
- package/dist/platform/ops/inbox.ops.js +19 -0
- package/dist/platform/ops/org-settings.ops.js +14 -5
- package/dist/platform/ops/platform.ops.d.ts +9 -1
- package/dist/platform/ops/platform.ops.js +34 -12
- package/dist/platform/ops/requests.ops.js +3 -2
- package/dist/platform/ops/schedules.ops.d.ts +21 -0
- package/dist/platform/ops/schedules.ops.js +470 -0
- package/dist/platform/ops/skills.ops.js +3 -3
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +5 -0
- package/dist/platform/route-schemas.js +3 -0
- package/dist/roles/worker.module.js +3 -0
- package/dist/runtime/context/build.js +13 -4
- package/dist/runtime/context/compaction.js +6 -3
- package/dist/runtime/context/device-paths.d.ts +43 -21
- package/dist/runtime/context/device-paths.js +104 -21
- package/dist/runtime/context/run-context.d.ts +9 -0
- package/dist/runtime/context/run-context.js +8 -0
- package/dist/runtime/context/skills.d.ts +14 -1
- package/dist/runtime/context/skills.js +36 -5
- package/dist/runtime/maintenance.d.ts +4 -2
- package/dist/runtime/maintenance.js +16 -5
- package/dist/runtime/run-finish.d.ts +6 -1
- package/dist/runtime/run-finish.js +16 -2
- package/dist/runtime/run-queue.js +21 -2
- package/dist/runtime/run-worker.js +14 -33
- package/dist/runtime/runtime.module.js +3 -0
- package/dist/runtime/subagents/definition.d.ts +32 -14
- package/dist/runtime/subagents/definition.js +106 -109
- package/dist/runtime/subagents/dispatch.js +4 -3
- package/dist/runtime/subagents/preamble.d.ts +2 -1
- package/dist/runtime/subagents/preamble.js +2 -1
- package/dist/runtime/waits.js +14 -4
- package/dist/sandbox/sandbox.service.d.ts +120 -10
- package/dist/sandbox/sandbox.service.js +335 -28
- package/dist/sandbox/tiers.d.ts +86 -0
- package/dist/sandbox/tiers.js +96 -0
- package/dist/schedules/effective-settings.d.ts +16 -0
- package/dist/schedules/effective-settings.js +17 -0
- package/dist/schedules/errors.d.ts +8 -0
- package/dist/schedules/errors.js +8 -0
- package/dist/schedules/fire.d.ts +118 -0
- package/dist/schedules/fire.js +625 -0
- package/dist/schedules/grant-rules.d.ts +94 -0
- package/dist/schedules/grant-rules.js +126 -0
- package/dist/schedules/job-ids.d.ts +32 -0
- package/dist/schedules/job-ids.js +27 -0
- package/dist/schedules/pause.d.ts +9 -0
- package/dist/schedules/pause.js +33 -0
- package/dist/schedules/run-tracker.d.ts +81 -0
- package/dist/schedules/run-tracker.js +333 -0
- package/dist/schedules/scan.job.d.ts +64 -0
- package/dist/schedules/scan.job.js +303 -0
- package/dist/schedules/schedule-math.d.ts +31 -0
- package/dist/schedules/schedule-math.js +223 -0
- package/dist/schedules/scheduled-tasks.service.d.ts +351 -0
- package/dist/schedules/scheduled-tasks.service.js +1193 -0
- package/dist/schedules/schedules.controller.d.ts +68 -0
- package/dist/schedules/schedules.controller.js +242 -0
- package/dist/schedules/schedules.module.d.ts +16 -0
- package/dist/schedules/schedules.module.js +65 -0
- package/dist/schedules/schedules.schemas.d.ts +316 -0
- package/dist/schedules/schedules.schemas.js +186 -0
- package/dist/schedules/store.d.ts +81 -0
- package/dist/schedules/store.js +390 -0
- package/dist/schedules/store.module.d.ts +6 -0
- package/dist/schedules/store.module.js +21 -0
- package/dist/schedules/types.d.ts +108 -0
- package/dist/schedules/types.js +1 -0
- package/dist/schedules/unattended.d.ts +117 -0
- package/dist/schedules/unattended.js +228 -0
- package/dist/setup/oauth-token-probe.js +8 -4
- package/dist/skills/agent-skills.service.d.ts +4 -8
- package/dist/skills/agent-skills.service.js +18 -53
- package/dist/skills/catalog.d.ts +65 -62
- package/dist/skills/catalog.js +109 -206
- package/dist/skills/conversation-catalog.d.ts +3 -3
- package/dist/skills/conversation-catalog.js +5 -12
- package/dist/skills/invoke.d.ts +2 -2
- package/dist/skills/skills.controller.d.ts +3 -1
- package/dist/skills/skills.service.d.ts +38 -37
- package/dist/skills/skills.service.js +168 -107
- package/dist/skills/usable.d.ts +57 -8
- package/dist/skills/usable.js +172 -38
- package/dist/tools/approvals.controller.d.ts +4 -2
- package/dist/tools/approvals.controller.js +17 -2
- package/dist/tools/approvals.schemas.d.ts +1 -0
- package/dist/tools/approvals.schemas.js +3 -1
- package/dist/tools/approvals.service.d.ts +182 -7
- package/dist/tools/approvals.service.js +597 -13
- package/dist/tools/builtin-names.d.ts +25 -4
- package/dist/tools/builtin-names.js +45 -4
- package/dist/tools/builtins/builtin-tools.d.ts +8 -0
- package/dist/tools/builtins/builtin-tools.js +23 -2
- package/dist/tools/builtins/sandbox/bash.js +16 -7
- package/dist/tools/builtins/sandbox/common.d.ts +1 -1
- package/dist/tools/builtins/sandbox/common.js +33 -10
- package/dist/tools/builtins/sandbox/edit.js +7 -3
- package/dist/tools/builtins/sandbox/file-state.d.ts +31 -1
- package/dist/tools/builtins/sandbox/file-state.js +33 -3
- package/dist/tools/builtins/sandbox/write.js +8 -4
- package/dist/tools/builtins/schedules.d.ts +107 -0
- package/dist/tools/builtins/schedules.js +789 -0
- package/dist/tools/elicitation.d.ts +2 -0
- package/dist/tools/elicitation.js +1 -1
- package/dist/tools/fixed-ask.d.ts +7 -0
- package/dist/tools/fixed-ask.js +9 -0
- package/dist/tools/network-asks.d.ts +109 -0
- package/dist/tools/network-asks.js +221 -0
- package/dist/tools/pipeline.d.ts +53 -6
- package/dist/tools/pipeline.js +202 -13
- package/dist/tools/policy.d.ts +21 -8
- package/dist/tools/policy.js +63 -9
- package/dist/tools/registry.d.ts +10 -1
- package/dist/tools/tools.module.js +12 -0
- package/package.json +2 -2
- package/src/migrations/0113_scheduled_tasks.sql +108 -0
- package/src/migrations/0114_network_asks.sql +23 -0
- package/src/migrations/0115_network_requests.sql +28 -0
- package/src/migrations/0116_skills_default_on.sql +37 -0
- package/src/migrations/0117_sandbox_tiers.sql +11 -0
|
@@ -5,13 +5,31 @@ import { KernelDb } from "../infra/database/kernel-db.js";
|
|
|
5
5
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
6
6
|
import { LockService } from "../infra/lock/lock.service.js";
|
|
7
7
|
import { OrgLock } from "../orgs/org-lock.js";
|
|
8
|
+
import { OrgSettingsService } from "../orgs/settings.service.js";
|
|
8
9
|
import { CatalogService, type MarketplaceImportResult } from "./catalog.service.js";
|
|
9
10
|
import { CatalogScopeService } from "./catalog-scope.service.js";
|
|
10
11
|
import { OrgCatalogService } from "./org-catalog.service.js";
|
|
11
12
|
import { PluginImport } from "./plugin-import.js";
|
|
12
13
|
import type { RequestActorOpts } from "./request-rules.js";
|
|
13
|
-
export type CatalogRequestKind = "access" | "import" | "enable" | "connector_setup";
|
|
14
|
+
export type CatalogRequestKind = "access" | "import" | "enable" | "connector_setup" | "network_allowlist";
|
|
14
15
|
export type CatalogRequestStatus = "pending" | "approved" | "rejected" | "cancelled";
|
|
16
|
+
/**
|
|
17
|
+
* What a `network_allowlist` request asks for (network ask-to-allow spec §5.3): the hosts (sorted,
|
|
18
|
+
* lowercased, exact) and the conversation and agent of the blocked command, when the member gave
|
|
19
|
+
* them. Stored in `catalog_requests.detail`; the member's note is the request's `note`.
|
|
20
|
+
*/
|
|
21
|
+
export interface NetworkRequestDetail {
|
|
22
|
+
hosts: string[];
|
|
23
|
+
conversationId: string | null;
|
|
24
|
+
agentId: string | null;
|
|
25
|
+
}
|
|
26
|
+
/** A member's network request input (`POST /orgs/:orgId/catalog-requests/network`). */
|
|
27
|
+
export interface NetworkRequestInput {
|
|
28
|
+
hosts: string[];
|
|
29
|
+
note?: string | undefined;
|
|
30
|
+
conversationId?: string | undefined;
|
|
31
|
+
agentId?: string | undefined;
|
|
32
|
+
}
|
|
15
33
|
/** A pending connector a `connector_setup` request waits for (ruling P4-T6-5). */
|
|
16
34
|
export interface SetupRequestConnector {
|
|
17
35
|
/** The agent's `pendingConnectors[].key` (the bind route's `:key`). */
|
|
@@ -81,6 +99,8 @@ export interface CatalogRequestView {
|
|
|
81
99
|
* other kinds.
|
|
82
100
|
*/
|
|
83
101
|
connectors: SetupRequestConnector[] | null;
|
|
102
|
+
/** The hosts, conversation and agent of a `network_allowlist` request; null for other kinds. */
|
|
103
|
+
network: NetworkRequestDetail | null;
|
|
84
104
|
status: CatalogRequestStatus;
|
|
85
105
|
note: string;
|
|
86
106
|
/** The optional reason of a rejection. */
|
|
@@ -99,6 +119,8 @@ export interface CatalogDecision {
|
|
|
99
119
|
approve: boolean;
|
|
100
120
|
reason?: string | undefined;
|
|
101
121
|
}
|
|
122
|
+
/** The most pending `network_allowlist` requests one member may have in an org. */
|
|
123
|
+
export declare const NETWORK_REQUESTS_PENDING_MAX = 10;
|
|
102
124
|
/** 申请列表一次最多返回的条数。 */
|
|
103
125
|
export declare const CATALOG_REQUESTS_LIST_MAX = 200;
|
|
104
126
|
/**
|
|
@@ -115,7 +137,9 @@ export declare const CATALOG_REQUESTS_LIST_MAX = 200;
|
|
|
115
137
|
* - `connector_setup` (ruling P4-7): one per agent (`agent_id`, `pending_keys`), asking the org
|
|
116
138
|
* admins to set up an agent's pending connectors; written by the agents module
|
|
117
139
|
* (`PendingConnectorRequests`), closed by binding (`approved`) or the agent's deletion
|
|
118
|
-
* (`cancelled`); here it is listed and rejected
|
|
140
|
+
* (`cancelled`); here it is listed and rejected;
|
|
141
|
+
* - `network_allowlist` (network ask-to-allow spec §5.3): a member asks the org admins to add hosts
|
|
142
|
+
* to the org's sandbox egress allowlist; approving merges them into the org's `sandboxEgress`.
|
|
119
143
|
* A rejection stores an optional reason. Decided requests show in the requester's inbox until
|
|
120
144
|
* dismissed. No in-process state.
|
|
121
145
|
*/
|
|
@@ -129,12 +153,16 @@ export declare class CatalogRequestsService {
|
|
|
129
153
|
private readonly catalog;
|
|
130
154
|
private readonly config;
|
|
131
155
|
private readonly pluginImport;
|
|
156
|
+
/** Approving a network request writes the org's `sandboxEgress` through the settings path. */
|
|
157
|
+
private readonly orgSettings;
|
|
132
158
|
readonly txHost: TransactionHost;
|
|
133
159
|
/** Serializes the approvals of one `import` request (`@WithLock`). */
|
|
134
160
|
readonly lockService: LockService;
|
|
135
161
|
constructor(kdb: KernelDb,
|
|
136
162
|
/** The org row and super admin locks: both join the caller's transaction. */
|
|
137
|
-
orgLock: OrgLock, adminLock: SuperAdminLock, orgCatalog: OrgCatalogService, scope: CatalogScopeService, catalog: CatalogService, config: KernelConfigService, pluginImport: PluginImport,
|
|
163
|
+
orgLock: OrgLock, adminLock: SuperAdminLock, orgCatalog: OrgCatalogService, scope: CatalogScopeService, catalog: CatalogService, config: KernelConfigService, pluginImport: PluginImport,
|
|
164
|
+
/** Approving a network request writes the org's `sandboxEgress` through the settings path. */
|
|
165
|
+
orgSettings: OrgSettingsService, txHost: TransactionHost,
|
|
138
166
|
/** Serializes the approvals of one `import` request (`@WithLock`). */
|
|
139
167
|
lockService: LockService);
|
|
140
168
|
/** The kernel-schema handle of the current context (the transaction, else the pool). */
|
|
@@ -155,6 +183,19 @@ export declare class CatalogRequestsService {
|
|
|
155
183
|
/** The role check alone, before any item error (a member cannot probe items by error codes). */
|
|
156
184
|
private authorize;
|
|
157
185
|
private writeItemRequest;
|
|
186
|
+
/**
|
|
187
|
+
* A member asks the org admins to allow hosts in the org's sandbox egress (`POST /orgs/:orgId/
|
|
188
|
+
* catalog-requests/network`, `catalog.request`; spec §5.3). The hosts arrive validated (exact,
|
|
189
|
+
* lowercased) and are deduped and sorted here. The deployment egress `none` ->
|
|
190
|
+
* `catalog.request_invalid` (only a super admin can turn sandbox network on); the org's own
|
|
191
|
+
* egress `none` -> `catalog.request_invalid`; {@link NETWORK_REQUESTS_PENDING_MAX} pending
|
|
192
|
+
* requests of the member already -> `catalog.request_invalid`; every host already
|
|
193
|
+
* allowed by the org's effective egress -> `catalog.request_invalid`; a conversation or agent not
|
|
194
|
+
* in the org -> `catalog.request_invalid`. A pending request of the same requester for the same
|
|
195
|
+
* host set is returned as is (no second request). Audits `catalog.requested`.
|
|
196
|
+
*/
|
|
197
|
+
requestNetwork(orgId: string, actor: string, input: NetworkRequestInput, opts?: RequestActorOpts): Promise<CatalogRequestView>;
|
|
198
|
+
private writeNetworkRequest;
|
|
158
199
|
/**
|
|
159
200
|
* An org admin asks for a marketplace entry to be imported (`POST /orgs/:orgId/
|
|
160
201
|
* marketplace-requests`, `catalog.decide`). The deployment setting `orgMarketplaceBrowse` off ->
|
|
@@ -173,17 +214,21 @@ export declare class CatalogRequestsService {
|
|
|
173
214
|
}, opts?: RequestActorOpts): Promise<CatalogRequestView>;
|
|
174
215
|
private writeImportRequest;
|
|
175
216
|
/**
|
|
176
|
-
* An org admin decides a member's `enable` or `connector_setup` request
|
|
177
|
-
* this org's request of those kinds -> `catalog.request_not_found`;
|
|
178
|
-
* `catalog.request_decided`. Approving
|
|
179
|
-
*
|
|
217
|
+
* An org admin decides a member's `enable`, `network_allowlist` or `connector_setup` request
|
|
218
|
+
* (`catalog.decide`). Not this org's request of those kinds -> `catalog.request_not_found`;
|
|
219
|
+
* decided -> `catalog.request_decided`. Approving a `network_allowlist` request merges its hosts
|
|
220
|
+
* into the org's `sandboxEgress` (`approveNetwork`). Approving an `enable` request switches the
|
|
221
|
+
* item on in the org as the deciding admin ({@link OrgCatalogService.setSwitch}: its errors, e.g. inputs the install
|
|
180
222
|
* needs, leave the request pending); the install itself resolves the request. A
|
|
181
223
|
* `connector_setup` request is approved only by binding its connectors (ruling P4-T6-6):
|
|
182
224
|
* approving it -> `catalog.request_invalid`. Rejecting stores the reason. Audits
|
|
183
225
|
* `catalog.request_decided`.
|
|
184
226
|
*/
|
|
185
227
|
decideOrg(orgId: string, actor: string, requestId: string, decision: CatalogDecision, opts?: RequestActorOpts): Promise<CatalogRequestView>;
|
|
186
|
-
/**
|
|
228
|
+
/**
|
|
229
|
+
* The item of a pending `enable` request of the org, null for a `network_allowlist` one (the
|
|
230
|
+
* decider re-checked).
|
|
231
|
+
*/
|
|
187
232
|
private pendingOrgRequest;
|
|
188
233
|
/**
|
|
189
234
|
* A super admin decides an `access` or `import` request (re-checked inside the super
|
|
@@ -218,6 +263,17 @@ export declare class CatalogRequestsService {
|
|
|
218
263
|
* approval of an `enable` request may find it approved already by the install it triggered.
|
|
219
264
|
*/
|
|
220
265
|
private writeDecision;
|
|
266
|
+
/**
|
|
267
|
+
* Approves a `network_allowlist` request inside its decision's transaction: the hosts the org's
|
|
268
|
+
* effective egress does not allow yet are added to the org's `sandboxEgress.allowHosts` through
|
|
269
|
+
* {@link OrgSettingsService.update} (audited `org.settings_updated` with `requestId`). An org
|
|
270
|
+
* following the deployment default starts from the deployment's list (it keeps its own list from
|
|
271
|
+
* then on). The deployment egress `none`, or the org's `none` -> `catalog.request_invalid`; more
|
|
272
|
+
* than {@link ORG_SANDBOX_ALLOW_HOSTS_MAX} hosts -> `catalog.request_invalid`; decided with an API
|
|
273
|
+
* token -> `org.sandbox_egress_not_allowed` (only a person widens the network). Hosts all allowed
|
|
274
|
+
* already: nothing is written.
|
|
275
|
+
*/
|
|
276
|
+
private approveNetwork;
|
|
221
277
|
/**
|
|
222
278
|
* The requester dismisses a decided request from the inbox (`POST /orgs/:orgId/
|
|
223
279
|
* catalog-requests/:id/dismiss`). Not the requester's decided request in this org ->
|
|
@@ -232,8 +288,8 @@ export declare class CatalogRequestsService {
|
|
|
232
288
|
countPendingDeployment(): Promise<number>;
|
|
233
289
|
/**
|
|
234
290
|
* The inbox: when the account is an org admin of the org (the role read live), the members'
|
|
235
|
-
* pending `enable` requests and the pending `connector_setup` requests,
|
|
236
|
-
* none.
|
|
291
|
+
* pending `enable` and `network_allowlist` requests and the pending `connector_setup` requests,
|
|
292
|
+
* newest first; otherwise none.
|
|
237
293
|
*/
|
|
238
294
|
listForOrgAdmin(orgId: string, accountId: string): Promise<CatalogRequestView[]>;
|
|
239
295
|
/** The inbox: the account's decided requests in the org it has not dismissed, newest first. */
|
|
@@ -20,14 +20,19 @@ import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
|
20
20
|
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
21
21
|
import { LockService } from "../infra/lock/lock.service.js";
|
|
22
22
|
import { WithLock } from "../infra/lock/with-lock.decorator.js";
|
|
23
|
+
import { orgAllows } from "../local-devices/network-grants.js";
|
|
23
24
|
import { OrgLock } from "../orgs/org-lock.js";
|
|
24
25
|
import { roleAtLeast } from "../orgs/roles.js";
|
|
26
|
+
import { ORG_SANDBOX_ALLOW_HOSTS_MAX, OrgSettingsService, readOrgSettings, } from "../orgs/settings.service.js";
|
|
27
|
+
import { baseSandboxEgress } from "../sandbox/tiers.js";
|
|
25
28
|
import { CatalogService } from "./catalog.service.js";
|
|
26
29
|
import { CatalogScopeService } from "./catalog-scope.service.js";
|
|
27
30
|
import { assertEntryNotDeprecated, loadMarketplaceEntry } from "./marketplaces.service.js";
|
|
28
31
|
import { OrgCatalogService } from "./org-catalog.service.js";
|
|
29
32
|
import { PluginImport } from "./plugin-import.js";
|
|
30
33
|
import { itemsSeenBy } from "./scope-state.js";
|
|
34
|
+
/** The most pending `network_allowlist` requests one member may have in an org. */
|
|
35
|
+
export const NETWORK_REQUESTS_PENDING_MAX = 10;
|
|
31
36
|
/** 申请列表一次最多返回的条数。 */
|
|
32
37
|
export const CATALOG_REQUESTS_LIST_MAX = 200;
|
|
33
38
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
@@ -36,12 +41,13 @@ const itemNotFound = () => new ApiError("catalog.item_not_found", "Catalog item
|
|
|
36
41
|
const itemDisabled = () => new ApiError("catalog.item_disabled", "This catalog item is disabled by the super admin and cannot be requested");
|
|
37
42
|
const alreadyDecided = () => new ApiError("catalog.request_decided", "This request has already been decided");
|
|
38
43
|
const pendingExists = () => new ApiError("catalog.request_pending", "This organization already has a pending request of this kind for it");
|
|
44
|
+
const deploymentEgressNone = () => new ApiError("catalog.request_invalid", "The platform turned sandbox network off; only a super admin can turn it on");
|
|
39
45
|
/** A unique violation (the partial index of pending requests). */
|
|
40
46
|
const isUniqueViolation = (e) => typeof e === "object" && e !== null && e.code === "23505";
|
|
41
47
|
/** The deployment-level kinds (decided by the super admin). */
|
|
42
48
|
const DEPLOYMENT_KINDS = ["access", "import"];
|
|
43
49
|
/** The org-level kinds (decided by the org admins). */
|
|
44
|
-
const ORG_KINDS = ["enable", "connector_setup"];
|
|
50
|
+
const ORG_KINDS = ["enable", "connector_setup", "network_allowlist"];
|
|
45
51
|
/**
|
|
46
52
|
* Catalog requests under plugin scope (spec §5.1). Three kinds, one pending request per (org,
|
|
47
53
|
* kind, item or entry) (partial unique indexes; org-side writes run inside the org row lock):
|
|
@@ -56,7 +62,9 @@ const ORG_KINDS = ["enable", "connector_setup"];
|
|
|
56
62
|
* - `connector_setup` (ruling P4-7): one per agent (`agent_id`, `pending_keys`), asking the org
|
|
57
63
|
* admins to set up an agent's pending connectors; written by the agents module
|
|
58
64
|
* (`PendingConnectorRequests`), closed by binding (`approved`) or the agent's deletion
|
|
59
|
-
* (`cancelled`); here it is listed and rejected
|
|
65
|
+
* (`cancelled`); here it is listed and rejected;
|
|
66
|
+
* - `network_allowlist` (network ask-to-allow spec §5.3): a member asks the org admins to add hosts
|
|
67
|
+
* to the org's sandbox egress allowlist; approving merges them into the org's `sandboxEgress`.
|
|
60
68
|
* A rejection stores an optional reason. Decided requests show in the requester's inbox until
|
|
61
69
|
* dismissed. No in-process state.
|
|
62
70
|
*/
|
|
@@ -69,11 +77,14 @@ let CatalogRequestsService = class CatalogRequestsService {
|
|
|
69
77
|
catalog;
|
|
70
78
|
config;
|
|
71
79
|
pluginImport;
|
|
80
|
+
orgSettings;
|
|
72
81
|
txHost;
|
|
73
82
|
lockService;
|
|
74
83
|
constructor(kdb,
|
|
75
84
|
/** The org row and super admin locks: both join the caller's transaction. */
|
|
76
|
-
orgLock, adminLock, orgCatalog, scope, catalog, config, pluginImport,
|
|
85
|
+
orgLock, adminLock, orgCatalog, scope, catalog, config, pluginImport,
|
|
86
|
+
/** Approving a network request writes the org's `sandboxEgress` through the settings path. */
|
|
87
|
+
orgSettings, txHost,
|
|
77
88
|
/** Serializes the approvals of one `import` request (`@WithLock`). */
|
|
78
89
|
lockService) {
|
|
79
90
|
this.kdb = kdb;
|
|
@@ -84,6 +95,7 @@ let CatalogRequestsService = class CatalogRequestsService {
|
|
|
84
95
|
this.catalog = catalog;
|
|
85
96
|
this.config = config;
|
|
86
97
|
this.pluginImport = pluginImport;
|
|
98
|
+
this.orgSettings = orgSettings;
|
|
87
99
|
this.txHost = txHost;
|
|
88
100
|
this.lockService = lockService;
|
|
89
101
|
}
|
|
@@ -170,6 +182,113 @@ let CatalogRequestsService = class CatalogRequestsService {
|
|
|
170
182
|
});
|
|
171
183
|
return id;
|
|
172
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* A member asks the org admins to allow hosts in the org's sandbox egress (`POST /orgs/:orgId/
|
|
187
|
+
* catalog-requests/network`, `catalog.request`; spec §5.3). The hosts arrive validated (exact,
|
|
188
|
+
* lowercased) and are deduped and sorted here. The deployment egress `none` ->
|
|
189
|
+
* `catalog.request_invalid` (only a super admin can turn sandbox network on); the org's own
|
|
190
|
+
* egress `none` -> `catalog.request_invalid`; {@link NETWORK_REQUESTS_PENDING_MAX} pending
|
|
191
|
+
* requests of the member already -> `catalog.request_invalid`; every host already
|
|
192
|
+
* allowed by the org's effective egress -> `catalog.request_invalid`; a conversation or agent not
|
|
193
|
+
* in the org -> `catalog.request_invalid`. A pending request of the same requester for the same
|
|
194
|
+
* host set is returned as is (no second request). Audits `catalog.requested`.
|
|
195
|
+
*/
|
|
196
|
+
async requestNetwork(orgId, actor, input, opts = {}) {
|
|
197
|
+
const id = await this.writeNetworkRequest(orgId, actor, input, opts);
|
|
198
|
+
return this.view(id);
|
|
199
|
+
}
|
|
200
|
+
async writeNetworkRequest(orgId, actor, input, opts) {
|
|
201
|
+
const t = this.db;
|
|
202
|
+
await this.orgLock.lockAndAuthorize(orgId, actor, "catalog.request");
|
|
203
|
+
const hosts = [...new Set(input.hosts.map((h) => h.toLowerCase()))].sort();
|
|
204
|
+
const deployment = this.orgSettings.deployment().sandboxEgress;
|
|
205
|
+
if (deployment.mode === "none")
|
|
206
|
+
throw deploymentEgressNone();
|
|
207
|
+
const settings = await readOrgSettings(t, orgId);
|
|
208
|
+
if (settings.sandboxEgress?.mode === "none") {
|
|
209
|
+
throw new ApiError("catalog.request_invalid", "The organization has sandbox network turned off; ask an admin to turn it on first.");
|
|
210
|
+
}
|
|
211
|
+
// the org's base list: the hosts a strict device reaches (the tiers only widen it)
|
|
212
|
+
const effective = baseSandboxEgress({ egress: deployment.mode, allowHosts: deployment.allowHosts }, settings.sandboxEgress);
|
|
213
|
+
if (hosts.every((h) => orgAllows(h, effective))) {
|
|
214
|
+
throw new ApiError("catalog.request_invalid", "This organization's sandbox network already allows these hosts; nothing to request");
|
|
215
|
+
}
|
|
216
|
+
if (input.conversationId !== undefined) {
|
|
217
|
+
const c = await t
|
|
218
|
+
.selectFrom("conversations")
|
|
219
|
+
.select("id")
|
|
220
|
+
.where("id", "=", input.conversationId)
|
|
221
|
+
.where("org_id", "=", orgId)
|
|
222
|
+
.executeTakeFirst();
|
|
223
|
+
if (!c) {
|
|
224
|
+
throw new ApiError("catalog.request_invalid", "The conversation is not in this organization");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (input.agentId !== undefined) {
|
|
228
|
+
const a = await t
|
|
229
|
+
.selectFrom("agents")
|
|
230
|
+
.select("id")
|
|
231
|
+
.where("id", "=", input.agentId)
|
|
232
|
+
.where("org_id", "=", orgId)
|
|
233
|
+
.executeTakeFirst();
|
|
234
|
+
if (!a) {
|
|
235
|
+
throw new ApiError("catalog.request_invalid", "The agent is not in this organization");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// The same requester asking for the same hosts again gets the pending request back (the org
|
|
239
|
+
// row lock above serializes concurrent asks)
|
|
240
|
+
const existing = await t
|
|
241
|
+
.selectFrom("catalog_requests")
|
|
242
|
+
.select("id")
|
|
243
|
+
.where("org_id", "=", orgId)
|
|
244
|
+
.where("kind", "=", "network_allowlist")
|
|
245
|
+
.where("requested_by", "=", actor)
|
|
246
|
+
.where("status", "=", "pending")
|
|
247
|
+
.where(sql `detail -> 'hosts' = ${JSON.stringify(hosts)}::jsonb`)
|
|
248
|
+
.executeTakeFirst();
|
|
249
|
+
if (existing)
|
|
250
|
+
return existing.id;
|
|
251
|
+
const pending = await t
|
|
252
|
+
.selectFrom("catalog_requests")
|
|
253
|
+
.select(sql `count(*)`.as("n"))
|
|
254
|
+
.where("org_id", "=", orgId)
|
|
255
|
+
.where("kind", "=", "network_allowlist")
|
|
256
|
+
.where("requested_by", "=", actor)
|
|
257
|
+
.where("status", "=", "pending")
|
|
258
|
+
.executeTakeFirstOrThrow();
|
|
259
|
+
if (Number(pending.n) >= NETWORK_REQUESTS_PENDING_MAX) {
|
|
260
|
+
throw new ApiError("catalog.request_invalid", "You have too many pending domain requests; wait for an admin to handle them.");
|
|
261
|
+
}
|
|
262
|
+
const detail = {
|
|
263
|
+
hosts,
|
|
264
|
+
conversationId: input.conversationId ?? null,
|
|
265
|
+
agentId: input.agentId ?? null,
|
|
266
|
+
};
|
|
267
|
+
const id = randomUUID();
|
|
268
|
+
await t
|
|
269
|
+
.insertInto("catalog_requests")
|
|
270
|
+
.values({
|
|
271
|
+
id,
|
|
272
|
+
org_id: orgId,
|
|
273
|
+
item_id: null,
|
|
274
|
+
kind: "network_allowlist",
|
|
275
|
+
// still written for the old NOT NULL / CHECK, never read
|
|
276
|
+
stage: "org",
|
|
277
|
+
detail: JSON.stringify(detail),
|
|
278
|
+
requested_by: actor,
|
|
279
|
+
note: input.note ?? "",
|
|
280
|
+
})
|
|
281
|
+
.execute();
|
|
282
|
+
await audit(t, {
|
|
283
|
+
actorAccountId: actor,
|
|
284
|
+
...(opts.actorTokenId !== undefined && { actorTokenId: opts.actorTokenId }),
|
|
285
|
+
orgId,
|
|
286
|
+
action: "catalog.requested",
|
|
287
|
+
target: id,
|
|
288
|
+
detail: { kind: "network_allowlist", hosts, ...opts.auditDetail },
|
|
289
|
+
});
|
|
290
|
+
return id;
|
|
291
|
+
}
|
|
173
292
|
/**
|
|
174
293
|
* An org admin asks for a marketplace entry to be imported (`POST /orgs/:orgId/
|
|
175
294
|
* marketplace-requests`, `catalog.decide`). The deployment setting `orgMarketplaceBrowse` off ->
|
|
@@ -254,10 +373,11 @@ let CatalogRequestsService = class CatalogRequestsService {
|
|
|
254
373
|
return id;
|
|
255
374
|
}
|
|
256
375
|
/**
|
|
257
|
-
* An org admin decides a member's `enable` or `connector_setup` request
|
|
258
|
-
* this org's request of those kinds -> `catalog.request_not_found`;
|
|
259
|
-
* `catalog.request_decided`. Approving
|
|
260
|
-
*
|
|
376
|
+
* An org admin decides a member's `enable`, `network_allowlist` or `connector_setup` request
|
|
377
|
+
* (`catalog.decide`). Not this org's request of those kinds -> `catalog.request_not_found`;
|
|
378
|
+
* decided -> `catalog.request_decided`. Approving a `network_allowlist` request merges its hosts
|
|
379
|
+
* into the org's `sandboxEgress` (`approveNetwork`). Approving an `enable` request switches the
|
|
380
|
+
* item on in the org as the deciding admin ({@link OrgCatalogService.setSwitch}: its errors, e.g. inputs the install
|
|
261
381
|
* needs, leave the request pending); the install itself resolves the request. A
|
|
262
382
|
* `connector_setup` request is approved only by binding its connectors (ruling P4-T6-6):
|
|
263
383
|
* approving it -> `catalog.request_invalid`. Rejecting stores the reason. Audits
|
|
@@ -268,12 +388,18 @@ let CatalogRequestsService = class CatalogRequestsService {
|
|
|
268
388
|
throw requestNotFound();
|
|
269
389
|
if (decision.approve) {
|
|
270
390
|
const itemId = await this.pendingOrgRequest(orgId, actor, requestId);
|
|
271
|
-
|
|
391
|
+
// a network request is approved inside the decision (writeDecision), under the row lock
|
|
392
|
+
if (itemId !== null) {
|
|
393
|
+
await this.orgCatalog.setSwitch(orgId, actor, itemId, { enabled: true }, opts.actorTokenId);
|
|
394
|
+
}
|
|
272
395
|
}
|
|
273
396
|
await this.writeDecision(requestId, actor, decision, opts, { orgId });
|
|
274
397
|
return this.view(requestId);
|
|
275
398
|
}
|
|
276
|
-
/**
|
|
399
|
+
/**
|
|
400
|
+
* The item of a pending `enable` request of the org, null for a `network_allowlist` one (the
|
|
401
|
+
* decider re-checked).
|
|
402
|
+
*/
|
|
277
403
|
async pendingOrgRequest(orgId, actor, requestId) {
|
|
278
404
|
await this.orgLock.lockAndAuthorize(orgId, actor, "catalog.decide");
|
|
279
405
|
const row = await this.db
|
|
@@ -290,6 +416,8 @@ let CatalogRequestsService = class CatalogRequestsService {
|
|
|
290
416
|
if (row.kind === "connector_setup") {
|
|
291
417
|
throw new ApiError("catalog.request_invalid", "A connector setup request is resolved by binding the agent's connectors; reject it to refuse");
|
|
292
418
|
}
|
|
419
|
+
if (row.kind === "network_allowlist")
|
|
420
|
+
return null;
|
|
293
421
|
if (row.item_id === null)
|
|
294
422
|
throw requestNotFound();
|
|
295
423
|
return row.item_id;
|
|
@@ -416,7 +544,7 @@ let CatalogRequestsService = class CatalogRequestsService {
|
|
|
416
544
|
await this.adminLock.lockAndAuthorize(actor);
|
|
417
545
|
let q = t
|
|
418
546
|
.selectFrom("catalog_requests")
|
|
419
|
-
.select(["id", "org_id", "item_id", "kind", "status", "decided_by"])
|
|
547
|
+
.select(["id", "org_id", "item_id", "kind", "status", "decided_by", "detail"])
|
|
420
548
|
.where("id", "=", requestId);
|
|
421
549
|
q =
|
|
422
550
|
"orgId" in side
|
|
@@ -448,6 +576,9 @@ let CatalogRequestsService = class CatalogRequestsService {
|
|
|
448
576
|
throw requestNotFound();
|
|
449
577
|
await this.scope.addOrg(actor, row.item_id, row.org_id, { ...opts, requestId });
|
|
450
578
|
}
|
|
579
|
+
if (decision.approve && row.kind === "network_allowlist") {
|
|
580
|
+
await this.approveNetwork(row.org_id, actor, requestId, row.detail, opts);
|
|
581
|
+
}
|
|
451
582
|
// An `import` request whose entry is in the catalog already: the org joins that item's list
|
|
452
583
|
const grantItem = "grantItem" in side ? side.grantItem : undefined;
|
|
453
584
|
if (decision.approve && row.kind === "import" && grantItem !== undefined) {
|
|
@@ -478,6 +609,39 @@ let CatalogRequestsService = class CatalogRequestsService {
|
|
|
478
609
|
},
|
|
479
610
|
});
|
|
480
611
|
}
|
|
612
|
+
/**
|
|
613
|
+
* Approves a `network_allowlist` request inside its decision's transaction: the hosts the org's
|
|
614
|
+
* effective egress does not allow yet are added to the org's `sandboxEgress.allowHosts` through
|
|
615
|
+
* {@link OrgSettingsService.update} (audited `org.settings_updated` with `requestId`). An org
|
|
616
|
+
* following the deployment default starts from the deployment's list (it keeps its own list from
|
|
617
|
+
* then on). The deployment egress `none`, or the org's `none` -> `catalog.request_invalid`; more
|
|
618
|
+
* than {@link ORG_SANDBOX_ALLOW_HOSTS_MAX} hosts -> `catalog.request_invalid`; decided with an API
|
|
619
|
+
* token -> `org.sandbox_egress_not_allowed` (only a person widens the network). Hosts all allowed
|
|
620
|
+
* already: nothing is written.
|
|
621
|
+
*/
|
|
622
|
+
async approveNetwork(orgId, actor, requestId, detail, opts) {
|
|
623
|
+
const hosts = detail?.hosts ?? [];
|
|
624
|
+
const deployment = this.orgSettings.deployment().sandboxEgress;
|
|
625
|
+
if (deployment.mode === "none")
|
|
626
|
+
throw deploymentEgressNone();
|
|
627
|
+
const current = (await readOrgSettings(this.db, orgId)).sandboxEgress;
|
|
628
|
+
if (current?.mode === "none") {
|
|
629
|
+
throw new ApiError("catalog.request_invalid", "The organization has sandbox network turned off; turn it on in the organization settings first");
|
|
630
|
+
}
|
|
631
|
+
// the org's base list: the hosts a strict device reaches (the tiers only widen it)
|
|
632
|
+
const effective = baseSandboxEgress({ egress: deployment.mode, allowHosts: deployment.allowHosts }, current);
|
|
633
|
+
const missing = hosts.filter((h) => !orgAllows(h, effective));
|
|
634
|
+
if (missing.length === 0)
|
|
635
|
+
return;
|
|
636
|
+
const base = current?.allowHosts ?? deployment.allowHosts;
|
|
637
|
+
const allowHosts = [...new Set([...base, ...missing])];
|
|
638
|
+
if (allowHosts.length > ORG_SANDBOX_ALLOW_HOSTS_MAX) {
|
|
639
|
+
throw new ApiError("catalog.request_invalid", `The organization's sandbox allowlist would exceed ${ORG_SANDBOX_ALLOW_HOSTS_MAX} hosts; remove some in the organization settings first`);
|
|
640
|
+
}
|
|
641
|
+
// approving widens the org's base list: only a person in a session does that, never an API
|
|
642
|
+
// token (sandbox dev tiers spec §7.2; the Platform tool has no decide operation at all)
|
|
643
|
+
await this.orgSettings.update(orgId, { sandboxEgress: { mode: "allowlist", allowHosts } }, actor, opts.actorTokenId, { requestId }, { onlyNarrows: opts.actorTokenId !== undefined });
|
|
644
|
+
}
|
|
481
645
|
/**
|
|
482
646
|
* The requester dismisses a decided request from the inbox (`POST /orgs/:orgId/
|
|
483
647
|
* catalog-requests/:id/dismiss`). Not the requester's decided request in this org ->
|
|
@@ -524,8 +688,8 @@ let CatalogRequestsService = class CatalogRequestsService {
|
|
|
524
688
|
}
|
|
525
689
|
/**
|
|
526
690
|
* The inbox: when the account is an org admin of the org (the role read live), the members'
|
|
527
|
-
* pending `enable` requests and the pending `connector_setup` requests,
|
|
528
|
-
* none.
|
|
691
|
+
* pending `enable` and `network_allowlist` requests and the pending `connector_setup` requests,
|
|
692
|
+
* newest first; otherwise none.
|
|
529
693
|
*/
|
|
530
694
|
async listForOrgAdmin(orgId, accountId) {
|
|
531
695
|
const m = await this.db
|
|
@@ -550,7 +714,7 @@ let CatalogRequestsService = class CatalogRequestsService {
|
|
|
550
714
|
const seen = await itemsSeenBy(this.db, orgId, ids);
|
|
551
715
|
return this.toViews(rows.filter((r) => r.kind === "connector_setup"
|
|
552
716
|
? r.agent_id !== null && r.agent_deleted_at === null
|
|
553
|
-
: r.item_id !== null && seen.has(r.item_id)));
|
|
717
|
+
: r.kind === "network_allowlist" || (r.item_id !== null && seen.has(r.item_id))));
|
|
554
718
|
}
|
|
555
719
|
/** The inbox: the account's decided requests in the org it has not dismissed, newest first. */
|
|
556
720
|
async listDecidedForRequester(orgId, accountId) {
|
|
@@ -644,6 +808,7 @@ let CatalogRequestsService = class CatalogRequestsService {
|
|
|
644
808
|
"r.decided_by",
|
|
645
809
|
"r.decided_at",
|
|
646
810
|
"r.staging_id",
|
|
811
|
+
"r.detail",
|
|
647
812
|
"r.created_at",
|
|
648
813
|
]));
|
|
649
814
|
}
|
|
@@ -654,6 +819,9 @@ __decorate([
|
|
|
654
819
|
__decorate([
|
|
655
820
|
Transactional()
|
|
656
821
|
], CatalogRequestsService.prototype, "writeItemRequest", null);
|
|
822
|
+
__decorate([
|
|
823
|
+
Transactional()
|
|
824
|
+
], CatalogRequestsService.prototype, "writeNetworkRequest", null);
|
|
657
825
|
__decorate([
|
|
658
826
|
Transactional()
|
|
659
827
|
], CatalogRequestsService.prototype, "writeImportRequest", null);
|
|
@@ -682,8 +850,9 @@ CatalogRequestsService = __decorate([
|
|
|
682
850
|
__param(5, Inject(CatalogService)),
|
|
683
851
|
__param(6, Inject(KernelConfigService)),
|
|
684
852
|
__param(7, Inject(PluginImport)),
|
|
685
|
-
__param(8, Inject(
|
|
686
|
-
__param(9, Inject(
|
|
853
|
+
__param(8, Inject(OrgSettingsService)),
|
|
854
|
+
__param(9, Inject(TransactionHost)),
|
|
855
|
+
__param(10, Inject(LockService))
|
|
687
856
|
], CatalogRequestsService);
|
|
688
857
|
export { CatalogRequestsService };
|
|
689
858
|
/** The view fields of a `connector_setup` request (ruling P4-T6-5); null for other kinds. */
|
|
@@ -721,6 +890,17 @@ function setupFields(r, templateNames) {
|
|
|
721
890
|
connectors,
|
|
722
891
|
};
|
|
723
892
|
}
|
|
893
|
+
/** The detail of a `network_allowlist` request; null for other kinds. */
|
|
894
|
+
function networkFields(r) {
|
|
895
|
+
if (r.kind !== "network_allowlist")
|
|
896
|
+
return null;
|
|
897
|
+
const d = (r.detail ?? {});
|
|
898
|
+
return {
|
|
899
|
+
hosts: Array.isArray(d.hosts) ? d.hosts.filter((h) => typeof h === "string") : [],
|
|
900
|
+
conversationId: typeof d.conversationId === "string" ? d.conversationId : null,
|
|
901
|
+
agentId: typeof d.agentId === "string" ? d.agentId : null,
|
|
902
|
+
};
|
|
903
|
+
}
|
|
724
904
|
function toView(r, templateNames) {
|
|
725
905
|
return {
|
|
726
906
|
id: r.id,
|
|
@@ -734,6 +914,7 @@ function toView(r, templateNames) {
|
|
|
734
914
|
? { marketplaceId: r.marketplace_id, name: r.entry_name, version: r.entry_version }
|
|
735
915
|
: null,
|
|
736
916
|
...setupFields(r, templateNames),
|
|
917
|
+
network: networkFields(r),
|
|
737
918
|
status: r.status,
|
|
738
919
|
note: r.note,
|
|
739
920
|
reason: r.reason,
|
|
@@ -35,5 +35,7 @@ export declare class ChannelCardActions {
|
|
|
35
35
|
private readonly deps;
|
|
36
36
|
constructor(deps: ChannelCardActionsDeps);
|
|
37
37
|
handle(connectionId: string, e: CardActionEvent): Promise<void>;
|
|
38
|
+
/** Whether the approval `id` of the org is a network ask. */
|
|
39
|
+
private isNetworkAsk;
|
|
38
40
|
}
|
|
39
41
|
export {};
|
package/dist/channels/actions.js
CHANGED
|
@@ -12,6 +12,8 @@ const REPLY_FOR = {
|
|
|
12
12
|
"question.already_answered": "alreadyHandled",
|
|
13
13
|
validation_failed: "invalidAction",
|
|
14
14
|
"approval.scope_not_allowed": "invalidAction",
|
|
15
|
+
// a network ask clicked by someone other than the device's owner
|
|
16
|
+
"approval.not_yours": "cannotHandle",
|
|
15
17
|
};
|
|
16
18
|
/**
|
|
17
19
|
* IM 卡片上的按钮操作(Review Focus 2):点击者按入站同样的规则映射到账号(未绑定 → 私聊回绑定卡片、群里只提示去私聊),
|
|
@@ -97,14 +99,17 @@ export class ChannelCardActions {
|
|
|
97
99
|
await reply("cannotHandle");
|
|
98
100
|
return;
|
|
99
101
|
}
|
|
102
|
+
// a network ask is the device owner's, decided by them in person (network ask-to-allow
|
|
103
|
+
// spec §5.2): the shared guest account never decides one, whoever clicks
|
|
104
|
+
if (a.cardKind === "approval" && (await this.isNetworkAsk(row.orgId, a.cardId))) {
|
|
105
|
+
await reply("cannotHandle");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
100
108
|
}
|
|
101
109
|
try {
|
|
102
110
|
if (a.cardKind === "approval" && (a.action === "approve" || a.action === "deny")) {
|
|
103
111
|
const scope = anonymous ? "once" : a.scope;
|
|
104
|
-
await this.deps.approvals.decide(row.orgId, a.cardId, who.accountId, {
|
|
105
|
-
decision: a.action,
|
|
106
|
-
...(scope !== undefined && { scope }),
|
|
107
|
-
});
|
|
112
|
+
await this.deps.approvals.decide(row.orgId, a.cardId, who.accountId, { decision: a.action, ...(scope !== undefined && { scope }) }, undefined, "im");
|
|
108
113
|
}
|
|
109
114
|
else if (a.cardKind === "question" && a.action === "answer") {
|
|
110
115
|
await this.deps.questions.answer(a.cardId, who.accountId, a.answer ?? {}, row.orgId);
|
|
@@ -125,4 +130,14 @@ export class ChannelCardActions {
|
|
|
125
130
|
// row is the child's, which has no delivery of its own
|
|
126
131
|
await this.deps.outbound.enqueue(card.conversation_id);
|
|
127
132
|
}
|
|
133
|
+
/** Whether the approval `id` of the org is a network ask. */
|
|
134
|
+
async isNetworkAsk(orgId, id) {
|
|
135
|
+
const r = await this.deps.db
|
|
136
|
+
.selectFrom("approvals")
|
|
137
|
+
.select("kind")
|
|
138
|
+
.where("id", "=", id)
|
|
139
|
+
.where("org_id", "=", orgId)
|
|
140
|
+
.executeTakeFirst();
|
|
141
|
+
return r?.kind === "network";
|
|
142
|
+
}
|
|
128
143
|
}
|
package/dist/channels/cards.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ChannelCard, type FormAnswer, type InputForm, type OutboundMessage } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import type { KernelSchemaDb } from "../db/kernel-schema.js";
|
|
3
|
-
import type { ApprovalsService, ApprovalView } from "../tools/approvals.service.js";
|
|
3
|
+
import type { ApprovalsService, ApprovalView, NetworkAskView } from "../tools/approvals.service.js";
|
|
4
4
|
import { type ToolInfo } from "../tools/info.js";
|
|
5
5
|
import type { ToolRegistry } from "../tools/registry.js";
|
|
6
6
|
import { type ChannelLocale } from "./phrases.js";
|
|
@@ -49,9 +49,23 @@ export interface CardDeps {
|
|
|
49
49
|
/** The tools' display metadata (title, key arguments) for a call without a summary. */
|
|
50
50
|
registry: Pick<ToolRegistry, "builtinSpecs" | "extensionToolSpecs">;
|
|
51
51
|
}
|
|
52
|
+
/** What a network ask's card shows instead of a tool call (network ask-to-allow spec §6.4, §6.5). */
|
|
53
|
+
export interface NetworkCardText {
|
|
54
|
+
title: string;
|
|
55
|
+
inputPreview: string;
|
|
56
|
+
scopeLabels: Partial<Record<"once" | "always", string>>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A network ask's card text: the title names the host (or the number of hosts); the preview lists
|
|
60
|
+
* the hosts, then the command clipped to {@link CARD_SUMMARY_MAX} and, for several hosts, that the
|
|
61
|
+
* decision applies to all of them (an IM card has no per-host choice). Model-controlled values
|
|
62
|
+
* are redacted by `redact` (the connection's secret values); the description stays on the web.
|
|
63
|
+
*/
|
|
64
|
+
export declare function networkCardText(detail: Pick<NetworkAskView, "hosts" | "command">, locale: ChannelLocale, redact?: (s: string) => string): NetworkCardText;
|
|
52
65
|
/**
|
|
53
66
|
* The approval card in the approval's current state; undefined when the approval does not exist.
|
|
54
|
-
* A role escalation offers only "once".
|
|
67
|
+
* A role escalation offers only "once". A network ask names its hosts and command and labels its
|
|
68
|
+
* scopes ({@link networkCardText}). `webUrl` = `http.publicUrl` + the conversation path.
|
|
55
69
|
*/
|
|
56
70
|
export declare function approvalCard(d: CardDeps, approvalId: string, base: string, locale: ChannelLocale, redact?: (s: string) => string): Promise<Extract<ChannelCard, {
|
|
57
71
|
kind: "approval";
|