@smithers-orchestrator/cli 0.16.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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +55 -0
  3. package/src/AgentAvailability.ts +13 -0
  4. package/src/AgentAvailabilityStatus.ts +5 -0
  5. package/src/AggregateNodeDetailParams.ts +5 -0
  6. package/src/AskOptions.ts +12 -0
  7. package/src/ChatAttemptMeta.ts +7 -0
  8. package/src/ChatAttemptRow.ts +12 -0
  9. package/src/ChatOutputEvent.ts +6 -0
  10. package/src/DiffBundleLike.ts +6 -0
  11. package/src/DiscoveredWorkflow.ts +9 -0
  12. package/src/EnrichedNodeDetail.ts +60 -0
  13. package/src/EventCategory.ts +18 -0
  14. package/src/FindDbWaitOptions.ts +4 -0
  15. package/src/FormatEventLineOptions.ts +4 -0
  16. package/src/HijackCandidate.ts +11 -0
  17. package/src/HijackLaunchSpec.ts +6 -0
  18. package/src/InitWorkflowPackOptions.ts +4 -0
  19. package/src/InitWorkflowPackResult.ts +6 -0
  20. package/src/NativeHijackEngine.ts +8 -0
  21. package/src/NodeDetailAttempt.ts +22 -0
  22. package/src/NodeDetailTokenUsage.ts +11 -0
  23. package/src/NodeDetailToolCall.ts +12 -0
  24. package/src/ParsedNodeOutputEvent.ts +9 -0
  25. package/src/RenderNodeDetailOptions.ts +4 -0
  26. package/src/RunAutoResumeSkipReason.ts +4 -0
  27. package/src/RunDiffCommandInput.ts +13 -0
  28. package/src/RunDiffCommandResult.ts +3 -0
  29. package/src/RunOutputCommandInput.ts +12 -0
  30. package/src/RunOutputCommandResult.ts +3 -0
  31. package/src/RunRewindCommandInput.ts +14 -0
  32. package/src/RunRewindCommandResult.ts +3 -0
  33. package/src/RunTreeCommandInput.ts +14 -0
  34. package/src/RunTreeCommandResult.ts +3 -0
  35. package/src/SmithersEventType.ts +3 -0
  36. package/src/SupervisorOptions.ts +33 -0
  37. package/src/SupervisorPollSummary.ts +6 -0
  38. package/src/TreeRenderOptions.ts +5 -0
  39. package/src/WatchLoopOptions.ts +9 -0
  40. package/src/WatchLoopResult.ts +8 -0
  41. package/src/WatchRenderContext.ts +4 -0
  42. package/src/WhyBlocker.ts +17 -0
  43. package/src/WhyBlockerKind.ts +9 -0
  44. package/src/WhyDiagnosis.ts +10 -0
  45. package/src/WorkflowCta.ts +4 -0
  46. package/src/WorkflowSourceType.ts +1 -0
  47. package/src/agent-detection.js +257 -0
  48. package/src/ask.js +491 -0
  49. package/src/chat.js +226 -0
  50. package/src/diff.js +221 -0
  51. package/src/event-categories.js +141 -0
  52. package/src/find-db.js +93 -0
  53. package/src/format.js +272 -0
  54. package/src/hijack-session.js +207 -0
  55. package/src/hijack.js +226 -0
  56. package/src/index.d.ts +1 -0
  57. package/src/index.js +4868 -0
  58. package/src/mcp/SemanticMcpServerOptions.ts +4 -0
  59. package/src/mcp/SemanticToolCallResult.ts +14 -0
  60. package/src/mcp/SemanticToolContext.ts +6 -0
  61. package/src/mcp/SemanticToolDefinition.ts +13 -0
  62. package/src/mcp/SemanticToolError.ts +6 -0
  63. package/src/mcp/semantic-server.js +41 -0
  64. package/src/mcp/semantic-tools.js +1242 -0
  65. package/src/node-detail.js +682 -0
  66. package/src/output.js +111 -0
  67. package/src/resume-detached.js +37 -0
  68. package/src/rewind.js +88 -0
  69. package/src/scheduler.js +112 -0
  70. package/src/smithersRuntime.js +63 -0
  71. package/src/supervisor.js +418 -0
  72. package/src/tree.js +307 -0
  73. package/src/tui/app.jsx +139 -0
  74. package/src/tui/app.tsx +5 -0
  75. package/src/tui/components/AskModal.jsx +109 -0
  76. package/src/tui/components/AskModal.tsx +3 -0
  77. package/src/tui/components/AttentionPane.jsx +112 -0
  78. package/src/tui/components/AttentionPane.tsx +6 -0
  79. package/src/tui/components/ChatPane.jsx +57 -0
  80. package/src/tui/components/ChatPane.tsx +7 -0
  81. package/src/tui/components/CronList.jsx +87 -0
  82. package/src/tui/components/CronList.tsx +5 -0
  83. package/src/tui/components/DetailsPane.jsx +96 -0
  84. package/src/tui/components/DetailsPane.tsx +7 -0
  85. package/src/tui/components/FramesPane.jsx +147 -0
  86. package/src/tui/components/FramesPane.tsx +8 -0
  87. package/src/tui/components/LogsPane.jsx +46 -0
  88. package/src/tui/components/LogsPane.tsx +6 -0
  89. package/src/tui/components/MetricsPane.jsx +108 -0
  90. package/src/tui/components/MetricsPane.tsx +5 -0
  91. package/src/tui/components/NodeDetailView.jsx +284 -0
  92. package/src/tui/components/NodeDetailView.tsx +7 -0
  93. package/src/tui/components/NodeInspector.jsx +51 -0
  94. package/src/tui/components/NodeInspector.tsx +7 -0
  95. package/src/tui/components/RunDetailView.jsx +190 -0
  96. package/src/tui/components/RunDetailView.tsx +7 -0
  97. package/src/tui/components/RunsList.jsx +184 -0
  98. package/src/tui/components/RunsList.tsx +7 -0
  99. package/src/tui/components/SqliteBrowser.jsx +131 -0
  100. package/src/tui/components/SqliteBrowser.tsx +5 -0
  101. package/src/tui/components/WorkflowLauncher.jsx +63 -0
  102. package/src/tui/components/WorkflowLauncher.tsx +3 -0
  103. package/src/util/CliErrorMapping.ts +7 -0
  104. package/src/util/CliExitCode.ts +10 -0
  105. package/src/util/errorMessage.js +212 -0
  106. package/src/util/exitCodes.js +18 -0
  107. package/src/watch.js +128 -0
  108. package/src/why-diagnosis.js +1000 -0
  109. package/src/workflow-pack.js +2151 -0
  110. package/src/workflows.js +122 -0
package/src/output.js ADDED
@@ -0,0 +1,111 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./RunOutputCommandInput.ts").RunOutputCommandInput} RunOutputCommandInput */
3
+ /** @typedef {import("./RunOutputCommandResult.ts").RunOutputCommandResult} RunOutputCommandResult */
4
+ // @smithers-type-exports-end
5
+
6
+ import { getNodeOutputRoute } from "@smithers-orchestrator/server/gatewayRoutes/getNodeOutput";
7
+ import { NodeOutputRouteError } from "@smithers-orchestrator/server/gatewayRoutes/NodeOutputRouteError";
8
+ import { EXIT_OK } from "./util/exitCodes.js";
9
+ import { formatCliErrorForStderr, getCliErrorMapping } from "./util/errorMessage.js";
10
+
11
+ /**
12
+ * @param {any} response
13
+ * @returns {string}
14
+ */
15
+ export function renderPrettyOutput(response) {
16
+ if (!response || response.row === null || response.row === undefined) {
17
+ if (response?.status === "pending") return "(pending)";
18
+ if (response?.status === "failed") return "(failed)";
19
+ return "(no output)";
20
+ }
21
+ const schemaFields = Array.isArray(response.schema?.fields) ? response.schema.fields : [];
22
+ const row = /** @type {Record<string, unknown>} */ (response.row);
23
+ const printed = new Set();
24
+ /** @type {string[]} */
25
+ const lines = [];
26
+ for (const field of schemaFields) {
27
+ if (!field || typeof field.name !== "string") continue;
28
+ if (!(field.name in row)) continue;
29
+ const value = row[field.name];
30
+ lines.push(`${field.name}: ${formatValue(value)}`);
31
+ printed.add(field.name);
32
+ }
33
+ for (const [key, value] of Object.entries(row)) {
34
+ if (printed.has(key)) continue;
35
+ lines.push(`${key}: ${formatValue(value)}`);
36
+ }
37
+ return lines.join("\n");
38
+ }
39
+
40
+ /** @param {unknown} value */
41
+ function formatValue(value) {
42
+ if (value === null) return "null";
43
+ if (value === undefined) return "";
44
+ if (typeof value === "string") return value;
45
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
46
+ try {
47
+ return JSON.stringify(value);
48
+ } catch {
49
+ return String(value);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * @param {import("@smithers-orchestrator/db/adapter").SmithersDb} adapter
55
+ * @param {string} runId
56
+ * @param {string} nodeId
57
+ * @returns {Promise<number | null>}
58
+ */
59
+ async function resolveLatestIteration(adapter, runId, nodeId) {
60
+ try {
61
+ const iterations = await adapter.listNodeIterations(runId, nodeId);
62
+ if (!Array.isArray(iterations) || iterations.length === 0) return null;
63
+ return iterations.reduce((max, row) => {
64
+ const it = typeof row?.iteration === "number" ? row.iteration : 0;
65
+ return it > max ? it : max;
66
+ }, 0);
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * @param {RunOutputCommandInput} input
74
+ * @returns {Promise<RunOutputCommandResult>}
75
+ */
76
+ export async function runOutputOnce(input) {
77
+ let iteration = input.iteration;
78
+ if (typeof iteration !== "number") {
79
+ const latest = await resolveLatestIteration(input.adapter, input.runId, input.nodeId);
80
+ iteration = latest ?? 0;
81
+ }
82
+ try {
83
+ const response = await getNodeOutputRoute({
84
+ runId: input.runId,
85
+ nodeId: input.nodeId,
86
+ iteration,
87
+ async resolveRun(runId) {
88
+ if (runId !== input.runId) return null;
89
+ const run = await input.adapter.getRun(runId);
90
+ if (!run) return null;
91
+ return { adapter: input.adapter, workflow: input.workflow ?? {} };
92
+ },
93
+ });
94
+ if (input.pretty) {
95
+ input.stdout.write(`${renderPrettyOutput(response)}\n`);
96
+ } else {
97
+ // Ticket 0014 §"output --json — raw row (default)": emit the row,
98
+ // not the response envelope. When the server signals non-produced
99
+ // state (pending/failed) we still emit the row field verbatim so
100
+ // scripts see `null` for those cases.
101
+ input.stdout.write(`${JSON.stringify(response?.row ?? null)}\n`);
102
+ }
103
+ return { exitCode: EXIT_OK };
104
+ } catch (err) {
105
+ const code = err instanceof NodeOutputRouteError ? err.code : undefined;
106
+ const message = err instanceof Error ? err.message : String(err);
107
+ input.stderr.write(`${formatCliErrorForStderr(code, message)}\n`);
108
+ const mapping = getCliErrorMapping(code, message);
109
+ return { exitCode: mapping.exitCode };
110
+ }
111
+ }
@@ -0,0 +1,37 @@
1
+ import { spawn } from "node:child_process";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ /** @typedef {import("./SupervisorOptions.ts").SupervisorSpawnClaim} SupervisorSpawnClaim */
6
+
7
+ /**
8
+ * Resume an existing run by launching `smithers up ... --resume` as a detached process.
9
+ * Returns the spawned PID when available.
10
+ *
11
+ * @param {string} workflowPath
12
+ * @param {string} runId
13
+ * @param {SupervisorSpawnClaim} [claim]
14
+ * @returns {number | null}
15
+ */
16
+ export function resumeRunDetached(workflowPath, runId, claim) {
17
+ const cliPath = fileURLToPath(new URL("./index.js", import.meta.url));
18
+ const args = [cliPath, "up", workflowPath, "--resume", "--run-id", runId, "-d", "--force"];
19
+ if (claim) {
20
+ args.push("--resume-claim-owner", claim.claimOwnerId);
21
+ args.push("--resume-claim-heartbeat", String(claim.claimHeartbeatAtMs));
22
+ if (claim.restoreRuntimeOwnerId !== undefined && claim.restoreRuntimeOwnerId !== null) {
23
+ args.push("--resume-restore-owner", claim.restoreRuntimeOwnerId);
24
+ }
25
+ if (claim.restoreHeartbeatAtMs !== undefined && claim.restoreHeartbeatAtMs !== null) {
26
+ args.push("--resume-restore-heartbeat", String(claim.restoreHeartbeatAtMs));
27
+ }
28
+ }
29
+ const child = spawn("bun", args, {
30
+ cwd: dirname(resolve(workflowPath)),
31
+ stdio: "ignore",
32
+ env: process.env,
33
+ detached: true,
34
+ });
35
+ child.unref();
36
+ return child.pid ?? null;
37
+ }
package/src/rewind.js ADDED
@@ -0,0 +1,88 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./RunRewindCommandInput.ts").RunRewindCommandInput} RunRewindCommandInput */
3
+ /** @typedef {import("./RunRewindCommandResult.ts").RunRewindCommandResult} RunRewindCommandResult */
4
+ // @smithers-type-exports-end
5
+
6
+ import readline from "node:readline";
7
+ import { jumpToFrameRoute } from "@smithers-orchestrator/server/gatewayRoutes/jumpToFrame";
8
+ import { JumpToFrameError } from "@smithers-orchestrator/time-travel/jumpToFrame";
9
+ import {
10
+ EXIT_OK,
11
+ EXIT_USER_ERROR,
12
+ EXIT_DECLINED,
13
+ } from "./util/exitCodes.js";
14
+ import { formatCliErrorForStderr, getCliErrorMapping } from "./util/errorMessage.js";
15
+
16
+ /**
17
+ * @param {RunRewindCommandInput} input
18
+ * @returns {Promise<boolean>}
19
+ */
20
+ async function defaultConfirm(input) {
21
+ if (!input.stdin.isTTY) {
22
+ return false;
23
+ }
24
+ const rl = readline.createInterface({ input: input.stdin, output: input.stderr });
25
+ try {
26
+ /** @type {string} */
27
+ const answer = await new Promise((resolve) => {
28
+ rl.question(
29
+ `Rewind run ${input.runId} to frame ${input.frameNo}? This is destructive. [y/N] `,
30
+ (raw) => resolve(raw ?? ""),
31
+ );
32
+ });
33
+ return /^y(es)?$/i.test(answer.trim());
34
+ } finally {
35
+ rl.close();
36
+ }
37
+ }
38
+
39
+ /**
40
+ * @param {RunRewindCommandInput} input
41
+ * @returns {Promise<RunRewindCommandResult>}
42
+ */
43
+ export async function runRewindOnce(input) {
44
+ if (!input.yes) {
45
+ if (!input.stdin.isTTY && !input.confirm) {
46
+ input.stderr.write(`${formatCliErrorForStderr("ConfirmationRequired", "stdin is not a TTY and --yes was not passed")}\n`);
47
+ return { exitCode: EXIT_DECLINED };
48
+ }
49
+ const confirmFn = input.confirm ?? (() => defaultConfirm(input));
50
+ const confirmed = await confirmFn();
51
+ if (!confirmed) {
52
+ input.stderr.write("rewind declined by user\n");
53
+ return { exitCode: EXIT_DECLINED };
54
+ }
55
+ }
56
+ try {
57
+ const result = await jumpToFrameRoute({
58
+ adapter: input.adapter,
59
+ runId: input.runId,
60
+ frameNo: input.frameNo,
61
+ confirm: true,
62
+ caller: "cli",
63
+ });
64
+ input.onResult?.(result);
65
+ if (input.json) {
66
+ input.stdout.write(`${JSON.stringify(result)}\n`);
67
+ } else {
68
+ input.stdout.write(
69
+ `rewound run ${input.runId} to frame ${result.newFrameNo} ` +
70
+ `(reverted ${result.revertedSandboxes} sandbox${result.revertedSandboxes === 1 ? "" : "es"}, ` +
71
+ `deleted ${result.deletedFrames} frame${result.deletedFrames === 1 ? "" : "s"}, ` +
72
+ `${result.deletedAttempts} attempt${result.deletedAttempts === 1 ? "" : "s"}, ` +
73
+ `invalidated ${result.invalidatedDiffs} diff${result.invalidatedDiffs === 1 ? "" : "s"}, ` +
74
+ `took ${result.durationMs}ms)\n`,
75
+ );
76
+ }
77
+ return { exitCode: EXIT_OK };
78
+ } catch (err) {
79
+ const code = err instanceof JumpToFrameError ? err.code : undefined;
80
+ const message = err instanceof Error ? err.message : String(err);
81
+ input.stderr.write(`${formatCliErrorForStderr(code, message)}\n`);
82
+ if (code === "ConfirmationRequired") {
83
+ return { exitCode: EXIT_USER_ERROR };
84
+ }
85
+ const mapping = getCliErrorMapping(code, message);
86
+ return { exitCode: mapping.exitCode };
87
+ }
88
+ }
@@ -0,0 +1,112 @@
1
+ import { spawn } from "node:child_process";
2
+ import { CronExpressionParser } from "cron-parser";
3
+ import { Effect, Schedule } from "effect";
4
+ import { toSmithersError } from "@smithers-orchestrator/errors/toSmithersError";
5
+ import { runPromise } from "./smithersRuntime.js";
6
+ import { findAndOpenDb } from "./find-db.js";
7
+ /**
8
+ * @param {unknown} error
9
+ */
10
+ function formatError(error) {
11
+ return error instanceof Error ? error.message : String(error);
12
+ }
13
+ function acquireSchedulerDbEffect() {
14
+ return Effect.acquireRelease(Effect.tryPromise({
15
+ try: () => findAndOpenDb(),
16
+ catch: (cause) => toSmithersError(cause, "find and open scheduler db"),
17
+ }), ({ cleanup }) => Effect.sync(() => cleanup()));
18
+ }
19
+ /**
20
+ * @param {SmithersDb} adapter
21
+ * @param {SchedulerCronRecord} job
22
+ * @param {number} now
23
+ * @returns {Effect.Effect<void, never>}
24
+ */
25
+ function processCronEffect(adapter, job, now) {
26
+ return Effect.gen(function* () {
27
+ yield* Effect.logInfo(`[smithers-cron] Triggering due workflow: ${job.workflowPath} (Schedule: ${job.pattern})`);
28
+ yield* Effect.try({
29
+ try: () => {
30
+ const proc = spawn("bun", ["run", "src/index.js", "up", job.workflowPath, "-d"], {
31
+ cwd: process.cwd(),
32
+ detached: true,
33
+ stdio: "ignore",
34
+ });
35
+ proc.unref();
36
+ },
37
+ catch: (cause) => toSmithersError(cause, `spawn cron workflow ${job.cronId}`),
38
+ });
39
+ const nextRunAtMs = yield* Effect.try({
40
+ try: () => {
41
+ const interval = CronExpressionParser.parse(job.pattern);
42
+ return interval.next().getTime();
43
+ },
44
+ catch: (cause) => toSmithersError(cause, `calculate next run for cron ${job.cronId}`),
45
+ });
46
+ yield* adapter.updateCronRunTimeEffect(job.cronId, now, nextRunAtMs);
47
+ }).pipe(Effect.catchAll((error) => Effect.gen(function* () {
48
+ const errorMessage = formatError(error);
49
+ yield* Effect.logWarning(`[smithers-cron] Error processing job ${job.cronId}: ${errorMessage}`);
50
+ const failedAtMs = Date.now();
51
+ yield* adapter
52
+ .updateCronRunTimeEffect(job.cronId, failedAtMs, job.nextRunAtMs ?? failedAtMs + 60_000, errorMessage)
53
+ .pipe(Effect.catchAll((updateError) => Effect.logWarning(`[smithers-cron] Failed to record error for job ${job.cronId}: ${formatError(updateError)}`)));
54
+ })));
55
+ }
56
+ /**
57
+ * @param {SmithersDb} adapter
58
+ * @returns {Effect.Effect<void, never>}
59
+ */
60
+ function schedulerTickEffect(adapter) {
61
+ return Effect.withLogSpan("scheduler:poll")(Effect.gen(function* () {
62
+ const crons = yield* adapter.listCronsEffect(true).pipe(Effect.catchAll((error) => Effect.logWarning(`[smithers-cron] Tick failed: ${formatError(error)}`).pipe(Effect.as([]))));
63
+ const now = Date.now();
64
+ for (const job of crons) {
65
+ if (typeof job.nextRunAtMs === "number" && now < job.nextRunAtMs) {
66
+ continue;
67
+ }
68
+ yield* processCronEffect(adapter, job, now);
69
+ }
70
+ }));
71
+ }
72
+ /**
73
+ * @param {number} pollIntervalMs
74
+ */
75
+ function schedulerLoopEffect(pollIntervalMs) {
76
+ return Effect.scoped(Effect.gen(function* () {
77
+ const { adapter } = yield* acquireSchedulerDbEffect();
78
+ yield* Effect.logInfo("[smithers-cron] Starting background scheduler loop...");
79
+ yield* Effect.logInfo(`[smithers-cron] Polling every ${pollIntervalMs / 1000}s for due jobs.`);
80
+ yield* schedulerTickEffect(adapter).pipe(Effect.repeat(Schedule.spaced(`${pollIntervalMs} millis`)));
81
+ }).pipe(Effect.annotateLogs({ component: "scheduler" }), Effect.ensuring(Effect.logInfo("[smithers-cron] Scheduler stopped.")), Effect.interruptible, Effect.asVoid));
82
+ }
83
+ function setupAbortSignal() {
84
+ const abort = new AbortController();
85
+ const onSigInt = () => abort.abort();
86
+ const onSigTerm = () => abort.abort();
87
+ process.once("SIGINT", onSigInt);
88
+ process.once("SIGTERM", onSigTerm);
89
+ return {
90
+ signal: abort.signal,
91
+ dispose() {
92
+ process.off("SIGINT", onSigInt);
93
+ process.off("SIGTERM", onSigTerm);
94
+ },
95
+ };
96
+ }
97
+ export async function runScheduler(pollIntervalMs = 15_000) {
98
+ const abort = setupAbortSignal();
99
+ try {
100
+ await runPromise(schedulerLoopEffect(pollIntervalMs), {
101
+ signal: abort.signal,
102
+ });
103
+ }
104
+ catch (error) {
105
+ abort.dispose();
106
+ if (abort.signal.aborted) {
107
+ process.exit(0);
108
+ }
109
+ throw error;
110
+ }
111
+ abort.dispose();
112
+ }
@@ -0,0 +1,63 @@
1
+ import * as WorkflowEngine from "@effect/workflow/WorkflowEngine";
2
+ import { Cause, Effect, Exit, Layer, ManagedRuntime } from "effect";
3
+ import { SchedulerLive, WorkflowSessionLive } from "@smithers-orchestrator/scheduler";
4
+ import { CorrelationContextLive, MetricsServiceLive, TracingServiceLive, createSmithersRuntimeLayer, getCurrentSmithersTraceAnnotations, getCurrentSmithersTraceSpan, } from "@smithers-orchestrator/observability";
5
+ import { toSmithersError } from "@smithers-orchestrator/errors/toSmithersError";
6
+ const ObservabilityLayer = Layer.mergeAll(CorrelationContextLive, MetricsServiceLive, TracingServiceLive);
7
+ const SmithersCoreLayer = Layer.mergeAll(ObservabilityLayer, SchedulerLive.pipe(Layer.provide(ObservabilityLayer)), WorkflowSessionLive);
8
+ const SmithersWorkflowEngineLayer = Layer.suspend(() => WorkflowEngine.layerMemory);
9
+ const SmithersRuntimeLayer = Layer.mergeAll(SmithersCoreLayer, SmithersWorkflowEngineLayer, createSmithersRuntimeLayer()).pipe(Layer.orDie);
10
+ const runtime = ManagedRuntime.make(SmithersRuntimeLayer);
11
+ /**
12
+ * @template A, E, R
13
+ * @param {Effect.Effect<A, E, R>} effect
14
+ */
15
+ function decorate(effect) {
16
+ let program = effect.pipe(Effect.annotateLogs("service", "smithers"), Effect.withTracerEnabled(true));
17
+ const traceAnnotations = getCurrentSmithersTraceAnnotations();
18
+ if (traceAnnotations) {
19
+ program = program.pipe(Effect.annotateLogs(traceAnnotations));
20
+ }
21
+ const parentSpan = getCurrentSmithersTraceSpan();
22
+ if (parentSpan) {
23
+ program = program.pipe(Effect.withParentSpan(parentSpan));
24
+ }
25
+ return program;
26
+ }
27
+ /**
28
+ * @param {unknown} cause
29
+ * @returns {SmithersError}
30
+ */
31
+ function normalizeRejection(cause) {
32
+ return toSmithersError(cause);
33
+ }
34
+ /**
35
+ * @template A, E, R
36
+ * @param {Effect.Effect<A, E, R>} effect
37
+ * @param {{ signal?: AbortSignal }} [options]
38
+ */
39
+ export async function runPromise(effect, options) {
40
+ const exit = await runtime.runPromiseExit(decorate(effect), options);
41
+ if (Exit.isSuccess(exit)) {
42
+ return exit.value;
43
+ }
44
+ const failure = Cause.failureOption(exit.cause);
45
+ if (failure._tag === "Some") {
46
+ throw normalizeRejection(failure.value);
47
+ }
48
+ throw normalizeRejection(Cause.squash(exit.cause));
49
+ }
50
+ /**
51
+ * @template A, E, R
52
+ * @param {Effect.Effect<A, E, R>} effect
53
+ */
54
+ export function runFork(effect) {
55
+ return runtime.runFork(decorate(effect));
56
+ }
57
+ /**
58
+ * @template A, E, R
59
+ * @param {Effect.Effect<A, E, R>} effect
60
+ */
61
+ export function runSync(effect) {
62
+ return runtime.runSync(decorate(effect));
63
+ }