@visual-z/xeno 0.8.4 → 0.9.0
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/CHANGELOG.md +8 -0
- package/README.md +31 -746
- package/bin/xeno.exe +26 -0
- package/install.cjs +88 -0
- package/package.json +18 -48
- package/docs/app-server.md +0 -115
- package/docs/foundations.md +0 -88
- package/docs/install.md +0 -244
- package/docs/providers.md +0 -172
- package/docs/research/foundation-clients-2026-10-09.md +0 -27
- package/docs/research/foundation-editing-2026-10-09.md +0 -56
- package/docs/research/foundation-network-2026-10-09.md +0 -37
- package/docs/research/foundation-workflow-2026-10-09.md +0 -37
- package/src/apps/acp-server.ts +0 -41
- package/src/apps/agent-profiles.ts +0 -19
- package/src/apps/app-server.ts +0 -30
- package/src/apps/approve.ts +0 -49
- package/src/apps/cli.ts +0 -99
- package/src/apps/commands.ts +0 -319
- package/src/apps/completions.ts +0 -87
- package/src/apps/control-transport.ts +0 -90
- package/src/apps/export-cli.ts +0 -24
- package/src/apps/goal-flow.ts +0 -70
- package/src/apps/image-draft.ts +0 -46
- package/src/apps/input.ts +0 -312
- package/src/apps/model-catalog.ts +0 -69
- package/src/apps/preferences.ts +0 -20
- package/src/apps/project-trust.ts +0 -9
- package/src/apps/provider-connect.ts +0 -346
- package/src/apps/question-ui.ts +0 -27
- package/src/apps/render.ts +0 -181
- package/src/apps/resources.ts +0 -66
- package/src/apps/review.ts +0 -109
- package/src/apps/session-hub.ts +0 -96
- package/src/apps/session-lifecycle.ts +0 -64
- package/src/apps/shell.ts +0 -2071
- package/src/apps/startup-input.ts +0 -135
- package/src/apps/startup.ts +0 -1677
- package/src/apps/ui/app.ts +0 -1818
- package/src/apps/ui/control-model.ts +0 -43
- package/src/apps/ui/extension-host.ts +0 -194
- package/src/apps/ui/image-cache.ts +0 -121
- package/src/apps/ui/palette.ts +0 -88
- package/src/apps/ui/project.ts +0 -127
- package/src/apps/ui/standalone.ts +0 -148
- package/src/apps/ui/status-model.ts +0 -68
- package/src/apps/ui/surface.ts +0 -91
- package/src/apps/ui/theme.ts +0 -86
- package/src/apps/update-check.ts +0 -112
- package/src/apps/workflow.ts +0 -58
- package/src/core/acceptance.ts +0 -139
- package/src/core/action-defaults.ts +0 -58
- package/src/core/activity-status.ts +0 -52
- package/src/core/agent-profile.ts +0 -130
- package/src/core/attachment-input.ts +0 -17
- package/src/core/blind-checks.ts +0 -72
- package/src/core/boundary.ts +0 -174
- package/src/core/branch-receipt.ts +0 -26
- package/src/core/catalog-merge.ts +0 -19
- package/src/core/checkpoints.ts +0 -85
- package/src/core/churn.ts +0 -61
- package/src/core/command-artifacts.ts +0 -94
- package/src/core/command-contract.ts +0 -151
- package/src/core/completion-action.ts +0 -20
- package/src/core/completion-review.ts +0 -98
- package/src/core/context-detail.ts +0 -37
- package/src/core/context-policy.ts +0 -133
- package/src/core/context-window.ts +0 -54
- package/src/core/continuation.ts +0 -130
- package/src/core/control-protocol.ts +0 -69
- package/src/core/control-view.ts +0 -39
- package/src/core/cycle.ts +0 -17
- package/src/core/data-query.ts +0 -201
- package/src/core/deadline.ts +0 -40
- package/src/core/derive.ts +0 -516
- package/src/core/draft-images.ts +0 -56
- package/src/core/edit-plan.ts +0 -200
- package/src/core/environment.ts +0 -181
- package/src/core/event-validation.ts +0 -220
- package/src/core/events.ts +0 -670
- package/src/core/evidence-ladder.ts +0 -62
- package/src/core/evidence-links.ts +0 -68
- package/src/core/evidence.ts +0 -240
- package/src/core/execution.ts +0 -18
- package/src/core/exhaustive.ts +0 -18
- package/src/core/export.ts +0 -179
- package/src/core/extension-flags.ts +0 -14
- package/src/core/external-effects.ts +0 -488
- package/src/core/failure-recovery.ts +0 -173
- package/src/core/file-precondition.ts +0 -78
- package/src/core/file-references.ts +0 -35
- package/src/core/frontmatter.ts +0 -213
- package/src/core/git-status.ts +0 -29
- package/src/core/goal-view.ts +0 -64
- package/src/core/goal.ts +0 -200
- package/src/core/handoff.ts +0 -479
- package/src/core/history-query.ts +0 -139
- package/src/core/history.ts +0 -237
- package/src/core/image-limits.ts +0 -72
- package/src/core/images.ts +0 -46
- package/src/core/integration-health.ts +0 -34
- package/src/core/json-event.ts +0 -131
- package/src/core/json-pointer.ts +0 -46
- package/src/core/keymap.ts +0 -153
- package/src/core/lsp-framing.ts +0 -34
- package/src/core/mcp-config.ts +0 -68
- package/src/core/model-picker.ts +0 -64
- package/src/core/model-preferences.ts +0 -28
- package/src/core/model-replay.ts +0 -22
- package/src/core/model-scope.ts +0 -53
- package/src/core/note-guidance.ts +0 -34
- package/src/core/oauth-fields.ts +0 -44
- package/src/core/observation-view.ts +0 -152
- package/src/core/outcome.ts +0 -44
- package/src/core/overflow.ts +0 -59
- package/src/core/packages.ts +0 -47
- package/src/core/patch.ts +0 -130
- package/src/core/path-suggestions.ts +0 -59
- package/src/core/permissions.ts +0 -410
- package/src/core/process-receipt.ts +0 -105
- package/src/core/process-state.ts +0 -37
- package/src/core/program-status.ts +0 -40
- package/src/core/progress.ts +0 -150
- package/src/core/project.ts +0 -431
- package/src/core/prompt-policy.ts +0 -46
- package/src/core/prompts.ts +0 -111
- package/src/core/question.ts +0 -94
- package/src/core/queue.ts +0 -78
- package/src/core/read-bounds.ts +0 -24
- package/src/core/read-observation.ts +0 -106
- package/src/core/recovery-sources.ts +0 -74
- package/src/core/regex-dialect.ts +0 -25
- package/src/core/request-budget.ts +0 -23
- package/src/core/request-progress.ts +0 -87
- package/src/core/request-state.ts +0 -273
- package/src/core/resource-selection.ts +0 -36
- package/src/core/response-recovery.ts +0 -59
- package/src/core/resume.ts +0 -59
- package/src/core/review.ts +0 -68
- package/src/core/ripgrep-release.ts +0 -64
- package/src/core/runtime-target.ts +0 -94
- package/src/core/scheduling.ts +0 -8
- package/src/core/search-observation.ts +0 -97
- package/src/core/secrets.ts +0 -188
- package/src/core/session-archive.ts +0 -4
- package/src/core/session-browser.ts +0 -82
- package/src/core/session-entry.ts +0 -61
- package/src/core/session-exit.ts +0 -42
- package/src/core/session-format.ts +0 -169
- package/src/core/session-position.ts +0 -26
- package/src/core/session-tree.ts +0 -147
- package/src/core/settings.ts +0 -192
- package/src/core/skills.ts +0 -165
- package/src/core/sql-query.ts +0 -211
- package/src/core/stall.ts +0 -128
- package/src/core/stop-report.ts +0 -68
- package/src/core/text-normalize.ts +0 -81
- package/src/core/theme.ts +0 -166
- package/src/core/thinking.ts +0 -26
- package/src/core/tokens.ts +0 -151
- package/src/core/tool-contract.ts +0 -153
- package/src/core/tool-discovery.ts +0 -87
- package/src/core/tool-failure.ts +0 -81
- package/src/core/tool-input.ts +0 -44
- package/src/core/transcript.ts +0 -108
- package/src/core/tui-mode.ts +0 -29
- package/src/core/update-check.ts +0 -157
- package/src/core/version.ts +0 -55
- package/src/core/web-content.ts +0 -80
- package/src/core/work-state.ts +0 -154
- package/src/core/workflow-mode.ts +0 -34
- package/src/core/working-set.ts +0 -119
- package/src/core/yaml.ts +0 -282
- package/src/runtime/acp-agent.ts +0 -289
- package/src/runtime/agent-profiles.ts +0 -21
- package/src/runtime/auth.ts +0 -367
- package/src/runtime/blind-check.ts +0 -36
- package/src/runtime/branch-jobs.ts +0 -36
- package/src/runtime/catalog.ts +0 -285
- package/src/runtime/clipboard.ts +0 -125
- package/src/runtime/command-artifacts.ts +0 -64
- package/src/runtime/connection-reuse.ts +0 -40
- package/src/runtime/control-client.ts +0 -149
- package/src/runtime/control-connection.ts +0 -139
- package/src/runtime/counter-check.ts +0 -74
- package/src/runtime/environment-tools.ts +0 -60
- package/src/runtime/evidence-notes.ts +0 -35
- package/src/runtime/exec-sessions.ts +0 -456
- package/src/runtime/execution-control.ts +0 -40
- package/src/runtime/extension-loader.ts +0 -37
- package/src/runtime/extension-ui.ts +0 -47
- package/src/runtime/extensions.ts +0 -370
- package/src/runtime/formatter-tool.ts +0 -38
- package/src/runtime/formatter.ts +0 -50
- package/src/runtime/foundation.ts +0 -116
- package/src/runtime/history-cache.ts +0 -57
- package/src/runtime/history-tools.ts +0 -168
- package/src/runtime/images.ts +0 -80
- package/src/runtime/integrations.ts +0 -216
- package/src/runtime/loop.ts +0 -3108
- package/src/runtime/lsp-client.ts +0 -201
- package/src/runtime/mcp-client.ts +0 -264
- package/src/runtime/mcp-oauth.ts +0 -168
- package/src/runtime/mcp-schema.ts +0 -51
- package/src/runtime/model-config.ts +0 -232
- package/src/runtime/model-data.LICENSE +0 -27
- package/src/runtime/model-data.ts +0 -21
- package/src/runtime/model-discovery.ts +0 -208
- package/src/runtime/model-replay.ts +0 -10
- package/src/runtime/model-scope.ts +0 -20
- package/src/runtime/model-sources.ts +0 -151
- package/src/runtime/model.ts +0 -686
- package/src/runtime/models-fetch.ts +0 -57
- package/src/runtime/oauth-device.ts +0 -86
- package/src/runtime/oauth.ts +0 -853
- package/src/runtime/open-image.ts +0 -42
- package/src/runtime/openai-responses.ts +0 -304
- package/src/runtime/openai.ts +0 -697
- package/src/runtime/output-schema.ts +0 -26
- package/src/runtime/package-manager.ts +0 -125
- package/src/runtime/patch-tool.ts +0 -38
- package/src/runtime/permission-resources.ts +0 -18
- package/src/runtime/pi-provider.ts +0 -423
- package/src/runtime/program-status.ts +0 -38
- package/src/runtime/progress.ts +0 -26
- package/src/runtime/provider-access.ts +0 -21
- package/src/runtime/provider-connect.ts +0 -109
- package/src/runtime/provider-presets.ts +0 -98
- package/src/runtime/provider-protocols.ts +0 -29
- package/src/runtime/provider-request.ts +0 -87
- package/src/runtime/provider-tool-policy.ts +0 -33
- package/src/runtime/provider.ts +0 -580
- package/src/runtime/question-tool.ts +0 -62
- package/src/runtime/retry.ts +0 -447
- package/src/runtime/review-tool.ts +0 -48
- package/src/runtime/rich-text.ts +0 -36
- package/src/runtime/runtime-target.ts +0 -149
- package/src/runtime/secrets.ts +0 -79
- package/src/runtime/self-update.ts +0 -74
- package/src/runtime/session-control.ts +0 -265
- package/src/runtime/session-hub.ts +0 -85
- package/src/runtime/stream-idle.ts +0 -80
- package/src/runtime/tool-broker.ts +0 -137
- package/src/runtime/tool-discovery.ts +0 -58
- package/src/runtime/tool-queue.ts +0 -77
- package/src/runtime/tools.ts +0 -1820
- package/src/runtime/turn-effects.ts +0 -31
- package/src/runtime/update-check.ts +0 -48
- package/src/runtime/usage.ts +0 -253
- package/src/runtime/version.ts +0 -12
- package/src/runtime/web-http.ts +0 -56
- package/src/runtime/web-tools.ts +0 -88
- package/src/runtime/working-notes.ts +0 -60
- package/src/runtime/workspace-lifecycle.ts +0 -141
- package/src/store/acceptance.ts +0 -15
- package/src/store/agent-profile-files.ts +0 -13
- package/src/store/atomic.ts +0 -31
- package/src/store/auth-store.ts +0 -50
- package/src/store/blind-checks.ts +0 -32
- package/src/store/blobs.ts +0 -143
- package/src/store/checkpoint-history.ts +0 -32
- package/src/store/command-artifacts.ts +0 -96
- package/src/store/command.ts +0 -111
- package/src/store/config.ts +0 -69
- package/src/store/context-files.ts +0 -436
- package/src/store/file-checkpoints.ts +0 -108
- package/src/store/file-index.ts +0 -174
- package/src/store/foundation-config.ts +0 -24
- package/src/store/git-checkpoints.ts +0 -67
- package/src/store/git-status.ts +0 -15
- package/src/store/history-search.ts +0 -19
- package/src/store/log.ts +0 -725
- package/src/store/lsp-document.ts +0 -30
- package/src/store/mcp-credentials.ts +0 -85
- package/src/store/models.ts +0 -60
- package/src/store/observations.ts +0 -224
- package/src/store/packages.ts +0 -98
- package/src/store/patch.ts +0 -92
- package/src/store/permission-paths.ts +0 -34
- package/src/store/process-receipts.ts +0 -34
- package/src/store/query-source.ts +0 -49
- package/src/store/recovery.ts +0 -44
- package/src/store/replay.ts +0 -76
- package/src/store/resource-index.ts +0 -170
- package/src/store/resources.ts +0 -285
- package/src/store/restore.ts +0 -110
- package/src/store/review.ts +0 -109
- package/src/store/runtime-target.ts +0 -64
- package/src/store/search.ts +0 -728
- package/src/store/secrets.ts +0 -44
- package/src/store/serial.ts +0 -13
- package/src/store/session-archive.ts +0 -165
- package/src/store/session-browser.ts +0 -68
- package/src/store/session-position.ts +0 -27
- package/src/store/sessions.ts +0 -525
- package/src/store/source-cancellation.ts +0 -6
- package/src/store/tool-binaries.ts +0 -90
- package/src/store/trust.ts +0 -82
- package/src/store/update-cache.ts +0 -36
- package/src/store/user-settings.ts +0 -30
- package/src/store/workspace-clone.ts +0 -42
- package/src/store/workspace-observation.ts +0 -113
- package/src/store/workspace.ts +0 -115
- package/src/store/worktree.ts +0 -278
- package/src/tui/ansi-spans.ts +0 -126
- package/src/tui/diff.ts +0 -160
- package/src/tui/exhaustive.ts +0 -14
- package/src/tui/fuzzy.ts +0 -75
- package/src/tui/style.ts +0 -208
- package/src/tui/ui/atomic-draft.ts +0 -121
- package/src/tui/ui/branch-panel.ts +0 -47
- package/src/tui/ui/canvas.ts +0 -96
- package/src/tui/ui/colors.ts +0 -70
- package/src/tui/ui/composer.ts +0 -199
- package/src/tui/ui/control-panel.ts +0 -25
- package/src/tui/ui/control-sidebar.ts +0 -49
- package/src/tui/ui/dialog.ts +0 -398
- package/src/tui/ui/draft.ts +0 -23
- package/src/tui/ui/duration.ts +0 -16
- package/src/tui/ui/edge.ts +0 -186
- package/src/tui/ui/image-size.ts +0 -44
- package/src/tui/ui/ink.ts +0 -210
- package/src/tui/ui/items.ts +0 -42
- package/src/tui/ui/keys.ts +0 -40
- package/src/tui/ui/layout.ts +0 -74
- package/src/tui/ui/list-model.ts +0 -86
- package/src/tui/ui/list-view.ts +0 -172
- package/src/tui/ui/mascot.ts +0 -67
- package/src/tui/ui/messages.ts +0 -518
- package/src/tui/ui/motion.ts +0 -28
- package/src/tui/ui/paths.ts +0 -10
- package/src/tui/ui/pictures.ts +0 -108
- package/src/tui/ui/prompt-history.ts +0 -60
- package/src/tui/ui/screen.ts +0 -579
- package/src/tui/ui/status.ts +0 -403
- package/src/tui/ui/styled.ts +0 -85
- package/src/tui/ui/tool-card.ts +0 -150
- package/src/tui/width.ts +0 -567
- package/src/vendor/pi-rich/LICENSE.upstream +0 -21
- package/src/vendor/pi-rich/UPSTREAM.json +0 -17
- package/src/vendor/pi-rich/clipboard-command.ts +0 -44
- package/src/vendor/pi-rich/exif-orientation.ts +0 -182
- package/src/vendor/pi-rich/html.ts +0 -51
- package/src/vendor/pi-rich/image-convert.ts +0 -49
- package/src/vendor/pi-rich/image-resize-core.ts +0 -164
- package/src/vendor/pi-rich/image-resize-worker.ts +0 -42
- package/src/vendor/pi-rich/image-resize.ts +0 -123
- package/src/vendor/pi-rich/mime.ts +0 -116
- package/src/vendor/pi-rich/photon.ts +0 -139
- package/src/vendor/pi-rich/syntax-highlight.ts +0 -214
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/** A hub owns live sessions, not transcript mirrors. Durable listing stays a store projection. */
|
|
2
|
-
import type { SessionControl } from "./session-control.ts";
|
|
3
|
-
import { basename } from "node:path";
|
|
4
|
-
|
|
5
|
-
// Assumed live-owner bound includes admitted opens, so concurrent requests cannot exceed it.
|
|
6
|
-
export const MAX_LIVE_SESSIONS = 32;
|
|
7
|
-
|
|
8
|
-
export interface SessionHubHost {
|
|
9
|
-
list(): Promise<unknown>;
|
|
10
|
-
open(kind: "new" | "resume" | "fork", params: Record<string, unknown>): Promise<SessionControl>;
|
|
11
|
-
dispose(control: SessionControl): Promise<void>;
|
|
12
|
-
}
|
|
13
|
-
export class SessionHub {
|
|
14
|
-
private readonly controls = new Map<string, SessionControl>();
|
|
15
|
-
private readonly opening = new Set<Promise<SessionControl>>();
|
|
16
|
-
private readonly resuming = new Map<string, Promise<SessionControl>>();
|
|
17
|
-
private readonly closing = new Map<string, Promise<void>>();
|
|
18
|
-
private reserved = 0;
|
|
19
|
-
private closed = false;
|
|
20
|
-
private disposal: Promise<void> | undefined;
|
|
21
|
-
private readonly host: SessionHubHost;
|
|
22
|
-
readonly initial: SessionControl;
|
|
23
|
-
constructor(initial: SessionControl, host: SessionHubHost) {
|
|
24
|
-
this.initial = initial; this.host = host; this.controls.set(initial.threadId, initial);
|
|
25
|
-
}
|
|
26
|
-
get(threadId: string): SessionControl {
|
|
27
|
-
if (this.closed || this.closing.has(threadId)) throw Error("session hub or thread is closing");
|
|
28
|
-
const control = this.controls.get(threadId);
|
|
29
|
-
if (!control) throw Error("unknown or closed threadId; resume the session first");
|
|
30
|
-
return control;
|
|
31
|
-
}
|
|
32
|
-
async list(): Promise<unknown> { if (this.closed) throw Error("session hub is closing"); return this.host.list(); }
|
|
33
|
-
open(kind: "new" | "resume" | "fork", params: Record<string, unknown>): Promise<SessionControl> {
|
|
34
|
-
if (this.closed) return Promise.reject(Error("session hub is closing; no new session was admitted"));
|
|
35
|
-
const captured = structuredClone(params);
|
|
36
|
-
// Coalesce resume only. Two explicit new/fork requests create two distinct sessions.
|
|
37
|
-
const key = kind === "resume" ? JSON.stringify(captured) : "";
|
|
38
|
-
const pending = key ? this.resuming.get(key) : undefined;
|
|
39
|
-
if (pending) return pending;
|
|
40
|
-
const requested = captured["session"];
|
|
41
|
-
const known = kind === "resume" && [...this.controls.values()].some(control => !this.closing.has(control.threadId)
|
|
42
|
-
&& [control.threadId, control.session.config.logDir, basename(control.session.config.logDir)].includes(String(requested)));
|
|
43
|
-
if (!known && this.controls.size + this.reserved >= MAX_LIVE_SESSIONS) return Promise.reject(Error("live session limit reached; close a session first"));
|
|
44
|
-
if (!known) this.reserved++;
|
|
45
|
-
const operation = (async () => {
|
|
46
|
-
try {
|
|
47
|
-
const control = await this.host.open(kind, captured), existing = this.controls.get(control.threadId);
|
|
48
|
-
if (this.closing.has(control.threadId) || !existing && this.controls.size >= MAX_LIVE_SESSIONS) {
|
|
49
|
-
if (existing !== control) await this.host.dispose(control);
|
|
50
|
-
throw Error("thread is closing or the live session limit was reached");
|
|
51
|
-
}
|
|
52
|
-
if (existing && existing !== control) { await this.host.dispose(control); if (this.closed) throw Error("session hub closed during opening"); return existing; }
|
|
53
|
-
this.controls.set(control.threadId, control);
|
|
54
|
-
if (this.closed) throw Error("session hub closed during opening; the admitted owner will be drained");
|
|
55
|
-
return control;
|
|
56
|
-
} finally { if (!known) this.reserved--; }
|
|
57
|
-
})();
|
|
58
|
-
this.opening.add(operation); if (key) this.resuming.set(key, operation);
|
|
59
|
-
// Observe rejected opens without changing the caller's promise or leaking unhandled cleanup rejections.
|
|
60
|
-
void operation.finally(() => { this.opening.delete(operation); if (key) this.resuming.delete(key); }).catch(() => {});
|
|
61
|
-
return operation;
|
|
62
|
-
}
|
|
63
|
-
close(threadId: string): Promise<void> {
|
|
64
|
-
const pending = this.closing.get(threadId); if (pending) return pending;
|
|
65
|
-
const control = this.controls.get(threadId); if (!control) return Promise.reject(Error("unknown or closed threadId"));
|
|
66
|
-
const operation = (async () => {
|
|
67
|
-
if (control.activeTurnId) control.interrupt(control.activeTurnId);
|
|
68
|
-
await control.whenIdle().catch(() => {});
|
|
69
|
-
await this.host.dispose(control); this.controls.delete(threadId);
|
|
70
|
-
})();
|
|
71
|
-
this.closing.set(threadId, operation);
|
|
72
|
-
void operation.then(() => { if (this.closing.get(threadId) === operation) this.closing.delete(threadId); }, () => {});
|
|
73
|
-
return operation;
|
|
74
|
-
}
|
|
75
|
-
dispose(): Promise<void> {
|
|
76
|
-
if (this.disposal) return this.disposal;
|
|
77
|
-
this.closed = true;
|
|
78
|
-
return this.disposal = (async () => {
|
|
79
|
-
// Every kind is tracked; late publication belongs to this same draining generation.
|
|
80
|
-
await Promise.allSettled([...this.opening]);
|
|
81
|
-
const results = await Promise.allSettled([...this.controls.keys()].map(id => this.close(id)));
|
|
82
|
-
if (results.some(result => result.status === "rejected")) throw Error("session cleanup could not be confirmed");
|
|
83
|
-
})();
|
|
84
|
-
}
|
|
85
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Abandons one attempt whose stream has gone silent.
|
|
3
|
-
*
|
|
4
|
-
* Without this a stream the network silently dropped waits until the user
|
|
5
|
-
* presses Escape (Horizyn 2026-10-12: a request sat 5m39s before the socket
|
|
6
|
-
* error surfaced). OpenAI's Codex CLI (`stream_idle_timeout_ms`, default
|
|
7
|
-
* 300000) and Claude Code (300s) both cut a stream that delivers nothing for
|
|
8
|
-
* five minutes against the same backends, so a live stream -- extended
|
|
9
|
-
* thinking included -- is not that quiet. The limit is theirs, not measured
|
|
10
|
-
* here (feat-stream-idle-watchdog).
|
|
11
|
-
*
|
|
12
|
-
* The clock starts when the request is sent, so it also bounds the wait for
|
|
13
|
-
* the first byte, and restarts on anything received: body bytes where the
|
|
14
|
-
* adapter owns fetch, stream events (pings included) where an SDK does.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
export const STREAM_IDLE_MS = 300_000;
|
|
18
|
-
|
|
19
|
-
/** retry.ts recognises this by name, so it need not import an adapter. */
|
|
20
|
-
export class StreamIdleError extends Error {
|
|
21
|
-
constructor(ms: number) {
|
|
22
|
-
super(`no data from the provider for ${Math.round(ms / 1000)}s; the stream was abandoned`);
|
|
23
|
-
this.name = "StreamIdleError";
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class IdleWatch {
|
|
28
|
-
/** Pass this, not the caller's, to the request: it also aborts on silence. */
|
|
29
|
-
readonly signal: AbortSignal;
|
|
30
|
-
private readonly controller = new AbortController();
|
|
31
|
-
private timer: ReturnType<typeof setTimeout> | undefined;
|
|
32
|
-
private expired = false;
|
|
33
|
-
private readonly outer: AbortSignal | undefined;
|
|
34
|
-
private readonly ms: number;
|
|
35
|
-
|
|
36
|
-
constructor(outer: AbortSignal | undefined, ms: number = STREAM_IDLE_MS) {
|
|
37
|
-
this.outer = outer; this.ms = ms;
|
|
38
|
-
this.signal = outer === undefined ? this.controller.signal : AbortSignal.any([outer, this.controller.signal]);
|
|
39
|
-
this.touch();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
touch(): void {
|
|
43
|
-
if (this.expired) return;
|
|
44
|
-
clearTimeout(this.timer);
|
|
45
|
-
this.timer = setTimeout(() => {
|
|
46
|
-
this.expired = true;
|
|
47
|
-
this.controller.abort(new StreamIdleError(this.ms));
|
|
48
|
-
}, this.ms);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** A body that restarts the clock on every chunk. */
|
|
52
|
-
body(stream: ReadableStream<Uint8Array>): ReadableStream<Uint8Array> {
|
|
53
|
-
return stream.pipeThrough(new TransformStream<Uint8Array, Uint8Array>({
|
|
54
|
-
transform: (chunk, sink) => { this.touch(); sink.enqueue(chunk); },
|
|
55
|
-
}));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** An event stream that restarts the clock on every event. */
|
|
59
|
-
async *events<T>(source: AsyncIterable<T>): AsyncGenerator<T> {
|
|
60
|
-
for await (const event of source) { this.touch(); yield event; }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Whatever the abort surfaced as -- AbortError, a closed body, the SDK's
|
|
65
|
-
* own wrapper -- silence becomes StreamIdleError. The caller's own abort
|
|
66
|
-
* stays an abort: Escape must not start another attempt.
|
|
67
|
-
*/
|
|
68
|
-
rethrow(error: unknown): never {
|
|
69
|
-
if (this.expired && this.outer?.aborted !== true) throw new StreamIdleError(this.ms);
|
|
70
|
-
throw error;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
dispose(): void { clearTimeout(this.timer); }
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Runs one attempt under its own idle watch, cleared however it ends. */
|
|
77
|
-
export async function watchIdle<T>(outer: AbortSignal | undefined, ms: number | undefined, run: (idle: IdleWatch) => Promise<T>): Promise<T> {
|
|
78
|
-
const idle = new IdleWatch(outer, ms);
|
|
79
|
-
try { return await run(idle); } catch (error) { return idle.rethrow(error); } finally { idle.dispose(); }
|
|
80
|
-
}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/** Pi A / D8 / D11: direct and nested execution share one admitted, durable call contract. */
|
|
2
|
-
import type { AssistantBlock, ToolInvocationEvent } from "../core/events.ts";
|
|
3
|
-
import { failureDiagnostic, NOT_STARTED, UNKNOWN_EFFECT, ToolFault } from "../core/tool-failure.ts";
|
|
4
|
-
import { executionOutcome } from "../core/outcome.ts";
|
|
5
|
-
import { finishToolResult, nativeCommandContext, NATIVE_COMMAND_NAME, normalizeToolInput, type ToolContext, type ToolResult, type NestedInvocationOptions } from "./tools.ts";
|
|
6
|
-
import { ToolQueue } from "./tool-queue.ts";
|
|
7
|
-
|
|
8
|
-
export type BrokerCall = Extract<AssistantBlock, { type: "tool_use" }>;
|
|
9
|
-
type Mode = "parallel" | "exclusive";
|
|
10
|
-
/** Inherits the existing extension limit, now shared by the entire invocation tree. */
|
|
11
|
-
export const MAX_NESTED_CALLS = 32;
|
|
12
|
-
export const MAX_NESTED_DEPTH = 32;
|
|
13
|
-
/** Assumed per-call composition budget; direct large edits retain their existing interface. */
|
|
14
|
-
export const MAX_NESTED_ARGUMENT_BYTES = 64 * 1024;
|
|
15
|
-
/** Assumed log metadata bound; complete structured results are independent artifacts. */
|
|
16
|
-
export const MAX_INLINE_RESULT_DATA_BYTES = 64 * 1024;
|
|
17
|
-
|
|
18
|
-
export interface BrokerHost<Result extends ToolResult> {
|
|
19
|
-
parallelLimit: number;
|
|
20
|
-
callId(): string;
|
|
21
|
-
mode(call: BrokerCall): Mode;
|
|
22
|
-
invocation(call: BrokerCall, ctx: ToolContext, parent: ToolInvocationEvent | undefined): Promise<ToolInvocationEvent>;
|
|
23
|
-
/** Receives normalized input and must still apply admission, including Session-owned tools. */
|
|
24
|
-
prepare(call: BrokerCall, ctx: ToolContext): Promise<Result | (() => Promise<Result>)>;
|
|
25
|
-
/** Session annotations and branch receipts are accepted before their result becomes evidence. */
|
|
26
|
-
settle(result: Result): Promise<void>;
|
|
27
|
-
result(invocation: ToolInvocationEvent, result: Result): Promise<void>;
|
|
28
|
-
}
|
|
29
|
-
export interface BrokerReceipt<Result extends ToolResult> {
|
|
30
|
-
value: Result;
|
|
31
|
-
invocation: ToolInvocationEvent;
|
|
32
|
-
settle(): Promise<void>;
|
|
33
|
-
commit(): Promise<void>;
|
|
34
|
-
}
|
|
35
|
-
export type BrokerExecution<Result extends ToolResult> = (() => Promise<BrokerReceipt<Result>>) & { close(): Promise<void> };
|
|
36
|
-
type Budget = { calls: number };
|
|
37
|
-
type Parent = { invocation: ToolInvocationEvent; budget: Budget; depth: number };
|
|
38
|
-
|
|
39
|
-
/** Scheduling belongs to a live scope; immutable requests/results belong to the host log. */
|
|
40
|
-
export class ToolBroker<Result extends ToolResult = ToolResult> {
|
|
41
|
-
private readonly host: BrokerHost<Result>;
|
|
42
|
-
constructor(host: BrokerHost<Result>) { this.host = host; }
|
|
43
|
-
|
|
44
|
-
async prepare(call: BrokerCall, context: ToolContext, parent?: Parent): Promise<BrokerExecution<Result>> {
|
|
45
|
-
let actual = call;
|
|
46
|
-
let invalid: ToolResult | undefined;
|
|
47
|
-
try {
|
|
48
|
-
// Extension argument preparation cannot rewrite the already-recorded model
|
|
49
|
-
// request. Hooks receive the exact immutable value archived for admission.
|
|
50
|
-
actual = { ...call, input: freezeInput(structuredClone(normalizeToolInput(context, call.name, structuredClone(call.input)))) };
|
|
51
|
-
}
|
|
52
|
-
catch (error) { invalid = error instanceof ToolFault ? finishToolResult({ ok: false, content: error.message, diagnostic: error.diagnostic }) : refused(`Invalid ${call.name} arguments: ${String(error)}`); }
|
|
53
|
-
const invocation = await this.host.invocation(actual, context, parent?.invocation);
|
|
54
|
-
if (parent && invocation.input === undefined) invalid = refused("nested arguments exceed the inline composition budget or are not JSON; pass a bounded request or use the direct tool");
|
|
55
|
-
const budget = parent?.budget ?? { calls: 0 }, depth = (parent?.depth ?? 0) + 1;
|
|
56
|
-
const children = new ToolQueue(this.host.parallelLimit);
|
|
57
|
-
const pending = new Set<Promise<ToolResult>>();
|
|
58
|
-
const failures: string[] = [];
|
|
59
|
-
let incomplete = false;
|
|
60
|
-
const mode = this.host.mode(actual);
|
|
61
|
-
let open = true;
|
|
62
|
-
const invoke = (name: string, input: unknown, options?: NestedInvocationOptions, native = false): Promise<ToolResult> => {
|
|
63
|
-
if (!open) return Promise.resolve(refused("tool scope is closed; no invocation started"));
|
|
64
|
-
if (mode !== "exclusive") return Promise.resolve(refused("only an exclusive composing tool may request nested execution"));
|
|
65
|
-
if (depth >= MAX_NESTED_DEPTH || budget.calls >= MAX_NESTED_CALLS) return Promise.resolve(refused("nested tool budget exhausted; no invocation started"));
|
|
66
|
-
// Capture at submission, before an asynchronous queue can observe a
|
|
67
|
-
// caller's later mutation of the same object.
|
|
68
|
-
try { input = structuredClone(input); }
|
|
69
|
-
catch { return Promise.resolve(refused("nested arguments must be serializable; no invocation started")); }
|
|
70
|
-
budget.calls++;
|
|
71
|
-
const nested: BrokerCall = { type: "tool_use", call_id: this.host.callId(), name, input };
|
|
72
|
-
const signals = [ctx.signal, options?.signal].filter((signal): signal is AbortSignal => signal !== undefined);
|
|
73
|
-
const nestedContext: ToolContext = native ? nativeCommandContext(ctx, options?.signal) : { ...ctx,
|
|
74
|
-
...(signals.length ? { signal: AbortSignal.any(signals) } : {}) };
|
|
75
|
-
const result = children.submit(this.host.mode(nested), async () => {
|
|
76
|
-
const execute = await this.prepare(nested, nestedContext, { invocation, budget, depth });
|
|
77
|
-
return async () => {
|
|
78
|
-
const receipt = await execute();
|
|
79
|
-
await receipt.settle(); await receipt.commit();
|
|
80
|
-
if (!receipt.value.ok) failures.push(`nested call ${receipt.invocation.call_id} failed`);
|
|
81
|
-
return { ...receipt.value, invocation: { call_id: receipt.invocation.call_id, event: receipt.invocation.id } };
|
|
82
|
-
};
|
|
83
|
-
});
|
|
84
|
-
pending.add(result);
|
|
85
|
-
// The broker owns the lifetime even when extension code drops the promise.
|
|
86
|
-
void result.then(() => pending.delete(result), error => { incomplete = true; failures.push(`nested execution or persistence failed: ${String(error)}`); pending.delete(result); });
|
|
87
|
-
return result;
|
|
88
|
-
};
|
|
89
|
-
const ctx: ToolContext = { ...context, callId: call.call_id,
|
|
90
|
-
...(parent ? { parentCallId: parent.invocation.call_id } : {}),
|
|
91
|
-
invoke: (name, input, options) => invoke(name, input, options),
|
|
92
|
-
runOwnedCommand: execute => children.submit("exclusive", async () => execute),
|
|
93
|
-
drainNested: () => children.whenIdle(),
|
|
94
|
-
invokeHost: (input, signal) => invoke(NATIVE_COMMAND_NAME, input, signal ? { signal } : {}, true) };
|
|
95
|
-
let closing: Promise<void> | undefined;
|
|
96
|
-
const close = () => {
|
|
97
|
-
open = false;
|
|
98
|
-
return closing ??= (async () => { await children.close(); await Promise.allSettled(pending); })();
|
|
99
|
-
};
|
|
100
|
-
let prepared: ToolResult | (() => Promise<Result>);
|
|
101
|
-
try { prepared = invalid ?? await this.host.prepare(actual, ctx); }
|
|
102
|
-
catch (error) { prepared = finishToolResult({ ok: false, content: String(error), diagnostic: failureDiagnostic("unknown_effect", String(error), UNKNOWN_EFFECT) }); }
|
|
103
|
-
return Object.assign(async () => {
|
|
104
|
-
let value: Result;
|
|
105
|
-
try { value = (typeof prepared === "function" ? await prepared() : prepared) as Result; }
|
|
106
|
-
catch (error) {
|
|
107
|
-
value = finishToolResult({ ok: false, content: `tool ${call.name} failed: ${String(error)}`,
|
|
108
|
-
diagnostic: failureDiagnostic("unknown_effect", String(error), UNKNOWN_EFFECT) }) as Result;
|
|
109
|
-
} finally {
|
|
110
|
-
await close();
|
|
111
|
-
}
|
|
112
|
-
if (failures.length) value = { ...value, warnings: [...(value.warnings ?? []),
|
|
113
|
-
failureDiagnostic("unknown_effect", `${failures.length} nested failures; inspect the original invocation receipts. ${failures[0]}`, UNKNOWN_EFFECT)] };
|
|
114
|
-
if (incomplete) value = { ...value, ok: false, outcome: executionOutcome("unknown"),
|
|
115
|
-
diagnostic: failureDiagnostic("unknown_effect", "nested execution or persistence did not produce a complete receipt", UNKNOWN_EFFECT) };
|
|
116
|
-
let settled: Promise<void> | undefined, committed: Promise<void> | undefined;
|
|
117
|
-
const settle = () => settled ??= this.host.settle(value);
|
|
118
|
-
return { value, invocation, settle,
|
|
119
|
-
commit: () => committed ??= settle().then(() => this.host.result(invocation, value)) };
|
|
120
|
-
}, { close });
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function refused(message: string): ToolResult {
|
|
125
|
-
return finishToolResult({ ok: false, content: message, diagnostic: failureDiagnostic("invalid_input", message, NOT_STARTED) });
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function freezeInput(value: unknown, ancestors = new Set<unknown>()): unknown {
|
|
129
|
-
if (value !== null && typeof value === "object") {
|
|
130
|
-
if (ancestors.has(value)) throw new Error("cyclic tool arguments are not JSON");
|
|
131
|
-
ancestors.add(value);
|
|
132
|
-
for (const child of Object.values(value)) freezeInput(child, ancestors);
|
|
133
|
-
ancestors.delete(value);
|
|
134
|
-
Object.freeze(value);
|
|
135
|
-
}
|
|
136
|
-
return value;
|
|
137
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
/** R11: a harness-owned discovery entry shares normal Session admission and result persistence. */
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { discoverTools, MAX_DISCOVERY_QUERY, MAX_DISCOVERY_RESULTS, type DeclarationRequest } from "../core/tool-discovery.ts";
|
|
4
|
-
import { decideWithSessionRules } from "../core/permissions.ts";
|
|
5
|
-
import { failureDiagnostic, NOT_STARTED } from "../core/tool-failure.ts";
|
|
6
|
-
import type { ToolSchema } from "./provider.ts";
|
|
7
|
-
import type { ToolContext, ToolDefinition, ToolResult } from "./tools.ts";
|
|
8
|
-
|
|
9
|
-
export const DISCOVERY_TOOL_NAME = "tool_search";
|
|
10
|
-
export const DISCOVERY_TOOL: ToolDefinition = {
|
|
11
|
-
name: DISCOVERY_TOOL_NAME,
|
|
12
|
-
description: "Find optional registered tools by name or purpose. Reads the local catalog without connecting to services or executing tools. Returns purpose, important input types and declaration status. Matching permitted tools become declared only after the complete tool batch, for the next model request; then call them with their actual schema. Discovery does not grant execution permission or prove service health. Example: {\"query\":\"issue tracker\",\"limit\":3}.",
|
|
13
|
-
input_schema: { type: "object", properties: {
|
|
14
|
-
query: { type: "string", minLength: 1, maxLength: MAX_DISCOVERY_QUERY },
|
|
15
|
-
limit: { type: "integer", minimum: 1, maximum: MAX_DISCOVERY_RESULTS, description: "Maximum matches to request; defaults to 5." },
|
|
16
|
-
}, required: ["query"] },
|
|
17
|
-
access: "read", execution: "exclusive", branch: "none",
|
|
18
|
-
run: async () => ({ ok: false, content: "Tool discovery requires a live Session checkpoint." }),
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/** Bind a request to the observed contract; an edited/replaced registration needs another lookup. */
|
|
22
|
-
export function discoveryFingerprint(tool: ToolSchema): string {
|
|
23
|
-
return createHash("sha256").update(JSON.stringify({ name: tool.name, description: tool.description, input_schema: tool.input_schema })).digest("hex");
|
|
24
|
-
}
|
|
25
|
-
export function discoveryPermitted(ctx: ToolContext, name: string): boolean {
|
|
26
|
-
if (ctx.workflowMode === "plan" && ctx.extensions?.get(name)?.access !== "read") return false;
|
|
27
|
-
return decideWithSessionRules(ctx.rules ?? [], ctx.sessionRules ?? [], { tool: name }) !== "deny";
|
|
28
|
-
}
|
|
29
|
-
/** A directory entry without its matching live implementation is unavailable, regardless of history. */
|
|
30
|
-
export function currentDeferredTools(ctx: ToolContext): readonly ToolSchema[] {
|
|
31
|
-
return (ctx.deferredTools ?? []).filter(tool => {
|
|
32
|
-
const definition = ctx.extensions?.get(tool.name);
|
|
33
|
-
return definition !== undefined && discoveryFingerprint(definition) === discoveryFingerprint(tool);
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
export function searchDeferredTools(ctx: ToolContext, input: Record<string, unknown>, declared: readonly string[]): ToolResult {
|
|
37
|
-
const catalog = currentDeferredTools(ctx);
|
|
38
|
-
let matches;
|
|
39
|
-
try { matches = discoverTools(catalog, input["query"] as string, input["limit"] as number | undefined); }
|
|
40
|
-
catch {
|
|
41
|
-
const content = "Discovery requires a nonblank query of at most 256 characters and an integer limit from 1 to 8.";
|
|
42
|
-
return { ok: false, content, diagnostic: failureDiagnostic("invalid_input", content, NOT_STARTED) };
|
|
43
|
-
}
|
|
44
|
-
const requested: DeclarationRequest[] = [];
|
|
45
|
-
const result = matches.map(match => {
|
|
46
|
-
const tool = catalog.find(item => item.name === match.name)!;
|
|
47
|
-
const permitted = discoveryPermitted(ctx, match.name);
|
|
48
|
-
const status = !permitted ? "permission_denied" : declared.includes(match.name) ? "declared" : "requested_for_next_request";
|
|
49
|
-
if (status === "requested_for_next_request") requested.push({ name: tool.name, fingerprint: discoveryFingerprint(tool) });
|
|
50
|
-
const health = ctx.extensions?.get(match.name)?.integrationHealth?.();
|
|
51
|
-
return { ...match, declaration: status, service_health: health?.state ?? "not_checked", ...(health ? { service_observation: health } : {}) };
|
|
52
|
-
});
|
|
53
|
-
return { ok: true, content: JSON.stringify({ matches: result, note: result.length
|
|
54
|
-
? "Only declared tools can run. Requested schemas become available after the current batch at a checkpoint; execution permissions still apply."
|
|
55
|
-
: "No matching tools in the currently permitted discovery catalog. No service was contacted." }),
|
|
56
|
-
// Keep only small, original requests inline so cold recovery never depends on a generated summary.
|
|
57
|
-
data: { kind: "tool_discovery", version: 1, requested: requested.map(item => ({ ...item })) } };
|
|
58
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/** Pi A / O.5: ordered admission, bounded readers and an exclusive write barrier. */
|
|
2
|
-
import { schedulingWindow } from "../core/scheduling.ts";
|
|
3
|
-
|
|
4
|
-
type Mode = "parallel" | "exclusive";
|
|
5
|
-
type Job = { mode: Mode; prepare(): Promise<() => Promise<unknown>>; resolve(value: unknown): void; reject(error: unknown): void };
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* A composing call owns an exclusive slot in its parent queue and gives its
|
|
9
|
-
* children a fresh queue. Re-entering the held parent slot would deadlock.
|
|
10
|
-
* Only exclusive tools may compose; concurrent readers therefore cannot each
|
|
11
|
-
* multiply the session's parallel limit with another queue.
|
|
12
|
-
*/
|
|
13
|
-
export class ToolQueue {
|
|
14
|
-
private readonly limit: number;
|
|
15
|
-
private readonly pending: Job[] = [];
|
|
16
|
-
private active = 0;
|
|
17
|
-
private exclusive = false;
|
|
18
|
-
private pumping = false;
|
|
19
|
-
private readonly idle = new Set<() => void>();
|
|
20
|
-
private closed = false;
|
|
21
|
-
|
|
22
|
-
constructor(limit: number) {
|
|
23
|
-
schedulingWindow([], 0, limit);
|
|
24
|
-
this.limit = limit;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
submit<T>(mode: Mode, prepare: () => Promise<() => Promise<T>>): Promise<T> {
|
|
28
|
-
if (this.closed) return Promise.reject(new Error("tool scope is closed; no invocation started"));
|
|
29
|
-
const result = new Promise<T>((resolve, reject) => {
|
|
30
|
-
this.pending.push({ mode, prepare, resolve: value => resolve(value as T), reject });
|
|
31
|
-
});
|
|
32
|
-
this.kick();
|
|
33
|
-
return result;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
private kick(): void { if (!this.pumping) { this.pumping = true; void this.pump(); } }
|
|
37
|
-
|
|
38
|
-
private async pump(): Promise<void> {
|
|
39
|
-
try {
|
|
40
|
-
while (this.pending.length > 0) {
|
|
41
|
-
const next = this.pending[0]!;
|
|
42
|
-
const occupied: Mode[] = Array.from({ length: this.active }, () => this.exclusive ? "exclusive" : "parallel");
|
|
43
|
-
if (schedulingWindow([...occupied, next.mode], 0, this.limit) <= this.active) break;
|
|
44
|
-
const job = this.pending.shift()!;
|
|
45
|
-
this.active++; this.exclusive = job.mode === "exclusive";
|
|
46
|
-
const finished = () => { this.active--; if (job.mode === "exclusive") this.exclusive = false; this.kick(); };
|
|
47
|
-
try {
|
|
48
|
-
const execute = await job.prepare();
|
|
49
|
-
void Promise.resolve().then(execute).then(job.resolve, job.reject).finally(finished);
|
|
50
|
-
} catch (error) { job.reject(error); this.active--; this.exclusive = false; }
|
|
51
|
-
}
|
|
52
|
-
} finally {
|
|
53
|
-
this.pumping = false;
|
|
54
|
-
if (this.active === 0 && this.pending.length === 0) { for (const resolve of this.idle) resolve(); this.idle.clear(); }
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** A generator can retain its slot across admission/display yields without executing early. */
|
|
59
|
-
acquire(mode: Mode): Promise<() => void> {
|
|
60
|
-
return new Promise((resolve, reject) => {
|
|
61
|
-
let release!: () => void;
|
|
62
|
-
const done = new Promise<void>(finish => { release = finish; });
|
|
63
|
-
void this.submit(mode, async () => async () => { resolve(release); await done; }).catch(reject);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
whenIdle(): Promise<void> {
|
|
68
|
-
if (!this.pumping && this.active === 0 && this.pending.length === 0) return Promise.resolve();
|
|
69
|
-
return new Promise(resolve => this.idle.add(resolve));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Accepted jobs drain even when their caller forgot to await their promises. */
|
|
73
|
-
async close(): Promise<void> {
|
|
74
|
-
this.closed = true;
|
|
75
|
-
await this.whenIdle();
|
|
76
|
-
}
|
|
77
|
-
}
|