@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.
- package/AGENTS.md +0 -12
- package/README.md +2 -2
- package/dist/channels/github/cursor-account.d.ts +11 -0
- package/dist/channels/github/cursor-account.d.ts.map +1 -1
- package/dist/channels/github/cursor-account.js +13 -0
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +29 -1
- package/dist/channels/github/types.d.ts +8 -0
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/origin/origin-channel.d.ts.map +1 -1
- package/dist/channels/origin/origin-channel.js +32 -10
- package/dist/channels/origin/types.d.ts +7 -0
- package/dist/channels/origin/types.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.js +2 -2
- package/dist/channels/slack/dispatch.d.ts +3 -0
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +18 -18
- package/dist/channels/slack/interactive.d.ts +2 -0
- package/dist/channels/slack/interactive.d.ts.map +1 -1
- package/dist/channels/slack/interactive.js +4 -16
- package/dist/channels/slack/slack-channel.d.ts +1 -1
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +21 -10
- package/dist/channels/slack/types.d.ts +7 -0
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +4 -0
- package/dist/continuation.d.ts +20 -0
- package/dist/continuation.d.ts.map +1 -0
- package/dist/continuation.js +18 -0
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.e4_wtjUS.js → app.6THHGRH-.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.D9uScPWb.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DbdzyEie.js → VPLocalSearchBox.BpHB9ulP.js} +1 -1
- package/dist/docs/assets/chunks/{theme.D8HpLrLK.js → theme.DBJvJznv.js} +2 -2
- package/dist/docs/assets/{guides_cloud-runtime.md.V5igN4Sq.js → guides_cloud-runtime.md.CbklWfzh.js} +1 -1
- package/dist/docs/assets/{guides_cloud-runtime.md.V5igN4Sq.lean.js → guides_cloud-runtime.md.CbklWfzh.lean.js} +1 -1
- package/dist/docs/assets/{reference_agent-config.md.BZnEKfEc.js → reference_agent-config.md.CTWp4DnU.js} +1 -1
- package/dist/docs/assets/{reference_skills.md.B_jHN7JL.js → reference_skills.md.BRF2nDv9.js} +2 -2
- package/dist/docs/assets/{reference_skills.md.B_jHN7JL.lean.js → reference_skills.md.BRF2nDv9.lean.js} +1 -1
- package/dist/docs/assets/troubleshooting.md.KgmmaCgw.js +1 -0
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +3 -3
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +5 -5
- package/dist/docs/guides/convert-automation.html +3 -3
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/opentelemetry.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +5 -5
- package/dist/docs/reference/artifacts.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +5 -5
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/templates/agentic-owners.html +3 -3
- package/dist/docs/templates/demo.html +3 -3
- package/dist/docs/templates/pr-autofixer.html +3 -3
- package/dist/docs/templates/security-reviewer.html +3 -3
- package/dist/docs/templates/triage.html +3 -3
- package/dist/docs/troubleshooting.html +5 -5
- package/dist/files-backends/cursor-hosted.d.ts +6 -0
- package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/files-backends/cursor-hosted.js +19 -0
- package/dist/files.d.ts +22 -3
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +22 -3
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/internal/continuation-channel.d.ts +29 -0
- package/dist/internal/continuation-channel.d.ts.map +1 -0
- package/dist/internal/continuation-channel.js +88 -0
- package/dist/internal/continuation-identity.d.ts +95 -0
- package/dist/internal/continuation-identity.d.ts.map +1 -0
- package/dist/internal/continuation-identity.js +445 -0
- package/dist/internal/deploy-manifest.d.ts +12 -2
- package/dist/internal/deploy-manifest.d.ts.map +1 -1
- package/dist/internal/deploy-manifest.js +15 -7
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +32 -5
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +6 -9
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +18 -8
- package/dist/internal/session-engine.d.ts +30 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +140 -36
- package/dist/internal/session-store.d.ts.map +1 -1
- package/dist/internal/session-store.js +6 -0
- package/dist/internal/skills-sync.d.ts +27 -0
- package/dist/internal/skills-sync.d.ts.map +1 -0
- package/dist/internal/skills-sync.js +98 -0
- package/dist/playground/assets/{index-DJzcIeGt.css → index-ChETdG-_.css} +1 -1
- package/dist/playground/assets/{index-K3Sxyx0I.js → index-Cspgk9jC.js} +45 -45
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +49 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/guides/cloud-runtime.md +6 -1
- package/docs/reference/agent-config.md +6 -5
- package/docs/reference/skills.md +12 -3
- package/docs/troubleshooting.md +1 -1
- package/package.json +1 -1
- package/skills/framework-map/SKILL.md +2 -1
- package/src/channels/github/cursor-account.ts +20 -0
- package/src/channels/github/github-channel.ts +52 -1
- package/src/channels/github/types.ts +8 -0
- package/src/channels/origin/origin-channel.ts +48 -10
- package/src/channels/origin/types.ts +7 -0
- package/src/channels/slack/cursor-account.ts +1 -0
- package/src/channels/slack/dispatch.ts +44 -2
- package/src/channels/slack/interactive.ts +5 -0
- package/src/channels/slack/slack-channel.ts +23 -2
- package/src/channels/slack/types.ts +7 -0
- package/src/channels.ts +7 -0
- package/src/continuation.ts +49 -0
- package/src/files-backends/cursor-hosted.ts +21 -0
- package/src/files.ts +29 -3
- package/src/index.ts +30 -1
- package/src/internal/continuation-channel.ts +129 -0
- package/src/internal/continuation-identity.ts +597 -0
- package/src/internal/deploy-manifest.ts +36 -3
- package/src/internal/discovery.ts +40 -2
- package/src/internal/sdk-runner.ts +10 -11
- package/src/internal/server.ts +23 -9
- package/src/internal/session-engine.ts +178 -37
- package/src/internal/session-store.ts +6 -0
- package/src/internal/skills-sync.ts +110 -0
- package/src/types.ts +60 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.BGiMJpnL.js +0 -1
- package/dist/docs/assets/troubleshooting.md.DQ-hYuZT.js +0 -1
- /package/dist/docs/assets/{reference_agent-config.md.BZnEKfEc.lean.js → reference_agent-config.md.CTWp4DnU.lean.js} +0 -0
- /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 {
|
|
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
|
|
463
|
-
|
|
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,
|
|
466
|
-
if (existing === 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,
|
|
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 =
|
|
479
|
-
|
|
501
|
+
const refreshWorkspace = sendOptions.workspaceDir !== undefined &&
|
|
502
|
+
sendOptions.workspaceDir !== existing.workspaceOverride;
|
|
480
503
|
if (refreshWorkspace ||
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
?
|
|
485
|
-
: current.workspaceOverride, pendingWorkspaceFiles: mergePendingWorkspaceFiles(current.pendingWorkspaceFiles,
|
|
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 (
|
|
492
|
-
JSON.stringify(
|
|
493
|
-
|
|
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 (
|
|
498
|
-
record = yield this.updateRecord(record.sessionId, (current) => (Object.assign(Object.assign({}, current), { channelState: mergeChannelStateSkipNull(current.channelState,
|
|
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 =
|
|
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:
|
|
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,
|
|
508
|
-
const purpose =
|
|
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 =
|
|
536
|
+
mode: (_d = sendOptions.mode) !== null && _d !== void 0 ? _d : "chat",
|
|
513
537
|
purpose,
|
|
514
|
-
continuationKey: (_e =
|
|
538
|
+
continuationKey: (_e = sendOptions.continuationToken) !== null && _e !== void 0 ? _e : newContinuationKey(),
|
|
515
539
|
auth,
|
|
516
|
-
title:
|
|
540
|
+
title: sendOptions.title,
|
|
517
541
|
channelState,
|
|
518
|
-
workspaceOverride:
|
|
519
|
-
pendingWorkspaceFiles:
|
|
520
|
-
pendingImages:
|
|
521
|
-
cloudOverride:
|
|
522
|
-
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 =
|
|
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:
|
|
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
|
|
1319
|
-
//
|
|
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
|
|
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 })),
|
|
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;
|
|
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
|
+
}
|