@ilya-lesikov/pi-pi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/3p/pi-ask-user/index.ts +1835 -0
- package/3p/pi-ask-user/package.json +50 -0
- package/3p/pi-ask-user/single-select-layout.ts +203 -0
- package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
- package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
- package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
- package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
- package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
- package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
- package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
- package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
- package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
- package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
- package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
- package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
- package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
- package/3p/pi-lsp/package.json +54 -0
- package/3p/pi-plannotator/AGENTS.md +583 -0
- package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
- package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
- package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
- package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
- package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
- package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
- package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
- package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
- package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
- package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
- package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
- package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
- package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
- package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
- package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
- package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
- package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
- package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
- package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
- package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
- package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
- package/3p/pi-plannotator/package.json +53 -0
- package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
- package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
- package/3p/pi-plannotator/packages/ai/context.ts +250 -0
- package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
- package/3p/pi-plannotator/packages/ai/index.ts +105 -0
- package/3p/pi-plannotator/packages/ai/package.json +21 -0
- package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
- package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
- package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
- package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
- package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
- package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
- package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/ai/types.ts +379 -0
- package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
- package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
- package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
- package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
- package/3p/pi-plannotator/packages/server/browser.ts +245 -0
- package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
- package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
- package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
- package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
- package/3p/pi-plannotator/packages/server/config.ts +8 -0
- package/3p/pi-plannotator/packages/server/draft.ts +1 -0
- package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
- package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
- package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
- package/3p/pi-plannotator/packages/server/git.ts +141 -0
- package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
- package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
- package/3p/pi-plannotator/packages/server/ide.ts +43 -0
- package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
- package/3p/pi-plannotator/packages/server/image.ts +66 -0
- package/3p/pi-plannotator/packages/server/index.ts +648 -0
- package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
- package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
- package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
- package/3p/pi-plannotator/packages/server/jj.ts +86 -0
- package/3p/pi-plannotator/packages/server/p4.ts +417 -0
- package/3p/pi-plannotator/packages/server/package.json +40 -0
- package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
- package/3p/pi-plannotator/packages/server/pr.ts +146 -0
- package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
- package/3p/pi-plannotator/packages/server/project.ts +45 -0
- package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
- package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
- package/3p/pi-plannotator/packages/server/remote.ts +74 -0
- package/3p/pi-plannotator/packages/server/repo.ts +80 -0
- package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
- package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
- package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
- package/3p/pi-plannotator/packages/server/review.ts +1318 -0
- package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
- package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
- package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
- package/3p/pi-plannotator/packages/server/storage.ts +17 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
- package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
- package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
- package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
- package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
- package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
- package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
- package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
- package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
- package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
- package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
- package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
- package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
- package/3p/pi-plannotator/packages/shared/config.ts +262 -0
- package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
- package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
- package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
- package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
- package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
- package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
- package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
- package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
- package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
- package/3p/pi-plannotator/packages/shared/package.json +55 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
- package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
- package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
- package/3p/pi-plannotator/packages/shared/project.ts +71 -0
- package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
- package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
- package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
- package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
- package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
- package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
- package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
- package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
- package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
- package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
- package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
- package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
- package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
- package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/shared/types.ts +29 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
- package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
- package/3p/pi-subagents/package.json +50 -0
- package/3p/pi-subagents/src/agent-manager.ts +413 -0
- package/3p/pi-subagents/src/agent-runner.ts +502 -0
- package/3p/pi-subagents/src/agent-types.ts +248 -0
- package/3p/pi-subagents/src/context.ts +58 -0
- package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
- package/3p/pi-subagents/src/custom-agents.ts +137 -0
- package/3p/pi-subagents/src/default-agents.ts +144 -0
- package/3p/pi-subagents/src/env.ts +33 -0
- package/3p/pi-subagents/src/group-join.ts +141 -0
- package/3p/pi-subagents/src/index.ts +1811 -0
- package/3p/pi-subagents/src/invocation-config.ts +40 -0
- package/3p/pi-subagents/src/memory.ts +165 -0
- package/3p/pi-subagents/src/model-resolver.ts +81 -0
- package/3p/pi-subagents/src/output-file.ts +77 -0
- package/3p/pi-subagents/src/prompts.ts +85 -0
- package/3p/pi-subagents/src/skill-loader.ts +79 -0
- package/3p/pi-subagents/src/types.ts +111 -0
- package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
- package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
- package/3p/pi-subagents/src/worktree.ts +162 -0
- package/3p/pi-tasks/package.json +52 -0
- package/3p/pi-tasks/src/auto-clear.ts +91 -0
- package/3p/pi-tasks/src/index.ts +1150 -0
- package/3p/pi-tasks/src/process-tracker.ts +140 -0
- package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
- package/3p/pi-tasks/src/task-store.ts +324 -0
- package/3p/pi-tasks/src/tasks-config.ts +27 -0
- package/3p/pi-tasks/src/types.ts +40 -0
- package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
- package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
- package/AGENTS.md +28 -0
- package/LICENSE +201 -0
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
- package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
- package/extensions/orchestrator/agents/explore.ts +33 -0
- package/extensions/orchestrator/agents/librarian.ts +43 -0
- package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
- package/extensions/orchestrator/agents/planner.ts +61 -0
- package/extensions/orchestrator/agents/registry.ts +156 -0
- package/extensions/orchestrator/agents/task.ts +50 -0
- package/extensions/orchestrator/agents/tool-routing.ts +84 -0
- package/extensions/orchestrator/ast-search.ts +85 -0
- package/extensions/orchestrator/cbm.ts +330 -0
- package/extensions/orchestrator/command-handlers.test.ts +163 -0
- package/extensions/orchestrator/command-handlers.ts +116 -0
- package/extensions/orchestrator/commands.test.ts +81 -0
- package/extensions/orchestrator/commands.ts +75 -0
- package/extensions/orchestrator/config.test.ts +221 -0
- package/extensions/orchestrator/config.ts +230 -0
- package/extensions/orchestrator/context.test.ts +293 -0
- package/extensions/orchestrator/context.ts +206 -0
- package/extensions/orchestrator/event-handlers.test.ts +190 -0
- package/extensions/orchestrator/event-handlers.ts +1416 -0
- package/extensions/orchestrator/exa.ts +104 -0
- package/extensions/orchestrator/flant-infra.ts +486 -0
- package/extensions/orchestrator/index.ts +80 -0
- package/extensions/orchestrator/integration.test.ts +1214 -0
- package/extensions/orchestrator/orchestrator.test.ts +252 -0
- package/extensions/orchestrator/orchestrator.ts +538 -0
- package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
- package/extensions/orchestrator/phases/brainstorm.ts +273 -0
- package/extensions/orchestrator/phases/implementation.ts +40 -0
- package/extensions/orchestrator/phases/machine.test.ts +293 -0
- package/extensions/orchestrator/phases/machine.ts +209 -0
- package/extensions/orchestrator/phases/planning.ts +255 -0
- package/extensions/orchestrator/phases/review.ts +193 -0
- package/extensions/orchestrator/plannotator.ts +56 -0
- package/extensions/orchestrator/pp-menu.ts +866 -0
- package/extensions/orchestrator/state.test.ts +343 -0
- package/extensions/orchestrator/state.ts +237 -0
- package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
- package/extensions/orchestrator/validate-artifacts.ts +272 -0
- package/extensions/orchestrator/vendor.d.ts +26 -0
- package/package.json +73 -0
- package/scripts/postinstall.sh +18 -0
|
@@ -0,0 +1,866 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "fs";
|
|
2
|
+
import { join, relative } from "path";
|
|
3
|
+
import { askUser } from "../../3p/pi-ask-user/index.js";
|
|
4
|
+
import { unregisterAgentDefinitions } from "./agents/registry.js";
|
|
5
|
+
import { loadConfig, type PiPiConfig } from "./config.js";
|
|
6
|
+
import {
|
|
7
|
+
loadBrainstormReviewOutputs,
|
|
8
|
+
loadCodeReviewOutputs,
|
|
9
|
+
loadPlanReviewOutputs,
|
|
10
|
+
} from "./context.js";
|
|
11
|
+
import { enterReviewCycle, finalizeReviewCycle } from "./event-handlers.js";
|
|
12
|
+
import { Orchestrator, deepReviewConfig } from "./orchestrator.js";
|
|
13
|
+
import { cancelPendingPlannotatorWait } from "./plannotator.js";
|
|
14
|
+
import { spawnPlanners, spawnPlanReviewers } from "./phases/planning.js";
|
|
15
|
+
import { spawnCodeReviewers } from "./phases/review.js";
|
|
16
|
+
import { spawnBrainstormReviewers } from "./phases/brainstorm.js";
|
|
17
|
+
import {
|
|
18
|
+
listTasks,
|
|
19
|
+
loadTask,
|
|
20
|
+
lockTask,
|
|
21
|
+
saveTask,
|
|
22
|
+
taskAge,
|
|
23
|
+
taskName,
|
|
24
|
+
type TaskInfo,
|
|
25
|
+
type TaskType,
|
|
26
|
+
} from "./state.js";
|
|
27
|
+
import {
|
|
28
|
+
clearFlantGeneratedConfig,
|
|
29
|
+
getFlantGeneratedConfig,
|
|
30
|
+
loadFlantSettings,
|
|
31
|
+
saveFlantSettings,
|
|
32
|
+
unregisterFlantProviders,
|
|
33
|
+
updateFlantInfra,
|
|
34
|
+
type FlantSettings,
|
|
35
|
+
} from "./flant-infra.js";
|
|
36
|
+
|
|
37
|
+
type MenuMode = "command" | "tool";
|
|
38
|
+
|
|
39
|
+
const BACK = "back" as const;
|
|
40
|
+
|
|
41
|
+
type OptionInput = string | { title: string; description?: string };
|
|
42
|
+
|
|
43
|
+
async function selectOption(ctx: any, question: string, options: OptionInput[]): Promise<string | undefined> {
|
|
44
|
+
const result = await askUser(ctx, {
|
|
45
|
+
question,
|
|
46
|
+
options,
|
|
47
|
+
allowFreeform: false,
|
|
48
|
+
allowComment: false,
|
|
49
|
+
allowMultiple: false,
|
|
50
|
+
});
|
|
51
|
+
if (!result || result.kind !== "selection") return undefined;
|
|
52
|
+
return result.selections[0];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function setStep(orchestrator: Orchestrator, step: string): void {
|
|
56
|
+
if (!orchestrator.active) return;
|
|
57
|
+
orchestrator.active.state.step = step;
|
|
58
|
+
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function showStatus(orchestrator: Orchestrator, ctx: any): void {
|
|
62
|
+
if (!orchestrator.active) {
|
|
63
|
+
ctx.ui.notify("No active task.", "info");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const cycle = orchestrator.active.state.reviewCycle
|
|
67
|
+
? ` | ReviewCycle: ${orchestrator.active.state.reviewCycle.kind}/${orchestrator.active.state.reviewCycle.step} (pass ${orchestrator.active.state.reviewCycle.pass})`
|
|
68
|
+
: "";
|
|
69
|
+
ctx.ui.notify(
|
|
70
|
+
`Type: ${orchestrator.active.type} | Phase: ${orchestrator.active.state.phase} | Step: ${orchestrator.active.state.step} | ReviewPass: ${orchestrator.active.state.reviewPass}${cycle} | Task: ${orchestrator.active.description} | Age: ${taskAge(orchestrator.active.state)} | Dir: ${orchestrator.active.dir}`,
|
|
71
|
+
"info",
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function finishTask(orchestrator: Orchestrator, ctx: any): Promise<string> {
|
|
76
|
+
if (!orchestrator.active) return "No active task.";
|
|
77
|
+
|
|
78
|
+
cancelPendingPlannotatorWait(orchestrator);
|
|
79
|
+
orchestrator.abortAllSubagents();
|
|
80
|
+
ctx.abort?.();
|
|
81
|
+
await ctx.waitForIdle?.();
|
|
82
|
+
|
|
83
|
+
const name = orchestrator.active.description;
|
|
84
|
+
const type = orchestrator.active.type;
|
|
85
|
+
const dir = orchestrator.active.dir;
|
|
86
|
+
|
|
87
|
+
orchestrator.taskDoneCompactionPending = true;
|
|
88
|
+
orchestrator.taskDoneCompactionSummary = `Task "${name}" (${type}) completed.`;
|
|
89
|
+
|
|
90
|
+
orchestrator.active.state.phase = "done";
|
|
91
|
+
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
92
|
+
unregisterAgentDefinitions(orchestrator.pi);
|
|
93
|
+
await orchestrator.cleanupActive();
|
|
94
|
+
|
|
95
|
+
const taskStore = (globalThis as any)[Symbol.for("pi-tasks:store")];
|
|
96
|
+
taskStore?.clearAll?.();
|
|
97
|
+
taskStore?.refreshWidget?.(ctx.ui);
|
|
98
|
+
|
|
99
|
+
orchestrator.updateStatus(ctx);
|
|
100
|
+
ctx.compact?.();
|
|
101
|
+
|
|
102
|
+
const urExists = existsSync(join(dir, "USER_REQUEST.md"));
|
|
103
|
+
const resExists = existsSync(join(dir, "RESEARCH.md"));
|
|
104
|
+
|
|
105
|
+
if ((type === "brainstorm" || type === "debug") && urExists && resExists) {
|
|
106
|
+
const taskRelPath = relative(join(orchestrator.cwd, ".pp", "state"), dir);
|
|
107
|
+
ctx.ui.notify(
|
|
108
|
+
`Task "${name}" completed. Artifacts saved.\nUse /pp → Implement → From and choose ${taskRelPath}`,
|
|
109
|
+
"info",
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
ctx.ui.notify(`Task "${name}" completed.`, "info");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return `Task "${name}" completed.`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function loadPhaseReviewOutputs(taskDir: string, phase: string, pass: number): { name: string; content: string }[] {
|
|
119
|
+
if (phase === "brainstorm") return loadBrainstormReviewOutputs(taskDir, pass);
|
|
120
|
+
if (phase === "plan") return loadPlanReviewOutputs(taskDir);
|
|
121
|
+
return loadCodeReviewOutputs(taskDir, pass);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function resumeTask(
|
|
125
|
+
orchestrator: Orchestrator,
|
|
126
|
+
ctx: any,
|
|
127
|
+
task: TaskInfo,
|
|
128
|
+
): Promise<{ ok: boolean; error?: string }> {
|
|
129
|
+
const pi = orchestrator.pi;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
orchestrator.config = loadConfig(orchestrator.cwd);
|
|
133
|
+
} catch (err: any) {
|
|
134
|
+
const message = `Config error: ${err.message}`;
|
|
135
|
+
ctx.ui.notify(message, "error");
|
|
136
|
+
return { ok: false, error: message };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let release: (() => Promise<void>) | null = null;
|
|
140
|
+
try {
|
|
141
|
+
release = await lockTask(task.dir, orchestrator.config.timeouts);
|
|
142
|
+
} catch {
|
|
143
|
+
const staleSeconds = Math.round(orchestrator.config.timeouts.lockStale / 1000);
|
|
144
|
+
const message =
|
|
145
|
+
`Cannot resume: task is locked by another pi session (or a session that crashed less than ${staleSeconds}s ago). ` +
|
|
146
|
+
`Wait ${staleSeconds}s for the lock to expire, or kill the other session first.`;
|
|
147
|
+
ctx.ui.notify(message, "error");
|
|
148
|
+
return { ok: false, error: message };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
orchestrator.resetTaskScopedState();
|
|
152
|
+
orchestrator.activeTaskToken++;
|
|
153
|
+
|
|
154
|
+
orchestrator.active = {
|
|
155
|
+
dir: task.dir,
|
|
156
|
+
type: task.type,
|
|
157
|
+
state: task.state,
|
|
158
|
+
release,
|
|
159
|
+
taskId: orchestrator.taskIdFromDir(task.dir),
|
|
160
|
+
modifiedFiles: new Set(task.state.modifiedFiles ?? []),
|
|
161
|
+
reviewPass: task.state.reviewPass,
|
|
162
|
+
description: task.state.description,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const modelConfig = orchestrator.config.mainModel[task.type === "debug" ? "debug" : task.type === "brainstorm" ? "brainstorm" : "implement"];
|
|
166
|
+
const modelOk = await orchestrator.switchModel(ctx, modelConfig.model, modelConfig.thinking);
|
|
167
|
+
if (!modelOk) {
|
|
168
|
+
ctx.ui.notify(`Model "${modelConfig.model}" not found — using current model`, "warning");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
orchestrator.registerAgents();
|
|
172
|
+
pi.setSessionName(orchestrator.active.description.slice(0, 50));
|
|
173
|
+
orchestrator.lastCtx = ctx;
|
|
174
|
+
orchestrator.updateStatus(ctx);
|
|
175
|
+
|
|
176
|
+
orchestrator.injectContextAndArtifacts(orchestrator.active.dir, orchestrator.active.state.phase);
|
|
177
|
+
|
|
178
|
+
if (orchestrator.active.state.phase === "plan" && orchestrator.active.state.step === "await_planners") {
|
|
179
|
+
const plansDir = join(orchestrator.active.dir, "plans");
|
|
180
|
+
const enabledVariants = Object.entries(orchestrator.config.planners).filter(([, v]) => v.enabled);
|
|
181
|
+
const planFiles = existsSync(plansDir)
|
|
182
|
+
? readdirSync(plansDir).filter((f) => f.endsWith(".md") && !f.includes("synthesized") && !f.includes("review_"))
|
|
183
|
+
: [];
|
|
184
|
+
if (planFiles.length >= enabledVariants.length) {
|
|
185
|
+
orchestrator.active.state.step = "synthesize";
|
|
186
|
+
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
187
|
+
} else {
|
|
188
|
+
const completedVariants = new Set(planFiles.map((f) => f.replace(/^\d+_/, "").replace(/\.md$/, "")));
|
|
189
|
+
const missingVariants = enabledVariants.filter(([name]) => !completedVariants.has(name));
|
|
190
|
+
if (missingVariants.length > 0) {
|
|
191
|
+
const missingConfig: Record<string, any> = {};
|
|
192
|
+
for (const [name, cfg] of missingVariants) missingConfig[name] = cfg;
|
|
193
|
+
const partialConfig = { ...orchestrator.config, planners: missingConfig };
|
|
194
|
+
orchestrator.pendingSubagentSpawns = missingVariants.length;
|
|
195
|
+
orchestrator.failedPlannerVariants = [];
|
|
196
|
+
spawnPlanners(pi, orchestrator.cwd, orchestrator.active.dir, orchestrator.active.taskId, partialConfig).then((result) => {
|
|
197
|
+
orchestrator.failedPlannerVariants = result.failedVariants;
|
|
198
|
+
if (result.spawned === 0) orchestrator.pendingSubagentSpawns = 0;
|
|
199
|
+
for (const id of result.agentIds ?? []) {
|
|
200
|
+
orchestrator.spawnedAgentIds.delete(id);
|
|
201
|
+
}
|
|
202
|
+
orchestrator.pendingSubagentSpawns = 0;
|
|
203
|
+
}).catch((err: any) => {
|
|
204
|
+
orchestrator.pendingSubagentSpawns = 0;
|
|
205
|
+
console.error(`[pi-pi] spawnPlanners failed: ${err.message}`);
|
|
206
|
+
});
|
|
207
|
+
} else {
|
|
208
|
+
orchestrator.active.state.step = "synthesize";
|
|
209
|
+
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (orchestrator.active.state.reviewCycle) {
|
|
215
|
+
const cycle = orchestrator.active.state.reviewCycle;
|
|
216
|
+
const reviewConfig = cycle.kind === "auto-deep" ? deepReviewConfig(orchestrator.config) : orchestrator.config;
|
|
217
|
+
const phase = orchestrator.active.state.phase;
|
|
218
|
+
const reviewers = phase === "brainstorm"
|
|
219
|
+
? reviewConfig.brainstormReviewers
|
|
220
|
+
: phase === "plan"
|
|
221
|
+
? reviewConfig.planReviewers
|
|
222
|
+
: reviewConfig.codeReviewers;
|
|
223
|
+
const reviewerCount = Object.values(reviewers).filter((v) => v.enabled).length;
|
|
224
|
+
|
|
225
|
+
if ((cycle.kind === "auto" || cycle.kind === "auto-deep") && (cycle.step === "spawn_reviewers" || cycle.step === "await_reviewers")) {
|
|
226
|
+
const outputs = loadPhaseReviewOutputs(orchestrator.active.dir, phase, cycle.pass);
|
|
227
|
+
if (outputs.length >= reviewerCount) {
|
|
228
|
+
cycle.step = "apply_feedback";
|
|
229
|
+
orchestrator.active.state.step = "apply_feedback";
|
|
230
|
+
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
231
|
+
const rendered = outputs.map((o) => `=== ${o.name} ===\n${o.content}`).join("\n\n");
|
|
232
|
+
pi.sendMessage(
|
|
233
|
+
{
|
|
234
|
+
customType: "pp-review-ready",
|
|
235
|
+
content: `[PI-PI] Reviewer outputs are ready.\n\n${rendered}`,
|
|
236
|
+
display: false,
|
|
237
|
+
},
|
|
238
|
+
{ deliverAs: "steer" },
|
|
239
|
+
);
|
|
240
|
+
} else {
|
|
241
|
+
const completedVariants = new Set(
|
|
242
|
+
outputs.map((o) => o.name.replace(/^\d+_/, "").replace(/_round-\d+\.md$/, "").replace(/\.md$/, "")),
|
|
243
|
+
);
|
|
244
|
+
const enabledVariants = Object.entries(reviewers).filter(([, v]) => v.enabled);
|
|
245
|
+
const missingVariants = enabledVariants.filter(([name]) => !completedVariants.has(name));
|
|
246
|
+
|
|
247
|
+
if (missingVariants.length === 0) {
|
|
248
|
+
cycle.step = "apply_feedback";
|
|
249
|
+
orchestrator.active.state.step = "apply_feedback";
|
|
250
|
+
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
251
|
+
const rendered = outputs.map((o) => `=== ${o.name} ===\n${o.content}`).join("\n\n");
|
|
252
|
+
pi.sendMessage(
|
|
253
|
+
{
|
|
254
|
+
customType: "pp-review-ready",
|
|
255
|
+
content: `[PI-PI] Reviewer outputs are ready.\n\n${rendered}`,
|
|
256
|
+
display: false,
|
|
257
|
+
},
|
|
258
|
+
{ deliverAs: "steer" },
|
|
259
|
+
);
|
|
260
|
+
} else {
|
|
261
|
+
const missingReviewerConfig: Record<string, any> = {};
|
|
262
|
+
for (const [name, cfg] of missingVariants) missingReviewerConfig[name] = cfg;
|
|
263
|
+
const partialConfig = phase === "brainstorm"
|
|
264
|
+
? { ...reviewConfig, brainstormReviewers: missingReviewerConfig }
|
|
265
|
+
: phase === "plan"
|
|
266
|
+
? { ...reviewConfig, planReviewers: missingReviewerConfig }
|
|
267
|
+
: { ...reviewConfig, codeReviewers: missingReviewerConfig };
|
|
268
|
+
orchestrator.pendingSubagentSpawns = missingVariants.length;
|
|
269
|
+
const spawnFn = phase === "brainstorm"
|
|
270
|
+
? () => spawnBrainstormReviewers(pi, orchestrator.cwd, orchestrator.active!.dir, orchestrator.active!.taskId, partialConfig, cycle.pass)
|
|
271
|
+
: phase === "plan"
|
|
272
|
+
? () => spawnPlanReviewers(pi, orchestrator.cwd, orchestrator.active!.dir, orchestrator.active!.taskId, partialConfig)
|
|
273
|
+
: () => spawnCodeReviewers(pi, orchestrator.cwd, orchestrator.active!.dir, orchestrator.active!.taskId, partialConfig, cycle.pass);
|
|
274
|
+
orchestrator.failedReviewerVariants = [];
|
|
275
|
+
spawnFn().then((result) => {
|
|
276
|
+
orchestrator.failedReviewerVariants = result.failedVariants;
|
|
277
|
+
if (result.spawned === 0) orchestrator.pendingSubagentSpawns = 0;
|
|
278
|
+
for (const id of result.agentIds ?? []) {
|
|
279
|
+
orchestrator.spawnedAgentIds.delete(id);
|
|
280
|
+
}
|
|
281
|
+
orchestrator.pendingSubagentSpawns = 0;
|
|
282
|
+
}).catch((err: any) => {
|
|
283
|
+
orchestrator.pendingSubagentSpawns = 0;
|
|
284
|
+
console.error(`[pi-pi] spawn reviewers failed: ${err.message}`);
|
|
285
|
+
});
|
|
286
|
+
cycle.step = "await_reviewers";
|
|
287
|
+
orchestrator.active.state.step = "await_reviewers";
|
|
288
|
+
saveTask(orchestrator.active.dir, orchestrator.active.state);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} else if (cycle.step === "apply_feedback") {
|
|
292
|
+
const outputs = loadPhaseReviewOutputs(orchestrator.active.dir, phase, cycle.pass);
|
|
293
|
+
const rendered = outputs.map((o) => `=== ${o.name} ===\n${o.content}`).join("\n\n");
|
|
294
|
+
pi.sendMessage(
|
|
295
|
+
{
|
|
296
|
+
customType: "pp-review-ready",
|
|
297
|
+
content: `[PI-PI] Review cycle is in apply_feedback step.\n\n${rendered}`,
|
|
298
|
+
display: false,
|
|
299
|
+
},
|
|
300
|
+
{ deliverAs: "steer" },
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const step = orchestrator.active.state.step;
|
|
306
|
+
if (step === "await_planners" || step === "await_reviewers") {
|
|
307
|
+
ctx.ui.notify(`Resumed task. Awaiting subagents (${step}).`, "info");
|
|
308
|
+
} else if (step === "apply_feedback") {
|
|
309
|
+
pi.sendUserMessage(`[PI-PI] Resumed ${orchestrator.active.state.phase} phase. Read reviewer outputs and apply feedback.`);
|
|
310
|
+
} else {
|
|
311
|
+
pi.sendUserMessage(`[PI-PI] Resumed ${orchestrator.active.state.phase} phase. Continue working.`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return { ok: true };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function listCompletedFromTasks(cwd: string): TaskInfo[] {
|
|
318
|
+
const paused = new Set<string>([
|
|
319
|
+
...listTasks(cwd, "brainstorm").map((t) => t.dir),
|
|
320
|
+
...listTasks(cwd, "debug").map((t) => t.dir),
|
|
321
|
+
]);
|
|
322
|
+
const results: TaskInfo[] = [];
|
|
323
|
+
|
|
324
|
+
for (const type of ["brainstorm", "debug"] as TaskType[]) {
|
|
325
|
+
const typeDir = join(cwd, ".pp", "state", type);
|
|
326
|
+
if (!existsSync(typeDir)) continue;
|
|
327
|
+
for (const entry of readdirSync(typeDir, { withFileTypes: true })) {
|
|
328
|
+
if (!entry.isDirectory()) continue;
|
|
329
|
+
const dir = join(typeDir, entry.name);
|
|
330
|
+
if (paused.has(dir)) continue;
|
|
331
|
+
try {
|
|
332
|
+
const state = loadTask(dir);
|
|
333
|
+
if (state.phase !== "done") continue;
|
|
334
|
+
if (!existsSync(join(dir, "USER_REQUEST.md")) || !existsSync(join(dir, "RESEARCH.md"))) continue;
|
|
335
|
+
results.push({ dir, state, type });
|
|
336
|
+
} catch {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
results.sort((a, b) => {
|
|
343
|
+
const aTime = a.state.startedAt ? new Date(a.state.startedAt).getTime() : 0;
|
|
344
|
+
const bTime = b.state.startedAt ? new Date(b.state.startedAt).getTime() : 0;
|
|
345
|
+
return bTime - aTime;
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
return results;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function showSubagentsMenu(ctx: any): Promise<void> {
|
|
352
|
+
const api = (globalThis as any)[Symbol.for("pi-subagents:menu")] as { showMenu?: (menuCtx: any) => Promise<void> } | undefined;
|
|
353
|
+
if (!api?.showMenu) {
|
|
354
|
+
ctx.ui.notify("Subagents menu API is not available.", "warning");
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
await api.showMenu(ctx);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function showLspMenu(ctx: any): Promise<typeof BACK> {
|
|
361
|
+
while (true) {
|
|
362
|
+
const choice = await selectOption(ctx, "LSP", [
|
|
363
|
+
{ title: "Status", description: "Show detected language servers and their state" },
|
|
364
|
+
{ title: "Restart", description: "Stop all servers. They reinitialize on next use" },
|
|
365
|
+
{ title: "Back", description: "Return to the previous menu" },
|
|
366
|
+
]);
|
|
367
|
+
if (!choice || choice === "Back") return BACK;
|
|
368
|
+
|
|
369
|
+
const api = (globalThis as any)[Symbol.for("pi-lsp:api")] as {
|
|
370
|
+
status?: (menuCtx: any) => Promise<void>;
|
|
371
|
+
restart?: (menuCtx: any) => Promise<void>;
|
|
372
|
+
} | undefined;
|
|
373
|
+
|
|
374
|
+
if (!api?.status || !api?.restart) {
|
|
375
|
+
ctx.ui.notify("LSP API is not available.", "warning");
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (choice === "Status") {
|
|
380
|
+
await api.status(ctx);
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
await api.restart(ctx);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function countFlantProviders(settings: FlantSettings): { anthropic: number; openai: number } {
|
|
389
|
+
const models = settings.cachedFlantModels ?? [];
|
|
390
|
+
const anthropic = models.filter((m) => m.startsWith("claude-")).length;
|
|
391
|
+
return { anthropic, openai: Math.max(0, models.length - anthropic) };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function collectRoleAssignments(config: Partial<PiPiConfig> | null): string[] {
|
|
395
|
+
if (!config) return [];
|
|
396
|
+
const out: string[] = [];
|
|
397
|
+
const add = (key: string, value: string | undefined) => {
|
|
398
|
+
if (typeof value === "string" && value.length > 0) out.push(`${key} = ${value}`);
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
add("mainModel.implement", config.mainModel?.implement?.model);
|
|
402
|
+
add("mainModel.debug", config.mainModel?.debug?.model);
|
|
403
|
+
add("mainModel.brainstorm", config.mainModel?.brainstorm?.model);
|
|
404
|
+
|
|
405
|
+
for (const [name, variant] of Object.entries(config.planners ?? {})) {
|
|
406
|
+
if (variant.enabled) add(`planners.${name}`, variant.model);
|
|
407
|
+
}
|
|
408
|
+
for (const [name, variant] of Object.entries(config.planReviewers ?? {})) {
|
|
409
|
+
if (variant.enabled) add(`planReviewers.${name}`, variant.model);
|
|
410
|
+
}
|
|
411
|
+
for (const [name, variant] of Object.entries(config.codeReviewers ?? {})) {
|
|
412
|
+
if (variant.enabled) add(`codeReviewers.${name}`, variant.model);
|
|
413
|
+
}
|
|
414
|
+
for (const [name, variant] of Object.entries(config.brainstormReviewers ?? {})) {
|
|
415
|
+
if (variant.enabled) add(`brainstormReviewers.${name}`, variant.model);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
add("agents.explore", config.agents?.explore?.model);
|
|
419
|
+
add("agents.librarian", config.agents?.librarian?.model);
|
|
420
|
+
add("agents.task", config.agents?.task?.model);
|
|
421
|
+
return out;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function flantStatusText(settings: FlantSettings): string {
|
|
425
|
+
const providers = countFlantProviders(settings);
|
|
426
|
+
const assignments = collectRoleAssignments(getFlantGeneratedConfig());
|
|
427
|
+
const lines = [
|
|
428
|
+
`Enabled: ${settings.enabled ? "yes" : "no"}`,
|
|
429
|
+
`Auto-update: ${settings.autoUpdate ? "yes" : "no"}`,
|
|
430
|
+
`Last updated: ${settings.lastUpdated ?? "never"}`,
|
|
431
|
+
`Providers: pp-flant-anthropic (${providers.anthropic} models), pp-flant-openai (${providers.openai} models)`,
|
|
432
|
+
];
|
|
433
|
+
if (assignments.length === 0) {
|
|
434
|
+
lines.push("Role assignments: none");
|
|
435
|
+
} else {
|
|
436
|
+
lines.push("Role assignments:");
|
|
437
|
+
for (const assignment of assignments) {
|
|
438
|
+
lines.push(`- ${assignment}`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return lines.join("\n");
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function describeUpdateResult(result: { ok: boolean; error?: string; models?: string[] }): { text: string; kind: "info" | "warning" | "error" } {
|
|
445
|
+
if (!result.ok) {
|
|
446
|
+
return { text: `Flant update failed: ${result.error ?? "unknown error"}`, kind: "error" };
|
|
447
|
+
}
|
|
448
|
+
const models = result.models ?? [];
|
|
449
|
+
const anthropic = models.filter((m) => m.startsWith("claude-")).length;
|
|
450
|
+
const openai = Math.max(0, models.length - anthropic);
|
|
451
|
+
return {
|
|
452
|
+
text: `Flant update completed: ${models.length} models (pp-flant-anthropic: ${anthropic}, pp-flant-openai: ${openai}).`,
|
|
453
|
+
kind: "info",
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async function showFlantInfraMenu(orchestrator: Orchestrator, ctx: any): Promise<typeof BACK> {
|
|
458
|
+
while (true) {
|
|
459
|
+
const settings = loadFlantSettings();
|
|
460
|
+
const enableLabel = `Enable: ${settings.enabled ? "ON" : "OFF"}`;
|
|
461
|
+
const options: OptionInput[] = [enableLabel];
|
|
462
|
+
if (settings.enabled) {
|
|
463
|
+
options.push(
|
|
464
|
+
`Auto-update on startup: ${settings.autoUpdate ? "ON" : "OFF"}`,
|
|
465
|
+
`Cache period: ${settings.cacheTTLDays} ${settings.cacheTTLDays === 1 ? "day" : "days"}`,
|
|
466
|
+
"Update now",
|
|
467
|
+
"Current status",
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
options.push({ title: "Back", description: "Return to the previous menu" });
|
|
471
|
+
|
|
472
|
+
const choice = await selectOption(ctx, "Flant AI Infrastructure", options);
|
|
473
|
+
if (!choice || choice === "Back") return BACK;
|
|
474
|
+
|
|
475
|
+
if (choice === enableLabel) {
|
|
476
|
+
if (settings.enabled) {
|
|
477
|
+
const next = { ...settings, enabled: false };
|
|
478
|
+
saveFlantSettings(next);
|
|
479
|
+
unregisterFlantProviders(orchestrator.pi);
|
|
480
|
+
clearFlantGeneratedConfig();
|
|
481
|
+
ctx.ui.notify("Flant AI Infrastructure disabled.", "info");
|
|
482
|
+
} else {
|
|
483
|
+
if (!process.env.FLANT_API_KEY) {
|
|
484
|
+
ctx.ui.notify("Set FLANT_API_KEY environment variable first.", "warning");
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
const next = { ...settings, enabled: true };
|
|
488
|
+
saveFlantSettings(next);
|
|
489
|
+
const result = await updateFlantInfra(orchestrator.pi);
|
|
490
|
+
const message = describeUpdateResult(result);
|
|
491
|
+
ctx.ui.notify(message.text, message.kind);
|
|
492
|
+
}
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (choice.startsWith("Auto-update on startup:")) {
|
|
497
|
+
saveFlantSettings({ ...settings, autoUpdate: !settings.autoUpdate });
|
|
498
|
+
ctx.ui.notify(`Auto-update on startup: ${!settings.autoUpdate ? "ON" : "OFF"}`, "info");
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (choice.startsWith("Cache period:")) {
|
|
503
|
+
const selected = await selectOption(ctx, "Cache period", ["1 day", "3 days", "7 days", "14 days", "30 days", "Back"]);
|
|
504
|
+
if (!selected || selected === "Back") continue;
|
|
505
|
+
const days = Number(selected.split(" ")[0]);
|
|
506
|
+
if (!Number.isFinite(days) || days <= 0) continue;
|
|
507
|
+
saveFlantSettings({ ...settings, cacheTTLDays: days });
|
|
508
|
+
ctx.ui.notify(`Cache period set to ${days} ${days === 1 ? "day" : "days"}.`, "info");
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (choice === "Update now") {
|
|
513
|
+
const result = await updateFlantInfra(orchestrator.pi);
|
|
514
|
+
const message = describeUpdateResult(result);
|
|
515
|
+
ctx.ui.notify(message.text, message.kind);
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
ctx.ui.notify(flantStatusText(settings), "info");
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async function showSettingsMenu(orchestrator: Orchestrator, ctx: any): Promise<typeof BACK> {
|
|
524
|
+
while (true) {
|
|
525
|
+
const choice = await selectOption(ctx, "Settings", [
|
|
526
|
+
{ title: "Flant AI Infrastructure", description: "Configure corporate AI model provider" },
|
|
527
|
+
{ title: "Back", description: "Return to the previous menu" },
|
|
528
|
+
]);
|
|
529
|
+
if (!choice || choice === "Back") return BACK;
|
|
530
|
+
await showFlantInfraMenu(orchestrator, ctx);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
async function promptDescription(ctx: any, prompt: string, fallback: string): Promise<string | undefined> {
|
|
535
|
+
const value = await ctx.ui.input(prompt);
|
|
536
|
+
if (value === undefined || value === null) return undefined;
|
|
537
|
+
const trimmed = String(value).trim();
|
|
538
|
+
return trimmed || fallback;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function resumeOptionTitle(t: TaskInfo): string {
|
|
542
|
+
return taskName(t.dir);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function resumeOptionDescription(t: TaskInfo): string {
|
|
546
|
+
const age = taskAge(t.state);
|
|
547
|
+
const phase = t.state.phase === t.type ? t.type : `${t.type}/${t.state.phase}`;
|
|
548
|
+
return `${phase}, ${age}`;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async function showResumeMenu(
|
|
552
|
+
orchestrator: Orchestrator,
|
|
553
|
+
ctx: any,
|
|
554
|
+
type: TaskType | undefined,
|
|
555
|
+
emptyMessage: string,
|
|
556
|
+
): Promise<typeof BACK | "started"> {
|
|
557
|
+
while (true) {
|
|
558
|
+
const tasks = listTasks(orchestrator.cwd, type);
|
|
559
|
+
if (tasks.length === 0) {
|
|
560
|
+
ctx.ui.notify(emptyMessage, "info");
|
|
561
|
+
return BACK;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const options: OptionInput[] = tasks.map((t) => ({
|
|
565
|
+
title: resumeOptionTitle(t),
|
|
566
|
+
description: resumeOptionDescription(t),
|
|
567
|
+
}));
|
|
568
|
+
options.push({ title: "Back", description: "Return to the previous menu" });
|
|
569
|
+
|
|
570
|
+
const choice = await selectOption(ctx, "Resume", options);
|
|
571
|
+
if (!choice || choice === "Back") return BACK;
|
|
572
|
+
|
|
573
|
+
const task = tasks.find((t) => resumeOptionTitle(t) === choice);
|
|
574
|
+
if (!task) continue;
|
|
575
|
+
const result = await resumeTask(orchestrator, ctx, task);
|
|
576
|
+
if (result.ok) return "started";
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function fromOptionTitle(t: TaskInfo): string {
|
|
581
|
+
return taskName(t.dir);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function fromOptionDescription(t: TaskInfo, cwd: string): string {
|
|
585
|
+
const age = taskAge(t.state);
|
|
586
|
+
const rel = relative(join(cwd, ".pp", "state"), t.dir);
|
|
587
|
+
return `${t.type}, ${age} — ${rel}`;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async function showFromMenu(orchestrator: Orchestrator, ctx: any): Promise<typeof BACK | "started"> {
|
|
591
|
+
while (true) {
|
|
592
|
+
const tasks = listCompletedFromTasks(orchestrator.cwd);
|
|
593
|
+
if (tasks.length === 0) {
|
|
594
|
+
ctx.ui.notify("No completed brainstorm/debug tasks with artifacts found.", "info");
|
|
595
|
+
return BACK;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const options: OptionInput[] = tasks.map((t) => ({
|
|
599
|
+
title: fromOptionTitle(t),
|
|
600
|
+
description: fromOptionDescription(t, orchestrator.cwd),
|
|
601
|
+
}));
|
|
602
|
+
options.push({ title: "Back", description: "Return to the previous menu" });
|
|
603
|
+
|
|
604
|
+
const choice = await selectOption(ctx, "From", options);
|
|
605
|
+
if (!choice || choice === "Back") return BACK;
|
|
606
|
+
|
|
607
|
+
const selected = tasks.find((t) => fromOptionTitle(t) === choice);
|
|
608
|
+
if (!selected) continue;
|
|
609
|
+
|
|
610
|
+
const description = await promptDescription(ctx, "Describe the task", "implement");
|
|
611
|
+
if (!description) continue;
|
|
612
|
+
|
|
613
|
+
await orchestrator.startTask(ctx, "implement", description, selected.dir, true);
|
|
614
|
+
return "started";
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
async function showImplementMenu(orchestrator: Orchestrator, ctx: any): Promise<typeof BACK | "started"> {
|
|
619
|
+
while (true) {
|
|
620
|
+
const choice = await selectOption(ctx, "Implement", [
|
|
621
|
+
{ title: "New", description: "Start a new implementation from scratch" },
|
|
622
|
+
{ title: "From", description: "Continue from a completed brainstorm or debug task" },
|
|
623
|
+
{ title: "Resume", description: "Resume a paused implementation" },
|
|
624
|
+
{ title: "Back", description: "Return to the previous menu" },
|
|
625
|
+
]);
|
|
626
|
+
if (!choice || choice === "Back") return BACK;
|
|
627
|
+
|
|
628
|
+
if (choice === "New") {
|
|
629
|
+
const description = await promptDescription(ctx, "Describe the task", "implement");
|
|
630
|
+
if (!description) continue;
|
|
631
|
+
await orchestrator.startTask(ctx, "implement", description);
|
|
632
|
+
return "started";
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (choice === "From") {
|
|
636
|
+
const result = await showFromMenu(orchestrator, ctx);
|
|
637
|
+
if (result === "started") return result;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
const result = await showResumeMenu(orchestrator, ctx, "implement", "No paused implement tasks found.");
|
|
642
|
+
if (result === "started") return result;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async function showTaskTypeMenu(
|
|
647
|
+
orchestrator: Orchestrator,
|
|
648
|
+
ctx: any,
|
|
649
|
+
type: TaskType,
|
|
650
|
+
inputPrompt: string,
|
|
651
|
+
): Promise<typeof BACK | "started"> {
|
|
652
|
+
while (true) {
|
|
653
|
+
const choice = await selectOption(ctx, type.charAt(0).toUpperCase() + type.slice(1), [
|
|
654
|
+
{ title: "New", description: "Start a new session" },
|
|
655
|
+
{ title: "Resume", description: "Resume a paused session" },
|
|
656
|
+
{ title: "Back", description: "Return to the previous menu" },
|
|
657
|
+
]);
|
|
658
|
+
if (!choice || choice === "Back") return BACK;
|
|
659
|
+
|
|
660
|
+
if (choice === "New") {
|
|
661
|
+
const description = await promptDescription(ctx, inputPrompt, type);
|
|
662
|
+
if (!description) continue;
|
|
663
|
+
await orchestrator.startTask(ctx, type, description);
|
|
664
|
+
return "started";
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const result = await showResumeMenu(orchestrator, ctx, type, `No paused ${type} tasks found.`);
|
|
668
|
+
if (result === "started") return result;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async function showNoActiveMenu(orchestrator: Orchestrator, ctx: any): Promise<string | undefined> {
|
|
673
|
+
while (true) {
|
|
674
|
+
const choice = await selectOption(ctx, "/pp", [
|
|
675
|
+
{ title: "Debug", description: "Diagnose an issue. Then (optionally) fix it" },
|
|
676
|
+
{ title: "Brainstorm", description: "Explore and brainstorm. Then (optionally) plan and implement" },
|
|
677
|
+
{ title: "Implement", description: "Brainstorm, plan and implement" },
|
|
678
|
+
{ title: "Resume", description: "Resume a previously unfinished task" },
|
|
679
|
+
{ title: "Subagents", description: "Manage running agents" },
|
|
680
|
+
{ title: "LSP", description: "Language server status and controls" },
|
|
681
|
+
{ title: "Settings", description: "Configure pi-pi extension" },
|
|
682
|
+
{ title: "Close", description: "Close this menu" },
|
|
683
|
+
]);
|
|
684
|
+
if (!choice || choice === "Close") return undefined;
|
|
685
|
+
|
|
686
|
+
if (choice === "Debug") {
|
|
687
|
+
const result = await showTaskTypeMenu(orchestrator, ctx, "debug", "Describe the task");
|
|
688
|
+
if (result === "started") return undefined;
|
|
689
|
+
continue;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (choice === "Brainstorm") {
|
|
693
|
+
const result = await showTaskTypeMenu(orchestrator, ctx, "brainstorm", "Describe the task");
|
|
694
|
+
if (result === "started") return undefined;
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (choice === "Implement") {
|
|
699
|
+
const result = await showImplementMenu(orchestrator, ctx);
|
|
700
|
+
if (result === "started") return undefined;
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (choice === "Resume") {
|
|
705
|
+
const result = await showResumeMenu(orchestrator, ctx, undefined, "No paused tasks found.");
|
|
706
|
+
if (result === "started") return undefined;
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
if (choice === "Subagents") {
|
|
711
|
+
await showSubagentsMenu(ctx);
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
if (choice === "LSP") {
|
|
716
|
+
await showLspMenu(ctx);
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
await showSettingsMenu(orchestrator, ctx);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function getReviewLabels(orchestrator: Orchestrator): { autoLabel: string; deepLabel: string } {
|
|
725
|
+
const byKind = orchestrator.active?.state.reviewPassByKind ?? {};
|
|
726
|
+
const autoCount = byKind["auto"] ?? 0;
|
|
727
|
+
const deepCount = byKind["auto-deep"] ?? 0;
|
|
728
|
+
const autoLabel = autoCount > 0 ? `Auto review (pass ${autoCount + 1})` : "Auto review";
|
|
729
|
+
const deepLabel = deepCount > 0 ? `Auto deep review (pass ${deepCount + 1})` : "Auto deep review";
|
|
730
|
+
return { autoLabel, deepLabel };
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function hasEnabledReviewers(orchestrator: Orchestrator, kind: "auto" | "auto-deep"): boolean {
|
|
734
|
+
if (!orchestrator.active) return false;
|
|
735
|
+
const phase = orchestrator.active.state.phase;
|
|
736
|
+
const config = kind === "auto-deep" ? deepReviewConfig(orchestrator.config) : orchestrator.config;
|
|
737
|
+
const reviewers = phase === "brainstorm"
|
|
738
|
+
? config.brainstormReviewers
|
|
739
|
+
: phase === "plan"
|
|
740
|
+
? config.planReviewers
|
|
741
|
+
: config.codeReviewers;
|
|
742
|
+
return Object.values(reviewers).some((v) => v.enabled);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function handleReviewResult(ctx: any, text: string): { continueLoop: boolean; text?: string } {
|
|
746
|
+
if (text.includes("Choose another option.") || text === "Plannotator approved the plan. Choose next action.") {
|
|
747
|
+
ctx.ui.notify(text, "info");
|
|
748
|
+
return { continueLoop: true };
|
|
749
|
+
}
|
|
750
|
+
return { continueLoop: false, text };
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export async function showActiveTaskMenu(
|
|
754
|
+
orchestrator: Orchestrator,
|
|
755
|
+
ctx: any,
|
|
756
|
+
summary: string,
|
|
757
|
+
mode: MenuMode = "command",
|
|
758
|
+
): Promise<string> {
|
|
759
|
+
const continueMessage = "User wants to continue. Run /pp when ready to advance.";
|
|
760
|
+
|
|
761
|
+
while (true) {
|
|
762
|
+
if (!orchestrator.active) return "No active task.";
|
|
763
|
+
|
|
764
|
+
const task = orchestrator.active;
|
|
765
|
+
const phase = task.state.phase;
|
|
766
|
+
const step = task.state.step;
|
|
767
|
+
|
|
768
|
+
const waiting = step === "await_planners" || step === "await_reviewers";
|
|
769
|
+
const { autoLabel, deepLabel } = getReviewLabels(orchestrator);
|
|
770
|
+
const hasPlannotator = phase === "plan" || phase === "implement";
|
|
771
|
+
const canFinishPhase = (phase === "brainstorm" && task.type === "brainstorm") || phase === "debug";
|
|
772
|
+
|
|
773
|
+
const opt = (title: string, description: string): OptionInput => ({ title, description });
|
|
774
|
+
|
|
775
|
+
const options: OptionInput[] = [];
|
|
776
|
+
if (!waiting) {
|
|
777
|
+
options.push(opt("Approve & continue", "Advance to the next phase"));
|
|
778
|
+
options.push(opt(autoLabel, "Run automated review with configured reviewers"));
|
|
779
|
+
options.push(opt(deepLabel, "Run automated review with higher thinking level"));
|
|
780
|
+
if (hasPlannotator) {
|
|
781
|
+
options.push(opt("Review in Plannotator", "Open visual review in browser"));
|
|
782
|
+
options.push(opt("Review on my own", "Review manually, then continue"));
|
|
783
|
+
}
|
|
784
|
+
options.push(opt("Back to prompt", "Return to the prompt and keep working"));
|
|
785
|
+
if (canFinishPhase) {
|
|
786
|
+
options.push(opt("Finish", "Complete this phase and end the task"));
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
options.push(opt("Abort", "Stop the task without completing"));
|
|
790
|
+
options.push(opt("Status", "Show current task phase, step, and timing"));
|
|
791
|
+
options.push(opt("Subagents", "Manage running agents"));
|
|
792
|
+
options.push(opt("LSP", "Language server status and controls"));
|
|
793
|
+
|
|
794
|
+
const choice = await selectOption(ctx, summary, options);
|
|
795
|
+
if (!choice) return mode === "tool" ? "No action selected." : "";
|
|
796
|
+
|
|
797
|
+
if (choice === "Status") {
|
|
798
|
+
showStatus(orchestrator, ctx);
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
if (choice === "Subagents") {
|
|
802
|
+
await showSubagentsMenu(ctx);
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
if (choice === "LSP") {
|
|
806
|
+
await showLspMenu(ctx);
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
if (choice === "Abort" || choice === "Finish") {
|
|
810
|
+
const text = await finishTask(orchestrator, ctx);
|
|
811
|
+
return mode === "tool" ? text : "";
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
finalizeReviewCycle(task);
|
|
815
|
+
|
|
816
|
+
if (choice === "Approve & continue") {
|
|
817
|
+
const result = await orchestrator.transitionToNextPhase(ctx);
|
|
818
|
+
if (!result.ok) return `Transition blocked: ${result.error}`;
|
|
819
|
+
if (orchestrator.phaseCompactionPending || orchestrator.taskDoneCompactionPending) return "";
|
|
820
|
+
const curStep = orchestrator.active?.state.step;
|
|
821
|
+
if (curStep === "await_planners" || curStep === "await_reviewers") return "";
|
|
822
|
+
return "";
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (choice === autoLabel || choice === deepLabel || choice === "Review in Plannotator") {
|
|
826
|
+
const kind = choice === autoLabel ? "auto" as const : choice === deepLabel ? "auto-deep" as const : "plannotator" as const;
|
|
827
|
+
if (kind !== "plannotator" && !hasEnabledReviewers(orchestrator, kind)) {
|
|
828
|
+
const label = phase === "brainstorm" ? "brainstorm" : phase === "plan" ? "plan" : "code";
|
|
829
|
+
ctx.ui.notify(`No ${label} reviewers enabled.`, "info");
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
const text = await enterReviewCycle(orchestrator, ctx, kind);
|
|
833
|
+
const curStep = orchestrator.active?.state.step;
|
|
834
|
+
if (curStep === "await_reviewers") return "";
|
|
835
|
+
const handled = handleReviewResult(ctx, text);
|
|
836
|
+
if (handled.continueLoop) continue;
|
|
837
|
+
return handled.text ?? text;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if (choice === "Review on my own") {
|
|
841
|
+
if (phase === "plan") {
|
|
842
|
+
setStep(orchestrator, "synthesize");
|
|
843
|
+
} else {
|
|
844
|
+
setStep(orchestrator, "llm_work");
|
|
845
|
+
}
|
|
846
|
+
return continueMessage;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
if (choice === "Back to prompt") {
|
|
850
|
+
if (phase === "plan") {
|
|
851
|
+
setStep(orchestrator, "synthesize");
|
|
852
|
+
} else {
|
|
853
|
+
setStep(orchestrator, "llm_work");
|
|
854
|
+
}
|
|
855
|
+
return continueMessage;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
export async function showPpMenu(orchestrator: Orchestrator, ctx: any, mode: MenuMode = "command"): Promise<string | undefined> {
|
|
861
|
+
if (!orchestrator.active) {
|
|
862
|
+
return showNoActiveMenu(orchestrator, ctx);
|
|
863
|
+
}
|
|
864
|
+
const text = await showActiveTaskMenu(orchestrator, ctx, "Choose next action", mode);
|
|
865
|
+
return text || undefined;
|
|
866
|
+
}
|