@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.
- package/AGENTS.md +1 -1
- package/dist/bin/agent-serve.js +6 -7
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +16 -0
- package/dist/channels/origin/origin-channel.d.ts +2 -0
- package/dist/channels/origin/origin-channel.d.ts.map +1 -1
- package/dist/channels/origin/origin-channel.js +37 -4
- package/dist/channels/slack/defaults.d.ts.map +1 -1
- package/dist/channels/slack/defaults.js +76 -55
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +1 -1
- package/dist/channels/slack/live-delivery.d.ts +1 -1
- package/dist/channels/slack/live-delivery.d.ts.map +1 -1
- package/dist/channels/slack/live-delivery.js +4 -2
- package/dist/channels/slack/progress-delivery.d.ts +28 -0
- package/dist/channels/slack/progress-delivery.d.ts.map +1 -0
- package/dist/channels/slack/progress-delivery.js +115 -0
- package/dist/channels/slack/types.d.ts +9 -1
- package/dist/channels/slack/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.B3rWNYE1.js → app.BplEAZ3S.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DpuI4i7Z.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.C1lhqJJR.js → VPLocalSearchBox.CHXyv4wP.js} +1 -1
- package/dist/docs/assets/chunks/{theme.B40_SXuv.js → theme.BTcsZmjQ.js} +2 -2
- package/dist/docs/assets/{reference_cli.md.RyZf5OTE.js → reference_cli.md.DfoeyvL0.js} +4 -4
- package/dist/docs/assets/{reference_cli.md.RyZf5OTE.lean.js → reference_cli.md.DfoeyvL0.lean.js} +1 -1
- package/dist/docs/assets/{reference_http-api.md.5zOAbV86.js → reference_http-api.md.BEJx9XVj.js} +1 -1
- package/dist/docs/assets/{reference_tools.md.B84gw9Ii.js → reference_tools.md.CvAHsdSp.js} +10 -2
- package/dist/docs/assets/{reference_tools.md.B84gw9Ii.lean.js → reference_tools.md.CvAHsdSp.lean.js} +1 -1
- 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/design/runtime-abstraction.md +1757 -0
- package/dist/docs/evals.html +4 -4
- package/dist/docs/guides/agent-to-agent.html +4 -4
- package/dist/docs/guides/cloud-runtime.html +4 -4
- package/dist/docs/guides/convert-automation.html +4 -4
- package/dist/docs/guides/github.html +4 -4
- 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 +4 -4
- 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 +4 -4
- package/dist/docs/llms-full.txt +1789 -7
- package/dist/docs/llms.txt +1 -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 -7
- package/dist/docs/reference/cli.md +1 -7
- package/dist/docs/reference/connections.html +4 -4
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +6 -6
- package/dist/docs/reference/http-api.md +1 -0
- 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/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 +14 -6
- package/dist/docs/reference/tools.md +25 -0
- package/dist/docs/scaffolding-agents.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 +4 -4
- package/dist/docs/templates/demo.html +4 -4
- 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-backends/cursor-hosted.d.ts +3 -2
- package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/files-backends/cursor-hosted.js +3 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/internal/as-of.d.ts +19 -0
- package/dist/internal/as-of.d.ts.map +1 -0
- package/dist/internal/as-of.js +82 -0
- package/dist/internal/cli-deploy.d.ts +2 -1
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +100 -5
- package/dist/internal/cursor-event-relay.d.ts.map +1 -1
- package/dist/internal/cursor-event-relay.js +5 -6
- package/dist/internal/framework-file-storage.d.ts +10 -16
- package/dist/internal/framework-file-storage.d.ts.map +1 -1
- package/dist/internal/framework-file-storage.js +11 -40
- package/dist/internal/framework-storage-selection.d.ts +13 -11
- package/dist/internal/framework-storage-selection.d.ts.map +1 -1
- package/dist/internal/framework-storage-selection.js +37 -26
- package/dist/internal/hosted-admission-adapter.d.ts +28 -0
- package/dist/internal/hosted-admission-adapter.d.ts.map +1 -0
- package/dist/internal/hosted-admission-adapter.js +7 -0
- package/dist/internal/hosted-delivery-protocol.d.ts +6 -1
- package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
- package/dist/internal/hosted-delivery-protocol.js +1 -1
- package/dist/internal/hosted-delivery.d.ts +15 -1
- package/dist/internal/hosted-delivery.d.ts.map +1 -1
- package/dist/internal/hosted-delivery.js +47 -21
- package/dist/internal/hosted-execution-diag.d.ts +11 -0
- package/dist/internal/hosted-execution-diag.d.ts.map +1 -1
- package/dist/internal/hosted-execution-diag.js +39 -2
- package/dist/internal/http-channel.d.ts.map +1 -1
- package/dist/internal/http-channel.js +4 -1
- package/dist/internal/null-kv-storage.d.ts +48 -0
- package/dist/internal/null-kv-storage.d.ts.map +1 -0
- package/dist/internal/null-kv-storage.js +58 -0
- package/dist/internal/server.d.ts +3 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +82 -19
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +24 -4
- package/dist/internal/session-run-log.d.ts +120 -0
- package/dist/internal/session-run-log.d.ts.map +1 -0
- package/dist/internal/session-run-log.js +359 -0
- package/dist/internal/storage-coordinator.d.ts +6 -0
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +9 -1
- package/dist/internal/tool-policy.d.ts +20 -8
- package/dist/internal/tool-policy.d.ts.map +1 -1
- package/dist/internal/tool-policy.js +11 -0
- package/dist/playground/assets/index-D7rGZNjy.css +1 -0
- package/dist/playground/assets/index-jzNbJENa.js +69 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-protocol.d.ts +8 -0
- package/dist/storage-protocol.d.ts.map +1 -1
- package/dist/storage-protocol.js +8 -0
- package/dist/tools.d.ts +12 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/design/runtime-abstraction.md +1757 -0
- package/docs/reference/cli.md +1 -7
- package/docs/reference/http-api.md +1 -0
- package/docs/reference/tools.md +25 -0
- package/package.json +1 -1
- package/src/bin/agent-serve.ts +6 -7
- package/src/channels/github/github-channel.ts +17 -0
- package/src/channels/origin/origin-channel.ts +74 -10
- package/src/channels/slack/defaults.ts +91 -63
- package/src/channels/slack/dispatch.ts +2 -0
- package/src/channels/slack/live-delivery.ts +5 -3
- package/src/channels/slack/progress-delivery.ts +133 -0
- package/src/channels/slack/types.ts +9 -1
- package/src/files-backends/cursor-hosted.ts +3 -2
- package/src/index.ts +4 -0
- package/src/internal/as-of.ts +107 -0
- package/src/internal/cli-deploy.ts +128 -8
- package/src/internal/cursor-event-relay.ts +5 -6
- package/src/internal/framework-file-storage.ts +11 -45
- package/src/internal/framework-storage-selection.ts +41 -25
- package/src/internal/hosted-admission-adapter.ts +42 -0
- package/src/internal/hosted-delivery-protocol.ts +7 -2
- package/src/internal/hosted-delivery.ts +88 -36
- package/src/internal/hosted-execution-diag.ts +45 -0
- package/src/internal/http-channel.ts +8 -0
- package/src/internal/null-kv-storage.ts +74 -0
- package/src/internal/server.ts +100 -2
- package/src/internal/session-engine.ts +32 -1
- package/src/internal/session-run-log.ts +584 -0
- package/src/internal/storage-coordinator.ts +11 -1
- package/src/internal/tool-policy.ts +31 -9
- package/src/storage-protocol.ts +10 -0
- package/src/tools.ts +12 -2
- package/src/types.ts +25 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.BU9nHdnQ.js +0 -1
- package/dist/playground/assets/index-DrkI6y5O.js +0 -88
- package/dist/playground/assets/index-DzNGwm7q.css +0 -1
- /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
|
|
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,
|
|
80
|
-
* conversation mirror — and is never
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
2011
|
-
|
|
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
|
-
//
|
|
620
|
-
//
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
|
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` / `
|
|
19
|
-
* file store (file-shaped; the store accepts values
|
|
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
|
|
169
|
-
*
|
|
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
|
-
}
|