vigthoria-cli 1.13.20 → 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.
- package/completions/_vigthoria +49 -0
- package/completions/vigthoria.bash +6 -0
- package/completions/vigthoria.fish +41 -0
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +63 -128
- package/dist/commands/background.d.ts +0 -2
- package/dist/commands/background.js +26 -34
- package/dist/commands/bridge.js +45 -41
- package/dist/commands/cancel.d.ts +1 -0
- package/dist/commands/cancel.js +22 -16
- package/dist/commands/chat.d.ts +3 -42
- package/dist/commands/chat.js +378 -1123
- package/dist/commands/coding-registration.d.ts +4 -0
- package/dist/commands/coding-registration.js +24 -0
- package/dist/commands/config.js +23 -29
- package/dist/commands/conversation-registration.d.ts +4 -0
- package/dist/commands/conversation-registration.js +122 -0
- package/dist/commands/deploy.d.ts +8 -2
- package/dist/commands/deploy.js +92 -90
- package/dist/commands/device.d.ts +1 -2
- package/dist/commands/device.js +37 -21
- package/dist/commands/doctor-registration.d.ts +4 -0
- package/dist/commands/doctor-registration.js +200 -0
- package/dist/commands/edit.js +22 -33
- package/dist/commands/explain.js +7 -10
- package/dist/commands/fork.d.ts +1 -2
- package/dist/commands/fork.js +72 -22
- package/dist/commands/game.d.ts +1 -1
- package/dist/commands/game.js +17 -6
- package/dist/commands/generate.js +7 -9
- package/dist/commands/history.js +11 -7
- package/dist/commands/hub.d.ts +2 -2
- package/dist/commands/hub.js +34 -35
- package/dist/commands/legion.d.ts +0 -1
- package/dist/commands/legion.js +62 -79
- package/dist/commands/music.d.ts +1 -2
- package/dist/commands/music.js +62 -27
- package/dist/commands/platform-registration.d.ts +4 -0
- package/dist/commands/platform-registration.js +45 -0
- package/dist/commands/preview.d.ts +0 -1
- package/dist/commands/preview.js +82 -35
- package/dist/commands/product-run-registration.d.ts +4 -0
- package/dist/commands/product-run-registration.js +490 -0
- package/dist/commands/replay.d.ts +1 -2
- package/dist/commands/replay.js +15 -11
- package/dist/commands/repo.d.ts +14 -7
- package/dist/commands/repo.js +245 -196
- package/dist/commands/review.js +9 -12
- package/dist/commands/security.d.ts +1 -2
- package/dist/commands/security.js +12 -6
- package/dist/commands/update-registration.d.ts +3 -0
- package/dist/commands/update-registration.js +338 -0
- package/dist/commands/v4-registration.d.ts +4 -0
- package/dist/commands/v4-registration.js +24 -0
- package/dist/commands/v4.d.ts +1 -1
- package/dist/commands/v4.js +46 -8
- package/dist/commands/wallet.d.ts +1 -3
- package/dist/commands/wallet.js +18 -45
- package/dist/commands/workflow.d.ts +2 -0
- package/dist/commands/workflow.js +52 -39
- package/dist/index.js +122 -1589
- package/dist/utils/agent-stream-state.d.ts +27 -0
- package/dist/utils/agent-stream-state.js +93 -0
- package/dist/utils/agentRoute.d.ts +2 -1
- package/dist/utils/agentRoute.js +22 -9
- package/dist/utils/agentRunOutcome.d.ts +16 -0
- package/dist/utils/agentRunOutcome.js +37 -2
- package/dist/utils/api-client-factory.d.ts +8 -0
- package/dist/utils/api-client-factory.js +8 -0
- package/dist/utils/api.d.ts +44 -266
- package/dist/utils/api.js +647 -3180
- package/dist/utils/auth-session.d.ts +41 -0
- package/dist/utils/auth-session.js +224 -0
- package/dist/utils/brain-hub-client.d.ts +0 -4
- package/dist/utils/brain-hub-client.js +6 -3
- package/dist/utils/bridge-client.js +2 -0
- package/dist/utils/capability-health-service.d.ts +48 -0
- package/dist/utils/capability-health-service.js +64 -0
- package/dist/utils/chat-prompt-policy.d.ts +18 -0
- package/dist/utils/chat-prompt-policy.js +61 -0
- package/dist/utils/chat-tool-call-parser.d.ts +11 -0
- package/dist/utils/chat-tool-call-parser.js +71 -0
- package/dist/utils/cli-state.d.ts +22 -4
- package/dist/utils/cli-state.js +105 -47
- package/dist/utils/code-operations-service.d.ts +104 -0
- package/dist/utils/code-operations-service.js +837 -0
- package/dist/utils/codebase-indexer.d.ts +1 -0
- package/dist/utils/codebase-indexer.js +28 -8
- package/dist/utils/command-contract.d.ts +61 -0
- package/dist/utils/command-contract.js +149 -0
- package/dist/utils/command-policy.d.ts +10 -0
- package/dist/utils/command-policy.js +54 -0
- package/dist/utils/config.d.ts +13 -5
- package/dist/utils/config.js +220 -94
- package/dist/utils/context-ranker.js +11 -2
- package/dist/utils/context-state.d.ts +39 -0
- package/dist/utils/context-state.js +52 -0
- package/dist/utils/deckEvents.d.ts +0 -5
- package/dist/utils/deckEvents.js +3 -2
- package/dist/utils/desktop-bridge-client.js +2 -0
- package/dist/utils/direct-output-policy.d.ts +3 -0
- package/dist/utils/direct-output-policy.js +76 -0
- package/dist/utils/durable-json.d.ts +57 -0
- package/dist/utils/durable-json.js +319 -0
- package/dist/utils/fastAgentRouter.d.ts +5 -6
- package/dist/utils/fastAgentRouter.js +17 -25
- package/dist/utils/files.js +21 -13
- package/dist/utils/frontend-preview-service.d.ts +76 -0
- package/dist/utils/frontend-preview-service.js +482 -0
- package/dist/utils/goaEvents.d.ts +0 -4
- package/dist/utils/goaEvents.js +5 -2
- package/dist/utils/logger.d.ts +2 -1
- package/dist/utils/logger.js +27 -20
- package/dist/utils/mcp-context-client.d.ts +24 -0
- package/dist/utils/mcp-context-client.js +58 -0
- package/dist/utils/model-governance.d.ts +18 -0
- package/dist/utils/model-governance.js +75 -0
- package/dist/utils/model-transport-service.d.ts +44 -0
- package/dist/utils/model-transport-service.js +111 -0
- package/dist/utils/mutation-journal.d.ts +42 -0
- package/dist/utils/mutation-journal.js +387 -0
- package/dist/utils/network-policy.d.ts +24 -0
- package/dist/utils/network-policy.js +202 -0
- package/dist/utils/operator-client.d.ts +45 -0
- package/dist/utils/operator-client.js +159 -0
- package/dist/utils/post-write-validator.js +7 -9
- package/dist/utils/preview-screenshot-adapter.d.ts +13 -0
- package/dist/utils/preview-screenshot-adapter.js +36 -0
- package/dist/utils/process-policy.d.ts +23 -0
- package/dist/utils/process-policy.js +284 -0
- package/dist/utils/project-memory.d.ts +13 -3
- package/dist/utils/project-memory.js +70 -17
- package/dist/utils/prompt-workspace-resolver.d.ts +14 -0
- package/dist/utils/prompt-workspace-resolver.js +64 -0
- package/dist/utils/release-install.d.ts +11 -0
- package/dist/utils/release-install.js +155 -0
- package/dist/utils/release-policy.d.ts +64 -0
- package/dist/utils/release-policy.js +293 -0
- package/dist/utils/requestIntent.js +22 -3
- package/dist/utils/runtime-capability.d.ts +8 -0
- package/dist/utils/runtime-capability.js +16 -0
- package/dist/utils/secret-policy.d.ts +20 -0
- package/dist/utils/secret-policy.js +160 -0
- package/dist/utils/session.d.ts +17 -3
- package/dist/utils/session.js +354 -110
- package/dist/utils/tool-approval-service.d.ts +17 -0
- package/dist/utils/tool-approval-service.js +78 -0
- package/dist/utils/tool-capability-providers.d.ts +57 -0
- package/dist/utils/tool-capability-providers.js +57 -0
- package/dist/utils/tools.d.ts +2 -23
- package/dist/utils/tools.js +216 -508
- package/dist/utils/update-policy.d.ts +18 -0
- package/dist/utils/update-policy.js +137 -0
- package/dist/utils/v3-agent-client.d.ts +40 -0
- package/dist/utils/v3-agent-client.js +165 -0
- package/dist/utils/v3-workspace-service.d.ts +28 -0
- package/dist/utils/v3-workspace-service.js +279 -0
- package/dist/utils/vigflow-client.d.ts +95 -0
- package/dist/utils/vigflow-client.js +105 -0
- package/dist/utils/workspace-boundary.d.ts +45 -0
- package/dist/utils/workspace-boundary.js +377 -0
- package/dist/utils/workspace-brain-service.d.ts +2 -0
- package/dist/utils/workspace-brain-service.js +11 -1
- package/dist/utils/workspace-cache.js +37 -15
- package/dist/utils/workspace-stream.js +35 -10
- package/install.ps1 +85 -142
- package/install.sh +75 -80
- package/package.json +43 -9
- package/release-policy.json +43 -0
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +1 -1
- package/scripts/release/generate-completions.mjs +24 -0
- package/scripts/release/install-release.mjs +10 -0
- package/scripts/release/publish-cli-release.mjs +116 -0
- package/scripts/release/publish-cli-release.sh +2 -70
- package/scripts/release/resolve-release-manifest.mjs +26 -0
- package/scripts/release/test-balanced-model-live.sh +42 -0
- package/scripts/release/validate-live-service-gates.sh +90 -0
- package/scripts/release/validate-no-go-gates.sh +135 -174
- package/scripts/release/verify-runtime-consistency.mjs +4 -1
- package/dist/commands/index.d.ts +0 -12
- package/dist/commands/index.js +0 -194
- package/dist/commands/update.d.ts +0 -9
- package/dist/commands/update.js +0 -301
- package/dist/utils/templateInstantPath.d.ts +0 -18
- package/dist/utils/templateInstantPath.js +0 -228
package/dist/utils/config.js
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
* Configuration Manager for Vigthoria CLI
|
|
3
3
|
*/
|
|
4
4
|
import Conf from 'conf';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as os from 'os';
|
|
7
|
+
import * as path from 'path';
|
|
5
8
|
import { secureFileMode } from './cli-state.js';
|
|
9
|
+
import { DurableJsonStore } from './durable-json.js';
|
|
10
|
+
import { assertTrustedEndpoint, assertTrustedWebSocketEndpoint, guardedFetch } from './network-policy.js';
|
|
6
11
|
const defaultConfig = {
|
|
7
12
|
apiUrl: 'https://coder.vigthoria.io',
|
|
8
13
|
modelsApiUrl: 'https://api.vigthoria.io', // Direct AI Models API - no proxying through Coder
|
|
@@ -43,95 +48,213 @@ const defaultConfig = {
|
|
|
43
48
|
],
|
|
44
49
|
},
|
|
45
50
|
};
|
|
51
|
+
function cloneDefaultConfig() {
|
|
52
|
+
return JSON.parse(JSON.stringify(defaultConfig));
|
|
53
|
+
}
|
|
54
|
+
function readJsonObject(filePath) {
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
57
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function optionalString(value) {
|
|
64
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
65
|
+
}
|
|
66
|
+
function normalizeConfigValue(value, base = cloneDefaultConfig()) {
|
|
67
|
+
const raw = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
68
|
+
const next = JSON.parse(JSON.stringify(base));
|
|
69
|
+
const legacyUser = raw.user && typeof raw.user === 'object' ? raw.user : {};
|
|
70
|
+
for (const key of ['apiUrl', 'modelsApiUrl', 'musicApiUrl', 'wsUrl']) {
|
|
71
|
+
if (typeof raw[key] === 'string' && raw[key])
|
|
72
|
+
next[key] = String(raw[key]);
|
|
73
|
+
}
|
|
74
|
+
if (raw.selfHostedModelsApiUrl === null || typeof raw.selfHostedModelsApiUrl === 'string')
|
|
75
|
+
next.selfHostedModelsApiUrl = raw.selfHostedModelsApiUrl;
|
|
76
|
+
next.authToken = optionalString(raw.authToken ?? raw.token) ?? next.authToken;
|
|
77
|
+
next.refreshToken = optionalString(raw.refreshToken) ?? next.refreshToken;
|
|
78
|
+
next.userId = optionalString(raw.userId ?? legacyUser.id) ?? next.userId;
|
|
79
|
+
next.email = optionalString(raw.email ?? legacyUser.email) ?? next.email;
|
|
80
|
+
next.v3ServiceKey = optionalString(raw.v3ServiceKey) ?? next.v3ServiceKey;
|
|
81
|
+
if (raw.subscription && typeof raw.subscription === 'object' && !Array.isArray(raw.subscription)) {
|
|
82
|
+
const subscription = raw.subscription;
|
|
83
|
+
next.subscription = {
|
|
84
|
+
plan: optionalString(subscription.plan),
|
|
85
|
+
status: optionalString(subscription.status),
|
|
86
|
+
expiresAt: optionalString(subscription.expiresAt ?? subscription.expires_at),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (raw.preferences && typeof raw.preferences === 'object' && !Array.isArray(raw.preferences)) {
|
|
90
|
+
next.preferences = { ...next.preferences, ...raw.preferences };
|
|
91
|
+
}
|
|
92
|
+
if (raw.project && typeof raw.project === 'object' && !Array.isArray(raw.project)) {
|
|
93
|
+
const project = raw.project;
|
|
94
|
+
next.project = {
|
|
95
|
+
rootPath: project.rootPath === null ? null : optionalString(project.rootPath) ?? next.project.rootPath,
|
|
96
|
+
ignorePatterns: Array.isArray(project.ignorePatterns)
|
|
97
|
+
? project.ignorePatterns.filter((entry) => typeof entry === 'string')
|
|
98
|
+
: next.project.ignorePatterns,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (raw.persona === 'default' || raw.persona === 'wiener_grant')
|
|
102
|
+
next.persona = raw.persona;
|
|
103
|
+
if (raw.hubModelPrefs && typeof raw.hubModelPrefs === 'object' && !Array.isArray(raw.hubModelPrefs)) {
|
|
104
|
+
const prefs = raw.hubModelPrefs;
|
|
105
|
+
next.hubModelPrefs = {
|
|
106
|
+
enabledCloudModels: Array.isArray(prefs.enabledCloudModels)
|
|
107
|
+
? prefs.enabledCloudModels.filter((entry) => typeof entry === 'string')
|
|
108
|
+
: [],
|
|
109
|
+
defaultCloudModel: optionalString(prefs.defaultCloudModel) || undefined,
|
|
110
|
+
defaultLocalModel: optionalString(prefs.defaultLocalModel) || undefined,
|
|
111
|
+
balance: typeof prefs.balance === 'number' && Number.isFinite(prefs.balance) ? prefs.balance : undefined,
|
|
112
|
+
fetchedAt: optionalString(prefs.fetchedAt) || undefined,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return next;
|
|
116
|
+
}
|
|
117
|
+
function isConfigValue(value) {
|
|
118
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
119
|
+
return false;
|
|
120
|
+
const candidate = value;
|
|
121
|
+
return typeof candidate.apiUrl === 'string'
|
|
122
|
+
&& typeof candidate.modelsApiUrl === 'string'
|
|
123
|
+
&& typeof candidate.musicApiUrl === 'string'
|
|
124
|
+
&& typeof candidate.wsUrl === 'string'
|
|
125
|
+
&& Boolean(candidate.subscription && typeof candidate.subscription === 'object')
|
|
126
|
+
&& Boolean(candidate.preferences && typeof candidate.preferences === 'object')
|
|
127
|
+
&& Boolean(candidate.project && typeof candidate.project === 'object')
|
|
128
|
+
&& Array.isArray(candidate.project.ignorePatterns);
|
|
129
|
+
}
|
|
46
130
|
export class Config {
|
|
47
131
|
store;
|
|
48
132
|
static OPERATOR_PLANS = new Set(['enterprise', 'admin', 'master_admin']);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
showDiffs: { type: 'boolean' },
|
|
86
|
-
contextLines: { type: 'number' },
|
|
87
|
-
maxTokens: { type: 'number' },
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
persona: { type: 'string', enum: ['default', 'wiener_grant'] },
|
|
91
|
-
project: {
|
|
92
|
-
type: 'object',
|
|
93
|
-
properties: {
|
|
94
|
-
rootPath: { type: ['string', 'null'] },
|
|
95
|
-
ignorePatterns: { type: 'array', items: { type: 'string' } },
|
|
96
|
-
},
|
|
97
|
-
},
|
|
133
|
+
constructor(options = {}) {
|
|
134
|
+
const stateRoot = path.resolve(options.stateRoot || os.homedir());
|
|
135
|
+
const canonicalPath = path.join(stateRoot, '.vigthoria', 'config.json');
|
|
136
|
+
const legacyConfPath = options.legacyConfPath
|
|
137
|
+
? path.resolve(options.legacyConfPath)
|
|
138
|
+
: options.stateRoot
|
|
139
|
+
? path.join(stateRoot, '.legacy-conf', 'config.json')
|
|
140
|
+
: new Conf({ projectName: 'vigthoria-cli' }).path;
|
|
141
|
+
const legacyCandidate = path.resolve(legacyConfPath) === path.resolve(canonicalPath) ? null : readJsonObject(legacyConfPath);
|
|
142
|
+
const legacyEnvelopeData = legacyCandidate?.data && typeof legacyCandidate.data === 'object'
|
|
143
|
+
? legacyCandidate.data
|
|
144
|
+
: null;
|
|
145
|
+
const legacyConf = legacyEnvelopeData?.credentialsRetired === true ? null : legacyCandidate;
|
|
146
|
+
const legacyMtime = legacyConf ? fs.statSync(legacyConfPath).mtimeMs : 0;
|
|
147
|
+
const canonicalMtime = fs.existsSync(canonicalPath) ? fs.statSync(canonicalPath).mtimeMs : 0;
|
|
148
|
+
this.store = new DurableJsonStore({
|
|
149
|
+
filePath: canonicalPath,
|
|
150
|
+
schemaVersion: 2,
|
|
151
|
+
defaultValue: cloneDefaultConfig,
|
|
152
|
+
validate: isConfigValue,
|
|
153
|
+
migrate: (value) => {
|
|
154
|
+
const fromConf = legacyConf ? normalizeConfigValue(legacyConf) : cloneDefaultConfig();
|
|
155
|
+
const merged = normalizeConfigValue(value, fromConf);
|
|
156
|
+
// During the one-time collision resolution, preserve the most
|
|
157
|
+
// recently written credential bundle. Non-auth preferences use
|
|
158
|
+
// canonical-file precedence.
|
|
159
|
+
if (legacyConf && legacyMtime > canonicalMtime) {
|
|
160
|
+
const legacyCredentials = normalizeConfigValue(legacyConf);
|
|
161
|
+
merged.authToken = legacyCredentials.authToken;
|
|
162
|
+
merged.refreshToken = legacyCredentials.refreshToken;
|
|
163
|
+
merged.userId = legacyCredentials.userId;
|
|
164
|
+
merged.email = legacyCredentials.email;
|
|
165
|
+
merged.v3ServiceKey = legacyCredentials.v3ServiceKey;
|
|
166
|
+
merged.subscription = legacyCredentials.subscription;
|
|
167
|
+
}
|
|
168
|
+
return merged;
|
|
98
169
|
},
|
|
99
170
|
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
171
|
+
const canonicalExisted = fs.existsSync(canonicalPath);
|
|
172
|
+
if (!canonicalExisted && legacyConf) {
|
|
173
|
+
this.store.write(normalizeConfigValue(legacyConf));
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
// Triggers legacy-flat migration and corruption recovery before any
|
|
177
|
+
// command can observe configuration.
|
|
178
|
+
this.store.read();
|
|
105
179
|
}
|
|
106
|
-
|
|
180
|
+
if (legacyConf)
|
|
181
|
+
this.retireLegacyConfStore(legacyConfPath, canonicalPath);
|
|
182
|
+
this.secureOnDisk();
|
|
183
|
+
}
|
|
184
|
+
retireLegacyConfStore(legacyPath, canonicalPath) {
|
|
185
|
+
const retired = new DurableJsonStore({
|
|
186
|
+
filePath: legacyPath,
|
|
187
|
+
schemaVersion: 1,
|
|
188
|
+
defaultValue: () => ({ migratedTo: canonicalPath, credentialsRetired: true }),
|
|
189
|
+
validate: (value) => Boolean(value && typeof value === 'object'
|
|
190
|
+
&& typeof value.migratedTo === 'string'
|
|
191
|
+
&& value.credentialsRetired === true),
|
|
192
|
+
migrate: () => ({ migratedTo: canonicalPath, credentialsRetired: true }),
|
|
193
|
+
});
|
|
194
|
+
retired.write({ migratedTo: canonicalPath, credentialsRetired: true });
|
|
107
195
|
}
|
|
108
196
|
/**
|
|
109
197
|
* Re-apply secure POSIX permissions to the on-disk config file. Safe
|
|
110
198
|
* to call repeatedly; no-op on Windows.
|
|
111
199
|
*/
|
|
112
200
|
secureOnDisk() {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
201
|
+
secureFileMode(this.store.filePath);
|
|
202
|
+
}
|
|
203
|
+
validateEndpointValue(key, value) {
|
|
204
|
+
if (typeof value !== 'string')
|
|
205
|
+
return value;
|
|
206
|
+
if (key === 'apiUrl')
|
|
207
|
+
assertTrustedEndpoint(value, { audience: 'coder', ignoreOffline: true });
|
|
208
|
+
else if (key === 'modelsApiUrl' || key === 'selfHostedModelsApiUrl')
|
|
209
|
+
assertTrustedEndpoint(value, { audience: 'models', ignoreOffline: true });
|
|
210
|
+
else if (key === 'musicApiUrl')
|
|
211
|
+
assertTrustedEndpoint(value, { audience: 'music', ignoreOffline: true });
|
|
212
|
+
else if (key === 'wsUrl')
|
|
213
|
+
assertTrustedWebSocketEndpoint(value, 'coder', false, true);
|
|
214
|
+
return value;
|
|
117
215
|
}
|
|
118
216
|
get(key) {
|
|
119
|
-
return this.store.
|
|
217
|
+
return this.validateEndpointValue(key, this.store.read()[key]);
|
|
120
218
|
}
|
|
121
219
|
set(key, value) {
|
|
122
|
-
this.
|
|
220
|
+
const validated = this.validateEndpointValue(key, value);
|
|
221
|
+
this.store.update((current) => ({ ...current, [key]: validated }));
|
|
222
|
+
this.secureOnDisk();
|
|
123
223
|
}
|
|
124
224
|
getAll() {
|
|
125
|
-
|
|
225
|
+
const current = this.store.read();
|
|
226
|
+
return {
|
|
227
|
+
...current,
|
|
228
|
+
apiUrl: this.validateEndpointValue('apiUrl', current.apiUrl),
|
|
229
|
+
modelsApiUrl: this.validateEndpointValue('modelsApiUrl', current.modelsApiUrl),
|
|
230
|
+
musicApiUrl: this.validateEndpointValue('musicApiUrl', current.musicApiUrl),
|
|
231
|
+
wsUrl: this.validateEndpointValue('wsUrl', current.wsUrl),
|
|
232
|
+
selfHostedModelsApiUrl: current.selfHostedModelsApiUrl
|
|
233
|
+
? this.validateEndpointValue('selfHostedModelsApiUrl', current.selfHostedModelsApiUrl)
|
|
234
|
+
: null,
|
|
235
|
+
};
|
|
126
236
|
}
|
|
127
237
|
reset() {
|
|
128
|
-
this.store.
|
|
238
|
+
this.store.write(cloneDefaultConfig());
|
|
239
|
+
}
|
|
240
|
+
resetPreservingAuth() {
|
|
241
|
+
this.store.update((current) => {
|
|
242
|
+
const next = cloneDefaultConfig();
|
|
243
|
+
next.authToken = current.authToken;
|
|
244
|
+
next.refreshToken = current.refreshToken;
|
|
245
|
+
next.userId = current.userId;
|
|
246
|
+
next.email = current.email;
|
|
247
|
+
next.v3ServiceKey = current.v3ServiceKey;
|
|
248
|
+
next.subscription = { ...current.subscription };
|
|
249
|
+
return next;
|
|
250
|
+
});
|
|
251
|
+
this.secureOnDisk();
|
|
129
252
|
}
|
|
130
253
|
isAuthenticated() {
|
|
131
|
-
return this.
|
|
254
|
+
return Boolean(this.get('authToken'));
|
|
132
255
|
}
|
|
133
256
|
hasValidSubscription() {
|
|
134
|
-
const sub = this.
|
|
257
|
+
const sub = this.get('subscription');
|
|
135
258
|
if (!sub.status || sub.status !== 'active')
|
|
136
259
|
return false;
|
|
137
260
|
if (sub.expiresAt) {
|
|
@@ -141,7 +264,7 @@ export class Config {
|
|
|
141
264
|
return true;
|
|
142
265
|
}
|
|
143
266
|
getNormalizedPlan() {
|
|
144
|
-
return (this.
|
|
267
|
+
return (this.get('subscription').plan || '').trim().toLowerCase();
|
|
145
268
|
}
|
|
146
269
|
hasOperatorAccess() {
|
|
147
270
|
return Config.OPERATOR_PLANS.has(this.getNormalizedPlan());
|
|
@@ -153,42 +276,45 @@ export class Config {
|
|
|
153
276
|
return this.isAuthenticated();
|
|
154
277
|
}
|
|
155
278
|
getConfigPath() {
|
|
156
|
-
return this.store.
|
|
279
|
+
return this.store.filePath;
|
|
157
280
|
}
|
|
158
281
|
// Auth helpers
|
|
159
282
|
setAuth(data) {
|
|
160
|
-
this.store.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
283
|
+
this.store.update((current) => ({
|
|
284
|
+
...current,
|
|
285
|
+
authToken: data.token,
|
|
286
|
+
refreshToken: data.refreshToken === undefined ? current.refreshToken : data.refreshToken,
|
|
287
|
+
userId: data.userId === undefined ? current.userId : data.userId,
|
|
288
|
+
email: data.email === undefined ? current.email : data.email,
|
|
289
|
+
apiUrl: data.apiUrl ? this.validateEndpointValue('apiUrl', data.apiUrl) : current.apiUrl,
|
|
290
|
+
v3ServiceKey: data.v3ServiceKey === undefined ? current.v3ServiceKey : data.v3ServiceKey,
|
|
291
|
+
}));
|
|
166
292
|
// Tighten permissions whenever the token rotates so a brand-new
|
|
167
293
|
// install (which may have created the file under a previous default
|
|
168
294
|
// umask) becomes 0600 immediately after first login.
|
|
169
295
|
this.secureOnDisk();
|
|
170
296
|
}
|
|
171
297
|
clearAuth() {
|
|
172
|
-
this.store.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
298
|
+
this.store.update((current) => ({
|
|
299
|
+
...current,
|
|
300
|
+
authToken: null,
|
|
301
|
+
refreshToken: null,
|
|
302
|
+
userId: null,
|
|
303
|
+
email: null,
|
|
304
|
+
v3ServiceKey: null,
|
|
305
|
+
hubModelPrefs: undefined,
|
|
306
|
+
subscription: { plan: null, status: null, expiresAt: null },
|
|
307
|
+
}));
|
|
181
308
|
}
|
|
182
309
|
setSubscription(data) {
|
|
183
|
-
this.store.
|
|
184
|
-
|
|
185
|
-
status: data.status,
|
|
186
|
-
|
|
187
|
-
});
|
|
310
|
+
this.store.update((current) => ({
|
|
311
|
+
...current,
|
|
312
|
+
subscription: { plan: data.plan, status: data.status, expiresAt: data.expiresAt || null },
|
|
313
|
+
}));
|
|
188
314
|
}
|
|
189
315
|
// Model selection - Vigthoria Models (internal routing IDs only)
|
|
190
316
|
getAvailableModels() {
|
|
191
|
-
const sub = this.
|
|
317
|
+
const sub = this.get('subscription');
|
|
192
318
|
const plan = (sub.plan || '').toLowerCase();
|
|
193
319
|
void plan;
|
|
194
320
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -208,28 +334,28 @@ export class Config {
|
|
|
208
334
|
// Legacy aliases
|
|
209
335
|
{ id: 'cloud', name: 'Vigthoria Cloud Balanced', description: 'Legacy alias for cloud-balanced', tier: 'cloud', backendModel: 'vigthoria-cloud-balanced' }, { id: 'cloud-reason', name: 'Vigthoria Cloud Balanced', description: 'Legacy alias for cloud-balanced', tier: 'cloud', backendModel: 'vigthoria-cloud-balanced' }, { id: 'ultra', name: 'Vigthoria Cloud Maximum', description: 'Legacy alias for cloud-maximum', tier: 'cloud', backendModel: 'vigthoria-cloud-maximum' });
|
|
210
336
|
}
|
|
211
|
-
const hubPrefs = this.
|
|
337
|
+
const hubPrefs = this.get('hubModelPrefs');
|
|
212
338
|
if (hubPrefs?.enabledCloudModels?.length) {
|
|
213
339
|
return this.filterModelsByHubPreferences(models, hubPrefs.enabledCloudModels);
|
|
214
340
|
}
|
|
215
341
|
return models;
|
|
216
342
|
}
|
|
217
343
|
async refreshHubModelPreferences() {
|
|
218
|
-
const token = this.
|
|
219
|
-
const apiUrl = String(this.
|
|
344
|
+
const token = this.get('authToken');
|
|
345
|
+
const apiUrl = String(this.get('apiUrl') || '').replace(/\/$/, '');
|
|
220
346
|
if (!token || !apiUrl)
|
|
221
347
|
return;
|
|
222
348
|
try {
|
|
223
|
-
const response = await
|
|
349
|
+
const response = await guardedFetch(`${apiUrl}/api/user/credits/model-preferences`, {
|
|
224
350
|
headers: { Authorization: `Bearer ${token}` },
|
|
225
351
|
signal: AbortSignal.timeout(8000),
|
|
226
|
-
});
|
|
352
|
+
}, { audience: 'coder' });
|
|
227
353
|
if (!response.ok)
|
|
228
354
|
return;
|
|
229
355
|
const prefs = await response.json();
|
|
230
356
|
if (prefs?.success === false)
|
|
231
357
|
return;
|
|
232
|
-
this.
|
|
358
|
+
this.set('hubModelPrefs', {
|
|
233
359
|
enabledCloudModels: Array.isArray(prefs.enabledCloudModels) ? prefs.enabledCloudModels : [],
|
|
234
360
|
defaultCloudModel: typeof prefs.defaultCloudModel === 'string' ? prefs.defaultCloudModel : undefined,
|
|
235
361
|
defaultLocalModel: typeof prefs.defaultLocalModel === 'string' ? prefs.defaultLocalModel : undefined,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import fs from 'node:fs';
|
|
11
11
|
import path from 'node:path';
|
|
12
|
+
import { containsHighConfidenceSecret, isSensitivePath } from './secret-policy.js';
|
|
12
13
|
const IGNORED_DIRS = new Set([
|
|
13
14
|
'.git', 'node_modules', 'dist', '.next', '__pycache__',
|
|
14
15
|
'.vigthoria', 'coverage', '.cache', '.turbo', 'build', 'out',
|
|
@@ -16,7 +17,7 @@ const IGNORED_DIRS = new Set([
|
|
|
16
17
|
const TEXT_EXTENSIONS = new Set([
|
|
17
18
|
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
18
19
|
'.py', '.go', '.rs', '.java', '.c', '.cpp', '.h', '.cs', '.php', '.rb',
|
|
19
|
-
'.json', '.yaml', '.yml', '.toml', '.
|
|
20
|
+
'.json', '.yaml', '.yml', '.toml', '.sh', '.bash',
|
|
20
21
|
'.html', '.css', '.scss', '.sass', '.less',
|
|
21
22
|
'.md', '.txt', '.xml', '.svg',
|
|
22
23
|
]);
|
|
@@ -86,6 +87,9 @@ function walkWorkspace(dir, maxFiles = 800) {
|
|
|
86
87
|
if (IGNORED_DIRS.has(entry.name))
|
|
87
88
|
continue;
|
|
88
89
|
const fullPath = path.join(current, entry.name);
|
|
90
|
+
const relativePath = path.relative(workspaceRoot, fullPath).replace(/\\/g, '/');
|
|
91
|
+
if (isSensitivePath(relativePath))
|
|
92
|
+
continue;
|
|
89
93
|
// Security: skip symlinks that escape workspace root (fixes 8.2)
|
|
90
94
|
if (entry.isSymbolicLink()) {
|
|
91
95
|
try {
|
|
@@ -101,7 +105,12 @@ function walkWorkspace(dir, maxFiles = 800) {
|
|
|
101
105
|
stack.push(fullPath);
|
|
102
106
|
}
|
|
103
107
|
else if (entry.isFile() && TEXT_EXTENSIONS.has(path.extname(entry.name).toLowerCase())) {
|
|
104
|
-
|
|
108
|
+
try {
|
|
109
|
+
const content = fs.readFileSync(fullPath, 'utf8').slice(0, 300000);
|
|
110
|
+
if (!containsHighConfidenceSecret(content))
|
|
111
|
+
results.push(fullPath);
|
|
112
|
+
}
|
|
113
|
+
catch { /* unreadable files are not context */ }
|
|
105
114
|
}
|
|
106
115
|
}
|
|
107
116
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type ContextStateSource = 'workflow-memory' | 'session-memory' | 'project-memory' | 'codebase-index' | 'mcp-context' | 'account-brain' | 'workspace-hashes';
|
|
2
|
+
export interface ContextOwnershipRule {
|
|
3
|
+
owner: string;
|
|
4
|
+
schema: string;
|
|
5
|
+
precedence: number;
|
|
6
|
+
privacy: 'local-only' | 'account-remote' | 'execution-remote' | 'metadata-only';
|
|
7
|
+
retention: string;
|
|
8
|
+
conflictPolicy: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const CONTEXT_STATE_OWNERSHIP: Readonly<Record<ContextStateSource, ContextOwnershipRule>>;
|
|
11
|
+
export interface ContextStateLayer {
|
|
12
|
+
source: ContextStateSource;
|
|
13
|
+
schema: string;
|
|
14
|
+
precedence: number;
|
|
15
|
+
privacy: ContextOwnershipRule['privacy'];
|
|
16
|
+
content?: string;
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
contentHash?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ContextStateEnvelope {
|
|
21
|
+
schema: 'vigthoria.context-state.v1';
|
|
22
|
+
precedence: ContextStateSource[];
|
|
23
|
+
layers: ContextStateLayer[];
|
|
24
|
+
compatibilityAdapters: {
|
|
25
|
+
projectMemory: 'project-memory';
|
|
26
|
+
codebaseContext: 'codebase-index';
|
|
27
|
+
accountBrainContext: 'account-brain';
|
|
28
|
+
vigthoriaBrain: 'project-memory-metadata';
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare function assembleContextState(input: Partial<Record<ContextStateSource, {
|
|
32
|
+
content?: string;
|
|
33
|
+
metadata?: Record<string, unknown>;
|
|
34
|
+
}>>): ContextStateEnvelope;
|
|
35
|
+
export declare function contextLayer(envelope: ContextStateEnvelope, source: ContextStateSource): ContextStateLayer | undefined;
|
|
36
|
+
export declare function appendContextLayer(envelope: ContextStateEnvelope | undefined, source: ContextStateSource, value: {
|
|
37
|
+
content?: string;
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
}): ContextStateEnvelope;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { scanOutboundContext } from './secret-policy.js';
|
|
3
|
+
export const CONTEXT_STATE_OWNERSHIP = Object.freeze({
|
|
4
|
+
'workflow-memory': { owner: 'VigFlow target adapter', schema: 'vigthoria.context.workflow.v1', precedence: 90, privacy: 'execution-remote', retention: 'remote workflow policy', conflictPolicy: 'explicit workflow inputs override remembered context' },
|
|
5
|
+
'session-memory': { owner: 'SessionManager', schema: 'vigthoria.context.session.v1', precedence: 80, privacy: 'local-only', retention: '90 days / 200 sessions', conflictPolicy: 'latest project-bound checkpoint wins' },
|
|
6
|
+
'project-memory': { owner: 'ProjectMemoryService', schema: 'vigthoria.context.project-memory.v2', precedence: 70, privacy: 'local-only', retention: '240 bounded items', conflictPolicy: 'newer explicit decisions/preferences win by item identity' },
|
|
7
|
+
'codebase-index': { owner: 'CodebaseIndexer', schema: 'vigthoria.context.codebase-index.v1', precedence: 60, privacy: 'local-only', retention: 'content-addressed until reindex', conflictPolicy: 'current workspace bytes are authoritative' },
|
|
8
|
+
'mcp-context': { owner: 'MCP execution binding', schema: 'vigthoria.context.mcp.v1', precedence: 50, privacy: 'execution-remote', retention: 'remote execution lifetime', conflictPolicy: 'scoped tool evidence is additive and cannot override local files' },
|
|
9
|
+
'account-brain': { owner: 'BrainHubClient', schema: 'vigthoria.context.account-brain.v1', precedence: 40, privacy: 'account-remote', retention: 'account Brain policy', conflictPolicy: 'account facts yield to project/session evidence' },
|
|
10
|
+
'workspace-hashes': { owner: 'WorkspaceHashCache', schema: 'vigthoria.context.workspace-hashes.v1', precedence: 10, privacy: 'metadata-only', retention: 'latest successful workspace observation', conflictPolicy: 'routing metadata only; never semantic authority' },
|
|
11
|
+
});
|
|
12
|
+
export function assembleContextState(input) {
|
|
13
|
+
const layers = Object.entries(input)
|
|
14
|
+
.filter((entry) => Boolean(entry[1]))
|
|
15
|
+
.map(([source, value]) => {
|
|
16
|
+
const rule = CONTEXT_STATE_OWNERSHIP[source];
|
|
17
|
+
const scanned = scanOutboundContext(value).value;
|
|
18
|
+
const content = String(scanned.content || '').trim();
|
|
19
|
+
const contentHash = content ? createHash('sha256').update(content).digest('hex') : undefined;
|
|
20
|
+
return {
|
|
21
|
+
source,
|
|
22
|
+
schema: rule.schema,
|
|
23
|
+
precedence: rule.precedence,
|
|
24
|
+
privacy: rule.privacy,
|
|
25
|
+
...(content ? { content, contentHash } : {}),
|
|
26
|
+
...(scanned.metadata ? { metadata: scanned.metadata } : {}),
|
|
27
|
+
};
|
|
28
|
+
})
|
|
29
|
+
.sort((left, right) => right.precedence - left.precedence)
|
|
30
|
+
.filter((layer, _index, all) => !layer.contentHash
|
|
31
|
+
|| all.findIndex((candidate) => candidate.contentHash === layer.contentHash) === all.indexOf(layer));
|
|
32
|
+
return {
|
|
33
|
+
schema: 'vigthoria.context-state.v1',
|
|
34
|
+
precedence: layers.map((layer) => layer.source),
|
|
35
|
+
layers,
|
|
36
|
+
compatibilityAdapters: {
|
|
37
|
+
projectMemory: 'project-memory',
|
|
38
|
+
codebaseContext: 'codebase-index',
|
|
39
|
+
accountBrainContext: 'account-brain',
|
|
40
|
+
vigthoriaBrain: 'project-memory-metadata',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function contextLayer(envelope, source) {
|
|
45
|
+
return envelope.layers.find((layer) => layer.source === source);
|
|
46
|
+
}
|
|
47
|
+
export function appendContextLayer(envelope, source, value) {
|
|
48
|
+
const existing = envelope?.layers || [];
|
|
49
|
+
const input = Object.fromEntries(existing.map((layer) => [layer.source, { content: layer.content, metadata: layer.metadata }]));
|
|
50
|
+
input[source] = value;
|
|
51
|
+
return assembleContextState(input);
|
|
52
|
+
}
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structured events for Vigthoria Workbench (VIGTHORIA_DECK_MODE=1).
|
|
3
|
-
* Emitted as single lines: @vigthoria-deck:{json}
|
|
4
|
-
* Workbench strips these from terminal display and updates UI panels.
|
|
5
|
-
*/
|
|
6
1
|
export type DeckEventType = 'agent_start' | 'tool_start' | 'tool_end' | 'report_ready' | 'run_complete' | 'run_summary';
|
|
7
2
|
export interface DeckEvent {
|
|
8
3
|
type: DeckEventType;
|
package/dist/utils/deckEvents.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Emitted as single lines: @vigthoria-deck:{json}
|
|
4
4
|
* Workbench strips these from terminal display and updates UI panels.
|
|
5
5
|
*/
|
|
6
|
+
import { scanOutboundContext } from './secret-policy.js';
|
|
6
7
|
const DECK_PREFIX = '@vigthoria-deck:';
|
|
7
8
|
export function isDeckModeEnabled() {
|
|
8
9
|
return /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_DECK_MODE || ''));
|
|
@@ -10,10 +11,10 @@ export function isDeckModeEnabled() {
|
|
|
10
11
|
export function emitDeckEvent(event) {
|
|
11
12
|
if (!isDeckModeEnabled())
|
|
12
13
|
return;
|
|
13
|
-
const payload = {
|
|
14
|
+
const payload = scanOutboundContext({
|
|
14
15
|
...event,
|
|
15
16
|
ts: event.ts ?? Date.now(),
|
|
16
|
-
};
|
|
17
|
+
}).value;
|
|
17
18
|
// Force the marker onto its own fresh line. Callers may have just updated an
|
|
18
19
|
// in-place spinner (e.g. ora) which redraws via "\r" and never emits a
|
|
19
20
|
// trailing "\n", so without this leading newline the marker can land stuck
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { assertTrustedEndpoint } from './network-policy.js';
|
|
1
2
|
export async function getDesktopBridgeStatus(options = {}) {
|
|
2
3
|
const endpoint = options.url
|
|
3
4
|
|| process.env.VIGTHORIA_DESKTOP_BRIDGE_URL
|
|
4
5
|
|| `http://${process.env.VIGTHORIA_DESKTOP_HOST || '127.0.0.1'}:${process.env.VIGTHORIA_DESKTOP_PORT || '49160'}`;
|
|
5
6
|
const timeoutMs = options.timeoutMs ?? 5000;
|
|
6
7
|
try {
|
|
8
|
+
assertTrustedEndpoint(endpoint, { audience: 'bridge' });
|
|
7
9
|
const controller = new AbortController();
|
|
8
10
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
9
11
|
const response = await fetch(`${endpoint.replace(/\/$/, '')}/health`, {
|