@capybara-hq/kernel 0.1.0-rc.3 → 0.1.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/admin.controller.d.ts +9 -4
- package/dist/admin/admin.controller.js +13 -4
- package/dist/admin/extensions.controller.d.ts +8 -1
- package/dist/admin/extensions.controller.js +22 -2
- package/dist/agents/agent-lookup.d.ts +3 -0
- package/dist/agents/agent-lookup.js +10 -0
- package/dist/agents/agent-profile.service.d.ts +29 -0
- package/dist/agents/agent-profile.service.js +117 -0
- package/dist/agents/agents.controller.d.ts +93 -3
- package/dist/agents/agents.controller.js +114 -4
- package/dist/agents/agents.module.js +6 -2
- package/dist/agents/agents.service.d.ts +13 -5
- package/dist/agents/agents.service.js +30 -7
- package/dist/agents/definition.d.ts +4 -0
- package/dist/agents/definition.js +10 -0
- package/dist/agents/instruction-files.d.ts +60 -0
- package/dist/agents/instruction-files.js +111 -0
- package/dist/agents/models.controller.d.ts +9 -3
- package/dist/agents/models.controller.js +19 -4
- package/dist/agents/profile.d.ts +96 -0
- package/dist/agents/profile.js +51 -0
- package/dist/agents/readiness.controller.d.ts +26 -0
- package/dist/agents/readiness.controller.js +56 -0
- package/dist/agents/templates.js +1 -1
- package/dist/background/background.controller.d.ts +38 -0
- package/dist/background/background.controller.js +87 -0
- package/dist/background/background.module.d.ts +10 -0
- package/dist/background/background.module.js +32 -0
- package/dist/background/finish.d.ts +43 -0
- package/dist/background/finish.js +94 -0
- package/dist/background/launch-lock.d.ts +14 -0
- package/dist/background/launch-lock.js +39 -0
- package/dist/background/launch.d.ts +36 -0
- package/dist/background/launch.js +70 -0
- package/dist/background/poll.job.d.ts +53 -0
- package/dist/background/poll.job.js +182 -0
- package/dist/background/render.d.ts +23 -0
- package/dist/background/render.js +48 -0
- package/dist/background/session.d.ts +37 -0
- package/dist/background/session.js +66 -0
- package/dist/background/status.d.ts +35 -0
- package/dist/background/status.js +57 -0
- package/dist/background/stop.d.ts +62 -0
- package/dist/background/stop.js +225 -0
- package/dist/background/store.d.ts +150 -0
- package/dist/background/store.js +351 -0
- package/dist/background/store.module.d.ts +6 -0
- package/dist/background/store.module.js +22 -0
- package/dist/background/tasks.service.d.ts +67 -0
- package/dist/background/tasks.service.js +186 -0
- package/dist/background/wake-rules.d.ts +57 -0
- package/dist/background/wake-rules.js +83 -0
- package/dist/background/wake.d.ts +28 -0
- package/dist/background/wake.js +57 -0
- package/dist/catalog/claude/agents.js +2 -0
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/plugin-import.js +1 -1
- package/dist/catalog/plugin-upgrade.js +1 -1
- package/dist/channels/cards.js +1 -1
- package/dist/channels/connections.service.d.ts +16 -1
- package/dist/channels/connections.service.js +38 -4
- package/dist/channels/threads.d.ts +5 -0
- package/dist/channels/threads.js +8 -0
- package/dist/config/schema.d.ts +18 -0
- package/dist/config/schema.js +102 -0
- package/dist/conversations/access.js +2 -0
- package/dist/conversations/attachments.service.d.ts +30 -0
- package/dist/conversations/attachments.service.js +130 -5
- package/dist/conversations/conversations.controller.d.ts +36 -5
- package/dist/conversations/conversations.controller.js +62 -12
- package/dist/conversations/conversations.module.js +3 -0
- package/dist/conversations/conversations.service.d.ts +44 -2
- package/dist/conversations/conversations.service.js +140 -12
- package/dist/conversations/event-log.d.ts +1 -1
- package/dist/conversations/event-log.js +10 -1
- package/dist/conversations/events.d.ts +58 -0
- package/dist/conversations/events.js +57 -2
- package/dist/conversations/queue.d.ts +3 -1
- package/dist/conversations/queue.js +13 -2
- package/dist/conversations/queue.service.js +3 -1
- package/dist/conversations/titles.d.ts +7 -0
- package/dist/conversations/titles.js +15 -0
- package/dist/db/kernel-schema.d.ts +58 -2
- package/dist/db/pool.d.ts +5 -0
- package/dist/db/pool.js +3 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +8 -0
- package/dist/evals/run-replay.d.ts +5 -2
- package/dist/evals/run-replay.js +30 -7
- package/dist/evals/step-replay.js +12 -6
- package/dist/evals/trajectory.js +1 -1
- package/dist/extensions/admin-ops.js +2 -0
- package/dist/extensions/admin-view.d.ts +3 -1
- package/dist/extensions/admin-view.js +10 -2
- package/dist/extensions/blobs.js +1 -0
- package/dist/extensions/config-issues.d.ts +13 -1
- package/dist/extensions/config-issues.js +22 -5
- package/dist/extensions/config-json-schema.d.ts +43 -7
- package/dist/extensions/config-json-schema.js +124 -58
- package/dist/extensions/deployment-config-store.d.ts +6 -0
- package/dist/extensions/deployment-config.d.ts +50 -3
- package/dist/extensions/deployment-config.js +125 -28
- package/dist/extensions/display-name.d.ts +22 -0
- package/dist/extensions/display-name.js +77 -0
- package/dist/extensions/extensions.module.js +6 -0
- package/dist/extensions/host.d.ts +26 -1
- package/dist/extensions/host.js +82 -9
- package/dist/extensions/org-config.controller.d.ts +11 -3
- package/dist/extensions/org-config.controller.js +14 -6
- package/dist/extensions/org-config.d.ts +6 -0
- package/dist/extensions/org-config.js +12 -13
- package/dist/extensions/package.d.ts +14 -1
- package/dist/extensions/package.js +51 -2
- package/dist/extensions/permission-view.d.ts +3 -0
- package/dist/extensions/point-defaults.controller.d.ts +34 -0
- package/dist/extensions/point-defaults.controller.js +71 -0
- package/dist/extensions/point-defaults.d.ts +64 -0
- package/dist/extensions/point-defaults.js +206 -0
- package/dist/extensions/registry.d.ts +1 -1
- package/dist/extensions/registry.js +2 -0
- package/dist/http/api.module.js +8 -0
- package/dist/http/nest-logger.d.ts +6 -5
- package/dist/http/nest-logger.js +24 -10
- package/dist/identity/auth.controller.js +9 -5
- package/dist/identity/cookies.d.ts +20 -5
- package/dist/identity/cookies.js +44 -6
- package/dist/identity/me.controller.d.ts +5 -1
- package/dist/identity/me.controller.js +12 -3
- package/dist/identity/sessions.d.ts +12 -0
- package/dist/identity/sessions.js +45 -0
- package/dist/index.d.ts +1 -1
- package/dist/infra/config/kernel-config.service.js +6 -0
- package/dist/infra/database/database.module.js +1 -0
- package/dist/infra/events/kernel-events.d.ts +8 -0
- package/dist/infra/lifecycle/process-guards.js +4 -0
- package/dist/infra/logging/logging.module.d.ts +4 -2
- package/dist/infra/logging/logging.module.js +24 -6
- package/dist/infra/queue/redis-connection.d.ts +6 -0
- package/dist/infra/queue/redis-connection.js +7 -0
- package/dist/infra/redis/redis.module.js +6 -2
- package/dist/infra/tokens.d.ts +6 -1
- package/dist/infra/tokens.js +6 -1
- package/dist/log.d.ts +13 -1
- package/dist/log.js +93 -17
- package/dist/models/purposes.d.ts +23 -0
- package/dist/models/purposes.js +31 -10
- package/dist/orgs/offboarding.d.ts +3 -1
- package/dist/orgs/offboarding.js +26 -16
- package/dist/orgs/settings.service.d.ts +5 -0
- package/dist/orgs/settings.service.js +13 -2
- package/dist/roles/worker.module.js +6 -0
- package/dist/runtime/context/build.d.ts +9 -7
- package/dist/runtime/context/build.js +27 -19
- package/dist/runtime/context/claude-tools.d.ts +2 -1
- package/dist/runtime/context/claude-tools.js +5 -4
- package/dist/runtime/context/compaction.js +4 -0
- package/dist/runtime/langgraph-loop.js +3 -2
- package/dist/runtime/loop.d.ts +4 -3
- package/dist/runtime/maintenance.d.ts +4 -2
- package/dist/runtime/maintenance.js +17 -5
- package/dist/runtime/model-call.d.ts +17 -1
- package/dist/runtime/model-call.js +3 -2
- package/dist/runtime/providers.d.ts +6 -0
- package/dist/runtime/providers.js +8 -0
- package/dist/runtime/run-queue.d.ts +25 -2
- package/dist/runtime/run-queue.js +129 -33
- package/dist/runtime/run-worker.js +16 -3
- package/dist/runtime/runtime.module.js +8 -3
- package/dist/runtime/step-log.d.ts +6 -0
- package/dist/runtime/step-log.js +16 -2
- package/dist/runtime/subagents/definition.js +3 -1
- package/dist/runtime/usage-report.d.ts +2 -1
- package/dist/runtime/usage-report.js +2 -1
- package/dist/runtime/waits.d.ts +8 -2
- package/dist/runtime/waits.js +37 -11
- package/dist/sandbox/sandbox.module.js +6 -1
- package/dist/sandbox/sandbox.service.d.ts +61 -3
- package/dist/sandbox/sandbox.service.js +141 -7
- package/dist/tools/approvals.service.js +2 -0
- package/dist/tools/builtin-names.d.ts +20 -2
- package/dist/tools/builtin-names.js +29 -2
- package/dist/tools/builtins/builtin-tools.d.ts +15 -1
- package/dist/tools/builtins/builtin-tools.js +34 -2
- package/dist/tools/builtins/sandbox/bash.d.ts +1 -1
- package/dist/tools/builtins/sandbox/bash.js +230 -3
- package/dist/tools/builtins/sandbox/common.d.ts +27 -4
- package/dist/tools/builtins/sandbox/common.js +7 -3
- package/dist/tools/builtins/sandbox/deliver.js +1 -1
- package/dist/tools/builtins/sandbox/index.d.ts +2 -0
- package/dist/tools/builtins/sandbox/index.js +6 -0
- package/dist/tools/builtins/sandbox/task-output.d.ts +63 -0
- package/dist/tools/builtins/sandbox/task-output.js +304 -0
- package/dist/tools/builtins/sandbox/task-stop.d.ts +10 -0
- package/dist/tools/builtins/sandbox/task-stop.js +112 -0
- package/dist/tools/builtins/web/errors.d.ts +8 -0
- package/dist/tools/builtins/web/errors.js +53 -0
- package/dist/tools/builtins/web/extract.d.ts +62 -0
- package/dist/tools/builtins/web/extract.js +267 -0
- package/dist/tools/builtins/web/fetch.d.ts +55 -0
- package/dist/tools/builtins/web/fetch.js +174 -0
- package/dist/tools/builtins/web/index.d.ts +13 -0
- package/dist/tools/builtins/web/index.js +12 -0
- package/dist/tools/builtins/web/page-cache.d.ts +25 -0
- package/dist/tools/builtins/web/page-cache.js +77 -0
- package/dist/tools/builtins/web/search.d.ts +25 -0
- package/dist/tools/builtins/web/search.js +209 -0
- package/dist/tools/builtins/web/url.d.ts +17 -0
- package/dist/tools/builtins/web/url.js +43 -0
- package/dist/tools/oauth/flow-paths.d.ts +1 -1
- package/dist/tools/oauth/flow-paths.js +1 -1
- package/dist/tools/pipeline.js +15 -0
- package/dist/tools/registry.d.ts +22 -3
- package/dist/tools/registry.js +26 -11
- package/dist/tools/tools.module.js +12 -0
- package/dist/tools/validate.js +1 -1
- package/package.json +3 -3
- package/src/migrations/0064_account_last_org.sql +7 -0
- package/src/migrations/0065_conversation_archive.sql +8 -0
- package/src/migrations/0066_extension_display_name.sql +4 -0
- package/src/migrations/0067_agent_profiles.sql +20 -0
- package/src/migrations/0068_agent_avatar_uploads.sql +7 -0
- package/src/migrations/0069_attachments_agent_idx.sql +3 -0
- package/src/migrations/0070_background_tasks.sql +37 -0
- package/src/migrations/0071_background_tasks_reserve.sql +6 -0
- package/src/migrations/0072_background_tasks_stopping.sql +5 -0
- package/src/migrations/0073_org_extension_point_defaults.sql +11 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { Controller, Get, HttpCode, Inject, Put } from "@nestjs/common";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { CurrentPrincipal, requireUser } from "../identity/principal.js";
|
|
13
|
+
import { createZodDto } from "../infra/validation/create-zod-dto.js";
|
|
14
|
+
import { ZBody, ZParam } from "../infra/validation/zod-params.js";
|
|
15
|
+
import { CurrentOrg, OrgAction } from "../orgs/org.guard.js";
|
|
16
|
+
import { ExtensionPointDefaults } from "./point-defaults.js";
|
|
17
|
+
const PointParams = z.object({ point: z.string().min(1).max(100) });
|
|
18
|
+
const DefaultBody = z.object({ extension: z.string().min(1).max(214) }).strict();
|
|
19
|
+
class ExtensionPointParamsDto extends createZodDto(PointParams, "ExtensionPointParams") {
|
|
20
|
+
}
|
|
21
|
+
class ExtensionPointDefaultBodyDto extends createZodDto(DefaultBody, "ExtensionPointDefaultBody") {
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The org default provider of `org-default` extension points (web tools spec §6):
|
|
25
|
+
* - `GET /api/v1/orgs/:orgId/extension-points` (`org.extensions.manage`) → `{ points }`: each
|
|
26
|
+
* point with its candidate providers, the stored default and the effective provider;
|
|
27
|
+
* - `PUT /api/v1/orgs/:orgId/extension-points/:point/default` `{ extension }`
|
|
28
|
+
* (`org.extensions.manage`) → 204;
|
|
29
|
+
* - `GET /api/v1/orgs/:orgId/extension-points/available` (`agents.use`) → `{ points }`: whether
|
|
30
|
+
* each point has a provider now, without extension names (for the agent form).
|
|
31
|
+
*/
|
|
32
|
+
let ExtensionPointDefaultsController = class ExtensionPointDefaultsController {
|
|
33
|
+
points;
|
|
34
|
+
constructor(points) {
|
|
35
|
+
this.points = points;
|
|
36
|
+
}
|
|
37
|
+
async list(org) {
|
|
38
|
+
return { points: await this.points.list(org.orgId) };
|
|
39
|
+
}
|
|
40
|
+
async available(org) {
|
|
41
|
+
return { points: await this.points.available(org.orgId) };
|
|
42
|
+
}
|
|
43
|
+
async setDefault(principal, org, { point }, body) {
|
|
44
|
+
const p = requireUser(principal);
|
|
45
|
+
await this.points.setDefault(org.orgId, point, body.extension, p.account.id);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
__decorate([
|
|
49
|
+
Get(),
|
|
50
|
+
OrgAction("org.extensions.manage"),
|
|
51
|
+
__param(0, CurrentOrg())
|
|
52
|
+
], ExtensionPointDefaultsController.prototype, "list", null);
|
|
53
|
+
__decorate([
|
|
54
|
+
Get("available"),
|
|
55
|
+
OrgAction("agents.use"),
|
|
56
|
+
__param(0, CurrentOrg())
|
|
57
|
+
], ExtensionPointDefaultsController.prototype, "available", null);
|
|
58
|
+
__decorate([
|
|
59
|
+
Put(":point/default"),
|
|
60
|
+
HttpCode(204),
|
|
61
|
+
OrgAction("org.extensions.manage"),
|
|
62
|
+
__param(0, CurrentPrincipal()),
|
|
63
|
+
__param(1, CurrentOrg()),
|
|
64
|
+
__param(2, ZParam(ExtensionPointParamsDto)),
|
|
65
|
+
__param(3, ZBody(ExtensionPointDefaultBodyDto))
|
|
66
|
+
], ExtensionPointDefaultsController.prototype, "setDefault", null);
|
|
67
|
+
ExtensionPointDefaultsController = __decorate([
|
|
68
|
+
Controller("api/v1/orgs/:orgId/extension-points"),
|
|
69
|
+
__param(0, Inject(ExtensionPointDefaults))
|
|
70
|
+
], ExtensionPointDefaultsController);
|
|
71
|
+
export { ExtensionPointDefaultsController };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
2
|
+
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
3
|
+
import type { KernelLogger } from "../log.js";
|
|
4
|
+
import { OrgExtensionConfigs } from "./org-config.js";
|
|
5
|
+
import { ExtensionRegistry } from "./registry.js";
|
|
6
|
+
/** The outcome of {@link ExtensionPointDefaults.resolve}. */
|
|
7
|
+
export type PointResolution<T> = {
|
|
8
|
+
ok: true;
|
|
9
|
+
extension: string;
|
|
10
|
+
impl: T;
|
|
11
|
+
orgConfig: unknown;
|
|
12
|
+
} | {
|
|
13
|
+
ok: false;
|
|
14
|
+
reason: "none" | "ambiguous";
|
|
15
|
+
};
|
|
16
|
+
/** One `org-default` extension point of an org (the admin view). */
|
|
17
|
+
export interface OrgPointDefaultView {
|
|
18
|
+
point: string;
|
|
19
|
+
/** The extensions that could serve the org now, by name. */
|
|
20
|
+
candidates: string[];
|
|
21
|
+
/** The stored default, also when it is not a candidate now. */
|
|
22
|
+
default: string | null;
|
|
23
|
+
/** The extension a call would use now; null when there is none or the choice is ambiguous. */
|
|
24
|
+
effective: string | null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The org default provider of an `org-default` extension point (web tools spec §6). Per call,
|
|
28
|
+
* never cached in process: the candidates are the providers loaded on this replica that are
|
|
29
|
+
* enabled in the org, whose org config is valid and whose `configured()` (when present) does not
|
|
30
|
+
* return false. One candidate is used as is; several need the org's stored default
|
|
31
|
+
* (`capybara.org_extension_point_defaults`), else the choice is ambiguous. A stored default that
|
|
32
|
+
* is not a candidate (disabled, uninstalled, unconfigured) stays and counts again once it is one.
|
|
33
|
+
*/
|
|
34
|
+
export declare class ExtensionPointDefaults {
|
|
35
|
+
private readonly kdb;
|
|
36
|
+
readonly txHost: TransactionHost;
|
|
37
|
+
private readonly registry;
|
|
38
|
+
private readonly orgConfigs;
|
|
39
|
+
private readonly log;
|
|
40
|
+
constructor(kdb: KernelDb, txHost: TransactionHost, registry: Pick<ExtensionRegistry, "list">, orgConfigs: Pick<OrgExtensionConfigs, "get">, log: KernelLogger);
|
|
41
|
+
/**
|
|
42
|
+
* The provider of `point` (the name without the major, e.g. `web-search`) for `orgId`, with the
|
|
43
|
+
* org's decrypted config to pass back to it. Throws when the point has no `selection`.
|
|
44
|
+
*/
|
|
45
|
+
resolve<T>(orgId: string, point: string): Promise<PointResolution<T>>;
|
|
46
|
+
/** Every `org-default` point with its candidates, stored default and effective provider. */
|
|
47
|
+
list(orgId: string): Promise<OrgPointDefaultView[]>;
|
|
48
|
+
/** Whether each `org-default` point has a provider for the org now (no extension names). */
|
|
49
|
+
available(orgId: string): Promise<{
|
|
50
|
+
point: string;
|
|
51
|
+
available: boolean;
|
|
52
|
+
}[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Stores `extension` as the org's default provider of `point`. 400 `validation_failed` unless
|
|
55
|
+
* the point is an `org-default` one and the extension provides it (loaded here) and is enabled
|
|
56
|
+
* in the org. Audited as `extension_point.default_set`.
|
|
57
|
+
*/
|
|
58
|
+
setDefault(orgId: string, point: string, extension: string, actorId: string): Promise<void>;
|
|
59
|
+
/** The candidates of `point` in `orgId`, by extension name. */
|
|
60
|
+
private candidates;
|
|
61
|
+
private choose;
|
|
62
|
+
/** The org's stored defaults, point → extension. */
|
|
63
|
+
private storedDefaults;
|
|
64
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { EXTENSION_POINTS } from "@capybara-hq/plugin-sdk";
|
|
11
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
12
|
+
import { ApiError } from "../errors.js";
|
|
13
|
+
import { audit } from "../identity/audit.js";
|
|
14
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
15
|
+
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
16
|
+
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
17
|
+
import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
18
|
+
import { enabledExtensionsQuery, isExtensionEnabled } from "./enabled.js";
|
|
19
|
+
import { OrgExtensionConfigs } from "./org-config.js";
|
|
20
|
+
import { ExtensionRegistry } from "./registry.js";
|
|
21
|
+
/** The extension points whose provider each org chooses (`selection: "org-default"`). */
|
|
22
|
+
function selectionPoints() {
|
|
23
|
+
return Object.entries(EXTENSION_POINTS)
|
|
24
|
+
.filter(([, info]) => info.selection === "org-default")
|
|
25
|
+
.map(([name]) => name);
|
|
26
|
+
}
|
|
27
|
+
const hasSelection = (point) => EXTENSION_POINTS[point]?.selection === "org-default";
|
|
28
|
+
/**
|
|
29
|
+
* The org default provider of an `org-default` extension point (web tools spec §6). Per call,
|
|
30
|
+
* never cached in process: the candidates are the providers loaded on this replica that are
|
|
31
|
+
* enabled in the org, whose org config is valid and whose `configured()` (when present) does not
|
|
32
|
+
* return false. One candidate is used as is; several need the org's stored default
|
|
33
|
+
* (`capybara.org_extension_point_defaults`), else the choice is ambiguous. A stored default that
|
|
34
|
+
* is not a candidate (disabled, uninstalled, unconfigured) stays and counts again once it is one.
|
|
35
|
+
*/
|
|
36
|
+
let ExtensionPointDefaults = class ExtensionPointDefaults {
|
|
37
|
+
kdb;
|
|
38
|
+
txHost;
|
|
39
|
+
registry;
|
|
40
|
+
orgConfigs;
|
|
41
|
+
log;
|
|
42
|
+
constructor(kdb, txHost, registry, orgConfigs, log) {
|
|
43
|
+
this.kdb = kdb;
|
|
44
|
+
this.txHost = txHost;
|
|
45
|
+
this.registry = registry;
|
|
46
|
+
this.orgConfigs = orgConfigs;
|
|
47
|
+
this.log = log;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The provider of `point` (the name without the major, e.g. `web-search`) for `orgId`, with the
|
|
51
|
+
* org's decrypted config to pass back to it. Throws when the point has no `selection`.
|
|
52
|
+
*/
|
|
53
|
+
async resolve(orgId, point) {
|
|
54
|
+
if (!hasSelection(point)) {
|
|
55
|
+
throw new Error(`extension point ${point} has no org-default selection`);
|
|
56
|
+
}
|
|
57
|
+
const candidates = await this.candidates(orgId, point);
|
|
58
|
+
const chosen = await this.choose(orgId, point, candidates);
|
|
59
|
+
if (!chosen.ok)
|
|
60
|
+
return chosen;
|
|
61
|
+
const c = chosen.candidate;
|
|
62
|
+
return { ok: true, extension: c.extension, impl: c.impl, orgConfig: c.orgConfig };
|
|
63
|
+
}
|
|
64
|
+
/** Every `org-default` point with its candidates, stored default and effective provider. */
|
|
65
|
+
async list(orgId) {
|
|
66
|
+
const stored = await this.storedDefaults(orgId);
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const point of selectionPoints()) {
|
|
69
|
+
const candidates = await this.candidates(orgId, point);
|
|
70
|
+
const chosen = pick(candidates, stored.get(point));
|
|
71
|
+
out.push({
|
|
72
|
+
point,
|
|
73
|
+
candidates: candidates.map((c) => c.extension),
|
|
74
|
+
default: stored.get(point) ?? null,
|
|
75
|
+
effective: chosen.ok ? chosen.candidate.extension : null,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
/** Whether each `org-default` point has a provider for the org now (no extension names). */
|
|
81
|
+
async available(orgId) {
|
|
82
|
+
const out = [];
|
|
83
|
+
for (const point of selectionPoints()) {
|
|
84
|
+
out.push({ point, available: (await this.resolve(orgId, point)).ok });
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Stores `extension` as the org's default provider of `point`. 400 `validation_failed` unless
|
|
90
|
+
* the point is an `org-default` one and the extension provides it (loaded here) and is enabled
|
|
91
|
+
* in the org. Audited as `extension_point.default_set`.
|
|
92
|
+
*/
|
|
93
|
+
async setDefault(orgId, point, extension, actorId) {
|
|
94
|
+
const db = this.kdb.db;
|
|
95
|
+
if (!hasSelection(point)) {
|
|
96
|
+
throw new ApiError("validation_failed", `${point} is not an extension point with an organization default`);
|
|
97
|
+
}
|
|
98
|
+
const provides = this.registry.list(point).some((e) => e.plugin === extension);
|
|
99
|
+
if (!provides || !(await isExtensionEnabled(db, orgId, extension))) {
|
|
100
|
+
throw new ApiError("validation_failed", `Extension ${extension} does not provide ${point} or is not enabled in this organization`);
|
|
101
|
+
}
|
|
102
|
+
await db
|
|
103
|
+
.insertInto("org_extension_point_defaults")
|
|
104
|
+
.values({ org_id: orgId, point, extension, updated_by: actorId })
|
|
105
|
+
.onConflict((oc) => oc.columns(["org_id", "point"]).doUpdateSet({
|
|
106
|
+
extension,
|
|
107
|
+
updated_by: actorId,
|
|
108
|
+
updated_at: new Date(),
|
|
109
|
+
}))
|
|
110
|
+
.execute();
|
|
111
|
+
await audit(db, {
|
|
112
|
+
actorAccountId: actorId,
|
|
113
|
+
orgId,
|
|
114
|
+
action: "extension_point.default_set",
|
|
115
|
+
target: point,
|
|
116
|
+
detail: { point, extension },
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/** The candidates of `point` in `orgId`, by extension name. */
|
|
120
|
+
async candidates(orgId, point) {
|
|
121
|
+
const db = this.kdb.db;
|
|
122
|
+
const enabled = new Set((await db
|
|
123
|
+
.selectFrom(enabledExtensionsQuery(db, orgId).as("en"))
|
|
124
|
+
.select("en.plugin")
|
|
125
|
+
.execute()).map((r) => r.plugin));
|
|
126
|
+
const providers = new Map();
|
|
127
|
+
for (const e of this.registry.list(point)) {
|
|
128
|
+
if (enabled.has(e.plugin) && !providers.has(e.plugin))
|
|
129
|
+
providers.set(e.plugin, e.impl);
|
|
130
|
+
}
|
|
131
|
+
const out = [];
|
|
132
|
+
for (const [extension, impl] of providers) {
|
|
133
|
+
let cfg;
|
|
134
|
+
try {
|
|
135
|
+
cfg = await this.orgConfigs.get(orgId, extension);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
this.log.warn("org default provider: provider excluded, its org config is invalid", {
|
|
139
|
+
orgId,
|
|
140
|
+
point,
|
|
141
|
+
extension,
|
|
142
|
+
err,
|
|
143
|
+
});
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (!cfg?.enabled)
|
|
147
|
+
continue;
|
|
148
|
+
const configured = impl.configured;
|
|
149
|
+
if (typeof configured === "function") {
|
|
150
|
+
let ok;
|
|
151
|
+
try {
|
|
152
|
+
ok = configured.call(impl, cfg.config) !== false;
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
this.log.warn("org default provider: provider excluded, configured() threw", {
|
|
156
|
+
orgId,
|
|
157
|
+
point,
|
|
158
|
+
extension,
|
|
159
|
+
err,
|
|
160
|
+
});
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (!ok)
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
out.push({ extension, impl, orgConfig: cfg.config });
|
|
167
|
+
}
|
|
168
|
+
return out.sort((a, b) => (a.extension < b.extension ? -1 : a.extension > b.extension ? 1 : 0));
|
|
169
|
+
}
|
|
170
|
+
async choose(orgId, point, candidates) {
|
|
171
|
+
if (candidates.length < 2)
|
|
172
|
+
return pick(candidates, undefined);
|
|
173
|
+
return pick(candidates, (await this.storedDefaults(orgId)).get(point));
|
|
174
|
+
}
|
|
175
|
+
/** The org's stored defaults, point → extension. */
|
|
176
|
+
async storedDefaults(orgId) {
|
|
177
|
+
const rows = await this.kdb.db
|
|
178
|
+
.selectFrom("org_extension_point_defaults")
|
|
179
|
+
.select(["point", "extension"])
|
|
180
|
+
.where("org_id", "=", orgId)
|
|
181
|
+
.execute();
|
|
182
|
+
return new Map(rows.map((r) => [r.point, r.extension]));
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
__decorate([
|
|
186
|
+
Transactional()
|
|
187
|
+
], ExtensionPointDefaults.prototype, "setDefault", null);
|
|
188
|
+
ExtensionPointDefaults = __decorate([
|
|
189
|
+
Injectable(),
|
|
190
|
+
__param(0, Inject(KernelDb)),
|
|
191
|
+
__param(1, Inject(TransactionHost)),
|
|
192
|
+
__param(2, Inject(ExtensionRegistry)),
|
|
193
|
+
__param(3, Inject(OrgExtensionConfigs)),
|
|
194
|
+
__param(4, Inject(KERNEL_LOGGER))
|
|
195
|
+
], ExtensionPointDefaults);
|
|
196
|
+
export { ExtensionPointDefaults };
|
|
197
|
+
/** Rules 2-4 of spec §6: one candidate → it; several → the stored default if a candidate. */
|
|
198
|
+
function pick(candidates, stored) {
|
|
199
|
+
const [only] = candidates;
|
|
200
|
+
if (only === undefined)
|
|
201
|
+
return { ok: false, reason: "none" };
|
|
202
|
+
if (candidates.length === 1)
|
|
203
|
+
return { ok: true, candidate: only };
|
|
204
|
+
const chosen = candidates.find((c) => c.extension === stored);
|
|
205
|
+
return chosen ? { ok: true, candidate: chosen } : { ok: false, reason: "ambiguous" };
|
|
206
|
+
}
|
|
@@ -11,7 +11,7 @@ export interface ExtensionPointEntry {
|
|
|
11
11
|
version: string;
|
|
12
12
|
/** 部署配置摘要,见 `deploymentConfigHash`。 */
|
|
13
13
|
configHash: string;
|
|
14
|
-
/**
|
|
14
|
+
/** The provider instance's in-flight tracker (a tracked point's `impl`: connector, sandbox, model-provider, web-search, web-fetch, is already a proxy counted in it). */
|
|
15
15
|
inflight: InflightTracker;
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
@@ -12,6 +12,8 @@ const TRACKED_EXTENSION_POINTS = {
|
|
|
12
12
|
connector: { sessions: ["open"] },
|
|
13
13
|
sandbox: {},
|
|
14
14
|
"model-provider": {},
|
|
15
|
+
"web-search": {},
|
|
16
|
+
"web-fetch": {},
|
|
15
17
|
};
|
|
16
18
|
/**
|
|
17
19
|
* The extension instances in effect in this process (by extension name, one each). One provider
|
package/dist/http/api.module.js
CHANGED
|
@@ -13,7 +13,10 @@ import { SetupController } from "../admin/setup.controller.js";
|
|
|
13
13
|
import { AgentsController } from "../agents/agents.controller.js";
|
|
14
14
|
import { AgentsModule } from "../agents/agents.module.js";
|
|
15
15
|
import { ModelsController } from "../agents/models.controller.js";
|
|
16
|
+
import { ReadinessController } from "../agents/readiness.controller.js";
|
|
16
17
|
import { AgentTemplatesController } from "../agents/templates.controller.js";
|
|
18
|
+
import { BackgroundController } from "../background/background.controller.js";
|
|
19
|
+
import { BackgroundModule } from "../background/background.module.js";
|
|
17
20
|
import { AdminCatalogController } from "../catalog/admin-catalog.controller.js";
|
|
18
21
|
import { CatalogModule } from "../catalog/catalog.module.js";
|
|
19
22
|
import { MarketplacesController } from "../catalog/marketplaces.controller.js";
|
|
@@ -29,6 +32,7 @@ import { EvalSetsController } from "../evals/sets.controller.js";
|
|
|
29
32
|
import { TrajectoryController } from "../evals/trajectory.controller.js";
|
|
30
33
|
import { ExtensionsModule } from "../extensions/extensions.module.js";
|
|
31
34
|
import { OrgExtensionConfigsController } from "../extensions/org-config.controller.js";
|
|
35
|
+
import { ExtensionPointDefaultsController } from "../extensions/point-defaults.controller.js";
|
|
32
36
|
import { FilesModule } from "../files/files.module.js";
|
|
33
37
|
import { FilePreviewersController } from "../files/previewers.controller.js";
|
|
34
38
|
import { AuthController } from "../identity/auth.controller.js";
|
|
@@ -75,7 +79,9 @@ const CONTROLLERS = [
|
|
|
75
79
|
AgentsController,
|
|
76
80
|
AgentTemplatesController,
|
|
77
81
|
ModelsController,
|
|
82
|
+
ReadinessController,
|
|
78
83
|
ConversationsController,
|
|
84
|
+
BackgroundController,
|
|
79
85
|
AttachmentsController,
|
|
80
86
|
SkillsController,
|
|
81
87
|
SkillChangesController,
|
|
@@ -97,6 +103,7 @@ const CONTROLLERS = [
|
|
|
97
103
|
DirectorySyncController,
|
|
98
104
|
UiController,
|
|
99
105
|
FilePreviewersController,
|
|
106
|
+
ExtensionPointDefaultsController,
|
|
100
107
|
TrajectoryController,
|
|
101
108
|
EvalSetsController,
|
|
102
109
|
EvalRunsController,
|
|
@@ -126,6 +133,7 @@ ApiModule = __decorate([
|
|
|
126
133
|
ToolsModule,
|
|
127
134
|
AgentsModule,
|
|
128
135
|
ConversationsModule,
|
|
136
|
+
BackgroundModule,
|
|
129
137
|
RuntimeModule,
|
|
130
138
|
ChannelsModule,
|
|
131
139
|
EvalsModule,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { Logger } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import type { LoggerService } from "@nestjs/common";
|
|
3
|
-
/**
|
|
3
|
+
/** A logger with an optional debug (the kernel logger has one; the SDK Logger does not). */
|
|
4
4
|
type MaybeDebugLogger = Logger & {
|
|
5
5
|
debug?: (msg: string, fields?: Record<string, unknown>) => void;
|
|
6
6
|
};
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* `
|
|
8
|
+
* Forwards Nest's own log lines to the kernel logger, as octok does: `log` (module loading,
|
|
9
|
+
* the `Mapped {/api/v1/…, GET} route` lines at startup) at info, `warn` / `error` as they are,
|
|
10
|
+
* `debug` / `verbose` at debug. Nest's context name becomes the record's `context`.
|
|
10
11
|
*/
|
|
11
12
|
export declare class NestLogAdapter implements LoggerService {
|
|
12
13
|
private readonly logger;
|
|
@@ -14,7 +15,7 @@ export declare class NestLogAdapter implements LoggerService {
|
|
|
14
15
|
log(message: unknown, ...params: unknown[]): void;
|
|
15
16
|
warn(message: unknown, ...params: unknown[]): void;
|
|
16
17
|
error(message: unknown, ...params: unknown[]): void;
|
|
17
|
-
debug(): void;
|
|
18
|
-
verbose(): void;
|
|
18
|
+
debug(message: unknown, ...params: unknown[]): void;
|
|
19
|
+
verbose(message: unknown, ...params: unknown[]): void;
|
|
19
20
|
}
|
|
20
21
|
export {};
|
package/dist/http/nest-logger.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* `
|
|
2
|
+
* Forwards Nest's own log lines to the kernel logger, as octok does: `log` (module loading,
|
|
3
|
+
* the `Mapped {/api/v1/…, GET} route` lines at startup) at info, `warn` / `error` as they are,
|
|
4
|
+
* `debug` / `verbose` at debug. Nest's context name becomes the record's `context`.
|
|
4
5
|
*/
|
|
5
6
|
export class NestLogAdapter {
|
|
6
7
|
logger;
|
|
@@ -8,7 +9,7 @@ export class NestLogAdapter {
|
|
|
8
9
|
this.logger = logger;
|
|
9
10
|
}
|
|
10
11
|
log(message, ...params) {
|
|
11
|
-
this.logger.
|
|
12
|
+
this.logger.info(String(message), fields(params));
|
|
12
13
|
}
|
|
13
14
|
warn(message, ...params) {
|
|
14
15
|
this.logger.warn(String(message), fields(params));
|
|
@@ -16,13 +17,26 @@ export class NestLogAdapter {
|
|
|
16
17
|
error(message, ...params) {
|
|
17
18
|
this.logger.error(String(message), fields(params));
|
|
18
19
|
}
|
|
19
|
-
debug() {
|
|
20
|
-
|
|
20
|
+
debug(message, ...params) {
|
|
21
|
+
this.logger.debug?.(String(message), fields(params));
|
|
22
|
+
}
|
|
23
|
+
verbose(message, ...params) {
|
|
24
|
+
this.logger.debug?.(String(message), fields(params));
|
|
25
|
+
}
|
|
21
26
|
}
|
|
22
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Nest passes its context name (e.g. `RoutesResolver`) last and, for errors, the stack before it. The
|
|
29
|
+
* name becomes `context` (the pretty `[context=…]`), anything else `nest`.
|
|
30
|
+
*/
|
|
23
31
|
function fields(params) {
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
const rest = params.filter((p) => p !== undefined);
|
|
33
|
+
const last = rest.at(-1);
|
|
34
|
+
const out = {};
|
|
35
|
+
if (typeof last === "string" && /^[A-Za-z][\w.-]*$/.test(last)) {
|
|
36
|
+
out.context = last;
|
|
37
|
+
rest.pop();
|
|
38
|
+
}
|
|
39
|
+
if (rest.length > 0)
|
|
40
|
+
out.nest = rest.length === 1 ? rest[0] : rest;
|
|
41
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
28
42
|
}
|
|
@@ -15,7 +15,7 @@ import { CredentialThrottler } from "../infra/throttler/credential-throttler.js"
|
|
|
15
15
|
import { createZodDto } from "../infra/validation/create-zod-dto.js";
|
|
16
16
|
import { ZBody } from "../infra/validation/zod-params.js";
|
|
17
17
|
import { AccountsService } from "./accounts.js";
|
|
18
|
-
import { ACCESS_COOKIE, clearSessionCookies,
|
|
18
|
+
import { ACCESS_COOKIE, clearSessionCookies, refreshCookieValues, setSessionCookies, } from "./cookies.js";
|
|
19
19
|
import { Public } from "./principal.js";
|
|
20
20
|
import { SessionService } from "./sessions.js";
|
|
21
21
|
const LoginBodySchema = z.object({ login: z.string().max(256), password: z.string() });
|
|
@@ -74,9 +74,13 @@ let AuthController = class AuthController {
|
|
|
74
74
|
*/
|
|
75
75
|
async refresh(req, reply) {
|
|
76
76
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
// the header may carry several cap_rt (legacy Path and Path=/): the current one of a live session wins
|
|
78
|
+
const candidates = refreshCookieValues(req.headers.cookie);
|
|
79
|
+
if (candidates.length === 0) {
|
|
79
80
|
throw new ApiError("auth.refresh_invalid", "Refresh token is missing");
|
|
81
|
+
}
|
|
82
|
+
// none is current: the last one (the Path=/ cookie) goes through the usual replay handling
|
|
83
|
+
const rt = (await this.sessions.pickCurrentRefresh(candidates)) ?? candidates.at(-1);
|
|
80
84
|
const session = await this.sessions.refresh(rt, clientMeta(req));
|
|
81
85
|
const account = await this.accounts.get(session.accountId);
|
|
82
86
|
if (!account)
|
|
@@ -94,13 +98,13 @@ let AuthController = class AuthController {
|
|
|
94
98
|
async logout(req, reply) {
|
|
95
99
|
clearSessionCookies(reply);
|
|
96
100
|
const at = req.cookies[ACCESS_COOKIE];
|
|
97
|
-
const
|
|
101
|
+
const rts = refreshCookieValues(req.headers.cookie);
|
|
98
102
|
if (at) {
|
|
99
103
|
const sid = await this.sessions.sessionIdFromAccess(at);
|
|
100
104
|
if (sid !== undefined)
|
|
101
105
|
await this.sessions.revoke(sid);
|
|
102
106
|
}
|
|
103
|
-
|
|
107
|
+
for (const rt of rts)
|
|
104
108
|
await this.sessions.revokeByRefresh(rt);
|
|
105
109
|
}
|
|
106
110
|
};
|
|
@@ -3,14 +3,29 @@ import type { KernelConfig } from "../config/schema.js";
|
|
|
3
3
|
import type { IssuedSession } from "./sessions.js";
|
|
4
4
|
/** access token Cookie:随所有请求发送。 */
|
|
5
5
|
export declare const ACCESS_COOKIE = "cap_at";
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* The refresh-token cookie: SameSite=Lax so a top-level navigation from another site (an IM card
|
|
8
|
+
* link, an email, an OAuth return) carries it and the web tier can renew the session. The refresh
|
|
9
|
+
* endpoint is POST-only and requires `X-Capybara-Csrf`, and Lax cookies are not sent on cross-site
|
|
10
|
+
* POSTs, so this does not widen CSRF exposure.
|
|
11
|
+
*/
|
|
7
12
|
export declare const REFRESH_COOKIE = "cap_rt";
|
|
8
|
-
/** refresh Cookie 的 Path。 */
|
|
9
|
-
export declare const REFRESH_COOKIE_PATH = "/api/v1/auth";
|
|
10
13
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
14
|
+
* Path of the refresh Cookie: `/`, so the web tier sees it on page requests and can renew the
|
|
15
|
+
* session while rendering on the server.
|
|
16
|
+
*/
|
|
17
|
+
export declare const REFRESH_COOKIE_PATH = "/";
|
|
18
|
+
/** The Path earlier releases set `cap_rt` on; browsers may still hold it, so it is cleared too. */
|
|
19
|
+
export declare const LEGACY_REFRESH_COOKIE_PATH = "/api/v1/auth";
|
|
20
|
+
/**
|
|
21
|
+
* Writes the session cookies. Both are HttpOnly, host-only (no Domain), SameSite=Lax and Path=/;
|
|
22
|
+
* the `cap_rt` on the legacy Path is cleared at the same time.
|
|
13
23
|
*/
|
|
14
24
|
export declare function setSessionCookies(reply: FastifyReply, s: IssuedSession, cfg: KernelConfig): void;
|
|
15
25
|
/** 清除会话 Cookie(Path 必须与设置时一致才能覆盖)。 */
|
|
16
26
|
export declare function clearSessionCookies(reply: FastifyReply): void;
|
|
27
|
+
/**
|
|
28
|
+
* Every `cap_rt` value of a raw `cookie` header, in header order. A browser holding the legacy
|
|
29
|
+
* Path cookie and the new one sends both, the more specific Path first.
|
|
30
|
+
*/
|
|
31
|
+
export declare function refreshCookieValues(header: string | undefined): string[];
|
package/dist/identity/cookies.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
/** access token Cookie:随所有请求发送。 */
|
|
2
2
|
export const ACCESS_COOKIE = "cap_at";
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* The refresh-token cookie: SameSite=Lax so a top-level navigation from another site (an IM card
|
|
5
|
+
* link, an email, an OAuth return) carries it and the web tier can renew the session. The refresh
|
|
6
|
+
* endpoint is POST-only and requires `X-Capybara-Csrf`, and Lax cookies are not sent on cross-site
|
|
7
|
+
* POSTs, so this does not widen CSRF exposure.
|
|
8
|
+
*/
|
|
4
9
|
export const REFRESH_COOKIE = "cap_rt";
|
|
5
|
-
/** refresh Cookie 的 Path。 */
|
|
6
|
-
export const REFRESH_COOKIE_PATH = "/api/v1/auth";
|
|
7
10
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
11
|
+
* Path of the refresh Cookie: `/`, so the web tier sees it on page requests and can renew the
|
|
12
|
+
* session while rendering on the server.
|
|
13
|
+
*/
|
|
14
|
+
export const REFRESH_COOKIE_PATH = "/";
|
|
15
|
+
/** The Path earlier releases set `cap_rt` on; browsers may still hold it, so it is cleared too. */
|
|
16
|
+
export const LEGACY_REFRESH_COOKIE_PATH = "/api/v1/auth";
|
|
17
|
+
/**
|
|
18
|
+
* Writes the session cookies. Both are HttpOnly, host-only (no Domain), SameSite=Lax and Path=/;
|
|
19
|
+
* the `cap_rt` on the legacy Path is cleared at the same time.
|
|
10
20
|
*/
|
|
11
21
|
export function setSessionCookies(reply, s, cfg) {
|
|
12
22
|
const secure = cfg.auth.cookieSecure;
|
|
@@ -20,13 +30,41 @@ export function setSessionCookies(reply, s, cfg) {
|
|
|
20
30
|
reply.setCookie(REFRESH_COOKIE, s.refreshToken, {
|
|
21
31
|
path: REFRESH_COOKIE_PATH,
|
|
22
32
|
httpOnly: true,
|
|
23
|
-
sameSite: "
|
|
33
|
+
sameSite: "lax",
|
|
24
34
|
secure,
|
|
25
35
|
maxAge: cfg.auth.refreshTokenTtlDays * 86400,
|
|
26
36
|
});
|
|
37
|
+
reply.clearCookie(REFRESH_COOKIE, { path: LEGACY_REFRESH_COOKIE_PATH });
|
|
27
38
|
}
|
|
28
39
|
/** 清除会话 Cookie(Path 必须与设置时一致才能覆盖)。 */
|
|
29
40
|
export function clearSessionCookies(reply) {
|
|
30
41
|
reply.clearCookie(ACCESS_COOKIE, { path: "/" });
|
|
31
42
|
reply.clearCookie(REFRESH_COOKIE, { path: REFRESH_COOKIE_PATH });
|
|
43
|
+
reply.clearCookie(REFRESH_COOKIE, { path: LEGACY_REFRESH_COOKIE_PATH });
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Every `cap_rt` value of a raw `cookie` header, in header order. A browser holding the legacy
|
|
47
|
+
* Path cookie and the new one sends both, the more specific Path first.
|
|
48
|
+
*/
|
|
49
|
+
export function refreshCookieValues(header) {
|
|
50
|
+
if (!header)
|
|
51
|
+
return [];
|
|
52
|
+
const out = [];
|
|
53
|
+
for (const part of header.split(";")) {
|
|
54
|
+
const eq = part.indexOf("=");
|
|
55
|
+
if (eq < 0 || part.slice(0, eq).trim() !== REFRESH_COOKIE)
|
|
56
|
+
continue;
|
|
57
|
+
let v = part.slice(eq + 1).trim();
|
|
58
|
+
if (v.startsWith('"') && v.endsWith('"') && v.length >= 2)
|
|
59
|
+
v = v.slice(1, -1);
|
|
60
|
+
try {
|
|
61
|
+
v = decodeURIComponent(v);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// keep the raw value
|
|
65
|
+
}
|
|
66
|
+
if (v)
|
|
67
|
+
out.push(v);
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
32
70
|
}
|
|
@@ -8,6 +8,7 @@ import { type Account, AccountsService } from "./accounts.js";
|
|
|
8
8
|
import { type ApiTokenRecord, ApiTokenService } from "./api-tokens.js";
|
|
9
9
|
import { type Principal } from "./principal.js";
|
|
10
10
|
import { SessionService } from "./sessions.js";
|
|
11
|
+
import { SettingsService } from "./settings.js";
|
|
11
12
|
declare const ChangePasswordSchema: z.ZodObject<{
|
|
12
13
|
currentPassword: z.ZodString;
|
|
13
14
|
newPassword: z.ZodString;
|
|
@@ -33,12 +34,15 @@ export declare class MeController {
|
|
|
33
34
|
private readonly tokens;
|
|
34
35
|
private readonly throttler;
|
|
35
36
|
private readonly log;
|
|
36
|
-
|
|
37
|
+
private readonly settings;
|
|
38
|
+
constructor(accounts: AccountsService, sessions: SessionService, orgs: OrgsService, tokens: ApiTokenService, throttler: CredentialThrottler, log: Logger, settings: SettingsService);
|
|
37
39
|
me(principal: Principal): Promise<{
|
|
38
40
|
account: Account;
|
|
39
41
|
via: "session" | "pat";
|
|
40
42
|
currentOrgId: string | null;
|
|
41
43
|
memberships: MembershipView[];
|
|
44
|
+
/** Whether `POST /api/v1/orgs` would accept this account (the `orgCreation` setting). */
|
|
45
|
+
canCreateOrg: boolean;
|
|
42
46
|
}>;
|
|
43
47
|
/** 切换当前组织(仅 Web 会话);`null` 清空。非成员或已软删除的组织 → `org.not_found`。 */
|
|
44
48
|
setCurrentOrg(principal: Principal, body: z.infer<typeof CurrentOrgSchema>): Promise<void>;
|