fix: honor audio_as_voice streaming + parse tests (#490) (thanks @jarvis-medmatic)
This commit is contained in:
@@ -42,6 +42,7 @@
|
|||||||
- Commands: harden slash command registry and list text-only commands in `/commands`.
|
- Commands: harden slash command registry and list text-only commands in `/commands`.
|
||||||
- Models/Auth: show per-agent auth candidates in `/model status`, and add `clawdbot models auth order {get,set,clear}` (per-agent auth rotation overrides). — thanks @steipete
|
- Models/Auth: show per-agent auth candidates in `/model status`, and add `clawdbot models auth order {get,set,clear}` (per-agent auth rotation overrides). — thanks @steipete
|
||||||
- Telegram: keep streamMode draft-only; avoid forcing block streaming. (#619) — thanks @rubyrunsstuff
|
- Telegram: keep streamMode draft-only; avoid forcing block streaming. (#619) — thanks @rubyrunsstuff
|
||||||
|
- Telegram: add `[[audio_as_voice]]` tag support for voice notes with streaming-safe delivery. (#490) — thanks @jarvis-medmatic
|
||||||
- Debugging: add raw model stream logging flags and document gateway watch mode.
|
- Debugging: add raw model stream logging flags and document gateway watch mode.
|
||||||
- Gateway: decode dns-sd escaped UTF-8 in discovery output and show scan progress immediately. — thanks @steipete
|
- Gateway: decode dns-sd escaped UTF-8 in discovery output and show scan progress immediately. — thanks @steipete
|
||||||
- Agent: add claude-cli/opus-4.5 runner via Claude CLI with resume support (tools disabled).
|
- Agent: add claude-cli/opus-4.5 runner via Claude CLI with resume support (tools disabled).
|
||||||
|
|||||||
@@ -776,6 +776,7 @@ export async function compactEmbeddedPiSession(params: {
|
|||||||
const enqueueGlobal =
|
const enqueueGlobal =
|
||||||
params.enqueue ??
|
params.enqueue ??
|
||||||
((task, opts) => enqueueCommandInLane(globalLane, task, opts));
|
((task, opts) => enqueueCommandInLane(globalLane, task, opts));
|
||||||
|
const runAbortController = new AbortController();
|
||||||
return enqueueCommandInLane(sessionLane, () =>
|
return enqueueCommandInLane(sessionLane, () =>
|
||||||
enqueueGlobal(async () => {
|
enqueueGlobal(async () => {
|
||||||
const resolvedWorkspace = resolveUserPath(params.workspaceDir);
|
const resolvedWorkspace = resolveUserPath(params.workspaceDir);
|
||||||
|
|||||||
@@ -651,7 +651,9 @@ export function createClawdbotCodingTools(options?: {
|
|||||||
// Without this, some providers (notably OpenAI) will reject root-level union schemas.
|
// Without this, some providers (notably OpenAI) will reject root-level union schemas.
|
||||||
const normalized = subagentFiltered.map(normalizeToolParameters);
|
const normalized = subagentFiltered.map(normalizeToolParameters);
|
||||||
const withAbort = options?.abortSignal
|
const withAbort = options?.abortSignal
|
||||||
? normalized.map((tool) => wrapToolWithAbortSignal(tool, options.abortSignal))
|
? normalized.map((tool) =>
|
||||||
|
wrapToolWithAbortSignal(tool, options.abortSignal),
|
||||||
|
)
|
||||||
: normalized;
|
: normalized;
|
||||||
|
|
||||||
// Anthropic blocks specific lowercase tool names (bash, read, write, edit) with OAuth tokens.
|
// Anthropic blocks specific lowercase tool names (bash, read, write, edit) with OAuth tokens.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { ClawdbotConfig } from "../../config/config.js";
|
|||||||
import {
|
import {
|
||||||
loadSessionStore,
|
loadSessionStore,
|
||||||
resolveStorePath,
|
resolveStorePath,
|
||||||
|
type SessionEntry,
|
||||||
saveSessionStore,
|
saveSessionStore,
|
||||||
} from "../../config/sessions.js";
|
} from "../../config/sessions.js";
|
||||||
import {
|
import {
|
||||||
@@ -35,7 +36,7 @@ export function setAbortMemory(key: string, value: boolean): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveSessionEntryForKey(
|
function resolveSessionEntryForKey(
|
||||||
store: Record<string, { sessionId: string; updatedAt: number }> | undefined,
|
store: Record<string, SessionEntry> | undefined,
|
||||||
sessionKey: string | undefined,
|
sessionKey: string | undefined,
|
||||||
) {
|
) {
|
||||||
if (!store || !sessionKey) return {};
|
if (!store || !sessionKey) return {};
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import type { ReplyDispatcher } from "./reply-dispatcher.js";
|
|||||||
|
|
||||||
const mocks = vi.hoisted(() => ({
|
const mocks = vi.hoisted(() => ({
|
||||||
routeReply: vi.fn(async () => ({ ok: true, messageId: "mock" })),
|
routeReply: vi.fn(async () => ({ ok: true, messageId: "mock" })),
|
||||||
tryFastAbortFromMessage: vi.fn(async () => ({ handled: false, aborted: false })),
|
tryFastAbortFromMessage: vi.fn(async () => ({
|
||||||
|
handled: false,
|
||||||
|
aborted: false,
|
||||||
|
})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("./route-reply.js", () => ({
|
vi.mock("./route-reply.js", () => ({
|
||||||
|
|||||||
19
src/media/parse.test.ts
Normal file
19
src/media/parse.test.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { splitMediaFromOutput } from "./parse.js";
|
||||||
|
|
||||||
|
describe("splitMediaFromOutput", () => {
|
||||||
|
it("detects audio_as_voice tag and strips it", () => {
|
||||||
|
const result = splitMediaFromOutput("Hello [[audio_as_voice]] world");
|
||||||
|
expect(result.audioAsVoice).toBe(true);
|
||||||
|
expect(result.text).toBe("Hello world");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps audio_as_voice detection stable across calls", () => {
|
||||||
|
const input = "Hello [[audio_as_voice]]";
|
||||||
|
const first = splitMediaFromOutput(input);
|
||||||
|
const second = splitMediaFromOutput(input);
|
||||||
|
expect(first.audioAsVoice).toBe(true);
|
||||||
|
expect(second.audioAsVoice).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -34,6 +34,7 @@ function isInsideFence(
|
|||||||
|
|
||||||
// Regex to detect [[audio_as_voice]] tag
|
// Regex to detect [[audio_as_voice]] tag
|
||||||
const AUDIO_AS_VOICE_RE = /\[\[audio_as_voice\]\]/gi;
|
const AUDIO_AS_VOICE_RE = /\[\[audio_as_voice\]\]/gi;
|
||||||
|
const AUDIO_AS_VOICE_TEST_RE = /\[\[audio_as_voice\]\]/i;
|
||||||
|
|
||||||
export function splitMediaFromOutput(raw: string): {
|
export function splitMediaFromOutput(raw: string): {
|
||||||
text: string;
|
text: string;
|
||||||
@@ -123,7 +124,7 @@ export function splitMediaFromOutput(raw: string): {
|
|||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
// Detect and strip [[audio_as_voice]] tag
|
// Detect and strip [[audio_as_voice]] tag
|
||||||
const hasAudioAsVoice = AUDIO_AS_VOICE_RE.test(cleanedText);
|
const hasAudioAsVoice = AUDIO_AS_VOICE_TEST_RE.test(cleanedText);
|
||||||
if (hasAudioAsVoice) {
|
if (hasAudioAsVoice) {
|
||||||
cleanedText = cleanedText
|
cleanedText = cleanedText
|
||||||
.replace(AUDIO_AS_VOICE_RE, "")
|
.replace(AUDIO_AS_VOICE_RE, "")
|
||||||
|
|||||||
Reference in New Issue
Block a user