pullfrog 0.1.18 → 0.1.20
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 +132 -158
- package/dist/index.js +131 -157
- package/dist/utils/activity.d.ts +18 -33
- package/dist/utils/subprocess.d.ts +0 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -101235,6 +101235,111 @@ function formatMcpToolRef(agentId, toolName) {
|
|
|
101235
101235
|
|
|
101236
101236
|
// utils/activity.ts
|
|
101237
101237
|
import { performance as performance2 } from "node:perf_hooks";
|
|
101238
|
+
function isMonitorDebugEnabled() {
|
|
101239
|
+
return process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || process.env.LOG_LEVEL === "debug";
|
|
101240
|
+
}
|
|
101241
|
+
var DEFAULT_ACTIVITY_TIMEOUT_MS = 3e5;
|
|
101242
|
+
var AGENT_ACTIVITY_TIMEOUT_MS = 9e5;
|
|
101243
|
+
var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
|
|
101244
|
+
var DEBUG_TS_PREFIX = /^(?:\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+)?/.source;
|
|
101245
|
+
var ACTIVITY_NOISE_PATTERNS = [
|
|
101246
|
+
new RegExp(`${DEBUG_TS_PREFIX}\\[mcp-proxy\\]`),
|
|
101247
|
+
new RegExp(`${DEBUG_TS_PREFIX}\xBB provider error detected`),
|
|
101248
|
+
new RegExp(`${DEBUG_TS_PREFIX}\\[DEBUG\\]\\s+(?:spawn|process) activity `),
|
|
101249
|
+
/^::debug::(?:spawn|process) activity /
|
|
101250
|
+
];
|
|
101251
|
+
function isActivityNoise(chunk) {
|
|
101252
|
+
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
101253
|
+
if (!text.trim()) return true;
|
|
101254
|
+
return text.split("\n").every((line) => {
|
|
101255
|
+
const trimmed = line.trim();
|
|
101256
|
+
if (!trimmed) return true;
|
|
101257
|
+
return ACTIVITY_NOISE_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
101258
|
+
});
|
|
101259
|
+
}
|
|
101260
|
+
var _lastActivity = performance2.now();
|
|
101261
|
+
function markActivity() {
|
|
101262
|
+
_lastActivity = performance2.now();
|
|
101263
|
+
}
|
|
101264
|
+
function getIdleMs() {
|
|
101265
|
+
return Math.round(performance2.now() - _lastActivity);
|
|
101266
|
+
}
|
|
101267
|
+
function wrapWrite(original, onActivity) {
|
|
101268
|
+
const wrapped = (chunk, encodingOrCb, cb) => {
|
|
101269
|
+
if (!isActivityNoise(chunk)) {
|
|
101270
|
+
onActivity();
|
|
101271
|
+
}
|
|
101272
|
+
if (typeof encodingOrCb === "function") {
|
|
101273
|
+
return original(chunk, encodingOrCb);
|
|
101274
|
+
}
|
|
101275
|
+
return original(chunk, encodingOrCb, cb);
|
|
101276
|
+
};
|
|
101277
|
+
return wrapped;
|
|
101278
|
+
}
|
|
101279
|
+
function startProcessOutputMonitor(ctx) {
|
|
101280
|
+
let timedOut = false;
|
|
101281
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
101282
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
101283
|
+
process.stdout.write = wrapWrite(originalStdoutWrite, markActivity);
|
|
101284
|
+
process.stderr.write = wrapWrite(originalStderrWrite, markActivity);
|
|
101285
|
+
const debugBypass = (msg) => {
|
|
101286
|
+
if (!isMonitorDebugEnabled()) return;
|
|
101287
|
+
originalStdoutWrite(`[${(/* @__PURE__ */ new Date()).toISOString()}] [DEBUG] ${msg}
|
|
101288
|
+
`);
|
|
101289
|
+
};
|
|
101290
|
+
debugBypass(`process activity monitor started: timeout=${ctx.timeoutMs}ms`);
|
|
101291
|
+
const intervalId = setInterval(() => {
|
|
101292
|
+
const idleMs = getIdleMs();
|
|
101293
|
+
debugBypass(`process activity check: idle=${idleMs}ms / ${ctx.timeoutMs}ms`);
|
|
101294
|
+
if (timedOut || idleMs <= ctx.timeoutMs) return;
|
|
101295
|
+
timedOut = true;
|
|
101296
|
+
ctx.onTimeout(idleMs);
|
|
101297
|
+
}, ctx.checkIntervalMs);
|
|
101298
|
+
function stop() {
|
|
101299
|
+
clearInterval(intervalId);
|
|
101300
|
+
process.stdout.write = originalStdoutWrite;
|
|
101301
|
+
process.stderr.write = originalStderrWrite;
|
|
101302
|
+
}
|
|
101303
|
+
return { stop };
|
|
101304
|
+
}
|
|
101305
|
+
function createProcessOutputActivityTimeout(ctx) {
|
|
101306
|
+
markActivity();
|
|
101307
|
+
let rejectFn = null;
|
|
101308
|
+
const promise2 = new Promise((_2, reject) => {
|
|
101309
|
+
rejectFn = reject;
|
|
101310
|
+
});
|
|
101311
|
+
let monitor = null;
|
|
101312
|
+
monitor = startProcessOutputMonitor({
|
|
101313
|
+
timeoutMs: ctx.timeoutMs,
|
|
101314
|
+
checkIntervalMs: ctx.checkIntervalMs,
|
|
101315
|
+
onTimeout: (idleMs) => {
|
|
101316
|
+
if (!rejectFn) return;
|
|
101317
|
+
const idleSec = Math.round(idleMs / 1e3);
|
|
101318
|
+
if (monitor) {
|
|
101319
|
+
monitor.stop();
|
|
101320
|
+
}
|
|
101321
|
+
const reject = rejectFn;
|
|
101322
|
+
rejectFn = null;
|
|
101323
|
+
reject(new Error(`activity timeout: no output for ${idleSec}s`));
|
|
101324
|
+
}
|
|
101325
|
+
});
|
|
101326
|
+
return {
|
|
101327
|
+
promise: promise2,
|
|
101328
|
+
// stop() also disarms forceReject so a late safety-net fire can't reject
|
|
101329
|
+
// the promise after the run has already succeeded.
|
|
101330
|
+
stop: () => {
|
|
101331
|
+
monitor?.stop();
|
|
101332
|
+
rejectFn = null;
|
|
101333
|
+
},
|
|
101334
|
+
forceReject: (reason) => {
|
|
101335
|
+
if (!rejectFn) return;
|
|
101336
|
+
monitor?.stop();
|
|
101337
|
+
const reject = rejectFn;
|
|
101338
|
+
rejectFn = null;
|
|
101339
|
+
reject(new Error(reason));
|
|
101340
|
+
}
|
|
101341
|
+
};
|
|
101342
|
+
}
|
|
101238
101343
|
|
|
101239
101344
|
// utils/log.ts
|
|
101240
101345
|
var core = __toESM(require_core(), 1);
|
|
@@ -101550,137 +101655,6 @@ function formatUsageSummary(entries) {
|
|
|
101550
101655
|
].join("\n");
|
|
101551
101656
|
}
|
|
101552
101657
|
|
|
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
101658
|
// utils/install.ts
|
|
101685
101659
|
import { spawnSync } from "node:child_process";
|
|
101686
101660
|
import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync } from "node:fs";
|
|
@@ -101880,7 +101854,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
101880
101854
|
// package.json
|
|
101881
101855
|
var package_default = {
|
|
101882
101856
|
name: "pullfrog",
|
|
101883
|
-
version: "0.1.
|
|
101857
|
+
version: "0.1.20",
|
|
101884
101858
|
type: "module",
|
|
101885
101859
|
bin: {
|
|
101886
101860
|
pullfrog: "dist/cli.mjs",
|
|
@@ -102218,11 +102192,6 @@ async function spawn3(options) {
|
|
|
102218
102192
|
`spawn activity timer: pid=${child.pid} cmd=${options.cmd} timeout=${activityTimeoutMs}ms`
|
|
102219
102193
|
);
|
|
102220
102194
|
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
102195
|
const idleMs = performance3.now() - lastActivityTime;
|
|
102227
102196
|
log.debug(
|
|
102228
102197
|
`spawn activity check: pid=${child.pid} idle=${Math.round(idleMs)}ms / ${activityTimeoutMs}ms`
|
|
@@ -103690,7 +103659,6 @@ async function runClaude(params) {
|
|
|
103690
103659
|
}
|
|
103691
103660
|
} else if (block.type === "tool_use") {
|
|
103692
103661
|
const toolName = block.name || "unknown";
|
|
103693
|
-
suspendActivity();
|
|
103694
103662
|
if (params.onToolUse) {
|
|
103695
103663
|
params.onToolUse({
|
|
103696
103664
|
toolName,
|
|
@@ -103735,7 +103703,6 @@ async function runClaude(params) {
|
|
|
103735
103703
|
for (const block of content) {
|
|
103736
103704
|
if (typeof block === "string") continue;
|
|
103737
103705
|
if (block.type === "tool_result") {
|
|
103738
|
-
resumeActivity();
|
|
103739
103706
|
timerFor(label).markToolResult();
|
|
103740
103707
|
const outputContent = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map(
|
|
103741
103708
|
(entry) => typeof entry === "string" ? entry : typeof entry === "object" && entry !== null && "text" in entry ? String(entry.text) : JSON.stringify(entry)
|
|
@@ -103822,9 +103789,10 @@ async function runClaude(params) {
|
|
|
103822
103789
|
args: params.args,
|
|
103823
103790
|
cwd: params.cwd,
|
|
103824
103791
|
env: params.env,
|
|
103825
|
-
|
|
103792
|
+
// flat agent idle budget — long synchronous MCP tool calls (issue #760)
|
|
103793
|
+
// sit well under it, so no per-toolcall suspend bracketing is needed.
|
|
103794
|
+
activityTimeout: AGENT_ACTIVITY_TIMEOUT_MS,
|
|
103826
103795
|
onActivityTimeout: params.onActivityTimeout,
|
|
103827
|
-
isPausedExternally: isActivitySuspended,
|
|
103828
103796
|
stdio: ["ignore", "pipe", "pipe"],
|
|
103829
103797
|
// run claude in its own process group so SIGKILL on activity timeout /
|
|
103830
103798
|
// outer cancellation reaches any subprocesses it spawns (rg, file
|
|
@@ -107960,6 +107928,20 @@ function buildSecurityConfig(ctx, model) {
|
|
|
107960
107928
|
const slashIndex = model.indexOf("/");
|
|
107961
107929
|
if (slashIndex > 0) {
|
|
107962
107930
|
config3.enabled_providers = [model.slice(0, slashIndex).toLowerCase()];
|
|
107931
|
+
if (model.startsWith("openrouter/moonshotai/")) {
|
|
107932
|
+
const modelID = model.slice(slashIndex + 1);
|
|
107933
|
+
config3.provider = {
|
|
107934
|
+
...config3.provider,
|
|
107935
|
+
openrouter: {
|
|
107936
|
+
npm: "@openrouter/ai-sdk-provider@2.9.0",
|
|
107937
|
+
options: {
|
|
107938
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
107939
|
+
apiKey: "{env:OPENROUTER_API_KEY}"
|
|
107940
|
+
},
|
|
107941
|
+
models: { [modelID]: {} }
|
|
107942
|
+
}
|
|
107943
|
+
};
|
|
107944
|
+
}
|
|
107963
107945
|
}
|
|
107964
107946
|
}
|
|
107965
107947
|
return JSON.stringify(config3);
|
|
@@ -108165,13 +108147,6 @@ async function onToolPart(ctx, part, label, isOrchestrator) {
|
|
|
108165
108147
|
const status = part.state.status;
|
|
108166
108148
|
const toolName = part.tool;
|
|
108167
108149
|
const toolId = part.callID;
|
|
108168
|
-
if (toolName !== "task") {
|
|
108169
|
-
if (status === "completed" || status === "error") {
|
|
108170
|
-
resumeActivity();
|
|
108171
|
-
} else {
|
|
108172
|
-
suspendActivity();
|
|
108173
|
-
}
|
|
108174
|
-
}
|
|
108175
108150
|
if (toolName === "task" && status === "running" && isOrchestrator && !ctx.taskDispatchByCallID.has(toolId)) {
|
|
108176
108151
|
const input = part.state.input ?? {};
|
|
108177
108152
|
const dispatched = ctx.labeler.recordTaskDispatch(input);
|
|
@@ -108419,7 +108394,6 @@ function startInnerActivityWatchdog(params) {
|
|
|
108419
108394
|
let fired = false;
|
|
108420
108395
|
const id = setInterval(() => {
|
|
108421
108396
|
if (fired) return;
|
|
108422
|
-
if (isActivitySuspended()) return;
|
|
108423
108397
|
const idleMs = performance6.now() - params.ctx.lastEventAt;
|
|
108424
108398
|
if (idleMs <= params.timeoutMs) return;
|
|
108425
108399
|
fired = true;
|
|
@@ -108575,13 +108549,13 @@ var opencode = agent({
|
|
|
108575
108549
|
const watchdog = startInnerActivityWatchdog({
|
|
108576
108550
|
ctx: runnerCtx,
|
|
108577
108551
|
// model-stall budget: how long the orchestrator may stream NO progress
|
|
108578
|
-
// (no token/tool part.updated) before we tear the turn down.
|
|
108579
|
-
//
|
|
108580
|
-
//
|
|
108581
|
-
//
|
|
108582
|
-
//
|
|
108583
|
-
//
|
|
108584
|
-
timeoutMs:
|
|
108552
|
+
// (no token/tool part.updated) before we tear the turn down. opencode's
|
|
108553
|
+
// keepalive/lifecycle events keep the outer process-output monitor
|
|
108554
|
+
// alive even while the model is silent, so this inner timer is the only
|
|
108555
|
+
// stall detector for the v2 SSE path. it shares the flat idle budget so
|
|
108556
|
+
// a long synchronous tool call (no part.updated while it runs) can't
|
|
108557
|
+
// false-positive it.
|
|
108558
|
+
timeoutMs: AGENT_ACTIVITY_TIMEOUT_MS,
|
|
108585
108559
|
abortController
|
|
108586
108560
|
});
|
|
108587
108561
|
const sdkModel = parseModel2(model);
|
|
@@ -161960,7 +161934,7 @@ ${instructions.user}` : null,
|
|
|
161960
161934
|
}
|
|
161961
161935
|
}
|
|
161962
161936
|
activityTimeout = createProcessOutputActivityTimeout({
|
|
161963
|
-
timeoutMs:
|
|
161937
|
+
timeoutMs: AGENT_ACTIVITY_TIMEOUT_MS,
|
|
161964
161938
|
checkIntervalMs: DEFAULT_ACTIVITY_CHECK_INTERVAL_MS
|
|
161965
161939
|
});
|
|
161966
161940
|
activityTimeout.promise.catch(() => {
|
|
@@ -163005,7 +162979,7 @@ async function run2() {
|
|
|
163005
162979
|
}
|
|
163006
162980
|
|
|
163007
162981
|
// cli.ts
|
|
163008
|
-
var VERSION10 = "0.1.
|
|
162982
|
+
var VERSION10 = "0.1.20";
|
|
163009
162983
|
var bin = basename2(process.argv[1] || "");
|
|
163010
162984
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
163011
162985
|
var rawArgs = process.argv.slice(2);
|
package/dist/index.js
CHANGED
|
@@ -99435,6 +99435,111 @@ function formatMcpToolRef(agentId, toolName) {
|
|
|
99435
99435
|
|
|
99436
99436
|
// utils/activity.ts
|
|
99437
99437
|
import { performance as performance2 } from "node:perf_hooks";
|
|
99438
|
+
function isMonitorDebugEnabled() {
|
|
99439
|
+
return process.env.ACTIONS_STEP_DEBUG === "true" || process.env.RUNNER_DEBUG === "1" || process.env.LOG_LEVEL === "debug";
|
|
99440
|
+
}
|
|
99441
|
+
var DEFAULT_ACTIVITY_TIMEOUT_MS = 3e5;
|
|
99442
|
+
var AGENT_ACTIVITY_TIMEOUT_MS = 9e5;
|
|
99443
|
+
var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
|
|
99444
|
+
var DEBUG_TS_PREFIX = /^(?:\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+)?/.source;
|
|
99445
|
+
var ACTIVITY_NOISE_PATTERNS = [
|
|
99446
|
+
new RegExp(`${DEBUG_TS_PREFIX}\\[mcp-proxy\\]`),
|
|
99447
|
+
new RegExp(`${DEBUG_TS_PREFIX}\xBB provider error detected`),
|
|
99448
|
+
new RegExp(`${DEBUG_TS_PREFIX}\\[DEBUG\\]\\s+(?:spawn|process) activity `),
|
|
99449
|
+
/^::debug::(?:spawn|process) activity /
|
|
99450
|
+
];
|
|
99451
|
+
function isActivityNoise(chunk) {
|
|
99452
|
+
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
99453
|
+
if (!text.trim()) return true;
|
|
99454
|
+
return text.split("\n").every((line) => {
|
|
99455
|
+
const trimmed = line.trim();
|
|
99456
|
+
if (!trimmed) return true;
|
|
99457
|
+
return ACTIVITY_NOISE_PATTERNS.some((pattern) => pattern.test(trimmed));
|
|
99458
|
+
});
|
|
99459
|
+
}
|
|
99460
|
+
var _lastActivity = performance2.now();
|
|
99461
|
+
function markActivity() {
|
|
99462
|
+
_lastActivity = performance2.now();
|
|
99463
|
+
}
|
|
99464
|
+
function getIdleMs() {
|
|
99465
|
+
return Math.round(performance2.now() - _lastActivity);
|
|
99466
|
+
}
|
|
99467
|
+
function wrapWrite(original, onActivity) {
|
|
99468
|
+
const wrapped = (chunk, encodingOrCb, cb) => {
|
|
99469
|
+
if (!isActivityNoise(chunk)) {
|
|
99470
|
+
onActivity();
|
|
99471
|
+
}
|
|
99472
|
+
if (typeof encodingOrCb === "function") {
|
|
99473
|
+
return original(chunk, encodingOrCb);
|
|
99474
|
+
}
|
|
99475
|
+
return original(chunk, encodingOrCb, cb);
|
|
99476
|
+
};
|
|
99477
|
+
return wrapped;
|
|
99478
|
+
}
|
|
99479
|
+
function startProcessOutputMonitor(ctx) {
|
|
99480
|
+
let timedOut = false;
|
|
99481
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
99482
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
99483
|
+
process.stdout.write = wrapWrite(originalStdoutWrite, markActivity);
|
|
99484
|
+
process.stderr.write = wrapWrite(originalStderrWrite, markActivity);
|
|
99485
|
+
const debugBypass = (msg) => {
|
|
99486
|
+
if (!isMonitorDebugEnabled()) return;
|
|
99487
|
+
originalStdoutWrite(`[${(/* @__PURE__ */ new Date()).toISOString()}] [DEBUG] ${msg}
|
|
99488
|
+
`);
|
|
99489
|
+
};
|
|
99490
|
+
debugBypass(`process activity monitor started: timeout=${ctx.timeoutMs}ms`);
|
|
99491
|
+
const intervalId = setInterval(() => {
|
|
99492
|
+
const idleMs = getIdleMs();
|
|
99493
|
+
debugBypass(`process activity check: idle=${idleMs}ms / ${ctx.timeoutMs}ms`);
|
|
99494
|
+
if (timedOut || idleMs <= ctx.timeoutMs) return;
|
|
99495
|
+
timedOut = true;
|
|
99496
|
+
ctx.onTimeout(idleMs);
|
|
99497
|
+
}, ctx.checkIntervalMs);
|
|
99498
|
+
function stop() {
|
|
99499
|
+
clearInterval(intervalId);
|
|
99500
|
+
process.stdout.write = originalStdoutWrite;
|
|
99501
|
+
process.stderr.write = originalStderrWrite;
|
|
99502
|
+
}
|
|
99503
|
+
return { stop };
|
|
99504
|
+
}
|
|
99505
|
+
function createProcessOutputActivityTimeout(ctx) {
|
|
99506
|
+
markActivity();
|
|
99507
|
+
let rejectFn = null;
|
|
99508
|
+
const promise2 = new Promise((_, reject) => {
|
|
99509
|
+
rejectFn = reject;
|
|
99510
|
+
});
|
|
99511
|
+
let monitor = null;
|
|
99512
|
+
monitor = startProcessOutputMonitor({
|
|
99513
|
+
timeoutMs: ctx.timeoutMs,
|
|
99514
|
+
checkIntervalMs: ctx.checkIntervalMs,
|
|
99515
|
+
onTimeout: (idleMs) => {
|
|
99516
|
+
if (!rejectFn) return;
|
|
99517
|
+
const idleSec = Math.round(idleMs / 1e3);
|
|
99518
|
+
if (monitor) {
|
|
99519
|
+
monitor.stop();
|
|
99520
|
+
}
|
|
99521
|
+
const reject = rejectFn;
|
|
99522
|
+
rejectFn = null;
|
|
99523
|
+
reject(new Error(`activity timeout: no output for ${idleSec}s`));
|
|
99524
|
+
}
|
|
99525
|
+
});
|
|
99526
|
+
return {
|
|
99527
|
+
promise: promise2,
|
|
99528
|
+
// stop() also disarms forceReject so a late safety-net fire can't reject
|
|
99529
|
+
// the promise after the run has already succeeded.
|
|
99530
|
+
stop: () => {
|
|
99531
|
+
monitor?.stop();
|
|
99532
|
+
rejectFn = null;
|
|
99533
|
+
},
|
|
99534
|
+
forceReject: (reason) => {
|
|
99535
|
+
if (!rejectFn) return;
|
|
99536
|
+
monitor?.stop();
|
|
99537
|
+
const reject = rejectFn;
|
|
99538
|
+
rejectFn = null;
|
|
99539
|
+
reject(new Error(reason));
|
|
99540
|
+
}
|
|
99541
|
+
};
|
|
99542
|
+
}
|
|
99438
99543
|
|
|
99439
99544
|
// utils/log.ts
|
|
99440
99545
|
var core = __toESM(require_core(), 1);
|
|
@@ -99750,137 +99855,6 @@ function formatUsageSummary(entries) {
|
|
|
99750
99855
|
].join("\n");
|
|
99751
99856
|
}
|
|
99752
99857
|
|
|
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
99858
|
// utils/install.ts
|
|
99885
99859
|
import { spawnSync } from "node:child_process";
|
|
99886
99860
|
import { chmodSync, createWriteStream, existsSync as existsSync2, mkdirSync } from "node:fs";
|
|
@@ -100080,7 +100054,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
100080
100054
|
// package.json
|
|
100081
100055
|
var package_default = {
|
|
100082
100056
|
name: "pullfrog",
|
|
100083
|
-
version: "0.1.
|
|
100057
|
+
version: "0.1.20",
|
|
100084
100058
|
type: "module",
|
|
100085
100059
|
bin: {
|
|
100086
100060
|
pullfrog: "dist/cli.mjs",
|
|
@@ -100418,11 +100392,6 @@ async function spawn(options) {
|
|
|
100418
100392
|
`spawn activity timer: pid=${child.pid} cmd=${options.cmd} timeout=${activityTimeoutMs}ms`
|
|
100419
100393
|
);
|
|
100420
100394
|
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
100395
|
const idleMs = performance3.now() - lastActivityTime;
|
|
100427
100396
|
log.debug(
|
|
100428
100397
|
`spawn activity check: pid=${child.pid} idle=${Math.round(idleMs)}ms / ${activityTimeoutMs}ms`
|
|
@@ -101890,7 +101859,6 @@ async function runClaude(params) {
|
|
|
101890
101859
|
}
|
|
101891
101860
|
} else if (block.type === "tool_use") {
|
|
101892
101861
|
const toolName = block.name || "unknown";
|
|
101893
|
-
suspendActivity();
|
|
101894
101862
|
if (params.onToolUse) {
|
|
101895
101863
|
params.onToolUse({
|
|
101896
101864
|
toolName,
|
|
@@ -101935,7 +101903,6 @@ async function runClaude(params) {
|
|
|
101935
101903
|
for (const block of content) {
|
|
101936
101904
|
if (typeof block === "string") continue;
|
|
101937
101905
|
if (block.type === "tool_result") {
|
|
101938
|
-
resumeActivity();
|
|
101939
101906
|
timerFor(label).markToolResult();
|
|
101940
101907
|
const outputContent = typeof block.content === "string" ? block.content : Array.isArray(block.content) ? block.content.map(
|
|
101941
101908
|
(entry) => typeof entry === "string" ? entry : typeof entry === "object" && entry !== null && "text" in entry ? String(entry.text) : JSON.stringify(entry)
|
|
@@ -102022,9 +101989,10 @@ async function runClaude(params) {
|
|
|
102022
101989
|
args: params.args,
|
|
102023
101990
|
cwd: params.cwd,
|
|
102024
101991
|
env: params.env,
|
|
102025
|
-
|
|
101992
|
+
// flat agent idle budget — long synchronous MCP tool calls (issue #760)
|
|
101993
|
+
// sit well under it, so no per-toolcall suspend bracketing is needed.
|
|
101994
|
+
activityTimeout: AGENT_ACTIVITY_TIMEOUT_MS,
|
|
102026
101995
|
onActivityTimeout: params.onActivityTimeout,
|
|
102027
|
-
isPausedExternally: isActivitySuspended,
|
|
102028
101996
|
stdio: ["ignore", "pipe", "pipe"],
|
|
102029
101997
|
// run claude in its own process group so SIGKILL on activity timeout /
|
|
102030
101998
|
// outer cancellation reaches any subprocesses it spawns (rg, file
|
|
@@ -106202,6 +106170,20 @@ function buildSecurityConfig(ctx, model) {
|
|
|
106202
106170
|
const slashIndex = model.indexOf("/");
|
|
106203
106171
|
if (slashIndex > 0) {
|
|
106204
106172
|
config3.enabled_providers = [model.slice(0, slashIndex).toLowerCase()];
|
|
106173
|
+
if (model.startsWith("openrouter/moonshotai/")) {
|
|
106174
|
+
const modelID = model.slice(slashIndex + 1);
|
|
106175
|
+
config3.provider = {
|
|
106176
|
+
...config3.provider,
|
|
106177
|
+
openrouter: {
|
|
106178
|
+
npm: "@openrouter/ai-sdk-provider@2.9.0",
|
|
106179
|
+
options: {
|
|
106180
|
+
baseURL: "https://openrouter.ai/api/v1",
|
|
106181
|
+
apiKey: "{env:OPENROUTER_API_KEY}"
|
|
106182
|
+
},
|
|
106183
|
+
models: { [modelID]: {} }
|
|
106184
|
+
}
|
|
106185
|
+
};
|
|
106186
|
+
}
|
|
106205
106187
|
}
|
|
106206
106188
|
}
|
|
106207
106189
|
return JSON.stringify(config3);
|
|
@@ -106407,13 +106389,6 @@ async function onToolPart(ctx, part, label, isOrchestrator) {
|
|
|
106407
106389
|
const status = part.state.status;
|
|
106408
106390
|
const toolName = part.tool;
|
|
106409
106391
|
const toolId = part.callID;
|
|
106410
|
-
if (toolName !== "task") {
|
|
106411
|
-
if (status === "completed" || status === "error") {
|
|
106412
|
-
resumeActivity();
|
|
106413
|
-
} else {
|
|
106414
|
-
suspendActivity();
|
|
106415
|
-
}
|
|
106416
|
-
}
|
|
106417
106392
|
if (toolName === "task" && status === "running" && isOrchestrator && !ctx.taskDispatchByCallID.has(toolId)) {
|
|
106418
106393
|
const input = part.state.input ?? {};
|
|
106419
106394
|
const dispatched = ctx.labeler.recordTaskDispatch(input);
|
|
@@ -106661,7 +106636,6 @@ function startInnerActivityWatchdog(params) {
|
|
|
106661
106636
|
let fired = false;
|
|
106662
106637
|
const id = setInterval(() => {
|
|
106663
106638
|
if (fired) return;
|
|
106664
|
-
if (isActivitySuspended()) return;
|
|
106665
106639
|
const idleMs = performance6.now() - params.ctx.lastEventAt;
|
|
106666
106640
|
if (idleMs <= params.timeoutMs) return;
|
|
106667
106641
|
fired = true;
|
|
@@ -106817,13 +106791,13 @@ var opencode = agent({
|
|
|
106817
106791
|
const watchdog = startInnerActivityWatchdog({
|
|
106818
106792
|
ctx: runnerCtx,
|
|
106819
106793
|
// model-stall budget: how long the orchestrator may stream NO progress
|
|
106820
|
-
// (no token/tool part.updated) before we tear the turn down.
|
|
106821
|
-
//
|
|
106822
|
-
//
|
|
106823
|
-
//
|
|
106824
|
-
//
|
|
106825
|
-
//
|
|
106826
|
-
timeoutMs:
|
|
106794
|
+
// (no token/tool part.updated) before we tear the turn down. opencode's
|
|
106795
|
+
// keepalive/lifecycle events keep the outer process-output monitor
|
|
106796
|
+
// alive even while the model is silent, so this inner timer is the only
|
|
106797
|
+
// stall detector for the v2 SSE path. it shares the flat idle budget so
|
|
106798
|
+
// a long synchronous tool call (no part.updated while it runs) can't
|
|
106799
|
+
// false-positive it.
|
|
106800
|
+
timeoutMs: AGENT_ACTIVITY_TIMEOUT_MS,
|
|
106827
106801
|
abortController
|
|
106828
106802
|
});
|
|
106829
106803
|
const sdkModel = parseModel2(model);
|
|
@@ -160202,7 +160176,7 @@ ${instructions.user}` : null,
|
|
|
160202
160176
|
}
|
|
160203
160177
|
}
|
|
160204
160178
|
activityTimeout = createProcessOutputActivityTimeout({
|
|
160205
|
-
timeoutMs:
|
|
160179
|
+
timeoutMs: AGENT_ACTIVITY_TIMEOUT_MS,
|
|
160206
160180
|
checkIntervalMs: DEFAULT_ACTIVITY_CHECK_INTERVAL_MS
|
|
160207
160181
|
});
|
|
160208
160182
|
activityTimeout.promise.catch(() => {
|
package/dist/utils/activity.d.ts
CHANGED
|
@@ -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;
|