@wix/pathgrade 1.0.17 → 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 +47 -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
|
@@ -22,6 +22,9 @@ const SDK_ERROR_SUBTYPES = [
|
|
|
22
22
|
'error_max_structured_output_retries',
|
|
23
23
|
];
|
|
24
24
|
import { TOOL_NAME_MAP, buildSummary, enrichSkillEvents } from '../../tool-events.js';
|
|
25
|
+
import { sanitizePersistenceValue } from '../../tool-event-results.js';
|
|
26
|
+
import { attachTurnResultSensitiveValues } from '../../sdk/turn-result-secrets.js';
|
|
27
|
+
import { attachToolEventSensitiveValues } from '../../sdk/tool-event-secrets.js';
|
|
25
28
|
import { attachOriginalMcpInput } from '../../sdk/mcp-event-input.js';
|
|
26
29
|
import { parseClaudeSdkMcpToolName } from './mcp-tool-name.js';
|
|
27
30
|
import { applyObservedToolResult, extractObservedToolResults, } from './tool-results.js';
|
|
@@ -132,13 +135,15 @@ export function projectSdkMessages(input) {
|
|
|
132
135
|
const trimmedAssistant = assistantText.trim();
|
|
133
136
|
const trimmedResult = resultText.trim();
|
|
134
137
|
const visible = isError ? '' : (trimmedAssistant || trimmedResult);
|
|
135
|
-
const rawOutput = resultText || assistantText;
|
|
138
|
+
const rawOutput = sanitizePersistenceValue(resultText || assistantText, input.sensitiveValues);
|
|
136
139
|
const enriched = enrichSkillEvents([
|
|
137
140
|
...toolEvents,
|
|
138
141
|
...(input.deniedMcpEvents?.all() ?? []),
|
|
139
142
|
]);
|
|
140
|
-
const finalToolEvents = prependSlashCommandSkillEvent(enriched, input.firstMessage, initSkills);
|
|
141
|
-
const traceOutput = input.messages
|
|
143
|
+
const finalToolEvents = prependSlashCommandSkillEvent(enriched, input.firstMessage, initSkills).map((event) => attachToolEventSensitiveValues(event, input.sensitiveValues ?? []));
|
|
144
|
+
const traceOutput = sanitizePersistenceValue(input.messages, input.sensitiveValues)
|
|
145
|
+
.map((message) => JSON.stringify(message))
|
|
146
|
+
.join('\n');
|
|
142
147
|
const result = {
|
|
143
148
|
rawOutput,
|
|
144
149
|
assistantMessage: visible,
|
|
@@ -155,7 +160,10 @@ export function projectSdkMessages(input) {
|
|
|
155
160
|
...(costUsd !== undefined ? { costUsd } : {}),
|
|
156
161
|
...(errorSubtype !== undefined ? { errorSubtype } : {}),
|
|
157
162
|
};
|
|
158
|
-
return {
|
|
163
|
+
return {
|
|
164
|
+
result: attachTurnResultSensitiveValues(result, input.sensitiveValues ?? []),
|
|
165
|
+
sessionId,
|
|
166
|
+
};
|
|
159
167
|
}
|
|
160
168
|
/**
|
|
161
169
|
* If the first user message of the turn is `/<name>` and `<name>` is one of
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SDKMessage } from '@anthropic-ai/claude-agent-sdk';
|
|
2
2
|
import type { ToolEvent } from '../../tool-events.js';
|
|
3
|
-
export
|
|
3
|
+
export { collectSensitiveEnvValues, TOOL_RESULT_MAX_CHARS } from '../../tool-event-results.js';
|
|
4
4
|
export interface ClaudeSdkMessageTiming {
|
|
5
5
|
receivedAt: string;
|
|
6
6
|
receivedMonotonicMs: number;
|
|
@@ -16,4 +16,3 @@ export interface ObservedToolResult {
|
|
|
16
16
|
export declare function hasToolLifecycleBoundary(message: SDKMessage): boolean;
|
|
17
17
|
export declare function extractObservedToolResults(message: SDKMessage, timing?: ClaudeSdkMessageTiming): ObservedToolResult[];
|
|
18
18
|
export declare function applyObservedToolResult(event: ToolEvent, observed: ObservedToolResult, startedMonotonicMs?: number, sensitiveValues?: readonly string[]): void;
|
|
19
|
-
export declare function collectSensitiveEnvValues(env: Readonly<Record<string, string>>): string[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { sanitizeToolEventResult, } from '../../tool-event-results.js';
|
|
2
|
+
export { collectSensitiveEnvValues, TOOL_RESULT_MAX_CHARS } from '../../tool-event-results.js';
|
|
3
3
|
export function hasToolLifecycleBoundary(message) {
|
|
4
4
|
if (message.type !== 'assistant' && message.type !== 'user')
|
|
5
5
|
return false;
|
|
@@ -52,50 +52,19 @@ export function applyObservedToolResult(event, observed, startedMonotonicMs, sen
|
|
|
52
52
|
event.arguments.status = event.status;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
export function collectSensitiveEnvValues(env) {
|
|
56
|
-
return [...new Set(Object.entries(env)
|
|
57
|
-
.filter(([key, value]) => SECRET_ENV_KEY_PATTERN.test(key) && value.length > 0)
|
|
58
|
-
.map(([, value]) => value))]
|
|
59
|
-
.sort((a, b) => b.length - a.length);
|
|
60
|
-
}
|
|
61
55
|
function buildBoundedToolResult(observed, sensitiveValues) {
|
|
62
56
|
const raw = observed.structuredResult;
|
|
63
|
-
const result = {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
result[key] = bounded.value;
|
|
70
|
-
truncated ||= bounded.truncated;
|
|
57
|
+
const result = {
|
|
58
|
+
...(typeof raw?.stdout === 'string' ? { stdout: raw.stdout } : {}),
|
|
59
|
+
...(typeof raw?.stderr === 'string' ? { stderr: raw.stderr } : {}),
|
|
60
|
+
...(typeof observed.content === 'string' && observed.content !== raw?.stdout
|
|
61
|
+
? { content: observed.content }
|
|
62
|
+
: {}),
|
|
71
63
|
};
|
|
72
|
-
addBounded('stdout', raw?.stdout);
|
|
73
|
-
addBounded('stderr', raw?.stderr);
|
|
74
|
-
if (observed.content !== raw?.stdout)
|
|
75
|
-
addBounded('content', observed.content);
|
|
76
64
|
const exitCode = raw?.exitCode ?? raw?.exit_code;
|
|
77
65
|
if (typeof exitCode === 'number' && Number.isFinite(exitCode))
|
|
78
66
|
result.exitCode = exitCode;
|
|
79
|
-
|
|
80
|
-
result.truncated = true;
|
|
81
|
-
return result;
|
|
82
|
-
}
|
|
83
|
-
function redactSensitiveValues(value, sensitiveValues) {
|
|
84
|
-
let redacted = value;
|
|
85
|
-
for (const secret of sensitiveValues) {
|
|
86
|
-
if (secret.length > 0)
|
|
87
|
-
redacted = redacted.split(secret).join('<redacted>');
|
|
88
|
-
}
|
|
89
|
-
return redacted;
|
|
90
|
-
}
|
|
91
|
-
function boundText(value) {
|
|
92
|
-
if (value.length <= TOOL_RESULT_MAX_CHARS)
|
|
93
|
-
return { value, truncated: false };
|
|
94
|
-
const marker = '\n[truncated by PathGrade]';
|
|
95
|
-
return {
|
|
96
|
-
value: `${value.slice(0, TOOL_RESULT_MAX_CHARS - marker.length)}${marker}`,
|
|
97
|
-
truncated: true,
|
|
98
|
-
};
|
|
67
|
+
return sanitizeToolEventResult(result, sensitiveValues);
|
|
99
68
|
}
|
|
100
69
|
function extractTextContent(content) {
|
|
101
70
|
if (typeof content === 'string')
|
package/dist/agents/claude.js
CHANGED
|
@@ -28,6 +28,8 @@ import { createSandboxedClaudeSpawn } from '../providers/sandboxed-claude-spawn.
|
|
|
28
28
|
import { assertClaudeLiveMcpSafetyPreflight, assertStdioMcpServersStartForClaudeSdk, mountMcpForClaudeSdk, } from '../providers/mcp-runtime-mounting.js';
|
|
29
29
|
import { buildClaudeSdkOptions, resolveClaudeCodeExecutable, } from './claude/sdk-options.js';
|
|
30
30
|
import { projectSdkMessages } from './claude/sdk-message-projector.js';
|
|
31
|
+
import { attachToolEventSensitiveValues } from '../sdk/tool-event-secrets.js';
|
|
32
|
+
import { cloneTurnResultWithSensitiveValues } from '../sdk/turn-result-secrets.js';
|
|
31
33
|
import { collectSensitiveEnvValues, hasToolLifecycleBoundary, } from './claude/tool-results.js';
|
|
32
34
|
import { createAskUserAnswerStore } from './claude/ask-user-answer-store.js';
|
|
33
35
|
import { createClaudeToolPermissionBridge } from './claude/tool-permission-bridge.js';
|
|
@@ -195,7 +197,8 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
195
197
|
deniedMcpEvents,
|
|
196
198
|
sensitiveValues,
|
|
197
199
|
});
|
|
198
|
-
projected.result.toolEvents = [...scriptedEvents, ...projected.result.toolEvents]
|
|
200
|
+
projected.result.toolEvents = [...scriptedEvents, ...projected.result.toolEvents]
|
|
201
|
+
.map((event) => attachToolEventSensitiveValues(event, sensitiveValues));
|
|
199
202
|
// Capture the SDK-reported session id BEFORE checking for a bus
|
|
200
203
|
// rejection so the next turn's `Options.resume` points at this
|
|
201
204
|
// turn's session even when the turn ended in an ask-bus error.
|
|
@@ -214,12 +217,11 @@ export class ClaudeAgent extends BaseAgent {
|
|
|
214
217
|
const errorMessage = bridgeError instanceof Error
|
|
215
218
|
? bridgeError.message
|
|
216
219
|
: String(bridgeError);
|
|
217
|
-
return {
|
|
218
|
-
...projected.result,
|
|
220
|
+
return cloneTurnResultWithSensitiveValues(projected.result, {
|
|
219
221
|
exitCode: 1,
|
|
220
222
|
errorSubtype: 'bus_rejection',
|
|
221
223
|
rawOutput: errorMessage,
|
|
222
|
-
};
|
|
224
|
+
});
|
|
223
225
|
}
|
|
224
226
|
return projected.result;
|
|
225
227
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AgentCommandRunner, AgentSession, AgentSessionOptions, BaseAgent, EnvironmentHandle } from '../../types.js';
|
|
2
2
|
import { type AppServerSessionHandle } from './transport.js';
|
|
3
|
+
import { type LifecycleClock } from './item-lifecycle.js';
|
|
3
4
|
type SandboxMode = 'workspace-write' | 'danger-full-access';
|
|
4
5
|
export interface PermissionGrantLogEntry {
|
|
5
6
|
type: 'permissions_granted';
|
|
@@ -25,6 +26,8 @@ export interface CodexAppServerAgentDeps {
|
|
|
25
26
|
sandboxMode?: SandboxMode;
|
|
26
27
|
/** Observer for per-grant audit entries (§7 of design decisions). */
|
|
27
28
|
onPermissionGrant?: (entry: PermissionGrantLogEntry) => void;
|
|
29
|
+
/** Injectable clocks keep wall timestamps and monotonic durations independently testable. */
|
|
30
|
+
clock?: LifecycleClock;
|
|
28
31
|
}
|
|
29
32
|
export declare class CodexAppServerAgent extends BaseAgent {
|
|
30
33
|
private deps;
|
|
@@ -1,133 +1,21 @@
|
|
|
1
1
|
import { BaseAgent, getRuntimeEnv, getWorkspacePath, } from '../../types.js';
|
|
2
2
|
import { mountMcpForCodexAppServer } from '../../providers/mcp-runtime-mounting.js';
|
|
3
3
|
import { assertMcpSecretReferencesReady } from '../../providers/mcp-config.js';
|
|
4
|
-
import {
|
|
4
|
+
import { enrichSkillEvents, } from '../../tool-events.js';
|
|
5
|
+
import { collectSensitiveEnvValues, sanitizePersistenceValue, } from '../../tool-event-results.js';
|
|
5
6
|
import { requireAskBusForLiveBatches } from '../../sdk/ask-bus/bus.js';
|
|
7
|
+
import { attachTurnResultSensitiveValues } from '../../sdk/turn-result-secrets.js';
|
|
8
|
+
import { attachToolEventSensitiveValues } from '../../sdk/tool-event-secrets.js';
|
|
6
9
|
import { toAskUserToolEvent } from '../../sdk/ask-bus/projection.js';
|
|
7
10
|
import { decideMcpToolCall } from '../../sdk/mcp-safety.js';
|
|
8
|
-
import { attachOriginalMcpInput } from '../../sdk/mcp-event-input.js';
|
|
9
11
|
import { spawnAppServerTransport, } from './transport.js';
|
|
10
12
|
import { normalizeUpstreamQuestion, toWireAnswerMap, } from './wire-translators.js';
|
|
11
13
|
import { extractTurnCompletionFailure } from './turn-completion.js';
|
|
12
14
|
import { resolveCodexModel } from '../codex-model.js';
|
|
13
15
|
import { CodexMcpApprovalCorrelator, extractMcpToolApprovalRequest, hasScriptedApprovalPolicy, isMcpToolCallApprovalRequest, recordPolicyDeniedMcpToolCall } from './mcp-approval-correlator.js';
|
|
16
|
+
import { projectItemIntoTurn } from './item-projection.js';
|
|
17
|
+
import { CodexItemLifecycle, } from './item-lifecycle.js';
|
|
14
18
|
const TURN_COMPLETED_METHOD = 'turn/completed';
|
|
15
|
-
function recordFromUnknown(value) {
|
|
16
|
-
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
17
|
-
return value;
|
|
18
|
-
}
|
|
19
|
-
return {};
|
|
20
|
-
}
|
|
21
|
-
function extractCommandActionSkillName(action) {
|
|
22
|
-
if (typeof action.path === 'string') {
|
|
23
|
-
const direct = extractSkillNameFromPath(action.path);
|
|
24
|
-
if (direct)
|
|
25
|
-
return direct;
|
|
26
|
-
const embedded = extractSkillNameFromText(action.path);
|
|
27
|
-
if (embedded)
|
|
28
|
-
return embedded;
|
|
29
|
-
}
|
|
30
|
-
return typeof action.command === 'string' ? extractSkillNameFromText(action.command) : undefined;
|
|
31
|
-
}
|
|
32
|
-
function extractSkillNameFromText(value) {
|
|
33
|
-
if (!value)
|
|
34
|
-
return undefined;
|
|
35
|
-
return value.match(/(?:^|[/\s"'])\.(?:agents|claude)\/skills\/([^/\s"']+)\/SKILL\.md(?:$|[\s"'])/)?.[1];
|
|
36
|
-
}
|
|
37
|
-
function extractSkillPathFromText(value) {
|
|
38
|
-
if (!value)
|
|
39
|
-
return undefined;
|
|
40
|
-
return value.match(/(?:^|[\s"'])(?<path>(?:\/|\.{1,2}\/)?[^\s"']*(?:\.agents|\.claude)\/skills\/[^/\s"']+\/SKILL\.md)(?:$|[\s"'])/)?.groups?.path;
|
|
41
|
-
}
|
|
42
|
-
function projectItemIntoTurn(item, turn) {
|
|
43
|
-
if (item.type === 'agentMessage') {
|
|
44
|
-
const msg = item;
|
|
45
|
-
if (msg.text) {
|
|
46
|
-
turn.assistantMessageParts.push(msg.text);
|
|
47
|
-
}
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (item.type === 'commandExecution') {
|
|
51
|
-
const cmd = item;
|
|
52
|
-
const action = inferCodexExecAction(cmd.command);
|
|
53
|
-
const skillPath = extractSkillPathFromText(cmd.command);
|
|
54
|
-
const args = {
|
|
55
|
-
command: cmd.command,
|
|
56
|
-
...(skillPath ? { path: skillPath } : {}),
|
|
57
|
-
};
|
|
58
|
-
turn.nonAskToolEvents.push({
|
|
59
|
-
action,
|
|
60
|
-
provider: 'codex',
|
|
61
|
-
providerToolName: 'commandExecution',
|
|
62
|
-
turnNumber: turn.turnNumber,
|
|
63
|
-
arguments: args,
|
|
64
|
-
summary: buildSummary(action, 'commandExecution', args),
|
|
65
|
-
confidence: 'high',
|
|
66
|
-
rawSnippet: JSON.stringify(cmd),
|
|
67
|
-
});
|
|
68
|
-
const recordedSkills = new Set();
|
|
69
|
-
for (const action of cmd.commandActions ?? []) {
|
|
70
|
-
const skillName = extractCommandActionSkillName(action) ?? extractSkillNameFromText(cmd.command);
|
|
71
|
-
if (!skillName || recordedSkills.has(skillName))
|
|
72
|
-
continue;
|
|
73
|
-
recordedSkills.add(skillName);
|
|
74
|
-
turn.nonAskToolEvents.push({
|
|
75
|
-
action: 'use_skill',
|
|
76
|
-
provider: 'codex',
|
|
77
|
-
providerToolName: `commandExecution.commandActions.${action.type ?? 'unknown'}`,
|
|
78
|
-
turnNumber: turn.turnNumber,
|
|
79
|
-
arguments: { path: action.path, name: action.name },
|
|
80
|
-
summary: `use_skill ${skillName}`,
|
|
81
|
-
confidence: 'high',
|
|
82
|
-
rawSnippet: JSON.stringify(action),
|
|
83
|
-
skillName,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (item.type === 'fileChange') {
|
|
89
|
-
const fc = item;
|
|
90
|
-
for (const change of fc.changes ?? []) {
|
|
91
|
-
turn.nonAskToolEvents.push({
|
|
92
|
-
action: 'edit_file',
|
|
93
|
-
provider: 'codex',
|
|
94
|
-
providerToolName: 'fileChange',
|
|
95
|
-
turnNumber: turn.turnNumber,
|
|
96
|
-
arguments: { file_path: change.path },
|
|
97
|
-
summary: `edit_file: ${change.path}`,
|
|
98
|
-
confidence: 'high',
|
|
99
|
-
rawSnippet: JSON.stringify(change),
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
if (item.type === 'mcpToolCall') {
|
|
105
|
-
const call = item;
|
|
106
|
-
if (turn.nonAskToolEvents.some((event) => event.action === 'mcp_tool_call'
|
|
107
|
-
&& event.toolUseId === call.id && event.mcp?.invocation === 'not_invoked'))
|
|
108
|
-
return;
|
|
109
|
-
const args = recordFromUnknown(call.arguments);
|
|
110
|
-
const providerToolName = `${call.server}.${call.tool}`;
|
|
111
|
-
turn.nonAskToolEvents.push(attachOriginalMcpInput({
|
|
112
|
-
action: 'mcp_tool_call',
|
|
113
|
-
provider: 'codex',
|
|
114
|
-
providerToolName,
|
|
115
|
-
toolUseId: call.id,
|
|
116
|
-
turnNumber: turn.turnNumber,
|
|
117
|
-
arguments: {
|
|
118
|
-
...args,
|
|
119
|
-
server: call.server,
|
|
120
|
-
tool: call.tool,
|
|
121
|
-
status: call.status ?? 'unknown',
|
|
122
|
-
},
|
|
123
|
-
summary: `MCP tool ${providerToolName} ${call.status ?? 'unknown'}`,
|
|
124
|
-
confidence: 'high',
|
|
125
|
-
rawSnippet: JSON.stringify(call),
|
|
126
|
-
...(call.result !== undefined ? { result: { content: JSON.stringify(call.result) } } : {}),
|
|
127
|
-
}, args));
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
19
|
function projectMcpStartupStatusIntoTurn(params, turn) {
|
|
132
20
|
const name = params.name ?? 'unknown';
|
|
133
21
|
const status = params.status ?? 'unknown';
|
|
@@ -156,6 +44,18 @@ function failTurn(turn, message) {
|
|
|
156
44
|
turn.failureMessage = message;
|
|
157
45
|
turn.signalFailure?.(message);
|
|
158
46
|
}
|
|
47
|
+
function extractTurnCompletionIdentity(params) {
|
|
48
|
+
if (!params || typeof params !== 'object' || Array.isArray(params))
|
|
49
|
+
return {};
|
|
50
|
+
const record = params;
|
|
51
|
+
const turnId = typeof record.turn?.id === 'string'
|
|
52
|
+
? record.turn.id
|
|
53
|
+
: typeof record.turnId === 'string' ? record.turnId : undefined;
|
|
54
|
+
return {
|
|
55
|
+
...(typeof record.threadId === 'string' ? { threadId: record.threadId } : {}),
|
|
56
|
+
...(turnId ? { turnId } : {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
159
59
|
function isLiveMcpSafetyMode(options) {
|
|
160
60
|
const runMode = options?.runMode ?? 'mock';
|
|
161
61
|
return runMode === 'live-readonly' || runMode === 'live-sandbox' || runMode === 'live';
|
|
@@ -170,6 +70,7 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
170
70
|
const askBus = requireAskBusForLiveBatches(options, 'CodexAppServerAgent');
|
|
171
71
|
const workspacePath = getWorkspacePath(runtime);
|
|
172
72
|
const runtimeEnv = getRuntimeEnv(runtime);
|
|
73
|
+
const sensitiveValues = collectSensitiveEnvValues(runtimeEnv);
|
|
173
74
|
const model = resolveCodexModel(options?.model);
|
|
174
75
|
const sandboxMode = this.deps.sandboxMode ?? 'workspace-write';
|
|
175
76
|
let handle = null;
|
|
@@ -207,7 +108,8 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
207
108
|
});
|
|
208
109
|
transport.onNotification((n) => {
|
|
209
110
|
if (process.env.PATHGRADE_CODEX_DEBUG) {
|
|
210
|
-
|
|
111
|
+
const debugParams = sanitizePersistenceValue(n.params, sensitiveValues);
|
|
112
|
+
console.error(`[codex app-server] notification method=${n.method} params=${JSON.stringify(debugParams).slice(0, 300)}`);
|
|
211
113
|
}
|
|
212
114
|
if (n.method === 'mcpServer/startupStatus/updated') {
|
|
213
115
|
const turn = activeTurn;
|
|
@@ -221,25 +123,14 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
221
123
|
return;
|
|
222
124
|
const params = n.params;
|
|
223
125
|
if (n.method === 'item/started') {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
catch (error) {
|
|
228
|
-
failTurn(turn, error instanceof Error ? error.message : String(error));
|
|
229
|
-
}
|
|
126
|
+
if (params)
|
|
127
|
+
turn.itemLifecycle?.receiveStarted(params);
|
|
230
128
|
return;
|
|
231
129
|
}
|
|
232
130
|
if (n.method !== 'item/completed')
|
|
233
131
|
return;
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
if (correlator?.completed(params) !== false)
|
|
238
|
-
projectItemIntoTurn(params.item, turn);
|
|
239
|
-
}
|
|
240
|
-
catch (error) {
|
|
241
|
-
failTurn(turn, error instanceof Error ? error.message : String(error));
|
|
242
|
-
}
|
|
132
|
+
if (params)
|
|
133
|
+
turn.itemLifecycle?.receiveCompleted(params);
|
|
243
134
|
});
|
|
244
135
|
const initialized = await transport.sendRequest('initialize', {
|
|
245
136
|
clientInfo: { name: 'pathgrade', version: '0.5.0', title: null },
|
|
@@ -262,6 +153,7 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
262
153
|
nonAskToolEvents: [],
|
|
263
154
|
assistantMessageParts: [],
|
|
264
155
|
turnFailed: false,
|
|
156
|
+
pendingTurnCompletions: [],
|
|
265
157
|
};
|
|
266
158
|
activeTurn = turn;
|
|
267
159
|
correlator?.beginTurn();
|
|
@@ -286,6 +178,31 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
286
178
|
}
|
|
287
179
|
threadId = resp.thread.id;
|
|
288
180
|
}
|
|
181
|
+
const authoritativeThreadId = threadId;
|
|
182
|
+
turn.authoritativeThreadId = authoritativeThreadId;
|
|
183
|
+
turn.itemLifecycle = new CodexItemLifecycle({
|
|
184
|
+
threadId: authoritativeThreadId,
|
|
185
|
+
clock: this.deps.clock,
|
|
186
|
+
onFailure: (message) => failTurn(turn, message),
|
|
187
|
+
onStarted: (params) => {
|
|
188
|
+
try {
|
|
189
|
+
correlator?.started(params);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
failTurn(turn, error instanceof Error ? error.message : String(error));
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
onCompleted: (item, timing, params) => {
|
|
196
|
+
try {
|
|
197
|
+
if (correlator?.completed(params) !== false) {
|
|
198
|
+
projectItemIntoTurn(item, turn, sensitiveValues, timing);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
failTurn(turn, error instanceof Error ? error.message : String(error));
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
});
|
|
289
206
|
// Wait for TurnCompleted OR subprocess crash OR dispatcher failure.
|
|
290
207
|
const turnCompletion = new Promise((resolve, reject) => {
|
|
291
208
|
let settled = false;
|
|
@@ -293,15 +210,30 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
293
210
|
if (settled)
|
|
294
211
|
return;
|
|
295
212
|
if (n.method === TURN_COMPLETED_METHOD) {
|
|
296
|
-
|
|
297
|
-
off();
|
|
298
|
-
closeOff();
|
|
299
|
-
const failure = extractTurnCompletionFailure(n.params);
|
|
300
|
-
if (failure)
|
|
301
|
-
Object.assign(turn, { turnFailed: true, failureMessage: failure });
|
|
302
|
-
resolve();
|
|
213
|
+
turn.acceptTurnCompletion?.(n.params);
|
|
303
214
|
}
|
|
304
215
|
});
|
|
216
|
+
const acceptTurnCompletion = (params) => {
|
|
217
|
+
if (settled)
|
|
218
|
+
return;
|
|
219
|
+
const identity = extractTurnCompletionIdentity(params);
|
|
220
|
+
if (identity.threadId && identity.threadId !== turn.authoritativeThreadId)
|
|
221
|
+
return;
|
|
222
|
+
if (identity.turnId && !turn.authoritativeTurnId) {
|
|
223
|
+
turn.pendingTurnCompletions.push(params);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (identity.turnId && identity.turnId !== turn.authoritativeTurnId)
|
|
227
|
+
return;
|
|
228
|
+
settled = true;
|
|
229
|
+
off();
|
|
230
|
+
closeOff();
|
|
231
|
+
const failure = extractTurnCompletionFailure(params);
|
|
232
|
+
if (failure)
|
|
233
|
+
Object.assign(turn, { turnFailed: true, failureMessage: failure });
|
|
234
|
+
resolve();
|
|
235
|
+
};
|
|
236
|
+
turn.acceptTurnCompletion = acceptTurnCompletion;
|
|
305
237
|
const closeOff = t.onClose((info) => {
|
|
306
238
|
if (settled)
|
|
307
239
|
return;
|
|
@@ -339,9 +271,25 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
339
271
|
}
|
|
340
272
|
}
|
|
341
273
|
});
|
|
342
|
-
const
|
|
274
|
+
const authoritativeTurn = t.sendRequest('turn/start', {
|
|
343
275
|
threadId,
|
|
344
276
|
input: [{ type: 'text', text: message, text_elements: [] }],
|
|
277
|
+
})
|
|
278
|
+
.then((started) => {
|
|
279
|
+
const authoritativeTurnId = started.turn?.id ?? started.turnId;
|
|
280
|
+
if (!authoritativeTurnId) {
|
|
281
|
+
scriptedHost?.failProtocol('turn/start did not return an authoritative turn id');
|
|
282
|
+
turn.signalFailure?.('turn/start did not return an authoritative turn id');
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
turn.itemLifecycle?.setAuthoritativeTurn(authoritativeTurnId);
|
|
286
|
+
turn.authoritativeTurnId = authoritativeTurnId;
|
|
287
|
+
if (correlator?.setAuthoritativeTurn(authoritativeThreadId, authoritativeTurnId) === false) {
|
|
288
|
+
turn.signalFailure?.('MCP lifecycle did not match the authoritative turn');
|
|
289
|
+
}
|
|
290
|
+
for (const pending of turn.pendingTurnCompletions.splice(0)) {
|
|
291
|
+
turn.acceptTurnCompletion?.(pending);
|
|
292
|
+
}
|
|
345
293
|
})
|
|
346
294
|
.catch((err) => {
|
|
347
295
|
// A JSON-RPC error on turn/start means the server will
|
|
@@ -355,18 +303,6 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
355
303
|
: 'turn/start failed';
|
|
356
304
|
turn.signalFailure?.(msg);
|
|
357
305
|
});
|
|
358
|
-
if (scriptedHost) {
|
|
359
|
-
const started = await startTurnResp;
|
|
360
|
-
if (!started?.turn?.id || !threadId) {
|
|
361
|
-
scriptedHost.failProtocol('turn/start did not return an authoritative turn id');
|
|
362
|
-
turn.signalFailure?.('turn/start did not return an authoritative turn id');
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
if (correlator?.setAuthoritativeTurn(threadId, started.turn.id) === false) {
|
|
366
|
-
turn.signalFailure?.('MCP lifecycle did not match the authoritative turn');
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
306
|
try {
|
|
371
307
|
await turnCompletion;
|
|
372
308
|
}
|
|
@@ -379,16 +315,25 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
379
315
|
message: info.message ?? 'app-server exited',
|
|
380
316
|
pid: info.pid,
|
|
381
317
|
signal: info.signal,
|
|
318
|
+
sensitiveValues,
|
|
382
319
|
});
|
|
383
320
|
}
|
|
384
|
-
if (!scriptedHost)
|
|
385
|
-
void startTurnResp;
|
|
386
321
|
if (turn.turnFailed) {
|
|
387
322
|
return assembleTurnResult({
|
|
388
323
|
askBus,
|
|
389
324
|
activeTurn: turn,
|
|
390
325
|
exitCode: 1,
|
|
391
326
|
message: turn.failureMessage ?? 'turn failed',
|
|
327
|
+
sensitiveValues,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
// Ensure pre-response lifecycle notifications have been correlated
|
|
331
|
+
// and projected before exposing the successful turn result.
|
|
332
|
+
await authoritativeTurn;
|
|
333
|
+
if (turn.turnFailed) {
|
|
334
|
+
return assembleTurnResult({
|
|
335
|
+
askBus, activeTurn: turn, exitCode: 1,
|
|
336
|
+
message: turn.failureMessage ?? 'turn failed', sensitiveValues,
|
|
392
337
|
});
|
|
393
338
|
}
|
|
394
339
|
return assembleTurnResult({
|
|
@@ -396,6 +341,7 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
396
341
|
activeTurn: turn,
|
|
397
342
|
exitCode: 0,
|
|
398
343
|
message: turn.assistantMessageParts.join('\n\n'),
|
|
344
|
+
sensitiveValues,
|
|
399
345
|
});
|
|
400
346
|
}
|
|
401
347
|
finally {
|
|
@@ -577,14 +523,15 @@ function buildThreadStartParams(opts) {
|
|
|
577
523
|
};
|
|
578
524
|
}
|
|
579
525
|
function assembleTurnResult(args) {
|
|
580
|
-
const { askBus, activeTurn, exitCode, message, pid, signal } = args;
|
|
526
|
+
const { askBus, activeTurn, exitCode, message, pid, signal, sensitiveValues = [] } = args;
|
|
581
527
|
const askBatchIds = new Set(activeTurn.askBatchIds);
|
|
582
528
|
const askEvents = askBus
|
|
583
529
|
.snapshot()
|
|
584
530
|
.filter((s) => askBatchIds.has(s.batchId))
|
|
585
531
|
.map((s) => toAskUserToolEvent(s));
|
|
586
|
-
const toolEvents = enrichSkillEvents([...askEvents, ...activeTurn.nonAskToolEvents])
|
|
587
|
-
|
|
532
|
+
const toolEvents = enrichSkillEvents([...askEvents, ...activeTurn.nonAskToolEvents])
|
|
533
|
+
.map((event) => attachToolEventSensitiveValues(event, sensitiveValues));
|
|
534
|
+
const rawOutput = sanitizePersistenceValue(exitCode === 0
|
|
588
535
|
? message
|
|
589
536
|
: [
|
|
590
537
|
message,
|
|
@@ -593,8 +540,8 @@ function assembleTurnResult(args) {
|
|
|
593
540
|
`exitCode=${exitCode}`,
|
|
594
541
|
]
|
|
595
542
|
.filter(Boolean)
|
|
596
|
-
.join(' ');
|
|
597
|
-
return {
|
|
543
|
+
.join(' '), sensitiveValues);
|
|
544
|
+
return attachTurnResultSensitiveValues({
|
|
598
545
|
rawOutput,
|
|
599
546
|
assistantMessage: exitCode === 0 ? message : '',
|
|
600
547
|
visibleAssistantMessage: exitCode === 0 ? message : '',
|
|
@@ -611,5 +558,5 @@ function assembleTurnResult(args) {
|
|
|
611
558
|
},
|
|
612
559
|
}
|
|
613
560
|
: {}),
|
|
614
|
-
};
|
|
561
|
+
}, sensitiveValues);
|
|
615
562
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CodexItem, ItemTiming } from './item-projection.js';
|
|
2
|
+
export interface ItemLifecycleParams {
|
|
3
|
+
item?: CodexItem;
|
|
4
|
+
threadId?: string;
|
|
5
|
+
turnId?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface LifecycleClock {
|
|
8
|
+
wallNow(): number;
|
|
9
|
+
monotonicNow(): number;
|
|
10
|
+
}
|
|
11
|
+
export declare class CodexItemLifecycle {
|
|
12
|
+
private readonly options;
|
|
13
|
+
private turnId?;
|
|
14
|
+
private readonly pending;
|
|
15
|
+
private readonly started;
|
|
16
|
+
private readonly completed;
|
|
17
|
+
constructor(options: {
|
|
18
|
+
threadId: string;
|
|
19
|
+
clock?: LifecycleClock;
|
|
20
|
+
onStarted?: (params: ItemLifecycleParams) => void;
|
|
21
|
+
onCompleted: (item: CodexItem, timing: ItemTiming, params: ItemLifecycleParams) => void;
|
|
22
|
+
onFailure: (message: string) => void;
|
|
23
|
+
});
|
|
24
|
+
receiveStarted(params: ItemLifecycleParams): void;
|
|
25
|
+
receiveCompleted(params: ItemLifecycleParams): void;
|
|
26
|
+
setAuthoritativeTurn(turnId: string): boolean;
|
|
27
|
+
private receive;
|
|
28
|
+
private validateEnvelope;
|
|
29
|
+
private process;
|
|
30
|
+
}
|