@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,209 @@
|
|
|
1
|
+
import { domainToASCII } from "node:url";
|
|
2
|
+
import { ToolError, } from "@capybara-hq/plugin-sdk";
|
|
3
|
+
import { BUILTIN } from "../../builtin-names.js";
|
|
4
|
+
import { webToolError } from "./errors.js";
|
|
5
|
+
/** The extension point name (without the major) the provider is resolved from. */
|
|
6
|
+
const POINT = "web-search";
|
|
7
|
+
/**
|
|
8
|
+
* The fixed spec (web tools spec §3.1). No date and nothing per org or provider: `specHash` stays
|
|
9
|
+
* the same across days and providers, so remembered grants survive both and replays match the log.
|
|
10
|
+
* The date is the first line of every result instead.
|
|
11
|
+
*/
|
|
12
|
+
export const WEB_SEARCH_SPEC = {
|
|
13
|
+
name: BUILTIN.WebSearch,
|
|
14
|
+
// Display only (the tool row; not in the fingerprint, never sent to the model).
|
|
15
|
+
keyArgs: ["query"],
|
|
16
|
+
description: "Search the web and get a list of results with title, URL and a short snippet. Use it for " +
|
|
17
|
+
"information newer than your training data, or to check whether something you know is still " +
|
|
18
|
+
"current. Then use WebFetch to read the pages you need. Do not use it for questions you can " +
|
|
19
|
+
"answer reliably without the web. Cite the sources you use.",
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
query: {
|
|
24
|
+
type: "string",
|
|
25
|
+
minLength: 2,
|
|
26
|
+
maxLength: 500,
|
|
27
|
+
description: "The search query.",
|
|
28
|
+
},
|
|
29
|
+
allowed_domains: {
|
|
30
|
+
type: "array",
|
|
31
|
+
items: { type: "string", minLength: 1, maxLength: 253 },
|
|
32
|
+
maxItems: 50,
|
|
33
|
+
description: "Only return results from these domains (hostnames such as example.com; subdomains " +
|
|
34
|
+
"are included). Do not combine with blocked_domains.",
|
|
35
|
+
},
|
|
36
|
+
blocked_domains: {
|
|
37
|
+
type: "array",
|
|
38
|
+
items: { type: "string", minLength: 1, maxLength: 253 },
|
|
39
|
+
maxItems: 50,
|
|
40
|
+
description: "Never return results from these domains (hostnames such as example.com; subdomains " +
|
|
41
|
+
"are included). Do not combine with allowed_domains.",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
required: ["query"],
|
|
45
|
+
additionalProperties: false,
|
|
46
|
+
},
|
|
47
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
48
|
+
};
|
|
49
|
+
/** The last line of a result with entries (Claude Code tells the model to list its sources). */
|
|
50
|
+
const CITE_LINE = "Cite the sources you use as markdown links in your answer.";
|
|
51
|
+
/** Why the tool is unavailable in an org (web tools spec §3.4; English, actionable). */
|
|
52
|
+
function unavailableText(reason) {
|
|
53
|
+
if (reason === "none") {
|
|
54
|
+
return "No web search provider is enabled in this organization; ask an org admin to enable one.";
|
|
55
|
+
}
|
|
56
|
+
return "Several web search providers are enabled and none is the default; ask an org admin to choose one in the organization settings.";
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A domain entry as a bare lowercase ASCII hostname: tolerates a scheme, a path, a port, a leading
|
|
60
|
+
* `*.` / `.` and a trailing dot; a Unicode (IDN) name becomes its punycode form, as result hosts
|
|
61
|
+
* are (WHATWG URL parsing). Empty when nothing is left.
|
|
62
|
+
*/
|
|
63
|
+
function normalizeDomain(entry) {
|
|
64
|
+
let d = entry.trim().toLowerCase();
|
|
65
|
+
d = d.replace(/^[a-z][a-z0-9+.-]*:\/\//, "");
|
|
66
|
+
d = d.replace(/[/?#].*$/, "").replace(/:\d+$/, "");
|
|
67
|
+
d = d.replace(/^\*\./, "").replace(/^\.+/, "").replace(/\.+$/, "");
|
|
68
|
+
if (d === "")
|
|
69
|
+
return d;
|
|
70
|
+
// domainToASCII is "" for a name IDNA rejects: keep the entry as given (it matches no host)
|
|
71
|
+
return domainToASCII(d) || d;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A domain list normalized; undefined when absent or empty (no filter). A list whose entries all
|
|
75
|
+
* normalize to nothing stays an empty list, which no host matches.
|
|
76
|
+
*/
|
|
77
|
+
const normalizeDomains = (list) => list === undefined || list.length === 0
|
|
78
|
+
? undefined
|
|
79
|
+
: [...new Set(list.map(normalizeDomain).filter((d) => d !== ""))];
|
|
80
|
+
/** Whether `host` is `domain` or one of its subdomains. */
|
|
81
|
+
const covers = (domain, host) => host === domain || host.endsWith(`.${domain}`);
|
|
82
|
+
const oneLine = (s) => s.replace(/\s+/g, " ").trim();
|
|
83
|
+
/**
|
|
84
|
+
* The provider's results the model may see (web tools spec §7.2): an absolute http(s) URL, a
|
|
85
|
+
* non-empty title, each URL once, within the domain lists, at most `max`. The kernel filters even
|
|
86
|
+
* when the provider honored the domain hints.
|
|
87
|
+
*/
|
|
88
|
+
function usable(results, allowed, blocked, max) {
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
const out = [];
|
|
91
|
+
for (const r of results) {
|
|
92
|
+
if (out.length >= max)
|
|
93
|
+
break;
|
|
94
|
+
if (typeof r?.url !== "string" || typeof r.title !== "string")
|
|
95
|
+
continue;
|
|
96
|
+
const title = oneLine(r.title);
|
|
97
|
+
if (title === "")
|
|
98
|
+
continue;
|
|
99
|
+
let u;
|
|
100
|
+
try {
|
|
101
|
+
// no base: a relative URL throws
|
|
102
|
+
u = new URL(r.url.trim());
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (u.protocol !== "https:" && u.protocol !== "http:")
|
|
108
|
+
continue;
|
|
109
|
+
const host = u.hostname.toLowerCase().replace(/\.$/, "");
|
|
110
|
+
if (host === "")
|
|
111
|
+
continue;
|
|
112
|
+
if (allowed !== undefined && !allowed.some((d) => covers(d, host)))
|
|
113
|
+
continue;
|
|
114
|
+
if (blocked?.some((d) => covers(d, host)))
|
|
115
|
+
continue;
|
|
116
|
+
if (seen.has(u.href))
|
|
117
|
+
continue;
|
|
118
|
+
seen.add(u.href);
|
|
119
|
+
const date = typeof r.publishedAt === "string" ? /^\d{4}-\d{2}-\d{2}/.exec(r.publishedAt.trim()) : null;
|
|
120
|
+
const snippet = typeof r.snippet === "string" ? oneLine(r.snippet) : "";
|
|
121
|
+
out.push({
|
|
122
|
+
title,
|
|
123
|
+
url: u.href,
|
|
124
|
+
...(date !== null && { date: date[0] }),
|
|
125
|
+
...(snippet !== "" && { snippet }),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
/** The model-visible result (web tools spec §3.1): the date line first, then the entries. */
|
|
131
|
+
function resultText(day, entries) {
|
|
132
|
+
const head = `Search date: ${day} (UTC).`;
|
|
133
|
+
if (entries.length === 0)
|
|
134
|
+
return `${head}\n\nNo results.`;
|
|
135
|
+
const blocks = entries.map((e, i) => [
|
|
136
|
+
`${i + 1}. ${e.title}`,
|
|
137
|
+
` URL: ${e.url}`,
|
|
138
|
+
...(e.date !== undefined ? [` Published: ${e.date}`] : []),
|
|
139
|
+
...(e.snippet !== undefined ? [` ${e.snippet}`] : []),
|
|
140
|
+
].join("\n"));
|
|
141
|
+
return [head, ...blocks, CITE_LINE].join("\n\n");
|
|
142
|
+
}
|
|
143
|
+
/** The query in the display summary: one line, at most this many code points. */
|
|
144
|
+
const DISPLAY_QUERY_MAX = 120;
|
|
145
|
+
function displayQuery(query) {
|
|
146
|
+
const cps = [...oneLine(query)];
|
|
147
|
+
return cps.length > DISPLAY_QUERY_MAX
|
|
148
|
+
? `${cps.slice(0, DISPLAY_QUERY_MAX - 1).join("")}…`
|
|
149
|
+
: cps.join("");
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The builtin `WebSearch` (web tools spec §3.1): searches with the org's `web-search@1` provider
|
|
153
|
+
* (`ExtensionPointDefaults`, resolved per call and per step, never cached), asking it for
|
|
154
|
+
* `web.search.maxResults` results with the domain lists as hints and the org's config, then
|
|
155
|
+
* filters the results again. Default `ask`; unavailable when the org has no provider or several
|
|
156
|
+
* without a default.
|
|
157
|
+
*/
|
|
158
|
+
export function webSearchTool(deps) {
|
|
159
|
+
const now = deps.now ?? (() => new Date());
|
|
160
|
+
return {
|
|
161
|
+
spec: WEB_SEARCH_SPEC,
|
|
162
|
+
defaultPolicy: "ask",
|
|
163
|
+
async unavailable({ orgId }) {
|
|
164
|
+
const r = await deps.points.resolve(orgId, POINT);
|
|
165
|
+
return r.ok ? null : unavailableText(r.reason);
|
|
166
|
+
},
|
|
167
|
+
async run(input, ctx) {
|
|
168
|
+
const { query, allowed_domains, blocked_domains } = input;
|
|
169
|
+
if (allowed_domains !== undefined && blocked_domains !== undefined) {
|
|
170
|
+
throw new ToolError("Pass either allowed_domains or blocked_domains, not both; call WebSearch again with one of them.");
|
|
171
|
+
}
|
|
172
|
+
const allowed = normalizeDomains(allowed_domains);
|
|
173
|
+
const blocked = normalizeDomains(blocked_domains);
|
|
174
|
+
// the provider may have changed since the step resolved the tool: resolve again
|
|
175
|
+
const resolved = await deps.points.resolve(ctx.orgId, POINT);
|
|
176
|
+
if (!resolved.ok)
|
|
177
|
+
throw new ToolError(unavailableText(resolved.reason));
|
|
178
|
+
const max = deps.config.get().web.search.maxResults;
|
|
179
|
+
const day = now().toISOString().slice(0, 10);
|
|
180
|
+
let results;
|
|
181
|
+
try {
|
|
182
|
+
const out = await resolved.impl.search({
|
|
183
|
+
query,
|
|
184
|
+
...(allowed !== undefined && allowed.length > 0 && { allowedDomains: allowed }),
|
|
185
|
+
...(blocked !== undefined && blocked.length > 0 && { blockedDomains: blocked }),
|
|
186
|
+
maxResults: max,
|
|
187
|
+
orgConfig: resolved.orgConfig,
|
|
188
|
+
signal: ctx.signal,
|
|
189
|
+
});
|
|
190
|
+
results = Array.isArray(out?.results) ? out.results : [];
|
|
191
|
+
}
|
|
192
|
+
catch (e) {
|
|
193
|
+
webToolError(e, "search");
|
|
194
|
+
}
|
|
195
|
+
const entries = usable(results, allowed, blocked, max);
|
|
196
|
+
const q = displayQuery(query);
|
|
197
|
+
const n = entries.length;
|
|
198
|
+
return {
|
|
199
|
+
content: [{ type: "text", text: resultText(day, entries) }],
|
|
200
|
+
display: {
|
|
201
|
+
summary: {
|
|
202
|
+
zh: `搜索网页:${q}(${n} 条结果)`,
|
|
203
|
+
en: `Searched the web: ${q} (${n} result${n === 1 ? "" : "s"})`,
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** The longest URL `WebFetch` accepts, in characters (Claude Code's limit; web tools spec §7.1). */
|
|
2
|
+
export declare const MAX_FETCH_URL_CHARS = 2000;
|
|
3
|
+
/** The outcome of {@link normalizeFetchUrl}: the URL to fetch, or a model-visible error. */
|
|
4
|
+
export type FetchUrlCheck = {
|
|
5
|
+
ok: true;
|
|
6
|
+
url: string;
|
|
7
|
+
} | {
|
|
8
|
+
ok: false;
|
|
9
|
+
error: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* A `WebFetch` URL checked and normalized (web tools spec §7.1): absolute http(s), `http:`
|
|
13
|
+
* upgraded to `https:`, no user name or password, at most {@link MAX_FETCH_URL_CHARS} characters
|
|
14
|
+
* (before and after normalization), and a host that can name a public internet host. Pure, no DNS: a public name that resolves to a
|
|
15
|
+
* private address is the provider's to refuse. Errors are English and tell the model what to pass.
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeFetchUrl(raw: string): FetchUrlCheck;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { isPublicHostname } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
/** The longest URL `WebFetch` accepts, in characters (Claude Code's limit; web tools spec §7.1). */
|
|
3
|
+
export const MAX_FETCH_URL_CHARS = 2000;
|
|
4
|
+
const fail = (error) => ({ ok: false, error });
|
|
5
|
+
/**
|
|
6
|
+
* A `WebFetch` URL checked and normalized (web tools spec §7.1): absolute http(s), `http:`
|
|
7
|
+
* upgraded to `https:`, no user name or password, at most {@link MAX_FETCH_URL_CHARS} characters
|
|
8
|
+
* (before and after normalization), and a host that can name a public internet host. Pure, no DNS: a public name that resolves to a
|
|
9
|
+
* private address is the provider's to refuse. Errors are English and tell the model what to pass.
|
|
10
|
+
*/
|
|
11
|
+
export function normalizeFetchUrl(raw) {
|
|
12
|
+
const input = raw.trim();
|
|
13
|
+
// a cheap guard before parsing; the normalized URL is checked again below
|
|
14
|
+
if (input.length > MAX_FETCH_URL_CHARS) {
|
|
15
|
+
return fail(`Pass a URL of at most ${MAX_FETCH_URL_CHARS} characters.`);
|
|
16
|
+
}
|
|
17
|
+
let u;
|
|
18
|
+
try {
|
|
19
|
+
// no base: a relative URL throws
|
|
20
|
+
u = new URL(input);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return fail("Pass a full absolute URL, such as https://example.com/page.");
|
|
24
|
+
}
|
|
25
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") {
|
|
26
|
+
return fail("Pass an https URL; only http and https URLs can be fetched.");
|
|
27
|
+
}
|
|
28
|
+
if (u.username !== "" || u.password !== "") {
|
|
29
|
+
return fail("Pass the URL without a user name or password.");
|
|
30
|
+
}
|
|
31
|
+
u.protocol = "https:";
|
|
32
|
+
// `u.hostname` is WHATWG-normalized (lowercased, IDNA, numeric IPv4 forms such as `0x7f.0.0.1`
|
|
33
|
+
// rewritten to dotted decimal, IPv6 in brackets), which is what isPublicHostname expects: never
|
|
34
|
+
// pass it a hand-parsed host.
|
|
35
|
+
if (!isPublicHostname(u.hostname)) {
|
|
36
|
+
return fail("Pass a full public https URL; this host is private or local.");
|
|
37
|
+
}
|
|
38
|
+
// the limit is on the URL as fetched: the upgrade, IDNA and percent-encoding can lengthen it
|
|
39
|
+
if (u.href.length > MAX_FETCH_URL_CHARS) {
|
|
40
|
+
return fail(`Pass a URL of at most ${MAX_FETCH_URL_CHARS} characters.`);
|
|
41
|
+
}
|
|
42
|
+
return { ok: true, url: u.href };
|
|
43
|
+
}
|
|
@@ -6,7 +6,7 @@ export declare const OAUTH_STATE_TTL_SECONDS = 600;
|
|
|
6
6
|
/** 浏览器绑定 Cookie:回调必须来自发起连接的同一个浏览器。 */
|
|
7
7
|
export declare const OAUTH_BINDING_COOKIE = "cap_oauth";
|
|
8
8
|
/** 回调失败、或连接时没给回跳页面时回到的网页(next-intl 会补语言前缀)。 */
|
|
9
|
-
export declare const DEFAULT_RETURN_PATH = "/
|
|
9
|
+
export declare const DEFAULT_RETURN_PATH = "/account/connections";
|
|
10
10
|
/** OAuth 连接器需要 `http.publicUrl`(回调地址);未配置 → 这条校验错误。 */
|
|
11
11
|
export declare const publicUrlRequired: () => ApiError;
|
|
12
12
|
export declare function callbackUrl(publicUrl: string): string;
|
|
@@ -8,7 +8,7 @@ export const OAUTH_STATE_TTL_SECONDS = 600;
|
|
|
8
8
|
/** 浏览器绑定 Cookie:回调必须来自发起连接的同一个浏览器。 */
|
|
9
9
|
export const OAUTH_BINDING_COOKIE = "cap_oauth";
|
|
10
10
|
/** 回调失败、或连接时没给回跳页面时回到的网页(next-intl 会补语言前缀)。 */
|
|
11
|
-
export const DEFAULT_RETURN_PATH = "/
|
|
11
|
+
export const DEFAULT_RETURN_PATH = "/account/connections";
|
|
12
12
|
/** OAuth 连接器需要 `http.publicUrl`(回调地址);未配置 → 这条校验错误。 */
|
|
13
13
|
export const publicUrlRequired = () => new ApiError("validation_failed", "Request validation failed", [
|
|
14
14
|
{
|
package/dist/tools/pipeline.js
CHANGED
|
@@ -985,6 +985,21 @@ let ToolPipeline = class ToolPipeline {
|
|
|
985
985
|
// a sub-agent works in its parent conversation's sandbox (spec §10.1)
|
|
986
986
|
...(tool.source.kind === "builtin" &&
|
|
987
987
|
ctx.parent !== undefined && { sandboxConversationId: ctx.parent.conversationId }),
|
|
988
|
+
...(tool.source.kind === "builtin" &&
|
|
989
|
+
ctx.parent !== undefined && { parentRunId: ctx.parent.runId }),
|
|
990
|
+
// the hook scope of a builtin's own model call (the WebFetch extraction)
|
|
991
|
+
...(tool.source.kind === "builtin" && {
|
|
992
|
+
step: ctx.step,
|
|
993
|
+
agentVersion: ctx.agentVersion,
|
|
994
|
+
...(ctx.conversationKind !== undefined && { conversationKind: ctx.conversationKind }),
|
|
995
|
+
...(ctx.parent !== undefined && {
|
|
996
|
+
parent: {
|
|
997
|
+
conversationId: ctx.parent.conversationId,
|
|
998
|
+
runId: ctx.parent.runId,
|
|
999
|
+
callId: ctx.parent.callId,
|
|
1000
|
+
},
|
|
1001
|
+
}),
|
|
1002
|
+
}),
|
|
988
1003
|
};
|
|
989
1004
|
// 内置工具可按输入给自己的时限(沙箱 bash 按命令超时加余量);缺省 tools.timeoutMs
|
|
990
1005
|
const timeoutMs = tool.timeoutMs?.(input) ?? this.config.get().tools.timeoutMs;
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Connector, InputRequired, ToolAnnotations, ToolCallContext, ToolResult, ToolSpec } from "@capybara-hq/plugin-sdk";
|
|
1
|
+
import type { Connector, HookParentScope, InputRequired, ToolAnnotations, ToolCallContext, ToolResult, ToolSpec } from "@capybara-hq/plugin-sdk";
|
|
2
2
|
import type { AgentDefinition } from "../agents/definition.js";
|
|
3
3
|
import type { ToolView } from "../conversations/events.js";
|
|
4
4
|
import { ExtensionHost } from "../extensions/host.js";
|
|
@@ -6,6 +6,7 @@ import { ExtensionRegistry } from "../extensions/registry.js";
|
|
|
6
6
|
import { KernelConfigService } from "../infra/config/kernel-config.service.js";
|
|
7
7
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
8
8
|
import type { OrgRole } from "../orgs/roles.js";
|
|
9
|
+
import type { ConversationKindForHooks } from "../runtime/hook-calls.js";
|
|
9
10
|
import { BuiltinTools } from "./builtins/builtin-tools.js";
|
|
10
11
|
export { BUILTIN_TOOL_NAMES } from "./builtin-names.js";
|
|
11
12
|
export { canonicalJson } from "./canonical-json.js";
|
|
@@ -32,6 +33,18 @@ export interface BuiltinCallContext extends ToolCallContext {
|
|
|
32
33
|
* for a sub-agent's call, the conversation itself otherwise (the default when absent).
|
|
33
34
|
*/
|
|
34
35
|
sandboxConversationId?: string;
|
|
36
|
+
/** The parent run of a sub-agent's run (absent for a top-level run). */
|
|
37
|
+
parentRunId?: string;
|
|
38
|
+
/**
|
|
39
|
+
* The rest of the run's hook scope, for a builtin that makes a model call of its own (the
|
|
40
|
+
* `WebFetch` extraction fires `model.request` / `model.response`): the step of the call, the
|
|
41
|
+
* agent version of the run, the conversation kind (hooks fire for `chat` and `subagent` only)
|
|
42
|
+
* and, for a sub-agent, where it was dispatched from.
|
|
43
|
+
*/
|
|
44
|
+
step?: number;
|
|
45
|
+
agentVersion?: number;
|
|
46
|
+
conversationKind?: ConversationKindForHooks;
|
|
47
|
+
parent?: HookParentScope;
|
|
35
48
|
}
|
|
36
49
|
/**
|
|
37
50
|
* 内置工具的结果:`ToolResult` 之外可带 `loadedTools`(`ToolSearch` 加载的工具 key,
|
|
@@ -58,8 +71,14 @@ export interface BuiltinTool {
|
|
|
58
71
|
timeoutMs?: (input: unknown) => number;
|
|
59
72
|
/** 结果转存阈值(字符):与 `tools.outputSpillChars` 取小者。缺省只用配置。 */
|
|
60
73
|
spillChars?: number;
|
|
61
|
-
/**
|
|
62
|
-
|
|
74
|
+
/**
|
|
75
|
+
* The factual reason this tool is unavailable for the org right now (English, actionable; it goes
|
|
76
|
+
* into the "tool unavailable" reminder), or null when it is available. May be async (e.g. a
|
|
77
|
+
* per-org extension-point default).
|
|
78
|
+
*/
|
|
79
|
+
unavailable?: (ctx: {
|
|
80
|
+
orgId: string;
|
|
81
|
+
}) => Promise<string | null> | string | null;
|
|
63
82
|
}
|
|
64
83
|
/**
|
|
65
84
|
* agent 定义里对工具的引用(Task 6 在 `AgentDefinitionSchema` 中落地同形的 zod schema)。
|
package/dist/tools/registry.js
CHANGED
|
@@ -18,7 +18,7 @@ import { readOrgSettings } from "../orgs/settings.service.js";
|
|
|
18
18
|
import { estimateTokens } from "../runtime/context/tokens.js";
|
|
19
19
|
import { SUBAGENT_LIMIT_DEFAULTS, subagentLimits } from "../runtime/subagents/limits.js";
|
|
20
20
|
import { resolveRoster } from "../runtime/subagents/roster.js";
|
|
21
|
-
import { AGENT_TOOL, BUILTIN_TOOL_NAMES, SKILL_LOAD, TOOL_SEARCH } from "./builtin-names.js";
|
|
21
|
+
import { AGENT_TOOL, BASH_COMPANION_TOOLS, BUILTIN, BUILTIN_TOOL_NAMES, SKILL_LOAD, TOOL_SEARCH, } from "./builtin-names.js";
|
|
22
22
|
import { agentToolSpec } from "./builtins/agent.js";
|
|
23
23
|
import { BuiltinTools } from "./builtins/builtin-tools.js";
|
|
24
24
|
import { canonicalJson } from "./canonical-json.js";
|
|
@@ -71,6 +71,16 @@ const sourceId = (s) => s.kind === "builtin"
|
|
|
71
71
|
: s.kind === "plugin"
|
|
72
72
|
? `plugin:${s.plugin}:${s.name}`
|
|
73
73
|
: `connector:${s.connectorId}:${s.name}`;
|
|
74
|
+
/** A builtin as a resolved tool (with `spec`, which `Agent` builds per definition). */
|
|
75
|
+
const builtinTool = (b, spec) => ({
|
|
76
|
+
key: b.spec.name,
|
|
77
|
+
source: { kind: "builtin", name: b.spec.name },
|
|
78
|
+
spec,
|
|
79
|
+
specHash: specHash(spec),
|
|
80
|
+
defaultPolicy: b.defaultPolicy,
|
|
81
|
+
...(b.timeoutMs !== undefined && { timeoutMs: b.timeoutMs }),
|
|
82
|
+
...(b.spillChars !== undefined && { spillChars: b.spillChars }),
|
|
83
|
+
});
|
|
74
84
|
/**
|
|
75
85
|
* Tool registry: resolves the references in an agent definition into the tools available for this
|
|
76
86
|
* run. Not cached (reads PG and the host live every time), so changes take effect right away across
|
|
@@ -201,6 +211,11 @@ let ToolRegistry = class ToolRegistry {
|
|
|
201
211
|
return;
|
|
202
212
|
}
|
|
203
213
|
claim(path, ref.name, `builtin:${ref.name}`);
|
|
214
|
+
// offered with Bash (see resolve), so their policies validate too
|
|
215
|
+
if (ref.name === BUILTIN.Bash) {
|
|
216
|
+
for (const c of BASH_COMPANION_TOOLS)
|
|
217
|
+
claim(path, c, `builtin:${c}`);
|
|
218
|
+
}
|
|
204
219
|
}
|
|
205
220
|
else if (ref.kind === "plugin") {
|
|
206
221
|
const key = extensionToolKey(ref.plugin, ref.name);
|
|
@@ -316,7 +331,7 @@ let ToolRegistry = class ToolRegistry {
|
|
|
316
331
|
unavailable.push({ ref: ref.name, reason: "Built-in tool does not exist" });
|
|
317
332
|
continue;
|
|
318
333
|
}
|
|
319
|
-
const why = b.unavailable?.() ?? null;
|
|
334
|
+
const why = (await b.unavailable?.({ orgId })) ?? null;
|
|
320
335
|
if (why !== null) {
|
|
321
336
|
unavailable.push({ ref: ref.name, reason: why });
|
|
322
337
|
continue;
|
|
@@ -330,15 +345,15 @@ let ToolRegistry = class ToolRegistry {
|
|
|
330
345
|
continue;
|
|
331
346
|
spec = agentToolSpec(roster, await this.maxConcurrentSubagents(orgId));
|
|
332
347
|
}
|
|
333
|
-
add(
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
348
|
+
add(builtinTool(b, spec));
|
|
349
|
+
// `TaskOutput` and `TaskStop` come with an available `Bash` (background Bash spec §2)
|
|
350
|
+
if (b.spec.name === BUILTIN.Bash) {
|
|
351
|
+
for (const name of BASH_COMPANION_TOOLS) {
|
|
352
|
+
const c = this.builtins.find((x) => x.spec.name === name);
|
|
353
|
+
if (c && ((await c.unavailable?.({ orgId })) ?? null) === null)
|
|
354
|
+
add(builtinTool(c, c.spec));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
342
357
|
}
|
|
343
358
|
else if (ref.kind === "plugin") {
|
|
344
359
|
const key = extensionToolKey(ref.plugin, ref.name);
|
|
@@ -5,20 +5,25 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
7
|
import { Module } from "@nestjs/common";
|
|
8
|
+
import { BackgroundModule } from "../background/background.module.js";
|
|
8
9
|
import { ConversationAccessModule } from "../conversations/access.js";
|
|
9
10
|
import { AttachmentsModule } from "../conversations/attachments.module.js";
|
|
10
11
|
import { EventLogModule } from "../conversations/event-log.js";
|
|
11
12
|
import { ExtensionsModule } from "../extensions/extensions.module.js";
|
|
12
13
|
import { QueueModule } from "../infra/queue/queue.module.js";
|
|
14
|
+
import { ModelsModule } from "../models/models.module.js";
|
|
13
15
|
import { OrgLockModule } from "../orgs/org-lock.js";
|
|
14
16
|
import { MAINTENANCE_QUEUE_NAME } from "../runtime/maintenance-queue.js";
|
|
17
|
+
import { StepLogModule } from "../runtime/step-log.js";
|
|
15
18
|
import { SubagentsModule } from "../runtime/subagents/subagents.service.js";
|
|
19
|
+
import { UsageReportModule } from "../runtime/usage-report.js";
|
|
16
20
|
import { SandboxModule } from "../sandbox/sandbox.module.js";
|
|
17
21
|
import { SkillsModule } from "../skills/skills.module.js";
|
|
18
22
|
import { StorageModule } from "../storage/storage.module.js";
|
|
19
23
|
import { ApprovalsService } from "./approvals.service.js";
|
|
20
24
|
import { Breaker } from "./breaker.js";
|
|
21
25
|
import { BuiltinTools } from "./builtins/builtin-tools.js";
|
|
26
|
+
import { WebPages } from "./builtins/web/page-cache.js";
|
|
22
27
|
import { ConnectorContent } from "./connector-content.js";
|
|
23
28
|
import { ConnectorsService } from "./connectors.service.js";
|
|
24
29
|
import { Credentials } from "./credentials.js";
|
|
@@ -52,14 +57,21 @@ ToolsModule = __decorate([
|
|
|
52
57
|
SubagentsModule,
|
|
53
58
|
// the builtin sandbox and skill tools run in the conversation's sandbox
|
|
54
59
|
SandboxModule,
|
|
60
|
+
// `Bash` with run_in_background records its task; TaskOutput / TaskStop finish and stop it
|
|
61
|
+
BackgroundModule,
|
|
55
62
|
// the conversation page's tool metadata reads only what the viewer may see
|
|
56
63
|
ConversationAccessModule,
|
|
64
|
+
// the WebFetch extraction call: its purpose, usage report and step writes
|
|
65
|
+
ModelsModule,
|
|
66
|
+
UsageReportModule,
|
|
67
|
+
StepLogModule,
|
|
57
68
|
QueueModule.registerQueue(MAINTENANCE_QUEUE_NAME),
|
|
58
69
|
],
|
|
59
70
|
providers: [
|
|
60
71
|
Credentials,
|
|
61
72
|
ToolOutputs,
|
|
62
73
|
BuiltinTools,
|
|
74
|
+
WebPages,
|
|
63
75
|
ToolRegistry,
|
|
64
76
|
ApprovalsService,
|
|
65
77
|
QuestionsService,
|
package/dist/tools/validate.js
CHANGED
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.4",
|
|
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": {
|
|
@@ -64,14 +64,14 @@
|
|
|
64
64
|
"yaml": "^2.9.1",
|
|
65
65
|
"yauzl": "3.4.0",
|
|
66
66
|
"zod": "^4.6.7",
|
|
67
|
-
"@capybara-hq/plugin-sdk": "^0.1.0-rc.
|
|
67
|
+
"@capybara-hq/plugin-sdk": "^0.1.0-rc.2"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@langchain/langgraph-checkpoint-validation": "^1.2.0",
|
|
71
71
|
"@types/pg": "^8.23.1",
|
|
72
72
|
"@types/semver": "^7.8.0",
|
|
73
73
|
"@types/yauzl": "3.4.0",
|
|
74
|
-
"@capybara-hq/testing": "0.1.0-rc.
|
|
74
|
+
"@capybara-hq/testing": "0.1.0-rc.4"
|
|
75
75
|
},
|
|
76
76
|
"scripts": {
|
|
77
77
|
"build": "tsc -b"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- The organization an account last chose as its current one: a new web session (login, SSO, setup,
|
|
2
|
+
-- an accepted invitation) starts in it while the account is still a member and the org is not
|
|
3
|
+
-- deleted, so people don't pick their org again at every sign-in. Written with the session's
|
|
4
|
+
-- current org by `PUT /me/current-org`; NULL = never chosen (a new session then starts in the only
|
|
5
|
+
-- org when there is exactly one). Additive: older replicas never read or write it.
|
|
6
|
+
ALTER TABLE capybara.accounts
|
|
7
|
+
ADD COLUMN IF NOT EXISTS last_org_id text REFERENCES capybara.orgs(id) ON DELETE SET NULL;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- A person archives a conversation (hidden from the default lists, still readable and unarchived
|
|
2
|
+
-- by a new message) or deletes it (soft: hidden everywhere for the owner; the event log, runs and
|
|
3
|
+
-- trajectories stay for enterprise admins).
|
|
4
|
+
ALTER TABLE capybara.conversations ADD COLUMN IF NOT EXISTS archived_at timestamptz;
|
|
5
|
+
ALTER TABLE capybara.conversations ADD COLUMN IF NOT EXISTS deleted_at timestamptz;
|
|
6
|
+
CREATE INDEX IF NOT EXISTS conversations_live_idx
|
|
7
|
+
ON capybara.conversations (org_id, account_id, updated_at DESC)
|
|
8
|
+
WHERE deleted_at IS NULL AND archived_at IS NULL;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
-- The plugin.json `displayName` ({ zh, en }) of an extension package, written when the package is
|
|
2
|
+
-- stored; NULL when the plugin has none and for packages stored before this migration (no
|
|
3
|
+
-- backfill: a name arrives with the next version). Additive: older replicas never read it.
|
|
4
|
+
ALTER TABLE capybara.extension_packages ADD COLUMN IF NOT EXISTS display_name jsonb;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-- The agent profile (avatar, description, quick actions): display data stored beside the agent,
|
|
2
|
+
-- outside agent_versions, so changing it creates no version. Expand-only.
|
|
3
|
+
-- profile_updated_at is kept at millisecond precision so a client can send it back as expectedUpdatedAt.
|
|
4
|
+
ALTER TABLE capybara.agents ADD COLUMN IF NOT EXISTS profile_avatar jsonb;
|
|
5
|
+
ALTER TABLE capybara.agents ADD COLUMN IF NOT EXISTS profile_description text NOT NULL DEFAULT '';
|
|
6
|
+
ALTER TABLE capybara.agents
|
|
7
|
+
ADD COLUMN IF NOT EXISTS profile_quick_actions jsonb NOT NULL DEFAULT '[]';
|
|
8
|
+
ALTER TABLE capybara.agents
|
|
9
|
+
ADD COLUMN IF NOT EXISTS profile_updated_at timestamptz NOT NULL
|
|
10
|
+
DEFAULT date_trunc('milliseconds', now());
|
|
11
|
+
|
|
12
|
+
-- A definition's avatarUrl becomes an external avatar (once: only where none is set yet).
|
|
13
|
+
UPDATE capybara.agents a
|
|
14
|
+
SET profile_avatar = jsonb_build_object('kind', 'external', 'url', v.definition->>'avatarUrl')
|
|
15
|
+
FROM capybara.agent_versions v
|
|
16
|
+
WHERE v.agent_id = a.id
|
|
17
|
+
AND v.version = a.current_version
|
|
18
|
+
AND a.profile_avatar IS NULL
|
|
19
|
+
AND jsonb_typeof(v.definition->'avatarUrl') = 'string'
|
|
20
|
+
AND v.definition->>'avatarUrl' <> '';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- Agent avatar uploads reuse the presigned attachment flow: a new purpose 'agent-avatar' and the
|
|
2
|
+
-- agent whose profile uses the upload (set once on save; a used upload has no expiry).
|
|
3
|
+
ALTER TABLE capybara.attachments DROP CONSTRAINT IF EXISTS attachments_purpose_check;
|
|
4
|
+
ALTER TABLE capybara.attachments
|
|
5
|
+
ADD CONSTRAINT attachments_purpose_check
|
|
6
|
+
CHECK (purpose IN ('chat', 'skill-package', 'agent-avatar'));
|
|
7
|
+
ALTER TABLE capybara.attachments ADD COLUMN IF NOT EXISTS agent_id text;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
-- The agent avatar lookup and the cleanup of an agent's uploads filter attachments by agent_id;
|
|
2
|
+
-- a partial index keeps it small, since most attachments belong to no agent.
|
|
3
|
+
CREATE INDEX IF NOT EXISTS attachments_agent_idx ON capybara.attachments (agent_id) WHERE agent_id IS NOT NULL;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
-- Background Bash (spec 2026-11-05-background-bash-design §4.1): a command started with
|
|
2
|
+
-- run_in_background keeps running in the conversation's sandbox after its tool call returns. One
|
|
3
|
+
-- row per task; the owner is `conversation_id` (the child conversation for a sub-agent) and
|
|
4
|
+
-- `sandbox_conversation_id` is the root conversation that owns the sandbox. Rows go with their
|
|
5
|
+
-- conversations; finished rows are pruned after tools.outputRetentionDays.
|
|
6
|
+
CREATE TABLE IF NOT EXISTS capybara.background_tasks (
|
|
7
|
+
id text PRIMARY KEY,
|
|
8
|
+
org_id text NOT NULL,
|
|
9
|
+
conversation_id text NOT NULL REFERENCES capybara.conversations(id) ON DELETE CASCADE,
|
|
10
|
+
sandbox_conversation_id text NOT NULL REFERENCES capybara.conversations(id) ON DELETE CASCADE,
|
|
11
|
+
run_id text NOT NULL,
|
|
12
|
+
call_id text NOT NULL,
|
|
13
|
+
trigger_account_id text NOT NULL,
|
|
14
|
+
description text NOT NULL,
|
|
15
|
+
command text NOT NULL,
|
|
16
|
+
-- the session id: the pid `setsid` started; stopping and liveness go by the session
|
|
17
|
+
sid text NOT NULL,
|
|
18
|
+
-- relative to /workspace
|
|
19
|
+
output_path text NOT NULL,
|
|
20
|
+
status text NOT NULL DEFAULT 'running'
|
|
21
|
+
CHECK (status IN ('running', 'completed', 'failed', 'stopped', 'timed_out', 'lost')),
|
|
22
|
+
lost_reason text,
|
|
23
|
+
exit_code integer,
|
|
24
|
+
started_at timestamptz NOT NULL DEFAULT now(),
|
|
25
|
+
finished_at timestamptz,
|
|
26
|
+
deadline_at timestamptz NOT NULL,
|
|
27
|
+
-- the sandbox instance the task runs in (the handle plus created_at identify an instance)
|
|
28
|
+
sandbox_created_at timestamptz NOT NULL,
|
|
29
|
+
-- a task marked lost while its process may still exist
|
|
30
|
+
kill_pending boolean NOT NULL DEFAULT false,
|
|
31
|
+
-- the bytes TaskOutput has already returned
|
|
32
|
+
read_offset bigint NOT NULL DEFAULT 0
|
|
33
|
+
);
|
|
34
|
+
CREATE INDEX IF NOT EXISTS background_tasks_running_idx
|
|
35
|
+
ON capybara.background_tasks (sandbox_conversation_id) WHERE status = 'running';
|
|
36
|
+
CREATE INDEX IF NOT EXISTS background_tasks_conversation_idx
|
|
37
|
+
ON capybara.background_tasks (conversation_id, started_at DESC);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
-- Background Bash: a launch reserves its row (status running, counted against
|
|
2
|
+
-- sandbox.background.maxTasks under a transaction-scoped lock) before the launch exec, and sets
|
|
3
|
+
-- sid and sandbox_created_at once the command started. A row without a sid is a reservation: the
|
|
4
|
+
-- poller skips it, and a failed launch finishes it as failed.
|
|
5
|
+
ALTER TABLE IF EXISTS capybara.background_tasks ALTER COLUMN sid DROP NOT NULL;
|
|
6
|
+
ALTER TABLE IF EXISTS capybara.background_tasks ALTER COLUMN sandbox_created_at DROP NOT NULL;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
-- Background Bash: a stop claims its task before it signals the session (stopping_at), so the
|
|
2
|
+
-- poller, which skips a claimed task, cannot finish it meanwhile from what the signals leave
|
|
3
|
+
-- behind (no process, or the wrapper's exit status); the stop's own finish is then the only one.
|
|
4
|
+
-- A launch that finds its reservation claimed kills the command it started at once.
|
|
5
|
+
ALTER TABLE IF EXISTS capybara.background_tasks ADD COLUMN IF NOT EXISTS stopping_at timestamptz;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Web tools (spec §6): an org's default provider of an extension point with `selection:
|
|
2
|
+
-- "org-default"` (`point` is the name without the major, e.g. `web-search`). A row naming an
|
|
3
|
+
-- extension that is disabled or uninstalled stays and is ignored until it is a candidate again.
|
|
4
|
+
CREATE TABLE IF NOT EXISTS capybara.org_extension_point_defaults (
|
|
5
|
+
org_id text NOT NULL REFERENCES capybara.orgs(id) ON DELETE CASCADE,
|
|
6
|
+
point text NOT NULL,
|
|
7
|
+
extension text NOT NULL,
|
|
8
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
9
|
+
updated_by text REFERENCES capybara.accounts(id) ON DELETE SET NULL,
|
|
10
|
+
PRIMARY KEY (org_id, point)
|
|
11
|
+
);
|