28 Commits
Author SHA1 Message Date
oma-release-bot[bot]andJack Chen 39eecb2697 chore: release core v1.21.3 and create-oma-app v0.8.10 (#627)
* chore: release core v1.21.3 and create-oma-app v0.8.10

* docs(changelog): tighten the 1.21.3 entry

State the security fix once under Security with the affected range, keep Fixed for the two behaviors earlier releases shipped, and drop the ELOOP claim, which described a defect that existed only inside the fix PR and never shipped. Compatibility now points callers who write through an outside link to widening the working directory.

---------

Co-authored-by: oma-release-bot[bot] <315310457+oma-release-bot[bot]@users.noreply.github.com>
Co-authored-by: Jack Chen <jack@yuanasi.com>
2026-10-17 00:00:52 +08:00
Jack Chen d4084ef803 fix(core): resolve dangling symlinks in the filesystem sandbox check (#626)
* fix(core): resolve dangling symlinks in the filesystem sandbox check

The sandbox resolver treated a dangling symlink as a path that does not exist yet, so the containment check passed on the link's own location and file_write then created the link's target, which could be outside the agent's working directory. The resolver now follows a dangling symlink to the target it names, rejects targets outside the root, keeps in-root targets writable, and bounds resolution at 40 hops so a loop fails the check.

Addresses GHSA-h42w-g3ch-rjjm (CVE-2026-108600).

* fix(core): fail the sandbox check when realpath reports ELOOP

A symlink chain longer than the kernel's link limit made realpath fail with ELOOP, and the resolver then followed the first link by hand and resolved the rest, accepting a path the kernel itself refuses. ELOOP from realpath now fails the check, and the resolver's own hop limit stays as the bound for links it follows by hand.

* fix(core): resolve symlink targets one component at a time

Following a dangling symlink, the resolver normalized the target with path.resolve, which collapses a/.. lexically. When a is itself a symlink the kernel steps out of the directory a points to, so a target such as pivot/../created.txt resolved to a different file than a write through the link would create. The resolver now walks the target component by component, following symlinks before applying .., and fails when .. would step out of a missing directory, as the kernel does.

The previous test for a self-referencing target built the link with path.join, which folded ghost/../self to self, so it only exercised a plain loop. Link targets in these tests are now joined without normalization.

* fix(core): keep the directory requirement of a trailing separator in symlink targets

A symlink target ending in a separator or "." names a directory, so the kernel refuses to open or create a file through it. The resolver dropped those components, and native realpath does the same on macOS, so a link to victim/ resolved to the file victim and file_write overwrote it. The resolver now requires an existing directory at the end of such a target, and confirms each realpath result with stat, which takes the same lookup as open, before trusting it.
2026-10-16 10:42:32 +08:00
oma-release-bot[bot] 6ec5498643 chore: release core v1.21.2 and create-oma-app v0.8.9 (#625)
Co-authored-by: oma-release-bot[bot] <315310457+oma-release-bot[bot]@users.noreply.github.com>
2026-10-10 03:23:19 +08:00
oma-release-bot[bot] 3563a9312b chore: release core v1.21.1 and create-oma-app v0.8.8 (#621) 2026-10-03 00:26:29 +08:00
Jack Chen 4e293a21a8 docs(core): clarify runImage validation errors (#618)
Clarify that validator-thrown values other than ImageModelError propagate, while ImageModelError follows normal retry and model-fallback rules. Align the guide, exported JSDoc, and changelog without changing runtime behavior.
2026-09-28 18:02:14 +08:00
oma-release-bot[bot] ab349e7ad0 chore: release core v1.21.0 and create-oma-app v0.8.7 (#615) 2026-09-26 01:32:08 +08:00
Jack Chen 01b63594fb feat(core): add Black Forest Labs image adapter (#614)
* feat(core): add Black Forest Labs image adapter

Black Forest Labs (FLUX) generates asynchronously: a submit returns a polling_url, the task is polled until ready, and the image is downloaded from a result URL. BlackForestLabsImageAdapter runs that flow with fetch, sends input images as input_image, input_image_2, and so on, and maps a WIDTHxHEIGHT size to width and height.

A moderated task shows up only in the poll status. The adapter reports Request Moderated and Content Moderated as a non-retryable content_policy failure as soon as it sees them, so runImage moves to the next model instead of polling until the attempt deadline and retrying. Failed and lost tasks are retryable.

The API key goes only to the API origin and HTTPS hosts under bfl.ai, keyed requests refuse redirects, and the pre-signed image download carries no key. providerOptions follows the same rule as the other adapters: fields that carry the prompt or input images are rejected at construction, and width and height are defaults that request.size overrides.

Follow-up to #613.

* fix(core): report detected image media type and the BFL dimensions sent

The built-in image adapters declared a fixed media type, which was wrong whenever providerOptions chose another output format, for example output_format png on Black Forest Labs or jpeg on OpenAI. All four now report the type read from the returned bytes and fall back to the old default only when the bytes are not a recognized image.

The Black Forest Labs attempt parameters kept default width and height from providerOptions even when request.size replaced them in the request body. They now record the same width and height the request sent.

* fix(core): keep a submitted BFL task through transient poll failures

A transient failure while polling or downloading, such as a 429, a 5xx, or a network error, escaped the adapter as retryable, so runImage retried generate() and submitted another billable task while the first kept running. The Black Forest Labs adapter now retries those failures in place until the attempt deadline, honoring Retry-After. Non-retryable errors still throw at once, and so does a 4xx on the download, since an expired delivery link cannot recover without a new task.

The adapter contract now says what it meant: an adapter must not resubmit a generation internally, while retrying a status poll or download of an already-submitted task is allowed.

* fix(core): type rate-limited and timed-out image downloads

downloadImage turned every non-2xx status into api_error, so a 429 or 408 on a pre-signed result URL did not count as transient for the Black Forest Labs in-place retry, escaped to runImage, and resubmitted a billable task. A download 429 is now rate_limit with Retry-After and a 408 is timeout, so both are retried in place against the existing task.

* fix(core): scope BFL credentials and keep a submitted task final at the deadline

The Black Forest Labs adapter sent its key to any HTTPS host under bfl.ai. Behind a proxy that key belongs to the proxy, and a forwarded BFL polling URL would receive it. Sibling BFL hosts now qualify only when baseURL is itself a BFL host; otherwise the key goes to the configured origin alone.

A transient outage that lasted past the attempt deadline still led runImage to retry generate() and submit another billable task. Once a task is submitted, the adapter now reports a deadline as a non-retryable timeout, and runImage keeps a non-retryable adapter verdict at the deadline instead of replacing it with a retryable timeout, so the chain moves to the next model. A deadline before the submit completes stays retryable.

* fix(core): tell deadlines from cancellation and end BFL turns on permanent download failures

runImage now aborts an attempt's signal with a DOMException named TimeoutError when the deadline fires, the same convention as AbortSignal.timeout(), and with the caller's own reason on cancellation. The Black Forest Labs adapter turns only a deadline after the submit into a final timeout; a caller that cancels a direct generate() call gets its own abort reason back.

A permanent failure downloading a submitted task, such as an expired or missing result link, was still retryable, so runImage submitted and paid for another task. Once the task exists, a download failure that is not transient now ends that model's turn as non-retryable.

* fix(core): end a submitted BFL task's turn on any failure and keep its cost

Several failures after a Black Forest Labs task was submitted were still retryable: a Ready status without result.sample, an Error or Failed task, and a lost task. runImage retried them by submitting and paying for another task. Every failure after the submit now ends that model's turn as non-retryable.

The adapter's in-place retry of transient poll and download failures slept through any Retry-After, so a header above runImage's maxRetryAfterMs stalled the attempt until its deadline. runImage now passes its cap to adapters as ImageCallOptions.maxRetryAfterMs, and a longer wait ends the turn at once.

A failure after the submit also dropped the task ID and the cost BFL reported, because adapter params were only returned on success. ImageModelError now carries optional params, runImage copies them into the failed attempt record, and the BFL adapter attaches the task and its cost to every post-submit failure.

* fix(core): close the last BFL paths that resubmit a created task

Two failures after a successful Black Forest Labs submit still reached runImage as retryable and led it to submit and pay for another task: a submit response without polling_url, and a result download that answers 200 with bytes that are not an image. Both now end that model's turn as non-retryable invalid_output and carry the task ID and cost into the failed attempt record. The adapter checks the downloaded bytes itself instead of leaving it to runImage, whose retry of an invalid output would resubmit.
2026-09-24 03:59:11 +08:00
Jack Chen 213bbf531b feat(core): add OpenRouter image adapter (#613)
* feat(core): add OpenRouter image adapter

OpenRouter serves image generation and editing through its own /images endpoint, with input images in input_references, so OpenAIImageAdapter pointed at an OpenRouter baseURL cannot reach it. OpenRouterImageAdapter calls that endpoint with fetch, reads base64 output only, rejects a mask instead of dropping it, and honors egressPolicy.

Safety rejections arrive in two shapes. OpenRouter's own moderation answers 403 with reasons or flagged_input in error.metadata; a rejection by the upstream provider carries the provider's code in error.metadata.provider_code and its original body in error.metadata.raw. Both are classified as content_policy, reusing the OpenAI and Seedream rules for upstream codes. The shared content-policy rule now also receives the HTTP status and the full error object.

Follow-up to #610.

* fix(core): classify forwarded safety rejections before retryable 5xx

A gateway such as OpenRouter can forward an upstream safety rejection with a 5xx status. The shared image error classifier checked the 5xx branch first, so the adapter's content-policy rule never ran and the rejection was retried on the same model. The content-policy rule now runs before the timeout and 5xx branches; a 429 is still classified as a rate limit.

* fix(core): reject image providerOptions that replace adapter-set fields

providerOptions was spread last into the request body, so a key such as input_references, image, prompt, or model silently replaced what the adapter built from the request, and an edit could run on different images or none. The OpenAI, OpenRouter, and Seedream adapters now reject those keys with a TypeError at construction and spread the remaining options before their own fields.

* fix(core): keep size and watermark as image providerOptions defaults

The reserved-key check rejected size in every image adapter and watermark in Seedream, which removed a reasonable way to set a default output size. Only keys that carry the request itself (model, prompt, n, the input image fields, mask, and response_format) are rejected now. A size in providerOptions is a default that request.size overrides, and Seedream watermark in providerOptions again overrides the adapter default; the OpenAI multipart body sends a single size field.
2026-09-23 23:49:17 +08:00
Jack Chen deab8804dd feat(core): add runImage and native image model adapters (#610)
* feat(core): add runImage and native image model adapters

Applications that generate or edit images had to call image APIs outside the framework and rebuild the same reliability code each time: retries, fallback across providers, error classification, and a record of every call. runImage() does this once, on an ImageModelAdapter interface that core owns, so no third-party SDK sits between OMA and the provider response it has to classify.

runImage() tries an ordered chain of models, retries retryable failures with jittered backoff that honors Retry-After, moves to the next model on anything else, reads the output format and size from the bytes, runs an optional validate check, and reports every provider call through result.attempts and onAttempt. It resolves with status failed instead of throwing when the chain runs out.

OpenAIImageAdapter (OpenAI Images API and compatible endpoints) and SeedreamImageAdapter (Seedream on Volcengine Ark) call the HTTP APIs with fetch, classify safety rejections as content_policy, fail rather than drop input they cannot use, request base64 output only, and honor egressPolicy. ImageModelError carries the normalized type and retryable flag, and isRetryableError() returns that flag.

Refs #469

* fix(core): keep runImage independent of attempt sinks and caller signals

onAttempt is no longer awaited, so a stalled or rejecting sink cannot hold up retries, cancellation, or the result. A caller that aborts while validate is pending now gets its abort instead of a successful image. Each attempt removes its abort listener from the caller's signal when it ends, so a signal reused across many calls no longer accumulates listeners.
2026-09-23 22:33:38 +08:00
oma-release-bot[bot] 4107fb1446 chore: release core v1.20.0 and create-oma-app v0.8.6 (#608)
Co-authored-by: oma-release-bot[bot] <315310457+oma-release-bot[bot]@users.noreply.github.com>
2026-09-19 03:04:33 +08:00
Jack Chen b67fd39fda docs: document the minSamples regression skip (#606)
The baseline section of docs/evaluation-ci.md enumerates every condition
that makes a regression comparison warn and skip, and it was missing the
one a threshold introduces: when it sets `minSamples` and either the
current or the baseline aggregate is short, the comparison does not run.
Record that condition and the consequence a gate author has to plan for,
namely that a short baseline now yields a warning where it previously
yielded a `regression` failure, so the guard loosens one check while it
tightens another.

Record the guard and `passSampleCount` in the changelog, set `minSamples`
on the runnable offline-regression example so the option appears where a
gate is actually configured, and credit the contributor.

Follow-up to #602.
2026-09-14 18:45:12 +08:00
oma-release-bot[bot]andJack Chen f9d3ca4606 chore: release core v1.19.0, otel v0.1.4, and create-oma-app v0.8.5 (#600)
* chore: release core v1.19.0, otel v0.1.4, and create-oma-app v0.8.5

* docs(changelog): correct two 1.19.0 claims and record two missed changes

Two entries described behavior that the code and docs do not support.

`restore()` was written as though the run store turns it into the resume
command. It is the resume command either way; `docs/run-store.md` states that
directly. What a run store adds is making a suspended record eligible and
taking the lease before the approval ledger is reconciled.

The `INVALID_TEAM_MAX_CONCURRENCY` entry asked readers to review `onProgress`
handlers that match event codes exhaustively. `OrchestratorEvent.type` gained
no member, `warning` already existed, and `data` is typed `unknown`, so no
exhaustive match can break. The entry now states what was added instead of
implying a compatibility review.

Two published changes were not recorded. The `delegate_to_agent` pool-exhausted
error text changed with the team concurrency work, and it is tool output rather
than a comment. The `@open-multi-agent/core` npm description was rewritten,
which is the first thing a reader sees on the package page.

Also drops "according to the supplied evidence" from the DeepSeek entry. It
described how the entry was written rather than how the name behaves.

* docs(changelog): describe the scaffolded _env.example change precisely

The 1.19.0 compatibility note said create-oma-app now scaffolds
OMA_MODEL=deepseek-flash. The template only names the model in a commented
DeepSeek block, so a generated project does not enable it by default. Say
what the file carries instead of implying an active setting.

* docs(changelog): name the scaffolded file as generated projects see it

The template ships as `_env.example` because npm strips real dotfiles on
publish, and `scaffold.ts` renames it to `.env.example` in the generated
project. A reader of this note has the generated project in front of them,
where the underscore form never appears.

---------

Co-authored-by: oma-release-bot[bot] <315310457+oma-release-bot[bot]@users.noreply.github.com>
Co-authored-by: Jack Chen <jack@yuanasi.com>
2026-09-12 01:42:13 +08:00
Jack Chen cc2a83395a fix: name the current DeepSeek Flash model instead of a retired alias (#598)
DeepSeek retired the V4-Flash model and named the current generation
`deepseek-flash`, with no version in the name. `deepseek-v4-flash` is still
accepted, but DeepSeek documents it as a temporary compatibility name and
already serves those requests from V4.1-Flash.

The repository handed that name to users in the places most likely to be
copied: the `oma` CLI default for `provider: 'deepseek'`, the adapter JSDoc
that ships in the type declarations, the provider table in docs, the
scaffolder env template, and every DeepSeek example. A copied name that the
vendor calls temporary has an expiry date the reader cannot see.

The provider table and the adapter JSDoc also stated that `deepseek-v4-flash`
resolves to DeepSeek-V4-Flash-0731. That model is retired, so the sentence
described a routing that no longer happens.

Two corrections of the same kind travel with it. The thinking-mode note
claimed the V4 models run at `high` effort, but the adapter forwards whatever
effort the caller passes and sets none itself, so the claim is dropped rather
than restated. Three `examples/basics` headers offered `deepseek-chat`, which
DeepSeek retired outright on 2026-07-24 and which no longer resolves at all.

The release bot moves to the same name. Callers that pass an explicit model
are unaffected, and the old name keeps working for as long as DeepSeek
accepts it.

Left unchanged on purpose: `bench/config.json`, whose values are frozen
controls for an already-run benchmark; the AtlasCloud pages, whose IDs come
from that vendor's own model library; and the changelog history.
2026-09-12 01:04:31 +08:00
Jack Chen dcffce18c8 feat(core): enforce TeamConfig.maxConcurrency on the team's pool (#593)
The field was public and typed but never read, so a caller who set
`maxConcurrency: 3` on a team typechecked cleanly, got no warning, and ran
at the orchestrator's value (5 by default). The class JSDoc example
demonstrated the dead field, and 22 examples copied it.

Wire it to the pool that runs the team. The team cap intersects with
`OrchestratorConfig.maxConcurrency` rather than replacing it: the smaller
of the two wins, so a team can narrow the pool for its own runs but never
widen it past the orchestrator ceiling. This covers all three `buildPool`
call sites, each of which already has the team in scope.

An unusable cap reports an `INVALID_TEAM_MAX_CONCURRENCY` warning on
`onProgress` and falls back to the orchestrator value, which is what those
configs already did while the field was ignored. Rejecting them would make
this a breaking change under the definition in RELEASING.md, and a value
computed from the environment is a realistic way to arrive at `NaN`. The
check still cannot be left to `Semaphore`, which rejects only values below
1 and would let `NaN` through to a pool that never grants a slot.

Every in-repo example sets the team cap equal to its orchestrator cap, so
their behavior is unchanged.
2026-09-08 17:55:31 +08:00
Jack Chen 71ac67a42f docs: add the run store to the unreleased changelog (#588)
The run store landed in #587 without a changelog entry. It meets both
warranted tests in .github/RELEASING.md: it adds public exports and a new
config field, and it changes runtime behavior an unchanged caller can
observe once a run store is configured.

Records the feature under Added and the fail-closed ownership and
lifecycle write semantics under Changed, both scoped to the opt-in so a
reader can tell that runs without a run store are unaffected.
2026-09-07 02:41:06 +08:00
Jack Chen 4ecb4d0510 chore: update copyright and maintainer attribution (#586)
- Updates the copyright line in LICENSE and the three package copies, adds a maintainer line to the package READMEs, and sets `author` in the published package.json files. CONTRIBUTORS.md and CHANGELOG.md updated accordingly.
- Copyrights of other contributors and the MIT license terms are unchanged.
2026-09-06 22:13:48 +08:00
oma-release-bot[bot]andJack Chen 8de4f3463b chore: release core v1.18.0 and create-oma-app v0.8.4 (#577)
* chore: release core v1.18.0 and create-oma-app v0.8.4

* docs(changelog): name the otel release and the judgePrompt call-count change

Two gaps in the generated 1.18.0 entry.

The entry names @open-multi-agent/otel twice but never its version, so a reader
cannot tell which release carries upstream-policy. Both opt-ins are required to
get tool input/output into OpenTelemetry, which makes that number exactly the
one a reader needs, and earlier entries stated otel's version even when it was
not republished. The generated GitHub Release body lists package versions in its
own section, so the omission was confined to this file.

judgePrompt also changed how often it runs, not only what it may return. #563
moved it from once per score() call to once per judge, which a caller whose
judgePrompt is costly or has side effects observes directly. The Compatibility
note is narrowed to the signature, which is the part that actually stayed the
same.

---------

Co-authored-by: oma-release-bot[bot] <315310457+oma-release-bot[bot]@users.noreply.github.com>
Co-authored-by: Jack Chen <jack@yuanasi.com>
2026-09-05 00:46:11 +08:00
oma-release-bot[bot]andJack Chen 3b21a7c33d chore: release core v1.17.0 and create-oma-app v0.8.3 (#558)
* chore: release core v1.17.0 and create-oma-app v0.8.3

* docs(changelog): state the actual JSON Schema required-field change

The generated Compatibility section described the release bot's own review
scope instead of the release. It also summarized the #549 alignment as
"potentially stricter", which is half right: switching from a wrapper-type
check to Zod's isOptional() makes .nullable() properties required and stops
z.any() and z.unknown() properties from being required.

Replace both bullets with what changed and who it reaches. The export claim
is verified against the v1.16.1 barrel and subpath exports; the schema
behavior against zod 3.25.76.

---------

Co-authored-by: oma-release-bot[bot] <315310457+oma-release-bot[bot]@users.noreply.github.com>
Co-authored-by: Jack Chen <jack@yuanasi.com>
2026-08-29 02:21:23 +08:00
oma-release-bot[bot]andJack Chen bdba3f9d20 chore: release core v1.16.1 and create-oma-app v0.8.2 (#543)
* chore: release core v1.16.1 and create-oma-app v0.8.2

* docs(changelog): scope the 1.16.1 entry to published behavior

Remove entries no consumer of the published packages can observe: the
release-bot fixes and the create-oma-app bump rule live in tooling that
is never published, the standalone consensus leak was introduced and
fixed inside this unreleased window so 1.16.0 never carried it, and
examples are excluded from the npm tarball. Correct the compatibility
note, which described the internal runConsensusCore signature as public
API.

Matches the editorial line set when 1.16.0 shipped in #523.

---------

Co-authored-by: oma-release-bot[bot] <315310457+oma-release-bot[bot]@users.noreply.github.com>
Co-authored-by: Jack Chen <jack@yuanasi.com>
2026-08-21 23:46:47 +08:00
Jack Chen 4ccc709cc2 fix(release-bot): restore the Unreleased changelog anchor (#542)
PR #523 collapsed the duplicated 1.16.0 sections by hand and removed the
`## Unreleased` header along with them. The bot resolves that header before
any analysis, so the 2026-08-21 scheduled run failed 25 seconds in without
planning a release.

Restore the header and guard it with a test that runs the real extractor
against the repository's own CHANGELOG.md, so the next removal fails a pull
request instead of a Friday run.
2026-08-21 23:13:01 +08:00
bdd332f719 chore: release core v1.16.0 and create-oma-app v0.8.1 (#523)
* chore: release core v1.16.0 and create-oma-app v0.8.1

* docs(changelog): dedupe 1.16.0 sections

Collapse the duplicated Added/Changed/Fixed sections and drop release-bot and release-process entries that are not core public behavior.

---------

Co-authored-by: open-multi-agent-release-bot[bot] <315310457+open-multi-agent-release-bot[bot]@users.noreply.github.com>
Co-authored-by: Jack Chen <jack@yuanasi.com>
Co-authored-by: Jack Chen <chenkaijie01@gmail.com>
2026-08-16 17:04:38 +08:00
Jack Chen 8fa3bb8362 feat(release): add OMA-powered release bot (#483)
* feat(release): add OMA-powered release bot

* fix(release): harden bot execution boundaries

* chore(release): schedule bot for Friday evening
2026-08-10 17:18:29 +08:00
Jack Chen 00f18baac6 chore: release core v1.15.0 and create-oma-app v0.8.0 (#482) 2026-08-10 10:56:00 +08:00
Jack Chen 04ade0fcbb docs: backfill changelog history (#464) 2026-08-03 01:15:13 +08:00
Jack Chen 7655299f1a docs: flag the 1.14.0 breaking changes and fix release note wrapping (#462)
1.14.0 raised the engines floor to Node 20 and moved openai from v4 to v6,
but shipped as a minor with no breaking-change notice. The conventional
commits `!` marker on the dependency upgrade never reached the changelog or
the release, so a `^1.x` caller on Node 18 received it with only an
EBADENGINE warning.

- CHANGELOG.md opens 1.14.0 with a Breaking changes section, collecting the
  Node floor, the openai major, and the newly rejected plans that were
  scattered across Changed and Compatibility.
- RELEASING.md gains a breaking-change decision step that runs before the
  release commit, and records that release bodies are rendered with GFM hard
  line breaks, so changelog text pasted at 80 columns renders as truncated
  lines.
2026-08-02 19:21:56 +08:00
Jack Chen 4b9e96f3da chore: release core v1.14.0 + otel v0.1.1 + create-oma-app v0.7.0 (#459)
Bump the three published packages for the coordinated npm release of the work
merged after v1.13.0, pin every generated starter to core 1.14.0, and finalize
the 1.14.0 changelog.

Core ships adaptive plan recovery, opt-in hybrid semantic execution routing,
DeepSeek V4 Flash reasoning controls, globally enforced task requirements, and
fail-closed validation for task DAGs and coordinator plans. The engines floor
moves from Node 18 to Node 20 across all three packages, so otel is republished
to carry that change; its core dependency range stays ^1.11.0.
2026-08-01 11:00:11 +08:00
Jack Chen 3a3da80686 chore: release core v1.13.0 + create-oma-app v0.6.0 (#443)
* chore: release core v1.13.0 + create-oma-app v0.6.0

* docs(core): document governance conclusion
2026-07-24 20:22:34 +08:00
Jack Chen 432b2fa6d1 feat(core): pipeline DAG tasks as dependencies complete (#434) 2026-07-23 19:52:48 +08:00