@cursor/july 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +24 -2
- package/README.md +25 -15
- package/dist/bin/agent-serve.js +101 -12
- package/dist/channels/github/api.d.ts.map +1 -1
- package/dist/channels/github/api.js +31 -14
- package/dist/channels/github/cursor-account.d.ts +43 -0
- package/dist/channels/github/cursor-account.d.ts.map +1 -0
- package/dist/channels/github/cursor-account.js +95 -0
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +46 -9
- package/dist/channels/github/index.d.ts +2 -2
- package/dist/channels/github/index.js +2 -2
- package/dist/channels/github/types.d.ts +17 -0
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.d.ts +8 -2
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +8 -0
- package/dist/channels/slack/types.d.ts +24 -3
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/channels/slack/types.js +15 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +8 -8
- package/dist/docs/assets/{ab.md.COdXkces.js → ab.md.BMCZ6Hd7.js} +3 -3
- package/dist/docs/assets/{ab.md.COdXkces.lean.js → ab.md.BMCZ6Hd7.lean.js} +1 -1
- package/dist/docs/assets/{app.DqfFEmJd.js → app.Oje4vhlk.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.zwQ9RCQ7.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.BaLEdS15.js → VPLocalSearchBox.H5XZ2zCB.js} +1 -1
- package/dist/docs/assets/chunks/{theme.CZRvu_0q.js → theme.CTR_TuaE.js} +2 -2
- package/dist/docs/assets/{deployment.md.Dx1TYNk5.js → deployment.md.DTKwE15Z.js} +3 -3
- package/dist/docs/assets/{deployment.md.Dx1TYNk5.lean.js → deployment.md.DTKwE15Z.lean.js} +1 -1
- package/dist/docs/assets/{evals.md.DPZ_MAnI.js → evals.md.DAgEc_hL.js} +3 -3
- package/dist/docs/assets/{guides_agent-to-agent.md.CrtrsySy.js → guides_agent-to-agent.md.Bpzgq2Pq.js} +1 -1
- package/dist/docs/assets/{guides_cloud-runtime.md.CYlNMTNp.js → guides_cloud-runtime.md.gVzabdQL.js} +1 -1
- package/dist/docs/assets/{guides_github.md.DwbKhCeS.js → guides_github.md.DOOCpqsW.js} +11 -4
- package/dist/docs/assets/{guides_github.md.DwbKhCeS.lean.js → guides_github.md.DOOCpqsW.lean.js} +1 -1
- package/dist/docs/assets/{guides_human-in-the-loop.md.Dvuctx7s.js → guides_human-in-the-loop.md.DlUqsp1S.js} +2 -2
- package/dist/docs/assets/{guides_slack.md.bv41fHfW.js → guides_slack.md.CCwqHvSV.js} +4 -4
- package/dist/docs/assets/{guides_slack.md.bv41fHfW.lean.js → guides_slack.md.CCwqHvSV.lean.js} +1 -1
- package/dist/docs/assets/{guides_webhooks.md.hFTik3lf.js → guides_webhooks.md.B1EswtUu.js} +2 -2
- package/dist/docs/assets/index.md.m81y7TY7.js +20 -0
- package/dist/docs/assets/{index.md.BPKcj5AI.lean.js → index.md.m81y7TY7.lean.js} +1 -1
- package/dist/docs/assets/quickstart.md.CfU8_uTC.js +192 -0
- package/dist/docs/assets/quickstart.md.CfU8_uTC.lean.js +1 -0
- package/dist/docs/assets/{reference_agent-config.md.Bpd7HQwf.js → reference_agent-config.md.DrW2JUM8.js} +4 -4
- package/dist/docs/assets/{reference_agent-config.md.Bpd7HQwf.lean.js → reference_agent-config.md.DrW2JUM8.lean.js} +1 -1
- package/dist/docs/assets/{reference_channels.md.D7JTR03W.js → reference_channels.md.DdmiKgqf.js} +4 -4
- package/dist/docs/assets/{reference_channels.md.D7JTR03W.lean.js → reference_channels.md.DdmiKgqf.lean.js} +1 -1
- package/dist/docs/assets/{reference_connections.md.C3vNH_DE.js → reference_connections.md.zaEYCLHT.js} +1 -1
- package/dist/docs/assets/{reference_hooks.md.BCEc3MyM.js → reference_hooks.md.DyLVfE1O.js} +1 -1
- package/dist/docs/assets/{reference_hooks.md.BCEc3MyM.lean.js → reference_hooks.md.DyLVfE1O.lean.js} +1 -1
- package/dist/docs/assets/{reference_http-api.md.DBAahtdz.js → reference_http-api.md.Dx_nmDG6.js} +1 -1
- package/dist/docs/assets/{reference_instructions.md.BC05LEQ8.js → reference_instructions.md.CgoV-YEb.js} +9 -7
- package/dist/docs/assets/{reference_instructions.md.BC05LEQ8.lean.js → reference_instructions.md.CgoV-YEb.lean.js} +1 -1
- package/dist/docs/assets/{reference_schedules.md.D7qijxLk.js → reference_schedules.md.w_F2mXB6.js} +2 -2
- package/dist/docs/assets/{reference_skills.md.VQnlBT3Q.js → reference_skills.md.B_jHN7JL.js} +3 -3
- package/dist/docs/assets/{reference_skills.md.VQnlBT3Q.lean.js → reference_skills.md.B_jHN7JL.lean.js} +1 -1
- package/dist/docs/assets/{reference_subagents.md.CIRAVcPK.js → reference_subagents.md.zWAMNfi1.js} +1 -1
- package/dist/docs/assets/{reference_tools.md.DF5kwlt0.js → reference_tools.md.CqgJroI0.js} +2 -2
- package/dist/docs/assets/scaffolding-agents.md.C3pTrmoE.js +1 -0
- package/dist/docs/assets/scaffolding-agents.md.C3pTrmoE.lean.js +1 -0
- package/dist/docs/assets/storage.md.CVnInNiN.js +17 -0
- package/dist/docs/assets/storage.md.CVnInNiN.lean.js +1 -0
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +4 -4
- package/dist/docs/deployment.html +7 -7
- package/dist/docs/evals.html +7 -7
- package/dist/docs/guides/agent-to-agent.html +6 -6
- package/dist/docs/guides/cloud-runtime.html +5 -5
- package/dist/docs/guides/github.html +14 -7
- package/dist/docs/guides/human-in-the-loop.html +6 -6
- package/dist/docs/guides/slack.html +8 -8
- package/dist/docs/guides/webhooks.html +6 -6
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +5 -5
- package/dist/docs/index.html +7 -7
- package/dist/docs/quickstart.html +180 -23
- package/dist/docs/reference/agent-config.html +7 -7
- package/dist/docs/reference/channels.html +8 -8
- package/dist/docs/reference/cli.html +4 -4
- package/dist/docs/reference/connections.html +5 -5
- package/dist/docs/reference/hooks.html +5 -5
- package/dist/docs/reference/http-api.html +6 -6
- package/dist/docs/reference/instructions.html +13 -11
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/schedules.html +7 -7
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +6 -6
- package/dist/docs/reference/subagents.html +6 -6
- package/dist/docs/reference/tools.html +7 -7
- package/dist/docs/scaffolding-agents.html +5 -5
- package/dist/docs/storage.html +41 -0
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/internal/cli-deploy.d.ts +52 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -0
- package/dist/internal/cli-deploy.js +731 -0
- package/dist/internal/cursor/backend-client.d.ts +10 -1
- package/dist/internal/cursor/backend-client.d.ts.map +1 -1
- package/dist/internal/cursor/backend-client.js +79 -1
- package/dist/internal/cursor/github-credentials.d.ts +44 -0
- package/dist/internal/cursor/github-credentials.d.ts.map +1 -0
- package/dist/internal/cursor/github-credentials.js +195 -0
- package/dist/internal/deploy-client.d.ts +176 -0
- package/dist/internal/deploy-client.d.ts.map +1 -0
- package/dist/internal/deploy-client.js +375 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +73 -5
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +1 -0
- package/dist/internal/eval-run-store.d.ts +22 -3
- package/dist/internal/eval-run-store.d.ts.map +1 -1
- package/dist/internal/eval-run-store.js +37 -19
- package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -1
- package/dist/internal/handleAgentServeTrigger.js +10 -12
- package/dist/internal/host-platforms.d.ts +7 -2
- package/dist/internal/host-platforms.d.ts.map +1 -1
- package/dist/internal/host-platforms.js +15 -10
- package/dist/internal/hosting.d.ts +37 -0
- package/dist/internal/hosting.d.ts.map +1 -0
- package/dist/internal/hosting.js +67 -0
- package/dist/internal/reminder-runner.d.ts +7 -0
- package/dist/internal/reminder-runner.d.ts.map +1 -1
- package/dist/internal/reminder-runner.js +50 -6
- package/dist/internal/reminder-store.d.ts +2 -0
- package/dist/internal/reminder-store.d.ts.map +1 -1
- package/dist/internal/reminder-store.js +18 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +173 -42
- package/dist/internal/session-engine.d.ts +49 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +246 -17
- package/dist/internal/storage-coordinator.d.ts +139 -0
- package/dist/internal/storage-coordinator.d.ts.map +1 -0
- package/dist/internal/storage-coordinator.js +499 -0
- package/dist/playground/assets/cursor-icons-outline-BxTT_FVJ.woff2 +0 -0
- package/dist/playground/assets/index-B1Qc9h2u.css +1 -0
- package/dist/playground/assets/{index-FlWjhg3x.js → index-CpDYCj8W.js} +42 -42
- package/dist/playground/index.html +2 -2
- package/dist/storage.d.ts +204 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +153 -0
- package/dist/types.d.ts +44 -3
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +3 -2
- package/docs/guides/github.md +43 -8
- package/docs/guides/slack.md +1 -1
- package/docs/quickstart.md +329 -51
- package/docs/reference/instructions.md +8 -6
- package/docs/scaffolding-agents.md +1 -1
- package/docs/storage.md +98 -0
- package/package.json +8 -1
- package/skills/github/SKILL.md +9 -1
- package/src/bin/agent-serve.ts +139 -0
- package/src/channels/github/api.ts +42 -23
- package/src/channels/github/cursor-account.ts +165 -0
- package/src/channels/github/github-channel.ts +66 -6
- package/src/channels/github/index.ts +2 -2
- package/src/channels/github/types.ts +19 -0
- package/src/channels/slack/slack-channel.ts +17 -3
- package/src/channels/slack/types.ts +44 -3
- package/src/index.ts +13 -0
- package/src/internal/cli-deploy.ts +940 -0
- package/src/internal/cursor/backend-client.ts +103 -1
- package/src/internal/cursor/github-credentials.ts +248 -0
- package/src/internal/deploy-client.ts +591 -0
- package/src/internal/discovery.ts +88 -1
- package/src/internal/distribution.ts +1 -0
- package/src/internal/eval-run-store.ts +48 -19
- package/src/internal/handleAgentServeTrigger.ts +10 -12
- package/src/internal/host-platforms.ts +28 -11
- package/src/internal/hosting.ts +77 -0
- package/src/internal/reminder-runner.ts +50 -6
- package/src/internal/reminder-store.ts +21 -0
- package/src/internal/server.ts +213 -27
- package/src/internal/session-engine.ts +285 -7
- package/src/internal/storage-coordinator.ts +615 -0
- package/src/storage.ts +325 -0
- package/src/types.ts +40 -2
- package/dist/docs/assets/chunks/@localSearchIndexroot.CcVk1uKq.js +0 -1
- package/dist/docs/assets/index.md.BPKcj5AI.js +0 -20
- package/dist/docs/assets/quickstart.md.tVPiGK_L.js +0 -35
- package/dist/docs/assets/quickstart.md.tVPiGK_L.lean.js +0 -1
- package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.js +0 -1
- package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.lean.js +0 -1
- package/dist/playground/assets/cursor-icons-outline-oY2V_mvK.woff2 +0 -0
- package/dist/playground/assets/index-1K-hG-7p.css +0 -1
- /package/dist/docs/assets/{evals.md.DPZ_MAnI.lean.js → evals.md.DAgEc_hL.lean.js} +0 -0
- /package/dist/docs/assets/{guides_agent-to-agent.md.CrtrsySy.lean.js → guides_agent-to-agent.md.Bpzgq2Pq.lean.js} +0 -0
- /package/dist/docs/assets/{guides_cloud-runtime.md.CYlNMTNp.lean.js → guides_cloud-runtime.md.gVzabdQL.lean.js} +0 -0
- /package/dist/docs/assets/{guides_human-in-the-loop.md.Dvuctx7s.lean.js → guides_human-in-the-loop.md.DlUqsp1S.lean.js} +0 -0
- /package/dist/docs/assets/{guides_webhooks.md.hFTik3lf.lean.js → guides_webhooks.md.B1EswtUu.lean.js} +0 -0
- /package/dist/docs/assets/{reference_connections.md.C3vNH_DE.lean.js → reference_connections.md.zaEYCLHT.lean.js} +0 -0
- /package/dist/docs/assets/{reference_http-api.md.DBAahtdz.lean.js → reference_http-api.md.Dx_nmDG6.lean.js} +0 -0
- /package/dist/docs/assets/{reference_schedules.md.D7qijxLk.lean.js → reference_schedules.md.w_F2mXB6.lean.js} +0 -0
- /package/dist/docs/assets/{reference_subagents.md.CIRAVcPK.lean.js → reference_subagents.md.zWAMNfi1.lean.js} +0 -0
- /package/dist/docs/assets/{reference_tools.md.DF5kwlt0.lean.js → reference_tools.md.CqgJroI0.lean.js} +0 -0
|
@@ -19,8 +19,10 @@ import {
|
|
|
19
19
|
import { pathToFileURL } from "node:url";
|
|
20
20
|
import { type ABConfigFile, resolveABMaxPlaygroundSessions } from "../ab.js";
|
|
21
21
|
import { httpChannel } from "../channels.js";
|
|
22
|
+
import type { StorageDefinition } from "../storage.js";
|
|
22
23
|
import {
|
|
23
24
|
type AgentConfigDefinition,
|
|
25
|
+
type AgentHostingOptions,
|
|
24
26
|
type AgentProject,
|
|
25
27
|
type AgentProjectInfo,
|
|
26
28
|
type ChannelDefinition,
|
|
@@ -51,6 +53,12 @@ import { registerAuthoredModuleLoaders } from "./authored-loaders.js";
|
|
|
51
53
|
import { getDefinitionKind } from "./brand.js";
|
|
52
54
|
import { isValidCron } from "./cron.js";
|
|
53
55
|
import { parseFrontmatter } from "./frontmatter.js";
|
|
56
|
+
import {
|
|
57
|
+
isReservedSecretName,
|
|
58
|
+
MAX_EGRESS_DOMAINS,
|
|
59
|
+
validateEgressDomain,
|
|
60
|
+
validateSecretName,
|
|
61
|
+
} from "./hosting.js";
|
|
54
62
|
import { toolInputJsonSchema } from "./json-schema.js";
|
|
55
63
|
import { MCP_ENDPOINT_PATH, mcpEndpointToolNames } from "./mcp-endpoint.js";
|
|
56
64
|
|
|
@@ -233,6 +241,7 @@ export async function loadAgentProject(
|
|
|
233
241
|
const hooks = await loadHooks(ctx, join(agentDir, "hooks"));
|
|
234
242
|
const abs = await loadABs(ctx, agentDir);
|
|
235
243
|
const abConfig = await loadABConfig(ctx, agentDir);
|
|
244
|
+
const storage = await loadStorage(ctx, agentDir);
|
|
236
245
|
|
|
237
246
|
if (agentDir !== rootDir) {
|
|
238
247
|
await warnUnknownSlots(ctx, agentDir);
|
|
@@ -248,7 +257,8 @@ export async function loadAgentProject(
|
|
|
248
257
|
schedules,
|
|
249
258
|
hooks,
|
|
250
259
|
abs,
|
|
251
|
-
|
|
260
|
+
abConfig,
|
|
261
|
+
storage,
|
|
252
262
|
diagnostics,
|
|
253
263
|
};
|
|
254
264
|
}
|
|
@@ -324,6 +334,7 @@ async function loadAgent(
|
|
|
324
334
|
: {
|
|
325
335
|
cwd: localCwd,
|
|
326
336
|
};
|
|
337
|
+
const hosting = resolveHosting(ctx, agentDir, config?.hosting);
|
|
327
338
|
|
|
328
339
|
if (runtime === "cloud") {
|
|
329
340
|
warnCloudRuntimeLimitations(ctx, agentDir, {
|
|
@@ -344,6 +355,7 @@ async function loadAgent(
|
|
|
344
355
|
runtime,
|
|
345
356
|
cloud,
|
|
346
357
|
local,
|
|
358
|
+
hosting,
|
|
347
359
|
tools,
|
|
348
360
|
skills,
|
|
349
361
|
connections,
|
|
@@ -367,6 +379,59 @@ function resolveAgentCwd(
|
|
|
367
379
|
return isAbsolute(trimmed) ? trimmed : resolve(ctx.rootDir, trimmed);
|
|
368
380
|
}
|
|
369
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Validate the authored `hosting` block against the same grammar the
|
|
384
|
+
* backend enforces on deploy. Violations are warning diagnostics (reported
|
|
385
|
+
* by `agentkit validate`), not hard failures: the backend stays
|
|
386
|
+
* authoritative and `agentkit deploy` re-checks before any network call.
|
|
387
|
+
*/
|
|
388
|
+
function resolveHosting(
|
|
389
|
+
ctx: DiscoveryContext,
|
|
390
|
+
agentDir: string,
|
|
391
|
+
hosting: AgentHostingOptions | undefined
|
|
392
|
+
): AgentHostingOptions | undefined {
|
|
393
|
+
if (hosting === undefined) {
|
|
394
|
+
return undefined;
|
|
395
|
+
}
|
|
396
|
+
const agentPath = displayPath(ctx, join(agentDir, "agent.ts"));
|
|
397
|
+
const warn = (message: string): void => {
|
|
398
|
+
ctx.diagnostics.push({ severity: "warning", path: agentPath, message });
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const egressDomains = hosting.egressDomains ?? [];
|
|
402
|
+
for (const domain of egressDomains) {
|
|
403
|
+
const domainError = validateEgressDomain(domain);
|
|
404
|
+
if (domainError !== undefined) {
|
|
405
|
+
warn(`hosting.egressDomains: ${domainError}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (egressDomains.length > MAX_EGRESS_DOMAINS) {
|
|
409
|
+
warn(
|
|
410
|
+
`hosting.egressDomains lists ${egressDomains.length} domains; deploys are capped at ${MAX_EGRESS_DOMAINS}.`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
for (const name of hosting.secretNames ?? []) {
|
|
415
|
+
const nameError = validateSecretName(name);
|
|
416
|
+
if (nameError !== undefined) {
|
|
417
|
+
warn(`hosting.secretNames: ${nameError}`);
|
|
418
|
+
} else if (isReservedSecretName(name)) {
|
|
419
|
+
warn(
|
|
420
|
+
`hosting.secretNames: ${JSON.stringify(name)} is platform-reserved (CURSOR_*) and injected automatically; it cannot be set with \`secrets set\`.`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
...(hosting.egressDomains === undefined
|
|
427
|
+
? {}
|
|
428
|
+
: { egressDomains: [...hosting.egressDomains] }),
|
|
429
|
+
...(hosting.secretNames === undefined
|
|
430
|
+
? {}
|
|
431
|
+
: { secretNames: [...hosting.secretNames] }),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
370
435
|
async function loadAgentConfig(
|
|
371
436
|
ctx: DiscoveryContext,
|
|
372
437
|
agentDir: string,
|
|
@@ -1323,6 +1388,22 @@ async function loadABConfig(
|
|
|
1323
1388
|
}
|
|
1324
1389
|
}
|
|
1325
1390
|
|
|
1391
|
+
// ============================================================================
|
|
1392
|
+
// Storage (agent/storage.ts)
|
|
1393
|
+
// ============================================================================
|
|
1394
|
+
|
|
1395
|
+
async function loadStorage(
|
|
1396
|
+
ctx: DiscoveryContext,
|
|
1397
|
+
agentDir: string
|
|
1398
|
+
): Promise<StorageDefinition | undefined> {
|
|
1399
|
+
const path = await findModuleFile(agentDir, "storage");
|
|
1400
|
+
if (path === undefined) {
|
|
1401
|
+
return undefined;
|
|
1402
|
+
}
|
|
1403
|
+
const definition = await importDefault(ctx, path, "storage");
|
|
1404
|
+
return definition as StorageDefinition | undefined;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1326
1407
|
function resolveABName(definition: unknown, fallback: string): string {
|
|
1327
1408
|
if (
|
|
1328
1409
|
typeof definition === "object" &&
|
|
@@ -1410,6 +1491,7 @@ export function projectInfo(project: AgentProject): AgentProjectInfo {
|
|
|
1410
1491
|
cloud:
|
|
1411
1492
|
agent.cloud === undefined ? undefined : toPublicCloudInfo(agent.cloud),
|
|
1412
1493
|
local: agent.local,
|
|
1494
|
+
hosting: agent.hosting,
|
|
1413
1495
|
mcp: { path: MCP_ENDPOINT_PATH, tools: mcpEndpointToolNames(project) },
|
|
1414
1496
|
instructions:
|
|
1415
1497
|
agent.instructions === undefined
|
|
@@ -1496,6 +1578,10 @@ export function projectInfo(project: AgentProject): AgentProjectInfo {
|
|
|
1496
1578
|
durableSnapshots: project.abConfig?.persistSnapshots !== undefined,
|
|
1497
1579
|
},
|
|
1498
1580
|
}),
|
|
1581
|
+
storage:
|
|
1582
|
+
project.storage === undefined
|
|
1583
|
+
? undefined
|
|
1584
|
+
: { name: project.storage.name },
|
|
1499
1585
|
diagnostics: project.diagnostics,
|
|
1500
1586
|
};
|
|
1501
1587
|
}
|
|
@@ -1515,6 +1601,7 @@ async function importDefault(
|
|
|
1515
1601
|
| "schedule"
|
|
1516
1602
|
| "hook"
|
|
1517
1603
|
| "ab"
|
|
1604
|
+
| "storage"
|
|
1518
1605
|
): Promise<unknown> {
|
|
1519
1606
|
let mod: Record<string, unknown>;
|
|
1520
1607
|
try {
|
|
@@ -62,9 +62,21 @@ export class EvalRunStore {
|
|
|
62
62
|
|
|
63
63
|
constructor(
|
|
64
64
|
private readonly projectRoot: string,
|
|
65
|
-
private readonly logger: (line: string) => void
|
|
65
|
+
private readonly logger: (line: string) => void,
|
|
66
|
+
/**
|
|
67
|
+
* Project-wide sink from `agent/storage.ts` (`defineStorage`),
|
|
68
|
+
* used when `evals.config.ts` sets no `persistRuns` of its own.
|
|
69
|
+
*/
|
|
70
|
+
private readonly fallbackPersistence?: EvalRunPersistence
|
|
66
71
|
) {}
|
|
67
72
|
|
|
73
|
+
/** `evals.config.ts` `persistRuns` wins; `defineStorage` is fallback. */
|
|
74
|
+
private resolvePersist(
|
|
75
|
+
config: Partial<EvalConfigFile>
|
|
76
|
+
): EvalRunPersistence | undefined {
|
|
77
|
+
return config.persistRuns ?? this.fallbackPersistence;
|
|
78
|
+
}
|
|
79
|
+
|
|
68
80
|
async list(): Promise<{
|
|
69
81
|
evals: Array<{
|
|
70
82
|
id: string;
|
|
@@ -74,7 +86,7 @@ export class EvalRunStore {
|
|
|
74
86
|
}>;
|
|
75
87
|
config: EvalListConfig;
|
|
76
88
|
}> {
|
|
77
|
-
await this.
|
|
89
|
+
await this.hydrate();
|
|
78
90
|
const { evals, config } = await discoverEvals(this.projectRoot);
|
|
79
91
|
return {
|
|
80
92
|
evals: evals.map((e) => ({
|
|
@@ -85,18 +97,21 @@ export class EvalRunStore {
|
|
|
85
97
|
: { description: e.definition.description }),
|
|
86
98
|
...(e.definition.tags === undefined ? {} : { tags: e.definition.tags }),
|
|
87
99
|
})),
|
|
88
|
-
config: snapshotListConfig(
|
|
100
|
+
config: snapshotListConfig(
|
|
101
|
+
config,
|
|
102
|
+
this.resolvePersist(config) !== undefined
|
|
103
|
+
),
|
|
89
104
|
};
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
async get(runId: string): Promise<EvalRunSnapshot | undefined> {
|
|
93
|
-
await this.
|
|
108
|
+
await this.hydrate();
|
|
94
109
|
return this.runs.get(runId);
|
|
95
110
|
}
|
|
96
111
|
|
|
97
112
|
/** Newest-first snapshots for playground rehydrate after navigation/reload. */
|
|
98
113
|
async listRuns(): Promise<EvalRunSnapshot[]> {
|
|
99
|
-
await this.
|
|
114
|
+
await this.hydrate();
|
|
100
115
|
return this.sortedRuns();
|
|
101
116
|
}
|
|
102
117
|
|
|
@@ -109,7 +124,7 @@ export class EvalRunStore {
|
|
|
109
124
|
runs: EvalRunSnapshot[];
|
|
110
125
|
activeRunId?: string;
|
|
111
126
|
}> {
|
|
112
|
-
await this.
|
|
127
|
+
await this.hydrate();
|
|
113
128
|
const activeRunId = this.activeRunId;
|
|
114
129
|
return {
|
|
115
130
|
runs: this.sortedRuns(),
|
|
@@ -122,7 +137,7 @@ export class EvalRunStore {
|
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
async start(input: StartEvalRunInput = {}): Promise<EvalRunSnapshot> {
|
|
125
|
-
await this.
|
|
140
|
+
await this.hydrate();
|
|
126
141
|
if (this.activeRunId !== undefined) {
|
|
127
142
|
const active = this.runs.get(this.activeRunId);
|
|
128
143
|
if (active !== undefined && active.status === "running") {
|
|
@@ -160,12 +175,15 @@ export class EvalRunStore {
|
|
|
160
175
|
done: 0,
|
|
161
176
|
},
|
|
162
177
|
cases,
|
|
163
|
-
config: snapshotRunConfig(
|
|
178
|
+
config: snapshotRunConfig(
|
|
179
|
+
config,
|
|
180
|
+
this.resolvePersist(config) !== undefined
|
|
181
|
+
),
|
|
164
182
|
};
|
|
165
183
|
this.runs.set(runId, snapshot);
|
|
166
184
|
this.activeRunId = runId;
|
|
167
185
|
await this.pruneOldRuns(config);
|
|
168
|
-
await this.enqueuePersist(snapshot, config
|
|
186
|
+
await this.enqueuePersist(snapshot, this.resolvePersist(config));
|
|
169
187
|
this.logger(
|
|
170
188
|
`[agent-serve] eval run ${runId}: starting ${cases.length} case(s)`
|
|
171
189
|
);
|
|
@@ -189,7 +207,14 @@ export class EvalRunStore {
|
|
|
189
207
|
);
|
|
190
208
|
}
|
|
191
209
|
|
|
192
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Hydrate playground eval history from durable persistence
|
|
212
|
+
* (`evals.config.ts` `persistRuns`, else the `defineStorage`
|
|
213
|
+
* fallback). Called eagerly from the serve startup restore step —
|
|
214
|
+
* alongside session restore — and lazily as a safety net by every read.
|
|
215
|
+
* Idempotent; concurrent callers share one pass.
|
|
216
|
+
*/
|
|
217
|
+
async hydrate(): Promise<void> {
|
|
193
218
|
if (this.hydrated) {
|
|
194
219
|
return;
|
|
195
220
|
}
|
|
@@ -202,7 +227,7 @@ export class EvalRunStore {
|
|
|
202
227
|
private async hydrateFromPersistence(): Promise<void> {
|
|
203
228
|
try {
|
|
204
229
|
const { config } = await discoverEvals(this.projectRoot);
|
|
205
|
-
const persist = config
|
|
230
|
+
const persist = this.resolvePersist(config);
|
|
206
231
|
if (persist === undefined) {
|
|
207
232
|
this.hydrated = true;
|
|
208
233
|
return;
|
|
@@ -260,7 +285,7 @@ export class EvalRunStore {
|
|
|
260
285
|
continue;
|
|
261
286
|
}
|
|
262
287
|
this.runs.delete(stale.runId);
|
|
263
|
-
await this.persistDelete(stale.runId, config
|
|
288
|
+
await this.persistDelete(stale.runId, this.resolvePersist(config));
|
|
264
289
|
}
|
|
265
290
|
}
|
|
266
291
|
|
|
@@ -384,7 +409,7 @@ export class EvalRunStore {
|
|
|
384
409
|
this.applyCaseResult(runId, result);
|
|
385
410
|
const run = this.runs.get(runId);
|
|
386
411
|
if (run !== undefined) {
|
|
387
|
-
void this.enqueuePersist(run, opts.config
|
|
412
|
+
void this.enqueuePersist(run, this.resolvePersist(opts.config));
|
|
388
413
|
}
|
|
389
414
|
},
|
|
390
415
|
});
|
|
@@ -394,7 +419,7 @@ export class EvalRunStore {
|
|
|
394
419
|
}
|
|
395
420
|
run.status = "completed";
|
|
396
421
|
run.finishedAt = new Date().toISOString();
|
|
397
|
-
await this.enqueuePersist(run, opts.config
|
|
422
|
+
await this.enqueuePersist(run, this.resolvePersist(opts.config));
|
|
398
423
|
this.logger(
|
|
399
424
|
`[agent-serve] eval run ${runId}: done — ${run.summary.passed} passed, ${run.summary.failed} failed`
|
|
400
425
|
);
|
|
@@ -406,7 +431,7 @@ export class EvalRunStore {
|
|
|
406
431
|
run.status = "failed";
|
|
407
432
|
run.finishedAt = new Date().toISOString();
|
|
408
433
|
run.error = error instanceof Error ? error.message : String(error);
|
|
409
|
-
await this.enqueuePersist(run, opts.config
|
|
434
|
+
await this.enqueuePersist(run, this.resolvePersist(opts.config));
|
|
410
435
|
this.logger(`[agent-serve] eval run ${runId}: failed — ${run.error}`);
|
|
411
436
|
} finally {
|
|
412
437
|
if (this.activeRunId === runId) {
|
|
@@ -467,21 +492,25 @@ function markInterruptedRun(run: EvalRunSnapshot): void {
|
|
|
467
492
|
}
|
|
468
493
|
}
|
|
469
494
|
|
|
470
|
-
function snapshotListConfig(
|
|
495
|
+
function snapshotListConfig(
|
|
496
|
+
config: Partial<EvalConfigFile>,
|
|
497
|
+
durableRuns: boolean
|
|
498
|
+
): EvalListConfig {
|
|
471
499
|
return {
|
|
472
500
|
...(config.maxConcurrency === undefined
|
|
473
501
|
? {}
|
|
474
502
|
: { maxConcurrency: config.maxConcurrency }),
|
|
475
503
|
...(config.timeoutMs === undefined ? {} : { timeoutMs: config.timeoutMs }),
|
|
476
504
|
maxPlaygroundRuns: resolveEvalMaxPlaygroundRuns(config.maxPlaygroundRuns),
|
|
477
|
-
durableRuns
|
|
505
|
+
durableRuns,
|
|
478
506
|
};
|
|
479
507
|
}
|
|
480
508
|
|
|
481
509
|
function snapshotRunConfig(
|
|
482
|
-
config: Partial<EvalConfigFile
|
|
510
|
+
config: Partial<EvalConfigFile>,
|
|
511
|
+
durableRuns: boolean
|
|
483
512
|
): EvalRunSnapshot["config"] {
|
|
484
|
-
const list = snapshotListConfig(config);
|
|
513
|
+
const list = snapshotListConfig(config, durableRuns);
|
|
485
514
|
return {
|
|
486
515
|
...(list.maxConcurrency === undefined
|
|
487
516
|
? {}
|
|
@@ -62,19 +62,17 @@ export function collectGitHubTriggerTargets(input: {
|
|
|
62
62
|
}): AgentServeGitHubTriggerTarget[] {
|
|
63
63
|
const targets: AgentServeGitHubTriggerTarget[] = [];
|
|
64
64
|
for (const mount of input.mounts) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
for (const channel of mount.channels) {
|
|
66
|
+
const options = getGitHubChannelOptions(channel.definition);
|
|
67
|
+
if (options === undefined) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
targets.push({
|
|
71
|
+
slug: mount.slug,
|
|
72
|
+
options,
|
|
73
|
+
args: mount.buildHandlerArgs(channel.id),
|
|
74
|
+
});
|
|
68
75
|
}
|
|
69
|
-
const options = getGitHubChannelOptions(github.definition);
|
|
70
|
-
if (options === undefined) {
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
targets.push({
|
|
74
|
-
slug: mount.slug,
|
|
75
|
-
options,
|
|
76
|
-
args: mount.buildHandlerArgs("github"),
|
|
77
|
-
});
|
|
78
76
|
}
|
|
79
77
|
return targets;
|
|
80
78
|
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
resolveGitHubInstallationId,
|
|
13
13
|
tryResolveGhAuthToken,
|
|
14
14
|
} from "../channels/github/auth.js";
|
|
15
|
+
import type { GitHubChannelCredentials } from "../channels/github/types.js";
|
|
15
16
|
import { tryResolveBotToken } from "../channels/slack/credentials.js";
|
|
16
17
|
import type { HostGitHubClient, HostSlackClient } from "../types.js";
|
|
17
18
|
|
|
@@ -25,21 +26,34 @@ export interface HostPlatforms {
|
|
|
25
26
|
* `app` = GitHub App installation token (preferred in production).
|
|
26
27
|
* `env` / `gh` = local PAT / CLI fallbacks when App creds are unset.
|
|
27
28
|
*/
|
|
28
|
-
export type HostGitHubAuthSource =
|
|
29
|
+
export type HostGitHubAuthSource =
|
|
30
|
+
| "app"
|
|
31
|
+
| "cursor-account"
|
|
32
|
+
| "env"
|
|
33
|
+
| "gh"
|
|
34
|
+
| "unavailable";
|
|
35
|
+
|
|
36
|
+
export interface HostPlatformsOptions {
|
|
37
|
+
githubCredentials?: GitHubChannelCredentials;
|
|
38
|
+
}
|
|
29
39
|
|
|
30
40
|
/**
|
|
31
41
|
* Build host platforms from ambient credentials. Prefer App installation
|
|
32
42
|
* auth when configured; otherwise fall back to PAT / `gh auth` for local
|
|
33
43
|
* testing. Webhook-scoped calls should still prefer `ctx.github`.
|
|
34
44
|
*/
|
|
35
|
-
export function createHostPlatforms(
|
|
45
|
+
export function createHostPlatforms(
|
|
46
|
+
options: HostPlatformsOptions = {}
|
|
47
|
+
): HostPlatforms {
|
|
36
48
|
let octokitPromise: ReturnType<HostGitHubClient["getOctokit"]> | undefined;
|
|
37
49
|
let slackPromise: ReturnType<HostSlackClient["getClient"]> | undefined;
|
|
38
50
|
|
|
39
51
|
return {
|
|
40
52
|
github: {
|
|
41
53
|
getOctokit: () => {
|
|
42
|
-
octokitPromise ??= createGitHubOctokit({
|
|
54
|
+
octokitPromise ??= createGitHubOctokit({
|
|
55
|
+
credentials: options.githubCredentials,
|
|
56
|
+
});
|
|
43
57
|
return octokitPromise;
|
|
44
58
|
},
|
|
45
59
|
},
|
|
@@ -95,8 +109,9 @@ export async function resolveHostGitHubAuthSource(): Promise<HostGitHubAuthSourc
|
|
|
95
109
|
export async function warmupHostGitHubAuth(options: {
|
|
96
110
|
logger: (line: string) => void;
|
|
97
111
|
github?: HostGitHubClient;
|
|
112
|
+
authSource?: HostGitHubAuthSource;
|
|
98
113
|
}): Promise<HostGitHubAuthSource> {
|
|
99
|
-
const source = await resolveHostGitHubAuthSource();
|
|
114
|
+
const source = options.authSource ?? (await resolveHostGitHubAuthSource());
|
|
100
115
|
if (source === "unavailable") {
|
|
101
116
|
options.logger(
|
|
102
117
|
"[agent-serve] GitHub auth: unavailable (set GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY + GITHUB_APP_INSTALLATION_ID, or GITHUB_TOKEN / GH_TOKEN, or run `gh auth login`)"
|
|
@@ -118,13 +133,15 @@ export async function warmupHostGitHubAuth(options: {
|
|
|
118
133
|
}
|
|
119
134
|
|
|
120
135
|
const label =
|
|
121
|
-
source === "
|
|
122
|
-
?
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
:
|
|
136
|
+
source === "cursor-account"
|
|
137
|
+
? "Cursor account repo-scoped credential (also exported as GH_TOKEN for gh CLI)"
|
|
138
|
+
: source === "app"
|
|
139
|
+
? exportedGhToken
|
|
140
|
+
? "GitHub App installation token (also exported as GH_TOKEN for gh CLI)"
|
|
141
|
+
: "GitHub App installation token"
|
|
142
|
+
: source === "env"
|
|
143
|
+
? "GITHUB_TOKEN/GH_TOKEN"
|
|
144
|
+
: "gh auth token";
|
|
128
145
|
if (options.github === undefined) {
|
|
129
146
|
options.logger(`[agent-serve] GitHub auth: ${label}`);
|
|
130
147
|
return source;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side mirrors of the Agent Serve hosting validation rules
|
|
3
|
+
* (backend/server/src/agentServe/agentServeShared.ts). Shared by discovery
|
|
4
|
+
* (`hosting` block diagnostics on `agentkit validate`) and the deploy /
|
|
5
|
+
* secrets CLI verbs (actionable errors before any network call). The
|
|
6
|
+
* backend stays authoritative — these exist so mistakes fail fast and
|
|
7
|
+
* locally with the same grammar the server enforces.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** `hosting.egressDomains` cap (backend caps the deploy body at 20). */
|
|
11
|
+
export const MAX_EGRESS_DOMAINS = 20;
|
|
12
|
+
/** Max secrets per deployment. */
|
|
13
|
+
export const MAX_SECRETS = 32;
|
|
14
|
+
/** Max secret value size in bytes. */
|
|
15
|
+
export const MAX_SECRET_VALUE_BYTES = 4096;
|
|
16
|
+
/** Max secret name length. */
|
|
17
|
+
export const MAX_SECRET_NAME_LENGTH = 64;
|
|
18
|
+
/** Secret names the platform injects; user writes are rejected. */
|
|
19
|
+
export const RESERVED_SECRET_NAME_PREFIX = "CURSOR_";
|
|
20
|
+
|
|
21
|
+
const SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/;
|
|
22
|
+
|
|
23
|
+
// Lowercase hostname with >=2 labels, an alphabetic TLD, and at most one
|
|
24
|
+
// leading `*.` wildcard. Labels: alphanumeric with interior hyphens.
|
|
25
|
+
const EGRESS_DOMAIN_PATTERN =
|
|
26
|
+
/^(\*\.)?([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]+$/;
|
|
27
|
+
|
|
28
|
+
/** Returns a human error message, or undefined when the domain is valid. */
|
|
29
|
+
export function validateEgressDomain(domain: string): string | undefined {
|
|
30
|
+
if (EGRESS_DOMAIN_PATTERN.test(domain)) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return `Invalid egress domain ${JSON.stringify(domain)}: use a lowercase hostname with at least two labels and an alphabetic TLD (e.g. "api.example.com"), optionally with a single leading "*." wildcard.`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Returns a human error message, or undefined when the name is a valid
|
|
38
|
+
* secret name. Grammar only — reserved names are checked separately so the
|
|
39
|
+
* manifest's declarative `secretNames` can mention them with a softer
|
|
40
|
+
* diagnostic than the hard write-time rejection.
|
|
41
|
+
*/
|
|
42
|
+
export function validateSecretName(name: string): string | undefined {
|
|
43
|
+
if (name.length > MAX_SECRET_NAME_LENGTH) {
|
|
44
|
+
return `Secret name ${JSON.stringify(name)} is too long (max ${MAX_SECRET_NAME_LENGTH} characters).`;
|
|
45
|
+
}
|
|
46
|
+
if (!SECRET_NAME_PATTERN.test(name)) {
|
|
47
|
+
return `Invalid secret name ${JSON.stringify(name)}: use UPPER_SNAKE_CASE starting with a letter (${SECRET_NAME_PATTERN}).`;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** True when the platform reserves this name (`CURSOR_*`); writes are rejected. */
|
|
53
|
+
export function isReservedSecretName(name: string): boolean {
|
|
54
|
+
return name.startsWith(RESERVED_SECRET_NAME_PREFIX);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Human error for a secret name a user is trying to write or delete:
|
|
59
|
+
* grammar plus the reserved `CURSOR_*` prefix.
|
|
60
|
+
*/
|
|
61
|
+
export function validateSettableSecretName(name: string): string | undefined {
|
|
62
|
+
const nameError = validateSecretName(name);
|
|
63
|
+
if (nameError !== undefined) {
|
|
64
|
+
return nameError;
|
|
65
|
+
}
|
|
66
|
+
if (isReservedSecretName(name)) {
|
|
67
|
+
return `Secret name ${JSON.stringify(name)} is platform-reserved (CURSOR_*); the platform injects those values automatically.`;
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const CURSOR_EVENT_REPO_PATTERN = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
73
|
+
|
|
74
|
+
/** `owner/name` — mirrors the backend's cursor-event repo grammar. */
|
|
75
|
+
export function isValidCursorEventRepo(repo: string): boolean {
|
|
76
|
+
return CURSOR_EVENT_REPO_PATTERN.test(repo) && !repo.includes("..");
|
|
77
|
+
}
|
|
@@ -121,6 +121,9 @@ export class ReminderRunner {
|
|
|
121
121
|
async start(): Promise<void> {
|
|
122
122
|
if (this.started) return;
|
|
123
123
|
this.started = true;
|
|
124
|
+
// Materialize sink-backed reminders onto local disk before arming
|
|
125
|
+
// (local wins when both sides have the same id).
|
|
126
|
+
await this.hydrateFromStorage();
|
|
124
127
|
const armed = await this.store.listArmed();
|
|
125
128
|
let restored = 0;
|
|
126
129
|
let disarmedOrphan = 0;
|
|
@@ -148,6 +151,47 @@ export class ReminderRunner {
|
|
|
148
151
|
}
|
|
149
152
|
}
|
|
150
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Pull reminder records from `defineStorage` into `<stateRoot>/reminders`
|
|
156
|
+
* when local disk is empty for that id. Failures are logged and skipped.
|
|
157
|
+
*/
|
|
158
|
+
private async hydrateFromStorage(): Promise<void> {
|
|
159
|
+
const storage = this.engine.storage;
|
|
160
|
+
if (storage === undefined || !storage.canBulkRestore) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
let hydrated = 0;
|
|
164
|
+
try {
|
|
165
|
+
const remote = await storage.listReminders();
|
|
166
|
+
for (const record of remote) {
|
|
167
|
+
const local = await this.store.get(record.id);
|
|
168
|
+
if (local !== undefined) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
// Local write only — re-mirroring would just echo the sink.
|
|
172
|
+
await this.store.save(record);
|
|
173
|
+
hydrated += 1;
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
177
|
+
this.logger(
|
|
178
|
+
`[agent-serve] reminders: storage hydrate failed: ${message}`
|
|
179
|
+
);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (hydrated > 0) {
|
|
183
|
+
this.logger(
|
|
184
|
+
`[agent-serve] reminders: hydrated ${hydrated} reminder(s) from storage`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Persist locally and mirror into the authored storage sink when present. */
|
|
190
|
+
private async persist(record: ReminderRecord): Promise<void> {
|
|
191
|
+
await this.store.save(record);
|
|
192
|
+
this.engine.storage?.reminder(record);
|
|
193
|
+
}
|
|
194
|
+
|
|
151
195
|
stop(): void {
|
|
152
196
|
this.started = false;
|
|
153
197
|
for (const timer of this.timers.values()) {
|
|
@@ -209,7 +253,7 @@ export class ReminderRunner {
|
|
|
209
253
|
updatedAt: now,
|
|
210
254
|
};
|
|
211
255
|
|
|
212
|
-
await this.
|
|
256
|
+
await this.persist(record);
|
|
213
257
|
this.handlers.set(id, {
|
|
214
258
|
run: input.run,
|
|
215
259
|
untilFn,
|
|
@@ -273,7 +317,7 @@ export class ReminderRunner {
|
|
|
273
317
|
disarmReason: reason,
|
|
274
318
|
updatedAt: new Date().toISOString(),
|
|
275
319
|
};
|
|
276
|
-
await this.
|
|
320
|
+
await this.persist(updated);
|
|
277
321
|
this.handlers.delete(id);
|
|
278
322
|
return toInfo(updated);
|
|
279
323
|
}
|
|
@@ -662,7 +706,7 @@ export class ReminderRunner {
|
|
|
662
706
|
disarmReason: reason,
|
|
663
707
|
updatedAt: new Date().toISOString(),
|
|
664
708
|
};
|
|
665
|
-
await this.
|
|
709
|
+
await this.persist(updated);
|
|
666
710
|
this.handlers.delete(record.id);
|
|
667
711
|
}
|
|
668
712
|
|
|
@@ -711,7 +755,7 @@ export class ReminderRunner {
|
|
|
711
755
|
: String(reschedule.at),
|
|
712
756
|
updatedAt: new Date().toISOString(),
|
|
713
757
|
};
|
|
714
|
-
await this.
|
|
758
|
+
await this.persist(updated);
|
|
715
759
|
this.armTimer(updated);
|
|
716
760
|
return;
|
|
717
761
|
}
|
|
@@ -725,7 +769,7 @@ export class ReminderRunner {
|
|
|
725
769
|
disarmReason: "one_shot_complete",
|
|
726
770
|
updatedAt: new Date().toISOString(),
|
|
727
771
|
};
|
|
728
|
-
await this.
|
|
772
|
+
await this.persist(updated);
|
|
729
773
|
this.handlers.delete(current.id);
|
|
730
774
|
return;
|
|
731
775
|
}
|
|
@@ -747,7 +791,7 @@ export class ReminderRunner {
|
|
|
747
791
|
nextFireAtMs,
|
|
748
792
|
updatedAt: new Date().toISOString(),
|
|
749
793
|
};
|
|
750
|
-
await this.
|
|
794
|
+
await this.persist(updated);
|
|
751
795
|
this.armTimer(updated);
|
|
752
796
|
}
|
|
753
797
|
}
|
|
@@ -66,6 +66,27 @@ export interface ReminderRecord {
|
|
|
66
66
|
updatedAt: string;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/** Narrow a sink value to a {@link ReminderRecord} (startup hydrate). */
|
|
70
|
+
export function isReminderRecord(value: unknown): value is ReminderRecord {
|
|
71
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const record = value as Partial<ReminderRecord>;
|
|
75
|
+
return (
|
|
76
|
+
record.version === REMINDER_SNAPSHOT_VERSION &&
|
|
77
|
+
typeof record.id === "string" &&
|
|
78
|
+
record.id !== "" &&
|
|
79
|
+
typeof record.purpose === "string" &&
|
|
80
|
+
typeof record.channelId === "string" &&
|
|
81
|
+
typeof record.continuationToken === "string" &&
|
|
82
|
+
typeof record.scheduleKind === "string" &&
|
|
83
|
+
typeof record.scheduleValue === "string" &&
|
|
84
|
+
typeof record.status === "string" &&
|
|
85
|
+
typeof record.nextFireAtMs === "number" &&
|
|
86
|
+
Number.isFinite(record.nextFireAtMs)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
69
90
|
export class ReminderStore {
|
|
70
91
|
constructor(private readonly rootDir: string) {}
|
|
71
92
|
|