@agent-native/core 0.222.0-nightly-20261029004912 → 0.222.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 (55) hide show
  1. package/dist/agent/production-agent.js +22 -9
  2. package/dist/agent/run-store.d.ts +9 -1
  3. package/dist/agent/run-store.js +26 -3
  4. package/dist/cli/code-agent-mcp-config.js +6 -1
  5. package/dist/client/chat/agentkit-agent-native.js +1 -2
  6. package/dist/client/chat/agentkit-protocol.js +14 -0
  7. package/dist/client/chat/runtime.d.ts +5 -1
  8. package/dist/client/chat/runtime.js +23 -1
  9. package/dist/client/sse-event-processor.d.ts +10 -0
  10. package/dist/client/sse-event-processor.js +1 -1
  11. package/dist/collab/routes.d.ts +1 -1
  12. package/dist/collab/struct-routes.d.ts +1 -1
  13. package/dist/connections/catalog.d.ts +1 -1
  14. package/dist/connections/catalog.js +4 -11
  15. package/dist/mcp-client/config.d.ts +6 -0
  16. package/dist/mcp-client/manager.js +25 -2
  17. package/dist/mcp-client/routes.js +2 -0
  18. package/dist/mcp-client/workspace-connection-servers.d.ts +30 -0
  19. package/dist/mcp-client/workspace-connection-servers.js +119 -0
  20. package/dist/notifications/routes.d.ts +3 -3
  21. package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
  22. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  23. package/dist/provider-api/actions/provider-api.js +2 -0
  24. package/dist/provider-api/agent-request-policy.d.ts +13 -0
  25. package/dist/provider-api/agent-request-policy.js +31 -0
  26. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  27. package/dist/provider-api/corpus-jobs.js +7 -1
  28. package/dist/provider-api/index.d.ts +4 -1
  29. package/dist/provider-api/index.js +4 -1
  30. package/dist/provider-api/read-only-requests.js +11 -2
  31. package/dist/resources/handlers.d.ts +1 -1
  32. package/dist/secrets/routes.d.ts +4 -4
  33. package/dist/server/action-discovery.js +6 -0
  34. package/dist/server/agent-chat/process-run-failure.js +58 -2
  35. package/dist/server/agent-engine-api-key-route.d.ts +3 -3
  36. package/dist/server/workspace-provider-oauth.d.ts +1 -0
  37. package/dist/server/workspace-provider-oauth.js +10 -2
  38. package/dist/templates/chat/.agents/skills/actions/references/provider-apis.md +13 -0
  39. package/dist/templates/default/.agents/skills/actions/references/provider-apis.md +13 -0
  40. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +10 -4
  41. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +287 -0
  42. package/dist/templates/factory/.agents/skills/review-prs/SKILL.md +266 -92
  43. package/dist/templates/headless/.agents/skills/actions/references/provider-apis.md +13 -0
  44. package/dist/templates/workspace-core/.agents/skills/actions/references/provider-apis.md +13 -0
  45. package/dist/vite/action-types-plugin.js +4 -0
  46. package/dist/workspace-connections/actions/list-workspace-connection-scopes.d.ts +16 -0
  47. package/dist/workspace-connections/actions/list-workspace-connection-scopes.js +62 -0
  48. package/package.json +5 -4
  49. package/src/templates/chat/.agents/skills/actions/references/provider-apis.md +13 -0
  50. package/src/templates/default/.agents/skills/actions/references/provider-apis.md +13 -0
  51. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +10 -4
  52. package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +287 -0
  53. package/src/templates/factory/.agents/skills/review-prs/SKILL.md +266 -92
  54. package/src/templates/headless/.agents/skills/actions/references/provider-apis.md +13 -0
  55. package/src/templates/workspace-core/.agents/skills/actions/references/provider-apis.md +13 -0
@@ -77,7 +77,6 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
77
77
  }, z.core.$strip>>, {
78
78
  id?: undefined;
79
79
  deleted?: undefined;
80
- provider?: undefined;
81
80
  message?: undefined;
82
81
  found?: undefined;
83
82
  providers: {
@@ -90,44 +89,45 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
90
89
  updatedAt: number;
91
90
  }[];
92
91
  count: number;
92
+ provider?: undefined;
93
93
  registered?: undefined;
94
94
  label?: undefined;
95
95
  } | {
96
96
  id?: undefined;
97
+ count?: undefined;
97
98
  deleted?: undefined;
98
99
  message?: undefined;
99
100
  providers?: undefined;
100
- count?: undefined;
101
101
  found: boolean;
102
102
  provider: import("../custom-registry.js").CustomProviderConfig;
103
103
  registered?: undefined;
104
104
  label?: undefined;
105
105
  } | {
106
+ count?: undefined;
106
107
  deleted?: undefined;
107
- provider?: undefined;
108
108
  message?: undefined;
109
109
  providers?: undefined;
110
- count?: undefined;
110
+ provider?: undefined;
111
111
  found: boolean;
112
112
  id: string;
113
113
  registered?: undefined;
114
114
  label?: undefined;
115
115
  } | {
116
- provider?: undefined;
116
+ count?: undefined;
117
117
  message?: undefined;
118
118
  found?: undefined;
119
119
  providers?: undefined;
120
- count?: undefined;
120
+ provider?: undefined;
121
121
  deleted: boolean;
122
122
  id: string;
123
123
  registered?: undefined;
124
124
  label?: undefined;
125
125
  } | {
126
+ count?: undefined;
126
127
  deleted?: undefined;
127
- provider?: undefined;
128
128
  found?: undefined;
129
129
  providers?: undefined;
130
- count?: undefined;
130
+ provider?: undefined;
131
131
  registered: boolean;
132
132
  id: string;
133
133
  label: string;
@@ -319,7 +319,6 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
319
319
  id?: undefined;
320
320
  message?: undefined;
321
321
  found?: undefined;
322
- deleted?: undefined;
323
322
  providers: {
324
323
  id: string;
325
324
  label: string;
@@ -331,26 +330,27 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
331
330
  }[];
332
331
  count: number;
333
332
  provider?: undefined;
333
+ deleted?: undefined;
334
334
  registered?: undefined;
335
335
  label?: undefined;
336
336
  } | {
337
337
  id?: undefined;
338
338
  count?: undefined;
339
339
  message?: undefined;
340
- deleted?: undefined;
341
340
  providers?: undefined;
342
341
  found: boolean;
343
342
  provider: import("../custom-registry.js").CustomProviderConfig;
343
+ deleted?: undefined;
344
344
  registered?: undefined;
345
345
  label?: undefined;
346
346
  } | {
347
347
  count?: undefined;
348
348
  message?: undefined;
349
- deleted?: undefined;
350
349
  providers?: undefined;
351
350
  provider?: undefined;
352
351
  found: boolean;
353
352
  id: string;
353
+ deleted?: undefined;
354
354
  registered?: undefined;
355
355
  label?: undefined;
356
356
  } | {
@@ -366,9 +366,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
366
366
  } | {
367
367
  count?: undefined;
368
368
  found?: undefined;
369
- deleted?: undefined;
370
369
  providers?: undefined;
371
370
  provider?: undefined;
371
+ deleted?: undefined;
372
372
  registered: boolean;
373
373
  id: string;
374
374
  label: string;
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { defineAction, } from "../../action.js";
3
3
  import { getCredentialContext } from "../../server/request-context.js";
4
+ import { assertAgentProviderRequestAllowed } from "../agent-request-policy.js";
4
5
  import { isReadOnlyProviderPost } from "../read-only-requests.js";
5
6
  import { rejectFailedProviderResult } from "../result-outcome.js";
6
7
  import { stagingExecuteRequest } from "../staging.js";
@@ -261,6 +262,7 @@ export function createProviderApiRequestAction(runtime, options = {}) {
261
262
  grounding: true,
262
263
  run: async (rawArgs) => {
263
264
  const args = rawArgs;
265
+ assertAgentProviderRequestAllowed(args);
264
266
  if (args.stageAs) {
265
267
  if (!options.appId) {
266
268
  throw new Error("createProviderApiRequestAction requires appId when stageAs is used.");
@@ -0,0 +1,13 @@
1
+ /** Whether agent-supplied provider requests are limited to reads for a provider. */
2
+ export declare function providerApiAgentRequestsReadOnly(provider: string): boolean;
3
+ /**
4
+ * Refuse a write in a request an agent supplied for a read-only provider.
5
+ * Every agent-facing action that forwards a caller-built request (request,
6
+ * corpus jobs) calls this before executing or storing it.
7
+ */
8
+ export declare function assertAgentProviderRequestAllowed(request: {
9
+ provider: string;
10
+ method?: string | null;
11
+ path: string;
12
+ body?: unknown;
13
+ }): void;
@@ -0,0 +1,31 @@
1
+ import { fail } from "../action.js";
2
+ import { isReadOnlyProviderPost } from "./read-only-requests.js";
3
+ /**
4
+ * Built-in providers the generic agent request tools may only read. Writes go
5
+ * through the provider's MCP tools or app-defined actions, which can enforce
6
+ * their own limits; server code calling `executeRequest` is unaffected.
7
+ *
8
+ * Kept out of `./index.js` so the shared request action only needs types from
9
+ * the runtime module, and template specs that mock it keep working.
10
+ */
11
+ const AGENT_READ_ONLY_PROVIDERS = new Set(["hubspot"]);
12
+ /** Whether agent-supplied provider requests are limited to reads for a provider. */
13
+ export function providerApiAgentRequestsReadOnly(provider) {
14
+ return AGENT_READ_ONLY_PROVIDERS.has(provider);
15
+ }
16
+ /**
17
+ * Refuse a write in a request an agent supplied for a read-only provider.
18
+ * Every agent-facing action that forwards a caller-built request (request,
19
+ * corpus jobs) calls this before executing or storing it.
20
+ */
21
+ export function assertAgentProviderRequestAllowed(request) {
22
+ if (!providerApiAgentRequestsReadOnly(String(request.provider)))
23
+ return;
24
+ const method = String(request.method ?? "GET").toUpperCase();
25
+ if (method === "GET" ||
26
+ method === "HEAD" ||
27
+ isReadOnlyProviderPost(request)) {
28
+ return;
29
+ }
30
+ fail(`${String(request.provider)} requests from agent tools are read-only. Make the change with the provider's MCP tools or an app action built for it.`, { errorCode: "provider_writes_not_allowed", statusCode: 403 });
31
+ }
@@ -73,6 +73,7 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
73
73
  offset?: unknown;
74
74
  limit?: unknown;
75
75
  }, {
76
+ deleted?: undefined;
76
77
  jobs: {
77
78
  id: string;
78
79
  name: string;
@@ -83,7 +84,6 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
83
84
  updatedAt: string;
84
85
  }[];
85
86
  total: number;
86
- deleted?: undefined;
87
87
  jobId?: undefined;
88
88
  } | {
89
89
  jobs?: undefined;
@@ -91,9 +91,9 @@ export declare function createProviderCorpusJobAction(options: CreateProviderCor
91
91
  deleted: boolean;
92
92
  jobId: string;
93
93
  } | {
94
+ deleted?: undefined;
94
95
  jobs?: undefined;
95
96
  total?: undefined;
96
- deleted?: undefined;
97
97
  jobId?: undefined;
98
98
  hits: Record<string, unknown>[];
99
99
  offset: number;
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
2
2
  import { z } from "zod";
3
3
  import { defineAction } from "../action.js";
4
4
  import { getCredentialContext } from "../server/request-context.js";
5
+ import { assertAgentProviderRequestAllowed } from "./agent-request-policy.js";
5
6
  import { appendProviderCorpusJobHits, createProviderCorpusJob, deleteProviderCorpusJob, getProviderCorpusJob, getProviderCorpusJobHits, listProviderCorpusJobs, updateProviderCorpusJob, } from "./corpus-jobs-store.js";
6
7
  import { setBodyValueAtPath } from "./request-body.js";
7
8
  import { getStagedDatasetMeta, getStagedDatasetRows, stagedDatasetVersion, } from "./staged-datasets-store.js";
@@ -325,6 +326,7 @@ async function startJob(args, appId, ownerEmail) {
325
326
  throw new Error("start requires mode.");
326
327
  if (!args.request)
327
328
  throw new Error("start requires request.");
329
+ assertAgentProviderRequestAllowed(args.request);
328
330
  const search = args.search ?? {};
329
331
  if (!hasSearchNeedle(search)) {
330
332
  throw new Error("start requires search.query, search.queries, search.terms, or search.regex.");
@@ -666,7 +668,11 @@ async function pauseForBudget(job, checkpoint) {
666
668
  })) ?? job);
667
669
  }
668
670
  async function callProvider(runtime, request) {
669
- const raw = (await runtime.executeRequest(cleanRequest(request)));
671
+ // Also checked here, not only at start: a job stored before a provider
672
+ // became read-only must not replay its write on continue.
673
+ const cleaned = cleanRequest(request);
674
+ assertAgentProviderRequestAllowed(cleaned);
675
+ const raw = (await runtime.executeRequest(cleaned));
670
676
  const response = raw.response;
671
677
  if (!response) {
672
678
  return { body: raw };
@@ -527,7 +527,10 @@ export declare function executeProviderApiRequest(args: ProviderApiRequestArgs,
527
527
  /**
528
528
  * Resolve a provider OAuth token for a trusted server-side integration bridge
529
529
  * such as a CRM adapter or Google Picker. Callers must keep the result out of
530
- * agent, MCP, A2A, logs, persistence, and extension/tool surfaces.
530
+ * agent, MCP, A2A, logs, persistence, and extension/tool surfaces. The one
531
+ * exception is the provider's own remote MCP endpoint, which may receive it as
532
+ * a per-request bearer (`mcp-client/workspace-connection-servers.ts`); it is
533
+ * still never stored on the server config or shown to the agent.
531
534
  */
532
535
  export declare function resolveProviderApiOAuthAccessToken(args: {
533
536
  provider: ProviderApiId;
@@ -1722,7 +1722,10 @@ function providerAccessErrorGuidance(config, status) {
1722
1722
  /**
1723
1723
  * Resolve a provider OAuth token for a trusted server-side integration bridge
1724
1724
  * such as a CRM adapter or Google Picker. Callers must keep the result out of
1725
- * agent, MCP, A2A, logs, persistence, and extension/tool surfaces.
1725
+ * agent, MCP, A2A, logs, persistence, and extension/tool surfaces. The one
1726
+ * exception is the provider's own remote MCP endpoint, which may receive it as
1727
+ * a per-request bearer (`mcp-client/workspace-connection-servers.ts`); it is
1728
+ * still never stored on the server config or shown to the agent.
1726
1729
  */
1727
1730
  export async function resolveProviderApiOAuthAccessToken(args, runtime) {
1728
1731
  await assertProviderAllowedAsync(args.provider, runtime);
@@ -44,17 +44,26 @@ const READ_ONLY_POST_RULES = [
44
44
  // extract jobs make no such promise.
45
45
  body: (body) => isDryRunQueryJob(body.configuration),
46
46
  },
47
+ // HubSpot paths carry `v3`/`v4` or a date-based version such as `2026-09`,
48
+ // which HubSpot accepts on either side of the API name
49
+ // (`/crm/objects/2026-09/...` and `/crm/2026-09/objects/...`).
47
50
  {
48
51
  provider: "hubspot",
49
52
  basePath: "",
50
53
  hostSuffixes: ["hubapi.com"],
51
- path: /^\/crm\/v3\/(?:objects\/[^/]+\/(?:search|batch\/read)|properties\/[^/]+\/batch\/read)$/,
54
+ path: /^\/crm\/(?:(?:v3|\d{4}-\d{2})\/objects|objects\/\d{4}-\d{2})\/[^/]+\/(?:search|batch\/read)$/,
52
55
  },
53
56
  {
54
57
  provider: "hubspot",
55
58
  basePath: "",
56
59
  hostSuffixes: ["hubapi.com"],
57
- path: /^\/crm\/v[34]\/associations\/[^/]+\/[^/]+\/batch\/read$/,
60
+ path: /^\/crm\/(?:(?:v3|\d{4}-\d{2})\/properties|properties\/\d{4}-\d{2})\/[^/]+\/batch\/read$/,
61
+ },
62
+ {
63
+ provider: "hubspot",
64
+ basePath: "",
65
+ hostSuffixes: ["hubapi.com"],
66
+ path: /^\/crm\/(?:(?:v[34]|\d{4}-\d{2})\/associations|associations\/\d{4}-\d{2})\/[^/]+\/[^/]+\/batch\/read$/,
58
67
  },
59
68
  {
60
69
  provider: "gong",
@@ -49,6 +49,7 @@ export declare function handleUploadResource(event: any): Promise<import("./stor
49
49
  error: string;
50
50
  storageSetupRequired?: undefined;
51
51
  } | {
52
+ error?: undefined;
52
53
  id: string;
53
54
  path: string;
54
55
  owner: string;
@@ -63,7 +64,6 @@ export declare function handleUploadResource(event: any): Promise<import("./stor
63
64
  runId: string | null;
64
65
  expiresAt: number | null;
65
66
  metadata: string | null;
66
- error?: undefined;
67
67
  url: string;
68
68
  provider: string;
69
69
  storageSetupRequired?: undefined;
@@ -55,18 +55,18 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
55
55
  errorCode: "secret_managed_elsewhere";
56
56
  managedBy: SecretManagedBy;
57
57
  } | {
58
- errorCode?: undefined;
59
- error: string;
60
58
  ok?: undefined;
59
+ error: string;
60
+ errorCode?: undefined;
61
61
  status?: undefined;
62
62
  } | {
63
+ ok?: undefined;
63
64
  error: string;
64
65
  errorCode: string;
65
- ok?: undefined;
66
66
  status?: undefined;
67
67
  } | {
68
- errorCode?: undefined;
69
68
  error?: undefined;
69
+ errorCode?: undefined;
70
70
  ok: boolean;
71
71
  status: string;
72
72
  } | {
@@ -289,6 +289,8 @@ export const ALWAYS_ON_CORE_ACTIONS = new Set([
289
289
  "call-mcp-tool",
290
290
  // Connection setup UI needs the workspace prerequisite even in minimal apps.
291
291
  "get-managed-mcp-oauth-readiness",
292
+ // Any app calling a provider API needs to see which scopes its connection holds.
293
+ "list-workspace-connection-scopes",
292
294
  "get-agentkit-capabilities",
293
295
  "get-hosted-harness-config",
294
296
  "set-hosted-harness-enabled",
@@ -437,6 +439,10 @@ export function coreActionLoaderEntries() {
437
439
  "list-workspace-user-groups",
438
440
  () => import("../workspace-connections/actions/list-workspace-user-groups.js"),
439
441
  ],
442
+ [
443
+ "list-workspace-connection-scopes",
444
+ () => import("../workspace-connections/actions/list-workspace-connection-scopes.js"),
445
+ ],
440
446
  [
441
447
  "upsert-workspace-user-group",
442
448
  () => import("../workspace-connections/actions/upsert-workspace-user-group.js"),
@@ -1,11 +1,67 @@
1
1
  import { CLAIMED_BACKGROUND_WORKER_FAILED_ERROR_EVENT, finalizeClaimedBackgroundRunFailure, readBackgroundRunClaim, recordRunDiagnostic, RUN_DIAG_STAGE, } from "../../agent/run-store.js";
2
+ function safeDiagnosticToken(value) {
3
+ return typeof value === "string" && /^[A-Za-z0-9_]{1,64}$/.test(value)
4
+ ? value
5
+ : undefined;
6
+ }
7
+ function readFailureMessage(err) {
8
+ try {
9
+ return {
10
+ message: err instanceof Error ? err.message : String(err),
11
+ readable: true,
12
+ };
13
+ }
14
+ catch {
15
+ return { message: "unreadable thrown value", readable: false };
16
+ }
17
+ }
18
+ function routeFailureEvidence(err, message, messageReadable) {
19
+ try {
20
+ const error = err;
21
+ const cause = error?.cause;
22
+ const errorName = safeDiagnosticToken(error?.name) ??
23
+ (err instanceof Error ? "unknown" : "NonError");
24
+ const errorCode = safeDiagnosticToken(error?.code) ?? safeDiagnosticToken(cause?.code);
25
+ const causeName = safeDiagnosticToken(cause?.name);
26
+ const errorMessage = /^Background worker returned HTTP ([45]\d\d)(?: code=([A-Za-z0-9_]{1,64}))?$/.exec(message);
27
+ const evidence = {
28
+ format: "route_failure_v1",
29
+ ...(!messageReadable ? { errorMetadata: "unreadable" } : {}),
30
+ errorName,
31
+ ...(errorCode
32
+ ? { errorCode }
33
+ : errorMessage?.[2]
34
+ ? { errorCode: errorMessage[2] }
35
+ : {}),
36
+ ...(errorMessage?.[1] ? { httpStatus: errorMessage[1] } : {}),
37
+ ...(causeName ? { causeName } : {}),
38
+ };
39
+ const tokens = [
40
+ ...(evidence.errorMetadata
41
+ ? [`errorMetadata=${evidence.errorMetadata}`]
42
+ : []),
43
+ ...(evidence.errorName ? [`errorName=${evidence.errorName}`] : []),
44
+ ...(evidence.httpStatus ? [`httpStatus=${evidence.httpStatus}`] : []),
45
+ ...(evidence.errorCode ? [`errorCode=${evidence.errorCode}`] : []),
46
+ ...(evidence.causeName ? [`causeName=${evidence.causeName}`] : []),
47
+ ];
48
+ return { detail: `${tokens.join(" ")} failure=${message}`, evidence };
49
+ }
50
+ catch {
51
+ return {
52
+ detail: `errorMetadata=unreadable failure=${message}`,
53
+ evidence: { format: "route_failure_v1", errorMetadata: "unreadable" },
54
+ };
55
+ }
56
+ }
2
57
  export async function finalizeClaimedAgentChatProcessRunFailure(runId, err, deps = {}) {
3
58
  const readClaim = deps.readBackgroundRunClaim ?? readBackgroundRunClaim;
4
59
  const record = deps.recordRunDiagnostic ?? recordRunDiagnostic;
5
60
  const finalizeFailure = deps.finalizeClaimedBackgroundRunFailure ??
6
61
  finalizeClaimedBackgroundRunFailure;
7
- const message = err instanceof Error ? err.message : String(err);
8
- await record(runId, RUN_DIAG_STAGE.routeThrew, message).catch(() => { });
62
+ const { message, readable } = readFailureMessage(err);
63
+ const diagnostic = routeFailureEvidence(err, message, readable);
64
+ await record(runId, RUN_DIAG_STAGE.routeThrew, diagnostic.detail, diagnostic.evidence).catch(() => { });
9
65
  const claim = await readClaim(runId);
10
66
  if (claim?.status !== "running" ||
11
67
  claim.dispatchMode !== "background-processing") {
@@ -54,21 +54,21 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
54
54
  error: string;
55
55
  }>;
56
56
  export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
57
- error: string;
58
57
  errorCode?: undefined;
59
58
  code?: undefined;
59
+ error: string;
60
60
  } | {
61
+ code?: undefined;
61
62
  error: string;
62
63
  errorCode: string;
63
- code?: undefined;
64
64
  } | {
65
65
  errorCode?: undefined;
66
66
  error: string;
67
67
  code: ProviderKeyCheckCode;
68
68
  } | {
69
- error?: undefined;
70
69
  errorCode?: undefined;
71
70
  code?: undefined;
71
+ error?: undefined;
72
72
  ok: boolean;
73
73
  key: string;
74
74
  baseUrlKey?: string | undefined;
@@ -85,6 +85,7 @@ export declare function isWorkspaceProviderOAuthFlowValid(input: {
85
85
  }): boolean;
86
86
  export declare function canConnectWorkspaceProviderOAuth(orgId: string | null | undefined, role: string | null | undefined): boolean;
87
87
  export declare function scopedOAuthAccountId(provider: GenericWorkspaceOAuthProvider, owner: string, accountId: string): string;
88
+ export declare function tokenScopes(tokens: Record<string, unknown>, fallback: readonly string[]): string[];
88
89
  export declare function mergeWorkspaceOAuthValues(existing: readonly string[], incoming: readonly string[]): string[];
89
90
  export declare function resolveSalesforceOAuthLoginUrl(environment: string | undefined): string | null;
90
91
  export declare function salesforceOAuthEndpoint(loginUrl: string, phase: "authorize" | "token"): string;
@@ -1202,9 +1202,17 @@ async function fetchBoundedProviderJsonValue(url, init, providerLabel) {
1202
1202
  return { response, body: {} };
1203
1203
  }
1204
1204
  }
1205
- function tokenScopes(tokens, fallback) {
1205
+ export function tokenScopes(tokens, fallback) {
1206
1206
  const scope = text(tokens.scope);
1207
- return scope ? scope.split(/[ ,]+/).filter(Boolean) : [...fallback];
1207
+ if (scope)
1208
+ return scope.split(/[ ,]+/).filter(Boolean);
1209
+ // HubSpot's token endpoint returns the granted scopes as a `scopes` array.
1210
+ if (Array.isArray(tokens.scopes)) {
1211
+ const scopes = tokens.scopes.filter((value) => typeof value === "string" && value !== "");
1212
+ if (scopes.length)
1213
+ return scopes;
1214
+ }
1215
+ return [...fallback];
1208
1216
  }
1209
1217
  export function mergeWorkspaceOAuthValues(existing, incoming) {
1210
1218
  return [...new Set([...existing, ...incoming])];
@@ -93,3 +93,16 @@ cross-source joins, multi-page provider sweeps, and heavy analysis scripts;
93
93
  keep quick scripts in the default foreground mode. Agents should continue
94
94
  other work between polls, and on `failed`/`timed_out` split the computation or
95
95
  persist intermediate progress with `workspaceWrite` and re-run.
96
+
97
+ ## HubSpot API versions
98
+
99
+ Write new HubSpot code against a date-based API version (2026-09 or later),
100
+ for example `POST /crm/objects/2026-09/contacts`, not `/crm/v3/...`. Only the
101
+ date-based versions apply the portal's required-property rules to API writes:
102
+ a create that leaves out a property the portal's create form requires, or a
103
+ stage change missing a conditionally required property, is rejected with a
104
+ 400, while the same request on `/crm/v3/` succeeds. HubSpot also stops
105
+ supporting v1-v3 after September 2027. Moving existing write code to a dated
106
+ version changes its behavior, because writes that pass today can start failing
107
+ validation, so migrate one app at a time and surface those 400s to the user.
108
+ Reads behave the same on either version.
@@ -93,3 +93,16 @@ cross-source joins, multi-page provider sweeps, and heavy analysis scripts;
93
93
  keep quick scripts in the default foreground mode. Agents should continue
94
94
  other work between polls, and on `failed`/`timed_out` split the computation or
95
95
  persist intermediate progress with `workspaceWrite` and re-run.
96
+
97
+ ## HubSpot API versions
98
+
99
+ Write new HubSpot code against a date-based API version (2026-09 or later),
100
+ for example `POST /crm/objects/2026-09/contacts`, not `/crm/v3/...`. Only the
101
+ date-based versions apply the portal's required-property rules to API writes:
102
+ a create that leaves out a property the portal's create form requires, or a
103
+ stage change missing a conditionally required property, is rejected with a
104
+ 400, while the same request on `/crm/v3/` succeeds. HubSpot also stops
105
+ supporting v1-v3 after September 2027. Moving existing write code to a dated
106
+ version changes its behavior, because writes that pass today can start failing
107
+ validation, so migrate one app at a time and surface those 400s to the user.
108
+ Reads behave the same on either version.
@@ -21,6 +21,10 @@ not judged by ledgers, receipts, or checker output, so don't produce them.
21
21
 
22
22
  ## The contract
23
23
 
24
+ The detailed Slack reaction and reply rules are in
25
+ [slack-reaction-replies.md](references/slack-reaction-replies.md); read it before
26
+ marking, replying to, or closing any Slack report.
27
+
24
28
  Slack reactions on a report's root message mean exactly this:
25
29
 
26
30
  | Mark | Meaning | When to add |
@@ -43,7 +47,8 @@ might need to take back. Every 👀 you add ends the run with a reply, plus
43
47
  either ✅ or 🎫, or a fix PR that is still open and lists the report (its
44
48
  merge adds the ✅). A 👀 with no follow-up is the most common complaint about this workflow.
45
49
 
46
- Every claimed report gets **one** reply in its thread per outcome. People
50
+ Every claimed report gets **one** reply in its thread per outcome. The one exception is
51
+ the merged `✅` close-out after an in-review reply, which gets no second reply. People
47
52
  have pushed back on replies that were long, technical, condescending, vague
48
53
  about who does what next, or that handed them work. Each reply must be:
49
54
 
@@ -261,9 +266,10 @@ apart from Steve's own 👀.
261
266
  - **Prove it.** The check has to fail with the fix's source changes put
262
267
  back to `main` and pass with them. A check that passes without your fix
263
268
  doesn't test the bug: write one that does. Tests that read source text
264
- never count. For a purely visual fix, a before and after screenshot of the
265
- signed-in app stands in. Put the proof under the report in the PR's
266
- `Reports` section.
269
+ never count. For a purely visual fix, before and after screenshots are
270
+ evidence for a person to inspect, not automated proof. Before merging, a
271
+ human must open both images and confirm they show the reported change. Put
272
+ the evidence under the report in the PR's `Reports` section.
267
273
  - **Two misses, then stop guessing.** If two attempts don't turn the check
268
274
  green, stop editing and capture the real failure first (stack, logs, the
269
275
  reporter's actual data). If that doesn't settle it within about 30