@cursor/july 0.1.114 → 0.2.0
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/inbound.d.ts +7 -0
- package/dist/channels/slack/inbound.d.ts.map +1 -1
- package/dist/channels/slack/inbound.js +23 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +83 -4
- package/dist/docs/404.html +2 -2
- package/dist/docs/assets/{app.BqkJwOZ-.js → app.9eAtsjAM.js} +4 -4
- package/dist/docs/assets/chunks/@localSearchIndexroot.DjNBgxTF.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.BJAi2KiV.js → VPLocalSearchBox.C9s69nxj.js} +1 -1
- package/dist/docs/assets/chunks/{arc.BZpXTgvV.js → arc.DmWDRaF-.js} +1 -1
- package/dist/docs/assets/chunks/{architectureDiagram-Q4EWVU46.WYI-7F-Y.js → architectureDiagram-Q4EWVU46.BE1Wj4f3.js} +1 -1
- package/dist/docs/assets/chunks/{baseUniq.CZaUPpg0.js → baseUniq.pmEZGnWu.js} +1 -1
- package/dist/docs/assets/chunks/{blockDiagram-DXYQGD6D.D6UES2pD.js → blockDiagram-DXYQGD6D.D6mM-5XN.js} +1 -1
- package/dist/docs/assets/chunks/{c4Diagram-AHTNJAMY.cwebIe4i.js → c4Diagram-AHTNJAMY.ZQYTPC1b.js} +1 -1
- package/dist/docs/assets/chunks/channel.BNF8VK-B.js +1 -0
- package/dist/docs/assets/chunks/{chunk-4BX2VUAB.fVyFnjxg.js → chunk-4BX2VUAB.CKJ7gaK8.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-4TB4RGXK.BanufG1c.js → chunk-4TB4RGXK.CL61HnvB.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-55IACEB6.VaSMz5-2.js → chunk-55IACEB6.DsA4XA1m.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-EDXVE4YY.CN2diZOM.js → chunk-EDXVE4YY.BODHCHFW.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-FMBD7UC4.g4ivypu3.js → chunk-FMBD7UC4.D6NBaZTa.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-OYMX7WX6.GZXKn9JJ.js → chunk-OYMX7WX6.BLPuqFXg.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-QZHKN3VN.itXxJZCd.js → chunk-QZHKN3VN.BFs_ML8Z.js} +1 -1
- package/dist/docs/assets/chunks/{chunk-YZCP3GAM.-rw2GfvX.js → chunk-YZCP3GAM.BDjuFyZv.js} +1 -1
- package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.iViHzfrK.js +1 -0
- package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.iViHzfrK.js +1 -0
- package/dist/docs/assets/chunks/clone.CJoR2BBM.js +1 -0
- package/dist/docs/assets/chunks/{cose-bilkent-S5V4N54A.CmaI5br0.js → cose-bilkent-S5V4N54A.D85HNEsi.js} +1 -1
- package/dist/docs/assets/chunks/{dagre-KV5264BT.4wY9S4Kt.js → dagre-KV5264BT.V463KlSF.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-5BDNPKRD.Pc3c0u9W.js → diagram-5BDNPKRD.DhBDu1ab.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-G4DWMVQ6.CYrWz-nj.js → diagram-G4DWMVQ6.v6sC68zl.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-MMDJMWI5.Bgj5hukb.js → diagram-MMDJMWI5.DhHsdXYv.js} +1 -1
- package/dist/docs/assets/chunks/{diagram-TYMM5635.DGMEXalS.js → diagram-TYMM5635.BPWNSFZc.js} +1 -1
- package/dist/docs/assets/chunks/{erDiagram-SMLLAGMA.GepTV9Im.js → erDiagram-SMLLAGMA.Cq0wTMPL.js} +1 -1
- package/dist/docs/assets/chunks/{flowDiagram-DWJPFMVM.DVKywg3j.js → flowDiagram-DWJPFMVM.MveMtucC.js} +1 -1
- package/dist/docs/assets/chunks/{ganttDiagram-T4ZO3ILL.C7qt9Mlo.js → ganttDiagram-T4ZO3ILL.BzKfGUSf.js} +1 -1
- package/dist/docs/assets/chunks/{gitGraphDiagram-UUTBAWPF.U30_r82P.js → gitGraphDiagram-UUTBAWPF.OToXTSW_.js} +1 -1
- package/dist/docs/assets/chunks/{graph.CyyMyAWv.js → graph.D82tam-l.js} +1 -1
- package/dist/docs/assets/chunks/{infoDiagram-42DDH7IO.Dn9ACW3y.js → infoDiagram-42DDH7IO.DzFlRmcE.js} +1 -1
- package/dist/docs/assets/chunks/{ishikawaDiagram-UXIWVN3A.DlIdIGOA.js → ishikawaDiagram-UXIWVN3A.CVPRJiKe.js} +1 -1
- package/dist/docs/assets/chunks/{journeyDiagram-VCZTEJTY.DZj4vy4E.js → journeyDiagram-VCZTEJTY.CQvNTfQC.js} +1 -1
- package/dist/docs/assets/chunks/{kanban-definition-6JOO6SKY.Dl63eMUV.js → kanban-definition-6JOO6SKY.BwywywUl.js} +1 -1
- package/dist/docs/assets/chunks/{layout.BLHZLWPH.js → layout.C4BkPPba.js} +1 -1
- package/dist/docs/assets/chunks/{linear.aXKGKaNw.js → linear.FoSfGKD4.js} +1 -1
- package/dist/docs/assets/chunks/{min.zWnFcpcc.js → min.yLh8jqfl.js} +1 -1
- package/dist/docs/assets/chunks/{mindmap-definition-QFDTVHPH.Qs4MQBea.js → mindmap-definition-QFDTVHPH.C9Dq2_CN.js} +1 -1
- package/dist/docs/assets/chunks/{pieDiagram-DEJITSTG.BmPHgsk7.js → pieDiagram-DEJITSTG.CiGyBcES.js} +1 -1
- package/dist/docs/assets/chunks/{quadrantDiagram-34T5L4WZ.D5MQ3gwA.js → quadrantDiagram-34T5L4WZ.1FWea9nj.js} +1 -1
- package/dist/docs/assets/chunks/{requirementDiagram-MS252O5E.CkdUFrO7.js → requirementDiagram-MS252O5E.B1q7ntiV.js} +1 -1
- package/dist/docs/assets/chunks/{sankeyDiagram-XADWPNL6.KZrljrAV.js → sankeyDiagram-XADWPNL6.Cpr4oU_k.js} +1 -1
- package/dist/docs/assets/chunks/{sequenceDiagram-FGHM5R23.XMoEW-Lx.js → sequenceDiagram-FGHM5R23.CUK68STn.js} +1 -1
- package/dist/docs/assets/chunks/{stateDiagram-FHFEXIEX.BmTzePLj.js → stateDiagram-FHFEXIEX.zU0yL2m4.js} +1 -1
- package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.nV8Nl-td.js +1 -0
- package/dist/docs/assets/chunks/{theme.BfQzpxsg.js → theme.DhnKd0CD.js} +2 -2
- package/dist/docs/assets/chunks/{timeline-definition-GMOUNBTQ.Dug0oamp.js → timeline-definition-GMOUNBTQ.CNYoXo3F.js} +1 -1
- package/dist/docs/assets/chunks/{vennDiagram-DHZGUBPP.BOTHrEFu.js → vennDiagram-DHZGUBPP.DFWH3G9s.js} +1 -1
- package/dist/docs/assets/chunks/{wardley-RL74JXVD.DXy2i1LS.js → wardley-RL74JXVD.Do4PwzeN.js} +1 -1
- package/dist/docs/assets/chunks/{wardleyDiagram-NUSXRM2D.CoXKdfi6.js → wardleyDiagram-NUSXRM2D.7WLkqM7s.js} +1 -1
- package/dist/docs/assets/chunks/{xychartDiagram-5P7HB3ND.DXoSCjAW.js → xychartDiagram-5P7HB3ND.-SyUNgWw.js} +1 -1
- package/dist/docs/assets/{guides_slack.md.Bo96y42E.js → guides_slack.md.DVjNyqq5.js} +3 -3
- package/dist/docs/assets/{guides_slack.md.Bo96y42E.lean.js → guides_slack.md.DVjNyqq5.lean.js} +1 -1
- package/dist/docs/assets/reference_cli.md.BvnQM8wd.js +97 -0
- package/dist/docs/assets/reference_cli.md.BvnQM8wd.lean.js +1 -0
- 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 +37 -37
- package/dist/docs/guides/slack.md +7 -0
- 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 +690 -751
- package/dist/docs/llms.txt +1 -1
- 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 +127 -125
- package/dist/docs/reference/cli.md +685 -753
- 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/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +9 -1
- package/dist/internal/deploy-client.d.ts +6 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +3 -0
- package/dist/internal/discovery/agent-config.d.ts +3 -1
- package/dist/internal/discovery/agent-config.d.ts.map +1 -1
- package/dist/internal/discovery/agent-config.js +7 -4
- package/dist/internal/framework-storage-selection.d.ts +1 -1
- package/dist/internal/framework-storage-selection.d.ts.map +1 -1
- package/dist/internal/platform-timers.d.ts.map +1 -1
- package/dist/internal/platform-timers.js +20 -2
- package/dist/internal/reminder-runner.d.ts +79 -1
- package/dist/internal/reminder-runner.d.ts.map +1 -1
- package/dist/internal/reminder-runner.js +276 -44
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +7 -0
- package/dist/memory.d.ts +4 -0
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +28 -4
- package/dist/playground/assets/index-DLwnR9ys.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +16 -9
- package/dist/types.d.ts.map +1 -1
- package/docs/guides/slack.md +7 -0
- package/docs/reference/cli.md +686 -754
- package/package.json +1 -1
- package/src/channels/slack/inbound.ts +24 -0
- package/src/channels/slack/slack-channel.ts +123 -1
- package/src/internal/cli-deploy.ts +12 -1
- package/src/internal/deploy-client.ts +9 -0
- package/src/internal/discovery/agent-config.ts +8 -6
- package/src/internal/framework-storage-selection.ts +1 -1
- package/src/internal/platform-timers.ts +24 -2
- package/src/internal/reminder-runner.ts +426 -63
- package/src/internal/server.ts +8 -0
- package/src/memory.ts +37 -3
- package/src/types.ts +16 -9
- package/dist/docs/assets/chunks/@localSearchIndexroot.BnSgidYE.js +0 -1
- package/dist/docs/assets/chunks/channel.DdM5EfNW.js +0 -1
- package/dist/docs/assets/chunks/classDiagram-6PBFFD2Q.CjfGHeg2.js +0 -1
- package/dist/docs/assets/chunks/classDiagram-v2-HSJHXN6E.CjfGHeg2.js +0 -1
- package/dist/docs/assets/chunks/clone.wSOICb_f.js +0 -1
- package/dist/docs/assets/chunks/stateDiagram-v2-QKLJ7IA2.Cu5X28zZ.js +0 -1
- package/dist/docs/assets/reference_cli.md.DLWDz9ij.js +0 -95
- package/dist/docs/assets/reference_cli.md.DLWDz9ij.lean.js +0 -1
- package/dist/playground/assets/index-C61EWMBK.css +0 -1
- /package/dist/playground/assets/{index-CrMWlgUU.js → index-CX6oTKwz.js} +0 -0
|
@@ -20,7 +20,7 @@ import { parseDuration } from "./duration.js";
|
|
|
20
20
|
import { REMINDER_CP_PROTOCOL_VERSION, scheduleSpecFromCreateInput, toWireReschedule, } from "./reminder-control-plane-protocol.js";
|
|
21
21
|
import { defaultBusyPolicy, MAX_TIMER_MS, nextCronFireMs, ReminderValidationError, resolveSchedule, } from "./reminder-schedule.js";
|
|
22
22
|
import { APP_AUTH } from "./schedule-runner.js";
|
|
23
|
-
import { HTTP_CHANNEL_ID, SessionBusyError, } from "./session-engine.js";
|
|
23
|
+
import { HTTP_CHANNEL_ID, SessionAuthError, SessionBusyError, } from "./session-engine.js";
|
|
24
24
|
export { ReminderValidationError } from "./reminder-schedule.js";
|
|
25
25
|
/** Max UTF-8 byte length of `JSON.stringify(args)` on named-handler create. */
|
|
26
26
|
export const REMINDER_ARGS_MAX_BYTES = 32 * 1024;
|
|
@@ -31,6 +31,13 @@ export class UnknownReminderError extends Error {
|
|
|
31
31
|
this.name = "UnknownReminderError";
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Reschedule applied when a hosted fire cannot open a session (no
|
|
36
|
+
* principal). A one-shot `skipped` outcome without a reschedule completes
|
|
37
|
+
* the reminder, which would lose the check-in for good.
|
|
38
|
+
*/
|
|
39
|
+
export const REMINDER_SESSION_OPEN_RETRY_DELAY = "5m";
|
|
40
|
+
export const REMINDER_FIRE_OUTCOME_METRIC = "agent_sdk.reminder.fire_outcome";
|
|
34
41
|
/**
|
|
35
42
|
* Server wiring for {@link ReminderRunnerOptions.rejectAnonymousHandlers}:
|
|
36
43
|
* architecture v2 always rejects anonymous run/until (local or hosted).
|
|
@@ -95,6 +102,19 @@ function toInfo(record) {
|
|
|
95
102
|
hasUntilFn: record.hasUntilFn === true,
|
|
96
103
|
};
|
|
97
104
|
}
|
|
105
|
+
function stopFire(reason) {
|
|
106
|
+
return { outcome: { kind: "stop", reason }, reason };
|
|
107
|
+
}
|
|
108
|
+
function runReason(result) {
|
|
109
|
+
switch (result.action) {
|
|
110
|
+
case "stop":
|
|
111
|
+
return "run_stop";
|
|
112
|
+
case "skip":
|
|
113
|
+
return "run_skip";
|
|
114
|
+
case "delivered":
|
|
115
|
+
return "run_delivered";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
98
118
|
function buildFirePrompt(record) {
|
|
99
119
|
var _a;
|
|
100
120
|
const recurring = record.scheduleKind === "every" || record.scheduleKind === "cron";
|
|
@@ -128,9 +148,15 @@ export class ReminderRunner {
|
|
|
128
148
|
/** Single-flight idle gates keyed by `channelId:continuationToken`. */
|
|
129
149
|
this.idleGates = new Map();
|
|
130
150
|
this.started = false;
|
|
151
|
+
/**
|
|
152
|
+
* A control-plane read failure is logged once per outage, not per call.
|
|
153
|
+
* The next successful read re-arms the log.
|
|
154
|
+
*/
|
|
155
|
+
this.controlPlaneReadFailureLogged = false;
|
|
131
156
|
this.rejectAnonymousHandlers = options.rejectAnonymousHandlers === true;
|
|
132
157
|
this.controlPlane = options.controlPlane;
|
|
133
158
|
this.architecture = options.architecture;
|
|
159
|
+
this.hostedGuest = options.hostedGuest;
|
|
134
160
|
for (const reminder of (_a = options.namedHandlers) !== null && _a !== void 0 ? _a : []) {
|
|
135
161
|
this.namedHandlers.set(reminder.name, reminder.definition);
|
|
136
162
|
this.namedHandlerNames.set(reminder.definition, reminder.name);
|
|
@@ -140,27 +166,56 @@ export class ReminderRunner {
|
|
|
140
166
|
get usesControlPlaneProtocol() {
|
|
141
167
|
return this.controlPlane !== undefined && this.architecture === "v2";
|
|
142
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Where reminder documents are read from. Under the CP protocol that is
|
|
171
|
+
* the v2 control-plane client; the v1 timed-work route answers 405 for a
|
|
172
|
+
* v2 deployment, which used to leave `list()` with only this process's
|
|
173
|
+
* in-memory map (breaking arm idempotence and `cancelForContinuation`).
|
|
174
|
+
*/
|
|
175
|
+
get reminderReader() {
|
|
176
|
+
if (this.usesControlPlaneProtocol) {
|
|
177
|
+
return this.controlPlane;
|
|
178
|
+
}
|
|
179
|
+
return this.platformTimers;
|
|
180
|
+
}
|
|
181
|
+
logControlPlaneReadFailure(what, error) {
|
|
182
|
+
if (this.usesControlPlaneProtocol && this.controlPlaneReadFailureLogged) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
this.controlPlaneReadFailureLogged = true;
|
|
186
|
+
this.logger(`[agent-sdk] reminders: control-plane ${what} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
187
|
+
}
|
|
143
188
|
/**
|
|
144
189
|
* Load reminder documents from the control plane into memory. Does not
|
|
145
190
|
* arm in-process timers — the CP owns clocks.
|
|
146
191
|
*/
|
|
147
192
|
hydrate() {
|
|
148
193
|
return __awaiter(this, void 0, void 0, function* () {
|
|
149
|
-
|
|
194
|
+
const reader = this.reminderReader;
|
|
195
|
+
if (reader === undefined) {
|
|
150
196
|
return;
|
|
151
197
|
}
|
|
152
198
|
let remote = [];
|
|
153
199
|
try {
|
|
154
|
-
remote = yield
|
|
200
|
+
remote = yield reader.listReminders();
|
|
155
201
|
}
|
|
156
202
|
catch (error) {
|
|
157
|
-
this.
|
|
203
|
+
this.logControlPlaneReadFailure("list", error);
|
|
158
204
|
return;
|
|
159
205
|
}
|
|
206
|
+
this.controlPlaneReadFailureLogged = false;
|
|
160
207
|
this.records.clear();
|
|
161
208
|
for (const record of remote) {
|
|
162
209
|
this.records.set(record.id, record);
|
|
163
210
|
}
|
|
211
|
+
if (this.usesControlPlaneProtocol) {
|
|
212
|
+
// The CP owns status; a fire whose handler is missing reports `stop`
|
|
213
|
+
// from executeFire instead of the guest writing a disarm.
|
|
214
|
+
if (remote.length > 0) {
|
|
215
|
+
this.logger(`[agent-sdk] reminders: hydrated ${remote.length} reminder(s) from control plane`);
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
164
219
|
let disarmedOrphan = 0;
|
|
165
220
|
for (const record of [...this.records.values()]) {
|
|
166
221
|
if (record.status !== "armed") {
|
|
@@ -238,9 +293,11 @@ export class ReminderRunner {
|
|
|
238
293
|
}
|
|
239
294
|
getRecord(id) {
|
|
240
295
|
return __awaiter(this, void 0, void 0, function* () {
|
|
241
|
-
|
|
296
|
+
const reader = this.reminderReader;
|
|
297
|
+
if (reader !== undefined) {
|
|
242
298
|
try {
|
|
243
|
-
const remote = yield
|
|
299
|
+
const remote = yield reader.getReminder(id);
|
|
300
|
+
this.controlPlaneReadFailureLogged = false;
|
|
244
301
|
if (remote === undefined) {
|
|
245
302
|
this.records.delete(id);
|
|
246
303
|
return undefined;
|
|
@@ -249,25 +306,35 @@ export class ReminderRunner {
|
|
|
249
306
|
return remote;
|
|
250
307
|
}
|
|
251
308
|
catch (error) {
|
|
252
|
-
this.
|
|
309
|
+
this.logControlPlaneReadFailure(`get for ${id}`, error);
|
|
253
310
|
return this.records.get(id);
|
|
254
311
|
}
|
|
255
312
|
}
|
|
256
313
|
return this.records.get(id);
|
|
257
314
|
});
|
|
258
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Under the CP protocol a list failure propagates: the in-memory map is
|
|
318
|
+
* only what this process created, and returning it would let a caller
|
|
319
|
+
* arm duplicates or miss rows to cancel. v1 keeps the best-effort form.
|
|
320
|
+
*/
|
|
259
321
|
allRecords() {
|
|
260
322
|
return __awaiter(this, void 0, void 0, function* () {
|
|
261
|
-
|
|
323
|
+
const reader = this.reminderReader;
|
|
324
|
+
if (reader !== undefined) {
|
|
262
325
|
try {
|
|
263
|
-
const remote = yield
|
|
326
|
+
const remote = yield reader.listReminders();
|
|
327
|
+
this.controlPlaneReadFailureLogged = false;
|
|
264
328
|
this.records.clear();
|
|
265
329
|
for (const record of remote) {
|
|
266
330
|
this.records.set(record.id, record);
|
|
267
331
|
}
|
|
268
332
|
}
|
|
269
333
|
catch (error) {
|
|
270
|
-
this.
|
|
334
|
+
this.logControlPlaneReadFailure("list", error);
|
|
335
|
+
if (this.usesControlPlaneProtocol) {
|
|
336
|
+
throw error;
|
|
337
|
+
}
|
|
271
338
|
}
|
|
272
339
|
}
|
|
273
340
|
return [...this.records.values()];
|
|
@@ -533,25 +600,49 @@ export class ReminderRunner {
|
|
|
533
600
|
*/
|
|
534
601
|
executeFire(descriptor) {
|
|
535
602
|
return __awaiter(this, void 0, void 0, function* () {
|
|
536
|
-
var _a;
|
|
537
603
|
const record = this.recordFromDescriptor(descriptor);
|
|
538
604
|
this.records.set(record.id, record);
|
|
605
|
+
let fire;
|
|
606
|
+
try {
|
|
607
|
+
fire = yield this.runFire(record);
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
// The hosted admission maps the throw to a retryable failure; count
|
|
611
|
+
// it so the retry loop is visible beside the settled outcomes.
|
|
612
|
+
this.recordFireOutcome(record, {
|
|
613
|
+
kind: "error",
|
|
614
|
+
reason: "fire_threw",
|
|
615
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
616
|
+
});
|
|
617
|
+
throw error;
|
|
618
|
+
}
|
|
619
|
+
this.recordFireOutcome(record, {
|
|
620
|
+
kind: fire.outcome.kind,
|
|
621
|
+
reason: fire.reason,
|
|
622
|
+
turnStatus: fire.turnStatus,
|
|
623
|
+
});
|
|
624
|
+
return fire.outcome;
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
runFire(record) {
|
|
628
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
629
|
+
var _a;
|
|
539
630
|
const live = (_a = this.handlers.get(record.id)) !== null && _a !== void 0 ? _a : {};
|
|
540
631
|
if (record.handler !== undefined) {
|
|
541
632
|
if (this.resolveNamedHandler(record.handler) === undefined) {
|
|
542
|
-
return
|
|
633
|
+
return stopFire("handler_missing");
|
|
543
634
|
}
|
|
544
635
|
}
|
|
545
636
|
else if ((record.hasRunHandler === true || record.hasUntilFn === true) &&
|
|
546
637
|
live.run === undefined &&
|
|
547
638
|
live.untilFn === undefined) {
|
|
548
|
-
return
|
|
639
|
+
return stopFire("handler_lost_on_restart");
|
|
549
640
|
}
|
|
550
641
|
if (live.untilFn !== undefined) {
|
|
551
642
|
try {
|
|
552
643
|
const done = yield live.untilFn(this.buildUntilContext(record));
|
|
553
644
|
if (done) {
|
|
554
|
-
return
|
|
645
|
+
return stopFire("until_fn");
|
|
555
646
|
}
|
|
556
647
|
}
|
|
557
648
|
catch (error) {
|
|
@@ -560,47 +651,80 @@ export class ReminderRunner {
|
|
|
560
651
|
}
|
|
561
652
|
if (yield this.isSessionBusy(record.channelId, record.continuationToken)) {
|
|
562
653
|
if (record.busyPolicy === "skipIfBusy") {
|
|
563
|
-
|
|
564
|
-
return { kind: "skipped" };
|
|
654
|
+
return { outcome: { kind: "skipped" }, reason: "busy" };
|
|
565
655
|
}
|
|
566
656
|
yield this.whenSessionIdle(record.channelId, record.continuationToken);
|
|
567
657
|
}
|
|
568
658
|
let result;
|
|
659
|
+
let reason;
|
|
660
|
+
let turnStatus;
|
|
569
661
|
if (record.handler !== undefined) {
|
|
570
662
|
const named = this.resolveNamedHandler(record.handler);
|
|
571
663
|
if (named === undefined) {
|
|
572
|
-
return
|
|
664
|
+
return stopFire("handler_missing");
|
|
573
665
|
}
|
|
574
666
|
result = yield named.run(this.buildFireContext(record));
|
|
667
|
+
reason = runReason(result);
|
|
575
668
|
}
|
|
576
669
|
else if (live.run !== undefined) {
|
|
577
670
|
result = yield live.run(this.buildFireContext(record));
|
|
671
|
+
reason = runReason(result);
|
|
578
672
|
}
|
|
579
673
|
else if (record.prompt !== undefined) {
|
|
580
|
-
|
|
674
|
+
const delivery = yield this.deliverPromptFire(record);
|
|
675
|
+
result = delivery.result;
|
|
676
|
+
reason = delivery.reason;
|
|
677
|
+
turnStatus = delivery.turnStatus;
|
|
581
678
|
}
|
|
582
679
|
else {
|
|
583
|
-
return
|
|
680
|
+
return stopFire("missing_handler");
|
|
584
681
|
}
|
|
585
682
|
if (result.action === "stop") {
|
|
586
|
-
return { kind: "stop", reason
|
|
587
|
-
}
|
|
588
|
-
if (result.action === "skip") {
|
|
589
|
-
return {
|
|
590
|
-
kind: "skipped",
|
|
591
|
-
reschedule: result.reschedule !== undefined
|
|
592
|
-
? toWireReschedule(result.reschedule)
|
|
593
|
-
: undefined,
|
|
594
|
-
};
|
|
683
|
+
return { outcome: { kind: "stop", reason }, reason };
|
|
595
684
|
}
|
|
685
|
+
const reschedule = result.reschedule !== undefined
|
|
686
|
+
? toWireReschedule(result.reschedule)
|
|
687
|
+
: undefined;
|
|
596
688
|
return {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
689
|
+
outcome: {
|
|
690
|
+
kind: result.action === "skip" ? "skipped" : "delivered",
|
|
691
|
+
reschedule,
|
|
692
|
+
},
|
|
693
|
+
reason,
|
|
694
|
+
turnStatus,
|
|
601
695
|
};
|
|
602
696
|
});
|
|
603
697
|
}
|
|
698
|
+
/**
|
|
699
|
+
* One structured line and one `agent_sdk.reminder.fire_outcome` count per
|
|
700
|
+
* fire. Emitted through the host OTel meter, the same path hosted v2
|
|
701
|
+
* exports turn/tool metrics on, so a fire that ends in `stop` is visible
|
|
702
|
+
* beside the control plane's `timed_work.fire` count.
|
|
703
|
+
*/
|
|
704
|
+
recordFireOutcome(record, fire) {
|
|
705
|
+
this.logger(`[agent-sdk] reminder fire_outcome kind=${fire.kind} reason=${fire.reason}` +
|
|
706
|
+
` reminderId=${record.id} purpose=${record.purpose}` +
|
|
707
|
+
` channelId=${record.channelId} fireCount=${record.fireCount}` +
|
|
708
|
+
(fire.turnStatus === undefined ? "" : ` turn=${fire.turnStatus}`) +
|
|
709
|
+
(fire.detail === undefined
|
|
710
|
+
? ""
|
|
711
|
+
: ` detail=${JSON.stringify(fire.detail)}`));
|
|
712
|
+
// Test doubles may omit `host.otel`; the metric is best-effort.
|
|
713
|
+
const otel = this.engine.host.otel;
|
|
714
|
+
if (otel === undefined) {
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
try {
|
|
718
|
+
otel.increment(REMINDER_FIRE_OUTCOME_METRIC, {
|
|
719
|
+
kind: fire.kind,
|
|
720
|
+
reason: fire.reason,
|
|
721
|
+
purpose: record.purpose,
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
catch (error) {
|
|
725
|
+
this.logger(`[agent-sdk] reminder fire_outcome metric failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
604
728
|
recordFromDescriptor(descriptor) {
|
|
605
729
|
const now = new Date().toISOString();
|
|
606
730
|
return {
|
|
@@ -817,16 +941,94 @@ export class ReminderRunner {
|
|
|
817
941
|
}
|
|
818
942
|
deliverPrompt(record) {
|
|
819
943
|
return __awaiter(this, void 0, void 0, function* () {
|
|
944
|
+
return (yield this.deliverPromptFire(record)).result;
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Where a prompt fire joins. The pod's own store first; on a hosted guest
|
|
949
|
+
* the durable mirror next, because the pod that armed the reminder is
|
|
950
|
+
* usually gone and the session lives only in storage. A restored session
|
|
951
|
+
* keeps its own principal (channel admissions may carry a sender), so the
|
|
952
|
+
* fire joins under that, never under {@link ReminderHostedGuestOptions.fireAuth}.
|
|
953
|
+
*/
|
|
954
|
+
resolveFollowupTarget(record) {
|
|
955
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
956
|
+
const local = yield this.resolveFollowupAuth(record.channelId, record.continuationToken);
|
|
957
|
+
if (local !== undefined) {
|
|
958
|
+
return { auth: local, reached: "session_joined" };
|
|
959
|
+
}
|
|
960
|
+
if (this.hostedGuest === undefined) {
|
|
961
|
+
return { auth: undefined, reached: "session_gone" };
|
|
962
|
+
}
|
|
963
|
+
// Materializes the durable record locally; a storage error propagates
|
|
964
|
+
// and the hosted admission retries the fire.
|
|
965
|
+
const restored = yield this.engine.hasContinuationSession(record.channelId, record.continuationToken);
|
|
966
|
+
if (!restored) {
|
|
967
|
+
return { auth: undefined, reached: "session_gone" };
|
|
968
|
+
}
|
|
820
969
|
const auth = yield this.resolveFollowupAuth(record.channelId, record.continuationToken);
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
970
|
+
return auth === undefined
|
|
971
|
+
? { auth: undefined, reached: "session_unreadable" }
|
|
972
|
+
: { auth, reached: "session_restored" };
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
deliverPromptFire(record) {
|
|
976
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
977
|
+
const target = yield this.resolveFollowupTarget(record);
|
|
978
|
+
if (target.auth !== undefined) {
|
|
979
|
+
return this.promptDelivery(record, target.auth, target.reached);
|
|
980
|
+
}
|
|
981
|
+
if (target.reached === "session_unreadable") {
|
|
982
|
+
this.logger(`[agent-sdk] reminder "${record.id}" (${record.purpose}) durable session did not materialize locally; not opening a new one`);
|
|
983
|
+
return { result: { action: "stop" }, reason: "session_gone" };
|
|
984
|
+
}
|
|
985
|
+
if (this.hostedGuest !== undefined &&
|
|
986
|
+
this.channelUsesControlPlaneContinuation(record.channelId)) {
|
|
987
|
+
// No session on this pod and none in durable storage. Open one on the
|
|
988
|
+
// stored key the way an inbound event on a fresh key does, instead of
|
|
989
|
+
// consuming the fire with no turn.
|
|
990
|
+
const fireAuth = this.hostedGuest.fireAuth();
|
|
991
|
+
if (fireAuth === undefined) {
|
|
992
|
+
return {
|
|
993
|
+
result: {
|
|
994
|
+
action: "skip",
|
|
995
|
+
reschedule: { delay: REMINDER_SESSION_OPEN_RETRY_DELAY },
|
|
996
|
+
},
|
|
997
|
+
reason: "session_gone",
|
|
998
|
+
};
|
|
826
999
|
}
|
|
827
|
-
|
|
1000
|
+
try {
|
|
1001
|
+
return yield this.promptDelivery(record, fireAuth, "session_opened");
|
|
1002
|
+
}
|
|
1003
|
+
catch (error) {
|
|
1004
|
+
if (!(error instanceof SessionAuthError)) {
|
|
1005
|
+
throw error;
|
|
1006
|
+
}
|
|
1007
|
+
// A session landed on this key under another principal between the
|
|
1008
|
+
// lookup and the send. That does not change on retry, so stop
|
|
1009
|
+
// rather than let the control plane re-admit until its attempt cap.
|
|
1010
|
+
this.logger(`[agent-sdk] reminder "${record.id}" (${record.purpose}) cannot join session on its continuation: ${error.message}`);
|
|
1011
|
+
return { result: { action: "stop" }, reason: "session_gone" };
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
// Under CP protocol, return stop and let the CP apply/CAS; do not
|
|
1015
|
+
// mutate authoritative status from the guest mid-fire.
|
|
1016
|
+
if (!this.usesControlPlaneProtocol) {
|
|
1017
|
+
yield this.disarm(record, "session_gone");
|
|
828
1018
|
}
|
|
829
|
-
return
|
|
1019
|
+
return { result: { action: "stop" }, reason: "session_gone" };
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
promptDelivery(record, auth, delivered) {
|
|
1023
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1024
|
+
var _a;
|
|
1025
|
+
const settle = {};
|
|
1026
|
+
const result = yield this.sendFollowup(record, buildFirePrompt(record), auth, settle);
|
|
1027
|
+
return {
|
|
1028
|
+
result,
|
|
1029
|
+
reason: result.action === "skip" ? ((_a = settle.skipReason) !== null && _a !== void 0 ? _a : "busy") : delivered,
|
|
1030
|
+
turnStatus: settle.turnStatus,
|
|
1031
|
+
};
|
|
830
1032
|
});
|
|
831
1033
|
}
|
|
832
1034
|
/**
|
|
@@ -859,29 +1061,55 @@ export class ReminderRunner {
|
|
|
859
1061
|
: channelContinuationIdentity((_a = project.channels.find(channel => channel.id === channelId)) === null || _a === void 0 ? void 0 : _a.definition);
|
|
860
1062
|
return continuationSource(identity) === "control-plane";
|
|
861
1063
|
}
|
|
862
|
-
|
|
1064
|
+
/**
|
|
1065
|
+
* Local and v1 track the turn in the background: the process outlives the
|
|
1066
|
+
* fire. A hosted v2 guest waits for the turn and its durable writes first,
|
|
1067
|
+
* because the control plane may stop the pod as soon as the fire's
|
|
1068
|
+
* outcome is applied, and `delivered` must mean the turn ran.
|
|
1069
|
+
*/
|
|
1070
|
+
settleFollowup(session, settle) {
|
|
1071
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1072
|
+
const completion = this.engine.waitForCompletion(session.id);
|
|
1073
|
+
if (this.hostedGuest === undefined) {
|
|
1074
|
+
this.engine.trackBackground(completion);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
const outcome = yield completion;
|
|
1078
|
+
yield this.engine.drainSessionDurability(session.id);
|
|
1079
|
+
if (settle !== undefined) {
|
|
1080
|
+
settle.turnStatus = outcome.status;
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
sendFollowup(record, message, auth, settle) {
|
|
863
1085
|
return __awaiter(this, void 0, void 0, function* () {
|
|
864
1086
|
const sendOptions = this.followupSendOptions(record, auth);
|
|
865
1087
|
try {
|
|
866
1088
|
const session = yield this.engine.send(record.channelId, message, sendOptions);
|
|
867
|
-
this.
|
|
1089
|
+
yield this.settleFollowup(session, settle);
|
|
868
1090
|
return { action: "delivered" };
|
|
869
1091
|
}
|
|
870
1092
|
catch (error) {
|
|
871
1093
|
if (error instanceof SessionBusyError) {
|
|
872
1094
|
if (record.busyPolicy === "skipIfBusy") {
|
|
1095
|
+
if (settle !== undefined) {
|
|
1096
|
+
settle.skipReason = "busy";
|
|
1097
|
+
}
|
|
873
1098
|
return { action: "skip" };
|
|
874
1099
|
}
|
|
875
1100
|
yield this.whenSessionIdle(record.channelId, record.continuationToken);
|
|
876
1101
|
try {
|
|
877
1102
|
const session = yield this.engine.send(record.channelId, message, sendOptions);
|
|
878
|
-
this.
|
|
1103
|
+
yield this.settleFollowup(session, settle);
|
|
879
1104
|
return { action: "delivered" };
|
|
880
1105
|
}
|
|
881
1106
|
catch (retryError) {
|
|
882
1107
|
this.logger(`[agent-sdk] reminder "${record.id}" followup failed after wait: ${retryError instanceof Error
|
|
883
1108
|
? retryError.message
|
|
884
1109
|
: String(retryError)}`);
|
|
1110
|
+
if (settle !== undefined) {
|
|
1111
|
+
settle.skipReason = "followup_failed";
|
|
1112
|
+
}
|
|
885
1113
|
return { action: "skip" };
|
|
886
1114
|
}
|
|
887
1115
|
}
|
|
@@ -904,9 +1132,13 @@ export class ReminderRunner {
|
|
|
904
1132
|
artifacts: this.engine.artifacts,
|
|
905
1133
|
followup(args) {
|
|
906
1134
|
return __awaiter(this, void 0, void 0, function* () {
|
|
907
|
-
const auth = yield self.
|
|
1135
|
+
const { auth } = yield self.resolveFollowupTarget(record);
|
|
908
1136
|
if (auth === undefined) {
|
|
909
|
-
|
|
1137
|
+
// Under the CP protocol the guest never writes reminder status;
|
|
1138
|
+
// the fire's `stop` outcome carries the reason instead.
|
|
1139
|
+
if (!self.usesControlPlaneProtocol) {
|
|
1140
|
+
yield self.disarm(record, "session_gone");
|
|
1141
|
+
}
|
|
910
1142
|
throw new Error(`Reminder "${record.id}" followup: session gone for continuation`);
|
|
911
1143
|
}
|
|
912
1144
|
const result = yield self.sendFollowup(record, args.message, auth);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/internal/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAqDH,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAOhB,YAAY,EACb,MAAM,aAAa,CAAC;AAwGrB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAuDpE,OAAO,EAAE,KAAK,WAAW,EAAmB,MAAM,iBAAiB,CAAC;AAUpE,OAAO,EAEL,wBAAwB,EAEzB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,EAAsB,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAgB/E,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;CACvB;AAwCD,6EAA6E;AAC7E,MAAM,WAAW,0BAA0B;IACzC,oEAAoE;IACpE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,4EAA4E;IAC5E,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACxC,yEAAyE;IACzE,aAAa,CAAC,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC;IAC5D,wEAAwE;IACxE,aAAa,CAAC,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;CACxD;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,UAAU,EAAE,EACpB,OAAO,GAAE,YAAY,GAAG,0BAA+B,GACtD,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/internal/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAqDH,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAOhB,YAAY,EACb,MAAM,aAAa,CAAC;AAwGrB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAuDpE,OAAO,EAAE,KAAK,WAAW,EAAmB,MAAM,iBAAiB,CAAC;AAUpE,OAAO,EAEL,wBAAwB,EAEzB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,EAAsB,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAgB/E,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;CACvB;AAwCD,6EAA6E;AAC7E,MAAM,WAAW,0BAA0B;IACzC,oEAAoE;IACpE,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,4EAA4E;IAC5E,eAAe,CAAC,EAAE,qBAAqB,CAAC;IACxC,yEAAyE;IACzE,aAAa,CAAC,EAAE,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC;IAC5D,wEAAwE;IACxE,aAAa,CAAC,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;CACxD;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,UAAU,EAAE,EACpB,OAAO,GAAE,YAAY,GAAG,0BAA+B,GACtD,OAAO,CAAC,gBAAgB,CAAC,CAqpC3B"}
|
package/dist/internal/server.js
CHANGED
|
@@ -598,6 +598,13 @@ export function startServer(mounts_1) {
|
|
|
598
598
|
}),
|
|
599
599
|
architecture,
|
|
600
600
|
controlPlane: reminderControlPlane,
|
|
601
|
+
// Hosted admissions build their channel args with no request
|
|
602
|
+
// principal (`buildChannelHandlerArgs` → `auth: null`), so a fire that
|
|
603
|
+
// opens a session on a fresh pod uses the same one and a session
|
|
604
|
+
// restored from durable storage still matches.
|
|
605
|
+
hostedGuest: hostedReminderControlPlane === undefined
|
|
606
|
+
? undefined
|
|
607
|
+
: { fireAuth: () => null },
|
|
601
608
|
});
|
|
602
609
|
const evalRuns = new EvalRunStore(project.rootDir, logger,
|
|
603
610
|
// Playground eval history mirrors onto the defineStorage evals table.
|
package/dist/memory.d.ts
CHANGED
|
@@ -68,6 +68,10 @@ export interface AgentStoreMemoryBackendOptions {
|
|
|
68
68
|
sink?: FileSink;
|
|
69
69
|
/** Rotation threshold, same meaning as {@link FileMemoryBackendOptions}. */
|
|
70
70
|
maxJournalBytes?: number;
|
|
71
|
+
/** Backoff sleep override (tests). */
|
|
72
|
+
sleep?: (ms: number) => Promise<void>;
|
|
73
|
+
/** Jitter source override (tests). Returns [0, 1). */
|
|
74
|
+
random?: () => number;
|
|
71
75
|
}
|
|
72
76
|
/**
|
|
73
77
|
* Journal on the deployment's Agent Store — durable across deploys, visible
|
package/dist/memory.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAM9E,OAAO,KAAK,EAAe,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEzE,sEAAsE;AACtE,eAAO,MAAM,eAAe,WAAW,CAAC;AAExC,gDAAgD;AAChD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC/B,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,UAAU,CACR,MAAM,EAAE,gBAAgB,EACxB,GAAG,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,wBAA6B,GACrC,aAAa,CAgCf;AAED,MAAM,WAAW,8BAA8B;IAC7C,uEAAuE;IACvE,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAKH,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAM9E,OAAO,KAAK,EAAe,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEzE,sEAAsE;AACtE,eAAO,MAAM,eAAe,WAAW,CAAC;AAExC,gDAAgD;AAChD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC;IAC/B,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB,CAAC;AAEF,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,UAAU,CACR,MAAM,EAAE,gBAAgB,EACxB,GAAG,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE,wBAA6B,GACrC,aAAa,CAgCf;AAED,MAAM,WAAW,8BAA8B;IAC7C,uEAAuE;IACvE,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,4EAA4E;IAC5E,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,MAAM,CAAC;CACvB;AAsBD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,8BAAmC,GAC3C,aAAa,CAgFf;AAqBD,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,cAAc,CA0F1E"}
|
package/dist/memory.js
CHANGED
|
@@ -80,6 +80,24 @@ export function fileMemoryBackend(options = {}) {
|
|
|
80
80
|
},
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* CAS attempts per append. The journal is one deployment-wide key written by
|
|
85
|
+
* every engine pod, so under concurrent turn traffic the etag precondition
|
|
86
|
+
* loses races routinely; each retry needs headroom to land.
|
|
87
|
+
*/
|
|
88
|
+
const MEMORY_CAS_MAX_ATTEMPTS = 5;
|
|
89
|
+
/**
|
|
90
|
+
* Jittered exponential backoff between CAS attempts. Retrying immediately is
|
|
91
|
+
* a fleet-wide stampede: every losing pod re-GETs the full journal and
|
|
92
|
+
* re-PUTs in lockstep, which multiplied reads ~65x on one deployment during
|
|
93
|
+
* the 2026-09-18 Agent Store read storm and tripped the store's
|
|
94
|
+
* per-service-account read limit (every pod shares the deployment
|
|
95
|
+
* credential). Jitter desynchronizes the writers so most retries land on the
|
|
96
|
+
* first or second attempt.
|
|
97
|
+
*/
|
|
98
|
+
function memoryCasBackoffMs(attempt, random) {
|
|
99
|
+
return 100 * Math.pow(2, attempt) * (0.5 + random());
|
|
100
|
+
}
|
|
83
101
|
/**
|
|
84
102
|
* Journal on the deployment's Agent Store — durable across deploys, visible
|
|
85
103
|
* on cloud VMs under the store mount. Appends are read-modify-write with
|
|
@@ -87,16 +105,20 @@ export function fileMemoryBackend(options = {}) {
|
|
|
87
105
|
* `<stateRoot>/memory/journal.jsonl` for the workspace symlink read path.
|
|
88
106
|
*/
|
|
89
107
|
export function agentStoreMemoryBackend(options = {}) {
|
|
90
|
-
var _a, _b;
|
|
108
|
+
var _a, _b, _c, _d;
|
|
91
109
|
const sink = (_a = options.sink) !== null && _a !== void 0 ? _a : cursorHostedFiles();
|
|
92
110
|
const maxJournalBytes = (_b = options.maxJournalBytes) !== null && _b !== void 0 ? _b : 5 * 1024 * 1024;
|
|
111
|
+
const sleep = (_c = options.sleep) !== null && _c !== void 0 ? _c : ((ms) => new Promise(resolve => setTimeout(resolve, ms)));
|
|
112
|
+
const random = (_d = options.random) !== null && _d !== void 0 ? _d : Math.random;
|
|
93
113
|
// Appends are chained per journal key (same shape as fileMemoryBackend);
|
|
94
|
-
// a failed append must not poison the chain for later turns.
|
|
114
|
+
// a failed append must not poison the chain for later turns. The chain
|
|
115
|
+
// serializes appends within this pod only — pods race each other on the
|
|
116
|
+
// shared key, which is what the CAS retry (with backoff) absorbs.
|
|
95
117
|
const appendChains = new Map();
|
|
96
118
|
const append = (record, ctx) => __awaiter(this, void 0, void 0, function* () {
|
|
97
119
|
const key = agentStoreKeys.memoryJournal(ctx.agentName);
|
|
98
120
|
const line = Buffer.from(`${JSON.stringify(record)}\n`, "utf8");
|
|
99
|
-
for (let attempt = 0; attempt <
|
|
121
|
+
for (let attempt = 0; attempt < MEMORY_CAS_MAX_ATTEMPTS; attempt += 1) {
|
|
100
122
|
const current = yield sink.get(key);
|
|
101
123
|
const currentBody = current === undefined
|
|
102
124
|
? new Uint8Array(0)
|
|
@@ -122,7 +144,9 @@ export function agentStoreMemoryBackend(options = {}) {
|
|
|
122
144
|
});
|
|
123
145
|
}
|
|
124
146
|
catch (error) {
|
|
125
|
-
if (error instanceof FileConflictError &&
|
|
147
|
+
if (error instanceof FileConflictError &&
|
|
148
|
+
attempt < MEMORY_CAS_MAX_ATTEMPTS - 1) {
|
|
149
|
+
yield sleep(memoryCasBackoffMs(attempt, random));
|
|
126
150
|
continue;
|
|
127
151
|
}
|
|
128
152
|
throw error;
|