vigthoria-cli 1.13.25 → 1.13.29

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 (54) hide show
  1. package/dist/commands/chat.js +83 -27
  2. package/dist/commands/config.js +4 -4
  3. package/dist/commands/fork.d.ts +3 -2
  4. package/dist/commands/fork.js +124 -123
  5. package/dist/commands/game.d.ts +8 -0
  6. package/dist/commands/game.js +118 -10
  7. package/dist/commands/history.d.ts +0 -1
  8. package/dist/commands/history.js +8 -22
  9. package/dist/commands/hub.d.ts +20 -0
  10. package/dist/commands/hub.js +17 -3
  11. package/dist/commands/preview.d.ts +1 -0
  12. package/dist/commands/preview.js +36 -15
  13. package/dist/commands/product-run-registration.js +1 -1
  14. package/dist/commands/replay.d.ts +0 -1
  15. package/dist/commands/replay.js +10 -19
  16. package/dist/commands/repo.d.ts +35 -2
  17. package/dist/commands/repo.js +76 -34
  18. package/dist/commands/update-registration.js +4 -3
  19. package/dist/commands/workflow.d.ts +4 -0
  20. package/dist/commands/workflow.js +27 -0
  21. package/dist/index.js +6 -4
  22. package/dist/utils/agentRunOutcome.d.ts +7 -0
  23. package/dist/utils/agentRunOutcome.js +13 -0
  24. package/dist/utils/api.d.ts +20 -5
  25. package/dist/utils/api.js +440 -54
  26. package/dist/utils/command-policy.js +1 -1
  27. package/dist/utils/config.d.ts +2 -0
  28. package/dist/utils/config.js +8 -3
  29. package/dist/utils/frontend-preview-service.d.ts +1 -0
  30. package/dist/utils/frontend-preview-service.js +54 -5
  31. package/dist/utils/model-governance.js +23 -14
  32. package/dist/utils/model-transport-service.js +1 -1
  33. package/dist/utils/network-policy.js +16 -4
  34. package/dist/utils/operator-client.js +23 -4
  35. package/dist/utils/post-write-validator.js +7 -3
  36. package/dist/utils/preview-screenshot-adapter.d.ts +19 -3
  37. package/dist/utils/preview-screenshot-adapter.js +289 -28
  38. package/dist/utils/runtime-capability.d.ts +7 -0
  39. package/dist/utils/runtime-capability.js +11 -0
  40. package/dist/utils/runtime-temp.d.ts +6 -2
  41. package/dist/utils/runtime-temp.js +143 -27
  42. package/dist/utils/tools.js +1 -1
  43. package/dist/utils/v3-stream-events.d.ts +8 -0
  44. package/dist/utils/v3-stream-events.js +70 -0
  45. package/dist/utils/v3-workspace-service.d.ts +1 -0
  46. package/dist/utils/v3-workspace-service.js +38 -1
  47. package/dist/utils/vigflow-client.d.ts +9 -0
  48. package/dist/utils/vigflow-client.js +48 -2
  49. package/dist/utils/workspace-reference.d.ts +8 -0
  50. package/dist/utils/workspace-reference.js +21 -0
  51. package/package.json +4 -6
  52. package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +2 -2
  53. package/scripts/release/validate-live-service-gates.sh +3 -3
  54. package/scripts/release/validate-no-go-gates.sh +2 -0
@@ -22,7 +22,7 @@ import { renderDynamicHelp } from '../utils/command-menu.js';
22
22
  import { resolvePromptWorkspace } from '../utils/prompt-workspace-resolver.js';
23
23
  import { FileUtils } from '../utils/files.js';
24
24
  import { isDirectModeFollowUpQuestion, sanitizeDirectModeOutput, stripHiddenThoughtBlocks } from '../utils/direct-output-policy.js';
25
- import { createLiveOutcome, evaluateExecutorSuccess, handleRunCompleteEvent, handleTaskEvent, isSubstantiveAgentAnswer, isToolEvidenceStubAnswer, normalizeAgentAnswerContent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
25
+ import { createLiveOutcome, evaluateExecutorSuccess, handleRunCompleteEvent, handleTaskEvent, isSubstantiveAgentAnswer, isToolEvidenceStubAnswer, normalizeAgentAnswerContent, noteAnalysisToolUse, resolveAgentPartialMutation, } from '../utils/agentRunOutcome.js';
26
26
  import { looksLikeMarkdownReport, renderMarkdownToTerminal, summarizeMarkdownReport, } from '../utils/terminalMarkdown.js';
27
27
  import { emitDeckEvent, isDeckModeEnabled } from '../utils/deckEvents.js';
28
28
  import { formatGoaSystemGrounding, splitGoaContextFromInput, } from '../utils/goaEvents.js';
@@ -30,6 +30,7 @@ import { inferAgentTaskTypeWithContext, resolvePlannerAgentTimeoutMs, resolveWor
30
30
  import { resolveAgentRoute } from '../utils/agentRoute.js';
31
31
  import { isV3StreamKeepaliveEvent } from '../utils/v3-stream-events.js';
32
32
  import { resolveAutoApproval } from '../utils/process-policy.js';
33
+ import { hasLocalV3ServiceIdentity } from '../utils/runtime-capability.js';
33
34
  import { ChatToolCallParser } from '../utils/chat-tool-call-parser.js';
34
35
  import { ChatPromptPolicy } from '../utils/chat-prompt-policy.js';
35
36
  import { AgentStreamState } from '../utils/agent-stream-state.js';
@@ -1333,17 +1334,18 @@ export class ChatCommand {
1333
1334
  isJsonOutput: () => this.jsonOutput,
1334
1335
  idleTimeoutMs: () => DEFAULT_V3_AGENT_IDLE_TIMEOUT_MS,
1335
1336
  });
1336
- this.sessionManager = new SessionManager();
1337
+ this.sessionManager = new SessionManager(this.config.getStateRoot());
1337
1338
  }
1338
1339
  async run(options) {
1340
+ this.agentMode = options.agent === true;
1341
+ this.operatorMode = options.operator === true;
1339
1342
  const hasRuntimeToken = Boolean(process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN);
1340
- if (!this.config.isAuthenticated() && !hasRuntimeToken) {
1343
+ const hasAgentServiceIdentity = this.agentMode && hasLocalV3ServiceIdentity();
1344
+ if (!this.config.isAuthenticated() && !hasRuntimeToken && !hasAgentServiceIdentity) {
1341
1345
  throw new CliCommandError('Not authenticated. Run: vigthoria login', {
1342
1346
  code: 'AUTH_REQUIRED', category: 'authentication',
1343
1347
  });
1344
1348
  }
1345
- this.agentMode = options.agent === true;
1346
- this.operatorMode = options.operator === true;
1347
1349
  this.workflowTarget = typeof options.workflow === 'string' && options.workflow.trim()
1348
1350
  ? options.workflow.trim()
1349
1351
  : null;
@@ -1884,26 +1886,14 @@ export class ChatCommand {
1884
1886
  await this.runOperatorDirectAnswer(prompt);
1885
1887
  return;
1886
1888
  }
1887
- // ── Repo-grounded operator path: try V3 agent first, then fall back ──
1888
- // Prompts that reference files, code symbols, or analysis verbs need
1889
- // tool access to read actual file contents. Route these through the
1890
- // V3 agent (which archives runs for history/replay/fork and supports
1891
- // workspace hydration) first. Falls back to local agent loop if V3
1892
- // is unreachable.
1893
- if (this.isRepoGroundedPrompt(prompt) && this.tools) {
1894
- this.operatorMode = true;
1895
- const handledByV3 = await this.tryV3AgentWorkflow(prompt);
1896
- if (handledByV3) {
1897
- this.saveSession();
1898
- return;
1899
- }
1900
- await this.runLocalAgentLoop(prompt);
1901
- return;
1902
- }
1903
1889
  getBridgeClient()?.emitPrompt({ prompt, mode: 'operator', model: this.currentModel });
1904
1890
  const runtimeContext = await this.getPromptRuntimeContext(prompt);
1905
1891
  const spinner = this.jsonOutput ? null : createSpinner({ text: 'Thinking like an operator...', spinner: 'clock' }).start();
1906
- const workflowType = 'full';
1892
+ // Operator is the BMAD surface. Keep grounded repository analysis on the
1893
+ // Operator service and make mutation intent explicit instead of silently
1894
+ // diverting it into V3 Agent mode. Read-only is the fail-safe default.
1895
+ const operatorTaskType = inferAgentTaskTypeWithContext(prompt);
1896
+ const workflowType = resolveWorkflowType(operatorTaskType, prompt);
1907
1897
  const executionPrompt = this.buildExecutionPrompt(prompt);
1908
1898
  try {
1909
1899
  this.rememberBrainEvent('task', `GoA operator workflow started for prompt: ${prompt.slice(0, 220)}`, 'operator');
@@ -2931,15 +2921,24 @@ export class ChatCommand {
2931
2921
  if (this.streamState.streamingStarted && !this.streamState.streamedAnswerDisplayed) {
2932
2922
  this.displayV3StreamedAnswer();
2933
2923
  }
2934
- const previewGate = (response.metadata?.previewGate || null);
2924
+ const serverPreviewGate = (response.metadata?.previewGate || null);
2935
2925
  const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
2936
2926
  const changedFileCount = response.changedFiles ? Object.keys(response.changedFiles).length : 0;
2937
2927
  const requiresWorkspaceChanges = taskRequiresWorkspaceChangesWithContext(contextualPrompt, intentContext);
2928
+ const previewGate = requiresWorkspaceChanges
2929
+ ? serverPreviewGate
2930
+ : { required: false, passed: true, skipped: false };
2938
2931
  const answerContent = normalizeAgentAnswerContent(response.content, this.streamState.streamedAnswerBuffer);
2939
2932
  liveOutcome.changedFileCount = changedFileCount;
2940
2933
  liveOutcome.requiresWorkspaceChanges = requiresWorkspaceChanges;
2941
2934
  liveOutcome.workspaceHasOutput = requiresWorkspaceChanges ? workspaceHasOutput : false;
2942
2935
  liveOutcome.answerContent = answerContent;
2936
+ const workflowError = response.metadata?.workflowError;
2937
+ if (workflowError && !liveOutcome.executorError) {
2938
+ liveOutcome.executorError = typeof workflowError === 'string'
2939
+ ? workflowError
2940
+ : String(workflowError.message || 'Agent execution failed.');
2941
+ }
2943
2942
  const success = previewGate?.required === true
2944
2943
  ? previewGate?.passed === true && previewGate?.skipped !== true
2945
2944
  : true;
@@ -2954,6 +2953,10 @@ export class ChatCommand {
2954
2953
  return false;
2955
2954
  }
2956
2955
  const errorMessage = `V3 agent workflow returned an incomplete result and legacy fallback is disabled. ${previewGate?.error || 'Workspace changes were not fully validated.'}`;
2956
+ 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');
2959
+ liveOutcome.unfinishedTaskIds.delete('agent-run-in-progress');
2957
2960
  if (spinner) {
2958
2961
  spinner.stop();
2959
2962
  }
@@ -2963,8 +2966,8 @@ export class ChatCommand {
2963
2966
  watcher?.stop();
2964
2967
  throw new CliCommandError(errorMessage, {
2965
2968
  code: 'AGENT_PREVIEW_FAILED',
2966
- category: previewGate?.partialMutation === false ? 'execution' : 'partial_mutation',
2967
- partialMutation: previewGate?.partialMutation ?? 'unknown',
2969
+ category: partialMutation === false ? 'execution' : 'partial_mutation',
2970
+ partialMutation,
2968
2971
  details: { executionPath: 'v3-agent', previewGate },
2969
2972
  });
2970
2973
  }
@@ -3159,10 +3162,11 @@ export class ChatCommand {
3159
3162
  }
3160
3163
  }
3161
3164
  if (!executorSucceeded) {
3165
+ const partialMutation = resolveAgentPartialMutation(changedFileCount, response.metadata?.partialMutation);
3162
3166
  throw new CliCommandError(runEvaluation.statusHeadline, {
3163
3167
  code: 'AGENT_INCOMPLETE',
3164
- category: response.partial === true || liveOutcome.tasksSucceeded > 0 ? 'partial_mutation' : 'execution',
3165
- partialMutation: response.partial === true || liveOutcome.tasksSucceeded > 0 ? 'unknown' : false,
3168
+ category: partialMutation === false ? 'execution' : 'partial_mutation',
3169
+ partialMutation,
3166
3170
  details: {
3167
3171
  mode: 'agent',
3168
3172
  model: routingPolicy.selectedModel,
@@ -3208,9 +3212,61 @@ export class ChatCommand {
3208
3212
  catch (error) {
3209
3213
  this.stopV3IdleWatch();
3210
3214
  watcher?.stop();
3215
+ const persistTypedAgentFailure = (failure) => {
3216
+ const safeFailure = sanitizeUserFacingErrorText(failure.message || 'Agent execution failed.');
3217
+ liveOutcome.streamAborted = true;
3218
+ if (!liveOutcome.executorError)
3219
+ liveOutcome.executorError = safeFailure;
3220
+ if (failure.code === 'AGENT_PLAN_INVALID')
3221
+ liveOutcome.failedTaskIds.add('agent-plan');
3222
+ liveOutcome.unfinishedTaskIds.delete('agent-run-in-progress');
3223
+ this.commitAgentRunOutcome({
3224
+ prompt,
3225
+ originalPrompt: null,
3226
+ taskId: null,
3227
+ contextId: executionId,
3228
+ tasksSucceeded: liveOutcome.tasksSucceeded,
3229
+ tasksTotal: liveOutcome.tasksTotal,
3230
+ failedTaskIds: [...liveOutcome.failedTaskIds],
3231
+ unfinishedTaskIds: [...liveOutcome.unfinishedTaskIds],
3232
+ qualityScore: liveOutcome.qualityScore,
3233
+ qualityMissing: liveOutcome.qualityMissing,
3234
+ qualityBlockers: liveOutcome.qualityBlockers,
3235
+ hasOutput: this.api.hasAgentWorkspaceOutput(workspaceContext),
3236
+ answerContent: null,
3237
+ selfHealStatus: 'failed',
3238
+ selfHealTool: null,
3239
+ plannerError: liveOutcome.plannerError ? sanitizeUserFacingErrorText(liveOutcome.plannerError) : null,
3240
+ executorError: liveOutcome.executorError,
3241
+ clientToolErrors: this.api.getClientToolErrors(),
3242
+ transportErrors: this.api.getLastChatTransportErrors(),
3243
+ workspacePath: workspacePath || null,
3244
+ workspaceSyncIssue: null,
3245
+ finishedAt: Date.now(),
3246
+ }, prompt);
3247
+ this.updateAgentExecutionCheckpoint({
3248
+ status: 'failed',
3249
+ contextId: executionId,
3250
+ failedTaskIds: [...liveOutcome.failedTaskIds],
3251
+ unfinishedTaskIds: [...liveOutcome.unfinishedTaskIds],
3252
+ error: liveOutcome.executorError,
3253
+ });
3254
+ };
3255
+ if (error?.code === 'AGENT_PLAN_INVALID') {
3256
+ const planFailure = new CliCommandError(error.message, {
3257
+ code: 'AGENT_PLAN_INVALID',
3258
+ category: 'execution',
3259
+ partialMutation: error?.partialMutation ?? false,
3260
+ details: error?.mutation ? { mutation: error.mutation } : undefined,
3261
+ cause: error,
3262
+ });
3263
+ persistTypedAgentFailure(planFailure);
3264
+ throw planFailure;
3265
+ }
3211
3266
  if (error instanceof CliCommandError) {
3212
3267
  if (spinner)
3213
3268
  spinner.stop();
3269
+ persistTypedAgentFailure(error);
3214
3270
  throw error;
3215
3271
  }
3216
3272
  if (!this.api.hasAgentWorkspaceOutput(workspaceContext)) {
@@ -101,8 +101,8 @@ export class ConfigCommand {
101
101
  message: 'Default AI model:',
102
102
  choices: [
103
103
  { name: '═══ Code Models ═══', disabled: true },
104
- { name: 'Vigthoria v4 Creative 27B - Architect and planning', value: 'architect' },
105
- { name: 'Vigthoria v4 Code 27B - Production executor', value: 'code' },
104
+ { name: 'Vigthoria v4.2 Creative 27B - Multimodal architect and planning', value: 'architect' },
105
+ { name: 'Vigthoria v4.2 Code 27B - Production executor', value: 'code' },
106
106
  { name: 'Vigthoria v4 Assistant 9B - Fast FIM and diagnostics', value: 'assistant' },
107
107
  ],
108
108
  default: 'code',
@@ -309,8 +309,8 @@ export class ConfigCommand {
309
309
  name: 'defaultModel',
310
310
  message: 'Default AI model:',
311
311
  choices: [
312
- { name: 'Vigthoria v4 Creative 27B — Architect', value: 'architect' },
313
- { name: 'Vigthoria v4 Code 27B — Executor', value: 'code' },
312
+ { name: 'Vigthoria v4.2 Creative 27B — Multimodal Architect', value: 'architect' },
313
+ { name: 'Vigthoria v4.2 Code 27B — Executor', value: 'code' },
314
314
  { name: 'Vigthoria v4 Assistant 9B — FIM and diagnostics', value: 'assistant' },
315
315
  ],
316
316
  default: current.preferences.defaultModel,
@@ -7,10 +7,11 @@ interface ForkOptions {
7
7
  }
8
8
  export declare class ForkCommand {
9
9
  private config;
10
- constructor(config: Config, _logger: Logger);
10
+ private logger;
11
+ constructor(config: Config, logger: Logger);
11
12
  private getHeaders;
12
13
  private getBaseUrl;
13
- private resolveWorkspaceRoot;
14
+ private buildForkHistory;
14
15
  run(runId: string, message: string, options: ForkOptions): Promise<void>;
15
16
  }
16
17
  export {};
@@ -3,14 +3,17 @@ import { hasLocalV3AgentCapability } from '../utils/runtime-capability.js';
3
3
  * fork.ts — Fork from an existing V3 agent run and stream the result.
4
4
  */
5
5
  import chalk from 'chalk';
6
- import { createRequire } from 'node:module';
7
6
  import { createSpinner, CH } from '../utils/logger.js';
8
7
  import { CliCommandError, commandFailure, formatSuccessJson } from '../utils/command-contract.js';
9
- const require = createRequire(import.meta.url);
8
+ import { createAPIClient } from '../utils/api-client-factory.js';
9
+ import { buildLocalWorkspaceReference } from '../utils/workspace-reference.js';
10
+ import { guardedFetch } from '../utils/network-policy.js';
10
11
  export class ForkCommand {
11
12
  config;
12
- constructor(config, _logger) {
13
+ logger;
14
+ constructor(config, logger) {
13
15
  this.config = config;
16
+ this.logger = logger;
14
17
  }
15
18
  getHeaders() {
16
19
  const headers = { 'Content-Type': 'application/json' };
@@ -34,50 +37,69 @@ export class ForkCommand {
34
37
  (allowLocal ? 'http://127.0.0.1:8030' : null) ||
35
38
  configuredApiUrl);
36
39
  }
37
- resolveWorkspaceRoot(project) {
38
- if (/^[a-zA-Z]:[\\/]/.test(project) || /^\\\\/.test(project))
39
- return '';
40
- if (typeof require !== 'undefined') {
41
- try {
42
- const path = require('path');
43
- if (!path.isAbsolute(project))
44
- return '';
40
+ buildForkHistory(events, eventIndex) {
41
+ const selected = eventIndex > 0 ? events.slice(0, eventIndex) : events;
42
+ const lines = [
43
+ `Fork point: ${eventIndex > 0 ? eventIndex : events.length} of ${events.length} recorded events.`,
44
+ 'Prior run evidence (metadata only; re-read current files through client tools):',
45
+ ];
46
+ let toolCalls = 0;
47
+ for (const event of selected.slice(-120)) {
48
+ const type = String(event?.type || 'unknown');
49
+ if (type === 'plan') {
50
+ const count = Array.isArray(event.tasks) ? event.tasks.length : 0;
51
+ lines.push(`- plan: ${count} task(s)`);
45
52
  }
46
- catch { }
53
+ else if (type === 'tool_call') {
54
+ toolCalls += 1;
55
+ lines.push(`- tool_call ${toolCalls}: ${String(event.name || 'unknown').slice(0, 80)}`);
56
+ }
57
+ else if (type === 'tool_result') {
58
+ lines.push(`- tool_result: ${String(event.name || 'unknown').slice(0, 80)} success=${event.success !== false}`);
59
+ }
60
+ else if (type === 'file_mutation') {
61
+ lines.push(`- file_mutation: ${String(event.kind || 'change').slice(0, 32)} (path intentionally omitted)`);
62
+ }
63
+ else if (type === 'complete') {
64
+ lines.push('- prior run reached its completion event');
65
+ }
66
+ else if (type === 'error') {
67
+ lines.push(`- prior run error code: ${String(event.code || event.error_code || 'unspecified').slice(0, 80)}`);
68
+ }
69
+ if (lines.join('\n').length > 12_000)
70
+ break;
47
71
  }
48
- return project;
72
+ return lines.join('\n').slice(0, 12_000);
49
73
  }
50
74
  async run(runId, message, options) {
51
75
  const project = options.project || process.cwd();
52
- const workspace = this.resolveWorkspaceRoot(project);
76
+ const workspaceRef = buildLocalWorkspaceReference(project, String(this.config.get('userId') || this.config.get('email') || ''));
53
77
  const eventIndex = options.eventIndex || 0;
54
- const spinner = createSpinner(`Forking run ${runId}...`).start();
78
+ const spinner = createSpinner(`Loading run ${runId} for a local fork...`).start();
55
79
  const streamController = new AbortController();
80
+ let api = null;
56
81
  let interrupted = false;
57
82
  const onInterrupt = () => {
58
83
  interrupted = true;
59
84
  streamController.abort();
85
+ api?.destroy();
60
86
  };
61
87
  process.once('SIGINT', onInterrupt);
62
88
  try {
63
89
  const baseUrl = this.getBaseUrl();
64
- const body = {
65
- workspace_root: workspace,
66
- from_event_index: eventIndex,
67
- new_request: message || '',
68
- stream: true,
69
- context: '',
70
- };
71
- const resp = await fetch(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/fork`, {
72
- method: 'POST',
90
+ const params = new URLSearchParams({
91
+ workspace_root: '',
92
+ local_workspace_path: workspaceRef,
93
+ project_path: workspaceRef,
94
+ });
95
+ const resp = await guardedFetch(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/events?${params}`, {
73
96
  headers: this.getHeaders(),
74
- body: JSON.stringify(body),
75
97
  signal: streamController.signal,
76
- });
98
+ }, { audience: 'v3' });
77
99
  if (!resp.ok) {
78
100
  spinner.stop();
79
101
  if (resp.status === 404) {
80
- throw new CliCommandError(`Run ${runId} not found or has no event log.`, {
102
+ throw new CliCommandError(`Run ${runId} was not found or has no event log.`, {
81
103
  code: 'RUN_NOT_FOUND', status: resp.status,
82
104
  });
83
105
  }
@@ -87,116 +109,94 @@ export class ForkCommand {
87
109
  });
88
110
  }
89
111
  else {
90
- throw new CliCommandError(`Fork failed: ${resp.status} ${resp.statusText}`, {
112
+ throw new CliCommandError(`Could not load the fork source: ${resp.status} ${resp.statusText}`, {
91
113
  code: 'RUN_FORK_FAILED', status: resp.status,
92
114
  });
93
115
  }
94
116
  }
117
+ const source = (await resp.json());
118
+ const sourceEvents = Array.isArray(source.events) ? source.events : [];
119
+ if (eventIndex < 0 || eventIndex > sourceEvents.length) {
120
+ throw new CliCommandError(`Fork event index ${eventIndex} is outside 0..${sourceEvents.length}.`, {
121
+ code: 'RUN_FORK_EVENT_INDEX_INVALID',
122
+ });
123
+ }
124
+ const forkHistory = this.buildForkHistory(sourceEvents, eventIndex);
95
125
  spinner.stop();
96
- if (!options.json)
97
- console.log(chalk.bold(`\n${CH.success} Forked from ${chalk.cyan(runId)} at event ${eventIndex}\n`));
98
- if (!resp.body) {
99
- throw new CliCommandError('Fork response did not contain an event stream', { code: 'RUN_FORK_STREAM_MISSING' });
126
+ if (!options.json) {
127
+ console.log(chalk.bold(`\n${CH.success} Starting local fork from ${chalk.cyan(runId)} at event ${eventIndex || sourceEvents.length}\n`));
100
128
  }
101
- // Stream SSE events
102
- const reader = resp.body.getReader();
103
- const decoder = new TextDecoder();
104
- let buffer = '';
129
+ const streamEvents = [];
105
130
  let toolCallNum = 0;
106
- let completed = false;
107
- const events = [];
108
- const configuredIdle = Number.parseInt(process.env.VIGTHORIA_FORK_IDLE_TIMEOUT_MS || '', 10);
109
- const idleTimeoutMs = Number.isFinite(configuredIdle) && configuredIdle > 0 ? configuredIdle : 60_000;
110
- stream: while (true) {
111
- let timer;
112
- const { done, value } = await Promise.race([
113
- reader.read(),
114
- new Promise((_resolve, reject) => {
115
- timer = setTimeout(() => {
116
- streamController.abort();
117
- reject(new CliCommandError(`Fork event stream was idle for ${idleTimeoutMs}ms.`, {
118
- code: 'RUN_FORK_IDLE_TIMEOUT', category: 'network',
119
- }));
120
- }, idleTimeoutMs);
121
- }),
122
- ]).finally(() => {
123
- if (timer)
124
- clearTimeout(timer);
125
- });
126
- if (done)
127
- break;
128
- buffer += decoder.decode(value, { stream: true });
129
- const lines = buffer.split('\n');
130
- buffer = lines.pop() || '';
131
- for (const line of lines) {
132
- if (!line.startsWith('data: '))
133
- continue;
134
- const payload = line.slice(6).trim();
135
- if (payload === '[DONE]') {
136
- completed = true;
137
- break stream;
131
+ api = createAPIClient(this.config, this.logger);
132
+ const request = [
133
+ message.trim() || 'Continue the previous run from the selected fork point.',
134
+ '',
135
+ forkHistory,
136
+ '',
137
+ 'Use the current client-authoritative workspace as truth. Re-read every needed file through client tools; do not assume prior file contents.',
138
+ ].join('\n');
139
+ const result = await api.runV3AgentWorkflow(request, {
140
+ projectPath: project,
141
+ targetPath: project,
142
+ workspacePath: project,
143
+ localWorkspacePath: project,
144
+ localMachineCapable: true,
145
+ clientToolExecution: true,
146
+ executionSurface: 'fork',
147
+ clientSurface: 'cli',
148
+ rawPrompt: message || request,
149
+ contextualPrompt: forkHistory,
150
+ forkedFrom: runId,
151
+ forkEventIndex: eventIndex,
152
+ onStreamEvent: (event) => {
153
+ streamEvents.push({
154
+ type: String(event.type || 'unknown'),
155
+ name: typeof event.name === 'string' ? event.name : undefined,
156
+ success: typeof event.success === 'boolean' ? event.success : undefined,
157
+ code: typeof event.code === 'string' ? event.code : undefined,
158
+ });
159
+ if (options.json)
160
+ return;
161
+ const type = String(event.type || 'unknown');
162
+ if (type === 'plan') {
163
+ console.log(chalk.magenta(` 📋 PLAN`) + ` ${event.tasks?.length || 0} tasks`);
138
164
  }
139
- try {
140
- const evt = JSON.parse(payload);
141
- const type = evt.type || 'unknown';
142
- events.push(evt);
143
- if (options.json) {
144
- continue;
145
- }
146
- switch (type) {
147
- case 'context':
148
- console.log(chalk.blue(` context: ${evt.context_id || '?'}`) +
149
- (evt.forked_from ? chalk.dim(` (forked from ${evt.forked_from})`) : ''));
150
- break;
151
- case 'start':
152
- console.log(chalk.green(` ▶ START`) + ` task=${evt.task_id || '?'}` +
153
- (evt.forked_from ? chalk.dim(` forked_from=${evt.forked_from}`) : ''));
154
- break;
155
- case 'plan':
156
- console.log(chalk.magenta(` 📋 PLAN`) + ` ${evt.tasks?.length || 0} tasks`);
157
- break;
158
- case 'tool_call':
159
- toolCallNum++;
160
- console.log(chalk.yellow(` 🔧 #${toolCallNum}`) + ` ${evt.name || '?'}(${JSON.stringify(evt.arguments || {}).substring(0, 60)})`);
161
- break;
162
- case 'tool_result': {
163
- const icon = evt.success !== false ? chalk.green('✓') : chalk.red('✗');
164
- console.log(` ${icon} ${evt.name || '?'}: ${chalk.dim((evt.output || '').substring(0, 100))}`);
165
- break;
166
- }
167
- case 'message':
168
- console.log(chalk.cyan(` 💬 `) + (evt.content || '').substring(0, 150));
169
- break;
170
- case 'complete': {
171
- const seal = evt.seal_score ? `[${evt.seal_score.tier} ${evt.seal_score.overall}]` : '';
172
- console.log(chalk.green(` ✅ COMPLETE `) + chalk.yellow(seal) +
173
- ` ${evt.iterations || '?'} iterations, ${evt.tool_calls || '?'} tool calls`);
174
- break;
175
- }
176
- case 'error':
177
- throw new CliCommandError(String(evt.message || 'Fork stream reported an error.'), {
178
- code: 'RUN_FORK_STREAM_ERROR', details: evt,
179
- });
180
- default:
181
- console.log(chalk.dim(` ${type}: ${JSON.stringify(evt).substring(0, 80)}`));
182
- }
165
+ else if (type === 'tool_call') {
166
+ toolCallNum += 1;
167
+ console.log(chalk.yellow(` 🔧 #${toolCallNum}`) + ` ${event.name || '?'}`);
183
168
  }
184
- catch (error) {
185
- if (error instanceof CliCommandError)
186
- throw error;
187
- throw new CliCommandError('Fork event stream contained malformed JSON.', {
188
- code: 'RUN_FORK_STREAM_MALFORMED', details: { payload: payload.slice(0, 200) }, cause: error,
189
- });
169
+ else if (type === 'tool_result') {
170
+ const icon = event.success !== false ? chalk.green('✓') : chalk.red('✗');
171
+ console.log(` ${icon} ${event.name || '?'}`);
190
172
  }
191
- }
192
- }
193
- if (!completed) {
194
- throw new CliCommandError('Fork event stream ended without a completion marker.', { code: 'RUN_FORK_STREAM_INCOMPLETE' });
173
+ else if (type === 'message' && event.content) {
174
+ console.log(chalk.cyan(' 💬 ') + String(event.content).slice(0, 180));
175
+ }
176
+ else if (type === 'complete') {
177
+ console.log(chalk.green(' ✅ COMPLETE'));
178
+ }
179
+ },
180
+ });
181
+ if (result.partial) {
182
+ throw new CliCommandError('Fork execution ended with a partial result.', {
183
+ code: 'RUN_FORK_PARTIAL', details: { contextId: result.contextId, taskId: result.taskId },
184
+ });
195
185
  }
196
186
  if (options.json) {
197
- console.log(formatSuccessJson('fork', { runId, eventIndex, events }, { stream: true, eventCount: events.length }));
187
+ console.log(formatSuccessJson('fork', {
188
+ runId,
189
+ eventIndex: eventIndex || sourceEvents.length,
190
+ taskId: result.taskId,
191
+ contextId: result.contextId,
192
+ content: result.content,
193
+ changedFiles: Object.keys(result.changedFiles || {}),
194
+ events: streamEvents,
195
+ }, { stream: true, eventCount: streamEvents.length, workspaceAuthority: 'client-tool-bridge' }));
198
196
  }
199
197
  else {
198
+ if (result.content)
199
+ console.log(result.content);
200
200
  console.log(chalk.bold(`\n${CH.success} Fork run complete\n`));
201
201
  }
202
202
  }
@@ -208,6 +208,7 @@ export class ForkCommand {
208
208
  throw commandFailure(err, { code: 'RUN_FORK_FAILED' });
209
209
  }
210
210
  finally {
211
+ api?.destroy();
211
212
  process.removeListener('SIGINT', onInterrupt);
212
213
  }
213
214
  }
@@ -8,6 +8,14 @@ export declare function gameProcessInvocation(pm: Manager, args: readonly string
8
8
  executable: string;
9
9
  args: string[];
10
10
  };
11
+ export declare function windowsProcessTreeTerminationInvocation(pid: number, systemRoot?: string): {
12
+ executable: string;
13
+ args: string[];
14
+ };
15
+ export declare const runGameProcess: (pm: Manager, args: string[], cwd: string, timeoutMs?: number, outputMode?: "inherit" | "capture") => Promise<{
16
+ stdout: string;
17
+ stderr: string;
18
+ }>;
11
19
  export declare class GameCommand {
12
20
  private logger;
13
21
  constructor(logger: Logger);