agent.libx.js 0.94.2 → 0.94.4
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 +2 -2
- package/dist/cli.js +140 -32
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,14 +104,14 @@ agentx --resume <id> "…" # resume a specific session
|
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
- **Filesystem + Shell** — by default the CLI has **full real-filesystem access like Claude Code** (root `/` is the machine root, the launch dir is the working dir, absolute host paths and above-cwd reach both work) with a **real `/bin/sh`** (`Shell` tool) so the agent can run git, bun, node, curl, and any installed binary. Secrets (`.env`, `.ssh`, keys, `.git`) stay hidden by the jail; env secrets are scrubbed from the child shell. `--sandbox` instead operates over an in-memory copy of the working dir with a VFS-only `bash` — the real disk is never touched. `--boddb <dir>` runs over a **persistent database workspace** (a bod-db store at `<dir>` — `meta.db` tree + `files/` bytes) that survives across runs while the real disk stays untouched; DB-native by default, or add `--seed` to hydrate it from cwd on the first run. `--no-shell` forces the VFS bash in disk mode. (`/sandbox` shows the active mode.)
|
|
107
|
-
- **Sessions** — every conversation persists to `./.agent/sessions/<id>.json`; `--continue`/`--resume` (and `/sessions`, `/resume`) pick it back up, *with memory across turns* — a REPL turn sees the previous one.
|
|
107
|
+
- **Sessions** — every conversation persists to `./.agent/sessions/<id>.json`; `--continue`/`--resume` (and `/sessions`, `/resume`) pick it back up, *with memory across turns* — a REPL turn sees the previous one. A global symlink index at `~/.agent/sessions/` enables cross-project lookup: `--resume 090715-335` resolves from any directory, and `/sessions all` lists every project's sessions in one picker.
|
|
108
108
|
- **Diffs** — every `Edit`/`Write`/`MultiEdit` renders a colorized `+/-` diff (TTY-gated; plain when piped).
|
|
109
109
|
- **Slash commands** — `/help /tools /model /compact /clear /sessions /resume /commands /init`; user-defined `./.agent/commands/<name>.md` are invokable directly as `/<name>` (the same registry the model's `SlashCommand` tool uses).
|
|
110
110
|
- **Project instructions** — `./AGENTS.md` (or `CLAUDE.md`) auto-loads into every run; `/init` scaffolds one.
|
|
111
111
|
- **Any provider** — set `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GOOGLE_API_KEY` / `GROQ_API_KEY`; choose with `-m provider/model`.
|
|
112
112
|
- **@-file mentions & headless JSON** — reference files inline in a prompt with `@path` (e.g. `explain @src/Agent.ts`); script with `-p --output-format json` to get one machine-readable result object on stdout (activity stays on stderr).
|
|
113
113
|
- **Tab-completion** — `Tab` completes `/<command>` names and `@<path>` file/dir references (descends subdirs, dotfiles hidden unless typed) straight from the working tree.
|
|
114
|
-
- **Duplex mode** — `agentx --duplex` runs the full standard REPL (slash commands, sessions, postures, rewind, MCP) with the three-tier engine driving turns: a fast voice model (`--voice-model`, default `groq/openai/gpt-oss-120b`) answers every line instantly and delegates real work to background workers built with the same wiring as a normal run (fs mode, permissions, MCP); worker activity shows as dim chrome and results are re-voiced when ready. Switch any tier live with `/model` (opens a reflex/act/think picker), or the `/voice-model` · `/think-model` shortcuts.
|
|
114
|
+
- **Duplex mode** — `agentx --duplex` runs the full standard REPL (slash commands, sessions, postures, rewind, MCP) with the three-tier engine driving turns: a fast voice model (`--voice-model`, default `groq/openai/gpt-oss-120b`) answers every line instantly and delegates real work to background workers built with the same wiring as a normal run (fs mode, permissions, MCP); worker activity shows as dim chrome and results are re-voiced when ready. Switch any tier live with `/model` (opens a reflex/act/think picker), or the `/voice-model` · `/think-model` shortcuts. `/tasks` lists background tasks and cancels a running one from a picker (Esc mid-turn cancels the foreground turn; Esc again at the idle prompt cancels running workers).
|
|
115
115
|
- **MCP servers** — declare `mcpServers: { name: { command, args } | { url } }` in config and they're auto-mounted at startup (in parallel, with an optional `mountTimeoutMs` deadline so one slow/dead server never blocks the rest): the client does the JSON-RPC handshake (stdio or HTTP) + `tools/list`, and the discovered tools appear as `mcp__<name>__<tool>` in `/tools` (inspect with `/mcp`). A bad server is logged and skipped, never blocking the agent. For large tool sets, **deferred mode** (`makeMcpToolSearch` / `mountMcpDeferred`) exposes just two bounded tools (`ToolSearch` + `McpCall`) instead of N defs — dodging the provider tool-cap and improving selection accuracy. **`mountMcpCatalog`** goes further: a cached, hash-keyed catalog + lazy connect means a turn that uses no MCP tool opens **zero** connections, and one that uses a tool connects exactly that server — latency scales with tools-used, not servers-configured. A down server is **negative-cached** (`failureCooldownMs`) so it never re-floors a later turn at the deadline. For zero turn-path latency even on a cold process, call **`warmMcpCatalog`** at boot + on a timer (off-turn discovery) and mount with **`{ discover: 'cache-only' }`** — the turn then never synchronously connects: it serves the warmed catalog and discovers any miss in the background.
|
|
116
116
|
|
|
117
117
|
## 🧬 It improves itself
|
package/dist/cli.js
CHANGED
|
@@ -1620,7 +1620,7 @@ var init_tools_shell = __esm({
|
|
|
1620
1620
|
// cli/cli.ts
|
|
1621
1621
|
import { createInterface } from "readline/promises";
|
|
1622
1622
|
import { existsSync as existsSync8, readFileSync as readFileSync5, appendFileSync, mkdirSync as mkdirSync7, writeFileSync as writeFileSync6, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
|
|
1623
|
-
import { homedir as
|
|
1623
|
+
import { homedir as homedir6, tmpdir as tmpdir2 } from "os";
|
|
1624
1624
|
|
|
1625
1625
|
// cli/clipboard.ts
|
|
1626
1626
|
import { execFileSync } from "child_process";
|
|
@@ -4192,6 +4192,15 @@ Today's date: ${(/* @__PURE__ */ new Date()).toDateString()}.`;
|
|
|
4192
4192
|
return res;
|
|
4193
4193
|
});
|
|
4194
4194
|
}
|
|
4195
|
+
/** Cancel a running background task — shared by the CancelTask tool and the CLI /tasks picker. */
|
|
4196
|
+
cancelTask(id) {
|
|
4197
|
+
const rec = this.tasks.get(id);
|
|
4198
|
+
if (!rec) return `No task '${id}'.`;
|
|
4199
|
+
if (rec.status !== "running") return `Task ${rec.id} is already ${rec.status}.`;
|
|
4200
|
+
rec.status = "cancelled";
|
|
4201
|
+
rec.controller.abort();
|
|
4202
|
+
return `Task ${rec.id} (${rec.label}) cancelled.`;
|
|
4203
|
+
}
|
|
4195
4204
|
/** Resolve when all queued voice turns AND all in-flight worker tasks have settled (tests, graceful shutdown). */
|
|
4196
4205
|
async idle() {
|
|
4197
4206
|
while (true) {
|
|
@@ -4608,14 +4617,7 @@ Another agent just implemented the above. Independently check the CURRENT state
|
|
|
4608
4617
|
name: "CancelTask",
|
|
4609
4618
|
description: "Cancel a running background task by id.",
|
|
4610
4619
|
parameters: { type: "object", required: ["id"], properties: { id: { type: "string" } } },
|
|
4611
|
-
run: async ({ id }) =>
|
|
4612
|
-
const rec = this.tasks.get(String(id));
|
|
4613
|
-
if (!rec) return `No task '${id}'.`;
|
|
4614
|
-
if (rec.status !== "running") return `Task ${rec.id} is already ${rec.status}.`;
|
|
4615
|
-
rec.status = "cancelled";
|
|
4616
|
-
rec.controller.abort();
|
|
4617
|
-
return `Task ${rec.id} (${rec.label}) cancelled.`;
|
|
4618
|
-
}
|
|
4620
|
+
run: async ({ id }) => this.cancelTask(String(id))
|
|
4619
4621
|
};
|
|
4620
4622
|
}
|
|
4621
4623
|
};
|
|
@@ -6564,19 +6566,22 @@ function formatDiff(ops, opts = {}) {
|
|
|
6564
6566
|
}
|
|
6565
6567
|
|
|
6566
6568
|
// cli/session.ts
|
|
6567
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, readdirSync, renameSync } from "fs";
|
|
6569
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, readdirSync, renameSync, symlinkSync, unlinkSync, readlinkSync } from "fs";
|
|
6570
|
+
import { homedir as homedir4 } from "os";
|
|
6568
6571
|
import { join as join6 } from "path";
|
|
6569
6572
|
var log15 = forComponent("session");
|
|
6573
|
+
var globalDir = () => join6(homedir4(), ".agent", "sessions");
|
|
6570
6574
|
var SessionStore = class {
|
|
6571
6575
|
dir;
|
|
6572
6576
|
constructor(cwd) {
|
|
6573
6577
|
this.dir = join6(cwd, ".agent", "sessions");
|
|
6574
6578
|
}
|
|
6575
|
-
/** Sortable, human-readable id: `YYYYMMDD-HHMMSS
|
|
6576
|
-
newId(now5 = Date.now()) {
|
|
6579
|
+
/** Sortable, human-readable id: `YYYYMMDD-HHMMSS-<folder>`. */
|
|
6580
|
+
newId(now5 = Date.now(), cwd) {
|
|
6577
6581
|
const d = new Date(now5);
|
|
6578
6582
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
6579
|
-
|
|
6583
|
+
const slug2 = (cwd ?? process.cwd()).split("/").pop()?.replace(/[^A-Za-z0-9_-]/g, "") || "session";
|
|
6584
|
+
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}-${slug2}`;
|
|
6580
6585
|
}
|
|
6581
6586
|
/** A session id must be one safe path segment — blocks `../`-style traversal via --resume/load/save. */
|
|
6582
6587
|
safeId(id) {
|
|
@@ -6589,6 +6594,13 @@ var SessionStore = class {
|
|
|
6589
6594
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
6590
6595
|
writeFileSync3(tmp, JSON.stringify(data));
|
|
6591
6596
|
renameSync(tmp, path);
|
|
6597
|
+
try {
|
|
6598
|
+
const gd = globalDir();
|
|
6599
|
+
if (!existsSync5(gd)) mkdirSync4(gd, { recursive: true });
|
|
6600
|
+
const link2 = join6(gd, `${data.meta.id}.json`);
|
|
6601
|
+
if (!existsSync5(link2)) symlinkSync(path, link2);
|
|
6602
|
+
} catch {
|
|
6603
|
+
}
|
|
6592
6604
|
}
|
|
6593
6605
|
load(id) {
|
|
6594
6606
|
if (!this.safeId(id)) {
|
|
@@ -6627,6 +6639,52 @@ var SessionStore = class {
|
|
|
6627
6639
|
return m ? this.load(m.id) : void 0;
|
|
6628
6640
|
}
|
|
6629
6641
|
};
|
|
6642
|
+
function globalSessionLoad(idOrPrefix) {
|
|
6643
|
+
const gd = globalDir();
|
|
6644
|
+
if (!existsSync5(gd)) return void 0;
|
|
6645
|
+
const exact = join6(gd, `${idOrPrefix}.json`);
|
|
6646
|
+
if (existsSync5(exact)) {
|
|
6647
|
+
try {
|
|
6648
|
+
const target = readlinkSync(exact);
|
|
6649
|
+
return JSON.parse(readFileSync3(target, "utf8"));
|
|
6650
|
+
} catch {
|
|
6651
|
+
return void 0;
|
|
6652
|
+
}
|
|
6653
|
+
}
|
|
6654
|
+
try {
|
|
6655
|
+
for (const f of readdirSync(gd)) {
|
|
6656
|
+
if (!f.endsWith(".json")) continue;
|
|
6657
|
+
const base = f.slice(0, -5);
|
|
6658
|
+
if (base.includes(idOrPrefix) || base.endsWith(idOrPrefix)) {
|
|
6659
|
+
const target = readlinkSync(join6(gd, f));
|
|
6660
|
+
return JSON.parse(readFileSync3(target, "utf8"));
|
|
6661
|
+
}
|
|
6662
|
+
}
|
|
6663
|
+
} catch {
|
|
6664
|
+
}
|
|
6665
|
+
return void 0;
|
|
6666
|
+
}
|
|
6667
|
+
function globalSessionList() {
|
|
6668
|
+
const gd = globalDir();
|
|
6669
|
+
if (!existsSync5(gd)) return [];
|
|
6670
|
+
const metas = [];
|
|
6671
|
+
for (const f of readdirSync(gd)) {
|
|
6672
|
+
if (!f.endsWith(".json")) continue;
|
|
6673
|
+
try {
|
|
6674
|
+
const target = readlinkSync(join6(gd, f));
|
|
6675
|
+
if (!existsSync5(target)) {
|
|
6676
|
+
try {
|
|
6677
|
+
unlinkSync(join6(gd, f));
|
|
6678
|
+
} catch {
|
|
6679
|
+
}
|
|
6680
|
+
continue;
|
|
6681
|
+
}
|
|
6682
|
+
metas.push(JSON.parse(readFileSync3(target, "utf8")).meta);
|
|
6683
|
+
} catch {
|
|
6684
|
+
}
|
|
6685
|
+
}
|
|
6686
|
+
return metas.sort((a, b) => b.updated - a.updated);
|
|
6687
|
+
}
|
|
6630
6688
|
function titleOf(messages) {
|
|
6631
6689
|
const u = messages.find((m) => m.role === "user");
|
|
6632
6690
|
const t = contentText(u?.content).replace(/\s+/g, " ").trim();
|
|
@@ -6930,7 +6988,7 @@ var GitCheckpointsOptions = class {
|
|
|
6930
6988
|
|
|
6931
6989
|
// cli/permissions.ts
|
|
6932
6990
|
import { existsSync as existsSync7, readFileSync as readFileSync4, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
|
|
6933
|
-
import { homedir as
|
|
6991
|
+
import { homedir as homedir5 } from "os";
|
|
6934
6992
|
import { join as join8 } from "path";
|
|
6935
6993
|
var RULE_RE = /^(\w+)(?:\((.+)\))?$/;
|
|
6936
6994
|
function parseOne(raw, decision) {
|
|
@@ -6964,7 +7022,7 @@ function loadPersistedRules(cwd) {
|
|
|
6964
7022
|
return {};
|
|
6965
7023
|
}
|
|
6966
7024
|
}
|
|
6967
|
-
function loadClaudeSettings(cwd, home =
|
|
7025
|
+
function loadClaudeSettings(cwd, home = homedir5()) {
|
|
6968
7026
|
const files = [join8(home, ".claude", "settings.json"), join8(cwd, ".claude", "settings.json"), join8(cwd, ".claude", "settings.local.json")];
|
|
6969
7027
|
let out = {};
|
|
6970
7028
|
for (const p of files) {
|
|
@@ -6996,7 +7054,7 @@ function mergePerms(a, b) {
|
|
|
6996
7054
|
}
|
|
6997
7055
|
return Object.keys(out).length ? out : void 0;
|
|
6998
7056
|
}
|
|
6999
|
-
var TRUST_FILE = join8(
|
|
7057
|
+
var TRUST_FILE = join8(homedir5(), ".agent", "trusted.json");
|
|
7000
7058
|
function isTrusted(cwd, file = TRUST_FILE) {
|
|
7001
7059
|
try {
|
|
7002
7060
|
return existsSync7(file) && JSON.parse(readFileSync4(file, "utf8")).includes(cwd);
|
|
@@ -7881,6 +7939,11 @@ function createLineEditor(out) {
|
|
|
7881
7939
|
}
|
|
7882
7940
|
return;
|
|
7883
7941
|
}
|
|
7942
|
+
if (key?.name === "escape" && !s.buf.length && !s.menuOpen && !s.searching && !s.prevEsc && opts.onEscapeIdle?.()) {
|
|
7943
|
+
curRow = 0;
|
|
7944
|
+
redraw();
|
|
7945
|
+
return;
|
|
7946
|
+
}
|
|
7884
7947
|
if (key?.meta && key.name === "p" && opts.onPickModel) {
|
|
7885
7948
|
process.stdin.off("keypress", onKey);
|
|
7886
7949
|
void opts.onPickModel().finally(() => {
|
|
@@ -8488,7 +8551,7 @@ Project instructions: ./AGENTS.md or ./CLAUDE.md are auto-loaded (scaffold with
|
|
|
8488
8551
|
Auto-loaded from ./.agent/: commands/, skills/, memory/, agents/.
|
|
8489
8552
|
|
|
8490
8553
|
REPL shortcuts: !<cmd> runs a shell command inline \xB7 #<note> saves a memory \xB7 @path inlines a file
|
|
8491
|
-
REPL slash commands: /help /version /tools /permissions /status /cost /context /cwd /model /reasoning /config /rename /compact /rewind /undo /clear /sessions /resume /commands /skills /mcp /init /export /paste /goal /exit (duplex: /act /think /voice /voice-model /think-model)
|
|
8554
|
+
REPL slash commands: /help /version /tools /permissions /status /cost /context /cwd /model /reasoning /config /rename /compact /rewind /undo /clear /sessions /resume /commands /skills /mcp /init /export /paste /goal /exit (duplex: /act /think /tasks /voice /voice-model /think-model)
|
|
8492
8555
|
REPL completion: type / (commands+skills) or @ (files) for a LIVE menu \u2014 \u2191/\u2193 select, \u23CE/Tab accept, Esc dismiss.
|
|
8493
8556
|
REPL multi-line: Option/Alt+Enter inserts a newline, or end a line with \\ to continue. Esc cancels a running turn / clears the input line; double-Esc jumps back to edit a previous message.
|
|
8494
8557
|
REPL shortcuts: Shift+Tab cycles permission posture (ask \u2192 accept-edits \u2192 plan) \xB7 Alt+T toggles reasoning \xB7 Alt+P switches model \xB7 Ctrl+O toggles verbose tool output \xB7 \u2192 or Tab accepts the dim history ghost-suggestion \xB7 Alt+S/Ctrl+S stash/unstash.
|
|
@@ -9001,7 +9064,7 @@ function pastePathClassifier(cwd) {
|
|
|
9001
9064
|
t = t.replace(/\\ /g, " ").replace(/^['"]|['"]$/g, "");
|
|
9002
9065
|
if (/\s/.test(t)) return null;
|
|
9003
9066
|
if (!/^(\/|~\/|\.\/|\.\.\/)/.test(t)) return null;
|
|
9004
|
-
const abs = t.startsWith("~/") ? join9(
|
|
9067
|
+
const abs = t.startsWith("~/") ? join9(homedir6(), t.slice(2)) : resolve3(cwd, t);
|
|
9005
9068
|
try {
|
|
9006
9069
|
if (!statSync3(abs).isFile()) return null;
|
|
9007
9070
|
} catch {
|
|
@@ -9105,13 +9168,16 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
9105
9168
|
const lastUser = res.messages.map((m2) => m2.role).lastIndexOf("user");
|
|
9106
9169
|
const tools = res.messages.slice(lastUser).filter((m2) => m2.role === "tool").length;
|
|
9107
9170
|
const ok = res.finishReason === "stop";
|
|
9108
|
-
const shortId = session.meta.id.
|
|
9109
|
-
|
|
9171
|
+
const shortId = session.meta.id.replace(/^\d{8}-/, "");
|
|
9172
|
+
const silentAbort = res.finishReason === "aborted" && !res.usage?.totalTokens;
|
|
9173
|
+
if (!silentAbort)
|
|
9174
|
+
err("\n" + (process.stderr.isTTY ? "\r\x1B[0J" : "") + (ok ? green(" \u2713 done") : red(` \u2717 ${res.finishReason}`)) + dim(` \xB7 ${res.steps} steps \xB7 ${tools} tools \xB7 ${tok}${secs}s \xB7 ${shortId}
|
|
9110
9175
|
`));
|
|
9111
9176
|
if (res.finishReason === "error" && res.error) {
|
|
9112
9177
|
const e = res.error;
|
|
9113
9178
|
err(red(` ${e?.message ?? e}`) + (e?.statusCode ? dim(` (${e.statusCode}${e.code ? " " + e.code : ""})`) : "") + "\n");
|
|
9114
9179
|
}
|
|
9180
|
+
if (silentAbort) return { ok: false, res };
|
|
9115
9181
|
session.messages = agent.transcript;
|
|
9116
9182
|
session.meta.turns += 1;
|
|
9117
9183
|
session.meta.tokens = (session.meta.tokens ?? 0) + (res.usage?.totalTokens ?? 0);
|
|
@@ -9133,12 +9199,12 @@ async function runTurn(agent, store, session, task, cp, cwd = process.cwd(), sen
|
|
|
9133
9199
|
}
|
|
9134
9200
|
function startSession(args, store, agent, cwd) {
|
|
9135
9201
|
if (args.resume || args.cont) {
|
|
9136
|
-
const data = args.resume ? store.load(args.resume) : store.latestData();
|
|
9202
|
+
const data = args.resume ? store.load(args.resume) ?? globalSessionLoad(args.resume) : store.latestData();
|
|
9137
9203
|
if (data) {
|
|
9138
9204
|
agent.transcript = data.messages;
|
|
9139
9205
|
if (args.fork) {
|
|
9140
9206
|
const now6 = Date.now();
|
|
9141
|
-
const forked = { meta: { ...data.meta, id: args.sessionId ?? store.newId(now6), created: now6, updated: now6, turns: data.meta.turns }, messages: data.messages };
|
|
9207
|
+
const forked = { meta: { ...data.meta, id: args.sessionId ?? store.newId(now6, cwd), created: now6, updated: now6, turns: data.meta.turns }, messages: data.messages };
|
|
9142
9208
|
err(dim(` forked ${data.meta.id} \u2192 ${forked.meta.id} (${data.meta.turns} turns)
|
|
9143
9209
|
`));
|
|
9144
9210
|
if (!args.task) printHistory(data.messages);
|
|
@@ -9153,7 +9219,7 @@ function startSession(args, store, agent, cwd) {
|
|
|
9153
9219
|
`));
|
|
9154
9220
|
}
|
|
9155
9221
|
const now5 = Date.now();
|
|
9156
|
-
const id = args.sessionId ?? store.newId(now5);
|
|
9222
|
+
const id = args.sessionId ?? store.newId(now5, cwd);
|
|
9157
9223
|
if (!args.task) err(dim(` session ${id}
|
|
9158
9224
|
`));
|
|
9159
9225
|
return { meta: { id, created: now5, updated: now5, cwd, model: agent.options.model, turns: 0, title: "" }, messages: [] };
|
|
@@ -9597,7 +9663,7 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
9597
9663
|
const fs = agent.options.fs;
|
|
9598
9664
|
const fsBase = fs.getCwd() === "/" ? "" : fs.getCwd();
|
|
9599
9665
|
const adot = (sub) => `${fsBase}/.agent/${sub}`;
|
|
9600
|
-
const adots = (sub) => [adot(sub), `${fsBase}/.claude/${sub}`, `${
|
|
9666
|
+
const adots = (sub) => [adot(sub), `${fsBase}/.claude/${sub}`, `${homedir6()}/.agent/${sub}`, `${homedir6()}/.claude/${sub}`];
|
|
9601
9667
|
const cmds = (await loadCommands(fs, adots("commands"))).commands;
|
|
9602
9668
|
const skills = (await loadSkills(fs, adots("skills"))).skills;
|
|
9603
9669
|
const histPath = join9(cwd, ".agent", "history");
|
|
@@ -9685,16 +9751,20 @@ async function repl(args, ai, cfg, cwd) {
|
|
|
9685
9751
|
}
|
|
9686
9752
|
return void 0;
|
|
9687
9753
|
};
|
|
9688
|
-
const pickSession = async () => {
|
|
9689
|
-
const list = store.list();
|
|
9754
|
+
const pickSession = async (global = false) => {
|
|
9755
|
+
const list = global ? globalSessionList() : store.list();
|
|
9690
9756
|
if (!list.length) {
|
|
9691
|
-
err(dim(
|
|
9757
|
+
err(dim(` (no saved sessions${global ? "" : " in this project"} yet)
|
|
9758
|
+
`));
|
|
9692
9759
|
return;
|
|
9693
9760
|
}
|
|
9694
|
-
const items = list.slice(0, 50).map((m) =>
|
|
9695
|
-
|
|
9761
|
+
const items = list.slice(0, 50).map((m) => {
|
|
9762
|
+
const where = global && m.cwd !== cwd ? ` \xB7 ${m.cwd.split("/").pop()}` : "";
|
|
9763
|
+
return { label: `${m.id} ${m.title || "(untitled)"}`, value: m.id, desc: `${ago(m.updated)} \xB7 ${m.turns} turn${m.turns === 1 ? "" : "s"}${where}` };
|
|
9764
|
+
});
|
|
9765
|
+
const id = await selectMenu(process.stderr, { title: global ? "Resume a session (all projects)" : "Resume a session", items, current: session.meta.id });
|
|
9696
9766
|
if (!id) return;
|
|
9697
|
-
const data = store.load(id);
|
|
9767
|
+
const data = store.load(id) ?? globalSessionLoad(id);
|
|
9698
9768
|
if (data) resumeInto(data);
|
|
9699
9769
|
else err(red(" no such session\n"));
|
|
9700
9770
|
};
|
|
@@ -9994,6 +10064,34 @@ ${extra}` : body);
|
|
|
9994
10064
|
const off = dx.options.thinkModel === false;
|
|
9995
10065
|
const id = await dx.dispatch(a.join(" "), "think");
|
|
9996
10066
|
err(dim(` \u2192 task ${id} ${off ? "(think tier off \u2014 running as act)" : "(think)"} started
|
|
10067
|
+
`));
|
|
10068
|
+
}
|
|
10069
|
+
}, tasks: {
|
|
10070
|
+
desc: "background tasks \u2014 /tasks [cancel <id>], or alone for a picker (\u21B5 cancels the selected running task)",
|
|
10071
|
+
run: async (a) => {
|
|
10072
|
+
const all = [...dx.tasks.values()];
|
|
10073
|
+
if (!all.length) {
|
|
10074
|
+
err(dim(" no background tasks\n"));
|
|
10075
|
+
return;
|
|
10076
|
+
}
|
|
10077
|
+
if (a[0]?.toLowerCase() === "cancel") {
|
|
10078
|
+
if (!a[1]) {
|
|
10079
|
+
err(yellow(" usage: /tasks cancel <id>\n"));
|
|
10080
|
+
return;
|
|
10081
|
+
}
|
|
10082
|
+
err(dim(` ${dx.cancelTask(a[1])}
|
|
10083
|
+
`));
|
|
10084
|
+
return;
|
|
10085
|
+
}
|
|
10086
|
+
const mark = (s) => s === "running" ? cyan("\u25D4 running") : s === "done" ? green("\u2713 done") : s === "cancelled" ? yellow("\u2298 cancelled") : red(`\u2717 ${s}`);
|
|
10087
|
+
if (!process.stderr.isTTY || !process.stdin.isTTY) {
|
|
10088
|
+
for (const t of all) err(` ${t.id} ${mark(t.status)} ${dim(t.label.slice(0, 60))}
|
|
10089
|
+
`);
|
|
10090
|
+
return;
|
|
10091
|
+
}
|
|
10092
|
+
const items = all.map((t) => ({ label: `${t.id} ${t.label.slice(0, 60)}`, value: t.id, desc: mark(t.status) + (t.status === "running" ? " \xB7 \u21B5 to cancel" : "") }));
|
|
10093
|
+
const id = await selectMenu(process.stderr, { title: "Background tasks \xB7 \u21B5 cancel running \xB7 esc close", items });
|
|
10094
|
+
if (id) err(dim(` ${dx.cancelTask(String(id))}
|
|
9997
10095
|
`));
|
|
9998
10096
|
}
|
|
9999
10097
|
} } : {},
|
|
@@ -10137,7 +10235,10 @@ ${extra}` : body);
|
|
|
10137
10235
|
err("\x1Bc");
|
|
10138
10236
|
}
|
|
10139
10237
|
},
|
|
10140
|
-
sessions: {
|
|
10238
|
+
sessions: {
|
|
10239
|
+
desc: "pick a saved session to resume \u2014 /sessions [all] (all = across all projects)",
|
|
10240
|
+
run: (a) => pickSession(a[0] === "all")
|
|
10241
|
+
},
|
|
10141
10242
|
resume: {
|
|
10142
10243
|
desc: "resume a session \u2014 /resume <id>, or /resume alone to pick from a list",
|
|
10143
10244
|
run: async (a) => {
|
|
@@ -10145,7 +10246,7 @@ ${extra}` : body);
|
|
|
10145
10246
|
await pickSession();
|
|
10146
10247
|
return;
|
|
10147
10248
|
}
|
|
10148
|
-
const data = store.load(a[0]);
|
|
10249
|
+
const data = store.load(a[0]) ?? globalSessionLoad(a[0]);
|
|
10149
10250
|
if (data) resumeInto(data);
|
|
10150
10251
|
else err(red(` no such session
|
|
10151
10252
|
`));
|
|
@@ -10717,6 +10818,13 @@ ${extra}` : body);
|
|
|
10717
10818
|
vimMode: cfg.editorMode === "vim",
|
|
10718
10819
|
statusTickMs: dx ? 1e3 : void 0,
|
|
10719
10820
|
// duplex: animate the running-task footer while idle at the prompt
|
|
10821
|
+
// Esc at the idle prompt with workers running → cancel them (CC-style: Esc stops the work).
|
|
10822
|
+
onEscapeIdle: dx ? () => {
|
|
10823
|
+
const running = [...dx.tasks.values()].filter((t) => t.status === "running");
|
|
10824
|
+
if (!running.length) return false;
|
|
10825
|
+
for (const t of running) err("\r\x1B[0J" + yellow(` \u2298 ${dx.cancelTask(t.id)}`) + "\n");
|
|
10826
|
+
return true;
|
|
10827
|
+
} : void 0,
|
|
10720
10828
|
onCyclePosture: cyclePosture,
|
|
10721
10829
|
onToggleThinking: toggleReasoning,
|
|
10722
10830
|
onToggleVerbose: toggleVerbose,
|