@cursor/july 0.1.61 → 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.
Files changed (196) hide show
  1. package/dist/bin/agent-serve.js +8 -0
  2. package/dist/channels/github/github-channel.d.ts +1 -1
  3. package/dist/channels/github/github-channel.d.ts.map +1 -1
  4. package/dist/channels/github/github-channel.js +14 -1
  5. package/dist/channels/github/types.d.ts +2 -0
  6. package/dist/channels/github/types.d.ts.map +1 -1
  7. package/dist/channels/slack/channel-watch.d.ts +2 -1
  8. package/dist/channels/slack/channel-watch.d.ts.map +1 -1
  9. package/dist/channels/slack/channel-watch.js +7 -3
  10. package/dist/channels/slack/dispatch.d.ts +9 -0
  11. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  12. package/dist/channels/slack/dispatch.js +41 -21
  13. package/dist/channels/slack/manifest.d.ts.map +1 -1
  14. package/dist/channels/slack/manifest.js +1 -0
  15. package/dist/channels/slack/nudge-store.d.ts +16 -0
  16. package/dist/channels/slack/nudge-store.d.ts.map +1 -1
  17. package/dist/channels/slack/nudge-store.js +53 -0
  18. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  19. package/dist/channels/slack/slack-channel.js +4 -1
  20. package/dist/channels/slack/types.d.ts +22 -5
  21. package/dist/channels/slack/types.d.ts.map +1 -1
  22. package/dist/docs/404.html +2 -2
  23. package/dist/docs/ab.html +3 -3
  24. package/dist/docs/assets/{app.CW3KJh72.js → app.C5XUTCw9.js} +1 -1
  25. package/dist/docs/assets/chunks/@localSearchIndexroot.DvUR3lv9.js +1 -0
  26. package/dist/docs/assets/chunks/{VPLocalSearchBox.Bk6EWpPj.js → VPLocalSearchBox.CGDTAYHY.js} +1 -1
  27. package/dist/docs/assets/chunks/{theme.Bz9AodBA.js → theme.lPixh250.js} +2 -2
  28. package/dist/docs/assets/{reference_connections.md.CSQiNzre.js → reference_connections.md.Bwswbm6H.js} +13 -3
  29. package/dist/docs/assets/{reference_connections.md.CSQiNzre.lean.js → reference_connections.md.Bwswbm6H.lean.js} +1 -1
  30. package/dist/docs/assets/{reference_tools.md.kJfJAP2m.js → reference_tools.md.Wmvb7Lob.js} +2 -2
  31. package/dist/docs/assets/{reference_tools.md.kJfJAP2m.lean.js → reference_tools.md.Wmvb7Lob.lean.js} +1 -1
  32. package/dist/docs/building-with-agents.html +3 -3
  33. package/dist/docs/concepts.html +3 -3
  34. package/dist/docs/deployment.html +3 -3
  35. package/dist/docs/evals.html +3 -3
  36. package/dist/docs/example-agents/approval-buddy.html +3 -3
  37. package/dist/docs/example-agents/benny.html +3 -3
  38. package/dist/docs/example-agents/bugbot.html +3 -3
  39. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  40. package/dist/docs/example-agents/codeowners-review.html +3 -3
  41. package/dist/docs/example-agents/concierge.html +3 -3
  42. package/dist/docs/example-agents/fsd.html +3 -3
  43. package/dist/docs/example-agents/index.html +3 -3
  44. package/dist/docs/example-agents/knowledge-base.html +3 -3
  45. package/dist/docs/example-agents/oncall.html +3 -3
  46. package/dist/docs/example-agents/security-reviewer.html +3 -3
  47. package/dist/docs/example-agents/slack-agent.html +3 -3
  48. package/dist/docs/example-agents/weather-agent.html +3 -3
  49. package/dist/docs/guides/agent-to-agent.html +3 -3
  50. package/dist/docs/guides/cloud-runtime.html +3 -3
  51. package/dist/docs/guides/convert-automation.html +3 -3
  52. package/dist/docs/guides/github.html +3 -3
  53. package/dist/docs/guides/human-in-the-loop.html +3 -3
  54. package/dist/docs/guides/mcp-oauth.html +3 -3
  55. package/dist/docs/guides/opentelemetry.html +3 -3
  56. package/dist/docs/guides/slack.html +3 -3
  57. package/dist/docs/guides/webhooks.html +3 -3
  58. package/dist/docs/hashmap.json +1 -1
  59. package/dist/docs/hillclimbing.html +3 -3
  60. package/dist/docs/index.html +3 -3
  61. package/dist/docs/quickstart.html +3 -3
  62. package/dist/docs/reference/agent-config.html +3 -3
  63. package/dist/docs/reference/artifacts.html +3 -3
  64. package/dist/docs/reference/channels.html +3 -3
  65. package/dist/docs/reference/cli.html +3 -3
  66. package/dist/docs/reference/connections.html +15 -5
  67. package/dist/docs/reference/hooks.html +3 -3
  68. package/dist/docs/reference/http-api.html +3 -3
  69. package/dist/docs/reference/instructions.html +3 -3
  70. package/dist/docs/reference/playground.html +3 -3
  71. package/dist/docs/reference/project-layout.html +3 -3
  72. package/dist/docs/reference/prompt.html +3 -3
  73. package/dist/docs/reference/schedules.html +3 -3
  74. package/dist/docs/reference/sessions.html +3 -3
  75. package/dist/docs/reference/skills.html +3 -3
  76. package/dist/docs/reference/subagents.html +3 -3
  77. package/dist/docs/reference/tools.html +5 -5
  78. package/dist/docs/scaffolding-agents.html +3 -3
  79. package/dist/docs/storage.html +3 -3
  80. package/dist/docs/templates/agentic-owners.html +3 -3
  81. package/dist/docs/templates/demo.html +3 -3
  82. package/dist/docs/templates/pr-autofixer.html +3 -3
  83. package/dist/docs/templates/security-reviewer.html +3 -3
  84. package/dist/docs/templates/triage.html +3 -3
  85. package/dist/docs/troubleshooting.html +3 -3
  86. package/dist/files-backends/agent-store-presigned-url.js +1 -1
  87. package/dist/index.d.ts +2 -2
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +1 -1
  90. package/dist/internal/advertise-tools.d.ts +74 -0
  91. package/dist/internal/advertise-tools.d.ts.map +1 -0
  92. package/dist/internal/advertise-tools.js +152 -0
  93. package/dist/internal/cli-deploy.d.ts.map +1 -1
  94. package/dist/internal/cli-deploy.js +11 -3
  95. package/dist/internal/cli-manifest.d.ts +19 -0
  96. package/dist/internal/cli-manifest.d.ts.map +1 -0
  97. package/dist/internal/cli-manifest.js +49 -0
  98. package/dist/internal/cli-slack.d.ts.map +1 -1
  99. package/dist/internal/cli-slack.js +9 -1
  100. package/dist/internal/cursor-agent-template.d.ts +9 -0
  101. package/dist/internal/cursor-agent-template.d.ts.map +1 -0
  102. package/dist/internal/cursor-agent-template.js +37 -0
  103. package/dist/internal/cursor-event-relay.d.ts +5 -1
  104. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  105. package/dist/internal/cursor-event-relay.js +5 -0
  106. package/dist/internal/deploy-client.d.ts +8 -0
  107. package/dist/internal/deploy-client.d.ts.map +1 -1
  108. package/dist/internal/deploy-client.js +21 -3
  109. package/dist/internal/deploy-manifest.d.ts +85 -0
  110. package/dist/internal/deploy-manifest.d.ts.map +1 -0
  111. package/dist/internal/deploy-manifest.js +106 -0
  112. package/dist/internal/deployment-scm-event-stream.d.ts +113 -0
  113. package/dist/internal/deployment-scm-event-stream.d.ts.map +1 -0
  114. package/dist/internal/deployment-scm-event-stream.js +460 -0
  115. package/dist/internal/discovery.d.ts.map +1 -1
  116. package/dist/internal/discovery.js +69 -0
  117. package/dist/internal/init-project.d.ts.map +1 -1
  118. package/dist/internal/init-project.js +5 -17
  119. package/dist/internal/mcp-host.d.ts +18 -0
  120. package/dist/internal/mcp-host.d.ts.map +1 -1
  121. package/dist/internal/mcp-host.js +41 -5
  122. package/dist/internal/mcp-oauth.d.ts +0 -4
  123. package/dist/internal/mcp-oauth.d.ts.map +1 -1
  124. package/dist/internal/mcp-oauth.js +0 -4
  125. package/dist/internal/sdk-runner.d.ts +1 -1
  126. package/dist/internal/sdk-runner.d.ts.map +1 -1
  127. package/dist/internal/sdk-runner.js +36 -13
  128. package/dist/internal/server.d.ts.map +1 -1
  129. package/dist/internal/server.js +20 -2
  130. package/dist/internal/session-engine.d.ts +66 -0
  131. package/dist/internal/session-engine.d.ts.map +1 -1
  132. package/dist/internal/session-engine.js +430 -110
  133. package/dist/internal/shallow-clone.d.ts +115 -0
  134. package/dist/internal/shallow-clone.d.ts.map +1 -0
  135. package/dist/internal/shallow-clone.js +292 -0
  136. package/dist/internal/slack-provision-client.d.ts +2 -0
  137. package/dist/internal/slack-provision-client.d.ts.map +1 -1
  138. package/dist/internal/slack-provision-client.js +1 -0
  139. package/dist/internal/turn-lease-store.d.ts +44 -0
  140. package/dist/internal/turn-lease-store.d.ts.map +1 -0
  141. package/dist/internal/turn-lease-store.js +139 -0
  142. package/dist/internal/workspace.d.ts +12 -0
  143. package/dist/internal/workspace.d.ts.map +1 -1
  144. package/dist/internal/workspace.js +28 -0
  145. package/dist/playground/assets/index-CoL7xCJl.css +1 -0
  146. package/dist/playground/assets/index-yxs1heea.js +85 -0
  147. package/dist/playground/index.html +2 -2
  148. package/dist/storage-protocol.d.ts +122 -0
  149. package/dist/storage-protocol.d.ts.map +1 -0
  150. package/dist/storage-protocol.js +173 -0
  151. package/dist/storage.d.ts +2 -72
  152. package/dist/storage.d.ts.map +1 -1
  153. package/dist/storage.js +2 -82
  154. package/dist/types.d.ts +114 -4
  155. package/dist/types.d.ts.map +1 -1
  156. package/docs/reference/connections.md +80 -0
  157. package/docs/reference/tools.md +14 -0
  158. package/package.json +4 -2
  159. package/skills/github/SKILL.md +2 -1
  160. package/src/bin/agent-serve.ts +9 -0
  161. package/src/channels/github/github-channel.ts +18 -2
  162. package/src/channels/github/types.ts +2 -0
  163. package/src/channels/slack/channel-watch.ts +6 -3
  164. package/src/channels/slack/dispatch.ts +40 -3
  165. package/src/channels/slack/manifest.ts +1 -0
  166. package/src/channels/slack/nudge-store.ts +56 -0
  167. package/src/channels/slack/slack-channel.ts +3 -0
  168. package/src/channels/slack/types.ts +22 -5
  169. package/src/index.ts +5 -0
  170. package/src/internal/advertise-tools.ts +229 -0
  171. package/src/internal/cli-deploy.ts +22 -1
  172. package/src/internal/cli-manifest.ts +63 -0
  173. package/src/internal/cli-slack.ts +10 -1
  174. package/src/internal/cursor-agent-template.ts +46 -0
  175. package/src/internal/cursor-event-relay.ts +8 -1
  176. package/src/internal/deploy-client.ts +35 -6
  177. package/src/internal/deploy-manifest.ts +167 -0
  178. package/src/internal/deployment-scm-event-stream.ts +577 -0
  179. package/src/internal/discovery.ts +85 -0
  180. package/src/internal/init-project.ts +11 -20
  181. package/src/internal/mcp-host.ts +66 -9
  182. package/src/internal/mcp-oauth.ts +0 -4
  183. package/src/internal/sdk-runner.ts +50 -11
  184. package/src/internal/server.ts +21 -2
  185. package/src/internal/session-engine.ts +385 -19
  186. package/src/internal/shallow-clone.ts +407 -0
  187. package/src/internal/slack-provision-client.ts +3 -0
  188. package/src/internal/turn-lease-store.ts +147 -0
  189. package/src/internal/workspace.ts +27 -0
  190. package/src/storage-protocol.ts +247 -0
  191. package/src/storage.ts +16 -110
  192. package/src/types.ts +118 -4
  193. package/templates/pr-autofixer/agent/lib/drive-schemas.ts +4 -1
  194. package/dist/docs/assets/chunks/@localSearchIndexroot.BuymfBAo.js +0 -1
  195. package/dist/playground/assets/index-ClbuaLpm.css +0 -1
  196. package/dist/playground/assets/index-SqkR13k_.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 / optional nudge helpers. Always present on a live
518
- * {@link SessionEngine} host. Mid-turn nudge inject is not used; busy
519
- * Slack follow-ups coalesce and drain at turn boundaries.
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
@@ -2752,7 +2866,7 @@ export interface ServeOptions {
2752
2866
  */
2753
2867
  cloudToolsUrl?: string;
2754
2868
  /**
2755
- * Pull SCM webhook metadata from `/v0/scm-events` (Cursor account auth).
2869
+ * Pull SCM webhook events from `/v0/scm-events` (Cursor account auth).
2756
2870
  * GitHub and Origin subscriptions are registered independently.
2757
2871
  * The stream is read as the host's Cursor user, so this requires a signed-in
2758
2872
  * host: `serve` throws when no key resolves from `apiKey` here,
@@ -1,6 +1,9 @@
1
1
  import { z } from "zod";
2
2
 
3
- export const driveStartBodySchema = z.object({
3
+ export const driveStartBodySchema: z.ZodObject<{
4
+ pr: z.ZodString;
5
+ hint: z.ZodOptional<z.ZodString>;
6
+ }> = z.object({
4
7
  pr: z
5
8
  .string()
6
9
  .describe(