@zackbart/connecta 0.27.0 → 0.28.1

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 (73) hide show
  1. package/AGENTS.md +6 -6
  2. package/CHANGELOG.md +229 -0
  3. package/README.md +23 -24
  4. package/bin/connecta.mjs +1 -5
  5. package/dist/activity.d.ts +5 -4
  6. package/dist/activity.js +1 -10
  7. package/dist/artifacts/connector.js +1 -1
  8. package/dist/artifacts/json.js +10 -0
  9. package/dist/artifacts/operations.js +1 -1
  10. package/dist/artifacts/validate.js +1 -2
  11. package/dist/auth/downstream-oauth.js +147 -28
  12. package/dist/auth/static-oauth.js +504 -0
  13. package/dist/catalog-service.js +1 -1
  14. package/dist/connectors/api-connector.d.ts +168 -0
  15. package/dist/connectors/api-connector.js +127 -0
  16. package/dist/connectors/api.d.ts +8 -71
  17. package/dist/connectors/api.js +10 -99
  18. package/dist/connectors/guarded-fetch.js +37 -3
  19. package/dist/connectors/remote-mcp.js +7 -32
  20. package/dist/execute.js +92 -368
  21. package/dist/executors/quickjs.js +1 -1
  22. package/dist/exempt-writes.js +162 -0
  23. package/dist/index.d.ts +13 -32
  24. package/dist/index.js +14 -47
  25. package/dist/invocation.js +6 -19
  26. package/dist/meta-tools.js +1 -2
  27. package/dist/oauth-partition.js +17 -0
  28. package/dist/operator-ui/generated.js +1 -1
  29. package/dist/operator-ui/model.d.ts +3 -3
  30. package/dist/operator-ui/view.js +6 -4
  31. package/dist/providers/basecamp.d.ts +65 -0
  32. package/dist/providers/basecamp.js +445 -0
  33. package/dist/providers/breeze.d.ts +34 -0
  34. package/dist/providers/breeze.js +1402 -0
  35. package/dist/providers/ccb.d.ts +103 -0
  36. package/dist/providers/ccb.js +1570 -0
  37. package/dist/providers/cloudflare.js +1 -1
  38. package/dist/providers/linear.js +1 -1
  39. package/dist/providers/mixpanel.js +1 -1
  40. package/dist/providers/notion.js +1 -1
  41. package/dist/providers/overflow.d.ts +60 -0
  42. package/dist/providers/overflow.js +1572 -0
  43. package/dist/providers/planning-center.d.ts +59 -0
  44. package/dist/providers/planning-center.js +2039 -0
  45. package/dist/providers/revenuecat.js +1 -1
  46. package/dist/providers/stripe.js +1 -1
  47. package/dist/providers/tithely.d.ts +49 -0
  48. package/dist/providers/tithely.js +1034 -0
  49. package/dist/providers/vercel.js +1 -1
  50. package/dist/routes/mcp.js +5 -21
  51. package/dist/routes/oauth.js +29 -3
  52. package/dist/routes/shared.d.ts +1 -6
  53. package/dist/server.js +0 -4
  54. package/dist/skills.js +8 -23
  55. package/dist/tool-safety.d.ts +1 -1
  56. package/dist/tool-safety.js +1 -1
  57. package/dist/types.d.ts +2 -2
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +1 -1
  60. package/documentation/architecture.md +15 -17
  61. package/documentation/auth.md +120 -6
  62. package/documentation/code-mode.md +91 -194
  63. package/documentation/meta-tools.md +38 -41
  64. package/ethos.md +8 -9
  65. package/examples/worker/README.md +10 -14
  66. package/examples/worker/src/d1-activity-row.ts +4 -1
  67. package/examples/worker/src/index.ts +1 -9
  68. package/package.json +26 -2
  69. package/templates/node/AGENTS.md +4 -3
  70. package/templates/node/README.md +3 -4
  71. package/templates/node/package.json +1 -1
  72. package/dist/resumable.js +0 -1323
  73. package/dist/run-journal.js +0 -347
@@ -357,7 +357,7 @@ class QuickJsChildPool {
357
357
  // Do not copy deployment credentials or Node startup configuration into
358
358
  // the process that contains the guest runtime. `TZ` alone is set: the
359
359
  // guest's local-time methods read the child's zone, and pinning it to
360
- // UTC makes them the same on every host, like a Dynamic Worker's (P6).
360
+ // UTC makes them the same on every host, like a Dynamic Worker's (X5).
361
361
  env: { TZ: "UTC" },
362
362
  execArgv: sourceMode ? ["--import", "tsx"] : [],
363
363
  stdio: ["ignore", "ignore", "pipe", "ipc"],
@@ -0,0 +1,162 @@
1
+ // The writes a program may send: config-exempt ones (#566), and nothing else.
2
+ //
3
+ // Every other write a program attempts is refused
4
+ // `destructive_tool_requires_approval` before it is validated or sent (E4),
5
+ // so the host's prompt on `call_destructive_tool` stays the only approval.
6
+ // An exempt write is still a write, though, and it gets what a write needs:
7
+ // its own budget, a dispatch the run waits for rather than aborts, and a
8
+ // result that never hides a write whose outcome is unknown.
9
+ //
10
+ // Web-API only, like everything reachable from the root entry.
11
+ import { Effect } from "effect";
12
+ import { jsonResult } from "./meta-tools.js";
13
+ /** Writes one program may send (`execute.maxWrites`), unless configured. */
14
+ export const DEFAULT_MAX_WRITES = 10;
15
+ /**
16
+ * Whether a write landed, as far as anyone can know.
17
+ *
18
+ * `unknown` is the outcome a result must never paper over: the call left
19
+ * connecta and no answer came back, so sending it again could do it twice and
20
+ * not sending it could leave it undone. That is a dispatched call that timed
21
+ * out, was cancelled, found the service unavailable, or failed any way that
22
+ * is not a verdict. A refusal code (`REFUSALS`) or a downstream tool's own
23
+ * `isError` is an answer, so it is `failed`, except that an `isError` whose text
24
+ * classifies as a timeout is a gateway reporting that it gave up, and stays
25
+ * unknown. A call that was never dispatched is `failed`: nothing was sent.
26
+ * `result_processing_failed` means the downstream call completed.
27
+ */
28
+ export function classifyWriteOutcome(outcome) {
29
+ if (outcome.ok)
30
+ return "ok";
31
+ if (!outcome.dispatched)
32
+ return "failed";
33
+ const code = outcome.error?.code;
34
+ if (code === "result_processing_failed")
35
+ return "ok";
36
+ if (code === "timeout" || code === "cancelled" || code === "unavailable") {
37
+ return "unknown";
38
+ }
39
+ if (code !== undefined && REFUSALS.has(code))
40
+ return "failed";
41
+ return outcome.answered === true ? "failed" : "unknown";
42
+ }
43
+ /**
44
+ * Codes that say the other side refused the call rather than acted on it:
45
+ * the credential, the arguments, the resource, the rate, or a base version
46
+ * someone else already moved past (`conflict`). Anything else a
47
+ * connector reports after dispatch — `connector_call_failed` from a response
48
+ * too large to read, a redirect it would not follow, a body it could not
49
+ * parse, a 5xx — may come after the write landed, so only a downstream
50
+ * tool's own `isError` answer makes it a known failure.
51
+ */
52
+ const REFUSALS = new Set([
53
+ "auth_required",
54
+ "invalid_args",
55
+ "not_found",
56
+ "rate_limited",
57
+ "input_required_unsupported",
58
+ "conflict",
59
+ ]);
60
+ /** What a dispatched write's outcome says about whether it landed. */
61
+ export function writeStateOf(outcome) {
62
+ return classifyWriteOutcome(outcome.ok
63
+ ? { ok: true, dispatched: outcome.dispatched }
64
+ : {
65
+ ok: false,
66
+ dispatched: outcome.dispatched,
67
+ error: outcome.error,
68
+ ...(outcome.answered !== undefined ? { answered: outcome.answered } : {}),
69
+ });
70
+ }
71
+ /**
72
+ * The exempt writes one program sends. The play closes this when the
73
+ * program settles — a write that reaches the gate after is not sent — and
74
+ * drains what is on the wire before the run's scope aborts it, so the write
75
+ * finishes and its activity says how it did. The result never hides a write
76
+ * whose outcome is unknown, and a program that fails after writing reports
77
+ * the counts.
78
+ */
79
+ export class ExemptWrites {
80
+ closed = false;
81
+ inFlight = new Set();
82
+ states = [];
83
+ /** Whether the program has settled: a write gated now is not sent. */
84
+ get isClosed() {
85
+ return this.closed;
86
+ }
87
+ close() {
88
+ this.closed = true;
89
+ }
90
+ /** A write is being dispatched; the function returned records how it ended. */
91
+ begin() {
92
+ let done = () => { };
93
+ const settled = new Promise((resolve) => {
94
+ done = resolve;
95
+ });
96
+ this.inFlight.add(settled);
97
+ let recorded = false;
98
+ return (state) => {
99
+ if (recorded)
100
+ return;
101
+ recorded = true;
102
+ this.states.push(state);
103
+ this.inFlight.delete(settled);
104
+ done();
105
+ };
106
+ }
107
+ /** Wait for dispatched writes, each bounded by its host-call deadline. */
108
+ drain() {
109
+ return Effect.promise(async () => {
110
+ while (this.inFlight.size > 0)
111
+ await Promise.all(this.inFlight);
112
+ });
113
+ }
114
+ finish(result) {
115
+ if (this.states.length === 0)
116
+ return result;
117
+ const writes = {
118
+ succeeded: this.states.filter((state) => state === "ok").length,
119
+ failed: this.states.filter((state) => state === "failed").length,
120
+ unknown: this.states.filter((state) => state === "unknown").length,
121
+ };
122
+ if (writes.unknown > 0) {
123
+ return errorEnvelope({
124
+ code: "write_outcome_unknown",
125
+ message: "A write this program sent has no known outcome, so that is the result rather than what the program returned. It will not be sent again. Check its target before doing anything that depends on it.",
126
+ retryable: false,
127
+ writes,
128
+ });
129
+ }
130
+ return result.isError ? withWrites(result, writes) : result;
131
+ }
132
+ }
133
+ /**
134
+ * An error result from a program that sent writes, carrying their counts: a
135
+ * program that fails after writing is not a program that is safe to repeat.
136
+ */
137
+ function withWrites(result, writes) {
138
+ const structured = result.structuredContent;
139
+ const error = structured?.error;
140
+ const annotated = errorEnvelope({
141
+ ...(error !== null && typeof error === "object"
142
+ ? error
143
+ : {
144
+ code: "executor_failed",
145
+ message: result.content[0]?.text ?? "",
146
+ retryable: false,
147
+ }),
148
+ writes,
149
+ note: "This run attempted writes, counted in writes; those that succeeded are not undone. Check them before running the task again.",
150
+ });
151
+ if (structured && error !== null && typeof error === "object") {
152
+ const { error: _replaced, ...rest } = structured;
153
+ annotated.structuredContent = { ...rest, error: annotated.structuredContent?.error };
154
+ annotated.content = [{ type: "text", text: JSON.stringify(annotated.structuredContent) }];
155
+ }
156
+ return annotated;
157
+ }
158
+ function errorEnvelope(error) {
159
+ const result = jsonResult({ error });
160
+ result.isError = true;
161
+ return result;
162
+ }
package/dist/index.d.ts CHANGED
@@ -92,43 +92,24 @@ export interface ConnectaExecuteConfig {
92
92
  */
93
93
  watchdogMs?: number;
94
94
  /**
95
- * Let programs write. A call to a tool that is not explicitly read-only
96
- * pauses the run before anything is sent and returns the exact write with
97
- * a token; `resume_execution` repeating it is the approval, and the
98
- * program replays from a journal to send it and continue. Needs `storage`
99
- * with `compareAndSet`, because two resumes of one pause must send its
100
- * writes at most once. Omitted, it is on exactly when the storage has one
101
- * (a single startup warning says when it is not); `true` over storage
102
- * without one refuses to construct; `false` keeps E4's refusal.
103
- * `resume_execution` is listed either way.
104
- */
105
- resumableWrites?: boolean;
106
- /**
107
- * Consequential calls one run may send, on top of the host-call budget
108
- * every call already spends. Default 10. A replayed write spends it again,
109
- * so it bounds the run, not each play. Invalid values fall back to the
110
- * default.
95
+ * Config-exempt writes one program may send, on top of the host-call
96
+ * budget every call already spends. Default 10. Invalid values fall back
97
+ * to the default.
111
98
  */
112
99
  maxWrites?: number;
113
100
  /**
114
- * How long a paused run can be resumed, in seconds from its first pause.
115
- * Default 1_800. A later pause in the same run keeps the first deadline, so
116
- * a run never replays reads older than this. Invalid values fall back to
117
- * the default.
118
- */
119
- pausedRunTtlSeconds?: number;
120
- /**
121
- * Which writes a program may make without pausing for approval. Keys are
101
+ * Which writes a program may make without asking. A program runs only
102
+ * explicitly read-only tools and these; every other write is refused and
103
+ * goes through `call_destructive_tool`, where the host asks. Keys are
122
104
  * connector ids (all of that connector's tools) or `connector.tool`
123
105
  * addresses (that tool); values are `"never"` (never ask) or `"ask"`. The
124
106
  * most specific key wins, and `"ask"` switches off a connector's own
125
- * default. An exempt write still spends the write budget, is recorded in
126
- * activity, and is journaled like any other; it is never read-only
127
- * anywhere else — discovery still lists it as approval-required and
128
- * `call_tool` still refuses it. Works with resumable writes off too. An
129
- * unknown connector id, or an `api()` address its tools do not include,
130
- * refuses to construct; a remote connector's tool names load later, so an
131
- * address naming one it never serves simply never matches.
107
+ * default. An exempt write still spends the write budget and is recorded
108
+ * in activity; it is never read-only anywhere else — discovery still lists
109
+ * it as approval-required and `call_tool` still refuses it. An unknown
110
+ * connector id, or an `api()` address its tools do not include, refuses to
111
+ * construct; a remote connector's tool names load later, so an address
112
+ * naming one it never serves simply never matches.
132
113
  */
133
114
  approval?: Readonly<Record<string, "never" | "ask">>;
134
115
  }
@@ -290,6 +271,6 @@ export { memoryStorage } from "./storage/memory.js";
290
271
  export { CONNECTA_VERSION } from "./version.js";
291
272
  export type { Registry } from "./registry.js";
292
273
  export type { RemoteMcpOptions, RemoteMcpAuth, RemoteMcpRedirectPolicy, } from "./connectors/remote-mcp.js";
293
- export type { ApiOptions, ApiTool } from "./connectors/api.js";
274
+ export type { ApiHandlerContext, ApiOAuthAccess, ApiOAuthClientAuthentication, ApiOAuthConfig, ApiOptions, ApiTool, } from "./connectors/api.js";
294
275
  export type { CatalogDriftCounts, CatalogDriftReport, ConnectaBranding, ConnectaTheme, Connector, ConnectorCallAdmissionInput, ConnectorCallAdmissionPolicy, ConnectorCallAdmissionRule, ConnectorRollingWindowBudget, ConnectorCredentialAccess, ConnectorCredentialConfig, ConnectorCredentialFieldConfig, ConnectorCredentialValues, ConnectorContext, ConnectorUsageGuide, ConnectorStatus, CredentialTestResult, AdmittingExecutor, AdmissionSnapshot, ExecuteResult, Executor, ExecutorLease, ExecutorProvider, InboundAuth, InboundAuthRuntimeContext, UiAuthConfig, AuthResult, AuthenticatedIdentity, IdentityReference, JsonSchema, KVStorage, Logger, ToolDef, ToolAnnotations, } from "./types.js";
295
276
  export type { ActivityActor, ActivityCallSource, ActivityOutcome, ActivityPage, ActivityReadActor, ActivityReadEvent, ActivityReader, ActivityReadGate, ActivityReadPage, ActivitySink, ActivityStore, AgentFriction, CatalogDriftActivityEvent, ToolCallActivityEvent, } from "./activity.js";
package/dist/index.js CHANGED
@@ -2,12 +2,12 @@ import { credentialTestRule, describeCredentialTestMismatch, } from "./credentia
2
2
  import { Registry } from "./registry.js";
3
3
  import { intersectAccess, parseConnectorAccess, POOL_NAME_RE } from "./connector-access.js";
4
4
  import { createFetchHandler } from "./server.js";
5
- import { createProgramRunner } from "./execute.js";
5
+ import { createExecuteTool, runClaimMs } from "./execute.js";
6
6
  import { droppedBrandingUrls, droppedThemeTokens, droppedUiAuthUrls, } from "./branding.js";
7
7
  import { memoryStorage } from "./storage/memory.js";
8
8
  import { CONNECTA_VERSION } from "./version.js";
9
9
  import { AdmissionController, executorName, isAdmittingExecutor, withExecutorAdmission, } from "./executor-admission.js";
10
- import { DEFAULT_MAX_WRITES, DEFAULT_PAUSED_RUN_TTL_SECONDS, } from "./resumable.js";
10
+ import { DEFAULT_MAX_WRITES } from "./exempt-writes.js";
11
11
  import { disposeEdgeRuntime } from "./runtime/run.js";
12
12
  import { NO_EXEMPTIONS } from "./tool-safety.js";
13
13
  import { createCoreRuntime, resolveLogger } from "./runtime/services.js";
@@ -92,9 +92,7 @@ const CONFIG_SCHEMA = {
92
92
  maxHostCalls: null,
93
93
  hostCallTimeoutMs: null,
94
94
  watchdogMs: null,
95
- resumableWrites: null,
96
95
  maxWrites: null,
97
- pausedRunTtlSeconds: null,
98
96
  approval: null,
99
97
  },
100
98
  admission: {
@@ -140,13 +138,21 @@ function unknownOptionPaths(value, path, schema) {
140
138
  }
141
139
  return unknown;
142
140
  }
141
+ /** Pause-only options #672 removed with `resume_execution`. */
142
+ const RETIRED_PAUSE_OPTIONS = [
143
+ "ConnectaConfig.execute.resumableWrites",
144
+ "ConnectaConfig.execute.pausedRunTtlSeconds",
145
+ ];
143
146
  function rejectUnknownOptions(paths) {
144
147
  if (paths.length === 0)
145
148
  return;
146
149
  throw new Error(`Unknown Connecta configuration option${paths.length === 1 ? "" : "s"}:\n` +
147
150
  paths.map((path) => `- ${path}`).join("\n") +
148
151
  (paths.includes("ConnectaConfig.credentials") ? "\nUse vault: encryptedCredentialVault(storage, key) from @zackbart/connecta/credentials." : "") +
149
- (paths.includes("ConnectaConfig.branding") ? "\nMove branding into ui: operatorUi({ branding }) from @zackbart/connecta/ui." : ""));
152
+ (paths.includes("ConnectaConfig.branding") ? "\nMove branding into ui: operatorUi({ branding }) from @zackbart/connecta/ui." : "") +
153
+ (paths.some((path) => RETIRED_PAUSE_OPTIONS.includes(path))
154
+ ? "\nPrograms no longer pause at writes, so there is nothing to configure: execute_code runs read-only and config-exempt tools, and every other write goes through call_destructive_tool (issue #672). Delete the option."
155
+ : ""));
150
156
  }
151
157
  /** Reject JavaScript typos and removed options before construction does work. */
152
158
  function assertKnownConfig(config) {
@@ -333,41 +339,6 @@ function positiveWhole(value, fallback) {
333
339
  ? Math.trunc(value)
334
340
  : fallback;
335
341
  }
336
- /**
337
- * Resumable writes' deployment settings, or undefined when they are off.
338
- * Omitted, they are on exactly when the storage can claim atomically, and a
339
- * deployment left without them hears so once. Asked for over storage that
340
- * cannot claim, they throw: at-most-once delivery of an approved write rests
341
- * on exactly one resume winning the claim, and a read-then-write stand-in
342
- * would let two win.
343
- */
344
- function resolveResumableWrites(execute, storage, logger) {
345
- const enabled = execute?.resumableWrites;
346
- if (enabled !== undefined && typeof enabled !== "boolean") {
347
- throw new Error("ConnectaConfig.execute.resumableWrites must be a boolean");
348
- }
349
- if (enabled === false)
350
- return undefined;
351
- if (enabled === undefined && typeof storage.compareAndSet !== "function") {
352
- logger.warn("[connecta] resumable writes are off: the configured storage has no " +
353
- "compareAndSet, so programs refuse writes (E4) and resume_execution " +
354
- "answers resumable_writes_unavailable. Use storage with an atomic " +
355
- "compareAndSet (the Worker example's D1 store, fileStorage, " +
356
- "memoryStorage) to let programs pause for approval, or set " +
357
- "execute.resumableWrites: false to keep this and silence the warning.");
358
- return undefined;
359
- }
360
- if (typeof storage.compareAndSet !== "function") {
361
- throw new Error("ConnectaConfig.execute.resumableWrites needs storage with compareAndSet: " +
362
- "two resumes of one paused run must send its writes at most once, which " +
363
- "takes an atomic claim. memoryStorage() and fileStorage() provide it, as " +
364
- "does the Worker example's D1 store; Cloudflare KV cannot.");
365
- }
366
- return {
367
- maxWrites: positiveWhole(execute?.maxWrites, DEFAULT_MAX_WRITES),
368
- ttlSeconds: positiveWhole(execute?.pausedRunTtlSeconds, DEFAULT_PAUSED_RUN_TTL_SECONDS),
369
- };
370
- }
371
342
  /**
372
343
  * One-time construction warnings for deployment shapes that run fine but are
373
344
  * usually unintended. Warning-only — never throws and never changes behavior;
@@ -539,9 +510,7 @@ export function createConnecta(config) {
539
510
  });
540
511
  const inboundAuth = configuredAuth;
541
512
  const pools = resolvePools(config.pools, registry);
542
- const resumable = resolveResumableWrites(config.execute, storage, logger);
543
513
  const approval = resolveApprovalPolicy(config.execute?.approval, registry);
544
- // Exempt writes need a budget whether or not programs can pause.
545
514
  const maxWrites = positiveWhole(config.execute?.maxWrites, DEFAULT_MAX_WRITES);
546
515
  warnInsecureConfig(config, inboundAuth, logger);
547
516
  const requestAdmission = admissionController(config.admission?.requests, REQUEST_ADMISSION_DEFAULTS, config.admission?.requests?.maxDurationMs ?? DEFAULT_REQUEST_MAX_DURATION_MS);
@@ -580,10 +549,9 @@ export function createConnecta(config) {
580
549
  tools: new Map(),
581
550
  },
582
551
  };
583
- const refreshRunner = createProgramRunner(registry.scoped({ connectorIds: [] }), config.publicUrl, executor, logger, undefined, refreshConfig);
584
552
  config.artifacts.bindRefresh({
585
553
  ...(config.ui?.branding ? { branding: config.ui.branding } : {}),
586
- claimMs: refreshRunner.claimMs,
554
+ claimMs: runClaimMs(refreshConfig),
587
555
  execute: async (program, owner, signal) => {
588
556
  if (!owner)
589
557
  throw new Error("Refresh owner is missing; reconfigure this program.");
@@ -612,8 +580,8 @@ export function createConnecta(config) {
612
580
  const view = registry.scoped({ connectorIds: access.connectorIds,
613
581
  ...(access.toolAccess ? { toolAccess: access.toolAccess } : {}),
614
582
  ...(access.guardedToolAccess ? { guardedToolAccess: access.guardedToolAccess } : {}) });
615
- const runner = createProgramRunner(view, config.publicUrl, executor, logger, undefined, refreshConfig);
616
- return runner.execute({ code: program }, { signal });
583
+ const execute = createExecuteTool(view, config.publicUrl, executor, logger, undefined, refreshConfig);
584
+ return execute({ code: program }, { signal });
617
585
  },
618
586
  });
619
587
  }
@@ -660,7 +628,6 @@ export function createConnecta(config) {
660
628
  maxHostCalls: config.execute?.maxHostCalls,
661
629
  hostCallTimeoutMs: config.execute?.hostCallTimeoutMs,
662
630
  watchdogMs: config.execute?.watchdogMs,
663
- resumable,
664
631
  approval,
665
632
  maxWrites,
666
633
  credentialVault,
@@ -152,8 +152,8 @@ export class InvocationService {
152
152
  let attempts = 0;
153
153
  let dispatchedToConnector = false;
154
154
  let answered = false;
155
- // How a write gate's refusal is recorded; see WriteGateDecision.
156
- let gateActivity;
155
+ // A write gate's refusal that is no attempt; see WriteGateDecision.
156
+ let unrecorded = false;
157
157
  let resolved;
158
158
  let activityTarget;
159
159
  // The address as written, used for activity when resolution never reached
@@ -257,15 +257,10 @@ export class InvocationService {
257
257
  ...(dispatchedToConnector ? { answered } : {}),
258
258
  error: details,
259
259
  });
260
- // A pause is not a failure: it leaves one payload-free `paused` event
261
- // naming the call that waits, and nothing in the operator's log. A call
262
- // refused only because its run had already stopped leaves neither.
263
- if (gateActivity === "none")
260
+ // A write refused only because its program had already returned
261
+ // leaves no event and nothing in the operator's log.
262
+ if (unrecorded)
264
263
  return outcome();
265
- if (gateActivity === "paused") {
266
- record("paused");
267
- return outcome();
268
- }
269
264
  // Activity rows stay payload-free by construction; the operator's log is
270
265
  // where the downstream reason goes, bounded and without arguments.
271
266
  if (target && details.code !== "destructive_tool_requires_approval") {
@@ -347,18 +342,10 @@ export class InvocationService {
347
342
  if (gate) {
348
343
  const decision = yield* gate(target, args ?? {});
349
344
  if (decision.kind === "refuse") {
350
- gateActivity = decision.activity;
345
+ unrecorded = decision.unrecorded === true;
351
346
  return decision.error;
352
347
  }
353
348
  }
354
- try {
355
- context.beforeDispatch?.();
356
- }
357
- catch (error) {
358
- return error instanceof InvocationFailure
359
- ? error.details
360
- : classifyCallError(error);
361
- }
362
349
  if (callSignal?.aborted)
363
350
  return yield* Effect.fail(callSignal.reason);
364
351
  attempts = 1;
@@ -871,8 +871,7 @@ const GET_RESULT_INPUT = advertisedSchema(z.object({
871
871
  }));
872
872
  /**
873
873
  * Register the six explicit meta-tools onto an McpServer instance.
874
- * `registerExecuteTool` adds the seventh, `execute_code`, and
875
- * `registerResumeTool` the eighth, `resume_execution`. Broad discovery and
874
+ * `registerExecuteTool` adds the seventh, `execute_code`. Broad discovery and
876
875
  * multi-call work uses discovery and ordinary JavaScript promises inside a
877
876
  * program, which `execute_code` builds over the same
878
877
  * `CatalogService` and `InvocationService` these handlers use — one shared
@@ -19,6 +19,23 @@ export function retainOAuthPartition(partition) {
19
19
  activeWork.set(partition, remaining);
20
20
  };
21
21
  }
22
+ /**
23
+ * Wrap one connector operation so its owner partition stays pinned from the
24
+ * first asynchronous storage read to the end, not only once a refresh flight
25
+ * exists: an idle personal registry can otherwise be evicted mid-flow. Status
26
+ * and auth operations take no call admission, so nothing else holds it.
27
+ */
28
+ export function retainingOAuthPartition(operation, contextIndex) {
29
+ return async (...args) => {
30
+ const release = retainOAuthPartition(oauthPartitionFor(args[contextIndex]));
31
+ try {
32
+ return await operation(...args);
33
+ }
34
+ finally {
35
+ release();
36
+ }
37
+ };
38
+ }
22
39
  export function oauthPartitionIdle(partition) {
23
40
  return !activeWork.has(partition);
24
41
  }