@bastani/atomic 0.9.15 → 0.9.16-alpha.1

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 (82) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +10 -0
  3. package/dist/builtin/intercom/README.md +8 -8
  4. package/dist/builtin/intercom/contact-supervisor-tool.ts +68 -32
  5. package/dist/builtin/intercom/index-heavy.ts +1 -0
  6. package/dist/builtin/intercom/index.ts +21 -15
  7. package/dist/builtin/intercom/intercom-tool.ts +66 -10
  8. package/dist/builtin/intercom/package.json +1 -1
  9. package/dist/builtin/intercom/parent-ask-handoff.ts +72 -0
  10. package/dist/builtin/mcp/package.json +1 -1
  11. package/dist/builtin/subagents/CHANGELOG.md +13 -0
  12. package/dist/builtin/subagents/README.md +23 -12
  13. package/dist/builtin/subagents/agents/worker.md +2 -2
  14. package/dist/builtin/subagents/package.json +1 -1
  15. package/dist/builtin/subagents/skills/subagent/SKILL.md +10 -17
  16. package/dist/builtin/subagents/src/extension/index.ts +5 -30
  17. package/dist/builtin/subagents/src/extension/notification-content.ts +1 -1
  18. package/dist/builtin/subagents/src/extension/schemas.ts +2 -14
  19. package/dist/builtin/subagents/src/extension/tool-description.ts +1 -2
  20. package/dist/builtin/subagents/src/extension/tool-rendering.ts +53 -0
  21. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +1 -1
  22. package/dist/builtin/subagents/src/intercom/result-intercom.ts +4 -4
  23. package/dist/builtin/subagents/src/runs/foreground/execution-parent-ask-handoff.ts +78 -0
  24. package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +7 -5
  25. package/dist/builtin/subagents/src/runs/foreground/notify.ts +5 -6
  26. package/dist/builtin/subagents/src/runs/foreground/parent-ask-output.ts +48 -0
  27. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst-display.ts +46 -0
  28. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst.ts +403 -0
  29. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +48 -9
  30. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-input.ts +3 -1
  31. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-live-update.ts +11 -0
  32. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +164 -93
  33. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +36 -29
  34. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parent-ask-projection.ts +11 -0
  35. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +42 -17
  36. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +3 -121
  37. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +7 -2
  38. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +30 -220
  39. package/dist/builtin/subagents/src/runs/inprocess/control-status.ts +0 -34
  40. package/dist/builtin/subagents/src/runs/inprocess/index.ts +0 -12
  41. package/dist/builtin/subagents/src/runs/inprocess/prompt-behavior.ts +1 -1
  42. package/dist/builtin/subagents/src/runs/inprocess/runner.ts +2 -141
  43. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +8 -0
  44. package/dist/builtin/subagents/src/shared/status-format.ts +3 -3
  45. package/dist/builtin/subagents/src/shared/types-config.ts +41 -0
  46. package/dist/builtin/subagents/src/shared/types-foreground-state.ts +7 -15
  47. package/dist/builtin/subagents/src/shared/types-results.ts +3 -1
  48. package/dist/builtin/subagents/src/shared/types-runtime.ts +1 -11
  49. package/dist/builtin/subagents/src/tui/render-progress.ts +2 -2
  50. package/dist/builtin/subagents/src/tui/render-result-compact.ts +2 -2
  51. package/dist/builtin/subagents/src/tui/render-result.ts +38 -14
  52. package/dist/builtin/subagents/src/tui/render-status-progress.ts +1 -1
  53. package/dist/builtin/web-access/package.json +1 -1
  54. package/dist/builtin/workflows/CHANGELOG.md +10 -0
  55. package/dist/builtin/workflows/README.md +1 -1
  56. package/dist/builtin/workflows/package.json +1 -1
  57. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres-root.ts +548 -39
  58. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres.ts +529 -65
  59. package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +30 -8
  60. package/dist/builtin/workflows/src/durable/dbos-local-postgres.ts +36 -3
  61. package/dist/builtin/workflows/src/durable/local-command.ts +68 -10
  62. package/dist/builtin/workflows/src/extension/dispatcher.ts +7 -1
  63. package/dist/builtin/workflows/src/extension/extension-factory.ts +2 -2
  64. package/dist/builtin/workflows/src/extension/index.bundle.mjs +1094 -298
  65. package/dist/builtin/workflows/src/extension/public-types.ts +2 -2
  66. package/dist/builtin/workflows/src/extension/render-result.ts +16 -2
  67. package/dist/builtin/workflows/src/extension/runtime.ts +8 -1
  68. package/dist/builtin/workflows/src/extension/workflow-request-abort.ts +15 -0
  69. package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +4 -3
  70. package/dist/builtin/workflows/src/extension/workflow-tool-registration.ts +107 -13
  71. package/dist/builtin/workflows/src/extension/workflow-tool.ts +22 -12
  72. package/dist/builtin/workflows/src/tui/widget.ts +25 -6
  73. package/dist/core/event-bus.d.ts.map +1 -1
  74. package/dist/core/event-bus.js +19 -3
  75. package/dist/core/event-bus.js.map +1 -1
  76. package/docs/intercom.md +28 -26
  77. package/docs/subagents.md +15 -11
  78. package/docs/workflows.md +27 -10
  79. package/npm-shrinkwrap.json +32 -32
  80. package/package.json +3 -3
  81. package/dist/builtin/subagents/src/runs/inprocess/attempt-handles.ts +0 -104
  82. /package/dist/builtin/subagents/src/runs/foreground/{subagent-executor-resume.ts → subagent-executor-cwd.ts} +0 -0
@@ -3,6 +3,7 @@ import {
3
3
  provisionResolvedLocalDbos,
4
4
  resolveDbosSystemDatabaseUrl,
5
5
  shouldProvisionLocalDbos,
6
+ shutdownResolvedLocalDbos,
6
7
  } from "./dbos-local-postgres.js";
7
8
  import { getDbosProcessOwner, resetDbosProcessOwner } from "./dbos-process-owner.js";
8
9
  import { classifyDbosDurabilityFailure, readDbosFailureDetail } from "./dbos-registration-diagnostics.js";
@@ -41,6 +42,7 @@ export class DbosShutdownError extends DbosDurabilityError {
41
42
  }
42
43
  type DbosConfigurator = () => Promise<ConfiguredDbosDurability>;
43
44
  type LocalDbosProvisioner = () => Promise<void>;
45
+ type LocalDbosShutdowner = () => Promise<void>;
44
46
 
45
47
  /**
46
48
  * Default path: resolve the local database first (explicit env URL, embedded
@@ -53,6 +55,7 @@ const defaultConfigurator: DbosConfigurator = async () => {
53
55
 
54
56
  let configureDurability: DbosConfigurator = defaultConfigurator;
55
57
  let provisionLocalDbos: LocalDbosProvisioner = provisionResolvedLocalDbos;
58
+ let shutdownLocalDbos: LocalDbosShutdowner = shutdownResolvedLocalDbos;
56
59
 
57
60
  function owner(): ReturnType<typeof getDbosProcessOwner> {
58
61
  return getDbosProcessOwner();
@@ -69,6 +72,11 @@ async function durabilityFailure(action: string, error: unknown): Promise<DbosDu
69
72
  return new DbosDurabilityError(`DBOS workflow durability ${action} failed: ${detail}. ${guidance}`, cause);
70
73
  }
71
74
 
75
+ function combinedShutdownFailure(errors: readonly unknown[]): unknown {
76
+ if (errors.length === 1) return errors[0];
77
+ return new AggregateError(errors, errors.map((error) => readDbosFailureDetail(error)).join("; "));
78
+ }
79
+
72
80
  export async function configureDbosOnce(): Promise<ConfiguredDbosDurability> {
73
81
  const slot = owner();
74
82
  if (slot.failure !== undefined) throw slot.failure;
@@ -140,17 +148,29 @@ export async function shutdownDbos(): Promise<void> {
140
148
  const configuredPromise = slot.configured;
141
149
  if (configuredPromise === undefined) return;
142
150
  slot.shutdownPromise = (async () => {
143
- // A backend that never reached "ready" has nothing to flush or stop.
144
- // `configured`/`launchPromise` memoize rejections, so re-awaiting them
145
- // unguarded would rethrow the original provisioning failure out of every
146
- // session dispose — crashing otherwise-successful runs at process exit.
151
+ // Configuration and launch failures are intentionally not rethrown during
152
+ // session disposal, but local provisioning may already have started an
153
+ // owned cluster. Always attempt that cleanup exactly once.
147
154
  const durability = await configuredPromise.catch(() => undefined);
148
- if (durability === undefined) return;
155
+ if (durability === undefined) {
156
+ await shutdownLocalDbos();
157
+ return;
158
+ }
149
159
  if (slot.launchPromise !== undefined) await slot.launchPromise.catch(() => undefined);
150
- if (slot.state !== "ready") return;
160
+ if (slot.state !== "ready") {
161
+ await shutdownLocalDbos();
162
+ return;
163
+ }
151
164
  slot.state = "shutting_down";
152
- await durability.backend.flush();
153
- await durability.shutdown();
165
+ const errors: unknown[] = [];
166
+ for (const shutdown of [() => durability.backend.flush(), () => durability.shutdown(), shutdownLocalDbos]) {
167
+ try {
168
+ await shutdown();
169
+ } catch (error) {
170
+ errors.push(error);
171
+ }
172
+ }
173
+ if (errors.length > 0) throw combinedShutdownFailure(errors);
154
174
  slot.state = "shut_down";
155
175
  })().catch(async (error: unknown) => {
156
176
  slot.failure = await durabilityFailure("shutdown", error);
@@ -179,8 +199,10 @@ export function dbosLifecycleState(): DbosLifecycleState {
179
199
  export function resetDbosLifecycleForTests(
180
200
  configurator: DbosConfigurator = defaultConfigurator,
181
201
  provisioner: LocalDbosProvisioner = provisionResolvedLocalDbos,
202
+ shutdowner: LocalDbosShutdowner = shutdownResolvedLocalDbos,
182
203
  ): void {
183
204
  resetDbosProcessOwner();
184
205
  configureDurability = configurator;
185
206
  provisionLocalDbos = provisioner;
207
+ shutdownLocalDbos = shutdowner;
186
208
  }
@@ -7,11 +7,16 @@
7
7
  *
8
8
  * 1. `DBOS_SYSTEM_DATABASE_URL` — the user's explicit database.
9
9
  * 2. Atomic's embedded Postgres (npm-distributed binaries, no Docker).
10
- * 3. DBOS's reusable `dbos-db` Docker container, only when the embedded
11
- * binaries are unavailable for this platform and Docker exists.
10
+ * 3. DBOS's reusable `dbos-db` Docker container, only after embedded
11
+ * provisioning fails without leaving retained-process cleanup pending.
12
12
  */
13
13
 
14
- import { EMBEDDED_DBOS_SYSTEM_DATABASE_URL, ensureEmbeddedDbosPostgres } from "./dbos-embedded-postgres.js";
14
+ import {
15
+ EMBEDDED_DBOS_SYSTEM_DATABASE_URL,
16
+ EmbeddedPostgresCleanupPendingError,
17
+ ensureEmbeddedDbosPostgres,
18
+ shutdownEmbeddedDbosPostgres,
19
+ } from "./dbos-embedded-postgres.js";
15
20
  import { commandFailureDetail, delay, runLocalCommand, tcpReachable } from "./local-command.js";
16
21
 
17
22
  const DOCKER_CONTAINER = "dbos-db";
@@ -20,11 +25,14 @@ const DOCKER_READY_ATTEMPTS = 60;
20
25
  const DOCKER_READY_DELAY_MS = 500;
21
26
 
22
27
  type LocalDbosProvider = () => Promise<void>;
28
+ type LocalDbosShutdowner = () => Promise<void>;
23
29
 
24
30
  let resolution: Promise<string | undefined> | undefined;
25
31
  let resolvedProvider: LocalDbosProvider | undefined;
26
32
  let embeddedProvider: LocalDbosProvider = ensureEmbeddedDbosPostgres;
27
33
  let dockerProvider: LocalDbosProvider = ensureDockerDbosPostgres;
34
+ let shutdownEmbeddedProvider: LocalDbosShutdowner = shutdownEmbeddedDbosPostgres;
35
+ let embeddedShutdown: Promise<void> | undefined;
28
36
 
29
37
  /**
30
38
  * Resolve the system database URL for this process and make its database
@@ -44,6 +52,22 @@ export async function provisionResolvedLocalDbos(): Promise<void> {
44
52
  await (resolvedProvider ?? embeddedProvider)();
45
53
  }
46
54
 
55
+ /** Stop the local database only when the resolved provider was embedded. */
56
+ export function shutdownResolvedLocalDbos(): Promise<void> {
57
+ if (resolvedProvider !== embeddedProvider) return Promise.resolve();
58
+ embeddedShutdown ??= shutdownEmbeddedProvider().then(
59
+ () => {
60
+ resolvedProvider = undefined;
61
+ embeddedShutdown = undefined;
62
+ },
63
+ (error: unknown) => {
64
+ embeddedShutdown = undefined;
65
+ throw error;
66
+ },
67
+ );
68
+ return embeddedShutdown;
69
+ }
70
+
47
71
  export function shouldProvisionLocalDbos(error: unknown): boolean {
48
72
  if (process.env.DBOS_SYSTEM_DATABASE_URL?.trim()) return false;
49
73
  const message = error instanceof Error ? `${error.message}\n${error.cause ?? ""}` : String(error);
@@ -61,6 +85,12 @@ async function resolve(): Promise<string | undefined> {
61
85
  resolvedProvider = embeddedProvider;
62
86
  return EMBEDDED_DBOS_SYSTEM_DATABASE_URL;
63
87
  } catch (embeddedError) {
88
+ if (embeddedError instanceof EmbeddedPostgresCleanupPendingError) {
89
+ // A second database must not hide the exact child lease whose startup
90
+ // rollback timed out. Keep teardown routed to that embedded owner.
91
+ resolvedProvider = embeddedProvider;
92
+ throw embeddedError;
93
+ }
64
94
  try {
65
95
  await dockerProvider();
66
96
  resolvedProvider = dockerProvider;
@@ -125,9 +155,12 @@ async function requireDockerSuccess(action: string, args: string[]): Promise<voi
125
155
  export function resetLocalDbosProvisioningForTests(
126
156
  embedded: LocalDbosProvider = ensureEmbeddedDbosPostgres,
127
157
  docker: LocalDbosProvider = ensureDockerDbosPostgres,
158
+ shutdownEmbedded: LocalDbosShutdowner = shutdownEmbeddedDbosPostgres,
128
159
  ): void {
129
160
  resolution = undefined;
130
161
  resolvedProvider = undefined;
162
+ embeddedShutdown = undefined;
131
163
  embeddedProvider = embedded;
132
164
  dockerProvider = docker;
165
+ shutdownEmbeddedProvider = shutdownEmbedded;
133
166
  }
@@ -8,12 +8,22 @@ export interface LocalCommandResult {
8
8
  readonly exitCode: number;
9
9
  readonly stdout: string;
10
10
  readonly stderr: string;
11
+ /** True when bounded capture discarded the beginning of stdout. */
12
+ readonly stdoutTruncated?: true;
13
+ /** True when bounded capture discarded the beginning of stderr. */
14
+ readonly stderrTruncated?: true;
11
15
  }
12
16
 
13
17
  const OUTPUT_LIMIT_BYTES = 16_384;
14
18
 
15
19
  export interface LocalCommandOptions {
16
20
  readonly env?: Readonly<Record<string, string>>;
21
+ /**
22
+ * For daemon launchers whose successful descendants inherit stdio, settle
23
+ * from a successful direct-child exit. Nonzero exits still drain through
24
+ * `close`, preserving bounded failure diagnostics.
25
+ */
26
+ readonly completion?: "successful-exit";
17
27
  /** POSIX drop-privilege identity for the spawned process (root only). */
18
28
  readonly uid?: number;
19
29
  readonly gid?: number;
@@ -34,16 +44,62 @@ export function runLocalCommand(
34
44
  });
35
45
  let stdout = "";
36
46
  let stderr = "";
47
+ let stdoutTruncated = false;
48
+ let stderrTruncated = false;
49
+ let settled = false;
50
+ let exitFallback: NodeJS.Immediate | undefined;
37
51
  child.stdout.setEncoding("utf8");
38
52
  child.stderr.setEncoding("utf8");
39
- child.stdout.on("data", (chunk: string) => {
40
- stdout = boundedAppend(stdout, chunk);
41
- });
42
- child.stderr.on("data", (chunk: string) => {
43
- stderr = boundedAppend(stderr, chunk);
44
- });
45
- child.once("error", reject);
46
- child.once("close", (code) => resolve({ exitCode: code ?? 1, stdout, stderr }));
53
+ const onStdout = (chunk: string): void => {
54
+ const appended = boundedAppend(stdout, chunk);
55
+ stdout = appended.value;
56
+ stdoutTruncated ||= appended.truncated;
57
+ };
58
+ const onStderr = (chunk: string): void => {
59
+ const appended = boundedAppend(stderr, chunk);
60
+ stderr = appended.value;
61
+ stderrTruncated ||= appended.truncated;
62
+ };
63
+ const cleanup = (): void => {
64
+ if (exitFallback !== undefined) clearImmediate(exitFallback);
65
+ child.off("error", onError);
66
+ child.off("exit", onExit);
67
+ child.off("close", onClose);
68
+ child.stdout.off("data", onStdout);
69
+ child.stderr.off("data", onStderr);
70
+ child.stdout.destroy();
71
+ child.stderr.destroy();
72
+ };
73
+ const finish = (code: number | null): void => {
74
+ if (settled) return;
75
+ settled = true;
76
+ cleanup();
77
+ resolve({
78
+ exitCode: code ?? 1,
79
+ stdout,
80
+ stderr,
81
+ ...(stdoutTruncated ? { stdoutTruncated: true as const } : {}),
82
+ ...(stderrTruncated ? { stderrTruncated: true as const } : {}),
83
+ });
84
+ };
85
+ const onError = (error: Error): void => {
86
+ if (settled) return;
87
+ settled = true;
88
+ cleanup();
89
+ reject(error);
90
+ };
91
+ const onExit = (code: number | null): void => {
92
+ if (code !== 0) return;
93
+ // pg_ctl redirects server output with `-l`; only its successful child
94
+ // exit needs early settlement because Postgres can retain inherited EOF.
95
+ exitFallback = setImmediate(() => finish(code));
96
+ };
97
+ const onClose = (code: number | null): void => finish(code);
98
+ child.stdout.on("data", onStdout);
99
+ child.stderr.on("data", onStderr);
100
+ child.once("error", onError);
101
+ if (options?.completion === "successful-exit") child.once("exit", onExit);
102
+ child.once("close", onClose);
47
103
  });
48
104
  }
49
105
 
@@ -71,7 +127,9 @@ export function tcpReachable(host: string, port: number, timeoutMs = 1_000): Pro
71
127
  });
72
128
  }
73
129
 
74
- function boundedAppend(current: string, chunk: string): string {
130
+ function boundedAppend(current: string, chunk: string): { readonly value: string; readonly truncated: boolean } {
75
131
  const next = current + chunk;
76
- return next.length <= OUTPUT_LIMIT_BYTES ? next : next.slice(-OUTPUT_LIMIT_BYTES);
132
+ return next.length <= OUTPUT_LIMIT_BYTES
133
+ ? { value: next, truncated: false }
134
+ : { value: next.slice(-OUTPUT_LIMIT_BYTES), truncated: true };
77
135
  }
@@ -29,6 +29,7 @@ import {
29
29
  import type { WorkflowRegistry } from "../workflows/registry.js";
30
30
  import type { WorkflowToolArgs } from "./index.js";
31
31
  import type { WorkflowInputEntry, WorkflowToolResult } from "./render-result.js";
32
+ import { raceWorkflowRequestAbort } from "./workflow-request-abort.js";
32
33
 
33
34
  type WorkflowRunResult = Extract<WorkflowToolResult, { action: "run" }>;
34
35
 
@@ -90,6 +91,10 @@ export interface DispatcherOpts {
90
91
  cwd?: string;
91
92
  /** Host-resolved non-default session directory inherited by stages without explicit sessionDir. */
92
93
  defaultSessionDir?: string;
94
+ /** Cancels only startup admission waiting; the detached run keeps its own lifecycle. */
95
+ signal?: AbortSignal;
96
+ /** Reports the exact detached identity before startup admission is awaited. */
97
+ onRunAccepted?: (runId: string) => void;
93
98
  }
94
99
 
95
100
  // ---------------------------------------------------------------------------
@@ -198,7 +203,8 @@ export async function dispatch(args: WorkflowToolArgs, opts: DispatcherOpts): Pr
198
203
  return failedRunResult(def.name, runId, error instanceof Error ? error.message : String(error));
199
204
  }
200
205
  const { accepted } = launch;
201
- const admission = await launch.wait;
206
+ opts.onRunAccepted?.(accepted.runId);
207
+ const admission = await raceWorkflowRequestAbort(launch.wait, opts.signal);
202
208
  if (!admission.started) {
203
209
  const activeStore = opts.store ?? defaultStore;
204
210
  const snapshot = activeStore.runs().find((run) => run.id === accepted.runId);
@@ -89,8 +89,8 @@ function factory(pi: ExtensionAPI): void {
89
89
  runtimeState.ensureWorkflowResourcesLoaded,
90
90
  { resolvePostMortemDeps: (runId) => postMortemDepsForRun(runId, postMortemResolverDeps) },
91
91
  );
92
- const executeWorkflowToolWithAutoAttach: typeof executeWorkflowTool = async (args, ctx) => {
93
- const result = await executeWorkflowTool(args, ctx);
92
+ const executeWorkflowToolWithAutoAttach: typeof executeWorkflowTool = async (args, ctx, signal, onRunAccepted) => {
93
+ const result = await executeWorkflowTool(args, ctx, signal, onRunAccepted);
94
94
  if (
95
95
  workflowPolicyFromContext(ctx).mode === "interactive" &&
96
96
  typeof args.workflow === "string" &&