@jwilger/pi-development-system 0.44.1 → 0.45.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/agents/implementer.md +1 -1
- package/package.json +6 -2
- package/principles/NON-NEGOTIABLES.md +10 -0
- package/src/context/status.ts +1 -1
- package/src/context/system-prompt.ts +1 -1
- package/src/context/turn-verifier.ts +3 -3
- package/src/core/departure.ts +2 -1
- package/src/core/gates.ts +1 -1
- package/src/core/git-invocations.ts +1 -1
- package/src/core/model-advice.ts +1 -1
- package/src/core/profile.ts +2 -2
- package/src/core/review-flow.ts +1 -1
- package/src/core/review.ts +1 -1
- package/src/core/types.ts +0 -6
- package/src/gates/approvals.ts +1 -1
- package/src/jev/questions/shell-intent.ts +1 -1
- package/src/state/config.ts +2 -2
- package/src/state/models-command.ts +4 -7
- package/src/state/session-state.ts +1 -1
- package/src/subagents/orch/mailbox.ts +2 -2
- package/src/subagents/prefs/agent-import.ts +3 -3
- package/src/subagents/prefs/settings.ts +4 -4
- package/src/subagents/types.ts +2 -2
- package/src/subagents/ui/dialog.ts +3 -3
- package/src/subagents/ui/import-picker.ts +1 -1
- package/src/subagents/ui/model-picker.ts +1 -1
- package/src/subagents/ui/status-ui.ts +2 -3
- package/src/subagents/ui/ui.ts +5 -5
package/agents/implementer.md
CHANGED
|
@@ -37,7 +37,7 @@ You are an implementer. You receive exactly one task record and nothing else: do
|
|
|
37
37
|
|
|
38
38
|
End with evidence the coordinator can check, one block per command you ran:
|
|
39
39
|
|
|
40
|
-
```
|
|
40
|
+
```text
|
|
41
41
|
Run: <exact command>
|
|
42
42
|
Expected: <what the task said>
|
|
43
43
|
Actual: <what you saw, quoted>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jwilger/pi-development-system",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.1",
|
|
4
4
|
"description": "A pi extension package representing a seasoned approach to software development using a full AI SDLC.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -51,6 +51,8 @@
|
|
|
51
51
|
"@types/node": "^26.6.4",
|
|
52
52
|
"@types/shell-quote": "^1.7.5",
|
|
53
53
|
"@typesafe-ai/sdk": "^0.6.0",
|
|
54
|
+
"knip": "^6.40.0",
|
|
55
|
+
"markdownlint-cli2": "^0.23.3",
|
|
54
56
|
"typescript": "^7.0.2"
|
|
55
57
|
},
|
|
56
58
|
"scripts": {
|
|
@@ -61,6 +63,8 @@
|
|
|
61
63
|
"lint": "biome check .",
|
|
62
64
|
"lint:fix": "biome check --write .",
|
|
63
65
|
"check:build-gate": "node scripts/build-gate.ts",
|
|
64
|
-
"check:version": "node scripts/check-version.ts"
|
|
66
|
+
"check:version": "node scripts/check-version.ts",
|
|
67
|
+
"lint:knip": "knip",
|
|
68
|
+
"lint:md": "markdownlint-cli2"
|
|
65
69
|
}
|
|
66
70
|
}
|
|
@@ -5,31 +5,41 @@ Departing from any item below requires an explicit decision from the author
|
|
|
5
5
|
departure is warranted: stop, say which item and why, and ask.
|
|
6
6
|
|
|
7
7
|
## 1. Never rewrite pushed history or force-push
|
|
8
|
+
|
|
8
9
|
Amend/rebase/reset of pushed refs, `--force*`, deleting shared branches. Because it destroys other people's basis for trust.
|
|
9
10
|
|
|
10
11
|
## 2. Never weaken verification to make a gate pass
|
|
12
|
+
|
|
11
13
|
Deleting, skipping or loosening tests; suppressing a lint; editing expected values to match output; `--no-verify`. Because a green gate must mean the same thing afterwards. (Changing a test because the *requirement* changed is a recorded departure, not this.)
|
|
12
14
|
|
|
13
15
|
## 3. Never claim something was verified, run, or green that was not
|
|
16
|
+
|
|
14
17
|
Because the record of evidence is the product. Evidence comes before claims.
|
|
15
18
|
|
|
16
19
|
## 4. Never push on a red trunk
|
|
20
|
+
|
|
17
21
|
Except a change whose purpose is fixing that failure. Because unrelated work on red hides the signal.
|
|
18
22
|
|
|
19
23
|
## 5. Never work around an unresolved gate
|
|
24
|
+
|
|
20
25
|
Stop, record, ask. Because a gate you can route around is not a gate.
|
|
21
26
|
|
|
22
27
|
## 6. Never violate repo-local delivery policy
|
|
28
|
+
|
|
23
29
|
Delivery mode, protected branches, required reviews. Because the repo, not this tool, owns policy.
|
|
24
30
|
|
|
25
31
|
## 7. Never let secrets leave the machine without sanitisation
|
|
32
|
+
|
|
26
33
|
In commits, logs, eval cases, or subagent prompts. Because exfiltration is irreversible.
|
|
27
34
|
|
|
28
35
|
## 8. Never commit without a rationale-bearing Conventional Commit, or add AI attribution trailers
|
|
36
|
+
|
|
29
37
|
No `Co-Authored-By`, no `Generated-by`. Because the message is the durable why; attribution noise is not.
|
|
30
38
|
|
|
31
39
|
## 9. Never make an architecture-shaping decision without an ADR
|
|
40
|
+
|
|
32
41
|
New dependency class, boundary change, persistence model, public contract. Because "Revisit when" is how decisions stay honest.
|
|
33
42
|
|
|
34
43
|
## 10. Never ship model-visible instructions without an evidence check
|
|
44
|
+
|
|
35
45
|
Skills, prompts, Jev questions need a fixture or eval. Because effectiveness is eval-driven, not vibes.
|
package/src/context/status.ts
CHANGED
|
@@ -13,7 +13,7 @@ const ciLabel = (state: DevsysState): string | undefined => {
|
|
|
13
13
|
return `ci ${ci.status}${sha}`;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
const activeReview = (state: DevsysState): string | undefined => {
|
|
17
17
|
// Without an active slice (before /devsys-start exists) show the most recent review instead of nothing.
|
|
18
18
|
const review =
|
|
19
19
|
state.activeSlice === undefined ? state.reviews?.at(-1) : reviewOf(state, state.activeSlice);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BeforeAgentStartEvent } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { DevsysState } from "../core/types.ts";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const PROMPT_SECTION_NAME = "development-system";
|
|
5
5
|
|
|
6
6
|
/** Pure: renders the system-prompt section from current state. */
|
|
7
7
|
export function buildPromptSection(state: DevsysState, nonNegotiables: string): string {
|
|
@@ -7,9 +7,9 @@ import type { Jev } from "../jev/client.ts";
|
|
|
7
7
|
import { judgeTurn, type ToolEvidence } from "../jev/questions/turn.ts";
|
|
8
8
|
import type { SessionState } from "../state/session-state.ts";
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
const VERIFIER_THRESHOLD = 0.75;
|
|
11
11
|
export const DEFAULT_VERIFIER_MAX = 6;
|
|
12
|
-
|
|
12
|
+
const VERIFIER_ENTRY_TYPE = "devsys-verifier";
|
|
13
13
|
const VERIFIED_PHASES: ReadonlySet<Phase> = new Set(["implementing", "reviewing", "delivering"]);
|
|
14
14
|
const MAX_EVIDENCE = 60;
|
|
15
15
|
|
|
@@ -23,7 +23,7 @@ export type TurnVerifierDeps = {
|
|
|
23
23
|
type Parts = { text: string; callsTools: boolean };
|
|
24
24
|
|
|
25
25
|
/** Text and tool-call presence of an assistant message; undefined for any other role. */
|
|
26
|
-
|
|
26
|
+
function assistantParts(message: AgentMessage): Parts | undefined {
|
|
27
27
|
if (message.role !== "assistant") return undefined;
|
|
28
28
|
const text = message.content.flatMap((c) => (c.type === "text" ? [c.text] : [])).join("\n");
|
|
29
29
|
return { text, callsTools: message.content.some((c) => c.type === "toolCall") };
|
package/src/core/departure.ts
CHANGED
|
@@ -79,7 +79,8 @@ function renderScope(scope: DepartureScope): string {
|
|
|
79
79
|
export function renderDepartureMarkdown(d: Departure): string {
|
|
80
80
|
const revisit = d.revisitWhen !== undefined ? ` · **Revisit when:** ${d.revisitWhen}` : "";
|
|
81
81
|
return [
|
|
82
|
-
|
|
82
|
+
`## ${d.recordedAt} · ${d.gate} · ${d.tier} · ${d.approver}`,
|
|
83
|
+
"",
|
|
83
84
|
`- **Default:** ${d.default}`,
|
|
84
85
|
`- **Chosen:** ${d.chosen}`,
|
|
85
86
|
`- **Why:** ${d.why}`,
|
package/src/core/gates.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Tier } from "./types.ts";
|
|
|
3
3
|
export type GateInfo = { readonly tier: Tier; readonly default: string };
|
|
4
4
|
|
|
5
5
|
/** Registered gate ids (plan Appendix A); grows per increment. Qualified ids use their base entry. */
|
|
6
|
-
|
|
6
|
+
const GATES: Readonly<Record<string, GateInfo>> = {
|
|
7
7
|
"git.history-rewrite": { tier: "hard", default: "never rewrite published history" },
|
|
8
8
|
"git.force-push": { tier: "hard", default: "never force-push" },
|
|
9
9
|
"git.branch-delete-remote": { tier: "hard", default: "never delete remote branches" },
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from "./git-intent.ts";
|
|
16
16
|
import { stripHeredocs } from "./heredoc.ts";
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
type GitInvocation = {
|
|
19
19
|
readonly sub: string;
|
|
20
20
|
readonly args: readonly string[];
|
|
21
21
|
/** Directory the command runs in, relative to the starting one (from `cd` and `git -C`), when it moved. */
|
package/src/core/model-advice.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
import type { Phase } from "./types.ts";
|
|
10
10
|
|
|
11
11
|
/** Highest first. */
|
|
12
|
-
|
|
12
|
+
const TIERS = ["frontier", "strong", "fast"] as const;
|
|
13
13
|
export type Tier = (typeof TIERS)[number];
|
|
14
14
|
|
|
15
15
|
const PHASE_SLOT: Partial<Record<Phase, Slot>> = {
|
package/src/core/profile.ts
CHANGED
|
@@ -4,14 +4,14 @@ import { isParseError, type ParseError, type Profile, parseError } from "./types
|
|
|
4
4
|
|
|
5
5
|
export type { Profile };
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const PROFILES: readonly Profile[] = ["rust", "typescript"];
|
|
8
8
|
|
|
9
9
|
const MARKERS: ReadonlyArray<{ profile: Profile; files: readonly string[] }> = [
|
|
10
10
|
{ profile: "rust", files: ["Cargo.toml"] },
|
|
11
11
|
{ profile: "typescript", files: ["package.json", "tsconfig.json"] },
|
|
12
12
|
];
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
const isProfile = (value: string): value is Profile => PROFILES.some((p) => p === value);
|
|
15
15
|
|
|
16
16
|
/** Boundary parse for configured profile names. */
|
|
17
17
|
export function parseProfiles(names: readonly string[]): Profile[] | ParseError {
|
package/src/core/review-flow.ts
CHANGED
|
@@ -110,7 +110,7 @@ export function combinePackets(packets: readonly ReviewPacket[]): {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
/** Jev confidence needed before its severity replaces the reviewer's. */
|
|
113
|
-
|
|
113
|
+
const SEVERITY_ADOPT_CONFIDENCE = 0.8;
|
|
114
114
|
|
|
115
115
|
const counts = (severity: Severity): boolean =>
|
|
116
116
|
severity === "blocking" || severity === "should-fix";
|
package/src/core/review.ts
CHANGED
|
@@ -90,7 +90,7 @@ export const LENSES = [
|
|
|
90
90
|
"performance",
|
|
91
91
|
] as const;
|
|
92
92
|
export type Lens = (typeof LENSES)[number];
|
|
93
|
-
|
|
93
|
+
const LENS_THRESHOLD = 0.5;
|
|
94
94
|
|
|
95
95
|
/** Lenses that apply when Jev is offline: the ones every change deserves. */
|
|
96
96
|
export const DEFAULT_LENSES: ReadonlyArray<Lens> = ["types", "tests"];
|
package/src/core/types.ts
CHANGED
|
@@ -22,12 +22,6 @@ export function parseGateId(input: string): GateId | ParseError {
|
|
|
22
22
|
: parseError(`invalid gate id "${input}": expected dotted kebab-case like "git.force-push"`);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export type GateDecision =
|
|
26
|
-
| { readonly kind: "allow" }
|
|
27
|
-
| { readonly kind: "block"; readonly reason: string }
|
|
28
|
-
| { readonly kind: "require-departure"; readonly gate: GateId; readonly reason: string }
|
|
29
|
-
| { readonly kind: "require-user"; readonly gate: GateId; readonly reason: string };
|
|
30
|
-
|
|
31
25
|
export type Phase = "intake" | "planning" | "implementing" | "reviewing" | "delivering" | "idle";
|
|
32
26
|
|
|
33
27
|
export type Sizing = "fix" | "change" | "capability" | "product";
|
package/src/gates/approvals.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { type GateId, isParseError } from "../core/types.ts";
|
|
|
6
6
|
import { appendDecision } from "../state/decision-log.ts";
|
|
7
7
|
import { DEPARTURE_ENTRY_TYPE } from "./record-departure-tool.ts";
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
const APPROVAL_ENTRY_TYPE = "devsys-approval";
|
|
10
10
|
|
|
11
11
|
type Approval = { id: string; gate: string; command: string; used: boolean };
|
|
12
12
|
|
|
@@ -4,7 +4,7 @@ import { redactSecrets } from "../../core/redact.ts";
|
|
|
4
4
|
import { err, ok, type Result } from "../../core/result.ts";
|
|
5
5
|
import type { Jev, JevError } from "../client.ts";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
const SHELL_INTENT_THRESHOLD = 0.6;
|
|
8
8
|
|
|
9
9
|
export const SHELL_INTENT_QUESTION: ClassifierChoiceQuestion = {
|
|
10
10
|
type: "choice",
|
package/src/state/config.ts
CHANGED
|
@@ -11,8 +11,8 @@ export const CONFIG_FILE = ".development-system.toml";
|
|
|
11
11
|
const DELIVERY_MODES = ["trunk", "pull-request", "local-only"] as const;
|
|
12
12
|
const TRACKER_KINDS = ["repo-files", "github", "jira", "linear"] as const;
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
type DeliveryMode = (typeof DELIVERY_MODES)[number];
|
|
15
|
+
type TrackerKind = (typeof TRACKER_KINDS)[number];
|
|
16
16
|
|
|
17
17
|
export type ConfigError = {
|
|
18
18
|
readonly kind: "config-error";
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
} from "../core/models.ts";
|
|
20
20
|
import { CONFIG_FILE, loadConfig } from "./config.ts";
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
const CONFIG_ENTRY_TYPE = "devsys-config";
|
|
23
23
|
|
|
24
24
|
export type CatalogModel = Available & {
|
|
25
25
|
readonly cost?: { readonly input: number; readonly output: number };
|
|
@@ -32,7 +32,7 @@ export interface ModelCatalog {
|
|
|
32
32
|
hasConfiguredAuth(model: CatalogModel): boolean;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
type SlotRow = {
|
|
36
36
|
readonly slot: Slot;
|
|
37
37
|
readonly resolution: Resolution | undefined;
|
|
38
38
|
};
|
|
@@ -45,7 +45,7 @@ export function availableModels(catalog: ModelCatalog): CatalogModel[] {
|
|
|
45
45
|
return [...catalog.getAvailable(), ...classifiers];
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
function resolveRows(matrix: ModelMatrix, available: readonly Available[]): SlotRow[] {
|
|
49
49
|
return SLOTS.map((slot) => {
|
|
50
50
|
const r = resolveSlot(matrix, slot, available);
|
|
51
51
|
return { slot, resolution: r.ok ? r.value : undefined };
|
|
@@ -62,10 +62,7 @@ function priceOf(model: string, available: readonly CatalogModel[]): string {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/** One line per slot: `slot → provider/id (via candidate) · $in/$out per M`, or `UNRESOLVED`. */
|
|
65
|
-
|
|
66
|
-
rows: readonly SlotRow[],
|
|
67
|
-
available: readonly CatalogModel[],
|
|
68
|
-
): string {
|
|
65
|
+
function renderModelsTable(rows: readonly SlotRow[], available: readonly CatalogModel[]): string {
|
|
69
66
|
const width = Math.max(...SLOTS.map((s) => s.length));
|
|
70
67
|
return rows
|
|
71
68
|
.map(({ slot, resolution }) =>
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import type { SessionEntry, SessionManager } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
const LEGACY_QUEUE_TYPE = "pi-subagent:queue:v1";
|
|
6
6
|
const ACCEPTED_TYPE = "pi-subagent:mailbox:accepted:v2";
|
|
7
7
|
const CONSUMED_TYPE = "pi-subagent:mailbox:consumed:v2";
|
|
8
8
|
const MIGRATION_TYPE = "pi-subagent:mailbox:migration:v2";
|
|
9
|
-
|
|
9
|
+
const MAILBOX_FIELD = "piSubagentMailbox";
|
|
10
10
|
export const BOOTSTRAP_MESSAGE = "Subagent session initialized; awaiting parent input.";
|
|
11
11
|
|
|
12
12
|
export type AcceptedKind = "steer" | "followUp" | "update";
|
|
@@ -12,7 +12,7 @@ import { dialogText } from "../ui/dialog.ts";
|
|
|
12
12
|
export const IMPORT_REQUEST_PREFIX =
|
|
13
13
|
"Import ONLY the agents I selected in the pi-subagent-manager migration picker.";
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
const IMPORT_CHILD_TOOLS = [
|
|
16
16
|
"read",
|
|
17
17
|
"bash",
|
|
18
18
|
"powershell",
|
|
@@ -61,7 +61,7 @@ function stateDirectory(agentDir: string, create: boolean): string | undefined {
|
|
|
61
61
|
return directory;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
function importWasOffered(agentDir: string): boolean {
|
|
65
65
|
const directory = stateDirectory(agentDir, false);
|
|
66
66
|
if (!directory) return false;
|
|
67
67
|
const stat = statIfPresent(join(directory, OFFER_MARKER));
|
|
@@ -89,7 +89,7 @@ export function markImportOffered(agentDir: string): void {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
function buildImportPrompt(options: {
|
|
93
93
|
candidates: readonly ImportCandidate[];
|
|
94
94
|
agentDir: string;
|
|
95
95
|
cwd: string;
|
|
@@ -14,16 +14,16 @@ import {
|
|
|
14
14
|
} from "node:fs";
|
|
15
15
|
import { dirname, join, resolve } from "node:path";
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
const SUBAGENT_MODES = ["off", "opportunistic", "orchestration"] as const;
|
|
18
18
|
export type SubagentMode = (typeof SUBAGENT_MODES)[number];
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
const WIDGET_MODES = ["full", "minimal"] as const;
|
|
21
21
|
export type WidgetMode = (typeof WIDGET_MODES)[number];
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
const TOOL_FILTERING_MODES = ["allowed", "all-except-blocked", "all"] as const;
|
|
24
24
|
export type ToolFilteringMode = (typeof TOOL_FILTERING_MODES)[number];
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
const MODEL_SELECTION_MODES = [
|
|
27
27
|
"pick-first-available",
|
|
28
28
|
"pick-first-scoped",
|
|
29
29
|
"use-current",
|
package/src/subagents/types.ts
CHANGED
|
@@ -44,7 +44,7 @@ export interface AgentType {
|
|
|
44
44
|
/** File path of the base definition under the customization, if any. */
|
|
45
45
|
baseFilePath?: string;
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
type ThreadState = "starting" | "running" | "paused" | "completed" | "failed" | "stopped";
|
|
48
48
|
export interface ThreadView {
|
|
49
49
|
path: string;
|
|
50
50
|
parent: string | null;
|
|
@@ -71,7 +71,7 @@ export interface ThreadView {
|
|
|
71
71
|
/** devsys: per-spawn model/thinkingLevel pin requested through agent_spawn, when any. */
|
|
72
72
|
pinned?: { model?: string; thinkingLevel?: ThinkingLevel };
|
|
73
73
|
}
|
|
74
|
-
|
|
74
|
+
type DriverEvent =
|
|
75
75
|
| { kind: "activity" | "error"; text: string }
|
|
76
76
|
| {
|
|
77
77
|
kind: "checkpoint";
|
|
@@ -24,7 +24,7 @@ export interface DialogHost {
|
|
|
24
24
|
requestRender(force?: boolean): void;
|
|
25
25
|
terminal?: { rows: number };
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
interface DialogEditorHost extends DialogHost {
|
|
28
28
|
terminal: { rows: number };
|
|
29
29
|
}
|
|
30
30
|
export interface DialogRow {
|
|
@@ -97,7 +97,7 @@ export function frameDialog(
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
/** A multiline editor inside the same bounded shell as the surrounding form. */
|
|
100
|
-
|
|
100
|
+
class DialogEditor {
|
|
101
101
|
private editor: Editor;
|
|
102
102
|
private initialText: string;
|
|
103
103
|
constructor(
|
|
@@ -180,7 +180,7 @@ export function dialogEditor(
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
/** Bounded, two-column menu shared by settings and agent definitions. */
|
|
183
|
-
|
|
183
|
+
class DialogMenu {
|
|
184
184
|
private selected = 0;
|
|
185
185
|
private viewport = 1;
|
|
186
186
|
constructor(
|
|
@@ -71,7 +71,7 @@ function wrapText(text: string, columns: number): string[] {
|
|
|
71
71
|
* Checkbox dialog for choosing external agents to migrate.
|
|
72
72
|
* Selection only — the caller imports; this does not read or write agent files.
|
|
73
73
|
*/
|
|
74
|
-
|
|
74
|
+
class ImportPicker {
|
|
75
75
|
private cursor = 0;
|
|
76
76
|
private viewport = 1;
|
|
77
77
|
private readonly checked = new Set<string>();
|
|
@@ -84,7 +84,7 @@ function describeModel(model: AvailableModel, scopedIdentities: ReadonlySet<stri
|
|
|
84
84
|
return [name, annotation].filter(Boolean).join(" · ");
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
class OrderedModelEditorComponent extends Container {
|
|
88
88
|
private readonly tui: {
|
|
89
89
|
requestRender(force?: boolean): void;
|
|
90
90
|
terminal?: { rows: number };
|
|
@@ -31,7 +31,6 @@ import {
|
|
|
31
31
|
} from "./ui.ts";
|
|
32
32
|
import { LiveAgentView, fillViewport, type AgentViewportState } from "./live-agent-view.ts";
|
|
33
33
|
|
|
34
|
-
export { buildStatusTree, type StatusRow };
|
|
35
34
|
|
|
36
35
|
const ROOT = "/root";
|
|
37
36
|
const INACTIVE_STATES = new Set(["paused", "stopped", "failed", "completed"]);
|
|
@@ -81,7 +80,7 @@ export interface AgentTreeState {
|
|
|
81
80
|
collapsed: Set<string>;
|
|
82
81
|
}
|
|
83
82
|
|
|
84
|
-
|
|
83
|
+
class StatusDialog {
|
|
85
84
|
private state: AgentTreeState;
|
|
86
85
|
private get selectedPath(): string {
|
|
87
86
|
return this.state.selectedPath;
|
|
@@ -530,7 +529,7 @@ export class AgentNavigationController {
|
|
|
530
529
|
|
|
531
530
|
const navigationByService = new WeakMap<ThreadService, AgentNavigationController>();
|
|
532
531
|
|
|
533
|
-
|
|
532
|
+
async function showAgentTree(
|
|
534
533
|
ctx: ExtensionContext,
|
|
535
534
|
service: ThreadService,
|
|
536
535
|
selectedPath?: string,
|
package/src/subagents/ui/ui.ts
CHANGED
|
@@ -55,7 +55,7 @@ export type ThreadController = Pick<
|
|
|
55
55
|
>;
|
|
56
56
|
|
|
57
57
|
/** Plain terminal-safe text: never pass agent-supplied terminal commands through. */
|
|
58
|
-
|
|
58
|
+
function sanitizeText(text: string): string {
|
|
59
59
|
return stripTerminalSequences(text).replace(/[\x00-\x1f\x7f-\x9f]/g, " ");
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -200,7 +200,7 @@ function isLive(thread: ThreadView): boolean {
|
|
|
200
200
|
);
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
function renderThreads(
|
|
204
204
|
threads: ThreadView[],
|
|
205
205
|
width: number,
|
|
206
206
|
theme: AgentBadgeTheme,
|
|
@@ -453,7 +453,7 @@ function takeWidgetRows(rows: StatusRow[], budget: number): StatusRow[] {
|
|
|
453
453
|
}
|
|
454
454
|
|
|
455
455
|
/** Themed agent tree above the editor. Synthetic /root is the main conversation and is omitted. */
|
|
456
|
-
|
|
456
|
+
function renderAgentTree(
|
|
457
457
|
threads: ThreadView[],
|
|
458
458
|
width: number,
|
|
459
459
|
theme: AgentBadgeTheme,
|
|
@@ -493,7 +493,7 @@ export function renderAgentTree(
|
|
|
493
493
|
}
|
|
494
494
|
|
|
495
495
|
/** One-line counts for real agents; starting agents count as running, including tokens. */
|
|
496
|
-
|
|
496
|
+
function renderAgentSummary(
|
|
497
497
|
threads: ThreadView[],
|
|
498
498
|
width: number,
|
|
499
499
|
theme: Pick<Theme, "fg">,
|
|
@@ -682,7 +682,7 @@ export async function showThreads(
|
|
|
682
682
|
}
|
|
683
683
|
|
|
684
684
|
/** Parse editor argv, rejecting shell operators instead of evaluating a shell command. */
|
|
685
|
-
|
|
685
|
+
function editorArguments(command: string): string[] {
|
|
686
686
|
const args = parseShell(command, process.env);
|
|
687
687
|
if (!args.length || args.some((arg) => typeof arg !== "string") || !args[0]) {
|
|
688
688
|
throw new Error(
|