@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.
Files changed (226) hide show
  1. package/dist/admin/admin.controller.d.ts +9 -4
  2. package/dist/admin/admin.controller.js +13 -4
  3. package/dist/admin/extensions.controller.d.ts +8 -1
  4. package/dist/admin/extensions.controller.js +22 -2
  5. package/dist/agents/agent-lookup.d.ts +3 -0
  6. package/dist/agents/agent-lookup.js +10 -0
  7. package/dist/agents/agent-profile.service.d.ts +29 -0
  8. package/dist/agents/agent-profile.service.js +117 -0
  9. package/dist/agents/agents.controller.d.ts +93 -3
  10. package/dist/agents/agents.controller.js +114 -4
  11. package/dist/agents/agents.module.js +6 -2
  12. package/dist/agents/agents.service.d.ts +13 -5
  13. package/dist/agents/agents.service.js +30 -7
  14. package/dist/agents/definition.d.ts +4 -0
  15. package/dist/agents/definition.js +10 -0
  16. package/dist/agents/instruction-files.d.ts +60 -0
  17. package/dist/agents/instruction-files.js +111 -0
  18. package/dist/agents/models.controller.d.ts +9 -3
  19. package/dist/agents/models.controller.js +19 -4
  20. package/dist/agents/profile.d.ts +96 -0
  21. package/dist/agents/profile.js +51 -0
  22. package/dist/agents/readiness.controller.d.ts +26 -0
  23. package/dist/agents/readiness.controller.js +56 -0
  24. package/dist/agents/templates.js +1 -1
  25. package/dist/background/background.controller.d.ts +38 -0
  26. package/dist/background/background.controller.js +87 -0
  27. package/dist/background/background.module.d.ts +10 -0
  28. package/dist/background/background.module.js +32 -0
  29. package/dist/background/finish.d.ts +43 -0
  30. package/dist/background/finish.js +94 -0
  31. package/dist/background/launch-lock.d.ts +14 -0
  32. package/dist/background/launch-lock.js +39 -0
  33. package/dist/background/launch.d.ts +36 -0
  34. package/dist/background/launch.js +70 -0
  35. package/dist/background/poll.job.d.ts +53 -0
  36. package/dist/background/poll.job.js +182 -0
  37. package/dist/background/render.d.ts +23 -0
  38. package/dist/background/render.js +48 -0
  39. package/dist/background/session.d.ts +37 -0
  40. package/dist/background/session.js +66 -0
  41. package/dist/background/status.d.ts +35 -0
  42. package/dist/background/status.js +57 -0
  43. package/dist/background/stop.d.ts +62 -0
  44. package/dist/background/stop.js +225 -0
  45. package/dist/background/store.d.ts +150 -0
  46. package/dist/background/store.js +351 -0
  47. package/dist/background/store.module.d.ts +6 -0
  48. package/dist/background/store.module.js +22 -0
  49. package/dist/background/tasks.service.d.ts +67 -0
  50. package/dist/background/tasks.service.js +186 -0
  51. package/dist/background/wake-rules.d.ts +57 -0
  52. package/dist/background/wake-rules.js +83 -0
  53. package/dist/background/wake.d.ts +28 -0
  54. package/dist/background/wake.js +57 -0
  55. package/dist/catalog/claude/agents.js +2 -0
  56. package/dist/catalog/marketplaces.service.d.ts +1 -1
  57. package/dist/catalog/plugin-import.js +1 -1
  58. package/dist/catalog/plugin-upgrade.js +1 -1
  59. package/dist/channels/cards.js +1 -1
  60. package/dist/channels/connections.service.d.ts +16 -1
  61. package/dist/channels/connections.service.js +38 -4
  62. package/dist/channels/threads.d.ts +5 -0
  63. package/dist/channels/threads.js +8 -0
  64. package/dist/config/schema.d.ts +18 -0
  65. package/dist/config/schema.js +102 -0
  66. package/dist/conversations/access.js +2 -0
  67. package/dist/conversations/attachments.service.d.ts +30 -0
  68. package/dist/conversations/attachments.service.js +130 -5
  69. package/dist/conversations/conversations.controller.d.ts +36 -5
  70. package/dist/conversations/conversations.controller.js +62 -12
  71. package/dist/conversations/conversations.module.js +3 -0
  72. package/dist/conversations/conversations.service.d.ts +44 -2
  73. package/dist/conversations/conversations.service.js +140 -12
  74. package/dist/conversations/event-log.d.ts +1 -1
  75. package/dist/conversations/event-log.js +10 -1
  76. package/dist/conversations/events.d.ts +58 -0
  77. package/dist/conversations/events.js +57 -2
  78. package/dist/conversations/queue.d.ts +3 -1
  79. package/dist/conversations/queue.js +13 -2
  80. package/dist/conversations/queue.service.js +3 -1
  81. package/dist/conversations/titles.d.ts +7 -0
  82. package/dist/conversations/titles.js +15 -0
  83. package/dist/db/kernel-schema.d.ts +58 -2
  84. package/dist/db/pool.d.ts +5 -0
  85. package/dist/db/pool.js +3 -0
  86. package/dist/errors.d.ts +8 -0
  87. package/dist/errors.js +8 -0
  88. package/dist/evals/run-replay.d.ts +5 -2
  89. package/dist/evals/run-replay.js +30 -7
  90. package/dist/evals/step-replay.js +12 -6
  91. package/dist/evals/trajectory.js +1 -1
  92. package/dist/extensions/admin-ops.js +2 -0
  93. package/dist/extensions/admin-view.d.ts +3 -1
  94. package/dist/extensions/admin-view.js +10 -2
  95. package/dist/extensions/blobs.js +1 -0
  96. package/dist/extensions/config-issues.d.ts +13 -1
  97. package/dist/extensions/config-issues.js +22 -5
  98. package/dist/extensions/config-json-schema.d.ts +43 -7
  99. package/dist/extensions/config-json-schema.js +124 -58
  100. package/dist/extensions/deployment-config-store.d.ts +6 -0
  101. package/dist/extensions/deployment-config.d.ts +50 -3
  102. package/dist/extensions/deployment-config.js +125 -28
  103. package/dist/extensions/display-name.d.ts +22 -0
  104. package/dist/extensions/display-name.js +77 -0
  105. package/dist/extensions/extensions.module.js +6 -0
  106. package/dist/extensions/host.d.ts +26 -1
  107. package/dist/extensions/host.js +82 -9
  108. package/dist/extensions/org-config.controller.d.ts +11 -3
  109. package/dist/extensions/org-config.controller.js +14 -6
  110. package/dist/extensions/org-config.d.ts +6 -0
  111. package/dist/extensions/org-config.js +12 -13
  112. package/dist/extensions/package.d.ts +14 -1
  113. package/dist/extensions/package.js +51 -2
  114. package/dist/extensions/permission-view.d.ts +3 -0
  115. package/dist/extensions/point-defaults.controller.d.ts +34 -0
  116. package/dist/extensions/point-defaults.controller.js +71 -0
  117. package/dist/extensions/point-defaults.d.ts +64 -0
  118. package/dist/extensions/point-defaults.js +206 -0
  119. package/dist/extensions/registry.d.ts +1 -1
  120. package/dist/extensions/registry.js +2 -0
  121. package/dist/http/api.module.js +8 -0
  122. package/dist/http/nest-logger.d.ts +6 -5
  123. package/dist/http/nest-logger.js +24 -10
  124. package/dist/identity/auth.controller.js +9 -5
  125. package/dist/identity/cookies.d.ts +20 -5
  126. package/dist/identity/cookies.js +44 -6
  127. package/dist/identity/me.controller.d.ts +5 -1
  128. package/dist/identity/me.controller.js +12 -3
  129. package/dist/identity/sessions.d.ts +12 -0
  130. package/dist/identity/sessions.js +45 -0
  131. package/dist/index.d.ts +1 -1
  132. package/dist/infra/config/kernel-config.service.js +6 -0
  133. package/dist/infra/database/database.module.js +1 -0
  134. package/dist/infra/events/kernel-events.d.ts +8 -0
  135. package/dist/infra/lifecycle/process-guards.js +4 -0
  136. package/dist/infra/logging/logging.module.d.ts +4 -2
  137. package/dist/infra/logging/logging.module.js +24 -6
  138. package/dist/infra/queue/redis-connection.d.ts +6 -0
  139. package/dist/infra/queue/redis-connection.js +7 -0
  140. package/dist/infra/redis/redis.module.js +6 -2
  141. package/dist/infra/tokens.d.ts +6 -1
  142. package/dist/infra/tokens.js +6 -1
  143. package/dist/log.d.ts +13 -1
  144. package/dist/log.js +93 -17
  145. package/dist/models/purposes.d.ts +23 -0
  146. package/dist/models/purposes.js +31 -10
  147. package/dist/orgs/offboarding.d.ts +3 -1
  148. package/dist/orgs/offboarding.js +26 -16
  149. package/dist/orgs/settings.service.d.ts +5 -0
  150. package/dist/orgs/settings.service.js +13 -2
  151. package/dist/roles/worker.module.js +6 -0
  152. package/dist/runtime/context/build.d.ts +9 -7
  153. package/dist/runtime/context/build.js +27 -19
  154. package/dist/runtime/context/claude-tools.d.ts +2 -1
  155. package/dist/runtime/context/claude-tools.js +5 -4
  156. package/dist/runtime/context/compaction.js +4 -0
  157. package/dist/runtime/langgraph-loop.js +3 -2
  158. package/dist/runtime/loop.d.ts +4 -3
  159. package/dist/runtime/maintenance.d.ts +4 -2
  160. package/dist/runtime/maintenance.js +17 -5
  161. package/dist/runtime/model-call.d.ts +17 -1
  162. package/dist/runtime/model-call.js +3 -2
  163. package/dist/runtime/providers.d.ts +6 -0
  164. package/dist/runtime/providers.js +8 -0
  165. package/dist/runtime/run-queue.d.ts +25 -2
  166. package/dist/runtime/run-queue.js +129 -33
  167. package/dist/runtime/run-worker.js +16 -3
  168. package/dist/runtime/runtime.module.js +8 -3
  169. package/dist/runtime/step-log.d.ts +6 -0
  170. package/dist/runtime/step-log.js +16 -2
  171. package/dist/runtime/subagents/definition.js +3 -1
  172. package/dist/runtime/usage-report.d.ts +2 -1
  173. package/dist/runtime/usage-report.js +2 -1
  174. package/dist/runtime/waits.d.ts +8 -2
  175. package/dist/runtime/waits.js +37 -11
  176. package/dist/sandbox/sandbox.module.js +6 -1
  177. package/dist/sandbox/sandbox.service.d.ts +61 -3
  178. package/dist/sandbox/sandbox.service.js +141 -7
  179. package/dist/tools/approvals.service.js +2 -0
  180. package/dist/tools/builtin-names.d.ts +20 -2
  181. package/dist/tools/builtin-names.js +29 -2
  182. package/dist/tools/builtins/builtin-tools.d.ts +15 -1
  183. package/dist/tools/builtins/builtin-tools.js +34 -2
  184. package/dist/tools/builtins/sandbox/bash.d.ts +1 -1
  185. package/dist/tools/builtins/sandbox/bash.js +230 -3
  186. package/dist/tools/builtins/sandbox/common.d.ts +27 -4
  187. package/dist/tools/builtins/sandbox/common.js +7 -3
  188. package/dist/tools/builtins/sandbox/deliver.js +1 -1
  189. package/dist/tools/builtins/sandbox/index.d.ts +2 -0
  190. package/dist/tools/builtins/sandbox/index.js +6 -0
  191. package/dist/tools/builtins/sandbox/task-output.d.ts +63 -0
  192. package/dist/tools/builtins/sandbox/task-output.js +304 -0
  193. package/dist/tools/builtins/sandbox/task-stop.d.ts +10 -0
  194. package/dist/tools/builtins/sandbox/task-stop.js +112 -0
  195. package/dist/tools/builtins/web/errors.d.ts +8 -0
  196. package/dist/tools/builtins/web/errors.js +53 -0
  197. package/dist/tools/builtins/web/extract.d.ts +62 -0
  198. package/dist/tools/builtins/web/extract.js +267 -0
  199. package/dist/tools/builtins/web/fetch.d.ts +55 -0
  200. package/dist/tools/builtins/web/fetch.js +174 -0
  201. package/dist/tools/builtins/web/index.d.ts +13 -0
  202. package/dist/tools/builtins/web/index.js +12 -0
  203. package/dist/tools/builtins/web/page-cache.d.ts +25 -0
  204. package/dist/tools/builtins/web/page-cache.js +77 -0
  205. package/dist/tools/builtins/web/search.d.ts +25 -0
  206. package/dist/tools/builtins/web/search.js +209 -0
  207. package/dist/tools/builtins/web/url.d.ts +17 -0
  208. package/dist/tools/builtins/web/url.js +43 -0
  209. package/dist/tools/oauth/flow-paths.d.ts +1 -1
  210. package/dist/tools/oauth/flow-paths.js +1 -1
  211. package/dist/tools/pipeline.js +15 -0
  212. package/dist/tools/registry.d.ts +22 -3
  213. package/dist/tools/registry.js +26 -11
  214. package/dist/tools/tools.module.js +12 -0
  215. package/dist/tools/validate.js +1 -1
  216. package/package.json +3 -3
  217. package/src/migrations/0064_account_last_org.sql +7 -0
  218. package/src/migrations/0065_conversation_archive.sql +8 -0
  219. package/src/migrations/0066_extension_display_name.sql +4 -0
  220. package/src/migrations/0067_agent_profiles.sql +20 -0
  221. package/src/migrations/0068_agent_avatar_uploads.sql +7 -0
  222. package/src/migrations/0069_attachments_agent_idx.sql +3 -0
  223. package/src/migrations/0070_background_tasks.sql +37 -0
  224. package/src/migrations/0071_background_tasks_reserve.sql +6 -0
  225. package/src/migrations/0072_background_tasks_stopping.sql +5 -0
  226. 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 = "/settings/connections";
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 = "/settings/connections";
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
  {
@@ -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;
@@ -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
- /** 本部署当前不可用时返回事实原因(英文、可执行,进「工具不可用」提醒);可用返回 null。 */
62
- unavailable?: () => string | null;
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)。
@@ -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
- key: b.spec.name,
335
- source: { kind: "builtin", name: b.spec.name },
336
- spec,
337
- specHash: specHash(spec),
338
- defaultPolicy: b.defaultPolicy,
339
- ...(b.timeoutMs !== undefined && { timeoutMs: b.timeoutMs }),
340
- ...(b.spillChars !== undefined && { spillChars: b.spillChars }),
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,
@@ -1,5 +1,5 @@
1
1
  import { Ajv2020 } from "ajv/dist/2020.js";
2
- import { canonicalJson } from "./registry.js";
2
+ import { canonicalJson } from "./canonical-json.js";
3
3
  /**
4
4
  * 编译结果缓存的上限:超过后整体丢弃(工具定义每步从库里重新读出,缓存不能无限增长)。
5
5
  * 缓存的是由定义推导出的只读数据,不是跨进程状态。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capybara-hq/kernel",
3
- "version": "0.1.0-rc.3",
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.1"
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.3"
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
+ );