Production-grade infrastructure for Adobe ExtendScript.
|
ESON ESB64 ESARR ESSTR ESCHARS ESHTTP ESTIMER ESRAND ESUUID ESENV ESPATH ESFS ESHASH ESLOG |
ESPACK ESMIN ESABI VectorIPC ESTC ESDB COMTool ESsemble |
Also from the same team: ArcFit.dev, deterministic arc warp for Illustrator.
- Why COMTool?
- Features
- Installation
- Quick Start
- Agent Automation
- API Reference
- Validation
- Performance
- Security Model
- Compatibility
- Engine quirks that shaped the design
- Development
- Repository layout
- Known limitations
- Credits
- License
Adobe desktop automation is deceptively stateful. A single operation can cross a process boundary, enter a single-threaded COM apartment, execute ExtendScript inside a UI application, call a native plug-in, and return through another transport. If the caller loses contact after dispatch, "try it again" is not a safe recovery strategy: the mutation may already have happened.
COMTool treats that entire path as one runtime problem rather than a collection of unrelated wrappers.
CLI, local IPC, NDJSON stdio, the optional MCP adapter, and the Node SDK all normalize into the same versioned operation protocol and route through one authoritative execution kernel:
caller / agent / CLI / Node / MCP
│
▼
RuntimeHost
│
▼
RuntimeSupervisor
│ │
│ └── durable state, leases,
│ workflows, artifacts,
│ reconciliation
▼
STA Worker
│
▼
Adobe host / ExtendScript / plug-in
The supervisor owns routing, policy, target identity, leases, durable mutation truth, reconciliation, workflows, artifacts, and observability. Host workers own Adobe execution contexts. Transports do not get to invent their own automation semantics.
COMTool 0.1.x is the first public release line of this project. Earlier experimental tooling was private development work and is not part of COMTool's public release lineage.
- One execution authority. CLI, pipe, NDJSON, MCP, and Node are transports over the same runtime rather than independent COM implementations.
- Strong target identity. A target is tied to a specific process generation, not merely an application name or PID.
- Persistent STA workers. Adobe execution stays in dedicated single-threaded apartment workers instead of borrowing arbitrary caller threads.
- Explicit leases. Effectful work requires exclusive target ownership across runtime processes.
- Write-ahead mutation state. Mutation intent is persisted before host dispatch.
- Safe replay semantics. Completed mutations can replay their stored result; potentially executed mutations are never silently re-run.
- Conservative ambiguity. A lost worker/runtime after possible dispatch becomes
reconciliation_requiredrather than "probably failed." - Generation-pinned recovery. Break-glass host termination re-proves the exact known process generation before doing anything destructive.
- Typed Illustrator operations. Document, artboard, and layer operations own their selector grammar and target member instead of accepting arbitrary caller paths.
- Guarded generic automation. COM and ExtendScript escape hatches remain available, but keep conservative mutation classifications.
- Durable workflows. Registered operations can run as recoverable multi-step jobs through the same policy and mutation machinery as direct calls.
- Persistent ExtendScript debugging. Debug sessions are worker-owned and bound to one exact target generation.
- Native plug-in RPC and diagnostics. Script messages and AIPDebug control remain behind the same runtime authority and provenance checks.
- Opaque large-result artifacts. Large host results are verified and materialized by the supervisor without exposing arbitrary state-directory paths.
- Embedded Illustrator COM knowledge. Search, signature, and enum lookup are backed by a generated, provenance-pinned knowledge pack.
- Dependency-free Node SDK. Generic execution, target sessions, leases, debugger orchestration, artifact access, and explicit ambiguity recovery are available without a second Adobe implementation.
- Caller-controlled watchdogs. Script worker watchdogs are bounded from 100 ms to 3,600,000 ms.
- Bounded control-plane framing. Public IPC and the authenticated worker broker retain a 1 MiB frame ceiling.
COMTool ships as a self-contained Windows x64 package and does not require a machine-wide .NET installation.
Build a production package from a clean source tree:
.\scripts\release.ps1 -Version 0.1.2The release transaction:
- validates the source tree and dependency lock state;
- runs deterministic .NET, schema, Node, type, and knowledge-pack gates;
- publishes self-contained runtime binaries;
- stages a complete immutable package;
- verifies the staged package;
- builds the release ZIP;
- extracts that ZIP into a fresh directory and verifies the shipped bytes again;
- records source, toolchain, NuGet-audit, file-hash, and archive-hash provenance in
release-manifest.json.
Install an extracted package for the current user:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\install-user.ps1The installer keeps immutable versions side-by-side and exposes a stable current\ junction for integrations. Point long-lived integrations at the executables beneath that stable current\ directory rather than at a version-specific package directory.
Durable runtime state is stored separately from installed binaries and is preserved across ordinary upgrades and uninstalls. A custom --state-dir can isolate a development or shadow runtime; one state root has one runtime owner at a time.
For development, build first and stage an immutable runtime copy:
.\scripts\stage-runtime-dev.ps1Do not run a persistent development runtime directly from bin/Release. Windows can lock loaded assemblies and obstruct later builds.
From an installed current\ directory or an extracted verified package:
.\ComTool.RuntimeHost.exeIn another shell:
.\ComTool.Cli.exe health
.\ComTool.Cli.exe probe
.\ComTool.Cli.exe targetsRead Illustrator state:
.\ComTool.Cli.exe status
.\ComTool.Cli.exe get --path ActiveDocument.NameThe runtime's machine-readable operation catalog is authoritative. Programmatic clients can query core.operations.list and core.operation.describe rather than hard-coding assumptions about the available surface.
probe is deliberately broader than target discovery. It reports live
process presence, COM registration, runtime configuration, and whether this
COMTool build actually contains an automation adapter for each known Adobe
desktop family. A detected application is not automatically an
automation-capable application.
Effectful work requires the runtime's mutation policy to be satisfied. Fixed mutation operations cannot be weakened by caller input, and arbitrary script execution cannot declare itself read-only.
The optional MCP entry point remains a thin transport adapter:
.\ComTool.Transport.Mcp.exe --self-test --pipe <name>For programmatic Node usage, see sdk/node/README.md. The SDK forwards ordinary runtime operations; it contains no independent Adobe/COM implementation.
COMTool ships its own agent bundle inside every release under agent/:
agent/SKILL.md, agent/AGENT_CONTRACT.md, and a portable OpenFork
slash-command definition at agent/openfork/comtool.md.
Agents can discover those installed assets without knowing the installation path:
.\ComTool.Cli.exe agent-guide
.\ComTool.Cli.exe agent-guide --contentBoth ComTool.Cli.exe help and the packaged Node runner's --help output
also include a short agent bootstrap hint pointing to agent-guide --content,
so an unfamiliar agent can discover the full contract through normal CLI
exploration.
The agent workflow starts with the live dynamic probe, then consults the runtime-owned operation catalog and exact target capabilities before choosing an execution path. This avoids teaching agents a stale hard-coded command surface and prevents a detected Photoshop/InDesign/etc. installation from being mistaken for a fully implemented COMTool adapter.
The complete machine-facing safety and discovery contract is documented in
agent/AGENT_CONTRACT.md.
| Surface | Entry point | Execution authority |
|---|---|---|
| CLI | ComTool.Cli.exe <command> |
RuntimeHost → RuntimeSupervisor |
| Local IPC | ComTool.RuntimeHost.exe current-user named pipe |
RuntimeSupervisor |
| NDJSON | ComTool.RuntimeHost.exe --stdio or ComTool.Cli.exe stdio |
Same operation protocol |
| MCP | ComTool.Transport.Mcp.exe --server |
Thin adapter over the runtime pipe |
| Node | sdk/node |
Thin client over CLI NDJSON |
NDJSON is correlation-based rather than response-order-based. The server admits 32 concurrent requests by default and serializes stdout writes only. Overlapping CLI stdio requests can use independent pooled runtime-pipe connections, so a recovery/control request does not have to wait behind an execution request that is blocked in Adobe.
Core runtime operations include:
core.runtime.healthcore.operations.listcore.operation.describecore.operation.examplescore.adobe.probecore.artifact.describecore.artifact.readcore.incidents.listcore.incident.resolvecore.targets.listcore.target.capabilitiescore.target.statuscore.target.snapshotcore.target.reconcilecore.target.lease.acquirecore.target.lease.renewcore.target.lease.releasecore.target.host.terminatecore.workflow.submitcore.workflow.getcore.workflow.cancelcore.workflow.resume
Introspection and planning operations include:
knowledge.describeknowledge.searchknowledge.symbolknowledge.enumscript.validatewatch.condition
The live operation catalog is authoritative. Call core.operations.list / core.operation.describe when exact policy or capability metadata matters.
com.getcom.call.readillustrator.document.readillustrator.artboard.readillustrator.layer.read
Read operations are target-scoped but do not require a mutation lease.
script.evalscript.runFile
script.runFile requires an absolute .jsx or .jsxbin path plus the caller's SHA-256 of the exact file bytes. The host adapter verifies those bytes again immediately before execution.
Arbitrary source execution may declare a write class, but it cannot self-certify as read-only. Omitted effects default to unknown.
illustrator.artboard.setNameillustrator.artboard.setRectillustrator.layer.setNameillustrator.layer.setVisibleillustrator.layer.setLockedillustrator.layer.setOpacityillustrator.action.runillustrator.menu.executeillustrator.document.createillustrator.document.openillustrator.document.saveillustrator.document.saveAsillustrator.document.close
Typed layer/artboard operations require explicit selectors, reject ambiguous name matches before mutation, and never accept a caller-supplied COM member path.
Document close requires an explicit policy: save, discard, or reject_if_unsaved. Destructive discard is never implied.
plugin.message— boundedApplication.SendScriptMessage(plugin, selector, input)RPC.plugin.debug.diagnostics— fixed AIPDebug discovery/info/log/snapshot/statistics surface.plugin.debug.control— bounded AIPDebug control actions.
When the optional native diagnostic helper is packaged, it is hash-checked
against release-manifest.json before use. Read-only
transport: "auto" diagnostics fall back to the host-thread COM path when
that helper is unavailable; explicit transport: "ipc" fails closed and
effectful control never silently changes transport. Direct VectorIPC
diagnostics are allowed only after endpoint provenance is established through
the exact Illustrator generation; the native peer then verifies the expected
PID and process-start generation again.
debug.session.opendebug.session.statusdebug.session.commanddebug.session.close
A debugger session is worker-owned and target-generation-scoped. Commands cover evaluation, breakpoint management, break/frame/property inspection, continue, halt, break, and step over/into/out.
The worker pins the debugger add-on bytes before creating the bridge child, and the bridge re-hashes that path immediately before loading it.
For non-read-only operations, durable execution state is:
prepared → completed
→ not_started
→ ambiguous
The request ID is the idempotency key.
- completed — an identical retry returns the stored result with replay evidence and does not execute the host operation again.
- not_started — the request may be safely retried.
- prepared after runtime loss / ambiguous — the target enters
reconciliation_required; COMTool does not guess and does not replay the operation. - same request ID, different semantic payload — rejected.
Host liveness is not mutation-outcome evidence.
core.target.mutation.reconcile can clear an incident only from verified postconditions whose canonical fingerprint matches the conditions durably declared with the original mutation request. New evidence cannot be invented after ambiguity and used to retroactively certify an unknown operation.
core.workflow.submit accepts 1–64 registered sequential steps. Each step has an explicit step ID, operation, input, and optional preconditions/postconditions. The outer request ID is the durable job ID and definition idempotency key.
Step request IDs are deterministically derived from job + step identity, so restart recovery reuses the same mutation-ledger semantics. Mutating workflows hold one target lease for the run. targetLease:false is accepted only when every step is read-only. Any ambiguous or reconciliation-required step halts the workflow.
One Illustrator target is intentionally a capacity-1 Adobe execution resource. Future scheduling may overlap runtime-only work or independent target generations, but it must not turn logical workflow parallelism into unsafe concurrent host execution against one Illustrator process. See docs/WORKFLOW_CONCURRENCY_MODEL.md.
Public pipe/NDJSON and the authenticated runtime↔worker broker retain a 1 MiB frame ceiling.
Successful host result payloads at or above 32 KiB are streamed as correlated 512 KiB raw chunks. The supervisor validates sequence, byte count, result kind, and SHA-256, reconstructs the canonical result, finalizes durable mutation truth when required, commits the payload through the artifact store, and returns a small opaque descriptor.
A result beyond the 64 MiB artifact ceiling fails explicitly rather than being truncated or silently replayed.
| Check | Command | Result |
|---|---|---|
| Deterministic .NET gate | release/test pipeline | 835/835 passing, 0 failed |
| Node SDK | node --test sdk/node/test/sdk.test.mjs |
20/20 passing |
| Protocol/schema fixtures | release schema gate | 41/41 matched |
| TypeScript SDK surface | node sdk/node/test/typecheck.mjs |
TypeScript 5.9.3 check passed |
| Embedded COM knowledge | node scripts/build-knowledge-pack.mjs --check |
byte-current against pinned SQLite/manifest provenance |
| Package verification | .\scripts\release.ps1 -Version <semver> |
staged package and extracted shipped ZIP verified |
| Windows PowerShell package smoke | release verification | 5.1.22621.6133 passed |
| NuGet audit | release transaction | 0 vulnerability records |
The deterministic .NET gate covers protocol contracts, operation-catalog policy, durable mutation/reconciliation behavior, target leasing, runtime IPC, pipe and NDJSON transport, MCP bridging, host launch/attach/recovery, Illustrator COM reads and mutations, document lifecycle, debugger integration, native plug-in diagnostics, artifact handling, knowledge lookup, workflows, and compatibility regression fixtures.
Live Illustrator 30.6.0 evidence covers:
- STA worker ownership and persistent worker reuse;
- typed COM reads and target snapshots;
- ESON-backed string, number, boolean, null, array, and object fidelity;
- structured argument transport;
- durable completed-result replay across a hard runtime/worker crash with zero target-worker re-execution;
- cross-runtime lease contention and immediate kernel-lock takeover after owner death;
- typed structure reads against a disposable document;
- persistent ExtendScript debugger operation;
- native plug-in diagnostics;
- host restart/reconnect where the process generation changed and the already-running RuntimeHost discovered the replacement target.
Captured proof lives under evidence/.
The measured live Illustrator 30.6.0 session recorded warm small-expression execution around 0.9–1.1 ms inside Illustrator.
COMTool optimizes for control-plane liveness and correctness rather than pretending a single Illustrator process can safely perform arbitrary host mutations in parallel. Same-target Adobe execution remains serialized, while runtime-only work and independent pipe connections can overlap so recovery/control traffic is not trapped behind a blocked execution call.
Large-result offload keeps the control plane bounded: payloads crossing the 32 KiB artifact threshold are streamed to the supervisor rather than inflating the 1 MiB control frame.
These numbers describe the recorded validation environment; they are not cross-machine or cross-host performance guarantees.
The persistent public runtime pipe uses Windows CurrentUserOnly. The current Windows user is therefore the local public trust boundary. COMTool does not claim to isolate one same-user process from another same-user process that already has equivalent local authority.
The private supervisor→worker channel adds a separate ephemeral bootstrap token plus verified child PID/process-start identity.
Mutation safety is transport-independent:
- effectful operations use explicit target leases;
- mutation intent is durably prepared before host dispatch;
- completed retries replay stored truth instead of re-executing;
- possible-dispatch failures remain ambiguous;
- a heartbeat cannot certify mutation outcome;
- break-glass host termination requires the already-known exact process generation;
- caller-supplied endpoints cannot replace worker-established plug-in provenance;
- packaged native/debugger helpers are hash-verified before load or use.
Release packages are content-addressed by a schema-versioned manifest and archive SHA-256. Authenticode signing is supported but optional. Checksums and manifests establish integrity; an unsigned archive does not establish publisher identity, so unsigned distribution still requires a trusted channel.
| Target | Status |
|---|---|
| Windows x86-64 | Primary supported runtime and package target |
| Adobe Illustrator 2026 / 30.6.0 | Complete reference adapter with deterministic and live conformance evidence |
| ExtendScript / ES3 | Script execution, static preflight, ESON transport, and debugger evidence |
| Photoshop 2026 | Second-host COM/script viability verified; not a complete Illustrator-equivalent adapter |
| Other Adobe desktop hosts | Capability-driven adapter required; unsupported capabilities are not advertised |
| .NET | Self-contained production package; no machine-wide .NET runtime required |
| Node.js | Dependency-free client/orchestration SDK; no independent Adobe implementation |
Illustrator is the complete production host adapter today. The runtime architecture is intentionally host-capability-driven so additional Adobe applications can integrate without being forced into Illustrator-specific semantics.
A single Illustrator target cannot be treated like an ordinary parallel server. COMTool keeps Adobe execution in dedicated STA workers and treats one strong target generation as a capacity-1 host-execution resource.
Live testing observed RPC_E_SERVERFAULT (0x80010105) from DoJavaScript while ordinary COM status reads still succeeded. Independent development probes reproduced the condition. COMTool therefore does not interpret a healthy COM heartbeat as proof that the script route is healthy, and it does not automatically replay a mutation after a script-route fault.
Applications restart. PIDs can eventually be reused. A COMTool target carries process-generation evidence, and a restarted Adobe process is always treated as a new target.
A Node responseTimeoutMs or AbortSignal can stop the caller from waiting. Once an operation has been submitted, local cancellation does not prove the host stopped. The result is therefore treated conservatively and any owned recovery authority is retained.
Destructive recovery fails closed if the exact process generation cannot be re-proven. Failure to read identity is not silently converted into evidence that the old process exited.
A fixed 1 MiB control frame keeps transport behavior bounded. Large successful results move through the verified artifact path rather than expanding the command surface into arbitrary filesystem access.
A project-local .NET 10.0.401 x64 SDK is bootstrapped into .dotnet/; it does not alter global PATH or the Windows registry.
Inspect the pinned toolchain:
.\scripts\dotnet.ps1 --infoBuild and test the repository without depending on a product-generation filename:
$sln = (Get-ChildItem -File *.slnx | Select-Object -First 1).FullName
.\scripts\dotnet.ps1 build $sln -c Release
.\scripts\dotnet.ps1 test $sln -c Release --no-buildVerify the embedded COM knowledge pack:
node scripts/build-knowledge-pack.mjs --checkRun the Node SDK gates:
node --test sdk/node/test/sdk.test.mjs
node sdk/node/test/typecheck.mjsStage an immutable development runtime:
.\scripts\stage-runtime-dev.ps1Build a production release:
.\scripts\release.ps1 -Version <semver>Production packaging refuses dirty or untracked source by default. Development validation can explicitly permit a dirty tree, and the resulting manifest records that provenance. Production release transactions cannot skip the deterministic test gate.
| Path | Purpose |
|---|---|
src/ComTool.Protocol/ |
Versioned request/result/value contracts |
src/ComTool.Runtime/ |
Operation catalog, examples, artifacts, and runtime-owned semantics |
src/ComTool.RuntimeHost/ |
Persistent RuntimeHost entry point |
src/ComTool.Supervisor/ |
Routing, leases, mutation state, workflows, host lifecycle, and recovery |
src/ComTool.Worker/ |
Dedicated host worker process |
src/ComTool.Hosts.Abstractions/ |
Host capability, launch, attach, and session contracts |
src/ComTool.Hosts.Illustrator/ |
Illustrator COM, script, debugger, and plug-in adapter |
src/ComTool.Knowledge/ |
Embedded Illustrator COM knowledge pack and query service |
src/ComTool.Transport.* |
Pipe, stdio, and MCP transport adapters |
src/ComTool.Cli/ |
Thin command-line client over RuntimeHost |
sdk/node/ |
Dependency-free Node client and orchestration layer |
schemas/ |
JSON Schema contracts |
protocol/operation-registry.json |
Checked-in runtime operation-catalog snapshot |
migration/ |
Historical compatibility and semantic-regression fixtures |
tests/ |
Deterministic protocol, runtime, host, transport, and compatibility suites |
spikes/ |
Executable architecture and host-behavior probes |
evidence/ |
Captured live and release evidence |
scripts/ |
Toolchain bootstrap, staging, knowledge generation, packaging, and install verification |
Architectural decisions and operational contracts live under docs/. PHASE0_STATUS.md is the executable architecture-gate ledger retained from development.
- Illustrator 30.6.0 is the only complete live-conformance host adapter today.
- Photoshop 2026 has second-host viability evidence, not full feature parity with the Illustrator adapter.
- Same-target Adobe execution is intentionally serialized.
- Generic script and COM escape hatches cannot provide the same narrow semantic guarantees as runtime-owned typed operations.
- Typed layer/artboard mutations have deterministic adapter coverage; live mutation conformance should continue to use disposable documents with exact identity and postcondition verification rather than user artwork.
- Authenticode signing is optional. Unsigned manifests and hashes establish package integrity, not publisher authentication.
- Some internal source filenames and compatibility fixtures preserve development-history naming. They are implementation details, not separate public release lines.
COMTool builds on Adobe's COM/OLE and ExtendScript host surfaces, the community-maintained docsforadobe ExtendScript documentation, the sibling ESON structured-data runtime, and VectorIPC for bounded native plug-in IPC.
The project also benefits from extensive differential and failure-mode testing performed during private development before the first public release.
COMTool is licensed under GPL-3.0-or-later. See LICENSE.