@agent-native/core 0.219.0 → 0.220.0-nightly-20261023212848
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/action.d.ts +23 -3
- package/dist/action.js +58 -3
- package/dist/agent/abort-reasons.d.ts +6 -0
- package/dist/agent/abort-reasons.js +6 -0
- package/dist/agent/execution-fence.js +5 -1
- package/dist/agent/production-agent.d.ts +22 -6
- package/dist/agent/production-agent.js +136 -37
- package/dist/agent/run-manager.js +92 -44
- package/dist/agent/run-store.d.ts +1 -0
- package/dist/agent/run-store.js +260 -31
- package/dist/agent/run-terminal-tracking.d.ts +29 -0
- package/dist/agent/run-terminal-tracking.js +56 -0
- package/dist/agent/run-turn-id-header.d.ts +9 -0
- package/dist/agent/run-turn-id-header.js +13 -0
- package/dist/agent/types.d.ts +11 -0
- package/dist/chat-threads/store.d.ts +23 -6
- package/dist/chat-threads/store.js +119 -36
- package/dist/cli/amplify-stream.js +1 -1
- package/dist/client/agent-chat/index.d.ts +2 -1
- package/dist/client/agent-chat/index.js +2 -1
- package/dist/client/agent-chat.d.ts +2 -0
- package/dist/client/agent-chat.js +31 -5
- package/dist/client/chat/agentkit-agent-native.js +8 -1
- package/dist/client/chat/agentkit-protocol.js +7 -1
- package/dist/client/chat/run-outcome-telemetry.js +1 -0
- package/dist/client/chat/run-outcome.d.ts +5 -0
- package/dist/client/chat/runtime.d.ts +34 -1
- package/dist/client/chat/runtime.js +96 -12
- package/dist/client/chat/turn-submitted-telemetry.d.ts +4 -0
- package/dist/client/chat/turn-submitted-telemetry.js +39 -0
- package/dist/client/chat/turn-submitted.d.ts +39 -0
- package/dist/client/chat/turn-submitted.js +16 -0
- package/dist/client/route-chunk-recovery.d.ts +3 -8
- package/dist/client/route-chunk-recovery.js +11 -9
- package/dist/client/use-chat-thread-list.d.ts +2 -0
- package/dist/client/use-chat-thread-list.js +48 -2
- package/dist/client/use-chat-threads.d.ts +13 -0
- package/dist/client/use-chat-threads.js +59 -4
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.js +28 -18
- package/dist/db/schema-snapshot.generated.js +5 -0
- package/dist/identity/rekey.d.ts +2 -0
- package/dist/identity/rekey.js +19 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp/agent-connected.d.ts +5 -0
- package/dist/mcp/agent-connected.js +10 -3
- package/dist/mcp/analytics.d.ts +14 -1
- package/dist/mcp/analytics.js +54 -2
- package/dist/mcp/embed-app.js +2 -1
- package/dist/mcp/oauth-route.js +56 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +7 -7
- package/dist/observability/traces.d.ts +2 -0
- package/dist/observability/traces.js +11 -2
- package/dist/provider-api/actions/custom-provider-registration.d.ts +16 -16
- package/dist/provider-api/actions/provider-api.d.ts +13 -13
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/review/index.d.ts +1 -1
- package/dist/review/index.js +1 -1
- package/dist/review/suggestions/actions.js +25 -17
- package/dist/review/suggestions/store.d.ts +14 -1
- package/dist/review/suggestions/store.js +16 -3
- package/dist/review/suggestions/types.d.ts +15 -1
- package/dist/scripts/chat/search-chats.js +14 -2
- package/dist/server/agent-chat/browser-team-tools.js +39 -6
- package/dist/server/agent-chat/script-entries.js +44 -9
- package/dist/server/agent-chat-plugin.d.ts +2 -0
- package/dist/server/agent-chat-plugin.js +66 -16
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/first-touch-route.d.ts +2 -2
- package/dist/shared/route-chunk-recovery-bootstrap.d.ts +9 -0
- package/dist/shared/route-chunk-recovery-bootstrap.js +25 -0
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/dist/templates/chat/.agents/skills/actions/SKILL.md +1 -0
- package/dist/templates/default/.agents/skills/actions/SKILL.md +1 -0
- package/dist/templates/factory/.agents/skills/review-prs/SKILL.md +27 -3
- package/dist/templates/headless/.agents/skills/actions/SKILL.md +1 -0
- package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +1 -0
- package/dist/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
- package/dist/tracking/providers.js +3 -0
- package/dist/triggers/actions/manage-automation.d.ts +2 -2
- package/docs/content/external-agents.mdx +1 -1
- package/docs/content/mcp-apps.mdx +3 -3
- package/docs/content/mcp-protocol.mdx +1 -1
- package/docs/content/sharing.mdx +1 -0
- package/docs/content/template-assets-integrations.mdx +5 -3
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/actions/SKILL.md +1 -0
- package/src/templates/default/.agents/skills/actions/SKILL.md +1 -0
- package/src/templates/factory/.agents/skills/review-prs/SKILL.md +27 -3
- package/src/templates/headless/.agents/skills/actions/SKILL.md +1 -0
- package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +1 -0
- package/src/templates/workspace-core/.agents/skills/external-agents/references/mcp-apps-embedding.md +12 -5
package/dist/agent/run-store.js
CHANGED
|
@@ -7,7 +7,9 @@ import { fromMigrationEntries } from "../db/versioned-store-migrations.js";
|
|
|
7
7
|
import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
|
|
8
8
|
import { automationRecoveryMessagesForLocale } from "../localization/automation-recovery-messages.js";
|
|
9
9
|
import { lockAndAssertServicePrincipalMayStartRun, prepareServicePrincipalRunStart, recordServicePrincipalDenial, } from "../org/service-principal-guard.js";
|
|
10
|
+
import { platformWaitUntil } from "../server/background-work.js";
|
|
10
11
|
import { captureError } from "../server/capture-error.js";
|
|
12
|
+
import { runWithRequestContext } from "../server/request-context.js";
|
|
11
13
|
import { assertNoInlineImageBytes, stripInlineBytes, stripInlineBytesFromJson, } from "../shared/inline-bytes.js";
|
|
12
14
|
import { isRequestedStopAbortReason } from "./abort-reasons.js";
|
|
13
15
|
import { approvalKeyForHistory } from "./approval-key.js";
|
|
@@ -17,6 +19,7 @@ import { LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, }
|
|
|
17
19
|
import { currentExecutionFence, currentExecutionFenceForScope, ExecutionFenceError, MissingExecutionFenceError, lockAndAssertCurrentExecutionFences, StaleExecutionFenceError, withFencedExecutionWrite, } from "./execution-fence.js";
|
|
18
20
|
import { AGENT_RUN_MIGRATIONS, AGENT_RUN_MIGRATIONS_TABLE, } from "./run-migrations.js";
|
|
19
21
|
import { lockThreadRunSlot } from "./run-slot-lock.js";
|
|
22
|
+
import { trackRunTerminal, } from "./run-terminal-tracking.js";
|
|
20
23
|
import { isConsistentToolCallInputFingerprint, isUnavailableToolCallInput, toolCallInputFingerprint, } from "./tool-call-journal.js";
|
|
21
24
|
import { CONTINUATION_REASONS, isContinuationTerminalReason } from "./types.js";
|
|
22
25
|
import { readWriteReceipt } from "./write-receipts.js";
|
|
@@ -1537,6 +1540,14 @@ export async function setRunTerminalReason(runId, terminalReason) {
|
|
|
1537
1540
|
// Diagnostics are best-effort; never let them break completion.
|
|
1538
1541
|
}
|
|
1539
1542
|
}
|
|
1543
|
+
export async function setRunTerminalReasonIfStatus(runId, expectedStatus, terminalReason) {
|
|
1544
|
+
await ensureRunTables();
|
|
1545
|
+
const { rowsAffected } = await withFencedExecutionWrite((tx) => tx.execute({
|
|
1546
|
+
sql: `UPDATE agent_runs SET terminal_reason = ? WHERE id = ? AND status = ? AND (terminal_reason IS NULL OR terminal_reason = '')`,
|
|
1547
|
+
args: [terminalReason.slice(0, 200), runId, expectedStatus],
|
|
1548
|
+
}), { runId });
|
|
1549
|
+
return (rowsAffected ?? 0) > 0;
|
|
1550
|
+
}
|
|
1540
1551
|
/**
|
|
1541
1552
|
* INVARIANT: a terminal event yields `completed` if and only if its reason is
|
|
1542
1553
|
* `done`. Everything else either failed (`errored`) or stopped short
|
|
@@ -2018,29 +2029,37 @@ function priorDiagStageLabel(raw) {
|
|
|
2018
2029
|
async function reconcileTerminalRunInRecoveryTransaction(tx, runId) {
|
|
2019
2030
|
const latest = await getRunEventForReconciliation(runId, tx);
|
|
2020
2031
|
if (!latest)
|
|
2021
|
-
return
|
|
2032
|
+
return null;
|
|
2022
2033
|
const status = terminalStatusForEvent(latest.event);
|
|
2023
2034
|
const terminalReason = terminalReasonForEvent(latest.event);
|
|
2024
2035
|
if (!status || !terminalReason)
|
|
2025
|
-
return
|
|
2026
|
-
const
|
|
2036
|
+
return null;
|
|
2037
|
+
const errorCode = errorCodeForTerminalEvent(latest.event);
|
|
2038
|
+
const { rows, rowsAffected } = await tx.execute({
|
|
2027
2039
|
sql: `UPDATE agent_runs
|
|
2028
2040
|
SET status = ?,
|
|
2029
2041
|
completed_at = COALESCE(completed_at, ?, ${livenessBasisSql()}),
|
|
2030
2042
|
error_code = ?,
|
|
2031
2043
|
error_detail = ?,
|
|
2032
2044
|
terminal_reason = ?
|
|
2033
|
-
WHERE id = ? AND status = 'running'
|
|
2045
|
+
WHERE id = ? AND status = 'running'
|
|
2046
|
+
${ENDED_RUN_RETURNING}`,
|
|
2034
2047
|
args: [
|
|
2035
2048
|
status,
|
|
2036
2049
|
latest.eventAt,
|
|
2037
|
-
|
|
2050
|
+
errorCode,
|
|
2038
2051
|
errorDetailForTerminalEvent(latest.event),
|
|
2039
2052
|
terminalReason,
|
|
2040
2053
|
runId,
|
|
2041
2054
|
],
|
|
2042
2055
|
});
|
|
2043
|
-
|
|
2056
|
+
if ((rowsAffected ?? 0) !== 1)
|
|
2057
|
+
return null;
|
|
2058
|
+
return {
|
|
2059
|
+
runId,
|
|
2060
|
+
ending: { status, terminalReason, errorCode: errorCode ?? undefined },
|
|
2061
|
+
row: rows?.[0],
|
|
2062
|
+
};
|
|
2044
2063
|
}
|
|
2045
2064
|
async function reapSingleStaleRun(runId, source, maxStaleMs) {
|
|
2046
2065
|
const completedAt = Date.now();
|
|
@@ -2057,7 +2076,8 @@ async function reapSingleStaleRun(runId, source, maxStaleMs) {
|
|
|
2057
2076
|
WHERE id = ?
|
|
2058
2077
|
AND status = 'running'
|
|
2059
2078
|
AND ${terminalRunEventExclusionSql()}
|
|
2060
|
-
AND ${staleClause}
|
|
2079
|
+
AND ${staleClause}
|
|
2080
|
+
${ENDED_RUN_RETURNING}`;
|
|
2061
2081
|
const updateArgs = [
|
|
2062
2082
|
STALE_RUN_ERROR_EVENT.errorCode,
|
|
2063
2083
|
STALE_RUN_ERROR_EVENT.details,
|
|
@@ -2096,7 +2116,8 @@ async function reapSingleStaleRun(runId, source, maxStaleMs) {
|
|
|
2096
2116
|
if (lease?.isLive && generationMatches) {
|
|
2097
2117
|
return { reaped: false, outcome: null };
|
|
2098
2118
|
}
|
|
2099
|
-
|
|
2119
|
+
const repaired = await reconcileTerminalRunInRecoveryTransaction(tx, runId);
|
|
2120
|
+
if (repaired) {
|
|
2100
2121
|
if (generationMatches && lease) {
|
|
2101
2122
|
await tx.execute({
|
|
2102
2123
|
sql: `UPDATE agent_execution_leases
|
|
@@ -2114,9 +2135,9 @@ async function reapSingleStaleRun(runId, source, maxStaleMs) {
|
|
|
2114
2135
|
args: [runId],
|
|
2115
2136
|
});
|
|
2116
2137
|
}
|
|
2117
|
-
return { reaped: false, outcome: null };
|
|
2138
|
+
return { reaped: false, outcome: null, repaired };
|
|
2118
2139
|
}
|
|
2119
|
-
const { rowsAffected } = await tx.execute({
|
|
2140
|
+
const { rows, rowsAffected } = await tx.execute({
|
|
2120
2141
|
sql: updateSql,
|
|
2121
2142
|
args: updateArgs,
|
|
2122
2143
|
});
|
|
@@ -2150,9 +2171,32 @@ async function reapSingleStaleRun(runId, source, maxStaleMs) {
|
|
|
2150
2171
|
}
|
|
2151
2172
|
}
|
|
2152
2173
|
}
|
|
2153
|
-
return {
|
|
2174
|
+
return {
|
|
2175
|
+
reaped,
|
|
2176
|
+
outcome,
|
|
2177
|
+
reapedRow: rows?.[0],
|
|
2178
|
+
};
|
|
2154
2179
|
});
|
|
2155
2180
|
const { reaped, outcome } = reapResult;
|
|
2181
|
+
// Taken from the committed attempt: a rolled-back one ended nothing.
|
|
2182
|
+
const ended = "repaired" in reapResult && reapResult.repaired
|
|
2183
|
+
? reapResult.repaired
|
|
2184
|
+
: reaped
|
|
2185
|
+
? {
|
|
2186
|
+
runId,
|
|
2187
|
+
ending: reapedRunEnding({
|
|
2188
|
+
source,
|
|
2189
|
+
errorCode: STALE_RUN_ERROR_EVENT.errorCode,
|
|
2190
|
+
terminalReason: STALE_RUN_TERMINAL_REASON,
|
|
2191
|
+
recoveryOutcome: staleRunRecoveryLabel(outcome),
|
|
2192
|
+
}),
|
|
2193
|
+
row: "reapedRow" in reapResult ? reapResult.reapedRow : undefined,
|
|
2194
|
+
}
|
|
2195
|
+
: null;
|
|
2196
|
+
// Handed off as soon as it commits: a sweep cut short must not lose the
|
|
2197
|
+
// runs it already ended, which no later sweep can see.
|
|
2198
|
+
if (ended)
|
|
2199
|
+
trackRunsEndedInSql([ended]);
|
|
2156
2200
|
let forensics = "";
|
|
2157
2201
|
let priorStageInfo = "";
|
|
2158
2202
|
if (reaped) {
|
|
@@ -2242,8 +2286,8 @@ export async function reapUnclaimedBackgroundRun(runId) {
|
|
|
2242
2286
|
}
|
|
2243
2287
|
const completedAt = Date.now();
|
|
2244
2288
|
const cutoff = completedAt - UNCLAIMED_BACKGROUND_RUN_GRACE_MS;
|
|
2245
|
-
const reaped = await client.transaction(async (tx) => {
|
|
2246
|
-
const { rowsAffected } = await tx.execute({
|
|
2289
|
+
const { reaped, row } = await client.transaction(async (tx) => {
|
|
2290
|
+
const { rows, rowsAffected } = await tx.execute({
|
|
2247
2291
|
sql: `UPDATE agent_runs
|
|
2248
2292
|
SET status = 'errored',
|
|
2249
2293
|
completed_at = ?,
|
|
@@ -2253,7 +2297,8 @@ export async function reapUnclaimedBackgroundRun(runId) {
|
|
|
2253
2297
|
WHERE id = ?
|
|
2254
2298
|
AND status = 'running'
|
|
2255
2299
|
AND dispatch_mode = 'background'
|
|
2256
|
-
AND COALESCE(heartbeat_at, started_at) <
|
|
2300
|
+
AND COALESCE(heartbeat_at, started_at) < ?
|
|
2301
|
+
${ENDED_RUN_RETURNING}`,
|
|
2257
2302
|
args: [
|
|
2258
2303
|
completedAt,
|
|
2259
2304
|
UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT.errorCode,
|
|
@@ -2267,7 +2312,7 @@ export async function reapUnclaimedBackgroundRun(runId) {
|
|
|
2267
2312
|
if (updated) {
|
|
2268
2313
|
await appendTerminalRunEventInTransaction(tx, runId, UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT);
|
|
2269
2314
|
}
|
|
2270
|
-
return updated;
|
|
2315
|
+
return { reaped: updated, row: rows?.[0] };
|
|
2271
2316
|
});
|
|
2272
2317
|
if (reaped) {
|
|
2273
2318
|
try {
|
|
@@ -2282,6 +2327,11 @@ export async function reapUnclaimedBackgroundRun(runId) {
|
|
|
2282
2327
|
extra: { runId },
|
|
2283
2328
|
});
|
|
2284
2329
|
}
|
|
2330
|
+
trackReapedRun(runId, {
|
|
2331
|
+
source: "reap-unclaimed-background",
|
|
2332
|
+
errorCode: UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT.errorCode,
|
|
2333
|
+
terminalReason: UNCLAIMED_BACKGROUND_RUN_ERROR_EVENT.errorCode,
|
|
2334
|
+
}, row);
|
|
2285
2335
|
}
|
|
2286
2336
|
return reaped;
|
|
2287
2337
|
}
|
|
@@ -2351,7 +2401,7 @@ export async function finalizeClaimedBackgroundRunFailure(runId, failure) {
|
|
|
2351
2401
|
return false;
|
|
2352
2402
|
}
|
|
2353
2403
|
const expectedGeneration = scope.worker_generation == null ? null : String(scope.worker_generation);
|
|
2354
|
-
|
|
2404
|
+
const ended = await client.transaction(async (tx) => {
|
|
2355
2405
|
await lockThreadRunSlot(tx, scope.thread_id);
|
|
2356
2406
|
const lease = await lockTurnExecutionLease(tx, scope.thread_id, scope.turn_id);
|
|
2357
2407
|
const selected = await tx.execute({
|
|
@@ -2364,7 +2414,7 @@ export async function finalizeClaimedBackgroundRunFailure(runId, failure) {
|
|
|
2364
2414
|
run.dispatch_mode !== "background-processing" ||
|
|
2365
2415
|
(run.worker_generation == null ? null : String(run.worker_generation)) !==
|
|
2366
2416
|
expectedGeneration) {
|
|
2367
|
-
return
|
|
2417
|
+
return null;
|
|
2368
2418
|
}
|
|
2369
2419
|
const generationMatches = run.worker_generation == null
|
|
2370
2420
|
? lease === null || lease.ownerRunId === runId
|
|
@@ -2372,14 +2422,15 @@ export async function finalizeClaimedBackgroundRunFailure(runId, failure) {
|
|
|
2372
2422
|
lease.ownerRunId === runId &&
|
|
2373
2423
|
String(run.worker_generation) === lease.generation;
|
|
2374
2424
|
if (!generationMatches)
|
|
2375
|
-
return
|
|
2425
|
+
return null;
|
|
2376
2426
|
const updated = await tx.execute({
|
|
2377
2427
|
sql: `UPDATE agent_runs
|
|
2378
2428
|
SET status = 'errored', completed_at = ?, error_code = ?,
|
|
2379
2429
|
error_detail = ?, terminal_reason = ?, dispatch_payload = NULL
|
|
2380
2430
|
WHERE id = ? AND status = 'running'
|
|
2381
2431
|
AND dispatch_mode = 'background-processing'
|
|
2382
|
-
AND ${terminalRunEventExclusionSql()}
|
|
2432
|
+
AND ${terminalRunEventExclusionSql()}
|
|
2433
|
+
${ENDED_RUN_RETURNING}`,
|
|
2383
2434
|
args: [
|
|
2384
2435
|
Date.now(),
|
|
2385
2436
|
failure.errorCode,
|
|
@@ -2389,7 +2440,7 @@ export async function finalizeClaimedBackgroundRunFailure(runId, failure) {
|
|
|
2389
2440
|
],
|
|
2390
2441
|
});
|
|
2391
2442
|
if ((updated.rowsAffected ?? 0) !== 1)
|
|
2392
|
-
return
|
|
2443
|
+
return null;
|
|
2393
2444
|
const lastEvent = await tx.execute({
|
|
2394
2445
|
sql: `SELECT seq FROM agent_run_events WHERE run_id = ? ORDER BY seq DESC LIMIT 1`,
|
|
2395
2446
|
args: [runId],
|
|
@@ -2409,8 +2460,16 @@ export async function finalizeClaimedBackgroundRunFailure(runId, failure) {
|
|
|
2409
2460
|
if ((inserted.rowsAffected ?? 0) !== 1) {
|
|
2410
2461
|
throw new Error(`Failed to persist failure event for run ${runId}`);
|
|
2411
2462
|
}
|
|
2412
|
-
return
|
|
2463
|
+
return { row: updated.rows?.[0] };
|
|
2413
2464
|
});
|
|
2465
|
+
if (!ended)
|
|
2466
|
+
return false;
|
|
2467
|
+
trackRunEndedInSql(runId, {
|
|
2468
|
+
status: "errored",
|
|
2469
|
+
terminalReason: `error:${failure.errorCode}`.slice(0, 200),
|
|
2470
|
+
errorCode: failure.errorCode,
|
|
2471
|
+
}, ended.row);
|
|
2472
|
+
return true;
|
|
2414
2473
|
}
|
|
2415
2474
|
export async function failRunBeforeDispatch(runId, errorCode, errorDetail, terminalReason) {
|
|
2416
2475
|
await ensureRunTables();
|
|
@@ -2428,7 +2487,7 @@ export async function failRunBeforeDispatch(runId, errorCode, errorDetail, termi
|
|
|
2428
2487
|
typeof scope.turn_id !== "string") {
|
|
2429
2488
|
return false;
|
|
2430
2489
|
}
|
|
2431
|
-
|
|
2490
|
+
const ended = await client.transaction(async (tx) => {
|
|
2432
2491
|
const lease = await lockTurnExecutionLease(tx, scope.thread_id, scope.turn_id);
|
|
2433
2492
|
const run = await tx.execute({
|
|
2434
2493
|
sql: `SELECT status, worker_generation FROM agent_runs WHERE id = ? FOR UPDATE`,
|
|
@@ -2436,19 +2495,20 @@ export async function failRunBeforeDispatch(runId, errorCode, errorDetail, termi
|
|
|
2436
2495
|
});
|
|
2437
2496
|
const currentRun = run.rows[0];
|
|
2438
2497
|
if (currentRun?.status !== "running") {
|
|
2439
|
-
return
|
|
2498
|
+
return null;
|
|
2440
2499
|
}
|
|
2441
2500
|
if (lease?.isLive &&
|
|
2442
2501
|
lease.ownerRunId === runId &&
|
|
2443
2502
|
(currentRun.worker_generation == null ||
|
|
2444
2503
|
String(currentRun.worker_generation) === lease.generation)) {
|
|
2445
|
-
return
|
|
2504
|
+
return null;
|
|
2446
2505
|
}
|
|
2447
2506
|
const updated = await tx.execute({
|
|
2448
2507
|
sql: `UPDATE agent_runs
|
|
2449
2508
|
SET status = 'errored', completed_at = ?, error_code = ?,
|
|
2450
2509
|
error_detail = ?, terminal_reason = ?, dispatch_payload = NULL
|
|
2451
|
-
WHERE id = ? AND status = 'running'
|
|
2510
|
+
WHERE id = ? AND status = 'running'
|
|
2511
|
+
${ENDED_RUN_RETURNING}`,
|
|
2452
2512
|
args: [
|
|
2453
2513
|
Date.now(),
|
|
2454
2514
|
errorCode,
|
|
@@ -2457,20 +2517,34 @@ export async function failRunBeforeDispatch(runId, errorCode, errorDetail, termi
|
|
|
2457
2517
|
runId,
|
|
2458
2518
|
],
|
|
2459
2519
|
});
|
|
2460
|
-
return (updated.rowsAffected ?? 0) === 1
|
|
2520
|
+
return (updated.rowsAffected ?? 0) === 1
|
|
2521
|
+
? { row: updated.rows?.[0] }
|
|
2522
|
+
: null;
|
|
2461
2523
|
});
|
|
2524
|
+
if (!ended)
|
|
2525
|
+
return false;
|
|
2526
|
+
// No run loop ever started for this row, so nothing else will report it.
|
|
2527
|
+
trackRunEndedInSql(runId, {
|
|
2528
|
+
status: "errored",
|
|
2529
|
+
terminalReason: terminalReason.slice(0, 200),
|
|
2530
|
+
errorCode,
|
|
2531
|
+
}, ended.row);
|
|
2532
|
+
return true;
|
|
2462
2533
|
}
|
|
2463
2534
|
/** The caller owns this claim and has not called startRun yet. */
|
|
2464
2535
|
export async function releaseBackgroundRunBeforeStart(runId, errorCode, errorDetail) {
|
|
2465
2536
|
await ensureRunTables();
|
|
2466
|
-
const { rowsAffected } = await getDbExec().execute({
|
|
2537
|
+
const { rowsAffected, rows } = await getDbExec().execute({
|
|
2467
2538
|
sql: `UPDATE agent_runs
|
|
2468
2539
|
SET status = 'errored', completed_at = ?, error_code = ?, error_detail = ?, dispatch_payload = NULL
|
|
2469
|
-
WHERE id = ? AND status = 'running' AND dispatch_mode = 'background-processing'
|
|
2540
|
+
WHERE id = ? AND status = 'running' AND dispatch_mode = 'background-processing'
|
|
2541
|
+
${ENDED_RUN_RETURNING}`,
|
|
2470
2542
|
args: [Date.now(), errorCode, errorDetail, runId],
|
|
2471
2543
|
});
|
|
2472
2544
|
if (rowsAffected !== 1)
|
|
2473
2545
|
throw new Error(`Unstarted background worker ${runId} could not be released`);
|
|
2546
|
+
// No run loop ever started for this row, so nothing else will report it.
|
|
2547
|
+
trackRunEndedInSql(runId, { status: "errored", terminalReason: `error:${errorCode}`, errorCode }, rows?.[0]);
|
|
2474
2548
|
}
|
|
2475
2549
|
export async function getRunStatus(runId) {
|
|
2476
2550
|
await ensureRunTables();
|
|
@@ -2556,7 +2630,7 @@ async function updateRunAborted(runId, reason, requireSafeBoundary) {
|
|
|
2556
2630
|
}
|
|
2557
2631
|
}
|
|
2558
2632
|
const result = await tx.execute({
|
|
2559
|
-
sql: `UPDATE agent_runs SET status = 'aborted', abort_reason = ?, completed_at = ?, terminal_reason = ? WHERE id = ? AND status = 'running'${requireSafeBoundary ? " AND in_flight_since IS NULL" : ""}`,
|
|
2633
|
+
sql: `UPDATE agent_runs SET status = 'aborted', abort_reason = ?, completed_at = ?, terminal_reason = ? WHERE id = ? AND status = 'running'${requireSafeBoundary ? " AND in_flight_since IS NULL" : ""} ${ENDED_RUN_RETURNING}`,
|
|
2560
2634
|
args: [
|
|
2561
2635
|
reason ?? "user",
|
|
2562
2636
|
Date.now(),
|
|
@@ -2568,7 +2642,11 @@ async function updateRunAborted(runId, reason, requireSafeBoundary) {
|
|
|
2568
2642
|
if (rowsAffected > 0) {
|
|
2569
2643
|
await revokeExecutionLeasesForRuns(tx, [runId]);
|
|
2570
2644
|
await appendAbortTerminalRunEventInTransaction(tx, runId, abortEvent);
|
|
2571
|
-
return {
|
|
2645
|
+
return {
|
|
2646
|
+
rowsAffected,
|
|
2647
|
+
appendTerminalEvent: true,
|
|
2648
|
+
endedRow: result.rows?.[0],
|
|
2649
|
+
};
|
|
2572
2650
|
}
|
|
2573
2651
|
const terminalState = await tx.execute({
|
|
2574
2652
|
sql: `SELECT status, terminal_reason, in_flight_since FROM agent_runs WHERE id = ?`,
|
|
@@ -2585,6 +2663,9 @@ async function updateRunAborted(runId, reason, requireSafeBoundary) {
|
|
|
2585
2663
|
return { rowsAffected: 0, appendTerminalEvent: false };
|
|
2586
2664
|
};
|
|
2587
2665
|
const abortResult = await retryAbortTerminalEventTransaction("mark-aborted", () => client.transaction(commitAbort));
|
|
2666
|
+
if (abortResult.rowsAffected > 0) {
|
|
2667
|
+
trackAbortedRun(runId, reason ?? "user", "endedRow" in abortResult ? abortResult.endedRow : undefined);
|
|
2668
|
+
}
|
|
2588
2669
|
return abortResult.rowsAffected > 0 || abortResult.appendTerminalEvent;
|
|
2589
2670
|
}
|
|
2590
2671
|
async function lockExecutionLeasesForRuns(db, runIds) {
|
|
@@ -2659,7 +2740,10 @@ export async function markTurnAborted(threadId, turnId, reason = "user") {
|
|
|
2659
2740
|
if (!client.transaction) {
|
|
2660
2741
|
throw new Error("Atomic turn cancellation requires transaction support");
|
|
2661
2742
|
}
|
|
2743
|
+
let abortedRows = [];
|
|
2662
2744
|
const commitAbort = async (tx) => {
|
|
2745
|
+
// A retried transaction runs this again; only the committed attempt counts.
|
|
2746
|
+
abortedRows = [];
|
|
2663
2747
|
const runIds = [];
|
|
2664
2748
|
await lockThreadRunSlot(tx, threadId);
|
|
2665
2749
|
await lockAndAssertCurrentExecutionFences(tx);
|
|
@@ -2682,9 +2766,10 @@ export async function markTurnAborted(threadId, turnId, reason = "user") {
|
|
|
2682
2766
|
await revokeExecutionLeasesForRuns(tx, candidateRunIds);
|
|
2683
2767
|
if (candidateRunIds.length > 0) {
|
|
2684
2768
|
const aborted = await tx.execute({
|
|
2685
|
-
sql: `UPDATE agent_runs SET status = 'aborted', abort_reason = ?, completed_at = ?, terminal_reason = ? WHERE thread_id = ? AND COALESCE(turn_id, id) = ? AND status = 'running'
|
|
2769
|
+
sql: `UPDATE agent_runs SET status = 'aborted', abort_reason = ?, completed_at = ?, terminal_reason = ? WHERE thread_id = ? AND COALESCE(turn_id, id) = ? AND status = 'running' ${ENDED_RUN_RETURNING}`,
|
|
2686
2770
|
args: [reason, now, `aborted:${reason}`, threadId, turnId],
|
|
2687
2771
|
});
|
|
2772
|
+
abortedRows = aborted.rows;
|
|
2688
2773
|
runIds.push(...aborted.rows
|
|
2689
2774
|
.map((row) => String(row.id ?? ""))
|
|
2690
2775
|
.filter(Boolean));
|
|
@@ -2789,6 +2874,13 @@ export async function markTurnAborted(threadId, turnId, reason = "user") {
|
|
|
2789
2874
|
return "aborted";
|
|
2790
2875
|
};
|
|
2791
2876
|
const outcome = await retryAbortTerminalEventTransaction("mark-turn-aborted", () => client.transaction(commitAbort));
|
|
2877
|
+
trackRunsEndedInSql(abortedRows
|
|
2878
|
+
.filter((row) => typeof row.id === "string")
|
|
2879
|
+
.map((row) => ({
|
|
2880
|
+
runId: row.id,
|
|
2881
|
+
ending: abortedRunEnding(reason),
|
|
2882
|
+
row,
|
|
2883
|
+
})));
|
|
2792
2884
|
return outcome;
|
|
2793
2885
|
}
|
|
2794
2886
|
export async function getRunTurnRef(runId) {
|
|
@@ -3904,6 +3996,143 @@ export async function listErroredRuns(options) {
|
|
|
3904
3996
|
export async function ensureTerminalRunEvent(runId, event) {
|
|
3905
3997
|
return appendTerminalRunEvent(runId, event);
|
|
3906
3998
|
}
|
|
3999
|
+
/**
|
|
4000
|
+
* A duplicate request, or a worker that lost its turn's execution lease, is
|
|
4001
|
+
* aborted before it does any work; the run it stood in for reports the turn.
|
|
4002
|
+
*/
|
|
4003
|
+
const NOT_A_RUN_ABORT_REASONS = new Set([
|
|
4004
|
+
"duplicate_submission",
|
|
4005
|
+
"execution-lease-busy",
|
|
4006
|
+
]);
|
|
4007
|
+
function trackAbortedRun(runId, abortReason, returnedRow) {
|
|
4008
|
+
trackRunEndedInSql(runId, abortedRunEnding(abortReason), returnedRow);
|
|
4009
|
+
}
|
|
4010
|
+
function abortedRunEnding(abortReason) {
|
|
4011
|
+
return {
|
|
4012
|
+
status: "aborted",
|
|
4013
|
+
terminalReason: `aborted:${abortReason}`,
|
|
4014
|
+
abortReason,
|
|
4015
|
+
};
|
|
4016
|
+
}
|
|
4017
|
+
/**
|
|
4018
|
+
* The ended run and its turn's initiator, read in the write that ended it: a
|
|
4019
|
+
* later read can find the initiator row already pruned.
|
|
4020
|
+
*/
|
|
4021
|
+
const ENDED_RUN_COLUMNS = `id, thread_id, turn_id, dispatch_mode, started_at, completed_at,
|
|
4022
|
+
(SELECT principal_email FROM agent_turn_initiators i
|
|
4023
|
+
WHERE i.thread_id = agent_runs.thread_id
|
|
4024
|
+
AND i.turn_id = COALESCE(agent_runs.turn_id, agent_runs.id)) AS principal_email,
|
|
4025
|
+
(SELECT auth_user_id FROM agent_turn_initiators i
|
|
4026
|
+
WHERE i.thread_id = agent_runs.thread_id
|
|
4027
|
+
AND i.turn_id = COALESCE(agent_runs.turn_id, agent_runs.id)) AS auth_user_id,
|
|
4028
|
+
(SELECT is_anonymous FROM agent_turn_initiators i
|
|
4029
|
+
WHERE i.thread_id = agent_runs.thread_id
|
|
4030
|
+
AND i.turn_id = COALESCE(agent_runs.turn_id, agent_runs.id)) AS is_anonymous`;
|
|
4031
|
+
const ENDED_RUN_RETURNING = `RETURNING ${ENDED_RUN_COLUMNS}`;
|
|
4032
|
+
function finiteMs(value) {
|
|
4033
|
+
if (value == null)
|
|
4034
|
+
return undefined;
|
|
4035
|
+
const ms = Number(value);
|
|
4036
|
+
return Number.isFinite(ms) ? ms : undefined;
|
|
4037
|
+
}
|
|
4038
|
+
/**
|
|
4039
|
+
* Sends `agent_run_terminal` for a run this process ended on another
|
|
4040
|
+
* process's behalf. Call it only once the reap's UPDATE has affected the run:
|
|
4041
|
+
* the conditional `status = 'running'` write is what makes each run's terminal
|
|
4042
|
+
* event fire once across the reaper and the run's own process.
|
|
4043
|
+
*
|
|
4044
|
+
* Returns synchronously; the reap never waits on tracking or sees it fail.
|
|
4045
|
+
*/
|
|
4046
|
+
function trackReapedRun(runId, reap, returnedRow) {
|
|
4047
|
+
trackRunEndedInSql(runId, reapedRunEnding(reap), returnedRow);
|
|
4048
|
+
}
|
|
4049
|
+
/** `recovered` and `not_background` stand alone; any other outcome declined. */
|
|
4050
|
+
function staleRunRecoveryLabel(recovery) {
|
|
4051
|
+
if (!recovery)
|
|
4052
|
+
return undefined;
|
|
4053
|
+
return recovery.outcome === "recovered" ||
|
|
4054
|
+
recovery.outcome === "not_background"
|
|
4055
|
+
? recovery.outcome
|
|
4056
|
+
: `declined:${recovery.outcome}`;
|
|
4057
|
+
}
|
|
4058
|
+
function reapedRunEnding(reap) {
|
|
4059
|
+
return {
|
|
4060
|
+
status: "errored",
|
|
4061
|
+
terminalReason: reap.terminalReason,
|
|
4062
|
+
errorCode: reap.errorCode,
|
|
4063
|
+
reap: { source: reap.source, recoveryOutcome: reap.recoveryOutcome },
|
|
4064
|
+
};
|
|
4065
|
+
}
|
|
4066
|
+
/**
|
|
4067
|
+
* Sends `agent_run_terminal` for a run a SQL write ended (a reap, stop,
|
|
4068
|
+
* repair, or pre-start release),
|
|
4069
|
+
* which the run's own process then cannot report: its own conditional
|
|
4070
|
+
* terminal write finds no running row. Call it only for runs the ending
|
|
4071
|
+
* UPDATE affected, so whichever write wins is the one that reports.
|
|
4072
|
+
*/
|
|
4073
|
+
function trackRunEndedInSql(runId, ending, returnedRow) {
|
|
4074
|
+
trackRunsEndedInSql([{ runId, ending, row: returnedRow }]);
|
|
4075
|
+
}
|
|
4076
|
+
/** One background send for the runs one write ended. */
|
|
4077
|
+
function trackRunsEndedInSql(endedRuns) {
|
|
4078
|
+
const runs = endedRuns.filter((run) => !run.ending.abortReason ||
|
|
4079
|
+
!NOT_A_RUN_ABORT_REASONS.has(run.ending.abortReason));
|
|
4080
|
+
if (runs.length === 0)
|
|
4081
|
+
return;
|
|
4082
|
+
const label = () => runs.map((run) => run.runId).join(", ");
|
|
4083
|
+
try {
|
|
4084
|
+
// The ending request is whoever happened to sweep or stop the run: its
|
|
4085
|
+
// synthetic flag, email and client platform must not decide or describe
|
|
4086
|
+
// another user's run, so the send runs without them.
|
|
4087
|
+
const work = Promise.resolve(runWithRequestContext({ isSyntheticTraffic: false }, () => sendSqlEndedRunTerminals(runs))).catch((error) => {
|
|
4088
|
+
console.error("[run-store] agent_run_terminal was not sent for a run ended in SQL:", label(), error instanceof Error ? error.message : error);
|
|
4089
|
+
});
|
|
4090
|
+
platformWaitUntil()?.(work);
|
|
4091
|
+
}
|
|
4092
|
+
catch (error) {
|
|
4093
|
+
console.error("[run-store] agent_run_terminal was not sent for a run ended in SQL:", label(), error instanceof Error ? error.message : error);
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
async function sendSqlEndedRunTerminals(runs) {
|
|
4097
|
+
await Promise.all(runs.map(async (run) => {
|
|
4098
|
+
const row = run.row;
|
|
4099
|
+
if (typeof row?.thread_id !== "string") {
|
|
4100
|
+
console.error("[run-store] agent_run_terminal was not sent: ended run row is unreadable:", run.runId);
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
const startedAt = finiteMs(row.started_at);
|
|
4104
|
+
const completedAt = finiteMs(row.completed_at);
|
|
4105
|
+
await trackRunTerminal({
|
|
4106
|
+
...run.ending,
|
|
4107
|
+
runId: run.runId,
|
|
4108
|
+
threadId: row.thread_id,
|
|
4109
|
+
// Same turn the initiator row and the run's own process key on.
|
|
4110
|
+
turnId: typeof row.turn_id === "string" && row.turn_id
|
|
4111
|
+
? row.turn_id
|
|
4112
|
+
: run.runId,
|
|
4113
|
+
dispatchMode: typeof row.dispatch_mode === "string"
|
|
4114
|
+
? row.dispatch_mode
|
|
4115
|
+
: undefined,
|
|
4116
|
+
durationMs: startedAt !== undefined && completedAt !== undefined
|
|
4117
|
+
? completedAt - startedAt
|
|
4118
|
+
: undefined,
|
|
4119
|
+
},
|
|
4120
|
+
// The turn's initiator, not the request that ended the run; `track()`
|
|
4121
|
+
// drops it when it is a test identity. An anonymous one is not a user.
|
|
4122
|
+
row.is_anonymous === true || row.is_anonymous === "t"
|
|
4123
|
+
? undefined
|
|
4124
|
+
: {
|
|
4125
|
+
userId: typeof row.principal_email === "string"
|
|
4126
|
+
? row.principal_email
|
|
4127
|
+
: undefined,
|
|
4128
|
+
authUserId: typeof row.auth_user_id === "string"
|
|
4129
|
+
? row.auth_user_id
|
|
4130
|
+
: undefined,
|
|
4131
|
+
});
|
|
4132
|
+
}));
|
|
4133
|
+
const { flushTracking } = await import("../tracking/registry.js");
|
|
4134
|
+
await flushTracking();
|
|
4135
|
+
}
|
|
3907
4136
|
/** Projects a run after its stale status and terminal event committed. */
|
|
3908
4137
|
async function finalizeStaleRun(runId, source, projectThread = true) {
|
|
3909
4138
|
if (!projectThread)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { TrackingMeta } from "../tracking/registry.js";
|
|
2
|
+
export interface RunTerminalTrackingFields {
|
|
3
|
+
runId: string;
|
|
4
|
+
threadId: string;
|
|
5
|
+
turnId: string;
|
|
6
|
+
status: "completed" | "errored" | "aborted" | "truncated";
|
|
7
|
+
terminalReason: string;
|
|
8
|
+
errorCode?: string;
|
|
9
|
+
dispatchMode?: string;
|
|
10
|
+
abortReason?: string;
|
|
11
|
+
durationMs?: number;
|
|
12
|
+
model?: string;
|
|
13
|
+
engineName?: string;
|
|
14
|
+
attemptCount?: number;
|
|
15
|
+
/** For `write_tool_outcome_unknown`: the call held back, and why. */
|
|
16
|
+
blockedTool?: string;
|
|
17
|
+
blockedBy?: "rejection" | "interruption";
|
|
18
|
+
/** Set only by a reap, which ends a run outside the process running it. */
|
|
19
|
+
reap?: {
|
|
20
|
+
source: string;
|
|
21
|
+
recoveryOutcome?: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The one `agent_run_terminal` sender, shared by the run's own process and the
|
|
26
|
+
* SQL writes that end a run for it, so the two shapes cannot drift apart. The returned promise never
|
|
27
|
+
* rejects, so a caller can hand it to `waitUntil` without guarding it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function trackRunTerminal(fields: RunTerminalTrackingFields, source: TrackingMeta | undefined): Promise<void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { runErrorTelemetryProperties } from "./engine/error-telemetry.js";
|
|
2
|
+
/**
|
|
3
|
+
* The one `agent_run_terminal` sender, shared by the run's own process and the
|
|
4
|
+
* SQL writes that end a run for it, so the two shapes cannot drift apart. The returned promise never
|
|
5
|
+
* rejects, so a caller can hand it to `waitUntil` without guarding it.
|
|
6
|
+
*/
|
|
7
|
+
export function trackRunTerminal(fields, source) {
|
|
8
|
+
try {
|
|
9
|
+
const properties = {
|
|
10
|
+
source: "agent_run_manager",
|
|
11
|
+
run_id: fields.runId,
|
|
12
|
+
thread_id: fields.threadId,
|
|
13
|
+
turn_id: fields.turnId,
|
|
14
|
+
status: fields.status,
|
|
15
|
+
terminal_reason: fields.terminalReason,
|
|
16
|
+
error_code: fields.errorCode,
|
|
17
|
+
...(fields.errorCode
|
|
18
|
+
? runErrorTelemetryProperties(fields.errorCode)
|
|
19
|
+
: {}),
|
|
20
|
+
dispatch_mode: fields.dispatchMode,
|
|
21
|
+
abort_reason: fields.abortReason,
|
|
22
|
+
duration_ms: fields.durationMs,
|
|
23
|
+
model: fields.model,
|
|
24
|
+
engine: fields.engineName,
|
|
25
|
+
attempt_count: fields.attemptCount,
|
|
26
|
+
blocked_tool: fields.blockedTool,
|
|
27
|
+
blocked_by: fields.blockedBy,
|
|
28
|
+
...(fields.reap
|
|
29
|
+
? {
|
|
30
|
+
reaped: true,
|
|
31
|
+
reap_source: fields.reap.source,
|
|
32
|
+
recovery_outcome: fields.reap.recoveryOutcome,
|
|
33
|
+
}
|
|
34
|
+
: {}),
|
|
35
|
+
};
|
|
36
|
+
for (const key of Object.keys(properties)) {
|
|
37
|
+
if (properties[key] === undefined)
|
|
38
|
+
delete properties[key];
|
|
39
|
+
}
|
|
40
|
+
return Promise.all([
|
|
41
|
+
import("../tracking/registry.js"),
|
|
42
|
+
import("../observability/tracking-identity.js"),
|
|
43
|
+
])
|
|
44
|
+
.then(([{ track }, { trackingIdentityProperties }]) => {
|
|
45
|
+
track("agent_run_terminal", { ...properties, ...trackingIdentityProperties() }, source);
|
|
46
|
+
})
|
|
47
|
+
.catch((error) => {
|
|
48
|
+
console.error("[run-terminal-tracking] agent_run_terminal was not sent:", fields.runId, error instanceof Error ? error.message : error);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
// Tracking must never affect the agent run or its persisted status.
|
|
53
|
+
console.error("[run-terminal-tracking] agent_run_terminal was not sent:", fields.runId, error instanceof Error ? error.message : error);
|
|
54
|
+
return Promise.resolve();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { setResponseHeader } from "h3";
|
|
2
|
+
/**
|
|
3
|
+
* The turn this request's run is stored under, for the client to report. The
|
|
4
|
+
* id can come from the request body, and a header value outside visible ASCII
|
|
5
|
+
* throws once the run has started, so such an id is left to the client's own.
|
|
6
|
+
* With no id (a replayed run held by another process) no header is set, and
|
|
7
|
+
* the client reports the replay's turn as unknown rather than guessing it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function setRunTurnIdHeader(event: Parameters<typeof setResponseHeader>[0], turnId: string | undefined): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { setResponseHeader } from "h3";
|
|
2
|
+
/**
|
|
3
|
+
* The turn this request's run is stored under, for the client to report. The
|
|
4
|
+
* id can come from the request body, and a header value outside visible ASCII
|
|
5
|
+
* throws once the run has started, so such an id is left to the client's own.
|
|
6
|
+
* With no id (a replayed run held by another process) no header is set, and
|
|
7
|
+
* the client reports the replay's turn as unknown rather than guessing it.
|
|
8
|
+
*/
|
|
9
|
+
export function setRunTurnIdHeader(event, turnId) {
|
|
10
|
+
if (turnId && /^[\x20-\x7e]+$/.test(turnId)) {
|
|
11
|
+
setResponseHeader(event, "X-Turn-Id", turnId);
|
|
12
|
+
}
|
|
13
|
+
}
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -240,6 +240,11 @@ export interface AgentChatRichEventEnvelope {
|
|
|
240
240
|
references?: AgentChatRichEventReference[];
|
|
241
241
|
metadata?: Record<string, unknown>;
|
|
242
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Why a failed tool call failed, where the loop could tell: its arguments
|
|
245
|
+
* failed validation before it ran, or it failed having written nothing.
|
|
246
|
+
*/
|
|
247
|
+
export type ToolErrorClass = "input_invalid" | "rejected";
|
|
243
248
|
export type AgentChatEvent = {
|
|
244
249
|
type: "text";
|
|
245
250
|
text: string;
|
|
@@ -308,6 +313,8 @@ export type AgentChatEvent = {
|
|
|
308
313
|
/** False marks current completed events; absence is reserved for older journals. */
|
|
309
314
|
approvalPause?: boolean;
|
|
310
315
|
outcomeUnknown?: true;
|
|
316
|
+
/** Why a failed call failed, where the loop could tell. */
|
|
317
|
+
errorClass?: ToolErrorClass;
|
|
311
318
|
replayed?: true;
|
|
312
319
|
fileMutation?: AgentFileMutationProof;
|
|
313
320
|
artifacts?: ArtifactReceipt[];
|
|
@@ -392,6 +399,10 @@ export type AgentChatEvent = {
|
|
|
392
399
|
details?: string;
|
|
393
400
|
recoverable?: boolean;
|
|
394
401
|
providerRetryable?: boolean;
|
|
402
|
+
/** For `write_tool_outcome_unknown`: the call held back. */
|
|
403
|
+
blockedTool?: string;
|
|
404
|
+
/** What left the earlier call of `blockedTool` unknown. */
|
|
405
|
+
blockedBy?: "rejection" | "interruption";
|
|
395
406
|
}
|
|
396
407
|
/**
|
|
397
408
|
* Legacy SSE terminal event. New streams emit
|