@cursor/july 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channels/slack/channel-watch.d.ts +19 -3
- package/dist/channels/slack/channel-watch.d.ts.map +1 -1
- package/dist/channels/slack/channel-watch.js +48 -9
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +4 -4
- package/dist/channels/slack/types.d.ts +11 -12
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/assets/{app.9eAtsjAM.js → app.D23Y-7Tp.js} +4 -4
- package/dist/docs/assets/chunks/@localSearchIndexroot.CzCCM7N8.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.C9s69nxj.js → VPLocalSearchBox.CWBeTFRZ.js} +1 -1
- package/dist/docs/assets/chunks/{arc.DmWDRaF-.js → arc.DSF2O3pm.js} +1 -1
- package/dist/docs/assets/chunks/{architectureDiagram-Q4EWVU46.BE1Wj4f3.js → architectureDiagram-Q4EWVU46.J52Wzbkg.js} +1 -1
- package/dist/docs/assets/chunks/{baseUniq.pmEZGnWu.js → baseUniq.CQS3LPCt.js} +1 -1
- package/dist/docs/assets/chunks/{blockDiagram-DXYQGD6D.D6mM-5XN.js → blockDiagram-DXYQGD6D.Dw339Gr5.js} +1 -1
- package/dist/docs/assets/chunks/{c4Diagram-AHTNJAMY.ZQYTPC1b.js → c4Diagram-AHTNJAMY.BUdtOaRZ.js} +1 -1
- package/dist/docs/assets/chunks/channel.Bfu4df88.js +1 -0
- package/dist/docs/assets/chunks/{chunk-4BX2VUAB.CKJ7gaK8.js → chunk-4BX2VUAB.CuOrkEqk.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-4TB4RGXK.CL61HnvB.js → chunk-4TB4RGXK.BJNBcY7U.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-55IACEB6.DsA4XA1m.js → chunk-55IACEB6.VJK5LAm_.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-EDXVE4YY.BODHCHFW.js → chunk-EDXVE4YY.BYYLihvj.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-FMBD7UC4.D6NBaZTa.js → chunk-FMBD7UC4.CmoW8BXP.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-OYMX7WX6.BLPuqFXg.js → chunk-OYMX7WX6.DTGY4C-M.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-QZHKN3VN.BFs_ML8Z.js → chunk-QZHKN3VN.Cg5n67vl.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-YZCP3GAM.BDjuFyZv.js → chunk-YZCP3GAM.C3GR_ia5.js} +1 -1
- package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.DdfgtaWs.js +1 -0
- package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.DdfgtaWs.js +1 -0
- package/dist/docs/assets/chunks/clone.rkmfti6d.js +1 -0
- package/dist/docs/assets/chunks/{cose-bilkent-S5V4N54A.D85HNEsi.js → cose-bilkent-S5V4N54A.BTRG8N3b.js} +1 -1
- package/dist/docs/assets/chunks/{dagre-KV5264BT.V463KlSF.js → dagre-KV5264BT.Bob_bp_p.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-5BDNPKRD.DhBDu1ab.js → diagram-5BDNPKRD.ggPcs9uO.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-G4DWMVQ6.v6sC68zl.js → diagram-G4DWMVQ6.BP0qyJkp.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-MMDJMWI5.DhHsdXYv.js → diagram-MMDJMWI5.B0X24UKr.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-TYMM5635.BPWNSFZc.js → diagram-TYMM5635.B4rXHFVt.js} +1 -1
- package/dist/docs/assets/chunks/{erDiagram-SMLLAGMA.Cq0wTMPL.js → erDiagram-SMLLAGMA._55Rt9oX.js} +1 -1
- package/dist/docs/assets/chunks/{flowDiagram-DWJPFMVM.MveMtucC.js → flowDiagram-DWJPFMVM.DGP4XvR5.js} +1 -1
- package/dist/docs/assets/chunks/{ganttDiagram-T4ZO3ILL.BzKfGUSf.js → ganttDiagram-T4ZO3ILL.BtXtkL4E.js} +1 -1
- package/dist/docs/assets/chunks/{gitGraphDiagram-UUTBAWPF.OToXTSW_.js → gitGraphDiagram-UUTBAWPF.B9cPWblK.js} +1 -1
- package/dist/docs/assets/chunks/{graph.D82tam-l.js → graph.D8HzNexS.js} +1 -1
- package/dist/docs/assets/chunks/{infoDiagram-42DDH7IO.DzFlRmcE.js → infoDiagram-42DDH7IO.Bw7CQUpi.js} +1 -1
- package/dist/docs/assets/chunks/{ishikawaDiagram-UXIWVN3A.CVPRJiKe.js → ishikawaDiagram-UXIWVN3A.MwkzF6nQ.js} +1 -1
- package/dist/docs/assets/chunks/{journeyDiagram-VCZTEJTY.CQvNTfQC.js → journeyDiagram-VCZTEJTY.DIGFF-3C.js} +1 -1
- package/dist/docs/assets/chunks/{kanban-definition-6JOO6SKY.BwywywUl.js → kanban-definition-6JOO6SKY.DhYef2BN.js} +1 -1
- package/dist/docs/assets/chunks/{layout.C4BkPPba.js → layout.C0XUxuPi.js} +1 -1
- package/dist/docs/assets/chunks/{linear.FoSfGKD4.js → linear.BwNPpZex.js} +1 -1
- package/dist/docs/assets/chunks/{min.yLh8jqfl.js → min.CwAQdL7z.js} +1 -1
- package/dist/docs/assets/chunks/{mindmap-definition-QFDTVHPH.C9Dq2_CN.js → mindmap-definition-QFDTVHPH.pWsSVLsP.js} +1 -1
- package/dist/docs/assets/chunks/{pieDiagram-DEJITSTG.CiGyBcES.js → pieDiagram-DEJITSTG.BDJ3FbBy.js} +1 -1
- package/dist/docs/assets/chunks/{quadrantDiagram-34T5L4WZ.1FWea9nj.js → quadrantDiagram-34T5L4WZ.Co80izyB.js} +1 -1
- package/dist/docs/assets/chunks/{requirementDiagram-MS252O5E.B1q7ntiV.js → requirementDiagram-MS252O5E.JveKw4yx.js} +1 -1
- package/dist/docs/assets/chunks/{sankeyDiagram-XADWPNL6.Cpr4oU_k.js → sankeyDiagram-XADWPNL6.B0A7adPi.js} +1 -1
- package/dist/docs/assets/chunks/{sequenceDiagram-FGHM5R23.CUK68STn.js → sequenceDiagram-FGHM5R23.d6JZ5Hre.js} +1 -1
- package/dist/docs/assets/chunks/{stateDiagram-FHFEXIEX.zU0yL2m4.js → stateDiagram-FHFEXIEX.DWnL0NQl.js} +1 -1
- package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.ZEetPk0E.js +1 -0
- package/dist/docs/assets/chunks/{theme.DhnKd0CD.js → theme.MJTLx0hh.js} +2 -2
- package/dist/docs/assets/chunks/{timeline-definition-GMOUNBTQ.CNYoXo3F.js → timeline-definition-GMOUNBTQ.CFS7Ai4c.js} +1 -1
- package/dist/docs/assets/chunks/{vennDiagram-DHZGUBPP.DFWH3G9s.js → vennDiagram-DHZGUBPP.CwSlnjCf.js} +1 -1
- package/dist/docs/assets/chunks/wardley-RL74JXVD.3gurI8YA.js +162 -0
- package/dist/docs/assets/chunks/{wardleyDiagram-NUSXRM2D.7WLkqM7s.js → wardleyDiagram-NUSXRM2D.B_8mvtjh.js} +1 -1
- package/dist/docs/assets/chunks/{xychartDiagram-5P7HB3ND.-SyUNgWw.js → xychartDiagram-5P7HB3ND.DtjU5H85.js} +1 -1
- package/dist/docs/assets/{guides_slack.md.DVjNyqq5.js → guides_slack.md.Bjw2r2gL.js} +4 -4
- package/dist/docs/assets/{guides_slack.md.DVjNyqq5.lean.js → guides_slack.md.Bjw2r2gL.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +35 -35
- package/dist/docs/deployment.html +35 -35
- package/dist/docs/evals.html +35 -35
- package/dist/docs/guides/agent-to-agent.html +35 -35
- package/dist/docs/guides/bitbucket.html +35 -35
- package/dist/docs/guides/cloud-agents.html +35 -35
- package/dist/docs/guides/convert-automation.html +35 -35
- package/dist/docs/guides/github.html +35 -35
- package/dist/docs/guides/gitlab.html +35 -35
- package/dist/docs/guides/grokbot-agents.html +35 -35
- package/dist/docs/guides/hooks.html +35 -35
- package/dist/docs/guides/improve.html +35 -35
- package/dist/docs/guides/jev.html +35 -35
- package/dist/docs/guides/mcp-oauth.html +35 -35
- package/dist/docs/guides/opentelemetry.html +35 -35
- package/dist/docs/guides/slack.html +39 -39
- package/dist/docs/guides/slack.md +15 -7
- package/dist/docs/guides/webhooks.html +35 -35
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +35 -35
- package/dist/docs/index.html +35 -35
- package/dist/docs/llms-full.txt +15 -7
- package/dist/docs/quickstart.html +35 -35
- package/dist/docs/reference/agent-config.html +35 -35
- package/dist/docs/reference/artifacts.html +35 -35
- package/dist/docs/reference/channels.html +35 -35
- package/dist/docs/reference/cli.html +35 -35
- package/dist/docs/reference/connections.html +35 -35
- package/dist/docs/reference/evals.html +35 -35
- package/dist/docs/reference/extensions.html +35 -35
- package/dist/docs/reference/hooks.html +35 -35
- package/dist/docs/reference/http-api.html +35 -35
- package/dist/docs/reference/instructions.html +35 -35
- package/dist/docs/reference/playground.html +35 -35
- package/dist/docs/reference/project-layout.html +35 -35
- package/dist/docs/reference/prompt.html +35 -35
- package/dist/docs/reference/schedules.html +35 -35
- package/dist/docs/reference/sessions.html +35 -35
- package/dist/docs/reference/skills.html +35 -35
- package/dist/docs/reference/subagents.html +35 -35
- package/dist/docs/reference/tools.html +35 -35
- package/dist/docs/templates/agentic-owners.html +35 -35
- package/dist/docs/templates/pr-autofixer.html +35 -35
- package/dist/docs/templates/security-reviewer.html +35 -35
- package/dist/docs/templates/thermo-quality-review.html +35 -35
- package/dist/docs/templates/thermo-review.html +35 -35
- package/dist/docs/templates/triage.html +35 -35
- package/dist/docs/troubleshooting.html +35 -35
- package/dist/files-backends/cursor-hosted.d.ts +11 -17
- package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/files-backends/cursor-hosted.js +13 -41
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/internal/artifacts-store.d.ts +11 -0
- package/dist/internal/artifacts-store.d.ts.map +1 -1
- package/dist/internal/artifacts-store.js +113 -18
- package/dist/internal/cursor/cursor-api-transport.d.ts +37 -0
- package/dist/internal/cursor/cursor-api-transport.d.ts.map +1 -0
- package/dist/internal/cursor/cursor-api-transport.js +44 -0
- package/dist/internal/cursor/hosted-store-secrets.d.ts +21 -0
- package/dist/internal/cursor/hosted-store-secrets.d.ts.map +1 -1
- package/dist/internal/cursor/hosted-store-secrets.js +26 -0
- package/dist/internal/cursor/store-api-client.d.ts +82 -0
- package/dist/internal/cursor/store-api-client.d.ts.map +1 -0
- package/dist/internal/cursor/store-api-client.js +227 -0
- package/dist/internal/deploy-manifest.d.ts +8 -0
- package/dist/internal/deploy-manifest.d.ts.map +1 -1
- package/dist/internal/deploy-manifest.js +7 -1
- package/dist/internal/reminder-runner.d.ts.map +1 -1
- package/dist/internal/reminder-runner.js +13 -4
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +13 -15
- package/dist/internal/store-api-protocol.d.ts +134 -0
- package/dist/internal/store-api-protocol.d.ts.map +1 -0
- package/dist/internal/store-api-protocol.js +126 -0
- package/dist/memory.d.ts +49 -14
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +193 -74
- package/dist/playground/assets/{index-CX6oTKwz.js → index-Cs0MKsv4.js} +30 -30
- package/dist/playground/index.html +1 -1
- package/dist/reminders.d.ts +1 -1
- package/dist/reminders.d.ts.map +1 -1
- package/dist/types.d.ts +28 -1
- package/dist/types.d.ts.map +1 -1
- package/docs/guides/slack.md +15 -7
- package/package.json +1 -1
- package/skills/setup-slack/SKILL.md +1 -1
- package/src/channels/slack/channel-watch.ts +57 -8
- package/src/channels/slack/slack-channel.ts +4 -3
- package/src/channels/slack/types.ts +11 -12
- package/src/files-backends/cursor-hosted.ts +31 -68
- package/src/index.ts +1 -0
- package/src/internal/artifacts-store.ts +131 -25
- package/src/internal/cursor/cursor-api-transport.ts +73 -0
- package/src/internal/cursor/hosted-store-secrets.ts +35 -0
- package/src/internal/cursor/store-api-client.ts +360 -0
- package/src/internal/deploy-manifest.ts +15 -0
- package/src/internal/reminder-runner.ts +30 -5
- package/src/internal/session-engine.ts +17 -22
- package/src/internal/store-api-protocol.ts +222 -0
- package/src/memory.ts +240 -93
- package/src/reminders.ts +1 -0
- package/src/types.ts +31 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DjNBgxTF.js +0 -1
- package/dist/docs/assets/chunks/channel.BNF8VK-B.js +0 -1
- package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.iViHzfrK.js +0 -1
- package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.iViHzfrK.js +0 -1
- package/dist/docs/assets/chunks/clone.CJoR2BBM.js +0 -1
- package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.nV8Nl-td.js +0 -1
- package/dist/docs/assets/chunks/wardley-RL74JXVD.Do4PwzeN.js +0 -162
|
@@ -57,19 +57,19 @@ import {
|
|
|
57
57
|
type FileSinkPutResult,
|
|
58
58
|
} from "../files.js";
|
|
59
59
|
import {
|
|
60
|
-
|
|
60
|
+
type CursorApiTransportOptions,
|
|
61
|
+
resolveCursorApiBaseUrl,
|
|
62
|
+
resolveCursorApiFetch,
|
|
63
|
+
resolveCursorApiKey,
|
|
64
|
+
} from "../internal/cursor/cursor-api-transport.js";
|
|
65
|
+
import {
|
|
61
66
|
resolveDeploymentStoreSourceIdSync,
|
|
62
67
|
resolveFrameworkStoreSourceIdSync,
|
|
63
68
|
resolveStoreProtocolSync,
|
|
69
|
+
storeProtocolEnablesFactoryFlush,
|
|
64
70
|
} from "../internal/cursor/hosted-store-secrets.js";
|
|
65
71
|
import { createAgentStorePresignedUrlValidatorForBackend } from "./agent-store-presigned-url.js";
|
|
66
72
|
|
|
67
|
-
/**
|
|
68
|
-
* Same default as the hosted storage backend, inlined so this entrypoint does
|
|
69
|
-
* not import CLI code.
|
|
70
|
-
*/
|
|
71
|
-
const DEFAULT_CURSOR_EXTERNAL_API_URL = "https://api.cursor.com";
|
|
72
|
-
|
|
73
73
|
const AGENT_SERVE_FILES_PATH = "/v0/agent-serve-files";
|
|
74
74
|
|
|
75
75
|
/**
|
|
@@ -156,18 +156,12 @@ export function isCursorUserStoreFilesAvailable(
|
|
|
156
156
|
return key !== undefined && key !== "";
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
* Lazy credential, re-resolved on every call and preferred over
|
|
166
|
-
* {@link apiKey} — hosted freeze snapshots listen unsigned and bind the
|
|
167
|
-
* key file after serve start. Kept separate so `apiKey` stays a plain
|
|
168
|
-
* string.
|
|
169
|
-
*/
|
|
170
|
-
resolveApiKey?: () => string | undefined;
|
|
159
|
+
/**
|
|
160
|
+
* Base URL / credential / fetch resolution is the shared
|
|
161
|
+
* {@link CursorApiTransportOptions} (`internal/cursor/cursor-api-transport.ts`),
|
|
162
|
+
* so this sink and the domain-lane store API client resolve identically.
|
|
163
|
+
*/
|
|
164
|
+
export interface CursorHostedFilesOptions extends CursorApiTransportOptions {
|
|
171
165
|
/** File store to address (default: `AGENT_SERVE_STORE_SOURCE_ID`). */
|
|
172
166
|
storeSourceId?: string;
|
|
173
167
|
/**
|
|
@@ -178,13 +172,12 @@ export interface CursorHostedFilesOptions {
|
|
|
178
172
|
resolveStoreSourceId?: () => string | undefined;
|
|
179
173
|
/**
|
|
180
174
|
* Lazy store protocol pin (default: env, then the task-bound file). When it
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
175
|
+
* enables the factory flush wire (`factory-flush-v1`, or the superset
|
|
176
|
+
* domain-API pin `factory-api-v1`) and the source id maps to a deployment
|
|
177
|
+
* store role, requests ride the factory store API instead of
|
|
178
|
+
* `/v0/agent-serve-files`. Any other value keeps `/v0`.
|
|
184
179
|
*/
|
|
185
180
|
resolveStoreProtocol?: () => string | undefined;
|
|
186
|
-
/** Test seam; defaults to global fetch. */
|
|
187
|
-
fetch?: typeof fetch;
|
|
188
181
|
}
|
|
189
182
|
|
|
190
183
|
/**
|
|
@@ -295,7 +288,7 @@ export function cursorHostedFiles(
|
|
|
295
288
|
const sourceId = resolveStoreSourceId(options);
|
|
296
289
|
const protocol =
|
|
297
290
|
options.resolveStoreProtocol?.() ?? resolveStoreProtocolSync();
|
|
298
|
-
if (protocol
|
|
291
|
+
if (storeProtocolEnablesFactoryFlush(protocol)) {
|
|
299
292
|
// The factory API addresses stores by role, so map the pinned source id
|
|
300
293
|
// back to the role it was pinned from. A source id matching neither (a
|
|
301
294
|
// local-dev override) has no role there and stays on `/v0`, as does any
|
|
@@ -567,7 +560,7 @@ export function cursorHostedFiles(
|
|
|
567
560
|
};
|
|
568
561
|
}
|
|
569
562
|
try {
|
|
570
|
-
const response = await
|
|
563
|
+
const response = await resolveCursorApiFetch(options)(
|
|
571
564
|
assertPresignedUrlSafe(options, instruction.url),
|
|
572
565
|
{
|
|
573
566
|
method: "PUT",
|
|
@@ -818,7 +811,7 @@ export function cursorHostedFiles(
|
|
|
818
811
|
if (url === undefined) {
|
|
819
812
|
return undefined;
|
|
820
813
|
}
|
|
821
|
-
const response = await
|
|
814
|
+
const response = await resolveCursorApiFetch(options)(
|
|
822
815
|
assertPresignedUrlSafe(options, url),
|
|
823
816
|
{
|
|
824
817
|
method: "GET",
|
|
@@ -992,9 +985,9 @@ function assertPresignedUrlSafe(
|
|
|
992
985
|
return url.toString();
|
|
993
986
|
}
|
|
994
987
|
try {
|
|
995
|
-
createAgentStorePresignedUrlValidatorForBackend(
|
|
996
|
-
|
|
997
|
-
);
|
|
988
|
+
createAgentStorePresignedUrlValidatorForBackend(
|
|
989
|
+
resolveCursorApiBaseUrl(options)
|
|
990
|
+
)(url);
|
|
998
991
|
} catch (error) {
|
|
999
992
|
throw new Error(
|
|
1000
993
|
`cursorHostedFiles: refused presigned URL: ${
|
|
@@ -1005,14 +998,6 @@ function assertPresignedUrlSafe(
|
|
|
1005
998
|
return url.toString();
|
|
1006
999
|
}
|
|
1007
1000
|
|
|
1008
|
-
function fetchImpl(options: CursorHostedFilesOptions): typeof fetch {
|
|
1009
|
-
const impl = options.fetch ?? globalThis.fetch;
|
|
1010
|
-
if (typeof impl !== "function") {
|
|
1011
|
-
throw new Error("cursorHostedFiles requires fetch");
|
|
1012
|
-
}
|
|
1013
|
-
return impl;
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
1001
|
/**
|
|
1017
1002
|
* The file store this engine's deployment owns. Every request names it, so
|
|
1018
1003
|
* the server resolves the store row and this process never handles a store id.
|
|
@@ -1034,17 +1019,6 @@ function resolveStoreSourceId(options: CursorHostedFilesOptions): string {
|
|
|
1034
1019
|
return id;
|
|
1035
1020
|
}
|
|
1036
1021
|
|
|
1037
|
-
function resolveApiKey(options: CursorHostedFilesOptions): string {
|
|
1038
|
-
const configured = options.resolveApiKey?.() ?? options.apiKey;
|
|
1039
|
-
const key = (configured ?? process.env.CURSOR_API_KEY)?.trim();
|
|
1040
|
-
if (key === undefined || key === "") {
|
|
1041
|
-
throw new Error(
|
|
1042
|
-
"cursorHostedFiles requires CURSOR_API_KEY (deployment pod credential on Cursor hosting)"
|
|
1043
|
-
);
|
|
1044
|
-
}
|
|
1045
|
-
return key;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
1022
|
const DEV_PRESIGN_HOSTS: ReadonlySet<string> = new Set(
|
|
1049
1023
|
(process.env.CURSOR_HOSTED_FILES_DEV_PRESIGN_HOSTS ?? "")
|
|
1050
1024
|
.split(",")
|
|
@@ -1052,19 +1026,6 @@ const DEV_PRESIGN_HOSTS: ReadonlySet<string> = new Set(
|
|
|
1052
1026
|
.filter(host => host.length > 0)
|
|
1053
1027
|
);
|
|
1054
1028
|
|
|
1055
|
-
function resolveBaseUrl(options: CursorHostedFilesOptions): string {
|
|
1056
|
-
return trimTrailingSlash(
|
|
1057
|
-
options.baseUrl ??
|
|
1058
|
-
process.env.CURSOR_API_BASE_URL ??
|
|
1059
|
-
DEFAULT_CURSOR_EXTERNAL_API_URL
|
|
1060
|
-
);
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
function trimTrailingSlash(raw: string): string {
|
|
1064
|
-
const trimmed = raw.trim();
|
|
1065
|
-
return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
1029
|
/**
|
|
1069
1030
|
* Public agent-serve-files API call. The pod credential is the whole authorization:
|
|
1070
1031
|
* the API forwards this caller's identity to the store service, which checks it
|
|
@@ -1077,7 +1038,7 @@ async function storePost<T>(
|
|
|
1077
1038
|
): Promise<T> {
|
|
1078
1039
|
return await postJson<T>(
|
|
1079
1040
|
options,
|
|
1080
|
-
`${
|
|
1041
|
+
`${resolveCursorApiBaseUrl(options)}${AGENT_SERVE_FILES_PATH}${path}`,
|
|
1081
1042
|
body
|
|
1082
1043
|
);
|
|
1083
1044
|
}
|
|
@@ -1088,7 +1049,7 @@ async function storeGet<T>(
|
|
|
1088
1049
|
): Promise<T> {
|
|
1089
1050
|
return await sendJson<T>(
|
|
1090
1051
|
options,
|
|
1091
|
-
`${
|
|
1052
|
+
`${resolveCursorApiBaseUrl(options)}${AGENT_SERVE_FILES_PATH}${path}`,
|
|
1092
1053
|
"GET"
|
|
1093
1054
|
);
|
|
1094
1055
|
}
|
|
@@ -1096,7 +1057,9 @@ async function storeGet<T>(
|
|
|
1096
1057
|
/**
|
|
1097
1058
|
* Factory store API call — same base URL and pod credential as
|
|
1098
1059
|
* {@link storePost}, different path family. The server resolves the
|
|
1099
|
-
* deployment (and the addressed store role) from the credential.
|
|
1060
|
+
* deployment (and the addressed store role) from the credential. The
|
|
1061
|
+
* domain-call lane one path family deeper is NOT here: it belongs to the
|
|
1062
|
+
* store API client (`../internal/cursor/store-api-client.ts`).
|
|
1100
1063
|
*/
|
|
1101
1064
|
async function flushPost<T>(
|
|
1102
1065
|
options: CursorHostedFilesOptions,
|
|
@@ -1105,7 +1068,7 @@ async function flushPost<T>(
|
|
|
1105
1068
|
): Promise<T> {
|
|
1106
1069
|
return await postJson<T>(
|
|
1107
1070
|
options,
|
|
1108
|
-
`${
|
|
1071
|
+
`${resolveCursorApiBaseUrl(options)}${AGENTSDK_STORE_PATH}${path}`,
|
|
1109
1072
|
body
|
|
1110
1073
|
);
|
|
1111
1074
|
}
|
|
@@ -1124,10 +1087,10 @@ async function sendJson<T>(
|
|
|
1124
1087
|
method: "GET" | "POST",
|
|
1125
1088
|
body?: Record<string, unknown>
|
|
1126
1089
|
): Promise<T> {
|
|
1127
|
-
const response = await
|
|
1090
|
+
const response = await resolveCursorApiFetch(options)(url, {
|
|
1128
1091
|
method,
|
|
1129
1092
|
headers: {
|
|
1130
|
-
Authorization: `Bearer ${
|
|
1093
|
+
Authorization: `Bearer ${resolveCursorApiKey(options)}`,
|
|
1131
1094
|
...(body === undefined ? {} : { "Content-Type": "application/json" }),
|
|
1132
1095
|
},
|
|
1133
1096
|
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
package/src/index.ts
CHANGED
|
@@ -31,14 +31,34 @@ import type {
|
|
|
31
31
|
ArtifactTagInput,
|
|
32
32
|
JsonValue,
|
|
33
33
|
} from "../types.js";
|
|
34
|
+
import { mapBounded } from "./bounded.js";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Row-body hydration fan-out cap. `list()` fetches one object per row; on
|
|
38
|
+
* the Agent Store each fetch is a control-plane request, and an unbounded
|
|
39
|
+
* `Promise.all` over ~1k rows is a fleet-visible read burst (the 2026-09-18
|
|
40
|
+
* Agent Store read storm was this exact fan-out, run once per insert by the
|
|
41
|
+
* pre-metadata eviction path).
|
|
42
|
+
*/
|
|
43
|
+
const ROW_HYDRATION_CONCURRENCY = 8;
|
|
44
|
+
|
|
45
|
+
/** One row's identity + modification metadata, without its body. */
|
|
46
|
+
interface ArtifactRowMeta {
|
|
47
|
+
id: string;
|
|
48
|
+
/** Last-modified epoch ms, when the backing store tracks one. */
|
|
49
|
+
mtimeMs?: number;
|
|
50
|
+
}
|
|
34
51
|
|
|
35
52
|
interface ArtifactRowStore {
|
|
36
53
|
get(id: string): Promise<ArtifactRecord | undefined>;
|
|
37
54
|
put(record: ArtifactRecord): Promise<void>;
|
|
38
55
|
delete(id: string): Promise<void>;
|
|
39
56
|
list(): Promise<ArtifactRecord[]>;
|
|
40
|
-
/**
|
|
41
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Row ids + modification metadata without hydrating bodies — the
|
|
59
|
+
* per-insert cap check and (when mtimes are tracked) the eviction order.
|
|
60
|
+
*/
|
|
61
|
+
listMeta(): Promise<ArtifactRowMeta[]>;
|
|
42
62
|
}
|
|
43
63
|
|
|
44
64
|
/** File/blob payload bytes, keyed by artifact id (metadata lives on the row). */
|
|
@@ -202,21 +222,54 @@ export class ArtifactsStore {
|
|
|
202
222
|
return { kind, data: input.data };
|
|
203
223
|
}
|
|
204
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Trim to the retention cap from listing metadata alone. Past the cap
|
|
227
|
+
* this runs on every new-row insert, so it must not hydrate row bodies:
|
|
228
|
+
* on the Agent Store that is one read per row (~`max` reads per insert,
|
|
229
|
+
* forever, since eviction trims right back to the cap) — the 2026-09-18
|
|
230
|
+
* read storm on the security-reviewer deployment. The store listing
|
|
231
|
+
* carries last-modified metadata, and a row is rewritten on every upsert,
|
|
232
|
+
* so listing mtime orders rows the same way their `updatedAt` does.
|
|
233
|
+
*/
|
|
205
234
|
private async evictPastCap(): Promise<void> {
|
|
206
|
-
|
|
235
|
+
const metas = await this.rows.listMeta();
|
|
236
|
+
const excess = metas.length - this.max;
|
|
237
|
+
if (excess <= 0) {
|
|
207
238
|
return;
|
|
208
239
|
}
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
240
|
+
for (const id of await this.oldestRowIds(metas, excess)) {
|
|
241
|
+
await this.rows.delete(id);
|
|
242
|
+
// Without the row body there is no `content` flag to consult; delete
|
|
243
|
+
// the payload unconditionally — absent payloads delete as a no-op.
|
|
244
|
+
await this.content.delete(id);
|
|
212
245
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** The `excess` oldest row ids, cheapest available ordering first. */
|
|
249
|
+
private async oldestRowIds(
|
|
250
|
+
metas: ArtifactRowMeta[],
|
|
251
|
+
excess: number
|
|
252
|
+
): Promise<string[]> {
|
|
253
|
+
if (metas.some(meta => meta.mtimeMs !== undefined)) {
|
|
254
|
+
// A store that tracks mtimes must never regress to per-insert
|
|
255
|
+
// hydration. The rare entry missing one (anomalous on such a store)
|
|
256
|
+
// sorts oldest via the comparator's `?? 0` and gets evicted first —
|
|
257
|
+
// evicting an anomaly beats re-reading every row on every insert.
|
|
258
|
+
return metas
|
|
259
|
+
.sort(byOldestModified)
|
|
260
|
+
.slice(0, excess)
|
|
261
|
+
.map(meta => meta.id);
|
|
219
262
|
}
|
|
263
|
+
// A store that tracks no modification times at all (the local
|
|
264
|
+
// filesystem store, deliberately) leaves only the rows' own
|
|
265
|
+
// `updatedAt`: hydrate — bounded — and order exactly. Corrupt rows
|
|
266
|
+
// drop out of `list()`, so recompute the excess against the rows that
|
|
267
|
+
// actually parsed.
|
|
268
|
+
const records = await this.rows.list();
|
|
269
|
+
return records
|
|
270
|
+
.sort((a, b) => byNewestUpdated(b, a))
|
|
271
|
+
.slice(0, Math.max(0, records.length - this.max))
|
|
272
|
+
.map(record => record.id);
|
|
220
273
|
}
|
|
221
274
|
}
|
|
222
275
|
|
|
@@ -228,6 +281,18 @@ export function artifactIdForKey(key: string): string {
|
|
|
228
281
|
return `art_${createHash("sha256").update(key, "utf8").digest("hex").slice(0, 32)}`;
|
|
229
282
|
}
|
|
230
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Oldest-modified first. Store mtimes can be coarse (S3 rounds to the
|
|
286
|
+
* second), so equal stamps tie-break by id — deterministic, and within a
|
|
287
|
+
* same-stamp cohort any victim is equally past the retention cap.
|
|
288
|
+
*/
|
|
289
|
+
function byOldestModified(a: ArtifactRowMeta, b: ArtifactRowMeta): number {
|
|
290
|
+
if (a.mtimeMs !== b.mtimeMs) {
|
|
291
|
+
return (a.mtimeMs ?? 0) - (b.mtimeMs ?? 0);
|
|
292
|
+
}
|
|
293
|
+
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
294
|
+
}
|
|
295
|
+
|
|
231
296
|
function byNewestUpdated(a: ArtifactRecord, b: ArtifactRecord): number {
|
|
232
297
|
if (a.updatedAt !== b.updatedAt) {
|
|
233
298
|
return a.updatedAt < b.updatedAt ? 1 : -1;
|
|
@@ -311,8 +376,10 @@ function createSinkRowStore(
|
|
|
311
376
|
const entries = await sink.list(
|
|
312
377
|
agentStoreKeys.artifactRowPrefix(agentName)
|
|
313
378
|
);
|
|
314
|
-
const bodies = await
|
|
315
|
-
entries
|
|
379
|
+
const bodies = await mapBounded(
|
|
380
|
+
entries,
|
|
381
|
+
ROW_HYDRATION_CONCURRENCY,
|
|
382
|
+
entry => sink.get(entry.key)
|
|
316
383
|
);
|
|
317
384
|
const records: ArtifactRecord[] = [];
|
|
318
385
|
for (const body of bodies) {
|
|
@@ -323,15 +390,42 @@ function createSinkRowStore(
|
|
|
323
390
|
}
|
|
324
391
|
return records;
|
|
325
392
|
},
|
|
326
|
-
async
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
)
|
|
330
|
-
|
|
393
|
+
async listMeta(): Promise<ArtifactRowMeta[]> {
|
|
394
|
+
const prefix = agentStoreKeys.artifactRowPrefix(agentName);
|
|
395
|
+
const metas: ArtifactRowMeta[] = [];
|
|
396
|
+
for (const entry of await sink.list(prefix)) {
|
|
397
|
+
const id = rowIdFromName(entry.key.slice(prefix.length));
|
|
398
|
+
if (id !== undefined) {
|
|
399
|
+
metas.push({ id, mtimeMs: entry.mtimeMs });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return metas;
|
|
331
403
|
},
|
|
332
404
|
};
|
|
333
405
|
}
|
|
334
406
|
|
|
407
|
+
/**
|
|
408
|
+
* Invert the row naming scheme (`encodeURIComponent(id) + ".json"`).
|
|
409
|
+
* Foreign keys under the prefix — no `.json` suffix, malformed escapes,
|
|
410
|
+
* non-canonical encodings — read as absent rather than throwing. The
|
|
411
|
+
* round-trip check matters for eviction: a name that decodes but does not
|
|
412
|
+
* re-encode to itself (`a+b.json`, `foo bar.json`) would count toward the
|
|
413
|
+
* cap yet be undeletable through `key(id)`, leaving a phantom victim that
|
|
414
|
+
* every subsequent insert re-selects and fails to remove.
|
|
415
|
+
*/
|
|
416
|
+
function rowIdFromName(name: string): string | undefined {
|
|
417
|
+
if (!name.endsWith(".json") || name.includes("/")) {
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
const encoded = name.slice(0, -".json".length);
|
|
421
|
+
try {
|
|
422
|
+
const id = decodeURIComponent(encoded);
|
|
423
|
+
return encodeURIComponent(id) === encoded ? id : undefined;
|
|
424
|
+
} catch {
|
|
425
|
+
return undefined;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
335
429
|
function createSinkContentStore(
|
|
336
430
|
sink: FileSink,
|
|
337
431
|
agentName: string
|
|
@@ -410,18 +504,30 @@ function createFilesystemRowStore(stateRoot: string): ArtifactRowStore {
|
|
|
410
504
|
}
|
|
411
505
|
return records;
|
|
412
506
|
},
|
|
413
|
-
async
|
|
507
|
+
async listMeta(): Promise<ArtifactRowMeta[]> {
|
|
508
|
+
// No mtimes on purpose: local rows are cheap to hydrate, and the
|
|
509
|
+
// rows' own `updatedAt` orders eviction exactly, immune to coarse
|
|
510
|
+
// filesystem timestamp granularity.
|
|
511
|
+
let names: string[];
|
|
414
512
|
try {
|
|
415
|
-
|
|
416
|
-
return names.filter(
|
|
417
|
-
name => !name.startsWith(".") && name.endsWith(".json")
|
|
418
|
-
).length;
|
|
513
|
+
names = await readdir(dir);
|
|
419
514
|
} catch (error) {
|
|
420
515
|
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
421
|
-
return
|
|
516
|
+
return [];
|
|
422
517
|
}
|
|
423
518
|
throw error;
|
|
424
519
|
}
|
|
520
|
+
const metas: ArtifactRowMeta[] = [];
|
|
521
|
+
for (const name of names) {
|
|
522
|
+
if (name.startsWith(".")) {
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
const id = rowIdFromName(name);
|
|
526
|
+
if (id !== undefined) {
|
|
527
|
+
metas.push({ id });
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return metas;
|
|
425
531
|
},
|
|
426
532
|
};
|
|
427
533
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one resolution of "which Cursor control plane, as whom" for hosted
|
|
3
|
+
* transports. The verb-lane files sink (`files-backends/cursor-hosted.ts`)
|
|
4
|
+
* and the domain-lane store API client (`store-api-client.ts`) both build on
|
|
5
|
+
* these, so the pod credential and base URL resolve identically on every
|
|
6
|
+
* wire — one place to change, no drift between lanes.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Same default as the hosted storage backend, inlined so these entrypoints
|
|
11
|
+
* do not import CLI code.
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_CURSOR_EXTERNAL_API_URL = "https://api.cursor.com";
|
|
14
|
+
|
|
15
|
+
export interface CursorApiTransportOptions {
|
|
16
|
+
/** Control-plane base URL (default: `CURSOR_API_BASE_URL`, else api.cursor.com). */
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
/** Bearer credential (default: `CURSOR_API_KEY`). */
|
|
19
|
+
apiKey?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Lazy credential, re-resolved on every call and preferred over
|
|
22
|
+
* {@link apiKey} — hosted freeze snapshots listen unsigned and bind the
|
|
23
|
+
* key file after serve start. Kept separate so `apiKey` stays a plain
|
|
24
|
+
* string.
|
|
25
|
+
*/
|
|
26
|
+
resolveApiKey?: () => string | undefined;
|
|
27
|
+
/** Test seam; defaults to global fetch. */
|
|
28
|
+
fetch?: typeof fetch;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function trimTrailingSlash(raw: string): string {
|
|
32
|
+
const trimmed = raw.trim();
|
|
33
|
+
return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resolveCursorApiBaseUrl(
|
|
37
|
+
options: CursorApiTransportOptions
|
|
38
|
+
): string {
|
|
39
|
+
return trimTrailingSlash(
|
|
40
|
+
options.baseUrl ??
|
|
41
|
+
process.env.CURSOR_API_BASE_URL ??
|
|
42
|
+
DEFAULT_CURSOR_EXTERNAL_API_URL
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The pod credential is the whole authorization on every hosted wire: the
|
|
48
|
+
* API resolves the caller's deployment from it, so there is no second,
|
|
49
|
+
* narrower credential to manage. Fails loudly rather than defaulting —
|
|
50
|
+
* calling the control plane as nobody is worse than not calling.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveCursorApiKey(
|
|
53
|
+
options: CursorApiTransportOptions
|
|
54
|
+
): string {
|
|
55
|
+
const configured = options.resolveApiKey?.() ?? options.apiKey;
|
|
56
|
+
const key = (configured ?? process.env.CURSOR_API_KEY)?.trim();
|
|
57
|
+
if (key === undefined || key === "") {
|
|
58
|
+
throw new Error(
|
|
59
|
+
"Cursor hosted transport requires CURSOR_API_KEY (deployment pod credential on Cursor hosting)"
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return key;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function resolveCursorApiFetch(
|
|
66
|
+
options: CursorApiTransportOptions
|
|
67
|
+
): typeof fetch {
|
|
68
|
+
const impl = options.fetch ?? globalThis.fetch;
|
|
69
|
+
if (typeof impl !== "function") {
|
|
70
|
+
throw new Error("Cursor hosted transport requires fetch");
|
|
71
|
+
}
|
|
72
|
+
return impl;
|
|
73
|
+
}
|
|
@@ -26,6 +26,41 @@ export const DEFAULT_AGENT_SERVE_STORE_PROTOCOL_FILE =
|
|
|
26
26
|
*/
|
|
27
27
|
export const AGENT_SERVE_FACTORY_FLUSH_PROTOCOL = "factory-flush-v1";
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Pin value additionally enabling the domain-call lane
|
|
31
|
+
* (`POST /internal/agentsdk/store/api/<call>`), where the server owns the
|
|
32
|
+
* storage pattern for framework state — starting with `memory.append`. A
|
|
33
|
+
* strict superset of {@link AGENT_SERVE_FACTORY_FLUSH_PROTOCOL}: verbs keep
|
|
34
|
+
* riding the factory flush wire under this pin too. The same fail-safe
|
|
35
|
+
* applies in both directions — an unknown value keeps today's behavior, and
|
|
36
|
+
* re-pinning to `factory-flush-v1` turns the domain lane back off without a
|
|
37
|
+
* client change.
|
|
38
|
+
*/
|
|
39
|
+
export const AGENT_SERVE_FACTORY_API_PROTOCOL = "factory-api-v1";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Does this pin select the factory flush wire for the store verbs? True for
|
|
43
|
+
* the flush pin and for the domain-API superset pin.
|
|
44
|
+
*/
|
|
45
|
+
export function storeProtocolEnablesFactoryFlush(
|
|
46
|
+
protocol: string | undefined
|
|
47
|
+
): boolean {
|
|
48
|
+
return (
|
|
49
|
+
protocol === AGENT_SERVE_FACTORY_FLUSH_PROTOCOL ||
|
|
50
|
+
protocol === AGENT_SERVE_FACTORY_API_PROTOCOL
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Does this pin enable the domain-call lane (server-owned storage patterns)?
|
|
56
|
+
* Only its exact value does; everything else keeps the client-side patterns.
|
|
57
|
+
*/
|
|
58
|
+
export function storeProtocolEnablesStoreApi(
|
|
59
|
+
protocol: string | undefined
|
|
60
|
+
): boolean {
|
|
61
|
+
return protocol === AGENT_SERVE_FACTORY_API_PROTOCOL;
|
|
62
|
+
}
|
|
63
|
+
|
|
29
64
|
const STORE_SOURCE_ID_ENV = "AGENT_SERVE_STORE_SOURCE_ID";
|
|
30
65
|
const FRAMEWORK_STORE_SOURCE_ID_ENV = "AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID";
|
|
31
66
|
const STORE_SOURCE_ID_FILE_ENV = "AGENT_SERVE_STORE_SOURCE_ID_FILE";
|