Commit Graph

1341 Commits

Author SHA1 Message Date
jkomyno f05fbf6323 refactor(cli): replace listen create_trigger error explicitly on unknown slugs 2026-09-15 12:55:29 +02:00
jkomyno f3bfa06704 fix(cli): report unknown listen slugs when the inferred toolkit has an account 2026-09-15 12:16:43 +02:00
jkomyno bda26b8116 docs(cli): restore changelog and record listen and exit-code fixes 2026-09-15 12:16:42 +02:00
jkomyno 6555773152 fix(cli): port listen slug check to Effect v4 and pin exit-code contract
- listen: replace `Effect.catchAll`, which no longer exists in
  effect@4.0.0-rc.112, with `Effect.result` and narrow the lookup failure
  with `NotFoundError` from @composio/client instead of a structural
  status check.
- tests: `it.scoped` is gone from @effect/vitest v4 (`it.effect` already
  provides a Scope); switch the new cases over and assert the exit code on
  `triggers info` too, so the shared handler's contract is covered by more
  than one command.
- handle-http-error: also print the hint plainly when stderr is not a TTY.
- changelog: record the four user-visible fixes.

Claude-Session: https://claude.ai/code/session_01SvfF1wniMJsBDo5PaBjm2Y
2026-09-15 09:31:13 +02:00
Alberto Schiabel 6a321a13ca Merge branch 'next' into fix/cli-listen-stream-help-slugs-not-found 2026-09-15 00:36:48 +02:00
jkomyno 0faaacc48b Merge remote-tracking branch 'origin/next' into docs/tools-direct-examples
# Conflicts:
#	ts/packages/cli/test/src/commands/setup.cmd.test.ts
2026-09-15 00:11:37 +02:00
jkomyno be9f3fc4ca fix(cli): judge the shared account page complete by its size, not total_items
Whether `total_items` honors the `statuses` filter is a server detail. A
count that did not would make the shared list look truncated on every call
and quietly route the account picker onto the second request forever. Take
a page as complete when it is shorter than the requested size and carries
no cursor, and cover both the short-page and full-page cases.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-15 00:09:23 +02:00
jkomyno 588cfe65a0 fix(cli): normalize the toolkit slug the same way on both account paths
The derived toolkit filter lower-cased only, while the grouping helpers in
the same module trim first and the fallback sent the raw slug to the server.
A padded `--toolkit` value could therefore match or miss depending on how
many accounts the user has. Trim once, compare with the shared normalizer,
and send the trimmed slug on the fallback. Drop the optional chaining on
fields the client types as required.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-15 00:09:23 +02:00
jkomyno 14f6011d7c fix(cli): build the consumer cache refresh client with the nano id
`resolveCommandProject` gives `composio execute` the consumer project's
nano id, while the cache refresh asked `getFor` for a client keyed on the
long id. Two clients meant two memo keys, so the connected-account list
was still fetched twice per consumer execute. Use the same id on both.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-15 00:09:23 +02:00
jkomyno 7c0788584b fix(cli): share the tool-version lookup with the executor
The memoized `get_latest_version` is keyed on slug, org, and project. The
command's version check passed the resolved project while the executor's
schema lookup passed nothing, so the two never shared a key and
`composio execute` still issued both requests. Hand the executor the
project scope the command resolved.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-15 00:09:23 +02:00
jkomyno 370fd61882 fix(cli): do not memoize the missing-api-key tool version
The user context is live state that `login` fills in during the same
process. Memoizing the `null` answered before that would skip the version
check for that tool for the rest of the run. Resolve the key outside the
memo and cache only real lookups.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-15 00:09:23 +02:00
jkomyno 2a0d52c315 test(cli): reset in-process memos between test cases
A memoized tool version or connected-account list lives for the whole
process, so within one vitest file the first case that lets the lookup
succeed would hand its answer to every later case sharing the key. Expose
`clear()` on each memo plus a process-wide `clearInProcessMemos`, and call
the latter from the shared vitest setup before every test.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-15 00:09:23 +02:00
jkomyno caead95fdd fix(cli): own memoized runs with a detached fiber
`Effect.cached` makes the first caller the owner of the shared run, so
interrupting that caller stores the interrupt in the cell and replays it to
every fiber already waiting on it. On `composio execute --parallel` two specs
sharing a slug could take the second one down when the first one's
`Effect.all` failed on the tool-detail fetch.

Run the memoized effect on a detached fiber that completes a Deferred, so a
caller's interruption cancels only its own wait.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-15 00:09:23 +02:00
jkomyno 881e2f606e refactor(cli): evict every memoized failure and drop the unused list error
memoizeInProcess evicted a key only on typed failures, but Effect.cached stores
the whole Exit, so a defect or an interrupted first caller was replayed for the
rest of the process. Evict on any failure cause.

ActiveConnectedAccountsListError was only ever unwrapped to its cause by both
callers, so the shared list now fails with the raw rejection.
2026-09-15 00:09:23 +02:00
DakshM on Exe (exe.dev) a5f52ace4f perf(cli): list connected accounts once per execute
`composio execute` listed the user's connected accounts twice on every
call: once toolkit-filtered by the account picker, once unfiltered by
session creation. The two code paths cannot see each other. The
unfiltered list is now fetched once per process and shared, and the
picker derives its toolkit subset from it: same slug match, server order
kept, first 100, exactly what its own query returned. If the shared list
is truncated (more active accounts than one page), the picker falls back
to its original request, so results are identical in every case.

Interleaved A/B against the parent commit, compiled binaries, 15 runs
each on a small response: 1735 -> 1636ms best, 1934 -> 1845ms median.
One fewer request on the critical path, ~140ms.

`memoizeInProcess` memoizes an Effect per key for the process lifetime,
shares one run between concurrent callers, and drops failures so the next
caller retries. It also covers `get_latest_version`, which the definition
refresh fetched twice with the same key on the stale path. The executor's
own version lookup stays unscoped and separate; scoping it to org and
project would change which definition it resolves, so that is left as is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wx9gEjuiHux2weiHjdNcDs
2026-09-15 00:09:23 +02:00
jkomyno 884951b002 test(cli): guard the tokenizer and companion entries at startup
The startup-imports test now forbids js-tiktoken and both companion entry modules, matching the executable graph check in scripts/_shared.ts.
2026-09-14 20:07:43 +02:00
jkomyno 73f710b5cf fix(cli): repair a companion's own files before importing it
Bun keeps a failed or already-loaded import in its module registry, so
importing a companion again after a repair can still see the missing
dependency or the old module. Repair now runs before the single import, and
only when the requested companion's wrapper or its import graph is missing,
so unrelated missing companions still cannot block it.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-14 19:59:37 +02:00
jkomyno 19f7f2a002 fix(cli): scope companion repair and keep unmeasured large output stored
- Import an in-process companion before checking the rest of the set, so a
  missing unrelated companion cannot fail generate or run through an offline
  repair. Repair runs only when the requested module fails to load.
- Check each companion's required exports, so a file left by another release
  fails with a typed reinstall error instead of calling a missing export.
- When the tokenizer cannot load, store any response past the byte pre-filter.
  The four-bytes-per-token estimate can undercount, so it no longer keeps a
  response inline.
- Bundle the executable graph check with the release build's env inlining,
  defines, NODE_ENV and syntax minification.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-14 17:46:44 +02:00
jkomyno af90b83c01 Merge branch 'next' into claude/cli-startup-bundle-diet-c7xicz
Resolve the generate and run loaders in favor of the generation companion, drop the entry modules it supersedes, and let the startup-imports test allow src/generation/errors.ts as the binary build guard does.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-14 16:28:04 +02:00
jkomyno c9c3e49fee test(cli): wait for the hung host command before advancing the clock
The hung-native-host test forked setup, yielded once, then advanced the
TestClock by two minutes. Setup does real file I/O before it reaches the
host command, so on a slow CI worker the timeout was not yet armed when
the clock moved; the sleep then waited forever and the test failed on
vitest's own 15s limit. It flaked on next-based branches today, not
only this one. The hanging runner now opens a latch when setup reaches
it, and the test advances the clock only after that.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-14 16:19:30 +02:00
jkomyno c0053d7eeb test(cli): guard the startup path against eager compiler imports
A static import of typescript or src/generation anywhere on the command
tree silently restores ~165ms of module evaluation to every invocation,
and nothing failed. The test loads the command tree in a fresh Bun
process and asserts, via the module registry, that neither the compiler
nor the generation pipeline nor the run source transforms were
evaluated. Verified it fails on a stray static import.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-14 16:11:43 +02:00
jkomyno 6b0db59c30 refactor(cli): load each generation pipeline through one entry module
The generate handlers assembled their pipeline from three or four dynamic
imports and repackaged the results by hand. Each pipeline now has an
index module that re-exports what the handler needs, so the deferred
load is a single import and the boundary the handler crosses is named.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-14 16:11:43 +02:00
jkomyno 33e54a76a7 refactor(cli): import run source transforms through the src alias
Every other deferred import in the CLI spells its target with the src
alias; the run command was the one relative specifier.

Claude-Session: https://claude.ai/code/session_01MTb47vexN35pLGsSZwBmrJ
2026-09-14 16:11:43 +02:00
jkomyno 4ce75c9245 fix(cli): keep large executes working when the encoder companion fails to load
A large execute measured its output after the tool call had already succeeded,
so a missing encoder companion whose repair download failed turned a successful
call into a failed command. Fall back to a byte-based token estimate instead.

Load companion modules with Effect.tryPromise so an unloadable file is a typed
RunCompanionRepairError rather than a defect, and add the two companions as
tsdown entries so the dist build can resolve them.

The executable graph check listed @composio/core's root entry with a pattern
that could never match Bun's relative module paths. The root entry is still
bundled behind the file-upload dynamic import, so drop that entry and correct
the constants comment.
2026-09-14 15:52:49 +02:00
DakshM on Exe (exe.dev) 0a1464d5e8 perf(cli): move the compiler and tokenizer out of the executable
`composio --version` goes from 288ms to 199ms, peak RSS from 97.8MB to
77.3MB, and the executable from 85.9MB to 79.7MB. Every command benefits.

A compiled Bun binary parses its whole embedded bundle before the first
line of JavaScript runs, and #4468 had already made sure the TypeScript
compiler and the tokenizer rank table were never *evaluated* unless
`generate`, `run`, or a large `execute` response needed them. They were
still *parsed* on every start: the compiler alone was 44% of the
executable's JavaScript and the o200k rank table another 28%, so
`--version` spent ~75ms reading code it could never call.

Both now ship as companion modules next to the executable, through the
mechanism `composio run` already uses for its own runtime helpers:

- `generation-runtime.mjs` carries `src/generation/*`, the `composio run`
  source rewrites, `typescript`, `@composio/ts-builders` and
  `openapi-typescript`. `generate ts`, `generate py` and `run` load it
  with `loadInstalledCompanionModule`; from a source checkout the loader
  resolves the `.ts` next to `run-companion-modules.ts` instead, so tests
  and `bun run src/bin.ts` need no build step.
- `execute-output-encoder-runtime.mjs` carries `js-tiktoken/lite` and the
  rank table. `execute` loads it only once a response exceeds the 10KB
  byte pre-filter.

A companion bundles its own copy of `effect`, and a fiber cannot run
primitives built by another copy of the runtime, so nothing Effect-shaped
crosses the boundary: the generation companion exposes plain functions
and promises, runs its pipelines on its own runtime, and returns failures
as values that `src/generation/errors.ts` rebuilds as the CLI's own error
classes, stack included. Generated output is byte-identical to #4468 for
`generate ts`, `generate ts --transpiled` and `generate py`.

Both modules join `RUN_COMPANION_MODULE_BASENAMES`, so the build, release
packaging, install verification, `upgrade` and the self-repair download
pick them up unchanged. The three hand-maintained uninstall lists and the
upgrade E2E fixture gain the two file names.

Two smaller startup costs go with it:

- `src/constants.ts` imported `constants` from `@composio/core`'s root
  entry for two strings and two URLs, which evaluated the whole SDK at
  startup (~25ms of module-scope work, mostly zod schemas). The four
  values are spelled out and pinned to core's by a test.
- `tool-file-uploads.ts` imported three core helpers at module scope that
  only a file upload reaches; they are imported on that path now.

The binary build gains a guard: after bundling the companions it bundles
`src/bin.ts` once more unminified and fails if the executable's graph
reaches `typescript`, `js-tiktoken`, core's root entry, `src/generation/*`
or a companion entry. Without it a stray static import would put the
compiler back into the executable with nothing to notice.

Building also surfaced that `assertBundledRuntimeFiles` blanked string
literals to same-length runs of spaces, which made the import patterns'
`^\s*` backtrack quadratically across the compiler's multi-megabyte
embedded lib strings and stalled the build for over ten minutes. String
bodies are dropped now. (The check itself has never matched a specifier,
since the specifiers it looks for are the string literals it removes;
that is left as it was.)

Measured on the pinned toolchain, Bun 1.4.1+4661e494f, linux-x64, best
of 15, telemetry disabled, both binaries built in the same session:

  composio --version       288ms -> 199ms
  tools execute --help     287ms -> 202ms
  peak RSS                 97.8MB -> 77.3MB
  executable               85.9MB -> 79.7MB
  executable JavaScript    8.3MB -> 2.1MB (minified)

The `execute` tail after `execute.tool_call.end` is unchanged for
responses under 10KB (~10ms) and ~20ms slower above it (351 -> 374ms),
which is the on-demand parse of the 2.2MB encoder companion.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wx9gEjuiHux2weiHjdNcDs
2026-09-14 15:52:49 +02:00
Claude 3b18bf38eb perf(cli): defer the TypeScript compiler and generation pipeline
`composio --version` drops from 622ms to 408ms, and eager module evaluation
from 363.8ms to 130.0ms, measured on the pinned toolchain (Bun
1.4.1+4661e494f, linux-x64, best of 7, analytics disabled).

`commands/index.ts` builds the root command tree from every `.cmd.ts` module,
so evaluating any one command evaluated all of them. Two of those modules
reached the TypeScript compiler and the code generation pipeline, which
nothing but `composio generate` and `composio run` ever calls:

   155.8ms -> 8.0ms   commands/run.cmd
    63.5ms -> 2.5ms   commands/generate

The command tree itself is untouched. `Command.withHandler(input => Effect)`
already runs lazily, so moving these imports inside the handler bodies is
enough. Specs, flags, descriptions, subcommand wiring and `root-help.ts`
introspection all still resolve eagerly, which is why parsing, help rendering
and "did you mean" suggestions cannot shift.

run.cmd.ts was the CLI's only consumer of `import ts from 'typescript'`,
through three source rewrites that `composio run` applies to a user script.
Those move to `run-source-transforms.ts`, which the handler imports
dynamically. The test suite imports them from the new path.

ts.generate.cmd.ts and py.generate.cmd.ts pulled `src/generation/*` at module
scope. Both now resolve it inside the handler, immediately before the first
use.

A rejected import of a module bundled into this binary is an impossible
invariant rather than a recoverable failure, so these use `Effect.promise`
rather than `Effect.tryPromise`. The module registry memoizes each import, so
repeat calls within one run cost nothing.

Behavior is unchanged, checked rather than assumed. Eleven invocations,
covering `--help` at root and for generate, generate ts, generate py, run,
tools and execute, plus `version`, `--version`, an unknown command and an
unknown flag, produce byte-identical stdout, stderr and exit codes before and
after.

Verified with typecheck (src and test), oxlint, validate:boundaries,
validate:skills, and the full package suite: 1326 passed, 1 skipped.
`test/src/cli-main.test.ts` times out in this container and does so identically
on the parent commit (25.6s and 25.2s there, 24.5s and 24.3s here) because it
spawns the CLI from source against a 15s timeout. Not caused by this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EzaE7oGVgziJ5nRvBhcci2
2026-09-14 15:52:48 +02:00
jkomyno 9895726510 docs(cli): describe special-token literals as counted special tokens
encode(json, 'all') turns each literal into one special token rather than
counting its characters as text, so say that in the comment and the test name.
2026-09-14 15:51:31 +02:00
Claude 4f11a440e9 fix(cli): stop tiktoken special-token literals from failing execute
`Tiktoken.encode` signs as `encode(text, allowedSpecial = [],
disallowedSpecial = "all")`. The CLI passed only the text, so every special
token was disallowed and the call threw on any response containing the
literal `<|endoftext|>` or `<|endofprompt|>`. A 66-byte payload is enough.
Reading a README that documents a tokenizer hits it.

The throw landed in `prepareExecuteOutput`, after `spinner.stop('Execution
successful')` had already printed. So the tool had run, its side effects had
happened, and the CLI still exited 1 with nothing on stdout and no session
history entry.

Here the encoder is only a length gauge for the inline-versus-file decision,
so those literals are ordinary characters. Passing `allowedSpecial: 'all'`
counts them instead of rejecting the payload. Token counts are unchanged on
text that contains no special tokens.

The preceding commit's byte-length pre-filter hid this below 10KB by
skipping the encoder. Larger responses still reached it, and both call sites
were affected: the threshold check and the `tokenCount` reported for a
stored file. Both now go through one `countOutputTokens` helper.

The regression test drives the real command with a response holding both
literals, sized past the inline threshold so the count is actually computed.
Verified it fails without the fix, with the original error:

  Error: The text contains a special token that is not allowed: <|endoftext|>

Verified with typecheck (src and test), oxlint, validate:boundaries, and the
tools.execute and run command suites, now 90 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EzaE7oGVgziJ5nRvBhcci2
2026-09-14 15:51:31 +02:00
jkomyno 1d3af803a3 test(cli): synchronize setup timeout clock 2026-09-14 15:51:05 +02:00
jkomyno cbcdecf3a9 perf(cli): skip the tokenizer for run-origin executes and count tokens once
Check the invocation origin before building the tokenizer, since composio run
always prints inline, and pass the token count from the threshold check into
the stored-output summary instead of encoding the payload twice.

Add a test for a response past the byte pre-filter but under the token
threshold, and drop the unused ts-morph dependency.
2026-09-14 15:46:57 +02:00
Claude ebe8bb780f perf(cli): cut 221ms and 44MB RSS off every CLI invocation
A compiled Bun binary parses its whole embedded module graph before the
first line of JS runs, so bundled-but-unused code is paid for on every
invocation. Verified: a binary that bundles everything but evaluates only
console.log still costs ~235ms, against 15ms for a hello-world build.
Most of this bundle was code `composio execute` never reaches.

Measured on the pinned toolchain (Bun 1.4.1+4661e494f, linux-x64,
best of 7, analytics disabled):

  composio --version   970ms   -> 749ms    (-221ms)
  peak RSS             175.8MB -> 132.2MB  (-43.6MB)
  compiled binary      96MB    -> 86MB
  bundle               31.24MB -> 15.84MB

Four changes.

run.cmd.ts imported `ts` from ts-morph, which vendors its own copy of the
TypeScript compiler, so the binary carried two of them. The file uses only
createSourceFile, forEachChild, ScriptTarget, ScriptKind and five isX
guards, all available in the typescript copy that
src/generation/typescript/* already pulls in. Sharing one compiler also
means commands/generate and commands/run.cmd no longer evaluate a compiler
each.

js-tiktoken's main entry statically inlines all six BPE rank tables (gpt2,
r50k, p50k, p50k_edit, cl100k, o200k) as string literals; the CLI only ever
uses o200k, via encodingForModel('gpt-4o'). The lite build with that single
table produces identical token-id streams.

prepareExecuteOutput built the o200k rank table on every successful
execute, purely to compare the response against a 10k-token threshold.
Constructing it measured ~390ms in a compiled binary on the pinned
toolchain (390.1, 367.1, 418.6ms across three runs), against ~4ms to
encode a 7.5KB payload once the table exists. A BPE token always covers at
least one UTF-8 byte, so a payload of at most THRESHOLD bytes can never
exceed THRESHOLD tokens; checking byte length first reaches the same
decision without the tokenizer.

That ~390ms is construction cost measured in isolation, not an end-to-end
delta on a real `composio execute`. No credentialed run was available to
measure the whole command before and after, so treat it as the size of the
work removed from the success path rather than a verified wall-clock
saving. `COMPOSIO_PERF_DEBUG=1` reports the gap between
`execute.tool_call.end` and exit for anyone able to run it for real.

ToolsExecutorLive resolved a client through clientSingleton.get()
unconditionally, walking the project context off disk, then discarded it
because every caller on the remote-execute path passes one in.

Three behavioral deltas, none of them the tokenization result or the
inline/file decision:

1. Tiktoken.encode() throws on the literals <|endoftext|> and
   <|endofprompt|> appearing anywhere in the response, at any size (a
   66-byte payload reproduces it). That throw landed after
   "Execution successful" had printed, so the tool ran and the CLI still
   exited 1 with nothing on stdout. Responses at or under 10KB no longer
   reach encode(), so they now succeed. Larger responses still hit it;
   the real fix is passing allowedSpecial 'all' and is not in this commit.
2. TypeScript 6.0.2 (ts-morph's vendored copy) to 6.0.3. Differential
   tested: the three real parse helpers over 20 sources covering TSX,
   decorators, `using`, `satisfies`, import attributes, optional-chained
   calls and unicode gave identical output on all 60 comparisons.
3. Under COMPOSIO_LOG_LEVEL=Debug, ProjectContext's "resolved from ..."
   debug lines no longer appear on the remote-execute path. The local-tool
   path still calls get() and is unchanged.

Verified with typecheck:src, oxlint, validate:boundaries, the
tools.execute and run command suites (89 tests), and differential tests of
both tokenizers and both TypeScript versions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EzaE7oGVgziJ5nRvBhcci2
2026-09-14 15:46:57 +02:00
jkomyno 9a69683efb docs(core): fix tools getInput, proxyExecute, and execute examples
Correct the getInput JSDoc to pass the required text field, rewrite the
proxyExecute example with the real flat parameter shape and explain that
relative endpoints are appended to a base URL that may include a path, and
show where to find the version to pin for tools.execute.
2026-09-14 13:56:26 +02:00
Alberto Schiabel efe6d89864 chore(cli): refresh baked toolkit slugs (#4477)
## Summary
Automated refresh of the toolkit slugs the CLI knows without asking
the API, generated by
`ts/packages/cli/scripts/generate-toolkit-slugs.ts`.

Toolkits added since the last refresh currently cost users one
toolkit-list fetch (~2 s) the first time they run one of that
toolkit's tools. Merging this makes them free.

The generator refuses to write a list that is short, malformed, or
missing staple toolkits, so a bad fetch opens no PR at all.
2026-09-14 13:25:30 +02:00
jkomyno 3ab81b3373 chore(cli): refresh baked toolkit slugs 2026-09-14 06:36:42 +00:00
jkomyno 8bb1d29950 fix(core): raise tool not found only on 404/400
getRawComposioToolBySlug relabelled every client error, including an
invalid API key (401), as ComposioToolNotFoundError. Map only 404/400 to
not-found and wrap the rest in a new ComposioToolFetchError that keeps
the client error as cause. Toolkits.getToolkitBySlug compared against
the OpenAI APIError class, so its not-found branch never fired; import
the Composio client class instead. Python mirrors the mapping: an
unknown slug raises ToolNotFoundError (now a NotFoundError), anything
else propagates the composio_client error unchanged.

PRDE-1613

Claude-Session: https://claude.ai/code/session_017HtbhwMAKcfebo8HyXWa5s
2026-09-11 22:54:53 +02:00
Alberto Schiabel 2367b80d9d chore(ci): enforce agent guidance validators in CI (#4447)
This PR:
- Add `.github/workflows/agent-substrate.yml` running `pnpm
validate:agent-skills` and `pnpm validate:skill-routing` on every push
and pull request; both validators previously ran in no CI workflow
- No path filters on the trigger: the stale-guidance walk scans every
text file in the repo, so any change can affect the result (PR runs
restore caches but only `next` pushes save them, per the
`setup-node-pnpm-bun` guidance)
- Skip `vendor/` directories in the `validate:agent-skills`
stale-guidance walk, which was failing on read-only third-party
snapshots mentioning other tools' rule conventions
- Extend the validator's command scan to `CONTRIBUTING.md` (with a `pnpm
dlx` exemption), so its documented commands are checked against
`package.json`, `python/Makefile`, and `python/noxfile.py` like the rest
of the guidance
- Point the routing-test header, root `AGENTS.md`, and
`skill-maintenance` reference docs at the new workflow, and add a
"Working with AI Coding Agents" section to `CONTRIBUTING.md` covering
the inherited agent setup, the two checks, and the routing-probe
requirement for skill edits

## Context

These two validators are the only checks keeping repo-level agent
guidance honest: command names mentioned in guidance are verified
against `package.json`, `python/Makefile`, and `python/noxfile.py`, and
routing probes assert each skill stays the unique top match for its
representative task. Until now nothing enforced either one, and the
stale-guidance walk was already red on vendored trees — a failure no
guidance owner could fix, which trains people to ignore the check. This
makes both checks blocking everywhere they can bite.

## Verification

- `pnpm validate:agent-skills` — 19 skills, green, now including
`CONTRIBUTING.md` commands
- `pnpm validate:skill-routing` — 19 probes over 19 skills, green
- Workflow YAML parsed; oxlint and prettier clean on touched files
- `Agent Substrate` workflow ran green on this PR (42s) before the
trigger change and re-runs on every push
2026-09-11 17:31:23 +02:00
Alberto Schiabel d1bc94c580 test(ts/e2e): exercise core tool execution under Deno (#4418)
This PR:
- builds on top of https://github.com/ComposioHQ/composio/pull/3901
- adds a second Deno e2e suite, `@e2e-tests/deno-tool-execution`, that
drives the runtime instead of the import surface: session creation over
`fetch`, custom-tool registration, Zod validation/defaults/failures, and
in-process local tool execution
- mirrors the node `custom-tools` suite trimmed to its local-execution
half; remote coverage (tool chaining, weathermap) stays node-only
- imports the workspace's built dist via a relative path: a version-less
`npm:@composio/core` resolves from the registry (published pre-migration
0.18.1), ignoring the pnpm workspace symlink, so the direct path is what
makes the suite test the Effect v4 build CI bakes into the image
- keeps the only backend call to `composio.create()`; requires
`COMPOSIO_API_KEY` (CI provides it, the runner passes it into the
container)

## Context

The existing `deno/esm-basic` suite stops at the import/export surface
and, despite its `deno.jsonc` comment, resolves `npm:@composio/core`
from the registry rather than the workspace — so nothing under Deno
exercised the Effect v4 runtime. This suite closes that gap. A side
observation for a follow-up: `esm-basic` has the same
registry-resolution drift and tests the published package, contrary to
its README.

Validation:

- full local Deno matrix passes against the staging backend: 22 tests /
2 suites (11 + 11), fixture markers `SESSION_CREATE_OK` through `ALL_OK`
all observed
- `pnpm --filter @e2e-tests/deno-tool-execution typecheck` and prettier
clean
- `pnpm-lock.yaml` updated for the new workspace importer

Merge after #3901.
2026-09-10 18:33:23 +02:00
Alberto Schiabel 0abc629f5d refactor(cli): migrate to Effect 4 (4.0.0-rc.112) (#3901)
Rebuilds the Effect v4 port on top of `next` at `effect@4.0.0-rc.112`
(the newest release that clears the repository's 3-day
`minimumReleaseAge` gate). The three v3-compatible preparation PRs
(#4358, #4359, #4360) already landed on `next`, so this PR is now only
the cutover.

## What changes

- Pins `effect`, `@effect/platform-bun`, and `@effect/vitest` to exact
`4.0.0-rc.112`; drops `@effect/cli`, `@effect/platform`,
`@effect/platform-node`, and the `toml` override that existed only for
`@effect/cli`. The `ts/vendor/effect` source oracle moves to the
`effect@4.0.0-rc.112` release commit.
- Services become `Context.Service` classes with explicit `Default`
layers; `Either` becomes `Result`; `ParseResult` becomes
`Schema.SchemaError`; platform modules come from `effect/FileSystem`,
`effect/Path`, `effect/PlatformError`, `effect/unstable/process`, and
`effect/unstable/http`.
- The runner drives `Command.runWith` with v4's default help and error
rendering. `CliError.ShowHelp` carries its own exit code, help for
non-explicit invocations renders on stderr, and "Did you mean?"
suggestions render. `command-introspection.ts` is gone: v4 renders the
resolved command's help and the "missing value" tip itself.
- `composio --version`, `composio -v`, and `composio version` print the
same bare semver (`GlobalFlag.Version` is not enabled; the flag
spellings are rewritten to the `version` command before parsing).
- Root `--log-level` is a shared flag applied after the subcommand tree
is attached, so `composio --log-level Debug <subcommand>` both parses
and takes effect.
- Every `Flag.boolean` carries an explicit default, because rc.112 makes
boolean flags required when omitted.
- A `Result` is not an `Effect` at runtime in rc.112 even though the
type checker accepts `yield*` on it (the fiber dies with "Not a valid
effect"); every `Result` is lifted with `Effect.fromResult`, and the
skill/AGENTS guidance says so.
- Every `ChildProcess.make` site passes `extendEnv: true`, because
rc.112 no longer inherits the parent environment by default.
- `--log-level` and `COMPOSIO_LOG_LEVEL` are exact-match on the
`LogLevel` names (`All`, `Fatal`, `Error`, `Warn`, `Info`, `Debug`,
`Trace`, `None`) with no case folding, per the earlier review decision;
README updated.
- Spawned children pass `extendEnv: true`, because rc.112's
`ChildProcess` no longer inherits the parent environment by default.
- ISO timestamps decode through `Schema.DateTimeUtcFromString`;
`Schema.DateTimeUtc` is no longer a string codec in rc.112.
- `ConfigProvider.fromEnv()` snapshots the environment at construction
in v4, so providers that must observe later changes are built per read
(`plugin-hint.ts`, `install.cmd.ts`, `config.ts`) and tests use a
live-env provider helper.
- `cli-keyring` and `json-schema-to-effect-schema` are ported alongside
(the latter on `Schema.makeFilter`).
- The `effect-v4` skill, the `cli-command` and `typescript-testing`
references, `ts/packages/cli/AGENTS.md`, and the oxlint config are
updated to the rc.112 reality. The skill's example checker
(`.agents/skills/effect-v4/scripts/check-examples.mjs`, lifted from
#3851) compiles every TypeScript block in the skill against the pinned
packages.
- The `js-yaml` overrides move to the 4.3.2 / 3.15.2 lines that
GHSA-2883-xcg3-v3hh requires; `pnpm audit --prod` is clean apart from
the already-ignored `extract-zip` advisory.

## Behaviour notes

- `composio <unknown> --help` now prints the root help with exit 0 (v4's
global `--help` handling); `composio <unknown>` without `--help` still
fails with the unknown-subcommand error.

## Validation

- `pnpm --filter @composio/cli typecheck` (src + test): 0 errors
- `pnpm --filter @composio/cli test`: 127 files, 1325 tests pass, 1
skipped; `validate:boundaries` and `validate:skills` pass
- `@composio/cli-keyring` and `@composio/json-schema-to-effect-schema`
typecheck, test, and build pass
- `pnpm validate:agent-skills` and `pnpm validate:skill-routing` pass
(19 skills)
- oxlint clean on `ts/packages/cli`, `cli-keyring`,
`json-schema-to-effect-schema`
- CLI bundle and standalone binary build; smoke-checked `version`,
`--version`, `-v`, `--help`, unknown subcommand, unrecognized flag,
missing flag value
- Docker CLI e2e suites pass against an image built from this branch:
`version`, `toolkits-list`, `toolkits-info`, `toolkits-search`,
`setup-plugins`, `run`. `whoami` (needs an API key), `install` (needs a
release dir), and `upgrade` (needs network) were not run.

No changeset: `@composio/cli` is Changesets-ignored and the ported
sibling packages are private. Human-facing notes are in
`ts/packages/cli/CHANGELOG.md`.

https://claude.ai/code/session_01AW7ZPhfZuni6PrCJ9X86DX
2026-09-10 17:47:14 +02:00
Saransh Rana a69f82d676 fix(sdk): run typedoc without a shell in generate-docs (SEC-899) (#4416)
## Summary
`ts/packages/core/scripts/generate-docs.ts` joined `npx typedoc` and
every discovered `src/models/*.ts` file name into one string and ran it
with `execSync`, so a model file whose name contains shell
metacharacters would execute as a command. The script runs in CI on
every push to `next` with a write-scoped app token
(`generate-sdk-docs.yml`). Reported by AppSecure as SEC-899 (command
injection via documentation generation).

Fixes SEC-899 (internal tracker).

## Changes
- Build the typedoc argument vector as an array (`buildTypeDocArgs`) and
run it with `execFileSync`, so no shell is involved.
- Skip model files whose names fall outside `[A-Za-z0-9_.-]` (with a
warning) in `discoverModelFiles`.
- Regression tests in `test/scripts/generate-docs.test.ts`:
metacharacter names are dropped, entry points stay separate arguments.

## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Refactor/Chore
- [ ] Documentation
- [ ] Breaking change

## How Has This Been Tested?
```
pnpm --filter @composio/core exec vitest run test/scripts/generate-docs.test.ts
 Test Files  1 passed (1)
      Tests  12 passed (12)
pnpm exec prettier --check ts/packages/core/scripts/generate-docs.ts ts/packages/core/test/scripts/generate-docs.test.ts
All matched files use Prettier code style!
```
Node 24.17.0 and pnpm 11.8.0 via mise.

## Screenshots (if applicable)

## Checklist
- [x] I have read the Code of Conduct and this PR adheres to it
- [x] I ran linters/tests locally and they passed
- [x] I updated documentation as needed
- [x] I added tests or explain why not applicable
- [x] I added a changeset if this change affects published packages (not
needed: `scripts/` is a build-time script, not part of the published
package)

## Additional context
The generated docs output is unchanged; only how typedoc is invoked
changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: jkomyno <alberto@composio.dev>
2026-09-10 17:14:47 +02:00
Saransh Rana 9d0cb2cf89 fix(cli): cap remote file downloads in tool uploads (SEC-908) (#4417)
## Summary
AppSecure SEC-908 reported that remote files fetched from user-supplied
URLs were read into memory with no size cap. The core SDK
(`fileUtils.node.ts`, `RemoteFile.ts`, `ToolRouterSessionFileMount.ts`)
and the Python SDK already stream through a 100 MiB limit. The CLI's
tool-input upload path
(`ts/packages/cli/src/services/tool-file-uploads.ts`) was the last
remaining sink: `readFileFromUrl` still did `response.arrayBuffer()`, so
a large or never-ending response could exhaust memory before the
presigned upload was even requested.

Fixes SEC-908 (internal tracker).

## Changes
- `@composio/core` exports `readResponseBodyWithLimit` and
`MAX_URL_UPLOAD_SIZE_BYTES`, next to the existing
`assertSafeFileUploadPath` export, so downstream packages reuse the one
bounded reader.
- CLI `readFileFromUrl` uses it in place of `response.arrayBuffer()`;
behaviour is unchanged below the cap.
- Regression test: a response declaring a body above the cap is rejected
before `createPresignedURL` is called.
- Changeset for `@composio/core` (patch). `@composio/cli` is in the
changeset ignore list.

## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Refactor/Chore
- [ ] Documentation
- [ ] Breaking change

## How Has This Been Tested?
```
pnpm --filter @composio/core build
pnpm --filter @composio/core exec vitest run test/utils/readResponseBody.test.ts
 Tests  4 passed (4)
pnpm --filter @composio/cli exec vitest run test/src/services/tool-file-uploads.test.ts
 Tests  8 passed (8)
pnpm exec prettier --check <touched files>
pnpm exec oxlint <touched files>
```
`tsc --noEmit` on the CLI package reports the same pre-existing errors
on `next` and none in the touched files. Node 24.17.0, pnpm 11.8.0 via
mise.

## Screenshots (if applicable)

## Checklist
- [x] I have read the Code of Conduct and this PR adheres to it
- [x] I ran linters/tests locally and they passed
- [x] I updated documentation as needed
- [x] I added tests or explain why not applicable
- [x] I added a changeset if this change affects published packages

## Additional context
The other files AppSecure listed for this finding were already capped on
`next` (core: ecd0861, 8a56383; python: 54d07dc); this PR closes the
residual.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 15:57:13 +02:00
Alberto Schiabel 85996c4a1d fix(sdk): harden pusher auth and cross-origin redirect headers (#4406)
This PR:

- wraps `pysher.Pusher` in `_ComposioPusher`, whose channel-auth POST
carries a `(5, 15)` connect/read timeout and raises
`TriggerSubscriptionAuthError` (a `TriggerSubscriptionError`) on a
transport failure, a non-200, or a response without an `auth` token —
pysher 1.0.8 sent it with no timeout and turned a non-200 into a bare
`AssertionError` on the websocket thread, on every (re)subscribe
- keeps that POST a plain `requests.post(..., timeout=...)` rather than
routing it through `safe_request`: the endpoint is built from the
configured Composio API base URL, a fixed trusted host, not a value from
a response, and the SSRF guard would refuse a local dev base URL
- validates `pusher_cluster` against `^[a-z0-9-]+$` (non-empty, at most
64 chars) before pysher formats it into `ws-{cluster}.pusher.com`,
raising `InvalidPusherClusterError` that names the shape violation
without echoing the value
- replaces the `unittest.mock.MagicMock` stand-in for pysher's
connection logger with a dedicated `logging.Logger` (`NullHandler`,
`propagate=False`, disabled), so `unittest` leaves the runtime import
graph while raw frames stay out of user logs; a test asserts the module
source no longer mentions `unittest`
- strips `Authorization`, `Proxy-Authorization`, and `Cookie` from the
next hop when `ssrfSafeFetch` or `safe_request` follows a redirect to a
different origin; same-origin hops keep them. Manual redirect following
bypasses both `fetch`'s cross-origin rule and `requests`'
`rebuild_auth`, so neither guard applied it before — the gap #4387 left
out
- `@composio/slim` has no mirrored source (its build copies
`core/dist`), so the changeset covers `@composio/core` and
`@composio/slim` as patches

Verified with `pytest tests/test_triggers.py tests/test_url_safety.py
tests/test_path_join_guardrail.py` (192 passed), `ruff check` / `ruff
format --check` on the changed files, `mypy --config-file
config/mypy.ini` on the three changed modules with the noxfile's stub
pins (no issues), `vitest run test/utils/ssrfGuard.test.ts` in
`@composio/core` (42 passed), `pnpm typecheck` at the root (14 tasks
successful), and `oxlint` + `prettier --check` on the changed TypeScript
files.

https://claude.ai/code/session_016ZuBv7JhVdSYTLYcTy2VJr
2026-09-09 21:29:18 +02:00
Alberto Schiabel b4b9fc4a32 fix(json-schema-to-zod): guard schema pattern compilation (#4405)
This PR:

- Adds `src/utils/compile-pattern.ts` in `@composio/json-schema-to-zod`,
a shared helper that compiles `pattern` and `patternProperties` keys
through `new RegExp` inside a try/catch and rethrows a typed
`InvalidPatternError` (exported) that names the keyword, the pattern,
and the property path (e.g. `at properties.name`), mirroring the eager
`assertRegexCompiles` guard in `@composio/json-schema-to-effect-schema`.
- Adds a 1024-character cap on pattern length, reported through the same
error with `reason: 'too-long'`.
- Chooses fail-at-conversion over degrade-with-warning: the package has
no warning hook or lenient `refs` mode, its sibling packages and `oneOf`
handling already throw on schema defects, and a silently dropped
`pattern` would widen what a tool accepts without anyone noticing.
- Threads `refs.path` into `parseString` and `parseTypelessConstraints`
so the error carries the property path, and appends
`patternProperties.<key>` for dynamic-key objects.
- Makes `@composio/core`'s `jsonSchemaToZodSchema` include the cause
message in `JsonSchemaToZodError`, so the wrapped error names the
malformed property without unwrapping `cause`.
- Leaves out a nested-quantifier (star-height) ReDoS heuristic on
purpose: it flags linear patterns such as `^(\d+\.)*\d+$`, a wrong
rejection makes `tools.get` fail for the whole tool, and it cannot be
validated against the live toolkit catalog without false-positive risk.
Catastrophic backtracking from a hostile `pattern` remains a known
limitation; only zero-false-positive guards ship here.
- Adds `test/compile-pattern.test.ts` (`(` -> `InvalidPatternError` with
`SyntaxError` cause, length cap, typeless and `patternProperties` paths,
`^(\d+\.)*\d+$` still compiles and enforces, valid patterns still
enforced) and a core test for the wrapped message; adds a patch
changeset for both packages.
- Verification: `pnpm test` + `pnpm typecheck` in
`ts/packages/json-schema-to-zod` (4 files, 255 tests), `pnpm test` in
`ts/packages/core` (54 files, 1281 passed, 2 expected fail), root `pnpm
typecheck` (14/14), `lint:packages` and Prettier clean.

https://claude.ai/code/session_016ZuBv7JhVdSYTLYcTy2VJr
2026-09-09 19:53:07 +02:00
Alberto Schiabel 100d56866f fix(experimental): stamp eve durable callback descriptors (#4385)
This PR:

- Closes https://github.com/ComposioHQ/composio/issues/4343
- stamps eve's durable callback descriptors on every tool `EveProvider`
wraps, via the new internal `withDurableClosure(closure, callback)`
helper — eve only stamps them on `defineTool` calls its build transform
finds in the agent's own source, which never runs on this package inside
`node_modules`, so eve discarded the whole resolver result and the agent
silently lost every Composio tool
- persists `{ slug, binding }` per callback, where `binding` is an id
minted per `wrapTools` call and prefixed with a per-process token, and
re-attaches it to that resolve's Composio executor through a
module-level binding map. `executeTool` is bound to one Composio
session, so a slug-only closure would have routed a call to whichever
session resolved last; sessions for different users share one provider,
and eve's callback registry is keyed by tool name only, so the map lives
at module level rather than on the instance
- covers `execute` and, when `needsApproval` is set, `approvalRequest`;
the descriptor key is the global-registry symbol
`Symbol.for('eve:durable-dynamic-callback')`, so no eve internal is
imported and the stamp is inert on eve versions that predate the
contract
- adds 9 regression tests: descriptor presence and shape,
JSON-serializability of the closure, replay of execute and approval from
the closure alone, per-resolve executor isolation when sessions share a
provider, hooks of the producing provider on replay, the unknown-slug
and unknown-binding errors, that two fresh module instances never mint
the same binding id, and one suite that loads eve 0.52.1's own
`validateDurableDynamicToolCallbacks`, `replayDynamicTools`, and
callback registry from the installed package to validate and replay a
wrapped tool end to end. Before the change eve threw `Dynamic tool "..."
callback "execute" does not have a durable descriptor`

## Context

The reporter hit this on eve 0.50 as `non-serializable capture`; 0.52.1
reports the same root cause as a missing descriptor. eve exports no
public durable-callback helper (tracked at vercel/eve#2967), so the
provider stamps the descriptor itself rather than pinning users to an
older eve.

Bindings are kept for the life of the process: eve can resume a parked
call at any time. A binding lives only in the process that resolved the
tools, so a call parked across a restart cannot be replayed; the
per-process token in the id makes the stale closure fail the lookup
loudly instead of matching whichever resolve reused its counter value in
the new process. Growth is one entry per `session.tools()` resolve.

Docs (`/docs/providers/eve`) now state the contract, the restart limit,
and the real reason the `step.started` resolver runs each step
(principal re-evaluation and retry, cached per session).

Also unblocks `Docs - Tests` on this branch: the catalog refresh in
#4330 renamed Stripe's triggers, so the Stripe knowledge-base guide
cited two dead slugs and the corpus verifier failed for any PR touching
docs. The guide now cites only the renamed slug the catalog lists, and
`generate-toolkits.ts` fetches trigger types with `limit=1000` so the
catalog stops truncating every toolkit to its first 20 triggers.

https://claude.ai/code/session_019wRk1S4Z6V6FWr4UsybGvR


EOF -R ComposioHQ/composio
2026-09-08 20:51:33 +02:00
Alberto Schiabel ba85f4d183 fix(sdk): honor Fetch redirect semantics in both SSRF guards (#4387)
This PR:

- builds on top of https://github.com/ComposioHQ/composio/pull/4271,
whose commit it carries unchanged
- applies the Fetch standard's redirect method/body rules in **both**
SSRF guards via `_redirect_rewrite` / `redirectRewrite`: a `303` retries
as a bodiless request, a `301`/`302` does the same for a `POST`, and
`307`/`308` replay both
- narrows `ssrfSafeFetch` to the five statuses the Fetch standard calls
a redirect, so a `304` or `305` carrying a `Location` is returned to the
caller instead of followed — Python already used
`_REDIRECT_STATUS_CODES`
- drops `params` after the first hop in `safe_request`, since `Location`
carries the query for the target it names and re-appending handed a
query-string credential to a target that never asked for one
- purges the union of the Fetch `request-body-header` set and the two
`requests` also drops, identically on both sides
- blocks the IPv6 transition ranges the TypeScript CIDR list missed —
6to4 `2002::/16`, Teredo and the rest of `2001::/23`, local-use NAT64
`64:ff9b:1::/48`, `100::/64`, `2001:db8::/32`, site-local `fec0::/10` —
and the IPv4/IPv6 multicast and `192.88.99.0/24` ranges Python's
`is_global` missed

## Context

Both guards follow redirects by hand so every hop is revalidated against
the address blocklist. That also means neither inherits the method and
body rewriting `fetch` and `requests` would have done, so an upload
answered with a `303` was replayed — payload and all — at a result URL
that expects a GET.

https://github.com/ComposioHQ/composio/pull/4271 landed that rule in
Python only, which left the two SDKs disagreeing on the same wire
behavior. Reviewing for that divergence surfaced the redirect-status
set, the `params` replay, and the address-blocklist gaps above.
`2002:7f00:1::` is 6to4 for `127.0.0.1`, and it passed the TypeScript
guard as a public address.

Verified with `pytest python/tests/test_url_safety.py` (56 passed) and
`vitest run` in `@composio/core` (54 files, 1280 passed), plus `ruff`,
`tsc --noEmit`, `oxlint` and `prettier`. Fail-before confirmed: 10 of
the new TypeScript cases and 5 of the new Python cases fail against the
unmodified guards.

Two known gaps are deliberately left out, each deserving its own change:
neither guard strips `Authorization`/`Cookie` on a cross-origin
redirect, and a non-seekable Python body is re-sent exhausted on a `307`
where TypeScript throws a bare `TypeError` on a consumed
`ReadableStream`.

https://claude.ai/code/session_01SB3ZJdvoqBcRrWb2toWVrX

---------

Co-authored-by: ump45nose <52391318+ump45nose@users.noreply.github.com>
2026-09-08 20:51:12 +02:00
Alberto Schiabel 705591451c chore(deps): upgrade CI actions and every outdated dependency (#4381)
This PR:

- upgrades every CI action to its latest release (only
`changesets/action` had one: v2.1.1 -> v2.1.2, SHA-pinned) and every
outdated dependency across the pnpm workspace, the docs bun workspace,
and all three `uv.lock` files
- moves zod to 4.5.4 everywhere first-party — catalog, docs,
`@composio/json-schema-to-zod`, `@composio/claude-agent-sdk` and the
zod-v4 e2e fixtures; the `*-zod-v3` fixtures stay on 3.25.76 because
that is what they exercise
- moves `@mastra/core` 1.52.1 -> 1.53.0, which is the ceiling rather
than a preference: bisecting `ts/examples/mastra`'s `cf:dry-run` shows
1.54.0 moved the workspace/sandbox subsystem behind
`@mastra/core/agent`, which drags execa (-> `npm-run-path` ->
`unicorn-magic`) into the Workers bundle where esbuild cannot link it.
`@mastra/mcp` is capped at 1.17.2 for the same reason — 1.17.3 wants
`@mastra/core` >=1.64. The docs bun workspace mirrors that cap as an
explicit devDependency plus `overrides` entry, because bun does not
apply overrides to auto-installed peers
- clears every production advisory that has a published fix, so the
audit gate can run without `--ignore`, which does not filter a single
run: it writes the advisory into `auditConfig` and exits 0 whatever else
is outstanding, so the gate was passing over nine advisories
- `qs` -> >=6.16.0, `fast-uri` -> >=3.1.6, `toml` -> the 4.x line, all
via overrides in the existing `# temporary: … drop when` style
- `extract-zip` (GHSA-jmr9-qjv8-65gv) has no fixed version to move to —
2.0.1 is the newest release and GitHub records `first_patched_version`
as null — so it moves to `auditConfig.ignoreGhsas` pointing at the
`extractZipSafely` mitigation that already covers it
- GHSA-866g-f22w-33x8 (`@ai-sdk/provider-utils` 3.x, low) also has
nothing to move to: the advisory names 3.0.98 as patched but the 3.x
line stopped at 3.0.30 and GitHub records no fixed version. It only
enters the tree through `@mastra/core`, which is a peer or dev
dependency of every published package, so all flagged paths are private
examples and e2e fixtures. It goes in `ignoreGhsas` with that rationale
so the un-levelled `pnpm audit --prod` step stops posting a warning
comment on every PR
- widens `@composio/anthropic`'s `@anthropic-ai/sdk` peer range to
include `^0.124.0`, the line its devDependency now tests against (for a
`0.x` caret, `^0.120.0` excluded it); the package is in the changeset
for that reason
- adapts three call sites that upstream broke: `eve` 0.52 moved
`ApprovalContext` to `eve/tools/approval`, `@pierre/diffs` 1.4 gave
`FileDiffProps` a second type parameter, and `fumadocs-openapi` 11.4
fixed the undeclared-tag drop that a docs guard test asserted (the guard
now also asserts the page positively, so it cannot pass vacuously)
- drops the stale `hono` `minimumReleaseAgeExclude` entry (its comment
said to after 2026-08-06) and adds an `undici` `peerDependencyRules`
allowance for openai 7.10's new optional peer

## Context

Some upgrades were deliberately declined, each for a reason recorded
next to the pin:

- `vitest`/`@vitest/ui` stay on 4.1.11 —
`@cloudflare/vitest-pool-workers@0.22.0` (latest) peers on `vitest
^4.1.0`
- `undici` stays on `^7` in core — `pinnedDispatcher.node.ts` documents
that Node's `fetch` rejects undici 8 dispatchers
- the `pnpm` catalog entry stays on `^11` to match the mise-owned
toolchain
- `eve` stays on 0.27.6 in docs — 0.52 changes the `defineAgent` model
definition and the `useEveAgent` helpers, so `agent/agent.ts` and
`components/eve-chat.tsx` fail `types:check`; migrating the docs agent
is its own PR
- `@earendil-works/pi-coding-agent` stays on 0.84.4 — 0.85.x imports
`@earendil-works/pi-server` without declaring it, so `test/pi.test.ts`
fails to load

`declareOperationTags` is kept as a safety net rather than retired, even
though `fumadocs-openapi` 11.4 makes it redundant: removing it changes
how specs are normalised at sync time and is worth its own PR.

Verified locally: `pnpm build:packages`, `pnpm typecheck`, `pnpm test`,
`pnpm typecheck:examples`, `pnpm lint:examples`, `turbo cf:dry-run
--filter='./ts/examples/*'`, `pnpm peers check`, `pnpm audit --prod
--audit-level=high` (exit 0), frozen-lockfile installs for pnpm and bun,
docs `types:check` + 542 static tests, and Python `make chk` + `make
tst` (1790 passed).

https://claude.ai/code/session_018evFic47PFPXuB95uRE1aw
EOF -R ComposioHQ/composio
2026-09-08 16:15:34 +02:00
Alberto Schiabel 0fb479b8f1 Merge commit from fork
* fix(cli): escape generated source metadata

* test(cli): execute generated Python regression

* fix(cli): order Python fallback assignments

---------

Co-authored-by: jkomyno <12381818+jkomyno@users.noreply.github.com>
2026-09-08 14:59:48 +02:00
Alberto Schiabel 230f81a737 refactor(cli): import @effect/platform modules by subpath (#4360)
This PR:

- rebases onto `next` now that
https://github.com/ComposioHQ/composio/pull/4358 and
https://github.com/ComposioHQ/composio/pull/4359 are merged
- rewrites every `@effect/platform` and `@effect/platform-bun` barrel
import under `ts/packages/cli` (155 statements in 112 files) as a
per-module namespace import, e.g. `import * as FileSystem from
'@effect/platform/FileSystem'`
- adds both barrels to the `no-restricted-imports` lists for
`ts/packages/cli/src`, with messages pointing at the subpath form
- updates the boundary guidance in `ts/packages/cli/AGENTS.md` and the
`cli-command` skill to the subpath form

## Context

Both barrels are pure namespace re-exports (60 and 19 modules), so this
is import-only with no runtime change. Effect v4 spreads these modules
across `effect` (`FileSystem`, `Path`, `PlatformError`),
`effect/unstable/http`, and `effect/unstable/process`; with per-module
imports the port becomes a scripted path rewrite instead of
hand-splitting each barrel line. Third of three preparation PRs.

## Validation

- `pnpm --filter @composio/cli typecheck` and oxlint clean; a probe
barrel import in `src/` is rejected by the new rule
- `pnpm --filter @composio/cli test`: 127 files, 1318 tests pass, 1
skipped
2026-09-07 15:14:15 +02:00
Alberto Schiabel f5ff810f2e refactor(cli): define services with Context.Tag and thread argv explicitly (#4359)
This PR:

- builds on top of https://github.com/ComposioHQ/composio/pull/4358
- replaces the eleven `Effect.Service` files (fourteen services) with
`Context.Tag` classes that export a `<Name>Shape` type and an explicit
`static readonly Default` layer built from a `make<Name>` constructor
- removes the three `accessors: true` declarations; nothing in `src/`
used a generated accessor, and the one test that did now yields the
service
- builds test doubles with `Service.of({ ... })` instead of `new
Service({ ... })`, and types helper parameters with the `Shape` types
where the class had been used as a type
- passes the normalized argv from `bin.ts` into `runCli` and through
`cli-main.ts` instead of mutating `process.argv` and reading it back in
five places
- documents the service pattern in `ts/packages/cli/AGENTS.md`

## Context

Effect v4 replaces `Effect.Service` with `Context.Service`, which
generates neither a `.Default` layer nor accessors; with the
explicit-layer shape already on v3, the port turns each service into a
one-line rename. `Command.runWith` in `effect/unstable/cli` takes user
arguments explicitly, so `cli-main.ts` now receives argv rather than
re-reading process state. Second of three preparation PRs.

## Validation

- `pnpm --filter @composio/cli typecheck`, `validate:boundaries`, and
oxlint clean
- `pnpm --filter @composio/cli test`: 127 files, 1319 tests pass, 1
skipped
2026-09-07 12:34:13 +02:00
Alberto Schiabel 20aaa95c96 ci(ts): verify packed provider compatibility (#4355)
This PR:

- adds a clean consumer harness that packs core, its internal JSON
Schema dependency, and all ten TypeScript providers
- verifies tarball contents, npm installation, named public exports,
consumer typechecking, provider construction, and a credential-free
`wrapTool` conversion
- covers the current workspace core, one verified minimum-core lane per
provider, and the packed workspace core presented as `1.0.0-beta.0`
- preserves existing 0.x minimum peer ranges while recording the
verified floors separately for the future breaking release
- additively accepts core 1.0 prereleases without claiming stable 1.x
support yet
- widens the Anthropic and OpenAI Agents peer ranges to include the
upstream versions already used by this repository
- runs the gate in TypeScript CI and immediately before Changesets
publishing

The release guard fails before publication and its regression test
verifies build -> compatibility -> publish ordering plus failure
propagation.

## Non-breaking scope

No public API is removed or renamed, and the existing 0.x core peer
floors remain unchanged. All peer-range changes are additive. The gate
reports the nine floor corrections that should be made with the planned
breaking release.

## Validation

- `pnpm run check:provider-compatibility` (12 packed consumer lanes)
- `pnpm run test:provider-compatibility`
- `pnpm run test:release-workflow`
- `pnpm run build:packages` (19 packages)
- focused TypeScript compile and Oxlint checks
- Prettier, Changesets validation, and `git diff --check`
2026-09-07 12:33:53 +02:00
sdkrelease[bot] 61c3cb6481 chore(cli): refresh baked toolkit slugs (#4372)
## Summary
Automated refresh of the toolkit slugs the CLI knows without asking
the API, generated by
`ts/packages/cli/scripts/generate-toolkit-slugs.ts`.

Toolkits added since the last refresh currently cost users one
toolkit-list fetch (~2 s) the first time they run one of that
toolkit's tools. Merging this makes them free.

The generator refuses to write a list that is short, malformed, or
missing staple toolkits, so a bad fetch opens no PR at all.

Co-authored-by: jkomyno <12381818+jkomyno@users.noreply.github.com>
2026-09-07 12:33:27 +02:00