@narumitw/pi-subagents 3.2.0 → 3.3.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/README.md CHANGED
@@ -16,6 +16,7 @@ Pi Subagents runs Pi jobs in separate child processes and supports authenticated
16
16
  - Gives every child `subagent_wait` for an answer to a child-originated request.
17
17
  - Lets the main agent question a queued or running job through Pi RPC steering without retaining the child after completion.
18
18
  - Publishes one asynchronous terminal completion and shows active-job progress above the editor.
19
+ - Provides a job manager with confirmed cancellation and immediate widget display settings.
19
20
  - Exposes privacy-filtered metadata without task text, output, prompts, selected tools, or broker credentials.
20
21
  - Cancels session-owned work and closes the broker during replacement, reload, or shutdown.
21
22
 
@@ -62,17 +63,36 @@ Call `subagent_spawn` with a self-contained task and only the work tools that ta
62
63
  The call returns a `jobId` immediately, and the job continues in the background.
63
64
  Continue useful main-agent work until the result is required or a completion arrives.
64
65
 
66
+ In TUI mode, each active-job row shows a preview of its existing `task` after the job ID by default; no new parameter is required.
67
+ The preview removes terminal controls, collapses whitespace, and uses at most 40 terminal columns, including an ellipsis when shortened.
68
+ It describes the assigned task, not live child activity; the full task sent to the child is unchanged.
69
+ Task text is visible in this local widget, but `subagent_inspect` continues to omit it.
70
+ Rows are clipped to the available terminal width, so trailing state, timing, or tools can be hidden on narrow terminals.
71
+
65
72
  Use messaging only when needed:
66
73
 
67
74
  - Call `subagent_send` with `recipient: jobId` to ask an active child a question.
68
75
  - If `subagent_wait` returns `reason: "subagent_message"`, handle the visible request or response and wait for the job again only when needed.
69
76
  - Answer a child-originated request by calling `subagent_send` with its `requestId`.
70
77
 
71
- Completion messages follow Pi's global tool-output expansion state and the `app.tools.expand` binding (`Ctrl+O` by default).
78
+ Completion messages and child requests and responses follow Pi's global tool-output expansion state and the `app.tools.expand` binding (`Ctrl+O` by default).
79
+ Collapsed messages show a summary and the configured expansion key; expanded messages show the full content.
80
+ This display state does not change the content sent to the model.
72
81
 
73
82
  In TUI mode, the above-editor widget shows each queued or running job's ID, state, elapsed time, timeout, and selected core and extension tool names.
74
83
  It omits fixed communication tools, attachment paths, and resource totals, disappears when no jobs remain active, and clears when the session ends.
75
84
 
85
+ ## 💬 Commands
86
+
87
+ `/subagents` opens a manager for current-session job metadata, confirmed cancellation of active jobs, display settings, status, and help.
88
+ `/subagents settings` opens the same settings screen directly.
89
+ These are the only accepted routes; unknown arguments and trailing input are rejected.
90
+
91
+ The manager supports TUI and RPC modes. RPC uses dialogs and reports the settings file path instead of opening a settings editor.
92
+ Print and JSON modes reject these interactive commands with an error; the model tools remain available.
93
+ Job lists and details always show job IDs, regardless of widget preferences, and omit task text and child output.
94
+ Jobs do not survive reload or session replacement.
95
+
76
96
  ## 🛠️ Tools
77
97
 
78
98
  The main Pi session exposes five fixed tools:
@@ -113,6 +133,30 @@ The terminal states are `completed`, `partial`, `failed`, `timed_out`, and `canc
113
133
 
114
134
  See [`docs/tools.md`](./docs/tools.md) for the concise schema reference, [`docs/attachments.md`](./docs/attachments.md) for attachment validation, and [`docs/messaging.md`](./docs/messaging.md) for messaging behavior.
115
135
 
136
+ ## ⚙️ Settings
137
+
138
+ Use `/subagents settings` or the manager's Settings action to edit user display preferences.
139
+ All four fields accept JSON booleans and default to `true`:
140
+
141
+ | Field | Effect |
142
+ | --- | --- |
143
+ | `showWidget` | Show the above-editor widget. |
144
+ | `showJobId` | Show job IDs in widget rows. |
145
+ | `showTools` | Show selected tool names in widget rows. |
146
+ | `showTiming` | Show elapsed time and timeout in widget rows. |
147
+
148
+ Successful UI saves apply immediately. Hiding the widget does not stop jobs, change model tools, or suppress completion messages.
149
+ Showing it again displays currently active jobs with the other saved preferences. Job state and the task preview remain visible when the widget is enabled.
150
+ Escape returns or closes without undoing completed saves; Ctrl+C closes the entire flow.
151
+ Settings uses Pi's configured selection keys. Its footer shows only the hard-cancel key to avoid displaying fixed keys that do not match your bindings.
152
+
153
+ Preferences are saved in `<getAgentDir()>/pi-subagents.json`, normally `~/.pi/agent/pi-subagents.json`.
154
+ Only user settings are supported; project files and legacy settings are not read. User fields override code defaults.
155
+ Manual file edits are re-read on `session_start`, including `/reload`, and before each UI save.
156
+ Reading defaults creates no file. Invalid values produce diagnostics and use their defaults; malformed documents block saves until repaired.
157
+ Saves preserve unknown fields, publish atomically, and keep reads and writes ordered within one Pi process. Separate Pi processes are not locked.
158
+ A failed save keeps the previous effective preference and reports the error.
159
+
116
160
  ## ⚙️ Job configuration
117
161
 
118
162
  The task should state the child's role, objective, scope, constraints, and expected result.
@@ -188,7 +232,8 @@ Use these replacements where the new job model supports the previous intent:
188
232
  | Running main-to-child questions | Main and child `subagent_send` |
189
233
 
190
234
  The version 3 `subagent_send` contracts are not compatible with the legacy retained-agent follow-up tool of the same name.
191
- The `/subagents` command, extension settings, legacy retained follow-ups, `subagent_mailbox`, `subagent_consult`, custom agent catalogs, advanced orchestration, alternate transports, trust-aware cwd policy, and extension-owned worktrees have no direct replacement.
235
+ The current `/subagents` manager provides job metadata, cancellation, and widget preferences; it does not restore the legacy management workflows or settings.
236
+ Legacy retained follow-ups, `subagent_mailbox`, `subagent_consult`, custom agent catalogs, advanced orchestration, alternate transports, trust-aware cwd policy, and extension-owned worktrees have no direct replacement.
192
237
  Describe the child's specialization in `task` and grant only the required work tools through `tools`.
193
238
 
194
239
  ## 🔒 Security and privacy
@@ -236,7 +281,10 @@ Parallel writers require disjoint ownership or workspace isolation outside this
236
281
  packages/pi-subagents/
237
282
  ├── src/ # Authoritative implementation and helpers
238
283
  │ ├── index.ts # Thin Pi entrypoint
239
- │ └── subagents.ts # Job, broker, and child lifecycle
284
+ │ ├── subagents.ts # Commands and session lifecycle
285
+ │ ├── menu.ts # Job manager
286
+ │ ├── settings.ts # User preferences and atomic storage
287
+ │ └── settings-ui.ts # Native settings editor
240
288
  ├── dist/ # Generated Jiti runtime, child bridge, and readiness probe
241
289
  ├── scripts/build-runtime.mjs # Runtime builder
242
290
  ├── docs/ # Published reference documentation
@@ -0,0 +1,42 @@
1
+ // @generated by scripts/build-runtime.mjs; do not edit.
2
+ // @ts-nocheck -- generated entries use .ts extensions for Pi's Jiti loader.
3
+
4
+ // src/types.ts
5
+ var JOB_STATES = ["queued", "running", "completed", "partial", "failed", "timed_out", "cancelled"];
6
+ var TERMINAL_JOB_STATES = /* @__PURE__ */ new Set([
7
+ "completed",
8
+ "partial",
9
+ "failed",
10
+ "timed_out",
11
+ "cancelled"
12
+ ]);
13
+ var CHILD_CORE_TOOL_NAMES = [
14
+ "read",
15
+ "bash",
16
+ "powershell",
17
+ "edit",
18
+ "write",
19
+ "grep",
20
+ "find",
21
+ "ls",
22
+ "codemode"
23
+ ];
24
+ var DEFAULT_SUBAGENT_TOOLS = ["read", "grep", "find", "ls"];
25
+ var SUBAGENT_THINKING_LEVELS = [
26
+ "off",
27
+ "minimal",
28
+ "low",
29
+ "medium",
30
+ "high",
31
+ "xhigh",
32
+ "max"
33
+ ];
34
+
35
+ export {
36
+ JOB_STATES,
37
+ TERMINAL_JOB_STATES,
38
+ CHILD_CORE_TOOL_NAMES,
39
+ DEFAULT_SUBAGENT_TOOLS,
40
+ SUBAGENT_THINKING_LEVELS
41
+ };
42
+ //# sourceMappingURL=chunk-FY5HIU6Y.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/types.ts"],
4
+ "sourcesContent": ["import type { ModelThinkingLevel } from \"@earendil-works/pi-ai\";\nimport type { ExtensionAttachment } from \"./resource-attachments.js\";\n\nexport const JOB_STATES = [\"queued\", \"running\", \"completed\", \"partial\", \"failed\", \"timed_out\", \"cancelled\"] as const;\n\nexport type SubagentJobState = (typeof JOB_STATES)[number];\n\nexport const TERMINAL_JOB_STATES = new Set<SubagentJobState>([\n \"completed\",\n \"partial\",\n \"failed\",\n \"timed_out\",\n \"cancelled\",\n]);\n\nexport const CHILD_CORE_TOOL_NAMES = [\n \"read\",\n \"bash\",\n \"powershell\",\n \"edit\",\n \"write\",\n \"grep\",\n \"find\",\n \"ls\",\n \"codemode\",\n] as const;\nexport const DEFAULT_SUBAGENT_TOOLS = [\"read\", \"grep\", \"find\", \"ls\"] as const;\nexport const SUBAGENT_THINKING_LEVELS = [\n \"off\",\n \"minimal\",\n \"low\",\n \"medium\",\n \"high\",\n \"xhigh\",\n \"max\",\n] as const satisfies readonly ModelThinkingLevel[];\nexport type SubagentThinkingLevel = (typeof SUBAGENT_THINKING_LEVELS)[number];\n\nexport interface ChildResult {\n state: Extract<SubagentJobState, \"completed\" | \"partial\" | \"failed\" | \"timed_out\" | \"cancelled\">;\n result?: string;\n error?: string;\n limitations: string[];\n truncated: boolean;\n}\n\nexport interface BrokerCredentials {\n host: \"127.0.0.1\";\n port: number;\n token: string;\n}\n\nexport interface ChildControl {\n send(message: string, signal?: AbortSignal): Promise<void>;\n}\n\nexport interface ChildRequest {\n task: string;\n tools: string[];\n skills: string[];\n extensions: ExtensionAttachment[];\n toolSources: Record<string, string[]>;\n model: string;\n thinkingLevel: SubagentThinkingLevel;\n cwd: string;\n timeout?: number;\n projectTrusted: boolean;\n communication: BrokerCredentials;\n signal: AbortSignal;\n onControl?: (control: ChildControl) => void;\n}\n\nexport interface JobSummary {\n jobId: string;\n state: SubagentJobState;\n createdAt: number;\n startedAt?: number;\n finishedAt?: number;\n timeout?: number;\n resultSummary?: string;\n errorSummary?: string;\n}\n"],
5
+ "mappings": ";;;;AAGO,IAAM,aAAa,CAAC,UAAU,WAAW,aAAa,WAAW,UAAU,aAAa,WAAW;AAInG,IAAM,sBAAsB,oBAAI,IAAsB;AAAA,EAC3D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACO,IAAM,yBAAyB,CAAC,QAAQ,QAAQ,QAAQ,IAAI;AAC5D,IAAM,2BAA2B;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,171 @@
1
+ // @generated by scripts/build-runtime.mjs; do not edit.
2
+ // @ts-nocheck -- generated entries use .ts extensions for Pi's Jiti loader.
3
+ import {
4
+ JOB_STATES
5
+ } from "./chunk-FY5HIU6Y.ts";
6
+
7
+ // src/menu.ts
8
+ import { defineMenu, runConfirmation, runMenu, runTask, sanitizeTerminalText } from "@narumitw/pi-tui-kit";
9
+ async function showSubagentsMenu(ctx, options) {
10
+ if (options.signal.aborted || !options.isCurrent()) return;
11
+ let selectedJobId;
12
+ const current = (signal) => !signal.aborted && options.isCurrent();
13
+ const menu = defineMenu({
14
+ start: "main",
15
+ screens: {
16
+ main: ({ state }) => ({
17
+ kind: "actions",
18
+ title: "Subagents",
19
+ lines: [jobCounts(state)],
20
+ items: [
21
+ { id: "jobs", label: "Jobs", to: "jobs" },
22
+ { id: "settings", label: "Settings", action: "settings" },
23
+ { id: "status", label: "Status", to: "status" },
24
+ { id: "help", label: "Help", to: "help" },
25
+ { id: "close", label: "Close", close: true }
26
+ ],
27
+ hint: "close"
28
+ }),
29
+ jobs: ({ state }) => ({
30
+ kind: "choice",
31
+ title: "Subagent Jobs",
32
+ lines: [jobCounts(state), ...state.jobs.length === 0 ? ["No jobs in this session."] : []],
33
+ items: state.jobs.map((job) => ({
34
+ id: job.jobId,
35
+ label: sanitizeTerminalText(job.jobId),
36
+ description: sanitizeTerminalText(job.state)
37
+ })),
38
+ action: "selectJob",
39
+ hint: "back"
40
+ }),
41
+ job: ({ state }) => {
42
+ const job = state.jobs.find((candidate) => candidate.jobId === selectedJobId);
43
+ return {
44
+ kind: "actions",
45
+ title: "Subagent Job",
46
+ lines: job ? jobLines(job) : ["This job is no longer available."],
47
+ items: job && isActive(job) ? [{ id: "cancel", label: "Cancel job\u2026", action: "cancelJob" }] : [],
48
+ hint: "back"
49
+ };
50
+ },
51
+ status: ({ state }) => ({
52
+ kind: "detail",
53
+ title: "Subagents Status",
54
+ lines: [
55
+ "Runtime: active for this session",
56
+ `Retained jobs: ${state.jobs.length}`,
57
+ `Active jobs: ${state.jobs.filter(isActive).length}`,
58
+ `Omitted jobs: ${state.omitted}`,
59
+ ...JOB_STATES.map((status) => `${status}: ${state.jobs.filter((job) => job.state === status).length}`),
60
+ `Show widget: ${state.settings.showWidget}`,
61
+ `Show job ID: ${state.settings.showJobId}`,
62
+ `Show tools: ${state.settings.showTools}`,
63
+ `Show timing: ${state.settings.showTiming}`,
64
+ `User settings: ${sanitizeTerminalText(state.settingsPath)}`
65
+ ],
66
+ hint: "back"
67
+ }),
68
+ help: () => ({
69
+ kind: "detail",
70
+ title: "Subagents Help",
71
+ lines: [
72
+ "/subagents opens the job manager.",
73
+ "/subagents settings opens the same settings screen directly.",
74
+ "Jobs shows this session's job summaries. Only queued or running jobs can be cancelled, after confirmation.",
75
+ "The model uses subagent_spawn to start jobs, subagent_inspect to check them, and subagent_wait to wait for results.",
76
+ "subagent_send sends a message; subagent_cancel cancels a job.",
77
+ "Jobs are session-only. The runtime retains up to 32 terminal jobs for up to 24 hours.",
78
+ "Settings changes display preferences only. All four settings default to true.",
79
+ "Successful saves apply immediately. Use /reload after manual settings edits.",
80
+ "In RPC mode, Settings reports the user settings file path."
81
+ ],
82
+ hint: "back"
83
+ })
84
+ },
85
+ actions: {
86
+ selectJob: ({ itemId, signal }) => {
87
+ if (!current(signal)) return { kind: "close" };
88
+ if (!options.getState().jobs.some((job) => job.jobId === itemId)) return { kind: "stay" };
89
+ selectedJobId = itemId;
90
+ return { kind: "to", screen: "job" };
91
+ },
92
+ settings: async ({ signal }) => {
93
+ if (!current(signal)) return { kind: "close" };
94
+ const reason = await options.showSettings(signal);
95
+ return { kind: current(signal) && reason === "back" ? "stay" : "close" };
96
+ },
97
+ cancelJob: async ({ signal }) => {
98
+ if (!current(signal)) return { kind: "close" };
99
+ const jobId = selectedJobId;
100
+ const job = options.getState().jobs.find((candidate) => candidate.jobId === jobId);
101
+ if (!job || !isActive(job)) return { kind: "stay" };
102
+ const confirmation = await runConfirmation(ctx, {
103
+ title: "Cancel subagent job?",
104
+ message: `Cancel job ${sanitizeTerminalText(job.jobId)} (${sanitizeTerminalText(job.state)})?`,
105
+ confirmLabel: "Cancel job",
106
+ cancelLabel: "Keep job",
107
+ signal,
108
+ isCurrent: options.isCurrent,
109
+ onError: () => void 0
110
+ });
111
+ if (!current(signal)) return { kind: "close" };
112
+ if (confirmation.kind === "closed") {
113
+ return { kind: confirmation.reason === "close" ? "close" : "stay" };
114
+ }
115
+ if (confirmation.kind === "error") throw confirmation.error;
116
+ if (confirmation.kind !== "confirmed") return { kind: "close" };
117
+ const latest = options.getState().jobs.find((candidate) => candidate.jobId === jobId);
118
+ if (selectedJobId !== jobId || !latest || !isActive(latest)) return { kind: "stay" };
119
+ const result = await runTask(ctx, {
120
+ label: "Cancelling subagent job\u2026",
121
+ signal,
122
+ isCurrent: options.isCurrent,
123
+ task: ({ signal: taskSignal }) => options.cancelJob(latest.jobId, taskSignal),
124
+ onError: () => void 0
125
+ });
126
+ if (!current(signal)) return { kind: "close" };
127
+ if (result.kind === "error") throw result.error;
128
+ if (result.kind === "stale") return { kind: "close" };
129
+ if (result.kind === "cancelled") return { kind: "close" };
130
+ return { kind: "stay" };
131
+ }
132
+ }
133
+ });
134
+ await runMenu(ctx, menu, {
135
+ signal: options.signal,
136
+ isCurrent: options.isCurrent,
137
+ getState: options.getState,
138
+ onError: (_ctx, error) => {
139
+ if (!options.signal.aborted && options.isCurrent() && ctx.hasUI) {
140
+ ctx.ui.notify(
141
+ sanitizeTerminalText(`Subagents menu failed: ${error instanceof Error ? error.message : String(error)}`),
142
+ "error"
143
+ );
144
+ }
145
+ }
146
+ });
147
+ }
148
+ function isActive(job) {
149
+ return job.state === "queued" || job.state === "running";
150
+ }
151
+ function jobCounts(state) {
152
+ return `Jobs: ${state.jobs.length} shown, ${state.jobs.filter(isActive).length} active, ${state.omitted} omitted`;
153
+ }
154
+ function jobLines(job) {
155
+ return [
156
+ `Job ID: ${sanitizeTerminalText(job.jobId)}`,
157
+ `State: ${sanitizeTerminalText(job.state)}`,
158
+ `Created: ${timestamp(job.createdAt)}`,
159
+ ...job.startedAt === void 0 ? [] : [`Started: ${timestamp(job.startedAt)}`],
160
+ ...job.finishedAt === void 0 ? [] : [`Finished: ${timestamp(job.finishedAt)}`],
161
+ ...job.timeout === void 0 ? [] : [`Timeout: ${job.timeout} seconds`]
162
+ ];
163
+ }
164
+ function timestamp(value) {
165
+ const date = new Date(value);
166
+ return Number.isFinite(date.getTime()) ? date.toISOString() : "unknown";
167
+ }
168
+ export {
169
+ showSubagentsMenu
170
+ };
171
+ //# sourceMappingURL=menu-KAIUEWL7.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/menu.ts"],
4
+ "sourcesContent": ["import type { ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport { defineMenu, runConfirmation, runMenu, runTask, sanitizeTerminalText } from \"@narumitw/pi-tui-kit\";\nimport type { SubagentSettings } from \"./settings.js\";\nimport { JOB_STATES, type JobSummary } from \"./types.js\";\n\ninterface SubagentsMenuState {\n settings: SubagentSettings;\n settingsPath: string;\n jobs: JobSummary[];\n omitted: number;\n}\n\ninterface SubagentsMenuOptions {\n signal: AbortSignal;\n isCurrent(): boolean;\n getState(): SubagentsMenuState;\n cancelJob(jobId: string, signal: AbortSignal): Promise<void>;\n showSettings(signal: AbortSignal): Promise<\"back\" | \"close\" | \"stale\">;\n}\n\nexport async function showSubagentsMenu(ctx: ExtensionCommandContext, options: SubagentsMenuOptions): Promise<void> {\n if (options.signal.aborted || !options.isCurrent()) return;\n let selectedJobId: string | undefined;\n const current = (signal: AbortSignal) => !signal.aborted && options.isCurrent();\n const menu = defineMenu<\n SubagentsMenuState,\n \"main\" | \"jobs\" | \"job\" | \"status\" | \"help\",\n \"selectJob\" | \"cancelJob\" | \"settings\"\n >({\n start: \"main\",\n screens: {\n main: ({ state }) => ({\n kind: \"actions\",\n title: \"Subagents\",\n lines: [jobCounts(state)],\n items: [\n { id: \"jobs\", label: \"Jobs\", to: \"jobs\" },\n { id: \"settings\", label: \"Settings\", action: \"settings\" },\n { id: \"status\", label: \"Status\", to: \"status\" },\n { id: \"help\", label: \"Help\", to: \"help\" },\n { id: \"close\", label: \"Close\", close: true },\n ],\n hint: \"close\",\n }),\n jobs: ({ state }) => ({\n kind: \"choice\",\n title: \"Subagent Jobs\",\n lines: [jobCounts(state), ...(state.jobs.length === 0 ? [\"No jobs in this session.\"] : [])],\n items: state.jobs.map((job) => ({\n id: job.jobId,\n label: sanitizeTerminalText(job.jobId),\n description: sanitizeTerminalText(job.state),\n })),\n action: \"selectJob\",\n hint: \"back\",\n }),\n job: ({ state }) => {\n const job = state.jobs.find((candidate) => candidate.jobId === selectedJobId);\n return {\n kind: \"actions\",\n title: \"Subagent Job\",\n lines: job ? jobLines(job) : [\"This job is no longer available.\"],\n items: job && isActive(job) ? [{ id: \"cancel\", label: \"Cancel job\u2026\", action: \"cancelJob\" }] : [],\n hint: \"back\",\n };\n },\n status: ({ state }) => ({\n kind: \"detail\",\n title: \"Subagents Status\",\n lines: [\n \"Runtime: active for this session\",\n `Retained jobs: ${state.jobs.length}`,\n `Active jobs: ${state.jobs.filter(isActive).length}`,\n `Omitted jobs: ${state.omitted}`,\n ...JOB_STATES.map((status) => `${status}: ${state.jobs.filter((job) => job.state === status).length}`),\n `Show widget: ${state.settings.showWidget}`,\n `Show job ID: ${state.settings.showJobId}`,\n `Show tools: ${state.settings.showTools}`,\n `Show timing: ${state.settings.showTiming}`,\n `User settings: ${sanitizeTerminalText(state.settingsPath)}`,\n ],\n hint: \"back\",\n }),\n help: () => ({\n kind: \"detail\",\n title: \"Subagents Help\",\n lines: [\n \"/subagents opens the job manager.\",\n \"/subagents settings opens the same settings screen directly.\",\n \"Jobs shows this session's job summaries. Only queued or running jobs can be cancelled, after confirmation.\",\n \"The model uses subagent_spawn to start jobs, subagent_inspect to check them, and subagent_wait to wait for results.\",\n \"subagent_send sends a message; subagent_cancel cancels a job.\",\n \"Jobs are session-only. The runtime retains up to 32 terminal jobs for up to 24 hours.\",\n \"Settings changes display preferences only. All four settings default to true.\",\n \"Successful saves apply immediately. Use /reload after manual settings edits.\",\n \"In RPC mode, Settings reports the user settings file path.\",\n ],\n hint: \"back\",\n }),\n },\n actions: {\n selectJob: ({ itemId, signal }) => {\n if (!current(signal)) return { kind: \"close\" };\n if (!options.getState().jobs.some((job) => job.jobId === itemId)) return { kind: \"stay\" };\n selectedJobId = itemId;\n return { kind: \"to\", screen: \"job\" };\n },\n settings: async ({ signal }) => {\n if (!current(signal)) return { kind: \"close\" };\n const reason = await options.showSettings(signal);\n return { kind: current(signal) && reason === \"back\" ? \"stay\" : \"close\" };\n },\n cancelJob: async ({ signal }) => {\n if (!current(signal)) return { kind: \"close\" };\n const jobId = selectedJobId;\n const job = options.getState().jobs.find((candidate) => candidate.jobId === jobId);\n if (!job || !isActive(job)) return { kind: \"stay\" };\n const confirmation = await runConfirmation(ctx, {\n title: \"Cancel subagent job?\",\n message: `Cancel job ${sanitizeTerminalText(job.jobId)} (${sanitizeTerminalText(job.state)})?`,\n confirmLabel: \"Cancel job\",\n cancelLabel: \"Keep job\",\n signal,\n isCurrent: options.isCurrent,\n onError: () => undefined,\n });\n if (!current(signal)) return { kind: \"close\" };\n if (confirmation.kind === \"closed\") {\n return { kind: confirmation.reason === \"close\" ? \"close\" : \"stay\" };\n }\n if (confirmation.kind === \"error\") throw confirmation.error;\n if (confirmation.kind !== \"confirmed\") return { kind: \"close\" };\n // The runtime can finish or remove a job while its confirmation is open.\n const latest = options.getState().jobs.find((candidate) => candidate.jobId === jobId);\n if (selectedJobId !== jobId || !latest || !isActive(latest)) return { kind: \"stay\" };\n const result = await runTask(ctx, {\n label: \"Cancelling subagent job\u2026\",\n signal,\n isCurrent: options.isCurrent,\n task: ({ signal: taskSignal }) => options.cancelJob(latest.jobId, taskSignal),\n onError: () => undefined,\n });\n if (!current(signal)) return { kind: \"close\" };\n if (result.kind === \"error\") throw result.error;\n if (result.kind === \"stale\") return { kind: \"close\" };\n if (result.kind === \"cancelled\") return { kind: \"close\" };\n return { kind: \"stay\" };\n },\n },\n });\n await runMenu(ctx, menu, {\n signal: options.signal,\n isCurrent: options.isCurrent,\n getState: options.getState,\n onError: (_ctx, error) => {\n if (!options.signal.aborted && options.isCurrent() && ctx.hasUI) {\n ctx.ui.notify(\n sanitizeTerminalText(`Subagents menu failed: ${error instanceof Error ? error.message : String(error)}`),\n \"error\",\n );\n }\n },\n });\n}\n\nfunction isActive(job: JobSummary): boolean {\n return job.state === \"queued\" || job.state === \"running\";\n}\n\nfunction jobCounts(state: SubagentsMenuState): string {\n return `Jobs: ${state.jobs.length} shown, ${state.jobs.filter(isActive).length} active, ${state.omitted} omitted`;\n}\n\nfunction jobLines(job: JobSummary): string[] {\n return [\n `Job ID: ${sanitizeTerminalText(job.jobId)}`,\n `State: ${sanitizeTerminalText(job.state)}`,\n `Created: ${timestamp(job.createdAt)}`,\n ...(job.startedAt === undefined ? [] : [`Started: ${timestamp(job.startedAt)}`]),\n ...(job.finishedAt === undefined ? [] : [`Finished: ${timestamp(job.finishedAt)}`]),\n ...(job.timeout === undefined ? [] : [`Timeout: ${job.timeout} seconds`]),\n ];\n}\n\nfunction timestamp(value: number): string {\n const date = new Date(value);\n return Number.isFinite(date.getTime()) ? date.toISOString() : \"unknown\";\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,196 @@
1
+ // @generated by scripts/build-runtime.mjs; do not edit.
2
+ // @ts-nocheck -- generated entries use .ts extensions for Pi's Jiti loader.
3
+
4
+ // src/settings-ui.ts
5
+ import { stripVTControlCharacters } from "node:util";
6
+ import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
7
+ import {
8
+ Key,
9
+ matchesKey,
10
+ SettingsList,
11
+ Text,
12
+ truncateToWidth
13
+ } from "@earendil-works/pi-tui";
14
+ import { HorizontalRule, renderBoundedFrame, runCustomInteraction, sanitizeTerminalText } from "@narumitw/pi-tui-kit";
15
+ var SETTINGS = [
16
+ { key: "showWidget", label: "Show widget", description: "Show subagent jobs above the editor." },
17
+ { key: "showJobId", label: "Show job ID", description: "Show job IDs in the widget." },
18
+ { key: "showTools", label: "Show tools", description: "Show active tools in the widget." },
19
+ { key: "showTiming", label: "Show timing", description: "Show job timing in the widget." }
20
+ ];
21
+ async function showSubagentSettings(ctx, options) {
22
+ const result = await runCustomInteraction(ctx, {
23
+ signal: options.signal,
24
+ isCurrent: options.isCurrent,
25
+ // The command owner reports interaction failures and non-TUI settings paths.
26
+ onError: () => {
27
+ },
28
+ create({ tui, theme, signal, complete }) {
29
+ let queue = Promise.resolve();
30
+ const current = () => !signal.aborted && options.isCurrent();
31
+ const revisions = /* @__PURE__ */ new Map();
32
+ const pending = /* @__PURE__ */ new Map();
33
+ const mouseRows = /* @__PURE__ */ new Map();
34
+ let renderedWidth = 0;
35
+ let contentHeight = 0;
36
+ let mousePressed = false;
37
+ const reportFailure = (message) => {
38
+ if (!current()) return;
39
+ try {
40
+ ctx.ui.notify(sanitizeTerminalText(`Could not save pi-subagents.json: ${message}`), "error");
41
+ } catch {
42
+ }
43
+ };
44
+ const rule = new HorizontalRule({ ruleStyle: (text) => theme.fg("border", text) });
45
+ const listTheme = {
46
+ ...getSettingsListTheme(),
47
+ label: (text, selected) => selected ? theme.fg("accent", text) : text,
48
+ value: (text, selected) => theme.fg(selected ? "accent" : "muted", text),
49
+ description: (text) => theme.fg("dim", text),
50
+ cursor: theme.fg("accent", "\u2192 "),
51
+ hint: (text) => theme.fg("dim", text)
52
+ };
53
+ const initial = options.get();
54
+ const items = SETTINGS.map(({ key, label, description }) => ({
55
+ id: key,
56
+ label,
57
+ description,
58
+ currentValue: String(initial[key]),
59
+ values: ["true", "false"]
60
+ }));
61
+ const list = new SettingsList(
62
+ items,
63
+ items.length,
64
+ listTheme,
65
+ (id, value) => {
66
+ if (!current()) return;
67
+ const key = id;
68
+ const revision = (revisions.get(id) ?? 0) + 1;
69
+ revisions.set(id, revision);
70
+ pending.set(id, revision);
71
+ queue = queue.then(async () => {
72
+ if (!current()) {
73
+ complete("stale");
74
+ return;
75
+ }
76
+ const before = { ...options.get() };
77
+ let effective = before;
78
+ try {
79
+ const settings = await options.save(key, value === "true", signal);
80
+ if (!options.isCurrent()) {
81
+ complete("stale");
82
+ return;
83
+ }
84
+ options.apply(settings);
85
+ effective = options.get();
86
+ } catch (error) {
87
+ if (!options.isCurrent()) {
88
+ complete("stale");
89
+ return;
90
+ }
91
+ let message = error instanceof Error ? error.message : String(error);
92
+ try {
93
+ options.apply(before);
94
+ } catch (rollbackError) {
95
+ message += `; could not restore effective settings: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`;
96
+ }
97
+ reportFailure(message);
98
+ }
99
+ if (!current()) return;
100
+ if (pending.get(id) === revision) pending.delete(id);
101
+ for (const { key: settingKey } of SETTINGS) {
102
+ if (!pending.has(settingKey)) list.updateValue(settingKey, String(effective[settingKey]));
103
+ }
104
+ tui.requestRender();
105
+ }).catch((error) => {
106
+ reportFailure(error instanceof Error ? error.message : String(error));
107
+ });
108
+ },
109
+ () => complete("back")
110
+ );
111
+ return {
112
+ render(width) {
113
+ if (!current()) complete("stale");
114
+ renderedWidth = Number.isFinite(width) ? Math.max(0, Math.floor(width)) : 0;
115
+ listTheme.cursor = theme.fg("accent", "\u2192 ");
116
+ const content = list.render(Math.max(4, renderedWidth)).slice(0, -2);
117
+ contentHeight = content.length;
118
+ const focusedRow = content.findIndex((line) => stripVTControlCharacters(line).startsWith("\u2192"));
119
+ mouseRows.clear();
120
+ const tokens = /* @__PURE__ */ new Map();
121
+ const layout = content.map((line, index) => {
122
+ if (!stripVTControlCharacters(line).trim()) return line;
123
+ const token = `content:${index}`;
124
+ tokens.set(token, index);
125
+ return token;
126
+ });
127
+ const terminalRows = Number.isFinite(tui.terminal.rows) ? Math.floor(tui.terminal.rows) : 24;
128
+ const frame = renderBoundedFrame({
129
+ width: Math.max(renderedWidth, `content:${content.length}`.length),
130
+ maxRows: Math.max(1, terminalRows - 3),
131
+ rule: rule.render(renderedWidth)[0] ?? "",
132
+ title: new Text(theme.fg("accent", theme.bold("Subagents Settings")), 0, 0).render(renderedWidth),
133
+ context: [theme.fg("muted", "Saved changes apply immediately.")],
134
+ content: layout,
135
+ hints: new Text(theme.fg("muted", "Ctrl+C close"), 0, 0).render(renderedWidth),
136
+ priorityRows: focusedRow < 0 ? [] : [focusedRow],
137
+ focusedRow
138
+ });
139
+ return frame.map((line, row) => {
140
+ const nativeRow = tokens.get(line);
141
+ if (nativeRow !== void 0) mouseRows.set(row, nativeRow);
142
+ return truncateToWidth(nativeRow === void 0 ? line : content[nativeRow] ?? "", renderedWidth, "");
143
+ });
144
+ },
145
+ invalidate() {
146
+ mouseRows.clear();
147
+ mousePressed = false;
148
+ list.invalidate();
149
+ },
150
+ handleInput(data) {
151
+ if (!current()) return complete("stale");
152
+ mouseRows.clear();
153
+ mousePressed = false;
154
+ if (matchesKey(data, Key.ctrl("c"))) complete("close");
155
+ else {
156
+ list.handleInput(data);
157
+ }
158
+ tui.requestRender();
159
+ },
160
+ handleMouse(event) {
161
+ if (!current()) {
162
+ complete("stale");
163
+ return;
164
+ }
165
+ const nativeRow = mouseRows.get(event.y);
166
+ if (nativeRow === void 0 || event.x < 0 || event.x >= renderedWidth) {
167
+ if (event.button === "left" && (event.type === "press" || event.type === "click")) mousePressed = false;
168
+ return;
169
+ }
170
+ const nativeEvent = { ...event, y: nativeRow, width: renderedWidth, height: contentHeight };
171
+ if (event.type === "click" && event.button === "left" && !mousePressed) {
172
+ list.handleMouse({ ...nativeEvent, type: "press" });
173
+ }
174
+ const result2 = list.handleMouse(nativeEvent);
175
+ if (event.type === "press" && event.button === "left") mousePressed = result2?.handled === true;
176
+ if (event.type === "click" || event.type === "wheel") mousePressed = false;
177
+ if (result2?.handled && result2.render !== false) tui.requestRender();
178
+ return result2;
179
+ },
180
+ waitForPending() {
181
+ return queue;
182
+ },
183
+ dispose() {
184
+ mouseRows.clear();
185
+ mousePressed = false;
186
+ }
187
+ };
188
+ }
189
+ });
190
+ if (result.kind === "error") throw result.error;
191
+ return result.kind === "completed" ? result.value : result.kind === "unsupported" ? "back" : "stale";
192
+ }
193
+ export {
194
+ showSubagentSettings
195
+ };
196
+ //# sourceMappingURL=settings-ui-PSEYB6JX.ts.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/settings-ui.ts"],
4
+ "sourcesContent": ["import { stripVTControlCharacters } from \"node:util\";\nimport { type ExtensionCommandContext, getSettingsListTheme } from \"@earendil-works/pi-coding-agent\";\nimport {\n Key,\n matchesKey,\n type SettingItem,\n SettingsList,\n Text,\n type TuiMouseEvent,\n truncateToWidth,\n} from \"@earendil-works/pi-tui\";\nimport { HorizontalRule, renderBoundedFrame, runCustomInteraction, sanitizeTerminalText } from \"@narumitw/pi-tui-kit\";\nimport type { SubagentSettings } from \"./settings.js\";\n\ninterface SubagentSettingsUIOptions {\n signal: AbortSignal;\n isCurrent(): boolean;\n get(): SubagentSettings;\n save(key: keyof SubagentSettings, value: boolean, signal: AbortSignal): Promise<SubagentSettings>;\n apply(settings: SubagentSettings): void;\n}\n\ntype CloseReason = \"back\" | \"close\" | \"stale\";\n\nconst SETTINGS: { key: keyof SubagentSettings; label: string; description: string }[] = [\n { key: \"showWidget\", label: \"Show widget\", description: \"Show subagent jobs above the editor.\" },\n { key: \"showJobId\", label: \"Show job ID\", description: \"Show job IDs in the widget.\" },\n { key: \"showTools\", label: \"Show tools\", description: \"Show active tools in the widget.\" },\n { key: \"showTiming\", label: \"Show timing\", description: \"Show job timing in the widget.\" },\n];\n\nexport async function showSubagentSettings(\n ctx: ExtensionCommandContext,\n options: SubagentSettingsUIOptions,\n): Promise<CloseReason> {\n const result = await runCustomInteraction<CloseReason>(ctx, {\n signal: options.signal,\n isCurrent: options.isCurrent,\n // The command owner reports interaction failures and non-TUI settings paths.\n onError: () => {},\n create({ tui, theme, signal, complete }) {\n let queue = Promise.resolve();\n const current = () => !signal.aborted && options.isCurrent();\n const revisions = new Map<string, number>();\n const pending = new Map<string, number>();\n const mouseRows = new Map<number, number>();\n let renderedWidth = 0;\n let contentHeight = 0;\n let mousePressed = false;\n const reportFailure = (message: string) => {\n if (!current()) return;\n try {\n ctx.ui.notify(sanitizeTerminalText(`Could not save pi-subagents.json: ${message}`), \"error\");\n } catch {\n // The host can dispose its notifier after the ownership check.\n }\n };\n const rule = new HorizontalRule({ ruleStyle: (text) => theme.fg(\"border\", text) });\n // Keep native SettingsList semantics, but use this screen's callback theme.\n const listTheme = {\n ...getSettingsListTheme(),\n label: (text: string, selected: boolean) => (selected ? theme.fg(\"accent\", text) : text),\n value: (text: string, selected: boolean) => theme.fg(selected ? \"accent\" : \"muted\", text),\n description: (text: string) => theme.fg(\"dim\", text),\n cursor: theme.fg(\"accent\", \"\u2192 \"),\n hint: (text: string) => theme.fg(\"dim\", text),\n };\n const initial = options.get();\n const items: SettingItem[] = SETTINGS.map(({ key, label, description }) => ({\n id: key,\n label,\n description,\n currentValue: String(initial[key]),\n values: [\"true\", \"false\"],\n }));\n const list = new SettingsList(\n items,\n items.length,\n listTheme,\n (id, value) => {\n if (!current()) return;\n const key = id as keyof SubagentSettings;\n const revision = (revisions.get(id) ?? 0) + 1;\n revisions.set(id, revision);\n pending.set(id, revision);\n queue = queue\n .then(async () => {\n if (!current()) {\n complete(\"stale\");\n return;\n }\n const before = { ...options.get() };\n let effective = before;\n try {\n const settings = await options.save(key, value === \"true\", signal);\n // A successful rename is durable, even when closing aborted the save signal.\n if (!options.isCurrent()) {\n complete(\"stale\");\n return;\n }\n options.apply(settings);\n effective = options.get();\n } catch (error) {\n if (!options.isCurrent()) {\n complete(\"stale\");\n return;\n }\n let message = error instanceof Error ? error.message : String(error);\n try {\n options.apply(before);\n } catch (rollbackError) {\n message += `; could not restore effective settings: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`;\n }\n reportFailure(message);\n }\n if (!current()) return;\n if (pending.get(id) === revision) pending.delete(id);\n // Saving rereads the full document, including unrelated manual edits. Keep newer\n // optimistic choices, but refresh every row with no outstanding user change.\n for (const { key: settingKey } of SETTINGS) {\n if (!pending.has(settingKey)) list.updateValue(settingKey, String(effective[settingKey]));\n }\n tui.requestRender();\n })\n .catch((error: unknown) => {\n // A failed action must not prevent later requests from entering the queue.\n reportFailure(error instanceof Error ? error.message : String(error));\n });\n },\n () => complete(\"back\"),\n );\n return {\n render(width: number) {\n if (!current()) complete(\"stale\");\n renderedWidth = Number.isFinite(width) ? Math.max(0, Math.floor(width)) : 0;\n listTheme.cursor = theme.fg(\"accent\", \"\u2192 \");\n // The native footer hardcodes keys. Advertise only the hard-cancel key owned here;\n // configured native navigation, change, and Back actions remain unchanged.\n const content = list.render(Math.max(4, renderedWidth)).slice(0, -2);\n contentHeight = content.length;\n const focusedRow = content.findIndex((line) => stripVTControlCharacters(line).startsWith(\"\u2192\"));\n mouseRows.clear();\n const tokens = new Map<string, number>();\n const layout = content.map((line, index) => {\n if (!stripVTControlCharacters(line).trim()) return line;\n const token = `content:${index}`;\n tokens.set(token, index);\n return token;\n });\n const terminalRows = Number.isFinite(tui.terminal.rows) ? Math.floor(tui.terminal.rows) : 24;\n const frame = renderBoundedFrame({\n width: Math.max(renderedWidth, `content:${content.length}`.length),\n maxRows: Math.max(1, terminalRows - 3),\n rule: rule.render(renderedWidth)[0] ?? \"\",\n title: new Text(theme.fg(\"accent\", theme.bold(\"Subagents Settings\")), 0, 0).render(renderedWidth),\n context: [theme.fg(\"muted\", \"Saved changes apply immediately.\")],\n content: layout,\n hints: new Text(theme.fg(\"muted\", \"Ctrl+C close\"), 0, 0).render(renderedWidth),\n priorityRows: focusedRow < 0 ? [] : [focusedRow],\n focusedRow,\n });\n return frame.map((line, row) => {\n const nativeRow = tokens.get(line);\n if (nativeRow !== undefined) mouseRows.set(row, nativeRow);\n return truncateToWidth(nativeRow === undefined ? line : (content[nativeRow] ?? \"\"), renderedWidth, \"\");\n });\n },\n invalidate() {\n mouseRows.clear();\n mousePressed = false;\n list.invalidate();\n },\n handleInput(data: string) {\n if (!current()) return complete(\"stale\");\n mouseRows.clear();\n mousePressed = false;\n if (matchesKey(data, Key.ctrl(\"c\"))) complete(\"close\");\n else {\n // SettingsList uses Pi's effective native keybindings for standard actions.\n list.handleInput(data);\n }\n tui.requestRender();\n },\n handleMouse(event: TuiMouseEvent) {\n if (!current()) {\n complete(\"stale\");\n return;\n }\n const nativeRow = mouseRows.get(event.y);\n if (nativeRow === undefined || event.x < 0 || event.x >= renderedWidth) {\n if (event.button === \"left\" && (event.type === \"press\" || event.type === \"click\")) mousePressed = false;\n return;\n }\n const nativeEvent = { ...event, y: nativeRow, width: renderedWidth, height: contentHeight };\n if (event.type === \"click\" && event.button === \"left\" && !mousePressed) {\n list.handleMouse({ ...nativeEvent, type: \"press\" });\n }\n const result = list.handleMouse(nativeEvent);\n if (event.type === \"press\" && event.button === \"left\") mousePressed = result?.handled === true;\n if (event.type === \"click\" || event.type === \"wheel\") mousePressed = false;\n if (result?.handled && result.render !== false) tui.requestRender();\n return result;\n },\n waitForPending() {\n return queue;\n },\n dispose() {\n mouseRows.clear();\n mousePressed = false;\n },\n };\n },\n });\n if (result.kind === \"error\") throw result.error;\n return result.kind === \"completed\" ? result.value : result.kind === \"unsupported\" ? \"back\" : \"stale\";\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["result"]
7
+ }