fix: smooth TUI waiting shimmer (#1196) (thanks @vignesh07)
This commit is contained in:
@@ -7,6 +7,7 @@ Docs: https://docs.clawd.bot
|
|||||||
### Changes
|
### Changes
|
||||||
- Dependencies: update core + plugin deps (grammy, vitest, openai, Microsoft agents hosting, etc.).
|
- Dependencies: update core + plugin deps (grammy, vitest, openai, Microsoft agents hosting, etc.).
|
||||||
- Agents: make inbound message envelopes configurable (timezone/timestamp/elapsed) and surface elapsed gaps; time design is actively being explored. See https://docs.clawd.bot/date-time. (#1150) — thanks @shiv19.
|
- Agents: make inbound message envelopes configurable (timezone/timestamp/elapsed) and surface elapsed gaps; time design is actively being explored. See https://docs.clawd.bot/date-time. (#1150) — thanks @shiv19.
|
||||||
|
- TUI: add animated waiting shimmer status in the terminal UI. (#1196) — thanks @vignesh07.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Configure: hide OpenRouter auto routing model from the model picker. (#1182) — thanks @zerone0x.
|
- Configure: hide OpenRouter auto routing model from the model picker. (#1182) — thanks @zerone0x.
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ describe("buildAgentSystemPrompt", () => {
|
|||||||
expect(prompt).toContain("- Read: Read file contents");
|
expect(prompt).toContain("- Read: Read file contents");
|
||||||
expect(prompt).toContain("- Exec: Run shell commands");
|
expect(prompt).toContain("- Exec: Run shell commands");
|
||||||
expect(prompt).toContain(
|
expect(prompt).toContain(
|
||||||
"Use `Read` to load the SKILL.md at the location listed for that skill.",
|
"read its SKILL.md at <location> with `Read`",
|
||||||
);
|
);
|
||||||
expect(prompt).toContain("Clawdbot docs: /tmp/clawd/docs");
|
expect(prompt).toContain("Clawdbot docs: /tmp/clawd/docs");
|
||||||
expect(prompt).toContain(
|
expect(prompt).toContain(
|
||||||
@@ -188,7 +188,7 @@ describe("buildAgentSystemPrompt", () => {
|
|||||||
|
|
||||||
expect(prompt).toContain("## Skills");
|
expect(prompt).toContain("## Skills");
|
||||||
expect(prompt).toContain(
|
expect(prompt).toContain(
|
||||||
"Use `read` to load the SKILL.md at the location listed for that skill.",
|
"read its SKILL.md at <location> with `read`",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import type { SessionEntry } from "../../config/sessions.js";
|
import type { SessionEntry } from "../../config/sessions.js";
|
||||||
import * as sessions from "../../config/sessions.js";
|
import * as sessions from "../../config/sessions.js";
|
||||||
import type { TypingMode } from "../../config/types.js";
|
import type { TypingMode } from "../../config/types.js";
|
||||||
@@ -44,6 +44,10 @@ vi.mock("./queue.js", async () => {
|
|||||||
|
|
||||||
import { runReplyAgent } from "./agent-runner.js";
|
import { runReplyAgent } from "./agent-runner.js";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
runEmbeddedPiAgentMock.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
function createMinimalRun(params?: {
|
function createMinimalRun(params?: {
|
||||||
opts?: GetReplyOptions;
|
opts?: GetReplyOptions;
|
||||||
resolvedVerboseLevel?: "off" | "on";
|
resolvedVerboseLevel?: "off" | "on";
|
||||||
@@ -137,18 +141,12 @@ describe("runReplyAgent typing (heartbeat)", () => {
|
|||||||
await fs.mkdir(path.dirname(transcriptPath), { recursive: true });
|
await fs.mkdir(path.dirname(transcriptPath), { recursive: true });
|
||||||
await fs.writeFile(transcriptPath, "ok", "utf-8");
|
await fs.writeFile(transcriptPath, "ok", "utf-8");
|
||||||
|
|
||||||
runEmbeddedPiAgentMock
|
runEmbeddedPiAgentMock.mockRejectedValueOnce(
|
||||||
.mockImplementationOnce(async () => {
|
new Error(
|
||||||
throw new Error(
|
|
||||||
'Context overflow: Summarization failed: 400 {"message":"prompt is too long"}',
|
'Context overflow: Summarization failed: 400 {"message":"prompt is too long"}',
|
||||||
|
),
|
||||||
);
|
);
|
||||||
})
|
|
||||||
.mockImplementationOnce(async () => ({
|
|
||||||
payloads: [{ text: "ok" }],
|
|
||||||
meta: {},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const callsBefore = runEmbeddedPiAgentMock.mock.calls.length;
|
|
||||||
const { run } = createMinimalRun({
|
const { run } = createMinimalRun({
|
||||||
sessionEntry,
|
sessionEntry,
|
||||||
sessionStore,
|
sessionStore,
|
||||||
@@ -157,9 +155,11 @@ describe("runReplyAgent typing (heartbeat)", () => {
|
|||||||
});
|
});
|
||||||
const res = await run();
|
const res = await run();
|
||||||
|
|
||||||
expect(runEmbeddedPiAgentMock.mock.calls.length - callsBefore).toBe(2);
|
expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1);
|
||||||
const payload = Array.isArray(res) ? res[0] : res;
|
const payload = Array.isArray(res) ? res[0] : res;
|
||||||
expect(payload).toMatchObject({ text: "ok" });
|
expect(payload).toMatchObject({
|
||||||
|
text: expect.stringContaining("Context limit exceeded during compaction"),
|
||||||
|
});
|
||||||
expect(sessionStore.main.sessionId).not.toBe(sessionId);
|
expect(sessionStore.main.sessionId).not.toBe(sessionId);
|
||||||
|
|
||||||
const persisted = JSON.parse(await fs.readFile(storePath, "utf-8"));
|
const persisted = JSON.parse(await fs.readFile(storePath, "utf-8"));
|
||||||
@@ -188,8 +188,7 @@ describe("runReplyAgent typing (heartbeat)", () => {
|
|||||||
await fs.mkdir(path.dirname(transcriptPath), { recursive: true });
|
await fs.mkdir(path.dirname(transcriptPath), { recursive: true });
|
||||||
await fs.writeFile(transcriptPath, "ok", "utf-8");
|
await fs.writeFile(transcriptPath, "ok", "utf-8");
|
||||||
|
|
||||||
runEmbeddedPiAgentMock
|
runEmbeddedPiAgentMock.mockResolvedValueOnce({
|
||||||
.mockImplementationOnce(async () => ({
|
|
||||||
payloads: [{ text: "Context overflow: prompt too large", isError: true }],
|
payloads: [{ text: "Context overflow: prompt too large", isError: true }],
|
||||||
meta: {
|
meta: {
|
||||||
durationMs: 1,
|
durationMs: 1,
|
||||||
@@ -199,13 +198,8 @@ describe("runReplyAgent typing (heartbeat)", () => {
|
|||||||
'Context overflow: Summarization failed: 400 {"message":"prompt is too long"}',
|
'Context overflow: Summarization failed: 400 {"message":"prompt is too long"}',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}))
|
});
|
||||||
.mockImplementationOnce(async () => ({
|
|
||||||
payloads: [{ text: "ok" }],
|
|
||||||
meta: { durationMs: 1 },
|
|
||||||
}));
|
|
||||||
|
|
||||||
const callsBefore = runEmbeddedPiAgentMock.mock.calls.length;
|
|
||||||
const { run } = createMinimalRun({
|
const { run } = createMinimalRun({
|
||||||
sessionEntry,
|
sessionEntry,
|
||||||
sessionStore,
|
sessionStore,
|
||||||
@@ -214,9 +208,11 @@ describe("runReplyAgent typing (heartbeat)", () => {
|
|||||||
});
|
});
|
||||||
const res = await run();
|
const res = await run();
|
||||||
|
|
||||||
expect(runEmbeddedPiAgentMock.mock.calls.length - callsBefore).toBe(2);
|
expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1);
|
||||||
const payload = Array.isArray(res) ? res[0] : res;
|
const payload = Array.isArray(res) ? res[0] : res;
|
||||||
expect(payload).toMatchObject({ text: "ok" });
|
expect(payload).toMatchObject({
|
||||||
|
text: expect.stringContaining("Context limit exceeded."),
|
||||||
|
});
|
||||||
expect(sessionStore.main.sessionId).not.toBe(sessionId);
|
expect(sessionStore.main.sessionId).not.toBe(sessionId);
|
||||||
|
|
||||||
const persisted = JSON.parse(await fs.readFile(storePath, "utf-8"));
|
const persisted = JSON.parse(await fs.readFile(storePath, "utf-8"));
|
||||||
|
|||||||
@@ -361,10 +361,14 @@ export async function runTui(opts: TuiOptions) {
|
|||||||
statusStartedAt = Date.now();
|
statusStartedAt = Date.now();
|
||||||
}
|
}
|
||||||
ensureStatusLoader();
|
ensureStatusLoader();
|
||||||
updateBusyStatusMessage();
|
if (activityStatus === "waiting") {
|
||||||
|
stopStatusTimer();
|
||||||
|
startWaitingTimer();
|
||||||
|
} else {
|
||||||
|
stopWaitingTimer();
|
||||||
startStatusTimer();
|
startStatusTimer();
|
||||||
if (activityStatus === "waiting") startWaitingTimer();
|
}
|
||||||
else stopWaitingTimer();
|
updateBusyStatusMessage();
|
||||||
} else {
|
} else {
|
||||||
statusStartedAt = null;
|
statusStartedAt = null;
|
||||||
stopStatusTimer();
|
stopStatusTimer();
|
||||||
|
|||||||
Reference in New Issue
Block a user