@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,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/utility — image generation transports
|
|
3
|
+
*
|
|
4
|
+
* Three wire formats, one result shape. Every function THROWS on failure with
|
|
5
|
+
* a message worth showing the agent (pi only marks a tool result as failed
|
|
6
|
+
* when execute() throws).
|
|
7
|
+
*
|
|
8
|
+
* openrouter POST {base}/chat/completions, `modalities`, images come back
|
|
9
|
+
* as data URLs in choices[0].message.images
|
|
10
|
+
* fal POST https://fal.run/<model>, `sync_mode` → data URIs;
|
|
11
|
+
* edits send `image_urls`
|
|
12
|
+
* openai-images POST {base}/images/generations; edits send an `image` array
|
|
13
|
+
* (the shape OpenAI, OmniRoute and most gateways accept)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ImageApi } from "./settings.js";
|
|
17
|
+
|
|
18
|
+
export interface ImagePayload {
|
|
19
|
+
data: string;
|
|
20
|
+
mimeType: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface GenerateRequest {
|
|
24
|
+
api: ImageApi;
|
|
25
|
+
/** Endpoint base (ignored for fal, which has one host). */
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
apiKey: string;
|
|
28
|
+
modelId: string;
|
|
29
|
+
prompt: string;
|
|
30
|
+
/** Source images → edit. */
|
|
31
|
+
images?: ImagePayload[];
|
|
32
|
+
/** Output modalities for OpenRouter (image-only models reject "text"). */
|
|
33
|
+
output?: string[];
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
fetchImpl?: typeof fetch;
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface GenerateResponse {
|
|
40
|
+
images: ImagePayload[];
|
|
41
|
+
text: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const OPENROUTER_BASE = "https://openrouter.ai/api/v1";
|
|
45
|
+
export const FAL_BASE = "https://fal.run";
|
|
46
|
+
const DEFAULT_TIMEOUT_MS = 240_000;
|
|
47
|
+
|
|
48
|
+
const dataUrl = (img: ImagePayload) => `data:${img.mimeType};base64,${img.data}`;
|
|
49
|
+
|
|
50
|
+
/** `data:image/png;base64,....` → payload; undefined for anything else. */
|
|
51
|
+
export function parseDataUrl(url: string): ImagePayload | undefined {
|
|
52
|
+
const m = /^data:([^;,]+)(?:;[^,]*)*,(.*)$/s.exec(url);
|
|
53
|
+
return m && m[2] ? { mimeType: m[1] || "image/png", data: m[2] } : undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function readError(res: Response): Promise<string> {
|
|
57
|
+
const body = await res.text().catch(() => "");
|
|
58
|
+
let detail = body.slice(0, 400);
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(body) as { error?: { message?: string } | string; detail?: unknown; message?: string };
|
|
61
|
+
if (typeof parsed.error === "string") detail = parsed.error;
|
|
62
|
+
else if (parsed.error?.message) detail = parsed.error.message;
|
|
63
|
+
else if (typeof parsed.message === "string") detail = parsed.message;
|
|
64
|
+
else if (parsed.detail) detail = typeof parsed.detail === "string" ? parsed.detail : JSON.stringify(parsed.detail).slice(0, 400);
|
|
65
|
+
} catch {
|
|
66
|
+
// not JSON
|
|
67
|
+
}
|
|
68
|
+
return `${res.status} ${res.statusText}${detail ? `: ${detail}` : ""}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Fetch a remote image (fal without sync_mode, gateways returning URLs). */
|
|
72
|
+
async function download(url: string, fetchImpl: typeof fetch, signal: AbortSignal): Promise<ImagePayload> {
|
|
73
|
+
const inline = parseDataUrl(url);
|
|
74
|
+
if (inline) return inline;
|
|
75
|
+
const res = await fetchImpl(url, { signal });
|
|
76
|
+
if (!res.ok) throw new Error(`Could not download the generated image: ${res.status}`);
|
|
77
|
+
const mimeType = res.headers.get("content-type")?.split(";")[0] || "image/png";
|
|
78
|
+
return { data: Buffer.from(await res.arrayBuffer()).toString("base64"), mimeType };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function join(base: string, suffix: string): string {
|
|
82
|
+
return `${base.replace(/\/+$/, "")}/${suffix.replace(/^\/+/, "")}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function withTimeout<T>(req: GenerateRequest, run: (signal: AbortSignal, f: typeof fetch) => Promise<T>): Promise<T> {
|
|
86
|
+
const controller = new AbortController();
|
|
87
|
+
const timer = setTimeout(() => controller.abort(), req.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
88
|
+
const onAbort = () => controller.abort();
|
|
89
|
+
req.signal?.addEventListener("abort", onAbort, { once: true });
|
|
90
|
+
try {
|
|
91
|
+
return await run(controller.signal, req.fetchImpl ?? fetch);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (req.signal?.aborted) throw new Error("Image request was cancelled.");
|
|
94
|
+
if (controller.signal.aborted) throw new Error("Image request timed out.");
|
|
95
|
+
throw error;
|
|
96
|
+
} finally {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
req.signal?.removeEventListener("abort", onAbort);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function viaOpenRouter(req: GenerateRequest): Promise<GenerateResponse> {
|
|
103
|
+
return withTimeout(req, async (signal, f) => {
|
|
104
|
+
const content: unknown[] = [{ type: "text", text: req.prompt }];
|
|
105
|
+
for (const img of req.images ?? []) content.push({ type: "image_url", image_url: { url: dataUrl(img) } });
|
|
106
|
+
const output = req.output?.includes("image") ? req.output : ["image"];
|
|
107
|
+
const res = await f(join(req.baseUrl || OPENROUTER_BASE, "chat/completions"), {
|
|
108
|
+
method: "POST",
|
|
109
|
+
headers: { Authorization: `Bearer ${req.apiKey}`, "Content-Type": "application/json" },
|
|
110
|
+
body: JSON.stringify({ model: req.modelId, messages: [{ role: "user", content }], modalities: output }),
|
|
111
|
+
signal,
|
|
112
|
+
});
|
|
113
|
+
if (!res.ok) throw new Error(await readError(res));
|
|
114
|
+
const json = (await res.json()) as {
|
|
115
|
+
error?: { message?: string };
|
|
116
|
+
choices?: Array<{ message?: { content?: unknown; images?: Array<{ image_url?: { url?: string } }> } }>;
|
|
117
|
+
};
|
|
118
|
+
if (json.error?.message) throw new Error(json.error.message);
|
|
119
|
+
const message = json.choices?.[0]?.message;
|
|
120
|
+
const images: ImagePayload[] = [];
|
|
121
|
+
for (const item of message?.images ?? []) {
|
|
122
|
+
const url = item.image_url?.url;
|
|
123
|
+
if (url) images.push(await download(url, f, signal));
|
|
124
|
+
}
|
|
125
|
+
const text = typeof message?.content === "string" ? message.content.trim() : "";
|
|
126
|
+
return { images, text };
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function viaFal(req: GenerateRequest): Promise<GenerateResponse> {
|
|
131
|
+
return withTimeout(req, async (signal, f) => {
|
|
132
|
+
const body: Record<string, unknown> = { prompt: req.prompt, sync_mode: true, num_images: 1 };
|
|
133
|
+
if (req.images?.length) body.image_urls = req.images.map(dataUrl);
|
|
134
|
+
const res = await f(join(req.baseUrl || FAL_BASE, req.modelId), {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: { Authorization: `Key ${req.apiKey}`, "Content-Type": "application/json" },
|
|
137
|
+
body: JSON.stringify(body),
|
|
138
|
+
signal,
|
|
139
|
+
});
|
|
140
|
+
if (!res.ok) throw new Error(await readError(res));
|
|
141
|
+
const json = (await res.json()) as { images?: Array<{ url?: string }>; image?: { url?: string }; description?: string };
|
|
142
|
+
const images: ImagePayload[] = [];
|
|
143
|
+
for (const item of json.images ?? (json.image ? [json.image] : [])) {
|
|
144
|
+
if (item.url) images.push(await download(item.url, f, signal));
|
|
145
|
+
}
|
|
146
|
+
return { images, text: typeof json.description === "string" ? json.description : "" };
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function viaOpenAIImages(req: GenerateRequest): Promise<GenerateResponse> {
|
|
151
|
+
if (!req.baseUrl) throw new Error(`No endpoint for ${req.modelId}: set a custom endpoint in /unipi:settings → Image.`);
|
|
152
|
+
return withTimeout(req, async (signal, f) => {
|
|
153
|
+
const body: Record<string, unknown> = { model: req.modelId, prompt: req.prompt };
|
|
154
|
+
if (req.images?.length) body.image = req.images.map(dataUrl);
|
|
155
|
+
const res = await f(join(req.baseUrl, "images/generations"), {
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers: { Authorization: `Bearer ${req.apiKey}`, "Content-Type": "application/json" },
|
|
158
|
+
body: JSON.stringify(body),
|
|
159
|
+
signal,
|
|
160
|
+
});
|
|
161
|
+
if (!res.ok) throw new Error(await readError(res));
|
|
162
|
+
const json = (await res.json()) as { error?: { message?: string }; data?: Array<{ b64_json?: string; url?: string; media_type?: string; mime_type?: string; revised_prompt?: string }> };
|
|
163
|
+
if (json.error?.message) throw new Error(json.error.message);
|
|
164
|
+
const images: ImagePayload[] = [];
|
|
165
|
+
const notes: string[] = [];
|
|
166
|
+
for (const item of json.data ?? []) {
|
|
167
|
+
const declared = item.media_type ?? item.mime_type;
|
|
168
|
+
if (item.b64_json) images.push({ data: item.b64_json, mimeType: declared ?? "image/png" });
|
|
169
|
+
else if (item.url) images.push(await download(item.url, f, signal));
|
|
170
|
+
if (item.revised_prompt && item.revised_prompt !== req.prompt) notes.push(`Revised prompt: ${item.revised_prompt}`);
|
|
171
|
+
}
|
|
172
|
+
return { images, text: notes.join("\n") };
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Run one generation/edit; throws unless at least one image came back. */
|
|
177
|
+
export async function generate(req: GenerateRequest): Promise<GenerateResponse> {
|
|
178
|
+
if (!req.prompt.trim()) throw new Error("A non-empty prompt is required.");
|
|
179
|
+
if (!req.apiKey) throw new Error("No API key for this image model.");
|
|
180
|
+
const run = req.api === "fal" ? viaFal : req.api === "openrouter" ? viaOpenRouter : viaOpenAIImages;
|
|
181
|
+
const out = await run(req);
|
|
182
|
+
if (out.images.length === 0) {
|
|
183
|
+
throw new Error(out.text ? `The model returned no image. It said: ${out.text.slice(0, 300)}` : "The model returned no image.");
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|
|
@@ -1,423 +1,102 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @pi-unipi/utility — Extension entry
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* - TUI: util settings, name badge (incl. Herdr pane title sync)
|
|
4
|
+
* - /unipi:settings — the unified settings hub
|
|
5
|
+
* - /unipi:continue (/unipi:retry), /unipi:cleanup, /unipi:doctor, /unipi:answer
|
|
6
|
+
* - Automatic session naming (jev gate + isolated one-tool session) + Herdr sync
|
|
7
|
+
* - Pasted images/files → [Image #N] / [File #N] attachments
|
|
8
|
+
* - Image tools: image_generate, image_edit, image_recognize
|
|
9
|
+
* - Response formatting: simple | regular | advanced tool rendering
|
|
10
|
+
* - The shared model cache (~/.unipi/config/models-cache.json)
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
13
|
import { dirname } from "node:path";
|
|
15
14
|
import { fileURLToPath } from "node:url";
|
|
16
|
-
import type { ExtensionAPI
|
|
15
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
17
16
|
import {
|
|
18
17
|
UNIPI_EVENTS,
|
|
19
18
|
MODULES,
|
|
20
19
|
UTILITY_COMMANDS,
|
|
21
|
-
UTILITY_TOOLS,
|
|
22
20
|
emitEvent,
|
|
23
21
|
getPackageVersion,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
type UnipiBadgeGenerateRequestEvent,
|
|
22
|
+
installHints,
|
|
23
|
+
openSettingsHub,
|
|
24
|
+
chatModelsToCache,
|
|
25
|
+
writeModelCache,
|
|
29
26
|
} from "@pi-unipi/core";
|
|
30
27
|
import { registerUtilityCommands } from "./commands.js";
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
28
|
+
import { registerAutoRename } from "./rename/index.js";
|
|
29
|
+
import { registerAnswerCommand } from "./answer/index.js";
|
|
30
|
+
import { registerAttachments } from "./attach/index.js";
|
|
31
|
+
import { imageCatalogEntries, loadImageConfig, refreshImageModelCache, registerImage } from "./image/index.js";
|
|
32
|
+
import { registerToolRenderers } from "./render/tools.js";
|
|
33
|
+
import { installTranscriptSpacing } from "./render/spacing.js";
|
|
34
|
+
import { installHarnessRenderers, installHarnessUserRendering, withHarnessToolAnnotations } from "./render/harness.js";
|
|
35
|
+
import { installHarnessProvenance } from "@pi-unipi/core";
|
|
36
|
+
import { readUtilSettings } from "./settings.js";
|
|
37
|
+
|
|
38
|
+
export { readUtilSettings } from "./settings.js";
|
|
39
|
+
export { simpleWrapTool, simpleWrapped, installSimpleGroupEvents } from "./render/simple.js";
|
|
40
|
+
export { withHarnessToolAnnotations } from "./render/harness.js";
|
|
38
41
|
|
|
39
|
-
/** Re-export readBadgeSettings for cross-package use */
|
|
40
|
-
export { readBadgeSettings } from "./settings.js";
|
|
41
|
-
|
|
42
|
-
/** Package version */
|
|
43
42
|
const VERSION = getPackageVersion(dirname(fileURLToPath(import.meta.url)));
|
|
44
43
|
|
|
45
|
-
/** Whether we've seen the first user message (for auto badge generation) */
|
|
46
|
-
let firstMessageSeen = false;
|
|
47
|
-
|
|
48
|
-
/** Stored user text from first input, used to build conversation summary after agent responds */
|
|
49
|
-
let firstUserText = "";
|
|
50
|
-
|
|
51
|
-
/** Stored UI context from first input, used to show badge overlay after agent responds */
|
|
52
|
-
let firstInputCtx: import("@earendil-works/pi-coding-agent").ExtensionContext | null = null;
|
|
53
|
-
|
|
54
|
-
/** All commands registered by this module */
|
|
55
44
|
const ALL_COMMANDS = [
|
|
56
45
|
UTILITY_COMMANDS.CONTINUE,
|
|
57
|
-
UTILITY_COMMANDS.
|
|
58
|
-
UTILITY_COMMANDS.STATUS,
|
|
46
|
+
UTILITY_COMMANDS.RETRY,
|
|
59
47
|
UTILITY_COMMANDS.CLEANUP,
|
|
60
|
-
UTILITY_COMMANDS.ENV,
|
|
61
48
|
UTILITY_COMMANDS.DOCTOR,
|
|
62
|
-
UTILITY_COMMANDS.
|
|
49
|
+
UTILITY_COMMANDS.ANSWER,
|
|
50
|
+
"settings",
|
|
63
51
|
].map((cmd) => `unipi:${cmd}`);
|
|
64
52
|
|
|
65
|
-
/** All tools registered by this module */
|
|
66
|
-
const ALL_TOOLS = [UTILITY_TOOLS.BATCH, UTILITY_TOOLS.ENV, UTILITY_TOOLS.SET_SESSION_NAME];
|
|
67
|
-
|
|
68
53
|
export default function (pi: ExtensionAPI) {
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// Session-local provider prefix observability. The random HMAC key and all
|
|
76
|
-
// payload-derived fingerprints remain in memory and are discarded on reload.
|
|
77
|
-
const prefixCache = new PrefixCacheTracker();
|
|
78
|
-
pi.registerCommand(`unipi:${UTILITY_COMMANDS.PREFIX_CACHE}`, {
|
|
79
|
-
description: "Show privacy-safe provider prefix-cache diagnostics",
|
|
80
|
-
handler: async (_args, ctx) => {
|
|
81
|
-
const report = formatPrefixCacheStats(prefixCache.getSnapshot());
|
|
82
|
-
if (ctx.hasUI) {
|
|
83
|
-
ctx.ui.notify(report, "info");
|
|
84
|
-
} else {
|
|
85
|
-
pi.sendMessage({ customType: "unipi-response", content: report, display: true }, { deliverAs: "followUp" });
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Badge actions — "Set session name…" / "Generate session name" rows in
|
|
91
|
-
// /unipi:settings (Utility group). The old badge slash commands are
|
|
92
|
-
// gone; the hub field is the same persisted setting.
|
|
93
|
-
registerCommandRunner("unipi:badge-set-name", async (rawCtx: unknown) => {
|
|
94
|
-
const ctx = rawCtx as ExtensionCommandContext;
|
|
95
|
-
if (!ctx.hasUI) {
|
|
96
|
-
ctx.ui.notify("Setting the session name requires an interactive UI.", "warning");
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const current = nameBadgeState.isVisible() ? undefined : "";
|
|
100
|
-
const name = await ctx.ui.input("Session name", current ?? "");
|
|
101
|
-
if (!name || !name.trim()) return;
|
|
102
|
-
nameBadgeState.setSessionName(pi, name.trim());
|
|
103
|
-
ctx.ui.notify(`Session name set to "${name.trim()}"`, "info");
|
|
104
|
-
});
|
|
105
|
-
registerCommandRunner("unipi:badge-generate", async (rawCtx: unknown) => {
|
|
106
|
-
const ctx = rawCtx as ExtensionCommandContext;
|
|
107
|
-
if (!ctx.hasUI) {
|
|
108
|
-
ctx.ui.notify("Badge generation requires an interactive UI.", "warning");
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
await nameBadgeState.generate(pi, ctx);
|
|
112
|
-
ctx.ui.notify("Generating session name...", "info");
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// The unified settings hub — every registered module in one panel.
|
|
54
|
+
// Standalone installs need provenance before any command can send.
|
|
55
|
+
try {
|
|
56
|
+
installHarnessProvenance(pi);
|
|
57
|
+
} catch {
|
|
58
|
+
// cosmetic
|
|
59
|
+
}
|
|
116
60
|
pi.registerCommand("unipi:settings", {
|
|
117
|
-
description: "Configure all unipi modules in one panel (global + project scopes)",
|
|
118
|
-
handler: async (
|
|
119
|
-
if (!ctx.hasUI) throw new Error("/unipi:settings needs the interactive TUI");
|
|
120
|
-
await ctx.ui.custom<void>(
|
|
121
|
-
(tui, _theme, _keybindings, done) => {
|
|
122
|
-
const hub = new SettingsHub({
|
|
123
|
-
cwd: ctx.cwd ?? process.cwd(),
|
|
124
|
-
onChanged: (namespace) => {
|
|
125
|
-
// Preserve the old overlay's live side-effect: the badge reacts
|
|
126
|
-
// immediately when "Show name badge" is toggled in the hub.
|
|
127
|
-
if (namespace !== "utility" || !ctx.hasUI) return;
|
|
128
|
-
try {
|
|
129
|
-
if (readUtilSettings().badge.badgeEnabled) void nameBadgeState.show(pi, ctx);
|
|
130
|
-
else nameBadgeState.hide();
|
|
131
|
-
} catch {
|
|
132
|
-
// Best effort — settings UI must never crash the panel.
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
runAction: async (command) => {
|
|
136
|
-
const ran = await runCommandByName(command, ctx);
|
|
137
|
-
if (!ran) ctx.ui.notify(`no handler registered for ${command}`, "warning");
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
hub.onClose = () => done();
|
|
141
|
-
return {
|
|
142
|
-
focused: true,
|
|
143
|
-
invalidate: () => hub.invalidate(),
|
|
144
|
-
render: (width: number) => hub.render(width),
|
|
145
|
-
handleInput: (data: string) => {
|
|
146
|
-
hub.handleInput(data);
|
|
147
|
-
tui.requestRender();
|
|
148
|
-
},
|
|
149
|
-
dispose: () => {},
|
|
150
|
-
};
|
|
151
|
-
},
|
|
152
|
-
HUB_OVERLAY_OPTIONS,
|
|
153
|
-
).catch(() => {
|
|
154
|
-
// Overlay errors are non-blocking.
|
|
155
|
-
});
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
pi.on("session_start", () => {
|
|
160
|
-
// A replacement/reloaded session has a distinct provider prefix lineage.
|
|
161
|
-
// The extension factory is normally recreated, but reset explicitly so
|
|
162
|
-
// custom hosts that reuse one factory cannot leak counts across sessions.
|
|
163
|
-
prefixCache.reset();
|
|
61
|
+
description: "Configure all unipi modules in one panel (global + project scopes); /unipi:settings <search> opens it filtered",
|
|
62
|
+
handler: async (args, ctx) => openSettingsHub(ctx, { filter: args.trim() }),
|
|
164
63
|
});
|
|
165
64
|
|
|
166
|
-
pi.on("before_provider_request", (event, ctx) => {
|
|
167
|
-
prefixCache.observeRequest(event.payload, ctx.model);
|
|
168
|
-
// Observe only: never replace or mutate provider payloads.
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
pi.on("agent_end", (event) => {
|
|
172
|
-
prefixCache.observeMessages(event.messages);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
pi.on("model_select", (event) => {
|
|
176
|
-
if (event.previousModel && (
|
|
177
|
-
event.previousModel.provider !== event.model.provider ||
|
|
178
|
-
event.previousModel.id !== event.model.id ||
|
|
179
|
-
event.previousModel.api !== event.model.api
|
|
180
|
-
)) {
|
|
181
|
-
prefixCache.markBoundary("envelope_changed");
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
pi.on("thinking_level_select", (event) => {
|
|
185
|
-
if (event.previousLevel !== event.level) prefixCache.markBoundary("envelope_changed");
|
|
186
|
-
});
|
|
187
|
-
pi.on("session_compact", () => prefixCache.markBoundary("history_rewritten"));
|
|
188
|
-
pi.on("session_tree", () => prefixCache.markBoundary("history_rewritten"));
|
|
189
|
-
|
|
190
|
-
// Register cleanup on shutdown
|
|
191
|
-
lifecycle.registerCleanup(async () => {
|
|
192
|
-
analytics.disable();
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// Initialize name badge state
|
|
196
|
-
const nameBadgeState = new NameBadgeState();
|
|
197
|
-
|
|
198
|
-
// Capture session context for cross-event use (not needed if BADGE_GENERATE_REQUEST removed)
|
|
199
|
-
|
|
200
|
-
// Skill startup discovery gate — when disabled (unipi.skills.discovery: false),
|
|
201
|
-
// Unipi's bundled skills are removed from the <available_skills> catalog so
|
|
202
|
-
// they never populate agent context; the user's own skills (global, project,
|
|
203
|
-
// settings-mounted, third-party packages) stay cataloged. /skill:name
|
|
204
|
-
// invocation is unaffected either way: pi expands those commands by reading
|
|
205
|
-
// SKILL.md directly. Applied consistently per turn, so the provider prefix
|
|
206
|
-
// cache stays intact.
|
|
207
|
-
// Skill exposure pipeline: bundled strip → jev freeze → recheck reveals.
|
|
208
|
-
registerSkillJudging(pi);
|
|
209
|
-
|
|
210
|
-
// Register commands
|
|
211
65
|
registerUtilityCommands(pi);
|
|
66
|
+
registerAutoRename(pi);
|
|
67
|
+
registerAnswerCommand(pi);
|
|
68
|
+
registerAttachments(pi);
|
|
69
|
+
registerImage(pi);
|
|
70
|
+
registerToolRenderers(pi, readUtilSettings().render.style);
|
|
71
|
+
// Normalize blank runs between transcript blocks (all render styles).
|
|
72
|
+
installTranscriptSpacing(pi);
|
|
73
|
+
// Harness provenance: known custom-type panels + native USER card patch.
|
|
74
|
+
installHarnessRenderers(pi);
|
|
75
|
+
installHarnessUserRendering(pi);
|
|
76
|
+
// Unicrab hints: one line above the editor on startup + matching events.
|
|
77
|
+
installHints(pi);
|
|
212
78
|
|
|
213
|
-
// Register tools
|
|
214
|
-
registerUtilityTools(pi, nameBadgeState);
|
|
215
|
-
|
|
216
|
-
// Register info-screen group
|
|
217
|
-
registerInfoScreen(pi);
|
|
218
|
-
|
|
219
|
-
// Session lifecycle — announce module + restore badge
|
|
220
79
|
pi.on("session_start", async (_event, ctx) => {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Write model cache for TUI components
|
|
245
|
-
if ((ctx as any).modelRegistry) {
|
|
246
|
-
const { writeModelCache } = await import("@pi-unipi/core");
|
|
247
|
-
const registry = (ctx as any).modelRegistry;
|
|
248
|
-
const models = (registry.getAvailable?.() ?? registry.getAll())
|
|
249
|
-
.map((m: any) => ({ provider: m.provider, id: m.id, name: m.name }));
|
|
250
|
-
writeModelCache(models);
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// First-message hook: capture user text for deferred badge generation
|
|
255
|
-
pi.on("input", async (_event, ctx) => {
|
|
256
|
-
// Only trigger on first user message
|
|
257
|
-
if (firstMessageSeen) return;
|
|
258
|
-
firstMessageSeen = true;
|
|
259
|
-
|
|
260
|
-
// Check if auto generation is enabled
|
|
261
|
-
const settings = readBadgeSettings();
|
|
262
|
-
if (!settings.autoGen) return;
|
|
263
|
-
|
|
264
|
-
// Skip if badge already has a name
|
|
265
|
-
const sessionName = pi.getSessionName?.();
|
|
266
|
-
if (sessionName) return;
|
|
267
|
-
|
|
268
|
-
// Store first message text for later use in agent_end
|
|
269
|
-
// Note: InputEvent.text is the documented property; content may exist at runtime
|
|
270
|
-
const content = (_event as unknown as Record<string, unknown>).content;
|
|
271
|
-
firstUserText = typeof content === "string"
|
|
272
|
-
? content
|
|
273
|
-
: Array.isArray(content)
|
|
274
|
-
? (content as Array<Record<string, unknown>>)
|
|
275
|
-
.filter((c) => c.type === "text")
|
|
276
|
-
.map((c) => String(c.text))
|
|
277
|
-
.join(" ")
|
|
278
|
-
: "";
|
|
279
|
-
|
|
280
|
-
// Store ctx for badge overlay show after agent responds
|
|
281
|
-
firstInputCtx = ctx;
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
// After agent completes first response, generate badge name with full conversation context
|
|
285
|
-
pi.on("agent_end", async (event, _ctx) => {
|
|
286
|
-
// Only act if we captured a first input and are waiting for badge generation
|
|
287
|
-
if (!firstInputCtx) return;
|
|
288
|
-
const ctx = firstInputCtx;
|
|
289
|
-
firstInputCtx = null; // consume — only trigger once
|
|
290
|
-
|
|
291
|
-
// Check if a name was already set (e.g. manually) in the meantime
|
|
292
|
-
const sessionName = pi.getSessionName?.();
|
|
293
|
-
if (sessionName) return;
|
|
294
|
-
|
|
295
|
-
// Show badge overlay if UI available
|
|
296
|
-
if (ctx?.hasUI && !nameBadgeState.isVisible()) {
|
|
297
|
-
await nameBadgeState.show(pi, ctx);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Build conversation summary from full message history (user + assistant)
|
|
301
|
-
const messages = event.messages;
|
|
302
|
-
const summaryParts: string[] = [];
|
|
303
|
-
|
|
304
|
-
// Include the user's first message
|
|
305
|
-
if (firstUserText) {
|
|
306
|
-
summaryParts.push(`User: ${firstUserText}`);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Include assistant's response text
|
|
310
|
-
const assistantMsgs = messages.filter((m) => m.role === "assistant");
|
|
311
|
-
for (const msg of assistantMsgs) {
|
|
312
|
-
const content = msg.content;
|
|
313
|
-
if (Array.isArray(content)) {
|
|
314
|
-
const textParts = content
|
|
315
|
-
.filter((c): c is { type: "text"; text: string } => "text" in c && c.type === "text")
|
|
316
|
-
.map((c) => c.text)
|
|
317
|
-
.join(" ");
|
|
318
|
-
if (textParts) summaryParts.push(`Assistant: ${textParts}`);
|
|
319
|
-
} else if (typeof content === "string" && content) {
|
|
320
|
-
summaryParts.push(`Assistant: ${content}`);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// Truncate to reasonable size
|
|
325
|
-
const conversationSummary = summaryParts.join("\n").slice(0, 800);
|
|
326
|
-
|
|
327
|
-
// Emit event for subagents to spawn background agent
|
|
328
|
-
emitEvent(pi, UNIPI_EVENTS.BADGE_GENERATE_REQUEST, {
|
|
329
|
-
source: "input-hook",
|
|
330
|
-
conversationSummary,
|
|
331
|
-
});
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// Track command usage
|
|
335
|
-
pi.on("tool_call", async (event) => {
|
|
336
|
-
if (event.toolName.startsWith("unipi:")) {
|
|
337
|
-
analytics.recordCommand(event.toolName, MODULES.UTILITY, 0, true);
|
|
80
|
+
try {
|
|
81
|
+
emitEvent(pi, UNIPI_EVENTS.MODULE_READY, {
|
|
82
|
+
name: MODULES.UTILITY,
|
|
83
|
+
version: VERSION,
|
|
84
|
+
commands: ALL_COMMANDS,
|
|
85
|
+
tools: [],
|
|
86
|
+
});
|
|
87
|
+
// Refresh the shared model cache: image models first (their real
|
|
88
|
+
// modalities win over the chat registry's text-only view), then pi's
|
|
89
|
+
// live chat registry (models with credentials, else the full registry).
|
|
90
|
+
const registry = ctx.modelRegistry as unknown as { getAvailable?: () => unknown[]; getAll?: () => unknown[] } | undefined;
|
|
91
|
+
const available = registry?.getAvailable?.() ?? [];
|
|
92
|
+
const chat = chatModelsToCache(available.length > 0 ? available : registry?.getAll?.() ?? []);
|
|
93
|
+
const write = () => writeModelCache([...imageCatalogEntries(loadImageConfig(ctx.cwd)), ...chat]);
|
|
94
|
+
if (chat.length > 0) write();
|
|
95
|
+
void refreshImageModelCache().then((changed) => {
|
|
96
|
+
if (changed && chat.length > 0) write();
|
|
97
|
+
});
|
|
98
|
+
} catch {
|
|
99
|
+
// Best effort — never block session start.
|
|
338
100
|
}
|
|
339
101
|
});
|
|
340
|
-
|
|
341
|
-
// Session shutdown cleanup
|
|
342
|
-
pi.on("session_shutdown", async () => {
|
|
343
|
-
nameBadgeState.hide();
|
|
344
|
-
firstMessageSeen = false;
|
|
345
|
-
firstUserText = "";
|
|
346
|
-
firstInputCtx = null;
|
|
347
|
-
await lifecycle.shutdown("session_shutdown");
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Register utility tools.
|
|
353
|
-
*/
|
|
354
|
-
function registerUtilityTools(pi: ExtensionAPI, nameBadgeState: NameBadgeState): void {
|
|
355
|
-
// ctx_env — environment info
|
|
356
|
-
pi.registerTool({
|
|
357
|
-
name: UTILITY_TOOLS.ENV,
|
|
358
|
-
label: "Environment Info",
|
|
359
|
-
description: "Show environment information: Node version, Pi version, OS, unipi modules, config paths.",
|
|
360
|
-
promptSnippet: "Get environment details for debugging.",
|
|
361
|
-
parameters: {
|
|
362
|
-
type: "object",
|
|
363
|
-
properties: {},
|
|
364
|
-
},
|
|
365
|
-
async execute() {
|
|
366
|
-
const { getEnvironmentInfo, formatEnvironmentInfo } = await import("./tools/env.js");
|
|
367
|
-
const info = getEnvironmentInfo();
|
|
368
|
-
return {
|
|
369
|
-
content: [{ type: "text", text: formatEnvironmentInfo(info) }],
|
|
370
|
-
details: info,
|
|
371
|
-
};
|
|
372
|
-
},
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
// set_session_name — set the session name for badge display
|
|
376
|
-
const badgeSettings = readBadgeSettings();
|
|
377
|
-
if (badgeSettings.agentTool) {
|
|
378
|
-
pi.registerTool({
|
|
379
|
-
name: UTILITY_TOOLS.SET_SESSION_NAME,
|
|
380
|
-
label: "Set Session Name",
|
|
381
|
-
description:
|
|
382
|
-
"Set the session name that appears in the badge overlay and session selector. " +
|
|
383
|
-
"Use this to give the current session a descriptive title. " +
|
|
384
|
-
"Name should be concise (max 5 words recommended).",
|
|
385
|
-
promptSnippet: "Set a name/title for the current session.",
|
|
386
|
-
parameters: {
|
|
387
|
-
type: "object",
|
|
388
|
-
properties: {
|
|
389
|
-
name: {
|
|
390
|
-
type: "string",
|
|
391
|
-
description: "The session name to set (max 5 words recommended).",
|
|
392
|
-
},
|
|
393
|
-
},
|
|
394
|
-
required: ["name"],
|
|
395
|
-
},
|
|
396
|
-
async execute(_toolCallId, params) {
|
|
397
|
-
const { name } = params as { name: string };
|
|
398
|
-
if (!name || typeof name !== "string") {
|
|
399
|
-
return {
|
|
400
|
-
content: [{ type: "text", text: "Error: name parameter is required and must be a string." }],
|
|
401
|
-
details: undefined,
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const trimmed = name.trim();
|
|
406
|
-
if (trimmed.length === 0) {
|
|
407
|
-
return {
|
|
408
|
-
content: [{ type: "text", text: "Error: name cannot be empty." }],
|
|
409
|
-
details: undefined,
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Set the session name
|
|
414
|
-
nameBadgeState.setSessionName(pi, trimmed);
|
|
415
|
-
|
|
416
|
-
return {
|
|
417
|
-
content: [{ type: "text", text: `Session name set to: "${trimmed}"` }],
|
|
418
|
-
details: { name: trimmed },
|
|
419
|
-
};
|
|
420
|
-
},
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
102
|
}
|