@hachej/boring-agent 0.1.23 → 0.1.24

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.
@@ -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 };
@@ -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",
@@ -78,24 +70,10 @@ var ErrorLogFieldsSchema = z.object({
78
70
  msg: z.string().min(1)
79
71
  }).catchall(z.unknown());
80
72
 
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
73
  export {
93
- noopTelemetry,
94
- safeCapture,
95
74
  ErrorCode,
96
75
  ERROR_CODES,
97
76
  ApiErrorPayloadSchema,
98
77
  ApiErrorResponseSchema,
99
- ErrorLogFieldsSchema,
100
- validateTool
78
+ ErrorLogFieldsSchema
101
79
  };
@@ -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
+ };
@@ -219,6 +219,18 @@ async function evalAgentPrompt(opts) {
219
219
  if (attempts > maxRetries) return lastResult;
220
220
  continue;
221
221
  }
222
+ if (captured.errorText) {
223
+ lastResult = {
224
+ ok: false,
225
+ actual: captured.toolCalls,
226
+ text: captured.text,
227
+ usage: captured.usage,
228
+ attempts,
229
+ reason: `stream: ${captured.errorText}`
230
+ };
231
+ if (attempts > maxRetries) return lastResult;
232
+ continue;
233
+ }
222
234
  const matchResult = matchExpectations(opts, captured.toolCalls);
223
235
  lastResult = {
224
236
  ok: matchResult.ok,
@@ -307,6 +319,7 @@ ${opts.prompt}` : opts.prompt;
307
319
  function parseSseStream(body) {
308
320
  const toolCalls = [];
309
321
  const textParts = [];
322
+ const errorParts = [];
310
323
  let usage;
311
324
  for (const line of body.split("\n")) {
312
325
  const trimmed = line.trim();
@@ -337,9 +350,28 @@ function parseSseStream(body) {
337
350
  usage = { input: obj.input, output: obj.output };
338
351
  }
339
352
  }
353
+ } else if (type === "error") {
354
+ const text = stringifyStreamError(chunk);
355
+ if (text) errorParts.push(text);
356
+ }
357
+ }
358
+ return {
359
+ toolCalls,
360
+ text: textParts.join(""),
361
+ usage,
362
+ errorText: errorParts.length ? errorParts.join("\n") : void 0
363
+ };
364
+ }
365
+ function stringifyStreamError(chunk) {
366
+ for (const key of ["errorText", "message", "error"]) {
367
+ const value = chunk[key];
368
+ if (typeof value === "string" && value.trim()) return value;
369
+ if (value && typeof value === "object") {
370
+ const message = value.message;
371
+ if (typeof message === "string" && message.trim()) return message;
340
372
  }
341
373
  }
342
- return { toolCalls, text: textParts.join(""), usage };
374
+ return "unknown stream error";
343
375
  }
344
376
  function withTimeout(p, ms, label) {
345
377
  return new Promise((resolve, reject) => {
@@ -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-BCit36Ha.js';
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<{