pullfrog 0.1.19 → 0.1.21

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/cli.mjs CHANGED
@@ -101189,11 +101189,12 @@ var modelAliases = Object.entries(providers).flatMap(
101189
101189
  hidden: def.hidden ?? false
101190
101190
  }))
101191
101191
  );
101192
- var defaultProxyAlias = modelAliases.find((a) => a.slug === "moonshotai/kimi-k2");
101192
+ var defaultProxyAlias = modelAliases.find((a) => a.slug === "deepseek/deepseek-pro");
101193
101193
  if (!defaultProxyAlias?.openRouterResolve) {
101194
- throw new Error("DEFAULT_PROXY_MODEL: moonshotai/kimi-k2 missing openRouterResolve");
101194
+ throw new Error("DEFAULT_PROXY_MODEL: deepseek/deepseek-pro missing openRouterResolve");
101195
101195
  }
101196
101196
  var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
101197
+ var defaultProxyDisplayName = defaultProxyAlias.displayName;
101197
101198
  var MAX_FALLBACK_DEPTH = 10;
101198
101199
  function resolveDisplayAlias(slug2) {
101199
101200
  let current = slug2;
@@ -101235,6 +101236,111 @@ function formatMcpToolRef(agentId, toolName) {
101235
101236
 
101236
101237
  // utils/activity.ts
101237
101238
  import { performance as performance2 } from "node:perf_hooks";
101239
+ function isMonitorDebugEnabled() {
101240
+ return process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || process.env.LOG_LEVEL === "debug";
101241
+ }
101242
+ var DEFAULT_ACTIVITY_TIMEOUT_MS = 3e5;
101243
+ var AGENT_ACTIVITY_TIMEOUT_MS = 9e5;
101244
+ var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
101245
+ var DEBUG_TS_PREFIX = /^(?:\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+)?/.source;
101246
+ var ACTIVITY_NOISE_PATTERNS = [
101247
+ new RegExp(`${DEBUG_TS_PREFIX}\\[mcp-proxy\\]`),
101248
+ new RegExp(`${DEBUG_TS_PREFIX}\xBB provider error detected`),
101249
+ new RegExp(`${DEBUG_TS_PREFIX}\\[DEBUG\\]\\s+(?:spawn|process) activity `),
101250
+ /^::debug::(?:spawn|process) activity /
101251
+ ];
101252
+ function isActivityNoise(chunk) {
101253
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
101254
+ if (!text.trim()) return true;
101255
+ return text.split("\n").every((line) => {
101256
+ const trimmed = line.trim();
101257
+ if (!trimmed) return true;
101258
+ return ACTIVITY_NOISE_PATTERNS.some((pattern) => pattern.test(trimmed));
101259
+ });
101260
+ }
101261
+ var _lastActivity = performance2.now();
101262
+ function markActivity() {
101263
+ _lastActivity = performance2.now();
101264
+ }
101265
+ function getIdleMs() {
101266
+ return Math.round(performance2.now() - _lastActivity);
101267
+ }
101268
+ function wrapWrite(original, onActivity) {
101269
+ const wrapped = (chunk, encodingOrCb, cb) => {
101270
+ if (!isActivityNoise(chunk)) {
101271
+ onActivity();
101272
+ }
101273
+ if (typeof encodingOrCb === "function") {
101274
+ return original(chunk, encodingOrCb);
101275
+ }
101276
+ return original(chunk, encodingOrCb, cb);
101277
+ };
101278
+ return wrapped;
101279
+ }
101280
+ function startProcessOutputMonitor(ctx) {
101281
+ let timedOut = false;
101282
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
101283
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
101284
+ process.stdout.write = wrapWrite(originalStdoutWrite, markActivity);
101285
+ process.stderr.write = wrapWrite(originalStderrWrite, markActivity);
101286
+ const debugBypass = (msg) => {
101287
+ if (!isMonitorDebugEnabled()) return;
101288
+ originalStdoutWrite(`[${(/* @__PURE__ */ new Date()).toISOString()}] [DEBUG] ${msg}
101289
+ `);
101290
+ };
101291
+ debugBypass(`process activity monitor started: timeout=${ctx.timeoutMs}ms`);
101292
+ const intervalId = setInterval(() => {
101293
+ const idleMs = getIdleMs();
101294
+ debugBypass(`process activity check: idle=${idleMs}ms / ${ctx.timeoutMs}ms`);
101295
+ if (timedOut || idleMs <= ctx.timeoutMs) return;
101296
+ timedOut = true;
101297
+ ctx.onTimeout(idleMs);
101298
+ }, ctx.checkIntervalMs);
101299
+ function stop() {
101300
+ clearInterval(intervalId);
101301
+ process.stdout.write = originalStdoutWrite;
101302
+ process.stderr.write = originalStderrWrite;
101303
+ }
101304
+ return { stop };
101305
+ }
101306
+ function createProcessOutputActivityTimeout(ctx) {
101307
+ markActivity();
101308
+ let rejectFn = null;
101309
+ const promise2 = new Promise((_2, reject) => {
101310
+ rejectFn = reject;
101311
+ });
101312
+ let monitor = null;
101313
+ monitor = startProcessOutputMonitor({
101314
+ timeoutMs: ctx.timeoutMs,
101315
+ checkIntervalMs: ctx.checkIntervalMs,
101316
+ onTimeout: (idleMs) => {
101317
+ if (!rejectFn) return;
101318
+ const idleSec = Math.round(idleMs / 1e3);
101319
+ if (monitor) {
101320
+ monitor.stop();
101321
+ }
101322
+ const reject = rejectFn;
101323
+ rejectFn = null;
101324
+ reject(new Error(`activity timeout: no output for ${idleSec}s`));
101325
+ }
101326
+ });
101327
+ return {
101328
+ promise: promise2,
101329
+ // stop() also disarms forceReject so a late safety-net fire can't reject
101330
+ // the promise after the run has already succeeded.
101331
+ stop: () => {
101332
+ monitor?.stop();
101333
+ rejectFn = null;
101334
+ },
101335
+ forceReject: (reason) => {
101336
+ if (!rejectFn) return;
101337
+ monitor?.stop();
101338
+ const reject = rejectFn;
101339
+ rejectFn = null;
101340
+ reject(new Error(reason));
101341
+ }
101342
+ };
101343
+ }
101238
101344
 
101239
101345
  // utils/log.ts
101240
101346
  var core = __toESM(require_core(), 1);
@@ -101550,137 +101656,6 @@ function formatUsageSummary(entries) {
101550
101656
  ].join("\n");
101551
101657
  }
101552
101658
 
101553
- // utils/activity.ts
101554
- function isMonitorDebugEnabled() {
101555
- return process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || process.env.LOG_LEVEL === "debug";
101556
- }
101557
- var DEFAULT_ACTIVITY_TIMEOUT_MS = 3e5;
101558
- var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
101559
- var DEBUG_TS_PREFIX = /^(?:\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+)?/.source;
101560
- var ACTIVITY_NOISE_PATTERNS = [
101561
- new RegExp(`${DEBUG_TS_PREFIX}\\[mcp-proxy\\]`),
101562
- new RegExp(`${DEBUG_TS_PREFIX}\xBB provider error detected`),
101563
- new RegExp(`${DEBUG_TS_PREFIX}\\[DEBUG\\]\\s+(?:spawn|process) activity `),
101564
- /^::debug::(?:spawn|process) activity /
101565
- ];
101566
- function isActivityNoise(chunk) {
101567
- const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
101568
- if (!text.trim()) return true;
101569
- return text.split("\n").every((line) => {
101570
- const trimmed = line.trim();
101571
- if (!trimmed) return true;
101572
- return ACTIVITY_NOISE_PATTERNS.some((pattern) => pattern.test(trimmed));
101573
- });
101574
- }
101575
- var _lastActivity = performance2.now();
101576
- var MAX_TOOL_CALL_SUSPENSION_MS = 15 * 60 * 1e3;
101577
- var _suspendedAt = null;
101578
- var _suspensionTimer = null;
101579
- function markActivity() {
101580
- _lastActivity = performance2.now();
101581
- }
101582
- function getIdleMs() {
101583
- if (_suspendedAt !== null) return 0;
101584
- return Math.round(performance2.now() - _lastActivity);
101585
- }
101586
- function suspendActivity(maxMs = MAX_TOOL_CALL_SUSPENSION_MS) {
101587
- if (_suspendedAt !== null) return;
101588
- _suspendedAt = performance2.now();
101589
- _suspensionTimer = setTimeout(() => {
101590
- log.warning(`activity watchdog suspended >${Math.round(maxMs / 1e3)}s \u2014 auto-resuming`);
101591
- resumeActivity();
101592
- }, maxMs);
101593
- _suspensionTimer.unref?.();
101594
- }
101595
- function resumeActivity() {
101596
- if (_suspendedAt === null) return;
101597
- _suspendedAt = null;
101598
- if (_suspensionTimer) {
101599
- clearTimeout(_suspensionTimer);
101600
- _suspensionTimer = null;
101601
- }
101602
- _lastActivity = performance2.now();
101603
- }
101604
- function isActivitySuspended() {
101605
- return _suspendedAt !== null;
101606
- }
101607
- function wrapWrite(original, onActivity) {
101608
- const wrapped = (chunk, encodingOrCb, cb) => {
101609
- if (!isActivityNoise(chunk)) {
101610
- onActivity();
101611
- }
101612
- if (typeof encodingOrCb === "function") {
101613
- return original(chunk, encodingOrCb);
101614
- }
101615
- return original(chunk, encodingOrCb, cb);
101616
- };
101617
- return wrapped;
101618
- }
101619
- function startProcessOutputMonitor(ctx) {
101620
- let timedOut = false;
101621
- const originalStdoutWrite = process.stdout.write.bind(process.stdout);
101622
- const originalStderrWrite = process.stderr.write.bind(process.stderr);
101623
- process.stdout.write = wrapWrite(originalStdoutWrite, markActivity);
101624
- process.stderr.write = wrapWrite(originalStderrWrite, markActivity);
101625
- const debugBypass = (msg) => {
101626
- if (!isMonitorDebugEnabled()) return;
101627
- originalStdoutWrite(`[${(/* @__PURE__ */ new Date()).toISOString()}] [DEBUG] ${msg}
101628
- `);
101629
- };
101630
- debugBypass(`process activity monitor started: timeout=${ctx.timeoutMs}ms`);
101631
- const intervalId = setInterval(() => {
101632
- const idleMs = getIdleMs();
101633
- debugBypass(`process activity check: idle=${idleMs}ms / ${ctx.timeoutMs}ms`);
101634
- if (timedOut || idleMs <= ctx.timeoutMs) return;
101635
- timedOut = true;
101636
- ctx.onTimeout(idleMs);
101637
- }, ctx.checkIntervalMs);
101638
- function stop() {
101639
- clearInterval(intervalId);
101640
- process.stdout.write = originalStdoutWrite;
101641
- process.stderr.write = originalStderrWrite;
101642
- }
101643
- return { stop };
101644
- }
101645
- function createProcessOutputActivityTimeout(ctx) {
101646
- markActivity();
101647
- let rejectFn = null;
101648
- const promise2 = new Promise((_2, reject) => {
101649
- rejectFn = reject;
101650
- });
101651
- let monitor = null;
101652
- monitor = startProcessOutputMonitor({
101653
- timeoutMs: ctx.timeoutMs,
101654
- checkIntervalMs: ctx.checkIntervalMs,
101655
- onTimeout: (idleMs) => {
101656
- if (!rejectFn) return;
101657
- const idleSec = Math.round(idleMs / 1e3);
101658
- if (monitor) {
101659
- monitor.stop();
101660
- }
101661
- const reject = rejectFn;
101662
- rejectFn = null;
101663
- reject(new Error(`activity timeout: no output for ${idleSec}s`));
101664
- }
101665
- });
101666
- return {
101667
- promise: promise2,
101668
- // stop() also disarms forceReject so a late safety-net fire can't reject
101669
- // the promise after the run has already succeeded.
101670
- stop: () => {
101671
- monitor?.stop();
101672
- rejectFn = null;
101673
- },
101674
- forceReject: (reason) => {
101675
- if (!rejectFn) return;
101676
- monitor?.stop();
101677
- const reject = rejectFn;
101678
- rejectFn = null;
101679
- reject(new Error(reason));
101680
- }
101681
- };
101682
- }
101683
-
101684
101659
  // utils/install.ts
101685
101660
  import { spawnSync } from "node:child_process";
101686
101661
  import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync } from "node:fs";
@@ -101880,7 +101855,7 @@ var import_semver = __toESM(require_semver2(), 1);
101880
101855
  // package.json
101881
101856
  var package_default = {
101882
101857
  name: "pullfrog",
101883
- version: "0.1.19",
101858
+ version: "0.1.21",
101884
101859
  type: "module",
101885
101860
  bin: {
101886
101861
  pullfrog: "dist/cli.mjs",
@@ -102218,11 +102193,6 @@ async function spawn3(options) {
102218
102193
  `spawn activity timer: pid=${child.pid} cmd=${options.cmd} timeout=${activityTimeoutMs}ms`
102219
102194
  );
102220
102195
  activityCheckIntervalId = setInterval(() => {
102221
- if (options.isPausedExternally?.()) {
102222
- lastActivityTime = performance3.now();
102223
- log.debug(`spawn activity check: pid=${child.pid} paused externally`);
102224
- return;
102225
- }
102226
102196
  const idleMs = performance3.now() - lastActivityTime;
102227
102197
  log.debug(
102228
102198
  `spawn activity check: pid=${child.pid} idle=${Math.round(idleMs)}ms / ${activityTimeoutMs}ms`
@@ -103690,7 +103660,6 @@ async function runClaude(params) {
103690
103660
  }
103691
103661
  } else if (block.type === "tool_use") {
103692
103662
  const toolName = block.name || "unknown";
103693
- suspendActivity();
103694
103663
  if (params.onToolUse) {
103695
103664
  params.onToolUse({
103696
103665
  toolName,
@@ -103735,7 +103704,6 @@ async function runClaude(params) {
103735
103704
  for (const block of content) {
103736
103705
  if (typeof block === "string") continue;
103737
103706
  if (block.type === "tool_result") {
103738
- resumeActivity();
103739
103707
  timerFor(label).markToolResult();
103740
103708
  const outputContent = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map(
103741
103709
  (entry) => typeof entry === "string" ? entry : typeof entry === "object" && entry !== null && "text" in entry ? String(entry.text) : JSON.stringify(entry)
@@ -103822,9 +103790,10 @@ async function runClaude(params) {
103822
103790
  args: params.args,
103823
103791
  cwd: params.cwd,
103824
103792
  env: params.env,
103825
- activityTimeout: 3e5,
103793
+ // flat agent idle budget — long synchronous MCP tool calls (issue #760)
103794
+ // sit well under it, so no per-toolcall suspend bracketing is needed.
103795
+ activityTimeout: AGENT_ACTIVITY_TIMEOUT_MS,
103826
103796
  onActivityTimeout: params.onActivityTimeout,
103827
- isPausedExternally: isActivitySuspended,
103828
103797
  stdio: ["ignore", "pipe", "pipe"],
103829
103798
  // run claude in its own process group so SIGKILL on activity timeout /
103830
103799
  // outer cancellation reaches any subprocesses it spawns (rg, file
@@ -108179,13 +108148,6 @@ async function onToolPart(ctx, part, label, isOrchestrator) {
108179
108148
  const status = part.state.status;
108180
108149
  const toolName = part.tool;
108181
108150
  const toolId = part.callID;
108182
- if (toolName !== "task") {
108183
- if (status === "completed" || status === "error") {
108184
- resumeActivity();
108185
- } else {
108186
- suspendActivity();
108187
- }
108188
- }
108189
108151
  if (toolName === "task" && status === "running" && isOrchestrator && !ctx.taskDispatchByCallID.has(toolId)) {
108190
108152
  const input = part.state.input ?? {};
108191
108153
  const dispatched = ctx.labeler.recordTaskDispatch(input);
@@ -108433,7 +108395,6 @@ function startInnerActivityWatchdog(params) {
108433
108395
  let fired = false;
108434
108396
  const id = setInterval(() => {
108435
108397
  if (fired) return;
108436
- if (isActivitySuspended()) return;
108437
108398
  const idleMs = performance6.now() - params.ctx.lastEventAt;
108438
108399
  if (idleMs <= params.timeoutMs) return;
108439
108400
  fired = true;
@@ -108589,13 +108550,13 @@ var opencode = agent({
108589
108550
  const watchdog = startInnerActivityWatchdog({
108590
108551
  ctx: runnerCtx,
108591
108552
  // model-stall budget: how long the orchestrator may stream NO progress
108592
- // (no token/tool part.updated) before we tear the turn down. tool
108593
- // execution is excluded via suspend/resumeActivity, so this only bounds
108594
- // a genuinely silent provider. 120s comfortably exceeds normal inter-
108595
- // token gaps (incl. extended-thinking pauses) while catching the
108596
- // observed stalls (model goes quiet ~3-4min in) far sooner than the old
108597
- // 300s undici cap that this fix removes.
108598
- timeoutMs: 12e4,
108553
+ // (no token/tool part.updated) before we tear the turn down. opencode's
108554
+ // keepalive/lifecycle events keep the outer process-output monitor
108555
+ // alive even while the model is silent, so this inner timer is the only
108556
+ // stall detector for the v2 SSE path. it shares the flat idle budget so
108557
+ // a long synchronous tool call (no part.updated while it runs) can't
108558
+ // false-positive it.
108559
+ timeoutMs: AGENT_ACTIVITY_TIMEOUT_MS,
108599
108560
  abortController
108600
108561
  });
108601
108562
  const sdkModel = parseModel2(model);
@@ -161974,7 +161935,7 @@ ${instructions.user}` : null,
161974
161935
  }
161975
161936
  }
161976
161937
  activityTimeout = createProcessOutputActivityTimeout({
161977
- timeoutMs: DEFAULT_ACTIVITY_TIMEOUT_MS,
161938
+ timeoutMs: AGENT_ACTIVITY_TIMEOUT_MS,
161978
161939
  checkIntervalMs: DEFAULT_ACTIVITY_CHECK_INTERVAL_MS
161979
161940
  });
161980
161941
  activityTimeout.promise.catch(() => {
@@ -163019,7 +162980,7 @@ async function run2() {
163019
162980
  }
163020
162981
 
163021
162982
  // cli.ts
163022
- var VERSION10 = "0.1.19";
162983
+ var VERSION10 = "0.1.21";
163023
162984
  var bin = basename2(process.argv[1] || "");
163024
162985
  var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
163025
162986
  var rawArgs = process.argv.slice(2);
package/dist/index.js CHANGED
@@ -99389,11 +99389,12 @@ var modelAliases = Object.entries(providers).flatMap(
99389
99389
  hidden: def.hidden ?? false
99390
99390
  }))
99391
99391
  );
99392
- var defaultProxyAlias = modelAliases.find((a) => a.slug === "moonshotai/kimi-k2");
99392
+ var defaultProxyAlias = modelAliases.find((a) => a.slug === "deepseek/deepseek-pro");
99393
99393
  if (!defaultProxyAlias?.openRouterResolve) {
99394
- throw new Error("DEFAULT_PROXY_MODEL: moonshotai/kimi-k2 missing openRouterResolve");
99394
+ throw new Error("DEFAULT_PROXY_MODEL: deepseek/deepseek-pro missing openRouterResolve");
99395
99395
  }
99396
99396
  var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
99397
+ var defaultProxyDisplayName = defaultProxyAlias.displayName;
99397
99398
  var MAX_FALLBACK_DEPTH = 10;
99398
99399
  function resolveDisplayAlias(slug2) {
99399
99400
  let current = slug2;
@@ -99435,6 +99436,111 @@ function formatMcpToolRef(agentId, toolName) {
99435
99436
 
99436
99437
  // utils/activity.ts
99437
99438
  import { performance as performance2 } from "node:perf_hooks";
99439
+ function isMonitorDebugEnabled() {
99440
+ return process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || process.env.LOG_LEVEL === "debug";
99441
+ }
99442
+ var DEFAULT_ACTIVITY_TIMEOUT_MS = 3e5;
99443
+ var AGENT_ACTIVITY_TIMEOUT_MS = 9e5;
99444
+ var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
99445
+ var DEBUG_TS_PREFIX = /^(?:\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+)?/.source;
99446
+ var ACTIVITY_NOISE_PATTERNS = [
99447
+ new RegExp(`${DEBUG_TS_PREFIX}\\[mcp-proxy\\]`),
99448
+ new RegExp(`${DEBUG_TS_PREFIX}\xBB provider error detected`),
99449
+ new RegExp(`${DEBUG_TS_PREFIX}\\[DEBUG\\]\\s+(?:spawn|process) activity `),
99450
+ /^::debug::(?:spawn|process) activity /
99451
+ ];
99452
+ function isActivityNoise(chunk) {
99453
+ const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
99454
+ if (!text.trim()) return true;
99455
+ return text.split("\n").every((line) => {
99456
+ const trimmed = line.trim();
99457
+ if (!trimmed) return true;
99458
+ return ACTIVITY_NOISE_PATTERNS.some((pattern) => pattern.test(trimmed));
99459
+ });
99460
+ }
99461
+ var _lastActivity = performance2.now();
99462
+ function markActivity() {
99463
+ _lastActivity = performance2.now();
99464
+ }
99465
+ function getIdleMs() {
99466
+ return Math.round(performance2.now() - _lastActivity);
99467
+ }
99468
+ function wrapWrite(original, onActivity) {
99469
+ const wrapped = (chunk, encodingOrCb, cb) => {
99470
+ if (!isActivityNoise(chunk)) {
99471
+ onActivity();
99472
+ }
99473
+ if (typeof encodingOrCb === "function") {
99474
+ return original(chunk, encodingOrCb);
99475
+ }
99476
+ return original(chunk, encodingOrCb, cb);
99477
+ };
99478
+ return wrapped;
99479
+ }
99480
+ function startProcessOutputMonitor(ctx) {
99481
+ let timedOut = false;
99482
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
99483
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
99484
+ process.stdout.write = wrapWrite(originalStdoutWrite, markActivity);
99485
+ process.stderr.write = wrapWrite(originalStderrWrite, markActivity);
99486
+ const debugBypass = (msg) => {
99487
+ if (!isMonitorDebugEnabled()) return;
99488
+ originalStdoutWrite(`[${(/* @__PURE__ */ new Date()).toISOString()}] [DEBUG] ${msg}
99489
+ `);
99490
+ };
99491
+ debugBypass(`process activity monitor started: timeout=${ctx.timeoutMs}ms`);
99492
+ const intervalId = setInterval(() => {
99493
+ const idleMs = getIdleMs();
99494
+ debugBypass(`process activity check: idle=${idleMs}ms / ${ctx.timeoutMs}ms`);
99495
+ if (timedOut || idleMs <= ctx.timeoutMs) return;
99496
+ timedOut = true;
99497
+ ctx.onTimeout(idleMs);
99498
+ }, ctx.checkIntervalMs);
99499
+ function stop() {
99500
+ clearInterval(intervalId);
99501
+ process.stdout.write = originalStdoutWrite;
99502
+ process.stderr.write = originalStderrWrite;
99503
+ }
99504
+ return { stop };
99505
+ }
99506
+ function createProcessOutputActivityTimeout(ctx) {
99507
+ markActivity();
99508
+ let rejectFn = null;
99509
+ const promise2 = new Promise((_, reject) => {
99510
+ rejectFn = reject;
99511
+ });
99512
+ let monitor = null;
99513
+ monitor = startProcessOutputMonitor({
99514
+ timeoutMs: ctx.timeoutMs,
99515
+ checkIntervalMs: ctx.checkIntervalMs,
99516
+ onTimeout: (idleMs) => {
99517
+ if (!rejectFn) return;
99518
+ const idleSec = Math.round(idleMs / 1e3);
99519
+ if (monitor) {
99520
+ monitor.stop();
99521
+ }
99522
+ const reject = rejectFn;
99523
+ rejectFn = null;
99524
+ reject(new Error(`activity timeout: no output for ${idleSec}s`));
99525
+ }
99526
+ });
99527
+ return {
99528
+ promise: promise2,
99529
+ // stop() also disarms forceReject so a late safety-net fire can't reject
99530
+ // the promise after the run has already succeeded.
99531
+ stop: () => {
99532
+ monitor?.stop();
99533
+ rejectFn = null;
99534
+ },
99535
+ forceReject: (reason) => {
99536
+ if (!rejectFn) return;
99537
+ monitor?.stop();
99538
+ const reject = rejectFn;
99539
+ rejectFn = null;
99540
+ reject(new Error(reason));
99541
+ }
99542
+ };
99543
+ }
99438
99544
 
99439
99545
  // utils/log.ts
99440
99546
  var core = __toESM(require_core(), 1);
@@ -99750,137 +99856,6 @@ function formatUsageSummary(entries) {
99750
99856
  ].join("\n");
99751
99857
  }
99752
99858
 
99753
- // utils/activity.ts
99754
- function isMonitorDebugEnabled() {
99755
- return process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || process.env.LOG_LEVEL === "debug";
99756
- }
99757
- var DEFAULT_ACTIVITY_TIMEOUT_MS = 3e5;
99758
- var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
99759
- var DEBUG_TS_PREFIX = /^(?:\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+)?/.source;
99760
- var ACTIVITY_NOISE_PATTERNS = [
99761
- new RegExp(`${DEBUG_TS_PREFIX}\\[mcp-proxy\\]`),
99762
- new RegExp(`${DEBUG_TS_PREFIX}\xBB provider error detected`),
99763
- new RegExp(`${DEBUG_TS_PREFIX}\\[DEBUG\\]\\s+(?:spawn|process) activity `),
99764
- /^::debug::(?:spawn|process) activity /
99765
- ];
99766
- function isActivityNoise(chunk) {
99767
- const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
99768
- if (!text.trim()) return true;
99769
- return text.split("\n").every((line) => {
99770
- const trimmed = line.trim();
99771
- if (!trimmed) return true;
99772
- return ACTIVITY_NOISE_PATTERNS.some((pattern) => pattern.test(trimmed));
99773
- });
99774
- }
99775
- var _lastActivity = performance2.now();
99776
- var MAX_TOOL_CALL_SUSPENSION_MS = 15 * 60 * 1e3;
99777
- var _suspendedAt = null;
99778
- var _suspensionTimer = null;
99779
- function markActivity() {
99780
- _lastActivity = performance2.now();
99781
- }
99782
- function getIdleMs() {
99783
- if (_suspendedAt !== null) return 0;
99784
- return Math.round(performance2.now() - _lastActivity);
99785
- }
99786
- function suspendActivity(maxMs = MAX_TOOL_CALL_SUSPENSION_MS) {
99787
- if (_suspendedAt !== null) return;
99788
- _suspendedAt = performance2.now();
99789
- _suspensionTimer = setTimeout(() => {
99790
- log.warning(`activity watchdog suspended >${Math.round(maxMs / 1e3)}s \u2014 auto-resuming`);
99791
- resumeActivity();
99792
- }, maxMs);
99793
- _suspensionTimer.unref?.();
99794
- }
99795
- function resumeActivity() {
99796
- if (_suspendedAt === null) return;
99797
- _suspendedAt = null;
99798
- if (_suspensionTimer) {
99799
- clearTimeout(_suspensionTimer);
99800
- _suspensionTimer = null;
99801
- }
99802
- _lastActivity = performance2.now();
99803
- }
99804
- function isActivitySuspended() {
99805
- return _suspendedAt !== null;
99806
- }
99807
- function wrapWrite(original, onActivity) {
99808
- const wrapped = (chunk, encodingOrCb, cb) => {
99809
- if (!isActivityNoise(chunk)) {
99810
- onActivity();
99811
- }
99812
- if (typeof encodingOrCb === "function") {
99813
- return original(chunk, encodingOrCb);
99814
- }
99815
- return original(chunk, encodingOrCb, cb);
99816
- };
99817
- return wrapped;
99818
- }
99819
- function startProcessOutputMonitor(ctx) {
99820
- let timedOut = false;
99821
- const originalStdoutWrite = process.stdout.write.bind(process.stdout);
99822
- const originalStderrWrite = process.stderr.write.bind(process.stderr);
99823
- process.stdout.write = wrapWrite(originalStdoutWrite, markActivity);
99824
- process.stderr.write = wrapWrite(originalStderrWrite, markActivity);
99825
- const debugBypass = (msg) => {
99826
- if (!isMonitorDebugEnabled()) return;
99827
- originalStdoutWrite(`[${(/* @__PURE__ */ new Date()).toISOString()}] [DEBUG] ${msg}
99828
- `);
99829
- };
99830
- debugBypass(`process activity monitor started: timeout=${ctx.timeoutMs}ms`);
99831
- const intervalId = setInterval(() => {
99832
- const idleMs = getIdleMs();
99833
- debugBypass(`process activity check: idle=${idleMs}ms / ${ctx.timeoutMs}ms`);
99834
- if (timedOut || idleMs <= ctx.timeoutMs) return;
99835
- timedOut = true;
99836
- ctx.onTimeout(idleMs);
99837
- }, ctx.checkIntervalMs);
99838
- function stop() {
99839
- clearInterval(intervalId);
99840
- process.stdout.write = originalStdoutWrite;
99841
- process.stderr.write = originalStderrWrite;
99842
- }
99843
- return { stop };
99844
- }
99845
- function createProcessOutputActivityTimeout(ctx) {
99846
- markActivity();
99847
- let rejectFn = null;
99848
- const promise2 = new Promise((_, reject) => {
99849
- rejectFn = reject;
99850
- });
99851
- let monitor = null;
99852
- monitor = startProcessOutputMonitor({
99853
- timeoutMs: ctx.timeoutMs,
99854
- checkIntervalMs: ctx.checkIntervalMs,
99855
- onTimeout: (idleMs) => {
99856
- if (!rejectFn) return;
99857
- const idleSec = Math.round(idleMs / 1e3);
99858
- if (monitor) {
99859
- monitor.stop();
99860
- }
99861
- const reject = rejectFn;
99862
- rejectFn = null;
99863
- reject(new Error(`activity timeout: no output for ${idleSec}s`));
99864
- }
99865
- });
99866
- return {
99867
- promise: promise2,
99868
- // stop() also disarms forceReject so a late safety-net fire can't reject
99869
- // the promise after the run has already succeeded.
99870
- stop: () => {
99871
- monitor?.stop();
99872
- rejectFn = null;
99873
- },
99874
- forceReject: (reason) => {
99875
- if (!rejectFn) return;
99876
- monitor?.stop();
99877
- const reject = rejectFn;
99878
- rejectFn = null;
99879
- reject(new Error(reason));
99880
- }
99881
- };
99882
- }
99883
-
99884
99859
  // utils/install.ts
99885
99860
  import { spawnSync } from "node:child_process";
99886
99861
  import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync } from "node:fs";
@@ -100080,7 +100055,7 @@ var import_semver = __toESM(require_semver2(), 1);
100080
100055
  // package.json
100081
100056
  var package_default = {
100082
100057
  name: "pullfrog",
100083
- version: "0.1.19",
100058
+ version: "0.1.21",
100084
100059
  type: "module",
100085
100060
  bin: {
100086
100061
  pullfrog: "dist/cli.mjs",
@@ -100418,11 +100393,6 @@ async function spawn(options) {
100418
100393
  `spawn activity timer: pid=${child.pid} cmd=${options.cmd} timeout=${activityTimeoutMs}ms`
100419
100394
  );
100420
100395
  activityCheckIntervalId = setInterval(() => {
100421
- if (options.isPausedExternally?.()) {
100422
- lastActivityTime = performance3.now();
100423
- log.debug(`spawn activity check: pid=${child.pid} paused externally`);
100424
- return;
100425
- }
100426
100396
  const idleMs = performance3.now() - lastActivityTime;
100427
100397
  log.debug(
100428
100398
  `spawn activity check: pid=${child.pid} idle=${Math.round(idleMs)}ms / ${activityTimeoutMs}ms`
@@ -101890,7 +101860,6 @@ async function runClaude(params) {
101890
101860
  }
101891
101861
  } else if (block.type === "tool_use") {
101892
101862
  const toolName = block.name || "unknown";
101893
- suspendActivity();
101894
101863
  if (params.onToolUse) {
101895
101864
  params.onToolUse({
101896
101865
  toolName,
@@ -101935,7 +101904,6 @@ async function runClaude(params) {
101935
101904
  for (const block of content) {
101936
101905
  if (typeof block === "string") continue;
101937
101906
  if (block.type === "tool_result") {
101938
- resumeActivity();
101939
101907
  timerFor(label).markToolResult();
101940
101908
  const outputContent = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map(
101941
101909
  (entry) => typeof entry === "string" ? entry : typeof entry === "object" && entry !== null && "text" in entry ? String(entry.text) : JSON.stringify(entry)
@@ -102022,9 +101990,10 @@ async function runClaude(params) {
102022
101990
  args: params.args,
102023
101991
  cwd: params.cwd,
102024
101992
  env: params.env,
102025
- activityTimeout: 3e5,
101993
+ // flat agent idle budget — long synchronous MCP tool calls (issue #760)
101994
+ // sit well under it, so no per-toolcall suspend bracketing is needed.
101995
+ activityTimeout: AGENT_ACTIVITY_TIMEOUT_MS,
102026
101996
  onActivityTimeout: params.onActivityTimeout,
102027
- isPausedExternally: isActivitySuspended,
102028
101997
  stdio: ["ignore", "pipe", "pipe"],
102029
101998
  // run claude in its own process group so SIGKILL on activity timeout /
102030
101999
  // outer cancellation reaches any subprocesses it spawns (rg, file
@@ -106421,13 +106390,6 @@ async function onToolPart(ctx, part, label, isOrchestrator) {
106421
106390
  const status = part.state.status;
106422
106391
  const toolName = part.tool;
106423
106392
  const toolId = part.callID;
106424
- if (toolName !== "task") {
106425
- if (status === "completed" || status === "error") {
106426
- resumeActivity();
106427
- } else {
106428
- suspendActivity();
106429
- }
106430
- }
106431
106393
  if (toolName === "task" && status === "running" && isOrchestrator && !ctx.taskDispatchByCallID.has(toolId)) {
106432
106394
  const input = part.state.input ?? {};
106433
106395
  const dispatched = ctx.labeler.recordTaskDispatch(input);
@@ -106675,7 +106637,6 @@ function startInnerActivityWatchdog(params) {
106675
106637
  let fired = false;
106676
106638
  const id = setInterval(() => {
106677
106639
  if (fired) return;
106678
- if (isActivitySuspended()) return;
106679
106640
  const idleMs = performance6.now() - params.ctx.lastEventAt;
106680
106641
  if (idleMs <= params.timeoutMs) return;
106681
106642
  fired = true;
@@ -106831,13 +106792,13 @@ var opencode = agent({
106831
106792
  const watchdog = startInnerActivityWatchdog({
106832
106793
  ctx: runnerCtx,
106833
106794
  // model-stall budget: how long the orchestrator may stream NO progress
106834
- // (no token/tool part.updated) before we tear the turn down. tool
106835
- // execution is excluded via suspend/resumeActivity, so this only bounds
106836
- // a genuinely silent provider. 120s comfortably exceeds normal inter-
106837
- // token gaps (incl. extended-thinking pauses) while catching the
106838
- // observed stalls (model goes quiet ~3-4min in) far sooner than the old
106839
- // 300s undici cap that this fix removes.
106840
- timeoutMs: 12e4,
106795
+ // (no token/tool part.updated) before we tear the turn down. opencode's
106796
+ // keepalive/lifecycle events keep the outer process-output monitor
106797
+ // alive even while the model is silent, so this inner timer is the only
106798
+ // stall detector for the v2 SSE path. it shares the flat idle budget so
106799
+ // a long synchronous tool call (no part.updated while it runs) can't
106800
+ // false-positive it.
106801
+ timeoutMs: AGENT_ACTIVITY_TIMEOUT_MS,
106841
106802
  abortController
106842
106803
  });
106843
106804
  const sdkModel = parseModel2(model);
@@ -160216,7 +160177,7 @@ ${instructions.user}` : null,
160216
160177
  }
160217
160178
  }
160218
160179
  activityTimeout = createProcessOutputActivityTimeout({
160219
- timeoutMs: DEFAULT_ACTIVITY_TIMEOUT_MS,
160180
+ timeoutMs: AGENT_ACTIVITY_TIMEOUT_MS,
160220
160181
  checkIntervalMs: DEFAULT_ACTIVITY_CHECK_INTERVAL_MS
160221
160182
  });
160222
160183
  activityTimeout.promise.catch(() => {
package/dist/internal.js CHANGED
@@ -476,20 +476,14 @@ var modelAliases = Object.entries(providers).flatMap(
476
476
  hidden: def.hidden ?? false
477
477
  }))
478
478
  );
479
- var defaultProxyAlias = modelAliases.find((a) => a.slug === "moonshotai/kimi-k2");
479
+ var defaultProxyAlias = modelAliases.find((a) => a.slug === "deepseek/deepseek-pro");
480
480
  if (!defaultProxyAlias?.openRouterResolve) {
481
- throw new Error("DEFAULT_PROXY_MODEL: moonshotai/kimi-k2 missing openRouterResolve");
481
+ throw new Error("DEFAULT_PROXY_MODEL: deepseek/deepseek-pro missing openRouterResolve");
482
482
  }
483
483
  var DEFAULT_PROXY_MODEL = defaultProxyAlias.openRouterResolve;
484
+ var defaultProxyDisplayName = defaultProxyAlias.displayName;
484
485
  function getAutoSelectHintModel() {
485
- const alias = defaultProxyAlias;
486
- if (!alias) return "Kimi 2.6";
487
- const modelId = alias.resolve.split("/")[1] ?? "kimi-k2.6";
488
- const version = modelId.replace(/^kimi-k2\./, "");
489
- if (version && version !== modelId) {
490
- return `Kimi 2.${version}`;
491
- }
492
- return alias.displayName;
486
+ return defaultProxyDisplayName;
493
487
  }
494
488
  function resolveModelSlug(slug) {
495
489
  return modelAliases.find((a) => a.slug === slug)?.resolve;
@@ -1,4 +1,21 @@
1
+ /**
2
+ * generic `spawn()` idle default for ordinary short-lived subprocesses (prep
3
+ * probes, package-manager invocations, dependency installs). these should be
4
+ * producing output steadily, so a comparatively tight budget catches a wedged
5
+ * command promptly. the long-silent-tool tolerance the agent harnesses need
6
+ * lives in AGENT_ACTIVITY_TIMEOUT_MS, applied explicitly at those sites.
7
+ */
1
8
  export declare const DEFAULT_ACTIVITY_TIMEOUT_MS = 300000;
9
+ /**
10
+ * flat idle budget for the agent activity watchdog (the outer process-output
11
+ * monitor, the v1 harness spawns, and the v2 inner event-silence watchdog).
12
+ * sized to exceed the worst-case legitimate silent tool window (issue #760:
13
+ * `checkout_pr` git fetch+deepen on a large monorepo, ~4-5min) with generous
14
+ * headroom, so no single in-flight tool call can be mistaken for a stall. a
15
+ * timeout this generous needs no suspend/resume bracketing — the cost of a
16
+ * genuinely hung run is only GitHub Actions minutes, not tokens.
17
+ */
18
+ export declare const AGENT_ACTIVITY_TIMEOUT_MS = 900000;
2
19
  export declare const DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5000;
3
20
  export declare const ACTIVITY_NOISE_PATTERNS: readonly RegExp[];
4
21
  export declare function isActivityNoise(chunk: string | Uint8Array): boolean;
@@ -12,44 +29,12 @@ export type ActivityTimeout = {
12
29
  /** force the timeout to reject immediately with a custom reason */
13
30
  forceReject: (reason: string) => void;
14
31
  };
15
- /**
16
- * upper bound on how long a single tool call can suspend the activity
17
- * watchdog. matched against the typical worst-case `checkout_pr`
18
- * fetch+deepen on a large monorepo (issue #760: 4-5min) plus generous
19
- * headroom for slower MCP tools, while still bounding the worst case if
20
- * a tool genuinely hangs and `tool_result` never arrives — auto-resume
21
- * fires here and the normal idle clock takes over from a fresh baseline.
22
- */
23
- export declare const MAX_TOOL_CALL_SUSPENSION_MS: number;
24
32
  /**
25
33
  * mark activity to reset the no-output timeout.
26
34
  * call this whenever the agent emits any event, even if it isn't logged to stdout.
27
35
  */
28
36
  export declare function markActivity(): void;
29
- /**
30
- * get the time since last activity in milliseconds.
31
- * returns 0 while the watchdog is suspended (issue #760).
32
- */
37
+ /** get the time since last activity in milliseconds. */
33
38
  export declare function getIdleMs(): number;
34
- /**
35
- * suspend the activity watchdog while a long-running, in-flight unit of
36
- * work is happening (e.g. an MCP `tools/call` that synchronously awaits
37
- * a multi-minute git fetch). bracket calls with `resumeActivity()` from
38
- * the agent harness's `tool_use` / `tool_result` event handlers.
39
- *
40
- * - idempotent: nested suspends are no-ops; the first resume wins.
41
- * - bounded: auto-resumes after `maxMs` so a buggy tool that never
42
- * produces a `tool_result` can't pin the watchdog open forever.
43
- * - safe: only the *agent harness* (claude.ts / opencode.ts) on explicit,
44
- * paired CLI events should call this. NEVER blanket-suspend on internal
45
- * noise — that would resurrect issue #12 zombie runs.
46
- */
47
- export declare function suspendActivity(maxMs?: number): void;
48
- /**
49
- * resume the activity watchdog. resets the idle baseline so a stale
50
- * idle window before the suspend can't immediately re-fire.
51
- */
52
- export declare function resumeActivity(): void;
53
- export declare function isActivitySuspended(): boolean;
54
39
  export declare function createProcessOutputActivityTimeout(ctx: ActivityTimeoutContext): ActivityTimeout;
55
40
  export {};
@@ -43,7 +43,6 @@ export interface SpawnOptions {
43
43
  timeout?: number;
44
44
  activityTimeout?: number;
45
45
  onActivityTimeout?: (() => void) | undefined;
46
- isPausedExternally?: () => boolean;
47
46
  cwd?: string;
48
47
  stdio?: ("pipe" | "ignore" | "inherit")[];
49
48
  onStdout?: (chunk: string) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pullfrog",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "pullfrog": "dist/cli.mjs",