vigthoria-cli 1.13.26 → 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 (51) hide show
  1. package/dist/commands/chat.js +73 -26
  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 +113 -9
  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.js +7 -2
  12. package/dist/commands/product-run-registration.js +1 -1
  13. package/dist/commands/replay.d.ts +0 -1
  14. package/dist/commands/replay.js +10 -19
  15. package/dist/commands/repo.js +16 -4
  16. package/dist/commands/update-registration.js +2 -2
  17. package/dist/commands/workflow.d.ts +4 -0
  18. package/dist/commands/workflow.js +27 -0
  19. package/dist/index.js +6 -4
  20. package/dist/utils/agentRunOutcome.d.ts +7 -0
  21. package/dist/utils/agentRunOutcome.js +13 -0
  22. package/dist/utils/api.d.ts +20 -5
  23. package/dist/utils/api.js +428 -43
  24. package/dist/utils/command-policy.js +1 -1
  25. package/dist/utils/config.d.ts +2 -0
  26. package/dist/utils/config.js +8 -3
  27. package/dist/utils/frontend-preview-service.d.ts +1 -0
  28. package/dist/utils/frontend-preview-service.js +54 -5
  29. package/dist/utils/model-governance.js +23 -14
  30. package/dist/utils/model-transport-service.js +1 -1
  31. package/dist/utils/network-policy.js +15 -3
  32. package/dist/utils/operator-client.js +23 -4
  33. package/dist/utils/post-write-validator.js +7 -3
  34. package/dist/utils/preview-screenshot-adapter.d.ts +16 -41
  35. package/dist/utils/preview-screenshot-adapter.js +273 -64
  36. package/dist/utils/runtime-capability.d.ts +7 -0
  37. package/dist/utils/runtime-capability.js +11 -0
  38. package/dist/utils/runtime-temp.d.ts +5 -2
  39. package/dist/utils/runtime-temp.js +125 -29
  40. package/dist/utils/tools.js +1 -1
  41. package/dist/utils/v3-stream-events.js +10 -2
  42. package/dist/utils/v3-workspace-service.d.ts +1 -0
  43. package/dist/utils/v3-workspace-service.js +38 -1
  44. package/dist/utils/vigflow-client.d.ts +9 -0
  45. package/dist/utils/vigflow-client.js +48 -2
  46. package/dist/utils/workspace-reference.d.ts +8 -0
  47. package/dist/utils/workspace-reference.js +21 -0
  48. package/package.json +4 -6
  49. package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +2 -2
  50. package/scripts/release/validate-live-service-gates.sh +3 -3
  51. package/scripts/release/validate-no-go-gates.sh +2 -0
@@ -46,11 +46,114 @@ export function gameProcessInvocation(pm, args, platform = process.platform, com
46
46
  const command = [executable, ...args].join(' ');
47
47
  return { executable: comSpec, args: ['/d', '/s', '/c', command] };
48
48
  }
49
- const run = (pm, args, cwd, timeoutMs = 10 * 60_000) => new Promise((resolve, reject) => { const invocation = gameProcessInvocation(pm, args); const child = spawn(invocation.executable, invocation.args, { cwd, stdio: 'inherit', windowsHide: true, env: safeChildProcessEnv() }); let interrupted = false; const onInterrupt = () => { interrupted = true; child.kill('SIGINT'); }; process.once('SIGINT', onInterrupt); const timer = timeoutMs > 0 ? setTimeout(() => { child.kill('SIGTERM'); reject(new CliCommandError(`${pm} command timed out after ${timeoutMs}ms`, { code: 'GAME_PROCESS_TIMEOUT' })); }, timeoutMs) : null; const finish = () => { if (timer)
50
- clearTimeout(timer); process.removeListener('SIGINT', onInterrupt); }; child.once('error', error => { finish(); reject(error); }); child.once('exit', (code, signal) => { finish(); if (interrupted)
51
- reject(new CliCommandError(`${pm} command cancelled by user`, { code: 'GAME_PROCESS_CANCELLED', category: 'cancelled' }));
52
- else
53
- code === 0 ? resolve() : reject(new Error(`${pm} exited with ${signal || code}`)); }); });
49
+ export function windowsProcessTreeTerminationInvocation(pid, systemRoot = process.env.SystemRoot || 'C:\\Windows') {
50
+ if (!Number.isSafeInteger(pid) || pid <= 0) {
51
+ throw new CliCommandError('Cannot terminate a game process without a valid owned PID.', {
52
+ code: 'GAME_PROCESS_ID_INVALID',
53
+ });
54
+ }
55
+ return {
56
+ executable: path.win32.join(systemRoot, 'System32', 'taskkill.exe'),
57
+ args: ['/PID', String(pid), '/T', '/F'],
58
+ };
59
+ }
60
+ async function terminateOwnedProcessTree(child, platform) {
61
+ const pid = child.pid;
62
+ if (!pid)
63
+ return;
64
+ if (platform === 'win32') {
65
+ const invocation = windowsProcessTreeTerminationInvocation(pid);
66
+ await new Promise((resolve) => {
67
+ const killer = spawn(invocation.executable, invocation.args, {
68
+ stdio: 'ignore',
69
+ windowsHide: true,
70
+ env: safeChildProcessEnv(),
71
+ });
72
+ const timer = setTimeout(() => { try {
73
+ killer.kill();
74
+ }
75
+ catch { /* exact helper only */ } resolve(); }, 5_000);
76
+ killer.once('error', () => { clearTimeout(timer); try {
77
+ child.kill();
78
+ }
79
+ catch { /* already gone */ } resolve(); });
80
+ killer.once('exit', () => { clearTimeout(timer); resolve(); });
81
+ });
82
+ return;
83
+ }
84
+ try {
85
+ process.kill(-pid, 'SIGTERM');
86
+ }
87
+ catch {
88
+ try {
89
+ child.kill('SIGTERM');
90
+ }
91
+ catch { /* already gone */ }
92
+ }
93
+ await new Promise((resolve) => setTimeout(resolve, 250));
94
+ try {
95
+ process.kill(-pid, 0);
96
+ process.kill(-pid, 'SIGKILL');
97
+ }
98
+ catch { /* process group exited */ }
99
+ }
100
+ export const runGameProcess = (pm, args, cwd, timeoutMs = 10 * 60_000, outputMode = 'inherit') => new Promise((resolve, reject) => {
101
+ const invocation = gameProcessInvocation(pm, args);
102
+ const platform = process.platform;
103
+ const child = spawn(invocation.executable, invocation.args, {
104
+ cwd,
105
+ stdio: outputMode === 'capture' ? ['ignore', 'pipe', 'pipe'] : 'inherit',
106
+ windowsHide: true,
107
+ env: safeChildProcessEnv(),
108
+ detached: platform !== 'win32',
109
+ });
110
+ let interrupted = false;
111
+ let timedOut = false;
112
+ let settled = false;
113
+ let stdout = '';
114
+ let stderr = '';
115
+ const appendBounded = (current, chunk) => `${current}${String(chunk)}`.slice(-64 * 1024);
116
+ if (outputMode === 'capture') {
117
+ child.stdout?.setEncoding('utf8');
118
+ child.stderr?.setEncoding('utf8');
119
+ child.stdout?.on('data', (chunk) => { stdout = appendBounded(stdout, chunk); });
120
+ child.stderr?.on('data', (chunk) => { stderr = appendBounded(stderr, chunk); });
121
+ }
122
+ const settle = (error) => {
123
+ if (settled)
124
+ return;
125
+ settled = true;
126
+ if (timer)
127
+ clearTimeout(timer);
128
+ process.removeListener('SIGINT', onInterrupt);
129
+ error ? reject(error) : resolve({ stdout, stderr });
130
+ };
131
+ const onInterrupt = () => {
132
+ interrupted = true;
133
+ void terminateOwnedProcessTree(child, platform);
134
+ };
135
+ process.once('SIGINT', onInterrupt);
136
+ const timer = timeoutMs > 0 ? setTimeout(() => {
137
+ timedOut = true;
138
+ void terminateOwnedProcessTree(child, platform).finally(() => settle(new CliCommandError(`${pm} command timed out after ${timeoutMs}ms`, { code: 'GAME_PROCESS_TIMEOUT' })));
139
+ }, timeoutMs) : null;
140
+ child.once('error', (error) => settle(error));
141
+ child.once('exit', (code, signal) => {
142
+ if (timedOut) {
143
+ settle(new CliCommandError(`${pm} command timed out after ${timeoutMs}ms`, { code: 'GAME_PROCESS_TIMEOUT' }));
144
+ }
145
+ else if (interrupted) {
146
+ settle(new CliCommandError(`${pm} command cancelled by user`, { code: 'GAME_PROCESS_CANCELLED', category: 'cancelled' }));
147
+ }
148
+ else if (code === 0) {
149
+ settle();
150
+ }
151
+ else {
152
+ const detail = outputMode === 'capture' ? `: ${(stderr || stdout).trim().slice(-4_000)}` : '';
153
+ settle(new Error(`${pm} exited with ${signal || code}${detail}`));
154
+ }
155
+ });
156
+ });
54
157
  const html = `<!doctype html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>VGE Game</title></head><body><canvas id="game"></canvas><div id="hud"><strong>Vigthoria Gaming Engine</strong><span>Ready</span></div><script type="module" src="/src/main.ts"></script></body></html>\n`;
55
158
  const style = `*{box-sizing:border-box}html,body,#game{width:100%;height:100%;margin:0;overflow:hidden}body{background:#030510;color:#e8f7ff;font-family:Inter,system-ui,sans-serif}#game{display:block;touch-action:none}#hud{position:fixed;left:24px;top:24px;display:flex;gap:16px;padding:12px 16px;border:1px solid #00d4ff66;border-radius:12px;background:#070a18cc;backdrop-filter:blur(12px);box-shadow:0 0 32px #7c3aed33}#hud span{color:#5eead4}\n`;
56
159
  const main = `import { ArcRotateCamera, Color3, Color4, DirectionalLight, Engine, HemisphericLight, MeshBuilder, PBRMaterial, Scene, ShadowGenerator, Vector3 } from '@babylonjs/core';
@@ -80,21 +183,22 @@ export class GameCommand {
80
183
  if (fs.existsSync(root) && fs.readdirSync(root).length)
81
184
  throw new Error(`Target directory is not empty: ${root}`);
82
185
  fs.mkdirSync(path.join(root, 'src'), { recursive: true });
83
- const pkg = { name: slug, version: '0.1.0', private: true, type: 'module', vigthoria: { kind: 'game', engine: 'babylon', renderTransport: 'client-state', schemaVersion: 1 }, scripts: { dev: 'vite', build: 'tsc --noEmit && vite build', preview: 'vite preview' }, dependencies: { '@babylonjs/core': '7.54.3', '@babylonjs/loaders': '7.54.3' }, devDependencies: { typescript: '5.8.3', vite: '6.1.0' } };
186
+ const pkg = { name: slug, version: '0.1.0', private: true, type: 'module', engines: { node: '>=20.19.0' }, vigthoria: { kind: 'game', engine: 'babylon', renderTransport: 'client-state', schemaVersion: 1 }, scripts: { dev: 'node ./node_modules/vite/bin/vite.js', build: 'node ./node_modules/typescript/bin/tsc --noEmit && node ./node_modules/vite/bin/vite.js build', preview: 'node ./node_modules/vite/bin/vite.js preview' }, dependencies: { '@babylonjs/core': '9.20.0', '@babylonjs/loaders': '9.20.0' }, devDependencies: { typescript: '5.9.3', vite: '7.3.6' } };
84
187
  fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify(pkg, null, 2) + '\n');
85
188
  fs.writeFileSync(path.join(root, 'index.html'), html);
86
189
  fs.writeFileSync(path.join(root, 'src/main.ts'), main);
87
190
  fs.writeFileSync(path.join(root, 'src/style.css'), style);
191
+ fs.writeFileSync(path.join(root, 'src/vite-env.d.ts'), '/// <reference types="vite/client" />\n');
88
192
  fs.writeFileSync(path.join(root, '.gitignore'), 'node_modules\ndist\n.vigthoria\n');
89
193
  fs.writeFileSync(path.join(root, 'tsconfig.json'), JSON.stringify({ compilerOptions: { target: 'ES2022', module: 'ESNext', lib: ['ES2022', 'DOM'], skipLibCheck: true, moduleResolution: 'Bundler', isolatedModules: true, noEmit: true, strict: true }, include: ['src'] }, null, 2) + '\n');
90
194
  if (o.install !== false)
91
- await run(manager(root, o.packageManager), ['install'], root);
195
+ await runGameProcess(manager(root, o.packageManager), ['install'], root);
92
196
  this.logger.success(`Babylon/VGE game created: ${root}`);
93
197
  this.logger.info(`Next: cd ${slug} && vigthoria game run`);
94
198
  }
95
199
  async run(o) { const root = path.resolve(o.project || process.cwd()); this.project(root); const args = ['run', 'dev', '--', '--host', '127.0.0.1']; if (o.port)
96
200
  args.push('--port', String(o.port)); if (o.open !== false)
97
- args.push('--open'); await run(manager(root, o.packageManager), args, root, 0); }
201
+ args.push('--open'); await runGameProcess(manager(root, o.packageManager), args, root, 0); }
98
202
  async validate(o) {
99
203
  const root = path.resolve(o.project || process.cwd());
100
204
  const checks = [];
@@ -110,7 +214,7 @@ export class GameCommand {
110
214
  const source = this.sources(root);
111
215
  checks.push({ check: 'Babylon client-state metadata', passed: pkg.vigthoria?.engine === 'babylon' && pkg.vigthoria?.renderTransport === 'client-state' }, { check: 'Pinned local Babylon dependencies', passed: /^\d/.test(pkg.dependencies?.['@babylonjs/core'] || '') && /^\d/.test(pkg.dependencies?.['@babylonjs/loaders'] || '') }, { check: 'No CDN engine imports', passed: !/https?:\/\/(cdn|unpkg|jsdelivr)[^\s"']*(babylon|three)/i.test(source) }, { check: 'Runtime diagnostics', passed: source.includes('__vgeDiagnostics') }, { check: 'Client source entry', passed: fs.existsSync(path.join(root, 'src/main.ts')) || fs.existsSync(path.join(root, 'src/main.js')) });
112
216
  try {
113
- await run(manager(root, o.packageManager), ['run', 'build'], root);
217
+ await runGameProcess(manager(root, o.packageManager), ['run', 'build'], root, 10 * 60_000, o.json ? 'capture' : 'inherit');
114
218
  checks.push({ check: 'Production build', passed: true });
115
219
  }
116
220
  catch (e) {
@@ -11,7 +11,6 @@ export declare class HistoryCommand {
11
11
  constructor(config: Config, logger: Logger);
12
12
  private getHeaders;
13
13
  private getBaseUrl;
14
- private resolveWorkspaceRoot;
15
14
  run(options: HistoryOptions): Promise<void>;
16
15
  }
17
16
  export {};
@@ -3,10 +3,10 @@ import { hasLocalV3AgentCapability } from '../utils/runtime-capability.js';
3
3
  * history.ts — List recent V3 agent runs with summaries.
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 { buildLocalWorkspaceReference } from '../utils/workspace-reference.js';
9
+ import { guardedFetch } from '../utils/network-policy.js';
10
10
  export class HistoryCommand {
11
11
  config;
12
12
  logger;
@@ -36,36 +36,22 @@ export class HistoryCommand {
36
36
  (allowLocal ? 'http://127.0.0.1:8030' : null) ||
37
37
  configuredApiUrl);
38
38
  }
39
- resolveWorkspaceRoot(project) {
40
- // On Windows or non-absolute paths, send empty so the server uses its fallback root
41
- if (/^[a-zA-Z]:[\\/]/.test(project) || /^\\\\/.test(project))
42
- return '';
43
- if (typeof require !== 'undefined') {
44
- try {
45
- const path = require('path');
46
- if (!path.isAbsolute(project))
47
- return '';
48
- }
49
- catch { }
50
- }
51
- return project;
52
- }
53
39
  async run(options) {
54
40
  const limit = options.limit || 20;
55
41
  const project = options.project || process.cwd();
56
- const workspace = this.resolveWorkspaceRoot(project);
42
+ const workspaceRef = buildLocalWorkspaceReference(project, String(this.config.get('userId') || this.config.get('email') || ''));
57
43
  const spinner = createSpinner('Loading run history...').start();
58
44
  try {
59
45
  const baseUrl = this.getBaseUrl();
60
46
  const params = new URLSearchParams({
61
47
  limit: String(limit),
62
- workspace_root: workspace,
63
- local_workspace_path: project,
64
- project_path: project,
48
+ workspace_root: '',
49
+ local_workspace_path: workspaceRef,
50
+ project_path: workspaceRef,
65
51
  });
66
- const resp = await fetch(`${baseUrl}/api/runs?${params}`, {
52
+ const resp = await guardedFetch(`${baseUrl}/api/runs?${params}`, {
67
53
  headers: this.getHeaders(),
68
- });
54
+ }, { audience: 'v3' });
69
55
  if (!resp.ok) {
70
56
  spinner.stop();
71
57
  if (resp.status === 404 || resp.status === 502 || resp.status === 503) {
@@ -6,6 +6,25 @@
6
6
  */
7
7
  import { Config } from '../utils/config.js';
8
8
  import { Logger } from '../utils/logger.js';
9
+ interface Module {
10
+ id: string;
11
+ name: string;
12
+ description: string;
13
+ endpoint: string;
14
+ documentation: string;
15
+ pricing: {
16
+ unit: string;
17
+ cost: number;
18
+ example: string;
19
+ };
20
+ status: string;
21
+ category: string;
22
+ tags: string[];
23
+ unit?: string;
24
+ creditsPerUnit?: number | string;
25
+ euroPerUnit?: number | string;
26
+ }
27
+ export declare function moduleMatchesIdentifier(module: Pick<Module, 'id' | 'name'>, requested: string): boolean;
9
28
  export declare class HubCommand {
10
29
  private config;
11
30
  constructor(config: Config, _logger: Logger);
@@ -39,3 +58,4 @@ export declare class HubCommand {
39
58
  */
40
59
  discover(): Promise<void>;
41
60
  }
61
+ export {};
@@ -8,6 +8,19 @@ import chalk from 'chalk';
8
8
  import { guardedFetch } from '../utils/network-policy.js';
9
9
  import { CliCommandError, commandFailure } from '../utils/command-contract.js';
10
10
  const API_BASE = 'https://hub.vigthoria.io';
11
+ function normalizeModuleIdentifier(value) {
12
+ return String(value || '').trim().toLowerCase().replace(/[\s_]+/g, '-');
13
+ }
14
+ export function moduleMatchesIdentifier(module, requested) {
15
+ const needle = normalizeModuleIdentifier(requested);
16
+ if (!needle)
17
+ return false;
18
+ const id = normalizeModuleIdentifier(module.id);
19
+ const name = normalizeModuleIdentifier(module.name);
20
+ return needle === id
21
+ || needle === name
22
+ || needle === id.replace(/-api$/, '');
23
+ }
11
24
  export class HubCommand {
12
25
  config;
13
26
  constructor(config, _logger) {
@@ -76,6 +89,7 @@ export class HubCommand {
76
89
  data.results.forEach((module, index) => {
77
90
  const statusColor = module.status === 'available' ? chalk.green : chalk.gray;
78
91
  console.log(chalk.bold.white(` ${index + 1}. ${module.name}`));
92
+ console.log(chalk.gray(` Module ID: ${module.id}`));
79
93
  console.log(chalk.gray(` ${module.description.substring(0, 80)}...`));
80
94
  console.log(chalk.cyan(` 💰 ${module.pricing.example}`));
81
95
  console.log(statusColor(` 📊 Status: ${module.status.toUpperCase()}`));
@@ -86,7 +100,7 @@ export class HubCommand {
86
100
  console.log(chalk.cyan(`💡 ${data.suggestion}`));
87
101
  }
88
102
  console.log(chalk.gray('\nTo activate a module: vigthoria hub activate <module-name>'));
89
- console.log(chalk.gray('Example: vigthoria hub activate music\n'));
103
+ console.log(chalk.gray(`Example: vigthoria hub activate ${data.results[0].id}\n`));
90
104
  }
91
105
  catch (error) {
92
106
  throw commandFailure(error, { code: 'HUB_SEARCH_FAILED' });
@@ -144,7 +158,7 @@ export class HubCommand {
144
158
  this.getAuthToken();
145
159
  console.log(chalk.cyan(`\n🔌 Activating module: ${moduleId}...\n`));
146
160
  try {
147
- const module = (await this.moduleCatalog()).find((entry) => entry.id === moduleId);
161
+ const module = (await this.moduleCatalog()).find((entry) => moduleMatchesIdentifier(entry, moduleId));
148
162
  if (!module)
149
163
  throw new CliCommandError(`Unknown Hub module: ${moduleId}`, { code: 'HUB_MODULE_NOT_FOUND', category: 'usage' });
150
164
  console.log(chalk.green(`✅ ${module.name} is published in the Vigthoria module catalog.`));
@@ -186,7 +200,7 @@ export class HubCommand {
186
200
  async info(moduleId) {
187
201
  console.log(chalk.cyan(`\n📖 Module Info: ${moduleId}\n`));
188
202
  try {
189
- const module = (await this.moduleCatalog()).find((entry) => entry.id === moduleId);
203
+ const module = (await this.moduleCatalog()).find((entry) => moduleMatchesIdentifier(entry, moduleId));
190
204
  if (!module)
191
205
  throw new CliCommandError(`Unknown Hub module: ${moduleId}`, { code: 'HUB_MODULE_NOT_FOUND', category: 'usage' });
192
206
  console.log(chalk.bold.white(`${module.name}`));
@@ -81,7 +81,7 @@ export class PreviewCommand {
81
81
  // Run Template Service preview proof gate
82
82
  if (isPreviewProofRequested(options)) {
83
83
  try {
84
- await this.runProofGate(projectPath, options.screenshot);
84
+ await this.runProofGate(projectPath, options.screenshot, options.screenshot === true && options.proof !== true);
85
85
  }
86
86
  catch (error) {
87
87
  this.api.destroy();
@@ -435,7 +435,7 @@ export class PreviewCommand {
435
435
  /**
436
436
  * Run Template Service preview gate and persist proof bundle
437
437
  */
438
- async runProofGate(projectPath, captureScreenshot) {
438
+ async runProofGate(projectPath, captureScreenshot, localScreenshotProof = false) {
439
439
  const spinner = createSpinner('Running preview proof gate...').start();
440
440
  try {
441
441
  const result = await this.api.runTemplateServicePreviewGate('', {
@@ -444,6 +444,11 @@ export class PreviewCommand {
444
444
  targetPath: projectPath,
445
445
  forceFrontendPreview: true,
446
446
  requireScreenshot: captureScreenshot === true,
447
+ // An explicit screenshot-only command is a local operation. Do not
448
+ // upload the user's frontend or require an ecosystem login merely to
449
+ // capture local visual evidence. `--proof` retains the hosted proof
450
+ // contract, including when combined with `--screenshot`.
451
+ localScreenshotProof,
447
452
  });
448
453
  spinner.stop();
449
454
  console.log(chalk.bold.white(` ${CH.hLine.repeat(3)} Preview Proof Gate ${CH.hLine.repeat(39)}`));
@@ -386,7 +386,7 @@ Examples:
386
386
  .option('--no-open', 'Do not auto-open browser')
387
387
  .option('--diff', 'Show consolidated diff of recent agent changes')
388
388
  .option('--proof', 'Run Template Service preview gate and persist proof bundle')
389
- .option('--screenshot', 'Capture screenshot via Puppeteer')
389
+ .option('--screenshot', 'Capture screenshot via an installed system browser')
390
390
  .action(async (options) => {
391
391
  const preview = new PreviewCommand(config, logger);
392
392
  await preview.run({
@@ -10,7 +10,6 @@ export declare class ReplayCommand {
10
10
  constructor(config: Config, _logger: Logger);
11
11
  private getHeaders;
12
12
  private getBaseUrl;
13
- private resolveWorkspaceRoot;
14
13
  private sleep;
15
14
  run(runId: string, options: ReplayOptions): Promise<void>;
16
15
  }
@@ -3,10 +3,10 @@ import { hasLocalV3AgentCapability } from '../utils/runtime-capability.js';
3
3
  * replay.ts — Replay events from a V3 agent run step-by-step.
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 { buildLocalWorkspaceReference } from '../utils/workspace-reference.js';
9
+ import { guardedFetch } from '../utils/network-policy.js';
10
10
  export class ReplayCommand {
11
11
  config;
12
12
  constructor(config, _logger) {
@@ -34,33 +34,24 @@ export class ReplayCommand {
34
34
  (allowLocal ? 'http://127.0.0.1:8030' : null) ||
35
35
  configuredApiUrl);
36
36
  }
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 '';
45
- }
46
- catch { }
47
- }
48
- return project;
49
- }
50
37
  sleep(ms) {
51
38
  return new Promise((resolve) => setTimeout(resolve, ms));
52
39
  }
53
40
  async run(runId, options) {
54
41
  const speed = options.speed || 200;
55
42
  const project = options.project || process.cwd();
56
- const workspace = this.resolveWorkspaceRoot(project);
43
+ const workspaceRef = buildLocalWorkspaceReference(project, String(this.config.get('userId') || this.config.get('email') || ''));
57
44
  const spinner = createSpinner(`Loading events for run ${runId}...`).start();
58
45
  try {
59
46
  const baseUrl = this.getBaseUrl();
60
- const params = new URLSearchParams({ workspace_root: workspace });
61
- const resp = await fetch(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/events?${params}`, {
62
- headers: this.getHeaders(),
47
+ const params = new URLSearchParams({
48
+ workspace_root: '',
49
+ local_workspace_path: workspaceRef,
50
+ project_path: workspaceRef,
63
51
  });
52
+ const resp = await guardedFetch(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/events?${params}`, {
53
+ headers: this.getHeaders(),
54
+ }, { audience: 'v3' });
64
55
  if (!resp.ok) {
65
56
  spinner.stop();
66
57
  if (resp.status === 404) {
@@ -161,6 +161,9 @@ export class RepoCommand {
161
161
  };
162
162
  try {
163
163
  const proxyResponse = await attempt(proxyPath, 'coder');
164
+ if (options.terminalStatuses?.includes(proxyResponse.status)) {
165
+ return proxyResponse;
166
+ }
164
167
  // A large repository payload can exceed the Coder API's general
165
168
  // 50 MiB parser while remaining valid under Community's 100 MiB
166
169
  // repository contract. Retry that one status directly.
@@ -615,7 +618,7 @@ export class RepoCommand {
615
618
  if (fs.existsSync(outputPath))
616
619
  validateExtractedWorkspace(outputPath);
617
620
  if (isCompleteRepositoryInlinePayload(data)) {
618
- const stagingRoot = createRuntimeTempDirectory('repo-inline-');
621
+ const stagingRoot = createRuntimeTempDirectory('repo-inline-', 128 * 1024 * 1024);
619
622
  try {
620
623
  let totalBytes = 0;
621
624
  for (const file of data.files) {
@@ -650,7 +653,7 @@ export class RepoCommand {
650
653
  throw new Error('Failed to download project archive');
651
654
  const archiveBuffer = Buffer.from(await archiveResponse.arrayBuffer());
652
655
  inspectZipArchive(archiveBuffer);
653
- const stagingRoot = createRuntimeTempDirectory('repo-pull-');
656
+ const stagingRoot = createRuntimeTempDirectory('repo-pull-', 256 * 1024 * 1024);
654
657
  const tempArchive = path.join(stagingRoot, 'archive.zip');
655
658
  const extractedPath = path.join(stagingRoot, 'extracted');
656
659
  try {
@@ -920,11 +923,20 @@ export class RepoCommand {
920
923
  const spinner = createSpinner('Deleting project...').start();
921
924
  try {
922
925
  const operationId = createOperationId();
923
- const repo = await this.resolveRepoByName(projectName);
926
+ // A numeric canonical ID is safe to retry directly even after the
927
+ // first deletion removed it from the active repository listing.
928
+ const repo = /^\d+$/.test(projectName.trim())
929
+ ? { id: projectName.trim() }
930
+ : await this.resolveRepoByName(projectName);
924
931
  const response = await this.repoFetch(`/api/repo/projects/${encodeURIComponent(String(repo.id))}`, {
925
932
  method: 'DELETE',
926
933
  headers: { 'X-Vigthoria-Operation-Id': operationId },
927
- }, { operationId });
934
+ }, { operationId, terminalStatuses: /^\d+$/.test(projectName.trim()) ? [404] : undefined });
935
+ if (response.status === 404 && /^\d+$/.test(projectName.trim())) {
936
+ spinner.succeed(chalk.green('Project was already deleted from Vigthoria Community Repository'));
937
+ console.log(chalk.gray('\nNote: Your local files are not affected.\n'));
938
+ return;
939
+ }
928
940
  if (!response.ok) {
929
941
  const error = await response.json();
930
942
  throw new Error(error.error || 'Failed to delete project');
@@ -186,7 +186,7 @@ export function registerUpdateCommand(program, version) {
186
186
  let updateTempDirectory = null;
187
187
  try {
188
188
  if (source.kind === 'remote') {
189
- updateTempDirectory = createRuntimeTempDirectory('update-');
189
+ updateTempDirectory = createRuntimeTempDirectory('update-', 320 * 1024 * 1024);
190
190
  installTarget = path.join(updateTempDirectory, 'candidate.tgz');
191
191
  await downloadFile(source.downloadUrl, installTarget);
192
192
  }
@@ -304,7 +304,7 @@ export function registerUpdateCommand(program, version) {
304
304
  && compareVersions(manifestEntry.version, currentVersion) > 0);
305
305
  if (manifestIsAuthoritative && manifestEntry) {
306
306
  assertReleaseTransition(currentVersion, manifestEntry, channel, allowDowngrade);
307
- const updateTempDirectory = createRuntimeTempDirectory('update-');
307
+ const updateTempDirectory = createRuntimeTempDirectory('update-', 320 * 1024 * 1024);
308
308
  const tmpFile = path.join(updateTempDirectory, 'candidate.tgz');
309
309
  try {
310
310
  console.log(chalk.cyan(`Downloading release package (${manifestEntry.version})...`));
@@ -19,6 +19,9 @@ interface WorkflowRunOptions extends WorkflowOutputOptions {
19
19
  interface WorkflowStatusOptions extends WorkflowOutputOptions {
20
20
  brain?: boolean;
21
21
  }
22
+ interface WorkflowDeleteOptions extends WorkflowOutputOptions {
23
+ yes?: boolean;
24
+ }
22
25
  export declare class WorkflowCommand {
23
26
  private config;
24
27
  private logger;
@@ -35,6 +38,7 @@ export declare class WorkflowCommand {
35
38
  useTemplate(templateId: string, options: WorkflowUseOptions): Promise<void>;
36
39
  run(workflowId: string, options: WorkflowRunOptions): Promise<void>;
37
40
  status(executionId: string, options: WorkflowStatusOptions): Promise<void>;
41
+ delete(selector: string, options: WorkflowDeleteOptions): Promise<void>;
38
42
  }
39
43
  export declare function registerWorkflowCommands(program: Command, config: Config, logger: Logger): void;
40
44
  export {};
@@ -190,6 +190,29 @@ export class WorkflowCommand {
190
190
  `Completed: ${execution.completedAt || '-'}`,
191
191
  ].join('\n'), 'Workflow Status');
192
192
  }
193
+ async delete(selector, options) {
194
+ this.ensureAuthenticated();
195
+ if (options.yes !== true) {
196
+ throw new CliCommandError('Workflow deletion requires explicit confirmation with --yes.', {
197
+ code: 'WORKFLOW_DELETE_CONFIRMATION_REQUIRED',
198
+ category: 'usage',
199
+ });
200
+ }
201
+ let deleted;
202
+ try {
203
+ deleted = await this.api.deleteVigFlowWorkflow(selector);
204
+ }
205
+ catch (error) {
206
+ throw commandFailure(error, { code: 'WORKFLOW_DELETE_FAILED' });
207
+ }
208
+ if (options.json) {
209
+ this.printJson('workflow delete', { deleted });
210
+ return;
211
+ }
212
+ this.logger.success(deleted.alreadyDeleted
213
+ ? `Workflow ${deleted.id} was already deleted.`
214
+ : `Workflow ${deleted.name || deleted.id} deleted.`);
215
+ }
193
216
  }
194
217
  export function registerWorkflowCommands(program, config, logger) {
195
218
  const workflowCommand = program.command('workflow').alias('flow')
@@ -215,5 +238,9 @@ export function registerWorkflowCommands(program, config, logger) {
215
238
  .option('--no-brain', 'Do not remember this workflow status in local Project Brain')
216
239
  .option('--json', 'Emit machine-readable JSON output', false)
217
240
  .action(async (executionId, options) => new WorkflowCommand(config, logger).status(executionId, options));
241
+ workflowCommand.command('delete <workflowIdOrName>').alias('rm').description('Delete an owned workflow (idempotent by workflow ID)')
242
+ .option('--yes', 'Confirm permanent workflow deletion', false)
243
+ .option('--json', 'Emit machine-readable JSON output', false)
244
+ .action(async (selector, options) => new WorkflowCommand(config, logger).delete(selector, options));
218
245
  workflowCommand.action(async () => new WorkflowCommand(config, logger).templates({}));
219
246
  }
package/dist/index.js CHANGED
@@ -51,6 +51,7 @@ import { installConsoleRedaction } from './utils/secret-policy.js';
51
51
  import { commandNameFromArgv, failureEnvelope, normalizeCommandError, CliCommandError, } from './utils/command-contract.js';
52
52
  import { commandAuthRequirement, commanderCommandPath } from './utils/command-policy.js';
53
53
  import { initializeRuntimeTempStorage } from './utils/runtime-temp.js';
54
+ import { hasLocalV3ServiceIdentity } from './utils/runtime-capability.js';
54
55
  initializeRuntimeTempStorage();
55
56
  applyLocalTestfarmDefaults();
56
57
  if (process.env.VIGTHORIA_CAPTURE_RUNTIME_MODEL !== '1')
@@ -534,11 +535,12 @@ export async function main(args) {
534
535
  const authRequirement = commandAuthRequirement(commandPath);
535
536
  if (authRequirement === 'none')
536
537
  return;
537
- // On-box service identities are authenticated by their destination
538
- // service and must not be confused with a user gateway session.
539
- const hasServiceKey = Boolean(process.env.HYPERLOOP_SERVICE_KEY || process.env.V3_SERVICE_KEY);
538
+ // A local V3 service identity is valid only for Agent. It must never
539
+ // bypass session policy for coding, repository, deploy, workflow, or any
540
+ // other command merely because a service key exists on this host.
541
+ const localAgentServiceIdentity = commandPath === 'agent' && hasLocalV3ServiceIdentity();
540
542
  const legionCortex = commandPath === 'legion' && actionCommand.opts().cortex === true;
541
- if (hasServiceKey || legionCortex)
543
+ if (localAgentServiceIdentity || legionCortex)
542
544
  return;
543
545
  const explicitEnvToken = Boolean(process.env.VIGTHORIA_TOKEN || process.env.VIGTHORIA_AUTH_TOKEN);
544
546
  if (authRequirement === 'required-session' && !config.isAuthenticated() && !explicitEnvToken) {
@@ -37,6 +37,13 @@ export interface RunEvaluation {
37
37
  statusHeadline: string;
38
38
  uiTheme: 'success' | 'warning' | 'error';
39
39
  }
40
+ /** Preserve mutation-journal truth while avoiding a false partial-mutation
41
+ * classification for a read-only run that changed no files. A failed or
42
+ * unknown rollback always wins over the observed final file count because a
43
+ * rollback can remove the changed-file snapshot while still leaving state
44
+ * uncertain on disk.
45
+ */
46
+ export declare function resolveAgentPartialMutation(changedFileCount: number, reported: unknown): false | 'unknown';
40
47
  export declare function createLiveOutcome(): LiveOutcome;
41
48
  export declare function isExecutorTimeoutFailure(liveOutcome: LiveOutcome): boolean;
42
49
  export declare function isInferenceDegradedFailure(liveOutcome: LiveOutcome): boolean;
@@ -2,6 +2,19 @@
2
2
  * Task-and-quality-validated success evaluation for V3 agent runs.
3
3
  * Decouples CLI verdict from raw file-change counts (1.11.0+).
4
4
  */
5
+ /** Preserve mutation-journal truth while avoiding a false partial-mutation
6
+ * classification for a read-only run that changed no files. A failed or
7
+ * unknown rollback always wins over the observed final file count because a
8
+ * rollback can remove the changed-file snapshot while still leaving state
9
+ * uncertain on disk.
10
+ */
11
+ export function resolveAgentPartialMutation(changedFileCount, reported) {
12
+ if (reported === true || reported === 'unknown')
13
+ return 'unknown';
14
+ if (reported === false)
15
+ return false;
16
+ return changedFileCount > 0 ? 'unknown' : false;
17
+ }
5
18
  export function createLiveOutcome() {
6
19
  return {
7
20
  executorFailed: false,