@hraness/dawg 0.6.1 → 0.8.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 (277) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/DAWG.md +454 -140
  3. package/README.md +30 -26
  4. package/core/autotune.ts +1119 -0
  5. package/core/chords.ts +271 -23
  6. package/core/clips.ts +499 -0
  7. package/core/diff.ts +184 -104
  8. package/core/expression.ts +15 -0
  9. package/core/fx.ts +99 -29
  10. package/core/ids.ts +52 -0
  11. package/core/instruments.ts +19 -0
  12. package/core/keys.ts +3 -3
  13. package/core/loop.ts +8 -0
  14. package/core/lyrics.ts +297 -0
  15. package/core/master.ts +3 -3
  16. package/core/range.ts +581 -0
  17. package/core/resonators.ts +16 -2
  18. package/core/routing.ts +165 -0
  19. package/core/score.ts +955 -14
  20. package/core/sdk/eval-child.ts +7 -2
  21. package/core/sdk/eval.ts +35 -6
  22. package/core/sdk/print.ts +287 -5
  23. package/core/sdk/sync-lyrics.ts +49 -0
  24. package/core/sdk/v1.ts +2132 -46
  25. package/core/sections.ts +480 -35
  26. package/core/sing.ts +815 -0
  27. package/core/style-provenance.ts +80 -0
  28. package/core/styles/africa-mena-southasia.ts +2893 -0
  29. package/core/styles/americas.ts +3810 -0
  30. package/core/styles/art.ts +4993 -0
  31. package/core/styles/base.ts +123 -0
  32. package/core/styles/cycles.ts +106 -0
  33. package/core/styles/electronic.ts +2723 -0
  34. package/core/styles/europe-asia-pacific.ts +2838 -0
  35. package/core/styles/excerpt.ts +29 -0
  36. package/core/styles/gamelan.ts +283 -0
  37. package/core/styles/generate.ts +2199 -0
  38. package/core/styles/index.ts +515 -0
  39. package/core/styles/parts.ts +106 -0
  40. package/core/styles/pop.ts +3189 -0
  41. package/core/styles/rock.ts +2993 -0
  42. package/core/styles/roots.ts +4175 -0
  43. package/core/styles/schema.ts +429 -0
  44. package/core/styles/taxonomy.ts +940 -0
  45. package/core/styles/validate.ts +528 -0
  46. package/core/tempo.ts +32 -2
  47. package/core/tuning.ts +19 -3
  48. package/core/vocoder.ts +524 -0
  49. package/guides/agent.md +29 -0
  50. package/guides/arrange.md +31 -0
  51. package/guides/audio.md +31 -0
  52. package/guides/audition.md +20 -14
  53. package/guides/automation.md +12 -7
  54. package/guides/chords.md +15 -15
  55. package/guides/effects.md +17 -16
  56. package/guides/faders.md +20 -16
  57. package/guides/files.md +13 -9
  58. package/guides/getting-started.md +15 -11
  59. package/guides/keys.md +19 -15
  60. package/guides/media.md +17 -12
  61. package/guides/mix.md +15 -7
  62. package/guides/music.md +23 -8
  63. package/guides/notes.md +15 -9
  64. package/guides/panes.md +32 -0
  65. package/guides/performance.md +15 -13
  66. package/guides/play.md +21 -13
  67. package/guides/project.md +26 -8
  68. package/guides/providers.md +20 -14
  69. package/guides/resample.md +15 -9
  70. package/guides/rhythm.md +18 -13
  71. package/guides/sessions.md +17 -7
  72. package/guides/show-me.md +31 -0
  73. package/guides/sound.md +25 -9
  74. package/guides/sounds.md +15 -13
  75. package/guides/styles.md +31 -0
  76. package/guides/tape.md +32 -0
  77. package/guides/tempo.md +15 -10
  78. package/guides/tracks.md +15 -10
  79. package/guides/tuning.md +32 -0
  80. package/guides/voice.md +31 -0
  81. package/guides/web-search.md +18 -8
  82. package/native/prebuilt/darwin-arm64/libdawg_sink.dylib +0 -0
  83. package/native/prebuilt/darwin-x64/libdawg_sink.dylib +0 -0
  84. package/native/prebuilt/linux-arm64/libdawg_sink.so +0 -0
  85. package/native/prebuilt/linux-x64/libdawg_sink.so +0 -0
  86. package/native/prebuilt/manifest.json +21 -0
  87. package/package.json +6 -2
  88. package/src/agent/agent.ts +130 -18
  89. package/src/agent/calibration-tools.ts +53 -0
  90. package/src/agent/clip-tools.ts +453 -0
  91. package/src/agent/command-agent.ts +378 -0
  92. package/src/agent/drum-tools.ts +2 -2
  93. package/src/agent/expression-tools.ts +1 -1
  94. package/src/agent/gateway.ts +246 -60
  95. package/src/agent/models.ts +53 -12
  96. package/src/agent/ops.ts +27 -1
  97. package/src/agent/pack-tools.ts +1 -1
  98. package/src/agent/planner.ts +25 -0
  99. package/src/agent/portable-schema.ts +80 -0
  100. package/src/agent/preview-tool.ts +4 -1
  101. package/src/agent/provider.ts +22 -8
  102. package/src/agent/range-tools.ts +216 -0
  103. package/src/agent/rhythm-tools.ts +1 -1
  104. package/src/agent/section-tools.ts +1 -1
  105. package/src/agent/show-me.ts +497 -0
  106. package/src/agent/steer.ts +15 -0
  107. package/src/agent/style-tools.ts +217 -0
  108. package/src/agent/tool-error.ts +12 -0
  109. package/src/agent/tools.ts +110 -23
  110. package/src/agent/usage.ts +2 -2
  111. package/src/agent/voice-tools.ts +925 -0
  112. package/src/agent/xcb-agent.ts +13 -10
  113. package/src/argv.ts +38 -0
  114. package/src/audio/analysis.ts +253 -0
  115. package/src/audio/arrange.ts +49 -5
  116. package/src/audio/audio-command.ts +218 -0
  117. package/src/audio/autotune-engine.ts +101 -0
  118. package/src/audio/autotune.ts +640 -0
  119. package/src/audio/clips.ts +240 -0
  120. package/src/audio/devices.ts +264 -0
  121. package/src/audio/doctor.ts +157 -0
  122. package/src/audio/dsp/bandbank.ts +138 -0
  123. package/src/audio/dsp/envelope.ts +10 -0
  124. package/src/audio/dsp/follow.ts +120 -0
  125. package/src/audio/dsp/formant.ts +427 -0
  126. package/src/audio/dsp/glottal.ts +243 -0
  127. package/src/audio/dsp/interp.ts +7 -2
  128. package/src/audio/dsp/lpc.ts +50 -0
  129. package/src/audio/dsp/periodicity.ts +59 -0
  130. package/src/audio/dsp/pitch.ts +995 -0
  131. package/src/audio/dsp/psola.ts +199 -0
  132. package/src/audio/effects/chain.ts +3 -1
  133. package/src/audio/effects/common.ts +43 -0
  134. package/src/audio/effects/convolution.ts +7 -4
  135. package/src/audio/effects/filter.ts +48 -69
  136. package/src/audio/effects/formant.ts +263 -0
  137. package/src/audio/engine.ts +360 -40
  138. package/src/audio/fit.ts +35 -3
  139. package/src/audio/instrument-check.ts +59 -43
  140. package/src/audio/instruments.ts +4 -0
  141. package/src/audio/keys/calibration.ts +56 -0
  142. package/src/audio/keys/electric.ts +8 -1
  143. package/src/audio/keys/engine.ts +13 -1
  144. package/src/audio/keys/piano.ts +22 -2
  145. package/src/audio/kits.ts +135 -6
  146. package/src/audio/live.ts +114 -20
  147. package/src/audio/native.ts +615 -0
  148. package/src/audio/preview.ts +30 -2
  149. package/src/audio/render-worker.ts +2 -0
  150. package/src/audio/renderer.ts +2 -0
  151. package/src/audio/resample.ts +2 -1
  152. package/src/audio/sampler.ts +85 -4
  153. package/src/audio/samples.ts +20 -2
  154. package/src/audio/sing/analysis.ts +193 -0
  155. package/src/audio/sing/engine.ts +949 -0
  156. package/src/audio/strings/bow.ts +48 -5
  157. package/src/audio/strings/engine.ts +8 -2
  158. package/src/audio/synth/oscillators.ts +31 -21
  159. package/src/audio/synth/voice.ts +34 -1
  160. package/src/audio/vocoder/bank.ts +314 -0
  161. package/src/audio/vocoder/carrier.ts +165 -0
  162. package/src/audio/vocoder/control.ts +68 -0
  163. package/src/audio/vocoder/detect.ts +50 -0
  164. package/src/audio/vocoder/index.ts +304 -0
  165. package/src/audio/vocoder/talkbox.ts +143 -0
  166. package/src/audio/wav.ts +500 -77
  167. package/src/audio/winds/engine.ts +5 -1
  168. package/src/audio/winds/trim.ts +28 -4
  169. package/src/audio/winds/trims1.ts +297 -0
  170. package/src/audio/winds/voice.ts +15 -2
  171. package/src/auth/cli.ts +38 -36
  172. package/src/auth/credentials.ts +30 -1
  173. package/src/auth/login.ts +15 -9
  174. package/src/auth/tui.ts +19 -10
  175. package/src/commands/arrange.ts +82 -32
  176. package/src/commands/autotune.ts +421 -0
  177. package/src/commands/calibration.ts +74 -0
  178. package/src/commands/clips.ts +887 -0
  179. package/src/commands/drums.ts +3 -2
  180. package/src/commands/edit.ts +11 -4
  181. package/src/commands/expression.ts +1 -1
  182. package/src/commands/formant.ts +221 -0
  183. package/src/commands/fx.ts +101 -32
  184. package/src/commands/grammar.ts +560 -0
  185. package/src/commands/help.ts +774 -366
  186. package/src/commands/history.ts +139 -10
  187. package/src/commands/keys.ts +25 -8
  188. package/src/commands/modal.ts +1 -1
  189. package/src/commands/music.ts +1 -1
  190. package/src/commands/nearest.ts +53 -0
  191. package/src/commands/pack.ts +9 -2
  192. package/src/commands/param-range.ts +56 -0
  193. package/src/commands/parses.ts +135 -0
  194. package/src/commands/progression.ts +170 -0
  195. package/src/commands/range.ts +763 -0
  196. package/src/commands/resample.ts +13 -10
  197. package/src/commands/rhythm.ts +3 -0
  198. package/src/commands/rig.ts +3 -24
  199. package/src/commands/sample.ts +11 -1
  200. package/src/commands/sing.ts +474 -0
  201. package/src/commands/strum.ts +13 -1
  202. package/src/commands/style.ts +415 -0
  203. package/src/commands/time.ts +6 -3
  204. package/src/commands/tuning.ts +3 -3
  205. package/src/commands/vocal-pitch.ts +617 -0
  206. package/src/commands/vocal.ts +147 -0
  207. package/src/commands/vocoder.ts +627 -0
  208. package/src/commands/wind.ts +2 -2
  209. package/src/fs/durable.ts +50 -0
  210. package/src/identity/actor.ts +127 -0
  211. package/src/lang/glossary.ts +511 -0
  212. package/src/launch-args.ts +267 -0
  213. package/src/main.ts +2508 -308
  214. package/src/media/cli.ts +20 -3
  215. package/src/media/import.ts +3 -1
  216. package/src/project/check.ts +21 -2
  217. package/src/project/clip-pins.ts +72 -0
  218. package/src/project/init.ts +23 -8
  219. package/src/project/sync.ts +418 -86
  220. package/src/render.ts +20 -1
  221. package/src/session/client.ts +107 -3
  222. package/src/session/clipboard.ts +79 -0
  223. package/src/session/daemon.ts +199 -5
  224. package/src/session/live-host.ts +232 -0
  225. package/src/session/meta.ts +14 -0
  226. package/src/session/origin.ts +174 -0
  227. package/src/session/port.ts +109 -14
  228. package/src/session/presence.ts +34 -4
  229. package/src/session/protocol.ts +365 -7
  230. package/src/session/rebase.ts +63 -12
  231. package/src/session/receipt.ts +265 -0
  232. package/src/session/shared-live.ts +131 -0
  233. package/src/session/store.ts +126 -45
  234. package/src/tui/arrange-menu.ts +225 -23
  235. package/src/tui/audition.ts +1 -1
  236. package/src/tui/euclid.ts +113 -27
  237. package/src/tui/fader.ts +282 -42
  238. package/src/tui/granular-menu.ts +2 -4
  239. package/src/tui/knob-fields.ts +107 -0
  240. package/src/tui/knob-map.ts +198 -0
  241. package/src/tui/menu-clips.ts +297 -0
  242. package/src/tui/menu-time.ts +20 -13
  243. package/src/tui/menu-voice.ts +405 -0
  244. package/src/tui/menu.ts +987 -179
  245. package/src/tui/modal-menu.ts +6 -6
  246. package/src/tui/performance-menu.ts +5 -2
  247. package/src/tui/play-chords.ts +4 -2
  248. package/src/tui/play-mode.ts +15 -1
  249. package/src/tui/play-session.ts +243 -28
  250. package/src/tui/sing-menu.ts +278 -0
  251. package/src/tui/style-menu.ts +104 -0
  252. package/src/tui/tape-mode.ts +580 -0
  253. package/src/tui/tape-view.ts +263 -0
  254. package/src/tui/vocoder-menu.ts +244 -0
  255. package/src/tui/wind-menu.ts +3 -3
  256. package/src/version.ts +8 -0
  257. package/src/web/fetch.ts +115 -29
  258. package/tui/activity.ts +180 -9
  259. package/tui/app.ts +491 -84
  260. package/tui/clip-row.ts +132 -0
  261. package/tui/delight.ts +193 -0
  262. package/tui/drawer.ts +233 -27
  263. package/tui/frame-gate.ts +76 -0
  264. package/tui/grammar.ts +143 -65
  265. package/tui/guide.ts +50 -5
  266. package/tui/highway.ts +309 -30
  267. package/tui/hints.ts +197 -0
  268. package/tui/hits.ts +7 -1
  269. package/tui/input.ts +60 -9
  270. package/tui/keys.ts +1 -1
  271. package/tui/knobs.ts +268 -0
  272. package/tui/play-strip.ts +80 -14
  273. package/tui/prompt.ts +1 -1
  274. package/tui/screen.ts +151 -18
  275. package/tui/tape.ts +439 -0
  276. package/tui/text.ts +35 -2
  277. package/tui/theme.ts +46 -1
@@ -0,0 +1,378 @@
1
+ /**
2
+ * Command mode (show-me): the agent acts by writing dawg prompt commands, one
3
+ * per line, the way a human types them. Each line runs through the host's
4
+ * command path (the same parser and commit a typed Enter uses) the moment
5
+ * it is complete, while the model is still streaming the next one, so the
6
+ * score changes during the turn. The partial line is reported as it grows
7
+ * (`command-typing`) for the prompt bar's ghost text. A small JSON tool set
8
+ * stays for what commands cannot express: workspace files, the web, media
9
+ * and measuring. Measured against the JSON tool loop on the same tasks this
10
+ * sends about a ninth of the input tokens and finishes in about half the
11
+ * time (docs/show-me.md).
12
+ */
13
+ import { newId } from "../../core/ids.ts";
14
+ import { HELP_SECTIONS } from "../commands/help.ts";
15
+ import { MEDIA_TOOLS } from "../media/tools.ts";
16
+ import {
17
+ AGENT_LIMITS,
18
+ MEDIA_PROMPT,
19
+ WORKSPACE_PROMPT,
20
+ classifyAgentError,
21
+ executeCall,
22
+ hostProjectOutline,
23
+ tighten,
24
+ turnDeadline,
25
+ type AgentEvent,
26
+ type AgentTurnOptions,
27
+ type AgentTurnResult,
28
+ } from "./agent.ts";
29
+ import { compositionBrief } from "./brief.ts";
30
+ import type { ChatMessage, ChatToolCall } from "./gateway.ts";
31
+ import { CommandLines } from "./show-me.ts";
32
+ import { AGENT_TOOLS, chatTools } from "./tools.ts";
33
+ import { SseBudgetError } from "./sse.ts";
34
+
35
+ /** What a host does with one complete command line. */
36
+ export type CommandOutcome = Readonly<{
37
+ ok: boolean;
38
+ /** The receipt a human would see (`tempo · 96`) or the error. */
39
+ message: string;
40
+ baseRevision: number;
41
+ resultRevision: number;
42
+ }>;
43
+
44
+ export type CommandHost = Readonly<{
45
+ /** Whether a complete line is a song-editing command (else it is prose). */
46
+ isCommand(line: string): boolean;
47
+ /** Run a line exactly as if the user had typed it and pressed Enter. */
48
+ run(line: string): Promise<CommandOutcome>;
49
+ }>;
50
+
51
+ /** The tools a command-mode model still gets: what commands cannot say. */
52
+ export const COMMAND_MODE_TOOL_NAMES: ReadonlySet<string> = new Set([
53
+ "list_files",
54
+ "read_file",
55
+ "write_file",
56
+ "edit_file",
57
+ "web_search",
58
+ "fetch_url",
59
+ "explain",
60
+ "preview_sound",
61
+ "measure_mix",
62
+ ...MEDIA_TOOLS.map((tool) => tool.name),
63
+ ]);
64
+
65
+ /** Steps per command-mode turn: each extra one is a correction round. */
66
+ export const COMMAND_MAX_STEPS = 4;
67
+
68
+ /** Reference groups the command agent is not shown. */
69
+ const AGENT_SKIPS: ReadonlySet<string> = new Set([
70
+ "project · window",
71
+ "project · panes",
72
+ "project · audio",
73
+ "agent",
74
+ ]);
75
+
76
+ function commandReference(): string {
77
+ // Song commands only. Panes, audio devices and the window groups change the person's window or
78
+ // machine; the clipboard row is a two-step buffer the agent never needs.
79
+ return HELP_SECTIONS.filter((section) => !AGENT_SKIPS.has(section.group))
80
+ .flatMap((section) =>
81
+ section.entries
82
+ .filter(
83
+ (entry) => !/\bpaste\b/.test(`${entry.command} ${entry.summary}`),
84
+ )
85
+ .map((entry) => `${entry.command} — ${entry.summary}`),
86
+ )
87
+ .join("\n");
88
+ }
89
+
90
+ export const COMMAND_AGENT_PROMPT = [
91
+ "You are dawg, a loop composer inside a terminal music workstation, and you teach it by doing.",
92
+ "Act by writing dawg prompt commands, exactly as a human types them in the prompt bar, one per line. The user watches each command appear in their prompt bar as you write it, and each line runs the moment it ends, so they learn the commands by watching you.",
93
+ "Prefer the gestures a person would use: a parameter as one value command (fx reverb mix 0.4, volume 0.7, pan -0.3, which the user sees as a fader moving), notes one per line with add (add C4 at 0 for 0.5, played on the user's keyboard as you write them), drums with hit, pattern and euclid on a kit track, a whole groove with groove <name>.",
94
+ "To arrange, name bars: loop 5-6 loops them, copy bass 5-6 to 7 and move bass 5-6 to 9 place them in one step, clear bass 5-6 empties them. Never copy to the clipboard and paste.",
95
+ "Commands apply to the focused track. /track <name> focuses a track or creates it; instrument kit makes a drum track. Times are beats from 0: in 4/4 musicians' beats 2 and 4 are beats 1 and 3 here, and bar n starts at (n-1)×beats per bar.",
96
+ "Write commands first, with no numbering, quotes or code fences. Then write one short plain sentence (not a command) that says what changed and names the key command so the user can do it by hand next time.",
97
+ "If a command fails you get its error; correct it with another command or stop.",
98
+ "Use the tools only for what commands cannot do: project files, the web, media, previewing and measuring.",
99
+ WORKSPACE_PROMPT,
100
+ MEDIA_PROMPT,
101
+ ].join(" ");
102
+
103
+ /** The system prompt with the command reference, built once. */
104
+ let systemPrompt: string | undefined;
105
+ export function commandAgentSystemPrompt(): string {
106
+ systemPrompt ??= `${COMMAND_AGENT_PROMPT}\n\nCommands:\n${commandReference()}\n/track <name> — focus or create a track`;
107
+ return systemPrompt;
108
+ }
109
+
110
+ export type CommandAgentTurnOptions = AgentTurnOptions &
111
+ Readonly<{ commands: CommandHost }>;
112
+
113
+ /**
114
+ * One command-mode turn. Lines run in order on one chain, so a slow command
115
+ * never reorders the next, and reading the stream never waits on them.
116
+ */
117
+ export async function runCommandAgentTurn(
118
+ options: CommandAgentTurnOptions,
119
+ ): Promise<AgentTurnResult> {
120
+ const limits = {
121
+ maxSteps: Math.min(
122
+ COMMAND_MAX_STEPS,
123
+ tighten(options.budget?.maxSteps, AGENT_LIMITS.maxSteps),
124
+ ),
125
+ maxToolCalls: tighten(
126
+ options.budget?.maxToolCalls,
127
+ AGENT_LIMITS.maxToolCalls,
128
+ ),
129
+ maxResponseBytes: tighten(
130
+ options.budget?.maxResponseBytes,
131
+ AGENT_LIMITS.maxResponseBytes,
132
+ ),
133
+ timeoutMs: tighten(options.budget?.timeoutMs, AGENT_LIMITS.timeoutMs),
134
+ };
135
+ const emit = (event: AgentEvent) => {
136
+ try {
137
+ options.onEvent?.(event);
138
+ } catch {
139
+ // A rendering failure must never corrupt the turn.
140
+ }
141
+ };
142
+ const allTools = options.tools ?? AGENT_TOOLS;
143
+ const offered = chatTools(
144
+ allTools.filter((tool) => COMMAND_MODE_TOOL_NAMES.has(tool.name)),
145
+ );
146
+ const newNoteId =
147
+ options.newNoteId ??
148
+ ((trackId: string, _revision: number, _index: number) => newId(trackId));
149
+ const deadline = turnDeadline(limits.timeoutMs);
150
+ const signal = options.signal
151
+ ? AbortSignal.any([options.signal, deadline.signal])
152
+ : deadline.signal;
153
+ const currentRevision = () => options.host.snapshot().revision;
154
+ const turnStartRevision = currentRevision();
155
+
156
+ let applied = 0;
157
+ let rejected = 0;
158
+ let toolCalls = 0;
159
+ let bytesUsed = 0;
160
+ let finalText = "";
161
+ const messages: ChatMessage[] = [
162
+ { role: "user", content: options.prompt.slice(0, 8_000) },
163
+ ];
164
+ const finish = (result: AgentTurnResult): AgentTurnResult => {
165
+ deadline.clear();
166
+ emit(result);
167
+ return result;
168
+ };
169
+ const done = (reason: "stop" | "max-steps" | "max-tool-calls") =>
170
+ finish({
171
+ type: "done",
172
+ reason,
173
+ text: finalText,
174
+ applied,
175
+ rejected,
176
+ revision: currentRevision(),
177
+ });
178
+
179
+ try {
180
+ for (let step = 1; step <= limits.maxSteps; step += 1) {
181
+ if (signal.aborted) throw signal.reason;
182
+ for (const steer of options.host.takeSteering?.() ?? [])
183
+ messages.push({
184
+ role: "user",
185
+ content: `Steering from the user mid-turn: ${steer.slice(0, 2_000)}`,
186
+ });
187
+ emit({ type: "step", step });
188
+ const snapshot = options.host.snapshot();
189
+ const brief = compositionBrief({
190
+ ...snapshot,
191
+ project: await hostProjectOutline(options.host, snapshot),
192
+ });
193
+ const briefText =
194
+ snapshot.revision === turnStartRevision
195
+ ? `Composition brief (JSON): ${brief}`
196
+ : `Composition brief (JSON, current score at revision ${snapshot.revision}: it already includes every command you ran this turn; do not repeat them): ${brief}`;
197
+ const remaining = limits.maxResponseBytes - bytesUsed;
198
+ if (remaining <= 0) throw new SseBudgetError(limits.maxResponseBytes);
199
+
200
+ const lines = new CommandLines();
201
+ let text = "";
202
+ const prose: string[] = [];
203
+ const results: string[] = [];
204
+ let failed = 0;
205
+ let chain: Promise<void> = Promise.resolve();
206
+ const runLine = (line: string) => {
207
+ if (!options.commands.isCommand(line)) {
208
+ prose.push(line);
209
+ return;
210
+ }
211
+ if (toolCalls >= limits.maxToolCalls) return;
212
+ toolCalls += 1;
213
+ const callId = `cmd-${step}-${toolCalls}`;
214
+ chain = chain.then(async () => {
215
+ if (signal.aborted) return;
216
+ emit({ type: "tool-start", callId, name: "command", step });
217
+ let outcome: CommandOutcome;
218
+ try {
219
+ outcome = await options.commands.run(line);
220
+ } catch (error) {
221
+ outcome = {
222
+ ok: false,
223
+ message: error instanceof Error ? error.message : String(error),
224
+ baseRevision: currentRevision(),
225
+ resultRevision: currentRevision(),
226
+ };
227
+ }
228
+ emit({ type: "command", line, ...outcome });
229
+ if (outcome.ok) {
230
+ if (outcome.resultRevision !== outcome.baseRevision) applied += 1;
231
+ results.push(`${line} → ${outcome.message}`);
232
+ } else {
233
+ failed += 1;
234
+ rejected += 1;
235
+ results.push(`${line} → FAILED: ${outcome.message}`);
236
+ }
237
+ });
238
+ };
239
+ const pending = new Map<
240
+ number,
241
+ { id: string; name: string; arguments: string }
242
+ >();
243
+ let typing = "";
244
+ for await (const event of options.client.stream(
245
+ {
246
+ model: options.model,
247
+ ...(options.fallbackModel
248
+ ? { fallbackModelId: options.fallbackModel }
249
+ : {}),
250
+ messages: [
251
+ { role: "system", content: commandAgentSystemPrompt() },
252
+ { role: "system", content: briefText },
253
+ ...messages,
254
+ ],
255
+ ...(offered.length > 0 ? { tools: offered } : {}),
256
+ maxResponseBytes: remaining,
257
+ },
258
+ signal,
259
+ )) {
260
+ if (event.type === "usage") {
261
+ const { type: _type, ...usage } = event;
262
+ emit({ type: "usage", ...usage });
263
+ } else if (event.type === "activity") {
264
+ emit({ type: "activity", message: event.message });
265
+ } else if (event.type === "text") {
266
+ bytesUsed += event.delta.length;
267
+ if (text.length >= AGENT_LIMITS.maxTextChars) continue;
268
+ text += event.delta;
269
+ const { complete, partial } = lines.push(event.delta);
270
+ for (const line of complete) runLine(line);
271
+ if (partial !== typing) {
272
+ typing = partial;
273
+ emit({ type: "command-typing", text: partial });
274
+ }
275
+ } else if (event.type === "tool-delta") {
276
+ const call = pending.get(event.index) ?? {
277
+ id: "",
278
+ name: "",
279
+ arguments: "",
280
+ };
281
+ if (event.id) call.id = event.id;
282
+ if (event.name) call.name += event.name;
283
+ if (event.arguments) {
284
+ call.arguments += event.arguments;
285
+ bytesUsed += event.arguments.length;
286
+ if (call.arguments.length > AGENT_LIMITS.maxToolArgumentBytes)
287
+ throw new SseBudgetError(AGENT_LIMITS.maxToolArgumentBytes);
288
+ }
289
+ pending.set(event.index, call);
290
+ }
291
+ }
292
+ const last = lines.flush();
293
+ if (last) runLine(last);
294
+ if (typing) emit({ type: "command-typing", text: "" });
295
+ await chain;
296
+ if (signal.aborted) throw signal.reason;
297
+ if (prose.length > 0) {
298
+ finalText = prose.join(" ");
299
+ emit({ type: "text-delta", delta: finalText });
300
+ }
301
+
302
+ const calls = [...pending.entries()]
303
+ .sort(([left], [right]) => left - right)
304
+ .map(([index, call]) => ({
305
+ ...call,
306
+ id: call.id || `call-${step}-${index}`,
307
+ }));
308
+ if (calls.length === 0 && failed === 0) return done("stop");
309
+ if (calls.length > 0) {
310
+ const assistantCalls: ChatToolCall[] = calls.map((call) => ({
311
+ id: call.id,
312
+ type: "function",
313
+ function: { name: call.name, arguments: call.arguments || "{}" },
314
+ }));
315
+ messages.push({
316
+ role: "assistant",
317
+ content: text || null,
318
+ tool_calls: assistantCalls,
319
+ });
320
+ for (const call of calls) {
321
+ if (signal.aborted) throw signal.reason;
322
+ toolCalls += 1;
323
+ emit({ type: "tool-start", callId: call.id, name: call.name, step });
324
+ const outcome = await executeCall(call, {
325
+ tools: allTools,
326
+ host: options.host,
327
+ newNoteId,
328
+ signal,
329
+ suspendTimeout: deadline.suspend,
330
+ onProgress: (line) =>
331
+ emit({
332
+ type: "tool-progress",
333
+ callId: call.id,
334
+ name: call.name,
335
+ line,
336
+ }),
337
+ });
338
+ if (outcome.ok) {
339
+ applied += outcome.mutated ? 1 : 0;
340
+ if (outcome.explanation) finalText = outcome.explanation;
341
+ emit(outcome.event);
342
+ } else {
343
+ rejected += 1;
344
+ emit({
345
+ type: "tool-rejected",
346
+ callId: call.id,
347
+ name: call.name,
348
+ diagnostic: outcome.diagnostic,
349
+ });
350
+ }
351
+ messages.push({
352
+ role: "tool",
353
+ tool_call_id: call.id,
354
+ content: outcome.content,
355
+ });
356
+ }
357
+ } else messages.push({ role: "assistant", content: text });
358
+ if (results.length > 0)
359
+ messages.push({
360
+ role: "user",
361
+ content: `Command results:\n${results.join("\n").slice(0, 6_000)}`,
362
+ });
363
+ if (toolCalls >= limits.maxToolCalls) return done("max-tool-calls");
364
+ }
365
+ return done("max-steps");
366
+ } catch (error) {
367
+ const { code, message } = classifyAgentError(error, signal, options.signal);
368
+ return finish({
369
+ type: "error",
370
+ code,
371
+ message,
372
+ applied,
373
+ revision: currentRevision(),
374
+ });
375
+ } finally {
376
+ deadline.clear();
377
+ }
378
+ }
@@ -38,7 +38,7 @@ export const DRUM_TOOLS: readonly AgentTool[] = Object.freeze([
38
38
  {
39
39
  name: "list_drum_patterns",
40
40
  description:
41
- "List the built-in drum patterns (name · tempo range · genre tags · voices). Filter by a word such as house, trap or 2-step.",
41
+ "List the built-in grooves (name · tempo range · style tags · drums). Filter by a word such as house, trap or 2-step.",
42
42
  parameters: {
43
43
  type: "object",
44
44
  properties: { filter: { type: "string", maxLength: 40 } },
@@ -105,7 +105,7 @@ export const DRUM_TOOLS: readonly AgentTool[] = Object.freeze([
105
105
  },
106
106
  {
107
107
  name: "set_drum_kit",
108
- description: `Choose the synthesized drum kit of a drum track: ${SYNTH_KITS.map((kit) => kit.name).join(", ")}, or default. Offline and instant; sample kits (909, 808, linn, …) go through use_sound.`,
108
+ description: `Choose the synthesized kit of a drum track: ${SYNTH_KITS.map((kit) => kit.name).join(", ")}, or default. Offline and instant; sample kits (909, 808, linn, …) go through use_sound.`,
109
109
  parameters: {
110
110
  type: "object",
111
111
  properties: {
@@ -340,7 +340,7 @@ export const EXPRESSION_TOOLS: readonly AgentTool[] = Object.freeze([
340
340
  // keys-electric (0.6.1): the modelled piano's soft and middle pedals.
341
341
  name: "set_piano_pedals",
342
342
  description:
343
- 'Modelled piano soft (una corda: fewer strings, softer, darker) and sostenuto (holds only keys down when it presses) pedals. Each: [{beat, state}], "bars" (whole loop) or null. Sostenuto has no half.',
343
+ 'Modeled piano soft (una corda: fewer strings, softer, darker) and sostenuto (holds only keys down when it presses) pedals. Each: [{beat, state}], "bars" (whole loop) or null. Sostenuto has no half.',
344
344
  parameters: {
345
345
  type: "object",
346
346
  properties: {