@zackbart/connecta 0.28.1 → 0.30.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.
- package/CHANGELOG.md +855 -5
- package/README.md +125 -116
- package/bin/connecta.mjs +121 -139
- package/bin/meta-tool-names.d.mts +2 -0
- package/bin/meta-tool-names.mjs +9 -0
- package/bin/version.mjs +3 -0
- package/dist/access-tokens.js +53 -63
- package/dist/activity-request.js +8 -0
- package/dist/activity.d.ts +76 -42
- package/dist/activity.js +110 -42
- package/dist/argument-redaction.js +230 -0
- package/dist/auth/clerk-sdk/client.d.ts +65 -0
- package/dist/auth/clerk-sdk/client.js +5 -0
- package/dist/auth/clerk-sdk/generated.js +1845 -0
- package/dist/auth/clerk-transport.js +19 -0
- package/dist/auth/clerk.d.ts +17 -7
- package/dist/auth/clerk.js +189 -107
- package/dist/auth/cloudflare-access.js +25 -26
- package/dist/auth/downstream-client-metadata.d.ts +29 -0
- package/dist/auth/downstream-client-metadata.js +133 -0
- package/dist/auth/downstream-oauth.js +1584 -1580
- package/dist/auth/downstream-revocation.js +106 -0
- package/dist/auth/oauth-token-response.js +231 -0
- package/dist/auth/oauth-v2-migration.js +116 -0
- package/dist/auth/static-oauth.js +93 -71
- package/dist/auth-elicitation.js +288 -0
- package/dist/authorization-handoff.js +111 -0
- package/dist/branding.d.ts +8 -16
- package/dist/branding.js +26 -59
- package/dist/byte-read-response.js +39 -0
- package/dist/call-admission.js +12 -16
- package/dist/catalog-cache.js +739 -0
- package/dist/catalog-drift.d.ts +50 -15
- package/dist/catalog-drift.js +365 -128
- package/dist/catalog-fingerprint.js +2 -4
- package/dist/catalog-freshness.js +13 -0
- package/dist/catalog-limits.js +2 -2
- package/dist/catalog-service.js +409 -225
- package/dist/catalog.js +43 -84
- package/dist/concurrency.js +3 -4
- package/dist/config-defaults.d.ts +39 -0
- package/dist/config-defaults.js +46 -0
- package/dist/config-schema.d.ts +181 -0
- package/dist/config-schema.js +435 -0
- package/dist/config-value-sources.js +102 -0
- package/dist/config.d.ts +633 -0
- package/dist/config.js +508 -0
- package/dist/connector-access.d.ts +1 -0
- package/dist/connector-access.js +13 -15
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +15 -6
- package/dist/connector-scope.js +1 -1
- package/dist/connectors/api-connector.d.ts +59 -7
- package/dist/connectors/api-connector.js +106 -19
- package/dist/connectors/api.js +4 -1
- package/dist/connectors/guarded-fetch.d.ts +144 -0
- package/dist/connectors/guarded-fetch.js +87 -38
- package/dist/connectors/negotiation-cache.js +46 -0
- package/dist/connectors/option-shapes.js +27 -0
- package/dist/connectors/remote-mcp.d.ts +30 -4
- package/dist/connectors/remote-mcp.js +1774 -320
- package/dist/connectors/resource-uri.js +335 -0
- package/dist/credential-contract.d.ts +10 -0
- package/dist/credential-rules.js +1 -3
- package/dist/credential-url.js +108 -0
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +27 -18
- package/dist/d1.d.ts +130 -0
- package/dist/d1.js +290 -0
- package/dist/describe-config.d.ts +165 -0
- package/dist/describe-config.js +315 -0
- package/dist/described.js +106 -0
- package/dist/downstream-input-context.js +128 -0
- package/dist/downstream-input.js +325 -0
- package/dist/errors.d.ts +132 -31
- package/dist/errors.js +175 -87
- package/dist/execute.js +798 -318
- package/dist/executor-admission.d.ts +1 -0
- package/dist/executor-admission.js +11 -15
- package/dist/executor-contract.d.ts +20 -0
- package/dist/executor-contract.js +48 -0
- package/dist/executor-result.js +30 -13
- package/dist/executors/quickjs-child.js +15 -9
- package/dist/executors/quickjs-protocol.js +5 -6
- package/dist/executors/quickjs-runtime.js +152 -59
- package/dist/executors/quickjs.js +49 -39
- package/dist/favicon.js +1 -2
- package/dist/fix-prompt.js +1 -3
- package/dist/guest-runtime.js +238 -0
- package/dist/guest-types.d.ts +218 -0
- package/dist/guest-types.js +1 -0
- package/dist/html-security.js +24 -0
- package/dist/identity.js +1 -3
- package/dist/inbound-credential.js +52 -0
- package/dist/index.d.ts +24 -256
- package/dist/index.js +65 -478
- package/dist/invocation-auth.js +69 -0
- package/dist/invocation.js +262 -79
- package/dist/json-schema.d.ts +1 -1
- package/dist/mcp-client-context.js +35 -0
- package/dist/mcp-result.js +11 -7
- package/dist/meta-output.js +61 -0
- package/dist/meta-tool-names.d.ts +2 -0
- package/dist/meta-tool-names.js +3 -0
- package/dist/meta-tools.js +419 -272
- package/dist/module-contracts.d.ts +8 -23
- package/dist/node.d.ts +0 -2
- package/dist/node.js +2 -2
- package/dist/oauth-callback-outcome.js +1 -3
- package/dist/oauth-handoff.js +194 -0
- package/dist/oauth-sealing.js +20 -0
- package/dist/operator-record.d.ts +137 -0
- package/dist/operator-record.js +420 -0
- package/dist/operator-ui/assets.js +31 -0
- package/dist/operator-ui/contract.d.ts +72 -0
- package/dist/operator-ui/contract.js +1 -0
- package/dist/operator-ui/fix-prompts.d.ts +18 -0
- package/dist/operator-ui/generated.js +5 -4
- package/dist/operator-ui/model.d.ts +12 -87
- package/dist/operator-ui/model.js +2 -9
- package/dist/operator-ui/setup-commands.js +1 -3
- package/dist/operator-ui/view.d.ts +323 -0
- package/dist/operator-ui/view.js +62 -189
- package/dist/page-styles.js +2 -3
- package/dist/program-result.js +5 -0
- package/dist/program-source.js +14 -2
- package/dist/{exempt-writes.js → program-writes.js} +60 -19
- package/dist/provider.d.ts +142 -0
- package/dist/provider.js +187 -0
- package/dist/providers/_shared/google/result-size.js +57 -0
- package/dist/providers/_shared/google/service-account.d.ts +58 -0
- package/dist/providers/_shared/google/service-account.js +500 -0
- package/dist/providers/_shared/google/workspace.d.ts +276 -0
- package/dist/providers/_shared/google/workspace.js +518 -0
- package/dist/providers/_shared/request-token.js +83 -0
- package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
- package/dist/providers/basecamp/index.js +997 -0
- package/dist/providers/basecamp/skill.generated.js +9 -0
- package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
- package/dist/providers/{breeze.js → breeze/index.js} +184 -140
- package/dist/providers/breeze/skill.generated.js +9 -0
- package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
- package/dist/providers/{ccb.js → ccb/index.js} +271 -116
- package/dist/providers/ccb/skill.generated.js +9 -0
- package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
- package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
- package/dist/providers/cloudflare/skill.generated.js +10 -0
- package/dist/providers/docs/index.d.ts +54 -0
- package/dist/providers/docs/index.js +1237 -0
- package/dist/providers/docs/skill.generated.js +9 -0
- package/dist/providers/drive/index.d.ts +53 -0
- package/dist/providers/drive/index.js +1523 -0
- package/dist/providers/drive/skill.generated.js +9 -0
- package/dist/providers/forms/index.d.ts +54 -0
- package/dist/providers/forms/index.js +1217 -0
- package/dist/providers/forms/skill.generated.js +11 -0
- package/dist/providers/github/auth.d.ts +32 -0
- package/dist/providers/github/auth.js +361 -0
- package/dist/providers/github/hosted.js +218 -0
- package/dist/providers/github/index.d.ts +13 -0
- package/dist/providers/github/index.js +140 -0
- package/dist/providers/github/key.js +100 -0
- package/dist/providers/github/rest.js +321 -0
- package/dist/providers/github/scope.d.ts +29 -0
- package/dist/providers/github/scope.js +124 -0
- package/dist/providers/github/skill.generated.js +8 -0
- package/dist/providers/gmail/index.d.ts +51 -0
- package/dist/providers/gmail/index.js +1436 -0
- package/dist/providers/gmail/skill.generated.js +9 -0
- package/dist/providers/infisical/index.d.ts +7 -0
- package/dist/providers/infisical/index.js +713 -0
- package/dist/providers/infisical/skill.generated.js +8 -0
- package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
- package/dist/providers/linear/index.js +352 -0
- package/dist/providers/linear/skill.generated.js +8 -0
- package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
- package/dist/providers/mixpanel/index.js +416 -0
- package/dist/providers/mixpanel/skill.generated.js +9 -0
- package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
- package/dist/providers/{notion.js → notion/index.js} +226 -239
- package/dist/providers/notion/skill.generated.js +10 -0
- package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
- package/dist/providers/{overflow.js → overflow/index.js} +74 -111
- package/dist/providers/overflow/skill.generated.js +8 -0
- package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
- package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
- package/dist/providers/planning-center/skill.generated.js +10 -0
- package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
- package/dist/providers/revenuecat/index.js +557 -0
- package/dist/providers/revenuecat/skill.generated.js +11 -0
- package/dist/providers/sheets/index.d.ts +54 -0
- package/dist/providers/sheets/index.js +1237 -0
- package/dist/providers/sheets/skill.generated.js +10 -0
- package/dist/providers/slides/index.d.ts +53 -0
- package/dist/providers/slides/index.js +2645 -0
- package/dist/providers/slides/skill.generated.js +9 -0
- package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
- package/dist/providers/stripe/index.js +266 -0
- package/dist/providers/stripe/skill.generated.js +11 -0
- package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
- package/dist/providers/{tithely.js → tithely/index.js} +60 -91
- package/dist/providers/tithely/skill.generated.js +8 -0
- package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
- package/dist/providers/vercel/index.js +1038 -0
- package/dist/providers/vercel/mcp-ownership.js +212 -0
- package/dist/providers/vercel/skill.generated.js +11 -0
- package/dist/registry.d.ts +110 -171
- package/dist/registry.js +588 -927
- package/dist/request-state.js +21 -0
- package/dist/resource-read.js +103 -0
- package/dist/result-shapes.js +17 -49
- package/dist/retired-toolkits.js +1 -1
- package/dist/routes/access-tokens.js +3 -6
- package/dist/routes/activity-context.js +21 -0
- package/dist/routes/activity-disclosure.js +40 -0
- package/dist/routes/activity.js +59 -21
- package/dist/routes/actor-labels.js +3 -50
- package/dist/routes/connect.js +221 -0
- package/dist/routes/credentials.js +15 -27
- package/dist/routes/mcp.js +365 -130
- package/dist/routes/oauth-client-metadata.js +15 -0
- package/dist/routes/oauth-management.js +45 -107
- package/dist/routes/oauth.js +110 -57
- package/dist/routes/operator.js +3 -3
- package/dist/routes/shared.d.ts +25 -64
- package/dist/routes/shared.js +160 -80
- package/dist/routes/skills.js +30 -0
- package/dist/routes/ui-config.js +225 -0
- package/dist/routes/ui-data.js +40 -35
- package/dist/routes/ui.js +66 -56
- package/dist/runtime/admission.js +4 -2
- package/dist/runtime/call-admission.js +3 -1
- package/dist/runtime/connector-scope.js +1 -4
- package/dist/runtime/run.js +10 -39
- package/dist/runtime/services.js +12 -117
- package/dist/runtime/shared-read.js +1 -3
- package/dist/runtime/storage.js +8 -1
- package/dist/sent-secrets.d.ts +93 -0
- package/dist/sent-secrets.js +1013 -0
- package/dist/server.js +28 -71
- package/dist/skills.js +416 -174
- package/dist/sqlite.d.ts +48 -0
- package/dist/sqlite.js +181 -0
- package/dist/storage/keys.js +443 -0
- package/dist/storage/memory.js +16 -4
- package/dist/storage/sql.d.ts +66 -0
- package/dist/storage/sql.js +543 -0
- package/dist/timeout.js +4 -2
- package/dist/tool-name.js +17 -0
- package/dist/tool-safety.d.ts +9 -35
- package/dist/tool-safety.js +20 -40
- package/dist/types.d.ts +336 -56
- package/dist/types.js +0 -1
- package/dist/ui.d.ts +7 -19
- package/dist/ui.js +76 -90
- package/dist/url-safety.js +3 -6
- package/dist/usage-guide.js +146 -0
- package/dist/validate.d.ts +7 -0
- package/dist/validate.js +309 -47
- package/dist/version.d.ts +1 -7
- package/dist/version.js +3 -7
- package/dist/worker.d.ts +11 -0
- package/dist/worker.js +435 -0
- package/documentation/README.md +63 -0
- package/documentation/architecture.md +696 -231
- package/documentation/auth.md +1069 -546
- package/documentation/code-mode.md +662 -356
- package/documentation/deploying.md +146 -0
- package/documentation/integrating.md +159 -0
- package/documentation/meta-tools.md +570 -216
- package/documentation/operating.md +135 -0
- package/documentation/operator-tests.md +97 -0
- package/documentation/operator-ui.md +132 -0
- package/documentation/provider-migration-0.29.md +65 -0
- package/examples/worker/AGENTS.md +34 -12
- package/examples/worker/README.md +293 -255
- package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
- package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
- package/examples/worker/src/connecta.config.ts +151 -0
- package/examples/worker/src/index.ts +14 -197
- package/examples/worker/wrangler.jsonc +15 -35
- package/package.json +126 -54
- package/templates/node/.dockerignore +1 -2
- package/templates/node/.env.example +20 -21
- package/templates/node/AGENTS.md +21 -17
- package/templates/node/Dockerfile +3 -5
- package/templates/node/README.md +140 -58
- package/templates/node/docker-compose.yml +11 -12
- package/templates/node/package.json +4 -2
- package/templates/node/src/connecta.config.ts +119 -0
- package/templates/node/src/index.ts +9 -154
- package/templates/node/src/provision-token.ts +17 -0
- package/AGENTS.md +0 -170
- package/dist/artifacts/connector.d.ts +0 -58
- package/dist/artifacts/connector.js +0 -730
- package/dist/artifacts/document.js +0 -86
- package/dist/artifacts/guide.js +0 -140
- package/dist/artifacts/html-scan.js +0 -329
- package/dist/artifacts/json.js +0 -78
- package/dist/artifacts/kv-store.d.ts +0 -20
- package/dist/artifacts/kv-store.js +0 -140
- package/dist/artifacts/markdown.js +0 -478
- package/dist/artifacts/module.d.ts +0 -37
- package/dist/artifacts/module.js +0 -66
- package/dist/artifacts/operations.d.ts +0 -197
- package/dist/artifacts/operations.js +0 -1002
- package/dist/artifacts/refresh.d.ts +0 -55
- package/dist/artifacts/refresh.js +0 -236
- package/dist/artifacts/routes.js +0 -206
- package/dist/artifacts/types.d.ts +0 -214
- package/dist/artifacts/types.js +0 -22
- package/dist/artifacts/validate.d.ts +0 -62
- package/dist/artifacts/validate.js +0 -651
- package/dist/artifacts.d.ts +0 -6
- package/dist/artifacts.js +0 -9
- package/dist/auth/bearer.d.ts +0 -11
- package/dist/auth/bearer.js +0 -50
- package/dist/providers/basecamp.js +0 -445
- package/dist/providers/linear.js +0 -217
- package/dist/providers/mixpanel.js +0 -247
- package/dist/providers/revenuecat.js +0 -317
- package/dist/providers/stripe.js +0 -253
- package/dist/providers/vercel.js +0 -1253
- package/dist/storage/file.d.ts +0 -15
- package/dist/storage/file.js +0 -455
- package/ethos.md +0 -93
- package/examples/worker/src/cloudflare-kv.ts +0 -43
- package/examples/worker/src/d1-activity-row.ts +0 -155
- package/examples/worker/src/d1-activity.ts +0 -147
- package/examples/worker/src/d1-storage.ts +0 -138
- package/examples/worker/src/r2-artifact-blobs.ts +0 -29
- package/templates/node/src/file-activity.ts +0 -182
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
// Keep this pure mapping module dependency-free so the repository's tests can
|
|
2
|
-
// exercise the example before the package's dist/ entrypoint has been built.
|
|
3
|
-
// The public adapter in d1-activity.ts remains checked against ActivityStore.
|
|
4
|
-
export interface ActivityEvent {
|
|
5
|
-
schemaVersion: 1;
|
|
6
|
-
id: string;
|
|
7
|
-
occurredAt: string;
|
|
8
|
-
requestId: string;
|
|
9
|
-
actor: {
|
|
10
|
-
kind: string;
|
|
11
|
-
id?: string;
|
|
12
|
-
namespace?: string;
|
|
13
|
-
};
|
|
14
|
-
connectorId: string;
|
|
15
|
-
toolName: string;
|
|
16
|
-
address: string;
|
|
17
|
-
source:
|
|
18
|
-
| "call_tool"
|
|
19
|
-
| "call_destructive_tool"
|
|
20
|
-
| "batch_call"
|
|
21
|
-
| "execute_code"
|
|
22
|
-
// History only: rows written before 0.28.0 removed program pauses.
|
|
23
|
-
| "resume_execution";
|
|
24
|
-
outcome:
|
|
25
|
-
| "success"
|
|
26
|
-
| "error"
|
|
27
|
-
| "timeout"
|
|
28
|
-
| "cancelled"
|
|
29
|
-
// History only, like `resume_execution` above.
|
|
30
|
-
| "paused"
|
|
31
|
-
| "approved";
|
|
32
|
-
durationMs: number;
|
|
33
|
-
attempts: number;
|
|
34
|
-
errorCode?: string;
|
|
35
|
-
friction?:
|
|
36
|
-
| "tool_not_found"
|
|
37
|
-
| "schema_retry"
|
|
38
|
-
| "destructive_reroute"
|
|
39
|
-
| "auth_required"
|
|
40
|
-
| "result_too_large";
|
|
41
|
-
/** History only: the scope an `approved` row covered, before 0.28.0. */
|
|
42
|
-
approval?: "call" | "tool";
|
|
43
|
-
serverName: string;
|
|
44
|
-
serverVersion: string;
|
|
45
|
-
deploymentId?: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Backfill for rows written before `friction` had a column of its own. Every
|
|
50
|
-
* arm must agree with the package's own `agentFrictionForCode`; the repository
|
|
51
|
-
* test suite pins that, because a dashboard that groups by friction should not
|
|
52
|
-
* change its answer depending on which side of a migration a row landed on.
|
|
53
|
-
* Friction that never had an error code — an oversized but successful result —
|
|
54
|
-
* is only recoverable from the column, which is why the column exists.
|
|
55
|
-
*/
|
|
56
|
-
function frictionForCode(code: string | null): ActivityEvent["friction"] {
|
|
57
|
-
switch (code) {
|
|
58
|
-
case "unknown_address":
|
|
59
|
-
case "unknown_tool":
|
|
60
|
-
case "ambiguous_tool_alias":
|
|
61
|
-
return "tool_not_found";
|
|
62
|
-
case "invalid_args":
|
|
63
|
-
return "schema_retry";
|
|
64
|
-
case "destructive_tool_requires_approval":
|
|
65
|
-
return "destructive_reroute";
|
|
66
|
-
case "auth_required":
|
|
67
|
-
return "auth_required";
|
|
68
|
-
case "result_too_large":
|
|
69
|
-
return "result_too_large";
|
|
70
|
-
default:
|
|
71
|
-
return undefined;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface ActivityRow {
|
|
76
|
-
id: string;
|
|
77
|
-
occurred_at_ms: number;
|
|
78
|
-
request_id: string;
|
|
79
|
-
actor_kind: string;
|
|
80
|
-
actor_id: string | null;
|
|
81
|
-
actor_namespace: string | null;
|
|
82
|
-
connector_id: string;
|
|
83
|
-
tool_name: string;
|
|
84
|
-
source: ActivityEvent["source"];
|
|
85
|
-
outcome: ActivityEvent["outcome"];
|
|
86
|
-
duration_ms: number;
|
|
87
|
-
attempts: number;
|
|
88
|
-
error_code: string | null;
|
|
89
|
-
friction: ActivityEvent["friction"] | null;
|
|
90
|
-
/** Absent from tables created before 0.26.0; null on every row since 0.28.0. */
|
|
91
|
-
approval?: ActivityEvent["approval"] | null;
|
|
92
|
-
server_name: string;
|
|
93
|
-
server_version: string;
|
|
94
|
-
deployment_id: string | null;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function activityEventToRow(
|
|
98
|
-
event: ActivityEvent,
|
|
99
|
-
): ActivityRow {
|
|
100
|
-
return {
|
|
101
|
-
id: event.id,
|
|
102
|
-
occurred_at_ms: Date.parse(event.occurredAt),
|
|
103
|
-
request_id: event.requestId,
|
|
104
|
-
actor_kind: event.actor.kind,
|
|
105
|
-
actor_id: event.actor.id ?? null,
|
|
106
|
-
actor_namespace: event.actor.namespace ?? null,
|
|
107
|
-
connector_id: event.connectorId,
|
|
108
|
-
tool_name: event.toolName,
|
|
109
|
-
source: event.source,
|
|
110
|
-
outcome: event.outcome,
|
|
111
|
-
duration_ms: event.durationMs,
|
|
112
|
-
attempts: event.attempts,
|
|
113
|
-
error_code: event.errorCode ?? null,
|
|
114
|
-
// Stored beside the code rather than derived from it: a truncated result is
|
|
115
|
-
// friction on a call that succeeded, so it has no error code to derive from
|
|
116
|
-
// — and `error_code IS NOT NULL` stays an honest count of failures.
|
|
117
|
-
friction: event.friction ?? null,
|
|
118
|
-
approval: event.approval ?? null,
|
|
119
|
-
server_name: event.serverName,
|
|
120
|
-
server_version: event.serverVersion,
|
|
121
|
-
deployment_id: event.deploymentId ?? null,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export function activityRowToEvent(
|
|
126
|
-
row: ActivityRow,
|
|
127
|
-
): ActivityEvent {
|
|
128
|
-
const friction = row.friction ?? frictionForCode(row.error_code);
|
|
129
|
-
return {
|
|
130
|
-
schemaVersion: 1,
|
|
131
|
-
id: row.id,
|
|
132
|
-
occurredAt: new Date(row.occurred_at_ms).toISOString(),
|
|
133
|
-
requestId: row.request_id,
|
|
134
|
-
actor: {
|
|
135
|
-
kind: row.actor_kind,
|
|
136
|
-
...(row.actor_id ? { id: row.actor_id } : {}),
|
|
137
|
-
...(row.actor_namespace ? { namespace: row.actor_namespace } : {}),
|
|
138
|
-
},
|
|
139
|
-
connectorId: row.connector_id,
|
|
140
|
-
toolName: row.tool_name,
|
|
141
|
-
address: `${row.connector_id}.${row.tool_name}`,
|
|
142
|
-
source: row.source,
|
|
143
|
-
outcome: row.outcome,
|
|
144
|
-
durationMs: row.duration_ms,
|
|
145
|
-
attempts: row.attempts,
|
|
146
|
-
...(row.error_code ? { errorCode: row.error_code } : {}),
|
|
147
|
-
...(friction ? { friction } : {}),
|
|
148
|
-
...(row.approval ? { approval: row.approval } : {}),
|
|
149
|
-
serverName: row.server_name,
|
|
150
|
-
serverVersion: row.server_version,
|
|
151
|
-
...(row.deployment_id
|
|
152
|
-
? { deploymentId: row.deployment_id }
|
|
153
|
-
: {}),
|
|
154
|
-
};
|
|
155
|
-
}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ActivityPage,
|
|
3
|
-
ActivityStore,
|
|
4
|
-
} from "@zackbart/connecta";
|
|
5
|
-
import { InvalidActivityCursorError } from "@zackbart/connecta/activity";
|
|
6
|
-
import {
|
|
7
|
-
activityEventToRow,
|
|
8
|
-
activityRowToEvent,
|
|
9
|
-
type ActivityRow,
|
|
10
|
-
} from "./d1-activity-row.js";
|
|
11
|
-
|
|
12
|
-
interface Cursor {
|
|
13
|
-
occurredAtMs: number;
|
|
14
|
-
id: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function encodeCursor(row: ActivityRow): string {
|
|
18
|
-
return btoa(`${row.occurred_at_ms}:${row.id}`);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function decodeCursor(value: string): Cursor {
|
|
22
|
-
let decoded: string;
|
|
23
|
-
try {
|
|
24
|
-
decoded = atob(value);
|
|
25
|
-
} catch {
|
|
26
|
-
throw new InvalidActivityCursorError();
|
|
27
|
-
}
|
|
28
|
-
const separator = decoded.indexOf(":");
|
|
29
|
-
const occurredAtMs = Number(decoded.slice(0, separator));
|
|
30
|
-
const id = decoded.slice(separator + 1);
|
|
31
|
-
if (
|
|
32
|
-
separator < 1 ||
|
|
33
|
-
!Number.isSafeInteger(occurredAtMs) ||
|
|
34
|
-
occurredAtMs < 0 ||
|
|
35
|
-
!/^[0-9a-f-]{36}$/i.test(id)
|
|
36
|
-
) {
|
|
37
|
-
throw new InvalidActivityCursorError();
|
|
38
|
-
}
|
|
39
|
-
return { occurredAtMs, id };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** One append-only row per completed downstream call; no arguments or results. */
|
|
43
|
-
export function d1ActivityStore(db: D1Database): ActivityStore {
|
|
44
|
-
return {
|
|
45
|
-
async record(event) {
|
|
46
|
-
const row = activityEventToRow(event);
|
|
47
|
-
await db
|
|
48
|
-
.prepare(
|
|
49
|
-
`INSERT INTO tool_call_activity (
|
|
50
|
-
id, occurred_at_ms, request_id, actor_kind, actor_id,
|
|
51
|
-
actor_namespace,
|
|
52
|
-
connector_id, tool_name, source, outcome, duration_ms, attempts,
|
|
53
|
-
error_code, friction, approval, server_name, server_version,
|
|
54
|
-
deployment_id
|
|
55
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
56
|
-
)
|
|
57
|
-
.bind(
|
|
58
|
-
row.id,
|
|
59
|
-
row.occurred_at_ms,
|
|
60
|
-
row.request_id,
|
|
61
|
-
row.actor_kind,
|
|
62
|
-
row.actor_id,
|
|
63
|
-
row.actor_namespace,
|
|
64
|
-
row.connector_id,
|
|
65
|
-
row.tool_name,
|
|
66
|
-
row.source,
|
|
67
|
-
row.outcome,
|
|
68
|
-
row.duration_ms,
|
|
69
|
-
row.attempts,
|
|
70
|
-
row.error_code,
|
|
71
|
-
row.friction ?? null,
|
|
72
|
-
row.approval ?? null,
|
|
73
|
-
row.server_name,
|
|
74
|
-
row.server_version,
|
|
75
|
-
row.deployment_id,
|
|
76
|
-
)
|
|
77
|
-
.run();
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
async list({ cursor, limit }): Promise<ActivityPage> {
|
|
81
|
-
const boundedLimit = Math.min(100, Math.max(1, Math.trunc(limit)));
|
|
82
|
-
const pageSize = boundedLimit + 1;
|
|
83
|
-
const position = cursor ? decodeCursor(cursor) : undefined;
|
|
84
|
-
const statement = position
|
|
85
|
-
? db
|
|
86
|
-
.prepare(
|
|
87
|
-
`SELECT * FROM tool_call_activity
|
|
88
|
-
WHERE occurred_at_ms < ?
|
|
89
|
-
OR (occurred_at_ms = ? AND id < ?)
|
|
90
|
-
ORDER BY occurred_at_ms DESC, id DESC
|
|
91
|
-
LIMIT ?`,
|
|
92
|
-
)
|
|
93
|
-
.bind(
|
|
94
|
-
position.occurredAtMs,
|
|
95
|
-
position.occurredAtMs,
|
|
96
|
-
position.id,
|
|
97
|
-
pageSize,
|
|
98
|
-
)
|
|
99
|
-
: db
|
|
100
|
-
.prepare(
|
|
101
|
-
`SELECT * FROM tool_call_activity
|
|
102
|
-
ORDER BY occurred_at_ms DESC, id DESC
|
|
103
|
-
LIMIT ?`,
|
|
104
|
-
)
|
|
105
|
-
.bind(pageSize);
|
|
106
|
-
const result = await statement.all<ActivityRow>();
|
|
107
|
-
const rows = result.results ?? [];
|
|
108
|
-
const hasMore = rows.length > boundedLimit;
|
|
109
|
-
const visible = hasMore ? rows.slice(0, boundedLimit) : rows;
|
|
110
|
-
const last = visible.at(-1);
|
|
111
|
-
return {
|
|
112
|
-
events: visible.map(activityRowToEvent),
|
|
113
|
-
...(hasMore && last ? { nextCursor: encodeCursor(last) } : {}),
|
|
114
|
-
};
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Daily retention pass. Each statement is capped at 5,000 rows, then repeats
|
|
121
|
-
* up to a per-run ceiling so normal traffic catches up without allowing one
|
|
122
|
-
* scheduled invocation to consume unbounded queries.
|
|
123
|
-
*/
|
|
124
|
-
export async function pruneActivity(
|
|
125
|
-
db: D1Database,
|
|
126
|
-
retentionDays: number,
|
|
127
|
-
maxBatches = 20,
|
|
128
|
-
): Promise<void> {
|
|
129
|
-
const cutoff = Date.now() -
|
|
130
|
-
Math.max(1, Math.trunc(retentionDays)) * 24 * 60 * 60 * 1_000;
|
|
131
|
-
const batchLimit = Math.max(1, Math.trunc(maxBatches));
|
|
132
|
-
for (let batch = 0; batch < batchLimit; batch++) {
|
|
133
|
-
const result = await db
|
|
134
|
-
.prepare(
|
|
135
|
-
`DELETE FROM tool_call_activity
|
|
136
|
-
WHERE id IN (
|
|
137
|
-
SELECT id FROM tool_call_activity
|
|
138
|
-
WHERE occurred_at_ms < ?
|
|
139
|
-
ORDER BY occurred_at_ms ASC
|
|
140
|
-
LIMIT 5000
|
|
141
|
-
)`,
|
|
142
|
-
)
|
|
143
|
-
.bind(cutoff)
|
|
144
|
-
.run();
|
|
145
|
-
if ((result.meta.changes ?? 0) < 5_000) return;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import type { KVStorage } from "@zackbart/connecta";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* KVStorage over a D1 database, with the atomic `compareAndSet` that Workers
|
|
5
|
-
* KV cannot offer. Apply the `connecta_kv` schema in README.md § "Strongly
|
|
6
|
-
* consistent storage" before first use.
|
|
7
|
-
*
|
|
8
|
-
* Every compare-and-set is a single SQL statement. D1 runs one database's
|
|
9
|
-
* statements one at a time against its primary, so the comparison and the
|
|
10
|
-
* write cannot interleave with another request's: of fifty concurrent claims
|
|
11
|
-
* on an absent key, exactly one changes a row.
|
|
12
|
-
*
|
|
13
|
-
* Expiry is judged by the Worker's clock at each call, the same `Date.now()`
|
|
14
|
-
* the core uses. An expired row reads as absent immediately and is removed
|
|
15
|
-
* physically by later writes, a bounded batch at a time, so no request starts
|
|
16
|
-
* a background job and no cron is required.
|
|
17
|
-
*/
|
|
18
|
-
export function d1Storage(db: D1Database): KVStorage {
|
|
19
|
-
// Every statement that tests liveness binds the current time as ?2.
|
|
20
|
-
const live = "(expires_at_ms IS NULL OR expires_at_ms > ?2)";
|
|
21
|
-
const expiresAt = (now: number, ttlSeconds?: number): number | null => {
|
|
22
|
-
if (ttlSeconds !== undefined && !Number.isFinite(ttlSeconds)) {
|
|
23
|
-
throw new RangeError("ttlSeconds must produce a finite expiration timestamp");
|
|
24
|
-
}
|
|
25
|
-
if (!ttlSeconds) return null;
|
|
26
|
-
const expiry = now + ttlSeconds * 1000;
|
|
27
|
-
if (!Number.isFinite(expiry)) {
|
|
28
|
-
throw new RangeError("ttlSeconds must produce a finite expiration timestamp");
|
|
29
|
-
}
|
|
30
|
-
return expiry;
|
|
31
|
-
};
|
|
32
|
-
const changed = (result: D1Result) => result.meta.changes > 0;
|
|
33
|
-
return {
|
|
34
|
-
async get(key) {
|
|
35
|
-
const row = await db
|
|
36
|
-
.prepare(`SELECT value FROM connecta_kv WHERE key = ?1 AND ${live}`)
|
|
37
|
-
.bind(key, Date.now())
|
|
38
|
-
.first<{ value: string }>();
|
|
39
|
-
return row?.value ?? null;
|
|
40
|
-
},
|
|
41
|
-
async set(key, value, opts) {
|
|
42
|
-
const now = Date.now();
|
|
43
|
-
const expiry = expiresAt(now, opts?.ttlSeconds);
|
|
44
|
-
await db.batch([
|
|
45
|
-
db
|
|
46
|
-
.prepare(
|
|
47
|
-
`DELETE FROM connecta_kv WHERE key IN (
|
|
48
|
-
SELECT key FROM connecta_kv WHERE expires_at_ms <= ?1 LIMIT 16
|
|
49
|
-
)`,
|
|
50
|
-
)
|
|
51
|
-
.bind(now),
|
|
52
|
-
db
|
|
53
|
-
.prepare(
|
|
54
|
-
`INSERT INTO connecta_kv (key, value, expires_at_ms)
|
|
55
|
-
VALUES (?1, ?2, ?3)
|
|
56
|
-
ON CONFLICT (key) DO UPDATE SET
|
|
57
|
-
value = excluded.value, expires_at_ms = excluded.expires_at_ms`,
|
|
58
|
-
)
|
|
59
|
-
.bind(key, value, expiry),
|
|
60
|
-
]);
|
|
61
|
-
},
|
|
62
|
-
async delete(key) {
|
|
63
|
-
await db.prepare("DELETE FROM connecta_kv WHERE key = ?1").bind(key).run();
|
|
64
|
-
},
|
|
65
|
-
async list(prefix) {
|
|
66
|
-
const { results } = await db
|
|
67
|
-
.prepare(
|
|
68
|
-
`SELECT key FROM connecta_kv
|
|
69
|
-
WHERE key >= ?1 AND substr(CAST(key AS BLOB), 1, length(CAST(?1 AS BLOB))) = CAST(?1 AS BLOB) AND ${live}`,
|
|
70
|
-
)
|
|
71
|
-
.bind(prefix, Date.now())
|
|
72
|
-
.all<{ key: string }>();
|
|
73
|
-
// Sort in JavaScript: SQLite orders UTF-8 bytes, the contract orders
|
|
74
|
-
// the UTF-16 code units every other adapter sorts by.
|
|
75
|
-
return results.map((row) => row.key).sort();
|
|
76
|
-
},
|
|
77
|
-
async compareAndSet(key, expected, next, opts) {
|
|
78
|
-
const now = Date.now();
|
|
79
|
-
if (next === null && expected === null) {
|
|
80
|
-
// Nothing to write; the claim holds exactly when no live row exists.
|
|
81
|
-
const row = await db
|
|
82
|
-
.prepare(`SELECT 1 AS present FROM connecta_kv WHERE key = ?1 AND ${live}`)
|
|
83
|
-
.bind(key, now)
|
|
84
|
-
.first();
|
|
85
|
-
return row === null;
|
|
86
|
-
}
|
|
87
|
-
if (next === null) {
|
|
88
|
-
return changed(
|
|
89
|
-
await db
|
|
90
|
-
.prepare(
|
|
91
|
-
`DELETE FROM connecta_kv WHERE key = ?1 AND ${live} AND value = ?3`,
|
|
92
|
-
)
|
|
93
|
-
.bind(key, now, expected)
|
|
94
|
-
.run(),
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
let expiry: number | null;
|
|
98
|
-
try {
|
|
99
|
-
expiry = expiresAt(now, opts?.ttlSeconds);
|
|
100
|
-
} catch (error) {
|
|
101
|
-
// Invalid write options do not change a failed comparison's result.
|
|
102
|
-
// Ordinary claims still use one atomic statement without this read.
|
|
103
|
-
const current = await db
|
|
104
|
-
.prepare(`SELECT value FROM connecta_kv WHERE key = ?1 AND ${live}`)
|
|
105
|
-
.bind(key, now)
|
|
106
|
-
.first<{ value: string }>();
|
|
107
|
-
if ((current?.value ?? null) !== expected) return false;
|
|
108
|
-
throw error;
|
|
109
|
-
}
|
|
110
|
-
if (expected === null) {
|
|
111
|
-
// Insert, or take over a row that has expired. A live row makes the
|
|
112
|
-
// upsert's WHERE false, so nothing changes and the claim is refused.
|
|
113
|
-
return changed(
|
|
114
|
-
await db
|
|
115
|
-
.prepare(
|
|
116
|
-
`INSERT INTO connecta_kv (key, value, expires_at_ms)
|
|
117
|
-
VALUES (?1, ?3, ?4)
|
|
118
|
-
ON CONFLICT (key) DO UPDATE SET
|
|
119
|
-
value = excluded.value, expires_at_ms = excluded.expires_at_ms
|
|
120
|
-
WHERE connecta_kv.expires_at_ms IS NOT NULL
|
|
121
|
-
AND connecta_kv.expires_at_ms <= ?2`,
|
|
122
|
-
)
|
|
123
|
-
.bind(key, now, next, expiry)
|
|
124
|
-
.run(),
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
return changed(
|
|
128
|
-
await db
|
|
129
|
-
.prepare(
|
|
130
|
-
`UPDATE connecta_kv SET value = ?4, expires_at_ms = ?5
|
|
131
|
-
WHERE key = ?1 AND ${live} AND value = ?3`,
|
|
132
|
-
)
|
|
133
|
-
.bind(key, now, expected, next, expiry)
|
|
134
|
-
.run(),
|
|
135
|
-
);
|
|
136
|
-
},
|
|
137
|
-
};
|
|
138
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { ArtifactBlobStore } from "@zackbart/connecta/artifacts";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Artifact bodies — page sources, data documents, refresh programs — in an R2
|
|
5
|
-
* bucket, beside a D1-backed `kvArtifactStore` that keeps the heads, versions,
|
|
6
|
-
* and runs. Optional: without it, bodies live in D1 rows, which the default
|
|
7
|
-
* limits keep well under D1's row size. Use it once pages or documents grow,
|
|
8
|
-
* or to keep large bodies out of the database a backup copies.
|
|
9
|
-
*
|
|
10
|
-
* Keys are content addresses, so writing one twice writes the same bytes, and
|
|
11
|
-
* nothing here ever deletes one: versions are immutable, and a rollback points
|
|
12
|
-
* back at an old body rather than copying it.
|
|
13
|
-
*/
|
|
14
|
-
export function r2ArtifactBlobs(
|
|
15
|
-
bucket: R2Bucket,
|
|
16
|
-
prefix = "artifacts/blobs/",
|
|
17
|
-
): ArtifactBlobStore {
|
|
18
|
-
return {
|
|
19
|
-
async put(key, body) {
|
|
20
|
-
await bucket.put(`${prefix}${key}`, body, {
|
|
21
|
-
httpMetadata: { contentType: "text/plain; charset=utf-8" },
|
|
22
|
-
});
|
|
23
|
-
},
|
|
24
|
-
async get(key) {
|
|
25
|
-
const object = await bucket.get(`${prefix}${key}`);
|
|
26
|
-
return object ? object.text() : null;
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
}
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Payload-free tool activity, kept in a JSON-lines file beside the state file.
|
|
3
|
-
*
|
|
4
|
-
* Deployment-owned on purpose: Connecta ships the `ActivityStore` contract, not
|
|
5
|
-
* a backend, because retention is a decision only the operator can make. This
|
|
6
|
-
* is the Node counterpart of the Worker example's D1 store, sized for one
|
|
7
|
-
* container rather than a fleet — a write appends one line, and the log is
|
|
8
|
-
* bounded by a count of events instead of by age, so nothing has to be
|
|
9
|
-
* scheduled.
|
|
10
|
-
*
|
|
11
|
-
* Nothing wires it by default. `src/index.ts` carries the commented lines that
|
|
12
|
-
* turn it on; README.md § "Turn on the operator surface" explains why they are
|
|
13
|
-
* commented.
|
|
14
|
-
*/
|
|
15
|
-
import {
|
|
16
|
-
appendFileSync,
|
|
17
|
-
chmodSync,
|
|
18
|
-
existsSync,
|
|
19
|
-
mkdirSync,
|
|
20
|
-
readFileSync,
|
|
21
|
-
renameSync,
|
|
22
|
-
writeFileSync,
|
|
23
|
-
} from "node:fs";
|
|
24
|
-
import { dirname } from "node:path";
|
|
25
|
-
import type {
|
|
26
|
-
ActivityPage,
|
|
27
|
-
ActivityStore,
|
|
28
|
-
ToolCallActivityEvent,
|
|
29
|
-
} from "@zackbart/connecta";
|
|
30
|
-
import { InvalidActivityCursorError } from "@zackbart/connecta/activity";
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Events allowed past `maxEvents` before the log is rewritten. Trimming on the
|
|
34
|
-
* write that first crosses the ceiling would rewrite the whole file on every
|
|
35
|
-
* call from then on; this pays that cost once per slack window instead.
|
|
36
|
-
*/
|
|
37
|
-
const COMPACTION_SLACK = 500;
|
|
38
|
-
|
|
39
|
-
export interface FileActivityOptions {
|
|
40
|
-
/** Newest events retained after a compaction. Default 5,000. */
|
|
41
|
-
maxEvents?: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Newest first, with the id breaking ties inside a millisecond. */
|
|
45
|
-
function newestFirst(
|
|
46
|
-
a: ToolCallActivityEvent,
|
|
47
|
-
b: ToolCallActivityEvent,
|
|
48
|
-
): number {
|
|
49
|
-
const byTime = Date.parse(b.occurredAt) - Date.parse(a.occurredAt);
|
|
50
|
-
return byTime !== 0 ? byTime : b.id.localeCompare(a.id);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function encodeCursor(event: ToolCallActivityEvent): string {
|
|
54
|
-
return Buffer.from(`${Date.parse(event.occurredAt)}:${event.id}`).toString(
|
|
55
|
-
"base64",
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function decodeCursor(value: string): { occurredAtMs: number; id: string } {
|
|
60
|
-
const decoded = Buffer.from(value, "base64").toString("utf8");
|
|
61
|
-
const separator = decoded.indexOf(":");
|
|
62
|
-
const occurredAtMs = Number(decoded.slice(0, separator));
|
|
63
|
-
const id = decoded.slice(separator + 1);
|
|
64
|
-
if (separator < 1 || !Number.isSafeInteger(occurredAtMs) || !id) {
|
|
65
|
-
throw new InvalidActivityCursorError();
|
|
66
|
-
}
|
|
67
|
-
return { occurredAtMs, id };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Strictly older than the cursor position, in the order above. */
|
|
71
|
-
function isBefore(
|
|
72
|
-
event: ToolCallActivityEvent,
|
|
73
|
-
position: { occurredAtMs: number; id: string },
|
|
74
|
-
): boolean {
|
|
75
|
-
const occurredAtMs = Date.parse(event.occurredAt);
|
|
76
|
-
if (occurredAtMs !== position.occurredAtMs) {
|
|
77
|
-
return occurredAtMs < position.occurredAtMs;
|
|
78
|
-
}
|
|
79
|
-
return event.id.localeCompare(position.id) < 0;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* One append-only record per completed downstream call. Events carry no
|
|
84
|
-
* arguments, results, generated code, or raw error messages — the store never
|
|
85
|
-
* has a payload to leak. Writes are best-effort by contract: a throw here is
|
|
86
|
-
* logged by Connecta and never reaches the tool result.
|
|
87
|
-
*/
|
|
88
|
-
export function fileActivityStore(
|
|
89
|
-
path: string,
|
|
90
|
-
opts: FileActivityOptions = {},
|
|
91
|
-
): ActivityStore {
|
|
92
|
-
const maxEvents = Math.max(1, Math.trunc(opts.maxEvents ?? 5_000));
|
|
93
|
-
// Activity records who called what, so it is owner-only like the state file.
|
|
94
|
-
const tighten = () => {
|
|
95
|
-
try {
|
|
96
|
-
chmodSync(path, 0o600);
|
|
97
|
-
} catch {
|
|
98
|
-
// Non-POSIX filesystem or a race on the file — leave the mode as-is.
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
let events: ToolCallActivityEvent[] = [];
|
|
102
|
-
// Damage found at load that the file itself still carries: a dropped line, or
|
|
103
|
-
// a last line with no newline after it. Both are repaired by rewriting the
|
|
104
|
-
// file from what parsed, because the alternative is appending onto a
|
|
105
|
-
// fragment and losing the *next* event too — one that never had anything
|
|
106
|
-
// wrong with it.
|
|
107
|
-
let repairNeeded = false;
|
|
108
|
-
if (existsSync(path)) {
|
|
109
|
-
tighten();
|
|
110
|
-
// A half-written trailing line is the normal cost of an append log, and
|
|
111
|
-
// one unreadable line is not a reason to lose the history above it. Unlike
|
|
112
|
-
// the state file there is nothing irreplaceable here — this is a record of
|
|
113
|
-
// calls that already happened — so damaged lines are dropped, loudly.
|
|
114
|
-
const raw = readFileSync(path, "utf8");
|
|
115
|
-
const lines = raw.split("\n").filter(Boolean);
|
|
116
|
-
let dropped = 0;
|
|
117
|
-
for (const line of lines) {
|
|
118
|
-
try {
|
|
119
|
-
events.push(JSON.parse(line) as ToolCallActivityEvent);
|
|
120
|
-
} catch {
|
|
121
|
-
dropped += 1;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
if (dropped > 0) {
|
|
125
|
-
console.error(
|
|
126
|
-
`[connecta] activity log ${path}: dropped ${dropped} unreadable ` +
|
|
127
|
-
`line(s) of ${lines.length}.`,
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
repairNeeded = dropped > 0 || (raw.length > 0 && !raw.endsWith("\n"));
|
|
131
|
-
}
|
|
132
|
-
const ensureDirectory = () => {
|
|
133
|
-
const dir = dirname(path);
|
|
134
|
-
if (dir) mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
135
|
-
};
|
|
136
|
-
const compact = () => {
|
|
137
|
-
events = [...events].sort(newestFirst).slice(0, maxEvents);
|
|
138
|
-
ensureDirectory();
|
|
139
|
-
const tmp = `${path}.tmp`;
|
|
140
|
-
// 0o600 on the temp file; the rename preserves it, so the live log is
|
|
141
|
-
// never briefly world-readable.
|
|
142
|
-
writeFileSync(
|
|
143
|
-
tmp,
|
|
144
|
-
events.map((event) => `${JSON.stringify(event)}\n`).join(""),
|
|
145
|
-
{ mode: 0o600 },
|
|
146
|
-
);
|
|
147
|
-
renameSync(tmp, path);
|
|
148
|
-
tighten();
|
|
149
|
-
};
|
|
150
|
-
// Repair before the first append rather than at the next restart: the
|
|
151
|
-
// fragment is on disk until something rewrites the file, and an append onto
|
|
152
|
-
// it corrupts a good event into an unreadable line.
|
|
153
|
-
if (repairNeeded) compact();
|
|
154
|
-
return {
|
|
155
|
-
record(event) {
|
|
156
|
-
events.push(event);
|
|
157
|
-
if (events.length > maxEvents + COMPACTION_SLACK) {
|
|
158
|
-
compact();
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
ensureDirectory();
|
|
162
|
-
appendFileSync(path, `${JSON.stringify(event)}\n`, { mode: 0o600 });
|
|
163
|
-
tighten();
|
|
164
|
-
},
|
|
165
|
-
|
|
166
|
-
async list({ cursor, limit }): Promise<ActivityPage> {
|
|
167
|
-
const boundedLimit = Math.min(100, Math.max(1, Math.trunc(limit)));
|
|
168
|
-
const position = cursor ? decodeCursor(cursor) : undefined;
|
|
169
|
-
const ordered = [...events]
|
|
170
|
-
.sort(newestFirst)
|
|
171
|
-
.filter((event) => !position || isBefore(event, position));
|
|
172
|
-
const visible = ordered.slice(0, boundedLimit);
|
|
173
|
-
const last = visible.at(-1);
|
|
174
|
-
return {
|
|
175
|
-
events: visible,
|
|
176
|
-
...(ordered.length > visible.length && last
|
|
177
|
-
? { nextCursor: encodeCursor(last) }
|
|
178
|
-
: {}),
|
|
179
|
-
};
|
|
180
|
-
},
|
|
181
|
-
};
|
|
182
|
-
}
|