@markii/host 0.17.0-next.2 → 0.17.0-next.4

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 (67) hide show
  1. package/README.md +10 -2
  2. package/dist/browser-worker.classic.js +640 -0
  3. package/dist/browser-worker.js +40 -40
  4. package/dist/browser.d.ts +7 -3
  5. package/dist/browser.js +16 -4
  6. package/dist/conformance/host/05-export-html-pack-directive/expected.json +2 -0
  7. package/dist/conformance/host/06-install-pack-then-render/expected.json +6 -1
  8. package/dist/conformance/host/README.md +4 -2
  9. package/dist/export/note-export.d.ts +1 -1
  10. package/dist/host/adapter.d.ts +22 -25
  11. package/dist/host/conformance-runner.d.ts +1 -1
  12. package/dist/host/conformance-runner.js +21 -11
  13. package/dist/host/create-host.d.ts +29 -11
  14. package/dist/host/create-host.js +34 -23
  15. package/dist/host/editor-behavior.d.ts +2 -6
  16. package/dist/host/editor-behavior.js +5 -9
  17. package/dist/host/export-behavior.d.ts +6 -7
  18. package/dist/host/export-behavior.js +1 -1
  19. package/dist/host/labels.d.ts +5 -15
  20. package/dist/host/labels.js +8 -22
  21. package/dist/host/pack-archive.d.ts +4 -10
  22. package/dist/host/pack-archive.js +12 -21
  23. package/dist/host/pack-install.d.ts +11 -23
  24. package/dist/host/pack-install.js +26 -23
  25. package/dist/host/pack-load.d.ts +1 -2
  26. package/dist/host/pack-trust-behavior.js +1 -1
  27. package/dist/host/refresh-interval.d.ts +1 -3
  28. package/dist/host/refresh-interval.js +1 -3
  29. package/dist/host/run-behavior.d.ts +3 -4
  30. package/dist/host/run-behavior.js +23 -2
  31. package/dist/host/script-execution.d.ts +1 -1
  32. package/dist/html-engine.js +4 -4
  33. package/dist/index.d.ts +15 -80
  34. package/dist/index.js +18 -63
  35. package/dist/internal.d.ts +30 -0
  36. package/dist/internal.js +31 -0
  37. package/dist/node-fs.d.ts +0 -3
  38. package/dist/node-fs.js +1 -21
  39. package/dist/node-worker.d.ts +8 -0
  40. package/dist/node-worker.js +20678 -0
  41. package/dist/node.d.ts +3 -3
  42. package/dist/node.js +5 -4
  43. package/dist/packs/discover.d.ts +2 -0
  44. package/dist/packs/discover.js +2 -1
  45. package/dist/packs/pack-diagnostics.d.ts +6 -8
  46. package/dist/packs/pack-diagnostics.js +6 -8
  47. package/dist/packs/pack-paths.js +1 -2
  48. package/dist/packs/pack-render-registry.d.ts +9 -10
  49. package/dist/run/browser-isolate.d.ts +5 -5
  50. package/dist/run/browser-isolate.js +5 -1
  51. package/dist/run/isolate.d.ts +3 -1
  52. package/dist/run/net-bridge.d.ts +5 -5
  53. package/dist/run/net-bridge.js +7 -5
  54. package/dist/run/net-pinning.d.ts +2 -0
  55. package/dist/run/net-pinning.js +17 -3
  56. package/dist/run/net-provider.d.ts +3 -1
  57. package/dist/run/net-provider.js +40 -15
  58. package/dist/run/net-refusal.d.ts +4 -0
  59. package/dist/run/net-refusal.js +24 -0
  60. package/dist/run/node-isolate.d.ts +13 -21
  61. package/dist/run/node-isolate.js +31 -80
  62. package/dist/run/run-flow.d.ts +1 -2
  63. package/dist/run/run-host.d.ts +3 -3
  64. package/dist/run/worker-entry-browser.d.ts +24 -4
  65. package/dist/run/worker-entry-browser.js +99 -4
  66. package/dist/run/worker-entry.js +2 -0
  67. package/package.json +31 -12
@@ -1,21 +1,11 @@
1
1
  /**
2
- * `HostLabels`: the wording nouns every shared behavior module
3
- * (`./script-execution.ts`, `./refresh-interval.ts`) interpolates, and the
4
- * one concrete record per app this repo ships. Each app's `HostAdapter`
5
- * imports its record rather than inventing its own strings, which is what
6
- * collapses the survey's near-identical wording pairs (`tmp/W11-survey.md`,
7
- * section A4) into one template.
2
+ * The shared wording templates a host's shared behaviors interpolate
3
+ * `HostLabels` into live beside each behavior. This module holds the one
4
+ * label sentence that has no per-host noun. Each app owns its own
5
+ * `HostLabels` record and passes it as `HostAdapter.labels`.
8
6
  *
9
- * Pure and dependency-free: re-exported from `../browser.ts` as well as
10
- * `../index.ts` so VS Code's Node-free webview bundle can read it too.
7
+ * Pure and dependency-free.
11
8
  */
12
- import type { HostLabels } from './adapter.js';
13
- /** The VS Code extension's labels, taken from its current wording (`apps/vscode/src/script-execution.ts`, `apps/vscode/src/extension.ts`'s output channel). */
14
- export declare const VSCODE_LABELS: HostLabels;
15
- /** The Obsidian plugin's labels, taken from its current wording (`apps/obsidian/src/script-execution.ts`, docs/integration.md's host checklist). */
16
- export declare const OBSIDIAN_LABELS: HostLabels;
17
- /** The `markii` command line tool's labels: it has no persistent preview panel, and its diagnostics surface is stderr, gated behind `--verbose` for anything short of a failure (`apps/cli/src/main.ts`). */
18
- export declare const CLI_LABELS: HostLabels;
19
9
  /**
20
10
  * The quiet marker a rendered note shows when its `uses:` list names packs
21
11
  * this host has not installed (docs/packs.md). One sentence for every host;
@@ -1,25 +1,11 @@
1
- /** The VS Code extension's labels, taken from its current wording (`apps/vscode/src/script-execution.ts`, `apps/vscode/src/extension.ts`'s output channel). */
2
- export const VSCODE_LABELS = {
3
- appNoun: 'extension',
4
- previewNoun: 'the Markii preview',
5
- diagnosticsSurface: 'the Markii output channel',
6
- deviceNoun: 'this device',
7
- settingName: 'markii.scriptsDisabled',
8
- };
9
- /** The Obsidian plugin's labels, taken from its current wording (`apps/obsidian/src/script-execution.ts`, docs/integration.md's host checklist). */
10
- export const OBSIDIAN_LABELS = {
11
- appNoun: 'plugin',
12
- previewNoun: 'the Markii preview',
13
- diagnosticsSurface: 'the Markii diagnostics',
14
- deviceNoun: 'this device',
15
- };
16
- /** The `markii` command line tool's labels: it has no persistent preview panel, and its diagnostics surface is stderr, gated behind `--verbose` for anything short of a failure (`apps/cli/src/main.ts`). */
17
- export const CLI_LABELS = {
18
- appNoun: 'command line tool',
19
- previewNoun: 'the rendered note',
20
- diagnosticsSurface: 'stderr',
21
- deviceNoun: 'this device',
22
- };
1
+ /**
2
+ * The shared wording templates a host's shared behaviors interpolate
3
+ * `HostLabels` into live beside each behavior. This module holds the one
4
+ * label sentence that has no per-host noun. Each app owns its own
5
+ * `HostLabels` record and passes it as `HostAdapter.labels`.
6
+ *
7
+ * Pure and dependency-free.
8
+ */
23
9
  /**
24
10
  * The quiet marker a rendered note shows when its `uses:` list names packs
25
11
  * this host has not installed (docs/packs.md). One sentence for every host;
@@ -1,14 +1,8 @@
1
1
  import type { PackArchiveContents, PackArchiveError } from '@markii/pack';
2
2
  /** One line describing why `openPackArchive` rejected an archive, for a host's install-diagnostics wording. Passes `kind: 'zip'`'s and `kind: 'missing-entry'`'s message through verbatim (already specific); summarizes a manifest failure plainly. */
3
3
  export declare function describeArchiveError(error: PackArchiveError): string;
4
- /** The filesystem write seam `writeArchiveContents` needs (`./pack-install.ts`). Deliberately narrow: extracting an archive only ever creates files under a directory it first clears, it never reads back or deletes anything else. */
5
- export interface ArchiveExtractFs {
6
- /** Removes `absolutePath` and everything under it, or resolves quietly if it does not exist. Never rejects: a directory that fails to clear is treated the same as a fresh one, and the write that follows fails loudly on its own if the path is genuinely unusable. */
7
- readonly removeDirectory: (absolutePath: string) => Promise<void>;
8
- /** Creates a directory and any missing parents. May reject on a genuine I/O failure. */
9
- readonly makeDirectory: (absolutePath: string) => Promise<void>;
10
- readonly writeFile: (absolutePath: string, bytes: Uint8Array) => Promise<void>;
11
- }
4
+ /** Writes one file and creates its missing parent folders: `HostAdapter.writeFile`. */
5
+ export type ArchiveFileWriter = (absolutePath: string, bytes: Uint8Array) => Promise<void>;
12
6
  /**
13
7
  * Writes a validated archive's contents into `destinationDir`: `pack.json`
14
8
  * (the parsed manifest, re-serialized: `openPackArchive` keeps only the
@@ -17,7 +11,7 @@ export interface ArchiveExtractFs {
17
11
  * copy), `webview.js`, `webview.css` and `terminal.js` when the archive has them, and
18
12
  * `scripts/*` when it ships any. Does NOT clear `destinationDir` first;
19
13
  * a caller that needs a clean re-extract (a reinstall replacing an
20
- * existing pack) calls `fs.removeDirectory` itself before this.
14
+ * existing pack) removes the folder itself before this.
21
15
  *
22
16
  * PATH JAIL: `@markii/pack`'s `openPackArchive` has already rejected any
23
17
  * entry whose name escapes the archive (`../`, an absolute path) or
@@ -27,4 +21,4 @@ export interface ArchiveExtractFs {
27
21
  * `webview.css`, or a `scripts/`-relative Lua module name), never from
28
22
  * anything read back out of the zip's raw entry names.
29
23
  */
30
- export declare function writeArchiveContents(archive: PackArchiveContents, destinationDir: string, fs: ArchiveExtractFs): Promise<void>;
24
+ export declare function writeArchiveContents(archive: PackArchiveContents, destinationDir: string, writeFile: ArchiveFileWriter): Promise<void>;
@@ -1,24 +1,17 @@
1
1
  /**
2
2
  * `.mkp` pack archive handling, merged from `apps/vscode/src/packs/archive-packs.ts`
3
- * and `apps/obsidian/src/packs/archive-packs.ts` (survey finding A8). The
4
- * orchestrator diffed the two files directly (not through the survey's
5
- * earlier, unverified guess) and found them functionally byte-identical:
6
- * the same three exports (`describeArchiveError`, `writeArchiveContents`,
7
- * `ArchiveExtractFs`), the same implementation, the same path-jail
8
- * reasoning. They differed only in doc-comment wording (which host's
9
- * install command they described) and one em dash in a comment, which
10
- * carries no behavior. See `tmp/W11-phase1b.md` for the exact diff output.
3
+ * and `apps/obsidian/src/packs/archive-packs.ts`. It exports
4
+ * `describeArchiveError`, `writeArchiveContents` and `ArchiveFileWriter`.
11
5
  *
12
6
  * This module bridges `@markii/pack`'s in-memory archive reader
13
7
  * (`openPackArchive`, never modified here) into the write a pack install
14
8
  * needs: extracting a validated archive's contents onto disk, under
15
9
  * whichever install root the calling host names (`HostPackSource.installRoot`).
16
10
  *
17
- * Environment-free (`ArchiveExtractFs` is an injected seam). The real
18
- * Node-backed one, `createNodeArchiveExtractFs`, lives in `../node-fs.ts`
19
- * and is exported from `@markii/host/node`.
11
+ * Environment-free: the write goes through the host adapter's `writeFile`,
12
+ * which creates any missing parent folder.
20
13
  */
21
- import { dirnamePath, joinPath } from '../path-util.js';
14
+ import { joinPath } from '../path-util.js';
22
15
  /** One line describing why `openPackArchive` rejected an archive, for a host's install-diagnostics wording. Passes `kind: 'zip'`'s and `kind: 'missing-entry'`'s message through verbatim (already specific); summarizes a manifest failure plainly. */
23
16
  export function describeArchiveError(error) {
24
17
  if (error.kind === 'zip')
@@ -35,7 +28,7 @@ export function describeArchiveError(error) {
35
28
  * copy), `webview.js`, `webview.css` and `terminal.js` when the archive has them, and
36
29
  * `scripts/*` when it ships any. Does NOT clear `destinationDir` first;
37
30
  * a caller that needs a clean re-extract (a reinstall replacing an
38
- * existing pack) calls `fs.removeDirectory` itself before this.
31
+ * existing pack) removes the folder itself before this.
39
32
  *
40
33
  * PATH JAIL: `@markii/pack`'s `openPackArchive` has already rejected any
41
34
  * entry whose name escapes the archive (`../`, an absolute path) or
@@ -45,24 +38,22 @@ export function describeArchiveError(error) {
45
38
  * `webview.css`, or a `scripts/`-relative Lua module name), never from
46
39
  * anything read back out of the zip's raw entry names.
47
40
  */
48
- export async function writeArchiveContents(archive, destinationDir, fs) {
49
- await fs.makeDirectory(destinationDir);
41
+ export async function writeArchiveContents(archive, destinationDir, writeFile) {
50
42
  const encoder = new TextEncoder();
51
- await fs.writeFile(joinPath(destinationDir, 'pack.json'), encoder.encode(JSON.stringify(archive.manifest, null, 2) + '\n'));
52
- await fs.writeFile(joinPath(destinationDir, 'webview.js'), archive.scriptBytes);
43
+ await writeFile(joinPath(destinationDir, 'pack.json'), encoder.encode(JSON.stringify(archive.manifest, null, 2) + '\n'));
44
+ await writeFile(joinPath(destinationDir, 'webview.js'), archive.scriptBytes);
53
45
  if (archive.stylesheetBytes !== undefined) {
54
- await fs.writeFile(joinPath(destinationDir, 'webview.css'), archive.stylesheetBytes);
46
+ await writeFile(joinPath(destinationDir, 'webview.css'), archive.stylesheetBytes);
55
47
  }
56
48
  if (archive.terminalScriptBytes !== undefined) {
57
- await fs.writeFile(joinPath(destinationDir, 'terminal.js'), archive.terminalScriptBytes);
49
+ await writeFile(joinPath(destinationDir, 'terminal.js'), archive.terminalScriptBytes);
58
50
  }
59
51
  const moduleNames = Object.keys(archive.scriptModules);
60
52
  if (moduleNames.length > 0) {
61
53
  const scriptsDir = joinPath(destinationDir, 'scripts');
62
54
  for (const name of moduleNames) {
63
55
  const target = joinPath(scriptsDir, name);
64
- await fs.makeDirectory(dirnamePath(target));
65
- await fs.writeFile(target, archive.scriptModules[name]);
56
+ await writeFile(target, archive.scriptModules[name]);
66
57
  }
67
58
  }
68
59
  }
@@ -1,5 +1,5 @@
1
- import type { ArchiveExtractFs } from './pack-archive.js';
2
1
  import type { HostAdapter, HostLabels } from './adapter.js';
2
+ import type { InstallPackOutcome as HostInstallPackOutcome } from './create-host.js';
3
3
  /** Whether a directory already exists under the install root, injected so this module needs no real disk to test. */
4
4
  export type PackDirectoryExists = (absolutePath: string) => Promise<boolean>;
5
5
  export interface InstallPackFromArchiveOptions {
@@ -9,7 +9,8 @@ export interface InstallPackFromArchiveOptions {
9
9
  /** This host's own pack-install directory (`HostPackSource.installRoot()`). Each installed pack gets a subdirectory named by its namespace, since two packs can never share one (docs/packs.md's collision rule), which makes "is this namespace already installed" a plain directory check. */
10
10
  readonly installRoot: string;
11
11
  readonly exists: PackDirectoryExists;
12
- readonly extractFs: ArchiveExtractFs;
12
+ /** Removes the installed folder before a replacing install (`HostPackSource.removeFolder`). Called only when the namespace is already installed. */
13
+ readonly removeFolder: (absolutePath: string) => Promise<void>;
13
14
  /**
14
15
  * Namespaces this install may never claim (`HostPackSource.reservedNamespaces()`).
15
16
  * Empty for a host with no reserved set (VS Code). Checked before the
@@ -17,22 +18,10 @@ export interface InstallPackFromArchiveOptions {
17
18
  */
18
19
  readonly reservedNamespaces: ReadonlySet<string>;
19
20
  }
20
- export type InstallPackOutcome = {
21
- readonly kind: 'installed';
22
- readonly packName: string;
23
- readonly installedDir: string;
24
- readonly replaced: boolean;
25
- } | {
26
- readonly kind: 'declined';
27
- readonly step: 'consent' | 'replace';
28
- readonly packName: string;
29
- } | {
30
- readonly kind: 'reserved';
31
- readonly packName: string;
32
- } | {
33
- readonly kind: 'rejected';
34
- readonly reason: string;
35
- };
21
+ /** What one install attempt produced, in the shape `createMarkiiHost(...).installPack` returns (minus `unsupported`, which only the host behavior can say). */
22
+ export type InstallAttemptOutcome = Exclude<HostInstallPackOutcome, {
23
+ readonly kind: 'unsupported';
24
+ }>;
36
25
  /**
37
26
  * Never throws: a validation failure, a declined prompt, or a write
38
27
  * failure all come back as a structured outcome. Order: validate the
@@ -47,7 +36,7 @@ export type InstallPackOutcome = {
47
36
  * a host that can only show a non-blocking surface must deny rather than
48
37
  * assume yes.
49
38
  */
50
- export declare function installPackFromArchive(adapter: HostAdapter, options: InstallPackFromArchiveOptions): Promise<InstallPackOutcome>;
39
+ export declare function installPackFromArchive(adapter: HostAdapter, options: InstallPackFromArchiveOptions): Promise<InstallAttemptOutcome>;
51
40
  /**
52
41
  * The consent prompt's wording (AGENTS.md's product principles: "clean is
53
42
  * not silent," and a consent step must say plainly what it authorizes).
@@ -67,8 +56,7 @@ export declare function installConsentMessage(packName: string, labels: HostLabe
67
56
  /**
68
57
  * The collision confirmation's wording: a namespace already installed,
69
58
  * asked before replacing it. CANONICAL WORDING: "removes" (VS Code's prior
70
- * wording) over Obsidian's prior "deletes" — see `tmp/W11-phase1b.md`'s
71
- * before/after table for the exact strings this replaces.
59
+ * wording) over Obsidian's prior "deletes".
72
60
  */
73
61
  export declare function installReplaceConfirmMessage(packName: string): string;
74
62
  /**
@@ -83,6 +71,6 @@ export declare function installReplaceConfirmMessage(packName: string): string;
83
71
  * `markii.packs` before anything loads it), so it doesn't. Defaults to
84
72
  * `false`.
85
73
  */
86
- export declare function installPackMessage(outcome: InstallPackOutcome, archivePath: string, labels: HostLabels, reloadsAutomatically?: boolean): string;
74
+ export declare function installPackMessage(outcome: HostInstallPackOutcome, archivePath: string, labels: HostLabels, reloadsAutomatically?: boolean): string;
87
75
  /** The full diagnostics-surface detail for one install attempt (AGENTS.md's "clean is not silent": the other of a failure's two homes). */
88
- export declare function installPackDiagnosticLines(outcome: InstallPackOutcome, archivePath: string): string[];
76
+ export declare function installPackDiagnosticLines(outcome: InstallAttemptOutcome, archivePath: string): string[];
@@ -1,11 +1,9 @@
1
1
  /**
2
2
  * The `.mkp` pack-install decision logic, merged from
3
3
  * `apps/vscode/src/packs/install-pack.ts` and
4
- * `apps/obsidian/src/packs/install-pack.ts` (survey finding A2: the single
5
- * biggest duplication finding in the survey — identical function names,
6
- * identical types, near-identical wording, differing only in that
7
- * Obsidian refuses a bundled-namespace archive before the consent prompt
8
- * and VS Code has no such concept).
4
+ * `apps/obsidian/src/packs/install-pack.ts`. Obsidian refuses a
5
+ * bundled-namespace archive before the consent prompt and VS Code has no
6
+ * such concept.
9
7
  *
10
8
  * `reservedNamespaces` generalizes Obsidian's `bundledNamespaces`: it
11
9
  * defaults to empty, so VS Code's behavior is byte-identical to before
@@ -41,7 +39,7 @@ function describeThrown(err) {
41
39
  * assume yes.
42
40
  */
43
41
  export async function installPackFromArchive(adapter, options) {
44
- const { archiveBytes, archivePath, installRoot, exists, extractFs, reservedNamespaces, } = options;
42
+ const { archiveBytes, archivePath, installRoot, exists, removeFolder, reservedNamespaces, } = options;
45
43
  const opened = await openPackArchive(archiveBytes);
46
44
  if (!opened.ok) {
47
45
  return {
@@ -51,7 +49,7 @@ export async function installPackFromArchive(adapter, options) {
51
49
  }
52
50
  const packName = opened.archive.manifest.name;
53
51
  if (reservedNamespaces.has(packName)) {
54
- return { kind: 'reserved', packName };
52
+ return { kind: 'reserved', namespace: packName };
55
53
  }
56
54
  let consented;
57
55
  try {
@@ -67,7 +65,7 @@ export async function installPackFromArchive(adapter, options) {
67
65
  return { kind: 'rejected', reason: describeThrown(err) };
68
66
  }
69
67
  if (!consented) {
70
- return { kind: 'declined', step: 'consent', packName };
68
+ return { kind: 'declined', step: 'consent', namespace: packName };
71
69
  }
72
70
  const targetDir = joinPath(installRoot, packName);
73
71
  let alreadyInstalled;
@@ -93,13 +91,14 @@ export async function installPackFromArchive(adapter, options) {
93
91
  return { kind: 'rejected', reason: describeThrown(err) };
94
92
  }
95
93
  if (!proceed) {
96
- return { kind: 'declined', step: 'replace', packName };
94
+ return { kind: 'declined', step: 'replace', namespace: packName };
97
95
  }
98
96
  replaced = true;
99
97
  }
100
98
  try {
101
- await extractFs.removeDirectory(targetDir);
102
- await writeArchiveContents(opened.archive, targetDir, extractFs);
99
+ if (alreadyInstalled)
100
+ await removeFolder(targetDir);
101
+ await writeArchiveContents(opened.archive, targetDir, (path, bytes) => adapter.writeFile(path, bytes));
103
102
  }
104
103
  catch (err) {
105
104
  return {
@@ -108,7 +107,12 @@ export async function installPackFromArchive(adapter, options) {
108
107
  };
109
108
  }
110
109
  await trustInstalledPack(adapter, packName, targetDir);
111
- return { kind: 'installed', packName, installedDir: targetDir, replaced };
110
+ return {
111
+ kind: 'installed',
112
+ namespace: packName,
113
+ installedDir: targetDir,
114
+ replaced,
115
+ };
112
116
  }
113
117
  /**
114
118
  * The consent prompt's wording (AGENTS.md's product principles: "clean is
@@ -131,8 +135,7 @@ export function installConsentMessage(packName, labels) {
131
135
  /**
132
136
  * The collision confirmation's wording: a namespace already installed,
133
137
  * asked before replacing it. CANONICAL WORDING: "removes" (VS Code's prior
134
- * wording) over Obsidian's prior "deletes" — see `tmp/W11-phase1b.md`'s
135
- * before/after table for the exact strings this replaces.
138
+ * wording) over Obsidian's prior "deletes".
136
139
  */
137
140
  export function installReplaceConfirmMessage(packName) {
138
141
  return `A pack named "${packName}" is already installed. Replacing it removes the installed copy and cannot be undone.`;
@@ -150,21 +153,21 @@ export function installReplaceConfirmMessage(packName) {
150
153
  * `false`.
151
154
  */
152
155
  export function installPackMessage(outcome, archivePath, labels, reloadsAutomatically = false) {
153
- if (outcome.kind === 'rejected') {
156
+ if (outcome.kind === 'rejected' || outcome.kind === 'unsupported') {
154
157
  return `Markii: could not install a pack from "${archivePath}". Open ${labels.diagnosticsSurface} for details.`;
155
158
  }
156
159
  if (outcome.kind === 'reserved') {
157
- return `Markii: "${outcome.packName}" is built in and cannot be installed. Nothing was installed.`;
160
+ return `Markii: "${outcome.namespace}" is built in and cannot be installed. Nothing was installed.`;
158
161
  }
159
162
  if (outcome.kind === 'declined') {
160
163
  return outcome.step === 'consent'
161
- ? `Markii: install of "${outcome.packName}" cancelled. Nothing was installed.`
162
- : `Markii: install of "${outcome.packName}" cancelled. The existing pack was kept.`;
164
+ ? `Markii: install of "${outcome.namespace}" cancelled. Nothing was installed.`
165
+ : `Markii: install of "${outcome.namespace}" cancelled. The existing pack was kept.`;
163
166
  }
164
167
  const reloadSuffix = reloadsAutomatically ? ' Markii packs reloaded.' : '';
165
168
  return outcome.replaced
166
- ? `Markii: reinstalled pack "${outcome.packName}".${reloadSuffix}`
167
- : `Markii: installed pack "${outcome.packName}".${reloadSuffix}`;
169
+ ? `Markii: reinstalled pack "${outcome.namespace}".${reloadSuffix}`
170
+ : `Markii: installed pack "${outcome.namespace}".${reloadSuffix}`;
168
171
  }
169
172
  /** The full diagnostics-surface detail for one install attempt (AGENTS.md's "clean is not silent": the other of a failure's two homes). */
170
173
  export function installPackDiagnosticLines(outcome, archivePath) {
@@ -175,15 +178,15 @@ export function installPackDiagnosticLines(outcome, archivePath) {
175
178
  }
176
179
  if (outcome.kind === 'reserved') {
177
180
  return [
178
- `Install pack from file refused "${archivePath}": pack "${outcome.packName}" is a reserved namespace and cannot be installed.`,
181
+ `Install pack from file refused "${archivePath}": pack "${outcome.namespace}" is a reserved namespace and cannot be installed.`,
179
182
  ];
180
183
  }
181
184
  if (outcome.kind === 'declined') {
182
185
  return [
183
- `Install pack from file cancelled for "${archivePath}" (pack "${outcome.packName}") at the ${outcome.step} step; nothing was installed.`,
186
+ `Install pack from file cancelled for "${archivePath}" (pack "${outcome.namespace}") at the ${outcome.step} step; nothing was installed.`,
184
187
  ];
185
188
  }
186
189
  return [
187
- `Install pack from file installed pack "${outcome.packName}" from "${archivePath}" to ${outcome.installedDir}${outcome.replaced ? ' (replacing the previous install)' : ''}.`,
190
+ `Install pack from file installed pack "${outcome.namespace}" from "${archivePath}" to ${outcome.installedDir}${outcome.replaced ? ' (replacing the previous install)' : ''}.`,
188
191
  ];
189
192
  }
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * The shared discovery path behind `loadPacks`, merged from
3
3
  * `apps/vscode/src/packs/discover-configured-packs.ts` and
4
- * `apps/obsidian/src/packs/discover-configured-packs.ts` (survey finding
5
- * A3) plus the discovery-and-namespace-collection half both apps'
4
+ * `apps/obsidian/src/packs/discover-configured-packs.ts` plus the discovery-and-namespace-collection half both apps'
6
5
  * `packs/pack-context.ts` also duplicate on top of it.
7
6
  *
8
7
  * Pack POLICY — which folders are even candidates before any content is
@@ -12,7 +12,7 @@
12
12
  * already asked), enabling a present folder asks the same prompt, and
13
13
  * disabling or removing withdraws the entry.
14
14
  *
15
- * A host that declares `trust: 'none'` (or omits the field) trusts every
15
+ * A host that declares `trust: 'none'` trusts every
16
16
  * folder its own policy authorizes, and every function here is inert for
17
17
  * it. Neutral on purpose: only the adapter's file and state primitives
18
18
  * are used.
@@ -3,9 +3,7 @@
3
3
  * merged from `apps/vscode/src/refresh-interval.ts` (the input-box
4
4
  * parse/validate half) and `apps/obsidian/src/local-settings.ts`'s
5
5
  * `refreshIntervalMsFromSeconds`/`MIN_REFRESH_INTERVAL_SECONDS` (the
6
- * read-side clamp half) — survey finding A7: the constant was duplicated
7
- * verbatim, with Obsidian's own comment noting it mirrors VS Code's, and
8
- * the clamp-not-reject policy was duplicated as both prose and behavior.
6
+ * read-side clamp half).
9
7
  *
10
8
  * The policy: a positive value under `MIN_REFRESH_INTERVAL_SECONDS` is
11
9
  * still ACCEPTED as typed (a host's own settings UI must not silently
@@ -3,9 +3,7 @@
3
3
  * merged from `apps/vscode/src/refresh-interval.ts` (the input-box
4
4
  * parse/validate half) and `apps/obsidian/src/local-settings.ts`'s
5
5
  * `refreshIntervalMsFromSeconds`/`MIN_REFRESH_INTERVAL_SECONDS` (the
6
- * read-side clamp half) — survey finding A7: the constant was duplicated
7
- * verbatim, with Obsidian's own comment noting it mirrors VS Code's, and
8
- * the clamp-not-reject policy was duplicated as both prose and behavior.
6
+ * read-side clamp half).
9
7
  *
10
8
  * The policy: a positive value under `MIN_REFRESH_INTERVAL_SECONDS` is
11
9
  * still ACCEPTED as typed (a host's own settings UI must not silently
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * The `run` behavior's host-shaped seams, built from a `HostAdapter`:
3
- * this is where survey findings A1 and A6 stop being duplicated three
4
- * times over.
3
+ * the grant prompts and the isolate spawn live here once for every host.
5
4
  *
6
5
  * A1: `apps/vscode/src/preview-panel.ts`'s `promptHostAdapter` /
7
6
  * `promptUnknownHostsAdapter` / `promptManyHostsAdapter`,
@@ -24,7 +23,7 @@
24
23
  * `runOnce` (the existing, already-shared grant-flow-plus-spawn
25
24
  * orchestration) and writes the `RunTrace` through `adapter.now()` rather
26
25
  * than `Date.now()`, so a fixed clock in a test fake makes the trace
27
- * byte-stable (batch 11 Phase 3's requirement).
26
+ * byte-stable.
28
27
  */
29
28
  import type { RunTrigger } from '@markii/runtime';
30
29
  import { type PromptHost, type PromptManyHosts, type PromptUnknownHosts } from '../run/grant-flow.js';
@@ -54,7 +53,7 @@ export declare function promptsFromAdapter(adapter: HostAdapter): {
54
53
  };
55
54
  /**
56
55
  * The one `spawnRun` adapter every Node host (`apps/vscode`, `apps/cli`)
57
- * used to duplicate (survey A6), plus the browser-isolate path Obsidian's
56
+ * needs, plus the browser-isolate path Obsidian's
58
57
  * own `spawnRun` wrapper needed. Dispatches on `HostIsolate`'s
59
58
  * discriminated union: a `'node'` host supplies only a worker path; a
60
59
  * `'browser'` host supplies an async, disposable `IsolateSpawner` factory,
@@ -63,9 +63,23 @@ function noIsolateResult(cacheSnapshot) {
63
63
  cacheSnapshot,
64
64
  };
65
65
  }
66
+ /** A synthetic `RunResult` for a node isolate whose worker file cannot be located. The run never throws. */
67
+ function workerMissingResult(cacheSnapshot, error) {
68
+ return {
69
+ values: {},
70
+ failures: [
71
+ {
72
+ name: '<document>',
73
+ message: `the script worker could not be located: ${error instanceof Error ? error.message : String(error)}`,
74
+ kind: 'capability-denied',
75
+ },
76
+ ],
77
+ cacheSnapshot,
78
+ };
79
+ }
66
80
  /**
67
81
  * The one `spawnRun` adapter every Node host (`apps/vscode`, `apps/cli`)
68
- * used to duplicate (survey A6), plus the browser-isolate path Obsidian's
82
+ * needs, plus the browser-isolate path Obsidian's
69
83
  * own `spawnRun` wrapper needed. Dispatches on `HostIsolate`'s
70
84
  * discriminated union: a `'node'` host supplies only a worker path; a
71
85
  * `'browser'` host supplies an async, disposable `IsolateSpawner` factory,
@@ -75,9 +89,16 @@ export async function spawnRunViaAdapter(isolate, options) {
75
89
  if (!isolate)
76
90
  return noIsolateResult(options.cacheSnapshot);
77
91
  if (isolate.kind === 'node') {
92
+ let workerPath;
93
+ try {
94
+ workerPath = isolate.workerPath();
95
+ }
96
+ catch (error) {
97
+ return workerMissingResult(options.cacheSnapshot, error);
98
+ }
78
99
  return spawnRunHost({
79
100
  ...options,
80
- workerPath: isolate.workerPath(),
101
+ workerPath,
81
102
  spawnIsolate: isolate.spawnIsolate,
82
103
  });
83
104
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The device-level script-execution switch and every sentence it can
3
3
  * produce, merged from `apps/vscode/src/script-execution.ts` and
4
- * `apps/obsidian/src/script-execution.ts` (survey finding A4: same file
4
+ * `apps/obsidian/src/script-execution.ts` (same file
5
5
  * name, same four exports, doc comments sharing the same "WHAT IT IS /
6
6
  * WHY DEVICE-LOCAL / WHY THE WORDING LIVES HERE" structure, differing
7
7
  * only in a handful of words).
@@ -1,9 +1,9 @@
1
1
  /**
2
- * The `@markii/html`-backed `HtmlExportEngine`. Kept out of the main entry
3
- * (it is the one module that carries the static HTML renderer); a host that
2
+ * The `@markii/host/html-engine` entry: the `@markii/html`-backed
3
+ * `HtmlExportEngine`. Kept out of the main and `/node` entries (it is the
4
+ * one module that needs the optional `@markii/html` peer); a host that
4
5
  * exports HTML builds it here and hands it to `createMarkiiHost`'s
5
- * `htmlEngine` option or to `buildNoteExport`. Reached through
6
- * `@markii/host/node`.
6
+ * `htmlEngine` option or to `buildNoteExport`.
7
7
  */
8
8
  import { escapeHtml, exportHtmlDocument, renderMarkToHtml } from '@markii/html';
9
9
  import { defaultHtmlRegistry } from '@markii/html/components';