@huseynsnmz/pi-suite 0.0.0-stage → 0.2.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/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +88 -2
- package/SECURITY.md +5 -0
- package/package.json +112 -4
- package/packages/board/CHANGELOG.md +340 -0
- package/packages/board/CONTRIBUTING.md +7 -0
- package/packages/board/LICENSE +21 -0
- package/packages/board/README.md +88 -0
- package/packages/board/SECURITY.md +19 -0
- package/packages/board/docs/how-it-works.md +31 -0
- package/packages/board/docs/reference.md +278 -0
- package/packages/board/package.json +81 -0
- package/packages/board/runner/job-runner-legacy.mjs +68 -0
- package/packages/board/runner/job-runner.mjs +834 -0
- package/packages/board/runner/pty-runner-legacy.mjs +50 -0
- package/packages/board/runner/pty-runner.mjs +2175 -0
- package/packages/board/runner/state-coordinator.mjs +673 -0
- package/packages/board/runner/state-runner.mjs +203 -0
- package/packages/board/runner/title-runner.mjs +80 -0
- package/packages/board/src/commands/agent-board.ts +394 -0
- package/packages/board/src/commands/attach-decision.mjs +66 -0
- package/packages/board/src/commands/attach-flow.ts +251 -0
- package/packages/board/src/commands/bg.ts +103 -0
- package/packages/board/src/commands/board-model.ts +21 -0
- package/packages/board/src/core/atomic.mjs +184 -0
- package/packages/board/src/core/attach-delete.mjs +24 -0
- package/packages/board/src/core/attach-frame-proof.mjs +870 -0
- package/packages/board/src/core/attach-frame-reporter.mjs +648 -0
- package/packages/board/src/core/attach-frame-stream.mjs +266 -0
- package/packages/board/src/core/attach-view-proof.mjs +709 -0
- package/packages/board/src/core/auto-state.mjs +377 -0
- package/packages/board/src/core/background-runs.mjs +273 -0
- package/packages/board/src/core/code-refs-store.mjs +315 -0
- package/packages/board/src/core/code-refs.mjs +913 -0
- package/packages/board/src/core/config.mjs +77 -0
- package/packages/board/src/core/control-protocol.mjs +482 -0
- package/packages/board/src/core/coordinator-client.mjs +633 -0
- package/packages/board/src/core/coordinator-journal.mjs +282 -0
- package/packages/board/src/core/coordinator-protocol.mjs +14 -0
- package/packages/board/src/core/cwd-stats.mjs +114 -0
- package/packages/board/src/core/dashboard-render.mjs +10 -0
- package/packages/board/src/core/derive.mjs +117 -0
- package/packages/board/src/core/diagnostics.mjs +133 -0
- package/packages/board/src/core/double-press.mjs +26 -0
- package/packages/board/src/core/editor-state-reporter.mjs +112 -0
- package/packages/board/src/core/events.mjs +286 -0
- package/packages/board/src/core/evidence.mjs +245 -0
- package/packages/board/src/core/follow-up-queue.mjs +211 -0
- package/packages/board/src/core/foreground-preview-cache.mjs +117 -0
- package/packages/board/src/core/foreground-projection-cache.mjs +99 -0
- package/packages/board/src/core/frame-witness-update.mjs +35 -0
- package/packages/board/src/core/heuristics.mjs +313 -0
- package/packages/board/src/core/hold-input.mjs +27 -0
- package/packages/board/src/core/host-coordination.mjs +300 -0
- package/packages/board/src/core/host-crash.mjs +79 -0
- package/packages/board/src/core/host-probe.mjs +207 -0
- package/packages/board/src/core/host-protocol.mjs +73 -0
- package/packages/board/src/core/ids.mjs +35 -0
- package/packages/board/src/core/ime-cursor-coalesce.mjs +193 -0
- package/packages/board/src/core/invocation.mjs +43 -0
- package/packages/board/src/core/launch-options.mjs +418 -0
- package/packages/board/src/core/launch.mjs +157 -0
- package/packages/board/src/core/load-config.mjs +4 -0
- package/packages/board/src/core/locks.mjs +530 -0
- package/packages/board/src/core/paths.mjs +183 -0
- package/packages/board/src/core/pid.mjs +75 -0
- package/packages/board/src/core/prewarm-schedule.mjs +41 -0
- package/packages/board/src/core/process-identity-probe.mjs +228 -0
- package/packages/board/src/core/prompt-transport.mjs +13 -0
- package/packages/board/src/core/pty-attach-jiggle-controller.mjs +356 -0
- package/packages/board/src/core/pty-attach-jiggle-retry.mjs +109 -0
- package/packages/board/src/core/pty-attach-reconnect.mjs +61 -0
- package/packages/board/src/core/pty-attach-render.mjs +229 -0
- package/packages/board/src/core/pty-links.mjs +81 -0
- package/packages/board/src/core/pty-scroll.mjs +522 -0
- package/packages/board/src/core/pty-support.mjs +327 -0
- package/packages/board/src/core/repo.mjs +103 -0
- package/packages/board/src/core/rows.mjs +414 -0
- package/packages/board/src/core/screen-log-gc.mjs +198 -0
- package/packages/board/src/core/screen-log.mjs +160 -0
- package/packages/board/src/core/session-view.mjs +174 -0
- package/packages/board/src/core/slash-commands.mjs +52 -0
- package/packages/board/src/core/state-commands.mjs +757 -0
- package/packages/board/src/core/status-consistency.mjs +98 -0
- package/packages/board/src/core/steering-prompts.mjs +34 -0
- package/packages/board/src/core/steering.mjs +133 -0
- package/packages/board/src/core/stop-latch.mjs +41 -0
- package/packages/board/src/core/store.mjs +523 -0
- package/packages/board/src/core/suspend.mjs +36 -0
- package/packages/board/src/core/terminal-attach-client.mjs +981 -0
- package/packages/board/src/core/terminal-attach-protocol.mjs +460 -0
- package/packages/board/src/core/terminal-model.mjs +222 -0
- package/packages/board/src/core/terminal-query-sequences.mjs +239 -0
- package/packages/board/src/core/terminal-reply-guard.mjs +222 -0
- package/packages/board/src/core/terminal-snapshot.mjs +440 -0
- package/packages/board/src/core/test-knobs.mjs +28 -0
- package/packages/board/src/core/title.mjs +43 -0
- package/packages/board/src/core/types.mjs +414 -0
- package/packages/board/src/core/warm-host-sweeper.mjs +168 -0
- package/packages/board/src/core/worktree.mjs +65 -0
- package/packages/board/src/index.ts +303 -0
- package/packages/board/src/runtime/service.mjs +3222 -0
- package/packages/board/src/ui/chrome.ts +20 -0
- package/packages/board/src/ui/dashboard-decisions.mjs +55 -0
- package/packages/board/src/ui/dashboard-evidence.mjs +85 -0
- package/packages/board/src/ui/dashboard.ts +2237 -0
- package/packages/board/src/ui/double-left.ts +30 -0
- package/packages/board/src/ui/external-editor.ts +25 -0
- package/packages/board/src/ui/focus.ts +32 -0
- package/packages/board/src/ui/pty-attach.ts +3184 -0
- package/packages/board/src/ui/waiting.ts +30 -0
- package/packages/commands/LICENSE +21 -0
- package/packages/commands/README.md +19 -0
- package/packages/commands/package.json +22 -0
- package/packages/commands/src/index.ts +43 -0
- package/packages/commands/src/security-review.ts +109 -0
- package/packages/frame/CHANGELOG.md +113 -0
- package/packages/frame/CONTRIBUTING.md +7 -0
- package/packages/frame/LICENSE +22 -0
- package/packages/frame/README.md +105 -0
- package/packages/frame/SECURITY.md +16 -0
- package/packages/frame/docs/how-it-works.md +53 -0
- package/packages/frame/docs/reference.md +78 -0
- package/packages/frame/package.json +72 -0
- package/packages/frame/src/data/git.ts +90 -0
- package/packages/frame/src/data/github.ts +140 -0
- package/packages/frame/src/data/gitlab.ts +63 -0
- package/packages/frame/src/data/mode.ts +49 -0
- package/packages/frame/src/data/past-prompts.ts +57 -0
- package/packages/frame/src/data/runner.ts +32 -0
- package/packages/frame/src/data/snapshot.ts +223 -0
- package/packages/frame/src/data/start-notes.ts +104 -0
- package/packages/frame/src/data/telemetry.ts +337 -0
- package/packages/frame/src/data/todos.ts +162 -0
- package/packages/frame/src/data/totals.ts +94 -0
- package/packages/frame/src/data/visits.ts +45 -0
- package/packages/frame/src/index.ts +696 -0
- package/packages/frame/src/kit/LICENSE +21 -0
- package/packages/frame/src/kit/app/attribution.ts +30 -0
- package/packages/frame/src/kit/app/builder.ts +186 -0
- package/packages/frame/src/kit/app/claim.ts +27 -0
- package/packages/frame/src/kit/app/feature.ts +91 -0
- package/packages/frame/src/kit/app/host.ts +214 -0
- package/packages/frame/src/kit/app/renderers.ts +62 -0
- package/packages/frame/src/kit/app/scope.ts +125 -0
- package/packages/frame/src/kit/board/model.ts +337 -0
- package/packages/frame/src/kit/contract.ts +79 -0
- package/packages/frame/src/kit/contracts/accounts.ts +33 -0
- package/packages/frame/src/kit/contracts/ask.ts +120 -0
- package/packages/frame/src/kit/contracts/board.ts +6 -0
- package/packages/frame/src/kit/contracts/by.ts +22 -0
- package/packages/frame/src/kit/contracts/calls.ts +46 -0
- package/packages/frame/src/kit/contracts/events.ts +89 -0
- package/packages/frame/src/kit/contracts/mode.ts +26 -0
- package/packages/frame/src/kit/contracts/names.ts +12 -0
- package/packages/frame/src/kit/contracts/peers.ts +77 -0
- package/packages/frame/src/kit/contracts/prompts.ts +8 -0
- package/packages/frame/src/kit/contracts/screen.ts +75 -0
- package/packages/frame/src/kit/contracts/skills.ts +81 -0
- package/packages/frame/src/kit/contracts/sources.ts +81 -0
- package/packages/frame/src/kit/contracts/views.ts +42 -0
- package/packages/frame/src/kit/control.ts +59 -0
- package/packages/frame/src/kit/dark-theme.ts +41 -0
- package/packages/frame/src/kit/decode.ts +210 -0
- package/packages/frame/src/kit/events/log.ts +313 -0
- package/packages/frame/src/kit/format.ts +25 -0
- package/packages/frame/src/kit/host.ts +4 -0
- package/packages/frame/src/kit/index.ts +25 -0
- package/packages/frame/src/kit/legacy-tasks.ts +39 -0
- package/packages/frame/src/kit/overlay-images.ts +36 -0
- package/packages/frame/src/kit/paths.ts +16 -0
- package/packages/frame/src/kit/screen/client.ts +74 -0
- package/packages/frame/src/kit/screen/feature.ts +64 -0
- package/packages/frame/src/kit/screen/model.ts +182 -0
- package/packages/frame/src/kit/screen/view.ts +838 -0
- package/packages/frame/src/kit/settings/files.ts +90 -0
- package/packages/frame/src/kit/settings/setting.ts +57 -0
- package/packages/frame/src/kit/settings/store.ts +192 -0
- package/packages/frame/src/kit/sources/file.ts +129 -0
- package/packages/frame/src/kit/sources/follow.ts +155 -0
- package/packages/frame/src/kit/testing.ts +260 -0
- package/packages/frame/src/kit/totals.ts +72 -0
- package/packages/frame/src/kit/ui/entry.ts +75 -0
- package/packages/frame/src/kit/ui/frame.ts +212 -0
- package/packages/frame/src/kit/ui/glyphs.ts +132 -0
- package/packages/frame/src/kit/ui/scroll.ts +14 -0
- package/packages/frame/src/render/diff.ts +157 -0
- package/packages/frame/src/render/fit.ts +86 -0
- package/packages/frame/src/render/format.ts +108 -0
- package/packages/frame/src/render/frame.ts +106 -0
- package/packages/frame/src/render/glyphs.ts +189 -0
- package/packages/frame/src/render/keys.ts +43 -0
- package/packages/frame/src/render/lead.ts +37 -0
- package/packages/frame/src/render/paint.ts +135 -0
- package/packages/frame/src/render/safe.ts +19 -0
- package/packages/frame/src/render/segments.ts +735 -0
- package/packages/frame/src/render/streaming.ts +223 -0
- package/packages/frame/src/render/timing.ts +106 -0
- package/packages/frame/src/render/tools.ts +167 -0
- package/packages/frame/src/settings.ts +105 -0
- package/packages/frame/src/ui/editor.ts +272 -0
- package/packages/frame/src/ui/effort.ts +36 -0
- package/packages/frame/src/ui/footer.ts +165 -0
- package/packages/frame/src/ui/header.ts +362 -0
- package/packages/frame/src/ui/history-search.ts +23 -0
- package/packages/frame/src/ui/interrupt.ts +21 -0
- package/packages/frame/src/ui/outputs.ts +170 -0
- package/packages/frame/src/ui/pi-entries.ts +298 -0
- package/packages/frame/src/ui/previews.ts +72 -0
- package/packages/frame/src/ui/queue.ts +75 -0
- package/packages/frame/src/ui/screen.ts +59 -0
- package/packages/frame/src/ui/timings.ts +48 -0
- package/packages/frame/src/ui/todos.ts +205 -0
- package/packages/frame/src/ui/tool-frame.ts +595 -0
- package/packages/frame/src/ui/working.ts +25 -0
- package/packages/frame/src/view/state.ts +97 -0
- package/packages/frame/src/view/transcript.ts +175 -0
- package/packages/frame/src/view/view.ts +162 -0
- package/packages/memory/CONTRIBUTING.md +7 -0
- package/packages/memory/LICENSE +21 -0
- package/packages/memory/README.md +44 -0
- package/packages/memory/SECURITY.md +20 -0
- package/packages/memory/package.json +55 -0
- package/packages/memory/skills/review-memory/SKILL.md +48 -0
- package/packages/memory/src/index.ts +163 -0
- package/packages/memory/src/prompt.ts +25 -0
- package/packages/memory/src/review.ts +150 -0
- package/packages/memory/src/secrets.ts +22 -0
- package/packages/memory/src/store.ts +173 -0
- package/packages/modes/CHANGELOG.md +104 -0
- package/packages/modes/CONTRIBUTING.md +7 -0
- package/packages/modes/LICENSE +21 -0
- package/packages/modes/README.md +174 -0
- package/packages/modes/SECURITY.md +17 -0
- package/packages/modes/package.json +55 -0
- package/packages/modes/skills/review-permissions/SKILL.md +54 -0
- package/packages/modes/src/bash.ts +218 -0
- package/packages/modes/src/deny-off.ts +62 -0
- package/packages/modes/src/git-safety.ts +72 -0
- package/packages/modes/src/index.ts +338 -0
- package/packages/modes/src/interview.ts +206 -0
- package/packages/modes/src/judge-gate.ts +113 -0
- package/packages/modes/src/modes.ts +121 -0
- package/packages/modes/src/review/apply.mjs +156 -0
- package/packages/modes/src/review/collect.mjs +208 -0
- package/packages/modes/src/review/tools.ts +71 -0
- package/packages/modes/src/sandbox.ts +37 -0
- package/packages/router/CHANGELOG.md +8 -0
- package/packages/router/LICENSE +21 -0
- package/packages/router/README.md +90 -0
- package/packages/router/package.json +51 -0
- package/packages/router/src/config.ts +65 -0
- package/packages/router/src/index.ts +172 -0
- package/packages/router/src/judge.ts +51 -0
- package/packages/router/src/route.ts +67 -0
- package/packages/web/CHANGELOG.md +36 -0
- package/packages/web/LICENSE +21 -0
- package/packages/web/README.md +87 -0
- package/packages/web/SECURITY.md +13 -0
- package/packages/web/package.json +59 -0
- package/packages/web/src/config.ts +109 -0
- package/packages/web/src/dnsfilter.ts +97 -0
- package/packages/web/src/extract.ts +65 -0
- package/packages/web/src/index.ts +217 -0
- package/packages/web/src/markdown.ts +122 -0
- package/packages/web/src/net.ts +109 -0
- package/packages/web/src/reader.ts +73 -0
- package/packages/web/src/sanitize.ts +50 -0
- package/packages/web/src/search.ts +76 -0
- package/packages/web/src/secrets.ts +22 -0
- package/packages/web/src/validate.ts +186 -0
- package/src/index.ts +156 -0
- package/src/settings.ts +79 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// One package, seven parts. A part that's off isn't even imported, so it adds no tools, prompts or start-up time.
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { type ExtensionAPI, type ExtensionCommandContext, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
|
|
8
|
+
import { isPart, loadSettings, type Part, PART_NAMES, PARTS, saveSettings, separateInstalls } from "./settings.ts";
|
|
9
|
+
|
|
10
|
+
function loadPart(part: Part): Promise<{ default: (pi: ExtensionAPI) => void | Promise<void> }> {
|
|
11
|
+
return import(new URL(`../packages/${part}/src/index.ts`, import.meta.url).href);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Skills are found through this event rather than the manifest, so a part that's off doesn't offer them. */
|
|
15
|
+
const SKILLS: Partial<Record<Part, string>> = { modes: "modes", memory: "memory" };
|
|
16
|
+
|
|
17
|
+
const MODES_OFF = "plan, read-only and auto aren't enforced, and Pi runs tools with no mode checks";
|
|
18
|
+
|
|
19
|
+
const ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
20
|
+
|
|
21
|
+
export default async function piSuite(pi: ExtensionAPI): Promise<void> {
|
|
22
|
+
const agentDir = getAgentDir();
|
|
23
|
+
const settings = loadSettings(agentDir);
|
|
24
|
+
const notices: string[] = settings.problem === undefined ? [] : [`pi-suite: ${settings.problem}`];
|
|
25
|
+
const loaded = new Set<Part>();
|
|
26
|
+
const failed = new Set<Part>();
|
|
27
|
+
if (settings.off.has("modes")) notices.push(`pi-suite: modes is off, so ${MODES_OFF}`);
|
|
28
|
+
|
|
29
|
+
for (const part of PART_NAMES) {
|
|
30
|
+
if (settings.off.has(part)) continue;
|
|
31
|
+
try {
|
|
32
|
+
await (await loadPart(part)).default(pi);
|
|
33
|
+
loaded.add(part);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
// Modes enforces plan and read-only, so Pi must not go on without it. Pi's own loader then reports this and stops.
|
|
36
|
+
if (part === "modes") throw error;
|
|
37
|
+
failed.add(part);
|
|
38
|
+
notices.push(`pi-suite: ${part} failed to load, and may be partly working: ${(error as Error).message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pi.on("resources_discover", () => ({
|
|
43
|
+
skillPaths: PART_NAMES.filter((part) => loaded.has(part) && SKILLS[part] !== undefined).map((part) => join(ROOT, "packages", SKILLS[part] as string, "skills")),
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
pi.on("session_start", (_event, ctx) => {
|
|
47
|
+
for (const notice of notices.splice(0)) {
|
|
48
|
+
if (ctx.hasUI) ctx.ui.notify(notice, "error");
|
|
49
|
+
else console.error(notice);
|
|
50
|
+
}
|
|
51
|
+
if (!ctx.hasUI) return;
|
|
52
|
+
let packages: unknown;
|
|
53
|
+
try {
|
|
54
|
+
packages = JSON.parse(readFileSync(join(agentDir, "settings.json"), "utf8")).packages;
|
|
55
|
+
} catch {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const twice = separateInstalls(packages);
|
|
59
|
+
if (twice.length > 0) ctx.ui.notify(`pi-suite already includes these, so remove them with pi remove: ${twice.join(", ")}`, "warning");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/** What's saved can differ from what's running, until the next /reload. */
|
|
63
|
+
function currentOff(): Set<Part> {
|
|
64
|
+
return new Set(loadSettings(agentDir).off);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function setPart(part: Part, on: boolean, ctx: ExtensionCommandContext): Promise<boolean> {
|
|
68
|
+
const off = currentOff();
|
|
69
|
+
if (off.has(part) === !on) return false;
|
|
70
|
+
if (!on && part === "modes") {
|
|
71
|
+
// Nobody can agree to it here, and a script could be passing on someone else's text.
|
|
72
|
+
if (!ctx.hasUI) {
|
|
73
|
+
console.error("pi-suite: /suite off modes needs someone to confirm it, so nothing changed.");
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (!(await ctx.ui.confirm("Turn off modes?", `With it off, ${MODES_OFF}.`))) return false;
|
|
77
|
+
}
|
|
78
|
+
if (on) off.delete(part);
|
|
79
|
+
else off.add(part);
|
|
80
|
+
saveSettings(agentDir, off);
|
|
81
|
+
if (!on && part === "router") await leaveRouter(ctx);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The session would otherwise stay on a model that's gone after the reload, and every prompt would fail. */
|
|
86
|
+
async function leaveRouter(ctx: ExtensionCommandContext): Promise<void> {
|
|
87
|
+
if (ctx.model?.api !== "pi-virtual") return;
|
|
88
|
+
const lastAnswer = ctx.sessionManager
|
|
89
|
+
.getBranch()
|
|
90
|
+
.flatMap((entry) => (entry.type === "message" && entry.message.role === "assistant" && entry.message.stopReason !== "error" && entry.message.stopReason !== "aborted" ? [entry.message] : []))
|
|
91
|
+
.at(-1);
|
|
92
|
+
const answered = lastAnswer && ctx.modelRegistry.find(lastAnswer.provider, lastAnswer.model);
|
|
93
|
+
const next = answered ?? ctx.modelRegistry.getAvailable().find((model) => model.api !== "pi-virtual");
|
|
94
|
+
if (next === undefined) return;
|
|
95
|
+
await pi.setModel(next);
|
|
96
|
+
if (ctx.hasUI) ctx.ui.notify(`Now on ${next.provider}/${next.id}.`, "info");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function finish(changed: boolean, ctx: ExtensionCommandContext): Promise<void> {
|
|
100
|
+
if (!changed) return;
|
|
101
|
+
if (ctx.hasUI && (await ctx.ui.confirm("Reload Pi now?", "Your changes apply after a reload."))) await ctx.reload();
|
|
102
|
+
else if (ctx.hasUI) ctx.ui.notify("Saved. Run /reload to apply it.", "info");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function status(): string {
|
|
106
|
+
const off = currentOff();
|
|
107
|
+
return PART_NAMES.map((part) => {
|
|
108
|
+
if (failed.has(part)) return `${part}: failed to load ${PARTS[part]}`;
|
|
109
|
+
const now = loaded.has(part);
|
|
110
|
+
const next = !off.has(part);
|
|
111
|
+
const pending = now === next ? "" : ` (${next ? "on" : "off"} after /reload)`;
|
|
112
|
+
return `${part}: ${now ? "on" : "off"}${pending} ${PARTS[part]}`;
|
|
113
|
+
}).join("\n");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
pi.registerCommand("suite", {
|
|
117
|
+
description: "Turn parts of pi-suite on or off: /suite, /suite on web, /suite off board, /suite list",
|
|
118
|
+
getArgumentCompletions: (prefix) => {
|
|
119
|
+
const typed = prefix.trimStart().toLowerCase();
|
|
120
|
+
const options = ["list", ...PART_NAMES.flatMap((part) => [`on ${part}`, `off ${part}`])];
|
|
121
|
+
const found = options.filter((option) => option.startsWith(typed) && option !== typed);
|
|
122
|
+
return found.length === 0 ? null : found.map((value) => ({ value, label: value }));
|
|
123
|
+
},
|
|
124
|
+
handler: async (args, ctx) => {
|
|
125
|
+
const [verb, name, ...rest] = args.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
126
|
+
if (verb === "list") {
|
|
127
|
+
if (ctx.hasUI) ctx.ui.notify(status(), "info");
|
|
128
|
+
else console.log(status());
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (verb === "on" || verb === "off") {
|
|
132
|
+
if (name === undefined || !isPart(name) || rest.length > 0) {
|
|
133
|
+
ctx.ui.notify(`Use /suite ${verb} with one of: ${PART_NAMES.join(", ")}`, "error");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
await finish(await setPart(name, verb === "on", ctx), ctx);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (verb !== undefined) {
|
|
140
|
+
ctx.ui.notify("Use /suite, /suite list, /suite on <part> or /suite off <part>", "error");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (!ctx.hasUI) return;
|
|
144
|
+
let changed = false;
|
|
145
|
+
for (;;) {
|
|
146
|
+
const off = currentOff();
|
|
147
|
+
const rows = PART_NAMES.map((part) => `${off.has(part) ? "[ ]" : "[x]"} ${part} ${PARTS[part]}`);
|
|
148
|
+
const row = await ctx.ui.select("pi-suite: Enter turns a part on or off (Esc when done)", rows);
|
|
149
|
+
if (row === undefined) break;
|
|
150
|
+
const part = PART_NAMES[rows.indexOf(row)] as Part;
|
|
151
|
+
if (await setPart(part, off.has(part), ctx)) changed = true;
|
|
152
|
+
}
|
|
153
|
+
await finish(changed, ctx);
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
package/src/settings.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// ~/.pi/agent/suite.json: which parts are off. Everything is on unless it's listed.
|
|
2
|
+
import { existsSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export const PARTS = {
|
|
6
|
+
modes: "Modes and permissions: plan, auto, read-only, yolo, submit_plan, ask_questions",
|
|
7
|
+
router: "router/auto: one model to plan, another to build, and one each for the reader, judge and subagents",
|
|
8
|
+
web: "web_fetch and web_search, read by a separate model with no tools",
|
|
9
|
+
memory: "Memory the agent keeps between sessions",
|
|
10
|
+
frame: "The start card, framed editor, tool boxes, footer and todo list",
|
|
11
|
+
board: "The board of agent sessions",
|
|
12
|
+
commands: "/clear and /security-review",
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
export type Part = keyof typeof PARTS;
|
|
16
|
+
/** The order they load in. Modes goes first so its checks run before anything else sees a tool call. */
|
|
17
|
+
export const PART_NAMES = Object.keys(PARTS) as Part[];
|
|
18
|
+
|
|
19
|
+
export function settingsPath(agentDir: string): string {
|
|
20
|
+
return join(agentDir, "suite.json");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function isPart(name: string): name is Part {
|
|
24
|
+
return (PART_NAMES as string[]).includes(name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Loaded {
|
|
28
|
+
off: ReadonlySet<Part>;
|
|
29
|
+
/** Everything stays on when the file can't be understood, so a typo can't turn off plan mode. */
|
|
30
|
+
problem?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function parseSettings(text: string): Loaded {
|
|
34
|
+
let raw: unknown;
|
|
35
|
+
try {
|
|
36
|
+
raw = JSON.parse(text);
|
|
37
|
+
} catch {
|
|
38
|
+
return { off: new Set(), problem: "suite.json isn't valid JSON, so everything is on" };
|
|
39
|
+
}
|
|
40
|
+
const off = (raw as { off?: unknown } | null)?.off;
|
|
41
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw) || (off !== undefined && !Array.isArray(off))) {
|
|
42
|
+
return { off: new Set(), problem: 'suite.json should look like { "off": ["board"] }, so everything is on' };
|
|
43
|
+
}
|
|
44
|
+
const names = (off ?? []) as unknown[];
|
|
45
|
+
const unknown = names.filter((name) => typeof name !== "string" || !isPart(name));
|
|
46
|
+
if (unknown.length > 0) {
|
|
47
|
+
return { off: new Set(), problem: `suite.json names ${unknown.join(", ")}, which isn't one of ${PART_NAMES.join(", ")}, so everything is on` };
|
|
48
|
+
}
|
|
49
|
+
return { off: new Set(names as Part[]) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function loadSettings(agentDir: string): Loaded {
|
|
53
|
+
let text: string;
|
|
54
|
+
try {
|
|
55
|
+
text = readFileSync(settingsPath(agentDir), "utf8");
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return { off: new Set() };
|
|
58
|
+
return { off: new Set(), problem: `suite.json can't be read, so everything is on: ${(error as Error).message}` };
|
|
59
|
+
}
|
|
60
|
+
return parseSettings(text);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function saveSettings(agentDir: string, off: ReadonlySet<Part>): void {
|
|
64
|
+
// A linked file, as in a dotfiles setup, stays linked.
|
|
65
|
+
const named = settingsPath(agentDir);
|
|
66
|
+
const path = existsSync(named) ? realpathSync(named) : named;
|
|
67
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
68
|
+
writeFileSync(temp, `${JSON.stringify({ off: PART_NAMES.filter((name) => off.has(name)) }, null, 2)}\n`);
|
|
69
|
+
renameSync(temp, path);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Separate installs of what's now in here would register everything twice. */
|
|
73
|
+
export function separateInstalls(packages: unknown): string[] {
|
|
74
|
+
if (!Array.isArray(packages)) return [];
|
|
75
|
+
const pattern = new RegExp(`@huseynsnmz/pi-(?:${PART_NAMES.join("|")})\\b`);
|
|
76
|
+
return packages
|
|
77
|
+
.map((entry) => String((entry as { source?: unknown } | null)?.source ?? entry))
|
|
78
|
+
.filter((source) => pattern.test(source));
|
|
79
|
+
}
|