agent-sh 0.13.4 → 0.13.5
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/agent/agent-loop.d.ts +2 -0
- package/dist/agent/agent-loop.js +23 -11
- package/dist/agent/index.js +0 -10
- package/examples/extensions/ashi/package.json +1 -1
- package/examples/extensions/ashi/src/frontend.ts +95 -22
- package/examples/extensions/ashi/src/multi-session-store.ts +8 -0
- package/package.json +1 -1
|
@@ -42,6 +42,7 @@ export declare class AgentLoop implements AgentBackend {
|
|
|
42
42
|
private ctorListeners;
|
|
43
43
|
private ctorPipeListeners;
|
|
44
44
|
private lastProjectSkillNames;
|
|
45
|
+
private lastAgentInfo;
|
|
45
46
|
private sessionStartTime;
|
|
46
47
|
private toolCallCounts;
|
|
47
48
|
private totalToolCalls;
|
|
@@ -99,6 +100,7 @@ export declare class AgentLoop implements AgentBackend {
|
|
|
99
100
|
private cancel;
|
|
100
101
|
private reasoningParams;
|
|
101
102
|
private get currentMode();
|
|
103
|
+
private emitAgentInfoIfChanged;
|
|
102
104
|
private get currentModel();
|
|
103
105
|
/**
|
|
104
106
|
* Run compaction via the `conversation:compact` handler. After any
|
package/dist/agent/agent-loop.js
CHANGED
|
@@ -60,6 +60,7 @@ export class AgentLoop {
|
|
|
60
60
|
ctorListeners = [];
|
|
61
61
|
ctorPipeListeners = [];
|
|
62
62
|
lastProjectSkillNames = new Set();
|
|
63
|
+
lastAgentInfo = null;
|
|
63
64
|
// ── Session telemetry — behavioral self-awareness ──────────────
|
|
64
65
|
// Every ash deserves to know what it's been doing. This tracks the
|
|
65
66
|
// agent's own behavioral patterns across the session: which tools
|
|
@@ -181,16 +182,7 @@ export class AgentLoop {
|
|
|
181
182
|
message: `${prev.provider}:${prev.model} is not in the refreshed catalog — keeping it active until you /model to another.`,
|
|
182
183
|
});
|
|
183
184
|
}
|
|
184
|
-
|
|
185
|
-
if (active && active.contextWindow !== prev?.contextWindow) {
|
|
186
|
-
this.bus.emit("agent:info", {
|
|
187
|
-
name: "ash",
|
|
188
|
-
version: PACKAGE_VERSION,
|
|
189
|
-
model: active.model,
|
|
190
|
-
provider: active.provider,
|
|
191
|
-
contextWindow: active.contextWindow,
|
|
192
|
-
});
|
|
193
|
-
}
|
|
185
|
+
this.emitAgentInfoIfChanged();
|
|
194
186
|
this.bus.emit("config:changed", {});
|
|
195
187
|
});
|
|
196
188
|
// Fires before wire() too — agent-backend emits this from
|
|
@@ -208,6 +200,7 @@ export class AgentLoop {
|
|
|
208
200
|
else {
|
|
209
201
|
this.llmClient.model = m.model;
|
|
210
202
|
}
|
|
203
|
+
this.emitAgentInfoIfChanged();
|
|
211
204
|
this.bus.emit("config:changed", {});
|
|
212
205
|
});
|
|
213
206
|
const getToolsPipe = () => ({ tools: this.getTools() });
|
|
@@ -256,7 +249,7 @@ export class AgentLoop {
|
|
|
256
249
|
this.llmClient.model = m.model;
|
|
257
250
|
}
|
|
258
251
|
const label = m.provider ? `${m.provider}: ${m.model}` : m.model;
|
|
259
|
-
this.
|
|
252
|
+
this.emitAgentInfoIfChanged();
|
|
260
253
|
// Persist as the new default — selection survives restart.
|
|
261
254
|
// Safe even for dynamic providers: agent-backend defers mode
|
|
262
255
|
// resolution to `core:extensions-loaded`, so the extension gets
|
|
@@ -375,6 +368,8 @@ export class AgentLoop {
|
|
|
375
368
|
this.bus.emit("conversation:message-appended", { role: "system", content: note });
|
|
376
369
|
}
|
|
377
370
|
});
|
|
371
|
+
this.lastAgentInfo = null;
|
|
372
|
+
this.emitAgentInfoIfChanged();
|
|
378
373
|
}
|
|
379
374
|
/** Unsubscribe from bus events — deactivates this backend. */
|
|
380
375
|
unwire() {
|
|
@@ -518,6 +513,23 @@ export class AgentLoop {
|
|
|
518
513
|
get currentMode() {
|
|
519
514
|
return this.modes[this.currentModeIndex];
|
|
520
515
|
}
|
|
516
|
+
emitAgentInfoIfChanged() {
|
|
517
|
+
const m = this.modes[this.currentModeIndex];
|
|
518
|
+
if (!m)
|
|
519
|
+
return;
|
|
520
|
+
const prev = this.lastAgentInfo;
|
|
521
|
+
if (prev && prev.model === m.model && prev.provider === m.provider && prev.contextWindow === m.contextWindow) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
this.lastAgentInfo = { model: m.model, provider: m.provider, contextWindow: m.contextWindow };
|
|
525
|
+
this.bus.emit("agent:info", {
|
|
526
|
+
name: "ash",
|
|
527
|
+
version: PACKAGE_VERSION,
|
|
528
|
+
model: m.model,
|
|
529
|
+
provider: m.provider,
|
|
530
|
+
contextWindow: m.contextWindow,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
521
533
|
get currentModel() {
|
|
522
534
|
return this.modes[this.currentModeIndex].model;
|
|
523
535
|
}
|
package/dist/agent/index.js
CHANGED
|
@@ -2,7 +2,6 @@ import { AgentLoop } from "./agent-loop.js";
|
|
|
2
2
|
import { LlmClient } from "../utils/llm-client.js";
|
|
3
3
|
import { createLlmFacade } from "../utils/llm-facade.js";
|
|
4
4
|
import { resolveProvider, getProviderNames, getSettings } from "../core/settings.js";
|
|
5
|
-
import { PACKAGE_VERSION } from "../utils/package-version.js";
|
|
6
5
|
import { discoverSkills } from "./skills.js";
|
|
7
6
|
import { resolveApiKey } from "../cli/auth/keys.js";
|
|
8
7
|
import activateOpenrouter from "./providers/openrouter.js";
|
|
@@ -227,13 +226,6 @@ export default function agentBackend(ctx) {
|
|
|
227
226
|
});
|
|
228
227
|
},
|
|
229
228
|
});
|
|
230
|
-
bus.emit("agent:info", {
|
|
231
|
-
name: "ash",
|
|
232
|
-
version: PACKAGE_VERSION,
|
|
233
|
-
model: llmClient.model,
|
|
234
|
-
provider: modes[initialModeIndex]?.provider,
|
|
235
|
-
contextWindow: modes[initialModeIndex]?.contextWindow,
|
|
236
|
-
});
|
|
237
229
|
},
|
|
238
230
|
});
|
|
239
231
|
});
|
|
@@ -330,9 +322,7 @@ export default function agentBackend(ctx) {
|
|
|
330
322
|
};
|
|
331
323
|
});
|
|
332
324
|
bus.emit("config:set-modes", { modes: newModes });
|
|
333
|
-
bus.emit("agent:info", { name: "ash", version: PACKAGE_VERSION, model: switchModel, provider: name, contextWindow: p.contextWindow });
|
|
334
325
|
bus.emit("ui:info", { message: `Switched to ${name} (${switchModel})` });
|
|
335
|
-
bus.emit("config:changed", {});
|
|
336
326
|
});
|
|
337
327
|
bus.onPipe("banner:collect", (e) => {
|
|
338
328
|
if (e.activeBackend && e.activeBackend !== "ash")
|
|
@@ -175,6 +175,7 @@ export function mountAshi(
|
|
|
175
175
|
|
|
176
176
|
const chat = new Container();
|
|
177
177
|
const footerSlot = new Container();
|
|
178
|
+
const queueSlot = new Container();
|
|
178
179
|
const editor = new Editor(tui, editorTheme(), { paddingX: 1 });
|
|
179
180
|
editor.setAutocompleteProvider(new BusAutocompleteProvider(bus));
|
|
180
181
|
editor.onSubmit = (text) => {
|
|
@@ -188,6 +189,12 @@ export function mountAshi(
|
|
|
188
189
|
bus.emit("command:execute", { name, args });
|
|
189
190
|
return;
|
|
190
191
|
}
|
|
192
|
+
if (processing) {
|
|
193
|
+
queuedQueries.push(query);
|
|
194
|
+
renderQueueSlot();
|
|
195
|
+
tui.requestRender();
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
191
198
|
bus.emit("agent:submit", { query });
|
|
192
199
|
};
|
|
193
200
|
|
|
@@ -211,6 +218,7 @@ export function mountAshi(
|
|
|
211
218
|
|
|
212
219
|
tui.addChild(chat);
|
|
213
220
|
tui.addChild(footerSlot);
|
|
221
|
+
tui.addChild(queueSlot);
|
|
214
222
|
tui.addChild(editor);
|
|
215
223
|
tui.addChild(statusFooter);
|
|
216
224
|
tui.setFocus(editor);
|
|
@@ -227,6 +235,16 @@ export function mountAshi(
|
|
|
227
235
|
let lastToolResult: ToolResultView | null = null;
|
|
228
236
|
let loader: Loader | null = null;
|
|
229
237
|
let processing = false;
|
|
238
|
+
const queuedQueries: string[] = [];
|
|
239
|
+
|
|
240
|
+
const renderQueueSlot = (): void => {
|
|
241
|
+
queueSlot.clear();
|
|
242
|
+
for (const q of queuedQueries) {
|
|
243
|
+
const oneLine = q.replace(/\s+/g, " ");
|
|
244
|
+
const preview = oneLine.length > 80 ? oneLine.slice(0, 77) + "…" : oneLine;
|
|
245
|
+
queueSlot.addChild(new InfoLine(`↳ queued: ${preview}`));
|
|
246
|
+
}
|
|
247
|
+
};
|
|
230
248
|
let hideThinking = true;
|
|
231
249
|
|
|
232
250
|
const renderState = (): { state: Record<string, unknown>; invalidate: () => void } => ({
|
|
@@ -552,6 +570,11 @@ export function mountAshi(
|
|
|
552
570
|
chat.addChild(new Spacer(1));
|
|
553
571
|
refreshFooterStats();
|
|
554
572
|
refreshBranch();
|
|
573
|
+
const next = queuedQueries.shift();
|
|
574
|
+
if (next !== undefined) {
|
|
575
|
+
renderQueueSlot();
|
|
576
|
+
bus.emit("agent:submit", { query: next });
|
|
577
|
+
}
|
|
555
578
|
tui.requestRender();
|
|
556
579
|
});
|
|
557
580
|
|
|
@@ -612,6 +635,9 @@ export function mountAshi(
|
|
|
612
635
|
|
|
613
636
|
// ── Pickers ────────────────────────────────────────────────────
|
|
614
637
|
let pickerOpen = false;
|
|
638
|
+
let activeSessionPicker: SelectList | null = null;
|
|
639
|
+
let activeSessionRepopulate: ((keepIndex?: number) => boolean) | null = null;
|
|
640
|
+
let activeSessionClose: (() => void) | null = null;
|
|
615
641
|
|
|
616
642
|
const openTreePicker = async (): Promise<void> => {
|
|
617
643
|
if (pickerOpen) return;
|
|
@@ -657,38 +683,60 @@ export function mountAshi(
|
|
|
657
683
|
|
|
658
684
|
const openSessionPicker = async (): Promise<void> => {
|
|
659
685
|
if (pickerOpen) return;
|
|
660
|
-
|
|
661
|
-
const
|
|
662
|
-
if (list.length === 0) {
|
|
663
|
-
bus.emit("ui:info", { message: "no past sessions in this cwd" });
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
const items: SelectItem[] = list.map((s) => ({
|
|
667
|
-
value: s.id,
|
|
668
|
-
label: formatSessionRow(s, false),
|
|
669
|
-
}));
|
|
670
|
-
const picker = new SelectList(items, 15, selectListTheme());
|
|
686
|
+
|
|
687
|
+
const hint = new InfoLine("↑↓ move · enter: resume · d: delete · esc: cancel");
|
|
671
688
|
|
|
672
689
|
const close = (): void => {
|
|
690
|
+
if (activeSessionPicker) footerSlot.removeChild(activeSessionPicker);
|
|
691
|
+
footerSlot.removeChild(hint);
|
|
692
|
+
activeSessionPicker = null;
|
|
693
|
+
activeSessionRepopulate = null;
|
|
694
|
+
activeSessionClose = null;
|
|
673
695
|
pickerOpen = false;
|
|
674
|
-
footerSlot.removeChild(picker);
|
|
675
696
|
tui.setFocus(editor);
|
|
676
697
|
tui.requestRender();
|
|
677
698
|
};
|
|
678
699
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
700
|
+
const populate = (keepIndex?: number): boolean => {
|
|
701
|
+
if (activeSessionPicker) footerSlot.removeChild(activeSessionPicker);
|
|
702
|
+
const currentId = getStore().current().id;
|
|
703
|
+
const list = getStore().listSessions().filter((s) => s.id !== currentId);
|
|
704
|
+
if (list.length === 0) {
|
|
705
|
+
activeSessionPicker = null;
|
|
706
|
+
return false;
|
|
707
|
+
}
|
|
708
|
+
const items: SelectItem[] = list.map((s) => ({
|
|
709
|
+
value: s.id,
|
|
710
|
+
label: formatSessionRow(s, false),
|
|
711
|
+
}));
|
|
712
|
+
const picker = new SelectList(items, 15, selectListTheme());
|
|
713
|
+
if (keepIndex !== undefined) {
|
|
714
|
+
picker.setSelectedIndex(Math.min(keepIndex, items.length - 1));
|
|
715
|
+
}
|
|
716
|
+
picker.onSelect = async (item) => {
|
|
717
|
+
const id = item.value;
|
|
718
|
+
close();
|
|
719
|
+
resumeSession(ctx, getStore, capture, id);
|
|
720
|
+
bus.emit("ui:info", { message: `resumed session ${id}` });
|
|
721
|
+
await rebuildChat();
|
|
722
|
+
refreshFooterStats();
|
|
723
|
+
};
|
|
724
|
+
picker.onCancel = close;
|
|
725
|
+
activeSessionPicker = picker;
|
|
726
|
+
footerSlot.addChild(picker);
|
|
727
|
+
tui.setFocus(picker);
|
|
728
|
+
return true;
|
|
686
729
|
};
|
|
687
|
-
picker.onCancel = close;
|
|
688
730
|
|
|
731
|
+
footerSlot.addChild(hint);
|
|
732
|
+
if (!populate()) {
|
|
733
|
+
footerSlot.removeChild(hint);
|
|
734
|
+
bus.emit("ui:info", { message: "no past sessions in this cwd" });
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
689
737
|
pickerOpen = true;
|
|
690
|
-
|
|
691
|
-
|
|
738
|
+
activeSessionRepopulate = populate;
|
|
739
|
+
activeSessionClose = close;
|
|
692
740
|
tui.requestRender();
|
|
693
741
|
};
|
|
694
742
|
|
|
@@ -711,6 +759,31 @@ export function mountAshi(
|
|
|
711
759
|
bus.emit("agent:cancel-request", {});
|
|
712
760
|
return { consume: true };
|
|
713
761
|
}
|
|
762
|
+
if (activeSessionPicker && matchesKey(data, "d")) {
|
|
763
|
+
const selected = activeSessionPicker.getSelectedItem();
|
|
764
|
+
if (selected) {
|
|
765
|
+
const currentId = getStore().current().id;
|
|
766
|
+
const idx = getStore().listSessions()
|
|
767
|
+
.filter((s) => s.id !== currentId)
|
|
768
|
+
.findIndex((s) => s.id === selected.value);
|
|
769
|
+
try {
|
|
770
|
+
getStore().deleteSession(selected.value);
|
|
771
|
+
} catch (e) {
|
|
772
|
+
bus.emit("ui:error", { message: `delete failed: ${(e as Error).message}` });
|
|
773
|
+
return { consume: true };
|
|
774
|
+
}
|
|
775
|
+
if (!activeSessionRepopulate?.(idx)) activeSessionClose?.();
|
|
776
|
+
tui.requestRender();
|
|
777
|
+
}
|
|
778
|
+
return { consume: true };
|
|
779
|
+
}
|
|
780
|
+
if (matchesKey(data, "up") && queuedQueries.length > 0 && editor.getText().length === 0) {
|
|
781
|
+
const last = queuedQueries.pop()!;
|
|
782
|
+
renderQueueSlot();
|
|
783
|
+
editor.setText(last);
|
|
784
|
+
tui.requestRender();
|
|
785
|
+
return { consume: true };
|
|
786
|
+
}
|
|
714
787
|
if (matchesKey(data, "ctrl+c")) {
|
|
715
788
|
editor.setText("");
|
|
716
789
|
return { consume: true };
|
|
@@ -81,6 +81,14 @@ export class MultiSessionStore {
|
|
|
81
81
|
return this.currentStore;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
deleteSession(id: string): void {
|
|
85
|
+
if (id === this.currentStore.id) throw new Error("cannot delete the active session");
|
|
86
|
+
const filePath = this.sessionFile(id);
|
|
87
|
+
for (const p of [filePath, filePath + ".leaf", filePath + ".meta"]) {
|
|
88
|
+
try { fs.unlinkSync(p); } catch { /* missing siblings are fine */ }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
84
92
|
listSessions(): SessionInfo[] {
|
|
85
93
|
let names: string[];
|
|
86
94
|
try { names = fs.readdirSync(this.dir); } catch { return []; }
|