@hachej/boring-agent 0.1.31 → 0.1.33
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/{DebugDrawer-MCYZ4AWZ.js → DebugDrawer-PX2PHXJH.js} +1 -1
- package/dist/chunk-IDRC4SFU.js +85 -0
- package/dist/{chunk-MMJA3QON.js → chunk-UTUXVT2K.js} +33 -3
- package/dist/front/index.d.ts +50 -3
- package/dist/front/index.js +774 -307
- package/dist/front/styles.css +40 -23
- package/dist/{harness-CQ0uw6xW.d.ts → harness-dYuMzxmp.d.ts} +6 -2
- package/dist/server/index.d.ts +18 -12
- package/dist/server/index.js +1161 -414
- package/dist/shared/index.d.ts +2 -2
- package/docs/ERROR_CODES.md +38 -2
- package/package.json +5 -3
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// src/shared/message-sanitizer.ts
|
|
2
|
+
function collapseExactRepeatedText(text) {
|
|
3
|
+
const maxCopies = Math.min(20, Math.floor(text.length / 4));
|
|
4
|
+
for (let copies = maxCopies; copies >= 2; copies -= 1) {
|
|
5
|
+
if (text.length % copies !== 0) continue;
|
|
6
|
+
const unit = text.slice(0, text.length / copies);
|
|
7
|
+
if (unit.length < 4) continue;
|
|
8
|
+
if (unit.repeat(copies) === text) return unit;
|
|
9
|
+
}
|
|
10
|
+
return text;
|
|
11
|
+
}
|
|
12
|
+
function textFromMessage(message, role) {
|
|
13
|
+
if (message.role !== role) return null;
|
|
14
|
+
const text = (message.parts ?? []).map((part) => {
|
|
15
|
+
const candidate = part;
|
|
16
|
+
return candidate.type === "text" && typeof candidate.text === "string" ? candidate.text : "";
|
|
17
|
+
}).join("").trim();
|
|
18
|
+
return text || null;
|
|
19
|
+
}
|
|
20
|
+
function assistantVisibleText(message) {
|
|
21
|
+
return textFromMessage(message, "assistant");
|
|
22
|
+
}
|
|
23
|
+
function userVisibleText(message) {
|
|
24
|
+
return textFromMessage(message, "user");
|
|
25
|
+
}
|
|
26
|
+
function isTransientUserId(id) {
|
|
27
|
+
return /^user-\d+$/.test(id);
|
|
28
|
+
}
|
|
29
|
+
function isEmptyAssistantMessage(message) {
|
|
30
|
+
return message.role === "assistant" && (!message.parts || message.parts.length === 0);
|
|
31
|
+
}
|
|
32
|
+
function sanitizeUiMessage(message) {
|
|
33
|
+
if (message.role !== "assistant") return message;
|
|
34
|
+
let changed = false;
|
|
35
|
+
const parts = [];
|
|
36
|
+
for (const part of message.parts ?? []) {
|
|
37
|
+
const candidate = part;
|
|
38
|
+
if (candidate.type !== "text" || typeof candidate.text !== "string") {
|
|
39
|
+
parts.push(part);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const text = collapseExactRepeatedText(candidate.text);
|
|
43
|
+
const previous = parts[parts.length - 1];
|
|
44
|
+
if (previous?.type === "text" && previous.text === text) {
|
|
45
|
+
changed = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (text !== candidate.text) changed = true;
|
|
49
|
+
parts.push(text === candidate.text ? part : { ...candidate, text });
|
|
50
|
+
}
|
|
51
|
+
return changed ? { ...message, parts } : message;
|
|
52
|
+
}
|
|
53
|
+
function uiMessageContentKey(message) {
|
|
54
|
+
const sanitized = sanitizeUiMessage(message);
|
|
55
|
+
return `${sanitized.role}:${JSON.stringify(sanitized.parts ?? [])}`;
|
|
56
|
+
}
|
|
57
|
+
function dropEmptyAssistantUiMessages(messages) {
|
|
58
|
+
return messages.filter((message) => !isEmptyAssistantMessage(message));
|
|
59
|
+
}
|
|
60
|
+
function sanitizeUiMessages(messages, options = {}) {
|
|
61
|
+
const deduped = [];
|
|
62
|
+
const seenUserText = /* @__PURE__ */ new Set();
|
|
63
|
+
for (const rawMessage of messages) {
|
|
64
|
+
if (options.dropEmptyAssistantMessages && isEmptyAssistantMessage(rawMessage)) continue;
|
|
65
|
+
const message = sanitizeUiMessage(rawMessage);
|
|
66
|
+
const userText = userVisibleText(message);
|
|
67
|
+
const id = typeof message.id === "string" ? message.id : "";
|
|
68
|
+
if (userText) {
|
|
69
|
+
if (seenUserText.has(userText) && isTransientUserId(id)) continue;
|
|
70
|
+
if (!id.startsWith("pending-user:")) seenUserText.add(userText);
|
|
71
|
+
}
|
|
72
|
+
const previous = deduped[deduped.length - 1];
|
|
73
|
+
const assistantText = assistantVisibleText(message);
|
|
74
|
+
if (assistantText && previous && assistantVisibleText(previous) === assistantText) continue;
|
|
75
|
+
deduped.push(message);
|
|
76
|
+
}
|
|
77
|
+
return deduped;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export {
|
|
81
|
+
sanitizeUiMessage,
|
|
82
|
+
uiMessageContentKey,
|
|
83
|
+
dropEmptyAssistantUiMessages,
|
|
84
|
+
sanitizeUiMessages
|
|
85
|
+
};
|
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
// src/front/DebugDrawer.tsx
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
3
|
|
|
4
|
+
// src/front/clipboard.ts
|
|
5
|
+
async function copyTextToClipboard(text) {
|
|
6
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
7
|
+
try {
|
|
8
|
+
await navigator.clipboard.writeText(text);
|
|
9
|
+
return true;
|
|
10
|
+
} catch {
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (typeof document === "undefined") return false;
|
|
14
|
+
const textarea = document.createElement("textarea");
|
|
15
|
+
textarea.value = text;
|
|
16
|
+
textarea.setAttribute("readonly", "");
|
|
17
|
+
textarea.style.position = "fixed";
|
|
18
|
+
textarea.style.top = "-9999px";
|
|
19
|
+
textarea.style.left = "-9999px";
|
|
20
|
+
textarea.style.opacity = "0";
|
|
21
|
+
textarea.style.pointerEvents = "none";
|
|
22
|
+
document.body.appendChild(textarea);
|
|
23
|
+
try {
|
|
24
|
+
textarea.focus();
|
|
25
|
+
textarea.select();
|
|
26
|
+
return document.execCommand?.("copy") ?? false;
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
} finally {
|
|
30
|
+
document.body.removeChild(textarea);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
4
34
|
// src/front/lib/index.ts
|
|
5
35
|
import { clsx } from "clsx";
|
|
6
36
|
import { twMerge } from "tailwind-merge";
|
|
@@ -21,9 +51,8 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
21
51
|
function CopyButton({ value, label }) {
|
|
22
52
|
const [copied, setCopied] = useState(false);
|
|
23
53
|
const onCopy = useCallback(() => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
void writeText(value).then(() => {
|
|
54
|
+
void copyTextToClipboard(value).then((ok) => {
|
|
55
|
+
if (!ok) return;
|
|
27
56
|
setCopied(true);
|
|
28
57
|
window.setTimeout(() => setCopied(false), 1200);
|
|
29
58
|
});
|
|
@@ -291,5 +320,6 @@ function DebugDrawer({ sessionId, messages, requestHeaders, width, onWidthChange
|
|
|
291
320
|
|
|
292
321
|
export {
|
|
293
322
|
cn,
|
|
323
|
+
copyTextToClipboard,
|
|
294
324
|
DebugDrawer
|
|
295
325
|
};
|
package/dist/front/index.d.ts
CHANGED
|
@@ -4,8 +4,7 @@ import { FileUIPart, UIMessage, ChatStatus } from 'ai';
|
|
|
4
4
|
import * as react from 'react';
|
|
5
5
|
import { ReactNode, ComponentType, HTMLAttributes, ComponentProps, FormEvent } from 'react';
|
|
6
6
|
import { T as ToolUiMetadata } from '../tool-ui-DIFNGwYd.js';
|
|
7
|
-
import
|
|
8
|
-
import { S as SendMessageInput, e as SessionSummary } from '../harness-CQ0uw6xW.js';
|
|
7
|
+
import { S as SendMessageInput, e as SessionSummary } from '../harness-dYuMzxmp.js';
|
|
9
8
|
import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger, InputGroupAddon, InputGroupButton, InputGroupTextarea } from '@hachej/boring-ui-kit';
|
|
10
9
|
import { Streamdown } from 'streamdown';
|
|
11
10
|
import { StickToBottom } from 'use-stick-to-bottom';
|
|
@@ -165,16 +164,24 @@ type ComposerBlocker = {
|
|
|
165
164
|
sessionId?: string;
|
|
166
165
|
actions?: ComposerBlockerAction[];
|
|
167
166
|
};
|
|
167
|
+
type ChatPanelRuntimeDependenciesWarmupStatus = {
|
|
168
|
+
state: 'preparing' | 'ready' | 'failed';
|
|
169
|
+
message?: string;
|
|
170
|
+
requirement?: string;
|
|
171
|
+
};
|
|
168
172
|
type ChatPanelWorkspaceWarmupStatus = {
|
|
169
173
|
status: 'preparing';
|
|
170
174
|
requirement?: 'workspace-fs' | 'sandbox-exec' | 'ui-bridge';
|
|
171
175
|
message?: string;
|
|
176
|
+
runtimeDependencies?: ChatPanelRuntimeDependenciesWarmupStatus;
|
|
172
177
|
} | {
|
|
173
178
|
status: 'ready';
|
|
179
|
+
runtimeDependencies?: ChatPanelRuntimeDependenciesWarmupStatus;
|
|
174
180
|
} | {
|
|
175
181
|
status: 'failed';
|
|
176
182
|
requirement?: 'workspace-fs' | 'sandbox-exec' | 'ui-bridge';
|
|
177
183
|
message?: string;
|
|
184
|
+
runtimeDependencies?: ChatPanelRuntimeDependenciesWarmupStatus;
|
|
178
185
|
};
|
|
179
186
|
type ChatSubmitSource = 'composer' | 'suggestion';
|
|
180
187
|
interface ChatSubmitContext {
|
|
@@ -320,25 +327,65 @@ type UseAgentChatOptions = Pick<SendMessageInput, 'sessionId' | 'model' | 'think
|
|
|
320
327
|
persistMessages?: boolean;
|
|
321
328
|
hydrateMessages?: boolean;
|
|
322
329
|
};
|
|
323
|
-
declare function useAgentChat(opts: UseAgentChatOptions):
|
|
330
|
+
declare function useAgentChat(opts: UseAgentChatOptions): {
|
|
331
|
+
messages: UIMessage<unknown, ai.UIDataTypes, ai.UITools>[];
|
|
332
|
+
sendMessage: (message?: (Omit<UIMessage<unknown, ai.UIDataTypes, ai.UITools>, "role" | "id"> & {
|
|
333
|
+
id?: string | undefined;
|
|
334
|
+
role?: "system" | "user" | "assistant" | undefined;
|
|
335
|
+
} & {
|
|
336
|
+
text?: never;
|
|
337
|
+
files?: never;
|
|
338
|
+
messageId?: string;
|
|
339
|
+
}) | {
|
|
340
|
+
text: string;
|
|
341
|
+
files?: FileList | ai.FileUIPart[];
|
|
342
|
+
metadata?: unknown;
|
|
343
|
+
parts?: never;
|
|
344
|
+
messageId?: string;
|
|
345
|
+
} | {
|
|
346
|
+
files: FileList | ai.FileUIPart[];
|
|
347
|
+
metadata?: unknown;
|
|
348
|
+
parts?: never;
|
|
349
|
+
messageId?: string;
|
|
350
|
+
} | undefined, options?: ai.ChatRequestOptions | undefined) => Promise<void>;
|
|
351
|
+
stop: () => void;
|
|
352
|
+
status: ai.ChatStatus;
|
|
353
|
+
hydrated: boolean;
|
|
354
|
+
hydratingMessages: boolean;
|
|
355
|
+
id: string;
|
|
356
|
+
setMessages: (messages: UIMessage<unknown, ai.UIDataTypes, ai.UITools>[] | ((messages: UIMessage<unknown, ai.UIDataTypes, ai.UITools>[]) => UIMessage<unknown, ai.UIDataTypes, ai.UITools>[])) => void;
|
|
357
|
+
error: Error | undefined;
|
|
358
|
+
regenerate: ({ messageId, ...options }?: {
|
|
359
|
+
messageId?: string;
|
|
360
|
+
} & ai.ChatRequestOptions) => Promise<void>;
|
|
361
|
+
resumeStream: (options?: ai.ChatRequestOptions) => Promise<void>;
|
|
362
|
+
addToolResult: ai.ChatAddToolOutputFunction<UIMessage<unknown, ai.UIDataTypes, ai.UITools>>;
|
|
363
|
+
addToolOutput: ai.ChatAddToolOutputFunction<UIMessage<unknown, ai.UIDataTypes, ai.UITools>>;
|
|
364
|
+
addToolApprovalResponse: ai.ChatAddToolApproveResponseFunction;
|
|
365
|
+
clearError: () => void;
|
|
366
|
+
};
|
|
324
367
|
|
|
325
368
|
interface UseSessionsOptions {
|
|
326
369
|
requestHeaders?: Record<string, string>;
|
|
327
370
|
storageKey?: string;
|
|
328
371
|
enabled?: boolean;
|
|
329
372
|
refreshKey?: unknown;
|
|
373
|
+
initialActiveSessionId?: string;
|
|
330
374
|
}
|
|
331
375
|
interface UseSessionsResult {
|
|
332
376
|
sessions: SessionSummary[];
|
|
333
377
|
activeSession: SessionSummary | undefined;
|
|
334
378
|
activeSessionId: string | undefined;
|
|
335
379
|
loading: boolean;
|
|
380
|
+
loadingMore: boolean;
|
|
381
|
+
hasMore: boolean;
|
|
336
382
|
error: Error | undefined;
|
|
337
383
|
create: (init?: {
|
|
338
384
|
title?: string;
|
|
339
385
|
}) => Promise<SessionSummary>;
|
|
340
386
|
switch: (id: string) => void;
|
|
341
387
|
delete: (id: string) => Promise<void>;
|
|
388
|
+
loadMore: () => Promise<void>;
|
|
342
389
|
}
|
|
343
390
|
declare function useSessions(opts?: UseSessionsOptions): UseSessionsResult;
|
|
344
391
|
|