@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,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/core — progress entries: `◎ Goal ██████▒░░░░░ ~55% turn 4/30`.
|
|
3
|
+
*
|
|
4
|
+
* Written with `pi.appendEntry`, so they persist in the transcript for the user
|
|
5
|
+
* but never reach the model (custom entries are outside the LLM context).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ExtensionAPI, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import { truncateToWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
|
|
10
|
+
import { type KitTheme, progressBar } from "./kit.js";
|
|
11
|
+
|
|
12
|
+
export const PROGRESS_ENTRY = "unipi-progress";
|
|
13
|
+
|
|
14
|
+
export interface ProgressData {
|
|
15
|
+
/** Leading glyph (◎ goal, ↻ ralph, ▣ kanboard). */
|
|
16
|
+
icon?: string;
|
|
17
|
+
label: string;
|
|
18
|
+
done: number;
|
|
19
|
+
/** Units in progress right now — rendered as the shaded part. */
|
|
20
|
+
active: number;
|
|
21
|
+
total: number;
|
|
22
|
+
/** Count suffix, e.g. "items", "tasks". Omitted → percent. */
|
|
23
|
+
unit?: string;
|
|
24
|
+
/** Estimated by a model rather than counted (prefixes `~`). */
|
|
25
|
+
estimated?: boolean;
|
|
26
|
+
detail?: string;
|
|
27
|
+
summary?: string;
|
|
28
|
+
color?: ThemeColor;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const BAR_WIDTH = 20;
|
|
32
|
+
|
|
33
|
+
export function progressLines(t: KitTheme, d: ProgressData, width: number): string[] {
|
|
34
|
+
const color = d.color ?? "accent";
|
|
35
|
+
const pct = d.total > 0 ? Math.round((d.done / d.total) * 100) : 0;
|
|
36
|
+
const count = d.unit ? `${String(d.done)}/${String(d.total)} ${d.unit}` : `${d.estimated ? "~" : ""}${String(pct)}%`;
|
|
37
|
+
const head = [
|
|
38
|
+
`${t.fg(color, d.icon ?? "◎")} ${t.bold(d.label)}`,
|
|
39
|
+
progressBar(t, d.done, d.active, d.total, BAR_WIDTH, color),
|
|
40
|
+
t.fg("text", count),
|
|
41
|
+
d.detail ? t.fg("dim", d.detail) : "",
|
|
42
|
+
].filter(Boolean).join(" ");
|
|
43
|
+
const out = [truncateToWidth(head, width)];
|
|
44
|
+
if (d.summary) {
|
|
45
|
+
for (const l of wrapTextWithAnsi(t.fg("muted", d.summary.trim()), Math.max(10, width - 2))) out.push(` ${l}`);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const registered = new WeakSet<object>();
|
|
51
|
+
|
|
52
|
+
/** Register the entry renderer once per extension API instance. */
|
|
53
|
+
export function registerProgressRenderer(pi: ExtensionAPI): void {
|
|
54
|
+
if (registered.has(pi)) return;
|
|
55
|
+
registered.add(pi);
|
|
56
|
+
try {
|
|
57
|
+
pi.registerEntryRenderer<ProgressData>(PROGRESS_ENTRY, (entry, _opts, theme): Component | undefined => {
|
|
58
|
+
const d = entry.data;
|
|
59
|
+
if (!d) return undefined;
|
|
60
|
+
return { invalidate() {}, render: (w: number) => progressLines(theme as KitTheme, d, w) } satisfies Component;
|
|
61
|
+
});
|
|
62
|
+
} catch {
|
|
63
|
+
/* UI-dependent */
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function appendProgress(pi: ExtensionAPI, data: ProgressData): void {
|
|
68
|
+
try {
|
|
69
|
+
pi.appendEntry(PROGRESS_ENTRY, data);
|
|
70
|
+
} catch {
|
|
71
|
+
/* session replaced */
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn arbiter — the single `agent_before_settle` decision point.
|
|
3
|
+
*
|
|
4
|
+
* Nudge providers (long-horizon owner, kanboard monitor, …) propose at most
|
|
5
|
+
* one continuation each; wait sources (bg wake, busy sidekick, background
|
|
6
|
+
* subagent) veto; the handler delivers at most ONE custom_message nudge per
|
|
7
|
+
* settle and continues the run exactly once.
|
|
8
|
+
*
|
|
9
|
+
* Composition semantics (pi 0.99, verified live, docs/plans/2026-09-30-01a0f095.md
|
|
10
|
+
* Step 0): boundary handlers run in registration order, each awaited; a handler
|
|
11
|
+
* result's `entries` REPLACES the accumulated array (so the handler must concat
|
|
12
|
+
* `event.entries` itself) and `continue` is OR-ed. Events queued as followUps
|
|
13
|
+
* are drained before the boundary, so `pendingMessages` is a defence-only
|
|
14
|
+
* check; a run aborted with Esc never reaches the boundary at all.
|
|
15
|
+
*
|
|
16
|
+
* State lives in a `Symbol.for` global so every package that imports core
|
|
17
|
+
* shares one arbiter, and the installer is idempotent across reloads.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { AgentBeforeSettleEventResult, CustomMessageEntryDraft, ExtensionAPI, SessionBoundaryDraft } from "@earendil-works/pi-coding-agent";
|
|
21
|
+
|
|
22
|
+
export interface SettleInfo {
|
|
23
|
+
outcome: "completed" | "aborted" | "error";
|
|
24
|
+
pendingMessages: number;
|
|
25
|
+
/** Text parts of the last assistant message, joined. */
|
|
26
|
+
lastAssistantText: string;
|
|
27
|
+
/** Whether the last assistant message carried tool-call parts. */
|
|
28
|
+
lastAssistantHadToolCalls: boolean;
|
|
29
|
+
/** Tool calls since the last `agent_start` (counted by the installer). */
|
|
30
|
+
toolCallsThisRun: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Nudge {
|
|
34
|
+
source: string;
|
|
35
|
+
priority: number;
|
|
36
|
+
customType: string;
|
|
37
|
+
content: string;
|
|
38
|
+
display: boolean;
|
|
39
|
+
details?: unknown;
|
|
40
|
+
/** Called once, only when THIS nudge is the one delivered. */
|
|
41
|
+
onDelivered?: () => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type SettleDecision =
|
|
45
|
+
| { kind: "nudge"; source: string; priority: number }
|
|
46
|
+
| { kind: "none"; reason: "outcome" | "pending" | "continuing" | "wait" | "no-proposal"; detail?: string };
|
|
47
|
+
|
|
48
|
+
export type NudgeProvider = {
|
|
49
|
+
source: string;
|
|
50
|
+
priority: number;
|
|
51
|
+
propose: (s: SettleInfo) => Nudge | null | Promise<Nudge | null>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type WaitSource = { source: string; waiting: () => string | null };
|
|
55
|
+
|
|
56
|
+
export type SettleDecisionListener = (decision: SettleDecision, info: SettleInfo) => void;
|
|
57
|
+
|
|
58
|
+
interface ArbiterHolder {
|
|
59
|
+
/** Keyed by source: re-registering a source replaces it (reload-safe) and keeps its original order. */
|
|
60
|
+
providers: Map<string, NudgeProvider>;
|
|
61
|
+
waitSources: Map<string, WaitSource>;
|
|
62
|
+
listeners: Set<SettleDecisionListener>;
|
|
63
|
+
installed: boolean;
|
|
64
|
+
toolCallsThisRun: number;
|
|
65
|
+
providerTimeoutMs: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const KEY = Symbol.for("unipi.turn.arbiter");
|
|
69
|
+
const DEFAULT_PROVIDER_TIMEOUT_MS = 2_000;
|
|
70
|
+
|
|
71
|
+
function holder(): ArbiterHolder {
|
|
72
|
+
const g = globalThis as { [KEY]?: ArbiterHolder };
|
|
73
|
+
g[KEY] ??= {
|
|
74
|
+
providers: new Map(),
|
|
75
|
+
waitSources: new Map(),
|
|
76
|
+
listeners: new Set(),
|
|
77
|
+
installed: false,
|
|
78
|
+
toolCallsThisRun: 0,
|
|
79
|
+
providerTimeoutMs: DEFAULT_PROVIDER_TIMEOUT_MS,
|
|
80
|
+
};
|
|
81
|
+
return g[KEY] as ArbiterHolder;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Children are the hands, the lead is the voice: no arbiter, no nudges. */
|
|
85
|
+
export function isChildProcess(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
86
|
+
return env.UNIPI_FUSION_CHILD === "1" || env.UNIPI_SUBAGENT_CHILD === "1" || Boolean(env.UNIPI_KANBOARD_CHILD);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function registerNudgeProvider(
|
|
90
|
+
source: string,
|
|
91
|
+
priority: number,
|
|
92
|
+
propose: (s: SettleInfo) => Nudge | null | Promise<Nudge | null>,
|
|
93
|
+
): () => void {
|
|
94
|
+
const h = holder();
|
|
95
|
+
h.providers.set(source, { source, priority, propose });
|
|
96
|
+
return () => {
|
|
97
|
+
const current = h.providers.get(source);
|
|
98
|
+
if (current?.propose === propose) h.providers.delete(source);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function registerWaitSource(source: string, waiting: () => string | null): () => void {
|
|
103
|
+
const h = holder();
|
|
104
|
+
h.waitSources.set(source, { source, waiting });
|
|
105
|
+
return () => {
|
|
106
|
+
const current = h.waitSources.get(source);
|
|
107
|
+
if (current?.waiting === waiting) h.waitSources.delete(source);
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function onSettleDecision(listener: SettleDecisionListener): () => void {
|
|
112
|
+
const h = holder();
|
|
113
|
+
h.listeners.add(listener);
|
|
114
|
+
return () => {
|
|
115
|
+
h.listeners.delete(listener);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function raceTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T | null> {
|
|
120
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
121
|
+
const timeout = new Promise<null>((resolve) => {
|
|
122
|
+
timer = setTimeout(() => resolve(null), timeoutMs);
|
|
123
|
+
});
|
|
124
|
+
try {
|
|
125
|
+
return await Promise.race([promise, timeout]);
|
|
126
|
+
} finally {
|
|
127
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Pure decision core of the settle handler (no pi types), exported for tests.
|
|
133
|
+
* Order matters: outcome → pending (defence only, see Step 0) → already
|
|
134
|
+
* continuing → wait sources → highest-priority proposal (tie: earliest
|
|
135
|
+
* registered). Provider throw/timeout counts as no proposal.
|
|
136
|
+
*/
|
|
137
|
+
export async function decideSettle(
|
|
138
|
+
info: SettleInfo,
|
|
139
|
+
alreadyContinuing: boolean,
|
|
140
|
+
): Promise<{ decision: SettleDecision; nudge?: Nudge }> {
|
|
141
|
+
const h = holder();
|
|
142
|
+
if (info.outcome !== "completed") {
|
|
143
|
+
return { decision: { kind: "none", reason: "outcome", detail: info.outcome } };
|
|
144
|
+
}
|
|
145
|
+
if (info.pendingMessages > 0) {
|
|
146
|
+
return { decision: { kind: "none", reason: "pending" } };
|
|
147
|
+
}
|
|
148
|
+
if (alreadyContinuing) {
|
|
149
|
+
return { decision: { kind: "none", reason: "continuing" } };
|
|
150
|
+
}
|
|
151
|
+
for (const waitSource of h.waitSources.values()) {
|
|
152
|
+
let reason: string | null = null;
|
|
153
|
+
try {
|
|
154
|
+
reason = waitSource.waiting();
|
|
155
|
+
} catch {
|
|
156
|
+
reason = null; // a broken wait source never blocks settlement
|
|
157
|
+
}
|
|
158
|
+
if (reason !== null) {
|
|
159
|
+
return { decision: { kind: "none", reason: "wait", detail: `${waitSource.source}: ${reason}` } };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
let best: { nudge: Nudge } | undefined;
|
|
163
|
+
for (const provider of h.providers.values()) {
|
|
164
|
+
let nudge: Nudge | null | undefined;
|
|
165
|
+
try {
|
|
166
|
+
nudge = await raceTimeout(Promise.resolve(provider.propose(info)), h.providerTimeoutMs);
|
|
167
|
+
} catch {
|
|
168
|
+
nudge = null;
|
|
169
|
+
}
|
|
170
|
+
if (nudge === null || nudge === undefined) continue;
|
|
171
|
+
if (best === undefined || nudge.priority > best.nudge.priority) best = { nudge };
|
|
172
|
+
}
|
|
173
|
+
if (best === undefined) return { decision: { kind: "none", reason: "no-proposal" } };
|
|
174
|
+
return {
|
|
175
|
+
decision: { kind: "nudge", source: best.nudge.source, priority: best.nudge.priority },
|
|
176
|
+
nudge: best.nudge,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const TOOL_CALL_PART_TYPES = new Set(["toolCall", "tool_call", "toolUse", "tool_use"]);
|
|
181
|
+
|
|
182
|
+
interface AssistantSummary {
|
|
183
|
+
text: string;
|
|
184
|
+
hadToolCalls: boolean;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function summarizeAssistant(messages: readonly unknown[]): AssistantSummary {
|
|
188
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
189
|
+
const message = messages[index];
|
|
190
|
+
if (typeof message !== "object" || message === null) continue;
|
|
191
|
+
const record = message as Record<string, unknown>;
|
|
192
|
+
if (record.role !== "assistant") continue;
|
|
193
|
+
const content = record.content;
|
|
194
|
+
if (typeof content === "string") return { text: content, hadToolCalls: false };
|
|
195
|
+
if (!Array.isArray(content)) return { text: "", hadToolCalls: false };
|
|
196
|
+
let text = "";
|
|
197
|
+
let hadToolCalls = false;
|
|
198
|
+
for (const part of content) {
|
|
199
|
+
if (typeof part !== "object" || part === null) continue;
|
|
200
|
+
const block = part as Record<string, unknown>;
|
|
201
|
+
if (block.type === "text" && typeof block.text === "string") text += block.text;
|
|
202
|
+
else if (block.type !== undefined && TOOL_CALL_PART_TYPES.has(String(block.type))) hadToolCalls = true;
|
|
203
|
+
}
|
|
204
|
+
return { text, hadToolCalls };
|
|
205
|
+
}
|
|
206
|
+
return { text: "", hadToolCalls: false };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Install the single `agent_before_settle` handler. Idempotent: the first
|
|
211
|
+
* call wins (holder flag) — later calls, including from long-horizon's
|
|
212
|
+
* standalone factory, are no-ops. In child processes it registers nothing.
|
|
213
|
+
*/
|
|
214
|
+
export function installArbiter(pi: ExtensionAPI): void {
|
|
215
|
+
const h = holder();
|
|
216
|
+
if (h.installed) return;
|
|
217
|
+
h.installed = true;
|
|
218
|
+
if (isChildProcess()) return;
|
|
219
|
+
|
|
220
|
+
pi.on("agent_start", () => {
|
|
221
|
+
try {
|
|
222
|
+
h.toolCallsThisRun = 0;
|
|
223
|
+
} catch {
|
|
224
|
+
// Accounting must never abort a turn.
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
pi.on("tool_call", () => {
|
|
228
|
+
try {
|
|
229
|
+
h.toolCallsThisRun += 1;
|
|
230
|
+
} catch {
|
|
231
|
+
// Accounting must never abort a turn.
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
pi.on("agent_before_settle", async (event): Promise<AgentBeforeSettleEventResult | undefined> => {
|
|
235
|
+
try {
|
|
236
|
+
const settleEvent = event as {
|
|
237
|
+
outcome?: SettleInfo["outcome"];
|
|
238
|
+
continue?: boolean;
|
|
239
|
+
entries?: SessionBoundaryDraft[];
|
|
240
|
+
context?: { pendingMessages?: unknown[]; contextMessages?: unknown[] };
|
|
241
|
+
};
|
|
242
|
+
const assistant = summarizeAssistant(settleEvent.context?.contextMessages ?? []);
|
|
243
|
+
const info: SettleInfo = {
|
|
244
|
+
outcome: settleEvent.outcome ?? "completed",
|
|
245
|
+
pendingMessages: settleEvent.context?.pendingMessages?.length ?? 0,
|
|
246
|
+
lastAssistantText: assistant.text,
|
|
247
|
+
lastAssistantHadToolCalls: assistant.hadToolCalls,
|
|
248
|
+
toolCallsThisRun: h.toolCallsThisRun,
|
|
249
|
+
};
|
|
250
|
+
const { decision, nudge } = await decideSettle(info, settleEvent.continue === true);
|
|
251
|
+
for (const listener of h.listeners) {
|
|
252
|
+
try {
|
|
253
|
+
listener(decision, info);
|
|
254
|
+
} catch {
|
|
255
|
+
// A broken listener must never break settlement.
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (decision.kind !== "nudge" || nudge === undefined) return undefined;
|
|
259
|
+
try {
|
|
260
|
+
nudge.onDelivered?.();
|
|
261
|
+
} catch {
|
|
262
|
+
// Delivery bookkeeping must never abort a turn.
|
|
263
|
+
}
|
|
264
|
+
const entry: CustomMessageEntryDraft = {
|
|
265
|
+
type: "custom_message",
|
|
266
|
+
customType: nudge.customType,
|
|
267
|
+
content: nudge.content,
|
|
268
|
+
display: nudge.display,
|
|
269
|
+
...(nudge.details !== undefined ? { details: nudge.details } : {}),
|
|
270
|
+
};
|
|
271
|
+
// Boundary `entries` are REPLACED by the last handler that returns
|
|
272
|
+
// them — always carry the accumulated array forward.
|
|
273
|
+
return { entries: [...(settleEvent.entries ?? []), entry], continue: true };
|
|
274
|
+
} catch {
|
|
275
|
+
// The arbiter must never abort a turn.
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Test hook: clear every registration and the installed flag. */
|
|
282
|
+
export function resetArbiterForTests(opts?: { providerTimeoutMs?: number }): void {
|
|
283
|
+
const h = holder();
|
|
284
|
+
h.providers.clear();
|
|
285
|
+
h.waitSources.clear();
|
|
286
|
+
h.listeners.clear();
|
|
287
|
+
h.installed = false;
|
|
288
|
+
h.toolCallsThisRun = 0;
|
|
289
|
+
h.providerTimeoutMs = opts?.providerTimeoutMs ?? DEFAULT_PROVIDER_TIMEOUT_MS;
|
|
290
|
+
}
|
|
@@ -37,7 +37,7 @@ Footer subscribes to events from every Unipi package:
|
|
|
37
37
|
| Group | Events | Segments |
|
|
38
38
|
|-------|--------|----------|
|
|
39
39
|
| core | Pi SDK | model, thinking, path, git, context_pct, cost, tokens, session |
|
|
40
|
-
| compactor | Pi session data
|
|
40
|
+
| compactor | Pi session data | compactions — `cmp 4× 39k→13k · 3m` (count, tokens before → after, time since the last; hidden until the first) |
|
|
41
41
|
| memory | `MEMORY_STORED`/`DELETED`/`CONSOLIDATED` | project_count, total_count, consolidations |
|
|
42
42
|
| mcp | `MCP_SERVER_STARTED`/`STOPPED`/`ERROR` | servers_total, servers_active, tools_total |
|
|
43
43
|
| ralph | `RALPH_LOOP_START`/`END`/`ITERATION_DONE` | active_loops, total_iterations, loop_status |
|
|
@@ -90,9 +90,7 @@ Settings in `~/.pi/agent/settings.json` under `unipi.footer`:
|
|
|
90
90
|
"compactor": {
|
|
91
91
|
"show": true,
|
|
92
92
|
"segments": {
|
|
93
|
-
"
|
|
94
|
-
"compactions": true,
|
|
95
|
-
"tokens_saved": true
|
|
93
|
+
"compactions": true
|
|
96
94
|
}
|
|
97
95
|
}
|
|
98
96
|
}
|
|
@@ -137,11 +135,11 @@ When `iconStyle` is not set, footer auto-detects Nerd Font support and defaults
|
|
|
137
135
|
|
|
138
136
|
```
|
|
139
137
|
Wide terminal (>120 cols):
|
|
140
|
-
model | thinking | path | git | context |
|
|
138
|
+
model | thinking | path | git | context | compactions | cost | project_count
|
|
141
139
|
|
|
142
140
|
Narrow terminal (<120 cols):
|
|
143
141
|
Row 1: model | thinking | path | git | context | cost
|
|
144
|
-
Row 2: compactions |
|
|
142
|
+
Row 2: compactions | project_count | ralph | workflow
|
|
145
143
|
```
|
|
146
144
|
|
|
147
145
|
## License
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/footer",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.21",
|
|
4
4
|
"description": "Persistent status bar for Unipi — subscribes to UNIPI_EVENTS and renders key stats from all unipi packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
36
|
-
"@pi-unipi/background-tasks": "3.0.0-alpha.
|
|
35
|
+
"@pi-unipi/core": "3.0.0-alpha.21",
|
|
36
|
+
"@pi-unipi/background-tasks": "3.0.0-alpha.21"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* subclass CustomEditor, use super.render() ONLY to obtain editor content
|
|
6
6
|
* lines, then compose our own rounded frame. Layout per the v3 design:
|
|
7
7
|
*
|
|
8
|
-
* ╭─UNIPI │ feat/footer-default-v2
|
|
9
|
-
*
|
|
8
|
+
* ╭─UNIPI │ feat/footer-default-v2 ─────────────────────╮
|
|
9
|
+
* Type your prompt here...
|
|
10
10
|
* ╰─ unipi ────────── 42%/1.0M │ Claude Opus 4.5 │ thinking:high ╯
|
|
11
11
|
*
|
|
12
12
|
* Top border carries the UNIPI brand + git branch. Bottom border right side
|
|
13
13
|
* carries workspace · context%/window · model · thinking level; left filler.
|
|
14
|
+
* The body is open-sided: no vertical rails — the rounded corners are the
|
|
15
|
+
* only frame marks (content keeps its column via a one-space indent).
|
|
14
16
|
* All keybindings/autocomplete/history/paste behavior inherited.
|
|
15
17
|
*/
|
|
16
18
|
|
|
@@ -19,6 +21,7 @@ import type { EditorOptions, EditorTheme, TUI } from "@earendil-works/pi-tui";
|
|
|
19
21
|
import type { KeybindingsManager } from "@earendil-works/pi-coding-agent";
|
|
20
22
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
21
23
|
import { getIcon, getResolvedIconStyle } from "./rendering/icons.js";
|
|
24
|
+
import { kanboardGlanceLabel } from "@pi-unipi/core";
|
|
22
25
|
import { lolcatRainbow, paintLolcatGradient } from "./rendering/lolcat.js";
|
|
23
26
|
|
|
24
27
|
/** Live status injected by the footer extension. */
|
|
@@ -41,12 +44,13 @@ export interface GlanceStatus {
|
|
|
41
44
|
modelName: string;
|
|
42
45
|
/** Current thinking level (already "off"-filtered by the provider). */
|
|
43
46
|
thinkingLevel: string | null;
|
|
44
|
-
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* and the plain thinking slot is suppressed (efforts are inline).
|
|
47
|
+
/** Active Fusion pair, when selected. When set, the model slot renders
|
|
48
|
+
* `Fusion · <lead> <effort> ◆ <sidekick>` — lead lit, sidekick muted —
|
|
49
|
+
* and the plain thinking slot is suppressed (efforts are inline).
|
|
48
50
|
*/
|
|
49
51
|
fusion: { leadName: string; leadEffort: string; sidekickName: string; sidekickEffort: string; savedUsd?: number; busy?: boolean; leadToolCalls?: number; sidekickToolCalls?: number } | null;
|
|
52
|
+
/** Kanboard claims/autowork snapshot (null → no `▣` segment). */
|
|
53
|
+
kanboard: { claims: string[]; autowork: boolean } | null;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
const BORDER = {
|
|
@@ -196,17 +200,21 @@ export function composeGlanceTitles(
|
|
|
196
200
|
branch: string | null,
|
|
197
201
|
workspace: string,
|
|
198
202
|
lhMode: string | null = null,
|
|
203
|
+
kanboard: { claims: string[]; autowork: boolean } | null = null,
|
|
199
204
|
): { titleParts: string[]; leftTitle: string } {
|
|
205
|
+
const kanboardLabel = kanboardGlanceLabel(kanboard);
|
|
200
206
|
const titleParts: string[] = [];
|
|
201
207
|
if (getResolvedIconStyle() === "text") {
|
|
202
208
|
titleParts.push(brand);
|
|
203
209
|
if (lhMode) titleParts.push(`mode:${lhMode}`);
|
|
210
|
+
if (kanboardLabel) titleParts.push(kanboardLabel.replace(/^▣ /, "kanboard:"));
|
|
204
211
|
if (branch) titleParts.push(`branch:${branch}`);
|
|
205
212
|
return { titleParts, leftTitle: ` workspace:${workspace} ` };
|
|
206
213
|
}
|
|
207
214
|
const brandIcon = getIcon("model");
|
|
208
215
|
titleParts.push(`${brandIcon ? brandIcon + " " : ""}${brand}`);
|
|
209
216
|
if (lhMode) titleParts.push(lhMode);
|
|
217
|
+
if (kanboardLabel) titleParts.push(kanboardLabel);
|
|
210
218
|
if (branch) {
|
|
211
219
|
const gitIcon = getIcon("git");
|
|
212
220
|
titleParts.push(`${gitIcon ? gitIcon + " " : ""}${branch}`);
|
|
@@ -276,7 +284,7 @@ export class GlanceEditor extends CustomEditor {
|
|
|
276
284
|
// Brand rendered as an animated lolcat gradient (phase from wall time;
|
|
277
285
|
// the footer's 1s refresh timer re-renders, so it shimmers each tick).
|
|
278
286
|
const brand = lolcatRainbow("UNIPI", Date.now() / 1000);
|
|
279
|
-
const { titleParts, leftTitle } = composeGlanceTitles(brand, st.branch, st.workspace, st.lhMode);
|
|
287
|
+
const { titleParts, leftTitle } = composeGlanceTitles(brand, st.branch, st.workspace, st.lhMode, st.kanboard);
|
|
280
288
|
const title = titleParts.join(SEP);
|
|
281
289
|
const leadRule = `${BORDER.horizontal} `;
|
|
282
290
|
const titleText = ` ${title}${SEP}`;
|
|
@@ -296,10 +304,10 @@ export class GlanceEditor extends CustomEditor {
|
|
|
296
304
|
topCluster +
|
|
297
305
|
border(BORDER.topRight);
|
|
298
306
|
|
|
299
|
-
// ── Body rows:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
);
|
|
307
|
+
// ── Body rows: open sides — the corner curves are the only frame marks.
|
|
308
|
+
// One-space indent keeps content in the column it had between the old
|
|
309
|
+
// rails; no trailing padding (rows may end short of the frame width).
|
|
310
|
+
const bodyRows = contentLines.map(row => " " + truncateToWidth(row, inner, ""));
|
|
303
311
|
|
|
304
312
|
// ── Bottom frame: ╰─ unipi ─────── [RIGHT CLUSTER] ─╯ ──
|
|
305
313
|
// Right cluster: workspace · pct%/window │ model │ thinking:level
|
|
@@ -75,7 +75,7 @@ function buildHelpLines(
|
|
|
75
75
|
export function getGroupForSegment(segId: string): string {
|
|
76
76
|
const coreIds = ["model", "api_state", "tool_count", "git", "context_pct", "cost", "tokens_total", "tokens_in", "tokens_out", "session", "hostname", "time", "tps", "clock", "duration", "thinking_level", "uni", "directory"];
|
|
77
77
|
if (coreIds.includes(segId)) return "core";
|
|
78
|
-
const compactorIds = ["
|
|
78
|
+
const compactorIds = ["compactions"];
|
|
79
79
|
if (compactorIds.includes(segId)) return "compactor";
|
|
80
80
|
if (["project_count", "total_count", "consolidations"].includes(segId)) return "memory";
|
|
81
81
|
if (["servers_total", "servers_active", "tools_total", "servers_failed"].includes(segId)) return "mcp";
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ExtensionAPI, Theme, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
10
|
-
import { UNIPI_EVENTS, emitEvent, UNIPI_PREFIX, FOOTER_COMMANDS, getSharedFusionStatus, getSharedLongHorizonMode, getSharedPlanPermissionStatus } from "@pi-unipi/core";
|
|
10
|
+
import { UNIPI_EVENTS, emitEvent, UNIPI_PREFIX, FOOTER_COMMANDS, getSharedFusionStatus, getSharedKanboardStatus, getSharedLongHorizonMode, getSharedPlanPermissionStatus } from "@pi-unipi/core";
|
|
11
11
|
import { FooterRegistry, getFooterRegistry } from "./registry/index.js";
|
|
12
12
|
import { FooterRenderer } from "./rendering/renderer.js";
|
|
13
13
|
import { subscribeToEvents } from "./events.js";
|
|
@@ -18,7 +18,7 @@ import { GlanceEditor } from "./glance-editor.js";
|
|
|
18
18
|
|
|
19
19
|
// Import segment groups
|
|
20
20
|
import { CORE_SEGMENTS } from "./segments/core.js";
|
|
21
|
-
import { COMPACTOR_SEGMENTS } from "./segments/compactor.js";
|
|
21
|
+
import { COMPACTOR_SEGMENTS, compactionSummary } from "./segments/compactor.js";
|
|
22
22
|
import { MEMORY_SEGMENTS } from "./segments/memory.js";
|
|
23
23
|
import { MCP_SEGMENTS } from "./segments/mcp.js";
|
|
24
24
|
import { RALPH_SEGMENTS } from "./segments/ralph.js";
|
|
@@ -448,6 +448,7 @@ function installGlanceEditor(
|
|
|
448
448
|
const lhMode = typeof lhModeRaw === "string" && lhModeRaw.length > 0
|
|
449
449
|
? MODE_LABELS[lhModeRaw] ?? lhModeRaw
|
|
450
450
|
: null;
|
|
451
|
+
const kanboard = getSharedKanboardStatus() ?? null;
|
|
451
452
|
// Plan/permission state rides the same CORE registry data, written by
|
|
452
453
|
// the workflow module's events (also re-emitted on session_start).
|
|
453
454
|
const coreData = st.registry.getGroupData("core") as
|
|
@@ -470,6 +471,7 @@ function installGlanceEditor(
|
|
|
470
471
|
modelName,
|
|
471
472
|
thinkingLevel: typeof p?.thinkingLevel === "string" ? p.thinkingLevel : null,
|
|
472
473
|
fusion,
|
|
474
|
+
kanboard,
|
|
473
475
|
};
|
|
474
476
|
}),
|
|
475
477
|
);
|
|
@@ -546,14 +548,30 @@ const STRIP_COLOR = {
|
|
|
546
548
|
psMid: "\x1b[97m", // white — in between
|
|
547
549
|
cacheHit: "\x1b[92m", // green — high hit
|
|
548
550
|
cacheWarn: "\x1b[93m", // amber — lowish hit
|
|
551
|
+
compact: "\x1b[94m", // blue — compactions
|
|
549
552
|
reset: "\x1b[39m",
|
|
550
553
|
} as const;
|
|
551
554
|
|
|
552
555
|
const c = (code: string, text: string) => `${code}${text}${STRIP_COLOR.reset}`;
|
|
553
556
|
|
|
557
|
+
function fmtTokensShort(n: number): string {
|
|
558
|
+
if (n < 1000) return String(n);
|
|
559
|
+
if (n < 10000) return `${(n / 1000).toFixed(1)}k`;
|
|
560
|
+
if (n < 1000000) return `${Math.round(n / 1000)}k`;
|
|
561
|
+
return `${(n / 1000000).toFixed(1)}M`;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function agoShort(ms: number): string {
|
|
565
|
+
const sec = Math.max(0, Math.round(ms / 1000));
|
|
566
|
+
if (sec < 60) return "just now";
|
|
567
|
+
if (sec < 3600) return `${Math.round(sec / 60)}m ago`;
|
|
568
|
+
if (sec < 86400) return `${Math.round(sec / 3600)}h ago`;
|
|
569
|
+
return `${Math.round(sec / 86400)}d ago`;
|
|
570
|
+
}
|
|
571
|
+
|
|
554
572
|
/**
|
|
555
573
|
* Glance-style centered stats strip under the input:
|
|
556
|
-
* n Turn · n Steps | wall · tool wall | avg TTFT · n tok/s | cache n%
|
|
574
|
+
* n Turn · n Steps | wall · tool wall | avg TTFT · n tok/s | n compactions · 39k→13k · 3m ago | cache n%
|
|
557
575
|
*/
|
|
558
576
|
function renderSessionStrip(piContext: unknown): string | null {
|
|
559
577
|
const parts: string[] = [];
|
|
@@ -588,6 +606,21 @@ function renderSessionStrip(piContext: unknown): string | null {
|
|
|
588
606
|
if (seg) parts.push(seg);
|
|
589
607
|
}
|
|
590
608
|
|
|
609
|
+
// Compactions: hidden until the first one.
|
|
610
|
+
const branch = (() => {
|
|
611
|
+
try {
|
|
612
|
+
return ((piContext as any)?.sessionManager?.getBranch?.() ?? []) as unknown[];
|
|
613
|
+
} catch {
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
})();
|
|
617
|
+
const cmp = compactionSummary(branch);
|
|
618
|
+
if (cmp.count > 0) {
|
|
619
|
+
const sizes = cmp.before > 0 ? ` \u00b7 ${c(STRIP_COLOR.compact, `${fmtTokensShort(cmp.before)}\u2192${fmtTokensShort(cmp.after)}`)}` : "";
|
|
620
|
+
const age = cmp.lastAt != null ? ` \u00b7 ${agoShort(Date.now() - cmp.lastAt)}` : "";
|
|
621
|
+
parts.push(`${c(STRIP_COLOR.compact, String(cmp.count))} compaction${cmp.count === 1 ? "" : "s"}${sizes}${age}`);
|
|
622
|
+
}
|
|
623
|
+
|
|
591
624
|
const hit = cacheHitPct(piContext);
|
|
592
625
|
if (hit !== null) {
|
|
593
626
|
const hitColor = hit >= 70 ? STRIP_COLOR.cacheHit : STRIP_COLOR.cacheWarn;
|
|
@@ -18,7 +18,7 @@ const DEFAULT_PRESET: PresetDef = {
|
|
|
18
18
|
"uni", "lh_mode", "model", "thinking_level", "directory", "git",
|
|
19
19
|
],
|
|
20
20
|
rightSegments: [
|
|
21
|
-
"context_pct", "tokens_total",
|
|
21
|
+
"context_pct", "compactions", "tokens_total",
|
|
22
22
|
"tps", "cost",
|
|
23
23
|
"clock", "duration",
|
|
24
24
|
],
|
|
@@ -33,7 +33,7 @@ const CLASSIC_PRESET: PresetDef = {
|
|
|
33
33
|
],
|
|
34
34
|
rightSegments: [
|
|
35
35
|
"tps", "context_pct", "cost",
|
|
36
|
-
"compactions", "
|
|
36
|
+
"compactions", "project_count",
|
|
37
37
|
"current_command", "loop_status", "extension_statuses",
|
|
38
38
|
"clock", "duration",
|
|
39
39
|
],
|
|
@@ -76,7 +76,7 @@ const FULL_PRESET: PresetDef = {
|
|
|
76
76
|
],
|
|
77
77
|
rightSegments: [
|
|
78
78
|
"tps", "context_pct", "cost", "tokens_total",
|
|
79
|
-
"
|
|
79
|
+
"compactions",
|
|
80
80
|
"project_count", "total_count",
|
|
81
81
|
"servers_total", "servers_active", "tools_total",
|
|
82
82
|
"active_loops", "loop_status",
|
|
@@ -87,7 +87,6 @@ const FULL_PRESET: PresetDef = {
|
|
|
87
87
|
secondarySegments: [
|
|
88
88
|
"hostname",
|
|
89
89
|
"tokens_in", "tokens_out",
|
|
90
|
-
"compression_ratio",
|
|
91
90
|
"platforms_enabled", "last_sent",
|
|
92
91
|
"thinking_level",
|
|
93
92
|
],
|