@capybara-hq/kernel 0.1.0-rc.26 → 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.
- package/dist/app.js +2 -0
- package/dist/catalog/catalog-content.d.ts +18 -1
- package/dist/catalog/catalog-content.js +5 -2
- package/dist/catalog/catalog-scope.service.js +3 -2
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-catalog.service.js +13 -3
- package/dist/catalog/plugin-install.d.ts +37 -16
- package/dist/catalog/plugin-install.js +108 -34
- package/dist/catalog/plugin-upgrade.js +1 -0
- package/dist/channels/actions.d.ts +4 -1
- package/dist/channels/actions.js +5 -2
- package/dist/channels/cards.d.ts +7 -1
- package/dist/channels/cards.js +28 -1
- package/dist/channels/phrases.d.ts +15 -0
- package/dist/channels/phrases.js +9 -0
- package/dist/config/schema.d.ts +8 -0
- package/dist/config/schema.js +69 -0
- package/dist/conversations/events.d.ts +14 -0
- package/dist/conversations/events.js +20 -0
- package/dist/db/kernel-schema.d.ts +19 -4
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +5 -0
- package/dist/git/gateway.d.ts +93 -0
- package/dist/git/gateway.js +750 -0
- package/dist/git/gateway.route.d.ts +16 -0
- package/dist/git/gateway.route.js +41 -0
- package/dist/git/git.module.d.ts +10 -0
- package/dist/git/git.module.js +31 -0
- package/dist/git/identity.d.ts +80 -0
- package/dist/git/identity.js +209 -0
- package/dist/git/pkt-line.d.ts +53 -0
- package/dist/git/pkt-line.js +146 -0
- package/dist/git/run-end.d.ts +29 -0
- package/dist/git/run-end.js +121 -0
- package/dist/git/sources.d.ts +42 -0
- package/dist/git/sources.js +134 -0
- package/dist/git/tickets.d.ts +69 -0
- package/dist/git/tickets.js +194 -0
- package/dist/http/api.module.js +3 -0
- package/dist/http/app.d.ts +6 -0
- package/dist/http/app.js +3 -0
- package/dist/infra/config/kernel-config.service.js +2 -0
- package/dist/orgs/settings.service.d.ts +21 -0
- package/dist/orgs/settings.service.js +18 -0
- package/dist/overrides.d.ts +6 -0
- package/dist/overrides.js +4 -0
- package/dist/platform/exclusions.js +3 -0
- package/dist/runtime/run-finish.d.ts +5 -0
- package/dist/runtime/run-finish.js +12 -3
- package/dist/runtime/runtime.module.js +2 -0
- package/dist/runtime/waits.js +7 -5
- package/dist/schedules/fire.d.ts +3 -3
- package/dist/schedules/fire.js +7 -4
- package/dist/schedules/grant-rules.d.ts +40 -0
- package/dist/schedules/grant-rules.js +83 -0
- package/dist/schedules/scheduled-tasks.service.d.ts +9 -7
- package/dist/schedules/scheduled-tasks.service.js +50 -15
- package/dist/schedules/types.d.ts +2 -0
- package/dist/tools/approvals.service.d.ts +100 -19
- package/dist/tools/approvals.service.js +288 -21
- package/dist/tools/builtin-names.d.ts +4 -2
- package/dist/tools/builtin-names.js +6 -1
- package/dist/tools/builtins/schedules.d.ts +11 -9
- package/dist/tools/builtins/schedules.js +202 -44
- package/dist/tools/connector-account-policies.d.ts +8 -0
- package/dist/tools/connector-account-policies.js +8 -0
- package/dist/tools/connectors.controller.d.ts +2 -0
- package/dist/tools/connectors.controller.js +5 -1
- package/dist/tools/connectors.service.d.ts +46 -3
- package/dist/tools/connectors.service.js +98 -9
- package/dist/tools/credentials.d.ts +18 -0
- package/dist/tools/credentials.js +60 -0
- package/dist/tools/credentials.module.d.ts +7 -0
- package/dist/tools/credentials.module.js +22 -0
- package/dist/tools/git-asks.d.ts +113 -0
- package/dist/tools/git-asks.js +174 -0
- package/dist/tools/git-push-policy.d.ts +89 -0
- package/dist/tools/git-push-policy.js +175 -0
- package/dist/tools/info.d.ts +3 -3
- package/dist/tools/info.js +3 -3
- package/dist/tools/network-asks.d.ts +5 -0
- package/dist/tools/network-asks.js +9 -2
- package/dist/tools/tools.module.js +4 -3
- package/package.json +3 -3
- package/src/migrations/0126_git_gateway.sql +10 -0
- package/src/migrations/0127_git_push_policy.sql +12 -0
- package/src/migrations/0128_scheduled_task_plugin_disabled.sql +10 -0
- package/src/migrations/0129_connector_plugin_disabled.sql +5 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { KernelSchemaDb } from "../db/kernel-schema.js";
|
|
2
|
+
import type { KernelConfigService } from "../infra/config/kernel-config.service.js";
|
|
3
|
+
import { type UnattendedRun } from "../schedules/unattended.js";
|
|
4
|
+
import { type GitRunState } from "./git-asks.js";
|
|
5
|
+
import type { ToolPolicy } from "./registry.js";
|
|
6
|
+
/** Why {@link decideGitPush} decided as it did. */
|
|
7
|
+
export type GitPushReason =
|
|
8
|
+
/** The org's default for the connector allows pushes. */
|
|
9
|
+
"org_allow"
|
|
10
|
+
/** The member's standing allow for the repository. */
|
|
11
|
+
| "member_allow"
|
|
12
|
+
/** An approved 「这次允许」 of the run tree for the repository. */
|
|
13
|
+
| "run_grant"
|
|
14
|
+
/** The scheduled task's `git:push` / `git:push:<owner>/<repo>` grant (unattended run). */
|
|
15
|
+
| "task_grant"
|
|
16
|
+
/** The policy asks the actor. */
|
|
17
|
+
| "ask"
|
|
18
|
+
/** The org's default denies pushes through the connector. */
|
|
19
|
+
| "org_deny"
|
|
20
|
+
/** The member denied pushes to the repository for themselves. */
|
|
21
|
+
| "member_deny"
|
|
22
|
+
/** The org forbids `git:push` (or the repository's key) in scheduled runs. */
|
|
23
|
+
| "forbidden"
|
|
24
|
+
/** An unattended run in `fail` mode without a task grant: no one can be asked. */
|
|
25
|
+
| "unattended"
|
|
26
|
+
/** The connector is not a live, active connector of the org. */
|
|
27
|
+
| "unavailable"
|
|
28
|
+
/** The repository is not `<owner>/<repo>`. */
|
|
29
|
+
| "invalid_repo";
|
|
30
|
+
/** The decision about one push. */
|
|
31
|
+
export interface GitPushDecision {
|
|
32
|
+
decision: "allow" | "ask" | "deny";
|
|
33
|
+
reason: GitPushReason;
|
|
34
|
+
/** The repository as keyed (`<owner>/<repo>`, lower-cased); null when it was invalid. */
|
|
35
|
+
repo: string | null;
|
|
36
|
+
/** An unattended refusal's `missing_grants` key (`git:push:<owner>/<repo>`) and task run. */
|
|
37
|
+
missingGrant?: {
|
|
38
|
+
taskRunId: string;
|
|
39
|
+
key: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** What {@link decideGitPush} decides about. */
|
|
43
|
+
export interface GitPushInput {
|
|
44
|
+
orgId: string;
|
|
45
|
+
/** The actor whose credential the push uses (the ticket's account). */
|
|
46
|
+
accountId: string;
|
|
47
|
+
/** The run the exec belongs to (the ticket's run). */
|
|
48
|
+
runId: string;
|
|
49
|
+
/** The git source the push uses (the connector whose credential is injected). */
|
|
50
|
+
connectorId: string;
|
|
51
|
+
host: string;
|
|
52
|
+
/** `<owner>/<repo>` (a trailing `.git` and case are normalized here). */
|
|
53
|
+
repo: string;
|
|
54
|
+
/**
|
|
55
|
+
* The scheduled task context of the run, when the caller has it (`unattendedRunOf`); undefined:
|
|
56
|
+
* read here. Null: an attended run.
|
|
57
|
+
*/
|
|
58
|
+
unattended?: UnattendedRun | null;
|
|
59
|
+
}
|
|
60
|
+
/** A decision the actor's or the org's policy settles without grants (pure). */
|
|
61
|
+
export declare function gitPushBase(input: {
|
|
62
|
+
orgDefault: ToolPolicy;
|
|
63
|
+
/** The member's own setting for the repository (a personal connector only). */
|
|
64
|
+
member?: ToolPolicy | undefined;
|
|
65
|
+
}): {
|
|
66
|
+
policy: ToolPolicy;
|
|
67
|
+
reason: GitPushReason;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Decides a push (sandbox git credentials spec §7), highest first:
|
|
71
|
+
* 1. the org's default `deny` for the connector; the member's own `deny` for the repository;
|
|
72
|
+
* 2. an unattended run (no trial, no person joined) whose org forbids `git:push` or the
|
|
73
|
+
* repository's key in scheduled runs (scheduled tasks spec §7): deny, whatever allows it below;
|
|
74
|
+
* 3. the member's standing allow for the repository on the host (personal connector), else the
|
|
75
|
+
* org's default `allow`; a member's `ask` keeps an org `allow` asking;
|
|
76
|
+
* 4. an ask is allowed by a run grant (an approved `once` git ask of the live run tree for the
|
|
77
|
+
* host, repository and connector, decided for the same actor);
|
|
78
|
+
* 5. an unattended run: a task grant `git:push` or `git:push:<owner>/<repo>` allows; else `fail`
|
|
79
|
+
* mode refuses (`unattended`, with the missing grant to record) and `wait` mode asks the
|
|
80
|
+
* creator.
|
|
81
|
+
* Grants never lift a deny. The caller records the missing grant and the Redis ask record.
|
|
82
|
+
*/
|
|
83
|
+
export declare function decideGitPush(db: KernelSchemaDb, config: Pick<KernelConfigService, "get">, input: GitPushInput): Promise<GitPushDecision>;
|
|
84
|
+
/**
|
|
85
|
+
* The run tree's git asks (the network asks' rules, network ask-to-allow spec §6.8): the
|
|
86
|
+
* repositories granted and refused in the tree (keyed `<host>/<owner>/<repo>`, {@link gitRepoKey}),
|
|
87
|
+
* this run's asks, a pending ask of the tree.
|
|
88
|
+
*/
|
|
89
|
+
export declare function gitRunState(db: KernelSchemaDb, runId: string): Promise<GitRunState>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
import { budgetRunOf } from "../runtime/subagents/limits.js";
|
|
3
|
+
import { GIT_PUSH_GRANT, gitPushForbidden, gitPushGrantKey, normalizeGitRepo, } from "../schedules/grant-rules.js";
|
|
4
|
+
import { unattendedRules, unattendedRunOf } from "../schedules/unattended.js";
|
|
5
|
+
import { gitPushPolicyName, personalAuth } from "./connector-account-policies.js";
|
|
6
|
+
import { gitRepoKey } from "./git-asks.js";
|
|
7
|
+
import { connectorPolicy } from "./policy.js";
|
|
8
|
+
/**
|
|
9
|
+
* The push policy of the git gateway (sandbox git credentials spec §7): fetches run, a push
|
|
10
|
+
* follows the connector's virtual tool `git_push`. Read from PG on every push (no in-process
|
|
11
|
+
* state), so a decision on one replica holds on every other.
|
|
12
|
+
*
|
|
13
|
+
* Storage (no new table): the org's default is `connectors.git_push_policy` (default `ask`); a
|
|
14
|
+
* member's standing allow (or deny) for one repository is their `connector_tool_account_policies`
|
|
15
|
+
* row `git_push:<host>/<owner>/<repo>` (a personal connector only: an org connector's push acts as the
|
|
16
|
+
* org); a run grant is an approved `once` git ask of the run tree; a scheduled task's grants are
|
|
17
|
+
* `git:push` and `git:push:<owner>/<repo>` in `scheduled_task_grants`.
|
|
18
|
+
*/
|
|
19
|
+
/** The statuses of a run that has not ended: its run grants still apply. */
|
|
20
|
+
const LIVE_RUN = ["queued", "running", "waiting"];
|
|
21
|
+
/** The statuses of a git ask that refuse its repository for the rest of the run tree. */
|
|
22
|
+
const REFUSING = new Set(["denied", "expired", "skipped", "cancelled"]);
|
|
23
|
+
/** A decision the actor's or the org's policy settles without grants (pure). */
|
|
24
|
+
export function gitPushBase(input) {
|
|
25
|
+
const r = connectorPolicy({ orgDefault: input.orgDefault, member: input.member });
|
|
26
|
+
if (r.policy === "deny")
|
|
27
|
+
return { policy: "deny", reason: r.source === "org" ? "org_deny" : "member_deny" };
|
|
28
|
+
if (r.policy === "allow") {
|
|
29
|
+
return { policy: "allow", reason: r.source === "member" ? "member_allow" : "org_allow" };
|
|
30
|
+
}
|
|
31
|
+
return { policy: "ask", reason: "ask" };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Decides a push (sandbox git credentials spec §7), highest first:
|
|
35
|
+
* 1. the org's default `deny` for the connector; the member's own `deny` for the repository;
|
|
36
|
+
* 2. an unattended run (no trial, no person joined) whose org forbids `git:push` or the
|
|
37
|
+
* repository's key in scheduled runs (scheduled tasks spec §7): deny, whatever allows it below;
|
|
38
|
+
* 3. the member's standing allow for the repository on the host (personal connector), else the
|
|
39
|
+
* org's default `allow`; a member's `ask` keeps an org `allow` asking;
|
|
40
|
+
* 4. an ask is allowed by a run grant (an approved `once` git ask of the live run tree for the
|
|
41
|
+
* host, repository and connector, decided for the same actor);
|
|
42
|
+
* 5. an unattended run: a task grant `git:push` or `git:push:<owner>/<repo>` allows; else `fail`
|
|
43
|
+
* mode refuses (`unattended`, with the missing grant to record) and `wait` mode asks the
|
|
44
|
+
* creator.
|
|
45
|
+
* Grants never lift a deny. The caller records the missing grant and the Redis ask record.
|
|
46
|
+
*/
|
|
47
|
+
export async function decideGitPush(db, config, input) {
|
|
48
|
+
const repo = normalizeGitRepo(input.repo);
|
|
49
|
+
if (repo === null)
|
|
50
|
+
return { decision: "deny", reason: "invalid_repo", repo: null };
|
|
51
|
+
const host = input.host.toLowerCase();
|
|
52
|
+
const connector = await db
|
|
53
|
+
.selectFrom("connectors")
|
|
54
|
+
.select(["auth", "status", "git_push_policy"])
|
|
55
|
+
.where("id", "=", input.connectorId)
|
|
56
|
+
.where("org_id", "=", input.orgId)
|
|
57
|
+
.where("deleted_at", "is", null)
|
|
58
|
+
.executeTakeFirst();
|
|
59
|
+
if (connector?.status !== "active") {
|
|
60
|
+
return { decision: "deny", reason: "unavailable", repo };
|
|
61
|
+
}
|
|
62
|
+
let member;
|
|
63
|
+
if (personalAuth(connector.auth)) {
|
|
64
|
+
const row = await db
|
|
65
|
+
.selectFrom("connector_tool_account_policies")
|
|
66
|
+
.select("policy")
|
|
67
|
+
.where("connector_id", "=", input.connectorId)
|
|
68
|
+
.where("account_id", "=", input.accountId)
|
|
69
|
+
.where("tool_name", "=", gitPushPolicyName(host, repo))
|
|
70
|
+
.executeTakeFirst();
|
|
71
|
+
member = row?.policy;
|
|
72
|
+
}
|
|
73
|
+
const base = gitPushBase({ orgDefault: connector.git_push_policy, member });
|
|
74
|
+
if (base.policy === "deny")
|
|
75
|
+
return { decision: "deny", reason: base.reason, repo };
|
|
76
|
+
// what an unattended run may not do at all comes before every allow (as the tool policy)
|
|
77
|
+
const key = gitPushGrantKey(repo);
|
|
78
|
+
const unattended = input.unattended !== undefined
|
|
79
|
+
? input.unattended
|
|
80
|
+
: await unattendedRunOf(db, config, input.orgId, input.runId);
|
|
81
|
+
const rules = unattendedRules(unattended) ? unattended : null;
|
|
82
|
+
if (rules !== null && gitPushForbidden(rules.forbiddenTools, key)) {
|
|
83
|
+
return { decision: "deny", reason: "forbidden", repo };
|
|
84
|
+
}
|
|
85
|
+
if (base.policy === "allow")
|
|
86
|
+
return { decision: "allow", reason: base.reason, repo };
|
|
87
|
+
if (await hasRunGrant(db, input, host, repo)) {
|
|
88
|
+
return { decision: "allow", reason: "run_grant", repo };
|
|
89
|
+
}
|
|
90
|
+
if (rules !== null) {
|
|
91
|
+
const granted = await db
|
|
92
|
+
.selectFrom("scheduled_task_grants")
|
|
93
|
+
.select("tool_key")
|
|
94
|
+
.where("task_id", "=", rules.taskId)
|
|
95
|
+
.where("tool_key", "in", [GIT_PUSH_GRANT, key])
|
|
96
|
+
.executeTakeFirst();
|
|
97
|
+
if (granted !== undefined)
|
|
98
|
+
return { decision: "allow", reason: "task_grant", repo };
|
|
99
|
+
if (rules.unattendedAsk === "fail") {
|
|
100
|
+
return {
|
|
101
|
+
decision: "deny",
|
|
102
|
+
reason: "unattended",
|
|
103
|
+
repo,
|
|
104
|
+
missingGrant: { taskRunId: rules.taskRunId, key },
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { decision: "ask", reason: "ask", repo };
|
|
109
|
+
}
|
|
110
|
+
/** The runs of the tree whose budget run is `root`. */
|
|
111
|
+
const treeOf = (db, root) => db
|
|
112
|
+
.selectFrom("runs")
|
|
113
|
+
.select("id")
|
|
114
|
+
.where((eb) => eb.or([eb("id", "=", root), eb("budget_run_id", "=", root)]));
|
|
115
|
+
/**
|
|
116
|
+
* Whether the run tree holds a run grant for this push: an approved `once` git ask of the tree in
|
|
117
|
+
* the org about the same host, repository and connector, asked of the same actor, while the tree's
|
|
118
|
+
* root run has not ended (a grant ends with its run; a later run of the same budget window does
|
|
119
|
+
* not inherit it). An `always` decision is the member's policy row and is read there, not here.
|
|
120
|
+
*/
|
|
121
|
+
async function hasRunGrant(db, input, host, repo) {
|
|
122
|
+
const root = await budgetRunOf(db, input.runId);
|
|
123
|
+
const live = await db
|
|
124
|
+
.selectFrom("runs")
|
|
125
|
+
.select("id")
|
|
126
|
+
.where("id", "=", root)
|
|
127
|
+
.where("status", "in", LIVE_RUN)
|
|
128
|
+
.executeTakeFirst();
|
|
129
|
+
if (live === undefined)
|
|
130
|
+
return false;
|
|
131
|
+
const row = await db
|
|
132
|
+
.selectFrom("approvals")
|
|
133
|
+
.select("id")
|
|
134
|
+
.where("kind", "=", "git")
|
|
135
|
+
.where("org_id", "=", input.orgId)
|
|
136
|
+
.where("connector_id", "=", input.connectorId)
|
|
137
|
+
.where("requested_for", "=", input.accountId)
|
|
138
|
+
.where("status", "=", "approved")
|
|
139
|
+
.where("scope", "=", "once")
|
|
140
|
+
.where(sql `detail->>'repo'`, "=", repo)
|
|
141
|
+
.where(sql `lower(detail->>'host')`, "=", host)
|
|
142
|
+
.where("run_id", "in", treeOf(db, root))
|
|
143
|
+
.executeTakeFirst();
|
|
144
|
+
return row !== undefined;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The run tree's git asks (the network asks' rules, network ask-to-allow spec §6.8): the
|
|
148
|
+
* repositories granted and refused in the tree (keyed `<host>/<owner>/<repo>`, {@link gitRepoKey}),
|
|
149
|
+
* this run's asks, a pending ask of the tree.
|
|
150
|
+
*/
|
|
151
|
+
export async function gitRunState(db, runId) {
|
|
152
|
+
const root = await budgetRunOf(db, runId);
|
|
153
|
+
const rows = await db
|
|
154
|
+
.selectFrom("approvals")
|
|
155
|
+
.select(["run_id", "status", "scope", "detail"])
|
|
156
|
+
.where("kind", "=", "git")
|
|
157
|
+
.where("run_id", "in", treeOf(db, root))
|
|
158
|
+
.execute();
|
|
159
|
+
const state = { granted: new Set(), refused: new Set(), asks: 0, pending: false };
|
|
160
|
+
for (const r of rows) {
|
|
161
|
+
if (r.run_id === runId)
|
|
162
|
+
state.asks++;
|
|
163
|
+
const d = r.detail;
|
|
164
|
+
if (r.status === "pending")
|
|
165
|
+
state.pending = true;
|
|
166
|
+
else if (typeof d?.repo !== "string" || typeof d.host !== "string")
|
|
167
|
+
continue;
|
|
168
|
+
else if (r.status === "approved" && r.scope === "once") {
|
|
169
|
+
state.granted.add(gitRepoKey(d.host, d.repo));
|
|
170
|
+
}
|
|
171
|
+
else if (REFUSING.has(r.status))
|
|
172
|
+
state.refused.add(gitRepoKey(d.host, d.repo));
|
|
173
|
+
}
|
|
174
|
+
return state;
|
|
175
|
+
}
|
package/dist/tools/info.d.ts
CHANGED
|
@@ -69,9 +69,9 @@ export interface ToolInfoDeps {
|
|
|
69
69
|
* - Extension tools resolve deployment-wide (from the loaded host), not per org: a tool of an
|
|
70
70
|
* extension that is not enabled in this org still answers `source: extension` with its title.
|
|
71
71
|
* That is deliberate: old conversation logs keep their labels, and the data is not sensitive.
|
|
72
|
-
* -
|
|
73
|
-
*
|
|
74
|
-
*
|
|
72
|
+
* - Extension and connector keys never compete for one key: extension tools are
|
|
73
|
+
* `plugin__<plugin>__<tool>` and connector tools `mcp__<slug>__<tool>`, so an extension name and
|
|
74
|
+
* a connector slug may be the same.
|
|
75
75
|
*/
|
|
76
76
|
export declare function toolInfo(deps: ToolInfoDeps, orgId: string, keys: readonly string[]): Promise<ToolInfo[]>;
|
|
77
77
|
/** `GET /conversations/:id/tools`: {@link toolInfo} for a conversation the viewer can read. */
|
package/dist/tools/info.js
CHANGED
|
@@ -39,9 +39,9 @@ const display = (spec) => ({
|
|
|
39
39
|
* - Extension tools resolve deployment-wide (from the loaded host), not per org: a tool of an
|
|
40
40
|
* extension that is not enabled in this org still answers `source: extension` with its title.
|
|
41
41
|
* That is deliberate: old conversation logs keep their labels, and the data is not sensitive.
|
|
42
|
-
* -
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* - Extension and connector keys never compete for one key: extension tools are
|
|
43
|
+
* `plugin__<plugin>__<tool>` and connector tools `mcp__<slug>__<tool>`, so an extension name and
|
|
44
|
+
* a connector slug may be the same.
|
|
45
45
|
*/
|
|
46
46
|
export async function toolInfo(deps, orgId, keys) {
|
|
47
47
|
const unique = [...new Set(keys)];
|
|
@@ -107,3 +107,8 @@ export declare const withNetworkNotes: (text: string, notes: readonly string[])
|
|
|
107
107
|
* {@link NETWORK_ASK_CLIPPED_NOTE}. The caller archives large output first, so this rarely cuts.
|
|
108
108
|
*/
|
|
109
109
|
export declare function clipKept(content: string): string;
|
|
110
|
+
/**
|
|
111
|
+
* `content` within {@link NETWORK_ASK_KEPT_MAX_BYTES} (UTF-8): unchanged when it fits, else its
|
|
112
|
+
* head and then `note` on a line of its own (the git asks clip with their own note).
|
|
113
|
+
*/
|
|
114
|
+
export declare function clipKeptWith(content: string, note: string): string;
|
|
@@ -210,13 +210,20 @@ export const withNetworkNotes = (text, notes) => notes.length === 0 ? text : `${
|
|
|
210
210
|
* {@link NETWORK_ASK_CLIPPED_NOTE}. The caller archives large output first, so this rarely cuts.
|
|
211
211
|
*/
|
|
212
212
|
export function clipKept(content) {
|
|
213
|
+
return clipKeptWith(content, NETWORK_ASK_CLIPPED_NOTE);
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* `content` within {@link NETWORK_ASK_KEPT_MAX_BYTES} (UTF-8): unchanged when it fits, else its
|
|
217
|
+
* head and then `note` on a line of its own (the git asks clip with their own note).
|
|
218
|
+
*/
|
|
219
|
+
export function clipKeptWith(content, note) {
|
|
213
220
|
const bytes = Buffer.from(content, "utf8");
|
|
214
221
|
if (bytes.length <= NETWORK_ASK_KEPT_MAX_BYTES)
|
|
215
222
|
return content;
|
|
216
|
-
const room = NETWORK_ASK_KEPT_MAX_BYTES - Buffer.byteLength(
|
|
223
|
+
const room = NETWORK_ASK_KEPT_MAX_BYTES - Buffer.byteLength(note) - 1;
|
|
217
224
|
// a cut inside a multi-byte character drops it rather than keeping half of it
|
|
218
225
|
const head = new TextDecoder("utf-8", { fatal: false })
|
|
219
226
|
.decode(bytes.subarray(0, room))
|
|
220
227
|
.replace(/�$/, "");
|
|
221
|
-
return `${head}\n${
|
|
228
|
+
return `${head}\n${note}`;
|
|
222
229
|
}
|
|
@@ -33,7 +33,7 @@ import { WebPages } from "./builtins/web/page-cache.js";
|
|
|
33
33
|
import { ConnectorAccountPolicies } from "./connector-account-policies.js";
|
|
34
34
|
import { ConnectorContent } from "./connector-content.js";
|
|
35
35
|
import { ConnectorsService } from "./connectors.service.js";
|
|
36
|
-
import {
|
|
36
|
+
import { CredentialsModule } from "./credentials.module.js";
|
|
37
37
|
import { DeviceOpResolver } from "./device-op-resolver.js";
|
|
38
38
|
import { ToolDrift } from "./drift.js";
|
|
39
39
|
import { ConversationToolInfo, OrgExtensionTools } from "./info.js";
|
|
@@ -83,9 +83,10 @@ ToolsModule = __decorate([
|
|
|
83
83
|
QueueModule.registerQueue(MAINTENANCE_QUEUE_NAME),
|
|
84
84
|
// a trial run's approvals become its scheduled task's grants (scheduled tasks spec §7)
|
|
85
85
|
ScheduledTaskStoreModule,
|
|
86
|
+
// connector credentials (also the git gateway's, without importing this module)
|
|
87
|
+
CredentialsModule,
|
|
86
88
|
],
|
|
87
89
|
providers: [
|
|
88
|
-
Credentials,
|
|
89
90
|
ToolOutputs,
|
|
90
91
|
BuiltinTools,
|
|
91
92
|
// `ScheduleUpdate`'s write in one transaction (scheduled tasks spec §11)
|
|
@@ -112,7 +113,7 @@ ToolsModule = __decorate([
|
|
|
112
113
|
],
|
|
113
114
|
exports: [
|
|
114
115
|
ScheduleToolsPort,
|
|
115
|
-
|
|
116
|
+
CredentialsModule,
|
|
116
117
|
ToolOutputs,
|
|
117
118
|
BuiltinTools,
|
|
118
119
|
ToolRegistry,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capybara-hq/kernel",
|
|
3
|
-
"version": "0.1.0-rc.
|
|
3
|
+
"version": "0.1.0-rc.28",
|
|
4
4
|
"description": "Capybara kernel: config, database, plugin host and HTTP API of the Capybara agent platform",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"yaml": "^2.9.1",
|
|
66
66
|
"yauzl": "3.4.0",
|
|
67
67
|
"zod": "^4.6.7",
|
|
68
|
-
"@capybara-hq/plugin-sdk": "^0.1.0-rc.
|
|
68
|
+
"@capybara-hq/plugin-sdk": "^0.1.0-rc.21"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@langchain/langgraph-checkpoint-validation": "^1.2.0",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@types/ws": "^8.18.2",
|
|
75
75
|
"@types/yauzl": "3.4.0",
|
|
76
76
|
"ws": "^8.22.0",
|
|
77
|
-
"@capybara-hq/testing": "0.1.0-rc.
|
|
77
|
+
"@capybara-hq/testing": "0.1.0-rc.18"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
80
|
"build": "tsc -b"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- Git credentials for sandboxes (spec 2026-11-12-sandbox-git-credentials-design.md): a connector
|
|
2
|
+
-- may declare the git hosts it can serve, and an org-owned one may be used as the source of the
|
|
3
|
+
-- git gateway (personal connectors are always sources). `git_hosts` is null for connectors that
|
|
4
|
+
-- declare none.
|
|
5
|
+
ALTER TABLE connectors ADD COLUMN IF NOT EXISTS git_hosts jsonb;
|
|
6
|
+
ALTER TABLE connectors ADD COLUMN IF NOT EXISTS use_for_git boolean NOT NULL DEFAULT false;
|
|
7
|
+
|
|
8
|
+
-- An approval of kind 'git' asks for a git operation through the gateway (widening 0114's CHECK).
|
|
9
|
+
ALTER TABLE approvals DROP CONSTRAINT IF EXISTS approvals_kind_check;
|
|
10
|
+
ALTER TABLE approvals ADD CONSTRAINT approvals_kind_check CHECK (kind IN ('tool', 'network', 'git'));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- The org's push policy for a connector's git hosts (sandbox git credentials spec §7): the
|
|
2
|
+
-- connector's virtual tool `git_push` (`allow` / `ask` / `deny`, default `ask`). It has no row in
|
|
3
|
+
-- `connector_tools` (that table mirrors the tools the connector serves, kept in step by drift
|
|
4
|
+
-- review), so it is the connector's own column. A member's standing allow for one repository is a
|
|
5
|
+
-- `connector_tool_account_policies` row named `git_push:<host>/<owner>/<repo>` (no schema change).
|
|
6
|
+
ALTER TABLE connectors ADD COLUMN IF NOT EXISTS git_push_policy text NOT NULL DEFAULT 'ask';
|
|
7
|
+
ALTER TABLE connectors DROP CONSTRAINT IF EXISTS connectors_git_push_policy_check;
|
|
8
|
+
ALTER TABLE connectors ADD CONSTRAINT connectors_git_push_policy_check
|
|
9
|
+
CHECK (git_push_policy IN ('allow', 'ask', 'deny'));
|
|
10
|
+
|
|
11
|
+
-- Run grants and the per-run caps read the git asks of a run tree.
|
|
12
|
+
CREATE INDEX IF NOT EXISTS approvals_git_run_idx ON approvals (run_id) WHERE kind = 'git';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- A chat task whose channel extension is disabled in the org skips its runs with `plugin_disabled`
|
|
2
|
+
-- (scheduled tasks spec §11), distinct from `target_gone`: the task stays active and recovers when
|
|
3
|
+
-- the extension is enabled again. Widening the inline CHECK of 0113 (PG's default name
|
|
4
|
+
-- scheduled_task_runs_skip_reason_check): drop and add in the file's one transaction, idempotent.
|
|
5
|
+
ALTER TABLE scheduled_task_runs DROP CONSTRAINT IF EXISTS scheduled_task_runs_skip_reason_check;
|
|
6
|
+
ALTER TABLE scheduled_task_runs ADD CONSTRAINT scheduled_task_runs_skip_reason_check
|
|
7
|
+
CHECK (skip_reason IN ('missed', 'overlap', 'rate_limited', 'queue_paused',
|
|
8
|
+
'removed', 'device_offline', 'creator_inactive', 'agent_gone', 'target_gone',
|
|
9
|
+
'conversation_gone', 'org_disabled', 'unsupported_target', 'task_deleted', 'task_paused',
|
|
10
|
+
'plugin_disabled'));
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
-- A connector's plugin was switched off in its org: the connector is kept with its endpoint and
|
|
2
|
+
-- credentials but disabled, and marked with the plugin (the catalog item) that switched it off, so
|
|
3
|
+
-- switching the plugin back on re-enables exactly the connectors it disabled. A connector an org
|
|
4
|
+
-- admin disabled by hand has no marker (null) and stays disabled.
|
|
5
|
+
ALTER TABLE connectors ADD COLUMN IF NOT EXISTS disabled_by_plugin_id uuid;
|