@oxygen-agent/cli 1.1014.135 → 1.1014.618

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 (292) hide show
  1. package/README.md +1 -1
  2. package/dist/admin-primary-providers-render.js +27 -19
  3. package/dist/cli-output.d.ts +12 -0
  4. package/dist/cli-output.js +395 -0
  5. package/dist/cli-paths.d.ts +1 -0
  6. package/dist/cli-paths.js +3 -0
  7. package/dist/cli-profile-context.d.ts +3 -0
  8. package/dist/cli-profile-context.js +13 -0
  9. package/dist/command-manifest.d.ts +15 -0
  10. package/dist/command-manifest.js +23 -1
  11. package/dist/command-support.d.ts +438 -0
  12. package/dist/command-support.js +1610 -0
  13. package/dist/commands/action-column.d.ts +3 -0
  14. package/dist/commands/action-column.js +43 -0
  15. package/dist/commands/activation.d.ts +3 -0
  16. package/dist/commands/activation.js +24 -0
  17. package/dist/commands/admin.d.ts +3 -0
  18. package/dist/commands/admin.js +347 -0
  19. package/dist/commands/ads.d.ts +3 -0
  20. package/dist/commands/ads.js +62 -0
  21. package/dist/commands/agent.d.ts +3 -0
  22. package/dist/commands/agent.js +274 -0
  23. package/dist/commands/api-keys.d.ts +3 -0
  24. package/dist/commands/api-keys.js +77 -0
  25. package/dist/commands/approvals.d.ts +3 -0
  26. package/dist/commands/approvals.js +518 -0
  27. package/dist/commands/auth.d.ts +3 -0
  28. package/dist/commands/auth.js +230 -0
  29. package/dist/commands/billing.d.ts +3 -0
  30. package/dist/commands/billing.js +357 -0
  31. package/dist/commands/blueprints.d.ts +3 -0
  32. package/dist/commands/blueprints.js +312 -0
  33. package/dist/commands/budget.d.ts +3 -0
  34. package/dist/commands/budget.js +55 -0
  35. package/dist/commands/callables.d.ts +3 -0
  36. package/dist/commands/callables.js +111 -0
  37. package/dist/commands/capabilities.d.ts +3 -0
  38. package/dist/commands/capabilities.js +44 -0
  39. package/dist/commands/cells.d.ts +3 -0
  40. package/dist/commands/cells.js +94 -0
  41. package/dist/commands/columns.d.ts +3 -0
  42. package/dist/commands/columns.js +1628 -0
  43. package/dist/commands/commands.d.ts +3 -0
  44. package/dist/commands/commands.js +45 -0
  45. package/dist/commands/comments.d.ts +3 -0
  46. package/dist/commands/comments.js +188 -0
  47. package/dist/commands/companies.d.ts +3 -0
  48. package/dist/commands/companies.js +312 -0
  49. package/dist/commands/connections.d.ts +3 -0
  50. package/dist/commands/connections.js +39 -0
  51. package/dist/commands/context.d.ts +3 -0
  52. package/dist/commands/context.js +170 -0
  53. package/dist/commands/copilot.d.ts +3 -0
  54. package/dist/commands/copilot.js +565 -0
  55. package/dist/commands/crm.d.ts +3 -0
  56. package/dist/commands/crm.js +1669 -0
  57. package/dist/commands/custom-integrations.d.ts +3 -0
  58. package/dist/commands/custom-integrations.js +140 -0
  59. package/dist/commands/dashboards.d.ts +3 -0
  60. package/dist/commands/dashboards.js +240 -0
  61. package/dist/commands/db.d.ts +3 -0
  62. package/dist/commands/db.js +190 -0
  63. package/dist/commands/deliverability.d.ts +3 -0
  64. package/dist/commands/deliverability.js +90 -0
  65. package/dist/commands/directory.d.ts +3 -0
  66. package/dist/commands/directory.js +101 -0
  67. package/dist/commands/domains.d.ts +3 -0
  68. package/dist/commands/domains.js +604 -0
  69. package/dist/commands/egress.d.ts +3 -0
  70. package/dist/commands/egress.js +130 -0
  71. package/dist/commands/email.d.ts +3 -0
  72. package/dist/commands/email.js +49 -0
  73. package/dist/commands/engagement.d.ts +3 -0
  74. package/dist/commands/engagement.js +189 -0
  75. package/dist/commands/enrich-column.d.ts +3 -0
  76. package/dist/commands/enrich-column.js +149 -0
  77. package/dist/commands/enrichment.d.ts +3 -0
  78. package/dist/commands/enrichment.js +49 -0
  79. package/dist/commands/feedback.d.ts +3 -0
  80. package/dist/commands/feedback.js +96 -0
  81. package/dist/commands/feeds.d.ts +3 -0
  82. package/dist/commands/feeds.js +486 -0
  83. package/dist/commands/find.d.ts +3 -0
  84. package/dist/commands/find.js +159 -0
  85. package/dist/commands/followers.d.ts +3 -0
  86. package/dist/commands/followers.js +39 -0
  87. package/dist/commands/formulas.d.ts +3 -0
  88. package/dist/commands/formulas.js +43 -0
  89. package/dist/commands/home.d.ts +3 -0
  90. package/dist/commands/home.js +22 -0
  91. package/dist/commands/inbox.d.ts +3 -0
  92. package/dist/commands/inbox.js +779 -0
  93. package/dist/commands/integrations.d.ts +3 -0
  94. package/dist/commands/integrations.js +290 -0
  95. package/dist/commands/knowledge.d.ts +3 -0
  96. package/dist/commands/knowledge.js +874 -0
  97. package/dist/commands/lead-sourcing.d.ts +3 -0
  98. package/dist/commands/lead-sourcing.js +52 -0
  99. package/dist/commands/limits.d.ts +3 -0
  100. package/dist/commands/limits.js +54 -0
  101. package/dist/commands/linkedin.d.ts +3 -0
  102. package/dist/commands/linkedin.js +275 -0
  103. package/dist/commands/login.d.ts +3 -0
  104. package/dist/commands/login.js +347 -0
  105. package/dist/commands/logout.d.ts +3 -0
  106. package/dist/commands/logout.js +12 -0
  107. package/dist/commands/mailboxes.d.ts +3 -0
  108. package/dist/commands/mailboxes.js +1290 -0
  109. package/dist/commands/managed-inboxes.d.ts +3 -0
  110. package/dist/commands/managed-inboxes.js +361 -0
  111. package/dist/commands/messages.d.ts +3 -0
  112. package/dist/commands/messages.js +78 -0
  113. package/dist/commands/notetaker.d.ts +3 -0
  114. package/dist/commands/notetaker.js +79 -0
  115. package/dist/commands/observability.d.ts +3 -0
  116. package/dist/commands/observability.js +125 -0
  117. package/dist/commands/orgs.d.ts +3 -0
  118. package/dist/commands/orgs.js +237 -0
  119. package/dist/commands/people.d.ts +3 -0
  120. package/dist/commands/people.js +353 -0
  121. package/dist/commands/posts.d.ts +3 -0
  122. package/dist/commands/posts.js +139 -0
  123. package/dist/commands/profiles.d.ts +3 -0
  124. package/dist/commands/profiles.js +250 -0
  125. package/dist/commands/projects.d.ts +3 -0
  126. package/dist/commands/projects.js +94 -0
  127. package/dist/commands/prompts.d.ts +3 -0
  128. package/dist/commands/prompts.js +84 -0
  129. package/dist/commands/publishing.d.ts +3 -0
  130. package/dist/commands/publishing.js +1419 -0
  131. package/dist/commands/recipes.d.ts +3 -0
  132. package/dist/commands/recipes.js +120 -0
  133. package/dist/commands/reviews.d.ts +3 -0
  134. package/dist/commands/reviews.js +75 -0
  135. package/dist/commands/rows.d.ts +3 -0
  136. package/dist/commands/rows.js +27 -0
  137. package/dist/commands/runs.d.ts +3 -0
  138. package/dist/commands/runs.js +37 -0
  139. package/dist/commands/search.d.ts +3 -0
  140. package/dist/commands/search.js +182 -0
  141. package/dist/commands/senders.d.ts +3 -0
  142. package/dist/commands/senders.js +393 -0
  143. package/dist/commands/sequences.d.ts +3 -0
  144. package/dist/commands/sequences.js +2322 -0
  145. package/dist/commands/session.d.ts +3 -0
  146. package/dist/commands/session.js +109 -0
  147. package/dist/commands/signals.d.ts +3 -0
  148. package/dist/commands/signals.js +289 -0
  149. package/dist/commands/signup-leads.d.ts +3 -0
  150. package/dist/commands/signup-leads.js +46 -0
  151. package/dist/commands/skills.d.ts +3 -0
  152. package/dist/commands/skills.js +50 -0
  153. package/dist/commands/sourcing.d.ts +3 -0
  154. package/dist/commands/sourcing.js +27 -0
  155. package/dist/commands/status.d.ts +3 -0
  156. package/dist/commands/status.js +51 -0
  157. package/dist/commands/supabase.d.ts +3 -0
  158. package/dist/commands/supabase.js +128 -0
  159. package/dist/commands/support.d.ts +3 -0
  160. package/dist/commands/support.js +651 -0
  161. package/dist/commands/suppressions.d.ts +3 -0
  162. package/dist/commands/suppressions.js +577 -0
  163. package/dist/commands/table-ingestions.d.ts +3 -0
  164. package/dist/commands/table-ingestions.js +145 -0
  165. package/dist/commands/table-runs.d.ts +3 -0
  166. package/dist/commands/table-runs.js +277 -0
  167. package/dist/commands/tables.d.ts +3 -0
  168. package/dist/commands/tables.js +3492 -0
  169. package/dist/commands/tags.d.ts +3 -0
  170. package/dist/commands/tags.js +147 -0
  171. package/dist/commands/tools.d.ts +3 -0
  172. package/dist/commands/tools.js +246 -0
  173. package/dist/commands/update.d.ts +3 -0
  174. package/dist/commands/update.js +106 -0
  175. package/dist/commands/verify.d.ts +3 -0
  176. package/dist/commands/verify.js +27 -0
  177. package/dist/commands/viewers.d.ts +3 -0
  178. package/dist/commands/viewers.js +72 -0
  179. package/dist/commands/voice.d.ts +3 -0
  180. package/dist/commands/voice.js +351 -0
  181. package/dist/commands/whatsapp.d.ts +3 -0
  182. package/dist/commands/whatsapp.js +226 -0
  183. package/dist/commands/whoami.d.ts +3 -0
  184. package/dist/commands/whoami.js +110 -0
  185. package/dist/commands/worker.d.ts +3 -0
  186. package/dist/commands/worker.js +60 -0
  187. package/dist/commands/workflows.d.ts +3 -0
  188. package/dist/commands/workflows.js +1752 -0
  189. package/dist/commands/workspace.d.ts +3 -0
  190. package/dist/commands/workspace.js +94 -0
  191. package/dist/compact-list-flags.d.ts +7 -0
  192. package/dist/compact-list-flags.js +12 -0
  193. package/dist/crm-support-flags.js +14 -5
  194. package/dist/customer-operation-commands.js +8 -6
  195. package/dist/diagnostic-uploader.d.ts +3 -1
  196. package/dist/diagnostic-uploader.js +29 -8
  197. package/dist/help.d.ts +5 -0
  198. package/dist/help.js +22 -0
  199. package/dist/index.d.ts +98 -1
  200. package/dist/index.js +331 -30719
  201. package/dist/local-module-loader.d.ts +4 -0
  202. package/dist/local-module-loader.js +156 -0
  203. package/dist/visual-commands.js +9 -5
  204. package/node_modules/@oxygen/cli-ugc/dist/commands.js +2 -2
  205. package/node_modules/@oxygen/shared/dist/agent-session-schedule.d.ts +36 -0
  206. package/node_modules/@oxygen/shared/dist/agent-session-schedule.js +69 -0
  207. package/node_modules/@oxygen/shared/dist/agent-sessions.d.ts +14 -5
  208. package/node_modules/@oxygen/shared/dist/agent-sessions.js +14 -3
  209. package/node_modules/@oxygen/shared/dist/capability-discovery.js +175 -14
  210. package/node_modules/@oxygen/shared/dist/compact-list-result.d.ts +6 -0
  211. package/node_modules/@oxygen/shared/dist/compact-list-result.js +27 -0
  212. package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.d.ts +12 -1
  213. package/node_modules/@oxygen/shared/dist/contracts/agent-sessions.js +5 -2
  214. package/node_modules/@oxygen/shared/dist/contracts/browser-operations.js +4 -2
  215. package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.d.ts +57 -0
  216. package/node_modules/@oxygen/shared/dist/contracts/compact-list-reads.js +23 -0
  217. package/node_modules/@oxygen/shared/dist/contracts/crm-support.d.ts +52 -0
  218. package/node_modules/@oxygen/shared/dist/contracts/crm-support.js +43 -2
  219. package/node_modules/@oxygen/shared/dist/contracts/customer-operations.js +17 -14
  220. package/node_modules/@oxygen/shared/dist/contracts/knowledge-content-reads.js +1 -1
  221. package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.d.ts +49 -0
  222. package/node_modules/@oxygen/shared/dist/contracts/knowledge-proposal-reads.js +14 -0
  223. package/node_modules/@oxygen/shared/dist/contracts/knowledge-sync.js +1 -1
  224. package/node_modules/@oxygen/shared/dist/contracts/limits-read.js +60 -7
  225. package/node_modules/@oxygen/shared/dist/contracts/operations.d.ts +9 -1
  226. package/node_modules/@oxygen/shared/dist/contracts/operations.js +13 -5
  227. package/node_modules/@oxygen/shared/dist/contracts/publishing-reads.js +1 -1
  228. package/node_modules/@oxygen/shared/dist/contracts/teammates.d.ts +16 -0
  229. package/node_modules/@oxygen/shared/dist/contracts/teammates.js +115 -0
  230. package/node_modules/@oxygen/shared/dist/cost-estimate-view.js +21 -3
  231. package/node_modules/@oxygen/shared/dist/cost-estimate.d.ts +31 -1
  232. package/node_modules/@oxygen/shared/dist/cost-estimate.js +56 -15
  233. package/node_modules/@oxygen/shared/dist/diagnostic-credentials.d.ts +2 -0
  234. package/node_modules/@oxygen/shared/dist/diagnostic-credentials.js +2 -0
  235. package/node_modules/@oxygen/shared/dist/diagnostic-lock.js +71 -44
  236. package/node_modules/@oxygen/shared/dist/diagnostic-spool.d.ts +18 -0
  237. package/node_modules/@oxygen/shared/dist/diagnostic-spool.js +348 -0
  238. package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.d.ts +10 -0
  239. package/node_modules/@oxygen/shared/dist/diagnostic-storage-failure.js +42 -0
  240. package/node_modules/@oxygen/shared/dist/diagnostic-storage.d.ts +1 -1
  241. package/node_modules/@oxygen/shared/dist/diagnostic-storage.js +59 -213
  242. package/node_modules/@oxygen/shared/dist/diagnostics.js +3 -2
  243. package/node_modules/@oxygen/shared/dist/email-dsn.d.ts +16 -5
  244. package/node_modules/@oxygen/shared/dist/email-dsn.js +97 -1
  245. package/node_modules/@oxygen/shared/dist/emailguard-placement-probe.js +5 -0
  246. package/node_modules/@oxygen/shared/dist/enrichment-intents.js +4 -5
  247. package/node_modules/@oxygen/shared/dist/file-import.js +5 -2
  248. package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +30 -0
  249. package/node_modules/@oxygen/shared/dist/hosted-ai.js +20 -0
  250. package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +6 -2
  251. package/node_modules/@oxygen/shared/dist/knowledge-seed-content.d.ts +17 -7
  252. package/node_modules/@oxygen/shared/dist/knowledge-seed-content.js +81 -17
  253. package/node_modules/@oxygen/shared/dist/langfuse.js +8 -4
  254. package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +1 -1
  255. package/node_modules/@oxygen/shared/dist/mailbox-import.d.ts +6 -3
  256. package/node_modules/@oxygen/shared/dist/mailbox-import.js +9 -9
  257. package/node_modules/@oxygen/shared/dist/mcp-server-info.d.ts +8 -0
  258. package/node_modules/@oxygen/shared/dist/mcp-server-info.js +10 -0
  259. package/node_modules/@oxygen/shared/dist/object-storage.d.ts +10 -0
  260. package/node_modules/@oxygen/shared/dist/object-storage.js +14 -0
  261. package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +5 -0
  262. package/node_modules/@oxygen/shared/dist/plan-limits-view.js +17 -1
  263. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
  264. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
  265. package/node_modules/@oxygen/shared/dist/provider-selection.d.ts +15 -0
  266. package/node_modules/@oxygen/shared/dist/provider-selection.js +3 -0
  267. package/node_modules/@oxygen/shared/dist/repricing.d.ts +1 -1
  268. package/node_modules/@oxygen/shared/dist/repricing.js +2 -2
  269. package/node_modules/@oxygen/shared/dist/schedule-label.d.ts +6 -0
  270. package/node_modules/@oxygen/shared/dist/schedule-label.js +12 -0
  271. package/node_modules/@oxygen/shared/dist/sequence-failures.d.ts +1 -0
  272. package/node_modules/@oxygen/shared/dist/sequence-failures.js +20 -6
  273. package/node_modules/@oxygen/shared/dist/teammate-groups.d.ts +55 -0
  274. package/node_modules/@oxygen/shared/dist/teammate-groups.js +74 -0
  275. package/node_modules/@oxygen/shared/dist/teammate-messages.d.ts +52 -0
  276. package/node_modules/@oxygen/shared/dist/teammate-messages.js +88 -0
  277. package/node_modules/@oxygen/shared/dist/teammate-notes.d.ts +58 -0
  278. package/node_modules/@oxygen/shared/dist/teammate-notes.js +73 -0
  279. package/node_modules/@oxygen/shared/dist/teammate-routines.d.ts +95 -0
  280. package/node_modules/@oxygen/shared/dist/teammate-routines.js +266 -0
  281. package/node_modules/@oxygen/shared/dist/teammate-tasks.d.ts +56 -0
  282. package/node_modules/@oxygen/shared/dist/teammate-tasks.js +114 -0
  283. package/node_modules/@oxygen/shared/dist/teammates.d.ts +78 -0
  284. package/node_modules/@oxygen/shared/dist/teammates.js +140 -0
  285. package/node_modules/@oxygen/shared/dist/user-capability-routing.js +120 -0
  286. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  287. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  288. package/node_modules/@oxygen/shared/dist/visual-render.d.ts +9 -1
  289. package/node_modules/@oxygen/shared/dist/visual-render.js +12 -5
  290. package/node_modules/@oxygen/shared/dist/warmup-metrics.d.ts +5 -0
  291. package/node_modules/@oxygen/shared/dist/warmup-metrics.js +44 -0
  292. package/package.json +1 -1
@@ -1,136 +1,17 @@
1
- import { createHash, randomUUID } from "node:crypto";
2
- import { mkdir, open, readFile, readdir, rename, stat, unlink } from "node:fs/promises";
3
- import { join } from "node:path";
1
+ import { createHash } from "node:crypto";
4
2
  import { gzipSync, gunzipSync } from "node:zlib";
5
3
  import { GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
6
- import { createDiagnosticRecord, credentialSafeDiagnostic, DIAGNOSTIC_MAX_BYTES, DIAGNOSTIC_RECORD_MAX_BYTES, DIAGNOSTIC_RETENTION_MS, diagnosticId, diagnosticSummary, parseDiagnosticRecord, sanitizeDiagnosticContent } from "./diagnostics.js";
7
- import { acquireDiagnosticStorageLock } from "./diagnostic-storage-lock.js";
4
+ import { createDiagnosticRecord, credentialSafeDiagnostic, DIAGNOSTIC_RECORD_MAX_BYTES, DIAGNOSTIC_RETENTION_MS, diagnosticId, diagnosticSummary, parseDiagnosticRecord, sanitizeDiagnosticContent } from "./diagnostics.js";
5
+ import { admitDiagnosticSpool, lockDiagnosticUpload, readUploadProgress, retryDiagnosticSpool, saveDiagnosticUploadProgress } from "./diagnostic-spool.js";
6
+ import { diagnosticRole, StorageFailure, storageFailureFields as diagnosticStorageFailureFields, storageStage } from "./diagnostic-storage-failure.js";
8
7
  import { log } from "./log.js";
9
8
  import { recordTelemetryCounter } from "./telemetry.js";
9
+ export { storageFailureFields as diagnosticStorageFailureFields } from "./diagnostic-storage-failure.js";
10
10
  let cached;
11
11
  let warned = false;
12
12
  let flushing = null;
13
13
  let timer;
14
14
  let onStored;
15
- const admissions = new Map();
16
- class StorageFailure extends Error {
17
- stage;
18
- failure;
19
- constructor(stage, failure) {
20
- super("Diagnostic storage failed");
21
- this.stage = stage;
22
- this.failure = failure;
23
- }
24
- }
25
- async function storageStage(stage, action) {
26
- try {
27
- return await action();
28
- }
29
- catch (error) {
30
- throw error instanceof StorageFailure ? error : new StorageFailure(stage, error);
31
- }
32
- }
33
- function storageFailureFields(error) {
34
- const stage = error instanceof StorageFailure ? error.stage : "encode";
35
- const failure = error instanceof StorageFailure ? error.failure : error;
36
- const value = failure && typeof failure === "object" ? failure : {};
37
- const code = typeof value.code === "string" && /^(?:E[A-Z0-9_]{1,30}|SQLITE_[A-Z_]{1,30}|spool_full|lock_timeout)$/.test(value.code) ? value.code : null;
38
- const sqlite = typeof value.errcode === "number" ? value.errcode : null;
39
- const cause = code === "lock_timeout" ? "lock_timeout" : code === "spool_full" ? "spool_limit"
40
- : ["EACCES", "EPERM", "EROFS"].includes(code ?? "") || (sqlite !== null && [3, 8, 23].includes(sqlite & 255)) ? "permission"
41
- : ["ENOSPC", "EDQUOT"].includes(code ?? "") || (sqlite !== null && (sqlite & 255) === 13) ? "filesystem_capacity" : "other";
42
- const status = value.$metadata?.httpStatusCode;
43
- return { cause, stage, error_code: code, sqlite_code: sqlite, errno: typeof value.errno === "number" ? value.errno : null,
44
- http_status: typeof status === "number" && status >= 100 && status <= 599 ? status : null, queue_role: diagnosticRole() };
45
- }
46
- function diagnosticRole() {
47
- const role = process.env.OXYGEN_WORKER_QUEUE_ROLE;
48
- return role && ["relay", "bulk", "maintenance", "interactive", "provider", "outbound"].includes(role) ? role
49
- : process.env.OXYGEN_PROCESS_ROLE === "worker" ? "legacy" : "web";
50
- }
51
- /** Coalesce local contenders; the kernel mutex still owns cross-process admission. */
52
- function admitDiagnostic(directory, record, encoded) {
53
- return new Promise((resolve, reject) => {
54
- const pending = admissions.get(directory);
55
- const admission = { record, encoded, deadline: performance.now() + 5_000, resolve, reject };
56
- if (pending)
57
- pending.pending.push(admission);
58
- else {
59
- const queue = { pending: [admission] };
60
- admissions.set(directory, queue);
61
- queueMicrotask(() => { void drainAdmissions(directory, queue); });
62
- }
63
- });
64
- }
65
- async function drainAdmissions(directory, queue) {
66
- try {
67
- while (queue.pending.length) {
68
- const batch = queue.pending.splice(0, 32);
69
- let release = null;
70
- try {
71
- release = await storageStage("lock", () => acquireDiagnosticStorageLock(join(directory, ".admission.lock"), Math.max(0, batch[0].deadline - performance.now())));
72
- if (!release)
73
- throw new StorageFailure("lock", { code: "lock_timeout" });
74
- const started = performance.now();
75
- let { used, files } = await storageStage("inspect", () => inspectDiagnosticSpool(directory));
76
- queue.files = files;
77
- const scanMs = performance.now() - started;
78
- for (const admission of batch) {
79
- try {
80
- if (performance.now() >= admission.deadline)
81
- throw new StorageFailure("lock", { code: "lock_timeout" });
82
- if (used + admission.encoded.compressed.length > DIAGNOSTIC_MAX_BYTES)
83
- throw new StorageFailure("capacity", { code: "spool_full" });
84
- // Reserve conservatively even if cleanup of a failed partial write fails.
85
- used += admission.encoded.compressed.length;
86
- admission.resolve(await writeDiagnosticSpool(directory, admission.record.id, admission.encoded.compressed));
87
- }
88
- catch (error) {
89
- admission.reject(error);
90
- }
91
- }
92
- log("info", "diagnostics.spool_admitted", { queue_role: diagnosticRole(), batch_records: batch.length, spool_files: files.length,
93
- spool_bytes: used, scan_ms: scanMs, admission_wait_ms: performance.now() - (batch[0].deadline - 5_000) });
94
- }
95
- catch (error) {
96
- for (const admission of batch)
97
- admission.reject(error);
98
- }
99
- finally {
100
- await release?.();
101
- }
102
- }
103
- }
104
- catch (error) {
105
- for (const admission of queue.pending.splice(0))
106
- admission.reject(error);
107
- }
108
- finally {
109
- admissions.delete(directory);
110
- }
111
- }
112
- async function writeDiagnosticSpool(directory, id, compressed) {
113
- const path = join(directory, `${id}.json.gz`);
114
- const temporary = join(directory, `${id}.${randomUUID()}.tmp`);
115
- let handle;
116
- try {
117
- handle = await storageStage("open", () => open(temporary, "wx", 0o600));
118
- await storageStage("write", () => handle.writeFile(compressed));
119
- await storageStage("sync", () => handle.sync());
120
- await handle.close();
121
- handle = undefined;
122
- await storageStage("rename", () => rename(temporary, path));
123
- return path;
124
- }
125
- finally {
126
- try {
127
- await handle?.close();
128
- }
129
- finally {
130
- await unlink(temporary).catch(() => undefined);
131
- }
132
- }
133
- }
134
15
  export function observeDiagnosticStorage(observer) {
135
16
  onStored = observer;
136
17
  startDiagnosticRetry();
@@ -210,7 +91,7 @@ export async function storeDiagnostic(record) {
210
91
  return null;
211
92
  }
212
93
  const directory = process.env.OXYGEN_DIAGNOSTICS_SPOOL_DIR;
213
- let path;
94
+ let acknowledge;
214
95
  try {
215
96
  let encoded;
216
97
  try {
@@ -221,26 +102,64 @@ export async function storeDiagnostic(record) {
221
102
  encoded = encodeDiagnostic(record);
222
103
  }
223
104
  record = parseDiagnosticRecord(JSON.parse(encoded.bytes.toString("utf8")));
224
- await storageStage("mkdir", () => mkdir(directory, { recursive: true, mode: 0o700 }));
225
- path = await admitDiagnostic(directory, record, encoded);
105
+ acknowledge = await admitDiagnosticSpool(directory, record, encoded.compressed);
226
106
  startDiagnosticRetry();
227
- const receipt = await storageStage("upload", () => uploadEncodedDiagnostic(record, encoded));
228
- await unlink(path).catch(() => undefined);
107
+ const receipt = await uploadEncodedDiagnostic(directory, record, encoded, acknowledge);
229
108
  return receipt;
230
109
  }
231
110
  catch (error) {
232
111
  // Neither storage credentials nor upstream exception text belong in logs.
233
- const fields = storageFailureFields(error);
234
- log("warn", path ? "diagnostics.capture_deferred" : "diagnostics.capture_lost", { diagnostic_id: record.id, reason: path ? "upload_failed" : "spool_admission_failed", ...fields });
235
- recordTelemetryCounter(path ? "oxygen.diagnostics.deferred" : "oxygen.diagnostics.lost", 1, { source: record.surface, cause: fields.cause, queue_role: diagnosticRole() });
112
+ const fields = diagnosticStorageFailureFields(error);
113
+ log("warn", acknowledge ? "diagnostics.capture_deferred" : "diagnostics.capture_lost", { diagnostic_id: record.id,
114
+ reason: acknowledge ? fields.stage === "observer" ? "metadata_publication_failed" : fields.stage === "receipt" ? "receipt_failed" : "upload_failed" : "spool_admission_failed", ...fields });
115
+ recordTelemetryCounter(acknowledge ? "oxygen.diagnostics.deferred" : "oxygen.diagnostics.lost", 1, { source: record.surface, cause: fields.cause, queue_role: diagnosticRole() });
236
116
  return null;
237
117
  }
238
118
  }
239
- async function uploadEncodedDiagnostic(record, encoded) {
119
+ async function uploadEncodedDiagnostic(directory, record, encoded, acknowledge) {
120
+ const release = await lockDiagnosticUpload(directory, record.id, 5_000);
121
+ try {
122
+ const receipt = await publishEncodedDiagnostic(directory, record, encoded);
123
+ await acknowledge();
124
+ return receipt;
125
+ }
126
+ finally {
127
+ await release();
128
+ }
129
+ }
130
+ function diagnosticStoreFingerprint() {
131
+ return createHash("sha256").update(`${process.env.OXYGEN_DIAGNOSTICS_S3_ENDPOINT}\0${process.env.OXYGEN_DIAGNOSTICS_S3_BUCKET}`).digest("hex");
132
+ }
133
+ async function publishEncodedDiagnostic(directory, record, encoded, reservation) {
134
+ // Upload acknowledgement never extends the captured record's retry window.
135
+ if (Date.now() - Date.parse(record.started_at) >= 72 * 60 * 60 * 1000)
136
+ throw new StorageFailure("upload", new Error("Diagnostic upload has expired"));
137
+ const receipt = { key: diagnosticObjectKey(record), sha256: encoded.sha256, bytes: encoded.bytes.length, compressed_bytes: encoded.compressed.length, record };
138
+ // The checksum binds the acknowledged upload to these exact evidence bytes;
139
+ // a receipt from another bucket or endpoint cannot suppress its upload.
140
+ let progress = await storageStage("receipt", async () => {
141
+ const value = await readUploadProgress(directory, record.id, diagnosticStoreFingerprint());
142
+ if (value && value.sha256 !== encoded.sha256)
143
+ throw new Error("Diagnostic upload receipt integrity mismatch");
144
+ return value;
145
+ });
146
+ if (!progress) {
147
+ await storageStage("upload", () => uploadDiagnosticObject(record, encoded));
148
+ progress = { sha256: encoded.sha256, store: diagnosticStoreFingerprint(), published: false };
149
+ await storageStage("receipt", () => saveDiagnosticUploadProgress(directory, record, progress, reservation));
150
+ }
151
+ if (!progress.published) {
152
+ if (onStored)
153
+ await storageStage("observer", () => onStored(receipt));
154
+ // A crash after an ambiguous observer acknowledgement can retry metadata
155
+ // with the same UUID; the destination owns deduplication of that UUID.
156
+ await storageStage("receipt", () => saveDiagnosticUploadProgress(directory, record, { ...progress, published: true }, reservation));
157
+ }
158
+ return receipt;
159
+ }
160
+ async function uploadDiagnosticObject(record, encoded) {
240
161
  const captured = Date.parse(record.started_at);
241
162
  const now = Date.now();
242
- if (now - captured >= 72 * 60 * 60 * 1000)
243
- throw new Error("Diagnostic upload has expired");
244
163
  // S3 day-based expiry rounds to the next UTC midnight. Tagged age bands
245
164
  // leave that final day inside the 30-day deadline measured from capture.
246
165
  const retentionDays = Math.min(29, 29 - (Math.floor(now / 86_400_000) - Math.floor(captured / 86_400_000)));
@@ -254,10 +173,6 @@ async function uploadEncodedDiagnostic(record, encoded) {
254
173
  log("info", "diagnostics.capture_stored", { ...diagnosticSummary(record), org_id: record.organization_id, user_id: record.user_id,
255
174
  diagnostic_key: key, bytes: encoded.bytes.length, compressed_bytes: encoded.compressed.length, sha256: encoded.sha256, queue_role: diagnosticRole() });
256
175
  recordTelemetryCounter("oxygen.diagnostics.stored", 1, { source: record.surface, layer: record.layer });
257
- const receipt = { key, sha256: encoded.sha256, bytes: encoded.bytes.length, compressed_bytes: encoded.compressed.length, record };
258
- if (onStored)
259
- await storageStage("observer", () => onStored(receipt));
260
- return receipt;
261
176
  }
262
177
  export function flushDiagnosticSpool() {
263
178
  if (flushing)
@@ -266,82 +181,13 @@ export function flushDiagnosticSpool() {
266
181
  if (!diagnosticsConfigured())
267
182
  return;
268
183
  const directory = process.env.OXYGEN_DIAGNOSTICS_SPOOL_DIR;
269
- const release = await storageStage("lock", () => acquireDiagnosticStorageLock(join(directory, ".upload.lock")));
270
- if (!release)
271
- return;
272
- try {
273
- // Idle workers enforce the same expiry as new admissions. The admission
274
- // lock protects cleanup against a writer replacing an old record path.
275
- let files;
276
- const local = admissions.get(directory);
277
- if (local) {
278
- // Reuse the active writer's protected inspection. Waiting for local idle
279
- // can starve retained evidence indefinitely under sustained capture.
280
- files = local.files?.slice(0, 20) ?? [];
281
- }
282
- else {
283
- const admission = await storageStage("lock", () => acquireDiagnosticStorageLock(join(directory, ".admission.lock")));
284
- if (!admission)
285
- return;
286
- try {
287
- files = (await storageStage("inspect", () => inspectDiagnosticSpool(directory))).files.slice(0, 20);
288
- }
289
- finally {
290
- await admission();
291
- }
292
- }
293
- for (const name of files) {
294
- const path = join(directory, name);
295
- try {
296
- const compressed = await storageStage("read", () => readFile(path));
297
- const record = await storageStage("decode", async () => decodeDiagnostic(compressed));
298
- await storageStage("upload", () => uploadEncodedDiagnostic(record, encodeDiagnostic(record)));
299
- await unlink(path).catch(() => undefined);
300
- }
301
- catch (error) {
302
- log("warn", "diagnostics.spool_upload_deferred", { diagnostic_id: name.slice(0, -8), ...storageFailureFields(error) });
303
- }
304
- }
305
- }
306
- finally {
307
- await release();
308
- }
309
- })().catch(error => { log("warn", "diagnostics.spool_retry_failed", storageFailureFields(error)); }).finally(() => { flushing = null; });
184
+ await retryDiagnosticSpool(directory, diagnosticStoreFingerprint(), async (compressed, reservation) => {
185
+ const record = await storageStage("decode", async () => decodeDiagnostic(compressed));
186
+ await publishEncodedDiagnostic(directory, record, encodeDiagnostic(record), reservation);
187
+ });
188
+ })().catch(error => { log("warn", "diagnostics.spool_retry_failed", diagnosticStorageFailureFields(error)); }).finally(() => { flushing = null; });
310
189
  return flushing;
311
190
  }
312
- /** Caller holds admission lock; count retained bytes and expire actual filenames. */
313
- async function inspectDiagnosticSpool(directory) {
314
- let used = 0;
315
- const retained = [];
316
- const names = (await readdir(directory)).filter(name => /^[0-9a-f-]+\.json\.gz$/.test(name) || /^[0-9a-f-]+(?:\.[0-9a-f-]+)?\.tmp$/.test(name));
317
- for (let offset = 0; offset < names.length; offset += 64) {
318
- const inspected = await Promise.all(names.slice(offset, offset + 64).map(async (name) => {
319
- const path = join(directory, name);
320
- const info = await stat(path).catch(error => { if (error?.code === "ENOENT")
321
- return null; throw error; });
322
- return { name, path, info };
323
- }));
324
- for (const { name, path, info } of inspected) {
325
- const record = /^[0-9a-f-]+\.json\.gz$/.test(name);
326
- if (!info)
327
- continue;
328
- if (Date.now() - info.mtimeMs >= 72 * 60 * 60 * 1000) {
329
- const removed = await unlink(path).then(() => true).catch(error => { if (error?.code === "ENOENT")
330
- return false; throw error; });
331
- if (removed) {
332
- log("warn", "diagnostics.capture_lost", { reason: "spool_expired", bytes: info.size, queue_role: diagnosticRole() });
333
- recordTelemetryCounter("oxygen.diagnostics.lost", 1, { reason: "spool_expired" });
334
- }
335
- }
336
- else {
337
- used += info.size;
338
- if (record)
339
- retained.push({ name, mtimeMs: info.mtimeMs });
340
- }
341
- }
342
- }
343
- return { used, files: retained.sort((a, b) => a.mtimeMs - b.mtimeMs || a.name.localeCompare(b.name)).map(file => file.name) };
344
- }
345
191
  /** A metadata-only fallback for an unsupported value never claims full capture. */
346
192
  export function partialDiagnostic(record, reason) {
347
193
  return createDiagnosticRecord({ ...record, input: null, output: null, capture_state: "partial", metadata: { capture_reason: reason } });
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { credentialBearingOperation } from "./diagnostic-credentials.js";
2
+ import { credentialBearingOperation, SPOKEN_OUTPUT_OPERATIONS } from "./diagnostic-credentials.js";
3
3
  import { redactSecretsInString } from "./redaction.js";
4
4
  /** Internal diagnostic evidence, distinct from customer run state and usage counts. */
5
5
  export const DIAGNOSTIC_VERSION = 1;
@@ -205,7 +205,8 @@ export function credentialSafeDiagnostic(record) {
205
205
  const selectedName = ["oxygen_capabilities_read", "oxygen_capabilities_call"].includes(toolName) && args && typeof args === "object" && "tool" in args
206
206
  ? String(args.tool) : toolName;
207
207
  return { ...record, input: credentialBearingOperation(selectedName, "input") ? { withheld: "credential_bearing_operation" } : record.input,
208
- output: credentialBearingOperation(selectedName, "output") ? { withheld: "credential_bearing_operation" } : record.output };
208
+ output: credentialBearingOperation(selectedName, "output") ? { withheld: "credential_bearing_operation" }
209
+ : SPOKEN_OUTPUT_OPERATIONS.has(record.operation) ? { withheld: "dictated_speech" } : record.output };
209
210
  }
210
211
  /** Bound the diagnostic branch of a response tee without delaying its consumer. */
211
212
  export async function readDiagnosticProtocolText(body) {
@@ -46,6 +46,14 @@ export type DeliveryStatusNotification = {
46
46
  /** Enhanced status code (5.7.1) when present, else the bare SMTP reply (550). */
47
47
  smtpCode: string | null;
48
48
  };
49
+ /** Bounds HTML hydration for the health rollup and pure fallback alike. */
50
+ export declare const MAX_DELIVERY_STATUS_HTML_CHARS = 20000;
51
+ type DsnInput = {
52
+ fromAddress?: string | null;
53
+ subject?: string | null;
54
+ bodyText?: string | null;
55
+ bodyHtml?: string | null;
56
+ };
49
57
  /**
50
58
  * Classify one stored inbound message as a delivery-status notification.
51
59
  *
@@ -53,8 +61,11 @@ export type DeliveryStatusNotification = {
53
61
  * is intentionally wide, and a false positive here would invent a deliverability
54
62
  * incident out of a customer reply.
55
63
  */
56
- export declare function classifyDeliveryStatusNotification(input: {
57
- fromAddress?: string | null;
58
- subject?: string | null;
59
- bodyText?: string | null;
60
- }): DeliveryStatusNotification | null;
64
+ export declare function classifyDeliveryStatusNotification(input: DsnInput): DeliveryStatusNotification | null;
65
+ /**
66
+ * A bounce generator is not necessarily the server that rejected delivery.
67
+ * Gmail can relay a Mimecast/Sophos refusal. Require receiving-server evidence;
68
+ * an absent or truncated diagnostic remains unattributed, never a Google block.
69
+ */
70
+ export declare function isGoogleDeliveryStatusRejection(input: DsnInput): boolean;
71
+ export {};
@@ -24,6 +24,86 @@ export const DELIVERY_STATUS_NOTIFICATION_KINDS = [
24
24
  "unclassified",
25
25
  "other",
26
26
  ];
27
+ /** Bounds HTML hydration for the health rollup and pure fallback alike. */
28
+ export const MAX_DELIVERY_STATUS_HTML_CHARS = 20_000;
29
+ /** Consume each tag once, including malformed input; a byte cap alone cannot bound regex rescanning. */
30
+ function dsnHtmlText(html) {
31
+ const parts = [];
32
+ let cursor = 0;
33
+ while (cursor < html.length) {
34
+ const start = html.indexOf("<", cursor);
35
+ if (start < 0) {
36
+ parts.push(html.slice(cursor));
37
+ break;
38
+ }
39
+ parts.push(html.slice(cursor, start));
40
+ if (html.startsWith("<!--", start)) {
41
+ const end = html.indexOf("-->", start + 4);
42
+ if (end < 0)
43
+ break;
44
+ cursor = end + 3;
45
+ continue;
46
+ }
47
+ let end = start + 1;
48
+ let quote = "";
49
+ for (; end < html.length; end++) {
50
+ const char = html[end];
51
+ if (quote) {
52
+ if (char === quote)
53
+ quote = "";
54
+ }
55
+ else if (char === '"' || char === "'")
56
+ quote = char;
57
+ else if (char === ">")
58
+ break;
59
+ }
60
+ if (end === html.length) {
61
+ parts.push(html.slice(start));
62
+ break;
63
+ }
64
+ const tag = html.slice(start + 1, end).trim();
65
+ const name = /^\/?([a-z][a-z0-9:-]*)/i.exec(tag)?.[1]?.toLowerCase() ?? "";
66
+ const closing = tag.startsWith("/");
67
+ if (!closing && ["head", "style", "script"].includes(name)) {
68
+ const close = new RegExp(`</${name}\\s*>`, "gi");
69
+ close.lastIndex = end + 1;
70
+ if (!close.exec(html))
71
+ break;
72
+ cursor = close.lastIndex;
73
+ parts.push(" ");
74
+ continue;
75
+ }
76
+ const line = name === "br" || name === "hr"
77
+ || (closing && ["div", "p", "table", "tr", "li", "blockquote", "h1", "h2", "h3", "h4", "h5", "h6"].includes(name));
78
+ parts.push(line ? "\n" : " ");
79
+ cursor = end + 1;
80
+ }
81
+ return parts.join("");
82
+ }
83
+ function readDsnBody(input) {
84
+ if (typeof input.bodyText === "string" && input.bodyText.trim())
85
+ return input.bodyText;
86
+ if (typeof input.bodyHtml !== "string")
87
+ return "";
88
+ // Unlike a human reply, an NDR may put the actual diagnostic in a blockquote.
89
+ // Keep it and preserve line boundaries for Remote-MTA; never read scripts,
90
+ // styles or markup as diagnostic content. This is text extraction, not HTML rendering.
91
+ const entities = {
92
+ nbsp: " ", amp: "&", lt: "<", gt: ">", quot: '"', apos: "'",
93
+ };
94
+ return dsnHtmlText(input.bodyHtml.slice(0, MAX_DELIVERY_STATUS_HTML_CHARS))
95
+ .replace(/&(#x[\da-f]{1,6}|#\d{1,7}|nbsp|amp|lt|gt|quot|apos);/gi, (_, entity) => {
96
+ const key = entity.toLowerCase();
97
+ if (!key.startsWith("#"))
98
+ return entities[key] ?? " ";
99
+ const code = key.startsWith("#x") ? Number.parseInt(key.slice(2), 16) : Number.parseInt(key.slice(1), 10);
100
+ return code > 0 && code <= 0x10ffff && !(code >= 0xd800 && code <= 0xdfff)
101
+ ? String.fromCodePoint(code) : " ";
102
+ })
103
+ .replace(/[^\S\n]+/g, " ")
104
+ .replace(/\n */g, "\n")
105
+ .trim();
106
+ }
27
107
  // A message only enters classification when its envelope looks like a DSN. The
28
108
  // SQL prefilter that feeds this is deliberately loose (ILIKE), so this gate is
29
109
  // what keeps an ordinary customer reply that happens to contain the word
@@ -106,7 +186,7 @@ function readKind(subject, haystack, hasBody) {
106
186
  export function classifyDeliveryStatusNotification(input) {
107
187
  const from = typeof input.fromAddress === "string" ? input.fromAddress : "";
108
188
  const subject = typeof input.subject === "string" ? input.subject : "";
109
- const body = typeof input.bodyText === "string" ? input.bodyText : "";
189
+ const body = readDsnBody(input);
110
190
  if (!DSN_FROM_PATTERN.test(from) && !DSN_SUBJECT_PATTERN.test(subject))
111
191
  return null;
112
192
  const haystack = `${subject}\n${body}`;
@@ -118,3 +198,19 @@ export function classifyDeliveryStatusNotification(input) {
118
198
  smtpCode,
119
199
  };
120
200
  }
201
+ /**
202
+ * A bounce generator is not necessarily the server that rejected delivery.
203
+ * Gmail can relay a Mimecast/Sophos refusal. Require receiving-server evidence;
204
+ * an absent or truncated diagnostic remains unattributed, never a Google block.
205
+ */
206
+ export function isGoogleDeliveryStatusRejection(input) {
207
+ if (classifyDeliveryStatusNotification(input)?.kind !== "provider_rejected")
208
+ return false;
209
+ const body = readDsnBody(input);
210
+ const remote = /^Remote-MTA:\s*(?:dns|rfc822);\s*([a-z0-9.-]+)/im.exec(body)?.[1];
211
+ if (remote) {
212
+ return /(?:^|\.)(?:google|googlemail)\.com$/i.test(remote.replace(/\.$/, ""));
213
+ }
214
+ return GOOGLE_BLOCK_URL.test(body)
215
+ || /\b5\.7\.\d{1,3}\b[^\n]*(?:\bgsmtp\b|https?:\/\/support\.google\.com\/mail\/answer\/)/i.test(body);
216
+ }
@@ -42,6 +42,11 @@ function headerLine(value, fallback) {
42
42
  }
43
43
  function messageBody(value, phrase) {
44
44
  const base = value?.trim() || DEFAULT_BODY;
45
+ // Explicit placement copy can put the required matching phrase in a neutral
46
+ // reference line. Keep legacy bodies byte-identical for durable send hashes.
47
+ if (base.includes("{{placement_test_phrase}}")) {
48
+ return base.replaceAll("{{placement_test_phrase}}", phrase);
49
+ }
45
50
  if (base.includes(phrase))
46
51
  return base;
47
52
  return `${base}\n\nEmailGuard test phrase: ${phrase}`;
@@ -596,11 +596,10 @@ export const ENRICHMENT_INTENT_TAXONOMY = [
596
596
  rank: 340,
597
597
  aliases: ["lookalike", "similar companies", "companies like", "more like these"],
598
598
  inputShapes: ["seed_accounts"],
599
- outputFields: [f("company_name", "Company", "company.name"), f("company_domain", "Domain", "company.domain")],
600
- surfaces: {},
601
- status: "coming_soon",
602
- statusReason: "Coming soon (D51, 2026-09-19): the catalogue ships the default chains customers need most first; this data type returns once its lane is measured.",
603
- decision: "D51",
599
+ outputFields: [f("lookalike_companies", "Similar companies", null, "jsonb"), f("lookalike_company_count", "Number returned", null, "numeric")],
600
+ surfaces: { preset: "company_lookalikes", catalogEntry: "preset:company_lookalikes", registryFunction: "company_search", registryIntent: "find_lookalikes" },
601
+ status: "selectable",
602
+ decision: "D54",
604
603
  candidateProviders: ["leadmagic"],
605
604
  },
606
605
  // ── Signals (streams and watch-lists; referenced) ─────────────────────────
@@ -332,14 +332,17 @@ async function* iterateCsvRowStreamBatches(chunks, batchSize) {
332
332
  if (!combined)
333
333
  continue;
334
334
  const processUntil = Math.max(0, combined.length - 1);
335
- for (let index = 0; index < processUntil; index += 1) {
335
+ let index = 0;
336
+ for (; index < processUntil; index += 1) {
336
337
  const ready = applyCsvTextCharacter(state, combined, index, batchSize);
337
338
  if (ready.skipNext)
338
339
  index += 1;
339
340
  if (ready.batch)
340
341
  yield ready.batch;
341
342
  }
342
- carry = combined.charAt(combined.length - 1);
343
+ // An escaped quote can consume the lookahead character too. Carry only
344
+ // unread text, or that quote is parsed again at the next stream boundary.
345
+ carry = combined.slice(index);
343
346
  }
344
347
  const tail = carry + decoder.decode();
345
348
  for (let index = 0; index < tail.length; index += 1) {
@@ -77,6 +77,36 @@ export declare const HOSTED_AI_MODEL_REGISTRY: {
77
77
  * own key and choice. Retired 2026-09-26 (see the ai_column block above).
78
78
  */
79
79
  export declare const LEGACY_MANAGED_AI_COLUMN_MODELS: Readonly<Record<string, HostedAiLevel>>;
80
+ /**
81
+ * The exact managed model a code-owned background pass dispatches to, chosen and
82
+ * measured for that pass rather than inherited from a customer tier. A pin moves
83
+ * the model only: the run still bills at its column's tier. It is a function
84
+ * argument (`runAiColumnForOrganization`'s `managedModelPin`), never a stored
85
+ * column field, so no customer column can select it.
86
+ */
87
+ export type ManagedModelPin = {
88
+ model: string;
89
+ /** OpenRouter fallbacks, tried in order when `model` is unavailable. */
90
+ fallbackModels: readonly string[];
91
+ /** Send `reasoning: {enabled: false}`, as the pass was measured. */
92
+ disableReasoning: boolean;
93
+ };
94
+ /**
95
+ * Signup research synthesis (knowledge bootstrap): the company profile, cited
96
+ * research note and GTM hypotheses written once per new workspace, plus the
97
+ * creator's private research summary.
98
+ *
99
+ * Pinned because inheriting the Balanced AI-column tier broke it silently. The
100
+ * 2026-10-10 tier refresh (#5115) moved Balanced to claude-haiku-5.5 at medium
101
+ * reasoning. In production through 2026-10-12, 30 of 30 traced syntheses then
102
+ * returned the company/offering/icp/market sections as `{}`, 26 left the research
103
+ * note empty, and 30 of 35 signup research runs ended `partial` with no
104
+ * `company-research` page. From 2026-09-12 to 2026-10-10 this configuration
105
+ * (deepseek-v4.1-flash, reasoning off, gpt-6-luna fallback, i.e. Balanced before
106
+ * the refresh) wrote the note in 199 of 211 runs. Change it only after replaying the
107
+ * synthesis on production-shaped evidence and comparing note and section fill rates.
108
+ */
109
+ export declare const KNOWLEDGE_BOOTSTRAP_SYNTHESIS_MODEL: ManagedModelPin;
80
110
  /** The tier a retired managed AI-column model id billed at, or null. */
81
111
  export declare function legacyManagedAiColumnLevel(model: string): HostedAiLevel | null;
82
112
  /**
@@ -268,6 +268,26 @@ export const LEGACY_MANAGED_AI_COLUMN_MODELS = {
268
268
  "deepseek/deepseek-v4.1-flash": "medium",
269
269
  "openai/gpt-6-sol": "high",
270
270
  };
271
+ /**
272
+ * Signup research synthesis (knowledge bootstrap): the company profile, cited
273
+ * research note and GTM hypotheses written once per new workspace, plus the
274
+ * creator's private research summary.
275
+ *
276
+ * Pinned because inheriting the Balanced AI-column tier broke it silently. The
277
+ * 2026-10-10 tier refresh (#5115) moved Balanced to claude-haiku-5.5 at medium
278
+ * reasoning. In production through 2026-10-12, 30 of 30 traced syntheses then
279
+ * returned the company/offering/icp/market sections as `{}`, 26 left the research
280
+ * note empty, and 30 of 35 signup research runs ended `partial` with no
281
+ * `company-research` page. From 2026-09-12 to 2026-10-10 this configuration
282
+ * (deepseek-v4.1-flash, reasoning off, gpt-6-luna fallback, i.e. Balanced before
283
+ * the refresh) wrote the note in 199 of 211 runs. Change it only after replaying the
284
+ * synthesis on production-shaped evidence and comparing note and section fill rates.
285
+ */
286
+ export const KNOWLEDGE_BOOTSTRAP_SYNTHESIS_MODEL = {
287
+ model: "deepseek/deepseek-v4.1-flash",
288
+ fallbackModels: ["openai/gpt-6-luna"],
289
+ disableReasoning: true,
290
+ };
271
291
  /**
272
292
  * Managed model ids OXYGEN ran that never sat in the registry above, mapped to
273
293
  * the tier they read as. Labeling only: unlike LEGACY_MANAGED_AI_COLUMN_MODELS,
@@ -151,14 +151,18 @@ export type KnowledgeBootstrapMarker = {
151
151
  completed_at: string | null;
152
152
  /** `ox_runs.runs` id (source_type `system`) carrying the observable run. */
153
153
  run_id: string | null;
154
- /** Managed credits actually spent, across every phase. Never exceeds the cap. */
154
+ /** Managed accounting total across every phase. When effect_outcome is unknown,
155
+ * this is a conservative bound rather than measured spend. Never exceeds the cap. */
155
156
  credits_used: number;
156
157
  /**
157
158
  * `unknown` when the pass stopped with no record of which provider calls it made: a
158
159
  * legacy attempt closed as `interrupted_without_checkpoint`. `credits_used` is then
159
- * not a measurement, and nothing about what was read or spent may be claimed.
160
+ * not a measurement. A customer read can also qualify saved research whose
161
+ * current checkpoint retains an unconfirmed paid lookup.
160
162
  */
161
163
  effect_outcome?: "unknown";
164
+ /** Read projection only: saved research remains available despite an unconfirmed lookup. */
165
+ research_available?: boolean;
162
166
  /**
163
167
  * The workspace domain researched (from organizations.iconDomain). Null (stored
164
168
  * as "", the key the execution and onboarding checks compare on) for a company