@haystackeditor/cli 0.15.2 → 0.15.3
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/commands/setup.js +17 -1
- package/dist/utils/prompter.d.ts +6 -0
- package/dist/utils/prompter.js +36 -17
- package/package.json +1 -1
package/dist/commands/setup.js
CHANGED
|
@@ -19,7 +19,7 @@ import { resolveAuthContext } from '../utils/auth.js';
|
|
|
19
19
|
import { hooksInstall } from './hooks.js';
|
|
20
20
|
import { findGitRoot } from '../utils/hooks.js';
|
|
21
21
|
import { trackError, trackSetupEvent } from '../utils/telemetry.js';
|
|
22
|
-
import { createPrompter, loadAnswersFile } from '../utils/prompter.js';
|
|
22
|
+
import { createPrompter, loadAnswersFile, PromptCancelledError } from '../utils/prompter.js';
|
|
23
23
|
/** The active prompter for this `setup` invocation (TTY or JSON). */
|
|
24
24
|
let ui;
|
|
25
25
|
/**
|
|
@@ -744,6 +744,13 @@ async function isAppInstalled(token) {
|
|
|
744
744
|
// keeps waiting instead of crashing the wizard.
|
|
745
745
|
if (err instanceof InstallationsAuthError)
|
|
746
746
|
throw err;
|
|
747
|
+
// Transient (5xx / network / rate limit): log so it isn't a silent fallback
|
|
748
|
+
// (PR001), then return false so the install poll keeps waiting. Re-throwing
|
|
749
|
+
// here — a literal reading of "no silent fallback" — would reintroduce the
|
|
750
|
+
// poll-crash this function exists to prevent, so we log instead.
|
|
751
|
+
trackError('haystack_setup_app_check_transient_error', {
|
|
752
|
+
error_message: err instanceof Error ? err.message : String(err),
|
|
753
|
+
});
|
|
747
754
|
return false;
|
|
748
755
|
}
|
|
749
756
|
}
|
|
@@ -1172,6 +1179,15 @@ export async function setupCommand(options = {}) {
|
|
|
1172
1179
|
try {
|
|
1173
1180
|
await runSetupFlow(options);
|
|
1174
1181
|
}
|
|
1182
|
+
catch (err) {
|
|
1183
|
+
// An agent cancelling a prompt is a clean exit, not a crash.
|
|
1184
|
+
if (err instanceof PromptCancelledError) {
|
|
1185
|
+
console.log(chalk.yellow('\n Setup cancelled.\n'));
|
|
1186
|
+
ui.result({ status: 'cancelled' });
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
throw err;
|
|
1190
|
+
}
|
|
1175
1191
|
finally {
|
|
1176
1192
|
ui.close();
|
|
1177
1193
|
}
|
package/dist/utils/prompter.d.ts
CHANGED
|
@@ -25,6 +25,12 @@ export type Choice<T> = {
|
|
|
25
25
|
} | {
|
|
26
26
|
separator: string;
|
|
27
27
|
};
|
|
28
|
+
/** Thrown when an agent explicitly rejects/cancels a prompt (vs. answering it),
|
|
29
|
+
* so callers can distinguish a cancellation from a valid empty answer. */
|
|
30
|
+
export declare class PromptCancelledError extends Error {
|
|
31
|
+
readonly requestID: string;
|
|
32
|
+
constructor(requestID: string);
|
|
33
|
+
}
|
|
28
34
|
export interface Prompter {
|
|
29
35
|
/** True for the human TTY prompter — gates side effects like opening a browser
|
|
30
36
|
* that only make sense for a local interactive user. */
|
package/dist/utils/prompter.js
CHANGED
|
@@ -23,6 +23,16 @@ import { readFileSync } from 'node:fs';
|
|
|
23
23
|
function isSeparator(c) {
|
|
24
24
|
return c.separator !== undefined;
|
|
25
25
|
}
|
|
26
|
+
/** Thrown when an agent explicitly rejects/cancels a prompt (vs. answering it),
|
|
27
|
+
* so callers can distinguish a cancellation from a valid empty answer. */
|
|
28
|
+
export class PromptCancelledError extends Error {
|
|
29
|
+
requestID;
|
|
30
|
+
constructor(requestID) {
|
|
31
|
+
super(`Prompt "${requestID}" was cancelled.`);
|
|
32
|
+
this.requestID = requestID;
|
|
33
|
+
this.name = 'PromptCancelledError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
26
36
|
export function loadAnswersFile(path) {
|
|
27
37
|
const raw = readFileSync(path, 'utf8');
|
|
28
38
|
const parsed = JSON.parse(raw);
|
|
@@ -193,10 +203,12 @@ class JsonPrompter {
|
|
|
193
203
|
// Reply: {requestID, answers: <value[]>}; reject: {requestID, reject: true}.
|
|
194
204
|
this.emit({ type: 'question', requestID: q.id, kind: 'multiselect', prompt: q.message, options });
|
|
195
205
|
const reply = await this.awaitReply(q.id);
|
|
206
|
+
// Surface a rejection as a distinct cancellation rather than masking it as an
|
|
207
|
+
// empty selection, and surface a non-array reply as a protocol error rather
|
|
208
|
+
// than silently coercing to [] (PR001: no silent fallback).
|
|
196
209
|
if (reply.reject)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
return Array.isArray(a) ? a : [];
|
|
210
|
+
throw new PromptCancelledError(q.id);
|
|
211
|
+
return asArray(reply.answers ?? reply.value, q.id);
|
|
200
212
|
}
|
|
201
213
|
async action(q) {
|
|
202
214
|
if (this.answers[q.id] === 'skip')
|
|
@@ -216,22 +228,29 @@ class JsonPrompter {
|
|
|
216
228
|
const r = (reply.response ?? reply.value);
|
|
217
229
|
override = r === 'reject' || r === 'skip' ? 'skip' : 'done';
|
|
218
230
|
});
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
+
try {
|
|
232
|
+
while (Date.now() - started < timeout) {
|
|
233
|
+
if (await q.check())
|
|
234
|
+
return true;
|
|
235
|
+
if (override === 'skip')
|
|
236
|
+
return false;
|
|
237
|
+
// 'done' means the agent reports the action complete — but completion may
|
|
238
|
+
// be eventually-consistent, so keep polling check() at the interval until
|
|
239
|
+
// it confirms or we time out (matching TTY behavior), rather than failing
|
|
240
|
+
// on a single immediate check.
|
|
241
|
+
if (override === 'done') {
|
|
242
|
+
await sleep(interval);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
await Promise.race([sleep(interval), answerPromise]);
|
|
231
246
|
}
|
|
232
|
-
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
// If polling or the timeout won before any stdin reply arrived, drop the
|
|
251
|
+
// dangling resolver so it doesn't accumulate across action() calls.
|
|
252
|
+
this.pending.delete(q.id);
|
|
233
253
|
}
|
|
234
|
-
return false;
|
|
235
254
|
}
|
|
236
255
|
progress(message, detail) {
|
|
237
256
|
this.emit({ type: 'progress', message, ...(detail ? { detail } : {}) });
|