Skip to content

[IMP] common: install the skills a run declares, resume the session it asked for, review its prompt - #41

Open
sea-odoo wants to merge 2 commits into
betafrom
imp/skills-refresh-resume-edit
Open

[IMP] common: install the skills a run declares, resume the session it asked for, review its prompt#41
sea-odoo wants to merge 2 commits into
betafrom
imp/skills-refresh-resume-edit

Conversation

@sea-odoo

Copy link
Copy Markdown
Contributor

Description

Five things a command could ask the AI plugin for and not get. They share mixins.py and one call path, so they land together, plus one isolated fix.

--resume never resumed anything, for three silent reasons stacked on top of each other:

  • The latest session was looked up in ~/.claude/sessions.json — a file Claude Code does not write, and that ClaudeHandler.cleanup_junk here creates as an empty {"sessions": []}. The lookup was reading odev's own placeholder, so every run ended on "No previous session found to resume". Sessions live in ~/.claude/projects/<slug>/<session-id>.jsonl.
  • The id was then passed as --session-id, which names a new session and refuses one that already exists. It is --resume.
  • latest was resolved before the working directory was known, though sessions are per-directory. Resolving it after is what makes odev scaffold in a folder and odev ai --resume in that same folder the same conversation; failing that, the most recent session anywhere is resumed and said so.

Where an agent keeps its sessions is now the handler's question rather than an if-chain on the CLI name — agy resolves latest itself, and opencode keeps its sessions somewhere other than the Claude layout it inherits, where answering would have resumed the wrong agent's conversation. A bare --resume now means the latest session.

Skills were suggested, never installed. _ensure_skills printed a skills add line for the developer to copy and ran the agent anyway — without the method its prompt sends it to, which looks exactly like an agent that chose not to read a skill. It installs now, and verifies by listing again since the installer exits 0 even where it failed. The suggested command was wrong too: --skills is not an option the CLI knows, and an unknown option is ignored rather than refused, so it installed every skill in the repository.

Which skills a run needs was decided by if self._name == "test" inside the shared mixin; each command declares required_skills instead. Every command now gets odoo_coding_guidelines: each of them puts an agent in front of a client's checkout, and the rule that a dev reformats nothing it was not asked to is not specific to writing code.

The skill store was never refreshed. Only missing skills were installed, so a skill stayed at the revision it was installed at for good — a rule added to it reached the agents that had never loaded it and no one else. skills update compares the upstream folder hash against the recorded one and refetches on a mismatch, rate-limited by a new skills.interval (1 day).

--edit / -E opens the assembled prompt in $VISUAL/$EDITOR and hands the agent what comes back, the way a commit message is written: an emptied buffer aborts the run, and so does an editor exiting in error. No CLI odev drives takes a prompt without answering it, so reviewing one before it is sent had no other way of happening. The editor opens before the sandbox and the ephemeral cluster, so a run decided against leaves nothing to clean up.

Also in passing: the (Environment: ...) note was assembled after the command carrying the prompt was built, so it reached no agent at all.

sandbox_repository lets a command name the checkout the run is about, preferred over the directory odev was called from. odev-plugin-ai-scaffold on beta already sets it — the attribute lived only on an unpushed branch, so it was set, never read, and the agent worked wherever the command happened to be typed.

[FIX] sandbox is independent: the Chrome wrapper was produced by assigning to odev.home_path, a read-only property. That raised on every run, so the wrapper was never written and ODOO_BROWSER_BIN never set — visible only as a Failed to setup Chrome wrapper warning.

Testing

Exercised end to end against a live odev ai: bare --resume resolving to the last session of the working directory, the warning and global fallback when that directory has none, --edit aborting before any sandbox is created on an emptied buffer, the edited text reaching the agent (verified in the resulting session transcript), and a stale skill store being refreshed by an ordinary odev ai run while a fresh one is left alone.

Note on ruff-required

The hook does not pass on beta today. On the files this PR touches it goes from 53 findings to 43 — none introduced, ten removed (BLE001, PLC0415, S110, SIM102, S607, PLR0912). The remainder is pre-existing and deliberately left alone rather than reformatted as a side effect of this change.

Linked Issues

  • none

Compliance

  • I have read the contribution guide
  • I made sure the documentation is up-to-date both in doctrings and the docs directory
  • I have added or modified unit tests where necessary
  • I have added new libraries to the requirements.txt file, if any
  • I have incremented the version number according the versioning guide
  • The PR contains my changes only and no other external commit

Assisted by Claude Opus 5

The wrapper was produced by pointing odev.home_path at sandbox_tmp for the
duration of the call. home_path is a read-only property, so that assignment
raised on every run: the wrapper was never written, ODOO_BROWSER_BIN never
set, and the failure surfaced only as a "Failed to setup Chrome wrapper"
warning nobody could act on.

Let get_wrapper write where it wants to, under odev's home, and copy the
result into the sandbox afterwards.

Assisted by Claude Opus 5
… asked for

Five things a command could ask for and not get. They share a file and a call
path, so they land together.

SESSION. --resume never resumed anything, for three silent reasons. The latest
session was looked up in ~/.claude/sessions.json, a file Claude Code does not
write and cleanup_junk here does, as an empty {"sessions": []}: the lookup read
odev's own placeholder, so every run ended on "No previous session found".
Sessions live in ~/.claude/projects/<slug>/, one <session-id>.jsonl per
conversation. The id was then passed as --session-id, which *names a new*
session and refuses one that exists; it is --resume. And "latest" was resolved
before the working directory was known, though sessions are per-directory:
resolving it after is what makes `odev scaffold` in a folder and `odev ai
--resume` in that same folder the same conversation.

Where an agent keeps its sessions is now the handler's question rather than an
if-chain on the CLI name: agy resolves "latest" itself, and opencode keeps its
sessions somewhere other than the Claude layout it inherits, where answering
would have resumed the wrong agent's conversation.

A bare --resume now means the latest session. That makes argparse take the
following word as its value, so `odev ai --resume "fix the report"` handed the
flag the prompt: the ai command gives it back.

SKILLS. _ensure_skills only ever warned: it printed a `skills add` line to copy
and ran the agent anyway, without the method its prompt sends it to - which
looks exactly like an agent that chose not to read a skill. It installs now,
and verifies by listing again, the installer exiting 0 even where it failed.
The suggested command was wrong too: `--skills` is not an option the CLI knows,
and an unknown option is ignored rather than refused, so it installed every
skill in the repository. It is `--skill`.

Which skills a run needs was decided by `if self._name == "test"` in the shared
mixin. A command knows what its own prompt leans on, so each declares
required_skills. Every command gets odoo_coding_guidelines: each puts an agent
in front of a client's checkout, and the rule that a dev reformats nothing it
was not asked to is not specific to writing code.

The store was never refreshed either - a skill stayed at the revision it was
installed at for good, so a rule added to it reached the agents that had never
loaded it and no one else. `skills update` compares the upstream folder hash
against the recorded one, rate-limited by skills.interval.

The CLI is called through one helper now, with a timeout and closed stdin: it
had neither, so a clone wanting an SSH passphrase asked a terminal nobody was
watching and hung the run on "Loading missing skill(s)...".

PROMPT. --edit opens the assembled prompt in $EDITOR and hands the agent what
comes back, the way a commit message is written: an emptied buffer aborts the
run, and so does an editor exiting in error. No CLI odev drives takes a prompt
without answering it, so reviewing one before it is sent had no other way of
happening. Opened before the sandbox and the ephemeral cluster, so a run
decided against leaves nothing to clean up.

The environment note moves ahead of the command that carries the prompt. It was
assembled after it and reached no agent at all. It is prefixed only to a prompt
there already is: given to an agent started with nothing to do, it becomes the
first thing it is asked.

WORKING DIRECTORY. sandbox_repository lets a command name the checkout the run
is about, preferred over the directory odev was called from.
odev-plugin-ai-scaffold already sets it - the attribute lived only on an
unpushed branch, so on beta it was set, never read, and the agent worked
wherever the command happened to be typed.

get_ai_agent is split into _resolve_cli and _resolve_model on the way past; it
had grown two prompt-for-a-favourite flows deep enough to hide each other.

Assisted by Claude Opus 5
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