@mongodb-js/agent-engine-runner-shared 0.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/LICENSE.md +201 -0
  3. package/README.md +29 -0
  4. package/dist/agent_config.d.ts +167 -0
  5. package/dist/agent_config.d.ts.map +1 -0
  6. package/dist/agent_config.js +544 -0
  7. package/dist/call_interrupted.d.ts +12 -0
  8. package/dist/call_interrupted.d.ts.map +1 -0
  9. package/dist/call_interrupted.js +11 -0
  10. package/dist/checkpoint_workspace.d.ts +25 -0
  11. package/dist/checkpoint_workspace.d.ts.map +1 -0
  12. package/dist/checkpoint_workspace.js +44 -0
  13. package/dist/context.d.ts +235 -0
  14. package/dist/context.d.ts.map +1 -0
  15. package/dist/context.js +322 -0
  16. package/dist/db_config.d.ts +28 -0
  17. package/dist/db_config.d.ts.map +1 -0
  18. package/dist/db_config.js +66 -0
  19. package/dist/db_naming.d.ts +54 -0
  20. package/dist/db_naming.d.ts.map +1 -0
  21. package/dist/db_naming.js +94 -0
  22. package/dist/error_reporting.d.ts +67 -0
  23. package/dist/error_reporting.d.ts.map +1 -0
  24. package/dist/error_reporting.js +311 -0
  25. package/dist/generated/workflow/v1/activity_pb.d.ts +342 -0
  26. package/dist/generated/workflow/v1/activity_pb.d.ts.map +1 -0
  27. package/dist/generated/workflow/v1/activity_pb.js +115 -0
  28. package/dist/generated/workflow/v1/common_pb.d.ts +184 -0
  29. package/dist/generated/workflow/v1/common_pb.d.ts.map +1 -0
  30. package/dist/generated/workflow/v1/common_pb.js +86 -0
  31. package/dist/generated/workflow/v1/runtime_pb.d.ts +200 -0
  32. package/dist/generated/workflow/v1/runtime_pb.d.ts.map +1 -0
  33. package/dist/generated/workflow/v1/runtime_pb.js +40 -0
  34. package/dist/generated/workflow/v1/state_pb.d.ts +254 -0
  35. package/dist/generated/workflow/v1/state_pb.d.ts.map +1 -0
  36. package/dist/generated/workflow/v1/state_pb.js +68 -0
  37. package/dist/guardrails_evaluator/core.d.ts +23 -0
  38. package/dist/guardrails_evaluator/core.d.ts.map +1 -0
  39. package/dist/guardrails_evaluator/core.js +122 -0
  40. package/dist/guardrails_evaluator/index.d.ts +10 -0
  41. package/dist/guardrails_evaluator/index.d.ts.map +1 -0
  42. package/dist/guardrails_evaluator/index.js +11 -0
  43. package/dist/guardrails_evaluator/regex.d.ts +20 -0
  44. package/dist/guardrails_evaluator/regex.d.ts.map +1 -0
  45. package/dist/guardrails_evaluator/regex.js +233 -0
  46. package/dist/hooks.d.ts +109 -0
  47. package/dist/hooks.d.ts.map +1 -0
  48. package/dist/hooks.js +216 -0
  49. package/dist/http_path.d.ts +18 -0
  50. package/dist/http_path.d.ts.map +1 -0
  51. package/dist/http_path.js +53 -0
  52. package/dist/index.d.ts +35 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +41 -0
  55. package/dist/launcher.d.ts +130 -0
  56. package/dist/launcher.d.ts.map +1 -0
  57. package/dist/launcher.js +325 -0
  58. package/dist/logger.d.ts +96 -0
  59. package/dist/logger.d.ts.map +1 -0
  60. package/dist/logger.js +204 -0
  61. package/dist/mcp_oauth.d.ts +51 -0
  62. package/dist/mcp_oauth.d.ts.map +1 -0
  63. package/dist/mcp_oauth.js +389 -0
  64. package/dist/mcp_oauth_secret.d.ts +21 -0
  65. package/dist/mcp_oauth_secret.d.ts.map +1 -0
  66. package/dist/mcp_oauth_secret.js +122 -0
  67. package/dist/mcp_tools.d.ts +71 -0
  68. package/dist/mcp_tools.d.ts.map +1 -0
  69. package/dist/mcp_tools.js +301 -0
  70. package/dist/memory_appbound.d.ts +42 -0
  71. package/dist/memory_appbound.d.ts.map +1 -0
  72. package/dist/memory_appbound.js +159 -0
  73. package/dist/memory_writer.d.ts +49 -0
  74. package/dist/memory_writer.d.ts.map +1 -0
  75. package/dist/memory_writer.js +171 -0
  76. package/dist/metrics.d.ts +84 -0
  77. package/dist/metrics.d.ts.map +1 -0
  78. package/dist/metrics.js +205 -0
  79. package/dist/models.d.ts +1458 -0
  80. package/dist/models.d.ts.map +1 -0
  81. package/dist/models.js +1726 -0
  82. package/dist/node_logger.d.ts +43 -0
  83. package/dist/node_logger.d.ts.map +1 -0
  84. package/dist/node_logger.js +158 -0
  85. package/dist/owner_callback.d.ts +16 -0
  86. package/dist/owner_callback.d.ts.map +1 -0
  87. package/dist/owner_callback.js +40 -0
  88. package/dist/progress.d.ts +57 -0
  89. package/dist/progress.d.ts.map +1 -0
  90. package/dist/progress.js +140 -0
  91. package/dist/runtime.d.ts +131 -0
  92. package/dist/runtime.d.ts.map +1 -0
  93. package/dist/runtime.js +351 -0
  94. package/dist/secure_llm_proxy.d.ts +115 -0
  95. package/dist/secure_llm_proxy.d.ts.map +1 -0
  96. package/dist/secure_llm_proxy.js +922 -0
  97. package/dist/secure_wrapper.d.ts +332 -0
  98. package/dist/secure_wrapper.d.ts.map +1 -0
  99. package/dist/secure_wrapper.js +1249 -0
  100. package/dist/server/aer.d.ts +61 -0
  101. package/dist/server/aer.d.ts.map +1 -0
  102. package/dist/server/aer.js +1124 -0
  103. package/dist/server/auth.d.ts +56 -0
  104. package/dist/server/auth.d.ts.map +1 -0
  105. package/dist/server/auth.js +132 -0
  106. package/dist/server/base.d.ts +104 -0
  107. package/dist/server/base.d.ts.map +1 -0
  108. package/dist/server/base.js +150 -0
  109. package/dist/server/callInterrupt.d.ts +49 -0
  110. package/dist/server/callInterrupt.d.ts.map +1 -0
  111. package/dist/server/callInterrupt.js +68 -0
  112. package/dist/server/callback_delivery.d.ts +14 -0
  113. package/dist/server/callback_delivery.d.ts.map +1 -0
  114. package/dist/server/callback_delivery.js +141 -0
  115. package/dist/server/chunk_types.d.ts +50 -0
  116. package/dist/server/chunk_types.d.ts.map +1 -0
  117. package/dist/server/chunk_types.js +62 -0
  118. package/dist/server/cors.d.ts +52 -0
  119. package/dist/server/cors.d.ts.map +1 -0
  120. package/dist/server/cors.js +107 -0
  121. package/dist/server/drain.d.ts +169 -0
  122. package/dist/server/drain.d.ts.map +1 -0
  123. package/dist/server/drain.js +455 -0
  124. package/dist/server/function.d.ts +77 -0
  125. package/dist/server/function.d.ts.map +1 -0
  126. package/dist/server/function.js +337 -0
  127. package/dist/server/http_retry.d.ts +37 -0
  128. package/dist/server/http_retry.d.ts.map +1 -0
  129. package/dist/server/http_retry.js +157 -0
  130. package/dist/server/index.d.ts +7 -0
  131. package/dist/server/index.d.ts.map +1 -0
  132. package/dist/server/index.js +5 -0
  133. package/dist/server/metadata.d.ts +50 -0
  134. package/dist/server/metadata.d.ts.map +1 -0
  135. package/dist/server/metadata.js +193 -0
  136. package/dist/server/oe_url.d.ts +36 -0
  137. package/dist/server/oe_url.d.ts.map +1 -0
  138. package/dist/server/oe_url.js +50 -0
  139. package/dist/server/owner_url.d.ts +35 -0
  140. package/dist/server/owner_url.d.ts.map +1 -0
  141. package/dist/server/owner_url.js +146 -0
  142. package/dist/server/query.d.ts +42 -0
  143. package/dist/server/query.d.ts.map +1 -0
  144. package/dist/server/query.js +28 -0
  145. package/dist/server/tool.d.ts +138 -0
  146. package/dist/server/tool.d.ts.map +1 -0
  147. package/dist/server/tool.js +1017 -0
  148. package/dist/span_names.d.ts +21 -0
  149. package/dist/span_names.d.ts.map +1 -0
  150. package/dist/span_names.js +31 -0
  151. package/dist/structured_logging/constants.d.ts +17 -0
  152. package/dist/structured_logging/constants.d.ts.map +1 -0
  153. package/dist/structured_logging/constants.js +71 -0
  154. package/dist/structured_logging/env.d.ts +18 -0
  155. package/dist/structured_logging/env.d.ts.map +1 -0
  156. package/dist/structured_logging/env.js +39 -0
  157. package/dist/structured_logging/install.d.ts +56 -0
  158. package/dist/structured_logging/install.d.ts.map +1 -0
  159. package/dist/structured_logging/install.js +107 -0
  160. package/dist/structured_logging/layout.d.ts +9 -0
  161. package/dist/structured_logging/layout.d.ts.map +1 -0
  162. package/dist/structured_logging/layout.js +144 -0
  163. package/dist/structured_logging/serialize.d.ts +27 -0
  164. package/dist/structured_logging/serialize.d.ts.map +1 -0
  165. package/dist/structured_logging/serialize.js +61 -0
  166. package/dist/structured_logging/stdio_capture.d.ts +59 -0
  167. package/dist/structured_logging/stdio_capture.d.ts.map +1 -0
  168. package/dist/structured_logging/stdio_capture.js +164 -0
  169. package/dist/structured_logging/uncaught.d.ts +14 -0
  170. package/dist/structured_logging/uncaught.d.ts.map +1 -0
  171. package/dist/structured_logging/uncaught.js +58 -0
  172. package/dist/structured_logging.d.ts +48 -0
  173. package/dist/structured_logging.d.ts.map +1 -0
  174. package/dist/structured_logging.js +47 -0
  175. package/dist/tls_client.d.ts +61 -0
  176. package/dist/tls_client.d.ts.map +1 -0
  177. package/dist/tls_client.js +298 -0
  178. package/dist/tool_api_error.d.ts +62 -0
  179. package/dist/tool_api_error.d.ts.map +1 -0
  180. package/dist/tool_api_error.js +399 -0
  181. package/dist/tool_memory_ownership.d.ts +10 -0
  182. package/dist/tool_memory_ownership.d.ts.map +1 -0
  183. package/dist/tool_memory_ownership.js +36 -0
  184. package/dist/toolpod_handlers.d.ts +126 -0
  185. package/dist/toolpod_handlers.d.ts.map +1 -0
  186. package/dist/toolpod_handlers.js +1016 -0
  187. package/dist/tracing/exporters.d.ts +51 -0
  188. package/dist/tracing/exporters.d.ts.map +1 -0
  189. package/dist/tracing/exporters.js +327 -0
  190. package/dist/tracing/index.d.ts +3 -0
  191. package/dist/tracing/index.d.ts.map +1 -0
  192. package/dist/tracing/index.js +2 -0
  193. package/dist/tracing/setup.d.ts +76 -0
  194. package/dist/tracing/setup.d.ts.map +1 -0
  195. package/dist/tracing/setup.js +436 -0
  196. package/dist/utils.d.ts +204 -0
  197. package/dist/utils.d.ts.map +1 -0
  198. package/dist/utils.js +867 -0
  199. package/dist/workflow/activity.d.ts +71 -0
  200. package/dist/workflow/activity.d.ts.map +1 -0
  201. package/dist/workflow/activity.js +357 -0
  202. package/dist/workflow/attempt.d.ts +12 -0
  203. package/dist/workflow/attempt.d.ts.map +1 -0
  204. package/dist/workflow/attempt.js +96 -0
  205. package/dist/workflow/client.d.ts +46 -0
  206. package/dist/workflow/client.d.ts.map +1 -0
  207. package/dist/workflow/client.js +299 -0
  208. package/dist/workflow/context.d.ts +37 -0
  209. package/dist/workflow/context.d.ts.map +1 -0
  210. package/dist/workflow/context.js +350 -0
  211. package/dist/workflow/heartbeat.d.ts +15 -0
  212. package/dist/workflow/heartbeat.d.ts.map +1 -0
  213. package/dist/workflow/heartbeat.js +78 -0
  214. package/dist/workflow/index.d.ts +14 -0
  215. package/dist/workflow/index.d.ts.map +1 -0
  216. package/dist/workflow/index.js +10 -0
  217. package/dist/workflow/memory.d.ts +17 -0
  218. package/dist/workflow/memory.d.ts.map +1 -0
  219. package/dist/workflow/memory.js +184 -0
  220. package/package.json +73 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Per-project database name resolution with historical-name compatibility.
3
+ *
4
+ * Mirrors the orchestration-engine Go implementation
5
+ * (`internal/domains/orchestration-engine/dbresolve.go`) and the Python copies
6
+ * (`mongomem_core/db_naming.py`, `agent_engine_runner_shared/db_naming.py`). The codebases
7
+ * are independent, so the algorithm is intentionally duplicated rather than
8
+ * shared. Keep them in sync. See
9
+ * `docs/decisions/008-per-project-database-isolation.md`.
10
+ *
11
+ * The runner/AER applies this to the agent *store* database only; memory
12
+ * writes go through the memory-server proxy, which scopes its own database.
13
+ */
14
+ /**
15
+ * Derive the effective database name for `base` scoped to `projectId`.
16
+ *
17
+ * 1. empty `projectId` -> return `base` unchanged.
18
+ * 2. `base` already ends with `_{projectId}` -> return as-is (idempotent).
19
+ * 3. `scoped = base + "_" + projectId`:
20
+ * - `scoped` exists on the cluster -> use it.
21
+ * - else an existing scoped `legacyBases` candidate -> use it.
22
+ * - else an existing unscoped `legacyBases` candidate -> use it.
23
+ * - else -> use `scoped` (fresh deployment).
24
+ *
25
+ * Unscoped fallback is limited to known platform defaults. The current base and arbitrary names are never auto-adopted.
26
+ */
27
+ export declare function resolveProjectScopedDb(base: string, projectId: string, existing: string[], legacyBases?: string[]): string;
28
+ /**
29
+ * Whether an empty PROJECT_ID must fail closed (REQUIRE_PROJECT_SCOPED_DB).
30
+ *
31
+ * ECP stamps this flag on managed AER pods (where PROJECT_ID is always injected),
32
+ * so an empty PROJECT_ID there fails closed rather than silently writing to the
33
+ * unscoped store. Local CLI dev leaves the flag unset and uses the unscoped name.
34
+ */
35
+ export declare function projectScopingRequired(): boolean;
36
+ export interface ListsDatabaseNames {
37
+ listDatabaseNames(): Promise<string[]>;
38
+ }
39
+ /**
40
+ * Resolve `base` against the live cluster via `listDatabaseNames`.
41
+ *
42
+ * Empty `projectId` throws when scoping is required (`required`, defaulting to
43
+ * {@link projectScopingRequired}), else warns and returns `base`. A listing
44
+ * failure throws so a transient error cannot create a competing current-name
45
+ * database beside an existing legacy one. `legacyBases` are previous defaults
46
+ * whose project-scoped forms, then bare forms, are adopted before a fresh
47
+ * scoped database is created.
48
+ */
49
+ export declare function resolveEffectiveDb(client: ListsDatabaseNames, base: string, projectId: string, opts?: {
50
+ required?: boolean;
51
+ label?: string;
52
+ legacyBases?: string[];
53
+ }): Promise<string>;
54
+ //# sourceMappingURL=db_naming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db_naming.d.ts","sourceRoot":"","sources":["../src/db_naming.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAAE,EAClB,WAAW,GAAE,MAAM,EAAO,GACzB,MAAM,CAeR;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAIhD;AAED,MAAM,WAAW,kBAAkB;IACjC,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CACxC;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GACxE,OAAO,CAAC,MAAM,CAAC,CAuCjB"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Per-project database name resolution with historical-name compatibility.
3
+ *
4
+ * Mirrors the orchestration-engine Go implementation
5
+ * (`internal/domains/orchestration-engine/dbresolve.go`) and the Python copies
6
+ * (`mongomem_core/db_naming.py`, `agent_engine_runner_shared/db_naming.py`). The codebases
7
+ * are independent, so the algorithm is intentionally duplicated rather than
8
+ * shared. Keep them in sync. See
9
+ * `docs/decisions/008-per-project-database-isolation.md`.
10
+ *
11
+ * The runner/AER applies this to the agent *store* database only; memory
12
+ * writes go through the memory-server proxy, which scopes its own database.
13
+ */
14
+ import { getLogger } from "./logger.js";
15
+ const logger = getLogger("agent_engine_runner_shared.db_naming");
16
+ const TRUTHY = new Set(["1", "true", "yes", "on"]);
17
+ /**
18
+ * Derive the effective database name for `base` scoped to `projectId`.
19
+ *
20
+ * 1. empty `projectId` -> return `base` unchanged.
21
+ * 2. `base` already ends with `_{projectId}` -> return as-is (idempotent).
22
+ * 3. `scoped = base + "_" + projectId`:
23
+ * - `scoped` exists on the cluster -> use it.
24
+ * - else an existing scoped `legacyBases` candidate -> use it.
25
+ * - else an existing unscoped `legacyBases` candidate -> use it.
26
+ * - else -> use `scoped` (fresh deployment).
27
+ *
28
+ * Unscoped fallback is limited to known platform defaults. The current base and arbitrary names are never auto-adopted.
29
+ */
30
+ export function resolveProjectScopedDb(base, projectId, existing, legacyBases = []) {
31
+ if (!projectId)
32
+ return base;
33
+ const suffix = "_" + projectId;
34
+ if (base.endsWith(suffix))
35
+ return base;
36
+ const scoped = base + suffix;
37
+ const names = new Set(existing);
38
+ if (names.has(scoped))
39
+ return scoped;
40
+ for (const legacy of legacyBases) {
41
+ const legacyScoped = legacy + suffix;
42
+ if (names.has(legacyScoped))
43
+ return legacyScoped;
44
+ }
45
+ for (const legacy of legacyBases) {
46
+ if (names.has(legacy))
47
+ return legacy;
48
+ }
49
+ return scoped;
50
+ }
51
+ /**
52
+ * Whether an empty PROJECT_ID must fail closed (REQUIRE_PROJECT_SCOPED_DB).
53
+ *
54
+ * ECP stamps this flag on managed AER pods (where PROJECT_ID is always injected),
55
+ * so an empty PROJECT_ID there fails closed rather than silently writing to the
56
+ * unscoped store. Local CLI dev leaves the flag unset and uses the unscoped name.
57
+ */
58
+ export function projectScopingRequired() {
59
+ return TRUTHY.has((process.env["REQUIRE_PROJECT_SCOPED_DB"] ?? "").trim().toLowerCase());
60
+ }
61
+ /**
62
+ * Resolve `base` against the live cluster via `listDatabaseNames`.
63
+ *
64
+ * Empty `projectId` throws when scoping is required (`required`, defaulting to
65
+ * {@link projectScopingRequired}), else warns and returns `base`. A listing
66
+ * failure throws so a transient error cannot create a competing current-name
67
+ * database beside an existing legacy one. `legacyBases` are previous defaults
68
+ * whose project-scoped forms, then bare forms, are adopted before a fresh
69
+ * scoped database is created.
70
+ */
71
+ export async function resolveEffectiveDb(client, base, projectId, opts = {}) {
72
+ const required = opts.required ?? projectScopingRequired();
73
+ const label = opts.label ?? "database";
74
+ if (!projectId) {
75
+ if (required) {
76
+ throw new Error(`PROJECT_ID is empty but per-project DB isolation is required ` +
77
+ `(REQUIRE_PROJECT_SCOPED_DB); refusing to use the unscoped ${label} '${base}'`);
78
+ }
79
+ logger.warn(`PROJECT_ID is empty; per-project DB isolation disabled, using unscoped ${label} '${base}'`);
80
+ return base;
81
+ }
82
+ let existing;
83
+ try {
84
+ existing = await client.listDatabaseNames();
85
+ }
86
+ catch (err) {
87
+ throw new Error(`database discovery failed for project '${projectId}'; refusing to select a database`, { cause: err });
88
+ }
89
+ const resolved = resolveProjectScopedDb(base, projectId, existing, opts.legacyBases);
90
+ if (resolved !== base + "_" + projectId && !base.endsWith("_" + projectId)) {
91
+ logger.info(`using historical ${label} '${resolved}' for project '${projectId}'`);
92
+ }
93
+ return resolved;
94
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Shared local error reporting helpers for CLI-generated runtime flows.
3
+ *
4
+ * Mirrors `agent_engine_runner_shared/error_reporting.py`. Reports agent runtime/build
5
+ * failures to Sentry, gated by env (`AGENTIC_SENTRY_ENABLED=1` + a DSN). All
6
+ * outbound payloads are scrubbed for secrets and the caller's home directory
7
+ * via `beforeSend` plus per-value redaction before capture.
8
+ *
9
+ * `@sentry/node` is an *optional* dependency (a devDependency here for types
10
+ * and tests): it is loaded via a guarded dynamic import only when reporting is
11
+ * enabled, so agents that never flip `AGENTIC_SENTRY_ENABLED` never install or
12
+ * pay for it. When the module isn't present, or anything in the reporting path
13
+ * faults, every function degrades to a no-op — reporting must never block an
14
+ * agent from starting or mask its real failure.
15
+ */
16
+ type SeverityLevel = "fatal" | "error" | "warning" | "log" | "info" | "debug";
17
+ /** @internal — test-only override of the resolved home directory. */
18
+ export declare function setHomeDirForTest(dir: string): void;
19
+ export interface InitErrorReportingArgs {
20
+ surface: string;
21
+ component?: string | null;
22
+ mode?: string | null;
23
+ }
24
+ /**
25
+ * Initialize Sentry error reporting. No-op (returns `false`) unless
26
+ * `AGENTIC_SENTRY_ENABLED=1` and `AGENTIC_SENTRY_DSN` are both set, and
27
+ * `@sentry/node` is installed. Tags every event with `surface` and, when
28
+ * provided, `component` / `mode`.
29
+ *
30
+ * Never throws: a missing module or a faulting `Sentry.init` leaves reporting
31
+ * disabled rather than propagating — enabling observability must not turn into
32
+ * a boot blocker.
33
+ */
34
+ export declare function initErrorReporting(args: InitErrorReportingArgs): Promise<boolean>;
35
+ export interface CaptureExceptionArgs {
36
+ summary?: string | null;
37
+ extra?: Record<string, unknown>;
38
+ }
39
+ /**
40
+ * Capture an exception. When `summary` is given, the reported error message is
41
+ * the (redacted) summary with the original exception's type/message preserved
42
+ * as scoped extras — matching Python's `_exception_for_capture`.
43
+ */
44
+ export declare function captureException(exc: unknown, args?: CaptureExceptionArgs): void;
45
+ export declare function captureMessage(message: string, level?: SeverityLevel, extra?: Record<string, unknown>): void;
46
+ export declare function flush(timeoutMs?: number): Promise<void>;
47
+ export interface SubprocessFailure {
48
+ /** The command that failed — array (argv) or a single string. */
49
+ command: string | readonly string[];
50
+ /** Process exit code, if known. */
51
+ exitCode?: number | null;
52
+ stdout?: string | Uint8Array | null;
53
+ stderr?: string | Uint8Array | null;
54
+ }
55
+ /**
56
+ * A one-line summary of a failed subprocess: the command label plus the most
57
+ * meaningful line of its output, or the exit code when no output stands out.
58
+ */
59
+ export declare function summarizeSubprocessFailure(exc: SubprocessFailure): string;
60
+ /** The trailing `MAX_OUTPUT_TAIL_CHARS` of a subprocess's stdout+stderr. */
61
+ export declare function subprocessOutputTail(exc: SubprocessFailure): string;
62
+ /** @internal — exported only for tests; not part of the public API. */
63
+ export declare function beforeSend(event: Record<string, unknown>, _hint?: unknown): Record<string, unknown>;
64
+ /** @internal — exported only for tests; not part of the public API. */
65
+ export declare function redactText(text: string): string;
66
+ export {};
67
+ //# sourceMappingURL=error_reporting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error_reporting.d.ts","sourceRoot":"","sources":["../src/error_reporting.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAUH,KAAK,aAAa,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAiC9E,qEAAqE;AACrE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,sBAAsB,GAC3B,OAAO,CAAC,OAAO,CAAC,CAwClB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,OAAO,EACZ,IAAI,GAAE,oBAAyB,GAC9B,IAAI,CAwBN;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,aAAuB,EAC9B,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAClC,IAAI,CAaN;AAED,wBAAsB,KAAK,CAAC,SAAS,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAO3D;AAMD,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,OAAO,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACpC,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IACpC,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;CACrC;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAKzE;AAED,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,CAUnE;AAMD,uEAAuE;AACvE,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,CAAC,EAAE,OAAO,GACd,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAwBzB;AAmCD,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS/C"}
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Shared local error reporting helpers for CLI-generated runtime flows.
3
+ *
4
+ * Mirrors `agent_engine_runner_shared/error_reporting.py`. Reports agent runtime/build
5
+ * failures to Sentry, gated by env (`AGENTIC_SENTRY_ENABLED=1` + a DSN). All
6
+ * outbound payloads are scrubbed for secrets and the caller's home directory
7
+ * via `beforeSend` plus per-value redaction before capture.
8
+ *
9
+ * `@sentry/node` is an *optional* dependency (a devDependency here for types
10
+ * and tests): it is loaded via a guarded dynamic import only when reporting is
11
+ * enabled, so agents that never flip `AGENTIC_SENTRY_ENABLED` never install or
12
+ * pay for it. When the module isn't present, or anything in the reporting path
13
+ * faults, every function degrades to a no-op — reporting must never block an
14
+ * agent from starting or mask its real failure.
15
+ */
16
+ import { homedir } from "node:os";
17
+ // The lazily-imported @sentry/node module; null until a successful enable.
18
+ let sentry = null;
19
+ let enabled = false;
20
+ const MAX_SUMMARY_LINE_LENGTH = 240;
21
+ const MAX_OUTPUT_TAIL_CHARS = 8 * 1024;
22
+ // Cap redaction recursion so a self-referential or pathologically nested
23
+ // caller-supplied `extra` can't drive `redactValue` into a stack overflow.
24
+ const MAX_REDACT_DEPTH = 12;
25
+ // `key=value` / `key: value` inline secrets. Case-insensitive; keeps the key
26
+ // and separator, redacts the value up to the next whitespace/comma/semicolon.
27
+ const SECRET_INLINE_PATTERN = /\b(api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password)(=|:)\s*([^\s,;]+)/gi;
28
+ const BEARER_PATTERN = /\b(bearer)\s+[A-Za-z0-9._-]+/gi;
29
+ const URL_USERINFO_PATTERN = /\b([a-z][a-z0-9+.-]*:\/\/)[^:/\s@]+:[^@\s/]+@/gi;
30
+ // Bare-token form (scheme://token@host, no ":" separator) — e.g. a GitHub PAT
31
+ // embedded as `https://ghp_xxx@github.com`. Applied after URL_USERINFO_PATTERN
32
+ // so a user:pass@ pair is never double-matched by this looser pattern.
33
+ const URL_USERINFO_BARE_PATTERN = /\b([a-z][a-z0-9+.-]*:\/\/)[^:/\s@]+@/gi;
34
+ function resolveHomeDir() {
35
+ try {
36
+ return homedir().trim();
37
+ }
38
+ catch {
39
+ return (process.env["HOME"] ?? "").trim();
40
+ }
41
+ }
42
+ let homeDir = resolveHomeDir();
43
+ /** @internal — test-only override of the resolved home directory. */
44
+ export function setHomeDirForTest(dir) {
45
+ homeDir = dir;
46
+ }
47
+ /**
48
+ * Initialize Sentry error reporting. No-op (returns `false`) unless
49
+ * `AGENTIC_SENTRY_ENABLED=1` and `AGENTIC_SENTRY_DSN` are both set, and
50
+ * `@sentry/node` is installed. Tags every event with `surface` and, when
51
+ * provided, `component` / `mode`.
52
+ *
53
+ * Never throws: a missing module or a faulting `Sentry.init` leaves reporting
54
+ * disabled rather than propagating — enabling observability must not turn into
55
+ * a boot blocker.
56
+ */
57
+ export async function initErrorReporting(args) {
58
+ enabled = false;
59
+ sentry = null;
60
+ const dsn = (process.env["AGENTIC_SENTRY_DSN"] ?? "").trim();
61
+ if (!(process.env["AGENTIC_SENTRY_ENABLED"] === "1" && dsn))
62
+ return false;
63
+ try {
64
+ sentry = await import("@sentry/node");
65
+ }
66
+ catch {
67
+ // @sentry/node not installed — reporting is opt-in, so stay disabled.
68
+ return false;
69
+ }
70
+ try {
71
+ sentry.init({
72
+ dsn,
73
+ environment: process.env["AGENTIC_SENTRY_ENVIRONMENT"] ?? "local-dev",
74
+ release: process.env["AGENTIC_SENTRY_RELEASE"] || undefined,
75
+ tracesSampleRate: 0.0,
76
+ debug: process.env["AGENTIC_SENTRY_DEBUG"] === "1",
77
+ // Our scrubber operates on a generic event dict and returns the same
78
+ // (mutated) object; narrow the return to the callback's event type.
79
+ beforeSend: (event, hint) => beforeSend(event, hint),
80
+ });
81
+ sentry.setTag("surface", args.surface);
82
+ if (args.component)
83
+ sentry.setTag("component", args.component);
84
+ if (args.mode)
85
+ sentry.setTag("mode", args.mode);
86
+ }
87
+ catch {
88
+ // A malformed DSN/environment or any init fault disables reporting rather
89
+ // than crashing the agent at boot.
90
+ sentry = null;
91
+ return false;
92
+ }
93
+ enabled = true;
94
+ return true;
95
+ }
96
+ /**
97
+ * Capture an exception. When `summary` is given, the reported error message is
98
+ * the (redacted) summary with the original exception's type/message preserved
99
+ * as scoped extras — matching Python's `_exception_for_capture`.
100
+ */
101
+ export function captureException(exc, args = {}) {
102
+ if (!enabled || !sentry)
103
+ return;
104
+ const s = sentry;
105
+ // Reporting must never throw into the caller — a fault here would mask the
106
+ // very failure the caller is trying to report.
107
+ try {
108
+ const { summary, extra } = args;
109
+ const captured = exceptionForCapture(exc, summary);
110
+ s.withScope((scope) => {
111
+ if (summary) {
112
+ scope.setExtra("original_exception_type", exc instanceof Error ? exc.name : typeof exc);
113
+ scope.setExtra("original_exception_message", redactText(String(exc)));
114
+ }
115
+ for (const [key, value] of Object.entries(extra ?? {})) {
116
+ scope.setExtra(key, redactValue(value));
117
+ }
118
+ s.captureException(captured);
119
+ });
120
+ }
121
+ catch {
122
+ /* swallow — best-effort reporting */
123
+ }
124
+ }
125
+ export function captureMessage(message, level = "error", extra = {}) {
126
+ if (!enabled || !sentry)
127
+ return;
128
+ const s = sentry;
129
+ try {
130
+ s.withScope((scope) => {
131
+ for (const [key, value] of Object.entries(extra)) {
132
+ scope.setExtra(key, redactValue(value));
133
+ }
134
+ s.captureMessage(redactText(message), level);
135
+ });
136
+ }
137
+ catch {
138
+ /* swallow — best-effort reporting */
139
+ }
140
+ }
141
+ export async function flush(timeoutMs = 2000) {
142
+ if (!enabled || !sentry)
143
+ return;
144
+ try {
145
+ await sentry.flush(timeoutMs);
146
+ }
147
+ catch {
148
+ /* swallow — best-effort reporting */
149
+ }
150
+ }
151
+ /**
152
+ * A one-line summary of a failed subprocess: the command label plus the most
153
+ * meaningful line of its output, or the exit code when no output stands out.
154
+ */
155
+ export function summarizeSubprocessFailure(exc) {
156
+ const command = commandLabel(exc.command);
157
+ const headline = failureHeadline(subprocessOutputTail(exc));
158
+ if (headline)
159
+ return `${command} failed: ${headline}`;
160
+ return `${command} failed with exit ${exc.exitCode ?? "unknown"}`;
161
+ }
162
+ /** The trailing `MAX_OUTPUT_TAIL_CHARS` of a subprocess's stdout+stderr. */
163
+ export function subprocessOutputTail(exc) {
164
+ const chunks = [];
165
+ for (const value of [exc.stdout, exc.stderr]) {
166
+ if (value === null || value === undefined)
167
+ continue;
168
+ chunks.push(typeof value === "string" ? value : Buffer.from(value).toString("utf-8"));
169
+ }
170
+ if (chunks.length === 0)
171
+ return "";
172
+ return tailText(chunks.join("\n"));
173
+ }
174
+ // =============================================================================
175
+ // Redaction — internal, but pure and unit-tested directly
176
+ // =============================================================================
177
+ /** @internal — exported only for tests; not part of the public API. */
178
+ export function beforeSend(event, _hint) {
179
+ const data = event;
180
+ data["server_name"] = null;
181
+ if ("message" in data) {
182
+ data["message"] = redactText(String(data["message"]));
183
+ }
184
+ for (const section of [
185
+ "request",
186
+ "user",
187
+ "tags",
188
+ "extra",
189
+ "contexts",
190
+ "exception",
191
+ "breadcrumbs",
192
+ "threads",
193
+ "logentry",
194
+ "modules",
195
+ ]) {
196
+ const payload = data[section];
197
+ if (payload !== null && typeof payload === "object") {
198
+ data[section] = redactValue(payload);
199
+ }
200
+ }
201
+ return event;
202
+ }
203
+ /**
204
+ * Recursively redact strings in a value. Guards against caller-supplied
205
+ * `extra` that is self-referential (WeakSet cycle check) or pathologically
206
+ * deep (`MAX_REDACT_DEPTH`): either would otherwise overflow the stack and
207
+ * take down the reporting path. A cycle/over-depth node is replaced with a
208
+ * sentinel rather than recursed into.
209
+ */
210
+ function redactValue(value, depth = 0, seen = new WeakSet()) {
211
+ if (typeof value === "string")
212
+ return redactText(value);
213
+ if (value === null || typeof value !== "object")
214
+ return value;
215
+ if (seen.has(value))
216
+ return "[Circular]";
217
+ if (depth >= MAX_REDACT_DEPTH)
218
+ return "[MaxDepth]";
219
+ seen.add(value);
220
+ const result = Array.isArray(value)
221
+ ? value.map((item) => redactValue(item, depth + 1, seen))
222
+ : Object.fromEntries(Object.entries(value).map(([k, v]) => [
223
+ k,
224
+ redactValue(v, depth + 1, seen),
225
+ ]));
226
+ // Allow the same object reached again via a sibling branch (a DAG, not a
227
+ // cycle) to serialize in full: only nodes on the current path are guarded.
228
+ seen.delete(value);
229
+ return result;
230
+ }
231
+ /** @internal — exported only for tests; not part of the public API. */
232
+ export function redactText(text) {
233
+ if (!text)
234
+ return text;
235
+ let redacted = text;
236
+ if (homeDir)
237
+ redacted = redacted.split(homeDir).join("<home>");
238
+ redacted = redacted.replace(URL_USERINFO_PATTERN, "$1<redacted>:<redacted>@");
239
+ redacted = redacted.replace(URL_USERINFO_BARE_PATTERN, "$1<redacted>@");
240
+ redacted = redacted.replace(SECRET_INLINE_PATTERN, "$1$2<redacted>");
241
+ redacted = redacted.replace(BEARER_PATTERN, "$1 <redacted>");
242
+ return redacted;
243
+ }
244
+ function exceptionForCapture(exc, summary) {
245
+ if (!summary)
246
+ return exc;
247
+ const wrapped = new Error(redactText(summary));
248
+ // Preserve the original stack so Sentry groups on the real failure site.
249
+ if (exc instanceof Error && exc.stack)
250
+ wrapped.stack = exc.stack;
251
+ return wrapped;
252
+ }
253
+ function commandLabel(cmd) {
254
+ if (Array.isArray(cmd)) {
255
+ const parts = cmd.map(String).filter((p) => p.trim());
256
+ if (parts.length === 0)
257
+ return "subprocess";
258
+ if (parts.length >= 3 && parts[0] === "uv" && parts[1] === "pip") {
259
+ return parts.slice(0, 3).join(" ");
260
+ }
261
+ if (parts.length >= 2)
262
+ return parts.slice(0, 2).join(" ");
263
+ return parts[0];
264
+ }
265
+ const text = String(cmd).trim();
266
+ return text || "subprocess";
267
+ }
268
+ function failureHeadline(output) {
269
+ const normalized = output
270
+ .split("\n")
271
+ .map(normalizeFailureLine)
272
+ .filter((line) => line);
273
+ for (let i = normalized.length - 1; i >= 0; i--) {
274
+ const line = normalized[i];
275
+ const lower = line.toLowerCase();
276
+ if (lower.includes("failed") ||
277
+ lower.includes("error") ||
278
+ lower.includes("not found") ||
279
+ lower.includes("no matching") ||
280
+ lower.includes("exception")) {
281
+ return truncateSummaryLine(line);
282
+ }
283
+ }
284
+ if (normalized.length === 0)
285
+ return "";
286
+ return truncateSummaryLine(normalized[normalized.length - 1]);
287
+ }
288
+ function normalizeFailureLine(line) {
289
+ let cleaned = line
290
+ .trim()
291
+ .replace(/^[│>]+/, "")
292
+ .trim();
293
+ if (cleaned.startsWith("×"))
294
+ cleaned = cleaned.slice(1).trim();
295
+ if (cleaned.startsWith("╰─▶"))
296
+ cleaned = cleaned.slice(3).trim();
297
+ if (cleaned.toLowerCase().startsWith("error:"))
298
+ cleaned = cleaned.slice(6).trim();
299
+ return cleaned;
300
+ }
301
+ function tailText(text) {
302
+ const trimmed = text.trim();
303
+ if (trimmed.length <= MAX_OUTPUT_TAIL_CHARS)
304
+ return trimmed;
305
+ return trimmed.slice(-MAX_OUTPUT_TAIL_CHARS);
306
+ }
307
+ function truncateSummaryLine(line) {
308
+ if (line.length <= MAX_SUMMARY_LINE_LENGTH)
309
+ return line;
310
+ return line.slice(0, MAX_SUMMARY_LINE_LENGTH - 3) + "...";
311
+ }