@astralyn/pi 1.4.5 → 1.4.7
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 +42 -0
- package/dist/bundle/chunks/chunk-FODCTZXW.js +230 -0
- package/dist/bundle/chunks/{chunk-I5S7H7GA.js → chunk-FYKNSJG6.js} +1 -1
- package/dist/bundle/chunks/{chunk-AXFWZIHH.js → chunk-MG5XDTXV.js} +1 -1
- package/dist/bundle/chunks/{chunk-ALHAFUHP.js → chunk-PU2IWWXV.js} +1 -1
- package/dist/bundle/chunks/{chunk-5M3YJ54Z.js → chunk-RO2VH6UZ.js} +1 -1
- package/dist/bundle/chunks/chunk-YJTSOLP2.js +1499 -0
- package/dist/bundle/chunks/{cli-FH77R7JR.js → cli-MEL7SGJD.js} +1 -1
- package/dist/bundle/chunks/{execute-GMFGC27P.js → execute-4CWRNHPO.js} +1 -1
- package/dist/bundle/chunks/{runtime-R5CMHJ5P.js → runtime-R3X67XMR.js} +1 -1
- package/dist/bundle/chunks/{virtual-modules-6FUV7ZDV.js → virtual-modules-PKEOKGYI.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/core/agent-session.d.ts +0 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +0 -4
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/isolated-session.d.ts +23 -0
- package/dist/core/isolated-session.d.ts.map +1 -0
- package/dist/core/isolated-session.js +55 -0
- package/dist/core/isolated-session.js.map +1 -0
- package/dist/core/package-manager.d.ts +7 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +72 -1
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resource-loader.d.ts +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +23 -7
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +4 -2
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +11 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-schema.d.ts +7 -0
- package/dist/core/settings-schema.d.ts.map +1 -1
- package/dist/core/settings-schema.js +6 -0
- package/dist/core/settings-schema.js.map +1 -1
- package/dist/core/skills.d.ts +10 -0
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +23 -0
- package/dist/core/skills.js.map +1 -1
- package/dist/core/tasks/codec.d.ts +12 -0
- package/dist/core/tasks/codec.d.ts.map +1 -0
- package/dist/core/tasks/codec.js +139 -0
- package/dist/core/tasks/codec.js.map +1 -0
- package/dist/core/tasks/delivery.d.ts +11 -4
- package/dist/core/tasks/delivery.d.ts.map +1 -1
- package/dist/core/tasks/delivery.js +9 -9
- package/dist/core/tasks/delivery.js.map +1 -1
- package/dist/core/tasks/execution.d.ts +71 -0
- package/dist/core/tasks/execution.d.ts.map +1 -0
- package/dist/core/tasks/execution.js +292 -0
- package/dist/core/tasks/execution.js.map +1 -0
- package/dist/core/tasks/format.d.ts +0 -2
- package/dist/core/tasks/format.d.ts.map +1 -1
- package/dist/core/tasks/format.js +1 -16
- package/dist/core/tasks/format.js.map +1 -1
- package/dist/core/tasks/limits.d.ts +12 -1
- package/dist/core/tasks/limits.d.ts.map +1 -1
- package/dist/core/tasks/limits.js +20 -1
- package/dist/core/tasks/limits.js.map +1 -1
- package/dist/core/tasks/output.d.ts +17 -17
- package/dist/core/tasks/output.d.ts.map +1 -1
- package/dist/core/tasks/output.js +72 -69
- package/dist/core/tasks/output.js.map +1 -1
- package/dist/core/tasks/presentation.d.ts +14 -5
- package/dist/core/tasks/presentation.d.ts.map +1 -1
- package/dist/core/tasks/presentation.js +56 -185
- package/dist/core/tasks/presentation.js.map +1 -1
- package/dist/core/tasks/record.d.ts +45 -0
- package/dist/core/tasks/record.d.ts.map +1 -0
- package/dist/core/tasks/record.js +80 -0
- package/dist/core/tasks/record.js.map +1 -0
- package/dist/core/tasks/runtime.d.ts +22 -22
- package/dist/core/tasks/runtime.d.ts.map +1 -1
- package/dist/core/tasks/runtime.js +170 -343
- package/dist/core/tasks/runtime.js.map +1 -1
- package/dist/core/tasks/scheduler.d.ts +16 -0
- package/dist/core/tasks/scheduler.d.ts.map +1 -0
- package/dist/core/tasks/scheduler.js +42 -0
- package/dist/core/tasks/scheduler.js.map +1 -0
- package/dist/core/tasks/session.d.ts +1 -1
- package/dist/core/tasks/session.d.ts.map +1 -1
- package/dist/core/tasks/session.js +22 -35
- package/dist/core/tasks/session.js.map +1 -1
- package/dist/core/tasks/store.d.ts +18 -25
- package/dist/core/tasks/store.d.ts.map +1 -1
- package/dist/core/tasks/store.js +45 -43
- package/dist/core/tasks/store.js.map +1 -1
- package/dist/core/tasks/types.d.ts +159 -70
- package/dist/core/tasks/types.d.ts.map +1 -1
- package/dist/core/tasks/types.js +3 -9
- package/dist/core/tasks/types.js.map +1 -1
- package/dist/core/tools/shell-execution.d.ts +8 -7
- package/dist/core/tools/shell-execution.d.ts.map +1 -1
- package/dist/core/tools/shell-execution.js +19 -27
- package/dist/core/tools/shell-execution.js.map +1 -1
- package/dist/core/tools/shell-tool.d.ts.map +1 -1
- package/dist/core/tools/shell-tool.js +42 -47
- package/dist/core/tools/shell-tool.js.map +1 -1
- package/dist/core/usage-totals.d.ts +4 -3
- package/dist/core/usage-totals.d.ts.map +1 -1
- package/dist/core/usage-totals.js +32 -8
- package/dist/core/usage-totals.js.map +1 -1
- package/dist/extensions/agent/activity.d.ts +4 -4
- package/dist/extensions/agent/activity.d.ts.map +1 -1
- package/dist/extensions/agent/activity.js +15 -24
- package/dist/extensions/agent/activity.js.map +1 -1
- package/dist/extensions/agent/index.d.ts +2 -11
- package/dist/extensions/agent/index.d.ts.map +1 -1
- package/dist/extensions/agent/index.js +3 -88
- package/dist/extensions/agent/index.js.map +1 -1
- package/dist/extensions/agent/progress.d.ts +31 -0
- package/dist/extensions/agent/progress.d.ts.map +1 -0
- package/dist/extensions/agent/progress.js +135 -0
- package/dist/extensions/agent/progress.js.map +1 -0
- package/dist/extensions/agent/render.d.ts.map +1 -1
- package/dist/extensions/agent/render.js +8 -17
- package/dist/extensions/agent/render.js.map +1 -1
- package/dist/extensions/agent/report.d.ts +9 -0
- package/dist/extensions/agent/report.d.ts.map +1 -0
- package/dist/extensions/agent/report.js +40 -0
- package/dist/extensions/agent/report.js.map +1 -0
- package/dist/extensions/agent/settings-panel.d.ts +43 -0
- package/dist/extensions/agent/settings-panel.d.ts.map +1 -0
- package/dist/extensions/agent/settings-panel.js +125 -0
- package/dist/extensions/agent/settings-panel.js.map +1 -0
- package/dist/extensions/agent/settings.d.ts +1 -1
- package/dist/extensions/agent/settings.d.ts.map +1 -1
- package/dist/extensions/agent/settings.js +101 -83
- package/dist/extensions/agent/settings.js.map +1 -1
- package/dist/extensions/agent/tool.d.ts +13 -0
- package/dist/extensions/agent/tool.d.ts.map +1 -0
- package/dist/extensions/agent/tool.js +88 -0
- package/dist/extensions/agent/tool.js.map +1 -0
- package/dist/extensions/agent/types.d.ts +19 -21
- package/dist/extensions/agent/types.d.ts.map +1 -1
- package/dist/extensions/agent/types.js +2 -10
- package/dist/extensions/agent/types.js.map +1 -1
- package/dist/extensions/agent/worker.d.ts +16 -0
- package/dist/extensions/agent/worker.d.ts.map +1 -0
- package/dist/extensions/agent/worker.js +66 -0
- package/dist/extensions/agent/worker.js.map +1 -0
- package/dist/extensions/btw/agent.d.ts +1 -1
- package/dist/extensions/btw/agent.d.ts.map +1 -1
- package/dist/extensions/btw/agent.js +3 -20
- package/dist/extensions/btw/agent.js.map +1 -1
- package/dist/extensions/cached-compaction/index.d.ts.map +1 -1
- package/dist/extensions/cached-compaction/index.js +3 -22
- package/dist/extensions/cached-compaction/index.js.map +1 -1
- package/dist/extensions/cached-compaction/render.d.ts +1 -0
- package/dist/extensions/cached-compaction/render.d.ts.map +1 -1
- package/dist/extensions/cached-compaction/render.js +42 -14
- package/dist/extensions/cached-compaction/render.js.map +1 -1
- package/dist/extensions/deepwiki/client.d.ts.map +1 -1
- package/dist/extensions/deepwiki/client.js +6 -78
- package/dist/extensions/deepwiki/client.js.map +1 -1
- package/dist/extensions/deepwiki/transport.d.ts +3 -0
- package/dist/extensions/deepwiki/transport.d.ts.map +1 -0
- package/dist/extensions/deepwiki/transport.js +96 -0
- package/dist/extensions/deepwiki/transport.js.map +1 -0
- package/dist/extensions/provider/ui/builtin-data.d.ts +3 -3
- package/dist/extensions/provider/ui/builtin-data.d.ts.map +1 -1
- package/dist/extensions/provider/ui/builtin-data.js.map +1 -1
- package/dist/extensions/provider/ui/compat.d.ts +3 -3
- package/dist/extensions/provider/ui/compat.d.ts.map +1 -1
- package/dist/extensions/provider/ui/compat.js.map +1 -1
- package/dist/extensions/provider/ui/controls.d.ts +2 -2
- package/dist/extensions/provider/ui/controls.d.ts.map +1 -1
- package/dist/extensions/provider/ui/controls.js.map +1 -1
- package/dist/extensions/provider/ui/dialogs.d.ts +3 -3
- package/dist/extensions/provider/ui/dialogs.d.ts.map +1 -1
- package/dist/extensions/provider/ui/dialogs.js.map +1 -1
- package/dist/extensions/provider/ui/dictionary.d.ts +2 -2
- package/dist/extensions/provider/ui/dictionary.d.ts.map +1 -1
- package/dist/extensions/provider/ui/dictionary.js.map +1 -1
- package/dist/extensions/provider/ui/editor.d.ts.map +1 -1
- package/dist/extensions/provider/ui/editor.js +1 -3
- package/dist/extensions/provider/ui/editor.js.map +1 -1
- package/dist/extensions/provider/ui/fetch-models.d.ts +2 -2
- package/dist/extensions/provider/ui/fetch-models.d.ts.map +1 -1
- package/dist/extensions/provider/ui/fetch-models.js.map +1 -1
- package/dist/extensions/provider/ui/model-fields.d.ts +2 -2
- package/dist/extensions/provider/ui/model-fields.d.ts.map +1 -1
- package/dist/extensions/provider/ui/model-fields.js.map +1 -1
- package/dist/extensions/provider/ui/model-options.d.ts +6 -6
- package/dist/extensions/provider/ui/model-options.d.ts.map +1 -1
- package/dist/extensions/provider/ui/model-options.js.map +1 -1
- package/dist/extensions/provider/ui/pane.d.ts +21 -12
- package/dist/extensions/provider/ui/pane.d.ts.map +1 -1
- package/dist/extensions/provider/ui/pane.js.map +1 -1
- package/dist/extensions/provider/ui/provider-fields.d.ts +3 -3
- package/dist/extensions/provider/ui/provider-fields.d.ts.map +1 -1
- package/dist/extensions/provider/ui/provider-fields.js.map +1 -1
- package/dist/extensions/provider/ui/thinking-map.d.ts +2 -2
- package/dist/extensions/provider/ui/thinking-map.d.ts.map +1 -1
- package/dist/extensions/provider/ui/thinking-map.js.map +1 -1
- package/dist/extensions/run-summary/format.js +1 -1
- package/dist/extensions/run-summary/format.js.map +1 -1
- package/dist/extensions/statusline/usage.d.ts.map +1 -1
- package/dist/extensions/statusline/usage.js +2 -6
- package/dist/extensions/statusline/usage.js.map +1 -1
- package/dist/extensions/tasks/tool/actions.d.ts +1 -1
- package/dist/extensions/tasks/tool/actions.d.ts.map +1 -1
- package/dist/extensions/tasks/tool/actions.js +50 -115
- package/dist/extensions/tasks/tool/actions.js.map +1 -1
- package/dist/extensions/tasks/tool/constants.d.ts +1 -6
- package/dist/extensions/tasks/tool/constants.d.ts.map +1 -1
- package/dist/extensions/tasks/tool/constants.js +1 -6
- package/dist/extensions/tasks/tool/constants.js.map +1 -1
- package/dist/extensions/tasks/tool/schema.d.ts +0 -3
- package/dist/extensions/tasks/tool/schema.d.ts.map +1 -1
- package/dist/extensions/tasks/tool/schema.js +5 -14
- package/dist/extensions/tasks/tool/schema.js.map +1 -1
- package/dist/extensions/tasks/tool/types.d.ts +2 -8
- package/dist/extensions/tasks/tool/types.d.ts.map +1 -1
- package/dist/extensions/tasks/tool/types.js +0 -2
- package/dist/extensions/tasks/tool/types.js.map +1 -1
- package/dist/extensions/tasks/tool.d.ts.map +1 -1
- package/dist/extensions/tasks/tool.js +2 -2
- package/dist/extensions/tasks/tool.js.map +1 -1
- package/dist/extensions/tasks/ui/completion-render.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/completion-render.js +46 -47
- package/dist/extensions/tasks/ui/completion-render.js.map +1 -1
- package/dist/extensions/tasks/ui/inspector.d.ts +5 -3
- package/dist/extensions/tasks/ui/inspector.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/inspector.js +18 -6
- package/dist/extensions/tasks/ui/inspector.js.map +1 -1
- package/dist/extensions/tasks/ui/layout.d.ts +7 -7
- package/dist/extensions/tasks/ui/layout.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/layout.js +118 -84
- package/dist/extensions/tasks/ui/layout.js.map +1 -1
- package/dist/extensions/tasks/ui/list.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/list.js +45 -31
- package/dist/extensions/tasks/ui/list.js.map +1 -1
- package/dist/extensions/tasks/ui/manager.d.ts +16 -7
- package/dist/extensions/tasks/ui/manager.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/manager.js +167 -129
- package/dist/extensions/tasks/ui/manager.js.map +1 -1
- package/dist/extensions/tasks/ui/model.d.ts +29 -0
- package/dist/extensions/tasks/ui/model.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/model.js +16 -1
- package/dist/extensions/tasks/ui/model.js.map +1 -1
- package/dist/extensions/tasks/ui/render.d.ts +1 -1
- package/dist/extensions/tasks/ui/render.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/render.js +12 -9
- package/dist/extensions/tasks/ui/render.js.map +1 -1
- package/dist/extensions/tasks/ui/status.d.ts +15 -0
- package/dist/extensions/tasks/ui/status.d.ts.map +1 -0
- package/dist/extensions/tasks/ui/status.js +16 -0
- package/dist/extensions/tasks/ui/status.js.map +1 -0
- package/dist/extensions/tasks/ui/tree.js +1 -1
- package/dist/extensions/tasks/ui/tree.js.map +1 -1
- package/dist/extensions/tasks/ui/views.d.ts +10 -5
- package/dist/extensions/tasks/ui/views.d.ts.map +1 -1
- package/dist/extensions/tasks/ui/views.js +61 -36
- package/dist/extensions/tasks/ui/views.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts +14 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +258 -7
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
- package/dist/modes/interactive/components/keybinding-hints.js +2 -1
- package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +7 -4
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/status-marker.d.ts +2 -1
- package/dist/modes/interactive/components/status-marker.d.ts.map +1 -1
- package/dist/modes/interactive/components/status-marker.js +0 -1
- package/dist/modes/interactive/components/status-marker.js.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +10 -3
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +7 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/duration.d.ts +3 -0
- package/dist/utils/duration.d.ts.map +1 -0
- package/dist/utils/duration.js +17 -0
- package/dist/utils/duration.js.map +1 -0
- package/dist/utils/number.d.ts +3 -0
- package/dist/utils/number.d.ts.map +1 -0
- package/dist/utils/number.js +5 -0
- package/dist/utils/number.js.map +1 -0
- package/docs/bundled/extensions/agent.md +14 -12
- package/docs/bundled/extensions/cached-compaction.md +13 -4
- package/docs/bundled/extensions/deepwiki.md +3 -1
- package/docs/bundled/extensions/statusline.md +1 -1
- package/docs/bundled/tasks.md +15 -14
- package/docs/extensions.md +26 -10
- package/docs/packages.md +5 -3
- package/docs/sdk.md +3 -3
- package/docs/session-format.md +28 -24
- package/docs/settings.md +11 -0
- package/docs/skills.md +23 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/schemas/settings.schema.json +36 -0
- package/dist/bundle/chunks/chunk-FSJ45CRP.js +0 -229
- package/dist/bundle/chunks/chunk-W3FIUMGM.js +0 -1501
- package/dist/core/tasks/history.d.ts +0 -4
- package/dist/core/tasks/history.d.ts.map +0 -1
- package/dist/core/tasks/history.js +0 -131
- package/dist/core/tasks/history.js.map +0 -1
- package/dist/extensions/agent/runner.d.ts +0 -24
- package/dist/extensions/agent/runner.d.ts.map +0 -1
- package/dist/extensions/agent/runner.js +0 -274
- package/dist/extensions/agent/runner.js.map +0 -1
- package/dist/extensions/deepwiki/http.d.ts +0 -13
- package/dist/extensions/deepwiki/http.d.ts.map +0 -1
- package/dist/extensions/deepwiki/http.js +0 -88
- package/dist/extensions/deepwiki/http.js.map +0 -1
- package/dist/extensions/tasks/ui/task-view.d.ts +0 -13
- package/dist/extensions/tasks/ui/task-view.d.ts.map +0 -1
- package/dist/extensions/tasks/ui/task-view.js +0 -20
- package/dist/extensions/tasks/ui/task-view.js.map +0 -1
|
@@ -1,40 +1,50 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
catch {
|
|
11
|
-
return "Task execution failed (unprintable error)";
|
|
12
|
-
}
|
|
13
|
-
}
|
|
2
|
+
import { finiteLimit } from "../../utils/number.js";
|
|
3
|
+
import { Execution } from "./execution.js";
|
|
4
|
+
import { boundWaitMs, TASKS_WAIT_DEFAULT_MS } from "./limits.js";
|
|
5
|
+
import { boundFacts, boundInput, boundText, errorText, readOutputSlice, sliceText, TASK_LABEL_BYTES, TASK_TITLE_BYTES, } from "./output.js";
|
|
6
|
+
import { TaskRecord } from "./record.js";
|
|
7
|
+
import { TaskScheduler } from "./scheduler.js";
|
|
8
|
+
import { TaskStore } from "./store.js";
|
|
9
|
+
import { TASK_BACKGROUND_REJECTION, TASK_KIND_PATTERN, TaskLookupError, } from "./types.js";
|
|
14
10
|
/** Session-local supervision. Executors own their processes, items and output files. */
|
|
15
11
|
export class TaskRuntime {
|
|
16
|
-
get closed() {
|
|
17
|
-
return this._closed;
|
|
18
|
-
}
|
|
19
|
-
get session() {
|
|
20
|
-
return this.options.session;
|
|
21
|
-
}
|
|
22
12
|
constructor(options = {}) {
|
|
23
13
|
this.executions = new Map();
|
|
14
|
+
this.scheduler = new TaskScheduler();
|
|
24
15
|
this.listeners = new Set();
|
|
25
16
|
this._closed = false;
|
|
26
|
-
this.pauses = 0;
|
|
27
17
|
this.options = options;
|
|
28
18
|
this.configuredEnabled = options.enabled ?? false;
|
|
29
|
-
this.
|
|
19
|
+
this.maxBackground = Math.max(1, finiteLimit(options.maxBackground, 8, 128));
|
|
30
20
|
this.maxHistory = finiteLimit(options.maxHistory, 32, 1024);
|
|
31
21
|
// Separate foreground and background histories, plus the protected-record allowance.
|
|
32
|
-
this.maxRetained = this.
|
|
22
|
+
this.maxRetained = this.maxBackground + 2 * Math.max(1, this.maxHistory) + this.maxHistory;
|
|
33
23
|
this.store = new TaskStore(this.maxHistory, this.maxRetained, () => this.closed, options.onCleanupError);
|
|
24
|
+
const runtime = this;
|
|
25
|
+
this.host = {
|
|
26
|
+
get closed() {
|
|
27
|
+
return runtime.closed;
|
|
28
|
+
},
|
|
29
|
+
emit: () => this.emit(),
|
|
30
|
+
settle: (task, usage) => this.options.onSettled?.(task, usage),
|
|
31
|
+
released: (execution) => {
|
|
32
|
+
this.executions.delete(execution.task.id);
|
|
33
|
+
this.scheduler.release(execution);
|
|
34
|
+
this.store.trim();
|
|
35
|
+
this.emit();
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
get closed() {
|
|
40
|
+
return this._closed;
|
|
41
|
+
}
|
|
42
|
+
get session() {
|
|
43
|
+
return this.options.session;
|
|
34
44
|
}
|
|
35
45
|
/**
|
|
36
|
-
* Rehydrate terminal
|
|
37
|
-
*
|
|
46
|
+
* Rehydrate terminal snapshots only, newest endedAt in each history (later input wins
|
|
47
|
+
* ties). Existing runtime records win ID collisions. No execution, accounting,
|
|
38
48
|
* notification, or deletion ownership is restored. Foreground records take their output
|
|
39
49
|
* from the branch's tool results. The host supplies the current branch; closed services
|
|
40
50
|
* ignore restoration.
|
|
@@ -49,16 +59,15 @@ export class TaskRuntime {
|
|
|
49
59
|
this.configuredEnabled = enabled;
|
|
50
60
|
this.emit();
|
|
51
61
|
}
|
|
52
|
-
/** Foreground work is bounded by its caller; only background work holds a slot. */
|
|
62
|
+
/** Foreground work is bounded by its caller; only background work, queued or running, holds a slot. */
|
|
63
|
+
activeBackground() {
|
|
64
|
+
return this.store.unsettled().filter((record) => record.task.mode === "background");
|
|
65
|
+
}
|
|
53
66
|
backgroundActive() {
|
|
54
|
-
|
|
55
|
-
for (const record of this.store.records.values())
|
|
56
|
-
if (!record.settled && record.task.mode === "background")
|
|
57
|
-
count++;
|
|
58
|
-
return count;
|
|
67
|
+
return this.activeBackground().length;
|
|
59
68
|
}
|
|
60
69
|
limitError() {
|
|
61
|
-
return new Error(`Background task limit reached (${this.
|
|
70
|
+
return new Error(`Background task limit reached (${this.maxBackground}); stop or wait for a background task, or run in the foreground.`);
|
|
62
71
|
}
|
|
63
72
|
async execute(execution) {
|
|
64
73
|
// Admission and registration happen synchronously, before invoking user code or awaiting anything.
|
|
@@ -69,302 +78,106 @@ export class TaskRuntime {
|
|
|
69
78
|
if (execution.background) {
|
|
70
79
|
if (!this.enabled)
|
|
71
80
|
throw new Error("Background execution is not available in this host");
|
|
72
|
-
if (this.backgroundActive() >= this.
|
|
81
|
+
if (this.backgroundActive() >= this.maxBackground)
|
|
73
82
|
throw this.limitError();
|
|
74
|
-
if (this.store.
|
|
83
|
+
if (this.store.retainedCount() >= this.maxRetained) {
|
|
75
84
|
throw new Error("Task history retention limit reached; deliver pending notifications or release pinned or claimed records");
|
|
76
85
|
}
|
|
77
86
|
}
|
|
78
87
|
if (execution.signal?.aborted)
|
|
79
88
|
throw execution.signal.reason ?? new Error("Execution aborted");
|
|
80
|
-
if (
|
|
89
|
+
if (!TASK_KIND_PATTERN.test(execution.kind))
|
|
81
90
|
throw new Error("Invalid task source");
|
|
91
|
+
const concurrency = execution.concurrency;
|
|
92
|
+
if (concurrency && (!concurrency.key || !Number.isSafeInteger(concurrency.limit) || concurrency.limit < 1))
|
|
93
|
+
throw new Error("Invalid task concurrency");
|
|
82
94
|
const anchorId = this.options.anchor?.() ?? null;
|
|
83
95
|
if (anchorId !== null && Buffer.byteLength(anchorId) > 8192)
|
|
84
96
|
throw new Error("Task branch anchor is too large");
|
|
85
|
-
const info = execution.info === undefined ? undefined : boundText(execution.info, TASK_INFO_BYTES);
|
|
86
97
|
const task = {
|
|
87
98
|
id: `${execution.kind}-${randomUUID()}`,
|
|
88
99
|
kind: execution.kind,
|
|
100
|
+
label: boundText(execution.label ?? execution.kind, TASK_LABEL_BYTES),
|
|
89
101
|
title: boundText(execution.title, TASK_TITLE_BYTES),
|
|
102
|
+
output: execution.output ?? "log",
|
|
103
|
+
...(execution.input ? { input: boundInput(execution.input) } : {}),
|
|
104
|
+
...(execution.facts?.length ? { facts: boundFacts(execution.facts) } : {}),
|
|
90
105
|
toolCallId: boundText(execution.toolCallId, 512),
|
|
91
106
|
anchorId,
|
|
92
107
|
mode: execution.background ? "background" : "foreground",
|
|
93
108
|
status: "queued",
|
|
94
109
|
startedAt: Date.now(),
|
|
95
|
-
command: execution.command === undefined ? undefined : boundText(execution.command, 8192),
|
|
96
|
-
commandTruncated: execution.command === undefined ? undefined : boundText(execution.command, 8192) !== execution.command,
|
|
97
|
-
cwd: execution.cwd === undefined ? undefined : boundText(execution.cwd, 4096),
|
|
98
|
-
info: info === undefined ? undefined : { text: info, truncated: info !== execution.info },
|
|
99
|
-
};
|
|
100
|
-
let resolveCaller;
|
|
101
|
-
let rejectCaller;
|
|
102
|
-
const caller = new Promise((resolve, reject) => {
|
|
103
|
-
resolveCaller = resolve;
|
|
104
|
-
rejectCaller = reject;
|
|
105
|
-
});
|
|
106
|
-
let resolveDone;
|
|
107
|
-
const record = {
|
|
108
|
-
task,
|
|
109
|
-
settled: false,
|
|
110
|
-
visible: true,
|
|
111
|
-
delivery: "none",
|
|
112
|
-
pins: 0,
|
|
113
|
-
deliveryHolds: 0,
|
|
114
|
-
waiters: new Set(),
|
|
115
110
|
};
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
handedOff: false,
|
|
120
|
-
detachRequested: execution.background ?? false,
|
|
121
|
-
modeListeners: new Set(),
|
|
122
|
-
removeParent: () => { },
|
|
123
|
-
handoff: () => {
|
|
124
|
-
if (!active.accepted ||
|
|
125
|
-
!active.detachRequested ||
|
|
126
|
-
record.settled ||
|
|
127
|
-
active.handedOff ||
|
|
128
|
-
this.closed ||
|
|
129
|
-
!record.visible ||
|
|
130
|
-
active.controller.signal.aborted)
|
|
131
|
-
return;
|
|
132
|
-
active.handedOff = true;
|
|
133
|
-
task.mode = "background";
|
|
134
|
-
changed(record);
|
|
135
|
-
active.removeParent();
|
|
136
|
-
resolveCaller({ kind: "background", task: snapshotOf(record) });
|
|
137
|
-
this.emit();
|
|
138
|
-
},
|
|
139
|
-
done: new Promise((resolve) => {
|
|
140
|
-
resolveDone = resolve;
|
|
141
|
-
}),
|
|
142
|
-
};
|
|
143
|
-
const parentSignal = execution.signal;
|
|
144
|
-
let onUpdate = execution.background ? undefined : execution.onUpdate;
|
|
145
|
-
const parentAbort = () => {
|
|
146
|
-
if (!active.detachRequested || !active.accepted)
|
|
147
|
-
this.cancel(record);
|
|
148
|
-
};
|
|
149
|
-
parentSignal?.addEventListener("abort", parentAbort, { once: true });
|
|
150
|
-
active.removeParent = () => {
|
|
151
|
-
parentSignal?.removeEventListener("abort", parentAbort);
|
|
152
|
-
onUpdate = undefined;
|
|
153
|
-
};
|
|
154
|
-
this.store.records.set(task.id, record);
|
|
111
|
+
const record = new TaskRecord(task);
|
|
112
|
+
const active = new Execution(this.host, record, execution);
|
|
113
|
+
this.store.add(record);
|
|
155
114
|
this.executions.set(task.id, active);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
return task.mode;
|
|
161
|
-
},
|
|
162
|
-
onModeChange: (listener) => {
|
|
163
|
-
if (record.settled || this.closed)
|
|
164
|
-
return () => { };
|
|
165
|
-
active.modeListeners.add(listener);
|
|
166
|
-
return () => {
|
|
167
|
-
active.modeListeners.delete(listener);
|
|
168
|
-
};
|
|
169
|
-
},
|
|
170
|
-
requestCancel: () => this.cancel(record),
|
|
171
|
-
accept: () => {
|
|
172
|
-
if (record.settled || active.accepted || this.closed || active.controller.signal.aborted)
|
|
173
|
-
return;
|
|
174
|
-
active.accepted = true;
|
|
175
|
-
if (task.status === "queued")
|
|
176
|
-
task.status = "running";
|
|
177
|
-
changed(record);
|
|
178
|
-
if (active.detachRequested)
|
|
179
|
-
active.removeParent();
|
|
180
|
-
// Let an already available final result win over a handoff.
|
|
181
|
-
queueMicrotask(() => queueMicrotask(active.handoff));
|
|
182
|
-
this.emit();
|
|
183
|
-
},
|
|
184
|
-
publish: (result) => {
|
|
185
|
-
if (record.settled || this.closed)
|
|
186
|
-
return;
|
|
187
|
-
retainResult(task, result);
|
|
188
|
-
changed(record);
|
|
189
|
-
if (result.usage !== undefined)
|
|
190
|
-
active.publishedUsage = structuredClone(result.usage);
|
|
191
|
-
if (!active.detachRequested && !active.handedOff) {
|
|
192
|
-
try {
|
|
193
|
-
onUpdate?.(result);
|
|
194
|
-
}
|
|
195
|
-
catch {
|
|
196
|
-
/* UI observers cannot stop execution. */
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
this.emit();
|
|
200
|
-
},
|
|
201
|
-
setOutputPath: (path, cleanup) => {
|
|
202
|
-
if (record.settled) {
|
|
203
|
-
if (cleanup)
|
|
204
|
-
throw new Error("Task execution has settled");
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
if (record.cleanup)
|
|
208
|
-
throw new Error("Managed output is already registered");
|
|
209
|
-
// Never truncate a real filesystem path into a different path.
|
|
210
|
-
if (Buffer.byteLength(path) > 8192)
|
|
211
|
-
throw new Error("Task output path is too large");
|
|
212
|
-
record.cleanup = cleanup;
|
|
213
|
-
task.outputPath = path;
|
|
214
|
-
changed(record);
|
|
215
|
-
this.emit();
|
|
216
|
-
},
|
|
217
|
-
};
|
|
218
|
-
const finish = (completion, error) => {
|
|
219
|
-
if (record.settled)
|
|
220
|
-
return;
|
|
221
|
-
record.settled = true;
|
|
222
|
-
active.modeListeners.clear();
|
|
223
|
-
active.removeParent();
|
|
224
|
-
task.endedAt = Date.now();
|
|
225
|
-
const failed = completion === undefined;
|
|
226
|
-
task.status =
|
|
227
|
-
completion?.status ??
|
|
228
|
-
(error instanceof TaskExecutionError
|
|
229
|
-
? error.status
|
|
230
|
-
: active.controller.signal.aborted
|
|
231
|
-
? "cancelled"
|
|
232
|
-
: failed
|
|
233
|
-
? "failed"
|
|
234
|
-
: "completed");
|
|
235
|
-
if (completion) {
|
|
236
|
-
retainResult(task, completion.result);
|
|
237
|
-
if (completion.error !== undefined)
|
|
238
|
-
task.error = boundText(completion.error, 4096);
|
|
239
|
-
if (completion.exitCode !== undefined)
|
|
240
|
-
task.exitCode = completion.exitCode;
|
|
241
|
-
}
|
|
242
|
-
if (failed) {
|
|
243
|
-
task.error = errorText(error);
|
|
244
|
-
retainResult(task, { content: [{ type: "text", text: task.error }, ...(task.result?.content ?? [])], details: undefined }, task.resultTruncated);
|
|
245
|
-
}
|
|
246
|
-
changed(record);
|
|
247
|
-
let settlementWarning;
|
|
248
|
-
try {
|
|
249
|
-
this.options.onSettled?.(snapshotOf(record), completion?.usage ?? completion?.result.usage ?? active.publishedUsage);
|
|
250
|
-
}
|
|
251
|
-
catch (accountingError) {
|
|
252
|
-
const warning = `Usage settlement failed: ${errorText(accountingError)}`;
|
|
253
|
-
settlementWarning = warning;
|
|
254
|
-
task.error = boundText([task.error, warning].filter(Boolean).join("\n"), 8192);
|
|
255
|
-
retainResult(task, { content: [{ type: "text", text: warning }, ...(task.result?.content ?? [])], details: undefined }, task.resultTruncated);
|
|
256
|
-
changed(record);
|
|
257
|
-
}
|
|
258
|
-
active.publishedUsage = undefined;
|
|
259
|
-
// Delivery becomes eligible only after settlement is persisted.
|
|
260
|
-
record.delivery = active.handedOff ? "pending" : "delivered";
|
|
261
|
-
if (!active.handedOff) {
|
|
262
|
-
if (failed)
|
|
263
|
-
rejectCaller(error);
|
|
264
|
-
else {
|
|
265
|
-
const { usage: _usage, ...result } = completion.result;
|
|
266
|
-
if (settlementWarning)
|
|
267
|
-
result.content = [{ type: "text", text: settlementWarning }, ...result.content];
|
|
268
|
-
resolveCaller({ kind: "result", result, status: task.status, error: task.error });
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
for (const waiter of [...record.waiters])
|
|
272
|
-
waiter();
|
|
273
|
-
resolveDone();
|
|
274
|
-
this.executions.delete(task.id);
|
|
275
|
-
this.store.trim();
|
|
276
|
-
this.emit();
|
|
277
|
-
};
|
|
278
|
-
// The rejection handler is installed immediately, including for synchronous preflight throws.
|
|
279
|
-
try {
|
|
280
|
-
const running = execution.run(control);
|
|
281
|
-
void Promise.resolve(running).then((result) => finish(result), (error) => finish(undefined, error));
|
|
282
|
-
}
|
|
283
|
-
catch (error) {
|
|
284
|
-
finish(undefined, error);
|
|
285
|
-
}
|
|
115
|
+
this.scheduler.schedule(active);
|
|
116
|
+
// Queued background work returns at once; started work hands off after its own accept().
|
|
117
|
+
if (active.queued)
|
|
118
|
+
active.handoff();
|
|
286
119
|
this.emit();
|
|
287
|
-
return caller;
|
|
120
|
+
return active.caller;
|
|
288
121
|
}
|
|
289
122
|
detachable(record) {
|
|
290
|
-
|
|
291
|
-
return !!active && !record.settled && !active.detachRequested && !active.controller.signal.aborted;
|
|
123
|
+
return this.executions.get(record.task.id)?.detachable ?? false;
|
|
292
124
|
}
|
|
293
125
|
detachForeground() {
|
|
294
126
|
if (!this.enabled)
|
|
295
127
|
return 0;
|
|
296
|
-
const eligible = [...this.
|
|
297
|
-
.filter((
|
|
128
|
+
const eligible = [...this.executions.values()]
|
|
129
|
+
.filter((execution) => execution.detachable)
|
|
298
130
|
.sort((left, right) => left.task.startedAt - right.task.startedAt);
|
|
299
131
|
if (!eligible.length)
|
|
300
132
|
return 0;
|
|
301
|
-
const free = this.
|
|
133
|
+
const free = this.maxBackground - this.backgroundActive();
|
|
302
134
|
if (free <= 0)
|
|
303
135
|
throw this.limitError();
|
|
304
|
-
const
|
|
305
|
-
for (const
|
|
306
|
-
|
|
307
|
-
for (const
|
|
308
|
-
|
|
136
|
+
const executions = eligible.slice(0, free);
|
|
137
|
+
for (const execution of executions)
|
|
138
|
+
execution.requestDetach();
|
|
139
|
+
for (const execution of executions)
|
|
140
|
+
execution.completeDetach();
|
|
309
141
|
this.emit();
|
|
310
|
-
return
|
|
142
|
+
return executions.length;
|
|
311
143
|
}
|
|
312
144
|
canDetach(id) {
|
|
313
145
|
const record = this.lookup(id);
|
|
314
|
-
return this.enabled && this.backgroundActive() < this.
|
|
146
|
+
return this.enabled && this.backgroundActive() < this.maxBackground && this.detachable(record);
|
|
315
147
|
}
|
|
316
148
|
detach(id) {
|
|
317
149
|
const record = this.lookup(id);
|
|
318
150
|
if (!this.enabled || !this.detachable(record))
|
|
319
151
|
return false;
|
|
320
|
-
if (this.backgroundActive() >= this.
|
|
152
|
+
if (this.backgroundActive() >= this.maxBackground)
|
|
321
153
|
throw this.limitError();
|
|
322
|
-
this.
|
|
323
|
-
|
|
154
|
+
const execution = this.executions.get(record.task.id);
|
|
155
|
+
execution.requestDetach();
|
|
156
|
+
execution.completeDetach();
|
|
324
157
|
this.emit();
|
|
325
158
|
return true;
|
|
326
159
|
}
|
|
327
|
-
prepareDetach(record) {
|
|
328
|
-
const active = this.executions.get(record.task.id);
|
|
329
|
-
active.detachRequested = true;
|
|
330
|
-
record.task.mode = "background";
|
|
331
|
-
changed(record);
|
|
332
|
-
if (active.accepted)
|
|
333
|
-
active.removeParent();
|
|
334
|
-
}
|
|
335
|
-
finishDetach(record) {
|
|
336
|
-
const active = this.executions.get(record.task.id);
|
|
337
|
-
active.handoff();
|
|
338
|
-
for (const listener of active.modeListeners) {
|
|
339
|
-
try {
|
|
340
|
-
listener(record.task.mode);
|
|
341
|
-
}
|
|
342
|
-
catch {
|
|
343
|
-
/* Executors own their diagnostics. */
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
160
|
list() {
|
|
348
|
-
return
|
|
161
|
+
return this.store.visible().map((record) => record.snapshot());
|
|
349
162
|
}
|
|
350
163
|
lookup(id) {
|
|
351
|
-
const exact = this.store.
|
|
164
|
+
const exact = this.store.get(id);
|
|
352
165
|
if (exact)
|
|
353
166
|
return exact;
|
|
354
|
-
const matches =
|
|
167
|
+
const matches = this.store.match(id);
|
|
355
168
|
if (!id || matches.length !== 1) {
|
|
356
|
-
throw new TaskLookupError(matches.length > 1 ? "ambiguous" : "unknown", matches.map(
|
|
169
|
+
throw new TaskLookupError(matches.length > 1 ? "ambiguous" : "unknown", matches.map((record) => record.snapshot()));
|
|
357
170
|
}
|
|
358
171
|
return matches[0];
|
|
359
172
|
}
|
|
360
173
|
get(id) {
|
|
361
|
-
return
|
|
174
|
+
return this.lookup(id).snapshot();
|
|
362
175
|
}
|
|
363
176
|
async read(id, options = {}) {
|
|
364
177
|
const record = this.lookup(id);
|
|
365
178
|
const release = this.retain(record.task.id);
|
|
366
179
|
try {
|
|
367
|
-
const task =
|
|
180
|
+
const task = record.snapshot();
|
|
368
181
|
if (task.outputPath) {
|
|
369
182
|
try {
|
|
370
183
|
return { task, ...(await readOutputSlice(task.outputPath, options)) };
|
|
@@ -384,12 +197,7 @@ export class TaskRuntime {
|
|
|
384
197
|
}
|
|
385
198
|
}
|
|
386
199
|
resultText(task) {
|
|
387
|
-
return
|
|
388
|
-
.filter((block) => block.type === "text")
|
|
389
|
-
.map((block) => block.text)
|
|
390
|
-
.join("\n") ||
|
|
391
|
-
task.error ||
|
|
392
|
-
"No output yet.");
|
|
200
|
+
return task.result?.text || task.error || "No output yet.";
|
|
393
201
|
}
|
|
394
202
|
/** One event-driven waiter can observe a whole waitAny cohort without per-task timers. */
|
|
395
203
|
waitForRecords(records, timeoutMs, signal, interruptSignal) {
|
|
@@ -422,47 +230,82 @@ export class TaskRuntime {
|
|
|
422
230
|
record.waiters.add(wake);
|
|
423
231
|
signal?.addEventListener("abort", abort, { once: true });
|
|
424
232
|
interruptSignal?.addEventListener("abort", interrupt, { once: true });
|
|
425
|
-
timer = setTimeout(wake,
|
|
233
|
+
timer = setTimeout(wake, boundWaitMs(timeoutMs));
|
|
426
234
|
if (this.closed || interruptSignal?.aborted)
|
|
427
235
|
wake();
|
|
428
236
|
if (signal?.aborted)
|
|
429
237
|
abort();
|
|
430
238
|
});
|
|
431
239
|
}
|
|
432
|
-
/**
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
240
|
+
/**
|
|
241
|
+
* One observation: hold delivery for the observed records, wait, read, then register the
|
|
242
|
+
* receipt with the host, and only then release. Nothing the caller does between those steps
|
|
243
|
+
* can leak a settled result into an automatic notification or lose it.
|
|
244
|
+
*/
|
|
245
|
+
async observe(records, options,
|
|
246
|
+
/** Record to read when none settled; a cohort has none. */
|
|
247
|
+
fallback) {
|
|
248
|
+
const { signal } = options;
|
|
249
|
+
const releases = records.map((record) => this.holdRecord(record));
|
|
250
|
+
try {
|
|
251
|
+
const start = Date.now();
|
|
252
|
+
const interrupt = this.options.waits?.interruptSignal;
|
|
253
|
+
await this.waitForRecords(records, options.timeoutMs ?? TASKS_WAIT_DEFAULT_MS, signal, interrupt);
|
|
254
|
+
const waitedMs = Date.now() - start;
|
|
255
|
+
const settled = records
|
|
256
|
+
.filter((record) => record.settled)
|
|
257
|
+
.sort((left, right) => (left.task.endedAt ?? 0) - (right.task.endedAt ?? 0))[0];
|
|
258
|
+
const outcome = settled
|
|
259
|
+
? "settled"
|
|
260
|
+
: this.closed
|
|
261
|
+
? "closed"
|
|
262
|
+
: interrupt?.aborted
|
|
263
|
+
? "interrupted"
|
|
264
|
+
: "timed_out";
|
|
265
|
+
const running = records
|
|
266
|
+
.filter((record) => record.visible && !record.settled)
|
|
267
|
+
.map((record) => record.snapshot());
|
|
268
|
+
const observed = settled ?? fallback;
|
|
269
|
+
const read = options.read && observed && outcome !== "interrupted"
|
|
270
|
+
? await this.read(observed.task.id, options.read)
|
|
271
|
+
: undefined;
|
|
272
|
+
signal?.throwIfAborted();
|
|
273
|
+
if (settled && settled.task.mode === "background" && options.toolCallId)
|
|
274
|
+
this.options.waits?.bind(options.toolCallId, settled.task.id);
|
|
275
|
+
return { outcome, waitedMs, settled, running, read };
|
|
276
|
+
}
|
|
277
|
+
finally {
|
|
278
|
+
for (const release of releases)
|
|
279
|
+
release();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async wait(id, options = {}) {
|
|
283
|
+
if (options.signal?.aborted)
|
|
284
|
+
throw options.signal.reason ?? new Error("Wait aborted");
|
|
436
285
|
const record = this.lookup(id);
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
286
|
+
const { outcome, waitedMs, read } = await this.observe([record], options, record);
|
|
287
|
+
return { outcome, waitedMs, task: record.snapshot(), ...(read ? { read } : {}) };
|
|
288
|
+
}
|
|
289
|
+
async waitAny(options = {}) {
|
|
290
|
+
if (options.signal?.aborted)
|
|
291
|
+
throw options.signal.reason ?? new Error("Wait aborted");
|
|
292
|
+
const records = this.activeBackground().filter((record) => record.visible);
|
|
293
|
+
if (records.length === 0)
|
|
294
|
+
return { outcome: "no_tasks", waitedMs: 0, running: [] };
|
|
295
|
+
const { outcome, waitedMs, settled, running, read } = await this.observe(records, options);
|
|
296
|
+
return {
|
|
297
|
+
outcome,
|
|
298
|
+
waitedMs,
|
|
299
|
+
running,
|
|
300
|
+
...(settled ? { task: settled.snapshot() } : {}),
|
|
301
|
+
...(read ? { read } : {}),
|
|
302
|
+
};
|
|
453
303
|
}
|
|
454
304
|
kill(id) {
|
|
455
305
|
return this.cancel(this.lookup(id));
|
|
456
306
|
}
|
|
457
307
|
cancel(record) {
|
|
458
|
-
|
|
459
|
-
if (!active || record.settled || active.controller.signal.aborted)
|
|
460
|
-
return false;
|
|
461
|
-
record.task.status = "stopping";
|
|
462
|
-
changed(record);
|
|
463
|
-
active.controller.abort();
|
|
464
|
-
this.emit();
|
|
465
|
-
return true;
|
|
308
|
+
return this.executions.get(record.task.id)?.cancel() ?? false;
|
|
466
309
|
}
|
|
467
310
|
subscribe(listener) {
|
|
468
311
|
if (this.closed)
|
|
@@ -476,7 +319,9 @@ export class TaskRuntime {
|
|
|
476
319
|
* Retention protects snapshots and logs only. Delivery has its own explicit hold.
|
|
477
320
|
*/
|
|
478
321
|
retain(id) {
|
|
479
|
-
|
|
322
|
+
return this.pin(this.lookup(id));
|
|
323
|
+
}
|
|
324
|
+
pin(record) {
|
|
480
325
|
record.pins++;
|
|
481
326
|
let released = false;
|
|
482
327
|
return () => {
|
|
@@ -485,13 +330,16 @@ export class TaskRuntime {
|
|
|
485
330
|
released = true;
|
|
486
331
|
record.pins--;
|
|
487
332
|
this.store.trim();
|
|
488
|
-
|
|
333
|
+
// Listeners decide whether delivery may proceed; TaskDelivery owns pausing.
|
|
334
|
+
if (!this.closed && record.deliverable)
|
|
489
335
|
this.emit();
|
|
490
336
|
};
|
|
491
337
|
}
|
|
492
338
|
holdDelivery(id) {
|
|
493
|
-
|
|
494
|
-
|
|
339
|
+
return this.holdRecord(this.lookup(id));
|
|
340
|
+
}
|
|
341
|
+
holdRecord(record) {
|
|
342
|
+
const releasePin = this.pin(record);
|
|
495
343
|
record.deliveryHolds++;
|
|
496
344
|
let released = false;
|
|
497
345
|
return () => {
|
|
@@ -499,50 +347,27 @@ export class TaskRuntime {
|
|
|
499
347
|
return;
|
|
500
348
|
released = true;
|
|
501
349
|
record.deliveryHolds--;
|
|
502
|
-
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
pause() {
|
|
506
|
-
this.pauses++;
|
|
507
|
-
let resumed = false;
|
|
508
|
-
return () => {
|
|
509
|
-
if (resumed)
|
|
510
|
-
return;
|
|
511
|
-
resumed = true;
|
|
512
|
-
this.pauses--;
|
|
513
|
-
this.emit();
|
|
350
|
+
releasePin();
|
|
514
351
|
};
|
|
515
352
|
}
|
|
516
353
|
pendingNotifications() {
|
|
517
|
-
if (this.closed
|
|
354
|
+
if (this.closed)
|
|
518
355
|
return [];
|
|
519
|
-
return
|
|
520
|
-
}
|
|
521
|
-
/** Only explicit result-delivery holds delay an automatic completion. */
|
|
522
|
-
candidate(record) {
|
|
523
|
-
return record.visible && record.delivery === "pending" && record.waiters.size === 0 && record.deliveryHolds === 0;
|
|
356
|
+
return this.store.deliverable().map((record) => record.snapshot());
|
|
524
357
|
}
|
|
525
358
|
claimNotification(id) {
|
|
526
|
-
|
|
527
|
-
return false;
|
|
528
|
-
const record = this.store.records.get(id);
|
|
529
|
-
if (!record || !this.candidate(record))
|
|
530
|
-
return false;
|
|
531
|
-
record.delivery = "claimed";
|
|
532
|
-
return true;
|
|
359
|
+
return !this.closed && (this.store.get(id)?.claim() ?? false);
|
|
533
360
|
}
|
|
534
361
|
markDelivered(id) {
|
|
535
|
-
const record = this.store.
|
|
362
|
+
const record = this.store.get(id);
|
|
536
363
|
if (!record)
|
|
537
364
|
return;
|
|
538
|
-
record.
|
|
365
|
+
record.acknowledge();
|
|
539
366
|
this.store.trim();
|
|
540
367
|
}
|
|
541
368
|
releaseNotification(id) {
|
|
542
|
-
|
|
543
|
-
if (!record || record.delivery !== "claimed")
|
|
369
|
+
if (!this.store.get(id)?.unclaim())
|
|
544
370
|
return;
|
|
545
|
-
record.delivery = "pending";
|
|
546
371
|
this.store.trim();
|
|
547
372
|
this.emit();
|
|
548
373
|
}
|
|
@@ -551,8 +376,8 @@ export class TaskRuntime {
|
|
|
551
376
|
return;
|
|
552
377
|
this._closed = true;
|
|
553
378
|
this.listeners.clear();
|
|
554
|
-
for (const record of this.store.
|
|
555
|
-
this.executions.get(record.task.id)?.
|
|
379
|
+
for (const record of this.store.all()) {
|
|
380
|
+
this.executions.get(record.task.id)?.releaseParent();
|
|
556
381
|
this.cancel(record);
|
|
557
382
|
for (const waiter of [...record.waiters])
|
|
558
383
|
waiter();
|
|
@@ -561,22 +386,24 @@ export class TaskRuntime {
|
|
|
561
386
|
}
|
|
562
387
|
async shutdown(graceMs = 2000) {
|
|
563
388
|
this.close();
|
|
564
|
-
await this.drain(
|
|
389
|
+
await this.drain(this.store.all(), graceMs);
|
|
565
390
|
}
|
|
566
391
|
/** Running tasks whose launch anchor is outside the branch made of `ancestors`. */
|
|
567
392
|
activeOutsideBranch(ancestors) {
|
|
568
|
-
return
|
|
569
|
-
.
|
|
570
|
-
.
|
|
393
|
+
return this.store
|
|
394
|
+
.unsettled()
|
|
395
|
+
.filter((record) => record.task.anchorId !== null && !ancestors.has(record.task.anchorId))
|
|
396
|
+
.map((record) => record.snapshot());
|
|
571
397
|
}
|
|
572
398
|
/**
|
|
573
399
|
* Hidden results stay pending: returning to their branch delivers them again.
|
|
574
400
|
* Restored history is already delivered.
|
|
575
401
|
*/
|
|
576
402
|
async cancelOutsideBranch(ancestors) {
|
|
577
|
-
|
|
403
|
+
const records = this.store.all();
|
|
404
|
+
for (const record of records)
|
|
578
405
|
record.visible = record.task.anchorId === null || ancestors.has(record.task.anchorId);
|
|
579
|
-
const outside =
|
|
406
|
+
const outside = records.filter((record) => !record.visible);
|
|
580
407
|
for (const record of outside)
|
|
581
408
|
this.cancel(record);
|
|
582
409
|
this.store.trim();
|
|
@@ -587,7 +414,7 @@ export class TaskRuntime {
|
|
|
587
414
|
let timer;
|
|
588
415
|
try {
|
|
589
416
|
await Promise.race([
|
|
590
|
-
Promise.all(records.map((record) => this.executions.get(record.task.id)?.done)).then(() =>
|
|
417
|
+
Promise.all(records.map((record) => this.executions.get(record.task.id)?.done)).then(() => this.store.cleanupsDone()),
|
|
591
418
|
new Promise((resolve) => {
|
|
592
419
|
timer = setTimeout(resolve, finiteLimit(graceMs, 2000, 60_000));
|
|
593
420
|
}),
|