@hachej/boring-agent 0.1.23 → 0.1.26
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/{agentPluginEvents-zyIvVjsA.d.ts → agentPluginEvents-CF0BGY4D.d.ts} +11 -1
- package/dist/chunk-HDOSWEXG.js +29 -0
- package/dist/{chunk-6R7O6BHW.js → chunk-UBWQ5LT4.js} +10 -28
- package/dist/eval/index.d.ts +4 -0
- package/dist/eval/index.js +53 -5
- package/dist/front/index.d.ts +49 -4
- package/dist/front/index.js +432 -76
- package/dist/front/styles.css +8 -0
- package/dist/{harness-BCit36Ha.d.ts → harness-CQ0uw6xW.d.ts} +5 -0
- package/dist/server/index.d.ts +29 -6
- package/dist/server/index.js +990 -609
- package/dist/shared/index.d.ts +17 -17
- package/dist/shared/index.js +8 -6
- package/docs/ERROR_CODES.md +8 -0
- package/docs/README.md +1 -0
- package/docs/runtime.md +133 -2
- package/package.json +9 -8
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
interface WorkspaceRuntimeContext {
|
|
2
|
+
/** Agent-visible working directory shared by file-tree and shell execution. */
|
|
3
|
+
readonly runtimeCwd: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
interface Workspace {
|
|
7
|
+
/** Agent-visible workspace root; must match runtimeContext.runtimeCwd. */
|
|
2
8
|
readonly root: string;
|
|
9
|
+
readonly runtimeContext: WorkspaceRuntimeContext;
|
|
3
10
|
readFile(relPath: string): Promise<string>;
|
|
4
11
|
/** Optional binary read operation for media/document previews. */
|
|
5
12
|
readBinaryFile?(relPath: string): Promise<Uint8Array>;
|
|
@@ -137,6 +144,8 @@ interface Sandbox {
|
|
|
137
144
|
readonly provider: string;
|
|
138
145
|
/** Capabilities this sandbox advertises (used for tool gating). */
|
|
139
146
|
readonly capabilities: readonly SandboxCapability[];
|
|
147
|
+
/** Agent-visible runtime cwd; must match the paired Workspace root. */
|
|
148
|
+
readonly runtimeContext: WorkspaceRuntimeContext;
|
|
140
149
|
/**
|
|
141
150
|
* Optional initialization hook. Some backends bind to a workspace +
|
|
142
151
|
* session at construction (Vercel: snapshot warm-up; Bwrap: bind
|
|
@@ -171,6 +180,7 @@ interface Sandbox {
|
|
|
171
180
|
dispose?(): Promise<void>;
|
|
172
181
|
}
|
|
173
182
|
interface ExecOptions {
|
|
183
|
+
/** Runtime-visible cwd. Relative paths resolve from `runtimeContext.runtimeCwd`. */
|
|
174
184
|
cwd?: string;
|
|
175
185
|
env?: Record<string, string>;
|
|
176
186
|
signal?: AbortSignal;
|
|
@@ -287,4 +297,4 @@ interface PluginRestartWarning {
|
|
|
287
297
|
message: string;
|
|
288
298
|
}
|
|
289
299
|
|
|
290
|
-
export { type Entry as E, type FileSearch as F, type IsolatedCodeInput as I, type PluginRestartWarning as P, type Sandbox as S, type Workspace as W, type ExecOptions as a, type ExecResult as b, type IsolatedCodeOutput as c, type SandboxCapability as d, type SandboxHandleRecord as e, type SandboxHandleStore as f, type Stat as g, WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT as h };
|
|
300
|
+
export { type Entry as E, type FileSearch as F, type IsolatedCodeInput as I, type PluginRestartWarning as P, type Sandbox as S, type Workspace as W, type ExecOptions as a, type ExecResult as b, type IsolatedCodeOutput as c, type SandboxCapability as d, type SandboxHandleRecord as e, type SandboxHandleStore as f, type Stat as g, WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT as h, type WorkspaceRuntimeContext as i };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/shared/telemetry.ts
|
|
2
|
+
var noopTelemetry = {
|
|
3
|
+
capture() {
|
|
4
|
+
}
|
|
5
|
+
};
|
|
6
|
+
function safeCapture(telemetry, event) {
|
|
7
|
+
try {
|
|
8
|
+
void Promise.resolve(telemetry.capture(event)).catch(() => {
|
|
9
|
+
});
|
|
10
|
+
} catch {
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/shared/validateTool.ts
|
|
15
|
+
function validateTool(tool) {
|
|
16
|
+
if (typeof tool !== "object" || tool === null) return null;
|
|
17
|
+
const t = tool;
|
|
18
|
+
if (typeof t.name !== "string" || t.name.length === 0) return null;
|
|
19
|
+
if (typeof t.description !== "string") return null;
|
|
20
|
+
if (typeof t.parameters !== "object" || t.parameters === null) return null;
|
|
21
|
+
if (typeof t.execute !== "function") return null;
|
|
22
|
+
return t;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
noopTelemetry,
|
|
27
|
+
safeCapture,
|
|
28
|
+
validateTool
|
|
29
|
+
};
|
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
// src/shared/telemetry.ts
|
|
2
|
-
var noopTelemetry = {
|
|
3
|
-
capture() {
|
|
4
|
-
}
|
|
5
|
-
};
|
|
6
|
-
function safeCapture(telemetry, event) {
|
|
7
|
-
try {
|
|
8
|
-
void Promise.resolve(telemetry.capture(event)).catch(() => {
|
|
9
|
-
});
|
|
10
|
-
} catch {
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
1
|
// src/shared/error-codes.ts
|
|
15
2
|
import { z } from "zod";
|
|
16
3
|
var ErrorCode = z.enum([
|
|
@@ -28,6 +15,11 @@ var ErrorCode = z.enum([
|
|
|
28
15
|
"PATH_NOT_FOUND",
|
|
29
16
|
"PATH_NOT_WRITABLE",
|
|
30
17
|
"WORKSPACE_UNINITIALIZED",
|
|
18
|
+
"WORKSPACE_NOT_READY",
|
|
19
|
+
// Agent runtime / provisioning
|
|
20
|
+
"AGENT_RUNTIME_NOT_READY",
|
|
21
|
+
"RUNTIME_PROVISIONING_FAILED",
|
|
22
|
+
"RUNTIME_PROVISIONING_LOCKED",
|
|
31
23
|
// Sandbox / exec
|
|
32
24
|
"BWRAP_UNAVAILABLE",
|
|
33
25
|
"BWRAP_TIMEOUT",
|
|
@@ -50,6 +42,10 @@ var ErrorCode = z.enum([
|
|
|
50
42
|
// Plugin
|
|
51
43
|
"PLUGIN_LOAD_FAILED",
|
|
52
44
|
"PLUGIN_NAME_COLLISION",
|
|
45
|
+
"PLUGIN_RUNTIME_REVISION_MISMATCH",
|
|
46
|
+
"PLUGIN_RUNTIME_PRIVATE_FILE",
|
|
47
|
+
"PLUGIN_RUNTIME_UNSAFE_IMPORT",
|
|
48
|
+
"PLUGIN_RUNTIME_TRANSFORM_FAILED",
|
|
53
49
|
// Runtime provisioning
|
|
54
50
|
"PROVISIONING_LAYOUT_FAILED",
|
|
55
51
|
"PROVISIONING_SKILLS_FAILED",
|
|
@@ -78,24 +74,10 @@ var ErrorLogFieldsSchema = z.object({
|
|
|
78
74
|
msg: z.string().min(1)
|
|
79
75
|
}).catchall(z.unknown());
|
|
80
76
|
|
|
81
|
-
// src/shared/validateTool.ts
|
|
82
|
-
function validateTool(tool) {
|
|
83
|
-
if (typeof tool !== "object" || tool === null) return null;
|
|
84
|
-
const t = tool;
|
|
85
|
-
if (typeof t.name !== "string" || t.name.length === 0) return null;
|
|
86
|
-
if (typeof t.description !== "string") return null;
|
|
87
|
-
if (typeof t.parameters !== "object" || t.parameters === null) return null;
|
|
88
|
-
if (typeof t.execute !== "function") return null;
|
|
89
|
-
return t;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
77
|
export {
|
|
93
|
-
noopTelemetry,
|
|
94
|
-
safeCapture,
|
|
95
78
|
ErrorCode,
|
|
96
79
|
ERROR_CODES,
|
|
97
80
|
ApiErrorPayloadSchema,
|
|
98
81
|
ApiErrorResponseSchema,
|
|
99
|
-
ErrorLogFieldsSchema
|
|
100
|
-
validateTool
|
|
82
|
+
ErrorLogFieldsSchema
|
|
101
83
|
};
|
package/dist/eval/index.d.ts
CHANGED
|
@@ -90,6 +90,10 @@ interface EvalPromptOptions {
|
|
|
90
90
|
systemPrompt?: string;
|
|
91
91
|
/** Override the chat session id (defaults to a fresh uuid). */
|
|
92
92
|
sessionId?: string;
|
|
93
|
+
/** Optional headers sent to session/chat requests (e.g. workspace scoping). */
|
|
94
|
+
headers?: Record<string, string>;
|
|
95
|
+
/** Optional query params appended to session/chat requests (e.g. workspaceId). */
|
|
96
|
+
query?: Record<string, string | number | boolean | undefined>;
|
|
93
97
|
/** Per-call timeout in ms. Defaults to 30_000. */
|
|
94
98
|
timeoutMs?: number;
|
|
95
99
|
/** Per-prompt retry count. Default: 0. */
|
package/dist/eval/index.js
CHANGED
|
@@ -206,7 +206,9 @@ async function evalAgentPrompt(opts) {
|
|
|
206
206
|
prompt: opts.prompt,
|
|
207
207
|
systemPrompt: opts.systemPrompt,
|
|
208
208
|
model,
|
|
209
|
-
timeoutMs
|
|
209
|
+
timeoutMs,
|
|
210
|
+
headers: opts.headers,
|
|
211
|
+
query: opts.query
|
|
210
212
|
});
|
|
211
213
|
} catch (err) {
|
|
212
214
|
lastResult = {
|
|
@@ -219,6 +221,18 @@ async function evalAgentPrompt(opts) {
|
|
|
219
221
|
if (attempts > maxRetries) return lastResult;
|
|
220
222
|
continue;
|
|
221
223
|
}
|
|
224
|
+
if (captured.errorText) {
|
|
225
|
+
lastResult = {
|
|
226
|
+
ok: false,
|
|
227
|
+
actual: captured.toolCalls,
|
|
228
|
+
text: captured.text,
|
|
229
|
+
usage: captured.usage,
|
|
230
|
+
attempts,
|
|
231
|
+
reason: `stream: ${captured.errorText}`
|
|
232
|
+
};
|
|
233
|
+
if (attempts > maxRetries) return lastResult;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
222
236
|
const matchResult = matchExpectations(opts, captured.toolCalls);
|
|
223
237
|
lastResult = {
|
|
224
238
|
ok: matchResult.ok,
|
|
@@ -265,9 +279,11 @@ function matchExpectations(opts, actual) {
|
|
|
265
279
|
return someCallMatches(opts.expect, actual);
|
|
266
280
|
}
|
|
267
281
|
async function runOnce(app, opts) {
|
|
282
|
+
const querySuffix = formatQuerySuffix(opts.query);
|
|
268
283
|
await app.inject({
|
|
269
284
|
method: "POST",
|
|
270
|
-
url:
|
|
285
|
+
url: `/api/v1/agent/sessions${querySuffix}`,
|
|
286
|
+
headers: opts.headers,
|
|
271
287
|
payload: { id: opts.sessionId }
|
|
272
288
|
});
|
|
273
289
|
const userMessage = opts.systemPrompt ? `[SYSTEM]
|
|
@@ -280,7 +296,8 @@ ${opts.prompt}` : opts.prompt;
|
|
|
280
296
|
res = await withTimeout(
|
|
281
297
|
app.inject({
|
|
282
298
|
method: "POST",
|
|
283
|
-
url:
|
|
299
|
+
url: `/api/v1/agent/chat${querySuffix}`,
|
|
300
|
+
headers: opts.headers,
|
|
284
301
|
payload: {
|
|
285
302
|
sessionId: opts.sessionId,
|
|
286
303
|
message: userMessage,
|
|
@@ -293,7 +310,8 @@ ${opts.prompt}` : opts.prompt;
|
|
|
293
310
|
} finally {
|
|
294
311
|
void app.inject({
|
|
295
312
|
method: "DELETE",
|
|
296
|
-
url: `/api/v1/agent/sessions/${encodeURIComponent(opts.sessionId)}
|
|
313
|
+
url: `/api/v1/agent/sessions/${encodeURIComponent(opts.sessionId)}${querySuffix}`,
|
|
314
|
+
headers: opts.headers
|
|
297
315
|
}).catch(() => {
|
|
298
316
|
});
|
|
299
317
|
}
|
|
@@ -304,9 +322,20 @@ ${opts.prompt}` : opts.prompt;
|
|
|
304
322
|
}
|
|
305
323
|
return parseSseStream(res.body);
|
|
306
324
|
}
|
|
325
|
+
function formatQuerySuffix(query) {
|
|
326
|
+
if (!query) return "";
|
|
327
|
+
const params = new URLSearchParams();
|
|
328
|
+
for (const [key, value] of Object.entries(query)) {
|
|
329
|
+
if (value === void 0) continue;
|
|
330
|
+
params.set(key, String(value));
|
|
331
|
+
}
|
|
332
|
+
const encoded = params.toString();
|
|
333
|
+
return encoded ? `?${encoded}` : "";
|
|
334
|
+
}
|
|
307
335
|
function parseSseStream(body) {
|
|
308
336
|
const toolCalls = [];
|
|
309
337
|
const textParts = [];
|
|
338
|
+
const errorParts = [];
|
|
310
339
|
let usage;
|
|
311
340
|
for (const line of body.split("\n")) {
|
|
312
341
|
const trimmed = line.trim();
|
|
@@ -337,9 +366,28 @@ function parseSseStream(body) {
|
|
|
337
366
|
usage = { input: obj.input, output: obj.output };
|
|
338
367
|
}
|
|
339
368
|
}
|
|
369
|
+
} else if (type === "error") {
|
|
370
|
+
const text = stringifyStreamError(chunk);
|
|
371
|
+
if (text) errorParts.push(text);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
toolCalls,
|
|
376
|
+
text: textParts.join(""),
|
|
377
|
+
usage,
|
|
378
|
+
errorText: errorParts.length ? errorParts.join("\n") : void 0
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function stringifyStreamError(chunk) {
|
|
382
|
+
for (const key of ["errorText", "message", "error"]) {
|
|
383
|
+
const value = chunk[key];
|
|
384
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
385
|
+
if (value && typeof value === "object") {
|
|
386
|
+
const message = value.message;
|
|
387
|
+
if (typeof message === "string" && message.trim()) return message;
|
|
340
388
|
}
|
|
341
389
|
}
|
|
342
|
-
return
|
|
390
|
+
return "unknown stream error";
|
|
343
391
|
}
|
|
344
392
|
function withTimeout(p, ms, label) {
|
|
345
393
|
return new Promise((resolve, reject) => {
|
package/dist/front/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as ai from 'ai';
|
|
3
|
+
import { FileUIPart, UIMessage, ChatStatus } from 'ai';
|
|
2
4
|
import * as react from 'react';
|
|
3
5
|
import { ReactNode, ComponentType, HTMLAttributes, ComponentProps, FormEvent } from 'react';
|
|
4
6
|
import { T as ToolUiMetadata } from '../tool-ui-DIFNGwYd.js';
|
|
5
|
-
import * as ai from 'ai';
|
|
6
|
-
import { UIMessage, FileUIPart, ChatStatus } from 'ai';
|
|
7
7
|
import * as _ai_sdk_react from '@ai-sdk/react';
|
|
8
|
-
import { S as SendMessageInput, e as SessionSummary } from '../harness-
|
|
8
|
+
import { S as SendMessageInput, e as SessionSummary } from '../harness-CQ0uw6xW.js';
|
|
9
9
|
import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger, InputGroupAddon, InputGroupButton, InputGroupTextarea } from '@hachej/boring-ui-kit';
|
|
10
10
|
import { Streamdown } from 'streamdown';
|
|
11
11
|
import { StickToBottom } from 'use-stick-to-bottom';
|
|
@@ -165,6 +165,23 @@ type ComposerBlocker = {
|
|
|
165
165
|
sessionId?: string;
|
|
166
166
|
actions?: ComposerBlockerAction[];
|
|
167
167
|
};
|
|
168
|
+
type ChatPanelWorkspaceWarmupStatus = {
|
|
169
|
+
status: 'preparing';
|
|
170
|
+
requirement?: 'workspace-fs' | 'sandbox-exec' | 'ui-bridge';
|
|
171
|
+
message?: string;
|
|
172
|
+
} | {
|
|
173
|
+
status: 'ready';
|
|
174
|
+
} | {
|
|
175
|
+
status: 'failed';
|
|
176
|
+
requirement?: 'workspace-fs' | 'sandbox-exec' | 'ui-bridge';
|
|
177
|
+
message?: string;
|
|
178
|
+
};
|
|
179
|
+
type ChatSubmitSource = 'composer' | 'suggestion';
|
|
180
|
+
interface ChatSubmitContext {
|
|
181
|
+
files: FileUIPart[];
|
|
182
|
+
sessionId: string;
|
|
183
|
+
source: ChatSubmitSource;
|
|
184
|
+
}
|
|
168
185
|
interface ChatPanelProps {
|
|
169
186
|
sessionId: string;
|
|
170
187
|
toolRenderers?: ToolRendererOverrides;
|
|
@@ -222,6 +239,8 @@ interface ChatPanelProps {
|
|
|
222
239
|
onData?: (part: unknown) => void;
|
|
223
240
|
/** Headers sent with chat and chat-history requests. */
|
|
224
241
|
requestHeaders?: Record<string, string>;
|
|
242
|
+
/** When false, skip initial chat history/stream resume requests until the user sends. */
|
|
243
|
+
hydrateMessages?: boolean;
|
|
225
244
|
/**
|
|
226
245
|
* Called with a file path when the user clicks the path label inside
|
|
227
246
|
* a read / write / edit tool card. Hosts (e.g. @hachej/boring-workspace)
|
|
@@ -237,6 +256,29 @@ interface ChatPanelProps {
|
|
|
237
256
|
* is zero-cost when off.
|
|
238
257
|
*/
|
|
239
258
|
debug?: boolean;
|
|
259
|
+
/** Draft text restored into the composer on mount/update. */
|
|
260
|
+
initialDraft?: string;
|
|
261
|
+
/** Auto-submit initialDraft once after it is restored. Defaults to false. */
|
|
262
|
+
autoSubmitInitialDraft?: boolean;
|
|
263
|
+
/** Called after initialDraft is applied to the composer. */
|
|
264
|
+
onDraftRestored?: () => void;
|
|
265
|
+
/** Called after an auto-submitted initial draft is accepted for sending. */
|
|
266
|
+
onAutoSubmitInitialDraftAccepted?: () => void;
|
|
267
|
+
/** Called after an auto-submitted initial draft finishes its turn. */
|
|
268
|
+
onAutoSubmitInitialDraftSettled?: () => void;
|
|
269
|
+
/**
|
|
270
|
+
* Runs before any agent/model/session-history network send. Return false to
|
|
271
|
+
* cancel submission and keep the draft in the composer.
|
|
272
|
+
*/
|
|
273
|
+
onBeforeSubmit?: (draft: string, ctx: ChatSubmitContext) => false | void | Promise<false | void>;
|
|
274
|
+
/** Disable server-backed model/skill discovery for public/local-only shells. */
|
|
275
|
+
serverResourcesEnabled?: boolean;
|
|
276
|
+
/** Center the empty-state prompt/composer for public landing experiences. */
|
|
277
|
+
emptyPlacement?: 'default' | 'hero';
|
|
278
|
+
/** Placeholder shown in the composer textarea. */
|
|
279
|
+
composerPlaceholder?: string;
|
|
280
|
+
/** Current workspace warmup state. Preparing/failed states keep submits local so drafts are not lost. */
|
|
281
|
+
workspaceWarmupStatus?: ChatPanelWorkspaceWarmupStatus;
|
|
240
282
|
/** Generic host-provided blockers that prevent starting a new user turn. */
|
|
241
283
|
composerBlockers?: ComposerBlocker[];
|
|
242
284
|
/** Called when the user presses Stop in the composer. */
|
|
@@ -276,12 +318,15 @@ type UseAgentChatOptions = Pick<SendMessageInput, 'sessionId' | 'model' | 'think
|
|
|
276
318
|
onData?: (part: unknown) => void;
|
|
277
319
|
requestHeaders?: Record<string, string>;
|
|
278
320
|
persistMessages?: boolean;
|
|
321
|
+
hydrateMessages?: boolean;
|
|
279
322
|
};
|
|
280
323
|
declare function useAgentChat(opts: UseAgentChatOptions): _ai_sdk_react.UseChatHelpers<UIMessage<unknown, ai.UIDataTypes, ai.UITools>>;
|
|
281
324
|
|
|
282
325
|
interface UseSessionsOptions {
|
|
283
326
|
requestHeaders?: Record<string, string>;
|
|
284
327
|
storageKey?: string;
|
|
328
|
+
enabled?: boolean;
|
|
329
|
+
refreshKey?: unknown;
|
|
285
330
|
}
|
|
286
331
|
interface UseSessionsResult {
|
|
287
332
|
sessions: SessionSummary[];
|
|
@@ -384,7 +429,7 @@ type PromptInputProps = Omit<HTMLAttributes<HTMLFormElement>, "onSubmit" | "onEr
|
|
|
384
429
|
code: "max_files" | "max_file_size" | "accept";
|
|
385
430
|
message: string;
|
|
386
431
|
}) => void;
|
|
387
|
-
onSubmit: (message: PromptInputMessage, event: FormEvent<HTMLFormElement>) => void | Promise<void>;
|
|
432
|
+
onSubmit: (message: PromptInputMessage, event: FormEvent<HTMLFormElement>) => false | void | Promise<false | void>;
|
|
388
433
|
/** When provided, files are uploaded to the server immediately on add and the
|
|
389
434
|
* attachment URL is replaced with the stable server path before submit. */
|
|
390
435
|
onUploadFile?: (file: File) => Promise<{
|