@holon-run/agentinbox 0.7.0 → 1.0.0-beta.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.
@@ -12,33 +12,35 @@ const node_path_1 = __importDefault(require("node:path"));
12
12
  const node_os_1 = __importDefault(require("node:os"));
13
13
  const logging_1 = require("./logging");
14
14
  const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
15
- const DEFAULT_ITERM2_SAMPLE_DELAY_MS = 900;
16
- const DEFAULT_TMUX_SAMPLE_DELAY_MS = 900;
17
- const NORMALIZED_BUFFER_TAIL_LINES = 20;
18
- const ACTIVE_BUFFER_MARKERS = [
19
- "esc to interrupt",
20
- "Working (",
21
- "⠼ ",
22
- ];
23
- const TYPING_PROMPT_PREFIXES = [
24
- "› ",
25
- "> ",
26
- ];
15
+ const DEFAULT_GATING_POLICY = {
16
+ iTerm2SampleDelayMs: 900,
17
+ tmuxSampleDelayMs: 900,
18
+ normalizedBufferTailLines: 20,
19
+ activeBufferMarkers: [
20
+ "esc to interrupt",
21
+ "Working (",
22
+ "⠼ ",
23
+ ],
24
+ typingPromptPrefixes: [
25
+ "› ",
26
+ "> ",
27
+ ],
28
+ };
27
29
  class DefaultActivationGate {
28
30
  runtimeProbes;
29
31
  terminalProbes;
30
32
  logger;
31
- constructor(runtimeProbes, terminalProbes, logger = new logging_1.NoopLogger()) {
33
+ constructor(runtimeProbes, terminalProbes, logger = new logging_1.NoopLogger(), gatingPolicy = DEFAULT_GATING_POLICY) {
32
34
  this.logger = logger.child("activation_gate");
33
35
  this.runtimeProbes = runtimeProbes ?? [
34
36
  new CodexRuntimePresenceProbe(undefined, undefined, this.logger.child("codex_runtime")),
35
37
  new ClaudeCodeRuntimePresenceProbe(undefined, undefined, this.logger.child("claude_runtime")),
36
38
  ];
37
39
  this.terminalProbes = terminalProbes ?? [
38
- new CodexTerminalStateProbe(undefined, undefined, undefined, undefined, undefined, this.logger.child("codex_terminal")),
39
- new Iterm2TerminalStateProbe(undefined, {}, this.logger.child("iterm2_terminal")),
40
+ new CodexTerminalStateProbe(undefined, undefined, undefined, undefined, undefined, this.logger.child("codex_terminal"), gatingPolicy),
41
+ new Iterm2TerminalStateProbe(undefined, {}, this.logger.child("iterm2_terminal"), gatingPolicy),
40
42
  new ClaudeCodeTerminalStateProbe(undefined, undefined, undefined, this.logger.child("claude_terminal")),
41
- new TmuxTerminalStateProbe(undefined, {}, this.logger.child("tmux_terminal")),
43
+ new TmuxTerminalStateProbe(undefined, {}, this.logger.child("tmux_terminal"), gatingPolicy),
42
44
  ];
43
45
  }
44
46
  async evaluate(target) {
@@ -178,13 +180,15 @@ class CodexTerminalStateProbe {
178
180
  statReader;
179
181
  nowFn;
180
182
  logger;
181
- constructor(iTermProbe = new Iterm2TerminalStateProbe(), tmuxProbe = new TmuxTerminalStateProbe(), sessionFileReader, statReader, nowFn = () => Date.now(), logger = new logging_1.NoopLogger()) {
183
+ gatingPolicy;
184
+ constructor(iTermProbe = new Iterm2TerminalStateProbe(undefined, {}, new logging_1.NoopLogger(), DEFAULT_GATING_POLICY), tmuxProbe = new TmuxTerminalStateProbe(undefined, {}, new logging_1.NoopLogger(), DEFAULT_GATING_POLICY), sessionFileReader, statReader, nowFn = () => Date.now(), logger = new logging_1.NoopLogger(), gatingPolicy = DEFAULT_GATING_POLICY) {
182
185
  this.iTermProbe = iTermProbe;
183
186
  this.tmuxProbe = tmuxProbe;
184
187
  this.sessionFileReader = sessionFileReader;
185
188
  this.statReader = statReader;
186
189
  this.nowFn = nowFn;
187
190
  this.logger = logger;
191
+ this.gatingPolicy = gatingPolicy;
188
192
  }
189
193
  supports(target) {
190
194
  return target.runtimeKind === "codex";
@@ -269,10 +273,12 @@ class Iterm2TerminalStateProbe {
269
273
  execAsync;
270
274
  options;
271
275
  logger;
272
- constructor(execAsync = execFileAsync, options = {}, logger = new logging_1.NoopLogger()) {
276
+ gatingPolicy;
277
+ constructor(execAsync = execFileAsync, options = {}, logger = new logging_1.NoopLogger(), gatingPolicy = DEFAULT_GATING_POLICY) {
273
278
  this.execAsync = execAsync;
274
279
  this.options = options;
275
280
  this.logger = logger;
281
+ this.gatingPolicy = gatingPolicy;
276
282
  }
277
283
  supports(target) {
278
284
  // Only support iTerm2 targets that are NOT Claude Code
@@ -337,7 +343,7 @@ class Iterm2TerminalStateProbe {
337
343
  if (data.status === "available") {
338
344
  // If we have cursor and lines data, use cursor-aware detection
339
345
  if (data.cursor && Array.isArray(data.lines)) {
340
- const bufferTail = normalizeProbeTailFromLines(data.lines);
346
+ const bufferTail = normalizeProbeTailFromLines(data.lines, this.gatingPolicy.normalizedBufferTailLines);
341
347
  if (!bufferTail) {
342
348
  this.logger.trace("probe_python_result", {
343
349
  sessionId,
@@ -363,13 +369,13 @@ class Iterm2TerminalStateProbe {
363
369
  // If cursor-aware detection is certain it's not busy, don't apply prompt heuristics
364
370
  if (busyStatus === "not_busy") {
365
371
  // Still check for buffer changes and active markers
366
- await (this.options.sleep ?? sleep)(this.options.sampleDelayMs ?? DEFAULT_ITERM2_SAMPLE_DELAY_MS);
372
+ await (this.options.sleep ?? sleep)(this.options.sampleDelayMs ?? this.gatingPolicy.iTerm2SampleDelayMs);
367
373
  const secondResult = await this.execAsync("python3", [scriptPath, sessionId], {
368
374
  env: { ...process.env, PYTHONIOENCODING: "utf-8" }
369
375
  });
370
376
  const secondData = JSON.parse(secondResult.stdout);
371
377
  if (secondData.status === "available" && Array.isArray(secondData.lines)) {
372
- const secondBufferTail = normalizeProbeTailFromLines(secondData.lines);
378
+ const secondBufferTail = normalizeProbeTailFromLines(secondData.lines, this.gatingPolicy.normalizedBufferTailLines);
373
379
  if (secondBufferTail && bufferTail !== secondBufferTail) {
374
380
  this.logger.trace("probe_python_result", {
375
381
  sessionId,
@@ -380,7 +386,7 @@ class Iterm2TerminalStateProbe {
380
386
  }
381
387
  }
382
388
  // Check for active buffer markers
383
- if (containsActiveBufferMarker(bufferTail)) {
389
+ if (containsActiveBufferMarker(bufferTail, this.gatingPolicy.activeBufferMarkers)) {
384
390
  this.logger.trace("probe_python_result", {
385
391
  sessionId,
386
392
  result: { presence: "available", busy: "busy" },
@@ -398,13 +404,13 @@ class Iterm2TerminalStateProbe {
398
404
  // If cursor-aware detection is uncertain, fall back to buffer change detection only
399
405
  // Do NOT use generic typing prompts to avoid false positives (see #116)
400
406
  if (busyStatus === "unknown") {
401
- await (this.options.sleep ?? sleep)(this.options.sampleDelayMs ?? DEFAULT_ITERM2_SAMPLE_DELAY_MS);
407
+ await (this.options.sleep ?? sleep)(this.options.sampleDelayMs ?? this.gatingPolicy.iTerm2SampleDelayMs);
402
408
  const secondResult = await this.execAsync("python3", [scriptPath, sessionId], {
403
409
  env: { ...process.env, PYTHONIOENCODING: "utf-8" }
404
410
  });
405
411
  const secondData = JSON.parse(secondResult.stdout);
406
412
  if (secondData.status === "available" && Array.isArray(secondData.lines)) {
407
- const secondBufferTail = normalizeProbeTailFromLines(secondData.lines);
413
+ const secondBufferTail = normalizeProbeTailFromLines(secondData.lines, this.gatingPolicy.normalizedBufferTailLines);
408
414
  if (secondBufferTail && bufferTail !== secondBufferTail) {
409
415
  this.logger.trace("probe_python_result", {
410
416
  sessionId,
@@ -415,7 +421,7 @@ class Iterm2TerminalStateProbe {
415
421
  }
416
422
  }
417
423
  // Check for active buffer markers
418
- if (containsActiveBufferMarker(bufferTail)) {
424
+ if (containsActiveBufferMarker(bufferTail, this.gatingPolicy.activeBufferMarkers)) {
419
425
  this.logger.trace("probe_python_result", {
420
426
  sessionId,
421
427
  result: { presence: "available", busy: "busy" },
@@ -493,7 +499,7 @@ class Iterm2TerminalStateProbe {
493
499
  if (!first) {
494
500
  return { presence: "available", busy: "unknown" };
495
501
  }
496
- await (this.options.sleep ?? sleep)(this.options.sampleDelayMs ?? DEFAULT_ITERM2_SAMPLE_DELAY_MS);
502
+ await (this.options.sleep ?? sleep)(this.options.sampleDelayMs ?? this.gatingPolicy.iTerm2SampleDelayMs);
497
503
  const second = await this.readBufferTail(it2api, sessionId);
498
504
  if (!second) {
499
505
  return { presence: "available", busy: "unknown" };
@@ -506,7 +512,7 @@ class Iterm2TerminalStateProbe {
506
512
  });
507
513
  return { presence: "available", busy: "busy" };
508
514
  }
509
- if (containsActiveBufferMarker(second)) {
515
+ if (containsActiveBufferMarker(second, this.gatingPolicy.activeBufferMarkers)) {
510
516
  this.logger.trace("probe_cli_result", {
511
517
  sessionId,
512
518
  result: { presence: "available", busy: "busy" },
@@ -549,7 +555,7 @@ class Iterm2TerminalStateProbe {
549
555
  async readBufferTail(it2api, sessionId) {
550
556
  try {
551
557
  const result = await this.execAsync(it2api, ["get-buffer", sessionId]);
552
- return normalizeBufferTail(result.stdout);
558
+ return normalizeBufferTail(result.stdout, this.gatingPolicy.normalizedBufferTailLines);
553
559
  }
554
560
  catch {
555
561
  return null;
@@ -561,10 +567,12 @@ class TmuxTerminalStateProbe {
561
567
  execAsync;
562
568
  options;
563
569
  logger;
564
- constructor(execAsync = execFileAsync, options = {}, logger = new logging_1.NoopLogger()) {
570
+ gatingPolicy;
571
+ constructor(execAsync = execFileAsync, options = {}, logger = new logging_1.NoopLogger(), gatingPolicy = DEFAULT_GATING_POLICY) {
565
572
  this.execAsync = execAsync;
566
573
  this.options = options;
567
574
  this.logger = logger;
575
+ this.gatingPolicy = gatingPolicy;
568
576
  }
569
577
  supports(target) {
570
578
  return target.backend === "tmux" && typeof target.tmuxPaneId === "string" && target.tmuxPaneId.trim().length > 0;
@@ -611,7 +619,7 @@ class TmuxTerminalStateProbe {
611
619
  if (!first) {
612
620
  return { presence: "available", busy: "unknown" };
613
621
  }
614
- await (this.options.sleep ?? sleep)(this.options.sampleDelayMs ?? DEFAULT_TMUX_SAMPLE_DELAY_MS);
622
+ await (this.options.sleep ?? sleep)(this.options.sampleDelayMs ?? this.gatingPolicy.tmuxSampleDelayMs);
615
623
  const second = await this.readBufferTail(paneId);
616
624
  if (!second) {
617
625
  return { presence: "available", busy: "unknown" };
@@ -625,7 +633,7 @@ class TmuxTerminalStateProbe {
625
633
  });
626
634
  return { presence: "available", busy: "busy" };
627
635
  }
628
- if (containsActiveBufferMarker(second)) {
636
+ if (containsActiveBufferMarker(second, this.gatingPolicy.activeBufferMarkers)) {
629
637
  this.logger.trace("probe_result", {
630
638
  targetId: target.targetId,
631
639
  paneId,
@@ -684,8 +692,8 @@ class TmuxTerminalStateProbe {
684
692
  }
685
693
  async readBufferTail(paneId) {
686
694
  try {
687
- const result = await this.execAsync("tmux", ["capture-pane", "-p", "-t", paneId, "-S", `-${NORMALIZED_BUFFER_TAIL_LINES}`]);
688
- return normalizeBufferTail(result.stdout);
695
+ const result = await this.execAsync("tmux", ["capture-pane", "-p", "-t", paneId, "-S", `-${this.gatingPolicy.normalizedBufferTailLines}`]);
696
+ return normalizeBufferTail(result.stdout, this.gatingPolicy.normalizedBufferTailLines);
689
697
  }
690
698
  catch {
691
699
  return null;
@@ -693,10 +701,10 @@ class TmuxTerminalStateProbe {
693
701
  }
694
702
  }
695
703
  exports.TmuxTerminalStateProbe = TmuxTerminalStateProbe;
696
- function containsActiveBufferMarker(bufferTail) {
697
- return ACTIVE_BUFFER_MARKERS.some((marker) => bufferTail.includes(marker));
704
+ function containsActiveBufferMarker(bufferTail, markers = DEFAULT_GATING_POLICY.activeBufferMarkers) {
705
+ return markers.some((marker) => bufferTail.includes(marker));
698
706
  }
699
- function hasVisibleTypingPrompt(bufferTail, prefixes = TYPING_PROMPT_PREFIXES) {
707
+ function hasVisibleTypingPrompt(bufferTail, prefixes = DEFAULT_GATING_POLICY.typingPromptPrefixes) {
700
708
  const lines = bufferTail
701
709
  .split("\n")
702
710
  .map((line) => line.trimEnd())
@@ -754,7 +762,7 @@ function typingPromptPrefixesForRuntime(runtimeKind) {
754
762
  if (runtimeKind === "claude_code") {
755
763
  return ["❯ ", "› "];
756
764
  }
757
- return TYPING_PROMPT_PREFIXES;
765
+ return DEFAULT_GATING_POLICY.typingPromptPrefixes;
758
766
  }
759
767
  function parseOptionalInt(value) {
760
768
  if (!value) {
@@ -763,18 +771,18 @@ function parseOptionalInt(value) {
763
771
  const parsed = Number.parseInt(value, 10);
764
772
  return Number.isFinite(parsed) ? parsed : null;
765
773
  }
766
- function normalizeBufferTail(buffer) {
774
+ function normalizeBufferTail(buffer, tailLines = DEFAULT_GATING_POLICY.normalizedBufferTailLines) {
767
775
  const normalized = buffer
768
776
  .replace(/\r/g, "")
769
777
  .split("\n")
770
778
  .map((line) => line.trimEnd())
771
- .slice(-NORMALIZED_BUFFER_TAIL_LINES)
779
+ .slice(-tailLines)
772
780
  .join("\n")
773
781
  .trim();
774
782
  return normalized.length > 0 ? normalized : null;
775
783
  }
776
- function normalizeProbeTailFromLines(lines) {
777
- return normalizeBufferTail(lines.join("\n"));
784
+ function normalizeProbeTailFromLines(lines, tailLines = DEFAULT_GATING_POLICY.normalizedBufferTailLines) {
785
+ return normalizeBufferTail(lines.join("\n"), tailLines);
778
786
  }
779
787
  function resolveIterm2ApiPath(override) {
780
788
  if (override) {