@cursor/july 0.1.10 → 0.1.12
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 -0
- package/dist/bin/agent-serve.js +17 -3
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +2 -1
- package/dist/channels/github/types.d.ts +4 -0
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.js +1 -1
- package/dist/channels/slack/dispatch.d.ts +13 -0
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +36 -11
- package/dist/channels/slack/eval-directive.d.ts +63 -0
- package/dist/channels/slack/eval-directive.d.ts.map +1 -0
- package/dist/channels/slack/eval-directive.js +283 -0
- package/dist/channels/slack/index.d.ts +1 -0
- package/dist/channels/slack/index.d.ts.map +1 -1
- package/dist/channels/slack/index.js +1 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +4 -2
- package/dist/channels/slack/types.d.ts +7 -0
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/connections.d.ts +10 -5
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +10 -5
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +4 -4
- package/dist/docs/assets/{app.CY2buaEE.js → app.Dh_JkJjI.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.Er_R50vY.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.Ix954SFJ.js → VPLocalSearchBox.CptQNLkR.js} +1 -1
- package/dist/docs/assets/chunks/{theme.PDwuJbyi.js → theme.DWflH-LQ.js} +2 -2
- package/dist/docs/assets/{deployment.md.DC5-ImcM.js → deployment.md.TecHo0_2.js} +3 -3
- package/dist/docs/assets/{deployment.md.DC5-ImcM.lean.js → deployment.md.TecHo0_2.lean.js} +1 -1
- package/dist/docs/assets/guides_mcp-oauth.md.DqmEOQY1.js +29 -0
- package/dist/docs/assets/guides_mcp-oauth.md.DqmEOQY1.lean.js +1 -0
- package/dist/docs/assets/{index.md.COiu-1jL.js → index.md.t0TM2Qzz.js} +3 -3
- package/dist/docs/assets/{index.md.COiu-1jL.lean.js → index.md.t0TM2Qzz.lean.js} +1 -1
- package/dist/docs/assets/{reference_cli.md.CjfiV2Q-.js → reference_cli.md.B74NrVzb.js} +3 -3
- package/dist/docs/assets/{reference_cli.md.CjfiV2Q-.lean.js → reference_cli.md.B74NrVzb.lean.js} +1 -1
- package/dist/docs/assets/{reference_connections.md.FbWupyzW.js → reference_connections.md.CsSr3rDz.js} +8 -3
- package/dist/docs/assets/{reference_connections.md.FbWupyzW.lean.js → reference_connections.md.CsSr3rDz.lean.js} +1 -1
- package/dist/docs/assets/reference_prompt.md.BaiweQxE.js +1 -0
- package/dist/docs/assets/reference_prompt.md.BaiweQxE.lean.js +1 -0
- package/dist/docs/assets/{reference_tools.md.CqgJroI0.js → reference_tools.md.BRSDnTbN.js} +16 -3
- package/dist/docs/assets/{reference_tools.md.CqgJroI0.lean.js → reference_tools.md.BRSDnTbN.lean.js} +1 -1
- package/dist/docs/assets/{troubleshooting.md.DkovjS7h.js → troubleshooting.md.CmQkmnzC.js} +1 -1
- package/dist/docs/assets/{troubleshooting.md.DkovjS7h.lean.js → troubleshooting.md.CmQkmnzC.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 +7 -7
- package/dist/docs/evals.html +4 -4
- package/dist/docs/example-agents/approval-buddy.html +4 -4
- package/dist/docs/example-agents/benny.html +4 -4
- package/dist/docs/example-agents/bugbot.html +4 -4
- package/dist/docs/example-agents/codebase-wiki.html +4 -4
- package/dist/docs/example-agents/codeowners-review.html +4 -4
- package/dist/docs/example-agents/concierge.html +4 -4
- package/dist/docs/example-agents/fsd.html +4 -4
- package/dist/docs/example-agents/index.html +4 -4
- package/dist/docs/example-agents/knowledge-base.html +4 -4
- package/dist/docs/example-agents/oncall.html +4 -4
- package/dist/docs/example-agents/security-reviewer.html +4 -4
- package/dist/docs/example-agents/slack-agent.html +4 -4
- package/dist/docs/example-agents/weather-agent.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/github.html +4 -4
- package/dist/docs/guides/human-in-the-loop.html +4 -4
- package/dist/docs/guides/mcp-oauth.html +53 -0
- 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 +6 -6
- package/dist/docs/quickstart.html +4 -4
- package/dist/docs/reference/agent-config.html +4 -4
- package/dist/docs/reference/channels.html +4 -4
- package/dist/docs/reference/cli.html +6 -6
- package/dist/docs/reference/connections.html +12 -7
- package/dist/docs/reference/hooks.html +4 -4
- package/dist/docs/reference/http-api.html +4 -4
- 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 +33 -0
- package/dist/docs/reference/schedules.html +4 -4
- package/dist/docs/reference/sessions.html +4 -4
- package/dist/docs/reference/skills.html +5 -5
- package/dist/docs/reference/subagents.html +4 -4
- package/dist/docs/reference/tools.html +20 -7
- package/dist/docs/scaffolding-agents.html +4 -4
- package/dist/docs/storage.html +4 -4
- package/dist/docs/troubleshooting.html +5 -5
- package/dist/evals.d.ts +39 -0
- package/dist/evals.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/internal/cli-mcp-oauth.d.ts +16 -0
- package/dist/internal/cli-mcp-oauth.d.ts.map +1 -0
- package/dist/internal/cli-mcp-oauth.js +194 -0
- package/dist/internal/cli-mcp.d.ts +4 -1
- package/dist/internal/cli-mcp.d.ts.map +1 -1
- package/dist/internal/cli-mcp.js +19 -1
- package/dist/internal/cursor/account-mcp.d.ts +17 -4
- package/dist/internal/cursor/account-mcp.d.ts.map +1 -1
- package/dist/internal/cursor/account-mcp.js +105 -17
- package/dist/internal/cursor-event-relay.d.ts +2 -0
- package/dist/internal/cursor-event-relay.d.ts.map +1 -1
- package/dist/internal/cursor-event-relay.js +3 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +64 -10
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +1 -0
- package/dist/internal/json-dir-store.js +100 -0
- package/dist/internal/logs-client.js +1 -1
- package/dist/internal/mcp-host.d.ts.map +1 -1
- package/dist/internal/mcp-host.js +49 -26
- package/dist/internal/mcp-oauth.d.ts +88 -0
- package/dist/internal/mcp-oauth.d.ts.map +1 -0
- package/dist/internal/mcp-oauth.js +385 -0
- package/dist/internal/resolved-connections.d.ts.map +1 -1
- package/dist/internal/resolved-connections.js +4 -1
- package/dist/internal/sdk-runner.d.ts +9 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +28 -2
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +13 -5
- package/dist/internal/session-engine.d.ts +7 -9
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +96 -64
- package/dist/internal/sessions-client.d.ts.map +1 -1
- package/dist/internal/sessions-client.js +2 -1
- package/dist/internal/stream-progress.d.ts.map +1 -1
- package/dist/internal/stream-progress.js +8 -4
- package/dist/internal/turn-governor.d.ts +54 -0
- package/dist/internal/turn-governor.d.ts.map +1 -0
- package/dist/internal/turn-governor.js +111 -0
- package/dist/playground/assets/index-1dzluIcW.js +86 -0
- package/dist/playground/assets/index-kZjeJs1v.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/prompt.d.ts +39 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +70 -0
- package/dist/types.d.ts +78 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -0
- package/docs/README.md +4 -0
- package/docs/deployment.md +6 -1
- package/docs/guides/mcp-oauth.md +157 -0
- package/docs/reference/cli.md +28 -0
- package/docs/reference/connections.md +28 -0
- package/docs/reference/prompt.md +47 -0
- package/docs/reference/tools.md +21 -0
- package/docs/troubleshooting.md +11 -0
- package/package.json +9 -2
- package/skills/create-agent/SKILL.md +7 -3
- package/skills/framework-map/SKILL.md +1 -0
- package/skills/mcp-auth/SKILL.md +138 -0
- package/src/bin/agent-serve.ts +20 -3
- package/src/channels/github/github-channel.ts +2 -0
- package/src/channels/github/types.ts +4 -0
- package/src/channels/slack/cursor-account.ts +2 -0
- package/src/channels/slack/dispatch.ts +49 -0
- package/src/channels/slack/eval-directive.ts +348 -0
- package/src/channels/slack/index.ts +1 -0
- package/src/channels/slack/slack-channel.ts +3 -0
- package/src/channels/slack/types.ts +7 -0
- package/src/connections.ts +10 -5
- package/src/evals.ts +43 -0
- package/src/index.ts +3 -0
- package/src/internal/cli-mcp-oauth.ts +254 -0
- package/src/internal/cli-mcp.ts +25 -2
- package/src/internal/cursor/account-mcp.ts +153 -41
- package/src/internal/cursor-event-relay.ts +5 -0
- package/src/internal/discovery.ts +70 -8
- package/src/internal/distribution.ts +1 -0
- package/src/internal/logs-client.ts +1 -1
- package/src/internal/mcp-host.ts +33 -9
- package/src/internal/mcp-oauth.ts +496 -0
- package/src/internal/resolved-connections.ts +6 -1
- package/src/internal/sdk-runner.ts +37 -1
- package/src/internal/server.ts +13 -5
- package/src/internal/session-engine.ts +115 -74
- package/src/internal/sessions-client.ts +2 -1
- package/src/internal/stream-progress.ts +8 -0
- package/src/internal/turn-governor.ts +145 -0
- package/src/prompt.ts +83 -0
- package/src/types.ts +76 -6
- package/dist/channels/slack/post-update-delivery.d.ts +0 -85
- package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.OfDZ5CXg.js +0 -1
- package/dist/internal/persistence-coordinator.d.ts +0 -127
- package/dist/internal/persistence-coordinator.d.ts.map +0 -1
- package/dist/persistence.d.ts +0 -184
- package/dist/persistence.d.ts.map +0 -1
- package/dist/playground/assets/index-C0-QEG4O.js +0 -86
- package/dist/playground/assets/index-z3JY9O2x.css +0 -1
|
@@ -51,6 +51,7 @@ import { assertCloudCanHonorAccountServersFilters } from "./resolved-connections
|
|
|
51
51
|
import { SessionStore } from "./session-store.js";
|
|
52
52
|
import { StorageCoordinator } from "./storage-coordinator.js";
|
|
53
53
|
import { normalizeToolResult, toolCallErrorMessage } from "./tool-result.js";
|
|
54
|
+
import { TurnGovernor } from "./turn-governor.js";
|
|
54
55
|
import { buildAgentsMdContent, isNestedInGitRepo, materializeWorkspace, writeWorkspaceFiles, } from "./workspace.js";
|
|
55
56
|
export const HTTP_CHANNEL_ID = "http";
|
|
56
57
|
export { SCHEDULE_CHANNEL_PREFIX } from "./prompt-context.js";
|
|
@@ -143,6 +144,7 @@ export class SessionEngine {
|
|
|
143
144
|
this.stateRoot = options.stateRoot;
|
|
144
145
|
this.runner = options.runner;
|
|
145
146
|
this.logger = (_a = options.logger) !== null && _a !== void 0 ? _a : (() => { });
|
|
147
|
+
this.turnGovernor = new TurnGovernor(options.project.agent.concurrency);
|
|
146
148
|
this.sessions = new SessionStore(join(options.stateRoot, "sessions"));
|
|
147
149
|
this.nudgeStore = new SlackNudgeStore(join(options.stateRoot, "slack-nudges"));
|
|
148
150
|
this.askDedupe = new SlackAskDedupeStore(join(options.stateRoot, "slack-asks"));
|
|
@@ -186,13 +188,14 @@ export class SessionEngine {
|
|
|
186
188
|
});
|
|
187
189
|
}
|
|
188
190
|
}
|
|
189
|
-
/** Shared host services (MCP / GitHub / Slack / reminders / kv / Slack nudges). */
|
|
191
|
+
/** Shared host services (MCP / GitHub / Slack / reminders / evals / kv / Slack nudges). */
|
|
190
192
|
get host() {
|
|
191
193
|
return {
|
|
192
194
|
mcp: this.mcpHost,
|
|
193
195
|
github: this.platforms.github,
|
|
194
196
|
slack: this.platforms.slack,
|
|
195
197
|
reminders: this.reminderApi,
|
|
198
|
+
evals: this.evalsApi,
|
|
196
199
|
kv: this.kvApi,
|
|
197
200
|
slackNudges: this.slackNudgeHostApi(),
|
|
198
201
|
};
|
|
@@ -212,6 +215,10 @@ export class SessionEngine {
|
|
|
212
215
|
setReminders(api) {
|
|
213
216
|
this.reminderApi = api;
|
|
214
217
|
}
|
|
218
|
+
/** Attach the serve-host playground evals API (called from startServer). */
|
|
219
|
+
setEvals(api) {
|
|
220
|
+
this.evalsApi = api;
|
|
221
|
+
}
|
|
215
222
|
// ==========================================================================
|
|
216
223
|
// Sending messages
|
|
217
224
|
// ==========================================================================
|
|
@@ -520,11 +527,8 @@ export class SessionEngine {
|
|
|
520
527
|
turnId,
|
|
521
528
|
data: { text: message },
|
|
522
529
|
});
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
turnId,
|
|
526
|
-
data: {},
|
|
527
|
-
});
|
|
530
|
+
// `turn.started` is appended by executeTurn once the turn holds a
|
|
531
|
+
// governor slot; saturated engines emit `turn.queued` first.
|
|
528
532
|
const abort = new AbortController();
|
|
529
533
|
this.turnAborts.set(sessionId, abort);
|
|
530
534
|
// Lifecycle promise includes post-turn coalesce drain so waiters and
|
|
@@ -707,54 +711,77 @@ export class SessionEngine {
|
|
|
707
711
|
}
|
|
708
712
|
};
|
|
709
713
|
const mapper = new TurnEventMapper(emit);
|
|
714
|
+
// Take a turn slot before any work happens. When every slot is busy the
|
|
715
|
+
// turn waits FIFO — durable `turn.queued` marks the wait — and a
|
|
716
|
+
// preempting follow-up (or interrupt) resolves the wait as cancelled
|
|
717
|
+
// without the turn ever running.
|
|
718
|
+
const permit = yield this.turnGovernor.acquire({
|
|
719
|
+
sessionId,
|
|
720
|
+
signal,
|
|
721
|
+
onQueued: (position) => {
|
|
722
|
+
var _a;
|
|
723
|
+
const maxRunningTurns = (_a = this.turnGovernor.maxRunningTurns) !== null && _a !== void 0 ? _a : 0;
|
|
724
|
+
emit({ type: "turn.queued", data: { position, maxRunningTurns } });
|
|
725
|
+
this.logger(`[session] ${sessionId} ${turnId} queued at position ${position} (${maxRunningTurns} turn slot(s) busy)`);
|
|
726
|
+
},
|
|
727
|
+
});
|
|
710
728
|
let outcome;
|
|
711
|
-
|
|
712
|
-
//
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
return rest;
|
|
729
|
+
if (permit === undefined) {
|
|
730
|
+
// Preempted while waiting for a slot; settle as an interrupted turn.
|
|
731
|
+
outcome = { status: "cancelled" };
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
emit({ type: "turn.started", data: {} });
|
|
735
|
+
try {
|
|
736
|
+
// Scaffold agent files into the session workspace even when the harness
|
|
737
|
+
// cwd is overridden (e.g. a PR worktree). A/B variant instruction
|
|
738
|
+
// overlays are applied via agentForSession.
|
|
739
|
+
const agent = this.agentForSession(record);
|
|
740
|
+
yield materializeWorkspace({
|
|
741
|
+
agent,
|
|
742
|
+
workspaceDir,
|
|
743
|
+
seed: isFirstTurn,
|
|
727
744
|
});
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
(yield isNestedInGitRepo(harnessCwd)));
|
|
737
|
-
const request = this.buildTurnRequest(record, agent, turnId, message, isFirstTurn, instructionsInvisibleToHarness, emit, (update) => __awaiter(this, void 0, void 0, function* () {
|
|
738
|
-
mapper.handleUpdate(update);
|
|
739
|
-
// Yield so the HTTP NDJSON writer can flush tool start/result
|
|
740
|
-
// events while sibling parallel tools are still executing.
|
|
741
|
-
if (update.type === "tool-call-started" ||
|
|
742
|
-
update.type === "tool-call-completed" ||
|
|
743
|
-
// Nested subagent progress (newer SDKs); string check keeps
|
|
744
|
-
// typecheck green against older published `@cursor/sdk` pins.
|
|
745
|
-
update.type === "tool-call-delta") {
|
|
746
|
-
yield yieldEventLoop();
|
|
745
|
+
const pendingFiles = record.pendingWorkspaceFiles;
|
|
746
|
+
if (pendingFiles !== undefined &&
|
|
747
|
+
Object.keys(pendingFiles).length > 0) {
|
|
748
|
+
yield writeWorkspaceFiles(harnessCwd, pendingFiles);
|
|
749
|
+
yield this.updateRecord(sessionId, (current) => {
|
|
750
|
+
const { pendingWorkspaceFiles: _cleared } = current, rest = __rest(current, ["pendingWorkspaceFiles"]);
|
|
751
|
+
return rest;
|
|
752
|
+
});
|
|
747
753
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
754
|
+
// The harness only loads the scaffolded AGENTS.md when the session
|
|
755
|
+
// workspace is its own project root. With a per-session cwd override
|
|
756
|
+
// (PR worktree) the scaffold lands elsewhere, and a workspace nested
|
|
757
|
+
// inside a git repo re-roots harness rules at that repo — in both
|
|
758
|
+
// cases deliver instructions inline on the first turn instead.
|
|
759
|
+
const instructionsInvisibleToHarness = isFirstTurn &&
|
|
760
|
+
(record.workspaceOverride !== undefined ||
|
|
761
|
+
(yield isNestedInGitRepo(harnessCwd)));
|
|
762
|
+
const request = this.buildTurnRequest(record, agent, turnId, message, isFirstTurn, instructionsInvisibleToHarness, emit, (update) => __awaiter(this, void 0, void 0, function* () {
|
|
763
|
+
mapper.handleUpdate(update);
|
|
764
|
+
// Yield so the HTTP NDJSON writer can flush tool start/result
|
|
765
|
+
// events while sibling parallel tools are still executing.
|
|
766
|
+
if (update.type === "tool-call-started" ||
|
|
767
|
+
update.type === "tool-call-completed" ||
|
|
768
|
+
// Nested subagent progress (newer SDKs); string check keeps
|
|
769
|
+
// typecheck green against older published `@cursor/sdk` pins.
|
|
770
|
+
update.type === "tool-call-delta") {
|
|
771
|
+
yield yieldEventLoop();
|
|
772
|
+
}
|
|
773
|
+
}), signal);
|
|
774
|
+
outcome = yield this.runner.runTurn(request);
|
|
775
|
+
}
|
|
776
|
+
catch (error) {
|
|
777
|
+
this.approvals.cancelSession(sessionId, "deny");
|
|
778
|
+
outcome = { status: "error", errorMessage: describeError(error) };
|
|
779
|
+
}
|
|
780
|
+
finally {
|
|
781
|
+
permit.release();
|
|
782
|
+
this.approvals.cancelSession(sessionId, "deny");
|
|
783
|
+
yield this.clearPendingApprovals(sessionId);
|
|
784
|
+
}
|
|
758
785
|
}
|
|
759
786
|
mapper.finish();
|
|
760
787
|
if (mapper.title !== undefined) {
|
|
@@ -867,34 +894,37 @@ export class SessionEngine {
|
|
|
867
894
|
});
|
|
868
895
|
}) });
|
|
869
896
|
}
|
|
870
|
-
/**
|
|
871
|
-
* MCP servers for a turn: authored concrete connections plus resolved
|
|
872
|
-
* serve-time resolved connections (peers, Cursor account bridge).
|
|
873
|
-
* Resolved connections pick their URL by runtime — loopback for local
|
|
874
|
-
* turns, `cloudUrl` for cloud turns — and are omitted from cloud turns
|
|
875
|
-
* when no cloud URL exists unless a `servers` filter requires the bridge
|
|
876
|
-
* (see {@link assertCloudCanHonorAccountServersFilters}).
|
|
877
|
-
*/
|
|
897
|
+
/** Authored + resolved MCP servers for a turn; skips `hostOnly`. */
|
|
878
898
|
buildMcpServers(runtime) {
|
|
899
|
+
const hostOnlyNames = new Set(this.project.agent.connections
|
|
900
|
+
.filter((connection) => connection.hostOnly === true)
|
|
901
|
+
.map((connection) => connection.name));
|
|
879
902
|
assertCloudCanHonorAccountServersFilters({
|
|
880
903
|
runtime,
|
|
881
|
-
resolvedConnections: this.resolvedConnections,
|
|
904
|
+
resolvedConnections: this.resolvedConnections.filter((resolved) => !hostOnlyNames.has(resolved.name)),
|
|
882
905
|
});
|
|
883
906
|
const servers = {};
|
|
884
907
|
for (const connection of this.project.agent.connections) {
|
|
908
|
+
if (hostOnlyNames.has(connection.name)) {
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
885
911
|
if (isSymbolicConnectionTransport(connection.transport)) {
|
|
886
912
|
continue;
|
|
887
913
|
}
|
|
888
914
|
servers[connection.name] = connection.transport;
|
|
889
915
|
}
|
|
890
916
|
for (const resolved of this.resolvedConnections) {
|
|
917
|
+
if (hostOnlyNames.has(resolved.name)) {
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
891
920
|
const url = runtime === "cloud" ? resolved.cloudUrl : resolved.localUrl;
|
|
892
921
|
if (url === undefined) {
|
|
893
922
|
continue;
|
|
894
923
|
}
|
|
895
|
-
servers[resolved.name] =
|
|
896
|
-
|
|
897
|
-
|
|
924
|
+
servers[resolved.name] = {
|
|
925
|
+
url,
|
|
926
|
+
headers: resolved.headers,
|
|
927
|
+
};
|
|
898
928
|
}
|
|
899
929
|
return servers;
|
|
900
930
|
}
|
|
@@ -1434,7 +1464,9 @@ export class SessionEngine {
|
|
|
1434
1464
|
.map((record) => {
|
|
1435
1465
|
const externalUrl = externalUrlForSession(record);
|
|
1436
1466
|
return Object.assign(Object.assign(Object.assign({ sessionId: record.sessionId, channelId: record.channelId, mode: record.mode }, (record.title === undefined ? {} : { title: record.title })), { turnCount: record.turnCount, running: this.activeTurns.has(record.sessionId) ||
|
|
1437
|
-
this.pendingTurns.has(record.sessionId),
|
|
1467
|
+
this.pendingTurns.has(record.sessionId), queued: this.turnGovernor.limited
|
|
1468
|
+
? this.turnGovernor.isQueued(record.sessionId)
|
|
1469
|
+
: undefined, createdAt: record.createdAt, updatedAt: record.updatedAt, continuationToken: record.continuationKey === null
|
|
1438
1470
|
? null
|
|
1439
1471
|
: formatContinuationToken(record.channelId, record.continuationKey) }), (externalUrl === undefined ? {} : { externalUrl }));
|
|
1440
1472
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions-client.d.ts","sourceRoot":"","sources":["../../src/internal/sessions-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACf,MAAM,aAAa,CAAC;AAErB,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE7E,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAE5B;AAED,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,GAAG,OAAO,CAAC,cAAc,CAAC,CAiC1B;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAuE1B;
|
|
1
|
+
{"version":3,"file":"sessions-client.d.ts","sourceRoot":"","sources":["../../src/internal/sessions-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EACjB,cAAc,EACf,MAAM,aAAa,CAAC;AAErB,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE7E,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAE5B;AAED,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,GAAG,OAAO,CAAC,cAAc,CAAC,CAiC1B;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAuE1B;AAiCD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAClC,cAAc,GAAG,SAAS,CAU5B;AAED,+DAA+D;AAC/D,wBAAsB,wBAAwB,CAAC,IAAI,EAAE;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAO9B;AAED,+DAA+D;AAC/D,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAIzE;AAED,4EAA4E;AAC5E,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,cAAc,EAAE,EAC1B,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAAE,GACvC,MAAM,CAkBR"}
|
|
@@ -143,6 +143,7 @@ function parseSessionSummary(value) {
|
|
|
143
143
|
title: typeof row.title === "string" ? row.title : undefined,
|
|
144
144
|
turnCount: typeof row.turnCount === "number" ? row.turnCount : 0,
|
|
145
145
|
running: row.running === true,
|
|
146
|
+
queued: typeof row.queued === "boolean" ? row.queued : undefined,
|
|
146
147
|
createdAt: typeof row.createdAt === "string" ? row.createdAt : "",
|
|
147
148
|
updatedAt: typeof row.updatedAt === "string" ? row.updatedAt : "",
|
|
148
149
|
continuationToken,
|
|
@@ -194,7 +195,7 @@ export function formatSessionsTable(sessions, options) {
|
|
|
194
195
|
s.channelId,
|
|
195
196
|
s.mode,
|
|
196
197
|
String(s.turnCount),
|
|
197
|
-
s.running ? "yes" : "no",
|
|
198
|
+
s.queued === true ? "queued" : s.running ? "yes" : "no",
|
|
198
199
|
s.updatedAt,
|
|
199
200
|
]);
|
|
200
201
|
return `${sourceLine}${renderTable(header, rows)}\n`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-progress.d.ts","sourceRoot":"","sources":["../../src/internal/stream-progress.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE;IAC7C,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"stream-progress.d.ts","sourceRoot":"","sources":["../../src/internal/stream-progress.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE;IAC7C,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,cAAc,CA6NjB"}
|
|
@@ -21,7 +21,7 @@ export function createStreamProgress(options) {
|
|
|
21
21
|
const elapsed = (since) => `${Date.now() - since}ms`;
|
|
22
22
|
return {
|
|
23
23
|
onEvent(event) {
|
|
24
|
-
var _a, _b, _c;
|
|
24
|
+
var _a, _b, _c, _d;
|
|
25
25
|
switch (event.type) {
|
|
26
26
|
case "session.started": {
|
|
27
27
|
write(c.dim(`session ${event.sessionId}\n`));
|
|
@@ -35,12 +35,16 @@ export function createStreamProgress(options) {
|
|
|
35
35
|
}
|
|
36
36
|
break;
|
|
37
37
|
}
|
|
38
|
+
case "turn.queued": {
|
|
39
|
+
write(c.dim(` turn ${(_a = event.turnId) !== null && _a !== void 0 ? _a : "?"} queued (position ${event.data.position}, max ${event.data.maxRunningTurns} running)\n`));
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
38
42
|
case "turn.started": {
|
|
39
43
|
turnClock = Date.now();
|
|
40
44
|
if (event.turnId !== undefined) {
|
|
41
45
|
turnStartedAt.set(event.turnId, turnClock);
|
|
42
46
|
}
|
|
43
|
-
write(c.dim(` turn ${(
|
|
47
|
+
write(c.dim(` turn ${(_b = event.turnId) !== null && _b !== void 0 ? _b : "?"} started\n`));
|
|
44
48
|
break;
|
|
45
49
|
}
|
|
46
50
|
case "message.received": {
|
|
@@ -131,11 +135,11 @@ export function createStreamProgress(options) {
|
|
|
131
135
|
const detail = event.data.description
|
|
132
136
|
? ` — ${truncate(event.data.description, 60)}`
|
|
133
137
|
: "";
|
|
134
|
-
write(` ${c.magenta("task")} ${c.bold((
|
|
138
|
+
write(` ${c.magenta("task")} ${c.bold((_c = event.data.name) !== null && _c !== void 0 ? _c : "subagent")}${c.dim(detail)}\n`);
|
|
135
139
|
break;
|
|
136
140
|
}
|
|
137
141
|
case "subagent.completed": {
|
|
138
|
-
write(` ${c.magenta("✓")} ${c.bold((
|
|
142
|
+
write(` ${c.magenta("✓")} ${c.bold((_d = event.data.name) !== null && _d !== void 0 ? _d : "subagent")} ${c.dim("done")}\n`);
|
|
139
143
|
break;
|
|
140
144
|
}
|
|
141
145
|
case "turn.completed": {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FIFO admission of model turns under `concurrency.maxRunningTurns`
|
|
3
|
+
* (see {@link AgentConcurrencyOptions}). One governor per session engine:
|
|
4
|
+
* every turn acquires a slot before its SDK run starts and releases it when
|
|
5
|
+
* the run settles, so at most `maxRunningTurns` turns execute at once and
|
|
6
|
+
* the rest wait in arrival order instead of failing.
|
|
7
|
+
*
|
|
8
|
+
* Waiting is cancellable: the engine passes the turn's abort signal, so a
|
|
9
|
+
* follow-up that preempts a queued turn (or an explicit `interrupt()`)
|
|
10
|
+
* removes the waiter without ever running it.
|
|
11
|
+
*/
|
|
12
|
+
import type { AgentConcurrencyOptions } from "../types.js";
|
|
13
|
+
export interface TurnPermit {
|
|
14
|
+
/** Return the slot. Idempotent; wakes the oldest waiter, if any. */
|
|
15
|
+
release(): void;
|
|
16
|
+
}
|
|
17
|
+
export interface AcquireTurnSlotArgs {
|
|
18
|
+
sessionId: string;
|
|
19
|
+
/** Abort waiting (preemption / interrupt). Ignored once a slot is held. */
|
|
20
|
+
signal?: AbortSignal;
|
|
21
|
+
/**
|
|
22
|
+
* Called synchronously when the turn must wait, with its 1-based
|
|
23
|
+
* position among waiting turns at enqueue time.
|
|
24
|
+
*/
|
|
25
|
+
onQueued?: (position: number) => void;
|
|
26
|
+
}
|
|
27
|
+
export declare class TurnGovernor {
|
|
28
|
+
/** The configured ceiling, or undefined for unlimited. */
|
|
29
|
+
readonly maxRunningTurns: number | undefined;
|
|
30
|
+
private running;
|
|
31
|
+
private readonly waiters;
|
|
32
|
+
/** Sessions currently waiting for a slot (at most one turn per session). */
|
|
33
|
+
private readonly queuedSessions;
|
|
34
|
+
constructor(concurrency: AgentConcurrencyOptions | undefined);
|
|
35
|
+
/** True when a `maxRunningTurns` ceiling is configured. */
|
|
36
|
+
get limited(): boolean;
|
|
37
|
+
/** True while `sessionId` has a turn waiting for a slot. */
|
|
38
|
+
isQueued(sessionId: string): boolean;
|
|
39
|
+
/** Turns currently waiting for a slot. */
|
|
40
|
+
get queuedCount(): number;
|
|
41
|
+
/** Turns currently holding a slot. */
|
|
42
|
+
get runningCount(): number;
|
|
43
|
+
/**
|
|
44
|
+
* Acquire a turn slot. Resolves with a permit once a slot is free (FIFO
|
|
45
|
+
* behind earlier waiters), or with `undefined` when the abort signal
|
|
46
|
+
* fires while waiting — the turn must then settle as cancelled without
|
|
47
|
+
* running. Unlimited governors grant immediately.
|
|
48
|
+
*/
|
|
49
|
+
acquire(args: AcquireTurnSlotArgs): Promise<TurnPermit | undefined>;
|
|
50
|
+
/** Take a slot and build its (idempotent) release handle. */
|
|
51
|
+
private grantSlot;
|
|
52
|
+
private wakeNextWaiter;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=turn-governor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-governor.d.ts","sourceRoot":"","sources":["../../src/internal/turn-governor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAE3D,MAAM,WAAW,UAAU;IACzB,oEAAoE;IACpE,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AASD,qBAAa,YAAY;IACvB,0DAA0D;IAC1D,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,4EAA4E;IAC5E,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqB;IAEpD,YAAY,WAAW,EAAE,uBAAuB,GAAG,SAAS,EAE3D;IAED,2DAA2D;IAC3D,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,4DAA4D;IAC5D,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEnC;IAED,0CAA0C;IAC1C,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,sCAAsC;IACtC,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;;;OAKG;IACH,OAAO,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAoClE;IAED,6DAA6D;IAC7D,OAAO,CAAC,SAAS;IAejB,OAAO,CAAC,cAAc;CAYvB"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FIFO admission of model turns under `concurrency.maxRunningTurns`
|
|
3
|
+
* (see {@link AgentConcurrencyOptions}). One governor per session engine:
|
|
4
|
+
* every turn acquires a slot before its SDK run starts and releases it when
|
|
5
|
+
* the run settles, so at most `maxRunningTurns` turns execute at once and
|
|
6
|
+
* the rest wait in arrival order instead of failing.
|
|
7
|
+
*
|
|
8
|
+
* Waiting is cancellable: the engine passes the turn's abort signal, so a
|
|
9
|
+
* follow-up that preempts a queued turn (or an explicit `interrupt()`)
|
|
10
|
+
* removes the waiter without ever running it.
|
|
11
|
+
*/
|
|
12
|
+
export class TurnGovernor {
|
|
13
|
+
constructor(concurrency) {
|
|
14
|
+
this.running = 0;
|
|
15
|
+
this.waiters = [];
|
|
16
|
+
/** Sessions currently waiting for a slot (at most one turn per session). */
|
|
17
|
+
this.queuedSessions = new Set();
|
|
18
|
+
this.maxRunningTurns = concurrency === null || concurrency === void 0 ? void 0 : concurrency.maxRunningTurns;
|
|
19
|
+
}
|
|
20
|
+
/** True when a `maxRunningTurns` ceiling is configured. */
|
|
21
|
+
get limited() {
|
|
22
|
+
return this.maxRunningTurns !== undefined;
|
|
23
|
+
}
|
|
24
|
+
/** True while `sessionId` has a turn waiting for a slot. */
|
|
25
|
+
isQueued(sessionId) {
|
|
26
|
+
return this.queuedSessions.has(sessionId);
|
|
27
|
+
}
|
|
28
|
+
/** Turns currently waiting for a slot. */
|
|
29
|
+
get queuedCount() {
|
|
30
|
+
return this.waiters.length;
|
|
31
|
+
}
|
|
32
|
+
/** Turns currently holding a slot. */
|
|
33
|
+
get runningCount() {
|
|
34
|
+
return this.running;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Acquire a turn slot. Resolves with a permit once a slot is free (FIFO
|
|
38
|
+
* behind earlier waiters), or with `undefined` when the abort signal
|
|
39
|
+
* fires while waiting — the turn must then settle as cancelled without
|
|
40
|
+
* running. Unlimited governors grant immediately.
|
|
41
|
+
*/
|
|
42
|
+
acquire(args) {
|
|
43
|
+
var _a;
|
|
44
|
+
const limit = this.maxRunningTurns;
|
|
45
|
+
if (limit === undefined) {
|
|
46
|
+
return Promise.resolve(NO_OP_PERMIT);
|
|
47
|
+
}
|
|
48
|
+
if (this.running < limit && this.waiters.length === 0) {
|
|
49
|
+
return Promise.resolve(this.grantSlot());
|
|
50
|
+
}
|
|
51
|
+
// Saturated. A turn that is already preempted never enqueues.
|
|
52
|
+
if (((_a = args.signal) === null || _a === void 0 ? void 0 : _a.aborted) === true) {
|
|
53
|
+
return Promise.resolve(undefined);
|
|
54
|
+
}
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
var _a, _b;
|
|
57
|
+
const waiter = {
|
|
58
|
+
sessionId: args.sessionId,
|
|
59
|
+
grant: (permit) => resolve(permit),
|
|
60
|
+
cancel: () => resolve(undefined),
|
|
61
|
+
cleanup: () => {
|
|
62
|
+
var _a;
|
|
63
|
+
this.queuedSessions.delete(args.sessionId);
|
|
64
|
+
(_a = args.signal) === null || _a === void 0 ? void 0 : _a.removeEventListener("abort", onAbort);
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
const onAbort = () => {
|
|
68
|
+
const index = this.waiters.indexOf(waiter);
|
|
69
|
+
if (index === -1) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.waiters.splice(index, 1);
|
|
73
|
+
waiter.cleanup();
|
|
74
|
+
waiter.cancel();
|
|
75
|
+
};
|
|
76
|
+
this.waiters.push(waiter);
|
|
77
|
+
this.queuedSessions.add(args.sessionId);
|
|
78
|
+
(_a = args.signal) === null || _a === void 0 ? void 0 : _a.addEventListener("abort", onAbort, { once: true });
|
|
79
|
+
(_b = args.onQueued) === null || _b === void 0 ? void 0 : _b.call(args, this.waiters.length);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/** Take a slot and build its (idempotent) release handle. */
|
|
83
|
+
grantSlot() {
|
|
84
|
+
this.running += 1;
|
|
85
|
+
let released = false;
|
|
86
|
+
return {
|
|
87
|
+
release: () => {
|
|
88
|
+
if (released) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
released = true;
|
|
92
|
+
this.running -= 1;
|
|
93
|
+
this.wakeNextWaiter();
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
wakeNextWaiter() {
|
|
98
|
+
const limit = this.maxRunningTurns;
|
|
99
|
+
if (limit === undefined || this.running >= limit) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const waiter = this.waiters.shift();
|
|
103
|
+
if (waiter === undefined) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
waiter.cleanup();
|
|
107
|
+
waiter.grant(this.grantSlot());
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** Permit handed out by unlimited governors. */
|
|
111
|
+
const NO_OP_PERMIT = { release: () => { } };
|