@pinepaper.studio/mcp-server 1.6.29 → 1.6.31

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 (90) hide show
  1. package/README.de.md +1 -1
  2. package/README.es.md +1 -1
  3. package/README.fr.md +1 -1
  4. package/README.hi.md +1 -1
  5. package/README.ja.md +1 -1
  6. package/README.ko.md +1 -1
  7. package/README.md +104 -1
  8. package/README.pt-BR.md +1 -1
  9. package/README.zh-CN.md +1 -1
  10. package/SECURITY.md +15 -0
  11. package/dist/browser/puppeteer-controller.d.ts +40 -0
  12. package/dist/browser/puppeteer-controller.d.ts.map +1 -1
  13. package/dist/browser/puppeteer-controller.js +179 -27
  14. package/dist/browser/puppeteer-controller.js.map +1 -1
  15. package/dist/build-info.json +2 -2
  16. package/dist/i18n/i18n-manager.d.ts.map +1 -1
  17. package/dist/i18n/i18n-manager.js +57 -3
  18. package/dist/i18n/i18n-manager.js.map +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +15 -9
  21. package/dist/index.js.map +1 -1
  22. package/dist/ontology/upstream-vocabulary.d.ts +2 -2
  23. package/dist/ontology/upstream-vocabulary.js +2 -2
  24. package/dist/ontology/vocabulary.d.ts.map +1 -1
  25. package/dist/ontology/vocabulary.js +2 -0
  26. package/dist/ontology/vocabulary.js.map +1 -1
  27. package/dist/tools/batch-op-names.d.ts +27 -0
  28. package/dist/tools/batch-op-names.d.ts.map +1 -0
  29. package/dist/tools/batch-op-names.js +102 -0
  30. package/dist/tools/batch-op-names.js.map +1 -0
  31. package/dist/tools/definitions.d.ts.map +1 -1
  32. package/dist/tools/definitions.js +31 -22
  33. package/dist/tools/definitions.js.map +1 -1
  34. package/dist/tools/effect-check.d.ts +1 -1
  35. package/dist/tools/effect-check.d.ts.map +1 -1
  36. package/dist/tools/effect-check.js.map +1 -1
  37. package/dist/tools/engine-lists.d.ts +6 -3
  38. package/dist/tools/engine-lists.d.ts.map +1 -1
  39. package/dist/tools/engine-lists.js +16 -3
  40. package/dist/tools/engine-lists.js.map +1 -1
  41. package/dist/tools/engine-methods.d.ts.map +1 -1
  42. package/dist/tools/engine-methods.js +3 -0
  43. package/dist/tools/engine-methods.js.map +1 -1
  44. package/dist/tools/engine-surface.d.ts +1 -1
  45. package/dist/tools/engine-surface.d.ts.map +1 -1
  46. package/dist/tools/engine-surface.js +20 -4
  47. package/dist/tools/engine-surface.js.map +1 -1
  48. package/dist/tools/handlers/generate.d.ts +2 -2
  49. package/dist/tools/handlers/generate.d.ts.map +1 -1
  50. package/dist/tools/handlers/generate.js +4 -4
  51. package/dist/tools/handlers/generate.js.map +1 -1
  52. package/dist/tools/handlers.d.ts +7 -0
  53. package/dist/tools/handlers.d.ts.map +1 -1
  54. package/dist/tools/handlers.js +70 -3
  55. package/dist/tools/handlers.js.map +1 -1
  56. package/dist/tools/lane.d.ts +8 -2
  57. package/dist/tools/lane.d.ts.map +1 -1
  58. package/dist/tools/lane.js +22 -3
  59. package/dist/tools/lane.js.map +1 -1
  60. package/dist/tools/p5-compat/p5-helpers.d.ts +6 -17
  61. package/dist/tools/p5-compat/p5-helpers.d.ts.map +1 -1
  62. package/dist/tools/p5-compat/p5-helpers.js +8 -1
  63. package/dist/tools/p5-compat/p5-helpers.js.map +1 -1
  64. package/dist/tools/shape-params.d.ts +2 -2
  65. package/dist/tools/shape-params.d.ts.map +1 -1
  66. package/dist/tools/shape-params.js +5 -2
  67. package/dist/tools/shape-params.js.map +1 -1
  68. package/dist/tools/stick-vocabulary.d.ts +1 -1
  69. package/dist/tools/stick-vocabulary.js +1 -1
  70. package/dist/types/code-generator.d.ts +17 -1
  71. package/dist/types/code-generator.d.ts.map +1 -1
  72. package/dist/types/code-generator.js +187 -90
  73. package/dist/types/code-generator.js.map +1 -1
  74. package/dist/types/schemas.d.ts +11 -0
  75. package/dist/types/schemas.d.ts.map +1 -1
  76. package/dist/types/schemas.js +5 -1
  77. package/dist/types/schemas.js.map +1 -1
  78. package/dist/utils/capabilities.d.ts.map +1 -1
  79. package/dist/utils/capabilities.js +3 -1
  80. package/dist/utils/capabilities.js.map +1 -1
  81. package/dist/utils/env.d.ts +47 -6
  82. package/dist/utils/env.d.ts.map +1 -1
  83. package/dist/utils/env.js +52 -6
  84. package/dist/utils/env.js.map +1 -1
  85. package/dist/utils/safe-fetch.d.ts +1 -0
  86. package/dist/utils/safe-fetch.d.ts.map +1 -1
  87. package/dist/utils/safe-fetch.js +3 -4
  88. package/dist/utils/safe-fetch.js.map +1 -1
  89. package/manifest.json +2 -2
  90. package/package.json +2 -2
package/README.de.md CHANGED
@@ -74,7 +74,7 @@ Bitten Sie Ihre KI zum Beispiel:
74
74
  **Lokal ist kostenlos und vollständig.** Alle Tools funktionieren, wenn Sie
75
75
  diesen Server selbst betreiben. Es gibt keine reduzierte Stufe.
76
76
 
77
- Benötigt: Node 18 oder neuer, rund **320 MB** Speicherplatz (Puppeteer lädt
77
+ Benötigt: Node 22.13 oder neuer, rund **320 MB** Speicherplatz (Puppeteer lädt
78
78
  Chrome herunter) und etwa 1 GB Arbeitsspeicher, solange ein Job läuft. Auf
79
79
  einem Laptop ist das unproblematisch, auf einem kleinen VPS oder einem
80
80
  gesperrten Arbeitsrechner eher nicht.
package/README.es.md CHANGED
@@ -74,7 +74,7 @@ Prueba a pedirle a tu IA:
74
74
  **Local es gratis y completo.** Todas las herramientas funcionan si ejecuta
75
75
  este servidor usted mismo. No hay una versión reducida.
76
76
 
77
- Necesita: Node 18 o superior, unos **320 MB** de disco (Puppeteer descarga
77
+ Necesita: Node 22.13 o superior, unos **320 MB** de disco (Puppeteer descarga
78
78
  Chrome) y alrededor de 1 GB de memoria mientras se ejecuta un trabajo. En un
79
79
  portátil no supone problema; en un VPS pequeño o un equipo de trabajo
80
80
  restringido, sí.
package/README.fr.md CHANGED
@@ -74,7 +74,7 @@ Essayez de demander à votre IA :
74
74
  **Le local est gratuit et complet.** Tous les outils fonctionnent si vous
75
75
  exécutez ce serveur vous-même. Il n'existe pas de version réduite.
76
76
 
77
- Il faut : Node 18 ou plus récent, environ **320 Mo** de disque (Puppeteer
77
+ Il faut : Node 22.13 ou plus récent, environ **320 Mo** de disque (Puppeteer
78
78
  télécharge Chrome) et à peu près 1 Go de mémoire pendant l'exécution d'une
79
79
  tâche. Sur un ordinateur portable, cela passe ; sur un petit VPS ou une machine
80
80
  de travail verrouillée, moins.
package/README.hi.md CHANGED
@@ -74,7 +74,7 @@ npm install -g @pinepaper.studio/mcp-server
74
74
  **लोकल मुफ़्त और पूरा है।** अगर आप यह सर्वर खुद चलाते हैं तो सारे टूल काम करते
75
75
  हैं। कोई सीमित संस्करण नहीं है।
76
76
 
77
- ज़रूरत: Node 18 या नया, लगभग **320 MB** डिस्क (Puppeteer क्रोम डाउनलोड करता है)
77
+ ज़रूरत: Node 22.13 या नया, लगभग **320 MB** डिस्क (Puppeteer क्रोम डाउनलोड करता है)
78
78
  और जॉब चलने के दौरान करीब 1 GB मेमोरी। लैपटॉप पर यह ठीक है; छोटे VPS या
79
79
  प्रतिबंधित ऑफ़िस मशीन पर मुश्किल।
80
80
 
package/README.ja.md CHANGED
@@ -74,7 +74,7 @@ AI にこう頼んでみてください:
74
74
  **ローカルは無料で、機能に制限はありません。** 自分でこのサーバーを動かせば、
75
75
  すべてのツールが使えます。制限版はありません。
76
76
 
77
- 必要なもの: Node 18 以降、約 **320 MB** のディスク (Puppeteer が Chrome を
77
+ 必要なもの: Node 22.13 以降、約 **320 MB** のディスク (Puppeteer が Chrome を
78
78
  ダウンロードします)、ジョブ実行中は約 1 GB のメモリ。ノート PC なら問題あり
79
79
  ませんが、小さな VPS や制限のある業務用マシンでは厳しいことがあります。
80
80
 
package/README.ko.md CHANGED
@@ -74,7 +74,7 @@ AI에게 이렇게 말해 보세요:
74
74
  **로컬은 무료이며 기능 제한이 없습니다.** 이 서버를 직접 실행하면 모든 도구가
75
75
  동작합니다. 축소된 버전은 없습니다.
76
76
 
77
- 필요한 것: Node 18 이상, 약 **320 MB** 디스크 (Puppeteer가 Chrome을
77
+ 필요한 것: Node 22.13 이상, 약 **320 MB** 디스크 (Puppeteer가 Chrome을
78
78
  내려받습니다), 작업 실행 중 약 1 GB 메모리. 노트북에서는 문제가 없지만 작은
79
79
  VPS나 제한된 업무용 컴퓨터에서는 부담스러울 수 있습니다.
80
80
 
package/README.md CHANGED
@@ -48,7 +48,7 @@ What it needs:
48
48
 
49
49
  | | |
50
50
  |---|---|
51
- | Node | 18 or newer |
51
+ | Node | 22.13 or newer (any supported Node release: 22, 24 or 26) |
52
52
  | Disk | With Puppeteer (below), Chrome is downloaded on install — roughly **320 MB** per version |
53
53
  | Memory | a Chrome process plus the Studio canvas, so budget ~1 GB while a job runs |
54
54
 
@@ -279,6 +279,108 @@ If you do not want an agent executing anything, `code` mode is a first-class pat
279
279
  - The npm package starts no process. Two export features use `ffprobe` and `ffmpeg`: checking a saved video against what the studio encoded, and joining the cached segments of a `perScene` export (`ffmpeg -f concat`). Only the Claude Desktop extension runs them, through a small runner it bundles that allows those two binaries alone, via `execFile` (no shell, argument lists the server writes, absolute paths). From npm, the check is skipped and said so, and a `perScene` export runs whole.
280
280
  - The server itself fetches the images, SVGs and media a tool call names by URL, and reads local files named by path. URLs that resolve to loopback, private, link-local or reserved addresses are refused, redirects included, and so are cloud metadata addresses. Local files are read only with `PINEPAPER_ALLOW_LOCAL_FILES=1`, and then only from the working directory, the export folder, and folders listed in `PINEPAPER_ASSET_DIRS` (the export folder is readable without the switch). `PINEPAPER_ALLOW_PRIVATE_NETWORK=1` allows local and LAN hosts. Metadata addresses stay refused even with it set.
281
281
 
282
+ ## What's new in 1.6.31
283
+
284
+ ### Changed
285
+
286
+ - **Requires Node 22.13 or newer.** Node 18 and 20 are end of life, and this
287
+ package's dependencies already needed 20.19 or 22.13, so `>=18` was never
288
+ really true. On an older Node, upgrade before updating.
289
+ - The server reads only a fixed list of environment variables, each by name
290
+ (`src/utils/env.ts`), and SECURITY.md lists what each capability of the
291
+ package is for and where it lives.
292
+ - Every module the package loads is named literally. `PINEPAPER_PUPPETEER_FROM`
293
+ now loads puppeteer from the folder you name with `createRequire`. The one
294
+ exception is the optional ffmpeg add-on that only the Claude Desktop
295
+ extension bundles.
296
+ - A `PINEPAPER_LOCALE` that is not a supported language falls back to English.
297
+ Before, the value became part of a file path.
298
+
299
+ ### Fixed
300
+
301
+ - A batch op now takes the parameter names its standalone tool takes, and the
302
+ reply lists each name it translated. Before, they were dropped, sometimes
303
+ without a word: `animationOptions: { intensity, speed, delay }` on an
304
+ `animate` op applied none of them. Relations, effects and generators take
305
+ `params`, masks take `preset` and `options`, generators take `region`,
306
+ `animate` takes `intensity` / `speed` / `delay` / `waveform` / `origin`,
307
+ and a `modify` op takes its changes beside `itemId`.
308
+ - A batch op's ignored-argument check now knows which keys each op type reads.
309
+ A key that only another op type uses (`speed` on `animate`, `preset` on a
310
+ mask, `itemId` on a generator) is reported instead of passing as known.
311
+ - An `animate` op names any `animationOptions` key the studio does not read,
312
+ with the likely intended name (`animationIntensty → animationIntensity?`).
313
+ The list comes from the studio's own code, so it follows the studio as it
314
+ changes.
315
+ - A batch `keyframe_animate` op gives the same warnings as
316
+ `pinepaper_keyframe_animate`: a key that leaves out a property another key
317
+ animates, `loop` in an export, easing on the first key, and `x` keyed beside
318
+ `fontSize` on an older studio. The batch op used to give none of them.
319
+ - An emitter warns about a `[min, max]` range only on keys that take one number
320
+ in every studio build (`count`, `aspect`, `strokeWidth`, `jitterHz`, the
321
+ fades). `gravity`, `drag`, `friction`, `opacity`, `sizeEnd` and `jitter` are
322
+ sampled per particle on the current studio, and an older one reports a range
323
+ it can't draw itself.
324
+ - `on_event_step_active` works without a `targetId` on the current studio. On
325
+ an older studio, which runs an event reaction only when it has a target, a
326
+ target-less one used to report success and never step; it is now refused
327
+ there, with the fix in the message (point it at any member of the group).
328
+ - The relation docs now say how a stepper or tab strip is built. Members join
329
+ an `exclusive_group` only through `exclusive_group` relations between them.
330
+ Being active has no look of its own: `on_enter_*` / `on_exit_*` reactions on
331
+ each member draw it, in exports too. `items` only sets the step order.
332
+
333
+ ## What's new in 1.6.30
334
+
335
+ ### Changed
336
+
337
+ - An idle server pauses the studio page after 30 s with no tool call
338
+ (`PINEPAPER_IDLE_PAUSE_SECONDS`, `0` to turn it off), and the next call
339
+ resumes it. A 400-item scene's live preview used about half a core to one
340
+ and a half cores with nobody watching; paused, it uses almost none. Exports
341
+ come out identical either way.
342
+ - Replies are much smaller for an agent editing a scene. JSON results are sent
343
+ compact; `pinepaper_lint_scene { only: "edit" }` returns just the ops that
344
+ change the scene, lists a group bound by a name pattern once instead of once
345
+ per member, and includes a custom relation's source only with
346
+ `withSource: true`; a batch reply leaves out entries that only repeat what you
347
+ sent (`verbose: true` lists every op); and the `edit` toolkit's batch
348
+ description covers editing, not building. One edit pass over a 20-dot scene
349
+ went from 44 KB to 15 KB.
350
+ - New relation `on_event_step_active`, for next and previous arrows: an event
351
+ moves the active member of an `exclusive_group` forward or back by `delta`
352
+ (default +1), wrapping at the ends unless `wrap: false`.
353
+ - `pinepaper_lint_scene` lists only the custom relations the scene uses, and
354
+ names the rest in `registeredUnused` (one registered before a
355
+ `pinepaper_clear_canvas` no longer shows up as part of the scene).
356
+
357
+ - Housekeeping for supply-chain scanners, with no change in behaviour: the
358
+ hosted-API client's request helper is named for what it is, three long text
359
+ constants are typed as plain strings, and the p5 drawing generator checks
360
+ `PINEPAPER_ALLOW_CODE` itself as well as the tool handler.
361
+
362
+ ### Fixed
363
+
364
+ - A very large batch (more than about 200 creates) now waits until every op has
365
+ run before it replies, and its reply lists the items it made. It used to
366
+ answer success immediately with no result while the work carried on, so the
367
+ next call could act on a half-built scene. An export sent right after it
368
+ could miss the background colour set in the same batch.
369
+ - An opaque export of a scene with no background colour (`mp4`, `webm`, or
370
+ `apng` with `transparent: false`) warns that the frames are filled with the
371
+ studio's page colour. The warning used to say black, but the current studio
372
+ fills them white.
373
+ - `skewX` and `skewY` keyframes are documented: the current studio animates
374
+ them (degrees, up to ±89). A `shear` key, which is not a channel on any
375
+ studio, is named in `ignoredProperties` with a pointer to `skewX` / `skewY`
376
+ instead of being accepted silently.
377
+ - On the current studio, scale at create, text keyed on both `x` and
378
+ `fontSize`, `bloom`, ink `keepColor` and removing a look are now fixed in the
379
+ studio itself. The 1.6.29 warnings and workarounds for them only appear on
380
+ an older studio that still needs them.
381
+ - `pinepaper_modify_item` no longer calls `scale`, `scaleX` or `scaleY`
382
+ ignored on the current studio.
383
+
282
384
  ## What's new in 1.6.29
283
385
 
284
386
  ### Changed
@@ -2759,6 +2861,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
2759
2861
  |----------|-------------|---------|
2760
2862
  | `PINEPAPER_STUDIO_URL` | PinePaper Studio URL to connect to (`/editor` is appended automatically) | `https://pinepaper.studio` |
2761
2863
  | `PINEPAPER_HEADLESS` | Run the browser headless (set `false` to watch it work) | `true` |
2864
+ | `PINEPAPER_IDLE_PAUSE_SECONDS` | Seconds with no tool call before the studio page is paused, so an idle server uses almost no CPU; the next call resumes it. Exports are unaffected. `0` never pauses | `30` |
2762
2865
  | `PINEPAPER_API_KEY` | Service-account key (`pp_sa_…`) for the cloud-backed tools (`pinepaper_generate`) | — |
2763
2866
  | `PINEPAPER_CLOUD_URL` | The PinePaper cloud those tools call | `https://cloud.pinepaper.studio` |
2764
2867
  | `PINEPAPER_EXECUTION_MODE` | `puppeteer` (execute in a real browser) or `code` (return generated code only) | `puppeteer` |
package/README.pt-BR.md CHANGED
@@ -74,7 +74,7 @@ Experimente pedir à sua IA:
74
74
  **Local é gratuito e completo.** Todas as ferramentas funcionam se você mesmo
75
75
  executar este servidor. Não existe uma versão reduzida.
76
76
 
77
- Requisitos: Node 18 ou mais recente, cerca de **320 MB** de disco (o Puppeteer
77
+ Requisitos: Node 22.13 ou mais recente, cerca de **320 MB** de disco (o Puppeteer
78
78
  baixa o Chrome) e aproximadamente 1 GB de memória enquanto um trabalho roda. Em
79
79
  um notebook isso é tranquilo; em um VPS pequeno ou uma máquina de trabalho
80
80
  restrita, nem tanto.
package/README.zh-CN.md CHANGED
@@ -74,7 +74,7 @@ npm install -g @pinepaper.studio/mcp-server
74
74
  **本地运行免费且功能完整。** 只要你自己运行这个服务器,所有工具都可用,
75
75
  没有功能缩水的版本。
76
76
 
77
- 需要:Node 18 或更高版本、约 **320 MB** 磁盘空间(Puppeteer 会下载 Chrome),
77
+ 需要:Node 22.13 或更高版本、约 **320 MB** 磁盘空间(Puppeteer 会下载 Chrome),
78
78
  以及任务运行期间约 1 GB 内存。笔记本电脑没问题,小型 VPS 或受限的办公电脑
79
79
  则未必。
80
80
 
package/SECURITY.md CHANGED
@@ -58,3 +58,18 @@ Every outbound request goes through one file, `src/utils/safe-fetch.ts`, on Node
58
58
  **No child process in the npm package.** `ffprobe` and `ffmpeg` run through `packages/mcp-ffmpeg`, which is never published (`private`) and is copied only into the Claude Desktop extension's bundle. The server loads it by name when it is there. It runs those two binaries and nothing else, through `execFile` (no shell), with argument lists `src/tools/export-file-receipt.ts` writes and absolute file paths. Without it (any npm install) both features are unavailable and the server says so.
59
59
 
60
60
  The source contains no `eval` or `new Function`.
61
+
62
+ ## What this package does and why
63
+
64
+ Supply-chain scanners list the capabilities a package's own code uses. This is each one, where it is in `src/`, and why it is there. `src/__tests__/unit/supply-chain-surface.test.ts` and `capability-footprint.test.ts` fail if any of it spreads to another file.
65
+
66
+ | Capability | Where | Why |
67
+ |---|---|---|
68
+ | **Network access** | `src/utils/safe-fetch.ts` (the guarded fetch); `src/assets/repositories/` (fixed icon hosts); `src/tools/handlers/generate.ts` (PinePaper Cloud, opt-in) | Importing an image, SVG or font by URL, and searching the free icon repositories. Every user-supplied URL goes through the guarded fetch: private, loopback and link-local addresses and cloud metadata endpoints are refused, and each connection is pinned to the address that passed the check (no DNS rebinding). Generation calls the PinePaper Cloud only when you set `PINEPAPER_API_KEY`; the key is sent only to the cloud URL (`https://cloud.pinepaper.studio`, or `PINEPAPER_CLOUD_URL` if you set it). |
69
+ | **Shell access** | None in this package. `src/tools/export-file-receipt.ts` loads the optional add-on `@pinepaper.studio/mcp-ffmpeg` (`packages/mcp-ffmpeg`), which is private, never published, and bundled only in the Claude Desktop extension (`.mcpb`) | `ffprobe` reads a saved video back to confirm it is what the studio encoded, and `ffmpeg` joins per-scene segments for a `perScene` export. The add-on runs only those two binaries, through `execFile` with argument lists the server writes: no shell, nothing from a tool call is interpreted as a command. Without the add-on, the read-back is skipped and a per-scene export runs whole; `PINEPAPER_FFPROBE=off` turns the read-back off. |
70
+ | **Environment variables** | `src/utils/env.ts` (the complete list, each read by its literal name); one literal read of `PINEPAPER_ALLOW_PRIVATE_NETWORK` in `safe-fetch.ts` | Configuration only: `PINEPAPER_*` settings (browser, tool surface, export folder, asset folders, timeouts, the opt-in cloud key, the code and local-file switches) and the locale (`PINEPAPER_LOCALE`, `LC_ALL`, `LC_MESSAGES`, `LANG`). No value is sent anywhere except `PINEPAPER_API_KEY`, to the PinePaper Cloud, as above. |
71
+ | **Browser code** | `src/types/code-generator.ts` writes it; `src/browser/puppeteer-controller.ts` sends it | Each tool compiles to JavaScript that runs in PinePaper Studio, in a Chrome that puppeteer drives on your machine (or that you paste, in `code` mode). It is sent to that page over the DevTools protocol. Nothing is evaluated in the Node process: the source has no `eval`, `new Function`, `vm` or `inspector`. |
72
+ | **Dynamic import** | `src/browser/puppeteer-controller.ts`; `src/i18n/i18n-manager.ts`; `src/tools/export-file-receipt.ts` | Optional modules load on first use. `puppeteer` and `puppeteer-core` are imported by literal name, resolved from this package's own location (never the working directory, which can be an untrusted checkout). For strict pnpm/Yarn layouts, `PINEPAPER_PUPPETEER_FROM` names a folder they are required from by the same literal names (`createRequire`, Node ≥ 22.13). The one computed import is the ffmpeg add-on above: its name is held in a variable, so the published package never names a package it does not ship. Locale files load from a fixed table, so a locale name never becomes a file path. |
73
+ | **Async context** | `src/tools/handlers.ts` (`AsyncLocalStorage`) | Carries which tool is running to the check that a mutating call changed something, across concurrent calls. |
74
+
75
+ The package has no install scripts and one runtime dependency (`zod`).
@@ -12,6 +12,26 @@ import type { Page } from 'puppeteer';
12
12
  */
13
13
  export type PageWaitUntil = 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2';
14
14
  export declare const PAGE_WAIT_UNTIL: readonly PageWaitUntil[];
15
+ /**
16
+ * FxTool's governor (GeneratedCodeRunner) does not scan code longer than this:
17
+ * injectLoopGuards and wrapTrailingReturn both give up and run it as written.
18
+ */
19
+ export declare const GOVERNOR_MAX_CODE_CHARS = 400000;
20
+ /**
21
+ * Whether app.runGenerated would LOSE this code's value, so it must run
22
+ * ungoverned instead.
23
+ *
24
+ * Every emitter ends in a trailing `(async function () { … })();`, and the
25
+ * governor only hands its value back by rewriting that into `return (…)`. Past
26
+ * GOVERNOR_MAX_CODE_CHARS it does not rewrite, so the promise is dropped: the
27
+ * call answers success with no result AT ONCE while the code keeps running in
28
+ * the page. A 403-op batch (754 KB, ~1.9 KB per create) replied in 83 ms; the
29
+ * export sent next started before the batch's set_background ran, and the APNG
30
+ * came out on the empty page colour, intermittently, depending on who won.
31
+ * A batch passes the limit at about 215 creates. Running it ungoverned costs
32
+ * only what the governor had already given up at that size: its loop guards.
33
+ */
34
+ export declare function governorDropsValue(code: string): boolean;
15
35
  export interface BrowserControllerConfig {
16
36
  /** PinePaper Studio URL (default: https://pinepaper.studio) */
17
37
  studioUrl?: string;
@@ -250,6 +270,24 @@ export declare class PinePaperBrowserController {
250
270
  * When agentMode is enabled (default), this automatically uses connectAgent().
251
271
  */
252
272
  connect(): Promise<void>;
273
+ private activity;
274
+ private frozen;
275
+ private freezing;
276
+ private idleTimer;
277
+ private idleSession;
278
+ /** Milliseconds of no activity before the page is frozen; 0 = never. */
279
+ static idlePauseMs(): number;
280
+ /** Whether the page is frozen for idleness right now. */
281
+ get isIdlePaused(): boolean;
282
+ /** A tool call starts: stop the idle countdown and thaw the page if it was frozen. */
283
+ beginActivity(): Promise<void>;
284
+ /** A tool call ends: when none is left, start the idle countdown. */
285
+ endActivity(): void;
286
+ /** Start (or restart) the idle countdown, if a page is up and the pause is on. */
287
+ scheduleIdlePause(): void;
288
+ private lifecycleSession;
289
+ private freezeForIdle;
290
+ private thaw;
253
291
  /**
254
292
  * Disconnect and close browser
255
293
  */
@@ -404,6 +442,8 @@ export declare class PinePaperBrowserController {
404
442
  * NOTE: MCP server ALWAYS uses agent mode with ?agent=1&mode=agent URL parameters.
405
443
  * This uses headless browser by default for optimal performance.
406
444
  */
445
+ /** The controller if one exists, without creating it (code mode never launches a browser). */
446
+ export declare function peekBrowserController(): PinePaperBrowserController | null;
407
447
  export declare function getBrowserController(config?: BrowserControllerConfig): PinePaperBrowserController;
408
448
  /**
409
449
  * Reset the global controller (for testing or when config needs to change).
@@ -1 +1 @@
1
- {"version":3,"file":"puppeteer-controller.d.ts","sourceRoot":"","sources":["../../src/browser/puppeteer-controller.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"puppeteer-controller.d.ts","sourceRoot":"","sources":["../../src/browser/puppeteer-controller.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAW,IAAI,EAAc,MAAM,WAAW,CAAC;AAc3D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,kBAAkB,GAAG,cAAc,GAAG,cAAc,CAAC;AAE1F,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EAAiE,CAAC;AAEtH;;;GAGG;AACH,eAAO,MAAM,uBAAuB,SAAU,CAAC;AAE/C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4BAA4B;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,KAAK,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,+FAA+F;IAC/F,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,mGAAmG;IACnG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6FAA6F;IAC7F,WAAW,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IAClC,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qEAAqE;IACrE,kBAAkB,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACxD;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAK1C;AAiCD;;;;;;;GAOG;AACH;;;;;GAKG;AACH,eAAO,MAAM,YAAY;;;CAAoC,CAAC;AA2G9D,qBAAa,0BAA0B;IACrC,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,iBAAiB,CAA8B;gBAE3C,MAAM,GAAE,uBAA4B;IAyChD;;;OAGG;IACH,OAAO,CAAC,UAAU;IAgBlB,yEAAyE;IACzE,OAAO,CAAC,aAAa;IAQrB;;OAEG;IACH,OAAO,CAAC,WAAW;IAOnB;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;;;;OAKG;IACG,YAAY,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAezD;;;;OAIG;IACG,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC;IAetC;;;;;OAKG;IACG,gBAAgB,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAarE;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,OAAO,CAExB;IAED;;;;OAIG;IACH,IAAI,kBAAkB,IAAI,QAAQ,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAEpE;IAED,6EAA6E;IAC7E,UAAU,IAAI,MAAM,EAAE;IAItB;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA+G9B,OAAO,CAAC,QAAQ,CAAK;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,WAAW,CAAoD;IAEvE,wEAAwE;IACxE,MAAM,CAAC,WAAW,IAAI,MAAM;IAO5B,yDAAyD;IACzD,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED,sFAAsF;IAChF,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAOpC,qEAAqE;IACrE,WAAW,IAAI,IAAI;IAKnB,kFAAkF;IAClF,iBAAiB,IAAI,IAAI;YAQX,gBAAgB;YAQhB,aAAa;YAiBb,IAAI;IAalB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAkBjC;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAKhC;;;;;;;;;;OAUG;YACW,aAAa;IAyB3B;;;;OAIG;IACH,OAAO,CAAC,cAAc,CAAkD;YAE1D,kBAAkB;IAYhC;;;;;OAKG;YACW,cAAc;IA6B5B,qEAAqE;IACrE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6E;IAC1G,OAAO,CAAC,YAAY,CAAS;YAEf,iBAAiB;IAK/B,kEAAkE;IAClE,OAAO,CAAC,eAAe;IAIvB;;;;;;OAMG;YACW,gBAAgB;IA6B9B,4EAA4E;YAC9D,eAAe;IAa7B;;OAEG;IACH;;;;OAIG;YACW,aAAa;IAiBrB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,UAAO,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,aAAa,CAAC;IAwOhH;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;YAQrC,eAAe;IA6B7B;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAIlD;;OAEG;IACG,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU5C;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAU7B;;;OAGG;IACH;;;;;;OAMG;IACG,WAAW,IAAI,OAAO,CAAC;QAAE,UAAU,CAAC,EAAE,OAAO,CAAC;QAAC,kBAAkB,CAAC,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IAwC9G;;;;;;OAMG;IACG,aAAa,IAAI,OAAO,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAgB/E;;OAEG;IACG,mBAAmB,IAAI,OAAO,CAAC,OAAO,CAAC;IAgB7C;;;OAGG;IACG,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmIpE;;OAEG;YACW,gBAAgB;IAO9B;;;OAGG;IACG,YAAY,CAChB,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,GAAE;QAAE,cAAc,CAAC,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAO,GAC3D,OAAO,CAAC,kBAAkB,CAAC;IA4F9B;;OAEG;IACG,kBAAkB,CACtB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,cAAc,UAAQ,EACtB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,aAAa,CAAC;IAsBzB;;OAEG;IACG,UAAU,CAAC,OAAO,GAAE;QAAE,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,aAAa,CAAC;IAgCxF;;OAEG;IACG,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;CAa9D;AAQD;;;;;;;;;GASG;AACH,8FAA8F;AAC9F,wBAAgB,qBAAqB,IAAI,0BAA0B,GAAG,IAAI,CAEzE;AAED,wBAAgB,oBAAoB,CAClC,MAAM,CAAC,EAAE,uBAAuB,GAC/B,0BAA0B,CAoC5B;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAU5D;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAKjD"}
@@ -13,6 +13,28 @@ import { readFileSync } from 'node:fs';
13
13
  import { isAbsolute, join } from 'node:path';
14
14
  import { pathToFileURL } from 'node:url';
15
15
  export const PAGE_WAIT_UNTIL = ['load', 'domcontentloaded', 'networkidle0', 'networkidle2'];
16
+ /**
17
+ * FxTool's governor (GeneratedCodeRunner) does not scan code longer than this:
18
+ * injectLoopGuards and wrapTrailingReturn both give up and run it as written.
19
+ */
20
+ export const GOVERNOR_MAX_CODE_CHARS = 400_000;
21
+ /**
22
+ * Whether app.runGenerated would LOSE this code's value, so it must run
23
+ * ungoverned instead.
24
+ *
25
+ * Every emitter ends in a trailing `(async function () { … })();`, and the
26
+ * governor only hands its value back by rewriting that into `return (…)`. Past
27
+ * GOVERNOR_MAX_CODE_CHARS it does not rewrite, so the promise is dropped: the
28
+ * call answers success with no result AT ONCE while the code keeps running in
29
+ * the page. A 403-op batch (754 KB, ~1.9 KB per create) replied in 83 ms; the
30
+ * export sent next started before the batch's set_background ran, and the APNG
31
+ * came out on the empty page colour, intermittently, depending on who won.
32
+ * A batch passes the limit at about 215 creates. Running it ungoverned costs
33
+ * only what the governor had already given up at that size: its loop guards.
34
+ */
35
+ export function governorDropsValue(code) {
36
+ return code.length > GOVERNOR_MAX_CODE_CHARS;
37
+ }
16
38
  /**
17
39
  * Is PinePaper Studio actually usable? Evaluated IN THE PAGE, so it closes over
18
40
  * nothing.
@@ -83,9 +105,9 @@ export const BROWSER_TIME = { executeMs: 0, screenshotMs: 0 };
83
105
  /**
84
106
  * The browser driver. Puppeteer is NOT a dependency of this package, not even an
85
107
  * optional peer (it was one up to 1.6.25): browser automation is a handful of tools, and a
86
- * headless-browser download with an install script is what supply-chain
87
- * scanners flag hardest. A declared peer, optional or not, still puts its whole
88
- * tree in a scanner's view of this package. The user installs it beside the
108
+ * headless-browser download with an install script is a large thing to put
109
+ * in every user's install. A declared peer, optional or not, still puts its whole
110
+ * tree in an audit of this package. The user installs it beside the
89
111
  * server when they want the executing mode (README, "Install").
90
112
  *
91
113
  * Tried in order: `puppeteer` (its own Chrome), then `puppeteer-core` driving a
@@ -95,23 +117,39 @@ export const BROWSER_TIME = { executeMs: 0, screenshotMs: 0 };
95
117
  */
96
118
  const PUPPETEER_MIN_MAJOR = 22;
97
119
  /**
98
- * Import a package this server does not depend on, by a name held in a
99
- * variable (so neither the build nor a scanner sees a dependency it does not
100
- * have). Resolution starts from THIS package, as a declared dependency's would:
101
- * `npx -p puppeteer -p @pinepaper.studio/mcp-server`, a global install of both,
102
- * and a project install all put puppeteer where that finds it.
120
+ * How each browser driver is found. Every module specifier is a string LITERAL, so a reader sees exactly which two
121
+ * packages this file can load; nothing here builds a module name at run time.
122
+ *
123
+ * `importHere` is an ordinary `import('puppeteer')`. Node resolves a bare specifier from the importing file's own
124
+ * location, walking up through the node_modules folders above dist/browser/, as it would for a declared dependency:
125
+ * `npx -p puppeteer -p @pinepaper.studio/mcp-server`, a global install of both, and a project install all put
126
+ * puppeteer where that finds it. It is NOT resolved from process.cwd() (ESM ignores the working directory and
127
+ * NODE_PATH): an MCP client starts the server in whatever folder is open, which can be a cloned repository, and a
128
+ * node_modules/puppeteer planted there must not run as this process.
103
129
  *
104
- * It is never resolved from process.cwd(): an MCP client starts the server in
105
- * whatever folder is open, which can be a cloned repository, and a
106
- * node_modules/puppeteer planted there would then run as this process. The one
107
- * other place looked in is a folder the user names in PINEPAPER_PUPPETEER_FROM
108
- * (for a strict layout — pnpm with hoist=false, Yarn PnP strict — where a
109
- * package cannot see its project's undeclared packages).
130
+ * `requireFrom` serves the one other place looked in: a folder the user names in PINEPAPER_PUPPETEER_FROM (an
131
+ * absolute path), for a strict layout — pnpm with hoist=false, Yarn PnP strict — where a package cannot see its
132
+ * project's undeclared packages. It requires the same literal specifier from that folder (createRequire), so every
133
+ * module the package loads is named in its source; it is reached only when the user has set the variable.
110
134
  */
135
+ const DRIVERS = {
136
+ puppeteer: {
137
+ importHere: () => import('puppeteer'),
138
+ requireFrom: (req) => req('puppeteer'),
139
+ packageJson: (req) => req.resolve('puppeteer/package.json'),
140
+ },
141
+ 'puppeteer-core': {
142
+ importHere: () => import('puppeteer-core'),
143
+ requireFrom: (req) => req('puppeteer-core'),
144
+ packageJson: (req) => req.resolve('puppeteer-core/package.json'),
145
+ },
146
+ };
147
+ /** Load a driver the user installed beside this package (or in PINEPAPER_PUPPETEER_FROM); null when absent. */
111
148
  async function importOptional(name) {
149
+ const driver = DRIVERS[name];
112
150
  const versionFrom = (base) => {
113
151
  try {
114
- const pkgPath = createRequire(base).resolve(`${name}/package.json`);
152
+ const pkgPath = driver.packageJson(createRequire(base));
115
153
  return String(JSON.parse(readFileSync(pkgPath, 'utf8')).version ?? '') || null;
116
154
  }
117
155
  catch {
@@ -119,7 +157,7 @@ async function importOptional(name) {
119
157
  }
120
158
  };
121
159
  try {
122
- return { mod: await import(name), version: versionFrom(import.meta.url) };
160
+ return { mod: await driver.importHere(), version: versionFrom(import.meta.url) };
123
161
  }
124
162
  catch { /* not beside this package (or present and failing to load: treated as absent, as before) */ }
125
163
  const from = env('PINEPAPER_PUPPETEER_FROM')?.trim();
@@ -127,8 +165,9 @@ async function importOptional(name) {
127
165
  return null;
128
166
  const base = pathToFileURL(join(from, 'package.json')).href;
129
167
  try {
130
- const entry = createRequire(base).resolve(name);
131
- return { mod: await import(pathToFileURL(entry).href), version: versionFrom(base) };
168
+ // require() of an ES module (Node ≥ 22.12 unflagged; engines says ≥ 22.13): the literal name, resolved from
169
+ // the folder the user named, and no import() of a computed path anywhere in the package.
170
+ return { mod: driver.requireFrom(createRequire(base)), version: versionFrom(base) };
132
171
  }
133
172
  catch {
134
173
  return null;
@@ -141,16 +180,14 @@ function refuseOldPuppeteer(name, version) {
141
180
  }
142
181
  }
143
182
  async function loadPuppeteer() {
144
- const fullName = 'puppeteer'; // a variable: see importOptional
145
- const full = await importOptional(fullName);
183
+ const full = await importOptional('puppeteer');
146
184
  if (full) {
147
- refuseOldPuppeteer(fullName, full.version);
185
+ refuseOldPuppeteer('puppeteer', full.version);
148
186
  return { launch: (opts) => full.mod.default.launch(opts) };
149
187
  }
150
- const coreName = 'puppeteer-core';
151
- const coreLoaded = await importOptional(coreName);
188
+ const coreLoaded = await importOptional('puppeteer-core');
152
189
  if (coreLoaded)
153
- refuseOldPuppeteer(coreName, coreLoaded.version);
190
+ refuseOldPuppeteer('puppeteer-core', coreLoaded.version);
154
191
  const core = coreLoaded?.mod ?? null;
155
192
  if (core) {
156
193
  const executablePath = env('PINEPAPER_CHROME_PATH')?.trim();
@@ -445,10 +482,121 @@ export class PinePaperBrowserController {
445
482
  throw new Error(`Failed to connect to PinePaper Studio: ${error instanceof Error ? error.message : 'Unknown error'}`);
446
483
  }
447
484
  }
485
+ // ---------------------------------------------------------------------------
486
+ // IDLE PAUSE (#45). With a scene loaded and nothing asked of it, the studio's
487
+ // live preview kept animating: 1.49 cores for a 400-item scene on a fast
488
+ // machine, 0.44 on the tester's VM. Nobody watches a headless preview. After
489
+ // PINEPAPER_IDLE_PAUSE_SECONDS (default 30; 0 = never) with no tool call in
490
+ // progress, the page is frozen (CDP Page.setWebLifecycleState), which stops
491
+ // its timers and animation frames: 0.03 cores. The next tool call thaws it
492
+ // first. A thawed page came back "hidden", with no animation frames, so focus
493
+ // emulation is switched on as it thaws, and the preview runs again. Exports
494
+ // render each frame at its exact time, so a pause changes nothing in a file
495
+ // (frame digests identical across a freeze, measured).
496
+ // ---------------------------------------------------------------------------
497
+ activity = 0;
498
+ frozen = false;
499
+ freezing = null;
500
+ idleTimer = null;
501
+ idleSession = null;
502
+ /** Milliseconds of no activity before the page is frozen; 0 = never. */
503
+ static idlePauseMs() {
504
+ const v = env('PINEPAPER_IDLE_PAUSE_SECONDS');
505
+ if (v === undefined || v.trim() === '')
506
+ return 30_000;
507
+ const n = Number(v);
508
+ return Number.isFinite(n) && n > 0 ? n * 1000 : 0;
509
+ }
510
+ /** Whether the page is frozen for idleness right now. */
511
+ get isIdlePaused() {
512
+ return this.frozen;
513
+ }
514
+ /** A tool call starts: stop the idle countdown and thaw the page if it was frozen. */
515
+ async beginActivity() {
516
+ this.activity++;
517
+ if (this.idleTimer) {
518
+ clearTimeout(this.idleTimer);
519
+ this.idleTimer = null;
520
+ }
521
+ if (this.freezing)
522
+ await this.freezing.catch(() => undefined);
523
+ if (this.frozen)
524
+ await this.thaw();
525
+ }
526
+ /** A tool call ends: when none is left, start the idle countdown. */
527
+ endActivity() {
528
+ this.activity = Math.max(0, this.activity - 1);
529
+ if (this.activity === 0)
530
+ this.scheduleIdlePause();
531
+ }
532
+ /** Start (or restart) the idle countdown, if a page is up and the pause is on. */
533
+ scheduleIdlePause() {
534
+ const ms = PinePaperBrowserController.idlePauseMs();
535
+ if (this.idleTimer) {
536
+ clearTimeout(this.idleTimer);
537
+ this.idleTimer = null;
538
+ }
539
+ if (!ms || !this.page || !this.isConnected || this.activity > 0)
540
+ return;
541
+ this.idleTimer = setTimeout(() => { this.idleTimer = null; void this.freezeForIdle(); }, ms);
542
+ this.idleTimer.unref?.();
543
+ }
544
+ async lifecycleSession() {
545
+ if (!this.page)
546
+ return null;
547
+ if (!this.idleSession || this.idleSession.page !== this.page) {
548
+ this.idleSession = { page: this.page, session: await this.page.createCDPSession() };
549
+ }
550
+ return this.idleSession.session;
551
+ }
552
+ async freezeForIdle() {
553
+ if (this.activity > 0 || this.frozen || !this.page)
554
+ return;
555
+ this.freezing = (async () => {
556
+ try {
557
+ const s = await this.lifecycleSession();
558
+ if (!s || this.activity > 0)
559
+ return;
560
+ await s.send('Page.setWebLifecycleState', { state: 'frozen' });
561
+ this.frozen = true;
562
+ }
563
+ catch {
564
+ // A browser that cannot freeze keeps running, as before.
565
+ }
566
+ })();
567
+ try {
568
+ await this.freezing;
569
+ }
570
+ finally {
571
+ this.freezing = null;
572
+ }
573
+ // A call that began during the freeze waited for it; if it is still running, thaw now.
574
+ if (this.frozen && this.activity > 0)
575
+ await this.thaw();
576
+ }
577
+ async thaw() {
578
+ try {
579
+ const s = await this.lifecycleSession();
580
+ if (s) {
581
+ await s.send('Page.setWebLifecycleState', { state: 'active' });
582
+ await s.send('Emulation.setFocusEmulationEnabled', { enabled: true });
583
+ }
584
+ }
585
+ catch {
586
+ // The page went away while frozen: the call's own recovery takes over.
587
+ }
588
+ this.frozen = false;
589
+ }
448
590
  /**
449
591
  * Disconnect and close browser
450
592
  */
451
593
  async disconnect() {
594
+ if (this.idleTimer) {
595
+ clearTimeout(this.idleTimer);
596
+ this.idleTimer = null;
597
+ }
598
+ this.frozen = false;
599
+ this.idleSession = null;
452
600
  if (this.browser) {
453
601
  try {
454
602
  await this.browser.close();
@@ -726,7 +874,7 @@ export class PinePaperBrowserController {
726
874
  // only once app.js has finished, so an early caller races init itself.
727
875
  const awaitBoot = BOOTSTRAP_SUBSYSTEMS.some((name) => code.includes(`app.${name}`));
728
876
  const runOptions = {
729
- bypass: options.bypassGovernor ?? !this.config.governor,
877
+ bypass: options.bypassGovernor ?? (!this.config.governor || governorDropsValue(code)),
730
878
  timeoutMs: options.governorTimeoutMs,
731
879
  heavyClass: heavyClass ?? null,
732
880
  awaitBoot,
@@ -841,8 +989,8 @@ export class PinePaperBrowserController {
841
989
  // bypass. The code is run from the Node side as a CDP Runtime.evaluate
842
990
  // (below), which returns the script's trailing value exactly as eval did:
843
991
  // both generated-code shapes are `(`-led expression statements. It used
844
- // to be evaluated here; the published package then carried an evaluating call
845
- // that supply-chain scanners flag, for what CDP already does.
992
+ // to be evaluated inside this callback; CDP already does that job, so this
993
+ // package contains no evaluating call of its own, in the page or in Node.
846
994
  window.__ppBridgeSafe = bridgeSafe;
847
995
  return { success: true, __ppRaw: true, executedVia: 'eval' };
848
996
  }, code, runOptions);
@@ -1396,6 +1544,10 @@ let globalController = null;
1396
1544
  * NOTE: MCP server ALWAYS uses agent mode with ?agent=1&mode=agent URL parameters.
1397
1545
  * This uses headless browser by default for optimal performance.
1398
1546
  */
1547
+ /** The controller if one exists, without creating it (code mode never launches a browser). */
1548
+ export function peekBrowserController() {
1549
+ return globalController;
1550
+ }
1399
1551
  export function getBrowserController(config) {
1400
1552
  if (!globalController) {
1401
1553
  // Env resolution lives here, not in the controller: explicit config wins,