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
@@ -23,6 +23,35 @@ import { CliCommandError, commandFailure, formatSuccessJson } from '../utils/com
23
23
  import { createRuntimeTempDirectory, removeRuntimeTempDirectory } from '../utils/runtime-temp.js';
24
24
  import { assertSafeDestinationPath, assertSafeRelativePath, inspectZipArchive, mergeValidatedWorkspace, resolveWorkspacePath, validateExtractedWorkspace, } from '../utils/workspace-boundary.js';
25
25
  import inquirer from 'inquirer';
26
+ export function isCompleteRepositoryInlinePayload(data) {
27
+ if (!Array.isArray(data.files) || data.files.length === 0 || data.inlineComplete === false)
28
+ return false;
29
+ return data.files.every((file) => typeof file?.path === 'string'
30
+ && typeof file?.content === 'string'
31
+ && (file.encoding === undefined || file.encoding === 'utf8' || file.encoding === 'base64')
32
+ && file.binary !== true);
33
+ }
34
+ export function resolveRepositoryDownloadRoute(rawUrl, coderApiBase, publicDownload = false) {
35
+ const parsed = new URL(rawUrl);
36
+ if (parsed.protocol !== 'https:' || parsed.username || parsed.password || parsed.port) {
37
+ throw new Error('Repository download URL is not an exact trusted HTTPS origin');
38
+ }
39
+ if (parsed.hostname.toLowerCase() === 'coder.vigthoria.io')
40
+ return { url: parsed.href, audience: 'coder' };
41
+ if (parsed.hostname.toLowerCase() !== 'community.vigthoria.io') {
42
+ throw new Error('Repository download URL is outside the Coder and Community trust boundary');
43
+ }
44
+ if (publicDownload)
45
+ return { url: parsed.href, audience: 'community' };
46
+ const match = parsed.pathname.match(/^\/api\/showcase\/(\d+)\/download$/);
47
+ if (!match || parsed.search || parsed.hash) {
48
+ throw new Error('Authenticated repository archive URL has an unsupported path');
49
+ }
50
+ return {
51
+ url: `${coderApiBase.replace(/\/$/, '')}/api/community-repo/archive/${encodeURIComponent(match[1])}`,
52
+ audience: 'coder',
53
+ };
54
+ }
26
55
  const MAX_REPOSITORY_CONTENT_BYTES = 100 * 1024 * 1024;
27
56
  const MAX_REPOSITORY_REQUEST_BYTES = 120 * 1024 * 1024;
28
57
  class RepositoryApiError extends Error {
@@ -87,17 +116,6 @@ export class RepoCommand {
87
116
  'Content-Type': 'application/json',
88
117
  };
89
118
  }
90
- getRepositoryAudienceForUrl(rawUrl) {
91
- const hostname = new URL(rawUrl).hostname.toLowerCase();
92
- if (hostname === 'coder.vigthoria.io')
93
- return 'coder';
94
- if (hostname === 'community.vigthoria.io')
95
- return 'community';
96
- throw new Error('Repository download URL is outside the Coder and Community trust boundary');
97
- }
98
- getAuthHeadersForUrl(rawUrl) {
99
- return this.getAuthHeaders(this.getRepositoryAudienceForUrl(rawUrl));
100
- }
101
119
  async ensureCommunityAuth() {
102
120
  if (this.communityToken) {
103
121
  return;
@@ -143,6 +161,9 @@ export class RepoCommand {
143
161
  };
144
162
  try {
145
163
  const proxyResponse = await attempt(proxyPath, 'coder');
164
+ if (options.terminalStatuses?.includes(proxyResponse.status)) {
165
+ return proxyResponse;
166
+ }
146
167
  // A large repository payload can exceed the Coder API's general
147
168
  // 50 MiB parser while remaining valid under Community's 100 MiB
148
169
  // repository contract. Retry that one status directly.
@@ -596,17 +617,43 @@ export class RepoCommand {
596
617
  try {
597
618
  if (fs.existsSync(outputPath))
598
619
  validateExtractedWorkspace(outputPath);
599
- if (data.downloadUrl) {
600
- const archiveResponse = await guardedFetch(data.downloadUrl, {
620
+ if (isCompleteRepositoryInlinePayload(data)) {
621
+ const stagingRoot = createRuntimeTempDirectory('repo-inline-', 128 * 1024 * 1024);
622
+ try {
623
+ let totalBytes = 0;
624
+ for (const file of data.files) {
625
+ const filePath = resolveWorkspacePath(stagingRoot, file.path, { allowMissing: true });
626
+ const content = file.encoding === 'base64'
627
+ ? Buffer.from(file.content, 'base64')
628
+ : Buffer.from(file.content, 'utf8');
629
+ if (typeof file.size === 'number' && file.size !== content.byteLength) {
630
+ throw new Error(`Repository file size mismatch: ${file.path}`);
631
+ }
632
+ totalBytes += content.byteLength;
633
+ if (totalBytes > MAX_REPOSITORY_CONTENT_BYTES)
634
+ throw new Error('Repository inline payload exceeds the materialization limit');
635
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
636
+ fs.writeFileSync(filePath, content, { mode: 0o600 });
637
+ }
638
+ validateExtractedWorkspace(stagingRoot);
639
+ mergeValidatedWorkspace(stagingRoot, outputPath);
640
+ }
641
+ finally {
642
+ removeRuntimeTempDirectory(stagingRoot);
643
+ }
644
+ }
645
+ else if (data.downloadUrl) {
646
+ const route = resolveRepositoryDownloadRoute(data.downloadUrl, this.apiBase, publicDownload);
647
+ const archiveResponse = await guardedFetch(route.url, {
601
648
  headers: publicDownload
602
649
  ? { 'Content-Type': 'application/json' }
603
- : this.getAuthHeadersForUrl(data.downloadUrl)
604
- }, { audience: this.getRepositoryAudienceForUrl(data.downloadUrl) });
650
+ : this.getAuthHeaders(route.audience)
651
+ }, { audience: route.audience });
605
652
  if (!archiveResponse.ok)
606
653
  throw new Error('Failed to download project archive');
607
654
  const archiveBuffer = Buffer.from(await archiveResponse.arrayBuffer());
608
655
  inspectZipArchive(archiveBuffer);
609
- const stagingRoot = createRuntimeTempDirectory('repo-pull-');
656
+ const stagingRoot = createRuntimeTempDirectory('repo-pull-', 256 * 1024 * 1024);
610
657
  const tempArchive = path.join(stagingRoot, 'archive.zip');
611
658
  const extractedPath = path.join(stagingRoot, 'extracted');
612
659
  try {
@@ -638,23 +685,8 @@ export class RepoCommand {
638
685
  removeRuntimeTempDirectory(stagingRoot);
639
686
  }
640
687
  }
641
- else if (data.files) {
642
- const stagingRoot = createRuntimeTempDirectory('repo-inline-');
643
- try {
644
- for (const file of data.files) {
645
- const filePath = resolveWorkspacePath(stagingRoot, file.path, { allowMissing: true });
646
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
647
- fs.writeFileSync(filePath, file.content, { mode: 0o600 });
648
- }
649
- validateExtractedWorkspace(stagingRoot);
650
- mergeValidatedWorkspace(stagingRoot, outputPath);
651
- }
652
- finally {
653
- removeRuntimeTempDirectory(stagingRoot);
654
- }
655
- }
656
688
  else {
657
- throw new Error('Repository response contained neither an archive nor inline files');
689
+ throw new Error('Repository response contained neither a complete inline payload nor an archive');
658
690
  }
659
691
  downloadSpinner.succeed(chalk.green('Project materialized successfully!'));
660
692
  console.log(chalk.cyan('\n📁 Project extracted to:'));
@@ -891,10 +923,20 @@ export class RepoCommand {
891
923
  const spinner = createSpinner('Deleting project...').start();
892
924
  try {
893
925
  const operationId = createOperationId();
894
- const response = await this.repoFetch(`/api/repo/projects/${encodeURIComponent(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);
931
+ const response = await this.repoFetch(`/api/repo/projects/${encodeURIComponent(String(repo.id))}`, {
895
932
  method: 'DELETE',
896
933
  headers: { 'X-Vigthoria-Operation-Id': operationId },
897
- }, { 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
+ }
898
940
  if (!response.ok) {
899
941
  const error = await response.json();
900
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
  }
@@ -252,7 +252,7 @@ export function registerUpdateCommand(program, version) {
252
252
  if (!signatureResponse.ok)
253
253
  throw new Error(`Detached signature returned HTTP ${signatureResponse.status}`);
254
254
  verifyReleaseSignature(channel, manifestEntry, await signatureResponse.text());
255
- assertReleaseTransition(currentVersion, manifestEntry, channel, allowDowngrade);
255
+ console.log(chalk.gray(`Verified ${channel} manifest: version=${manifestEntry.version} package=${manifestEntry.packageName}@${manifestEntry.packageVersion} size=${manifestEntry.size} sha256=${manifestEntry.sha256}`));
256
256
  }
257
257
  catch (error) {
258
258
  if (error instanceof CliCommandError)
@@ -303,7 +303,8 @@ export function registerUpdateCommand(program, version) {
303
303
  && compareVersions(manifestEntry.version, effectiveLatest) >= 0
304
304
  && compareVersions(manifestEntry.version, currentVersion) > 0);
305
305
  if (manifestIsAuthoritative && manifestEntry) {
306
- const updateTempDirectory = createRuntimeTempDirectory('update-');
306
+ assertReleaseTransition(currentVersion, manifestEntry, channel, allowDowngrade);
307
+ const updateTempDirectory = createRuntimeTempDirectory('update-', 320 * 1024 * 1024);
307
308
  const tmpFile = path.join(updateTempDirectory, 'candidate.tgz');
308
309
  try {
309
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,
@@ -30,6 +30,17 @@ export type ChatRequestOptions = {
30
30
  export interface APIClientDependencies {
31
31
  screenshotAdapter?: PreviewScreenshotPort;
32
32
  }
33
+ type V3ClientToolMutation = {
34
+ path: string;
35
+ content: string;
36
+ sha256: string;
37
+ };
38
+ type V3ClientToolResult = {
39
+ success: boolean;
40
+ output: string;
41
+ error?: string;
42
+ mutation?: V3ClientToolMutation;
43
+ };
33
44
  export declare class CLIError extends Error {
34
45
  category: CLIErrorCategory;
35
46
  statusCode?: number;
@@ -167,6 +178,11 @@ export declare class APIClient {
167
178
  executionOptions?: Record<string, unknown>;
168
179
  }): Promise<VigFlowExecutionResult>;
169
180
  getVigFlowExecutionStatus(executionId: string): Promise<VigFlowExecutionStatus>;
181
+ deleteVigFlowWorkflow(selector: string): Promise<{
182
+ id: string;
183
+ name?: string;
184
+ alreadyDeleted?: boolean;
185
+ }>;
170
186
  getV3AgentHeaders(): Promise<Record<string, string>>;
171
187
  ensureV3ServiceKey(): Promise<void>;
172
188
  runV3HealthCheck(): Promise<{
@@ -229,12 +245,15 @@ export declare class APIClient {
229
245
  captureV3AgentStreamMutation(event: any, streamedFiles: Record<string, string>, serverRoot?: string | null): void;
230
246
  private applyV3AgentStreamEventToWorkspace;
231
247
  private resolveV3ClientToolPath;
248
+ private describeMissingV3ClientToolPath;
232
249
  private recordV3ClientToolMutation;
250
+ private buildV3ClientToolMutation;
233
251
  private executeV3ClientToolRequest;
234
252
  private handleV3ClientToolRequest;
235
253
  private writeV3AgentWorkspaceFile;
236
254
  recoverAgentWorkspaceFiles(context?: Record<string, any>, streamedFiles?: Record<string, string>, expectedFiles?: string[]): void;
237
255
  normalizeAgentWorkspaceRelativePath(rawPath: string, rootPath?: string): string;
256
+ private isExplicitSingleFileFrontendRequest;
238
257
  ensureAgentFrontendPolish(message?: string, context?: Record<string, any>): Promise<void>;
239
258
  private injectSectionBeforeFooter;
240
259
  private injectNavLink;
@@ -342,11 +361,7 @@ export declare class APIClient {
342
361
  getClientToolErrors(): string[];
343
362
  clearAgentRunDiagnostics(): void;
344
363
  private recordClientToolError;
345
- submitClientToolResult(contextId: string, callId: string, result: {
346
- success: boolean;
347
- output: string;
348
- error?: string;
349
- }, backendUrl?: string | null): Promise<void>;
364
+ submitClientToolResult(contextId: string, callId: string, result: V3ClientToolResult, backendUrl?: string | null): Promise<void>;
350
365
  private isHealthyServicePayload;
351
366
  private extractModelCount;
352
367
  private probeModelList;