@cursor/july 0.1.97 → 0.1.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/AGENTS.md +1 -1
  2. package/dist/bin/agent-serve.js +6 -7
  3. package/dist/channels/github/github-channel.d.ts.map +1 -1
  4. package/dist/channels/github/github-channel.js +16 -0
  5. package/dist/channels/origin/origin-channel.d.ts +2 -0
  6. package/dist/channels/origin/origin-channel.d.ts.map +1 -1
  7. package/dist/channels/origin/origin-channel.js +37 -4
  8. package/dist/channels/slack/defaults.d.ts.map +1 -1
  9. package/dist/channels/slack/defaults.js +76 -55
  10. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  11. package/dist/channels/slack/dispatch.js +1 -1
  12. package/dist/channels/slack/live-delivery.d.ts +1 -1
  13. package/dist/channels/slack/live-delivery.d.ts.map +1 -1
  14. package/dist/channels/slack/live-delivery.js +4 -2
  15. package/dist/channels/slack/progress-delivery.d.ts +28 -0
  16. package/dist/channels/slack/progress-delivery.d.ts.map +1 -0
  17. package/dist/channels/slack/progress-delivery.js +115 -0
  18. package/dist/channels/slack/types.d.ts +9 -1
  19. package/dist/channels/slack/types.d.ts.map +1 -1
  20. package/dist/docs/404.html +2 -2
  21. package/dist/docs/ab.html +4 -4
  22. package/dist/docs/assets/{app.B3rWNYE1.js → app.BplEAZ3S.js} +1 -1
  23. package/dist/docs/assets/chunks/@localSearchIndexroot.DpuI4i7Z.js +1 -0
  24. package/dist/docs/assets/chunks/{VPLocalSearchBox.C1lhqJJR.js → VPLocalSearchBox.CHXyv4wP.js} +1 -1
  25. package/dist/docs/assets/chunks/{theme.B40_SXuv.js → theme.BTcsZmjQ.js} +2 -2
  26. package/dist/docs/assets/{reference_cli.md.RyZf5OTE.js → reference_cli.md.DfoeyvL0.js} +4 -4
  27. package/dist/docs/assets/{reference_cli.md.RyZf5OTE.lean.js → reference_cli.md.DfoeyvL0.lean.js} +1 -1
  28. package/dist/docs/assets/{reference_http-api.md.5zOAbV86.js → reference_http-api.md.BEJx9XVj.js} +1 -1
  29. package/dist/docs/assets/{reference_tools.md.B84gw9Ii.js → reference_tools.md.CvAHsdSp.js} +10 -2
  30. package/dist/docs/assets/{reference_tools.md.B84gw9Ii.lean.js → reference_tools.md.CvAHsdSp.lean.js} +1 -1
  31. package/dist/docs/building-with-agents.html +4 -4
  32. package/dist/docs/concepts.html +4 -4
  33. package/dist/docs/deployment.html +4 -4
  34. package/dist/docs/design/runtime-abstraction.md +1757 -0
  35. package/dist/docs/evals.html +4 -4
  36. package/dist/docs/guides/agent-to-agent.html +4 -4
  37. package/dist/docs/guides/cloud-runtime.html +4 -4
  38. package/dist/docs/guides/convert-automation.html +4 -4
  39. package/dist/docs/guides/github.html +4 -4
  40. package/dist/docs/guides/human-in-the-loop.html +4 -4
  41. package/dist/docs/guides/mcp-oauth.html +4 -4
  42. package/dist/docs/guides/opentelemetry.html +4 -4
  43. package/dist/docs/guides/slack.html +4 -4
  44. package/dist/docs/guides/webhooks.html +4 -4
  45. package/dist/docs/hashmap.json +1 -1
  46. package/dist/docs/hillclimbing.html +4 -4
  47. package/dist/docs/index.html +4 -4
  48. package/dist/docs/llms-full.txt +1789 -7
  49. package/dist/docs/llms.txt +1 -0
  50. package/dist/docs/quickstart.html +4 -4
  51. package/dist/docs/reference/agent-config.html +4 -4
  52. package/dist/docs/reference/artifacts.html +4 -4
  53. package/dist/docs/reference/channels.html +4 -4
  54. package/dist/docs/reference/cli.html +7 -7
  55. package/dist/docs/reference/cli.md +1 -7
  56. package/dist/docs/reference/connections.html +4 -4
  57. package/dist/docs/reference/hooks.html +4 -4
  58. package/dist/docs/reference/http-api.html +6 -6
  59. package/dist/docs/reference/http-api.md +1 -0
  60. package/dist/docs/reference/instructions.html +4 -4
  61. package/dist/docs/reference/playground.html +4 -4
  62. package/dist/docs/reference/project-layout.html +4 -4
  63. package/dist/docs/reference/prompt.html +4 -4
  64. package/dist/docs/reference/schedules.html +4 -4
  65. package/dist/docs/reference/sessions.html +4 -4
  66. package/dist/docs/reference/skills.html +4 -4
  67. package/dist/docs/reference/subagents.html +4 -4
  68. package/dist/docs/reference/tools.html +14 -6
  69. package/dist/docs/reference/tools.md +25 -0
  70. package/dist/docs/scaffolding-agents.html +4 -4
  71. package/dist/docs/storage.html +4 -4
  72. package/dist/docs/templates/agentic-owners.html +4 -4
  73. package/dist/docs/templates/agents-md.html +4 -4
  74. package/dist/docs/templates/code-wiki.html +4 -4
  75. package/dist/docs/templates/demo.html +4 -4
  76. package/dist/docs/templates/pr-autofixer.html +4 -4
  77. package/dist/docs/templates/security-help.html +4 -4
  78. package/dist/docs/templates/security-reviewer.html +4 -4
  79. package/dist/docs/templates/triage.html +4 -4
  80. package/dist/docs/troubleshooting.html +4 -4
  81. package/dist/files-backends/cursor-hosted.d.ts +3 -2
  82. package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
  83. package/dist/files-backends/cursor-hosted.js +3 -2
  84. package/dist/index.d.ts +1 -0
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +1 -0
  87. package/dist/internal/as-of.d.ts +19 -0
  88. package/dist/internal/as-of.d.ts.map +1 -0
  89. package/dist/internal/as-of.js +82 -0
  90. package/dist/internal/cli-deploy.d.ts +2 -1
  91. package/dist/internal/cli-deploy.d.ts.map +1 -1
  92. package/dist/internal/cli-deploy.js +100 -5
  93. package/dist/internal/cursor-event-relay.d.ts.map +1 -1
  94. package/dist/internal/cursor-event-relay.js +5 -6
  95. package/dist/internal/framework-file-storage.d.ts +10 -16
  96. package/dist/internal/framework-file-storage.d.ts.map +1 -1
  97. package/dist/internal/framework-file-storage.js +11 -40
  98. package/dist/internal/framework-storage-selection.d.ts +13 -11
  99. package/dist/internal/framework-storage-selection.d.ts.map +1 -1
  100. package/dist/internal/framework-storage-selection.js +37 -26
  101. package/dist/internal/hosted-admission-adapter.d.ts +28 -0
  102. package/dist/internal/hosted-admission-adapter.d.ts.map +1 -0
  103. package/dist/internal/hosted-admission-adapter.js +7 -0
  104. package/dist/internal/hosted-delivery-protocol.d.ts +6 -1
  105. package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
  106. package/dist/internal/hosted-delivery-protocol.js +1 -1
  107. package/dist/internal/hosted-delivery.d.ts +15 -1
  108. package/dist/internal/hosted-delivery.d.ts.map +1 -1
  109. package/dist/internal/hosted-delivery.js +47 -21
  110. package/dist/internal/hosted-execution-diag.d.ts +11 -0
  111. package/dist/internal/hosted-execution-diag.d.ts.map +1 -1
  112. package/dist/internal/hosted-execution-diag.js +39 -2
  113. package/dist/internal/http-channel.d.ts.map +1 -1
  114. package/dist/internal/http-channel.js +4 -1
  115. package/dist/internal/null-kv-storage.d.ts +48 -0
  116. package/dist/internal/null-kv-storage.d.ts.map +1 -0
  117. package/dist/internal/null-kv-storage.js +58 -0
  118. package/dist/internal/server.d.ts +3 -0
  119. package/dist/internal/server.d.ts.map +1 -1
  120. package/dist/internal/server.js +82 -19
  121. package/dist/internal/session-engine.d.ts.map +1 -1
  122. package/dist/internal/session-engine.js +24 -4
  123. package/dist/internal/session-run-log.d.ts +120 -0
  124. package/dist/internal/session-run-log.d.ts.map +1 -0
  125. package/dist/internal/session-run-log.js +359 -0
  126. package/dist/internal/storage-coordinator.d.ts +6 -0
  127. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  128. package/dist/internal/storage-coordinator.js +9 -1
  129. package/dist/internal/tool-policy.d.ts +20 -8
  130. package/dist/internal/tool-policy.d.ts.map +1 -1
  131. package/dist/internal/tool-policy.js +11 -0
  132. package/dist/playground/assets/index-D7rGZNjy.css +1 -0
  133. package/dist/playground/assets/index-jzNbJENa.js +69 -0
  134. package/dist/playground/index.html +2 -2
  135. package/dist/storage-protocol.d.ts +8 -0
  136. package/dist/storage-protocol.d.ts.map +1 -1
  137. package/dist/storage-protocol.js +8 -0
  138. package/dist/tools.d.ts +12 -2
  139. package/dist/tools.d.ts.map +1 -1
  140. package/dist/types.d.ts +25 -0
  141. package/dist/types.d.ts.map +1 -1
  142. package/docs/design/runtime-abstraction.md +1757 -0
  143. package/docs/reference/cli.md +1 -7
  144. package/docs/reference/http-api.md +1 -0
  145. package/docs/reference/tools.md +25 -0
  146. package/package.json +1 -1
  147. package/src/bin/agent-serve.ts +6 -7
  148. package/src/channels/github/github-channel.ts +17 -0
  149. package/src/channels/origin/origin-channel.ts +74 -10
  150. package/src/channels/slack/defaults.ts +91 -63
  151. package/src/channels/slack/dispatch.ts +2 -0
  152. package/src/channels/slack/live-delivery.ts +5 -3
  153. package/src/channels/slack/progress-delivery.ts +133 -0
  154. package/src/channels/slack/types.ts +9 -1
  155. package/src/files-backends/cursor-hosted.ts +3 -2
  156. package/src/index.ts +4 -0
  157. package/src/internal/as-of.ts +107 -0
  158. package/src/internal/cli-deploy.ts +128 -8
  159. package/src/internal/cursor-event-relay.ts +5 -6
  160. package/src/internal/framework-file-storage.ts +11 -45
  161. package/src/internal/framework-storage-selection.ts +41 -25
  162. package/src/internal/hosted-admission-adapter.ts +42 -0
  163. package/src/internal/hosted-delivery-protocol.ts +7 -2
  164. package/src/internal/hosted-delivery.ts +88 -36
  165. package/src/internal/hosted-execution-diag.ts +45 -0
  166. package/src/internal/http-channel.ts +8 -0
  167. package/src/internal/null-kv-storage.ts +74 -0
  168. package/src/internal/server.ts +100 -2
  169. package/src/internal/session-engine.ts +32 -1
  170. package/src/internal/session-run-log.ts +584 -0
  171. package/src/internal/storage-coordinator.ts +11 -1
  172. package/src/internal/tool-policy.ts +31 -9
  173. package/src/storage-protocol.ts +10 -0
  174. package/src/tools.ts +12 -2
  175. package/src/types.ts +25 -0
  176. package/dist/docs/assets/chunks/@localSearchIndexroot.BU9nHdnQ.js +0 -1
  177. package/dist/playground/assets/index-DrkI6y5O.js +0 -88
  178. package/dist/playground/assets/index-DzNGwm7q.css +0 -1
  179. /package/dist/docs/assets/{reference_http-api.md.5zOAbV86.lean.js → reference_http-api.md.BEJx9XVj.lean.js} +0 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Mid-turn thread progress when `chat.startStream` is unavailable.
3
+ * One editable message; `setThreadStatus` stays on the caller.
4
+ */
5
+
6
+ import type { ChannelEventContext } from "../../types.js";
7
+ import { deleteMessage, postMessage, updateMessage } from "./api.js";
8
+ import type { SlackLog } from "./log.js";
9
+ import type { SlackBotToken, SlackChannelState } from "./types.js";
10
+
11
+ type SlackChannel = ChannelEventContext<SlackChannelState>;
12
+
13
+ export interface ProgressDeliveryDeps {
14
+ botToken: SlackBotToken | undefined;
15
+ getLog: () => SlackLog;
16
+ }
17
+
18
+ function activeProgressTs(state: SlackChannelState): string | null {
19
+ const ts = state.progressTs;
20
+ return ts == null || ts === "" ? null : ts;
21
+ }
22
+
23
+ export interface ProgressDelivery {
24
+ upsert(args: { channel: SlackChannel; text: string }): Promise<void>;
25
+ replace(args: { channel: SlackChannel; text: string }): Promise<boolean>;
26
+ clear(args: { channel: SlackChannel }): Promise<void>;
27
+ }
28
+
29
+ export function createProgressDelivery(
30
+ deps: ProgressDeliveryDeps
31
+ ): ProgressDelivery {
32
+ const { botToken, getLog } = deps;
33
+
34
+ const clear: ProgressDelivery["clear"] = async (args) => {
35
+ const state = args.channel.state;
36
+ const ts = activeProgressTs(state);
37
+ state.progressTs = null;
38
+ if (ts === null || state.channelId === null) {
39
+ return;
40
+ }
41
+ const deleted = await deleteMessage({
42
+ botToken,
43
+ channel: state.channelId,
44
+ ts,
45
+ });
46
+ if (!deleted.ok) {
47
+ getLog().warn("progress delete failed", {
48
+ error: deleted.error,
49
+ channel: state.channelId,
50
+ progress_ts: ts,
51
+ });
52
+ }
53
+ };
54
+
55
+ return {
56
+ async upsert(args) {
57
+ const state = args.channel.state;
58
+ if (
59
+ state.channelId === null ||
60
+ state.threadTs === null ||
61
+ args.text === ""
62
+ ) {
63
+ return;
64
+ }
65
+ const existing = activeProgressTs(state);
66
+ if (existing !== null) {
67
+ const updated = await updateMessage({
68
+ botToken,
69
+ channel: state.channelId,
70
+ ts: existing,
71
+ text: args.text,
72
+ });
73
+ if (updated.ok) {
74
+ return;
75
+ }
76
+ getLog().warn("progress update failed; posting a new message", {
77
+ error: updated.error,
78
+ channel: state.channelId,
79
+ thread_ts: state.threadTs,
80
+ progress_ts: existing,
81
+ });
82
+ await clear({ channel: args.channel });
83
+ }
84
+ try {
85
+ const posted = await postMessage({
86
+ botToken,
87
+ channel: state.channelId,
88
+ threadTs: state.threadTs,
89
+ text: args.text,
90
+ });
91
+ state.progressTs = posted.ts;
92
+ } catch (error) {
93
+ getLog().warn(
94
+ `progress post failed: ${
95
+ error instanceof Error ? error.message : String(error)
96
+ }`,
97
+ {
98
+ channel: state.channelId,
99
+ thread_ts: state.threadTs,
100
+ }
101
+ );
102
+ }
103
+ },
104
+
105
+ async replace(args) {
106
+ const state = args.channel.state;
107
+ const ts = activeProgressTs(state);
108
+ if (ts === null || state.channelId === null || args.text === "") {
109
+ return false;
110
+ }
111
+ const updated = await updateMessage({
112
+ botToken,
113
+ channel: state.channelId,
114
+ ts,
115
+ text: args.text,
116
+ });
117
+ if (!updated.ok) {
118
+ getLog().warn("progress replace failed", {
119
+ error: updated.error,
120
+ channel: state.channelId,
121
+ progress_ts: ts,
122
+ });
123
+ return false;
124
+ }
125
+ state.lastPostedText = args.text;
126
+ state.lastBotMessageTs = ts;
127
+ state.progressTs = null;
128
+ return true;
129
+ },
130
+
131
+ clear,
132
+ };
133
+ }
@@ -61,8 +61,14 @@ export interface SlackChannelState {
61
61
  streamTs: string | null;
62
62
  /** Channel id used when the stream was started. */
63
63
  streamChannelId?: string | null;
64
- /** Last cumulative text sent via appendStream (for incremental append). */
64
+ /** Last assistant text delivered to the thread (stream or postMessage). */
65
65
  lastPostedText?: string | null;
66
+ /**
67
+ * Text of the current assistant block already written into the open stream.
68
+ * The harness restarts its cumulative text at every tool boundary, so the
69
+ * append delta cannot be measured against {@link lastPostedText}.
70
+ */
71
+ streamBlockText?: string | null;
66
72
  /**
67
73
  * Slack message ts of the agent's last delivered reply in this thread.
68
74
  * Warm turns use this as the `<slack_thread_delta>` watermark.
@@ -73,6 +79,8 @@ export interface SlackChannelState {
73
79
  * for the rest of the turn.
74
80
  */
75
81
  streamingUnavailable?: boolean;
82
+ /** Editable progress message ts when `chat.startStream` is unavailable. */
83
+ progressTs: string | null;
76
84
  /**
77
85
  * Message ts of approval cards keyed by tool call id (for
78
86
  * replacing Approve/Deny buttons after a decision).
@@ -76,8 +76,9 @@ export const AGENT_SERVE_STORE_SOURCE_ID_ENV_VAR =
76
76
  * user-level store above. The user store carries author-facing bytes
77
77
  * (`ctx.host.files`, skills, memory, artifacts) and is browsable and
78
78
  * mountable; the framework store carries engine-owned session state —
79
- * session records, event transcripts, continuation index, and the SDK
80
- * conversation mirror — and is never exposed to authors or models.
79
+ * session records, event transcripts, hosted-execution run logs,
80
+ * continuation index, and the SDK conversation mirror — and is never
81
+ * exposed to authors or models.
81
82
  * Provisioned by the v2 deploy pipeline alongside the user store; v1
82
83
  * deployments have only the user store.
83
84
  */
package/src/index.ts CHANGED
@@ -79,6 +79,10 @@ export {
79
79
  V2EngineError,
80
80
  } from "./continuation.js";
81
81
  export { defineHook } from "./hooks.js";
82
+ export {
83
+ InvalidAsOfError,
84
+ VacuousTimeArgsError,
85
+ } from "./internal/as-of.js";
82
86
  export type { LoadAgentProjectOptions } from "./internal/discovery.js";
83
87
  export {
84
88
  formatDiagnostics,
@@ -0,0 +1,107 @@
1
+ const TIMEZONE_SUFFIX = /(?:Z|[+-]\d{2}:?\d{2})$/i;
2
+
3
+ export class InvalidAsOfError extends Error {
4
+ constructor(value: string) {
5
+ super(
6
+ `asOf must be an ISO-8601 instant with a timezone (e.g. ` +
7
+ `2026-08-31T12:00:00Z); received ${JSON.stringify(value)}.`
8
+ );
9
+ this.name = "InvalidAsOfError";
10
+ }
11
+ }
12
+
13
+ function parseInstant(value: string): Date | undefined {
14
+ const trimmed = value.trim();
15
+ if (trimmed === "" || !TIMEZONE_SUFFIX.test(trimmed)) {
16
+ return undefined;
17
+ }
18
+ const instant = new Date(trimmed);
19
+ return Number.isNaN(instant.getTime()) ? undefined : instant;
20
+ }
21
+
22
+ export function normalizeAsOf(value: string | undefined): string | undefined {
23
+ if (value === undefined) {
24
+ return undefined;
25
+ }
26
+ const instant = parseInstant(value);
27
+ if (instant === undefined) {
28
+ throw new InvalidAsOfError(value);
29
+ }
30
+ return instant.toISOString();
31
+ }
32
+
33
+ export function sessionClock(asOf: string | undefined): () => Date {
34
+ if (asOf === undefined) {
35
+ return () => new Date();
36
+ }
37
+ const frozen = new Date(asOf).getTime();
38
+ return () => new Date(frozen);
39
+ }
40
+
41
+ export class VacuousTimeArgsError extends Error {
42
+ constructor(toolName: string, missing: readonly string[]) {
43
+ super(
44
+ `Tool "${toolName}" declares timeArgs ` +
45
+ `${JSON.stringify(missing)} that its input schema does not define, ` +
46
+ `so bounding would silently check nothing. Make the declaration ` +
47
+ `match the schema's property names.`
48
+ );
49
+ this.name = "VacuousTimeArgsError";
50
+ }
51
+ }
52
+
53
+ export function assertTimeArgs(
54
+ tools: readonly {
55
+ name: string;
56
+ definition: { timeArgs?: readonly string[] };
57
+ inputSchema?: { properties?: Record<string, unknown> };
58
+ }[],
59
+ asOf: string | undefined
60
+ ): void {
61
+ if (asOf === undefined) {
62
+ return;
63
+ }
64
+ for (const tool of tools) {
65
+ const declared = tool.definition.timeArgs;
66
+ if (declared === undefined || declared.length === 0) {
67
+ continue;
68
+ }
69
+ const properties = tool.inputSchema?.properties ?? {};
70
+ const missing = declared.filter((name) => !(name in properties));
71
+ if (missing.length > 0) {
72
+ throw new VacuousTimeArgsError(tool.name, missing);
73
+ }
74
+ }
75
+ }
76
+
77
+ export function timeArgViolation(
78
+ input: unknown,
79
+ timeArgs: readonly string[],
80
+ asOf: string
81
+ ): string | undefined {
82
+ const record =
83
+ typeof input === "object" && input !== null
84
+ ? (input as Record<string, unknown>)
85
+ : {};
86
+ const frozen = new Date(asOf).getTime();
87
+ for (const name of timeArgs) {
88
+ const value = record[name];
89
+ const instant = typeof value === "string" ? parseInstant(value) : undefined;
90
+ if (instant === undefined) {
91
+ return (
92
+ `Time argument "${name}" must be an absolute ISO-8601 instant with ` +
93
+ `a timezone (e.g. ${asOf}); received ${JSON.stringify(value)}. This ` +
94
+ `session reads the world as of ${asOf} — supply absolute bounds at ` +
95
+ `or before that instant and retry.`
96
+ );
97
+ }
98
+ if (instant.getTime() > frozen) {
99
+ return (
100
+ `Time argument "${name}" (${JSON.stringify(value)}) is later than ` +
101
+ `the instant this session reads the world as of (${asOf}). Supply ` +
102
+ `bounds at or before that instant and retry.`
103
+ );
104
+ }
105
+ }
106
+ return undefined;
107
+ }
@@ -66,6 +66,8 @@ import { stderrPalette } from "./terminal-style.js";
66
66
 
67
67
  const DEFAULT_POLL_INTERVAL_MS = 3_000;
68
68
  const DEFAULT_DEPLOY_TIMEOUT_MS = 10 * 60 * 1000;
69
+ /** `--multi-tenant` HTTP deploy is Anysphere-only (team id 1). */
70
+ const MULTI_TENANT_HTTP_DEPLOY_TEAM_ID = 1;
69
71
 
70
72
  /** One agent directory selected for deploy (single or multi-agent parent). */
71
73
  export type DeployTarget = ChildAgentDir;
@@ -114,7 +116,8 @@ export interface DeployCliOptions {
114
116
  allowDomains?: string[];
115
117
  /**
116
118
  * deploy `--multi-tenant`: add a release to an existing Cursor-managed
117
- * (multi-tenant) product. Cannot create a new multi-tenant application.
119
+ * (multi-tenant) product. Hidden Anysphere-only flag. Cannot create a
120
+ * new multi-tenant application.
118
121
  */
119
122
  multiTenant?: boolean;
120
123
  /** Test seams. */
@@ -297,6 +300,14 @@ export async function cmdDeploy(options: DeployCliOptions): Promise<number> {
297
300
  return ctx;
298
301
  }
299
302
 
303
+ if (
304
+ options.multiTenant === true &&
305
+ ctx.teamId !== MULTI_TENANT_HTTP_DEPLOY_TEAM_ID
306
+ ) {
307
+ ctx.err("--multi-tenant is not available for this team.\n");
308
+ return 1;
309
+ }
310
+
300
311
  const targets = await resolveDeployTargets(options, ctx);
301
312
  if (typeof targets === "number") {
302
313
  return targets;
@@ -990,7 +1001,10 @@ async function deployOneAgentsdk(
990
1001
  ...(args.multiTenant ? { multiTenant: true } : {}),
991
1002
  });
992
1003
  } catch (error) {
993
- return renderApiError(error, ctx.err);
1004
+ return renderApiError(error, ctx.err, {
1005
+ multiTenant: args.multiTenant,
1006
+ teamId: ctx.teamId,
1007
+ });
994
1008
  }
995
1009
 
996
1010
  if (ctx.noWait) {
@@ -1301,6 +1315,21 @@ export async function cmdDeploymentStatus(
1301
1315
  if (typeof ctx === "number") {
1302
1316
  return ctx;
1303
1317
  }
1318
+ // Same dispatch as `stop`: v1 409s architecture-v2 slugs, so the v2
1319
+ // GET goes first. A v2 404 (missing slug, or an older backend) falls
1320
+ // through to the v1 agent-serve detail.
1321
+ try {
1322
+ const detail = await ctx.client.getAgentkitDeploy({
1323
+ teamId: ctx.teamId,
1324
+ slug,
1325
+ });
1326
+ renderAgentkitDeploymentStatus(ctx, detail);
1327
+ return 0;
1328
+ } catch (error) {
1329
+ if (!(error instanceof DeployApiError) || error.status !== 404) {
1330
+ return renderApiError(error, ctx.err);
1331
+ }
1332
+ }
1304
1333
  let detail: DeploymentDetail;
1305
1334
  try {
1306
1335
  detail = await ctx.client.getDeployment({ teamId: ctx.teamId, slug });
@@ -1393,6 +1422,40 @@ export async function cmdDeploymentStatus(
1393
1422
  return 0;
1394
1423
  }
1395
1424
 
1425
+ function renderAgentkitDeploymentStatus(
1426
+ ctx: DeployCliContext,
1427
+ detail: AgentkitDeployStatus
1428
+ ): void {
1429
+ if (ctx.json) {
1430
+ ctx.out(`${JSON.stringify(detail, null, 2)}\n`);
1431
+ return;
1432
+ }
1433
+ const lines = [
1434
+ `${detail.slug} (team ${detail.teamId})`,
1435
+ ` status: ${detail.status}`,
1436
+ ` architecture: v2`,
1437
+ ` application: ${detail.applicationId}`,
1438
+ ` release: ${detail.releaseId}`,
1439
+ ` progress: ${detail.progress}`,
1440
+ ];
1441
+ if (detail.statusMessage !== null && detail.statusMessage.trim() !== "") {
1442
+ lines.push(` step: ${detail.statusMessage}`);
1443
+ }
1444
+ if (detail.repositories !== undefined) {
1445
+ lines.push(
1446
+ ` watching: ${
1447
+ detail.repositories.length === 0
1448
+ ? "(nothing)"
1449
+ : detail.repositories.join(", ")
1450
+ }`
1451
+ );
1452
+ }
1453
+ if (detail.lastError !== null && detail.lastError !== undefined) {
1454
+ lines.push(` last error: ${sanitizeCustomerError(detail.lastError)}`);
1455
+ }
1456
+ ctx.out(`${lines.join("\n")}\n`);
1457
+ }
1458
+
1396
1459
  // ============================================================================
1397
1460
  // stop <slug>
1398
1461
  // ============================================================================
@@ -1901,10 +1964,35 @@ async function pollUntil<
1901
1964
  };
1902
1965
 
1903
1966
  for (;;) {
1904
- const detail = await fetchDetail().catch((error: unknown) => {
1967
+ let detail: T;
1968
+ try {
1969
+ detail = await fetchDetail();
1970
+ } catch (error: unknown) {
1971
+ // Capacity / rate-limit on GET is not a bake failure. The v2 accept
1972
+ // already started freeze; keep polling until the wait budget expires.
1973
+ if (
1974
+ isTransientDeployStatusError(error) &&
1975
+ Date.now() - startedAt < ctx.timeoutMs
1976
+ ) {
1977
+ if (!ctx.json) {
1978
+ const elapsed = Math.round((Date.now() - startedAt) / 1000);
1979
+ const label = "Waiting for status (server busy)";
1980
+ if (tty) {
1981
+ ctx.err(`\r\x1b[2K${slug}: ${label} (${elapsed}s)`);
1982
+ sawProgress = true;
1983
+ } else if (label !== lastLabel) {
1984
+ ctx.err(`${slug}: ${label}\n`);
1985
+ }
1986
+ lastLabel = label;
1987
+ }
1988
+ const waitMs = transientDeployStatusWaitMs(error, ctx.pollIntervalMs);
1989
+ const remaining = ctx.timeoutMs - (Date.now() - startedAt);
1990
+ await sleep(Math.max(0, Math.min(waitMs, remaining)));
1991
+ continue;
1992
+ }
1905
1993
  finishProgressLine();
1906
1994
  throw error;
1907
- });
1995
+ }
1908
1996
  report(detail);
1909
1997
  if (terminal.has(detail.status)) {
1910
1998
  finishProgressLine();
@@ -1922,6 +2010,28 @@ async function pollUntil<
1922
2010
  }
1923
2011
  }
1924
2012
 
2013
+ /** Load-shed / rate-limit on status GET — the deploy itself may still be baking. */
2014
+ function isTransientDeployStatusError(error: unknown): error is DeployApiError {
2015
+ return (
2016
+ error instanceof DeployApiError &&
2017
+ (error.status === 503 || error.status === 429)
2018
+ );
2019
+ }
2020
+
2021
+ function transientDeployStatusWaitMs(
2022
+ error: DeployApiError,
2023
+ pollIntervalMs: number
2024
+ ): number {
2025
+ if (
2026
+ error.status === 429 &&
2027
+ error.retryAfterSeconds !== undefined &&
2028
+ error.retryAfterSeconds > 0
2029
+ ) {
2030
+ return Math.max(pollIntervalMs, error.retryAfterSeconds * 1000);
2031
+ }
2032
+ return pollIntervalMs;
2033
+ }
2034
+
1925
2035
  /**
1926
2036
  * Wait until GET 404s (the delete reconciler purged the record). Treats
1927
2037
  * other errors as failures. Throws DeployApiError on timeout.
@@ -2000,16 +2110,26 @@ function deployFailureDetail(lastError: string | null | undefined): string {
2000
2110
  return `: ${sanitizeCustomerError(lastError.trim())}`;
2001
2111
  }
2002
2112
 
2003
- function renderApiError(error: unknown, err: (text: string) => void): number {
2113
+ function renderApiError(
2114
+ error: unknown,
2115
+ err: (text: string) => void,
2116
+ context?: { multiTenant?: boolean; teamId?: number }
2117
+ ): number {
2004
2118
  if (error instanceof DeployApiError) {
2005
2119
  err(`${error.message}\n`);
2006
2120
  if (
2007
2121
  error.status === 409 &&
2008
2122
  error.message.includes("application multiTenant does not match")
2009
2123
  ) {
2010
- err(
2011
- "This slug is a Cursor-managed (multi-tenant) product. Redeploy it with --multi-tenant.\n"
2012
- );
2124
+ if (context?.multiTenant === true) {
2125
+ err(
2126
+ "This slug is a single-tenant application. Redeploy it without --multi-tenant.\n"
2127
+ );
2128
+ } else if (context?.teamId === MULTI_TENANT_HTTP_DEPLOY_TEAM_ID) {
2129
+ err(
2130
+ "This slug is a Cursor-managed (multi-tenant) product. Redeploy it with --multi-tenant.\n"
2131
+ );
2132
+ }
2013
2133
  }
2014
2134
  return 1;
2015
2135
  }
@@ -616,12 +616,11 @@ export class CursorEventRelay {
616
616
  events: [...subscription.events],
617
617
  })),
618
618
  }),
619
- // Empty subscriptions drop this consumer's registration on clean stop
620
- // instead of leaving it to lapse by TTL.
621
- buildDeregistrationBody: (consumerId) => ({
622
- consumerId,
623
- subscriptions: [],
624
- }),
619
+ // No deregistration on stop: the backend only records events for a
620
+ // repo that some consumer is subscribed to, so dropping the
621
+ // registration leaves a hole in the log until the next process
622
+ // registers, and the durable offset cannot replay what was never
623
+ // written. A consumer that does not come back lapses by TTL.
625
624
  applyRegistrationResponse: (json) => {
626
625
  const result = json as { repos?: SubscriptionStatus[] };
627
626
  const statuses = result.repos ?? [];
@@ -8,15 +8,16 @@
8
8
  * is browsable from the dashboard and mountable on cloud-runtime VMs;
9
9
  * - the **framework-level** store (`AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID`)
10
10
  * holds engine-owned session state — session records, event transcripts,
11
- * the continuation index, and the SDK conversation mirror — and is never
12
- * exposed to authors or models.
11
+ * hosted-execution run logs, the continuation index, and the SDK
12
+ * conversation mirror — and is never exposed to authors or models.
13
13
  *
14
14
  * This module puts framework session state on the framework store. The
15
15
  * {@link storageKeys} scheme was designed for exactly this: keys are a
16
16
  * stable versioned contract that sinks may route on, so
17
17
  * {@link composeFrameworkStorage} splits one {@link StorageConfig} by key
18
- * kind — `session` / `session-events` / `continuation` go to the framework
19
- * file store (file-shaped; the store accepts values up to 64 MiB per
18
+ * kind — `session` / `session-events` / `session-runs` / `continuation`
19
+ * go to the framework file store (file-shaped; the store accepts values
20
+ * up to 64 MiB per
20
21
  * object without chunking, versus the KV proxy's 1 MiB row cap), while
21
22
  * author `kv`, eval runs, A/B samples, and conditional writes stay on the
22
23
  * control-plane KV proxy (the dashboard reads them there, and CAS needs a
@@ -40,6 +41,7 @@ import { mapBounded } from "./bounded.js";
40
41
  const FRAMEWORK_FILE_KINDS: ReadonlySet<string> = new Set([
41
42
  "session",
42
43
  "session-events",
44
+ "session-runs",
43
45
  "continuation",
44
46
  ]);
45
47
 
@@ -165,8 +167,11 @@ function decodeDocument(key: string, bytes: Uint8Array): JsonValue {
165
167
  * Split one storage core by key kind: framework session state to `files`,
166
168
  * everything else to `kv`. Spread over the KV backend when building the
167
169
  * full definition so its `evals` / `abs` groups (and the cursor-hosted
168
- * marker) ride along untouched; wrap the KV `cas` group with
169
- * {@link frameworkGuardedCas} rather than spreading it as-is.
170
+ * marker) ride along untouched. A KV `cas` group must not be spread as-is:
171
+ * conditional writes run on a KV database row, so one over a `session/` or
172
+ * `continuation/` key would read and write a different store than the
173
+ * routed plain core — silent split-brain. Nothing in the package CASes
174
+ * those kinds today.
170
175
  */
171
176
  export function composeFrameworkStorage(input: {
172
177
  kv: FileSinkStorageCore;
@@ -187,42 +192,3 @@ export function composeFrameworkStorage(input: {
187
192
  listNewest: undefined,
188
193
  };
189
194
  }
190
-
191
- /**
192
- * The KV backend's conditional writes, refusing framework-file keys.
193
- *
194
- * CAS runs on the KV proxy (a database row version); the plain core routes
195
- * framework session state to the file store. A future coordination path
196
- * that CASes a `session/` or `continuation/` key would otherwise read and
197
- * write a different store than the plain path — silent split-brain. Fail
198
- * loudly instead; nothing in the package CASes those kinds today.
199
- */
200
- export function frameworkGuardedCas(
201
- cas: NonNullable<StorageConfig["cas"]>
202
- ): NonNullable<StorageConfig["cas"]> {
203
- const refuse = (key: string): void => {
204
- if (isFrameworkFileKey(key)) {
205
- throw new Error(
206
- `framework file storage: conditional writes are KV-only, but "${key}" routes to the framework file store`
207
- );
208
- }
209
- };
210
- return {
211
- getWithVersion: (key, ctx) => {
212
- refuse(key);
213
- return cas.getWithVersion(key, ctx);
214
- },
215
- putIfAbsent: (key, value, ctx) => {
216
- refuse(key);
217
- return cas.putIfAbsent(key, value, ctx);
218
- },
219
- putIfVersion: (key, value, expectedVersion, ctx) => {
220
- refuse(key);
221
- return cas.putIfVersion(key, value, expectedVersion, ctx);
222
- },
223
- listKeys: (prefix, ctx) => {
224
- refuse(prefix);
225
- return cas.listKeys(prefix, ctx);
226
- },
227
- };
228
- }