thinkwork-cli 0.12.16 → 0.13.0

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 (149) hide show
  1. package/dist/cli.js +517 -10403
  2. package/dist/commands/enterprise/templates/deploy-repo/docs/runbook.md +3 -8
  3. package/dist/commands/enterprise/templates/deploy-repo/terraform/main.tf +1 -184
  4. package/dist/drizzle/0050_brain_v0_entity_subtype.sql +2 -2
  5. package/dist/drizzle/0051_brain_v0_tenant_entity_pages.sql +6 -6
  6. package/dist/drizzle/0052_brain_v0_external_refs.sql +2 -2
  7. package/dist/drizzle/0053_brain_v0_unresolved_mentions_subtype.sql +2 -2
  8. package/dist/drizzle/0145_knowledge_graph_thread_ingest.sql +14 -14
  9. package/dist/drizzle/0146_knowledge_graph_source_scope.sql +1 -1
  10. package/dist/drizzle/0157_knowledge_graph_observations_source.sql +2 -2
  11. package/dist/drizzle/0158_wiki_tenant_scope.sql +1 -1
  12. package/dist/drizzle/0165_plugin_premium_entitlements.sql +2 -2
  13. package/dist/drizzle/{0166_company_brain_substrate_contract.sql → 0166_brain_substrate_contract.sql} +9 -9
  14. package/dist/drizzle/{0167_company_brain_artifact_manifest_runtime.sql → 0167_brain_artifact_manifest_runtime.sql} +5 -5
  15. package/dist/drizzle/0168_eval_skill_gate.sql +1 -1
  16. package/dist/drizzle/0183_okf_artifact_manifests.sql +1 -1
  17. package/dist/drizzle/0200_eval_results_execution_tier.sql +40 -0
  18. package/dist/drizzle/0201_brain_dream_runs.sql +72 -0
  19. package/dist/drizzle/0202_backfill_thread_participants_from_senders.sql +110 -0
  20. package/dist/drizzle/0203_threads_mode_override.sql +61 -0
  21. package/dist/drizzle/0204_thread_turns_triggering_message_id.sql +48 -0
  22. package/dist/drizzle/0205_routines_git_python.sql +185 -0
  23. package/dist/drizzle/0206_agent_loop_versions_routine_actions.sql +40 -0
  24. package/dist/drizzle/0207_tenant_credentials_github_repo_kind.sql +52 -0
  25. package/dist/drizzle/0208_knowledge_graph_neutral_identifiers.sql +253 -0
  26. package/dist/drizzle/0209_living_artifacts_schema.sql +109 -0
  27. package/dist/drizzle/0210_think137_automations_additive_schema.sql +113 -0
  28. package/dist/drizzle/0211_backfill_agent_loop_versions_target_spec.sql +120 -0
  29. package/dist/drizzle/0212_fold_webhooks_into_automations.sql +312 -0
  30. package/dist/drizzle/0213_agent_loop_versions_judge_evidence_nullable.sql +63 -0
  31. package/dist/drizzle/0214_think137_drop_judge_evidence_roi.sql +99 -0
  32. package/dist/drizzle/0215_agent_loop_versions_goal_worker_policy_nullable.sql +70 -0
  33. package/dist/drizzle/0216_drop_agent_loop_versions_goal_worker_policy.sql +81 -0
  34. package/dist/drizzle/0217_agents_capability_folder_dispatch.sql +12 -0
  35. package/dist/drizzle/0218_document_plates.sql +30 -0
  36. package/dist/drizzle/0219_document_section_waivers.sql +29 -0
  37. package/dist/drizzle/0220_artifacts_created_by_user.sql +21 -0
  38. package/dist/drizzle/0221_artifact_shares.sql +29 -0
  39. package/dist/drizzle/0221_workflow_interpreter_thin_slice.sql +71 -0
  40. package/dist/drizzle/0222_compliance_drainer_select_audit_events.sql +21 -0
  41. package/dist/drizzle/0223_document_conformance_reports.sql +38 -0
  42. package/dist/drizzle/0224_artifact_refresh_state.sql +11 -0
  43. package/dist/drizzle/0225_think216_agent_loop_convergence.sql +21 -0
  44. package/dist/drizzle/0226_drop_n8n_agent_step_runs.sql +6 -0
  45. package/dist/drizzle/0227_analyst_reader_role.sql +1055 -0
  46. package/dist/drizzle/0228_tenant_credentials_rds_iam_kind.sql +62 -0
  47. package/dist/drizzle/0229_analyst_reader_rds_iam_grant.sql +79 -0
  48. package/dist/drizzle/0230_analyst_rls.sql +1613 -0
  49. package/dist/drizzle/0231_cost_events_cache_write_enforcement_exempt.sql +11 -0
  50. package/dist/plugins/catalog/package.json +0 -1
  51. package/dist/plugins/catalog/src/__tests__/build-catalog.test.ts +0 -1
  52. package/dist/plugins/catalog/src/__tests__/catalog.test.ts +16 -8
  53. package/dist/plugins/catalog/src/__tests__/contracts.test.ts +56 -0
  54. package/dist/plugins/catalog/src/__tests__/plugin-package.test.ts +0 -2
  55. package/dist/plugins/catalog/src/__tests__/plugin-registry.test.ts +0 -5
  56. package/dist/plugins/catalog/src/contracts.ts +112 -0
  57. package/dist/plugins/catalog/src/registry/generated-first-party.ts +0 -6
  58. package/dist/plugins/n8n/README.md +4 -36
  59. package/dist/plugins/n8n/src/deployment/managed-app.ts +0 -17
  60. package/dist/plugins/n8n/src/manifest.ts +0 -5
  61. package/dist/plugins/n8n/terraform/n8n/main.tf +0 -26
  62. package/dist/plugins/n8n/terraform/n8n/outputs.tf +0 -5
  63. package/dist/plugins/n8n/terraform/n8n/variables.tf +0 -6
  64. package/dist/plugins/n8n/test/manifest.test.ts +0 -1
  65. package/dist/plugins/twenty/package.json +5 -0
  66. package/dist/plugins/twenty/scripts/lib/__tests__/load-records.test.ts +232 -0
  67. package/dist/plugins/twenty/scripts/lib/__tests__/mappers.test.ts +489 -0
  68. package/dist/plugins/twenty/scripts/lib/__tests__/members-ensure.test.ts +199 -0
  69. package/dist/plugins/twenty/scripts/lib/__tests__/provision-members-db.test.ts +43 -0
  70. package/dist/plugins/twenty/scripts/lib/__tests__/schema-ensure.test.ts +230 -0
  71. package/dist/plugins/twenty/scripts/lib/__tests__/twenty-client.test.ts +207 -0
  72. package/dist/plugins/twenty/scripts/lib/lastmile-reader.ts +282 -0
  73. package/dist/plugins/twenty/scripts/lib/load-attachments.ts +220 -0
  74. package/dist/plugins/twenty/scripts/lib/load-records.ts +680 -0
  75. package/dist/plugins/twenty/scripts/lib/mappers.ts +559 -0
  76. package/dist/plugins/twenty/scripts/lib/members-ensure.ts +381 -0
  77. package/dist/plugins/twenty/scripts/lib/provision-members-db.ts +191 -0
  78. package/dist/plugins/twenty/scripts/lib/schema-ensure.ts +425 -0
  79. package/dist/plugins/twenty/scripts/lib/twenty-client.ts +246 -0
  80. package/dist/plugins/twenty/scripts/migrate-lastmile.ts +610 -0
  81. package/dist/plugins/twenty/scripts/provision-twenty-members.ts +214 -0
  82. package/dist/plugins/twenty/src/manifest.ts +99 -0
  83. package/dist/plugins/twenty/terraform/twenty/README.md +1 -1
  84. package/dist/plugins/twenty/test/manifest.test.ts +12 -2
  85. package/dist/terraform/examples/greenfield/main.tf +90 -380
  86. package/dist/terraform/examples/greenfield/terraform.tfvars.example +0 -1
  87. package/dist/terraform/modules/app/agentcore-pi/README.md +1 -3
  88. package/dist/terraform/modules/app/agentcore-pi/main.tf +29 -38
  89. package/dist/terraform/modules/app/agentcore-pi/variables.tf +23 -15
  90. package/dist/terraform/modules/app/agentcore-runtime/main.tf +3 -3
  91. package/dist/terraform/modules/app/customer-domain/main.tf +16 -2
  92. package/dist/terraform/modules/app/deployment-control-plane/main.tf +5 -0
  93. package/dist/terraform/modules/app/deployment-control-plane/runner.py +85 -207
  94. package/dist/terraform/modules/app/deployment-control-plane/test_runner_bundle.py +56 -2
  95. package/dist/terraform/modules/app/hindsight-memory/main.tf +52 -4
  96. package/dist/terraform/modules/app/lambda-api/eval-fanout.tf +7 -0
  97. package/dist/terraform/modules/app/lambda-api/handlers.tf +493 -89
  98. package/dist/terraform/modules/app/lambda-api/iam-grouped.tf +215 -96
  99. package/dist/terraform/modules/app/lambda-api/main.tf +24 -5
  100. package/dist/terraform/modules/app/lambda-api/outputs.tf +19 -11
  101. package/dist/terraform/modules/app/lambda-api/variables.tf +94 -67
  102. package/dist/terraform/modules/app/ses-email/main.tf +14 -2
  103. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/main.tf +493 -0
  104. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/outputs.tf +34 -0
  105. package/dist/terraform/modules/app/workflow-interpreter-stepfunctions/variables.tf +32 -0
  106. package/dist/terraform/modules/data/aurora-postgres/main.tf +55 -0
  107. package/dist/terraform/modules/data/aurora-postgres/outputs.tf +15 -0
  108. package/dist/terraform/modules/data/s3-buckets/main.tf +26 -0
  109. package/dist/terraform/modules/thinkwork/README.md +0 -1
  110. package/dist/terraform/modules/thinkwork/main.tf +167 -446
  111. package/dist/terraform/modules/thinkwork/outputs.tf +2 -142
  112. package/dist/terraform/modules/thinkwork/variables.tf +55 -309
  113. package/dist/terraform/schema.graphql +4 -0
  114. package/dist/workspace-defaults/files/AGENTS.md +45 -31
  115. package/dist/workspace-defaults/files/CONTEXT.md +48 -4
  116. package/dist/workspace-defaults/files/GUARDRAILS.md +18 -0
  117. package/dist/workspace-defaults/files/MEMORY_GUIDE.md +32 -4
  118. package/dist/workspace-defaults/files/SPACE.md +34 -19
  119. package/dist/workspace-defaults/files/TOOLS.md +30 -0
  120. package/dist/workspace-defaults/files/skills/document-composer/SKILL.md +154 -0
  121. package/dist/workspace-defaults/files/skills/document-composer/references/authoring-rules.md +101 -0
  122. package/package.json +1 -1
  123. package/dist/plugins/company-brain/README.md +0 -37
  124. package/dist/plugins/company-brain/package.json +0 -33
  125. package/dist/plugins/company-brain/runtime/cognee/Dockerfile +0 -11
  126. package/dist/plugins/company-brain/smoke/cognee-managed-app-smoke.mjs +0 -458
  127. package/dist/plugins/company-brain/smoke/cognee-memory-cutover-smoke.mjs +0 -539
  128. package/dist/plugins/company-brain/smoke/company-brain-context-engine-smoke.mjs +0 -312
  129. package/dist/plugins/company-brain/smoke/company-brain-operations-smoke.mjs +0 -449
  130. package/dist/plugins/company-brain/smoke/company-brain-plugin-smoke.mjs +0 -655
  131. package/dist/plugins/company-brain/smoke/hindsight-memory-isolation-smoke.mjs +0 -474
  132. package/dist/plugins/company-brain/src/api/cognee-client.ts +0 -896
  133. package/dist/plugins/company-brain/src/api/cognee-cluster-identity.ts +0 -49
  134. package/dist/plugins/company-brain/src/api/cognee-memory-scope.ts +0 -84
  135. package/dist/plugins/company-brain/src/api/context-engine-provider.ts +0 -888
  136. package/dist/plugins/company-brain/src/api/migration.ts +0 -665
  137. package/dist/plugins/company-brain/src/deployment/cognee-managed-app.ts +0 -396
  138. package/dist/plugins/company-brain/src/index.ts +0 -62
  139. package/dist/plugins/company-brain/src/manifest.ts +0 -112
  140. package/dist/plugins/company-brain/terraform/cognee/README.md +0 -263
  141. package/dist/plugins/company-brain/terraform/cognee/main.tf +0 -766
  142. package/dist/plugins/company-brain/terraform/cognee/outputs.tf +0 -144
  143. package/dist/plugins/company-brain/terraform/cognee/variables.tf +0 -402
  144. package/dist/plugins/company-brain/test/api/cognee-client.test.ts +0 -562
  145. package/dist/plugins/company-brain/test/api/cognee-memory-scope.test.ts +0 -74
  146. package/dist/plugins/company-brain/test/api/context-engine-provider.test.ts +0 -447
  147. package/dist/plugins/company-brain/test/api/migration.test.ts +0 -465
  148. package/dist/plugins/company-brain/test/manifest.test.ts +0 -95
  149. package/dist/plugins/company-brain/tsconfig.json +0 -8
@@ -1,896 +0,0 @@
1
- export interface KnowledgeGraphOntologyExport {
2
- mechanism: "cognee_owl_ontology" | "custom_prompt";
3
- entityTypes: unknown[];
4
- relationshipTypes: unknown[];
5
- customPrompt: string;
6
- ontologyKey: string | null;
7
- ontologyOwlXml: string | null;
8
- }
9
-
10
- export interface CogneeGraphNode {
11
- id: string;
12
- label: string;
13
- type?: string | null;
14
- properties?: Record<string, unknown> | null;
15
- }
16
-
17
- export interface CogneeGraphEdge {
18
- id?: string | null;
19
- source: string;
20
- target: string;
21
- label: string;
22
- type?: string | null;
23
- properties?: Record<string, unknown> | null;
24
- }
25
-
26
- export interface CogneeGraphPayload {
27
- nodes: CogneeGraphNode[];
28
- edges: CogneeGraphEdge[];
29
- }
30
-
31
- export interface CogneeIngestResult {
32
- datasetId: string | null;
33
- datasetName: string;
34
- mode: "remember" | "add_cognify";
35
- pipelineRunId: string | null;
36
- raw: unknown;
37
- }
38
-
39
- export type CogneeDatasetStatus =
40
- | "completed"
41
- | "errored"
42
- | "running"
43
- | "unknown";
44
-
45
- export interface CogneeDatasetStatusSnapshot {
46
- status: CogneeDatasetStatus;
47
- rawStatus: string | null;
48
- raw: unknown;
49
- }
50
-
51
- export interface CogneeDatasetWaitResult {
52
- status: CogneeDatasetStatus;
53
- rawStatus: string | null;
54
- attempts: number;
55
- elapsedMs: number;
56
- samples: CogneeDatasetStatusSnapshot[];
57
- }
58
-
59
- export type CogneeDocumentSourceKind =
60
- | "thread"
61
- | "wiki"
62
- | "brain"
63
- | "observations"
64
- | "user_memory"
65
- | "space_memory";
66
-
67
- export interface CogneeDocumentIngestArgs {
68
- tenantId: string;
69
- sourceKind: CogneeDocumentSourceKind;
70
- sourceRef: string;
71
- datasetName: string;
72
- document: string;
73
- filename: string;
74
- ontology: KnowledgeGraphOntologyExport;
75
- customPrompt?: string | null;
76
- }
77
-
78
- export interface CogneeSearchArgs {
79
- query: string;
80
- searchType?: string;
81
- datasets?: string[];
82
- datasetIds?: string[];
83
- nodeNames?: string[];
84
- nodeNameFilterOperator?: "AND" | "OR";
85
- topK?: number;
86
- onlyContext?: boolean;
87
- includeReferences?: boolean;
88
- systemPrompt?: string | null;
89
- }
90
-
91
- export class CogneeClientError extends Error {
92
- /** HTTP status when the error originated from a non-2xx Cognee response. */
93
- readonly status?: number;
94
- constructor(message: string, status?: number) {
95
- super(message);
96
- this.name = "CogneeClientError";
97
- this.status = status;
98
- }
99
- }
100
-
101
- export class CogneeClient {
102
- private readonly endpoint: string;
103
- private readonly token: string | null;
104
- private readonly fetchFn: typeof fetch;
105
- private readonly mode: "remember" | "add_cognify";
106
- private readonly retryAttempts: number;
107
- private readonly retryDelayMs: number;
108
- private readonly indexPollMs: number;
109
- private readonly indexTimeoutMs: number;
110
-
111
- constructor(
112
- opts: {
113
- endpoint?: string | null;
114
- token?: string | null;
115
- fetchFn?: typeof fetch;
116
- mode?: "remember" | "add_cognify" | null;
117
- retryAttempts?: number;
118
- retryDelayMs?: number;
119
- indexPollMs?: number;
120
- indexTimeoutMs?: number;
121
- } = {},
122
- ) {
123
- const endpoint = opts.endpoint ?? process.env.COGNEE_ENDPOINT;
124
- if (!endpoint) {
125
- throw new CogneeClientError("COGNEE_ENDPOINT is not configured");
126
- }
127
- this.endpoint = endpoint.replace(/\/+$/, "");
128
- this.token = opts.token ?? process.env.COGNEE_API_KEY ?? null;
129
- this.fetchFn = opts.fetchFn ?? fetch;
130
- this.mode =
131
- opts.mode ??
132
- (process.env.COGNEE_INGEST_MODE === "add_cognify"
133
- ? "add_cognify"
134
- : "remember");
135
- this.retryAttempts =
136
- opts.retryAttempts ?? readPositiveIntEnv("COGNEE_HTTP_RETRY_ATTEMPTS", 3);
137
- this.retryDelayMs =
138
- opts.retryDelayMs ??
139
- readPositiveIntEnv("COGNEE_HTTP_RETRY_DELAY_MS", 250);
140
- this.indexPollMs =
141
- opts.indexPollMs ?? readPositiveIntEnv("COGNEE_INDEX_POLL_MS", 5_000);
142
- this.indexTimeoutMs =
143
- opts.indexTimeoutMs ??
144
- readPositiveIntEnv("COGNEE_INDEX_TIMEOUT_MS", 240_000);
145
- }
146
-
147
- async ingestThread(args: {
148
- tenantId: string;
149
- threadId: string;
150
- datasetName: string;
151
- transcript: string;
152
- ontology: KnowledgeGraphOntologyExport;
153
- }): Promise<CogneeIngestResult> {
154
- return this.ingestDocument({
155
- tenantId: args.tenantId,
156
- sourceKind: "thread",
157
- sourceRef: args.threadId,
158
- datasetName: args.datasetName,
159
- document: args.transcript,
160
- filename: "thinkwork-thread.md",
161
- ontology: args.ontology,
162
- });
163
- }
164
-
165
- async ingestDocument(
166
- args: CogneeDocumentIngestArgs,
167
- ): Promise<CogneeIngestResult> {
168
- await this.ensureOntology(args.ontology);
169
- if (this.mode === "add_cognify") {
170
- return this.addAndCognify(args);
171
- }
172
- try {
173
- return await this.remember(args);
174
- } catch (err) {
175
- if (!isUnsupportedRememberError(err)) throw err;
176
- return this.addAndCognify(args);
177
- }
178
- }
179
-
180
- async fetchDatasetGraph(datasetId: string): Promise<CogneeGraphPayload> {
181
- const payload = await this.requestJson(
182
- `/api/v1/datasets/${encodeURIComponent(datasetId)}/graph`,
183
- { method: "GET" },
184
- { retryTransient: true },
185
- );
186
- return parseGraphPayload(payload);
187
- }
188
-
189
- async search(args: CogneeSearchArgs): Promise<unknown> {
190
- return this.requestJson(
191
- "/api/v1/search",
192
- {
193
- method: "POST",
194
- headers: { "content-type": "application/json" },
195
- body: JSON.stringify({
196
- query: args.query,
197
- search_type: args.searchType ?? "GRAPH_COMPLETION",
198
- ...(args.datasets?.length ? { datasets: args.datasets } : {}),
199
- ...(args.datasetIds?.length ? { dataset_ids: args.datasetIds } : {}),
200
- ...(args.nodeNames?.length ? { node_name: args.nodeNames } : {}),
201
- ...(args.nodeNameFilterOperator
202
- ? { node_name_filter_operator: args.nodeNameFilterOperator }
203
- : {}),
204
- ...(args.topK ? { top_k: args.topK } : {}),
205
- ...(args.onlyContext !== undefined
206
- ? { only_context: args.onlyContext }
207
- : {}),
208
- ...(args.includeReferences !== undefined
209
- ? { include_references: args.includeReferences }
210
- : {}),
211
- ...(args.systemPrompt ? { system_prompt: args.systemPrompt } : {}),
212
- }),
213
- },
214
- { retryTransient: true },
215
- );
216
- }
217
-
218
- /** List datasets currently known to Cognee (id + name pairs). */
219
- async listDatasets(): Promise<Array<{ id: string; name: string }>> {
220
- const payload = await this.requestJson(
221
- "/api/v1/datasets",
222
- { method: "GET" },
223
- { retryTransient: true },
224
- );
225
- const rows = Array.isArray(payload)
226
- ? payload
227
- : Array.isArray((payload as Record<string, unknown>)?.datasets)
228
- ? ((payload as Record<string, unknown>).datasets as unknown[])
229
- : [];
230
- return rows
231
- .map((row) => row as Record<string, unknown>)
232
- .filter(
233
- (row) =>
234
- row && (row.id ?? row.dataset_id) && (row.name ?? row.dataset_name),
235
- )
236
- .map((row) => ({
237
- id: String(row.id ?? row.dataset_id),
238
- name: String(row.name ?? row.dataset_name),
239
- }));
240
- }
241
-
242
- /**
243
- * Delete every Cognee dataset whose name matches `datasetName`, so a
244
- * fullRebuild starts from an empty graph for that source instead of
245
- * appending to the accumulated one. Idempotent — a missing dataset is a
246
- * no-op. Returns the number of datasets deleted.
247
- */
248
- async deleteDatasetByName(datasetName: string): Promise<number> {
249
- let datasets: Array<{ id: string; name: string }>;
250
- try {
251
- datasets = await this.listDatasets();
252
- } catch {
253
- return 0;
254
- }
255
- const matches = datasets.filter((d) => d.name === datasetName);
256
- let deleted = 0;
257
- for (const match of matches) {
258
- await this.requestJson(
259
- `/api/v1/datasets/${encodeURIComponent(match.id)}`,
260
- { method: "DELETE" },
261
- { retryTransient: true },
262
- );
263
- deleted += 1;
264
- }
265
- return deleted;
266
- }
267
-
268
- /**
269
- * Nuclear clear of the entire Cognee store (all datasets + system graph).
270
- * Only safe in single-tenant/dev contexts — callers gate this behind an
271
- * explicit flag. Returns true on success.
272
- */
273
- async pruneAll(): Promise<boolean> {
274
- await this.requestJson(
275
- "/api/v1/prune",
276
- {
277
- method: "POST",
278
- headers: { "content-type": "application/json" },
279
- body: "{}",
280
- },
281
- { retryTransient: true },
282
- );
283
- return true;
284
- }
285
-
286
- async waitForDatasetIndexing(
287
- datasetId: string,
288
- ): Promise<CogneeDatasetWaitResult> {
289
- const started = Date.now();
290
- const samples: CogneeDatasetStatusSnapshot[] = [];
291
- let attempts = 0;
292
- let transientStatusErrors = 0;
293
-
294
- for (;;) {
295
- attempts += 1;
296
- // The single dogfood Cognee task makes the /datasets/status endpoint
297
- // briefly unavailable (502/503/504) while it writes the graph — but the
298
- // cognify pipeline still completes. Treat a transient status error as
299
- // "not ready yet, keep polling" within the index-timeout budget rather
300
- // than failing the whole run on a flaky window.
301
- let snapshot: CogneeDatasetStatusSnapshot;
302
- try {
303
- snapshot = await this.fetchDatasetStatus(datasetId);
304
- } catch (err) {
305
- const transient =
306
- err instanceof CogneeClientError &&
307
- (err.status === undefined || isTransientStatus(err.status));
308
- if (!transient || Date.now() - started >= this.indexTimeoutMs) {
309
- // Budget exhausted (or a non-transient error): before giving up,
310
- // probe the graph directly — if cognify finished during a status
311
- // outage, the graph has nodes and the run should proceed.
312
- const probed = await this.probeGraphComplete(datasetId);
313
- if (probed) {
314
- return {
315
- status: "completed",
316
- rawStatus: "completed_via_graph_probe",
317
- attempts,
318
- elapsedMs: Date.now() - started,
319
- samples: compactStatusSamples(samples),
320
- };
321
- }
322
- throw err;
323
- }
324
- transientStatusErrors += 1;
325
- await sleep(this.indexPollMs);
326
- continue;
327
- }
328
- samples.push(snapshot);
329
-
330
- if (snapshot.status === "completed") {
331
- return {
332
- status: "completed",
333
- rawStatus: snapshot.rawStatus,
334
- attempts,
335
- elapsedMs: Date.now() - started,
336
- samples: compactStatusSamples(samples),
337
- };
338
- }
339
- if (snapshot.status === "errored") {
340
- throw new CogneeClientError(
341
- `Cognee dataset ${datasetId} indexing failed with status ${snapshot.rawStatus ?? "errored"}`,
342
- );
343
- }
344
- if (Date.now() - started >= this.indexTimeoutMs) {
345
- // Timed out waiting for "completed" — but a flaky status endpoint can
346
- // report STARTED long after the pipeline actually finished. Probe the
347
- // graph before failing.
348
- const probed = await this.probeGraphComplete(datasetId);
349
- if (probed) {
350
- return {
351
- status: "completed",
352
- rawStatus: "completed_via_graph_probe",
353
- attempts,
354
- elapsedMs: Date.now() - started,
355
- samples: compactStatusSamples(samples),
356
- };
357
- }
358
- throw new CogneeClientError(
359
- `Cognee dataset ${datasetId} indexing did not complete within ${this.indexTimeoutMs}ms; latest status ${snapshot.rawStatus ?? "unknown"} (transient status errors: ${transientStatusErrors})`,
360
- );
361
- }
362
-
363
- await sleep(this.indexPollMs);
364
- }
365
- }
366
-
367
- /**
368
- * Best-effort check that a dataset's graph is populated, used as a fallback
369
- * when the status endpoint is flaky but the cognify pipeline may have
370
- * finished. Returns true only when the graph fetch succeeds with >=1 node.
371
- */
372
- private async probeGraphComplete(datasetId: string): Promise<boolean> {
373
- try {
374
- const graph = await this.fetchDatasetGraph(datasetId);
375
- return graph.nodes.length > 0;
376
- } catch {
377
- return false;
378
- }
379
- }
380
-
381
- async fetchDatasetStatus(
382
- datasetId: string,
383
- ): Promise<CogneeDatasetStatusSnapshot> {
384
- const payload = await this.requestJson(
385
- `/api/v1/datasets/status?dataset=${encodeURIComponent(datasetId)}`,
386
- { method: "GET" },
387
- { retryTransient: true },
388
- );
389
- return parseDatasetStatus(payload, datasetId);
390
- }
391
-
392
- private async remember(args: {
393
- tenantId: string;
394
- sourceKind: CogneeDocumentSourceKind;
395
- sourceRef: string;
396
- datasetName: string;
397
- document: string;
398
- filename: string;
399
- ontology: KnowledgeGraphOntologyExport;
400
- customPrompt?: string | null;
401
- }): Promise<CogneeIngestResult> {
402
- const body = buildDocumentForm(args, { runInBackground: true });
403
- const raw = await this.requestJson("/api/v1/remember", {
404
- method: "POST",
405
- body,
406
- });
407
- return {
408
- datasetId: extractDatasetId(raw),
409
- datasetName: args.datasetName,
410
- mode: "remember",
411
- pipelineRunId: extractPipelineRunId(raw),
412
- raw,
413
- };
414
- }
415
-
416
- private async addAndCognify(args: {
417
- tenantId: string;
418
- sourceKind: CogneeDocumentSourceKind;
419
- sourceRef: string;
420
- datasetName: string;
421
- document: string;
422
- filename: string;
423
- ontology: KnowledgeGraphOntologyExport;
424
- customPrompt?: string | null;
425
- }): Promise<CogneeIngestResult> {
426
- const addRaw = await this.requestJson("/api/v1/add", {
427
- method: "POST",
428
- body: buildDocumentForm(args, { runInBackground: false }),
429
- });
430
- const cognifyRaw = await this.requestJson("/api/v1/cognify", {
431
- method: "POST",
432
- headers: { "content-type": "application/json" },
433
- body: JSON.stringify({
434
- datasets: [args.datasetName],
435
- run_in_background: true,
436
- custom_prompt: buildCustomPrompt(args),
437
- ...(args.ontology.ontologyKey
438
- ? { ontology_key: [args.ontology.ontologyKey] }
439
- : {}),
440
- }),
441
- });
442
- return {
443
- datasetId: extractDatasetId(cognifyRaw) ?? extractDatasetId(addRaw),
444
- datasetName: args.datasetName,
445
- mode: "add_cognify",
446
- pipelineRunId: extractPipelineRunId(cognifyRaw),
447
- raw: { add: addRaw, cognify: cognifyRaw },
448
- };
449
- }
450
-
451
- private async ensureOntology(
452
- ontology: KnowledgeGraphOntologyExport,
453
- ): Promise<void> {
454
- if (!ontology.ontologyKey || !ontology.ontologyOwlXml) return;
455
- const existing = await this.requestJson(
456
- "/api/v1/ontologies",
457
- {
458
- method: "GET",
459
- },
460
- { retryTransient: true },
461
- );
462
- if (hasOntologyKey(existing, ontology.ontologyKey)) return;
463
-
464
- const form = new FormData();
465
- form.append("ontology_key", ontology.ontologyKey);
466
- form.append(
467
- "ontology_file",
468
- new Blob([ontology.ontologyOwlXml], { type: "application/xml" }),
469
- `${ontology.ontologyKey}.owl`,
470
- );
471
- form.append(
472
- "description",
473
- "ThinkWork approved ontology export for thread graph extraction",
474
- );
475
-
476
- try {
477
- await this.requestJson("/api/v1/ontologies", {
478
- method: "POST",
479
- body: form,
480
- });
481
- } catch (err) {
482
- if (isDuplicateOntologyError(err, ontology.ontologyKey)) return;
483
- throw err;
484
- }
485
- }
486
-
487
- private async requestJson(
488
- path: string,
489
- init: RequestInit,
490
- opts: { retryTransient?: boolean } = {},
491
- ): Promise<unknown> {
492
- let attempt = 0;
493
- for (;;) {
494
- try {
495
- const response = await this.fetchFn(`${this.endpoint}${path}`, {
496
- ...init,
497
- headers: {
498
- ...(this.token ? { authorization: `Bearer ${this.token}` } : {}),
499
- ...(init.headers ?? {}),
500
- },
501
- });
502
- const text = await response.text();
503
- const payload = text ? safeJson(text) : {};
504
- if (response.ok) return payload;
505
- const error = new CogneeClientError(
506
- `Cognee ${path} failed with ${response.status}: ${summarizePayload(payload)}`,
507
- response.status,
508
- );
509
- if (
510
- !opts.retryTransient ||
511
- !isTransientStatus(response.status) ||
512
- attempt >= this.retryAttempts - 1
513
- ) {
514
- throw error;
515
- }
516
- } catch (err) {
517
- if (
518
- !opts.retryTransient ||
519
- err instanceof CogneeClientError ||
520
- attempt >= this.retryAttempts - 1
521
- ) {
522
- throw err;
523
- }
524
- }
525
- attempt += 1;
526
- await sleep(this.retryDelayMs * attempt);
527
- }
528
- }
529
- }
530
-
531
- function buildDocumentForm(
532
- args: {
533
- tenantId: string;
534
- sourceKind: CogneeDocumentSourceKind;
535
- sourceRef: string;
536
- datasetName: string;
537
- document: string;
538
- filename: string;
539
- ontology: KnowledgeGraphOntologyExport;
540
- customPrompt?: string | null;
541
- },
542
- opts: { runInBackground: boolean },
543
- ): FormData {
544
- const form = new FormData();
545
- form.append(
546
- "data",
547
- new Blob([args.document], { type: "text/markdown" }),
548
- args.filename,
549
- );
550
- form.append("datasetName", args.datasetName);
551
- form.append("run_in_background", opts.runInBackground ? "true" : "false");
552
- for (const nodeSet of buildNodeSets(
553
- args.tenantId,
554
- args.sourceKind,
555
- args.sourceRef,
556
- )) {
557
- form.append("node_set", nodeSet);
558
- }
559
- if (args.ontology.ontologyKey) {
560
- form.append("ontology_key", args.ontology.ontologyKey);
561
- }
562
- form.append("custom_prompt", buildCustomPrompt(args));
563
- return form;
564
- }
565
-
566
- function buildNodeSets(
567
- tenantId: string,
568
- sourceKind: CogneeDocumentSourceKind,
569
- sourceRef: string,
570
- ): string[] {
571
- if (sourceKind === "user_memory" || sourceKind === "space_memory") {
572
- const scopeKind = sourceKind === "user_memory" ? "user" : "space";
573
- return [
574
- "thinkwork_memory",
575
- "thinkwork_memory_v1",
576
- `thinkwork_${scopeKind}_memory`,
577
- `tenant_${scopeToken(tenantId)}`,
578
- `${scopeKind}_${scopeToken(sourceRef)}`,
579
- ];
580
- }
581
-
582
- return [
583
- `thinkwork_${sourceKind}`,
584
- `tenant_${scopeToken(tenantId)}`,
585
- `${sourceKind}_${scopeToken(sourceRef)}`,
586
- ];
587
- }
588
-
589
- function buildCustomPrompt(args: {
590
- sourceKind: CogneeDocumentSourceKind;
591
- ontology: KnowledgeGraphOntologyExport;
592
- customPrompt?: string | null;
593
- }): string {
594
- const sourcePrompt =
595
- args.sourceKind === "thread"
596
- ? ""
597
- : [
598
- "",
599
- "Source packet instructions:",
600
- "- Prefer declared entity titles and ontology_type_slug fields over invented generic labels.",
601
- "- Preserve source_packet, citation, page, and section identifiers in properties.",
602
- "- Treat relationship_hint lines as candidate relationships only when the label matches the approved ontology.",
603
- ].join("\n");
604
- return [args.ontology.customPrompt, args.customPrompt, sourcePrompt]
605
- .filter(Boolean)
606
- .join("\n\n");
607
- }
608
-
609
- function scopeToken(value: string): string {
610
- return value
611
- .trim()
612
- .toLowerCase()
613
- .replace(/[^a-z0-9]+/g, "_")
614
- .replace(/^_+|_+$/g, "");
615
- }
616
-
617
- function parseGraphPayload(payload: unknown): CogneeGraphPayload {
618
- const record = payload as Record<string, unknown>;
619
- const nodes = Array.isArray(record.nodes) ? record.nodes : [];
620
- const edges = Array.isArray(record.edges) ? record.edges : [];
621
- return {
622
- nodes: nodes
623
- .map((node) => node as Record<string, unknown>)
624
- .filter((node) => node.id && node.label)
625
- .map((node) => {
626
- const {
627
- id: _id,
628
- label: _label,
629
- type: _type,
630
- properties,
631
- ...rest
632
- } = node;
633
- return {
634
- id: String(node.id),
635
- label: String(node.label),
636
- type: typeof node.type === "string" ? node.type : null,
637
- properties: mergeProperties(rest, properties),
638
- };
639
- }),
640
- edges: edges
641
- .map((edge) => edge as Record<string, unknown>)
642
- .filter((edge) => edge.source && edge.target && coerceEdgeLabel(edge))
643
- .map((edge) => {
644
- const {
645
- id: _id,
646
- source: _source,
647
- target: _target,
648
- label: _label,
649
- type: _type,
650
- properties,
651
- ...rest
652
- } = edge;
653
- const edgeLabel = coerceEdgeLabel(edge)!;
654
- return {
655
- id: edge.id ? String(edge.id) : null,
656
- source: String(edge.source),
657
- target: String(edge.target),
658
- label: edgeLabel,
659
- type:
660
- typeof edge.type === "string"
661
- ? edge.type
662
- : typeof edge.relationship_type === "string"
663
- ? edge.relationship_type
664
- : null,
665
- properties: mergeProperties(rest, properties),
666
- };
667
- }),
668
- };
669
- }
670
-
671
- function coerceEdgeLabel(edge: Record<string, unknown>): string | null {
672
- for (const key of ["label", "relationship_type", "type"]) {
673
- if (typeof edge[key] === "string" && edge[key].trim()) {
674
- return edge[key];
675
- }
676
- }
677
- return null;
678
- }
679
-
680
- function extractDatasetId(payload: unknown): string | null {
681
- const seen = new Set<unknown>();
682
- const visit = (value: unknown): string | null => {
683
- if (!value || typeof value !== "object" || seen.has(value)) return null;
684
- seen.add(value);
685
- const record = value as Record<string, unknown>;
686
- for (const key of ["dataset_id", "datasetId", "id"]) {
687
- if (typeof record[key] === "string" && looksLikeUuid(record[key])) {
688
- return record[key];
689
- }
690
- }
691
- for (const nested of Object.values(record)) {
692
- const found = visit(nested);
693
- if (found) return found;
694
- }
695
- return null;
696
- };
697
- return visit(payload);
698
- }
699
-
700
- function extractPipelineRunId(payload: unknown): string | null {
701
- const seen = new Set<unknown>();
702
- const visit = (value: unknown): string | null => {
703
- if (!value || typeof value !== "object" || seen.has(value)) return null;
704
- seen.add(value);
705
- const record = value as Record<string, unknown>;
706
- for (const key of ["pipeline_run_id", "pipelineRunId", "run_id", "runId"]) {
707
- if (typeof record[key] === "string" && looksLikeUuid(record[key])) {
708
- return record[key];
709
- }
710
- }
711
- for (const nested of Object.values(record)) {
712
- const found = visit(nested);
713
- if (found) return found;
714
- }
715
- return null;
716
- };
717
- return visit(payload);
718
- }
719
-
720
- function parseDatasetStatus(
721
- payload: unknown,
722
- datasetId: string,
723
- ): CogneeDatasetStatusSnapshot {
724
- const rawStatus =
725
- findDatasetStatusString(payload, datasetId) ?? findStatusString(payload);
726
- return {
727
- status: normalizeDatasetStatus(rawStatus),
728
- rawStatus,
729
- raw: payload,
730
- };
731
- }
732
-
733
- function findDatasetStatusString(
734
- payload: unknown,
735
- datasetId: string,
736
- ): string | null {
737
- if (!payload || typeof payload !== "object") return null;
738
- const record = payload as Record<string, unknown>;
739
- const datasetValue = record[datasetId];
740
- if (typeof datasetValue === "string") return datasetValue;
741
- if (datasetValue && typeof datasetValue === "object") {
742
- return findStatusString(datasetValue);
743
- }
744
- return null;
745
- }
746
-
747
- function findStatusString(payload: unknown): string | null {
748
- const seen = new Set<unknown>();
749
- const visit = (value: unknown): string | null => {
750
- if (!value || typeof value !== "object" || seen.has(value)) return null;
751
- seen.add(value);
752
- const record = value as Record<string, unknown>;
753
- for (const key of ["status", "pipeline_status", "run_status"]) {
754
- if (typeof record[key] === "string" && record[key].trim()) {
755
- return record[key];
756
- }
757
- }
758
- for (const nested of Object.values(record)) {
759
- if (typeof nested === "string" && /PROCESSING|PIPELINE/i.test(nested)) {
760
- return nested;
761
- }
762
- const found = visit(nested);
763
- if (found) return found;
764
- }
765
- return null;
766
- };
767
- return typeof payload === "string" ? payload : visit(payload);
768
- }
769
-
770
- function normalizeDatasetStatus(status: string | null): CogneeDatasetStatus {
771
- if (!status) return "unknown";
772
- const normalized = status.toUpperCase();
773
- if (/COMPLETED|ALREADY_COMPLETED|SUCCESS|SUCCEEDED/.test(normalized)) {
774
- return "completed";
775
- }
776
- if (/ERRORED|ERROR|FAILED|FAILURE/.test(normalized)) return "errored";
777
- if (/INITIATED|STARTED|RUNNING|PROCESSING|QUEUED|YIELD/.test(normalized)) {
778
- return "running";
779
- }
780
- return "unknown";
781
- }
782
-
783
- function compactStatusSamples(
784
- samples: CogneeDatasetStatusSnapshot[],
785
- ): CogneeDatasetStatusSnapshot[] {
786
- const compacted: CogneeDatasetStatusSnapshot[] = [];
787
- for (const sample of samples) {
788
- const previous = compacted[compacted.length - 1];
789
- if (
790
- previous?.status === sample.status &&
791
- previous.rawStatus === sample.rawStatus
792
- ) {
793
- continue;
794
- }
795
- compacted.push(sample);
796
- }
797
- return compacted.slice(-8);
798
- }
799
-
800
- function isUnsupportedRememberError(err: unknown): boolean {
801
- const message = (err as Error)?.message ?? "";
802
- return /\b(404|405|501)\b/.test(message);
803
- }
804
-
805
- function isDuplicateOntologyError(err: unknown, ontologyKey: string): boolean {
806
- const message = (err as Error)?.message ?? "";
807
- return (
808
- /\b400\b/.test(message) &&
809
- message.includes("already exists") &&
810
- message.includes(ontologyKey)
811
- );
812
- }
813
-
814
- function hasOntologyKey(payload: unknown, ontologyKey: string): boolean {
815
- return Boolean(
816
- payload &&
817
- typeof payload === "object" &&
818
- Object.prototype.hasOwnProperty.call(payload, ontologyKey),
819
- );
820
- }
821
-
822
- function looksLikeUuid(value: unknown): value is string {
823
- return (
824
- typeof value === "string" &&
825
- /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(
826
- value,
827
- )
828
- );
829
- }
830
-
831
- function asRecord(value: unknown): Record<string, unknown> {
832
- return value && typeof value === "object" && !Array.isArray(value)
833
- ? (value as Record<string, unknown>)
834
- : {};
835
- }
836
-
837
- function mergeProperties(
838
- topLevel: Record<string, unknown>,
839
- properties: unknown,
840
- ): Record<string, unknown> {
841
- return { ...topLevel, ...asRecord(properties) };
842
- }
843
-
844
- function safeJson(text: string): unknown {
845
- try {
846
- return JSON.parse(text);
847
- } catch {
848
- return { text };
849
- }
850
- }
851
-
852
- function summarizePayload(payload: unknown): string {
853
- const rawText =
854
- typeof payload === "string"
855
- ? payload
856
- : payload &&
857
- typeof payload === "object" &&
858
- typeof (payload as Record<string, unknown>).text === "string"
859
- ? String((payload as Record<string, unknown>).text)
860
- : null;
861
- if (rawText) {
862
- const htmlSummary = summarizeHtml(rawText);
863
- return (htmlSummary ?? rawText).slice(0, 500);
864
- }
865
- return JSON.stringify(payload ?? {}).slice(0, 500);
866
- }
867
-
868
- function summarizeHtml(text: string): string | null {
869
- if (!/<html[\s>]/i.test(text) && !/<body[\s>]/i.test(text)) return null;
870
- const title =
871
- text.match(/<title[^>]*>(.*?)<\/title>/is)?.[1] ??
872
- text.match(/<h1[^>]*>(.*?)<\/h1>/is)?.[1] ??
873
- null;
874
- return title ? stripHtml(title).trim() : "HTML error response";
875
- }
876
-
877
- function stripHtml(value: string): string {
878
- return value.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ");
879
- }
880
-
881
- function isTransientStatus(status: number): boolean {
882
- return [502, 503, 504].includes(status);
883
- }
884
-
885
- function sleep(ms: number): Promise<void> {
886
- return ms > 0
887
- ? new Promise((resolve) => setTimeout(resolve, ms))
888
- : Promise.resolve();
889
- }
890
-
891
- function readPositiveIntEnv(name: string, fallback: number): number {
892
- const raw = process.env[name];
893
- if (!raw) return fallback;
894
- const parsed = Number.parseInt(raw, 10);
895
- return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
896
- }