@sagmans/dsh-tui 0.6.0 → 0.7.0

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.
Files changed (250) hide show
  1. package/.agents/skills/dsh-tui-dogfood/SKILL.md +83 -0
  2. package/.agents/skills/dsh-tui-dogfood/references/home-state.md +78 -0
  3. package/.agents/skills/dsh-tui-dogfood/scripts/clone-links.mjs +133 -0
  4. package/.agents/skills/dsh-tui-dogfood/scripts/run-plugin-from-worktree.sh +417 -0
  5. package/.agents/skills/dsh-tui-update-models/SKILL.md +121 -0
  6. package/.agents/skills/dsh-tui-update-models/references/model-wiring.md +162 -0
  7. package/.agents/skills/dsh-tui-update-models/scripts/dump-model-catalog.mjs +178 -0
  8. package/README.md +99 -5
  9. package/lib/agent/present.d.ts.map +1 -1
  10. package/lib/agent/present.js +2 -1
  11. package/lib/agent/present.js.map +1 -1
  12. package/lib/cards/composition.d.ts +68 -0
  13. package/lib/cards/composition.d.ts.map +1 -0
  14. package/lib/cards/composition.js +117 -0
  15. package/lib/cards/composition.js.map +1 -0
  16. package/lib/cards/presenter.d.ts +23 -0
  17. package/lib/cards/presenter.d.ts.map +1 -0
  18. package/lib/cards/presenter.js +394 -0
  19. package/lib/cards/presenter.js.map +1 -0
  20. package/lib/cards/preview.d.ts +42 -0
  21. package/lib/cards/preview.d.ts.map +1 -0
  22. package/lib/cards/preview.js +51 -0
  23. package/lib/cards/preview.js.map +1 -0
  24. package/lib/cards.d.ts +12 -120
  25. package/lib/cards.d.ts.map +1 -1
  26. package/lib/cards.js +4 -556
  27. package/lib/cards.js.map +1 -1
  28. package/lib/compat/section.d.ts +29 -0
  29. package/lib/compat/section.d.ts.map +1 -0
  30. package/lib/compat/section.js +82 -0
  31. package/lib/compat/section.js.map +1 -0
  32. package/lib/config.d.ts +27 -2
  33. package/lib/config.d.ts.map +1 -1
  34. package/lib/config.js +61 -1
  35. package/lib/config.js.map +1 -1
  36. package/lib/contracts.d.ts +28 -0
  37. package/lib/contracts.d.ts.map +1 -1
  38. package/lib/fold-cursor.d.ts +8 -0
  39. package/lib/fold-cursor.d.ts.map +1 -1
  40. package/lib/fold-cursor.js +18 -0
  41. package/lib/fold-cursor.js.map +1 -1
  42. package/lib/gates/question-card.d.ts +77 -0
  43. package/lib/gates/question-card.d.ts.map +1 -0
  44. package/lib/gates/question-card.js +113 -0
  45. package/lib/gates/question-card.js.map +1 -0
  46. package/lib/gates/questions.d.ts +127 -0
  47. package/lib/gates/questions.d.ts.map +1 -0
  48. package/lib/gates/questions.js +418 -0
  49. package/lib/gates/questions.js.map +1 -0
  50. package/lib/gates.d.ts +1 -113
  51. package/lib/gates.d.ts.map +1 -1
  52. package/lib/gates.js +1 -484
  53. package/lib/gates.js.map +1 -1
  54. package/lib/index.d.ts +1 -0
  55. package/lib/index.d.ts.map +1 -1
  56. package/lib/index.js +343 -2602
  57. package/lib/index.js.map +1 -1
  58. package/lib/input/action-catalog.d.ts +74 -0
  59. package/lib/input/action-catalog.d.ts.map +1 -0
  60. package/lib/input/action-catalog.js +137 -0
  61. package/lib/input/action-catalog.js.map +1 -0
  62. package/lib/input/actions.d.ts +5 -81
  63. package/lib/input/actions.d.ts.map +1 -1
  64. package/lib/input/actions.js +4 -625
  65. package/lib/input/actions.js.map +1 -1
  66. package/lib/input/completion.d.ts +1 -1
  67. package/lib/input/completion.d.ts.map +1 -1
  68. package/lib/input/completion.js +2 -1
  69. package/lib/input/completion.js.map +1 -1
  70. package/lib/input/file-index.d.ts +40 -0
  71. package/lib/input/file-index.d.ts.map +1 -0
  72. package/lib/input/file-index.js +141 -0
  73. package/lib/input/file-index.js.map +1 -0
  74. package/lib/input/file-search.d.ts +1 -38
  75. package/lib/input/file-search.d.ts.map +1 -1
  76. package/lib/input/file-search.js +1 -487
  77. package/lib/input/file-search.js.map +1 -1
  78. package/lib/input/key-press.d.ts +57 -0
  79. package/lib/input/key-press.d.ts.map +1 -0
  80. package/lib/input/key-press.js +218 -0
  81. package/lib/input/key-press.js.map +1 -0
  82. package/lib/input/keymap-conflicts.d.ts +59 -0
  83. package/lib/input/keymap-conflicts.d.ts.map +1 -0
  84. package/lib/input/keymap-conflicts.js +292 -0
  85. package/lib/input/keymap-conflicts.js.map +1 -0
  86. package/lib/input/keymap-settings.js +1 -1
  87. package/lib/input/keymap-settings.js.map +1 -1
  88. package/lib/input/keymap.d.ts.map +1 -1
  89. package/lib/input/keymap.js +3 -1
  90. package/lib/input/keymap.js.map +1 -1
  91. package/lib/input/workspace-files.d.ts +10 -0
  92. package/lib/input/workspace-files.d.ts.map +1 -0
  93. package/lib/input/workspace-files.js +359 -0
  94. package/lib/input/workspace-files.js.map +1 -0
  95. package/lib/install-skills.d.ts +20 -0
  96. package/lib/install-skills.d.ts.map +1 -0
  97. package/lib/install-skills.js +198 -0
  98. package/lib/install-skills.js.map +1 -0
  99. package/lib/keys-command.d.ts +2 -1
  100. package/lib/keys-command.d.ts.map +1 -1
  101. package/lib/keys-command.js +2 -1
  102. package/lib/keys-command.js.map +1 -1
  103. package/lib/model-list.d.ts +37 -0
  104. package/lib/model-list.d.ts.map +1 -0
  105. package/lib/model-list.js +60 -0
  106. package/lib/model-list.js.map +1 -0
  107. package/lib/startup.d.ts +8 -0
  108. package/lib/startup.d.ts.map +1 -1
  109. package/lib/startup.js +120 -24
  110. package/lib/startup.js.map +1 -1
  111. package/lib/subagents.d.ts +7 -3
  112. package/lib/subagents.d.ts.map +1 -1
  113. package/lib/subagents.js +28 -4
  114. package/lib/subagents.js.map +1 -1
  115. package/lib/surface/appearance-preferences.d.ts +26 -0
  116. package/lib/surface/appearance-preferences.d.ts.map +1 -0
  117. package/lib/surface/appearance-preferences.js +139 -0
  118. package/lib/surface/appearance-preferences.js.map +1 -0
  119. package/lib/surface/appearance.d.ts +87 -0
  120. package/lib/surface/appearance.d.ts.map +1 -0
  121. package/lib/surface/appearance.js +353 -0
  122. package/lib/surface/appearance.js.map +1 -0
  123. package/lib/surface/background-work.d.ts +49 -0
  124. package/lib/surface/background-work.d.ts.map +1 -0
  125. package/lib/surface/background-work.js +146 -0
  126. package/lib/surface/background-work.js.map +1 -0
  127. package/lib/surface/commands.d.ts +80 -0
  128. package/lib/surface/commands.d.ts.map +1 -0
  129. package/lib/surface/commands.js +364 -0
  130. package/lib/surface/commands.js.map +1 -0
  131. package/lib/surface/modal-input.d.ts +58 -0
  132. package/lib/surface/modal-input.d.ts.map +1 -0
  133. package/lib/surface/modal-input.js +237 -0
  134. package/lib/surface/modal-input.js.map +1 -0
  135. package/lib/surface/model-choice.d.ts +54 -0
  136. package/lib/surface/model-choice.d.ts.map +1 -0
  137. package/lib/surface/model-choice.js +325 -0
  138. package/lib/surface/model-choice.js.map +1 -0
  139. package/lib/surface/preset-choice.d.ts +78 -0
  140. package/lib/surface/preset-choice.d.ts.map +1 -0
  141. package/lib/surface/preset-choice.js +163 -0
  142. package/lib/surface/preset-choice.js.map +1 -0
  143. package/lib/surface/prompt-input.d.ts +97 -0
  144. package/lib/surface/prompt-input.d.ts.map +1 -0
  145. package/lib/surface/prompt-input.js +215 -0
  146. package/lib/surface/prompt-input.js.map +1 -0
  147. package/lib/surface/prompt-memory.d.ts +76 -0
  148. package/lib/surface/prompt-memory.d.ts.map +1 -0
  149. package/lib/surface/prompt-memory.js +167 -0
  150. package/lib/surface/prompt-memory.js.map +1 -0
  151. package/lib/surface/session-lifecycle.d.ts +120 -0
  152. package/lib/surface/session-lifecycle.d.ts.map +1 -0
  153. package/lib/surface/session-lifecycle.js +321 -0
  154. package/lib/surface/session-lifecycle.js.map +1 -0
  155. package/lib/surface/session-picker.d.ts +39 -0
  156. package/lib/surface/session-picker.d.ts.map +1 -0
  157. package/lib/surface/session-picker.js +107 -0
  158. package/lib/surface/session-picker.js.map +1 -0
  159. package/lib/surface/session-view.d.ts +86 -0
  160. package/lib/surface/session-view.d.ts.map +1 -0
  161. package/lib/surface/session-view.js +215 -0
  162. package/lib/surface/session-view.js.map +1 -0
  163. package/lib/surface/staged-turns.d.ts +81 -0
  164. package/lib/surface/staged-turns.d.ts.map +1 -0
  165. package/lib/surface/staged-turns.js +206 -0
  166. package/lib/surface/staged-turns.js.map +1 -0
  167. package/lib/surface/terminal-lifecycle.d.ts +76 -0
  168. package/lib/surface/terminal-lifecycle.d.ts.map +1 -0
  169. package/lib/surface/terminal-lifecycle.js +222 -0
  170. package/lib/surface/terminal-lifecycle.js.map +1 -0
  171. package/lib/terminal-text/scan.d.ts +44 -0
  172. package/lib/terminal-text/scan.d.ts.map +1 -0
  173. package/lib/terminal-text/scan.js +195 -0
  174. package/lib/terminal-text/scan.js.map +1 -0
  175. package/lib/terminal-text/sgr.d.ts +32 -0
  176. package/lib/terminal-text/sgr.d.ts.map +1 -0
  177. package/lib/terminal-text/sgr.js +203 -0
  178. package/lib/terminal-text/sgr.js.map +1 -0
  179. package/lib/terminal-text.d.ts +1 -1
  180. package/lib/terminal-text.d.ts.map +1 -1
  181. package/lib/terminal-text.js +2 -379
  182. package/lib/terminal-text.js.map +1 -1
  183. package/lib/theme-command.js +2 -1
  184. package/lib/theme-command.js.map +1 -1
  185. package/lib/theme-defaults.d.ts +64 -0
  186. package/lib/theme-defaults.d.ts.map +1 -0
  187. package/lib/theme-defaults.js +248 -0
  188. package/lib/theme-defaults.js.map +1 -0
  189. package/lib/theme-resolver.d.ts +36 -0
  190. package/lib/theme-resolver.d.ts.map +1 -0
  191. package/lib/theme-resolver.js +112 -0
  192. package/lib/theme-resolver.js.map +1 -0
  193. package/lib/theme-schema.d.ts.map +1 -1
  194. package/lib/theme-schema.js +3 -2
  195. package/lib/theme-schema.js.map +1 -1
  196. package/lib/theme-settings.d.ts +5 -7
  197. package/lib/theme-settings.d.ts.map +1 -1
  198. package/lib/theme-settings.js +27 -19
  199. package/lib/theme-settings.js.map +1 -1
  200. package/lib/theme-tokens.d.ts +0 -89
  201. package/lib/theme-tokens.d.ts.map +1 -1
  202. package/lib/theme-tokens.js +0 -356
  203. package/lib/theme-tokens.js.map +1 -1
  204. package/lib/theme.d.ts +1 -1
  205. package/lib/theme.d.ts.map +1 -1
  206. package/lib/theme.js +2 -1
  207. package/lib/theme.js.map +1 -1
  208. package/lib/tool-display.d.ts.map +1 -1
  209. package/lib/tool-display.js +2 -1
  210. package/lib/tool-display.js.map +1 -1
  211. package/lib/transcript/message-content.d.ts +21 -0
  212. package/lib/transcript/message-content.d.ts.map +1 -0
  213. package/lib/transcript/message-content.js +61 -0
  214. package/lib/transcript/message-content.js.map +1 -0
  215. package/lib/transcript/tool-calls.d.ts +106 -0
  216. package/lib/transcript/tool-calls.d.ts.map +1 -0
  217. package/lib/transcript/tool-calls.js +254 -0
  218. package/lib/transcript/tool-calls.js.map +1 -0
  219. package/lib/transcript.d.ts +14 -55
  220. package/lib/transcript.d.ts.map +1 -1
  221. package/lib/transcript.js +31 -253
  222. package/lib/transcript.js.map +1 -1
  223. package/lib/ui/dock.d.ts +14 -2
  224. package/lib/ui/dock.d.ts.map +1 -1
  225. package/lib/ui/dock.js +52 -10
  226. package/lib/ui/dock.js.map +1 -1
  227. package/lib/ui/editor.d.ts.map +1 -1
  228. package/lib/ui/editor.js +2 -1
  229. package/lib/ui/editor.js.map +1 -1
  230. package/lib/ui/keymap-picker.d.ts +2 -1
  231. package/lib/ui/keymap-picker.d.ts.map +1 -1
  232. package/lib/ui/keymap-picker.js +1 -0
  233. package/lib/ui/keymap-picker.js.map +1 -1
  234. package/lib/ui/view/gate-card.d.ts +21 -0
  235. package/lib/ui/view/gate-card.d.ts.map +1 -0
  236. package/lib/ui/view/gate-card.js +104 -0
  237. package/lib/ui/view/gate-card.js.map +1 -0
  238. package/lib/ui/view/tool-card.d.ts +138 -0
  239. package/lib/ui/view/tool-card.d.ts.map +1 -0
  240. package/lib/ui/view/tool-card.js +413 -0
  241. package/lib/ui/view/tool-card.js.map +1 -0
  242. package/lib/ui/view/transcript-message.d.ts +62 -0
  243. package/lib/ui/view/transcript-message.d.ts.map +1 -0
  244. package/lib/ui/view/transcript-message.js +158 -0
  245. package/lib/ui/view/transcript-message.js.map +1 -0
  246. package/lib/ui/view.d.ts +80 -220
  247. package/lib/ui/view.d.ts.map +1 -1
  248. package/lib/ui/view.js +81 -675
  249. package/lib/ui/view.js.map +1 -1
  250. package/package.json +3 -1
@@ -0,0 +1,162 @@
1
+ # Model wiring reference
2
+
3
+ Companion to [SKILL.md](../SKILL.md): exact fields, files, and checks.
4
+
5
+ ## Resolution order
6
+
7
+ 1. Bundle layers in `cordis.yml`, in order.
8
+ 2. The profile patch `~/.dsh/profiles/<profile>/cordis.patch.yml`, entry by entry:
9
+ `id` targeted config, `disabled`, `insert`.
10
+ 3. A patch `config` **replaces** that row's base config; fields it omits fall back
11
+ to the schema default, not to the bundle's value.
12
+ 4. The model directory a profile sees is owned by whichever row is enabled:
13
+ `llm-pi-ai` (additive provider config), `dsh-provider-extra` (owned catalog),
14
+ or both, if the profile is not using exclusive ownership.
15
+
16
+ Exclusive ownership is a set of disable rows plus the catalog itself:
17
+
18
+ ```yaml
19
+ - { "id": "agent-default-model", "disabled": true }
20
+ - { "id": "llm-pi-ai", "disabled": true }
21
+ - { "id": "llm-deepseek", "disabled": true }
22
+ - id: dsh-provider-extra
23
+ config: { catalog: { version: 1, ... } }
24
+ ```
25
+
26
+ ## `llm-pi-ai` provider config
27
+
28
+ ```yaml
29
+ - id: llm-pi-ai
30
+ config:
31
+ providers:
32
+ qwen-token-plan:
33
+ apiKeyEnv: QWEN_TOKEN_PLAN_API_KEY # env var holding the key
34
+ baseURL: https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1
35
+ api: openai-completions # optional wire override
36
+ models:
37
+ - id: qwen3.8-max # served as-is
38
+ - id: deepseek-v4.1-flash
39
+ name: DeepSeek V4.1 Flash
40
+ contextWindow: 1000000
41
+ maxTokens: 384000
42
+ input: [text, image]
43
+ reasoningEfforts: { low: low, high: high, max: max }
44
+ compat:
45
+ supportsStore: false
46
+ supportsDeveloperRole: false
47
+ supportsReasoningEffort: true
48
+ supportsStrictMode: true
49
+ thinkingFormat: qwen
50
+ ```
51
+
52
+ An id that the installed catalog already describes needs no restatement. An id it
53
+ does not describe needs the metadata (or a `template` sibling to clone).
54
+
55
+ ## `dsh-provider-extra`
56
+
57
+ Two config generations exist; a profile normally uses one:
58
+
59
+ | Config | Use |
60
+ | --- | --- |
61
+ | Additive: `routeId`, `displayName`, `apiKeyEnv`, `fallbackSessionId`, `extraModels`, `models`, `codexExtraModels`, `codexModels`, `codexTransport` | add a gateway or subscription route beside whatever else the profile serves |
62
+ | `catalog` v1 | own the entire directory: ids, aliases, metadata, and the default selection |
63
+
64
+ Catalog fields:
65
+
66
+ ```yaml
67
+ catalog:
68
+ version: 1 # exactly 1
69
+ default: # null only when the catalog is empty
70
+ { provider: opencode-go-session, model: deepseek-flash, reasoningEffort: max }
71
+ providers:
72
+ - id: opencode-go-session # the id every selection and alias check uses
73
+ name: OpenCode Go (session)
74
+ source: opencode-go # pi-ai provider id: its installed data is the base
75
+ baseURL: https://api.x.ai/v1 # optional
76
+ transport: sse # optional
77
+ fallbackSessionId: dsh-provider-extra # optional
78
+ auth: { apiKeyRef: OPENCODE_GO_API_KEY } # or { credentialProvider: openai-codex }
79
+ models:
80
+ - id: deepseek-flash
81
+ name: DeepSeek V4.1 Flash
82
+ aliases: [deepseek-v4.1-flash] # extra ids that resolve to this model
83
+ template: deepseek-v4-flash # clone wire behavior when the source lacks the id
84
+ defaultMaxTokens: 384000 # request ceiling; metadata.maxTokens overrides
85
+ metadata:
86
+ api: openai-completions
87
+ reasoning: true
88
+ input: [text, image]
89
+ cost: { input: 0.22, output: 0.66, cacheRead: 0.007, cacheWrite: 0 }
90
+ contextWindow: 1000000
91
+ maxTokens: 384000
92
+ thinkingLevelMap: { off: null, minimal: null, low: low, medium: null, high: high, xhigh: null, max: max }
93
+ compat: { supportsStore: false, supportsDeveloperRole: false, supportsReasoningEffort: true, supportsStrictMode: true, thinkingFormat: qwen }
94
+ ```
95
+
96
+ Behaviour:
97
+
98
+ | Case | Result |
99
+ | --- | --- |
100
+ | `template` names a sibling the source ships | clone its wire behavior and metadata |
101
+ | `template` names nothing | that model lands in the route's diagnostics; the rest of the route still serves |
102
+ | a `models` whitelist names an id nothing provides | the whole route is refused - a typo there is a broken deployment |
103
+ | two providers share an id | composition error |
104
+ | `default` names a model outside the catalog | composition error |
105
+ | `reasoningEffort` unsupported by the model | composition error |
106
+
107
+ ## Where the base data comes from
108
+
109
+ ```sh
110
+ # the harness install owns the pi-ai catalog the profile resolves against
111
+ DSH=$(readlink -f "$(command -v dsh)")
112
+ PI_AI="${DSH%/lib/bin.js}/node_modules/@earendil-works/pi-ai"
113
+ ls "$PI_AI/dist/providers/data" # one JSON per provider source, keyed by api
114
+ ```
115
+
116
+ A JSON entry is the whole truth for a `source`: `id`, `name`, `cost`,
117
+ `contextWindow`, `maxTokens`, `input`, `reasoning`, `thinkingLevelMap`, `compat`.
118
+ A model declared without `template` and without `metadata` is served exactly as
119
+ this file describes it, so an id the vendor renamed or repriced shows stale
120
+ numbers until the catalog declares them. An entry that carries its own complete
121
+ `metadata` needs no installed entry at all, which is how a route the harness does
122
+ not describe is wired.
123
+
124
+ ## Credentials
125
+
126
+ | Field | Meaning |
127
+ | --- | --- |
128
+ | `apiKeyEnv` | read the key from this environment variable |
129
+ | `apiKeyRef` | read it from the profiles' stored credentials under this reference |
130
+ | `credentialProvider` | use a login flow's stored credentials (codex subscriptions) |
131
+
132
+ Keys never belong in a patch, a settings file, or a repository. Put launch-time
133
+ values in `$DSH_HOME/.env` or the environment. A provider row whose credential is
134
+ missing fails its route only; other routes still serve.
135
+
136
+ ## Verify
137
+
138
+ ```sh
139
+ dsh --profile tui list-models # provider/model<TAB>display name, one per reachable route
140
+ ~/.agents/skills/dsh-tui-update-models/scripts/dump-model-catalog.mjs --home "$DSH_HOME"
141
+ ```
142
+
143
+ `list-models` exits zero only after printing a line, so a deployment that
144
+ advertises nothing cannot pass for one that was enumerated.
145
+
146
+ For every model you touched, confirm against the vendor's own page:
147
+
148
+ 1. price per 1M tokens for input, output, cached input, cache write;
149
+ 2. context window and maximum output tokens (a "272K" beside "above 272K input
150
+ is priced 2x" is a billing threshold, not the window);
151
+ 3. the accepted reasoning effort values, and how the vendor spells "no reasoning"
152
+ (OpenAI: `none`; codex: omit the parameter);
153
+ 4. whether the route bills per token at all - a credit or token plan wants cost
154
+ `0`, a metered API wants its published rates;
155
+ 5. that an `xhigh` or `max` mapping exists when the vendor documents one.
156
+
157
+ A model with no vendor page (stealth releases, previews) is documented only by the
158
+ routing catalog: its pi.dev model page and `models.dev/api.json`.
159
+
160
+ Then run the profile against a cloned home before the real one - see the
161
+ `dsh-tui-dogfood` skill - and `pnpm run build` any linked bundle first: a linked
162
+ profile loads `lib/`, not `src/`.
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Print the model directory a dsh profile resolves, with the provenance of
4
+ * every field.
5
+ *
6
+ * The surface reads prices, context windows, and reasoning levels from the
7
+ * composition, not from the vendor's page, so an inherited or stale value is
8
+ * invisible in the UI. This reads the same two inputs the resolver does - the
9
+ * owned catalog in the profile patch, and the installed pi-ai provider data -
10
+ * and names where each number came from: "installed" for the source's own
11
+ * entry, "template:<id>" for a clone, "metadata" for what the catalog declares.
12
+ * An entry the catalog spells out completely needs no installed entry at all,
13
+ * so a missing harness install must not hide it.
14
+ *
15
+ * Usage: dump-model-catalog.mjs [--home <dsh home>] [--profile <name>] [--json]
16
+ */
17
+ import { execFileSync } from 'node:child_process'
18
+ import { readFileSync, realpathSync } from 'node:fs'
19
+ import { homedir } from 'node:os'
20
+ import { join } from 'node:path'
21
+ import { fileURLToPath } from 'node:url'
22
+
23
+ const LEVELS = ['off', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']
24
+ /** Fields a catalog entry may inherit, and the layer each value came from. */
25
+ const INHERITED_FIELDS = ['contextWindow', 'maxTokens', 'cost', 'input', 'reasoning', 'thinkingLevelMap', 'api']
26
+ const INSTALLED = 'installed'
27
+ const METADATA = 'metadata'
28
+ const ABSENT = 'none'
29
+
30
+ /** The levels pi-ai offers: a null mapping removes one, and xhigh/max need one. */
31
+ export function supportedLevels(levelMap) {
32
+ return LEVELS.filter((level) => {
33
+ const mapped = levelMap?.[level]
34
+ if (mapped === null) return false
35
+ if (level === 'xhigh' || level === 'max') return mapped !== undefined
36
+ return true
37
+ })
38
+ }
39
+
40
+ function parseArgs(argv) {
41
+ const options = { home: process.env.DSH_HOME ?? join(homedir(), '.dsh'), profile: 'tui', json: false }
42
+ for (let index = 0; index < argv.length; index += 1) {
43
+ const flag = argv[index]
44
+ if (flag === '--json') options.json = true
45
+ else if (flag === '--home') options.home = argv[++index]
46
+ else if (flag === '--profile') options.profile = argv[++index]
47
+ else throw new Error('unknown argument ' + flag)
48
+ }
49
+ return options
50
+ }
51
+
52
+ /** The pi-ai data directory of the harness install the launcher belongs to. */
53
+ function dataDirectory() {
54
+ try {
55
+ const bin = execFileSync('sh', ['-c', 'command -v dsh'], { encoding: 'utf8' }).trim()
56
+ const root = join(realpathSync(bin), '..', '..')
57
+ return join(root, 'node_modules', '@earendil-works', 'pi-ai', 'dist', 'providers', 'data')
58
+ } catch {
59
+ // No harness install is a supported reading: declared entries still resolve.
60
+ return undefined
61
+ }
62
+ }
63
+
64
+ /** Every JSON patch entry that configures the owned catalog, last one winning. */
65
+ export function catalogConfig(patch) {
66
+ const lines = readFileSync(patch, 'utf8').split('\n')
67
+ let catalog
68
+ for (const line of lines) {
69
+ if (!line.startsWith('- {') || !line.includes('"catalog"')) continue
70
+ const entry = JSON.parse(line.slice(2))
71
+ if (entry.config?.catalog !== undefined) catalog = entry.config.catalog
72
+ }
73
+ return catalog
74
+ }
75
+
76
+ export function providerData(directory, source) {
77
+ if (directory === undefined) return undefined
78
+ try {
79
+ return JSON.parse(readFileSync(join(directory, source + '.json'), 'utf8'))
80
+ } catch {
81
+ return undefined
82
+ }
83
+ }
84
+
85
+ /** Look one id up across a provider's api maps; an api in the spec disambiguates. */
86
+ function lookup(data, id, api) {
87
+ if (data === undefined) return undefined
88
+ for (const [key, models] of Object.entries(data)) {
89
+ if ((api === undefined || key === api) && models[id] !== undefined) return { ...models[id], api: models[id].api ?? key }
90
+ }
91
+ return undefined
92
+ }
93
+
94
+ /** Resolve one catalog entry against the installed data it may inherit from. */
95
+ export function resolve(spec, data) {
96
+ const metadata = spec.metadata ?? {}
97
+ const declared = Object.keys(metadata).length > 0
98
+ const cloneOf = spec.template ?? spec.id
99
+ const base = lookup(data, cloneOf, metadata.api)
100
+ if (base === undefined && !declared) {
101
+ return { error: 'no source entry for ' + cloneOf + '; declare a template or metadata' }
102
+ }
103
+ const merged = { ...base, ...metadata, id: spec.id, name: spec.name ?? base?.name ?? spec.id }
104
+ merged.maxTokens = metadata.maxTokens ?? spec.defaultMaxTokens ?? base?.maxTokens
105
+ const origins = {}
106
+ for (const field of INHERITED_FIELDS) {
107
+ const declaredValue = field === 'maxTokens' ? metadata.maxTokens ?? spec.defaultMaxTokens : metadata[field]
108
+ if (declaredValue !== undefined) origins[field] = METADATA
109
+ else if (base === undefined) origins[field] = ABSENT
110
+ else origins[field] = spec.template === undefined ? INSTALLED : 'template:' + spec.template
111
+ }
112
+ return { model: merged, origins, levels: supportedLevels(merged.thinkingLevelMap) }
113
+ }
114
+
115
+ function rowsOf(catalog, directory) {
116
+ const rows = []
117
+ for (const provider of catalog.providers ?? []) {
118
+ const data = providerData(directory, provider.source)
119
+ for (const spec of provider.models ?? []) {
120
+ const result = resolve(spec, data)
121
+ const route = provider.id + '/' + spec.id
122
+ if (result.error !== undefined) {
123
+ rows.push({ route, error: result.error })
124
+ continue
125
+ }
126
+ const cost = result.model.cost ?? {}
127
+ rows.push({
128
+ route,
129
+ name: result.model.name,
130
+ api: result.model.api,
131
+ contextWindow: result.model.contextWindow,
132
+ maxTokens: result.model.maxTokens,
133
+ cost: '$' + [cost.input, cost.output, cost.cacheRead, cost.cacheWrite].join('/') + ' per 1M',
134
+ levels: result.levels.join(','),
135
+ origins: INHERITED_FIELDS
136
+ .map(field => [field, result.origins[field]])
137
+ .filter(([, origin]) => origin !== INSTALLED)
138
+ .map(([field, origin]) => field + '=' + origin)
139
+ .join(' ') || 'all installed',
140
+ })
141
+ }
142
+ }
143
+ return rows
144
+ }
145
+
146
+ function main() {
147
+ const options = parseArgs(process.argv.slice(2))
148
+ const patch = join(options.home, 'profiles', options.profile, 'cordis.patch.yml')
149
+ const catalog = catalogConfig(patch)
150
+ if (catalog === undefined) {
151
+ process.stderr.write('no owned catalog in ' + patch + '\n'
152
+ + 'this profile wires models another way; see references/model-wiring.md\n')
153
+ process.exit(1)
154
+ }
155
+ const directory = dataDirectory()
156
+ if (directory === undefined) {
157
+ process.stderr.write('no harness install found; entries that inherit from it cannot be checked\n')
158
+ }
159
+ const rows = rowsOf(catalog, directory)
160
+ const unresolved = rows.filter(row => row.error !== undefined).length
161
+ if (options.json) {
162
+ process.stdout.write(JSON.stringify({ default: catalog.default ?? null, rows }, null, 2) + '\n')
163
+ } else {
164
+ for (const row of rows) {
165
+ if (row.error !== undefined) {
166
+ process.stdout.write(row.route + '\n UNRESOLVED: ' + row.error + '\n')
167
+ continue
168
+ }
169
+ process.stdout.write(row.route + '\n ' + row.name + ' | ' + row.api + ' | ctx ' + row.contextWindow
170
+ + ' | max ' + row.maxTokens + ' | ' + row.cost + ' | efforts ' + row.levels + '\n inherited: ' + row.origins + '\n')
171
+ }
172
+ process.stdout.write('default: ' + JSON.stringify(catalog.default ?? null) + '\n')
173
+ }
174
+ process.exit(unresolved === 0 ? 0 : 1)
175
+ }
176
+
177
+ // Importing the resolver must not run the CLI: the spec reads it that way.
178
+ if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) main()
package/README.md CHANGED
@@ -6,7 +6,7 @@ Status: **v1 feature-complete; published on npm as `@sagmans/dsh-tui`.** The sur
6
6
 
7
7
  ## Install
8
8
 
9
- A profile keeps this plugin as one bundle layer. Install it from a checkout of this repository, or from the registry (`0.1.0` or later). Both paths need Node.js >= 22.19 and `pnpm` on `PATH`. Both need a real terminal: stdin and stdout must be TTYs.
9
+ A profile keeps this plugin as one bundle layer. Install it from a checkout of this repository, or from the registry (`0.1.0` or later). Both paths need Node.js >= 22.19 and `pnpm` on `PATH`. Interactive sessions need a real terminal: stdin and stdout must be TTYs.
10
10
 
11
11
  ### From a plugin checkout
12
12
 
@@ -30,6 +30,33 @@ dsh --profile tui
30
30
 
31
31
  Releases are published, so this path works today. A checkout stays the path for unreleased work.
32
32
 
33
+ ### Install the dogfood skill (optional)
34
+
35
+ Run this explicit command after you add the plugin:
36
+
37
+ ```sh
38
+ dsh --profile tui install-skills
39
+ ```
40
+
41
+ The command copies `dsh-tui-dogfood` to
42
+ `~/.agents/skills/dsh-tui-dogfood/`. Agents can then load it from any dsh
43
+ plugin repository. The skill uses a cloned dsh home, so tests do not change
44
+ your real profile. A first install needs no TTY. `npm install` does not copy
45
+ skills into your home.
46
+
47
+ If the skill exists, the command asks `Update existing skill? [y/N]` in a
48
+ terminal. Only `y` or `yes` replaces it. Enter or `n` keeps the existing
49
+ copy. To update without a prompt, including in non-interactive runs, use:
50
+
51
+ ```sh
52
+ dsh --profile tui install-skills --update
53
+ ```
54
+
55
+ Updating removes any local changes inside the previous skill directory.
56
+ The command stages the new copy before replacement and restores the old
57
+ copy if replacement fails. If cleanup fails after replacement, the new copy
58
+ stays installed and the command reports the old backup path.
59
+
33
60
  ### Confirm the plugin mounted
34
61
 
35
62
  The profile records its layers in `$DSH_HOME/profiles/tui/package.json` (`~/.dsh` by default). `@sagmans/dsh-tui` must appear in `dsh.profile.bundles`:
@@ -117,8 +144,18 @@ dsh --profile tui --preset minimal # start in a shipped mode other than
117
144
  dsh --profile tui --model deepseek-chat
118
145
  dsh --profile tui --no-color
119
146
  dsh --profile tui --no-bell # do not ring when a long turn finishes
147
+ dsh --profile tui list-models # print every provider/model the picker can reach
120
148
  ```
121
149
 
150
+ `list-models` prints one line per route the `/model` picker can reach —
151
+ `provider/model`, a tab, then the model's display name — in picker order:
152
+ providers as the llm service registered them, then each provider's own model
153
+ order. It writes plain stdout, so it works piped or redirected, never opens the
154
+ alternate screen, and exits 0 after printing at least one route. When the
155
+ profile has no llm service, when the provider listing cannot be read, or when
156
+ nothing is configured to advertise a model, it names the reason on stderr and
157
+ exits 1.
158
+
122
159
  Every key below is a shipped default. `/keys` opens every action the surface
123
160
  and its library can perform as a list you filter as you type, with the keys in
124
161
  force, and the `keys:` section moves any of them — see [Keys](#keys).
@@ -223,7 +260,7 @@ While a turn runs, a prompt submitted into the editor waits in the agent's own i
223
260
 
224
261
  Copying is read back through what the surface drew rather than through the screen, so a selection is the words alone: dragging across a message takes its box with it on screen, and the surface takes its own frame back out before the text reaches the clipboard — the sides, the padding beside them, and the rules above and below. A selection that covers a whole message, two of them, or a part of one is read the same way, and a row the transcript did not draw — the editor's own bar, a picker's card — is copied exactly as it read, and a selection that was nothing but frame is handed back as the reader made it, because a copy is never emptied. The frame still comes back in a copy taken with the terminal's own selection — Shift held while dragging, or a terminal that keeps selection to itself — which is the one path no program can filter.
225
262
 
226
- Every submitted line is also kept in a global prompt history at
263
+ When prompt history is enabled, each submitted line is kept in a global history at
227
264
  `$DSH_HOME/prompt-history.json`. Typing the start of a prompt that was sent
228
265
  before draws the rest of the newest match after the cursor in a faint shade.
229
266
  `Ctrl+E` takes the whole suggestion and the word-movement key takes the next
@@ -328,8 +365,8 @@ must not also be a way to lose the work.
328
365
 
329
366
  Every styled element is a named token with a shipped default, and every key is
330
367
  an action with one, so the surface can be restyled and rebound without touching
331
- code. Preferences live in the same user-settings document as every other
332
- (`$DSH_HOME/settings.yaml`), under a `dsh-tui:` section:
368
+ code. Released hosts with section APIs store preferences in
369
+ `$DSH_HOME/settings.yaml`, under a `dsh-tui:` section:
333
370
 
334
371
  ```yaml
335
372
  dsh-tui:
@@ -364,6 +401,61 @@ dsh-tui:
364
401
  hidden: true # the element renders nothing at all
365
402
  ```
366
403
 
404
+ Config-backed source hosts store the same preference fields in the TUI entry's
405
+ `config` in the active profile patch. Use the actual entry ID, normally `tui`,
406
+ not the legacy `dsh-tui` namespace. For example:
407
+
408
+ ```yaml
409
+ - id: tui
410
+ config:
411
+ theme: violet-orbit
412
+ history:
413
+ enabled: false
414
+ ghost: false
415
+ ```
416
+
417
+ The plugin exports a Config schema for all preferences shown above, including
418
+ `prefix`, `prefixWindow`, and `keys`. Launch fields such as `sessionId`,
419
+ `model`, and `provider` remain separate from live preference edits.
420
+ The settings service owns persistence. TUI does not create another preference store.
421
+ It uses released `installSection` or `register` APIs, or Config-backed
422
+ `describe` and revision-checked `update` APIs. Unsupported or read-only writes
423
+ show a notice instead of reporting success. A rejected theme selection restores
424
+ the applied theme, and `/theme tokens` reports that same appearance.
425
+
426
+ On Config-backed hosts, absent `history.enabled` and `history.ghost` stay off.
427
+ Set each switch to `true` explicitly to enable it. This prevents recording while
428
+ the host's asynchronous legacy import is pending or has failed. Released section
429
+ hosts retain their existing defaults. If the host reports unreadable preferences,
430
+ history stays off. Readable `false` switches survive errors in other fields.
431
+ Malformed updates retain the last valid appearance and do not overwrite their source.
432
+
433
+ When public descriptors expose raw user layers, history checks those layers at use
434
+ time. A readable opt-out survives rejected siblings even without a change event.
435
+ Another rejected opt-in cannot clear this protection; a valid committed update can.
436
+ A released-provider limit remains: after an absent user section, rejected scalar
437
+ sections can produce identical public descriptors. TUI cannot detect that transition
438
+ without host validity metadata, so normal released defaults remain active.
439
+
440
+ The inspected source host has no legacy import alias from `dsh-tui` to `tui`.
441
+ Exporting Config does not resolve that namespace mismatch. The host can rename
442
+ `settings.yaml` to `settings.yaml.imported` before import completes.
443
+ TUI does not retry or restore that file automatically. Preserve current privacy
444
+ opt-outs and copy only the intended legacy fields into the actual TUI entry's
445
+ `config`. Keep the original file for rollback.
446
+
447
+ Live Config edits require the loaded schema runtime to create native volatile
448
+ references. The released schemastery 3.18.2 implementation used by this checkout's
449
+ unit tests lacks that API. A source host can resolve a different implementation
450
+ with the same version label. TUI checks the loaded implementation and actual
451
+ references, not the package version or CLI identity.
452
+
453
+ If the loaded schema runtime lacks native support, TUI exports ordinary fields
454
+ rather than unsupported live metadata. TUI then rejects Config-backed writes,
455
+ and the host cannot import preferences through its volatile-field settings API.
456
+ Reading row preferences still works, and ordinary profile changes use the host's
457
+ reload lifecycle. Native schema-backed hosts can apply live preference updates.
458
+
367
459
  Every field is optional, so a section that changes one shade is enough. The
368
460
  document is hot-reloaded: an edit restyles a running session and re-arms the
369
461
  keymap on the next press, and `/theme` shows each element's effective value and
@@ -778,9 +870,11 @@ The workflow stores no npm token: the registry trusts `release.yml` on the `npm-
778
870
 
779
871
  - Two different things are called a preset. The agent mode (`--preset`, `/preset`) is fixed once a session has produced a turn; the permission preset (`/permission <preset>`, named in the status line) can change at any time.
780
872
  - `/model` changes the route and reasoning effort for the running session only. Catalog membership is advisory — an adapter may accept an id it does not advertise, while an explicit effort is checked against the route's own levels before it is applied. The picker offers the routes this deployment configured, not the ones it can prove credentialed: a provider whose key or sign-in is still missing appears like any other, and its first request names the missing credential.
873
+ - Model discovery failures show a safe notice. Healthy provider catalogs remain selectable. Check provider configuration and credentials before retrying `/model`.
781
874
  - Scrolling is the mouse wheel, or the terminal's own scrollback keys where it offers them.
782
875
  - A turn that ran longer than ten seconds rings the terminal bell when it ends, because the reader may have walked away; `--no-bell` turns that off.
783
876
  - The dock shows the goal, plan mode, the todo items still to do, and any background job or delegation still running; a settled item leaves rather than turns into a completed row, and the list clears when the next turn opens so a fresh task never inherits the previous one's checklist. The transcript marks where older history was compacted away. `/plan` toggles plan mode; `/plan <message>` also steers that message, which is the base command's own behaviour.
877
+ - The dock shows three running subagents by default. When more than three run, click the heading, overflow row, or blank cells in that section to show all. Click again to show three. Click a child’s text to view its transcript. Press `ctrl+b` to return.
784
878
  - Background jobs and subagent runs are live process state, not durable events: they disappear when the run ends, and a resumed session starts with an empty board and roster.
785
879
  - A card reads its tool's own render intent through the agent whose session is on screen, so a stored session with no live agent — one this process is not running, or a child that has already finished — folds to the generic card instead of the tool's own.
786
880
  - Reading a child's conversation does not move the terminal: commands, approvals, and the status line stay with the session you launched, and the transcript is the only thing that switches. The status line carries the way back, read from the map in force, so a remap shows up without reopening the view.
@@ -804,7 +898,7 @@ discipline as this one.
804
898
  | Plugin | What it adds |
805
899
  |---|---|
806
900
  | [`@sagmans/dsh-auto-compact`](https://github.com/sagmans/dsh-auto-compact) | An absolute token trigger for automatic compaction: the conversation condenses at `min(thresholdTokens, contextWindow × thresholdRatio)` instead of the window ratio alone, so a large-window model pays a fixed price, with per-route overrides. Its patch swaps the shipped `compaction-basic` backend — the same row this bundle already takes out of the global composition — so the profile still keeps exactly one compaction service, and this surface needs no setting for it. |
807
- | [`@sagmans/dsh-provider-extra`](https://github.com/sagmans/dsh-provider-extra) | Extra provider routes: OpenCode Go, which sends the live conversation id in `x-opencode-session` for routing and prompt caching, and OpenAI Codex over a ChatGPT subscription's OAuth flow with the harness credential store. Its routes join the `/model` picker like every configured provider. |
901
+ | [`@sagmans/dsh-provider-extra`](https://github.com/sagmans/dsh-provider-extra) | Extra provider routes: OpenCode Go, which sends the live conversation id in `x-opencode-session` for routing and prompt caching, and OpenAI Codex over a ChatGPT subscription's OAuth flow with the harness credential store. Its routes join the `/model` picker like every configured provider. TUI also works without this package, using the core `llm` and `agentDefaultModel` services. |
808
902
 
809
903
  ```sh
810
904
  dsh plugin --profile tui add @sagmans/dsh-auto-compact
@@ -1 +1 @@
1
- {"version":3,"file":"present.d.ts","sourceRoot":"","sources":["../../src/agent/present.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,EAIL,KAAK,aAAa,EAEnB,MAAM,aAAa,CAAA;AA4BpB;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,GAAG,SAAS,GAAG,aAAa,CAqChG"}
1
+ {"version":3,"file":"present.d.ts","sourceRoot":"","sources":["../../src/agent/present.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAEnD,OAAO,EAAgB,KAAK,aAAa,EAAwB,MAAM,aAAa,CAAA;AA4BpF;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,GAAG,SAAS,GAAG,aAAa,CAqChG"}
@@ -1,4 +1,5 @@
1
- import { cardOfCall, cardOfResult, contentLines, } from "../cards.js";
1
+ import { cardOfCall, cardOfResult } from "../cards/presenter.js";
2
+ import { contentLines } from "../cards.js";
2
3
  function parseArguments(argumentsJson) {
3
4
  try {
4
5
  return JSON.parse(argumentsJson);
@@ -1 +1 @@
1
- {"version":3,"file":"present.js","sourceRoot":"","sources":["../../src/agent/present.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,UAAU,EACV,YAAY,EACZ,YAAY,GAGb,MAAM,aAAa,CAAA;AAEpB,SAAS,cAAc,CAAC,aAAqB;IAC3C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAY,CAAA;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,iBAAiB,CAAC,OAAgB;IACzC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAA;IAC3C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;YAAE,SAAQ;QACzD,MAAM,MAAM,GAAG,KAAgC,CAAA;QAC/C,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;YAAE,OAAO,MAAM,CAAC,OAAO,CAAA;IAC3F,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY,EAAE,KAA+B;IAC/E,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,EAAE;QAClC,IAAI,CAAC;YACH,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAA;QAClB,CAAC;IACH,CAAC,CAAA;IACD,OAAO;QACL,IAAI,CAAC,IAAI,EAAE,aAAa;YACtB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC,cAAc,CAAC,aAAa,CAAU,CAAC,CAAA;gBACpF,wEAAwE;gBACxE,wEAAwE;gBACxE,sEAAsE;gBACtE,yCAAyC;gBACzC,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;YAChE,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,SAAS,CAAA;YAClB,CAAC;QACH,CAAC;QACD,MAAM,CAAC,IAAI,EAAE,KAAsB;YACjC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,CAC5C,cAAc,CAAC,KAAK,CAAC,aAAa,CAAU,EAC5C,EAAE,OAAO,EAAE,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAW,CACjG,CAAA;gBACD,OAAO,YAAY,CAAC,IAAI,EAAE;oBACxB,IAAI;oBACJ,MAAM,EAAE,KAAK,CAAC,OAAO;oBACrB,YAAY,EAAE,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC;iBAC1C,CAAC,CAAA;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,SAAS,CAAA;YAClB,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"present.js","sourceRoot":"","sources":["../../src/agent/present.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAE,YAAY,EAA4C,MAAM,aAAa,CAAA;AAEpF,SAAS,cAAc,CAAC,aAAqB;IAC3C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAY,CAAA;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,iBAAiB,CAAC,OAAgB;IACzC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAA;IAC3C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;YAAE,SAAQ;QACzD,MAAM,MAAM,GAAG,KAAgC,CAAA;QAC/C,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;YAAE,OAAO,MAAM,CAAC,OAAO,CAAA;IAC3F,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY,EAAE,KAA+B;IAC/E,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,EAAE;QAClC,IAAI,CAAC;YACH,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAA;QAClB,CAAC;IACH,CAAC,CAAA;IACD,OAAO;QACL,IAAI,CAAC,IAAI,EAAE,aAAa;YACtB,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,CAAC,cAAc,CAAC,aAAa,CAAU,CAAC,CAAA;gBACpF,wEAAwE;gBACxE,wEAAwE;gBACxE,sEAAsE;gBACtE,yCAAyC;gBACzC,OAAO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;YAChE,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,SAAS,CAAA;YAClB,CAAC;QACH,CAAC;QACD,MAAM,CAAC,IAAI,EAAE,KAAsB;YACjC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,CAC5C,cAAc,CAAC,KAAK,CAAC,aAAa,CAAU,EAC5C,EAAE,OAAO,EAAE,iBAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAW,CACjG,CAAA;gBACD,OAAO,YAAY,CAAC,IAAI,EAAE;oBACxB,IAAI;oBACJ,MAAM,EAAE,KAAK,CAAC,OAAO;oBACrB,YAAY,EAAE,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC;iBAC1C,CAAC,CAAA;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,SAAS,CAAA;YAClB,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,68 @@
1
+ import { type ToolSubCall, type ToolSubCallRows, type ToolCard } from '../cards.ts';
2
+ /**
3
+ * Nested calls retained on one PTC card.
4
+ *
5
+ * A program can dispatch in a loop, and one line each is small but not free;
6
+ * the cap keeps a card bounded while {@link ToolCard.subCallsTotal} still
7
+ * reports everything the program ran.
8
+ */
9
+ export declare const SUBCALL_MAX = 100;
10
+ /**
11
+ * What the fold learned about one dispatched call, in the order it learned it.
12
+ *
13
+ * A call is logged twice — once when the program asks for it, once with what it
14
+ * answered — and each moment supplies part of the row: the call view names it,
15
+ * the result view says how it ended and what it produced.
16
+ */
17
+ export interface SubCallFacts {
18
+ /** The card the call declared, or nothing when no presenter answered for it. */
19
+ readonly view: ToolCard | undefined;
20
+ /** The rows the row opens to, once the call reported them. */
21
+ readonly output: ToolSubCallRows | undefined;
22
+ /** How the call finished, as the tool that ran it reported. */
23
+ readonly status: string | undefined;
24
+ /** Whether the call is logged and still unanswered. */
25
+ readonly running: boolean;
26
+ /** Whether the call is one the log marked as an error. */
27
+ readonly failed: boolean;
28
+ }
29
+ /**
30
+ * The one-line row for one nested call, as its start or its settle leaves it.
31
+ *
32
+ * A tool that declares a call view is drawn exactly as its own header would be;
33
+ * only a tool with no view at all falls back to its registry name and raw call,
34
+ * because the surface cannot name a salient argument for a schema it never saw.
35
+ * A settle logs the same call again, and the presenter that answered the first
36
+ * time is asked again for the view it would draw — so the row's identity comes
37
+ * from the row already on screen wherever there is one, and only a call that was
38
+ * dropped earlier is named from the log. The row also keeps how the call ended,
39
+ * because a program's calls are told apart by their outcomes rather than by
40
+ * anything the program itself says about them.
41
+ */
42
+ export declare function subCallRow(id: string, name: string, argumentsJson: string, facts: SubCallFacts, existing?: ToolSubCall): ToolSubCall;
43
+ /**
44
+ * The rows one dispatched call keeps, from the card that drew them.
45
+ *
46
+ * The total is clamped to what was kept: a card built from a call or result
47
+ * view reports no total of its own, because it is not a stream retention had to
48
+ * cut, and a count below the rows on screen would hint at rows already shown.
49
+ */
50
+ export declare function subCallRows(card: ToolCard | undefined): ToolSubCallRows | undefined;
51
+ /**
52
+ * The header fields a rebuilt card must keep.
53
+ *
54
+ * Replacing a card's rows says nothing about what the call was made with, what
55
+ * it measured, how it ended, or which calls it dispatched, so those fields
56
+ * travel with the rebuild: dropping the argument is how a shell card loses its
57
+ * command the moment a presenter declines the result.
58
+ */
59
+ export declare function carriedFields(card: ToolCard): Pick<ToolCard, 'argument' | 'stats' | 'status' | 'subCalls' | 'subCallsTotal'>;
60
+ /**
61
+ * Collapse one call and its result into the single row the reader sees.
62
+ *
63
+ * The pending row already told the reader what the tool is doing, so the
64
+ * settled row keeps that header and swaps in the outcome; a result that
65
+ * presents nothing keeps the call's detail rather than blanking the row.
66
+ */
67
+ export declare function mergeCards(call: ToolCard | undefined, result: ToolCard | undefined): ToolCard;
68
+ //# sourceMappingURL=composition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../../src/cards/composition.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,KAAK,QAAQ,EAAY,MAAM,aAAa,CAAA;AAG7F;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,MAAM,CAAA;AAE9B;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAA;IACnC,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAAA;IAC5C,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CACzB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,WAAW,GAAG,WAAW,CAuBpI;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,eAAe,GAAG,SAAS,CAGnF;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,eAAe,CAAC,CAQ5H;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,EAAE,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CA8B7F"}