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/extension.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
1
3
|
import type {
|
|
2
4
|
ExtensionAPI,
|
|
3
5
|
ExtensionCommandContext,
|
|
@@ -28,26 +30,32 @@ import {
|
|
|
28
30
|
type BgStatusDetails,
|
|
29
31
|
type BgTask,
|
|
30
32
|
type BgTaskSnapshot,
|
|
33
|
+
type ReloadShellActivationClaimV1,
|
|
34
|
+
type ReloadShellActivationLeaseV1,
|
|
35
|
+
type ReloadShellIdentityV1,
|
|
31
36
|
type StartAttestedPiTaskOptions,
|
|
32
37
|
type StartTaskOptions,
|
|
38
|
+
ReloadSurvivalError,
|
|
39
|
+
rejectSurvivalForTaskKind,
|
|
33
40
|
} from './core/common.js';
|
|
34
41
|
import {
|
|
35
42
|
fetchLatestVersion,
|
|
36
43
|
readPackageInfo,
|
|
37
44
|
type FetchLatestVersionOptions,
|
|
38
45
|
} from './core/update-check.js';
|
|
39
|
-
import { BackgroundTaskRegistry } from './core/registry.js';
|
|
46
|
+
import { BackgroundTaskRegistry, type BackgroundTaskContext } from './core/registry.js';
|
|
47
|
+
import {
|
|
48
|
+
getProcessReloadShellOwnerV1,
|
|
49
|
+
makeReloadShellIdentity,
|
|
50
|
+
} from './core/reload-shell-owner.js';
|
|
51
|
+
import { createShellPolicyGuidanceHandler, initializeShellPolicy } from './core/shell-policy.js';
|
|
40
52
|
import {
|
|
41
53
|
installBackgroundTaskExtensionApi,
|
|
42
54
|
type BackgroundTaskExtensionService,
|
|
43
55
|
} from './core/extension-api.js';
|
|
44
|
-
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
type TaskManagerResult,
|
|
48
|
-
} from './ui/background-tasks-manager.js';
|
|
49
|
-
import { registerFusionExtension } from './fusion-extension.js';
|
|
50
|
-
import { registerDelegateExtension } from './delegate-extension.js';
|
|
56
|
+
import type { BackgroundTaskForUi, TaskManagerResult } from './ui/background-tasks-manager.js';
|
|
57
|
+
import { dockShortcutFooterHint, parseBackgroundTasksConfig } from './core/config.js';
|
|
58
|
+
import { LazyModule, SynchronousActivationCloseFence } from './core/lazy-module.js';
|
|
51
59
|
|
|
52
60
|
/**
|
|
53
61
|
* Project-local Pi background task manager.
|
|
@@ -55,8 +63,9 @@ import { registerDelegateExtension } from './delegate-extension.js';
|
|
|
55
63
|
* Scope:
|
|
56
64
|
* - Explicit background shell jobs only: /bg and bg_run spawn commands directly.
|
|
57
65
|
* - No Ctrl+B support for backgrounding an already-running built-in bash tool.
|
|
58
|
-
* -
|
|
59
|
-
* extension
|
|
66
|
+
* - Opted ordinary shell jobs can hand their same live process ownership to a
|
|
67
|
+
* fresh extension activation on real same-process reload only.
|
|
68
|
+
* - No PID/file adoption, process-restart survival, or crash recovery.
|
|
60
69
|
*/
|
|
61
70
|
|
|
62
71
|
const STATUS_INTERVAL_MS = 1000;
|
|
@@ -71,7 +80,6 @@ const PACKAGE_VERSION = packageInfo.version;
|
|
|
71
80
|
const LIGHT_BLUE_BG = '\x1b[48;2;183;223;255m';
|
|
72
81
|
const LIGHT_BLUE_FG = '\x1b[38;2;11;70;110m';
|
|
73
82
|
const ANSI_RESET = '\x1b[0m';
|
|
74
|
-
|
|
75
83
|
function lightBlue(value: string): string {
|
|
76
84
|
return `${LIGHT_BLUE_BG}${LIGHT_BLUE_FG}${value}${ANSI_RESET}`;
|
|
77
85
|
}
|
|
@@ -92,6 +100,7 @@ interface BgToolArgumentRecord {
|
|
|
92
100
|
readonly timeoutSeconds?: unknown;
|
|
93
101
|
readonly notifyOnCompletion?: unknown;
|
|
94
102
|
readonly triggerOnCompletion?: unknown;
|
|
103
|
+
readonly surviveReload?: unknown;
|
|
95
104
|
}
|
|
96
105
|
|
|
97
106
|
interface BgPiAttestedArgumentRecord {
|
|
@@ -103,6 +112,7 @@ interface BgPiAttestedArgumentRecord {
|
|
|
103
112
|
readonly extraPiArgs?: unknown;
|
|
104
113
|
readonly thinking?: unknown;
|
|
105
114
|
readonly timeoutSeconds?: unknown;
|
|
115
|
+
readonly surviveReload?: unknown;
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
function optionalTrimmed(value: string): string | undefined {
|
|
@@ -138,6 +148,12 @@ const BgRunParams = Type.Object({
|
|
|
138
148
|
'Whether that notification should automatically trigger a follow-up agent turn. Default: true for bg_run; requires notifyOnCompletion.',
|
|
139
149
|
}),
|
|
140
150
|
),
|
|
151
|
+
surviveReload: Type.Optional(
|
|
152
|
+
Type.Boolean({
|
|
153
|
+
description:
|
|
154
|
+
'Opt in to retaining this exact ordinary isAgent:false shell execution across a real same-process Pi reload. Default: false. Unsupported for agent, managed, delegate, Fusion, and attested tasks.',
|
|
155
|
+
}),
|
|
156
|
+
),
|
|
141
157
|
});
|
|
142
158
|
|
|
143
159
|
const BgPiAttestedParams = Type.Object({
|
|
@@ -203,13 +219,38 @@ function renderPlainResult(result: TextToolResult, options: ToolRenderResultOpti
|
|
|
203
219
|
return new Text(text, 0, 0);
|
|
204
220
|
}
|
|
205
221
|
|
|
206
|
-
export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
222
|
+
export default async function backgroundTasksExtension(pi: ExtensionAPI): Promise<void> {
|
|
223
|
+
const config = parseBackgroundTasksConfig();
|
|
224
|
+
const dockEntryHint = dockShortcutFooterHint(config.dockShortcut);
|
|
225
|
+
const shellPolicy = initializeShellPolicy();
|
|
226
|
+
const reloadShellOwner = getProcessReloadShellOwnerV1();
|
|
227
|
+
pi.on('before_agent_start', createShellPolicyGuidanceHandler(shellPolicy));
|
|
207
228
|
const seenTaskIds = new Set<string>();
|
|
208
229
|
let currentCtx: ExtensionContext | undefined;
|
|
230
|
+
let currentRegistryCtx: BackgroundTaskContext | undefined;
|
|
231
|
+
let activationLease: ReloadShellActivationLeaseV1 | undefined;
|
|
232
|
+
let activationIdentity: ReloadShellIdentityV1 | undefined;
|
|
233
|
+
let pendingActivationClaim: ReloadShellActivationClaimV1 | undefined;
|
|
209
234
|
let dockOpen = false;
|
|
210
235
|
let statusInterval: NodeJS.Timeout | undefined;
|
|
211
236
|
let latestKnownVersion: string | undefined;
|
|
212
237
|
let updateCheckStarted = false;
|
|
238
|
+
let disposed = false;
|
|
239
|
+
let shutdownCleanupStarted = false;
|
|
240
|
+
let reloadHandoffFailed = false;
|
|
241
|
+
let shutdownReason = 'shutdown';
|
|
242
|
+
const activationCloseFence = new SynchronousActivationCloseFence();
|
|
243
|
+
const taskManagerLoader = new LazyModule(
|
|
244
|
+
'background-task-manager',
|
|
245
|
+
() => import('./ui/background-tasks-manager.js'),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const registryContext = (ctx: ExtensionContext): BackgroundTaskContext => ({
|
|
249
|
+
cwd: ctx.cwd,
|
|
250
|
+
sessionId: ctx.sessionManager.getSessionId(),
|
|
251
|
+
modelRegistry: ctx.modelRegistry,
|
|
252
|
+
model: ctx.model,
|
|
253
|
+
});
|
|
213
254
|
|
|
214
255
|
const registry = new BackgroundTaskRegistry({
|
|
215
256
|
onChange: () => {
|
|
@@ -221,33 +262,187 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
221
262
|
publishTerminal: (task) => {
|
|
222
263
|
eventService.publishTerminal(task);
|
|
223
264
|
},
|
|
265
|
+
shellPolicy,
|
|
266
|
+
reloadShellOwner,
|
|
224
267
|
});
|
|
225
268
|
const eventService: BackgroundTaskExtensionService = installBackgroundTaskExtensionApi({
|
|
226
269
|
events: pi.events,
|
|
227
270
|
registry,
|
|
228
|
-
getContext: () =>
|
|
271
|
+
getContext: () => currentRegistryCtx,
|
|
229
272
|
isShuttingDown: () => registry.isShuttingDown(),
|
|
230
273
|
});
|
|
231
274
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
275
|
+
const beginSessionShutdown = (reason: string): void => {
|
|
276
|
+
let handoffError: unknown;
|
|
277
|
+
if (!disposed) {
|
|
278
|
+
disposed = true;
|
|
279
|
+
registry.closeTaskAdmissions();
|
|
280
|
+
if (pendingActivationClaim !== undefined) {
|
|
281
|
+
const claim = pendingActivationClaim;
|
|
282
|
+
pendingActivationClaim = undefined;
|
|
283
|
+
registry.abortReloadActivation(claim);
|
|
284
|
+
try {
|
|
285
|
+
reloadShellOwner.abortActivation(
|
|
286
|
+
claim,
|
|
287
|
+
new ReloadSurvivalError(
|
|
288
|
+
'pi_bg_reload_owner_stale_claim',
|
|
289
|
+
'activation shut down while its reload claim was staging',
|
|
290
|
+
),
|
|
291
|
+
);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
handoffError = error;
|
|
294
|
+
}
|
|
295
|
+
} else if (reason === 'reload' && activationLease !== undefined) {
|
|
296
|
+
const lease = activationLease;
|
|
297
|
+
try {
|
|
298
|
+
registry.prepareReloadHandoff(lease);
|
|
299
|
+
activationLease = undefined;
|
|
300
|
+
activationIdentity = undefined;
|
|
301
|
+
} catch (error) {
|
|
302
|
+
reloadHandoffFailed = true;
|
|
303
|
+
handoffError = error;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
registry.setShuttingDown(true);
|
|
307
|
+
eventService.close();
|
|
308
|
+
}
|
|
309
|
+
// Teardown remains active on repeated calls so even a handle assigned by a
|
|
310
|
+
// racing continuation is still disposed rather than hidden by idempotence.
|
|
311
|
+
currentCtx = undefined;
|
|
312
|
+
currentRegistryCtx = undefined;
|
|
313
|
+
if (statusInterval !== undefined) {
|
|
314
|
+
clearInterval(statusInterval);
|
|
315
|
+
statusInterval = undefined;
|
|
316
|
+
}
|
|
317
|
+
if (handoffError !== undefined) throw handoffError;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// Join the one synchronous all-lane barrier before any facade can register
|
|
321
|
+
// asynchronous cleanup. The core callback performs an eligible reload
|
|
322
|
+
// handoff first; every lazy lane then closes on the same call stack before
|
|
323
|
+
// Pi can await any later cleanup handler.
|
|
324
|
+
activationCloseFence.add(() => {
|
|
325
|
+
beginSessionShutdown(shutdownReason);
|
|
326
|
+
});
|
|
327
|
+
activationCloseFence.add(() => {
|
|
328
|
+
taskManagerLoader.close('session shutdown');
|
|
329
|
+
});
|
|
330
|
+
pi.on('session_shutdown', (event) => {
|
|
331
|
+
shutdownReason = event.reason;
|
|
332
|
+
activationCloseFence.close();
|
|
239
333
|
});
|
|
240
334
|
|
|
241
|
-
|
|
242
|
-
|
|
335
|
+
// This is the first session_start callback. Claim synchronously before the
|
|
336
|
+
// first await, stage/import durably, then expose the fresh host adapter.
|
|
337
|
+
pi.on('session_start', async (event, ctx) => {
|
|
338
|
+
if (disposed) return;
|
|
339
|
+
const nextRegistryCtx = registryContext(ctx);
|
|
340
|
+
const identity = makeReloadShellIdentity(
|
|
341
|
+
nextRegistryCtx.sessionId ?? '',
|
|
342
|
+
realpathSync(ctx.cwd),
|
|
343
|
+
);
|
|
344
|
+
if (activationLease !== undefined && registry.hasCurrentReloadLease()) {
|
|
345
|
+
if (
|
|
346
|
+
activationIdentity?.hostPid !== identity.hostPid ||
|
|
347
|
+
activationIdentity.sessionId !== identity.sessionId ||
|
|
348
|
+
activationIdentity.cwdRealpath !== identity.cwdRealpath
|
|
349
|
+
) {
|
|
350
|
+
throw new ReloadSurvivalError(
|
|
351
|
+
'pi_bg_reload_owner_activation_conflict',
|
|
352
|
+
'a repeated session_start changed the bound reload owner identity without shutdown',
|
|
353
|
+
);
|
|
354
|
+
}
|
|
243
355
|
currentCtx = ctx;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
356
|
+
currentRegistryCtx = nextRegistryCtx;
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
const activationNonce = randomBytes(16).toString('hex');
|
|
360
|
+
const claim = reloadShellOwner.beginActivation(identity, event.reason, activationNonce);
|
|
361
|
+
pendingActivationClaim = claim;
|
|
362
|
+
try {
|
|
363
|
+
const adapter = await registry.stageReloadActivation(claim);
|
|
364
|
+
if (pendingActivationClaim !== claim) {
|
|
365
|
+
// The synchronous shutdown barrier already aborted this claim and
|
|
366
|
+
// detached staged records while the durable import awaited.
|
|
367
|
+
registry.abortReloadActivation(claim);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (disposed) {
|
|
371
|
+
registry.abortReloadActivation(claim);
|
|
372
|
+
pendingActivationClaim = undefined;
|
|
373
|
+
reloadShellOwner.abortActivation(
|
|
374
|
+
claim,
|
|
375
|
+
new ReloadSurvivalError(
|
|
376
|
+
'pi_bg_reload_owner_stale_claim',
|
|
377
|
+
'activation was disposed before reload claim commit',
|
|
378
|
+
),
|
|
379
|
+
);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const lease = reloadShellOwner.commitActivation(claim, adapter);
|
|
383
|
+
pendingActivationClaim = undefined;
|
|
384
|
+
activationLease = lease;
|
|
385
|
+
activationIdentity = identity;
|
|
386
|
+
currentCtx = ctx;
|
|
387
|
+
currentRegistryCtx = nextRegistryCtx;
|
|
388
|
+
} catch (error) {
|
|
389
|
+
registry.abortReloadActivation(claim);
|
|
390
|
+
if (pendingActivationClaim === claim) pendingActivationClaim = undefined;
|
|
391
|
+
try {
|
|
392
|
+
reloadShellOwner.abortActivation(claim, error);
|
|
393
|
+
} catch (abortError) {
|
|
394
|
+
if (
|
|
395
|
+
typeof abortError !== 'object' ||
|
|
396
|
+
abortError === null ||
|
|
397
|
+
Reflect.get(abortError, 'code') !== 'pi_bg_reload_owner_stale_claim'
|
|
398
|
+
) {
|
|
399
|
+
throw new AggregateError([error, abortError], 'Reload activation claim and abort failed');
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
throw error;
|
|
403
|
+
}
|
|
249
404
|
});
|
|
250
405
|
|
|
406
|
+
if (config.features.fusion) {
|
|
407
|
+
const { registerFusionExtension } = await import('./fusion-extension.js');
|
|
408
|
+
registerFusionExtension(pi, {
|
|
409
|
+
startManagedTask: async (ctx, options) => {
|
|
410
|
+
currentCtx = ctx;
|
|
411
|
+
const nextRegistryCtx = registryContext(ctx);
|
|
412
|
+
currentRegistryCtx = nextRegistryCtx;
|
|
413
|
+
return registry.startManagedTask(nextRegistryCtx, options);
|
|
414
|
+
},
|
|
415
|
+
snapshot: (task) => registry.snapshot(task),
|
|
416
|
+
updateManagedTask: (task, state, line) => registry.updateManagedTask(task, state, line),
|
|
417
|
+
activationCloseFence,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (config.features.delegate) {
|
|
422
|
+
const { registerDelegateExtension } = await import('./delegate-extension.js');
|
|
423
|
+
registerDelegateExtension(pi, {
|
|
424
|
+
startDelegateTask: async (ctx, options) => {
|
|
425
|
+
currentCtx = ctx;
|
|
426
|
+
const nextRegistryCtx = registryContext(ctx);
|
|
427
|
+
currentRegistryCtx = nextRegistryCtx;
|
|
428
|
+
return registry.startDelegateTask(nextRegistryCtx, options);
|
|
429
|
+
},
|
|
430
|
+
snapshot: (task) => registry.snapshot(task),
|
|
431
|
+
isDelegateTaskRegistered: (taskId) =>
|
|
432
|
+
registry.allTasks().some((task) => task.id === taskId && task.delegate !== undefined),
|
|
433
|
+
activationCloseFence,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (config.features.delegate || config.features.fusion) {
|
|
438
|
+
const { registerBackgroundResultExtension } = await import('./delegate-extension.js');
|
|
439
|
+
registerBackgroundResultExtension(pi, {
|
|
440
|
+
resolveTask: (idOrPrefix) => registry.resolveTask(idOrPrefix),
|
|
441
|
+
claimFusionUsage: (task) => registry.claimFusionUsage(task),
|
|
442
|
+
activationCloseFence,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
251
446
|
function unseenFinishedTasks(): BgTask[] {
|
|
252
447
|
return registry
|
|
253
448
|
.allTasks()
|
|
@@ -306,7 +501,7 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
306
501
|
if (unseenDone.length > 0) parts.push(`${String(unseenDone.length)} done`);
|
|
307
502
|
const entryHint = dockOpen
|
|
308
503
|
? 'focused'
|
|
309
|
-
:
|
|
504
|
+
: `${dockEntryHint}${unseenFinishedCount > 0 ? ' · /bg-clear' : ''}`;
|
|
310
505
|
const segments = [...parts, entryHint];
|
|
311
506
|
if (updateSegment) segments.push(updateSegment);
|
|
312
507
|
const label = ` bg ${segments.join(' · ')} `;
|
|
@@ -325,7 +520,9 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
325
520
|
options: StartTaskOptions = {},
|
|
326
521
|
): Promise<BgTask> {
|
|
327
522
|
currentCtx = ctx;
|
|
328
|
-
|
|
523
|
+
const nextRegistryCtx = registryContext(ctx);
|
|
524
|
+
currentRegistryCtx = nextRegistryCtx;
|
|
525
|
+
return registry.startTask(nextRegistryCtx, command, options);
|
|
329
526
|
}
|
|
330
527
|
|
|
331
528
|
async function startAttestedPiTask(
|
|
@@ -333,7 +530,9 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
333
530
|
options: StartAttestedPiTaskOptions,
|
|
334
531
|
): Promise<BgTask> {
|
|
335
532
|
currentCtx = ctx;
|
|
336
|
-
|
|
533
|
+
const nextRegistryCtx = registryContext(ctx);
|
|
534
|
+
currentRegistryCtx = nextRegistryCtx;
|
|
535
|
+
return registry.startAttestedPiTask(nextRegistryCtx, options);
|
|
337
536
|
}
|
|
338
537
|
|
|
339
538
|
async function openTaskManager(
|
|
@@ -348,6 +547,7 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
348
547
|
);
|
|
349
548
|
return;
|
|
350
549
|
}
|
|
550
|
+
const { BackgroundTasksManager } = await taskManagerLoader.run((runtime) => runtime);
|
|
351
551
|
dockOpen = true;
|
|
352
552
|
updateUi(ctx);
|
|
353
553
|
try {
|
|
@@ -373,6 +573,7 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
373
573
|
const rerunOptions: StartTaskOptions = {
|
|
374
574
|
name: taskDisplayName(task),
|
|
375
575
|
isAgent: task.isAgent,
|
|
576
|
+
surviveReload: task.surviveReload,
|
|
376
577
|
notifyOnCompletion: true,
|
|
377
578
|
triggerOnCompletion: false,
|
|
378
579
|
};
|
|
@@ -449,7 +650,7 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
449
650
|
);
|
|
450
651
|
|
|
451
652
|
async function scheduleUpdateCheck(ctx: ExtensionContext): Promise<void> {
|
|
452
|
-
if (updateCheckStarted) return;
|
|
653
|
+
if (disposed || updateCheckStarted) return;
|
|
453
654
|
updateCheckStarted = true;
|
|
454
655
|
const env = process.env;
|
|
455
656
|
if (env['PI_BG_DISABLE_UPDATE_CHECK'] === '1') return;
|
|
@@ -464,6 +665,7 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
464
665
|
const registryUrl = env['PI_BG_REGISTRY_URL'];
|
|
465
666
|
if (registryUrl) options.registryUrl = registryUrl;
|
|
466
667
|
const latest = await fetchLatestVersion(options);
|
|
668
|
+
if (disposed) return;
|
|
467
669
|
if (latest && isNewerVersion(latest, PACKAGE_VERSION)) {
|
|
468
670
|
latestKnownVersion = latest;
|
|
469
671
|
updateUi(ctx);
|
|
@@ -471,26 +673,40 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
471
673
|
}
|
|
472
674
|
|
|
473
675
|
pi.on('session_start', async (_event, ctx) => {
|
|
676
|
+
// Pi replacement binds a fresh extension instance. Never revive this old
|
|
677
|
+
// activation if a late lifecycle dispatch reaches it after shutdown.
|
|
678
|
+
if (disposed) return;
|
|
474
679
|
registry.setShuttingDown(false);
|
|
475
680
|
currentCtx = ctx;
|
|
476
|
-
|
|
681
|
+
const nextRegistryCtx = currentRegistryCtx ?? registryContext(ctx);
|
|
682
|
+
currentRegistryCtx = nextRegistryCtx;
|
|
683
|
+
await registry.ensureRuntimeDir(nextRegistryCtx);
|
|
684
|
+
if (disposed) return;
|
|
477
685
|
updateUi(ctx);
|
|
478
|
-
if (
|
|
479
|
-
statusInterval
|
|
686
|
+
if (disposed) return;
|
|
687
|
+
if (statusInterval !== undefined) clearInterval(statusInterval);
|
|
688
|
+
if (disposed) return;
|
|
689
|
+
const nextStatusInterval = setInterval(() => {
|
|
480
690
|
updateUi();
|
|
481
691
|
}, STATUS_INTERVAL_MS);
|
|
692
|
+
if (disposed) {
|
|
693
|
+
clearInterval(nextStatusInterval);
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
statusInterval = nextStatusInterval;
|
|
482
697
|
// One-shot, non-blocking: never awaited on the session-start path or the status tick.
|
|
483
|
-
void scheduleUpdateCheck(ctx);
|
|
698
|
+
if (!disposed) void scheduleUpdateCheck(ctx);
|
|
484
699
|
});
|
|
485
700
|
|
|
486
|
-
pi.on('session_shutdown', async (
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
clearInterval(statusInterval);
|
|
491
|
-
statusInterval = undefined;
|
|
492
|
-
}
|
|
701
|
+
pi.on('session_shutdown', async (event, ctx) => {
|
|
702
|
+
beginSessionShutdown(event.reason);
|
|
703
|
+
if (shutdownCleanupStarted) return;
|
|
704
|
+
shutdownCleanupStarted = true;
|
|
493
705
|
try {
|
|
706
|
+
// Admission closure aborts cooperative preflight and the drain retains
|
|
707
|
+
// ownership until subprocess/file/managed cleanup settles. Any admitted
|
|
708
|
+
// child is inserted synchronously before spawn and is visible below.
|
|
709
|
+
await registry.waitForTaskAdmissions();
|
|
494
710
|
const running = registry.allTasks().filter((task) => task.status === 'running');
|
|
495
711
|
if (running.length === 0) return;
|
|
496
712
|
|
|
@@ -498,7 +714,11 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
498
714
|
await Promise.all(
|
|
499
715
|
running.map(async (task) => {
|
|
500
716
|
try {
|
|
501
|
-
await registry.stopTask(
|
|
717
|
+
await registry.stopTask(
|
|
718
|
+
task,
|
|
719
|
+
'shutdown',
|
|
720
|
+
`Killed during Pi session shutdown (${event.reason})`,
|
|
721
|
+
);
|
|
502
722
|
} catch (error) {
|
|
503
723
|
const message = `${task.id}: ${error instanceof Error ? error.message : String(error)}`;
|
|
504
724
|
failures.push(message);
|
|
@@ -511,17 +731,31 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
511
731
|
}
|
|
512
732
|
} finally {
|
|
513
733
|
eventService.close();
|
|
734
|
+
if ((event.reason !== 'reload' || reloadHandoffFailed) && activationLease !== undefined) {
|
|
735
|
+
try {
|
|
736
|
+
await registry.waitForReloadHostSettlement();
|
|
737
|
+
} catch (error) {
|
|
738
|
+
console.error(
|
|
739
|
+
'[background-tasks] reload owner host settlement failed during shutdown:',
|
|
740
|
+
error,
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
registry.releaseReloadActivation(activationLease);
|
|
744
|
+
activationLease = undefined;
|
|
745
|
+
activationIdentity = undefined;
|
|
746
|
+
}
|
|
514
747
|
}
|
|
515
748
|
});
|
|
516
749
|
|
|
517
750
|
pi.registerCommand('bg', {
|
|
518
751
|
description:
|
|
519
|
-
'Start a shell command
|
|
752
|
+
'Start a tracked shell command: /bg [--survive-reload] [--agent] [--name "Task name"] <command>',
|
|
520
753
|
handler: async (args, ctx) => {
|
|
521
754
|
try {
|
|
522
755
|
const parsed = parseBgCommandArgs(args);
|
|
523
756
|
const taskOptions: StartTaskOptions = {
|
|
524
757
|
isAgent: parsed.isAgent,
|
|
758
|
+
surviveReload: parsed.surviveReload,
|
|
525
759
|
notifyOnCompletion: true,
|
|
526
760
|
triggerOnCompletion: false,
|
|
527
761
|
};
|
|
@@ -587,12 +821,23 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
587
821
|
},
|
|
588
822
|
});
|
|
589
823
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
824
|
+
if (config.dockShortcut === 'shift+down') {
|
|
825
|
+
pi.registerShortcut('shift+down' satisfies KeyId, {
|
|
826
|
+
description: 'Open focused background task footer dock',
|
|
827
|
+
handler: async (ctx) => {
|
|
828
|
+
await openTaskManager(ctx);
|
|
829
|
+
},
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (config.dockShortcut === 'ctrl+alt+b') {
|
|
834
|
+
pi.registerShortcut('ctrl+alt+b' satisfies KeyId, {
|
|
835
|
+
description: 'Open focused background task footer dock',
|
|
836
|
+
handler: async (ctx) => {
|
|
837
|
+
await openTaskManager(ctx);
|
|
838
|
+
},
|
|
839
|
+
});
|
|
840
|
+
}
|
|
596
841
|
|
|
597
842
|
pi.registerShortcut('ctrl+alt+c' satisfies KeyId, {
|
|
598
843
|
description:
|
|
@@ -705,6 +950,21 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
705
950
|
'bg_run requires isAgent boolean. Set true only for LLM/agent tasks; set false for scripts, tests, servers, sleeps, and ordinary shell commands.',
|
|
706
951
|
);
|
|
707
952
|
}
|
|
953
|
+
if (
|
|
954
|
+
Object.prototype.hasOwnProperty.call(input, 'surviveReload') &&
|
|
955
|
+
typeof input.surviveReload !== 'boolean'
|
|
956
|
+
) {
|
|
957
|
+
throw new ReloadSurvivalError(
|
|
958
|
+
'pi_bg_survive_reload_invalid',
|
|
959
|
+
'bg_run surviveReload must be boolean when present',
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
if (input.surviveReload === true && input.isAgent) {
|
|
963
|
+
throw new ReloadSurvivalError(
|
|
964
|
+
'pi_bg_survive_reload_requires_non_agent',
|
|
965
|
+
'bg_run surviveReload requires isAgent:false',
|
|
966
|
+
);
|
|
967
|
+
}
|
|
708
968
|
const prepared: BgRunParamsValue = {
|
|
709
969
|
command: input.command,
|
|
710
970
|
name:
|
|
@@ -719,6 +979,7 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
719
979
|
prepared.notifyOnCompletion = input.notifyOnCompletion;
|
|
720
980
|
if (typeof input.triggerOnCompletion === 'boolean')
|
|
721
981
|
prepared.triggerOnCompletion = input.triggerOnCompletion;
|
|
982
|
+
if (typeof input.surviveReload === 'boolean') prepared.surviveReload = input.surviveReload;
|
|
722
983
|
return prepared;
|
|
723
984
|
},
|
|
724
985
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -727,9 +988,16 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
727
988
|
'bg_run requires isAgent boolean. Set true only for LLM/agent tasks; set false for scripts, tests, servers, sleeps, and ordinary shell commands.',
|
|
728
989
|
);
|
|
729
990
|
}
|
|
991
|
+
if (params.surviveReload === true && params.isAgent) {
|
|
992
|
+
throw new ReloadSurvivalError(
|
|
993
|
+
'pi_bg_survive_reload_requires_non_agent',
|
|
994
|
+
'bg_run surviveReload requires isAgent:false',
|
|
995
|
+
);
|
|
996
|
+
}
|
|
730
997
|
const taskOptions: StartTaskOptions = {
|
|
731
998
|
name: params.name,
|
|
732
999
|
isAgent: params.isAgent,
|
|
1000
|
+
surviveReload: params.surviveReload ?? false,
|
|
733
1001
|
notifyOnCompletion: params.notifyOnCompletion ?? true,
|
|
734
1002
|
triggerOnCompletion: params.triggerOnCompletion ?? true,
|
|
735
1003
|
};
|
|
@@ -764,70 +1032,75 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
764
1032
|
},
|
|
765
1033
|
});
|
|
766
1034
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
throw new Error('bg_run_pi_attested requires
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
)
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1035
|
+
if (config.features.attested) {
|
|
1036
|
+
pi.registerTool<typeof BgPiAttestedParams, BgRunDetails>({
|
|
1037
|
+
name: 'bg_run_pi_attested',
|
|
1038
|
+
label: 'Attested Pi Run',
|
|
1039
|
+
description:
|
|
1040
|
+
'Opt-in evidence-oriented direct Pi spawn. Launches exactly one `pi --mode json` child, records raw Pi events/stderr, hashes prompt/report/output, observes OAuth through ModelRegistry, and emits a strict attestation sidecar only after successful completion.',
|
|
1041
|
+
promptSnippet:
|
|
1042
|
+
'Start an attested direct Pi agent task and return its task ID plus output path',
|
|
1043
|
+
promptGuidelines: [
|
|
1044
|
+
'Use only when the user explicitly asks for an attested Pi evidence-producing task; ordinary background work should use bg_run unchanged.',
|
|
1045
|
+
'Provide provider/model as structured fields and a relative reportPath that the child Pi prompt will write before exit.',
|
|
1046
|
+
'Do not provide channel, auth, route, or hash claims; the producer observes those facts itself and fails loudly if it cannot attest them.',
|
|
1047
|
+
],
|
|
1048
|
+
parameters: BgPiAttestedParams,
|
|
1049
|
+
prepareArguments(args): BgPiAttestedParamsValue {
|
|
1050
|
+
if (!args || typeof args !== 'object')
|
|
1051
|
+
throw new Error('bg_run_pi_attested arguments must be an object');
|
|
1052
|
+
const input = args as BgPiAttestedArgumentRecord;
|
|
1053
|
+
rejectSurvivalForTaskKind(input, 'attested Pi tasks');
|
|
1054
|
+
if (typeof input.name !== 'string') throw new Error('bg_run_pi_attested requires name');
|
|
1055
|
+
if (typeof input.provider !== 'string')
|
|
1056
|
+
throw new Error('bg_run_pi_attested requires provider');
|
|
1057
|
+
if (typeof input.model !== 'string') throw new Error('bg_run_pi_attested requires model');
|
|
1058
|
+
if (typeof input.prompt !== 'string') throw new Error('bg_run_pi_attested requires prompt');
|
|
1059
|
+
if (typeof input.reportPath !== 'string')
|
|
1060
|
+
throw new Error('bg_run_pi_attested requires reportPath');
|
|
1061
|
+
const prepared: BgPiAttestedParamsValue = {
|
|
1062
|
+
name: input.name,
|
|
1063
|
+
provider: input.provider,
|
|
1064
|
+
model: input.model,
|
|
1065
|
+
prompt: input.prompt,
|
|
1066
|
+
reportPath: input.reportPath,
|
|
1067
|
+
};
|
|
1068
|
+
if (Array.isArray(input.extraPiArgs)) {
|
|
1069
|
+
if (!input.extraPiArgs.every((entry) => typeof entry === 'string'))
|
|
1070
|
+
throw new Error('bg_run_pi_attested extraPiArgs entries must be strings');
|
|
1071
|
+
prepared.extraPiArgs = input.extraPiArgs;
|
|
1072
|
+
}
|
|
1073
|
+
if (typeof input.thinking === 'string') prepared.thinking = input.thinking;
|
|
1074
|
+
if (typeof input.timeoutSeconds === 'number')
|
|
1075
|
+
prepared.timeoutSeconds = input.timeoutSeconds;
|
|
1076
|
+
return prepared;
|
|
1077
|
+
},
|
|
1078
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
1079
|
+
const task = await startAttestedPiTask(ctx, params);
|
|
1080
|
+
return {
|
|
1081
|
+
content: textContent(
|
|
1082
|
+
`Started attested Pi task ${taskDisplayName(task)} (${task.id})\nStatus: ${task.status}\nPID: ${String(task.pid ?? 'unknown')}\nOutput: ${task.outputPath}\nAttestation: ${task.attestationPath ?? 'pending until completion'}`,
|
|
1083
|
+
),
|
|
1084
|
+
details: { task: registry.snapshot(task) },
|
|
1085
|
+
};
|
|
1086
|
+
},
|
|
1087
|
+
renderCall(args, theme) {
|
|
1088
|
+
return new Text(
|
|
1089
|
+
`${theme.fg('toolTitle', theme.bold('bg_run_pi_attested '))}${theme.fg('muted', truncateChars(args.name, COMMAND_PREVIEW_CHARS))}`,
|
|
1090
|
+
0,
|
|
1091
|
+
0,
|
|
1092
|
+
);
|
|
1093
|
+
},
|
|
1094
|
+
renderResult(result, _options, theme) {
|
|
1095
|
+
const { task } = result.details;
|
|
1096
|
+
return new Text(
|
|
1097
|
+
`${theme.fg('success', '✓ started')} ${theme.fg('accent', taskDisplayName(task))} ${theme.fg('dim', `(${task.id})`)}\n${theme.fg('dim', `Output: ${task.outputPath}`)}\n${theme.fg('dim', `Attestation: ${task.attestationPath ?? 'pending'}`)}`,
|
|
1098
|
+
0,
|
|
1099
|
+
0,
|
|
1100
|
+
);
|
|
1101
|
+
},
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
831
1104
|
|
|
832
1105
|
pi.registerTool<typeof BgStatusParams, BgStatusDetails>({
|
|
833
1106
|
name: 'bg_status',
|