@cursor/july 0.1.105 → 0.1.107
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/agent-serve.js +1 -8
- package/dist/channels/bitbucket/types.d.ts +2 -4
- package/dist/channels/bitbucket/types.d.ts.map +1 -1
- package/dist/channels/deployments/types.d.ts +5 -8
- package/dist/channels/deployments/types.d.ts.map +1 -1
- package/dist/channels/github/types.d.ts +2 -4
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/gitlab/types.d.ts +2 -5
- package/dist/channels/gitlab/types.d.ts.map +1 -1
- package/dist/channels/origin/types.d.ts +2 -3
- package/dist/channels/origin/types.d.ts.map +1 -1
- package/dist/channels/slack/defaults.d.ts.map +1 -1
- package/dist/channels/slack/defaults.js +3 -0
- 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.map +1 -1
- package/dist/channels/slack/live-delivery.js +48 -11
- package/dist/channels/slack/types.d.ts +4 -3
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/docs/404.html +3 -3
- package/dist/docs/ab.html +6 -6
- package/dist/docs/assets/{app.BVwaffdK.js → app.CtuckIiL.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DXXZxiMv.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.WuWTZ1x8.js → VPLocalSearchBox.Bkkhnu8K.js} +2 -2
- package/dist/docs/assets/chunks/{theme.BYdI_6sF.js → theme.DhpIe0Pa.js} +2 -2
- package/dist/docs/assets/{guides_opentelemetry.md.COh9jFVg.js → guides_opentelemetry.md.SCRMskno.js} +3 -3
- package/dist/docs/assets/{guides_opentelemetry.md.COh9jFVg.lean.js → guides_opentelemetry.md.SCRMskno.lean.js} +1 -1
- package/dist/docs/assets/{reference_channels.md.DBGcaB7m.js → reference_channels.md.D-qTqwcq.js} +2 -2
- package/dist/docs/assets/{reference_channels.md.DBGcaB7m.lean.js → reference_channels.md.D-qTqwcq.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.C6Lo8SEd.js → reference_cli.md.Dm67hd2D.js} +1 -1
- package/dist/docs/assets/{skills_otel.md.CqhobH3B.js → skills_otel.md.B5Ddv1xH.js} +1 -1
- package/dist/docs/assets/{style.BRuM8477.css → style.DCD_zyg3.css} +1 -1
- package/dist/docs/building-with-agents.html +6 -6
- package/dist/docs/concepts.html +5 -5
- package/dist/docs/deployment.html +6 -6
- package/dist/docs/evals.html +6 -6
- package/dist/docs/guides/agent-to-agent.html +6 -6
- package/dist/docs/guides/cloud-agents.html +6 -6
- package/dist/docs/guides/convert-automation.html +6 -6
- package/dist/docs/guides/github.html +6 -6
- package/dist/docs/guides/grokbot-agents.html +6 -6
- package/dist/docs/guides/human-in-the-loop.html +6 -6
- package/dist/docs/guides/mcp-oauth.html +6 -6
- package/dist/docs/guides/opentelemetry.html +8 -8
- package/dist/docs/guides/opentelemetry.md +3 -4
- package/dist/docs/guides/slack.html +6 -6
- package/dist/docs/guides/webhooks.html +6 -6
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +6 -6
- package/dist/docs/index.html +6 -6
- package/dist/docs/llms-full.txt +22 -28
- package/dist/docs/quickstart.html +6 -6
- package/dist/docs/reference/agent-config.html +6 -6
- package/dist/docs/reference/artifacts.html +6 -6
- package/dist/docs/reference/channels.html +7 -7
- package/dist/docs/reference/channels.md +11 -19
- package/dist/docs/reference/cli.html +8 -8
- package/dist/docs/reference/cli.md +5 -2
- package/dist/docs/reference/connections.html +6 -6
- package/dist/docs/reference/extensions.html +6 -6
- package/dist/docs/reference/hooks.html +6 -6
- package/dist/docs/reference/http-api.html +6 -6
- package/dist/docs/reference/instructions.html +6 -6
- package/dist/docs/reference/playground.html +5 -5
- package/dist/docs/reference/project-layout.html +6 -6
- package/dist/docs/reference/prompt.html +6 -6
- package/dist/docs/reference/result.html +6 -6
- package/dist/docs/reference/schedules.html +6 -6
- package/dist/docs/reference/sessions.html +5 -5
- package/dist/docs/reference/skills.html +6 -6
- package/dist/docs/reference/subagents.html +6 -6
- package/dist/docs/reference/tools.html +6 -6
- package/dist/docs/scaffolding-agents.html +5 -5
- package/dist/docs/skills/ab.html +6 -6
- package/dist/docs/skills/create-agent.html +6 -6
- package/dist/docs/skills/debug.html +5 -5
- package/dist/docs/skills/evals.html +6 -6
- package/dist/docs/skills/framework-map.html +5 -5
- package/dist/docs/skills/github.html +6 -6
- package/dist/docs/skills/hillclimb.html +6 -6
- package/dist/docs/skills/index.html +5 -5
- package/dist/docs/skills/mcp-auth.html +6 -6
- package/dist/docs/skills/otel.html +8 -8
- package/dist/docs/skills/otel.md +3 -3
- package/dist/docs/skills/setup-slack.html +6 -6
- package/dist/docs/storage.html +6 -6
- package/dist/docs/templates/agentic-owners.html +6 -6
- package/dist/docs/templates/agents-md.html +6 -6
- package/dist/docs/templates/code-wiki.html +6 -6
- package/dist/docs/templates/demo.html +6 -6
- package/dist/docs/templates/grokbot-agents.html +6 -6
- package/dist/docs/templates/pr-autofixer.html +6 -6
- package/dist/docs/templates/security-help.html +6 -6
- package/dist/docs/templates/security-reviewer.html +6 -6
- package/dist/docs/templates/triage.html +6 -6
- package/dist/docs/troubleshooting.html +5 -5
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +23 -11
- package/dist/internal/cursor/backend-client.d.ts +25 -0
- package/dist/internal/cursor/backend-client.d.ts.map +1 -1
- package/dist/internal/cursor/backend-client.js +57 -2
- package/dist/internal/deploy-client.d.ts +8 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +14 -0
- package/dist/internal/deploy-manifest.d.ts +3 -0
- package/dist/internal/deploy-manifest.d.ts.map +1 -1
- package/dist/internal/deploy-manifest.js +15 -12
- package/dist/internal/discovery/agent.d.ts.map +1 -1
- package/dist/internal/discovery/agent.js +1 -0
- package/dist/internal/discovery/info.d.ts.map +1 -1
- package/dist/internal/discovery/info.js +7 -3
- package/dist/internal/discovery/modules.d.ts +1 -1
- package/dist/internal/discovery/modules.d.ts.map +1 -1
- package/dist/internal/discovery/modules.js +1 -0
- package/dist/internal/discovery/project.d.ts.map +1 -1
- package/dist/internal/discovery/project.js +3 -0
- package/dist/internal/discovery/reminders.d.ts +7 -0
- package/dist/internal/discovery/reminders.d.ts.map +1 -0
- package/dist/internal/discovery/reminders.js +65 -0
- package/dist/internal/discovery/subagent.js +1 -0
- package/dist/internal/distribution.d.ts +5 -0
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +17 -0
- package/dist/internal/framework-storage-selection.d.ts +14 -3
- package/dist/internal/framework-storage-selection.d.ts.map +1 -1
- package/dist/internal/framework-storage-selection.js +36 -6
- package/dist/internal/guest-network.d.ts.map +1 -1
- package/dist/internal/guest-network.js +2 -0
- package/dist/internal/hosted-delivery-protocol.d.ts +48 -0
- package/dist/internal/hosted-delivery-protocol.d.ts.map +1 -1
- package/dist/internal/hosted-delivery-protocol.js +60 -0
- package/dist/internal/hosted-delivery.d.ts +10 -0
- package/dist/internal/hosted-delivery.d.ts.map +1 -1
- package/dist/internal/hosted-delivery.js +83 -18
- package/dist/internal/local-control-plane.d.ts +18 -2
- package/dist/internal/local-control-plane.d.ts.map +1 -1
- package/dist/internal/local-control-plane.js +199 -17
- package/dist/internal/reminder-control-plane-protocol.d.ts +187 -0
- package/dist/internal/reminder-control-plane-protocol.d.ts.map +1 -0
- package/dist/internal/reminder-control-plane-protocol.js +244 -0
- package/dist/internal/reminder-runner.d.ts +48 -6
- package/dist/internal/reminder-runner.d.ts.map +1 -1
- package/dist/internal/reminder-runner.js +336 -48
- package/dist/internal/reminder-schedule.d.ts +5 -2
- package/dist/internal/reminder-schedule.d.ts.map +1 -1
- package/dist/internal/reminder-schedule.js +8 -6
- package/dist/internal/reminder-store.d.ts +24 -7
- package/dist/internal/reminder-store.d.ts.map +1 -1
- package/dist/internal/reminder-store.js +6 -5
- package/dist/internal/request-error-status.d.ts +2 -0
- package/dist/internal/request-error-status.d.ts.map +1 -1
- package/dist/internal/request-error-status.js +22 -3
- package/dist/internal/scm/pr-url.d.ts +40 -0
- package/dist/internal/scm/pr-url.d.ts.map +1 -1
- package/dist/internal/scm/pr-url.js +93 -14
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +67 -27
- package/dist/internal/session-engine.d.ts +4 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +5 -0
- package/dist/playground/assets/{index-BaQzjaNK.js → index-Bq2HpEQB.js} +23 -23
- package/dist/playground/assets/index-CZKKNlmb.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/reminders.d.ts +24 -5
- package/dist/reminders.d.ts.map +1 -1
- package/dist/reminders.js +28 -5
- package/dist/types.d.ts +89 -20
- package/dist/types.d.ts.map +1 -1
- package/docs/guides/opentelemetry.md +3 -4
- package/docs/reference/channels.md +11 -19
- package/docs/reference/cli.md +5 -2
- package/package.json +3 -3
- package/skills/otel/SKILL.md +3 -3
- package/src/bin/agent-serve.ts +1 -9
- package/src/channels/bitbucket/types.ts +2 -4
- package/src/channels/deployments/types.ts +5 -8
- package/src/channels/github/types.ts +2 -4
- package/src/channels/gitlab/types.ts +2 -5
- package/src/channels/origin/types.ts +2 -3
- package/src/channels/slack/defaults.ts +3 -0
- package/src/channels/slack/dispatch.ts +1 -0
- package/src/channels/slack/live-delivery.ts +53 -12
- package/src/channels/slack/types.ts +4 -3
- package/src/index.ts +6 -3
- package/src/internal/cli-deploy.ts +30 -12
- package/src/internal/cursor/backend-client.ts +90 -2
- package/src/internal/deploy-client.ts +16 -0
- package/src/internal/deploy-manifest.ts +16 -11
- package/src/internal/discovery/agent.ts +1 -0
- package/src/internal/discovery/info.ts +6 -0
- package/src/internal/discovery/modules.ts +2 -4
- package/src/internal/discovery/project.ts +3 -0
- package/src/internal/discovery/reminders.ts +65 -0
- package/src/internal/discovery/subagent.ts +1 -0
- package/src/internal/distribution.ts +20 -0
- package/src/internal/framework-storage-selection.ts +43 -6
- package/src/internal/guest-network.ts +3 -0
- package/src/internal/hosted-delivery-protocol.ts +96 -0
- package/src/internal/hosted-delivery.ts +154 -27
- package/src/internal/local-control-plane.ts +281 -22
- package/src/internal/reminder-control-plane-protocol.ts +499 -0
- package/src/internal/reminder-runner.ts +421 -48
- package/src/internal/reminder-schedule.ts +11 -6
- package/src/internal/reminder-store.ts +27 -7
- package/src/internal/request-error-status.ts +27 -3
- package/src/internal/scm/pr-url.ts +167 -20
- package/src/internal/server.ts +69 -16
- package/src/internal/session-engine.ts +5 -0
- package/src/reminders.ts +35 -4
- package/src/types.ts +95 -20
- package/dist/docs/assets/chunks/@localSearchIndexroot.CJxMklAy.js +0 -1
- package/dist/playground/assets/index-CceqnLTX.css +0 -1
- /package/dist/docs/assets/{reference_cli.md.C6Lo8SEd.lean.js → reference_cli.md.Dm67hd2D.lean.js} +0 -0
- /package/dist/docs/assets/{skills_otel.md.CqhobH3B.lean.js → skills_otel.md.B5Ddv1xH.lean.js} +0 -0
|
@@ -23,8 +23,14 @@ export interface ResolvedSchedule {
|
|
|
23
23
|
nextFireAtMs: number;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Resolve create-input schedule fields into a concrete next-fire plan.
|
|
28
|
+
* Pass `nowMs` for deterministic CP / test resolution; CP owns this call.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveSchedule(
|
|
31
|
+
input: Pick<ReminderCreateInput, "every" | "cron" | "delay" | "at">,
|
|
32
|
+
nowMs: number = Date.now()
|
|
33
|
+
): ResolvedSchedule {
|
|
28
34
|
const shapes = [
|
|
29
35
|
input.every !== undefined,
|
|
30
36
|
input.cron !== undefined,
|
|
@@ -37,7 +43,6 @@ export function resolveSchedule(input: ReminderCreateInput): ResolvedSchedule {
|
|
|
37
43
|
);
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
const now = Date.now();
|
|
41
46
|
if (input.every !== undefined) {
|
|
42
47
|
const intervalMs = parseDuration(input.every);
|
|
43
48
|
if (intervalMs <= 0) {
|
|
@@ -49,7 +54,7 @@ export function resolveSchedule(input: ReminderCreateInput): ResolvedSchedule {
|
|
|
49
54
|
kind: "every",
|
|
50
55
|
value: String(input.every),
|
|
51
56
|
intervalMs,
|
|
52
|
-
nextFireAtMs:
|
|
57
|
+
nextFireAtMs: nowMs + intervalMs,
|
|
53
58
|
};
|
|
54
59
|
}
|
|
55
60
|
if (input.cron !== undefined) {
|
|
@@ -57,7 +62,7 @@ export function resolveSchedule(input: ReminderCreateInput): ResolvedSchedule {
|
|
|
57
62
|
return {
|
|
58
63
|
kind: "cron",
|
|
59
64
|
value: input.cron,
|
|
60
|
-
nextFireAtMs: nextCronFireMs(input.cron,
|
|
65
|
+
nextFireAtMs: nextCronFireMs(input.cron, nowMs),
|
|
61
66
|
};
|
|
62
67
|
}
|
|
63
68
|
if (input.delay !== undefined) {
|
|
@@ -65,7 +70,7 @@ export function resolveSchedule(input: ReminderCreateInput): ResolvedSchedule {
|
|
|
65
70
|
return {
|
|
66
71
|
kind: "delay",
|
|
67
72
|
value: String(input.delay),
|
|
68
|
-
nextFireAtMs:
|
|
73
|
+
nextFireAtMs: nowMs + delayMs,
|
|
69
74
|
};
|
|
70
75
|
}
|
|
71
76
|
const atMs =
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Reminder document store used by the local control plane
|
|
3
3
|
* (`<stateRoot>/control-plane/reminders/`).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* in the in-memory {@link ReminderRunner} registry.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Anonymous `run` / function `until` handlers are not serializable — they live
|
|
6
|
+
* only in the in-memory {@link ReminderRunner} registry. Named `handler` +
|
|
7
|
+
* `args` and prompt-form reminders survive restart (handlers resolve from
|
|
8
|
+
* `agent/reminders/` on every fire).
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import {
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "node:fs/promises";
|
|
19
19
|
import { join } from "node:path";
|
|
20
20
|
import type {
|
|
21
|
+
JsonObject,
|
|
21
22
|
ReminderBusyPolicy,
|
|
22
23
|
ReminderScheduleKind,
|
|
23
24
|
ReminderStatus,
|
|
@@ -31,6 +32,12 @@ export type {
|
|
|
31
32
|
ReminderStatus,
|
|
32
33
|
} from "../types.js";
|
|
33
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Internal record status. `firing` is CP claim state and must not appear on
|
|
37
|
+
* public {@link ReminderStatus} / {@link ReminderInfo}.
|
|
38
|
+
*/
|
|
39
|
+
export type ReminderRecordStatus = ReminderStatus | "firing";
|
|
40
|
+
|
|
34
41
|
/** Serializable reminder record (no function bodies). */
|
|
35
42
|
export interface ReminderRecord {
|
|
36
43
|
version: typeof REMINDER_SNAPSHOT_VERSION;
|
|
@@ -46,8 +53,15 @@ export interface ReminderRecord {
|
|
|
46
53
|
/** Raw schedule value: every/delay string, cron expression, or ISO `at`. */
|
|
47
54
|
scheduleValue: string;
|
|
48
55
|
intervalMs?: number;
|
|
49
|
-
/** Prompt form — mutually exclusive with in-memory `run`. */
|
|
56
|
+
/** Prompt form — mutually exclusive with `handler` and in-memory `run`. */
|
|
50
57
|
prompt?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Named handler stem (`agent/reminders/<name>.ts`). Mutually exclusive with
|
|
60
|
+
* `prompt` and in-memory `run`.
|
|
61
|
+
*/
|
|
62
|
+
handler?: string;
|
|
63
|
+
/** Serializable args for {@link handler}. */
|
|
64
|
+
args?: JsonObject;
|
|
51
65
|
/**
|
|
52
66
|
* True when create() registered a `run` handler. Handlers are not
|
|
53
67
|
* serializable; after restart, armed records with this flag and no live
|
|
@@ -59,9 +73,14 @@ export interface ReminderRecord {
|
|
|
59
73
|
/** Natural-language until (agent-facing); function until is in-memory only. */
|
|
60
74
|
untilPrompt?: string;
|
|
61
75
|
busyPolicy: ReminderBusyPolicy;
|
|
62
|
-
status:
|
|
76
|
+
status: ReminderRecordStatus;
|
|
63
77
|
disarmReason?: string;
|
|
64
78
|
fireCount: number;
|
|
79
|
+
/**
|
|
80
|
+
* CAS generation. Cancel/rekey and successful outcome apply bump this so
|
|
81
|
+
* in-flight outcomes cannot resurrect a superseded reminder.
|
|
82
|
+
*/
|
|
83
|
+
generation?: number;
|
|
65
84
|
nextFireAtMs: number;
|
|
66
85
|
createdAt: string;
|
|
67
86
|
updatedAt: string;
|
|
@@ -120,7 +139,8 @@ export class ReminderStore {
|
|
|
120
139
|
|
|
121
140
|
async listArmed(): Promise<ReminderRecord[]> {
|
|
122
141
|
const all = await this.list();
|
|
123
|
-
|
|
142
|
+
// Treat in-flight claims as still armed for restart / listing purposes.
|
|
143
|
+
return all.filter((r) => r.status === "armed" || r.status === "firing");
|
|
124
144
|
}
|
|
125
145
|
|
|
126
146
|
async get(id: string): Promise<ReminderRecord | undefined> {
|
|
@@ -7,12 +7,18 @@ import {
|
|
|
7
7
|
hasGuestNetworkErrno,
|
|
8
8
|
isQuietGuestConnectStall,
|
|
9
9
|
} from "./guest-network.js";
|
|
10
|
+
import {
|
|
11
|
+
HOSTED_GUEST_RETRY_TERMINAL,
|
|
12
|
+
HOSTED_GUEST_RETRY_TRANSIENT,
|
|
13
|
+
type HostedGuestRetryDisposition,
|
|
14
|
+
} from "./hosted-delivery-protocol.js";
|
|
10
15
|
import { SessionAuthError, SessionBusyError } from "./session-engine.js";
|
|
11
16
|
|
|
12
17
|
export interface MappedRequestError {
|
|
13
18
|
status: 400 | 403 | 409 | 503;
|
|
14
19
|
code: string;
|
|
15
20
|
message: string;
|
|
21
|
+
retry: HostedGuestRetryDisposition;
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
/**
|
|
@@ -23,23 +29,39 @@ export function mapThrownRequestError(
|
|
|
23
29
|
error: unknown
|
|
24
30
|
): MappedRequestError | undefined {
|
|
25
31
|
if (error instanceof SessionBusyError) {
|
|
26
|
-
return {
|
|
32
|
+
return {
|
|
33
|
+
status: 409,
|
|
34
|
+
code: "session_busy",
|
|
35
|
+
message: error.message,
|
|
36
|
+
retry: HOSTED_GUEST_RETRY_TRANSIENT,
|
|
37
|
+
};
|
|
27
38
|
}
|
|
28
39
|
if (error instanceof V2EngineError) {
|
|
29
40
|
return {
|
|
30
41
|
status: 409,
|
|
31
42
|
code: "v2_identity_conflict",
|
|
32
43
|
message: "Control-plane session identity conflicts with existing state.",
|
|
44
|
+
retry: HOSTED_GUEST_RETRY_TERMINAL,
|
|
33
45
|
};
|
|
34
46
|
}
|
|
35
47
|
if (error instanceof SessionAuthError) {
|
|
36
|
-
return {
|
|
48
|
+
return {
|
|
49
|
+
status: 403,
|
|
50
|
+
code: "forbidden",
|
|
51
|
+
message: error.message,
|
|
52
|
+
retry: HOSTED_GUEST_RETRY_TERMINAL,
|
|
53
|
+
};
|
|
37
54
|
}
|
|
38
55
|
if (
|
|
39
56
|
error instanceof InvalidAsOfError ||
|
|
40
57
|
error instanceof VacuousTimeArgsError
|
|
41
58
|
) {
|
|
42
|
-
return {
|
|
59
|
+
return {
|
|
60
|
+
status: 400,
|
|
61
|
+
code: "invalid_request",
|
|
62
|
+
message: error.message,
|
|
63
|
+
retry: HOSTED_GUEST_RETRY_TERMINAL,
|
|
64
|
+
};
|
|
43
65
|
}
|
|
44
66
|
if (
|
|
45
67
|
error instanceof GuestNetworkUnavailableError ||
|
|
@@ -52,6 +74,7 @@ export function mapThrownRequestError(
|
|
|
52
74
|
error instanceof Error
|
|
53
75
|
? error.message
|
|
54
76
|
: "Guest network is not ready for outbound requests.",
|
|
77
|
+
retry: HOSTED_GUEST_RETRY_TRANSIENT,
|
|
55
78
|
};
|
|
56
79
|
}
|
|
57
80
|
if (isQuietGuestConnectStall(error)) {
|
|
@@ -62,6 +85,7 @@ export function mapThrownRequestError(
|
|
|
62
85
|
error instanceof Error
|
|
63
86
|
? error.message
|
|
64
87
|
: "Guest Connect stalled without a network errno.",
|
|
88
|
+
retry: HOSTED_GUEST_RETRY_TRANSIENT,
|
|
65
89
|
};
|
|
66
90
|
}
|
|
67
91
|
return undefined;
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Provider-neutral PR URL parse. GitHub.com plus the Origin page shapes
|
|
3
3
|
* humans paste: cursor.com/codebase, review.cursor.com/pr, origin.cursor.com.
|
|
4
|
+
*
|
|
5
|
+
* {@link parseScmPrUrl} is the wider parse the review seams use: it also
|
|
6
|
+
* recognizes GitLab merge requests (`/-/merge_requests/<iid>` on any host),
|
|
7
|
+
* Bitbucket Cloud / Data Center pull requests, and GitHub Enterprise Server
|
|
8
|
+
* (`/<owner>/<repo>/pull/<n>` on a host that is none of the above). The
|
|
9
|
+
* narrow {@link parsePrUrl} keeps its github.com + Origin contract for the
|
|
10
|
+
* free-text callers (Slack `pr-ref`) where "any host with /pull/N" would be
|
|
11
|
+
* too eager.
|
|
4
12
|
*/
|
|
5
13
|
|
|
6
14
|
export type PrProvider = "github" | "origin";
|
|
7
15
|
|
|
16
|
+
/** Every SCM the review seams can address. */
|
|
17
|
+
export type ScmPrProvider = PrProvider | "gitlab" | "bitbucket";
|
|
18
|
+
|
|
8
19
|
export type ParsedPrUrl = {
|
|
9
20
|
provider: PrProvider;
|
|
10
21
|
owner: string;
|
|
@@ -13,6 +24,27 @@ export type ParsedPrUrl = {
|
|
|
13
24
|
url: string;
|
|
14
25
|
};
|
|
15
26
|
|
|
27
|
+
export type ParsedScmPrUrl = {
|
|
28
|
+
provider: ScmPrProvider;
|
|
29
|
+
/**
|
|
30
|
+
* Namespace half of the repo: GitHub owner, GitLab group path (nested
|
|
31
|
+
* groups joined with `/`), Bitbucket workspace / project key (`~user`
|
|
32
|
+
* for Data Center personal repos).
|
|
33
|
+
*/
|
|
34
|
+
owner: string;
|
|
35
|
+
repo: string;
|
|
36
|
+
number: number;
|
|
37
|
+
/** Canonical PR page URL. */
|
|
38
|
+
url: string;
|
|
39
|
+
/**
|
|
40
|
+
* Hostname the PR lives on (lowercase). `github.com` for dotcom, the
|
|
41
|
+
* GHES hostname for GitHub Enterprise, `gitlab.com` or the self-managed
|
|
42
|
+
* host for GitLab, `bitbucket.org` or the Data Center host for Bitbucket.
|
|
43
|
+
* Origin PRs report the Origin page host.
|
|
44
|
+
*/
|
|
45
|
+
host: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
16
48
|
const ORIGIN_PAGE = /^origin(-[a-z0-9]+)?\.cursor\.com$/i;
|
|
17
49
|
|
|
18
50
|
function positiveInt(raw: string | undefined): number | undefined {
|
|
@@ -27,19 +59,28 @@ function positiveInt(raw: string | undefined): number | undefined {
|
|
|
27
59
|
}
|
|
28
60
|
|
|
29
61
|
function parsed(
|
|
30
|
-
provider:
|
|
62
|
+
provider: ScmPrProvider,
|
|
63
|
+
host: string,
|
|
31
64
|
owner: string | undefined,
|
|
32
65
|
repo: string | undefined,
|
|
33
|
-
number: number | undefined
|
|
34
|
-
|
|
35
|
-
|
|
66
|
+
number: number | undefined,
|
|
67
|
+
url?: string
|
|
68
|
+
): ParsedScmPrUrl | undefined {
|
|
69
|
+
if (
|
|
70
|
+
owner === undefined ||
|
|
71
|
+
owner === "" ||
|
|
72
|
+
repo === undefined ||
|
|
73
|
+
repo === "" ||
|
|
74
|
+
number === undefined
|
|
75
|
+
) {
|
|
36
76
|
return undefined;
|
|
37
77
|
}
|
|
38
|
-
const
|
|
39
|
-
|
|
78
|
+
const canonical =
|
|
79
|
+
url ??
|
|
80
|
+
(provider === "origin"
|
|
40
81
|
? `https://cursor.com/codebase/${owner}/${repo}/pull/${number}`
|
|
41
|
-
: `https
|
|
42
|
-
return { provider, owner, repo, number, url };
|
|
82
|
+
: `https://${host}/${owner}/${repo}/pull/${number}`);
|
|
83
|
+
return { provider, owner, repo, number, url: canonical, host };
|
|
43
84
|
}
|
|
44
85
|
|
|
45
86
|
/** Strip Slack mrkdwn wrappers and trailing punctuation before `URL`. */
|
|
@@ -50,27 +91,49 @@ function normalizeRaw(raw: string): string {
|
|
|
50
91
|
.replace(/[>\].,;]+$/, "");
|
|
51
92
|
}
|
|
52
93
|
|
|
94
|
+
function isGithubDotCom(host: string): boolean {
|
|
95
|
+
return host === "github.com" || host === "www.github.com";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** github.com and Origin only — the contract free-text PR references use. */
|
|
53
99
|
export function parsePrUrl(raw: string): ParsedPrUrl | undefined {
|
|
100
|
+
const wide = parseScmPrUrl(raw);
|
|
101
|
+
if (wide === undefined) {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
if (wide.provider === "origin") {
|
|
105
|
+
return { ...wide, provider: "origin" };
|
|
106
|
+
}
|
|
107
|
+
if (wide.provider === "github" && isGithubDotCom(wide.host)) {
|
|
108
|
+
return { ...wide, provider: "github" };
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Every provider shape, including GitHub Enterprise Server. The GitLab and
|
|
115
|
+
* Bitbucket path shapes are unambiguous on any host; a plain
|
|
116
|
+
* `/<owner>/<repo>/pull/<n>` on a host that is not github.com, Origin,
|
|
117
|
+
* GitLab, or Bitbucket is taken as GHES (the only remaining forge with that
|
|
118
|
+
* route). Callers that must not accept arbitrary hosts use
|
|
119
|
+
* {@link parsePrUrl}.
|
|
120
|
+
*/
|
|
121
|
+
export function parseScmPrUrl(raw: string): ParsedScmPrUrl | undefined {
|
|
54
122
|
let url: URL;
|
|
55
123
|
try {
|
|
56
124
|
url = new URL(normalizeRaw(raw));
|
|
57
125
|
} catch {
|
|
58
126
|
return undefined;
|
|
59
127
|
}
|
|
128
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
60
131
|
|
|
61
132
|
const parts = url.pathname.split("/").filter(Boolean);
|
|
62
133
|
const host = url.hostname.toLowerCase();
|
|
63
134
|
|
|
64
|
-
if (
|
|
65
|
-
(host === "github.com" || host === "www.github.com") &&
|
|
66
|
-
parts.length >= 4 &&
|
|
67
|
-
parts[2] === "pull"
|
|
68
|
-
) {
|
|
69
|
-
return parsed("github", parts[0], parts[1], positiveInt(parts[3]));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
135
|
if (ORIGIN_PAGE.test(host) && parts.length >= 4 && parts[2] === "github") {
|
|
73
|
-
return parsed("origin", parts[0], parts[1], positiveInt(parts[3]));
|
|
136
|
+
return parsed("origin", host, parts[0], parts[1], positiveInt(parts[3]));
|
|
74
137
|
}
|
|
75
138
|
|
|
76
139
|
if (
|
|
@@ -79,17 +142,101 @@ export function parsePrUrl(raw: string): ParsedPrUrl | undefined {
|
|
|
79
142
|
parts.length >= 5 &&
|
|
80
143
|
parts[3] === "pull"
|
|
81
144
|
) {
|
|
82
|
-
return parsed("origin", parts[1], parts[2], positiveInt(parts[4]));
|
|
145
|
+
return parsed("origin", host, parts[1], parts[2], positiveInt(parts[4]));
|
|
83
146
|
}
|
|
84
147
|
|
|
85
148
|
if (host === "review.cursor.com" && parts[0] === "pr") {
|
|
86
149
|
if (parts.length >= 5 && parts[3] === "github") {
|
|
87
|
-
return parsed("origin", parts[1], parts[2], positiveInt(parts[4]));
|
|
150
|
+
return parsed("origin", host, parts[1], parts[2], positiveInt(parts[4]));
|
|
88
151
|
}
|
|
89
152
|
if (parts.length >= 4) {
|
|
90
|
-
return parsed("origin", parts[1], parts[2], positiveInt(parts[3]));
|
|
153
|
+
return parsed("origin", host, parts[1], parts[2], positiveInt(parts[3]));
|
|
91
154
|
}
|
|
92
155
|
}
|
|
93
156
|
|
|
157
|
+
// GitLab MR page: /<group>/[<subgroup>/…]<project>/-/merge_requests/<iid>
|
|
158
|
+
const separator = parts.indexOf("-");
|
|
159
|
+
if (
|
|
160
|
+
separator >= 2 &&
|
|
161
|
+
parts[separator + 1] === "merge_requests" &&
|
|
162
|
+
parts.length >= separator + 3
|
|
163
|
+
) {
|
|
164
|
+
const owner = parts.slice(0, separator - 1).join("/");
|
|
165
|
+
const repo = parts[separator - 1];
|
|
166
|
+
const number = positiveInt(parts[separator + 2]);
|
|
167
|
+
return parsed(
|
|
168
|
+
"gitlab",
|
|
169
|
+
host,
|
|
170
|
+
owner,
|
|
171
|
+
repo,
|
|
172
|
+
number,
|
|
173
|
+
number === undefined
|
|
174
|
+
? undefined
|
|
175
|
+
: `https://${host}/${owner}/${repo}/-/merge_requests/${number}`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Bitbucket Cloud: bitbucket.org/<workspace>/<slug>/pull-requests/<id>
|
|
180
|
+
if (
|
|
181
|
+
(host === "bitbucket.org" || host === "www.bitbucket.org") &&
|
|
182
|
+
parts.length >= 4 &&
|
|
183
|
+
parts[2] === "pull-requests"
|
|
184
|
+
) {
|
|
185
|
+
const number = positiveInt(parts[3]);
|
|
186
|
+
return parsed(
|
|
187
|
+
"bitbucket",
|
|
188
|
+
"bitbucket.org",
|
|
189
|
+
parts[0],
|
|
190
|
+
parts[1],
|
|
191
|
+
number,
|
|
192
|
+
number === undefined
|
|
193
|
+
? undefined
|
|
194
|
+
: `https://bitbucket.org/${parts[0]}/${parts[1]}/pull-requests/${number}`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Bitbucket Data Center: [/<context>]/projects/<KEY>/repos/<slug>/pull-requests/<id>
|
|
199
|
+
// (personal repos: /users/<name>/repos/<slug>/…, full name `~name/slug`).
|
|
200
|
+
const dc = parts.findIndex(
|
|
201
|
+
(part, index) =>
|
|
202
|
+
(part === "projects" || part === "users") &&
|
|
203
|
+
parts[index + 2] === "repos" &&
|
|
204
|
+
parts[index + 4] === "pull-requests"
|
|
205
|
+
);
|
|
206
|
+
if (dc !== -1 && parts.length >= dc + 6) {
|
|
207
|
+
const number = positiveInt(parts[dc + 5]);
|
|
208
|
+
const owner = parts[dc] === "users" ? `~${parts[dc + 1]}` : parts[dc + 1];
|
|
209
|
+
const slug = parts[dc + 3];
|
|
210
|
+
const context = parts.slice(0, dc).join("/");
|
|
211
|
+
return parsed(
|
|
212
|
+
"bitbucket",
|
|
213
|
+
host,
|
|
214
|
+
owner,
|
|
215
|
+
slug,
|
|
216
|
+
number,
|
|
217
|
+
number === undefined
|
|
218
|
+
? undefined
|
|
219
|
+
: `${url.origin}/${context === "" ? "" : `${context}/`}${parts.slice(dc, dc + 4).join("/")}/pull-requests/${number}`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// GitHub (dotcom or Enterprise Server): /<owner>/<repo>/pull/<n>. Checked
|
|
224
|
+
// last so the forge-specific shapes above win on shared hosts.
|
|
225
|
+
if (
|
|
226
|
+
parts.length >= 4 &&
|
|
227
|
+
parts[2] === "pull" &&
|
|
228
|
+
!ORIGIN_PAGE.test(host) &&
|
|
229
|
+
!/(^|\.)cursor\.com$/.test(host)
|
|
230
|
+
) {
|
|
231
|
+
const githubHost = isGithubDotCom(host) ? "github.com" : host;
|
|
232
|
+
return parsed(
|
|
233
|
+
"github",
|
|
234
|
+
githubHost,
|
|
235
|
+
parts[0],
|
|
236
|
+
parts[1],
|
|
237
|
+
positiveInt(parts[3])
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
94
241
|
return undefined;
|
|
95
242
|
}
|
package/src/internal/server.ts
CHANGED
|
@@ -129,6 +129,11 @@ import {
|
|
|
129
129
|
} from "./handleAgentServeTrigger.js";
|
|
130
130
|
import { createHostPlatforms, warmupHostGitHubAuth } from "./host-platforms.js";
|
|
131
131
|
import { registerHostedDeliveryRoute } from "./hosted-delivery.js";
|
|
132
|
+
import {
|
|
133
|
+
HOSTED_DELIVERY_HEADER,
|
|
134
|
+
HOSTED_GUEST_RETRY_TERMINAL,
|
|
135
|
+
hostedGuestErrorResponse,
|
|
136
|
+
} from "./hosted-delivery-protocol.js";
|
|
132
137
|
import { hostedErrorStackSnippet } from "./hosted-execution-diag.js";
|
|
133
138
|
import { flushHostedExecutionProcessDeath } from "./hosted-execution-flush.js";
|
|
134
139
|
import {
|
|
@@ -191,6 +196,7 @@ import {
|
|
|
191
196
|
import {
|
|
192
197
|
ReminderRunner,
|
|
193
198
|
ReminderValidationError,
|
|
199
|
+
rejectAnonymousReminderHandlers,
|
|
194
200
|
UnknownReminderError,
|
|
195
201
|
} from "./reminder-runner.js";
|
|
196
202
|
import { mapThrownRequestError } from "./request-error-status.js";
|
|
@@ -319,12 +325,9 @@ export async function startServer(
|
|
|
319
325
|
noControlPlane?: boolean;
|
|
320
326
|
/** `--cursor-scm-proxy`: every cursorAccount SCM pack routes via the backend forwarder. */
|
|
321
327
|
cursorScmProxy?: boolean;
|
|
322
|
-
/** Deprecated alias (`--cursor-github-proxy`); same effect as cursorScmProxy. */
|
|
323
|
-
cursorGithubProxy?: boolean;
|
|
324
328
|
};
|
|
325
329
|
const noControlPlane = hosted.noControlPlane === true;
|
|
326
|
-
const scmProxyRequested =
|
|
327
|
-
hosted.cursorScmProxy === true || hosted.cursorGithubProxy === true;
|
|
330
|
+
const scmProxyRequested = hosted.cursorScmProxy === true;
|
|
328
331
|
const runSchedules = options.schedules ?? !dev;
|
|
329
332
|
const runReminders = options.reminders ?? !dev;
|
|
330
333
|
|
|
@@ -846,12 +849,23 @@ export async function startServer(
|
|
|
846
849
|
}
|
|
847
850
|
const scheduleRunner = new ScheduleRunner(engine, project, logger);
|
|
848
851
|
let reminderRunner!: ReminderRunner;
|
|
852
|
+
const architecture = project.agent.architecture;
|
|
853
|
+
const useReminderCpProtocol = !noControlPlane && architecture === "v2";
|
|
849
854
|
const localControlPlane = noControlPlane
|
|
850
855
|
? undefined
|
|
851
856
|
: await createLocalControlPlane({
|
|
852
857
|
fireSchedule: (name) => scheduleRunner.dispatch(name),
|
|
853
|
-
|
|
854
|
-
|
|
858
|
+
...(useReminderCpProtocol
|
|
859
|
+
? {
|
|
860
|
+
executeReminder: (descriptor) =>
|
|
861
|
+
reminderRunner.executeFire(descriptor),
|
|
862
|
+
}
|
|
863
|
+
: {
|
|
864
|
+
fireReminder: (id, fireCount) =>
|
|
865
|
+
reminderRunner.dispatch(id, {
|
|
866
|
+
expectedFireCount: fireCount,
|
|
867
|
+
}),
|
|
868
|
+
}),
|
|
855
869
|
stateDir: join(stateRoot, "control-plane"),
|
|
856
870
|
armReminderClocks: runReminders,
|
|
857
871
|
logger,
|
|
@@ -859,7 +873,16 @@ export async function startServer(
|
|
|
859
873
|
const timerClient = noControlPlane
|
|
860
874
|
? createCursorHostedPlatformTimers()
|
|
861
875
|
: localControlPlane;
|
|
862
|
-
reminderRunner = new ReminderRunner(engine, logger, timerClient
|
|
876
|
+
reminderRunner = new ReminderRunner(engine, logger, timerClient, {
|
|
877
|
+
namedHandlers: project.reminders ?? [],
|
|
878
|
+
// Architecture v2 rejects anonymous run/until everywhere (local + hosted).
|
|
879
|
+
rejectAnonymousHandlers: rejectAnonymousReminderHandlers({
|
|
880
|
+
noControlPlane,
|
|
881
|
+
architecture,
|
|
882
|
+
}),
|
|
883
|
+
architecture,
|
|
884
|
+
controlPlane: useReminderCpProtocol ? localControlPlane : undefined,
|
|
885
|
+
});
|
|
863
886
|
const evalRuns = new EvalRunStore(
|
|
864
887
|
project.rootDir,
|
|
865
888
|
logger,
|
|
@@ -935,6 +958,7 @@ export async function startServer(
|
|
|
935
958
|
const startArgs: ChannelStartArgs = {
|
|
936
959
|
send: (message, sendOptions) =>
|
|
937
960
|
engine.send(channel.id, message, sendOptions),
|
|
961
|
+
architecture: project.agent.architecture,
|
|
938
962
|
getSession: (sessionId) =>
|
|
939
963
|
engine.getChannelSession(channel.id, sessionId),
|
|
940
964
|
receive: (target, input) => engine.receive(target, input),
|
|
@@ -1521,6 +1545,8 @@ function mountAgentRoutes(
|
|
|
1521
1545
|
channelId,
|
|
1522
1546
|
hostedSessionId,
|
|
1523
1547
|
}),
|
|
1548
|
+
fireReminder: (reminderId, fireCount) =>
|
|
1549
|
+
reminderRunner.dispatch(reminderId, { expectedFireCount: fireCount }),
|
|
1524
1550
|
drainSessionDurability: (sessionId) =>
|
|
1525
1551
|
engine.drainSessionDurability(sessionId),
|
|
1526
1552
|
flushOtel: args.flushOtel,
|
|
@@ -2985,19 +3011,34 @@ async function handleRequest(
|
|
|
2985
3011
|
logger: (line: string) => void
|
|
2986
3012
|
): Promise<void> {
|
|
2987
3013
|
let response: Response;
|
|
3014
|
+
let hostedDeliveryRequest = false;
|
|
2988
3015
|
const method = req.method ?? "GET";
|
|
2989
3016
|
try {
|
|
2990
3017
|
const request = await toFetchRequest(req);
|
|
3018
|
+
hostedDeliveryRequest = request.headers.get(HOSTED_DELIVERY_HEADER) === "1";
|
|
3019
|
+
const requestErrorResponse = (
|
|
3020
|
+
status: number,
|
|
3021
|
+
error: string,
|
|
3022
|
+
message: string
|
|
3023
|
+
) =>
|
|
3024
|
+
hostedDeliveryRequest
|
|
3025
|
+
? hostedGuestErrorResponse({
|
|
3026
|
+
status,
|
|
3027
|
+
error,
|
|
3028
|
+
retry: HOSTED_GUEST_RETRY_TERMINAL,
|
|
3029
|
+
message,
|
|
3030
|
+
})
|
|
3031
|
+
: errorResponse(status, error, message);
|
|
2991
3032
|
const url = new URL(request.url);
|
|
2992
3033
|
const match = router.match(request.method, url.pathname);
|
|
2993
3034
|
if (match === undefined) {
|
|
2994
3035
|
response = router.hasPath(url.pathname)
|
|
2995
|
-
?
|
|
3036
|
+
? requestErrorResponse(
|
|
2996
3037
|
405,
|
|
2997
3038
|
"method_not_allowed",
|
|
2998
3039
|
`${request.method} is not supported on ${url.pathname}.`
|
|
2999
3040
|
)
|
|
3000
|
-
:
|
|
3041
|
+
: requestErrorResponse(
|
|
3001
3042
|
404,
|
|
3002
3043
|
"not_found",
|
|
3003
3044
|
`No route for ${request.method} ${url.pathname}.`
|
|
@@ -3011,7 +3052,7 @@ async function handleRequest(
|
|
|
3011
3052
|
unauthorized = auth === null;
|
|
3012
3053
|
}
|
|
3013
3054
|
response = unauthorized
|
|
3014
|
-
?
|
|
3055
|
+
? requestErrorResponse(
|
|
3015
3056
|
401,
|
|
3016
3057
|
"unauthorized",
|
|
3017
3058
|
"No auth policy admitted this request. In production, configure channel auth (e.g. bearerAuth) — the default localDevStrict() policy only admits loopback callers with a loopback Host."
|
|
@@ -3025,7 +3066,14 @@ async function handleRequest(
|
|
|
3025
3066
|
} catch (error) {
|
|
3026
3067
|
const mapped = mapThrownRequestError(error);
|
|
3027
3068
|
if (mapped !== undefined) {
|
|
3028
|
-
response =
|
|
3069
|
+
response = hostedDeliveryRequest
|
|
3070
|
+
? hostedGuestErrorResponse({
|
|
3071
|
+
status: mapped.status,
|
|
3072
|
+
error: mapped.code,
|
|
3073
|
+
retry: mapped.retry,
|
|
3074
|
+
message: mapped.message,
|
|
3075
|
+
})
|
|
3076
|
+
: errorResponse(mapped.status, mapped.code, mapped.message);
|
|
3029
3077
|
} else {
|
|
3030
3078
|
logger(
|
|
3031
3079
|
`[agent-sdk] request failed: ${
|
|
@@ -3035,13 +3083,18 @@ async function handleRequest(
|
|
|
3035
3083
|
}`
|
|
3036
3084
|
);
|
|
3037
3085
|
const snippet = hostedErrorStackSnippet(error);
|
|
3038
|
-
|
|
3039
|
-
500,
|
|
3040
|
-
"internal_error",
|
|
3086
|
+
const message =
|
|
3041
3087
|
snippet.length > 0
|
|
3042
3088
|
? `Internal server error: ${snippet}`
|
|
3043
|
-
: "Internal server error."
|
|
3044
|
-
|
|
3089
|
+
: "Internal server error.";
|
|
3090
|
+
response = hostedDeliveryRequest
|
|
3091
|
+
? hostedGuestErrorResponse({
|
|
3092
|
+
status: 500,
|
|
3093
|
+
error: "internal_error",
|
|
3094
|
+
retry: HOSTED_GUEST_RETRY_TERMINAL,
|
|
3095
|
+
message,
|
|
3096
|
+
})
|
|
3097
|
+
: errorResponse(500, "internal_error", message);
|
|
3045
3098
|
}
|
|
3046
3099
|
}
|
|
3047
3100
|
|
|
@@ -4744,6 +4744,10 @@ export class SessionEngine {
|
|
|
4744
4744
|
* conversation whose blobs persisted but whose record (which selects the
|
|
4745
4745
|
* conversation to hydrate) never reached the sink. No-op without a
|
|
4746
4746
|
* storage mirror.
|
|
4747
|
+
*
|
|
4748
|
+
* Under a debounced event policy the turn-end flush is a timer, not the
|
|
4749
|
+
* chain's tail, so flush explicitly before waiting on the queue; a no-op
|
|
4750
|
+
* when the chain already flushed.
|
|
4747
4751
|
*/
|
|
4748
4752
|
async drainSessionDurability(sessionId: string): Promise<void> {
|
|
4749
4753
|
if (this.storage === undefined) {
|
|
@@ -4753,6 +4757,7 @@ export class SessionEngine {
|
|
|
4753
4757
|
if (chain !== undefined) {
|
|
4754
4758
|
await chain.catch(() => {});
|
|
4755
4759
|
}
|
|
4760
|
+
this.storage.flushSession(sessionId);
|
|
4756
4761
|
await this.storage.whenIdle();
|
|
4757
4762
|
}
|
|
4758
4763
|
|