@herbertgao/pi-subagents 0.19.0 → 0.22.0-local.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/CHANGELOG.md +8 -0
- package/docs/packages.md +211 -0
- package/docs/rpc.md +61 -63
- package/docs/workflows.md +24 -16
- package/package.json +6 -5
- package/src/agent-file-toggle.ts +51 -14
- package/src/agent-manager.ts +123 -55
- package/src/agent-runner.ts +144 -13
- package/src/agent-source-badge.ts +78 -0
- package/src/completion-outbox.ts +122 -0
- package/src/custom-agents.ts +171 -79
- package/src/index.ts +597 -296
- package/src/package-resources.ts +724 -0
- package/src/schedule.ts +1 -1
- package/src/settings.ts +66 -0
- package/src/types.ts +26 -4
- package/src/ui/agent-widget.ts +2 -0
- package/src/ui/conversation-viewer.ts +354 -32
- package/src/ui/fleet-list.ts +33 -15
- package/src/workflow/host.ts +6 -0
- package/src/workflow/saved.ts +48 -2
package/src/schedule.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* of dispatching a user message — schedule fires bypass maxConcurrent so
|
|
13
13
|
* a 5-minute interval can't be deferred behind 4 long-running agents.
|
|
14
14
|
* - Result delivery is implicit: spawn → background completion → existing
|
|
15
|
-
* `subagent-notification`
|
|
15
|
+
* `subagent-notification` path. No new delivery code.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type {
|
package/src/settings.ts
CHANGED
|
@@ -294,6 +294,41 @@ export interface SubagentsSettings {
|
|
|
294
294
|
* narrow terminal.
|
|
295
295
|
*/
|
|
296
296
|
showModel?: boolean
|
|
297
|
+
/**
|
|
298
|
+
* Whether agents declared by installed pi packages load, and from which
|
|
299
|
+
* packages. Defaults to `true`.
|
|
300
|
+
*
|
|
301
|
+
* - `true` — every package whose `package.json` declares `pi.subagents`
|
|
302
|
+
* (or the top-level `pi-subagents` key) contributes its agents.
|
|
303
|
+
* - `false` — none do.
|
|
304
|
+
* - list — only packages matching an entry, compared case-insensitively
|
|
305
|
+
* against the unscoped short name (`@scope/foo` matches `foo`),
|
|
306
|
+
* the full package name, or the settings source string.
|
|
307
|
+
*
|
|
308
|
+
* On by default because installing is already the trust decision: pi executes
|
|
309
|
+
* an installed package's `extensions/` and injects its `skills/` into the
|
|
310
|
+
* system prompt with no further prompt, so a declared `.md` agent is strictly
|
|
311
|
+
* less privileged than what the same package already runs. The gate exists for
|
|
312
|
+
* users who want to narrow that anyway, not as a second install ritual.
|
|
313
|
+
*
|
|
314
|
+
* Package agents always load at the *lowest* precedence, below global and
|
|
315
|
+
* project files, so a same-named local agent silently takes the name back.
|
|
316
|
+
*/
|
|
317
|
+
packageAgents?: boolean | string[]
|
|
318
|
+
/**
|
|
319
|
+
* The same gate for workflow scripts declared by installed pi packages
|
|
320
|
+
* (`pi.subagents.workflows`). Defaults to `true`.
|
|
321
|
+
*
|
|
322
|
+
* Deliberately separate from `packageAgents` rather than folded into it: an
|
|
323
|
+
* agent is markdown we hand to a model, a workflow is `.js` we execute. Both
|
|
324
|
+
* still sit below the package's own `extensions/`, which pi runs unsandboxed,
|
|
325
|
+
* so the default matches — but a user who wants package agents without package
|
|
326
|
+
* workflow scripts can have that without giving up either wholesale.
|
|
327
|
+
*
|
|
328
|
+
* No-op when `workflowsEnabled` is off: the tool is never registered, so there
|
|
329
|
+
* is nothing to resolve a workflow name against.
|
|
330
|
+
*/
|
|
331
|
+
packageWorkflows?: boolean | string[]
|
|
297
332
|
/**
|
|
298
333
|
* How much of the conversation viewer's transcript renders as Markdown.
|
|
299
334
|
* Defaults to `assistant`. Applied live — the viewer's `m` key cycles this
|
|
@@ -337,6 +372,8 @@ export interface SettingsAppliers {
|
|
|
337
372
|
setShowCost: (b: boolean) => void
|
|
338
373
|
setShowModel: (b: boolean) => void
|
|
339
374
|
setViewerMarkdown: (mode: ViewerMarkdownMode) => void
|
|
375
|
+
setPackageAgents: (gate: boolean | string[]) => void
|
|
376
|
+
setPackageWorkflows: (gate: boolean | string[]) => void
|
|
340
377
|
}
|
|
341
378
|
|
|
342
379
|
/** Emit callback — a subset of `pi.events.emit` to keep helpers testable. */
|
|
@@ -367,6 +404,28 @@ const MAX_TURNS_CEILING = 10_000
|
|
|
367
404
|
const GRACE_TURNS_CEILING = 1_000
|
|
368
405
|
const SUBAGENT_DEPTH_CEILING = 16
|
|
369
406
|
|
|
407
|
+
/**
|
|
408
|
+
* Normalize a package gate: a boolean passes through, an array is trimmed to its
|
|
409
|
+
* non-empty string entries, a bare string becomes a one-entry allowlist, and
|
|
410
|
+
* anything else is dropped.
|
|
411
|
+
*
|
|
412
|
+
* The two coercions exist for the same reason: this setting's default is `true`,
|
|
413
|
+
* so *dropping* a malformed value silently admits every package — the opposite
|
|
414
|
+
* of what someone narrowing the gate meant. So an array that trims to empty is
|
|
415
|
+
* kept as `[]` (matching nothing, like `false`) rather than dropped, and
|
|
416
|
+
* `"my-package"` — the obvious typo for a one-entry list — is read as
|
|
417
|
+
* `["my-package"]` rather than widened to everything.
|
|
418
|
+
*/
|
|
419
|
+
function sanitizePackageGate(val: unknown): boolean | string[] | undefined {
|
|
420
|
+
if (typeof val === "boolean") return val
|
|
421
|
+
if (typeof val === "string") return val.trim() ? [val.trim()] : []
|
|
422
|
+
if (!Array.isArray(val)) return undefined
|
|
423
|
+
return val
|
|
424
|
+
.filter((e): e is string => typeof e === "string")
|
|
425
|
+
.map((e) => e.trim())
|
|
426
|
+
.filter(Boolean)
|
|
427
|
+
}
|
|
428
|
+
|
|
370
429
|
/** Drop fields that don't match the expected shape. Silent — garbage becomes absent. */
|
|
371
430
|
function sanitize(raw: unknown): SubagentsSettings {
|
|
372
431
|
if (!raw || typeof raw !== "object") return {}
|
|
@@ -482,6 +541,10 @@ function sanitize(raw: unknown): SubagentsSettings {
|
|
|
482
541
|
if (typeof r.workflowsEnabled === "boolean") {
|
|
483
542
|
out.workflowsEnabled = r.workflowsEnabled
|
|
484
543
|
}
|
|
544
|
+
const packageAgents = sanitizePackageGate(r.packageAgents)
|
|
545
|
+
if (packageAgents !== undefined) out.packageAgents = packageAgents
|
|
546
|
+
const packageWorkflows = sanitizePackageGate(r.packageWorkflows)
|
|
547
|
+
if (packageWorkflows !== undefined) out.packageWorkflows = packageWorkflows
|
|
485
548
|
if (r.fallbackSubagent === false) {
|
|
486
549
|
// The only non-string spelling worth accepting: a boolean would otherwise be
|
|
487
550
|
// dropped, silently leaving the PERMISSIVE default in place. Every string is
|
|
@@ -595,6 +658,9 @@ export function applySettings(
|
|
|
595
658
|
if (s.viewerMarkdown) appliers.setViewerMarkdown(s.viewerMarkdown)
|
|
596
659
|
if (typeof s.workflowsEnabled === "boolean")
|
|
597
660
|
appliers.setWorkflowsEnabled(s.workflowsEnabled)
|
|
661
|
+
if (s.packageAgents !== undefined) appliers.setPackageAgents(s.packageAgents)
|
|
662
|
+
if (s.packageWorkflows !== undefined)
|
|
663
|
+
appliers.setPackageWorkflows(s.packageWorkflows)
|
|
598
664
|
}
|
|
599
665
|
|
|
600
666
|
/**
|
package/src/types.ts
CHANGED
|
@@ -88,8 +88,12 @@ export interface AgentConfig {
|
|
|
88
88
|
isDefault?: boolean
|
|
89
89
|
/** false = agent is hidden from the registry */
|
|
90
90
|
enabled?: boolean
|
|
91
|
-
/**
|
|
92
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Where this agent was loaded from. `"package"` means an installed pi package
|
|
93
|
+
* declared it (see package-resources.ts) — those files live under pi's install
|
|
94
|
+
* root, so `/agents` treats them as read-only and offers Eject instead of Edit.
|
|
95
|
+
*/
|
|
96
|
+
source?: "default" | "project" | "global" | "package"
|
|
93
97
|
/** Path of the .md it was loaded from. Unset for embedded defaults. */
|
|
94
98
|
sourcePath?: string
|
|
95
99
|
}
|
|
@@ -157,6 +161,9 @@ export type MentionResolution =
|
|
|
157
161
|
| { kind: "live"; record: AgentRecord }
|
|
158
162
|
| { kind: "tombstone"; entry: AgentTombstone }
|
|
159
163
|
|
|
164
|
+
/** Which concurrency pool a run is charged to. */
|
|
165
|
+
export type Pool = "background" | "foreground"
|
|
166
|
+
|
|
160
167
|
export interface AgentRecord {
|
|
161
168
|
id: string
|
|
162
169
|
type: SubagentType
|
|
@@ -197,8 +204,22 @@ export interface AgentRecord {
|
|
|
197
204
|
* which says only that the agent has an inline result surface: a detached
|
|
198
205
|
* cross-extension RPC spawn is foreground by that measure and yet blocks
|
|
199
206
|
* nobody, so it takes no slot.
|
|
207
|
+
*
|
|
208
|
+
* Cleared when the inline caller stops waiting: by `sendToBackground`, or
|
|
209
|
+
* when the run settles.
|
|
200
210
|
*/
|
|
201
211
|
blocking?: boolean
|
|
212
|
+
/**
|
|
213
|
+
* The concurrency pool this run's slot is charged to, set once when it
|
|
214
|
+
* starts. `settleRun` releases exactly this; `sendToBackground` is the only
|
|
215
|
+
* thing that moves it, together with the counters.
|
|
216
|
+
*/
|
|
217
|
+
chargedPool?: Pool
|
|
218
|
+
/**
|
|
219
|
+
* Removes the listener that stops this run when its caller's signal aborts.
|
|
220
|
+
* At most one is armed at a time — queueing arms one, starting replaces it.
|
|
221
|
+
*/
|
|
222
|
+
detachParentAbort?: () => void
|
|
202
223
|
/**
|
|
203
224
|
* Present only while the record is "queued": resolves when it leaves the
|
|
204
225
|
* queue, started or aborted. `spawnAndWait` waits on this because a queued
|
|
@@ -244,8 +265,9 @@ export interface AgentRecord {
|
|
|
244
265
|
/** Number of times this agent's session has compacted. Initialized to 0 at spawn. */
|
|
245
266
|
compactionCount: number
|
|
246
267
|
/**
|
|
247
|
-
* Whether this agent
|
|
248
|
-
*
|
|
268
|
+
* Whether this agent runs in the background. Tri-state, initialized at spawn
|
|
269
|
+
* from `SpawnOptions.isBackground` and flipped `false` → `true` (once, never
|
|
270
|
+
* back) by `sendToBackground`: `true` = background, `false` =
|
|
249
271
|
* foreground (has an inline Agent tool-result surface), `undefined` = the
|
|
250
272
|
* caller never declared it (e.g. a cross-extension RPC spawn, which is detached
|
|
251
273
|
* and has no inline surface). The widget's background-only filter keys off this
|
package/src/ui/agent-widget.ts
CHANGED
|
@@ -99,6 +99,8 @@ export interface AgentDetails {
|
|
|
99
99
|
activity?: string
|
|
100
100
|
/** Current spinner frame index (for animated running indicator). */
|
|
101
101
|
spinnerFrame?: number
|
|
102
|
+
/** Show the ctrl+b hint on this running foreground call's row — one row per batch. */
|
|
103
|
+
backgroundHint?: boolean
|
|
102
104
|
/** Short label for the model the run used, e.g. "haiku 4.5". */
|
|
103
105
|
modelName?: string
|
|
104
106
|
/** Notable config tags (e.g. ["thinking: high", "isolated"]). */
|