vigthoria-cli 1.13.26 → 1.13.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/completions/_vigthoria +1 -0
- package/completions/vigthoria.bash +1 -1
- package/completions/vigthoria.fish +1 -0
- package/dist/commands/chat.js +73 -26
- package/dist/commands/config.js +4 -4
- package/dist/commands/creative-registration.d.ts +13 -0
- package/dist/commands/creative-registration.js +88 -0
- package/dist/commands/fork.d.ts +3 -2
- package/dist/commands/fork.js +124 -123
- package/dist/commands/game.d.ts +8 -0
- package/dist/commands/game.js +113 -9
- package/dist/commands/history.d.ts +0 -1
- package/dist/commands/history.js +8 -22
- package/dist/commands/hub.d.ts +20 -0
- package/dist/commands/hub.js +17 -3
- package/dist/commands/preview.js +7 -2
- package/dist/commands/product-run-registration.js +1 -1
- package/dist/commands/replay.d.ts +0 -1
- package/dist/commands/replay.js +10 -19
- package/dist/commands/repo.js +16 -4
- package/dist/commands/update-registration.js +2 -2
- package/dist/commands/workflow.d.ts +4 -0
- package/dist/commands/workflow.js +27 -0
- package/dist/index.js +8 -4
- package/dist/utils/agentRunOutcome.d.ts +7 -0
- package/dist/utils/agentRunOutcome.js +13 -0
- package/dist/utils/api.d.ts +20 -5
- package/dist/utils/api.js +428 -43
- package/dist/utils/command-policy.js +3 -1
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.js +22 -9
- package/dist/utils/frontend-preview-service.d.ts +1 -0
- package/dist/utils/frontend-preview-service.js +54 -5
- package/dist/utils/model-governance.js +23 -14
- package/dist/utils/model-transport-service.js +1 -1
- package/dist/utils/network-policy.js +15 -3
- package/dist/utils/operator-client.js +23 -4
- package/dist/utils/post-write-validator.js +7 -3
- package/dist/utils/preview-screenshot-adapter.d.ts +16 -41
- package/dist/utils/preview-screenshot-adapter.js +273 -64
- package/dist/utils/runtime-capability.d.ts +7 -0
- package/dist/utils/runtime-capability.js +11 -0
- package/dist/utils/runtime-temp.d.ts +5 -2
- package/dist/utils/runtime-temp.js +131 -30
- package/dist/utils/tools.js +1 -1
- package/dist/utils/v3-stream-events.js +10 -2
- package/dist/utils/v3-workspace-service.d.ts +1 -0
- package/dist/utils/v3-workspace-service.js +38 -1
- package/dist/utils/vigflow-client.d.ts +9 -0
- package/dist/utils/vigflow-client.js +48 -2
- package/dist/utils/workspace-reference.d.ts +8 -0
- package/dist/utils/workspace-reference.js +21 -0
- package/install.ps1 +2 -2
- package/install.sh +2 -2
- package/package.json +4 -6
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +2 -2
- package/scripts/release/validate-live-service-gates.sh +3 -3
- package/scripts/release/validate-no-go-gates.sh +2 -0
|
@@ -4,11 +4,14 @@ import * as path from 'node:path';
|
|
|
4
4
|
const MIB = 1024 * 1024;
|
|
5
5
|
const DEFAULT_MAX_BYTES = 1024 * MIB;
|
|
6
6
|
const DEFAULT_MIN_FREE_BYTES = 128 * MIB;
|
|
7
|
+
const DEFAULT_ALLOCATION_MAX_BYTES = 64 * MIB;
|
|
7
8
|
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
8
9
|
const CLEANUP_LOCK_MAX_AGE_MS = 10 * 60 * 1000;
|
|
10
|
+
const LOCK_RETRY_WAIT_MS = 20;
|
|
9
11
|
const MAX_SCAN_ENTRIES = 100_000;
|
|
10
12
|
const LEASE_FILE = '.vigthoria-temp-lease.json';
|
|
11
13
|
const CLEANUP_LOCK = '.vigthoria-temp-cleanup.lock';
|
|
14
|
+
const ALLOCATION_LOCK = '.vigthoria-temp-allocation.lock';
|
|
12
15
|
const ROOT_MARKER = '.vigthoria-temp-root.json';
|
|
13
16
|
const LEGACY_CLEANUP_MARKER = '.vigthoria-legacy-temp-cleanup-v1';
|
|
14
17
|
const LEGACY_PREFIXES = [
|
|
@@ -18,6 +21,12 @@ const LEGACY_PREFIXES = [
|
|
|
18
21
|
'vigthoria-cli-install-',
|
|
19
22
|
'puppeteer_dev_chrome_profile-',
|
|
20
23
|
];
|
|
24
|
+
// Capture the operating-system temp root before CLI initialization rewrites
|
|
25
|
+
// TMP/TEMP/TMPDIR to the managed per-user root. `os.tmpdir()` is dynamic on
|
|
26
|
+
// Node: after the rewrite it returns the managed root, which previously meant
|
|
27
|
+
// a second public RuntimeTempManager instance could mistake the original
|
|
28
|
+
// shared %TEMP% for a safe explicit override.
|
|
29
|
+
const PROCESS_START_SYSTEM_TEMP = os.tmpdir();
|
|
21
30
|
export class RuntimeTempError extends Error {
|
|
22
31
|
code;
|
|
23
32
|
details;
|
|
@@ -51,7 +60,15 @@ function rejectUnsafeWindowsPath(value) {
|
|
|
51
60
|
}
|
|
52
61
|
function isKnownSharedPosixTemp(value) {
|
|
53
62
|
const normalized = path.posix.resolve(value);
|
|
54
|
-
return
|
|
63
|
+
return ['/tmp', '/var/tmp', '/usr/tmp'].some((root) => pathIsContained(root, normalized, path.posix));
|
|
64
|
+
}
|
|
65
|
+
function isWithinSharedTemp(candidate, root, platform) {
|
|
66
|
+
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
67
|
+
const normalize = (value) => {
|
|
68
|
+
const resolved = pathApi.resolve(value);
|
|
69
|
+
return platform === 'win32' ? resolved.toLocaleLowerCase('en-US') : resolved;
|
|
70
|
+
};
|
|
71
|
+
return pathIsContained(normalize(root), normalize(candidate), pathApi);
|
|
55
72
|
}
|
|
56
73
|
function pathsEqual(left, right, platform) {
|
|
57
74
|
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
@@ -91,6 +108,10 @@ function defaultProcessAlive(pid) {
|
|
|
91
108
|
return error?.code === 'EPERM';
|
|
92
109
|
}
|
|
93
110
|
}
|
|
111
|
+
function synchronousWait(milliseconds) {
|
|
112
|
+
const signal = new Int32Array(new SharedArrayBuffer(4));
|
|
113
|
+
Atomics.wait(signal, 0, 0, milliseconds);
|
|
114
|
+
}
|
|
94
115
|
export class RuntimeTempManager {
|
|
95
116
|
environment;
|
|
96
117
|
platform;
|
|
@@ -104,6 +125,7 @@ export class RuntimeTempManager {
|
|
|
104
125
|
source;
|
|
105
126
|
maxBytes;
|
|
106
127
|
minimumFreeBytes;
|
|
128
|
+
allocationMaxBytes;
|
|
107
129
|
ttlMs;
|
|
108
130
|
initializedRoot = null;
|
|
109
131
|
initializedIdentity = null;
|
|
@@ -125,20 +147,21 @@ export class RuntimeTempManager {
|
|
|
125
147
|
const platformTemp = this.platform === 'win32'
|
|
126
148
|
? String(this.environment.TEMP || this.environment.TMP || '').trim()
|
|
127
149
|
: String(this.environment.TMPDIR || '').trim();
|
|
128
|
-
this.systemTempRoot = options.systemTempDirectory || platformTemp || os.tmpdir();
|
|
150
|
+
this.systemTempRoot = options.systemTempDirectory || platformTemp || (this.platform === process.platform ? PROCESS_START_SYSTEM_TEMP : os.tmpdir());
|
|
129
151
|
this.sharedTempRoots = [
|
|
130
152
|
options.systemTempDirectory,
|
|
131
153
|
platformTemp,
|
|
132
154
|
this.environment.TEMP,
|
|
133
155
|
this.environment.TMP,
|
|
134
156
|
this.environment.TMPDIR,
|
|
135
|
-
this.platform === process.platform ?
|
|
157
|
+
this.platform === process.platform ? PROCESS_START_SYSTEM_TEMP : undefined,
|
|
136
158
|
].filter((value) => typeof value === 'string' && value.trim().length > 0);
|
|
137
159
|
const resolved = resolveRuntimeTempRoot({ ...options, environment: this.environment, platform: this.platform, homeDirectory: this.homeDirectory });
|
|
138
160
|
this.configuredRoot = resolved.root;
|
|
139
161
|
this.source = resolved.source;
|
|
140
162
|
this.maxBytes = numericSetting('VIGTHORIA_TEMP_MAX_BYTES', this.environment.VIGTHORIA_TEMP_MAX_BYTES, DEFAULT_MAX_BYTES, 64 * MIB, 64 * 1024 * MIB);
|
|
141
163
|
this.minimumFreeBytes = numericSetting('VIGTHORIA_TEMP_MIN_FREE_BYTES', this.environment.VIGTHORIA_TEMP_MIN_FREE_BYTES, DEFAULT_MIN_FREE_BYTES, 64 * MIB, 64 * 1024 * MIB);
|
|
164
|
+
this.allocationMaxBytes = numericSetting('VIGTHORIA_TEMP_ALLOCATION_MAX_BYTES', this.environment.VIGTHORIA_TEMP_ALLOCATION_MAX_BYTES, DEFAULT_ALLOCATION_MAX_BYTES, MIB, this.maxBytes);
|
|
142
165
|
const ttlHours = numericSetting('VIGTHORIA_TEMP_TTL_HOURS', this.environment.VIGTHORIA_TEMP_TTL_HOURS, DEFAULT_TTL_MS / 3_600_000, 1, 720);
|
|
143
166
|
this.ttlMs = ttlHours * 3_600_000;
|
|
144
167
|
}
|
|
@@ -150,7 +173,7 @@ export class RuntimeTempManager {
|
|
|
150
173
|
// also what makes cross-platform policy probes deterministic: a Windows
|
|
151
174
|
// policy instance running in a non-Windows test host must not attempt to
|
|
152
175
|
// create a drive-letter path before it can reject `%TEMP%`.
|
|
153
|
-
if (this.sharedTempRoots.some((root) =>
|
|
176
|
+
if (this.sharedTempRoots.some((root) => isWithinSharedTemp(this.configuredRoot, root, this.platform))
|
|
154
177
|
|| (this.platform !== 'win32' && isKnownSharedPosixTemp(this.configuredRoot))) {
|
|
155
178
|
throw new RuntimeTempError('The shared operating-system temporary directory cannot be used for Vigthoria runtime storage.', 'TEMP_ROOT_SHARED');
|
|
156
179
|
}
|
|
@@ -169,12 +192,17 @@ export class RuntimeTempManager {
|
|
|
169
192
|
const markerPath = path.join(realRoot, ROOT_MARKER);
|
|
170
193
|
if (this.source === 'explicit-override' && existed && !fs.existsSync(markerPath)) {
|
|
171
194
|
const existingEntries = fs.readdirSync(realRoot);
|
|
172
|
-
|
|
195
|
+
// Another CLI process may have claimed this previously-empty root
|
|
196
|
+
// between our first marker check and directory listing. Re-check the
|
|
197
|
+
// ownership marker before rejecting its newly-created lease/lock as
|
|
198
|
+
// foreign data. The marker content is validated below, so this does
|
|
199
|
+
// not weaken the dedicated-root boundary.
|
|
200
|
+
if (existingEntries.length > 0 && !fs.existsSync(markerPath)) {
|
|
173
201
|
throw new RuntimeTempError('Explicit temporary storage must be empty or already marked as Vigthoria-managed.', 'TEMP_ROOT_NOT_DEDICATED');
|
|
174
202
|
}
|
|
175
203
|
}
|
|
176
204
|
const pathApi = this.platform === 'win32' ? path.win32 : path.posix;
|
|
177
|
-
if (this.sharedTempRoots.some((root) =>
|
|
205
|
+
if (this.sharedTempRoots.some((root) => isWithinSharedTemp(realRoot, root, this.platform))
|
|
178
206
|
|| (this.platform !== 'win32' && isKnownSharedPosixTemp(realRoot))) {
|
|
179
207
|
throw new RuntimeTempError('The shared operating-system temporary directory cannot be used for Vigthoria runtime storage.', 'TEMP_ROOT_SHARED');
|
|
180
208
|
}
|
|
@@ -218,31 +246,53 @@ export class RuntimeTempManager {
|
|
|
218
246
|
}
|
|
219
247
|
return this.status();
|
|
220
248
|
}
|
|
221
|
-
createDirectory(prefix = 'run-') {
|
|
249
|
+
createDirectory(prefix = 'run-', requestedMaxBytes = this.allocationMaxBytes) {
|
|
222
250
|
this.initialize();
|
|
223
251
|
const root = this.requireRoot();
|
|
224
252
|
if (!/^[a-z0-9][a-z0-9-]{0,47}-$/i.test(prefix)) {
|
|
225
253
|
throw new RuntimeTempError('Temporary directory prefix is invalid.', 'TEMP_PREFIX_INVALID');
|
|
226
254
|
}
|
|
255
|
+
if (!Number.isSafeInteger(requestedMaxBytes) || requestedMaxBytes < MIB || requestedMaxBytes > this.maxBytes) {
|
|
256
|
+
throw new RuntimeTempError('Temporary allocation reservation is outside the managed root contract.', 'TEMP_RESERVATION_INVALID');
|
|
257
|
+
}
|
|
227
258
|
this.lastCleanup = this.scavenge({ includeLegacy: false });
|
|
228
|
-
const
|
|
229
|
-
if (
|
|
230
|
-
throw new RuntimeTempError('
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
259
|
+
const allocationLock = this.acquireLock(path.join(root, ALLOCATION_LOCK));
|
|
260
|
+
if (allocationLock === null) {
|
|
261
|
+
throw new RuntimeTempError('Temporary allocation admission is busy.', 'TEMP_ALLOCATION_BUSY');
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
const entries = this.readEntries(root);
|
|
265
|
+
const before = this.measure(root, entries);
|
|
266
|
+
const reservedBytes = entries.reduce((total, entry) => total + Math.max(entry.bytes, entry.active ? entry.reservedBytes : 0), 0);
|
|
267
|
+
if (reservedBytes + requestedMaxBytes > this.maxBytes
|
|
268
|
+
|| (before.freeBytes !== null && before.freeBytes - requestedMaxBytes < this.minimumFreeBytes)) {
|
|
269
|
+
throw new RuntimeTempError('Vigthoria temporary storage has insufficient reserved capacity after cleanup.', 'TEMP_CAPACITY_EXHAUSTED', {
|
|
270
|
+
root,
|
|
271
|
+
usedBytes: before.usedBytes,
|
|
272
|
+
reservedBytes,
|
|
273
|
+
requestedMaxBytes,
|
|
274
|
+
maxBytes: this.maxBytes,
|
|
275
|
+
freeBytes: before.freeBytes,
|
|
276
|
+
minimumFreeBytes: this.minimumFreeBytes,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const directory = fs.mkdtempSync(path.join(root, prefix));
|
|
280
|
+
if (this.platform !== 'win32')
|
|
281
|
+
fs.chmodSync(directory, 0o700);
|
|
282
|
+
fs.writeFileSync(path.join(directory, LEASE_FILE), `${JSON.stringify({
|
|
283
|
+
schemaVersion: 1,
|
|
284
|
+
pid: this.pid,
|
|
285
|
+
createdAt: new Date(this.now()).toISOString(),
|
|
286
|
+
reservedMaxBytes: requestedMaxBytes,
|
|
287
|
+
})}\n`, {
|
|
288
|
+
flag: 'wx',
|
|
289
|
+
mode: 0o600,
|
|
236
290
|
});
|
|
291
|
+
return directory;
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
this.releaseLock(path.join(root, ALLOCATION_LOCK), allocationLock);
|
|
237
295
|
}
|
|
238
|
-
const directory = fs.mkdtempSync(path.join(root, prefix));
|
|
239
|
-
if (this.platform !== 'win32')
|
|
240
|
-
fs.chmodSync(directory, 0o700);
|
|
241
|
-
fs.writeFileSync(path.join(directory, LEASE_FILE), `${JSON.stringify({ schemaVersion: 1, pid: this.pid, createdAt: new Date(this.now()).toISOString() })}\n`, {
|
|
242
|
-
flag: 'wx',
|
|
243
|
-
mode: 0o600,
|
|
244
|
-
});
|
|
245
|
-
return directory;
|
|
246
296
|
}
|
|
247
297
|
removeDirectory(directory) {
|
|
248
298
|
const root = this.requireRoot();
|
|
@@ -250,7 +300,7 @@ export class RuntimeTempManager {
|
|
|
250
300
|
if (path.dirname(absolute) !== root || absolute === root) {
|
|
251
301
|
throw new RuntimeTempError('Refusing to remove a path outside the Vigthoria temporary root.', 'TEMP_CLEANUP_ESCAPE');
|
|
252
302
|
}
|
|
253
|
-
fs.rmSync(absolute, { recursive: true, force: true });
|
|
303
|
+
fs.rmSync(absolute, { recursive: true, force: true, maxRetries: 8, retryDelay: 100 });
|
|
254
304
|
}
|
|
255
305
|
scavenge(options = {}) {
|
|
256
306
|
const root = this.requireRoot();
|
|
@@ -282,6 +332,13 @@ export class RuntimeTempManager {
|
|
|
282
332
|
}
|
|
283
333
|
if (lockAge <= CLEANUP_LOCK_MAX_AGE_MS)
|
|
284
334
|
return result;
|
|
335
|
+
let ownerPid = 0;
|
|
336
|
+
try {
|
|
337
|
+
ownerPid = Number(fs.readFileSync(lockPath, 'utf8').trim());
|
|
338
|
+
}
|
|
339
|
+
catch { /* malformed stale lock */ }
|
|
340
|
+
if (ownerPid > 0 && this.isProcessAlive(ownerPid))
|
|
341
|
+
return result;
|
|
285
342
|
try {
|
|
286
343
|
fs.unlinkSync(lockPath);
|
|
287
344
|
}
|
|
@@ -387,7 +444,7 @@ export class RuntimeTempManager {
|
|
|
387
444
|
readEntries(root) {
|
|
388
445
|
const entries = [];
|
|
389
446
|
for (const name of fs.readdirSync(root)) {
|
|
390
|
-
if (name === CLEANUP_LOCK || name === ROOT_MARKER || name === LEGACY_CLEANUP_MARKER)
|
|
447
|
+
if (name === CLEANUP_LOCK || name === ALLOCATION_LOCK || name === ROOT_MARKER || name === LEGACY_CLEANUP_MARKER)
|
|
391
448
|
continue;
|
|
392
449
|
const absolutePath = path.join(root, name);
|
|
393
450
|
let stat;
|
|
@@ -400,15 +457,19 @@ export class RuntimeTempManager {
|
|
|
400
457
|
throw error;
|
|
401
458
|
}
|
|
402
459
|
let active = false;
|
|
460
|
+
let reservedBytes = 0;
|
|
403
461
|
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
404
462
|
const leasePath = path.join(absolutePath, LEASE_FILE);
|
|
405
463
|
try {
|
|
406
464
|
const lease = JSON.parse(fs.readFileSync(leasePath, 'utf8'));
|
|
407
465
|
active = typeof lease.pid === 'number' && this.isProcessAlive(lease.pid);
|
|
466
|
+
if (Number.isSafeInteger(lease.reservedMaxBytes) && Number(lease.reservedMaxBytes) > 0) {
|
|
467
|
+
reservedBytes = Number(lease.reservedMaxBytes);
|
|
468
|
+
}
|
|
408
469
|
}
|
|
409
470
|
catch { /* third-party and abandoned directories have no live lease */ }
|
|
410
471
|
}
|
|
411
|
-
entries.push({ absolutePath, name, bytes: this.entrySize(absolutePath), modifiedAt: stat.mtimeMs, active });
|
|
472
|
+
entries.push({ absolutePath, name, bytes: this.entrySize(absolutePath), reservedBytes, modifiedAt: stat.mtimeMs, active });
|
|
412
473
|
}
|
|
413
474
|
return entries;
|
|
414
475
|
}
|
|
@@ -456,10 +517,50 @@ export class RuntimeTempManager {
|
|
|
456
517
|
if (path.dirname(entry.absolutePath) !== this.requireRoot()) {
|
|
457
518
|
throw new RuntimeTempError('Temporary cleanup target escaped its root.', 'TEMP_CLEANUP_ESCAPE');
|
|
458
519
|
}
|
|
459
|
-
fs.rmSync(entry.absolutePath, { recursive: true, force: true });
|
|
520
|
+
fs.rmSync(entry.absolutePath, { recursive: true, force: true, maxRetries: 8, retryDelay: 100 });
|
|
460
521
|
result.removedEntries += 1;
|
|
461
522
|
result.removedBytes += entry.bytes;
|
|
462
523
|
}
|
|
524
|
+
acquireLock(lockPath) {
|
|
525
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
526
|
+
try {
|
|
527
|
+
const descriptor = fs.openSync(lockPath, 'wx', 0o600);
|
|
528
|
+
fs.writeFileSync(descriptor, `${this.pid}\n`);
|
|
529
|
+
return descriptor;
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
if (error?.code !== 'EEXIST')
|
|
533
|
+
throw error;
|
|
534
|
+
try {
|
|
535
|
+
if (this.now() - fs.statSync(lockPath).mtimeMs > CLEANUP_LOCK_MAX_AGE_MS) {
|
|
536
|
+
let ownerPid = 0;
|
|
537
|
+
try {
|
|
538
|
+
ownerPid = Number(fs.readFileSync(lockPath, 'utf8').trim());
|
|
539
|
+
}
|
|
540
|
+
catch { /* malformed stale lock */ }
|
|
541
|
+
if (ownerPid <= 0 || !this.isProcessAlive(ownerPid)) {
|
|
542
|
+
fs.unlinkSync(lockPath);
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
catch (lockError) {
|
|
548
|
+
if (lockError?.code === 'ENOENT')
|
|
549
|
+
continue;
|
|
550
|
+
throw lockError;
|
|
551
|
+
}
|
|
552
|
+
synchronousWait(LOCK_RETRY_WAIT_MS);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return null;
|
|
556
|
+
}
|
|
557
|
+
releaseLock(lockPath, descriptor) {
|
|
558
|
+
fs.closeSync(descriptor);
|
|
559
|
+
try {
|
|
560
|
+
fs.unlinkSync(lockPath);
|
|
561
|
+
}
|
|
562
|
+
catch { /* stale-lock recovery may have raced */ }
|
|
563
|
+
}
|
|
463
564
|
cleanupLegacySystemTemp(result) {
|
|
464
565
|
const legacyRoot = this.systemTempRoot;
|
|
465
566
|
if (!fs.existsSync(legacyRoot) || path.resolve(legacyRoot) === this.requireRoot())
|
|
@@ -487,7 +588,7 @@ export class RuntimeTempManager {
|
|
|
487
588
|
if (this.now() - stat.mtimeMs <= this.ttlMs)
|
|
488
589
|
continue;
|
|
489
590
|
const bytes = this.entrySize(candidate);
|
|
490
|
-
fs.rmSync(candidate, { recursive: true, force: true });
|
|
591
|
+
fs.rmSync(candidate, { recursive: true, force: true, maxRetries: 8, retryDelay: 100 });
|
|
491
592
|
result.legacyRemovedEntries += 1;
|
|
492
593
|
result.legacyRemovedBytes += bytes;
|
|
493
594
|
}
|
|
@@ -504,8 +605,8 @@ export function initializeRuntimeTempStorage() {
|
|
|
504
605
|
export function runtimeTempStatus() {
|
|
505
606
|
return getRuntimeTempManager().initialize();
|
|
506
607
|
}
|
|
507
|
-
export function createRuntimeTempDirectory(prefix) {
|
|
508
|
-
return getRuntimeTempManager().createDirectory(prefix);
|
|
608
|
+
export function createRuntimeTempDirectory(prefix, requestedMaxBytes) {
|
|
609
|
+
return getRuntimeTempManager().createDirectory(prefix, requestedMaxBytes);
|
|
509
610
|
}
|
|
510
611
|
export function removeRuntimeTempDirectory(directory) {
|
|
511
612
|
getRuntimeTempManager().removeDirectory(directory);
|
package/dist/utils/tools.js
CHANGED
|
@@ -1344,7 +1344,7 @@ export class AgenticTools {
|
|
|
1344
1344
|
validateJavaScriptSyntax(source) {
|
|
1345
1345
|
let tempDirectory;
|
|
1346
1346
|
try {
|
|
1347
|
-
tempDirectory = createRuntimeTempDirectory('syntax-');
|
|
1347
|
+
tempDirectory = createRuntimeTempDirectory('syntax-', 16 * 1024 * 1024);
|
|
1348
1348
|
}
|
|
1349
1349
|
catch (error) {
|
|
1350
1350
|
return this.formatExternalToolError('syntax_check', 'allocate bounded temporary storage', error);
|
|
@@ -61,9 +61,17 @@ export function assertValidAgentPlanEvent(event) {
|
|
|
61
61
|
if (!event || typeof event !== 'object')
|
|
62
62
|
return;
|
|
63
63
|
const record = event;
|
|
64
|
-
if (record.type
|
|
64
|
+
if (record.type === 'error' && record.code === 'AGENT_PLAN_INVALID') {
|
|
65
|
+
throw new AgentPlanContractError(String(record.message || 'Agent plan graph is invalid.'));
|
|
66
|
+
}
|
|
67
|
+
if (record.type !== 'plan')
|
|
68
|
+
return;
|
|
69
|
+
// Accept every server envelope used by V3/Operator integrations, while
|
|
70
|
+
// ignoring planning keepalives that intentionally have no task graph.
|
|
71
|
+
const taskCandidate = record.plan?.tasks ?? record.tasks ?? record.data?.tasks;
|
|
72
|
+
if (!Array.isArray(taskCandidate))
|
|
65
73
|
return;
|
|
66
|
-
const tasks =
|
|
74
|
+
const tasks = taskCandidate;
|
|
67
75
|
const ids = tasks.map((task, index) => {
|
|
68
76
|
const id = dependencyId(task.id ?? task.task_id ?? task.taskId);
|
|
69
77
|
if (!id)
|
|
@@ -21,6 +21,7 @@ export declare class V3WorkspaceService {
|
|
|
21
21
|
relativePath: string;
|
|
22
22
|
absolutePath: string;
|
|
23
23
|
} | null;
|
|
24
|
+
private resolveCaseInsensitiveWorkspacePath;
|
|
24
25
|
writeFile(rootPath: string, rawPath: string, content: string, sourceRoot?: string): boolean;
|
|
25
26
|
deleteFile(rootPath: string, rawPath: string): boolean;
|
|
26
27
|
recover(context?: Record<string, any>, streamedFiles?: Record<string, string>, expectedFiles?: string[]): void;
|
|
@@ -133,6 +133,14 @@ export class V3WorkspaceService {
|
|
|
133
133
|
}
|
|
134
134
|
extractExpectedFiles(message = '', context = {}) {
|
|
135
135
|
const candidates = new Set();
|
|
136
|
+
if (Array.isArray(context.expectedFiles)) {
|
|
137
|
+
for (const value of context.expectedFiles) {
|
|
138
|
+
const filePath = String(value || '').trim().replace(/^\.\//, '');
|
|
139
|
+
if (filePath && !/^https?:\/\//i.test(filePath) && !isSensitivePath(filePath)) {
|
|
140
|
+
candidates.add(filePath);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
136
144
|
for (const value of [message, context.rawMessage, context.agentPrompt]) {
|
|
137
145
|
const text = String(value || '');
|
|
138
146
|
const extensions = 'c|cc|cpp|cxx|h|hpp|cs|css|go|html|htm|ini|java|js|jsx|json|kt|kts|md|mjs|cjs|php|ps1|py|rb|rs|scss|sh|sql|svelte|toml|ts|tsx|txt|vue|xml|yaml|yml';
|
|
@@ -208,15 +216,44 @@ export class V3WorkspaceService {
|
|
|
208
216
|
if (relativePath && isSensitivePath(relativePath))
|
|
209
217
|
return null;
|
|
210
218
|
try {
|
|
211
|
-
|
|
219
|
+
let absolutePath = relativePath
|
|
212
220
|
? resolveWorkspacePath(rootPath, relativePath, { allowMissing: true })
|
|
213
221
|
: fs.realpathSync(path.resolve(rootPath));
|
|
222
|
+
if (relativePath && !fs.existsSync(absolutePath)) {
|
|
223
|
+
const canonical = this.resolveCaseInsensitiveWorkspacePath(rootPath, relativePath);
|
|
224
|
+
relativePath = canonical.relativePath;
|
|
225
|
+
absolutePath = canonical.absolutePath;
|
|
226
|
+
}
|
|
214
227
|
return { relativePath: relativePath || '.', absolutePath };
|
|
215
228
|
}
|
|
216
229
|
catch {
|
|
217
230
|
return null;
|
|
218
231
|
}
|
|
219
232
|
}
|
|
233
|
+
resolveCaseInsensitiveWorkspacePath(rootPath, relativePath) {
|
|
234
|
+
const suppliedSegments = relativePath.split('/').filter(Boolean);
|
|
235
|
+
const canonicalSegments = [];
|
|
236
|
+
let current = fs.realpathSync(path.resolve(rootPath));
|
|
237
|
+
for (let index = 0; index < suppliedSegments.length; index += 1) {
|
|
238
|
+
const supplied = suppliedSegments[index];
|
|
239
|
+
let selected = supplied;
|
|
240
|
+
if (fs.existsSync(current) && fs.statSync(current).isDirectory()) {
|
|
241
|
+
const exact = path.join(current, supplied);
|
|
242
|
+
if (!fs.existsSync(exact)) {
|
|
243
|
+
const foldedMatches = fs.readdirSync(current).filter((entry) => entry.toLowerCase() === supplied.toLowerCase());
|
|
244
|
+
if (foldedMatches.length === 1)
|
|
245
|
+
selected = foldedMatches[0];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
canonicalSegments.push(selected);
|
|
249
|
+
current = path.join(current, selected);
|
|
250
|
+
}
|
|
251
|
+
const canonicalRelative = canonicalSegments.join('/');
|
|
252
|
+
return {
|
|
253
|
+
relativePath: canonicalRelative,
|
|
254
|
+
absolutePath: resolveWorkspacePath(rootPath, canonicalRelative, { allowMissing: true }),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
220
257
|
writeFile(rootPath, rawPath, content, sourceRoot) {
|
|
221
258
|
const relativePath = this.normalizeRelativePath(rawPath, sourceRoot || rootPath);
|
|
222
259
|
if (!relativePath || isSensitivePath(relativePath)) {
|
|
@@ -61,6 +61,9 @@ export interface VigFlowTransport {
|
|
|
61
61
|
post<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<{
|
|
62
62
|
data: T;
|
|
63
63
|
}>;
|
|
64
|
+
delete<T>(url: string, config?: AxiosRequestConfig): Promise<{
|
|
65
|
+
data: T;
|
|
66
|
+
}>;
|
|
64
67
|
}
|
|
65
68
|
export interface VigFlowClientDependencies {
|
|
66
69
|
getBaseUrls(): string[];
|
|
@@ -76,6 +79,7 @@ export declare class VigFlowClient {
|
|
|
76
79
|
clearCredentials(): void;
|
|
77
80
|
private accessToken;
|
|
78
81
|
private withBackend;
|
|
82
|
+
private withMutationBackend;
|
|
79
83
|
private endpoint;
|
|
80
84
|
listTemplates(options?: {
|
|
81
85
|
category?: string;
|
|
@@ -92,4 +96,9 @@ export declare class VigFlowClient {
|
|
|
92
96
|
executionOptions?: Record<string, unknown>;
|
|
93
97
|
}): Promise<VigFlowExecutionResult>;
|
|
94
98
|
executionStatus(executionId: string): Promise<VigFlowExecutionStatus>;
|
|
99
|
+
deleteWorkflow(selector: string): Promise<{
|
|
100
|
+
id: string;
|
|
101
|
+
name?: string;
|
|
102
|
+
alreadyDeleted?: boolean;
|
|
103
|
+
}>;
|
|
95
104
|
}
|
|
@@ -37,6 +37,29 @@ export class VigFlowClient {
|
|
|
37
37
|
}
|
|
38
38
|
throw new Error(`No VigFlow backend available for ${operation}. The workflow service is not deployed or not reachable.`);
|
|
39
39
|
}
|
|
40
|
+
async withMutationBackend(operation, action) {
|
|
41
|
+
const authenticationErrors = [];
|
|
42
|
+
for (const baseUrl of this.dependencies.getBaseUrls()) {
|
|
43
|
+
let token;
|
|
44
|
+
try {
|
|
45
|
+
token = await this.accessToken(baseUrl);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
authenticationErrors.push(`${baseUrl}: ${error instanceof Error ? error.message : String(error)}`);
|
|
49
|
+
this.dependencies.debug(`VigFlow ${operation} authentication via ${baseUrl} failed:`, error instanceof Error ? error.message : String(error));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
// Once a mutation request is dispatched its outcome can be ambiguous.
|
|
53
|
+
// Never repeat it against a different backend; callers must reconcile
|
|
54
|
+
// the operation by its identifier before choosing to retry.
|
|
55
|
+
return action(baseUrl, {
|
|
56
|
+
'Content-Type': 'application/json',
|
|
57
|
+
Accept: 'application/json',
|
|
58
|
+
Authorization: `Bearer ${token}`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
throw new Error(`No VigFlow backend accepted authentication for ${operation}. ${authenticationErrors.join(' | ')}`.trim());
|
|
62
|
+
}
|
|
40
63
|
endpoint(baseUrl, subPath) {
|
|
41
64
|
return /\/api\/vigflow\/?$/i.test(baseUrl) ? `${baseUrl.replace(/\/$/, '')}${subPath}` : `${baseUrl}/api${subPath}`;
|
|
42
65
|
}
|
|
@@ -79,7 +102,7 @@ export class VigFlowClient {
|
|
|
79
102
|
throw new Error(`No VigFlow workflow matched "${normalized}".`);
|
|
80
103
|
}
|
|
81
104
|
useTemplate(templateId, options = {}) {
|
|
82
|
-
return this.
|
|
105
|
+
return this.withMutationBackend('use template', async (baseUrl, headers) => {
|
|
83
106
|
const response = await this.transport.post(this.endpoint(baseUrl, `/templates/${encodeURIComponent(templateId)}/use`), scanOutboundContext({ name: options.name, variables: options.variables || {} }).value, { headers, timeout: 30_000 });
|
|
84
107
|
if (!response.data.workflow?.id)
|
|
85
108
|
throw new Error('VigFlow use-template response did not include a workflow id.');
|
|
@@ -87,7 +110,7 @@ export class VigFlowClient {
|
|
|
87
110
|
});
|
|
88
111
|
}
|
|
89
112
|
runWorkflow(workflowId, options = {}) {
|
|
90
|
-
return this.
|
|
113
|
+
return this.withMutationBackend('run workflow', async (baseUrl, headers) => {
|
|
91
114
|
const response = await this.transport.post(this.endpoint(baseUrl, `/executions/run/${encodeURIComponent(workflowId)}`), scanOutboundContext({ data: options.data || {}, options: options.executionOptions || {} }).value, { headers, timeout: 60_000 });
|
|
92
115
|
if (!response.data.executionId)
|
|
93
116
|
throw new Error('VigFlow run response did not include an execution id.');
|
|
@@ -102,4 +125,27 @@ export class VigFlowClient {
|
|
|
102
125
|
return response.data.execution;
|
|
103
126
|
});
|
|
104
127
|
}
|
|
128
|
+
async deleteWorkflow(selector) {
|
|
129
|
+
const normalized = String(selector || '').trim();
|
|
130
|
+
if (!normalized)
|
|
131
|
+
throw new Error('Workflow selector is required. Provide a workflow id or name.');
|
|
132
|
+
// Native VigFlow IDs can be retried idempotently without first listing an
|
|
133
|
+
// object that may already be gone. Human-readable names remain resolved
|
|
134
|
+
// through the ownership-scoped list to avoid ambiguous deletion.
|
|
135
|
+
const target = /^(?:wf_[a-z0-9_-]+|[0-9]+|[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$/i.test(normalized)
|
|
136
|
+
? { id: normalized, name: undefined }
|
|
137
|
+
: await this.resolveWorkflow(normalized);
|
|
138
|
+
return this.withMutationBackend('delete workflow', async (baseUrl, headers) => {
|
|
139
|
+
try {
|
|
140
|
+
const response = await this.transport.delete(this.endpoint(baseUrl, `/workflows/${encodeURIComponent(target.id)}`), { headers, timeout: 30_000 });
|
|
141
|
+
return { id: target.id, name: target.name, alreadyDeleted: response.data.alreadyDeleted === true };
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (Number(error?.response?.status) === 404) {
|
|
145
|
+
return { id: target.id, name: target.name, alreadyDeleted: true };
|
|
146
|
+
}
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
105
151
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return a stable, non-filesystem workspace label for hosted history APIs.
|
|
3
|
+
*
|
|
4
|
+
* Client absolute paths are private machine metadata and must never be used as
|
|
5
|
+
* server workspace roots. File access is provided separately by the
|
|
6
|
+
* authenticated client-tool bridge.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildLocalWorkspaceReference(workspacePath: string, accountNamespace?: string): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
/**
|
|
4
|
+
* Return a stable, non-filesystem workspace label for hosted history APIs.
|
|
5
|
+
*
|
|
6
|
+
* Client absolute paths are private machine metadata and must never be used as
|
|
7
|
+
* server workspace roots. File access is provided separately by the
|
|
8
|
+
* authenticated client-tool bridge.
|
|
9
|
+
*/
|
|
10
|
+
export function buildLocalWorkspaceReference(workspacePath, accountNamespace = '') {
|
|
11
|
+
const normalized = String(workspacePath || 'workspace').replace(/\\/g, '/').replace(/\/+$/, '');
|
|
12
|
+
const displayName = path.posix.basename(normalized) || 'workspace';
|
|
13
|
+
const canonicalIdentity = process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
|
14
|
+
const opaqueId = createHash('sha256')
|
|
15
|
+
.update(String(accountNamespace || 'anonymous'))
|
|
16
|
+
.update('\0')
|
|
17
|
+
.update(canonicalIdentity)
|
|
18
|
+
.digest('hex')
|
|
19
|
+
.slice(0, 24);
|
|
20
|
+
return `vigthoria://local-workspace/${encodeURIComponent(displayName)}?id=${opaqueId}`;
|
|
21
|
+
}
|
package/install.ps1
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
$ErrorActionPreference = "Stop"
|
|
6
6
|
|
|
7
7
|
# Configuration
|
|
8
|
-
$CLI_VERSION = "1.13.
|
|
8
|
+
$CLI_VERSION = "1.13.30"
|
|
9
9
|
$INSTALL_DIR = "$env:USERPROFILE\.vigthoria"
|
|
10
10
|
$NPM_PACKAGE = "vigthoria-cli"
|
|
11
11
|
$MANIFEST_URL = "https://extension.vigthoria.io/downloads/manifest.json"
|
|
12
12
|
$HOSTED_TARBALL_URL = "https://extension.vigthoria.io/downloads/vigthoria-cli-$CLI_VERSION.tgz"
|
|
13
|
-
$HOSTED_TARBALL_SHA256 = ""
|
|
13
|
+
$HOSTED_TARBALL_SHA256 = "1d125d37edb459a02ef3f38c0e8515622f3c6ea128199dbb1641d107c80020b9"
|
|
14
14
|
$RELEASE_RESOLVER = Join-Path $PSScriptRoot "scripts\release\resolve-release-manifest.mjs"
|
|
15
15
|
$RELEASE_INSTALLER = Join-Path $PSScriptRoot "scripts\release\install-release.mjs"
|
|
16
16
|
|
package/install.sh
CHANGED
|
@@ -26,11 +26,11 @@ else
|
|
|
26
26
|
fi
|
|
27
27
|
|
|
28
28
|
# Configuration
|
|
29
|
-
CLI_VERSION="1.13.
|
|
29
|
+
CLI_VERSION="1.13.30"
|
|
30
30
|
INSTALL_DIR="$HOME/.vigthoria"
|
|
31
31
|
MANIFEST_URL="${VIGTHORIA_UPDATE_MANIFEST_URL:-https://extension.vigthoria.io/downloads/manifest.json}"
|
|
32
32
|
HOSTED_TARBALL_URL="https://extension.vigthoria.io/downloads/vigthoria-cli-${CLI_VERSION}.tgz"
|
|
33
|
-
HOSTED_TARBALL_SHA256=""
|
|
33
|
+
HOSTED_TARBALL_SHA256="1d125d37edb459a02ef3f38c0e8515622f3c6ea128199dbb1641d107c80020b9"
|
|
34
34
|
INSTALLER_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd || true)"
|
|
35
35
|
RELEASE_RESOLVER="$INSTALLER_ROOT/scripts/release/resolve-release-manifest.mjs"
|
|
36
36
|
RELEASE_INSTALLER="$INSTALLER_ROOT/scripts/release/install-release.mjs"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vigthoria-cli",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.30",
|
|
4
4
|
"description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -84,13 +84,14 @@
|
|
|
84
84
|
"test:legion:billing:e2e": "npm run build && node scripts/test-legion-godmode-billing-e2e.js",
|
|
85
85
|
"test:windows:v3-sync": "npm run build && node scripts/test-windows-v3-sync-recovery.js",
|
|
86
86
|
"test:v3-workspace-path": "npm run build && node scripts/test-v3-workspace-path.js",
|
|
87
|
+
"test:workspace:reference": "npm run build && node scripts/test-workspace-reference.mjs",
|
|
87
88
|
"test:v3-server-tool-execution": "npm run build && node scripts/test-v3-server-tool-execution.js",
|
|
88
89
|
"test:session:project-match": "npm run build && node scripts/test-session-project-match.mjs",
|
|
89
90
|
"test:external-client-resume": "npm run build && node scripts/test-external-client-resume.mjs",
|
|
90
91
|
"test:v3-stream-mutation": "npm run build && node scripts/test-v3-stream-mutation.js",
|
|
91
92
|
"test:context:budget": "npm run build && node scripts/test-context-budget.js",
|
|
92
93
|
"test:v3-client-tool-quality": "npm run build && node scripts/test-v3-client-tool-quality.mjs",
|
|
93
|
-
"test:pitfall:context": "npm run build && node scripts/test-pitfall-context-smoke.
|
|
94
|
+
"test:pitfall:context": "npm run build && node scripts/test-pitfall-context-smoke.mjs",
|
|
94
95
|
"test:game:command": "node scripts/test-game-command.mjs",
|
|
95
96
|
"test:security:dependencies": "node scripts/test-dependency-security.mjs",
|
|
96
97
|
"test:security:network": "npm run build && node scripts/test-network-policy.mjs && node scripts/test-installer-trust-policy.mjs && node scripts/test-local-security-service.mjs",
|
|
@@ -131,7 +132,6 @@
|
|
|
131
132
|
"marked": "^11.0.0",
|
|
132
133
|
"marked-terminal": "^6.2.0",
|
|
133
134
|
"ora": "^7.0.1",
|
|
134
|
-
"puppeteer": "^24.40.0",
|
|
135
135
|
"ws": "^8.14.2"
|
|
136
136
|
},
|
|
137
137
|
"devDependencies": {
|
|
@@ -148,8 +148,6 @@
|
|
|
148
148
|
"overrides": {
|
|
149
149
|
"glob": "^13.0.6",
|
|
150
150
|
"brace-expansion": "5.0.9",
|
|
151
|
-
"fast-uri": "3.1.5"
|
|
152
|
-
"ip-address": "10.3.1",
|
|
153
|
-
"js-yaml": "4.3.1"
|
|
151
|
+
"fast-uri": "3.1.5"
|
|
154
152
|
}
|
|
155
153
|
}
|
|
@@ -10,7 +10,7 @@ Use this checklist on real user machines before and after rollout. The goal is t
|
|
|
10
10
|
- [ ] Network access to:
|
|
11
11
|
- `https://coder.vigthoria.io`
|
|
12
12
|
- `https://api.vigthoria.io`
|
|
13
|
-
- [ ] If running browser runtime proofs: Chrome
|
|
13
|
+
- [ ] If running browser runtime proofs: Microsoft Edge, Chrome, or Chromium is installed in a standard OS location
|
|
14
14
|
|
|
15
15
|
Commands:
|
|
16
16
|
|
|
@@ -131,7 +131,7 @@ These are external dependencies, not CLI code regressions:
|
|
|
131
131
|
- Live repo/game/platform E2E requires:
|
|
132
132
|
- `VIGTHORIA_COMMUNITY_EMAIL`
|
|
133
133
|
- `VIGTHORIA_COMMUNITY_PASSWORD`
|
|
134
|
-
- Browser proof requires Chrome
|
|
134
|
+
- Browser proof requires Microsoft Edge, Chrome, or Chromium in a standard OS installation location; the CLI never downloads a browser
|
|
135
135
|
|
|
136
136
|
## 9. Rollback Readiness
|
|
137
137
|
|