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.
- 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 +84 -135
- 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 +5 -42
- package/dist/commands/chat.js +462 -1122
- package/dist/commands/coding-registration.d.ts +4 -0
- package/dist/commands/coding-registration.js +24 -0
- package/dist/commands/config.js +32 -42
- 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 -1498
- 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 +45 -269
- package/dist/utils/api.js +664 -3210
- 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 +227 -99
- 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 +6 -7
- package/dist/utils/fastAgentRouter.js +21 -30
- 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 +34 -2
- 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 +86 -143
- package/install.sh +76 -81
- package/package.json +44 -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 -172
- 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
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { assertSafeRelativePath, resolveWorkspacePath } from './workspace-boundary.js';
|
|
5
|
+
const IGNORE_NAMES = new Set(['.git', 'node_modules', '.vigthoria']);
|
|
6
|
+
const DEFAULT_MAX_BACKUP_BYTES = 128 * 1024 * 1024;
|
|
7
|
+
const DEFAULT_MAX_FILE_BYTES = 16 * 1024 * 1024;
|
|
8
|
+
const DEFAULT_MAX_FILES = 20_000;
|
|
9
|
+
function hashFile(filePath) {
|
|
10
|
+
const hash = createHash('sha256');
|
|
11
|
+
const descriptor = fs.openSync(filePath, 'r');
|
|
12
|
+
const buffer = Buffer.allocUnsafe(64 * 1024);
|
|
13
|
+
try {
|
|
14
|
+
let bytesRead = 0;
|
|
15
|
+
do {
|
|
16
|
+
bytesRead = fs.readSync(descriptor, buffer, 0, buffer.length, null);
|
|
17
|
+
if (bytesRead > 0)
|
|
18
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
19
|
+
} while (bytesRead > 0);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
fs.closeSync(descriptor);
|
|
23
|
+
}
|
|
24
|
+
return hash.digest('hex');
|
|
25
|
+
}
|
|
26
|
+
export function createOperationId(value) {
|
|
27
|
+
const candidate = String(value || '').trim();
|
|
28
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]{7,127}$/.test(candidate) ? candidate : randomUUID();
|
|
29
|
+
}
|
|
30
|
+
export class MutationJournal {
|
|
31
|
+
limits;
|
|
32
|
+
operationId;
|
|
33
|
+
rootPath;
|
|
34
|
+
manifestPath;
|
|
35
|
+
journalDirectory;
|
|
36
|
+
backupDirectory;
|
|
37
|
+
baseline = new Map();
|
|
38
|
+
baselineDirectories = new Set();
|
|
39
|
+
snapshotIssues = [];
|
|
40
|
+
status = 'open';
|
|
41
|
+
constructor(rootPath, requestedOperationId, limits = {
|
|
42
|
+
maxBackupBytes: DEFAULT_MAX_BACKUP_BYTES,
|
|
43
|
+
maxFileBytes: DEFAULT_MAX_FILE_BYTES,
|
|
44
|
+
maxFiles: DEFAULT_MAX_FILES,
|
|
45
|
+
}) {
|
|
46
|
+
this.limits = limits;
|
|
47
|
+
this.rootPath = fs.realpathSync(path.resolve(rootPath));
|
|
48
|
+
this.operationId = createOperationId(requestedOperationId);
|
|
49
|
+
this.captureBaselineDirectories();
|
|
50
|
+
this.journalDirectory = resolveWorkspacePath(this.rootPath, `.vigthoria/mutation-journal/${this.operationId}`, { allowMissing: true });
|
|
51
|
+
this.backupDirectory = path.join(this.journalDirectory, 'backups');
|
|
52
|
+
this.manifestPath = path.join(this.journalDirectory, 'journal.json');
|
|
53
|
+
this.captureBaseline();
|
|
54
|
+
this.persist('open', [], [], false);
|
|
55
|
+
}
|
|
56
|
+
listWorkspaceDirectories() {
|
|
57
|
+
const directories = [];
|
|
58
|
+
const walk = (directory) => {
|
|
59
|
+
let entries;
|
|
60
|
+
try {
|
|
61
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
if (IGNORE_NAMES.has(entry.name) || !entry.isDirectory())
|
|
68
|
+
continue;
|
|
69
|
+
const absolute = path.join(directory, entry.name);
|
|
70
|
+
directories.push(path.relative(this.rootPath, absolute).replace(/\\/g, '/'));
|
|
71
|
+
walk(absolute);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
walk(this.rootPath);
|
|
75
|
+
return directories;
|
|
76
|
+
}
|
|
77
|
+
captureBaselineDirectories() {
|
|
78
|
+
for (const directory of this.listWorkspaceDirectories())
|
|
79
|
+
this.baselineDirectories.add(directory);
|
|
80
|
+
}
|
|
81
|
+
listWorkspaceFiles() {
|
|
82
|
+
const files = [];
|
|
83
|
+
const walk = (directory) => {
|
|
84
|
+
let entries;
|
|
85
|
+
try {
|
|
86
|
+
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
files.push({ relativePath: path.relative(this.rootPath, directory).replace(/\\/g, '/') || '.', absolutePath: directory, kind: 'unreadable', issue: `unreadable directory: ${error.message}` });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
if (IGNORE_NAMES.has(entry.name))
|
|
94
|
+
continue;
|
|
95
|
+
const absolutePath = path.join(directory, entry.name);
|
|
96
|
+
const relativePath = path.relative(this.rootPath, absolutePath).replace(/\\/g, '/');
|
|
97
|
+
if (entry.isSymbolicLink()) {
|
|
98
|
+
files.push({ relativePath, absolutePath, kind: 'symlink' });
|
|
99
|
+
}
|
|
100
|
+
else if (entry.isDirectory()) {
|
|
101
|
+
walk(absolutePath);
|
|
102
|
+
}
|
|
103
|
+
else if (entry.isFile()) {
|
|
104
|
+
files.push({ relativePath, absolutePath, kind: 'file' });
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
files.push({ relativePath, absolutePath, kind: 'special', issue: 'special file' });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
walk(this.rootPath);
|
|
112
|
+
return files;
|
|
113
|
+
}
|
|
114
|
+
captureBaseline() {
|
|
115
|
+
const files = this.listWorkspaceFiles();
|
|
116
|
+
let backupBytes = 0;
|
|
117
|
+
let backedFiles = 0;
|
|
118
|
+
for (const file of files) {
|
|
119
|
+
if (file.kind === 'symlink') {
|
|
120
|
+
try {
|
|
121
|
+
const linkTarget = fs.readlinkSync(file.absolutePath);
|
|
122
|
+
let linkType = 'file';
|
|
123
|
+
try {
|
|
124
|
+
if (fs.statSync(file.absolutePath).isDirectory())
|
|
125
|
+
linkType = process.platform === 'win32' ? 'junction' : 'dir';
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// A dangling link is still preserved exactly; file is the portable
|
|
129
|
+
// recreation default where the target type cannot be determined.
|
|
130
|
+
}
|
|
131
|
+
this.baseline.set(file.relativePath, {
|
|
132
|
+
path: file.relativePath,
|
|
133
|
+
kind: 'symlink',
|
|
134
|
+
size: Buffer.byteLength(linkTarget),
|
|
135
|
+
sha256: createHash('sha256').update(linkTarget).digest('hex'),
|
|
136
|
+
linkTarget,
|
|
137
|
+
linkType,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
this.snapshotIssues.push(`${file.relativePath}: link snapshot failed: ${error.message}`);
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (file.issue) {
|
|
146
|
+
this.snapshotIssues.push(`${file.relativePath}: ${file.issue}`);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const stat = fs.statSync(file.absolutePath);
|
|
151
|
+
const baseline = {
|
|
152
|
+
path: file.relativePath,
|
|
153
|
+
kind: 'file',
|
|
154
|
+
size: stat.size,
|
|
155
|
+
sha256: hashFile(file.absolutePath),
|
|
156
|
+
mode: stat.mode & 0o777,
|
|
157
|
+
};
|
|
158
|
+
if (backedFiles >= this.limits.maxFiles
|
|
159
|
+
|| stat.size > this.limits.maxFileBytes
|
|
160
|
+
|| backupBytes + stat.size > this.limits.maxBackupBytes) {
|
|
161
|
+
this.snapshotIssues.push(`${file.relativePath}: rollback backup limit exceeded (${stat.size} bytes)`);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
fs.mkdirSync(this.backupDirectory, { recursive: true, mode: 0o700 });
|
|
165
|
+
const backupName = `${createHash('sha256').update(file.relativePath).digest('hex')}.bin`;
|
|
166
|
+
baseline.backupPath = path.join(this.backupDirectory, backupName);
|
|
167
|
+
fs.copyFileSync(file.absolutePath, baseline.backupPath, fs.constants.COPYFILE_EXCL);
|
|
168
|
+
fs.chmodSync(baseline.backupPath, 0o600);
|
|
169
|
+
backupBytes += stat.size;
|
|
170
|
+
backedFiles += 1;
|
|
171
|
+
}
|
|
172
|
+
this.baseline.set(file.relativePath, baseline);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
this.snapshotIssues.push(`${file.relativePath}: snapshot failed: ${error.message}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
changedPaths() {
|
|
180
|
+
const changed = new Set();
|
|
181
|
+
const current = new Map(this.listWorkspaceFiles().map((file) => [file.relativePath, file]));
|
|
182
|
+
for (const [relativePath, baseline] of this.baseline) {
|
|
183
|
+
const file = current.get(relativePath);
|
|
184
|
+
if (!file || file.issue || file.kind !== baseline.kind) {
|
|
185
|
+
changed.add(relativePath);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
if (baseline.kind === 'symlink') {
|
|
190
|
+
const target = fs.readlinkSync(file.absolutePath);
|
|
191
|
+
if (target !== baseline.linkTarget)
|
|
192
|
+
changed.add(relativePath);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const stat = fs.statSync(file.absolutePath);
|
|
196
|
+
if (stat.size !== baseline.size || hashFile(file.absolutePath) !== baseline.sha256)
|
|
197
|
+
changed.add(relativePath);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
changed.add(relativePath);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const [relativePath] of current) {
|
|
205
|
+
if (!this.baseline.has(relativePath))
|
|
206
|
+
changed.add(relativePath);
|
|
207
|
+
}
|
|
208
|
+
return [...changed].sort();
|
|
209
|
+
}
|
|
210
|
+
hasMutations() {
|
|
211
|
+
return this.changedPaths().length > 0;
|
|
212
|
+
}
|
|
213
|
+
resolveRollbackDestination(relativePath) {
|
|
214
|
+
const safe = assertSafeRelativePath(relativePath);
|
|
215
|
+
const segments = safe.split('/');
|
|
216
|
+
const name = segments.pop();
|
|
217
|
+
const parentRelative = segments.join('/');
|
|
218
|
+
const parent = parentRelative
|
|
219
|
+
? resolveWorkspacePath(this.rootPath, parentRelative, { allowMissing: true })
|
|
220
|
+
: this.rootPath;
|
|
221
|
+
fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
222
|
+
const parentReal = fs.realpathSync(parent);
|
|
223
|
+
const relativeParent = path.relative(this.rootPath, parentReal);
|
|
224
|
+
if (relativeParent === '..' || relativeParent.startsWith(`..${path.sep}`) || path.isAbsolute(relativeParent)) {
|
|
225
|
+
throw new Error('rollback parent escaped workspace');
|
|
226
|
+
}
|
|
227
|
+
return path.join(parentReal, name);
|
|
228
|
+
}
|
|
229
|
+
removeRollbackDestination(destination) {
|
|
230
|
+
try {
|
|
231
|
+
const stat = fs.lstatSync(destination);
|
|
232
|
+
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
|
233
|
+
fs.rmSync(destination, { recursive: true, force: true });
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
fs.unlinkSync(destination);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
if (error?.code !== 'ENOENT')
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
persist(status, affectedPaths, rollbackFailures, rolledBack) {
|
|
245
|
+
this.status = status;
|
|
246
|
+
fs.mkdirSync(this.journalDirectory, { recursive: true, mode: 0o700 });
|
|
247
|
+
const payload = {
|
|
248
|
+
schemaVersion: 1,
|
|
249
|
+
operationId: this.operationId,
|
|
250
|
+
workspace: this.rootPath,
|
|
251
|
+
status,
|
|
252
|
+
affectedPaths,
|
|
253
|
+
rolledBack,
|
|
254
|
+
partialMutation: status === 'partial_mutation',
|
|
255
|
+
rollbackFailures,
|
|
256
|
+
snapshotIssues: this.snapshotIssues,
|
|
257
|
+
updatedAt: new Date().toISOString(),
|
|
258
|
+
};
|
|
259
|
+
const temporary = `${this.manifestPath}.tmp-${randomUUID()}`;
|
|
260
|
+
try {
|
|
261
|
+
fs.writeFileSync(temporary, `${JSON.stringify(payload, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
262
|
+
fs.renameSync(temporary, this.manifestPath);
|
|
263
|
+
}
|
|
264
|
+
finally {
|
|
265
|
+
try {
|
|
266
|
+
fs.unlinkSync(temporary);
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
if (error?.code !== 'ENOENT')
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
report(affectedPaths, rollbackFailures, rolledBack) {
|
|
275
|
+
return {
|
|
276
|
+
operationId: this.operationId,
|
|
277
|
+
status: this.status,
|
|
278
|
+
affectedPaths,
|
|
279
|
+
rolledBack,
|
|
280
|
+
partialMutation: this.status === 'partial_mutation',
|
|
281
|
+
rollbackFailures,
|
|
282
|
+
snapshotIssues: [...this.snapshotIssues],
|
|
283
|
+
manifestPath: this.manifestPath,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
commit() {
|
|
287
|
+
const affectedPaths = this.changedPaths();
|
|
288
|
+
this.persist('committed', affectedPaths, [], false);
|
|
289
|
+
fs.rmSync(this.backupDirectory, { recursive: true, force: true });
|
|
290
|
+
return this.report(affectedPaths, [], false);
|
|
291
|
+
}
|
|
292
|
+
rollback() {
|
|
293
|
+
const affectedPaths = this.changedPaths();
|
|
294
|
+
const failures = [];
|
|
295
|
+
for (const relativePath of affectedPaths.filter((entry) => !this.baseline.has(entry))) {
|
|
296
|
+
try {
|
|
297
|
+
const safe = assertSafeRelativePath(relativePath);
|
|
298
|
+
const absolute = path.resolve(this.rootPath, ...safe.split('/'));
|
|
299
|
+
const relative = path.relative(this.rootPath, absolute);
|
|
300
|
+
if (relative.startsWith('..') || path.isAbsolute(relative))
|
|
301
|
+
throw new Error('path escaped workspace');
|
|
302
|
+
try {
|
|
303
|
+
fs.lstatSync(absolute);
|
|
304
|
+
fs.unlinkSync(absolute);
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
if (error?.code !== 'ENOENT')
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
failures.push(`${relativePath}: could not remove created file: ${error.message}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
for (const relativePath of affectedPaths.filter((entry) => this.baseline.has(entry))) {
|
|
316
|
+
const baseline = this.baseline.get(relativePath);
|
|
317
|
+
if (baseline.kind === 'file' && (!baseline.backupPath || !fs.existsSync(baseline.backupPath))) {
|
|
318
|
+
failures.push(`${relativePath}: rollback backup unavailable`);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
const destination = this.resolveRollbackDestination(relativePath);
|
|
323
|
+
this.removeRollbackDestination(destination);
|
|
324
|
+
if (baseline.kind === 'symlink') {
|
|
325
|
+
if (baseline.linkTarget === undefined)
|
|
326
|
+
throw new Error('rollback link target unavailable');
|
|
327
|
+
fs.symlinkSync(baseline.linkTarget, destination, baseline.linkType);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
if (!baseline.backupPath || !fs.existsSync(baseline.backupPath))
|
|
331
|
+
throw new Error('rollback backup unavailable');
|
|
332
|
+
const temporary = `${destination}.rollback-${randomUUID()}`;
|
|
333
|
+
try {
|
|
334
|
+
fs.copyFileSync(baseline.backupPath, temporary, fs.constants.COPYFILE_EXCL);
|
|
335
|
+
fs.chmodSync(temporary, baseline.mode ?? 0o600);
|
|
336
|
+
fs.renameSync(temporary, destination);
|
|
337
|
+
}
|
|
338
|
+
finally {
|
|
339
|
+
try {
|
|
340
|
+
fs.unlinkSync(temporary);
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
if (error?.code !== 'ENOENT')
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
failures.push(`${relativePath}: restore failed: ${error.message}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
for (const relativePath of [...this.baselineDirectories].sort((a, b) => a.length - b.length)) {
|
|
354
|
+
try {
|
|
355
|
+
fs.mkdirSync(resolveWorkspacePath(this.rootPath, relativePath, { allowMissing: true }), { recursive: true });
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
failures.push(`${relativePath}: directory restore failed: ${error.message}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
for (const relativePath of this.listWorkspaceDirectories().sort((a, b) => b.length - a.length)) {
|
|
362
|
+
if (this.baselineDirectories.has(relativePath))
|
|
363
|
+
continue;
|
|
364
|
+
try {
|
|
365
|
+
const directory = resolveWorkspacePath(this.rootPath, relativePath);
|
|
366
|
+
if (fs.readdirSync(directory).length === 0)
|
|
367
|
+
fs.rmdirSync(directory);
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
failures.push(`${relativePath}: created directory remains: ${error.message}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const residual = this.changedPaths();
|
|
374
|
+
for (const relativePath of residual) {
|
|
375
|
+
if (!failures.some((failure) => failure.startsWith(`${relativePath}:`))) {
|
|
376
|
+
failures.push(`${relativePath}: mutation remains after rollback`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const partial = failures.length > 0;
|
|
380
|
+
this.persist(partial ? 'partial_mutation' : 'rolled_back', affectedPaths, failures, !partial);
|
|
381
|
+
fs.rmSync(this.backupDirectory, { recursive: true, force: true });
|
|
382
|
+
return this.report(affectedPaths, failures, !partial);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
export function finalizeMutationTransaction(journal, proofSucceeded) {
|
|
386
|
+
return proofSucceeded ? journal.commit() : journal.rollback();
|
|
387
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AxiosInstance } from 'axios';
|
|
2
|
+
export { redactSensitiveText } from './secret-policy.js';
|
|
3
|
+
export type EndpointAudience = 'auto' | 'coder' | 'models' | 'hub' | 'community' | 'music' | 'brain-hub' | 'mcp' | 'v3' | 'template' | 'hyperloop' | 'vigflow' | 'bridge' | 'release' | 'public';
|
|
4
|
+
export declare const DEFAULT_NETWORK_TIMEOUT_MS = 30000;
|
|
5
|
+
export declare class NetworkPolicyError extends Error {
|
|
6
|
+
readonly code: 'offline' | 'untrusted_endpoint' | 'credential_audience' | 'unsafe_redirect';
|
|
7
|
+
constructor(code: NetworkPolicyError['code'], message: string);
|
|
8
|
+
}
|
|
9
|
+
export declare function isOfflineNetworkMode(): boolean;
|
|
10
|
+
export declare function isExplicitLoopbackEnabled(): boolean;
|
|
11
|
+
export declare function assertTrustedEndpoint(raw: string, options?: {
|
|
12
|
+
audience?: EndpointAudience;
|
|
13
|
+
credentialBearing?: boolean;
|
|
14
|
+
websocket?: boolean;
|
|
15
|
+
ignoreOffline?: boolean;
|
|
16
|
+
}): URL;
|
|
17
|
+
export declare function assertTrustedWebSocketEndpoint(raw: string, audience?: EndpointAudience, credentialBearing?: boolean, ignoreOffline?: boolean): URL;
|
|
18
|
+
export declare function guardedFetch(input: string | URL | Request, init?: RequestInit, options?: {
|
|
19
|
+
audience?: EndpointAudience;
|
|
20
|
+
maxRedirects?: number;
|
|
21
|
+
fetchImpl?: typeof fetch;
|
|
22
|
+
}): Promise<Response>;
|
|
23
|
+
export declare function installGlobalFetchPolicy(): void;
|
|
24
|
+
export declare function installAxiosNetworkPolicy(instance: AxiosInstance, audience?: EndpointAudience): void;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { redactSensitiveText } from './secret-policy.js';
|
|
2
|
+
export { redactSensitiveText } from './secret-policy.js';
|
|
3
|
+
const AUDIENCE_HOSTS = {
|
|
4
|
+
coder: new Set(['coder.vigthoria.io']),
|
|
5
|
+
models: new Set(['api.vigthoria.io']),
|
|
6
|
+
hub: new Set(['api.vigthoria.io']),
|
|
7
|
+
community: new Set(['community.vigthoria.io']),
|
|
8
|
+
music: new Set(['music.vigthoria.io']),
|
|
9
|
+
'brain-hub': new Set(['coder.vigthoria.io']),
|
|
10
|
+
mcp: new Set(['coder.vigthoria.io', 'mcp.vigthoria.io']),
|
|
11
|
+
v3: new Set(['coder.vigthoria.io']),
|
|
12
|
+
template: new Set(['coder.vigthoria.io', 'template.vigthoria.io']),
|
|
13
|
+
hyperloop: new Set(['coder.vigthoria.io', 'hyperloop.vigthoria.io']),
|
|
14
|
+
vigflow: new Set(['coder.vigthoria.io', 'vigflow.vigthoria.io']),
|
|
15
|
+
bridge: new Set(['bridge.vigthoria.io', 'devtools.vigthoria.io']),
|
|
16
|
+
release: new Set([
|
|
17
|
+
'extension.vigthoria.io',
|
|
18
|
+
'landing.vigthoria.io',
|
|
19
|
+
'cli.vigthoria.io',
|
|
20
|
+
'coder.vigthoria.io',
|
|
21
|
+
'market.vigthoria.io',
|
|
22
|
+
'registry.npmjs.org',
|
|
23
|
+
]),
|
|
24
|
+
};
|
|
25
|
+
// `auto` exists only as a compatibility guard for legacy calls to the primary
|
|
26
|
+
// Coder origin. It must never mean "any Vigthoria host": doing so allows a
|
|
27
|
+
// credential intended for one service to be forwarded to another trusted but
|
|
28
|
+
// unrelated service. Every non-Coder credential-bearing transport must name
|
|
29
|
+
// its audience explicitly.
|
|
30
|
+
const AUTO_CREDENTIAL_HOSTS = AUDIENCE_HOSTS.coder;
|
|
31
|
+
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1']);
|
|
32
|
+
const LOOPBACK_PORTS = new Set(['4008', '4009', '4011', '4016', '49160', '8030', '8020', '5060', '5050', '8016', '9000']);
|
|
33
|
+
const SENSITIVE_HEADER_NAMES = new Set(['authorization', 'cookie', 'proxy-authorization', 'x-api-key', 'x-service-key']);
|
|
34
|
+
export const DEFAULT_NETWORK_TIMEOUT_MS = 30_000;
|
|
35
|
+
export class NetworkPolicyError extends Error {
|
|
36
|
+
code;
|
|
37
|
+
constructor(code, message) {
|
|
38
|
+
super(redactSensitiveText(message));
|
|
39
|
+
this.name = 'NetworkPolicyError';
|
|
40
|
+
this.code = code;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function isOfflineNetworkMode() {
|
|
44
|
+
return process.env.VIGTHORIA_OFFLINE === '1';
|
|
45
|
+
}
|
|
46
|
+
export function isExplicitLoopbackEnabled() {
|
|
47
|
+
return process.env.VIGTHORIA_ALLOW_LOOPBACK === '1'
|
|
48
|
+
|| process.env.VIGTHORIA_ALLOW_LOCAL_SERVICES === '1'
|
|
49
|
+
|| process.env.VIGTHORIA_LOCAL_TEST_MODE === '1';
|
|
50
|
+
}
|
|
51
|
+
function normalizePort(url) {
|
|
52
|
+
if (url.port)
|
|
53
|
+
return url.port;
|
|
54
|
+
if (url.protocol === 'https:' || url.protocol === 'wss:')
|
|
55
|
+
return '443';
|
|
56
|
+
if (url.protocol === 'http:' || url.protocol === 'ws:')
|
|
57
|
+
return '80';
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
function isLoopback(url) {
|
|
61
|
+
return LOOPBACK_HOSTS.has(url.hostname.toLowerCase());
|
|
62
|
+
}
|
|
63
|
+
function isAllowedLoopback(url) {
|
|
64
|
+
return isExplicitLoopbackEnabled() && LOOPBACK_PORTS.has(normalizePort(url));
|
|
65
|
+
}
|
|
66
|
+
function parseEndpoint(raw) {
|
|
67
|
+
const value = String(raw || '').trim();
|
|
68
|
+
if (!value || /[\\\u0000-\u001f\u007f]/.test(value)) {
|
|
69
|
+
throw new NetworkPolicyError('untrusted_endpoint', 'Endpoint contains invalid or ambiguous characters');
|
|
70
|
+
}
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = new URL(value);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
throw new NetworkPolicyError('untrusted_endpoint', 'Endpoint is not a valid absolute URL');
|
|
77
|
+
}
|
|
78
|
+
if (parsed.username || parsed.password) {
|
|
79
|
+
throw new NetworkPolicyError('untrusted_endpoint', 'Endpoint user information is prohibited');
|
|
80
|
+
}
|
|
81
|
+
return parsed;
|
|
82
|
+
}
|
|
83
|
+
export function assertTrustedEndpoint(raw, options = {}) {
|
|
84
|
+
if (isOfflineNetworkMode() && options.ignoreOffline !== true) {
|
|
85
|
+
throw new NetworkPolicyError('offline', 'Network access is disabled by offline mode');
|
|
86
|
+
}
|
|
87
|
+
const parsed = parseEndpoint(raw);
|
|
88
|
+
const websocket = options.websocket === true;
|
|
89
|
+
const secureProtocol = websocket ? 'wss:' : 'https:';
|
|
90
|
+
const insecureProtocol = websocket ? 'ws:' : 'http:';
|
|
91
|
+
if (parsed.protocol !== secureProtocol && parsed.protocol !== insecureProtocol) {
|
|
92
|
+
throw new NetworkPolicyError('untrusted_endpoint', `Unsupported endpoint protocol: ${parsed.protocol}`);
|
|
93
|
+
}
|
|
94
|
+
if (parsed.protocol === insecureProtocol && !isAllowedLoopback(parsed)) {
|
|
95
|
+
throw new NetworkPolicyError('untrusted_endpoint', 'Insecure endpoints require explicit loopback opt-in on an approved port');
|
|
96
|
+
}
|
|
97
|
+
if (isLoopback(parsed) && !isAllowedLoopback(parsed)) {
|
|
98
|
+
throw new NetworkPolicyError('untrusted_endpoint', 'Loopback endpoints require explicit opt-in on an approved port');
|
|
99
|
+
}
|
|
100
|
+
const audience = options.audience || 'auto';
|
|
101
|
+
const credentialBearing = options.credentialBearing === true;
|
|
102
|
+
if (audience !== 'public' && audience !== 'auto' && !isLoopback(parsed)) {
|
|
103
|
+
const allowed = AUDIENCE_HOSTS[audience];
|
|
104
|
+
if (!allowed.has(parsed.hostname.toLowerCase()) || normalizePort(parsed) !== '443') {
|
|
105
|
+
throw new NetworkPolicyError('credential_audience', `Endpoint is outside the ${audience} credential audience`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (credentialBearing && audience === 'auto' && !isLoopback(parsed)) {
|
|
109
|
+
if (!AUTO_CREDENTIAL_HOSTS.has(parsed.hostname.toLowerCase()) || normalizePort(parsed) !== '443') {
|
|
110
|
+
throw new NetworkPolicyError('credential_audience', 'Credential-bearing request requires an explicit service audience');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return parsed;
|
|
114
|
+
}
|
|
115
|
+
export function assertTrustedWebSocketEndpoint(raw, audience = 'bridge', credentialBearing = false, ignoreOffline = false) {
|
|
116
|
+
return assertTrustedEndpoint(raw, { audience, credentialBearing, websocket: true, ignoreOffline });
|
|
117
|
+
}
|
|
118
|
+
function headerEntries(headers) {
|
|
119
|
+
if (!headers)
|
|
120
|
+
return [];
|
|
121
|
+
return [...new Headers(headers).entries()];
|
|
122
|
+
}
|
|
123
|
+
function hasSensitiveHeaders(headers) {
|
|
124
|
+
return headerEntries(headers).some(([name]) => SENSITIVE_HEADER_NAMES.has(name.toLowerCase()));
|
|
125
|
+
}
|
|
126
|
+
function stripSensitiveHeaders(headers) {
|
|
127
|
+
const result = new Headers(headers);
|
|
128
|
+
for (const name of SENSITIVE_HEADER_NAMES)
|
|
129
|
+
result.delete(name);
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
export async function guardedFetch(input, init = {}, options = {}) {
|
|
133
|
+
const implementation = options.fetchImpl
|
|
134
|
+
|| (globalFetchWrapper && globalThis.fetch === globalFetchWrapper ? nativeFetchImplementation : globalThis.fetch)
|
|
135
|
+
|| globalThis.fetch;
|
|
136
|
+
const initialUrl = input instanceof Request ? input.url : String(input);
|
|
137
|
+
let current = assertTrustedEndpoint(initialUrl, {
|
|
138
|
+
audience: options.audience,
|
|
139
|
+
credentialBearing: hasSensitiveHeaders(init.headers || (input instanceof Request ? input.headers : undefined)),
|
|
140
|
+
});
|
|
141
|
+
let headers = new Headers(init.headers || (input instanceof Request ? input.headers : undefined));
|
|
142
|
+
const credentialBearing = hasSensitiveHeaders(headers);
|
|
143
|
+
const maxRedirects = options.maxRedirects ?? 5;
|
|
144
|
+
const signal = init.signal || AbortSignal.timeout(DEFAULT_NETWORK_TIMEOUT_MS);
|
|
145
|
+
for (let redirects = 0; redirects <= maxRedirects; redirects += 1) {
|
|
146
|
+
const response = await implementation(current, { ...init, headers, signal, redirect: 'manual' });
|
|
147
|
+
if (![301, 302, 303, 307, 308].includes(response.status))
|
|
148
|
+
return response;
|
|
149
|
+
const location = response.headers.get('location');
|
|
150
|
+
if (!location)
|
|
151
|
+
throw new NetworkPolicyError('unsafe_redirect', 'Redirect response omitted its destination');
|
|
152
|
+
if (redirects === maxRedirects)
|
|
153
|
+
throw new NetworkPolicyError('unsafe_redirect', 'Maximum redirect count exceeded');
|
|
154
|
+
const next = new URL(location, current);
|
|
155
|
+
assertTrustedEndpoint(next.toString(), { audience: options.audience, credentialBearing });
|
|
156
|
+
if (next.origin !== current.origin) {
|
|
157
|
+
if (credentialBearing) {
|
|
158
|
+
throw new NetworkPolicyError('unsafe_redirect', 'Cross-origin redirect of a credential-bearing request is prohibited');
|
|
159
|
+
}
|
|
160
|
+
headers = stripSensitiveHeaders(headers);
|
|
161
|
+
}
|
|
162
|
+
current = next;
|
|
163
|
+
}
|
|
164
|
+
throw new NetworkPolicyError('unsafe_redirect', 'Redirect validation failed');
|
|
165
|
+
}
|
|
166
|
+
let globalFetchInstalled = false;
|
|
167
|
+
let nativeFetchImplementation = null;
|
|
168
|
+
let globalFetchWrapper = null;
|
|
169
|
+
export function installGlobalFetchPolicy() {
|
|
170
|
+
if (globalFetchInstalled || typeof globalThis.fetch !== 'function')
|
|
171
|
+
return;
|
|
172
|
+
const nativeFetch = globalThis.fetch.bind(globalThis);
|
|
173
|
+
nativeFetchImplementation = nativeFetch;
|
|
174
|
+
globalFetchWrapper = ((input, init) => guardedFetch(input, init, { fetchImpl: nativeFetch }));
|
|
175
|
+
globalThis.fetch = globalFetchWrapper;
|
|
176
|
+
globalFetchInstalled = true;
|
|
177
|
+
}
|
|
178
|
+
function resolveAxiosUrl(config) {
|
|
179
|
+
const direct = String(config.url || '');
|
|
180
|
+
const base = String(config.baseURL || '');
|
|
181
|
+
if (/^https?:\/\//i.test(direct))
|
|
182
|
+
return direct;
|
|
183
|
+
return new URL(direct || '', base).toString();
|
|
184
|
+
}
|
|
185
|
+
function axiosHasCredentials(config) {
|
|
186
|
+
return Object.keys(config.headers || {}).some((name) => SENSITIVE_HEADER_NAMES.has(name.toLowerCase()));
|
|
187
|
+
}
|
|
188
|
+
const installedAxios = new WeakSet();
|
|
189
|
+
export function installAxiosNetworkPolicy(instance, audience = 'auto') {
|
|
190
|
+
if (installedAxios.has(instance))
|
|
191
|
+
return;
|
|
192
|
+
installedAxios.add(instance);
|
|
193
|
+
instance.interceptors.request.use((config) => {
|
|
194
|
+
const endpoint = resolveAxiosUrl(config);
|
|
195
|
+
assertTrustedEndpoint(endpoint, { audience, credentialBearing: axiosHasCredentials(config) });
|
|
196
|
+
config.maxRedirects = axiosHasCredentials(config) ? 0 : Math.min(config.maxRedirects ?? 5, 5);
|
|
197
|
+
if (!Number.isFinite(Number(config.timeout)) || Number(config.timeout) <= 0) {
|
|
198
|
+
config.timeout = DEFAULT_NETWORK_TIMEOUT_MS;
|
|
199
|
+
}
|
|
200
|
+
return config;
|
|
201
|
+
});
|
|
202
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface OperatorWorkflowResponse {
|
|
2
|
+
content: string;
|
|
3
|
+
workflowId: string | null;
|
|
4
|
+
contextId?: string | null;
|
|
5
|
+
backendUrl: string;
|
|
6
|
+
savedWorkflow?: {
|
|
7
|
+
id: string;
|
|
8
|
+
name?: string | null;
|
|
9
|
+
sourceWorkflowId?: string | null;
|
|
10
|
+
} | null;
|
|
11
|
+
metadata?: Record<string, unknown>;
|
|
12
|
+
changedFiles?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
export declare class OperatorClientError extends Error {
|
|
15
|
+
readonly category: 'timeout' | 'model_backend';
|
|
16
|
+
constructor(message: string, category: 'timeout' | 'model_backend');
|
|
17
|
+
}
|
|
18
|
+
export interface OperatorRunInput {
|
|
19
|
+
message: string;
|
|
20
|
+
executionContext: Record<string, any>;
|
|
21
|
+
workspaceAlias: string;
|
|
22
|
+
workspaceSummary: Record<string, any> | null;
|
|
23
|
+
requestedModel: string;
|
|
24
|
+
resolvedModel: string;
|
|
25
|
+
timeoutMs: number;
|
|
26
|
+
onStreamEvent?: (event: Record<string, any>) => void;
|
|
27
|
+
}
|
|
28
|
+
export interface OperatorClientDependencies {
|
|
29
|
+
getBaseUrls(): string[];
|
|
30
|
+
getAuthToken(): string | null;
|
|
31
|
+
fetch(input: string, init: RequestInit): Promise<Response>;
|
|
32
|
+
sanitizeError(text: string): string;
|
|
33
|
+
}
|
|
34
|
+
export declare class OperatorClient {
|
|
35
|
+
private readonly dependencies;
|
|
36
|
+
constructor(dependencies: OperatorClientDependencies);
|
|
37
|
+
formatResponse(data?: {
|
|
38
|
+
summary?: string;
|
|
39
|
+
details?: any;
|
|
40
|
+
completed_agents?: string[];
|
|
41
|
+
answer?: string;
|
|
42
|
+
result?: string;
|
|
43
|
+
}): string;
|
|
44
|
+
run(input: OperatorRunInput): Promise<OperatorWorkflowResponse>;
|
|
45
|
+
}
|