@sensigo/realm-cli 0.39.0 → 0.41.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 (109) hide show
  1. package/dist/agent/drive-failure.d.ts +62 -0
  2. package/dist/agent/drive-failure.d.ts.map +1 -0
  3. package/dist/agent/drive-failure.js +241 -0
  4. package/dist/agent/drive-failure.js.map +1 -0
  5. package/dist/agent/providers/agent-utils.d.ts +156 -0
  6. package/dist/agent/providers/agent-utils.d.ts.map +1 -1
  7. package/dist/agent/providers/agent-utils.js +423 -1
  8. package/dist/agent/providers/agent-utils.js.map +1 -1
  9. package/dist/agent/providers/anthropic-provider.d.ts +51 -1
  10. package/dist/agent/providers/anthropic-provider.d.ts.map +1 -1
  11. package/dist/agent/providers/anthropic-provider.js +109 -30
  12. package/dist/agent/providers/anthropic-provider.js.map +1 -1
  13. package/dist/agent/providers/llm-provider.d.ts +13 -1
  14. package/dist/agent/providers/llm-provider.d.ts.map +1 -1
  15. package/dist/agent/providers/llm-provider.js +8 -4
  16. package/dist/agent/providers/llm-provider.js.map +1 -1
  17. package/dist/agent/providers/openai-provider.d.ts +6 -1
  18. package/dist/agent/providers/openai-provider.d.ts.map +1 -1
  19. package/dist/agent/providers/openai-provider.js +75 -20
  20. package/dist/agent/providers/openai-provider.js.map +1 -1
  21. package/dist/agent/providers/openai-reasoning-provider.d.ts +4 -1
  22. package/dist/agent/providers/openai-reasoning-provider.d.ts.map +1 -1
  23. package/dist/agent/providers/openai-reasoning-provider.js +21 -6
  24. package/dist/agent/providers/openai-reasoning-provider.js.map +1 -1
  25. package/dist/agent/run-agent.d.ts +9 -0
  26. package/dist/agent/run-agent.d.ts.map +1 -1
  27. package/dist/agent/run-agent.js +775 -680
  28. package/dist/agent/run-agent.js.map +1 -1
  29. package/dist/agent/run-attach.d.ts.map +1 -1
  30. package/dist/agent/run-attach.js +26 -2
  31. package/dist/agent/run-attach.js.map +1 -1
  32. package/dist/agent/test-support/anthropic-stub.d.ts +45 -0
  33. package/dist/agent/test-support/anthropic-stub.d.ts.map +1 -0
  34. package/dist/agent/test-support/anthropic-stub.js +146 -0
  35. package/dist/agent/test-support/anthropic-stub.js.map +1 -0
  36. package/dist/agent/test-support/composed-journey.d.ts +40 -0
  37. package/dist/agent/test-support/composed-journey.d.ts.map +1 -0
  38. package/dist/agent/test-support/composed-journey.js +104 -0
  39. package/dist/agent/test-support/composed-journey.js.map +1 -0
  40. package/dist/agent/test-support/mcp-server-entry.d.ts +20 -0
  41. package/dist/agent/test-support/mcp-server-entry.d.ts.map +1 -0
  42. package/dist/agent/test-support/mcp-server-entry.js +39 -0
  43. package/dist/agent/test-support/mcp-server-entry.js.map +1 -0
  44. package/dist/agent/test-support/openai-stub.d.ts +47 -0
  45. package/dist/agent/test-support/openai-stub.d.ts.map +1 -0
  46. package/dist/agent/test-support/openai-stub.js +115 -0
  47. package/dist/agent/test-support/openai-stub.js.map +1 -0
  48. package/dist/commands/agent.d.ts +6 -0
  49. package/dist/commands/agent.d.ts.map +1 -1
  50. package/dist/commands/agent.js +44 -3
  51. package/dist/commands/agent.js.map +1 -1
  52. package/dist/commands/drain.d.ts.map +1 -1
  53. package/dist/commands/drain.js +37 -3
  54. package/dist/commands/drain.js.map +1 -1
  55. package/dist/commands/gc.d.ts.map +1 -1
  56. package/dist/commands/gc.js.map +1 -1
  57. package/dist/commands/inspect.d.ts.map +1 -1
  58. package/dist/commands/inspect.js +61 -2
  59. package/dist/commands/inspect.js.map +1 -1
  60. package/dist/commands/list.d.ts.map +1 -1
  61. package/dist/commands/list.js +86 -0
  62. package/dist/commands/list.js.map +1 -1
  63. package/dist/commands/listen.d.ts +13 -0
  64. package/dist/commands/listen.d.ts.map +1 -1
  65. package/dist/commands/listen.js +36 -4
  66. package/dist/commands/listen.js.map +1 -1
  67. package/dist/commands/purge.d.ts +22 -1
  68. package/dist/commands/purge.d.ts.map +1 -1
  69. package/dist/commands/purge.js +70 -42
  70. package/dist/commands/purge.js.map +1 -1
  71. package/dist/commands/register.d.ts +25 -0
  72. package/dist/commands/register.d.ts.map +1 -1
  73. package/dist/commands/register.js +91 -21
  74. package/dist/commands/register.js.map +1 -1
  75. package/dist/commands/respond.d.ts.map +1 -1
  76. package/dist/commands/respond.js +23 -1
  77. package/dist/commands/respond.js.map +1 -1
  78. package/dist/commands/run.d.ts +34 -0
  79. package/dist/commands/run.d.ts.map +1 -1
  80. package/dist/commands/run.js +225 -11
  81. package/dist/commands/run.js.map +1 -1
  82. package/dist/commands/test.d.ts.map +1 -1
  83. package/dist/commands/test.js +48 -8
  84. package/dist/commands/test.js.map +1 -1
  85. package/dist/commands/validate.d.ts +19 -0
  86. package/dist/commands/validate.d.ts.map +1 -1
  87. package/dist/commands/validate.js +358 -84
  88. package/dist/commands/validate.js.map +1 -1
  89. package/dist/commands/watch.d.ts +30 -0
  90. package/dist/commands/watch.d.ts.map +1 -1
  91. package/dist/commands/watch.js +159 -23
  92. package/dist/commands/watch.js.map +1 -1
  93. package/dist/commands/workflow-list.d.ts +16 -0
  94. package/dist/commands/workflow-list.d.ts.map +1 -0
  95. package/dist/commands/workflow-list.js +96 -0
  96. package/dist/commands/workflow-list.js.map +1 -0
  97. package/dist/commands-registry.d.ts.map +1 -1
  98. package/dist/commands-registry.js +2 -0
  99. package/dist/commands-registry.js.map +1 -1
  100. package/dist/extensions/load-project-extensions.d.ts.map +1 -1
  101. package/dist/extensions/load-project-extensions.js +9 -3
  102. package/dist/extensions/load-project-extensions.js.map +1 -1
  103. package/dist/index.js +22 -1
  104. package/dist/index.js.map +1 -1
  105. package/dist/lib/loader-warnings.d.ts +45 -3
  106. package/dist/lib/loader-warnings.d.ts.map +1 -1
  107. package/dist/lib/loader-warnings.js +106 -5
  108. package/dist/lib/loader-warnings.js.map +1 -1
  109. package/package.json +5 -4
@@ -4,18 +4,40 @@
4
4
  // against the resolved adapters' config_schema (two-pass) BEFORE anything is persisted.
5
5
  import { Command } from 'commander';
6
6
  import { join } from 'node:path';
7
- import { loadWorkflowFromFileWithDiagnostics, JsonWorkflowStore, resolveSeverity, } from '@sensigo/realm';
7
+ import { loadWorkflowFromFileWithDiagnostics, renderLoaderWarning, JsonWorkflowStore, WorkflowError, } from '@sensigo/realm';
8
8
  import { loadProjectExtensions, } from '../extensions/load-project-extensions.js';
9
9
  import { ManifestSecretsError } from '../extensions/manifest-secrets.js';
10
- import { printLoaderWarnings, rejectOnErrorSeverity, failsStrict } from '../lib/loader-warnings.js';
11
- /** Wraps loadProjectExtensions' sentinel-credential warnings as LoaderWarning (issue #169). */
12
- function wrapSentinelWarnings(sentinelWarnings) {
13
- return (sentinelWarnings ?? []).map((message) => ({
14
- code: 'EXTENSION_SENTINEL',
15
- severity: resolveSeverity('EXTENSION_SENTINEL'),
16
- scope: 'workflow',
17
- message: `⚠ ${message}`,
18
- }));
10
+ import { printLoaderWarnings, rejectOnErrorSeverity, failsStrict, renderLoadFailure, renderEscalationLine, wrapSentinelWarnings, } from '../lib/loader-warnings.js';
11
+ /**
12
+ * Tags a failure thrown by the extensions load inside `loadWorkflowForRegistration` so the
13
+ * callers' catches can say `Error loading extensions:` — the sentence run and validate already
14
+ * print for this class (issue #451) — without guessing from the message. The message is the
15
+ * original's, byte for byte (register-extensions.test.ts's broken-module cell matches on it
16
+ * THROUGH this wrapper), and the original rides along as `cause`.
17
+ *
18
+ * Minted at exactly the two throw paths out of the extensions block: the real-mode load's
19
+ * non-secrets failure, and the sentinel retry. The retry can never throw ManifestSecretsError
20
+ * itself (sentinel mode resolves every name without reading a source), so nothing is wrapped
21
+ * twice. A WorkflowError from the two-pass re-validation is thrown OUTSIDE the block and keeps
22
+ * the family split.
23
+ *
24
+ * Carries the workflow's own pass-1 loader warnings (issue #463 — the #424 carry shape on this
25
+ * vehicle) so the catch that renders the sentence can print them FIRST, instead of the author
26
+ * fixing the module, re-running, and only then learning about the typo. ABSENT, never `[]`: a
27
+ * construction that passes no warnings, or an empty list, leaves the field unset.
28
+ *
29
+ * @internal Exported for watch.ts and for tests.
30
+ */
31
+ export class ExtensionLoadError extends Error {
32
+ warnings;
33
+ constructor(original, warnings) {
34
+ super(original instanceof Error ? original.message : String(original), { cause: original });
35
+ this.name = 'ExtensionLoadError';
36
+ // The if-guard form on purpose: a ternary to `undefined` is a TS2412 under the repo's
37
+ // exactOptionalPropertyTypes (lane-executed, #463).
38
+ if (warnings !== undefined && warnings.length > 0)
39
+ this.warnings = [...warnings];
40
+ }
19
41
  }
20
42
  /**
21
43
  * Loads and validates a workflow for registration. Extension-declaring workflows get the
@@ -25,6 +47,7 @@ function wrapSentinelWarnings(sentinelWarnings) {
25
47
  * both callers (register's action, watch's registerFile) decide how to surface/act on warnings
26
48
  * (register supports `--strict` + the dormant #170 reject; watch just prints and continues).
27
49
  * @throws on any validation or extension-load failure — nothing is persisted on throw.
50
+ * Extension-load failures are tagged `ExtensionLoadError` (issue #451).
28
51
  */
29
52
  export async function loadWorkflowForRegistration(filePath) {
30
53
  const { definition, warnings: pass1Warnings } = loadWorkflowFromFileWithDiagnostics(filePath);
@@ -37,11 +60,20 @@ export async function loadWorkflowForRegistration(filePath) {
37
60
  loaded = await loadProjectExtensions(definition);
38
61
  }
39
62
  catch (err) {
63
+ // The guard is the degradation itself: only a secrets failure degrades, everything else is an
64
+ // extension-load failure and leaves tagged (issue #451). Dropping the conditional kills
65
+ // degradation — register-extensions.test.ts's sentinel control is the cell in this command's
66
+ // own home that sees it (the manifest E2E in extensions/ does too, one substring deep).
40
67
  if (!(err instanceof ManifestSecretsError))
41
- throw err;
42
- console.warn(`⚠ ${err.message}`);
43
- console.warn('⚠ Registering with SENTINEL credentials — execution paths still require real secret resolution.');
44
- loaded = await loadProjectExtensions(definition, { secretMode: 'sentinel' });
68
+ throw new ExtensionLoadError(err, pass1Warnings);
69
+ console.warn(`⚠ ${err.message}`);
70
+ console.warn('⚠ Registering with SENTINEL credentials — execution paths still require real secret resolution.');
71
+ try {
72
+ loaded = await loadProjectExtensions(definition, { secretMode: 'sentinel' });
73
+ }
74
+ catch (err) {
75
+ throw new ExtensionLoadError(err, pass1Warnings);
76
+ }
45
77
  }
46
78
  // Two-pass: re-validate with the resolved registry so step config is checked against
47
79
  // each adapter's config_schema before the definition is persisted. Its warnings are proven
@@ -63,16 +95,20 @@ export const registerCommand = new Command('register')
63
95
  : join(inputPath, 'workflow.yaml');
64
96
  try {
65
97
  const { definition, warnings } = await loadWorkflowForRegistration(filePath);
66
- // Dormant issue #170 boundary-reject: inert today (DEFAULT_POLICY has no 'error' entries).
98
+ // The issue #170 boundary-reject, LIVE since the flip checked before --strict, so an
99
+ // unknown key is refused with or without the flag. Store-registered definitions already in
100
+ // the registry are never re-parsed and are unaffected.
67
101
  if (rejectOnErrorSeverity(warnings)) {
68
102
  printLoaderWarnings(warnings);
69
- console.error(`Error: '${definition.id}' has a warning escalated to an error by policy refusing to register.`);
103
+ // One grammar with validate (issue #451): the line names WHICH warning escalated. The id
104
+ // the old line carried is not missed — the operator just typed the path.
105
+ console.error(renderEscalationLine(warnings));
70
106
  process.exit(1);
71
107
  return;
72
108
  }
73
109
  if (opts.strict && failsStrict(warnings)) {
74
110
  printLoaderWarnings(warnings);
75
- console.error(`Error: '${definition.id}' v${definition.version} has ${warnings.length} warning(s); refusing to register due to --strict`);
111
+ console.error(`Error: '${definition.id}' v${definition.version} has ${warnings.length} ${warnings.length === 1 ? 'warning' : 'warnings'}; refusing to register due to --strict`);
76
112
  process.exit(1);
77
113
  return;
78
114
  }
@@ -81,16 +117,50 @@ export const registerCommand = new Command('register')
81
117
  printLoaderWarnings(warnings);
82
118
  const contextWarnings = lintWorkflowContext(definition);
83
119
  for (const warning of contextWarnings) {
84
- console.warn(`⚠ ${warning}`);
120
+ console.warn(`⚠ ${warning}`);
85
121
  }
86
- console.log(`Registered: ${definition.id} v${definition.version} (${Object.keys(definition.steps).length} steps)`);
122
+ const stepCount = Object.keys(definition.steps).length;
123
+ console.log(`Registered: ${definition.id} v${definition.version} (${stepCount} ${stepCount === 1 ? 'step' : 'steps'})`);
87
124
  if (definition.description !== undefined) {
88
125
  console.log(` ${definition.description}`);
89
126
  }
90
127
  }
91
128
  catch (err) {
92
- const message = err instanceof Error ? err.message : String(err);
93
- console.error(`Error: ${message}`);
129
+ // issue #424 see the comment at validate.ts's extension-free catch. This is the ONLY
130
+ // render site for a loader failure here: the ManifestSecretsError catch above rethrows
131
+ // into this one, so rendering there too would double-print.
132
+ if (err instanceof WorkflowError && err.warnings !== undefined) {
133
+ printLoaderWarnings(err.warnings);
134
+ }
135
+ // issue #451 — an extensions-load failure gets the sentence run and validate print for it.
136
+ // First arm by placement only: an ExtensionLoadError is never a WorkflowError, so the
137
+ // order against the family split below is immaterial.
138
+ if (err instanceof ExtensionLoadError) {
139
+ // issue #463 — the workflow's own warnings first, then the sentence (the #424 catch-render
140
+ // shape). The PLAIN render, not printLoaderWarnings: that helper rewrites `— ignored` to
141
+ // `— REFUSED below` for the codes this boundary refuses, and on this path the escalation
142
+ // gate never ran — the refusal below is the extensions error, so "REFUSED below" would name
143
+ // the wrong cause. `— ignored` is the core's statement about the PARSE, true here as on
144
+ // every surface, and the composition then reads exactly as `realm run` prints it
145
+ // (test.ts's render comment — #450's reasoning, generalized).
146
+ if (err.warnings !== undefined) {
147
+ for (const w of err.warnings)
148
+ console.warn(renderLoaderWarning(w));
149
+ }
150
+ console.error(`Error loading extensions: ${err.message}`);
151
+ }
152
+ // issue #425 — THE FAMILY SPLIT. An `Invalid workflow:` message announces itself, so it
153
+ // renders verbatim through the shared helper (which also lists a multi-error throw one per
154
+ // line). Everything else this catch can see — a store failure, an unreadable path —
155
+ // announces nothing on its own, so it keeps the prefix that earns its place (#417). The
156
+ // predicate carries the colon, byte-matching the helper's own check.
157
+ else if (err instanceof WorkflowError && err.message.startsWith('Invalid workflow:')) {
158
+ console.error(renderLoadFailure(err));
159
+ }
160
+ else {
161
+ const message = err instanceof Error ? err.message : String(err);
162
+ console.error(`Error: ${message}`);
163
+ }
94
164
  process.exit(1);
95
165
  }
96
166
  });
@@ -1 +1 @@
1
- {"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/commands/register.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/commands/register.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"respond.d.ts","sourceRoot":"","sources":["../../src/commands/respond.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAIxD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,EACtF,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,iBAAiB,EAChC,QAAQ,CAAC,EAAE,iBAAiB,GAC3B,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAqC/C;AAED,eAAO,MAAM,cAAc,SA6BxB,CAAC"}
1
+ {"version":3,"file":"respond.d.ts","sourceRoot":"","sources":["../../src/commands/respond.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAIxD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,EACtF,QAAQ,EAAE,QAAQ,EAClB,aAAa,EAAE,iBAAiB,EAChC,QAAQ,CAAC,EAAE,iBAAiB,GAC3B,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAuC/C;AAED,eAAO,MAAM,cAAc,SAwDxB,CAAC"}
@@ -17,6 +17,8 @@ export async function respondToGate(runId, options, runStore, workflowStore, reg
17
17
  // which COMPLETES the run fires its finalizers with project handlers — consistent with
18
18
  // `realm run`. Same options/cwd handling as run.ts; reuses loadProjectExtensions so the
19
19
  // orphan-manifest topology guard is honoured (no hand-rolled registry).
20
+ // Production always passes `registry` now (issue #466's action hoist) — this fallback is the
21
+ // test/direct-caller seam, kept for callers that resolve their own (none in production today).
20
22
  const effectiveRegistry = registry ??
21
23
  (await loadProjectExtensions(workflow, {
22
24
  ...(options.extensionsModule !== undefined
@@ -53,7 +55,27 @@ export const respondCommand = new Command('respond')
53
55
  const runStore = new JsonFileStore();
54
56
  const workflowStore = new JsonWorkflowStore();
55
57
  try {
56
- const { choice, newState } = await respondToGate(runId, opts, runStore, workflowStore);
58
+ // issue #466 the run/workflow fetches stay OUTSIDE the sentence-try: a bad run-id is
59
+ // respond's most common operator error, and it must never wear the extensions sentence
60
+ // (the naked catch below is its home, #477). Only the extension resolution itself is
61
+ // wrapped, in place, mirroring run.ts's exact arm.
62
+ const run = await runStore.get(runId);
63
+ const workflow = await workflowStore.get(run.workflow_id);
64
+ let registry;
65
+ try {
66
+ ({ registry } = await loadProjectExtensions(workflow, {
67
+ ...(opts.extensionsModule !== undefined
68
+ ? { overrideModule: opts.extensionsModule }
69
+ : {}),
70
+ projectDir: opts.project ?? process.cwd(),
71
+ }));
72
+ }
73
+ catch (err) {
74
+ console.error(`Error loading extensions: ${err instanceof Error ? err.message : String(err)}`);
75
+ process.exit(1);
76
+ return;
77
+ }
78
+ const { choice, newState } = await respondToGate(runId, opts, runStore, workflowStore, registry);
57
79
  console.log(`Responded: ${runId} | choice '${choice}' | new state '${newState}'`);
58
80
  }
59
81
  catch (err) {
@@ -1 +1 @@
1
- {"version":3,"file":"respond.js","sourceRoot":"","sources":["../../src/commands/respond.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"respond.js","sourceRoot":"","sources":["../../src/commands/respond.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,37 @@
1
1
  import { Command } from 'commander';
2
+ import type { RunRecord } from '@sensigo/realm';
3
+ /**
4
+ * The detach map (issue #447): what to do next, for THIS run's state.
5
+ *
6
+ * Cancelling a dev-run prompt used to dump a raw Node stack over an unhandled AbortError, which
7
+ * reads like a crash. The run was always saved — every settled step persists before the next
8
+ * prompt — so the exit should say so, and then hand over the exact commands that work from here.
9
+ *
10
+ * The fork is on the RECORD, not on a guess, because two of the three states REFUSE most of the
11
+ * remedies and printing a command that will be rejected is worse than printing nothing:
12
+ *
13
+ * - TERMINAL — reachable for real: an external `realm run respond`, or a gate-expiry enactment,
14
+ * can terminalize this run while this process sits blocked on the prompt (the #291 race the
15
+ * fresh get below exists to catch). Inspect ONLY, because both other remedies refuse a
16
+ * terminal run, by two DIFFERENT mechanisms: `realm run abandon` throws STATE_RUN_TERMINAL
17
+ * (abandon-run.ts), while `realm agent --run-id` refuses through a separate uncoded check in
18
+ * resolveRunAttach (run-attach.ts) — a plain Error, not that code.
19
+ * - PENDING GATE — respond and inspect, and deliberately NO Discard line: `realm run abandon`
20
+ * REFUSES a run with a pending gate (STATE_TRANSITION_DENIED, abandon-run.ts) and tells you to
21
+ * resolve the gate first. The gate_id and choices come from the FROZEN record, the same source
22
+ * `respond` validates against, so what is printed is what will be accepted.
23
+ * - OTHERWISE — drive, inspect, or discard, all three of which apply.
24
+ *
25
+ * The choices are joined with `|` deliberately: this is a usage template showing alternation, not
26
+ * a list. (inspect renders them `', '` and the prompt `'/'`; three renderings, three purposes.)
27
+ * An authored-empty `choices: []` renders an empty alternation — that run is already
28
+ * human-unresolvable, which is issue #433's to make unmintable at the loader; not special-cased
29
+ * here.
30
+ *
31
+ * @internal Exported for testing only.
32
+ */
33
+ export declare function renderDetachMap(record: RunRecord, promptStep: string | undefined, opts?: {
34
+ headline?: string;
35
+ }): string;
2
36
  export declare const runCommand: Command;
3
37
  //# sourceMappingURL=run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA2BpC,eAAO,MAAM,UAAU,SAoNpB,CAAC"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAepC,OAAO,KAAK,EAIV,SAAS,EACV,MAAM,gBAAgB,CAAC;AAexB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,EACjB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3B,MAAM,CAgCR;AAmCD,eAAO,MAAM,UAAU,SAmVpB,CAAC"}
@@ -2,7 +2,8 @@
2
2
  import { Command } from 'commander';
3
3
  import { createInterface } from 'node:readline/promises';
4
4
  import { join } from 'node:path';
5
- import { loadWorkflowFromFile, JsonFileStore, findEligibleSteps, executeChain, submitHumanResponse, unmetCapabilities, capabilityWarning, } from '@sensigo/realm';
5
+ import { loadWorkflowFromFile, JsonFileStore, findEligibleSteps, executeChain, submitHumanResponse, unmetCapabilities, capabilityWarning, WorkflowError, deriveRunPhase, } from '@sensigo/realm';
6
+ import { renderLoadFailure } from '../lib/loader-warnings.js';
6
7
  import { loadProjectExtensions } from '../extensions/load-project-extensions.js';
7
8
  import { scheduleGateExpiryTimer } from '../agent/gate/gate-expiry-timer.js';
8
9
  /**
@@ -14,6 +15,94 @@ function isWriterNonceRequired() {
14
15
  const v = process.env['REALM_REQUIRE_WRITER_NONCE'];
15
16
  return v !== undefined && v !== '' && v !== '0' && v !== 'false';
16
17
  }
18
+ /**
19
+ * The detach map (issue #447): what to do next, for THIS run's state.
20
+ *
21
+ * Cancelling a dev-run prompt used to dump a raw Node stack over an unhandled AbortError, which
22
+ * reads like a crash. The run was always saved — every settled step persists before the next
23
+ * prompt — so the exit should say so, and then hand over the exact commands that work from here.
24
+ *
25
+ * The fork is on the RECORD, not on a guess, because two of the three states REFUSE most of the
26
+ * remedies and printing a command that will be rejected is worse than printing nothing:
27
+ *
28
+ * - TERMINAL — reachable for real: an external `realm run respond`, or a gate-expiry enactment,
29
+ * can terminalize this run while this process sits blocked on the prompt (the #291 race the
30
+ * fresh get below exists to catch). Inspect ONLY, because both other remedies refuse a
31
+ * terminal run, by two DIFFERENT mechanisms: `realm run abandon` throws STATE_RUN_TERMINAL
32
+ * (abandon-run.ts), while `realm agent --run-id` refuses through a separate uncoded check in
33
+ * resolveRunAttach (run-attach.ts) — a plain Error, not that code.
34
+ * - PENDING GATE — respond and inspect, and deliberately NO Discard line: `realm run abandon`
35
+ * REFUSES a run with a pending gate (STATE_TRANSITION_DENIED, abandon-run.ts) and tells you to
36
+ * resolve the gate first. The gate_id and choices come from the FROZEN record, the same source
37
+ * `respond` validates against, so what is printed is what will be accepted.
38
+ * - OTHERWISE — drive, inspect, or discard, all three of which apply.
39
+ *
40
+ * The choices are joined with `|` deliberately: this is a usage template showing alternation, not
41
+ * a list. (inspect renders them `', '` and the prompt `'/'`; three renderings, three purposes.)
42
+ * An authored-empty `choices: []` renders an empty alternation — that run is already
43
+ * human-unresolvable, which is issue #433's to make unmintable at the loader; not special-cased
44
+ * here.
45
+ *
46
+ * @internal Exported for testing only.
47
+ */
48
+ export function renderDetachMap(record, promptStep, opts) {
49
+ // DERIVED, never the persisted field: a record can carry a stale `run_phase` that disagrees
50
+ // with what its own seal says (issue #432's class), and a map that names the wrong phase sends
51
+ // an operator to the wrong remedy.
52
+ const phase = deriveRunPhase(record);
53
+ const step = promptStep ?? record.pending_gate?.step_name ?? '(step unknown)';
54
+ // issue #468 — the default is the #447 cancel route's own claim, byte-identical. The stall
55
+ // route (below, in the loop) passes 'Workflow stalled': no prompt was ever cancelled there, and
56
+ // the hardcoded word would be a false statement about what just happened.
57
+ const headline = opts?.headline ?? 'Prompt cancelled';
58
+ const lines = [
59
+ `${headline} — detached from run '${record.id}' at step '${step}' (phase: ${phase}). The run is saved.`,
60
+ ];
61
+ if (record.terminal_state) {
62
+ lines.push(` Inspect: realm run inspect ${record.id}`);
63
+ return lines.join('\n');
64
+ }
65
+ const gate = record.pending_gate;
66
+ if (gate !== undefined) {
67
+ lines.push(` Respond: realm run respond ${record.id} --gate ${gate.gate_id} --choice ${gate.choices.join('|')}`);
68
+ lines.push(` Inspect: realm run inspect ${record.id}`);
69
+ return lines.join('\n');
70
+ }
71
+ lines.push(` Drive it: realm agent --run-id ${record.id}`);
72
+ lines.push(` Inspect: realm run inspect ${record.id}`);
73
+ lines.push(` Discard: realm run abandon ${record.id}`);
74
+ return lines.join('\n');
75
+ }
76
+ /**
77
+ * Asks until the answer is usable: empty ⇒ {}, invalid JSON or a non-object ⇒ says why
78
+ * and re-asks (issue #459 — operator input gets a re-prompt, never the #123 rethrow;
79
+ * `42`/`null`/`[1]` are valid JSON that would lie through the object cast, MA-executed).
80
+ * Cancellation is untouched BY CONSTRUCTION: `rl.question` sits OUTSIDE the try, so an
81
+ * ABORT_ERR rejection propagates straight to the #447 catch and its detach map.
82
+ */
83
+ async function askJsonObject(rl, prompt) {
84
+ for (;;) {
85
+ const raw = await rl.question(prompt);
86
+ const trimmed = raw.trim();
87
+ if (trimmed === '')
88
+ return {};
89
+ let parsed;
90
+ try {
91
+ parsed = JSON.parse(trimmed);
92
+ }
93
+ catch (err) {
94
+ if (!(err instanceof SyntaxError))
95
+ throw err; // belt — only JSON.parse is in the try
96
+ console.error(` Not valid JSON: ${err.message} — try again (Enter for {}).`);
97
+ continue;
98
+ }
99
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
100
+ console.error(' Not a JSON object — the step\'s output must be an object like {"key": "value"}. Try again (Enter for {}).');
101
+ continue;
102
+ }
103
+ return parsed;
104
+ }
105
+ }
17
106
  export const runCommand = new Command('run')
18
107
  .argument('<path>', 'Path to workflow directory or workflow.yaml file')
19
108
  .option('--params <json>', 'Initial run parameters as JSON string', '{}')
@@ -34,7 +123,15 @@ export const runCommand = new Command('run')
34
123
  definition = loadWorkflowFromFile(filePath);
35
124
  }
36
125
  catch (err) {
37
- console.error(`Error loading workflow: ${err instanceof Error ? err.message : String(err)}`);
126
+ // issue #425 this catch wraps loadWorkflowFromFile ALONE, so everything it can see is
127
+ // a loader failure and the family split needs no else-arm here. A structural refusal
128
+ // renders verbatim; an unreadable file rides the helper's `Invalid: ` fallback, which
129
+ // replaces today's `Error loading workflow: ` prefix — a deliberate text change, so that
130
+ // one voice reaches an author from every command.
131
+ //
132
+ // No `err.warnings` render here: surfacing the lenient path's warnings on run/agent/
133
+ // listen stays out of scope (#424's stated non-goal).
134
+ console.error(renderLoadFailure(err instanceof WorkflowError ? err : String(err)));
38
135
  process.exit(1);
39
136
  }
40
137
  // 1b. Load project extensions BEFORE run creation (fail-before-create).
@@ -60,6 +157,26 @@ export const runCommand = new Command('run')
60
157
  console.error('Error: --params is not valid JSON');
61
158
  process.exit(1);
62
159
  }
160
+ // issue #426 — dev mode is interactive BY DESIGN: every step kind and every gate prompts
161
+ // on stdin (agent output, auto mock output, gate choices), so a non-TTY stdin can only
162
+ // ever EOF into ERR_USE_AFTER_CLOSE — and it did so AFTER the run was minted and its id
163
+ // printed, leaving a wedged `running` record behind on every scripted invocation.
164
+ //
165
+ // Refused BEFORE any store work, joining the "1b … BEFORE run creation" doctrine one
166
+ // member up. The placement is load → extensions → params → HERE, and each boundary is
167
+ // pinned: earlier than the load and the loader-voice cells lose their messages; earlier
168
+ // than extensions and the spawned orphan-manifest case loses its refusal; earlier than
169
+ // params and `--params '{'` reports the wrong problem.
170
+ //
171
+ // `!== true` rather than a truthiness test: isTTY is `undefined` on a pipe, never false.
172
+ if (process.stdin.isTTY !== true) {
173
+ console.error('Error: dev-mode run is interactive — it prompts on stdin for every step and gate, ' +
174
+ 'and stdin here is not a terminal. No run was created. ' +
175
+ "Scripted flows: 'realm workflow test' drives fixtures; " +
176
+ "'realm listen' / 'realm agent' are the production drives. " +
177
+ 'To run this workflow by hand, use a real terminal.');
178
+ process.exit(1);
179
+ }
63
180
  // 3. Create store and initial run record
64
181
  const store = new JsonFileStore();
65
182
  // issue #207 PR-2 (D3 §5, mixed-wiring gap): construct a JsonTraceBufferStore beside the
@@ -85,6 +202,10 @@ export const runCommand = new Command('run')
85
202
  const rl = createInterface({ input: process.stdin, output: process.stdout });
86
203
  // 5. Execution loop
87
204
  let run = await store.get(runId);
205
+ // issue #447: which step the operator was answering for, assigned immediately before each
206
+ // question. The block-scoped names below are not visible from the catch, and the catch is
207
+ // where this is needed.
208
+ let promptStep;
88
209
  try {
89
210
  while (!run.terminal_state) {
90
211
  // Handle open gate
@@ -105,6 +226,7 @@ export const runCommand = new Command('run')
105
226
  definition,
106
227
  registry,
107
228
  });
229
+ promptStep = g.step_name;
108
230
  const raw = await rl.question(` Choice [${g.choices.join('/')}]: `).finally(() => {
109
231
  clearExpiryTimer();
110
232
  });
@@ -123,14 +245,28 @@ export const runCommand = new Command('run')
123
245
  }
124
246
  else {
125
247
  console.error(` ✗ ${respondResult.errors.join(', ')}\n`);
126
- break;
248
+ // issue #468 — a FRESH read, not a break: most of this arm's members are a live
249
+ // gate re-asking (a typo, a stale choice) — rl.question blocks, no hot spin. The
250
+ // members that are NOT retryable (the gate/run moved or terminalized elsewhere) are
251
+ // exactly what this read converges: the next iteration sees the real state and
252
+ // either re-prompts honestly or reaches the stall/tail. Mirrors the ok arm above.
253
+ run = await store.get(runId);
127
254
  }
128
255
  continue;
129
256
  }
130
257
  const eligibleSteps = findEligibleSteps(definition, run);
131
258
  if (eligibleSteps.length === 0) {
132
259
  console.error(`\nNo eligible steps in phase '${run.run_phase}'. Workflow stalled.`);
133
- break;
260
+ // issue #468 — hands the run back with a truthful map instead of silently exiting 0.
261
+ // A fresh read: the loop's own snapshot is already current here (nothing awaited
262
+ // since the last read reached this branch in the same iteration), but the fresh read
263
+ // is the doctrine this file already keeps for every detach point (#447) — kept for
264
+ // consistency, not because a staleness gap is constructible in this spot.
265
+ const record = await store.get(runId);
266
+ console.error(renderDetachMap(record, promptStep, { headline: 'Workflow stalled' }));
267
+ // process.exit SKIPS the finally (the catch's own rule, below), so close explicitly.
268
+ rl.close();
269
+ process.exit(1);
134
270
  }
135
271
  // Take the first eligible step (linear workflow for dev mode)
136
272
  const stepName = eligibleSteps[0];
@@ -139,8 +275,8 @@ export const runCommand = new Command('run')
139
275
  // Build dispatcher output based on execution type
140
276
  let userOutput;
141
277
  if (stepDef.execution === 'agent') {
142
- const raw = await rl.question(' Agent output JSON (Enter for {}): ');
143
- userOutput = raw.trim() === '' ? {} : JSON.parse(raw);
278
+ promptStep = stepName;
279
+ userOutput = await askJsonObject(rl, ' Agent output JSON (Enter for {}): ');
144
280
  }
145
281
  else {
146
282
  // auto step
@@ -149,8 +285,8 @@ export const runCommand = new Command('run')
149
285
  : stepDef.uses_service !== undefined
150
286
  ? `service: ${stepDef.uses_service}`
151
287
  : 'auto';
152
- const raw = await rl.question(` Mock output (${hint}) — JSON (Enter for {}): `);
153
- userOutput = raw.trim() === '' ? {} : JSON.parse(raw);
288
+ promptStep = stepName;
289
+ userOutput = await askJsonObject(rl, ` Mock output (${hint}) JSON (Enter for {}): `);
154
290
  }
155
291
  const dispatcher = async () => userOutput;
156
292
  const result = await executeChain(store, definition, {
@@ -178,15 +314,93 @@ export const runCommand = new Command('run')
178
314
  }
179
315
  else {
180
316
  console.error(` ✗ ${result.status}: ${result.errors.join(', ')}\n`);
181
- break;
317
+ // issue #468 — a FRESH read, not a break: below the validation-exhaustion threshold
318
+ // the step is still eligible and this re-prompts it honestly; AT the threshold the
319
+ // run just terminalized in the store, and only a fresh read lets the while condition
320
+ // see that and exit to the tail — a bare continue would re-ask a dead run forever
321
+ // (verified: the mock chain exhausts and the answer never lands anywhere real).
322
+ run = await store.get(runId);
182
323
  }
183
324
  }
184
- if (run.terminal_state) {
185
- console.log(`Run complete. Phase: ${run.run_phase}`);
325
+ // Loop-head invariant (issue #468): exactly two exits from here — the while condition
326
+ // (terminal) and the stall (mapped + exit 1). Every ✗ arm above re-reads then
327
+ // continues; no `break` remains in this loop.
328
+ }
329
+ catch (err) {
330
+ // issue #447 — the operator cancelled the prompt. Ctrl-D at a pending `rl.question`
331
+ // rejects with an AbortError carrying `code: 'ABORT_ERR'`. Nothing awaits
332
+ // `program.parse()`, so today that surfaces as an unhandled-rejection stack: a saved run
333
+ // that looks like a crash.
334
+ //
335
+ // BOTH Ctrl-D and Ctrl-C land here on a real terminal — measured on this build: ^C at a
336
+ // live prompt printed the full map and exited 1. What decides it is the WIRING, not the
337
+ // key: readline enables raw mode only in terminal mode, which requires `output.isTTY`.
338
+ // With stdout PIPED, readline never takes the terminal path and ^C takes the
339
+ // signal/inert route instead (measured: the process died at 130) — out of this catch's
340
+ // reach, and outside anything the map claims.
341
+ //
342
+ // One timing caveat, worth recording because it produced a confident wrong answer during
343
+ // this work: the pty is COOKED until readline switches it, so a ^C delivered before that
344
+ // switch takes the cooked ISIG path and kills the process before any JS runs. A scripted
345
+ // `printf '\x03' | …` hits that window; the same harness with the byte delayed a second
346
+ // rejects ABORT_ERR-coded as expected. Cooked-mode ^D persists in the stream as EOF and
347
+ // still ends up ABORT_ERR-coded, which is exactly why it looked like a valid control and
348
+ // was not. No claim here about anyone typing that fast.
349
+ //
350
+ // Keyed on the CODE alone, never the message, which names the trigger and varies.
351
+ //
352
+ // The classification is precise TODAY and the precision is not free: a handler that
353
+ // out-throws is re-coded ENGINE_HANDLER_FAILED (execution-loop.ts), every engine throw
354
+ // is WorkflowError-coded and none uses ABORT_ERR, and the gate-expiry timer contains
355
+ // its own errors. So ABORT_ERR reaching here means the prompt, and only the prompt.
356
+ // ADDING ANY AbortSignal-CONSUMING AWAIT TO THIS LOOP REQUIRES RE-ESTABLISHING THAT.
357
+ if (err?.code === 'ABORT_ERR') {
358
+ // A FRESH read, not the loop's `run`: while this process sat blocked on the prompt,
359
+ // another terminal's `realm run respond` or an expiry enactment may have moved it —
360
+ // the #291 race. The map is only as good as the state it forks on.
361
+ let record;
362
+ try {
363
+ record = await store.get(runId);
364
+ }
365
+ catch (getErr) {
366
+ // The same concurrent-writer reality that makes the fresh read necessary can also
367
+ // make it FAIL — a record purged from another terminal mid-prompt. Without this arm
368
+ // the new cancel path would crash with exactly the stack this feature exists to
369
+ // remove. `inspect` still earns its line: it is the only read surface, and its own
370
+ // answer for a missing record is a clean not-found rather than a stack.
371
+ const message = getErr instanceof Error ? getErr.message : String(getErr);
372
+ console.error(`Prompt cancelled — detached from run '${runId}'. Its record could not be re-read: ${message}. Inspect: realm run inspect ${runId}`);
373
+ rl.close();
374
+ process.exit(1);
375
+ }
376
+ console.error(renderDetachMap(record, promptStep));
377
+ // process.exit SKIPS the finally, so close explicitly. (rl.close is idempotent, so
378
+ // the double-close on any path that reaches both is harmless — probed.)
379
+ rl.close();
380
+ // Exit 1, not 130: readline consumed the keypress and the process was never signalled,
381
+ // so 130 would claim a death that did not happen.
382
+ process.exit(1);
186
383
  }
384
+ throw err;
187
385
  }
188
386
  finally {
189
387
  rl.close();
190
388
  }
389
+ // issue #468 — the exit code tells the truth. `run` is declared outside the try, so by the
390
+ // time control reaches here the finally above has already closed `rl` on every path that
391
+ // gets this far (the catch's ABORT_ERR arm exits directly and never falls through to here).
392
+ // Only the while condition being false gets here — the stall exits from inside the try
393
+ // above — so `run.terminal_state` is always true at this point.
394
+ if (deriveRunPhase(run) === 'completed') {
395
+ console.log(`Run complete. Phase: ${run.run_phase}`);
396
+ // NATURAL RETURN — never process.exit(0): three declared controls (run-detach.test.ts's
397
+ // R1/R2/R3) pin the completed path as an unwrapped, un-exited resolution.
398
+ // completed-with-failed-steps (the #302 world) exits 0 too, here — outcome-keyed,
399
+ // consistent with the engine's own seal ruling; #304's run-health finding +
400
+ // terminal_reason are the disclosure channel, not this exit code.
401
+ return;
402
+ }
403
+ console.log(`Run complete. Phase: ${run.run_phase}`);
404
+ process.exit(1);
191
405
  });
192
406
  //# sourceMappingURL=run.js.map