substrate-ai 0.20.63 → 0.20.65
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/adapter-registry-BbVWH3Yv.js +4 -0
- package/dist/cli/index.js +93 -23
- package/dist/decision-router-DblHY8se.js +97 -0
- package/dist/{decisions-4F91LrVD.js → decisions-DilHo99V.js} +2 -2
- package/dist/{dist-W2emvN3F.js → dist-K_RRWnBX.js} +2 -2
- package/dist/{errors-CKFu8YI9.js → errors-pSiZbn6e.js} +2 -2
- package/dist/{experimenter-DxxwicpK.js → experimenter-DT9v2Pto.js} +1 -1
- package/dist/health-DC3y-sR6.js +1715 -0
- package/dist/health-qhtWYh49.js +8 -0
- package/dist/index-c924O9mj.d.ts +1432 -0
- package/dist/index.d.ts +132 -736
- package/dist/index.js +2 -2
- package/dist/interactive-prompt-C7wpE4z4.js +183 -0
- package/dist/{health-C-KOZrFJ.js → manifest-read-DDkXC3L_.js} +130 -2013
- package/dist/modules/interactive-prompt/index.d.ts +86 -0
- package/dist/modules/interactive-prompt/index.js +6 -0
- package/dist/recovery-engine-BKGBeBnW.js +281 -0
- package/dist/{routing-0ykvBl_4.js → routing-CzF0p6lI.js} +2 -2
- package/dist/run-DX95j4_D.js +14 -0
- package/dist/{run-CHUFlRbH.js → run-DzB4rgkj.js} +391 -31
- package/dist/src/modules/decision-router/index.d.ts +88 -0
- package/dist/src/modules/decision-router/index.js +3 -0
- package/dist/src/modules/recovery-engine/index.d.ts +1101 -0
- package/dist/src/modules/recovery-engine/index.js +5 -0
- package/dist/{upgrade-C8LAnB6W.js → upgrade-DxzQ1nss.js} +3 -3
- package/dist/{upgrade-CAqLkNUP.js → upgrade-MP9XzrI6.js} +2 -2
- package/dist/version-manager-impl-GZDUBt0Q.js +4 -0
- package/dist/work-graph-repository-DZyJv5pV.js +265 -0
- package/package.json +1 -1
- package/dist/adapter-registry-k7ZX3Bz6.js +0 -4
- package/dist/health-CJqd1FzY.js +0 -6
- package/dist/run-Z_-caE_i.js +0 -9
- package/dist/version-manager-impl-DaA_ALYK.js +0 -4
- /package/dist/{decisions-C0pz9Clx.js → decisions-CzSIEeGP.js} +0 -0
- /package/dist/{routing-CcBOCuC9.js → routing-DFxoKHDt.js} +0 -0
- /package/dist/{version-manager-impl-BmOWu8ml.js → version-manager-impl-qFBiO4Eh.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { childLogger, createLogger, logger } from "./logger-KeHncl-f.js";
|
|
2
2
|
import { assertDefined, createEventBus, createTuiApp, deepClone, formatDuration, generateId, isPlainObject, isTuiCapable, printNonTtyWarning, sleep, withRetry } from "./helpers-CElYrONe.js";
|
|
3
|
-
import { AdapterRegistry, AdtError, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, GeminiCLIAdapter } from "./dist-
|
|
3
|
+
import { AdapterRegistry$1 as AdapterRegistry, AdtError$1 as AdtError, ClaudeCodeAdapter$1 as ClaudeCodeAdapter, CodexCLIAdapter$1 as CodexCLIAdapter, ConfigError$1 as ConfigError, ConfigIncompatibleFormatError$1 as ConfigIncompatibleFormatError, GeminiCLIAdapter$1 as GeminiCLIAdapter } from "./dist-K_RRWnBX.js";
|
|
4
4
|
import "./adapter-registry-DXLMTmfD.js";
|
|
5
|
-
import { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError } from "./errors-
|
|
5
|
+
import { BudgetExceededError, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError } from "./errors-pSiZbn6e.js";
|
|
6
6
|
|
|
7
7
|
//#region src/core/di.ts
|
|
8
8
|
/**
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { createLogger } from "./logger-KeHncl-f.js";
|
|
2
|
+
import { readCurrentRunId, resolveMainRepoRoot } from "./manifest-read-DDkXC3L_.js";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import * as readline from "node:readline";
|
|
6
|
+
|
|
7
|
+
//#region src/modules/interactive-prompt/index.ts
|
|
8
|
+
const logger = createLogger("interactive-prompt");
|
|
9
|
+
const SEPARATOR = "─────────────────────────────────────────────────";
|
|
10
|
+
/**
|
|
11
|
+
* Write the numbered-choice prompt to stdout (AC2).
|
|
12
|
+
* Called before readline to ensure the separator appears before stdin read.
|
|
13
|
+
*/
|
|
14
|
+
function renderPrompt(ctx) {
|
|
15
|
+
process.stdout.write(`${SEPARATOR}\n`);
|
|
16
|
+
process.stdout.write(`⚠ Halt: ${ctx.decisionType} (${ctx.severity})\n`);
|
|
17
|
+
process.stdout.write(`${SEPARATOR}\n`);
|
|
18
|
+
process.stdout.write(`${ctx.summary}\n\n`);
|
|
19
|
+
process.stdout.write(`1) Accept default: ${ctx.defaultAction}\n`);
|
|
20
|
+
process.stdout.write(`2) Retry with custom context\n`);
|
|
21
|
+
process.stdout.write(`3) Propose re-scope\n`);
|
|
22
|
+
process.stdout.write(`4) Abort run\n`);
|
|
23
|
+
process.stdout.write(`\nChoice [1]: `);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Sanitize an ISO timestamp string for use in a filename (replace colons and dots).
|
|
27
|
+
*/
|
|
28
|
+
function sanitizeTimestampForFilename(iso) {
|
|
29
|
+
return iso.replace(/:/g, "-").replace(/\./g, "-");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the notifications directory (AC8 — use resolveMainRepoRoot).
|
|
33
|
+
*/
|
|
34
|
+
async function resolveNotificationsDir() {
|
|
35
|
+
const repoRoot = await resolveMainRepoRoot();
|
|
36
|
+
return join(repoRoot, ".substrate", "notifications");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Write the initial notification file BEFORE prompting (AC5).
|
|
40
|
+
* Returns the file path so we can update operatorChoice after the prompt.
|
|
41
|
+
*/
|
|
42
|
+
async function writeNotificationFile(runId, ctx, timestamp) {
|
|
43
|
+
const notifDir = await resolveNotificationsDir();
|
|
44
|
+
await mkdir(notifDir, { recursive: true });
|
|
45
|
+
const safeTs = sanitizeTimestampForFilename(timestamp);
|
|
46
|
+
const filePath = join(notifDir, `${runId}-${safeTs}.json`);
|
|
47
|
+
const notification = {
|
|
48
|
+
runId,
|
|
49
|
+
timestamp,
|
|
50
|
+
decisionType: ctx.decisionType,
|
|
51
|
+
severity: ctx.severity,
|
|
52
|
+
context: {
|
|
53
|
+
summary: ctx.summary,
|
|
54
|
+
defaultAction: ctx.defaultAction
|
|
55
|
+
},
|
|
56
|
+
choices: ctx.choices,
|
|
57
|
+
operatorChoice: null
|
|
58
|
+
};
|
|
59
|
+
await writeFile(filePath, JSON.stringify(notification, null, 2), "utf8");
|
|
60
|
+
logger.debug({ filePath }, "notification file written");
|
|
61
|
+
return filePath;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Update operatorChoice in the notification file after the operator responds (AC5).
|
|
65
|
+
* If the file was deleted by an external monitor, swallow ENOENT and continue (AC7).
|
|
66
|
+
*/
|
|
67
|
+
async function updateNotificationChoice(filePath, operatorChoice) {
|
|
68
|
+
try {
|
|
69
|
+
const raw = await readFile(filePath, "utf8");
|
|
70
|
+
const parsed = JSON.parse(raw);
|
|
71
|
+
parsed.operatorChoice = operatorChoice;
|
|
72
|
+
await writeFile(filePath, JSON.stringify(parsed, null, 2), "utf8");
|
|
73
|
+
logger.debug({
|
|
74
|
+
filePath,
|
|
75
|
+
operatorChoice
|
|
76
|
+
}, "notification file updated with operator choice");
|
|
77
|
+
} catch (err) {
|
|
78
|
+
if (err.code === "ENOENT") {
|
|
79
|
+
logger.debug({ filePath }, "notification file already deleted by external monitor — continuing");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
logger.warn({
|
|
83
|
+
err,
|
|
84
|
+
filePath
|
|
85
|
+
}, "failed to update notification file — continuing");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Parse a raw stdin line into a 1–4 integer choice, defaulting to 1 on failure.
|
|
90
|
+
*/
|
|
91
|
+
function parseChoice(raw) {
|
|
92
|
+
const trimmed = raw.trim();
|
|
93
|
+
if (trimmed === "") return 1;
|
|
94
|
+
const n = parseInt(trimmed, 10);
|
|
95
|
+
if (isNaN(n) || n < 1 || n > 4) return 1;
|
|
96
|
+
return n;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Map a parsed choice integer to the corresponding operator action string.
|
|
100
|
+
*/
|
|
101
|
+
function mapChoiceToAction(choice, defaultAction) {
|
|
102
|
+
switch (choice) {
|
|
103
|
+
case 1: return defaultAction;
|
|
104
|
+
case 2: return "retry-with-custom-context";
|
|
105
|
+
case 3: return "propose-re-scope";
|
|
106
|
+
case 4: return "abort-run";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Run the interactive prompt for a Decision Router halt.
|
|
111
|
+
*
|
|
112
|
+
* Workflow:
|
|
113
|
+
* 1. Resolve run ID (from context or manifest-read.ts).
|
|
114
|
+
* 2. Write notification file BEFORE prompting (AC5).
|
|
115
|
+
* 3. If non-interactive mode, emit halt-skipped event and return default (AC4).
|
|
116
|
+
* 4. If stdin is not a TTY, log warning and treat as non-interactive (defensive).
|
|
117
|
+
* 5. Render the numbered-choice prompt (AC2).
|
|
118
|
+
* 6. Read one line from stdin via readline.createInterface (AC3).
|
|
119
|
+
* 7. Parse and return the chosen action.
|
|
120
|
+
* 8. Update notification file with the operator's choice (AC5).
|
|
121
|
+
*
|
|
122
|
+
* @param decisionContext - Decision context from the Decision Router halt.
|
|
123
|
+
* @returns The operator's chosen action string.
|
|
124
|
+
*/
|
|
125
|
+
async function runInteractivePrompt(decisionContext) {
|
|
126
|
+
let runId = decisionContext.runId ?? null;
|
|
127
|
+
if (!runId) try {
|
|
128
|
+
const repoRoot = await resolveMainRepoRoot();
|
|
129
|
+
runId = await readCurrentRunId(repoRoot);
|
|
130
|
+
} catch {}
|
|
131
|
+
if (!runId) runId = "unknown";
|
|
132
|
+
const timestamp = new Date().toISOString();
|
|
133
|
+
let notifPath = null;
|
|
134
|
+
try {
|
|
135
|
+
notifPath = await writeNotificationFile(runId, decisionContext, timestamp);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
logger.warn({ err }, "failed to write notification file — continuing without it");
|
|
138
|
+
}
|
|
139
|
+
const isNonInteractive = process.env["SUBSTRATE_NON_INTERACTIVE"] === "true" || decisionContext.nonInteractive === true;
|
|
140
|
+
if (isNonInteractive) {
|
|
141
|
+
const haltSkippedPayload = {
|
|
142
|
+
runId,
|
|
143
|
+
decisionType: decisionContext.decisionType,
|
|
144
|
+
severity: decisionContext.severity,
|
|
145
|
+
defaultAction: decisionContext.defaultAction,
|
|
146
|
+
reason: "non-interactive: stdin prompt suppressed"
|
|
147
|
+
};
|
|
148
|
+
decisionContext.onHaltSkipped?.(haltSkippedPayload);
|
|
149
|
+
logger.debug({
|
|
150
|
+
runId,
|
|
151
|
+
decisionType: decisionContext.decisionType
|
|
152
|
+
}, "non-interactive mode: returning default action");
|
|
153
|
+
return decisionContext.defaultAction;
|
|
154
|
+
}
|
|
155
|
+
if (!process.stdin.isTTY) {
|
|
156
|
+
logger.warn({
|
|
157
|
+
runId,
|
|
158
|
+
decisionType: decisionContext.decisionType
|
|
159
|
+
}, "stdin is not a TTY — treating as non-interactive and returning default action");
|
|
160
|
+
return decisionContext.defaultAction;
|
|
161
|
+
}
|
|
162
|
+
renderPrompt(decisionContext);
|
|
163
|
+
const chosenAction = await new Promise((resolve$1) => {
|
|
164
|
+
const rl = readline.createInterface({
|
|
165
|
+
input: process.stdin,
|
|
166
|
+
output: process.stdout
|
|
167
|
+
});
|
|
168
|
+
rl.once("line", (line) => {
|
|
169
|
+
rl.close();
|
|
170
|
+
const choice = parseChoice(line);
|
|
171
|
+
resolve$1(mapChoiceToAction(choice, decisionContext.defaultAction));
|
|
172
|
+
});
|
|
173
|
+
rl.once("close", () => {
|
|
174
|
+
resolve$1(decisionContext.defaultAction);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
if (notifPath) await updateNotificationChoice(notifPath, chosenAction);
|
|
178
|
+
return chosenAction;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
//#endregion
|
|
182
|
+
export { runInteractivePrompt };
|
|
183
|
+
//# sourceMappingURL=interactive-prompt-C7wpE4z4.js.map
|