@oxygen-agent/cli 1.948.1 → 1.987.20
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/README.md +1 -1
- package/dist/admin-primary-providers-render.js +9 -1
- package/dist/cli-values.d.ts +14 -0
- package/dist/cli-values.js +26 -0
- package/dist/command-manifest.js +6 -0
- package/dist/functions-commands.js +33 -9
- package/dist/help.d.ts +21 -0
- package/dist/help.js +94 -0
- package/dist/index.js +1493 -297
- package/dist/knowledge-repository-commands.d.ts +6 -0
- package/dist/knowledge-repository-commands.js +198 -0
- package/dist/skills.js +20 -0
- package/dist/ugc-commands.d.ts +3 -6
- package/dist/ugc-commands.js +2 -1086
- package/node_modules/@oxygen/cli-ugc/dist/commands.d.ts +3 -0
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +1178 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +7 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +25 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.d.ts +14 -0
- package/node_modules/@oxygen/cli-ugc/dist/index.js +5 -0
- package/node_modules/@oxygen/cli-ugc/package.json +15 -0
- package/node_modules/@oxygen/formula/dist/expression.js +14 -1
- package/node_modules/@oxygen/formula/dist/formula-functions.js +71 -1
- package/node_modules/@oxygen/formula/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/formula/dist/index.js +1 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +69 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.js +374 -0
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +27 -27
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +127 -18
- package/node_modules/@oxygen/shared/dist/column-output-fields.js +12 -4
- package/node_modules/@oxygen/shared/dist/copilot-errors.js +3 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.d.ts +19 -1
- package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.d.ts +19 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.generated.js +26 -0
- package/node_modules/@oxygen/shared/dist/copilot-journeys.js +8 -41
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/copilot-playbooks.js +43 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +31 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +41 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.js +6 -0
- package/node_modules/@oxygen/shared/dist/inbox-avatar-url.d.ts +28 -0
- package/node_modules/@oxygen/shared/dist/inbox-avatar-url.js +57 -0
- package/node_modules/@oxygen/shared/dist/index.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/index.js +5 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bases.d.ts +74 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bases.js +456 -0
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +32 -38
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +38 -41
- package/node_modules/@oxygen/shared/dist/knowledge-repository.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/knowledge-repository.js +121 -0
- package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.d.ts +20 -0
- package/node_modules/@oxygen/shared/dist/knowledge-vault-markdown.js +155 -0
- package/node_modules/@oxygen/shared/dist/langfuse.d.ts +12 -1
- package/node_modules/@oxygen/shared/dist/langfuse.js +48 -8
- package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/mailbox-import.js +53 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.js +8 -0
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +24 -0
- package/node_modules/@oxygen/shared/dist/recipes.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/recipes.js +23 -0
- package/node_modules/@oxygen/shared/dist/research-output-contract.d.ts +33 -1
- package/node_modules/@oxygen/shared/dist/research-output-contract.js +64 -2
- package/node_modules/@oxygen/shared/dist/sequence-crm-events.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequence-hubspot-sync.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +152 -2
- package/node_modules/@oxygen/shared/dist/sequences.js +304 -4
- package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/ugc-amplification-identity.js +24 -0
- package/node_modules/@oxygen/shared/dist/ugc.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/user-capability-routing.js +8 -1
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +3 -1
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +6 -2
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +15 -4
- package/node_modules/@oxygen/shared/package.json +25 -0
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +15 -15
- package/package.json +6 -2
|
@@ -27,6 +27,7 @@ export * from "./crm-activity-events.js";
|
|
|
27
27
|
export * from "./column-types.js";
|
|
28
28
|
export * from "./copilot-errors.js";
|
|
29
29
|
export * from "./copilot-journeys.js";
|
|
30
|
+
export * from "./copilot-playbooks.js";
|
|
30
31
|
export * from "./copilot-plan.js";
|
|
31
32
|
export * from "./credit-guidance.js";
|
|
32
33
|
export * from "./directory.js";
|
|
@@ -47,6 +48,7 @@ export * from "./knowledge-constants.js";
|
|
|
47
48
|
export * from "./knowledge-bootstrap.js";
|
|
48
49
|
export * from "./knowledge-links.js";
|
|
49
50
|
export * from "./knowledge-markdown.js";
|
|
51
|
+
export * from "./knowledge-vault-markdown.js";
|
|
50
52
|
export * from "./knowledge-seed-content.js";
|
|
51
53
|
export * from "./langfuse.js";
|
|
52
54
|
export * from "./llm-usage.js";
|
|
@@ -155,3 +157,6 @@ export function isVersionLess(a, b) {
|
|
|
155
157
|
return compareSemver(a, b) < 0;
|
|
156
158
|
}
|
|
157
159
|
export * from "./ugc.js";
|
|
160
|
+
export * from "./ugc-amplification-identity.js";
|
|
161
|
+
export * from "./knowledge-repository.js";
|
|
162
|
+
export * from "./knowledge-bases.js";
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export type KnowledgeBaseViewType = "table" | "cards" | "kanban";
|
|
2
|
+
export type KnowledgeBaseFilter = string | {
|
|
3
|
+
and?: KnowledgeBaseFilter[];
|
|
4
|
+
or?: KnowledgeBaseFilter[];
|
|
5
|
+
not?: KnowledgeBaseFilter[];
|
|
6
|
+
};
|
|
7
|
+
export type KnowledgeBaseIssue = {
|
|
8
|
+
code: "invalid_base" | "unsupported_filter" | "unsupported_view" | "invalid_view";
|
|
9
|
+
message: string;
|
|
10
|
+
expression?: string;
|
|
11
|
+
view?: string;
|
|
12
|
+
};
|
|
13
|
+
export type KnowledgeBaseProperty = {
|
|
14
|
+
displayName?: string;
|
|
15
|
+
raw: Record<string, unknown>;
|
|
16
|
+
};
|
|
17
|
+
export type KnowledgeBaseView = {
|
|
18
|
+
type: string;
|
|
19
|
+
name: string;
|
|
20
|
+
groupBy?: {
|
|
21
|
+
property: string;
|
|
22
|
+
direction: "ASC" | "DESC";
|
|
23
|
+
};
|
|
24
|
+
order: string[];
|
|
25
|
+
boardColumns: string[];
|
|
26
|
+
cardTitleProperty?: string;
|
|
27
|
+
sort: Array<{
|
|
28
|
+
property: string;
|
|
29
|
+
direction: "ASC" | "DESC";
|
|
30
|
+
}>;
|
|
31
|
+
filters?: KnowledgeBaseFilter;
|
|
32
|
+
columnColors: Record<string, string>;
|
|
33
|
+
wipLimits: Record<string, number>;
|
|
34
|
+
raw: Record<string, unknown>;
|
|
35
|
+
};
|
|
36
|
+
export type KnowledgeBaseDefinition = {
|
|
37
|
+
path: string;
|
|
38
|
+
name: string;
|
|
39
|
+
filters?: KnowledgeBaseFilter;
|
|
40
|
+
properties: Record<string, KnowledgeBaseProperty>;
|
|
41
|
+
views: KnowledgeBaseView[];
|
|
42
|
+
raw: Record<string, unknown>;
|
|
43
|
+
};
|
|
44
|
+
export type KnowledgeBaseFile = {
|
|
45
|
+
id: string;
|
|
46
|
+
path: string;
|
|
47
|
+
body?: string;
|
|
48
|
+
properties: Record<string, unknown>;
|
|
49
|
+
revision: number;
|
|
50
|
+
};
|
|
51
|
+
export type KnowledgeBaseParseResult = {
|
|
52
|
+
definition: KnowledgeBaseDefinition | null;
|
|
53
|
+
issues: KnowledgeBaseIssue[];
|
|
54
|
+
};
|
|
55
|
+
export type KnowledgeBaseEvaluation = {
|
|
56
|
+
files: KnowledgeBaseFile[];
|
|
57
|
+
issues: KnowledgeBaseIssue[];
|
|
58
|
+
};
|
|
59
|
+
export type KnowledgeBaseGroup = {
|
|
60
|
+
key: string;
|
|
61
|
+
title: string;
|
|
62
|
+
files: KnowledgeBaseFile[];
|
|
63
|
+
};
|
|
64
|
+
export declare function parseKnowledgeBase(path: string, source: string): KnowledgeBaseParseResult;
|
|
65
|
+
export declare function knowledgeBasePropertyKey(property: string): string;
|
|
66
|
+
export declare function knowledgeBasePropertyValue(file: KnowledgeBaseFile, property: string): unknown;
|
|
67
|
+
export declare function knowledgeBasePropertyText(file: KnowledgeBaseFile, property: string): string;
|
|
68
|
+
export declare function evaluateKnowledgeBaseView(definition: KnowledgeBaseDefinition, view: KnowledgeBaseView, files: readonly KnowledgeBaseFile[]): KnowledgeBaseEvaluation;
|
|
69
|
+
export declare function knowledgeBaseGroups(view: KnowledgeBaseView, files: readonly KnowledgeBaseFile[], configuredColumns?: readonly string[]): KnowledgeBaseGroup[];
|
|
70
|
+
export declare function knowledgeBaseDisplayName(definition: KnowledgeBaseDefinition, property: string): string;
|
|
71
|
+
export declare function knowledgeBaseCardTitle(file: KnowledgeBaseFile, view: KnowledgeBaseView): string;
|
|
72
|
+
export declare function parseBaseBoardColumnOrder(value: unknown): Record<string, string[]>;
|
|
73
|
+
/** Resolve Base Board's `<folder>::<view>::<property>` lane order, with view-name shorthand for API callers. */
|
|
74
|
+
export declare function resolveBaseBoardColumnOrder(definition: KnowledgeBaseDefinition, view: KnowledgeBaseView, configured: Readonly<Record<string, readonly string[]>>): string[];
|
|
@@ -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
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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
|
-
*
|
|
56
|
-
*
|
|
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. */
|
|
@@ -101,6 +73,19 @@ export declare const KNOWLEDGE_BOOTSTRAP_SYNTHESIS_TIER: HostedAiLevel;
|
|
|
101
73
|
* it is a quality/latency knob, and the credit ceilings above remain the money guard.
|
|
102
74
|
*/
|
|
103
75
|
export declare const KNOWLEDGE_BOOTSTRAP_MAX_PAGES_READ = 5;
|
|
76
|
+
/**
|
|
77
|
+
* Per-call provider abort budget for every paid leg of the pass (LinkedIn company,
|
|
78
|
+
* person search/profile, page search, page read), in milliseconds.
|
|
79
|
+
*
|
|
80
|
+
* The platform default is 120 s, sized for a durable column run. This pass runs
|
|
81
|
+
* inside the worker's tenant-maintenance tick under a 300 s step deadline, so two
|
|
82
|
+
* hung LinkedIn calls at the default (measured 2026-09-15/16: the managed vendor
|
|
83
|
+
* accepted connections and never answered) consumed the whole budget before the
|
|
84
|
+
* page reads and the synthesis got a turn. 45 s keeps the worst case — five legs,
|
|
85
|
+
* every one hanging — inside the step, and a healthy vendor answers in well under
|
|
86
|
+
* it. This bounds latency only; the credit ceilings above remain the money guard.
|
|
87
|
+
*/
|
|
88
|
+
export declare const KNOWLEDGE_BOOTSTRAP_PROVIDER_TIMEOUT_MS = 45000;
|
|
104
89
|
/** Fleet-wide kill switch, matching OXYGEN_KNOWLEDGE_RETRIEVAL/AUTO_SOURCES_DISABLED. */
|
|
105
90
|
export declare const KNOWLEDGE_BOOTSTRAP_DISABLED_ENV_VAR = "OXYGEN_KNOWLEDGE_BOOTSTRAP_DISABLED";
|
|
106
91
|
/**
|
|
@@ -143,6 +128,9 @@ export declare function isKnowledgeBootstrapStatus(value: unknown): value is Kno
|
|
|
143
128
|
* same thing.
|
|
144
129
|
*/
|
|
145
130
|
export type KnowledgeBootstrapMarker = {
|
|
131
|
+
/** Versioned claims can recover a crash before the first dispatch checkpoint.
|
|
132
|
+
* Absence identifies legacy attempts whose dispatch history is unknown. */
|
|
133
|
+
execution_version?: 1;
|
|
146
134
|
status: KnowledgeBootstrapStatus;
|
|
147
135
|
/** ISO timestamp the claim was won. */
|
|
148
136
|
started_at: string | null;
|
|
@@ -194,6 +182,11 @@ export type KnowledgeBootstrapMarker = {
|
|
|
194
182
|
export declare const KNOWLEDGE_BOOTSTRAP_CONSENT_METADATA_KEY = "knowledge_bootstrap_consent";
|
|
195
183
|
/** The recorded consent, exactly as stored under the key above. */
|
|
196
184
|
export type KnowledgeBootstrapConsent = {
|
|
185
|
+
/** New workspace creation may authorize bounded creator-profile research. */
|
|
186
|
+
scope?: "company_and_creator";
|
|
187
|
+
domain?: string;
|
|
188
|
+
/** Canonical `/in/` profile supplied by this exact creator; never company evidence. */
|
|
189
|
+
creator_linkedin_url?: string;
|
|
197
190
|
/** ISO timestamp consent was recorded. Must parse; a marker that does not is not consent. */
|
|
198
191
|
granted_at: string;
|
|
199
192
|
/**
|
|
@@ -267,13 +260,14 @@ export declare const KNOWLEDGE_BOOTSTRAP_COVERAGE_IDEMPOTENCY_PREFIX = "knowledg
|
|
|
267
260
|
*/
|
|
268
261
|
export declare const KNOWLEDGE_BOOTSTRAP_RESEARCH_PAGE_SLUG = "company-research";
|
|
269
262
|
/**
|
|
270
|
-
* The typed `company_profile` sections
|
|
271
|
-
* "never clobber a profile a human already filled" gate
|
|
263
|
+
* The typed `company_profile` sections inspected by the automatic bootstrap's
|
|
264
|
+
* "never clobber a profile a human already filled" gate.
|
|
272
265
|
*
|
|
273
266
|
* `gtm_stack` and `custom` are structurally excluded: research of a company's public
|
|
274
267
|
* web presence says nothing about which tools it has connected here, and a workspace
|
|
275
268
|
* that filled only those has told us nothing about its company and must still be
|
|
276
|
-
* bootstrapped.
|
|
269
|
+
* bootstrapped. This is the existing-context guard's list, not a list of writes:
|
|
270
|
+
* current research writes company facts and stores inferred GTM hypotheses separately.
|
|
277
271
|
*/
|
|
278
272
|
export declare const KNOWLEDGE_BOOTSTRAP_PROFILE_SECTIONS: readonly ["company", "offering", "icp", "market"];
|
|
279
273
|
/**
|