@cursor/july 0.1.39 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -3
- package/dist/bin/agent-serve.js +28 -9
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +18 -8
- package/dist/channels/slack/attachments.d.ts +82 -0
- package/dist/channels/slack/attachments.d.ts.map +1 -0
- package/dist/channels/slack/attachments.js +520 -0
- package/dist/channels/slack/cursor-account.d.ts +7 -1
- package/dist/channels/slack/cursor-account.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.js +2 -2
- package/dist/channels/slack/defaults.d.ts.map +1 -1
- package/dist/channels/slack/defaults.js +26 -17
- package/dist/channels/slack/dispatch.d.ts +4 -3
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +84 -21
- package/dist/channels/slack/inbound.d.ts +6 -0
- package/dist/channels/slack/inbound.d.ts.map +1 -1
- package/dist/channels/slack/inbound.js +48 -2
- package/dist/channels/slack/index.d.ts +1 -0
- package/dist/channels/slack/index.d.ts.map +1 -1
- package/dist/channels/slack/index.js +1 -0
- package/dist/channels/slack/live-delivery.d.ts.map +1 -1
- package/dist/channels/slack/live-delivery.js +16 -4
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +1 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +7 -4
- package/dist/channels/slack/types.d.ts +49 -1
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +6 -6
- package/dist/docs/assets/{ab.md.6cLOW7--.js → ab.md.hdxsvX5M.js} +2 -2
- package/dist/docs/assets/{app.BQ8Hihdf.js → app.mIw24pb1.js} +1 -1
- package/dist/docs/assets/{building-with-agents.md.txrcGU2B.js → building-with-agents.md.PeZaZA1P.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BA7V8dEJ.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.BBCr8Yuy.js → VPLocalSearchBox.hOzpPwrG.js} +1 -1
- package/dist/docs/assets/chunks/{theme.CK_NiGC-.js → theme.BtFhUYmh.js} +2 -2
- package/dist/docs/assets/{deployment.md.CuK5SNjN.js → deployment.md.BHVjVqRa.js} +4 -4
- package/dist/docs/assets/{deployment.md.CuK5SNjN.lean.js → deployment.md.BHVjVqRa.lean.js} +1 -1
- package/dist/docs/assets/{evals.md.BQXI3rXy.js → evals.md.qeoGgPw0.js} +3 -3
- package/dist/docs/assets/example-agents_index.md.xmJ87d_3.js +2 -0
- package/dist/docs/assets/{guides_webhooks.md.BERuBSJW.js → guides_webhooks.md.DB-r_er9.js} +47 -4
- package/dist/docs/assets/{guides_webhooks.md.BERuBSJW.lean.js → guides_webhooks.md.DB-r_er9.lean.js} +1 -1
- package/dist/docs/assets/index.md.BKpbkCa6.js +24 -0
- package/dist/docs/assets/{index.md.CmhptOmN.lean.js → index.md.BKpbkCa6.lean.js} +1 -1
- package/dist/docs/assets/{quickstart.md.C_b6ESpD.js → quickstart.md.rIbfahJM.js} +4 -4
- package/dist/docs/assets/{reference_cli.md.Byvrg8eu.js → reference_cli.md.DqEqQpGn.js} +5 -5
- package/dist/docs/assets/{reference_cli.md.Byvrg8eu.lean.js → reference_cli.md.DqEqQpGn.lean.js} +1 -1
- package/dist/docs/assets/{reference_hooks.md.BGDw4VLm.js → reference_hooks.md.BHeG-vjK.js} +2 -2
- package/dist/docs/assets/{reference_hooks.md.BGDw4VLm.lean.js → reference_hooks.md.BHeG-vjK.lean.js} +1 -1
- package/dist/docs/assets/{storage.md.mQDtIULc.js → storage.md.DcGSnIxX.js} +10 -4
- package/dist/docs/assets/{storage.md.mQDtIULc.lean.js → storage.md.DcGSnIxX.lean.js} +1 -1
- package/dist/docs/assets/troubleshooting.md.CkgwmVI0.js +1 -0
- package/dist/docs/assets/{troubleshooting.md.B5RVX_tL.lean.js → troubleshooting.md.CkgwmVI0.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +5 -5
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +6 -6
- package/dist/docs/evals.html +7 -7
- 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 +5 -5
- 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/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/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +49 -6
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +7 -7
- package/dist/docs/quickstart.html +8 -8
- 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 +8 -8
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +5 -5
- 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 +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +14 -8
- package/dist/docs/troubleshooting.html +5 -5
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +22 -2
- package/dist/internal/cli-deploy.d.ts +6 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +14 -4
- package/dist/internal/cli-mcp-oauth.d.ts.map +1 -1
- package/dist/internal/cli-mcp-oauth.js +3 -1
- package/dist/internal/cli-mcp.js +4 -4
- package/dist/internal/cursor-event-relay.d.ts +45 -0
- package/dist/internal/cursor-event-relay.d.ts.map +1 -1
- package/dist/internal/cursor-event-relay.js +96 -2
- package/dist/internal/deploy-client.js +1 -1
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +35 -7
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +3 -1
- package/dist/internal/durable-session-store.d.ts +48 -0
- package/dist/internal/durable-session-store.d.ts.map +1 -0
- package/dist/internal/durable-session-store.js +86 -0
- package/dist/internal/hosting.d.ts +1 -1
- package/dist/internal/hosting.js +2 -2
- package/dist/internal/init-project.d.ts +34 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +121 -2
- package/dist/internal/ledger-serving.d.ts +101 -0
- package/dist/internal/ledger-serving.d.ts.map +1 -0
- package/dist/internal/ledger-serving.js +174 -0
- package/dist/internal/listen-port.d.ts +22 -0
- package/dist/internal/listen-port.d.ts.map +1 -0
- package/dist/internal/listen-port.js +77 -0
- package/dist/internal/mcp-oauth.d.ts +12 -1
- package/dist/internal/mcp-oauth.d.ts.map +1 -1
- package/dist/internal/mcp-oauth.js +25 -3
- package/dist/internal/playground/static.d.ts +21 -4
- package/dist/internal/playground/static.d.ts.map +1 -1
- package/dist/internal/playground/static.js +82 -18
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +10 -1
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +67 -49
- package/dist/internal/session-engine.d.ts +57 -1
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +117 -28
- package/dist/internal/slack-provision-client.js +1 -1
- package/dist/internal/workspace.d.ts +6 -2
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +10 -2
- package/dist/kv/memory-kv.d.ts +23 -0
- package/dist/kv/memory-kv.d.ts.map +1 -0
- package/dist/kv/memory-kv.js +103 -0
- package/dist/kv.d.ts +22 -0
- package/dist/kv.d.ts.map +1 -0
- package/dist/kv.js +20 -0
- package/dist/ledger/session-ledger.d.ts +204 -0
- package/dist/ledger/session-ledger.d.ts.map +1 -0
- package/dist/ledger/session-ledger.js +382 -0
- package/dist/ledger.d.ts +11 -0
- package/dist/ledger.d.ts.map +1 -0
- package/dist/ledger.js +9 -0
- package/dist/playground/assets/index-B3HBneZZ.js +85 -0
- package/dist/playground/assets/index-Bqk9nplh.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-backends/cursor-hosted.d.ts +25 -3
- package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/storage-backends/cursor-hosted.js +50 -2
- package/dist/storage-backends/file-kv.d.ts +5 -2
- package/dist/storage-backends/file-kv.d.ts.map +1 -1
- package/dist/storage-backends/file-kv.js +107 -7
- package/dist/storage.d.ts +60 -5
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +28 -4
- package/dist/types.d.ts +123 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +14 -0
- package/docs/README.md +10 -7
- package/docs/ab.md +2 -2
- package/docs/building-with-agents.md +1 -1
- package/docs/deployment.md +34 -11
- package/docs/evals.md +3 -3
- package/docs/example-agents/index.md +1 -1
- package/docs/guides/webhooks.md +87 -0
- package/docs/quickstart.md +7 -8
- package/docs/reference/cli.md +15 -6
- package/docs/reference/hooks.md +8 -2
- package/docs/storage.md +67 -7
- package/docs/troubleshooting.md +10 -2
- package/package.json +20 -7
- package/skills/create-agent/SKILL.md +1 -1
- package/src/bin/agent-serve.ts +27 -11
- package/src/channels/slack/api.ts +19 -14
- package/src/channels/slack/attachments.ts +635 -0
- package/src/channels/slack/cursor-account.ts +8 -0
- package/src/channels/slack/defaults.ts +34 -18
- package/src/channels/slack/dispatch.ts +121 -6
- package/src/channels/slack/inbound.ts +60 -0
- package/src/channels/slack/index.ts +1 -0
- package/src/channels/slack/live-delivery.ts +17 -3
- package/src/channels/slack/manifest.ts +1 -0
- package/src/channels/slack/slack-channel.ts +7 -0
- package/src/channels/slack/types.ts +47 -1
- package/src/index.ts +9 -0
- package/src/internal/cli-ax.ts +25 -1
- package/src/internal/cli-deploy.ts +22 -4
- package/src/internal/cli-mcp-oauth.ts +3 -1
- package/src/internal/cli-mcp.ts +4 -4
- package/src/internal/cursor-event-relay.ts +168 -0
- package/src/internal/deploy-client.ts +1 -1
- package/src/internal/discovery.ts +42 -5
- package/src/internal/distribution.ts +3 -1
- package/src/internal/durable-session-store.ts +95 -0
- package/src/internal/hosting.ts +2 -2
- package/src/internal/init-project.ts +151 -2
- package/src/internal/ledger-serving.ts +240 -0
- package/src/internal/listen-port.ts +85 -0
- package/src/internal/mcp-oauth.ts +29 -4
- package/src/internal/playground/static.ts +111 -20
- package/src/internal/sdk-runner.ts +11 -1
- package/src/internal/server.ts +76 -62
- package/src/internal/session-engine.ts +192 -16
- package/src/internal/slack-provision-client.ts +1 -1
- package/src/internal/workspace.ts +14 -3
- package/src/kv/memory-kv.ts +99 -0
- package/src/kv.ts +27 -0
- package/src/ledger/session-ledger.ts +535 -0
- package/src/ledger.ts +29 -0
- package/src/storage-backends/cursor-hosted.ts +108 -3
- package/src/storage-backends/file-kv.ts +128 -12
- package/src/storage.ts +90 -6
- package/src/types.ts +130 -4
- package/dist/channels/slack/post-update-delivery.d.ts +0 -85
- package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.B-VTH4As.js +0 -1
- package/dist/docs/assets/example-agents_index.md.DgGBwckv.js +0 -2
- package/dist/docs/assets/index.md.CmhptOmN.js +0 -24
- package/dist/docs/assets/troubleshooting.md.B5RVX_tL.js +0 -1
- package/dist/internal/json-dir-store.d.ts +0 -32
- package/dist/internal/json-dir-store.d.ts.map +0 -1
- package/dist/internal/persistence-coordinator.d.ts +0 -127
- package/dist/internal/persistence-coordinator.d.ts.map +0 -1
- package/dist/multi-tenant.d.ts +0 -80
- package/dist/multi-tenant.d.ts.map +0 -1
- package/dist/multi-tenant.js +0 -69
- package/dist/persistence.d.ts +0 -184
- package/dist/persistence.d.ts.map +0 -1
- package/dist/playground/assets/index-BRhBbnd4.css +0 -1
- package/dist/playground/assets/index-_2WKw9a-.js +0 -85
- package/dist/storage-backends/postgres-kv.d.ts +0 -82
- package/dist/storage-backends/postgres-kv.d.ts.map +0 -1
- package/dist/storage-backends/postgres-kv.js +0 -185
- package/src/storage-backends/postgres-kv.ts +0 -253
- /package/dist/docs/assets/{ab.md.6cLOW7--.lean.js → ab.md.hdxsvX5M.lean.js} +0 -0
- /package/dist/docs/assets/{building-with-agents.md.txrcGU2B.lean.js → building-with-agents.md.PeZaZA1P.lean.js} +0 -0
- /package/dist/docs/assets/{evals.md.BQXI3rXy.lean.js → evals.md.qeoGgPw0.lean.js} +0 -0
- /package/dist/docs/assets/{example-agents_index.md.DgGBwckv.lean.js → example-agents_index.md.xmJ87d_3.lean.js} +0 -0
- /package/dist/docs/assets/{quickstart.md.C_b6ESpD.lean.js → quickstart.md.rIbfahJM.lean.js} +0 -0
|
@@ -23,6 +23,12 @@ import {
|
|
|
23
23
|
} from "../channels/slack/nudge-store.js";
|
|
24
24
|
import { resolveDedicatedSlackHostBinding } from "../channels/slack/slack-channel.js";
|
|
25
25
|
import { isCursorHostedFilesAvailable } from "../files-backends/cursor-hosted.js";
|
|
26
|
+
import {
|
|
27
|
+
SessionLedger,
|
|
28
|
+
type SessionLedgerKv,
|
|
29
|
+
supportsSessionLedgerKv,
|
|
30
|
+
} from "../ledger/session-ledger.js";
|
|
31
|
+
import { fileKv } from "../storage-backends/file-kv.js";
|
|
26
32
|
import {
|
|
27
33
|
type AgentProject,
|
|
28
34
|
type ApprovalDecision,
|
|
@@ -40,6 +46,7 @@ import {
|
|
|
40
46
|
DIRECT_TOOL_CALL_CHANNEL_ID,
|
|
41
47
|
type HookHandler,
|
|
42
48
|
type HostContext,
|
|
49
|
+
type HostKvApi,
|
|
43
50
|
type HttpChannelDefinition,
|
|
44
51
|
isHostOauthConnectionTransport,
|
|
45
52
|
isSymbolicConnectionTransport,
|
|
@@ -78,6 +85,11 @@ import {
|
|
|
78
85
|
mapSdkTokenUsage,
|
|
79
86
|
} from "./cloud-turn-cost.js";
|
|
80
87
|
import { isDevMode } from "./dev-mode.js";
|
|
88
|
+
import {
|
|
89
|
+
ledgerKeyPrefix,
|
|
90
|
+
namespacedKv,
|
|
91
|
+
storageCasKv,
|
|
92
|
+
} from "./durable-session-store.js";
|
|
81
93
|
import { EventLogStore } from "./event-log.js";
|
|
82
94
|
import { TurnEventMapper } from "./event-mapper.js";
|
|
83
95
|
import { selectFrameworkStorageDefinition } from "./framework-storage-selection.js";
|
|
@@ -95,6 +107,7 @@ import {
|
|
|
95
107
|
newToolCallId,
|
|
96
108
|
} from "./ids.js";
|
|
97
109
|
import { validateToolInput } from "./json-schema.js";
|
|
110
|
+
import { LedgerServing, newLedgerPodId } from "./ledger-serving.js";
|
|
98
111
|
import { McpHost } from "./mcp-host.js";
|
|
99
112
|
import {
|
|
100
113
|
buildSessionContextBlock,
|
|
@@ -215,6 +228,21 @@ export interface SessionEngineOptions {
|
|
|
215
228
|
* stub; production uses billed usage events when an agent id is known.
|
|
216
229
|
*/
|
|
217
230
|
estimateTurnCost?: EstimateTurnCostFn;
|
|
231
|
+
/**
|
|
232
|
+
* Explicit durable-session store for `serving: "ledger"` agents — a KV
|
|
233
|
+
* dedicated to the ledger (session identity, leases, commit heads),
|
|
234
|
+
* separate from agent state. Must implement the ledger's CAS methods
|
|
235
|
+
* (`getWithVersion` / `putIfAbsent` / `putIfVersion`); construction
|
|
236
|
+
* fails fast otherwise. Supply the **raw** store: the engine owns key
|
|
237
|
+
* scoping and namespaces every ledger key under
|
|
238
|
+
* `agentkit/v1/<agent>/durable-session/` itself — a self-hoster never
|
|
239
|
+
* writes prefixes. Default when omitted: the authored storage backend
|
|
240
|
+
* (`agent/storage.ts`) when it implements the `cas` group, else — with
|
|
241
|
+
* no authored storage — a file store under `<stateRoot>/ledger-kv`
|
|
242
|
+
* (single-process correctness only). An authored storage backend
|
|
243
|
+
* *without* `cas` fails fast. Ignored for resident-mode agents.
|
|
244
|
+
*/
|
|
245
|
+
durableSessionStore?: HostKvApi;
|
|
218
246
|
}
|
|
219
247
|
|
|
220
248
|
/**
|
|
@@ -250,6 +278,13 @@ interface ActiveToolContext {
|
|
|
250
278
|
|
|
251
279
|
export interface EngineSendOptions extends SendMessageOptions {
|
|
252
280
|
mode?: "chat" | "task";
|
|
281
|
+
/**
|
|
282
|
+
* Internal (ledger serving): this send *is* a lease-holding turn
|
|
283
|
+
* delivery, so it must run the resident path. Without the flag, every
|
|
284
|
+
* send on a `serving: "ledger"` agent routes through the ledger
|
|
285
|
+
* (claim → turn → commit) and the pump would recurse into itself.
|
|
286
|
+
*/
|
|
287
|
+
ledgerDelivery?: boolean;
|
|
253
288
|
}
|
|
254
289
|
|
|
255
290
|
interface CoalescedQueuedTurn {
|
|
@@ -341,6 +376,12 @@ export class SessionEngine {
|
|
|
341
376
|
private readonly cloudCostBackfillDone = new Set<string>();
|
|
342
377
|
/** Deduped in-flight cloud cost backfills, keyed by session id. */
|
|
343
378
|
private readonly cloudCostBackfill = new Map<string, Promise<void>>();
|
|
379
|
+
/**
|
|
380
|
+
* Ledger-mode delivery seam; set only when the agent declares
|
|
381
|
+
* `serving: "ledger"`. When present, {@link send} routes every
|
|
382
|
+
* non-delivery call through the ledger's claim → turn → commit.
|
|
383
|
+
*/
|
|
384
|
+
private readonly ledgerServing: LedgerServing | undefined;
|
|
344
385
|
|
|
345
386
|
constructor(options: SessionEngineOptions) {
|
|
346
387
|
this.project = options.project;
|
|
@@ -425,6 +466,82 @@ export class SessionEngine {
|
|
|
425
466
|
for (const channel of options.project.channels) {
|
|
426
467
|
this.channelsById.set(channel.id, channel.definition);
|
|
427
468
|
}
|
|
469
|
+
this.ledgerServing = this.buildLedgerServing(options);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Ledger-mode wiring (`serving: "ledger"`): resolve a CAS-capable
|
|
474
|
+
* durable-session store and build the delivery seam. Resident agents
|
|
475
|
+
* (the default) return undefined and never touch ledger keys.
|
|
476
|
+
*/
|
|
477
|
+
private buildLedgerServing(
|
|
478
|
+
options: SessionEngineOptions
|
|
479
|
+
): LedgerServing | undefined {
|
|
480
|
+
if (options.project.agent.serving !== "ledger") {
|
|
481
|
+
return undefined;
|
|
482
|
+
}
|
|
483
|
+
const ledgerKv = this.resolveDurableSessionStore(options);
|
|
484
|
+
return new LedgerServing({
|
|
485
|
+
ledger: new SessionLedger({ kv: ledgerKv, podId: newLedgerPodId() }),
|
|
486
|
+
deliverTurn: (request) =>
|
|
487
|
+
this.send(request.channelId, request.message, {
|
|
488
|
+
...request.options,
|
|
489
|
+
continuationToken: request.continuationToken,
|
|
490
|
+
ledgerDelivery: true,
|
|
491
|
+
}),
|
|
492
|
+
logger: this.logger,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Pick the KV the ledger coordinates on, in precedence order:
|
|
498
|
+
*
|
|
499
|
+
* 1. `options.durableSessionStore` — an explicit store separate from
|
|
500
|
+
* agent state (advanced deployments, tests).
|
|
501
|
+
* 2. The authored storage backend (`agent/storage.ts`) when it
|
|
502
|
+
* implements the `cas` group — one backend serves both the storage
|
|
503
|
+
* API and the durable session API.
|
|
504
|
+
* 3. No authored storage: a file store under `<stateRoot>/ledger-kv`
|
|
505
|
+
* (same place resident state lives; single-process only).
|
|
506
|
+
*
|
|
507
|
+
* An authored backend without `cas` fails fast: silently falling back
|
|
508
|
+
* to local files would fork coordination away from the deployment's
|
|
509
|
+
* durable store. The check demands exactly the methods the
|
|
510
|
+
* coordination-only ledger uses — `listKeys` is not one of them.
|
|
511
|
+
*
|
|
512
|
+
* Whatever store wins, the returned handle is scoped here — once —
|
|
513
|
+
* under `agentkit/v1/<agent>/durable-session/`. The ledger speaks bare
|
|
514
|
+
* `session/…` / `ledger/…` keys; namespacing is this boundary's job,
|
|
515
|
+
* exactly like `host.kv`'s agent scoping.
|
|
516
|
+
*/
|
|
517
|
+
private resolveDurableSessionStore(
|
|
518
|
+
options: SessionEngineOptions
|
|
519
|
+
): SessionLedgerKv {
|
|
520
|
+
const namespace = ledgerKeyPrefix(options.project.name);
|
|
521
|
+
const explicit = options.durableSessionStore;
|
|
522
|
+
if (explicit !== undefined) {
|
|
523
|
+
if (!supportsSessionLedgerKv(explicit)) {
|
|
524
|
+
throw new Error(
|
|
525
|
+
`Agent "${options.project.name}" declares serving: "ledger", but the configured durableSessionStore is not CAS-capable (getWithVersion / putIfAbsent / putIfVersion). Provide a store implementing those methods (see @cursor/july/kv).`
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
return namespacedKv(explicit, namespace);
|
|
529
|
+
}
|
|
530
|
+
const casArgs = {
|
|
531
|
+
agentName: options.project.name,
|
|
532
|
+
projectRoot: options.project.rootDir,
|
|
533
|
+
};
|
|
534
|
+
const authored = options.project.storage;
|
|
535
|
+
if (authored !== undefined) {
|
|
536
|
+
if (authored.cas === undefined) {
|
|
537
|
+
throw new Error(
|
|
538
|
+
`Agent "${options.project.name}" declares serving: "ledger", which needs conditional writes, but the storage backend in agent/storage.ts does not implement the cas group. Add cas: {...} to defineStorage (fileKv and cursorHostedStorage already include it), or pass an explicit durableSessionStore.`
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
return namespacedKv(storageCasKv(authored, casArgs), namespace);
|
|
542
|
+
}
|
|
543
|
+
const fallback = fileKv({ dir: join(options.stateRoot, "ledger-kv") });
|
|
544
|
+
return namespacedKv(storageCasKv(fallback, casArgs), namespace);
|
|
428
545
|
}
|
|
429
546
|
|
|
430
547
|
/**
|
|
@@ -592,6 +709,12 @@ export class SessionEngine {
|
|
|
592
709
|
message: string,
|
|
593
710
|
options: EngineSendOptions = {}
|
|
594
711
|
): Promise<ChannelSession> {
|
|
712
|
+
// Ledger-mode agents acquire the session lease before any turn runs;
|
|
713
|
+
// only the seam's own lease-holding deliveries fall through to the
|
|
714
|
+
// resident path.
|
|
715
|
+
if (this.ledgerServing !== undefined && options.ledgerDelivery !== true) {
|
|
716
|
+
return this.ledgerServing.deliver(channelId, message, options);
|
|
717
|
+
}
|
|
595
718
|
const auth = options.auth ?? null;
|
|
596
719
|
let existing =
|
|
597
720
|
options.continuationToken === undefined
|
|
@@ -616,20 +739,27 @@ export class SessionEngine {
|
|
|
616
739
|
// Refresh harness cwd when the caller supplies a newer worktree (e.g. PR
|
|
617
740
|
// synchronize). Leave the durable continuation key unchanged.
|
|
618
741
|
let record = existing;
|
|
619
|
-
|
|
742
|
+
const refreshWorkspace =
|
|
620
743
|
options.workspaceDir !== undefined &&
|
|
621
|
-
options.workspaceDir !== existing.workspaceOverride
|
|
744
|
+
options.workspaceDir !== existing.workspaceOverride;
|
|
745
|
+
if (
|
|
746
|
+
refreshWorkspace ||
|
|
747
|
+
options.workspaceFiles !== undefined ||
|
|
748
|
+
options.images !== undefined
|
|
622
749
|
) {
|
|
623
750
|
record = await this.updateRecord(existing.sessionId, (current) => ({
|
|
624
751
|
...current,
|
|
625
|
-
workspaceOverride:
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
752
|
+
workspaceOverride: refreshWorkspace
|
|
753
|
+
? options.workspaceDir
|
|
754
|
+
: current.workspaceOverride,
|
|
755
|
+
pendingWorkspaceFiles: mergePendingWorkspaceFiles(
|
|
756
|
+
current.pendingWorkspaceFiles,
|
|
757
|
+
options.workspaceFiles
|
|
758
|
+
),
|
|
759
|
+
pendingImages: mergePendingImages(
|
|
760
|
+
current.pendingImages,
|
|
761
|
+
options.images
|
|
762
|
+
),
|
|
633
763
|
}));
|
|
634
764
|
}
|
|
635
765
|
// Refresh the cloud attach the same way: a follow-up send carries the
|
|
@@ -668,6 +798,7 @@ export class SessionEngine {
|
|
|
668
798
|
channelState,
|
|
669
799
|
workspaceOverride: options.workspaceDir,
|
|
670
800
|
pendingWorkspaceFiles: options.workspaceFiles,
|
|
801
|
+
pendingImages: options.images,
|
|
671
802
|
cloudOverride: options.cloud,
|
|
672
803
|
sdkAgentId: options.sdkAgentId,
|
|
673
804
|
turnCount: 0,
|
|
@@ -1299,15 +1430,29 @@ export class SessionEngine {
|
|
|
1299
1430
|
stateRoot: this.stateRoot,
|
|
1300
1431
|
});
|
|
1301
1432
|
const pendingFiles = record.pendingWorkspaceFiles;
|
|
1433
|
+
const pendingImages = record.pendingImages;
|
|
1302
1434
|
if (
|
|
1303
1435
|
pendingFiles !== undefined &&
|
|
1304
1436
|
Object.keys(pendingFiles).length > 0
|
|
1305
1437
|
) {
|
|
1306
1438
|
await writeWorkspaceFiles(harnessCwd, pendingFiles);
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1439
|
+
}
|
|
1440
|
+
if (
|
|
1441
|
+
(pendingFiles !== undefined &&
|
|
1442
|
+
Object.keys(pendingFiles).length > 0) ||
|
|
1443
|
+
(pendingImages !== undefined && pendingImages.length > 0)
|
|
1444
|
+
) {
|
|
1445
|
+
await this.updateRecord(sessionId, (current) => ({
|
|
1446
|
+
...current,
|
|
1447
|
+
pendingWorkspaceFiles:
|
|
1448
|
+
current.pendingWorkspaceFiles === pendingFiles
|
|
1449
|
+
? undefined
|
|
1450
|
+
: current.pendingWorkspaceFiles,
|
|
1451
|
+
pendingImages:
|
|
1452
|
+
current.pendingImages === pendingImages
|
|
1453
|
+
? undefined
|
|
1454
|
+
: current.pendingImages,
|
|
1455
|
+
}));
|
|
1311
1456
|
}
|
|
1312
1457
|
// The harness only loads the scaffolded AGENTS.md when the session
|
|
1313
1458
|
// workspace is its own project root. When the harness cwd is borrowed
|
|
@@ -1340,7 +1485,8 @@ export class SessionEngine {
|
|
|
1340
1485
|
await yieldEventLoop();
|
|
1341
1486
|
}
|
|
1342
1487
|
},
|
|
1343
|
-
signal
|
|
1488
|
+
signal,
|
|
1489
|
+
pendingImages
|
|
1344
1490
|
);
|
|
1345
1491
|
this.activeToolContexts.set(sessionId, { record, turnId, emit });
|
|
1346
1492
|
try {
|
|
@@ -1443,7 +1589,8 @@ export class SessionEngine {
|
|
|
1443
1589
|
instructionsInvisibleToHarness: boolean,
|
|
1444
1590
|
emit: (payload: SessionEventPayload) => void,
|
|
1445
1591
|
onUpdate: RunnerTurnRequest["onUpdate"],
|
|
1446
|
-
signal: AbortSignal
|
|
1592
|
+
signal: AbortSignal,
|
|
1593
|
+
images?: Array<{ data: string; mimeType: string }>
|
|
1447
1594
|
): RunnerTurnRequest {
|
|
1448
1595
|
// Per-session cloud attach (channel/tool handoff) forces cloud runtime
|
|
1449
1596
|
// even when the agent defaults to local for chat server tools.
|
|
@@ -1508,6 +1655,7 @@ export class SessionEngine {
|
|
|
1508
1655
|
runnerStateDir: join(this.stateRoot, "runner"),
|
|
1509
1656
|
prompt: turnPrompt,
|
|
1510
1657
|
isFirstTurn,
|
|
1658
|
+
...(images !== undefined && images.length > 0 ? { images } : {}),
|
|
1511
1659
|
...(agent.model === undefined ? {} : { model: agent.model }),
|
|
1512
1660
|
runtime,
|
|
1513
1661
|
...(mergedCloud === undefined ? {} : { cloud: mergedCloud }),
|
|
@@ -3193,6 +3341,8 @@ export class SessionEngine {
|
|
|
3193
3341
|
/** Drain running turns, dispatches, and pending writes, then dispose. */
|
|
3194
3342
|
async close(): Promise<void> {
|
|
3195
3343
|
this.approvals.cancelAll("deny");
|
|
3344
|
+
// Ledger deliveries spawn turns, so settle them before activeTurns.
|
|
3345
|
+
await this.ledgerServing?.close();
|
|
3196
3346
|
await Promise.allSettled([...this.activeTurns.values()]);
|
|
3197
3347
|
await Promise.allSettled([...this.backgroundWork]);
|
|
3198
3348
|
await Promise.allSettled([...this.dispatchChains.values()]);
|
|
@@ -3208,6 +3358,32 @@ export class SessionEngine {
|
|
|
3208
3358
|
const RESTORE_FETCH_CONCURRENCY = 8;
|
|
3209
3359
|
|
|
3210
3360
|
/** NDJSON payload for a restored event stream; undefined when empty. */
|
|
3361
|
+
function mergePendingImages(
|
|
3362
|
+
current: Array<{ data: string; mimeType: string }> | undefined,
|
|
3363
|
+
incoming: Array<{ data: string; mimeType: string }> | undefined
|
|
3364
|
+
): Array<{ data: string; mimeType: string }> | undefined {
|
|
3365
|
+
if (incoming === undefined) {
|
|
3366
|
+
return current;
|
|
3367
|
+
}
|
|
3368
|
+
if (current === undefined || current.length === 0) {
|
|
3369
|
+
return incoming;
|
|
3370
|
+
}
|
|
3371
|
+
return [...current, ...incoming];
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
function mergePendingWorkspaceFiles(
|
|
3375
|
+
current: SessionRecord["pendingWorkspaceFiles"],
|
|
3376
|
+
incoming: SessionRecord["pendingWorkspaceFiles"]
|
|
3377
|
+
): SessionRecord["pendingWorkspaceFiles"] {
|
|
3378
|
+
if (incoming === undefined) {
|
|
3379
|
+
return current;
|
|
3380
|
+
}
|
|
3381
|
+
if (current === undefined) {
|
|
3382
|
+
return incoming;
|
|
3383
|
+
}
|
|
3384
|
+
return { ...current, ...incoming };
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3211
3387
|
function eventsNdjson(events: SessionEvent[]): string | undefined {
|
|
3212
3388
|
if (events.length === 0) {
|
|
3213
3389
|
return undefined;
|
|
@@ -386,7 +386,7 @@ function mapErrorResponse(
|
|
|
386
386
|
);
|
|
387
387
|
case 403:
|
|
388
388
|
return new SlackProvisionApiError(
|
|
389
|
-
`${serverError ?? "Not authorized"} — Slack provisioning needs team
|
|
389
|
+
`${serverError ?? "Not authorized"} — Slack provisioning needs team membership and the cloud-agent entitlement on that team.`,
|
|
390
390
|
{ status: 403 }
|
|
391
391
|
);
|
|
392
392
|
case 404: {
|
|
@@ -421,16 +421,27 @@ export function buildSkillMd(skill: DiscoveredSkill): string {
|
|
|
421
421
|
|
|
422
422
|
/**
|
|
423
423
|
* Write relative path → contents under `workspaceDir`, refusing paths that
|
|
424
|
-
* escape the directory.
|
|
424
|
+
* escape the directory. Values may be utf8 strings or base64 blob envelopes.
|
|
425
425
|
*/
|
|
426
426
|
export async function writeWorkspaceFiles(
|
|
427
427
|
workspaceDir: string,
|
|
428
|
-
files: Record<
|
|
428
|
+
files: Record<
|
|
429
|
+
string,
|
|
430
|
+
string | { encoding: "base64"; data: string; mimeType?: string }
|
|
431
|
+
>
|
|
429
432
|
): Promise<void> {
|
|
430
433
|
for (const [relativePath, contents] of Object.entries(files)) {
|
|
431
434
|
const target = resolveWorkspaceWritePath(workspaceDir, relativePath);
|
|
432
435
|
await mkdir(dirname(target), { recursive: true });
|
|
433
|
-
|
|
436
|
+
if (typeof contents === "string") {
|
|
437
|
+
await writeFile(target, contents, "utf8");
|
|
438
|
+
} else if (contents.encoding === "base64") {
|
|
439
|
+
await writeFile(target, Buffer.from(contents.data, "base64"));
|
|
440
|
+
} else {
|
|
441
|
+
throw new Error(
|
|
442
|
+
`Unsupported workspace file encoding for ${relativePath}`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
434
445
|
}
|
|
435
446
|
}
|
|
436
447
|
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory {@link HostKvCasApi} — the test/dev implementation of the
|
|
3
|
+
* interface module (`@cursor/july/kv`), exported at
|
|
4
|
+
* `@cursor/july/kv/memory`.
|
|
5
|
+
*
|
|
6
|
+
* The memory KV *is* the dev shim: tests and local runs exercise the
|
|
7
|
+
* same CAS coordination paths that hosted serving uses, not a mock of an
|
|
8
|
+
* RPC. Values are deep-copied on both write and
|
|
9
|
+
* read so callers can never observe (or cause) shared-reference mutation.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { StorageCasTable } from "../storage.js";
|
|
13
|
+
import type {
|
|
14
|
+
HostKvCasApi,
|
|
15
|
+
JsonValue,
|
|
16
|
+
KvCasResult,
|
|
17
|
+
KvVersionedEntry,
|
|
18
|
+
} from "../types.js";
|
|
19
|
+
|
|
20
|
+
/** Create an empty in-memory CAS-capable KV. */
|
|
21
|
+
export function createMemoryKv(): HostKvCasApi {
|
|
22
|
+
const entries = new Map<string, { value: JsonValue; version: string }>();
|
|
23
|
+
let versionCounter = 0;
|
|
24
|
+
|
|
25
|
+
const nextVersion = (): string => {
|
|
26
|
+
versionCounter += 1;
|
|
27
|
+
return `v${versionCounter}`;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const snapshot = (key: string): KvVersionedEntry | undefined => {
|
|
31
|
+
const entry = entries.get(key);
|
|
32
|
+
if (entry === undefined) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
return { value: structuredClone(entry.value), version: entry.version };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
async get(key): Promise<JsonValue | undefined> {
|
|
40
|
+
return snapshot(key)?.value;
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async put(key, value): Promise<void> {
|
|
44
|
+
entries.set(key, {
|
|
45
|
+
value: structuredClone(value),
|
|
46
|
+
version: nextVersion(),
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async delete(key): Promise<void> {
|
|
51
|
+
entries.delete(key);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async getWithVersion(key): Promise<KvVersionedEntry | undefined> {
|
|
55
|
+
return snapshot(key);
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
async putIfAbsent(key, value): Promise<KvCasResult> {
|
|
59
|
+
if (entries.has(key)) {
|
|
60
|
+
return { ok: false, current: snapshot(key) };
|
|
61
|
+
}
|
|
62
|
+
const version = nextVersion();
|
|
63
|
+
entries.set(key, { value: structuredClone(value), version });
|
|
64
|
+
return { ok: true, version };
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
async putIfVersion(key, value, expectedVersion): Promise<KvCasResult> {
|
|
68
|
+
const entry = entries.get(key);
|
|
69
|
+
if (entry === undefined || entry.version !== expectedVersion) {
|
|
70
|
+
return { ok: false, current: snapshot(key) };
|
|
71
|
+
}
|
|
72
|
+
const version = nextVersion();
|
|
73
|
+
entries.set(key, { value: structuredClone(value), version });
|
|
74
|
+
return { ok: true, version };
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
async listKeys(prefix): Promise<string[]> {
|
|
78
|
+
return [...entries.keys()].filter((key) => key.startsWith(prefix)).sort();
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* In-memory `cas` group for `defineStorage` — a complete conditional-write
|
|
85
|
+
* group for sinks whose plain methods are inert or live elsewhere (test
|
|
86
|
+
* fixtures, an off-hosting no-op sink). Non-durable and process-local by
|
|
87
|
+
* construction; a real deployment backend should implement CAS against
|
|
88
|
+
* its own store instead.
|
|
89
|
+
*/
|
|
90
|
+
export function memoryCasTable(): StorageCasTable {
|
|
91
|
+
const kv = createMemoryKv();
|
|
92
|
+
return {
|
|
93
|
+
getWithVersion: (key) => kv.getWithVersion(key),
|
|
94
|
+
putIfAbsent: (key, value) => kv.putIfAbsent(key, value),
|
|
95
|
+
putIfVersion: (key, value, expectedVersion) =>
|
|
96
|
+
kv.putIfVersion(key, value, expectedVersion),
|
|
97
|
+
listKeys: (prefix) => kv.listKeys(prefix),
|
|
98
|
+
};
|
|
99
|
+
}
|
package/src/kv.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CAS-capable KV **interface** — `@cursor/july/kv`.
|
|
3
|
+
*
|
|
4
|
+
* This module is implementation-free on purpose: it is the contract a
|
|
5
|
+
* bring-your-own KV implements so coordination protocols (conditional
|
|
6
|
+
* writes are the substrate for the upcoming session ledger) can run on
|
|
7
|
+
* it. {@link HostKvCasApi} is the conditional-write superset of the plain
|
|
8
|
+
* {@link HostKvApi}; {@link supportsKvCas} is the runtime probe for
|
|
9
|
+
* stores of unknown provenance — a `defineStorage` backend declares CAS
|
|
10
|
+
* through its `cas` group instead, so it never needs probing.
|
|
11
|
+
*
|
|
12
|
+
* Implementations live elsewhere:
|
|
13
|
+
*
|
|
14
|
+
* - `@cursor/july/kv/memory` — `createMemoryKv`, the test/dev shim.
|
|
15
|
+
* - The storage backends implement the same operations as the `cas`
|
|
16
|
+
* group on `defineStorage` (see `StorageCasTable` in
|
|
17
|
+
* `@cursor/july/storage`): `fileKv` (single-process) and
|
|
18
|
+
* `cursorHostedStorage` (control-plane proxy). See `docs/storage.md`.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export type {
|
|
22
|
+
HostKvApi,
|
|
23
|
+
HostKvCasApi,
|
|
24
|
+
KvCasResult,
|
|
25
|
+
KvVersionedEntry,
|
|
26
|
+
} from "./types.js";
|
|
27
|
+
export { supportsKvCas } from "./types.js";
|