@wix/pathgrade 1.0.17 → 1.0.19
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/README.md +7 -4
- 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 +5 -0
- package/dist/agents/codex-app-server/agent.js +145 -179
- 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/codex-app-server/managed-auth.d.ts +12 -0
- package/dist/agents/codex-app-server/managed-auth.js +53 -0
- package/dist/agents/codex-app-server/transport.d.ts +2 -0
- package/dist/agents/codex-app-server/transport.js +30 -3
- 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 +47 -38
- package/dist/openai-oauth/chatgpt-oauth-llm.d.ts +25 -0
- package/dist/openai-oauth/chatgpt-oauth-llm.js +398 -0
- package/dist/openai-oauth/codex-auth-broker.d.ts +32 -0
- package/dist/openai-oauth/codex-auth-broker.js +110 -0
- package/dist/openai-oauth/index.d.ts +2 -0
- package/dist/openai-oauth/index.js +1 -0
- package/dist/providers/credentials.d.ts +4 -1
- package/dist/providers/credentials.js +4 -3
- package/dist/providers/sandbox.d.ts +2 -0
- package/dist/providers/scripted-mcp-mock-host.js +6 -3
- package/dist/providers/workspace.d.ts +1 -0
- package/dist/providers/workspace.js +9 -1
- package/dist/sdk/agent-result-log.js +4 -2
- package/dist/sdk/agent.js +7 -0
- package/dist/sdk/judge-tools.js +14 -3
- 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/dist/utils/llm.js +11 -0
- package/docs/OPENAI_OAUTH_JUDGE.md +91 -0
- package/package.json +13 -2
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { getTurnResultLogMetadata, getVisibleAssistantMessage } from './visible-turn.js';
|
|
2
|
+
import { sanitizePersistenceValue } from '../tool-event-results.js';
|
|
3
|
+
import { getTurnResultSensitiveValues } from './turn-result-secrets.js';
|
|
2
4
|
function getOutputMetrics(message) {
|
|
3
5
|
return {
|
|
4
6
|
output_lines: message.split('\n').length,
|
|
@@ -7,7 +9,7 @@ function getOutputMetrics(message) {
|
|
|
7
9
|
}
|
|
8
10
|
export function buildModelAgentResultLogEntry(params) {
|
|
9
11
|
const assistantMessage = params.assistantMessage ?? getVisibleAssistantMessage(params.turnResult);
|
|
10
|
-
return {
|
|
12
|
+
return sanitizePersistenceValue({
|
|
11
13
|
type: 'agent_result',
|
|
12
14
|
timestamp: params.timestamp,
|
|
13
15
|
...(params.turnNumber === undefined ? {} : { turn_number: params.turnNumber }),
|
|
@@ -30,7 +32,7 @@ export function buildModelAgentResultLogEntry(params) {
|
|
|
30
32
|
? { error_subtype: params.turnResult.errorSubtype }
|
|
31
33
|
: {}),
|
|
32
34
|
...getOutputMetrics(assistantMessage),
|
|
33
|
-
};
|
|
35
|
+
}, getTurnResultSensitiveValues(params.turnResult));
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
36
38
|
* Build one `ask_batch` LogEntry per AskBus batch emitted in the given turn.
|
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
|
}
|
|
@@ -425,6 +430,7 @@ export async function createAgent(opts) {
|
|
|
425
430
|
const workspace = await prepareWorkspace({
|
|
426
431
|
...rest,
|
|
427
432
|
agent: agentName,
|
|
433
|
+
transport,
|
|
428
434
|
mcp: scriptedMcp
|
|
429
435
|
? undefined
|
|
430
436
|
: mcpConfigFile ? { configFile: mcpConfigFile } : mcpMock ? { mock: mcpMock } : undefined,
|
|
@@ -457,6 +463,7 @@ export async function createAgent(opts) {
|
|
|
457
463
|
opencodeExecutable,
|
|
458
464
|
opencodeMcpToolNames: agentName === 'opencode' ? collectOpenCodeMcpToolNames(mcpMock) : undefined,
|
|
459
465
|
scriptedMcp,
|
|
466
|
+
sensitiveValues: workspace.sensitiveValues ?? collectSensitiveEnvValues(workspace.env),
|
|
460
467
|
});
|
|
461
468
|
lifecycleCore.registerAgent(agent);
|
|
462
469
|
return agent;
|
package/dist/sdk/judge-tools.js
CHANGED
|
@@ -69,10 +69,21 @@ async function checkFinalContainment(workspace, absolutePath) {
|
|
|
69
69
|
return false;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
+
async function resolveExistingInWorkspace(workspace, relPath) {
|
|
73
|
+
const resolved = await resolveInWorkspace(workspace, relPath);
|
|
74
|
+
const [workspaceReal, targetReal] = await Promise.all([
|
|
75
|
+
fs.realpath(workspace),
|
|
76
|
+
fs.realpath(resolved),
|
|
77
|
+
]);
|
|
78
|
+
if (!isInside(workspaceReal, targetReal)) {
|
|
79
|
+
throw new Error(`Path "${relPath}" is outside workspace`);
|
|
80
|
+
}
|
|
81
|
+
return targetReal;
|
|
82
|
+
}
|
|
72
83
|
export async function readFile(ctx, relPath) {
|
|
73
|
-
const resolved = await resolveInWorkspace(ctx.workspace, relPath);
|
|
74
84
|
let content;
|
|
75
85
|
try {
|
|
86
|
+
const resolved = await resolveExistingInWorkspace(ctx.workspace, relPath);
|
|
76
87
|
content = await fs.readFile(resolved, 'utf8');
|
|
77
88
|
}
|
|
78
89
|
catch (err) {
|
|
@@ -88,9 +99,9 @@ export async function readFile(ctx, relPath) {
|
|
|
88
99
|
return content;
|
|
89
100
|
}
|
|
90
101
|
export async function listDir(ctx, relPath) {
|
|
91
|
-
const resolved = await resolveInWorkspace(ctx.workspace, relPath);
|
|
92
102
|
let entries;
|
|
93
103
|
try {
|
|
104
|
+
const resolved = await resolveExistingInWorkspace(ctx.workspace, relPath);
|
|
94
105
|
entries = await fs.readdir(resolved, { withFileTypes: true });
|
|
95
106
|
}
|
|
96
107
|
catch (err) {
|
|
@@ -107,7 +118,7 @@ export async function listDir(ctx, relPath) {
|
|
|
107
118
|
}
|
|
108
119
|
export async function grep(ctx, pattern, relPath) {
|
|
109
120
|
const rootRel = relPath ?? '.';
|
|
110
|
-
const root = await
|
|
121
|
+
const root = await resolveExistingInWorkspace(ctx.workspace, rootRel);
|
|
111
122
|
const workspaceReal = await fs.realpath(ctx.workspace);
|
|
112
123
|
const regex = new RegExp(pattern);
|
|
113
124
|
const matches = [];
|
|
@@ -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/dist/utils/llm.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveCodexModel } from '../agents/codex-model.js';
|
|
2
|
+
import { createChatGptOAuthJudgeLLM } from '../openai-oauth/chatgpt-oauth-llm.js';
|
|
1
3
|
import { cliProvider } from './llm-providers/cli.js';
|
|
2
4
|
import { anthropicProvider } from './llm-providers/anthropic.js';
|
|
3
5
|
import { openaiProvider } from './llm-providers/openai.js';
|
|
@@ -191,6 +193,15 @@ export async function callLLM(prompt, opts = {}) {
|
|
|
191
193
|
* the agent's env propagates to persona/judge/summarization calls.
|
|
192
194
|
*/
|
|
193
195
|
export function createAgentLLM(agentName, agentEnv, agentModel) {
|
|
196
|
+
const effectiveAgentEnv = agentEnv ?? process.env;
|
|
197
|
+
if (agentName === 'codex'
|
|
198
|
+
&& !effectiveAgentEnv.OPENAI_API_KEY
|
|
199
|
+
&& !effectiveAgentEnv.OPENAI_BASE_URL) {
|
|
200
|
+
return createChatGptOAuthJudgeLLM({
|
|
201
|
+
model: resolveCodexModel(agentModel),
|
|
202
|
+
reasoningEffort: 'high',
|
|
203
|
+
});
|
|
204
|
+
}
|
|
194
205
|
// Tool-using judges need a provider that implements callWithTools.
|
|
195
206
|
// For claude, anthropicProvider is added as a tool-use-capable fallback
|
|
196
207
|
// alongside the CLI. The CLI still wins for plain call() when available.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Experimental Codex-authenticated ChatGPT judge
|
|
2
|
+
|
|
3
|
+
Status: **experimental**. W0b passed for the direct
|
|
4
|
+
`@openai-oauth/core@2.0.0` Responses mapping, and W0c passed for acquiring a
|
|
5
|
+
managed ChatGPT access session from Codex App Server.
|
|
6
|
+
|
|
7
|
+
## Install and authenticate
|
|
8
|
+
|
|
9
|
+
Install the exact optional peer and sign in once with Codex:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
yarn add -D @wix/pathgrade @openai-oauth/core@2.0.0
|
|
13
|
+
codex login
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Pathgrade starts a short-lived `codex app-server` child over stdio, calls only
|
|
17
|
+
the auth-status RPC, then closes it before making the direct judge request. It
|
|
18
|
+
does not read, copy, or write `~/.codex/auth.json`, receive a refresh token,
|
|
19
|
+
open a local port, or start a Codex model thread. After a real upstream 401 it
|
|
20
|
+
asks Codex to refresh, retries once, and otherwise fails closed.
|
|
21
|
+
|
|
22
|
+
## Inject explicitly
|
|
23
|
+
|
|
24
|
+
Codex agents select this judge automatically when neither `OPENAI_API_KEY` nor
|
|
25
|
+
`OPENAI_BASE_URL` is present. The judge uses the resolved Codex model (Luna by
|
|
26
|
+
default) with high reasoning effort. Passing `evaluate(agent, scorers, { llm })`
|
|
27
|
+
remains the highest-precedence override. Explicit construction is available for
|
|
28
|
+
other agents and custom flows:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { createChatGptOAuthJudgeLLM } from '@wix/pathgrade/openai-oauth';
|
|
32
|
+
import { evaluate, judge } from '@wix/pathgrade';
|
|
33
|
+
|
|
34
|
+
const llm = createChatGptOAuthJudgeLLM({
|
|
35
|
+
model: 'gpt-5.6-sol',
|
|
36
|
+
reasoningEffort: 'high',
|
|
37
|
+
requestTimeoutMs: 60_000,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const result = await evaluate(agent, [
|
|
41
|
+
judge('quality', { rubric: 'Apply the frozen rubric.', model: 'gpt-5.6-sol' }),
|
|
42
|
+
], { llm });
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The scorer model must exactly equal the factory model. There is no auth-file,
|
|
46
|
+
API-key, base-URL, login callback, provider fallback, model rewrite, or default
|
|
47
|
+
provider registration. Plain calls expose zero tools. Tool judges expose only
|
|
48
|
+
their declared Pathgrade schemas through the existing bounded tool registry.
|
|
49
|
+
|
|
50
|
+
## Stable public failures
|
|
51
|
+
|
|
52
|
+
- `OAUTH_CONFIG_INVALID`
|
|
53
|
+
- `OAUTH_DEPENDENCY_UNAVAILABLE`
|
|
54
|
+
- `OAUTH_CODEX_UNAVAILABLE`
|
|
55
|
+
- `OAUTH_CODEX_LOGIN_REQUIRED`
|
|
56
|
+
- `OAUTH_AUTH_TIMEOUT`
|
|
57
|
+
- `OAUTH_MODEL_MISMATCH`
|
|
58
|
+
- `OAUTH_MODEL_UNAVAILABLE`
|
|
59
|
+
- `OAUTH_REQUEST_TIMEOUT`
|
|
60
|
+
- `OAUTH_UPSTREAM_RATE_LIMITED`
|
|
61
|
+
- `OAUTH_UPSTREAM_FAILED`
|
|
62
|
+
- `OAUTH_PROTOCOL_RESPONSE_INVALID`
|
|
63
|
+
|
|
64
|
+
Tokens, Codex paths, auth JSON, prompts, App Server stderr/RPC text, upstream
|
|
65
|
+
bodies, and tool results are never included in public errors. Failures never
|
|
66
|
+
fall back to another provider.
|
|
67
|
+
|
|
68
|
+
## Repository-only verification
|
|
69
|
+
|
|
70
|
+
These maintainer probes run from a source checkout and are not included in the
|
|
71
|
+
published npm package. Run them from the repository root.
|
|
72
|
+
|
|
73
|
+
The no-cost installed-binary probe reports only version, auth-mode booleans,
|
|
74
|
+
and its ADAPT/STOP verdict:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
yarn smoke:codex-auth
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The explicit paid live suite uses only the active Codex login. It exercises
|
|
81
|
+
direct plain and bounded `readFile` judges, then a real keyless Codex app-server
|
|
82
|
+
agent followed by automatically selected plain and `readFile` judges:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
yarn test:evals:judge-oauth:live
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
It writes redacted reports to `.pathgrade/judge-oauth-live-smoke.{json,md}` and
|
|
89
|
+
`.pathgrade/codex-oauth-agent-judge-e2e.json`.
|
|
90
|
+
Any `@openai-oauth/core` or Codex protocol upgrade must rerun W0b, W0c, and
|
|
91
|
+
the live smoke.
|