vigthoria-cli 1.13.18 → 1.13.22

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 (185) hide show
  1. package/completions/_vigthoria +49 -0
  2. package/completions/vigthoria.bash +6 -0
  3. package/completions/vigthoria.fish +41 -0
  4. package/dist/commands/auth.d.ts +1 -0
  5. package/dist/commands/auth.js +84 -135
  6. package/dist/commands/background.d.ts +0 -2
  7. package/dist/commands/background.js +26 -34
  8. package/dist/commands/bridge.js +45 -41
  9. package/dist/commands/cancel.d.ts +1 -0
  10. package/dist/commands/cancel.js +22 -16
  11. package/dist/commands/chat.d.ts +5 -42
  12. package/dist/commands/chat.js +462 -1122
  13. package/dist/commands/coding-registration.d.ts +4 -0
  14. package/dist/commands/coding-registration.js +24 -0
  15. package/dist/commands/config.js +32 -42
  16. package/dist/commands/conversation-registration.d.ts +4 -0
  17. package/dist/commands/conversation-registration.js +122 -0
  18. package/dist/commands/deploy.d.ts +8 -2
  19. package/dist/commands/deploy.js +92 -90
  20. package/dist/commands/device.d.ts +1 -2
  21. package/dist/commands/device.js +37 -21
  22. package/dist/commands/doctor-registration.d.ts +4 -0
  23. package/dist/commands/doctor-registration.js +200 -0
  24. package/dist/commands/edit.js +22 -33
  25. package/dist/commands/explain.js +7 -10
  26. package/dist/commands/fork.d.ts +1 -2
  27. package/dist/commands/fork.js +72 -22
  28. package/dist/commands/game.d.ts +1 -1
  29. package/dist/commands/game.js +17 -6
  30. package/dist/commands/generate.js +7 -9
  31. package/dist/commands/history.js +11 -7
  32. package/dist/commands/hub.d.ts +2 -2
  33. package/dist/commands/hub.js +34 -35
  34. package/dist/commands/legion.d.ts +0 -1
  35. package/dist/commands/legion.js +62 -79
  36. package/dist/commands/music.d.ts +1 -2
  37. package/dist/commands/music.js +62 -27
  38. package/dist/commands/platform-registration.d.ts +4 -0
  39. package/dist/commands/platform-registration.js +45 -0
  40. package/dist/commands/preview.d.ts +0 -1
  41. package/dist/commands/preview.js +82 -35
  42. package/dist/commands/product-run-registration.d.ts +4 -0
  43. package/dist/commands/product-run-registration.js +490 -0
  44. package/dist/commands/replay.d.ts +1 -2
  45. package/dist/commands/replay.js +15 -11
  46. package/dist/commands/repo.d.ts +14 -7
  47. package/dist/commands/repo.js +245 -196
  48. package/dist/commands/review.js +9 -12
  49. package/dist/commands/security.d.ts +1 -2
  50. package/dist/commands/security.js +12 -6
  51. package/dist/commands/update-registration.d.ts +3 -0
  52. package/dist/commands/update-registration.js +338 -0
  53. package/dist/commands/v4-registration.d.ts +4 -0
  54. package/dist/commands/v4-registration.js +24 -0
  55. package/dist/commands/v4.d.ts +1 -1
  56. package/dist/commands/v4.js +46 -8
  57. package/dist/commands/wallet.d.ts +1 -3
  58. package/dist/commands/wallet.js +18 -45
  59. package/dist/commands/workflow.d.ts +2 -0
  60. package/dist/commands/workflow.js +52 -39
  61. package/dist/index.js +122 -1498
  62. package/dist/utils/agent-stream-state.d.ts +27 -0
  63. package/dist/utils/agent-stream-state.js +93 -0
  64. package/dist/utils/agentRoute.d.ts +2 -1
  65. package/dist/utils/agentRoute.js +22 -9
  66. package/dist/utils/agentRunOutcome.d.ts +16 -0
  67. package/dist/utils/agentRunOutcome.js +37 -2
  68. package/dist/utils/api-client-factory.d.ts +8 -0
  69. package/dist/utils/api-client-factory.js +8 -0
  70. package/dist/utils/api.d.ts +45 -269
  71. package/dist/utils/api.js +664 -3210
  72. package/dist/utils/auth-session.d.ts +41 -0
  73. package/dist/utils/auth-session.js +224 -0
  74. package/dist/utils/brain-hub-client.d.ts +0 -4
  75. package/dist/utils/brain-hub-client.js +6 -3
  76. package/dist/utils/bridge-client.js +2 -0
  77. package/dist/utils/capability-health-service.d.ts +48 -0
  78. package/dist/utils/capability-health-service.js +64 -0
  79. package/dist/utils/chat-prompt-policy.d.ts +18 -0
  80. package/dist/utils/chat-prompt-policy.js +61 -0
  81. package/dist/utils/chat-tool-call-parser.d.ts +11 -0
  82. package/dist/utils/chat-tool-call-parser.js +71 -0
  83. package/dist/utils/cli-state.d.ts +22 -4
  84. package/dist/utils/cli-state.js +105 -47
  85. package/dist/utils/code-operations-service.d.ts +104 -0
  86. package/dist/utils/code-operations-service.js +837 -0
  87. package/dist/utils/codebase-indexer.d.ts +1 -0
  88. package/dist/utils/codebase-indexer.js +28 -8
  89. package/dist/utils/command-contract.d.ts +61 -0
  90. package/dist/utils/command-contract.js +149 -0
  91. package/dist/utils/command-policy.d.ts +10 -0
  92. package/dist/utils/command-policy.js +54 -0
  93. package/dist/utils/config.d.ts +13 -5
  94. package/dist/utils/config.js +227 -99
  95. package/dist/utils/context-ranker.js +11 -2
  96. package/dist/utils/context-state.d.ts +39 -0
  97. package/dist/utils/context-state.js +52 -0
  98. package/dist/utils/deckEvents.d.ts +0 -5
  99. package/dist/utils/deckEvents.js +3 -2
  100. package/dist/utils/desktop-bridge-client.js +2 -0
  101. package/dist/utils/direct-output-policy.d.ts +3 -0
  102. package/dist/utils/direct-output-policy.js +76 -0
  103. package/dist/utils/durable-json.d.ts +57 -0
  104. package/dist/utils/durable-json.js +319 -0
  105. package/dist/utils/fastAgentRouter.d.ts +6 -7
  106. package/dist/utils/fastAgentRouter.js +21 -30
  107. package/dist/utils/files.js +21 -13
  108. package/dist/utils/frontend-preview-service.d.ts +76 -0
  109. package/dist/utils/frontend-preview-service.js +482 -0
  110. package/dist/utils/goaEvents.d.ts +0 -4
  111. package/dist/utils/goaEvents.js +5 -2
  112. package/dist/utils/logger.d.ts +2 -1
  113. package/dist/utils/logger.js +27 -20
  114. package/dist/utils/mcp-context-client.d.ts +24 -0
  115. package/dist/utils/mcp-context-client.js +58 -0
  116. package/dist/utils/model-governance.d.ts +18 -0
  117. package/dist/utils/model-governance.js +75 -0
  118. package/dist/utils/model-transport-service.d.ts +44 -0
  119. package/dist/utils/model-transport-service.js +111 -0
  120. package/dist/utils/mutation-journal.d.ts +42 -0
  121. package/dist/utils/mutation-journal.js +387 -0
  122. package/dist/utils/network-policy.d.ts +24 -0
  123. package/dist/utils/network-policy.js +202 -0
  124. package/dist/utils/operator-client.d.ts +45 -0
  125. package/dist/utils/operator-client.js +159 -0
  126. package/dist/utils/post-write-validator.js +7 -9
  127. package/dist/utils/preview-screenshot-adapter.d.ts +13 -0
  128. package/dist/utils/preview-screenshot-adapter.js +36 -0
  129. package/dist/utils/process-policy.d.ts +23 -0
  130. package/dist/utils/process-policy.js +284 -0
  131. package/dist/utils/project-memory.d.ts +13 -3
  132. package/dist/utils/project-memory.js +70 -17
  133. package/dist/utils/prompt-workspace-resolver.d.ts +14 -0
  134. package/dist/utils/prompt-workspace-resolver.js +64 -0
  135. package/dist/utils/release-install.d.ts +11 -0
  136. package/dist/utils/release-install.js +155 -0
  137. package/dist/utils/release-policy.d.ts +64 -0
  138. package/dist/utils/release-policy.js +293 -0
  139. package/dist/utils/requestIntent.js +22 -3
  140. package/dist/utils/runtime-capability.d.ts +8 -0
  141. package/dist/utils/runtime-capability.js +16 -0
  142. package/dist/utils/secret-policy.d.ts +20 -0
  143. package/dist/utils/secret-policy.js +160 -0
  144. package/dist/utils/session.d.ts +34 -2
  145. package/dist/utils/session.js +354 -110
  146. package/dist/utils/tool-approval-service.d.ts +17 -0
  147. package/dist/utils/tool-approval-service.js +78 -0
  148. package/dist/utils/tool-capability-providers.d.ts +57 -0
  149. package/dist/utils/tool-capability-providers.js +57 -0
  150. package/dist/utils/tools.d.ts +2 -23
  151. package/dist/utils/tools.js +216 -508
  152. package/dist/utils/update-policy.d.ts +18 -0
  153. package/dist/utils/update-policy.js +137 -0
  154. package/dist/utils/v3-agent-client.d.ts +40 -0
  155. package/dist/utils/v3-agent-client.js +165 -0
  156. package/dist/utils/v3-workspace-service.d.ts +28 -0
  157. package/dist/utils/v3-workspace-service.js +279 -0
  158. package/dist/utils/vigflow-client.d.ts +95 -0
  159. package/dist/utils/vigflow-client.js +105 -0
  160. package/dist/utils/workspace-boundary.d.ts +45 -0
  161. package/dist/utils/workspace-boundary.js +377 -0
  162. package/dist/utils/workspace-brain-service.d.ts +2 -0
  163. package/dist/utils/workspace-brain-service.js +11 -1
  164. package/dist/utils/workspace-cache.js +37 -15
  165. package/dist/utils/workspace-stream.js +35 -10
  166. package/install.ps1 +86 -143
  167. package/install.sh +76 -81
  168. package/package.json +44 -9
  169. package/release-policy.json +43 -0
  170. package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +1 -1
  171. package/scripts/release/generate-completions.mjs +24 -0
  172. package/scripts/release/install-release.mjs +10 -0
  173. package/scripts/release/publish-cli-release.mjs +116 -0
  174. package/scripts/release/publish-cli-release.sh +2 -70
  175. package/scripts/release/resolve-release-manifest.mjs +26 -0
  176. package/scripts/release/test-balanced-model-live.sh +42 -0
  177. package/scripts/release/validate-live-service-gates.sh +90 -0
  178. package/scripts/release/validate-no-go-gates.sh +135 -172
  179. package/scripts/release/verify-runtime-consistency.mjs +4 -1
  180. package/dist/commands/index.d.ts +0 -12
  181. package/dist/commands/index.js +0 -194
  182. package/dist/commands/update.d.ts +0 -9
  183. package/dist/commands/update.js +0 -301
  184. package/dist/utils/templateInstantPath.d.ts +0 -18
  185. package/dist/utils/templateInstantPath.js +0 -228
@@ -0,0 +1,76 @@
1
+ export function stripHiddenThoughtBlocks(text) {
2
+ if (!text)
3
+ return text;
4
+ return text
5
+ .replace(/<\|mask_start\|>[\s\S]*?<\|mask_end\|>/g, '')
6
+ .replace(/<think>[\s\S]*?<\/redacted_thinking>/gi, '')
7
+ .replace(/<thinking>[\s\S]*?<\/thinking>/gi, '')
8
+ .replace(/<\/redacted_thinking>/gi, '')
9
+ .replace(/<\/thinking>/gi, '')
10
+ .replace(/<\|(?:mask_start|mask_end)\|>/g, '')
11
+ .trim();
12
+ }
13
+ export function sanitizeDirectModeOutput(text) {
14
+ let cleaned = stripHiddenThoughtBlocks(text)
15
+ .replace(/<\|mask_start\|>[\s\S]*?<\|mask_end\|>/g, '')
16
+ .replace(/<think>[\s\S]*?<\/think>/gi, '')
17
+ .replace(/<\|(?:mask_start|mask_end)\|>/g, '');
18
+ cleaned = cleaned.replace(/Tool (?:read_file|grep|list_dir|glob|bash|write_file|edit_file|ssh_exec|task|multi_edit|codebase_search) (?:succeeded|FAILED)\.[\s\S]*?(?=\nTool |\n\n|$)/g, '');
19
+ const contaminationPatterns = [
20
+ /^\[Agent recovered from backend failure[^\]]*\]\s*/m,
21
+ /^Evidence gathered before backend failure:?\s*/m,
22
+ /^MANDATORY CROSS-FILE EVIDENCE[^\n]*\n?/m,
23
+ /^CONFIRMED CONFLICTING keys[^\n]*/m,
24
+ /^Keys found ONLY in [^\n]*/m,
25
+ /^CONSTRAINT: Your answer MUST[^\n]*/m,
26
+ /^GROUNDING CHECK:[^\n]*/m,
27
+ /^VERIFICATION PROTOCOL[^\n]*/m,
28
+ /^Quality gate:[^\n]*/m,
29
+ /^Evidence collected:[^\n]*/m,
30
+ /^Warning: \d+ search tool[^\n]*/m,
31
+ /^Tool results received for direct mode[^\n]*/m,
32
+ /^Step \d+ complete\. Task:[^\n]*/m,
33
+ /^Original user request:[^\n]*/m,
34
+ /^Project root boundary:[^\n]*/m,
35
+ /^Do not declare success[^\n]*/m,
36
+ /^Keep working from concrete[^\n]*/m,
37
+ /^Continue with tool calls if more[^\n]*/m,
38
+ /^IMPORTANT: Your response must be ONLY[^\n]*/m,
39
+ /^Because this is a debugging[^\n]*/m,
40
+ /^If a user is asking which file[^\n]*/m,
41
+ /^If the request is already[^\n]*/m,
42
+ /^If more work is required[^\n]*/m,
43
+ /^Do not ask follow-up[^\n]*/m,
44
+ /^CRITICAL GROUNDING RULE:[^\n]*/m,
45
+ /^CROSS-FILE ATTRIBUTION:[^\n]*/m,
46
+ /^EVIDENCE-GROUNDING RULE:[^\n]*/m,
47
+ /^CROSS-FILE RULE:[^\n]*/m,
48
+ /^OUTPUT DISCIPLINE:[^\n]*/m,
49
+ /^Vigthoria CLI agent operating contract\.[^\n]*/m,
50
+ /^You are operating inside the project root:[^\n]*/m,
51
+ /^CRITICAL: Begin working on the user's task[^\n]*/m,
52
+ /^File: \S+\s*$/m,
53
+ /^Search status: \S+\s*$/m,
54
+ /^Output:\s*$/m,
55
+ /^\[\.\.\. ?truncated\]\s*$/m,
56
+ /^---\s*$/m,
57
+ ];
58
+ for (const pattern of contaminationPatterns)
59
+ cleaned = cleaned.replace(pattern, '');
60
+ cleaned = cleaned.split(/\n\n+/).filter((paragraph) => {
61
+ const value = paragraph.trim();
62
+ if (!value || /^Tool (?:read_file|grep|list_dir|glob|bash|write_file) /i.test(value))
63
+ return false;
64
+ const lines = value.split('\n');
65
+ if (lines.length > 6) {
66
+ const codeLines = lines.filter((line) => /[{};=]|function |class |const |let |var |import |export |switch |case /.test(line));
67
+ if (codeLines.length / lines.length > 0.4)
68
+ return false;
69
+ }
70
+ return true;
71
+ }).join('\n\n');
72
+ return cleaned.replace(/\n{3,}/g, '\n\n').trim() || stripHiddenThoughtBlocks(text);
73
+ }
74
+ export function isDirectModeFollowUpQuestion(text) {
75
+ return /^(would you like me|do you want me|which aspect|what aspect|can you clarify|could you clarify|should i focus on|i will follow|i understand|i('ll| will) adhere|provide your|waiting for)/i.test(text.trim());
76
+ }
@@ -0,0 +1,57 @@
1
+ export interface DurableJsonEnvelope<T> {
2
+ schemaVersion: number;
3
+ revision: number;
4
+ updatedAt: string;
5
+ data: T;
6
+ }
7
+ export interface DurableJsonStoreOptions<T> {
8
+ filePath: string;
9
+ schemaVersion: number;
10
+ defaultValue: () => T;
11
+ validate: (value: unknown) => value is T;
12
+ migrate?: (value: unknown, fromVersion: number) => T;
13
+ mode?: number;
14
+ lockTimeoutMs?: number;
15
+ staleLockMs?: number;
16
+ now?: () => number;
17
+ }
18
+ export declare class DurableStateError extends Error {
19
+ readonly code: 'LOCK_TIMEOUT' | 'INVALID_STATE' | 'MIGRATION_FAILED' | 'WRITE_FAILED';
20
+ readonly filePath: string;
21
+ constructor(message: string, code: 'LOCK_TIMEOUT' | 'INVALID_STATE' | 'MIGRATION_FAILED' | 'WRITE_FAILED', filePath: string, options?: {
22
+ cause?: unknown;
23
+ });
24
+ }
25
+ export declare class DurableJsonStore<T> {
26
+ readonly filePath: string;
27
+ readonly backupPath: string;
28
+ readonly lockPath: string;
29
+ private readonly schemaVersion;
30
+ private readonly defaultValue;
31
+ private readonly validate;
32
+ private readonly migrate?;
33
+ private readonly mode;
34
+ private readonly lockTimeoutMs;
35
+ private readonly staleLockMs;
36
+ private readonly now;
37
+ constructor(options: DurableJsonStoreOptions<T>);
38
+ read(): T;
39
+ readEnvelope(): DurableJsonEnvelope<T>;
40
+ write(value: T): DurableJsonEnvelope<T>;
41
+ update(mutator: (current: T) => T): DurableJsonEnvelope<T>;
42
+ remove(): boolean;
43
+ private createEnvelope;
44
+ private readEnvelopeLocked;
45
+ private parseFile;
46
+ private decode;
47
+ private runMigration;
48
+ private preserveMigrationSource;
49
+ private tryRecoverFromBackup;
50
+ private writeEnvelopeLocked;
51
+ private writeBackupEnvelope;
52
+ private applyMode;
53
+ private syncDirectory;
54
+ private withLock;
55
+ private acquireLock;
56
+ private removeStaleLock;
57
+ }
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Durable, schema-versioned JSON persistence for CLI-owned state.
3
+ *
4
+ * The store deliberately stays synchronous because the existing CLI state APIs
5
+ * are synchronous. Every read that may migrate/recover and every write is
6
+ * serialized by an inter-process directory lock. Writes use a same-directory
7
+ * temporary file, fsync, and atomic rename; the last valid generation is kept
8
+ * as a recovery backup.
9
+ */
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { randomUUID } from 'crypto';
13
+ export class DurableStateError extends Error {
14
+ code;
15
+ filePath;
16
+ constructor(message, code, filePath, options) {
17
+ super(message, options);
18
+ this.code = code;
19
+ this.filePath = filePath;
20
+ this.name = 'DurableStateError';
21
+ }
22
+ }
23
+ function sleepSync(milliseconds) {
24
+ const waitBuffer = new Int32Array(new SharedArrayBuffer(4));
25
+ Atomics.wait(waitBuffer, 0, 0, milliseconds);
26
+ }
27
+ function isEnvelope(value) {
28
+ if (!value || typeof value !== 'object')
29
+ return false;
30
+ const candidate = value;
31
+ return Number.isInteger(candidate.schemaVersion)
32
+ && Number.isInteger(candidate.revision)
33
+ && typeof candidate.updatedAt === 'string'
34
+ && Object.prototype.hasOwnProperty.call(candidate, 'data');
35
+ }
36
+ export class DurableJsonStore {
37
+ filePath;
38
+ backupPath;
39
+ lockPath;
40
+ schemaVersion;
41
+ defaultValue;
42
+ validate;
43
+ migrate;
44
+ mode;
45
+ lockTimeoutMs;
46
+ staleLockMs;
47
+ now;
48
+ constructor(options) {
49
+ if (!Number.isInteger(options.schemaVersion) || options.schemaVersion < 1) {
50
+ throw new Error('Durable JSON schemaVersion must be a positive integer.');
51
+ }
52
+ this.filePath = path.resolve(options.filePath);
53
+ this.backupPath = `${this.filePath}.bak`;
54
+ this.lockPath = `${this.filePath}.lock`;
55
+ this.schemaVersion = options.schemaVersion;
56
+ this.defaultValue = options.defaultValue;
57
+ this.validate = options.validate;
58
+ this.migrate = options.migrate;
59
+ this.mode = options.mode ?? 0o600;
60
+ this.lockTimeoutMs = options.lockTimeoutMs ?? 5000;
61
+ this.staleLockMs = options.staleLockMs ?? 30_000;
62
+ this.now = options.now ?? Date.now;
63
+ }
64
+ read() {
65
+ return this.withLock(() => this.readEnvelopeLocked(true).data);
66
+ }
67
+ readEnvelope() {
68
+ return this.withLock(() => this.readEnvelopeLocked(true));
69
+ }
70
+ write(value) {
71
+ if (!this.validate(value)) {
72
+ throw new DurableStateError('Refusing to write invalid durable state.', 'INVALID_STATE', this.filePath);
73
+ }
74
+ return this.withLock(() => {
75
+ const current = this.readEnvelopeLocked(false);
76
+ const envelope = this.createEnvelope(value, current.revision + 1);
77
+ this.writeEnvelopeLocked(envelope);
78
+ return envelope;
79
+ });
80
+ }
81
+ update(mutator) {
82
+ return this.withLock(() => {
83
+ const current = this.readEnvelopeLocked(true);
84
+ const next = mutator(current.data);
85
+ if (!this.validate(next)) {
86
+ throw new DurableStateError('Durable state update produced an invalid value.', 'INVALID_STATE', this.filePath);
87
+ }
88
+ const envelope = this.createEnvelope(next, current.revision + 1);
89
+ this.writeEnvelopeLocked(envelope);
90
+ return envelope;
91
+ });
92
+ }
93
+ remove() {
94
+ return this.withLock(() => {
95
+ let removed = false;
96
+ for (const candidate of [this.filePath, this.backupPath]) {
97
+ try {
98
+ fs.unlinkSync(candidate);
99
+ removed = true;
100
+ }
101
+ catch (error) {
102
+ if (error.code !== 'ENOENT')
103
+ throw error;
104
+ }
105
+ }
106
+ this.syncDirectory();
107
+ return removed;
108
+ });
109
+ }
110
+ createEnvelope(data, revision) {
111
+ return {
112
+ schemaVersion: this.schemaVersion,
113
+ revision,
114
+ updatedAt: new Date(this.now()).toISOString(),
115
+ data,
116
+ };
117
+ }
118
+ readEnvelopeLocked(repair) {
119
+ if (!fs.existsSync(this.filePath)) {
120
+ return this.createEnvelope(this.defaultValue(), 0);
121
+ }
122
+ try {
123
+ const parsed = this.parseFile(this.filePath);
124
+ const decoded = this.decode(parsed);
125
+ if (decoded.requiresRewrite && repair) {
126
+ this.preserveMigrationSource();
127
+ this.writeEnvelopeLocked(decoded.envelope);
128
+ }
129
+ return decoded.envelope;
130
+ }
131
+ catch (primaryError) {
132
+ if (repair) {
133
+ const recovered = this.tryRecoverFromBackup(primaryError);
134
+ if (recovered)
135
+ return recovered;
136
+ }
137
+ if (primaryError instanceof DurableStateError)
138
+ throw primaryError;
139
+ throw new DurableStateError('Durable state is unreadable and no valid recovery copy exists.', 'INVALID_STATE', this.filePath, { cause: primaryError });
140
+ }
141
+ }
142
+ parseFile(candidate) {
143
+ const text = fs.readFileSync(candidate, 'utf8');
144
+ if (!text.trim()) {
145
+ throw new Error('State file is empty.');
146
+ }
147
+ return JSON.parse(text);
148
+ }
149
+ decode(parsed) {
150
+ if (isEnvelope(parsed)) {
151
+ if (parsed.schemaVersion > this.schemaVersion) {
152
+ throw new DurableStateError(`State schema ${parsed.schemaVersion} is newer than supported schema ${this.schemaVersion}.`, 'INVALID_STATE', this.filePath);
153
+ }
154
+ if (parsed.schemaVersion === this.schemaVersion && this.validate(parsed.data)) {
155
+ return { envelope: parsed, requiresRewrite: false };
156
+ }
157
+ return {
158
+ envelope: this.createEnvelope(this.runMigration(parsed.data, parsed.schemaVersion), parsed.revision + 1),
159
+ requiresRewrite: true,
160
+ };
161
+ }
162
+ return {
163
+ envelope: this.createEnvelope(this.runMigration(parsed, 0), 1),
164
+ requiresRewrite: true,
165
+ };
166
+ }
167
+ runMigration(value, fromVersion) {
168
+ try {
169
+ const migrated = this.migrate ? this.migrate(value, fromVersion) : value;
170
+ if (!this.validate(migrated))
171
+ throw new Error('Migrated value failed schema validation.');
172
+ return migrated;
173
+ }
174
+ catch (error) {
175
+ throw new DurableStateError(`Could not migrate durable state from schema ${fromVersion} to ${this.schemaVersion}.`, 'MIGRATION_FAILED', this.filePath, { cause: error });
176
+ }
177
+ }
178
+ preserveMigrationSource() {
179
+ const migrationBackup = `${this.filePath}.pre-migration.bak`;
180
+ if (!fs.existsSync(migrationBackup)) {
181
+ fs.copyFileSync(this.filePath, migrationBackup, fs.constants.COPYFILE_EXCL);
182
+ this.applyMode(migrationBackup);
183
+ }
184
+ }
185
+ tryRecoverFromBackup(primaryError) {
186
+ if (!fs.existsSync(this.backupPath))
187
+ return null;
188
+ try {
189
+ const recovered = this.decode(this.parseFile(this.backupPath)).envelope;
190
+ const corruptPath = `${this.filePath}.corrupt-${this.now()}-${process.pid}`;
191
+ fs.renameSync(this.filePath, corruptPath);
192
+ this.applyMode(corruptPath);
193
+ this.writeEnvelopeLocked(recovered, false);
194
+ return recovered;
195
+ }
196
+ catch (backupError) {
197
+ throw new DurableStateError('Durable state and its recovery backup are both invalid.', 'INVALID_STATE', this.filePath, { cause: { primaryError, backupError } });
198
+ }
199
+ }
200
+ writeEnvelopeLocked(envelope, rotateBackup = true) {
201
+ const parent = path.dirname(this.filePath);
202
+ fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
203
+ if (process.platform !== 'win32') {
204
+ try {
205
+ fs.chmodSync(parent, 0o700);
206
+ }
207
+ catch { /* filesystem may not support chmod */ }
208
+ }
209
+ const temporary = `${this.filePath}.${process.pid}.${randomUUID()}.tmp`;
210
+ let descriptor;
211
+ try {
212
+ descriptor = fs.openSync(temporary, 'wx', this.mode);
213
+ fs.writeFileSync(descriptor, `${JSON.stringify(envelope, null, 2)}\n`, 'utf8');
214
+ fs.fsyncSync(descriptor);
215
+ fs.closeSync(descriptor);
216
+ descriptor = undefined;
217
+ this.applyMode(temporary);
218
+ if (rotateBackup && fs.existsSync(this.filePath)) {
219
+ try {
220
+ const current = this.decode(this.parseFile(this.filePath)).envelope;
221
+ this.writeBackupEnvelope(current);
222
+ }
223
+ catch {
224
+ // Never replace a known-good backup with a corrupt current file.
225
+ }
226
+ }
227
+ fs.renameSync(temporary, this.filePath);
228
+ this.applyMode(this.filePath);
229
+ this.syncDirectory();
230
+ }
231
+ catch (error) {
232
+ if (descriptor !== undefined) {
233
+ try {
234
+ fs.closeSync(descriptor);
235
+ }
236
+ catch { /* already closed */ }
237
+ }
238
+ try {
239
+ fs.unlinkSync(temporary);
240
+ }
241
+ catch { /* absent */ }
242
+ if (error instanceof DurableStateError)
243
+ throw error;
244
+ throw new DurableStateError('Atomic durable-state write failed.', 'WRITE_FAILED', this.filePath, { cause: error });
245
+ }
246
+ }
247
+ writeBackupEnvelope(envelope) {
248
+ const temporary = `${this.backupPath}.${process.pid}.${randomUUID()}.tmp`;
249
+ fs.writeFileSync(temporary, `${JSON.stringify(envelope, null, 2)}\n`, { encoding: 'utf8', mode: this.mode, flag: 'wx' });
250
+ this.applyMode(temporary);
251
+ fs.renameSync(temporary, this.backupPath);
252
+ this.applyMode(this.backupPath);
253
+ }
254
+ applyMode(candidate) {
255
+ if (process.platform === 'win32')
256
+ return;
257
+ try {
258
+ fs.chmodSync(candidate, this.mode);
259
+ }
260
+ catch { /* filesystem may not support chmod */ }
261
+ }
262
+ syncDirectory() {
263
+ if (process.platform === 'win32')
264
+ return;
265
+ let descriptor;
266
+ try {
267
+ descriptor = fs.openSync(path.dirname(this.filePath), 'r');
268
+ fs.fsyncSync(descriptor);
269
+ }
270
+ catch {
271
+ // Some filesystems do not allow directory fsync.
272
+ }
273
+ finally {
274
+ if (descriptor !== undefined)
275
+ fs.closeSync(descriptor);
276
+ }
277
+ }
278
+ withLock(operation) {
279
+ this.acquireLock();
280
+ try {
281
+ return operation();
282
+ }
283
+ finally {
284
+ try {
285
+ fs.rmdirSync(this.lockPath);
286
+ }
287
+ catch { /* stale lock cleanup handles leftovers */ }
288
+ }
289
+ }
290
+ acquireLock() {
291
+ fs.mkdirSync(path.dirname(this.filePath), { recursive: true, mode: 0o700 });
292
+ const startedAt = this.now();
293
+ while (true) {
294
+ try {
295
+ fs.mkdirSync(this.lockPath, { mode: 0o700 });
296
+ return;
297
+ }
298
+ catch (error) {
299
+ if (error.code !== 'EEXIST')
300
+ throw error;
301
+ this.removeStaleLock();
302
+ if (this.now() - startedAt >= this.lockTimeoutMs) {
303
+ throw new DurableStateError('Timed out waiting for the durable-state writer lock.', 'LOCK_TIMEOUT', this.filePath);
304
+ }
305
+ sleepSync(10);
306
+ }
307
+ }
308
+ }
309
+ removeStaleLock() {
310
+ try {
311
+ const age = this.now() - fs.statSync(this.lockPath).mtimeMs;
312
+ if (age > this.staleLockMs)
313
+ fs.rmdirSync(this.lockPath);
314
+ }
315
+ catch {
316
+ // Another writer may have released the lock between stat and removal.
317
+ }
318
+ }
319
+ }
@@ -1,8 +1,5 @@
1
- /**
2
- * Vigthoria Balanced 4B — fast agent path classifier (~0.7–2s on local inference :8016).
3
- * Regex/trivial detection always wins; LLM router refines ambiguous build prompts only.
4
- */
5
- export type AgentRoutePath = 'template-instant' | 'v3-agent' | 'analysis-only';
1
+ /** The CLI has one executable agent transport; specialized behavior is a server-side hint. */
2
+ export type AgentRoutePath = 'v3-agent';
6
3
  export interface BalancedRouterJson {
7
4
  path?: string;
8
5
  task_kind?: string;
@@ -21,14 +18,16 @@ export interface BalancedRouterResult {
21
18
  endpoint?: string;
22
19
  error?: string;
23
20
  }
21
+ export type InferenceFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
24
22
  export declare function parseBalancedRouterJson(raw: string): BalancedRouterJson | null;
25
23
  /**
26
- * Inference bases for Balanced 4B routing only.
24
+ * Inference bases for the V4 Assistant routing lane only.
27
25
  * Local developer machines must opt in (env) — avoids ~1.5s localhost probe on every Windows turn.
28
26
  */
29
27
  export declare function getInferenceRouterBaseUrls(selfHostedUrl: string | null): string[];
30
- export declare function isFastInferenceRouterAvailable(selfHostedUrl: string | null, timeoutMs?: number): Promise<boolean>;
28
+ export declare function isFastInferenceRouterAvailable(selfHostedUrl: string | null, timeoutMs?: number, fetchImpl?: InferenceFetch): Promise<boolean>;
31
29
  export declare function routeWithBalanced4b(prompt: string, selfHostedUrl: string | null, options?: {
32
30
  timeoutMs?: number;
31
+ fetchImpl?: InferenceFetch;
33
32
  }): Promise<BalancedRouterResult>;
34
33
  export declare function shouldUseBalancedRouter(prompt: string, regexTaskKind?: string): boolean;
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Vigthoria Balanced 4B — fast agent path classifier (~0.7–2s on local inference :8016).
2
+ * Vigthoria v4 Assistant 9B — fast agent path classifier through the governed router.
3
3
  * Regex/trivial detection always wins; LLM router refines ambiguous build prompts only.
4
4
  */
5
+ import { redactSensitiveText } from './secret-policy.js';
5
6
  const ROUTER_SYSTEM = `You are Vigthoria Agent Router. Reply with ONE JSON object only. No markdown, no HTML, no code fences.
6
7
 
7
8
  Paths:
@@ -14,22 +15,8 @@ fast_path: "template-instant" or null
14
15
 
15
16
  Example: {"path":"template-instant","task_kind":"web-build","fast_path":"template-instant","confidence":0.95,"reason":"trivial html popup page"}`;
16
17
  const INFERENCE_MODEL_IDS = [
17
- 'vigthoria-balanced-4b',
18
- 'vigthoria-v3-balanced-4b',
18
+ 'Vigthoria-v4-Assistant-9B',
19
19
  ];
20
- function normalizePath(value) {
21
- const p = String(value || '').trim().toLowerCase();
22
- if (p === 'template-instant' || p === 'template' || p === 'html-template') {
23
- return 'template-instant';
24
- }
25
- if (p === 'analysis-only' || p === 'analysis') {
26
- return 'analysis-only';
27
- }
28
- if (p === 'v3-agent' || p === 'v3' || p === 'agent') {
29
- return 'v3-agent';
30
- }
31
- return null;
32
- }
33
20
  export function parseBalancedRouterJson(raw) {
34
21
  const text = String(raw || '').trim();
35
22
  if (!text)
@@ -56,7 +43,7 @@ export function parseBalancedRouterJson(raw) {
56
43
  return null;
57
44
  }
58
45
  /**
59
- * Inference bases for Balanced 4B routing only.
46
+ * Inference bases for the V4 Assistant routing lane only.
60
47
  * Local developer machines must opt in (env) — avoids ~1.5s localhost probe on every Windows turn.
61
48
  */
62
49
  export function getInferenceRouterBaseUrls(selfHostedUrl) {
@@ -69,7 +56,7 @@ export function getInferenceRouterBaseUrls(selfHostedUrl) {
69
56
  || process.env.VIGTHORIA_AGENT_LLM_ROUTER === '1'
70
57
  || process.platform === 'linux';
71
58
  if (allowLocalhost) {
72
- urls.push('http://127.0.0.1:8016');
59
+ urls.push('http://127.0.0.1:4009');
73
60
  }
74
61
  return [...new Set(urls)];
75
62
  }
@@ -95,15 +82,15 @@ function modelIdsFromPayload(payload) {
95
82
  }).filter(Boolean);
96
83
  }
97
84
  function includesBalancedRouterModel(modelIds) {
98
- const allowed = new Set(INFERENCE_MODEL_IDS);
85
+ const allowed = new Set(INFERENCE_MODEL_IDS.map((id) => id.toLowerCase()));
99
86
  return modelIds.some((id) => allowed.has(String(id).trim().toLowerCase().replace(/:latest$/, '')));
100
87
  }
101
- async function probeBalancedRouterBase(base, timeoutMs) {
88
+ async function probeBalancedRouterBase(base, timeoutMs, fetchImpl) {
102
89
  const fetchJson = async (path) => {
103
90
  const controller = new AbortController();
104
91
  const timer = setTimeout(() => controller.abort(), timeoutMs);
105
92
  try {
106
- const response = await fetch(`${base}${path}`, { signal: controller.signal });
93
+ const response = await fetchImpl(`${base}${path}`, { signal: controller.signal });
107
94
  const payload = await response.json().catch(() => null);
108
95
  return { ok: response.ok, payload };
109
96
  }
@@ -125,33 +112,37 @@ async function probeBalancedRouterBase(base, timeoutMs) {
125
112
  const catalog = await fetchJson('/v1/models');
126
113
  return catalog.ok && includesBalancedRouterModel(modelIdsFromPayload(catalog.payload));
127
114
  }
128
- async function compatibleInferenceRouterBases(selfHostedUrl, timeoutMs) {
115
+ async function compatibleInferenceRouterBases(selfHostedUrl, timeoutMs, fetchImpl) {
129
116
  const bases = getInferenceRouterBaseUrls(selfHostedUrl);
130
117
  const key = bases.join('|');
131
118
  const now = Date.now();
132
- if (inferenceHealthCache
119
+ if (fetchImpl === globalThis.fetch
120
+ &&
121
+ inferenceHealthCache
133
122
  && inferenceHealthCache.key === key
134
123
  && now - inferenceHealthCache.checkedAt < 60_000) {
135
124
  return inferenceHealthCache.compatibleBases;
136
125
  }
137
126
  const compatibleBases = [];
138
127
  for (const base of bases) {
139
- if (await probeBalancedRouterBase(base, timeoutMs)) {
128
+ if (await probeBalancedRouterBase(base, timeoutMs, fetchImpl)) {
140
129
  compatibleBases.push(base);
141
130
  }
142
131
  }
143
- inferenceHealthCache = { key, compatibleBases, checkedAt: now };
132
+ if (fetchImpl === globalThis.fetch) {
133
+ inferenceHealthCache = { key, compatibleBases, checkedAt: now };
134
+ }
144
135
  return compatibleBases;
145
136
  }
146
- export async function isFastInferenceRouterAvailable(selfHostedUrl, timeoutMs = 400) {
137
+ export async function isFastInferenceRouterAvailable(selfHostedUrl, timeoutMs = 400, fetchImpl = globalThis.fetch) {
147
138
  if (process.env.VIGTHORIA_AGENT_LLM_ROUTER === '0') {
148
139
  return false;
149
140
  }
150
- return (await compatibleInferenceRouterBases(selfHostedUrl, timeoutMs)).length > 0;
141
+ return (await compatibleInferenceRouterBases(selfHostedUrl, timeoutMs, fetchImpl)).length > 0;
151
142
  }
152
143
  export async function routeWithBalanced4b(prompt, selfHostedUrl, options = {}) {
153
144
  const timeoutMs = options.timeoutMs ?? Number.parseInt(process.env.VIGTHORIA_AGENT_ROUTER_TIMEOUT_MS || '8000', 10);
154
- const bases = await compatibleInferenceRouterBases(selfHostedUrl, Math.min(timeoutMs, 1500));
145
+ const bases = await compatibleInferenceRouterBases(selfHostedUrl, Math.min(timeoutMs, 1500), options.fetchImpl || globalThis.fetch);
155
146
  let lastError = 'Inference router unreachable';
156
147
  if (bases.length === 0) {
157
148
  return { ok: false, error: 'No compatible Balanced 4B inference endpoint is available' };
@@ -162,14 +153,14 @@ export async function routeWithBalanced4b(prompt, selfHostedUrl, options = {}) {
162
153
  const controller = new AbortController();
163
154
  const timer = setTimeout(() => controller.abort(), timeoutMs);
164
155
  try {
165
- const response = await fetch(`${base}/v1/chat/completions`, {
156
+ const response = await (options.fetchImpl || globalThis.fetch)(`${base}/v1/chat/completions`, {
166
157
  method: 'POST',
167
158
  headers: { 'Content-Type': 'application/json' },
168
159
  body: JSON.stringify({
169
160
  model,
170
161
  messages: [
171
162
  { role: 'system', content: ROUTER_SYSTEM },
172
- { role: 'user', content: String(prompt || '').slice(0, 1200) },
163
+ { role: 'user', content: redactSensitiveText(String(prompt || '')).slice(0, 1200) },
173
164
  ],
174
165
  max_tokens: 96,
175
166
  temperature: 0,
@@ -4,6 +4,8 @@
4
4
  import * as fs from 'fs';
5
5
  import * as path from 'path';
6
6
  import { glob } from 'glob';
7
+ import { assertContainedAbsolutePath, resolveWorkspacePath } from './workspace-boundary.js';
8
+ import { containsHighConfidenceSecret, isSensitivePath } from './secret-policy.js';
7
9
  export class FileUtils {
8
10
  projectRoot;
9
11
  ignorePatterns;
@@ -21,22 +23,19 @@ export class FileUtils {
21
23
  // Read file with metadata
22
24
  readFile(filePath) {
23
25
  try {
24
- let absolutePath = path.isAbsolute(filePath)
25
- ? filePath
26
- : path.join(this.projectRoot, filePath);
27
- // Resolve symlinks for consistent path handling
28
- try {
29
- absolutePath = fs.realpathSync(absolutePath);
30
- }
31
- catch {
32
- // Keep original path if realpath fails
33
- }
26
+ if (isSensitivePath(filePath))
27
+ return null;
28
+ const absolutePath = path.isAbsolute(filePath)
29
+ ? assertContainedAbsolutePath(this.projectRoot, filePath)
30
+ : resolveWorkspacePath(this.projectRoot, filePath);
34
31
  // Check if it's a regular file (not a directory or special file)
35
32
  const stats = fs.statSync(absolutePath);
36
33
  if (!stats.isFile()) {
37
34
  return null;
38
35
  }
39
36
  const content = fs.readFileSync(absolutePath, 'utf-8');
37
+ if (containsHighConfidenceSecret(content))
38
+ return null;
40
39
  const relativePath = path.relative(this.projectRoot, absolutePath);
41
40
  return {
42
41
  path: absolutePath,
@@ -62,8 +61,8 @@ export class FileUtils {
62
61
  writeFile(filePath, content) {
63
62
  try {
64
63
  const absolutePath = path.isAbsolute(filePath)
65
- ? filePath
66
- : path.join(this.projectRoot, filePath);
64
+ ? assertContainedAbsolutePath(this.projectRoot, filePath, { allowMissing: true })
65
+ : resolveWorkspacePath(this.projectRoot, filePath, { allowMissing: true });
67
66
  // Create directory if needed
68
67
  const dir = path.dirname(absolutePath);
69
68
  if (!fs.existsSync(dir)) {
@@ -98,8 +97,17 @@ export class FileUtils {
98
97
  cwd: this.projectRoot,
99
98
  ignore: this.ignorePatterns,
100
99
  nodir: true,
100
+ follow: false,
101
+ });
102
+ return files.filter((file) => {
103
+ try {
104
+ resolveWorkspacePath(this.projectRoot, file);
105
+ return !isSensitivePath(file);
106
+ }
107
+ catch {
108
+ return false;
109
+ }
101
110
  });
102
- return files;
103
111
  }
104
112
  catch (error) {
105
113
  // Return empty array on error (e.g., permission issues on Windows)