@wix/pathgrade 1.0.16 → 1.0.18
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/dist/agents/claude/sdk-message-projector.js +12 -4
- package/dist/agents/claude/tool-results.d.ts +1 -2
- package/dist/agents/claude/tool-results.js +9 -40
- package/dist/agents/claude.js +6 -4
- package/dist/agents/codex-app-server/agent.d.ts +3 -0
- package/dist/agents/codex-app-server/agent.js +109 -162
- package/dist/agents/codex-app-server/item-lifecycle.d.ts +30 -0
- package/dist/agents/codex-app-server/item-lifecycle.js +95 -0
- package/dist/agents/codex-app-server/item-projection.d.ts +62 -0
- package/dist/agents/codex-app-server/item-projection.js +135 -0
- package/dist/agents/opencode/host-safety.d.ts +3 -0
- package/dist/agents/opencode/host-safety.js +30 -0
- package/dist/agents/opencode.d.ts +2 -4
- package/dist/agents/opencode.js +55 -38
- package/dist/providers/credentials.d.ts +2 -0
- package/dist/providers/credentials.js +1 -0
- package/dist/providers/scripted-mcp-mock-host.js +6 -3
- package/dist/providers/workspace.d.ts +1 -0
- package/dist/providers/workspace.js +5 -0
- package/dist/sdk/agent-result-log.js +4 -2
- package/dist/sdk/agent.js +6 -0
- package/dist/sdk/managed-session.d.ts +2 -0
- package/dist/sdk/managed-session.js +22 -5
- package/dist/sdk/mcp-safety.js +2 -18
- package/dist/sdk/snapshots.d.ts +1 -0
- package/dist/sdk/snapshots.js +3 -2
- package/dist/sdk/tool-event-log.js +5 -2
- package/dist/sdk/tool-event-secrets.d.ts +4 -0
- package/dist/sdk/tool-event-secrets.js +14 -0
- package/dist/sdk/turn-result-secrets.d.ts +5 -0
- package/dist/sdk/turn-result-secrets.js +12 -0
- package/dist/tool-event-results.d.ts +10 -0
- package/dist/tool-event-results.js +171 -0
- package/dist/types.d.ts +2 -0
- package/package.json +2 -2
package/dist/sdk/agent.js
CHANGED
|
@@ -17,6 +17,7 @@ import fs from 'fs-extra';
|
|
|
17
17
|
import * as path from 'path';
|
|
18
18
|
import { cleanDebugRuns, DEFAULT_DEBUG_RETAIN_RUNS, prepareManagedDebugRun, } from '../providers/debug-runs.js';
|
|
19
19
|
import { collectOpenCodeMcpToolNames, validateOpenCodeDeclaration } from '../agents/opencode/contract.js';
|
|
20
|
+
import { collectSensitiveEnvValues } from '../tool-event-results.js';
|
|
20
21
|
import { compileMcpMockApprovalSession, } from './mcp-mock-approvals.js';
|
|
21
22
|
/**
|
|
22
23
|
* Test-only injection point: override the sink used by the next emitter
|
|
@@ -49,6 +50,7 @@ class AgentImpl {
|
|
|
49
50
|
opencodeMcpToolNames;
|
|
50
51
|
activeChatSession;
|
|
51
52
|
scriptedMcp;
|
|
53
|
+
sensitiveValues;
|
|
52
54
|
constructor(opts) {
|
|
53
55
|
this.ws = opts.workspace;
|
|
54
56
|
this.agentName = opts.agentName;
|
|
@@ -65,6 +67,7 @@ class AgentImpl {
|
|
|
65
67
|
this.opencodeExecutable = opts.opencodeExecutable;
|
|
66
68
|
this.opencodeMcpToolNames = opts.opencodeMcpToolNames;
|
|
67
69
|
this.scriptedMcp = opts.scriptedMcp;
|
|
70
|
+
this.sensitiveValues = opts.sensitiveValues;
|
|
68
71
|
}
|
|
69
72
|
get messages() {
|
|
70
73
|
return this._messages;
|
|
@@ -110,6 +113,7 @@ class AgentImpl {
|
|
|
110
113
|
...(this.opencodeExecutable !== undefined ? { opencodeExecutable: this.opencodeExecutable } : {}),
|
|
111
114
|
...(this.opencodeMcpToolNames !== undefined ? { opencodeMcpToolNames: this.opencodeMcpToolNames } : {}),
|
|
112
115
|
...(this.scriptedMcp !== undefined ? { scriptedMcp: this.scriptedMcp } : {}),
|
|
116
|
+
sensitiveValues: this.sensitiveValues,
|
|
113
117
|
});
|
|
114
118
|
}
|
|
115
119
|
resolveTimeoutSec(mode, maxTurns) {
|
|
@@ -349,6 +353,7 @@ class AgentImpl {
|
|
|
349
353
|
log: this._log,
|
|
350
354
|
conversationResult: this.lastConversationResult,
|
|
351
355
|
workspace: dest,
|
|
356
|
+
sensitiveValues: this.sensitiveValues,
|
|
352
357
|
});
|
|
353
358
|
await fs.writeJSON(path.join(dest, 'run-snapshot.json'), snapshot, { spaces: 2 });
|
|
354
359
|
}
|
|
@@ -457,6 +462,7 @@ export async function createAgent(opts) {
|
|
|
457
462
|
opencodeExecutable,
|
|
458
463
|
opencodeMcpToolNames: agentName === 'opencode' ? collectOpenCodeMcpToolNames(mcpMock) : undefined,
|
|
459
464
|
scriptedMcp,
|
|
465
|
+
sensitiveValues: workspace.sensitiveValues ?? collectSensitiveEnvValues(workspace.env),
|
|
460
466
|
});
|
|
461
467
|
lifecycleCore.registerAgent(agent);
|
|
462
468
|
return agent;
|
|
@@ -36,6 +36,8 @@ export interface ManagedSessionDeps {
|
|
|
36
36
|
opencodeMcpToolNames?: string[];
|
|
37
37
|
/** Trusted pre-workspace compiled generated-MCP declaration. */
|
|
38
38
|
scriptedMcp?: CompiledMcpMockSession;
|
|
39
|
+
/** Runtime-only values that must not enter session logs or persisted results. */
|
|
40
|
+
sensitiveValues?: readonly string[];
|
|
39
41
|
}
|
|
40
42
|
export interface ManagedSession {
|
|
41
43
|
/** Full lifecycle: log start/result, push messages, check exit code. */
|
|
@@ -6,6 +6,9 @@ import { buildToolEventLogEntry } from './tool-event-log.js';
|
|
|
6
6
|
import { planRuntimePolicies } from './runtime-policy.js';
|
|
7
7
|
import { getVisibleAssistantMessage } from './visible-turn.js';
|
|
8
8
|
import { createAskBus } from './ask-bus/bus.js';
|
|
9
|
+
import { attachTurnResultSensitiveValues, cloneTurnResultWithSensitiveValues, getTurnResultSensitiveValues, } from './turn-result-secrets.js';
|
|
10
|
+
import { attachToolEventSensitiveValues, getToolEventSensitiveValues } from './tool-event-secrets.js';
|
|
11
|
+
import { sanitizePersistenceValue } from '../tool-event-results.js';
|
|
9
12
|
import { startScriptedMcpMockHost, } from '../providers/scripted-mcp-mock-host.js';
|
|
10
13
|
export function createManagedSession(deps) {
|
|
11
14
|
const { ws, agentName, timeoutSec, messages, log, model, conversationWindow, llm } = deps;
|
|
@@ -28,6 +31,7 @@ export function createManagedSession(deps) {
|
|
|
28
31
|
...(deps.mcpSafety !== undefined ? { mcpSafety: deps.mcpSafety } : {}),
|
|
29
32
|
...(deps.opencodeExecutable !== undefined ? { opencodeExecutable: deps.opencodeExecutable } : {}),
|
|
30
33
|
...(deps.opencodeMcpToolNames !== undefined ? { opencodeMcpToolNames: deps.opencodeMcpToolNames } : {}),
|
|
34
|
+
...(deps.sensitiveValues !== undefined ? { sensitiveValues: deps.sensitiveValues } : {}),
|
|
31
35
|
getAbortSignal: () => currentSignal,
|
|
32
36
|
getRemainingMs: () => Math.max(0, deadlineMs - Date.now()),
|
|
33
37
|
};
|
|
@@ -40,14 +44,14 @@ export function createManagedSession(deps) {
|
|
|
40
44
|
let disposePromise;
|
|
41
45
|
const runCommand = async (cmd) => {
|
|
42
46
|
const result = await ws.exec(cmd, { signal: currentSignal });
|
|
43
|
-
log.push({
|
|
47
|
+
log.push(sanitizePersistenceValue({
|
|
44
48
|
type: 'command',
|
|
45
49
|
timestamp: new Date().toISOString(),
|
|
46
50
|
command: cmd,
|
|
47
51
|
stdout: result.stdout,
|
|
48
52
|
stderr: result.stderr,
|
|
49
53
|
exitCode: result.exitCode,
|
|
50
|
-
});
|
|
54
|
+
}, deps.sensitiveValues));
|
|
51
55
|
return result;
|
|
52
56
|
};
|
|
53
57
|
const executeTurn = async (message) => {
|
|
@@ -94,17 +98,30 @@ export function createManagedSession(deps) {
|
|
|
94
98
|
}
|
|
95
99
|
const turnNumber = ++nextTurnNumber;
|
|
96
100
|
scriptedHost?.beginTurn(turnNumber);
|
|
97
|
-
|
|
101
|
+
let result = turnNumber === 1
|
|
98
102
|
? await session.start({ message })
|
|
99
103
|
: await session.reply({ message });
|
|
100
104
|
if (scriptedHost) {
|
|
101
105
|
const settled = scriptedHost.settleEvents(result.toolEvents);
|
|
102
106
|
result.toolEvents = settled.events;
|
|
103
107
|
if (settled.error) {
|
|
104
|
-
|
|
108
|
+
result = cloneTurnResultWithSensitiveValues(result, {
|
|
109
|
+
exitCode: 1,
|
|
110
|
+
rawOutput: settled.error.message,
|
|
111
|
+
});
|
|
105
112
|
}
|
|
106
113
|
}
|
|
107
|
-
|
|
114
|
+
const sensitiveValues = [...new Set([
|
|
115
|
+
...(deps.sensitiveValues ?? []),
|
|
116
|
+
...getTurnResultSensitiveValues(result),
|
|
117
|
+
])];
|
|
118
|
+
for (const event of result.toolEvents) {
|
|
119
|
+
attachToolEventSensitiveValues(event, [...new Set([
|
|
120
|
+
...sensitiveValues,
|
|
121
|
+
...getToolEventSensitiveValues(event),
|
|
122
|
+
])]);
|
|
123
|
+
}
|
|
124
|
+
return attachTurnResultSensitiveValues(result, sensitiveValues);
|
|
108
125
|
}, remaining, label);
|
|
109
126
|
}
|
|
110
127
|
finally {
|
package/dist/sdk/mcp-safety.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { sanitizePersistenceValue } from '../tool-event-results.js';
|
|
2
2
|
export function decideMcpToolCall(options, request) {
|
|
3
3
|
const runMode = options?.runMode ?? 'mock';
|
|
4
4
|
if (runMode === 'mock')
|
|
@@ -31,7 +31,7 @@ export function decideMcpToolCall(options, request) {
|
|
|
31
31
|
return { action: 'allow' };
|
|
32
32
|
}
|
|
33
33
|
export function redactMcpSecrets(value) {
|
|
34
|
-
return
|
|
34
|
+
return sanitizePersistenceValue(value);
|
|
35
35
|
}
|
|
36
36
|
function ruleMatches(rule, request) {
|
|
37
37
|
if (rule.serverName !== undefined && rule.serverName !== request.serverName)
|
|
@@ -43,19 +43,3 @@ function ruleMatches(rule, request) {
|
|
|
43
43
|
function deny(reason, message) {
|
|
44
44
|
return { action: 'deny', reason, message };
|
|
45
45
|
}
|
|
46
|
-
function redactValue(value, key) {
|
|
47
|
-
if (SECRET_KEY_PATTERN.test(key))
|
|
48
|
-
return '<redacted>';
|
|
49
|
-
if (Array.isArray(value))
|
|
50
|
-
return value.map((entry) => redactValue(entry, key));
|
|
51
|
-
if (isRecord(value)) {
|
|
52
|
-
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
|
|
53
|
-
entryKey,
|
|
54
|
-
redactValue(entryValue, entryKey),
|
|
55
|
-
]));
|
|
56
|
-
}
|
|
57
|
-
return value;
|
|
58
|
-
}
|
|
59
|
-
function isRecord(value) {
|
|
60
|
-
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
61
|
-
}
|
package/dist/sdk/snapshots.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export declare function buildRunSnapshot(params: {
|
|
|
25
25
|
conversationResult: ConversationResult;
|
|
26
26
|
workspace: string | null;
|
|
27
27
|
timestamp?: string;
|
|
28
|
+
sensitiveValues?: readonly string[];
|
|
28
29
|
}): RunSnapshot;
|
|
29
30
|
export declare class SnapshotParseError extends Error {
|
|
30
31
|
constructor(message: string, options?: {
|
package/dist/sdk/snapshots.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sanitizePersistenceValue } from '../tool-event-results.js';
|
|
1
2
|
import fs from 'fs-extra';
|
|
2
3
|
export const RUN_SNAPSHOT_VERSION = 2;
|
|
3
4
|
export function buildRunSnapshot(params) {
|
|
@@ -5,7 +6,7 @@ export function buildRunSnapshot(params) {
|
|
|
5
6
|
const toolEvents = log
|
|
6
7
|
.filter((entry) => entry.type === 'tool_event' && entry.tool_event)
|
|
7
8
|
.map((entry) => entry.tool_event);
|
|
8
|
-
return {
|
|
9
|
+
return sanitizePersistenceValue({
|
|
9
10
|
version: agent === 'opencode' ? 2 : 1,
|
|
10
11
|
timestamp: timestamp ?? new Date().toISOString(),
|
|
11
12
|
agent,
|
|
@@ -20,7 +21,7 @@ export function buildRunSnapshot(params) {
|
|
|
20
21
|
turnTimings: [...conversationResult.turnTimings],
|
|
21
22
|
},
|
|
22
23
|
workspace,
|
|
23
|
-
};
|
|
24
|
+
}, params.sensitiveValues);
|
|
24
25
|
}
|
|
25
26
|
export class SnapshotParseError extends Error {
|
|
26
27
|
constructor(message, options) {
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { sanitizePersistenceValue } from '../tool-event-results.js';
|
|
2
|
+
import { getToolEventSensitiveValues } from './tool-event-secrets.js';
|
|
1
3
|
export function buildToolEventLogEntry(toolEvent, fallbackTimestamp) {
|
|
4
|
+
const persistedEvent = sanitizePersistenceValue(toolEvent, getToolEventSensitiveValues(toolEvent));
|
|
2
5
|
return {
|
|
3
6
|
type: 'tool_event',
|
|
4
|
-
timestamp:
|
|
5
|
-
tool_event:
|
|
7
|
+
timestamp: persistedEvent.startedAt ?? fallbackTimestamp,
|
|
8
|
+
tool_event: persistedEvent,
|
|
6
9
|
};
|
|
7
10
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ToolEvent } from '../tool-events.js';
|
|
2
|
+
export declare function attachToolEventSensitiveValues(event: ToolEvent, sensitiveValues: readonly string[]): ToolEvent;
|
|
3
|
+
export declare function getToolEventSensitiveValues(event: ToolEvent): readonly string[];
|
|
4
|
+
export declare function cloneToolEventWithRuntimeMetadata(source: ToolEvent, overrides: Partial<ToolEvent>): ToolEvent;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { attachOriginalMcpInput, getOriginalMcpInput } from './mcp-event-input.js';
|
|
2
|
+
const sensitiveValuesByEvent = new WeakMap();
|
|
3
|
+
export function attachToolEventSensitiveValues(event, sensitiveValues) {
|
|
4
|
+
sensitiveValuesByEvent.set(event, [...sensitiveValues]);
|
|
5
|
+
return event;
|
|
6
|
+
}
|
|
7
|
+
export function getToolEventSensitiveValues(event) {
|
|
8
|
+
return sensitiveValuesByEvent.get(event) ?? [];
|
|
9
|
+
}
|
|
10
|
+
export function cloneToolEventWithRuntimeMetadata(source, overrides) {
|
|
11
|
+
const clone = attachToolEventSensitiveValues({ ...source, ...overrides }, getToolEventSensitiveValues(source));
|
|
12
|
+
const originalInput = getOriginalMcpInput(source);
|
|
13
|
+
return originalInput ? attachOriginalMcpInput(clone, originalInput) : clone;
|
|
14
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AgentTurnResult } from '../types.js';
|
|
2
|
+
/** Attach runtime-only redaction context without changing the serialized result contract. */
|
|
3
|
+
export declare function attachTurnResultSensitiveValues(result: AgentTurnResult, sensitiveValues: readonly string[]): AgentTurnResult;
|
|
4
|
+
export declare function getTurnResultSensitiveValues(result: AgentTurnResult): readonly string[];
|
|
5
|
+
export declare function cloneTurnResultWithSensitiveValues(source: AgentTurnResult, overrides: Partial<AgentTurnResult>): AgentTurnResult;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const sensitiveValuesByResult = new WeakMap();
|
|
2
|
+
/** Attach runtime-only redaction context without changing the serialized result contract. */
|
|
3
|
+
export function attachTurnResultSensitiveValues(result, sensitiveValues) {
|
|
4
|
+
sensitiveValuesByResult.set(result, [...sensitiveValues]);
|
|
5
|
+
return result;
|
|
6
|
+
}
|
|
7
|
+
export function getTurnResultSensitiveValues(result) {
|
|
8
|
+
return sensitiveValuesByResult.get(result) ?? [];
|
|
9
|
+
}
|
|
10
|
+
export function cloneTurnResultWithSensitiveValues(source, overrides) {
|
|
11
|
+
return attachTurnResultSensitiveValues({ ...source, ...overrides }, getTurnResultSensitiveValues(source));
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ToolEventResult } from './tool-events.js';
|
|
2
|
+
export declare const TOOL_RESULT_MAX_CHARS: number;
|
|
3
|
+
export declare function collectSensitiveEnvValues(env?: Readonly<Record<string, string>>): string[];
|
|
4
|
+
/**
|
|
5
|
+
* Clone a persistence payload while removing secrets from both structured
|
|
6
|
+
* containers and any other strings that repeat their values. The second pass
|
|
7
|
+
* is important for summaries, snippets, traces, and provider error text.
|
|
8
|
+
*/
|
|
9
|
+
export declare function sanitizePersistenceValue<T>(source: T, explicitSensitiveValues?: readonly string[]): T;
|
|
10
|
+
export declare function sanitizeToolEventResult(source: Readonly<ToolEventResult>, sensitiveValues: readonly string[]): ToolEventResult;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
export const TOOL_RESULT_MAX_CHARS = 64 * 1024;
|
|
2
|
+
const SECRET_KEY_NAMES = [
|
|
3
|
+
'apikey',
|
|
4
|
+
'accesskey',
|
|
5
|
+
'accesskeyid',
|
|
6
|
+
'accesstoken',
|
|
7
|
+
'refreshtoken',
|
|
8
|
+
'idtoken',
|
|
9
|
+
'sessiontoken',
|
|
10
|
+
'token',
|
|
11
|
+
'secret',
|
|
12
|
+
'clientsecret',
|
|
13
|
+
'password',
|
|
14
|
+
'passwd',
|
|
15
|
+
'authorization',
|
|
16
|
+
'auth',
|
|
17
|
+
'bearer',
|
|
18
|
+
'cookie',
|
|
19
|
+
'setcookie',
|
|
20
|
+
'privatekey',
|
|
21
|
+
'signingkey',
|
|
22
|
+
'serviceaccount',
|
|
23
|
+
'databaseurl',
|
|
24
|
+
'connectionstring',
|
|
25
|
+
'credentials',
|
|
26
|
+
'credential',
|
|
27
|
+
];
|
|
28
|
+
export function collectSensitiveEnvValues(env) {
|
|
29
|
+
return [...new Set(Object.entries(env ?? {})
|
|
30
|
+
.filter(([key, value]) => isSecretKey(key) && value.length > 0)
|
|
31
|
+
.map(([, value]) => value))]
|
|
32
|
+
.sort((a, b) => b.length - a.length);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Clone a persistence payload while removing secrets from both structured
|
|
36
|
+
* containers and any other strings that repeat their values. The second pass
|
|
37
|
+
* is important for summaries, snippets, traces, and provider error text.
|
|
38
|
+
*/
|
|
39
|
+
export function sanitizePersistenceValue(source, explicitSensitiveValues = []) {
|
|
40
|
+
const sensitiveValues = [...new Set([
|
|
41
|
+
...explicitSensitiveValues.filter((value) => value.length > 0),
|
|
42
|
+
...collectStructuredSensitiveValues(source),
|
|
43
|
+
])].sort((a, b) => b.length - a.length);
|
|
44
|
+
return sanitizeValue(source, '', sensitiveValues);
|
|
45
|
+
}
|
|
46
|
+
export function sanitizeToolEventResult(source, sensitiveValues) {
|
|
47
|
+
const result = {};
|
|
48
|
+
let truncated = source.truncated === true;
|
|
49
|
+
const addBounded = (key, value) => {
|
|
50
|
+
if (typeof value !== 'string')
|
|
51
|
+
return;
|
|
52
|
+
const bounded = boundText(sanitizePersistenceValue(value, sensitiveValues));
|
|
53
|
+
result[key] = bounded.value;
|
|
54
|
+
truncated ||= bounded.truncated;
|
|
55
|
+
};
|
|
56
|
+
addBounded('content', source.content);
|
|
57
|
+
addBounded('stdout', source.stdout);
|
|
58
|
+
addBounded('stderr', source.stderr);
|
|
59
|
+
if (typeof source.exitCode === 'number' && Number.isFinite(source.exitCode)) {
|
|
60
|
+
result.exitCode = source.exitCode;
|
|
61
|
+
}
|
|
62
|
+
if (truncated)
|
|
63
|
+
result.truncated = true;
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
function redactSensitiveValues(value, sensitiveValues) {
|
|
67
|
+
let redacted = value;
|
|
68
|
+
for (const secret of sensitiveValues) {
|
|
69
|
+
if (secret.length > 0)
|
|
70
|
+
redacted = redacted.split(secret).join('<redacted>');
|
|
71
|
+
}
|
|
72
|
+
return redacted;
|
|
73
|
+
}
|
|
74
|
+
function collectStructuredSensitiveValues(source) {
|
|
75
|
+
const values = new Set();
|
|
76
|
+
const visit = (value, key) => {
|
|
77
|
+
if (typeof value === 'string' && isSecretKey(key) && value.length > 0) {
|
|
78
|
+
values.add(value);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (Array.isArray(value)) {
|
|
82
|
+
for (const entry of value)
|
|
83
|
+
visit(entry, key);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (isRecord(value)) {
|
|
87
|
+
for (const [entryKey, entryValue] of Object.entries(value))
|
|
88
|
+
visit(entryValue, entryKey);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
visit(source, '');
|
|
92
|
+
return [...values];
|
|
93
|
+
}
|
|
94
|
+
function sanitizeValue(value, key, sensitiveValues) {
|
|
95
|
+
if (isSecretKey(key))
|
|
96
|
+
return '<redacted>';
|
|
97
|
+
if (typeof value === 'string')
|
|
98
|
+
return redactCredentialShapes(redactSensitiveValues(value, sensitiveValues));
|
|
99
|
+
if (Array.isArray(value))
|
|
100
|
+
return value.map((entry) => sanitizeValue(entry, key, sensitiveValues));
|
|
101
|
+
if (isRecord(value)) {
|
|
102
|
+
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [
|
|
103
|
+
entryKey,
|
|
104
|
+
sanitizeValue(entryValue, entryKey, sensitiveValues),
|
|
105
|
+
]));
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
function redactCredentialShapes(value) {
|
|
110
|
+
let redacted = value;
|
|
111
|
+
const structured = parseStructuredJson(redacted);
|
|
112
|
+
if (structured !== undefined) {
|
|
113
|
+
redacted = JSON.stringify(sanitizePersistenceValue(structured));
|
|
114
|
+
}
|
|
115
|
+
if (redacted.includes('PRIVATE KEY-----')) {
|
|
116
|
+
redacted = redacted.replace(/-----BEGIN [^-\n]*PRIVATE KEY-----[\s\S]*?-----END [^-\n]*PRIVATE KEY-----/g, '<redacted>');
|
|
117
|
+
}
|
|
118
|
+
if (/\b(?:bearer|basic)\s/i.test(redacted)) {
|
|
119
|
+
redacted = redacted.replace(/(\b(?:bearer|basic)\s+)[^\s,;"']+/gi, '$1<redacted>');
|
|
120
|
+
}
|
|
121
|
+
if (redacted.includes('://')) {
|
|
122
|
+
redacted = redacted.replace(/([a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:)[^\s/@]+(@)/gi, '$1<redacted>$2');
|
|
123
|
+
}
|
|
124
|
+
if (redacted.includes('=') || redacted.includes(':')) {
|
|
125
|
+
redacted = redacted.replace(/(\b(?:api[_-]?key|access[_-]?token|refresh[_-]?token|client[_-]?secret|password|passwd|session(?:id)?|cookie)\s*[=:]\s*)[^\s,;"'}]+/gi, '$1<redacted>');
|
|
126
|
+
}
|
|
127
|
+
return redacted;
|
|
128
|
+
}
|
|
129
|
+
function isSecretKey(key) {
|
|
130
|
+
const normalized = key.replace(/[^a-z0-9]/gi, '').toLowerCase();
|
|
131
|
+
if (SECRET_KEY_NAMES.some((name) => normalized === name))
|
|
132
|
+
return true;
|
|
133
|
+
const compoundNames = SECRET_KEY_NAMES.filter((name) => name !== 'auth');
|
|
134
|
+
if (compoundNames.some((name) => normalized.startsWith(name) || normalized.endsWith(name)))
|
|
135
|
+
return true;
|
|
136
|
+
const segments = key.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
137
|
+
for (let start = 0; start < segments.length; start++) {
|
|
138
|
+
let candidate = '';
|
|
139
|
+
for (let end = start; end < segments.length; end++) {
|
|
140
|
+
candidate += segments[end];
|
|
141
|
+
if (SECRET_KEY_NAMES.some((name) => candidate === name))
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
function parseStructuredJson(value) {
|
|
148
|
+
const trimmed = value.trim();
|
|
149
|
+
if (!(trimmed.startsWith('{') && trimmed.endsWith('}'))
|
|
150
|
+
&& !(trimmed.startsWith('[') && trimmed.endsWith(']')))
|
|
151
|
+
return undefined;
|
|
152
|
+
try {
|
|
153
|
+
const parsed = JSON.parse(trimmed);
|
|
154
|
+
return isRecord(parsed) || Array.isArray(parsed) ? parsed : undefined;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function isRecord(value) {
|
|
161
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
162
|
+
}
|
|
163
|
+
function boundText(value) {
|
|
164
|
+
if (value.length <= TOOL_RESULT_MAX_CHARS)
|
|
165
|
+
return { value, truncated: false };
|
|
166
|
+
const marker = '\n[truncated by PathGrade]';
|
|
167
|
+
return {
|
|
168
|
+
value: `${value.slice(0, TOOL_RESULT_MAX_CHARS - marker.length)}${marker}`,
|
|
169
|
+
truncated: true,
|
|
170
|
+
};
|
|
171
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -387,6 +387,8 @@ export interface AgentSessionOptions {
|
|
|
387
387
|
opencodeMcpToolNames?: string[];
|
|
388
388
|
/** Managed-session-owned runtime for scripted generated MCP. */
|
|
389
389
|
scriptedMcpHost?: import('./providers/scripted-mcp-mock-host.js').ScriptedMcpMockHost;
|
|
390
|
+
/** Runtime-only values inherited by drivers and persistence sinks for redaction. */
|
|
391
|
+
sensitiveValues?: readonly string[];
|
|
390
392
|
}
|
|
391
393
|
export declare abstract class BaseAgent {
|
|
392
394
|
createSession(runtime: EnvironmentHandle, runCommand: AgentCommandRunner, options?: AgentSessionOptions): Promise<AgentSession>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/pathgrade",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"packageManager": "yarn@4.12.0",
|
|
5
5
|
"description": "Evaluate whether AI agents discover and use your skills correctly",
|
|
6
6
|
"exports": {
|
|
@@ -133,5 +133,5 @@
|
|
|
133
133
|
"typescript": "^5.9.3",
|
|
134
134
|
"zod": "4.3.6"
|
|
135
135
|
},
|
|
136
|
-
"falconPackageHash": "
|
|
136
|
+
"falconPackageHash": "3b9bb9a9b04396a2419626ab26afcee636663d2a034597d2e4fcff92"
|
|
137
137
|
}
|