@moda-ai/cli 1.13.8 → 1.13.10
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.
|
@@ -214,10 +214,18 @@ function hydrateArtifactBody(artifact, rootDir) {
|
|
|
214
214
|
}
|
|
215
215
|
const evidenceLine = firstEvidenceLineNumber(artifact.evidence);
|
|
216
216
|
const evidencePath = firstEvidencePath(artifact);
|
|
217
|
-
if (evidencePath
|
|
217
|
+
if (evidencePath) {
|
|
218
218
|
const text = readRepoRelativeText(rootDir, evidencePath);
|
|
219
219
|
if (text !== undefined) {
|
|
220
|
-
|
|
220
|
+
if (evidenceLine !== undefined) {
|
|
221
|
+
return extractSnippetAroundLine(text, evidenceLine, artifact.type === 'tool' ? 30 : 80);
|
|
222
|
+
}
|
|
223
|
+
// No line number (common for agent/hosted analyst citations): fall back to
|
|
224
|
+
// the whole evidence file so the body is still captured in full.
|
|
225
|
+
if (artifact.type === 'prompt' || isDedicatedToolFilePath(evidencePath))
|
|
226
|
+
return text;
|
|
227
|
+
if (artifact.type === 'tool')
|
|
228
|
+
return extractToolBodyFromText(text, artifact.name) ?? text;
|
|
221
229
|
}
|
|
222
230
|
}
|
|
223
231
|
return undefined;
|
package/dist/harness-remote.js
CHANGED
|
@@ -97,15 +97,34 @@ export async function runRemoteHarnessAnalyst(options) {
|
|
|
97
97
|
});
|
|
98
98
|
writeHumanProgress(context, `Moda Analyst (remote): analysis run ${runId} accepted; waiting for results...`);
|
|
99
99
|
const final = await pollRemoteAnalyze({ ingestUrl, apiKey, runId, context });
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
100
|
+
// A run that returned no report at all is a hard failure (timeout, or the
|
|
101
|
+
// hosted agent crashed before producing anything) — nothing to recover.
|
|
102
|
+
if (!final.report) {
|
|
103
|
+
throw remoteAnalyzeHardFailure(final, runId);
|
|
104
|
+
}
|
|
105
|
+
// A `failed` run only qualifies for client-side recovery when the server tells
|
|
106
|
+
// us the failure was its own REPORT VALIDATION (it produced a report but the
|
|
107
|
+
// report-contract check failed). That is the one class the client can recover:
|
|
108
|
+
// the uploaded snapshot is truncated/sampled, so the hosted analyst commonly
|
|
109
|
+
// leaves prompt/tool `body` (or citation lines) empty, and the client — which
|
|
110
|
+
// has the full local repo — can backfill those source-dependent gaps and
|
|
111
|
+
// re-validate. Any other `failed` run (a crashed/aborted agent, which carries
|
|
112
|
+
// an `error` string and no structured validation verdict) stays a hard failure
|
|
113
|
+
// even if a partial report leaked through, so we never write or sync a report
|
|
114
|
+
// from a hosted run the server aborted for a reason the client cannot repair.
|
|
115
|
+
if (final.status === 'failed' && !serverFailedReportValidation(final)) {
|
|
116
|
+
throw remoteAnalyzeHardFailure(final, runId);
|
|
117
|
+
}
|
|
118
|
+
// The hosted analyst returned a report that failed SERVER-side validation —
|
|
119
|
+
// most often because it enumerated prompt/tool artifacts but left their `body`
|
|
120
|
+
// empty, or because the uploaded snapshot was truncated so the server never
|
|
121
|
+
// saw the source. The client has the full local repo, so `repairExternalAnalystReport`
|
|
122
|
+
// backfills those bodies from disk here and we re-validate locally before
|
|
123
|
+
// deciding the outcome (a report that stays invalid after repair still exits 1
|
|
124
|
+
// and is never synced). This lets `--remote` succeed even when the server
|
|
125
|
+
// could not complete the report.
|
|
126
|
+
if (final.status === 'failed') {
|
|
127
|
+
writeHumanProgress(context, 'Moda Analyst (remote): server validation incomplete; completing artifact bodies from local source...');
|
|
109
128
|
}
|
|
110
129
|
let report = repairExternalAnalystReport(normalizeHarnessReport(final.report), rootDir);
|
|
111
130
|
if (snapshot.truncated) {
|
|
@@ -145,6 +164,33 @@ export async function runRemoteHarnessAnalyst(options) {
|
|
|
145
164
|
});
|
|
146
165
|
return { exitCode: validation.status === 'pass' ? 0 : 1 };
|
|
147
166
|
}
|
|
167
|
+
/** Build the terminal error for a remote run that cannot be recovered client-side. */
|
|
168
|
+
function remoteAnalyzeHardFailure(final, runId) {
|
|
169
|
+
const detail = final.status === 'failed'
|
|
170
|
+
? `Remote harness analysis failed: ${final.error ?? 'unknown error'}.`
|
|
171
|
+
: `Remote harness analysis did not finish before the timeout (last status: ${final.status}).`;
|
|
172
|
+
return new CliInputError(detail, [
|
|
173
|
+
`Retry \`moda harness analyze --remote\`${runId ? ` (run ${runId})` : ''}.`,
|
|
174
|
+
'Or run `moda harness analyze --analyst=local-scan` to analyze locally.',
|
|
175
|
+
'Increase MODA_HARNESS_REMOTE_TIMEOUT_MS if large repos need longer.',
|
|
176
|
+
].join('\n'));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* True when a `failed` hosted run failed specifically because its own report
|
|
180
|
+
* validation failed — the only server failure the client can recover. The hosted
|
|
181
|
+
* service attaches a structured validation verdict (`{ status: 'fail', ... }`)
|
|
182
|
+
* only when it produced a report that failed the report-contract check
|
|
183
|
+
* (`apps/moda-harness-analyze` server.ts); an operational failure (crashed agent)
|
|
184
|
+
* reports an `error` string with no validation object. Gating recovery on this
|
|
185
|
+
* verdict keeps crashed/aborted runs as hard failures, and the mandatory
|
|
186
|
+
* client-side re-validation still rejects any report that stays invalid after
|
|
187
|
+
* body/citation repair.
|
|
188
|
+
*/
|
|
189
|
+
function serverFailedReportValidation(final) {
|
|
190
|
+
const { validation } = final;
|
|
191
|
+
return typeof validation === 'object' && validation !== null && !Array.isArray(validation)
|
|
192
|
+
&& validation.status === 'fail';
|
|
193
|
+
}
|
|
148
194
|
/** Map CLI flags to the optional server-side analyze options in the wire contract. */
|
|
149
195
|
function buildRemoteAnalyzeOptions(flags) {
|
|
150
196
|
const options = {};
|
package/dist/harness.js
CHANGED
|
@@ -730,6 +730,10 @@ function parseHarnessReportCandidate(candidate) {
|
|
|
730
730
|
export function repairExternalAnalystReport(report, rootDir) {
|
|
731
731
|
const normalized = normalizeHarnessReport(report);
|
|
732
732
|
repairCitationLines(normalized, rootDir);
|
|
733
|
+
// Agent/hosted analysts reliably enumerate prompt/tool artifacts but often omit
|
|
734
|
+
// the verbatim `body`. Backfill it from source the same way local-scan does so
|
|
735
|
+
// external reports satisfy the prompt/tool body requirement.
|
|
736
|
+
hydratePromptAndToolBodies(normalized.graph, rootDir);
|
|
733
737
|
repairReportSummaryCounts(normalized);
|
|
734
738
|
return normalized;
|
|
735
739
|
}
|
package/dist/init/tenant.d.ts
CHANGED
|
@@ -4,6 +4,13 @@ interface BootstrapResult {
|
|
|
4
4
|
tenantName: string;
|
|
5
5
|
tenantSlug: string;
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Classify a failed /api/cli/context (or bootstrap) response so moda init
|
|
9
|
+
* distinguishes retryable infra outages (Postgres pool exhaustion → 503
|
|
10
|
+
* `database_unavailable`) from auth failures. Auth itself can succeed from
|
|
11
|
+
* a cached moda_ct_ token even when this hydrate call 500s (MODA-742).
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatControlPlaneFetchError(action: string, status: number, body: string): string;
|
|
7
14
|
export interface TenantKeyResult extends BootstrapResult {
|
|
8
15
|
/**
|
|
9
16
|
* True when a previously stored key for the selected tenant passed the
|
package/dist/init/tenant.js
CHANGED
|
@@ -8,6 +8,37 @@ import { authFetch, getCachedLoginContext } from '../auth.js';
|
|
|
8
8
|
import { loadCliConfig, resolveIngestUrl, resolveModaBaseUrl, saveCliConfig, } from '../config.js';
|
|
9
9
|
import { getModaDir, resolveApiKeyFromActiveProfile, resolveTenantIdFromActiveProfile, writeSecretJsonFile, } from '../profiles.js';
|
|
10
10
|
import { CliAccessError, CliInputError, CliInputRequiredError } from '../errors.js';
|
|
11
|
+
/**
|
|
12
|
+
* Classify a failed /api/cli/context (or bootstrap) response so moda init
|
|
13
|
+
* distinguishes retryable infra outages (Postgres pool exhaustion → 503
|
|
14
|
+
* `database_unavailable`) from auth failures. Auth itself can succeed from
|
|
15
|
+
* a cached moda_ct_ token even when this hydrate call 500s (MODA-742).
|
|
16
|
+
*/
|
|
17
|
+
export function formatControlPlaneFetchError(action, status, body) {
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = JSON.parse(body);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Non-JSON body — fall through to generic formatting.
|
|
24
|
+
}
|
|
25
|
+
const isDbUnavailable = status === 503 &&
|
|
26
|
+
(parsed?.error === 'database_unavailable' ||
|
|
27
|
+
parsed?.retryable === true ||
|
|
28
|
+
/database_unavailable|too many database connections|rds_reserved|connection pool/i.test(body));
|
|
29
|
+
if (isDbUnavailable) {
|
|
30
|
+
return (`Failed to ${action} (${status}): Moda control-plane database is temporarily unavailable ` +
|
|
31
|
+
`(connection pool exhausted). This is not an authentication failure — your session token ` +
|
|
32
|
+
`is fine. Retry in a few seconds.` +
|
|
33
|
+
(parsed?.message ? ` (${parsed.message})` : ''));
|
|
34
|
+
}
|
|
35
|
+
if (status >= 500 &&
|
|
36
|
+
/too many database connections|rds_reserved|remaining connection slots/i.test(body)) {
|
|
37
|
+
return (`Failed to ${action} (${status}): Moda control-plane database ran out of connections. ` +
|
|
38
|
+
`This is a retryable infra outage, not an auth failure. Retry in a few seconds. Body: ${body}`);
|
|
39
|
+
}
|
|
40
|
+
return `Failed to ${action} (${status}): ${body}`;
|
|
41
|
+
}
|
|
11
42
|
// Mirrors the backend's checkAdminAccess: only the viewer role is denied.
|
|
12
43
|
const ADMIN_ACCESS_MESSAGE = 'You need admin or owner access to create API keys.';
|
|
13
44
|
const ADMIN_ACCESS_GUIDANCE = 'Ask your organization owner to upgrade your role.';
|
|
@@ -250,7 +281,7 @@ export async function selectTenantAndCreateKey(options = {}) {
|
|
|
250
281
|
const contextRes = await authFetch('/api/cli/context', {}, authOptions);
|
|
251
282
|
if (!contextRes.ok) {
|
|
252
283
|
const body = await contextRes.text();
|
|
253
|
-
throw new Error(
|
|
284
|
+
throw new Error(formatControlPlaneFetchError('fetch context', contextRes.status, body));
|
|
254
285
|
}
|
|
255
286
|
ctx = (await contextRes.json());
|
|
256
287
|
}
|
|
@@ -368,7 +399,7 @@ export async function selectTenantAndCreateKey(options = {}) {
|
|
|
368
399
|
}
|
|
369
400
|
if (!bootstrapRes.ok) {
|
|
370
401
|
const body = await bootstrapRes.text();
|
|
371
|
-
throw new Error(
|
|
402
|
+
throw new Error(formatControlPlaneFetchError('create API key', bootstrapRes.status, body));
|
|
372
403
|
}
|
|
373
404
|
result = (await bootstrapRes.json());
|
|
374
405
|
clearBootstrapIdempotencyKey(selected.id, authOptions);
|
package/package.json
CHANGED