vigthoria-cli 1.13.34 → 1.13.42

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.
Files changed (51) hide show
  1. package/README.md +11 -56
  2. package/SECURITY_HARDENING.md +2 -4
  3. package/dist/commands/auth.js +16 -14
  4. package/dist/commands/cancel.d.ts +1 -0
  5. package/dist/commands/cancel.js +21 -4
  6. package/dist/commands/chat.d.ts +13 -0
  7. package/dist/commands/chat.js +170 -14
  8. package/dist/commands/fork.js +8 -3
  9. package/dist/commands/history.js +8 -3
  10. package/dist/commands/legion.js +1 -1
  11. package/dist/commands/replay.js +8 -3
  12. package/dist/commands/update-registration.js +63 -206
  13. package/dist/commands/v4-menu.d.ts +0 -1
  14. package/dist/commands/v4-menu.js +8 -6
  15. package/dist/commands/v4-registration.js +0 -2
  16. package/dist/commands/v4.js +9 -4
  17. package/dist/commands/wallet.d.ts +1 -1
  18. package/dist/commands/wallet.js +3 -3
  19. package/dist/index.js +2 -3
  20. package/dist/utils/agent-stream-state.d.ts +7 -0
  21. package/dist/utils/agent-stream-state.js +31 -0
  22. package/dist/utils/agentRunOutcome.js +5 -1
  23. package/dist/utils/api.d.ts +19 -4
  24. package/dist/utils/api.js +322 -550
  25. package/dist/utils/frontend-preview-service.d.ts +10 -1
  26. package/dist/utils/frontend-preview-service.js +162 -17
  27. package/dist/utils/javascript-syntax.d.ts +19 -0
  28. package/dist/utils/javascript-syntax.js +35 -0
  29. package/dist/utils/localTestMode.js +1 -1
  30. package/dist/utils/mutation-journal.d.ts +10 -0
  31. package/dist/utils/mutation-journal.js +104 -76
  32. package/dist/utils/network-policy.js +7 -10
  33. package/dist/utils/preview-screenshot-adapter.d.ts +81 -0
  34. package/dist/utils/preview-screenshot-adapter.js +615 -38
  35. package/dist/utils/release-install.d.ts +0 -1
  36. package/dist/utils/release-install.js +44 -7
  37. package/dist/utils/requestIntent.d.ts +1 -1
  38. package/dist/utils/requestIntent.js +17 -3
  39. package/dist/utils/tools.js +21 -15
  40. package/dist/utils/update-policy.d.ts +0 -4
  41. package/dist/utils/update-policy.js +5 -9
  42. package/dist/utils/v3-agent-client.js +25 -11
  43. package/install.ps1 +30 -50
  44. package/install.sh +13 -24
  45. package/package.json +4 -2
  46. package/release-policy.json +1 -5
  47. package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +6 -7
  48. package/scripts/release/install-release.mjs +5 -4
  49. package/scripts/release/publish-cli-release.mjs +21 -8
  50. package/scripts/release/test-balanced-model-live.sh +4 -1
  51. package/scripts/release/validate-live-service-gates.sh +27 -5
package/README.md CHANGED
@@ -35,78 +35,33 @@ The `vigthoria-cli` package installs the `vigthoria`, `vig`, and `vigthoria-chat
35
35
  ### Quick Install (Linux/macOS)
36
36
 
37
37
  ```bash
38
- curl -fsSL https://cli.vigthoria.io/install.sh | bash
38
+ curl -fsSL https://extension.vigthoria.io/downloads/install.sh | bash
39
39
  ```
40
40
 
41
41
  ### Quick Install (Windows PowerShell)
42
42
 
43
43
  ```powershell
44
- irm https://cli.vigthoria.io/install.ps1 | iex
44
+ irm https://extension.vigthoria.io/downloads/install.ps1 | iex
45
45
  ```
46
46
 
47
- ### npm Install (All Platforms)
48
-
49
- ```bash
50
- npm install -g vigthoria-cli
51
- ```
52
-
53
- ### Manual Install
54
-
55
- ```bash
56
- # Clone from Vigthoria Market
57
- git clone https://market.vigthoria.io/vigthoria/vigthoria-cli.git
58
- cd vigthoria-cli
59
-
60
- # Or from Vigthoria Community
61
- git clone https://community.vigthoria.io/repos/vigthoria-cli.git
62
- cd vigthoria-cli
63
-
64
- # Install dependencies
65
- npm install
66
-
67
- # Build
68
- npm run build
69
-
70
- # Link globally
71
- npm link
72
- ```
47
+ The installer downloads the signed release manifest and package only from
48
+ `extension.vigthoria.io`, verifies the detached Ed25519 signature and SHA-256
49
+ identity, and installs the verified local archive transactionally.
73
50
 
74
51
  ## Troubleshooting Installation
75
52
 
76
- ### Network Issues (npm ENOTFOUND)
53
+ ### Network Issues
77
54
 
78
- If you see `ENOTFOUND registry.npmjs.org`, try these solutions:
55
+ If the canonical extension endpoint cannot be reached:
79
56
 
80
57
  1. **Check internet connection:**
81
58
  ```bash
82
- ping registry.npmjs.org
59
+ curl -I https://extension.vigthoria.io/downloads/manifest.json
83
60
  ```
84
61
 
85
- 2. **If behind a corporate proxy:**
86
- ```bash
87
- npm config set proxy http://proxy.company.com:8080
88
- npm config set https-proxy http://proxy.company.com:8080
89
- ```
90
-
91
- 3. **Try an alternative npm registry:**
92
- ```bash
93
- npm config set registry https://registry.npmmirror.com
94
- npm install -g vigthoria-cli
95
- # Reset to default after:
96
- npm config delete registry
97
- ```
98
-
99
- 4. **Direct tarball download:**
100
- ```bash
101
- # Download directly
102
- npm install -g https://coder.vigthoria.io/releases/vigthoria-cli-latest.tgz
103
- ```
104
-
105
- 5. **Use Git clone method (no npm registry needed):**
106
- ```bash
107
- git clone https://market.vigthoria.io/vigthoria/vigthoria-cli.git
108
- cd vigthoria-cli && npm install && npm run build && npm link
109
- ```
62
+ 2. Permit outbound HTTPS to `extension.vigthoria.io` through the corporate
63
+ proxy or firewall. Do not substitute a registry, mirror, Git clone, or
64
+ alternate download host for the signed release path.
110
65
 
111
66
  ### Windows-Specific Issues
112
67
 
@@ -21,7 +21,7 @@ The code model is now branded as **Vigthoria_v3_Code_30B**:
21
21
 
22
22
  1. **Installation:**
23
23
  ```bash
24
- npm install -g vigthoria-cli
24
+ curl -fsSL https://extension.vigthoria.io/downloads/install.sh | bash
25
25
  ```
26
26
 
27
27
  2. **Login:**
@@ -57,14 +57,12 @@ When users with version < 1.4.0 run the CLI, they will see:
57
57
  ⚠️ SECURITY UPDATE AVAILABLE
58
58
  Version 1.3.0 has security vulnerabilities.
59
59
  Please update to 1.4.0 immediately:
60
- npm install -g vigthoria-cli@latest
60
+ vigthoria update
61
61
  ```
62
62
 
63
63
  ### Update Command
64
64
  ```bash
65
65
  vigthoria update
66
- # or
67
- npm install -g vigthoria-cli@latest
68
66
  ```
69
67
 
70
68
  ## Vulnerabilities Fixed
@@ -525,25 +525,27 @@ export async function statusAction() {
525
525
  console.log(chalk.yellow('Not logged in.'));
526
526
  return;
527
527
  }
528
- const authProbeHeaders = {
529
- Authorization: `Bearer ${effectiveToken}`,
530
- Cookie: `vigthoria-auth-token=${effectiveToken}`,
531
- };
532
- let authLooksValid = true;
533
528
  try {
534
- const authProbe = await fetch(`${effectiveApiUrl}/api/user/subscription`, { headers: authProbeHeaders, signal: AbortSignal.timeout(6000) });
535
- if (authProbe.status === 401 || authProbe.status === 403) {
536
- authLooksValid = false;
537
- }
538
- }
539
- catch {
540
- // Network failures should not force logout status.
529
+ validateTokenStructure(effectiveToken, effectiveApiUrl);
541
530
  }
542
- if (!authLooksValid) {
531
+ catch (error) {
543
532
  throw new CliCommandError('Not logged in. Session expired or invalid. Run: vigthoria login', {
544
- code: 'AUTH_SESSION_INVALID', category: 'authentication',
533
+ code: 'AUTH_SESSION_INVALID', category: 'authentication', cause: error,
545
534
  });
546
535
  }
536
+ try {
537
+ await new AuthSessionService(sharedConfig).validateServerAcceptance(effectiveToken, effectiveApiUrl);
538
+ }
539
+ catch (error) {
540
+ // An explicit authorization rejection invalidates the session. Network
541
+ // and optional-route failures keep local status available as degraded,
542
+ // matching the health-probe behavior below.
543
+ if (/HTTP\s+(?:401|403)\b/i.test(humanMessage(error))) {
544
+ throw new CliCommandError('Not logged in. Session expired or invalid. Run: vigthoria login', {
545
+ code: 'AUTH_SESSION_INVALID', category: 'authentication', cause: error,
546
+ });
547
+ }
548
+ }
547
549
  let user = legacyConfig.user;
548
550
  try {
549
551
  user = await whoami();
@@ -5,6 +5,7 @@ interface CancelOptions {
5
5
  list?: boolean;
6
6
  json?: boolean;
7
7
  }
8
+ export declare function resolveCancelRunsPath(baseUrl: string): '/api/runs' | '/api/runs-cli';
8
9
  export declare class CancelCommand {
9
10
  private config;
10
11
  private logger;
@@ -10,6 +10,18 @@ import chalk from 'chalk';
10
10
  import { createSpinner, CH } from '../utils/logger.js';
11
11
  import { SessionManager } from '../utils/session.js';
12
12
  import { CliCommandError, formatSuccessJson } from '../utils/command-contract.js';
13
+ export function resolveCancelRunsPath(baseUrl) {
14
+ try {
15
+ return new Set(['8032', '18032']).has(new URL(String(baseUrl)).port)
16
+ ? '/api/runs'
17
+ : '/api/runs-cli';
18
+ }
19
+ catch {
20
+ return /(?:127\.0\.0\.1|localhost|172\.19\.0\.1|host\.lan):(?:8032|18032)(?:\/|$)/i.test(String(baseUrl))
21
+ ? '/api/runs'
22
+ : '/api/runs-cli';
23
+ }
24
+ }
13
25
  export class CancelCommand {
14
26
  config;
15
27
  logger;
@@ -19,7 +31,11 @@ export class CancelCommand {
19
31
  this.logger = logger;
20
32
  }
21
33
  getHeaders() {
22
- const headers = { 'Content-Type': 'application/json' };
34
+ const headers = {
35
+ 'Content-Type': 'application/json',
36
+ 'X-Vigthoria-Agent-Lane': 'cli',
37
+ 'X-Vigthoria-Source-Product': 'vigthoria-cli',
38
+ };
23
39
  const token = process.env.VIGTHORIA_TOKEN ||
24
40
  process.env.VIGTHORIA_AUTH_TOKEN ||
25
41
  this.config.get('authToken');
@@ -37,11 +53,12 @@ export class CancelCommand {
37
53
  const allowLocal = process.env.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1';
38
54
  return (process.env.VIGTHORIA_V3_AGENT_URL ||
39
55
  process.env.V3_AGENT_URL ||
40
- (allowLocal ? 'http://127.0.0.1:8030' : null) ||
56
+ (allowLocal ? 'http://127.0.0.1:8032' : null) ||
41
57
  configuredApiUrl);
42
58
  }
43
59
  async listActive(baseUrl) {
44
- const resp = await fetch(`${baseUrl}/api/runs/active`, { headers: this.getHeaders() });
60
+ const runsPath = resolveCancelRunsPath(baseUrl);
61
+ const resp = await fetch(`${baseUrl}${runsPath}/active`, { headers: this.getHeaders() });
45
62
  if (!resp.ok) {
46
63
  if (resp.status === 404 || resp.status === 502 || resp.status === 503) {
47
64
  throw new CliCommandError(`V3 cancel API is not available on ${baseUrl}. Backend may not expose /api/runs/active.`, { code: 'CANCEL_API_UNAVAILABLE', category: 'network', status: resp.status });
@@ -59,7 +76,7 @@ export class CancelCommand {
59
76
  return (await resp.json());
60
77
  }
61
78
  async cancelOne(baseUrl, contextId) {
62
- const resp = await fetch(`${baseUrl}/api/runs/${encodeURIComponent(contextId)}/cancel`, { method: 'POST', headers: this.getHeaders() });
79
+ const resp = await fetch(`${baseUrl}${resolveCancelRunsPath(baseUrl)}/${encodeURIComponent(contextId)}/cancel`, { method: 'POST', headers: this.getHeaders() });
63
80
  if (!resp.ok) {
64
81
  if (resp.status === 404) {
65
82
  throw new CliCommandError(`No active run for context_id=${contextId}`, { code: 'ACTIVE_RUN_NOT_FOUND', status: resp.status });
@@ -52,6 +52,9 @@ export declare class ChatCommand {
52
52
  private retryPromptSignature;
53
53
  private retryPromptStreak;
54
54
  private readonly streamState;
55
+ private processInterruptHandled;
56
+ private readonly onSigint;
57
+ private readonly onSigterm;
55
58
  private lastAgentRoute;
56
59
  private pendingGoaContext;
57
60
  private lastAgentRunOutcome;
@@ -131,6 +134,16 @@ export declare class ChatCommand {
131
134
  private updateOperatorSpinner;
132
135
  constructor(config: Config, logger: Logger, commandProgram?: Command);
133
136
  run(options: ChatOptions): Promise<void>;
137
+ private installProcessInterruptHandlers;
138
+ private removeProcessInterruptHandlers;
139
+ private handleProcessInterrupt;
140
+ /**
141
+ * Signal handlers cannot await the normal run lifecycle. Persist the
142
+ * terminal checkpoint synchronously after workspace rollback so a killed
143
+ * foreground process is never advertised as resumable work still running.
144
+ */
145
+ private finalizeInterruptedAgentCheckpoint;
146
+ private runWithLifecycle;
134
147
  /** Handle an inbound admin command from the Commando Bridge. */
135
148
  private handleAdminCommand;
136
149
  private ensureProjectWorkspace;
@@ -33,8 +33,9 @@ import { resolveAutoApproval } from '../utils/process-policy.js';
33
33
  import { hasLocalV3ServiceIdentity } from '../utils/runtime-capability.js';
34
34
  import { ChatToolCallParser } from '../utils/chat-tool-call-parser.js';
35
35
  import { ChatPromptPolicy } from '../utils/chat-prompt-policy.js';
36
- import { AgentStreamState } from '../utils/agent-stream-state.js';
36
+ import { AgentStreamState, classifyAgentToolResultPresentation, normalizeAgentToolOutputForDisplay, } from '../utils/agent-stream-state.js';
37
37
  import { CliCommandError, commandFailure, commandNameFromArgv, failureEnvelope, normalizeCommandError, successEnvelope, } from '../utils/command-contract.js';
38
+ import { rollbackActiveMutationJournals } from '../utils/mutation-journal.js';
38
39
  // Stream health policy (corrected 2026-07-04 after TestFarm incident with
39
40
  // Gideon Lenz / C:\vigthoria\Apps\monopoly): agentic tasks have no
40
41
  // predictable duration, so there is intentionally NO overall wall-clock
@@ -161,6 +162,9 @@ export class ChatCommand {
161
162
  retryPromptSignature = null;
162
163
  retryPromptStreak = 0;
163
164
  streamState;
165
+ processInterruptHandled = false;
166
+ onSigint = () => this.handleProcessInterrupt('SIGINT');
167
+ onSigterm = () => this.handleProcessInterrupt('SIGTERM');
164
168
  lastAgentRoute = null;
165
169
  pendingGoaContext = null;
166
170
  // Last completed Agent run — used by /retry, /continue, and the final summary block.
@@ -788,10 +792,7 @@ export class ChatCommand {
788
792
  return `${visible.slice(0, Math.max(1, width - 1))}…`;
789
793
  }
790
794
  normalizeToolOutputForDisplay(output) {
791
- return String(output || '')
792
- .replace(/\/r\/n|\/n|\\n/g, '\n')
793
- .replace(/\/r|\\r/g, '')
794
- .replace(/\t/g, ' ');
795
+ return normalizeAgentToolOutputForDisplay(output);
795
796
  }
796
797
  toolEventKey(event) {
797
798
  const name = String(event?.name || event?.tool || event?.tool_name || '').trim();
@@ -908,6 +909,9 @@ export class ChatCommand {
908
909
  if (!event || typeof event !== 'object') {
909
910
  return;
910
911
  }
912
+ if (event.user_visible === false || String(event.visibility || '').toLowerCase() === 'internal') {
913
+ return;
914
+ }
911
915
  this.streamState.noteActivity();
912
916
  if (isV3StreamKeepaliveEvent(event)) {
913
917
  return;
@@ -960,12 +964,24 @@ export class ChatCommand {
960
964
  this.streamState.seenToolResults.add(eventKey);
961
965
  const success = event.success !== false;
962
966
  const toolName = event.name || event.tool || '';
967
+ const presentation = classifyAgentToolResultPresentation(event);
963
968
  emitDeckEvent({
964
969
  type: 'tool_end',
965
970
  tool: toolName,
966
971
  success,
967
972
  index: this.streamState.toolCallCount,
968
973
  });
974
+ if (presentation === 'hidden')
975
+ return;
976
+ if (presentation === 'correcting') {
977
+ if (!this.streamState.shouldShowCorrectionNotice())
978
+ return;
979
+ if (spinner.isSpinning)
980
+ spinner.stop();
981
+ this.writeAgentActivityLine(chalk.gray(' ↻ Agent adjusted this step and is continuing.\n'));
982
+ spinner.text = 'Correcting step...';
983
+ return;
984
+ }
969
985
  const indicator = success ? chalk.green(' ✓') : chalk.red(' ✗');
970
986
  if (spinner.isSpinning)
971
987
  spinner.stop();
@@ -1337,6 +1353,73 @@ export class ChatCommand {
1337
1353
  this.sessionManager = new SessionManager(this.config.getStateRoot());
1338
1354
  }
1339
1355
  async run(options) {
1356
+ this.installProcessInterruptHandlers();
1357
+ try {
1358
+ await this.runWithLifecycle(options);
1359
+ }
1360
+ finally {
1361
+ this.removeProcessInterruptHandlers();
1362
+ }
1363
+ }
1364
+ installProcessInterruptHandlers() {
1365
+ this.processInterruptHandled = false;
1366
+ process.once('SIGINT', this.onSigint);
1367
+ process.once('SIGTERM', this.onSigterm);
1368
+ }
1369
+ removeProcessInterruptHandlers() {
1370
+ process.off('SIGINT', this.onSigint);
1371
+ process.off('SIGTERM', this.onSigterm);
1372
+ }
1373
+ handleProcessInterrupt(signal) {
1374
+ if (this.processInterruptHandled)
1375
+ return;
1376
+ this.processInterruptHandled = true;
1377
+ const ownedMutation = this.api.destroy();
1378
+ const fallbackMutations = rollbackActiveMutationJournals();
1379
+ const mutation = ownedMutation || fallbackMutations[0] || null;
1380
+ this.finalizeInterruptedAgentCheckpoint(signal);
1381
+ this.workspaceBrain?.destroy();
1382
+ getBridgeClient()?.destroy();
1383
+ if (!this.jsonOutput) {
1384
+ const restored = mutation?.rolledBack
1385
+ ? ` Workspace changes were restored (${mutation.affectedPaths.length} path(s)).`
1386
+ : mutation?.partialMutation
1387
+ ? ' WARNING: the workspace could not be restored completely; inspect the mutation journal before continuing.'
1388
+ : '';
1389
+ process.stderr.write(`\nAgent interrupted.${restored}\n`);
1390
+ }
1391
+ this.removeProcessInterruptHandlers();
1392
+ // Rollback is synchronous and complete before ownership returns to the OS.
1393
+ // Re-raise the original signal after removing our handlers so the central
1394
+ // process lifecycle retains the conventional 130/143 shell status without
1395
+ // letting a command implementation terminate the process directly.
1396
+ process.kill(process.pid, signal);
1397
+ }
1398
+ /**
1399
+ * Signal handlers cannot await the normal run lifecycle. Persist the
1400
+ * terminal checkpoint synchronously after workspace rollback so a killed
1401
+ * foreground process is never advertised as resumable work still running.
1402
+ */
1403
+ finalizeInterruptedAgentCheckpoint(signal) {
1404
+ const checkpoint = this.currentSession?.activeAgentRun;
1405
+ if (!checkpoint || ['completed', 'cancelled'].includes(checkpoint.status)) {
1406
+ return;
1407
+ }
1408
+ try {
1409
+ assertAgentStatusTransition(checkpoint.status, 'cancelled');
1410
+ this.currentSession.activeAgentRun = {
1411
+ ...checkpoint,
1412
+ status: 'cancelled',
1413
+ updatedAt: new Date().toISOString(),
1414
+ error: `Interrupted by ${signal}; workspace rollback completed before process exit.`,
1415
+ };
1416
+ this.sessionManager.save(this.currentSession);
1417
+ }
1418
+ catch (error) {
1419
+ this.logger.warn('Workspace rollback completed, but the interrupted session checkpoint could not be persisted.', error);
1420
+ }
1421
+ }
1422
+ async runWithLifecycle(options) {
1340
1423
  this.agentMode = options.agent === true;
1341
1424
  this.operatorMode = options.operator === true;
1342
1425
  const hasRuntimeToken = Boolean(process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN);
@@ -2934,6 +3017,9 @@ export class ChatCommand {
2934
3017
  liveOutcome.workspaceHasOutput = requiresWorkspaceChanges ? workspaceHasOutput : false;
2935
3018
  liveOutcome.answerContent = answerContent;
2936
3019
  const workflowError = response.metadata?.workflowError;
3020
+ const workflowErrorCode = typeof workflowError === 'object' && workflowError
3021
+ ? String(workflowError.code || '').trim().toUpperCase()
3022
+ : '';
2937
3023
  if (workflowError && !liveOutcome.executorError) {
2938
3024
  liveOutcome.executorError = typeof workflowError === 'string'
2939
3025
  ? workflowError
@@ -2952,10 +3038,36 @@ export class ChatCommand {
2952
3038
  watcher?.stop();
2953
3039
  return false;
2954
3040
  }
2955
- const errorMessage = `V3 agent workflow returned an incomplete result and legacy fallback is disabled. ${previewGate?.error || 'Workspace changes were not fully validated.'}`;
3041
+ const workflowFailure = workflowError
3042
+ ? (typeof workflowError === 'string'
3043
+ ? workflowError
3044
+ : String(workflowError.message || ''))
3045
+ : '';
3046
+ const actionableFailure = sanitizeUserFacingErrorText(liveOutcome.plannerError
3047
+ || workflowFailure
3048
+ || liveOutcome.executorError
3049
+ || previewGate?.error
3050
+ || 'The agent stopped before it could validate the requested workspace changes.');
3051
+ const reportedMutation = response.metadata?.mutation
3052
+ || previewGate?.mutation;
3053
+ const rollbackNote = reportedMutation?.rolledBack === true
3054
+ ? ' Unvalidated workspace changes were rolled back; your prior files were preserved.'
3055
+ : '';
3056
+ const errorMessage = `${actionableFailure}${rollbackNote}`.trim();
2956
3057
  const partialMutation = resolveAgentPartialMutation(changedFileCount, response.metadata?.partialMutation);
2957
- liveOutcome.executorError = previewGate?.error || 'Template Service preview proof did not pass.';
2958
- liveOutcome.failedTaskIds.add('preview-gate');
3058
+ const preservedTerminalCodes = new Set([
3059
+ 'AGENT_PLAN_INVALID',
3060
+ 'AGENT_CAPACITY_BUSY',
3061
+ 'V3_RUNTIME_IDENTITY_MISMATCH',
3062
+ ]);
3063
+ const terminalCode = preservedTerminalCodes.has(workflowErrorCode)
3064
+ ? workflowErrorCode
3065
+ : '';
3066
+ liveOutcome.executorError = actionableFailure;
3067
+ liveOutcome.failedTaskIds.add(terminalCode === 'AGENT_PLAN_INVALID' ? 'agent-plan'
3068
+ : terminalCode === 'AGENT_CAPACITY_BUSY' ? 'architect-capacity'
3069
+ : terminalCode === 'V3_RUNTIME_IDENTITY_MISMATCH' ? 'runtime-identity'
3070
+ : 'preview-gate');
2959
3071
  liveOutcome.unfinishedTaskIds.delete('agent-run-in-progress');
2960
3072
  if (spinner) {
2961
3073
  spinner.stop();
@@ -2965,10 +3077,19 @@ export class ChatCommand {
2965
3077
  this.messages.push({ role: 'assistant', content: errorMessage });
2966
3078
  watcher?.stop();
2967
3079
  throw new CliCommandError(errorMessage, {
2968
- code: 'AGENT_PREVIEW_FAILED',
2969
- category: partialMutation === false ? 'execution' : 'partial_mutation',
3080
+ code: terminalCode || 'AGENT_PREVIEW_FAILED',
3081
+ category: terminalCode === 'V3_RUNTIME_IDENTITY_MISMATCH'
3082
+ ? 'configuration'
3083
+ : partialMutation === false ? 'execution' : 'partial_mutation',
2970
3084
  partialMutation,
2971
- details: { executionPath: 'v3-agent', previewGate },
3085
+ details: {
3086
+ executionPath: 'v3-agent',
3087
+ previewGate,
3088
+ tasksSucceeded: liveOutcome.tasksSucceeded,
3089
+ tasksTotal: liveOutcome.tasksTotal,
3090
+ failedTaskIds: [...liveOutcome.failedTaskIds],
3091
+ nextAction: 'Resolve the first reported blocker, then retry from the same workspace.',
3092
+ },
2972
3093
  });
2973
3094
  }
2974
3095
  if (!this.jsonOutput && previewGate?.required) {
@@ -3219,7 +3340,12 @@ export class ChatCommand {
3219
3340
  liveOutcome.executorError = safeFailure;
3220
3341
  if (failure.code === 'AGENT_PLAN_INVALID')
3221
3342
  liveOutcome.failedTaskIds.add('agent-plan');
3222
- liveOutcome.unfinishedTaskIds.delete('agent-run-in-progress');
3343
+ if (failure.code === 'AGENT_OUTCOME_UNKNOWN') {
3344
+ liveOutcome.unfinishedTaskIds.add('agent-run-in-progress');
3345
+ }
3346
+ else {
3347
+ liveOutcome.unfinishedTaskIds.delete('agent-run-in-progress');
3348
+ }
3223
3349
  this.commitAgentRunOutcome({
3224
3350
  prompt,
3225
3351
  originalPrompt: null,
@@ -3263,6 +3389,27 @@ export class ChatCommand {
3263
3389
  persistTypedAgentFailure(planFailure);
3264
3390
  throw planFailure;
3265
3391
  }
3392
+ if (error?.code === 'AGENT_OUTCOME_UNKNOWN') {
3393
+ const interruptedFailure = new CliCommandError(error.message, {
3394
+ code: 'AGENT_OUTCOME_UNKNOWN',
3395
+ category: 'partial_mutation',
3396
+ partialMutation: 'unknown',
3397
+ details: {
3398
+ operationId: error?.operationId || null,
3399
+ contextId: error?.contextId || executionId,
3400
+ resumeCommand: '/continue',
3401
+ },
3402
+ cause: error,
3403
+ });
3404
+ if (spinner)
3405
+ spinner.stop();
3406
+ persistTypedAgentFailure(interruptedFailure);
3407
+ if (!this.jsonOutput && !this.directPromptMode) {
3408
+ console.log(chalk.yellow('Agent connection interrupted after the run started.'));
3409
+ console.log(chalk.gray('The task was not submitted again. Type /continue to resume the saved run without duplicating changes.'));
3410
+ }
3411
+ throw interruptedFailure;
3412
+ }
3266
3413
  if (error instanceof CliCommandError) {
3267
3414
  if (spinner)
3268
3415
  spinner.stop();
@@ -3348,10 +3495,15 @@ export class ChatCommand {
3348
3495
  this.printAgentRunSummary(this.lastAgentRunOutcome, failedEval, 0);
3349
3496
  }
3350
3497
  }
3498
+ const reportedMutation = error?.mutation;
3499
+ const reportedPartialMutation = reportedMutation?.rolledBack === true
3500
+ ? false
3501
+ : (error?.partialMutation ?? reportedMutation?.partialMutation);
3502
+ const partialMutation = resolveAgentPartialMutation(liveOutcome.changedFileCount, reportedPartialMutation);
3351
3503
  throw new CliCommandError(errorMessage, {
3352
3504
  code: 'AGENT_EXECUTION_FAILED',
3353
- category: liveOutcome.tasksSucceeded > 0 ? 'partial_mutation' : 'execution',
3354
- partialMutation: liveOutcome.tasksSucceeded > 0 ? 'unknown' : false,
3505
+ category: partialMutation === false ? 'execution' : 'partial_mutation',
3506
+ partialMutation,
3355
3507
  details: {
3356
3508
  executionPath: 'v3-agent',
3357
3509
  mode: 'agent',
@@ -3359,12 +3511,16 @@ export class ChatCommand {
3359
3511
  tasksSucceeded: liveOutcome.tasksSucceeded,
3360
3512
  tasksTotal: liveOutcome.tasksTotal,
3361
3513
  statusHeadline: evaluateExecutorSuccess(liveOutcome).statusHeadline,
3514
+ ...(reportedMutation ? { mutation: reportedMutation } : {}),
3362
3515
  },
3363
3516
  cause: error,
3364
3517
  });
3365
3518
  }
3366
3519
  }
3367
3520
  async tryRecoverV3ServiceAndRetry(executionPrompt, rawPrompt, workspaceContext, routingPolicy, spinner, error) {
3521
+ if (error?.code === 'AGENT_OUTCOME_UNKNOWN') {
3522
+ return false;
3523
+ }
3368
3524
  const recovery = await this.api.attemptV3ServiceRecovery((error && error.message) || '');
3369
3525
  if (!recovery.recovered) {
3370
3526
  return false;
@@ -16,7 +16,11 @@ export class ForkCommand {
16
16
  this.logger = logger;
17
17
  }
18
18
  getHeaders() {
19
- const headers = { 'Content-Type': 'application/json' };
19
+ const headers = {
20
+ 'Content-Type': 'application/json',
21
+ 'X-Vigthoria-Agent-Lane': 'cli',
22
+ 'X-Vigthoria-Source-Product': 'vigthoria-cli',
23
+ };
20
24
  const token = process.env.VIGTHORIA_TOKEN ||
21
25
  process.env.VIGTHORIA_AUTH_TOKEN ||
22
26
  this.config.get('authToken');
@@ -34,7 +38,7 @@ export class ForkCommand {
34
38
  const allowLocal = hasLocalV3AgentCapability();
35
39
  return (process.env.VIGTHORIA_V3_AGENT_URL ||
36
40
  process.env.V3_AGENT_URL ||
37
- (allowLocal ? 'http://127.0.0.1:8030' : null) ||
41
+ (allowLocal ? 'http://127.0.0.1:8032' : null) ||
38
42
  configuredApiUrl);
39
43
  }
40
44
  buildForkHistory(events, eventIndex) {
@@ -92,7 +96,8 @@ export class ForkCommand {
92
96
  local_workspace_path: workspaceRef,
93
97
  project_path: workspaceRef,
94
98
  });
95
- const resp = await guardedFetch(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/events?${params}`, {
99
+ const runsPath = /:8032\/?$/.test(baseUrl) ? '/api/runs' : '/api/runs-cli';
100
+ const resp = await guardedFetch(`${baseUrl}${runsPath}/${encodeURIComponent(runId)}/events?${params}`, {
96
101
  headers: this.getHeaders(),
97
102
  signal: streamController.signal,
98
103
  }, { audience: 'v3' });
@@ -15,7 +15,11 @@ export class HistoryCommand {
15
15
  this.logger = logger;
16
16
  }
17
17
  getHeaders() {
18
- const headers = { 'Content-Type': 'application/json' };
18
+ const headers = {
19
+ 'Content-Type': 'application/json',
20
+ 'X-Vigthoria-Agent-Lane': 'cli',
21
+ 'X-Vigthoria-Source-Product': 'vigthoria-cli',
22
+ };
19
23
  const token = process.env.VIGTHORIA_TOKEN ||
20
24
  process.env.VIGTHORIA_AUTH_TOKEN ||
21
25
  this.config.get('authToken');
@@ -33,7 +37,7 @@ export class HistoryCommand {
33
37
  const allowLocal = hasLocalV3AgentCapability();
34
38
  return (process.env.VIGTHORIA_V3_AGENT_URL ||
35
39
  process.env.V3_AGENT_URL ||
36
- (allowLocal ? 'http://127.0.0.1:8030' : null) ||
40
+ (allowLocal ? 'http://127.0.0.1:8032' : null) ||
37
41
  configuredApiUrl);
38
42
  }
39
43
  async run(options) {
@@ -49,7 +53,8 @@ export class HistoryCommand {
49
53
  local_workspace_path: workspaceRef,
50
54
  project_path: workspaceRef,
51
55
  });
52
- const resp = await guardedFetch(`${baseUrl}/api/runs?${params}`, {
56
+ const runsPath = /:8032\/?$/.test(baseUrl) ? '/api/runs' : '/api/runs-cli';
57
+ const resp = await guardedFetch(`${baseUrl}${runsPath}?${params}`, {
53
58
  headers: this.getHeaders(),
54
59
  }, { audience: 'v3' });
55
60
  if (!resp.ok) {
@@ -303,7 +303,7 @@ export class LegionCommand {
303
303
  console.log(chalk.yellow('Budget depleted for current round. Additional paid loop required.'));
304
304
  console.log(chalk.gray(` Next round: ${nextRound}`));
305
305
  console.log(chalk.gray(` Failed step: ${execution.failedStepId || 'unknown'} (${execution.failedWorker || 'unknown'})`));
306
- console.log(chalk.gray(` Additional estimate: $${nextQuote.finalUsd.toFixed(4)} / ${nextQuote.vigcoinRequired.toFixed(3)} VIG`));
306
+ console.log(chalk.gray(` Additional estimate: $${nextQuote.finalUsd.toFixed(4)} / ${nextQuote.vigcoinRequired.toFixed(3)} credits`));
307
307
  const answer = (await rl.question('Confirm additional PAYG credit deduction and continue? (y/N): ')).trim().toLowerCase();
308
308
  return answer === 'y' || answer === 'yes';
309
309
  }
@@ -13,7 +13,11 @@ export class ReplayCommand {
13
13
  this.config = config;
14
14
  }
15
15
  getHeaders() {
16
- const headers = { 'Content-Type': 'application/json' };
16
+ const headers = {
17
+ 'Content-Type': 'application/json',
18
+ 'X-Vigthoria-Agent-Lane': 'cli',
19
+ 'X-Vigthoria-Source-Product': 'vigthoria-cli',
20
+ };
17
21
  const token = process.env.VIGTHORIA_TOKEN ||
18
22
  process.env.VIGTHORIA_AUTH_TOKEN ||
19
23
  this.config.get('authToken');
@@ -31,7 +35,7 @@ export class ReplayCommand {
31
35
  const allowLocal = hasLocalV3AgentCapability();
32
36
  return (process.env.VIGTHORIA_V3_AGENT_URL ||
33
37
  process.env.V3_AGENT_URL ||
34
- (allowLocal ? 'http://127.0.0.1:8030' : null) ||
38
+ (allowLocal ? 'http://127.0.0.1:8032' : null) ||
35
39
  configuredApiUrl);
36
40
  }
37
41
  sleep(ms) {
@@ -49,7 +53,8 @@ export class ReplayCommand {
49
53
  local_workspace_path: workspaceRef,
50
54
  project_path: workspaceRef,
51
55
  });
52
- const resp = await guardedFetch(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/events?${params}`, {
56
+ const runsPath = /:8032\/?$/.test(baseUrl) ? '/api/runs' : '/api/runs-cli';
57
+ const resp = await guardedFetch(`${baseUrl}${runsPath}/${encodeURIComponent(runId)}/events?${params}`, {
53
58
  headers: this.getHeaders(),
54
59
  }, { audience: 'v3' });
55
60
  if (!resp.ok) {