@cursor/july 0.1.45 → 0.1.47
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 +4 -2
- package/README.md +3 -1
- package/dist/channels/github/api.d.ts +25 -0
- package/dist/channels/github/api.d.ts.map +1 -1
- package/dist/channels/github/api.js +48 -0
- package/dist/channels/github/cursor-account.d.ts +7 -0
- package/dist/channels/github/cursor-account.d.ts.map +1 -1
- package/dist/channels/github/cursor-account.js +18 -0
- package/dist/channels/github/defaults.d.ts +1 -1
- package/dist/channels/github/defaults.js +1 -1
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +2 -2
- package/dist/channels/github/progress.d.ts +6 -5
- package/dist/channels/github/progress.d.ts.map +1 -1
- package/dist/channels/github/progress.js +58 -16
- package/dist/channels/github/state.d.ts +11 -0
- package/dist/channels/github/state.d.ts.map +1 -1
- package/dist/channels/github/state.js +1 -1
- package/dist/channels/github/types.d.ts +3 -3
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.fHnNXEJ6.js → app.IEXEim5m.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.D7w61u6Y.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.Cf6ghfFY.js → VPLocalSearchBox.78s6nqsH.js} +1 -1
- package/dist/docs/assets/chunks/{theme.B_5-VLe8.js → theme.DroGdz_Q.js} +2 -2
- package/dist/docs/assets/{guides_github.md.DSg0j9qj.js → guides_github.md.B8QKtIpw.js} +7 -6
- package/dist/docs/assets/{guides_github.md.DSg0j9qj.lean.js → guides_github.md.B8QKtIpw.lean.js} +1 -1
- package/dist/docs/assets/{reference_agent-config.md.XRpxctsA.js → reference_agent-config.md.DHkkvcEZ.js} +4 -4
- package/dist/docs/assets/{reference_agent-config.md.XRpxctsA.lean.js → reference_agent-config.md.DHkkvcEZ.lean.js} +1 -1
- package/dist/docs/assets/{reference_hooks.md.4cbRRIIu.js → reference_hooks.md.DJE5DXcT.js} +1 -1
- package/dist/docs/assets/{storage.md.DcGSnIxX.js → storage.md.CJYd5YmN.js} +2 -2
- package/dist/docs/assets/{storage.md.DcGSnIxX.lean.js → storage.md.CJYd5YmN.lean.js} +1 -1
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +3 -3
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +3 -3
- package/dist/docs/guides/github.html +10 -9
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/opentelemetry.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +6 -6
- package/dist/docs/reference/artifacts.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +5 -5
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +5 -5
- package/dist/docs/templates/triage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/auth.d.ts +4 -0
- package/dist/internal/auth.d.ts.map +1 -1
- package/dist/internal/auth.js +7 -0
- package/dist/internal/durable-session-store.d.ts +35 -23
- package/dist/internal/durable-session-store.d.ts.map +1 -1
- package/dist/internal/durable-session-store.js +71 -26
- package/dist/internal/ledger-serving.d.ts +135 -67
- package/dist/internal/ledger-serving.d.ts.map +1 -1
- package/dist/internal/ledger-serving.js +326 -107
- package/dist/internal/sdk-runner.d.ts +21 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +34 -1
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +9 -4
- package/dist/internal/session-engine.d.ts +18 -17
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +60 -27
- package/dist/kv/memory-kv.d.ts +8 -0
- package/dist/kv/memory-kv.d.ts.map +1 -1
- package/dist/kv/memory-kv.js +8 -0
- package/dist/ledger/janitor.d.ts +60 -0
- package/dist/ledger/janitor.d.ts.map +1 -0
- package/dist/ledger/janitor.js +137 -0
- package/dist/ledger/session-ledger.d.ts +132 -43
- package/dist/ledger/session-ledger.d.ts.map +1 -1
- package/dist/ledger/session-ledger.js +282 -50
- package/dist/ledger.d.ts +9 -8
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +7 -7
- package/dist/playground/assets/{index-Dces8BDp.js → index-BgUHfe3L.js} +32 -32
- package/dist/playground/assets/index-uFaeqVZS.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-backends/cursor-hosted.d.ts +5 -0
- package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/storage-backends/cursor-hosted.js +6 -0
- package/dist/types.d.ts +19 -8
- package/dist/types.d.ts.map +1 -1
- package/docs/guides/github.md +23 -18
- package/docs/reference/agent-config.md +31 -36
- package/docs/reference/hooks.md +1 -1
- package/docs/storage.md +26 -9
- package/package.json +1 -1
- package/src/channels/github/api.ts +122 -0
- package/src/channels/github/cursor-account.ts +23 -0
- package/src/channels/github/defaults.ts +1 -1
- package/src/channels/github/github-channel.ts +5 -1
- package/src/channels/github/progress.ts +84 -19
- package/src/channels/github/state.ts +14 -0
- package/src/channels/github/types.ts +3 -3
- package/src/index.ts +1 -0
- package/src/internal/auth.ts +10 -0
- package/src/internal/durable-session-store.ts +66 -27
- package/src/internal/ledger-serving.ts +439 -148
- package/src/internal/sdk-runner.ts +46 -1
- package/src/internal/server.ts +17 -3
- package/src/internal/session-engine.ts +95 -37
- package/src/kv/memory-kv.ts +8 -0
- package/src/ledger/janitor.ts +193 -0
- package/src/ledger/session-ledger.ts +372 -68
- package/src/ledger.ts +21 -6
- package/src/storage-backends/cursor-hosted.ts +17 -2
- package/src/types.ts +19 -8
- package/dist/docs/assets/chunks/@localSearchIndexroot.BMT0xM4I.js +0 -1
- package/dist/playground/assets/index-CZH9EFvO.css +0 -1
- /package/dist/docs/assets/{reference_hooks.md.4cbRRIIu.lean.js → reference_hooks.md.DJE5DXcT.lean.js} +0 -0
|
@@ -1,24 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ledger-mode serving seam (`serving: "ledger"` on `defineAgent`):
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* it completes, so cross-pod mutual exclusion, generation fencing, and
|
|
6
|
-
* the committed head sequence are real.
|
|
2
|
+
* Ledger-mode serving seam (`serving: "ledger"` on `defineAgent`): an event
|
|
3
|
+
* pump that routes every engine `send` through the KV session ledger
|
|
4
|
+
* instead of starting turns directly.
|
|
7
5
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
6
|
+
* Delivery: ensureSession (continuation key, as today) → principal gate
|
|
7
|
+
* (an existing session rejects a sender that is not its principal BEFORE
|
|
8
|
+
* anything is parked; mirrors the resident `samePrincipal` check) →
|
|
9
|
+
* appendEvent (payload + CAS — the source ack point moves after this) →
|
|
10
|
+
* claim → drain → run the drained turns through the resident engine path →
|
|
11
|
+
* commit (heartbeats while turns run) → repeat while the commit reports
|
|
12
|
+
* pending. A deliver that finds the lease held walks away (the holder's
|
|
13
|
+
* commit sees the append); the janitor recovers sessions whose holder
|
|
14
|
+
* died. A foreign-principal envelope that reaches the mailbox anyway is
|
|
15
|
+
* consumed and committed away at drain (warn log), never replayed.
|
|
17
16
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
17
|
+
* v1 scope: the mailbox envelope carries `message`, `auth`, `title`,
|
|
18
|
+
* `state`, `mode`, `purpose`, and the isolation-relevant per-send
|
|
19
|
+
* overrides `workspaceDir` and `cloud` — the two options
|
|
20
|
+
* `resolveSessionRuntime` and harness-cwd selection read. Dropping
|
|
21
|
+
* those is a sandbox escape, not a feature gap: without them a drained
|
|
22
|
+
* GitHub/Slack turn falls back to the local runtime with the default
|
|
23
|
+
* harness cwd, so untrusted channel content drives tools on the serve
|
|
24
|
+
* host instead of the cloud VM / channel worktree. `coalesceSourceTs`
|
|
25
|
+
* (when present) is used as the drain-ordering `sourceTs`. Deferred
|
|
26
|
+
* (documented, not silently faked — deliver logs a warning naming what
|
|
27
|
+
* it dropped): `workspaceFiles` and per-turn `images` (content
|
|
28
|
+
* injection, and their base64 payloads can blow the mailbox blob past
|
|
29
|
+
* hosted KV value caps), `sdkAgentId` seeding, and `admission`
|
|
30
|
+
* semantics (both `"preempt"` and Slack `"coalesce"` nudge bookkeeping
|
|
31
|
+
* — ledger draining already serializes turns, but nothing is
|
|
32
|
+
* interrupted or coalesced through the mailbox).
|
|
22
33
|
*/
|
|
23
34
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
24
35
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
@@ -30,77 +41,277 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
30
41
|
});
|
|
31
42
|
};
|
|
32
43
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
33
|
-
import {
|
|
44
|
+
import { createLedgerJanitor } from "../ledger/janitor.js";
|
|
45
|
+
import { HEARTBEAT_INTERVAL_MS, LedgerFencedError, } from "../ledger/session-ledger.js";
|
|
46
|
+
import { SessionAuthError, samePrincipal } from "./auth.js";
|
|
47
|
+
import { describeError } from "./describe-error.js";
|
|
34
48
|
import { newContinuationKey } from "./ids.js";
|
|
35
|
-
/** Wait between claim retries while another holder has the lease. */
|
|
36
|
-
export const CLAIM_RETRY_DELAY_MS = 250;
|
|
37
|
-
/**
|
|
38
|
-
* Give up waiting for the lease after this long — two full lease TTLs,
|
|
39
|
-
* so a dead holder's expiry (~30s) is always survived with margin.
|
|
40
|
-
*/
|
|
41
|
-
export const CLAIM_WAIT_TIMEOUT_MS = 2 * LEASE_TTL_MS;
|
|
42
49
|
export class LedgerServing {
|
|
43
50
|
constructor(options) {
|
|
44
|
-
var _a, _b, _c
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.chains = new Map();
|
|
51
|
+
var _a, _b, _c;
|
|
52
|
+
/** One pump per ledger session — the in-process face of the lease. */
|
|
53
|
+
this.pumps = new Map();
|
|
54
|
+
/** Fire-and-forget re-pump checks, awaited on close. */
|
|
55
|
+
this.background = new Set();
|
|
50
56
|
this.ledger = options.ledger;
|
|
51
57
|
this.deliverTurn = options.deliverTurn;
|
|
58
|
+
this.findSession = options.findSession;
|
|
52
59
|
this.logger = (_a = options.logger) !== null && _a !== void 0 ? _a : (() => { });
|
|
53
60
|
this.heartbeatIntervalMs = (_b = options.heartbeatIntervalMs) !== null && _b !== void 0 ? _b : HEARTBEAT_INTERVAL_MS;
|
|
54
|
-
this.
|
|
55
|
-
this.
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
this.clock = (_c = options.clock) !== null && _c !== void 0 ? _c : Date.now;
|
|
62
|
+
this.janitor = createLedgerJanitor(Object.assign(Object.assign({ ledger: options.ledger, onSession: (claimed) => this.serveClaimed(claimed),
|
|
63
|
+
// Consumed mailbox blobs (full message envelopes) would otherwise
|
|
64
|
+
// accumulate forever; the default TTL (1h) is far beyond any lease
|
|
65
|
+
// or turn horizon, so only truly dead blobs are collected.
|
|
66
|
+
orphanSweep: {} }, (options.janitorIntervalMs === undefined
|
|
67
|
+
? {}
|
|
68
|
+
: { intervalMs: options.janitorIntervalMs })), { onError: (error, sessionId) => {
|
|
69
|
+
this.logger(`[ledger] janitor error${sessionId === undefined ? "" : ` (session ${sessionId})`}: ${describeError(error)}`);
|
|
70
|
+
}, clock: this.clock }));
|
|
58
71
|
}
|
|
59
|
-
/**
|
|
72
|
+
/** Start the janitor loop (recovers expired-lease sessions). */
|
|
73
|
+
start() {
|
|
74
|
+
this.janitor.start();
|
|
75
|
+
}
|
|
76
|
+
/** Stop the janitor and settle in-flight pumps and re-pump checks. */
|
|
60
77
|
close() {
|
|
61
78
|
return __awaiter(this, void 0, void 0, function* () {
|
|
62
|
-
|
|
79
|
+
// Awaiting stop() matters: an in-flight janitor pass can claim a
|
|
80
|
+
// session and register a pump after this method snapshots the maps.
|
|
81
|
+
yield this.janitor.stop();
|
|
82
|
+
yield Promise.allSettled([...this.pumps.values()]);
|
|
83
|
+
yield Promise.allSettled([...this.background]);
|
|
63
84
|
});
|
|
64
85
|
}
|
|
65
86
|
/**
|
|
66
|
-
* Ledger-mode `send`:
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
87
|
+
* Ledger-mode `send`: append the message to the session mailbox, then
|
|
88
|
+
* serve pending events (or walk away if another holder is already
|
|
89
|
+
* serving them). Resolves with the engine session handle once one
|
|
90
|
+
* exists — the ack point is the durable append, not turn completion —
|
|
91
|
+
* so HTTP create/follow-up can return a session id while the holder is
|
|
92
|
+
* still in flight. When this process wins the claim, the pump keeps
|
|
93
|
+
* draining in the background; callers that need the turn to finish use
|
|
94
|
+
* `waitForCompletion` (and the lease releases on the pump's commit).
|
|
95
|
+
* When another holder is serving the session and no engine session
|
|
96
|
+
* exists on this pod, the delivery is still a success — the event is
|
|
97
|
+
* durably parked and the holder's commit (or the janitor) drains it —
|
|
98
|
+
* so the resolved handle is a parked stand-in carrying the durable
|
|
99
|
+
* session identity.
|
|
70
100
|
*/
|
|
71
101
|
deliver(channelId, message, options) {
|
|
72
102
|
return __awaiter(this, void 0, void 0, function* () {
|
|
73
|
-
var _a;
|
|
103
|
+
var _a, _b, _c;
|
|
74
104
|
const continuationToken = (_a = options.continuationToken) !== null && _a !== void 0 ? _a : newContinuationKey();
|
|
105
|
+
this.warnOnDroppedOptions(channelId, options);
|
|
75
106
|
const ensured = yield this.ledger.ensureSession(`${channelId}/${continuationToken}`);
|
|
76
|
-
|
|
107
|
+
const ledgerSessionId = ensured.session.sessionId;
|
|
108
|
+
// Principal gate BEFORE the durable append: once an envelope is
|
|
109
|
+
// parked it WILL be drained, so a foreign sender must be rejected
|
|
110
|
+
// here — not on the resident path after the mailbox write. A new
|
|
111
|
+
// ledger session has no owner yet: the creating sender becomes it,
|
|
112
|
+
// exactly as on resident session creation.
|
|
113
|
+
if (!ensured.created) {
|
|
114
|
+
const existing = yield this.findSession(channelId, continuationToken);
|
|
115
|
+
if (existing !== undefined) {
|
|
116
|
+
if (!samePrincipal(existing.info.auth, (_b = options.auth) !== null && _b !== void 0 ? _b : null)) {
|
|
117
|
+
throw new SessionAuthError(existing.id);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// No handle: the engine session does not exist yet (the first
|
|
121
|
+
// parked event has not run — the lease holder creates it) or a
|
|
122
|
+
// cross-pod restore transiently failed. Park rather than fail
|
|
123
|
+
// closed: rejecting here would bounce the legitimate owner racing
|
|
124
|
+
// the session's first turn. This is not fail-open — the resident
|
|
125
|
+
// path re-checks the principal when the event drains, and the
|
|
126
|
+
// drain treats a SessionAuthError as consume-and-commit, so a
|
|
127
|
+
// foreign envelope that parks through this window costs one warn
|
|
128
|
+
// line and never actuates, bricks, or replays the session.
|
|
129
|
+
}
|
|
130
|
+
const envelope = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ channelId,
|
|
131
|
+
continuationToken,
|
|
132
|
+
message, auth: (_c = options.auth) !== null && _c !== void 0 ? _c : null }, (options.title === undefined ? {} : { title: options.title })), (options.state === undefined ? {} : { state: options.state })), (options.mode === undefined ? {} : { mode: options.mode })), (options.purpose === undefined ? {} : { purpose: options.purpose })), (options.workspaceDir === undefined
|
|
133
|
+
? {}
|
|
134
|
+
: { workspaceDir: options.workspaceDir })), (options.cloud === undefined ? {} : { cloud: options.cloud }));
|
|
135
|
+
yield this.ledger.appendEvent(ledgerSessionId, {
|
|
136
|
+
eventId: `evt_${randomBytes(12).toString("hex")}`,
|
|
137
|
+
envelope: envelope,
|
|
138
|
+
sourceTs: this.resolveSourceTs(options),
|
|
139
|
+
});
|
|
140
|
+
// Ack point: the event is durable (mailbox blob + ledger pending).
|
|
141
|
+
// Do not await the pump: awaiting turns here would block HTTP create
|
|
142
|
+
// until the first turn committed, so a client could never park a
|
|
143
|
+
// mid-turn follow-up and a second pod would see a released lease.
|
|
144
|
+
const serving = this.servePending(ledgerSessionId);
|
|
145
|
+
return this.resolveDeliverHandle({
|
|
77
146
|
channelId,
|
|
78
147
|
continuationToken,
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
148
|
+
ledgerSessionId,
|
|
149
|
+
envelope,
|
|
150
|
+
created: ensured.created,
|
|
151
|
+
serving,
|
|
152
|
+
});
|
|
82
153
|
});
|
|
83
154
|
}
|
|
84
|
-
/**
|
|
85
|
-
|
|
155
|
+
/**
|
|
156
|
+
* The stand-in handle for a delivery whose event is parked while
|
|
157
|
+
* another holder serves the session: real durable identity, no local
|
|
158
|
+
* turn. `waitForCompletion` resolves immediately — the parked event's
|
|
159
|
+
* turn runs on the holder, so there is nothing local to await (the
|
|
160
|
+
* same contract as a coalesced send, whose outcome is not its own
|
|
161
|
+
* message's turn). The event stream and re-keying live wherever the
|
|
162
|
+
* turn runs, so both fail loudly here.
|
|
163
|
+
*/
|
|
164
|
+
parkedSession(ledgerSessionId, envelope, isNew) {
|
|
86
165
|
var _a;
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
.
|
|
93
|
-
|
|
94
|
-
|
|
166
|
+
const unavailable = (what) => new Error(`ledger serving: ${what} is unavailable for session ${ledgerSessionId}: the parked event runs on the session's current lease holder, not this process`);
|
|
167
|
+
return {
|
|
168
|
+
id: ledgerSessionId,
|
|
169
|
+
continuationToken: envelope.continuationToken,
|
|
170
|
+
isNew,
|
|
171
|
+
info: Object.assign(Object.assign(Object.assign({ id: ledgerSessionId, channelId: envelope.channelId, mode: (_a = envelope.mode) !== null && _a !== void 0 ? _a : "chat", auth: envelope.auth }, (envelope.title === undefined ? {} : { title: envelope.title })), (envelope.purpose === undefined
|
|
172
|
+
? {}
|
|
173
|
+
: { purpose: envelope.purpose })), { continuationKey: envelope.continuationToken }),
|
|
174
|
+
getEventStream: () => Promise.reject(unavailable("the event stream")),
|
|
175
|
+
waitForCompletion: () => __awaiter(this, void 0, void 0, function* () { return ({ status: "finished" }); }),
|
|
176
|
+
setContinuationToken: () => Promise.reject(unavailable("re-keying the continuation token")),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Return a handle as soon as the engine session exists, without waiting
|
|
181
|
+
* for the pump to commit. If the pump settles without creating a
|
|
182
|
+
* session (walk-away, or a throw before admit), surface that: rethrow
|
|
183
|
+
* a failed claimant turn, otherwise the parked stand-in.
|
|
184
|
+
*/
|
|
185
|
+
resolveDeliverHandle(args) {
|
|
186
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
187
|
+
const { channelId, continuationToken, ledgerSessionId, envelope, created, serving, } = args;
|
|
188
|
+
for (;;) {
|
|
189
|
+
const handle = yield this.findSession(channelId, continuationToken);
|
|
190
|
+
if (handle !== undefined) {
|
|
191
|
+
return handle;
|
|
192
|
+
}
|
|
193
|
+
const pump = this.pumps.get(ledgerSessionId);
|
|
194
|
+
if (pump !== undefined) {
|
|
195
|
+
yield Promise.race([
|
|
196
|
+
new Promise((resolve) => {
|
|
197
|
+
setTimeout(resolve, 15);
|
|
198
|
+
}),
|
|
199
|
+
pump.then(() => undefined, () => undefined),
|
|
200
|
+
]);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const createdLate = yield this.findSession(channelId, continuationToken);
|
|
204
|
+
if (createdLate !== undefined) {
|
|
205
|
+
return createdLate;
|
|
206
|
+
}
|
|
207
|
+
yield serving;
|
|
208
|
+
this.logger(`[ledger] deliver on channel "${channelId}": event parked for session ${ledgerSessionId} (continuation ${continuationToken}); another holder is serving it and no local session exists — its commit (or the janitor) drains the event`);
|
|
209
|
+
return this.parkedSession(ledgerSessionId, envelope, created);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Drain ordering: prefer the real source time over this pod's receipt
|
|
215
|
+
* time. `coalesceSourceTs` is a Slack-style seconds string
|
|
216
|
+
* (`"1712345678.000100"`); normalize sub-epoch-ms values to ms so they
|
|
217
|
+
* stay comparable with the `Date.now()` fallback.
|
|
218
|
+
*/
|
|
219
|
+
resolveSourceTs(options) {
|
|
220
|
+
if (options.coalesceSourceTs !== undefined) {
|
|
221
|
+
const parsed = Number(options.coalesceSourceTs);
|
|
222
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
223
|
+
return parsed < 1e11 ? Math.round(parsed * 1000) : parsed;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return this.clock();
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* One warning per delivery naming the send options the mailbox does not
|
|
230
|
+
* replay — better a loud line than an author silently losing images or
|
|
231
|
+
* admission semantics on a ledger-mode agent.
|
|
232
|
+
*/
|
|
233
|
+
warnOnDroppedOptions(channelId, options) {
|
|
234
|
+
const dropped = ["images", "sdkAgentId", "admission", "workspaceFiles"].filter((name) => options[name] !== undefined);
|
|
235
|
+
if (dropped.length > 0) {
|
|
236
|
+
this.logger(`[ledger] deliver on channel "${channelId}": dropping send options not replayed through the mailbox: ${dropped.join(", ")}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Serve a session's pending events. When a pump is already running the
|
|
241
|
+
* caller walks away (the holder's commit re-reads pending) — except
|
|
242
|
+
* that the holder's *final* commit may have released before this
|
|
243
|
+
* append landed, so a re-check is chained behind it. Deliver never
|
|
244
|
+
* awaits this promise for its full lifetime: awaiting a new pump would
|
|
245
|
+
* block HTTP until the turn committed, and awaiting a running pump
|
|
246
|
+
* would deadlock a send issued from inside one of its turns.
|
|
247
|
+
*/
|
|
248
|
+
servePending(ledgerSessionId) {
|
|
249
|
+
const running = this.pumps.get(ledgerSessionId);
|
|
250
|
+
if (running !== undefined) {
|
|
251
|
+
const recheck = running
|
|
252
|
+
.catch(() => { })
|
|
253
|
+
.then(() => this.repumpIfPending(ledgerSessionId))
|
|
254
|
+
.catch(() => { });
|
|
255
|
+
this.background.add(recheck);
|
|
256
|
+
void recheck.finally(() => {
|
|
257
|
+
this.background.delete(recheck);
|
|
258
|
+
});
|
|
259
|
+
return Promise.resolve();
|
|
260
|
+
}
|
|
261
|
+
return this.track(ledgerSessionId, () => this.runPump(ledgerSessionId));
|
|
262
|
+
}
|
|
263
|
+
/** Claim-or-defer entry used after an append and after a pump settles. */
|
|
264
|
+
runPump(ledgerSessionId) {
|
|
265
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
266
|
+
const claim = yield this.ledger.claim(ledgerSessionId);
|
|
267
|
+
if (!claim.claimed) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
yield this.runClaimedTurns(ledgerSessionId, claim.lease);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
/** Janitor hand-off: the claim already happened, just run the turns. */
|
|
274
|
+
serveClaimed(claimed) {
|
|
275
|
+
return this.track(claimed.sessionId, () => this.runClaimedTurns(claimed.sessionId, claimed.lease));
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Re-pump after the previous holder settled, covering the release window
|
|
279
|
+
* where an append landed after that holder's final commit.
|
|
280
|
+
*/
|
|
281
|
+
repumpIfPending(ledgerSessionId) {
|
|
282
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
283
|
+
if (this.pumps.has(ledgerSessionId)) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const snapshot = yield this.ledger.readLedger(ledgerSessionId);
|
|
287
|
+
if (snapshot === undefined || snapshot.pending.length === 0) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (snapshot.lease !== null && snapshot.lease.expiresAt > this.clock()) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
yield this.track(ledgerSessionId, () => this.runPump(ledgerSessionId));
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/** Register a pump in the per-session map for its lifetime. */
|
|
297
|
+
track(ledgerSessionId, run) {
|
|
298
|
+
const tracked = run().finally(() => {
|
|
299
|
+
if (this.pumps.get(ledgerSessionId) === tracked) {
|
|
300
|
+
this.pumps.delete(ledgerSessionId);
|
|
95
301
|
}
|
|
96
302
|
});
|
|
97
|
-
|
|
303
|
+
this.pumps.set(ledgerSessionId, tracked);
|
|
304
|
+
return tracked;
|
|
98
305
|
}
|
|
99
|
-
/**
|
|
100
|
-
|
|
306
|
+
/**
|
|
307
|
+
* The drain → turn → commit loop for a held lease. Heartbeats extend the
|
|
308
|
+
* lease while turns run; a fenced heartbeat (or fenced commit) means a
|
|
309
|
+
* new holder took over — discard without committing; the new holder
|
|
310
|
+
* re-drains (at-least-once actuation, exactly-once ledger apply).
|
|
311
|
+
*/
|
|
312
|
+
runClaimedTurns(ledgerSessionId, lease) {
|
|
101
313
|
return __awaiter(this, void 0, void 0, function* () {
|
|
102
314
|
var _a;
|
|
103
|
-
const lease = yield this.claimWithWait(ledgerSessionId);
|
|
104
315
|
let fenced = false;
|
|
105
316
|
const heartbeat = setInterval(() => {
|
|
106
317
|
this.ledger.heartbeat(ledgerSessionId, lease.generation).then((result) => {
|
|
@@ -113,60 +324,68 @@ export class LedgerServing {
|
|
|
113
324
|
}, this.heartbeatIntervalMs);
|
|
114
325
|
(_a = heartbeat.unref) === null || _a === void 0 ? void 0 : _a.call(heartbeat);
|
|
115
326
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
327
|
+
for (;;) {
|
|
328
|
+
if (fenced) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const { head, events } = yield this.ledger.drain(ledgerSessionId);
|
|
332
|
+
if (events.length === 0) {
|
|
333
|
+
yield this.ledger.release(ledgerSessionId, lease.generation);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
for (const event of events) {
|
|
337
|
+
if (fenced) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const envelope = event.envelope;
|
|
341
|
+
try {
|
|
342
|
+
const session = yield this.deliverTurn(envelope);
|
|
343
|
+
yield session.waitForCompletion();
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
if (!(error instanceof SessionAuthError)) {
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
// Defense in depth behind deliver's principal gate: a parked
|
|
350
|
+
// envelope from a foreign principal (gate raced the session's
|
|
351
|
+
// first turn, or a pre-gate build wrote it) is consumed and
|
|
352
|
+
// committed away like any drained event. Aborting the batch
|
|
353
|
+
// instead would replay the already-actuated turns before it
|
|
354
|
+
// and re-throw on every future drain — one poison event must
|
|
355
|
+
// cost a log line, never a stuck session.
|
|
356
|
+
this.logger(`[ledger] session ${ledgerSessionId}: discarding parked event ${event.eventId} — its sender is not the session's principal (${describeError(error)})`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const commit = yield this.ledger.commit(ledgerSessionId, {
|
|
360
|
+
drainedEventIds: events.map((event) => event.eventId),
|
|
361
|
+
generation: lease.generation,
|
|
362
|
+
expectedHead: head,
|
|
363
|
+
commitId: randomUUID(),
|
|
364
|
+
});
|
|
365
|
+
if (commit.leaseReleased) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
129
368
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
this.logger(`[ledger] session ${ledgerSessionId} fenced mid-turn (generation ${lease.generation}); discarding
|
|
134
|
-
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
if (error instanceof LedgerFencedError) {
|
|
372
|
+
this.logger(`[ledger] session ${ledgerSessionId} fenced mid-turn (generation ${lease.generation}); discarding — the new holder re-drains`);
|
|
373
|
+
return;
|
|
135
374
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
375
|
+
// The turn never committed; free the session for the next
|
|
376
|
+
// append-driven claim instead of making it wait out the lease TTL
|
|
377
|
+
// (and the janitor cadence). Best-effort: a fenced holder's
|
|
378
|
+
// release is a no-op, and the events stay pending either way.
|
|
379
|
+
yield this.ledger
|
|
380
|
+
.release(ledgerSessionId, lease.generation)
|
|
381
|
+
.catch(() => { });
|
|
382
|
+
throw error;
|
|
142
383
|
}
|
|
143
384
|
finally {
|
|
144
385
|
clearInterval(heartbeat);
|
|
145
386
|
}
|
|
146
387
|
});
|
|
147
388
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Claim the lease, waiting out another holder with bounded retries.
|
|
150
|
-
* The common waits are short: our own pod's deliveries never reach
|
|
151
|
-
* here concurrently (the in-process chain serializes them), so a held
|
|
152
|
-
* lease means another pod is serving — usually for one turn — or died
|
|
153
|
-
* (expiry unblocks us at ~30s).
|
|
154
|
-
*/
|
|
155
|
-
claimWithWait(ledgerSessionId) {
|
|
156
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
157
|
-
const start = this.clock();
|
|
158
|
-
for (;;) {
|
|
159
|
-
const claim = yield this.ledger.claim(ledgerSessionId);
|
|
160
|
-
if (claim.claimed) {
|
|
161
|
-
return { generation: claim.lease.generation, head: claim.head };
|
|
162
|
-
}
|
|
163
|
-
if (this.clock() - start >= this.claimWaitTimeoutMs) {
|
|
164
|
-
throw new Error(`ledger serving: session ${ledgerSessionId} is still leased by ${claim.holder} after ${this.claimWaitTimeoutMs}ms; giving up on this delivery`);
|
|
165
|
-
}
|
|
166
|
-
yield this.sleep(this.claimRetryDelayMs);
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
389
|
}
|
|
171
390
|
/** Mint the lease holder id for this process. */
|
|
172
391
|
export function newLedgerPodId() {
|
|
@@ -32,6 +32,22 @@ export type PrewarmOutcome = {
|
|
|
32
32
|
readonly warmed: false;
|
|
33
33
|
readonly reason: string;
|
|
34
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* How long a non-`--dev` serve holds workspace scans (rules, skills,
|
|
37
|
+
* `AGENTS.md`, ignore files). The SDK default is 20s for an editor whose
|
|
38
|
+
* files move under it; agent-serve checkouts only change on deploy, so a
|
|
39
|
+
* missed mid-process edit is the right trade. `CURSOR_RIPWALK_CACHE_TTL_MS`
|
|
40
|
+
* still wins when set. `--dev` leaves the 20s default so skill edits show up.
|
|
41
|
+
*/
|
|
42
|
+
export declare const SERVE_WORKSPACE_SCAN_CACHE_TTL_MS: number;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the SDK workspace-scan TTL for this serve. `undefined` means "do
|
|
45
|
+
* not call `Cursor.configure`" and leave the SDK at its 20s editor default.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveServeWorkspaceScanCacheTtlMs(args: {
|
|
48
|
+
dev: boolean;
|
|
49
|
+
env?: NodeJS.ProcessEnv;
|
|
50
|
+
}): number | undefined;
|
|
35
51
|
export interface CursorSdkRunnerOptions {
|
|
36
52
|
/** Cursor API key. Defaults to the `CURSOR_API_KEY` environment variable. */
|
|
37
53
|
apiKey?: string;
|
|
@@ -43,6 +59,11 @@ export interface CursorSdkRunnerOptions {
|
|
|
43
59
|
store?: LocalAgentStore;
|
|
44
60
|
/** Diagnostics for degraded paths (resume-miss fallback). */
|
|
45
61
|
logger?: (line: string) => void;
|
|
62
|
+
/**
|
|
63
|
+
* Hold workspace scans this long (ms). Applied via `Cursor.configure` so
|
|
64
|
+
* each new local executor reuses the walk. Omit to keep the SDK 20s default.
|
|
65
|
+
*/
|
|
66
|
+
workspaceScanCacheTtlMs?: number;
|
|
46
67
|
}
|
|
47
68
|
/**
|
|
48
69
|
* The backend's `NotLoggedIn` copy ("Authentication error" title, "try
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdk-runner.d.ts","sourceRoot":"","sources":["../../src/internal/sdk-runner.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,
|
|
1
|
+
{"version":3,"file":"sdk-runner.d.ts","sourceRoot":"","sources":["../../src/internal/sdk-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,9 +15,36 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
15
15
|
});
|
|
16
16
|
};
|
|
17
17
|
import { mkdir } from "node:fs/promises";
|
|
18
|
-
import { Agent, createAgentPlatform, JsonlLocalAgentStore, } from "@cursor/sdk";
|
|
18
|
+
import { Agent, Cursor, createAgentPlatform, JsonlLocalAgentStore, } from "@cursor/sdk";
|
|
19
19
|
import { DEFAULT_MODEL, } from "../types.js";
|
|
20
20
|
import { mapSdkTokenUsage, usageTokenSum } from "./cloud-turn-cost.js";
|
|
21
|
+
/**
|
|
22
|
+
* How long a non-`--dev` serve holds workspace scans (rules, skills,
|
|
23
|
+
* `AGENTS.md`, ignore files). The SDK default is 20s for an editor whose
|
|
24
|
+
* files move under it; agent-serve checkouts only change on deploy, so a
|
|
25
|
+
* missed mid-process edit is the right trade. `CURSOR_RIPWALK_CACHE_TTL_MS`
|
|
26
|
+
* still wins when set. `--dev` leaves the 20s default so skill edits show up.
|
|
27
|
+
*/
|
|
28
|
+
export const SERVE_WORKSPACE_SCAN_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the SDK workspace-scan TTL for this serve. `undefined` means "do
|
|
31
|
+
* not call `Cursor.configure`" and leave the SDK at its 20s editor default.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveServeWorkspaceScanCacheTtlMs(args) {
|
|
34
|
+
var _a;
|
|
35
|
+
var _b;
|
|
36
|
+
const raw = (_a = ((_b = args.env) !== null && _b !== void 0 ? _b : process.env).CURSOR_RIPWALK_CACHE_TTL_MS) === null || _a === void 0 ? void 0 : _a.trim();
|
|
37
|
+
if (raw !== undefined && raw !== "") {
|
|
38
|
+
const parsed = Number(raw);
|
|
39
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
40
|
+
return parsed;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (args.dev) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return SERVE_WORKSPACE_SCAN_CACHE_TTL_MS;
|
|
47
|
+
}
|
|
21
48
|
/**
|
|
22
49
|
* How long one SDK agent handle (and the Connect transport inside it) may be
|
|
23
50
|
* reused before the runner retires it.
|
|
@@ -52,6 +79,12 @@ export class CursorSdkRunner {
|
|
|
52
79
|
this.agents = new Map();
|
|
53
80
|
this.stores = new Map();
|
|
54
81
|
this.prewarmReleases = [];
|
|
82
|
+
if (options.workspaceScanCacheTtlMs !== undefined) {
|
|
83
|
+
Cursor.configure({
|
|
84
|
+
local: { workspaceScanCacheTtlMs: options.workspaceScanCacheTtlMs },
|
|
85
|
+
});
|
|
86
|
+
this.log(`[agent-sdk] workspace scan cache ttl=${options.workspaceScanCacheTtlMs}ms`);
|
|
87
|
+
}
|
|
55
88
|
}
|
|
56
89
|
runTurn(request) {
|
|
57
90
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/internal/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/internal/server.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA6BH,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAOhB,YAAY,EACb,MAAM,aAAa,CAAC;AAyGrB,OAAO,EACL,KAAK,WAAW,EAGjB,MAAM,iBAAiB,CAAC;AAiBzB,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;CACvB;AAaD,6EAA6E;AAC7E,MAAM,WAAW,0BAA0B;IACzC,oEAAoE;IACpE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,UAAU,EAAE,EACpB,OAAO,GAAE,YAAY,GAAG,0BAA+B,GACtD,OAAO,CAAC,gBAAgB,CAAC,CAwvB3B"}
|
package/dist/internal/server.js
CHANGED
|
@@ -35,7 +35,7 @@ import { Buffer } from "node:buffer";
|
|
|
35
35
|
import { once } from "node:events";
|
|
36
36
|
import { createServer, } from "node:http";
|
|
37
37
|
import { join } from "node:path";
|
|
38
|
-
import { createCursorAccountGithubTokenResolver, getGithubCursorAccountRuntime, parseCursorGithubRepoScope, resolveCursorGithubPermissions, resolveCursorGithubProxyUrl, } from "../channels/github/cursor-account.js";
|
|
38
|
+
import { createCursorAccountGithubTokenResolver, cursorGithubPermissionsForChannel, getGithubCursorAccountRuntime, parseCursorGithubRepoScope, resolveCursorGithubPermissions, resolveCursorGithubProxyUrl, } from "../channels/github/cursor-account.js";
|
|
39
39
|
import { findGitHubChannelTargets } from "../channels/github/forward.js";
|
|
40
40
|
import { dispatchCursorSlackEnvelope, getSlackCursorAccountRuntime, } from "../channels/slack/cursor-account.js";
|
|
41
41
|
import { assertSlackCursorAgentName } from "../channels/slack/types.js";
|
|
@@ -72,7 +72,7 @@ import { ReminderRunner, ReminderValidationError, UnknownReminderError, } from "
|
|
|
72
72
|
import { assertAccountConnectionsNotAnonymous, resolveMountConnections, validateMountConnectionsConfig, } from "./resolved-connections.js";
|
|
73
73
|
import { Router } from "./router.js";
|
|
74
74
|
import { ScheduleRunner, UnknownScheduleError } from "./schedule-runner.js";
|
|
75
|
-
import { CursorSdkRunner } from "./sdk-runner.js";
|
|
75
|
+
import { CursorSdkRunner, resolveServeWorkspaceScanCacheTtlMs, } from "./sdk-runner.js";
|
|
76
76
|
import { SessionAuthError, SessionBusyError, SessionEngine, ToolInputError, ToolNotDirectlyCallableError, UnknownToolError, } from "./session-engine.js";
|
|
77
77
|
import { sessionUrlsForRequest } from "./session-urls.js";
|
|
78
78
|
const MAX_BODY_BYTES = 32 * 1024 * 1024;
|
|
@@ -212,7 +212,12 @@ export function startServer(mounts_1) {
|
|
|
212
212
|
if (options.allowAnonymous === true) {
|
|
213
213
|
logger("[agent-sdk] warning: --allow-anonymous admits every HTTP caller as the same principal; use only on a trusted network (e.g. Tailscale) and prefer --bearer-token for shared hosts");
|
|
214
214
|
}
|
|
215
|
-
const
|
|
215
|
+
const workspaceScanCacheTtlMs = resolveServeWorkspaceScanCacheTtlMs({
|
|
216
|
+
dev,
|
|
217
|
+
});
|
|
218
|
+
const runner = (_l = options.runner) !== null && _l !== void 0 ? _l : new CursorSdkRunner(Object.assign(Object.assign(Object.assign({}, (accountKey === undefined ? {} : { apiKey: accountKey.apiKey })), { logger }), (workspaceScanCacheTtlMs === undefined
|
|
219
|
+
? {}
|
|
220
|
+
: { workspaceScanCacheTtlMs })));
|
|
216
221
|
const router = new Router();
|
|
217
222
|
const runtimes = [];
|
|
218
223
|
const channelStopHooks = [];
|
|
@@ -1450,7 +1455,7 @@ function resolveCursorGithubEventConfig(input) {
|
|
|
1450
1455
|
...((_b = (_a = target.accountRuntime.scope) === null || _a === void 0 ? void 0 : _a.repos) !== null && _b !== void 0 ? _b : []),
|
|
1451
1456
|
]; }),
|
|
1452
1457
|
];
|
|
1453
|
-
const permissions = resolveCursorGithubPermissions(input.targets.map((target) => { var _a;
|
|
1458
|
+
const permissions = resolveCursorGithubPermissions(input.targets.map((target) => { var _a; return cursorGithubPermissionsForChannel(target.accountRuntime.options, (_a = target.accountRuntime.scope) === null || _a === void 0 ? void 0 : _a.permissions); }));
|
|
1454
1459
|
const scope = parseCursorGithubRepoScope(repos, permissions);
|
|
1455
1460
|
return {
|
|
1456
1461
|
cursorEvents: Object.assign(Object.assign({}, input.configured), { repos: [...scope.repos] }),
|