ai-whisper 0.5.8 → 0.6.0
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/dist/bin/companion-agent.js +969 -26
- package/dist/bin/whisper.js +1041 -64
- package/package.json +6 -6
|
@@ -5914,6 +5914,7 @@ function createLiveSessionRuntime(input) {
|
|
|
5914
5914
|
let relayPreviewVisible = false;
|
|
5915
5915
|
let inputState = {};
|
|
5916
5916
|
let pausedInputDepth = 0;
|
|
5917
|
+
let overlayPush = null;
|
|
5917
5918
|
function setMountedRawMode(mode) {
|
|
5918
5919
|
if (canManageRawMode) {
|
|
5919
5920
|
ttyStdin.setRawMode?.(mode);
|
|
@@ -5951,7 +5952,7 @@ function createLiveSessionRuntime(input) {
|
|
|
5951
5952
|
input.interactiveSession.sendLocalMessage(CLEAR_LINE2);
|
|
5952
5953
|
}
|
|
5953
5954
|
let inputLineBuffer = "";
|
|
5954
|
-
function feedLineBufferedInput(data) {
|
|
5955
|
+
async function feedLineBufferedInput(data) {
|
|
5955
5956
|
for (const char of data) {
|
|
5956
5957
|
if (char === "") {
|
|
5957
5958
|
if (inputLineBuffer.length > 0) {
|
|
@@ -5970,7 +5971,7 @@ function createLiveSessionRuntime(input) {
|
|
|
5970
5971
|
const completed = inputLineBuffer;
|
|
5971
5972
|
inputLineBuffer = "";
|
|
5972
5973
|
const session = input.interactiveSession;
|
|
5973
|
-
if (completed.length > 0
|
|
5974
|
+
if (completed.length > 0) {
|
|
5974
5975
|
const cols = ttyCols(input.stdout);
|
|
5975
5976
|
const len = Array.from(completed).length;
|
|
5976
5977
|
const plainRows = Math.max(1, Math.ceil((STRIPE_COLS + len) / cols));
|
|
@@ -5978,13 +5979,18 @@ function createLiveSessionRuntime(input) {
|
|
|
5978
5979
|
if (plainRows > 1) erase += `\x1B[${plainRows - 1}A`;
|
|
5979
5980
|
erase += "\x1B[J";
|
|
5980
5981
|
input.stdout.write(erase);
|
|
5981
|
-
session.
|
|
5982
|
+
if (await session.tryConsumeLocalCommand?.(completed)) {
|
|
5983
|
+
continue;
|
|
5984
|
+
}
|
|
5985
|
+
if (session.echoUserInput) {
|
|
5986
|
+
session.echoUserInput(completed, cols);
|
|
5987
|
+
} else {
|
|
5988
|
+
input.stdout.write("\n");
|
|
5989
|
+
}
|
|
5990
|
+
input.interactiveSession.writeUserInput(completed);
|
|
5982
5991
|
} else {
|
|
5983
5992
|
input.stdout.write("\n");
|
|
5984
5993
|
}
|
|
5985
|
-
if (completed.length > 0) {
|
|
5986
|
-
input.interactiveSession.writeUserInput(completed);
|
|
5987
|
-
}
|
|
5988
5994
|
continue;
|
|
5989
5995
|
}
|
|
5990
5996
|
if (char === "\b" || char === "\x7F") {
|
|
@@ -5999,6 +6005,10 @@ function createLiveSessionRuntime(input) {
|
|
|
5999
6005
|
}
|
|
6000
6006
|
}
|
|
6001
6007
|
async function processChunk(raw) {
|
|
6008
|
+
if (overlayPush) {
|
|
6009
|
+
overlayPush(raw);
|
|
6010
|
+
return;
|
|
6011
|
+
}
|
|
6002
6012
|
const normalized = normalizeTerminalInput({
|
|
6003
6013
|
raw,
|
|
6004
6014
|
state: inputState
|
|
@@ -6054,7 +6064,7 @@ function createLiveSessionRuntime(input) {
|
|
|
6054
6064
|
clearRelayPreview();
|
|
6055
6065
|
if (decision.kind === "passthrough") {
|
|
6056
6066
|
if (input.lineBufferedInput) {
|
|
6057
|
-
feedLineBufferedInput(decision.data);
|
|
6067
|
+
await feedLineBufferedInput(decision.data);
|
|
6058
6068
|
} else {
|
|
6059
6069
|
input.interactiveSession.writeUserInput(decision.data);
|
|
6060
6070
|
}
|
|
@@ -6141,6 +6151,42 @@ function createLiveSessionRuntime(input) {
|
|
|
6141
6151
|
}
|
|
6142
6152
|
}
|
|
6143
6153
|
},
|
|
6154
|
+
async runInteractiveOverlay(run) {
|
|
6155
|
+
pausedInputDepth += 1;
|
|
6156
|
+
setMountedRawMode(true);
|
|
6157
|
+
const buf = [];
|
|
6158
|
+
const wakeHolder = { fn: null };
|
|
6159
|
+
let done = false;
|
|
6160
|
+
overlayPush = (chunk) => {
|
|
6161
|
+
buf.push(chunk);
|
|
6162
|
+
wakeHolder.fn?.();
|
|
6163
|
+
};
|
|
6164
|
+
const keys = (async function* () {
|
|
6165
|
+
for (; ; ) {
|
|
6166
|
+
if (buf.length) {
|
|
6167
|
+
yield buf.shift();
|
|
6168
|
+
continue;
|
|
6169
|
+
}
|
|
6170
|
+
if (done) return;
|
|
6171
|
+
await new Promise((r) => wakeHolder.fn = r);
|
|
6172
|
+
}
|
|
6173
|
+
})();
|
|
6174
|
+
try {
|
|
6175
|
+
await run({
|
|
6176
|
+
keys,
|
|
6177
|
+
write: (s) => input.stdout.write(s),
|
|
6178
|
+
setRawMode: (on) => setMountedRawMode(on)
|
|
6179
|
+
});
|
|
6180
|
+
} finally {
|
|
6181
|
+
done = true;
|
|
6182
|
+
const fn = wakeHolder.fn;
|
|
6183
|
+
wakeHolder.fn = null;
|
|
6184
|
+
fn?.();
|
|
6185
|
+
overlayPush = null;
|
|
6186
|
+
pausedInputDepth = Math.max(0, pausedInputDepth - 1);
|
|
6187
|
+
setMountedRawMode(true);
|
|
6188
|
+
}
|
|
6189
|
+
},
|
|
6144
6190
|
async stop() {
|
|
6145
6191
|
clearRelayPreview();
|
|
6146
6192
|
setMountedRawMode(Boolean(previousRawMode));
|
|
@@ -7321,10 +7367,21 @@ function isProtocolCompatible(theirs, ours = PROTOCOL_VERSION) {
|
|
|
7321
7367
|
// ../../node_modules/.pnpm/@ai-ezio+harness@file+..+ai-ezio+packages+harness/node_modules/@ai-ezio/harness/dist/turn-gate.js
|
|
7322
7368
|
var TurnGate = class {
|
|
7323
7369
|
tail = Promise.resolve();
|
|
7370
|
+
heldCount = 0;
|
|
7371
|
+
/** True while any acquirer (queued or active) is outstanding. */
|
|
7372
|
+
get held() {
|
|
7373
|
+
return this.heldCount > 0;
|
|
7374
|
+
}
|
|
7324
7375
|
/** Resolves with a release function once every earlier acquirer released. */
|
|
7325
7376
|
acquire() {
|
|
7377
|
+
this.heldCount += 1;
|
|
7326
7378
|
let release;
|
|
7327
|
-
const held = new Promise((r) =>
|
|
7379
|
+
const held = new Promise((r) => {
|
|
7380
|
+
release = () => {
|
|
7381
|
+
this.heldCount -= 1;
|
|
7382
|
+
r();
|
|
7383
|
+
};
|
|
7384
|
+
});
|
|
7328
7385
|
const turn = this.tail.then(() => release);
|
|
7329
7386
|
this.tail = this.tail.then(() => held);
|
|
7330
7387
|
return turn;
|
|
@@ -7352,6 +7409,12 @@ var EngineExitedError = class extends Error {
|
|
|
7352
7409
|
this.name = "EngineExitedError";
|
|
7353
7410
|
}
|
|
7354
7411
|
};
|
|
7412
|
+
var EngineBusyError = class extends Error {
|
|
7413
|
+
constructor(message = "cannot resume while a turn is in flight") {
|
|
7414
|
+
super(message);
|
|
7415
|
+
this.name = "EngineBusyError";
|
|
7416
|
+
}
|
|
7417
|
+
};
|
|
7355
7418
|
var CompactTimeoutError = class extends Error {
|
|
7356
7419
|
constructor(ms) {
|
|
7357
7420
|
super(`compact control timed out after ${ms}ms (no compacted event)`);
|
|
@@ -7368,6 +7431,13 @@ var Session = class {
|
|
|
7368
7431
|
closed = false;
|
|
7369
7432
|
exitHandlers = [];
|
|
7370
7433
|
ready;
|
|
7434
|
+
/** Bumped on every spawn (start + resume). The pump closure captures its own
|
|
7435
|
+
* generation; a stale generation's events and EOF are dropped, so a closed
|
|
7436
|
+
* child cannot corrupt a freshly-respawned session. */
|
|
7437
|
+
generation = 0;
|
|
7438
|
+
/** Resolves when the CURRENT pump's `for await` loop has fully unwound
|
|
7439
|
+
* (its `finally` ran). `resume()` awaits this to sequence teardown. */
|
|
7440
|
+
pumpDone = Promise.resolve();
|
|
7371
7441
|
_transcriptPath;
|
|
7372
7442
|
/** The `HAX_TRANSCRIPT` mirror path this session was started with, if any.
|
|
7373
7443
|
* Consumers (ezio CLI, ai-whisper) read it here rather than re-deriving the
|
|
@@ -7499,25 +7569,40 @@ var Session = class {
|
|
|
7499
7569
|
}
|
|
7500
7570
|
/** Spawn hax, pump events, and gate on the `ready` protocol version. */
|
|
7501
7571
|
async start(options = {}) {
|
|
7572
|
+
return this.spawnAndPump(options);
|
|
7573
|
+
}
|
|
7574
|
+
/** Spawn the child, launch the (generation-stamped) event pump, and resolve
|
|
7575
|
+
* the `ready` version gate. Shared by start() and resume() so the pump is
|
|
7576
|
+
* stamped in exactly one place. Captures the CURRENT generation — callers that
|
|
7577
|
+
* need a fresh generation (resume) bump it before calling. */
|
|
7578
|
+
async spawnAndPump(options) {
|
|
7502
7579
|
this._transcriptPath = options.transcriptPath;
|
|
7503
|
-
const
|
|
7580
|
+
const gen = this.generation;
|
|
7581
|
+
const spawned = (this.options.spawn ?? spawnHax)(options);
|
|
7504
7582
|
this.spawned = spawned;
|
|
7505
7583
|
spawned.child.on("exit", (code, signal) => {
|
|
7584
|
+
if (gen !== this.generation)
|
|
7585
|
+
return;
|
|
7506
7586
|
for (const handler of this.exitHandlers)
|
|
7507
7587
|
handler({ code, signal });
|
|
7508
7588
|
});
|
|
7509
|
-
const transport = new FdTransport(spawned.eventStream, spawned.controlStream);
|
|
7589
|
+
const transport = (this.options.transportFactory ?? ((e, c) => new FdTransport(e, c)))(spawned.eventStream, spawned.controlStream);
|
|
7510
7590
|
this.transport = transport;
|
|
7511
|
-
|
|
7591
|
+
this.pumpDone = (async () => {
|
|
7512
7592
|
try {
|
|
7513
|
-
for await (const event of transport.events())
|
|
7593
|
+
for await (const event of transport.events()) {
|
|
7594
|
+
if (gen !== this.generation)
|
|
7595
|
+
continue;
|
|
7514
7596
|
this.deliver(event);
|
|
7597
|
+
}
|
|
7515
7598
|
} finally {
|
|
7516
|
-
this.
|
|
7517
|
-
|
|
7518
|
-
this.idleHooks.
|
|
7519
|
-
|
|
7520
|
-
this.
|
|
7599
|
+
if (gen === this.generation) {
|
|
7600
|
+
this.ended = true;
|
|
7601
|
+
while (this.idleHooks.length)
|
|
7602
|
+
this.idleHooks.shift()();
|
|
7603
|
+
while (this.waiters.length || this.exclusiveWaiters.length)
|
|
7604
|
+
this.deliver(null);
|
|
7605
|
+
}
|
|
7521
7606
|
}
|
|
7522
7607
|
})();
|
|
7523
7608
|
const ready = await this.waitForEvent("ready");
|
|
@@ -7689,6 +7774,47 @@ var Session = class {
|
|
|
7689
7774
|
throw new Error("unexpected event");
|
|
7690
7775
|
return { turnId: e.turnId, content: e.content };
|
|
7691
7776
|
}
|
|
7777
|
+
/** Switch this session to a past one: tear down the current hax child, reset
|
|
7778
|
+
* lifecycle state, and respawn headless hax with `--resume=ID` plus the prior
|
|
7779
|
+
* options. The constructor-bound onEvent stays attached. Rejects (engine left
|
|
7780
|
+
* closed) on spawn/protocol failure. Refuses while a turn holds the gate. */
|
|
7781
|
+
async resume(sessionId, options = {}) {
|
|
7782
|
+
if (this.gate.held) {
|
|
7783
|
+
throw new EngineBusyError();
|
|
7784
|
+
}
|
|
7785
|
+
const release = await this.gate.acquire();
|
|
7786
|
+
try {
|
|
7787
|
+
const dying = this.pumpDone;
|
|
7788
|
+
this.generation += 1;
|
|
7789
|
+
this.close();
|
|
7790
|
+
await dying;
|
|
7791
|
+
this.resetLifecycleLatches();
|
|
7792
|
+
const args = [...options.args ?? [], `--resume=${sessionId}`];
|
|
7793
|
+
return await this.spawnAndPump({
|
|
7794
|
+
...options,
|
|
7795
|
+
args,
|
|
7796
|
+
transcriptPath: options.transcriptPath ?? this._transcriptPath
|
|
7797
|
+
});
|
|
7798
|
+
} finally {
|
|
7799
|
+
release();
|
|
7800
|
+
}
|
|
7801
|
+
}
|
|
7802
|
+
/** Clear the close()/EOF latches and any parked stream state so the same
|
|
7803
|
+
* Session object is reusable across a respawn. Safe only between turns (resume
|
|
7804
|
+
* holds the gate, so no waiters are outstanding). */
|
|
7805
|
+
resetLifecycleLatches() {
|
|
7806
|
+
this.closed = false;
|
|
7807
|
+
this.ended = false;
|
|
7808
|
+
this.ready = void 0;
|
|
7809
|
+
this.queue.length = 0;
|
|
7810
|
+
this.waiters.length = 0;
|
|
7811
|
+
this.exclusiveQueue.length = 0;
|
|
7812
|
+
this.exclusiveWaiters.length = 0;
|
|
7813
|
+
this.idleHooks.length = 0;
|
|
7814
|
+
this.swallowNextIdle = false;
|
|
7815
|
+
this.staleCompactsPending = 0;
|
|
7816
|
+
this.cycleInternal = false;
|
|
7817
|
+
}
|
|
7692
7818
|
/** Start a fresh conversation; resolves once the engine is idle again.
|
|
7693
7819
|
* Gate-serialized like every turn initiator (M11). */
|
|
7694
7820
|
async newConversation() {
|
|
@@ -7926,6 +8052,122 @@ function createAutoCompactDriver(opts) {
|
|
|
7926
8052
|
};
|
|
7927
8053
|
}
|
|
7928
8054
|
|
|
8055
|
+
// ../../node_modules/.pnpm/@ai-ezio+harness@file+..+ai-ezio+packages+harness/node_modules/@ai-ezio/harness/dist/session-titles.js
|
|
8056
|
+
import { join as join11 } from "node:path";
|
|
8057
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8058
|
+
import { dirname as dirname4 } from "node:path";
|
|
8059
|
+
var nodeTitleFs = {
|
|
8060
|
+
readFileSync: (p) => {
|
|
8061
|
+
try {
|
|
8062
|
+
return readFileSync3(p, "utf8");
|
|
8063
|
+
} catch {
|
|
8064
|
+
return void 0;
|
|
8065
|
+
}
|
|
8066
|
+
},
|
|
8067
|
+
writeFileSync: (p, d) => writeFileSync3(p, d),
|
|
8068
|
+
renameSync: (from, to) => renameSync(from, to),
|
|
8069
|
+
mkdirSync: (dir) => void mkdirSync4(dir, { recursive: true })
|
|
8070
|
+
};
|
|
8071
|
+
function defaultTitleStorePath(env = process.env) {
|
|
8072
|
+
const base = env.XDG_STATE_HOME || join11(env.HOME ?? "", ".local", "state");
|
|
8073
|
+
return join11(base, "ai-ezio", "session-titles.json");
|
|
8074
|
+
}
|
|
8075
|
+
function createSessionTitleStore(opts = {}) {
|
|
8076
|
+
const filePath = opts.filePath ?? defaultTitleStorePath();
|
|
8077
|
+
const fs2 = opts.fs ?? nodeTitleFs;
|
|
8078
|
+
const read = () => {
|
|
8079
|
+
const raw = fs2.readFileSync(filePath);
|
|
8080
|
+
if (!raw)
|
|
8081
|
+
return {};
|
|
8082
|
+
try {
|
|
8083
|
+
const parsed = JSON.parse(raw);
|
|
8084
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
8085
|
+
} catch {
|
|
8086
|
+
return {};
|
|
8087
|
+
}
|
|
8088
|
+
};
|
|
8089
|
+
return {
|
|
8090
|
+
getTitle: (id) => {
|
|
8091
|
+
const rec = read()[id];
|
|
8092
|
+
return rec && typeof rec.title === "string" ? rec.title : void 0;
|
|
8093
|
+
},
|
|
8094
|
+
setTitle: (id, title) => {
|
|
8095
|
+
const t = title.trim();
|
|
8096
|
+
if (t === "")
|
|
8097
|
+
return;
|
|
8098
|
+
const all = read();
|
|
8099
|
+
all[id] = { title: t, updatedAt: clockNow() };
|
|
8100
|
+
fs2.mkdirSync(dirname4(filePath));
|
|
8101
|
+
const tmp = `${filePath}.tmp`;
|
|
8102
|
+
fs2.writeFileSync(tmp, JSON.stringify(all, null, " "));
|
|
8103
|
+
fs2.renameSync(tmp, filePath);
|
|
8104
|
+
},
|
|
8105
|
+
loadTitles: () => {
|
|
8106
|
+
const all = read();
|
|
8107
|
+
const map = /* @__PURE__ */ new Map();
|
|
8108
|
+
for (const [id, rec] of Object.entries(all)) {
|
|
8109
|
+
if (rec && typeof rec.title === "string")
|
|
8110
|
+
map.set(id, rec.title);
|
|
8111
|
+
}
|
|
8112
|
+
return map;
|
|
8113
|
+
}
|
|
8114
|
+
};
|
|
8115
|
+
}
|
|
8116
|
+
function clockNow() {
|
|
8117
|
+
return Date.now();
|
|
8118
|
+
}
|
|
8119
|
+
function normalizeId(id) {
|
|
8120
|
+
return id && id !== "unknown" ? id : void 0;
|
|
8121
|
+
}
|
|
8122
|
+
function createRenameController(deps) {
|
|
8123
|
+
let currentId;
|
|
8124
|
+
let pendingTitle;
|
|
8125
|
+
let statusRequested = false;
|
|
8126
|
+
const requestStatusOnce = () => {
|
|
8127
|
+
if (statusRequested)
|
|
8128
|
+
return;
|
|
8129
|
+
statusRequested = true;
|
|
8130
|
+
deps.requestStatus();
|
|
8131
|
+
};
|
|
8132
|
+
const setId = (id) => {
|
|
8133
|
+
const next = normalizeId(id);
|
|
8134
|
+
if (!next || next === currentId)
|
|
8135
|
+
return;
|
|
8136
|
+
currentId = next;
|
|
8137
|
+
if (pendingTitle !== void 0) {
|
|
8138
|
+
deps.store.setTitle(next, pendingTitle);
|
|
8139
|
+
pendingTitle = void 0;
|
|
8140
|
+
}
|
|
8141
|
+
};
|
|
8142
|
+
return {
|
|
8143
|
+
currentSessionId: () => currentId,
|
|
8144
|
+
getSessionTitle: () => pendingTitle ?? (currentId ? deps.store.getTitle(currentId) : void 0),
|
|
8145
|
+
setSessionTitle: (title) => {
|
|
8146
|
+
const t = title.trim();
|
|
8147
|
+
if (t === "")
|
|
8148
|
+
return;
|
|
8149
|
+
if (currentId)
|
|
8150
|
+
deps.store.setTitle(currentId, t);
|
|
8151
|
+
else
|
|
8152
|
+
pendingTitle = t;
|
|
8153
|
+
},
|
|
8154
|
+
noteEvent: (event) => {
|
|
8155
|
+
if (event.type === "ready" || event.type === "status") {
|
|
8156
|
+
statusRequested = false;
|
|
8157
|
+
setId(event.sessionId);
|
|
8158
|
+
} else if (event.type === "idle" && currentId === void 0) {
|
|
8159
|
+
requestStatusOnce();
|
|
8160
|
+
}
|
|
8161
|
+
},
|
|
8162
|
+
noteNewConversation: () => {
|
|
8163
|
+
pendingTitle = void 0;
|
|
8164
|
+
currentId = void 0;
|
|
8165
|
+
statusRequested = false;
|
|
8166
|
+
requestStatusOnce();
|
|
8167
|
+
}
|
|
8168
|
+
};
|
|
8169
|
+
}
|
|
8170
|
+
|
|
7929
8171
|
// ../adapter-ai-ezio/dist/ai-ezio-engine.js
|
|
7930
8172
|
var defaultCreateEngineSession = ({ onEvent }) => new Session({ onEvent });
|
|
7931
8173
|
|
|
@@ -8209,11 +8451,11 @@ async function callHostRehydration(host) {
|
|
|
8209
8451
|
}
|
|
8210
8452
|
|
|
8211
8453
|
// ../../node_modules/.pnpm/@ai-ezio+mcp-host@file+..+ai-ezio+packages+mcp-host_zod@3.25.76/node_modules/@ai-ezio/mcp-host/dist/config.js
|
|
8212
|
-
import { readFileSync as
|
|
8213
|
-
import { join as
|
|
8454
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
8455
|
+
import { join as join12 } from "node:path";
|
|
8214
8456
|
function configPath(env = process.env) {
|
|
8215
|
-
const base = env.XDG_CONFIG_HOME?.trim() ||
|
|
8216
|
-
return
|
|
8457
|
+
const base = env.XDG_CONFIG_HOME?.trim() || join12(env.HOME ?? "", ".config");
|
|
8458
|
+
return join12(base, "ai-ezio", "mcp.json");
|
|
8217
8459
|
}
|
|
8218
8460
|
function parseConfig(text) {
|
|
8219
8461
|
if (!text || !text.trim())
|
|
@@ -8233,7 +8475,7 @@ function parseConfig(text) {
|
|
|
8233
8475
|
}
|
|
8234
8476
|
function loadConfig2(env = process.env) {
|
|
8235
8477
|
try {
|
|
8236
|
-
return parseConfig(
|
|
8478
|
+
return parseConfig(readFileSync4(configPath(env), "utf8"));
|
|
8237
8479
|
} catch {
|
|
8238
8480
|
return { servers: [], toolPolicy: {}, hostPrivateTools: [] };
|
|
8239
8481
|
}
|
|
@@ -8401,8 +8643,8 @@ function fmtTokens(n) {
|
|
|
8401
8643
|
return `${Math.floor((n + 512 * 1024) / (1024 * 1024))}M`;
|
|
8402
8644
|
}
|
|
8403
8645
|
function truncate(s, max = ARG_MAX) {
|
|
8404
|
-
const
|
|
8405
|
-
return
|
|
8646
|
+
const oneLine2 = s.replace(/\s+/g, " ").trim();
|
|
8647
|
+
return oneLine2.length > max ? `${oneLine2.slice(0, max - 1)}\u2026` : oneLine2;
|
|
8406
8648
|
}
|
|
8407
8649
|
function createMountedRenderer(input) {
|
|
8408
8650
|
const utf8 = input.utf8 ?? true;
|
|
@@ -8545,6 +8787,9 @@ ${prompt}`);
|
|
|
8545
8787
|
renderPrompt,
|
|
8546
8788
|
handle(event) {
|
|
8547
8789
|
switch (event.type) {
|
|
8790
|
+
case "ready":
|
|
8791
|
+
bannerRendered = false;
|
|
8792
|
+
break;
|
|
8548
8793
|
case "status":
|
|
8549
8794
|
if (!bannerRendered) {
|
|
8550
8795
|
renderBanner(event.provider, event.model, event.effort ?? "");
|
|
@@ -8596,6 +8841,580 @@ ${RED}\u258C ${event.message}${RESET2}`);
|
|
|
8596
8841
|
};
|
|
8597
8842
|
}
|
|
8598
8843
|
|
|
8844
|
+
// ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/skills.js
|
|
8845
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync5, statSync as statSync3 } from "node:fs";
|
|
8846
|
+
import { join as join13 } from "node:path";
|
|
8847
|
+
function configBase(env) {
|
|
8848
|
+
return env.xdgConfigHome && env.xdgConfigHome !== "" ? env.xdgConfigHome : join13(env.home, ".config");
|
|
8849
|
+
}
|
|
8850
|
+
function skillDirs(env) {
|
|
8851
|
+
const base = configBase(env);
|
|
8852
|
+
return [
|
|
8853
|
+
{ source: "project", path: join13(env.cwd, ".agents", "skills"), engineVisible: true },
|
|
8854
|
+
{
|
|
8855
|
+
source: "ai-ezio-global",
|
|
8856
|
+
path: join13(base, "ai-ezio", "skills"),
|
|
8857
|
+
// Engine-visible as of M4: ai-ezio sets HAX_EXTRA_SKILLS_DIR to this dir
|
|
8858
|
+
// on launch, so hax injects these skills into the model prompt.
|
|
8859
|
+
engineVisible: true
|
|
8860
|
+
},
|
|
8861
|
+
{ source: "hax-global", path: join13(base, "hax", "skills"), engineVisible: true }
|
|
8862
|
+
];
|
|
8863
|
+
}
|
|
8864
|
+
function parseSkillDescription(md) {
|
|
8865
|
+
const text = md.replace(/^/, "");
|
|
8866
|
+
if (!/^---\r?\n/.test(text))
|
|
8867
|
+
return null;
|
|
8868
|
+
const end = text.indexOf("\n---", 4);
|
|
8869
|
+
if (end === -1)
|
|
8870
|
+
return null;
|
|
8871
|
+
const front = text.slice(text.indexOf("\n") + 1, end);
|
|
8872
|
+
for (const raw of front.split(/\r?\n/)) {
|
|
8873
|
+
const m = raw.match(/^description:\s*(.*)$/);
|
|
8874
|
+
if (m) {
|
|
8875
|
+
let value = (m[1] ?? "").trim();
|
|
8876
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
8877
|
+
value = value.slice(1, -1);
|
|
8878
|
+
}
|
|
8879
|
+
return value.length > 0 ? value : null;
|
|
8880
|
+
}
|
|
8881
|
+
}
|
|
8882
|
+
return null;
|
|
8883
|
+
}
|
|
8884
|
+
function discoverSkills(env, fs2) {
|
|
8885
|
+
const byName = /* @__PURE__ */ new Map();
|
|
8886
|
+
for (const dir of skillDirs(env)) {
|
|
8887
|
+
if (!fs2.isDirectory(dir.path))
|
|
8888
|
+
continue;
|
|
8889
|
+
for (const name of fs2.listDirs(dir.path)) {
|
|
8890
|
+
if (byName.has(name))
|
|
8891
|
+
continue;
|
|
8892
|
+
const skillMdPath = join13(dir.path, name, "SKILL.md");
|
|
8893
|
+
const md = fs2.readFile(skillMdPath);
|
|
8894
|
+
if (md === null)
|
|
8895
|
+
continue;
|
|
8896
|
+
byName.set(name, {
|
|
8897
|
+
name,
|
|
8898
|
+
description: parseSkillDescription(md),
|
|
8899
|
+
skillMdPath,
|
|
8900
|
+
source: dir.source,
|
|
8901
|
+
engineVisible: dir.engineVisible
|
|
8902
|
+
});
|
|
8903
|
+
}
|
|
8904
|
+
}
|
|
8905
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
8906
|
+
}
|
|
8907
|
+
function nodeSkillFs() {
|
|
8908
|
+
return {
|
|
8909
|
+
isDirectory: (path) => {
|
|
8910
|
+
try {
|
|
8911
|
+
return statSync3(path).isDirectory();
|
|
8912
|
+
} catch {
|
|
8913
|
+
return false;
|
|
8914
|
+
}
|
|
8915
|
+
},
|
|
8916
|
+
listDirs: (path) => {
|
|
8917
|
+
try {
|
|
8918
|
+
return readdirSync2(path, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
8919
|
+
} catch {
|
|
8920
|
+
return [];
|
|
8921
|
+
}
|
|
8922
|
+
},
|
|
8923
|
+
readFile: (path) => {
|
|
8924
|
+
try {
|
|
8925
|
+
return readFileSync5(path, "utf8");
|
|
8926
|
+
} catch {
|
|
8927
|
+
return null;
|
|
8928
|
+
}
|
|
8929
|
+
}
|
|
8930
|
+
};
|
|
8931
|
+
}
|
|
8932
|
+
|
|
8933
|
+
// ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/clipboard.js
|
|
8934
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
8935
|
+
function tryCopy(argv, text, spawnFn) {
|
|
8936
|
+
return new Promise((resolve2, reject) => {
|
|
8937
|
+
const child = spawnFn(argv[0], argv.slice(1), { stdio: ["pipe", "ignore", "ignore"] });
|
|
8938
|
+
child.on("error", reject);
|
|
8939
|
+
child.on("close", (code) => code === 0 ? resolve2() : reject(new Error(`${argv[0]} exited ${code}`)));
|
|
8940
|
+
child.stdin?.end(text);
|
|
8941
|
+
});
|
|
8942
|
+
}
|
|
8943
|
+
function makeClipboard(platform, spawnFn = spawn6) {
|
|
8944
|
+
const candidates = platform === "darwin" ? [["pbcopy"]] : [["wl-copy"], ["xclip", "-selection", "clipboard"]];
|
|
8945
|
+
return async (text) => {
|
|
8946
|
+
let lastErr = new Error("no clipboard tool available");
|
|
8947
|
+
for (const argv of candidates) {
|
|
8948
|
+
try {
|
|
8949
|
+
await tryCopy(argv, text, spawnFn);
|
|
8950
|
+
return;
|
|
8951
|
+
} catch (e) {
|
|
8952
|
+
lastErr = e;
|
|
8953
|
+
}
|
|
8954
|
+
}
|
|
8955
|
+
throw lastErr;
|
|
8956
|
+
};
|
|
8957
|
+
}
|
|
8958
|
+
|
|
8959
|
+
// ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/transcript-view.js
|
|
8960
|
+
import { join as join14 } from "node:path";
|
|
8961
|
+
async function showTranscript(deps) {
|
|
8962
|
+
const text = deps.path ? deps.readText(deps.path) : void 0;
|
|
8963
|
+
if (text === void 0 || text === "") {
|
|
8964
|
+
deps.write("\x1B[2m\u2500 no transcript yet \u2500\x1B[0m\n");
|
|
8965
|
+
return;
|
|
8966
|
+
}
|
|
8967
|
+
if (!deps.interactive) {
|
|
8968
|
+
deps.write(text.endsWith("\n") ? text : `${text}
|
|
8969
|
+
`);
|
|
8970
|
+
return;
|
|
8971
|
+
}
|
|
8972
|
+
deps.suspendRaw();
|
|
8973
|
+
try {
|
|
8974
|
+
await deps.spawnPager(deps.path);
|
|
8975
|
+
} catch {
|
|
8976
|
+
deps.write(text.endsWith("\n") ? text : `${text}
|
|
8977
|
+
`);
|
|
8978
|
+
} finally {
|
|
8979
|
+
deps.restoreRaw();
|
|
8980
|
+
}
|
|
8981
|
+
}
|
|
8982
|
+
|
|
8983
|
+
// ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/resume-picker.js
|
|
8984
|
+
var PAGE_SIZE = 15;
|
|
8985
|
+
function parseSessions(json) {
|
|
8986
|
+
let raw;
|
|
8987
|
+
try {
|
|
8988
|
+
raw = JSON.parse(json);
|
|
8989
|
+
} catch {
|
|
8990
|
+
return [];
|
|
8991
|
+
}
|
|
8992
|
+
if (!Array.isArray(raw))
|
|
8993
|
+
return [];
|
|
8994
|
+
const rows = [];
|
|
8995
|
+
for (const e of raw) {
|
|
8996
|
+
if (!e || typeof e !== "object")
|
|
8997
|
+
continue;
|
|
8998
|
+
const o = e;
|
|
8999
|
+
if (typeof o.id !== "string" || o.id === "")
|
|
9000
|
+
continue;
|
|
9001
|
+
rows.push({
|
|
9002
|
+
id: o.id,
|
|
9003
|
+
mtime: typeof o.mtime === "number" ? o.mtime : 0,
|
|
9004
|
+
firstPrompt: typeof o.firstPrompt === "string" ? o.firstPrompt : null
|
|
9005
|
+
});
|
|
9006
|
+
}
|
|
9007
|
+
return rows;
|
|
9008
|
+
}
|
|
9009
|
+
function formatRelativeTime(mtimeSec, nowMs) {
|
|
9010
|
+
const diff = Math.max(0, Math.floor(nowMs / 1e3) - mtimeSec);
|
|
9011
|
+
if (diff < 60)
|
|
9012
|
+
return "just now";
|
|
9013
|
+
if (diff < 3600)
|
|
9014
|
+
return `${Math.floor(diff / 60)}m ago`;
|
|
9015
|
+
if (diff < 86400)
|
|
9016
|
+
return `${Math.floor(diff / 3600)}h ago`;
|
|
9017
|
+
return `${Math.floor(diff / 86400)}d ago`;
|
|
9018
|
+
}
|
|
9019
|
+
function formatRow(row, nowMs, title) {
|
|
9020
|
+
const age = formatRelativeTime(row.mtime, nowMs);
|
|
9021
|
+
const label = (title ?? row.firstPrompt ?? "(no prompt)").replace(/\s+/g, " ").trim();
|
|
9022
|
+
const clamped = label.length > 70 ? `${label.slice(0, 69)}\u2026` : label;
|
|
9023
|
+
return `${age} \xB7 ${clamped}`;
|
|
9024
|
+
}
|
|
9025
|
+
function decodeChunk(s) {
|
|
9026
|
+
if (s === "\x1B[A" || s === "\x1BOA" || s === "k")
|
|
9027
|
+
return "up";
|
|
9028
|
+
if (s === "\x1B[B" || s === "\x1BOB" || s === "j")
|
|
9029
|
+
return "down";
|
|
9030
|
+
if (s === "\r" || s === "\n")
|
|
9031
|
+
return "enter";
|
|
9032
|
+
if (s === "\x1B" || s === "" || s === "" || s === "q")
|
|
9033
|
+
return "cancel";
|
|
9034
|
+
if (s === "[")
|
|
9035
|
+
return "pageprev";
|
|
9036
|
+
if (s === "]")
|
|
9037
|
+
return "pagenext";
|
|
9038
|
+
if (s === "")
|
|
9039
|
+
return "toggleall";
|
|
9040
|
+
return "other";
|
|
9041
|
+
}
|
|
9042
|
+
function applyKey(state, token) {
|
|
9043
|
+
const { index, count, pageSize, showAll } = state;
|
|
9044
|
+
const pageStart = Math.floor(index / pageSize) * pageSize;
|
|
9045
|
+
const pageEnd = Math.min(count - 1, pageStart + pageSize - 1);
|
|
9046
|
+
const keep = { index, showAll };
|
|
9047
|
+
switch (token) {
|
|
9048
|
+
case "up":
|
|
9049
|
+
return { index: Math.max(showAll ? 0 : pageStart, index - 1), showAll };
|
|
9050
|
+
case "down":
|
|
9051
|
+
return { index: Math.min(showAll ? count - 1 : pageEnd, index + 1), showAll };
|
|
9052
|
+
case "pageprev":
|
|
9053
|
+
return !showAll && pageStart > 0 ? { index: pageStart - pageSize, showAll } : keep;
|
|
9054
|
+
case "pagenext":
|
|
9055
|
+
return !showAll && pageStart + pageSize < count ? { index: pageStart + pageSize, showAll } : keep;
|
|
9056
|
+
case "toggleall":
|
|
9057
|
+
return { index, showAll: !showAll };
|
|
9058
|
+
case "enter":
|
|
9059
|
+
return { index, showAll, done: "select" };
|
|
9060
|
+
case "cancel":
|
|
9061
|
+
return { index, showAll, done: "cancel" };
|
|
9062
|
+
default:
|
|
9063
|
+
return keep;
|
|
9064
|
+
}
|
|
9065
|
+
}
|
|
9066
|
+
var HINTS_PAGED = "\u2191/\u2193 move \xB7 [ ] page \xB7 Ctrl+A all \xB7 Enter select \xB7 Esc cancel";
|
|
9067
|
+
var HINTS_SINGLE = "\u2191/\u2193 move \xB7 Ctrl+A all \xB7 Enter select \xB7 Esc cancel";
|
|
9068
|
+
var HINTS_ALL = "\u2191/\u2193 move \xB7 Ctrl+A pages \xB7 Enter select \xB7 Esc cancel";
|
|
9069
|
+
function renderView(rows, state, nowMs, titles) {
|
|
9070
|
+
const { index, count, pageSize, showAll } = state;
|
|
9071
|
+
const pageCount = Math.max(1, Math.ceil(count / pageSize));
|
|
9072
|
+
const page = Math.floor(index / pageSize);
|
|
9073
|
+
const sliceStart = showAll ? 0 : page * pageSize;
|
|
9074
|
+
const sliceEnd = showAll ? count : Math.min(count, page * pageSize + pageSize);
|
|
9075
|
+
let header;
|
|
9076
|
+
let footer;
|
|
9077
|
+
if (showAll) {
|
|
9078
|
+
header = `Resume a session (showing all ${count} sessions)`;
|
|
9079
|
+
footer = HINTS_ALL;
|
|
9080
|
+
} else if (pageCount === 1) {
|
|
9081
|
+
header = `Resume a session (${count} session${count === 1 ? "" : "s"})`;
|
|
9082
|
+
footer = HINTS_SINGLE;
|
|
9083
|
+
} else {
|
|
9084
|
+
header = `Resume a session (page ${page + 1}/${pageCount} \xB7 ${count} sessions)`;
|
|
9085
|
+
footer = HINTS_PAGED;
|
|
9086
|
+
}
|
|
9087
|
+
const lines = [header];
|
|
9088
|
+
for (let i = sliceStart; i < sliceEnd; i++) {
|
|
9089
|
+
lines.push(oneLine(rows[i], i, index, nowMs, titles));
|
|
9090
|
+
}
|
|
9091
|
+
lines.push(footer);
|
|
9092
|
+
return `${lines.join("\n")}
|
|
9093
|
+
`;
|
|
9094
|
+
}
|
|
9095
|
+
function oneLine(row, i, selected, nowMs, titles) {
|
|
9096
|
+
const cursor = i === selected ? "\x1B[36m\u276F\x1B[0m" : " ";
|
|
9097
|
+
const n = `${i + 1}.`;
|
|
9098
|
+
const body = formatRow(row, nowMs, titles?.get(row.id));
|
|
9099
|
+
const text = i === selected ? `\x1B[1m${body}\x1B[0m` : `\x1B[2m${body}\x1B[0m`;
|
|
9100
|
+
return `${cursor} ${n} ${text}`;
|
|
9101
|
+
}
|
|
9102
|
+
async function runResumePicker(deps) {
|
|
9103
|
+
const rows = parseSessions(await deps.listSessions());
|
|
9104
|
+
if (rows.length === 0)
|
|
9105
|
+
return void 0;
|
|
9106
|
+
let state = { index: 0, count: rows.length, pageSize: PAGE_SIZE, showAll: false };
|
|
9107
|
+
let prevLines = 0;
|
|
9108
|
+
const draw = (first) => {
|
|
9109
|
+
const view = renderView(rows, state, deps.now(), deps.titles);
|
|
9110
|
+
const reset = first ? "" : `\x1B[${prevLines}A\r\x1B[0J`;
|
|
9111
|
+
deps.write(reset + view);
|
|
9112
|
+
prevLines = (view.match(/\n/g) ?? []).length;
|
|
9113
|
+
};
|
|
9114
|
+
deps.setRawMode?.(true);
|
|
9115
|
+
try {
|
|
9116
|
+
draw(true);
|
|
9117
|
+
for await (const chunk of deps.keys) {
|
|
9118
|
+
const token = decodeChunk(typeof chunk === "string" ? chunk : String(chunk));
|
|
9119
|
+
const r = applyKey(state, token);
|
|
9120
|
+
state = { ...state, index: r.index, showAll: r.showAll };
|
|
9121
|
+
if (r.done === "cancel")
|
|
9122
|
+
return void 0;
|
|
9123
|
+
if (r.done === "select")
|
|
9124
|
+
return rows[state.index]?.id;
|
|
9125
|
+
draw(false);
|
|
9126
|
+
}
|
|
9127
|
+
return void 0;
|
|
9128
|
+
} finally {
|
|
9129
|
+
deps.setRawMode?.(false);
|
|
9130
|
+
deps.write("\n");
|
|
9131
|
+
}
|
|
9132
|
+
}
|
|
9133
|
+
|
|
9134
|
+
// ../../node_modules/.pnpm/@ai-ezio+surface@file+..+ai-ezio+packages+surface/node_modules/@ai-ezio/surface/dist/slash.js
|
|
9135
|
+
var NAME_RE = /^[a-zA-Z][\w-]*$/;
|
|
9136
|
+
function classifyLine(line, known) {
|
|
9137
|
+
if (!line.startsWith("/"))
|
|
9138
|
+
return { kind: "submit" };
|
|
9139
|
+
if (line.includes("\n"))
|
|
9140
|
+
return { kind: "submit" };
|
|
9141
|
+
const body = line.slice(1);
|
|
9142
|
+
const ws = body.search(/\s/);
|
|
9143
|
+
const rawName = ws === -1 ? body : body.slice(0, ws);
|
|
9144
|
+
const args = ws === -1 ? "" : body.slice(ws).trim();
|
|
9145
|
+
if (rawName === "")
|
|
9146
|
+
return { kind: "submit" };
|
|
9147
|
+
if (!NAME_RE.test(rawName))
|
|
9148
|
+
return { kind: "submit" };
|
|
9149
|
+
const name = rawName.toLowerCase();
|
|
9150
|
+
return known.has(name) ? { kind: "command", name, args } : { kind: "unknown", name };
|
|
9151
|
+
}
|
|
9152
|
+
function renderHelp(ctx, cmds) {
|
|
9153
|
+
for (const c of cmds)
|
|
9154
|
+
ctx.write(` /${c.name} ${c.summary}
|
|
9155
|
+
`);
|
|
9156
|
+
ctx.write("\nshortcuts: Enter submit \xB7 Alt+Enter newline \xB7 paste multiline \xB7 Ctrl-C interrupt \xB7 Ctrl-D exit\n");
|
|
9157
|
+
}
|
|
9158
|
+
function formatUsage(u) {
|
|
9159
|
+
if (!u)
|
|
9160
|
+
return null;
|
|
9161
|
+
const parts = [];
|
|
9162
|
+
if (u.contextTokens !== void 0)
|
|
9163
|
+
parts.push(`context ${u.contextTokens}`);
|
|
9164
|
+
if (u.outputTokens !== void 0)
|
|
9165
|
+
parts.push(`output ${u.outputTokens}`);
|
|
9166
|
+
if (u.cachedTokens !== void 0)
|
|
9167
|
+
parts.push(`cached ${u.cachedTokens}`);
|
|
9168
|
+
if (u.contextLimit !== void 0)
|
|
9169
|
+
parts.push(`limit ${u.contextLimit}`);
|
|
9170
|
+
if (u.contextTokens !== void 0 && u.contextLimit !== void 0 && u.contextLimit > 0) {
|
|
9171
|
+
parts.push(`${Math.round(u.contextTokens / u.contextLimit * 100)}%`);
|
|
9172
|
+
}
|
|
9173
|
+
return parts.length ? parts.join(" \xB7 ") : null;
|
|
9174
|
+
}
|
|
9175
|
+
function builtinCommands(listCommands) {
|
|
9176
|
+
return [
|
|
9177
|
+
{
|
|
9178
|
+
name: "help",
|
|
9179
|
+
summary: "list commands and keyboard shortcuts",
|
|
9180
|
+
run: (ctx) => renderHelp(ctx, listCommands())
|
|
9181
|
+
},
|
|
9182
|
+
{
|
|
9183
|
+
name: "new",
|
|
9184
|
+
aliases: ["clear"],
|
|
9185
|
+
summary: "start a new conversation",
|
|
9186
|
+
run: async (ctx) => {
|
|
9187
|
+
ctx.recorder?.noteNewConversation();
|
|
9188
|
+
await ctx.session.newConversation();
|
|
9189
|
+
ctx.write("\u2014 new conversation \u2014\n");
|
|
9190
|
+
}
|
|
9191
|
+
},
|
|
9192
|
+
{
|
|
9193
|
+
name: "status",
|
|
9194
|
+
summary: "show provider, model, and effort",
|
|
9195
|
+
run: async (ctx) => {
|
|
9196
|
+
const s = await ctx.session.status();
|
|
9197
|
+
const effort = s.effort ? ` \xB7 ${s.effort}` : "";
|
|
9198
|
+
ctx.write(`${s.provider} \xB7 ${s.model}${effort}
|
|
9199
|
+
`);
|
|
9200
|
+
}
|
|
9201
|
+
},
|
|
9202
|
+
{
|
|
9203
|
+
name: "skills",
|
|
9204
|
+
summary: "list discovered skills",
|
|
9205
|
+
run: (ctx) => {
|
|
9206
|
+
const skills = ctx.skills();
|
|
9207
|
+
if (skills.length === 0) {
|
|
9208
|
+
ctx.write("(no skills found)\n");
|
|
9209
|
+
return;
|
|
9210
|
+
}
|
|
9211
|
+
for (const s of skills)
|
|
9212
|
+
ctx.write(` ${s.name} \xB7 ${s.source}
|
|
9213
|
+
`);
|
|
9214
|
+
}
|
|
9215
|
+
},
|
|
9216
|
+
{
|
|
9217
|
+
name: "copy",
|
|
9218
|
+
summary: "copy the last response to the clipboard",
|
|
9219
|
+
run: async (ctx) => {
|
|
9220
|
+
const text = ctx.lastContent();
|
|
9221
|
+
if (text === "") {
|
|
9222
|
+
ctx.write("no response to copy\n");
|
|
9223
|
+
return;
|
|
9224
|
+
}
|
|
9225
|
+
try {
|
|
9226
|
+
await ctx.clipboard(text);
|
|
9227
|
+
ctx.write(`copied ${Buffer.byteLength(text, "utf8")} bytes
|
|
9228
|
+
`);
|
|
9229
|
+
} catch (e) {
|
|
9230
|
+
ctx.write(`clipboard unavailable: ${e.message}
|
|
9231
|
+
`);
|
|
9232
|
+
}
|
|
9233
|
+
}
|
|
9234
|
+
},
|
|
9235
|
+
{
|
|
9236
|
+
name: "usage",
|
|
9237
|
+
summary: "show the last turn's token usage",
|
|
9238
|
+
run: (ctx) => {
|
|
9239
|
+
const formatted = formatUsage(ctx.lastUsage());
|
|
9240
|
+
ctx.write(formatted ? `${formatted}
|
|
9241
|
+
` : "no usage yet\n");
|
|
9242
|
+
}
|
|
9243
|
+
},
|
|
9244
|
+
{
|
|
9245
|
+
name: "transcript",
|
|
9246
|
+
summary: "view the model-perspective transcript (same as Ctrl+T)",
|
|
9247
|
+
run: async (ctx) => {
|
|
9248
|
+
if (!ctx.showTranscript) {
|
|
9249
|
+
ctx.write("transcript unavailable\n");
|
|
9250
|
+
return;
|
|
9251
|
+
}
|
|
9252
|
+
await ctx.showTranscript();
|
|
9253
|
+
}
|
|
9254
|
+
},
|
|
9255
|
+
{
|
|
9256
|
+
name: "compact",
|
|
9257
|
+
summary: "summarize old history and free context",
|
|
9258
|
+
run: async (ctx) => {
|
|
9259
|
+
if (!ctx.compactor) {
|
|
9260
|
+
ctx.write("compaction unavailable\n");
|
|
9261
|
+
return;
|
|
9262
|
+
}
|
|
9263
|
+
const out = await ctx.compactor.compactNow();
|
|
9264
|
+
if (out.kind === "skipped" && out.reason === "in-progress") {
|
|
9265
|
+
ctx.write("compaction already in progress\n");
|
|
9266
|
+
}
|
|
9267
|
+
}
|
|
9268
|
+
},
|
|
9269
|
+
{
|
|
9270
|
+
name: "rename",
|
|
9271
|
+
summary: "set a friendly title for this session (shown in /resume)",
|
|
9272
|
+
run: (ctx, args) => {
|
|
9273
|
+
if (!ctx.setSessionTitle) {
|
|
9274
|
+
ctx.write("rename unavailable\n");
|
|
9275
|
+
return;
|
|
9276
|
+
}
|
|
9277
|
+
const title = args.trim();
|
|
9278
|
+
if (title === "") {
|
|
9279
|
+
const current = ctx.getSessionTitle?.();
|
|
9280
|
+
ctx.write(current ? `${current}
|
|
9281
|
+
` : "no title set \xB7 usage: /rename <text>\n");
|
|
9282
|
+
return;
|
|
9283
|
+
}
|
|
9284
|
+
ctx.setSessionTitle(title);
|
|
9285
|
+
ctx.write(`\u2014 renamed to "${title}" \u2014
|
|
9286
|
+
`);
|
|
9287
|
+
}
|
|
9288
|
+
},
|
|
9289
|
+
{
|
|
9290
|
+
name: "resume",
|
|
9291
|
+
summary: "switch to a past session in this folder",
|
|
9292
|
+
run: async (ctx) => {
|
|
9293
|
+
if (!ctx.resume) {
|
|
9294
|
+
ctx.write("resume unavailable\n");
|
|
9295
|
+
return;
|
|
9296
|
+
}
|
|
9297
|
+
await ctx.resume();
|
|
9298
|
+
}
|
|
9299
|
+
},
|
|
9300
|
+
{
|
|
9301
|
+
name: "quit",
|
|
9302
|
+
aliases: ["exit"],
|
|
9303
|
+
summary: "exit ezio",
|
|
9304
|
+
run: () => {
|
|
9305
|
+
}
|
|
9306
|
+
// the controller maps /quit to the exit outcome before run()
|
|
9307
|
+
}
|
|
9308
|
+
];
|
|
9309
|
+
}
|
|
9310
|
+
var SlashController = class {
|
|
9311
|
+
ctx;
|
|
9312
|
+
/** name OR alias → command (so classifyLine's `known` set is keys()). */
|
|
9313
|
+
byKey = /* @__PURE__ */ new Map();
|
|
9314
|
+
constructor(ctx, opts) {
|
|
9315
|
+
this.ctx = ctx;
|
|
9316
|
+
const exclude = new Set(opts?.excludeCommands ?? []);
|
|
9317
|
+
for (const cmd of builtinCommands(() => this.summaries())) {
|
|
9318
|
+
if (exclude.has(cmd.name))
|
|
9319
|
+
continue;
|
|
9320
|
+
this.register(cmd);
|
|
9321
|
+
}
|
|
9322
|
+
}
|
|
9323
|
+
/** Register (or override) a command and its aliases. Last registration wins
|
|
9324
|
+
* per key: any command that already owns this command's NAME is fully evicted
|
|
9325
|
+
* (all of its keys removed) so an override replaces it cleanly; an alias key
|
|
9326
|
+
* collision is resolved key-by-key (the alias now points at the new command,
|
|
9327
|
+
* but the prior owner keeps its other keys). */
|
|
9328
|
+
register(cmd) {
|
|
9329
|
+
const displaced = this.byKey.get(cmd.name);
|
|
9330
|
+
if (displaced) {
|
|
9331
|
+
for (const [k, v] of this.byKey)
|
|
9332
|
+
if (v === displaced)
|
|
9333
|
+
this.byKey.delete(k);
|
|
9334
|
+
}
|
|
9335
|
+
this.byKey.set(cmd.name, cmd);
|
|
9336
|
+
for (const a of cmd.aliases ?? [])
|
|
9337
|
+
this.byKey.set(a, cmd);
|
|
9338
|
+
}
|
|
9339
|
+
/** Deduped canonical command list for /help. A command is listed only if it
|
|
9340
|
+
* still OWNS its own name key — this filters out any command reachable only
|
|
9341
|
+
* through a stolen alias key (e.g. another command claimed its name), so
|
|
9342
|
+
* /help never shows a stale entry. */
|
|
9343
|
+
summaries() {
|
|
9344
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9345
|
+
const out = [];
|
|
9346
|
+
for (const cmd of this.byKey.values()) {
|
|
9347
|
+
if (seen.has(cmd))
|
|
9348
|
+
continue;
|
|
9349
|
+
seen.add(cmd);
|
|
9350
|
+
if (this.byKey.get(cmd.name) !== cmd)
|
|
9351
|
+
continue;
|
|
9352
|
+
out.push({ name: cmd.name, summary: cmd.summary });
|
|
9353
|
+
}
|
|
9354
|
+
return out;
|
|
9355
|
+
}
|
|
9356
|
+
async handle(line) {
|
|
9357
|
+
const c = classifyLine(line, new Set(this.byKey.keys()));
|
|
9358
|
+
if (c.kind === "submit")
|
|
9359
|
+
return { action: "submit", text: line };
|
|
9360
|
+
if (c.kind === "unknown") {
|
|
9361
|
+
this.ctx.write(`unknown command: /${c.name}. type /help for the list.
|
|
9362
|
+
`);
|
|
9363
|
+
return { action: "handled" };
|
|
9364
|
+
}
|
|
9365
|
+
const cmd = this.byKey.get(c.name);
|
|
9366
|
+
if (!cmd)
|
|
9367
|
+
return { action: "handled" };
|
|
9368
|
+
if (cmd.name === "quit")
|
|
9369
|
+
return { action: "exit" };
|
|
9370
|
+
try {
|
|
9371
|
+
await cmd.run(this.ctx, c.args);
|
|
9372
|
+
} catch (e) {
|
|
9373
|
+
this.ctx.write(`/${c.name} failed: ${e.message}
|
|
9374
|
+
`);
|
|
9375
|
+
}
|
|
9376
|
+
return { action: "handled" };
|
|
9377
|
+
}
|
|
9378
|
+
};
|
|
9379
|
+
async function runResumeFlow(deps) {
|
|
9380
|
+
if (deps.isBusy()) {
|
|
9381
|
+
deps.write("finish or interrupt the current turn first\n");
|
|
9382
|
+
return;
|
|
9383
|
+
}
|
|
9384
|
+
const titles = deps.titles();
|
|
9385
|
+
const active = deps.currentSessionId();
|
|
9386
|
+
const rows = parseSessions(await deps.listSessions()).filter((r) => r.id !== active);
|
|
9387
|
+
if (rows.length === 0) {
|
|
9388
|
+
deps.write("no other sessions in this folder\n");
|
|
9389
|
+
return;
|
|
9390
|
+
}
|
|
9391
|
+
let chosen;
|
|
9392
|
+
await deps.runOverlay(async (io) => {
|
|
9393
|
+
chosen = await runResumePicker({
|
|
9394
|
+
listSessions: async () => JSON.stringify(rows),
|
|
9395
|
+
// already parsed+filtered; re-serialize for the picker
|
|
9396
|
+
keys: io.keys,
|
|
9397
|
+
write: io.write,
|
|
9398
|
+
now: deps.now,
|
|
9399
|
+
setRawMode: io.setRawMode,
|
|
9400
|
+
titles
|
|
9401
|
+
});
|
|
9402
|
+
});
|
|
9403
|
+
if (!chosen)
|
|
9404
|
+
return;
|
|
9405
|
+
try {
|
|
9406
|
+
await deps.resume(chosen);
|
|
9407
|
+
} catch (e) {
|
|
9408
|
+
if (e.name === "EngineBusyError") {
|
|
9409
|
+
deps.write("finish or interrupt the current turn first\n");
|
|
9410
|
+
return;
|
|
9411
|
+
}
|
|
9412
|
+
deps.write(`resume failed: ${e.message}
|
|
9413
|
+
`);
|
|
9414
|
+
deps.onFatal();
|
|
9415
|
+
}
|
|
9416
|
+
}
|
|
9417
|
+
|
|
8599
9418
|
// ../adapter-ai-ezio/dist/mid-composition-shape.js
|
|
8600
9419
|
var DRAFTING_PREFIXES = [
|
|
8601
9420
|
"let's draft",
|
|
@@ -8632,6 +9451,11 @@ function isMidCompositionShape(message) {
|
|
|
8632
9451
|
}
|
|
8633
9452
|
|
|
8634
9453
|
// ../adapter-ai-ezio/dist/create-ai-ezio-live-session.js
|
|
9454
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
9455
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
9456
|
+
import { readFileSync as readFileSync6 } from "node:fs";
|
|
9457
|
+
import { tmpdir } from "node:os";
|
|
9458
|
+
import { join as join15 } from "node:path";
|
|
8635
9459
|
var defaultBuildAutoCompact = ({ session, host, write }) => {
|
|
8636
9460
|
const { compaction } = loadConfig();
|
|
8637
9461
|
return createAutoCompactDriver({
|
|
@@ -8649,16 +9473,40 @@ function createAiEzioLiveSession(input) {
|
|
|
8649
9473
|
const create = input.createEngineSession ?? defaultCreateEngineSession;
|
|
8650
9474
|
const buildCompact = input.buildAutoCompact ?? defaultBuildAutoCompact;
|
|
8651
9475
|
const host = input.mcpHost ?? loadMcpHost({ mode: "mounted", cwd: process.cwd() });
|
|
9476
|
+
const titleStore = input.titleStore ?? createSessionTitleStore();
|
|
9477
|
+
const listSessions = input.listSessions ?? (() => new Promise((resolve2) => {
|
|
9478
|
+
let out = "";
|
|
9479
|
+
const child = spawn7(resolveHaxBinary(), ["--list-sessions"], {
|
|
9480
|
+
cwd: process.cwd(),
|
|
9481
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
9482
|
+
});
|
|
9483
|
+
child.stdout?.on("data", (d) => void (out += d.toString("utf8")));
|
|
9484
|
+
child.on("error", () => resolve2("[]"));
|
|
9485
|
+
child.on("exit", (code) => resolve2(code === 0 ? out : "[]"));
|
|
9486
|
+
}));
|
|
8652
9487
|
let session = null;
|
|
8653
9488
|
let driver = null;
|
|
8654
9489
|
let sawTurn = false;
|
|
8655
9490
|
let pendingContent = null;
|
|
9491
|
+
let lastContent = "";
|
|
9492
|
+
let lastUsage;
|
|
9493
|
+
let slash = null;
|
|
9494
|
+
let inTurn = false;
|
|
8656
9495
|
const outputHandlers = [];
|
|
8657
9496
|
const turnFinishedHandlers = [];
|
|
8658
9497
|
const fidelityDecisionHandlers = [];
|
|
8659
9498
|
const exitHandlers = [];
|
|
8660
9499
|
const renderer = createMountedRenderer({ stdout: input.stdout });
|
|
9500
|
+
const rename = createRenameController({
|
|
9501
|
+
store: titleStore,
|
|
9502
|
+
// Deferred for the same reason as standalone: noteEvent runs inside the
|
|
9503
|
+
// onEvent tee before the event reaches waiters; queueMicrotask runs status()
|
|
9504
|
+
// after the settling idle is routed to the in-flight turn.
|
|
9505
|
+
requestStatus: () => queueMicrotask(() => void session?.status().catch(() => {
|
|
9506
|
+
}))
|
|
9507
|
+
});
|
|
8661
9508
|
const onEvent = (event) => {
|
|
9509
|
+
rename.noteEvent(event);
|
|
8662
9510
|
const compacting = driver?.compacting() ?? false;
|
|
8663
9511
|
if (!compacting)
|
|
8664
9512
|
switch (event.type) {
|
|
@@ -8667,8 +9515,13 @@ function createAiEzioLiveSession(input) {
|
|
|
8667
9515
|
for (const h of outputHandlers)
|
|
8668
9516
|
h(event.text);
|
|
8669
9517
|
break;
|
|
9518
|
+
case "assistant_turn_started":
|
|
9519
|
+
inTurn = true;
|
|
9520
|
+
break;
|
|
8670
9521
|
case "assistant_turn_finished":
|
|
8671
9522
|
sawTurn = true;
|
|
9523
|
+
lastContent = event.content;
|
|
9524
|
+
lastUsage = event.usage;
|
|
8672
9525
|
if (pendingContent !== null) {
|
|
8673
9526
|
const superseded = pendingContent;
|
|
8674
9527
|
for (const h of fidelityDecisionHandlers)
|
|
@@ -8681,6 +9534,7 @@ function createAiEzioLiveSession(input) {
|
|
|
8681
9534
|
pendingContent = event.content;
|
|
8682
9535
|
break;
|
|
8683
9536
|
case "idle":
|
|
9537
|
+
inTurn = false;
|
|
8684
9538
|
if (sawTurn && pendingContent !== null) {
|
|
8685
9539
|
const candidate = pendingContent;
|
|
8686
9540
|
if (isMidCompositionShape(candidate)) {
|
|
@@ -8722,8 +9576,87 @@ function createAiEzioLiveSession(input) {
|
|
|
8722
9576
|
for (const h of exitHandlers)
|
|
8723
9577
|
h();
|
|
8724
9578
|
});
|
|
8725
|
-
|
|
9579
|
+
const transcriptPath = join15(tmpdir(), `ezio-mounted-${randomUUID3()}.txt`);
|
|
9580
|
+
await session.start({ transcriptPath });
|
|
8726
9581
|
await host.start(session);
|
|
9582
|
+
const skillEnv = {
|
|
9583
|
+
cwd: process.cwd(),
|
|
9584
|
+
home: process.env.HOME ?? "",
|
|
9585
|
+
xdgConfigHome: process.env.XDG_CONFIG_HOME
|
|
9586
|
+
};
|
|
9587
|
+
const skillFs = nodeSkillFs();
|
|
9588
|
+
const sessionFacet = session;
|
|
9589
|
+
const resumeThunk = () => runResumeFlow({
|
|
9590
|
+
write: (s) => input.stdout.write(s),
|
|
9591
|
+
isBusy: () => inTurn,
|
|
9592
|
+
listSessions,
|
|
9593
|
+
titles: () => titleStore.loadTitles(),
|
|
9594
|
+
currentSessionId: () => rename.currentSessionId(),
|
|
9595
|
+
runOverlay: async (run) => {
|
|
9596
|
+
if (!input.runInteractiveOverlay) {
|
|
9597
|
+
input.stdout.write("resume unavailable in this host\n");
|
|
9598
|
+
return;
|
|
9599
|
+
}
|
|
9600
|
+
await input.runInteractiveOverlay(run);
|
|
9601
|
+
},
|
|
9602
|
+
resume: async (id) => {
|
|
9603
|
+
if (sessionFacet.transcriptPath !== void 0) {
|
|
9604
|
+
await session.resume(id, { transcriptPath: sessionFacet.transcriptPath });
|
|
9605
|
+
} else {
|
|
9606
|
+
await session.resume(id);
|
|
9607
|
+
}
|
|
9608
|
+
await host.start(session);
|
|
9609
|
+
},
|
|
9610
|
+
// Spec §4: a failed respawn leaves the engine closed — report (in
|
|
9611
|
+
// runResumeFlow) and exit cleanly. Firing the adapter's exit handlers is
|
|
9612
|
+
// the "normal engine-exit path" for a mounted pane.
|
|
9613
|
+
onFatal: () => {
|
|
9614
|
+
for (const h of exitHandlers)
|
|
9615
|
+
h();
|
|
9616
|
+
},
|
|
9617
|
+
now: input.now ?? (() => Date.now())
|
|
9618
|
+
});
|
|
9619
|
+
const slashCtx = {
|
|
9620
|
+
write: (s) => input.stdout.write(s),
|
|
9621
|
+
session: {
|
|
9622
|
+
newConversation: async () => {
|
|
9623
|
+
await sessionFacet.newConversation();
|
|
9624
|
+
rename.noteNewConversation();
|
|
9625
|
+
},
|
|
9626
|
+
status: () => sessionFacet.status()
|
|
9627
|
+
},
|
|
9628
|
+
...driver ? { compactor: { compactNow: () => driver.compactNow() } } : {},
|
|
9629
|
+
lastContent: () => lastContent,
|
|
9630
|
+
lastUsage: () => lastUsage,
|
|
9631
|
+
skills: () => discoverSkills(skillEnv, skillFs).map((s) => ({
|
|
9632
|
+
name: s.name,
|
|
9633
|
+
source: s.source,
|
|
9634
|
+
description: s.description
|
|
9635
|
+
})),
|
|
9636
|
+
clipboard: input.clipboard ?? makeClipboard(process.platform, spawn7),
|
|
9637
|
+
showTranscript: () => showTranscript({
|
|
9638
|
+
path: sessionFacet.transcriptPath ?? "",
|
|
9639
|
+
readText: (p) => {
|
|
9640
|
+
try {
|
|
9641
|
+
return readFileSync6(p, "utf8");
|
|
9642
|
+
} catch {
|
|
9643
|
+
return void 0;
|
|
9644
|
+
}
|
|
9645
|
+
},
|
|
9646
|
+
interactive: false,
|
|
9647
|
+
spawnPager: () => Promise.resolve(),
|
|
9648
|
+
suspendRaw: () => {
|
|
9649
|
+
},
|
|
9650
|
+
restoreRaw: () => {
|
|
9651
|
+
},
|
|
9652
|
+
write: (s) => input.stdout.write(s)
|
|
9653
|
+
}),
|
|
9654
|
+
currentSessionId: () => rename.currentSessionId(),
|
|
9655
|
+
getSessionTitle: () => rename.getSessionTitle(),
|
|
9656
|
+
setSessionTitle: (t) => rename.setSessionTitle(t),
|
|
9657
|
+
resume: resumeThunk
|
|
9658
|
+
};
|
|
9659
|
+
slash = new SlashController(slashCtx, { excludeCommands: ["quit"] });
|
|
8727
9660
|
},
|
|
8728
9661
|
async stop() {
|
|
8729
9662
|
await host.stop();
|
|
@@ -8731,11 +9664,18 @@ function createAiEzioLiveSession(input) {
|
|
|
8731
9664
|
session = null;
|
|
8732
9665
|
},
|
|
8733
9666
|
writeUserInput(data) {
|
|
9667
|
+
inTurn = true;
|
|
8734
9668
|
session?.submit(data);
|
|
8735
9669
|
},
|
|
8736
9670
|
interrupt() {
|
|
8737
9671
|
session?.interrupt();
|
|
8738
9672
|
},
|
|
9673
|
+
async tryConsumeLocalCommand(line) {
|
|
9674
|
+
if (!slash)
|
|
9675
|
+
return false;
|
|
9676
|
+
const outcome = await slash.handle(line);
|
|
9677
|
+
return outcome.action !== "submit";
|
|
9678
|
+
},
|
|
8739
9679
|
echoUserInput(text, cols) {
|
|
8740
9680
|
renderer.echoUserInput(text, cols);
|
|
8741
9681
|
},
|
|
@@ -8802,7 +9742,10 @@ function createProviderForTarget(target) {
|
|
|
8802
9742
|
}
|
|
8803
9743
|
function createInteractiveSessionForTarget(input) {
|
|
8804
9744
|
if (input.target === "ezio") {
|
|
8805
|
-
return createAiEzioLiveSession({
|
|
9745
|
+
return createAiEzioLiveSession({
|
|
9746
|
+
stdout: input.stdout,
|
|
9747
|
+
...input.runInteractiveOverlay !== void 0 ? { runInteractiveOverlay: input.runInteractiveOverlay } : {}
|
|
9748
|
+
});
|
|
8806
9749
|
}
|
|
8807
9750
|
const baseExecArgs = getInteractiveSessionExecArgsForTarget(input.target, input.turnEvents);
|
|
8808
9751
|
const execArgs = [...baseExecArgs, ...input.passthroughArgs ?? []];
|