@cursor/july 0.1.80 → 0.1.83

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.
Files changed (169) hide show
  1. package/AGENTS.md +0 -12
  2. package/README.md +2 -2
  3. package/dist/channels/github/cursor-account.d.ts +11 -0
  4. package/dist/channels/github/cursor-account.d.ts.map +1 -1
  5. package/dist/channels/github/cursor-account.js +13 -0
  6. package/dist/channels/github/github-channel.d.ts.map +1 -1
  7. package/dist/channels/github/github-channel.js +29 -1
  8. package/dist/channels/github/types.d.ts +8 -0
  9. package/dist/channels/github/types.d.ts.map +1 -1
  10. package/dist/channels/origin/origin-channel.d.ts.map +1 -1
  11. package/dist/channels/origin/origin-channel.js +32 -10
  12. package/dist/channels/origin/types.d.ts +7 -0
  13. package/dist/channels/origin/types.d.ts.map +1 -1
  14. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  15. package/dist/channels/slack/cursor-account.js +2 -2
  16. package/dist/channels/slack/dispatch.d.ts +3 -0
  17. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  18. package/dist/channels/slack/dispatch.js +18 -18
  19. package/dist/channels/slack/interactive.d.ts +2 -0
  20. package/dist/channels/slack/interactive.d.ts.map +1 -1
  21. package/dist/channels/slack/interactive.js +4 -16
  22. package/dist/channels/slack/slack-channel.d.ts +1 -1
  23. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  24. package/dist/channels/slack/slack-channel.js +21 -10
  25. package/dist/channels/slack/types.d.ts +7 -0
  26. package/dist/channels/slack/types.d.ts.map +1 -1
  27. package/dist/channels.d.ts.map +1 -1
  28. package/dist/channels.js +4 -0
  29. package/dist/continuation.d.ts +20 -0
  30. package/dist/continuation.d.ts.map +1 -0
  31. package/dist/continuation.js +18 -0
  32. package/dist/docs/404.html +2 -2
  33. package/dist/docs/ab.html +3 -3
  34. package/dist/docs/assets/{app.e4_wtjUS.js → app.6THHGRH-.js} +1 -1
  35. package/dist/docs/assets/chunks/@localSearchIndexroot.D9uScPWb.js +1 -0
  36. package/dist/docs/assets/chunks/{VPLocalSearchBox.DbdzyEie.js → VPLocalSearchBox.BpHB9ulP.js} +1 -1
  37. package/dist/docs/assets/chunks/{theme.D8HpLrLK.js → theme.DBJvJznv.js} +2 -2
  38. package/dist/docs/assets/{guides_cloud-runtime.md.V5igN4Sq.js → guides_cloud-runtime.md.CbklWfzh.js} +1 -1
  39. package/dist/docs/assets/{guides_cloud-runtime.md.V5igN4Sq.lean.js → guides_cloud-runtime.md.CbklWfzh.lean.js} +1 -1
  40. package/dist/docs/assets/{reference_agent-config.md.BZnEKfEc.js → reference_agent-config.md.CTWp4DnU.js} +1 -1
  41. package/dist/docs/assets/{reference_skills.md.B_jHN7JL.js → reference_skills.md.BRF2nDv9.js} +2 -2
  42. package/dist/docs/assets/{reference_skills.md.B_jHN7JL.lean.js → reference_skills.md.BRF2nDv9.lean.js} +1 -1
  43. package/dist/docs/assets/troubleshooting.md.KgmmaCgw.js +1 -0
  44. package/dist/docs/building-with-agents.html +3 -3
  45. package/dist/docs/concepts.html +3 -3
  46. package/dist/docs/deployment.html +3 -3
  47. package/dist/docs/evals.html +3 -3
  48. package/dist/docs/example-agents/approval-buddy.html +3 -3
  49. package/dist/docs/example-agents/benny.html +3 -3
  50. package/dist/docs/example-agents/bugbot.html +3 -3
  51. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  52. package/dist/docs/example-agents/codeowners-review.html +3 -3
  53. package/dist/docs/example-agents/concierge.html +3 -3
  54. package/dist/docs/example-agents/fsd.html +3 -3
  55. package/dist/docs/example-agents/index.html +3 -3
  56. package/dist/docs/example-agents/knowledge-base.html +3 -3
  57. package/dist/docs/example-agents/oncall.html +3 -3
  58. package/dist/docs/example-agents/security-reviewer.html +3 -3
  59. package/dist/docs/example-agents/slack-agent.html +3 -3
  60. package/dist/docs/example-agents/weather-agent.html +3 -3
  61. package/dist/docs/guides/agent-to-agent.html +3 -3
  62. package/dist/docs/guides/cloud-runtime.html +5 -5
  63. package/dist/docs/guides/convert-automation.html +3 -3
  64. package/dist/docs/guides/github.html +3 -3
  65. package/dist/docs/guides/human-in-the-loop.html +3 -3
  66. package/dist/docs/guides/mcp-oauth.html +3 -3
  67. package/dist/docs/guides/opentelemetry.html +3 -3
  68. package/dist/docs/guides/slack.html +3 -3
  69. package/dist/docs/guides/webhooks.html +3 -3
  70. package/dist/docs/hashmap.json +1 -1
  71. package/dist/docs/hillclimbing.html +3 -3
  72. package/dist/docs/index.html +3 -3
  73. package/dist/docs/quickstart.html +3 -3
  74. package/dist/docs/reference/agent-config.html +5 -5
  75. package/dist/docs/reference/artifacts.html +3 -3
  76. package/dist/docs/reference/channels.html +3 -3
  77. package/dist/docs/reference/cli.html +3 -3
  78. package/dist/docs/reference/connections.html +3 -3
  79. package/dist/docs/reference/hooks.html +3 -3
  80. package/dist/docs/reference/http-api.html +3 -3
  81. package/dist/docs/reference/instructions.html +3 -3
  82. package/dist/docs/reference/playground.html +3 -3
  83. package/dist/docs/reference/project-layout.html +3 -3
  84. package/dist/docs/reference/prompt.html +3 -3
  85. package/dist/docs/reference/schedules.html +3 -3
  86. package/dist/docs/reference/sessions.html +3 -3
  87. package/dist/docs/reference/skills.html +5 -5
  88. package/dist/docs/reference/subagents.html +3 -3
  89. package/dist/docs/reference/tools.html +3 -3
  90. package/dist/docs/scaffolding-agents.html +3 -3
  91. package/dist/docs/storage.html +3 -3
  92. package/dist/docs/templates/agentic-owners.html +3 -3
  93. package/dist/docs/templates/demo.html +3 -3
  94. package/dist/docs/templates/pr-autofixer.html +3 -3
  95. package/dist/docs/templates/security-reviewer.html +3 -3
  96. package/dist/docs/templates/triage.html +3 -3
  97. package/dist/docs/troubleshooting.html +5 -5
  98. package/dist/files-backends/cursor-hosted.d.ts +6 -0
  99. package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
  100. package/dist/files-backends/cursor-hosted.js +19 -0
  101. package/dist/files.d.ts +22 -3
  102. package/dist/files.d.ts.map +1 -1
  103. package/dist/files.js +22 -3
  104. package/dist/index.d.ts +5 -1
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +4 -1
  107. package/dist/internal/continuation-channel.d.ts +29 -0
  108. package/dist/internal/continuation-channel.d.ts.map +1 -0
  109. package/dist/internal/continuation-channel.js +88 -0
  110. package/dist/internal/continuation-identity.d.ts +95 -0
  111. package/dist/internal/continuation-identity.d.ts.map +1 -0
  112. package/dist/internal/continuation-identity.js +445 -0
  113. package/dist/internal/deploy-manifest.d.ts +12 -2
  114. package/dist/internal/deploy-manifest.d.ts.map +1 -1
  115. package/dist/internal/deploy-manifest.js +15 -7
  116. package/dist/internal/discovery.d.ts.map +1 -1
  117. package/dist/internal/discovery.js +32 -5
  118. package/dist/internal/sdk-runner.d.ts.map +1 -1
  119. package/dist/internal/sdk-runner.js +6 -9
  120. package/dist/internal/server.d.ts.map +1 -1
  121. package/dist/internal/server.js +18 -8
  122. package/dist/internal/session-engine.d.ts +30 -0
  123. package/dist/internal/session-engine.d.ts.map +1 -1
  124. package/dist/internal/session-engine.js +140 -36
  125. package/dist/internal/session-store.d.ts.map +1 -1
  126. package/dist/internal/session-store.js +6 -0
  127. package/dist/internal/skills-sync.d.ts +27 -0
  128. package/dist/internal/skills-sync.d.ts.map +1 -0
  129. package/dist/internal/skills-sync.js +98 -0
  130. package/dist/playground/assets/{index-DJzcIeGt.css → index-ChETdG-_.css} +1 -1
  131. package/dist/playground/assets/{index-K3Sxyx0I.js → index-Cspgk9jC.js} +45 -45
  132. package/dist/playground/index.html +2 -2
  133. package/dist/types.d.ts +49 -0
  134. package/dist/types.d.ts.map +1 -1
  135. package/docs/guides/cloud-runtime.md +6 -1
  136. package/docs/reference/agent-config.md +6 -5
  137. package/docs/reference/skills.md +12 -3
  138. package/docs/troubleshooting.md +1 -1
  139. package/package.json +1 -1
  140. package/skills/framework-map/SKILL.md +2 -1
  141. package/src/channels/github/cursor-account.ts +20 -0
  142. package/src/channels/github/github-channel.ts +52 -1
  143. package/src/channels/github/types.ts +8 -0
  144. package/src/channels/origin/origin-channel.ts +48 -10
  145. package/src/channels/origin/types.ts +7 -0
  146. package/src/channels/slack/cursor-account.ts +1 -0
  147. package/src/channels/slack/dispatch.ts +44 -2
  148. package/src/channels/slack/interactive.ts +5 -0
  149. package/src/channels/slack/slack-channel.ts +23 -2
  150. package/src/channels/slack/types.ts +7 -0
  151. package/src/channels.ts +7 -0
  152. package/src/continuation.ts +49 -0
  153. package/src/files-backends/cursor-hosted.ts +21 -0
  154. package/src/files.ts +29 -3
  155. package/src/index.ts +30 -1
  156. package/src/internal/continuation-channel.ts +129 -0
  157. package/src/internal/continuation-identity.ts +597 -0
  158. package/src/internal/deploy-manifest.ts +36 -3
  159. package/src/internal/discovery.ts +40 -2
  160. package/src/internal/sdk-runner.ts +10 -11
  161. package/src/internal/server.ts +23 -9
  162. package/src/internal/session-engine.ts +178 -37
  163. package/src/internal/session-store.ts +6 -0
  164. package/src/internal/skills-sync.ts +110 -0
  165. package/src/types.ts +60 -0
  166. package/dist/docs/assets/chunks/@localSearchIndexroot.BGiMJpnL.js +0 -1
  167. package/dist/docs/assets/troubleshooting.md.DQ-hYuZT.js +0 -1
  168. /package/dist/docs/assets/{reference_agent-config.md.BZnEKfEc.lean.js → reference_agent-config.md.CTWp4DnU.lean.js} +0 -0
  169. /package/dist/docs/assets/{troubleshooting.md.DQ-hYuZT.lean.js → troubleshooting.md.KgmmaCgw.lean.js} +0 -0
@@ -22,7 +22,8 @@ import { SLACK_CHANNEL_ID } from "../channels/slack/constants.js";
22
22
  import { materializeSlackNudgeCarrier } from "../channels/slack/nudge-carrier.js";
23
23
  import { SlackAskDedupeStore, SlackNudgeStore, } from "../channels/slack/nudge-store.js";
24
24
  import { resolveDedicatedSlackHostBinding } from "../channels/slack/slack-channel.js";
25
- import { isCursorHostedFilesAvailable } from "../files-backends/cursor-hosted.js";
25
+ import { AGENT_SERVE_USER_STORE_FILES_SOURCE_ID, agentStoreKeys, } from "../files.js";
26
+ import { cursorHostedFiles, isCursorHostedFilesAvailable, isCursorUserStoreFilesAvailable, } from "../files-backends/cursor-hosted.js";
26
27
  import { DEFAULT_MODEL, DIRECT_TOOL_CALL_CHANNEL_ID, isHostOauthConnectionTransport, isSymbolicConnectionTransport, } from "../types.js";
27
28
  import { ABCollector } from "./ab-collector.js";
28
29
  import { foldABStream } from "./ab-fold.js";
@@ -33,6 +34,8 @@ import { ArtifactsStore } from "./artifacts-store.js";
33
34
  import { SessionAuthError, samePrincipal } from "./auth.js";
34
35
  import { mergeCloudOptions, resolveSessionRuntime } from "./cloud-merge.js";
35
36
  import { applyCloudUsageToEvents, cloudSdkAgentIdFromEvents, isUnpricedCompletedTurn, mapSdkTokenUsage, } from "./cloud-turn-cost.js";
37
+ import { applyAgentContinuationDefaults, channelContinuationIdentity, httpContinuationIdentity, } from "./continuation-channel.js";
38
+ import { continuationSource, resolveV2Key, V2EngineError, } from "./continuation-identity.js";
36
39
  import { isDevMode } from "./dev-mode.js";
37
40
  import { EventLogStore } from "./event-log.js";
38
41
  import { TurnEventMapper } from "./event-mapper.js";
@@ -49,6 +52,7 @@ import { buildSessionContextBlock, SCHEDULE_CHANNEL_PREFIX, } from "./prompt-con
49
52
  import { assertCloudCanHonorAccountServersFilters } from "./resolved-connections.js";
50
53
  import { sessionExternalLinks } from "./session-links.js";
51
54
  import { SessionStore } from "./session-store.js";
55
+ import { syncSkillsToStore } from "./skills-sync.js";
52
56
  import { StorageCoordinator } from "./storage-coordinator.js";
53
57
  import { normalizeToolResult, toolCallErrorMessage } from "./tool-result.js";
54
58
  import { estimateTurnCostFromApi, roundCostUsd, } from "./turn-cost.js";
@@ -152,6 +156,7 @@ export class SessionEngine {
152
156
  /** Deduped in-flight cloud cost backfills, keyed by session id. */
153
157
  this.cloudCostBackfill = new Map();
154
158
  this.project = options.project;
159
+ applyAgentContinuationDefaults(this.project);
155
160
  this.stateRoot = options.stateRoot;
156
161
  this.runner = options.runner;
157
162
  this.logger = (_a = options.logger) !== null && _a !== void 0 ? _a : (() => { });
@@ -454,19 +459,37 @@ export class SessionEngine {
454
459
  * Start or resume a session on a channel. When `continuationToken`
455
460
  * addresses an existing session this is a follow-up turn; otherwise a new
456
461
  * session is created (keyed by the provided token, or a fresh one).
462
+ *
463
+ * v2 (`/v2` selectors) refuses `continuationToken` and looks
464
+ * up `(agentId, cKey)` from a mailbox stamp or structured claim.
457
465
  */
458
466
  send(channelId_1, message_1) {
459
467
  return __awaiter(this, arguments, void 0, function* (channelId, message, options = {}) {
460
468
  var _a;
461
469
  var _b, _c, _d, _e, _f;
462
- const auth = (_b = options.auth) !== null && _b !== void 0 ? _b : null;
463
- let existing = options.continuationToken === undefined
470
+ const identity = channelId === HTTP_CHANNEL_ID
471
+ ? httpContinuationIdentity(this.project.httpChannel)
472
+ : channelContinuationIdentity(this.channelsById.get(channelId));
473
+ let sendOptions = options;
474
+ let skipContinuationLookup = false;
475
+ if (continuationSource(identity) === "control-plane") {
476
+ if (options.continuationToken !== undefined) {
477
+ throw new V2EngineError("v2: do not pass continuationToken; use mailboxContinuation or claim");
478
+ }
479
+ const cKey = resolveV2Key(identity, options);
480
+ sendOptions = Object.assign(Object.assign({}, options), { continuationToken: cKey });
481
+ skipContinuationLookup = cKey.startsWith("evt:");
482
+ }
483
+ const auth = (_b = sendOptions.auth) !== null && _b !== void 0 ? _b : null;
484
+ let existing = skipContinuationLookup || sendOptions.continuationToken === undefined
464
485
  ? undefined
465
- : yield this.sessions.findByContinuation(channelId, options.continuationToken);
466
- if (existing === undefined && options.continuationToken !== undefined) {
486
+ : yield this.sessions.findByContinuation(channelId, sendOptions.continuationToken);
487
+ if (existing === undefined &&
488
+ !skipContinuationLookup &&
489
+ sendOptions.continuationToken !== undefined) {
467
490
  // Local miss: the session may live only in the storage sink (it
468
491
  // fell outside the startup restore window, or restore is lazy-only).
469
- existing = yield this.restoreSessionByContinuation(channelId, options.continuationToken);
492
+ existing = yield this.restoreSessionByContinuation(channelId, sendOptions.continuationToken);
470
493
  }
471
494
  if (existing !== undefined) {
472
495
  if (!samePrincipal(existing.auth, auth)) {
@@ -475,51 +498,52 @@ export class SessionEngine {
475
498
  // Refresh harness cwd when the caller supplies a newer worktree (e.g. PR
476
499
  // synchronize). Leave the durable continuation key unchanged.
477
500
  let record = existing;
478
- const refreshWorkspace = options.workspaceDir !== undefined &&
479
- options.workspaceDir !== existing.workspaceOverride;
501
+ const refreshWorkspace = sendOptions.workspaceDir !== undefined &&
502
+ sendOptions.workspaceDir !== existing.workspaceOverride;
480
503
  if (refreshWorkspace ||
481
- options.workspaceFiles !== undefined ||
482
- options.images !== undefined) {
504
+ sendOptions.workspaceFiles !== undefined ||
505
+ sendOptions.images !== undefined) {
483
506
  record = yield this.updateRecord(existing.sessionId, (current) => (Object.assign(Object.assign({}, current), { workspaceOverride: refreshWorkspace
484
- ? options.workspaceDir
485
- : current.workspaceOverride, pendingWorkspaceFiles: mergePendingWorkspaceFiles(current.pendingWorkspaceFiles, options.workspaceFiles), pendingImages: mergePendingImages(current.pendingImages, options.images) })));
507
+ ? sendOptions.workspaceDir
508
+ : current.workspaceOverride, pendingWorkspaceFiles: mergePendingWorkspaceFiles(current.pendingWorkspaceFiles, sendOptions.workspaceFiles), pendingImages: mergePendingImages(current.pendingImages, sendOptions.images) })));
486
509
  }
487
510
  // Refresh the cloud attach the same way: a follow-up send carries the
488
511
  // caller's CURRENT cloud options (e.g. a PR attach re-derived per wake),
489
512
  // and the first send's snapshot must not pin this session's turns to a
490
513
  // stale repo/prUrl for the session's lifetime.
491
- if (options.cloud !== undefined &&
492
- JSON.stringify(options.cloud) !== JSON.stringify(record.cloudOverride)) {
493
- record = yield this.updateRecord(record.sessionId, (current) => (Object.assign(Object.assign({}, current), { cloudOverride: options.cloud })));
514
+ if (sendOptions.cloud !== undefined &&
515
+ JSON.stringify(sendOptions.cloud) !==
516
+ JSON.stringify(record.cloudOverride)) {
517
+ record = yield this.updateRecord(record.sessionId, (current) => (Object.assign(Object.assign({}, current), { cloudOverride: sendOptions.cloud })));
494
518
  }
495
519
  // Refresh wake-derived channel state (GitHub headSha / refs) without
496
520
  // wiping sticky fields. Null patch values are skipped.
497
- if (options.refreshState !== undefined) {
498
- record = yield this.updateRecord(record.sessionId, (current) => (Object.assign(Object.assign({}, current), { channelState: mergeChannelStateSkipNull(current.channelState, options.refreshState) })));
521
+ if (sendOptions.refreshState !== undefined) {
522
+ record = yield this.updateRecord(record.sessionId, (current) => (Object.assign(Object.assign({}, current), { channelState: mergeChannelStateSkipNull(current.channelState, sendOptions.refreshState) })));
499
523
  }
500
524
  const since = (yield this.logs.get(record.sessionId)).count;
501
- const coalesced = yield this.startTurn(record.sessionId, message, Object.assign({ admission: (_c = options.admission) !== null && _c !== void 0 ? _c : "preempt" }, (options.coalesceSourceTs === undefined
525
+ const coalesced = yield this.startTurn(record.sessionId, message, Object.assign({ admission: (_c = sendOptions.admission) !== null && _c !== void 0 ? _c : "preempt" }, (sendOptions.coalesceSourceTs === undefined
502
526
  ? {}
503
- : { sourceTs: options.coalesceSourceTs })));
527
+ : { sourceTs: sendOptions.coalesceSourceTs })));
504
528
  return this.channelSession(record.sessionId, record, false, coalesced, since);
505
529
  }
506
530
  const now = new Date().toISOString();
507
- const channelState = this.initialChannelState(channelId, options.state);
508
- const purpose = options.purpose === "eval" ? "eval" : "live";
531
+ const channelState = this.initialChannelState(channelId, sendOptions.state);
532
+ const purpose = sendOptions.purpose === "eval" ? "eval" : "live";
509
533
  let record = {
510
534
  sessionId: newSessionId(),
511
535
  channelId,
512
- mode: (_d = options.mode) !== null && _d !== void 0 ? _d : "chat",
536
+ mode: (_d = sendOptions.mode) !== null && _d !== void 0 ? _d : "chat",
513
537
  purpose,
514
- continuationKey: (_e = options.continuationToken) !== null && _e !== void 0 ? _e : newContinuationKey(),
538
+ continuationKey: (_e = sendOptions.continuationToken) !== null && _e !== void 0 ? _e : newContinuationKey(),
515
539
  auth,
516
- title: options.title,
540
+ title: sendOptions.title,
517
541
  channelState,
518
- workspaceOverride: options.workspaceDir,
519
- pendingWorkspaceFiles: options.workspaceFiles,
520
- pendingImages: options.images,
521
- cloudOverride: options.cloud,
522
- sdkAgentId: options.sdkAgentId,
542
+ workspaceOverride: sendOptions.workspaceDir,
543
+ pendingWorkspaceFiles: sendOptions.workspaceFiles,
544
+ pendingImages: sendOptions.images,
545
+ cloudOverride: sendOptions.cloud,
546
+ sdkAgentId: sendOptions.sdkAgentId,
523
547
  turnCount: 0,
524
548
  createdAt: now,
525
549
  updatedAt: now,
@@ -532,9 +556,9 @@ export class SessionEngine {
532
556
  });
533
557
  record = yield this.enrollABs(record);
534
558
  const since = (yield this.logs.get(record.sessionId)).count;
535
- yield this.startTurn(record.sessionId, message, Object.assign({ admission: (_f = options.admission) !== null && _f !== void 0 ? _f : "preempt" }, (options.coalesceSourceTs === undefined
559
+ yield this.startTurn(record.sessionId, message, Object.assign({ admission: (_f = sendOptions.admission) !== null && _f !== void 0 ? _f : "preempt" }, (sendOptions.coalesceSourceTs === undefined
536
560
  ? {}
537
- : { sourceTs: options.coalesceSourceTs })));
561
+ : { sourceTs: sendOptions.coalesceSourceTs })));
538
562
  return this.channelSession(record.sessionId, record, true, false, since);
539
563
  });
540
564
  }
@@ -1270,6 +1294,75 @@ export class SessionEngine {
1270
1294
  return Object.assign(Object.assign(Object.assign({}, outcome), (finalResult === undefined ? {} : { result: finalResult })), (finalUsage === undefined ? {} : { usage: finalUsage }));
1271
1295
  });
1272
1296
  }
1297
+ /**
1298
+ * Sync authored skills onto the Agent Store at `skills/<name>/…` (hosted)
1299
+ * or `agent-serve/<agent>/skills/<name>/…` (personal-key local+cloud)
1300
+ * before the first cloud prompt, so `--agent-store-skills-dir` and the
1301
+ * first-turn S3 scan can discover them. False when the sync fails: the
1302
+ * turn then runs without store skills rather than pointing discovery at
1303
+ * paths that do not exist.
1304
+ *
1305
+ * With skills authored, every first cloud turn re-copies the authored
1306
+ * bytes and the memo only dedupes concurrent first turns. The mount is
1307
+ * writable from cloud sessions, so a once-per-process copy would let one
1308
+ * session's edits masquerade as agent definition for every later session
1309
+ * until the engine restarts; re-syncing keeps the store true to discovery
1310
+ * the same way local materialization always rewrites skills. Without
1311
+ * skills the sync is best-effort stale cleanup and one attempt per
1312
+ * process is enough, success or not — a deployment without the files
1313
+ * surface must not fail and log on every new session for a feature it
1314
+ * does not use.
1315
+ */
1316
+ ensureCloudSkillsSynced() {
1317
+ if (this.cloudSkillsSync !== undefined) {
1318
+ return this.cloudSkillsSync;
1319
+ }
1320
+ const target = this.cloudSkillsStore();
1321
+ if (target === undefined) {
1322
+ this.cloudSkillsSync = Promise.resolve(false);
1323
+ return this.cloudSkillsSync;
1324
+ }
1325
+ const skills = this.project.agent.skills;
1326
+ let sync = syncSkillsToStore({
1327
+ sink: target.sink,
1328
+ agentName: this.project.name,
1329
+ skills,
1330
+ skillsPrefix: target.skillsPrefix,
1331
+ }).then(() => true, (error) => {
1332
+ this.logger(`[skills] ${target.label} sync failed: ${error instanceof Error ? error.message : String(error)}`);
1333
+ return false;
1334
+ });
1335
+ if (skills.length > 0) {
1336
+ sync = sync.finally(() => {
1337
+ this.cloudSkillsSync = undefined;
1338
+ });
1339
+ }
1340
+ this.cloudSkillsSync = sync;
1341
+ return sync;
1342
+ }
1343
+ /**
1344
+ * Hosted deployments own the deployment store. Local `serve`/`run` with
1345
+ * a personal API key writes into the USER store under `agent-serve/`.
1346
+ */
1347
+ cloudSkillsStore() {
1348
+ if (isCursorHostedFilesAvailable()) {
1349
+ return {
1350
+ sink: cursorHostedFiles(),
1351
+ skillsPrefix: agentStoreKeys.skillsPrefix(),
1352
+ label: "deployment store",
1353
+ };
1354
+ }
1355
+ if (isCursorUserStoreFilesAvailable()) {
1356
+ return {
1357
+ sink: cursorHostedFiles({
1358
+ storeSourceId: AGENT_SERVE_USER_STORE_FILES_SOURCE_ID,
1359
+ }),
1360
+ skillsPrefix: agentStoreKeys.userSkillsPrefix(this.project.name),
1361
+ label: "user store",
1362
+ };
1363
+ }
1364
+ return undefined;
1365
+ }
1273
1366
  buildTurnRequest(record, agent, turnId, prompt, isFirstTurn, instructionsInvisibleToHarness, emit, onUpdate, signal, images) {
1274
1367
  return __awaiter(this, void 0, void 0, function* () {
1275
1368
  var _a, _b, _c;
@@ -1315,8 +1408,9 @@ export class SessionEngine {
1315
1408
  if (isFirstTurn) {
1316
1409
  if (runtime === "cloud") {
1317
1410
  // Cloud VMs have no materialized workspace: ship the full context,
1318
- // including agent tool script bodies. On hosted deployments the
1319
- // shared memory journal is on the store mount say where. When the
1411
+ // including agent tool script bodies. On hosted deployments authored
1412
+ // skills land on the store mount for native discovery, and the
1413
+ // shared memory journal is on that mount too — say where. When the
1320
1414
  // turn carries the session-bound server-tool MCP endpoint, the
1321
1415
  // preamble catalogs server tools under that MCP server name *before*
1322
1416
  // the agent-tool "write the script if missing" recipe, so a model
@@ -1327,7 +1421,15 @@ export class SessionEngine {
1327
1421
  ? undefined
1328
1422
  : SERVER_TOOLS_MCP_SERVER_NAME,
1329
1423
  });
1330
- const memorySection = isCursorHostedFilesAvailable()
1424
+ const hosted = isCursorHostedFilesAvailable();
1425
+ // Synced whenever a store is reachable, not just when skills exist:
1426
+ // removing the last skill has to delete its store copies too.
1427
+ // Discovery is native (`--agent-store-skills-dir`); this only has
1428
+ // to land the bytes.
1429
+ if (hosted || isCursorUserStoreFilesAvailable()) {
1430
+ yield this.ensureCloudSkillsSynced();
1431
+ }
1432
+ const memorySection = hosted
1331
1433
  ? `\n\n${buildCloudMemorySection(this.project.name)}`
1332
1434
  : "";
1333
1435
  turnPrompt = `${preamble}${memorySection}\n\n${turnPrompt}`;
@@ -1342,11 +1444,13 @@ export class SessionEngine {
1342
1444
  turnPrompt = `${preamble}\n\n${turnPrompt}`;
1343
1445
  }
1344
1446
  }
1345
- return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ sessionId: record.sessionId }, (record.sdkAgentId === undefined
1447
+ return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ sessionId: record.sessionId }, (record.sdkAgentId === undefined
1346
1448
  ? {}
1347
1449
  : { sdkAgentId: record.sdkAgentId })), { workspaceDir: this.harnessCwd(record), sandbox: ((_c = this.project.agent.local) === null || _c === void 0 ? void 0 : _c.sandbox) === true }), (runtime === "local" && agent.allowedTools !== undefined
1348
1450
  ? { tools: agent.allowedTools }
1349
- : {})), { runnerStateDir: join(this.stateRoot, "runner"), prompt: turnPrompt, isFirstTurn }), (images !== undefined && images.length > 0 ? { images } : {})), (agent.model === undefined ? {} : { model: agent.model })), { runtime }), (mergedCloud === undefined ? {} : { cloud: mergedCloud })), { customTools: runtime === "local"
1451
+ : {})), { runnerStateDir: join(this.stateRoot, "runner"), prompt: turnPrompt, isFirstTurn }), (images !== undefined && images.length > 0 ? { images } : {})), (agent.model === undefined ? {} : { model: agent.model })), { runtime }), (mergedCloud === undefined ? {} : { cloud: mergedCloud })), (runtime === "cloud" && !isCursorHostedFilesAvailable()
1452
+ ? { agentServeAgent: this.project.name }
1453
+ : {})), { customTools: runtime === "local"
1350
1454
  ? yield this.buildCustomTools(record, agent, turnId, emit)
1351
1455
  : {}, mcpServers: this.buildMcpServers(runtime, record.sessionId, yield this.evaluateConnectionAuth(record)) }), (runtime === "local" && this.hasAttachedAuthedConnections()
1352
1456
  ? { perSendMcpServers: true }
@@ -1 +1 @@
1
- {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/internal/session-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,qBAAa,YAAY;IAKX,OAAO,CAAC,QAAQ,CAAC,WAAW;IAJxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA6B;IAC/D,OAAO,CAAC,QAAQ,CAA4B;IAE5C,YAA6B,WAAW,EAAE,MAAM,EAAI;IAEpD,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKpC;IAED,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtC;IAED,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvC;IAED,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,iBAAiB;YAIX,OAAO;YAmBP,cAAc;YAKd,UAAU;IAcxB,OAAO,CAAC,WAAW;IASb,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAc/D;IAEK,IAAI,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAKrC;IAEK,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAMpC;IAEK,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB/C;CACF"}
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/internal/session-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD,qBAAa,YAAY;IAKX,OAAO,CAAC,QAAQ,CAAC,WAAW;IAJxC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoC;IAC1D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA6B;IAC/D,OAAO,CAAC,QAAQ,CAA4B;IAE5C,YAA6B,WAAW,EAAE,MAAM,EAAI;IAEpD,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAKpC;IAED,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEtC;IAED,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvC;IAED,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,iBAAiB;YASX,OAAO;YAmBP,cAAc;YAKd,UAAU;IAcxB,OAAO,CAAC,WAAW;IASb,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAc/D;IAEK,IAAI,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAKrC;IAEK,kBAAkB,CACtB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAMpC;IAEK,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB/C;CACF"}
@@ -18,6 +18,7 @@ import { randomBytes } from "node:crypto";
18
18
  import { mkdir, readdir, readFile, rename, writeFile } from "node:fs/promises";
19
19
  import { join } from "node:path";
20
20
  import { isSafeSessionId } from "./auth.js";
21
+ import { isV2DurableContinuationKey } from "./continuation-identity.js";
21
22
  export class SessionStore {
22
23
  constructor(sessionsDir) {
23
24
  this.sessionsDir = sessionsDir;
@@ -40,6 +41,11 @@ export class SessionStore {
40
41
  return join(this.sessionDir(sessionId), "session.json");
41
42
  }
42
43
  continuationKeyOf(channelId, key) {
44
+ // v2 index is (agentId, cKey). This store is already per-agent, so drop
45
+ // channelId for `cont:` / `evt:` keys. v1 stays `{channelId}\n{token}`.
46
+ if (isV2DurableContinuationKey(key)) {
47
+ return `\0v2\n${key}`;
48
+ }
43
49
  return `${channelId}\n${key}`;
44
50
  }
45
51
  hydrate() {
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Sync authored skills onto an Agent Store so cloud-runtime turns discover
3
+ * them natively. Hosted deployments write store-root `skills/<name>/…` (the
4
+ * VM's `--agent-store-skills-dir /cursor/stores/deployment/skills`). Local
5
+ * `serve`/`run` with `runtime: "cloud"` writes
6
+ * `agent-serve/<agent>/skills/<name>/…` on the USER store.
7
+ */
8
+ import { type FileSink } from "../files.js";
9
+ import type { DiscoveredSkill } from "../types.js";
10
+ /**
11
+ * Mirror the agent's skills onto the store: write every SKILL.md (with the
12
+ * same generated frontmatter local materialization uses) plus sibling files,
13
+ * and delete keys no authored skill claims so redeploys drop removed skills.
14
+ *
15
+ * Everything loads and validates before the store is touched, and writes
16
+ * land before stale deletes, so a failed sync never leaves a gap a
17
+ * concurrent cloud reader could see.
18
+ */
19
+ export declare function syncSkillsToStore(args: {
20
+ sink: FileSink;
21
+ /** Sweeps leftover keys from the pre-native `agentkit/v1/{agent}/skills/` prefix. */
22
+ agentName: string;
23
+ skills: readonly DiscoveredSkill[];
24
+ /** Store-root directory that holds SKILL.md files. Defaults to `skills/`. */
25
+ skillsPrefix?: string;
26
+ }): Promise<void>;
27
+ //# sourceMappingURL=skills-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills-sync.d.ts","sourceRoot":"","sources":["../../src/internal/skills-sync.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE/E,OAAO,KAAK,EAAE,eAAe,EAAmB,MAAM,aAAa,CAAC;AAQpE;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,IAAI,EAAE,QAAQ,CAAC;IACf,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IACnC,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBhB"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Sync authored skills onto an Agent Store so cloud-runtime turns discover
3
+ * them natively. Hosted deployments write store-root `skills/<name>/…` (the
4
+ * VM's `--agent-store-skills-dir /cursor/stores/deployment/skills`). Local
5
+ * `serve`/`run` with `runtime: "cloud"` writes
6
+ * `agent-serve/<agent>/skills/<name>/…` on the USER store.
7
+ */
8
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
9
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10
+ return new (P || (P = Promise))(function (resolve, reject) {
11
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
12
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
13
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
14
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
15
+ });
16
+ };
17
+ import { readFile } from "node:fs/promises";
18
+ import { posix } from "node:path";
19
+ import { agentStoreKeys, FileConflictError } from "../files.js";
20
+ import { STORAGE_KEY_ROOT } from "../storage.js";
21
+ import { buildSkillMd } from "./workspace.js";
22
+ /** Pre-native layout. Deleted on every sync so a rolled-forward deploy does not leave undiscoverable copies. */
23
+ function legacySkillsPrefix(agent) {
24
+ return `${STORAGE_KEY_ROOT}/${agent}/skills/`;
25
+ }
26
+ /**
27
+ * Mirror the agent's skills onto the store: write every SKILL.md (with the
28
+ * same generated frontmatter local materialization uses) plus sibling files,
29
+ * and delete keys no authored skill claims so redeploys drop removed skills.
30
+ *
31
+ * Everything loads and validates before the store is touched, and writes
32
+ * land before stale deletes, so a failed sync never leaves a gap a
33
+ * concurrent cloud reader could see.
34
+ */
35
+ export function syncSkillsToStore(args) {
36
+ return __awaiter(this, void 0, void 0, function* () {
37
+ var _a;
38
+ const skillsPrefix = (_a = args.skillsPrefix) !== null && _a !== void 0 ? _a : agentStoreKeys.skillsPrefix();
39
+ const desired = new Map();
40
+ for (const skill of args.skills) {
41
+ const key = (relPath) => agentStoreKeys.skillFile(skill.name, relPath, skillsPrefix);
42
+ desired.set(key("SKILL.md"), Buffer.from(buildSkillMd(skill), "utf8"));
43
+ for (const [relPath, source] of Object.entries(skill.files)) {
44
+ desired.set(key(storeRelPath(relPath)), yield sourceBytes(source));
45
+ }
46
+ }
47
+ yield Promise.all([...desired].map(([key, body]) => putLastWriterWins(args.sink, key, body)));
48
+ const [existing, leftover] = yield Promise.all([
49
+ args.sink.list(skillsPrefix),
50
+ args.sink.list(legacySkillsPrefix(args.agentName)),
51
+ ]);
52
+ yield Promise.all([...existing.filter((entry) => !desired.has(entry.key)), ...leftover].map((entry) => args.sink.delete(entry.key)));
53
+ });
54
+ }
55
+ /**
56
+ * Same refusals as local materialization's `resolveInside`: a rel that
57
+ * escapes the skill directory or would not round-trip through the FUSE
58
+ * mount (`..`, `.`, `//`, absolute) fails the sync, exactly as it fails a
59
+ * local turn's workspace write. Backslashes are refused too — the store
60
+ * normalizes `\` to `/` on write, so the stored key would differ from the
61
+ * desired one and the stale pass would delete the file it just wrote.
62
+ */
63
+ function storeRelPath(relPath) {
64
+ if (relPath.includes("\0") ||
65
+ relPath.includes("\\") ||
66
+ posix.isAbsolute(relPath) ||
67
+ relPath.endsWith("/") ||
68
+ relPath.split("/").some((seg) => seg === ".." || seg === ".") ||
69
+ posix.normalize(relPath) !== relPath) {
70
+ throw new Error(`Refusing skill file path "${relPath}" (must be a normalized relative path)`);
71
+ }
72
+ return relPath;
73
+ }
74
+ /**
75
+ * Last writer wins. A raced conditional put (dual engine pods during a
76
+ * deploy rollover) clears the sink's cached etag, so one retry re-learns
77
+ * the current version and lands.
78
+ */
79
+ function putLastWriterWins(sink, key, body) {
80
+ return __awaiter(this, void 0, void 0, function* () {
81
+ try {
82
+ yield sink.put(key, body);
83
+ }
84
+ catch (error) {
85
+ if (!(error instanceof FileConflictError)) {
86
+ throw error;
87
+ }
88
+ yield sink.put(key, body);
89
+ }
90
+ });
91
+ }
92
+ function sourceBytes(source) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ return "contents" in source
95
+ ? Buffer.from(source.contents, "utf8")
96
+ : readFile(source.sourcePath);
97
+ });
98
+ }