@selesai/code 0.3.0 → 0.3.1
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/dist/core/diagnostics.d.ts +1 -0
- package/dist/core/diagnostics.d.ts.map +1 -1
- package/dist/core/diagnostics.js.map +1 -1
- package/dist/core/package-manager.d.ts +10 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +93 -8
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/package-manager.test.d.ts +2 -0
- package/dist/core/package-manager.test.d.ts.map +1 -0
- package/dist/core/package-manager.test.js +20 -0
- package/dist/core/package-manager.test.js.map +1 -0
- package/dist/core/resource-loader.d.ts +3 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +10 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/extensions/caveman/caveman-instructions.cjs +39 -0
- package/dist/extensions/caveman/index.js +116 -0
- package/dist/extensions/caveman/package.json +8 -0
- package/dist/extensions/caveman/test/extension.test.js +185 -0
- package/dist/extensions/caveman/test/helpers.test.js +67 -0
- package/dist/extensions/pi-intercom/broker/broker.ts +2 -3
- package/dist/extensions/pi-intercom/broker/paths.ts +10 -5
- package/dist/extensions/pi-intercom/broker/spawn.ts +2 -3
- package/dist/extensions/pi-intercom/config.ts +2 -2
- package/dist/extensions/pi-intercom/index.ts +2 -2
- package/dist/extensions/pi-intercom/package.json +2 -2
- package/dist/extensions/pi-intercom/ui/compose.ts +3 -3
- package/dist/extensions/pi-intercom/ui/inline-message.ts +3 -3
- package/dist/extensions/pi-intercom/ui/session-list.ts +3 -3
- package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +2 -1
- package/dist/extensions/pi-powerline-footer/index.ts +9 -16
- package/dist/extensions/pi-powerline-footer/welcome.ts +23 -16
- package/dist/extensions/pi-powerline-footer/working-vibes.ts +2 -9
- package/dist/extensions/pi-rewind-hook/index.ts +1 -1
- package/dist/extensions/pi-rewind-hook/package.json +1 -1
- package/dist/extensions/pi-subagents/README.md +224 -52
- package/dist/extensions/pi-subagents/install.mjs +93 -0
- package/dist/extensions/pi-subagents/package.json +4 -5
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +76 -13
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +179 -2
- package/dist/extensions/pi-subagents/src/agents/agent-memory.ts +254 -0
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +11 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +193 -19
- package/dist/extensions/pi-subagents/src/agents/chain-serializer.ts +27 -2
- package/dist/extensions/pi-subagents/src/extension/config.ts +27 -4
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +1 -7
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -2
- package/dist/extensions/pi-subagents/src/extension/index.ts +73 -71
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +369 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +54 -10
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +200 -0
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +21 -253
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +519 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +195 -105
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +88 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +67 -10
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +34 -4
- package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +166 -0
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +156 -1
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +515 -0
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +161 -44
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +1 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +3 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +167 -6
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +514 -0
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +28 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +873 -204
- package/dist/extensions/pi-subagents/src/runs/background/wait.ts +353 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +123 -27
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +174 -70
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +569 -81
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +45 -22
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +3 -1
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +172 -21
- package/dist/extensions/pi-subagents/src/runs/shared/model-scope.ts +128 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +89 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +50 -1
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +35 -4
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +63 -45
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +125 -5
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +74 -0
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +52 -0
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +28 -5
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +16 -1
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +15 -2
- package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +212 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +133 -22
- package/dist/extensions/pi-subagents/src/shared/settings.ts +3 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +197 -4
- package/dist/extensions/pi-subagents/src/shared/utils.ts +100 -15
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +330 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +135 -4
- package/dist/extensions/pi-subagents/src/tui/render.ts +32 -12
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2772 -0
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +1107 -0
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +463 -0
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +274 -0
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1535 -0
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +242 -0
- package/dist/extensions/pi-subagents/test/integration/doctor-executor.test.ts +128 -0
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +252 -0
- package/dist/extensions/pi-subagents/test/integration/foreground-result-size.test.ts +188 -0
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +1306 -0
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +1033 -0
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +397 -0
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +583 -0
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +691 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +892 -0
- package/dist/extensions/pi-subagents/test/integration/session-tokens.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1824 -0
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1284 -0
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +127 -0
- package/dist/extensions/pi-subagents/test/integration/template-resolution.test.ts +299 -0
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +37 -0
- package/dist/extensions/pi-subagents/test/support/helpers.ts +189 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +335 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +129 -0
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +184 -0
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +285 -0
- package/dist/extensions/pi-subagents/test/support/register-loader.mjs +15 -0
- package/dist/extensions/pi-subagents/test/support/ts-loader.mjs +108 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +421 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +343 -0
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +1199 -0
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +556 -0
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +420 -0
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +579 -0
- package/dist/extensions/pi-subagents/test/unit/agent-scope.test.ts +20 -0
- package/dist/extensions/pi-subagents/test/unit/agent-selection.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/artifacts.test.ts +24 -0
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +221 -0
- package/dist/extensions/pi-subagents/test/unit/async-permission-session.test.ts +98 -0
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +285 -0
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +112 -0
- package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +206 -0
- package/dist/extensions/pi-subagents/test/unit/chain-root-attachment.test.ts +159 -0
- package/dist/extensions/pi-subagents/test/unit/chain-serializer.test.ts +229 -0
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +225 -0
- package/dist/extensions/pi-subagents/test/unit/close-grace-timer.test.ts +101 -0
- package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +228 -0
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +152 -0
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +72 -0
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +327 -0
- package/dist/extensions/pi-subagents/test/unit/control-notices.test.ts +161 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +159 -0
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +217 -0
- package/dist/extensions/pi-subagents/test/unit/extra-agent-dirs.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/file-coalescer.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/foreground-tool-call-compaction.test.ts +78 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +415 -0
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +126 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +271 -0
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +173 -0
- package/dist/extensions/pi-subagents/test/unit/jsonl-writer.test.ts +119 -0
- package/dist/extensions/pi-subagents/test/unit/live-async-resume.test.ts +170 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +346 -0
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope-settings.test.ts +87 -0
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +145 -0
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +162 -0
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +470 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +329 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +319 -0
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +70 -0
- package/dist/extensions/pi-subagents/test/unit/parallel-utils.test.ts +247 -0
- package/dist/extensions/pi-subagents/test/unit/path-handling.test.ts +113 -0
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +94 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +803 -0
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +238 -0
- package/dist/extensions/pi-subagents/test/unit/proactive-skills.test.ts +118 -0
- package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +299 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-template-bridge.test.ts +408 -0
- package/dist/extensions/pi-subagents/test/unit/prompt-workflows.test.ts +134 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +257 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +209 -0
- package/dist/extensions/pi-subagents/test/unit/result-intercom.test.ts +226 -0
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +294 -0
- package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +147 -0
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +997 -0
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +478 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +562 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +213 -0
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +438 -0
- package/dist/extensions/pi-subagents/test/unit/slash-bridge.test.ts +54 -0
- package/dist/extensions/pi-subagents/test/unit/slash-chain-groups.test.ts +464 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +255 -0
- package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +19 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +245 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +517 -0
- package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +72 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +237 -0
- package/dist/extensions/pi-subagents/test/unit/total-cost.test.ts +60 -0
- package/dist/extensions/pi-subagents/test/unit/ts-loader.test.ts +33 -0
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +202 -0
- package/dist/extensions/pi-subagents/test/unit/types-fork-preamble.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/wait.test.ts +398 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +88 -0
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +26 -0
- package/dist/extensions/pi-subagents/test/unit/workflow-graph.test.ts +134 -0
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +479 -0
- package/dist/extensions/pi-web-agent/src/changelog-notice.ts +5 -2
- package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +6 -4
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +19 -5
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/skills/caveman/SKILL.md +49 -0
- package/dist/skills/handoff-text/SKILL.md +13 -0
- package/docs/docs.json +4 -0
- package/docs/extensions.md +2 -0
- package/docs/settings.md +15 -0
- package/docs/shared-host-extensions.md +109 -0
- package/package.json +1 -1
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import type { ExtensionAPI, ExtensionContext } from "@selesai/code";
|
|
5
|
+
import { parseFrontmatter } from "../agents/frontmatter.ts";
|
|
6
|
+
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
7
|
+
import type { ChainStep } from "../shared/settings.ts";
|
|
8
|
+
import { getAgentDir, getProjectConfigDir } from "../shared/utils.ts";
|
|
9
|
+
|
|
10
|
+
interface PromptWorkflow {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
body: string;
|
|
14
|
+
filePath: string;
|
|
15
|
+
agent: string;
|
|
16
|
+
context?: "fresh" | "fork";
|
|
17
|
+
model?: string;
|
|
18
|
+
skill?: string | string[] | false;
|
|
19
|
+
cwd?: string;
|
|
20
|
+
worktree?: boolean;
|
|
21
|
+
chain?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type PromptWorkflowRunner = (params: SubagentParamsLike, ctx: ExtensionContext) => Promise<void>;
|
|
25
|
+
|
|
26
|
+
const RESERVED_COMMAND_NAMES = new Set([
|
|
27
|
+
"chain-prompts",
|
|
28
|
+
"prompt-workflow",
|
|
29
|
+
"run",
|
|
30
|
+
"chain",
|
|
31
|
+
"parallel",
|
|
32
|
+
"run-chain",
|
|
33
|
+
"subagents-doctor",
|
|
34
|
+
"subagents-models",
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
function packagePromptsDir(): string {
|
|
38
|
+
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "prompts");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function promptDirs(cwd: string): string[] {
|
|
42
|
+
return [
|
|
43
|
+
packagePromptsDir(),
|
|
44
|
+
path.join(getAgentDir(), "prompts"),
|
|
45
|
+
path.join(getProjectConfigDir(cwd), "prompts"),
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readPromptFiles(cwd: string): string[] {
|
|
50
|
+
const files: string[] = [];
|
|
51
|
+
for (const dir of promptDirs(cwd)) {
|
|
52
|
+
let entries: fs.Dirent[];
|
|
53
|
+
try {
|
|
54
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
55
|
+
} catch {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (entry.isFile() && entry.name.endsWith(".md")) files.push(path.join(dir, entry.name));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return files;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function firstNonEmptyLine(value: string): string {
|
|
66
|
+
return value.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "Prompt workflow";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function stringField(frontmatter: Record<string, string>, key: string): string | undefined {
|
|
70
|
+
const value = frontmatter[key]?.trim();
|
|
71
|
+
return value ? value : undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function booleanField(frontmatter: Record<string, string>, key: string): boolean | undefined {
|
|
75
|
+
const value = frontmatter[key]?.trim().toLowerCase();
|
|
76
|
+
if (value === "true" || value === "yes" || value === "1") return true;
|
|
77
|
+
if (value === "false" || value === "no" || value === "0") return false;
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parseSkill(value: string | undefined): string | string[] | false | undefined {
|
|
82
|
+
if (!value) return undefined;
|
|
83
|
+
if (value === "false") return false;
|
|
84
|
+
const parts = value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
85
|
+
return parts.length > 1 ? parts : parts[0];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseAgent(frontmatter: Record<string, string>): string {
|
|
89
|
+
const subagent = stringField(frontmatter, "subagent");
|
|
90
|
+
if (!subagent || subagent === "true") return "delegate";
|
|
91
|
+
return subagent;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function loadPromptWorkflow(filePath: string): PromptWorkflow | undefined {
|
|
95
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
96
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
97
|
+
const name = path.basename(filePath, ".md");
|
|
98
|
+
if (!name || RESERVED_COMMAND_NAMES.has(name)) return undefined;
|
|
99
|
+
const model = stringField(frontmatter, "model");
|
|
100
|
+
const skill = parseSkill(stringField(frontmatter, "skill"));
|
|
101
|
+
const cwd = stringField(frontmatter, "cwd");
|
|
102
|
+
const chain = stringField(frontmatter, "chain");
|
|
103
|
+
return {
|
|
104
|
+
name,
|
|
105
|
+
description: stringField(frontmatter, "description") ?? firstNonEmptyLine(body),
|
|
106
|
+
body,
|
|
107
|
+
filePath,
|
|
108
|
+
agent: parseAgent(frontmatter),
|
|
109
|
+
...(booleanField(frontmatter, "inheritContext") === true || booleanField(frontmatter, "fork") === true ? { context: "fork" as const } : {}),
|
|
110
|
+
...(booleanField(frontmatter, "fresh") === true ? { context: "fresh" as const } : {}),
|
|
111
|
+
...(model ? { model } : {}),
|
|
112
|
+
...(skill !== undefined ? { skill } : {}),
|
|
113
|
+
...(cwd ? { cwd } : {}),
|
|
114
|
+
...(booleanField(frontmatter, "worktree") === true ? { worktree: true } : {}),
|
|
115
|
+
...(chain ? { chain } : {}),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function discoverPromptWorkflows(cwd: string): PromptWorkflow[] {
|
|
120
|
+
const workflows = new Map<string, PromptWorkflow>();
|
|
121
|
+
for (const file of readPromptFiles(cwd)) {
|
|
122
|
+
const workflow = loadPromptWorkflow(file);
|
|
123
|
+
if (workflow) workflows.set(workflow.name, workflow);
|
|
124
|
+
}
|
|
125
|
+
return [...workflows.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function shellWords(input: string): string[] {
|
|
129
|
+
const words: string[] = [];
|
|
130
|
+
let current = "";
|
|
131
|
+
let quote: "'" | '"' | undefined;
|
|
132
|
+
let escaped = false;
|
|
133
|
+
for (const ch of input) {
|
|
134
|
+
if (escaped) {
|
|
135
|
+
current += ch;
|
|
136
|
+
escaped = false;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (ch === "\\") {
|
|
140
|
+
escaped = true;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (quote) {
|
|
144
|
+
if (ch === quote) quote = undefined;
|
|
145
|
+
else current += ch;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (ch === "'" || ch === '"') {
|
|
149
|
+
quote = ch;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (/\s/.test(ch)) {
|
|
153
|
+
if (current) {
|
|
154
|
+
words.push(current);
|
|
155
|
+
current = "";
|
|
156
|
+
}
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
current += ch;
|
|
160
|
+
}
|
|
161
|
+
if (current) words.push(current);
|
|
162
|
+
return words;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function substituteArgs(template: string, args: string[]): string {
|
|
166
|
+
const all = args.join(" ");
|
|
167
|
+
return template
|
|
168
|
+
.replace(/\$ARGUMENTS/g, all)
|
|
169
|
+
.replace(/\$@/g, all)
|
|
170
|
+
.replace(/\$\{(\d+):-([^}]*)\}/g, (_match, index: string, fallback: string) => args[Number(index) - 1] || fallback)
|
|
171
|
+
.replace(/\$(\d+)/g, (_match, index: string) => args[Number(index) - 1] ?? "");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function parseRuntimeOptions(words: string[]): { args: string[]; agentOverride?: string; fork?: boolean; fresh?: boolean; worktree?: boolean; bg?: boolean } {
|
|
175
|
+
const args: string[] = [];
|
|
176
|
+
let agentOverride: string | undefined;
|
|
177
|
+
let fork = false;
|
|
178
|
+
let fresh = false;
|
|
179
|
+
let worktree = false;
|
|
180
|
+
let bg = false;
|
|
181
|
+
for (let i = 0; i < words.length; i++) {
|
|
182
|
+
const word = words[i]!;
|
|
183
|
+
if (word === "--fork") {
|
|
184
|
+
fork = true;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (word === "--fresh") {
|
|
188
|
+
fresh = true;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (word === "--worktree") {
|
|
192
|
+
worktree = true;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (word === "--bg" || word === "--async") {
|
|
196
|
+
bg = true;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (word === "--subagent") {
|
|
200
|
+
agentOverride = words[++i];
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const eq = word.match(/^--subagent(?:=|:)(.+)$/);
|
|
204
|
+
if (eq) {
|
|
205
|
+
agentOverride = eq[1];
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
args.push(word);
|
|
209
|
+
}
|
|
210
|
+
return { args, agentOverride, fork, fresh, worktree, bg };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function splitChainDeclaration(input: string): { declaration: string; argsText: string } {
|
|
214
|
+
const delimiter = input.indexOf(" -- ");
|
|
215
|
+
if (delimiter === -1) return { declaration: input.trim(), argsText: "" };
|
|
216
|
+
return { declaration: input.slice(0, delimiter).trim(), argsText: input.slice(delimiter + 4).trim() };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function splitPromptChain(input: string): string[] {
|
|
220
|
+
return input.split(" -> ").map((part) => part.trim()).filter(Boolean);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function workflowParams(workflow: PromptWorkflow, args: string[], runtime: ReturnType<typeof parseRuntimeOptions>): SubagentParamsLike {
|
|
224
|
+
const task = substituteArgs(workflow.body, args).trim();
|
|
225
|
+
const context = runtime.fork ? "fork" : runtime.fresh ? "fresh" : workflow.context;
|
|
226
|
+
return {
|
|
227
|
+
agent: runtime.agentOverride ?? workflow.agent,
|
|
228
|
+
task,
|
|
229
|
+
clarify: false,
|
|
230
|
+
agentScope: "both",
|
|
231
|
+
...(context ? { context } : {}),
|
|
232
|
+
...(workflow.model ? { model: workflow.model } : {}),
|
|
233
|
+
...(workflow.skill !== undefined ? { skill: workflow.skill } : {}),
|
|
234
|
+
...(workflow.cwd ? { cwd: workflow.cwd } : {}),
|
|
235
|
+
...(runtime.worktree || workflow.worktree ? { worktree: true } : {}),
|
|
236
|
+
...(runtime.bg ? { async: true } : {}),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function workflowChainStep(workflow: PromptWorkflow, args: string[], runtime: ReturnType<typeof parseRuntimeOptions>): ChainStep {
|
|
241
|
+
const params = workflowParams(workflow, args, runtime);
|
|
242
|
+
return {
|
|
243
|
+
agent: params.agent ?? "delegate",
|
|
244
|
+
task: params.task,
|
|
245
|
+
...(params.model ? { model: params.model } : {}),
|
|
246
|
+
...(params.skill !== undefined ? { skill: params.skill } : {}),
|
|
247
|
+
...(params.cwd ? { cwd: params.cwd } : {}),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function findWorkflow(workflows: PromptWorkflow[], name: string): PromptWorkflow | undefined {
|
|
252
|
+
return workflows.find((workflow) => workflow.name === name);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function formatWorkflowList(workflows: PromptWorkflow[]): string {
|
|
256
|
+
if (workflows.length === 0) return "No prompt workflows found in package, user, or project prompts.";
|
|
257
|
+
return [
|
|
258
|
+
"Prompt workflows:",
|
|
259
|
+
...workflows.map((workflow) => `- ${workflow.name}: ${workflow.description} (${workflow.filePath})`),
|
|
260
|
+
].join("\n");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function registerPromptWorkflowCommands(input: {
|
|
264
|
+
pi: ExtensionAPI;
|
|
265
|
+
run: PromptWorkflowRunner;
|
|
266
|
+
}): void {
|
|
267
|
+
const { pi, run } = input;
|
|
268
|
+
|
|
269
|
+
pi.registerCommand("prompt-workflow", {
|
|
270
|
+
description: "Run a prompt template through native pi-subagents: /prompt-workflow <name> [args]",
|
|
271
|
+
handler: async (rawArgs, ctx) => {
|
|
272
|
+
const words = shellWords(rawArgs);
|
|
273
|
+
const name = words.shift();
|
|
274
|
+
const workflows = discoverPromptWorkflows(ctx.cwd);
|
|
275
|
+
if (!name || name === "list") {
|
|
276
|
+
pi.sendMessage({ content: formatWorkflowList(workflows), display: true });
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const workflow = findWorkflow(workflows, name);
|
|
280
|
+
if (!workflow) {
|
|
281
|
+
ctx.ui.notify(`Unknown prompt workflow: ${name}`, "error");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const runtime = parseRuntimeOptions(words);
|
|
285
|
+
try {
|
|
286
|
+
if (workflow.chain) {
|
|
287
|
+
const chainNames = splitPromptChain(workflow.chain);
|
|
288
|
+
const chain = chainNames.map((stepName) => {
|
|
289
|
+
const step = findWorkflow(workflows, stepName);
|
|
290
|
+
if (!step) throw new Error(`Unknown prompt workflow in chain '${workflow.name}': ${stepName}`);
|
|
291
|
+
return workflowChainStep(step, runtime.args, runtime);
|
|
292
|
+
});
|
|
293
|
+
await run({ chain, task: runtime.args.join(" "), clarify: false, agentScope: "both", ...(runtime.bg ? { async: true } : {}) }, ctx);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
await run(workflowParams(workflow, runtime.args, runtime), ctx);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
pi.registerCommand("chain-prompts", {
|
|
304
|
+
description: "Run prompt templates as a native subagent chain: /chain-prompts analyze -> fix -- args",
|
|
305
|
+
handler: async (rawArgs, ctx) => {
|
|
306
|
+
const { declaration, argsText } = splitChainDeclaration(rawArgs);
|
|
307
|
+
const workflows = discoverPromptWorkflows(ctx.cwd);
|
|
308
|
+
if (!declaration || declaration === "list") {
|
|
309
|
+
pi.sendMessage({ content: formatWorkflowList(workflows), display: true });
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const runtime = parseRuntimeOptions(shellWords(argsText));
|
|
313
|
+
const names = splitPromptChain(declaration);
|
|
314
|
+
if (names.length === 0) {
|
|
315
|
+
ctx.ui.notify("Usage: /chain-prompts prompt-a -> prompt-b -- args", "error");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
try {
|
|
319
|
+
const chain = names.map((name) => {
|
|
320
|
+
const workflow = findWorkflow(workflows, name);
|
|
321
|
+
if (!workflow) throw new Error(`Unknown prompt workflow: ${name}`);
|
|
322
|
+
return workflowChainStep(workflow, runtime.args, runtime);
|
|
323
|
+
});
|
|
324
|
+
await run({ chain, task: runtime.args.join(" "), clarify: false, agentScope: "both", ...(runtime.bg ? { async: true } : {}) }, ctx);
|
|
325
|
+
} catch (error) {
|
|
326
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import type
|
|
4
|
+
import { keyText, type ExtensionAPI, type ExtensionContext } from "@selesai/code";
|
|
5
5
|
import { Key, matchesKey } from "@earendil-works/pi-tui";
|
|
6
6
|
import { BUILTIN_AGENT_NAMES, discoverAgents, discoverAgentsAll, type ChainConfig } from "../agents/agents.ts";
|
|
7
7
|
import {
|
|
@@ -16,14 +16,17 @@ import {
|
|
|
16
16
|
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
17
17
|
import { isDynamicParallelStep, isParallelStep, type ChainStep } from "../shared/settings.ts";
|
|
18
18
|
import { findModelInfo, toModelInfo } from "../shared/model-info.ts";
|
|
19
|
+
import { formatTokens } from "../shared/formatters.ts";
|
|
19
20
|
import { assertJsonSchemaObject } from "../runs/shared/structured-output.ts";
|
|
20
21
|
import { validateAcceptanceInput } from "../runs/shared/acceptance.ts";
|
|
21
22
|
import type { SlashSubagentResponse, SlashSubagentUpdate } from "./slash-bridge.ts";
|
|
23
|
+
import { registerPromptWorkflowCommands } from "./prompt-workflows.ts";
|
|
22
24
|
import {
|
|
23
25
|
applySlashUpdate,
|
|
24
26
|
buildSlashInitialResult,
|
|
25
27
|
failSlashResult,
|
|
26
28
|
finalizeSlashResult,
|
|
29
|
+
resolveSlashMessageDetails,
|
|
27
30
|
} from "./slash-live-state.ts";
|
|
28
31
|
import {
|
|
29
32
|
SLASH_RESULT_TYPE,
|
|
@@ -33,9 +36,11 @@ import {
|
|
|
33
36
|
SLASH_SUBAGENT_RESPONSE_EVENT,
|
|
34
37
|
SLASH_SUBAGENT_STARTED_EVENT,
|
|
35
38
|
SLASH_SUBAGENT_UPDATE_EVENT,
|
|
39
|
+
type Details,
|
|
36
40
|
type JsonSchemaObject,
|
|
37
41
|
type SingleResult,
|
|
38
42
|
type SubagentState,
|
|
43
|
+
type Usage,
|
|
39
44
|
} from "../shared/types.ts";
|
|
40
45
|
|
|
41
46
|
interface InlineConfig {
|
|
@@ -216,6 +221,113 @@ async function withSlashStatus<T>(
|
|
|
216
221
|
}
|
|
217
222
|
}
|
|
218
223
|
|
|
224
|
+
function emptyUsage(): Usage {
|
|
225
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function addUsage(target: Usage, source: Usage): void {
|
|
229
|
+
target.input += source.input;
|
|
230
|
+
target.output += source.output;
|
|
231
|
+
target.cacheRead += source.cacheRead;
|
|
232
|
+
target.cacheWrite += source.cacheWrite;
|
|
233
|
+
target.cost += source.cost;
|
|
234
|
+
target.turns += source.turns;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function usageHasValue(usage: Usage): boolean {
|
|
238
|
+
return usage.input !== 0 || usage.output !== 0 || usage.cacheRead !== 0 || usage.cacheWrite !== 0 || usage.cost !== 0 || usage.turns !== 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function assistantUsageFromMessage(message: unknown): Usage | undefined {
|
|
242
|
+
if (!message || typeof message !== "object") return undefined;
|
|
243
|
+
const msg = message as { role?: unknown; usage?: unknown };
|
|
244
|
+
if (msg.role !== "assistant" || !msg.usage || typeof msg.usage !== "object") return undefined;
|
|
245
|
+
const usage = msg.usage as {
|
|
246
|
+
input?: unknown;
|
|
247
|
+
output?: unknown;
|
|
248
|
+
cacheRead?: unknown;
|
|
249
|
+
cacheWrite?: unknown;
|
|
250
|
+
cost?: { total?: unknown };
|
|
251
|
+
};
|
|
252
|
+
return {
|
|
253
|
+
input: typeof usage.input === "number" ? usage.input : 0,
|
|
254
|
+
output: typeof usage.output === "number" ? usage.output : 0,
|
|
255
|
+
cacheRead: typeof usage.cacheRead === "number" ? usage.cacheRead : 0,
|
|
256
|
+
cacheWrite: typeof usage.cacheWrite === "number" ? usage.cacheWrite : 0,
|
|
257
|
+
cost: typeof usage.cost?.total === "number" ? usage.cost.total : 0,
|
|
258
|
+
turns: 1,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function isSubagentDetails(value: unknown): value is Details {
|
|
263
|
+
if (!value || typeof value !== "object") return false;
|
|
264
|
+
const details = value as { mode?: unknown; results?: unknown };
|
|
265
|
+
return typeof details.mode === "string" && Array.isArray(details.results);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function detailsFromSessionEntry(entry: unknown): Details | undefined {
|
|
269
|
+
if (!entry || typeof entry !== "object") return undefined;
|
|
270
|
+
const record = entry as { type?: unknown; customType?: unknown; details?: unknown; message?: unknown };
|
|
271
|
+
if (record.type === "custom_message" && record.customType === SLASH_RESULT_TYPE) {
|
|
272
|
+
const details = resolveSlashMessageDetails(record.details)?.result.details;
|
|
273
|
+
return isSubagentDetails(details) ? details : undefined;
|
|
274
|
+
}
|
|
275
|
+
if (record.type !== "message" || !record.message || typeof record.message !== "object") return undefined;
|
|
276
|
+
const message = record.message as { role?: unknown; toolName?: unknown; details?: unknown };
|
|
277
|
+
if (message.role !== "toolResult" || message.toolName !== "subagent") return undefined;
|
|
278
|
+
return isSubagentDetails(message.details) ? message.details : undefined;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function formatCostUsage(label: string, usage: Usage): string {
|
|
282
|
+
const extras = [
|
|
283
|
+
usage.cacheRead ? `cache read ${formatTokens(usage.cacheRead)}` : "",
|
|
284
|
+
usage.cacheWrite ? `cache write ${formatTokens(usage.cacheWrite)}` : "",
|
|
285
|
+
usage.turns ? `${usage.turns} turn${usage.turns === 1 ? "" : "s"}` : "",
|
|
286
|
+
].filter(Boolean);
|
|
287
|
+
return `${label}: ↑${formatTokens(usage.input)} ↓${formatTokens(usage.output)} $${usage.cost.toFixed(4)}${extras.length ? ` (${extras.join(", ")})` : ""}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function buildSubagentCostReport(ctx: ExtensionContext): string {
|
|
291
|
+
const parent = emptyUsage();
|
|
292
|
+
const childTotal = emptyUsage();
|
|
293
|
+
const total = emptyUsage();
|
|
294
|
+
const children: Array<{ label: string; usage: Usage; sessionFile?: string }> = [];
|
|
295
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
296
|
+
const message = entry.type === "message" ? (entry as { message?: unknown }).message : undefined;
|
|
297
|
+
const parentUsage = assistantUsageFromMessage(message);
|
|
298
|
+
if (parentUsage) addUsage(parent, parentUsage);
|
|
299
|
+
const details = detailsFromSessionEntry(entry);
|
|
300
|
+
if (!details) continue;
|
|
301
|
+
for (const result of details.results) {
|
|
302
|
+
if (!usageHasValue(result.usage)) continue;
|
|
303
|
+
const usage = { ...result.usage };
|
|
304
|
+
children.push({
|
|
305
|
+
label: `Child ${children.length + 1} (${result.agent})`,
|
|
306
|
+
usage,
|
|
307
|
+
...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
|
|
308
|
+
});
|
|
309
|
+
addUsage(childTotal, usage);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
addUsage(total, parent);
|
|
313
|
+
addUsage(total, childTotal);
|
|
314
|
+
const lines = [
|
|
315
|
+
"Subagent cost",
|
|
316
|
+
"",
|
|
317
|
+
formatCostUsage("Parent", parent),
|
|
318
|
+
];
|
|
319
|
+
if (children.length === 0) {
|
|
320
|
+
lines.push("No subagent child usage found in this session.");
|
|
321
|
+
} else {
|
|
322
|
+
for (const child of children) {
|
|
323
|
+
lines.push(formatCostUsage(child.label, child.usage));
|
|
324
|
+
if (child.sessionFile) lines.push(` Session: ${child.sessionFile}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
lines.push("────────────────────────────", formatCostUsage("Children", childTotal), formatCostUsage("Total", total));
|
|
328
|
+
return lines.join("\n");
|
|
329
|
+
}
|
|
330
|
+
|
|
219
331
|
function parseSingleRequiredArg(args: string, usage: string): { ok: true; value: string } | { ok: false; message: string } {
|
|
220
332
|
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
221
333
|
if (parts.length !== 1) return { ok: false, message: usage };
|
|
@@ -321,7 +433,8 @@ async function requestSlashRun(
|
|
|
321
433
|
if (!ctx.hasUI) return;
|
|
322
434
|
const tool = update.currentTool ? ` ${update.currentTool}` : "";
|
|
323
435
|
const count = update.toolCount ?? 0;
|
|
324
|
-
|
|
436
|
+
const liveDetailKey = keyText("app.tools.expand");
|
|
437
|
+
ctx.ui.setStatus("subagent-slash", `${count} tools${tool} | ${liveDetailKey} live detail`);
|
|
325
438
|
};
|
|
326
439
|
|
|
327
440
|
const onTerminalInput = ctx.hasUI
|
|
@@ -962,6 +1075,12 @@ export function registerSlashCommands(
|
|
|
962
1075
|
},
|
|
963
1076
|
});
|
|
964
1077
|
|
|
1078
|
+
pi.registerCommand("subagent-cost", {
|
|
1079
|
+
description: "Show parent and subagent child usage cost for this session",
|
|
1080
|
+
handler: async (_args, ctx) => {
|
|
1081
|
+
sendSlashText(pi, buildSubagentCostReport(ctx));
|
|
1082
|
+
},
|
|
1083
|
+
});
|
|
965
1084
|
|
|
966
1085
|
pi.registerCommand("subagents-doctor", {
|
|
967
1086
|
description: "Show subagent diagnostics",
|
|
@@ -970,6 +1089,18 @@ export function registerSlashCommands(
|
|
|
970
1089
|
},
|
|
971
1090
|
});
|
|
972
1091
|
|
|
1092
|
+
pi.registerCommand("subagents-fleet", {
|
|
1093
|
+
description: "Show active subagent fleet status and transcript commands",
|
|
1094
|
+
handler: async (_args, ctx) => {
|
|
1095
|
+
await runSlashSubagent(pi, ctx, { action: "status", view: "fleet" });
|
|
1096
|
+
},
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
registerPromptWorkflowCommands({
|
|
1100
|
+
pi,
|
|
1101
|
+
run: (params, ctx) => runSlashSubagent(pi, ctx, params),
|
|
1102
|
+
});
|
|
1103
|
+
|
|
973
1104
|
pi.registerCommand("subagents-models", {
|
|
974
1105
|
description: "Show runtime-loaded builtin subagent models",
|
|
975
1106
|
getArgumentCompletions: makeBuiltinAgentNameCompletions(),
|
|
@@ -998,7 +1129,7 @@ export function registerSlashCommands(
|
|
|
998
1129
|
handler: async (_args, _ctx) => {
|
|
999
1130
|
const profiles = listSubagentProfiles();
|
|
1000
1131
|
if (profiles.length === 0) {
|
|
1001
|
-
sendSlashText(pi, "Subagent profiles\n\nNo subagent profiles found in ~/.
|
|
1132
|
+
sendSlashText(pi, "Subagent profiles\n\nNo subagent profiles found in ~/.selesai/agent/profiles/pi-subagents/");
|
|
1002
1133
|
return;
|
|
1003
1134
|
}
|
|
1004
1135
|
sendSlashText(pi, `Subagent profiles\n\n${profiles.join("\n")}`);
|
|
@@ -1006,7 +1137,7 @@ export function registerSlashCommands(
|
|
|
1006
1137
|
});
|
|
1007
1138
|
|
|
1008
1139
|
pi.registerCommand("subagents-load-profile", {
|
|
1009
|
-
description: "Load a subagent profile into ~/.
|
|
1140
|
+
description: "Load a subagent profile into ~/.selesai/agent/settings.json",
|
|
1010
1141
|
getArgumentCompletions: (prefix) => {
|
|
1011
1142
|
if (prefix.includes(" ")) return null;
|
|
1012
1143
|
return listSubagentProfiles()
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import * as path from "node:path";
|
|
6
6
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
7
|
-
import { getMarkdownTheme, type ExtensionContext } from "@selesai/code";
|
|
7
|
+
import { getMarkdownTheme, keyText, type ExtensionContext } from "@selesai/code";
|
|
8
8
|
import { Container, Markdown, Spacer, Text, visibleWidth, type Component } from "@earendil-works/pi-tui";
|
|
9
9
|
import {
|
|
10
10
|
type AgentProgress,
|
|
@@ -26,6 +26,14 @@ import { aggregateStepStatus, formatActivityLabel, formatAgentRunningLabel, form
|
|
|
26
26
|
|
|
27
27
|
type Theme = ExtensionContext["ui"]["theme"];
|
|
28
28
|
|
|
29
|
+
function liveDetailKeyText(): string {
|
|
30
|
+
return keyText("app.tools.expand");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function liveDetailHintText(): string {
|
|
34
|
+
return `Press ${liveDetailKeyText()} for live detail`;
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
function getTermWidth(): number {
|
|
30
38
|
return process.stdout.columns || 120;
|
|
31
39
|
}
|
|
@@ -236,6 +244,15 @@ function formatToolUseStat(count: number): string {
|
|
|
236
244
|
return `${count} tool use${count === 1 ? "" : "s"}`;
|
|
237
245
|
}
|
|
238
246
|
|
|
247
|
+
function formatTotalCostStat(totalCost: Details["totalCost"] | undefined): string {
|
|
248
|
+
if (!totalCost || (totalCost.inputTokens === 0 && totalCost.outputTokens === 0 && totalCost.costUsd === 0)) return "";
|
|
249
|
+
const parts: string[] = [];
|
|
250
|
+
if (totalCost.inputTokens) parts.push(`in:${formatTokens(totalCost.inputTokens)}`);
|
|
251
|
+
if (totalCost.outputTokens) parts.push(`out:${formatTokens(totalCost.outputTokens)}`);
|
|
252
|
+
if (totalCost.costUsd) parts.push(`$${totalCost.costUsd.toFixed(4)}`);
|
|
253
|
+
return parts.join(" ");
|
|
254
|
+
}
|
|
255
|
+
|
|
239
256
|
function formatProgressStats(theme: Theme, progress: Pick<AgentProgress, "toolCount" | "tokens" | "durationMs"> | undefined, includeDuration = true): string {
|
|
240
257
|
if (!progress) return "";
|
|
241
258
|
const parts: string[] = [];
|
|
@@ -858,7 +875,7 @@ function foregroundStyleWidgetStepLines(
|
|
|
858
875
|
lines.push(` ${nestedLine}`);
|
|
859
876
|
}
|
|
860
877
|
if (step.status === "running") {
|
|
861
|
-
if (!expanded) lines.push(` ${theme.fg("accent",
|
|
878
|
+
if (!expanded) lines.push(` ${theme.fg("accent", liveDetailHintText())}`);
|
|
862
879
|
const output = widgetOutputPath(job, step);
|
|
863
880
|
if (output) lines.push(` ${theme.fg("dim", `output: ${shortenPath(output)}`)}`);
|
|
864
881
|
if (expanded) {
|
|
@@ -925,7 +942,7 @@ function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: numbe
|
|
|
925
942
|
lines.push(` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index))} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
|
|
926
943
|
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt)) lines.push(` ${nestedLine}`);
|
|
927
944
|
}
|
|
928
|
-
if (job.steps.some((step) => step.status === "running")) lines.push(theme.fg("accent",
|
|
945
|
+
if (job.steps.some((step) => step.status === "running")) lines.push(theme.fg("accent", ` ${liveDetailHintText()}`));
|
|
929
946
|
return lines.map((line) => truncLine(line, width));
|
|
930
947
|
}
|
|
931
948
|
|
|
@@ -1117,7 +1134,7 @@ function fitWidgetLineBudget(lines: string[], theme: Theme, width: number, expan
|
|
|
1117
1134
|
const hiddenCount = lines.length - visibleLines;
|
|
1118
1135
|
const hint = expanded
|
|
1119
1136
|
? `… ${hiddenCount} live-detail lines hidden`
|
|
1120
|
-
: `… ${hiddenCount} lines hidden ·
|
|
1137
|
+
: `… ${hiddenCount} lines hidden · ${liveDetailKeyText()} expands`;
|
|
1121
1138
|
return [...lines.slice(0, visibleLines), truncLine(theme.fg("dim", hint), width)];
|
|
1122
1139
|
}
|
|
1123
1140
|
|
|
@@ -1275,7 +1292,7 @@ function renderSingleCompact(d: Details, r: Details["results"][number], theme: T
|
|
|
1275
1292
|
c.addChild(new Text(truncLine(theme.fg("dim", ` ⎿ ${activity}`), width), 0, 0));
|
|
1276
1293
|
const liveStatus = buildLiveStatusLine(r.progress, progressSnapshotNow);
|
|
1277
1294
|
if (liveStatus && liveStatus !== activity) c.addChild(new Text(truncLine(theme.fg("dim", ` ${liveStatus}`), width), 0, 0));
|
|
1278
|
-
c.addChild(new Text(truncLine(theme.fg("accent",
|
|
1295
|
+
c.addChild(new Text(truncLine(theme.fg("accent", ` ${liveDetailHintText()}`), width), 0, 0));
|
|
1279
1296
|
if (r.artifactPaths) c.addChild(new Text(truncLine(theme.fg("dim", ` output: ${shortenPath(r.artifactPaths.outputPath)}`), width), 0, 0));
|
|
1280
1297
|
return c;
|
|
1281
1298
|
}
|
|
@@ -1315,7 +1332,7 @@ function renderMultiCompact(d: Details, theme: Theme, frame?: number): Component
|
|
|
1315
1332
|
}
|
|
1316
1333
|
const multiLabel = buildMultiProgressLabel(d, hasRunning);
|
|
1317
1334
|
const itemTitle = multiLabel.itemTitle;
|
|
1318
|
-
const stats = statJoin(theme, [multiLabel.headerLabel, formatProgressStats(theme, totalSummary)]);
|
|
1335
|
+
const stats = statJoin(theme, [multiLabel.headerLabel, formatProgressStats(theme, totalSummary), formatTotalCostStat(d.totalCost)]);
|
|
1319
1336
|
const glyph = hasRunning
|
|
1320
1337
|
? theme.fg("accent", runningGlyph(frame !== undefined ? (runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex) ?? 0) + frame : runningSeed(progressRunningSeed(totalSummary), d.currentStepIndex)))
|
|
1321
1338
|
: failed
|
|
@@ -1371,7 +1388,7 @@ function renderMultiCompact(d: Details, theme: Theme, frame?: number): Component
|
|
|
1371
1388
|
if (rRunning && rProg && "status" in rProg) {
|
|
1372
1389
|
const activity = compactCurrentActivity(rProg);
|
|
1373
1390
|
c.addChild(new Text(truncLine(theme.fg("dim", ` ⎿ ${activity}`), width), 0, 0));
|
|
1374
|
-
c.addChild(new Text(truncLine(theme.fg("accent",
|
|
1391
|
+
c.addChild(new Text(truncLine(theme.fg("accent", ` ${liveDetailHintText()}`), width), 0, 0));
|
|
1375
1392
|
} else if (!rPending && (r.exitCode !== 0 || r.interrupted || r.detached || hasEmptyTextOutputWithoutOutputTarget(r.task, output))) {
|
|
1376
1393
|
c.addChild(new Text(truncLine(theme.fg(r.exitCode !== 0 ? "error" : "dim", ` ⎿ ${resultStatusLine(r, output)}`), width), 0, 0));
|
|
1377
1394
|
}
|
|
@@ -1453,7 +1470,7 @@ export function renderSubagentResult(
|
|
|
1453
1470
|
if (liveStatusLine) {
|
|
1454
1471
|
c.addChild(new Text(fit(theme.fg("accent", liveStatusLine)), 0, 0));
|
|
1455
1472
|
}
|
|
1456
|
-
c.addChild(new Text(fit(theme.fg("accent",
|
|
1473
|
+
c.addChild(new Text(fit(theme.fg("accent", liveDetailHintText())), 0, 0));
|
|
1457
1474
|
if (r.artifactPaths) {
|
|
1458
1475
|
c.addChild(new Text(fit(theme.fg("dim", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
|
|
1459
1476
|
}
|
|
@@ -1547,10 +1564,13 @@ export function renderSubagentResult(
|
|
|
1547
1564
|
{ toolCount: 0, tokens: 0, durationMs: 0 },
|
|
1548
1565
|
);
|
|
1549
1566
|
|
|
1550
|
-
const
|
|
1567
|
+
const summaryParts = [
|
|
1551
1568
|
totalSummary.toolCount || totalSummary.tokens
|
|
1552
|
-
?
|
|
1553
|
-
: ""
|
|
1569
|
+
? `${totalSummary.toolCount} tools, ${formatTokens(totalSummary.tokens)} tok, ${formatDuration(totalSummary.durationMs)}`
|
|
1570
|
+
: "",
|
|
1571
|
+
formatTotalCostStat(d.totalCost),
|
|
1572
|
+
].filter(Boolean);
|
|
1573
|
+
const summaryStr = summaryParts.length ? ` | ${summaryParts.join(", ")}` : "";
|
|
1554
1574
|
|
|
1555
1575
|
const modeLabel = d.mode;
|
|
1556
1576
|
const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
|
|
@@ -1687,7 +1707,7 @@ export function renderSubagentResult(
|
|
|
1687
1707
|
if (liveStatusLine) {
|
|
1688
1708
|
c.addChild(new Text(fit(theme.fg("accent", ` ${liveStatusLine}`)), 0, 0));
|
|
1689
1709
|
}
|
|
1690
|
-
c.addChild(new Text(fit(theme.fg("accent",
|
|
1710
|
+
c.addChild(new Text(fit(theme.fg("accent", ` ${liveDetailHintText()}`)), 0, 0));
|
|
1691
1711
|
if (r.artifactPaths) {
|
|
1692
1712
|
c.addChild(new Text(fit(theme.fg("dim", ` artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
|
|
1693
1713
|
}
|