Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,77 @@

Each version's section is also what the app's update dialog shows.

## 0.7.0

Claude Platform, enowX's own agent; Project Docs; the community library at https://ui.enowx.ai; Claude Code now always running in bypass; a fifth of the scrollback; and much less memory for the app and the daemon.

- **Claude Platform, enowX's own coding agent.** It is part of enowX's program: a Go loop over Anthropic's Messages API, nothing to install (no Node, no Python) on any host, macOS, Linux, Windows and servers alike. It runs on a key from the vault and is billed per token by Anthropic. Its tools are the ones Claude Code names (Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion, Task, and MCP servers' tools), its permission prompts are answered by keys, a hook can rewrite what it runs, and "always allow" keeps only a bounded command.
- **Its sessions are sessions like any other:** tabs, hibernation, restarts, other hosts. Its chat streams the answer as it arrives, a chip shows what the session has cost against its budget, and its sheet keeps the API key in the vault and edits the host's system prompt.
- **The key stays the agent's:** its tools' commands don't get it, it is in no file, command line, record or log, and text a tool brings back has it replaced.
- **Not offered:** rewinding from the chat and forking a session.
- **Project Docs: a whole project documented as notes.** A PRD, Technical Spec, ERD and the rest live as ordinary notes under one project note, so sync, teams, history, RAG and a note's agent work on them. Its metadata and approval line sit in the body, where an older daemon can't lose them; sections say what they are built on, with that source's hash, and read as stale when it moves on. Agents write to an approved document as a proposal you accept, and the approval gate blocks building until the PRD and Technical Spec are approved and unchanged.
- Templates for product, UX, planning, launch and the technical areas; `docs` on the command line and as MCP tools; `goa delegate --doc` hands a task over with the sections, links and acceptance criteria it needs; a project exports to a repository when you ask.
- **The community library, enowX UI, at https://ui.enowx.ai.** Designs are shared from canvas to the site and browsed in the app: search by words and by meaning, filters, likes, collections, follows, comments, notifications for what you made and what was decided, and moderation by people who aren't its creator. Take a design as code, as a reference or as a skill, and `enowx://` links open in the app. It stays off unless the cloud you set has it on, and nothing reaches any other service.
- **GOD of Agents can change the agent and config it runs on,** from Settings in its header, with what a pick would do or why it can't before anything is pressed. It is offered only on agents run for real as GOA (Claude Code and its teams, Codex, OpenCode 1 and 2; Claude Platform ahead of that run), and refuses the others saying why.
- **One line per turn,** the task's report and its turn's end together, with the worker's real last message.
- **A queued task survives a daemon's restart,** reaches a session on another host, and brings a sleeping session back for it.
- **A task under way, or a session's background agents, keep hibernation off it.**
- **Claude Code always runs in `bypassPermissions`, for every user, with no way to turn it off.** Every Claude Code session enowX starts or brings back runs in bypass: New session, `spawn`, `goa delegate`, resume, `continue`, `wake`, a message to one asleep, fork, a tab, a session copied from another host, an account switch, a daemon's restart and an app's update, whatever an earlier daemon saved.
- **No setting turns it off:** the New session switch and Settings' permission mode aren't offered for Claude Code, and one an app, the command line or an automation still sends is ignored. The rule is Claude Code's alone; another agent doesn't inherit it.
- **enowX accepts Claude Code's warning for you:** on a home that never accepted it, Claude Code stops at a warning whose first answer exits, so enowX passes the setting that skips it in the `--settings` it starts Claude Code with. Your own files aren't touched, and the session's banner says it skips permission prompts.
- **What still stops it:** a hook's refusal (a deny, which bypass doesn't pass), the judge's sure no, and a hook's ask, which Claude Code still prompts for. As root Claude Code refuses bypass itself, and the session starts asking and says why. A mode you set mid-session holds for that process only; a restart, a wake or an update brings bypass back.
- **RAG searches on every prompt of an indexed project,** and adds the four best parts, up to 6000 characters. Nothing turns it off: not a switch, not the judge. The judge's "RAG: context or not" use is retired, and settings that name it are read and ignored.
- **That is one call to the embedder per prompt.** Past 3 seconds the prompt goes on without the index; a failed embedder falls back to the words alone, in the host's own store, the parts say so, and the failure shows in Settings → RAG and the Index tool until a search works.
- **Agents with a start-of-prompt hook get the parts:** Claude Code, Claude Agent Teams, Codex and Claude Platform. Others have no moment of the prompt to answer, and search with `rag search` themselves.
- **Default scrollback falls from 10 to 2 MiB a session.** The daemon's own memory was mostly this: five full scrollbacks measured 65 MB before and 10 MB after, about 11 MB a full session. A long build's log is cut sooner; an agent's chat isn't affected, being read from its transcript.
- **Switching a session's model restarts it on its conversation.** The chat's composer, `switch`, GOA's **Change model** and `goa delegate` change that session alone. Claude Code and Codex start again on their conversation with the model and effort as flags, never `/model`, so your own default in your settings isn't touched.
- **Refused while its agent works or asks,** when stopping would lose what it runs or serves, and on a session running a config, which decides its model. Nothing changes then, and GOA still gives the task, its board line saying the session kept its model. A session that isn't running only takes the new values, for when it comes back. An older daemon answers that it can't, and nothing is typed in its place.
- **Much less memory, and the numbers to show it.** A session's terminal is parsed only while it is shown, the last-shown few are let go after 3 minutes, a canvas's layers are drawn as asked for, a conversation is held only while a chat shows it, a chat polls only on screen, and an idle sync does nothing.
- **The token store keeps the last 2 days of calls** and sums older days: it held 45.6 MB for 126,000 calls before. Every split, day row, budget and session badge stays exact.
- **A team's pull that was cut off carries on where it stopped,** and a chat's refresh after a completed sync asks the cloud for nothing; before, a re-check read the whole space again every 5 seconds.
- **GOGC 50, a 1 GiB soft limit** well above ordinary use, and memory returned after a burst.
- **`enowx-app profile`** measures the running app and daemon: memory by the system's count and by Go's, what holds the heap, what they keep, CPU and pprof files; the same report goes in a diagnostics bundle.
- **Agents:**
- **OMP takes a model API per session:** a folder of the session's own with `models.yml`, the key by variable and `--model enowx/<model>`. Run for real on OMP 18.15 on a throwaway daemon.
- **OpenCode 2:** enowX starts `opencode --standalone` when it reports 2 or later, so a config's provider, model, guide and tools aren't ignored by the user's shared background server, and the models' context limits come from the config.
- **Kilo Code takes its first prompt by flag.**
- **The guide comes in two parts:** an inject made for each session (who it is, the rules that hold whatever a file says, GOA's duty to delegate) and a guide file outside any repository. An agent with a file form gets both as one system prompt; one with only an inline flag gets the inject, told to read the guide. Nothing of it lands in your project.
- **Agents with no other way get a marked block** at the top of the project's `AGENTS.md`, or its `CLAUDE.md`, only in a session's environment. It is on by default, a tracked file shows it in `git diff`, and `project settings --guide-block off` takes it out.
- **Each session shows its own conversation,** not the newest file in its folder: by the ID its agent reported, by its own session folder, or the first unclaimed conversation started after it. Resuming, forking, importing and the model shown all go by that choice.
- **Sleeping sessions, and updates that don't cut work off.** Hibernating by hand shows what would stop (a dev server, ports, background agents, a task under way) before it happens, and a hibernated or ended agent comes back on its conversation when something is sent to it. Automatic hibernation stays at 30 idle minutes by default and never takes a session with a task under way or background agents; one can be kept out of it, and the choice syncs.
- The app replaces its daemon only when a restart would stop nothing, asking the daemon first; otherwise a card offers **Restart it now** or **When agents are done**. Sessions survive an update and a reconnect, and what a restart stops comes back.
- **Also fixed:**
- A platform tab gets its own key from the vault, and a credential's use in a tab is recorded as that tab's.
- A chat no longer shows its folder's newest conversation instead of the session's own.
- An OpenCode 2 session is given its model's context limit.
- The model shown follows a resumed, forked or imported session instead of its folder.
- **Always allow** no longer allows every command, and hooks are obeyed fully.
- The agent's own API key is no longer handed to the commands it runs.
- Nothing is typed into the shell an agent leaves behind.
- Permission checks that a reader could see differently were closed (reads, MCP hints, attachments, kept rules).
- A canvas renders through an app that only watches the host, and a page whose address changes says so.
- A note's agent starts anew when its conversation left with its session.
- An agent's transcripts are found for a folder reached behind a symlink.
- A late turn's end no longer lands on a task being typed in.
- A team's items that waited aren't lost; a dropped connection backs off instead of hammering a host as it comes up.
- Menus, dropdowns and pop-up lists stay inside the window.

### When you update

- **The first start restarts the daemon,** as with any update. It now waits while a restart would stop work (an agent at work or asking, a command running in a terminal), and offers to do it later instead. Agents that do stop come back on their conversation.
- **Update every device and host together.** A canvas's versions have stopped being synced since 0.6.0: an older app given no manifest would delete them, so an older app's sync fails and says to update.
- **Claude Code sessions in bypass:** nothing to do, it is on for every session from now on, and its warning won't show.

### What hasn't been tested with the real app

- **Claude Platform** ran end to end against a stand-in Messages API and, for its key, with the real program on a throwaway daemon; it hasn't run a release build against a real Anthropic account at this scale, and the model's own behavior under bypass wasn't judged.
- **The community library** has automated tests for its cloud side on SQLite and Postgres, and none of its moderation, comments, notifications or numbers has been used by real people yet.
- **Project Docs** is covered by tests and the app's pages; a whole real project hasn't gone from a brainstorm through approval, tasks and export.
- **The memory numbers** were measured on Linux, step by step in [docs/performance.md](https://github.com/enowdev/enowx/blob/main/docs/performance.md) (available in the app's docs). The Mac's figures there are estimates from one profile; the scrollback change wasn't re-measured on a Mac, and the app's and daemon's footprints on macOS weren't taken.
- **Agents' real sessions:** Claude Code 2.1.318, Codex 0.166, OpenCode 1.19.5, Claude Agent Teams, OMP 18.16 and Qwen Code 0.26.7 were checked with a stand-in model that records what they receive; Gemini CLI 0.66 only partly. The others are read from their documentation and aren't counted as working: start one and ask it who it is in enowX.
- **Claude Code's bypass** was checked on 2.1.318 with its answers served locally; that a real account's first session never sees the warning is by the setting enowX passes, not by a run with Anthropic's API.
- **Windows** has the workflow's job for the pieces that must look the same there (icons, command lines through real PowerShell and cmd.exe, quoting), not the whole test suite.

## 0.6.2

Fixes the app dropping its connection when you move to Canvas with large designs, and adds logs that say why a connection ended, so a problem like this can be read afterwards.
Expand Down
33 changes: 16 additions & 17 deletions cli/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"
"text/tabwriter"

"github.com/enowx/enowx/client"
"github.com/enowx/enowx/core/agent"
"github.com/enowx/enowx/proto"
)
Expand Down Expand Up @@ -372,26 +373,24 @@ func (r runner) switchSession(args []string) error {
return errors.New(usage)
}
set := agent.SettingsOf(s.Agent)
typeIn := func(what, command, v string, choices []agent.Choice) error {
if command == "" {
return fmt.Errorf("switch: %s's %s can't be changed while it runs", nonEmpty(s.Agent, "this program"), what)
note := func(what, v string, choices []agent.Choice) {
if v == "" || len(choices) == 0 || slices.ContainsFunc(choices, func(c agent.Choice) bool { return c.ID == v }) {
return
}
if len(choices) > 0 && !slices.ContainsFunc(choices, func(c agent.Choice) bool { return c.ID == v }) {
var ids []string
for _, c := range choices {
ids = append(ids, c.ID)
}
fmt.Fprintf(r.out, "(%s isn't among the %ss enowX knows of %s: %s; sent anyway)\n", v, what, s.Agent, strings.Join(ids, ", "))
var ids []string
for _, c := range choices {
ids = append(ids, c.ID)
}
return r.c.Input(s.ID, fmt.Sprintf(command, v), false, true)
}
if *model != "" {
if err := typeIn("model", set.ModelCommand, *model, set.Models); err != nil {
return err
fmt.Fprintf(r.out, "(%s isn't among the %ss enowX knows of %s: %s; asked anyway)\n", v, what, s.Agent, strings.Join(ids, ", "))
}
if *model != "" || *effort != "" {
note("model", *model, set.Models)
note("effort", *effort, set.Efforts)
err := r.c.SwitchSettings(proto.SessionSettingsParams{ID: s.ID, Model: *model, Effort: *effort})
if client.IsUnknownMethod(err) {
return errors.New("switch: this host's enowX is too old to change one session's model alone: update it. The session is as it was")
}
}
if *effort != "" {
if err := typeIn("effort", set.EffortCommand, *effort, set.Efforts); err != nil {
if err != nil {
return err
}
}
Expand Down
2 changes: 2 additions & 0 deletions cli/decide_route.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ func (r runner) decideRoute(p proto.DecideRouteParams, asJSON bool) error {
fmt.Fprintf(r.out, "model: %s · effort: %s\n", model, effort)
if len(out.Switch) > 0 {
fmt.Fprintf(r.out, "in a running session: %s\n", strings.Join(out.Switch, ", "))
} else if model != "its own" || effort != "its own" {
fmt.Fprintln(r.out, "in a running session: enowx-app switch SESSION with --model and --effort (typing its /model would change the user's default)")
}
return nil
}
Expand Down
5 changes: 5 additions & 0 deletions client/transcript.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ func (c *Client) SwitchMode(id, mode string) error {
return c.Call(proto.MethodSessionMode, proto.SessionModeParams{ID: id, Mode: mode}, nil)
}

// SwitchSettings changes a session's model, its effort or both (SessionSettingsParams).
func (c *Client) SwitchSettings(p proto.SessionSettingsParams) error {
return c.Call(proto.MethodSessionSettings, p, nil)
}

// Rewind takes session id's agent back to before the user's message back
// messages from the end, whose text is text; the message sent next takes
// its place.
Expand Down
2 changes: 1 addition & 1 deletion cmd/enowx/mygo.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "enowX",
"identifier": "ai.enowx.app",
"version": "0.6.2",
"version": "0.7.0",
"copyright": "© 2026 enowX contributors",
"out": "../../build",
"urlSchemes": ["enowx"],
Expand Down
2 changes: 1 addition & 1 deletion core/accounts/ways.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ var agentWays = map[string]Way{
"qoder-cn": {Key: true, Why: "A model is added only in its /model screen: its docs say not to edit the settings by hand. No other account: QODERCN_CONFIG_DIR moves its folder, but its docs name no file for the sign-in.", Checked: "docs"},
"zcode": {Why: "Its docs describe a desktop app, with no command line to give a config to.", Checked: "docs"},
"pi": {Key: true, Why: "Another account is read from its docs, not signed in: auth.json in PI_CODING_AGENT_DIR is the account's. It has no sign-in command: use /login in a session on the config.", Checked: "docs"},
"omp": {Key: true, Why: "It takes a provider's address, but only for models named in models.yml, which enowX doesn't write yet: a model of your own wasn't found when run.", Checked: "run"},
"omp": {Key: true, Why: "A config's sessions read only the config's models, not your own ~/.omp settings, skills and sign-in: a folder of the session's own is the only way to give one another models.yml.", Checked: "run"},
"prime-agent": {Key: true, Why: "Only a models.json takes a model API, at a place its docs don't name.", Checked: "docs"},
"antigravity": {Key: true, Why: "No separate account is documented.", Checked: "help"},
"aider": {Key: true, Why: "No account: it has no sign-in to separate, only API keys.", Checked: "docs"},
Expand Down
12 changes: 10 additions & 2 deletions core/agent/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ type Choice struct {
// is changed in a running session.
type Settings struct {
Models []Choice
// ModelCommand, with %s the model's ID, switches the model.
// ModelCommand, with %s the model's ID, switches the model in the session it runs in;
// where it saves it for every session too (SavesDefaults), enowX doesn't type it.
ModelCommand string
Modes []Choice
// ModeTitle names the modes in the composer; "" for "Permissions".
Expand All @@ -32,8 +33,13 @@ type Settings struct {
// mode can't be changed while the agent runs.
ModeKey string
Efforts []Choice
// EffortCommand, with %s the level, sets the effort.
// EffortCommand, with %s the level, sets the effort, as ModelCommand sets the model.
EffortCommand string
// SavesDefaults says ModelCommand and EffortCommand save what they set as the default
// for new sessions, in a file all the agent's sessions read: typing one would change
// the user's own settings too, so enowX starts the agent again on its conversation
// with ModelFlag and EffortFlag instead (session.settings).
SavesDefaults bool
// ModelFlag, EffortFlag and ModeFlag, with %s the choice, start the agent on a
// model, an effort and a permission mode: arguments of its command line.
ModelFlag, EffortFlag, ModeFlag []string
Expand Down Expand Up @@ -125,6 +131,7 @@ var claudeSettings = Settings{
{ID: "xhigh", Label: "Extra high"}, {ID: "max", Label: "Max"},
},
EffortCommand: "/effort %s",
SavesDefaults: true,
ModelFlag: []string{"--model", "%s"},
EffortFlag: []string{"--effort", "%s"},
ModeFlag: []string{"--permission-mode", "%s"},
Expand Down Expand Up @@ -153,6 +160,7 @@ var codexSettings = Settings{
{ID: "xhigh", Label: "Extra high"}, {ID: "max", Label: "Max"},
},
EffortCommand: "/model --reasoning=%s",
SavesDefaults: true, // "failed to persist model selection", in 0.166's program; not run
ModelFlag: []string{"-c", `model="%s"`},
EffortFlag: []string{"-c", `model_reasoning_effort="%s"`},
}
Expand Down
Loading
Loading