Skip to content

πŸ•ΉοΈ feat: Run Declared Project Actions on Attached Workers - #15943

Merged
danny-avila merged 4 commits into
devfrom
danny-avila/code-environment-actions
Sep 14, 2026
Merged

danny-avila merged 4 commits into
devfrom
danny-avila/code-environment-actions

Conversation

@danny-avila

Copy link
Copy Markdown
Collaborator

Summary

Attached workers can declare project metadata and fixed named actions. LibreChat validates and carries that metadata through its existing authorized workspace selection, displays repository/ref in the picker, and exposes advertised actions through the attached Bash tool's environmentAction parameter.

Actions retain Bash tool approvals, command limits and cancellation. They cannot be combined with arbitrary command text, arguments or a different working directory. Each dispatch carries the worker definition fingerprint; the worker resolves the command locally. Approval target binding includes that fingerprint, so changing a definition while approval is pending requires a new review.

Existing machine selection, agent workspace defaults, conversation binding and offline rejection remain the routing mechanism. No new database registry or infrastructure service is introduced. Workers without named environments retain the existing Bash schema and behavior.

Companion: LibreChat-AI/code-interpreter#209. Deploy this consumer and the updated Code API before enabling --environment on workers. Named actions deliberately retain ordinary command approval rules and do not grant new scope.

Validation

  • No-emit TypeScript checks: data-provider, API and client.
  • 96 focused API tests covering workspace resolution, commands, bridge descriptors and approval binding.
  • 16 focused workspace menu tests.
  • Local integration: LibreChat tool invocation through HTTP into the real native worker executor; verifies the file mutation and rejects a stale definition fingerprint. This fixture exercises the tool/executor boundary, not the production Redis admission service.
  • Scoped lint and whitespace checks.

Run the opt-in native integration with LIBRECHAT_CODE_TEST_PACKAGE=/path/to/code-interpreter/packages/code npx jest src/code/environment.live.spec.ts --runInBand --coverage=false from packages/api, after building the companion package.

@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review this exact head: 528ed58. Please audit authorized workspace metadata propagation, named-action validation, approval fingerprint pinning, cancellation, and backwards compatibility.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
πŸ“ Code Review βœ… Completed 2026-09-14T21:50:11.705968Z b71f023 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with πŸ‘€ while any review is running, comments if it has suggestions, and reacts with πŸ‘ once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown
Contributor

Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures.

β”‚ 22      β”‚ 'http://localhost:3080/api/mcp/servers'                                                                         β”‚ 2950.106000000029  β”‚ 4219.222000000009  β”‚ 200    β”‚
β”‚ 23      β”‚ 'http://localhost:3080/api/permissions/mcpServer/effective/all'                                                 β”‚ 2951.0100000000093 β”‚ 3713.0810000000056 β”‚ 200    β”‚
β”‚ 24      β”‚ 'http://localhost:3080/api/prompts/groups?limit=10'                                                             β”‚ 2951.261999999988  β”‚ 4250.498000000021  β”‚ 200    β”‚
β”‚ 25      β”‚ 'http://localhost:3080/api/keys?name=openAI'                                                                    β”‚ 3189.9070000000065 β”‚ 3898.93200000003   β”‚ 200    β”‚
β”‚ 26      β”‚ 'http://localhost:3080/api/presets'                                                                             β”‚ 3190.8709999999846 β”‚ 3907.356000000029  β”‚ 200    β”‚
β”‚ 27      β”‚ 'http://localhost:3080/api/tags'                                                                                β”‚ 3191.7490000000107 β”‚ 3903.5349999999744 β”‚ 200    β”‚
β”‚ 28      β”‚ 'http://localhost:3080/api/share/link/16390000-0000-4000-8000-000000000001'                                     β”‚ 3193.3520000000135 β”‚ 4219.504000000015  β”‚ 200    β”‚
β”‚ 29      β”‚ 'http://localhost:3080/api/messages/16390000-0000-4000-8000-000000000001'                                       β”‚ 3193.542000000016  β”‚ 4411.420999999973  β”‚ 200    β”‚
β”‚ 30      β”‚ 'http://localhost:3080/api/files/config'                                                                        β”‚ 3194.767999999982  β”‚ 4159.658999999985  β”‚ 200    β”‚
β”‚ 31      β”‚ 'http://localhost:3080/api/agents/tools/web_search/auth'                                                        β”‚ 3195.7029999999795 β”‚ 6928.951000000001  β”‚ 200    β”‚
β”‚ 32      β”‚ 'http://localhost:3080/api/endpoints/token-config'                                                              β”‚ 3195.9379999999655 β”‚ 4415.623000000021  β”‚ 200    β”‚
β”‚ 33      β”‚ 'http://localhost:3080/api/agents/tools/calls?conversationId=16390000-0000-4000-8000-000000000001'              β”‚ 3196.3030000000144 β”‚ 4729.304000000004  β”‚ 200    β”‚
β”‚ 34      β”‚ 'http://localhost:3080/api/agents/chat/status/16390000-0000-4000-8000-000000000001?generationProtocolVersion=2' β”‚ 4496.728000000003  β”‚ 4753.3610000000335 β”‚ 200    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Inspect .lighthouse HTML/JSON and e2e/lighthouse/README.md. Reuse loaded user/config data; overlap independent reads without bypassing authorization.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”
β”‚ (index) β”‚ audit                      β”‚ median               β”‚ limit β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 0       β”‚ 'largest-contentful-paint' β”‚ 4502.261             β”‚ 4500  β”‚
β”‚ 1       β”‚ 'cumulative-layout-shift'  β”‚ 0.016889534218763675 β”‚ 0.1   β”‚
β”‚ 2       β”‚ 'total-blocking-time'      β”‚ 191.34300000000076   β”‚ 500   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”˜

  1) [chrome] β€Ί e2e/lighthouse/load.spec.ts:10:5 β€Ί serial database latency stays within web-vitals budgets 

    Error: Median largest-contentful-paint must stay within 4500

    expect(received).toBeLessThanOrEqual(expected)

    Expected: <= 4500
    Received:    4502.261

       at audit.ts:159

      157 |   console.table(measured);
      158 |   for (const { audit, median, limit } of measured) {
    > 159 |     expect(median, `Median ${audit} must stay within ${limit}`).toBeLessThanOrEqual(limit);
          |                                                                 ^
      160 |   }
      161 |   return results;
      162 | }
        at auditPage (/home/runner/work/LibreChat/LibreChat/e2e/lighthouse/audit.ts:159:65)
        at /home/runner/work/LibreChat/LibreChat/e2e/lighthouse/load.spec.ts:33:19

    attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/test-failed-1.png
    ────────────────────────────────────────────────────────────────────────────────────────────────

    Error Context: e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/error-context.md

    attachment #3: trace (application/zip) ─────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip
    Usage:

        npx playwright show-trace e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip

    ────────────────────────────────────────────────────────────────────────────────────────────────


πŸ€–: global teardown has been started
2026-09-14 21:33:30 οΏ½[32minfoοΏ½[39m: οΏ½[32mMongo Connection optionsοΏ½[39m
2026-09-14 21:33:30 οΏ½[32minfoοΏ½[39m: οΏ½[32m{οΏ½[39m
οΏ½[32m  "bufferCommands": falseοΏ½[39m
οΏ½[32m}οΏ½[39m
πŸ€–:  βœ…  Connected to Database
πŸ€–:  βœ…  Found user in Database
πŸ€–:  βœ…  Deleted 1 convos & 2 messages
πŸ€–:  βœ…  Deleted user from Database
2026-09-14 21:33:30 οΏ½[31merrorοΏ½[39m: οΏ½[31mIndex build failed for "Conversation": Operation interrupted because client was closedοΏ½[39m
πŸ€–: global teardown has been started
2026-09-14 21:33:30 οΏ½[32minfoοΏ½[39m: οΏ½[32mMongo Connection optionsοΏ½[39m
2026-09-14 21:33:30 οΏ½[32minfoοΏ½[39m: οΏ½[32m{οΏ½[39m
οΏ½[32m  "bufferCommands": falseοΏ½[39m
οΏ½[32m}οΏ½[39m
πŸ€–:  βœ…  Connected to Database
πŸ€–:  ⚠️  User not found in Database
  1 failed
    [chrome] β€Ί e2e/lighthouse/load.spec.ts:10:5 β€Ί serial database latency stays within web-vitals budgets 

Open the full run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 528ed583e4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread api/server/services/ToolService.js
Comment thread packages/api/src/code/command.ts Outdated
Comment thread packages/api/src/code/bridge.ts
Comment thread packages/data-provider/src/code/workspace.ts
Comment thread client/src/components/Chat/Input/CodeWorkspaceMenu.tsx Outdated
@danny-avila

Copy link
Copy Markdown
Collaborator Author

@codex review current exact head b71f023. Model and execution definitions now share action schema/description builders, initialize and skill paths receive authorized metadata, approval previews show named arguments, status budgets cover maximum metadata, and ref-only metadata renders. Focused initialize/model, preview, maximum-payload and live native action regressions pass. Consumer-first rollout remains explicit; new declarations are opt-in only after all consumers are deployed.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: b71f02312c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danny-avila danny-avila changed the title feat: Use Declared Attached Project Actions πŸ•ΉοΈ feat: Run Declared Project Actions on Attached Workers Sep 14, 2026
@danny-avila
danny-avila merged commit bc12fad into dev Sep 14, 2026
40 checks passed
@danny-avila
danny-avila deleted the danny-avila/code-environment-actions branch September 14, 2026 22:35
danny-avila added a commit that referenced this pull request Sep 15, 2026
* πŸͺ‚ feat: Opt-In Text Fallback for Files No Tool Can Read

An upload routed to tools (`llmDeliveryPath: none`) reaches nothing when the
agent handling the turn runs neither Run Code nor File Search, or runs one that
cannot read the type. With `fileConfig.endpoints.<endpoint>.textFallbackWithoutTools`
(also accepted at the top level and inherited from `default`), such a turn
delivers the text extracted at upload instead. The setting is off by default,
which keeps today's behavior.

- Upload: when the setting is on and an inferred `none` route finds no reading
  tool, the built-in document parser or native text reader stores the text on
  the file, best effort, under the extracted-text size cap and content policy.
- Turn: `resolveTurnLLMDeliveryPath` re-resolves each inferred route against
  the tools the agent runs, and `applyTurnTextFallback` marks copies of the
  records a turn loads, so endpoint filtering, limits, inspection, history
  replay and steer media all see the text the turn delivers. The stored route
  is never rewritten.
- Explicit destinations, records predating routing, and turns whose tools are
  unknown are left as they are.

* πŸ§ͺ test: Resolve Custom Endpoint Fallback Under the Endpoint Name

Uploads route a custom endpoint agent by its saved provider, the endpoint name. initializeAgent marks turn copies before getProviderConfig swaps the provider for the backing client, and the endpoint keeps the name afterward, so the client and child encoders resolve the opt-in under the same endpoint. Cover both the initialization path and the post-initialization agent shape.

* ♻️ refactor: Share One Extracted-Text Storage Cap Across Uploads

The fallback defined its own copy of the 15 MiB cap context uploads already apply to extracted text. Export MAX_STORED_EXTRACTED_TEXT_BYTES from files/extract and use it in both places, documenting that it bounds what a MongoDB file document can hold rather than operator policy.

* 🧭 fix: Keep Fallback Text Wherever a Later Turn Can Deliver It

Extraction skipped uploads filed under a reading tool, so a handoff agent
without that tool, or the same agent after its tools or grants changed, had
no text to fall back to. Store fallback text for every inferred route on a
message attachment; files kept on an agent's tool resources never reach a
prompt and still skip it.

Turn marking only ran while the fallback was enabled, so a stored tool-routed
record whose type the endpoint now routes to text stayed marked `none` and
`extractFileContext` skipped it. applyTurnTextDelivery (renamed from
applyTurnTextFallback) now marks any stored `none` record with text that the
turn resolves to text, and returns early when no such record is loaded.

* 🚦 fix: Admit Every Attachment by the Route Its Turn Delivers

A record upload routed to tools that the current endpoint sends to the
provider stayed marked `none` on the turn copy, so admission skipped it while
BaseClient encoded its bytes, bypassing attachment count and size limits.
applyTurnDelivery (renamed from applyTurnTextDelivery) now gives every record
whose route upload inferred the route the turn resolves, before endpoint
filtering, limits and inspection, matching the run-file encoder.
hasInferredLLMDeliveryPath shares the inferred-route rule with the resolver.

Fallback extraction selected the document parser inside the CJS upload path.
resolveUploadFallbackText now takes the upload and picks parseDocument or
parseTextNative itself, with the extractors injectable, so process.js passes
request data only. Its tests run the real parser against the xlsx fixture and
the real native reader against temporary files.

* 🎚️ fix: Read Custom Endpoint Dialect From Config on Both Sides of Init

initializeAgent materializes turn routes before getProviderConfig swaps the
saved custom endpoint name in agent.provider for its backing client, so
routing read that name as a non-OpenAI dialect. Media a custom endpoint opted
into could then be materialized off the provider path, skipping admission,
while the finalized client still sent the bytes. resolveAgentDeliveryRouting
now reads the dialect with getCustomEndpointProvider, as upload does, which
holds before and after the swap.

The upload fallback plan now keys on the chosen-destination marker the turn
resolver reads, so an upload recorded as chosen, including every legacy
chooser upload, never pays for text a turn cannot deliver.

* πŸ›‘οΈ fix: Apply Turn Routes Only While the Record Stays Admitted

Resolution while initializing and at delivery can disagree: delivery reads a
Responses API choice only the finished client config holds. Applying a route
that takes a record out of admission, off the model path or to text it never
stored, would then skip the limits for a file the client still sends.
applyTurnDelivery now applies a route only while the record stays model-bound;
admitting a record the turn leaves out cannot slip past a limit.

* πŸ•ΉοΈ feat: Run Declared Project Actions on Attached Workers (#15943)

* feat: use declared attached project actions

* Complete named action definitions and approval previews

* Sort named environment imports

* Sort native environment fixture imports

* πŸ”“ fix: Keep Code Approval Mode Selectable Mid-Run (#15938)

* πŸ”“ fix: Keep Code Approval Mode Selectable Mid-Run

The composer's code approval mode selector was disabled while a run streamed, and a mode picked during a run was reverted when the final or abort event merged the server conversation back. The selector now stays usable in flight and both merges retain a mid-run selection, which the next send carries.

* πŸ”“ fix: Retain Mid-Run Approval Mode Across Cache and Recovery

Stamp the live approval mode onto the submission conversation at send so the retention baseline matches what was read, keep a retained mode in the conversation query cache on final and cancel, and rebuild failed or aborted conversations from a preset that carries the mid-run pick.

* πŸ”“ fix: Scope Retained Approval Mode to the Submitted Conversation

Retention now requires the live conversation to be the one the run submitted, or the id the server assigned to a new chat, so navigating elsewhere mid-run cannot write that conversation's mode back. A single recovery helper rebuilds failed or aborted conversations from the retained preset and patches the detail cache for every recovery site.

* πŸ”“ refactor: Keep the Local Approval Mode on Every Server Merge

Replace per-path retention with one rule: a locally picked code approval mode is newer than any server copy of the same conversation, so the final and abort merges and the error recovery preset keep it. The selector writes the pick into the conversation's detail cache, which navigation rebuilds from, so the pick is conversation-scoped without reading the index-global atom from the SSE handlers.

* πŸ”“ fix: Prefer the Live Conversation When Caching the Final Merge

The final handler's detail-cache merge now takes the local approval mode from the open conversation when it is the one that finished, falling back to the cached record only for a conversation that is no longer on screen.

* πŸ”“ fix: Keep the Local Approval Mode Through Settled-Start Reconciliation

The resumable transport's settled-start reconciliation and replacement handoff wrote a fetched conversation straight into conversation state and the detail cache. Both now apply the same local-mode rule as the event handlers, treating a new chat's pending id as the settled conversation's own.

* πŸ”“ fix: Seed the Detail Record When a Mode Is Picked

A conversation that has its id but no detail record yet now gets one from the live conversation when a mode is picked, the same key the resumable transport seeds optimistically, so recovery and navigation find the pick. A chat with no id yet still keeps the pick in conversation state alone.

* ⬆️ chore: bump agents to 3.8.7 (#15947)

* 🧭 refactor: Settle Turn Delivery Routing Once in Agent Initialization

`initializeAgent` resolved the provider and its client options only after the
turn's files were loaded and admitted, and the two delivery readers each rebuilt
the attachment routing from the agent object at their own moment: `BaseClient`
took the custom-endpoint dialect from the already-swapped `agent.provider`, the
run-file encoder from whatever child config it was handed.

Move `getProviderConfig`/`getOptions` ahead of file discovery, where nothing in
between fed them, and settle one `deliveryRouting` value with every input final:
the file policy under the endpoint's own name, the dialect its config declares,
the Responses API decision the model call uses, and the transcription setting.
`InitializedAgent`, the child encoder and `BaseClient` consume that value;
`resolveTurnLLMDeliveryPath` is the one place a stored route is resolved again.

* fix: Preserve attachment reachability across handoffs and late steers

* test: Type the untrusted attachment reference fixture

---------

Co-authored-by: Ravi Kumar L <upman@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant