@mudlet/mudlet-web 0.3.1 → 0.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 (97) hide show
  1. package/README.md +8 -0
  2. package/dist-lib/assets/qt-resources/index.d.ts +16 -0
  3. package/dist-lib/branding.d.ts +1 -1
  4. package/dist-lib/hooks/useMudSession.d.ts +1 -1
  5. package/dist-lib/hooks/useOutput.d.ts +3 -1
  6. package/dist-lib/import/defaultPackages.d.ts +35 -2
  7. package/dist-lib/import/defaults/generic_mapper/generic_mapper.mpackage +0 -0
  8. package/dist-lib/import/defaults/gui-drop/gui-drop.mpackage +0 -0
  9. package/dist-lib/import/defaults/{run-lua-code.mpackage → mudlet-base-ui/mudlet-base-ui.mpackage} +0 -0
  10. package/dist-lib/import/defaults/run-lua-code/run-lua-code.mpackage +0 -0
  11. package/dist-lib/import/packageExport.d.ts +71 -0
  12. package/dist-lib/import/packageInstaller.d.ts +4 -1
  13. package/dist-lib/index.js +45935 -40688
  14. package/dist-lib/logging/SessionLogger.d.ts +66 -1
  15. package/dist-lib/map/MapStore.d.ts +32 -4
  16. package/dist-lib/map/mapImageExport.d.ts +1 -1
  17. package/dist-lib/map/mapZoom.d.ts +71 -0
  18. package/dist-lib/mud/ItemIdSequence.d.ts +23 -0
  19. package/dist-lib/mud/MudSession.d.ts +113 -11
  20. package/dist-lib/mud/PatternEngine.d.ts +6 -1
  21. package/dist-lib/mud/commandSplit.d.ts +16 -0
  22. package/dist-lib/mud/connection/LineAssembler.d.ts +56 -0
  23. package/dist-lib/mud/connection/MudClient.d.ts +120 -16
  24. package/dist-lib/mud/connection/TelnetNegotiator.d.ts +39 -0
  25. package/dist-lib/mud/connection/telnetByteTags.d.ts +35 -0
  26. package/dist-lib/mud/events.d.ts +19 -4
  27. package/dist-lib/mud/games/bundledGames.d.ts +29 -0
  28. package/dist-lib/mud/games/gameIcons.d.ts +5 -0
  29. package/dist-lib/mud/keybindings/KeyEngine.d.ts +9 -2
  30. package/dist-lib/mud/keybindings/browserReservedKeys.d.ts +5 -1
  31. package/dist-lib/mud/keybindings/keyEventTarget.d.ts +17 -0
  32. package/dist-lib/mud/keybindings/qtKeys.d.ts +2 -0
  33. package/dist-lib/mud/protocol/charLoginFlow.d.ts +68 -0
  34. package/dist-lib/mud/protocol/charset.d.ts +27 -5
  35. package/dist-lib/mud/protocol/constants.d.ts +1 -0
  36. package/dist-lib/mud/protocol/gmcp.d.ts +19 -3
  37. package/dist-lib/mud/protocol/index.d.ts +1 -1
  38. package/dist-lib/mud/protocol/mxp.d.ts +28 -5
  39. package/dist-lib/mud/replay/ReplayPlayer.d.ts +17 -0
  40. package/dist-lib/mud/text/Console.d.ts +64 -0
  41. package/dist-lib/mud/text/FormatState.d.ts +66 -3
  42. package/dist-lib/mud/text/hyperlinkVisibility.d.ts +35 -0
  43. package/dist-lib/mud/text/osc8Docs.d.ts +23 -0
  44. package/dist-lib/mud/text/serverWrap.d.ts +191 -0
  45. package/dist-lib/mud/timers/TimerEngine.d.ts +43 -12
  46. package/dist-lib/mud/triggers/TriggerEngine.d.ts +112 -3
  47. package/dist-lib/scripting/EngineHost.d.ts +56 -3
  48. package/dist-lib/scripting/IScriptingRuntime.d.ts +16 -0
  49. package/dist-lib/scripting/MxpFrameManager.d.ts +118 -0
  50. package/dist-lib/scripting/ScriptingAPI.d.ts +323 -54
  51. package/dist-lib/scripting/ScriptingEngine.d.ts +180 -23
  52. package/dist-lib/scripting/http/HttpService.d.ts +51 -1
  53. package/dist-lib/scripting/lua/LuaRuntime.d.ts +151 -2
  54. package/dist-lib/scripting/lua/bindings/map.d.ts +0 -12
  55. package/dist-lib/scripting/lua/utf8Patterns.d.ts +59 -0
  56. package/dist-lib/storage/index.d.ts +1 -1
  57. package/dist-lib/storage/persistentStorage.d.ts +10 -0
  58. package/dist-lib/storage/schema.d.ts +116 -0
  59. package/dist-lib/styles.css +1 -1
  60. package/dist-lib/ui/BundledGameGrid.d.ts +21 -0
  61. package/dist-lib/ui/CommandBar.d.ts +7 -1
  62. package/dist-lib/ui/ConnectionGrid.d.ts +4 -1
  63. package/dist-lib/ui/FilePickerModal.d.ts +3 -3
  64. package/dist-lib/ui/HelpModal.d.ts +11 -0
  65. package/dist-lib/ui/LogBrowserModal.d.ts +4 -1
  66. package/dist-lib/ui/PlayerMarkerPreview.d.ts +20 -0
  67. package/dist-lib/ui/Toolbar.d.ts +2 -1
  68. package/dist-lib/ui/VfsPickerModal.d.ts +26 -0
  69. package/dist-lib/ui/components/FileSourceButton.d.ts +47 -0
  70. package/dist-lib/ui/components/index.d.ts +1 -0
  71. package/dist-lib/ui/helpTopics.d.ts +19 -0
  72. package/dist-lib/ui/labels/LabelManager.d.ts +7 -0
  73. package/dist-lib/ui/layout/ScriptWindow.d.ts +11 -2
  74. package/dist-lib/ui/markdown.d.ts +10 -1
  75. package/dist-lib/ui/output/OutputArea.d.ts +11 -1
  76. package/dist-lib/ui/output/OutputContextMenu.d.ts +3 -1
  77. package/dist-lib/ui/output/OutputRenderer.d.ts +7 -1
  78. package/dist-lib/ui/output/OutputSearchBar.d.ts +25 -0
  79. package/dist-lib/ui/output/StickyOutputPanel.d.ts +5 -1
  80. package/dist-lib/ui/output/outputSearch.d.ts +130 -0
  81. package/dist-lib/ui/search/matcher.d.ts +15 -0
  82. package/dist-lib/ui/search/useDebounced.d.ts +3 -0
  83. package/dist-lib/ui/tts/TtsManager.d.ts +3 -0
  84. package/dist-lib/ui/useCommandHistory.d.ts +4 -2
  85. package/dist-lib/ui/useOpenProfiles.d.ts +26 -0
  86. package/dist-lib/ui/video/VideoManager.d.ts +17 -0
  87. package/dist-lib/ui/windows/WindowManager.d.ts +132 -2
  88. package/dist-lib/ui/windows/panels/MapPanel.d.ts +4 -1
  89. package/dist-lib/ui/windows/panels/PackageExportModal.d.ts +18 -0
  90. package/dist-lib/ui/windows/panels/TextPanel.d.ts +3 -1
  91. package/dist-lib/ui/windows/types.d.ts +35 -0
  92. package/dist-lib/utils/describeThrown.d.ts +22 -0
  93. package/dist-lib/utils/fontLoader.d.ts +5 -0
  94. package/dist-lib/vite.js +22 -0
  95. package/package.json +10 -6
  96. package/dist-lib/scripting/lua/mudlet-lua/generic-mapper/generic_mapper.mpackage +0 -0
  97. package/dist-lib/ui/windows/panels/VfsModulePickerModal.d.ts +0 -8
@@ -1,11 +1,13 @@
1
1
  import type { MudSession } from '../mud/MudSession';
2
+ import type { LogFormat } from '../logging/SessionLogger';
2
3
  import type { AliasEngine } from '../mud/aliases/AliasEngine';
3
4
  import { TriggerEngine } from '../mud/triggers/TriggerEngine';
4
5
  import type { TimerEngine } from '../mud/timers/TimerEngine';
5
6
  import type { KeyEngine } from '../mud/keybindings/KeyEngine';
6
7
  import type { ButtonNode, ScriptNode } from '../storage/schema';
7
8
  import { type InstallOutcome } from './ScriptingAPI';
8
- import type { EngineHost } from './EngineHost';
9
+ import { type EngineHost, type TempComplexTriggerSpec } from './EngineHost';
10
+ import { ItemIdSequence } from '../mud/ItemIdSequence';
9
11
  import type { LuaGlobalEntry } from './IScriptingRuntime';
10
12
  import { ProfileVFS } from './vfs/ProfileVFS';
11
13
  import type { PackageManifest } from '../storage/schema';
@@ -74,13 +76,18 @@ export declare class ScriptingEngine implements EngineHost {
74
76
  /** For a linked Mudlet profile: the timestamped current/*.xml filename this
75
77
  * session writes back to (created lazily on first write, reused thereafter). */
76
78
  private mudletSaveName;
77
- private mspBaseUrl;
79
+ private mspBaseUrl?;
78
80
  private gmcpMediaDefaultUrl;
79
81
  private exitFired;
80
82
  private readonly beforeUnload;
81
83
  private uuidToNumericId;
82
- private nextNumericId;
84
+ /** The profile's single item-id sequence, shared with every engine and with
85
+ * the Lua runtime's temporary items — see {@link ItemIdSequence}. */
86
+ readonly itemIds: ItemIdSequence;
83
87
  private numericIdFor;
88
+ /** Hand out the next item id. Backs the Lua runtime's temporary items, so a
89
+ * temp and a permanent item can never collide on one number. */
90
+ allocateItemId(): number;
84
91
  constructor(session: MudSession, aliasEngine: AliasEngine, triggerEngine: TriggerEngine, timerEngine: TimerEngine, keyEngine: KeyEngine, connectionId: string, connectionName?: string, proxyUrlGetter?: () => string | undefined, injectedVfs?: ProfileVFS | null);
85
92
  /**
86
93
  * Subscribe to the appStore and apply diffs synchronously on every
@@ -168,6 +175,19 @@ export declare class ScriptingEngine implements EngineHost {
168
175
  getModulePriority(moduleName: string): number;
169
176
  /** List installed module names. Order matches install order. */
170
177
  getModuleNames(): string[];
178
+ /**
179
+ * Write every module flagged to sync back out to its own file.
180
+ *
181
+ * Called by saveProfile, which is the only moment it happens — and the
182
+ * moment it has to: a module shared between profiles carries this one's
183
+ * edits to the others through its file, so a save that skipped this left
184
+ * them in our store alone, to be lost the next time another profile read
185
+ * the module back.
186
+ *
187
+ * Fire-and-forget per module, like syncModule itself: the write is async,
188
+ * and a save reports on what it started rather than waiting.
189
+ */
190
+ saveSyncedModules(): void;
171
191
  /** Snapshot of a module's manifest, or null if not installed / not a module. */
172
192
  getModuleInfo(moduleName: string): PackageManifest | null;
173
193
  /**
@@ -246,8 +266,31 @@ export declare class ScriptingEngine implements EngineHost {
246
266
  private initRuntime;
247
267
  /** Run a chunk of Lua on behalf of a clicked hyperlink. */
248
268
  runLinkCode(code: string): void;
249
- /** Mudlet `expandAlias` — run text through the alias pipeline, sending it
250
- * to the MUD when nothing consumes it. */
269
+ /**
270
+ * Mudlet `Host::send(cmd, wantPrint, dontExpandAliases = false)` — the one
271
+ * path every command the player types, and every item's built-in `command`
272
+ * field, travels down. Three stages, in this order:
273
+ *
274
+ * 1. echo the **whole, unsplit** text under the showSentText mode;
275
+ * 2. split it on the profile's command separator;
276
+ * 3. run each part through the aliases, and put on the wire whatever no
277
+ * alias consumed.
278
+ *
279
+ * The order is the point. Echoing before the alias pass is why typing an
280
+ * alias shows the text you typed even though the alias swallows it — and
281
+ * why the alias's own command field, arriving back here in turn, is echoed
282
+ * again as a second line. Splitting after the echo is why `n;;s` echoes as
283
+ * one line but sends two commands.
284
+ *
285
+ * Lua `send()` skips only stage 3 ({@link ScriptingAPI.send}); Mudlet's
286
+ * `expandAlias(text, echo)` is exactly this function.
287
+ */
288
+ hostSend(text: string, echo?: boolean): void;
289
+ /** Nesting depth of {@link hostSend} — see the runaway-alias note there. */
290
+ private sendDepth;
291
+ private static readonly MAX_SEND_DEPTH;
292
+ /** Mudlet `expandAlias(text, echo)` — `Host::send` with both defaults, i.e.
293
+ * "run this as though I had typed it". */
251
294
  expandAlias(text: string, echo: boolean): void;
252
295
  /** Mudlet `reconnect()` — redial the last URL, refused during teardown. */
253
296
  requestReconnect(): boolean;
@@ -255,7 +298,11 @@ export declare class ScriptingEngine implements EngineHost {
255
298
  dispatchSendRequest(text: string): boolean;
256
299
  /** Names of every package installed in this profile. */
257
300
  getPackageNames(): string[];
258
- /** Module manifest fields as a flat record, with Lua-set overrides applied. */
301
+ /** What the module's config.lua declared, with Lua-set overrides applied.
302
+ * The same contract as getPackageInfo — see manifestInfoBase. This used to
303
+ * hand back the whole manifest, so callers saw mudix's own bookkeeping
304
+ * (installedAt, sourceFile, and latterly declaredInfo itself) alongside the
305
+ * fields the module author actually wrote. */
259
306
  getModuleInfoRecord(name: string): Record<string, unknown> | null;
260
307
  /** Rewrite VFS-relative url(...) references to service-worker paths. */
261
308
  rewriteCss(css: string): string;
@@ -267,6 +314,8 @@ export declare class ScriptingEngine implements EngineHost {
267
314
  * getMudletHomeDir()-prefixed paths work) and relative ones against the
268
315
  * profile root. */
269
316
  readFileBytes(path: string): Uint8Array | null;
317
+ writeFileBytes(path: string, bytes: Uint8Array): boolean;
318
+ configDirectory(): string | null;
270
319
  private createRuntime;
271
320
  /**
272
321
  * Tear down a script's event-handler registrations. Used when a script is
@@ -284,7 +333,7 @@ export declare class ScriptingEngine implements EngineHost {
284
333
  * loads the new scripts synchronously inside that commit, so by the time
285
334
  * this method runs the package's event handlers are already registered.
286
335
  */
287
- notifyPackageInstalled(packageName: string): void;
336
+ notifyPackageInstalled(packageName: string, fileName?: string): void;
288
337
  /**
289
338
  * Raise sysUninstall / sysUninstallPackage. Call this BEFORE removing the
290
339
  * package's items from the store so the package's own handlers (and the
@@ -342,6 +391,21 @@ export declare class ScriptingEngine implements EngineHost {
342
391
  * reloads. Returns null when the file can't be located or fetched.
343
392
  */
344
393
  private resolveMspMedia;
394
+ /**
395
+ * Last-resort media location when a server plays a file without ever
396
+ * announcing where it lives — neither an MSP `U=` nor a GMCP
397
+ * `Client.Media.Default { url }`. Mudlet guesses the MUD's own website:
398
+ * `https://www.<host>/media/` (TMedia::parseUrl). The host is the telnet
399
+ * host — for a `websocket`-mode profile, the endpoint's hostname — the same
400
+ * value getConnectionInfo() reports, standing in for Mudlet's `Host::mUrl`.
401
+ *
402
+ * The one deviation from Mudlet: it prepends `www.` unconditionally, which
403
+ * turns an already-qualified `www.example.com` into `www.www.example.com`.
404
+ * We skip the prefix when the host already carries it. Returns undefined
405
+ * when there's no host to guess from, so the caller reports "no base URL"
406
+ * rather than fetching a nonsense URL.
407
+ */
408
+ private defaultMediaLocation;
345
409
  /**
346
410
  * Resolve a media `file` (+ optional base `url` directory) to a VFS-relative
347
411
  * path under `media/` the SoundManager loader can read, downloading and
@@ -349,8 +413,9 @@ export declare class ScriptingEngine implements EngineHost {
349
413
  * protocol (`handleClientMedia`). The whole filename — including any
350
414
  * subdirectories — is appended to the base URL and mirrored under `media/`,
351
415
  * so the cache layout matches the server's. `..`/`.` segments are rejected
352
- * so a hostile server can't escape the cache root. Returns null when the
353
- * file can't be located or fetched.
416
+ * so a hostile server can't escape the cache root. With no base URL at all,
417
+ * {@link defaultMediaLocation} guesses one from the MUD's host. Returns null
418
+ * when the file can't be located or fetched.
354
419
  */
355
420
  private resolveMediaFile;
356
421
  /**
@@ -379,9 +444,15 @@ export declare class ScriptingEngine implements EngineHost {
379
444
  */
380
445
  uninstallPackageByName(packageName: string): boolean;
381
446
  /**
382
- * Toggle a script's enabled flag by name (Mudlet enableScript/disableScript).
447
+ * Toggle scripts' enabled flag by name (Mudlet enableScript/disableScript).
383
448
  * The store subscription picks up the change synchronously and either loads
384
449
  * or unloads handlers in the runtime.
450
+ *
451
+ * Every script sharing the name flips, not just the first — same rule as
452
+ * {@link toggleTriggerByName}, and Other_spec pins it. Names are not unique
453
+ * in the tree, so stopping at the first match would leave duplicates in a
454
+ * state the caller never asked for and `isActive(name)` would disagree with
455
+ * `exists(name)`.
385
456
  */
386
457
  toggleScriptByName(name: string, enabled: boolean): boolean;
387
458
  /**
@@ -438,6 +509,19 @@ export declare class ScriptingEngine implements EngineHost {
438
509
  * Those live in the Lua runtime, not the saved tree. */
439
510
  setTempItemEnabled(id: number, enabled: boolean): boolean;
440
511
  setForceMxpProcessorOn(on: boolean): void;
512
+ /** Mudlet `cTelnet::autoEnableMXPProcessor`. Runs when MXP started from an
513
+ * in-band `ESC[<n>z` rather than a telnet option-91 handshake: such servers
514
+ * are IRE-style and never send mode switches, they just emit secure tags —
515
+ * so without the secure-mode lock every `<SEND>`/`<A>`/definition tag would
516
+ * be discarded as unsafe. The `promptForMXPProcessorOn` latch records that
517
+ * this profile has been auto-enabled once, which is what lets a user turn
518
+ * `specialForceMXPProcessorOn` back off without being overridden on the
519
+ * next connect (the gate lives in MudClient → TelnetNegotiator).
520
+ *
521
+ * A second detection while already forced on is a *re-initialisation* (a
522
+ * server re-announcing MXP after e.g. `config mxp on`): re-apply the lock,
523
+ * but stay silent — Mudlet returns before its postMessage there. */
524
+ private autoEnableMxpProcessor;
441
525
  existsByName(nameOrId: string | number, type: string): number;
442
526
  /**
443
527
  * Mudlet `isActive(nameOrId, type [, checkAncestors])`. Returns the count of
@@ -452,6 +536,7 @@ export declare class ScriptingEngine implements EngineHost {
452
536
  * findItems, isAncestorsActive). Type aliases mirror Mudlet: "key" and
453
537
  * "keybind" both target keybindings. Unknown types return an empty list.
454
538
  */
539
+ isKnownItemType(type: string): boolean;
455
540
  private nodeListForType;
456
541
  private nodeTypeLabel;
457
542
  /**
@@ -473,7 +558,7 @@ export declare class ScriptingEngine implements EngineHost {
473
558
  * true) toggles exact vs substring; `caseSensitive` (default true) toggles
474
559
  * case folding. Empty array when nothing matches or the type is unknown.
475
560
  */
476
- findItemsByName(name: string, type: string, exact: boolean, caseSensitive: boolean): number[];
561
+ findItemsByName(name: string, type: string, exact: boolean, caseSensitive: boolean): number[] | null;
477
562
  /**
478
563
  * Mudlet `isAncestorsActive(id, type)`. True when every ancestor group of
479
564
  * the item is enabled (the item's own state is ignored); true when the item
@@ -533,6 +618,33 @@ export declare class ScriptingEngine implements EngineHost {
533
618
  */
534
619
  createPermPromptTrigger(name: string, parent: string, code: string): number;
535
620
  private createPermTrigger;
621
+ /**
622
+ * Mudlet `tempComplexRegexTrigger(...)`. A session-scoped trigger with
623
+ * everything a permanent one can have — an AND chain across lines, a filter
624
+ * that hands its captures to children, a fire length, a line delta, colour
625
+ * patterns — because that is what the call offers and none of it fits the
626
+ * flat temp-trigger primitive. So it makes a real node, marked temporary so
627
+ * it is not saved with the profile.
628
+ *
629
+ * Calling it again with a name that is already taken does not replace that
630
+ * trigger — it ADDS to it. Mudlet names the argument "trigger name create
631
+ * or add to": the new pattern is appended to the ones already there and the
632
+ * trigger is rebuilt around the longer list, which is how a multiline AND
633
+ * chain is assembled one condition per call.
634
+ */
635
+ createTempComplexTrigger(spec: TempComplexTriggerSpec): number;
636
+ /**
637
+ * Remove a temporary trigger by the numeric id `tempComplexRegexTrigger`
638
+ * handed the script. False when that id names no temporary trigger — a
639
+ * permanent one is refused, as Mudlet's killTrigger refuses it.
640
+ */
641
+ removeTemporaryTriggerById(id: number): boolean;
642
+ /** Remove a trigger and everything under it. A child outlives its parent
643
+ * nowhere in the tree, and a permanent trigger parented to a temporary one
644
+ * goes with it — which is the only way it could have been reached. */
645
+ private removeTriggerSubtree;
646
+ /** The root ancestor of a trigger node — what a lineage is recorded against. */
647
+ private rootTriggerOf;
536
648
  /**
537
649
  * Mudlet `permAlias(name, parent, regex, luaCode)`. Creates a saved alias
538
650
  * named `name` under the alias group `parent` (empty = root). The pattern
@@ -574,6 +686,16 @@ export declare class ScriptingEngine implements EngineHost {
574
686
  createTempButtonToolbar(name: string, orientation: number, location: number): number;
575
687
  /** Mudlet `setButtonState(name, state)`. Flips the buttonState on the named
576
688
  * two-state button. */
689
+ /**
690
+ * What `name` refers to, so the Lua side can say which of Mudlet's several
691
+ * refusals applies: no such button, or a button that has no state to set
692
+ * because it is not a two-state one.
693
+ */
694
+ buttonKindByName(name: string): 'missing' | 'plain' | 'pushdown';
695
+ /** True when the state actually changed. Mudlet answers false for a button
696
+ * that was already in the state asked for — "nothing to do" rather than
697
+ * "could not do it", which is why the caller can tell them apart only by
698
+ * having checked the name first. */
577
699
  setButtonStateByName(name: string, state: boolean): boolean;
578
700
  /** Mudlet `getButtonState(name)`. Reads the pressed state on a two-state
579
701
  * button by name. Returns null when no such button (the Lua binding maps
@@ -582,10 +704,24 @@ export declare class ScriptingEngine implements EngineHost {
582
704
  /** Mudlet `setButtonStyleSheet(name, css)`. Stores raw CSS on the
583
705
  * ButtonNode; ButtonsBar applies it inline. */
584
706
  setButtonStyleSheetByName(name: string, css: string): boolean;
585
- /** Mudlet `showToolBar(name)` / `hideToolBar(name)`. Toggles the toolbar
586
- * group's enabled flag (the ButtonsBar already filters by
587
- * isEffectivelyEnabled, so this is the show/hide hook). */
588
- toggleToolBarByName(name: string, show: boolean): boolean;
707
+ /**
708
+ * Mudlet `showToolBar(name)` / `hideToolBar(name)`. Toggles the toolbar's
709
+ * own enabled flag the ButtonsBar filters by isEffectivelyEnabled, so that
710
+ * is the show/hide hook. Returns null on success, or the reason it moved
711
+ * nothing (which the Bridge shapes into Mudlet's `(nil, errMsg)`).
712
+ *
713
+ * Three rules from `ActionUnit::setToolBarActive`, none of them obvious:
714
+ *
715
+ * - A toolbar that came in a package answers to its OWN name, and the
716
+ * package's root node is left alone. Reaching a toolbar through its
717
+ * package used to be the only way, which meant hiding one hid every bar
718
+ * that package installed.
719
+ * - The package's name still works, and then it moves every toolbar in it.
720
+ * - A floating toolbar is refused, and says so: these functions dock and
721
+ * undock, and a floating bar is not in the docking arrangement at all.
722
+ * Reporting it as missing sent scripts looking for a name they had right.
723
+ */
724
+ toggleToolBarByName(name: string, show: boolean): string | null;
589
725
  /**
590
726
  * Mudlet `setScript(name, luaCode[, pos])`. Replaces the source of the
591
727
  * `pos`-th script (1-indexed; default 1) named `name`. Updating via the
@@ -595,14 +731,15 @@ export declare class ScriptingEngine implements EngineHost {
595
731
  */
596
732
  setScriptByName(name: string, code: string, pos: number): number;
597
733
  /**
598
- * Mudlet `getScript(name [, pos]) → code, count`. Returns the source of the
599
- * pos-th (1-indexed; default 1) script named `name` and how many scripts
600
- * share that name. Returns null when none match — the Bridge.lua wrapper
601
- * turns that into ("", 0).
734
+ * Mudlet `getScript(name [, pos]) → code, id`. Returns the source of the
735
+ * pos-th (1-indexed; default 1) script named `name` and that script's own
736
+ * numeric id. Null when nothing sits at that position — the Bridge.lua
737
+ * wrapper turns that into Mudlet's (-1, message).
602
738
  */
739
+ removeScriptById(id: number): boolean;
603
740
  getScriptByName(name: string, pos: number): {
604
741
  code: string;
605
- count: number;
742
+ id: number;
606
743
  } | null;
607
744
  /**
608
745
  * Mudlet `killTimer/killAlias/killTrigger/killKey(name)` deletes every
@@ -638,12 +775,18 @@ export declare class ScriptingEngine implements EngineHost {
638
775
  private runScriptLoad;
639
776
  get currentVFS(): ProfileVFS | null;
640
777
  /**
641
- * Send a command from the command bar. Raises sysDataSendRequest, then either
642
- * sends (and echoes locally) or aborts when a handler calls denyCurrentSend().
778
+ * Submit one line from the command bar Mudlet's `TCommandLine` calling
779
+ * `Host::send` per newline-separated line. Echoes it, splits it on the
780
+ * command separator, runs the parts through the aliases, and sends what
781
+ * survives (raising sysDataSendRequest per part, so a handler calling
782
+ * denyCurrentSend() can still abort an individual command).
643
783
  */
644
784
  sendCommand(text: string): void;
645
785
  /** Run input through aliases. Returns true if an alias matched (caller should not send). */
646
786
  processInput(text: string): boolean;
787
+ /** Nesting depth of {@link processInput} — see the note there. */
788
+ private aliasDepth;
789
+ private processInputPass;
647
790
  /** Process a keyboard event. Returns true if a keybinding consumed it. */
648
791
  processKey(event: KeyboardEvent): boolean;
649
792
  raiseEvent(event: string, args?: unknown[]): void;
@@ -658,7 +801,9 @@ export declare class ScriptingEngine implements EngineHost {
658
801
  setCmdLineValue(text: string): void;
659
802
  /** Hand the API a startLogging hook. Wired by ProfileSession, which owns
660
803
  * the actual SessionLogger lifecycle. */
661
- setLoggingToggler(fn: ((enabled: boolean) => boolean) | null): void;
804
+ setLoggingToggler(fn: ((enabled: boolean, format: LogFormat) => boolean) | null): void;
805
+ /** Where the live logger is writing, so startLogging can report the file. */
806
+ setLoggingPathProvider(fn: (() => string | null) | null): void;
662
807
  /** Mudlet's "Show errors in main console" — when on, script errors are also
663
808
  * echoed (in red) into the main output window, not just the Errors tab. */
664
809
  setShowErrorsInMainWindow(enabled: boolean): void;
@@ -774,7 +919,19 @@ export declare class ScriptingEngine implements EngineHost {
774
919
  processFlushBatch(groups: {
775
920
  text: string;
776
921
  type: string;
922
+ fromServer?: boolean;
777
923
  }[]): void;
924
+ /**
925
+ * Free everything killed while this batch was processed.
926
+ *
927
+ * Mudlet's alias/trigger/timer/key units each defer freeing a killed item to
928
+ * the end of the line that killed it, rather than dropping it at the kill.
929
+ * That is what makes a killed item still findable for the rest of the pass —
930
+ * `exists` finds it, `isActive` says 0, and a second kill can tell "already
931
+ * dead" (false) from "never existed" (also false, but for the other reason).
932
+ * Nothing can fire in the meantime: killing unsubscribes immediately.
933
+ */
934
+ private reapKilledTempItems;
778
935
  /**
779
936
  * Attach clickable hyperlinks for the MXP `<SEND>`/`<A>` ranges the parser
780
937
  * found. `setHyperlink` overlays each range while preserving the segments'
@@ -2,20 +2,70 @@ import type { ProfileVFS } from '../vfs/ProfileVFS';
2
2
  type EmitFn = (event: string, args: unknown[]) => void;
3
3
  type VFSGetter = () => ProfileVFS | null;
4
4
  type ProxyUrlGetter = () => string | undefined;
5
+ /** Runs `fn` off the current call stack. See {@link HttpService.emitLater}. */
6
+ type DeferFn = (fn: () => void) => void;
7
+ /** Bytes behind a path in a read-only bundled namespace, or null. */
8
+ type LocalReader = (path: string) => Uint8Array | null;
5
9
  export declare class HttpService {
6
10
  private readonly emit;
7
11
  private readonly vfsGetter;
8
12
  private readonly proxyUrlGetter;
13
+ private readonly defer;
14
+ /** Consulted before the profile VFS when serving a `file:` URL, so a
15
+ * bundled path resolves the same way Lua's io.open resolves it. */
16
+ private readonly localReader;
9
17
  private readonly proxiedOrigins;
10
- constructor(emit: EmitFn, vfsGetter: VFSGetter, proxyUrlGetter?: ProxyUrlGetter);
18
+ /**
19
+ * The response record Mudlet hands to every HTTP event as its last argument:
20
+ * `{ headers = {...}, cookies = {...} }`. Scripts read it to branch on what
21
+ * came back — a content type, a rate-limit header — and we were passing an
22
+ * empty string, so `response.headers` was an index into nothing.
23
+ *
24
+ * `cookies` is always empty, and has to be: a browser never exposes
25
+ * `Set-Cookie` to script, cross-origin or not. It is still present, because
26
+ * the shape is the contract — a script doing `response.cookies["session"]`
27
+ * should read nil, not fail on indexing a missing table.
28
+ */
29
+ private responseRecord;
30
+ constructor(emit: EmitFn, vfsGetter: VFSGetter, proxyUrlGetter?: ProxyUrlGetter, defer?: DeferFn,
31
+ /** Consulted before the profile VFS when serving a `file:` URL, so a
32
+ * bundled path resolves the same way Lua's io.open resolves it. */
33
+ localReader?: LocalReader);
11
34
  downloadFile(saveTo: string, url: string): void;
12
35
  getHTTP(url: string, headers?: Record<string, string>): void;
13
36
  postHTTP(data: string | null, url: string, headers?: Record<string, string>, file?: string): void;
14
37
  putHTTP(data: string | null, url: string, headers?: Record<string, string>, file?: string): void;
15
38
  deleteHTTP(url: string, headers?: Record<string, string>): void;
16
39
  customHTTP(method: string, data: string | null, url: string, headers?: Record<string, string>, file?: string): void;
40
+ /**
41
+ * Serve a `file:` URL out of the VFS, reporting through the same
42
+ * sysDownloadDone/sysDownloadError a network download would.
43
+ *
44
+ * The path is the URL's own, percent-decoded — `file:///lua/x` reads
45
+ * `/lua/x`, which may be the read-only bundled namespace as readily as the
46
+ * profile, exactly as Lua's io.open sees both.
47
+ */
48
+ private copyLocalFile;
17
49
  private runDownload;
18
50
  private readWithProgress;
51
+ /**
52
+ * Emit on a later turn of the event loop.
53
+ *
54
+ * Every other emit here fires from an async continuation, so it is already
55
+ * off the call that started the request. The synchronous failures are the
56
+ * exception — building an upload body, or refusing a url scheme outright —
57
+ * because they would otherwise emit, and therefore dispatch Lua event
58
+ * handlers, while still inside the `__postHTTP`/`__downloadFile` binding
59
+ * that Lua itself called. Re-entering the Lua state mid-call crashes
60
+ * wasmoon outright ("memory access out of bounds"), taking the whole
61
+ * runtime with it rather than failing one call.
62
+ *
63
+ * The caller supplies the deferral so it can pick a queue a script can
64
+ * actually observe: microtasks are fine in the app, but a synchronous Lua
65
+ * run (the busted harness) never yields to one, and the runtime hands us
66
+ * its timer queue instead.
67
+ */
68
+ private emitLater;
19
69
  private bodyForUpload;
20
70
  private runRequest;
21
71
  private fetchWithFallback;
@@ -7,8 +7,13 @@ export declare class LuaRuntime implements IScriptingRuntime {
7
7
  private readonly api;
8
8
  private vfs;
9
9
  private readonly proxyUrlGetter;
10
+ /** Script-created temp aliases/triggers by id. `dead` marks one killed but
11
+ * not yet reaped: Mudlet frees a killed unit in the deferred cleanup it
12
+ * runs at the end of a line, not at the kill, so until then it is still
13
+ * findable while no longer firing — `exists` says 1, `isActive` says 0, and
14
+ * a second kill finds a corpse and answers false. {@link reapKilledTempItems}
15
+ * does the freeing. */
10
16
  private readonly tempIds;
11
- private nextTempId;
12
17
  private readonly labelCbIds;
13
18
  private cmdLineActionCbId;
14
19
  private windowCmdLineActionCbIds;
@@ -17,8 +22,53 @@ export declare class LuaRuntime implements IScriptingRuntime {
17
22
  private currentCaptureSpans;
18
23
  private currentNamedSpans;
19
24
  private currentFullMatchSpan;
25
+ /**
26
+ * Move the capture positions recorded for the line being processed to
27
+ * follow `delta` characters inserted at column `at`.
28
+ *
29
+ * A trigger that calls `insertText` before its own match and then
30
+ * `selectCaptureGroup` would otherwise select the text that has slid into
31
+ * where the capture used to be. A capture that starts at or after the
32
+ * insert moves whole; one the insert lands inside grows by it.
33
+ */
34
+ shiftCaptureSpans(at: number, delta: number): void;
20
35
  private _denyCurrentSend;
36
+ /** The read-only `/lua/` namespace: bundled Mudlet Lua, and in busted builds
37
+ * the spec corpus and its fixtures. Values are Latin-1 byte-strings. */
38
+ private builtinFiles;
39
+ /**
40
+ * Bytes behind a built-in `/lua/` path, or null when the path is not one.
41
+ *
42
+ * Lua's own `io.open` reaches these through the VFS shim, but a caller
43
+ * working in bytes does not: the package installer is handed a path and
44
+ * reads it through the *profile* VFS, which knows nothing about this
45
+ * namespace. That is why installing one of the corpus's fixture packages
46
+ * reported "file not found" for a file Lua could open perfectly well.
47
+ */
48
+ readBuiltinBytes(path: string): Uint8Array | null;
21
49
  private destroyed;
50
+ /**
51
+ * Set once the Lua WASM module has terminated for good — an Emscripten
52
+ * `ExitStatus` or `WebAssembly.RuntimeError` unwinding out of a call.
53
+ *
54
+ * Lua reaches `exit(EXIT_FAILURE)` in `luaD_throw` whenever an error is
55
+ * raised with no protected frame to catch it: a panic, or an allocation
56
+ * failure inside a JS-side API call (`lua_newthread`, `lua_pushstring`, …)
57
+ * that runs on the main state rather than inside a pcall. Emscripten then
58
+ * latches its own ABORT flag, so every later call into the module throws the
59
+ * same thing — one identical error per timer tick, forever, with nothing
60
+ * saying the engine is gone.
61
+ *
62
+ * Tracked apart from {@link destroyed} because teardown still has to run:
63
+ * the JS-side resources (global event channel, TTS, map dispatchers) outlive
64
+ * the WASM module and must still be released.
65
+ */
66
+ private fatal;
67
+ /** Notified once, when {@link fatal} latches. The engine uses it to stop the
68
+ * timer pump — the only thing left that would keep calling in. */
69
+ onFatalError: (() => void) | null;
70
+ /** True once nothing can be executed any more, for either reason. */
71
+ private get inert();
22
72
  private readonly watchedPaths;
23
73
  private readonly rawFnPtrs;
24
74
  private http;
@@ -29,6 +79,34 @@ export declare class LuaRuntime implements IScriptingRuntime {
29
79
  private constructor();
30
80
  static create(api: ScriptingAPI, vfs?: ProfileVFS | null, proxyUrlGetter?: () => string | undefined): Promise<LuaRuntime>;
31
81
  private setup;
82
+ /**
83
+ * Make `tostring(someFunction)` safe — and Mudlet-shaped.
84
+ *
85
+ * wasmoon pushes every JS function as a C closure and gives it a metatable
86
+ * carrying `__tostring`/`__index` that dereference the JS value behind it.
87
+ * But Lua 5.1's `lua_setmetatable` on a non-table, non-userdata value sets
88
+ * the metatable for that *whole type* (`G(L)->mt[ttype(o)]`), so the last JS
89
+ * function bound here becomes the metatable of EVERY Lua function. Call
90
+ * `tostring` on an ordinary one and wasmoon looks up a JS ref that was never
91
+ * registered, gets `undefined`, and throws `Cannot read properties of
92
+ * undefined (reading 'toString')` — a JS TypeError, not a Lua error, so it
93
+ * unwinds straight through `pcall` and out of `doStringSync`, killing the
94
+ * whole lua_State. `tostring(print)`, `display{f = print}` and Mudlet's own
95
+ * `printTable` on a table holding a function all hit it.
96
+ *
97
+ * The fix replaces the global `tostring` rather than the metamethod: a JS
98
+ * function pushed later installs a *fresh* metatable and would silently
99
+ * revert a metamethod patch, whereas nothing rebinds this global. Clearing
100
+ * the shared metatable for the duration of the call takes Lua's own
101
+ * `function: 0x…` path — which is what Mudlet prints too, so this also drops
102
+ * wasmoon's habit of stringifying a bound global into its entire JS source.
103
+ * Lua 5.1's `print` reads the global at call time, so it follows along.
104
+ *
105
+ * Installed after every `global.set` above and before Bridge.lua, so no
106
+ * bundled or user chunk can capture the unsafe version as an upvalue.
107
+ */
108
+ private installSafeFunctionTostring;
109
+ private installMudletLuaOverrides;
32
110
  private installFastColorEcho;
33
111
  private setupAnsiColorTable;
34
112
  /**
@@ -37,8 +115,36 @@ export declare class LuaRuntime implements IScriptingRuntime {
37
115
  * don't live in the persisted store the way permanent items do.
38
116
  */
39
117
  tempItemExists(id: number, type: string): boolean;
118
+ /** A live temp item of `type` carrying this Mudlet-supplied name — how
119
+ * exists()/killTrigger() reach a tempComplexRegexTrigger. */
120
+ tempItemIdByName(name: string, type: string): number | null;
121
+ /**
122
+ * killAlias/killTrigger with a numeric id, for a script-created item.
123
+ *
124
+ * The item is unsubscribed and its callback released straight away — it must
125
+ * not fire again, including later in the very line pass that killed it — but
126
+ * the entry stays in the map, marked dead, until {@link reapKilledTempItems}.
127
+ * That is Mudlet's deferred cleanup: until the line ends, the corpse is still
128
+ * findable, which is what lets a second kill report `false` rather than
129
+ * mistaking the id for one that never existed.
130
+ */
131
+ /** Resolve a killTrigger/killAlias argument to a live temp item's id: the
132
+ * number itself, or the name a tempComplexRegexTrigger was given. */
133
+ tempItemId(idOrName: number | string, type: 'alias' | 'trigger'): number | null;
134
+ private killTempItem;
135
+ /**
136
+ * Free every temp alias/trigger killed since the last call, or only those of
137
+ * `type` when given.
138
+ *
139
+ * Mudlet cleans up per unit, at the end of whatever pass that unit runs:
140
+ * aliases when nested expandAlias processing unwinds, triggers when the line
141
+ * finishes. Passing the type keeps those independent, so an alias pass can't
142
+ * reap a trigger the current line is still walking past.
143
+ */
144
+ reapKilledTempItems(type?: 'alias' | 'trigger'): void;
40
145
  /** enableTrigger/disableTrigger/enableAlias/disableAlias with a numeric id.
41
- * False when no temp item of that id is live. */
146
+ * False when no temp item of that id is live — a killed one is still
147
+ * findable until the reap, but re-enabling it would resurrect it. */
42
148
  setTempItemEnabled(id: number, enabled: boolean): boolean;
43
149
  /** Whether a live temp item is enabled — backs isActive(id, type). */
44
150
  tempItemEnabled(id: number): boolean;
@@ -79,8 +185,30 @@ export declare class LuaRuntime implements IScriptingRuntime {
79
185
  * unmatched optional group (`undefined`) is left unset → nil, as before. */
80
186
  private pushMatchesTable;
81
187
  private exec;
188
+ private static readonly LUA_GCCOUNT;
189
+ private static readonly MEMORY_WARN_MB;
190
+ private static readonly MEMORY_SAMPLE_EVERY;
191
+ private memoryWarnIndex;
192
+ private dispatchesSinceMemorySample;
193
+ /** Sample Lua's own memory every few thousand dispatches and warn once per
194
+ * threshold crossed. Called from the two execution funnels; the counter
195
+ * keeps the `lua_gc` call itself off the hot path. */
196
+ private checkMemoryPressure;
197
+ /**
198
+ * Latch a WASM-level termination — see {@link fatal}. Emscripten throws its
199
+ * `ExitStatus` from the `proc_exit` import (C `exit()`) and a
200
+ * `WebAssembly.RuntimeError` from `abort()`; either means the module is
201
+ * gone, not that this one script failed, so the per-entity error report is
202
+ * wrong and repeating it every tick is worse.
203
+ *
204
+ * Returns true when `e` was such a termination, so the caller drops its own
205
+ * reporting. The one user-facing line is printed here, on the first one.
206
+ */
207
+ private markFatal;
82
208
  private execInner;
209
+ private execOnThread;
83
210
  private runChunk;
211
+ private runChunkOnThread;
84
212
  private static readonly FILE_DIALOG_SENTINEL;
85
213
  /** Threads suspended in invokeFileDialog, tracked so destroy() forgets
86
214
  * them (their registry refs die with the closed Lua state). */
@@ -112,6 +240,19 @@ export declare class LuaRuntime implements IScriptingRuntime {
112
240
  */
113
241
  startSpeedWalk(from: number, to: number): void;
114
242
  private execModule;
243
+ /**
244
+ * The native matcher behind `utf8.find`, and so behind `utf8.match`,
245
+ * `gmatch` and `gsub` — utf8.lua routes all four through one local, which
246
+ * calls this first and keeps its own matcher for whatever this declines.
247
+ * See utf8Patterns.ts for why (ASCII-only classes, ~35k VM instructions a
248
+ * find). Bound before the module loads, though the Lua side looks it up per
249
+ * call, so a runtime without it still works.
250
+ *
251
+ * The result crosses as a flat array — wasmoon hands JS arrays over
252
+ * 0-indexed — of `start, end, capture…`, or `false` for "no match" as
253
+ * distinct from nil's "not my pattern, use yours".
254
+ */
255
+ private installNativeUtf8Find;
115
256
  private dispatchingEvent;
116
257
  private readonly pendingEvents;
117
258
  emitEvent(event: string, args: unknown[]): void;
@@ -133,6 +274,14 @@ export declare class LuaRuntime implements IScriptingRuntime {
133
274
  * raises sysUnzipDone / sysUnzipError. Always fire-and-forget.
134
275
  */
135
276
  private runUnzipAsync;
277
+ /** Run `fn` off the current call stack, on the queue a blocked script still
278
+ * pumps. See the note in HttpService.emitLater: a microtask would never be
279
+ * reached by a script sitting in waitForEvent. */
280
+ private deferToTimerQueue;
281
+ /** The line the global `line` currently holds, so a caller can put it back
282
+ * after a nested feedTriggers has processed a line of its own. */
283
+ getCurrentLine(): string;
284
+ private currentLineText;
136
285
  setCurrentLine(line: string, _isPrompt: boolean): void;
137
286
  setCommand(command: string): void;
138
287
  dispatchSendRequest(text: string): boolean;
@@ -1,14 +1,2 @@
1
1
  import type { BindingContext } from './context';
2
- /**
3
- * Mudlet's map API: the 2D view, room and area CRUD, exits, doors, custom
4
- * lines, labels, highlights, selection, per-room styling, user data, and the
5
- * mapInfo / context-menu extension points.
6
- *
7
- * This is by far the widest binding surface in the runtime — it mirrors
8
- * Mudlet's map Lua API essentially one-for-one — so it lives in its own
9
- * module. The heavy lifting is all in MapStore behind `api.map`; the code
10
- * here is argument coercion and Mudlet's return-shape conventions (notably
11
- * alse where Mudlet returns nil, which Bridge.lua unpacks into the
12
- * documented (nil, errMsg) multi-returns).
13
- */
14
2
  export declare function installMapBindings({ lua, api, vfs, channel, emitEvent, unregisterCb, pushJsValue, registerRawGlobal, evaluateMapInfo, evaluateExitWeightFilter, }: BindingContext): void;