@agent-native/core 0.220.0 → 0.220.1-beta-embedded-chat-20261025-20261025150325

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 (131) hide show
  1. package/dist/action.d.ts +18 -0
  2. package/dist/action.js +15 -0
  3. package/dist/agent/actions/list-model-providers.d.ts +6 -0
  4. package/dist/agent/actions/list-model-providers.js +104 -21
  5. package/dist/agent/harness/background.js +119 -30
  6. package/dist/agent/harness/index.d.ts +1 -1
  7. package/dist/agent/harness/index.js +1 -1
  8. package/dist/agent/harness/lifecycle.d.ts +16 -0
  9. package/dist/agent/harness/lifecycle.js +1175 -87
  10. package/dist/agent/harness/migrations.js +21 -0
  11. package/dist/agent/harness/runner.d.ts +5 -0
  12. package/dist/agent/harness/runner.js +186 -43
  13. package/dist/agent/harness/store.d.ts +131 -0
  14. package/dist/agent/harness/store.js +1384 -13
  15. package/dist/agent/production-agent.js +76 -17
  16. package/dist/agent/run-loop-with-resume.js +62 -19
  17. package/dist/agent/run-manager.js +41 -10
  18. package/dist/agent/run-store.d.ts +2 -0
  19. package/dist/agent/run-store.fencing.worker.js +115 -3
  20. package/dist/agent/run-store.js +80 -30
  21. package/dist/app-config/observability.d.ts +5 -0
  22. package/dist/app-config/observability.js +7 -0
  23. package/dist/app-config/schema.d.ts +5 -0
  24. package/dist/audit/redact.d.ts +7 -1
  25. package/dist/audit/redact.js +62 -22
  26. package/dist/automation/index.d.ts +1 -1
  27. package/dist/cli/code-agent-executor.js +28 -5
  28. package/dist/cli/code-agent-mcp-config.d.ts +4 -0
  29. package/dist/cli/code-agent-mcp-config.js +7 -1
  30. package/dist/client/sign-out.js +11 -4
  31. package/dist/client/sse-event-processor.d.ts +7 -0
  32. package/dist/client/sse-event-processor.js +3 -2
  33. package/dist/client/use-action.d.ts +4 -1
  34. package/dist/client/use-action.js +3 -1
  35. package/dist/client/use-session.d.ts +2 -0
  36. package/dist/client/use-session.js +10 -0
  37. package/dist/code-agents/background-run.d.ts +2 -1
  38. package/dist/collab/agent-presence.js +9 -3
  39. package/dist/collab/awareness-post.d.ts +2 -0
  40. package/dist/collab/awareness-post.js +101 -0
  41. package/dist/collab/awareness-sequence.d.ts +3 -0
  42. package/dist/collab/awareness-sequence.js +3 -0
  43. package/dist/collab/awareness-store.d.ts +12 -3
  44. package/dist/collab/awareness-store.js +426 -60
  45. package/dist/collab/awareness.d.ts +23 -2
  46. package/dist/collab/awareness.js +376 -41
  47. package/dist/collab/client.js +101 -36
  48. package/dist/collab/payload.d.ts +4 -0
  49. package/dist/collab/payload.js +15 -0
  50. package/dist/collab/routes.d.ts +1 -1
  51. package/dist/collab/routes.js +4 -26
  52. package/dist/collab/struct-routes.js +3 -22
  53. package/dist/credentials/index.js +34 -15
  54. package/dist/db/schema-snapshot.generated.js +54 -3
  55. package/dist/file-upload/actions/get-file-storage.js +1 -1
  56. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  57. package/dist/file-upload/storage-settings.d.ts +8 -1
  58. package/dist/file-upload/storage-settings.js +92 -21
  59. package/dist/identity/rekey.js +9 -0
  60. package/dist/integrations/actions/confirm-messaging-channel-credential-removal.d.ts +10 -0
  61. package/dist/integrations/actions/confirm-messaging-channel-credential-removal.js +23 -0
  62. package/dist/integrations/actions/list-messaging-channels.js +1 -1
  63. package/dist/integrations/actions/manage-messaging-channel.d.ts +4 -0
  64. package/dist/integrations/actions/manage-messaging-channel.js +13 -3
  65. package/dist/integrations/actions/prepare-messaging-channel-credential-removal.d.ts +7 -0
  66. package/dist/integrations/actions/prepare-messaging-channel-credential-removal.js +17 -0
  67. package/dist/integrations/channel-settings.d.ts +31 -4
  68. package/dist/integrations/channel-settings.js +353 -65
  69. package/dist/integrations/google-docs-poller.d.ts +9 -1
  70. package/dist/integrations/google-docs-poller.js +16 -5
  71. package/dist/integrations/pending-tasks-retry-job.d.ts +1 -1
  72. package/dist/integrations/pending-tasks-retry-job.js +6 -2
  73. package/dist/integrations/plugin.js +36 -5
  74. package/dist/integrations/remote-push-delivery-job.d.ts +1 -1
  75. package/dist/integrations/remote-push-delivery-job.js +6 -2
  76. package/dist/integrations/remote-retry-job.d.ts +1 -1
  77. package/dist/integrations/remote-retry-job.js +6 -2
  78. package/dist/integrations/webhook-handler.js +4 -2
  79. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  80. package/dist/mcp-client/config.d.ts +2 -0
  81. package/dist/mcp-client/manager.js +15 -0
  82. package/dist/mcp-client/remote-store.d.ts +9 -0
  83. package/dist/mcp-client/remote-store.js +42 -11
  84. package/dist/mcp-client/routes.js +57 -4
  85. package/dist/observability/hosted-model-experiment.d.ts +63 -0
  86. package/dist/observability/hosted-model-experiment.js +92 -0
  87. package/dist/observability/traces.js +10 -9
  88. package/dist/org/handlers.d.ts +2 -2
  89. package/dist/org/index.d.ts +1 -1
  90. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  91. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  92. package/dist/provider-api/corpus-jobs.js +3 -21
  93. package/dist/provider-api/index.js +17 -23
  94. package/dist/provider-api/request-body.d.ts +1 -0
  95. package/dist/provider-api/request-body.js +50 -0
  96. package/dist/provider-api/staging.js +2 -20
  97. package/dist/resources/handlers.d.ts +1 -1
  98. package/dist/secrets/errors.d.ts +11 -0
  99. package/dist/secrets/errors.js +15 -0
  100. package/dist/secrets/routes.d.ts +10 -10
  101. package/dist/secrets/storage.d.ts +27 -0
  102. package/dist/secrets/storage.js +141 -32
  103. package/dist/server/agent-chat-plugin.js +37 -0
  104. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  105. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  106. package/dist/server/background-jobs.d.ts +3 -0
  107. package/dist/server/background-jobs.js +33 -0
  108. package/dist/server/collab-plugin.js +19 -10
  109. package/dist/server/core-routes-plugin.d.ts +19 -1
  110. package/dist/server/core-routes-plugin.js +129 -17
  111. package/dist/server/credential-provider.d.ts +5 -4
  112. package/dist/server/credential-provider.js +153 -23
  113. package/dist/server/dev-database-lifecycle.js +13 -3
  114. package/dist/server/interval-job.d.ts +2 -1
  115. package/dist/server/interval-job.js +1 -1
  116. package/dist/server/secret-key-aliases.js +20 -2
  117. package/dist/shared/poll-engine.d.ts +2 -1
  118. package/dist/shared/poll-engine.js +25 -1
  119. package/dist/shared/progress-events.d.ts +1 -0
  120. package/dist/shared/progress-events.js +13 -0
  121. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +95 -76
  122. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +91 -36
  123. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +72 -31
  124. package/dist/triggers/actions/manage-automation.d.ts +4 -4
  125. package/dist/triggers/dispatcher.d.ts +1 -0
  126. package/dist/triggers/dispatcher.js +28 -2
  127. package/package.json +4 -4
  128. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +95 -76
  129. package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +91 -36
  130. package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +72 -31
  131. package/tsconfig.base.json +2 -0
package/dist/action.d.ts CHANGED
@@ -23,8 +23,26 @@ export interface ActionAutomationContext {
23
23
  triggerName: string;
24
24
  policyId?: string;
25
25
  }
26
+ export declare const DEFAULT_TOOL_RESULT_CHARS = 50000;
27
+ /**
28
+ * The most characters of a pretty-printed result the agent keeps for a call:
29
+ * the action's own limit, else the agent-wide limit, else the default, bounded
30
+ * by any hard limit. The agent loop and actions that fit their own results
31
+ * resolve it here so they cannot disagree.
32
+ */
33
+ export declare function resolveToolMaxResultChars(action: {
34
+ maxResultChars?: number;
35
+ }, limits?: {
36
+ maxResultChars?: number;
37
+ hardMaxResultChars?: number;
38
+ }): number;
26
39
  export interface ActionRunContext {
27
40
  send?: (event: AgentChatEvent) => void;
41
+ /**
42
+ * Result cap the agent loop enforces on this call, from
43
+ * `resolveToolMaxResultChars`. Absent outside the agent loop.
44
+ */
45
+ maxResultChars?: number;
28
46
  userEmail?: string;
29
47
  /**
30
48
  * Request headers for server-only actions that must delegate to an
package/dist/action.js CHANGED
@@ -48,6 +48,21 @@ export async function recordInvalidActionCall(entry, args, ctx, error) {
48
48
  }
49
49
  return true;
50
50
  }
51
+ export const DEFAULT_TOOL_RESULT_CHARS = 50_000;
52
+ /**
53
+ * The most characters of a pretty-printed result the agent keeps for a call:
54
+ * the action's own limit, else the agent-wide limit, else the default, bounded
55
+ * by any hard limit. The agent loop and actions that fit their own results
56
+ * resolve it here so they cannot disagree.
57
+ */
58
+ export function resolveToolMaxResultChars(action, limits) {
59
+ const configured = action.maxResultChars ??
60
+ limits?.maxResultChars ??
61
+ DEFAULT_TOOL_RESULT_CHARS;
62
+ return typeof limits?.hardMaxResultChars === "number"
63
+ ? Math.min(configured, limits.hardMaxResultChars)
64
+ : configured;
65
+ }
51
66
  export class ActionContractError extends Error {
52
67
  actionContractError = true;
53
68
  errorCode;
@@ -18,6 +18,12 @@ export interface ModelProviderKey {
18
18
  rejectedAt?: number;
19
19
  /** The rejection markers couldn't be read, so nobody knows if it works. */
20
20
  rejectionUnknown?: true;
21
+ /** A saved key or endpoint exists but could not be decrypted. */
22
+ unreadable?: true;
23
+ /** At least one stored API key name could not be decrypted. */
24
+ unreadableKey?: true;
25
+ /** The stored OpenAI-compatible endpoint could not be decrypted. */
26
+ unreadableEndpoint?: true;
21
27
  /**
22
28
  * Stored in a legacy `workspace` row: the organization's (`workspace` /
23
29
  * orgId) for `scope: "org"`, the pre-organization solo row
@@ -2,8 +2,9 @@ import { z } from "zod";
2
2
  import { defineAction, fail } from "../../action.js";
3
3
  import { AGENT_PROVIDER_CATALOG, } from "../../client/agent-provider-catalog.js";
4
4
  import { canManageOrg } from "../../org/permissions.js";
5
+ import { AppSecretDecryptionError } from "../../secrets/errors.js";
5
6
  import { secretKeyNames } from "../../secrets/key-aliases.js";
6
- import { readAppSecrets, } from "../../secrets/storage.js";
7
+ import { readAppSecret, readAppSecrets, } from "../../secrets/storage.js";
7
8
  import { readProviderCredentialRejections } from "../../server/credential-provider.js";
8
9
  import { isPersonalProviderKeyUseRestricted, readOrgMemberRole, } from "../../server/personal-provider-key-policy.js";
9
10
  import { getRequestContext } from "../../server/request-context.js";
@@ -30,6 +31,40 @@ function firstPresent(rows, names) {
30
31
  }
31
32
  return null;
32
33
  }
34
+ async function readScopeSecrets(source, keys) {
35
+ try {
36
+ return {
37
+ rows: await readAppSecrets({ keys, ...source }),
38
+ unreadableKeys: new Set(),
39
+ };
40
+ }
41
+ catch (error) {
42
+ if (!(error instanceof AppSecretDecryptionError) ||
43
+ error.scope !== source.scope ||
44
+ error.scopeId !== source.scopeId) {
45
+ throw error;
46
+ }
47
+ }
48
+ const rows = new Map();
49
+ const unreadableKeys = new Set();
50
+ await Promise.all(keys.map(async (key) => {
51
+ try {
52
+ const row = await readAppSecret({ key, ...source });
53
+ if (row)
54
+ rows.set(key, row);
55
+ }
56
+ catch (error) {
57
+ if (error instanceof AppSecretDecryptionError &&
58
+ error.scope === source.scope &&
59
+ error.scopeId === source.scopeId) {
60
+ unreadableKeys.add(key);
61
+ return;
62
+ }
63
+ throw error;
64
+ }
65
+ }));
66
+ return { rows, unreadableKeys };
67
+ }
33
68
  /**
34
69
  * The stored rows the credential resolver reads for one listing scope, in its
35
70
  * precedence order. Legacy `workspace` rows power chats like the first-class
@@ -50,35 +85,70 @@ async function readScopeKeys(scope, scopeId, reveal) {
50
85
  const names = providerKeyNames();
51
86
  const keys = names.flatMap(({ primary, endpoint }) => endpoint ? [...primary, endpoint] : primary);
52
87
  const sources = storedRowsFor(scope, scopeId);
53
- const reads = await Promise.all(sources.map((source) => readAppSecrets({ keys, ...source })));
88
+ const reads = await Promise.all(sources.map((source) => readScopeSecrets(source, keys)));
54
89
  const found = [];
55
90
  const endpointProviders = new Set();
56
91
  for (const entry of names) {
57
- for (const [index, rows] of reads.entries()) {
58
- if (entry.endpoint && firstPresent(rows, [entry.endpoint])) {
92
+ let unreadableEndpointIndex = null;
93
+ let resolved = false;
94
+ for (const [index, read] of reads.entries()) {
95
+ const { rows, unreadableKeys } = read;
96
+ const endpoint = entry.endpoint
97
+ ? firstPresent(rows, [entry.endpoint])
98
+ : null;
99
+ const unreadablePrimary = entry.primary.some((key) => unreadableKeys.has(key));
100
+ const unreadableEndpoint = !!entry.endpoint && unreadableKeys.has(entry.endpoint);
101
+ if (unreadableEndpoint && unreadableEndpointIndex === null) {
102
+ unreadableEndpointIndex = index;
103
+ }
104
+ if (endpoint || unreadableEndpoint) {
59
105
  endpointProviders.add(entry.option.id);
60
106
  }
61
107
  const row = firstPresent(rows, entry.primary);
62
- if (!row)
63
- continue;
108
+ if (row) {
109
+ found.push({
110
+ names: entry,
111
+ row,
112
+ endpoint,
113
+ legacy: sources[index].scope === "workspace",
114
+ unreadableKey: unreadablePrimary,
115
+ unreadableEndpoint: unreadableEndpoint || unreadableEndpointIndex !== null,
116
+ });
117
+ resolved = true;
118
+ break;
119
+ }
120
+ if (unreadablePrimary) {
121
+ found.push({
122
+ names: entry,
123
+ row: null,
124
+ endpoint: null,
125
+ legacy: sources[index].scope === "workspace",
126
+ unreadableKey: true,
127
+ unreadableEndpoint: unreadableEndpointIndex !== null,
128
+ });
129
+ resolved = true;
130
+ break;
131
+ }
132
+ }
133
+ if (!resolved && unreadableEndpointIndex !== null) {
64
134
  found.push({
65
135
  names: entry,
66
- row,
67
- endpoint: entry.endpoint ? firstPresent(rows, [entry.endpoint]) : null,
68
- legacy: sources[index].scope === "workspace",
136
+ row: null,
137
+ endpoint: null,
138
+ legacy: sources[unreadableEndpointIndex].scope === "workspace",
139
+ unreadableKey: false,
140
+ unreadableEndpoint: true,
69
141
  });
70
- break;
71
142
  }
72
143
  }
73
144
  // Markers are fingerprinted with the canonical key name, the one the
74
145
  // runtime records a rejection under, whichever alias the value is stored as.
75
- const checkable = found.filter(({ names }) => names.option.id !== "ollama");
146
+ const checkable = found.flatMap(({ names, row }) => names.option.id !== "ollama" && row
147
+ ? [{ key: names.option.key, value: row.value }]
148
+ : []);
76
149
  let rejections;
77
150
  try {
78
- rejections = await readProviderCredentialRejections(checkable.map(({ names, row }) => ({
79
- key: names.option.key,
80
- value: row.value,
81
- })));
151
+ rejections = await readProviderCredentialRejections(checkable);
82
152
  }
83
153
  catch (error) {
84
154
  console.warn("[list-model-providers] could not read rejection markers", {
@@ -87,28 +157,41 @@ async function readScopeKeys(scope, scopeId, reveal) {
87
157
  rejections = null;
88
158
  }
89
159
  const result = new Map();
90
- for (const { names: entry, row, endpoint, legacy } of found) {
160
+ for (const { names: entry, row, endpoint, legacy, unreadableKey, unreadableEndpoint, } of found) {
91
161
  const id = entry.option.id;
92
162
  const isOllama = id === "ollama";
93
- const rejection = !isOllama && rejections ? rejections.get(entry.option.key) : undefined;
163
+ const rejection = !isOllama && row && rejections
164
+ ? rejections.get(entry.option.key)
165
+ : undefined;
94
166
  result.set(id, {
95
167
  scope,
96
- ...(reveal
168
+ ...(row && reveal
97
169
  ? {
98
170
  ...(isOllama ? { endpoint: row.value } : { masked: row.last4 }),
99
171
  ...(endpoint?.value ? { endpoint: endpoint.value } : {}),
100
172
  }
101
173
  : {}),
102
- updatedAt: row.updatedAt || null,
174
+ updatedAt: row?.updatedAt || null,
175
+ ...(unreadableKey || unreadableEndpoint
176
+ ? {
177
+ unreadable: true,
178
+ ...(unreadableKey ? { unreadableKey: true } : {}),
179
+ ...(unreadableEndpoint
180
+ ? { unreadableEndpoint: true }
181
+ : {}),
182
+ }
183
+ : {}),
103
184
  ...(rejection ? { rejectedAt: rejection.at } : {}),
104
- ...(!isOllama && !rejections ? { rejectionUnknown: true } : {}),
185
+ ...(!isOllama && row && !rejections
186
+ ? { rejectionUnknown: true }
187
+ : {}),
105
188
  ...(legacy ? { legacyWorkspaceRow: true } : {}),
106
189
  });
107
190
  }
108
191
  return { keys: result, endpointProviders };
109
192
  }
110
193
  export default defineAction({
111
- description: "List the model providers on Settings › Model: for each provider (OpenRouter, Ollama, Anthropic, OpenAI, Google Gemini, Groq, Mistral, Cohere), the organization's key and the caller's personal key, masked, with the endpoint URL saved for Ollama or an OpenAI-compatible gateway and when the provider rejected the key (rejectedAt: chats that use it stop until someone replaces it). Members see only that an organization key exists, not its mask. A key in a legacy workspace row (the organization's, or the caller's pre-organization solo row) lists at the scope chats resolve it as, marked legacyWorkspaceRow; removing the provider at that scope deletes it. Also returns whether the caller can manage organization providers, whether personal API keys are restricted for them (their personal keys are then stored but unused), and the stored default model. Builder.io connections come from the Builder.io status, not here. Never returns a key value. Related: get-provider-models and manage-provider-models (which models each provider shows), manage-agent-engine (the default model), manage-provider-key-policy (Restrict personal API keys), preview-secret-removal (what removing a key stops).",
194
+ description: "List the model providers on Settings › Model: for each provider (OpenRouter, Ollama, Anthropic, OpenAI, Google Gemini, Groq, Mistral, Cohere), the organization's key and the caller's personal key, masked, with the endpoint URL saved for Ollama or an OpenAI-compatible gateway and when the provider rejected the key (rejectedAt: chats that use it stop until someone replaces it). A key entry marked unreadable means saved provider data could not be decrypted; unreadableKey and unreadableEndpoint identify which value. Owners and admins can replace or clear the unreadable value from the row. Members see only that an organization key exists, not its mask. A key in a legacy workspace row (the organization's, or the caller's pre-organization solo row) lists at the scope chats resolve it as, marked legacyWorkspaceRow; removing the provider at that scope deletes it. Also returns whether the caller can manage organization providers, whether personal API keys are restricted for them (their personal keys are then stored but unused), and the stored default model. Builder.io connections come from the Builder.io status, not here. Never returns a key value. Related: get-provider-models and manage-provider-models (which models each provider shows), manage-agent-engine (the default model), manage-provider-key-policy (Restrict personal API keys), preview-secret-removal (what removing a key stops).",
112
195
  schema: z.object({}),
113
196
  http: { method: "GET" },
114
197
  readOnly: true,
@@ -1,8 +1,8 @@
1
1
  import { redactArgsToValue, redactTextToSummary } from "../../audit/redact.js";
2
2
  import { abortRunDurably } from "../run-manager.js";
3
3
  import { getRunEventsSince } from "../run-store.js";
4
- import { resolveAgentHarnessApproval, sendAgentHarnessFollowUp, stopLiveAgentHarnessSession, } from "./lifecycle.js";
5
- import { getAgentHarnessSessionByRunId, listAgentHarnessSessions, markAgentHarnessSessionStopped, } from "./store.js";
4
+ import { resolveAgentHarnessApproval, sendAgentHarnessFollowUp, stopAndPersistAgentHarnessSession, } from "./lifecycle.js";
5
+ import { getAgentHarnessSessionByRunId, listAgentHarnessSessionEvents, listAgentHarnessSessions, } from "./store.js";
6
6
  export function createAgentHarnessBackgroundAgentController(scope = { ownerEmail: null }) {
7
7
  return {
8
8
  list: (options) => listAgentHarnessBackgroundRuns({
@@ -47,11 +47,35 @@ export async function listAgentHarnessBackgroundTranscriptEvents(runId, options
47
47
  if (!session?.runId)
48
48
  return [];
49
49
  const events = await getRunEventsSince(session.runId, 0);
50
- return events
50
+ const runTranscript = events
51
51
  .map((event) => parseRunEvent(event))
52
52
  .filter((event) => Boolean(event))
53
- .map((event) => toAgentHarnessBackgroundTranscriptEvent(session, event))
54
- .filter((event) => Boolean(event));
53
+ .map(({ runEvent, eventAt, transcriptOrder }) => ({
54
+ transcriptOrder,
55
+ sourceRank: 0,
56
+ sourceSeq: runEvent.seq,
57
+ event: toAgentHarnessBackgroundTranscriptEvent(session, runEvent, eventAt),
58
+ }))
59
+ .filter((entry) => Boolean(entry.event));
60
+ const sessionTranscript = (await listAgentHarnessSessionEvents(session.id))
61
+ .map((event) => ({
62
+ transcriptOrder: event.transcriptOrder,
63
+ sourceRank: 1,
64
+ sourceSeq: event.seq,
65
+ event: toAgentHarnessBackgroundSessionTranscriptEvent(session, event),
66
+ }))
67
+ .filter((entry) => Boolean(entry.event));
68
+ return assignCanonicalTranscriptSequence([...runTranscript, ...sessionTranscript]
69
+ .sort(compareTranscriptOrder)
70
+ .map(({ event, transcriptOrder }) => transcriptOrder === null
71
+ ? {
72
+ ...event,
73
+ metadata: {
74
+ ...(event.metadata ?? {}),
75
+ transcriptOrderFallback: "legacy_source_order",
76
+ },
77
+ }
78
+ : event));
55
79
  }
56
80
  export async function stopAgentHarnessBackgroundRun(runId, options = {
57
81
  ownerEmail: null,
@@ -64,6 +88,7 @@ export async function stopAgentHarnessBackgroundRun(runId, options = {
64
88
  return missingHarnessRunResult(runId);
65
89
  }
66
90
  const failures = [];
91
+ let stoppedSession = session;
67
92
  if (session.runId) {
68
93
  try {
69
94
  await abortRunDurably(session.runId, "user", { allowNotStarted: true });
@@ -73,16 +98,16 @@ export async function stopAgentHarnessBackgroundRun(runId, options = {
73
98
  }
74
99
  }
75
100
  try {
76
- await stopLiveAgentHarnessSession({
101
+ const stopped = await stopAndPersistAgentHarnessSession({
77
102
  sessionId: session.id,
78
103
  scope: options,
104
+ status: failures.length > 0 ? "errored" : "stopped",
79
105
  });
80
- }
81
- catch (error) {
82
- failures.push(error);
83
- }
84
- try {
85
- await markAgentHarnessSessionStopped(session.id, failures.length > 0 ? "errored" : "stopped");
106
+ stoppedSession = stopped ?? {
107
+ ...session,
108
+ status: "stopped",
109
+ stoppedAt: Date.now(),
110
+ };
86
111
  }
87
112
  catch (error) {
88
113
  failures.push(error);
@@ -95,11 +120,7 @@ export async function stopAgentHarnessBackgroundRun(runId, options = {
95
120
  return {
96
121
  ok: true,
97
122
  runId,
98
- run: toAgentHarnessBackgroundRun({
99
- ...session,
100
- status: "stopped",
101
- stoppedAt: Date.now(),
102
- }),
123
+ run: toAgentHarnessBackgroundRun(stoppedSession),
103
124
  };
104
125
  }
105
126
  function toAgentHarnessBackgroundRun(session) {
@@ -144,37 +165,84 @@ function toAgentHarnessBackgroundRun(session) {
144
165
  status: session.status,
145
166
  pendingApproval: session.pendingApproval,
146
167
  workspaceRef: session.workspaceRef,
168
+ followUpOutcome: session.followUpOutcome,
147
169
  },
148
170
  };
149
171
  }
150
- function toAgentHarnessBackgroundTranscriptEvent(session, runEvent) {
151
- const summary = summarizeAgentChatEvent(runEvent.event);
172
+ function toAgentHarnessBackgroundTranscriptEvent(session, runEvent, eventAt) {
173
+ return toAgentHarnessTranscriptEvent({
174
+ session,
175
+ event: runEvent.event,
176
+ eventId: `${session.runId ?? session.id}:${runEvent.seq}`,
177
+ seq: runEvent.seq,
178
+ createdAt: eventAt ?? session.createdAt,
179
+ sourceRecordType: "agent-harness-run-event",
180
+ ...(eventAt === null
181
+ ? { metadata: { createdAtFallback: "session-created-at" } }
182
+ : {}),
183
+ });
184
+ }
185
+ function toAgentHarnessBackgroundSessionTranscriptEvent(session, sessionEvent) {
186
+ const runId = session.runId ?? session.id;
187
+ const eventId = `${runId}:operation:${sessionEvent.operationId}:${sessionEvent.seq}`;
188
+ return toAgentHarnessTranscriptEvent({
189
+ session,
190
+ event: sessionEvent.event,
191
+ eventId,
192
+ seq: sessionEvent.seq,
193
+ createdAt: sessionEvent.eventAt,
194
+ sourceRecordType: "agent-harness-session-event",
195
+ operationId: sessionEvent.operationId,
196
+ });
197
+ }
198
+ function toAgentHarnessTranscriptEvent(input) {
199
+ const { session } = input;
200
+ const summary = summarizeAgentChatEvent(input.event);
152
201
  if (!summary)
153
202
  return null;
154
- const eventId = `${session.runId ?? session.id}:${runEvent.seq}`;
203
+ const runId = session.runId ?? session.id;
155
204
  return {
156
205
  schemaVersion: 1,
157
- id: eventId,
158
- runId: session.runId ?? session.id,
206
+ id: input.eventId,
207
+ runId,
159
208
  kind: summary.kind,
160
209
  source: "agent-harness",
161
210
  sourceRecord: {
162
- type: "agent-harness-run-event",
163
- id: eventId,
164
- seq: runEvent.seq,
211
+ type: input.sourceRecordType,
212
+ id: input.eventId,
213
+ seq: input.seq,
214
+ ...(input.operationId ? { operationId: input.operationId } : {}),
165
215
  },
166
216
  message: summary.message,
167
- createdAt: new Date(session.updatedAt).toISOString(),
217
+ createdAt: new Date(input.createdAt).toISOString(),
168
218
  metadata: {
169
219
  ...(summary.metadata ?? {}),
170
- seq: runEvent.seq,
220
+ ...(input.metadata ?? {}),
221
+ seq: input.seq,
171
222
  harnessName: session.harnessName,
172
223
  sessionId: session.id,
224
+ ...(input.operationId ? { operationId: input.operationId } : {}),
173
225
  },
174
226
  };
175
227
  }
228
+ function assignCanonicalTranscriptSequence(events) {
229
+ return events.map((event, seq) => {
230
+ const sourceSeq = event.sourceRecord.seq;
231
+ return {
232
+ ...event,
233
+ sourceRecord: { ...event.sourceRecord, seq },
234
+ metadata: {
235
+ ...(event.metadata ?? {}),
236
+ seq,
237
+ ...(sourceSeq === undefined ? {} : { sourceSeq }),
238
+ },
239
+ };
240
+ });
241
+ }
176
242
  function summarizeAgentChatEvent(event) {
177
243
  switch (event.type) {
244
+ case "user_input":
245
+ return { kind: "user", message: event.text };
178
246
  case "text":
179
247
  return { kind: "note", message: event.text };
180
248
  case "thinking":
@@ -233,19 +301,40 @@ function summarizeAgentChatEvent(event) {
233
301
  function parseRunEvent(row) {
234
302
  try {
235
303
  const event = JSON.parse(row.eventData);
236
- return { seq: Number(row.seq), event };
304
+ const eventAt = typeof row.eventAt === "number" && Number.isFinite(row.eventAt)
305
+ ? row.eventAt
306
+ : null;
307
+ const transcriptOrder = typeof row.transcriptOrder === "number" &&
308
+ Number.isFinite(row.transcriptOrder)
309
+ ? row.transcriptOrder
310
+ : null;
311
+ return {
312
+ runEvent: { seq: Number(row.seq), event },
313
+ eventAt,
314
+ transcriptOrder,
315
+ };
237
316
  }
238
317
  catch {
239
318
  return null;
240
319
  }
241
320
  }
321
+ function compareTranscriptOrder(left, right) {
322
+ if (left.transcriptOrder != null && right.transcriptOrder != null) {
323
+ return left.transcriptOrder - right.transcriptOrder;
324
+ }
325
+ if (left.transcriptOrder == null && right.transcriptOrder != null)
326
+ return -1;
327
+ if (left.transcriptOrder != null && right.transcriptOrder == null)
328
+ return 1;
329
+ return left.sourceRank - right.sourceRank || left.sourceSeq - right.sourceSeq;
330
+ }
242
331
  function backgroundStatus(status, needsApproval) {
332
+ if (needsApproval)
333
+ return "needs-approval";
243
334
  if (status === "running")
244
335
  return "running";
245
336
  if (status === "errored")
246
337
  return "errored";
247
- if (needsApproval)
248
- return "needs-approval";
249
338
  if (status === "idle")
250
339
  return "paused";
251
340
  if (status === "stopped" || status === "destroyed")
@@ -3,7 +3,7 @@ export { agentHarnessEventToAgentChatEvents, stringifyResult as stringifyAgentHa
3
3
  export { getAgentHarnessEntry, isAgentHarnessPackageInstalled, listAgentHarnesses, registerAgentHarness, resolveAgentHarness, type AgentHarnessEntry, } from "./registry.js";
4
4
  export { ensureAgentHarnessSessionTables, getAgentHarnessSession, getAgentHarnessSessionByRunId, getLatestAgentHarnessSessionForThread, listAgentHarnessSessions, markAgentHarnessSessionStopped, saveAgentHarnessSession, updateAgentHarnessSession, type AgentHarnessSessionStatus, type SaveAgentHarnessSessionInput, type StoredAgentHarnessSession, } from "./store.js";
5
5
  export { sendAgentHarnessEvent, startAgentHarnessRun, type StartAgentHarnessRunOptions, } from "./runner.js";
6
- export { registerLiveAgentHarnessSession, releaseLiveAgentHarnessSession, resolveAgentHarnessApproval, sendAgentHarnessFollowUp, stopLiveAgentHarnessSession, sweepExpiredAgentHarnessSessions, type AgentHarnessLifecycleErrorCode, type AgentHarnessLifecycleResult, type AgentHarnessOwnerScope, } from "./lifecycle.js";
6
+ export { registerLiveAgentHarnessSession, releaseLiveAgentHarnessSession, resolveAgentHarnessApproval, sendAgentHarnessFollowUp, stopLiveAgentHarnessSession, sweepExpiredAgentHarnessApprovalResolutions, sweepExpiredAgentHarnessSessions, type AgentHarnessLifecycleErrorCode, type AgentHarnessLifecycleResult, type AgentHarnessOwnerScope, } from "./lifecycle.js";
7
7
  export { aiSdkHarnessPartToEvents, createAiSdkHarnessAdapter, type AiSdkHarnessAdapterOptions, type AiSdkHarnessRuntime, } from "./ai-sdk-adapter.js";
8
8
  export { ACP_PACKAGE, acpAutoPermissionDecision, acpContentBlockToText, acpFileChangeEventsFromToolContent, acpUpdateToHarnessEvents, buildAcpPromptBlocks, createAcpHarnessAdapter, resolveAcpWorkspacePath, selectAcpPermissionOption, type AcpHarnessAdapterOptions, type AcpSessionUpdate, } from "./acp-adapter.js";
9
9
  export { BUILTIN_ACP_PRESETS, registerBuiltinAcpHarnesses, } from "./acp-builtin.js";
@@ -2,7 +2,7 @@ export { agentHarnessEventToAgentChatEvents, stringifyResult as stringifyAgentHa
2
2
  export { getAgentHarnessEntry, isAgentHarnessPackageInstalled, listAgentHarnesses, registerAgentHarness, resolveAgentHarness, } from "./registry.js";
3
3
  export { ensureAgentHarnessSessionTables, getAgentHarnessSession, getAgentHarnessSessionByRunId, getLatestAgentHarnessSessionForThread, listAgentHarnessSessions, markAgentHarnessSessionStopped, saveAgentHarnessSession, updateAgentHarnessSession, } from "./store.js";
4
4
  export { sendAgentHarnessEvent, startAgentHarnessRun, } from "./runner.js";
5
- export { registerLiveAgentHarnessSession, releaseLiveAgentHarnessSession, resolveAgentHarnessApproval, sendAgentHarnessFollowUp, stopLiveAgentHarnessSession, sweepExpiredAgentHarnessSessions, } from "./lifecycle.js";
5
+ export { registerLiveAgentHarnessSession, releaseLiveAgentHarnessSession, resolveAgentHarnessApproval, sendAgentHarnessFollowUp, stopLiveAgentHarnessSession, sweepExpiredAgentHarnessApprovalResolutions, sweepExpiredAgentHarnessSessions, } from "./lifecycle.js";
6
6
  export { aiSdkHarnessPartToEvents, createAiSdkHarnessAdapter, } from "./ai-sdk-adapter.js";
7
7
  export { ACP_PACKAGE, acpAutoPermissionDecision, acpContentBlockToText, acpFileChangeEventsFromToolContent, acpUpdateToHarnessEvents, buildAcpPromptBlocks, createAcpHarnessAdapter, resolveAcpWorkspacePath, selectAcpPermissionOption, } from "./acp-adapter.js";
8
8
  export { BUILTIN_ACP_PRESETS, registerBuiltinAcpHarnesses, } from "./acp-builtin.js";
@@ -22,7 +22,10 @@ export declare function registerLiveAgentHarnessSession(input: {
22
22
  createSession?: AgentHarnessCreateSessionOptions;
23
23
  ownerEmail?: string | null;
24
24
  orgId?: string | null;
25
+ generation: number;
25
26
  }): void;
27
+ export declare function refreshLiveAgentHarnessSessionGeneration(sessionId: string, session: AgentHarnessSession, generation: number): void;
28
+ export declare function getLiveAgentHarnessSessionGeneration(sessionId: string, session: AgentHarnessSession): number | null;
26
29
  export declare function releaseLiveAgentHarnessSession(sessionId: string, session?: AgentHarnessSession): void;
27
30
  export declare function resolveAgentHarnessApproval(input: {
28
31
  runId: string;
@@ -41,4 +44,17 @@ export declare function stopLiveAgentHarnessSession(input: {
41
44
  sessionId: string;
42
45
  scope: AgentHarnessOwnerScope;
43
46
  }): Promise<boolean>;
47
+ export declare function stopAndPersistAgentHarnessSession(input: {
48
+ sessionId: string;
49
+ scope: AgentHarnessOwnerScope;
50
+ status: "stopped" | "errored";
51
+ }): Promise<StoredAgentHarnessSession | null>;
44
52
  export declare function sweepExpiredAgentHarnessSessions(now?: number): Promise<number>;
53
+ export declare function sweepExpiredAgentHarnessFollowUps(limit?: number): Promise<{
54
+ recovered: number;
55
+ truncated: boolean;
56
+ }>;
57
+ export declare function sweepExpiredAgentHarnessApprovalResolutions(limit?: number): Promise<{
58
+ recovered: number;
59
+ truncated: boolean;
60
+ }>;