@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.
- package/AGENTS.md +6 -6
- package/CHANGELOG.md +229 -0
- package/README.md +23 -24
- package/bin/connecta.mjs +1 -5
- package/dist/activity.d.ts +5 -4
- package/dist/activity.js +1 -10
- package/dist/artifacts/connector.js +1 -1
- package/dist/artifacts/json.js +10 -0
- package/dist/artifacts/operations.js +1 -1
- package/dist/artifacts/validate.js +1 -2
- package/dist/auth/downstream-oauth.js +147 -28
- package/dist/auth/static-oauth.js +504 -0
- package/dist/catalog-service.js +1 -1
- package/dist/connectors/api-connector.d.ts +168 -0
- package/dist/connectors/api-connector.js +127 -0
- package/dist/connectors/api.d.ts +8 -71
- package/dist/connectors/api.js +10 -99
- package/dist/connectors/guarded-fetch.js +37 -3
- package/dist/connectors/remote-mcp.js +7 -32
- package/dist/execute.js +92 -368
- package/dist/executors/quickjs.js +1 -1
- package/dist/exempt-writes.js +162 -0
- package/dist/index.d.ts +13 -32
- package/dist/index.js +14 -47
- package/dist/invocation.js +6 -19
- package/dist/meta-tools.js +1 -2
- package/dist/oauth-partition.js +17 -0
- package/dist/operator-ui/generated.js +1 -1
- package/dist/operator-ui/model.d.ts +3 -3
- package/dist/operator-ui/view.js +6 -4
- package/dist/providers/basecamp.d.ts +65 -0
- package/dist/providers/basecamp.js +445 -0
- package/dist/providers/breeze.d.ts +34 -0
- package/dist/providers/breeze.js +1402 -0
- package/dist/providers/ccb.d.ts +103 -0
- package/dist/providers/ccb.js +1570 -0
- package/dist/providers/cloudflare.js +1 -1
- package/dist/providers/linear.js +1 -1
- package/dist/providers/mixpanel.js +1 -1
- package/dist/providers/notion.js +1 -1
- package/dist/providers/overflow.d.ts +60 -0
- package/dist/providers/overflow.js +1572 -0
- package/dist/providers/planning-center.d.ts +59 -0
- package/dist/providers/planning-center.js +2039 -0
- package/dist/providers/revenuecat.js +1 -1
- package/dist/providers/stripe.js +1 -1
- package/dist/providers/tithely.d.ts +49 -0
- package/dist/providers/tithely.js +1034 -0
- package/dist/providers/vercel.js +1 -1
- package/dist/routes/mcp.js +5 -21
- package/dist/routes/oauth.js +29 -3
- package/dist/routes/shared.d.ts +1 -6
- package/dist/server.js +0 -4
- package/dist/skills.js +8 -23
- package/dist/tool-safety.d.ts +1 -1
- package/dist/tool-safety.js +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +15 -17
- package/documentation/auth.md +120 -6
- package/documentation/code-mode.md +91 -194
- package/documentation/meta-tools.md +38 -41
- package/ethos.md +8 -9
- package/examples/worker/README.md +10 -14
- package/examples/worker/src/d1-activity-row.ts +4 -1
- package/examples/worker/src/index.ts +1 -9
- package/package.json +26 -2
- package/templates/node/AGENTS.md +4 -3
- package/templates/node/README.md +3 -4
- package/templates/node/package.json +1 -1
- package/dist/resumable.js +0 -1323
- 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 (
|
|
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
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
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
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
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
|
|
126
|
-
* activity
|
|
127
|
-
*
|
|
128
|
-
* `
|
|
129
|
-
*
|
|
130
|
-
*
|
|
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 {
|
|
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
|
|
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:
|
|
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
|
|
616
|
-
return
|
|
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,
|
package/dist/invocation.js
CHANGED
|
@@ -152,8 +152,8 @@ export class InvocationService {
|
|
|
152
152
|
let attempts = 0;
|
|
153
153
|
let dispatchedToConnector = false;
|
|
154
154
|
let answered = false;
|
|
155
|
-
//
|
|
156
|
-
let
|
|
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
|
|
261
|
-
//
|
|
262
|
-
|
|
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
|
-
|
|
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;
|
package/dist/meta-tools.js
CHANGED
|
@@ -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
|
|
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
|
package/dist/oauth-partition.js
CHANGED
|
@@ -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
|
}
|