@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
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The store API client: July's one gateway to the factory domain-call lane,
|
|
3
|
+
* `POST /internal/agentsdk/store/api/<call>`. This module owns the whole
|
|
4
|
+
* transport — base URL and deployment credential (the same resolution the
|
|
5
|
+
* files sink uses, via `cursor-api-transport.ts`), envelope encoding,
|
|
6
|
+
* response validation, typed error mapping, and the bounded backoff policy.
|
|
7
|
+
* Feature modules (`memory.ts`, later `session-run-log.ts`, …) call typed
|
|
8
|
+
* methods like {@link StoreApiClient.memoryAppend} and never hand-roll
|
|
9
|
+
* fetch; future domain calls (`sessionRun.report`, `startLog.flush`, …) are
|
|
10
|
+
* added here as new methods on this one client.
|
|
11
|
+
*
|
|
12
|
+
* The lane is selected per pod by the task-bound store protocol pin:
|
|
13
|
+
* {@link isStoreApiLaneEnabled} is true only under the exact
|
|
14
|
+
* `factory-api-v1` pin AND with a provisioned deployment store. The
|
|
15
|
+
* availability check is not redundant: the pin file is written by the hosted
|
|
16
|
+
* task bind, but a local-dev env var could name the pin without any store to
|
|
17
|
+
* serve reads — appending into the void. Both resolve lazily per call, so a
|
|
18
|
+
* pin bound after serve start takes effect without a restart.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
MEMORY_APPEND_MAX_RECORD_BYTES,
|
|
23
|
+
memoryAppendResponseSchema,
|
|
24
|
+
memoryReadResponseSchema,
|
|
25
|
+
STORE_API_MEMORY_APPEND_CALL,
|
|
26
|
+
STORE_API_MEMORY_READ_CALL,
|
|
27
|
+
STORE_API_PATH,
|
|
28
|
+
STORE_API_PROTOCOL_VERSION,
|
|
29
|
+
serializedRecordBytes,
|
|
30
|
+
} from "../store-api-protocol.js";
|
|
31
|
+
import {
|
|
32
|
+
type CursorApiTransportOptions,
|
|
33
|
+
resolveCursorApiBaseUrl,
|
|
34
|
+
resolveCursorApiFetch,
|
|
35
|
+
resolveCursorApiKey,
|
|
36
|
+
} from "./cursor-api-transport.js";
|
|
37
|
+
import {
|
|
38
|
+
resolveDeploymentStoreSourceIdSync,
|
|
39
|
+
resolveStoreProtocolSync,
|
|
40
|
+
storeProtocolEnablesStoreApi,
|
|
41
|
+
} from "./hosted-store-secrets.js";
|
|
42
|
+
|
|
43
|
+
export interface StoreApiClientOptions extends CursorApiTransportOptions {
|
|
44
|
+
/** Deployment store source id override (lane availability check). */
|
|
45
|
+
storeSourceId?: string;
|
|
46
|
+
/** Lazy store source id, preferred over {@link storeSourceId}. */
|
|
47
|
+
resolveStoreSourceId?: () => string | undefined;
|
|
48
|
+
/** Lazy store protocol pin (default: env, then the task-bound file). */
|
|
49
|
+
resolveStoreProtocol?: () => string | undefined;
|
|
50
|
+
/** Backoff sleep override (tests). */
|
|
51
|
+
sleep?: (ms: number) => Promise<void>;
|
|
52
|
+
/** Jitter source override (tests). Returns [0, 1). */
|
|
53
|
+
random?: () => number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* How one domain call failed, after the client's bounded retries:
|
|
58
|
+
*
|
|
59
|
+
* - `rejected` — the server (or the client's own pre-check) refused the
|
|
60
|
+
* payload with a typed 4xx. Deterministic; never retried.
|
|
61
|
+
* - `shed` — 429 from the per-deployment limiter or kill switch. Retried
|
|
62
|
+
* only within the small Retry-After bound below; retrying harder against
|
|
63
|
+
* a shed is exactly the stampede the domain lane exists to prevent.
|
|
64
|
+
* - `unavailable` — 5xx or a network failure, retried up to the attempt
|
|
65
|
+
* bound.
|
|
66
|
+
* - `bad_response` — a 2xx whose body failed the response schema; a
|
|
67
|
+
* contract break, not a transient, so never retried.
|
|
68
|
+
*/
|
|
69
|
+
export type StoreApiFailureKind =
|
|
70
|
+
| "rejected"
|
|
71
|
+
| "shed"
|
|
72
|
+
| "unavailable"
|
|
73
|
+
| "bad_response";
|
|
74
|
+
|
|
75
|
+
export class StoreApiCallError extends Error {
|
|
76
|
+
constructor(
|
|
77
|
+
readonly kind: StoreApiFailureKind,
|
|
78
|
+
message: string,
|
|
79
|
+
readonly status?: number
|
|
80
|
+
) {
|
|
81
|
+
super(message);
|
|
82
|
+
this.name = "StoreApiCallError";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Bounded backoff policy. Domain calls run at turn boundaries (a memory
|
|
88
|
+
* append after `turn.completed`), so the budget is a few short retries, not
|
|
89
|
+
* persistence: 3 attempts total and never more than 2s asleep between them.
|
|
90
|
+
* A 429 whose Retry-After exceeds the cap is a deliberate shed (a kill
|
|
91
|
+
* switch, a hard budget) — give up immediately instead of camping on it.
|
|
92
|
+
*/
|
|
93
|
+
const STORE_API_MAX_ATTEMPTS = 3;
|
|
94
|
+
const STORE_API_MAX_BACKOFF_MS = 2_000;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Hard deadline per fetch (the envelope POSTs and the presigned journal
|
|
98
|
+
* GET). Without one, a stalled — not failed — connection rides undici's
|
|
99
|
+
* ~300 s inactivity timers, and hydration runs at the head of the session
|
|
100
|
+
* dispatch chain where a stall delays every queued handler. 15 s matches
|
|
101
|
+
* the server's own presigned-fetch deadline and is generous for a ≤5 MiB
|
|
102
|
+
* journal.
|
|
103
|
+
*/
|
|
104
|
+
const STORE_API_FETCH_TIMEOUT_MS = 15_000;
|
|
105
|
+
|
|
106
|
+
function backoffMs(attempt: number, random: () => number): number {
|
|
107
|
+
return Math.min(
|
|
108
|
+
200 * 2 ** attempt * (0.5 + random()),
|
|
109
|
+
STORE_API_MAX_BACKOFF_MS
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Is the domain-call lane on for this pod? Exact pin match plus a
|
|
115
|
+
* provisioned deployment store; anything else keeps the client-side
|
|
116
|
+
* patterns, fail-safe in both directions like the flush pin itself.
|
|
117
|
+
*/
|
|
118
|
+
export function isStoreApiLaneEnabled(
|
|
119
|
+
options: StoreApiClientOptions = {}
|
|
120
|
+
): boolean {
|
|
121
|
+
const protocol =
|
|
122
|
+
options.resolveStoreProtocol?.() ?? resolveStoreProtocolSync();
|
|
123
|
+
if (!storeProtocolEnablesStoreApi(protocol)) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
const sourceId = (
|
|
127
|
+
options.resolveStoreSourceId?.() ??
|
|
128
|
+
options.storeSourceId ??
|
|
129
|
+
resolveDeploymentStoreSourceIdSync()
|
|
130
|
+
)?.trim();
|
|
131
|
+
return sourceId !== undefined && sourceId !== "";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export type StoreApiClient = {
|
|
135
|
+
/**
|
|
136
|
+
* One `memory.append` domain call: the server appends the record to a
|
|
137
|
+
* server-owned journal shard. Resolves on success; throws
|
|
138
|
+
* {@link StoreApiCallError} after the bounded retries. The caller owns the
|
|
139
|
+
* failure policy (memory drops the record — appending is advisory).
|
|
140
|
+
*/
|
|
141
|
+
memoryAppend(payload: {
|
|
142
|
+
agent: string;
|
|
143
|
+
record: Record<string, unknown>;
|
|
144
|
+
}): Promise<void>;
|
|
145
|
+
/**
|
|
146
|
+
* One `memory.read` domain call plus the presigned byte fetch: the server
|
|
147
|
+
* flushes the agent's pending append buffer (best-effort) and presigns
|
|
148
|
+
* the canonical journal; this method then downloads it. Undefined while
|
|
149
|
+
* the agent has no journal. Throws {@link StoreApiCallError} after the
|
|
150
|
+
* bounded retries — the caller owns the failure policy (hydration keeps
|
|
151
|
+
* the existing mirror).
|
|
152
|
+
*/
|
|
153
|
+
memoryReadContent(payload: {
|
|
154
|
+
agent: string;
|
|
155
|
+
}): Promise<Uint8Array | undefined>;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export function createStoreApiClient(
|
|
159
|
+
options: StoreApiClientOptions = {}
|
|
160
|
+
): StoreApiClient {
|
|
161
|
+
const sleep =
|
|
162
|
+
options.sleep ??
|
|
163
|
+
((ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms)));
|
|
164
|
+
const random = options.random ?? Math.random;
|
|
165
|
+
|
|
166
|
+
/** One POST of a versioned envelope; returns the parsed 2xx body. */
|
|
167
|
+
const postOnce = async (
|
|
168
|
+
call: string,
|
|
169
|
+
envelope: Record<string, unknown>
|
|
170
|
+
): Promise<Response> => {
|
|
171
|
+
const fetchImpl = resolveCursorApiFetch(options);
|
|
172
|
+
return await fetchImpl(
|
|
173
|
+
`${resolveCursorApiBaseUrl(options)}${STORE_API_PATH}/${call}`,
|
|
174
|
+
{
|
|
175
|
+
method: "POST",
|
|
176
|
+
headers: {
|
|
177
|
+
Authorization: `Bearer ${resolveCursorApiKey(options)}`,
|
|
178
|
+
"Content-Type": "application/json",
|
|
179
|
+
},
|
|
180
|
+
body: JSON.stringify(envelope),
|
|
181
|
+
signal: AbortSignal.timeout(STORE_API_FETCH_TIMEOUT_MS),
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/** The response's `Retry-After` in ms, when present and numeric. */
|
|
187
|
+
const retryAfterDelayMs = (response: Response): number | undefined => {
|
|
188
|
+
const header = response.headers.get("retry-after")?.trim();
|
|
189
|
+
const seconds =
|
|
190
|
+
header === undefined || header === "" ? Number.NaN : Number(header);
|
|
191
|
+
return Number.isFinite(seconds) ? seconds * 1_000 : undefined;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const describeErrorBody = async (response: Response): Promise<string> => {
|
|
195
|
+
try {
|
|
196
|
+
const parsed = (await response.json()) as { error?: unknown };
|
|
197
|
+
if (typeof parsed?.error === "string") {
|
|
198
|
+
return parsed.error;
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
// Non-JSON error body; the status is the message.
|
|
202
|
+
}
|
|
203
|
+
return `HTTP ${response.status}`;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const callApi = async (
|
|
207
|
+
call: string,
|
|
208
|
+
envelope: Record<string, unknown>
|
|
209
|
+
): Promise<unknown> => {
|
|
210
|
+
let lastFailure: StoreApiCallError | undefined;
|
|
211
|
+
// A shed's Retry-After (when within the cap) overrides the next jittered
|
|
212
|
+
// backoff; cleared before every attempt.
|
|
213
|
+
let nextDelayMs: number | undefined;
|
|
214
|
+
for (let attempt = 0; attempt < STORE_API_MAX_ATTEMPTS; attempt += 1) {
|
|
215
|
+
if (attempt > 0) {
|
|
216
|
+
await sleep(nextDelayMs ?? backoffMs(attempt, random));
|
|
217
|
+
}
|
|
218
|
+
nextDelayMs = undefined;
|
|
219
|
+
let response: Response;
|
|
220
|
+
try {
|
|
221
|
+
response = await postOnce(call, envelope);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
lastFailure = new StoreApiCallError(
|
|
224
|
+
"unavailable",
|
|
225
|
+
`store api ${call} transport failure: ${
|
|
226
|
+
error instanceof Error ? error.message : String(error)
|
|
227
|
+
}`
|
|
228
|
+
);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (response.ok) {
|
|
232
|
+
try {
|
|
233
|
+
return (await response.json()) as unknown;
|
|
234
|
+
} catch {
|
|
235
|
+
throw new StoreApiCallError(
|
|
236
|
+
"bad_response",
|
|
237
|
+
`store api ${call} answered non-JSON (${response.status})`,
|
|
238
|
+
response.status
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (response.status === 429) {
|
|
243
|
+
const retryDelayMs = retryAfterDelayMs(response);
|
|
244
|
+
const shed = new StoreApiCallError(
|
|
245
|
+
"shed",
|
|
246
|
+
`store api ${call} shed: ${await describeErrorBody(response)}`,
|
|
247
|
+
response.status
|
|
248
|
+
);
|
|
249
|
+
if (
|
|
250
|
+
retryDelayMs !== undefined &&
|
|
251
|
+
retryDelayMs > STORE_API_MAX_BACKOFF_MS
|
|
252
|
+
) {
|
|
253
|
+
// A long Retry-After is a kill switch or hard budget: obey it by
|
|
254
|
+
// giving up now, not by camping on the route.
|
|
255
|
+
throw shed;
|
|
256
|
+
}
|
|
257
|
+
nextDelayMs = retryDelayMs;
|
|
258
|
+
lastFailure = shed;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (response.status >= 500) {
|
|
262
|
+
// A degraded 503 carries `Retry-After` too (the server's buffer-down
|
|
263
|
+
// answer). Honor it clamped to the backoff cap — unlike a shed, a
|
|
264
|
+
// 5xx is transient by definition, so a long hint never means "give
|
|
265
|
+
// up", just "wait the cap".
|
|
266
|
+
const retryDelayMs = retryAfterDelayMs(response);
|
|
267
|
+
if (retryDelayMs !== undefined) {
|
|
268
|
+
nextDelayMs = Math.min(retryDelayMs, STORE_API_MAX_BACKOFF_MS);
|
|
269
|
+
}
|
|
270
|
+
lastFailure = new StoreApiCallError(
|
|
271
|
+
"unavailable",
|
|
272
|
+
`store api ${call} failed: ${await describeErrorBody(response)}`,
|
|
273
|
+
response.status
|
|
274
|
+
);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
// Any other 4xx is deterministic; retrying re-sends the same refusal.
|
|
278
|
+
throw new StoreApiCallError(
|
|
279
|
+
"rejected",
|
|
280
|
+
`store api ${call} rejected: ${await describeErrorBody(response)}`,
|
|
281
|
+
response.status
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
throw lastFailure ?? new StoreApiCallError("unavailable", "no attempt ran");
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
async memoryAppend(payload) {
|
|
289
|
+
const recordBytes = serializedRecordBytes(payload.record);
|
|
290
|
+
if (recordBytes > MEMORY_APPEND_MAX_RECORD_BYTES) {
|
|
291
|
+
// The server-side controller enforces the same cap; checking here
|
|
292
|
+
// spares a doomed request and fails with the same typed shape.
|
|
293
|
+
throw new StoreApiCallError(
|
|
294
|
+
"rejected",
|
|
295
|
+
`memory.append record is ${recordBytes} serialized bytes; the cap is ${MEMORY_APPEND_MAX_RECORD_BYTES}`
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
const body = await callApi(STORE_API_MEMORY_APPEND_CALL, {
|
|
299
|
+
v: STORE_API_PROTOCOL_VERSION,
|
|
300
|
+
call: STORE_API_MEMORY_APPEND_CALL,
|
|
301
|
+
payload,
|
|
302
|
+
});
|
|
303
|
+
if (!memoryAppendResponseSchema.safeParse(body).success) {
|
|
304
|
+
throw new StoreApiCallError(
|
|
305
|
+
"bad_response",
|
|
306
|
+
"unexpected memory.append response shape"
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
|
|
311
|
+
async memoryReadContent(payload) {
|
|
312
|
+
const body = await callApi(STORE_API_MEMORY_READ_CALL, {
|
|
313
|
+
v: STORE_API_PROTOCOL_VERSION,
|
|
314
|
+
call: STORE_API_MEMORY_READ_CALL,
|
|
315
|
+
payload,
|
|
316
|
+
});
|
|
317
|
+
const parsed = memoryReadResponseSchema.safeParse(body);
|
|
318
|
+
if (!parsed.success) {
|
|
319
|
+
throw new StoreApiCallError(
|
|
320
|
+
"bad_response",
|
|
321
|
+
"unexpected memory.read response shape"
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
if (parsed.data.journal === null) {
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
327
|
+
// Bulk bytes ride the presigned URL, never the control plane — same
|
|
328
|
+
// split as the verb lane. Unretried: the whole call reruns at the
|
|
329
|
+
// next session start, and hydration keeps the existing mirror.
|
|
330
|
+
const fetchImpl = resolveCursorApiFetch(options);
|
|
331
|
+
let response: Response;
|
|
332
|
+
try {
|
|
333
|
+
response = await fetchImpl(parsed.data.journal.url, {
|
|
334
|
+
method: "GET",
|
|
335
|
+
redirect: "manual",
|
|
336
|
+
signal: AbortSignal.timeout(STORE_API_FETCH_TIMEOUT_MS),
|
|
337
|
+
});
|
|
338
|
+
} catch (error) {
|
|
339
|
+
throw new StoreApiCallError(
|
|
340
|
+
"unavailable",
|
|
341
|
+
`memory.read journal fetch transport failure: ${
|
|
342
|
+
error instanceof Error ? error.message : String(error)
|
|
343
|
+
}`
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
if (response.status === 404) {
|
|
347
|
+
// The presign raced a rotation or delete; absent is absent.
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
if (!response.ok) {
|
|
351
|
+
throw new StoreApiCallError(
|
|
352
|
+
"unavailable",
|
|
353
|
+
`memory.read journal fetch failed (HTTP ${response.status})`,
|
|
354
|
+
response.status
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
}
|
|
@@ -128,6 +128,10 @@ const agentDeployManifestV1Schema = z.object({
|
|
|
128
128
|
continuation: continuationIdentitySchema,
|
|
129
129
|
catch: catchDeclarationSchema.optional(),
|
|
130
130
|
slackReply: slackReplyDeclarationSchema.optional(),
|
|
131
|
+
slackChannelPosts: z
|
|
132
|
+
.array(z.string().min(1).max(80))
|
|
133
|
+
.max(64)
|
|
134
|
+
.optional(),
|
|
131
135
|
})
|
|
132
136
|
)
|
|
133
137
|
.max(64),
|
|
@@ -166,6 +170,14 @@ export interface AgentDeployManifestDeclarations {
|
|
|
166
170
|
* hosted runtime starts. Present only for `slack` channels in agentic mode.
|
|
167
171
|
*/
|
|
168
172
|
slackReply?: AgentDeployManifestSlackReplyDeclaration;
|
|
173
|
+
/**
|
|
174
|
+
* The channel-watch allowlist (`engagement.channelPosts.allow`).
|
|
175
|
+
* Present only for `slack` channels that watch channel posts. The
|
|
176
|
+
* control plane materializes channel-scoped subscription edges from
|
|
177
|
+
* the channel-id entries so posts outside the allowlist drop at
|
|
178
|
+
* ingress.
|
|
179
|
+
*/
|
|
180
|
+
slackChannelPosts?: string[];
|
|
169
181
|
/**
|
|
170
182
|
* Control-plane catch routes: present only for an authored channel
|
|
171
183
|
* whose auth chain carries a `sharedSecretAuth` / `hmacSignatureAuth`
|
|
@@ -242,6 +254,9 @@ export function buildAgentDeployManifest(
|
|
|
242
254
|
...(kind === "slack" && meta?.slackReply !== undefined
|
|
243
255
|
? { slackReply: meta.slackReply }
|
|
244
256
|
: {}),
|
|
257
|
+
...(kind === "slack" && meta?.slackChannelPosts !== undefined
|
|
258
|
+
? { slackChannelPosts: [...meta.slackChannelPosts] }
|
|
259
|
+
: {}),
|
|
245
260
|
};
|
|
246
261
|
});
|
|
247
262
|
|
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
ReminderDefinition,
|
|
17
17
|
ReminderFireContext,
|
|
18
18
|
ReminderFireResult,
|
|
19
|
+
ReminderFollowupOptions,
|
|
19
20
|
ReminderInfo,
|
|
20
21
|
ReminderStatus,
|
|
21
22
|
ReminderUntilContext,
|
|
@@ -1447,9 +1448,16 @@ export class ReminderRunner {
|
|
|
1447
1448
|
record: ReminderRecord,
|
|
1448
1449
|
message: string,
|
|
1449
1450
|
auth: AuthContext | null,
|
|
1450
|
-
settle?: FollowupSettle
|
|
1451
|
+
settle?: FollowupSettle,
|
|
1452
|
+
extra?: ReminderFollowupOptions
|
|
1451
1453
|
): Promise<ReminderFireResult> {
|
|
1452
|
-
const sendOptions =
|
|
1454
|
+
const sendOptions: EngineSendOptions = {
|
|
1455
|
+
...this.followupSendOptions(record, auth),
|
|
1456
|
+
state: extra?.state,
|
|
1457
|
+
refreshState: extra?.refreshState,
|
|
1458
|
+
title: extra?.title,
|
|
1459
|
+
clearCloudOverride: extra?.clearCloudOverride,
|
|
1460
|
+
};
|
|
1453
1461
|
try {
|
|
1454
1462
|
const session = await this.engine.send(
|
|
1455
1463
|
record.channelId,
|
|
@@ -1509,8 +1517,19 @@ export class ReminderRunner {
|
|
|
1509
1517
|
mcp: this.engine.host.mcp,
|
|
1510
1518
|
appAuth: APP_AUTH,
|
|
1511
1519
|
artifacts: this.engine.artifacts,
|
|
1512
|
-
async followup(args: { message: string }) {
|
|
1513
|
-
const
|
|
1520
|
+
async followup(args: { message: string } & ReminderFollowupOptions) {
|
|
1521
|
+
const target = await self.resolveFollowupTarget(record);
|
|
1522
|
+
let auth = target.auth;
|
|
1523
|
+
// `openAuth` opens only when nothing exists anywhere: an unreadable
|
|
1524
|
+
// durable session must not be shadowed by a second one on the same
|
|
1525
|
+
// key (prompt-fire parity).
|
|
1526
|
+
if (
|
|
1527
|
+
auth === undefined &&
|
|
1528
|
+
target.reached === "session_gone" &&
|
|
1529
|
+
args.openAuth !== undefined
|
|
1530
|
+
) {
|
|
1531
|
+
auth = args.openAuth;
|
|
1532
|
+
}
|
|
1514
1533
|
if (auth === undefined) {
|
|
1515
1534
|
// Under the CP protocol the guest never writes reminder status;
|
|
1516
1535
|
// the fire's `stop` outcome carries the reason instead.
|
|
@@ -1521,7 +1540,13 @@ export class ReminderRunner {
|
|
|
1521
1540
|
`Reminder "${record.id}" followup: session gone for continuation`
|
|
1522
1541
|
);
|
|
1523
1542
|
}
|
|
1524
|
-
const result = await self.sendFollowup(
|
|
1543
|
+
const result = await self.sendFollowup(
|
|
1544
|
+
record,
|
|
1545
|
+
args.message,
|
|
1546
|
+
auth,
|
|
1547
|
+
undefined,
|
|
1548
|
+
args
|
|
1549
|
+
);
|
|
1525
1550
|
if (result.action === "skip") {
|
|
1526
1551
|
throw new Error(
|
|
1527
1552
|
`Reminder "${record.id}" followup skipped: session busy`
|
|
@@ -1105,17 +1105,23 @@ export class SessionEngine {
|
|
|
1105
1105
|
sendOptions.continuationToken
|
|
1106
1106
|
);
|
|
1107
1107
|
}
|
|
1108
|
-
if (
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1108
|
+
if (existing === undefined && sendOptions.hostedSessionId !== undefined) {
|
|
1109
|
+
const byId = await this.getOrRestoreSession(sendOptions.hostedSessionId);
|
|
1110
|
+
if (byId !== undefined) {
|
|
1111
|
+
// evt: skips the mailbox index; same hostedSessionId is a Temporal
|
|
1112
|
+
// retry. cont:session:<id> is the explicit CP resume (keep the
|
|
1113
|
+
// original key so a PR lane stays addressable by its webhooks).
|
|
1114
|
+
if (
|
|
1115
|
+
byId.continuationKey !== sendOptions.continuationToken &&
|
|
1116
|
+
sendOptions.mailboxContinuation?.key !==
|
|
1117
|
+
hostedSessionResumeKey(sendOptions.hostedSessionId)
|
|
1118
|
+
) {
|
|
1119
|
+
throw new V2EngineError(
|
|
1120
|
+
`v2: hosted session id ${sendOptions.hostedSessionId} already belongs to another mailbox`
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
existing = byId;
|
|
1124
|
+
}
|
|
1119
1125
|
}
|
|
1120
1126
|
|
|
1121
1127
|
if (existing !== undefined) {
|
|
@@ -1223,17 +1229,6 @@ export class SessionEngine {
|
|
|
1223
1229
|
throw new HostedEnqueueNotActiveError(sendOptions.hostedSessionId);
|
|
1224
1230
|
}
|
|
1225
1231
|
|
|
1226
|
-
if (sendOptions.hostedSessionId !== undefined) {
|
|
1227
|
-
const collision = await this.getOrRestoreSession(
|
|
1228
|
-
sendOptions.hostedSessionId
|
|
1229
|
-
);
|
|
1230
|
-
if (collision !== undefined) {
|
|
1231
|
-
throw new V2EngineError(
|
|
1232
|
-
`v2: hosted session id ${sendOptions.hostedSessionId} already belongs to another mailbox`
|
|
1233
|
-
);
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
|
|
1237
1232
|
const now = new Date().toISOString();
|
|
1238
1233
|
const channelState = this.initialChannelState(channelId, sendOptions.state);
|
|
1239
1234
|
const purpose = sendOptions.purpose === "eval" ? "eval" : "live";
|