@haystackeditor/cli 0.17.0 → 0.18.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/README.md +208 -26
- package/dist/commands/case-batch-contract.js +2 -2
- package/dist/commands/case-batch.js +7 -17
- package/dist/commands/crawl-contract.js +3 -0
- package/dist/commands/db-profile-contract.js +419 -0
- package/dist/commands/db-profile-database.js +289 -0
- package/dist/commands/db-profile-distinct.js +97 -0
- package/dist/commands/db-profile-owners.js +94 -0
- package/dist/commands/db-profile-privacy.js +450 -0
- package/dist/commands/db-profile-show.js +218 -0
- package/dist/commands/db-profile-sql-literals.js +140 -0
- package/dist/commands/db-profile-sql.js +362 -0
- package/dist/commands/db-profile-upload.js +88 -0
- package/dist/commands/db-profile.js +1020 -0
- package/dist/commands/fleet-policy-contract.js +28 -0
- package/dist/commands/fleet-policy.js +305 -0
- package/dist/commands/precompute-delivery-worker.js +17 -3
- package/dist/commands/precompute-delivery.js +16 -6
- package/dist/commands/verify-hosted.js +20 -0
- package/dist/commands/verify-precompute.js +140 -95
- package/dist/commands/verify.js +523 -52
- package/dist/index.js +173 -21
- package/dist/schema.js +1 -0
- package/dist/triage/astra.js +5 -2
- package/dist/triage/runner.js +1 -1
- package/dist/utils/verify-base.js +31 -0
- package/package.json +4 -1
- package/schemas/verify.v1.json +217 -0
- package/dist/commands/combination-search-hook-contract.js +0 -1
- package/dist/commands/combination-search-hook.js +0 -139
|
@@ -6,17 +6,18 @@ import { isAbsolute, join, resolve } from 'node:path';
|
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { NotLoggedInError, resolveAuthContext } from '../utils/auth.js';
|
|
9
|
-
import { findGitRoot, parseRemoteUrl,
|
|
9
|
+
import { findGitRoot, parseRemoteUrl, } from '../utils/git.js';
|
|
10
|
+
import { resolveVerifyBaseSha } from '../utils/verify-base.js';
|
|
10
11
|
import { HaystackApiError, haystackJson, REPO_NOT_ENTITLED_CODE, SUBSCRIPTION_REQUIRED_CODE, } from '../utils/haystack-api.js';
|
|
11
12
|
import { trackPrecomputeEvent } from '../utils/telemetry.js';
|
|
12
13
|
import { findSecretsInPatch, isSecretBearingPath } from '../utils/secret-paths.js';
|
|
13
14
|
import { startPrecomputeDelivery } from './precompute-delivery.js';
|
|
14
|
-
import {
|
|
15
|
+
import { CRAWL_TITLE_MAX_CHARS } from './crawl-contract.js';
|
|
15
16
|
// --hook has five seconds to capture the exact source and publish local custody.
|
|
16
17
|
// Its detached sender owns the bounded network delivery and acknowledgement.
|
|
17
18
|
// An explicit invocation still waits synchronously for the server response.
|
|
18
19
|
const HOOK_WALL_MS = 5_000;
|
|
19
|
-
const EXPLICIT_WALL_MS = 60_000;
|
|
20
|
+
export const EXPLICIT_WALL_MS = 60_000;
|
|
20
21
|
const REMOTE_COMMAND_MS = 750;
|
|
21
22
|
// The published CLI cannot import verifier/contracts outside its package root.
|
|
22
23
|
const hasOnlyCharacters = (value, allowed) => value.length > 0
|
|
@@ -79,50 +80,29 @@ function resolveOriginRepository(gitRoot) {
|
|
|
79
80
|
throw new PrecomputeFailure('No supported origin repository was found.');
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
|
-
|
|
83
|
+
/** The shared base rule (utils/verify-base.ts), bounded by the hook's deadline. */
|
|
84
|
+
function deriveBaseSha(gitRoot, deadline) {
|
|
83
85
|
try {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
catch {
|
|
90
|
-
// A clone does not have to carry origin/HEAD; ask the remote below.
|
|
86
|
+
return resolveVerifyBaseSha('HEAD', {
|
|
87
|
+
run: (args, options) => gitOutput(args, gitRoot, deadline, options?.remote
|
|
88
|
+
? { env: { ...process.env, GIT_TERMINAL_PROMPT: '0' }, timeoutCapMs: REMOTE_COMMAND_MS } : {}),
|
|
89
|
+
fetchOptions: () => ({ cwd: gitRoot, timeoutMs: Math.max(1, Math.min(remainingMs(deadline), REMOTE_COMMAND_MS)) }),
|
|
90
|
+
});
|
|
91
91
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const prefix = 'ref: refs/heads/';
|
|
97
|
-
const heads = remoteRefs.split('\n').map(line => line.split('\t'))
|
|
98
|
-
.filter(fields => fields.length === 2 && fields[1] === 'HEAD' && fields[0].startsWith(prefix))
|
|
99
|
-
.map(fields => fields[0].slice(prefix.length));
|
|
100
|
-
if (heads.length !== 1 || heads[0].length === 0) {
|
|
101
|
-
throw new PrecomputeFailure('Could not resolve the origin default branch.');
|
|
102
|
-
}
|
|
103
|
-
gitOutput(['check-ref-format', `refs/heads/${heads[0]}`], gitRoot, deadline);
|
|
104
|
-
return heads[0];
|
|
105
|
-
}
|
|
106
|
-
function deriveBaseSha(gitRoot, deadline) {
|
|
107
|
-
const defaultBranch = resolveOriginDefaultBranch(gitRoot, deadline);
|
|
108
|
-
const baseRef = resolveDiffBaseRef(defaultBranch, {
|
|
109
|
-
cwd: gitRoot,
|
|
110
|
-
timeoutMs: Math.max(1, Math.min(remainingMs(deadline), REMOTE_COMMAND_MS)),
|
|
111
|
-
});
|
|
112
|
-
const baseSha = gitOutput(['merge-base', 'HEAD', baseRef], gitRoot, deadline);
|
|
113
|
-
if (!isCommitSha(baseSha)) {
|
|
114
|
-
throw new PrecomputeFailure('Could not derive the base commit.');
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (error instanceof PrecomputeFailure)
|
|
94
|
+
throw error;
|
|
95
|
+
throw new PrecomputeFailure(error instanceof Error ? error.message : 'Could not derive the base commit.');
|
|
115
96
|
}
|
|
116
|
-
return baseSha;
|
|
117
97
|
}
|
|
118
98
|
// The -U0 scan text omits binary content and context, so it stays far below
|
|
119
99
|
// the uploaded patch; this only guards against git's 1 MiB default.
|
|
120
100
|
const SCAN_PATCH_MAX_BYTES = 256 * 1024 * 1024;
|
|
121
101
|
/**
|
|
122
102
|
* Withholds secret-bearing changes from the snapshot index before the tree is
|
|
123
|
-
* written, so the tree, work commit
|
|
124
|
-
*
|
|
125
|
-
*
|
|
103
|
+
* written, so the tree, work commit and uploaded patch all agree. A withheld
|
|
104
|
+
* path keeps its base version (or is absent when base has none). Every
|
|
105
|
+
* withheld path is named on stderr; secret values never are.
|
|
126
106
|
*/
|
|
127
107
|
function withholdSecretBearingChanges(gitRoot, baseSha, indexEnv, deadline) {
|
|
128
108
|
const changed = gitOutput(['diff', '--cached', '--no-renames', '--diff-filter=d', '--name-only', '-z', baseSha, '--'], gitRoot, deadline, { env: indexEnv, maxBuffer: SCAN_PATCH_MAX_BYTES, raw: true }).split('\0').filter(path => path.length > 0);
|
|
@@ -186,17 +166,40 @@ function deriveWorkCommit(gitRoot, baseSha, treeSha, deadline) {
|
|
|
186
166
|
}
|
|
187
167
|
// A clean checkout of the base already has the exact required identity.
|
|
188
168
|
if (baseSha === headSha && treeSha === headTreeSha)
|
|
189
|
-
return headSha;
|
|
169
|
+
return { workCommit: headSha, headSha, headTreeSha };
|
|
190
170
|
const workCommit = gitOutput(['commit-tree', treeSha, '-p', baseSha, '-m', 'front-half-snapshot'], gitRoot, deadline, { env: { ...process.env, ...FIXED_COMMIT_ENV } });
|
|
191
171
|
if (!isCommitSha(workCommit)) {
|
|
192
172
|
throw new PrecomputeFailure('Could not derive the working commit.');
|
|
193
173
|
}
|
|
194
|
-
return workCommit;
|
|
174
|
+
return { workCommit, headSha, headTreeSha };
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* CrawlSubmission.changeTitle (CRAWL-V1): HEAD's subject when HEAD differs
|
|
178
|
+
* from base, then " (with uncommitted changes)" when the captured tree differs
|
|
179
|
+
* from HEAD's; "Uncommitted changes" when HEAD is base. A subject too long for
|
|
180
|
+
* 200 characters is cut at a character boundary, never the suffix.
|
|
181
|
+
*/
|
|
182
|
+
function deriveChangeTitle(gitRoot, baseSha, treeSha, commits, deadline) {
|
|
183
|
+
if (commits.headSha === baseSha)
|
|
184
|
+
return 'Uncommitted changes';
|
|
185
|
+
// A commit with an empty message is named by its id, so the title is never empty.
|
|
186
|
+
const subject = gitOutput(['log', '-1', '--no-show-signature', '--encoding=UTF-8', '--format=%s', commits.headSha, '--'], gitRoot, deadline, { raw: true }).replace(/\n$/, '') || `Commit ${commits.headSha.slice(0, 12)}`;
|
|
187
|
+
const suffix = treeSha === commits.headTreeSha ? '' : ' (with uncommitted changes)';
|
|
188
|
+
let title = '';
|
|
189
|
+
for (const character of subject) {
|
|
190
|
+
if (title.length + character.length + suffix.length > CRAWL_TITLE_MAX_CHARS)
|
|
191
|
+
break;
|
|
192
|
+
title += character;
|
|
193
|
+
}
|
|
194
|
+
return title + suffix;
|
|
195
195
|
}
|
|
196
196
|
function slimInputHash() {
|
|
197
|
-
// Mirrors
|
|
197
|
+
// Mirrors front_half_cache_derive in blast-cache.sh for a slim bundle:
|
|
198
|
+
// printf '%s\0%s\0%s\0%s\0%s\0' slim agent-deferred none none crawl-blast-v1.
|
|
199
|
+
// The fifth field (CRAWL-V1 rule 4) keeps bundles written before
|
|
200
|
+
// crawl-blast.json existed from being restored for a crawl.
|
|
198
201
|
return createHash('sha256')
|
|
199
|
-
.update(Buffer.from('slim\0agent-deferred\0none\0none\0'))
|
|
202
|
+
.update(Buffer.from('slim\0agent-deferred\0none\0none\0crawl-blast-v1\0'))
|
|
200
203
|
.digest('hex');
|
|
201
204
|
}
|
|
202
205
|
async function buildCompressedPatch(gitRoot, baseSha, workCommit, deadline) {
|
|
@@ -262,7 +265,9 @@ export async function derivePrecomputeRequest(gitRoot, owner, repository, deadli
|
|
|
262
265
|
const observedAt = Date.now();
|
|
263
266
|
const baseSha = deriveBaseSha(gitRoot, deadline);
|
|
264
267
|
const treeSha = deriveWorktreeTreeSha(gitRoot, baseSha, deadline);
|
|
265
|
-
const
|
|
268
|
+
const commits = deriveWorkCommit(gitRoot, baseSha, treeSha, deadline);
|
|
269
|
+
const { workCommit } = commits;
|
|
270
|
+
const changeTitle = deriveChangeTitle(gitRoot, baseSha, treeSha, commits, deadline);
|
|
266
271
|
const patchGzip = await buildCompressedPatch(gitRoot, baseSha, workCommit, deadline);
|
|
267
272
|
const cacheKey = [
|
|
268
273
|
baseSha.slice(0, 12),
|
|
@@ -271,7 +276,6 @@ export async function derivePrecomputeRequest(gitRoot, owner, repository, deadli
|
|
|
271
276
|
'slim',
|
|
272
277
|
slimInputHash(),
|
|
273
278
|
].join('-');
|
|
274
|
-
const combinationSearch = captureCombinationSearchHook(gitRoot, `${owner}/${repository}`, baseSha, workCommit, deadline);
|
|
275
279
|
return {
|
|
276
280
|
treeSha,
|
|
277
281
|
request: {
|
|
@@ -283,7 +287,7 @@ export async function derivePrecomputeRequest(gitRoot, owner, repository, deadli
|
|
|
283
287
|
cacheKey,
|
|
284
288
|
patchGzBase64: patchGzip.toString('base64'),
|
|
285
289
|
patchSha256: createHash('sha256').update(patchGzip).digest('hex'),
|
|
286
|
-
...(
|
|
290
|
+
...(workCommit === baseSha ? {} : { crawl: { treeSha, changeTitle } }),
|
|
287
291
|
},
|
|
288
292
|
};
|
|
289
293
|
}
|
|
@@ -291,16 +295,17 @@ function precomputeResult(response) {
|
|
|
291
295
|
if (!response || typeof response !== 'object' || Array.isArray(response)) {
|
|
292
296
|
throw new PrecomputeFailure('The precompute service returned an invalid response.');
|
|
293
297
|
}
|
|
294
|
-
const { status, runId, walker: rawWalker,
|
|
298
|
+
const { status, runId, walker: rawWalker, crawl: rawCrawl } = response;
|
|
295
299
|
const walker = rawWalker === undefined ? undefined : readWalkerStatus(rawWalker);
|
|
296
|
-
const
|
|
300
|
+
const crawl = rawCrawl === undefined ? undefined : readCrawlHookStatus(rawCrawl);
|
|
301
|
+
const reported = { ...(walker === undefined ? {} : { walker }), ...(crawl === undefined ? {} : { crawl }) };
|
|
297
302
|
if (status === 'queued' && typeof runId === 'string' && isRunId(runId)) {
|
|
298
|
-
return { outcome: 'enqueued', runId, ...
|
|
303
|
+
return { outcome: 'enqueued', runId, ...reported };
|
|
299
304
|
}
|
|
300
305
|
if (status === 'cached')
|
|
301
|
-
return { outcome: 'cached', ...
|
|
306
|
+
return { outcome: 'cached', ...reported };
|
|
302
307
|
if (status === 'existing')
|
|
303
|
-
return { outcome: 'existing', ...
|
|
308
|
+
return { outcome: 'existing', ...reported };
|
|
304
309
|
throw new PrecomputeFailure('The precompute service returned an invalid response.');
|
|
305
310
|
}
|
|
306
311
|
/** One authenticated submission of the already captured request. No source reads or retries. */
|
|
@@ -325,6 +330,31 @@ function readWalkerStatus(value) {
|
|
|
325
330
|
}
|
|
326
331
|
throw new PrecomputeFailure('The precompute service returned an invalid walker status.');
|
|
327
332
|
}
|
|
333
|
+
const CRAWL_UNAVAILABLE_REASONS = ['not-configured', 'runner-unavailable', 'profile-ambiguous', 'superseded', 'enqueue-failed'];
|
|
334
|
+
function readCrawlHookStatus(value) {
|
|
335
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
336
|
+
const record = value;
|
|
337
|
+
if (Object.keys(record).length === 2) {
|
|
338
|
+
if ((record.status === 'queued' || record.status === 'existing')
|
|
339
|
+
&& typeof record.runId === 'string' && isRunId(record.runId))
|
|
340
|
+
return { status: record.status, runId: record.runId };
|
|
341
|
+
const reason = CRAWL_UNAVAILABLE_REASONS.find(known => known === record.reason);
|
|
342
|
+
if (record.status === 'unavailable' && reason !== undefined)
|
|
343
|
+
return { status: record.status, reason };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
throw new PrecomputeFailure('The precompute service returned an invalid crawl status.');
|
|
347
|
+
}
|
|
348
|
+
/** Why the service could not start a crawl, in plain words. */
|
|
349
|
+
export function crawlUnavailableText(reason) {
|
|
350
|
+
switch (reason) {
|
|
351
|
+
case 'not-configured': return 'this repository is not set up for crawling yet';
|
|
352
|
+
case 'runner-unavailable': return 'no machine that can crawl this repository is available right now';
|
|
353
|
+
case 'profile-ambiguous': return 'the machines that crawl this repository disagree about how to run it';
|
|
354
|
+
case 'superseded': return 'a newer stop in this repository came first';
|
|
355
|
+
case 'enqueue-failed': return 'the service could not queue it';
|
|
356
|
+
}
|
|
357
|
+
}
|
|
328
358
|
function walkerMetadataDirectory(gitRoot, deadline) {
|
|
329
359
|
const common = gitOutput(['rev-parse', '--git-common-dir'], gitRoot, deadline);
|
|
330
360
|
return join(isAbsolute(common) ? common : resolve(gitRoot, common), 'haystack');
|
|
@@ -361,7 +391,8 @@ function saveWalkerStatus(directory, request, walker) {
|
|
|
361
391
|
rmSync(temporary, { force: true });
|
|
362
392
|
}
|
|
363
393
|
}
|
|
364
|
-
|
|
394
|
+
/** The one-line error an explicit (non-hook) submission reports. */
|
|
395
|
+
export function normalModeFailure(error) {
|
|
365
396
|
if (error instanceof PrecomputeFailure)
|
|
366
397
|
return error;
|
|
367
398
|
if (error instanceof NotLoggedInError) {
|
|
@@ -389,7 +420,8 @@ function normalModeFailure(error) {
|
|
|
389
420
|
}
|
|
390
421
|
return new PrecomputeFailure('The precompute request failed.');
|
|
391
422
|
}
|
|
392
|
-
function printResult(
|
|
423
|
+
function printResult(result) {
|
|
424
|
+
const { outcome, runId, walker, crawl } = result;
|
|
393
425
|
if (outcome === 'enqueued') {
|
|
394
426
|
console.log(chalk.green(`Precompute request queued${runId ? ` (${runId})` : ''}.`));
|
|
395
427
|
}
|
|
@@ -409,10 +441,55 @@ function printResult(outcome, runId, walker, search) {
|
|
|
409
441
|
: 'Walker unavailable: the enqueue request failed.');
|
|
410
442
|
else
|
|
411
443
|
console.log('Walker status was not reported by the service.');
|
|
412
|
-
if (
|
|
413
|
-
console.log(`
|
|
414
|
-
else if (
|
|
415
|
-
console.log(`
|
|
444
|
+
if (crawl?.status === 'queued')
|
|
445
|
+
console.log(`Crawl queued (${crawl.runId}).`);
|
|
446
|
+
else if (crawl?.status === 'existing')
|
|
447
|
+
console.log(`A matching crawl already exists (${crawl.runId}).`);
|
|
448
|
+
else if (crawl?.status === 'unavailable')
|
|
449
|
+
console.log(`Crawl unavailable: ${crawlUnavailableText(crawl.reason)}.`);
|
|
450
|
+
else
|
|
451
|
+
console.log('Crawl status was not reported by the service.');
|
|
452
|
+
}
|
|
453
|
+
/** The hook, an explicit precompute and `haystack verify` all capture here.
|
|
454
|
+
* The repository defaults to origin's, as the hook's does. */
|
|
455
|
+
export async function captureCheckout(deadline, repository) {
|
|
456
|
+
const gitRoot = findGitRoot();
|
|
457
|
+
if (!gitRoot) {
|
|
458
|
+
throw new PrecomputeFailure('Run this command from a git repository.');
|
|
459
|
+
}
|
|
460
|
+
const metadataDirectory = walkerMetadataDirectory(gitRoot, deadline);
|
|
461
|
+
const target = repository ?? resolveOriginRepository(gitRoot);
|
|
462
|
+
const derivation = await derivePrecomputeRequest(gitRoot, target.owner, target.repository, deadline);
|
|
463
|
+
return { metadataDirectory, derivation };
|
|
464
|
+
}
|
|
465
|
+
/** The explicit (non-hook) submission of a capture: one synchronous
|
|
466
|
+
* authenticated POST, bounded by the deadline, and its walker status saved.
|
|
467
|
+
* `haystack verify precompute` and `haystack verify` both submit here. */
|
|
468
|
+
export async function postPrecomputeCapture(capture, token, deadline) {
|
|
469
|
+
const controller = new AbortController();
|
|
470
|
+
const fetchTimer = setTimeout(() => controller.abort(), remainingMs(deadline));
|
|
471
|
+
let response;
|
|
472
|
+
try {
|
|
473
|
+
response = await haystackJson('/api/agent/cloud-verifier/front-half', token, {
|
|
474
|
+
method: 'POST',
|
|
475
|
+
headers: { 'Content-Type': 'application/json' },
|
|
476
|
+
body: JSON.stringify(capture.derivation.request),
|
|
477
|
+
signal: controller.signal,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
finally {
|
|
481
|
+
clearTimeout(fetchTimer);
|
|
482
|
+
}
|
|
483
|
+
const result = precomputeResult(response);
|
|
484
|
+
if (result.walker) {
|
|
485
|
+
try {
|
|
486
|
+
saveWalkerStatus(capture.metadataDirectory, capture.derivation.request, result.walker);
|
|
487
|
+
}
|
|
488
|
+
catch {
|
|
489
|
+
throw new PrecomputeFailure('Precompute response received, but its walker status could not be saved.');
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return result;
|
|
416
493
|
}
|
|
417
494
|
export async function verifyPrecomputeCommand(options) {
|
|
418
495
|
const startedAt = Date.now();
|
|
@@ -420,61 +497,29 @@ export async function verifyPrecomputeCommand(options) {
|
|
|
420
497
|
const deadline = startedAt + totalWallMs;
|
|
421
498
|
let outcome = 'error';
|
|
422
499
|
try {
|
|
423
|
-
const
|
|
424
|
-
if (!gitRoot) {
|
|
425
|
-
throw new PrecomputeFailure('Run this command from a git repository.');
|
|
426
|
-
}
|
|
427
|
-
const metadataDirectory = walkerMetadataDirectory(gitRoot, deadline);
|
|
428
|
-
const repository = resolveOriginRepository(gitRoot);
|
|
429
|
-
const derivation = await derivePrecomputeRequest(gitRoot, repository.owner, repository.repository, deadline);
|
|
500
|
+
const capture = await captureCheckout(deadline);
|
|
430
501
|
if (options.hook) {
|
|
431
502
|
remainingMs(deadline);
|
|
432
|
-
await startPrecomputeDelivery(metadataDirectory, derivation.request);
|
|
503
|
+
await startPrecomputeDelivery(capture.metadataDirectory, capture.derivation.request);
|
|
433
504
|
outcome = 'pending';
|
|
434
505
|
return;
|
|
435
506
|
}
|
|
436
507
|
let token;
|
|
437
508
|
try {
|
|
438
509
|
const auth = await resolveAuthContext({
|
|
439
|
-
owner:
|
|
440
|
-
repo:
|
|
510
|
+
owner: capture.derivation.request.owner,
|
|
511
|
+
repo: capture.derivation.request.repository,
|
|
441
512
|
});
|
|
442
513
|
token = auth.token;
|
|
443
514
|
}
|
|
444
515
|
catch (error) {
|
|
445
|
-
if (error instanceof NotLoggedInError)
|
|
516
|
+
if (error instanceof NotLoggedInError)
|
|
446
517
|
outcome = 'skipped_no_auth';
|
|
447
|
-
if (options.hook)
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
518
|
throw error;
|
|
451
519
|
}
|
|
452
|
-
const
|
|
453
|
-
const fetchTimer = setTimeout(() => controller.abort(), remainingMs(deadline));
|
|
454
|
-
let response;
|
|
455
|
-
try {
|
|
456
|
-
response = await haystackJson('/api/agent/cloud-verifier/front-half', token, {
|
|
457
|
-
method: 'POST',
|
|
458
|
-
headers: { 'Content-Type': 'application/json' },
|
|
459
|
-
body: JSON.stringify(derivation.request),
|
|
460
|
-
signal: controller.signal,
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
finally {
|
|
464
|
-
clearTimeout(fetchTimer);
|
|
465
|
-
}
|
|
466
|
-
const result = precomputeResult(response);
|
|
520
|
+
const result = await postPrecomputeCapture(capture, token, deadline);
|
|
467
521
|
outcome = result.outcome;
|
|
468
|
-
|
|
469
|
-
try {
|
|
470
|
-
saveWalkerStatus(metadataDirectory, derivation.request, result.walker);
|
|
471
|
-
}
|
|
472
|
-
catch {
|
|
473
|
-
throw new PrecomputeFailure('Precompute response received, but its walker status could not be saved.');
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
if (!options.hook)
|
|
477
|
-
printResult(outcome, result.runId, result.walker, result.search);
|
|
522
|
+
printResult(result);
|
|
478
523
|
}
|
|
479
524
|
catch (error) {
|
|
480
525
|
if (!options.hook)
|