@cursor/july 0.1.62 → 0.1.68
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/dist/bin/agent-serve.js +8 -0
- package/dist/channels/slack/channel-watch.d.ts +2 -1
- package/dist/channels/slack/channel-watch.d.ts.map +1 -1
- package/dist/channels/slack/channel-watch.js +7 -3
- package/dist/channels/slack/dispatch.d.ts +9 -0
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +41 -21
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +1 -0
- package/dist/channels/slack/nudge-store.d.ts +16 -0
- package/dist/channels/slack/nudge-store.d.ts.map +1 -1
- package/dist/channels/slack/nudge-store.js +53 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +4 -1
- package/dist/channels/slack/types.d.ts +22 -5
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.B-biogkN.js → app.C5XUTCw9.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DvUR3lv9.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DZqKtFZS.js → VPLocalSearchBox.CGDTAYHY.js} +1 -1
- package/dist/docs/assets/chunks/{theme.BwC5zP2r.js → theme.lPixh250.js} +2 -2
- package/dist/docs/assets/{reference_connections.md.CSQiNzre.js → reference_connections.md.Bwswbm6H.js} +13 -3
- package/dist/docs/assets/{reference_connections.md.CSQiNzre.lean.js → reference_connections.md.Bwswbm6H.lean.js} +1 -1
- package/dist/docs/assets/{reference_tools.md.kJfJAP2m.js → reference_tools.md.Wmvb7Lob.js} +2 -2
- package/dist/docs/assets/{reference_tools.md.kJfJAP2m.lean.js → reference_tools.md.Wmvb7Lob.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +3 -3
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +3 -3
- package/dist/docs/guides/convert-automation.html +3 -3
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/opentelemetry.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/artifacts.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +15 -5
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +5 -5
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/templates/agentic-owners.html +3 -3
- package/dist/docs/templates/demo.html +3 -3
- package/dist/docs/templates/pr-autofixer.html +3 -3
- package/dist/docs/templates/security-reviewer.html +3 -3
- package/dist/docs/templates/triage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/files-backends/agent-store-presigned-url.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/advertise-tools.d.ts +74 -0
- package/dist/internal/advertise-tools.d.ts.map +1 -0
- package/dist/internal/advertise-tools.js +152 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +11 -3
- package/dist/internal/cli-manifest.d.ts +19 -0
- package/dist/internal/cli-manifest.d.ts.map +1 -0
- package/dist/internal/cli-manifest.js +49 -0
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +9 -1
- package/dist/internal/cursor-agent-template.d.ts +9 -0
- package/dist/internal/cursor-agent-template.d.ts.map +1 -0
- package/dist/internal/cursor-agent-template.js +37 -0
- package/dist/internal/deploy-client.d.ts +8 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +21 -3
- package/dist/internal/deploy-manifest.d.ts +85 -0
- package/dist/internal/deploy-manifest.d.ts.map +1 -0
- package/dist/internal/deploy-manifest.js +106 -0
- package/dist/internal/deployment-scm-event-stream.d.ts +113 -0
- package/dist/internal/deployment-scm-event-stream.d.ts.map +1 -0
- package/dist/internal/deployment-scm-event-stream.js +460 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +69 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +5 -17
- package/dist/internal/mcp-host.d.ts +18 -0
- package/dist/internal/mcp-host.d.ts.map +1 -1
- package/dist/internal/mcp-host.js +41 -5
- package/dist/internal/sdk-runner.d.ts +1 -1
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +36 -13
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +18 -0
- package/dist/internal/session-engine.d.ts +66 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +430 -110
- package/dist/internal/shallow-clone.d.ts +115 -0
- package/dist/internal/shallow-clone.d.ts.map +1 -0
- package/dist/internal/shallow-clone.js +292 -0
- package/dist/internal/slack-provision-client.d.ts +2 -0
- package/dist/internal/slack-provision-client.d.ts.map +1 -1
- package/dist/internal/slack-provision-client.js +1 -0
- package/dist/internal/turn-lease-store.d.ts +44 -0
- package/dist/internal/turn-lease-store.d.ts.map +1 -0
- package/dist/internal/turn-lease-store.js +139 -0
- package/dist/internal/workspace.d.ts +12 -0
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +28 -0
- package/dist/playground/assets/index-CoL7xCJl.css +1 -0
- package/dist/playground/assets/index-yxs1heea.js +85 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-protocol.d.ts +122 -0
- package/dist/storage-protocol.d.ts.map +1 -0
- package/dist/storage-protocol.js +173 -0
- package/dist/storage.d.ts +2 -72
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +2 -82
- package/dist/types.d.ts +113 -3
- package/dist/types.d.ts.map +1 -1
- package/docs/reference/connections.md +80 -0
- package/docs/reference/tools.md +14 -0
- package/package.json +4 -2
- package/src/bin/agent-serve.ts +9 -0
- package/src/channels/slack/channel-watch.ts +6 -3
- package/src/channels/slack/dispatch.ts +40 -3
- package/src/channels/slack/manifest.ts +1 -0
- package/src/channels/slack/nudge-store.ts +56 -0
- package/src/channels/slack/slack-channel.ts +3 -0
- package/src/channels/slack/types.ts +22 -5
- package/src/index.ts +5 -0
- package/src/internal/advertise-tools.ts +229 -0
- package/src/internal/cli-deploy.ts +22 -1
- package/src/internal/cli-manifest.ts +63 -0
- package/src/internal/cli-slack.ts +10 -1
- package/src/internal/cursor-agent-template.ts +46 -0
- package/src/internal/deploy-client.ts +35 -6
- package/src/internal/deploy-manifest.ts +167 -0
- package/src/internal/deployment-scm-event-stream.ts +577 -0
- package/src/internal/discovery.ts +85 -0
- package/src/internal/init-project.ts +11 -20
- package/src/internal/mcp-host.ts +66 -9
- package/src/internal/sdk-runner.ts +50 -11
- package/src/internal/server.ts +19 -0
- package/src/internal/session-engine.ts +385 -19
- package/src/internal/shallow-clone.ts +407 -0
- package/src/internal/slack-provision-client.ts +3 -0
- package/src/internal/turn-lease-store.ts +147 -0
- package/src/internal/workspace.ts +27 -0
- package/src/storage-protocol.ts +247 -0
- package/src/storage.ts +16 -110
- package/src/types.ts +117 -3
- package/templates/pr-autofixer/agent/lib/drive-schemas.ts +4 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.5ozYNf74.js +0 -1
- package/dist/playground/assets/index-B4rcOgeP.css +0 -1
- package/dist/playground/assets/index-DXcY42Jv.js +0 -85
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable session KV contract: key families plus a tolerant SessionRecord
|
|
3
|
+
* decoder. This is the lockstep surface for the control plane (see
|
|
4
|
+
* `backend/server/src/agentServe/durableSessionProtocol.ts`) so a dashboard
|
|
5
|
+
* list can read Bugbot KV without importing `@cursor/july`.
|
|
6
|
+
*
|
|
7
|
+
* Additive JSON only — unknown fields are ignored. Do not put ledger
|
|
8
|
+
* (`durable-session/`) types here; those are coordination-only and must not
|
|
9
|
+
* appear in the CP session list.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Framework-owned key root. All {@link storageKeys} values live under this
|
|
16
|
+
* prefix so a shared store can route or namespace Agent SDK data.
|
|
17
|
+
*/
|
|
18
|
+
export const STORAGE_KEY_ROOT = "agentkit/v1" as const;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Exact session-record keys: `agentkit/v1/{agent}/session/{sessionId}`.
|
|
22
|
+
* Rejects `session-events/`, `durable-session/`, and the CP-owned `_cp`
|
|
23
|
+
* agent name.
|
|
24
|
+
*/
|
|
25
|
+
const DURABLE_SESSION_RECORD_KEY_RE =
|
|
26
|
+
/^agentkit\/v1\/([^/]+)\/session\/([^/]+)$/;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The framework-owned key scheme. Keys are a **stable, versioned contract**
|
|
30
|
+
* under {@link STORAGE_KEY_ROOT}: sinks may treat them as opaque strings, or
|
|
31
|
+
* route on prefixes (e.g. event chunks to object storage, everything else
|
|
32
|
+
* to a database). Channel ids and continuation tokens are the only segments
|
|
33
|
+
* that may contain caller-controlled characters; they are URI-encoded, and
|
|
34
|
+
* a segment whose encoding exceeds {@link MAX_KEY_SEGMENT_BYTES} is replaced
|
|
35
|
+
* by a `sha256:…` digest — so every minted key has a bounded length that any
|
|
36
|
+
* backend (VARCHAR columns, btree index tuples, S3 key limits) can store,
|
|
37
|
+
* no matter what a caller stuffs into a token. The substitution is
|
|
38
|
+
* deterministic: writes and continuation lookups build the same key.
|
|
39
|
+
*
|
|
40
|
+
* | Key | Value |
|
|
41
|
+
* | --- | --- |
|
|
42
|
+
* | `agentkit/v1/{agent}/session/{sessionId}` | `SessionRecord` |
|
|
43
|
+
* | `agentkit/v1/{agent}/session-events/{sessionId}/{index}` | `SessionEvent[]` chunk (index = first event's index, zero-padded) |
|
|
44
|
+
* | `agentkit/v1/{agent}/continuation/{channelId}/{token}` | `{ sessionId }` |
|
|
45
|
+
* | `agentkit/v1/{agent}/kv/{key}` | Author JSON via `HostContext.kv` |
|
|
46
|
+
*
|
|
47
|
+
* The `eval-runs` / `ab-samples` / `ab-snapshot` families are minted only
|
|
48
|
+
* by the tables `defineStorage` derives over the KV core; a backend that
|
|
49
|
+
* authors dedicated `StorageConfig.evals` / `StorageConfig.abs` groups never
|
|
50
|
+
* sees them.
|
|
51
|
+
*/
|
|
52
|
+
export const storageKeys = {
|
|
53
|
+
session: (agent: string, sessionId: string): string =>
|
|
54
|
+
`${STORAGE_KEY_ROOT}/${agent}/session/${sessionId}`,
|
|
55
|
+
sessionPrefix: (agent: string): string =>
|
|
56
|
+
`${STORAGE_KEY_ROOT}/${agent}/session/`,
|
|
57
|
+
sessionEvents: (
|
|
58
|
+
agent: string,
|
|
59
|
+
sessionId: string,
|
|
60
|
+
firstIndex: number
|
|
61
|
+
): string =>
|
|
62
|
+
`${STORAGE_KEY_ROOT}/${agent}/session-events/${sessionId}/${String(firstIndex).padStart(8, "0")}`,
|
|
63
|
+
sessionEventsPrefix: (agent: string, sessionId: string): string =>
|
|
64
|
+
`${STORAGE_KEY_ROOT}/${agent}/session-events/${sessionId}/`,
|
|
65
|
+
continuation: (
|
|
66
|
+
agent: string,
|
|
67
|
+
channelId: string,
|
|
68
|
+
continuationKey: string
|
|
69
|
+
): string =>
|
|
70
|
+
`${STORAGE_KEY_ROOT}/${agent}/continuation/${keySegment(channelId)}/${keySegment(continuationKey)}`,
|
|
71
|
+
/**
|
|
72
|
+
* Author-facing opaque JSON (see `HostContext.kv`). `key` is URI-encoded /
|
|
73
|
+
* digested like other caller-controlled segments.
|
|
74
|
+
*/
|
|
75
|
+
kv: (agent: string, key: string): string =>
|
|
76
|
+
`${STORAGE_KEY_ROOT}/${agent}/kv/${keySegment(key)}`,
|
|
77
|
+
kvPrefix: (agent: string): string => `${STORAGE_KEY_ROOT}/${agent}/kv/`,
|
|
78
|
+
/** One playground eval-run snapshot (derived `evals` table). */
|
|
79
|
+
evalRun: (agent: string, runId: string): string =>
|
|
80
|
+
`${STORAGE_KEY_ROOT}/${agent}/eval-runs/${keySegment(runId)}`,
|
|
81
|
+
evalRunsPrefix: (agent: string): string =>
|
|
82
|
+
`${STORAGE_KEY_ROOT}/${agent}/eval-runs/`,
|
|
83
|
+
/**
|
|
84
|
+
* One A/B metric sample (derived `abs` table); the segments mirror the
|
|
85
|
+
* dedicated table's row identity (experiment + sessionId + at).
|
|
86
|
+
*/
|
|
87
|
+
abSample: (
|
|
88
|
+
agent: string,
|
|
89
|
+
experiment: string,
|
|
90
|
+
sessionId: string,
|
|
91
|
+
at: string
|
|
92
|
+
): string =>
|
|
93
|
+
`${STORAGE_KEY_ROOT}/${agent}/ab-samples/${keySegment(experiment)}/${keySegment(sessionId)}/${keySegment(at)}`,
|
|
94
|
+
/** Latest aggregate A/B snapshot (derived `abs` table, LWW). */
|
|
95
|
+
abSnapshot: (agent: string): string =>
|
|
96
|
+
`${STORAGE_KEY_ROOT}/${agent}/ab-snapshot`,
|
|
97
|
+
} as const;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Max bytes a caller-controlled key segment may occupy after URI-encoding.
|
|
101
|
+
* Chosen so full keys stay well under common backend limits (Postgres btree
|
|
102
|
+
* index tuples cap at ~2704 bytes; S3 keys at 1024). Longer segments are
|
|
103
|
+
* replaced by their SHA-256 digest, keeping every minted key bounded.
|
|
104
|
+
*/
|
|
105
|
+
export const MAX_KEY_SEGMENT_BYTES = 256;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* URI-encode one caller-controlled key segment, substituting a `sha256:…`
|
|
109
|
+
* digest of the raw value when the encoding exceeds
|
|
110
|
+
* {@link MAX_KEY_SEGMENT_BYTES}. Deterministic, so key construction on the
|
|
111
|
+
* write path and the continuation-lookup path always agree.
|
|
112
|
+
*
|
|
113
|
+
* Exported for the session ledger, which builds its `session/` and
|
|
114
|
+
* `ledger/` keys with the same encoding rules.
|
|
115
|
+
*/
|
|
116
|
+
export function keySegment(raw: string): string {
|
|
117
|
+
const encoded = encodeURIComponent(raw);
|
|
118
|
+
if (Buffer.byteLength(encoded, "utf8") <= MAX_KEY_SEGMENT_BYTES) {
|
|
119
|
+
return encoded;
|
|
120
|
+
}
|
|
121
|
+
return `sha256:${createHash("sha256").update(raw, "utf8").digest("hex")}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function isDurableSessionRecordKey(key: string): boolean {
|
|
125
|
+
const match = DURABLE_SESSION_RECORD_KEY_RE.exec(key);
|
|
126
|
+
return match !== null && match[1] !== "_cp";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** List fields of a persisted `SessionRecord`. Unknown properties are dropped. */
|
|
130
|
+
export type DurableSessionRecord = {
|
|
131
|
+
sessionId: string;
|
|
132
|
+
channelId: string;
|
|
133
|
+
mode: string;
|
|
134
|
+
continuationKey: string | null | undefined;
|
|
135
|
+
title: string | undefined;
|
|
136
|
+
turnCount: number;
|
|
137
|
+
createdAt: string;
|
|
138
|
+
updatedAt: string;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Session metadata for history UIs. `running` is omitted unless a live
|
|
143
|
+
* overlay (the engine process) supplies it — a KV-only reader must not
|
|
144
|
+
* claim a turn is in flight.
|
|
145
|
+
*/
|
|
146
|
+
export type DurableSessionSummary = {
|
|
147
|
+
sessionId: string;
|
|
148
|
+
channelId: string;
|
|
149
|
+
mode: string;
|
|
150
|
+
title: string | undefined;
|
|
151
|
+
turnCount: number;
|
|
152
|
+
createdAt: string;
|
|
153
|
+
updatedAt: string;
|
|
154
|
+
running: boolean | undefined;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
158
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function stringValue(value: unknown): string | undefined {
|
|
162
|
+
return typeof value === "string" ? value : undefined;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Tolerant `SessionRecord` decoder. Extra fields (`auth`, `purpose`, ledger
|
|
167
|
+
* extras, future keys) are ignored. Returns undefined when required list
|
|
168
|
+
* fields are missing or the wrong type.
|
|
169
|
+
*/
|
|
170
|
+
export function parseDurableSessionRecord(
|
|
171
|
+
value: unknown
|
|
172
|
+
): DurableSessionRecord | undefined {
|
|
173
|
+
if (!isRecord(value)) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
const sessionId = stringValue(value.sessionId);
|
|
177
|
+
const channelId = stringValue(value.channelId);
|
|
178
|
+
const mode = stringValue(value.mode);
|
|
179
|
+
const turnCount =
|
|
180
|
+
typeof value.turnCount === "number" ? value.turnCount : undefined;
|
|
181
|
+
const createdAt = stringValue(value.createdAt);
|
|
182
|
+
const updatedAt = stringValue(value.updatedAt);
|
|
183
|
+
if (
|
|
184
|
+
sessionId === undefined ||
|
|
185
|
+
sessionId.length === 0 ||
|
|
186
|
+
channelId === undefined ||
|
|
187
|
+
mode === undefined ||
|
|
188
|
+
turnCount === undefined ||
|
|
189
|
+
createdAt === undefined ||
|
|
190
|
+
updatedAt === undefined
|
|
191
|
+
) {
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
194
|
+
if (
|
|
195
|
+
value.continuationKey !== undefined &&
|
|
196
|
+
value.continuationKey !== null &&
|
|
197
|
+
typeof value.continuationKey !== "string"
|
|
198
|
+
) {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
const continuationKey = value.continuationKey;
|
|
202
|
+
return {
|
|
203
|
+
sessionId,
|
|
204
|
+
channelId,
|
|
205
|
+
mode,
|
|
206
|
+
continuationKey,
|
|
207
|
+
title: typeof value.title === "string" ? value.title : undefined,
|
|
208
|
+
turnCount,
|
|
209
|
+
createdAt,
|
|
210
|
+
updatedAt,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function summarizeDurableSessionRecord(args: {
|
|
215
|
+
record: DurableSessionRecord;
|
|
216
|
+
running?: boolean;
|
|
217
|
+
}): DurableSessionSummary {
|
|
218
|
+
const { record, running } = args;
|
|
219
|
+
return {
|
|
220
|
+
sessionId: record.sessionId,
|
|
221
|
+
channelId: record.channelId,
|
|
222
|
+
mode: record.mode,
|
|
223
|
+
title: record.title,
|
|
224
|
+
turnCount: record.turnCount,
|
|
225
|
+
createdAt: record.createdAt,
|
|
226
|
+
updatedAt: record.updatedAt,
|
|
227
|
+
running,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Golden persisted record: required list fields plus extras a future engine
|
|
233
|
+
* might write. Both this package and the CP decoder must accept it.
|
|
234
|
+
*/
|
|
235
|
+
export const GOLDEN_DURABLE_SESSION_RECORD: Record<string, unknown> = {
|
|
236
|
+
sessionId: "ses_aaaaaaaaaaaaaaaaaaaaaaaa",
|
|
237
|
+
channelId: "playground",
|
|
238
|
+
mode: "chat",
|
|
239
|
+
purpose: "live",
|
|
240
|
+
continuationKey: "cont_key_1",
|
|
241
|
+
auth: { kind: "user", userId: 42 },
|
|
242
|
+
title: "Weather in NYC",
|
|
243
|
+
turnCount: 3,
|
|
244
|
+
createdAt: "2026-08-01T12:00:00.000Z",
|
|
245
|
+
updatedAt: "2026-08-01T12:05:00.000Z",
|
|
246
|
+
extraUnknownField: { nested: true },
|
|
247
|
+
};
|
package/src/storage.ts
CHANGED
|
@@ -52,13 +52,28 @@
|
|
|
52
52
|
* interface is the durable mirror.
|
|
53
53
|
*/
|
|
54
54
|
|
|
55
|
-
import { createHash } from "node:crypto";
|
|
56
55
|
import type { ABMetricSample } from "./ab.js";
|
|
57
56
|
import type { EvalRunSnapshot } from "./evals.js";
|
|
58
57
|
import type { ABSnapshot } from "./internal/ab-snapshot.js";
|
|
59
58
|
import { brandDefinition } from "./internal/brand.js";
|
|
59
|
+
import { storageKeys } from "./storage-protocol.js";
|
|
60
60
|
import type { JsonValue, KvCasResult, KvVersionedEntry } from "./types.js";
|
|
61
61
|
|
|
62
|
+
export type {
|
|
63
|
+
DurableSessionRecord,
|
|
64
|
+
DurableSessionSummary,
|
|
65
|
+
} from "./storage-protocol.js";
|
|
66
|
+
export {
|
|
67
|
+
GOLDEN_DURABLE_SESSION_RECORD,
|
|
68
|
+
isDurableSessionRecordKey,
|
|
69
|
+
keySegment,
|
|
70
|
+
MAX_KEY_SEGMENT_BYTES,
|
|
71
|
+
parseDurableSessionRecord,
|
|
72
|
+
STORAGE_KEY_ROOT,
|
|
73
|
+
storageKeys,
|
|
74
|
+
summarizeDurableSessionRecord,
|
|
75
|
+
} from "./storage-protocol.js";
|
|
76
|
+
|
|
62
77
|
// ============================================================================
|
|
63
78
|
// Sink interface
|
|
64
79
|
// ============================================================================
|
|
@@ -424,115 +439,6 @@ function deriveAbsTable(config: StorageConfig): StorageABTable {
|
|
|
424
439
|
};
|
|
425
440
|
}
|
|
426
441
|
|
|
427
|
-
// ============================================================================
|
|
428
|
-
// Key scheme
|
|
429
|
-
// ============================================================================
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Framework-owned key root. All {@link storageKeys} values live under this
|
|
433
|
-
* prefix so a shared store can route or namespace Agent SDK data.
|
|
434
|
-
*/
|
|
435
|
-
export const STORAGE_KEY_ROOT = "agentkit/v1" as const;
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* The framework-owned key scheme. Keys are a **stable, versioned contract**
|
|
439
|
-
* under {@link STORAGE_KEY_ROOT}: sinks may treat them as opaque strings, or
|
|
440
|
-
* route on prefixes (e.g. event chunks to object storage, everything else
|
|
441
|
-
* to a database). Channel ids and continuation tokens are the only segments
|
|
442
|
-
* that may contain caller-controlled characters; they are URI-encoded, and
|
|
443
|
-
* a segment whose encoding exceeds {@link MAX_KEY_SEGMENT_BYTES} is replaced
|
|
444
|
-
* by a `sha256:…` digest — so every minted key has a bounded length that any
|
|
445
|
-
* backend (VARCHAR columns, btree index tuples, S3 key limits) can store,
|
|
446
|
-
* no matter what a caller stuffs into a token. The substitution is
|
|
447
|
-
* deterministic: writes and continuation lookups build the same key.
|
|
448
|
-
*
|
|
449
|
-
* | Key | Value |
|
|
450
|
-
* | --- | --- |
|
|
451
|
-
* | `agentkit/v1/{agent}/session/{sessionId}` | `SessionRecord` |
|
|
452
|
-
* | `agentkit/v1/{agent}/session-events/{sessionId}/{index}` | `SessionEvent[]` chunk (index = first event's index, zero-padded) |
|
|
453
|
-
* | `agentkit/v1/{agent}/continuation/{channelId}/{token}` | `{ sessionId }` |
|
|
454
|
-
* | `agentkit/v1/{agent}/kv/{key}` | Author JSON via {@link HostContext.kv} |
|
|
455
|
-
* | `agentkit/v1/{agent}/eval-runs/{runId}` | `EvalRunSnapshot` (derived `evals` table) |
|
|
456
|
-
* | `agentkit/v1/{agent}/ab-samples/{experiment}/{sessionId}/{at}` | `ABMetricSample` (derived `abs` table, append-only) |
|
|
457
|
-
* | `agentkit/v1/{agent}/ab-snapshot` | `ABSnapshot` (derived `abs` table, LWW) |
|
|
458
|
-
*
|
|
459
|
-
* The `eval-runs` / `ab-samples` / `ab-snapshot` families are minted only
|
|
460
|
-
* by the tables `defineStorage` derives over the KV core; a backend that
|
|
461
|
-
* authors dedicated {@link StorageConfig.evals} / {@link StorageConfig.abs}
|
|
462
|
-
* groups never sees them.
|
|
463
|
-
*/
|
|
464
|
-
export const storageKeys = {
|
|
465
|
-
session: (agent: string, sessionId: string): string =>
|
|
466
|
-
`${STORAGE_KEY_ROOT}/${agent}/session/${sessionId}`,
|
|
467
|
-
sessionPrefix: (agent: string): string =>
|
|
468
|
-
`${STORAGE_KEY_ROOT}/${agent}/session/`,
|
|
469
|
-
sessionEvents: (
|
|
470
|
-
agent: string,
|
|
471
|
-
sessionId: string,
|
|
472
|
-
firstIndex: number
|
|
473
|
-
): string =>
|
|
474
|
-
`${STORAGE_KEY_ROOT}/${agent}/session-events/${sessionId}/${String(firstIndex).padStart(8, "0")}`,
|
|
475
|
-
sessionEventsPrefix: (agent: string, sessionId: string): string =>
|
|
476
|
-
`${STORAGE_KEY_ROOT}/${agent}/session-events/${sessionId}/`,
|
|
477
|
-
continuation: (
|
|
478
|
-
agent: string,
|
|
479
|
-
channelId: string,
|
|
480
|
-
continuationKey: string
|
|
481
|
-
): string =>
|
|
482
|
-
`${STORAGE_KEY_ROOT}/${agent}/continuation/${keySegment(channelId)}/${keySegment(continuationKey)}`,
|
|
483
|
-
/**
|
|
484
|
-
* Author-facing opaque JSON (see {@link HostContext.kv}). `key` is
|
|
485
|
-
* URI-encoded / digested like other caller-controlled segments.
|
|
486
|
-
*/
|
|
487
|
-
kv: (agent: string, key: string): string =>
|
|
488
|
-
`${STORAGE_KEY_ROOT}/${agent}/kv/${keySegment(key)}`,
|
|
489
|
-
kvPrefix: (agent: string): string => `${STORAGE_KEY_ROOT}/${agent}/kv/`,
|
|
490
|
-
/** One playground eval-run snapshot (derived `evals` table). */
|
|
491
|
-
evalRun: (agent: string, runId: string): string =>
|
|
492
|
-
`${STORAGE_KEY_ROOT}/${agent}/eval-runs/${keySegment(runId)}`,
|
|
493
|
-
evalRunsPrefix: (agent: string): string =>
|
|
494
|
-
`${STORAGE_KEY_ROOT}/${agent}/eval-runs/`,
|
|
495
|
-
/**
|
|
496
|
-
* One A/B metric sample (derived `abs` table); the segments mirror the
|
|
497
|
-
* dedicated table's row identity (experiment + sessionId + at).
|
|
498
|
-
*/
|
|
499
|
-
abSample: (
|
|
500
|
-
agent: string,
|
|
501
|
-
experiment: string,
|
|
502
|
-
sessionId: string,
|
|
503
|
-
at: string
|
|
504
|
-
): string =>
|
|
505
|
-
`${STORAGE_KEY_ROOT}/${agent}/ab-samples/${keySegment(experiment)}/${keySegment(sessionId)}/${keySegment(at)}`,
|
|
506
|
-
/** Latest aggregate A/B snapshot (derived `abs` table, LWW). */
|
|
507
|
-
abSnapshot: (agent: string): string =>
|
|
508
|
-
`${STORAGE_KEY_ROOT}/${agent}/ab-snapshot`,
|
|
509
|
-
} as const;
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Max bytes a caller-controlled key segment may occupy after URI-encoding.
|
|
513
|
-
* Chosen so full keys stay well under common backend limits (Postgres btree
|
|
514
|
-
* index tuples cap at ~2704 bytes; S3 keys at 1024). Longer segments are
|
|
515
|
-
* replaced by their SHA-256 digest, keeping every minted key bounded.
|
|
516
|
-
*/
|
|
517
|
-
export const MAX_KEY_SEGMENT_BYTES = 256;
|
|
518
|
-
|
|
519
|
-
/**
|
|
520
|
-
* URI-encode one caller-controlled key segment, substituting a `sha256:…`
|
|
521
|
-
* digest of the raw value when the encoding exceeds
|
|
522
|
-
* {@link MAX_KEY_SEGMENT_BYTES}. Deterministic, so key construction on the
|
|
523
|
-
* write path and the continuation-lookup path always agree.
|
|
524
|
-
*
|
|
525
|
-
* Exported for the session ledger, which builds its `session/` and
|
|
526
|
-
* `ledger/` keys with the same encoding rules.
|
|
527
|
-
*/
|
|
528
|
-
export function keySegment(raw: string): string {
|
|
529
|
-
const encoded = encodeURIComponent(raw);
|
|
530
|
-
if (Buffer.byteLength(encoded, "utf8") <= MAX_KEY_SEGMENT_BYTES) {
|
|
531
|
-
return encoded;
|
|
532
|
-
}
|
|
533
|
-
return `sha256:${createHash("sha256").update(raw, "utf8").digest("hex")}`;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
442
|
// ============================================================================
|
|
537
443
|
// Policy resolution
|
|
538
444
|
// ============================================================================
|
package/src/types.ts
CHANGED
|
@@ -514,9 +514,11 @@ export interface HostContext {
|
|
|
514
514
|
*/
|
|
515
515
|
reminders?: ReminderHostApi;
|
|
516
516
|
/**
|
|
517
|
-
* Slack ask-dedupe /
|
|
518
|
-
* {@link SessionEngine} host.
|
|
519
|
-
*
|
|
517
|
+
* Slack ask-dedupe / nudge helpers. Always present on a live
|
|
518
|
+
* {@link SessionEngine} host. A busy Slack thread's follow-ups queue here
|
|
519
|
+
* and reach the running agent at its next tool boundary, via the workspace
|
|
520
|
+
* carrier hook; whatever the carrier does not reach in time still drains
|
|
521
|
+
* into the boundary turn, deduped by source ts.
|
|
520
522
|
*/
|
|
521
523
|
slackNudges?: SlackNudgeHostApi;
|
|
522
524
|
/**
|
|
@@ -1078,6 +1080,76 @@ export type ConnectionConfig = (
|
|
|
1078
1080
|
* cloud-native account connectors provisioned by the platform.
|
|
1079
1081
|
*/
|
|
1080
1082
|
hostOnly?: boolean;
|
|
1083
|
+
/**
|
|
1084
|
+
* Per-session auth: called at turn-build time with the session's
|
|
1085
|
+
* {@link SessionInfo}, returning headers merged over the transport's
|
|
1086
|
+
* static `headers` for that session's use of this connection. This is
|
|
1087
|
+
* how a multi-tenant agent injects the caller's identity — it derives
|
|
1088
|
+
* who the session is for host-side and mints a tenant-scoped
|
|
1089
|
+
* credential; the model never sees a tenant parameter and can never
|
|
1090
|
+
* choose the tenant.
|
|
1091
|
+
*
|
|
1092
|
+
* Derive the identity from durable session facts — `session.auth`,
|
|
1093
|
+
* `session.id`, or your channel's own session state. Do **not** key it
|
|
1094
|
+
* off `session.continuationKey`: the HTTP channel rotates the
|
|
1095
|
+
* continuation key after every accepted follow-up, so a tenant mapping
|
|
1096
|
+
* keyed on it silently breaks mid-conversation. (Channels that mint
|
|
1097
|
+
* stable, parseable tokens by design are the exception.)
|
|
1098
|
+
*
|
|
1099
|
+
* Works attached or advertised. Advertised connections
|
|
1100
|
+
* ({@link advertiseTools}) open per-operation clients with the
|
|
1101
|
+
* evaluated headers. Attached connections ride the turn's SDK
|
|
1102
|
+
* `mcpServers`, passed on every send rather than pinned on the cached
|
|
1103
|
+
* per-session agent handle, so every turn — follow-ups, reminder
|
|
1104
|
+
* wakes — runs on headers evaluated for that turn. The cost: when any
|
|
1105
|
+
* attached connection has `auth`, *all* of the agent's attached
|
|
1106
|
+
* connections are configured per send, so the harness opens fresh MCP
|
|
1107
|
+
* clients for them each turn (stdio servers respawn per turn — avoid
|
|
1108
|
+
* pairing an auth'd connection with a stateful stdio server), and
|
|
1109
|
+
* workspace prewarm omits auth'd connections rather than attaching
|
|
1110
|
+
* them without an identity.
|
|
1111
|
+
*
|
|
1112
|
+
* HTTP/SSE transports only, not combinable with `oauth: true`
|
|
1113
|
+
* (discovery rejects both). A callback that throws fails the turn: a
|
|
1114
|
+
* turn never silently runs without the connection's identity. Cloud
|
|
1115
|
+
* turns are refused (cloud-runtime agents fail discovery). `host.mcp`
|
|
1116
|
+
* calls from server tools keep the static headers only.
|
|
1117
|
+
*/
|
|
1118
|
+
auth?: ConnectionAuthProvider;
|
|
1119
|
+
/**
|
|
1120
|
+
* Advertise this connection's tools by name instead of leaving them
|
|
1121
|
+
* behind the MCP meta-tools: at turn-build time the engine lists the
|
|
1122
|
+
* connection's tools and synthesizes a named 1:1 passthrough server
|
|
1123
|
+
* tool per entry — the server's own names, descriptions, and live
|
|
1124
|
+
* schemas — merged into the authored `agent/tools/` catalog. The raw
|
|
1125
|
+
* connection is then not attached to the model (it stays available on
|
|
1126
|
+
* `host.mcp`), so each tool appears exactly once.
|
|
1127
|
+
*
|
|
1128
|
+
* Fail-closed: a `listTools` failure, an invalid tool name, or a name
|
|
1129
|
+
* collision (with an authored tool or another advertised connection)
|
|
1130
|
+
* fails the turn — a turn never silently runs with a shrunken toolset.
|
|
1131
|
+
* Local runtime only, like all server tools: a `runtime: "cloud"` agent
|
|
1132
|
+
* fails discovery, and hybrid per-send cloud sessions refuse the turn.
|
|
1133
|
+
* Not combinable with {@link hostOnly} (advertising puts the tools in
|
|
1134
|
+
* the model's toolset; hostOnly means off the model — discovery rejects
|
|
1135
|
+
* the pair). The listing runs once per turn; point this at endpoints
|
|
1136
|
+
* that can answer `tools/list` cheaply.
|
|
1137
|
+
*/
|
|
1138
|
+
advertiseTools?: boolean;
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
/**
|
|
1142
|
+
* Per-session connection auth on {@link ConnectionConfig.auth}: session →
|
|
1143
|
+
* headers merged over the transport's static headers for that session's
|
|
1144
|
+
* turns. See the field docs for the fail-closed semantics.
|
|
1145
|
+
*/
|
|
1146
|
+
export type ConnectionAuthProvider = (
|
|
1147
|
+
session: SessionInfo
|
|
1148
|
+
) => ConnectionAuthResult | Promise<ConnectionAuthResult>;
|
|
1149
|
+
|
|
1150
|
+
/** What a {@link ConnectionAuthProvider} returns. */
|
|
1151
|
+
export type ConnectionAuthResult = {
|
|
1152
|
+
headers: Record<string, string>;
|
|
1081
1153
|
};
|
|
1082
1154
|
|
|
1083
1155
|
/** True when the connection authors tools instead of a spawn/URL/peer. */
|
|
@@ -1271,7 +1343,27 @@ export type SessionEventPayload =
|
|
|
1271
1343
|
type: "turn.completed";
|
|
1272
1344
|
data: { result?: string; usage?: TurnUsage; cost?: TurnCost };
|
|
1273
1345
|
}
|
|
1346
|
+
| {
|
|
1347
|
+
/**
|
|
1348
|
+
* The harness accepted the turn and named the run. Emitted once the
|
|
1349
|
+
* send lands, so a channel can show the request id while the turn is
|
|
1350
|
+
* still going rather than after it. Quote it verbatim when correlating
|
|
1351
|
+
* with backend logs — never abbreviated.
|
|
1352
|
+
*/
|
|
1353
|
+
type: "turn.bound";
|
|
1354
|
+
data: { requestId: string };
|
|
1355
|
+
}
|
|
1274
1356
|
| { type: "turn.failed"; data: { message: string } }
|
|
1357
|
+
| {
|
|
1358
|
+
/**
|
|
1359
|
+
* A Slack follow-up queued mid-turn reached the running agent: the
|
|
1360
|
+
* workspace carrier moved it from `pending/` to `delivered/` at a tool
|
|
1361
|
+
* boundary. Channels use this to reposition a live progress message
|
|
1362
|
+
* below the follow-up, which Slack cannot do any other way.
|
|
1363
|
+
*/
|
|
1364
|
+
type: "nudge.delivered";
|
|
1365
|
+
data: Record<string, never>;
|
|
1366
|
+
}
|
|
1275
1367
|
| { type: "session.waiting"; data: Record<string, never> }
|
|
1276
1368
|
| { type: "session.completed"; data: Record<string, never> }
|
|
1277
1369
|
| { type: "session.failed"; data: { message: string } };
|
|
@@ -2388,6 +2480,10 @@ export interface DiscoveredConnection {
|
|
|
2388
2480
|
transport: ConnectionTransport;
|
|
2389
2481
|
description?: string;
|
|
2390
2482
|
hostOnly?: boolean;
|
|
2483
|
+
/** Per-session auth from {@link ConnectionConfig.auth} (http/sse only). */
|
|
2484
|
+
auth?: ConnectionAuthProvider;
|
|
2485
|
+
/** Advertise-by-name from {@link ConnectionConfig.advertiseTools}. */
|
|
2486
|
+
advertiseTools?: boolean;
|
|
2391
2487
|
}
|
|
2392
2488
|
|
|
2393
2489
|
export interface DiscoveredChannel {
|
|
@@ -2624,10 +2720,28 @@ export interface RunnerTurnRequest {
|
|
|
2624
2720
|
customTools: Record<string, SDKCustomTool>;
|
|
2625
2721
|
/** MCP servers from `agent/mcp-connections/`. */
|
|
2626
2722
|
mcpServers: Record<string, ConnectionTransport>;
|
|
2723
|
+
/**
|
|
2724
|
+
* Configure {@link mcpServers} on every send instead of once on the
|
|
2725
|
+
* cached SDK agent handle. Set when an attached connection has
|
|
2726
|
+
* per-session `auth`: its headers are re-evaluated at turn-build time,
|
|
2727
|
+
* and a handle-pinned copy would keep the first turn's credentials until
|
|
2728
|
+
* the handle recycles. The SDK builds the send's MCP session from exactly
|
|
2729
|
+
* this set (fresh clients each turn — stdio servers respawn per turn),
|
|
2730
|
+
* and the create/resume/prewarm options carry no `mcpServers` at all, so
|
|
2731
|
+
* the local executor cache key stays stable while credentials rotate.
|
|
2732
|
+
* Local runtime only; cloud turns with such connections are refused.
|
|
2733
|
+
*/
|
|
2734
|
+
perSendMcpServers?: boolean;
|
|
2627
2735
|
subagents: RunnerSubagent[];
|
|
2628
2736
|
onUpdate: (update: RunnerUpdate) => void | Promise<void>;
|
|
2629
2737
|
/** Called once the SDK agent is open so the engine can persist its id. */
|
|
2630
2738
|
onSdkAgentId?: (sdkAgentId: string) => void | Promise<void>;
|
|
2739
|
+
/**
|
|
2740
|
+
* Called once the harness names the run, before it produces anything.
|
|
2741
|
+
* Surfaces the request id while the turn is still running rather than at
|
|
2742
|
+
* its end.
|
|
2743
|
+
*/
|
|
2744
|
+
onRequestId?: (requestId: string) => void | Promise<void>;
|
|
2631
2745
|
/**
|
|
2632
2746
|
* Abort the in-flight run when signalled. The engine aborts this when a
|
|
2633
2747
|
* follow-up message preempts the current turn; the runner maps it to
|