@narumitw/pi-subagents 1.0.1 → 1.0.2
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/package.json +7 -7
- package/src/config-registration.ts +29 -4
- package/src/consult-render.ts +1 -1
- package/src/panel-planning.ts +2 -2
- package/src/panel-presets.ts +3 -0
- package/src/params.ts +1 -1
- package/src/render.ts +2 -41
- package/src/settings.ts +4 -1
- package/src/stateful.ts +252 -101
- package/src/subagents.ts +1 -1
- package/src/usage-format.ts +42 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narumitw/pi-subagents",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Pi extension for delegating work to specialized isolated subagents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,12 +39,12 @@
|
|
|
39
39
|
"typebox": "*"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@biomejs/biome": "2.5.
|
|
43
|
-
"@earendil-works/pi-agent-core": "0.84.
|
|
44
|
-
"@earendil-works/pi-ai": "0.84.
|
|
45
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
46
|
-
"@earendil-works/pi-tui": "0.84.
|
|
47
|
-
"typebox": "1.3.
|
|
42
|
+
"@biomejs/biome": "2.5.8",
|
|
43
|
+
"@earendil-works/pi-agent-core": "0.84.2",
|
|
44
|
+
"@earendil-works/pi-ai": "0.84.2",
|
|
45
|
+
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
46
|
+
"@earendil-works/pi-tui": "0.84.2",
|
|
47
|
+
"typebox": "1.3.14",
|
|
48
48
|
"typescript": "7.0.2"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { cachedModuleLoader } from "./cached-module-loader.js";
|
|
3
|
-
import { showSubagentHelp, showSubagentStatus } from "./config-status.js";
|
|
4
3
|
import type { SubagentMenuOwner, SubagentSettingsRuntime } from "./config-ui.js";
|
|
5
4
|
|
|
6
5
|
const SUBCOMMANDS = [
|
|
@@ -14,8 +13,14 @@ type ConfigUiModule = Pick<
|
|
|
14
13
|
"showSubagentManager" | "showSubagentSettings"
|
|
15
14
|
>;
|
|
16
15
|
|
|
16
|
+
type ConfigStatusModule = Pick<
|
|
17
|
+
typeof import("./config-status.js"),
|
|
18
|
+
"showSubagentHelp" | "showSubagentStatus"
|
|
19
|
+
>;
|
|
20
|
+
|
|
17
21
|
export interface ConfigRegistrationDependencies {
|
|
18
22
|
loadConfigUi?: () => Promise<ConfigUiModule>;
|
|
23
|
+
loadConfigStatus?: () => Promise<ConfigStatusModule>;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
export function registerSubagentConfigLifecycle(pi: ExtensionAPI): SubagentMenuOwner {
|
|
@@ -41,6 +46,9 @@ export function registerSubagentConfigCommand(
|
|
|
41
46
|
const loadConfigUi = cachedModuleLoader(
|
|
42
47
|
dependencies.loadConfigUi ?? (() => import("./config-ui.js")),
|
|
43
48
|
);
|
|
49
|
+
const loadConfigStatus = cachedModuleLoader<ConfigStatusModule>(
|
|
50
|
+
dependencies.loadConfigStatus ?? (() => import("./config-status.js")),
|
|
51
|
+
);
|
|
44
52
|
pi.registerCommand("subagents", {
|
|
45
53
|
description: "Manage current-session subagents and user settings",
|
|
46
54
|
getArgumentCompletions(prefix: string) {
|
|
@@ -50,16 +58,33 @@ export function registerSubagentConfigCommand(
|
|
|
50
58
|
},
|
|
51
59
|
async handler(args, ctx) {
|
|
52
60
|
const subcommand = args.trim().toLowerCase();
|
|
61
|
+
const runStatusCommand = async (show: (status: ConfigStatusModule) => void) => {
|
|
62
|
+
const generation = owner.generation;
|
|
63
|
+
const controller = owner.controller;
|
|
64
|
+
const isCurrent = () =>
|
|
65
|
+
generation === owner.generation &&
|
|
66
|
+
controller === owner.controller &&
|
|
67
|
+
!controller.signal.aborted;
|
|
68
|
+
let status: ConfigStatusModule;
|
|
69
|
+
try {
|
|
70
|
+
status = await loadConfigStatus();
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (!isCurrent()) return;
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
if (!isCurrent()) return;
|
|
76
|
+
show(status);
|
|
77
|
+
};
|
|
53
78
|
if (!subcommand && ctx.mode !== "tui") {
|
|
54
|
-
showSubagentStatus(ctx, runtime);
|
|
79
|
+
await runStatusCommand((status) => status.showSubagentStatus(ctx, runtime));
|
|
55
80
|
return;
|
|
56
81
|
}
|
|
57
82
|
if (subcommand === "status") {
|
|
58
|
-
showSubagentStatus(ctx, runtime);
|
|
83
|
+
await runStatusCommand((status) => status.showSubagentStatus(ctx, runtime));
|
|
59
84
|
return;
|
|
60
85
|
}
|
|
61
86
|
if (subcommand === "help") {
|
|
62
|
-
showSubagentHelp(ctx, runtime);
|
|
87
|
+
await runStatusCommand((status) => status.showSubagentHelp(ctx, runtime));
|
|
63
88
|
return;
|
|
64
89
|
}
|
|
65
90
|
if (!subcommand || subcommand === "settings") {
|
package/src/consult-render.ts
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
} from "@earendil-works/pi-coding-agent";
|
|
7
7
|
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
8
8
|
import type { ConsultDetails, SubagentConsultParams } from "./consult.js";
|
|
9
|
-
import { formatUsageStats } from "./render.js";
|
|
10
9
|
import {
|
|
11
10
|
COLLAPSED_ANSWER_LINES,
|
|
12
11
|
COLLAPSED_LIST_LIMIT,
|
|
@@ -25,6 +24,7 @@ import {
|
|
|
25
24
|
textResult,
|
|
26
25
|
toolHeader,
|
|
27
26
|
} from "./render-common.js";
|
|
27
|
+
import { formatUsageStats } from "./usage-format.js";
|
|
28
28
|
|
|
29
29
|
export function renderConsultCall(args: Partial<SubagentConsultParams>, theme: Theme) {
|
|
30
30
|
const scope = args.agentScope ?? "user";
|
package/src/panel-planning.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { AgentConfig } from "./agents/types.js";
|
|
2
2
|
import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
|
|
3
|
+
import type { PanelPreset } from "./panel-presets.js";
|
|
3
4
|
import { type WorkItemDefinition, WorkItemLedger } from "./work-item-ledger.js";
|
|
4
5
|
|
|
5
|
-
export
|
|
6
|
-
export type PanelPreset = (typeof PANEL_PRESETS)[number];
|
|
6
|
+
export type { PanelPreset } from "./panel-presets.js";
|
|
7
7
|
|
|
8
8
|
export interface PanelReviewerRequest {
|
|
9
9
|
id: string;
|
package/src/params.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type Static, Type } from "typebox";
|
|
|
3
3
|
import { THINKING_LEVELS } from "./agents/types.js";
|
|
4
4
|
import { DelegationContractSchema } from "./delegation-contract.js";
|
|
5
5
|
import { MAX_CONFIGURABLE_PARALLEL_TASKS, MAX_SUBAGENT_TIMEOUT_MS } from "./limits.js";
|
|
6
|
-
import { PANEL_PRESETS } from "./panel-
|
|
6
|
+
import { PANEL_PRESETS } from "./panel-presets.js";
|
|
7
7
|
import { SUBAGENT_RESULT_FORMATS } from "./result-contract.js";
|
|
8
8
|
import { MAX_SUBAGENT_TOOL_CALLS, MAX_SUBAGENT_TURNS } from "./turn-budget.js";
|
|
9
9
|
import { VerifiedExecutionContractSchema } from "./verified-execution-schema.js";
|
package/src/render.ts
CHANGED
|
@@ -7,12 +7,13 @@ import {
|
|
|
7
7
|
type ToolRenderResultOptions,
|
|
8
8
|
} from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
10
|
-
import type { AgentScope
|
|
10
|
+
import type { AgentScope } from "./agents/types.js";
|
|
11
11
|
import { renderPanelCall, renderPanelResult } from "./panel-render.js";
|
|
12
12
|
import { hasUsableAggregator, type SubagentParams } from "./params.js";
|
|
13
13
|
import { expansionHint, formatToolActivity, safeBlock, safeLine } from "./render-common.js";
|
|
14
14
|
import type { SingleResult, SubagentDetails } from "./runner.js";
|
|
15
15
|
import { getResultFinalOutput, isResultError } from "./runner-outcome.js";
|
|
16
|
+
import { formatUsageStats } from "./usage-format.js";
|
|
16
17
|
|
|
17
18
|
const COLLAPSED_ITEM_COUNT = 5;
|
|
18
19
|
|
|
@@ -25,46 +26,6 @@ function previewAgent(agent: unknown): string {
|
|
|
25
26
|
return safeLine(agent, "...", 256);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export function formatTokens(count: number): string {
|
|
29
|
-
if (count < 1000) return count.toString();
|
|
30
|
-
if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
|
|
31
|
-
if (count < 1000000) return `${Math.round(count / 1000)}k`;
|
|
32
|
-
return `${(count / 1000000).toFixed(1)}M`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function formatUsageStats(
|
|
36
|
-
usage: {
|
|
37
|
-
input: number;
|
|
38
|
-
output: number;
|
|
39
|
-
cacheRead: number;
|
|
40
|
-
cacheWrite: number;
|
|
41
|
-
cost: number;
|
|
42
|
-
contextTokens?: number;
|
|
43
|
-
turns?: number;
|
|
44
|
-
},
|
|
45
|
-
model?: string,
|
|
46
|
-
thinkingLevel?: SubagentThinkingLevel,
|
|
47
|
-
actualProvider?: string,
|
|
48
|
-
actualModel?: string,
|
|
49
|
-
): string {
|
|
50
|
-
const parts: string[] = [];
|
|
51
|
-
if (usage.turns) parts.push(`${usage.turns} turn${usage.turns > 1 ? "s" : ""}`);
|
|
52
|
-
if (usage.input) parts.push(`↑${formatTokens(usage.input)}`);
|
|
53
|
-
if (usage.output) parts.push(`↓${formatTokens(usage.output)}`);
|
|
54
|
-
if (usage.cacheRead) parts.push(`R${formatTokens(usage.cacheRead)}`);
|
|
55
|
-
if (usage.cacheWrite) parts.push(`W${formatTokens(usage.cacheWrite)}`);
|
|
56
|
-
if (usage.cost) parts.push(`$${usage.cost.toFixed(4)}`);
|
|
57
|
-
if (usage.contextTokens && usage.contextTokens > 0)
|
|
58
|
-
parts.push(`ctx:${formatTokens(usage.contextTokens)}`);
|
|
59
|
-
const safeProvider = actualProvider ? safeLine(actualProvider, "", 256) : undefined;
|
|
60
|
-
const safeModel = actualModel ? safeLine(actualModel, "", 256) : undefined;
|
|
61
|
-
const actual =
|
|
62
|
-
safeProvider && safeModel ? `${safeProvider}/${safeModel}` : (safeModel ?? safeProvider);
|
|
63
|
-
if (actual ?? model) parts.push(actual ?? safeLine(model, "", 256));
|
|
64
|
-
if (thinkingLevel) parts.push(`requested-thinking:${safeLine(thinkingLevel, "", 128)}`);
|
|
65
|
-
return parts.join(" ");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
29
|
function formatResultUsageStats(result: SingleResult): string {
|
|
69
30
|
return formatUsageStats(
|
|
70
31
|
result.usage,
|
package/src/settings.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import * as path from "node:path";
|
|
4
5
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import lockfile from "proper-lockfile";
|
|
6
6
|
import type {
|
|
7
7
|
AgentConfig,
|
|
8
8
|
CompletionDelivery,
|
|
@@ -76,6 +76,8 @@ export {
|
|
|
76
76
|
|
|
77
77
|
const SETTINGS_FILE = "pi-subagents.json";
|
|
78
78
|
const LEGACY_SETTINGS_FILE = "pi-subagents-config.json";
|
|
79
|
+
const require = createRequire(import.meta.url);
|
|
80
|
+
|
|
79
81
|
const SETTINGS_LOCK_FS_ADAPTER = {
|
|
80
82
|
mkdir: fs.mkdir,
|
|
81
83
|
mkdirSync: fs.mkdirSync,
|
|
@@ -511,6 +513,7 @@ function writeSettingsObjectUnlocked(settings: object, replaceCanonical?: boolea
|
|
|
511
513
|
}
|
|
512
514
|
|
|
513
515
|
function withSettingsMutationLock<T>(mutate: () => T): T {
|
|
516
|
+
const lockfile = require("proper-lockfile") as typeof import("proper-lockfile");
|
|
514
517
|
const agentDir = getAgentDir();
|
|
515
518
|
fs.mkdirSync(agentDir, { recursive: true });
|
|
516
519
|
const configPath = path.join(agentDir, SETTINGS_FILE);
|
package/src/stateful.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { randomUUID } from "node:crypto";
|
|
|
7
7
|
import { StringEnum } from "@earendil-works/pi-ai";
|
|
8
8
|
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { Type } from "typebox";
|
|
10
|
-
import { discoverAgents } from "./agents/discovery.js";
|
|
11
10
|
import {
|
|
12
11
|
type AgentScope,
|
|
13
12
|
type CompletionDelivery,
|
|
@@ -17,20 +16,10 @@ import {
|
|
|
17
16
|
type SubagentTransportKind,
|
|
18
17
|
THINKING_LEVELS,
|
|
19
18
|
} from "./agents/types.js";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import
|
|
23
|
-
import {
|
|
24
|
-
type CreateStatefulTransportOptions,
|
|
25
|
-
createStatefulTransport,
|
|
26
|
-
} from "./create-stateful-transport.js";
|
|
27
|
-
import {
|
|
28
|
-
assertDelegationTargetAllowed,
|
|
29
|
-
resolveSubagentTarget,
|
|
30
|
-
targetPolicyAudit,
|
|
31
|
-
} from "./cwd-policy.js";
|
|
32
|
-
import { DelegationContractSchema, normalizeDelegationContract } from "./delegation-contract.js";
|
|
33
|
-
import { assertSubagentDepthAllowed } from "./execution/runtime-policy.js";
|
|
19
|
+
import type { CompletionDeliveryBroker } from "./completion-delivery.js";
|
|
20
|
+
import type { ContextMode } from "./context.js";
|
|
21
|
+
import type { CreateStatefulTransportOptions } from "./create-stateful-transport.js";
|
|
22
|
+
import { DelegationContractSchema } from "./delegation-contract.js";
|
|
34
23
|
import type { ChildSessionFactory, ParentRuntimeSnapshot } from "./in-process-transport.js";
|
|
35
24
|
import {
|
|
36
25
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
@@ -38,32 +27,24 @@ import {
|
|
|
38
27
|
MAX_TOOL_MESSAGE_BYTES,
|
|
39
28
|
truncateUtf8,
|
|
40
29
|
} from "./limits.js";
|
|
41
|
-
import { AgentPersistence } from "./persistence.js";
|
|
42
|
-
import {
|
|
30
|
+
import type { AgentPersistence } from "./persistence.js";
|
|
31
|
+
import type {
|
|
43
32
|
AgentRegistry,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
AgentRunInspectionDetail,
|
|
34
|
+
AgentRunInspectionSummary,
|
|
35
|
+
ManagedAgent,
|
|
47
36
|
} from "./registry.js";
|
|
48
37
|
import { SUBAGENT_RESULT_FORMATS, type SubagentResultFormat } from "./result-contract.js";
|
|
49
|
-
import { buildRetainedSemanticState } from "./retained-semantic-state.js";
|
|
50
|
-
import { evaluateSemanticCompatibility } from "./semantic-snapshot.js";
|
|
51
38
|
import { DEFAULT_DELEGATION_CWD_POLICY } from "./settings/inspection.js";
|
|
52
39
|
import { readSubagentSettings } from "./settings.js";
|
|
53
40
|
import {
|
|
54
41
|
assertSpawnIdempotencyKey,
|
|
55
|
-
hashSpawnRequest,
|
|
56
42
|
MAX_SPAWN_IDEMPOTENCY_KEY_LENGTH,
|
|
57
43
|
} from "./spawn-idempotency.js";
|
|
58
44
|
import { summarizeStatefulAgent } from "./stateful-agent-view.js";
|
|
59
45
|
import { resolveCompletionDelivery, resolveStatefulTransportKind } from "./stateful-config.js";
|
|
60
46
|
import { createSpawnPromptGuidelines } from "./stateful-guidance.js";
|
|
61
|
-
import {
|
|
62
|
-
assertCurrentSpawn,
|
|
63
|
-
cleanupPersistedWorkspaces,
|
|
64
|
-
disposeStatefulRuntime,
|
|
65
|
-
waitForOwnedSpawn,
|
|
66
|
-
} from "./stateful-lifecycle.js";
|
|
47
|
+
import { assertCurrentSpawn, waitForOwnedSpawn } from "./stateful-lifecycle.js";
|
|
67
48
|
import { resolveStatefulLimits, type StatefulLimits } from "./stateful-limits.js";
|
|
68
49
|
import { createStatefulToolRenderer } from "./stateful-render.js";
|
|
69
50
|
import {
|
|
@@ -85,7 +66,112 @@ import {
|
|
|
85
66
|
validateMailboxParams,
|
|
86
67
|
validateManageParams,
|
|
87
68
|
} from "./stateful-tool-params.js";
|
|
88
|
-
import { WorkspaceManager } from "./workspace.js";
|
|
69
|
+
import type { WorkspaceManager } from "./workspace.js";
|
|
70
|
+
|
|
71
|
+
type CwdPolicyModule = typeof import("./cwd-policy.js");
|
|
72
|
+
type StateLifecycleModule = typeof import("./stateful-lifecycle.js");
|
|
73
|
+
|
|
74
|
+
type StatefulSessionModules = {
|
|
75
|
+
broker: typeof import("./completion-delivery.js");
|
|
76
|
+
context: typeof import("./context.js");
|
|
77
|
+
transport: typeof import("./create-stateful-transport.js");
|
|
78
|
+
cwdPolicy: CwdPolicyModule;
|
|
79
|
+
persistence: typeof import("./persistence.js");
|
|
80
|
+
registry: typeof import("./registry.js");
|
|
81
|
+
lifecycle: StateLifecycleModule;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
type StatefulSpawnModules = {
|
|
85
|
+
agents: typeof import("./agents/discovery.js");
|
|
86
|
+
capabilityGrant: typeof import("./capability-grant.js");
|
|
87
|
+
context: typeof import("./context.js");
|
|
88
|
+
cwdPolicy: CwdPolicyModule;
|
|
89
|
+
delegationContract: typeof import("./delegation-contract.js");
|
|
90
|
+
runtimePolicy: typeof import("./execution/runtime-policy.js");
|
|
91
|
+
retainedSemanticState: typeof import("./retained-semantic-state.js");
|
|
92
|
+
semanticSnapshot: typeof import("./semantic-snapshot.js");
|
|
93
|
+
spawnIdempotency: typeof import("./spawn-idempotency.js");
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
let statefulSessionModules: Promise<StatefulSessionModules> | undefined;
|
|
97
|
+
let statefulSpawnModules: Promise<StatefulSpawnModules> | undefined;
|
|
98
|
+
let workspaceModule: Promise<typeof import("./workspace.js")> | undefined;
|
|
99
|
+
|
|
100
|
+
function loadStatefulSessionModules(): Promise<StatefulSessionModules> {
|
|
101
|
+
statefulSessionModules ??= Promise.all([
|
|
102
|
+
import("./completion-delivery.js"),
|
|
103
|
+
import("./context.js"),
|
|
104
|
+
import("./create-stateful-transport.js"),
|
|
105
|
+
import("./cwd-policy.js"),
|
|
106
|
+
import("./persistence.js"),
|
|
107
|
+
import("./registry.js"),
|
|
108
|
+
import("./stateful-lifecycle.js"),
|
|
109
|
+
])
|
|
110
|
+
.then(([broker, context, transport, cwdPolicy, persistence, registry, lifecycle]) => ({
|
|
111
|
+
broker,
|
|
112
|
+
context,
|
|
113
|
+
transport,
|
|
114
|
+
cwdPolicy,
|
|
115
|
+
persistence,
|
|
116
|
+
registry,
|
|
117
|
+
lifecycle,
|
|
118
|
+
}))
|
|
119
|
+
.catch((error: unknown) => {
|
|
120
|
+
statefulSessionModules = undefined;
|
|
121
|
+
throw error;
|
|
122
|
+
});
|
|
123
|
+
return statefulSessionModules;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function loadStatefulSpawnModules(): Promise<StatefulSpawnModules> {
|
|
127
|
+
statefulSpawnModules ??= Promise.all([
|
|
128
|
+
import("./agents/discovery.js"),
|
|
129
|
+
import("./capability-grant.js"),
|
|
130
|
+
import("./context.js"),
|
|
131
|
+
import("./cwd-policy.js"),
|
|
132
|
+
import("./delegation-contract.js"),
|
|
133
|
+
import("./execution/runtime-policy.js"),
|
|
134
|
+
import("./retained-semantic-state.js"),
|
|
135
|
+
import("./semantic-snapshot.js"),
|
|
136
|
+
import("./spawn-idempotency.js"),
|
|
137
|
+
])
|
|
138
|
+
.then(
|
|
139
|
+
([
|
|
140
|
+
agents,
|
|
141
|
+
capabilityGrant,
|
|
142
|
+
context,
|
|
143
|
+
cwdPolicy,
|
|
144
|
+
delegationContract,
|
|
145
|
+
runtimePolicy,
|
|
146
|
+
retainedSemanticState,
|
|
147
|
+
semanticSnapshot,
|
|
148
|
+
spawnIdempotency,
|
|
149
|
+
]) => ({
|
|
150
|
+
agents,
|
|
151
|
+
capabilityGrant,
|
|
152
|
+
context,
|
|
153
|
+
cwdPolicy,
|
|
154
|
+
delegationContract,
|
|
155
|
+
runtimePolicy,
|
|
156
|
+
retainedSemanticState,
|
|
157
|
+
semanticSnapshot,
|
|
158
|
+
spawnIdempotency,
|
|
159
|
+
}),
|
|
160
|
+
)
|
|
161
|
+
.catch((error: unknown) => {
|
|
162
|
+
statefulSpawnModules = undefined;
|
|
163
|
+
throw error;
|
|
164
|
+
});
|
|
165
|
+
return statefulSpawnModules;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function loadWorkspaceModule(): Promise<typeof import("./workspace.js")> {
|
|
169
|
+
workspaceModule ??= import("./workspace.js").catch((error: unknown) => {
|
|
170
|
+
workspaceModule = undefined;
|
|
171
|
+
throw error;
|
|
172
|
+
});
|
|
173
|
+
return workspaceModule;
|
|
174
|
+
}
|
|
89
175
|
|
|
90
176
|
const ContextModeSchema = Type.Union([
|
|
91
177
|
StringEnum(["none", "all", "summary"] as const),
|
|
@@ -186,7 +272,13 @@ export function registerStatefulSubagents(
|
|
|
186
272
|
let sweepTimer: NodeJS.Timeout | undefined;
|
|
187
273
|
let runtimeGeneration = 0;
|
|
188
274
|
let runtimeTransition: Promise<void> = Promise.resolve();
|
|
189
|
-
|
|
275
|
+
let workspaceManager = dependencies.workspaceManager;
|
|
276
|
+
const getWorkspaceManager = async () => {
|
|
277
|
+
if (workspaceManager) return workspaceManager;
|
|
278
|
+
const { WorkspaceManager } = await loadWorkspaceModule();
|
|
279
|
+
workspaceManager = new WorkspaceManager();
|
|
280
|
+
return workspaceManager;
|
|
281
|
+
};
|
|
190
282
|
const isolatedAgents = new Map<string, string>();
|
|
191
283
|
const seenMessageIds = new Set<string>();
|
|
192
284
|
type PendingIdempotentSpawn = {
|
|
@@ -206,11 +298,12 @@ export function registerStatefulSubagents(
|
|
|
206
298
|
const currentRegistry = registry;
|
|
207
299
|
const currentPersistence = persistence;
|
|
208
300
|
if (!currentRegistry) return 0;
|
|
301
|
+
const currentWorkspaceManager = await getWorkspaceManager();
|
|
209
302
|
const count = currentRegistry.list().length;
|
|
210
303
|
const clear = async () => {
|
|
211
304
|
await currentRegistry.closeAll();
|
|
212
305
|
if (generation !== runtimeGeneration) return;
|
|
213
|
-
await
|
|
306
|
+
await currentWorkspaceManager.cleanupAll();
|
|
214
307
|
isolatedAgents.clear();
|
|
215
308
|
seenMessageIds.clear();
|
|
216
309
|
await currentPersistence?.delete();
|
|
@@ -277,7 +370,12 @@ export function registerStatefulSubagents(
|
|
|
277
370
|
seenMessageIds.clear();
|
|
278
371
|
pendingIdempotentSpawns.clear();
|
|
279
372
|
const initialize = async () => {
|
|
280
|
-
const
|
|
373
|
+
const currentWorkspaceManager = await getWorkspaceManager();
|
|
374
|
+
const modules = await loadStatefulSessionModules();
|
|
375
|
+
const cleanupErrors = await modules.lifecycle.disposeStatefulRuntime(
|
|
376
|
+
previousRegistry,
|
|
377
|
+
currentWorkspaceManager,
|
|
378
|
+
);
|
|
281
379
|
if (generation !== runtimeGeneration) return;
|
|
282
380
|
if (cleanupErrors.length > 0 && ctx.hasUI) {
|
|
283
381
|
ctx.ui.notify(
|
|
@@ -293,31 +391,36 @@ export function registerStatefulSubagents(
|
|
|
293
391
|
ctx.sessionManager.getSessionId?.() ??
|
|
294
392
|
ctx.sessionManager.getSessionFile?.() ??
|
|
295
393
|
`ephemeral:${ctx.cwd}`;
|
|
296
|
-
const sessionPersistence = new AgentPersistence(owner, {
|
|
394
|
+
const sessionPersistence = new modules.persistence.AgentPersistence(owner, {
|
|
297
395
|
retentionDays: sessionSettings.retentionDays,
|
|
298
396
|
maxStoredAgents: nextLimits.maxStoredAgents,
|
|
299
397
|
});
|
|
300
398
|
let nextRegistry: AgentRegistry;
|
|
301
|
-
const sessionBroker = new CompletionDeliveryBroker(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
399
|
+
const sessionBroker = new modules.broker.CompletionDeliveryBroker(
|
|
400
|
+
pi,
|
|
401
|
+
ctx,
|
|
402
|
+
completionDelivery,
|
|
403
|
+
{
|
|
404
|
+
onDeliveryError: (error) => {
|
|
405
|
+
if (!ctx.hasUI) return;
|
|
406
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
407
|
+
ctx.ui.notify(`Subagent completion delivery failed: ${reason}`, "warning");
|
|
408
|
+
},
|
|
409
|
+
onAcknowledged: (completions, deliveredAt) => {
|
|
410
|
+
if (generation !== runtimeGeneration) return;
|
|
411
|
+
for (const completion of completions) {
|
|
412
|
+
void nextRegistry
|
|
413
|
+
.markCompletionDelivered(completion.completionId, deliveredAt)
|
|
414
|
+
.catch((error: unknown) => {
|
|
415
|
+
if (!ctx.hasUI || generation !== runtimeGeneration) return;
|
|
416
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
417
|
+
ctx.ui.notify(`Subagent completion acknowledgement failed: ${reason}`, "warning");
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
},
|
|
318
421
|
},
|
|
319
|
-
|
|
320
|
-
const transport = createStatefulTransport({
|
|
422
|
+
);
|
|
423
|
+
const transport = modules.transport.createStatefulTransport({
|
|
321
424
|
kind: transportKind,
|
|
322
425
|
modelRegistry: ctx.modelRegistry,
|
|
323
426
|
getParentRuntime: () => ({ ...parentRuntime }),
|
|
@@ -325,7 +428,7 @@ export function registerStatefulSubagents(
|
|
|
325
428
|
createInProcessSession: dependencies.createInProcessSession,
|
|
326
429
|
loadTransport: dependencies.loadTransport,
|
|
327
430
|
});
|
|
328
|
-
nextRegistry = new AgentRegistry(transport, {
|
|
431
|
+
nextRegistry = new modules.registry.AgentRegistry(transport, {
|
|
329
432
|
maxAgents: nextLimits.maxAgents,
|
|
330
433
|
maxActiveTurns: nextLimits.maxActiveTurns,
|
|
331
434
|
maxDepth: nextLimits.maxDepth,
|
|
@@ -344,7 +447,7 @@ export function registerStatefulSubagents(
|
|
|
344
447
|
pi.appendEntry("pi-subagent-message", {
|
|
345
448
|
senderId: message.senderId,
|
|
346
449
|
recipientId: message.recipientId,
|
|
347
|
-
content: redactPrivateText(message.content).slice(0, 160),
|
|
450
|
+
content: modules.context.redactPrivateText(message.content).slice(0, 160),
|
|
348
451
|
});
|
|
349
452
|
}
|
|
350
453
|
}
|
|
@@ -354,7 +457,10 @@ export function registerStatefulSubagents(
|
|
|
354
457
|
},
|
|
355
458
|
});
|
|
356
459
|
const persisted = sessionPersistence.load();
|
|
357
|
-
const orphanCleanupFailures = await cleanupPersistedWorkspaces(
|
|
460
|
+
const orphanCleanupFailures = await modules.lifecycle.cleanupPersistedWorkspaces(
|
|
461
|
+
persisted,
|
|
462
|
+
currentWorkspaceManager,
|
|
463
|
+
);
|
|
358
464
|
if (ctx.hasUI && orphanCleanupFailures > 0) {
|
|
359
465
|
ctx.ui.notify("Some orphaned subagent worktrees could not be cleaned", "warning");
|
|
360
466
|
}
|
|
@@ -367,12 +473,14 @@ export function registerStatefulSubagents(
|
|
|
367
473
|
)
|
|
368
474
|
.flatMap((agent) => {
|
|
369
475
|
try {
|
|
370
|
-
const target = resolveSubagentTarget({
|
|
476
|
+
const target = modules.cwdPolicy.resolveSubagentTarget({
|
|
371
477
|
workspace: ctx.cwd,
|
|
372
478
|
requestedCwd: agent.cwd,
|
|
373
479
|
currentProjectTrusted: ctx.isProjectTrusted(),
|
|
374
480
|
});
|
|
375
|
-
return [
|
|
481
|
+
return [
|
|
482
|
+
{ ...agent, cwd: target.cwd, target: modules.cwdPolicy.targetPolicyAudit(target) },
|
|
483
|
+
];
|
|
376
484
|
} catch {
|
|
377
485
|
return [];
|
|
378
486
|
}
|
|
@@ -383,7 +491,7 @@ export function registerStatefulSubagents(
|
|
|
383
491
|
nextRegistry.restore(restored);
|
|
384
492
|
if (generation !== runtimeGeneration) {
|
|
385
493
|
sessionBroker.close();
|
|
386
|
-
await disposeStatefulRuntime(nextRegistry,
|
|
494
|
+
await modules.lifecycle.disposeStatefulRuntime(nextRegistry, currentWorkspaceManager);
|
|
387
495
|
return;
|
|
388
496
|
}
|
|
389
497
|
registry = nextRegistry;
|
|
@@ -445,7 +553,9 @@ export function registerStatefulSubagents(
|
|
|
445
553
|
seenMessageIds.clear();
|
|
446
554
|
pendingIdempotentSpawns.clear();
|
|
447
555
|
const shutdown = async () => {
|
|
448
|
-
const
|
|
556
|
+
const currentWorkspaceManager = await getWorkspaceManager();
|
|
557
|
+
const { disposeStatefulRuntime } = await import("./stateful-lifecycle.js");
|
|
558
|
+
const errors = await disposeStatefulRuntime(previousRegistry, currentWorkspaceManager);
|
|
449
559
|
if (errors.length > 0 && ctx.hasUI) {
|
|
450
560
|
ctx.ui.notify(`Subagent shutdown cleanup reported ${errors.length} error(s).`, "warning");
|
|
451
561
|
}
|
|
@@ -502,30 +612,47 @@ export function registerStatefulSubagents(
|
|
|
502
612
|
}),
|
|
503
613
|
...createStatefulToolRenderer("spawn"),
|
|
504
614
|
async execute(_id, params, signal, _update, ctx) {
|
|
615
|
+
const generation = runtimeGeneration;
|
|
616
|
+
const capturedRegistry = registry;
|
|
617
|
+
let modules: StatefulSpawnModules;
|
|
618
|
+
try {
|
|
619
|
+
modules = await loadStatefulSpawnModules();
|
|
620
|
+
} catch (error) {
|
|
621
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
622
|
+
throw error;
|
|
623
|
+
}
|
|
624
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
625
|
+
let currentWorkspaceManager: WorkspaceManager;
|
|
626
|
+
try {
|
|
627
|
+
currentWorkspaceManager = await getWorkspaceManager();
|
|
628
|
+
} catch (error) {
|
|
629
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
630
|
+
throw error;
|
|
631
|
+
}
|
|
632
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
505
633
|
const scope = (params.agentScope ?? "user") as AgentScope;
|
|
506
634
|
const resultFormat = (params.resultFormat ?? "text") as SubagentResultFormat;
|
|
507
|
-
const contract = normalizeDelegationContract(params.contract);
|
|
635
|
+
const contract = modules.delegationContract.normalizeDelegationContract(params.contract);
|
|
508
636
|
if (params.contract !== undefined && !contract) {
|
|
509
637
|
throw new Error(
|
|
510
638
|
"subagent_spawn contract must be a valid pi-subagents:delegation:v2 object",
|
|
511
639
|
);
|
|
512
640
|
}
|
|
513
|
-
assertSubagentDepthAllowed();
|
|
641
|
+
modules.runtimePolicy.assertSubagentDepthAllowed();
|
|
514
642
|
assertSpawnIdempotencyKey(params.idempotencyKey);
|
|
515
|
-
const generation = runtimeGeneration;
|
|
516
643
|
const currentSettings = getCurrentSettings();
|
|
517
|
-
const target = resolveSubagentTarget({
|
|
644
|
+
const target = modules.cwdPolicy.resolveSubagentTarget({
|
|
518
645
|
workspace: ctx.cwd,
|
|
519
646
|
requestedCwd: params.cwd,
|
|
520
647
|
currentProjectTrusted: ctx.isProjectTrusted(),
|
|
521
648
|
});
|
|
522
|
-
assertDelegationTargetAllowed(
|
|
649
|
+
modules.cwdPolicy.assertDelegationTargetAllowed(
|
|
523
650
|
target,
|
|
524
651
|
currentSettings?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY,
|
|
525
652
|
);
|
|
526
653
|
const cwd = target.cwd;
|
|
527
654
|
const mode = resolveSpawnContextMode(params.context, params.contextEntryIds);
|
|
528
|
-
const snapshot = buildContextSnapshot(
|
|
655
|
+
const snapshot = modules.context.buildContextSnapshot(
|
|
529
656
|
ctx.sessionManager.getBranch(),
|
|
530
657
|
mode,
|
|
531
658
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
@@ -534,27 +661,28 @@ export function registerStatefulSubagents(
|
|
|
534
661
|
if ((scope === "project" || scope === "both") && !ctx.isProjectTrusted()) {
|
|
535
662
|
throw new Error("Project-local subagent definitions require a trusted project");
|
|
536
663
|
}
|
|
537
|
-
const resolvedAgents = discoverAgents(cwd, scope, currentSettings).agents;
|
|
664
|
+
const resolvedAgents = modules.agents.discoverAgents(cwd, scope, currentSettings).agents;
|
|
538
665
|
const resolvedAgent = resolvedAgents.find((agent) => agent.name === params.agent);
|
|
539
666
|
if (!resolvedAgent) {
|
|
540
667
|
const available = resolvedAgents.map((agent) => agent.name).join(", ") || "none";
|
|
541
668
|
throw new Error(`Unknown subagent ${params.agent}. Available agents: ${available}`);
|
|
542
669
|
}
|
|
543
|
-
const targetSnapshot = targetPolicyAudit(target);
|
|
544
|
-
const { executionPlan, semanticSnapshot } =
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
670
|
+
const targetSnapshot = modules.cwdPolicy.targetPolicyAudit(target);
|
|
671
|
+
const { executionPlan, semanticSnapshot } =
|
|
672
|
+
await modules.retainedSemanticState.buildRetainedSemanticState({
|
|
673
|
+
agent: resolvedAgent,
|
|
674
|
+
contract,
|
|
675
|
+
target: targetSnapshot,
|
|
676
|
+
cwd,
|
|
677
|
+
workspaceMode: params.workspaceMode === "worktree" ? "worktree" : "shared",
|
|
678
|
+
transport: transportKind,
|
|
679
|
+
resultFormat,
|
|
680
|
+
thinkingLevel: params.thinkingLevel,
|
|
681
|
+
timeoutMs: params.timeoutMs,
|
|
682
|
+
taskGeneration: 1,
|
|
683
|
+
});
|
|
556
684
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
557
|
-
const requestHash = hashSpawnRequest({
|
|
685
|
+
const requestHash = modules.spawnIdempotency.hashSpawnRequest({
|
|
558
686
|
agent: params.agent,
|
|
559
687
|
task: params.task,
|
|
560
688
|
cwd,
|
|
@@ -572,7 +700,10 @@ export function registerStatefulSubagents(
|
|
|
572
700
|
contract,
|
|
573
701
|
resultFormat,
|
|
574
702
|
});
|
|
575
|
-
|
|
703
|
+
if (!capturedRegistry) {
|
|
704
|
+
throw new Error("Stateful subagents are not initialized for this session");
|
|
705
|
+
}
|
|
706
|
+
const ownedRegistry = capturedRegistry;
|
|
576
707
|
const retained = ownedRegistry.findBySpawnIdempotencyKey(params.idempotencyKey, requestHash);
|
|
577
708
|
if (retained) return result(retained, `Reused ${retained.agent} as ${retained.id}.`);
|
|
578
709
|
const foundPending = params.idempotencyKey
|
|
@@ -633,17 +764,17 @@ export function registerStatefulSubagents(
|
|
|
633
764
|
const workspaceOwner = `pending-${randomUUID()}`;
|
|
634
765
|
const workspace =
|
|
635
766
|
params.workspaceMode === "worktree"
|
|
636
|
-
? await
|
|
767
|
+
? await currentWorkspaceManager.create(workspaceOwner, requestedCwd)
|
|
637
768
|
: undefined;
|
|
638
769
|
try {
|
|
639
770
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
640
771
|
} catch (error) {
|
|
641
|
-
if (workspace) await
|
|
772
|
+
if (workspace) await currentWorkspaceManager.cleanup(workspaceOwner);
|
|
642
773
|
throw error;
|
|
643
774
|
}
|
|
644
775
|
let agent: ManagedAgent | undefined;
|
|
645
776
|
try {
|
|
646
|
-
const capabilityGrant = issueCapabilityGrant(
|
|
777
|
+
const capabilityGrant = modules.capabilityGrant.issueCapabilityGrant(
|
|
647
778
|
executionPlan,
|
|
648
779
|
Date.now(),
|
|
649
780
|
Math.max(1, (params.timeoutMs ?? resolvedAgent.timeoutMs ?? 600_000) + 60_000),
|
|
@@ -678,12 +809,12 @@ export function registerStatefulSubagents(
|
|
|
678
809
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
679
810
|
} catch (error) {
|
|
680
811
|
if (agent) await ownedRegistry.closeTree(agent.id).catch(() => undefined);
|
|
681
|
-
if (workspace) await
|
|
812
|
+
if (workspace) await currentWorkspaceManager.cleanup(workspaceOwner);
|
|
682
813
|
throw error;
|
|
683
814
|
}
|
|
684
815
|
if (!agent) throw new Error("Subagent spawn completed without a retained agent");
|
|
685
816
|
if (workspace && agent.cwd === workspace.path) isolatedAgents.set(agent.id, workspaceOwner);
|
|
686
|
-
else if (workspace) await
|
|
817
|
+
else if (workspace) await currentWorkspaceManager.cleanup(workspaceOwner);
|
|
687
818
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
688
819
|
resolvePending?.(agent);
|
|
689
820
|
const deliveryNote =
|
|
@@ -747,25 +878,31 @@ export function registerStatefulSubagents(
|
|
|
747
878
|
async execute(_id, params, signal, _update, ctx) {
|
|
748
879
|
const generation = runtimeGeneration;
|
|
749
880
|
const ownedRegistry = requireRegistry();
|
|
881
|
+
let modules: StatefulSpawnModules;
|
|
882
|
+
try {
|
|
883
|
+
modules = await loadStatefulSpawnModules();
|
|
884
|
+
} catch (error) {
|
|
885
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
886
|
+
throw error;
|
|
887
|
+
}
|
|
888
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
750
889
|
const currentSettings = getCurrentSettings();
|
|
751
890
|
const existing = ownedRegistry.get(params.agentId);
|
|
752
891
|
if (!existing) throw new Error(`Unknown subagent: ${params.agentId}`);
|
|
753
|
-
const currentAgent =
|
|
754
|
-
existing.cwd,
|
|
755
|
-
|
|
756
|
-
currentSettings,
|
|
757
|
-
).agents.find((agent) => agent.name === existing.agent);
|
|
892
|
+
const currentAgent = modules.agents
|
|
893
|
+
.discoverAgents(existing.cwd, existing.agentScope ?? "user", currentSettings)
|
|
894
|
+
.agents.find((agent) => agent.name === existing.agent);
|
|
758
895
|
if (!currentAgent) throw new Error(`Unknown retained subagent definition: ${existing.agent}`);
|
|
759
896
|
const resolvedFollowUpTarget =
|
|
760
897
|
existing.workspaceMode === "worktree"
|
|
761
898
|
? undefined
|
|
762
|
-
: resolveSubagentTarget({
|
|
899
|
+
: modules.cwdPolicy.resolveSubagentTarget({
|
|
763
900
|
workspace: ctx.cwd,
|
|
764
901
|
requestedCwd: existing.cwd,
|
|
765
902
|
currentProjectTrusted: ctx.isProjectTrusted(),
|
|
766
903
|
});
|
|
767
904
|
if (resolvedFollowUpTarget) {
|
|
768
|
-
assertDelegationTargetAllowed(
|
|
905
|
+
modules.cwdPolicy.assertDelegationTargetAllowed(
|
|
769
906
|
resolvedFollowUpTarget,
|
|
770
907
|
currentSettings?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY,
|
|
771
908
|
);
|
|
@@ -776,9 +913,11 @@ export function registerStatefulSubagents(
|
|
|
776
913
|
const currentTarget =
|
|
777
914
|
existing.workspaceMode === "worktree" && existing.target
|
|
778
915
|
? existing.target
|
|
779
|
-
: targetPolicyAudit(
|
|
916
|
+
: modules.cwdPolicy.targetPolicyAudit(
|
|
917
|
+
resolvedFollowUpTarget as NonNullable<typeof resolvedFollowUpTarget>,
|
|
918
|
+
);
|
|
780
919
|
const { executionPlan: currentPlan, semanticSnapshot: currentSnapshot } =
|
|
781
|
-
await buildRetainedSemanticState({
|
|
920
|
+
await modules.retainedSemanticState.buildRetainedSemanticState({
|
|
782
921
|
agent: currentAgent,
|
|
783
922
|
contract: existing.contract,
|
|
784
923
|
target: currentTarget,
|
|
@@ -798,7 +937,10 @@ export function registerStatefulSubagents(
|
|
|
798
937
|
});
|
|
799
938
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
800
939
|
const compatibility = existing.semanticSnapshot
|
|
801
|
-
? evaluateSemanticCompatibility(
|
|
940
|
+
? modules.semanticSnapshot.evaluateSemanticCompatibility(
|
|
941
|
+
existing.semanticSnapshot,
|
|
942
|
+
currentSnapshot,
|
|
943
|
+
)
|
|
802
944
|
: { status: "warning" as const, changedComponents: ["legacy-missing-snapshot"] };
|
|
803
945
|
if (
|
|
804
946
|
(compatibility.status === "needs-revalidation" || compatibility.status === "rejected") &&
|
|
@@ -824,7 +966,7 @@ export function registerStatefulSubagents(
|
|
|
824
966
|
isolatedAgents.has(existing.id),
|
|
825
967
|
currentSettings,
|
|
826
968
|
);
|
|
827
|
-
const currentGrant = issueCapabilityGrant(
|
|
969
|
+
const currentGrant = modules.capabilityGrant.issueCapabilityGrant(
|
|
828
970
|
currentPlan,
|
|
829
971
|
Date.now(),
|
|
830
972
|
Math.max(1, (params.timeoutMs ?? existing.timeoutMs ?? 600_000) + 60_000),
|
|
@@ -838,6 +980,7 @@ export function registerStatefulSubagents(
|
|
|
838
980
|
? { status: "warning", changedComponents: compatibility.changedComponents }
|
|
839
981
|
: compatibility,
|
|
840
982
|
);
|
|
983
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
841
984
|
const agent = await ownedRegistry.followUp(params.agentId, params.task, {
|
|
842
985
|
timeoutMs: params.timeoutMs,
|
|
843
986
|
idleTimeoutMs: params.idleTimeoutMs,
|
|
@@ -860,6 +1003,14 @@ export function registerStatefulSubagents(
|
|
|
860
1003
|
async execute(_id, params, signal): Promise<StatefulActionToolResult> {
|
|
861
1004
|
const generation = runtimeGeneration;
|
|
862
1005
|
const ownedRegistry = requireRegistry();
|
|
1006
|
+
let currentWorkspaceManager: WorkspaceManager;
|
|
1007
|
+
try {
|
|
1008
|
+
currentWorkspaceManager = await getWorkspaceManager();
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
1011
|
+
throw error;
|
|
1012
|
+
}
|
|
1013
|
+
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
863
1014
|
const ownedAgent = (agentId: string): ManagedAgent => {
|
|
864
1015
|
const value = ownedRegistry.get(agentId);
|
|
865
1016
|
if (!value) throw new Error(`Unknown subagent: ${agentId}`);
|
|
@@ -886,7 +1037,7 @@ export function registerStatefulSubagents(
|
|
|
886
1037
|
const existing = ownedRegistry.get(agentId);
|
|
887
1038
|
if (existing?.state === "closed" && !operation.subtree) {
|
|
888
1039
|
const pendingOwner = isolatedAgents.get(existing.id);
|
|
889
|
-
if (pendingOwner) await
|
|
1040
|
+
if (pendingOwner) await currentWorkspaceManager.cleanup(pendingOwner);
|
|
890
1041
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
891
1042
|
isolatedAgents.delete(existing.id);
|
|
892
1043
|
return result(existing, `Closed ${existing.id}.`);
|
|
@@ -896,7 +1047,7 @@ export function registerStatefulSubagents(
|
|
|
896
1047
|
try {
|
|
897
1048
|
agents = await ownedRegistry.closeTree(agentId);
|
|
898
1049
|
} finally {
|
|
899
|
-
await cleanupClosedWorkspaces(ownedRegistry, isolatedAgents,
|
|
1050
|
+
await cleanupClosedWorkspaces(ownedRegistry, isolatedAgents, currentWorkspaceManager);
|
|
900
1051
|
}
|
|
901
1052
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
902
1053
|
return {
|
|
@@ -911,7 +1062,7 @@ export function registerStatefulSubagents(
|
|
|
911
1062
|
try {
|
|
912
1063
|
agent = await ownedRegistry.close(agentId);
|
|
913
1064
|
} finally {
|
|
914
|
-
await cleanupClosedWorkspaces(ownedRegistry, isolatedAgents,
|
|
1065
|
+
await cleanupClosedWorkspaces(ownedRegistry, isolatedAgents, currentWorkspaceManager);
|
|
915
1066
|
}
|
|
916
1067
|
assertCurrentSpawn(signal, generation, runtimeGeneration);
|
|
917
1068
|
return result(agent, `Closed ${agent.id}.`);
|
package/src/subagents.ts
CHANGED
|
@@ -310,7 +310,6 @@ function appendAgentCatalog(baseDescription: string, catalog: string): string {
|
|
|
310
310
|
|
|
311
311
|
export { parsePositiveInteger } from "./execution/runtime-policy.js";
|
|
312
312
|
export { buildPiArgs } from "./pi-args.js";
|
|
313
|
-
export { formatTokens, formatUsageStats } from "./render.js";
|
|
314
313
|
export {
|
|
315
314
|
DEFAULT_CONSULT_RESOURCE_POLICY,
|
|
316
315
|
DEFAULT_CONSULTATION_CWD_POLICY,
|
|
@@ -339,3 +338,4 @@ export {
|
|
|
339
338
|
updateDelegationWorkflowSetting,
|
|
340
339
|
updateStatefulLimitSetting,
|
|
341
340
|
} from "./settings.js";
|
|
341
|
+
export { formatTokens, formatUsageStats } from "./usage-format.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { SubagentThinkingLevel } from "./agents/types.js";
|
|
2
|
+
import { safeLine } from "./render-common.js";
|
|
3
|
+
|
|
4
|
+
export function formatTokens(count: number): string {
|
|
5
|
+
if (count < 1000) return count.toString();
|
|
6
|
+
if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
|
|
7
|
+
if (count < 1000000) return `${Math.round(count / 1000)}k`;
|
|
8
|
+
return `${(count / 1000000).toFixed(1)}M`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function formatUsageStats(
|
|
12
|
+
usage: {
|
|
13
|
+
input: number;
|
|
14
|
+
output: number;
|
|
15
|
+
cacheRead: number;
|
|
16
|
+
cacheWrite: number;
|
|
17
|
+
cost: number;
|
|
18
|
+
contextTokens?: number;
|
|
19
|
+
turns?: number;
|
|
20
|
+
},
|
|
21
|
+
model?: string,
|
|
22
|
+
thinkingLevel?: SubagentThinkingLevel,
|
|
23
|
+
actualProvider?: string,
|
|
24
|
+
actualModel?: string,
|
|
25
|
+
): string {
|
|
26
|
+
const parts: string[] = [];
|
|
27
|
+
if (usage.turns) parts.push(`${usage.turns} turn${usage.turns > 1 ? "s" : ""}`);
|
|
28
|
+
if (usage.input) parts.push(`↑${formatTokens(usage.input)}`);
|
|
29
|
+
if (usage.output) parts.push(`↓${formatTokens(usage.output)}`);
|
|
30
|
+
if (usage.cacheRead) parts.push(`R${formatTokens(usage.cacheRead)}`);
|
|
31
|
+
if (usage.cacheWrite) parts.push(`W${formatTokens(usage.cacheWrite)}`);
|
|
32
|
+
if (usage.cost) parts.push(`$${usage.cost.toFixed(4)}`);
|
|
33
|
+
if (usage.contextTokens && usage.contextTokens > 0)
|
|
34
|
+
parts.push(`ctx:${formatTokens(usage.contextTokens)}`);
|
|
35
|
+
const safeProvider = actualProvider ? safeLine(actualProvider, "", 256) : undefined;
|
|
36
|
+
const safeModel = actualModel ? safeLine(actualModel, "", 256) : undefined;
|
|
37
|
+
const actual =
|
|
38
|
+
safeProvider && safeModel ? `${safeProvider}/${safeModel}` : (safeModel ?? safeProvider);
|
|
39
|
+
if (actual ?? model) parts.push(actual ?? safeLine(model, "", 256));
|
|
40
|
+
if (thinkingLevel) parts.push(`requested-thinking:${safeLine(thinkingLevel, "", 128)}`);
|
|
41
|
+
return parts.join(" ");
|
|
42
|
+
}
|