@agent-native/core 0.204.0 → 0.204.1-nightly-20261008034100

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.
Files changed (106) hide show
  1. package/dist/action.js +5 -6
  2. package/dist/agent/auto-continue.d.ts +35 -0
  3. package/dist/agent/auto-continue.js +38 -0
  4. package/dist/agent/connection-required-note.d.ts +52 -0
  5. package/dist/agent/connection-required-note.js +187 -0
  6. package/dist/agent/jev-tool-prefetch.d.ts +5 -1
  7. package/dist/agent/jev-tool-prefetch.js +19 -6
  8. package/dist/agent/previous-turn-note.d.ts +9 -0
  9. package/dist/agent/previous-turn-note.js +48 -0
  10. package/dist/agent/production-agent.d.ts +17 -13
  11. package/dist/agent/production-agent.js +185 -43
  12. package/dist/agent/reaped-run-thread.d.ts +13 -0
  13. package/dist/agent/reaped-run-thread.js +45 -0
  14. package/dist/agent/run-store.d.ts +29 -5
  15. package/dist/agent/run-store.js +144 -30
  16. package/dist/agent/thread-data-builder.d.ts +16 -0
  17. package/dist/agent/thread-data-builder.js +30 -8
  18. package/dist/agent/thread-message-projection.js +5 -3
  19. package/dist/agent/tool-search.js +5 -1
  20. package/dist/agent/types.d.ts +3 -0
  21. package/dist/audit/config.d.ts +6 -0
  22. package/dist/audit/config.js +12 -1
  23. package/dist/audit/index.d.ts +1 -1
  24. package/dist/audit/index.js +1 -1
  25. package/dist/audit/store.js +39 -4
  26. package/dist/cli/skills.d.ts +1 -0
  27. package/dist/cli/skills.js +1 -1
  28. package/dist/client/chat/agentkit-agent-native.js +83 -7
  29. package/dist/client/chat/agentkit-protocol.js +140 -67
  30. package/dist/client/chat/runtime.js +5 -3
  31. package/dist/client/use-run-stuck-detection.d.ts +18 -1
  32. package/dist/client/use-run-stuck-detection.js +115 -75
  33. package/dist/db/request-telemetry.d.ts +7 -1
  34. package/dist/db/request-telemetry.js +20 -1
  35. package/dist/deploy/build.d.ts +25 -1
  36. package/dist/deploy/build.js +176 -3
  37. package/dist/deploy/workspace-deploy.js +8 -3
  38. package/dist/extensions/fetch-tool.js +50 -14
  39. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  40. package/dist/integrations/adapters/email.js +11 -3
  41. package/dist/integrations/adapters/telegram.js +4 -2
  42. package/dist/jobs/actions/automation-no-op.d.ts +12 -0
  43. package/dist/jobs/actions/automation-no-op.js +20 -0
  44. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  45. package/dist/jobs/background-automation-runner.d.ts +8 -1
  46. package/dist/jobs/background-automation-runner.js +102 -19
  47. package/dist/jobs/run-history.d.ts +1 -1
  48. package/dist/jobs/run-history.js +8 -4
  49. package/dist/jobs/scheduler.js +25 -12
  50. package/dist/localization/automation-outcome-messages.d.ts +22 -0
  51. package/dist/localization/automation-outcome-messages.js +90 -0
  52. package/dist/localization/default-messages.d.ts +2 -0
  53. package/dist/localization/default-messages.js +4 -2
  54. package/dist/notifications/routes.d.ts +1 -1
  55. package/dist/observability/metrics.d.ts +3 -0
  56. package/dist/observability/metrics.js +10 -0
  57. package/dist/observability/migrations.js +22 -0
  58. package/dist/observability/routes.d.ts +13 -8
  59. package/dist/observability/routes.js +88 -14
  60. package/dist/observability/sentiment.d.ts +5 -0
  61. package/dist/observability/sentiment.js +75 -25
  62. package/dist/observability/store.d.ts +28 -0
  63. package/dist/observability/store.js +58 -0
  64. package/dist/observability/traces.js +37 -5
  65. package/dist/org/index.d.ts +2 -2
  66. package/dist/progress/actions.js +1 -0
  67. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  68. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  69. package/dist/provider-api/actions/provider-api.js +6 -2
  70. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  71. package/dist/provider-api/index.d.ts +1 -0
  72. package/dist/provider-api/index.js +18 -0
  73. package/dist/provider-api/result-outcome.d.ts +1 -0
  74. package/dist/provider-api/result-outcome.js +20 -0
  75. package/dist/provider-api/staging.js +10 -9
  76. package/dist/server/agent-chat/prompt-resources.d.ts +8 -2
  77. package/dist/server/agent-chat/prompt-resources.js +30 -7
  78. package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +19 -4
  79. package/dist/server/agent-chat/recurring-jobs-runtime.js +52 -16
  80. package/dist/server/agent-chat/sweep-trigger-auth.d.ts +18 -0
  81. package/dist/server/agent-chat/sweep-trigger-auth.js +36 -0
  82. package/dist/server/agent-chat-plugin.js +20 -13
  83. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  84. package/dist/server/auth.js +4 -4
  85. package/dist/server/framework-request-handler.js +19 -0
  86. package/dist/server/http-response-telemetry.js +4 -1
  87. package/dist/server/realtime-token.d.ts +1 -1
  88. package/dist/server/request-context.d.ts +6 -0
  89. package/dist/shared/context-status.d.ts +21 -0
  90. package/dist/shared/context-status.js +54 -0
  91. package/dist/templates/chat/app/components/chat/ChatRouteContent.tsx +8 -1
  92. package/dist/templates/workspace-core/.agents/skills/automations/SKILL.md +8 -1
  93. package/dist/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +3 -1
  94. package/dist/triggers/actions/manage-automation.d.ts +7 -7
  95. package/dist/triggers/dispatcher.js +10 -7
  96. package/docs/content/audit-log.mdx +3 -3
  97. package/docs/content/cloudflare.mdx +27 -0
  98. package/docs/content/deployment-environment-variables.mdx +2 -0
  99. package/docs/content/durable-resume.mdx +3 -1
  100. package/docs/content/environment-variables.mdx +2 -0
  101. package/docs/content/recurring-jobs.mdx +29 -9
  102. package/docs/content/vercel.mdx +30 -0
  103. package/package.json +3 -3
  104. package/src/templates/chat/app/components/chat/ChatRouteContent.tsx +8 -1
  105. package/src/templates/workspace-core/.agents/skills/automations/SKILL.md +8 -1
  106. package/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +3 -1
@@ -6,7 +6,7 @@ import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
6
6
  import { lockAndAssertServicePrincipalMayStartRun, prepareServicePrincipalRunStart, recordServicePrincipalDenial, } from "../org/service-principal-guard.js";
7
7
  import { captureError } from "../server/capture-error.js";
8
8
  import { isRequestedStopAbortReason } from "./abort-reasons.js";
9
- import { admitAutoContinue, } from "./auto-continue.js";
9
+ import { admitAutoContinue, admitManualContinue, } from "./auto-continue.js";
10
10
  import { LLM_MISSING_CREDENTIALS_ERROR_CODE, LLM_MISSING_CREDENTIALS_MESSAGE, } from "./engine/credential-errors.js";
11
11
  import { isContinuationTerminalReason } from "./types.js";
12
12
  let _initPromise;
@@ -697,6 +697,31 @@ export async function countRunsForTurn(threadId, turnId) {
697
697
  const count = Number(raw);
698
698
  return Number.isFinite(count) ? count : 0;
699
699
  }
700
+ /**
701
+ * The newest run of the turn before `turnId` in a thread: how the previous
702
+ * request ended. Null when the thread has no earlier turn.
703
+ */
704
+ export async function getPreviousTurnNewestRun(threadId, turnId) {
705
+ await ensureRunTables();
706
+ const { rows } = await getDbExec().execute({
707
+ sql: `SELECT COALESCE(turn_id, id) AS turn_id, status, terminal_reason
708
+ FROM agent_runs
709
+ WHERE thread_id = ?
710
+ AND COALESCE(turn_id, id) <> ?
711
+ AND dispatch_mode IS DISTINCT FROM 'turn-abort'
712
+ ORDER BY started_at DESC
713
+ LIMIT 1`,
714
+ args: [threadId, turnId],
715
+ });
716
+ const row = rows[0];
717
+ return row
718
+ ? {
719
+ turnId: row.turn_id,
720
+ status: row.status,
721
+ terminalReason: row.terminal_reason,
722
+ }
723
+ : null;
724
+ }
700
725
  export async function getTurnInitiatorByRun(runId) {
701
726
  await ensureRunTables();
702
727
  const { rows } = await getDbExec().execute({
@@ -810,7 +835,12 @@ export async function tryClaimRunSlot(threadId, runId, maxStaleMs, options) {
810
835
  const client = getDbExec();
811
836
  const now = Date.now();
812
837
  const turnId = options?.turnId ?? runId;
813
- const replayCompletedTurn = options?.replayCompletedTurn === true && Boolean(options.turnId);
838
+ const continueOf = options?.continueOf;
839
+ // A continuation names a run that already ended, so replaying the turn's
840
+ // last finished run would hand back the stop it asked to move past.
841
+ const replayCompletedTurn = options?.replayCompletedTurn === true &&
842
+ Boolean(options.turnId) &&
843
+ !continueOf;
814
844
  if (!client.transaction) {
815
845
  throw new Error("Atomic run-slot claims require transaction support");
816
846
  }
@@ -885,40 +915,54 @@ export async function tryClaimRunSlot(threadId, runId, maxStaleMs, options) {
885
915
  }
886
916
  // Admitted under the thread's slot lock, so two tabs continuing the same
887
917
  // stop start one run and the count of continuations stays exact.
888
- if (options?.autoContinueOf) {
918
+ if (continueOf) {
889
919
  const turnRuns = await tx.execute({
890
920
  sql: `SELECT id, status, terminal_reason, completed_at,
891
921
  COUNT(auto_continue_of) OVER () AS auto_continues,
892
- MIN(started_at) OVER () AS turn_started_at
922
+ MIN(started_at) OVER () AS turn_started_at,
923
+ EXISTS (
924
+ SELECT 1 FROM agent_runs later
925
+ WHERE later.thread_id = ?
926
+ AND COALESCE(later.turn_id, later.id) <> ?
927
+ AND later.started_at > agent_runs.started_at
928
+ ) AS later_turn_started
893
929
  FROM agent_runs
894
930
  WHERE thread_id = ? AND turn_id = ?
895
931
  AND dispatch_mode IS DISTINCT FROM 'turn-abort'
896
932
  ORDER BY started_at DESC LIMIT 1`,
897
- args: [threadId, turnId],
933
+ args: [threadId, turnId, threadId, turnId],
898
934
  });
899
935
  const row = turnRuns.rows[0];
900
- const admission = admitAutoContinue({
901
- turn: row
902
- ? {
903
- newest: {
904
- id: row.id,
905
- status: row.status,
906
- terminalReason: row.terminal_reason,
907
- completedAt: row.completed_at === null ? null : Number(row.completed_at),
908
- },
909
- autoContinues: Number(row.auto_continues),
910
- startedAt: Number(row.turn_started_at),
911
- }
912
- : null,
913
- stoppedRunId: options.autoContinueOf,
914
- nowMs: now,
915
- });
936
+ const turn = row
937
+ ? {
938
+ newest: {
939
+ id: row.id,
940
+ status: row.status,
941
+ terminalReason: row.terminal_reason,
942
+ completedAt: row.completed_at === null ? null : Number(row.completed_at),
943
+ },
944
+ autoContinues: Number(row.auto_continues),
945
+ startedAt: Number(row.turn_started_at),
946
+ }
947
+ : null;
948
+ const admission = continueOf.trigger === "auto"
949
+ ? admitAutoContinue({
950
+ turn,
951
+ stoppedRunId: continueOf.runId,
952
+ nowMs: now,
953
+ })
954
+ : admitManualContinue({
955
+ turn,
956
+ stoppedRunId: continueOf.runId,
957
+ laterTurnStarted: row?.later_turn_started === true ||
958
+ row?.later_turn_started === "t",
959
+ });
916
960
  if (!admission.admit) {
917
961
  return {
918
962
  result: {
919
963
  claimed: false,
920
964
  activeRunId: null,
921
- autoContinueRefused: admission.code,
965
+ continueRefused: admission.code,
922
966
  },
923
967
  };
924
968
  }
@@ -937,7 +981,8 @@ export async function tryClaimRunSlot(threadId, runId, maxStaleMs, options) {
937
981
  options?.dispatchMode ?? null,
938
982
  options?.dispatchPayload ?? null,
939
983
  continuationOrder,
940
- options?.autoContinueOf ?? null,
984
+ // Only automatic continuations count toward the turn's cap.
985
+ continueOf?.trigger === "auto" ? continueOf.runId : null,
941
986
  ],
942
987
  });
943
988
  if ((inserted.rowsAffected ?? 0) !== 1) {
@@ -1416,7 +1461,7 @@ function priorDiagStageLabel(raw) {
1416
1461
  }
1417
1462
  return raw.slice(0, 120);
1418
1463
  }
1419
- async function reapSingleStaleRun(runId, maxStaleMs) {
1464
+ async function reapSingleStaleRun(runId, source, maxStaleMs) {
1420
1465
  const completedAt = Date.now();
1421
1466
  const staleClause = typeof maxStaleMs === "number"
1422
1467
  ? `${livenessBasisSql()} < ?`
@@ -1511,6 +1556,11 @@ async function reapSingleStaleRun(runId, maxStaleMs) {
1511
1556
  forensics = read.forensics;
1512
1557
  priorStageInfo = read.priorStageInfo;
1513
1558
  }
1559
+ // Saved before the successor starts: a successor that folds its own reply
1560
+ // first would have this older run's steps land after its, and take its
1561
+ // error and status.
1562
+ if (reaped)
1563
+ await finalizeStaleRun(runId, source);
1514
1564
  if (reaped && outcome && outcome.outcome !== "not_background") {
1515
1565
  const outcomeDetail = outcome.outcome === "recovered"
1516
1566
  ? `recovered successorRunId=${outcome.successorRunId}`
@@ -1532,12 +1582,9 @@ export async function reapIfStale(runId, maxStaleMs) {
1532
1582
  await ensureRunTables();
1533
1583
  if (await reconcileTerminalRunFromEvents(runId))
1534
1584
  return false;
1535
- const reaped = await reapSingleStaleRun(runId, maxStaleMs);
1585
+ const reaped = await reapSingleStaleRun(runId, "reap-if-stale", maxStaleMs);
1536
1586
  if (!reaped && (await reconcileTerminalRunFromEvents(runId)))
1537
1587
  return false;
1538
- if (reaped) {
1539
- await safeAppendTerminalRunEvent(runId, STALE_RUN_ERROR_EVENT, "reap-if-stale");
1540
- }
1541
1588
  return reaped;
1542
1589
  }
1543
1590
  export async function reapUnclaimedBackgroundRun(runId) {
@@ -1826,6 +1873,51 @@ export async function getRunById(runId) {
1826
1873
  terminalReason: r.terminal_reason ?? null,
1827
1874
  };
1828
1875
  }
1876
+ /**
1877
+ * What saving a run the server ended on its behalf needs: the run's ledger,
1878
+ * and whether anything ran after it in its turn or in a later turn.
1879
+ */
1880
+ export async function readStoppedRunForThreadFold(runId) {
1881
+ await ensureRunTables();
1882
+ const client = getDbExec();
1883
+ const { rows } = await client.execute({
1884
+ sql: `SELECT r.thread_id, COALESCE(r.turn_id, r.id) AS turn_id,
1885
+ r.status, r.started_at,
1886
+ EXISTS (
1887
+ SELECT 1 FROM agent_runs later
1888
+ WHERE later.thread_id = r.thread_id
1889
+ AND COALESCE(later.turn_id, later.id) = COALESCE(r.turn_id, r.id)
1890
+ AND later.id <> r.id
1891
+ AND later.started_at >= r.started_at
1892
+ AND later.dispatch_mode IS DISTINCT FROM 'turn-abort'
1893
+ ) AS continued_in_turn,
1894
+ EXISTS (
1895
+ SELECT 1 FROM agent_runs later
1896
+ WHERE later.thread_id = r.thread_id
1897
+ AND COALESCE(later.turn_id, later.id) <> COALESCE(r.turn_id, r.id)
1898
+ AND later.started_at > r.started_at
1899
+ ) AS later_turn_started
1900
+ FROM agent_runs r WHERE r.id = ?`,
1901
+ args: [runId],
1902
+ });
1903
+ const row = rows[0];
1904
+ // A sweep can name a run whose worker answered again before it was reaped;
1905
+ // that worker still saves its own reply.
1906
+ if (!row || row.status === "running")
1907
+ return null;
1908
+ const events = [];
1909
+ for (const { seq, eventData } of await getRunEventsSince(runId, 0)) {
1910
+ events.push({ seq, event: JSON.parse(eventData) });
1911
+ }
1912
+ return {
1913
+ threadId: row.thread_id,
1914
+ turnId: row.turn_id,
1915
+ startedAt: Number(row.started_at),
1916
+ events,
1917
+ continuedInTurn: row.continued_in_turn === true || row.continued_in_turn === "t",
1918
+ laterTurnStarted: row.later_turn_started === true || row.later_turn_started === "t",
1919
+ };
1920
+ }
1829
1921
  export async function getLastTerminalRunEvent(runId) {
1830
1922
  await ensureRunTables();
1831
1923
  const client = getDbExec();
@@ -2057,7 +2149,7 @@ export async function reapAllStaleRuns() {
2057
2149
  if (typeof id !== "string")
2058
2150
  continue;
2059
2151
  try {
2060
- if (await reapSingleStaleRun(id))
2152
+ if (await reapSingleStaleRun(id, "reap-all-stale"))
2061
2153
  reapedCount += 1;
2062
2154
  }
2063
2155
  catch (error) {
@@ -2256,7 +2348,7 @@ async function cleanupOldRunsInternal(olderThanMs, erroredOlderThanMs) {
2256
2348
  for (const row of stale.rows) {
2257
2349
  const id = row.id;
2258
2350
  if (typeof id === "string") {
2259
- await safeAppendTerminalRunEvent(id, STALE_RUN_ERROR_EVENT, "cleanup-old-runs");
2351
+ await finalizeStaleRun(id, "cleanup-old-runs");
2260
2352
  }
2261
2353
  }
2262
2354
  await pruneAndRollUpPrunedRunOutcomes(client, cutoff, erroredCutoff);
@@ -2308,6 +2400,28 @@ export async function listErroredRuns(options) {
2308
2400
  export async function ensureTerminalRunEvent(runId, event) {
2309
2401
  return appendTerminalRunEvent(runId, event);
2310
2402
  }
2403
+ /**
2404
+ * Ends a run the server reaped because its worker stopped: records the stop
2405
+ * in the run's ledger, then saves what the run did into its thread, which the
2406
+ * dead worker never got to do.
2407
+ */
2408
+ async function finalizeStaleRun(runId, source) {
2409
+ await safeAppendTerminalRunEvent(runId, STALE_RUN_ERROR_EVENT, source);
2410
+ try {
2411
+ const { foldReapedRunIntoThread } = await import("./reaped-run-thread.js");
2412
+ await foldReapedRunIntoThread(runId);
2413
+ }
2414
+ catch (error) {
2415
+ captureError(error, {
2416
+ tags: {
2417
+ component: "agent-run-store",
2418
+ operation: "fold-reaped-run",
2419
+ source,
2420
+ },
2421
+ extra: { runId },
2422
+ });
2423
+ }
2424
+ }
2311
2425
  async function safeAppendTerminalRunEvent(runId, event, source) {
2312
2426
  let firstError;
2313
2427
  try {
@@ -75,6 +75,12 @@ export declare function resumeThreadHistoryForRequest(threadData: string | Recor
75
75
  messages: EngineMessage[];
76
76
  foundTurnPrompt: boolean;
77
77
  };
78
+ /**
79
+ * The turn the thread's newest prompt was sent in, when the server stamped
80
+ * it. A prompt whose run was refused before it started has no run row, so
81
+ * this is how a continuation learns a newer prompt is waiting.
82
+ */
83
+ export declare function latestPromptTurnId(threadData: string | Record<string, unknown>): string | undefined;
78
84
  export declare function threadMessageTextForEngine(message: any): string;
79
85
  export interface CodeAgentThreadTranscriptEvent {
80
86
  id: string;
@@ -119,6 +125,16 @@ export declare function applySubmittedUserMessage(repo: any, userMessage: UserMe
119
125
  } | {
120
126
  status: "already_claimed" | "claim_expired";
121
127
  };
128
+ /**
129
+ * Whether a stored user message is the prompt this run answers, rather than
130
+ * one sent after it started. Messages saved without the server's turn stamp
131
+ * fall back to their creation time.
132
+ */
133
+ export declare function isRunPrompt(user: any, run: {
134
+ runId: string;
135
+ turnId?: string | null;
136
+ startedAt: number;
137
+ }): boolean;
122
138
  export type ThreadSuggestionRun = Pick<ActiveRun, "runId" | "threadId" | "turnId" | "startedAt" | "status" | "events" | "abortReason">;
123
139
  export declare function foldThreadRunSuggestions(repo: any, run: ThreadSuggestionRun): any;
124
140
  export declare function mergeThreadDataForClientSave(existingRepo: any, incomingRepo: any, options?: MergeThreadDataOptions): any;
@@ -864,6 +864,16 @@ export function resumeThreadHistoryForRequest(threadData) {
864
864
  foundTurnPrompt: true,
865
865
  };
866
866
  }
867
+ /**
868
+ * The turn the thread's newest prompt was sent in, when the server stamped
869
+ * it. A prompt whose run was refused before it started has no run row, so
870
+ * this is how a continuation learns a newer prompt is waiting.
871
+ */
872
+ export function latestPromptTurnId(threadData) {
873
+ const data = typeof threadData === "string" ? JSON.parse(threadData) : threadData;
874
+ const turnId = latestStoredUser(data)?.metadata?.custom?.submittedTurnId;
875
+ return typeof turnId === "string" ? turnId : undefined;
876
+ }
867
877
  const MAX_INTEGRATION_ARTIFACTS_IN_CONTEXT = 12;
868
878
  const MAX_INTEGRATION_ARTIFACT_FIELD_CHARS = 500;
869
879
  function boundedString(value) {
@@ -1389,21 +1399,28 @@ function latestStoredUser(repo) {
1389
1399
  ?.map((entry) => getStoredMessage(entry))
1390
1400
  .findLast((message) => message?.role === "user");
1391
1401
  }
1402
+ /**
1403
+ * Whether a stored user message is the prompt this run answers, rather than
1404
+ * one sent after it started. Messages saved without the server's turn stamp
1405
+ * fall back to their creation time.
1406
+ */
1407
+ export function isRunPrompt(user, run) {
1408
+ const userContext = user?.metadata?.custom;
1409
+ if (userContext?.submittedTurnId) {
1410
+ return userContext.submittedTurnId === run.turnId;
1411
+ }
1412
+ return (userContext?.submittedRunId === run.runId ||
1413
+ !user?.createdAt ||
1414
+ new Date(user.createdAt).getTime() <= run.startedAt);
1415
+ }
1392
1416
  function clearThreadSuggestions(repo) {
1393
1417
  return repo.agentKit
1394
1418
  ? { ...repo, agentKit: { ...repo.agentKit, suggestions: [] } }
1395
1419
  : repo;
1396
1420
  }
1397
1421
  export function foldThreadRunSuggestions(repo, run) {
1398
- const user = latestStoredUser(repo);
1399
- const userContext = user?.metadata?.custom;
1400
- if (userContext?.submittedTurnId
1401
- ? userContext.submittedTurnId !== run.turnId
1402
- : userContext?.submittedRunId !== run.runId &&
1403
- user?.createdAt &&
1404
- new Date(user.createdAt).getTime() > run.startedAt) {
1422
+ if (!isRunPrompt(latestStoredUser(repo), run))
1405
1423
  return repo;
1406
- }
1407
1424
  const previous = repo.agentKit ?? {};
1408
1425
  const latest = latestSnapshotRun(previous.runs);
1409
1426
  const startedAt = new Date(run.startedAt).toISOString();
@@ -2872,6 +2889,11 @@ export function foldAssistantTurn(repo, assistantMsg, options) {
2872
2889
  }
2873
2890
  if (incomingCustom.continued !== true)
2874
2891
  delete mergedCustom.continued;
2892
+ // A turn's failure is its newest run's: a run that continued past an
2893
+ // earlier stop clears the stop's error instead of inheriting it.
2894
+ if (!runAlreadyFolded && incomingCustom.runError === undefined) {
2895
+ delete mergedCustom.runError;
2896
+ }
2875
2897
  const mergedMessage = {
2876
2898
  ...lastMsg,
2877
2899
  content: normalizeAssistantToolCallIds({
@@ -157,13 +157,15 @@ function representedToolCallMessageIds(value, toolCalls) {
157
157
  ? rootCall.id
158
158
  : undefined;
159
159
  const call = id ? byId.get(id) : undefined;
160
+ // A tool call's identity is its id, name, and args. Never compare results:
161
+ // the stored root keeps the model-facing text while the snapshot output is
162
+ // the structured chat-UI result, so one call never compares equal and its
163
+ // reply renders twice.
160
164
  if (!call ||
161
165
  (typeof rootCall.toolName === "string" &&
162
166
  rootCall.toolName !== call.name) ||
163
167
  (Object.prototype.hasOwnProperty.call(rootCall, "args") &&
164
- !sameValue(rootCall.args, call.input)) ||
165
- (Object.prototype.hasOwnProperty.call(rootCall, "result") &&
166
- !sameValue(rootCall.result, call.output))) {
168
+ !sameValue(rootCall.args, call.input))) {
167
169
  return undefined;
168
170
  }
169
171
  if (typeof call.messageId === "string")
@@ -500,7 +500,11 @@ function scoreTool(input) {
500
500
  .join(" "));
501
501
  const all = `${name} ${source} ${description} ${params} ${input.kind}`;
502
502
  const phrase = searchableText(input.query);
503
- let score = 0;
503
+ // A query that is a tool's whole name is a request for that tool, not for
504
+ // whatever else repeats its words.
505
+ let score = input.name.toLowerCase() === input.query.trim().toLowerCase()
506
+ ? EXACT_NAME_SCORE
507
+ : 0;
504
508
  if (name.includes(phrase))
505
509
  score += 14;
506
510
  if (source && source.includes(phrase))
@@ -170,6 +170,8 @@ export interface AgentChatRequest {
170
170
  internalContinuation?: boolean;
171
171
  /** The time-limit stop this request continues, in the same turn. */
172
172
  autoContinueOfRunId?: string;
173
+ /** The stopped run a person chose to continue, in the same turn. */
174
+ continueOfRunId?: string;
173
175
  __backgroundRun?: {
174
176
  runId: string;
175
177
  turnId?: string;
@@ -273,6 +275,7 @@ export type AgentChatEvent = {
273
275
  input?: AgentToolInput;
274
276
  result: string;
275
277
  isError?: boolean;
278
+ errorCode?: string;
276
279
  completedSideEffect?: boolean;
277
280
  replayed?: true;
278
281
  fileMutation?: AgentFileMutationProof;
@@ -2,6 +2,12 @@ import type { ActionAuditConfig, AuditActorKind, AuditStatus } from "./types.js"
2
2
  export declare function normalizeAuditConfig(raw: unknown): ActionAuditConfig | undefined;
3
3
  export declare function resolveAuditAttach(config: ActionAuditConfig | undefined, readOnly: boolean | undefined): boolean;
4
4
  export declare function shouldRecordAudit(config: ActionAuditConfig | undefined, actionName: string): boolean;
5
+ /**
6
+ * Agent protocols: the app's own agent (`tool`) and the MCP, WebMCP, and A2A
7
+ * surfaces outside agents use. A call over one records as the agent acting
8
+ * for the signed-in user; that user's email does not make it a click.
9
+ */
10
+ export declare const AGENT_AUDIT_CALLERS: ReadonlySet<string>;
5
11
  export declare function deriveActorKind(caller: string | undefined, actorEmail: string | undefined | null, orgId?: string | null): AuditActorKind;
6
12
  export declare function isAuditDisabled(): boolean;
7
13
  export type { AuditStatus };
@@ -29,11 +29,22 @@ export function shouldRecordAudit(config, actionName) {
29
29
  return false;
30
30
  return true;
31
31
  }
32
+ /**
33
+ * Agent protocols: the app's own agent (`tool`) and the MCP, WebMCP, and A2A
34
+ * surfaces outside agents use. A call over one records as the agent acting
35
+ * for the signed-in user; that user's email does not make it a click.
36
+ */
37
+ export const AGENT_AUDIT_CALLERS = new Set([
38
+ "tool",
39
+ "mcp",
40
+ "webmcp",
41
+ "a2a",
42
+ ]);
32
43
  export function deriveActorKind(caller, actorEmail, orgId) {
33
44
  const serviceIdentity = parseServiceIdentityEmail(actorEmail);
34
45
  if (serviceIdentity && serviceIdentity.orgId === orgId)
35
46
  return "service";
36
- if (caller === "tool")
47
+ if (caller && AGENT_AUDIT_CALLERS.has(caller))
37
48
  return "agent";
38
49
  return actorEmail ? "human" : "system";
39
50
  }
@@ -1,5 +1,5 @@
1
1
  export type { ActionAuditConfig, AuditActorKind, AuditCallMeta, AuditEvent, AuditQueryFilters, AuditStatus, AuditTarget, AuditVisibility, } from "./types.js";
2
- export { deriveActorKind, isAuditDisabled, normalizeAuditConfig, resolveAuditAttach, shouldRecordAudit, } from "./config.js";
2
+ export { AGENT_AUDIT_CALLERS, deriveActorKind, isAuditDisabled, normalizeAuditConfig, resolveAuditAttach, shouldRecordAudit, } from "./config.js";
3
3
  export { redactArgsToJson } from "./redact.js";
4
4
  export { ensureAuditTables, insertAuditEvent, queryAuditEvents, queryAuditEventPage, queryAuditApps, getAuditEventById, deleteOldAuditEvents, type AuditEventPage, type AuditReadScope, type AuditTrail, } from "./store.js";
5
5
  export { AuditAccessError, resolveAuditReadScope } from "./read-scope.js";
@@ -1,4 +1,4 @@
1
- export { deriveActorKind, isAuditDisabled, normalizeAuditConfig, resolveAuditAttach, shouldRecordAudit, } from "./config.js";
1
+ export { AGENT_AUDIT_CALLERS, deriveActorKind, isAuditDisabled, normalizeAuditConfig, resolveAuditAttach, shouldRecordAudit, } from "./config.js";
2
2
  export { redactArgsToJson } from "./redact.js";
3
3
  export { ensureAuditTables, insertAuditEvent, queryAuditEvents, queryAuditEventPage, queryAuditApps, getAuditEventById, deleteOldAuditEvents, } from "./store.js";
4
4
  export { AuditAccessError, resolveAuditReadScope } from "./read-scope.js";
@@ -1,5 +1,6 @@
1
1
  import { getDbExec } from "../db/client.js";
2
2
  import { ensureColumnExists, ensureTableExists, ensureIndexExists, } from "../db/ddl-guard.js";
3
+ import { AGENT_AUDIT_CALLERS } from "./config.js";
3
4
  let _initPromise;
4
5
  export const AGENT_AUDIT_LOG_CREATE_SQL = `
5
6
  CREATE TABLE IF NOT EXISTS agent_audit_log (
@@ -115,13 +116,44 @@ export async function insertAuditEvent(event) {
115
116
  ],
116
117
  });
117
118
  }
119
+ // Rows recorded before outside agents (MCP, WebMCP, A2A) counted as agents
120
+ // stored `human` or `system` for them. Reads classify those by caller here and
121
+ // in `actorKindClause`, so an old row and a filter on it agree without
122
+ // rewriting the append-only log. A `service` row keeps its kind: org service
123
+ // principals mostly call over MCP.
124
+ const LEGACY_AGENT_KINDS = ["human", "system"];
125
+ function readActorKind(storedKind, caller) {
126
+ return LEGACY_AGENT_KINDS.includes(storedKind) &&
127
+ AGENT_AUDIT_CALLERS.has(caller)
128
+ ? "agent"
129
+ : storedKind;
130
+ }
131
+ function actorKindClause(kind) {
132
+ const callers = [...AGENT_AUDIT_CALLERS];
133
+ const agentCaller = `caller IN (${callers.map(() => "?").join(", ")})`;
134
+ if (kind === "agent") {
135
+ const legacy = LEGACY_AGENT_KINDS.map(() => "?").join(", ");
136
+ return {
137
+ sql: `(actor_kind = ? OR (actor_kind IN (${legacy}) AND ${agentCaller}))`,
138
+ args: [kind, ...LEGACY_AGENT_KINDS, ...callers],
139
+ };
140
+ }
141
+ if (LEGACY_AGENT_KINDS.includes(kind)) {
142
+ return {
143
+ sql: `(actor_kind = ? AND NOT ${agentCaller})`,
144
+ args: [kind, ...callers],
145
+ };
146
+ }
147
+ return { sql: "actor_kind = ?", args: [kind] };
148
+ }
118
149
  function mapRow(row) {
150
+ const caller = String(row.caller);
119
151
  return {
120
152
  id: String(row.id),
121
153
  createdAt: Number(row.created_at),
122
154
  action: String(row.action),
123
- caller: String(row.caller),
124
- actorKind: row.actor_kind,
155
+ caller,
156
+ actorKind: readActorKind(row.actor_kind, caller),
125
157
  actorEmail: row.actor_email ?? null,
126
158
  orgId: row.org_id ?? null,
127
159
  threadId: row.thread_id ?? null,
@@ -230,8 +262,11 @@ async function selectAuditRows(scope, filters, limit) {
230
262
  push("target_type = ?", filters.targetType);
231
263
  if (filters.targetId)
232
264
  push("target_id = ?", filters.targetId);
233
- if (filters.actorKind)
234
- push("actor_kind = ?", filters.actorKind);
265
+ if (filters.actorKind) {
266
+ const clause = actorKindClause(filters.actorKind);
267
+ where.push(clause.sql);
268
+ args.push(...clause.args);
269
+ }
235
270
  if (filters.actorEmail)
236
271
  push("actor_email = ?", filters.actorEmail);
237
272
  if (filters.status)
@@ -197,6 +197,7 @@ interface SkillsScopePromptContext {
197
197
  interface SkillsPlanModePromptContext {
198
198
  initialMode: PlanInstallMode;
199
199
  }
200
+ export declare function builtInCommandsRootForAgent(agent: string, scope: "project" | "user", baseDir: string): string;
200
201
  export declare function parseSkillsArgs(argv: string[]): ParsedSkillsArgs;
201
202
  export declare function addAgentNativeSkill(parsed: ParsedSkillsArgs, options?: RunSkillsOptions): Promise<SkillsAddResult>;
202
203
  export declare function runSkills(argv: string[], options?: RunSkillsOptions): Promise<void>;
@@ -984,7 +984,7 @@ function builtInSkillsRootForAgent(agent, scope, baseDir) {
984
984
  }
985
985
  return path.join(home, ".claude", "skills");
986
986
  }
987
- function builtInCommandsRootForAgent(agent, scope, baseDir) {
987
+ export function builtInCommandsRootForAgent(agent, scope, baseDir) {
988
988
  const home = homeDir() ?? baseDir;
989
989
  if (scope === "project") {
990
990
  if (agent === "codex")