@ai-hero/sandcastle 0.9.0 → 0.10.0

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/README.md CHANGED
@@ -218,14 +218,20 @@ const result = await run({
218
218
  type: "file",
219
219
  path: ".sandcastle/logs/my-run.log",
220
220
  // Optional: forward the agent's output stream to your own observability system.
221
- // Fires for each text chunk and tool call the agent produces. Errors thrown
222
- // by the callback are swallowed so a broken forwarder cannot kill the run.
221
+ // Fires for each text chunk, tool call, and raw stdout line the agent
222
+ // produces. Errors thrown by the callback are swallowed so a broken
223
+ // forwarder cannot kill the run.
223
224
  onAgentStreamEvent: (event) => {
224
- // event is { type: "text" | "toolCall", iteration, timestamp, ... }
225
+ // event is { type: "text" | "toolCall" | "raw", iteration, timestamp, ... }
225
226
  myLogger.info(event);
226
227
  },
228
+ // Optional: append every raw stdout line the agent emits to the same
229
+ // log file, interleaved with the human-readable output. Includes lines
230
+ // the provider's stream parser would otherwise drop. Intended for
231
+ // debugging stuck or unexpected agent behaviour.
232
+ verbose: true,
227
233
  },
228
- // logging: { type: "stdout" }, // OR render an interactive UI in the terminal
234
+ // logging: { type: "stdout", verbose: true }, // OR terminal mode (verbose: raw lines to stdout)
229
235
 
230
236
  // String (or array of strings) the agent emits to end the iteration loop early.
231
237
  // Default: "<promise>COMPLETE</promise>"
package/dist/index.d.ts CHANGED
@@ -246,6 +246,12 @@ declare const claudeCode: (model: string, options?: ClaudeCodeOptions) => AgentP
246
246
  *
247
247
  * Emitted only in log-to-file mode when an `onAgentStreamEvent` callback is
248
248
  * provided via `logging`. See `run()`.
249
+ *
250
+ * The `"raw"` variant carries every stdout line the agent emits, verbatim and
251
+ * before parsing — including lines that the provider's stream parser would
252
+ * otherwise drop (e.g. tool-use blocks for unrecognised tools). Intended for
253
+ * debugging when the typed `"text"` / `"toolCall"` events don't surface
254
+ * enough detail.
249
255
  */
250
256
  type AgentStreamEvent = {
251
257
  readonly type: "text";
@@ -258,6 +264,11 @@ type AgentStreamEvent = {
258
264
  readonly formattedArgs: string;
259
265
  readonly iteration: number;
260
266
  readonly timestamp: Date;
267
+ } | {
268
+ readonly type: "raw";
269
+ readonly line: string;
270
+ readonly iteration: number;
271
+ readonly timestamp: Date;
261
272
  };
262
273
 
263
274
  type SandboxHooks = {
@@ -414,16 +425,34 @@ type LoggingOption =
414
425
  readonly type: "file";
415
426
  readonly path: string;
416
427
  /**
417
- * Optional callback invoked for each agent stream event (text chunk or
418
- * tool call) in addition to being written to the log file. Intended for
419
- * forwarding the agent's output stream to external observability
420
- * systems. Errors thrown by the callback are swallowed.
428
+ * Optional callback invoked for each agent stream event (text chunk,
429
+ * tool call, or raw stdout line) in addition to being written to the
430
+ * log file. Intended for forwarding the agent's output stream to
431
+ * external observability systems. Errors thrown by the callback are
432
+ * swallowed.
421
433
  */
422
434
  readonly onAgentStreamEvent?: (event: AgentStreamEvent) => void;
435
+ /**
436
+ * When `true`, every raw stdout line the agent emits is appended
437
+ * verbatim to the same log file at `path`, in real time. Includes
438
+ * lines the provider's stream parser would otherwise drop (e.g.
439
+ * tool-use blocks for unrecognised tools). Intended for debugging
440
+ * stuck or unexpected agent behavior — note that the raw JSON is
441
+ * interleaved with the human-readable log output. Default: `false`.
442
+ */
443
+ readonly verbose?: boolean;
423
444
  }
424
445
  /** Render progress and agent output as an interactive UI in the terminal (terminal mode). */
425
446
  | {
426
447
  readonly type: "stdout";
448
+ /**
449
+ * When `true`, every raw stdout line the agent emits is written
450
+ * verbatim to `process.stdout`, in real time. Includes lines the
451
+ * provider's stream parser would otherwise drop. Intended for
452
+ * debugging stuck or unexpected agent behavior. Note: the raw output
453
+ * is interleaved with the interactive terminal UI. Default: `false`.
454
+ */
455
+ readonly verbose?: boolean;
427
456
  };
428
457
  /** Override default timeouts for built-in lifecycle steps. Unset keys keep their defaults. */
429
458
  interface Timeouts {
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ import { Context_exports, CwdError, Effect_exports, resolveCwd, getCurrentBranch
4
4
  export { createBindMountSandboxProvider, createIsolatedSandboxProvider } from './chunk-BIWNFKGV.js';
5
5
  import { noSandbox } from './chunk-72UVAC7B.js';
6
6
  import './chunk-NGBM7T3E.js';
7
+ import { mkdirSync, appendFileSync } from 'fs';
7
8
  import path, { join, posix, dirname, relative } from 'path';
8
9
  import { styleText } from 'util';
9
10
  import * as clack from '@clack/prompts';
@@ -177,7 +178,7 @@ var TextDeltaBuffer = class {
177
178
 
178
179
  // src/Orchestrator.ts
179
180
  var IDLE_WARNING_INTERVAL_MS = 6e4;
180
- var invokeAgent = (sandbox, sandboxRepoDir, prompt, provider, idleTimeoutMs, completionTimeoutMs, completionSignals, onText, onToolCall, onIdleWarning, onCompletionTimeout, idleWarningIntervalMs = IDLE_WARNING_INTERVAL_MS, resumeSession, forkSession, signal) => Effect_exports.gen(function* () {
181
+ var invokeAgent = (sandbox, sandboxRepoDir, prompt, provider, idleTimeoutMs, completionTimeoutMs, completionSignals, onText, onToolCall, onRawLine, onIdleWarning, onCompletionTimeout, idleWarningIntervalMs = IDLE_WARNING_INTERVAL_MS, resumeSession, forkSession, signal) => Effect_exports.gen(function* () {
181
182
  let resultText = "";
182
183
  let sessionId;
183
184
  let usage;
@@ -256,6 +257,10 @@ var invokeAgent = (sandbox, sandboxRepoDir, prompt, provider, idleTimeoutMs, com
256
257
  });
257
258
  const execResult = yield* sandbox.exec(printCmd.command, {
258
259
  onLine: (line) => {
260
+ try {
261
+ onRawLine(line);
262
+ } catch {
263
+ }
259
264
  for (const parsed of provider.parseStreamLine(line)) {
260
265
  if (parsed.type === "text") {
261
266
  onText(parsed.text);
@@ -421,6 +426,16 @@ var orchestrate = (options) => {
421
426
  })
422
427
  );
423
428
  };
429
+ const onRawLine = (line) => {
430
+ Effect_exports.runPromise(
431
+ streamEmitter.emit({
432
+ type: "raw",
433
+ line,
434
+ iteration: i,
435
+ timestamp: /* @__PURE__ */ new Date()
436
+ })
437
+ );
438
+ };
424
439
  const onIdleWarning = (minutes) => {
425
440
  const msg = minutes === 1 ? "Agent idle for 1 minute" : `Agent idle for ${minutes} minutes`;
426
441
  Effect_exports.runPromise(display.status(label(msg), "warn"));
@@ -449,6 +464,7 @@ var orchestrate = (options) => {
449
464
  completionSignals,
450
465
  onText,
451
466
  onToolCall,
467
+ onRawLine,
452
468
  onIdleWarning,
453
469
  onCompletionTimeout,
454
470
  options._idleWarningIntervalMs,
@@ -896,6 +912,40 @@ var formatContextWindowSize = (usage) => {
896
912
  return `${Math.ceil(total / 1e3)}k`;
897
913
  };
898
914
  var buildContextWindowLines = (iterations) => iterations.filter((it) => it.usage !== void 0).map((it) => `Context window: ${formatContextWindowSize(it.usage)}`);
915
+ var buildAgentStreamHandler = (logging) => {
916
+ const userHandler = logging.type === "file" ? logging.onAgentStreamEvent : void 0;
917
+ const verboseSink = logging.verbose ? buildVerboseRawLineSink(logging) : void 0;
918
+ if (!userHandler && !verboseSink) return void 0;
919
+ return (event) => {
920
+ if (userHandler) {
921
+ try {
922
+ userHandler(event);
923
+ } catch {
924
+ }
925
+ }
926
+ if (verboseSink && event.type === "raw") {
927
+ verboseSink(event.line);
928
+ }
929
+ };
930
+ };
931
+ var buildVerboseRawLineSink = (logging) => {
932
+ if (logging.type === "file") {
933
+ const logPath = logging.path;
934
+ try {
935
+ mkdirSync(path.dirname(logPath), { recursive: true });
936
+ } catch {
937
+ }
938
+ return (line) => {
939
+ try {
940
+ appendFileSync(logPath, line + "\n");
941
+ } catch {
942
+ }
943
+ };
944
+ }
945
+ return (line) => {
946
+ process.stdout.write(line + "\n");
947
+ };
948
+ };
899
949
  async function run(options) {
900
950
  options.signal?.throwIfAborted();
901
951
  const {
@@ -1013,7 +1063,7 @@ async function run(options) {
1013
1063
  )
1014
1064
  );
1015
1065
  const streamEmitterLayer = agentStreamEmitterLayer(
1016
- resolvedLogging.type === "file" ? resolvedLogging.onAgentStreamEvent : void 0
1066
+ buildAgentStreamHandler(resolvedLogging)
1017
1067
  );
1018
1068
  const runLayer = Layer_exports.mergeAll(
1019
1069
  factoryLayer,
@@ -1525,7 +1575,7 @@ var buildSandboxHandle = (ctx, close) => {
1525
1575
  )
1526
1576
  });
1527
1577
  const streamEmitterLayer = agentStreamEmitterLayer(
1528
- resolvedLogging.type === "file" ? resolvedLogging.onAgentStreamEvent : void 0
1578
+ buildAgentStreamHandler(resolvedLogging)
1529
1579
  );
1530
1580
  const runLayer = Layer_exports.mergeAll(
1531
1581
  reuseFactoryLayer,
@@ -2304,7 +2354,7 @@ var createWorktree = async (options) => {
2304
2354
  )
2305
2355
  });
2306
2356
  const streamEmitterLayer = agentStreamEmitterLayer(
2307
- resolvedLogging.type === "file" ? resolvedLogging.onAgentStreamEvent : void 0
2357
+ buildAgentStreamHandler(resolvedLogging)
2308
2358
  );
2309
2359
  const runLayer = Layer_exports.mergeAll(
2310
2360
  reuseFactoryLayer,