@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
|
@@ -17,17 +17,19 @@
|
|
|
17
17
|
*
|
|
18
18
|
* - **Cursor-managed hosting** (the deployment-generation environment
|
|
19
19
|
* variable is set, same signal that registers `GET /v1/manifest`):
|
|
20
|
-
* the
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
20
|
+
* the deployment's **framework-level file store**
|
|
21
|
+
* (`AGENT_SERVE_FRAMEWORK_STORE_SOURCE_ID`) holds session records, event
|
|
22
|
+
* transcripts, and the continuation index — file-shaped state on file
|
|
23
|
+
* storage, apart from the author-facing user store. The control-plane KV
|
|
24
|
+
* proxy is *not* used: it authorizes by mapping a pod service account to
|
|
25
|
+
* a v1 deployment row, which no v2 deployment has, so every call 403s and
|
|
26
|
+
* fails the delivery that read it. The KV half is inert until the proxy
|
|
27
|
+
* resolves v2 deployments (see null-kv-storage.ts). Without a framework
|
|
28
|
+
* store (a deploy predating framework-store provisioning) there is
|
|
29
|
+
* nothing left to mirror to, so the state root is again the only copy.
|
|
30
|
+
* The API key resolves through the full credential chain on every call
|
|
31
|
+
* because hosted freeze snapshots listen unsigned and the binder drops
|
|
32
|
+
* the key file after restore.
|
|
31
33
|
* - **Self-hosted**: no mirror. `--state-root` is the durable copy, which
|
|
32
34
|
* is correct when the operator owns the disk.
|
|
33
35
|
*
|
|
@@ -45,15 +47,17 @@ import {
|
|
|
45
47
|
cursorHostedFiles,
|
|
46
48
|
} from "../files-backends/cursor-hosted.js";
|
|
47
49
|
import { defineStorage, type StorageDefinition } from "../storage.js";
|
|
48
|
-
import {
|
|
50
|
+
import { CURSOR_HOSTED_STORAGE } from "../storage-backends/cursor-hosted.js";
|
|
49
51
|
import type { AgentArchitecture } from "../types.js";
|
|
50
52
|
import { resolveApiKeySync } from "./cursor/credentials.js";
|
|
51
53
|
import { AGENT_SERVE_GENERATION_ENV_VAR } from "./cursor-relay-core.js";
|
|
52
54
|
import {
|
|
53
55
|
composeFrameworkStorage,
|
|
54
56
|
fileSinkStorageCore,
|
|
55
|
-
frameworkGuardedCas,
|
|
56
57
|
} from "./framework-file-storage.js";
|
|
58
|
+
import { nullKvStorage } from "./null-kv-storage.js";
|
|
59
|
+
|
|
60
|
+
const cursorHostedMarker = { [CURSOR_HOSTED_STORAGE]: true } as const;
|
|
57
61
|
|
|
58
62
|
export interface FrameworkStorageSelectionInput {
|
|
59
63
|
/** `agent/storage.ts`, when the author wrote one (v1 only: discovery rejects it on v2). */
|
|
@@ -96,23 +100,38 @@ export function selectFrameworkStorageDefinition(
|
|
|
96
100
|
// comes up). Follow-ups restore lazily by continuation or session id;
|
|
97
101
|
// GET /v1/sessions lists from the sink either way.
|
|
98
102
|
const policy = { restore: "off" } as const;
|
|
99
|
-
// Full chain (env → key file → stored login): frozen engines listen
|
|
100
|
-
// unsigned and bind CURSOR_API_KEY_FILE at task time, after this
|
|
101
|
-
// definition is constructed.
|
|
102
|
-
const resolveApiKey = (): string | undefined => resolveApiKeySync()?.apiKey;
|
|
103
|
-
const kv = cursorHostedStorage({ resolveApiKey });
|
|
104
103
|
const frameworkStoreId = cursorFrameworkStoreSourceId(
|
|
105
104
|
env as NodeJS.ProcessEnv
|
|
106
105
|
);
|
|
107
106
|
if (frameworkStoreId === undefined) {
|
|
108
|
-
|
|
107
|
+
// Nothing to mirror to: the KV proxy rejects v2 service accounts, so a
|
|
108
|
+
// definition over it would fail every delivery that reads it. The state
|
|
109
|
+
// root is the durable copy, as when self-hosted.
|
|
110
|
+
return undefined;
|
|
109
111
|
}
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
112
|
+
// Full chain (env → key file → stored login): frozen engines listen
|
|
113
|
+
// unsigned and bind CURSOR_API_KEY_FILE at task time, after this
|
|
114
|
+
// definition is constructed.
|
|
115
|
+
const resolveApiKey = (): string | undefined => resolveApiKeySync()?.apiKey;
|
|
116
|
+
// Framework session state on the framework-level file store, which
|
|
117
|
+
// authorizes by store source id and so works for v2. Everything the KV
|
|
118
|
+
// proxy used to serve — author kv, eval runs, A/B samples, conditional
|
|
119
|
+
// writes — is inert; `defineStorage` derives the eval/A/B tables over the
|
|
120
|
+
// core, which means that bookkeeping reads back empty rather than 403ing.
|
|
121
|
+
//
|
|
122
|
+
// Restoring `cursorHostedStorage()` here once the proxy resolves v2
|
|
123
|
+
// deployments also means restoring the guard its `cas` group needs: a
|
|
124
|
+
// conditional write over a `session/` or `continuation/` key would run on
|
|
125
|
+
// the KV database while the plain core routes that key to the file store.
|
|
126
|
+
const kv = nullKvStorage();
|
|
113
127
|
return defineStorage({
|
|
114
128
|
name: "cursor-hosted (framework files)",
|
|
115
129
|
policy,
|
|
130
|
+
// GET /v1/sessions still prefers the durable list: session records live
|
|
131
|
+
// on the framework store, which the composed core lists. Spread rather
|
|
132
|
+
// than named, the way cursorHostedStorage carries it: the marker is a
|
|
133
|
+
// platform stamp, not part of the authored config surface.
|
|
134
|
+
...cursorHostedMarker,
|
|
116
135
|
...kv,
|
|
117
136
|
...composeFrameworkStorage({
|
|
118
137
|
kv,
|
|
@@ -120,8 +139,5 @@ export function selectFrameworkStorageDefinition(
|
|
|
120
139
|
cursorHostedFiles({ storeSourceId: frameworkStoreId, resolveApiKey })
|
|
121
140
|
),
|
|
122
141
|
}),
|
|
123
|
-
// CAS stays on the KV proxy but must refuse framework-file keys, or a
|
|
124
|
-
// future coordination path would split-brain against the plain core.
|
|
125
|
-
cas: frameworkGuardedCas(kv.cas),
|
|
126
142
|
});
|
|
127
143
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ChannelHandlerArgs } from "../types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hosted-admission hook a channel pack attaches to its definition.
|
|
5
|
+
*
|
|
6
|
+
* Hosted delivery looks this up from the mounted definition — it does not
|
|
7
|
+
* switch on `channelKind`. A new forge implements `dispatch` / `getOutcome`
|
|
8
|
+
* next to its webhook handler and calls {@link attachHostedAdmissionAdapter}
|
|
9
|
+
* from the channel constructor.
|
|
10
|
+
*/
|
|
11
|
+
export type HostedAdmissionDispatchOutcome = "skip" | "task";
|
|
12
|
+
|
|
13
|
+
export type HostedAdmissionAdapter = {
|
|
14
|
+
kind: string;
|
|
15
|
+
/**
|
|
16
|
+
* Optional wrap of the capturing `send` / `waitUntil` args (GitHub uses
|
|
17
|
+
* this to propagate background-task errors). Identity when omitted.
|
|
18
|
+
*/
|
|
19
|
+
prepareArgs?(args: ChannelHandlerArgs): ChannelHandlerArgs;
|
|
20
|
+
dispatch(input: {
|
|
21
|
+
eventType: string;
|
|
22
|
+
payload: unknown;
|
|
23
|
+
deliveryId: string;
|
|
24
|
+
args: ChannelHandlerArgs;
|
|
25
|
+
}): Promise<Response>;
|
|
26
|
+
getOutcome(response: Response): HostedAdmissionDispatchOutcome | undefined;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const adapters = new WeakMap<object, HostedAdmissionAdapter>();
|
|
30
|
+
|
|
31
|
+
export function attachHostedAdmissionAdapter(
|
|
32
|
+
definition: object,
|
|
33
|
+
adapter: HostedAdmissionAdapter
|
|
34
|
+
): void {
|
|
35
|
+
adapters.set(definition, adapter);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getHostedAdmissionAdapter(
|
|
39
|
+
definition: object
|
|
40
|
+
): HostedAdmissionAdapter | undefined {
|
|
41
|
+
return adapters.get(definition);
|
|
42
|
+
}
|
|
@@ -18,7 +18,12 @@ export type HostedAdmissionManagedAutomation = {
|
|
|
18
18
|
export type HostedAdmission = {
|
|
19
19
|
v: 2;
|
|
20
20
|
channelId: string;
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Mailbox / subscription channel kind (`github`, `origin`, …). The guest
|
|
23
|
+
* resolves dispatch from the mounted definition's hosted-admission
|
|
24
|
+
* adapter; this field is not an allowlist.
|
|
25
|
+
*/
|
|
26
|
+
channelKind: string;
|
|
22
27
|
eventType: string;
|
|
23
28
|
deliveryId: string;
|
|
24
29
|
sessionMode: "event" | "continuation";
|
|
@@ -107,7 +112,7 @@ export function parseHostedAdmission(
|
|
|
107
112
|
!Object.hasOwn(body, "payload") ||
|
|
108
113
|
body.v !== HOSTED_DELIVERY_PROTOCOL_VERSION ||
|
|
109
114
|
!boundedString(body.channelId, 128) ||
|
|
110
|
-
body.channelKind
|
|
115
|
+
!boundedString(body.channelKind, 128) ||
|
|
111
116
|
!boundedString(body.eventType, 128) ||
|
|
112
117
|
!boundedString(body.deliveryId, 256) ||
|
|
113
118
|
(body.sessionMode !== "event" && body.sessionMode !== "continuation") ||
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
dispatchGitHubWebhook,
|
|
4
|
-
getGitHubChannelOptions,
|
|
5
|
-
getGitHubDispatchOutcome,
|
|
6
|
-
withPropagatingGitHubBackgroundErrors,
|
|
7
|
-
} from "../channels/github/github-channel.js";
|
|
8
2
|
import type {
|
|
9
3
|
AuthPolicy,
|
|
10
4
|
ChannelHandlerArgs,
|
|
@@ -14,6 +8,10 @@ import {
|
|
|
14
8
|
createDeferredChannelSession,
|
|
15
9
|
DeferredChannelSessionError,
|
|
16
10
|
} from "./deferred-channel-session.js";
|
|
11
|
+
import {
|
|
12
|
+
getHostedAdmissionAdapter,
|
|
13
|
+
type HostedAdmissionAdapter,
|
|
14
|
+
} from "./hosted-admission-adapter.js";
|
|
17
15
|
import {
|
|
18
16
|
HostedAdmissionLocalSendError,
|
|
19
17
|
runDuringHostedAdmission,
|
|
@@ -30,12 +28,19 @@ import {
|
|
|
30
28
|
import {
|
|
31
29
|
abortReasonFromTurnStatus,
|
|
32
30
|
HOSTED_EXECUTION_DIAG_RUNNING_STATUS,
|
|
31
|
+
hostedErrorStackSnippet,
|
|
33
32
|
hostedExecutionDiagId,
|
|
34
33
|
writeHostedExecutionDiag,
|
|
35
34
|
} from "./hosted-execution-diag.js";
|
|
36
35
|
import { recordHostedManagedAutomation } from "./hosted-managed-automation.js";
|
|
37
36
|
import { errorResponse, readJsonObject } from "./http-channel.js";
|
|
37
|
+
import type { LogRingEntry } from "./log-ring.js";
|
|
38
38
|
import type { Router } from "./router.js";
|
|
39
|
+
import {
|
|
40
|
+
type SessionRunExecutionWrite,
|
|
41
|
+
serializeSessionRunError,
|
|
42
|
+
snapshotSessionRunLogs,
|
|
43
|
+
} from "./session-run-log.js";
|
|
39
44
|
|
|
40
45
|
type DispatchInput = {
|
|
41
46
|
delivery: HostedAdmission;
|
|
@@ -115,17 +120,20 @@ function nonRetryableRejection(error: unknown): AdmissionDispatch | undefined {
|
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
/**
|
|
118
|
-
* Run authored admission without creating a framework session.
|
|
123
|
+
* Run authored admission without creating a framework session. Authored
|
|
119
124
|
* `task` callbacks execute as sessionless work; the first `send` is captured
|
|
120
125
|
* and executed only after the control plane allocates `ses_…`. Capturing
|
|
121
126
|
* does not terminate the caller — `send` returns an inert deferred handle so
|
|
122
127
|
* a task body runs to completion before the admission response is computed.
|
|
128
|
+
*
|
|
129
|
+
* Dispatch is the adapter the mounted channel attached. Hosted delivery
|
|
130
|
+
* does not switch on `channelKind`.
|
|
123
131
|
*/
|
|
124
132
|
export async function dispatchHostedAdmission(
|
|
125
133
|
input: DispatchInput
|
|
126
134
|
): Promise<AdmissionDispatch> {
|
|
127
|
-
const
|
|
128
|
-
if (
|
|
135
|
+
const adapter = getHostedAdmissionAdapter(input.definition);
|
|
136
|
+
if (adapter === undefined || adapter.kind !== input.delivery.channelKind) {
|
|
129
137
|
return {
|
|
130
138
|
response: Response.json(
|
|
131
139
|
{ ok: false, error: "channel_kind_mismatch" },
|
|
@@ -135,16 +143,16 @@ export async function dispatchHostedAdmission(
|
|
|
135
143
|
}
|
|
136
144
|
// The admission window marker follows the async graph into task bodies so
|
|
137
145
|
// the engine can refuse local sends that would bypass the capture stub.
|
|
138
|
-
return runDuringHostedAdmission(() => runAdmission(input,
|
|
146
|
+
return runDuringHostedAdmission(() => runAdmission(input, adapter));
|
|
139
147
|
}
|
|
140
148
|
|
|
141
149
|
async function runAdmission(
|
|
142
150
|
input: DispatchInput,
|
|
143
|
-
|
|
151
|
+
adapter: HostedAdmissionAdapter
|
|
144
152
|
): Promise<AdmissionDispatch> {
|
|
145
153
|
const background: Promise<unknown>[] = [];
|
|
146
154
|
let captured: { message: string; options: SendMessageOptions } | undefined;
|
|
147
|
-
const
|
|
155
|
+
const capturingArgs: ChannelHandlerArgs = {
|
|
148
156
|
...input.args,
|
|
149
157
|
send: async (message, options = {}) => {
|
|
150
158
|
if (captured !== undefined) {
|
|
@@ -156,12 +164,12 @@ async function runAdmission(
|
|
|
156
164
|
waitUntil: (promise) => {
|
|
157
165
|
background.push(promise);
|
|
158
166
|
},
|
|
159
|
-
}
|
|
167
|
+
};
|
|
168
|
+
const args = adapter.prepareArgs?.(capturingArgs) ?? capturingArgs;
|
|
160
169
|
let dispatched: Response;
|
|
161
170
|
try {
|
|
162
|
-
dispatched = await
|
|
163
|
-
|
|
164
|
-
eventName: input.delivery.eventType,
|
|
171
|
+
dispatched = await adapter.dispatch({
|
|
172
|
+
eventType: input.delivery.eventType,
|
|
165
173
|
payload: input.delivery.payload,
|
|
166
174
|
deliveryId: input.delivery.deliveryId,
|
|
167
175
|
args,
|
|
@@ -212,7 +220,7 @@ async function runAdmission(
|
|
|
212
220
|
},
|
|
213
221
|
};
|
|
214
222
|
}
|
|
215
|
-
const outcome =
|
|
223
|
+
const outcome = adapter.getOutcome(dispatched);
|
|
216
224
|
if (outcome === undefined) {
|
|
217
225
|
return {
|
|
218
226
|
response: Response.json(
|
|
@@ -264,6 +272,15 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
264
272
|
* `hosted-execution.json` from that path after the POST returns.
|
|
265
273
|
*/
|
|
266
274
|
stateRoot?: string;
|
|
275
|
+
/**
|
|
276
|
+
* Upsert this execution into the v2 session-level framework-store log.
|
|
277
|
+
* Called for the pre-turn `running` claim and every terminal outcome so
|
|
278
|
+
* a dead Node process still leaves a leftover. Soft-fail inside the
|
|
279
|
+
* store; this path still awaits so a parked pod cannot shed the write.
|
|
280
|
+
*/
|
|
281
|
+
persistSessionRun?(write: SessionRunExecutionWrite): Promise<void>;
|
|
282
|
+
/** Snapshot of the process log ring at persist time. */
|
|
283
|
+
snapshotLogs?(): LogRingEntry[];
|
|
267
284
|
}): void {
|
|
268
285
|
const pending = new Map<
|
|
269
286
|
string,
|
|
@@ -404,30 +421,64 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
404
421
|
abortReason?:
|
|
405
422
|
| ReturnType<typeof abortReasonFromTurnStatus>
|
|
406
423
|
| "execute_throw";
|
|
424
|
+
error?: unknown;
|
|
407
425
|
}): Promise<void> => {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
426
|
+
const durationMs = Math.max(
|
|
427
|
+
0,
|
|
428
|
+
Math.round(performance.now() - startedAt)
|
|
429
|
+
);
|
|
411
430
|
const message =
|
|
412
431
|
fields.message === undefined
|
|
413
432
|
? undefined
|
|
414
433
|
: boundedMessage(fields.message);
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
:
|
|
427
|
-
|
|
428
|
-
? {}
|
|
429
|
-
|
|
430
|
-
|
|
434
|
+
const stack =
|
|
435
|
+
fields.error === undefined
|
|
436
|
+
? undefined
|
|
437
|
+
: hostedErrorStackSnippet(fields.error);
|
|
438
|
+
if (input.stateRoot !== undefined) {
|
|
439
|
+
await writeHostedExecutionDiag(input.stateRoot, {
|
|
440
|
+
v: 1,
|
|
441
|
+
executionId,
|
|
442
|
+
writtenAt: new Date().toISOString(),
|
|
443
|
+
sessionPublicId: execution.sessionPublicId,
|
|
444
|
+
sessionId: fields.sessionId,
|
|
445
|
+
turnStatus: fields.turnStatus,
|
|
446
|
+
durationMs,
|
|
447
|
+
...(message === undefined ? {} : { message }),
|
|
448
|
+
...(stack === undefined || stack.length === 0 ? {} : { stack }),
|
|
449
|
+
...(message !== undefined && /abort/i.test(message)
|
|
450
|
+
? { signalAborted: true }
|
|
451
|
+
: {}),
|
|
452
|
+
...(fields.abortReason === undefined
|
|
453
|
+
? {}
|
|
454
|
+
: { abortReason: fields.abortReason }),
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
if (input.persistSessionRun === undefined) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const error =
|
|
461
|
+
fields.error === undefined
|
|
462
|
+
? undefined
|
|
463
|
+
: serializeSessionRunError(fields.error);
|
|
464
|
+
try {
|
|
465
|
+
await input.persistSessionRun({
|
|
466
|
+
executionId,
|
|
467
|
+
writtenAt: new Date().toISOString(),
|
|
468
|
+
sessionPublicId: execution.sessionPublicId,
|
|
469
|
+
sessionId: fields.sessionId,
|
|
470
|
+
turnStatus: fields.turnStatus,
|
|
471
|
+
durationMs,
|
|
472
|
+
...(fields.abortReason === undefined
|
|
473
|
+
? {}
|
|
474
|
+
: { abortReason: fields.abortReason }),
|
|
475
|
+
...(message === undefined ? {} : { message }),
|
|
476
|
+
...(error === undefined ? {} : { error }),
|
|
477
|
+
logs: snapshotSessionRunLogs(input.snapshotLogs?.() ?? []),
|
|
478
|
+
});
|
|
479
|
+
} catch {
|
|
480
|
+
// Soft-fail: a store outage must not change the HTTP reply.
|
|
481
|
+
}
|
|
431
482
|
};
|
|
432
483
|
// Claim the file before the turn runs. The host reads it after its
|
|
433
484
|
// own POST returns, which for a 9-minute timeout is while this turn
|
|
@@ -486,6 +537,7 @@ export function registerHostedDeliveryRoute(input: {
|
|
|
486
537
|
turnStatus: "error",
|
|
487
538
|
message: error instanceof Error ? error.message : String(error),
|
|
488
539
|
abortReason: "execute_throw",
|
|
540
|
+
error,
|
|
489
541
|
});
|
|
490
542
|
throw error;
|
|
491
543
|
}
|
|
@@ -46,6 +46,8 @@ export type HostedExecutionDiag = {
|
|
|
46
46
|
turnStatus: string;
|
|
47
47
|
durationMs: number;
|
|
48
48
|
message?: string;
|
|
49
|
+
/** First frames of a thrown Error; omitted on soft turn failures. */
|
|
50
|
+
stack?: string;
|
|
49
51
|
signalAborted?: boolean;
|
|
50
52
|
requestId?: string;
|
|
51
53
|
abortReason?: HostedExecutionDiagAbortReason;
|
|
@@ -63,6 +65,7 @@ const DIAG_KEYS = new Set([
|
|
|
63
65
|
"turnStatus",
|
|
64
66
|
"durationMs",
|
|
65
67
|
"message",
|
|
68
|
+
"stack",
|
|
66
69
|
"signalAborted",
|
|
67
70
|
"requestId",
|
|
68
71
|
"abortReason",
|
|
@@ -86,6 +89,42 @@ function boundedOptionalString(
|
|
|
86
89
|
: undefined;
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
/** First line + frames; factory `last_error` is 1024 chars with a TAS prefix. */
|
|
93
|
+
export const HOSTED_ERROR_STACK_LINE_COUNT = 5;
|
|
94
|
+
export const HOSTED_ERROR_SNIPPET_MAX_CHARS = 700;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Bounded throw text for the hosted 500 body and the on-disk diag `stack`.
|
|
98
|
+
* Prefers `Error.stack` (message line + `at` frames) so `last_error` is not
|
|
99
|
+
* the generic "Internal server error." when the throw has a stack.
|
|
100
|
+
*/
|
|
101
|
+
export function hostedErrorStackSnippet(error: unknown): string {
|
|
102
|
+
if (error instanceof Error) {
|
|
103
|
+
const stack = error.stack?.trim();
|
|
104
|
+
if (stack !== undefined && stack.length > 0) {
|
|
105
|
+
const lines = stack
|
|
106
|
+
.split("\n")
|
|
107
|
+
.map((line) => line.trimEnd())
|
|
108
|
+
.filter((line) => line.trim().length > 0)
|
|
109
|
+
.slice(0, HOSTED_ERROR_STACK_LINE_COUNT);
|
|
110
|
+
const text = lines.join("\n");
|
|
111
|
+
return text.length > HOSTED_ERROR_SNIPPET_MAX_CHARS
|
|
112
|
+
? `${text.slice(0, HOSTED_ERROR_SNIPPET_MAX_CHARS)}…`
|
|
113
|
+
: text;
|
|
114
|
+
}
|
|
115
|
+
return error.message.length > HOSTED_ERROR_SNIPPET_MAX_CHARS
|
|
116
|
+
? `${error.message.slice(0, HOSTED_ERROR_SNIPPET_MAX_CHARS)}…`
|
|
117
|
+
: error.message;
|
|
118
|
+
}
|
|
119
|
+
const text = String(error);
|
|
120
|
+
if (text.length === 0) {
|
|
121
|
+
return "";
|
|
122
|
+
}
|
|
123
|
+
return text.length > HOSTED_ERROR_SNIPPET_MAX_CHARS
|
|
124
|
+
? `${text.slice(0, HOSTED_ERROR_SNIPPET_MAX_CHARS)}…`
|
|
125
|
+
: text;
|
|
126
|
+
}
|
|
127
|
+
|
|
89
128
|
export function hostedExecutionDiagPath(stateRoot: string): string {
|
|
90
129
|
return join(stateRoot, HOSTED_EXECUTION_DIAG_FILENAME);
|
|
91
130
|
}
|
|
@@ -147,6 +186,10 @@ export function parseHostedExecutionDiag(
|
|
|
147
186
|
return undefined;
|
|
148
187
|
}
|
|
149
188
|
const message = boundedOptionalString(body.message, 1024);
|
|
189
|
+
const stack = boundedOptionalString(
|
|
190
|
+
body.stack,
|
|
191
|
+
HOSTED_ERROR_SNIPPET_MAX_CHARS
|
|
192
|
+
);
|
|
150
193
|
const requestId = boundedOptionalString(body.requestId, 128);
|
|
151
194
|
const abortReason =
|
|
152
195
|
body.abortReason === undefined
|
|
@@ -173,6 +216,7 @@ export function parseHostedExecutionDiag(
|
|
|
173
216
|
turnStatus: body.turnStatus,
|
|
174
217
|
durationMs: body.durationMs,
|
|
175
218
|
...(message === undefined ? {} : { message }),
|
|
219
|
+
...(stack === undefined ? {} : { stack }),
|
|
176
220
|
...(body.signalAborted === true ? { signalAborted: true } : {}),
|
|
177
221
|
...(requestId === undefined ? {} : { requestId }),
|
|
178
222
|
...(abortReason === undefined ? {} : { abortReason }),
|
|
@@ -196,6 +240,7 @@ export async function writeHostedExecutionDiag(
|
|
|
196
240
|
serialized.length > HOSTED_EXECUTION_DIAG_MAX_BYTES
|
|
197
241
|
? JSON.stringify({
|
|
198
242
|
...diag,
|
|
243
|
+
stack: undefined,
|
|
199
244
|
message: boundedOptionalString(diag.message, 256),
|
|
200
245
|
})
|
|
201
246
|
: serialized;
|
|
@@ -95,6 +95,13 @@ export function buildHttpChannelRoutes(args: {
|
|
|
95
95
|
"Expected a JSON body with a string `message`."
|
|
96
96
|
);
|
|
97
97
|
}
|
|
98
|
+
if (body.asOf !== undefined && typeof body.asOf !== "string") {
|
|
99
|
+
return errorResponse(
|
|
100
|
+
400,
|
|
101
|
+
"invalid_request",
|
|
102
|
+
"`asOf` must be an ISO-8601 instant with a timezone when provided."
|
|
103
|
+
);
|
|
104
|
+
}
|
|
98
105
|
const message = await transformMessage(body.message, ctx.auth);
|
|
99
106
|
const workspaceFiles = readWorkspaceFiles(body.workspaceFiles);
|
|
100
107
|
// Per-session cloud attach (create only). Eval harness uses this to pin
|
|
@@ -105,6 +112,7 @@ export function buildHttpChannelRoutes(args: {
|
|
|
105
112
|
...(typeof body.title === "string" ? { title: body.title } : {}),
|
|
106
113
|
...(body.purpose === "eval" ? { purpose: "eval" as const } : {}),
|
|
107
114
|
...(body.dryRun === true ? { dryRun: true } : {}),
|
|
115
|
+
asOf: body.asOf,
|
|
108
116
|
...(workspaceFiles === undefined ? {} : { workspaceFiles }),
|
|
109
117
|
...(cloud === undefined ? {} : { cloud }),
|
|
110
118
|
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inert KV half of hosted v2 storage.
|
|
3
|
+
*
|
|
4
|
+
* The control-plane KV proxy (`/internal/agent-serve/storage/*`) authorizes a
|
|
5
|
+
* request by mapping the caller's pod service account to a row in the v1
|
|
6
|
+
* deployment table. Architecture v2 registers its deployments in the factory
|
|
7
|
+
* control plane instead, so every proxy call from a v2 pod answers
|
|
8
|
+
* `403 No Agent Serve deployment is bound to this service account`. The
|
|
9
|
+
* lazy-restore path deliberately propagates read failures (a store outage must
|
|
10
|
+
* not read as "unknown continuation token"), so those 403s fail follow-up
|
|
11
|
+
* deliveries outright.
|
|
12
|
+
*
|
|
13
|
+
* This backend stands in for the proxy until it can resolve v2 deployments:
|
|
14
|
+
* writes are dropped, reads are definitive misses, and lists are empty. It is
|
|
15
|
+
* paired with the framework file store (see framework-file-storage.ts), which
|
|
16
|
+
* authorizes by store source id rather than by deployment row and therefore
|
|
17
|
+
* still carries session records, event transcripts, and the continuation
|
|
18
|
+
* index — the state sessions are restored from.
|
|
19
|
+
*
|
|
20
|
+
* Author KV is the one thing that must not silently accept writes here, since
|
|
21
|
+
* handlers read their own writes back. {@link AUTHOR_KV_UNSUPPORTED} marks the
|
|
22
|
+
* definition so `ctx.host.kv` falls back to `--state-root` disk instead, the
|
|
23
|
+
* same fallback a deployment with no configured backend gets.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { StorageConfig } from "../storage.js";
|
|
27
|
+
import type { JsonValue } from "../types.js";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Marker stamped on a definition whose KV core cannot serve `ctx.host.kv`,
|
|
31
|
+
* even though `get` is present for framework session state. Explicit
|
|
32
|
+
* `unique symbol` annotation required for `--isolatedDeclarations` (TS9010).
|
|
33
|
+
*/
|
|
34
|
+
export const AUTHOR_KV_UNSUPPORTED: unique symbol = Symbol.for(
|
|
35
|
+
"@cursor/july.storage.author-kv-unsupported"
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/** True when `value` was built from {@link nullKvStorage}. */
|
|
39
|
+
export function isAuthorKvUnsupported(value: unknown): boolean {
|
|
40
|
+
return (
|
|
41
|
+
typeof value === "object" &&
|
|
42
|
+
value !== null &&
|
|
43
|
+
(value as Record<symbol, unknown>)[AUTHOR_KV_UNSUPPORTED] === true
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type NullKvStorageMethods = Required<
|
|
48
|
+
Pick<StorageConfig, "put" | "get" | "delete" | "list">
|
|
49
|
+
> & {
|
|
50
|
+
readonly [AUTHOR_KV_UNSUPPORTED]: true;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A KV core that stores nothing. `get` resolving `undefined` is honest here
|
|
55
|
+
* rather than dangerous: nothing was ever written, so a miss cannot shadow a
|
|
56
|
+
* session that exists elsewhere.
|
|
57
|
+
*
|
|
58
|
+
* No `cas` group and no `listNewest`: conditional writes over a store that
|
|
59
|
+
* forgets would report a lock acquired, and write-time ordering means nothing
|
|
60
|
+
* without writes.
|
|
61
|
+
*/
|
|
62
|
+
export function nullKvStorage(): NullKvStorageMethods {
|
|
63
|
+
return {
|
|
64
|
+
[AUTHOR_KV_UNSUPPORTED]: true,
|
|
65
|
+
put(): void {},
|
|
66
|
+
get(): JsonValue | undefined {
|
|
67
|
+
return undefined;
|
|
68
|
+
},
|
|
69
|
+
delete(): void {},
|
|
70
|
+
list(): [] {
|
|
71
|
+
return [];
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|