@agent-native/core 0.203.2-nightly-20261007213109 → 0.204.0

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 (121) hide show
  1. package/dist/a2a/handlers.js +57 -1
  2. package/dist/a2a/server.js +37 -1
  3. package/dist/action.d.ts +1 -0
  4. package/dist/action.js +26 -3
  5. package/dist/agent/contain-principal.d.ts +9 -0
  6. package/dist/agent/contain-principal.js +53 -0
  7. package/dist/agent/production-agent.js +32 -2
  8. package/dist/agent/run-manager.js +14 -2
  9. package/dist/agent/run-store.js +66 -12
  10. package/dist/audit/actions/export-audit-events.d.ts +1 -1
  11. package/dist/audit/actions/export-audit-events.js +2 -2
  12. package/dist/audit/actions/export-audit-ocsf.d.ts +11 -0
  13. package/dist/audit/actions/export-audit-ocsf.js +183 -0
  14. package/dist/audit/actions/list-audit-events.d.ts +1 -1
  15. package/dist/audit/actions/list-audit-events.js +3 -3
  16. package/dist/audit/config.d.ts +1 -1
  17. package/dist/audit/config.js +5 -1
  18. package/dist/audit/index.d.ts +1 -0
  19. package/dist/audit/index.js +1 -0
  20. package/dist/audit/ocsf.d.ts +70 -0
  21. package/dist/audit/ocsf.js +228 -0
  22. package/dist/audit/record.js +1 -1
  23. package/dist/audit/store.js +9 -2
  24. package/dist/audit/types.d.ts +12 -1
  25. package/dist/cli/agent.js +2 -11
  26. package/dist/cli/connect.js +7 -2
  27. package/dist/client/use-db-sync.d.ts +13 -0
  28. package/dist/client/use-db-sync.js +22 -1
  29. package/dist/coding-tools/run-code.js +22 -1
  30. package/dist/coding-tools/sandbox/local-child-process-adapter.js +20 -0
  31. package/dist/collab/client.js +36 -3
  32. package/dist/collab/routes.d.ts +9 -4
  33. package/dist/collab/routes.js +15 -1
  34. package/dist/collab/struct-routes.d.ts +1 -1
  35. package/dist/deploy/build.js +15 -8
  36. package/dist/deploy/workspace-deploy.js +88 -13
  37. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  38. package/dist/file-upload/builder.js +18 -4
  39. package/dist/framework-tools.js +3 -0
  40. package/dist/identity/rekey.js +6 -0
  41. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  42. package/dist/localization/core-messages/supplemental/ar-SA.js +15 -0
  43. package/dist/localization/core-messages/supplemental/de-DE.js +15 -0
  44. package/dist/localization/core-messages/supplemental/en-US.js +15 -0
  45. package/dist/localization/core-messages/supplemental/es-ES.js +15 -0
  46. package/dist/localization/core-messages/supplemental/fr-FR.js +15 -0
  47. package/dist/localization/core-messages/supplemental/hi-IN.js +15 -0
  48. package/dist/localization/core-messages/supplemental/ja-JP.js +15 -0
  49. package/dist/localization/core-messages/supplemental/ko-KR.js +15 -0
  50. package/dist/localization/core-messages/supplemental/pt-BR.js +15 -0
  51. package/dist/localization/core-messages/supplemental/zh-CN.js +15 -0
  52. package/dist/localization/core-messages/supplemental/zh-TW.js +15 -0
  53. package/dist/localization/mcp-settings-messages.d.ts +15 -0
  54. package/dist/mcp/actions/create-org-service-token.d.ts +8 -0
  55. package/dist/mcp/actions/create-org-service-token.js +93 -4
  56. package/dist/mcp/actions/list-org-service-tokens.d.ts +2 -0
  57. package/dist/mcp/actions/list-org-service-tokens.js +36 -8
  58. package/dist/mcp/actions/service-principal-input.d.ts +48 -0
  59. package/dist/mcp/actions/service-principal-input.js +102 -0
  60. package/dist/mcp/actions/set-service-principal-lifecycle.d.ts +15 -0
  61. package/dist/mcp/actions/set-service-principal-lifecycle.js +150 -0
  62. package/dist/mcp/actions/set-service-principal-policy.d.ts +14 -0
  63. package/dist/mcp/actions/set-service-principal-policy.js +105 -0
  64. package/dist/mcp/build-server.js +117 -13
  65. package/dist/mcp/connect-route.js +116 -4
  66. package/dist/mcp/connect-store.d.ts +7 -0
  67. package/dist/mcp/connect-store.js +21 -14
  68. package/dist/mcp-client/oauth-routes.js +2 -1
  69. package/dist/notifications/routes.d.ts +3 -3
  70. package/dist/observability/routes.d.ts +7 -7
  71. package/dist/org/index.d.ts +2 -2
  72. package/dist/org/service-principal-guard.d.ts +64 -0
  73. package/dist/org/service-principal-guard.js +140 -0
  74. package/dist/org/service-principal-policy.d.ts +104 -0
  75. package/dist/org/service-principal-policy.js +272 -0
  76. package/dist/provider-api/actions/custom-provider-registration.d.ts +5 -5
  77. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  78. package/dist/scripts/dev/index.js +2 -8
  79. package/dist/scripts/parse-args.d.ts +7 -0
  80. package/dist/scripts/parse-args.js +76 -0
  81. package/dist/scripts/utils.d.ts +1 -1
  82. package/dist/scripts/utils.js +1 -1
  83. package/dist/secrets/routes.d.ts +11 -11
  84. package/dist/server/action-discovery.js +16 -42
  85. package/dist/server/agent-chat/script-entries.js +3 -8
  86. package/dist/server/agent-chat-plugin.js +8 -42
  87. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  88. package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
  89. package/dist/server/auth.js +5 -2
  90. package/dist/server/core-routes-plugin.js +25 -6
  91. package/dist/server/google-oauth.js +8 -4
  92. package/dist/server/identity-sso.js +2 -1
  93. package/dist/server/index.d.ts +1 -0
  94. package/dist/server/index.js +1 -0
  95. package/dist/server/poll.d.ts +9 -0
  96. package/dist/server/poll.js +86 -7
  97. package/dist/server/query-echo-safe-redirect.d.ts +10 -0
  98. package/dist/server/query-echo-safe-redirect.js +102 -0
  99. package/dist/server/release-schema.js +4 -0
  100. package/dist/server/workspace-provider-oauth.js +2 -1
  101. package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +1 -0
  102. package/dist/templates/default/.agents/skills/actions/references/action-fields.md +1 -0
  103. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +24 -25
  104. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +33 -0
  105. package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +1 -0
  106. package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -0
  107. package/dist/triggers/actions/manage-automation.d.ts +7 -7
  108. package/dist/vite/client.js +1 -0
  109. package/docs/content/audit-log.mdx +15 -0
  110. package/docs/content/external-agents-catalog.mdx +19 -0
  111. package/docs/content/http-api.mdx +21 -2
  112. package/docs/content/pr-visual-recap.mdx +3 -1
  113. package/docs/content/template-clips-embed.mdx +1 -1
  114. package/docs/content/turn-into-app.mdx +118 -0
  115. package/package.json +4 -4
  116. package/src/templates/chat/.agents/skills/actions/references/action-fields.md +1 -0
  117. package/src/templates/default/.agents/skills/actions/references/action-fields.md +1 -0
  118. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +24 -25
  119. package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +33 -0
  120. package/src/templates/headless/.agents/skills/actions/references/action-fields.md +1 -0
  121. package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -0
@@ -1695,7 +1695,7 @@ export async function runServiceTokenMint(parsed, deps = {}) {
1695
1695
  const serviceName = parsed.serviceToken?.trim() ?? "";
1696
1696
  if (!parsed.url) {
1697
1697
  logErr(" --service-token requires the app URL.");
1698
- logErr(" Usage: npx @agent-native/core@latest connect <url> --service-token <name> [--ttl-days <1-365>]");
1698
+ logErr(" Usage: npx @agent-native/core@latest connect <url> --service-token <name> [--ttl-days <1-3650>]");
1699
1699
  return false;
1700
1700
  }
1701
1701
  if (!serviceName) {
@@ -1790,7 +1790,7 @@ Usage:
1790
1790
  No-browser fallback. Skip the device flow and write the entry with
1791
1791
  the supplied token (get it from the app's Connect page).
1792
1792
 
1793
- npx @agent-native/core@latest connect <url> --service-token <name> [--ttl-days <1-365>]
1793
+ npx @agent-native/core@latest connect <url> --service-token <name> [--ttl-days <1-3650>]
1794
1794
  Mint an ORG service token for CI (e.g. the PLAN_RECAP_TOKEN secret for
1795
1795
  PR Visual Recap). Authenticates you via the browser device flow, then
1796
1796
  mints a token owned by your ORGANIZATION — it keeps working if you
@@ -1831,6 +1831,11 @@ export async function runConnect(args, deps = {}) {
1831
1831
  return;
1832
1832
  }
1833
1833
  const parsed = parseConnectArgs(args);
1834
+ if (parsed.ttlDays !== undefined && parsed.serviceToken === undefined) {
1835
+ logErr(" --ttl-days is only supported with --service-token. Set a personal token lifetime on the app's Connect page.");
1836
+ process.exitCode = 1;
1837
+ return;
1838
+ }
1834
1839
  if (parsed.mode) {
1835
1840
  let ok;
1836
1841
  if (parsed.mode === "reconnect" || parsed.mode === "reauth") {
@@ -21,6 +21,19 @@ export declare function registerCollabActivityResource(resource: {
21
21
  resourceType: string;
22
22
  resourceId: string;
23
23
  }): () => void;
24
+ type CollabActivityEvent = {
25
+ source?: string;
26
+ resourceType?: string;
27
+ resourceId?: string;
28
+ requestSource?: string;
29
+ };
30
+ /**
31
+ * For a collab connection's own ~12 s poll. A viewer on a screen nobody else
32
+ * is on has no presence to notice, but that poll still carries the design's
33
+ * resource-scoped events from the other screens, and the shared transport is
34
+ * asleep on the 1-5 minute idle cadence until something wakes it.
35
+ */
36
+ export declare function noteCollabPollActivity(events: readonly CollabActivityEvent[]): void;
24
37
  /**
25
38
  * Declare that another person is working on the same resource as this tab.
26
39
  * While any lease is held, the shared transport polls every 2.5 s whenever no
@@ -72,11 +72,20 @@ function collabBoostWanted() {
72
72
  }
73
73
  return ((collabBoostLeases > 0 && collabBoostFresh()) || hasRecentResourceActivity);
74
74
  }
75
+ // The server mirrors every saved file into its Yjs doc under the "agent"
76
+ // source, so a lone editor's own save comes back as a collab event that is not
77
+ // from anyone else. Only a Yjs update a browser tab posted names its sender.
78
+ function isHumanCollabUpdate(event) {
79
+ return (event.source === "collab" &&
80
+ typeof event.requestSource === "string" &&
81
+ event.requestSource !== "" &&
82
+ event.requestSource !== "agent");
83
+ }
75
84
  function noteCollaboratorActivity(events) {
76
85
  const ownSource = getBrowserTabId();
77
86
  const until = Date.now() + COLLAB_ACTIVITY_WINDOW_MS;
78
87
  for (const event of events) {
79
- if (event.source !== "action" ||
88
+ if ((event.source !== "action" && !isHumanCollabUpdate(event)) ||
80
89
  typeof event.resourceType !== "string" ||
81
90
  event.resourceType === "" ||
82
91
  typeof event.resourceId !== "string" ||
@@ -89,6 +98,18 @@ function noteCollaboratorActivity(events) {
89
98
  }
90
99
  }
91
100
  }
101
+ /**
102
+ * For a collab connection's own ~12 s poll. A viewer on a screen nobody else
103
+ * is on has no presence to notice, but that poll still carries the design's
104
+ * resource-scoped events from the other screens, and the shared transport is
105
+ * asleep on the 1-5 minute idle cadence until something wakes it.
106
+ */
107
+ export function noteCollabPollActivity(events) {
108
+ const wasWanted = collabBoostWanted();
109
+ noteCollaboratorActivity(events);
110
+ if (!wasWanted && collabBoostWanted())
111
+ notifyCollabBoostChange();
112
+ }
92
113
  function collabBoostFresh() {
93
114
  return Date.now() - collabBoostActivityAt <= COLLAB_BOOST_IDLE_CEILING_MS;
94
115
  }
@@ -31,6 +31,7 @@
31
31
  import crypto from "node:crypto";
32
32
  import http from "node:http";
33
33
  import { createRunner, RunError } from "run";
34
+ import { parseServiceIdentityEmail } from "../org/service-identity.js";
34
35
  import { getRequestContext, getRequestRunContext, getRequestUserEmail, runWithRequestContext, } from "../server/request-context.js";
35
36
  import { failExpiredSandboxExecution, getSandboxExecutionForOwner, } from "./sandbox/executions-store.js";
36
37
  import { BACKGROUND_DEFAULT_TIMEOUT_MS, BACKGROUND_MAX_TIMEOUT_MS, SANDBOX_EXECUTION_REDRIVE_AFTER_MS, driveSandboxExecution, enqueueSandboxExecution, getSandboxAdapter, isQueuedSandboxAdapter, registerSandboxExecutionRunner, resolveExecutionSandboxAdapter, } from "./sandbox/index.js";
@@ -501,8 +502,22 @@ function createBridgeInvoker(options) {
501
502
  if (callBudget)
502
503
  callBudget.count += 1;
503
504
  usedTools.add(toolName);
505
+ let servicePrincipalRefusedError;
504
506
  try {
505
- const run = () => entry.run(childArgs, options.context);
507
+ const context = options.context;
508
+ if (context && parseServiceIdentityEmail(context.userEmail)) {
509
+ const guard = await import("../org/service-principal-guard.js");
510
+ servicePrincipalRefusedError = guard.ServicePrincipalRefusedError;
511
+ await guard.enforceServicePrincipalActionGrant({
512
+ email: context.userEmail,
513
+ orgId: context.orgId,
514
+ actionName: toolName,
515
+ caller: context.caller,
516
+ });
517
+ }
518
+ // Name the child, not `run-code`: a service principal's grant is checked
519
+ // against the action actually being run.
520
+ const run = () => entry.run(childArgs, options.context && { ...options.context, actionName: toolName });
506
521
  const result = options.context?.credentialScope === "org"
507
522
  ? await runWithRequestContext({
508
523
  ...getRequestContext(),
@@ -516,6 +531,12 @@ function createBridgeInvoker(options) {
516
531
  return formatBridgeResult(result);
517
532
  }
518
533
  catch (error) {
534
+ if (error instanceof BridgeInvocationError)
535
+ throw error;
536
+ if (servicePrincipalRefusedError &&
537
+ error instanceof servicePrincipalRefusedError) {
538
+ throw new BridgeInvocationError(error.statusCode, error.message);
539
+ }
519
540
  throw new BridgeInvocationError(500, error instanceof Error ? error.message : String(error));
520
541
  }
521
542
  };
@@ -21,6 +21,7 @@ function sandboxWriteAllowPaths(tmpDir) {
21
21
  return [...paths];
22
22
  }
23
23
  let cachedPermissionFlag;
24
+ let cachedNetPermissionSupport;
24
25
  function resolvePermissionFlag() {
25
26
  if (cachedPermissionFlag !== undefined)
26
27
  return cachedPermissionFlag;
@@ -42,6 +43,19 @@ function resolvePermissionFlag() {
42
43
  cachedPermissionFlag = null;
43
44
  return null;
44
45
  }
46
+ function supportsNetPermissionFlag(permissionFlag) {
47
+ if (cachedNetPermissionSupport !== undefined) {
48
+ return cachedNetPermissionSupport;
49
+ }
50
+ try {
51
+ const probe = spawnSync(process.execPath, [permissionFlag, "--allow-net", "-e", "process.exit(0)"], { timeout: 10_000, stdio: "ignore" });
52
+ cachedNetPermissionSupport = probe.status === 0;
53
+ }
54
+ catch {
55
+ cachedNetPermissionSupport = false;
56
+ }
57
+ return cachedNetPermissionSupport;
58
+ }
45
59
  export class LocalChildProcessAdapter {
46
60
  id = "local-child-process";
47
61
  async run(request) {
@@ -60,6 +74,12 @@ export class LocalChildProcessAdapter {
60
74
  const nodeArgs = permissionFlag
61
75
  ? [
62
76
  permissionFlag,
77
+ // Node 25+ also denies network under --permission. The sandbox
78
+ // bridge uses loopback HTTP, and direct outbound requests are
79
+ // already part of its unauthenticated execution contract.
80
+ ...(supportsNetPermissionFlag(permissionFlag)
81
+ ? ["--allow-net"]
82
+ : []),
63
83
  ...sandboxReadAllowPaths(tmpDir).map((allowedPath) => `--allow-fs-read=${allowedPath}`),
64
84
  ...sandboxWriteAllowPaths(tmpDir).map((allowedPath) => `--allow-fs-write=${allowedPath}`),
65
85
  tmpFile,
@@ -3,7 +3,7 @@ import { Awareness } from "y-protocols/awareness";
3
3
  import * as Y from "yjs";
4
4
  import { agentNativePath } from "../client/api-path.js";
5
5
  import { useAvatarUrl } from "../client/use-avatar.js";
6
- import { acquireCollabPollBoost, registerCollabActivityResource, subscribeSyncEvents, } from "../client/use-db-sync.js";
6
+ import { acquireCollabPollBoost, noteCollabPollActivity, registerCollabActivityResource, subscribeSyncEvents, } from "../client/use-db-sync.js";
7
7
  import { REALTIME_CAP_NO_AWARENESS, REALTIME_CAP_POLL_LIVE, } from "../realtime-protocol.js";
8
8
  import { dedupeCollabUsersByEmail } from "./types.js";
9
9
  export { dedupeCollabUsersByEmail, emailToColor, emailToName, } from "./types.js";
@@ -151,6 +151,8 @@ class CollabDocConnection {
151
151
  pollCycleCount = 0;
152
152
  pollVersion = 0;
153
153
  lastPolledVersion = 0;
154
+ pollCursor = null;
155
+ pollActivityBaselineReady = false;
154
156
  stateVectorFetch = null;
155
157
  stateVectorAbortControllers = new Set();
156
158
  sseActive = false;
@@ -415,7 +417,9 @@ class CollabDocConnection {
415
417
  });
416
418
  }
417
419
  fetchInitialState() {
418
- fetch(`${this.baseUrl}/${this.docId}/state`).then(async (res) => {
420
+ fetch(`${this.baseUrl}/${this.docId}/state`, {
421
+ headers: { "X-Agent-Native-Poll-Baseline": "1" },
422
+ }).then(async (res) => {
419
423
  if (this.disposed)
420
424
  return;
421
425
  if (res.status === 404 || res.status === 403) {
@@ -433,6 +437,17 @@ class CollabDocConnection {
433
437
  this.markInitializationFailed("invalid-payload");
434
438
  return;
435
439
  }
440
+ const baseline = data.activityBaseline;
441
+ if (baseline?.status === "ready" &&
442
+ Number.isSafeInteger(baseline.version) &&
443
+ baseline.version >= 0 &&
444
+ typeof baseline.cursor === "string" &&
445
+ baseline.cursor.length > 0) {
446
+ this.pollVersion = baseline.version;
447
+ this.lastPolledVersion = baseline.version;
448
+ this.pollCursor = baseline.cursor;
449
+ this.pollActivityBaselineReady = true;
450
+ }
436
451
  if (data.state) {
437
452
  try {
438
453
  const binary = base64ToUint8Array(data.state);
@@ -807,12 +822,16 @@ class CollabDocConnection {
807
822
  return;
808
823
  this.flushPendingUpdates();
809
824
  try {
810
- const res = await fetch(agentNativePath(`/_agent-native/poll?since=${this.pollVersion}`));
825
+ const pollUrl = this.pollCursor
826
+ ? `/_agent-native/poll?cursor=${encodeURIComponent(this.pollCursor)}`
827
+ : `/_agent-native/poll?since=${this.pollVersion}`;
828
+ const res = await fetch(agentNativePath(pollUrl));
811
829
  if (!res.ok)
812
830
  throw new Error("HTTP " + res.status);
813
831
  const data = await res.json();
814
832
  if (!this.syncActive || this.disposed)
815
833
  return;
834
+ const hadActivityBaseline = this.pollActivityBaselineReady;
816
835
  const { version, events } = data;
817
836
  const versionGap = version - this.lastPolledVersion;
818
837
  const hadGap = versionGap > POLL_RING_BUFFER_SIZE;
@@ -832,6 +851,20 @@ class CollabDocConnection {
832
851
  }
833
852
  }
834
853
  }
854
+ if (hadActivityBaseline) {
855
+ noteCollabPollActivity(events);
856
+ }
857
+ else {
858
+ const reconciliation = await this.fetchStateVector();
859
+ if (reconciliation.status !== "synced") {
860
+ throw reconciliation.status === "failed"
861
+ ? reconciliation.error
862
+ : new Error("Could not reconcile state without an activity baseline");
863
+ }
864
+ }
865
+ this.pollActivityBaselineReady = true;
866
+ if (typeof data.cursor === "string")
867
+ this.pollCursor = data.cursor;
835
868
  this.pollVersion = version;
836
869
  this.lastPolledVersion = version;
837
870
  this.pollCycleCount++;
@@ -1,23 +1,28 @@
1
1
  export declare const getCollabState: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
2
2
  error: string;
3
- docId?: undefined;
4
- state?: undefined;
5
3
  } | {
6
4
  error?: undefined;
7
5
  docId: string;
8
6
  state: string;
7
+ activityBaseline?: {
8
+ status: "ready";
9
+ version: number;
10
+ cursor: string;
11
+ } | {
12
+ status: "unavailable";
13
+ };
9
14
  }>>;
10
15
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
11
- error: string;
12
16
  ok?: undefined;
17
+ error: string;
13
18
  } | {
14
19
  error?: undefined;
15
20
  ok: boolean;
16
21
  }>>;
17
22
  export declare const postCollabText: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
18
- text?: undefined;
19
23
  ok?: undefined;
20
24
  error: string;
25
+ text?: undefined;
21
26
  } | {
22
27
  error?: undefined;
23
28
  ok: boolean;
@@ -1,4 +1,4 @@
1
- import { defineEventHandler, setResponseStatus, setResponseHeader, getRouterParam, getQuery, } from "h3";
1
+ import { defineEventHandler, setResponseStatus, setResponseHeader, getRouterParam, getQuery, getRequestHeader, } from "h3";
2
2
  import { readBody } from "../server/h3-helpers.js";
3
3
  import { uint8ArrayToBase64, base64ToUint8Array } from "./storage.js";
4
4
  import * as manager from "./ydoc-manager.js";
@@ -25,6 +25,19 @@ export const getCollabState = defineEventHandler(async (event) => {
25
25
  }
26
26
  const query = getQuery(event);
27
27
  const encodedStateVector = typeof query.stateVector === "string" ? query.stateVector : null;
28
+ let activityBaseline;
29
+ if (getRequestHeader(event, "x-agent-native-poll-baseline") === "1") {
30
+ try {
31
+ const { getCurrentPollBaseline } = await import("../server/poll.js");
32
+ activityBaseline = {
33
+ status: "ready",
34
+ ...(await getCurrentPollBaseline()),
35
+ };
36
+ }
37
+ catch {
38
+ activityBaseline = { status: "unavailable" };
39
+ }
40
+ }
28
41
  let state;
29
42
  if (encodedStateVector) {
30
43
  try {
@@ -41,6 +54,7 @@ export const getCollabState = defineEventHandler(async (event) => {
41
54
  return {
42
55
  docId,
43
56
  state: uint8ArrayToBase64(state),
57
+ ...(activityBaseline ? { activityBaseline } : {}),
44
58
  };
45
59
  });
46
60
  export const postCollabUpdate = defineEventHandler(async (event) => {
@@ -1,6 +1,6 @@
1
1
  export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
2
- ok?: undefined;
3
2
  error: string;
3
+ ok?: undefined;
4
4
  } | {
5
5
  error?: undefined;
6
6
  ok: boolean;
@@ -984,6 +984,18 @@ export function generateWorkerEntry(routes, pluginPaths, defaultPluginStems = []
984
984
  { status: 405, headers: { "Content-Type": "application/json" } }
985
985
  );
986
986
  }
987
+ const actionSession = ${varName}.requiresAuth === true
988
+ ? await getGeneratedSession(event)
989
+ : undefined;
990
+ const actionContext = uiActionContext ?? (actionSession?.email
991
+ ? { userEmail: actionSession.email, orgId: actionSession.orgId ?? null }
992
+ : undefined);
993
+ if (${varName}.requiresAuth === true && !actionContext?.userEmail) {
994
+ return new Response(
995
+ JSON.stringify({ error: "Unauthorized" }),
996
+ { status: 401, headers: { "Content-Type": "application/json" } },
997
+ );
998
+ }
987
999
  const params = ${a.method === "get" ? "parseActionSearchParams(event.url.searchParams)" : "(await readBody(event)) ?? {}"};
988
1000
  try {
989
1001
  const caller =
@@ -994,16 +1006,11 @@ export function generateWorkerEntry(routes, pluginPaths, defaultPluginStems = []
994
1006
  caller,
995
1007
  requestHeaders: event.req.headers,
996
1008
  actionName: ${JSON.stringify(a.name)},
997
- ...(uiActionContext
998
- ? {
999
- userEmail: uiActionContext.userEmail,
1000
- orgId: uiActionContext.orgId ?? null,
1001
- }
1002
- : {}),
1009
+ ...(actionContext ?? {}),
1003
1010
  };
1004
1011
  const runAction = () => ${varName}.run(params, actionRunContext);
1005
- const result = actionIsUiOnly
1006
- ? await runWithGeneratedRequestContext(uiActionContext, runAction)
1012
+ const result = actionContext
1013
+ ? await runWithGeneratedRequestContext(actionContext, runAction)
1007
1014
  : await runAction();
1008
1015
  if (typeof result === "string") { try { return JSON.parse(result); } catch { return result; } }
1009
1016
  return result;
@@ -64,19 +64,97 @@ function builtinAgentsEnvSnippet() {
64
64
  `;
65
65
  }
66
66
  function workspaceDirectoryEnvSnippet(workspaceApps) {
67
- const orgDirectoryUrl = getAppConfig().workspace.orgDirectoryUrl?.trim();
68
- if (!orgDirectoryUrl && !workspaceApps.some((app) => app.isDispatch)) {
67
+ const configuredOrgDirectoryUrl = getAppConfig().workspace.orgDirectoryUrl?.trim();
68
+ const dispatchApp = workspaceApps.find((app) => app.isDispatch);
69
+ if (!configuredOrgDirectoryUrl && !dispatchApp) {
69
70
  return builtinAgentsEnvSnippet();
70
71
  }
72
+ const runtimeDirectoryResolver = dispatchApp
73
+ ? `
74
+ function logInvalidDirectoryBase() {
75
+ const logKey = Symbol.for("agent-native.workspace.invalid-directory-base");
76
+ if (globalThis[logKey]) return;
77
+ globalThis[logKey] = true;
78
+ console.error("[workspace] Invalid organization directory base URL");
79
+ }
80
+
81
+ function resolveRuntimeDirectoryUrl() {
82
+ const isVercelProduction =
83
+ processRef.env.VERCEL_ENV?.trim().toLowerCase() === "production";
84
+ const vercelCandidates = processRef.env.VERCEL
85
+ ? (isVercelProduction
86
+ ? [
87
+ processRef.env.VERCEL_PROJECT_PRODUCTION_URL,
88
+ processRef.env.VERCEL_URL,
89
+ processRef.env.VERCEL_BRANCH_URL,
90
+ ]
91
+ : [processRef.env.VERCEL_URL, processRef.env.VERCEL_BRANCH_URL]
92
+ )
93
+ .filter(Boolean)
94
+ .map((candidate) =>
95
+ /^https?:\\/\\//i.test(candidate)
96
+ ? candidate
97
+ : "https://" + candidate,
98
+ )
99
+ : [];
100
+ const candidates = [
101
+ processRef.env.APP_URL,
102
+ processRef.env.URL,
103
+ processRef.env.DEPLOY_URL,
104
+ processRef.env.BETTER_AUTH_URL,
105
+ ...vercelCandidates,
106
+ processRef.env.WORKSPACE_GATEWAY_URL,
107
+ processRef.env.VITE_WORKSPACE_GATEWAY_URL,
108
+ ].filter(Boolean);
109
+ let loopbackUrl;
110
+ for (const candidate of candidates) {
111
+ let baseUrl;
112
+ try {
113
+ baseUrl = new URL(candidate);
114
+ } catch {
115
+ logInvalidDirectoryBase();
116
+ continue;
117
+ }
118
+ if (baseUrl.protocol !== "http:" && baseUrl.protocol !== "https:") {
119
+ logInvalidDirectoryBase();
120
+ continue;
121
+ }
122
+ const directoryUrl = new URL(${JSON.stringify(dispatchApp.path)}, baseUrl)
123
+ .toString()
124
+ .replace(/\\/$/, "");
125
+ const hostname = baseUrl.hostname.toLowerCase().replace(/\\.$/, "");
126
+ const mappedIpv4 = hostname.match(
127
+ /^\\[::ffff:([0-9a-f]{1,4}):[0-9a-f]{1,4}\\]$/,
128
+ );
129
+ const isMappedIpv4Loopback =
130
+ mappedIpv4 !== null &&
131
+ (Number.parseInt(mappedIpv4[1], 16) >> 8) === 0x7f;
132
+ if (
133
+ hostname === "localhost" ||
134
+ hostname.endsWith(".localhost") ||
135
+ /^127(?:\\.\\d{1,3}){3}$/.test(hostname) ||
136
+ /^0(?:\\.\\d{1,3}){3}$/.test(hostname) ||
137
+ hostname === "[::1]" ||
138
+ hostname === "::1" ||
139
+ hostname === "[::]" ||
140
+ hostname === "::" ||
141
+ isMappedIpv4Loopback
142
+ ) {
143
+ loopbackUrl ??= directoryUrl;
144
+ continue;
145
+ }
146
+ return directoryUrl;
147
+ }
148
+ return loopbackUrl ?? null;
149
+ }
150
+ `
151
+ : "";
71
152
  return `${builtinAgentsEnvSnippet()}
153
+ ${runtimeDirectoryResolver}
72
154
  const directoryOrigin =
73
155
  processRef.env.AGENT_NATIVE_ORG_DIRECTORY_URL ||
74
- ${JSON.stringify(orgDirectoryUrl ?? null)} ||
75
- processRef.env.WORKSPACE_GATEWAY_URL ||
76
- processRef.env.APP_URL ||
77
- processRef.env.URL ||
78
- processRef.env.DEPLOY_URL ||
79
- processRef.env.BETTER_AUTH_URL;
156
+ ${JSON.stringify(configuredOrgDirectoryUrl ?? null)} ||
157
+ ${dispatchApp ? "resolveRuntimeDirectoryUrl()" : "null"};
80
158
  if (directoryOrigin) {
81
159
  processRef.env.AGENT_NATIVE_ORG_DIRECTORY_URL = directoryOrigin;
82
160
  }
@@ -167,11 +245,8 @@ function buildOneApp(workspaceRoot, appsDir, app, preset, execFile, workspaceApp
167
245
  const appDir = path.join(appsDir, app);
168
246
  const workspaceAppAudience = workspaceAppAudienceForApp(workspaceApps, app);
169
247
  const workspaceAppRouteAccess = workspaceAppRouteAccessForApp(workspaceApps, app);
170
- const workspaceGatewayUrl = process.env.VITE_WORKSPACE_GATEWAY_URL || workspaceBaseUrl();
171
- const orgDirectoryUrl = getAppConfig().workspace.orgDirectoryUrl?.trim() ||
172
- (workspaceApps.some((entry) => entry.isDispatch)
173
- ? workspaceGatewayUrl
174
- : null);
248
+ const workspaceGatewayUrl = workspaceBaseUrl();
249
+ const orgDirectoryUrl = getAppConfig().workspace.orgDirectoryUrl?.trim();
175
250
  const workspaceOAuthUrl = workspaceOAuthOrigin(workspaceGatewayUrl);
176
251
  const frameworkRoutePrefix = workspaceFrameworkRoutePrefixEnv();
177
252
  const env = {
@@ -42,8 +42,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
42
42
  url: string;
43
43
  id: string;
44
44
  provider: string;
45
- error?: undefined;
46
45
  configured?: undefined;
46
+ error?: undefined;
47
47
  } | {
48
48
  url?: undefined;
49
49
  id?: undefined;
@@ -12,8 +12,22 @@ function makeBody(bytes, mimeType) {
12
12
  ? new Blob([bytes], { type: mimeType })
13
13
  : bytes;
14
14
  }
15
+ // Builder's `/api/v1/upload` endpoint sniffs these two Content-Types as a
16
+ // JSON-bodied request (its legacy `{ image: "data:..." }` upload shape)
17
+ // instead of raw file bytes, so it responds 400 "No image specified" for any
18
+ // real `application/json` or `text/plain` file — even though every other
19
+ // mimetype in `UPLOAD_ALLOWED_MIME_PREFIXES` (csv, pdf, zip, docs, video,
20
+ // octet-stream, other text/* subtypes) uploads through it without issue. The
21
+ // signed-URL flow PUTs raw bytes straight to GCS, so it never hits that
22
+ // body-sniffing path.
23
+ function builderMisparsesContentTypeAsJsonBody(mimeType) {
24
+ const normalized = mimeType.toLowerCase();
25
+ return normalized === "application/json" || normalized === "text/plain";
26
+ }
15
27
  function shouldUseSignedUrlUpload(bytes, mimeType) {
16
- return (bytes.byteLength > LARGE_FILE_THRESHOLD_BYTES || /^video\//i.test(mimeType));
28
+ return (bytes.byteLength > LARGE_FILE_THRESHOLD_BYTES ||
29
+ /^video\//i.test(mimeType) ||
30
+ builderMisparsesContentTypeAsJsonBody(mimeType));
17
31
  }
18
32
  function fetchWithTimeout(url, init) {
19
33
  const controller = new AbortController();
@@ -75,10 +89,10 @@ async function withAssetAuthorization(run, held = {}) {
75
89
  }
76
90
  }
77
91
  }
78
- async function uploadLargeFileViaSignedUrl(input, held, bareMimeType, bytes) {
92
+ async function uploadViaSignedUrl(input, held, bareMimeType, bytes) {
79
93
  const name = input.filename ?? "upload";
80
94
  const mb = (bytes.byteLength / (1024 * 1024)).toFixed(1);
81
- console.log(`[builder-upload] large-file path: ${name} ${mb}MB ${bareMimeType}`);
95
+ console.log(`[builder-upload] signed-url path: ${name} ${mb}MB ${bareMimeType}`);
82
96
  console.log(`[builder-upload] step 1: requesting signed URL`);
83
97
  const { uploadUrl, assetId, requiredHeaders } = await withAssetAuthorization(({ authorization, apiKey }) => requestBuilderSignedUrl(authorization, apiKey, name, bareMimeType, bytes.byteLength), held);
84
98
  console.log(`[builder-upload] step 1 ok: assetId=${assetId}`);
@@ -228,7 +242,7 @@ export const builderFileUploadProvider = {
228
242
  const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
229
243
  const mb = (bytes.byteLength / (1024 * 1024)).toFixed(1);
230
244
  if (shouldUseSignedUrlUpload(bytes, bareMimeType)) {
231
- return uploadLargeFileViaSignedUrl(input, held, bareMimeType, bytes);
245
+ return uploadViaSignedUrl(input, held, bareMimeType, bytes);
232
246
  }
233
247
  console.log(`[builder-upload] small-file path: ${filename ?? "upload"} ${mb}MB ${bareMimeType}`);
234
248
  const response = await withAssetAuthorization(({ authorization, apiKey }) => {
@@ -165,6 +165,7 @@ export const CORE_ACTION_GROUPS = {
165
165
  "list-audit-events": "audit",
166
166
  "get-audit-event": "audit",
167
167
  "export-audit-events": "audit",
168
+ "export-audit-ocsf": "audit",
168
169
  // Observability promotion reuses `labs` until a dedicated group exists.
169
170
  // A new FRAMEWORK_TOOL_GROUPS member is filtered at thirteen composition
170
171
  // sites; do not add `observability` in the same change as this action.
@@ -189,6 +190,8 @@ export const CORE_ACTION_GROUPS = {
189
190
  "create-org-service-token": "orgServiceTokens",
190
191
  "list-org-service-tokens": "orgServiceTokens",
191
192
  "revoke-org-service-token": "orgServiceTokens",
193
+ "set-service-principal-policy": "orgServiceTokens",
194
+ "set-service-principal-lifecycle": "orgServiceTokens",
192
195
  "list-review-comments": "review",
193
196
  "create-review-comment": "review",
194
197
  "reply-review-comment": "review",
@@ -112,6 +112,12 @@ export const IDENTITY_REKEY_COLUMNS = [
112
112
  // A missing connect-token row reads as "not revoked", so revoke, never delete.
113
113
  { table: "mcp_connect_tokens", column: "owner_email", offboard: "revoke" },
114
114
  { table: "mcp_connect_tokens", column: "created_by" },
115
+ // The accountable owner outlives their account: the principal stays governed and its owner reads as stale.
116
+ {
117
+ table: "service_principal_policies",
118
+ column: "owner_email",
119
+ offboard: "retain",
120
+ },
115
121
  { table: "mcp_oauth_codes", column: "owner_email", offboard: "delete" },
116
122
  {
117
123
  table: "mcp_oauth_codes",
@@ -11,9 +11,9 @@ declare const _default: import("../../action.js").ActionDefinition<{
11
11
  enabled?: undefined;
12
12
  nextRun?: undefined;
13
13
  } | {
14
+ deleted?: undefined;
14
15
  name: string;
15
16
  enabled: boolean;
16
17
  nextRun: string;
17
- deleted?: undefined;
18
18
  }>;
19
19
  export default _default;
@@ -54,6 +54,21 @@ export const mcpConnectMessages = {
54
54
  couldNotCreate: "تعذر إنشاء الرمز.",
55
55
  networkError: "حدث خطأ في الشبكة. حاول مرة أخرى.",
56
56
  urlTitle: "عنوان MCP الخاص بك",
57
+ servicePrincipals: "كيانات الخدمة",
58
+ principalUngoverned: "غير خاضع للحوكمة",
59
+ principalActive: "نشط",
60
+ principalSuspended: "معلّق",
61
+ principalRetired: "متقاعد",
62
+ principalUngovernedHint: "لم يتم تعيين مالك أو إذن للإجراءات.",
63
+ principalOwner: "المالك",
64
+ principalRisk: "المخاطر",
65
+ riskLow: "منخفضة",
66
+ riskMedium: "متوسطة",
67
+ riskHigh: "عالية",
68
+ suspend: "تعليق",
69
+ resume: "استئناف",
70
+ couldNotUpdatePrincipal: "تعذّر تحديث كيان الخدمة.",
71
+ containmentIncomplete: "تم التحديث، لكن تعذّر إيقاف بعض عمليات التشغيل أو الرموز. حاول مرة أخرى.",
57
72
  };
58
73
  export const mcpSettingsMessages = {
59
74
  mcpTitle: "MCP",
@@ -54,6 +54,21 @@ export const mcpConnectMessages = {
54
54
  couldNotCreate: "Token konnte nicht erstellt werden.",
55
55
  networkError: "Netzwerkfehler. Bitte versuche es erneut.",
56
56
  urlTitle: "Deine MCP-URL",
57
+ servicePrincipals: "Dienstprinzipale",
58
+ principalUngoverned: "Nicht verwaltet",
59
+ principalActive: "Aktiv",
60
+ principalSuspended: "Gesperrt",
61
+ principalRetired: "Stillgelegt",
62
+ principalUngovernedHint: "Kein Eigentümer und keine Aktionsfreigabe festgelegt.",
63
+ principalOwner: "Eigentümer",
64
+ principalRisk: "Risiko",
65
+ riskLow: "niedrig",
66
+ riskMedium: "mittel",
67
+ riskHigh: "hoch",
68
+ suspend: "Sperren",
69
+ resume: "Fortsetzen",
70
+ couldNotUpdatePrincipal: "Der Dienstprinzipal konnte nicht aktualisiert werden.",
71
+ containmentIncomplete: "Aktualisiert, aber einige Ausführungen oder Token konnten nicht gestoppt werden. Versuche es erneut.",
57
72
  };
58
73
  export const mcpSettingsMessages = {
59
74
  mcpTitle: "MCP",