@pi-unipi/unipi 3.0.0-alpha.2 → 3.0.0-alpha.21
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 +292 -0
- package/README.md +8 -11
- package/docs/prefix-cache-architecture.md +14 -0
- package/package.json +22 -24
- package/packages/ask-user/README.md +55 -107
- package/packages/ask-user/ask-ui.ts +306 -732
- package/packages/ask-user/clipboard.ts +50 -0
- package/packages/ask-user/config.ts +74 -78
- package/packages/ask-user/package.json +2 -2
- package/packages/ask-user/questions.ts +151 -0
- package/packages/ask-user/skills/ask-user/SKILL.md +59 -133
- package/packages/ask-user/tools.ts +198 -432
- package/packages/autocomplete/package.json +1 -1
- package/packages/autocomplete/src/constants.ts +38 -43
- package/packages/autocomplete/src/sorting.ts +8 -0
- package/packages/background-tasks/package.json +2 -2
- package/packages/background-tasks/src/config.ts +0 -73
- package/packages/background-tasks/src/index.ts +20 -16
- package/packages/background-tasks/src/registry.ts +1 -171
- package/packages/background-tasks/src/types.ts +0 -32
- package/packages/btw/README.md +13 -56
- package/packages/btw/extensions/btw.ts +403 -1796
- package/packages/btw/package.json +5 -2
- package/packages/btw/skills/btw/SKILL.md +7 -143
- package/packages/compactor/README.md +56 -287
- package/packages/compactor/package.json +5 -9
- package/packages/compactor/skills/compactor/SKILL.md +13 -20
- package/packages/compactor/src/card.ts +90 -0
- package/packages/compactor/src/commands/index.ts +73 -130
- package/packages/compactor/src/compaction/compact-args.ts +5 -3
- package/packages/compactor/src/compaction/filter-noise.ts +1 -1
- package/packages/compactor/src/compaction/hooks.ts +487 -311
- package/packages/compactor/src/compaction/source.ts +178 -0
- package/packages/compactor/src/compaction/summarize.ts +586 -52
- package/packages/compactor/src/config/manager.ts +130 -127
- package/packages/compactor/src/config/schema.ts +32 -48
- package/packages/compactor/src/index.ts +40 -431
- package/packages/compactor/src/info-screen.ts +44 -190
- package/packages/compactor/src/session/recall-blocks.ts +1 -1
- package/packages/compactor/src/stats.ts +76 -0
- package/packages/compactor/src/tools/context-budget.ts +17 -34
- package/packages/compactor/src/tools/ctx-doctor.ts +45 -36
- package/packages/compactor/src/tools/register.ts +29 -269
- package/packages/compactor/src/types.ts +47 -186
- package/packages/core/README.md +25 -0
- package/packages/core/child-agent.ts +1 -0
- package/packages/core/command-echo.ts +63 -0
- package/packages/core/compaction-context.ts +41 -0
- package/packages/core/constants.ts +8 -38
- package/packages/core/events.ts +0 -53
- package/packages/core/fusion-status.ts +4 -0
- package/packages/core/global.d.ts +10 -0
- package/packages/core/harness-messages.ts +392 -0
- package/packages/core/index.ts +14 -0
- package/packages/core/kanboard-status.ts +49 -0
- package/packages/core/long-horizon-owner-status.ts +46 -0
- package/packages/core/model-cache.ts +81 -7
- package/packages/core/package.json +6 -3
- package/packages/core/src/attach/detect.ts +163 -0
- package/packages/core/src/child-agent/child-args.ts +31 -0
- package/packages/core/src/child-agent/delivery.ts +41 -0
- package/packages/core/src/child-agent/index.ts +5 -0
- package/packages/core/src/child-agent/readers.ts +203 -0
- package/packages/core/src/child-agent/runtime.ts +584 -0
- package/packages/core/src/evidence.ts +78 -0
- package/packages/core/src/hints/browser.ts +278 -0
- package/packages/core/src/hints/crab-data.ts +19 -0
- package/packages/core/src/hints/index.ts +865 -0
- package/packages/core/src/hints/lines.ts +607 -0
- package/packages/core/src/hints/store.ts +120 -0
- package/packages/core/src/jev/chatter.ts +24 -0
- package/packages/core/src/jev/settings.ts +177 -14
- package/packages/core/src/package-colors.ts +2 -0
- package/packages/core/src/settings/catalog.ts +39 -15
- package/packages/core/src/settings/engine.ts +48 -2
- package/packages/core/src/settings/hub.ts +83 -20
- package/packages/core/src/settings/open.ts +49 -0
- package/packages/core/src/settings/schema.ts +27 -7
- package/packages/core/src/tui/hub-kit.ts +18 -5
- package/packages/core/src/tui/kit.ts +198 -0
- package/packages/core/src/tui/progress.ts +73 -0
- package/packages/core/src/turn/arbiter.ts +290 -0
- package/packages/footer/README.md +4 -6
- package/packages/footer/package.json +3 -3
- package/packages/footer/src/glance-editor.ts +19 -11
- package/packages/footer/src/help.ts +1 -1
- package/packages/footer/src/index.ts +36 -3
- package/packages/footer/src/presets.ts +3 -4
- package/packages/footer/src/rendering/icons.ts +0 -20
- package/packages/footer/src/segments/compactor.ts +52 -142
- package/packages/footer/src/segments/status-ext.ts +0 -1
- package/packages/fusion/README.md +14 -4
- package/packages/fusion/package.json +4 -3
- package/packages/fusion/src/child-args.ts +1 -0
- package/packages/fusion/src/index.ts +161 -8
- package/packages/fusion/src/pi-settings.ts +57 -0
- package/packages/fusion/src/picker.ts +186 -51
- package/packages/fusion/src/preset.ts +56 -2
- package/packages/fusion/src/prompts.ts +1 -1
- package/packages/fusion/src/sidekick-runtime.ts +1 -420
- package/packages/fusion/src/tools.ts +107 -128
- package/packages/fusion/src/transcript.ts +27 -85
- package/packages/info-screen/package.json +2 -2
- package/packages/input-shortcuts/README.md +18 -30
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/input-shortcuts/src/burst.ts +88 -0
- package/packages/input-shortcuts/src/chord-overlay.ts +20 -70
- package/packages/input-shortcuts/src/cleared-input.ts +97 -0
- package/packages/input-shortcuts/src/index.ts +99 -193
- package/packages/input-shortcuts/src/last-response.ts +40 -0
- package/packages/input-shortcuts/src/registers.ts +8 -23
- package/packages/input-shortcuts/src/types.ts +4 -16
- package/packages/input-shortcuts/src/undo-redo.ts +59 -47
- package/packages/kanboard/README.md +51 -47
- package/packages/kanboard/index.ts +293 -55
- package/packages/kanboard/package.json +8 -8
- package/packages/kanboard/skills/kanboard/SKILL.md +61 -27
- package/packages/kanboard/src/badges.ts +93 -0
- package/packages/kanboard/src/bin.ts +4 -2
- package/packages/kanboard/src/commands.ts +218 -84
- package/packages/kanboard/src/debug.ts +20 -0
- package/packages/kanboard/src/guard.ts +267 -109
- package/packages/kanboard/src/monitor.ts +330 -0
- package/packages/kanboard/src/notice-buffer.ts +74 -0
- package/packages/kanboard/src/progress.ts +27 -0
- package/packages/kanboard/src/reminders.ts +234 -0
- package/packages/kanboard/src/settings.ts +69 -15
- package/packages/kanboard/src/shapes.ts +5 -0
- package/packages/long-horizon/index.ts +190 -13
- package/packages/long-horizon/package.json +3 -3
- package/packages/long-horizon/src/commands.ts +103 -7
- package/packages/long-horizon/src/compaction-brief.ts +35 -0
- package/packages/long-horizon/src/engine/continuation.ts +84 -15
- package/packages/long-horizon/src/engine/goal-state.ts +64 -13
- package/packages/long-horizon/src/engine/nudge-stash.ts +71 -0
- package/packages/long-horizon/src/engine/ralph.ts +3 -2
- package/packages/long-horizon/src/engine/runaway.ts +68 -11
- package/packages/long-horizon/src/engine/verifier.ts +19 -0
- package/packages/long-horizon/src/gate.ts +37 -12
- package/packages/long-horizon/src/judge/resolve.ts +1 -0
- package/packages/long-horizon/src/owner.ts +35 -2
- package/packages/long-horizon/src/progress.ts +87 -0
- package/packages/long-horizon/src/prompts/goal.ts +1 -1
- package/packages/long-horizon/src/runtime.ts +172 -18
- package/packages/long-horizon/src/settings.ts +54 -39
- package/packages/long-horizon/src/tools/goal.ts +6 -0
- package/packages/long-horizon/src/tools/graph.ts +1 -1
- package/packages/long-horizon/src/tools/swarm.ts +3 -3
- package/packages/mcp/package.json +2 -2
- package/packages/memory/commands.ts +7 -2
- package/packages/memory/index.ts +210 -32
- package/packages/memory/package.json +3 -3
- package/packages/memory/save-session.ts +264 -0
- package/packages/memory/settings.ts +17 -0
- package/packages/memory/tools.ts +253 -203
- package/packages/notify/package.json +2 -2
- package/packages/skill-registry/README.md +53 -16
- package/packages/skill-registry/index.ts +230 -0
- package/packages/skill-registry/package.json +19 -8
- package/packages/skill-registry/src/editor.ts +206 -0
- package/packages/skill-registry/src/judge.ts +289 -0
- package/packages/skill-registry/src/registry.ts +87 -0
- package/packages/skill-registry/src/settings.ts +259 -0
- package/packages/skill-registry/src/vault.ts +80 -0
- package/packages/subagents/README.md +39 -119
- package/packages/subagents/package.json +6 -13
- package/packages/subagents/skills/subagents/SKILL.md +18 -42
- package/packages/subagents/src/agents.ts +213 -0
- package/packages/subagents/src/cards.ts +182 -0
- package/packages/subagents/src/index.ts +609 -1084
- package/packages/subagents/src/manager.ts +429 -0
- package/packages/subagents/src/profiles.ts +130 -0
- package/packages/subagents/src/transcript.ts +154 -0
- package/packages/subagents/src/ui.ts +389 -0
- package/packages/unipi/bundled.js +39756 -45569
- package/packages/unipi/index.ts +55 -10
- package/packages/updater/package.json +2 -2
- package/packages/utility/README.md +76 -69
- package/packages/utility/package.json +2 -2
- package/packages/utility/skills/image/SKILL.md +37 -0
- package/packages/utility/skills/utility/SKILL.md +17 -60
- package/packages/utility/src/answer/extract.ts +128 -0
- package/packages/utility/src/answer/index.ts +213 -0
- package/packages/utility/src/answer/reply.ts +133 -0
- package/packages/utility/src/answer/web.ts +167 -0
- package/packages/utility/src/attach/index.ts +205 -0
- package/packages/utility/src/commands.ts +38 -142
- package/packages/utility/src/diagnostics/engine.ts +50 -5
- package/packages/utility/src/image/index.ts +33 -0
- package/packages/utility/src/image/models.ts +221 -0
- package/packages/{image/src → utility/src/image}/recognize.ts +2 -2
- package/packages/utility/src/image/settings.ts +178 -0
- package/packages/{image/src/image-source.ts → utility/src/image/source.ts} +1 -1
- package/packages/utility/src/image/tools.ts +168 -0
- package/packages/utility/src/image/transports.ts +186 -0
- package/packages/utility/src/index.ts +68 -389
- package/packages/utility/src/lifecycle/cleanup.ts +116 -311
- package/packages/utility/src/rename/gate.ts +102 -0
- package/packages/utility/src/rename/index.ts +191 -0
- package/packages/utility/src/rename/session.ts +170 -0
- package/packages/utility/src/render/delegated.ts +285 -0
- package/packages/utility/src/render/harness.ts +683 -0
- package/packages/utility/src/render/parse.ts +156 -0
- package/packages/utility/src/render/pi-settings.ts +38 -0
- package/packages/utility/src/render/reply-bg.ts +307 -0
- package/packages/utility/src/render/simple.ts +724 -0
- package/packages/utility/src/render/spacing.ts +235 -0
- package/packages/utility/src/render/styled.ts +251 -0
- package/packages/utility/src/render/tools.ts +353 -0
- package/packages/utility/src/settings.ts +166 -233
- package/packages/utility/src/types.ts +1 -222
- package/packages/watchdog/index.ts +17 -6
- package/packages/watchdog/package.json +3 -3
- package/packages/watchdog/src/config.ts +3 -2
- package/packages/web-api/package.json +2 -2
- package/packages/workflow/README.md +3 -0
- package/packages/workflow/index.ts +0 -2
- package/packages/workflow/package.json +2 -2
- package/packages/workflow/src/permission/decide.ts +6 -4
- package/packages/workflow/src/permission/index.ts +17 -7
- package/packages/workflow/src/permission/settings.ts +39 -4
- package/packages/workflow/src/plan/index.ts +19 -4
- package/packages/background-tasks/extensions/delegate-child.ts +0 -1
- package/packages/background-tasks/src/context-parent-snapshot.ts +0 -142
- package/packages/background-tasks/src/context-token-budget.ts +0 -903
- package/packages/background-tasks/src/context-visible-conversation-v2.ts +0 -551
- package/packages/background-tasks/src/delegate/artifacts.ts +0 -487
- package/packages/background-tasks/src/delegate/budget.ts +0 -415
- package/packages/background-tasks/src/delegate/hook-contract-evidence.json +0 -18
- package/packages/background-tasks/src/delegate/hook-contract.ts +0 -154
- package/packages/background-tasks/src/delegate/launch.ts +0 -484
- package/packages/background-tasks/src/delegate/result-package.ts +0 -459
- package/packages/background-tasks/src/delegate/runner.ts +0 -430
- package/packages/background-tasks/src/delegate/seed.ts +0 -423
- package/packages/background-tasks/src/delegate/types.ts +0 -323
- package/packages/background-tasks/src/delegate-child-extension.ts +0 -978
- package/packages/background-tasks/src/delegate-extension.ts +0 -654
- package/packages/background-tasks/src/fixtures/delegate-context-incident.json +0 -17
- package/packages/compactor/skills/compactor-detail/SKILL.md +0 -116
- package/packages/compactor/skills/compactor-doctor/SKILL.md +0 -71
- package/packages/compactor/skills/compactor-stats/SKILL.md +0 -51
- package/packages/compactor/src/compaction/build-sections.ts +0 -76
- package/packages/compactor/src/compaction/extract/goals.ts +0 -36
- package/packages/compactor/src/compaction/extract/preferences.ts +0 -40
- package/packages/compactor/src/compaction/format.ts +0 -53
- package/packages/compactor/src/compaction/merge.ts +0 -143
- package/packages/compactor/src/config/presets.ts +0 -160
- package/packages/compactor/src/executor/executor.ts +0 -233
- package/packages/compactor/src/executor/runtime.ts +0 -115
- package/packages/compactor/src/security/evaluator.ts +0 -89
- package/packages/compactor/src/security/policy.ts +0 -96
- package/packages/compactor/src/security/scanner.ts +0 -65
- package/packages/compactor/src/session/auto-inject.ts +0 -60
- package/packages/compactor/src/session/db.ts +0 -325
- package/packages/compactor/src/session/extract.ts +0 -144
- package/packages/compactor/src/session/resume-inject.ts +0 -64
- package/packages/compactor/src/session/snapshot.ts +0 -327
- package/packages/compactor/src/tools/ctx-batch-execute.ts +0 -41
- package/packages/compactor/src/tools/ctx-execute-file.ts +0 -23
- package/packages/compactor/src/tools/ctx-execute.ts +0 -23
- package/packages/compactor/src/tools/ctx-stats.ts +0 -96
- package/packages/image/README.md +0 -99
- package/packages/image/package.json +0 -61
- package/packages/image/skills/image/SKILL.md +0 -76
- package/packages/image/src/generate.ts +0 -256
- package/packages/image/src/index.ts +0 -135
- package/packages/image/src/models.ts +0 -519
- package/packages/image/src/openai-images-api.ts +0 -282
- package/packages/image/src/register-providers.ts +0 -209
- package/packages/image/src/settings.ts +0 -173
- package/packages/image/src/tools.ts +0 -326
- package/packages/input-shortcuts/src/clipboard.ts +0 -119
- package/packages/kanboard/src/runner.ts +0 -817
- package/packages/skill-registry/skills/auto/SKILL.md +0 -465
- package/packages/skill-registry/skills/brainstorm/SKILL.md +0 -219
- package/packages/skill-registry/skills/chore-create/SKILL.md +0 -315
- package/packages/skill-registry/skills/chore-execute/SKILL.md +0 -314
- package/packages/skill-registry/skills/consolidate/SKILL.md +0 -142
- package/packages/skill-registry/skills/consultant/SKILL.md +0 -97
- package/packages/skill-registry/skills/debug/SKILL.md +0 -228
- package/packages/skill-registry/skills/document/SKILL.md +0 -120
- package/packages/skill-registry/skills/fix/SKILL.md +0 -212
- package/packages/skill-registry/skills/gather-context/SKILL.md +0 -145
- package/packages/skill-registry/skills/plan/SKILL.md +0 -231
- package/packages/skill-registry/skills/quick-fix/SKILL.md +0 -135
- package/packages/skill-registry/skills/quick-work/SKILL.md +0 -110
- package/packages/skill-registry/skills/research/SKILL.md +0 -251
- package/packages/skill-registry/skills/review-work/SKILL.md +0 -187
- package/packages/skill-registry/skills/scan-issues/SKILL.md +0 -195
- package/packages/skill-registry/skills/work/SKILL.md +0 -218
- package/packages/skill-registry/skills/worktree-create/SKILL.md +0 -69
- package/packages/skill-registry/skills/worktree-list/SKILL.md +0 -78
- package/packages/skill-registry/skills/worktree-merge/SKILL.md +0 -89
- package/packages/subagents/agents/delegate.md +0 -14
- package/packages/subagents/agents/oracle.md +0 -78
- package/packages/subagents/agents/researcher.md +0 -52
- package/packages/subagents/agents/reviewer.md +0 -80
- package/packages/subagents/agents/scout.md +0 -50
- package/packages/subagents/agents/worker.md +0 -59
- package/packages/subagents/prompts/council.md +0 -48
- package/packages/subagents/prompts/gather-context-and-clarify.md +0 -13
- package/packages/subagents/prompts/parallel-cleanup.md +0 -59
- package/packages/subagents/prompts/parallel-research.md +0 -50
- package/packages/subagents/prompts/parallel-review.md +0 -54
- package/packages/subagents/prompts/review-loop.md +0 -43
- package/packages/subagents/skills/council-mode/SKILL.md +0 -230
- package/packages/subagents/skills/subagents/references/constraints-and-recipes.md +0 -259
- package/packages/subagents/skills/subagents/references/execution-controls.md +0 -454
- package/packages/subagents/skills/subagents/references/management-authoring-rpc.md +0 -161
- package/packages/subagents/skills/subagents/references/multi-lane-orchestration.md +0 -39
- package/packages/subagents/skills/subagents/references/prompting-and-roles.md +0 -267
- package/packages/subagents/src/acceptance.ts +0 -331
- package/packages/subagents/src/agent-manager.ts +0 -405
- package/packages/subagents/src/agent-memory.ts +0 -300
- package/packages/subagents/src/agent-overrides.ts +0 -183
- package/packages/subagents/src/agent-runner.ts +0 -271
- package/packages/subagents/src/async-runner.ts +0 -422
- package/packages/subagents/src/authority-policy.ts +0 -47
- package/packages/subagents/src/budgets.ts +0 -285
- package/packages/subagents/src/child-safety.ts +0 -140
- package/packages/subagents/src/config.ts +0 -249
- package/packages/subagents/src/conversation-viewer.ts +0 -281
- package/packages/subagents/src/core-compat.ts +0 -94
- package/packages/subagents/src/custom-agents.ts +0 -343
- package/packages/subagents/src/file-system-retry.ts +0 -50
- package/packages/subagents/src/fleet-data.ts +0 -105
- package/packages/subagents/src/fleet-view.ts +0 -322
- package/packages/subagents/src/foreground-detach.ts +0 -59
- package/packages/subagents/src/fork-context.ts +0 -285
- package/packages/subagents/src/global.d.ts +0 -7
- package/packages/subagents/src/guide.ts +0 -129
- package/packages/subagents/src/mission-state.ts +0 -133
- package/packages/subagents/src/mission-store.ts +0 -436
- package/packages/subagents/src/output-limits.ts +0 -142
- package/packages/subagents/src/parity-types.ts +0 -315
- package/packages/subagents/src/pi-args.ts +0 -167
- package/packages/subagents/src/result-files.ts +0 -232
- package/packages/subagents/src/result-watcher.ts +0 -221
- package/packages/subagents/src/retained-children.ts +0 -176
- package/packages/subagents/src/run-fanout-budget.ts +0 -356
- package/packages/subagents/src/scheduled-runs.ts +0 -349
- package/packages/subagents/src/schemas.ts +0 -263
- package/packages/subagents/src/slash-commands.ts +0 -80
- package/packages/subagents/src/supervisor-channel.ts +0 -249
- package/packages/subagents/src/tool-handler.ts +0 -1144
- package/packages/subagents/src/types.ts +0 -238
- package/packages/subagents/src/widget.ts +0 -457
- package/packages/subagents/src/workflow-script.ts +0 -737
- package/packages/subagents/src/workflow-worker.ts +0 -384
- package/packages/subagents/src/worktree.ts +0 -614
- package/packages/utility/src/analytics/collector.ts +0 -293
- package/packages/utility/src/info-screen.ts +0 -80
- package/packages/utility/src/lifecycle/process.ts +0 -162
- package/packages/utility/src/prefix-cache.ts +0 -263
- package/packages/utility/src/skill-discovery.ts +0 -542
- package/packages/utility/src/tools/env.ts +0 -113
- package/packages/utility/src/tui/name-badge-state.ts +0 -315
- package/packages/utility/src/tui/name-badge.ts +0 -109
- /package/packages/{subagents/src → core/src/child-agent}/pi-spawn.ts +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/skill-registry — the skill vault
|
|
3
|
+
*
|
|
4
|
+
* `~/.unipi/skill-vault/` holds skills the user keeps but does not want
|
|
5
|
+
* everywhere. With the proxy on, pi discovers the vault (resources_discover)
|
|
6
|
+
* and the registry keeps vault skills OFF until a scope turns them on.
|
|
7
|
+
* Layout: any directory containing SKILL.md, up to three levels deep
|
|
8
|
+
* (`vault/<skill>/SKILL.md` or `vault/<pack>/<skill>/SKILL.md`).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
export function vaultDir(): string {
|
|
16
|
+
return process.env.UNIPI_SKILL_VAULT || join(homedir(), ".unipi", "skill-vault");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface VaultSkill {
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
filePath: string;
|
|
23
|
+
baseDir: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Minimal frontmatter reader: `name:` and `description:` (plain, `>`, `|`). */
|
|
27
|
+
export function parseFrontmatter(text: string): { name?: string; description?: string } {
|
|
28
|
+
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
29
|
+
if (!match) return {};
|
|
30
|
+
const lines = match[1]!.split(/\r?\n/);
|
|
31
|
+
const out: { name?: string; description?: string } = {};
|
|
32
|
+
for (let i = 0; i < lines.length; i++) {
|
|
33
|
+
const m = lines[i]!.match(/^(name|description):\s*(.*)$/);
|
|
34
|
+
if (!m) continue;
|
|
35
|
+
const key = m[1] as "name" | "description";
|
|
36
|
+
let value = m[2]!.trim();
|
|
37
|
+
if (/^[>|][+-]?$/.test(value)) {
|
|
38
|
+
const block: string[] = [];
|
|
39
|
+
while (i + 1 < lines.length && (/^\s+\S/.test(lines[i + 1]!) || lines[i + 1] === "")) block.push(lines[++i]!.trim());
|
|
40
|
+
value = block.join(" ").trim();
|
|
41
|
+
}
|
|
42
|
+
out[key] = value.replace(/^["']|["']$/g, "");
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function listVaultSkills(dir: string = vaultDir()): VaultSkill[] {
|
|
48
|
+
const out: VaultSkill[] = [];
|
|
49
|
+
const walk = (d: string, depth: number) => {
|
|
50
|
+
if (depth > 3 || !existsSync(d)) return;
|
|
51
|
+
let names: string[];
|
|
52
|
+
try {
|
|
53
|
+
names = readdirSync(d);
|
|
54
|
+
} catch {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (names.includes("SKILL.md") && d !== dir) {
|
|
58
|
+
const filePath = join(d, "SKILL.md");
|
|
59
|
+
try {
|
|
60
|
+
const fm = parseFrontmatter(readFileSync(filePath, "utf-8"));
|
|
61
|
+
const name = fm.name || d.split("/").pop()!;
|
|
62
|
+
if (fm.description) out.push({ name, description: fm.description, filePath, baseDir: d });
|
|
63
|
+
} catch {
|
|
64
|
+
// unreadable skill — skip
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
for (const n of names) {
|
|
69
|
+
if (n.startsWith(".")) continue;
|
|
70
|
+
const p = join(d, n);
|
|
71
|
+
try {
|
|
72
|
+
if (statSync(p).isDirectory()) walk(p, depth + 1);
|
|
73
|
+
} catch {
|
|
74
|
+
// skip
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
walk(dir, 0);
|
|
79
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
80
|
+
}
|
|
@@ -1,121 +1,41 @@
|
|
|
1
1
|
# @pi-unipi/subagents
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
### Single child
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
spawn_helper({ agent: "scout", task: "Analyze the auth flow" })
|
|
47
|
-
spawn_helper({ agent: "worker", task: "Implement it", run_in_background: true })
|
|
48
|
-
spawn_helper({ agent: "reviewer", task: "Review", gate: "npm test" })
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Legacy aliases (`type`, `prompt`, `max_turns`) still work.
|
|
52
|
-
|
|
53
|
-
### Scripted workflows
|
|
54
|
-
|
|
55
|
-
```js
|
|
56
|
-
spawn_helper({ workflowScript: `
|
|
57
|
-
const scan = await runs.run("scan", { agent: "scout", task: "Analyze auth" });
|
|
58
|
-
const reviews = await runs.all([
|
|
59
|
-
{ key: "correctness", agent: "reviewer", task: "Review correctness: " + scan.output },
|
|
60
|
-
{ key: "tests", agent: "reviewer", task: "Review tests: " + scan.output }
|
|
61
|
-
]);
|
|
62
|
-
return reviews.map(r => r.output);
|
|
63
|
-
`, async: false })
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
`runs.run` / `runs.all` / `runs.steer` inside a sandboxed VM. Budgets (`turnBudget`, `toolBudget`, `usageBudget`), worktree isolation, fork context, and acceptance gates available per child.
|
|
67
|
-
|
|
68
|
-
### Management actions
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
spawn_helper({ action: "list" | "get" | "status" | "children.list" })
|
|
72
|
-
spawn_helper({ action: "resume", id: "<run>", message: "Reconsider X" })
|
|
73
|
-
spawn_helper({ action: "doctor" | "guide", topic: "workflows" })
|
|
74
|
-
spawn_helper({ action: "mission.create", mission: { title, objective } })
|
|
75
|
-
spawn_helper({ action: "schedule.create", name, agent, task, every: "30m" })
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Observability
|
|
79
|
-
|
|
80
|
-
- **FleetView** panel: active work from both transports. `↓` to inspect, `j/k` navigate, `enter` opens transcripts, `esc` closes.
|
|
81
|
-
- **`/unipi:subagents-fleet`** · **`/unipi:subagents-doctor`** · **`/unipi:subagents-guide [topic]`**
|
|
82
|
-
- Background completions arrive as `<task-notification>` follow-ups automatically.
|
|
83
|
-
- Supervisor channel: blocked children can `contact_supervisor` for decisions.
|
|
84
|
-
|
|
85
|
-
## Configuration
|
|
86
|
-
|
|
87
|
-
`~/.unipi/config/subagents.json` (global) + `<workspace>/.unipi/config/subagents.json`:
|
|
88
|
-
|
|
89
|
-
```json
|
|
90
|
-
{
|
|
91
|
-
"subagents": {
|
|
92
|
-
"defaultModel": "ds/deepseek-v4-flash",
|
|
93
|
-
"asyncByDefault": true,
|
|
94
|
-
"maxSubagentSpawnsPerRun": 64,
|
|
95
|
-
"fleetViewPlacement": "belowEditor"
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Full key reference: `spawn_helper({ action: "guide", topic: "configuration" })`. Env overrides use the `UNIPI_SUBAGENT_*` prefix.
|
|
101
|
-
|
|
102
|
-
## Storage
|
|
103
|
-
|
|
104
|
-
| Path | Contents |
|
|
105
|
-
|------|----------|
|
|
106
|
-
| `~/.unipi/missions/<project-hash>/` | Durable mission records |
|
|
107
|
-
| `~/.unipi/schedules/<project-hash>/` | Scheduled runs |
|
|
108
|
-
| `~/.unipi/agent-memory/` | Per-agent persistent memory |
|
|
109
|
-
| temp root (`unipi-subagents-*`) | Run artifacts, results, channels (auto-cleaned) |
|
|
110
|
-
|
|
111
|
-
## Commands
|
|
112
|
-
|
|
113
|
-
| Command | Description |
|
|
114
|
-
|---------|-------------|
|
|
115
|
-
| `/unipi:subagents-fleet` | Show active fleet |
|
|
116
|
-
| `/unipi:subagents-doctor` | Config + capacity diagnosis |
|
|
117
|
-
| `/unipi:subagents-guide [topic]` | Bundled guide |
|
|
118
|
-
|
|
119
|
-
## Prompt shortcuts
|
|
120
|
-
|
|
121
|
-
`/council`, `/parallel-review`, `/review-loop`, `/parallel-research`, `/gather-context-and-clarify`, `/parallel-cleanup` — packaged prompt templates for common orchestration patterns.
|
|
3
|
+
Devin-model subagents for pi: the lead delegates self-contained work to independent child
|
|
4
|
+
agents (`pi --mode rpc`) that run alongside it.
|
|
5
|
+
|
|
6
|
+
- `run_subagent` — foreground (wait + report) or background (`<subagent_completion_notification>`
|
|
7
|
+
followUp, exactly once). Approval prompts reach the user only while a foreground waiter is
|
|
8
|
+
attached; background subagents are denied with a reason ("running in the background… don't
|
|
9
|
+
retry"). `resume:<agent_id>` continues an earlier agent in its session file.
|
|
10
|
+
- `read_subagent` — shared tool (core registry): reads subagents AND Fusion sidekick handoffs.
|
|
11
|
+
- Profiles: `subagent_explore` (read-only allowlist + cheap default model),
|
|
12
|
+
`subagent_general` (all tools minus nesting, parent's model), and custom markdown agents from
|
|
13
|
+
`~/.unipi/config/agents/` + `<workspace>/.unipi/config/agents/` (project wins).
|
|
14
|
+
- Nesting: children spawn only below `UNIPI_SUBAGENT_MAX_DEPTH` (default 1 = no nesting).
|
|
15
|
+
|
|
16
|
+
## Screen
|
|
17
|
+
|
|
18
|
+
| Where | What |
|
|
19
|
+
|---|---|
|
|
20
|
+
| Chat | `● Explore subagent <title>` card — live tail + `└ General · ds/deepseek-flash · 32s · 20 tool calls · 10k in · 0.2k out · $0.11 · ctrl+b background · esc cancel` while it runs, then `└ Completed · 32s · 20 tool calls · 10k in · 0.2k out · $0.11` (ctrl+o shows the report). The stats line ticks live — profile, model, tool-call count, duration, tokens and cost (cost is omitted when the model's pricing is unknown). Background finishes: `● Subagent "<title>" completed └ …`. |
|
|
21
|
+
| Below the input | `N subagents (k running) · ↓ select` plus one stat line per running agent, ticking each second — stays for the session, survives restart + resume. |
|
|
22
|
+
| Dock (↓ from an empty input, or `/unipi:subagents`) | `↑↓` navigate · `↵` view the live transcript · `f` foreground · `x` cancel · `esc` close. |
|
|
23
|
+
| Transcript view | task, tool calls with output, text; `↑↓`/PgUp/PgDn scroll, `g`/`G` top/end, `o` full tool output. |
|
|
24
|
+
| Foreground | Spinner reads `Subagent running · Ctrl+B to run in background`. **Ctrl+B** sends every foreground subagent to the background; **Esc** cancels a foreground run. `f` on a background agent shows its live steps above the input and routes its approvals to you. |
|
|
25
|
+
|
|
26
|
+
States: running `◐`, completed `✓`, failed `✗`, cancelled `⊘` (its own state — "cancelled by you"
|
|
27
|
+
when you pressed Esc or `x`).
|
|
28
|
+
|
|
29
|
+
## Config
|
|
30
|
+
|
|
31
|
+
`/unipi:settings → Subagents`: enabled, default subagent model (picker; empty = Fusion sidekick →
|
|
32
|
+
your model), default thinking, max running at once, **Manage agents…**. Settings and agent files
|
|
33
|
+
are re-read every turn — no restart.
|
|
34
|
+
|
|
35
|
+
`/unipi:agents` — list profiles; create a custom agent step by step (name → this project / all
|
|
36
|
+
projects → description → model → tools → prompt); edit, copy between global/project, or delete.
|
|
37
|
+
Files use the Devin/Claude format (`name`, `description`, `model`, `allowed-tools`|`tools`,
|
|
38
|
+
`thinking`, `max-nesting`; body = system prompt).
|
|
39
|
+
|
|
40
|
+
State: `~/.unipi/workspace/<id>/state/subagents/sessions/<lead-session-id>/` (`index.json` + one
|
|
41
|
+
`<agent_id>.jsonl` child session per agent).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/subagents",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
4
|
-
"description": "Subagents for UniPi —
|
|
3
|
+
"version": "3.0.0-alpha.21",
|
|
4
|
+
"description": "Subagents for UniPi — Devin-model independent child agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"types": "src/index.ts",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"test": "npx tsx --test src/__tests__/*.test.ts"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
12
|
+
"@pi-unipi/core": "3.0.0-alpha.21",
|
|
13
|
+
"@pi-unipi/utility": "3.0.0-alpha.21",
|
|
13
14
|
"@earendil-works/pi-agent-core": "^0.87.1",
|
|
14
15
|
"acorn": "8.18.0"
|
|
15
16
|
},
|
|
@@ -24,22 +25,14 @@
|
|
|
24
25
|
"files": [
|
|
25
26
|
"src/**/*.ts",
|
|
26
27
|
"!src/__tests__/**",
|
|
27
|
-
"src/global.d.ts",
|
|
28
|
-
"agents/*.md",
|
|
29
|
-
"prompts/*.md",
|
|
30
|
-
"skills/**/*",
|
|
31
28
|
"README.md"
|
|
32
29
|
],
|
|
33
30
|
"pi": {
|
|
34
31
|
"extensions": [
|
|
35
32
|
"./src/index.ts"
|
|
36
33
|
],
|
|
37
|
-
"skills": [
|
|
38
|
-
|
|
39
|
-
],
|
|
40
|
-
"prompts": [
|
|
41
|
-
"./prompts"
|
|
42
|
-
],
|
|
34
|
+
"skills": [],
|
|
35
|
+
"prompts": [],
|
|
43
36
|
"themes": []
|
|
44
37
|
}
|
|
45
38
|
}
|
|
@@ -1,49 +1,25 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description:
|
|
4
|
-
Delegate work to builtin or custom subagents with single-agent, parallel,
|
|
5
|
-
scripted-chaining, async, forked-context, and coordinated workflows. Use
|
|
6
|
-
for advisory review, implementation handoffs, and multi-step tasks where a
|
|
7
|
-
single agent should stay in control while other agents contribute context,
|
|
8
|
-
planning, or execution.
|
|
2
|
+
name: subagents
|
|
3
|
+
description: Delegating work to independent subagents (run_subagent/read_subagent).
|
|
9
4
|
---
|
|
10
5
|
|
|
11
|
-
#
|
|
6
|
+
# Subagents
|
|
12
7
|
|
|
13
|
-
|
|
8
|
+
The model drives delegation through tools. For the user: `/unipi:subagents` (or ↓ from an empty
|
|
9
|
+
input) opens the subagent panel; `/unipi:agents` manages custom agents; Ctrl+B moves a foreground
|
|
10
|
+
subagent to the background.
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
- `run_subagent({title, task, profile, is_background?, resume?})` launches an independent agent
|
|
13
|
+
with its own context. It does not see this conversation — put everything it needs in `task`.
|
|
14
|
+
- `read_subagent({agent_id?, block?, timeout?})` reads/waits on a subagent (or a Fusion
|
|
15
|
+
sidekick handoff) by id.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Profiles:
|
|
18
|
+
- `subagent_explore` — read-only (read/grep/find/ls/web_search/memory_search/memory_list), cheaper default model.
|
|
19
|
+
- `subagent_general` — all tools except nesting tools, runs on your model.
|
|
20
|
+
- Custom `.md` agents under `~/.unipi/config/agents/` (global) or
|
|
21
|
+
`<workspace>/.unipi/config/agents/` (project), YAML frontmatter:
|
|
22
|
+
`name, description, model, tools|allowed-tools, thinking, max-nesting`; body = system prompt.
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Read the matching reference file before acting. Paths are relative to this `SKILL.md`; resolve them against `skills/subagents/` and load them with the read tool.
|
|
22
|
-
|
|
23
|
-
| Task | Read |
|
|
24
|
-
| --- | --- |
|
|
25
|
-
| Decide whether to delegate, choose agents, compare tool versus slash commands, apply prompt techniques, or understand builtin roles | `references/prompting-and-roles.md` |
|
|
26
|
-
| Use council mode, convene several advisors, debate a decision, cross-examine recommendations, critique or improve a plan with multiple model perspectives, or run `/council` | `../council-mode/SKILL.md` |
|
|
27
|
-
| Run one-child, scripted, async, scheduled, mission-backed, forked, watchdog, oracle, or intercom-coordinated workflows | `references/execution-controls.md` |
|
|
28
|
-
| Coordinate several independent tasks, worktrees, repositories, or writer lanes | `references/multi-lane-orchestration.md` |
|
|
29
|
-
| List/create/update/delete/eject/disable agents, inspect legacy chain records, edit agent files, use prompt-template integration, or expose extension RPC | `references/management-authoring-rpc.md` |
|
|
30
|
-
| Check safety constraints, best practices, standard workflows, or error handling | `references/constraints-and-recipes.md` |
|
|
31
|
-
|
|
32
|
-
For broad or uncertain requests, read more than one reference. For complex work, start with `references/prompting-and-roles.md` and `references/execution-controls.md`, then consult `references/constraints-and-recipes.md` before launching or reviewing child work.
|
|
33
|
-
|
|
34
|
-
## Always-on constraints
|
|
35
|
-
|
|
36
|
-
- Keep the parent as orchestrator and final decision-maker.
|
|
37
|
-
- Before multiple mutation-capable lanes, record a lane board and each lane's isolation path.
|
|
38
|
-
- For plan, design, or architecture advice that asks for council mode, asks to convene several advisors, compare model perspectives, debate a decision, cross-examine recommendations, or critique and improve a plan, read `../council-mode/SKILL.md` and use Council Mode instead of ad hoc parallel oracle calls.
|
|
39
|
-
- For plan, design, or architecture advice that asks to consult, discuss with, or come to agreement with one `oracle`, use a short same-session consultation loop: read the first result, resume once with a targeted challenge when material tradeoffs remain, then synthesize the parent decision. Keep explicit one-shot, trivial, and fully settled consultations one-shot.
|
|
40
|
-
- Use one writer per cwd/worktree unless isolated worktrees are intentional.
|
|
41
|
-
- For cross-codebase work, record the target repo, explicit `cwd`, authority boundary, and expected output before launch. Do not assume the parent session cwd is the child repo.
|
|
42
|
-
- For parallel fanout, compare child prompts before launch. Do not send clone prompts with only issue numbers, titles, or broad file globs swapped; each child needs a lane-specific task, source seam, prior evidence, and decision that remains distinct without the item number. Launch that fanout as one async `workflowScript` with stable keys and aggregate output unless there is truly only one child.
|
|
43
|
-
- Prefer fresh-context review/validation fanout, then synthesize and apply fixes in the parent.
|
|
44
|
-
- Use async/background by default. Final reviews, gate checks, oracle checks, and backlog lanes stay async. Use `async:false` only when the parent must block until completion. Do not poll just to wait. For adaptive gates, branch in `workflowScript`.
|
|
45
|
-
- For Pi extension repos whose canonical checkout is under `~/.pi/agent/extensions`, never create lane worktrees as sibling directories there. Pi auto-loads `~/.pi/agent/extensions/*/index.ts`, so sibling worktrees can register duplicate tools. Put lanes under `~/.pi/agent/worktrees`, another worktree base outside auto-discovery, or a temporary clone. If a lane must run the modified extension itself, use an isolated Pi config home with `PI_CODING_AGENT_DIR=<lane-config> pi --no-extensions -e <lane>/index.ts`. Use full containers only when path and config isolation are insufficient.
|
|
46
|
-
- Preserve capability ceilings, including child tool restrictions and session-scoped allowed-agent restrictions.
|
|
47
|
-
- Escalate unresolved product, architecture, authority, release, merge, or safety decisions upward instead of letting a child decide silently.
|
|
48
|
-
- Treat receipts, CI, review bots, and external-run records as evidence, not authority to merge, close, comment, publish, or release.
|
|
49
|
-
- As a conservative orchestration policy, do not pass `turnBudget`, a hard `toolBudget`, or a tight `usageBudget` to mutation-capable workers. The default tool budget blocks read/search tools rather than mutation tools, and reported usage has no reservation model. If a worker is interrupted after a tool call starts, checkpoint after the current tool returns with changed files, build/test state, and commit or PR state.
|
|
24
|
+
Rules: parallelize independent work; foreground waits for the report; background can't ask
|
|
25
|
+
for approval (auto-denied); resume a finished/failed/cancelled agent with `resume:<agent_id>`.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/subagents — `/unipi:agents`: manage custom agents without
|
|
3
|
+
* hand-writing frontmatter. List built-ins + customs, create one step by step
|
|
4
|
+
* (name → scope → description → model → tools → prompt), edit the file in
|
|
5
|
+
* pi's editor, copy between global and project, delete.
|
|
6
|
+
*
|
|
7
|
+
* Files: ~/.unipi/config/agents/<name>.md (global) and
|
|
8
|
+
* <workspace>/.unipi/config/agents/<name>.md (project, wins).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
15
|
+
import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import { getSharedFusionStatus, registerCommandRunner } from "@pi-unipi/core";
|
|
17
|
+
import { EXPLORE_TOOLS, builtinProfiles, loadProfiles, type AgentProfile } from "./profiles.js";
|
|
18
|
+
import { resolveModel } from "./model-resolver.js";
|
|
19
|
+
|
|
20
|
+
export const AGENTS_COMMAND = "unipi:agents";
|
|
21
|
+
const NAME_RE = /^[a-z0-9][a-z0-9_-]{0,47}$/;
|
|
22
|
+
const NEW_LABEL = "+ New agent…";
|
|
23
|
+
|
|
24
|
+
export function agentsDir(scope: "global" | "project", cwd: string, home = homedir()): string {
|
|
25
|
+
return join(scope === "global" ? home : cwd, ".unipi", "config", "agents");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Where a custom profile's file lives (flat file or directory layout). */
|
|
29
|
+
export function agentFile(profile: Pick<AgentProfile, "id" | "source">, cwd: string, home = homedir()): string | undefined {
|
|
30
|
+
if (profile.source === "builtin") return undefined;
|
|
31
|
+
const dir = agentsDir(profile.source, cwd, home);
|
|
32
|
+
for (const candidate of [join(dir, `${profile.id}.md`), ...["AGENT.md", "AGENTS.md", "agent.md", "agents.md"].map((f) => join(dir, profile.id, f))]) {
|
|
33
|
+
if (existsSync(candidate)) return candidate;
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface AgentDraft {
|
|
39
|
+
name: string;
|
|
40
|
+
description: string;
|
|
41
|
+
model?: string;
|
|
42
|
+
tools?: string[];
|
|
43
|
+
thinking?: string;
|
|
44
|
+
prompt: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Render a draft as the Devin/Claude-compatible markdown file. */
|
|
48
|
+
export function agentMarkdown(d: AgentDraft): string {
|
|
49
|
+
const fm = [`name: ${d.name}`, `description: ${JSON.stringify(d.description)}`];
|
|
50
|
+
if (d.model) fm.push(`model: ${d.model}`);
|
|
51
|
+
if (d.thinking) fm.push(`thinking: ${d.thinking}`);
|
|
52
|
+
if (d.tools && d.tools.length > 0) fm.push("allowed-tools:", ...d.tools.map((t) => ` - ${t}`));
|
|
53
|
+
return `---\n${fm.join("\n")}\n---\n\n${d.prompt.trim()}\n`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function validateName(name: string, existing: readonly string[]): string | null {
|
|
57
|
+
if (!NAME_RE.test(name)) return "Use lowercase letters, digits, - or _ (max 48).";
|
|
58
|
+
if (builtinProfiles().some((b) => b.id === name)) return `${name} is a built-in profile name.`;
|
|
59
|
+
if (existing.includes(name)) return `An agent named ${name} already exists.`;
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function promptTemplate(name: string, description: string): string {
|
|
64
|
+
return `You are the ${name} subagent: ${description}\n\nWork only on the task you are given. You do not see the parent conversation; everything you need is in the task.\n\nFinish with a concise report for the parent agent: what you found or did, with file paths and line numbers, and anything you could not determine.`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function describe(p: AgentProfile): string {
|
|
68
|
+
const tools = p.tools === undefined ? "all tools" : p.tools.length <= 4 ? p.tools.join(", ") : `${String(p.tools.length)} tools`;
|
|
69
|
+
const model = p.model ?? (p.id === "subagent_general" ? "your model" : "default subagent model");
|
|
70
|
+
return `${p.id} · ${p.source} · ${model} · ${tools} — ${p.description}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type UI = ExtensionContext["ui"];
|
|
74
|
+
|
|
75
|
+
/** Short list + "Other…" with a fuzzy name match (hundreds of models don't
|
|
76
|
+
* fit a plain select). undefined = default subagent model; null = cancelled. */
|
|
77
|
+
async function pickModel(ui: UI, ctx: ExtensionContext): Promise<string | undefined | null> {
|
|
78
|
+
const DEFAULT = "Default subagent model (recommended)";
|
|
79
|
+
const OTHER = "Other… (type a name — fuzzy match)";
|
|
80
|
+
const cur = ctx.model as { provider?: string; id?: string } | undefined;
|
|
81
|
+
const choices = new Map<string, string>();
|
|
82
|
+
if (cur?.provider && cur.id) choices.set(`Your current model — ${cur.provider}/${cur.id}`, `${cur.provider}/${cur.id}`);
|
|
83
|
+
const sidekick = getSharedFusionStatus()?.sidekickKey;
|
|
84
|
+
if (sidekick) choices.set(`Fusion sidekick — ${sidekick}`, sidekick);
|
|
85
|
+
const choice = await ui.select("Model", [DEFAULT, ...choices.keys(), OTHER]);
|
|
86
|
+
if (choice === undefined) return null;
|
|
87
|
+
if (choice === DEFAULT) return undefined;
|
|
88
|
+
if (choice !== OTHER) return choices.get(choice);
|
|
89
|
+
while (true) {
|
|
90
|
+
const raw = (await ui.input("Model name (e.g. deepseek-flash, sonnet, openrouter/…)", ""))?.trim();
|
|
91
|
+
if (!raw) return null;
|
|
92
|
+
const found = ctx.modelRegistry ? resolveModel(raw, ctx.modelRegistry as never) : raw;
|
|
93
|
+
if (typeof found !== "string") return `${found.provider}/${found.id}`;
|
|
94
|
+
if (!ctx.modelRegistry) return raw;
|
|
95
|
+
ui.notify(`No model matches "${raw}".`, "warning");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function pickTools(ui: UI): Promise<string[] | undefined | null> {
|
|
100
|
+
const READ = `Read-only (${EXPLORE_TOOLS.join(", ")})`;
|
|
101
|
+
const ALL = "All tools (like subagent_general)";
|
|
102
|
+
const CUSTOM = "Custom list…";
|
|
103
|
+
const choice = await ui.select("Tools", [READ, ALL, CUSTOM]);
|
|
104
|
+
if (choice === undefined) return null;
|
|
105
|
+
if (choice === READ) return [...EXPLORE_TOOLS];
|
|
106
|
+
if (choice === ALL) return undefined;
|
|
107
|
+
const raw = await ui.input("Tools (comma-separated)", "read, grep, find, ls, bash");
|
|
108
|
+
if (raw === undefined) return null;
|
|
109
|
+
const list = raw.split(",").map((t) => t.trim()).filter(Boolean);
|
|
110
|
+
return list.length > 0 ? list : undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function createAgent(ctx: ExtensionContext, existing: readonly string[], onChange: () => void): Promise<void> {
|
|
114
|
+
const ui = ctx.ui;
|
|
115
|
+
let name: string | undefined;
|
|
116
|
+
while (name === undefined) {
|
|
117
|
+
const raw = await ui.input("New agent — name (e.g. reviewer)", "");
|
|
118
|
+
if (raw === undefined) return;
|
|
119
|
+
const err = validateName(raw.trim(), existing);
|
|
120
|
+
if (err) ui.notify(err, "warning");
|
|
121
|
+
else name = raw.trim();
|
|
122
|
+
}
|
|
123
|
+
const scopeChoice = await ui.select(`Save ${name} for`, ["This project (.unipi/config/agents)", "All projects (~/.unipi/config/agents)"]);
|
|
124
|
+
if (scopeChoice === undefined) return;
|
|
125
|
+
const scope = scopeChoice.startsWith("This") ? "project" : "global";
|
|
126
|
+
const description = (await ui.input("Description — the parent agent reads this to pick the profile", ""))?.trim();
|
|
127
|
+
if (!description) return;
|
|
128
|
+
const model = await pickModel(ui, ctx);
|
|
129
|
+
if (model === null) return;
|
|
130
|
+
const tools = await pickTools(ui);
|
|
131
|
+
if (tools === null) return;
|
|
132
|
+
const prompt = await ui.editor(`${name} — system prompt (enter saves)`, promptTemplate(name, description));
|
|
133
|
+
if (prompt === undefined || !prompt.trim()) return;
|
|
134
|
+
const file = join(agentsDir(scope, ctx.cwd), `${name}.md`);
|
|
135
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
136
|
+
writeFileSync(file, agentMarkdown({ name, description, model, tools, prompt }), "utf8");
|
|
137
|
+
onChange();
|
|
138
|
+
ui.notify(`Agent ${name} saved to ${file}. The agent can use it from the next turn.`, "info");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function editAgent(ctx: ExtensionContext, file: string, onChange: () => void): Promise<void> {
|
|
142
|
+
const before = readFileSync(file, "utf8");
|
|
143
|
+
const after = await ctx.ui.editor(`Edit ${file}`, before);
|
|
144
|
+
if (after === undefined || after === before) return;
|
|
145
|
+
try {
|
|
146
|
+
parseFrontmatter(after);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
ctx.ui.notify(`Not saved — frontmatter doesn't parse: ${e instanceof Error ? e.message : String(e)}`, "error");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
writeFileSync(file, after, "utf8");
|
|
152
|
+
onChange();
|
|
153
|
+
ctx.ui.notify(`Saved ${file}.`, "info");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function manageCustom(ctx: ExtensionContext, profile: AgentProfile, onChange: () => void): Promise<void> {
|
|
157
|
+
const file = agentFile(profile, ctx.cwd);
|
|
158
|
+
if (file === undefined) {
|
|
159
|
+
ctx.ui.notify(`Can't find the file for ${profile.id}.`, "error");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const other = profile.source === "project" ? "global" : "project";
|
|
163
|
+
const EDIT = "Edit file";
|
|
164
|
+
const COPY = `Copy to ${other === "global" ? "all projects (global)" : "this project"}`;
|
|
165
|
+
const DELETE = "Delete";
|
|
166
|
+
const choice = await ctx.ui.select(`${profile.id} (${profile.source}) — ${file}`, [EDIT, COPY, DELETE]);
|
|
167
|
+
if (choice === EDIT) return editAgent(ctx, file, onChange);
|
|
168
|
+
if (choice === COPY) {
|
|
169
|
+
const target = join(agentsDir(other, ctx.cwd), `${profile.id}.md`);
|
|
170
|
+
if (existsSync(target) && !(await ctx.ui.confirm("Overwrite?", `${target} already exists.`))) return;
|
|
171
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
172
|
+
writeFileSync(target, readFileSync(file, "utf8"), "utf8");
|
|
173
|
+
onChange();
|
|
174
|
+
ctx.ui.notify(`Copied to ${target}.`, "info");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (choice === DELETE && (await ctx.ui.confirm(`Delete ${profile.id}?`, file))) {
|
|
178
|
+
rmSync(file);
|
|
179
|
+
onChange();
|
|
180
|
+
ctx.ui.notify(`Deleted ${file}.`, "info");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function agentsManager(ctx: ExtensionContext, onChange: () => void): Promise<void> {
|
|
185
|
+
if (!ctx.hasUI) return;
|
|
186
|
+
while (true) {
|
|
187
|
+
const { profiles, warnings } = loadProfiles(ctx.cwd);
|
|
188
|
+
for (const w of warnings) ctx.ui.notify(`subagents: ${w}`, "warning");
|
|
189
|
+
const labels = profiles.map(describe);
|
|
190
|
+
const choice = await ctx.ui.select("Subagent profiles — pick one to manage, or create a new agent", [...labels, NEW_LABEL]);
|
|
191
|
+
if (choice === undefined) return;
|
|
192
|
+
if (choice === NEW_LABEL) {
|
|
193
|
+
await createAgent(ctx, profiles.map((p) => p.id), onChange);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const profile = profiles[labels.indexOf(choice)];
|
|
197
|
+
if (profile === undefined) return;
|
|
198
|
+
if (profile.source === "builtin") {
|
|
199
|
+
ctx.ui.notify(`${profile.id} is built in and can't be edited. Create a custom agent to change the model, tools or prompt.`, "info");
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
await manageCustom(ctx, profile, onChange);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function registerAgentsCommand(pi: ExtensionAPI, deps: { onChange: (cwd: string) => void }): void {
|
|
207
|
+
const run = (ctx: ExtensionContext) => agentsManager(ctx, () => deps.onChange(ctx.cwd));
|
|
208
|
+
pi.registerCommand("unipi:agents", {
|
|
209
|
+
description: "Manage subagent profiles: list, create, edit, copy, delete custom agents",
|
|
210
|
+
handler: async (_args, ctx) => run(ctx),
|
|
211
|
+
});
|
|
212
|
+
registerCommandRunner(AGENTS_COMMAND, async (rawCtx) => run(rawCtx as ExtensionContext));
|
|
213
|
+
}
|