@sensigo/realm-cli 0.40.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.
- package/dist/agent/providers/agent-utils.d.ts +37 -13
- package/dist/agent/providers/agent-utils.d.ts.map +1 -1
- package/dist/agent/providers/agent-utils.js +92 -17
- package/dist/agent/providers/agent-utils.js.map +1 -1
- package/dist/commands/agent.d.ts +6 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +28 -4
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/drain.d.ts.map +1 -1
- package/dist/commands/drain.js +37 -3
- package/dist/commands/drain.js.map +1 -1
- package/dist/commands/inspect.d.ts.map +1 -1
- package/dist/commands/inspect.js +19 -2
- package/dist/commands/inspect.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +59 -8
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/listen.d.ts +13 -0
- package/dist/commands/listen.d.ts.map +1 -1
- package/dist/commands/listen.js +36 -4
- package/dist/commands/listen.js.map +1 -1
- package/dist/commands/register.d.ts +25 -0
- package/dist/commands/register.d.ts.map +1 -1
- package/dist/commands/register.js +88 -20
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/respond.d.ts.map +1 -1
- package/dist/commands/respond.js +23 -1
- package/dist/commands/respond.js.map +1 -1
- package/dist/commands/run.d.ts +34 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +225 -11
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +48 -8
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/validate.d.ts +19 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +354 -82
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/watch.d.ts +30 -0
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/commands/watch.js +155 -21
- package/dist/commands/watch.js.map +1 -1
- package/dist/commands/workflow-list.d.ts +16 -0
- package/dist/commands/workflow-list.d.ts.map +1 -0
- package/dist/commands/workflow-list.js +96 -0
- package/dist/commands/workflow-list.js.map +1 -0
- package/dist/commands-registry.d.ts.map +1 -1
- package/dist/commands-registry.js +2 -0
- package/dist/commands-registry.js.map +1 -1
- package/dist/extensions/load-project-extensions.d.ts.map +1 -1
- package/dist/extensions/load-project-extensions.js +9 -3
- package/dist/extensions/load-project-extensions.js.map +1 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/loader-warnings.d.ts +24 -3
- package/dist/lib/loader-warnings.d.ts.map +1 -1
- package/dist/lib/loader-warnings.js +66 -5
- package/dist/lib/loader-warnings.js.map +1 -1
- package/package.json +4 -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,
|
|
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
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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(`⚠
|
|
43
|
-
console.warn('⚠
|
|
44
|
-
|
|
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
|
|
@@ -68,13 +100,15 @@ export const registerCommand = new Command('register')
|
|
|
68
100
|
// the registry are never re-parsed and are unaffected.
|
|
69
101
|
if (rejectOnErrorSeverity(warnings)) {
|
|
70
102
|
printLoaderWarnings(warnings);
|
|
71
|
-
|
|
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));
|
|
72
106
|
process.exit(1);
|
|
73
107
|
return;
|
|
74
108
|
}
|
|
75
109
|
if (opts.strict && failsStrict(warnings)) {
|
|
76
110
|
printLoaderWarnings(warnings);
|
|
77
|
-
console.error(`Error: '${definition.id}' v${definition.version} has ${warnings.length} warning
|
|
111
|
+
console.error(`Error: '${definition.id}' v${definition.version} has ${warnings.length} ${warnings.length === 1 ? 'warning' : 'warnings'}; refusing to register due to --strict`);
|
|
78
112
|
process.exit(1);
|
|
79
113
|
return;
|
|
80
114
|
}
|
|
@@ -83,16 +117,50 @@ export const registerCommand = new Command('register')
|
|
|
83
117
|
printLoaderWarnings(warnings);
|
|
84
118
|
const contextWarnings = lintWorkflowContext(definition);
|
|
85
119
|
for (const warning of contextWarnings) {
|
|
86
|
-
console.warn(`⚠
|
|
120
|
+
console.warn(`⚠ ${warning}`);
|
|
87
121
|
}
|
|
88
|
-
|
|
122
|
+
const stepCount = Object.keys(definition.steps).length;
|
|
123
|
+
console.log(`Registered: ${definition.id} v${definition.version} (${stepCount} ${stepCount === 1 ? 'step' : 'steps'})`);
|
|
89
124
|
if (definition.description !== undefined) {
|
|
90
125
|
console.log(` ${definition.description}`);
|
|
91
126
|
}
|
|
92
127
|
}
|
|
93
128
|
catch (err) {
|
|
94
|
-
|
|
95
|
-
|
|
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
|
+
}
|
|
96
164
|
process.exit(1);
|
|
97
165
|
}
|
|
98
166
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/commands/register.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,0FAA0F;AAC1F,4FAA4F;AAC5F,wFAAwF;AACxF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EACL,mCAAmC,EACnC,iBAAiB,EACjB,
|
|
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,
|
|
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"}
|
package/dist/commands/respond.js
CHANGED
|
@@ -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
|
-
|
|
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":"AAAA,0EAA0E;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AAEjF;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAa,EACb,OAAsF,EACtF,QAAkB,EAClB,aAAgC,EAChC,QAA4B;IAE5B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAE1D,4FAA4F;IAC5F,uFAAuF;IACvF,wFAAwF;IACxF,wEAAwE;IACxE,MAAM,iBAAiB,GACrB,QAAQ;QACR,CACE,MAAM,qBAAqB,CAAC,QAAQ,EAAE;YACpC,GAAG,CAAC,OAAO,CAAC,gBAAgB,KAAK,SAAS;gBACxC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,gBAAgB,EAAE;gBAC9C,CAAC,CAAC,EAAE,CAAC;YACP,UAAU,EAAE,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE;SAC7C,CAAC,CACH,CAAC,QAAQ,CAAC;IAEb,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE;QAC3D,KAAK;QACL,MAAM,EAAE,OAAO,CAAC,IAAI;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,iBAAiB;KAC5B,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QAC3B,MAAM,IAAI,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,sBAAsB,EAAE;YAClE,IAAI,EAAE,eAAe;YACrB,QAAQ,EAAE,OAAO;YACjB,WAAW,EAAE,gBAAgB;YAC7B,SAAS,EAAE,KAAK;SACjB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC7C,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,UAAU,CAAC,SAAS,EAAE,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,OAAO,CAAC,SAAS,CAAC;KACjD,WAAW,CAAC,4DAA4D,CAAC;KACzE,QAAQ,CAAC,UAAU,EAAE,iCAAiC,CAAC;KACvD,cAAc,CAAC,kBAAkB,EAAE,4CAA4C,CAAC;KAChF,cAAc,CAAC,mBAAmB,EAAE,6CAA6C,CAAC;KAClF,MAAM,CACL,iBAAiB,EACjB,iIAAiI,CAClI;KACA,MAAM,CACL,4BAA4B,EAC5B,gGAAgG,CACjG;KACA,MAAM,CACL,KAAK,EACH,KAAa,EACb,IAAmF,EACnF,EAAE;IACF,MAAM,EAAE,aAAa,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IAC5E,MAAM,QAAQ,GAAG,IAAI,aAAa,EAAE,CAAC;IACrC,MAAM,aAAa,GAAG,IAAI,iBAAiB,EAAE,CAAC;IAC9C,IAAI,CAAC;QACH,MAAM,
|
|
1
|
+
{"version":3,"file":"respond.js","sourceRoot":"","sources":["../../src/commands/respond.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/run.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/commands/run.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
143
|
-
userOutput =
|
|
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
|
-
|
|
153
|
-
userOutput =
|
|
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
|
-
|
|
185
|
-
|
|
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
|