@agent-native/core 0.227.0 → 0.228.0-nightly-20261111004142

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 (190) hide show
  1. package/dist/action.d.ts +6 -2
  2. package/dist/agent/engine/builder-engine.js +6 -1
  3. package/dist/agent/engine/tool-call-journal-seed.d.ts +2 -0
  4. package/dist/agent/engine/tool-call-journal-seed.js +14 -0
  5. package/dist/agent/harness/store.js +14 -0
  6. package/dist/agent/production-agent.js +257 -17
  7. package/dist/agent/reaped-run-thread.js +6 -0
  8. package/dist/agent/run-manager.js +6 -0
  9. package/dist/agent/tool-call-journal.d.ts +4 -0
  10. package/dist/agent/tool-call-journal.js +12 -0
  11. package/dist/agent/types.d.ts +7 -1
  12. package/dist/app-config/agent.d.ts +3 -0
  13. package/dist/app-config/agent.js +7 -0
  14. package/dist/app-config/schema.d.ts +3 -0
  15. package/dist/application-state/handlers.d.ts +3 -3
  16. package/dist/application-state/handlers.js +6 -1
  17. package/dist/application-state/index.d.ts +3 -2
  18. package/dist/application-state/index.js +3 -2
  19. package/dist/application-state/last-used-tab.d.ts +11 -0
  20. package/dist/application-state/last-used-tab.js +43 -0
  21. package/dist/application-state/script-helpers.d.ts +26 -0
  22. package/dist/application-state/script-helpers.js +83 -15
  23. package/dist/application-state/store.d.ts +8 -0
  24. package/dist/application-state/store.js +16 -0
  25. package/dist/cli/agent.js +7 -3
  26. package/dist/cli/code-agent-executor.d.ts +1 -0
  27. package/dist/cli/code-agent-executor.js +37 -2
  28. package/dist/cli/code.js +3 -1
  29. package/dist/client/agent-sidebar-state.d.ts +2 -0
  30. package/dist/client/browser-tab-use.d.ts +9 -0
  31. package/dist/client/browser-tab-use.js +112 -0
  32. package/dist/client/chat/runtime.js +3 -1
  33. package/dist/client/error-capture.js +33 -1
  34. package/dist/client/navigation/index.d.ts +1 -0
  35. package/dist/client/navigation/index.js +1 -0
  36. package/dist/client/resources/mcp-integration-logos.js +16 -0
  37. package/dist/client/review/use-review.d.ts +28 -0
  38. package/dist/client/review/use-review.js +41 -12
  39. package/dist/client/route-state.js +2 -0
  40. package/dist/client/sse-event-processor.d.ts +1 -0
  41. package/dist/client/sse-event-processor.js +7 -1
  42. package/dist/client/use-db-sync.d.ts +8 -1
  43. package/dist/client/use-db-sync.js +6 -5
  44. package/dist/coding-tools/run-code.js +12 -2
  45. package/dist/coding-tools/sandbox/executions-store.d.ts +1 -1
  46. package/dist/coding-tools/sandbox/executions-store.js +9 -3
  47. package/dist/collab/routes.d.ts +1 -1
  48. package/dist/data-programs/execute.js +1 -1
  49. package/dist/db/schema-snapshot.generated.js +17 -3
  50. package/dist/extensions/url-safety.d.ts +1 -3
  51. package/dist/extensions/url-safety.js +23 -23
  52. package/dist/file-upload/actions/upload-image.d.ts +5 -5
  53. package/dist/file-upload/index.d.ts +1 -1
  54. package/dist/file-upload/index.js +1 -1
  55. package/dist/file-upload/s3-client.d.ts +1 -0
  56. package/dist/file-upload/s3-client.js +70 -13
  57. package/dist/file-upload/s3.d.ts +9 -0
  58. package/dist/file-upload/s3.js +14 -1
  59. package/dist/framework-package-versions.json +8 -8
  60. package/dist/jobs/run-history.js +10 -3
  61. package/dist/jobs/scheduler.js +5 -36
  62. package/dist/localization/core-messages/supplemental/ar-SA.js +35 -0
  63. package/dist/localization/core-messages/supplemental/de-DE.js +35 -0
  64. package/dist/localization/core-messages/supplemental/en-US.js +35 -0
  65. package/dist/localization/core-messages/supplemental/es-ES.js +35 -0
  66. package/dist/localization/core-messages/supplemental/fr-FR.js +35 -0
  67. package/dist/localization/core-messages/supplemental/hi-IN.js +35 -0
  68. package/dist/localization/core-messages/supplemental/ja-JP.js +35 -0
  69. package/dist/localization/core-messages/supplemental/ko-KR.js +35 -0
  70. package/dist/localization/core-messages/supplemental/pt-BR.js +35 -0
  71. package/dist/localization/core-messages/supplemental/zh-CN.js +35 -0
  72. package/dist/localization/core-messages/supplemental/zh-TW.js +35 -0
  73. package/dist/localization/default-messages.d.ts +35 -0
  74. package/dist/localization/default-messages.js +35 -0
  75. package/dist/localization/mcp-settings-messages.d.ts +35 -0
  76. package/dist/mcp/actions/list-mcp-client-connections.d.ts +8 -0
  77. package/dist/mcp/actions/list-mcp-client-connections.js +39 -0
  78. package/dist/mcp/browser-tab-target.d.ts +16 -0
  79. package/dist/mcp/browser-tab-target.js +58 -0
  80. package/dist/mcp/build-server.d.ts +1 -0
  81. package/dist/mcp/build-server.js +38 -7
  82. package/dist/mcp/call-attribution.d.ts +4 -1
  83. package/dist/mcp/call-attribution.js +3 -1
  84. package/dist/mcp/client-connections.d.ts +6 -0
  85. package/dist/mcp/client-connections.js +91 -0
  86. package/dist/mcp/connect-route.js +12 -2
  87. package/dist/mcp/connect-store.d.ts +3 -0
  88. package/dist/mcp/connect-store.js +10 -1
  89. package/dist/mcp/direct-action-call.js +1 -0
  90. package/dist/mcp/oauth-store.d.ts +6 -0
  91. package/dist/mcp/oauth-store.js +23 -0
  92. package/dist/mcp/oauth-token.d.ts +1 -0
  93. package/dist/mcp/oauth-token.js +1 -0
  94. package/dist/mcp/server.js +9 -20
  95. package/dist/mcp-client/config.d.ts +2 -0
  96. package/dist/mcp-client/manager.js +23 -0
  97. package/dist/mcp-client/workspace-servers.js +42 -10
  98. package/dist/notifications/routes.d.ts +3 -3
  99. package/dist/org/handlers.d.ts +1 -1
  100. package/dist/org/handlers.js +21 -17
  101. package/dist/provider-api/actions/custom-provider-registration.d.ts +12 -12
  102. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  103. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  104. package/dist/provider-api/index.js +17 -36
  105. package/dist/review/actions/create-review-comment.d.ts +7 -0
  106. package/dist/review/actions/create-review-comment.js +29 -3
  107. package/dist/review/actions/delete-review-comment.js +2 -2
  108. package/dist/review/actions/list-review-comments.d.ts +3 -0
  109. package/dist/review/actions/list-review-comments.js +41 -17
  110. package/dist/review/actions/react-to-review-comment.js +2 -2
  111. package/dist/review/actions/reply-review-comment.d.ts +4 -0
  112. package/dist/review/actions/reply-review-comment.js +15 -6
  113. package/dist/review/actions/update-review-comment-anchor.d.ts +6 -0
  114. package/dist/review/actions/update-review-comment-anchor.js +39 -10
  115. package/dist/review/actions/update-review-comment.d.ts +3 -0
  116. package/dist/review/actions/update-review-comment.js +28 -3
  117. package/dist/review/errors.d.ts +33 -0
  118. package/dist/review/errors.js +51 -0
  119. package/dist/review/index.d.ts +5 -4
  120. package/dist/review/index.js +3 -2
  121. package/dist/review/registry.d.ts +51 -1
  122. package/dist/review/registry.js +117 -0
  123. package/dist/review/store.d.ts +59 -14
  124. package/dist/review/store.js +622 -101
  125. package/dist/review/suggestions/actions.js +65 -10
  126. package/dist/review/suggestions/actor-kind.d.ts +5 -0
  127. package/dist/review/suggestions/actor-kind.js +20 -0
  128. package/dist/review/suggestions/store.js +16 -2
  129. package/dist/review/suggestions/types.d.ts +13 -0
  130. package/dist/review/types.d.ts +89 -0
  131. package/dist/scripts/db/dev-query-proxy.d.ts +2 -0
  132. package/dist/scripts/db/dev-query-proxy.js +4 -1
  133. package/dist/scripts/db/query.js +3 -0
  134. package/dist/scripts/dev/shell.js +4 -0
  135. package/dist/scripts/dev-session.d.ts +31 -0
  136. package/dist/scripts/dev-session.js +58 -0
  137. package/dist/scripts/resources/list.js +53 -4
  138. package/dist/scripts/runner.js +22 -4
  139. package/dist/search/query.d.ts +11 -5
  140. package/dist/search/query.js +52 -15
  141. package/dist/search/tokenize.d.ts +1 -0
  142. package/dist/search/tokenize.js +3 -0
  143. package/dist/secrets/api-keys.d.ts +4 -0
  144. package/dist/secrets/api-keys.js +5 -0
  145. package/dist/secrets/register.d.ts +2 -0
  146. package/dist/secrets/routes.d.ts +2 -2
  147. package/dist/secrets/routes.js +85 -6
  148. package/dist/secrets/storage.d.ts +2 -0
  149. package/dist/secrets/storage.js +34 -9
  150. package/dist/secrets/substitution.d.ts +3 -2
  151. package/dist/secrets/substitution.js +56 -7
  152. package/dist/server/action-discovery.js +5 -0
  153. package/dist/server/action-routes.js +8 -6
  154. package/dist/server/agent-chat/prompt-resources.js +117 -79
  155. package/dist/server/agent-chat/script-entries.js +15 -8
  156. package/dist/server/agent-chat-plugin.js +9 -4
  157. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  158. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  159. package/dist/server/agents-bundle.d.ts +3 -1
  160. package/dist/server/agents-bundle.js +4 -4
  161. package/dist/server/csrf.js +1 -0
  162. package/dist/server/dev-action-bridge.d.ts +2 -0
  163. package/dist/server/dev-action-bridge.js +34 -5
  164. package/dist/server/first-touch-route.d.ts +1 -1
  165. package/dist/server/http-response-telemetry.js +4 -0
  166. package/dist/server/loopback.js +3 -1
  167. package/dist/server/request-context.d.ts +26 -0
  168. package/dist/shared/app-state-routes.d.ts +7 -0
  169. package/dist/shared/app-state-routes.js +7 -0
  170. package/dist/shared/mcp-client-connections.d.ts +41 -0
  171. package/dist/shared/mcp-client-connections.js +18 -0
  172. package/dist/shared/mcp-connect-content.d.ts +18 -0
  173. package/dist/shared/mcp-connect-content.js +200 -172
  174. package/dist/shared/runtime-config.js +5 -1
  175. package/dist/templates/chat/.agents/skills/context-awareness/SKILL.md +13 -0
  176. package/dist/templates/default/.agents/skills/context-awareness/SKILL.md +13 -0
  177. package/dist/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +13 -0
  178. package/dist/tracking/providers.d.ts +7 -0
  179. package/dist/tracking/providers.js +39 -17
  180. package/dist/triggers/condition-evaluator.d.ts +2 -0
  181. package/dist/triggers/condition-evaluator.js +93 -3
  182. package/dist/triggers/dispatcher.js +24 -6
  183. package/docs/AGENTS.md +16 -16
  184. package/docs/content/create-with-an-agent.mdx +10 -4
  185. package/docs/content/durable-resume.mdx +11 -2
  186. package/docs/content/toolkit-comments-review.mdx +54 -1
  187. package/package.json +5 -4
  188. package/src/templates/chat/.agents/skills/context-awareness/SKILL.md +13 -0
  189. package/src/templates/default/.agents/skills/context-awareness/SKILL.md +13 -0
  190. package/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +13 -0
@@ -3,6 +3,7 @@ import { isArtifactReceipt, } from "../artifacts/detect.js";
3
3
  import { stripInlineBytes } from "../shared/inline-bytes.js";
4
4
  import { toolCallInputSignature as inputSignature } from "./tool-call-input-signature.js";
5
5
  import { isUnknownWriteCause } from "./types.js";
6
+ import { readWriteReceipt } from "./write-receipts.js";
6
7
  export function toolCallIdentityKey(id, executionStepId) {
7
8
  if (executionStepId !== undefined)
8
9
  return JSON.stringify(["step", executionStepId]);
@@ -241,6 +242,9 @@ function replayOutcomeIdentity(entry) {
241
242
  entry.result,
242
243
  entry.isError,
243
244
  entry.errorCode,
245
+ entry.receipt,
246
+ entry.actionSettled,
247
+ entry.settledOnCleanToolStop,
244
248
  entry.committed,
245
249
  entry.outcomeUnknown,
246
250
  entry.artifacts,
@@ -339,6 +343,14 @@ export function classifyToolCallJournal(events) {
339
343
  entry.isError = true;
340
344
  if (event.errorCode)
341
345
  entry.errorCode = event.errorCode;
346
+ const receipt = readWriteReceipt({ _receipt: event.receipt });
347
+ if (receipt)
348
+ entry.receipt = receipt;
349
+ if (event.actionSettled === true && event.isError !== true) {
350
+ entry.actionSettled = true;
351
+ if (event.settledOnCleanToolStop === true)
352
+ entry.settledOnCleanToolStop = true;
353
+ }
342
354
  if (event.completedSideEffect === true)
343
355
  entry.committed = true;
344
356
  if (event.argumentsHash)
@@ -317,6 +317,12 @@ type AgentChatEventPayload = {
317
317
  notExecuted?: true;
318
318
  /** Compact action write evidence retained even when `result` is truncated. */
319
319
  receipt?: WriteReceipt;
320
+ /** A successful invocation explicitly settled its work. */
321
+ actionSettled?: true;
322
+ /** The model completed the tool-call round before the action ran. */
323
+ settledOnCleanToolStop?: true;
324
+ /** The action emitted warnings the model must reconcile. */
325
+ hasAgentWarnings?: true;
320
326
  /**
321
327
  * Exact identity of the arguments the call ran with, defaults applied. A
322
328
  * continued turn replays a committed call only for the same identity.
@@ -405,7 +411,7 @@ type AgentChatEventPayload = {
405
411
  summary: string;
406
412
  } | {
407
413
  type: "done";
408
- reason?: "user" | "loop_breaker";
414
+ reason?: "user" | "loop_breaker" | "action_settled";
409
415
  message?: string;
410
416
  } | {
411
417
  type: "error";
@@ -1,5 +1,8 @@
1
1
  import { z } from "zod";
2
2
  export declare const agentConfig: z.ZodObject<{
3
+ promptSkills: z.ZodPrefault<z.ZodObject<{
4
+ includeSupportingFiles: z.ZodDefault<z.ZodBoolean>;
5
+ }, z.core.$strip>>;
3
6
  engine: z.ZodOptional<z.ZodString>;
4
7
  model: z.ZodOptional<z.ZodString>;
5
8
  questionIntake: z.ZodOptional<z.ZodObject<{
@@ -1,5 +1,12 @@
1
1
  import { z } from "zod";
2
2
  export const agentConfig = z.object({
3
+ promptSkills: z
4
+ .object({
5
+ includeSupportingFiles: z.boolean().default(true).meta({
6
+ doc: "Include supporting files, such as references and scripts, when presenting skills to the agent.",
7
+ }),
8
+ })
9
+ .prefault({}),
3
10
  engine: z
4
11
  .string()
5
12
  .min(1)
@@ -86,6 +86,9 @@ export declare const appConfigSchema: z.ZodObject<{
86
86
  }, z.core.$strip>>;
87
87
  }, z.core.$strip>>;
88
88
  agent: z.ZodPrefault<z.ZodObject<{
89
+ promptSkills: z.ZodPrefault<z.ZodObject<{
90
+ includeSupportingFiles: z.ZodDefault<z.ZodBoolean>;
91
+ }, z.core.$strip>>;
89
92
  engine: z.ZodOptional<z.ZodString>;
90
93
  model: z.ZodOptional<z.ZodString>;
91
94
  questionIntake: z.ZodOptional<z.ZodObject<{
@@ -36,18 +36,18 @@ export declare const listComposeDrafts: import("h3").EventHandlerWithFetch<impor
36
36
  export declare const getComposeDraft: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<Record<string, unknown> | null>>;
37
37
  export declare const putComposeDraft: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<any>>;
38
38
  export declare const saveComposeDraft: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
39
- errorCode?: undefined;
40
- error: string;
41
39
  value?: undefined;
40
+ error: string;
42
41
  revision?: undefined;
42
+ errorCode?: undefined;
43
43
  details?: undefined;
44
44
  } | {
45
- errorCode?: undefined;
46
45
  error?: undefined;
47
46
  value: {
48
47
  id: string;
49
48
  };
50
49
  revision: string;
50
+ errorCode?: undefined;
51
51
  details?: undefined;
52
52
  } | {
53
53
  value?: undefined;
@@ -2,7 +2,8 @@ import { createError, defineEventHandler, getRouterParam, getHeader, getQuery, s
2
2
  import { isSaveConflict, saveRevisionOf } from "../save-conflict/index.js";
3
3
  import { readBrowserTabIdHeader } from "../server/agent-run-context.js";
4
4
  import { readBody } from "../server/h3-helpers.js";
5
- import { APP_STATE_BATCH_ROUTE_HEADER, APP_STATE_SEQ_HEADER, APP_STATE_WRITER_HEADER, TAB_SCOPED_APP_STATE_KEYS, } from "../shared/app-state-routes.js";
5
+ import { APP_STATE_BATCH_ROUTE_HEADER, APP_STATE_SEQ_HEADER, APP_STATE_WRITER_HEADER, BROWSER_TAB_USED_APP_STATE_KEY, TAB_SCOPED_APP_STATE_KEYS, } from "../shared/app-state-routes.js";
6
+ import { browserTabUsedKey } from "./last-used-tab.js";
6
7
  import { appStateSaveIfCurrent } from "./save-if-current.js";
7
8
  import { appStateKeyForBrowserTab } from "./script-helpers.js";
8
9
  import { appStateGet, appStateGetManyEntries, appStatePut, appStateCompareAndSet, appStateDelete, appStateList, appStateDeleteByPrefix, } from "./store.js";
@@ -54,6 +55,10 @@ function headerSeq(event) {
54
55
  return seq !== undefined && /^[0-9]{1,16}$/.test(seq) ? Number(seq) : seq;
55
56
  }
56
57
  function requestScopedKey(key, event) {
58
+ if (key === BROWSER_TAB_USED_APP_STATE_KEY) {
59
+ const browserTabId = readBrowserTabIdHeader(event);
60
+ return browserTabId ? browserTabUsedKey(browserTabId) : key;
61
+ }
57
62
  return TAB_SCOPED_APP_STATE_KEYS.has(key)
58
63
  ? appStateKeyForBrowserTab(key, readBrowserTabIdHeader(event))
59
64
  : key;
@@ -1,4 +1,5 @@
1
- export { appStateGet, appStateGetMany, appStateGetManyEntries, appStatePut, appStateDelete, appStateCompareAndSet, appStateCompareAndSetMany, appStateList, appStateDeleteByPrefix, type AppStateCompareAndSetOperation, } from "./store.js";
1
+ export { appStateGet, appStateGetMany, appStateGetManyEntries, appStatePut, appStateDelete, appStateCompareAndSet, appStateCompareAndSetMany, appStateList, appStateListUpdatedAt, appStateDeleteByPrefix, type AppStateCompareAndSetOperation, } from "./store.js";
2
+ export { browserTabUsedKey, findLastUsedBrowserTab, LAST_USED_BROWSER_TAB_MAX_AGE_MS, } from "./last-used-tab.js";
2
3
  export { getAppStateEmitter, emitAppStateChange, emitAppStateDelete, type AppStateEvent, } from "./emitter.js";
3
4
  export { getState, getStateMany, MAX_APP_STATE_BATCH_KEYS, MAX_APP_STATE_BATCH_WRITES, putState, putStateMany, deleteState, listComposeDrafts, getComposeDraft, putComposeDraft, deleteComposeDraft, deleteAllComposeDrafts, } from "./handlers.js";
4
- export { readAppState, writeAppState, deleteAppState, compareAndSetAppState, compareAndSetManyAppState, listAppState, deleteAppStateByPrefix, readAppStateForCurrentTab, writeAppStateForCurrentTab, appStateKeyForBrowserTab, getCurrentRequestBrowserTabId, } from "./script-helpers.js";
5
+ export { readAppState, writeAppState, deleteAppState, compareAndSetAppState, compareAndSetManyAppState, listAppState, deleteAppStateByPrefix, readAppStateForCurrentTab, writeAppStateForCurrentTab, appStateKeyForBrowserTab, getCurrentRequestBrowserTabId, resolveCurrentRequestStateTab, } from "./script-helpers.js";
@@ -1,4 +1,5 @@
1
- export { appStateGet, appStateGetMany, appStateGetManyEntries, appStatePut, appStateDelete, appStateCompareAndSet, appStateCompareAndSetMany, appStateList, appStateDeleteByPrefix, } from "./store.js";
1
+ export { appStateGet, appStateGetMany, appStateGetManyEntries, appStatePut, appStateDelete, appStateCompareAndSet, appStateCompareAndSetMany, appStateList, appStateListUpdatedAt, appStateDeleteByPrefix, } from "./store.js";
2
+ export { browserTabUsedKey, findLastUsedBrowserTab, LAST_USED_BROWSER_TAB_MAX_AGE_MS, } from "./last-used-tab.js";
2
3
  export { getAppStateEmitter, emitAppStateChange, emitAppStateDelete, } from "./emitter.js";
3
4
  export { getState, getStateMany, MAX_APP_STATE_BATCH_KEYS, MAX_APP_STATE_BATCH_WRITES, putState, putStateMany, deleteState, listComposeDrafts, getComposeDraft, putComposeDraft, deleteComposeDraft, deleteAllComposeDrafts, } from "./handlers.js";
4
- export { readAppState, writeAppState, deleteAppState, compareAndSetAppState, compareAndSetManyAppState, listAppState, deleteAppStateByPrefix, readAppStateForCurrentTab, writeAppStateForCurrentTab, appStateKeyForBrowserTab, getCurrentRequestBrowserTabId, } from "./script-helpers.js";
5
+ export { readAppState, writeAppState, deleteAppState, compareAndSetAppState, compareAndSetManyAppState, listAppState, deleteAppStateByPrefix, readAppStateForCurrentTab, writeAppStateForCurrentTab, appStateKeyForBrowserTab, getCurrentRequestBrowserTabId, resolveCurrentRequestStateTab, } from "./script-helpers.js";
@@ -0,0 +1,11 @@
1
+ import type { BrowserTabTarget } from "../server/request-context.js";
2
+ /** How long after its last use a tab still counts as the one to act on. */
3
+ export declare const LAST_USED_BROWSER_TAB_MAX_AGE_MS: number;
4
+ /** The row that records a tab's last use: one per app and tab. */
5
+ export declare function browserTabUsedKey(browserTabId: string): string;
6
+ /**
7
+ * The tab of this app the user used last, if one was used within
8
+ * LAST_USED_BROWSER_TAB_MAX_AGE_MS. Tabs are ordered by when the server
9
+ * stored their use, so browser clocks don't matter.
10
+ */
11
+ export declare function findLastUsedBrowserTab(sessionId: string, now?: number): Promise<BrowserTabTarget>;
@@ -0,0 +1,43 @@
1
+ import { getAppConfig } from "../app-config/index.js";
2
+ import { BROWSER_TAB_USED_APP_STATE_KEY } from "../shared/app-state-routes.js";
3
+ import { appStateListUpdatedAt } from "./store.js";
4
+ /** How long after its last use a tab still counts as the one to act on. */
5
+ export const LAST_USED_BROWSER_TAB_MAX_AGE_MS = 12 * 60 * 60 * 1000;
6
+ const SAFE_TAB_ID_RE = /^[A-Za-z0-9_-]{1,96}$/;
7
+ // Several apps can share one database (Design and Slides do), so each app
8
+ // keeps its tabs' records under its own name. The browser's write and the
9
+ // MCP lookup both run in this app's server, so they name it the same way.
10
+ // Encoding keeps distinct names distinct and keeps ":" out of the name.
11
+ function appScope() {
12
+ const app = getAppConfig().app;
13
+ const name = app.workspaceId ?? app.id ?? app.slug ?? app.packageName ?? app.name;
14
+ return name ? encodeURIComponent(name) : "app";
15
+ }
16
+ function browserTabUsedKeyPrefix() {
17
+ return `${BROWSER_TAB_USED_APP_STATE_KEY}:${appScope()}:`;
18
+ }
19
+ /** The row that records a tab's last use: one per app and tab. */
20
+ export function browserTabUsedKey(browserTabId) {
21
+ return `${browserTabUsedKeyPrefix()}${browserTabId}`;
22
+ }
23
+ /**
24
+ * The tab of this app the user used last, if one was used within
25
+ * LAST_USED_BROWSER_TAB_MAX_AGE_MS. Tabs are ordered by when the server
26
+ * stored their use, so browser clocks don't matter.
27
+ */
28
+ export async function findLastUsedBrowserTab(sessionId, now = Date.now()) {
29
+ const prefix = browserTabUsedKeyPrefix();
30
+ let best;
31
+ for (const { key, updatedAt } of await appStateListUpdatedAt(sessionId, prefix, now - LAST_USED_BROWSER_TAB_MAX_AGE_MS)) {
32
+ const tabId = key.slice(prefix.length);
33
+ if (!SAFE_TAB_ID_RE.test(tabId))
34
+ continue;
35
+ if (!Number.isFinite(updatedAt))
36
+ continue;
37
+ if (now - updatedAt > LAST_USED_BROWSER_TAB_MAX_AGE_MS)
38
+ continue;
39
+ if (!best || updatedAt > best.usedAt)
40
+ best = { tabId, usedAt: updatedAt };
41
+ }
42
+ return best ? { kind: "last-used", ...best } : { kind: "unavailable" };
43
+ }
@@ -10,9 +10,35 @@ export declare function listAppState(prefix: string): Promise<Array<{
10
10
  }>>;
11
11
  export declare function deleteAppStateByPrefix(prefix: string): Promise<number>;
12
12
  export declare function normalizeBrowserTabId(value: unknown): string | null;
13
+ /** The browser tab the current request came from, if any. */
13
14
  export declare function getCurrentRequestBrowserTabId(): string | null;
15
+ /**
16
+ * The tab whose tab-scoped app state the current request reads and writes:
17
+ * the tab it came from or, for an MCP call from no tab, the tab of this app
18
+ * the user used last. `unavailable` when the call looked for a tab and none
19
+ * was used recently. Requests that have neither (scripts, jobs, the CLI) get
20
+ * no tab and keep the account-wide rows.
21
+ */
22
+ export declare function resolveCurrentRequestStateTab(): Promise<{
23
+ tabId: string | null;
24
+ unavailable: boolean;
25
+ }>;
14
26
  export declare function appStateKeyForBrowserTab(key: string, browserTabId: unknown): string;
27
+ /**
28
+ * Reads `key` for the current request's tab (see
29
+ * resolveCurrentRequestStateTab), falling back to the account-wide row only
30
+ * when `fallbackToGlobal` is true. A request with no tab at all reads the
31
+ * account-wide row. An MCP call that found no recently used tab reads it
32
+ * only when it opted in, and never for the ambient keys: no tab keeps those
33
+ * rows current.
34
+ */
15
35
  export declare function readAppStateForCurrentTab(key: string, options?: {
16
36
  fallbackToGlobal?: boolean;
17
37
  }): Promise<Record<string, unknown> | null>;
38
+ /**
39
+ * Writes `key` for the current request's tab (see
40
+ * resolveCurrentRequestStateTab). With no tab, it writes the account-wide
41
+ * row, as it always has: an MCP caller learns from the call's result that no
42
+ * tab was found.
43
+ */
18
44
  export declare function writeAppStateForCurrentTab(key: string, value: Record<string, unknown>): Promise<void>;
@@ -19,30 +19,43 @@ async function resolveSessionId() {
19
19
  throw new Error("Application state access requires an authenticated request context or AGENT_USER_EMAIL env var");
20
20
  }
21
21
  export async function readAppState(key) {
22
- return readUnscopedAppState(requestScopedAppStateKey(key));
22
+ const scoped = await requestScopedAppStateKey(key);
23
+ // No tab to read from: a call that asked for one gets nothing rather than
24
+ // the account-wide row, which no tab keeps current.
25
+ if (scoped.unavailable)
26
+ return null;
27
+ return readUnscopedAppState(scoped.key);
23
28
  }
24
29
  export async function writeAppState(key, value) {
25
30
  const sessionId = await resolveSessionId();
26
- return appStatePut(sessionId, requestScopedAppStateKey(key), value, {
31
+ const { key: scopedKey } = await requestScopedAppStateKey(key);
32
+ return appStatePut(sessionId, scopedKey, value, {
27
33
  requestSource: "agent",
28
34
  });
29
35
  }
30
36
  export async function deleteAppState(key) {
31
37
  const sessionId = await resolveSessionId();
32
- return appStateDelete(sessionId, requestScopedAppStateKey(key), {
38
+ const { key: scopedKey } = await requestScopedAppStateKey(key);
39
+ return appStateDelete(sessionId, scopedKey, {
33
40
  requestSource: "agent",
34
41
  });
35
42
  }
36
43
  export async function compareAndSetAppState(key, expectedValue, nextValue) {
37
44
  const sessionId = await resolveSessionId();
38
- return appStateCompareAndSet(sessionId, requestScopedAppStateKey(key), expectedValue, nextValue, { requestSource: "agent" });
45
+ const { key: scopedKey } = await requestScopedAppStateKey(key);
46
+ return appStateCompareAndSet(sessionId, scopedKey, expectedValue, nextValue, {
47
+ requestSource: "agent",
48
+ });
39
49
  }
40
50
  export async function compareAndSetManyAppState(operations) {
41
51
  const sessionId = await resolveSessionId();
42
- return appStateCompareAndSetMany(sessionId, operations.map((operation) => ({
52
+ const scopedOperations = await Promise.all(operations.map(async (operation) => ({
43
53
  ...operation,
44
- key: requestScopedAppStateKey(operation.key),
45
- })), { requestSource: "agent" });
54
+ key: (await requestScopedAppStateKey(operation.key)).key,
55
+ })));
56
+ return appStateCompareAndSetMany(sessionId, scopedOperations, {
57
+ requestSource: "agent",
58
+ });
46
59
  }
47
60
  export async function listAppState(prefix) {
48
61
  const sessionId = await resolveSessionId();
@@ -69,6 +82,7 @@ export function normalizeBrowserTabId(value) {
69
82
  const trimmed = value.trim();
70
83
  return SAFE_TAB_ID_RE.test(trimmed) ? trimmed : null;
71
84
  }
85
+ /** The browser tab the current request came from, if any. */
72
86
  export function getCurrentRequestBrowserTabId() {
73
87
  try {
74
88
  return normalizeBrowserTabId(getRequestRunContext()?.browserTabId);
@@ -77,32 +91,86 @@ export function getCurrentRequestBrowserTabId() {
77
91
  return null;
78
92
  }
79
93
  }
94
+ /**
95
+ * The tab whose tab-scoped app state the current request reads and writes:
96
+ * the tab it came from or, for an MCP call from no tab, the tab of this app
97
+ * the user used last. `unavailable` when the call looked for a tab and none
98
+ * was used recently. Requests that have neither (scripts, jobs, the CLI) get
99
+ * no tab and keep the account-wide rows.
100
+ */
101
+ export async function resolveCurrentRequestStateTab() {
102
+ const browserTabId = getCurrentRequestBrowserTabId();
103
+ if (browserTabId)
104
+ return { tabId: browserTabId, unavailable: false };
105
+ let slot;
106
+ try {
107
+ slot = getRequestRunContext()?.browserTabTarget;
108
+ }
109
+ catch {
110
+ slot = undefined;
111
+ }
112
+ if (!slot)
113
+ return { tabId: null, unavailable: false };
114
+ const target = await slot.resolve();
115
+ if (target.kind === "last-used") {
116
+ const tabId = normalizeBrowserTabId(target.tabId);
117
+ if (tabId)
118
+ return { tabId, unavailable: false };
119
+ }
120
+ return { tabId: null, unavailable: true };
121
+ }
80
122
  export function appStateKeyForBrowserTab(key, browserTabId) {
81
123
  const normalized = normalizeBrowserTabId(browserTabId);
82
124
  return normalized ? `${key}:${normalized}` : key;
83
125
  }
84
- function requestScopedAppStateKey(key) {
126
+ async function requestScopedAppStateKey(key) {
85
127
  if (!TAB_SCOPED_AMBIENT_KEYS.has(key))
86
- return key;
87
- return appStateKeyForBrowserTab(key, getCurrentRequestBrowserTabId());
128
+ return { key, unavailable: false };
129
+ const tab = await resolveCurrentRequestStateTab();
130
+ return {
131
+ key: appStateKeyForBrowserTab(key, tab.tabId),
132
+ unavailable: tab.unavailable,
133
+ };
88
134
  }
89
135
  async function readUnscopedAppState(key) {
90
136
  const sessionId = await resolveSessionId();
91
137
  return appStateGet(sessionId, key);
92
138
  }
139
+ /**
140
+ * Reads `key` for the current request's tab (see
141
+ * resolveCurrentRequestStateTab), falling back to the account-wide row only
142
+ * when `fallbackToGlobal` is true. A request with no tab at all reads the
143
+ * account-wide row. An MCP call that found no recently used tab reads it
144
+ * only when it opted in, and never for the ambient keys: no tab keeps those
145
+ * rows current.
146
+ */
93
147
  export async function readAppStateForCurrentTab(key, options) {
94
- const browserTabId = getCurrentRequestBrowserTabId();
95
- const tabKey = appStateKeyForBrowserTab(key, browserTabId);
148
+ const tab = await resolveCurrentRequestStateTab();
149
+ const tabKey = appStateKeyForBrowserTab(key, tab.tabId);
150
+ const fallbackToGlobal = options?.fallbackToGlobal === true;
96
151
  if (tabKey !== key) {
97
152
  const scoped = await readAppState(tabKey);
98
153
  if (scoped)
99
154
  return scoped;
100
- if (options?.fallbackToGlobal !== true)
155
+ if (!fallbackToGlobal)
101
156
  return null;
102
157
  }
158
+ if (tab.unavailable &&
159
+ (!fallbackToGlobal || TAB_SCOPED_AMBIENT_KEYS.has(key)))
160
+ return null;
103
161
  return readUnscopedAppState(key);
104
162
  }
163
+ /**
164
+ * Writes `key` for the current request's tab (see
165
+ * resolveCurrentRequestStateTab). With no tab, it writes the account-wide
166
+ * row, as it always has: an MCP caller learns from the call's result that no
167
+ * tab was found.
168
+ */
105
169
  export async function writeAppStateForCurrentTab(key, value) {
106
- const tabKey = appStateKeyForBrowserTab(key, getCurrentRequestBrowserTabId());
107
- return writeAppState(tabKey, value);
170
+ const tab = await resolveCurrentRequestStateTab();
171
+ const tabKey = appStateKeyForBrowserTab(key, tab.tabId);
172
+ if (tabKey !== key)
173
+ return writeAppState(tabKey, value);
174
+ const sessionId = await resolveSessionId();
175
+ return appStatePut(sessionId, key, value, { requestSource: "agent" });
108
176
  }
@@ -46,6 +46,14 @@ export declare function appStateList(sessionId: string, keyPrefix: string): Prom
46
46
  key: string;
47
47
  value: Record<string, unknown>;
48
48
  }>>;
49
+ /**
50
+ * Lists the keys under `keyPrefix` with when the server last wrote each,
51
+ * leaving out any written before `writtenSince`.
52
+ */
53
+ export declare function appStateListUpdatedAt(sessionId: string, keyPrefix: string, writtenSince?: number): Promise<Array<{
54
+ key: string;
55
+ updatedAt: number;
56
+ }>>;
49
57
  export declare function appStateListByKeyPrefix(keyPrefix: string, limit?: number): Promise<Array<{
50
58
  sessionId: string;
51
59
  key: string;
@@ -253,6 +253,22 @@ export async function appStateList(sessionId, keyPrefix) {
253
253
  value: JSON.parse(row.value),
254
254
  }));
255
255
  }
256
+ /**
257
+ * Lists the keys under `keyPrefix` with when the server last wrote each,
258
+ * leaving out any written before `writtenSince`.
259
+ */
260
+ export async function appStateListUpdatedAt(sessionId, keyPrefix, writtenSince = 0) {
261
+ await ensureTable();
262
+ const client = getDbExec();
263
+ const { rows } = await client.execute({
264
+ sql: `SELECT key, updated_at FROM application_state WHERE session_id = ? AND key LIKE ? ESCAPE '!' AND updated_at >= ?`,
265
+ args: [sessionId, escapeLike(keyPrefix) + "%", writtenSince],
266
+ });
267
+ return rows.map((row) => ({
268
+ key: row.key,
269
+ updatedAt: Number(row.updated_at),
270
+ }));
271
+ }
256
272
  export async function appStateListByKeyPrefix(keyPrefix, limit = 100) {
257
273
  await ensureTable();
258
274
  const client = getDbExec();
package/dist/cli/agent.js CHANGED
@@ -8,7 +8,7 @@ import { runAgentLoopDirectWithSoftTimeout } from "../agent/run-loop-with-resume
8
8
  import { getAppConfig } from "../app-config/index.js";
9
9
  import { createGitHubRepoToolEntries } from "../provider-api/github-repo.js";
10
10
  import { loadCliBootstrap } from "../scripts/cli-bootstrap.js";
11
- import { resolveDevUserEmail } from "../scripts/dev-session.js";
11
+ import { devOrgChoiceFromEnv, resolveDevOrgScope, resolveDevUserEmail, } from "../scripts/dev-session.js";
12
12
  import { loadEnv, serializeCliArgs } from "../scripts/utils.js";
13
13
  import { autoDiscoverActions } from "../server/action-discovery.js";
14
14
  import { captureCliOutput } from "../server/cli-capture.js";
@@ -134,9 +134,13 @@ export async function runAgent(args, io = {}) {
134
134
  return 1;
135
135
  }
136
136
  const ownerEmail = parsed.userEmail ?? env.AGENT_USER_EMAIL ?? (await resolveDevUserEmail());
137
- const orgId = parsed.orgId ?? env.AGENT_ORG_ID ?? undefined;
137
+ const envChoice = devOrgChoiceFromEnv(env);
138
+ const orgScope = await resolveDevOrgScope(ownerEmail, {
139
+ orgId: parsed.orgId ?? envChoice.orgId,
140
+ personal: envChoice.personal,
141
+ });
138
142
  try {
139
- const result = await runWithRequestContext({ userEmail: ownerEmail, orgId }, () => runLocalAgentLoop(parsed));
143
+ const result = await runWithRequestContext({ ...(ownerEmail ? { userEmail: ownerEmail } : {}), ...orgScope }, () => runLocalAgentLoop(parsed));
140
144
  if (parsed.json) {
141
145
  stdout(JSON.stringify(result, null, 2));
142
146
  return result.failure ? 1 : 0;
@@ -21,6 +21,7 @@ interface CodeAgentApprovalExecutionOptions {
21
21
  streamToolOutputToStdout?: boolean;
22
22
  signal?: AbortSignal;
23
23
  }
24
+ export declare const RECAP_SOURCE_TOOL_PROFILE = "recap-source";
24
25
  export declare function executeCodeAgentRun(options: ExecuteCodeAgentRunOptions): Promise<CodeAgentRunRecord | null>;
25
26
  export declare function codeAgentMcpInvocationPolicy(permissionMode: CodeAgentPermissionMode): McpToolInvocationPolicy;
26
27
  export declare function executeExistingCodeAgentRun(runId: string, options?: Omit<ExecuteCodeAgentRunOptions, "runId">): Promise<CodeAgentRunRecord | null>;
@@ -31,8 +31,18 @@ const MCP_CONFIG_REMOVE_ATTEMPTS = 3;
31
31
  const PI_CLI_ENGINE_NAME = "pi-cli";
32
32
  const OPENCODE_CLI_ENGINE_NAME = "opencode-cli";
33
33
  const CLAUDE_CLI_DEFAULT_MODEL = "claude-sonnet-5-5";
34
- const RECAP_SOURCE_TOOL_PROFILE = "recap-source";
34
+ export const RECAP_SOURCE_TOOL_PROFILE = "recap-source";
35
35
  const RECAP_SOURCE_OUTPUT_FILE = "recap-source.json";
36
+ const RECAP_SOURCE_INPUT_FILES = new Set([
37
+ "recap.diff",
38
+ "recap.stat",
39
+ "recap-blocks.md",
40
+ "recap-prompt.md",
41
+ "recap-source.initial.json",
42
+ "recap-source.json",
43
+ "recap-url-reason.txt",
44
+ "recap-repair-prompt.md",
45
+ ]);
36
46
  const STRUCTURED_HISTORY_RECENT_EVENTS = 40;
37
47
  const STRUCTURED_HISTORY_RESULT_CAP = MAX_TOOL_OUTPUT_CHARS;
38
48
  export async function executeCodeAgentRun(options) {
@@ -2268,8 +2278,28 @@ function createLocalCodeAgentActions(cwd, permissionMode, runId, onToolMetadata,
2268
2278
  if (toolProfile === RECAP_SOURCE_TOOL_PROFILE) {
2269
2279
  const allowedOutput = path.resolve(cwd, RECAP_SOURCE_OUTPUT_FILE);
2270
2280
  const writeAction = actions.write;
2281
+ const readAction = actions.read;
2271
2282
  return {
2272
- read: actions.read,
2283
+ read: {
2284
+ ...readAction,
2285
+ run: async (args, context) => {
2286
+ const requestedPath = args && typeof args.path === "string" ? args.path : "";
2287
+ try {
2288
+ const resolved = path.resolve(cwd, requestedPath);
2289
+ const name = path.basename(resolved);
2290
+ if (!RECAP_SOURCE_INPUT_FILES.has(name) ||
2291
+ resolved !== path.join(cwd, name) ||
2292
+ fs.lstatSync(resolved).isSymbolicLink() ||
2293
+ path.dirname(fs.realpathSync(resolved)) !== fs.realpathSync(cwd)) {
2294
+ return "Error: recap source reads are limited to generated recap inputs.";
2295
+ }
2296
+ }
2297
+ catch {
2298
+ return "Error: recap source reads are limited to generated recap inputs.";
2299
+ }
2300
+ return readAction.run(args, context);
2301
+ },
2302
+ },
2273
2303
  write: {
2274
2304
  ...writeAction,
2275
2305
  run: async (args, context) => {
@@ -2277,6 +2307,11 @@ function createLocalCodeAgentActions(cwd, permissionMode, runId, onToolMetadata,
2277
2307
  if (path.resolve(cwd, requestedPath) !== allowedOutput) {
2278
2308
  return `Error: only ${RECAP_SOURCE_OUTPUT_FILE} may be written in the recap-source tool profile.`;
2279
2309
  }
2310
+ if (fs
2311
+ .lstatSync(allowedOutput, { throwIfNoEntry: false })
2312
+ ?.isSymbolicLink()) {
2313
+ return `Error: ${RECAP_SOURCE_OUTPUT_FILE} cannot be a symbolic link.`;
2314
+ }
2280
2315
  return writeAction.run(args, context);
2281
2316
  },
2282
2317
  },
package/dist/cli/code.js CHANGED
@@ -4,7 +4,7 @@ import { createInterface } from "node:readline";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { runAuditAgentWeb } from "./audit-agent-web.js";
6
6
  import { findProjectSlashCommand, listProjectSlashCommands, renderProjectSlashCommandPrompt, } from "./code-agent-commands.js";
7
- import { executeApproveAlwaysCodeAgentApproval, executeCodeAgentRun, executeDenyCodeAgentApproval, executeExistingCodeAgentRun, executePendingCodeAgentApproval, } from "./code-agent-executor.js";
7
+ import { RECAP_SOURCE_TOOL_PROFILE, executeApproveAlwaysCodeAgentApproval, executeCodeAgentRun, executeDenyCodeAgentApproval, executeExistingCodeAgentRun, executePendingCodeAgentApproval, } from "./code-agent-executor.js";
8
8
  import { CODE_AGENT_PERMISSION_MODES, appendCodeAgentTranscriptEvent, createCodeAgentRunRecord, getCodeAgentRunRecord, getLastCodeAgentRunRecord, isActiveCodeAgentRun, listCodeAgentRunRecords, listCodeAgentTranscriptEvents, normalizeCodeAgentPermissionMode, queueCodeAgentFollowUp, updateCodeAgentRunRecord, } from "./code-agent-runs.js";
9
9
  import { runMigrate } from "./migrate.js";
10
10
  export const CODE_AGENT_CLI_GOALS = [
@@ -325,6 +325,8 @@ Usage:
325
325
  agent-native code run <runId>
326
326
  agent-native code goals
327
327
 
328
+ Restricted tool profile: ${RECAP_SOURCE_TOOL_PROFILE}
329
+
328
330
  Interactive shell:
329
331
  /help Show shell commands
330
332
  /goals List available coding-agent goals
@@ -13,6 +13,8 @@ export interface AgentSidebarStateChangeDetail {
13
13
  open: boolean;
14
14
  source: AgentSidebarStateSource;
15
15
  mode: AgentSidebarStateMode;
16
+ /** The panel draws inside the app's `panelHost`, not beside the page. */
17
+ hosted?: boolean;
16
18
  }
17
19
  export declare function snapAgentSidebarWidth(width: number): number;
18
20
  export declare function getAgentSidebarWideWidth(viewportWidth?: number): number;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Records when someone uses this browser tab (it gains focus, or its route
3
+ * changes while focused) and forgets the tab when the page goes away. An MCP
4
+ * tool call comes from no tab, so the server points its tab-scoped app
5
+ * state (navigation, navigate commands, selection) at the tab of this app
6
+ * used last. `useAgentRouteState` calls it; an app with its own navigation
7
+ * sync calls it once with its current route.
8
+ */
9
+ export declare function useBrowserTabUse(route: string, enabled?: boolean): void;