@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,683 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/utility — harness message provenance rendering (UNI-53).
|
|
3
|
+
*
|
|
4
|
+
* Production renderer for harness-generated content: ONE violet ▏ rail
|
|
5
|
+
* (#a78bfa) on a dark-slate fill (#20222d) per row, body markdown rendered at
|
|
6
|
+
* width-2 so nothing is cut, light text forced on the dark fill, blank rows
|
|
7
|
+
* painted. Three densities (simple / regular / advanced) mirroring the approved
|
|
8
|
+
* UNI-49 preview; the expand hint is Ctrl+O (pi\'s real toggle), not the
|
|
9
|
+
* preview\'s "e".
|
|
10
|
+
*
|
|
11
|
+
* THEME: the panel is theme-PARAMETERIZED — the caller passes the host theme it
|
|
12
|
+
* already received (renderer callbacks, entry renderers, the setWidget probe).
|
|
13
|
+
* This module never imports or overrides the global theme; body markdown uses
|
|
14
|
+
* the SDK\'s public getMarkdownTheme() (current global theme) with a forced
|
|
15
|
+
* readable light fg on the dark fill.
|
|
16
|
+
*
|
|
17
|
+
* Also installs:
|
|
18
|
+
* - registerMessageRenderer for the KNOWN harness custom types (details
|
|
19
|
+
* `unipiHarness` meta when present, generic type fallback otherwise — never
|
|
20
|
+
* text detection);
|
|
21
|
+
* - the native USER card patch: wraps UserMessageComponent.render only, mapped
|
|
22
|
+
* by strict ordinal alignment against ctx.sessionManager.buildContextEntries()
|
|
23
|
+
* (compaction-aware), skill blocks excluded via the SDK\'s own parseSkillBlock;
|
|
24
|
+
* - `withHarnessToolAnnotations(def)` for tool renderers: a provenance header
|
|
25
|
+
* line for results carrying `unipiHarnessAnnotations` details (text stays
|
|
26
|
+
* exactly what the handler produced).
|
|
27
|
+
*/
|
|
28
|
+
import { getMarkdownTheme, parseSkillBlock } from "@earendil-works/pi-coding-agent";
|
|
29
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
30
|
+
import { Markdown, truncateToWidth, wrapTextWithAnsi, visibleWidth, type Component } from "@earendil-works/pi-tui";
|
|
31
|
+
import { paintLine } from "./reply-bg.js";
|
|
32
|
+
import { findTranscriptContainer } from "./spacing.js";
|
|
33
|
+
import { renderStyle, type RenderStyle } from "./styled.js";
|
|
34
|
+
import {
|
|
35
|
+
harnessToolResultDetails,
|
|
36
|
+
readHarnessMeta,
|
|
37
|
+
type HarnessDelivery,
|
|
38
|
+
type HarnessMessageMeta,
|
|
39
|
+
type HarnessMessageMetaInput,
|
|
40
|
+
type HarnessToolAnnotation,
|
|
41
|
+
} from "@pi-unipi/core";
|
|
42
|
+
|
|
43
|
+
export type { HarnessToolAnnotation } from "@pi-unipi/core";
|
|
44
|
+
export { harnessToolResultDetails, readHarnessMeta } from "@pi-unipi/core";
|
|
45
|
+
export type { HarnessMessageMeta, HarnessDelivery } from "@pi-unipi/core";
|
|
46
|
+
export type { RenderStyle } from "./styled.js";
|
|
47
|
+
|
|
48
|
+
// ── palette (fixed dark fill; light text; mode-aware SGR) ───────────────────
|
|
49
|
+
interface Palette {
|
|
50
|
+
violet: string;
|
|
51
|
+
warn: string;
|
|
52
|
+
fill: string;
|
|
53
|
+
light: string;
|
|
54
|
+
lightDim: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const TRUECOLOR: Palette = {
|
|
58
|
+
violet: "\x1b[38;2;167;139;250m", // #a78bfa
|
|
59
|
+
warn: "\x1b[38;2;251;191;36m", // #fbbf24
|
|
60
|
+
fill: "\x1b[48;2;32;34;45m", // #20222d
|
|
61
|
+
light: "\x1b[38;2;232;234;242m", // #e8eaf2
|
|
62
|
+
lightDim: "\x1b[38;2;154;160;181m", // #9aa0b5
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const COLOR256: Palette = {
|
|
66
|
+
violet: "\x1b[38;5;139m",
|
|
67
|
+
warn: "\x1b[38;5;215m",
|
|
68
|
+
fill: "\x1b[48;5;235m",
|
|
69
|
+
light: "\x1b[38;5;253m",
|
|
70
|
+
lightDim: "\x1b[38;5;245m",
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
type ColorMode = "truecolor" | "256color";
|
|
74
|
+
|
|
75
|
+
/** Theme surface the panel reads (pi\'s Theme satisfies it). May also be
|
|
76
|
+
* supplied as a FUNCTION resolved per render for dynamic native state. */
|
|
77
|
+
export type HarnessThemeCompat = { getColorMode?: () => ColorMode; appearance?: string } | (() => HarnessThemeCompat);
|
|
78
|
+
|
|
79
|
+
function resolveTheme(theme: HarnessThemeCompat | undefined): { getColorMode?: () => ColorMode; appearance?: string } | undefined {
|
|
80
|
+
return (typeof theme === "function" ? theme() : theme) as { getColorMode?: () => ColorMode; appearance?: string } | undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function paletteFor(theme: HarnessThemeCompat | undefined): Palette {
|
|
84
|
+
const mode = resolveTheme(theme)?.getColorMode?.() ?? (process.env.COLORTERM === "truecolor" ? "truecolor" : "256color");
|
|
85
|
+
return mode === "truecolor" ? TRUECOLOR : COLOR256;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function modeOf(theme: HarnessThemeCompat | undefined): ColorMode {
|
|
89
|
+
return resolveTheme(theme)?.getColorMode?.() ?? (process.env.COLORTERM === "truecolor" ? "truecolor" : "256color");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const FG_RESET = "\x1b[39m";
|
|
93
|
+
|
|
94
|
+
function stripAnsi(line: string): string {
|
|
95
|
+
return line.replace(/\x1b\[[0-9;]*m/g, "").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function mdLines(text: string, width: number, light: string): string[] {
|
|
99
|
+
if (text.length === 0) return [];
|
|
100
|
+
let markdownTheme: unknown;
|
|
101
|
+
try {
|
|
102
|
+
markdownTheme = getMarkdownTheme(); // SDK public getter — current global theme
|
|
103
|
+
} catch {
|
|
104
|
+
markdownTheme = undefined; // test mocks / uninitialized hosts: plain fallback
|
|
105
|
+
}
|
|
106
|
+
if (markdownTheme === undefined || markdownTheme === null) {
|
|
107
|
+
// Forced readable light fg for the raw fallback (invisible otherwise on
|
|
108
|
+
// light terminals).
|
|
109
|
+
return text.split("\n").flatMap((segment) => {
|
|
110
|
+
if (segment.length === 0) return [""];
|
|
111
|
+
return wrapTextWithAnsi(`${light}${segment}${FG_RESET}`, width);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const comp = new Markdown(text, 0, 0, markdownTheme as never, { color: (c: string) => `${light}${c}${FG_RESET}` });
|
|
115
|
+
return comp.render(width);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface HarnessRenderOptions {
|
|
119
|
+
expanded: boolean | (() => boolean);
|
|
120
|
+
style?: RenderStyle | (() => RenderStyle);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface ThemeTagger {
|
|
124
|
+
tag(theme: unknown): string;
|
|
125
|
+
mode(theme: HarnessThemeCompat | undefined): ColorMode;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function makeThemeTagger(): ThemeTagger {
|
|
129
|
+
const tags = new WeakMap<object, number>();
|
|
130
|
+
let counter = 0;
|
|
131
|
+
return {
|
|
132
|
+
tag(theme: unknown): string {
|
|
133
|
+
const resolved = resolveTheme(theme as HarnessThemeCompat | undefined);
|
|
134
|
+
if (!resolved || typeof resolved !== "object") return "fn";
|
|
135
|
+
let t = tags.get(resolved);
|
|
136
|
+
if (t === undefined) {
|
|
137
|
+
counter += 1;
|
|
138
|
+
t = counter;
|
|
139
|
+
tags.set(resolved, t);
|
|
140
|
+
}
|
|
141
|
+
return `th${t}`;
|
|
142
|
+
},
|
|
143
|
+
mode(theme: HarnessThemeCompat | undefined): ColorMode {
|
|
144
|
+
return modeOf(theme);
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const themeTagger = makeThemeTagger();
|
|
150
|
+
|
|
151
|
+
/** Shared production panel for harness content — full text preserved expanded.
|
|
152
|
+
* `theme`/`options.style` may be functions (dynamic native state); resolved per
|
|
153
|
+
* render and included in the cache key. */
|
|
154
|
+
export class HarnessPanel implements Component {
|
|
155
|
+
private cacheKey = "";
|
|
156
|
+
private cache: string[] = [];
|
|
157
|
+
private readonly expandedOf: () => boolean;
|
|
158
|
+
private readonly theme: HarnessThemeCompat | (() => HarnessThemeCompat);
|
|
159
|
+
|
|
160
|
+
constructor(
|
|
161
|
+
private readonly content: string,
|
|
162
|
+
private readonly meta: HarnessMessageMeta,
|
|
163
|
+
private readonly options: HarnessRenderOptions,
|
|
164
|
+
theme: HarnessThemeCompat | (() => HarnessThemeCompat),
|
|
165
|
+
) {
|
|
166
|
+
this.expandedOf = typeof options.expanded === "function" ? options.expanded : () => options.expanded === true;
|
|
167
|
+
this.theme = theme;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
invalidate(): void {
|
|
171
|
+
this.cacheKey = ""; // next render rebuilds (refreshes markdown)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
render(width: number): string[] {
|
|
175
|
+
const style: RenderStyle = typeof this.options.style === "function" ? this.options.style() : this.options.style ?? "simple";
|
|
176
|
+
const theme = resolveTheme(this.theme);
|
|
177
|
+
const expanded = this.expandedOf();
|
|
178
|
+
const key = `${width}|${style}|${expanded}|${this.content.length}|${this.meta.id}|${themeTagger.tag(this.theme)}|${themeTagger.mode(this.theme)}`;
|
|
179
|
+
if (key !== this.cacheKey) {
|
|
180
|
+
this.cache = this.build(width, style, expanded, theme, paletteFor(theme));
|
|
181
|
+
this.cacheKey = key;
|
|
182
|
+
}
|
|
183
|
+
return this.cache;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private build(width: number, style: RenderStyle, expanded: boolean, theme: HarnessThemeCompat | undefined, pal: Palette): string[] {
|
|
187
|
+
const railGlyph = `${pal.violet}▏${FG_RESET}`;
|
|
188
|
+
const inner = Math.max(8, width - 2);
|
|
189
|
+
const V = (s: string) => `${pal.violet}${s}${FG_RESET}`;
|
|
190
|
+
const W = (s: string) => `${pal.warn}${s}${FG_RESET}`;
|
|
191
|
+
const DIM = (s: string) => `${pal.lightDim}${s}${FG_RESET}`;
|
|
192
|
+
const paint = (line: string): string => paintLine(`${railGlyph} ${line}`, width, pal.fill);
|
|
193
|
+
// Rows are built as PLAIN text, wrapped at the inner width, then colorized
|
|
194
|
+
// wholesale — no ANSI inside the wrap input, so wrapping is width-exact.
|
|
195
|
+
const plainRows = (text: string, colorize: (l: string) => string): string[] =>
|
|
196
|
+
wrapTextWithAnsi(text, inner).map(colorize);
|
|
197
|
+
const warning = this.meta.severity === "warning";
|
|
198
|
+
const label = `${warning ? `${pal.warn}⚠${FG_RESET} ` : ""}UniPi · ${this.meta.source} · ${this.meta.title}`;
|
|
199
|
+
const labelColorize = this.meta.severity === "warning" ? W : V;
|
|
200
|
+
|
|
201
|
+
const body = mdLines(this.content, inner, pal.light);
|
|
202
|
+
const out: string[] = [];
|
|
203
|
+
out.push(...plainRows(label, labelColorize));
|
|
204
|
+
if (style === "simple") {
|
|
205
|
+
const synopsis = this.meta.synopsis ?? this.meta.title;
|
|
206
|
+
out.push(...plainRows(synopsis, (l) => (warning ? W(l) : DIM(l))));
|
|
207
|
+
if (expanded) out.push(...body);
|
|
208
|
+
else out.push(...plainRows(`Ctrl+O: full message (${body.length} rendered rows)`, DIM));
|
|
209
|
+
} else if (style === "regular") {
|
|
210
|
+
if (expanded) out.push(...plainRows(detailText(this.meta), DIM));
|
|
211
|
+
for (const l of body) out.push(l);
|
|
212
|
+
} else {
|
|
213
|
+
if (expanded) {
|
|
214
|
+
out.push(...body);
|
|
215
|
+
} else {
|
|
216
|
+
for (const l of body.slice(0, 4)) out.push(l);
|
|
217
|
+
if (body.length > 4) out.push(...plainRows(`Ctrl+O: expand full (${body.length - 4} more rendered rows)`, DIM));
|
|
218
|
+
}
|
|
219
|
+
out.push(...plainRows(`origin: harness | model role: user | delivery: ${this.meta.delivery}`, DIM));
|
|
220
|
+
}
|
|
221
|
+
return out.map((l) => truncateToWidth(l, inner)).map(paint);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function detailText(meta: HarnessMessageMeta): string {
|
|
226
|
+
return [
|
|
227
|
+
`id: ${meta.id}`,
|
|
228
|
+
`origin: harness · transport: ${meta.delivery}`,
|
|
229
|
+
meta.synopsis ? `synopsis: ${meta.synopsis}` : "",
|
|
230
|
+
].filter((r) => r.length > 0).join("\n");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Public pure entry: build the panel component for a harness message.
|
|
234
|
+
* `theme` is REQUIRED — pass the host theme the caller already received. */
|
|
235
|
+
export function renderHarnessMessage(
|
|
236
|
+
content: string,
|
|
237
|
+
meta: HarnessMessageMeta,
|
|
238
|
+
options: HarnessRenderOptions,
|
|
239
|
+
theme: HarnessThemeCompat | (() => HarnessThemeCompat),
|
|
240
|
+
): Component {
|
|
241
|
+
return new HarnessPanel(content, meta, options, theme);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ── known custom types ──────────────────────────────────────────────────────
|
|
245
|
+
interface KnownType {
|
|
246
|
+
source: string;
|
|
247
|
+
title: string;
|
|
248
|
+
severity?: "warning";
|
|
249
|
+
delivery: HarnessDelivery;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Faithful per-type fallback delivery (actual transport), stable fallback id. */
|
|
253
|
+
export const KNOWN_CUSTOM_TYPES: Record<string, KnownType> = {
|
|
254
|
+
"unipi:lh-continue": { source: "Long-horizon", title: "Continuation", delivery: "boundary" },
|
|
255
|
+
"unipi:kanboard-continue": { source: "Kanboard", title: "Unfinished task", delivery: "boundary" },
|
|
256
|
+
"unipi:kanboard-next": { source: "Kanboard", title: "Next task", delivery: "boundary" },
|
|
257
|
+
"unipi-memory-recall-reminder": { source: "Memory", title: "Memory recall", delivery: "before_agent_start" },
|
|
258
|
+
"unipi-memory-retro-reminder": { source: "Memory", title: "Memory save?", delivery: "nextTurn" },
|
|
259
|
+
"unipi-skills-revealed": { source: "Skills", title: "Skill reveal", delivery: "direct" },
|
|
260
|
+
"unipi:plan-mode-message": { source: "Plan mode", title: "Plan mode", delivery: "direct" },
|
|
261
|
+
"unipi-watchdog": { source: "Watchdog", title: "Warning", severity: "warning", delivery: "before_agent_start" },
|
|
262
|
+
"compactor-recall": { source: "Compactor", title: "Session recall", delivery: "direct" },
|
|
263
|
+
"unipi-response": { source: "Utility", title: "Response", delivery: "followUp" },
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/** Stable fallback meta (no fresh ids per paint). */
|
|
267
|
+
export function fallbackHarnessMeta(customType: string): HarnessMessageMeta {
|
|
268
|
+
const known = KNOWN_CUSTOM_TYPES[customType] ?? { source: customType, title: customType, delivery: "direct" as HarnessDelivery };
|
|
269
|
+
return {
|
|
270
|
+
version: 1,
|
|
271
|
+
id: `hm-fallback-${customType}`,
|
|
272
|
+
delivery: known.delivery,
|
|
273
|
+
source: known.source,
|
|
274
|
+
title: known.title,
|
|
275
|
+
...(known.severity ? { severity: known.severity } : {}),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function contentText(content: unknown): string {
|
|
280
|
+
if (typeof content === "string") return content;
|
|
281
|
+
if (!Array.isArray(content)) return "";
|
|
282
|
+
return content
|
|
283
|
+
.filter((c): c is { type: "text"; text: string } => (c as { type?: string }).type === "text")
|
|
284
|
+
.map((c) => (c as { text: string }).text)
|
|
285
|
+
.join("");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Renderer for the KNOWN harness custom types. Body text unchanged; meta from
|
|
289
|
+
* details.unipiHarness when present, generic type fallback otherwise. */
|
|
290
|
+
export function installHarnessRenderers(pi: ExtensionAPI): void {
|
|
291
|
+
for (const customType of Object.keys(KNOWN_CUSTOM_TYPES)) {
|
|
292
|
+
try {
|
|
293
|
+
pi.registerMessageRenderer(customType, ((message: { content?: unknown; details?: unknown }, options: { expanded?: boolean }, theme: HarnessThemeCompat) => {
|
|
294
|
+
const meta = readHarnessMeta(message.details) ?? fallbackHarnessMeta(customType);
|
|
295
|
+
const text = contentText(message.content);
|
|
296
|
+
return renderHarnessMessage(text, meta, {
|
|
297
|
+
expanded: options?.expanded === true,
|
|
298
|
+
style: renderStyle(),
|
|
299
|
+
}, theme);
|
|
300
|
+
}) as never);
|
|
301
|
+
} catch {
|
|
302
|
+
// Renderer registration is UI-dependent; never block load.
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ── native USER card patch ──────────────────────────────────────────────────
|
|
308
|
+
interface UserComponentLike {
|
|
309
|
+
text: string;
|
|
310
|
+
rebuild: () => void;
|
|
311
|
+
setOutputPad: (pad: number) => void;
|
|
312
|
+
render: (width: number) => string[];
|
|
313
|
+
invalidate?: () => void;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
interface ExpectedCard {
|
|
317
|
+
entryId: string;
|
|
318
|
+
text: string;
|
|
319
|
+
meta?: HarnessMessageMeta;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
interface HarnessUiContext {
|
|
323
|
+
cwd?: string;
|
|
324
|
+
sessionManager?: { buildContextEntries: () => Array<{ type?: string; id?: string; message?: unknown }>; getLeafId?: () => string | undefined };
|
|
325
|
+
ui?: { getToolsExpanded?: () => boolean; requestRender?: (force?: boolean) => void };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
interface Wrapped {
|
|
329
|
+
original: (width: number) => string[];
|
|
330
|
+
originalInvalidate: (() => void) | undefined;
|
|
331
|
+
panel: HarnessPanel;
|
|
332
|
+
metaId: string;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const OSC133_ZONE_START = "\x1b]133;A\x07";
|
|
336
|
+
const OSC133_ZONE_END = "\x1b]133;B\x07";
|
|
337
|
+
const OSC133_ZONE_FINAL = "\x1b]133;C\x07";
|
|
338
|
+
const MAX_PERSIST_RETRIES = 3;
|
|
339
|
+
|
|
340
|
+
export function installHarnessUserRendering(pi: ExtensionAPI): void {
|
|
341
|
+
let scheduled = false;
|
|
342
|
+
const timers = new Set<ReturnType<typeof setTimeout>>();
|
|
343
|
+
const wrapped = new Map<UserComponentLike, Wrapped>();
|
|
344
|
+
let lastContainer: { children?: unknown[] } | undefined;
|
|
345
|
+
let lastChildrenRef: unknown[] | undefined;
|
|
346
|
+
let lastFirst: unknown;
|
|
347
|
+
let lastLast: unknown;
|
|
348
|
+
let lastLength = -1;
|
|
349
|
+
let lastLeaf: string | undefined;
|
|
350
|
+
let lastTheme: HarnessThemeCompat | undefined;
|
|
351
|
+
let lastCtx: HarnessUiContext;
|
|
352
|
+
let tui: unknown;
|
|
353
|
+
let theme: HarnessThemeCompat | undefined;
|
|
354
|
+
let probeInstalled = false;
|
|
355
|
+
let persistRetries = 0;
|
|
356
|
+
let shuttingDown = false;
|
|
357
|
+
|
|
358
|
+
const requestRender = (): void => {
|
|
359
|
+
try {
|
|
360
|
+
(tui as { requestRender?: (force?: boolean) => void } | undefined)?.requestRender?.(true);
|
|
361
|
+
} catch {}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const schedule = (): void => {
|
|
365
|
+
if (shuttingDown || scheduled) return;
|
|
366
|
+
scheduled = true;
|
|
367
|
+
const t = setTimeout(() => {
|
|
368
|
+
scheduled = false;
|
|
369
|
+
timers.delete(t);
|
|
370
|
+
try {
|
|
371
|
+
reconcile();
|
|
372
|
+
} catch (err) {
|
|
373
|
+
}
|
|
374
|
+
}, 0);
|
|
375
|
+
timers.add(t);
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const captureCtx = (ctx: unknown): void => {
|
|
379
|
+
if (!ctx || typeof ctx !== "object") return;
|
|
380
|
+
lastCtx = ctx as HarnessUiContext;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const leafId = (): string | undefined => {
|
|
384
|
+
try {
|
|
385
|
+
return lastCtx?.sessionManager?.getLeafId?.();
|
|
386
|
+
} catch {
|
|
387
|
+
return undefined;
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const expectedCards = (): ExpectedCard[] => {
|
|
392
|
+
const sm = lastCtx?.sessionManager;
|
|
393
|
+
if (!sm || typeof sm.buildContextEntries !== "function") return [];
|
|
394
|
+
const out: ExpectedCard[] = [];
|
|
395
|
+
for (const entry of sm.buildContextEntries()) {
|
|
396
|
+
if (entry?.type !== "message") continue;
|
|
397
|
+
const message = entry.message as { role?: unknown; content?: unknown; unipiHarness?: unknown } | undefined;
|
|
398
|
+
if (!message || message.role !== "user") continue;
|
|
399
|
+
const text = contentText(message.content);
|
|
400
|
+
const skill = parseSkillBlock(text);
|
|
401
|
+
if (skill) {
|
|
402
|
+
// Skill blocks render as their own component — never harness-labelled.
|
|
403
|
+
if (skill.userMessage) out.push({ entryId: String(entry.id), text: skill.userMessage });
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (text.length === 0) continue; // images-only: no native card
|
|
407
|
+
const meta = readHarnessMeta(message);
|
|
408
|
+
out.push({ entryId: String(entry.id), text, meta });
|
|
409
|
+
}
|
|
410
|
+
return out;
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const isUserComponent = (c: unknown): c is UserComponentLike =>
|
|
414
|
+
!!c && typeof c === "object" &&
|
|
415
|
+
typeof (c as { text?: unknown }).text === "string" &&
|
|
416
|
+
typeof (c as { rebuild?: unknown }).rebuild === "function" &&
|
|
417
|
+
typeof (c as { setOutputPad?: unknown }).setOutputPad === "function" &&
|
|
418
|
+
(c as { message?: unknown }).message === undefined;
|
|
419
|
+
|
|
420
|
+
function unpatch(comp: UserComponentLike, w: Wrapped): void {
|
|
421
|
+
comp.render = w.original;
|
|
422
|
+
if (w.originalInvalidate) comp.invalidate = w.originalInvalidate;
|
|
423
|
+
else delete (comp as { invalidate?: unknown }).invalidate;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function unpatchAll(): boolean {
|
|
427
|
+
if (wrapped.size === 0) return false;
|
|
428
|
+
for (const [comp, w] of wrapped) unpatch(comp, w);
|
|
429
|
+
wrapped.clear();
|
|
430
|
+
return true;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const reconcile = (): void => {
|
|
434
|
+
const container = findTranscriptContainer(tui) as { children?: unknown[] } | undefined;
|
|
435
|
+
if (!container || !Array.isArray(container.children)) return;
|
|
436
|
+
const children = container.children;
|
|
437
|
+
const comps = children.filter((c): c is UserComponentLike => isUserComponent(c));
|
|
438
|
+
const expected = expectedCards();
|
|
439
|
+
|
|
440
|
+
let changed = false;
|
|
441
|
+
let ok = comps.length === expected.length;
|
|
442
|
+
if (ok) {
|
|
443
|
+
for (let i = 0; i < comps.length; i++) {
|
|
444
|
+
if (comps[i]!.text !== expected[i]!.text) {
|
|
445
|
+
ok = false;
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (!ok) {
|
|
451
|
+
// Mismatch: fall back to native. Bounded persistence retries after a
|
|
452
|
+
// message_end (the entry may not be visible yet); then stop rescheduling.
|
|
453
|
+
changed = unpatchAll();
|
|
454
|
+
if (persistRetries > 0) {
|
|
455
|
+
persistRetries -= 1;
|
|
456
|
+
schedule();
|
|
457
|
+
}
|
|
458
|
+
} else {
|
|
459
|
+
persistRetries = 0;
|
|
460
|
+
// Prune wrapped entries whose component is absent.
|
|
461
|
+
for (const comp of [...wrapped.keys()]) {
|
|
462
|
+
if (!comps.includes(comp)) {
|
|
463
|
+
unpatch(comp, wrapped.get(comp)!);
|
|
464
|
+
wrapped.delete(comp);
|
|
465
|
+
changed = true;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
for (let i = 0; i < comps.length; i++) {
|
|
469
|
+
const comp = comps[i]!;
|
|
470
|
+
const meta = expected[i]!.meta;
|
|
471
|
+
const existing = wrapped.get(comp);
|
|
472
|
+
if (!meta) {
|
|
473
|
+
if (existing) {
|
|
474
|
+
unpatch(comp, existing);
|
|
475
|
+
wrapped.delete(comp);
|
|
476
|
+
changed = true;
|
|
477
|
+
}
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (existing && existing.metaId === meta.id) continue;
|
|
481
|
+
// New wrap OR metadata changed (same component) → (re)create the panel.
|
|
482
|
+
if (existing) unpatch(comp, existing);
|
|
483
|
+
const panel = new HarnessPanel(comp.text, meta, {
|
|
484
|
+
expanded: () => lastCtx?.ui?.getToolsExpanded?.() === true,
|
|
485
|
+
style: () => renderStyle(lastCtx?.cwd) as RenderStyle,
|
|
486
|
+
}, () => theme ?? { getColorMode: () => (process.env.COLORTERM === "truecolor" ? "truecolor" : "256color") });
|
|
487
|
+
const original = comp.render.bind(comp);
|
|
488
|
+
const originalInvalidate = comp.invalidate?.bind(comp);
|
|
489
|
+
const osc = (rows: string[]): string[] => {
|
|
490
|
+
if (rows.length === 0) return rows;
|
|
491
|
+
const o = [...rows];
|
|
492
|
+
o[0] = OSC133_ZONE_START + o[0]!;
|
|
493
|
+
o[o.length - 1] = OSC133_ZONE_END + OSC133_ZONE_FINAL + o[o.length - 1]!;
|
|
494
|
+
return o;
|
|
495
|
+
};
|
|
496
|
+
comp.render = (w: number) => osc(panel.render(w));
|
|
497
|
+
comp.invalidate = () => {
|
|
498
|
+
panel.invalidate();
|
|
499
|
+
originalInvalidate?.();
|
|
500
|
+
};
|
|
501
|
+
wrapped.set(comp, { original, originalInvalidate, panel, metaId: meta.id });
|
|
502
|
+
changed = true;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Store the O(1) probe fields AFTER this pass — both paths.
|
|
507
|
+
lastContainer = container;
|
|
508
|
+
lastChildrenRef = children;
|
|
509
|
+
lastLength = children.length;
|
|
510
|
+
lastFirst = children[0];
|
|
511
|
+
lastLast = children[children.length - 1];
|
|
512
|
+
lastLeaf = leafId();
|
|
513
|
+
lastTheme = theme;
|
|
514
|
+
|
|
515
|
+
if (changed) requestRender();
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
/** O(1) probe against the stored identity fields. */
|
|
519
|
+
const probe = (): void => {
|
|
520
|
+
if (shuttingDown) return;
|
|
521
|
+
const container = findTranscriptContainer(tui) as { children?: unknown[] } | undefined;
|
|
522
|
+
if (!container) return;
|
|
523
|
+
const children = container.children;
|
|
524
|
+
if (!Array.isArray(children)) return;
|
|
525
|
+
const leaf = leafId();
|
|
526
|
+
if (
|
|
527
|
+
container !== lastContainer ||
|
|
528
|
+
children !== lastChildrenRef ||
|
|
529
|
+
children.length !== lastLength ||
|
|
530
|
+
children[0] !== lastFirst ||
|
|
531
|
+
children[children.length - 1] !== lastLast ||
|
|
532
|
+
leaf !== lastLeaf ||
|
|
533
|
+
theme !== lastTheme
|
|
534
|
+
) {
|
|
535
|
+
schedule();
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
pi.on("session_start", ((_event: unknown, ctx: unknown) => {
|
|
541
|
+
captureCtx(ctx);
|
|
542
|
+
// AFTER event on a FRESH context: re-register the probe widget.
|
|
543
|
+
probeInstalled = false;
|
|
544
|
+
const c = lastCtx as { hasUI?: boolean; ui?: { setWidget?: (name: string, render: (t: unknown, th: unknown) => unknown, opts?: unknown) => void } } | undefined;
|
|
545
|
+
if (!c?.hasUI || probeInstalled || typeof c.ui?.setWidget !== "function") return;
|
|
546
|
+
probeInstalled = true;
|
|
547
|
+
try {
|
|
548
|
+
// 0-height probe widget: the factory captures root+theme once; the
|
|
549
|
+
// render callback runs per frame and O(1)-probes for changes.
|
|
550
|
+
c.ui.setWidget(
|
|
551
|
+
"unipi.harnessUserProbe",
|
|
552
|
+
(t: unknown, th: unknown) => {
|
|
553
|
+
tui = t;
|
|
554
|
+
theme = th as HarnessThemeCompat;
|
|
555
|
+
probe();
|
|
556
|
+
return {
|
|
557
|
+
invalidate(): void {
|
|
558
|
+
schedule();
|
|
559
|
+
},
|
|
560
|
+
render(): string[] {
|
|
561
|
+
probe();
|
|
562
|
+
return [];
|
|
563
|
+
},
|
|
564
|
+
};
|
|
565
|
+
},
|
|
566
|
+
{ placement: "belowEditor" },
|
|
567
|
+
);
|
|
568
|
+
} catch {}
|
|
569
|
+
}) as never);
|
|
570
|
+
} catch {
|
|
571
|
+
// optional
|
|
572
|
+
}
|
|
573
|
+
const onAny = pi.on as unknown as (event: string, handler: (event?: unknown, ctx?: unknown) => unknown) => void;
|
|
574
|
+
for (const evt of ["message_start", "message_end", "agent_start"]) {
|
|
575
|
+
onAny(evt, (event, ctx) => {
|
|
576
|
+
if (ctx) captureCtx(ctx);
|
|
577
|
+
if (evt === "message_end") persistRetries = MAX_PERSIST_RETRIES;
|
|
578
|
+
schedule();
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
for (const evt of ["session_before_switch", "session_before_fork", "session_before_tree"]) {
|
|
582
|
+
// BEFORE events: cancel stale pending work — do not apply the old tree.
|
|
583
|
+
onAny(evt, () => {
|
|
584
|
+
for (const t of timers) clearTimeout(t);
|
|
585
|
+
timers.clear();
|
|
586
|
+
scheduled = false;
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
for (const evt of ["session_tree", "session_compact"]) {
|
|
590
|
+
// AFTER events: full rebind against the new tree.
|
|
591
|
+
onAny(evt, (_event, ctx) => {
|
|
592
|
+
if (ctx) captureCtx(ctx);
|
|
593
|
+
persistRetries = MAX_PERSIST_RETRIES;
|
|
594
|
+
schedule();
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
onAny("session_shutdown", () => {
|
|
598
|
+
shuttingDown = true;
|
|
599
|
+
for (const t of timers) clearTimeout(t);
|
|
600
|
+
timers.clear();
|
|
601
|
+
unpatchAll();
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
// ── tool annotations ────────────────────────────────────────────────────────
|
|
607
|
+
const ANNOTATION_WRAPPED = Symbol.for("unipi.harnessToolAnnotations");
|
|
608
|
+
|
|
609
|
+
function annotationsOf(result: unknown): HarnessToolAnnotation[] {
|
|
610
|
+
const details = (result as { details?: { unipiHarnessAnnotations?: unknown } } | null | undefined)?.details;
|
|
611
|
+
const list = details?.unipiHarnessAnnotations;
|
|
612
|
+
if (!Array.isArray(list)) return [];
|
|
613
|
+
return list.filter((a): a is HarnessToolAnnotation => {
|
|
614
|
+
const meta = (a as { meta?: unknown })?.meta;
|
|
615
|
+
const text = (a as { text?: unknown })?.text;
|
|
616
|
+
return !!meta && typeof text === "string" && readHarnessMeta({ unipiHarness: meta }) !== undefined;
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/** Width-aware lazy composite: renders base + annotation rows at call width.
|
|
621
|
+
* `base` may be a Component (live width) or pre-rendered rows (string[]). */
|
|
622
|
+
function composeWithRows(base: unknown, rows: (width: number) => string[]): Component {
|
|
623
|
+
const inner = base as Component | undefined;
|
|
624
|
+
if (inner && typeof inner.render === "function") {
|
|
625
|
+
return {
|
|
626
|
+
render(width: number): string[] {
|
|
627
|
+
return [...inner.render(width), ...rows(width)];
|
|
628
|
+
},
|
|
629
|
+
invalidate(): void {
|
|
630
|
+
inner?.invalidate?.();
|
|
631
|
+
},
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
const fixed = Array.isArray(base) ? (base as string[]) : base !== undefined && base !== null ? [String(base)] : [];
|
|
635
|
+
return {
|
|
636
|
+
render(width: number): string[] {
|
|
637
|
+
return [...fixed, ...rows(width)];
|
|
638
|
+
},
|
|
639
|
+
invalidate(): void {},
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/** Wrap a tool renderer definition so results carrying `unipiHarnessAnnotations`
|
|
644
|
+
* get a provenance header. Original output (including the annotation text
|
|
645
|
+
* itself, shown exactly once) is untouched; defs without annotations render
|
|
646
|
+
* EXACTLY as before; defs without a renderResult keep the native default.
|
|
647
|
+
* Idempotent per definition (mutates + marks). */
|
|
648
|
+
export function withHarnessToolAnnotations<T extends object>(def: T & { name?: string }): T {
|
|
649
|
+
const anyDef = def as { [ANNOTATION_WRAPPED]?: boolean; renderResult?: unknown; simpleResult?: unknown; name?: string };
|
|
650
|
+
if (anyDef[ANNOTATION_WRAPPED]) return def;
|
|
651
|
+
const toolName = typeof anyDef.name === "string" ? anyDef.name : undefined;
|
|
652
|
+
const headerFor = (result: unknown): string[] => {
|
|
653
|
+
const pal = paletteFor(undefined);
|
|
654
|
+
const rows: string[] = [];
|
|
655
|
+
for (const a of annotationsOf(result)) {
|
|
656
|
+
const glyph = a.meta.severity === "warning" ? `${pal.warn}⚠${FG_RESET}` : `${pal.violet}◇${FG_RESET}`;
|
|
657
|
+
rows.push(`${glyph} UniPi · ${pal.violet}${a.meta.source}${FG_RESET} · ${a.meta.title}${toolName ? ` · ${toolName} guidance` : " · tool guidance"}`);
|
|
658
|
+
if (a.meta.synopsis) rows.push(` ${a.meta.synopsis}`);
|
|
659
|
+
}
|
|
660
|
+
return rows;
|
|
661
|
+
};
|
|
662
|
+
if (typeof anyDef.renderResult === "function") {
|
|
663
|
+
const original = anyDef.renderResult as unknown as (r: unknown, o: unknown, t: unknown, c?: unknown) => unknown;
|
|
664
|
+
anyDef.renderResult = ((result: unknown, options: unknown, theme: unknown, ctx: unknown) => {
|
|
665
|
+
const out: unknown = original(result, options, theme, ctx);
|
|
666
|
+
if (annotationsOf(result).length === 0) return out;
|
|
667
|
+
return composeWithRows(out, (width) => headerFor(result).map((l) => truncateToWidth(l, width)));
|
|
668
|
+
}) as unknown as (result: never, options: never, theme: never, ctx: never) => unknown;
|
|
669
|
+
}
|
|
670
|
+
const originalSimple = anyDef.simpleResult as unknown as ((r: unknown, t: unknown, c?: unknown) => unknown) | undefined;
|
|
671
|
+
anyDef.simpleResult = ((result: unknown, theme: unknown, ctx?: unknown) => {
|
|
672
|
+
const base: unknown = originalSimple ? originalSimple(result, theme, ctx) : undefined;
|
|
673
|
+
const anns = annotationsOf(result);
|
|
674
|
+
if (anns.length === 0) return base;
|
|
675
|
+
return composeWithRows(base, (width) =>
|
|
676
|
+
anns.flatMap((a) =>
|
|
677
|
+
wrapTextWithAnsi(`⚠ UniPi · ${a.meta.source} · ${a.meta.synopsis ?? a.meta.title}`, width),
|
|
678
|
+
),
|
|
679
|
+
);
|
|
680
|
+
}) as unknown as (result: never, theme: never, ctx?: never) => unknown;
|
|
681
|
+
Object.defineProperty(def, ANNOTATION_WRAPPED, { value: true, enumerable: false });
|
|
682
|
+
return def;
|
|
683
|
+
}
|