@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
|
@@ -23,6 +23,7 @@ import { httpChannel } from "../channels.js";
|
|
|
23
23
|
import type { OtelDefinition } from "../otel.js";
|
|
24
24
|
import type { StorageDefinition } from "../storage.js";
|
|
25
25
|
import {
|
|
26
|
+
type AgentArchitecture,
|
|
26
27
|
type AgentConcurrencyOptions,
|
|
27
28
|
type AgentConfigDefinition,
|
|
28
29
|
type AgentHostingOptions,
|
|
@@ -68,6 +69,7 @@ import {
|
|
|
68
69
|
builtinTools,
|
|
69
70
|
TAG_ARTIFACT_TOOL_NAME,
|
|
70
71
|
} from "./builtin-tools/index.js";
|
|
72
|
+
import { applyAgentContinuationDefaults } from "./continuation-channel.js";
|
|
71
73
|
import { isValidCron } from "./cron.js";
|
|
72
74
|
import { parseFrontmatter } from "./frontmatter.js";
|
|
73
75
|
import {
|
|
@@ -311,12 +313,19 @@ export async function loadAgentProject(
|
|
|
311
313
|
await warnUnknownSlots(ctx, agentDir);
|
|
312
314
|
}
|
|
313
315
|
|
|
316
|
+
const resolvedHttpChannel = httpChannelDefinition ?? httpChannel();
|
|
317
|
+
applyAgentContinuationDefaults({
|
|
318
|
+
agent,
|
|
319
|
+
channels,
|
|
320
|
+
httpChannel: resolvedHttpChannel,
|
|
321
|
+
});
|
|
322
|
+
|
|
314
323
|
return {
|
|
315
324
|
rootDir,
|
|
316
325
|
agentDir,
|
|
317
326
|
name: agent.name,
|
|
318
327
|
agent,
|
|
319
|
-
httpChannel:
|
|
328
|
+
httpChannel: resolvedHttpChannel,
|
|
320
329
|
channels,
|
|
321
330
|
schedules,
|
|
322
331
|
hooks,
|
|
@@ -387,6 +396,13 @@ async function loadAgent(
|
|
|
387
396
|
"tools is not supported on subagents (they keep the harness task-tool toolset); ignoring.",
|
|
388
397
|
});
|
|
389
398
|
}
|
|
399
|
+
if (config?.architecture !== undefined) {
|
|
400
|
+
ctx.diagnostics.push({
|
|
401
|
+
severity: "warning",
|
|
402
|
+
path: displayPath(ctx, join(agentDir, "agent.ts")),
|
|
403
|
+
message: "architecture is not supported on subagents; ignoring.",
|
|
404
|
+
});
|
|
405
|
+
}
|
|
390
406
|
const artifactsPath = await findModuleFile(agentDir, "artifacts");
|
|
391
407
|
if (artifactsPath !== undefined) {
|
|
392
408
|
ctx.diagnostics.push({
|
|
@@ -505,6 +521,7 @@ async function loadAgent(
|
|
|
505
521
|
};
|
|
506
522
|
const hosting = resolveHosting(ctx, agentDir, config?.hosting);
|
|
507
523
|
const concurrency = resolveConcurrency(ctx, agentDir, config?.concurrency);
|
|
524
|
+
const architecture = resolveArchitecture(ctx, agentDir, config?.architecture);
|
|
508
525
|
|
|
509
526
|
if (runtime === "cloud") {
|
|
510
527
|
warnCloudRuntimeLimitations(ctx, agentDir, {
|
|
@@ -529,6 +546,7 @@ async function loadAgent(
|
|
|
529
546
|
...(allowedTools === undefined ? {} : { allowedTools }),
|
|
530
547
|
hosting,
|
|
531
548
|
concurrency,
|
|
549
|
+
...(architecture === undefined ? {} : { architecture }),
|
|
532
550
|
tools,
|
|
533
551
|
skills,
|
|
534
552
|
connections,
|
|
@@ -537,6 +555,26 @@ async function loadAgent(
|
|
|
537
555
|
};
|
|
538
556
|
}
|
|
539
557
|
|
|
558
|
+
function resolveArchitecture(
|
|
559
|
+
ctx: DiscoveryContext,
|
|
560
|
+
agentDir: string,
|
|
561
|
+
architecture: AgentArchitecture | undefined
|
|
562
|
+
): AgentArchitecture | undefined {
|
|
563
|
+
if (
|
|
564
|
+
architecture === undefined ||
|
|
565
|
+
architecture === "v1" ||
|
|
566
|
+
architecture === "v2"
|
|
567
|
+
) {
|
|
568
|
+
return architecture;
|
|
569
|
+
}
|
|
570
|
+
ctx.diagnostics.push({
|
|
571
|
+
severity: "error",
|
|
572
|
+
path: displayPath(ctx, join(agentDir, "agent.ts")),
|
|
573
|
+
message: `architecture must be "v1" or "v2"; got ${JSON.stringify(architecture)}.`,
|
|
574
|
+
});
|
|
575
|
+
return undefined;
|
|
576
|
+
}
|
|
577
|
+
|
|
540
578
|
/** Absolute harness cwd parent, or undefined when unset / blank. */
|
|
541
579
|
function resolveAgentCwd(
|
|
542
580
|
ctx: DiscoveryContext,
|
|
@@ -1549,7 +1587,7 @@ function warnCloudRuntimeLimitations(
|
|
|
1549
1587
|
severity: "warning",
|
|
1550
1588
|
path: displayPath(ctx, join(agentDir, "skills")),
|
|
1551
1589
|
message:
|
|
1552
|
-
"Authored skills
|
|
1590
|
+
"Authored skills reach cloud agents through native store discovery: hosted deployments write the deployment store's skills/ directory; local serve/run with a personal API key writes agent-serve/<agent>/skills/ on the USER store. Without a reachable store, only skills already in the cloud repo are visible.",
|
|
1553
1591
|
});
|
|
1554
1592
|
}
|
|
1555
1593
|
if (counts.seedFiles > 0) {
|
|
@@ -514,7 +514,7 @@ export class CursorSdkRunner implements AgentRunner {
|
|
|
514
514
|
request: RunnerTurnRequest,
|
|
515
515
|
shared: Pick<AgentOptions, "model" | "apiKey" | "mcpServers" | "agents">
|
|
516
516
|
): Promise<SDKAgent> {
|
|
517
|
-
const cloud = toCloudAgentOptions(request.cloud);
|
|
517
|
+
const cloud = toCloudAgentOptions(request.cloud, request.agentServeAgent);
|
|
518
518
|
const hasEnvVars =
|
|
519
519
|
cloud.envVars !== undefined && Object.keys(cloud.envVars).length > 0;
|
|
520
520
|
const options: AgentOptions = {
|
|
@@ -603,24 +603,23 @@ export function toModelSelection(model: ModelSetting): ModelSelection {
|
|
|
603
603
|
}
|
|
604
604
|
|
|
605
605
|
function toCloudAgentOptions(
|
|
606
|
-
cloud: RunnerTurnRequest["cloud"]
|
|
606
|
+
cloud: RunnerTurnRequest["cloud"],
|
|
607
|
+
agentServeAgent?: string
|
|
607
608
|
): CloudAgentOptions {
|
|
608
|
-
if (cloud === undefined) {
|
|
609
|
-
return {};
|
|
610
|
-
}
|
|
611
609
|
return {
|
|
612
|
-
...(cloud
|
|
613
|
-
...(cloud
|
|
614
|
-
...(cloud
|
|
610
|
+
...(cloud?.env === undefined ? {} : { env: cloud.env }),
|
|
611
|
+
...(cloud?.repos === undefined ? {} : { repos: cloud.repos }),
|
|
612
|
+
...(cloud?.workOnCurrentBranch === undefined
|
|
615
613
|
? {}
|
|
616
614
|
: { workOnCurrentBranch: cloud.workOnCurrentBranch }),
|
|
617
|
-
...(cloud
|
|
615
|
+
...(cloud?.autoCreatePR === undefined
|
|
618
616
|
? {}
|
|
619
617
|
: { autoCreatePR: cloud.autoCreatePR }),
|
|
620
|
-
...(cloud
|
|
618
|
+
...(cloud?.skipReviewerRequest === undefined
|
|
621
619
|
? {}
|
|
622
620
|
: { skipReviewerRequest: cloud.skipReviewerRequest }),
|
|
623
|
-
...(cloud
|
|
621
|
+
...(cloud?.envVars === undefined ? {} : { envVars: cloud.envVars }),
|
|
622
|
+
...(agentServeAgent === undefined ? {} : { agentServeAgent }),
|
|
624
623
|
};
|
|
625
624
|
}
|
|
626
625
|
|
package/src/internal/server.ts
CHANGED
|
@@ -20,8 +20,10 @@ import {
|
|
|
20
20
|
} from "../channels/deployments/deployments-channel.js";
|
|
21
21
|
import type { DeploymentsChannelOptions } from "../channels/deployments/types.js";
|
|
22
22
|
import {
|
|
23
|
+
type CursorAccountGithubTokenResolver,
|
|
23
24
|
type CursorGithubRepoScope,
|
|
24
25
|
createCursorAccountGithubTokenResolver,
|
|
26
|
+
createCursorGithubProxyHostResolver,
|
|
25
27
|
cursorGithubPermissionsForChannel,
|
|
26
28
|
type GithubCursorAccountRuntime,
|
|
27
29
|
getGithubCursorAccountRuntime,
|
|
@@ -316,6 +318,10 @@ export async function startServer(
|
|
|
316
318
|
: requireCursorEventsAuth(effectiveCursorEvents, accountKey);
|
|
317
319
|
|
|
318
320
|
let cursorGithubCredentials: CursorGithubCredentialProvider | undefined;
|
|
321
|
+
// Proxy-wired GitHub token resolver for `ctx.host.github` when no startup
|
|
322
|
+
// token mint runs (empty repo scope): host Octokit calls must route
|
|
323
|
+
// through github-proxy exactly like channel Octokit calls.
|
|
324
|
+
let hostGithubProxyResolver: CursorAccountGithubTokenResolver | undefined;
|
|
319
325
|
if (cursorGithubTargets.length > 0) {
|
|
320
326
|
const githubProxyRequested = options.cursorGithubProxy === true;
|
|
321
327
|
const needsGithubAuth =
|
|
@@ -408,23 +414,31 @@ export async function startServer(
|
|
|
408
414
|
);
|
|
409
415
|
target.accountRuntime.credentials.bindProxyTransport(proxyTransport);
|
|
410
416
|
}
|
|
417
|
+
// `ctx.host.github` gets the same proxy transport; without this it
|
|
418
|
+
// would fall back to ambient auth (env token / `gh`) or an
|
|
419
|
+
// unauthenticated Octokit, and every host GitHub call would 404.
|
|
420
|
+
hostGithubProxyResolver = createCursorGithubProxyHostResolver({
|
|
421
|
+
getApiKey: async () => cursorGithubAuth.apiKey,
|
|
422
|
+
transport: proxyTransport,
|
|
423
|
+
});
|
|
411
424
|
logger(
|
|
412
|
-
`[agent-sdk] GitHub auth: channel API calls route through ${githubProxyUrl} (repo scope per request)`
|
|
425
|
+
`[agent-sdk] GitHub auth: channel and host API calls route through ${githubProxyUrl} (repo scope per request)`
|
|
413
426
|
);
|
|
414
427
|
}
|
|
415
428
|
}
|
|
416
429
|
}
|
|
417
430
|
const resolvedCursorGithubCredentials = cursorGithubCredentials;
|
|
431
|
+
const hostGithubToken =
|
|
432
|
+
hostGithubProxyResolver ??
|
|
433
|
+
(resolvedCursorGithubCredentials === undefined
|
|
434
|
+
? undefined
|
|
435
|
+
: createCursorAccountGithubTokenResolver(() =>
|
|
436
|
+
resolvedCursorGithubCredentials.getToken()
|
|
437
|
+
));
|
|
418
438
|
const hostPlatforms = createHostPlatforms(
|
|
419
|
-
|
|
439
|
+
hostGithubToken === undefined
|
|
420
440
|
? {}
|
|
421
|
-
: {
|
|
422
|
-
githubCredentials: {
|
|
423
|
-
token: createCursorAccountGithubTokenResolver(() =>
|
|
424
|
-
resolvedCursorGithubCredentials.getToken()
|
|
425
|
-
),
|
|
426
|
-
},
|
|
427
|
-
}
|
|
441
|
+
: { githubCredentials: { token: hostGithubToken } }
|
|
428
442
|
);
|
|
429
443
|
|
|
430
444
|
if (playground) {
|
|
@@ -24,7 +24,15 @@ import {
|
|
|
24
24
|
SlackNudgeStore,
|
|
25
25
|
} from "../channels/slack/nudge-store.js";
|
|
26
26
|
import { resolveDedicatedSlackHostBinding } from "../channels/slack/slack-channel.js";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
AGENT_SERVE_USER_STORE_FILES_SOURCE_ID,
|
|
29
|
+
agentStoreKeys,
|
|
30
|
+
} from "../files.js";
|
|
31
|
+
import {
|
|
32
|
+
cursorHostedFiles,
|
|
33
|
+
isCursorHostedFilesAvailable,
|
|
34
|
+
isCursorUserStoreFilesAvailable,
|
|
35
|
+
} from "../files-backends/cursor-hosted.js";
|
|
28
36
|
import {
|
|
29
37
|
type AgentProject,
|
|
30
38
|
type ApprovalDecision,
|
|
@@ -80,6 +88,16 @@ import {
|
|
|
80
88
|
isUnpricedCompletedTurn,
|
|
81
89
|
mapSdkTokenUsage,
|
|
82
90
|
} from "./cloud-turn-cost.js";
|
|
91
|
+
import {
|
|
92
|
+
applyAgentContinuationDefaults,
|
|
93
|
+
channelContinuationIdentity,
|
|
94
|
+
httpContinuationIdentity,
|
|
95
|
+
} from "./continuation-channel.js";
|
|
96
|
+
import {
|
|
97
|
+
continuationSource,
|
|
98
|
+
resolveV2Key,
|
|
99
|
+
V2EngineError,
|
|
100
|
+
} from "./continuation-identity.js";
|
|
83
101
|
import { isDevMode } from "./dev-mode.js";
|
|
84
102
|
import { type EventLogMemoryStats, EventLogStore } from "./event-log.js";
|
|
85
103
|
import { TurnEventMapper } from "./event-mapper.js";
|
|
@@ -113,6 +131,7 @@ import { assertCloudCanHonorAccountServersFilters } from "./resolved-connections
|
|
|
113
131
|
import type { AgentRunner } from "./sdk-runner.js";
|
|
114
132
|
import { sessionExternalLinks } from "./session-links.js";
|
|
115
133
|
import { SessionStore } from "./session-store.js";
|
|
134
|
+
import { syncSkillsToStore } from "./skills-sync.js";
|
|
116
135
|
import { StorageCoordinator } from "./storage-coordinator.js";
|
|
117
136
|
import type { FrameworkSessionStore } from "./storage-roles.js";
|
|
118
137
|
import { normalizeToolResult, toolCallErrorMessage } from "./tool-result.js";
|
|
@@ -302,6 +321,8 @@ export class SessionEngine {
|
|
|
302
321
|
>();
|
|
303
322
|
/** Once-per-process cache for the A/B snapshot backfill (see abSnapshot). */
|
|
304
323
|
private restoredAbSnapshot: Promise<ABSnapshot | undefined> | undefined;
|
|
324
|
+
/** In-flight skills → Agent Store sync (see ensureCloudSkillsSynced). */
|
|
325
|
+
private cloudSkillsSync: Promise<boolean> | undefined;
|
|
305
326
|
|
|
306
327
|
private readonly runner: AgentRunner;
|
|
307
328
|
private readonly logger: (line: string) => void;
|
|
@@ -366,6 +387,7 @@ export class SessionEngine {
|
|
|
366
387
|
|
|
367
388
|
constructor(options: SessionEngineOptions) {
|
|
368
389
|
this.project = options.project;
|
|
390
|
+
applyAgentContinuationDefaults(this.project);
|
|
369
391
|
this.stateRoot = options.stateRoot;
|
|
370
392
|
this.runner = options.runner;
|
|
371
393
|
this.logger = options.logger ?? (() => {});
|
|
@@ -734,26 +756,50 @@ export class SessionEngine {
|
|
|
734
756
|
* Start or resume a session on a channel. When `continuationToken`
|
|
735
757
|
* addresses an existing session this is a follow-up turn; otherwise a new
|
|
736
758
|
* session is created (keyed by the provided token, or a fresh one).
|
|
759
|
+
*
|
|
760
|
+
* v2 (`/v2` selectors) refuses `continuationToken` and looks
|
|
761
|
+
* up `(agentId, cKey)` from a mailbox stamp or structured claim.
|
|
737
762
|
*/
|
|
738
763
|
async send(
|
|
739
764
|
channelId: string,
|
|
740
765
|
message: string,
|
|
741
766
|
options: EngineSendOptions = {}
|
|
742
767
|
): Promise<ChannelSession> {
|
|
743
|
-
const
|
|
768
|
+
const identity =
|
|
769
|
+
channelId === HTTP_CHANNEL_ID
|
|
770
|
+
? httpContinuationIdentity(this.project.httpChannel)
|
|
771
|
+
: channelContinuationIdentity(this.channelsById.get(channelId));
|
|
772
|
+
let sendOptions = options;
|
|
773
|
+
let skipContinuationLookup = false;
|
|
774
|
+
if (continuationSource(identity) === "control-plane") {
|
|
775
|
+
if (options.continuationToken !== undefined) {
|
|
776
|
+
throw new V2EngineError(
|
|
777
|
+
"v2: do not pass continuationToken; use mailboxContinuation or claim"
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
const cKey = resolveV2Key(identity, options);
|
|
781
|
+
sendOptions = { ...options, continuationToken: cKey };
|
|
782
|
+
skipContinuationLookup = cKey.startsWith("evt:");
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const auth = sendOptions.auth ?? null;
|
|
744
786
|
let existing =
|
|
745
|
-
|
|
787
|
+
skipContinuationLookup || sendOptions.continuationToken === undefined
|
|
746
788
|
? undefined
|
|
747
789
|
: await this.sessions.findByContinuation(
|
|
748
790
|
channelId,
|
|
749
|
-
|
|
791
|
+
sendOptions.continuationToken
|
|
750
792
|
);
|
|
751
|
-
if (
|
|
793
|
+
if (
|
|
794
|
+
existing === undefined &&
|
|
795
|
+
!skipContinuationLookup &&
|
|
796
|
+
sendOptions.continuationToken !== undefined
|
|
797
|
+
) {
|
|
752
798
|
// Local miss: the session may live only in the storage sink (it
|
|
753
799
|
// fell outside the startup restore window, or restore is lazy-only).
|
|
754
800
|
existing = await this.restoreSessionByContinuation(
|
|
755
801
|
channelId,
|
|
756
|
-
|
|
802
|
+
sendOptions.continuationToken
|
|
757
803
|
);
|
|
758
804
|
}
|
|
759
805
|
|
|
@@ -765,25 +811,25 @@ export class SessionEngine {
|
|
|
765
811
|
// synchronize). Leave the durable continuation key unchanged.
|
|
766
812
|
let record = existing;
|
|
767
813
|
const refreshWorkspace =
|
|
768
|
-
|
|
769
|
-
|
|
814
|
+
sendOptions.workspaceDir !== undefined &&
|
|
815
|
+
sendOptions.workspaceDir !== existing.workspaceOverride;
|
|
770
816
|
if (
|
|
771
817
|
refreshWorkspace ||
|
|
772
|
-
|
|
773
|
-
|
|
818
|
+
sendOptions.workspaceFiles !== undefined ||
|
|
819
|
+
sendOptions.images !== undefined
|
|
774
820
|
) {
|
|
775
821
|
record = await this.updateRecord(existing.sessionId, (current) => ({
|
|
776
822
|
...current,
|
|
777
823
|
workspaceOverride: refreshWorkspace
|
|
778
|
-
?
|
|
824
|
+
? sendOptions.workspaceDir
|
|
779
825
|
: current.workspaceOverride,
|
|
780
826
|
pendingWorkspaceFiles: mergePendingWorkspaceFiles(
|
|
781
827
|
current.pendingWorkspaceFiles,
|
|
782
|
-
|
|
828
|
+
sendOptions.workspaceFiles
|
|
783
829
|
),
|
|
784
830
|
pendingImages: mergePendingImages(
|
|
785
831
|
current.pendingImages,
|
|
786
|
-
|
|
832
|
+
sendOptions.images
|
|
787
833
|
),
|
|
788
834
|
}));
|
|
789
835
|
}
|
|
@@ -792,31 +838,32 @@ export class SessionEngine {
|
|
|
792
838
|
// and the first send's snapshot must not pin this session's turns to a
|
|
793
839
|
// stale repo/prUrl for the session's lifetime.
|
|
794
840
|
if (
|
|
795
|
-
|
|
796
|
-
JSON.stringify(
|
|
841
|
+
sendOptions.cloud !== undefined &&
|
|
842
|
+
JSON.stringify(sendOptions.cloud) !==
|
|
843
|
+
JSON.stringify(record.cloudOverride)
|
|
797
844
|
) {
|
|
798
845
|
record = await this.updateRecord(record.sessionId, (current) => ({
|
|
799
846
|
...current,
|
|
800
|
-
cloudOverride:
|
|
847
|
+
cloudOverride: sendOptions.cloud,
|
|
801
848
|
}));
|
|
802
849
|
}
|
|
803
850
|
// Refresh wake-derived channel state (GitHub headSha / refs) without
|
|
804
851
|
// wiping sticky fields. Null patch values are skipped.
|
|
805
|
-
if (
|
|
852
|
+
if (sendOptions.refreshState !== undefined) {
|
|
806
853
|
record = await this.updateRecord(record.sessionId, (current) => ({
|
|
807
854
|
...current,
|
|
808
855
|
channelState: mergeChannelStateSkipNull(
|
|
809
856
|
current.channelState,
|
|
810
|
-
|
|
857
|
+
sendOptions.refreshState
|
|
811
858
|
),
|
|
812
859
|
}));
|
|
813
860
|
}
|
|
814
861
|
const since = (await this.logs.get(record.sessionId)).count;
|
|
815
862
|
const coalesced = await this.startTurn(record.sessionId, message, {
|
|
816
|
-
admission:
|
|
817
|
-
...(
|
|
863
|
+
admission: sendOptions.admission ?? "preempt",
|
|
864
|
+
...(sendOptions.coalesceSourceTs === undefined
|
|
818
865
|
? {}
|
|
819
|
-
: { sourceTs:
|
|
866
|
+
: { sourceTs: sendOptions.coalesceSourceTs }),
|
|
820
867
|
});
|
|
821
868
|
return this.channelSession(
|
|
822
869
|
record.sessionId,
|
|
@@ -828,22 +875,22 @@ export class SessionEngine {
|
|
|
828
875
|
}
|
|
829
876
|
|
|
830
877
|
const now = new Date().toISOString();
|
|
831
|
-
const channelState = this.initialChannelState(channelId,
|
|
832
|
-
const purpose =
|
|
878
|
+
const channelState = this.initialChannelState(channelId, sendOptions.state);
|
|
879
|
+
const purpose = sendOptions.purpose === "eval" ? "eval" : "live";
|
|
833
880
|
let record: SessionRecord = {
|
|
834
881
|
sessionId: newSessionId(),
|
|
835
882
|
channelId,
|
|
836
|
-
mode:
|
|
883
|
+
mode: sendOptions.mode ?? "chat",
|
|
837
884
|
purpose,
|
|
838
|
-
continuationKey:
|
|
885
|
+
continuationKey: sendOptions.continuationToken ?? newContinuationKey(),
|
|
839
886
|
auth,
|
|
840
|
-
title:
|
|
887
|
+
title: sendOptions.title,
|
|
841
888
|
channelState,
|
|
842
|
-
workspaceOverride:
|
|
843
|
-
pendingWorkspaceFiles:
|
|
844
|
-
pendingImages:
|
|
845
|
-
cloudOverride:
|
|
846
|
-
sdkAgentId:
|
|
889
|
+
workspaceOverride: sendOptions.workspaceDir,
|
|
890
|
+
pendingWorkspaceFiles: sendOptions.workspaceFiles,
|
|
891
|
+
pendingImages: sendOptions.images,
|
|
892
|
+
cloudOverride: sendOptions.cloud,
|
|
893
|
+
sdkAgentId: sendOptions.sdkAgentId,
|
|
847
894
|
turnCount: 0,
|
|
848
895
|
createdAt: now,
|
|
849
896
|
updatedAt: now,
|
|
@@ -857,10 +904,10 @@ export class SessionEngine {
|
|
|
857
904
|
record = await this.enrollABs(record);
|
|
858
905
|
const since = (await this.logs.get(record.sessionId)).count;
|
|
859
906
|
await this.startTurn(record.sessionId, message, {
|
|
860
|
-
admission:
|
|
861
|
-
...(
|
|
907
|
+
admission: sendOptions.admission ?? "preempt",
|
|
908
|
+
...(sendOptions.coalesceSourceTs === undefined
|
|
862
909
|
? {}
|
|
863
|
-
: { sourceTs:
|
|
910
|
+
: { sourceTs: sendOptions.coalesceSourceTs }),
|
|
864
911
|
});
|
|
865
912
|
return this.channelSession(record.sessionId, record, true, false, since);
|
|
866
913
|
}
|
|
@@ -1753,6 +1800,88 @@ export class SessionEngine {
|
|
|
1753
1800
|
};
|
|
1754
1801
|
}
|
|
1755
1802
|
|
|
1803
|
+
/**
|
|
1804
|
+
* Sync authored skills onto the Agent Store at `skills/<name>/…` (hosted)
|
|
1805
|
+
* or `agent-serve/<agent>/skills/<name>/…` (personal-key local+cloud)
|
|
1806
|
+
* before the first cloud prompt, so `--agent-store-skills-dir` and the
|
|
1807
|
+
* first-turn S3 scan can discover them. False when the sync fails: the
|
|
1808
|
+
* turn then runs without store skills rather than pointing discovery at
|
|
1809
|
+
* paths that do not exist.
|
|
1810
|
+
*
|
|
1811
|
+
* With skills authored, every first cloud turn re-copies the authored
|
|
1812
|
+
* bytes and the memo only dedupes concurrent first turns. The mount is
|
|
1813
|
+
* writable from cloud sessions, so a once-per-process copy would let one
|
|
1814
|
+
* session's edits masquerade as agent definition for every later session
|
|
1815
|
+
* until the engine restarts; re-syncing keeps the store true to discovery
|
|
1816
|
+
* the same way local materialization always rewrites skills. Without
|
|
1817
|
+
* skills the sync is best-effort stale cleanup and one attempt per
|
|
1818
|
+
* process is enough, success or not — a deployment without the files
|
|
1819
|
+
* surface must not fail and log on every new session for a feature it
|
|
1820
|
+
* does not use.
|
|
1821
|
+
*/
|
|
1822
|
+
private ensureCloudSkillsSynced(): Promise<boolean> {
|
|
1823
|
+
if (this.cloudSkillsSync !== undefined) {
|
|
1824
|
+
return this.cloudSkillsSync;
|
|
1825
|
+
}
|
|
1826
|
+
const target = this.cloudSkillsStore();
|
|
1827
|
+
if (target === undefined) {
|
|
1828
|
+
this.cloudSkillsSync = Promise.resolve(false);
|
|
1829
|
+
return this.cloudSkillsSync;
|
|
1830
|
+
}
|
|
1831
|
+
const skills = this.project.agent.skills;
|
|
1832
|
+
let sync = syncSkillsToStore({
|
|
1833
|
+
sink: target.sink,
|
|
1834
|
+
agentName: this.project.name,
|
|
1835
|
+
skills,
|
|
1836
|
+
skillsPrefix: target.skillsPrefix,
|
|
1837
|
+
}).then(
|
|
1838
|
+
() => true,
|
|
1839
|
+
(error) => {
|
|
1840
|
+
this.logger(
|
|
1841
|
+
`[skills] ${target.label} sync failed: ${error instanceof Error ? error.message : String(error)}`
|
|
1842
|
+
);
|
|
1843
|
+
return false;
|
|
1844
|
+
}
|
|
1845
|
+
);
|
|
1846
|
+
if (skills.length > 0) {
|
|
1847
|
+
sync = sync.finally(() => {
|
|
1848
|
+
this.cloudSkillsSync = undefined;
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
this.cloudSkillsSync = sync;
|
|
1852
|
+
return sync;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
/**
|
|
1856
|
+
* Hosted deployments own the deployment store. Local `serve`/`run` with
|
|
1857
|
+
* a personal API key writes into the USER store under `agent-serve/`.
|
|
1858
|
+
*/
|
|
1859
|
+
private cloudSkillsStore():
|
|
1860
|
+
| {
|
|
1861
|
+
sink: ReturnType<typeof cursorHostedFiles>;
|
|
1862
|
+
skillsPrefix: string;
|
|
1863
|
+
label: string;
|
|
1864
|
+
}
|
|
1865
|
+
| undefined {
|
|
1866
|
+
if (isCursorHostedFilesAvailable()) {
|
|
1867
|
+
return {
|
|
1868
|
+
sink: cursorHostedFiles(),
|
|
1869
|
+
skillsPrefix: agentStoreKeys.skillsPrefix(),
|
|
1870
|
+
label: "deployment store",
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
if (isCursorUserStoreFilesAvailable()) {
|
|
1874
|
+
return {
|
|
1875
|
+
sink: cursorHostedFiles({
|
|
1876
|
+
storeSourceId: AGENT_SERVE_USER_STORE_FILES_SOURCE_ID,
|
|
1877
|
+
}),
|
|
1878
|
+
skillsPrefix: agentStoreKeys.userSkillsPrefix(this.project.name),
|
|
1879
|
+
label: "user store",
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
return undefined;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1756
1885
|
private async buildTurnRequest(
|
|
1757
1886
|
record: SessionRecord,
|
|
1758
1887
|
agent: ResolvedAgent,
|
|
@@ -1815,8 +1944,9 @@ export class SessionEngine {
|
|
|
1815
1944
|
if (isFirstTurn) {
|
|
1816
1945
|
if (runtime === "cloud") {
|
|
1817
1946
|
// Cloud VMs have no materialized workspace: ship the full context,
|
|
1818
|
-
// including agent tool script bodies. On hosted deployments
|
|
1819
|
-
//
|
|
1947
|
+
// including agent tool script bodies. On hosted deployments authored
|
|
1948
|
+
// skills land on the store mount for native discovery, and the
|
|
1949
|
+
// shared memory journal is on that mount too — say where. When the
|
|
1820
1950
|
// turn carries the session-bound server-tool MCP endpoint, the
|
|
1821
1951
|
// preamble catalogs server tools under that MCP server name *before*
|
|
1822
1952
|
// the agent-tool "write the script if missing" recipe, so a model
|
|
@@ -1828,7 +1958,15 @@ export class SessionEngine {
|
|
|
1828
1958
|
? undefined
|
|
1829
1959
|
: SERVER_TOOLS_MCP_SERVER_NAME,
|
|
1830
1960
|
});
|
|
1831
|
-
const
|
|
1961
|
+
const hosted = isCursorHostedFilesAvailable();
|
|
1962
|
+
// Synced whenever a store is reachable, not just when skills exist:
|
|
1963
|
+
// removing the last skill has to delete its store copies too.
|
|
1964
|
+
// Discovery is native (`--agent-store-skills-dir`); this only has
|
|
1965
|
+
// to land the bytes.
|
|
1966
|
+
if (hosted || isCursorUserStoreFilesAvailable()) {
|
|
1967
|
+
await this.ensureCloudSkillsSynced();
|
|
1968
|
+
}
|
|
1969
|
+
const memorySection = hosted
|
|
1832
1970
|
? `\n\n${buildCloudMemorySection(this.project.name)}`
|
|
1833
1971
|
: "";
|
|
1834
1972
|
turnPrompt = `${preamble}${memorySection}\n\n${turnPrompt}`;
|
|
@@ -1859,6 +1997,9 @@ export class SessionEngine {
|
|
|
1859
1997
|
...(agent.model === undefined ? {} : { model: agent.model }),
|
|
1860
1998
|
runtime,
|
|
1861
1999
|
...(mergedCloud === undefined ? {} : { cloud: mergedCloud }),
|
|
2000
|
+
...(runtime === "cloud" && !isCursorHostedFilesAvailable()
|
|
2001
|
+
? { agentServeAgent: this.project.name }
|
|
2002
|
+
: {}),
|
|
1862
2003
|
customTools:
|
|
1863
2004
|
runtime === "local"
|
|
1864
2005
|
? await this.buildCustomTools(record, agent, turnId, emit)
|
|
@@ -11,6 +11,7 @@ import { mkdir, readdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import type { SessionRecord } from "../types.js";
|
|
13
13
|
import { isSafeSessionId } from "./auth.js";
|
|
14
|
+
import { isV2DurableContinuationKey } from "./continuation-identity.js";
|
|
14
15
|
|
|
15
16
|
export class SessionStore {
|
|
16
17
|
private readonly cache = new Map<string, SessionRecord>();
|
|
@@ -39,6 +40,11 @@ export class SessionStore {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
private continuationKeyOf(channelId: string, key: string): string {
|
|
43
|
+
// v2 index is (agentId, cKey). This store is already per-agent, so drop
|
|
44
|
+
// channelId for `cont:` / `evt:` keys. v1 stays `{channelId}\n{token}`.
|
|
45
|
+
if (isV2DurableContinuationKey(key)) {
|
|
46
|
+
return `\0v2\n${key}`;
|
|
47
|
+
}
|
|
42
48
|
return `${channelId}\n${key}`;
|
|
43
49
|
}
|
|
44
50
|
|