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,293 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createPublicKey, verify as verifySignature } from 'node:crypto';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { CliCommandError } from './command-contract.js';
|
|
6
|
+
import { compareSemanticVersions } from './update-policy.js';
|
|
7
|
+
const SHA256 = /^[a-f0-9]{64}$/;
|
|
8
|
+
const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
9
|
+
const TOP_LEVEL_FIELDS = new Set(['schemaVersion', 'channels']);
|
|
10
|
+
const ENTRY_FIELDS = new Set(['version', 'url', 'sha256', 'size', 'archiveFilename', 'packageName', 'packageVersion', 'publishedAt', 'signature', 'sbom', 'provenance', 'notes']);
|
|
11
|
+
const SIGNATURE_FIELDS = new Set(['algorithm', 'keyId', 'url']);
|
|
12
|
+
const EVIDENCE_FIELDS = new Set(['url', 'sha256']);
|
|
13
|
+
function fail(message, code = 'RELEASE_MANIFEST_INVALID') {
|
|
14
|
+
throw new CliCommandError(message, { code, category: 'configuration' });
|
|
15
|
+
}
|
|
16
|
+
function policyPath() {
|
|
17
|
+
const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const candidates = [
|
|
19
|
+
path.resolve(moduleDirectory, '../../release-policy.json'),
|
|
20
|
+
path.resolve(moduleDirectory, '../release-policy.json'),
|
|
21
|
+
path.resolve(process.cwd(), 'release-policy.json'),
|
|
22
|
+
];
|
|
23
|
+
const resolved = candidates.find((candidate) => fs.existsSync(candidate));
|
|
24
|
+
if (!resolved)
|
|
25
|
+
return fail('The packaged release-policy.json file is missing.', 'RELEASE_POLICY_MISSING');
|
|
26
|
+
return resolved;
|
|
27
|
+
}
|
|
28
|
+
let cachedPolicy = null;
|
|
29
|
+
export function getReleasePolicy() {
|
|
30
|
+
if (cachedPolicy)
|
|
31
|
+
return cachedPolicy;
|
|
32
|
+
const raw = JSON.parse(fs.readFileSync(policyPath(), 'utf8'));
|
|
33
|
+
if (raw.schemaVersion !== 1 || raw.manifest?.schemaVersion !== 1 || raw.signature?.algorithm !== 'Ed25519') {
|
|
34
|
+
return fail('Unsupported release policy schema.', 'RELEASE_POLICY_UNSUPPORTED');
|
|
35
|
+
}
|
|
36
|
+
cachedPolicy = Object.freeze(raw);
|
|
37
|
+
return cachedPolicy;
|
|
38
|
+
}
|
|
39
|
+
function assertExactFields(value, allowed, label) {
|
|
40
|
+
for (const field of Object.keys(value)) {
|
|
41
|
+
if (!allowed.has(field))
|
|
42
|
+
fail(`${label} contains unknown field: ${field}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function assertReleaseUrl(raw, kind = 'artifact') {
|
|
46
|
+
const policy = getReleasePolicy();
|
|
47
|
+
if (typeof raw !== 'string' || !raw || /[\\\u0000-\u001f\u007f]/.test(raw))
|
|
48
|
+
fail(`Invalid ${kind} URL.`);
|
|
49
|
+
let url;
|
|
50
|
+
try {
|
|
51
|
+
url = new URL(raw);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return fail(`Invalid ${kind} URL.`);
|
|
55
|
+
}
|
|
56
|
+
if (url.protocol !== 'https:' || url.username || url.password || (url.port && Number(url.port) !== policy.origins.port)) {
|
|
57
|
+
fail(`${kind} URL must use HTTPS without credentials on the policy port.`);
|
|
58
|
+
}
|
|
59
|
+
if (!policy.origins.allowedHosts.includes(url.hostname.toLowerCase()))
|
|
60
|
+
fail(`${kind} URL host is not allowlisted.`);
|
|
61
|
+
if (kind === 'manifest' && url.toString() !== policy.origins.manifest)
|
|
62
|
+
fail('Manifest URL does not match the canonical release policy origin.');
|
|
63
|
+
return url;
|
|
64
|
+
}
|
|
65
|
+
function parseEvidence(raw, label) {
|
|
66
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
67
|
+
return fail(`${label} metadata is required.`);
|
|
68
|
+
const record = raw;
|
|
69
|
+
assertExactFields(record, EVIDENCE_FIELDS, label);
|
|
70
|
+
const url = assertReleaseUrl(record.url, 'evidence').toString();
|
|
71
|
+
const sha256 = String(record.sha256 || '').toLowerCase();
|
|
72
|
+
if (!SHA256.test(sha256))
|
|
73
|
+
fail(`${label} SHA-256 is malformed.`);
|
|
74
|
+
return { url, sha256 };
|
|
75
|
+
}
|
|
76
|
+
export function parseReleaseManifest(input, channel = 'stable', serializedBytes) {
|
|
77
|
+
const policy = getReleasePolicy();
|
|
78
|
+
if (serializedBytes !== undefined && serializedBytes > policy.manifest.maximumBytes)
|
|
79
|
+
fail('Release manifest exceeds the policy size limit.');
|
|
80
|
+
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
81
|
+
return fail('Release manifest must be a JSON object.');
|
|
82
|
+
const manifest = input;
|
|
83
|
+
assertExactFields(manifest, TOP_LEVEL_FIELDS, 'Release manifest');
|
|
84
|
+
if (manifest.schemaVersion !== policy.manifest.schemaVersion)
|
|
85
|
+
fail('Release manifest schemaVersion is missing or unsupported.');
|
|
86
|
+
if (!policy.manifest.allowedChannels.includes(channel))
|
|
87
|
+
fail(`Release channel is not allowed: ${channel}`);
|
|
88
|
+
if (!manifest.channels || typeof manifest.channels !== 'object' || Array.isArray(manifest.channels))
|
|
89
|
+
fail('Release manifest channels object is required.');
|
|
90
|
+
const channels = manifest.channels;
|
|
91
|
+
for (const name of Object.keys(channels))
|
|
92
|
+
if (!policy.manifest.allowedChannels.includes(name))
|
|
93
|
+
fail(`Release manifest contains unknown channel: ${name}`);
|
|
94
|
+
for (const name of policy.manifest.requiredChannels)
|
|
95
|
+
if (!(name in channels))
|
|
96
|
+
fail(`Release manifest is missing required channel: ${name}`);
|
|
97
|
+
const raw = channels[channel];
|
|
98
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
99
|
+
return fail(`Release manifest has no entry for channel ${channel}.`);
|
|
100
|
+
const entry = raw;
|
|
101
|
+
assertExactFields(entry, ENTRY_FIELDS, `Release channel ${channel}`);
|
|
102
|
+
const version = String(entry.version || '');
|
|
103
|
+
const packageVersion = String(entry.packageVersion || '');
|
|
104
|
+
const archiveFilename = String(entry.archiveFilename || '');
|
|
105
|
+
const packageName = String(entry.packageName || '');
|
|
106
|
+
const sha256 = String(entry.sha256 || '').toLowerCase();
|
|
107
|
+
const size = Number(entry.size);
|
|
108
|
+
if (!SEMVER.test(version) || compareSemanticVersions(version, version) !== 0)
|
|
109
|
+
fail('Release version is invalid.');
|
|
110
|
+
if (version !== packageVersion)
|
|
111
|
+
fail('Release version conflicts with packageVersion.');
|
|
112
|
+
if (packageName !== policy.packageName)
|
|
113
|
+
fail('Release packageName conflicts with policy.');
|
|
114
|
+
if (!SHA256.test(sha256))
|
|
115
|
+
fail('Release artifact SHA-256 is malformed.');
|
|
116
|
+
if (!Number.isSafeInteger(size) || size <= 0 || size > policy.artifact.maximumBytes)
|
|
117
|
+
fail('Release artifact size is invalid or exceeds policy.');
|
|
118
|
+
if (!new RegExp(policy.artifact.filenamePattern).test(archiveFilename) || archiveFilename !== `${policy.packageName}-${version}.tgz`)
|
|
119
|
+
fail('Release archive filename conflicts with version identity.');
|
|
120
|
+
const artifactUrl = assertReleaseUrl(entry.url, 'artifact');
|
|
121
|
+
if (path.posix.basename(artifactUrl.pathname) !== archiveFilename)
|
|
122
|
+
fail('Release URL filename conflicts with archiveFilename.');
|
|
123
|
+
if (!policy.channels[channel].allowPrerelease && version.includes('-'))
|
|
124
|
+
fail(`Prerelease versions are forbidden on channel ${channel}.`);
|
|
125
|
+
const publishedAt = String(entry.publishedAt || '');
|
|
126
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(publishedAt) || !Number.isFinite(Date.parse(publishedAt)))
|
|
127
|
+
fail('Release publishedAt is invalid.');
|
|
128
|
+
if (!entry.signature || typeof entry.signature !== 'object' || Array.isArray(entry.signature))
|
|
129
|
+
return fail('Detached signature metadata is required.');
|
|
130
|
+
const signature = entry.signature;
|
|
131
|
+
assertExactFields(signature, SIGNATURE_FIELDS, 'Release signature');
|
|
132
|
+
const algorithm = String(signature.algorithm || '');
|
|
133
|
+
const keyId = String(signature.keyId || '');
|
|
134
|
+
if (algorithm !== policy.signature.algorithm)
|
|
135
|
+
fail('Release signature algorithm conflicts with policy.');
|
|
136
|
+
if (!Object.hasOwn(policy.signature.trustedKeys, keyId))
|
|
137
|
+
fail('Release signature key is not trusted.');
|
|
138
|
+
const signatureUrl = assertReleaseUrl(signature.url, 'signature');
|
|
139
|
+
if (path.posix.basename(signatureUrl.pathname) !== `${archiveFilename}.sig`)
|
|
140
|
+
fail('Detached signature filename conflicts with archive identity.');
|
|
141
|
+
return {
|
|
142
|
+
version,
|
|
143
|
+
url: artifactUrl.toString(),
|
|
144
|
+
sha256,
|
|
145
|
+
size,
|
|
146
|
+
archiveFilename,
|
|
147
|
+
packageName,
|
|
148
|
+
packageVersion,
|
|
149
|
+
publishedAt,
|
|
150
|
+
signature: { algorithm: 'Ed25519', keyId, url: signatureUrl.toString() },
|
|
151
|
+
sbom: parseEvidence(entry.sbom, 'SBOM'),
|
|
152
|
+
provenance: parseEvidence(entry.provenance, 'Provenance'),
|
|
153
|
+
...(typeof entry.notes === 'string' ? { notes: entry.notes } : {}),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
export function parseReleaseManifestText(text, channel = 'stable') {
|
|
157
|
+
const bytes = Buffer.byteLength(text, 'utf8');
|
|
158
|
+
if (bytes > getReleasePolicy().manifest.maximumBytes)
|
|
159
|
+
fail('Release manifest exceeds the policy size limit.');
|
|
160
|
+
let index = 0;
|
|
161
|
+
const whitespace = () => { while (/\s/.test(text[index] || ''))
|
|
162
|
+
index += 1; };
|
|
163
|
+
const parseString = () => {
|
|
164
|
+
whitespace();
|
|
165
|
+
if (text[index] !== '"')
|
|
166
|
+
fail('Release manifest JSON is malformed.');
|
|
167
|
+
const start = index++;
|
|
168
|
+
let escaped = false;
|
|
169
|
+
while (index < text.length) {
|
|
170
|
+
const character = text[index++];
|
|
171
|
+
if (escaped) {
|
|
172
|
+
escaped = false;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (character === '\\') {
|
|
176
|
+
escaped = true;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (character === '"') {
|
|
180
|
+
try {
|
|
181
|
+
return JSON.parse(text.slice(start, index));
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return fail('Release manifest JSON string is malformed.');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (character.charCodeAt(0) < 0x20)
|
|
188
|
+
fail('Release manifest JSON string contains a control character.');
|
|
189
|
+
}
|
|
190
|
+
return fail('Release manifest JSON string is unterminated.');
|
|
191
|
+
};
|
|
192
|
+
const parseValue = () => {
|
|
193
|
+
whitespace();
|
|
194
|
+
if (text[index] === '{') {
|
|
195
|
+
index += 1;
|
|
196
|
+
const keys = new Set();
|
|
197
|
+
whitespace();
|
|
198
|
+
if (text[index] === '}') {
|
|
199
|
+
index += 1;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
while (index < text.length) {
|
|
203
|
+
const key = parseString();
|
|
204
|
+
if (keys.has(key))
|
|
205
|
+
fail(`Release manifest contains duplicate field: ${key}`);
|
|
206
|
+
keys.add(key);
|
|
207
|
+
whitespace();
|
|
208
|
+
if (text[index++] !== ':')
|
|
209
|
+
fail('Release manifest JSON object is malformed.');
|
|
210
|
+
parseValue();
|
|
211
|
+
whitespace();
|
|
212
|
+
const separator = text[index++];
|
|
213
|
+
if (separator === '}')
|
|
214
|
+
return;
|
|
215
|
+
if (separator !== ',')
|
|
216
|
+
fail('Release manifest JSON object is malformed.');
|
|
217
|
+
}
|
|
218
|
+
fail('Release manifest JSON object is unterminated.');
|
|
219
|
+
}
|
|
220
|
+
if (text[index] === '[') {
|
|
221
|
+
index += 1;
|
|
222
|
+
whitespace();
|
|
223
|
+
if (text[index] === ']') {
|
|
224
|
+
index += 1;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
while (index < text.length) {
|
|
228
|
+
parseValue();
|
|
229
|
+
whitespace();
|
|
230
|
+
const separator = text[index++];
|
|
231
|
+
if (separator === ']')
|
|
232
|
+
return;
|
|
233
|
+
if (separator !== ',')
|
|
234
|
+
fail('Release manifest JSON array is malformed.');
|
|
235
|
+
}
|
|
236
|
+
fail('Release manifest JSON array is unterminated.');
|
|
237
|
+
}
|
|
238
|
+
if (text[index] === '"') {
|
|
239
|
+
parseString();
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const remainder = text.slice(index);
|
|
243
|
+
const primitive = /^(?:true|false|null|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)/.exec(remainder);
|
|
244
|
+
if (!primitive)
|
|
245
|
+
fail('Release manifest JSON value is malformed.');
|
|
246
|
+
index += primitive[0].length;
|
|
247
|
+
};
|
|
248
|
+
parseValue();
|
|
249
|
+
whitespace();
|
|
250
|
+
if (index !== text.length)
|
|
251
|
+
fail('Release manifest JSON has trailing content.');
|
|
252
|
+
let parsed;
|
|
253
|
+
try {
|
|
254
|
+
parsed = JSON.parse(text);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
return fail('Release manifest JSON is malformed.');
|
|
258
|
+
}
|
|
259
|
+
return parseReleaseManifest(parsed, channel, bytes);
|
|
260
|
+
}
|
|
261
|
+
function canonicalize(value) {
|
|
262
|
+
if (Array.isArray(value))
|
|
263
|
+
return `[${value.map(canonicalize).join(',')}]`;
|
|
264
|
+
if (value && typeof value === 'object') {
|
|
265
|
+
const record = value;
|
|
266
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalize(record[key])}`).join(',')}}`;
|
|
267
|
+
}
|
|
268
|
+
return JSON.stringify(value);
|
|
269
|
+
}
|
|
270
|
+
export function releaseSignaturePayload(channel, entry) {
|
|
271
|
+
return Buffer.from(canonicalize({ channel, entry }), 'utf8');
|
|
272
|
+
}
|
|
273
|
+
export function verifyReleaseSignature(channel, entry, detachedBase64, trustedKeys) {
|
|
274
|
+
const keys = trustedKeys || getReleasePolicy().signature.trustedKeys;
|
|
275
|
+
const publicKey = keys[entry.signature.keyId];
|
|
276
|
+
if (!publicKey)
|
|
277
|
+
fail('Detached signature key is not trusted.', 'RELEASE_SIGNATURE_UNTRUSTED');
|
|
278
|
+
const encoded = String(detachedBase64 || '').trim();
|
|
279
|
+
if (!/^[A-Za-z0-9+/]+={0,2}$/.test(encoded))
|
|
280
|
+
fail('Detached signature encoding is malformed.', 'RELEASE_SIGNATURE_INVALID');
|
|
281
|
+
const signature = Buffer.from(encoded, 'base64');
|
|
282
|
+
if (signature.length !== 64 || !verifySignature(null, releaseSignaturePayload(channel, entry), createPublicKey(publicKey), signature)) {
|
|
283
|
+
fail('Detached release signature verification failed.', 'RELEASE_SIGNATURE_INVALID');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
export function assertReleaseTransition(currentVersion, entry, channel, allowDowngrade = false) {
|
|
287
|
+
const channelPolicy = getReleasePolicy().channels[channel];
|
|
288
|
+
if (!channelPolicy)
|
|
289
|
+
fail(`Release channel is not allowed: ${channel}`);
|
|
290
|
+
if (compareSemanticVersions(entry.version, currentVersion) < 0 && !(allowDowngrade && channelPolicy.allowDowngrade)) {
|
|
291
|
+
fail(`Downgrade from ${currentVersion} to ${entry.version} is prohibited.`, 'RELEASE_DOWNGRADE_PROHIBITED');
|
|
292
|
+
}
|
|
293
|
+
}
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
const CLI_SHAPING_BLOCK = /(?:^|\n\n)Platform:\s*(?:Windows|macOS|Linux)\.[\s\S]*?(?=\n\n[A-Z][^\n]{0,40}:|\s*$)/i;
|
|
6
6
|
const READONLY_SHAPING_BLOCK = /(?:^|\n\n)(?:Read-only analysis mode is active\.|Diagnostic mode is active\.)[\s\S]*?(?=\n\n[A-Z][^\n]{0,40}:|\s*$)/i;
|
|
7
|
-
const BUILD_VERBS = /\b(build|create|make|implement|complete|redesign|overhaul|rebuild|transform|polish|art-direct|fix|repair|edit|modify|write|generate|add|finish|scaffold|develop|update|change|refactor|want|need|require|looking for|let's|let us|we need|we should|erstelle|erstellen|schreib|schreibe|bearbeite)\b/i;
|
|
7
|
+
const BUILD_VERBS = /\b(build|create|make|implement|complete|redesign|overhaul|rebuild|transform|polish|refine|art-direct|fix|repair|correct|edit|modify|write|generate|add|finish|scaffold|develop|update|change|refactor|want|need|require|looking for|let's|let us|we need|we should|erstelle|erstellen|schreib|schreibe|bearbeite)\b/i;
|
|
8
8
|
const ARTIFACT_NOUNS = /\b(file|files|project|game|spiel|app|website|html5|frontend|component|feature|code|workspace|repo|clone|replica|remake|page|site|design|showcase|screen|button|popup|modal|alert|canvas|sprite|level|levels|enemy|enemies|character|npc|world|scene|script|module|api|database|config|landing|dashboard|platformer|arcade|playable|collectible|scoreboard|leaderboard|pitfall|pacman|rogue|html|css|javascript|typescript)\b/i;
|
|
9
9
|
/** Canvas/playable games only — bare "html5" or "website" must NOT match. */
|
|
10
10
|
const GAME_INTENT = /\b(game|spiel|playable|babylon(?:\.js|js)?|vge|webgl|3d\s+(?:game|runner|prototype|world|experience)|html5\s+(?:game|canvas)|html5\s+game|canvas\s+game|game\s+canvas|arcade|platformer|side[- ]?scroller|pitfall|pac[- ]?man|tetris|snake|breakout|pong|roguelike|metroidvania|tower\s+defense|clone\s+of\s+(?:a\s+)?(?:game|pitfall|pac|mario|zelda|arcade)|gameplay|collectible|boss\s+fight|level\s+design|sprite\s+sheet|wild\s+(?:boar|pig|forest)|forest\s+pig)\b/i;
|
|
11
11
|
const WEB_PAGE_INTENT = /\b(website|web\s*site|webpage|web\s*page|landing\s+page|home\s*page|index\.html|html\s+page|static\s+page|single\s+page|popup|alert|modal|hello\s+world)\b/i;
|
|
12
12
|
const WEB_SHOWCASE_INTENT = /\b(web[- ]?design\s+showcase|frontend|marketing\s+site|dashboard|saas)\b/i;
|
|
13
|
+
const COMPREHENSIVE_WEB_BUILD = /\b(?:redesign|overhaul|rebuild|transform|art[- ]direct|enterprise[- ]grade|premium\s+(?:website|web|design)|showcase\s+templates?|five\s+(?:distinct\s+)?(?:pages?|templates?))\b/i;
|
|
13
14
|
const CLONE_BUILD = /\bclone\s+of\b|\b(build|create|make)\s+(?:a|an|the|us|me)?\s*(?:clone|replica|remake|port)\b/i;
|
|
14
15
|
const READ_ONLY_INTENT = /\b(analy[sz]e|analyse|analysis|audit|review|inspect|proof|understand|summari[sz]e|scan|read[\s-]?only|where we left|what is|how does|show me|tell me|identify\s+gaps?|gap\s+analysis|production\s+blockers?)\b/i;
|
|
15
16
|
const EXPLICIT_WRITE_VERBS = /\b(implement|write|edit|modify|update|redesign|overhaul|rebuild|transform|polish|art-direct|fix|repair|create|build|generate|scaffold|deploy|refactor|develop|integrate|correct|apply|patch)\b/i;
|
|
@@ -17,7 +18,7 @@ const TRIVIAL_HTML_PAGE = /\b(hello\s*world|simple\s+(?:html|web|page|site)|html
|
|
|
17
18
|
/** User explicitly grants write permission after an analysis or confirmation gate. */
|
|
18
19
|
const WRITE_PERMISSION_GRANT = /\b(?:hereby\s+confirm|i\s+confirm|confirm(?:ed)?\s+that|give\s+(?:you|permission)|granting\s+permission|allow\s+(?:you|me\s+to)|you\s+(?:may|can|have\s+permission\s+to)|permission\s+to|authorized?\s+to|approve(?:d)?\s+(?:the\s+)?(?:changes?|writes?|edits?)|go\s+ahead\s+and(?:\s+write|\s+apply|\s+make|\s+do)?|do\s+all\s+the\s+changes|apply\s+(?:the\s+)?(?:changes?|fixes?|fix)|proceed\s+with\s+(?:the\s+)?(?:changes?|fixes?|implementation|writes?)|make\s+(?:the\s+)?changes|please\s+write)\b/i;
|
|
19
20
|
/** Fix/correct/repair syntax or errors — write path without requiring artifact nouns. */
|
|
20
|
-
const FIX_CORRECT_INTENT = /\b(correct|fix|repair|resolve|patch|address|remedy)\b[\s\S]{0,48}\b(syntax|errors?|bugs?|issues?|mistakes?|typos?)\b|\b(syntax|errors?|bugs?|issues?)\b[\s\S]{0,48}\b(correct|fix|repair|resolve|patch)\b/i;
|
|
21
|
+
const FIX_CORRECT_INTENT = /\b(correct|fix|repair|resolve|patch|address|remedy)\b[\s\S]{0,48}\b(syntax|errors?|bugs?|issues?|defects?|mistakes?|typos?)\b|\b(syntax|errors?|bugs?|issues?|defects?)\b[\s\S]{0,48}\b(correct|fix|repair|resolve|patch)\b/i;
|
|
21
22
|
/** Read-only audit: check/review/find issues without an paired fix/implement verb nearby. */
|
|
22
23
|
const READ_ONLY_CHECK = /\b(check|verify|audit|inspect|review|investigate|look\s+for|find|scan|validate|examine)\b[\s\S]{0,60}\b(syntax|errors?|bugs?|issues?|missing|gaps?|functionality)\b/i;
|
|
23
24
|
/** Strip CLI-appended platform / mode shaping so it cannot flip quality profiles. */
|
|
@@ -77,6 +78,14 @@ export function isAgentContinuePrompt(prompt) {
|
|
|
77
78
|
if (isBuiltContinuePrompt(text) || isBuiltRetryPrompt(text)) {
|
|
78
79
|
return false;
|
|
79
80
|
}
|
|
81
|
+
const concreteFileScope = /(?<![A-Za-z0-9_.-])[A-Za-z0-9_./\\-]+\.[A-Za-z0-9]{1,16}(?![A-Za-z0-9_.-])/.test(text);
|
|
82
|
+
const detailedMutationFollowUp = EXPLICIT_WRITE_VERBS.test(text) && (concreteFileScope || text.split(/\s+/).length > 20);
|
|
83
|
+
if (detailedMutationFollowUp) {
|
|
84
|
+
// A user may naturally say "continue" while also supplying new defect
|
|
85
|
+
// evidence, exact file ownership, or revised acceptance criteria. That is
|
|
86
|
+
// a new scoped follow-up, not shorthand for replaying the stored prompt.
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
80
89
|
if (/^\/(?:continue|contiune|contiunue)$/i.test(text)) {
|
|
81
90
|
return true;
|
|
82
91
|
}
|
|
@@ -128,7 +137,7 @@ export function isTrivialHtmlPageRequest(prompt) {
|
|
|
128
137
|
if (text.length > 420) {
|
|
129
138
|
return false;
|
|
130
139
|
}
|
|
131
|
-
if (/\b(multi[- ]?page|full\s+saas|dashboard|e[\s-]?commerce|auth|database|backend|api|microservice)\b/i.test(text)) {
|
|
140
|
+
if (/\b(multi[- ]?page|five\s+pages?|showcase|enterprise(?:[- ]grade)?|animated?|motion|redesign|overhaul|existing\s+(?:site|website|project)|full\s+saas|dashboard|e[\s-]?commerce|auth|database|backend|api|microservice)\b/i.test(text)) {
|
|
132
141
|
return false;
|
|
133
142
|
}
|
|
134
143
|
return TRIVIAL_HTML_PAGE.test(text)
|
|
@@ -247,6 +256,16 @@ export function inferAgentTaskType(prompt) {
|
|
|
247
256
|
}
|
|
248
257
|
return 'implementation';
|
|
249
258
|
}
|
|
259
|
+
// A comprehensive website redesign remains a web-build even when the brief
|
|
260
|
+
// also says to fix/repair defects found during the audit. Treating the repair
|
|
261
|
+
// verb as globally dominant collapses the required markup/style/interaction
|
|
262
|
+
// decomposition into a narrow three-step bug-fix workflow.
|
|
263
|
+
if (hasWebPageIntent(prompt)
|
|
264
|
+
&& hasBuildVerb(prompt)
|
|
265
|
+
&& COMPREHENSIVE_WEB_BUILD.test(text)
|
|
266
|
+
&& !hasGameIntent(prompt)) {
|
|
267
|
+
return 'web-build';
|
|
268
|
+
}
|
|
250
269
|
if (hasFixOrCorrectIntent(prompt)) {
|
|
251
270
|
if (hasGameIntent(prompt)) {
|
|
252
271
|
return 'game-build';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type RuntimeCapabilities = {
|
|
2
|
+
loopbackServices: boolean;
|
|
3
|
+
localV3Agent: boolean;
|
|
4
|
+
};
|
|
5
|
+
/** Explicit operator grants only; never infer trust from hostname, cwd, OS, or container state. */
|
|
6
|
+
export declare function resolveRuntimeCapabilities(environment?: NodeJS.ProcessEnv): RuntimeCapabilities;
|
|
7
|
+
export declare function hasLoopbackServiceCapability(environment?: NodeJS.ProcessEnv): boolean;
|
|
8
|
+
export declare function hasLocalV3AgentCapability(environment?: NodeJS.ProcessEnv): boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Explicit operator grants only; never infer trust from hostname, cwd, OS, or container state. */
|
|
2
|
+
export function resolveRuntimeCapabilities(environment = process.env) {
|
|
3
|
+
const loopbackServices = environment.VIGTHORIA_ALLOW_LOCAL_SERVICES === '1'
|
|
4
|
+
|| environment.VIGTHORIA_ALLOW_LOOPBACK === '1'
|
|
5
|
+
|| environment.VIGTHORIA_LOCAL_TEST_MODE === '1';
|
|
6
|
+
return {
|
|
7
|
+
loopbackServices,
|
|
8
|
+
localV3Agent: loopbackServices && environment.VIGTHORIA_ALLOW_LOCAL_V3_AGENT === '1',
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function hasLoopbackServiceCapability(environment = process.env) {
|
|
12
|
+
return resolveRuntimeCapabilities(environment).loopbackServices;
|
|
13
|
+
}
|
|
14
|
+
export function hasLocalV3AgentCapability(environment = process.env) {
|
|
15
|
+
return resolveRuntimeCapabilities(environment).localV3Agent;
|
|
16
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface OutboundScanResult<T> {
|
|
2
|
+
value: T;
|
|
3
|
+
findings: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare class OutboundContextError extends Error {
|
|
6
|
+
readonly findings: string[];
|
|
7
|
+
constructor(findings: string[]);
|
|
8
|
+
}
|
|
9
|
+
export declare function isSensitivePath(pathValue: string): boolean;
|
|
10
|
+
export declare function containsHighConfidenceSecret(input: string): boolean;
|
|
11
|
+
export declare function redactSensitiveText(input: string): string;
|
|
12
|
+
export declare function redactSensitiveValue(value: unknown, seen?: WeakSet<object>): unknown;
|
|
13
|
+
/**
|
|
14
|
+
* Final production-output guard. Commands should still redact at their own
|
|
15
|
+
* boundaries, but this prevents an overlooked remote error or JSON object
|
|
16
|
+
* from writing credentials directly to stdout/stderr.
|
|
17
|
+
*/
|
|
18
|
+
export declare function installConsoleRedaction(): void;
|
|
19
|
+
export declare function scanOutboundContext<T>(input: T): OutboundScanResult<T>;
|
|
20
|
+
export declare function safeChildProcessEnv(source?: NodeJS.ProcessEnv, additions?: Record<string, string>): NodeJS.ProcessEnv;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const REDACTED = '[REDACTED]';
|
|
2
|
+
const SENSITIVE_SEGMENTS = new Set([
|
|
3
|
+
'.git', '.ssh', '.aws', '.azure', '.kube', '.gnupg', '.vigthoria',
|
|
4
|
+
'credentials', 'credential', 'secrets', 'secret',
|
|
5
|
+
]);
|
|
6
|
+
const SENSITIVE_BASENAMES = new Set([
|
|
7
|
+
'.env', '.npmrc', '.pypirc', '.netrc', '.git-credentials',
|
|
8
|
+
'credentials.json', 'credential.json', 'secrets.json', 'secret.json',
|
|
9
|
+
'auth.json', 'session.json', 'tokens.json', 'token.json',
|
|
10
|
+
'id_rsa', 'id_dsa', 'id_ecdsa', 'id_ed25519',
|
|
11
|
+
]);
|
|
12
|
+
const SENSITIVE_EXTENSIONS = new Set([
|
|
13
|
+
'.pem', '.key', '.p12', '.pfx', '.jks', '.keystore', '.crtkey', '.ovpn',
|
|
14
|
+
]);
|
|
15
|
+
const SECRET_PATTERNS = [
|
|
16
|
+
/-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/i,
|
|
17
|
+
/\bAKIA[0-9A-Z]{16}\b/,
|
|
18
|
+
/\b(?:ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_]{20,}\b/,
|
|
19
|
+
/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/,
|
|
20
|
+
/\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/,
|
|
21
|
+
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/,
|
|
22
|
+
/\b(?:password|passwd|pwd|client_secret|private_key|api[_-]?key|access[_-]?token|refresh[_-]?token|auth[_-]?token)\s*[:=]\s*["']?[A-Za-z0-9_./+=:-]{8,}/i,
|
|
23
|
+
];
|
|
24
|
+
const SENSITIVE_KEY = /(?:^|[_-])(?:authorization|cookie|password|passwd|pwd|secret|private[_-]?key|api[_-]?key|access[_-]?token|refresh[_-]?token|auth[_-]?token|session)(?:$|[_-])/i;
|
|
25
|
+
const SENSITIVE_ENV_KEY = /(?:TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|COOKIE|SESSION|PRIVATE_KEY|API_KEY|AUTH|AWS_|AZURE_|GITHUB_|GITLAB_|NPM_|SSH_)/i;
|
|
26
|
+
export class OutboundContextError extends Error {
|
|
27
|
+
findings;
|
|
28
|
+
constructor(findings) {
|
|
29
|
+
super(`Outbound context contains prohibited secret material (${findings.join(', ')})`);
|
|
30
|
+
this.name = 'OutboundContextError';
|
|
31
|
+
this.findings = findings;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function isSensitivePath(pathValue) {
|
|
35
|
+
const normalized = String(pathValue || '').replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase();
|
|
36
|
+
if (!normalized)
|
|
37
|
+
return false;
|
|
38
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
39
|
+
const basename = segments.at(-1) || '';
|
|
40
|
+
if (segments.some((segment) => SENSITIVE_SEGMENTS.has(segment)))
|
|
41
|
+
return true;
|
|
42
|
+
if (SENSITIVE_BASENAMES.has(basename))
|
|
43
|
+
return true;
|
|
44
|
+
if (basename.startsWith('.env.') || basename.endsWith('.env'))
|
|
45
|
+
return true;
|
|
46
|
+
if (/^(?:credentials?|secrets?)[._-]/.test(basename))
|
|
47
|
+
return true;
|
|
48
|
+
if (/^(?:tokens?|auth|sessions?)\.(?:json|db|sqlite|sqlite3|yaml|yml)$/i.test(basename))
|
|
49
|
+
return true;
|
|
50
|
+
return SENSITIVE_EXTENSIONS.has(basename.slice(basename.lastIndexOf('.')));
|
|
51
|
+
}
|
|
52
|
+
export function containsHighConfidenceSecret(input) {
|
|
53
|
+
const value = String(input || '').replace(/\[REDACTED\]/g, '');
|
|
54
|
+
return SECRET_PATTERNS.some((pattern) => pattern.test(value));
|
|
55
|
+
}
|
|
56
|
+
export function redactSensitiveText(input) {
|
|
57
|
+
return String(input || '')
|
|
58
|
+
.replace(/-----BEGIN (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH |DSA )?PRIVATE KEY-----/gi, REDACTED)
|
|
59
|
+
.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, `Bearer ${REDACTED}`)
|
|
60
|
+
.replace(/\bAKIA[0-9A-Z]{16}\b/g, REDACTED)
|
|
61
|
+
.replace(/\b(?:ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_]{20,}\b/g, REDACTED)
|
|
62
|
+
.replace(/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, REDACTED)
|
|
63
|
+
.replace(/\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g, REDACTED)
|
|
64
|
+
.replace(/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, REDACTED)
|
|
65
|
+
.replace(/([?&](?:token|access_token|refresh_token|api_key|apikey|key|secret|signature)=)[^&#\s]+/gi, `$1${REDACTED}`)
|
|
66
|
+
.replace(/\b((?:password|passwd|pwd|token|access_token|refresh_token|api_key|apikey|client_secret|private_key|secret|signature)\s*[:=]\s*)["']?[^,;\s"']+["']?/gi, `$1${REDACTED}`)
|
|
67
|
+
.replace(/((?:authorization|cookie|x-api-key|x-service-key)\s*[:=]\s*)[^,;\s]+/gi, `$1${REDACTED}`)
|
|
68
|
+
.replace(/(vigthoria-auth-token=)[^;\s]+/gi, `$1${REDACTED}`);
|
|
69
|
+
}
|
|
70
|
+
export function redactSensitiveValue(value, seen = new WeakSet()) {
|
|
71
|
+
if (typeof value === 'string')
|
|
72
|
+
return redactSensitiveText(value);
|
|
73
|
+
if (value instanceof Error)
|
|
74
|
+
return `${value.name}: ${redactSensitiveText(value.message)}`;
|
|
75
|
+
if (!value || typeof value !== 'object')
|
|
76
|
+
return value;
|
|
77
|
+
if (seen.has(value))
|
|
78
|
+
return '[Circular]';
|
|
79
|
+
seen.add(value);
|
|
80
|
+
if (Array.isArray(value))
|
|
81
|
+
return value.map((entry) => redactSensitiveValue(entry, seen));
|
|
82
|
+
const output = {};
|
|
83
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
84
|
+
output[key] = SENSITIVE_KEY.test(key) ? REDACTED : redactSensitiveValue(entry, seen);
|
|
85
|
+
}
|
|
86
|
+
return output;
|
|
87
|
+
}
|
|
88
|
+
let consoleRedactionInstalled = false;
|
|
89
|
+
/**
|
|
90
|
+
* Final production-output guard. Commands should still redact at their own
|
|
91
|
+
* boundaries, but this prevents an overlooked remote error or JSON object
|
|
92
|
+
* from writing credentials directly to stdout/stderr.
|
|
93
|
+
*/
|
|
94
|
+
export function installConsoleRedaction() {
|
|
95
|
+
if (consoleRedactionInstalled)
|
|
96
|
+
return;
|
|
97
|
+
consoleRedactionInstalled = true;
|
|
98
|
+
for (const method of ['log', 'info', 'warn', 'error', 'debug']) {
|
|
99
|
+
const original = console[method].bind(console);
|
|
100
|
+
console[method] = ((...args) => {
|
|
101
|
+
original(...args.map((value) => redactSensitiveValue(value)));
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function scanOutboundContext(input) {
|
|
106
|
+
const findings = new Set();
|
|
107
|
+
const seen = new WeakSet();
|
|
108
|
+
const visit = (value, location, depth) => {
|
|
109
|
+
if (depth > 24)
|
|
110
|
+
throw new OutboundContextError([`${location}: depth limit exceeded`]);
|
|
111
|
+
if (typeof value === 'string') {
|
|
112
|
+
if (!/\s/.test(value) && isSensitivePath(value)) {
|
|
113
|
+
findings.add(`${location}: sensitive path value excluded`);
|
|
114
|
+
return REDACTED;
|
|
115
|
+
}
|
|
116
|
+
if (containsHighConfidenceSecret(value))
|
|
117
|
+
findings.add(`${location}: secret value redacted`);
|
|
118
|
+
return redactSensitiveText(value);
|
|
119
|
+
}
|
|
120
|
+
if (!value || typeof value !== 'object')
|
|
121
|
+
return value;
|
|
122
|
+
if (seen.has(value))
|
|
123
|
+
throw new OutboundContextError([`${location}: cyclic value`]);
|
|
124
|
+
seen.add(value);
|
|
125
|
+
if (Array.isArray(value))
|
|
126
|
+
return value.map((entry, index) => visit(entry, `${location}[${index}]`, depth + 1));
|
|
127
|
+
const output = {};
|
|
128
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
129
|
+
const child = `${location}.${key}`;
|
|
130
|
+
if (isSensitivePath(key)) {
|
|
131
|
+
findings.add(`${child}: sensitive path excluded`);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (SENSITIVE_KEY.test(key)) {
|
|
135
|
+
findings.add(`${child}: credential field redacted`);
|
|
136
|
+
output[key] = REDACTED;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
output[key] = visit(entry, child, depth + 1);
|
|
140
|
+
}
|
|
141
|
+
return output;
|
|
142
|
+
};
|
|
143
|
+
const value = visit(input, '$', 0);
|
|
144
|
+
if (containsHighConfidenceSecret(JSON.stringify(value))) {
|
|
145
|
+
throw new OutboundContextError(['residual secret after redaction']);
|
|
146
|
+
}
|
|
147
|
+
return { value, findings: [...findings].sort() };
|
|
148
|
+
}
|
|
149
|
+
export function safeChildProcessEnv(source = process.env, additions = {}) {
|
|
150
|
+
const output = {};
|
|
151
|
+
for (const [key, value] of Object.entries(source)) {
|
|
152
|
+
if (value !== undefined && !SENSITIVE_ENV_KEY.test(key))
|
|
153
|
+
output[key] = value;
|
|
154
|
+
}
|
|
155
|
+
for (const [key, value] of Object.entries(additions)) {
|
|
156
|
+
if (!SENSITIVE_ENV_KEY.test(key))
|
|
157
|
+
output[key] = value;
|
|
158
|
+
}
|
|
159
|
+
return output;
|
|
160
|
+
}
|
package/dist/utils/session.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Session Manager - Persist and resume conversations
|
|
3
3
|
* Similar to Vigthoria's session persistence
|
|
4
4
|
*/
|
|
5
|
-
import { ChatMessage } from './api.js';
|
|
5
|
+
import type { ChatMessage } from './api.js';
|
|
6
6
|
export type AuthState = {
|
|
7
7
|
token: string | null;
|
|
8
8
|
expiresAt: number | null;
|
|
@@ -40,16 +40,40 @@ export interface Session {
|
|
|
40
40
|
project: string;
|
|
41
41
|
model: string;
|
|
42
42
|
messages: ChatMessage[];
|
|
43
|
+
/** Stable event identities aligned one-to-one with messages. */
|
|
44
|
+
messageIds?: string[];
|
|
43
45
|
memorySummary?: string;
|
|
44
46
|
compactedAt?: string | null;
|
|
45
47
|
createdAt: string;
|
|
46
48
|
updatedAt: string;
|
|
47
49
|
agentMode: boolean;
|
|
48
50
|
operatorMode?: boolean;
|
|
51
|
+
projectId?: string;
|
|
49
52
|
lastAgentRunOutcome?: Record<string, unknown> | null;
|
|
53
|
+
activeAgentRun?: AgentExecutionCheckpoint | null;
|
|
50
54
|
}
|
|
55
|
+
export type AgentExecutionStatus = 'submitted' | 'retry' | 'continue' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
56
|
+
export interface AgentExecutionCheckpoint {
|
|
57
|
+
executionId: string;
|
|
58
|
+
parentExecutionId?: string | null;
|
|
59
|
+
contextId: string;
|
|
60
|
+
prompt: string;
|
|
61
|
+
originalPrompt: string;
|
|
62
|
+
workspacePath: string;
|
|
63
|
+
userTier: string | null;
|
|
64
|
+
taskType: string;
|
|
65
|
+
workflowType: string;
|
|
66
|
+
status: AgentExecutionStatus;
|
|
67
|
+
submittedAt: string;
|
|
68
|
+
updatedAt: string;
|
|
69
|
+
failedTaskIds: string[];
|
|
70
|
+
unfinishedTaskIds: string[];
|
|
71
|
+
error?: string | null;
|
|
72
|
+
}
|
|
73
|
+
export declare function assertAgentStatusTransition(from: AgentExecutionStatus, to: AgentExecutionStatus): void;
|
|
51
74
|
export declare class SessionManager {
|
|
52
75
|
private sessionsDir;
|
|
76
|
+
private indexStore;
|
|
53
77
|
private readonly compactThreshold;
|
|
54
78
|
private readonly retainRecentMessages;
|
|
55
79
|
/** Keep at most this many session files on disk per CLI install. */
|
|
@@ -58,7 +82,12 @@ export declare class SessionManager {
|
|
|
58
82
|
private readonly retentionWindowMs;
|
|
59
83
|
/** Avoid running prune on every save — at most once per CLI process. */
|
|
60
84
|
private prunedThisProcess;
|
|
61
|
-
constructor();
|
|
85
|
+
constructor(stateRoot?: string);
|
|
86
|
+
private workspaceSessionsDir;
|
|
87
|
+
private sessionStore;
|
|
88
|
+
private ensureMessageIds;
|
|
89
|
+
private readSessionFile;
|
|
90
|
+
private readSessionsFromDir;
|
|
62
91
|
private ensureDir;
|
|
63
92
|
/**
|
|
64
93
|
* Prune sessions older than the retention window AND cap the number of
|
|
@@ -70,6 +99,7 @@ export declare class SessionManager {
|
|
|
70
99
|
* Generate unique session ID
|
|
71
100
|
*/
|
|
72
101
|
private generateId;
|
|
102
|
+
private getProjectId;
|
|
73
103
|
/**
|
|
74
104
|
* Create a new session
|
|
75
105
|
*/
|
|
@@ -79,6 +109,7 @@ export declare class SessionManager {
|
|
|
79
109
|
* read by other local users on a shared POSIX host.
|
|
80
110
|
*/
|
|
81
111
|
save(session: Session): void;
|
|
112
|
+
private mergeConcurrentSession;
|
|
82
113
|
/**
|
|
83
114
|
* Load session by ID
|
|
84
115
|
*/
|
|
@@ -101,6 +132,7 @@ export declare class SessionManager {
|
|
|
101
132
|
* Clear all sessions
|
|
102
133
|
*/
|
|
103
134
|
clearAll(): number;
|
|
135
|
+
markCancelledByContext(contextId: string): number;
|
|
104
136
|
/**
|
|
105
137
|
* Add message to session
|
|
106
138
|
*/
|