@moda-ai/cli 1.22.1 → 1.23.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/cli-c4se7a5d.js +762 -0
- package/dist/cli-mnqa4rp6.js +1563 -0
- package/dist/cli-stmjdgvy.js +2842 -0
- package/dist/cli-y55bgg14.js +6980 -0
- package/dist/cli-ysegdrm5.js +286 -0
- package/dist/cli.js +4779 -1159
- package/dist/harness-github-actions-7t3cncnt.js +95 -0
- package/dist/index-4ser01zr.js +2769 -0
- package/dist/provision-hm1ppwj6.js +74 -0
- package/package.json +3 -2
- package/skills/integration/index.json +2 -2
- package/dist/agent-schemas.d.ts +0 -3
- package/dist/agent-schemas.js +0 -358
- package/dist/agent.d.ts +0 -5
- package/dist/agent.js +0 -477
- package/dist/analyst-tui.d.ts +0 -81
- package/dist/analyst-tui.js +0 -314
- package/dist/api-client.d.ts +0 -46
- package/dist/api-client.js +0 -120
- package/dist/ask-stream.d.ts +0 -146
- package/dist/ask-stream.js +0 -473
- package/dist/auth-storage.d.ts +0 -35
- package/dist/auth-storage.js +0 -159
- package/dist/auth.d.ts +0 -50
- package/dist/auth.js +0 -694
- package/dist/cli-session.d.ts +0 -26
- package/dist/cli-session.js +0 -169
- package/dist/cli.d.ts +0 -44
- package/dist/command-context.d.ts +0 -62
- package/dist/command-context.js +0 -57
- package/dist/command-registry.d.ts +0 -22
- package/dist/command-registry.js +0 -15
- package/dist/config.d.ts +0 -60
- package/dist/config.js +0 -110
- package/dist/doctor.d.ts +0 -79
- package/dist/doctor.js +0 -1182
- package/dist/errors.d.ts +0 -50
- package/dist/errors.js +0 -64
- package/dist/exit-funnel.d.ts +0 -14
- package/dist/exit-funnel.js +0 -53
- package/dist/harness-artifact-bodies.d.ts +0 -28
- package/dist/harness-artifact-bodies.js +0 -276
- package/dist/harness-candidates.d.ts +0 -77
- package/dist/harness-candidates.js +0 -482
- package/dist/harness-remote.d.ts +0 -135
- package/dist/harness-remote.js +0 -540
- package/dist/harness-report.d.ts +0 -145
- package/dist/harness-report.js +0 -1579
- package/dist/harness-sync-limits.d.ts +0 -8
- package/dist/harness-sync-limits.js +0 -21
- package/dist/harness.d.ts +0 -463
- package/dist/harness.js +0 -4009
- package/dist/init/index.d.ts +0 -78
- package/dist/init/index.js +0 -1442
- package/dist/init/mcp.d.ts +0 -2
- package/dist/init/mcp.js +0 -27
- package/dist/init/progress-board.d.ts +0 -80
- package/dist/init/progress-board.js +0 -225
- package/dist/init/rules.d.ts +0 -6
- package/dist/init/rules.js +0 -171
- package/dist/init/sdk-catalog.d.ts +0 -10
- package/dist/init/sdk-catalog.js +0 -80
- package/dist/init/sdk-guide.d.ts +0 -19
- package/dist/init/sdk-guide.js +0 -185
- package/dist/init/sdk-integration.d.ts +0 -53
- package/dist/init/sdk-integration.js +0 -835
- package/dist/init/sdk-protocol.d.ts +0 -135
- package/dist/init/sdk-protocol.js +0 -152
- package/dist/init/sdk-verification.d.ts +0 -27
- package/dist/init/sdk-verification.js +0 -123
- package/dist/init/tenant.d.ts +0 -67
- package/dist/init/tenant.js +0 -421
- package/dist/intelligence-schema.d.ts +0 -349
- package/dist/intelligence-schema.js +0 -174
- package/dist/keys.d.ts +0 -47
- package/dist/keys.js +0 -136
- package/dist/manifest.d.ts +0 -5
- package/dist/manifest.js +0 -114
- package/dist/markdown-terminal.d.ts +0 -49
- package/dist/markdown-terminal.js +0 -212
- package/dist/output.d.ts +0 -191
- package/dist/output.js +0 -624
- package/dist/production-intelligence.d.ts +0 -175
- package/dist/production-intelligence.js +0 -1430
- package/dist/profile-commands.d.ts +0 -10
- package/dist/profile-commands.js +0 -342
- package/dist/profiles.d.ts +0 -112
- package/dist/profiles.js +0 -573
- package/dist/prompts-ab.d.ts +0 -71
- package/dist/prompts-ab.js +0 -313
- package/dist/prompts-propose.d.ts +0 -15
- package/dist/prompts-propose.js +0 -212
- package/dist/prompts.d.ts +0 -58
- package/dist/prompts.js +0 -418
- package/dist/provision.d.ts +0 -28
- package/dist/provision.js +0 -104
- package/dist/redact.d.ts +0 -2
- package/dist/redact.js +0 -90
- package/dist/schemas.d.ts +0 -225
- package/dist/schemas.js +0 -126
- package/dist/skills-bundled.d.ts +0 -35
- package/dist/skills-bundled.js +0 -157
- package/dist/skills-manifest.d.ts +0 -33
- package/dist/skills-manifest.js +0 -244
- package/dist/skills.d.ts +0 -57
- package/dist/skills.js +0 -783
- package/dist/sync.d.ts +0 -5
- package/dist/sync.js +0 -36
- package/dist/telemetry.d.ts +0 -56
- package/dist/telemetry.js +0 -167
- package/dist/transforms.d.ts +0 -25
- package/dist/transforms.js +0 -102
- package/dist/tui.d.ts +0 -48
- package/dist/tui.js +0 -393
- package/dist/update-check.d.ts +0 -31
- package/dist/update-check.js +0 -141
- package/dist/workspace.d.ts +0 -12
- package/dist/workspace.js +0 -1294
package/dist/output.js
DELETED
|
@@ -1,624 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { CliAccessError, CliAuthError, CliCancellationError, CliInputError, CliInputRequiredError, isApiLikeError, isNetworkLikeError, } from './errors.js';
|
|
3
|
-
import { redactDeep } from './redact.js';
|
|
4
|
-
export const AGENT_SCHEMA_VERSION = 'moda.agent.v1';
|
|
5
|
-
export const AGENT_EVENT_SCHEMA_VERSION = 'moda.agent_event.v1';
|
|
6
|
-
/**
|
|
7
|
-
* One-line feedback nudge stamped into every non-feedback success envelope's
|
|
8
|
-
* `meta.tip` (agents are the CLI's most common callers, and the envelope is
|
|
9
|
-
* where they actually look). Deliberately conditional wording so agents flag
|
|
10
|
-
* genuine observations instead of habitually running the command. The
|
|
11
|
-
* human-TTY twin is printed on stderr by the dispatcher in cli.ts.
|
|
12
|
-
*/
|
|
13
|
-
export const FEEDBACK_TIP = 'Help us improve! If a result in this response looks wrong or is missing data, flag it: moda feedback "<describe what looked wrong>" [--category=...] [--conversation-id=... | --cluster-id=... | --tool-name=...]. Only submit genuine observations.';
|
|
14
|
-
/**
|
|
15
|
-
* Stable process exit codes that an external agent can branch on:
|
|
16
|
-
* 0 ok — grounded answer from Moda Cloud intelligence.
|
|
17
|
-
* 3 degraded — answer produced from a local/degraded fallback (lower trust).
|
|
18
|
-
* 1 error — command/input/unexpected failure (see envelope `errors[]`).
|
|
19
|
-
* 4 auth — login or token renewal required before retrying.
|
|
20
|
-
* 5 input — non-interactive caller must choose and re-invoke.
|
|
21
|
-
* 130 cancel — interrupted or the output consumer closed the pipe.
|
|
22
|
-
* `EXIT_DEGRADED` is intentionally distinct so a caller can tell a degraded
|
|
23
|
-
* answer apart from both success and hard failure without parsing the payload.
|
|
24
|
-
*/
|
|
25
|
-
export const EXIT_OK = 0;
|
|
26
|
-
export const EXIT_ERROR = 1;
|
|
27
|
-
export const EXIT_DEGRADED = 3;
|
|
28
|
-
export const EXIT_AUTH_REQUIRED = 4;
|
|
29
|
-
export const EXIT_INPUT_REQUIRED = 5;
|
|
30
|
-
export const EXIT_CANCELLED = 130;
|
|
31
|
-
export const AGENT_ERROR_CODES = [
|
|
32
|
-
'input_error',
|
|
33
|
-
'api_error',
|
|
34
|
-
'network_error',
|
|
35
|
-
'auth_required',
|
|
36
|
-
'input_required',
|
|
37
|
-
'cancelled',
|
|
38
|
-
'command_error',
|
|
39
|
-
'access_denied',
|
|
40
|
-
'unknown_error',
|
|
41
|
-
'cloud_unavailable',
|
|
42
|
-
];
|
|
43
|
-
const FALLBACK_RUN_ID = randomUUID();
|
|
44
|
-
let fallbackSeq = 0;
|
|
45
|
-
let stdoutClosed = false;
|
|
46
|
-
let stderrClosed = false;
|
|
47
|
-
process.stdout.on('error', (error) => {
|
|
48
|
-
if (error.code === 'EPIPE') {
|
|
49
|
-
stdoutClosed = true;
|
|
50
|
-
process.exitCode = EXIT_CANCELLED;
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
throw error;
|
|
54
|
-
});
|
|
55
|
-
process.stderr.on('error', (error) => {
|
|
56
|
-
if (error.code === 'EPIPE') {
|
|
57
|
-
stderrClosed = true;
|
|
58
|
-
process.exitCode = EXIT_CANCELLED;
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
throw error;
|
|
62
|
-
});
|
|
63
|
-
/**
|
|
64
|
-
* True once an EPIPE from a closed stdout/stderr consumer has been observed.
|
|
65
|
-
* The stream error listeners above own this state; expose it so callers can
|
|
66
|
-
* detect consumer-cancelled runs explicitly instead of sniffing the global
|
|
67
|
-
* `process.exitCode`.
|
|
68
|
-
*/
|
|
69
|
-
export function wasOutputCancelled() {
|
|
70
|
-
return stdoutClosed || stderrClosed;
|
|
71
|
-
}
|
|
72
|
-
export function safeWriteStdout(text) {
|
|
73
|
-
if (stdoutClosed)
|
|
74
|
-
return false;
|
|
75
|
-
try {
|
|
76
|
-
if (process.env.NODE_ENV === 'test') {
|
|
77
|
-
console.log(text.endsWith('\n') ? text.slice(0, -1) : text);
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
process.stdout.write(text);
|
|
81
|
-
}
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
if (error.code === 'EPIPE') {
|
|
86
|
-
stdoutClosed = true;
|
|
87
|
-
process.exitCode = EXIT_CANCELLED;
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
export function safeWriteStderr(text) {
|
|
94
|
-
if (stderrClosed)
|
|
95
|
-
return false;
|
|
96
|
-
try {
|
|
97
|
-
if (process.env.NODE_ENV === 'test') {
|
|
98
|
-
console.error(text.endsWith('\n') ? text.slice(0, -1) : text);
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
process.stderr.write(text);
|
|
102
|
-
}
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
if (error.code === 'EPIPE') {
|
|
107
|
-
stderrClosed = true;
|
|
108
|
-
process.exitCode = EXIT_CANCELLED;
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
throw error;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Output mode requested by explicit flags only, or `undefined` when the caller
|
|
116
|
-
* did not pick one. Explicit flags always win over auto-detection.
|
|
117
|
-
*/
|
|
118
|
-
function explicitModeFromFlags(flags) {
|
|
119
|
-
if (flags.agent === 'true' && (flags.stream === 'true' || flags.format === 'ndjson'))
|
|
120
|
-
return 'agent-stream';
|
|
121
|
-
if (flags.agent === 'true')
|
|
122
|
-
return 'agent';
|
|
123
|
-
if (flags.json === 'true')
|
|
124
|
-
return 'json';
|
|
125
|
-
if (flags.human === 'true' || flags.prose === 'true')
|
|
126
|
-
return 'human';
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
/** Flag-only resolution (default human). Kept for callers that only have flags. */
|
|
130
|
-
export function outputModeFromFlags(flags) {
|
|
131
|
-
return explicitModeFromFlags(flags) ?? 'human';
|
|
132
|
-
}
|
|
133
|
-
function outputModeFromEnv(env) {
|
|
134
|
-
const fmt = (env.MODA_FORMAT ?? '').trim().toLowerCase();
|
|
135
|
-
if (fmt === 'human' || fmt === 'json' || fmt === 'agent')
|
|
136
|
-
return fmt;
|
|
137
|
-
if (fmt === 'agent-stream' || fmt === 'ndjson')
|
|
138
|
-
return 'agent-stream';
|
|
139
|
-
return undefined;
|
|
140
|
-
}
|
|
141
|
-
function truthyEnv(value) {
|
|
142
|
-
return value != null && value !== '' && value !== '0' && value.toLowerCase() !== 'false';
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* True when the process appears to be driven by an agent / automation rather
|
|
146
|
-
* than an interactive human (Claude Code, Cursor, generic CI, or an explicit
|
|
147
|
-
* `MODA_AGENT` opt-in). Such callers get machine-parseable output by default.
|
|
148
|
-
*/
|
|
149
|
-
function isAgentEnvironment(env) {
|
|
150
|
-
return (truthyEnv(env.CLAUDECODE) ||
|
|
151
|
-
truthyEnv(env.CURSOR) ||
|
|
152
|
-
truthyEnv(env.CI) ||
|
|
153
|
-
truthyEnv(env.MODA_AGENT));
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* Resolve the effective output mode for a real CLI invocation, in priority
|
|
157
|
-
* order: explicit flags -> `MODA_FORMAT` -> agent-environment detection ->
|
|
158
|
-
* TTY detection (piped/redirected => agent JSON) -> human prose. This is what
|
|
159
|
-
* makes the CLI agent-safe: a piped/automated caller gets structured output
|
|
160
|
-
* with zero flags, while a human at a terminal still gets prose.
|
|
161
|
-
*/
|
|
162
|
-
export function resolveOutputMode(flags, opts = {}) {
|
|
163
|
-
const explicit = explicitModeFromFlags(flags);
|
|
164
|
-
if (explicit)
|
|
165
|
-
return explicit;
|
|
166
|
-
const env = opts.env ?? process.env;
|
|
167
|
-
const fromEnv = outputModeFromEnv(env);
|
|
168
|
-
if (fromEnv)
|
|
169
|
-
return fromEnv;
|
|
170
|
-
if (isAgentEnvironment(env))
|
|
171
|
-
return 'agent';
|
|
172
|
-
const isTTY = opts.isTTY ?? Boolean(process.stdout.isTTY);
|
|
173
|
-
if (!isTTY)
|
|
174
|
-
return 'agent';
|
|
175
|
-
return 'human';
|
|
176
|
-
}
|
|
177
|
-
export function createCliOutput(options) {
|
|
178
|
-
const state = {
|
|
179
|
-
runId: options.runId ?? randomUUID(),
|
|
180
|
-
seq: 0,
|
|
181
|
-
started: false,
|
|
182
|
-
terminal: false,
|
|
183
|
-
startedAt: Date.now(),
|
|
184
|
-
lastEventAt: Date.now(),
|
|
185
|
-
};
|
|
186
|
-
const emitEvent = (event) => {
|
|
187
|
-
if (options.quiet || options.mode !== 'agent-stream')
|
|
188
|
-
return;
|
|
189
|
-
if (state.terminal)
|
|
190
|
-
return;
|
|
191
|
-
if (event.event === 'started' && state.started)
|
|
192
|
-
return;
|
|
193
|
-
if (event.event === 'started')
|
|
194
|
-
state.started = true;
|
|
195
|
-
if (isTerminalEvent(event.event)) {
|
|
196
|
-
state.terminal = true;
|
|
197
|
-
clearHeartbeat(state);
|
|
198
|
-
}
|
|
199
|
-
state.lastEventAt = Date.now();
|
|
200
|
-
safeWriteStdout(`${JSON.stringify(createAgentEvent(options.command, event, state))}\n`);
|
|
201
|
-
};
|
|
202
|
-
const ensureStarted = () => {
|
|
203
|
-
if (options.mode !== 'agent-stream' || state.started || options.quiet)
|
|
204
|
-
return;
|
|
205
|
-
startHeartbeat(options.command, state);
|
|
206
|
-
emitEvent({
|
|
207
|
-
event: 'started',
|
|
208
|
-
phase: options.command,
|
|
209
|
-
message: `Starting ${options.command}.`,
|
|
210
|
-
elapsed_ms: 0,
|
|
211
|
-
});
|
|
212
|
-
};
|
|
213
|
-
const writeEnvelope = (data, writeOptions = {}) => {
|
|
214
|
-
return createAgentEnvelope({
|
|
215
|
-
command: options.command,
|
|
216
|
-
status: writeOptions.status === 'error'
|
|
217
|
-
? 'error'
|
|
218
|
-
: writeOptions.status === 'degraded'
|
|
219
|
-
? 'degraded'
|
|
220
|
-
: 'ok',
|
|
221
|
-
data,
|
|
222
|
-
meta: {
|
|
223
|
-
...(writeOptions.meta ?? {}),
|
|
224
|
-
run_id: state.runId,
|
|
225
|
-
// No tip on the feedback command itself; it would point at the
|
|
226
|
-
// command that just ran.
|
|
227
|
-
...(options.command === 'feedback' ? {} : { tip: FEEDBACK_TIP }),
|
|
228
|
-
},
|
|
229
|
-
summary: writeOptions.summary,
|
|
230
|
-
findings: writeOptions.findings,
|
|
231
|
-
evidenceRefs: writeOptions.evidenceRefs,
|
|
232
|
-
recommendedActions: writeOptions.recommendedActions,
|
|
233
|
-
nextCommands: writeOptions.nextCommands,
|
|
234
|
-
artifacts: writeOptions.artifacts,
|
|
235
|
-
warnings: writeOptions.warnings,
|
|
236
|
-
errors: writeOptions.errors,
|
|
237
|
-
});
|
|
238
|
-
};
|
|
239
|
-
const output = {
|
|
240
|
-
mode: options.mode,
|
|
241
|
-
quiet: options.quiet === true,
|
|
242
|
-
writeData(data, writeOptions = {}) {
|
|
243
|
-
if (options.quiet)
|
|
244
|
-
return;
|
|
245
|
-
if (options.mode === 'agent' || options.mode === 'agent-stream') {
|
|
246
|
-
if (state.terminal)
|
|
247
|
-
return;
|
|
248
|
-
const envelope = writeEnvelope(data, writeOptions);
|
|
249
|
-
if (options.mode === 'agent-stream') {
|
|
250
|
-
ensureStarted();
|
|
251
|
-
emitEvent({
|
|
252
|
-
event: envelope.status === 'error' ? 'error' : 'completed',
|
|
253
|
-
status: envelope.status,
|
|
254
|
-
result: envelope,
|
|
255
|
-
...(envelope.errors[0] ? { error: envelope.errors[0] } : {}),
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
state.terminal = true;
|
|
260
|
-
safeWriteStdout(`${JSON.stringify(envelope)}\n`);
|
|
261
|
-
}
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
safeWriteStdout(`${JSON.stringify(redactDeep(data), null, 2)}\n`);
|
|
265
|
-
},
|
|
266
|
-
writeEvent(event) {
|
|
267
|
-
if (options.quiet)
|
|
268
|
-
return;
|
|
269
|
-
if (options.mode !== 'agent-stream')
|
|
270
|
-
return;
|
|
271
|
-
if (event.event === 'started') {
|
|
272
|
-
startHeartbeat(options.command, state);
|
|
273
|
-
emitEvent(event);
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
ensureStarted();
|
|
277
|
-
emitEvent(event);
|
|
278
|
-
},
|
|
279
|
-
writeError(error) {
|
|
280
|
-
if (options.quiet || state.terminal)
|
|
281
|
-
return;
|
|
282
|
-
const agentError = errorToAgentError(error);
|
|
283
|
-
const envelope = createAgentErrorEnvelope(options.command || 'unknown', agentError, state.runId);
|
|
284
|
-
if (options.mode === 'agent-stream') {
|
|
285
|
-
ensureStarted();
|
|
286
|
-
if (error instanceof CliInputRequiredError) {
|
|
287
|
-
emitEvent({
|
|
288
|
-
event: 'needs_input',
|
|
289
|
-
status: 'error',
|
|
290
|
-
message: error.message,
|
|
291
|
-
error: agentError,
|
|
292
|
-
input_request: error.input_request,
|
|
293
|
-
result: envelope,
|
|
294
|
-
});
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
if (error instanceof CliAuthError) {
|
|
298
|
-
emitEvent({
|
|
299
|
-
event: 'auth_required',
|
|
300
|
-
status: 'error',
|
|
301
|
-
message: error.message,
|
|
302
|
-
error: agentError,
|
|
303
|
-
auth: error.auth,
|
|
304
|
-
result: envelope,
|
|
305
|
-
});
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
if (error instanceof CliCancellationError) {
|
|
309
|
-
emitEvent({
|
|
310
|
-
event: 'cancelled',
|
|
311
|
-
status: 'error',
|
|
312
|
-
message: error.message,
|
|
313
|
-
error: agentError,
|
|
314
|
-
result: envelope,
|
|
315
|
-
});
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
emitEvent({
|
|
319
|
-
event: 'error',
|
|
320
|
-
status: 'error',
|
|
321
|
-
message: agentError.message,
|
|
322
|
-
error: agentError,
|
|
323
|
-
result: envelope,
|
|
324
|
-
});
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
if (options.mode === 'agent' || options.mode === 'json') {
|
|
328
|
-
state.terminal = true;
|
|
329
|
-
safeWriteStdout(`${JSON.stringify(envelope)}\n`);
|
|
330
|
-
}
|
|
331
|
-
},
|
|
332
|
-
writeCancelled(reason = 'Command cancelled.') {
|
|
333
|
-
output.writeError(new CliCancellationError(reason));
|
|
334
|
-
},
|
|
335
|
-
hasTerminalOutput() {
|
|
336
|
-
return state.terminal;
|
|
337
|
-
},
|
|
338
|
-
close() {
|
|
339
|
-
clearHeartbeat(state);
|
|
340
|
-
},
|
|
341
|
-
};
|
|
342
|
-
return output;
|
|
343
|
-
}
|
|
344
|
-
export function createAgentEnvelope(options) {
|
|
345
|
-
const safeData = redactDeep(options.data);
|
|
346
|
-
const bounded = boundForAgent(safeData);
|
|
347
|
-
const envelope = {
|
|
348
|
-
schema_version: AGENT_SCHEMA_VERSION,
|
|
349
|
-
command: options.command,
|
|
350
|
-
status: options.status,
|
|
351
|
-
generated_at: new Date().toISOString(),
|
|
352
|
-
...(options.profile ? { profile: options.profile } : {}),
|
|
353
|
-
summary: normalizeSummary(options.summary, options.command, options.status, safeData),
|
|
354
|
-
findings: options.findings ?? [],
|
|
355
|
-
evidence_refs: options.evidenceRefs ?? [],
|
|
356
|
-
recommended_actions: options.recommendedActions ?? [],
|
|
357
|
-
next_commands: normalizeNextCommands(options.nextCommands ?? []),
|
|
358
|
-
artifacts: options.artifacts ?? [],
|
|
359
|
-
warnings: options.warnings ?? inferWarnings(safeData),
|
|
360
|
-
errors: options.errors ?? [],
|
|
361
|
-
data: bounded.data,
|
|
362
|
-
...(options.meta ? { meta: options.meta } : {}),
|
|
363
|
-
...(bounded.truncation ? { truncation: bounded.truncation } : {}),
|
|
364
|
-
};
|
|
365
|
-
return redactDeep(envelope);
|
|
366
|
-
}
|
|
367
|
-
export function createAgentErrorEnvelope(command, error, runId) {
|
|
368
|
-
return createAgentEnvelope({
|
|
369
|
-
command,
|
|
370
|
-
status: 'error',
|
|
371
|
-
data: null,
|
|
372
|
-
meta: runId ? { run_id: runId } : undefined,
|
|
373
|
-
summary: {
|
|
374
|
-
text: error.message,
|
|
375
|
-
confidence: 'high',
|
|
376
|
-
},
|
|
377
|
-
errors: [error],
|
|
378
|
-
nextCommands: (error.suggested_commands ?? []).map((commandText) => ({
|
|
379
|
-
command: commandText,
|
|
380
|
-
purpose: 'Suggested recovery command.',
|
|
381
|
-
mutability: commandText.includes(' sync') || commandText.startsWith('moda sync') ? 'sync' : 'read',
|
|
382
|
-
requires_approval: commandText.includes(' --yes') || commandText.includes(' sync'),
|
|
383
|
-
})),
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
export function createAgentEvent(command, event, state) {
|
|
387
|
-
const stamped = {
|
|
388
|
-
schema_version: AGENT_EVENT_SCHEMA_VERSION,
|
|
389
|
-
command,
|
|
390
|
-
generated_at: new Date().toISOString(),
|
|
391
|
-
run_id: state?.runId ?? FALLBACK_RUN_ID,
|
|
392
|
-
seq: state ? ++state.seq : ++fallbackSeq,
|
|
393
|
-
event_version: event.event_version ?? 1,
|
|
394
|
-
...event,
|
|
395
|
-
};
|
|
396
|
-
return redactDeep(stamped);
|
|
397
|
-
}
|
|
398
|
-
export function errorToAgentError(error) {
|
|
399
|
-
if (error instanceof CliInputRequiredError) {
|
|
400
|
-
return {
|
|
401
|
-
code: 'input_required',
|
|
402
|
-
message: error.message,
|
|
403
|
-
cause: error.usage,
|
|
404
|
-
suggested_commands: error.input_request.resume_command
|
|
405
|
-
? [error.input_request.resume_command.join(' ')]
|
|
406
|
-
: undefined,
|
|
407
|
-
input_request: error.input_request,
|
|
408
|
-
blocking: true,
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
if (error instanceof CliAuthError) {
|
|
412
|
-
const suggested = [error.auth.login_command.join(' ')];
|
|
413
|
-
if (error.auth.resume_command)
|
|
414
|
-
suggested.push(error.auth.resume_command.join(' '));
|
|
415
|
-
return {
|
|
416
|
-
code: 'auth_required',
|
|
417
|
-
message: error.message,
|
|
418
|
-
cause: error.usage,
|
|
419
|
-
suggested_commands: suggested,
|
|
420
|
-
auth: error.auth,
|
|
421
|
-
blocking: true,
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
if (error instanceof CliCancellationError) {
|
|
425
|
-
return {
|
|
426
|
-
code: 'cancelled',
|
|
427
|
-
message: error.message,
|
|
428
|
-
blocking: true,
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
if (error instanceof CliAccessError) {
|
|
432
|
-
// Access problem (missing organization or insufficient role), not
|
|
433
|
-
// auth-missing: exit stays EXIT_ERROR and no login command is suggested.
|
|
434
|
-
return {
|
|
435
|
-
code: 'access_denied',
|
|
436
|
-
message: error.message,
|
|
437
|
-
cause: error.guidance,
|
|
438
|
-
blocking: true,
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
if (error instanceof Error) {
|
|
442
|
-
if (error instanceof CliInputError || error.name === 'ZodError') {
|
|
443
|
-
return {
|
|
444
|
-
code: 'input_error',
|
|
445
|
-
message: error.message,
|
|
446
|
-
cause: 'usage' in error && typeof error.usage === 'string' ? error.usage : undefined,
|
|
447
|
-
suggested_commands: 'usage' in error && typeof error.usage === 'string'
|
|
448
|
-
? extractSuggestedCommands(error.usage)
|
|
449
|
-
: undefined,
|
|
450
|
-
blocking: true,
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
if (isApiLikeError(error)) {
|
|
454
|
-
const statusCode = 'statusCode' in error ? error.statusCode : undefined;
|
|
455
|
-
return {
|
|
456
|
-
code: 'api_error',
|
|
457
|
-
message: error.message,
|
|
458
|
-
cause: typeof statusCode === 'number' ? `HTTP ${statusCode}` : undefined,
|
|
459
|
-
blocking: true,
|
|
460
|
-
};
|
|
461
|
-
}
|
|
462
|
-
if (isNetworkLikeError(error)) {
|
|
463
|
-
return {
|
|
464
|
-
code: 'network_error',
|
|
465
|
-
message: error.message,
|
|
466
|
-
blocking: true,
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
return {
|
|
470
|
-
code: 'command_error',
|
|
471
|
-
message: error.message,
|
|
472
|
-
cause: 'usage' in error && typeof error.usage === 'string' ? error.usage : undefined,
|
|
473
|
-
suggested_commands: 'usage' in error && typeof error.usage === 'string'
|
|
474
|
-
? extractSuggestedCommands(error.usage)
|
|
475
|
-
: undefined,
|
|
476
|
-
blocking: true,
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
return {
|
|
480
|
-
code: 'unknown_error',
|
|
481
|
-
message: String(error),
|
|
482
|
-
blocking: true,
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
function isTerminalEvent(event) {
|
|
486
|
-
return event === 'completed' ||
|
|
487
|
-
event === 'error' ||
|
|
488
|
-
event === 'cancelled' ||
|
|
489
|
-
event === 'needs_input' ||
|
|
490
|
-
event === 'auth_required';
|
|
491
|
-
}
|
|
492
|
-
function startHeartbeat(command, state) {
|
|
493
|
-
if (state.heartbeat)
|
|
494
|
-
return;
|
|
495
|
-
const intervalMs = Number(process.env.MODA_HEARTBEAT_MS ?? 10_000);
|
|
496
|
-
if (!Number.isFinite(intervalMs) || intervalMs <= 0)
|
|
497
|
-
return;
|
|
498
|
-
state.heartbeat = setInterval(() => {
|
|
499
|
-
if (state.terminal || stdoutClosed)
|
|
500
|
-
return;
|
|
501
|
-
const now = Date.now();
|
|
502
|
-
if (now - state.lastEventAt < intervalMs)
|
|
503
|
-
return;
|
|
504
|
-
state.lastEventAt = now;
|
|
505
|
-
safeWriteStdout(`${JSON.stringify(createAgentEvent(command, {
|
|
506
|
-
event: 'heartbeat',
|
|
507
|
-
elapsed_ms: now - state.startedAt,
|
|
508
|
-
}, state))}\n`);
|
|
509
|
-
}, intervalMs);
|
|
510
|
-
state.heartbeat.unref();
|
|
511
|
-
}
|
|
512
|
-
function clearHeartbeat(state) {
|
|
513
|
-
if (!state.heartbeat)
|
|
514
|
-
return;
|
|
515
|
-
clearInterval(state.heartbeat);
|
|
516
|
-
state.heartbeat = undefined;
|
|
517
|
-
}
|
|
518
|
-
function normalizeSummary(summary, command, status, data) {
|
|
519
|
-
if (typeof summary === 'string') {
|
|
520
|
-
return { text: summary, confidence: 'high' };
|
|
521
|
-
}
|
|
522
|
-
if (summary?.text) {
|
|
523
|
-
return {
|
|
524
|
-
text: summary.text,
|
|
525
|
-
confidence: summary.confidence ?? 'high',
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
return inferSummary(command, status, data);
|
|
529
|
-
}
|
|
530
|
-
function inferSummary(command, status, data) {
|
|
531
|
-
const record = asRecord(data);
|
|
532
|
-
if (status === 'error') {
|
|
533
|
-
return { text: `${command} failed.`, confidence: 'high' };
|
|
534
|
-
}
|
|
535
|
-
const summary = asRecord(record?.summary);
|
|
536
|
-
if (summary) {
|
|
537
|
-
const statusValue = typeof summary.status === 'string' ? summary.status : undefined;
|
|
538
|
-
const fail = typeof summary.fail === 'number' ? summary.fail : undefined;
|
|
539
|
-
const warning = typeof summary.warning === 'number' ? summary.warning : undefined;
|
|
540
|
-
if (statusValue && fail !== undefined && warning !== undefined) {
|
|
541
|
-
return {
|
|
542
|
-
text: `${command} completed with status ${statusValue}: ${fail} failing checks and ${warning} warnings.`,
|
|
543
|
-
confidence: 'high',
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
const runtimeAgentCount = typeof summary.runtimeAgentCount === 'number'
|
|
547
|
-
? summary.runtimeAgentCount
|
|
548
|
-
: undefined;
|
|
549
|
-
if (runtimeAgentCount !== undefined) {
|
|
550
|
-
return {
|
|
551
|
-
text: `${command} completed. Harness summary includes ${runtimeAgentCount} runtime agent(s).`,
|
|
552
|
-
confidence: 'high',
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
if (typeof record?.status === 'string') {
|
|
557
|
-
return { text: `${command} completed with status ${record.status}.`, confidence: 'high' };
|
|
558
|
-
}
|
|
559
|
-
return { text: `${command} completed.`, confidence: 'medium' };
|
|
560
|
-
}
|
|
561
|
-
function normalizeNextCommands(commands) {
|
|
562
|
-
return commands.map((command) => {
|
|
563
|
-
if (typeof command !== 'string')
|
|
564
|
-
return command;
|
|
565
|
-
return {
|
|
566
|
-
command,
|
|
567
|
-
purpose: 'Suggested next Moda command.',
|
|
568
|
-
mutability: inferMutability(command),
|
|
569
|
-
requires_approval: command.includes(' --yes') || command.includes(' sync'),
|
|
570
|
-
};
|
|
571
|
-
});
|
|
572
|
-
}
|
|
573
|
-
function inferMutability(command) {
|
|
574
|
-
if (/\b(sync|approve|init|install|pull|gen|remove|promote)\b/.test(command)) {
|
|
575
|
-
if (/\bsync\b/.test(command))
|
|
576
|
-
return 'sync';
|
|
577
|
-
return 'write';
|
|
578
|
-
}
|
|
579
|
-
return 'read';
|
|
580
|
-
}
|
|
581
|
-
function inferWarnings(data) {
|
|
582
|
-
const record = asRecord(data);
|
|
583
|
-
const direct = record?.warnings;
|
|
584
|
-
if (Array.isArray(direct))
|
|
585
|
-
return direct.filter((item) => typeof item === 'string');
|
|
586
|
-
const validation = asRecord(record?.validation);
|
|
587
|
-
if (Array.isArray(validation?.warnings)) {
|
|
588
|
-
return validation.warnings.filter((item) => typeof item === 'string');
|
|
589
|
-
}
|
|
590
|
-
return [];
|
|
591
|
-
}
|
|
592
|
-
function boundForAgent(data) {
|
|
593
|
-
const maxBytes = Number(process.env.MODA_AGENT_MAX_OUTPUT_BYTES ?? 200_000);
|
|
594
|
-
const serialized = JSON.stringify(data);
|
|
595
|
-
const bytes = Buffer.byteLength(serialized, 'utf8');
|
|
596
|
-
if (bytes <= maxBytes)
|
|
597
|
-
return { data };
|
|
598
|
-
const preview = serialized.slice(0, Math.max(0, maxBytes - 256));
|
|
599
|
-
return {
|
|
600
|
-
data: {
|
|
601
|
-
truncated: true,
|
|
602
|
-
original_bytes: bytes,
|
|
603
|
-
max_bytes: maxBytes,
|
|
604
|
-
preview,
|
|
605
|
-
},
|
|
606
|
-
truncation: {
|
|
607
|
-
data: {
|
|
608
|
-
original_bytes: bytes,
|
|
609
|
-
max_bytes: maxBytes,
|
|
610
|
-
reason: 'agent_output_byte_limit',
|
|
611
|
-
},
|
|
612
|
-
},
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
function asRecord(value) {
|
|
616
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
617
|
-
? value
|
|
618
|
-
: undefined;
|
|
619
|
-
}
|
|
620
|
-
function extractSuggestedCommands(text) {
|
|
621
|
-
const commands = [...text.matchAll(/`(moda [^`]+)`/g)].map((match) => match[1]);
|
|
622
|
-
return commands.length > 0 ? commands : undefined;
|
|
623
|
-
}
|
|
624
|
-
//# sourceMappingURL=output.js.map
|