@gajae-code/coding-agent 0.18.7 → 0.18.8
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 +279 -1
- package/dist/types/async/job-manager.d.ts +1 -1
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/capability/skill.d.ts +2 -0
- package/dist/types/capability/types.d.ts +2 -0
- package/dist/types/cli/public-command-errors.d.ts +3 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/autorouting-tier-map.d.ts +348 -9
- package/dist/types/config/file-lock.d.ts +1 -0
- package/dist/types/config/model-bindings-applier.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -3
- package/dist/types/config/model-registry.d.ts +4 -9
- package/dist/types/config/skill-settings-defaults.d.ts +2 -0
- package/dist/types/cursor-delete-path.d.ts +1 -0
- package/dist/types/cursor-mcp-name.d.ts +8 -0
- package/dist/types/customization/inventory.d.ts +2 -0
- package/dist/types/debug/report-bundle-parent.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
- package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
- package/dist/types/discovery/helpers.d.ts +6 -1
- package/dist/types/export/html/css-color.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
- package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
- package/dist/types/extensibility/skill-management.d.ts +3 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
- package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
- package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +11 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
- package/dist/types/internal-urls/skill-path.d.ts +1 -0
- package/dist/types/lsp/workspace-path.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
- package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
- package/dist/types/modes/controllers/input-controller.d.ts +9 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
- package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
- package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
- package/dist/types/progress/collect-project-progress.d.ts +38 -0
- package/dist/types/progress/progress-contract.d.ts +154 -0
- package/dist/types/progress/progress-snapshot.d.ts +11 -0
- package/dist/types/progress/project-progress.d.ts +141 -0
- package/dist/types/progress/render-progress.d.ts +28 -0
- package/dist/types/runtime-mcp/oauth-public-url.d.ts +1 -0
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
- package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk/acp/adapter.d.ts +1 -1
- package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
- package/dist/types/sdk/broker/broker.d.ts +18 -1
- package/dist/types/sdk/broker/session-index.d.ts +12 -6
- package/dist/types/sdk/bus/index.d.ts +12 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +3 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/sdk/session.d.ts +8 -0
- package/dist/types/session/agent-session.d.ts +38 -9
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +26 -1
- package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
- package/dist/types/session/internal/task-artifact-owner-access.d.ts +2 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +9 -1
- package/dist/types/session/session-manager.d.ts +31 -5
- package/dist/types/session/session-retirement.d.ts +31 -7
- package/dist/types/session/session-storage.d.ts +62 -2
- package/dist/types/session/task-artifact-owner-codec.d.ts +2 -0
- package/dist/types/setup/credential-auto-import.d.ts +1 -1
- package/dist/types/setup/credential-import.d.ts +6 -4
- package/dist/types/skill-state/active-state.d.ts +38 -0
- package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -1
- package/dist/types/tools/index.d.ts +3 -0
- package/dist/types/tools/python.d.ts +9 -1
- package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
- package/dist/types/tools/restricted-cwd.d.ts +1 -0
- package/dist/types/tools/todo-write.d.ts +6 -0
- package/dist/types/utils/tools-checksum.d.ts +7 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +21 -0
- package/scripts/verify-managed-empty-stop.ts +28 -0
- package/src/async/job-manager.ts +228 -122
- package/src/capability/index.ts +17 -0
- package/src/capability/mcp.ts +2 -0
- package/src/capability/skill.ts +2 -0
- package/src/capability/types.ts +2 -0
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/customize-doctor.ts +15 -2
- package/src/cli/public-command-errors.ts +21 -1
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/skills-cli.ts +5 -0
- package/src/commands/deep-interview.ts +47 -8
- package/src/commands/sdk.ts +28 -8
- package/src/config/autorouting-tier-map.ts +117 -7
- package/src/config/file-lock.ts +307 -68
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-bindings-applier.ts +20 -0
- package/src/config/model-profile-activation.ts +240 -46
- package/src/config/model-registry.ts +47 -153
- package/src/config/skill-settings-defaults.ts +7 -0
- package/src/coordinator-mcp/policy.ts +3 -2
- package/src/cursor-delete-path.ts +10 -0
- package/src/cursor-mcp-name.ts +15 -0
- package/src/cursor.ts +13 -2
- package/src/customization/inventory.ts +3 -0
- package/src/debug/report-bundle-parent.ts +22 -0
- package/src/debug/report-bundle.ts +3 -0
- package/src/debug/system-info.ts +13 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
- package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
- package/src/defaults/gjc-defaults.test.ts +18 -1
- package/src/defaults/gjc-defaults.ts +3 -0
- package/src/defaults/gjc-skills.generated.ts +58 -36
- package/src/discovery/agents.ts +6 -1
- package/src/discovery/builtin.ts +10 -0
- package/src/discovery/helpers.ts +159 -20
- package/src/discovery/mcp-json.ts +6 -0
- package/src/discovery/ssh.ts +1 -1
- package/src/discovery/substitute-plugin-root.ts +3 -1
- package/src/edit/file-read-cache.ts +1 -1
- package/src/eval/py/executor.ts +380 -120
- package/src/export/html/css-color.ts +9 -0
- package/src/export/html/index.ts +5 -4
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +7 -0
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +73 -8
- package/src/extensibility/plugins/manager.ts +3 -2
- package/src/extensibility/plugins/marketplace/cache.ts +15 -0
- package/src/extensibility/plugins/marketplace/manager.ts +11 -3
- package/src/extensibility/plugins/plugin-link-path.ts +12 -0
- package/src/extensibility/runtime-skill-discovery.ts +20 -2
- package/src/extensibility/skill-management.ts +6 -2
- package/src/extensibility/skills.ts +6 -1
- package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
- package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
- package/src/gjc-runtime/deep-interview-stage.ts +98 -21
- package/src/gjc-runtime/gc-runtime.ts +206 -9
- package/src/gjc-runtime/launch-worktree.ts +14 -4
- package/src/gjc-runtime/ralplan-runtime.ts +447 -52
- package/src/gjc-runtime/state-argv.ts +2 -0
- package/src/gjc-runtime/state-migrations.ts +91 -29
- package/src/gjc-runtime/state-runtime.ts +3716 -135
- package/src/gjc-runtime/state-writer.ts +83 -8
- package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
- package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
- package/src/gjc-runtime/workflow-manifest.ts +54 -21
- package/src/harness-control-plane/operate.ts +12 -4
- package/src/harness-control-plane/owner.ts +5 -1
- package/src/hashline/parser.ts +48 -9
- package/src/hashline/recovery.ts +13 -4
- package/src/internal-urls/docs-index.generated.ts +18 -17
- package/src/internal-urls/skill-path.ts +19 -0
- package/src/internal-urls/skill-protocol.ts +2 -0
- package/src/lsp/edits.ts +6 -0
- package/src/lsp/workspace-path.ts +51 -0
- package/src/main.ts +195 -31
- package/src/modes/acp/acp-agent.ts +88 -4
- package/src/modes/components/custom-editor.ts +14 -0
- package/src/modes/components/customization/customization-dashboard.ts +12 -0
- package/src/modes/components/extensions/extension-dashboard.ts +2 -2
- package/src/modes/components/extensions/state-manager.ts +15 -5
- package/src/modes/components/model-selector.ts +1 -4
- package/src/modes/components/provider-onboarding-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +4 -0
- package/src/modes/controllers/event-controller.ts +11 -4
- package/src/modes/controllers/input-controller.ts +74 -29
- package/src/modes/controllers/runtime-mcp-command-controller.ts +8 -3
- package/src/modes/controllers/selector-controller.ts +28 -15
- package/src/modes/interactive-mode.ts +4 -0
- package/src/modes/runtime-init.ts +4 -0
- package/src/modes/utils/abort-message.ts +3 -1
- package/src/modes/utils/interrupt-ownership.ts +12 -0
- package/src/modes/utils/keybinding-matchers.ts +4 -0
- package/src/modes/utils/macos-option-forwarding.ts +80 -0
- package/src/progress/collect-project-progress.ts +153 -0
- package/src/progress/progress-contract.ts +172 -0
- package/src/progress/progress-snapshot.ts +140 -0
- package/src/progress/project-progress.ts +446 -0
- package/src/progress/render-progress.ts +209 -0
- package/src/prompts/system/master-mode.md +1 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/runtime-mcp/config-writer.ts +10 -6
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/oauth-flow.ts +5 -0
- package/src/runtime-mcp/oauth-public-url.ts +8 -0
- package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
- package/src/runtime-mcp/smithery-registry.ts +22 -3
- package/src/runtime-mcp/transports/http.ts +2 -2
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk/acp/adapter.ts +93 -8
- package/src/sdk/broker/broker-exit.ts +52 -23
- package/src/sdk/broker/broker.ts +75 -6
- package/src/sdk/broker/ensure.ts +2 -0
- package/src/sdk/broker/lifecycle.ts +711 -66
- package/src/sdk/broker/session-index.ts +191 -81
- package/src/sdk/bus/chat-daemon-cli.ts +38 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +238 -43
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
- package/src/sdk/cli/session-cli.ts +17 -3
- package/src/sdk/client/client.ts +28 -8
- package/src/sdk/host/query/handlers.ts +9 -1
- package/src/sdk/host/session-runtime.test.ts +2647 -294
- package/src/sdk/host/session-runtime.ts +426 -109
- package/src/sdk/host/surface-policy.ts +1 -0
- package/src/sdk/index.ts +8 -0
- package/src/sdk/lifecycle-session.ts +3 -1
- package/src/sdk/prompt-deadline-manager.ts +15 -0
- package/src/sdk/protocol/operation-inventory.generated.json +146 -0
- package/src/sdk/protocol/operation-registry.ts +9 -2
- package/src/sdk/router/session-router.ts +75 -12
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +67 -38
- package/src/session/agent-session.ts +623 -224
- package/src/session/artifacts.ts +174 -70
- package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
- package/src/session/internal/managed-session-scope.ts +3457 -462
- package/src/session/internal/managed-session-storage.ts +376 -61
- package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
- package/src/session/internal/task-artifact-owner-access.ts +19 -3
- package/src/session/internal/task-artifact-owner-transcript.ts +547 -1
- package/src/session/session-manager.ts +939 -133
- package/src/session/session-retirement.ts +573 -14
- package/src/session/session-storage.ts +417 -14
- package/src/session/task-artifact-owner-codec.ts +71 -8
- package/src/session/task-artifact-owner-retirement.ts +21 -19
- package/src/setup/credential-auto-import.ts +12 -3
- package/src/setup/credential-import.ts +176 -5
- package/src/setup/paseo/announce.ts +6 -1
- package/src/skill-state/active-state.ts +132 -2
- package/src/slash-commands/builtin-registry.ts +39 -9
- package/src/slash-commands/helpers/progress-report.ts +23 -0
- package/src/stt/setup.ts +3 -5
- package/src/task/commands.ts +3 -1
- package/src/task/executor.ts +4 -0
- package/src/task/index.ts +6 -0
- package/src/tools/ask.ts +205 -6
- package/src/tools/ast-edit.ts +4 -0
- package/src/tools/bash-allowed-prefixes.ts +15 -0
- package/src/tools/bash-skill-urls.ts +56 -3
- package/src/tools/bash.ts +48 -79
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/eval.ts +56 -36
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +3 -0
- package/src/tools/python.ts +187 -66
- package/src/tools/recipe/index.ts +9 -0
- package/src/tools/recipe/recipe-guard.ts +19 -0
- package/src/tools/restricted-cwd.ts +10 -0
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +28 -1
- package/src/tools/todo-write.ts +10 -1
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/tools/write.ts +1 -0
- package/src/utils/tools-checksum.ts +34 -0
- package/src/utils/tools-manager.ts +24 -6
- package/src/web/search/providers/codex.ts +3 -2
package/src/async/job-manager.ts
CHANGED
|
@@ -39,7 +39,7 @@ export interface AsyncJob {
|
|
|
39
39
|
endTime?: number;
|
|
40
40
|
label: string;
|
|
41
41
|
abortController: AbortController;
|
|
42
|
-
promise: Promise<void>;
|
|
42
|
+
readonly promise: Promise<void>;
|
|
43
43
|
resultText?: string;
|
|
44
44
|
errorText?: string;
|
|
45
45
|
/** Safe, bounded cause when session setup failed before the LLM began work. */
|
|
@@ -151,7 +151,8 @@ export interface OwnerSubagentShutdownProof {
|
|
|
151
151
|
|
|
152
152
|
interface OwnerSubagentShutdownLeaseState {
|
|
153
153
|
lease: OwnerSubagentShutdownLease;
|
|
154
|
-
|
|
154
|
+
backingExecutions: ReadonlyMap<string, readonly AsyncJobExecution[]>;
|
|
155
|
+
records: ReadonlyMap<string, OwnerSubagentShutdownRecordCapture>;
|
|
155
156
|
phase: "active" | "proving" | "proved";
|
|
156
157
|
proof?: OwnerSubagentShutdownProof;
|
|
157
158
|
}
|
|
@@ -213,6 +214,21 @@ export interface SubagentRecord {
|
|
|
213
214
|
currentJobGeneration?: string;
|
|
214
215
|
}
|
|
215
216
|
|
|
217
|
+
/** Retains one exact job object until its runner and deferred eviction cleanup settle. */
|
|
218
|
+
interface AsyncJobExecution {
|
|
219
|
+
readonly job: AsyncJob;
|
|
220
|
+
readonly physicalCompletion: Promise<void>;
|
|
221
|
+
physicallySettled: boolean;
|
|
222
|
+
/** An evicted row's lifecycle cleanup must not race its still-running runner. */
|
|
223
|
+
pendingEvictionCleanup: boolean;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
interface OwnerSubagentShutdownRecordCapture {
|
|
227
|
+
readonly record: SubagentRecord;
|
|
228
|
+
readonly currentJobId: string | null;
|
|
229
|
+
readonly currentJobGeneration?: string;
|
|
230
|
+
}
|
|
231
|
+
|
|
216
232
|
/** Lightweight, manager-owned resume payload. The async layer treats `data` as opaque. */
|
|
217
233
|
export interface ResumeDescriptor {
|
|
218
234
|
subagentId: string;
|
|
@@ -676,8 +692,10 @@ export class AsyncJobManager {
|
|
|
676
692
|
readonly #evictionTimers = new Map<string, NodeJS.Timeout>();
|
|
677
693
|
readonly #outputState = new Map<string, AsyncJobOutputState>();
|
|
678
694
|
readonly #ownerCleanups = new Map<string, Set<() => void>>();
|
|
679
|
-
readonly #lifecycles = new
|
|
680
|
-
readonly #lifecyclePhases = new
|
|
695
|
+
readonly #lifecycles = new WeakMap<AsyncJob, AsyncJobLifecycleCleanup>();
|
|
696
|
+
readonly #lifecyclePhases = new WeakMap<AsyncJob, Set<"cancel" | "terminal" | "evict">>();
|
|
697
|
+
readonly #executionsByJob = new WeakMap<AsyncJob, AsyncJobExecution>();
|
|
698
|
+
readonly #activeExecutions = new Set<AsyncJobExecution>();
|
|
681
699
|
readonly #monitorTombstones = new Map<string, MonitorTombstone>();
|
|
682
700
|
readonly #outputRetentionBytes = DEFAULT_JOB_OUTPUT_RETENTION_BYTES;
|
|
683
701
|
readonly #onJobComplete: AsyncJobManagerOptions["onJobComplete"];
|
|
@@ -697,7 +715,7 @@ export class AsyncJobManager {
|
|
|
697
715
|
readonly #terminalWaits = new Map<string, TerminalWaitState>();
|
|
698
716
|
#waitSeq = 0;
|
|
699
717
|
readonly #publishedTerminalGenerations = new Set<string>();
|
|
700
|
-
readonly #
|
|
718
|
+
readonly #settledJobs = new Set<AsyncJob>();
|
|
701
719
|
#jobGenerationSeq = 0;
|
|
702
720
|
readonly #liveHandles = new Map<string, SubagentLiveHandle>();
|
|
703
721
|
readonly #subagentProgress = new Map<string, AgentProgress>();
|
|
@@ -1074,9 +1092,9 @@ export class AsyncJobManager {
|
|
|
1074
1092
|
|
|
1075
1093
|
this.#expireMonitorTombstones();
|
|
1076
1094
|
const id = this.#resolveJobId(options?.id);
|
|
1077
|
-
this.#settledJobIds.delete(id);
|
|
1078
1095
|
const abortController = new AbortController();
|
|
1079
1096
|
const startTime = Date.now();
|
|
1097
|
+
const completion = Promise.withResolvers<void>();
|
|
1080
1098
|
|
|
1081
1099
|
const job: AsyncJob = {
|
|
1082
1100
|
id,
|
|
@@ -1086,11 +1104,20 @@ export class AsyncJobManager {
|
|
|
1086
1104
|
startTime,
|
|
1087
1105
|
label,
|
|
1088
1106
|
abortController,
|
|
1089
|
-
promise:
|
|
1107
|
+
promise: completion.promise,
|
|
1090
1108
|
ownerId: options?.ownerId,
|
|
1091
1109
|
metadata: options?.metadata,
|
|
1092
1110
|
};
|
|
1093
|
-
|
|
1111
|
+
Object.defineProperty(job, "promise", { writable: false, configurable: false });
|
|
1112
|
+
if (options?.lifecycle) this.#lifecycles.set(job, options.lifecycle);
|
|
1113
|
+
const execution: AsyncJobExecution = {
|
|
1114
|
+
job,
|
|
1115
|
+
physicalCompletion: completion.promise,
|
|
1116
|
+
physicallySettled: false,
|
|
1117
|
+
pendingEvictionCleanup: false,
|
|
1118
|
+
};
|
|
1119
|
+
this.#executionsByJob.set(job, execution);
|
|
1120
|
+
this.#activeExecutions.add(execution);
|
|
1094
1121
|
|
|
1095
1122
|
const reportProgress = async (text: string, details?: Record<string, unknown>): Promise<void> => {
|
|
1096
1123
|
if (!options?.onProgress) return;
|
|
@@ -1103,7 +1130,8 @@ export class AsyncJobManager {
|
|
|
1103
1130
|
});
|
|
1104
1131
|
}
|
|
1105
1132
|
};
|
|
1106
|
-
|
|
1133
|
+
this.#jobs.set(id, job);
|
|
1134
|
+
void (async () => {
|
|
1107
1135
|
try {
|
|
1108
1136
|
const result = await run({ jobId: id, signal: abortController.signal, reportProgress });
|
|
1109
1137
|
const outcome: SubagentRunOutcome =
|
|
@@ -1120,8 +1148,8 @@ export class AsyncJobManager {
|
|
|
1120
1148
|
job.resultText = outcome.kind === "paused" ? outcome.note : outcome.text;
|
|
1121
1149
|
this.#markRecordTerminal(id, "cancelled", job.generation);
|
|
1122
1150
|
this.#publishTerminal(job);
|
|
1123
|
-
this.#runLifecycle(
|
|
1124
|
-
this.#scheduleEviction(
|
|
1151
|
+
this.#runLifecycle(job, "terminal");
|
|
1152
|
+
this.#scheduleEviction(job);
|
|
1125
1153
|
this.#drainResumeQueue();
|
|
1126
1154
|
// The run has now actually unwound: retire the owned tuple so a
|
|
1127
1155
|
// later scope:"owned" abort of the same turn does not report
|
|
@@ -1151,8 +1179,8 @@ export class AsyncJobManager {
|
|
|
1151
1179
|
this.#markRecordTerminal(id, "failed", job.generation);
|
|
1152
1180
|
this.#publishTerminal(job);
|
|
1153
1181
|
this.#enqueueDelivery(id, outcome.text);
|
|
1154
|
-
this.#runLifecycle(
|
|
1155
|
-
this.#scheduleEviction(
|
|
1182
|
+
this.#runLifecycle(job, "terminal");
|
|
1183
|
+
this.#scheduleEviction(job);
|
|
1156
1184
|
this.#drainResumeQueue();
|
|
1157
1185
|
return;
|
|
1158
1186
|
}
|
|
@@ -1163,8 +1191,8 @@ export class AsyncJobManager {
|
|
|
1163
1191
|
this.#markRecordTerminal(id, "completed", job.generation);
|
|
1164
1192
|
this.#publishTerminal(job);
|
|
1165
1193
|
this.#enqueueDelivery(id, outcome.text);
|
|
1166
|
-
this.#runLifecycle(
|
|
1167
|
-
this.#scheduleEviction(
|
|
1194
|
+
this.#runLifecycle(job, "terminal");
|
|
1195
|
+
this.#scheduleEviction(job);
|
|
1168
1196
|
this.#drainResumeQueue();
|
|
1169
1197
|
} catch (error) {
|
|
1170
1198
|
if (this.#externallySettled.has(job.generation)) {
|
|
@@ -1175,8 +1203,8 @@ export class AsyncJobManager {
|
|
|
1175
1203
|
job.errorText = error instanceof Error ? error.message : String(error);
|
|
1176
1204
|
this.#markRecordTerminal(id, "cancelled", job.generation);
|
|
1177
1205
|
this.#publishTerminal(job);
|
|
1178
|
-
this.#runLifecycle(
|
|
1179
|
-
this.#scheduleEviction(
|
|
1206
|
+
this.#runLifecycle(job, "terminal");
|
|
1207
|
+
this.#scheduleEviction(job);
|
|
1180
1208
|
this.#drainResumeQueue();
|
|
1181
1209
|
this.#retireCancelledJobOwned(id, job.generation);
|
|
1182
1210
|
return;
|
|
@@ -1188,20 +1216,43 @@ export class AsyncJobManager {
|
|
|
1188
1216
|
this.#markRecordTerminal(id, "failed", job.generation);
|
|
1189
1217
|
this.#publishTerminal(job);
|
|
1190
1218
|
this.#enqueueDelivery(id, errorText);
|
|
1191
|
-
this.#runLifecycle(
|
|
1192
|
-
this.#scheduleEviction(
|
|
1219
|
+
this.#runLifecycle(job, "terminal");
|
|
1220
|
+
this.#scheduleEviction(job);
|
|
1193
1221
|
this.#drainResumeQueue();
|
|
1194
1222
|
}
|
|
1195
|
-
})()
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1223
|
+
})()
|
|
1224
|
+
.finally(() => {
|
|
1225
|
+
this.#externallySettled.delete(job.generation);
|
|
1226
|
+
if (this.#jobs.get(id) === job) this.#settledJobs.add(job);
|
|
1227
|
+
this.#settleExecution(execution);
|
|
1228
|
+
})
|
|
1229
|
+
.then(completion.resolve, completion.reject);
|
|
1199
1230
|
|
|
1200
|
-
this.#jobs.set(id, job);
|
|
1201
1231
|
this.#notifyChange();
|
|
1202
1232
|
return id;
|
|
1203
1233
|
}
|
|
1204
1234
|
|
|
1235
|
+
#settleExecution(execution: AsyncJobExecution): void {
|
|
1236
|
+
if (execution.physicallySettled) return;
|
|
1237
|
+
try {
|
|
1238
|
+
if (execution.pendingEvictionCleanup) this.#finishEvictionCleanup(execution.job);
|
|
1239
|
+
} finally {
|
|
1240
|
+
execution.physicallySettled = true;
|
|
1241
|
+
this.#activeExecutions.delete(execution);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
#finishEvictionCleanup(job: AsyncJob): void {
|
|
1246
|
+
this.#runLifecycle(job, "evict");
|
|
1247
|
+
this.#purgeTerminalSubagentStateForJob(job.id, job.generation);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
#cancelExactExecution(execution: AsyncJobExecution): boolean {
|
|
1251
|
+
const job = execution.job;
|
|
1252
|
+
if (this.#jobs.get(job.id) !== job) return false;
|
|
1253
|
+
return this.cancel(job.id, job.ownerId ? { ownerId: job.ownerId } : undefined);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1205
1256
|
/**
|
|
1206
1257
|
* Cancel a single job by id. When `filter.ownerId` is set and does not
|
|
1207
1258
|
* match the job's owner, the call is treated as not-found (returns false)
|
|
@@ -1229,8 +1280,8 @@ export class AsyncJobManager {
|
|
|
1229
1280
|
this.#markRecordTerminal(id, "cancelled", job.generation);
|
|
1230
1281
|
this.#freezeEndTime(job);
|
|
1231
1282
|
this.#publishTerminal(job);
|
|
1232
|
-
this.#runLifecycle(
|
|
1233
|
-
this.#scheduleEviction(
|
|
1283
|
+
this.#runLifecycle(job, "cancel");
|
|
1284
|
+
this.#scheduleEviction(job);
|
|
1234
1285
|
this.#drainResumeQueue();
|
|
1235
1286
|
// A PAUSED job's run has already unwound (it returned "paused"), so
|
|
1236
1287
|
// its owned tuple is retired here; a RUNNING job's tuple is retired
|
|
@@ -1243,7 +1294,7 @@ export class AsyncJobManager {
|
|
|
1243
1294
|
this.#freezeEndTime(job);
|
|
1244
1295
|
this.#markRecordTerminal(id, "cancelled", job.generation);
|
|
1245
1296
|
this.#publishTerminal(job);
|
|
1246
|
-
this.#runLifecycle(
|
|
1297
|
+
this.#runLifecycle(job, "cancel");
|
|
1247
1298
|
job.abortController.abort();
|
|
1248
1299
|
this.#notifyChange();
|
|
1249
1300
|
return true;
|
|
@@ -1260,14 +1311,14 @@ export class AsyncJobManager {
|
|
|
1260
1311
|
job.endTime ??= Date.now();
|
|
1261
1312
|
}
|
|
1262
1313
|
|
|
1263
|
-
#runLifecycle(
|
|
1264
|
-
const lifecycle = this.#lifecycles.get(
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
const fired = this.#lifecyclePhases.get(
|
|
1314
|
+
#runLifecycle(job: AsyncJob, phase: "cancel" | "terminal" | "evict"): void {
|
|
1315
|
+
const lifecycle = this.#lifecycles.get(job);
|
|
1316
|
+
if (!lifecycle) return;
|
|
1317
|
+
const jobId = job.id;
|
|
1318
|
+
const fired = this.#lifecyclePhases.get(job) ?? new Set<"cancel" | "terminal" | "evict">();
|
|
1268
1319
|
if (fired.has(phase)) return;
|
|
1269
1320
|
fired.add(phase);
|
|
1270
|
-
this.#lifecyclePhases.set(
|
|
1321
|
+
this.#lifecyclePhases.set(job, fired);
|
|
1271
1322
|
try {
|
|
1272
1323
|
if (phase === "cancel") lifecycle.onCancel?.(job);
|
|
1273
1324
|
else if (phase === "terminal") lifecycle.onTerminal?.(job);
|
|
@@ -1288,16 +1339,16 @@ export class AsyncJobManager {
|
|
|
1288
1339
|
}
|
|
1289
1340
|
}
|
|
1290
1341
|
|
|
1291
|
-
#recordMonitorTombstone(
|
|
1292
|
-
const
|
|
1293
|
-
if (!job
|
|
1294
|
-
const lifecycle = this.#lifecycles.get(
|
|
1342
|
+
#recordMonitorTombstone(job: AsyncJob): void {
|
|
1343
|
+
const jobId = job.id;
|
|
1344
|
+
if (!job.metadata?.monitor) return;
|
|
1345
|
+
const lifecycle = this.#lifecycles.get(job);
|
|
1295
1346
|
this.#monitorTombstones.set(jobId, {
|
|
1296
1347
|
jobId,
|
|
1297
1348
|
ownerId: job.ownerId,
|
|
1298
1349
|
status: job.status,
|
|
1299
1350
|
expiresAt: Date.now() + MONITOR_TOMBSTONE_TTL_MS,
|
|
1300
|
-
purge: () =>
|
|
1351
|
+
purge: () => lifecycle?.onTombstonePurge?.(job),
|
|
1301
1352
|
});
|
|
1302
1353
|
}
|
|
1303
1354
|
|
|
@@ -1484,37 +1535,63 @@ export class AsyncJobManager {
|
|
|
1484
1535
|
beginOwnerSubagentShutdown(ownerId: string): OwnerSubagentShutdownLease | undefined {
|
|
1485
1536
|
if (!ownerId || this.#ownerSubagentShutdownLeases.has(ownerId)) return undefined;
|
|
1486
1537
|
const targets = new Map<string, OwnerSubagentShutdownTarget>();
|
|
1487
|
-
const
|
|
1488
|
-
const
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1538
|
+
const backingExecutions = new Map<string, Set<AsyncJobExecution>>();
|
|
1539
|
+
const records = new Map<string, OwnerSubagentShutdownRecordCapture>();
|
|
1540
|
+
const findExecution = (jobId: string, generation?: string): AsyncJobExecution | undefined => {
|
|
1541
|
+
if (generation === undefined) return undefined;
|
|
1542
|
+
const job = this.#jobs.get(jobId);
|
|
1543
|
+
if (job?.generation === generation) {
|
|
1544
|
+
return this.#executionsByJob.get(job);
|
|
1545
|
+
}
|
|
1546
|
+
for (const execution of this.#activeExecutions) {
|
|
1547
|
+
if (execution.job.id === jobId && execution.job.generation === generation) return execution;
|
|
1548
|
+
}
|
|
1549
|
+
return undefined;
|
|
1550
|
+
};
|
|
1551
|
+
const addBackingExecution = (subagentId: string, execution: AsyncJobExecution | undefined): void => {
|
|
1552
|
+
if (!execution || execution.job.ownerId !== ownerId) return;
|
|
1553
|
+
const executions = backingExecutions.get(subagentId) ?? new Set<AsyncJobExecution>();
|
|
1554
|
+
executions.add(execution);
|
|
1555
|
+
backingExecutions.set(subagentId, executions);
|
|
1493
1556
|
};
|
|
1494
1557
|
for (const record of this.#subagentRecords.values()) {
|
|
1495
|
-
if (record.ownerId !== ownerId
|
|
1558
|
+
if (record.ownerId !== ownerId) continue;
|
|
1559
|
+
const backingExecution =
|
|
1560
|
+
record.currentJobId && record.currentJobGeneration
|
|
1561
|
+
? findExecution(record.currentJobId, record.currentJobGeneration)
|
|
1562
|
+
: undefined;
|
|
1563
|
+
records.set(record.subagentId, {
|
|
1564
|
+
record,
|
|
1565
|
+
currentJobId: record.currentJobId,
|
|
1566
|
+
currentJobGeneration: record.currentJobGeneration,
|
|
1567
|
+
});
|
|
1568
|
+
if (this.#isTerminalSubagentStatus(record.status) && backingExecution?.physicallySettled !== false) continue;
|
|
1496
1569
|
targets.set(record.subagentId, {
|
|
1497
1570
|
subagentId: record.subagentId,
|
|
1498
1571
|
jobId: record.status === "queued" ? null : record.currentJobId,
|
|
1499
1572
|
source: "record",
|
|
1500
1573
|
});
|
|
1501
|
-
|
|
1574
|
+
addBackingExecution(record.subagentId, backingExecution);
|
|
1502
1575
|
}
|
|
1503
|
-
|
|
1576
|
+
const candidateJobs = new Set<AsyncJob>(this.#jobs.values());
|
|
1577
|
+
for (const execution of this.#activeExecutions) candidateJobs.add(execution.job);
|
|
1578
|
+
for (const job of candidateJobs) {
|
|
1504
1579
|
const subagentId = job.metadata?.subagent?.id;
|
|
1580
|
+
const execution = this.#executionsByJob.get(job);
|
|
1581
|
+
const physicallyActive = execution !== undefined && !execution.physicallySettled;
|
|
1505
1582
|
if (
|
|
1506
1583
|
job.ownerId !== ownerId ||
|
|
1507
1584
|
!subagentId ||
|
|
1508
|
-
(job.status !== "running" && job.status !== "paused" && job.status !== "cancelled")
|
|
1585
|
+
(!physicallyActive && job.status !== "running" && job.status !== "paused" && job.status !== "cancelled")
|
|
1509
1586
|
) {
|
|
1510
1587
|
continue;
|
|
1511
1588
|
}
|
|
1512
|
-
if (job.status === "cancelled" &&
|
|
1589
|
+
if (job.status === "cancelled" && execution?.physicallySettled && !this.#subagentRecords.has(subagentId))
|
|
1513
1590
|
continue;
|
|
1514
1591
|
if (!targets.has(subagentId)) {
|
|
1515
1592
|
targets.set(subagentId, { subagentId, jobId: job.id, source: "metadata_job" });
|
|
1516
1593
|
}
|
|
1517
|
-
|
|
1594
|
+
addBackingExecution(subagentId, execution);
|
|
1518
1595
|
}
|
|
1519
1596
|
const lease: OwnerSubagentShutdownLease = {
|
|
1520
1597
|
ownerId,
|
|
@@ -1523,12 +1600,13 @@ export class AsyncJobManager {
|
|
|
1523
1600
|
};
|
|
1524
1601
|
this.#ownerSubagentShutdownLeases.set(ownerId, {
|
|
1525
1602
|
lease,
|
|
1526
|
-
|
|
1527
|
-
Array.from(
|
|
1603
|
+
backingExecutions: new Map<string, readonly AsyncJobExecution[]>(
|
|
1604
|
+
Array.from(backingExecutions, ([subagentId, executions]): [string, readonly AsyncJobExecution[]] => [
|
|
1528
1605
|
subagentId,
|
|
1529
|
-
Array.from(
|
|
1606
|
+
Array.from(executions),
|
|
1530
1607
|
]),
|
|
1531
1608
|
),
|
|
1609
|
+
records,
|
|
1532
1610
|
phase: "active",
|
|
1533
1611
|
});
|
|
1534
1612
|
return lease;
|
|
@@ -1593,33 +1671,38 @@ export class AsyncJobManager {
|
|
|
1593
1671
|
}
|
|
1594
1672
|
if (state.phase === "proved" && state.proof?.confirmed) return state.proof;
|
|
1595
1673
|
state.phase = "proving";
|
|
1596
|
-
const
|
|
1597
|
-
const promises: Promise<void>[] = [];
|
|
1674
|
+
const promises = new Set<Promise<void>>();
|
|
1598
1675
|
for (const target of lease.targets) {
|
|
1599
|
-
const
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1676
|
+
const executions = state.backingExecutions.get(target.subagentId) ?? [];
|
|
1677
|
+
const capture = state.records.get(target.subagentId);
|
|
1678
|
+
if (
|
|
1679
|
+
target.source === "record" &&
|
|
1680
|
+
capture &&
|
|
1681
|
+
this.#subagentRecords.get(target.subagentId) === capture.record &&
|
|
1682
|
+
capture.record.status === "queued"
|
|
1683
|
+
) {
|
|
1684
|
+
this.cancelSubagent(target.subagentId, { ownerId: lease.ownerId });
|
|
1685
|
+
}
|
|
1686
|
+
for (const execution of executions) {
|
|
1687
|
+
this.#cancelExactExecution(execution);
|
|
1688
|
+
promises.add(execution.physicalCompletion);
|
|
1689
|
+
}
|
|
1690
|
+
if (target.source !== "record" || executions.length > 0) continue;
|
|
1691
|
+
const record = capture?.record;
|
|
1692
|
+
if (!capture || !record || this.#subagentRecords.get(target.subagentId) !== record) continue;
|
|
1693
|
+
if (record.status === "paused") {
|
|
1694
|
+
const row = record.currentJobId ? this.#jobs.get(record.currentJobId) : undefined;
|
|
1695
|
+
if (row && row.generation === capture.currentJobGeneration) continue;
|
|
1696
|
+
record.status = "cancelled";
|
|
1697
|
+
this.#liveHandles.delete(record.subagentId);
|
|
1698
|
+
this.#subagentProgress.delete(record.subagentId);
|
|
1699
|
+
this.#notifyChange();
|
|
1617
1700
|
}
|
|
1618
1701
|
}
|
|
1619
1702
|
const timeoutMs = Math.max(0, options?.timeoutMs ?? OWNER_SUBAGENT_SHUTDOWN_TIMEOUT_MS);
|
|
1620
1703
|
let deadlineExceeded = false;
|
|
1621
1704
|
await Promise.race([
|
|
1622
|
-
Promise.allSettled(promises),
|
|
1705
|
+
Promise.allSettled([...promises]),
|
|
1623
1706
|
Bun.sleep(timeoutMs).then(() => {
|
|
1624
1707
|
deadlineExceeded = true;
|
|
1625
1708
|
}),
|
|
@@ -1633,7 +1716,7 @@ export class AsyncJobManager {
|
|
|
1633
1716
|
);
|
|
1634
1717
|
}
|
|
1635
1718
|
const unresolvedIds = lease.targets
|
|
1636
|
-
.filter(target => !this.#hasTerminalShutdownEvidence(target, lease.ownerId, current
|
|
1719
|
+
.filter(target => !this.#hasTerminalShutdownEvidence(target, lease.ownerId, current))
|
|
1637
1720
|
.map(target => target.subagentId);
|
|
1638
1721
|
const reason =
|
|
1639
1722
|
unresolvedIds.length === 0
|
|
@@ -1650,16 +1733,23 @@ export class AsyncJobManager {
|
|
|
1650
1733
|
#hasTerminalShutdownEvidence(
|
|
1651
1734
|
target: OwnerSubagentShutdownTarget,
|
|
1652
1735
|
ownerId: string,
|
|
1653
|
-
|
|
1654
|
-
settled: ReadonlySet<string>,
|
|
1736
|
+
state: OwnerSubagentShutdownLeaseState,
|
|
1655
1737
|
): boolean {
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1738
|
+
const capture = state.records.get(target.subagentId);
|
|
1739
|
+
const record = capture?.record;
|
|
1740
|
+
if (
|
|
1741
|
+
!record ||
|
|
1742
|
+
this.#subagentRecords.get(target.subagentId) !== record ||
|
|
1743
|
+
record.ownerId !== ownerId ||
|
|
1744
|
+
record.currentJobId !== capture.currentJobId ||
|
|
1745
|
+
record.currentJobGeneration !== capture.currentJobGeneration ||
|
|
1746
|
+
!this.#isTerminalSubagentStatus(record.status)
|
|
1747
|
+
) {
|
|
1748
|
+
return false;
|
|
1749
|
+
}
|
|
1750
|
+
return (state.backingExecutions.get(target.subagentId) ?? []).every(
|
|
1751
|
+
execution => execution.job.ownerId === ownerId && execution.physicallySettled,
|
|
1752
|
+
);
|
|
1663
1753
|
}
|
|
1664
1754
|
|
|
1665
1755
|
#ownerSubagentShutdownProof(
|
|
@@ -1962,7 +2052,7 @@ export class AsyncJobManager {
|
|
|
1962
2052
|
rec.status = "cancelled";
|
|
1963
2053
|
this.#liveHandles.delete(rec.subagentId);
|
|
1964
2054
|
this.#subagentProgress.delete(rec.subagentId);
|
|
1965
|
-
if (shouldScheduleEviction &&
|
|
2055
|
+
if (shouldScheduleEviction && job) this.#scheduleEviction(job);
|
|
1966
2056
|
else this.#notifyChange();
|
|
1967
2057
|
this.#drainResumeQueue();
|
|
1968
2058
|
// A paused run has already unwound (it returned "paused") and this
|
|
@@ -2597,8 +2687,8 @@ export class AsyncJobManager {
|
|
|
2597
2687
|
this.#markRecordTerminal(jobId, "failed", generation);
|
|
2598
2688
|
this.#publishTerminal(job);
|
|
2599
2689
|
this.#enqueueDelivery(jobId, errorText);
|
|
2600
|
-
this.#runLifecycle(
|
|
2601
|
-
this.#scheduleEviction(
|
|
2690
|
+
this.#runLifecycle(job, "terminal");
|
|
2691
|
+
this.#scheduleEviction(job);
|
|
2602
2692
|
this.#drainResumeQueue();
|
|
2603
2693
|
this.#notifyChange();
|
|
2604
2694
|
return true;
|
|
@@ -2606,7 +2696,7 @@ export class AsyncJobManager {
|
|
|
2606
2696
|
|
|
2607
2697
|
cancelAll(filter?: AsyncJobFilter): void {
|
|
2608
2698
|
for (const job of this.getRunningJobs(filter)) {
|
|
2609
|
-
if (this.cancel(job.id, filter)) this.#scheduleEviction(job
|
|
2699
|
+
if (this.cancel(job.id, filter)) this.#scheduleEviction(job);
|
|
2610
2700
|
}
|
|
2611
2701
|
}
|
|
2612
2702
|
|
|
@@ -2628,11 +2718,23 @@ export class AsyncJobManager {
|
|
|
2628
2718
|
}
|
|
2629
2719
|
async cancelAndSettleOwnerJobs(ownerId: string, options?: { timeoutMs?: number }): Promise<boolean> {
|
|
2630
2720
|
const jobs = this.getAllJobs({ ownerId });
|
|
2631
|
-
|
|
2721
|
+
const executions = new Set<AsyncJobExecution>();
|
|
2722
|
+
for (const job of jobs) {
|
|
2723
|
+
const execution = this.#executionsByJob.get(job);
|
|
2724
|
+
if (execution) {
|
|
2725
|
+
executions.add(execution);
|
|
2726
|
+
this.#cancelExactExecution(execution);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
for (const execution of this.#activeExecutions) {
|
|
2730
|
+
if (execution.job.ownerId !== ownerId) continue;
|
|
2731
|
+
executions.add(execution);
|
|
2732
|
+
this.#cancelExactExecution(execution);
|
|
2733
|
+
}
|
|
2632
2734
|
const timeoutMs = Math.max(0, options?.timeoutMs ?? OWNER_SUBAGENT_SHUTDOWN_TIMEOUT_MS);
|
|
2633
2735
|
let timedOut = false;
|
|
2634
2736
|
await Promise.race([
|
|
2635
|
-
Promise.allSettled(
|
|
2737
|
+
Promise.allSettled([...executions].map(execution => execution.physicalCompletion)),
|
|
2636
2738
|
Bun.sleep(timeoutMs).then(() => {
|
|
2637
2739
|
timedOut = true;
|
|
2638
2740
|
}),
|
|
@@ -2657,11 +2759,11 @@ export class AsyncJobManager {
|
|
|
2657
2759
|
}
|
|
2658
2760
|
|
|
2659
2761
|
async #waitForAllWithDeadline(timeoutMs: number): Promise<{ completed: boolean; stuckJobIds: string[] }> {
|
|
2660
|
-
const
|
|
2661
|
-
if (
|
|
2762
|
+
const executions = [...this.#activeExecutions];
|
|
2763
|
+
if (executions.length === 0) return { completed: true, stuckJobIds: [] };
|
|
2662
2764
|
let timedOut = false;
|
|
2663
2765
|
await Promise.race([
|
|
2664
|
-
Promise.allSettled(
|
|
2766
|
+
Promise.allSettled(executions.map(execution => execution.physicalCompletion)),
|
|
2665
2767
|
Bun.sleep(Math.max(0, timeoutMs)).then(() => {
|
|
2666
2768
|
timedOut = true;
|
|
2667
2769
|
}),
|
|
@@ -2669,9 +2771,7 @@ export class AsyncJobManager {
|
|
|
2669
2771
|
if (!timedOut) return { completed: true, stuckJobIds: [] };
|
|
2670
2772
|
return {
|
|
2671
2773
|
completed: false,
|
|
2672
|
-
stuckJobIds:
|
|
2673
|
-
.filter(job => job.status === "running" || job.status === "cancelled")
|
|
2674
|
-
.map(job => job.id),
|
|
2774
|
+
stuckJobIds: executions.filter(execution => !execution.physicallySettled).map(execution => execution.job.id),
|
|
2675
2775
|
};
|
|
2676
2776
|
}
|
|
2677
2777
|
|
|
@@ -2757,7 +2857,7 @@ export class AsyncJobManager {
|
|
|
2757
2857
|
}
|
|
2758
2858
|
|
|
2759
2859
|
async waitForAll(): Promise<void> {
|
|
2760
|
-
await Promise.all(
|
|
2860
|
+
await Promise.all([...this.#activeExecutions].map(execution => execution.physicalCompletion));
|
|
2761
2861
|
}
|
|
2762
2862
|
|
|
2763
2863
|
async drainDeliveries(options?: { timeoutMs?: number; filter?: AsyncJobFilter }): Promise<boolean> {
|
|
@@ -2824,15 +2924,15 @@ export class AsyncJobManager {
|
|
|
2824
2924
|
// registration keeps the original protection (cleanups cannot register fresh
|
|
2825
2925
|
// work) without disabling delivery. Errors in cleanups are logged, never
|
|
2826
2926
|
// escalated.
|
|
2827
|
-
const
|
|
2927
|
+
const unsettledExecutionPromises = new Set<Promise<void>>();
|
|
2828
2928
|
const jobIdsByPromise = new Map<Promise<void>, string>();
|
|
2829
|
-
for (const
|
|
2830
|
-
const promise =
|
|
2831
|
-
|
|
2832
|
-
jobIdsByPromise.set(promise, job.id);
|
|
2929
|
+
for (const execution of this.#activeExecutions) {
|
|
2930
|
+
const promise = execution.physicalCompletion;
|
|
2931
|
+
unsettledExecutionPromises.add(promise);
|
|
2932
|
+
jobIdsByPromise.set(promise, execution.job.id);
|
|
2833
2933
|
void promise.then(
|
|
2834
|
-
() =>
|
|
2835
|
-
() =>
|
|
2934
|
+
() => unsettledExecutionPromises.delete(promise),
|
|
2935
|
+
() => unsettledExecutionPromises.delete(promise),
|
|
2836
2936
|
);
|
|
2837
2937
|
}
|
|
2838
2938
|
this.#registrationClosed = true;
|
|
@@ -2861,12 +2961,12 @@ export class AsyncJobManager {
|
|
|
2861
2961
|
const remainingDeliveryMs = Math.max(0, disposalDeadline - Date.now());
|
|
2862
2962
|
const drained = await this.drainDeliveries({ timeoutMs: remainingDeliveryMs });
|
|
2863
2963
|
if (!drained) this.#projectUndeliveredDisposalFailures();
|
|
2864
|
-
const retainedAuthority = new Set<Promise<void>>(
|
|
2964
|
+
const retainedAuthority = new Set<Promise<void>>(unsettledExecutionPromises);
|
|
2865
2965
|
for (const delivery of this.#inFlightDeliveries) {
|
|
2866
2966
|
if (delivery.promise) retainedAuthority.add(delivery.promise);
|
|
2867
2967
|
}
|
|
2868
2968
|
this.#retainedDisposalCompletion = Promise.allSettled(retainedAuthority).then(() => {});
|
|
2869
|
-
const physicalStuckJobIds = [...
|
|
2969
|
+
const physicalStuckJobIds = [...unsettledExecutionPromises]
|
|
2870
2970
|
.map(promise => jobIdsByPromise.get(promise))
|
|
2871
2971
|
.filter((id): id is string => id !== undefined);
|
|
2872
2972
|
const stuckJobIds = [...new Set([...waitResult.stuckJobIds, ...physicalStuckJobIds])];
|
|
@@ -2881,6 +2981,7 @@ export class AsyncJobManager {
|
|
|
2881
2981
|
this.#clearEvictionTimers();
|
|
2882
2982
|
this.#disposed = true;
|
|
2883
2983
|
this.#jobs.clear();
|
|
2984
|
+
this.#settledJobs.clear();
|
|
2884
2985
|
this.#deliveries.length = 0;
|
|
2885
2986
|
this.#inFlightDeliveries.length = 0;
|
|
2886
2987
|
this.#deadLetteredDeliveries.clear();
|
|
@@ -2968,11 +3069,13 @@ export class AsyncJobManager {
|
|
|
2968
3069
|
return true;
|
|
2969
3070
|
}
|
|
2970
3071
|
|
|
2971
|
-
#scheduleEviction(
|
|
2972
|
-
|
|
3072
|
+
#scheduleEviction(job: AsyncJob): void {
|
|
3073
|
+
const jobId = job.id;
|
|
3074
|
+
if (this.#disposed || this.#jobs.get(jobId) !== job) return;
|
|
2973
3075
|
this.#notifyChange();
|
|
3076
|
+
if (this.#disposed || this.#jobs.get(jobId) !== job) return;
|
|
2974
3077
|
if (this.#retentionMs <= 0) {
|
|
2975
|
-
this.#evictJob(
|
|
3078
|
+
this.#evictJob(job);
|
|
2976
3079
|
// The terminal notification above precedes eviction so consumers can see
|
|
2977
3080
|
// the terminal transition; publish once more after the record disappears
|
|
2978
3081
|
// so observers can project any delivery that still owns the generation.
|
|
@@ -2984,28 +3087,32 @@ export class AsyncJobManager {
|
|
|
2984
3087
|
clearTimeout(existing);
|
|
2985
3088
|
}
|
|
2986
3089
|
const timer = setTimeout(() => {
|
|
3090
|
+
if (this.#evictionTimers.get(jobId) !== timer) return;
|
|
2987
3091
|
this.#evictionTimers.delete(jobId);
|
|
2988
|
-
this.#evictJob(
|
|
3092
|
+
this.#evictJob(job);
|
|
2989
3093
|
this.#notifyChange();
|
|
2990
3094
|
}, this.#retentionMs);
|
|
2991
3095
|
timer.unref();
|
|
2992
3096
|
this.#evictionTimers.set(jobId, timer);
|
|
2993
3097
|
}
|
|
2994
3098
|
|
|
2995
|
-
#evictJob(
|
|
3099
|
+
#evictJob(job: AsyncJob): void {
|
|
3100
|
+
const jobId = job.id;
|
|
3101
|
+
if (this.#jobs.get(jobId) !== job) return;
|
|
2996
3102
|
this.#expireMonitorTombstones();
|
|
2997
|
-
this.#recordMonitorTombstone(
|
|
2998
|
-
this.#
|
|
2999
|
-
|
|
3000
|
-
|
|
3103
|
+
this.#recordMonitorTombstone(job);
|
|
3104
|
+
const execution = this.#executionsByJob.get(job);
|
|
3105
|
+
if (execution && !execution.physicallySettled) execution.pendingEvictionCleanup = true;
|
|
3106
|
+
else this.#finishEvictionCleanup(job);
|
|
3107
|
+
if (this.#jobs.get(jobId) !== job) return;
|
|
3001
3108
|
const deadLetter = this.#deadLetteredDeliveries.get(jobId);
|
|
3002
3109
|
const failure =
|
|
3003
|
-
job
|
|
3110
|
+
job.deliveryFailure && (!deadLetter || deadLetter.generation === job.generation)
|
|
3004
3111
|
? job.deliveryFailure
|
|
3005
|
-
: deadLetter && job
|
|
3112
|
+
: deadLetter && job.generation === deadLetter.generation
|
|
3006
3113
|
? deadLetter
|
|
3007
3114
|
: undefined;
|
|
3008
|
-
if (
|
|
3115
|
+
if (failure) {
|
|
3009
3116
|
this.#evictedDeadLetters.set(job.generation, {
|
|
3010
3117
|
jobId: job.id,
|
|
3011
3118
|
generation: job.generation,
|
|
@@ -3023,13 +3130,12 @@ export class AsyncJobManager {
|
|
|
3023
3130
|
this.#evictedDeadLetters.delete(oldestGeneration);
|
|
3024
3131
|
}
|
|
3025
3132
|
}
|
|
3133
|
+
if (this.#jobs.get(jobId) !== job) return;
|
|
3026
3134
|
this.#jobs.delete(jobId);
|
|
3027
|
-
this.#
|
|
3028
|
-
this.#lifecycles.delete(jobId);
|
|
3029
|
-
this.#lifecyclePhases.delete(jobId);
|
|
3135
|
+
this.#settledJobs.delete(job);
|
|
3030
3136
|
this.#deadLetteredDeliveries.delete(jobId);
|
|
3031
3137
|
this.#deadLetteredDeliveryOwners.delete(jobId);
|
|
3032
|
-
|
|
3138
|
+
this.#publishedTerminalGenerations.delete(job.generation);
|
|
3033
3139
|
this.#outputState.delete(jobId);
|
|
3034
3140
|
}
|
|
3035
3141
|
|