@oxygen-agent/cli 1.948.1 → 1.982.3

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 (54) hide show
  1. package/README.md +1 -1
  2. package/dist/admin-primary-providers-render.js +9 -1
  3. package/dist/cli-values.d.ts +14 -0
  4. package/dist/cli-values.js +26 -0
  5. package/dist/command-manifest.js +6 -0
  6. package/dist/functions-commands.js +13 -5
  7. package/dist/help.js +1 -0
  8. package/dist/index.js +1171 -240
  9. package/dist/knowledge-repository-commands.d.ts +6 -0
  10. package/dist/knowledge-repository-commands.js +198 -0
  11. package/dist/skills.js +20 -0
  12. package/dist/ugc-commands.js +122 -8
  13. package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +2 -0
  14. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +8 -0
  15. package/node_modules/@oxygen/shared/dist/capability-discovery.js +99 -15
  16. package/node_modules/@oxygen/shared/dist/copilot-errors.js +3 -0
  17. package/node_modules/@oxygen/shared/dist/copilot-journeys.d.ts +19 -1
  18. package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.d.ts +19 -0
  19. package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.js +26 -0
  20. package/node_modules/@oxygen/shared/dist/copilot-journeys.js +8 -41
  21. package/node_modules/@oxygen/shared/dist/inbox-avatar-url.d.ts +28 -0
  22. package/node_modules/@oxygen/shared/dist/inbox-avatar-url.js +57 -0
  23. package/node_modules/@oxygen/shared/dist/index.d.ts +4 -0
  24. package/node_modules/@oxygen/shared/dist/index.js +4 -0
  25. package/node_modules/@oxygen/shared/dist/knowledge-bases.d.ts +74 -0
  26. package/node_modules/@oxygen/shared/dist/knowledge-bases.js +456 -0
  27. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +17 -38
  28. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +14 -39
  29. package/node_modules/@oxygen/shared/dist/knowledge-repository.d.ts +22 -0
  30. package/node_modules/@oxygen/shared/dist/knowledge-repository.js +121 -0
  31. package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.d.ts +20 -0
  32. package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.js +155 -0
  33. package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +10 -0
  34. package/node_modules/@oxygen/shared/dist/mailbox-import.js +53 -0
  35. package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +8 -0
  36. package/node_modules/@oxygen/shared/dist/plan-limits.js +8 -0
  37. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
  38. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
  39. package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +24 -0
  40. package/node_modules/@oxygen/shared/dist/product-analytics-events.js +24 -0
  41. package/node_modules/@oxygen/shared/dist/recipes.d.ts +6 -0
  42. package/node_modules/@oxygen/shared/dist/recipes.js +23 -0
  43. package/node_modules/@oxygen/shared/dist/sequences.d.ts +126 -2
  44. package/node_modules/@oxygen/shared/dist/sequences.js +280 -4
  45. package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.d.ts +2 -0
  46. package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.js +24 -0
  47. package/node_modules/@oxygen/shared/dist/ugc.d.ts +8 -0
  48. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +8 -1
  49. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
  50. package/node_modules/@oxygen/shared/dist/version.js +3 -1
  51. package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +6 -2
  52. package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +15 -4
  53. package/node_modules/@oxygen/shared/package.json +15 -0
  54. package/package.json +2 -1
@@ -0,0 +1,456 @@
1
+ import { parseDocument } from "yaml";
2
+ function record(value) {
3
+ return value !== null && typeof value === "object" && !Array.isArray(value)
4
+ ? value
5
+ : null;
6
+ }
7
+ function stringValue(value) {
8
+ return typeof value === "string" && value.trim() ? value.trim() : null;
9
+ }
10
+ function strings(value) {
11
+ return Array.isArray(value)
12
+ ? value
13
+ .filter((item) => typeof item === "string" && item.trim().length > 0)
14
+ .map((item) => item.trim())
15
+ : [];
16
+ }
17
+ function direction(value) {
18
+ return typeof value === "string" && value.toUpperCase() === "DESC"
19
+ ? "DESC"
20
+ : "ASC";
21
+ }
22
+ function filterNode(value, issues, location) {
23
+ if (typeof value === "string")
24
+ return value;
25
+ const source = record(value);
26
+ if (!source) {
27
+ if (value !== undefined)
28
+ issues.push({
29
+ code: "unsupported_filter",
30
+ message: `${location} is preserved but has an unsupported filter shape.`,
31
+ });
32
+ return undefined;
33
+ }
34
+ const result = {};
35
+ const knownKeys = new Set(["and", "or", "not"]);
36
+ for (const key of Object.keys(source)) {
37
+ if (!knownKeys.has(key))
38
+ issues.push({
39
+ code: "unsupported_filter",
40
+ message: `${location}.${key} is preserved but is not supported.`,
41
+ });
42
+ }
43
+ for (const key of ["and", "or", "not"]) {
44
+ if (source[key] === undefined)
45
+ continue;
46
+ if (!Array.isArray(source[key])) {
47
+ issues.push({
48
+ code: "unsupported_filter",
49
+ message: `${location}.${key} must be a list.`,
50
+ });
51
+ continue;
52
+ }
53
+ const children = source[key].flatMap((child) => {
54
+ const parsed = filterNode(child, issues, `${location}.${key}`);
55
+ return parsed === undefined ? [] : [parsed];
56
+ });
57
+ result[key] = children;
58
+ }
59
+ return Object.keys(result).length > 0 ? result : undefined;
60
+ }
61
+ function mapRecord(value, map) {
62
+ const source = record(value);
63
+ if (!source)
64
+ return {};
65
+ return Object.fromEntries(Object.entries(source).flatMap(([key, item]) => {
66
+ const mapped = map(key, item);
67
+ return mapped === null ? [] : [[key, mapped]];
68
+ }));
69
+ }
70
+ function parseView(value, index, issues) {
71
+ const source = record(value);
72
+ if (!source) {
73
+ issues.push({
74
+ code: "invalid_view",
75
+ message: `View ${index + 1} is not an object.`,
76
+ });
77
+ return null;
78
+ }
79
+ const type = stringValue(source.type) ?? "table";
80
+ const name = stringValue(source.name) ?? `View ${index + 1}`;
81
+ if (!["table", "cards", "kanban"].includes(type)) {
82
+ issues.push({
83
+ code: "unsupported_view",
84
+ message: `The ${type} view type is preserved but cannot be rendered.`,
85
+ view: name,
86
+ });
87
+ }
88
+ const group = record(source.groupBy);
89
+ const groupProperty = stringValue(group?.property);
90
+ const view = {
91
+ type,
92
+ name,
93
+ order: strings(source.order),
94
+ boardColumns: strings(source.boardColumns),
95
+ sort: Array.isArray(source.sort)
96
+ ? source.sort.flatMap((item) => {
97
+ const sort = record(item);
98
+ const property = stringValue(sort?.property);
99
+ return property
100
+ ? [{ property, direction: direction(sort?.direction) }]
101
+ : [];
102
+ })
103
+ : [],
104
+ columnColors: mapRecord(source.columnColors, (_key, item) => typeof item === "string" ? item : null),
105
+ wipLimits: mapRecord(source.wipLimits, (_key, item) => typeof item === "number" && Number.isFinite(item) ? item : null),
106
+ raw: source,
107
+ };
108
+ if (groupProperty)
109
+ view.groupBy = {
110
+ property: groupProperty,
111
+ direction: direction(group?.direction),
112
+ };
113
+ const cardTitleProperty = stringValue(source.cardTitleProperty);
114
+ if (cardTitleProperty)
115
+ view.cardTitleProperty = cardTitleProperty;
116
+ const filters = filterNode(source.filters, issues, `${name}.filters`);
117
+ if (filters)
118
+ view.filters = filters;
119
+ return view;
120
+ }
121
+ export function parseKnowledgeBase(path, source) {
122
+ const document = parseDocument(source, { customTags: [] });
123
+ if (document.errors.length > 0) {
124
+ return {
125
+ definition: null,
126
+ issues: document.errors.map((error) => ({
127
+ code: "invalid_base",
128
+ message: error.message,
129
+ })),
130
+ };
131
+ }
132
+ let parsed;
133
+ try {
134
+ parsed = document.toJS({ maxAliasCount: 0 });
135
+ }
136
+ catch (error) {
137
+ return {
138
+ definition: null,
139
+ issues: [
140
+ {
141
+ code: "invalid_base",
142
+ message: error instanceof Error
143
+ ? error.message
144
+ : "The base document could not be decoded.",
145
+ },
146
+ ],
147
+ };
148
+ }
149
+ const root = record(parsed);
150
+ if (!root)
151
+ return {
152
+ definition: null,
153
+ issues: [
154
+ {
155
+ code: "invalid_base",
156
+ message: "The base document must be a YAML object.",
157
+ },
158
+ ],
159
+ };
160
+ const issues = [];
161
+ const views = Array.isArray(root.views)
162
+ ? root.views.flatMap((value, index) => {
163
+ const view = parseView(value, index, issues);
164
+ return view ? [view] : [];
165
+ })
166
+ : [];
167
+ const properties = mapRecord(root.properties, (_key, item) => {
168
+ const raw = record(item) ?? {};
169
+ const displayName = stringValue(raw.displayName);
170
+ return { ...(displayName ? { displayName } : {}), raw };
171
+ });
172
+ const name = path
173
+ .split("/")
174
+ .pop()
175
+ ?.replace(/\.base$/i, "") || path;
176
+ const definition = {
177
+ path,
178
+ name,
179
+ properties,
180
+ views,
181
+ raw: root,
182
+ };
183
+ const filters = filterNode(root.filters, issues, `${name}.filters`);
184
+ if (filters)
185
+ definition.filters = filters;
186
+ return { definition, issues };
187
+ }
188
+ export function knowledgeBasePropertyKey(property) {
189
+ return property.startsWith("note.") ? property.slice(5) : property;
190
+ }
191
+ function fileName(path) {
192
+ const name = path.split("/").pop() ?? path;
193
+ return name.replace(/\.[^.]+$/, "");
194
+ }
195
+ export function knowledgeBasePropertyValue(file, property) {
196
+ if (property === "file.name")
197
+ return fileName(file.path);
198
+ if (property === "file.path")
199
+ return file.path;
200
+ if (property === "file.ext")
201
+ return file.path.includes(".")
202
+ ? (file.path.split(".").pop()?.toLowerCase() ?? "")
203
+ : "";
204
+ return file.properties[knowledgeBasePropertyKey(property)];
205
+ }
206
+ export function knowledgeBasePropertyText(file, property) {
207
+ const value = knowledgeBasePropertyValue(file, property);
208
+ if (value == null)
209
+ return "";
210
+ if (Array.isArray(value))
211
+ return value.map(String).join(", ");
212
+ if (typeof value === "object")
213
+ return JSON.stringify(value);
214
+ return String(value);
215
+ }
216
+ function isEmpty(value) {
217
+ return (value == null ||
218
+ value === "" ||
219
+ (Array.isArray(value) && value.length === 0));
220
+ }
221
+ function equals(left, right) {
222
+ if (Array.isArray(left))
223
+ return left.some((item) => String(item) === right);
224
+ return String(left ?? "") === right;
225
+ }
226
+ function compare(left, right) {
227
+ if (typeof left === "number" && typeof right === "number")
228
+ return left - right;
229
+ const leftNumber = typeof left === "string" && left.trim() ? Number(left) : Number.NaN;
230
+ const rightNumber = typeof right === "string" && right.trim() ? Number(right) : Number.NaN;
231
+ if (Number.isFinite(leftNumber) && Number.isFinite(rightNumber))
232
+ return leftNumber - rightNumber;
233
+ return String(left ?? "").localeCompare(String(right ?? ""), undefined, {
234
+ numeric: true,
235
+ sensitivity: "base",
236
+ });
237
+ }
238
+ function evaluatePredicate(expression, file) {
239
+ let match;
240
+ if ((match = /^file\.inFolder\(\s*"([^"]*)"\s*\)$/.exec(expression))) {
241
+ const folder = (match[1] ?? "").replace(/^\/+|\/+$/g, "");
242
+ return { matches: file.path.startsWith(`${folder}/`), supported: true };
243
+ }
244
+ if ((match = /^([\w.]+)\.(isEmpty|isNotEmpty)\(\)$/.exec(expression))) {
245
+ const empty = isEmpty(knowledgeBasePropertyValue(file, match[1] ?? ""));
246
+ return {
247
+ matches: match[2] === "isEmpty" ? empty : !empty,
248
+ supported: true,
249
+ };
250
+ }
251
+ if ((match = /^([\w.]+)\.contains\(\s*"([^"]*)"\s*\)$/.exec(expression))) {
252
+ const value = knowledgeBasePropertyValue(file, match[1] ?? "");
253
+ const needle = match[2] ?? "";
254
+ return {
255
+ matches: Array.isArray(value)
256
+ ? value.some((item) => String(item).includes(needle))
257
+ : String(value ?? "").includes(needle),
258
+ supported: true,
259
+ };
260
+ }
261
+ if ((match =
262
+ /^([\w.]+)\s*(==|!=|>=|<=|>|<)\s*(?:"([^"]*)"|(-?\d+(?:\.\d+)?))$/.exec(expression))) {
263
+ const value = knowledgeBasePropertyValue(file, match[1] ?? "");
264
+ const operand = match[3] ?? match[4] ?? "";
265
+ const operator = match[2];
266
+ if (operator === "==" || operator === "!=") {
267
+ const equal = equals(value, operand);
268
+ return { matches: operator === "==" ? equal : !equal, supported: true };
269
+ }
270
+ const numeric = Number(value);
271
+ const expected = Number(operand);
272
+ const matches = Number.isFinite(numeric) &&
273
+ Number.isFinite(expected) &&
274
+ (operator === ">="
275
+ ? numeric >= expected
276
+ : operator === "<="
277
+ ? numeric <= expected
278
+ : operator === ">"
279
+ ? numeric > expected
280
+ : numeric < expected);
281
+ return { matches, supported: true };
282
+ }
283
+ return { matches: true, supported: false };
284
+ }
285
+ function evaluateFilter(node, file, unsupported) {
286
+ if (node === undefined)
287
+ return true;
288
+ if (typeof node === "string") {
289
+ const result = evaluatePredicate(node.trim(), file);
290
+ if (!result.supported)
291
+ unsupported.add(node);
292
+ return result.matches;
293
+ }
294
+ if (node.and)
295
+ return node.and
296
+ .map((child) => evaluateFilter(child, file, unsupported))
297
+ .every(Boolean);
298
+ if (node.or)
299
+ return node.or
300
+ .map((child) => evaluateFilter(child, file, unsupported))
301
+ .some(Boolean);
302
+ if (node.not)
303
+ return !node.not
304
+ .map((child) => evaluateFilter(child, file, unsupported))
305
+ .every(Boolean);
306
+ return true;
307
+ }
308
+ function collectUnsupportedFilters(node, unsupported) {
309
+ if (node === undefined)
310
+ return;
311
+ if (typeof node === "string") {
312
+ if (!evaluatePredicate(node.trim(), {
313
+ id: "",
314
+ path: "",
315
+ properties: {},
316
+ revision: 0,
317
+ }).supported)
318
+ unsupported.add(node);
319
+ return;
320
+ }
321
+ for (const child of [
322
+ ...(node.and ?? []),
323
+ ...(node.or ?? []),
324
+ ...(node.not ?? []),
325
+ ]) {
326
+ collectUnsupportedFilters(child, unsupported);
327
+ }
328
+ }
329
+ function stableSort(files, sort, includeKanbanOrder) {
330
+ const rules = includeKanbanOrder
331
+ ? [{ property: "kanban_order", direction: "ASC" }, ...sort]
332
+ : sort;
333
+ return files
334
+ .map((file, index) => ({ file, index }))
335
+ .sort((left, right) => {
336
+ for (const rule of rules) {
337
+ const compared = compare(knowledgeBasePropertyValue(left.file, rule.property), knowledgeBasePropertyValue(right.file, rule.property));
338
+ if (compared !== 0)
339
+ return rule.direction === "DESC" ? -compared : compared;
340
+ }
341
+ return left.index - right.index;
342
+ })
343
+ .map(({ file }) => file);
344
+ }
345
+ export function evaluateKnowledgeBaseView(definition, view, files) {
346
+ const unsupported = new Set();
347
+ collectUnsupportedFilters(definition.filters, unsupported);
348
+ collectUnsupportedFilters(view.filters, unsupported);
349
+ const matched = files.filter((file) => evaluateFilter(definition.filters, file, unsupported) &&
350
+ evaluateFilter(view.filters, file, unsupported));
351
+ return {
352
+ files: stableSort(matched, view.sort, view.type === "kanban"),
353
+ issues: [...unsupported].map((expression) => ({
354
+ code: "unsupported_filter",
355
+ message: `This filter is preserved but is not supported: ${expression}`,
356
+ expression,
357
+ view: view.name,
358
+ })),
359
+ };
360
+ }
361
+ export function knowledgeBaseGroups(view, files, configuredColumns = []) {
362
+ const property = view.groupBy?.property;
363
+ if (!property)
364
+ return [{ key: "__all", title: view.name, files: [...files] }];
365
+ const groupDirection = view.groupBy?.direction ?? "ASC";
366
+ const discovered = [
367
+ ...new Set(files
368
+ .map((file) => knowledgeBasePropertyText(file, property).trim())
369
+ .filter(Boolean)),
370
+ ];
371
+ const ordered = configuredColumns.length > 0
372
+ ? [...configuredColumns]
373
+ : view.boardColumns.length > 0
374
+ ? [...view.boardColumns]
375
+ : discovered.sort((left, right) => left.localeCompare(right));
376
+ const configured = new Set(ordered.map((value) => value.toLowerCase()));
377
+ ordered.push(...discovered
378
+ .filter((value) => !configured.has(value.toLowerCase()))
379
+ .sort((left, right) => left.localeCompare(right)));
380
+ if (groupDirection === "DESC")
381
+ ordered.reverse();
382
+ const noValueLabels = new Set(["(no value)", "(none)", "(empty)"]);
383
+ const groups = ordered.map((value) => {
384
+ const isNoValue = noValueLabels.has(value.toLowerCase());
385
+ return {
386
+ key: isNoValue ? "__empty" : value,
387
+ title: value,
388
+ files: files.filter((file) => {
389
+ const current = knowledgeBasePropertyText(file, property).trim();
390
+ return isNoValue
391
+ ? !current
392
+ : current.toLowerCase() === value.toLowerCase();
393
+ }),
394
+ };
395
+ });
396
+ const known = new Set(ordered
397
+ .filter((value) => !noValueLabels.has(value.toLowerCase()))
398
+ .map((value) => value.toLowerCase()));
399
+ const hasEmptyColumn = ordered.some((value) => noValueLabels.has(value.toLowerCase()));
400
+ const unmatched = files.filter((file) => {
401
+ const current = knowledgeBasePropertyText(file, property).trim();
402
+ return current ? !known.has(current.toLowerCase()) : !hasEmptyColumn;
403
+ });
404
+ if (unmatched.length > 0)
405
+ groups.push({
406
+ key: "__empty",
407
+ title: `(No ${knowledgeBasePropertyKey(property)})`,
408
+ files: unmatched,
409
+ });
410
+ return groups;
411
+ }
412
+ export function knowledgeBaseDisplayName(definition, property) {
413
+ const key = knowledgeBasePropertyKey(property);
414
+ return (definition.properties[property]?.displayName ??
415
+ definition.properties[`note.${key}`]?.displayName ??
416
+ definition.properties[key]?.displayName ??
417
+ (property === "file.name" ? "Title" : key));
418
+ }
419
+ export function knowledgeBaseCardTitle(file, view) {
420
+ return (knowledgeBasePropertyText(file, view.cardTitleProperty ?? "file.name") ||
421
+ fileName(file.path));
422
+ }
423
+ export function parseBaseBoardColumnOrder(value) {
424
+ const root = record(value);
425
+ const configs = record(root?.columnConfigs);
426
+ if (!configs)
427
+ return {};
428
+ return mapRecord(configs, (_key, item) => {
429
+ const columns = strings(record(item)?.columns);
430
+ return columns.length > 0 ? columns : null;
431
+ });
432
+ }
433
+ function inFolderExpressions(node) {
434
+ if (node === undefined)
435
+ return [];
436
+ if (typeof node === "string") {
437
+ const match = /^file\.inFolder\(\s*"([^"]*)"\s*\)$/.exec(node.trim());
438
+ return match?.[1] ? [match[1].replace(/^\/+|\/+$/g, "")] : [];
439
+ }
440
+ return [...(node.and ?? []), ...(node.or ?? []), ...(node.not ?? [])].flatMap(inFolderExpressions);
441
+ }
442
+ /** Resolve Base Board's `<folder>::<view>::<property>` lane order, with view-name shorthand for API callers. */
443
+ export function resolveBaseBoardColumnOrder(definition, view, configured) {
444
+ const property = knowledgeBasePropertyKey(view.groupBy?.property ?? "status");
445
+ const direct = configured[view.name];
446
+ if (direct)
447
+ return [...direct];
448
+ for (const folder of inFolderExpressions(definition.filters)) {
449
+ const exact = configured[`${folder}::${view.name}::${property}`];
450
+ if (exact)
451
+ return [...exact];
452
+ }
453
+ const suffix = `::${view.name}::${property}`;
454
+ const candidates = Object.entries(configured).filter(([key]) => key.endsWith(suffix));
455
+ return candidates.length === 1 ? [...candidates[0][1]] : [];
456
+ }
@@ -43,42 +43,14 @@
43
43
  * ordinary revisioned wiki writes a human can revert.
44
44
  * 6. EXACTLY-ONCE — the marker is claimed with a conditional UPDATE, so N worker
45
45
  * replicas polling one tenant produce one run, never N runs.
46
- * 7. OXYGEN-FUNDED — the automatic pass meters managed credits through the ordinary
47
- * tool and AI-column lanes (so the cap, the marker and the run row
48
- * all stay honest), and the worker then grants exactly the credits
49
- * it used back to the workspace as an idempotent `bonus` ledger
50
- * row keyed by the run (`KNOWLEDGE_BOOTSTRAP_COVERAGE_IDEMPOTENCY_PREFIX`).
51
- * The customer's balance nets to zero and both rows are visible in
52
- * their ledger. Only the AUTOMATIC pass is covered: a requested
53
- * `--live --max-credits` re-run is the customer's own paid action.
46
+ * 7. OXYGEN-FUNDED — the internal worker scope records nominal managed-credit
47
+ * holds and settlements with zero customer-balance deltas.
48
+ * Manual reruns remain customer-funded. Durable call checkpoints
49
+ * replay completed results and never repeat uncertain dispatch.
54
50
  *
55
- * CAP ARITHMETIC (prices re-read from packages/integrations/src/cogs-rates.ts —
56
- * confirm them there rather than trusting this comment after a re-pricing):
57
- *
58
- * serper.search 5 cr per call (cogs-rates: serper.customerOxygen…search)
59
- * exa.contents 5 cr per CALL (not per URL — this is the whole trick)
60
- * AI column, medium tier 75 cr per run (pricing-sheet: AI_COLUMN_CREDITS.medium)
61
- *
62
- * Happy path = 1 serper.search (5) + 1 exa.contents over <= 5 URLs (5)
63
- * + 1 medium synthesis pass (75) = 85 cr
64
- * Enrichment sub-cap 60 cr — ~6x the 10 cr the happy path needs, so a waterfall
65
- * retry or a second search fits, and a runaway
66
- * per-URL fetch loop is stopped long before the
67
- * overall ceiling.
68
- * Hard cap 300 cr — 85 cr nominal plus room for two retried synthesis
69
- * passes. 3% of the 10,000-credit signup grant
70
- * (FREE_SIGNUP_GRANT_CREDITS, ./billing.ts), which is
71
- * the headroom the balance gate needs even though the
72
- * spend is granted back.
73
- *
74
- * The obvious implementation — firecrawl.map + 3x firecrawl.scrape — costs
75
- * 4 x 40 = 160 cr for the SAME job, because Firecrawl bills per page. exa.contents
76
- * is priced per call and is built for fetching text at known URLs; that is why the
77
- * read budget is a page COUNT (`KNOWLEDGE_BOOTSTRAP_MAX_PAGES_READ`) inside one
78
- * call rather than a call count.
79
- *
80
- * This module is a leaf: constants and types only, no DB and no provider imports, so
81
- * the worker, tenant-db, CLI, and API surfaces all bind to one contract.
51
+ * New workspace consent scopes the submitted domain and actual signup creator.
52
+ * Professional person evidence remains actor-scoped; ICP/offers are hypotheses.
53
+ * The provider sub-cap covers every external research leg, including fallbacks.
82
54
  */
83
55
  import type { HostedAiLevel } from "./hosted-ai.js";
84
56
  /** Hard ceiling on managed credits ONE workspace bootstrap may spend, all phases. */
@@ -143,6 +115,9 @@ export declare function isKnowledgeBootstrapStatus(value: unknown): value is Kno
143
115
  * same thing.
144
116
  */
145
117
  export type KnowledgeBootstrapMarker = {
118
+ /** Versioned claims can recover a crash before the first dispatch checkpoint.
119
+ * Absence identifies legacy attempts whose dispatch history is unknown. */
120
+ execution_version?: 1;
146
121
  status: KnowledgeBootstrapStatus;
147
122
  /** ISO timestamp the claim was won. */
148
123
  started_at: string | null;
@@ -194,6 +169,9 @@ export type KnowledgeBootstrapMarker = {
194
169
  export declare const KNOWLEDGE_BOOTSTRAP_CONSENT_METADATA_KEY = "knowledge_bootstrap_consent";
195
170
  /** The recorded consent, exactly as stored under the key above. */
196
171
  export type KnowledgeBootstrapConsent = {
172
+ /** New workspace creation may authorize bounded creator-profile research. */
173
+ scope?: "company_and_creator";
174
+ domain?: string;
197
175
  /** ISO timestamp consent was recorded. Must parse; a marker that does not is not consent. */
198
176
  granted_at: string;
199
177
  /**
@@ -267,13 +245,14 @@ export declare const KNOWLEDGE_BOOTSTRAP_COVERAGE_IDEMPOTENCY_PREFIX = "knowledg
267
245
  */
268
246
  export declare const KNOWLEDGE_BOOTSTRAP_RESEARCH_PAGE_SLUG = "company-research";
269
247
  /**
270
- * The typed `company_profile` sections a bootstrap may write — and the exact set the
271
- * "never clobber a profile a human already filled" gate inspects.
248
+ * The typed `company_profile` sections inspected by the automatic bootstrap's
249
+ * "never clobber a profile a human already filled" gate.
272
250
  *
273
251
  * `gtm_stack` and `custom` are structurally excluded: research of a company's public
274
252
  * web presence says nothing about which tools it has connected here, and a workspace
275
253
  * that filled only those has told us nothing about its company and must still be
276
- * bootstrapped. Same reasoning as the slug above — the preview promises this list.
254
+ * bootstrapped. This is the existing-context guard's list, not a list of writes:
255
+ * current research writes company facts and stores inferred GTM hypotheses separately.
277
256
  */
278
257
  export declare const KNOWLEDGE_BOOTSTRAP_PROFILE_SECTIONS: readonly ["company", "offering", "icp", "market"];
279
258
  /**
@@ -43,42 +43,14 @@
43
43
  * ordinary revisioned wiki writes a human can revert.
44
44
  * 6. EXACTLY-ONCE — the marker is claimed with a conditional UPDATE, so N worker
45
45
  * replicas polling one tenant produce one run, never N runs.
46
- * 7. OXYGEN-FUNDED — the automatic pass meters managed credits through the ordinary
47
- * tool and AI-column lanes (so the cap, the marker and the run row
48
- * all stay honest), and the worker then grants exactly the credits
49
- * it used back to the workspace as an idempotent `bonus` ledger
50
- * row keyed by the run (`KNOWLEDGE_BOOTSTRAP_COVERAGE_IDEMPOTENCY_PREFIX`).
51
- * The customer's balance nets to zero and both rows are visible in
52
- * their ledger. Only the AUTOMATIC pass is covered: a requested
53
- * `--live --max-credits` re-run is the customer's own paid action.
54
- *
55
- * CAP ARITHMETIC (prices re-read from packages/integrations/src/cogs-rates.ts —
56
- * confirm them there rather than trusting this comment after a re-pricing):
57
- *
58
- * serper.search 5 cr per call (cogs-rates: serper.customerOxygen…search)
59
- * exa.contents 5 cr per CALL (not per URL — this is the whole trick)
60
- * AI column, medium tier 75 cr per run (pricing-sheet: AI_COLUMN_CREDITS.medium)
61
- *
62
- * Happy path = 1 serper.search (5) + 1 exa.contents over <= 5 URLs (5)
63
- * + 1 medium synthesis pass (75) = 85 cr
64
- * Enrichment sub-cap 60 cr — ~6x the 10 cr the happy path needs, so a waterfall
65
- * retry or a second search fits, and a runaway
66
- * per-URL fetch loop is stopped long before the
67
- * overall ceiling.
68
- * Hard cap 300 cr — 85 cr nominal plus room for two retried synthesis
69
- * passes. 3% of the 10,000-credit signup grant
70
- * (FREE_SIGNUP_GRANT_CREDITS, ./billing.ts), which is
71
- * the headroom the balance gate needs even though the
72
- * spend is granted back.
73
- *
74
- * The obvious implementation — firecrawl.map + 3x firecrawl.scrape — costs
75
- * 4 x 40 = 160 cr for the SAME job, because Firecrawl bills per page. exa.contents
76
- * is priced per call and is built for fetching text at known URLs; that is why the
77
- * read budget is a page COUNT (`KNOWLEDGE_BOOTSTRAP_MAX_PAGES_READ`) inside one
78
- * call rather than a call count.
79
- *
80
- * This module is a leaf: constants and types only, no DB and no provider imports, so
81
- * the worker, tenant-db, CLI, and API surfaces all bind to one contract.
46
+ * 7. OXYGEN-FUNDED — the internal worker scope records nominal managed-credit
47
+ * holds and settlements with zero customer-balance deltas.
48
+ * Manual reruns remain customer-funded. Durable call checkpoints
49
+ * replay completed results and never repeat uncertain dispatch.
50
+ *
51
+ * New workspace consent scopes the submitted domain and actual signup creator.
52
+ * Professional person evidence remains actor-scoped; ICP/offers are hypotheses.
53
+ * The provider sub-cap covers every external research leg, including fallbacks.
82
54
  */
83
55
  /** Hard ceiling on managed credits ONE workspace bootstrap may spend, all phases. */
84
56
  export const KNOWLEDGE_BOOTSTRAP_MAX_CREDITS = 300;
@@ -188,6 +160,8 @@ export function readKnowledgeBootstrapConsent(metadata) {
188
160
  return null;
189
161
  return {
190
162
  granted_at: grantedAt,
163
+ ...(record.scope === "company_and_creator" ? { scope: "company_and_creator" } : {}),
164
+ ...(typeof record.domain === "string" ? { domain: record.domain } : {}),
191
165
  source: typeof record.source === "string" && record.source.length > 0 ? record.source : null,
192
166
  granted_by_clerk_user_id: typeof record.granted_by_clerk_user_id === "string" && record.granted_by_clerk_user_id.length > 0
193
167
  ? record.granted_by_clerk_user_id
@@ -248,13 +222,14 @@ export const KNOWLEDGE_BOOTSTRAP_COVERAGE_IDEMPOTENCY_PREFIX = "knowledge_bootst
248
222
  */
249
223
  export const KNOWLEDGE_BOOTSTRAP_RESEARCH_PAGE_SLUG = "company-research";
250
224
  /**
251
- * The typed `company_profile` sections a bootstrap may write — and the exact set the
252
- * "never clobber a profile a human already filled" gate inspects.
225
+ * The typed `company_profile` sections inspected by the automatic bootstrap's
226
+ * "never clobber a profile a human already filled" gate.
253
227
  *
254
228
  * `gtm_stack` and `custom` are structurally excluded: research of a company's public
255
229
  * web presence says nothing about which tools it has connected here, and a workspace
256
230
  * that filled only those has told us nothing about its company and must still be
257
- * bootstrapped. Same reasoning as the slug above — the preview promises this list.
231
+ * bootstrapped. This is the existing-context guard's list, not a list of writes:
232
+ * current research writes company facts and stores inferred GTM hypotheses separately.
258
233
  */
259
234
  export const KNOWLEDGE_BOOTSTRAP_PROFILE_SECTIONS = [
260
235
  "company",
@@ -0,0 +1,22 @@
1
+ export declare const KNOWLEDGE_REPOSITORY_FILE_LIMIT = 25000;
2
+ export declare const KNOWLEDGE_REPOSITORY_POLL_MS: number;
3
+ export declare const KNOWLEDGE_REPOSITORY_IDLE_MS = 15000;
4
+ export declare const KNOWLEDGE_REPOSITORY_MAX_WAIT_MS = 60000;
5
+ export type KnowledgeRepositoryMode = "import" | "two_way";
6
+ /** Files may be preserved without being executed. Credentials and machine state are never imported. */
7
+ export declare function knowledgeRepositoryExclusion(path: string, mode?: string): string | null;
8
+ export declare function normalizeKnowledgeRepositoryRoot(root: string): string;
9
+ export declare function knowledgeRepositoryRelativePath(path: string, root: string): string | null;
10
+ export type KnowledgeMergeResult = {
11
+ kind: "merged";
12
+ text: string | null;
13
+ } | {
14
+ kind: "conflict";
15
+ };
16
+ /** Conservative three-way merge: identical changes and disjoint line edits are safe.
17
+ * Overlap, edit/delete and binary changes retain both versions for an explicit decision. */
18
+ export declare function mergeKnowledgeText(base: string | null, local: string | null, remote: string | null): KnowledgeMergeResult;
19
+ export declare function parseKnowledgeLfsPointer(text: string): {
20
+ oid: string;
21
+ size: number;
22
+ } | null;