pi-background-tasks 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BACKGROUND-TASKS-INSTRUCTIONS.md +1 -1
- package/README.md +70 -25
- package/TESTING.md +42 -2
- package/TEST_PLAN.md +7 -4
- package/dist/extensions/anthropic-attribution-child.js +4 -0
- package/dist/extensions/anthropic-attribution-child.js.map +1 -0
- package/dist/extensions/anthropic-attribution.js +107 -0
- package/dist/extensions/anthropic-attribution.js.map +1 -0
- package/dist/extensions/background-tasks.js +2 -0
- package/dist/extensions/background-tasks.js.map +1 -0
- package/dist/extensions/delegate-child.js +2 -0
- package/dist/extensions/delegate-child.js.map +1 -0
- package/dist/extensions/fusion-child.js +2 -0
- package/dist/extensions/fusion-child.js.map +1 -0
- package/dist/package.json +5 -0
- package/dist/src/core/anthropic-attribution-path.js +24 -0
- package/dist/src/core/anthropic-attribution-path.js.map +1 -0
- package/dist/src/core/anthropic-attribution.js +2247 -0
- package/dist/src/core/anthropic-attribution.js.map +1 -0
- package/dist/src/core/attested-pi-contract.js +5 -0
- package/dist/src/core/attested-pi-contract.js.map +1 -0
- package/dist/src/core/attested-pi-run.js +749 -0
- package/dist/src/core/attested-pi-run.js.map +1 -0
- package/dist/src/core/canonical-json.js +19 -0
- package/dist/src/core/canonical-json.js.map +1 -0
- package/dist/src/core/common.js +787 -0
- package/dist/src/core/common.js.map +1 -0
- package/dist/src/core/config.js +78 -0
- package/dist/src/core/config.js.map +1 -0
- package/dist/src/core/context/parent-snapshot.js +75 -0
- package/dist/src/core/context/parent-snapshot.js.map +1 -0
- package/dist/src/core/context/token-budget.js +631 -0
- package/dist/src/core/context/token-budget.js.map +1 -0
- package/dist/src/core/context/visible-conversation-v2.js +390 -0
- package/dist/src/core/context/visible-conversation-v2.js.map +1 -0
- package/dist/src/core/delegate/artifacts.js +357 -0
- package/dist/src/core/delegate/artifacts.js.map +1 -0
- package/dist/src/core/delegate/budget.js +266 -0
- package/dist/src/core/delegate/budget.js.map +1 -0
- package/dist/src/core/delegate/facade-contract.js +7 -0
- package/dist/src/core/delegate/facade-contract.js.map +1 -0
- package/dist/src/core/delegate/hook-contract-evidence.json +18 -0
- package/dist/src/core/delegate/hook-contract.js +124 -0
- package/dist/src/core/delegate/hook-contract.js.map +1 -0
- package/dist/src/core/delegate/launch.js +366 -0
- package/dist/src/core/delegate/launch.js.map +1 -0
- package/dist/src/core/delegate/result-package.js +293 -0
- package/dist/src/core/delegate/result-package.js.map +1 -0
- package/dist/src/core/delegate/runner.js +398 -0
- package/dist/src/core/delegate/runner.js.map +1 -0
- package/dist/src/core/delegate/seed.js +334 -0
- package/dist/src/core/delegate/seed.js.map +1 -0
- package/dist/src/core/delegate/types.js +103 -0
- package/dist/src/core/delegate/types.js.map +1 -0
- package/dist/src/core/durable-fs.js +372 -0
- package/dist/src/core/durable-fs.js.map +1 -0
- package/dist/src/core/extension-api.js +382 -0
- package/dist/src/core/extension-api.js.map +1 -0
- package/dist/src/core/fusion/artifacts.js +654 -0
- package/dist/src/core/fusion/artifacts.js.map +1 -0
- package/dist/src/core/fusion/budget.js +949 -0
- package/dist/src/core/fusion/budget.js.map +1 -0
- package/dist/src/core/fusion/child-protocol.js +176 -0
- package/dist/src/core/fusion/child-protocol.js.map +1 -0
- package/dist/src/core/fusion/claude-cache.js +156 -0
- package/dist/src/core/fusion/claude-cache.js.map +1 -0
- package/dist/src/core/fusion/clean-context.js +64 -0
- package/dist/src/core/fusion/clean-context.js.map +1 -0
- package/dist/src/core/fusion/config.js +349 -0
- package/dist/src/core/fusion/config.js.map +1 -0
- package/dist/src/core/fusion/context.js +184 -0
- package/dist/src/core/fusion/context.js.map +1 -0
- package/dist/src/core/fusion/evaluation.js +696 -0
- package/dist/src/core/fusion/evaluation.js.map +1 -0
- package/dist/src/core/fusion/facade-contract.js +3 -0
- package/dist/src/core/fusion/facade-contract.js.map +1 -0
- package/dist/src/core/fusion/orchestrator.js +867 -0
- package/dist/src/core/fusion/orchestrator.js.map +1 -0
- package/dist/src/core/fusion/output-contract.js +27 -0
- package/dist/src/core/fusion/output-contract.js.map +1 -0
- package/dist/src/core/fusion/pi-child.js +1741 -0
- package/dist/src/core/fusion/pi-child.js.map +1 -0
- package/dist/src/core/fusion/prompts.js +260 -0
- package/dist/src/core/fusion/prompts.js.map +1 -0
- package/dist/src/core/fusion/result-package.js +857 -0
- package/dist/src/core/fusion/result-package.js.map +1 -0
- package/dist/src/core/fusion/source-policy.js +259 -0
- package/dist/src/core/fusion/source-policy.js.map +1 -0
- package/dist/src/core/fusion/types.js +207 -0
- package/dist/src/core/fusion/types.js.map +1 -0
- package/dist/src/core/fusion/web-fetch.js +777 -0
- package/dist/src/core/fusion/web-fetch.js.map +1 -0
- package/dist/src/core/fusion/workflows.js +126 -0
- package/dist/src/core/fusion/workflows.js.map +1 -0
- package/dist/src/core/lazy-module.js +181 -0
- package/dist/src/core/lazy-module.js.map +1 -0
- package/dist/src/core/pi-launch.js +431 -0
- package/dist/src/core/pi-launch.js.map +1 -0
- package/dist/src/core/registry.js +3331 -0
- package/dist/src/core/registry.js.map +1 -0
- package/dist/src/core/reload-shell-owner.js +1396 -0
- package/dist/src/core/reload-shell-owner.js.map +1 -0
- package/dist/src/core/shell-policy.js +80 -0
- package/dist/src/core/shell-policy.js.map +1 -0
- package/dist/src/core/task-durable.js +51 -0
- package/dist/src/core/task-durable.js.map +1 -0
- package/dist/src/core/update-check.js +92 -0
- package/dist/src/core/update-check.js.map +1 -0
- package/dist/src/core/windows-taskkill.js +185 -0
- package/dist/src/core/windows-taskkill.js.map +1 -0
- package/dist/src/delegate-child-extension.js +776 -0
- package/dist/src/delegate-child-extension.js.map +1 -0
- package/dist/src/delegate-extension.js +746 -0
- package/dist/src/delegate-extension.js.map +1 -0
- package/dist/src/extension.js +953 -0
- package/dist/src/extension.js.map +1 -0
- package/dist/src/fusion-child-extension.js +760 -0
- package/dist/src/fusion-child-extension.js.map +1 -0
- package/dist/src/fusion-extension.js +1030 -0
- package/dist/src/fusion-extension.js.map +1 -0
- package/dist/src/ui/background-tasks-manager.js +689 -0
- package/dist/src/ui/background-tasks-manager.js.map +1 -0
- package/dist/src/ui/fusion-model-selector.js +277 -0
- package/dist/src/ui/fusion-model-selector.js.map +1 -0
- package/docs/INDEX.md +35 -33
- package/docs/api/eventbus-v1.md +21 -5
- package/docs/choose-a-workflow.md +2 -0
- package/docs/commands/bg-clear.md +3 -3
- package/docs/commands/bg-update.md +3 -3
- package/docs/commands/bg.md +18 -6
- package/docs/commands/claude-cache.md +4 -4
- package/docs/commands/fusion-models.md +49 -4
- package/docs/commands/fusion.md +3 -3
- package/docs/commands/jobs.md +3 -3
- package/docs/commands/kill.md +3 -3
- package/docs/commands/logs.md +3 -3
- package/docs/commands/task-manager.md +5 -5
- package/docs/concepts/completion-delivery.md +6 -2
- package/docs/getting-started.md +7 -1
- package/docs/manifest.json +228 -42
- package/docs/operations/configuration.md +58 -5
- package/docs/operations/releasing.md +4 -2
- package/docs/operations/testing.md +22 -0
- package/docs/operations/troubleshooting.md +6 -2
- package/docs/read-before-edit.md +44 -31
- package/docs/reference/runtime-contracts.md +87 -51
- package/docs/reference/shortcuts-and-dock.md +28 -14
- package/docs/subsystems/anthropic-attribution.md +23 -11
- package/docs/subsystems/attested-pi-runs.md +12 -7
- package/docs/subsystems/background-task-runtime.md +53 -11
- package/docs/subsystems/child-launch-durability-and-safety.md +28 -9
- package/docs/subsystems/delegation.md +16 -3
- package/docs/subsystems/docs-freshness-gate.md +14 -7
- package/docs/subsystems/fusion.md +36 -11
- package/docs/subsystems/host-ui-and-telemetry.md +22 -9
- package/docs/tools/bg_delegate.md +3 -1
- package/docs/tools/bg_kill.md +3 -1
- package/docs/tools/bg_logs.md +3 -1
- package/docs/tools/bg_result.md +5 -3
- package/docs/tools/bg_run.md +31 -5
- package/docs/tools/bg_run_pi_attested.md +3 -1
- package/docs/tools/bg_status.md +3 -1
- package/docs/tools/fusion_investigate.md +3 -1
- package/docs/tools/fusion_reason.md +3 -1
- package/docs/tools/fusion_research.md +3 -1
- package/docs/tools/fusion_validate.md +3 -1
- package/extensions/anthropic-attribution-child.ts +3 -0
- package/extensions/anthropic-attribution.ts +166 -1
- package/package.json +12 -10
- package/src/core/anthropic-attribution-path.ts +5 -4
- package/src/core/anthropic-attribution.ts +293 -113
- package/src/core/attested-pi-contract.ts +4 -0
- package/src/core/attested-pi-run.ts +468 -81
- package/src/core/canonical-json.ts +21 -0
- package/src/core/common.ts +450 -40
- package/src/core/config.ts +121 -0
- package/src/core/context/visible-conversation-v2.ts +3 -6
- package/src/core/delegate/artifacts.ts +13 -9
- package/src/core/delegate/budget.ts +13 -6
- package/src/core/delegate/facade-contract.ts +6 -0
- package/src/core/delegate/result-package.ts +32 -14
- package/src/core/delegate/runner.ts +129 -58
- package/src/core/delegate/seed.ts +5 -7
- package/src/core/durable-fs.ts +178 -30
- package/src/core/extension-api.ts +36 -8
- package/src/core/fusion/artifacts.ts +17 -16
- package/src/core/fusion/clean-context.ts +8 -5
- package/src/core/fusion/config.ts +2 -1
- package/src/core/fusion/context.ts +2 -6
- package/src/core/fusion/facade-contract.ts +2 -0
- package/src/core/fusion/orchestrator.ts +3 -3
- package/src/core/fusion/prompts.ts +1 -1
- package/src/core/fusion/result-package.ts +259 -72
- package/src/core/fusion/source-policy.ts +43 -17
- package/src/core/lazy-module.ts +215 -0
- package/src/core/pi-launch.ts +390 -65
- package/src/core/registry.ts +2010 -381
- package/src/core/reload-shell-owner.ts +1662 -0
- package/src/core/shell-policy.ts +134 -0
- package/src/core/task-durable.ts +67 -0
- package/src/delegate-extension.ts +404 -144
- package/src/extension.ts +387 -114
- package/src/fusion-extension.ts +202 -96
package/src/fusion-extension.ts
CHANGED
|
@@ -8,15 +8,10 @@ import type {
|
|
|
8
8
|
import { getMarkdownTheme } from '@earendil-works/pi-coding-agent';
|
|
9
9
|
import { Container, Markdown, Text } from '@earendil-works/pi-tui';
|
|
10
10
|
import { Type, type TSchema } from 'typebox';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
resolveFusionModels,
|
|
16
|
-
saveFusionModelConfig,
|
|
17
|
-
} from './core/fusion/config.js';
|
|
18
|
-
import * as FusionContextModule from './core/fusion/context.js';
|
|
19
|
-
import type { BuiltFusionCanonicalInput } from './core/fusion/context.js';
|
|
11
|
+
import type {
|
|
12
|
+
BuildFusionCanonicalInputOptions,
|
|
13
|
+
BuiltFusionCanonicalInput,
|
|
14
|
+
} from './core/fusion/context.js';
|
|
20
15
|
import {
|
|
21
16
|
FUSION_INVESTIGATE,
|
|
22
17
|
FUSION_REASON,
|
|
@@ -24,12 +19,9 @@ import {
|
|
|
24
19
|
FUSION_VALIDATE,
|
|
25
20
|
type FusionWorkflowProfile,
|
|
26
21
|
} from './core/fusion/workflows.js';
|
|
27
|
-
import {
|
|
28
|
-
buildCleanFusionCanonicalInput,
|
|
29
|
-
type BuiltFusionCleanTaskCanonicalInput,
|
|
30
|
-
} from './core/fusion/clean-context.js';
|
|
22
|
+
import type { BuiltFusionCleanTaskCanonicalInput } from './core/fusion/clean-context.js';
|
|
31
23
|
import { canonicalizeFusionPublicUrl } from './core/fusion/source-policy.js';
|
|
32
|
-
import { canonicalJson } from './core/
|
|
24
|
+
import { canonicalJson } from './core/canonical-json.js';
|
|
33
25
|
import type {
|
|
34
26
|
BgTask,
|
|
35
27
|
BgTaskSnapshot,
|
|
@@ -37,7 +29,7 @@ import type {
|
|
|
37
29
|
JsonObject,
|
|
38
30
|
StartManagedTaskOptions,
|
|
39
31
|
} from './core/common.js';
|
|
40
|
-
import { FusionOrchestrator } from './core/fusion/orchestrator.js';
|
|
32
|
+
import type { FusionOrchestrator } from './core/fusion/orchestrator.js';
|
|
41
33
|
import {
|
|
42
34
|
FUSION_LEGACY_RESULT_SCHEMA_VERSION,
|
|
43
35
|
FUSION_RESULT_SCHEMA_VERSION,
|
|
@@ -49,11 +41,9 @@ import {
|
|
|
49
41
|
type FusionResultDetails,
|
|
50
42
|
type FusionRunResult,
|
|
51
43
|
} from './core/fusion/types.js';
|
|
52
|
-
import {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
type FusionModelSelectorResult,
|
|
56
|
-
} from './ui/fusion-model-selector.js';
|
|
44
|
+
import type { FusionModelChoice, FusionModelSelectorResult } from './ui/fusion-model-selector.js';
|
|
45
|
+
import { CURRENT_MODEL_SELECTION } from './core/fusion/facade-contract.js';
|
|
46
|
+
import { LazyModule, SynchronousActivationCloseFence } from './core/lazy-module.js';
|
|
57
47
|
|
|
58
48
|
const FUSION_RESULT_MESSAGE_TYPE = 'fusion-result';
|
|
59
49
|
const FUSION_PROGRESS_SCHEMA_VERSION = 'pi-background-tasks.fusion-progress.v1';
|
|
@@ -94,10 +84,63 @@ interface ActiveFusionRun {
|
|
|
94
84
|
settled: Promise<void>;
|
|
95
85
|
}
|
|
96
86
|
|
|
87
|
+
export interface FusionExecutionRuntime {
|
|
88
|
+
buildFusionCanonicalInput: typeof import('./core/fusion/context.js').buildFusionCanonicalInput;
|
|
89
|
+
buildCleanFusionCanonicalInput: typeof import('./core/fusion/clean-context.js').buildCleanFusionCanonicalInput;
|
|
90
|
+
loadFusionModelConfig: typeof import('./core/fusion/config.js').loadFusionModelConfig;
|
|
91
|
+
resolveFusionModels: typeof import('./core/fusion/config.js').resolveFusionModels;
|
|
92
|
+
orchestrator: Pick<FusionOrchestrator, 'run'>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface FusionModelSelectorRuntime {
|
|
96
|
+
fusionModelConfigPath: typeof import('./core/fusion/config.js').fusionModelConfigPath;
|
|
97
|
+
loadFusionModelConfig: typeof import('./core/fusion/config.js').loadFusionModelConfig;
|
|
98
|
+
saveFusionModelConfig: typeof import('./core/fusion/config.js').saveFusionModelConfig;
|
|
99
|
+
createSelector: (
|
|
100
|
+
options: ConstructorParameters<
|
|
101
|
+
typeof import('./ui/fusion-model-selector.js').FusionModelSelector
|
|
102
|
+
>[0],
|
|
103
|
+
) => InstanceType<typeof import('./ui/fusion-model-selector.js').FusionModelSelector>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function importFusionExecutionRuntime(): Promise<FusionExecutionRuntime> {
|
|
107
|
+
const [context, cleanContext, config, orchestratorModule] = await Promise.all([
|
|
108
|
+
import('./core/fusion/context.js'),
|
|
109
|
+
import('./core/fusion/clean-context.js'),
|
|
110
|
+
import('./core/fusion/config.js'),
|
|
111
|
+
import('./core/fusion/orchestrator.js'),
|
|
112
|
+
]);
|
|
113
|
+
return {
|
|
114
|
+
buildFusionCanonicalInput: context.buildFusionCanonicalInput,
|
|
115
|
+
buildCleanFusionCanonicalInput: cleanContext.buildCleanFusionCanonicalInput,
|
|
116
|
+
loadFusionModelConfig: config.loadFusionModelConfig,
|
|
117
|
+
resolveFusionModels: config.resolveFusionModels,
|
|
118
|
+
orchestrator: new orchestratorModule.FusionOrchestrator(),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function importFusionModelSelectorRuntime(): Promise<FusionModelSelectorRuntime> {
|
|
123
|
+
const [config, selector] = await Promise.all([
|
|
124
|
+
import('./core/fusion/config.js'),
|
|
125
|
+
import('./ui/fusion-model-selector.js'),
|
|
126
|
+
]);
|
|
127
|
+
return {
|
|
128
|
+
fusionModelConfigPath: config.fusionModelConfigPath,
|
|
129
|
+
loadFusionModelConfig: config.loadFusionModelConfig,
|
|
130
|
+
saveFusionModelConfig: config.saveFusionModelConfig,
|
|
131
|
+
createSelector: (options) => new selector.FusionModelSelector(options),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
97
135
|
export interface FusionExtensionDependencies {
|
|
98
136
|
startManagedTask: (ctx: ExtensionContext, options: StartManagedTaskOptions) => Promise<BgTask>;
|
|
99
137
|
snapshot: (task: BgTask) => BgTaskSnapshot;
|
|
100
138
|
updateManagedTask: (task: BgTask, state: string, line?: string) => Promise<void>;
|
|
139
|
+
/** Activation-local fence installed synchronously before asynchronous cleanup. */
|
|
140
|
+
activationCloseFence: SynchronousActivationCloseFence;
|
|
141
|
+
/** Internal deterministic deferred-import seams. */
|
|
142
|
+
loadExecutionRuntime?: (() => Promise<FusionExecutionRuntime>) | undefined;
|
|
143
|
+
loadModelSelectorRuntime?: (() => Promise<FusionModelSelectorRuntime>) | undefined;
|
|
101
144
|
}
|
|
102
145
|
|
|
103
146
|
interface FusionRunRequest {
|
|
@@ -720,18 +763,21 @@ function declaredSourcesForRequest(
|
|
|
720
763
|
return 'sources' in request ? request.sources : [];
|
|
721
764
|
}
|
|
722
765
|
|
|
723
|
-
function buildFusionInput(
|
|
766
|
+
function buildFusionInput(
|
|
767
|
+
runtime: FusionExecutionRuntime,
|
|
768
|
+
request: FusionRunRequest,
|
|
769
|
+
): BuiltFusionWorkflowInput {
|
|
724
770
|
if (request.profile.contextKind === 'session_projection') {
|
|
725
|
-
const options:
|
|
771
|
+
const options: BuildFusionCanonicalInputOptions = {
|
|
726
772
|
source: request.source,
|
|
727
773
|
request: serializePublicRequest(request.request),
|
|
728
774
|
workflow: request.profile.id,
|
|
729
775
|
toolName: request.toolName,
|
|
730
776
|
};
|
|
731
777
|
if (request.toolCallId !== undefined) options.toolCallId = request.toolCallId;
|
|
732
|
-
return
|
|
778
|
+
return runtime.buildFusionCanonicalInput(request.ctx, options);
|
|
733
779
|
}
|
|
734
|
-
return buildCleanFusionCanonicalInput({
|
|
780
|
+
return runtime.buildCleanFusionCanonicalInput({
|
|
735
781
|
cwd: request.ctx.cwd,
|
|
736
782
|
source: request.source,
|
|
737
783
|
request: serializePublicRequest(request.request),
|
|
@@ -759,17 +805,46 @@ function renderToolCall(name: string, preview: string, theme: Theme) {
|
|
|
759
805
|
}
|
|
760
806
|
|
|
761
807
|
export function registerFusionExtension(pi: ExtensionAPI, deps: FusionExtensionDependencies): void {
|
|
762
|
-
const
|
|
808
|
+
const executionRuntime = new LazyModule(
|
|
809
|
+
'fusion-execution',
|
|
810
|
+
deps.loadExecutionRuntime ?? importFusionExecutionRuntime,
|
|
811
|
+
);
|
|
812
|
+
const modelSelectorRuntime = new LazyModule(
|
|
813
|
+
'fusion-model-selector',
|
|
814
|
+
deps.loadModelSelectorRuntime ?? importFusionModelSelectorRuntime,
|
|
815
|
+
);
|
|
763
816
|
const activeRuns = new Set<ActiveFusionRun>();
|
|
764
817
|
let shuttingDown = false;
|
|
765
818
|
let lifecycleGeneration = 0;
|
|
819
|
+
let shutdownCleanupPromise: Promise<PromiseSettledResult<void>[]> | undefined;
|
|
820
|
+
const closedActivationError = new Error(
|
|
821
|
+
'lazy_module_closed: Fusion facade belongs to a closed activation (Pi session shutdown)',
|
|
822
|
+
);
|
|
823
|
+
const assertFusionActive = (): void => {
|
|
824
|
+
if (shuttingDown) throw closedActivationError;
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
const beginFusionShutdown = (): void => {
|
|
828
|
+
if (shuttingDown) return;
|
|
829
|
+
// Closure, generation advance, and cancellation all happen before any
|
|
830
|
+
// shutdown handler awaits, including handlers registered by other facades.
|
|
831
|
+
shuttingDown = true;
|
|
832
|
+
lifecycleGeneration += 1;
|
|
833
|
+
executionRuntime.close('Pi session shutdown');
|
|
834
|
+
modelSelectorRuntime.close('Pi session shutdown');
|
|
835
|
+
const runs = [...activeRuns];
|
|
836
|
+
for (const run of runs) run.controller.abort();
|
|
837
|
+
shutdownCleanupPromise = Promise.allSettled(runs.map((run) => run.settled));
|
|
838
|
+
};
|
|
839
|
+
|
|
840
|
+
deps.activationCloseFence.add(beginFusionShutdown);
|
|
766
841
|
|
|
767
842
|
async function runFusion(
|
|
768
843
|
request: FusionRunRequest,
|
|
769
844
|
suppliedController?: AbortController,
|
|
770
845
|
onReady?: Parameters<FusionOrchestrator['run']>[0]['onReady'],
|
|
771
846
|
): Promise<FusionRunResult> {
|
|
772
|
-
|
|
847
|
+
assertFusionActive();
|
|
773
848
|
const generation = lifecycleGeneration;
|
|
774
849
|
const controller = suppliedController ?? new AbortController();
|
|
775
850
|
let resolveSettled: () => void = () => undefined;
|
|
@@ -787,39 +862,51 @@ export function registerFusionExtension(pi: ExtensionAPI, deps: FusionExtensionD
|
|
|
787
862
|
});
|
|
788
863
|
if (shuttingDown || lifecycleGeneration !== generation)
|
|
789
864
|
throw new Error('fusion extension is shutting down');
|
|
865
|
+
executionRuntime.assertOpen();
|
|
790
866
|
};
|
|
791
867
|
try {
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
868
|
+
return await executionRuntime.run(async (runtime) => {
|
|
869
|
+
assertActive();
|
|
870
|
+
const built = buildFusionInput(runtime, request);
|
|
871
|
+
const cwd = request.ctx.cwd;
|
|
872
|
+
const sessionId = request.ctx.sessionManager.getSessionId();
|
|
873
|
+
const modelRegistry = request.ctx.modelRegistry;
|
|
874
|
+
const currentModel = request.ctx.model;
|
|
875
|
+
const thinkingLevel = pi.getThinkingLevel();
|
|
876
|
+
const loadedConfig = await runtime.loadFusionModelConfig();
|
|
877
|
+
assertActive();
|
|
878
|
+
const models = runtime.resolveFusionModels({
|
|
879
|
+
config: loadedConfig.config,
|
|
880
|
+
modelRegistry,
|
|
881
|
+
currentModel,
|
|
882
|
+
thinkingLevel,
|
|
883
|
+
});
|
|
884
|
+
assertActive();
|
|
885
|
+
const guardedOnReady =
|
|
886
|
+
onReady === undefined
|
|
887
|
+
? undefined
|
|
888
|
+
: async (ready: Parameters<NonNullable<typeof onReady>>[0]) => {
|
|
889
|
+
assertActive();
|
|
890
|
+
await onReady(ready);
|
|
891
|
+
assertActive();
|
|
892
|
+
};
|
|
893
|
+
const runInput: Parameters<FusionOrchestrator['run']>[0] = {
|
|
894
|
+
source: request.source,
|
|
895
|
+
cwd,
|
|
896
|
+
sessionId,
|
|
897
|
+
canonicalInput: built.input,
|
|
898
|
+
canonicalInputSerialized: built.serialized,
|
|
899
|
+
config: loadedConfig.config,
|
|
900
|
+
models,
|
|
901
|
+
profile: request.profile,
|
|
902
|
+
signal: controller.signal,
|
|
903
|
+
onProgress: request.onProgress,
|
|
904
|
+
onReady: guardedOnReady,
|
|
905
|
+
};
|
|
906
|
+
if ('ledger' in built) runInput.contextLedger = built.ledger;
|
|
907
|
+
assertActive();
|
|
908
|
+
return runtime.orchestrator.run(runInput);
|
|
806
909
|
});
|
|
807
|
-
assertActive();
|
|
808
|
-
const runInput: Parameters<FusionOrchestrator['run']>[0] = {
|
|
809
|
-
source: request.source,
|
|
810
|
-
cwd,
|
|
811
|
-
sessionId,
|
|
812
|
-
canonicalInput: built.input,
|
|
813
|
-
canonicalInputSerialized: built.serialized,
|
|
814
|
-
config: loaded.config,
|
|
815
|
-
models,
|
|
816
|
-
profile: request.profile,
|
|
817
|
-
signal: controller.signal,
|
|
818
|
-
onProgress: request.onProgress,
|
|
819
|
-
onReady,
|
|
820
|
-
};
|
|
821
|
-
if ('ledger' in built) runInput.contextLedger = built.ledger;
|
|
822
|
-
return await orchestrator.run(runInput);
|
|
823
910
|
} finally {
|
|
824
911
|
unlink();
|
|
825
912
|
activeRuns.delete(active);
|
|
@@ -968,7 +1055,7 @@ export function registerFusionExtension(pi: ExtensionAPI, deps: FusionExtensionD
|
|
|
968
1055
|
args: string,
|
|
969
1056
|
ctx: ExtensionCommandContext,
|
|
970
1057
|
): Promise<string | undefined> {
|
|
971
|
-
const direct =
|
|
1058
|
+
const direct = args.trim();
|
|
972
1059
|
if (direct.length > 0) return direct;
|
|
973
1060
|
if (!ctx.hasUI) throw new Error(FUSION_COMMAND_USAGE);
|
|
974
1061
|
const edited = await ctx.ui.editor('Fusion prompt', '');
|
|
@@ -998,10 +1085,13 @@ export function registerFusionExtension(pi: ExtensionAPI, deps: FusionExtensionD
|
|
|
998
1085
|
handler: async (args, ctx) => {
|
|
999
1086
|
let requestText: string | undefined;
|
|
1000
1087
|
try {
|
|
1088
|
+
assertFusionActive();
|
|
1001
1089
|
requestText = await promptFromCommandArgs(args, ctx);
|
|
1090
|
+
assertFusionActive();
|
|
1002
1091
|
if (requestText === undefined) return;
|
|
1003
1092
|
const request = prepareFusionReasonArguments({ prompt: requestText });
|
|
1004
1093
|
await ctx.waitForIdle();
|
|
1094
|
+
assertFusionActive();
|
|
1005
1095
|
const task = await launchFusionTask({
|
|
1006
1096
|
source: 'command',
|
|
1007
1097
|
ctx,
|
|
@@ -1012,6 +1102,7 @@ export function registerFusionExtension(pi: ExtensionAPI, deps: FusionExtensionD
|
|
|
1012
1102
|
const fusion = task.fusion;
|
|
1013
1103
|
if (fusion === undefined)
|
|
1014
1104
|
throw new Error('Fusion command task was registered without Fusion facts');
|
|
1105
|
+
assertFusionActive();
|
|
1015
1106
|
if (ctx.hasUI) {
|
|
1016
1107
|
ctx.ui.notify(
|
|
1017
1108
|
`Started fusion reason (${task.id})\nArtifacts: ${fusion.artifactDir}\nIt will notify on completion; retrieve with bg_result.`,
|
|
@@ -1019,6 +1110,9 @@ export function registerFusionExtension(pi: ExtensionAPI, deps: FusionExtensionD
|
|
|
1019
1110
|
);
|
|
1020
1111
|
}
|
|
1021
1112
|
} catch (error) {
|
|
1113
|
+
// Closure wins over command error rendering: never inspect or notify an
|
|
1114
|
+
// old host context after the activation fence has fired.
|
|
1115
|
+
assertFusionActive();
|
|
1022
1116
|
const message = `Fusion failed: ${errorMessage(error)}${errorArtifactSuffix(error)}`;
|
|
1023
1117
|
if (!ctx.hasUI) throw new Error(message);
|
|
1024
1118
|
ctx.ui.notify(message, 'error');
|
|
@@ -1029,6 +1123,7 @@ export function registerFusionExtension(pi: ExtensionAPI, deps: FusionExtensionD
|
|
|
1029
1123
|
pi.registerCommand(FUSION_MODEL_COMMAND_NAME, {
|
|
1030
1124
|
description: 'Open the five-slot global fusion model selector.',
|
|
1031
1125
|
handler: async (_args, ctx) => {
|
|
1126
|
+
assertFusionActive();
|
|
1032
1127
|
const modeError =
|
|
1033
1128
|
'/fusion-models requires Pi TUI mode; it is unavailable in RPC, JSON, and print modes.';
|
|
1034
1129
|
if (!ctx.hasUI) throw new Error(modeError);
|
|
@@ -1036,41 +1131,54 @@ export function registerFusionExtension(pi: ExtensionAPI, deps: FusionExtensionD
|
|
|
1036
1131
|
ctx.ui.notify(modeError, 'error');
|
|
1037
1132
|
return;
|
|
1038
1133
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
loaded
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1134
|
+
await modelSelectorRuntime.run(async (runtime) => {
|
|
1135
|
+
modelSelectorRuntime.assertOpen();
|
|
1136
|
+
const path = runtime.fusionModelConfigPath();
|
|
1137
|
+
let loaded: Awaited<ReturnType<typeof runtime.loadFusionModelConfig>>;
|
|
1138
|
+
try {
|
|
1139
|
+
loaded = await runtime.loadFusionModelConfig(path);
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
assertFusionActive();
|
|
1142
|
+
modelSelectorRuntime.assertOpen();
|
|
1143
|
+
ctx.ui.notify(`Cannot open ${path}: ${errorMessage(error)}`, 'error');
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
assertFusionActive();
|
|
1147
|
+
modelSelectorRuntime.assertOpen();
|
|
1148
|
+
const choices = choicesForSelector(ctx, loaded.config);
|
|
1149
|
+
const result = await ctx.ui.custom<FusionModelSelectorResult>(
|
|
1150
|
+
(tui, theme, _keybindings, done) =>
|
|
1151
|
+
runtime.createSelector({
|
|
1152
|
+
initialConfig: loaded.config,
|
|
1153
|
+
choices,
|
|
1154
|
+
theme,
|
|
1155
|
+
onSave: async (config) => {
|
|
1156
|
+
assertFusionActive();
|
|
1157
|
+
modelSelectorRuntime.assertOpen();
|
|
1158
|
+
await runtime.saveFusionModelConfig(path, config, loaded.revision);
|
|
1159
|
+
assertFusionActive();
|
|
1160
|
+
modelSelectorRuntime.assertOpen();
|
|
1161
|
+
},
|
|
1162
|
+
onDone: done,
|
|
1163
|
+
onRenderRequest: () => {
|
|
1164
|
+
tui.requestRender();
|
|
1165
|
+
},
|
|
1166
|
+
}),
|
|
1167
|
+
{
|
|
1168
|
+
overlay: true,
|
|
1169
|
+
overlayOptions: {
|
|
1170
|
+
anchor: 'center',
|
|
1171
|
+
width: '82%',
|
|
1172
|
+
minWidth: 64,
|
|
1173
|
+
maxHeight: '75%',
|
|
1060
1174
|
},
|
|
1061
|
-
}),
|
|
1062
|
-
{
|
|
1063
|
-
overlay: true,
|
|
1064
|
-
overlayOptions: {
|
|
1065
|
-
anchor: 'center',
|
|
1066
|
-
width: '82%',
|
|
1067
|
-
minWidth: 64,
|
|
1068
|
-
maxHeight: '75%',
|
|
1069
1175
|
},
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1176
|
+
);
|
|
1177
|
+
assertFusionActive();
|
|
1178
|
+
modelSelectorRuntime.assertOpen();
|
|
1179
|
+
if (result.type === 'saved')
|
|
1180
|
+
ctx.ui.notify(`Saved fusion model configuration to ${path}`, 'info');
|
|
1181
|
+
});
|
|
1074
1182
|
},
|
|
1075
1183
|
});
|
|
1076
1184
|
|
|
@@ -1254,7 +1362,7 @@ export function registerFusionExtension(pi: ExtensionAPI, deps: FusionExtensionD
|
|
|
1254
1362
|
});
|
|
1255
1363
|
|
|
1256
1364
|
pi.on('session_start', () => {
|
|
1257
|
-
|
|
1365
|
+
// Reload creates a fresh facade. Never reopen a closed stale activation.
|
|
1258
1366
|
lifecycleGeneration += 1;
|
|
1259
1367
|
const next: string[] = [];
|
|
1260
1368
|
const seen = new Set<string>();
|
|
@@ -1274,11 +1382,9 @@ export function registerFusionExtension(pi: ExtensionAPI, deps: FusionExtensionD
|
|
|
1274
1382
|
});
|
|
1275
1383
|
|
|
1276
1384
|
pi.on('session_shutdown', async (_event, ctx) => {
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
const
|
|
1280
|
-
for (const run of runs) run.controller.abort();
|
|
1281
|
-
const settled = await Promise.allSettled(runs.map((run) => run.settled));
|
|
1385
|
+
const cleanup = shutdownCleanupPromise;
|
|
1386
|
+
if (cleanup === undefined) return;
|
|
1387
|
+
const settled = await cleanup;
|
|
1282
1388
|
const failures = settled.flatMap((result) =>
|
|
1283
1389
|
result.status === 'rejected' ? [errorMessage(result.reason)] : [],
|
|
1284
1390
|
);
|