@capybara-hq/kernel 0.1.0-rc.27 → 0.1.0-rc.28

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.
Files changed (88) hide show
  1. package/dist/app.js +2 -0
  2. package/dist/catalog/catalog-content.d.ts +15 -3
  3. package/dist/catalog/catalog-content.js +5 -3
  4. package/dist/catalog/catalog-scope.service.js +3 -2
  5. package/dist/catalog/marketplaces.service.d.ts +1 -1
  6. package/dist/catalog/org-catalog.service.js +10 -3
  7. package/dist/catalog/plugin-install.d.ts +34 -18
  8. package/dist/catalog/plugin-install.js +84 -46
  9. package/dist/catalog/plugin-upgrade.js +1 -0
  10. package/dist/channels/actions.d.ts +4 -1
  11. package/dist/channels/actions.js +5 -2
  12. package/dist/channels/cards.d.ts +7 -1
  13. package/dist/channels/cards.js +28 -1
  14. package/dist/channels/phrases.d.ts +15 -0
  15. package/dist/channels/phrases.js +9 -0
  16. package/dist/config/schema.d.ts +8 -0
  17. package/dist/config/schema.js +69 -0
  18. package/dist/conversations/events.d.ts +14 -0
  19. package/dist/conversations/events.js +20 -0
  20. package/dist/db/kernel-schema.d.ts +19 -4
  21. package/dist/errors.d.ts +5 -0
  22. package/dist/errors.js +5 -0
  23. package/dist/git/gateway.d.ts +93 -0
  24. package/dist/git/gateway.js +750 -0
  25. package/dist/git/gateway.route.d.ts +16 -0
  26. package/dist/git/gateway.route.js +41 -0
  27. package/dist/git/git.module.d.ts +10 -0
  28. package/dist/git/git.module.js +31 -0
  29. package/dist/git/identity.d.ts +80 -0
  30. package/dist/git/identity.js +209 -0
  31. package/dist/git/pkt-line.d.ts +53 -0
  32. package/dist/git/pkt-line.js +146 -0
  33. package/dist/git/run-end.d.ts +29 -0
  34. package/dist/git/run-end.js +121 -0
  35. package/dist/git/sources.d.ts +42 -0
  36. package/dist/git/sources.js +134 -0
  37. package/dist/git/tickets.d.ts +69 -0
  38. package/dist/git/tickets.js +194 -0
  39. package/dist/http/api.module.js +3 -0
  40. package/dist/http/app.d.ts +6 -0
  41. package/dist/http/app.js +3 -0
  42. package/dist/infra/config/kernel-config.service.js +2 -0
  43. package/dist/orgs/settings.service.d.ts +21 -0
  44. package/dist/orgs/settings.service.js +18 -0
  45. package/dist/overrides.d.ts +6 -0
  46. package/dist/overrides.js +4 -0
  47. package/dist/platform/exclusions.js +3 -0
  48. package/dist/runtime/run-finish.d.ts +5 -0
  49. package/dist/runtime/run-finish.js +12 -3
  50. package/dist/runtime/runtime.module.js +2 -0
  51. package/dist/runtime/waits.js +7 -5
  52. package/dist/schedules/fire.d.ts +3 -3
  53. package/dist/schedules/fire.js +7 -4
  54. package/dist/schedules/grant-rules.d.ts +40 -0
  55. package/dist/schedules/grant-rules.js +83 -0
  56. package/dist/schedules/scheduled-tasks.service.d.ts +9 -7
  57. package/dist/schedules/scheduled-tasks.service.js +50 -15
  58. package/dist/schedules/types.d.ts +2 -0
  59. package/dist/tools/approvals.service.d.ts +100 -19
  60. package/dist/tools/approvals.service.js +288 -21
  61. package/dist/tools/builtin-names.d.ts +4 -2
  62. package/dist/tools/builtin-names.js +6 -1
  63. package/dist/tools/builtins/schedules.d.ts +11 -9
  64. package/dist/tools/builtins/schedules.js +202 -44
  65. package/dist/tools/connector-account-policies.d.ts +8 -0
  66. package/dist/tools/connector-account-policies.js +8 -0
  67. package/dist/tools/connectors.controller.d.ts +2 -0
  68. package/dist/tools/connectors.controller.js +5 -1
  69. package/dist/tools/connectors.service.d.ts +46 -3
  70. package/dist/tools/connectors.service.js +98 -5
  71. package/dist/tools/credentials.d.ts +18 -0
  72. package/dist/tools/credentials.js +60 -0
  73. package/dist/tools/credentials.module.d.ts +7 -0
  74. package/dist/tools/credentials.module.js +22 -0
  75. package/dist/tools/git-asks.d.ts +113 -0
  76. package/dist/tools/git-asks.js +174 -0
  77. package/dist/tools/git-push-policy.d.ts +89 -0
  78. package/dist/tools/git-push-policy.js +175 -0
  79. package/dist/tools/info.d.ts +3 -3
  80. package/dist/tools/info.js +3 -3
  81. package/dist/tools/network-asks.d.ts +5 -0
  82. package/dist/tools/network-asks.js +9 -2
  83. package/dist/tools/tools.module.js +4 -3
  84. package/package.json +3 -3
  85. package/src/migrations/0126_git_gateway.sql +10 -0
  86. package/src/migrations/0127_git_push_policy.sql +12 -0
  87. package/src/migrations/0128_scheduled_task_plugin_disabled.sql +10 -0
  88. package/src/migrations/0129_connector_plugin_disabled.sql +5 -0
@@ -144,6 +144,14 @@ export declare const KernelConfigSchema: z.ZodObject<{
144
144
  driftIntervalHours: z.ZodDefault<z.ZodNumber>;
145
145
  maxResultReadChars: z.ZodDefault<z.ZodNumber>;
146
146
  oauthAllowInsecureLoopback: z.ZodDefault<z.ZodBoolean>;
147
+ git: z.ZodPrefault<z.ZodObject<{
148
+ enabled: z.ZodDefault<z.ZodBoolean>;
149
+ gatewayUrl: z.ZodOptional<z.ZodURL>;
150
+ maxBytesPerRun: z.ZodDefault<z.ZodNumber>;
151
+ idleTimeoutMs: z.ZodDefault<z.ZodNumber>;
152
+ maxConcurrentPerOrg: z.ZodDefault<z.ZodNumber>;
153
+ ticketTtlSeconds: z.ZodDefault<z.ZodNumber>;
154
+ }, z.core.$strip>>;
147
155
  }, z.core.$strip>>;
148
156
  channels: z.ZodDefault<z.ZodObject<{
149
157
  leaseTtlMs: z.ZodDefault<z.ZodNumber>;
@@ -773,6 +773,68 @@ export const KernelConfigSchema = z.object({
773
773
  .meta(meta({
774
774
  description: "Allow OAuth discovery, registration and token endpoints over plain http on loopback hosts (development and tests only; production requires https)",
775
775
  })),
776
+ git: z
777
+ .object({
778
+ enabled: z
779
+ .boolean()
780
+ .default(true)
781
+ .meta(meta({
782
+ hot: true,
783
+ description: "Serve the git gateway that lets sandboxes clone and push over HTTPS with run tickets; when off the gateway refuses every request",
784
+ })),
785
+ gatewayUrl: z
786
+ .url({ protocol: /^https?$/ })
787
+ .refine((u) => {
788
+ const x = URL.canParse(u) ? new URL(u) : null;
789
+ return (x !== null &&
790
+ x.pathname === "/" &&
791
+ !x.search &&
792
+ !x.hash &&
793
+ !x.username &&
794
+ !x.password);
795
+ }, { message: "must be an origin: no path, query, fragment or credentials" })
796
+ .optional()
797
+ .meta(meta({
798
+ description: "Origin (http or https, no path, query or credentials) of the kernel as sandboxes reach it, e.g. https://kernel.internal:8080; when unset the kernel uses http.publicUrl",
799
+ })),
800
+ maxBytesPerRun: z
801
+ .number()
802
+ .int()
803
+ .min(1048576)
804
+ .max(107374182400)
805
+ .default(5368709120)
806
+ .meta(meta({
807
+ description: "Maximum bytes the git gateway transfers (both directions) for one run, between 1 MiB and 100 GiB (default 5 GiB)",
808
+ })),
809
+ idleTimeoutMs: z
810
+ .number()
811
+ .int()
812
+ .min(1000)
813
+ .max(3600000)
814
+ .default(60000)
815
+ .meta(meta({
816
+ description: "A git gateway request that moves no data for this many milliseconds is aborted, between 1000 and 3600000",
817
+ })),
818
+ maxConcurrentPerOrg: z
819
+ .number()
820
+ .int()
821
+ .min(1)
822
+ .max(1000)
823
+ .default(8)
824
+ .meta(meta({
825
+ description: "Maximum git gateway requests in flight at once for one org, between 1 and 1000",
826
+ })),
827
+ ticketTtlSeconds: z
828
+ .number()
829
+ .int()
830
+ .min(60)
831
+ .max(86400)
832
+ .default(900)
833
+ .meta(meta({
834
+ description: "Lifetime in seconds of the ticket issued for one exec to the git gateway (a ticket also dies when its run ends), between 60 and 86400",
835
+ })),
836
+ })
837
+ .prefault({}),
776
838
  })
777
839
  .default({
778
840
  timeoutMs: 60000,
@@ -787,6 +849,13 @@ export const KernelConfigSchema = z.object({
787
849
  driftIntervalHours: 24,
788
850
  maxResultReadChars: 20000,
789
851
  oauthAllowInsecureLoopback: false,
852
+ git: {
853
+ enabled: true,
854
+ maxBytesPerRun: 5368709120,
855
+ idleTimeoutMs: 60000,
856
+ maxConcurrentPerOrg: 8,
857
+ ticketTtlSeconds: 900,
858
+ },
790
859
  }),
791
860
  channels: z
792
861
  .object({
@@ -4,6 +4,15 @@ import { z } from "zod";
4
4
  export declare const NETWORK_ASK_MAX_HOSTS = 20;
5
5
  /** The longest host name a network ask or `network_blocked` event carries. */
6
6
  export declare const NETWORK_HOST_MAX_CHARS = 255;
7
+ /** The most refs one git push ask (and its `approval_requested.git`) lists. */
8
+ export declare const GIT_ASK_MAX_REFS = 20;
9
+ /** The longest ref name a git push ask carries. */
10
+ export declare const GIT_REF_MAX_CHARS = 255;
11
+ /**
12
+ * The longest `<owner>/<repo>` a git push ask carries (GitHub allows 39 + 100 characters): the
13
+ * grant key `git:push:<owner>/<repo>` must fit an org's `forbiddenTools` entry (200 characters).
14
+ */
15
+ export declare const GIT_REPO_MAX_CHARS = 191;
7
16
  /** The longest deny reason (characters): the decision route's limit and `approval_decided.reason`'s. */
8
17
  export declare const APPROVAL_DENY_REASON_MAX_CHARS = 500;
9
18
  /** 一条 `media_excluded` 事件最多的附件数。 */
@@ -716,6 +725,11 @@ export declare const ConversationEventSchema: z.ZodDiscriminatedUnion<[z.ZodObje
716
725
  network: z.ZodOptional<z.ZodObject<{
717
726
  hosts: z.ZodArray<z.ZodString>;
718
727
  }, z.core.$strip>>;
728
+ git: z.ZodOptional<z.ZodObject<{
729
+ host: z.ZodString;
730
+ repo: z.ZodString;
731
+ refs: z.ZodArray<z.ZodString>;
732
+ }, z.core.$strip>>;
719
733
  }, z.core.$strip>;
720
734
  }, z.core.$strip>, z.ZodObject<{
721
735
  type: z.ZodLiteral<"approval_decided">;
@@ -31,6 +31,15 @@ const ApproverFields = {
31
31
  export const NETWORK_ASK_MAX_HOSTS = 20;
32
32
  /** The longest host name a network ask or `network_blocked` event carries. */
33
33
  export const NETWORK_HOST_MAX_CHARS = 255;
34
+ /** The most refs one git push ask (and its `approval_requested.git`) lists. */
35
+ export const GIT_ASK_MAX_REFS = 20;
36
+ /** The longest ref name a git push ask carries. */
37
+ export const GIT_REF_MAX_CHARS = 255;
38
+ /**
39
+ * The longest `<owner>/<repo>` a git push ask carries (GitHub allows 39 + 100 characters): the
40
+ * grant key `git:push:<owner>/<repo>` must fit an org's `forbiddenTools` entry (200 characters).
41
+ */
42
+ export const GIT_REPO_MAX_CHARS = 191;
34
43
  /** The longest deny reason (characters): the decision route's limit and `approval_decided.reason`'s. */
35
44
  export const APPROVAL_DENY_REASON_MAX_CHARS = 500;
36
45
  /** 一条 `media_excluded` 事件最多的附件数。 */
@@ -604,6 +613,17 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
604
613
  .max(NETWORK_ASK_MAX_HOSTS),
605
614
  })
606
615
  .optional(),
616
+ /**
617
+ * A git push ask (sandbox git credentials spec §7): the host, repository and refs the push
618
+ * the gateway held would have updated. Display only, never model-visible, as `network`.
619
+ */
620
+ git: z
621
+ .object({
622
+ host: z.string().min(1).max(NETWORK_HOST_MAX_CHARS),
623
+ repo: z.string().min(1).max(GIT_REPO_MAX_CHARS),
624
+ refs: z.array(z.string().min(1).max(GIT_REF_MAX_CHARS)).max(GIT_ASK_MAX_REFS),
625
+ })
626
+ .optional(),
607
627
  ...ApproverFields,
608
628
  })),
609
629
  /**
@@ -1,4 +1,4 @@
1
- import type { ConversationSource, FileSystemHandle, ParsedTheme, SandboxTier, SkillInvocation } from "@capybara-hq/plugin-sdk";
1
+ import type { ConversationSource, FileSystemHandle, GitHostEntry, ParsedTheme, SandboxTier, SkillInvocation } from "@capybara-hq/plugin-sdk";
2
2
  import type { ColumnType, Generated, Kysely } from "kysely";
3
3
  import type { ModelRef } from "../agents/definition.js";
4
4
  import type { ScheduleChat, ScheduleMarker, ScheduleMode, SchedulePauseReason, ScheduleStatus, ScheduleTrigger, TaskRunSkipReason, TaskRunStatus } from "../schedules/types.js";
@@ -454,6 +454,18 @@ export interface ConnectorsTable {
454
454
  * hash, mode: null }`); null when unknown (custom connectors, connectors before 0106).
455
455
  */
456
456
  guide_ack: ColumnType<GuideAckJson | null, string | null | undefined, string | null>;
457
+ /** 0126: the git hosts this connector declares (null: none). */
458
+ git_hosts: ColumnType<GitHostEntry[] | null, string | null | undefined, string | null>;
459
+ /** 0126: an org-owned connector serves as a git gateway source (personal ones always do). */
460
+ use_for_git: Generated<boolean>;
461
+ /** 0127: the org's policy for pushes through this connector (virtual tool `git_push`). */
462
+ git_push_policy: Generated<"allow" | "ask" | "deny">;
463
+ /**
464
+ * 0129: the plugin (catalog item) whose switch-off disabled this connector; null = not switched
465
+ * off by a plugin (an org admin's own disable, or enabled). Switching that plugin back on
466
+ * re-enables exactly the connectors it disabled.
467
+ */
468
+ disabled_by_plugin_id: ColumnType<string | null, string | null | undefined, string | null>;
457
469
  }
458
470
  /**
459
471
  * 0104: a member's own policy for one tool of a personal-credential connector, for calls made with
@@ -539,9 +551,12 @@ export interface ApprovalsTable {
539
551
  } | null, string | null | undefined, string | null>;
540
552
  /** 0086: the scopes the person may choose (null: derived, see `approvals.service.ts`). */
541
553
  scopes: ColumnType<("once" | "session" | "always")[] | null, ("once" | "session" | "always")[] | null | undefined, ("once" | "session" | "always")[] | null>;
542
- /** 0114: what is asked: a tool call ('tool') or a refused network domain ('network'). */
543
- kind: Generated<"tool" | "network">;
544
- /** 0114: a network ask's hosts, command, kept result and outcome (null for tool approvals). */
554
+ /**
555
+ * 0114: what is asked: a tool call ('tool') or a refused network domain ('network'); 0126: a
556
+ * git push the gateway held ('git').
557
+ */
558
+ kind: Generated<"tool" | "network" | "git">;
559
+ /** 0114: a network or git ask's facts, kept result and outcome (null for tool approvals). */
545
560
  detail: ColumnType<unknown | null, string | null | undefined, string | null>;
546
561
  }
547
562
  /** 0010_tools.sql:AskUserQuestion 提问。 */
package/dist/errors.d.ts CHANGED
@@ -191,6 +191,11 @@ export declare const ERROR_CODES: {
191
191
  readonly "catalog.marketplace_browse_disabled": 409;
192
192
  readonly "catalog.custom_disabled": 403;
193
193
  readonly "catalog.not_deployed": 409;
194
+ /**
195
+ * The connector an install planned to reuse (a template it asked no input for) was removed before
196
+ * the install ran: retry it, so the new plan asks for the input.
197
+ */
198
+ readonly "catalog.connector_removed": 409;
194
199
  readonly "catalog.icon_not_found": 404;
195
200
  readonly "catalog.guide_not_found": 404;
196
201
  /** The check is not in the setup guide rendered for that mode and audience (setup guides v2 §10.3). */
package/dist/errors.js CHANGED
@@ -191,6 +191,11 @@ export const ERROR_CODES = {
191
191
  "catalog.marketplace_browse_disabled": 409,
192
192
  "catalog.custom_disabled": 403,
193
193
  "catalog.not_deployed": 409,
194
+ /**
195
+ * The connector an install planned to reuse (a template it asked no input for) was removed before
196
+ * the install ran: retry it, so the new plan asks for the input.
197
+ */
198
+ "catalog.connector_removed": 409,
194
199
  "catalog.icon_not_found": 404,
195
200
  "catalog.guide_not_found": 404,
196
201
  /** The check is not in the setup guide rendered for that mode and audience (setup guides v2 §10.3). */
@@ -0,0 +1,93 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+ import type { Redis } from "ioredis";
3
+ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
4
+ import { KernelDb } from "../infra/database/kernel-db.js";
5
+ import type { KernelLogger } from "../log.js";
6
+ import { Credentials } from "../tools/credentials.js";
7
+ import { GitSources } from "./sources.js";
8
+ import { GitTickets } from "./tickets.js";
9
+ /**
10
+ * The git gateway (sandbox git credentials spec §5): relays git smart-HTTP from a sandbox to a git
11
+ * host, authenticated by a run ticket, adding the actor's credential on the way out. Stateless
12
+ * across replicas: the ticket, the source, the push policy, the byte and concurrency counters are
13
+ * read from Redis / PG on every request; only the request's own idle timer lives in the process.
14
+ *
15
+ * The route (`/git/v1/<host>/<owner>/<repo>[.git]/<endpoint>`, {@link GIT_GATEWAY_PREFIX}) is a raw
16
+ * Fastify route (`gateway.route.ts`), like the channel webhook: git sends no CSRF header and the
17
+ * ticket is not an API credential, so neither the `AuthGuard` nor the API's error envelope apply.
18
+ * Everything git shows is plain text or pkt-lines in English.
19
+ */
20
+ /** The route prefix. */
21
+ export declare const GIT_GATEWAY_PREFIX = "/git/v1/";
22
+ /** The smart-HTTP services. */
23
+ export type GitService = "git-upload-pack" | "git-receive-pack";
24
+ /** A parsed gateway path. */
25
+ export interface GitGatewayTarget {
26
+ host: string;
27
+ /** `<owner>/<repo>` as written (validated), for the upstream URL. */
28
+ path: string;
29
+ /** `<owner>/<repo>` normalized (lower case, no `.git`), for policy and allowlists. */
30
+ repo: string;
31
+ /** `info/refs` (GET, with `service`) or the service endpoint itself (POST). */
32
+ endpoint: "info/refs" | GitService;
33
+ service: GitService;
34
+ }
35
+ /**
36
+ * Test seam (`KernelAppOptions.overrides`): the upstream origin of a git host (default
37
+ * `https://<host>`), e.g. a loopback fake for `github.com`. Production validation is unchanged:
38
+ * the fake is reached through the same `safeFetch` rules (loopback http only under
39
+ * `tools.oauthAllowInsecureLoopback`).
40
+ */
41
+ export declare const GIT_UPSTREAM_ORIGIN: unique symbol;
42
+ export type GitUpstreamOrigin = (host: string) => string;
43
+ /**
44
+ * Parses `/git/v1/<host>/<owner>/<repo>[.git]/<endpoint>` with its query: GET `info/refs` with
45
+ * `service=git-upload-pack|git-receive-pack` and nothing else in the query, or POST
46
+ * `git-upload-pack` / `git-receive-pack` without a query. Anything else (dumb HTTP, other paths,
47
+ * other methods) is null (404).
48
+ */
49
+ export declare function parseGitGatewayPath(method: string, url: string): GitGatewayTarget | null;
50
+ /**
51
+ * Whether the ticket's repository allowlist admits `repo` (normalized). Entries are
52
+ * `<owner>/<repo>`, `<owner>/*` or `*`, compared case-insensitively; no list admits every one.
53
+ */
54
+ export declare function repoAllowed(repos: readonly string[] | undefined, repo: string): boolean;
55
+ /** The Redis counter of a run's relayed bytes. */
56
+ export declare const gitBytesKey: (runId: string) => string;
57
+ /** The Redis counter of an org's open gateway requests. */
58
+ export declare const gitConcurrencyKey: (orgId: string) => string;
59
+ export declare class GitGateway {
60
+ private readonly tickets;
61
+ private readonly sources;
62
+ private readonly credentials;
63
+ private readonly db;
64
+ private readonly redis;
65
+ /** Read per request: `tools.git.*` is read live. */
66
+ private readonly config;
67
+ private readonly log;
68
+ private readonly upstreamOrigin?;
69
+ constructor(tickets: Pick<GitTickets, "resolve">, sources: Pick<GitSources, "sourcesFor">, credentials: Pick<Credentials, "gitAuthorization" | "gitUnauthorized">, db: KernelDb, redis: Redis,
70
+ /** Read per request: `tools.git.*` is read live. */
71
+ config: Pick<KernelConfigService, "get">, log: Pick<KernelLogger, "info" | "warn" | "debug">, upstreamOrigin?: GitUpstreamOrigin | undefined);
72
+ /** Handles one gateway request (the raw route's handler). */
73
+ handle(req: FastifyRequest, reply: FastifyReply): Promise<void>;
74
+ /** The `git.push` audit row of a receive-pack and the run's fetch counters (best effort). */
75
+ private afterRequest;
76
+ /**
77
+ * Takes one of the org's gateway slots (Redis `git-conc:<orgId>`, TTL refreshed by every
78
+ * request and every byte meter flush) and returns how many are in use now with the slot's
79
+ * release (idempotent; a counter that went below zero after an expiry is reset).
80
+ */
81
+ private acquireOrgSlot;
82
+ private done;
83
+ private relay;
84
+ /** The push decision for this target (spec §7). */
85
+ private decide;
86
+ /**
87
+ * Records a refused push for the exec (`git-ask:<execId>`, read by the Bash pipeline after the
88
+ * command) and, for an unattended run without a task grant, the missing grant on the task run.
89
+ */
90
+ private recordRefusal;
91
+ /** The connector's auth (it may have changed since the source was chosen); null when gone. */
92
+ private connectorAuth;
93
+ }