Paper Lab foundation: trade journal + Windows dev fixes - #1
Open
Adkr1989 wants to merge 342 commits into
Open
Conversation
…amily feat: alice CLI export family + codex headless reporting + headless management plane (0.40.0-beta.2)
ConPTY's CreateProcess only appends `.exe` when resolving a bare command name from PATH — it never tries `.cmd`/`.bat`. claude/codex ship native `.exe`s and resolved fine; opencode/pi install as npm shims (only a `.cmd`/`.ps1` exists, no `.exe`), so spawning them by bare name ENOENTed and the workspace never launched. Reported by a community user on Windows. Add `resolveLaunchCommand` (src/workspaces/win-command.ts): on win32, do the PATH × PATHEXT lookup ourselves — a native `.exe` is spawned directly, a `.cmd`/`.bat` shim is wrapped through `cmd.exe /d /c`. No-op off Windows. Wired into both node-pty spawn sites (interactive `persistent-session`, agent `probe`), where args are flags + a uuid so the shell wrap is injection-safe. Headless dispatch gets the same resolution for native-exe agents (claude/codex now work headless on win32 too), but `.cmd`-shim agents stay headless-unsupported on Windows: the task prompt is the trailing arg and routing it through cmd.exe would re-parse shell metacharacters (a real injection surface). That now fails with a clear, recorded reason instead of a silent ENOENT — interactive launch of those agents works. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(workspace): launch opencode/pi from the frontend on Windows
The preset catalog hardcodes selectable models per provider; several had drifted behind the providers' current releases. Most visibly, OpenAI Codex (Subscription) only offered GPT 5.4 / 5.4 Mini, so users couldn't pick GPT 5.5 (now Codex's default). Closes TraderAlice#284. Verified each against the provider's official model docs: - Codex (OAuth + API): add gpt-5.5, set as default - Claude (OAuth + API): add claude-opus-4-8, set as default; drop opus-4-6 - Gemini: 2.5 line -> gemini-3.5-flash (default) / 3.1-flash-lite / 2.5-pro - MiniMax: add MiniMax-M3, set as default - GLM: glm-5.1 now served internationally (z.ai) — drop "China only"; set as default; drop end-of-life glm-4.6 - Kimi (k2.6) / DeepSeek (v4) already current — unchanged Also bumps the runtime fallback defaults that apply when a profile omits a model (config.ts profile schemas, codex-provider, agent-sdk query) to match. Legacy migration schema + migration bodies left frozen. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…model-refresh feat(ai-config): refresh provider model catalog to current lineups
…onal modal
First step of collapsing the AI-config story onto Workspace-native execution
(see plan): make Alice's existing central credential store
(`aiProviderSchema.credentials`) the single source for provider creds and let
it flow into workspaces, in both directions, without coupling to the legacy
GenerateRouter/profile machinery.
- Map Credential → WorkspaceAiCred: new `credential-injection.ts`
(`credentialToWorkspaceAiCred`) — credential carries no model, so the caller
supplies model + adapter knobs (claude authMode via `resolveAnthropicAuthMode`,
codex wireApi).
- Template-driven injection: `TemplateMeta.agentCredentials`
(`{ agentId: { credentialSlug, model?, authMode?, wireApi? } }`, from
template.json). `injectWorkspaceCredentials` writes each declared agent's AI
config at create time via the adapter's existing `writeAiConfig` — so a
workspace boots ready-to-run, no manual UI step. Best-effort: a miss
(agent not enabled / no adapter / slug absent) warns + skips, never fails the
create.
- Security: injection runs POST initial-commit so the key never lands in the
first commit, and `_common.sh setup_git_excludes` now also covers
`opencode.json` + `.pi-agent/` (previously only claude/codex were excluded —
a latent leak for the other two adapters).
- Bidirectional modal (`WorkspaceAIConfigModal`): keeps free-text entry; the
picker now loads from saved *credentials* (not profiles); after a successful
Save of a hand-entered key, offers "Save to Alice" so the cred is solidified
into the central store and reusable everywhere. New
`GET/POST /api/workspaces/credentials` + `addCredential` (dedupe + slug) +
`inferCredentialVendor`.
World B (GenerateRouter / in-process providers / AgentWork) is untouched this
PR — it keeps running. Wiring automation onto headless Workspace dispatch and
deleting World B are the next two PRs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… AgentWork Wire autonomous cron onto the headless Workspace dispatch primitive — the step that makes the new automation actually run an agent. A cron job is now "run this prompt in that workspace, headless"; the workspace agent runs it and reports back via the Inbox (the run shows live in the Runs panel). - CronJob + CronFirePayload gain `workspaceId` + `agent` (the explicit CLI: claude / codex / pi / opencode). Threaded through add/update/fire. - Cron listener rewritten: on `cron.fire` it resolves the target workspace + adapter from a WorkspaceService ref and calls `dispatchHeadlessTask`, instead of emitting `agent.work.requested` into the legacy in-process path. Failures are LOUD (no target / unknown workspace / disabled agent / capacity / service not ready all log an error and skip) — never a silent orphan-fire. - main.ts: the workspaceServiceRef box is created before the cron listener and injected; WebPlugin fills it on start (an early fire is a loud skip). - Cron HTTP route + cronAdd/cronUpdate MCP tools accept the target; the MCP tool descriptions are updated off the stale "heartbeat tick" model. AutomationPage cron form gains workspace + agent selectors (agent options follow the picked workspace's enabled adapters); the card shows the target. - Migration 0008 disables enabled cron jobs that have no workspaceId (legacy AgentWork-era jobs) so they stop firing into the retired path — visible for the user to re-target or delete. Scope: cron only. Webhook ingest + heartbeat still ride AgentWork; they and the whole in-process World B (GenerateRouter / providers / AgentWork) are removed in PR3. After that, AgentWork has no callers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The AI Provider settings page was still profile-centric (backend / loginMethod / model wizard, profiles grouped by credential, an SDK-adapters column, Test via the in-process GenerateRouter) — all World-B concepts. Post-Workspace the only thing it needs to manage is Alice's central api-key credentials, the set that gets injected into workspaces (and pulled/pushed from the per-workspace modal). - Page is now a credential list + add/edit modal. The preset catalog is reused purely as an "add credential" helper (endpoint + model suggestions + request shape); no profiles, no active-profile, no SDK column, no backend/loginMethod. - Subscription logins (Claude Pro/Max, ChatGPT) are deliberately excluded — they live in the CLI's own auth (`claude login` / `codex login`), not in Alice. The modal only offers api-key presets. - Test runs the lightweight HTTP probe (`agent-probe`), dispatched by request shape (anthropic vs openai) — NOT the provider router. This is the change that lets World B be deleted in the next PR: the page no longer touches it. A credential carries no model, so the modal picks a throwaway "test model" just to verify the key. - New credential CRUD + probe-test routes under `/api/config/credentials` (reusing the core store fns); list redacts the raw key. Frontend client fns + demo handlers added. The legacy `/api/config/profiles*` routes + GenerateRouter still exist (unused by this page now); they're removed with the rest of World B in the next PR. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore the page's two-column layout: left is the credential vault, right is a read-only "Agent runtimes" panel introducing the four CLI runtimes a workspace can launch (Claude Code / Codex / opencode / Pi) — each with its auth model, wire shape, and tool-access story. Replaces the old World-B "Available SDKs" column; orients the user on which credential feeds which runtime. Editorial copy grounded in the adapters. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eserving)
Introduce a barId-keyed federation ("{sourceId}|{nativeSymbol}") over OHLCV
K-lines, unifying vendor (OpenTypeBB) and — Phase 1 — broker (UTA) sources
behind one interface. The AI analysis path routes through it and now reports
which source served the bars; the v1 calculateIndicator contract is unchanged.
OpenTypeBB is demoted from "the K-line provider" to one vendor source behind
the federation — extraction, not teardown.
- src/domain/market-data/bars/: BarService (searchBarSources + getBars),
barId parse/format, window resolution (count/asOf/range) + MAX_BARS cap,
vendor branch live + UTA branch via UTAAccountSDK.getHistorical.
- DataSourceMeta gains optional source/sourceId/barId/provider/barCapability;
flows to calculator dataRange unchanged.
- uta-protocol: Bar/BarParams/BarInterval + optional capability-gated
IBroker.getHistorical + AccountCapabilities.historicalBars;
UTAAccountSDK.getHistorical (server route lands in Phase 1).
- tool/analysis.ts -> barService; main.ts wires it into EngineContext.
- Tests: 12 deterministic unit (bar-service.spec.ts) + 7 real-data e2e
(bars.bbProvider.spec.ts, gated). Verified: tsc clean, pnpm test 1846/1846,
4 asset classes x yfinance/fmp with cross-source AAPL close agreement.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…x, shared
The credential-vault rewrite regressed the preset's enumeration value: it
rendered baseUrl as free text (dropping the China/International region options)
and didn't enumerate models where they're actually chosen. Restore it, and make
the pickers shared so both surfaces stay consistent.
- Shared `ui/src/lib/presetHelpers.ts`: presetModels / presetEndpoints /
presetBaseUrlDefault / vendorPreset / VENDOR_BY_PRESET / baseUrlToVendor (+
shape/wireApi/isApiKey). One vendor map, no drift between the two pages.
- New reusable controls (`components/credentials/PresetFields.tsx`):
- EndpointField — region <select> from the preset's endpoints, with a
"Custom…" escape that preserves a stored non-listed baseUrl (a bare select
would silently overwrite a custom endpoint with endpoints[0] on save).
- ModelCombobox — <input>+<datalist>: suggests known model ids (curbs typos
like minimax-m3 vs MiniMax-M3) while still allowing a free-typed id.
- AIProviderPage credential modal uses EndpointField (region dropdown back) +
ModelCombobox (test model). Runtime panel copy corrected (opencode/pi are
multi-provider; tools reach every runtime via MCP or the alice CLI).
- WorkspaceAIConfigModal model field → ModelCombobox; suggestions inferred from
the entered baseUrl's vendor (api.minimax.io → MiniMax-M3/M2.7), with a tab
fallback (claude→anthropic, codex→openai). Vendor-axis, so it works on any tab
pointed at any gateway; custom/local → free text.
- Storage: credentialSchema.baseUrl now trims + collapses '' → undefined so the
===-based dedup can't duplicate one logical credential (feedback_optional_
empty_string class); same trim applied in extractCredentialFromProfile. Specs
added.
- Delete dead CredentialCard / SdkAdapterCard (leftovers of the profile system).
Verified: catalog was never the bug (endpoints/models intact); regression was
purely UI. Adversarial review PASS on all requirements + edit-mode custom-baseUrl
preservation + the vendor-axis suggestion path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…— Phase 1 (CCXT)
Make broker OHLCV bars fetchable end-to-end and discoverable, mirroring the
getQuote path. The federated bar service can now resolve a broker barId
(= aliceId) to a UTA read endpoint, and searchBarSources surfaces broker
candidates alongside vendor ones.
UTA side (the getQuote-mirror chain):
- UnifiedTradingAccount.getHistorical: loud-refuse (CONFIG) when the broker
has no getHistorical, else _expandAliceIdIfNeeded + _callBroker.
- POST /api/trading/uta/:id/historical (revives Date params off the wire).
- CcxtBroker.getHistorical via exchange.fetchOHLCV + CCXT_TIMEFRAME map,
validates against exchange.timeframes (loud-refuse unsupported interval);
capability { supported, quality:'realtime' }. MockBroker: deterministic
synthetic bars. CCXT bars are free/keyless/REST — the cheapest wedge.
Federated search + wire-shape fix:
- Add ContractSearchHit (flat { source, contract, derivativeSecTypes }) to
uta-protocol — the shape /contracts/search actually returns. UTA's
contract-search.ts now uses it; UTAManagerSDK.searchContracts is corrected
from the mistyped grouped ContractSearchResult[] (no other caller).
- bar-service.searchBarSources unions vendor + UTA (allSettled, no dedup —
redundancy is the feature); secType → assetClass mapping; barId = aliceId.
Tests: CcxtBroker.getHistorical (map + loud-refuse + resolve-fail), MockBroker,
federated searchBarSources (vendor∪uta + one-side-fail). Verified: Alice tsc
clean, services/uta tsc 0 errors, pnpm test 1852/1852, bbProvider e2e 21/21.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second real broker bar source, mirroring CCXT. Drains Alpaca market-data v2 `getBarsV2` (async generator) into string-typed bars; `adjustment:'all'` for split/dividend-adjusted data. Free-tier accounts use the IEX feed (partial tape) → capability quality 'iex' so the federation can surface the entitlement. - AlpacaBroker.getHistorical + historicalBars capability. - ALPACA_TIMEFRAME map + AlpacaBarRaw type. - Tests: drain→bars mapping + resolve-fail. Verified: Alice tsc clean, services/uta tsc 0 errors, pnpm test 1854/1854. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…credential form Model the API wire shape as a first-class, extensible registry instead of a 2-way boolean: anthropic-messages / openai-chat / openai-responses (room for google etc.). A provider often exposes the SAME key behind multiple, incompatible shapes at different endpoints — so the create form lets you pick the shape and auto-fills the matching endpoint, rather than hard-coding one. Backend: - preset-catalog: `WireShape` + per-provider `wires` (shape × region endpoint table). GLM/MiniMax/Kimi/DeepSeek now carry BOTH their anthropic and openai-chat endpoints (verified URLs incl. /api/paas/v4, /v1, /anthropic); OpenAI carries responses + chat; Gemini the OpenAI-compat endpoint; custom all three. `WIRE_SHAPE_LABELS` shared. - presets.ts serializes `wires` to the frontend. - /api/config/credentials/test dispatches the prober by `wireShape` via a table (anthropic / openai-chat / openai-responses) — no if/else ladder. Frontend: - Shared `useTestGate` hook: the standard "Test passes for the current form before Save" gate, keyed by the testable fields. The credential vault now enforces it (Save locked until a green Test), matching the workspace modal. - Vault create/edit modal restructured: provider → API-mode selector (only when >1 shape) → region/endpoint (auto-filled, Custom escape) → key → test model → Test → Save. Cleaner header/footer, ✓-Tested state, stale-result re-test hint. - presetHelpers: presetWires / wireEndpoints / defaultWireShape / wireShapeForBaseUrl; dropped the now-dead presetShape/presetWireApi/ presetEndpoints/presetBaseUrlDefault. wireShape is a build-time + probe-time concept this round (the endpoint implies it; each adapter still speaks its own one shape). Making opencode/pi actually emit multiple shapes is a follow-on adapter change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fold the workspace modal's bespoke per-tab test-before-save logic onto the same useTestGate hook the credential vault uses — one abstraction, not two. Behavior preserved exactly: one gate per tab (so switching tabs keeps each agent's verdict), the result is bound to a `testKey` of the tested fields (agent-specific wireApi/authMode), Save stays locked until the current form has a passing test, and editing mid-flight re-locks. Removes the duplicate TestResult/formsMatch machinery. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Unify "public-data source" into the UTA structure (not a parallel/dual layer) via three account-level capability flags, then ship 3 built-in keyless data UTAs so any user gets crypto K-lines out-of-box without an API key. - UTAConfig gains keyless / readOnly / editable. keyless ⟹ readOnly. - UnifiedTradingAccount: stores keyless/readOnly; _assertWritable() loud-refuses stage* (place/modify/close/cancel) on a read-only account — the security boundary made explicit + enforceable. - CcxtBroker: keyless mode skips checkRequiredCredentials in init() (public endpoints only: loadMarkets / fetchTicker / fetchOHLCV). Threaded via the factory → brokerConfig. - UTAManager: passes keyless/readOnly through; getAggregatedEquity excludes keyless UTAs (no account → no phantom $0 in the aggregate). - Boot injects binance-readonly / okx-readonly / bybit-readonly (presetId ccxt-custom, keyless+readOnly+non-editable) — code-defined, NOT persisted to accounts.json, so they can't be edited/clobbered; a user's own exchange UTA uses a different id. They're redundant bar sources the federation surfaces for free (barId binance-readonly|BTC/USDT, etc.). Tests: write-guard (read-only/keyless refuse) + real keyless e2e (CcxtBroker.e2e.spec.ts, gated CCXT_E2E=1) proving init-without-key + getHistorical → real bars on binance/okx/bybit. vitest.e2e.config now includes services/**. Verified: Alice tsc clean, services/uta tsc 0 errors, pnpm test 1857/1857, keyless e2e 3/3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ved endpoint URL
Two gaps from the screenshot review:
1. The endpoint dropdown showed only a region label ("International (minimax.io)")
— the user couldn't see where requests actually go. EndpointField now renders
the concrete resolved URL (read-only) under the region select; only "Custom…"
makes it an editable free-text field.
2. wireShape wasn't stored on the credential — so a Test verified a shape the
saved credential didn't record. Critically, OpenAI Chat Completions and
Responses share ONE base URL (api.openai.com/v1), so baseUrl alone can't say
which shape a credential is. wireShape is now a first-class credential field:
- `credentialWireShapeEnum` + `credentialSchema.wireShape`; `addCredential`
dedup includes it (chat vs responses are distinct creds).
- GET/POST/PUT /credentials carry it; the vault form saves it and, on edit,
restores it from the stored value (falling back to endpoint inference for
pre-wireShape creds).
- The credential list shows a wire-shape chip.
Runtime consumption (adapters emitting per the stored wireShape) is the remaining
follow-on; this makes the credential self-describing and the Test meaningful.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ot tiny text The resolved endpoint URL was small muted "→ …" text below the dropdown — too small to register. Render it as a proper input box instead: read-only (but selectable, so the URL is copyable) when a region is picked, and editable the moment the user selects "Custom". Same visual weight as a real field, so it's clear exactly where requests go. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Phase 2)
A fresh, bounded, side-effect-free expression language for technical analysis,
keyed by barId so the model can target a specific source (broker/vendor) or mix
sources in one script. v1 calculateIndicator is untouched. Built from scratch
(the Nov-2024 thinking parser was not reused).
- calc-v2/: lexer → recursive-descent parser → evaluator. Surface = Python/
pandas subset: `s = bars("alpaca-paper|AAPL","1d",count=250)` let-bindings +
a final result expression; series columns s.close/.high/.low/.open/.volume;
functions sma/ema/stdev/max/min/sum/average/rsi/bbands/macd/atr/rvol/obv/mfi/
vwap (reuse indicator math verbatim); index s.close[-1]; arithmetic + - * /.
Indicators return the latest scalar (no [-1]); only raw columns are series.
- Compiler-grade diagnostics (structured CalcDiagnostic, first-class in the
tool contract): syntax w/ position, unknown-function + did-you-mean,
undeclared-name, arity, type, insufficient-bars, and pandas-reflex redirects
(method chaining → use sma(...); indexing a scalar → drop the [-1]).
- bars() window: count + asOf (count anchor) OR start/end date range. Fixed the
vendor branch to honor end_date (passed to OpenTypeBB + defensive post-filter).
- New calculateQuant MCP tool (description doubles as the language spec).
Tests: 9 parser + 12 evaluator unit + 6 real-yfinance e2e (SMA/RSI compute
correctly, start→end bounds the window, insufficient-bars is a structured error,
cross-source basis). Verified: Alice tsc clean, pnpm test 1898/1898.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
So the AI picks the right calculator and doesn't mix the two syntaxes.
- cli-commands: add `alice analysis quant` → calculateQuant (v2 now CLI-reachable
alongside `analysis indicator` = v1).
- calculateIndicator (v1) description: lead with v1-vs-v2 — v1 = quick ticker /
vendor-default; for a specific source (broker bars matching a held position) or
mixing sources, use calculateQuant. Flags the syntax difference (UPPERCASE
formula `SMA(CLOSE('AAPL','1d'),50)` vs lowercase pandas script `sma(s.close,50)`).
- openalice-cli SKILL.md: add a v2 quant example + an "indicator vs quant" note.
- cli-commands.spec: register the quant tool for the alias-map anti-rot guard.
Verified: Alice tsc clean, pnpm test 1878/1878.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ed unsaved) The key WAS saved — GET /credentials just redacted it, so reopening the edit modal showed an empty key field and looked like the save was lost. Return the apiKey from GET /credentials (same admin-gated exposure as the workspace credentials + agent-profiles routes) and pre-fill it in the edit form, with a Show/Hide toggle so the user can verify it. This also makes edit-mode testable (the key is present to probe with). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r's default In the workspace AI config modal, "Load from saved credential" flashed the base URL + key but left the Model field untouched — so a stale model from a previous provider (minimax-m3 on a GLM endpoint) stuck around and 404'd on Test. Loading now sets the model to the credential vendor's first preset model (user can still pick another from the combobox) and clears the prior test verdict. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…it at runtime The workspace Test dispatched by agent (opencode/pi always probed openai-chat), ignoring the credential's wireShape — so "Test passed" could mean a shape the runtime wouldn't actually use. Now both the vault and the workspace Test go through ONE dispatcher and the runtime honors the same shape end-to-end. - agent-probe.ts: `probeByWireShape(wireShape, …)` — single switch mapping shape → prober (anthropic / openai-chat / openai-responses), defaulting baseUrl per shape. Both /api/config/credentials/test and /api/workspaces/:id/agent-config/:agent/test call it. - Adapters honor wireShape (WorkspaceAiCred.wireShape, threaded from the credential): opencode picks the @ai-sdk package, pi picks the `api` field, codex is Responses-only (always wire_api="responses" — it hard-rejects chat), claude is anthropic. readAiConfig surfaces wireShape; injection carries it. - Workspace modal: FormState.wireShape (default per agent; set from the loaded credential's wireShape); Test + Save + dirty + testKey all include it. The workspace /credentials endpoint returns + POST stores wireShape. - So: a GLM credential created as anthropic, loaded into Pi, tests anthropic AND the pi adapter writes api=anthropic-messages — test == runtime. Specs: ai-config.spec updated for the wireShape round-trip + the codex Responses-only change; added opencode/pi per-shape emission tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…uthMode by baseUrl Two coherence fixes from the adversarial review of the wireShape change: - Codex is Responses-only, but loading a chat/anthropic credential into the codex tab set form.wireShape to that shape, so the Test probed it and "passed" while the codex adapter still writes wire_api="responses" — the exact test-lies-about-runtime bug. applyCredential now clamps the codex tab to openai-responses regardless of the loaded credential. - The per-workspace Test resolved the anthropic auth header from the claude-only authMode field, defaulting others to x-api-key — so a MiniMax-international (api.minimax.io, Bearer-only) anthropic credential tested through an opencode/pi tab would 401. It now resolves by baseUrl via resolveAnthropicAuthMode, matching the vault test + the runtime injection. Verified opencode's openai-responses → @ai-sdk/openai mapping against opencode's own provider docs (@ai-sdk/openai for /v1/responses, @ai-sdk/openai-compatible for chat) — correct, no change needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Foundation for "fill the key once": a credential can declare multiple wire
shapes (each with its endpoint) — its "wire capabilities" — instead of one
shape per credential. `credentialSchema.wires: { [shape]: baseUrl }`; legacy
`{baseUrl, wireShape}` kept and read transparently via the new `credentialWires()`
accessor (no migration — old creds upgrade on read).
addCredential now dedups by {vendor, authType, apiKey} (one key = one account)
and UPGRADES the matched record's wires in place rather than creating a
duplicate slug — which also fixes the dedup footgun the review flagged
(re-adding a pre-wireShape key no longer duplicates).
Next: preset region matrix + create form captures all of a region's wires +
injection matches a credential's wires to the agent's supported shapes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e it speaks
A provider exposes the same key behind several incompatible wire shapes that
differ only by endpoint (GLM: anthropic at /api/anthropic, openai-chat at
/api/paas/v4). Before, each shape was a separate credential — the user re-entered
the key per shape. Now a credential captures ALL of a region's wires at once
("fill the key once") and injection picks the shape the target agent speaks.
- preset-catalog: region-first matrix — `regions: [{ id, label, wires: {shape: url} }]`
(was shape-first `wires[]`). presets.ts serializes regions; the model field is
the only remaining schema-driven dropdown.
- Vault create form: pick provider → region → key. The "API mode" selector is
gone; the form shows the region's wire capabilities (read-only) and Test probes
the primary shape (one probe validates the shared key). Custom stays free-form
(shape + URL).
- Injection: `credentialToWorkspaceAiCred` picks the agent's preferred wire from
the credential's capabilities via `pickAgentWire`/`AGENT_WIRE_PREFERENCE`, and
returns null (loud skip) when none is compatible — so a chat-only key can't be
injected into codex (Responses-only).
- Workspace modal: the "Load from saved credential" picker lists only
tab-compatible credentials and loads the matched wire; codex shows few/none,
steering users to pi/opencode (the intended funnel).
- credentialSchema gains `wires` (partial map); legacy `{baseUrl,wireShape}` read
transparently via `credentialWires()` — no migration. /api/config/credentials
and /api/workspaces/credentials carry `wires`.
Specs: credential-injection.spec rewritten for the wires model + the
no-compatible-wire null/loud-skip; all green (1871).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mpty reply The Test "(empty reply)" came from a reasoning model (glm-5.1) spending the tiny 32-token probe budget entirely on reasoning, leaving content empty — the test connected, but the reply looked broken. - agent-probe: bump the probe budget to 512 (one-off per Test) so a reasoning model can finish thinking AND emit a visible reply; fall back to reasoning_content if the final content is still empty. - Both Test surfaces: when the reply is genuinely empty, show "provider reachable (returned no text)" instead of the awkward "(empty reply)". Also delete the now-unused EndpointField (superseded by the region-based form). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…entials-template-injection feat(workspace): central credentials — template injection + bidirectional config modal
…page-credential-vault feat(ui): AI Provider page → credential vault; Test via probe (unblocks World-B deletion)
The model loop runs inside the native workspace CLIs now; the parallel
in-process stack had no live consumers left (cron → headless dispatch in PR2,
Test → probe in PR3a). Removed end to end (-6.1k LOC):
- AI backends: ai-provider-manager.ts (GenerateRouter), ai-providers/{agent-sdk,
codex,vercel-ai-sdk,mock}/, sdk-adapters.ts, core/ai-config.ts.
- AgentWork: core/agent-work.ts + agent-work-listener.ts, and main.ts's
provider-chain / runner / listener / task-source wiring + EngineContext.router.
- Heartbeat (OpenClaw remnant — output was already stubbed, drove only the
deleted loop, redundant with PR2's cron→headless): task/heartbeat/, the
/api/heartbeat route, and the Automation › Heartbeat UI surface (page, sidebar
row, tab, URL redirect, i18n, demo mocks).
- Legacy profile surface: /api/config/profiles* + /sdk-adapters, the workspace
/agent-profiles route + listAgentProfiles (dead since PR1's credential picker),
and the matching demo mocks.
Kept: preset-catalog as a pure suggestion catalog (its sdkAdapters wiring
stripped); the credential vault + all of World A; the webhook /ingest +
agent.work.* event family (now producer-only / dormant — a future
webhook→headless listener can consume it; not removing a documented external
surface unilaterally).
Left dead-but-harmless for a focused follow-up: the core config `heartbeat`
section + `aiProviderSchema.profiles` schema/helpers (no consumers; removing
touches index-based config plumbing + exported helpers). Unused @ai-sdk/* deps
in package.json likewise deferred.
tsc (Alice + UI) clean; pnpm test 1732 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e endpoint CCXT's base enableDemoTrading sets urls.api = urls.demo; on an exchange with no demo domain (e.g. okx, whose demo IS the sandbox x-simulated-trading header) urls.api becomes undefined and the next request crashes deep in sign() with a cryptic "Cannot read properties of undefined (reading 'rest')". Detect the broken post-state and throw a clear CONFIG error instead, and wrap setSandboxMode symmetrically. Real-ccxt construct-time regression spec. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Binance — the world's largest crypto exchange — gets a dedicated preset. Modes: live + Demo Trading (the unified demo-*.binance.com simulator via enableDemoTrading, spot + futures). Legacy Testnet is deliberately omitted: futures testnet is deprecated (CCXT hard-throws) and spot-only testnet would need a market-type-scoped account (tracked in ANG-111's sibling work). Verified end-to-end on a real Binance demo account via the alice-uta CLI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(uta): CCXT-custom boolean create bug + demoTrading guard + Binance preset
Two-sidebar hierarchy: - Three-tier depth: ActivityBar → bg-tertiary (recessed rail), Sidebar bg-secondary, main bg — the two left columns now read as distinct zones instead of one undifferentiated slab. - Align the three top surfaces to a 40px header rhythm (branding h-10 px-4, was px-5 py-4 ≈ 60px); slim rail 216→200; sidebar Panel → px min/max (200/420) + preserve-pixel-size for a stable fixed-width feel. - Active nav item → bg-accent-dim (its old bg-tertiary collided with the new rail background); footer rhythm py-2→py-1.5. Responsive: - Kill the 768–1024 dead zone: add a middle tier (rail static ≥768, sidebar static ≥1024, drawer below) so the main pane keeps full-width-minus-rail and its md:-keyed content (Portfolio stat grid) no longer overflows/overlaps. - Unify mobile drawer width 216→280 (matches the secondary drawer, no drill-in jump); body scroll lock while a drawer is open; z-index ladder — dialogs z-60 / toasts z-70 above the nav drawers z-50. - Re-clicking the active rail item re-opens a hidden sidebar instead of toggling selection off (sidebarVisible). Consistency / cheap fixes: - SidebarRow: optional icon/title slots; migrate TrackedSidebar onto it. - code-copy hover → --color-overlay-strong (was invisible in light); PageHeader → .text-title; responsive grid + dialog widths on News / Quote / AIProvider / Dialog. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix(ui): responsive shell + two-sidebar hierarchy pass
Trading workbench wants the most capable model, not fast/cheap tiers. Bump + prune each preset's model list: - GLM: glm-5.1 -> glm-5.2 (sole flagship); drop 4.7-flash, 4.5-air - Kimi: default kimi-k2.7-code; keep k2.6; drop k2.5 - Claude: drop superseded Opus 4.7; drop Haiku 4.5 (cheap tier) - Codex: drop gpt-5.4-mini (cheap tier); gpt-5.5 stays current - DeepSeek: drop v4-flash (cheap tier) - MiniMax: unchanged (M3 already the current flagship) Also refresh the stale Workspace AI-config modal placeholders (claude-sonnet-4-6 -> claude-opus-4-8, gpt-4o -> gpt-5.5). New CN model ids (glm-5.2, kimi-k2.7-code) verified against provider docs; confirm via the credential Test path before relying on them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…datory-reasoning models Kimi k2.7 (and similar) 400 with 'invalid thinking: only type=enabled is allowed for this model' when the probe omits thinking — they cannot run it disabled. The Anthropic probe now retries once with thinking enabled (budget_tokens 1024, max_tokens bumped to 2048) only when the error is thinking-related, leaving the common path (Claude/GLM/MiniMax/DeepSeek) untouched. Verified end-to-end against the live Kimi endpoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AI preset model refresh + thinking-probe fix + 0.51.0-beta.1
…date check The packaged Electron shell only spawned Alice, which hard-requires OPENALICE_UTA_URL at boot and threw without it — so the desktop app never ran end to end. Port the prod.mjs supervision into apps/desktop: spawn UTA, poll /__uta/health, spawn Alice with OPENALICE_UTA_URL injected, watch restart-uta.flag to respawn UTA, and cascade-shutdown both children. - Cross-platform tree-kill (taskkill /T /F on win32) so the two children and their PTY/CLI grandchildren don't leak on quit / UTA restart. - Fix OPENALICE_APP_HOME: it must be app.getAppPath() (the dir that contains default/, ui/dist, src/workspaces, services/uta/dist), not dirname() — the old value pointed one level above the shipped files, breaking templates/UI. - L1 update check: poll the GitHub releases list (prereleases included, since we ship -beta.N with no formal release soon), surface a non-blocking download dialog when a newer version exists. No auto-download, no signing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Zero source imports — the in-process AI loop that used it was deleted in the 0.40 World B collapse; the model loop runs in native workspace CLIs now. The package bundles a full copy of the Claude Code CLI (cli.js ~11M) + per-platform ripgrep binaries (vendor/ ~40M) = 54M of dead weight in the desktop package. @anthropic-ai/sdk (the key-probe), openai, and ai (the tool() primitive) stay — those are live. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
electron-builder config: include services/uta/dist + workspace templates, dist/*.js (tsup code-splits into hashed chunks — dist/main.js alone misses them), asar:false, mac dmg + win nsis (arch follows the runner). Unsigned — no Developer ID / Windows cert yet. release.yml: new build-desktop matrix (macos-14 arm64 / macos-13 x64 / windows-latest) gated on needs.release.outputs.created so a full Electron build (mac runners bill 10x) only fires when a new version is actually cut. Each runner builds its own platform/arch (pnpm installs only the os/cpu prebuilt — longbridge, node-pty), then softprops appends the artifact to the release the release job already created (release.yml stays the tag owner). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First version to exercise the desktop build/publish pipeline — cutting this release triggers build-desktop (mac arm64 + mac x64 + windows) to attach installers to the GitHub Release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…port feat(desktop): UTA-supervising Electron app + per-platform installer pipeline
GitHub's x64 mac runners queued 20+ min and never allocated on the 0.51.0-beta.2 release, hanging the whole run while arm64 + windows had long since published. Intel Macs are a shrinking base (Apple stopped selling them in 2023; arm64 covers every Mac since), not worth blocking every release. Matrix is now macos-14 (arm64 dmg) + windows-latest (x64 nsis). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…build ci(desktop): drop macos-13 (Intel) from the desktop build matrix
…tstrap errors Two papercuts found dogfooding the packaged Windows build: - The default Electron File/Edit/View/Window/Help menu renders *inside* the window on Windows/Linux (it never shows on macOS, where menus live in the system bar) — meaningless clutter for a single-window web-UI app. Set a minimal menu on macOS (keeps copy/paste accelerators), none elsewhere. - Workspace bootstrap failures showed 'exited with code unknown' (a null exit code from a spawn failure) instead of the actual reason, which was already sitting in result.stderr (e.g. the 'bash not found, install Git for Windows' hint). Surface the stderr tail in the message. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ood-fixes fix(desktop): drop in-window menu bar + surface real bootstrap errors
…e) + port bash to Node Workspace creation (the Harness: every workspace is a fresh git repo) had two external deps that break on a bare machine: bash (the bootstrap scripts) and system git. bash is absent on Windows; git is latent on Mac too (a fresh Mac's /usr/bin/git is an Xcode-CLT stub — our dev Macs only have it because CLT was installed long ago). So workspace creation failed on a bare Windows box, and would on a bare Mac. Now it needs neither: - Bundle git via dugite (GitHub Desktop's package; postinstall fetches a per-platform standalone git — same per-platform model as longbridge, picked up by electron-builder's node_modules inclusion under asar:false). - Port the bash bootstraps to Node: templates/_common.mjs (the sole dugite importer, exposes git()), chat/bootstrap.mjs, auto-quant/bootstrap.mjs. The launcher spawns them on the Electron-bundled Node (process.execPath + ELECTRON_RUN_AS_NODE) — no bash, no shebang reliance, plain ESM. - Route ALL git through the bundled git: runGit (initial commit) and git-service (panel log/branch/status) → dugite's exec(). - template-registry prefers bootstrap.mjs, falls back to bootstrap.sh for third-party templates (which still need bash where they run). dugite MUST stay in pnpm.onlyBuiltDependencies — its postinstall fetches the git binary; drop it and node_modules/dugite/git/ is silently empty. Release CI asserts the binary is present per-platform. Verified: tsc clean, 2003 unit tests + e2e (incl a PATH-stripped case proving no system git/bash) green; packaged .app carries dugite's git 2.53.0 + the .mjs templates. Closes the bare-Windows / bare-Mac workspace-creation gap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…trap feat(workspaces): native cross-platform bootstrap — bundle git (dugite) + port bash to Node
…S kick war ANG-120: opening two session tabs in one workspace (e.g. claude + opencode) froze claude while opencode stayed alive. Root cause: WorkspaceView's runningSlots mounted a <TerminalView> for EVERY running session in the workspace — a leftover from the old single-shared-view design where switching sessions was a CSS toggle within one view. The current design is session-as-tab: each session has its own tab + its own WorkspaceView, and TabHost keeps every tab mounted (display:none when inactive). So mounting all running sessions in each view duplicated every session's terminal into every open tab — a session open in N tabs got N WebSockets fighting over its single-attach PTY, producing a kick/reconnect war (the cols 80↔146 churn + pause/resume thrash + double resume-spawn seen in workspace-sessions.log). Fix: mount only this tab's own pinned session (activeRecord if running). Each session's terminal already persists across tab switches via TabHost's display:none, so the multi-mount was pure redundancy on top of the bug. Net effect: one terminal per session, one WS per session, no kick war. Verified: tsc -b clean, 2003 tests pass. Unrelated to native-bootstrap (TraderAlice#363). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-120 defense-in-depth)
The frontend fix removes the cross-tab duplicate mounts that drove the resume
churn, but the resume route still had a check-then-spawn race: two POST
/resume within ms both passed the 'already running?' gate while the session
was paused, and each called pool.spawn() → two 'claude --resume <id>' racing
on one transcript.
Serialize resumes per ${wsId}::${recordId}: a later caller awaits the
in-flight resume, then an in-lock pool.get() re-check short-circuits it to
alreadyRunning instead of spawning a second agent. A failed/early-exit resume
leaves the slot free so a genuine retry still works.
Regression test: two simultaneous resumes spawn exactly once.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cuts a release carrying the native cross-platform bootstrap (TraderAlice#363, bundled git + Node bootstrap) plus the ANG-120 multi-session fixes — the build to verify bare-Windows workspace creation on the cloud box. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nal-remount fix(workspaces): stop multi-session terminal freeze (ANG-120) + cut 0.51.0-beta.3
…undation # Conflicts: # package.json # scripts/guardian/shared.ts # vitest.config.ts
Adkr1989
pushed a commit
that referenced
this pull request
Jun 21, 2026
…ive-volume movers Shift volume analysis from absolute to relative. Two launcher-side layers: - Indicator engine: add RVOL/OBV/MFI/VWAP pure functions (technical.ts) + calculator dispatch. RVOL is the key read — latest bar vs its own N-bar average — since raw volume isn't comparable across tickers. Document the scalar-operand foot-gun in the calculateIndicator tool description. - Discovery: enrich gainers/losers/active rows with relative_volume (vol / 3-month avg) and turnover (vol / shares-out), computed in getPredefinedScreener from averageDailyVolume3Month (added to the screener field whitelist — zero extra requests). equityDiscover gains sortBy="relative_volume" so "most active" surfaces genuine unusual volume instead of ever-active mega-caps. Also fixes a pre-existing break in the same path: screener() never passed validateResult:false, so yahoo's recently-expanded includeFields tripped yahoo-finance2's strict ScreenerResult schema and threw — gainers/losers/ active were all dead against live yahoo. Mirrors the existing search() fix; validateResult is screener()'s THIRD arg. Verified live: absolute "most active" #1 was NVDA at RVOL 1.01 (normal volume, no signal); relative-volume ranking surfaced RDW (+15.1%, 2.3x) and AVGO (-12.6%, 3.3x) — the events the absolute list buries. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…et-liq/unrealized P&L, Notes stub
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Foundation for using OpenAlice as a paper-first executive trading workflow, added as a thin extension layer with no
src/behavior changes beyond Windows-compat fixes.extensions/journal/): a standalone watcher that polls the engine's file-driven trade record (data/trading/<acct>/commit.json) and appends each new commit — thesis, operations, fills, guard rejections, P&L — to an Obsidian markdown journal. Zero coupling tosrc/. 14 unit tests; live-verified writing real entries from real paper trades.scripts/guardian/shared.ts):pnpm devdid not work on win32 —tsx/pnpmspawned by bare name (ENOENT on.CMDshim),tsx watchworker hung before binding, and the restart-flagbasenameignored backslashes. Fixed all three; engine now boots and the UTA restart-flag protocol fires on Windows.extensions/intovitest.config.ts+tsconfig.jsonso the layer is test- and type-checked as first-class.Test plan
npx tsc --noEmitcleanpnpm test— 1722 passed; the only failures are 10 pre-existing Windows-path assertions insrc/core/paths.spec.ts(documented indocs/decisions/baseline-test-state.md)pnpm devboots Alice + UTA on WindowsBoundary touch
Touches the guardian dev supervisor (process spawning) and reads trade records. Does NOT modify trading/broker logic, auth, or migrations. No credentials in the diff (
data/gitignored).