moontide-mcp 2.1.0 → 2.4.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 (57) hide show
  1. package/README.md +54 -331
  2. package/dist/ask.js +59 -12
  3. package/dist/catalog.js +118 -9
  4. package/dist/instruments.js +83 -9
  5. package/dist/mcp.js +146 -20
  6. package/dist/midi-state.js +43 -1
  7. package/dist/protocol.js +123 -5
  8. package/dist/reference.js +12 -0
  9. package/dist/request-status.js +221 -0
  10. package/dist/session.js +203 -7
  11. package/dist/vocabulary.js +420 -0
  12. package/package.json +3 -2
  13. package/page/ask-view.js +118 -50
  14. package/page/ask.js +139 -169
  15. package/page/bar-model.js +44 -2
  16. package/page/change-timing.js +35 -0
  17. package/page/index.html +6 -2
  18. package/page/instruments-model.js +50 -3
  19. package/page/instruments-view.js +14 -2
  20. package/page/jam-analysis.js +68 -23
  21. package/page/jam-model.js +185 -29
  22. package/page/jam-resolve.js +271 -0
  23. package/page/jam-view.js +165 -10
  24. package/page/lights.js +29 -7
  25. package/page/main.js +807 -66
  26. package/page/midi-clock.js +22 -3
  27. package/page/midi-in.js +407 -0
  28. package/page/midi-out.js +557 -48
  29. package/page/midi-plan.js +42 -6
  30. package/page/midi-rig.js +286 -13
  31. package/page/midi-settings.js +507 -0
  32. package/page/mixer-view.js +7 -1
  33. package/page/note-panel.js +29 -8
  34. package/page/note-tag.js +19 -4
  35. package/page/page-rewrite.js +1 -1
  36. package/page/played-buffer.js +208 -0
  37. package/page/query-check.js +21 -2
  38. package/page/request-status-view.js +419 -0
  39. package/page/request-status.js +686 -0
  40. package/page/scratch-gesture.js +239 -0
  41. package/page/scratch-listen.js +260 -0
  42. package/page/scratch-model.js +1696 -0
  43. package/page/scratch-view.js +2176 -0
  44. package/page/song-grid-model.js +15 -0
  45. package/page/song-grid-view.js +1 -1
  46. package/page/structure.js +2 -1
  47. package/page/style.css +1243 -43
  48. package/page/tempo-glide.js +330 -0
  49. package/page/tempo-guard.js +62 -10
  50. package/page/timeline-edit-view.js +1 -1
  51. package/page/timeline.js +2 -1
  52. package/page/top-bar-view.js +18 -13
  53. package/page/views.js +20 -7
  54. package/reference/midi.md +2 -0
  55. package/reference/motif.md +83 -0
  56. package/reference/settings.md +161 -0
  57. package/reference/transitions.md +13 -1
package/dist/session.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { AskChannel, listenerScriptPath } from './ask.js';
3
+ import { NOT_OPEN_REQUEST_MESSAGE, RequestStatusBoard, cleanAnswerText } from './request-status.js';
3
4
  import { MIDI_PERMISSION_WAIT_MESSAGE, MidiChannel } from './midi-state.js';
4
5
  import { applyEdits } from './edit-code.js';
5
6
  import { CatalogSaveError, MAX_CATALOG_DRAFTS, MAX_CATALOG_KITS, MAX_CATALOG_SOUNDS, MAX_PART_SOUNDS, catalogFileExists, countText, flattenCatalogPart, parseCatalog, problemsText, saveCatalog, summarizeCatalog, } from './catalog.js';
@@ -71,6 +72,14 @@ export function cleanErrorDetail(detail) {
71
72
  }
72
73
  return codePoints.slice(0, MAX_DETAIL_CODE_POINTS).join('') + '…';
73
74
  }
75
+ /** The page's refusal text of a tempo ramp it could not make, trimmed and cut like any error detail but kept as a sentence. */
76
+ function cleanRefusal(text) {
77
+ const trimmed = text.trim();
78
+ if (trimmed === '')
79
+ return UNKNOWN_ERROR_DETAIL;
80
+ const codePoints = Array.from(trimmed);
81
+ return codePoints.length <= MAX_DETAIL_CODE_POINTS ? trimmed : codePoints.slice(0, MAX_DETAIL_CODE_POINTS).join('') + '…';
82
+ }
74
83
  /**
75
84
  * Builds the tool-response text for a failed evaluation: Strudel's message, the line when
76
85
  * known, and a truthful note about what is (or isn't) still playing.
@@ -189,6 +198,26 @@ export function takeoverCanceledLine(cycle, reason) {
189
198
  }
190
199
  return `${head} the page was closed, reloaded or replaced by another tab before then. get_code returns the code from before it.`;
191
200
  }
201
+ /**
202
+ * The note Claude gets, once, when a tempo ramp ended without Claude asking for it: a program stated another tempo, a
203
+ * program stated no tempo, a step failed, or the page that ran it was closed, reloaded or replaced by another tab.
204
+ * Stop, a finished ramp and a replaced ramp say nothing. Only the cause (a fixed word) and a number come in: no
205
+ * page text. `bpm` is the tempo that plays after the end; the wording leaves it out when the page gave none.
206
+ */
207
+ export function glideEndedLine(reason, bpm) {
208
+ const shown = bpm === undefined ? undefined : Number.isInteger(bpm) ? String(bpm) : bpm.toFixed(1);
209
+ const head = 'Note: the tempo ramp ended early:';
210
+ if (reason === 'other-tempo') {
211
+ return `${head} a program stated another tempo${shown === undefined ? '' : `, and ${shown} BPM plays now`}.`;
212
+ }
213
+ if (reason === 'no-tempo') {
214
+ return `${head} a program stated no tempo${shown === undefined ? '' : `, so the tempo stays at ${shown} BPM, where the ramp had got to`}.`;
215
+ }
216
+ if (reason === 'error') {
217
+ return `${head} a step failed in the page${shown === undefined ? '' : `, so the tempo stays at ${shown} BPM`}.`;
218
+ }
219
+ return `${head} the page was closed, reloaded or replaced by another tab, so the tempo of the code plays.`;
220
+ }
192
221
  /**
193
222
  * The note when a change that waited for its line took over and replaced code that had been edited by hand
194
223
  * in the page. Said at the takeover, never at the request: until the line the hand edit is still what plays, and a
@@ -261,6 +290,11 @@ export class Session {
261
290
  pendingChange = null;
262
291
  // The notes of waiting changes that did not take over, queued once and drained once, like historyNotes.
263
292
  takeoverNotes = [];
293
+ // The tempo ramp the page runs, as far as the server knows: set when a reply says a ramp started (or the takeover of a
294
+ // change whose ramp starts at its line), cleared by Stop, a page end and a page loss. Null when none runs.
295
+ glide = null;
296
+ // The notes of a ramp that ended early, queued once and drained once, like takeoverNotes.
297
+ glideNotes = [];
264
298
  // The notes of load failure reports a page sent, one per distinct text (at most three), drained once.
265
299
  loadFailureNotes = new Set();
266
300
  // BPM: the tempo (rounded to one decimal) the last set_code or restore_version reply stated; null before any.
@@ -280,6 +314,8 @@ export class Session {
280
314
  supersededPages = new Set();
281
315
  /** The page-to-Claude channel (tokens, trust, limits, queue, listener). */
282
316
  ask;
317
+ /** What became of each page request: started, waiting for its line, applied and the other states. */
318
+ requests;
283
319
  /** The MIDI facts of the active page, worded once per change into notice lines. */
284
320
  midi;
285
321
  // The setup file's store (null without one), the JSON of the last midi-rig each page was
@@ -311,12 +347,36 @@ export class Session {
311
347
  this.rig = options?.rig ?? null;
312
348
  this.build = options?.build ?? null;
313
349
  this.recordIntentTtlMs = options?.recordIntentTtlMs ?? RECORD_INTENT_TTL_MS;
350
+ const send = (conn, msg, what) => this.sendToPage(conn, msg, what);
351
+ this.requests = new RequestStatusBoard({ send });
314
352
  this.ask = new AskChannel({
315
- send: (conn, msg, what) => this.sendToPage(conn, msg, what),
353
+ send,
354
+ board: this.requests,
316
355
  ackTimeoutMs: options?.askAckTimeoutMs,
317
356
  });
318
357
  this.midi = new MidiChannel();
319
358
  }
359
+ /** A Moontide tool call began: the requests already handed to Claude are being worked on. */
360
+ noteToolCall() {
361
+ this.requests.noteToolCall();
362
+ }
363
+ /**
364
+ * Claude closes an open page request in words. The sentence is cleaned first; one that is empty after
365
+ * cleaning, and an id that is not an open request, are refused with a plain error and change nothing.
366
+ */
367
+ answerRequest(id, outcome, text) {
368
+ const sentence = cleanAnswerText(text);
369
+ if (sentence === '') {
370
+ throw new Error('The sentence is empty after removing spaces and control characters; give one sentence of words.');
371
+ }
372
+ if (!this.requests.closed(id, outcome, sentence)) {
373
+ throw new Error(NOT_OPEN_REQUEST_MESSAGE);
374
+ }
375
+ }
376
+ /** Whether a page request with this id is on the board and not finished. */
377
+ requestIsOpen(id) {
378
+ return this.requests.isOpen(id);
379
+ }
320
380
  setPageUrl(url) {
321
381
  this.pageUrl = url;
322
382
  }
@@ -362,6 +422,42 @@ export class Session {
362
422
  return;
363
423
  this.pendingChange = null;
364
424
  this.takeoverNotes.push(takeoverCanceledLine(waiting.cycle, reason));
425
+ const answering = waiting.intent?.answering;
426
+ if (answering === undefined)
427
+ return;
428
+ if (reason !== 'replaced') {
429
+ this.requests.canceled(answering);
430
+ }
431
+ else if (!this.answeringStillCarried(answering, waiting.id)) {
432
+ this.requests.replaced(answering);
433
+ }
434
+ }
435
+ /**
436
+ * The ramp ended. Clears the record and queues its one note for the next tool reply, except for a finished ramp and
437
+ * for Stop, which are not told. A no-op when no ramp is recorded, so a second cause (a cancel, then a closing page)
438
+ * never adds a second line.
439
+ */
440
+ dropGlide(reason, bpm) {
441
+ if (this.glide === null)
442
+ return;
443
+ this.glide = null;
444
+ if (reason === 'finished' || reason === 'stop')
445
+ return;
446
+ this.glideNotes.push(glideEndedLine(reason, bpm));
447
+ }
448
+ /**
449
+ * Whether a change other than this one still in flight answers the same request. A newer change that
450
+ * names the request carries its answer on, so the request must not end as replaced while that answer is
451
+ * still coming: the newer change settles it (applied, failed or replaced in its turn).
452
+ */
453
+ answeringStillCarried(answering, exceptEvaluateId) {
454
+ for (const [evaluateId, entry] of this.pending) {
455
+ if (entry.kind === 'evaluate' && evaluateId !== exceptEvaluateId) {
456
+ if (this.recordIntents.get(evaluateId)?.answering === answering)
457
+ return true;
458
+ }
459
+ }
460
+ return false;
365
461
  }
366
462
  /**
367
463
  * Keeps the change the page accepted for its line. One at most (last wins): a change that is
@@ -369,16 +465,32 @@ export class Session {
369
465
  * itself (takeover-canceled 'replaced').
370
466
  */
371
467
  keepWaitingChange(change) {
372
- if (this.pendingChange !== null && this.pendingChange.id !== change.id) {
373
- console.error(`[moontide] A waiting change (${this.pendingChange.id}) was replaced by request ${change.id} without a cancel message`);
468
+ const replaced = this.pendingChange;
469
+ if (replaced !== null && replaced.id !== change.id) {
470
+ console.error(`[moontide] A waiting change (${replaced.id}) was replaced by request ${change.id} without a cancel message`);
471
+ // The same rule as a cancel message: the request ends as replaced unless the new change answers it too.
472
+ const answering = replaced.intent?.answering;
473
+ if (answering !== undefined && answering !== change.intent?.answering) {
474
+ this.requests.replaced(answering);
475
+ }
374
476
  }
375
477
  this.pendingChange = change;
478
+ if (change.intent?.answering !== undefined) {
479
+ this.requests.line(change.intent.answering, change.at === 'phrase' ? 'phrase' : 'bar');
480
+ }
376
481
  }
377
482
  /**
378
483
  * BPM: rounds the tempo of a reply to one decimal and says whether it differs from the last tempo a reply
379
484
  * stated. A reply without a tempo leaves the last told tempo alone.
380
485
  */
381
- noteTempo(bpm) {
486
+ noteTempo(bpm, glide) {
487
+ // A reply that states a ramp says the ramp, not a tempo change: the ramp's target is the tempo told from here on, so the
488
+ // next reply never announces it as a change when the ramp gets there.
489
+ if (glide !== undefined) {
490
+ const target = Math.round(glide.toBpm * 10) / 10;
491
+ this.lastToldBpm = target;
492
+ return { bpm: bpm === undefined ? null : Math.round(bpm * 10) / 10, tempoChanged: false };
493
+ }
382
494
  if (bpm === undefined)
383
495
  return { bpm: null, tempoChanged: false };
384
496
  const rounded = Math.round(bpm * 10) / 10;
@@ -404,6 +516,7 @@ export class Session {
404
516
  // A change that waits does not survive a page taking the role: the page that held its gate is
405
517
  // gone or replays its code, so Claude is told once (the line is queued once however many causes meet).
406
518
  this.dropWaitingChange('page');
519
+ this.dropGlide('page');
407
520
  this.supersededPages.delete(conn);
408
521
  if (previous !== null && previous !== conn) {
409
522
  this.supersededPages.add(previous);
@@ -458,6 +571,7 @@ export class Session {
458
571
  if (this.activePage === conn) {
459
572
  this.activePage = null;
460
573
  this.dropWaitingChange('page');
574
+ this.dropGlide('page');
461
575
  this.midi.clear();
462
576
  this.lastMidiFacts = null;
463
577
  // A superseded tab says "playing in another tab": tell it that no tab plays any more, or it
@@ -582,6 +696,10 @@ export class Session {
582
696
  this.handleTakeoverCanceled(conn, msg);
583
697
  return;
584
698
  }
699
+ if (msg.type === 'glide-ended') {
700
+ this.handleGlideEnded(conn, msg);
701
+ return;
702
+ }
585
703
  if (msg.type === 'user-stopped') {
586
704
  this.handleUserStopped(conn);
587
705
  return;
@@ -623,6 +741,13 @@ export class Session {
623
741
  this.ask.handleAsk(conn, msg);
624
742
  return;
625
743
  }
744
+ if (msg.type === 'ask-board-request') {
745
+ const requests = this.requests.snapshotFor(conn);
746
+ // The board told the page where each request stands; the hand-over messages that follow reach it too.
747
+ this.ask.adoptOrigin(conn, requests.map((row) => row.id));
748
+ this.sendToPage(conn, { type: 'ask-board', requests }, 'the request board');
749
+ return;
750
+ }
626
751
  if (msg.type === 'history-request') {
627
752
  this.handleHistoryRequest(conn);
628
753
  return;
@@ -1165,6 +1290,8 @@ export class Session {
1165
1290
  return;
1166
1291
  }
1167
1292
  this.stopped = true;
1293
+ // Stop ends a ramp too; it is the user's own act, so Claude is not told.
1294
+ this.dropGlide('stop');
1168
1295
  // A stop the page reports ends a wait too; the page's takeover-canceled for the same id (sent
1169
1296
  // just before) already queued the line, and a second cause adds nothing.
1170
1297
  this.dropWaitingChange('stop');
@@ -1186,6 +1313,7 @@ export class Session {
1186
1313
  cycle: msg.pending.cycle,
1187
1314
  at: lateIntent?.at ?? 'bar',
1188
1315
  intent: lateIntent,
1316
+ ...(msg.glide?.state === 'at-line' ? { glide: msg.glide } : {}),
1189
1317
  });
1190
1318
  console.error(`[moontide] Late pending confirmation kept for request ${msg.id}`);
1191
1319
  }
@@ -1208,6 +1336,15 @@ export class Session {
1208
1336
  this.handFailureLine = null;
1209
1337
  const lateVersionId = this.recordConfirmed(conn, msg.code, lateOrigin, lateIntent);
1210
1338
  this.rememberVersion(msg.id, lateVersionId);
1339
+ // A reply whose audio is stopped records no ramp: Stop ends every ramp, so none runs.
1340
+ // An at-line report on a reply with no pending line is a waiting change that already took over: its ramp started at its line.
1341
+ if ((msg.glide?.state === 'started' || msg.glide?.state === 'at-line') && msg.audio !== 'stopped') {
1342
+ this.glide = { toBpm: msg.glide.toBpm };
1343
+ }
1344
+ // This branch does not pass through confirmSuccess, so it tells the page itself.
1345
+ if (lateIntent?.answering !== undefined) {
1346
+ this.requests.applied(lateIntent.answering, lateVersionId);
1347
+ }
1211
1348
  console.error(`[moontide] Late confirmation stored for request ${msg.id}`);
1212
1349
  }
1213
1350
  else if (msg.ok) {
@@ -1241,6 +1378,7 @@ export class Session {
1241
1378
  cycle: msg.pending.cycle,
1242
1379
  at: intent?.at ?? 'bar',
1243
1380
  intent,
1381
+ ...(msg.glide?.state === 'at-line' ? { glide: msg.glide } : {}),
1244
1382
  });
1245
1383
  entry.resolve({
1246
1384
  code: msg.code,
@@ -1249,17 +1387,25 @@ export class Session {
1249
1387
  replacedHandEdit: false,
1250
1388
  versionId: null,
1251
1389
  pending: msg.pending,
1252
- ...this.noteTempo(msg.bpm),
1390
+ ...this.noteTempo(msg.bpm, msg.glide),
1391
+ ...(msg.glide !== undefined ? { glide: msg.glide } : {}),
1253
1392
  });
1254
1393
  }
1255
1394
  else {
1256
1395
  const { replacedHandEdit, versionId } = this.confirmSuccess(conn, msg.id, msg.code, msg.audio, intent);
1396
+ // A reply whose audio is stopped states and records no ramp: Stop ends every ramp, so none runs.
1397
+ const glide = msg.audio === 'stopped' ? undefined : msg.glide;
1398
+ // A ramp this change started is the one the server tells about when it ends early; a running report leaves the record.
1399
+ // An at-line report here is a waiting change that already took over when it was answered: its ramp started at its line.
1400
+ if (glide?.state === 'started' || glide?.state === 'at-line')
1401
+ this.glide = { toBpm: glide.toBpm };
1257
1402
  entry.resolve({
1258
1403
  code: msg.code,
1259
1404
  audio: msg.audio,
1260
1405
  replacedHandEdit,
1261
1406
  versionId,
1262
- ...this.noteTempo(msg.bpm),
1407
+ ...this.noteTempo(msg.bpm, glide),
1408
+ ...(glide !== undefined ? { glide } : {}),
1263
1409
  });
1264
1410
  }
1265
1411
  }
@@ -1274,7 +1420,24 @@ export class Session {
1274
1420
  }
1275
1421
  }
1276
1422
  else {
1423
+ const failedIntent = this.recordIntents.get(msg.id);
1277
1424
  this.recordIntents.delete(msg.id);
1425
+ const failedFor = failedIntent?.answering;
1426
+ // The same rule as a replaced change: an earlier change for the same request that still waits for
1427
+ // its line, or is still on its way, carries the answer on and settles the request in its turn.
1428
+ // A ramp the page refused was refused whole and nothing changed, so the request stays open for the change Claude sends
1429
+ // next (without glideBars), which can still answer it.
1430
+ if (msg.glideRefused !== true &&
1431
+ failedFor !== undefined &&
1432
+ this.pendingChange?.intent?.answering !== failedFor &&
1433
+ !this.answeringStillCarried(failedFor, msg.id)) {
1434
+ this.requests.failed(failedFor, cleanErrorDetail(msg.error));
1435
+ }
1436
+ if (msg.glideRefused === true) {
1437
+ // The page's own refusal text, whole: it already says that nothing was changed, so no evaluation-failure tail is added.
1438
+ entry.reject(new Error(cleanRefusal(msg.error)));
1439
+ return;
1440
+ }
1278
1441
  const tail = this.code === '' ? NOTHING_PLAYING_TAIL : this.stopped ? STILL_STOPPED_TAIL : STILL_PLAYING_TAIL;
1279
1442
  entry.reject(new Error(evalFailedMessage(msg.error, msg.line, tail)));
1280
1443
  }
@@ -1299,12 +1462,17 @@ export class Session {
1299
1462
  this.handFailureLine = null;
1300
1463
  const versionId = this.recordConfirmed(conn, code, origin, intent);
1301
1464
  this.rememberVersion(id, versionId);
1465
+ if (intent?.answering !== undefined) {
1466
+ this.requests.applied(intent.answering, versionId);
1467
+ }
1302
1468
  return { replacedHandEdit, versionId };
1303
1469
  }
1304
1470
  /**
1305
1471
  * The page says the waiting change is audible now. Accepted only from the active page and only for
1306
1472
  * the id of the change that waits; anything else is one stderr line and changes nothing. The message carries
1307
- * only the id, so the recorded code is the one stored from the pending reply.
1473
+ * the id and, when the ramp the waiting change started at its line still runs, `glideRuns: true`; the recorded code
1474
+ * is the one stored from the pending reply, and the ramp record is set from `glideRuns` (or cleared without it) for a
1475
+ * change that asked for a ramp.
1308
1476
  */
1309
1477
  handleTakeover(conn, msg) {
1310
1478
  if (conn !== this.activePage) {
@@ -1318,6 +1486,11 @@ export class Session {
1318
1486
  }
1319
1487
  this.pendingChange = null;
1320
1488
  const { replacedHandEdit } = this.confirmSuccess(conn, waiting.id, waiting.code, 'playing', waiting.intent);
1489
+ // A change that asked for a ramp at its line ended any older ramp there. Its own ramp is the recorded ramp from here on only
1490
+ // when the page says it still runs; otherwise none runs (nothing to ramp between, or it already ended) and the record is
1491
+ // cleared with no line. A change replaced or canceled before its line never gets here.
1492
+ if (waiting.glide !== undefined)
1493
+ this.glide = msg.glideRuns === true ? { toBpm: waiting.glide.toBpm } : null;
1321
1494
  // An edit that carried a hand edit replaced nothing of it: the Mixer and Parts refuse while a change
1322
1495
  // waits and a hand evaluation cancels the wait, so the code playing at this takeover is still the hand-edited
1323
1496
  // text the edit was built on, and the note would be false.
@@ -1342,6 +1515,17 @@ export class Session {
1342
1515
  }
1343
1516
  this.dropWaitingChange(msg.reason);
1344
1517
  }
1518
+ /**
1519
+ * The page says its tempo ramp ended on its own. Only the active page is trusted; the numbers are bounded by the
1520
+ * protocol schema and the wording is the server's.
1521
+ */
1522
+ handleGlideEnded(conn, msg) {
1523
+ if (conn !== this.activePage) {
1524
+ console.error('[moontide] Ignoring glide-ended from a non-active page');
1525
+ return;
1526
+ }
1527
+ this.dropGlide(msg.reason, msg.bpm === undefined ? undefined : Math.round(msg.bpm * 10) / 10);
1528
+ }
1345
1529
  /**
1346
1530
  * The editedByHand flag after a confirmed evaluation: Claude's code makes the stored code
1347
1531
  * Claude's again (false). A change made from the page (origin 'page') is the user's:
@@ -1509,6 +1693,7 @@ export class Session {
1509
1693
  if (conn === this.activePage) {
1510
1694
  // A late stop confirmation still reflects reality — the page did stop.
1511
1695
  this.stopped = true;
1696
+ this.dropGlide('stop');
1512
1697
  this.dropWaitingChange('stop');
1513
1698
  console.error(`[moontide] Late stop confirmation stored for request ${msg.id}`);
1514
1699
  }
@@ -1526,6 +1711,7 @@ export class Session {
1526
1711
  this.pending.delete(msg.id);
1527
1712
  if (conn === this.activePage) {
1528
1713
  this.stopped = true;
1714
+ this.dropGlide('stop');
1529
1715
  this.dropWaitingChange('stop');
1530
1716
  entry.resolve(undefined);
1531
1717
  }
@@ -1880,6 +2066,9 @@ export class Session {
1880
2066
  // A change that waited for a line and did not take over, told once.
1881
2067
  lines.push(...this.takeoverNotes);
1882
2068
  this.takeoverNotes.length = 0;
2069
+ // A tempo ramp that ended early, told once.
2070
+ lines.push(...this.glideNotes);
2071
+ this.glideNotes.length = 0;
1883
2072
  // The build line comes before the setup file lines, then the MIDI lines, then the ask
1884
2073
  // lines (the arming line stays last). Runtime error lines and the history notes come earlier.
1885
2074
  lines.push(...this.buildNotes);
@@ -1949,6 +2138,8 @@ export class Session {
1949
2138
  force: false,
1950
2139
  at: options?.at,
1951
2140
  phraseCycles: options?.phraseCycles,
2141
+ glideBars: options?.glideBars,
2142
+ answering: options?.answering,
1952
2143
  });
1953
2144
  }
1954
2145
  /**
@@ -2007,6 +2198,8 @@ export class Session {
2007
2198
  force: false,
2008
2199
  at: options?.at,
2009
2200
  phraseCycles: options?.phraseCycles,
2201
+ glideBars: options?.glideBars,
2202
+ answering: options?.answering,
2010
2203
  carriesHandEdit,
2011
2204
  }).then((outcome) => ({ outcome, code: result.code, ranges: result.ranges, base, carriesHandEdit }));
2012
2205
  }
@@ -2054,6 +2247,7 @@ export class Session {
2054
2247
  label: restoredLabel(id),
2055
2248
  restoredFrom: id,
2056
2249
  origin: options?.origin,
2250
+ answering: options?.answering,
2057
2251
  force: true,
2058
2252
  });
2059
2253
  }
@@ -2073,6 +2267,8 @@ export class Session {
2073
2267
  message.at = intent.at;
2074
2268
  if (intent.phraseCycles !== undefined)
2075
2269
  message.phraseCycles = intent.phraseCycles;
2270
+ if (intent.glideBars !== undefined)
2271
+ message.glideBars = intent.glideBars;
2076
2272
  return message;
2077
2273
  });
2078
2274
  }