@cursor/july 0.1.104 → 0.1.105
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channels/deployments/deployments-channel.d.ts.map +1 -1
- package/dist/channels/deployments/deployments-channel.js +1 -0
- package/dist/channels/deployments/types.d.ts +2 -0
- package/dist/channels/deployments/types.d.ts.map +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.DbMWUyGb.js → app.BVwaffdK.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.CJxMklAy.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DcNeTh9o.js → VPLocalSearchBox.WuWTZ1x8.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DMjhY3gl.js → theme.BYdI_6sF.js} +2 -2
- package/dist/docs/assets/guides_grokbot-agents.md.DVqdhBKb.js +9 -0
- package/dist/docs/assets/guides_grokbot-agents.md.DVqdhBKb.lean.js +1 -0
- package/dist/docs/assets/index.md.BW_6tOgR.js +5 -0
- package/dist/docs/assets/index.md.BW_6tOgR.lean.js +1 -0
- package/dist/docs/assets/{reference_cli.md.CJNc_xYK.js → reference_cli.md.C6Lo8SEd.js} +2 -1
- package/dist/docs/assets/{reference_extensions.md.DykHW3qL.js → reference_extensions.md.CGmMLblt.js} +1 -1
- package/dist/docs/assets/templates_grokbot-agents.md.c1s6Flp9.js +3 -0
- package/dist/docs/assets/templates_grokbot-agents.md.c1s6Flp9.lean.js +1 -0
- package/dist/docs/building-with-agents.html +4 -4
- package/dist/docs/concepts.html +4 -4
- package/dist/docs/deployment.html +4 -4
- package/dist/docs/evals.html +4 -4
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-agents.html +5 -5
- package/dist/docs/guides/convert-automation.html +4 -4
- package/dist/docs/guides/github.html +4 -4
- package/dist/docs/guides/grokbot-agents.html +35 -0
- package/dist/docs/guides/grokbot-agents.md +88 -0
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/mcp-oauth.html +4 -4
- package/dist/docs/guides/opentelemetry.html +5 -5
- package/dist/docs/guides/slack.html +4 -4
- package/dist/docs/guides/webhooks.html +4 -4
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +4 -4
- package/dist/docs/index.html +6 -6
- package/dist/docs/index.md +6 -1
- package/dist/docs/llms-full.txt +156 -2
- package/dist/docs/llms.txt +2 -0
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +4 -4
- package/dist/docs/reference/artifacts.html +4 -4
- package/dist/docs/reference/channels.html +4 -4
- package/dist/docs/reference/cli.html +7 -6
- package/dist/docs/reference/cli.md +2 -1
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/extensions.html +6 -6
- package/dist/docs/reference/extensions.md +2 -0
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +4 -4
- package/dist/docs/reference/instructions.html +4 -4
- package/dist/docs/reference/playground.html +4 -4
- package/dist/docs/reference/project-layout.html +4 -4
- package/dist/docs/reference/prompt.html +4 -4
- package/dist/docs/reference/result.html +4 -4
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +4 -4
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +4 -4
- package/dist/docs/scaffolding-agents.html +4 -4
- package/dist/docs/skills/ab.html +4 -4
- package/dist/docs/skills/create-agent.html +4 -4
- package/dist/docs/skills/debug.html +4 -4
- package/dist/docs/skills/evals.html +4 -4
- package/dist/docs/skills/framework-map.html +4 -4
- package/dist/docs/skills/github.html +4 -4
- package/dist/docs/skills/hillclimb.html +4 -4
- package/dist/docs/skills/index.html +4 -4
- package/dist/docs/skills/mcp-auth.html +4 -4
- package/dist/docs/skills/otel.html +4 -4
- package/dist/docs/skills/setup-slack.html +4 -4
- package/dist/docs/storage.html +4 -4
- package/dist/docs/templates/agentic-owners.html +4 -4
- package/dist/docs/templates/agents-md.html +4 -4
- package/dist/docs/templates/code-wiki.html +5 -5
- package/dist/docs/templates/demo.html +5 -5
- package/dist/docs/templates/grokbot-agents.html +29 -0
- package/dist/docs/templates/grokbot-agents.md +48 -0
- package/dist/docs/templates/pr-autofixer.html +4 -4
- package/dist/docs/templates/security-help.html +4 -4
- package/dist/docs/templates/security-reviewer.html +4 -4
- package/dist/docs/templates/triage.html +4 -4
- package/dist/docs/troubleshooting.html +4 -4
- package/dist/files.d.ts +7 -2
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +7 -2
- package/dist/internal/cursor-agent-template.d.ts +1 -1
- package/dist/internal/cursor-agent-template.d.ts.map +1 -1
- package/dist/internal/cursor-agent-template.js +1 -0
- package/dist/internal/framework-file-storage.d.ts +3 -1
- package/dist/internal/framework-file-storage.d.ts.map +1 -1
- package/dist/internal/framework-file-storage.js +3 -1
- package/dist/internal/hosted-delivery.d.ts +7 -0
- package/dist/internal/hosted-delivery.d.ts.map +1 -1
- package/dist/internal/hosted-delivery.js +29 -1
- package/dist/internal/hosted-managed-automation.d.ts.map +1 -1
- package/dist/internal/hosted-managed-automation.js +27 -2
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +4 -1
- package/dist/internal/sdk-runner.d.ts +12 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +18 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +11 -3
- package/dist/internal/session-engine.d.ts +8 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +132 -52
- package/dist/internal/store-mirror.d.ts +2 -2
- package/dist/internal/store-mirror.js +2 -2
- package/dist/internal/tenant-slack/relay.d.ts +2 -0
- package/dist/internal/tenant-slack/relay.d.ts.map +1 -1
- package/dist/internal/tenant-slack/relay.js +18 -8
- package/dist/internal/workspace-sync.d.ts +31 -6
- package/dist/internal/workspace-sync.d.ts.map +1 -1
- package/dist/internal/workspace-sync.js +67 -12
- package/dist/internal/workspace.d.ts +9 -0
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +16 -0
- package/dist/playground/assets/{index-8YiozU89.js → index-BaQzjaNK.js} +36 -36
- package/dist/playground/assets/index-CceqnLTX.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +9 -8
- package/dist/types.d.ts.map +1 -1
- package/docs/README.md +6 -1
- package/docs/guides/grokbot-agents.md +93 -0
- package/docs/reference/cli.md +2 -1
- package/docs/reference/extensions.md +2 -0
- package/docs/templates/grokbot-agents.md +53 -0
- package/package.json +10 -1
- package/src/channels/deployments/deployments-channel.ts +1 -0
- package/src/channels/deployments/types.ts +2 -0
- package/src/files.ts +9 -2
- package/src/internal/cursor-agent-template.ts +1 -0
- package/src/internal/framework-file-storage.ts +3 -1
- package/src/internal/hosted-delivery.ts +53 -1
- package/src/internal/hosted-managed-automation.ts +49 -5
- package/src/internal/init-project.ts +4 -1
- package/src/internal/sdk-runner.ts +19 -0
- package/src/internal/server.ts +16 -0
- package/src/internal/session-engine.ts +168 -56
- package/src/internal/store-mirror.ts +2 -2
- package/src/internal/tenant-slack/relay.ts +28 -4
- package/src/internal/workspace-sync.ts +84 -12
- package/src/internal/workspace.ts +20 -0
- package/src/types.ts +9 -8
- package/templates/grokbot-agents/README.md +26 -0
- package/templates/grokbot-agents/agent/agent.ts +6 -0
- package/templates/grokbot-agents/agent/extensions/grokbot.ts +5 -0
- package/templates/grokbot-agents/agent/instructions.md +7 -0
- package/templates/grokbot-agents/evals/evals.config.ts +5 -0
- package/templates/grokbot-agents/evals/grokbot-agents.eval.ts +18 -0
- package/templates/grokbot-agents/init.json +18 -0
- package/templates/grokbot-agents/package.json +17 -0
- package/templates/grokbot-agents/tsconfig.json +12 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.Bqsulb9K.js +0 -1
- package/dist/docs/assets/index.md.D75GWhPl.js +0 -5
- package/dist/docs/assets/index.md.D75GWhPl.lean.js +0 -1
- package/dist/playground/assets/index-MbMxXg-y.css +0 -1
- /package/dist/docs/assets/{reference_cli.md.CJNc_xYK.lean.js → reference_cli.md.C6Lo8SEd.lean.js} +0 -0
- /package/dist/docs/assets/{reference_extensions.md.DykHW3qL.lean.js → reference_extensions.md.CGmMLblt.lean.js} +0 -0
|
@@ -358,6 +358,13 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
358
358
|
flushStartLog?(startLogId: string): Promise<void>;
|
|
359
359
|
/** Snapshot of the process log ring at persist time. */
|
|
360
360
|
snapshotLogs?(): LogRingEntry[];
|
|
361
|
+
/**
|
|
362
|
+
* Drop the cached SDK agent handle before the HTTP reply. Hosted v2
|
|
363
|
+
* hibernates this process as soon as the reply lands; leaving the handle
|
|
364
|
+
* in memory freezes a dead Connect/HTTP2 session that the next wake
|
|
365
|
+
* then reuses.
|
|
366
|
+
*/
|
|
367
|
+
evictSdkSession?(sessionId: string): void;
|
|
361
368
|
/**
|
|
362
369
|
* Wait until guest egress works before authored admission/execution.
|
|
363
370
|
* Defaults to {@link waitForGuestNetworkReady}. Tests inject a stub.
|
|
@@ -463,8 +470,18 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
463
470
|
if (unconsumed !== undefined) {
|
|
464
471
|
return finish(unconsumed);
|
|
465
472
|
}
|
|
466
|
-
const
|
|
473
|
+
const rawAdmission = await readJsonObject(request);
|
|
474
|
+
const delivery = parseHostedAdmission(rawAdmission);
|
|
467
475
|
if (delivery === undefined) {
|
|
476
|
+
const rawKeys =
|
|
477
|
+
typeof rawAdmission === "object" &&
|
|
478
|
+
rawAdmission !== null &&
|
|
479
|
+
!Array.isArray(rawAdmission)
|
|
480
|
+
? Object.keys(rawAdmission as Record<string, unknown>)
|
|
481
|
+
: [];
|
|
482
|
+
console.info(
|
|
483
|
+
`[agent-sdk] hosted admission rejected: envelope failed to parse keys=${JSON.stringify(rawKeys)} hasManagedAutomation=${String(rawKeys.includes("managedAutomation"))}`
|
|
484
|
+
);
|
|
468
485
|
return finish(
|
|
469
486
|
errorResponse(
|
|
470
487
|
400,
|
|
@@ -473,6 +490,9 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
473
490
|
)
|
|
474
491
|
);
|
|
475
492
|
}
|
|
493
|
+
console.info(
|
|
494
|
+
`[agent-sdk] hosted admission received deliveryId=${delivery.deliveryId} channelId=${delivery.channelId} channelKind=${delivery.channelKind} eventType=${delivery.eventType} sessionMode=${delivery.sessionMode} managedAutomation=${delivery.managedAutomation === undefined ? "<absent>" : `automationUuid=${delivery.managedAutomation.automationUuid} mcpServerNames=${String(delivery.managedAutomation.mcpServerNames.length)} slackChannels=${delivery.managedAutomation.slackChannels === undefined ? "<absent>" : JSON.stringify(delivery.managedAutomation.slackChannels)}`}`
|
|
495
|
+
);
|
|
476
496
|
recordHostedManagedAutomation(delivery);
|
|
477
497
|
const channel = input.channels.find(
|
|
478
498
|
(candidate) => candidate.id === delivery.channelId
|
|
@@ -624,6 +644,7 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
624
644
|
sessionId: "",
|
|
625
645
|
turnStatus: HOSTED_EXECUTION_DIAG_RUNNING_STATUS,
|
|
626
646
|
});
|
|
647
|
+
let sessionId = "";
|
|
627
648
|
try {
|
|
628
649
|
const args = input.buildHandlerArgs(
|
|
629
650
|
admitted.send.channelId,
|
|
@@ -635,10 +656,19 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
635
656
|
key: admitted.send.mailboxContinuationKey,
|
|
636
657
|
},
|
|
637
658
|
});
|
|
659
|
+
sessionId = session.id;
|
|
638
660
|
const outcome = await session.waitForCompletion();
|
|
639
661
|
// Failed turns drain too: their record/continuation updates must
|
|
640
662
|
// survive the park just like a finished turn's.
|
|
641
663
|
await input.drainSessionDurability?.(session.id);
|
|
664
|
+
// Evict before the reply: TAS parks and hibernates this process
|
|
665
|
+
// as soon as the POST returns. A leftover SDK handle freezes a
|
|
666
|
+
// dead Connect/HTTP2 session that the next wake then reuses.
|
|
667
|
+
evictHostedSdkSessions(
|
|
668
|
+
input.evictSdkSession,
|
|
669
|
+
session.id,
|
|
670
|
+
execution.sessionPublicId
|
|
671
|
+
);
|
|
642
672
|
await writeDiag({
|
|
643
673
|
sessionId: session.id,
|
|
644
674
|
turnStatus: outcome.status,
|
|
@@ -671,6 +701,11 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
671
701
|
},
|
|
672
702
|
};
|
|
673
703
|
} catch (error) {
|
|
704
|
+
evictHostedSdkSessions(
|
|
705
|
+
input.evictSdkSession,
|
|
706
|
+
sessionId,
|
|
707
|
+
execution.sessionPublicId
|
|
708
|
+
);
|
|
674
709
|
await writeDiag({
|
|
675
710
|
sessionId: "",
|
|
676
711
|
turnStatus: "error",
|
|
@@ -687,6 +722,23 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
687
722
|
});
|
|
688
723
|
}
|
|
689
724
|
|
|
725
|
+
function evictHostedSdkSessions(
|
|
726
|
+
evict: ((sessionId: string) => void) | undefined,
|
|
727
|
+
...ids: Array<string | undefined>
|
|
728
|
+
): void {
|
|
729
|
+
if (evict === undefined) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
const seen = new Set<string>();
|
|
733
|
+
for (const id of ids) {
|
|
734
|
+
if (id === undefined || id === "" || seen.has(id)) {
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
seen.add(id);
|
|
738
|
+
evict(id);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
690
742
|
function hostedErrorForUnfinishedTurn(
|
|
691
743
|
errorMessage: string | undefined
|
|
692
744
|
): typeof GUEST_CONNECT_STALLED | "turn_failed" {
|
|
@@ -17,10 +17,36 @@
|
|
|
17
17
|
|
|
18
18
|
import type { HostedAdmissionManagedAutomation } from "./hosted-delivery-protocol.js";
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
type ManagedAutomationEntry = {
|
|
21
|
+
managedAutomation: HostedAdmissionManagedAutomation;
|
|
22
|
+
expiresAt: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Process-wide slot for the table. Hosted engines run the compiled CLI
|
|
27
|
+
* (`dist/`), which records at admission, while first-party factory agents
|
|
28
|
+
* relative-import this file from `src/` and read at finalize — two module
|
|
29
|
+
* instances in one process. A module-local Map would be recorded in one
|
|
30
|
+
* copy and read from the other (always undefined, so a fork never sees its
|
|
31
|
+
* Slack channels or MCP servers); the shared symbol keeps one table.
|
|
32
|
+
*/
|
|
33
|
+
const MANAGED_AUTOMATION_TABLE_KEY = Symbol.for(
|
|
34
|
+
"cursor.agentkit.hostedManagedAutomationByDelivery"
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
function managedAutomationTable(): Map<string, ManagedAutomationEntry> {
|
|
38
|
+
const slots = globalThis as unknown as Record<
|
|
39
|
+
symbol,
|
|
40
|
+
Map<string, ManagedAutomationEntry> | undefined
|
|
41
|
+
>;
|
|
42
|
+
let table = slots[MANAGED_AUTOMATION_TABLE_KEY];
|
|
43
|
+
if (table === undefined) {
|
|
44
|
+
table = new Map();
|
|
45
|
+
slots[MANAGED_AUTOMATION_TABLE_KEY] = table;
|
|
46
|
+
}
|
|
47
|
+
return table;
|
|
48
|
+
}
|
|
49
|
+
|
|
24
50
|
/** Long enough to outlive a slow review run kicked off by the delivery. */
|
|
25
51
|
const MANAGED_AUTOMATION_TTL_MS = 60 * 60_000;
|
|
26
52
|
const MANAGED_AUTOMATION_MAX_ENTRIES = 10_000;
|
|
@@ -30,8 +56,12 @@ export function recordHostedManagedAutomation(delivery: {
|
|
|
30
56
|
managedAutomation?: HostedAdmissionManagedAutomation;
|
|
31
57
|
}): void {
|
|
32
58
|
if (delivery.managedAutomation === undefined) {
|
|
59
|
+
console.info(
|
|
60
|
+
`[agent-sdk] hosted managedAutomation not recorded deliveryId=${delivery.deliveryId}: admission envelope carried no managedAutomation slice (non-managed delivery, or the control plane resolved no MCP servers and no Slack channels)`
|
|
61
|
+
);
|
|
33
62
|
return;
|
|
34
63
|
}
|
|
64
|
+
const managedAutomationByDelivery = managedAutomationTable();
|
|
35
65
|
const now = Date.now();
|
|
36
66
|
for (const [key, entry] of managedAutomationByDelivery) {
|
|
37
67
|
if (entry.expiresAt <= now) {
|
|
@@ -39,12 +69,19 @@ export function recordHostedManagedAutomation(delivery: {
|
|
|
39
69
|
}
|
|
40
70
|
}
|
|
41
71
|
if (managedAutomationByDelivery.size >= MANAGED_AUTOMATION_MAX_ENTRIES) {
|
|
72
|
+
console.info(
|
|
73
|
+
`[agent-sdk] hosted managedAutomation table full (${String(managedAutomationByDelivery.size)} entries); clearing before recording deliveryId=${delivery.deliveryId}`
|
|
74
|
+
);
|
|
42
75
|
managedAutomationByDelivery.clear();
|
|
43
76
|
}
|
|
44
77
|
managedAutomationByDelivery.set(delivery.deliveryId, {
|
|
45
78
|
managedAutomation: delivery.managedAutomation,
|
|
46
79
|
expiresAt: now + MANAGED_AUTOMATION_TTL_MS,
|
|
47
80
|
});
|
|
81
|
+
const slackChannels = delivery.managedAutomation.slackChannels;
|
|
82
|
+
console.info(
|
|
83
|
+
`[agent-sdk] hosted managedAutomation recorded deliveryId=${delivery.deliveryId} automationUuid=${delivery.managedAutomation.automationUuid} mcpServerNames=${String(delivery.managedAutomation.mcpServerNames.length)} slackChannels=${slackChannels === undefined ? "<absent>" : JSON.stringify(slackChannels)} ttlMs=${String(MANAGED_AUTOMATION_TTL_MS)} tableSize=${String(managedAutomationByDelivery.size)}`
|
|
84
|
+
);
|
|
48
85
|
}
|
|
49
86
|
|
|
50
87
|
/**
|
|
@@ -55,11 +92,18 @@ export function recordHostedManagedAutomation(delivery: {
|
|
|
55
92
|
export function getHostedManagedAutomation(
|
|
56
93
|
deliveryId: string
|
|
57
94
|
): HostedAdmissionManagedAutomation | undefined {
|
|
95
|
+
const managedAutomationByDelivery = managedAutomationTable();
|
|
58
96
|
const entry = managedAutomationByDelivery.get(deliveryId);
|
|
59
97
|
if (entry === undefined) {
|
|
98
|
+
console.info(
|
|
99
|
+
`[agent-sdk] hosted managedAutomation lookup miss deliveryId=${deliveryId}: no entry (never recorded in this process, or evicted) tableSize=${String(managedAutomationByDelivery.size)} knownDeliveryIds=${JSON.stringify([...managedAutomationByDelivery.keys()].slice(0, 10))}`
|
|
100
|
+
);
|
|
60
101
|
return undefined;
|
|
61
102
|
}
|
|
62
103
|
if (entry.expiresAt <= Date.now()) {
|
|
104
|
+
console.info(
|
|
105
|
+
`[agent-sdk] hosted managedAutomation lookup expired deliveryId=${deliveryId} automationUuid=${entry.managedAutomation.automationUuid} expiredMsAgo=${String(Date.now() - entry.expiresAt)}`
|
|
106
|
+
);
|
|
63
107
|
managedAutomationByDelivery.delete(deliveryId);
|
|
64
108
|
return undefined;
|
|
65
109
|
}
|
|
@@ -68,5 +112,5 @@ export function getHostedManagedAutomation(
|
|
|
68
112
|
|
|
69
113
|
/** Drop recorded managed-automation slices (tests only). */
|
|
70
114
|
export function resetHostedManagedAutomationForTest(): void {
|
|
71
|
-
|
|
115
|
+
managedAutomationTable().clear();
|
|
72
116
|
}
|
|
@@ -77,6 +77,7 @@ const TEMPLATES: Readonly<Record<TemplateName, TemplateEntry>> = {
|
|
|
77
77
|
"agents-md": { dir: "agents-md" },
|
|
78
78
|
"code-wiki": { dir: "code-wiki" },
|
|
79
79
|
demo: { dir: "demo" },
|
|
80
|
+
"grokbot-agents": { dir: "grokbot-agents" },
|
|
80
81
|
"pr-autofixer": { dir: "pr-autofixer" },
|
|
81
82
|
"security-help": { dir: "security-help" },
|
|
82
83
|
"security-reviewer": { dir: "security-reviewer" },
|
|
@@ -711,6 +712,7 @@ export function printInitGuide(): void {
|
|
|
711
712
|
Scaffold a project, then open the playground:
|
|
712
713
|
npx ${PACKAGE_NAME} init ./my-agent # installs deps + links agent-sdk onto PATH
|
|
713
714
|
npx ${PACKAGE_NAME} init ./my-demo --template demo # record a PR walkthrough
|
|
715
|
+
npx ${PACKAGE_NAME} init ./grokbot --template grokbot-agents # talk to Grok Bot agents
|
|
714
716
|
npx ${PACKAGE_NAME} init ./code-wiki --template code-wiki # keep wiki pages current after merge
|
|
715
717
|
npx ${PACKAGE_NAME} init ./agents-md --template agents-md # keep AGENTS.md current from last week's PRs and Slack
|
|
716
718
|
npx ${PACKAGE_NAME} init ./my-reviewer --template security-reviewer # review PRs for security bugs
|
|
@@ -719,8 +721,9 @@ Scaffold a project, then open the playground:
|
|
|
719
721
|
npx ${PACKAGE_NAME} init ./my-triage --template triage-jira # comment on Jira issues
|
|
720
722
|
npx ${PACKAGE_NAME} init ./my-owners --template agentic-owners # review PRs via owners policies
|
|
721
723
|
npx ${PACKAGE_NAME} init ./pr-autofixer --template pr-autofixer # fix PRs on a cloud VM
|
|
722
|
-
# TTY asks
|
|
724
|
+
# TTY asks the template's questions. Scripts pass --var:
|
|
723
725
|
# --var repos=acme/widgets,acme/api
|
|
726
|
+
# --var agents="Plan Keeper, Chief of Staff"
|
|
724
727
|
cd ./my-agent && agent-sdk dev # or: npx ${PACKAGE_NAME} dev
|
|
725
728
|
|
|
726
729
|
Recommended loop once the project exists (from the project directory):
|
|
@@ -432,6 +432,25 @@ export class CursorSdkRunner implements AgentRunner {
|
|
|
432
432
|
return { warmed: true };
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Drop the cached SDK agent for this session, if one is open.
|
|
437
|
+
*
|
|
438
|
+
* Hosted v2 parks and hibernates this process as soon as the execution
|
|
439
|
+
* reply lands on non-interactive channels. A leftover handle still holds
|
|
440
|
+
* the pre-freeze Connect/HTTP2 session; the next wake then reuses it and
|
|
441
|
+
* `AgentService/Run` never starts. Call after `waitForCompletion` so the
|
|
442
|
+
* hibernate snapshot has an empty cache. Slack's interactive hot-window
|
|
443
|
+
* still reuses handles because that path does not go through hosted
|
|
444
|
+
* delivery.
|
|
445
|
+
*/
|
|
446
|
+
evictSession(sessionId: string): void {
|
|
447
|
+
const cached = this.agents.get(sessionId);
|
|
448
|
+
if (cached === undefined) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
this.evictAgent(sessionId, cached.agent);
|
|
452
|
+
}
|
|
453
|
+
|
|
435
454
|
async dispose(): Promise<void> {
|
|
436
455
|
const releases = this.prewarmReleases.splice(0);
|
|
437
456
|
await Promise.allSettled(releases.map((release) => release()));
|
package/src/internal/server.ts
CHANGED
|
@@ -918,6 +918,13 @@ export async function startServer(
|
|
|
918
918
|
...(accountRefs !== undefined && accountBridge !== undefined
|
|
919
919
|
? { accountBridge, accountRefs }
|
|
920
920
|
: {}),
|
|
921
|
+
...(sdkRunner instanceof CursorSdkRunner
|
|
922
|
+
? {
|
|
923
|
+
evictSdkSession: (sessionId: string) => {
|
|
924
|
+
sdkRunner.evictSession(sessionId);
|
|
925
|
+
},
|
|
926
|
+
}
|
|
927
|
+
: {}),
|
|
921
928
|
});
|
|
922
929
|
for (const channel of project.channels) {
|
|
923
930
|
const definition = channel.definition;
|
|
@@ -1440,6 +1447,12 @@ function mountAgentRoutes(
|
|
|
1440
1447
|
* here so the Temporal host can read a stable path after the POST.
|
|
1441
1448
|
*/
|
|
1442
1449
|
diagStateRoot?: string;
|
|
1450
|
+
/**
|
|
1451
|
+
* Drop the cached SDK agent handle after a hosted turn so hibernate
|
|
1452
|
+
* cannot snapshot a frozen Connect/HTTP2 session. Injected test
|
|
1453
|
+
* runners may omit this.
|
|
1454
|
+
*/
|
|
1455
|
+
evictSdkSession?: (sessionId: string) => void;
|
|
1443
1456
|
}
|
|
1444
1457
|
): void {
|
|
1445
1458
|
const { slug, project, engine, scheduleRunner, reminderRunner } = runtime;
|
|
@@ -1523,6 +1536,9 @@ function mountAgentRoutes(
|
|
|
1523
1536
|
flushStartLog,
|
|
1524
1537
|
}),
|
|
1525
1538
|
snapshotLogs: () => logRing.since(),
|
|
1539
|
+
...(args.evictSdkSession === undefined
|
|
1540
|
+
? {}
|
|
1541
|
+
: { evictSdkSession: args.evictSdkSession }),
|
|
1526
1542
|
});
|
|
1527
1543
|
}
|
|
1528
1544
|
// Playground lists/opens Slack (and other channel) sessions across
|
|
@@ -202,13 +202,19 @@ import { resolveAgentV2Hosting } from "./v2-hosting.js";
|
|
|
202
202
|
import {
|
|
203
203
|
buildAgentsMdContent,
|
|
204
204
|
buildCloudMemorySection,
|
|
205
|
+
buildCloudSessionWorkspaceSection,
|
|
205
206
|
buildCloudWorkspaceSection,
|
|
206
207
|
ensureOwnGitRoot,
|
|
207
208
|
isNestedInGitRepo,
|
|
208
209
|
materializeWorkspace,
|
|
209
210
|
SERVER_TOOLS_MCP_SERVER_NAME,
|
|
210
211
|
} from "./workspace.js";
|
|
211
|
-
import {
|
|
212
|
+
import {
|
|
213
|
+
syncSessionWorkspaceFiles,
|
|
214
|
+
syncWorkspaceToStore,
|
|
215
|
+
WorkspaceFileRefusal,
|
|
216
|
+
wipeSessionWorkspace,
|
|
217
|
+
} from "./workspace-sync.js";
|
|
212
218
|
|
|
213
219
|
export const HTTP_CHANNEL_ID = "http";
|
|
214
220
|
export { SCHEDULE_CHANNEL_PREFIX } from "./prompt-context.js";
|
|
@@ -222,6 +228,7 @@ interface CloudStoreTarget {
|
|
|
222
228
|
mountPath: string;
|
|
223
229
|
skillsPrefix: string;
|
|
224
230
|
workspacePrefix: string;
|
|
231
|
+
sessionWorkspacePrefix: (sessionId: string) => string;
|
|
225
232
|
label: string;
|
|
226
233
|
}
|
|
227
234
|
|
|
@@ -2254,10 +2261,13 @@ export class SessionEngine {
|
|
|
2254
2261
|
await this.baselineDeliveredNudges(record);
|
|
2255
2262
|
const pendingFiles = record.pendingWorkspaceFiles;
|
|
2256
2263
|
const pendingImages = record.pendingImages;
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
)
|
|
2264
|
+
const runtime = resolveSessionRuntime({
|
|
2265
|
+
agentRuntime: agent.runtime,
|
|
2266
|
+
cloudOverride: record.cloudOverride,
|
|
2267
|
+
});
|
|
2268
|
+
const hasPendingFiles =
|
|
2269
|
+
pendingFiles !== undefined && Object.keys(pendingFiles).length > 0;
|
|
2270
|
+
if (hasPendingFiles && runtime !== "cloud") {
|
|
2261
2271
|
const root = await this.resolveFileSystem({
|
|
2262
2272
|
sessionId,
|
|
2263
2273
|
continuationKey: record.continuationKey ?? undefined,
|
|
@@ -2267,13 +2277,14 @@ export class SessionEngine {
|
|
|
2267
2277
|
await writeWorkspaceFilesToRoot(root, pendingFiles);
|
|
2268
2278
|
}
|
|
2269
2279
|
if (
|
|
2270
|
-
(
|
|
2271
|
-
Object.keys(pendingFiles).length > 0) ||
|
|
2280
|
+
(hasPendingFiles && runtime !== "cloud") ||
|
|
2272
2281
|
(pendingImages !== undefined && pendingImages.length > 0)
|
|
2273
2282
|
) {
|
|
2274
2283
|
await this.updateRecord(sessionId, (current) => ({
|
|
2275
2284
|
...current,
|
|
2276
2285
|
pendingWorkspaceFiles:
|
|
2286
|
+
hasPendingFiles &&
|
|
2287
|
+
runtime !== "cloud" &&
|
|
2277
2288
|
current.pendingWorkspaceFiles === pendingFiles
|
|
2278
2289
|
? undefined
|
|
2279
2290
|
: current.pendingWorkspaceFiles,
|
|
@@ -2441,6 +2452,7 @@ export class SessionEngine {
|
|
|
2441
2452
|
data: { message: outcome.errorMessage ?? "task failed" },
|
|
2442
2453
|
}
|
|
2443
2454
|
);
|
|
2455
|
+
await this.wipeCloudSessionWorkspace(sessionId);
|
|
2444
2456
|
} else {
|
|
2445
2457
|
await this.appendEvent(sessionId, {
|
|
2446
2458
|
type: "session.waiting",
|
|
@@ -2495,6 +2507,68 @@ export class SessionEngine {
|
|
|
2495
2507
|
);
|
|
2496
2508
|
}
|
|
2497
2509
|
|
|
2510
|
+
/**
|
|
2511
|
+
* Replace this session's store prefix with the pending `workspaceFiles`.
|
|
2512
|
+
* Undefined when the write did not land; the prompt must not name the
|
|
2513
|
+
* mount, matching ensureCloudWorkspaceSynced.
|
|
2514
|
+
*/
|
|
2515
|
+
private async syncCloudSessionWorkspaceFiles(
|
|
2516
|
+
store: CloudStoreTarget,
|
|
2517
|
+
sessionId: string,
|
|
2518
|
+
files: NonNullable<SessionRecord["pendingWorkspaceFiles"]>
|
|
2519
|
+
): Promise<{ dir: string; relPaths: string[] } | undefined> {
|
|
2520
|
+
const workspacePrefix = store.sessionWorkspacePrefix(sessionId);
|
|
2521
|
+
try {
|
|
2522
|
+
const relPaths = await syncSessionWorkspaceFiles({
|
|
2523
|
+
sink: store.sink,
|
|
2524
|
+
files,
|
|
2525
|
+
workspacePrefix,
|
|
2526
|
+
});
|
|
2527
|
+
return {
|
|
2528
|
+
dir: `${store.mountPath}/${workspacePrefix}`,
|
|
2529
|
+
relPaths,
|
|
2530
|
+
};
|
|
2531
|
+
} catch (error) {
|
|
2532
|
+
this.logger(
|
|
2533
|
+
`[workspace] ${store.label} session files sync failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2534
|
+
);
|
|
2535
|
+
if (error instanceof WorkspaceFileRefusal) {
|
|
2536
|
+
await this.clearPendingWorkspaceFiles(sessionId, files);
|
|
2537
|
+
}
|
|
2538
|
+
return undefined;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
private async wipeCloudSessionWorkspace(sessionId: string): Promise<void> {
|
|
2543
|
+
const store = this.cloudStore();
|
|
2544
|
+
if (store === undefined) {
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
try {
|
|
2548
|
+
await wipeSessionWorkspace({
|
|
2549
|
+
sink: store.sink,
|
|
2550
|
+
workspacePrefix: store.sessionWorkspacePrefix(sessionId),
|
|
2551
|
+
});
|
|
2552
|
+
} catch (error) {
|
|
2553
|
+
this.logger(
|
|
2554
|
+
`[workspace] ${store.label} session files wipe failed: ${error instanceof Error ? error.message : String(error)}`
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
private async clearPendingWorkspaceFiles(
|
|
2560
|
+
sessionId: string,
|
|
2561
|
+
pending: NonNullable<SessionRecord["pendingWorkspaceFiles"]>
|
|
2562
|
+
): Promise<void> {
|
|
2563
|
+
await this.updateRecord(sessionId, (current) => ({
|
|
2564
|
+
...current,
|
|
2565
|
+
pendingWorkspaceFiles:
|
|
2566
|
+
current.pendingWorkspaceFiles === pending
|
|
2567
|
+
? undefined
|
|
2568
|
+
: current.pendingWorkspaceFiles,
|
|
2569
|
+
}));
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2498
2572
|
/**
|
|
2499
2573
|
* With files authored, every first cloud turn re-copies the authored
|
|
2500
2574
|
* bytes and the memo only dedupes concurrent first turns. The mount is
|
|
@@ -2547,6 +2621,8 @@ export class SessionEngine {
|
|
|
2547
2621
|
mountPath: DEPLOYMENT_STORE_MOUNT_PATH,
|
|
2548
2622
|
skillsPrefix: agentStoreKeys.skillsPrefix(),
|
|
2549
2623
|
workspacePrefix: agentStoreKeys.workspacePrefix(),
|
|
2624
|
+
sessionWorkspacePrefix: (sessionId: string) =>
|
|
2625
|
+
agentStoreKeys.sessionWorkspacePrefix(sessionId),
|
|
2550
2626
|
label: "deployment store",
|
|
2551
2627
|
};
|
|
2552
2628
|
if (v2Hosting.kind === "cursor-managed") {
|
|
@@ -2569,6 +2645,11 @@ export class SessionEngine {
|
|
|
2569
2645
|
mountPath: USER_STORE_MOUNT_PATH,
|
|
2570
2646
|
skillsPrefix: agentStoreKeys.userSkillsPrefix(this.project.name),
|
|
2571
2647
|
workspacePrefix: agentStoreKeys.userWorkspacePrefix(this.project.name),
|
|
2648
|
+
sessionWorkspacePrefix: (sessionId: string) =>
|
|
2649
|
+
agentStoreKeys.userSessionWorkspacePrefix(
|
|
2650
|
+
this.project.name,
|
|
2651
|
+
sessionId
|
|
2652
|
+
),
|
|
2572
2653
|
label: "user store",
|
|
2573
2654
|
};
|
|
2574
2655
|
}
|
|
@@ -2690,34 +2771,39 @@ export class SessionEngine {
|
|
|
2690
2771
|
});
|
|
2691
2772
|
turnPrompt = `${contextBlock}\n\n${prompt}`;
|
|
2692
2773
|
}
|
|
2693
|
-
if (
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2774
|
+
if (runtime === "cloud") {
|
|
2775
|
+
// Cloud VMs have no materialized workspace: ship the full context,
|
|
2776
|
+
// including agent tool script bodies. Authored skills and workspace
|
|
2777
|
+
// seeds land on the store mount (skills for native discovery, seeds
|
|
2778
|
+
// for the model's file tools), per-turn workspaceFiles land under
|
|
2779
|
+
// the session prefix, and on hosted deployments the shared memory
|
|
2780
|
+
// journal is on that mount too — say where. When the turn carries
|
|
2781
|
+
// the session-bound server-tool MCP endpoint, the preamble catalogs
|
|
2782
|
+
// server tools under that MCP server name *before* the agent-tool
|
|
2783
|
+
// "write the script if missing" recipe, so a model told to "use
|
|
2784
|
+
// <server tool>" calls MCP instead of inventing a .sh.
|
|
2785
|
+
const hosted = isCursorHostedFilesAvailable();
|
|
2786
|
+
const store =
|
|
2787
|
+
hosted || isCursorUserStoreFilesAvailable()
|
|
2788
|
+
? this.cloudStore()
|
|
2789
|
+
: undefined;
|
|
2790
|
+
let preamble = "";
|
|
2791
|
+
const sections: string[] = [];
|
|
2792
|
+
const pending = record.pendingWorkspaceFiles;
|
|
2793
|
+
if (isFirstTurn) {
|
|
2794
|
+
preamble = buildAgentsMdContent(agent, {
|
|
2705
2795
|
includeScripts: true,
|
|
2706
2796
|
serverToolsMcpName:
|
|
2707
2797
|
this.cloudServerToolsConnection === undefined
|
|
2708
2798
|
? undefined
|
|
2709
2799
|
: SERVER_TOOLS_MCP_SERVER_NAME,
|
|
2710
2800
|
});
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
? this.cloudStore()
|
|
2718
|
-
: undefined;
|
|
2719
|
-
let sections = "";
|
|
2720
|
-
if (store !== undefined) {
|
|
2801
|
+
}
|
|
2802
|
+
if (store !== undefined) {
|
|
2803
|
+
if (isFirstTurn) {
|
|
2804
|
+
// Synced whenever a store is reachable, not just when skills or
|
|
2805
|
+
// seeds exist: removing the last one has to delete its store copies
|
|
2806
|
+
// too.
|
|
2721
2807
|
const [, seedsSynced] = await Promise.all([
|
|
2722
2808
|
this.ensureCloudSkillsSynced(store),
|
|
2723
2809
|
this.ensureCloudWorkspaceSynced(store),
|
|
@@ -2725,37 +2811,63 @@ export class SessionEngine {
|
|
|
2725
2811
|
// Only name the mount when the bytes landed; a failed sync must
|
|
2726
2812
|
// not send the model looking for files that are not there.
|
|
2727
2813
|
if (seedsSynced && agent.seedFiles.length > 0) {
|
|
2728
|
-
|
|
2729
|
-
|
|
2814
|
+
sections.push(
|
|
2815
|
+
buildCloudWorkspaceSection(
|
|
2816
|
+
`${store.mountPath}/${store.workspacePrefix}`
|
|
2817
|
+
)
|
|
2818
|
+
);
|
|
2730
2819
|
}
|
|
2731
2820
|
}
|
|
2732
|
-
if (
|
|
2733
|
-
|
|
2821
|
+
if (pending !== undefined && Object.keys(pending).length > 0) {
|
|
2822
|
+
const synced = await this.syncCloudSessionWorkspaceFiles(
|
|
2823
|
+
store,
|
|
2824
|
+
record.sessionId,
|
|
2825
|
+
pending
|
|
2826
|
+
);
|
|
2827
|
+
if (synced !== undefined) {
|
|
2828
|
+
sections.push(
|
|
2829
|
+
buildCloudSessionWorkspaceSection(synced.dir, synced.relPaths)
|
|
2830
|
+
);
|
|
2831
|
+
await this.clearPendingWorkspaceFiles(record.sessionId, pending);
|
|
2832
|
+
}
|
|
2734
2833
|
}
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
{ ...agent, tools: [...agent.tools, ...synthesized] },
|
|
2741
|
-
{ includeScripts: true }
|
|
2742
|
-
);
|
|
2743
|
-
const workspaceInfo = `<user_info>\nWorkspace Path: /\n</user_info>`;
|
|
2744
|
-
turnPrompt =
|
|
2745
|
-
preamble === ""
|
|
2746
|
-
? `${workspaceInfo}\n\n${turnPrompt}`
|
|
2747
|
-
: `${workspaceInfo}\n\n${preamble}\n\n${turnPrompt}`;
|
|
2748
|
-
} else if (runtime === "local" && instructionsInvisibleToHarness) {
|
|
2749
|
-
// Local workspace whose AGENTS.md the harness will not load. With a
|
|
2750
|
-
// borrowed harness cwd, agent tool scripts live only in the durable
|
|
2751
|
-
// scaffold dir — embed bodies like cloud. Otherwise the catalog can
|
|
2752
|
-
// reference on-disk paths under the harness cwd. Grokbot turns are
|
|
2753
|
-
// excluded: their instructions ride the request's `instructions`
|
|
2754
|
-
// field into the Sand system prompt instead.
|
|
2755
|
-
const includeScripts = this.hasBorrowedWorkspace(record);
|
|
2756
|
-
const preamble = buildAgentsMdContent(agent, { includeScripts });
|
|
2757
|
-
turnPrompt = `${preamble}\n\n${turnPrompt}`;
|
|
2834
|
+
} else if (pending !== undefined && Object.keys(pending).length > 0) {
|
|
2835
|
+
await this.clearPendingWorkspaceFiles(record.sessionId, pending);
|
|
2836
|
+
}
|
|
2837
|
+
if (isFirstTurn && hosted) {
|
|
2838
|
+
sections.push(buildCloudMemorySection(this.project.name));
|
|
2758
2839
|
}
|
|
2840
|
+
if (preamble !== "" || sections.length > 0) {
|
|
2841
|
+
turnPrompt = [preamble, ...sections, turnPrompt]
|
|
2842
|
+
.filter((part) => part !== "")
|
|
2843
|
+
.join("\n\n");
|
|
2844
|
+
}
|
|
2845
|
+
} else if (isFirstTurn && runtime === "local" && authoredRoot) {
|
|
2846
|
+
// Virtual roots skip materialize, so AGENTS.md never lands on disk.
|
|
2847
|
+
// Ship the same preamble the borrowed-cwd local path uses.
|
|
2848
|
+
const preamble = buildAgentsMdContent(
|
|
2849
|
+
{ ...agent, tools: [...agent.tools, ...synthesized] },
|
|
2850
|
+
{ includeScripts: true }
|
|
2851
|
+
);
|
|
2852
|
+
const workspaceInfo = `<user_info>\nWorkspace Path: /\n</user_info>`;
|
|
2853
|
+
turnPrompt =
|
|
2854
|
+
preamble === ""
|
|
2855
|
+
? `${workspaceInfo}\n\n${turnPrompt}`
|
|
2856
|
+
: `${workspaceInfo}\n\n${preamble}\n\n${turnPrompt}`;
|
|
2857
|
+
} else if (
|
|
2858
|
+
isFirstTurn &&
|
|
2859
|
+
runtime === "local" &&
|
|
2860
|
+
instructionsInvisibleToHarness
|
|
2861
|
+
) {
|
|
2862
|
+
// Local workspace whose AGENTS.md the harness will not load. With a
|
|
2863
|
+
// borrowed harness cwd, agent tool scripts live only in the durable
|
|
2864
|
+
// scaffold dir — embed bodies like cloud. Otherwise the catalog can
|
|
2865
|
+
// reference on-disk paths under the harness cwd. Grokbot turns are
|
|
2866
|
+
// excluded: their instructions ride the request's `instructions`
|
|
2867
|
+
// field into the Sand system prompt instead.
|
|
2868
|
+
const includeScripts = this.hasBorrowedWorkspace(record);
|
|
2869
|
+
const preamble = buildAgentsMdContent(agent, { includeScripts });
|
|
2870
|
+
turnPrompt = `${preamble}\n\n${turnPrompt}`;
|
|
2759
2871
|
}
|
|
2760
2872
|
return {
|
|
2761
2873
|
sessionId: record.sessionId,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Mirror an authored file tree onto an Agent Store prefix. Shared by the
|
|
3
|
-
* skills
|
|
4
|
-
* discipline and the same path refusals.
|
|
3
|
+
* skills, workspace seed, and session workspaceFile syncs so they keep the
|
|
4
|
+
* same write-then-sweep discipline and the same path refusals.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { posix } from "node:path";
|