@qodeca/xezar 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -25
- package/dist/runs/retention.d.ts +1 -1
- package/dist/runs/retention.js +1 -1
- package/dist/workflows/run.js +45 -20
- package/dist/workflows/run.js.map +1 -1
- package/package.json +3 -3
- package/web/dist/assets/{compare-variants-CdS8r8E2.js → compare-variants-D62m4j3B.js} +1 -1
- package/web/dist/assets/{git-toolbar-BYlnf4qf.js → git-toolbar-DJANqg7Y.js} +1 -1
- package/web/dist/assets/{github-CqGK62Lw.js → github-D6vK0jFn.js} +1 -1
- package/web/dist/assets/index-CkmR6h7s.css +2 -0
- package/web/dist/assets/{index-qX7n11FX.js → index-DVDqF4bM.js} +2 -2
- package/web/dist/assets/{repo-git-C2On8SlI.js → repo-git-btG0sdbO.js} +1 -1
- package/web/dist/assets/{skills-Bj-uG_Hz.js → skills-DMfxQtrD.js} +1 -1
- package/web/dist/assets/{task-changes-6aopqAmV.js → task-changes-BtZId14z.js} +1 -1
- package/web/dist/assets/{task-commits-u-dwaCyw.js → task-commits-BoeyJXrv.js} +1 -1
- package/web/dist/assets/{task-files-IMOZnIUK.js → task-files-DIy2oUnj.js} +1 -1
- package/web/dist/assets/{use-desktop-fLDKEJoB.js → use-desktop-CPBwzQ7M.js} +1 -1
- package/web/dist/assets/{workflows-BY3KTkFw.js → workflows-DG115scU.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-3nxVmdn5.css +0 -2
package/README.md
CHANGED
|
@@ -113,7 +113,7 @@ and an orchestrator keeps a whole queue of them moving.
|
|
|
113
113
|
Take it over interactively in one click (`claude --resume <id>`), or continue it
|
|
114
114
|
in-process from the cockpit.
|
|
115
115
|
- 🔀 **Locked into one agent vendor.** Most tools wed you to a single CLI. xezar
|
|
116
|
-
drives **Claude Code, Codex and
|
|
116
|
+
drives **Claude Code, Codex, OpenCode and pi (the last two experimental)** through one runner seam — set a
|
|
117
117
|
default, pick a backend per task, or mix them inside one workflow (implement
|
|
118
118
|
with one agent, review with another) — and through **OpenCode** you can point
|
|
119
119
|
a run at **open-source or local models**, not just the big vendors. See
|
|
@@ -147,8 +147,9 @@ and an orchestrator keeps a whole queue of them moving.
|
|
|
147
147
|
|
|
148
148
|
**Prerequisites:** Node 20+, at least one logged-in agent CLI — the
|
|
149
149
|
[`claude` CLI](https://github.com/anthropics/claude-code) (Pro/Max subscription),
|
|
150
|
-
the [`codex` CLI](https://github.com/openai/codex),
|
|
151
|
-
[OpenCode](https://opencode.ai)
|
|
150
|
+
the [`codex` CLI](https://github.com/openai/codex),
|
|
151
|
+
[OpenCode](https://opencode.ai), or [pi](https://github.com/badlogic/pi-mono) —
|
|
152
|
+
and, optionally, `git` and the `gh` CLI.
|
|
152
153
|
|
|
153
154
|
Install it once, then run it in any repo:
|
|
154
155
|
|
|
@@ -192,8 +193,8 @@ To upgrade later: `npm install -g @qodeca/xezar@latest`.
|
|
|
192
193
|
|
|
193
194
|
You describe a task. xezar runs it as a **workflow** — an ordered list of agent
|
|
194
195
|
steps and shell checks — shelling out to your locally installed agent CLI
|
|
195
|
-
(Claude Code by default; Codex and
|
|
196
|
-
or per step). In a git repo each task gets its own worktree by default — switch
|
|
196
|
+
(Claude Code by default; Codex, OpenCode and pi are drop-in alternatives, per
|
|
197
|
+
task or per step). In a git repo each task gets its own worktree by default — switch
|
|
197
198
|
the composer's **Worktree** toggle off to run in the repo working tree (under
|
|
198
199
|
xezar's repository-root lease), and a non-git directory runs in place. A git
|
|
199
200
|
task that asks for a worktree it cannot create stops as `failed`; it never falls
|
|
@@ -259,7 +260,7 @@ Three words, no jargon — **task**, **skill**, **chain**:
|
|
|
259
260
|
AI draft a chain for your task that you review, trim and start. The built-in
|
|
260
261
|
`quick-task` (one agent step) works with zero setup.
|
|
261
262
|
|
|
262
|
-
|
|
263
|
+
Six moves that make the cockpit worth the browser tab:
|
|
263
264
|
|
|
264
265
|
- 🗃️ **Queue + orchestration.** Start as many tasks as you like: xezar runs up to
|
|
265
266
|
`maxParallel` at once across every project (default **2**; a non-git directory
|
|
@@ -316,7 +317,7 @@ Nine views, one browser window, all live over Server-Sent Events (seven by defau
|
|
|
316
317
|
| **Automations** | **Opt-in** (`XEZ_AUTOMATIONS=1`; hidden by default). Scheduled GitHub watches: each automation polls on its own interval and launches a task when its bounded filter matches. Test a filter before enabling it, and read the per-check log. |
|
|
317
318
|
| **Skills** | Local skills plus the team skills repo, with a rendered body + prompt preview. Refresh pulls the latest from the remote. |
|
|
318
319
|
| **Workflows** | Build a chain by drag-ordering skills, save it as portable YAML, import/export, or delete. Built-ins always come back. |
|
|
319
|
-
| **Settings** | Appearance (dark/light theme, accent, density), agent backends, notifications, and
|
|
320
|
+
| **Settings** | Appearance (dark/light theme, accent, density), agent backends and their own config files, agent accounts, notifications, machine resources, projects, and skill-update checks. |
|
|
320
321
|
|
|
321
322
|
The cockpit is a React app served pre-built from the package — running `xezar`
|
|
322
323
|
needs no dev server on your machine — with a dark/light
|
|
@@ -373,10 +374,11 @@ These read and write `~/.xezar/config.json` directly, so they work with the
|
|
|
373
374
|
server stopped, and `XEZ_HOME` selects which workspace they operate on.
|
|
374
375
|
|
|
375
376
|
Settings split along the same line: **General** (the project's folder, its
|
|
376
|
-
registry facts, its parallel-task ceiling, and Remove), **Agents**,
|
|
377
|
-
**
|
|
378
|
-
repo and live under
|
|
379
|
-
**Notifications**, **Resources**,
|
|
377
|
+
registry facts, its parallel-task ceiling, and Remove), **Agents**, **Agent
|
|
378
|
+
config** (each agent's own settings, MCP and memory files), **Worktrees**,
|
|
379
|
+
**Bookmarklets** and **Prompt templates** describe one repo and live under
|
|
380
|
+
`/p/<projectId>/settings`; **Appearance**, **Notifications**, **Resources**,
|
|
381
|
+
**Skills** (update checks), **Agent accounts** and **Projects** are yours or the
|
|
380
382
|
machine's and live at `/settings/global`.
|
|
381
383
|
|
|
382
384
|
### Grouping connected repositories: tags and the All tasks page
|
|
@@ -623,16 +625,16 @@ On startup xezar probes which CLIs are installed and the cockpit only offers
|
|
|
623
625
|
the backends it found — install any one of the four and you're operational.
|
|
624
626
|
|
|
625
627
|
**Models come from your own machine.** The model picker does not ship a list of
|
|
626
|
-
vendor releases that goes stale between xezar versions. For
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
account gained yesterday is selectable today with no
|
|
631
|
-
provider retired stops being offered. Discovery is
|
|
632
|
-
and is cached briefly in memory. If
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
628
|
+
vendor releases that goes stale between xezar versions. For each backend xezar
|
|
629
|
+
reads what *your host* currently offers — Claude Code's `list_models` control
|
|
630
|
+
request, the Codex app-server's `model/list`, `opencode models`, and for pi its
|
|
631
|
+
own `~/.pi/agent/models.json` and `settings.json` — and shows exactly that, in
|
|
632
|
+
that order, so a model your account gained yesterday is selectable today with no
|
|
633
|
+
xezar release, and one your provider retired stops being offered. Discovery is
|
|
634
|
+
read-only, costs no tokens, and is cached briefly in memory. If a CLI is
|
|
635
|
+
missing, logged out, too old or slow — or pi has no providers configured yet —
|
|
636
|
+
the picker quietly falls back to that runner's built-in entries (`auto` plus
|
|
637
|
+
Claude's tier aliases) and says so in a status row. `auto` — send no
|
|
636
638
|
model at all and let the CLI decide — is always available, and a model you
|
|
637
639
|
pinned by hand stays selectable even when it is no longer advertised.
|
|
638
640
|
|
|
@@ -688,8 +690,8 @@ happens per task and per step, not inside a variant group.
|
|
|
688
690
|
|
|
689
691
|
The seam is deliberately small: a backend is one class implementing the
|
|
690
692
|
`AgentRunner` interface (`packages/xezar/src/core/agent-runner.ts`) that turns a prompt into
|
|
691
|
-
a stream of normalized events.
|
|
692
|
-
can slot
|
|
693
|
+
a stream of normalized events. `pi` was added exactly that way; other CLIs —
|
|
694
|
+
aider, whatever ships next — can slot into the same seam.
|
|
693
695
|
|
|
694
696
|
---
|
|
695
697
|
|
|
@@ -823,8 +825,9 @@ cd xezar
|
|
|
823
825
|
npm install
|
|
824
826
|
```
|
|
825
827
|
|
|
826
|
-
**3. Build** — compiles the
|
|
827
|
-
|
|
828
|
+
**3. Build** — compiles the server (`tsc → packages/xezar/dist/`), folds the
|
|
829
|
+
contract into `dist/contract/` so the published tarball resolves it, and builds
|
|
830
|
+
the cockpit (`vite build → packages/xezar/web/dist/`), then runs the pack gate:
|
|
828
831
|
|
|
829
832
|
```bash
|
|
830
833
|
npm run build
|
package/dist/runs/retention.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface RematerializeStore {
|
|
|
34
34
|
* run regains its isolated tree and becomes eligible for retention again;
|
|
35
35
|
* without it the run would keep a directory on disk while staying invisible to
|
|
36
36
|
* the enforcer forever (a leak). Returns true when it re-materialized.
|
|
37
|
-
* Best-effort: never throws
|
|
37
|
+
* Best-effort: never throws; the continuation caller refuses lost isolation before spawning.
|
|
38
38
|
*/
|
|
39
39
|
export declare function rematerializeReclaimedWorktree(repoRoot: string, store: RematerializeStore, runId: string): Promise<boolean>;
|
|
40
40
|
/**
|
package/dist/runs/retention.js
CHANGED
|
@@ -45,7 +45,7 @@ export function selectReclaimableWorktrees(runs, keep) {
|
|
|
45
45
|
* run regains its isolated tree and becomes eligible for retention again;
|
|
46
46
|
* without it the run would keep a directory on disk while staying invisible to
|
|
47
47
|
* the enforcer forever (a leak). Returns true when it re-materialized.
|
|
48
|
-
* Best-effort: never throws
|
|
48
|
+
* Best-effort: never throws; the continuation caller refuses lost isolation before spawning.
|
|
49
49
|
*/
|
|
50
50
|
export async function rematerializeReclaimedWorktree(repoRoot, store, runId) {
|
|
51
51
|
const run = store.getRun(runId);
|
package/dist/workflows/run.js
CHANGED
|
@@ -1981,6 +1981,12 @@ export class RunManager {
|
|
|
1981
1981
|
// human got there first — and then the counter starts over, because the cap only exists to
|
|
1982
1982
|
// bound UNATTENDED resumes.
|
|
1983
1983
|
this.clearAutoResume(runId);
|
|
1984
|
+
// Accepted Continue is explicit new work: make it discoverable in Active again.
|
|
1985
|
+
// Refusals above leave the user's archive decision untouched.
|
|
1986
|
+
if (run.archived) {
|
|
1987
|
+
this.store.setArchived(runId, false);
|
|
1988
|
+
this.store.appendEvent(runId, { type: 'note', message: 'unarchived for continuation' });
|
|
1989
|
+
}
|
|
1984
1990
|
const continuations = run.steps.filter((s) => s.id.startsWith('continue-')).length;
|
|
1985
1991
|
const stepId = `continue-${continuations + 1}`;
|
|
1986
1992
|
this.store.addStep(runId, { id: stepId, name: 'Continue', kind: 'agent' });
|
|
@@ -2023,15 +2029,49 @@ export class RunManager {
|
|
|
2023
2029
|
* reconstructed at dequeue. Keep them separate from fresh `images` so
|
|
2024
2030
|
* opening a recovered continuation does not persist duplicate files. */
|
|
2025
2031
|
persistedImages = [], persistedAttachments = []) {
|
|
2032
|
+
// Continue has already been accepted. Preserve its input before asynchronous
|
|
2033
|
+
// recovery or isolation refusal can stop startup; persistence is not runner receipt.
|
|
2034
|
+
// Attachments pasted into the follow-up composer, on the same terms as a live-session
|
|
2035
|
+
// message (#357): persisted to the run's own attachment store so the thread renders the
|
|
2036
|
+
// bubble's images rather than a bare count, and handed to the agent as absolute paths
|
|
2037
|
+
// appended to the prompt (so it can operate on them — and because codex/opencode drop image
|
|
2038
|
+
// blocks before they reach the model). An image ALSO rides along as a base64 block so the
|
|
2039
|
+
// model can view it; a file (#950) has nothing to view and travels as its path alone.
|
|
2040
|
+
const freshAttachments = this.persistPastedAttachments(runId, images);
|
|
2041
|
+
const openingImages = [...contentBlocksOf(images), ...persistedImages];
|
|
2042
|
+
const attachments = [...freshAttachments, ...persistedAttachments];
|
|
2043
|
+
this.store.appendEvent(runId, {
|
|
2044
|
+
type: 'user-message',
|
|
2045
|
+
stepId,
|
|
2046
|
+
text: prompt,
|
|
2047
|
+
imageCount: openingImages.filter((b) => b.type === 'image').length,
|
|
2048
|
+
...(attachments.length ? { images: attachments.map((saved) => saved.url) } : {}),
|
|
2049
|
+
});
|
|
2026
2050
|
// Continuation runs in the task's worktree when it still exists (spec
|
|
2027
2051
|
// 006) — the resumed session sees exactly what the original run left.
|
|
2028
2052
|
// Retention (#483) may have reclaimed this run's worktree directory while
|
|
2029
2053
|
// keeping its branch and worktreePath. Re-materialize it on resume and clear
|
|
2030
2054
|
// the stamp so the session regains its isolated tree and the run is eligible
|
|
2031
2055
|
// for retention again — otherwise it keeps a dir on disk while staying
|
|
2032
|
-
// invisible to the enforcer forever.
|
|
2056
|
+
// invisible to the enforcer forever. A failed restoration must not widen isolation.
|
|
2033
2057
|
await rematerializeReclaimedWorktree(this.repoRoot, this.store, runId);
|
|
2034
2058
|
const record = this.store.getRun(runId);
|
|
2059
|
+
// A recorded path/branch proves prior isolation; an absent policy alone does not
|
|
2060
|
+
// (legacy and non-git runs legitimately ran in place). Never resume that isolated
|
|
2061
|
+
// session against primary while that isolation identity is still recorded.
|
|
2062
|
+
// Legacy cleanup that erased BOTH path and branch cannot be distinguished
|
|
2063
|
+
// from an old in-place run; it is not a qualified isolation recovery route.
|
|
2064
|
+
if (record?.worktree !== false && (record?.worktreePath || record?.branch) &&
|
|
2065
|
+
(!record.worktreePath || !existsSync(record.worktreePath))) {
|
|
2066
|
+
const error = 'continuation isolation unavailable — restore the task worktree before retrying';
|
|
2067
|
+
const finishedAt = new Date().toISOString();
|
|
2068
|
+
this.store.updateStep(runId, stepId, { status: 'failed', error, finishedAt });
|
|
2069
|
+
this.store.updateRun(runId, { status: 'failed', error, finishedAt, currentStepId: undefined });
|
|
2070
|
+
this.store.appendEvent(runId, { type: 'lifecycle', message: `run failed — ${error}` });
|
|
2071
|
+
this.starting.delete(runId);
|
|
2072
|
+
this.dropActive(runId);
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2035
2075
|
// The env is a live ceiling: a run created while the inbox was on must not keep writing
|
|
2036
2076
|
// follow-ups after it is switched off.
|
|
2037
2077
|
const generateFollowups = this.semaphore.followupsEnabled() && record?.generateFollowups !== false;
|
|
@@ -2100,22 +2140,6 @@ export class RunManager {
|
|
|
2100
2140
|
backend,
|
|
2101
2141
|
});
|
|
2102
2142
|
this.store.appendEvent(runId, { type: 'step-start', stepId, name: 'Continue', kind: 'agent', iteration: 1 });
|
|
2103
|
-
// Attachments pasted into the follow-up composer, on the same terms as a live-session
|
|
2104
|
-
// message (#357): persisted to the run's own attachment store so the thread renders the
|
|
2105
|
-
// bubble's images rather than a bare count, and handed to the agent as absolute paths
|
|
2106
|
-
// appended to the prompt (so it can operate on them — and because codex/opencode drop image
|
|
2107
|
-
// blocks before they reach the model). An image ALSO rides along as a base64 block so the
|
|
2108
|
-
// model can view it; a file (#950) has nothing to view and travels as its path alone.
|
|
2109
|
-
const freshAttachments = this.persistPastedAttachments(runId, images);
|
|
2110
|
-
const openingImages = [...contentBlocksOf(images), ...persistedImages];
|
|
2111
|
-
const attachments = [...freshAttachments, ...persistedAttachments];
|
|
2112
|
-
this.store.appendEvent(runId, {
|
|
2113
|
-
type: 'user-message',
|
|
2114
|
-
stepId,
|
|
2115
|
-
text: prompt,
|
|
2116
|
-
imageCount: openingImages.filter((b) => b.type === 'image').length,
|
|
2117
|
-
...(attachments.length ? { images: attachments.map((saved) => saved.url) } : {}),
|
|
2118
|
-
});
|
|
2119
2143
|
let stepCost = 0;
|
|
2120
2144
|
let turnText = '';
|
|
2121
2145
|
let sessionError;
|
|
@@ -2179,13 +2203,14 @@ export class RunManager {
|
|
|
2179
2203
|
// Autonomous (#autonomous): never hand the ball back to the user. Nudge the agent to
|
|
2180
2204
|
// keep going (bounded by MAX_AUTO_CONTINUES) instead of parking at `waiting`. Shared
|
|
2181
2205
|
// with `execute`'s turn-end handler — one sender, both sites (#141).
|
|
2182
|
-
autoContinued = this.autoContinueTurn(runId, state);
|
|
2206
|
+
autoContinued = !monitoring && this.autoContinueTurn(runId, state);
|
|
2183
2207
|
if (!autoContinued) {
|
|
2184
2208
|
// `XEZ:ASK` → park `waiting` (attention) AND surface the structured
|
|
2185
2209
|
// question as an ask card (#473). `XEZ:MONITORING` → non-attention
|
|
2186
2210
|
// `running`/`activity:'monitoring'` (#490). Both share the waiting
|
|
2187
2211
|
// lifecycle (free the slot, keep the idle timer); the autonomous
|
|
2188
|
-
// nudge
|
|
2212
|
+
// nudge may answer ASK in autonomous mode, but explicit monitoring
|
|
2213
|
+
// parks with its bounded wake timer instead of spinning immediately.
|
|
2189
2214
|
if (ask)
|
|
2190
2215
|
emitAskRequested(sink, ask);
|
|
2191
2216
|
if (monitoring) {
|
|
@@ -2861,7 +2886,7 @@ export class RunManager {
|
|
|
2861
2886
|
// `runContinuation`'s handler (#141) — the field `execute` has always set is finally
|
|
2862
2887
|
// read here. `interactive` still gates it: a mid-workflow step ends its own session and
|
|
2863
2888
|
// the next step follows, so there is no ball to hand back and nothing to nudge.
|
|
2864
|
-
const autoContinued = waiting ? this.autoContinueTurn(runId, state) : false;
|
|
2889
|
+
const autoContinued = waiting && !monitoring ? this.autoContinueTurn(runId, state) : false;
|
|
2865
2890
|
if (waiting && !autoContinued) {
|
|
2866
2891
|
// Turn over, session open. Either the ball is in the user's court
|
|
2867
2892
|
// (`waiting`) — optionally with a structured `XEZ:ASK` question the
|