@replayci/replay 0.1.14 → 0.1.16
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/index.cjs +227 -35
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +228 -35
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1188,6 +1188,14 @@ function optionalString(value, path) {
|
|
|
1188
1188
|
}
|
|
1189
1189
|
return value;
|
|
1190
1190
|
}
|
|
1191
|
+
function extractReplayTrace(capture) {
|
|
1192
|
+
const replay2 = capture.replay;
|
|
1193
|
+
if (!isRecord(replay2)) return void 0;
|
|
1194
|
+
const trace = replay2.trace;
|
|
1195
|
+
if (!isRecord(trace)) return void 0;
|
|
1196
|
+
if (!Array.isArray(trace.entries)) return void 0;
|
|
1197
|
+
return trace;
|
|
1198
|
+
}
|
|
1191
1199
|
function parseCommonCapture(capture, index, modelId, schemaVersion) {
|
|
1192
1200
|
const toolNames = requireStringArray(capture.tool_names, `captures[${index}].tool_names`);
|
|
1193
1201
|
const primaryToolName = capture.primary_tool_name === void 0 ? toolNames[0] ?? null : nullableString(capture.primary_tool_name, `captures[${index}].primary_tool_name`);
|
|
@@ -1205,7 +1213,8 @@ function parseCommonCapture(capture, index, modelId, schemaVersion) {
|
|
|
1205
1213
|
...capture.validation !== void 0 ? { validation: validateValidation(capture.validation, `captures[${index}].validation`) } : {},
|
|
1206
1214
|
...capture.usage !== void 0 ? { usage: validateUsage(capture.usage, `captures[${index}].usage`) } : {},
|
|
1207
1215
|
latency_ms: requireNonNegativeInt(capture.latency_ms, `captures[${index}].latency_ms`),
|
|
1208
|
-
...sdkSessionId !== void 0 ? { sdk_session_id: sdkSessionId } : {}
|
|
1216
|
+
...sdkSessionId !== void 0 ? { sdk_session_id: sdkSessionId } : {},
|
|
1217
|
+
...extractReplayTrace(capture) !== void 0 ? { replay_trace: extractReplayTrace(capture) } : {}
|
|
1209
1218
|
};
|
|
1210
1219
|
}
|
|
1211
1220
|
function parseLegacyCapturedCall(capture, index) {
|
|
@@ -2320,7 +2329,6 @@ function normalizeInlineContract(input) {
|
|
|
2320
2329
|
if (!tool) {
|
|
2321
2330
|
throw new ReplayConfigurationError("Inline contract is missing required field: tool");
|
|
2322
2331
|
}
|
|
2323
|
-
const assertions = toRecord5(source.assertions);
|
|
2324
2332
|
const expectTools = toStringArray(source.expect_tools);
|
|
2325
2333
|
const expectedToolCalls = toExpectedToolCalls(source.expected_tool_calls);
|
|
2326
2334
|
const contract = {
|
|
@@ -2328,28 +2336,38 @@ function normalizeInlineContract(input) {
|
|
|
2328
2336
|
...toString5(source.tool_schema_hash) ? { tool_schema_hash: toString5(source.tool_schema_hash) } : {},
|
|
2329
2337
|
...isSideEffect(source.side_effect) ? { side_effect: source.side_effect } : {},
|
|
2330
2338
|
...toString5(source.contract_file) ? { contract_file: toString5(source.contract_file) } : {},
|
|
2331
|
-
timeouts
|
|
2332
|
-
total_ms: toNonNegativeNumber(toRecord5(source.timeouts).total_ms, 0)
|
|
2333
|
-
},
|
|
2334
|
-
retries
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
rate_limits: {
|
|
2339
|
-
on_429: {
|
|
2340
|
-
respect_retry_after: toBoolean(toRecord5(toRecord5(source.rate_limits).on_429).respect_retry_after, false),
|
|
2341
|
-
max_sleep_seconds: toNonNegativeNumber(
|
|
2342
|
-
toRecord5(toRecord5(source.rate_limits).on_429).max_sleep_seconds,
|
|
2343
|
-
0
|
|
2344
|
-
)
|
|
2339
|
+
...source.timeouts != null ? {
|
|
2340
|
+
timeouts: { total_ms: toNonNegativeNumber(toRecord5(source.timeouts).total_ms, 0) }
|
|
2341
|
+
} : {},
|
|
2342
|
+
...source.retries != null ? {
|
|
2343
|
+
retries: {
|
|
2344
|
+
max_attempts: Math.max(1, toNonNegativeNumber(toRecord5(source.retries).max_attempts, 1)),
|
|
2345
|
+
retry_on: toStringArray(toRecord5(source.retries).retry_on)
|
|
2345
2346
|
}
|
|
2346
|
-
},
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2347
|
+
} : {},
|
|
2348
|
+
...source.rate_limits != null ? {
|
|
2349
|
+
rate_limits: {
|
|
2350
|
+
on_429: {
|
|
2351
|
+
respect_retry_after: toBoolean(toRecord5(toRecord5(source.rate_limits).on_429).respect_retry_after, false),
|
|
2352
|
+
max_sleep_seconds: toNonNegativeNumber(
|
|
2353
|
+
toRecord5(toRecord5(source.rate_limits).on_429).max_sleep_seconds,
|
|
2354
|
+
0
|
|
2355
|
+
)
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
} : {},
|
|
2359
|
+
...source.assertions != null ? {
|
|
2360
|
+
assertions: {
|
|
2361
|
+
input_invariants: toInvariantArray(toRecord5(source.assertions).input_invariants),
|
|
2362
|
+
output_invariants: toInvariantArray(toRecord5(source.assertions).output_invariants)
|
|
2363
|
+
}
|
|
2364
|
+
} : {},
|
|
2365
|
+
...source.golden_cases != null ? {
|
|
2366
|
+
golden_cases: Array.isArray(source.golden_cases) ? source.golden_cases : []
|
|
2367
|
+
} : {},
|
|
2368
|
+
...source.allowed_errors != null ? {
|
|
2369
|
+
allowed_errors: toStringArray(source.allowed_errors)
|
|
2370
|
+
} : {},
|
|
2353
2371
|
...expectTools.length > 0 ? { expect_tools: expectTools } : {},
|
|
2354
2372
|
...toToolOrder(source.tool_order, expectTools.length > 0) ? {
|
|
2355
2373
|
tool_order: toToolOrder(source.tool_order, expectTools.length > 0)
|
|
@@ -2375,8 +2393,9 @@ function normalizeInlineContract(input) {
|
|
|
2375
2393
|
...Array.isArray(source.schema_derived_exclude) ? { schema_derived_exclude: source.schema_derived_exclude } : {},
|
|
2376
2394
|
...Array.isArray(source.binds) ? { binds: source.binds } : {}
|
|
2377
2395
|
};
|
|
2378
|
-
|
|
2379
|
-
|
|
2396
|
+
const filled = { ...(0, import_contracts_core2.fillContractDefaults)(contract), ...contract.contract_file ? { contract_file: contract.contract_file } : {} };
|
|
2397
|
+
validateSafeRegexes(filled);
|
|
2398
|
+
return filled;
|
|
2380
2399
|
}
|
|
2381
2400
|
function validateContractSet(contracts) {
|
|
2382
2401
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
@@ -2395,11 +2414,11 @@ function validateSafeRegexes(contract) {
|
|
|
2395
2414
|
const invariantGroups = [
|
|
2396
2415
|
{
|
|
2397
2416
|
label: "assertions.input_invariants",
|
|
2398
|
-
invariants: contract.assertions
|
|
2417
|
+
invariants: contract.assertions?.input_invariants ?? []
|
|
2399
2418
|
},
|
|
2400
2419
|
{
|
|
2401
2420
|
label: "assertions.output_invariants",
|
|
2402
|
-
invariants: contract.assertions
|
|
2421
|
+
invariants: contract.assertions?.output_invariants ?? []
|
|
2403
2422
|
}
|
|
2404
2423
|
];
|
|
2405
2424
|
for (const [index, expectedToolCall] of (contract.expected_tool_calls ?? []).entries()) {
|
|
@@ -3002,7 +3021,7 @@ function evaluateExpectTools(contract, toolCalls) {
|
|
|
3002
3021
|
function evaluateOutputInvariants(contract, normalizedResponse) {
|
|
3003
3022
|
const invariantFailures = (0, import_contracts_core3.evaluateInvariants)(
|
|
3004
3023
|
normalizedResponse,
|
|
3005
|
-
contract.assertions
|
|
3024
|
+
contract.assertions?.output_invariants ?? [],
|
|
3006
3025
|
process.env
|
|
3007
3026
|
);
|
|
3008
3027
|
return invariantFailures.map(
|
|
@@ -3052,7 +3071,7 @@ function evaluateArgumentInvariants(contract, toolCalls) {
|
|
|
3052
3071
|
return failures;
|
|
3053
3072
|
}
|
|
3054
3073
|
function mapInvariantFailure(contract, failure, normalizedResponse) {
|
|
3055
|
-
const invariant = findMatchingInvariant(contract.assertions
|
|
3074
|
+
const invariant = findMatchingInvariant(contract.assertions?.output_invariants ?? [], failure);
|
|
3056
3075
|
const lookup = (0, import_contracts_core3.getPathValue)(normalizedResponse, failure.path);
|
|
3057
3076
|
return {
|
|
3058
3077
|
path: failure.path,
|
|
@@ -4634,7 +4653,7 @@ function validateToolResultMessages(messages, contracts, provider) {
|
|
|
4634
4653
|
for (const result of toolResults) {
|
|
4635
4654
|
const contract = contractByTool.get(result.toolName);
|
|
4636
4655
|
if (!contract) continue;
|
|
4637
|
-
const outputInvariants = contract.assertions
|
|
4656
|
+
const outputInvariants = contract.assertions?.output_invariants ?? [];
|
|
4638
4657
|
if (outputInvariants.length === 0) continue;
|
|
4639
4658
|
let parsed;
|
|
4640
4659
|
try {
|
|
@@ -5284,6 +5303,32 @@ var RuntimeClient = class {
|
|
|
5284
5303
|
stateVersion: h.state_version
|
|
5285
5304
|
};
|
|
5286
5305
|
}
|
|
5306
|
+
/**
|
|
5307
|
+
* Fetch governance plan for an agent.
|
|
5308
|
+
* Returns null on 404 (no plan exists).
|
|
5309
|
+
* @see zero-config-governance.md § GET /api/v1/governance/plan
|
|
5310
|
+
*/
|
|
5311
|
+
async fetchGovernancePlan(agent, environment) {
|
|
5312
|
+
const env = environment ?? "development";
|
|
5313
|
+
try {
|
|
5314
|
+
const data = await this.get(
|
|
5315
|
+
`/api/v1/governance/plan?agent=${encodeURIComponent(agent)}&environment=${encodeURIComponent(env)}`
|
|
5316
|
+
);
|
|
5317
|
+
return {
|
|
5318
|
+
status: data.status,
|
|
5319
|
+
compiledSession: data.compiled_session,
|
|
5320
|
+
compiledHash: data.compiled_hash,
|
|
5321
|
+
observations: data.observations,
|
|
5322
|
+
confidence: data.confidence,
|
|
5323
|
+
version: data.version
|
|
5324
|
+
};
|
|
5325
|
+
} catch (err) {
|
|
5326
|
+
if (err instanceof RuntimeClientError && err.httpStatus === 404) {
|
|
5327
|
+
return null;
|
|
5328
|
+
}
|
|
5329
|
+
throw err;
|
|
5330
|
+
}
|
|
5331
|
+
}
|
|
5287
5332
|
getHealth() {
|
|
5288
5333
|
return {
|
|
5289
5334
|
circuitOpen: this.now() < this.circuitOpenUntil,
|
|
@@ -5450,9 +5495,14 @@ function replay(client, opts = {}) {
|
|
|
5450
5495
|
return createInactiveSession(client, sessionId, "Client already has an active observe() or replay() attachment");
|
|
5451
5496
|
}
|
|
5452
5497
|
let contracts;
|
|
5498
|
+
let zeroConfigMode = false;
|
|
5453
5499
|
try {
|
|
5454
5500
|
contracts = resolveContracts(opts);
|
|
5455
5501
|
} catch (err) {
|
|
5502
|
+
const apiKeyForGov = resolveApiKey2(opts);
|
|
5503
|
+
if (apiKeyForGov && !opts.contracts && !opts.contractsDir) {
|
|
5504
|
+
return createGovernanceSession(client, sessionId, agent, provider, apiKeyForGov, opts, diagnostics);
|
|
5505
|
+
}
|
|
5456
5506
|
const detail = err instanceof Error ? err.message : "Failed to load contracts";
|
|
5457
5507
|
emitDiagnostic2(diagnostics, { type: "replay_compile_error", details: detail });
|
|
5458
5508
|
return createBlockingInactiveSession(client, sessionId, detail);
|
|
@@ -5660,6 +5710,7 @@ function replay(client, opts = {}) {
|
|
|
5660
5710
|
let manualFilter = null;
|
|
5661
5711
|
const deferredReceipts = /* @__PURE__ */ new Map();
|
|
5662
5712
|
let deferredPhase = null;
|
|
5713
|
+
const hasWrappedTools = opts.tools != null && Object.keys(opts.tools).length > 0;
|
|
5663
5714
|
const contractLimits = resolveSessionLimits(contracts);
|
|
5664
5715
|
const compiledLimits = compiledSession?.sessionLimits;
|
|
5665
5716
|
const mergedLimits = { ...contractLimits ?? {}, ...compiledLimits ?? {} };
|
|
@@ -6619,9 +6670,29 @@ function replay(client, opts = {}) {
|
|
|
6619
6670
|
}
|
|
6620
6671
|
}
|
|
6621
6672
|
}
|
|
6673
|
+
const compatHasPhaseTransition = !!(phaseResult?.legal && phaseResult.newPhase !== sessionState.currentPhase);
|
|
6674
|
+
const compatShouldDefer = hasWrappedTools && compatHasPhaseTransition;
|
|
6622
6675
|
const prevVersion = sessionState.stateVersion;
|
|
6623
|
-
sessionState = finalizeExecutedStep(
|
|
6676
|
+
sessionState = finalizeExecutedStep(
|
|
6677
|
+
sessionState,
|
|
6678
|
+
completedStep,
|
|
6679
|
+
contracts,
|
|
6680
|
+
compiledSession,
|
|
6681
|
+
compatShouldDefer ? { deferPhase: true } : void 0
|
|
6682
|
+
);
|
|
6624
6683
|
syncStateToStore(prevVersion, sessionState);
|
|
6684
|
+
if (compatShouldDefer && compiledSession && phaseResult) {
|
|
6685
|
+
const advancingTools = /* @__PURE__ */ new Set();
|
|
6686
|
+
for (const tc of toolCalls) {
|
|
6687
|
+
const contract = compiledSession.perToolContracts.get(tc.name);
|
|
6688
|
+
if (contract?.transitions?.advances_to === phaseResult.newPhase) {
|
|
6689
|
+
advancingTools.add(tc.name);
|
|
6690
|
+
}
|
|
6691
|
+
}
|
|
6692
|
+
if (advancingTools.size > 0 && phaseResult.newPhase != null) {
|
|
6693
|
+
deferredPhase = { newPhase: phaseResult.newPhase, toolNames: advancingTools };
|
|
6694
|
+
}
|
|
6695
|
+
}
|
|
6625
6696
|
}
|
|
6626
6697
|
if (advisoryDecision.action === "block") {
|
|
6627
6698
|
sessionState = recordDecisionOutcome(sessionState, "blocked");
|
|
@@ -6712,7 +6783,7 @@ function replay(client, opts = {}) {
|
|
|
6712
6783
|
}
|
|
6713
6784
|
}
|
|
6714
6785
|
const hasPhaseTransition = phaseResult?.legal && phaseResult.newPhase !== sessionState.currentPhase;
|
|
6715
|
-
const shouldDeferPhase =
|
|
6786
|
+
const shouldDeferPhase = hasWrappedTools && !!hasPhaseTransition;
|
|
6716
6787
|
const prevVersionAllow = sessionState.stateVersion;
|
|
6717
6788
|
sessionState = finalizeExecutedStep(
|
|
6718
6789
|
sessionState,
|
|
@@ -7355,7 +7426,7 @@ function validateResponse2(response, toolCalls, contracts, requestToolNames, unm
|
|
|
7355
7426
|
}
|
|
7356
7427
|
}
|
|
7357
7428
|
for (const contract of matched) {
|
|
7358
|
-
const outputInvariants = contract.assertions
|
|
7429
|
+
const outputInvariants = contract.assertions?.output_invariants ?? [];
|
|
7359
7430
|
if (outputInvariants.length > 0) {
|
|
7360
7431
|
const normalizedResponse = buildNormalizedResponse(response, toolCalls);
|
|
7361
7432
|
const result = (0, import_contracts_core7.evaluateInvariants)(normalizedResponse, outputInvariants, process.env);
|
|
@@ -7523,8 +7594,9 @@ function evaluateInputInvariants(request, contracts) {
|
|
|
7523
7594
|
const requestToolSet = new Set(requestToolNames);
|
|
7524
7595
|
for (const contract of contracts) {
|
|
7525
7596
|
if (!requestToolSet.has(contract.tool)) continue;
|
|
7526
|
-
|
|
7527
|
-
|
|
7597
|
+
const inputInvariants = contract.assertions?.input_invariants ?? [];
|
|
7598
|
+
if (inputInvariants.length === 0) continue;
|
|
7599
|
+
const result = (0, import_contracts_core7.evaluateInvariants)(request, inputInvariants, process.env);
|
|
7528
7600
|
for (const failure of result) {
|
|
7529
7601
|
failures.push({
|
|
7530
7602
|
path: failure.path,
|
|
@@ -7899,6 +7971,126 @@ function createBlockingInactiveSession(client, sessionId, detail, configError) {
|
|
|
7899
7971
|
handoff: () => Promise.resolve(null)
|
|
7900
7972
|
};
|
|
7901
7973
|
}
|
|
7974
|
+
function resolveGovernanceEnvironment(opts) {
|
|
7975
|
+
if (opts.environment) return opts.environment;
|
|
7976
|
+
const envVar = typeof process !== "undefined" ? process.env.REPLAYCI_ENVIRONMENT : void 0;
|
|
7977
|
+
if (envVar === "staging") return "staging";
|
|
7978
|
+
if (envVar === "production") return "production";
|
|
7979
|
+
if (envVar === "development") return "development";
|
|
7980
|
+
const nodeEnv = typeof process !== "undefined" ? process.env.NODE_ENV : void 0;
|
|
7981
|
+
if (nodeEnv === "production") return "production";
|
|
7982
|
+
return "development";
|
|
7983
|
+
}
|
|
7984
|
+
function governanceProtectionLevel(env) {
|
|
7985
|
+
switch (env) {
|
|
7986
|
+
case "production":
|
|
7987
|
+
return "govern";
|
|
7988
|
+
case "staging":
|
|
7989
|
+
return "protect";
|
|
7990
|
+
default:
|
|
7991
|
+
return "monitor";
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7994
|
+
function createGovernanceSession(client, sessionId, agent, provider, apiKey, opts, diagnostics) {
|
|
7995
|
+
const environment = resolveGovernanceEnvironment(opts);
|
|
7996
|
+
const protLevel = governanceProtectionLevel(environment);
|
|
7997
|
+
const runtimeClient = new RuntimeClient({
|
|
7998
|
+
apiKey,
|
|
7999
|
+
apiUrl: opts.runtimeUrl
|
|
8000
|
+
});
|
|
8001
|
+
let governancePlan;
|
|
8002
|
+
let planFetchPromise = null;
|
|
8003
|
+
let planFetchDone = false;
|
|
8004
|
+
let planFetchError = null;
|
|
8005
|
+
planFetchPromise = runtimeClient.fetchGovernancePlan(agent, environment).then((result) => {
|
|
8006
|
+
governancePlan = result;
|
|
8007
|
+
planFetchDone = true;
|
|
8008
|
+
}).catch((err) => {
|
|
8009
|
+
planFetchDone = true;
|
|
8010
|
+
planFetchError = err instanceof Error ? err.message : String(err);
|
|
8011
|
+
governancePlan = null;
|
|
8012
|
+
});
|
|
8013
|
+
const captureBuffer = new CaptureBuffer({
|
|
8014
|
+
apiKey,
|
|
8015
|
+
endpoint: opts.runtimeUrl
|
|
8016
|
+
});
|
|
8017
|
+
registerBeforeExit(captureBuffer);
|
|
8018
|
+
const terminalInfo = resolveTerminal(client, provider);
|
|
8019
|
+
if (!terminalInfo) {
|
|
8020
|
+
emitDiagnostic2(diagnostics, { type: "replay_inactive", reason: "unsupported_client" });
|
|
8021
|
+
return createInactiveSession(client, sessionId, "Could not resolve terminal resource");
|
|
8022
|
+
}
|
|
8023
|
+
const { terminal, originalCreate } = terminalInfo;
|
|
8024
|
+
const patchedCreate = async function(...args) {
|
|
8025
|
+
if (!planFetchDone && planFetchPromise) {
|
|
8026
|
+
await planFetchPromise;
|
|
8027
|
+
}
|
|
8028
|
+
const hasApprovedPlan = governancePlan && (governancePlan.status === "approved" || governancePlan.status === "enforcing") && governancePlan.compiledSession;
|
|
8029
|
+
if (hasApprovedPlan) {
|
|
8030
|
+
}
|
|
8031
|
+
const result = await originalCreate.apply(this, args);
|
|
8032
|
+
try {
|
|
8033
|
+
const toolCalls = extractToolCalls(result, provider);
|
|
8034
|
+
const usage = extractUsage(result, provider);
|
|
8035
|
+
const requestArg = args[0] && typeof args[0] === "object" ? args[0] : {};
|
|
8036
|
+
captureBuffer.push({
|
|
8037
|
+
schema_version: CAPTURE_SCHEMA_VERSION_CURRENT,
|
|
8038
|
+
agent,
|
|
8039
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8040
|
+
provider,
|
|
8041
|
+
model_id: requestArg.model ?? "unknown",
|
|
8042
|
+
primary_tool_name: toolCalls[0]?.name ?? null,
|
|
8043
|
+
tool_names: toolCalls.map((tc) => tc.name),
|
|
8044
|
+
request: requestArg,
|
|
8045
|
+
response: result,
|
|
8046
|
+
usage,
|
|
8047
|
+
latency_ms: 0,
|
|
8048
|
+
sdk_session_id: sessionId
|
|
8049
|
+
});
|
|
8050
|
+
} catch {
|
|
8051
|
+
}
|
|
8052
|
+
return result;
|
|
8053
|
+
};
|
|
8054
|
+
terminal[terminalInfo.methodName] = patchedCreate;
|
|
8055
|
+
setReplayAttached(client);
|
|
8056
|
+
return {
|
|
8057
|
+
client,
|
|
8058
|
+
flush: () => captureBuffer.flush(),
|
|
8059
|
+
restore() {
|
|
8060
|
+
terminal[terminalInfo.methodName] = originalCreate;
|
|
8061
|
+
},
|
|
8062
|
+
kill() {
|
|
8063
|
+
},
|
|
8064
|
+
getHealth: () => ({
|
|
8065
|
+
status: "healthy",
|
|
8066
|
+
authorityState: "active",
|
|
8067
|
+
protectionLevel: protLevel,
|
|
8068
|
+
durability: "inactive",
|
|
8069
|
+
tier: "compat",
|
|
8070
|
+
compatEnforcement: "protective",
|
|
8071
|
+
cluster_detected: false,
|
|
8072
|
+
bypass_detected: false,
|
|
8073
|
+
totalSteps: 0,
|
|
8074
|
+
totalBlocks: 0,
|
|
8075
|
+
totalErrors: 0,
|
|
8076
|
+
killed: false,
|
|
8077
|
+
shadowEvaluations: 0
|
|
8078
|
+
}),
|
|
8079
|
+
getState: () => EMPTY_STATE_SNAPSHOT,
|
|
8080
|
+
getLastNarrowing: () => null,
|
|
8081
|
+
getLastShadowDelta: () => null,
|
|
8082
|
+
getLastTrace: () => null,
|
|
8083
|
+
narrow() {
|
|
8084
|
+
},
|
|
8085
|
+
widen() {
|
|
8086
|
+
},
|
|
8087
|
+
addLabel() {
|
|
8088
|
+
},
|
|
8089
|
+
tools: {},
|
|
8090
|
+
getWorkflowState: () => Promise.resolve(null),
|
|
8091
|
+
handoff: () => Promise.resolve(null)
|
|
8092
|
+
};
|
|
8093
|
+
}
|
|
7902
8094
|
function toNarrowingSnapshot(result) {
|
|
7903
8095
|
if (!result || result.removed.length === 0) return null;
|
|
7904
8096
|
return {
|
package/dist/index.d.cts
CHANGED
|
@@ -587,6 +587,15 @@ type ReplayOptions = {
|
|
|
587
587
|
runtimeUrl?: string;
|
|
588
588
|
captureLevel?: CapturePrivacyTier;
|
|
589
589
|
diagnostics?: (event: ObserveDiagnosticEvent | ReplayDiagnosticEvent) => void;
|
|
590
|
+
/**
|
|
591
|
+
* Explicit environment for zero-config governance mode selection.
|
|
592
|
+
* - "development" → monitor mode (log, don't block)
|
|
593
|
+
* - "staging" → protect mode (warn, don't block)
|
|
594
|
+
* - "production" → govern mode (block violations)
|
|
595
|
+
* Falls back to NODE_ENV if not set.
|
|
596
|
+
* @see zero-config-governance.md § Environment promotion
|
|
597
|
+
*/
|
|
598
|
+
environment?: "development" | "staging" | "production";
|
|
590
599
|
};
|
|
591
600
|
/**
|
|
592
601
|
* Raw tool executor provided by the user in `replay()` options.
|
|
@@ -1305,6 +1314,14 @@ type HandoffOfferResult = {
|
|
|
1305
1314
|
eventSeq: number;
|
|
1306
1315
|
stateVersion: number;
|
|
1307
1316
|
};
|
|
1317
|
+
type GovernancePlanResult = {
|
|
1318
|
+
status: string;
|
|
1319
|
+
compiledSession?: unknown;
|
|
1320
|
+
compiledHash?: string;
|
|
1321
|
+
observations?: number;
|
|
1322
|
+
confidence?: string;
|
|
1323
|
+
version?: number;
|
|
1324
|
+
};
|
|
1308
1325
|
type RuntimeClientHealth = {
|
|
1309
1326
|
circuitOpen: boolean;
|
|
1310
1327
|
failureCount: number;
|
|
@@ -1336,6 +1353,12 @@ declare class RuntimeClient {
|
|
|
1336
1353
|
getWorkflowState(workflowId: string): Promise<WorkflowStateResult>;
|
|
1337
1354
|
/** v4: Offer a handoff from a session. */
|
|
1338
1355
|
offerHandoff(input: HandoffOfferInput): Promise<HandoffOfferResult>;
|
|
1356
|
+
/**
|
|
1357
|
+
* Fetch governance plan for an agent.
|
|
1358
|
+
* Returns null on 404 (no plan exists).
|
|
1359
|
+
* @see zero-config-governance.md § GET /api/v1/governance/plan
|
|
1360
|
+
*/
|
|
1361
|
+
fetchGovernancePlan(agent: string, environment?: string): Promise<GovernancePlanResult | null>;
|
|
1339
1362
|
getHealth(): RuntimeClientHealth;
|
|
1340
1363
|
isCircuitOpen(): boolean;
|
|
1341
1364
|
private get;
|
package/dist/index.d.ts
CHANGED
|
@@ -587,6 +587,15 @@ type ReplayOptions = {
|
|
|
587
587
|
runtimeUrl?: string;
|
|
588
588
|
captureLevel?: CapturePrivacyTier;
|
|
589
589
|
diagnostics?: (event: ObserveDiagnosticEvent | ReplayDiagnosticEvent) => void;
|
|
590
|
+
/**
|
|
591
|
+
* Explicit environment for zero-config governance mode selection.
|
|
592
|
+
* - "development" → monitor mode (log, don't block)
|
|
593
|
+
* - "staging" → protect mode (warn, don't block)
|
|
594
|
+
* - "production" → govern mode (block violations)
|
|
595
|
+
* Falls back to NODE_ENV if not set.
|
|
596
|
+
* @see zero-config-governance.md § Environment promotion
|
|
597
|
+
*/
|
|
598
|
+
environment?: "development" | "staging" | "production";
|
|
590
599
|
};
|
|
591
600
|
/**
|
|
592
601
|
* Raw tool executor provided by the user in `replay()` options.
|
|
@@ -1305,6 +1314,14 @@ type HandoffOfferResult = {
|
|
|
1305
1314
|
eventSeq: number;
|
|
1306
1315
|
stateVersion: number;
|
|
1307
1316
|
};
|
|
1317
|
+
type GovernancePlanResult = {
|
|
1318
|
+
status: string;
|
|
1319
|
+
compiledSession?: unknown;
|
|
1320
|
+
compiledHash?: string;
|
|
1321
|
+
observations?: number;
|
|
1322
|
+
confidence?: string;
|
|
1323
|
+
version?: number;
|
|
1324
|
+
};
|
|
1308
1325
|
type RuntimeClientHealth = {
|
|
1309
1326
|
circuitOpen: boolean;
|
|
1310
1327
|
failureCount: number;
|
|
@@ -1336,6 +1353,12 @@ declare class RuntimeClient {
|
|
|
1336
1353
|
getWorkflowState(workflowId: string): Promise<WorkflowStateResult>;
|
|
1337
1354
|
/** v4: Offer a handoff from a session. */
|
|
1338
1355
|
offerHandoff(input: HandoffOfferInput): Promise<HandoffOfferResult>;
|
|
1356
|
+
/**
|
|
1357
|
+
* Fetch governance plan for an agent.
|
|
1358
|
+
* Returns null on 404 (no plan exists).
|
|
1359
|
+
* @see zero-config-governance.md § GET /api/v1/governance/plan
|
|
1360
|
+
*/
|
|
1361
|
+
fetchGovernancePlan(agent: string, environment?: string): Promise<GovernancePlanResult | null>;
|
|
1339
1362
|
getHealth(): RuntimeClientHealth;
|
|
1340
1363
|
isCircuitOpen(): boolean;
|
|
1341
1364
|
private get;
|
package/dist/index.js
CHANGED
|
@@ -1142,6 +1142,14 @@ function optionalString(value, path) {
|
|
|
1142
1142
|
}
|
|
1143
1143
|
return value;
|
|
1144
1144
|
}
|
|
1145
|
+
function extractReplayTrace(capture) {
|
|
1146
|
+
const replay2 = capture.replay;
|
|
1147
|
+
if (!isRecord(replay2)) return void 0;
|
|
1148
|
+
const trace = replay2.trace;
|
|
1149
|
+
if (!isRecord(trace)) return void 0;
|
|
1150
|
+
if (!Array.isArray(trace.entries)) return void 0;
|
|
1151
|
+
return trace;
|
|
1152
|
+
}
|
|
1145
1153
|
function parseCommonCapture(capture, index, modelId, schemaVersion) {
|
|
1146
1154
|
const toolNames = requireStringArray(capture.tool_names, `captures[${index}].tool_names`);
|
|
1147
1155
|
const primaryToolName = capture.primary_tool_name === void 0 ? toolNames[0] ?? null : nullableString(capture.primary_tool_name, `captures[${index}].primary_tool_name`);
|
|
@@ -1159,7 +1167,8 @@ function parseCommonCapture(capture, index, modelId, schemaVersion) {
|
|
|
1159
1167
|
...capture.validation !== void 0 ? { validation: validateValidation(capture.validation, `captures[${index}].validation`) } : {},
|
|
1160
1168
|
...capture.usage !== void 0 ? { usage: validateUsage(capture.usage, `captures[${index}].usage`) } : {},
|
|
1161
1169
|
latency_ms: requireNonNegativeInt(capture.latency_ms, `captures[${index}].latency_ms`),
|
|
1162
|
-
...sdkSessionId !== void 0 ? { sdk_session_id: sdkSessionId } : {}
|
|
1170
|
+
...sdkSessionId !== void 0 ? { sdk_session_id: sdkSessionId } : {},
|
|
1171
|
+
...extractReplayTrace(capture) !== void 0 ? { replay_trace: extractReplayTrace(capture) } : {}
|
|
1163
1172
|
};
|
|
1164
1173
|
}
|
|
1165
1174
|
function parseLegacyCapturedCall(capture, index) {
|
|
@@ -2173,6 +2182,7 @@ import {
|
|
|
2173
2182
|
|
|
2174
2183
|
// src/contracts.ts
|
|
2175
2184
|
import {
|
|
2185
|
+
fillContractDefaults,
|
|
2176
2186
|
hashToolSchema,
|
|
2177
2187
|
loadContractSync,
|
|
2178
2188
|
normalizeToolArray as normalizeToolArray2
|
|
@@ -2300,7 +2310,6 @@ function normalizeInlineContract(input) {
|
|
|
2300
2310
|
if (!tool) {
|
|
2301
2311
|
throw new ReplayConfigurationError("Inline contract is missing required field: tool");
|
|
2302
2312
|
}
|
|
2303
|
-
const assertions = toRecord5(source.assertions);
|
|
2304
2313
|
const expectTools = toStringArray(source.expect_tools);
|
|
2305
2314
|
const expectedToolCalls = toExpectedToolCalls(source.expected_tool_calls);
|
|
2306
2315
|
const contract = {
|
|
@@ -2308,28 +2317,38 @@ function normalizeInlineContract(input) {
|
|
|
2308
2317
|
...toString5(source.tool_schema_hash) ? { tool_schema_hash: toString5(source.tool_schema_hash) } : {},
|
|
2309
2318
|
...isSideEffect(source.side_effect) ? { side_effect: source.side_effect } : {},
|
|
2310
2319
|
...toString5(source.contract_file) ? { contract_file: toString5(source.contract_file) } : {},
|
|
2311
|
-
timeouts
|
|
2312
|
-
total_ms: toNonNegativeNumber(toRecord5(source.timeouts).total_ms, 0)
|
|
2313
|
-
},
|
|
2314
|
-
retries
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
rate_limits: {
|
|
2319
|
-
on_429: {
|
|
2320
|
-
respect_retry_after: toBoolean(toRecord5(toRecord5(source.rate_limits).on_429).respect_retry_after, false),
|
|
2321
|
-
max_sleep_seconds: toNonNegativeNumber(
|
|
2322
|
-
toRecord5(toRecord5(source.rate_limits).on_429).max_sleep_seconds,
|
|
2323
|
-
0
|
|
2324
|
-
)
|
|
2320
|
+
...source.timeouts != null ? {
|
|
2321
|
+
timeouts: { total_ms: toNonNegativeNumber(toRecord5(source.timeouts).total_ms, 0) }
|
|
2322
|
+
} : {},
|
|
2323
|
+
...source.retries != null ? {
|
|
2324
|
+
retries: {
|
|
2325
|
+
max_attempts: Math.max(1, toNonNegativeNumber(toRecord5(source.retries).max_attempts, 1)),
|
|
2326
|
+
retry_on: toStringArray(toRecord5(source.retries).retry_on)
|
|
2325
2327
|
}
|
|
2326
|
-
},
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2328
|
+
} : {},
|
|
2329
|
+
...source.rate_limits != null ? {
|
|
2330
|
+
rate_limits: {
|
|
2331
|
+
on_429: {
|
|
2332
|
+
respect_retry_after: toBoolean(toRecord5(toRecord5(source.rate_limits).on_429).respect_retry_after, false),
|
|
2333
|
+
max_sleep_seconds: toNonNegativeNumber(
|
|
2334
|
+
toRecord5(toRecord5(source.rate_limits).on_429).max_sleep_seconds,
|
|
2335
|
+
0
|
|
2336
|
+
)
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
} : {},
|
|
2340
|
+
...source.assertions != null ? {
|
|
2341
|
+
assertions: {
|
|
2342
|
+
input_invariants: toInvariantArray(toRecord5(source.assertions).input_invariants),
|
|
2343
|
+
output_invariants: toInvariantArray(toRecord5(source.assertions).output_invariants)
|
|
2344
|
+
}
|
|
2345
|
+
} : {},
|
|
2346
|
+
...source.golden_cases != null ? {
|
|
2347
|
+
golden_cases: Array.isArray(source.golden_cases) ? source.golden_cases : []
|
|
2348
|
+
} : {},
|
|
2349
|
+
...source.allowed_errors != null ? {
|
|
2350
|
+
allowed_errors: toStringArray(source.allowed_errors)
|
|
2351
|
+
} : {},
|
|
2333
2352
|
...expectTools.length > 0 ? { expect_tools: expectTools } : {},
|
|
2334
2353
|
...toToolOrder(source.tool_order, expectTools.length > 0) ? {
|
|
2335
2354
|
tool_order: toToolOrder(source.tool_order, expectTools.length > 0)
|
|
@@ -2355,8 +2374,9 @@ function normalizeInlineContract(input) {
|
|
|
2355
2374
|
...Array.isArray(source.schema_derived_exclude) ? { schema_derived_exclude: source.schema_derived_exclude } : {},
|
|
2356
2375
|
...Array.isArray(source.binds) ? { binds: source.binds } : {}
|
|
2357
2376
|
};
|
|
2358
|
-
|
|
2359
|
-
|
|
2377
|
+
const filled = { ...fillContractDefaults(contract), ...contract.contract_file ? { contract_file: contract.contract_file } : {} };
|
|
2378
|
+
validateSafeRegexes(filled);
|
|
2379
|
+
return filled;
|
|
2360
2380
|
}
|
|
2361
2381
|
function validateContractSet(contracts) {
|
|
2362
2382
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
@@ -2375,11 +2395,11 @@ function validateSafeRegexes(contract) {
|
|
|
2375
2395
|
const invariantGroups = [
|
|
2376
2396
|
{
|
|
2377
2397
|
label: "assertions.input_invariants",
|
|
2378
|
-
invariants: contract.assertions
|
|
2398
|
+
invariants: contract.assertions?.input_invariants ?? []
|
|
2379
2399
|
},
|
|
2380
2400
|
{
|
|
2381
2401
|
label: "assertions.output_invariants",
|
|
2382
|
-
invariants: contract.assertions
|
|
2402
|
+
invariants: contract.assertions?.output_invariants ?? []
|
|
2383
2403
|
}
|
|
2384
2404
|
];
|
|
2385
2405
|
for (const [index, expectedToolCall] of (contract.expected_tool_calls ?? []).entries()) {
|
|
@@ -2982,7 +3002,7 @@ function evaluateExpectTools(contract, toolCalls) {
|
|
|
2982
3002
|
function evaluateOutputInvariants(contract, normalizedResponse) {
|
|
2983
3003
|
const invariantFailures = evaluateInvariants(
|
|
2984
3004
|
normalizedResponse,
|
|
2985
|
-
contract.assertions
|
|
3005
|
+
contract.assertions?.output_invariants ?? [],
|
|
2986
3006
|
process.env
|
|
2987
3007
|
);
|
|
2988
3008
|
return invariantFailures.map(
|
|
@@ -3032,7 +3052,7 @@ function evaluateArgumentInvariants(contract, toolCalls) {
|
|
|
3032
3052
|
return failures;
|
|
3033
3053
|
}
|
|
3034
3054
|
function mapInvariantFailure(contract, failure, normalizedResponse) {
|
|
3035
|
-
const invariant = findMatchingInvariant(contract.assertions
|
|
3055
|
+
const invariant = findMatchingInvariant(contract.assertions?.output_invariants ?? [], failure);
|
|
3036
3056
|
const lookup = getPathValue(normalizedResponse, failure.path);
|
|
3037
3057
|
return {
|
|
3038
3058
|
path: failure.path,
|
|
@@ -4625,7 +4645,7 @@ function validateToolResultMessages(messages, contracts, provider) {
|
|
|
4625
4645
|
for (const result of toolResults) {
|
|
4626
4646
|
const contract = contractByTool.get(result.toolName);
|
|
4627
4647
|
if (!contract) continue;
|
|
4628
|
-
const outputInvariants = contract.assertions
|
|
4648
|
+
const outputInvariants = contract.assertions?.output_invariants ?? [];
|
|
4629
4649
|
if (outputInvariants.length === 0) continue;
|
|
4630
4650
|
let parsed;
|
|
4631
4651
|
try {
|
|
@@ -5275,6 +5295,32 @@ var RuntimeClient = class {
|
|
|
5275
5295
|
stateVersion: h.state_version
|
|
5276
5296
|
};
|
|
5277
5297
|
}
|
|
5298
|
+
/**
|
|
5299
|
+
* Fetch governance plan for an agent.
|
|
5300
|
+
* Returns null on 404 (no plan exists).
|
|
5301
|
+
* @see zero-config-governance.md § GET /api/v1/governance/plan
|
|
5302
|
+
*/
|
|
5303
|
+
async fetchGovernancePlan(agent, environment) {
|
|
5304
|
+
const env = environment ?? "development";
|
|
5305
|
+
try {
|
|
5306
|
+
const data = await this.get(
|
|
5307
|
+
`/api/v1/governance/plan?agent=${encodeURIComponent(agent)}&environment=${encodeURIComponent(env)}`
|
|
5308
|
+
);
|
|
5309
|
+
return {
|
|
5310
|
+
status: data.status,
|
|
5311
|
+
compiledSession: data.compiled_session,
|
|
5312
|
+
compiledHash: data.compiled_hash,
|
|
5313
|
+
observations: data.observations,
|
|
5314
|
+
confidence: data.confidence,
|
|
5315
|
+
version: data.version
|
|
5316
|
+
};
|
|
5317
|
+
} catch (err) {
|
|
5318
|
+
if (err instanceof RuntimeClientError && err.httpStatus === 404) {
|
|
5319
|
+
return null;
|
|
5320
|
+
}
|
|
5321
|
+
throw err;
|
|
5322
|
+
}
|
|
5323
|
+
}
|
|
5278
5324
|
getHealth() {
|
|
5279
5325
|
return {
|
|
5280
5326
|
circuitOpen: this.now() < this.circuitOpenUntil,
|
|
@@ -5441,9 +5487,14 @@ function replay(client, opts = {}) {
|
|
|
5441
5487
|
return createInactiveSession(client, sessionId, "Client already has an active observe() or replay() attachment");
|
|
5442
5488
|
}
|
|
5443
5489
|
let contracts;
|
|
5490
|
+
let zeroConfigMode = false;
|
|
5444
5491
|
try {
|
|
5445
5492
|
contracts = resolveContracts(opts);
|
|
5446
5493
|
} catch (err) {
|
|
5494
|
+
const apiKeyForGov = resolveApiKey2(opts);
|
|
5495
|
+
if (apiKeyForGov && !opts.contracts && !opts.contractsDir) {
|
|
5496
|
+
return createGovernanceSession(client, sessionId, agent, provider, apiKeyForGov, opts, diagnostics);
|
|
5497
|
+
}
|
|
5447
5498
|
const detail = err instanceof Error ? err.message : "Failed to load contracts";
|
|
5448
5499
|
emitDiagnostic2(diagnostics, { type: "replay_compile_error", details: detail });
|
|
5449
5500
|
return createBlockingInactiveSession(client, sessionId, detail);
|
|
@@ -5651,6 +5702,7 @@ function replay(client, opts = {}) {
|
|
|
5651
5702
|
let manualFilter = null;
|
|
5652
5703
|
const deferredReceipts = /* @__PURE__ */ new Map();
|
|
5653
5704
|
let deferredPhase = null;
|
|
5705
|
+
const hasWrappedTools = opts.tools != null && Object.keys(opts.tools).length > 0;
|
|
5654
5706
|
const contractLimits = resolveSessionLimits(contracts);
|
|
5655
5707
|
const compiledLimits = compiledSession?.sessionLimits;
|
|
5656
5708
|
const mergedLimits = { ...contractLimits ?? {}, ...compiledLimits ?? {} };
|
|
@@ -6610,9 +6662,29 @@ function replay(client, opts = {}) {
|
|
|
6610
6662
|
}
|
|
6611
6663
|
}
|
|
6612
6664
|
}
|
|
6665
|
+
const compatHasPhaseTransition = !!(phaseResult?.legal && phaseResult.newPhase !== sessionState.currentPhase);
|
|
6666
|
+
const compatShouldDefer = hasWrappedTools && compatHasPhaseTransition;
|
|
6613
6667
|
const prevVersion = sessionState.stateVersion;
|
|
6614
|
-
sessionState = finalizeExecutedStep(
|
|
6668
|
+
sessionState = finalizeExecutedStep(
|
|
6669
|
+
sessionState,
|
|
6670
|
+
completedStep,
|
|
6671
|
+
contracts,
|
|
6672
|
+
compiledSession,
|
|
6673
|
+
compatShouldDefer ? { deferPhase: true } : void 0
|
|
6674
|
+
);
|
|
6615
6675
|
syncStateToStore(prevVersion, sessionState);
|
|
6676
|
+
if (compatShouldDefer && compiledSession && phaseResult) {
|
|
6677
|
+
const advancingTools = /* @__PURE__ */ new Set();
|
|
6678
|
+
for (const tc of toolCalls) {
|
|
6679
|
+
const contract = compiledSession.perToolContracts.get(tc.name);
|
|
6680
|
+
if (contract?.transitions?.advances_to === phaseResult.newPhase) {
|
|
6681
|
+
advancingTools.add(tc.name);
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
6684
|
+
if (advancingTools.size > 0 && phaseResult.newPhase != null) {
|
|
6685
|
+
deferredPhase = { newPhase: phaseResult.newPhase, toolNames: advancingTools };
|
|
6686
|
+
}
|
|
6687
|
+
}
|
|
6616
6688
|
}
|
|
6617
6689
|
if (advisoryDecision.action === "block") {
|
|
6618
6690
|
sessionState = recordDecisionOutcome(sessionState, "blocked");
|
|
@@ -6703,7 +6775,7 @@ function replay(client, opts = {}) {
|
|
|
6703
6775
|
}
|
|
6704
6776
|
}
|
|
6705
6777
|
const hasPhaseTransition = phaseResult?.legal && phaseResult.newPhase !== sessionState.currentPhase;
|
|
6706
|
-
const shouldDeferPhase =
|
|
6778
|
+
const shouldDeferPhase = hasWrappedTools && !!hasPhaseTransition;
|
|
6707
6779
|
const prevVersionAllow = sessionState.stateVersion;
|
|
6708
6780
|
sessionState = finalizeExecutedStep(
|
|
6709
6781
|
sessionState,
|
|
@@ -7346,7 +7418,7 @@ function validateResponse2(response, toolCalls, contracts, requestToolNames, unm
|
|
|
7346
7418
|
}
|
|
7347
7419
|
}
|
|
7348
7420
|
for (const contract of matched) {
|
|
7349
|
-
const outputInvariants = contract.assertions
|
|
7421
|
+
const outputInvariants = contract.assertions?.output_invariants ?? [];
|
|
7350
7422
|
if (outputInvariants.length > 0) {
|
|
7351
7423
|
const normalizedResponse = buildNormalizedResponse(response, toolCalls);
|
|
7352
7424
|
const result = evaluateInvariants4(normalizedResponse, outputInvariants, process.env);
|
|
@@ -7514,8 +7586,9 @@ function evaluateInputInvariants(request, contracts) {
|
|
|
7514
7586
|
const requestToolSet = new Set(requestToolNames);
|
|
7515
7587
|
for (const contract of contracts) {
|
|
7516
7588
|
if (!requestToolSet.has(contract.tool)) continue;
|
|
7517
|
-
|
|
7518
|
-
|
|
7589
|
+
const inputInvariants = contract.assertions?.input_invariants ?? [];
|
|
7590
|
+
if (inputInvariants.length === 0) continue;
|
|
7591
|
+
const result = evaluateInvariants4(request, inputInvariants, process.env);
|
|
7519
7592
|
for (const failure of result) {
|
|
7520
7593
|
failures.push({
|
|
7521
7594
|
path: failure.path,
|
|
@@ -7890,6 +7963,126 @@ function createBlockingInactiveSession(client, sessionId, detail, configError) {
|
|
|
7890
7963
|
handoff: () => Promise.resolve(null)
|
|
7891
7964
|
};
|
|
7892
7965
|
}
|
|
7966
|
+
function resolveGovernanceEnvironment(opts) {
|
|
7967
|
+
if (opts.environment) return opts.environment;
|
|
7968
|
+
const envVar = typeof process !== "undefined" ? process.env.REPLAYCI_ENVIRONMENT : void 0;
|
|
7969
|
+
if (envVar === "staging") return "staging";
|
|
7970
|
+
if (envVar === "production") return "production";
|
|
7971
|
+
if (envVar === "development") return "development";
|
|
7972
|
+
const nodeEnv = typeof process !== "undefined" ? process.env.NODE_ENV : void 0;
|
|
7973
|
+
if (nodeEnv === "production") return "production";
|
|
7974
|
+
return "development";
|
|
7975
|
+
}
|
|
7976
|
+
function governanceProtectionLevel(env) {
|
|
7977
|
+
switch (env) {
|
|
7978
|
+
case "production":
|
|
7979
|
+
return "govern";
|
|
7980
|
+
case "staging":
|
|
7981
|
+
return "protect";
|
|
7982
|
+
default:
|
|
7983
|
+
return "monitor";
|
|
7984
|
+
}
|
|
7985
|
+
}
|
|
7986
|
+
function createGovernanceSession(client, sessionId, agent, provider, apiKey, opts, diagnostics) {
|
|
7987
|
+
const environment = resolveGovernanceEnvironment(opts);
|
|
7988
|
+
const protLevel = governanceProtectionLevel(environment);
|
|
7989
|
+
const runtimeClient = new RuntimeClient({
|
|
7990
|
+
apiKey,
|
|
7991
|
+
apiUrl: opts.runtimeUrl
|
|
7992
|
+
});
|
|
7993
|
+
let governancePlan;
|
|
7994
|
+
let planFetchPromise = null;
|
|
7995
|
+
let planFetchDone = false;
|
|
7996
|
+
let planFetchError = null;
|
|
7997
|
+
planFetchPromise = runtimeClient.fetchGovernancePlan(agent, environment).then((result) => {
|
|
7998
|
+
governancePlan = result;
|
|
7999
|
+
planFetchDone = true;
|
|
8000
|
+
}).catch((err) => {
|
|
8001
|
+
planFetchDone = true;
|
|
8002
|
+
planFetchError = err instanceof Error ? err.message : String(err);
|
|
8003
|
+
governancePlan = null;
|
|
8004
|
+
});
|
|
8005
|
+
const captureBuffer = new CaptureBuffer({
|
|
8006
|
+
apiKey,
|
|
8007
|
+
endpoint: opts.runtimeUrl
|
|
8008
|
+
});
|
|
8009
|
+
registerBeforeExit(captureBuffer);
|
|
8010
|
+
const terminalInfo = resolveTerminal(client, provider);
|
|
8011
|
+
if (!terminalInfo) {
|
|
8012
|
+
emitDiagnostic2(diagnostics, { type: "replay_inactive", reason: "unsupported_client" });
|
|
8013
|
+
return createInactiveSession(client, sessionId, "Could not resolve terminal resource");
|
|
8014
|
+
}
|
|
8015
|
+
const { terminal, originalCreate } = terminalInfo;
|
|
8016
|
+
const patchedCreate = async function(...args) {
|
|
8017
|
+
if (!planFetchDone && planFetchPromise) {
|
|
8018
|
+
await planFetchPromise;
|
|
8019
|
+
}
|
|
8020
|
+
const hasApprovedPlan = governancePlan && (governancePlan.status === "approved" || governancePlan.status === "enforcing") && governancePlan.compiledSession;
|
|
8021
|
+
if (hasApprovedPlan) {
|
|
8022
|
+
}
|
|
8023
|
+
const result = await originalCreate.apply(this, args);
|
|
8024
|
+
try {
|
|
8025
|
+
const toolCalls = extractToolCalls(result, provider);
|
|
8026
|
+
const usage = extractUsage(result, provider);
|
|
8027
|
+
const requestArg = args[0] && typeof args[0] === "object" ? args[0] : {};
|
|
8028
|
+
captureBuffer.push({
|
|
8029
|
+
schema_version: CAPTURE_SCHEMA_VERSION_CURRENT,
|
|
8030
|
+
agent,
|
|
8031
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8032
|
+
provider,
|
|
8033
|
+
model_id: requestArg.model ?? "unknown",
|
|
8034
|
+
primary_tool_name: toolCalls[0]?.name ?? null,
|
|
8035
|
+
tool_names: toolCalls.map((tc) => tc.name),
|
|
8036
|
+
request: requestArg,
|
|
8037
|
+
response: result,
|
|
8038
|
+
usage,
|
|
8039
|
+
latency_ms: 0,
|
|
8040
|
+
sdk_session_id: sessionId
|
|
8041
|
+
});
|
|
8042
|
+
} catch {
|
|
8043
|
+
}
|
|
8044
|
+
return result;
|
|
8045
|
+
};
|
|
8046
|
+
terminal[terminalInfo.methodName] = patchedCreate;
|
|
8047
|
+
setReplayAttached(client);
|
|
8048
|
+
return {
|
|
8049
|
+
client,
|
|
8050
|
+
flush: () => captureBuffer.flush(),
|
|
8051
|
+
restore() {
|
|
8052
|
+
terminal[terminalInfo.methodName] = originalCreate;
|
|
8053
|
+
},
|
|
8054
|
+
kill() {
|
|
8055
|
+
},
|
|
8056
|
+
getHealth: () => ({
|
|
8057
|
+
status: "healthy",
|
|
8058
|
+
authorityState: "active",
|
|
8059
|
+
protectionLevel: protLevel,
|
|
8060
|
+
durability: "inactive",
|
|
8061
|
+
tier: "compat",
|
|
8062
|
+
compatEnforcement: "protective",
|
|
8063
|
+
cluster_detected: false,
|
|
8064
|
+
bypass_detected: false,
|
|
8065
|
+
totalSteps: 0,
|
|
8066
|
+
totalBlocks: 0,
|
|
8067
|
+
totalErrors: 0,
|
|
8068
|
+
killed: false,
|
|
8069
|
+
shadowEvaluations: 0
|
|
8070
|
+
}),
|
|
8071
|
+
getState: () => EMPTY_STATE_SNAPSHOT,
|
|
8072
|
+
getLastNarrowing: () => null,
|
|
8073
|
+
getLastShadowDelta: () => null,
|
|
8074
|
+
getLastTrace: () => null,
|
|
8075
|
+
narrow() {
|
|
8076
|
+
},
|
|
8077
|
+
widen() {
|
|
8078
|
+
},
|
|
8079
|
+
addLabel() {
|
|
8080
|
+
},
|
|
8081
|
+
tools: {},
|
|
8082
|
+
getWorkflowState: () => Promise.resolve(null),
|
|
8083
|
+
handoff: () => Promise.resolve(null)
|
|
8084
|
+
};
|
|
8085
|
+
}
|
|
7893
8086
|
function toNarrowingSnapshot(result) {
|
|
7894
8087
|
if (!result || result.removed.length === 0) return null;
|
|
7895
8088
|
return {
|