pullfrog 0.1.59 → 0.1.60
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/agents/shared.d.ts +7 -0
- package/dist/cli.mjs +162 -545
- package/dist/index.js +107 -39
- package/dist/internal.js +13 -3
- package/dist/utils/activity.d.ts +12 -0
- package/dist/utils/credentialCheck.d.ts +7 -1
- package/package.json +1 -1
package/dist/agents/shared.d.ts
CHANGED
|
@@ -120,6 +120,13 @@ export interface AgentRunContext {
|
|
|
120
120
|
* server) so lingering SSE reconnects don't keep the outer timer alive.
|
|
121
121
|
*/
|
|
122
122
|
onActivityTimeout?: (() => void) | undefined;
|
|
123
|
+
/**
|
|
124
|
+
* called when an aborted turn actually comes back and the harness intends to
|
|
125
|
+
* keep going. stands down the safety-net timer `onActivityTimeout` armed, so
|
|
126
|
+
* it only ever guards the window where an abort might have been ignored
|
|
127
|
+
* rather than force-exiting a run mid-salvage. see #1085.
|
|
128
|
+
*/
|
|
129
|
+
onTurnRecovered?: (() => void) | undefined;
|
|
123
130
|
onToolUse?: ((event: AgentToolUseEvent) => void) | undefined;
|
|
124
131
|
/**
|
|
125
132
|
* Pullfrog API JWT scoped to this run. agents only need this when they
|
package/dist/cli.mjs
CHANGED
|
@@ -106587,6 +106587,11 @@ var DEFAULT_ACTIVITY_TIMEOUT_MS = 3e5;
|
|
|
106587
106587
|
var AGENT_ACTIVITY_TIMEOUT_MS = 9e5;
|
|
106588
106588
|
var AGENT_FIRST_EVENT_TIMEOUT_MS = 12e4;
|
|
106589
106589
|
var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
|
|
106590
|
+
function watchdogBudgetMs(compiled, envVar) {
|
|
106591
|
+
const raw2 = Number(process.env[envVar]);
|
|
106592
|
+
if (!Number.isFinite(raw2) || raw2 <= 0) return compiled;
|
|
106593
|
+
return Math.min(compiled, raw2);
|
|
106594
|
+
}
|
|
106590
106595
|
var DEBUG_TS_PREFIX = /^(?:\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+)?/.source;
|
|
106591
106596
|
var ACTIVITY_NOISE_PATTERNS = [
|
|
106592
106597
|
new RegExp(`${DEBUG_TS_PREFIX}\\[mcp-proxy\\]`),
|
|
@@ -107350,7 +107355,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
107350
107355
|
// package.json
|
|
107351
107356
|
var package_default = {
|
|
107352
107357
|
name: "pullfrog",
|
|
107353
|
-
version: "0.1.
|
|
107358
|
+
version: "0.1.60",
|
|
107354
107359
|
type: "module",
|
|
107355
107360
|
bin: {
|
|
107356
107361
|
pullfrog: "dist/cli.mjs",
|
|
@@ -109459,7 +109464,7 @@ var claude = agent({
|
|
|
109459
109464
|
if ((isBedrockRoute || isVertexRoute2) && specifier && effort.alias?.effort) {
|
|
109460
109465
|
applyHostedEffortCapabilities({ env: env2, modelId: specifier, levels: effort.alias.effort });
|
|
109461
109466
|
}
|
|
109462
|
-
if (env2.CLAUDE_CODE_OAUTH_TOKEN && !isBedrockRoute && env2.ANTHROPIC_API_KEY) {
|
|
109467
|
+
if (env2.CLAUDE_CODE_OAUTH_TOKEN && !isBedrockRoute && !isVertexRoute2 && !env2.ANTHROPIC_BASE_URL && !env2.ANTHROPIC_AUTH_TOKEN && env2.ANTHROPIC_API_KEY) {
|
|
109463
109468
|
const preflight = await preflightClaudeSubscription({
|
|
109464
109469
|
token: env2.CLAUDE_CODE_OAUTH_TOKEN,
|
|
109465
109470
|
model
|
|
@@ -116277,6 +116282,7 @@ function processTerminalToolPart(ctx, part, label, isOrchestrator) {
|
|
|
116277
116282
|
const callLine = inputFormatted !== "{}" ? `\xBB ${toolName}(${inputFormatted})` : `\xBB ${toolName}()`;
|
|
116278
116283
|
log2.info(withLabel(label, callLine));
|
|
116279
116284
|
if (isOrchestrator) ctx.loggedToolCallIDs.add(toolId);
|
|
116285
|
+
if (isOrchestrator && toolName.startsWith("pullfrog_")) ctx.mcpToolCalls++;
|
|
116280
116286
|
if (state.status === "completed") {
|
|
116281
116287
|
log2.debug(withLabel(label, ` output: ${state.output}`));
|
|
116282
116288
|
} else {
|
|
@@ -116515,21 +116521,28 @@ function formatConfigError(name, data) {
|
|
|
116515
116521
|
).join("");
|
|
116516
116522
|
return `${name}${detail}${bullets}`;
|
|
116517
116523
|
}
|
|
116524
|
+
var WATCHDOG_SALVAGE_PROMPT = "Your previous turn was cut off mid-response by a provider stall. Nothing you had not already submitted through a tool was saved. Continue from where you stopped and submit your work now \u2014 do not restart your analysis.";
|
|
116518
116525
|
function startInnerActivityWatchdog(params) {
|
|
116519
116526
|
let fired = false;
|
|
116527
|
+
let everFired = false;
|
|
116520
116528
|
const id = setInterval(() => {
|
|
116521
116529
|
if (fired) return;
|
|
116522
116530
|
const idleMs = performance7.now() - params.ctx.lastEventAt;
|
|
116523
|
-
const
|
|
116531
|
+
const compiledMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
|
|
116532
|
+
const budgetMs = everFired ? compiledMs : watchdogBudgetMs(
|
|
116533
|
+
compiledMs,
|
|
116534
|
+
params.ctx.sawModelOutput ? "PULLFROG_E2E_ACTIVITY_TIMEOUT_MS" : "PULLFROG_E2E_FIRST_EVENT_TIMEOUT_MS"
|
|
116535
|
+
);
|
|
116524
116536
|
if (idleMs <= budgetMs) return;
|
|
116525
116537
|
fired = true;
|
|
116538
|
+
everFired = true;
|
|
116526
116539
|
const idleSec = Math.round(idleMs / 1e3);
|
|
116527
116540
|
params.ctx.diagnostic.idleSec = idleSec;
|
|
116528
116541
|
params.ctx.diagnostic.sawModelOutput = params.ctx.sawModelOutput;
|
|
116529
116542
|
log2.info(
|
|
116530
116543
|
params.ctx.sawModelOutput ? `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness` : `\xBB no opencode events for ${idleSec}s \u2014 the provider never returned a first token; aborting in-flight prompt and notifying harness`
|
|
116531
116544
|
);
|
|
116532
|
-
params.
|
|
116545
|
+
params.abortTurn();
|
|
116533
116546
|
try {
|
|
116534
116547
|
params.ctx.onActivityTimeout?.();
|
|
116535
116548
|
} catch (err) {
|
|
@@ -116539,7 +116552,22 @@ function startInnerActivityWatchdog(params) {
|
|
|
116539
116552
|
}
|
|
116540
116553
|
}, 5e3);
|
|
116541
116554
|
id.unref?.();
|
|
116542
|
-
return {
|
|
116555
|
+
return {
|
|
116556
|
+
stop: () => clearInterval(id),
|
|
116557
|
+
/**
|
|
116558
|
+
* Start a turn's idle budget from now. The clock must be reset with the
|
|
116559
|
+
* latch: `lastEventAt` only advances on model output, so a turn following a
|
|
116560
|
+
* stall would inherit the full stalled interval and be aborted on the very
|
|
116561
|
+
* next 5s tick — killing the salvage before the provider could answer. It
|
|
116562
|
+
* is the right semantics for an ordinary resume too, whose budget should
|
|
116563
|
+
* not be pre-spent by the post-run gate checks that ran between turns.
|
|
116564
|
+
*/
|
|
116565
|
+
armForTurn: () => {
|
|
116566
|
+
fired = false;
|
|
116567
|
+
params.ctx.lastEventAt = performance7.now();
|
|
116568
|
+
},
|
|
116569
|
+
firedThisTurn: () => fired
|
|
116570
|
+
};
|
|
116543
116571
|
}
|
|
116544
116572
|
var opencode = agent({
|
|
116545
116573
|
name: "opencode",
|
|
@@ -116651,6 +116679,7 @@ var opencode = agent({
|
|
|
116651
116679
|
variant: effort.rung,
|
|
116652
116680
|
todoTracker: ctx.todoTracker,
|
|
116653
116681
|
onActivityTimeout: ctx.onActivityTimeout,
|
|
116682
|
+
onTurnRecovered: ctx.onTurnRecovered,
|
|
116654
116683
|
onToolUse: ctx.onToolUse,
|
|
116655
116684
|
currentTurn: null,
|
|
116656
116685
|
eventCount: 0,
|
|
@@ -116659,6 +116688,7 @@ var opencode = agent({
|
|
|
116659
116688
|
promptMessageID: void 0,
|
|
116660
116689
|
taskDispatchByCallID: /* @__PURE__ */ new Map(),
|
|
116661
116690
|
loggedToolCallIDs: /* @__PURE__ */ new Set(),
|
|
116691
|
+
mcpToolCalls: 0,
|
|
116662
116692
|
recentStderr: server.recentStderr,
|
|
116663
116693
|
diagnostic: {
|
|
116664
116694
|
label: "Pullfrog",
|
|
@@ -116682,9 +116712,14 @@ var opencode = agent({
|
|
|
116682
116712
|
}
|
|
116683
116713
|
}
|
|
116684
116714
|
});
|
|
116685
|
-
const
|
|
116686
|
-
|
|
116687
|
-
|
|
116715
|
+
const runController = new AbortController();
|
|
116716
|
+
let turnController = new AbortController();
|
|
116717
|
+
const nextTurnSignal = () => {
|
|
116718
|
+
turnController = new AbortController();
|
|
116719
|
+
return AbortSignal.any([runController.signal, turnController.signal]);
|
|
116720
|
+
};
|
|
116721
|
+
const eventLoopPromise = consumeEvents(runnerCtx, runController.signal).catch((err) => {
|
|
116722
|
+
if (!runController.signal.aborted) {
|
|
116688
116723
|
log2.warning(
|
|
116689
116724
|
`\xBB opencode event subscription ended: ${err instanceof Error ? err.message : String(err)}`
|
|
116690
116725
|
);
|
|
@@ -116700,31 +116735,48 @@ var opencode = agent({
|
|
|
116700
116735
|
// a long synchronous tool call (no part.updated while it runs) can't
|
|
116701
116736
|
// false-positive it.
|
|
116702
116737
|
timeoutMs: AGENT_ACTIVITY_TIMEOUT_MS,
|
|
116703
|
-
|
|
116738
|
+
abortTurn: () => turnController.abort()
|
|
116704
116739
|
});
|
|
116705
116740
|
const sdkModel = parseModel2(model);
|
|
116741
|
+
let salvagesLeft = 1;
|
|
116742
|
+
const runTurn = async (text) => {
|
|
116743
|
+
const attempt = (prompt) => {
|
|
116744
|
+
watchdog.armForTurn();
|
|
116745
|
+
return runTurnGuarded(
|
|
116746
|
+
runnerCtx,
|
|
116747
|
+
() => runPromptTurn(runnerCtx, { text: prompt, model: sdkModel, signal: nextTurnSignal() })
|
|
116748
|
+
);
|
|
116749
|
+
};
|
|
116750
|
+
const standDownIfRecovered = (turn) => {
|
|
116751
|
+
if (watchdog.firedThisTurn() && turn.success) ctx.onTurnRecovered?.();
|
|
116752
|
+
return turn;
|
|
116753
|
+
};
|
|
116754
|
+
const result = await attempt(text);
|
|
116755
|
+
if (!watchdog.firedThisTurn()) return result;
|
|
116756
|
+
if (result.success) return standDownIfRecovered(result);
|
|
116757
|
+
if (salvagesLeft <= 0) return result;
|
|
116758
|
+
salvagesLeft--;
|
|
116759
|
+
ctx.onTurnRecovered?.();
|
|
116760
|
+
log2.info("\xBB activity watchdog cut the turn off \u2014 re-prompting once on the same session");
|
|
116761
|
+
const mcpCallsBefore = runnerCtx.mcpToolCalls;
|
|
116762
|
+
const salvaged = await attempt(WATCHDOG_SALVAGE_PROMPT);
|
|
116763
|
+
const usage = mergeAgentUsage(result.usage, salvaged.usage);
|
|
116764
|
+
if (runnerCtx.mcpToolCalls === mcpCallsBefore) {
|
|
116765
|
+
log2.info(
|
|
116766
|
+
"\xBB salvage turn reached no Pullfrog tool \u2014 keeping the activity-timeout failure"
|
|
116767
|
+
);
|
|
116768
|
+
return { ...result, usage };
|
|
116769
|
+
}
|
|
116770
|
+
return standDownIfRecovered({ ...salvaged, usage });
|
|
116771
|
+
};
|
|
116706
116772
|
try {
|
|
116707
|
-
const initial = await
|
|
116708
|
-
runnerCtx,
|
|
116709
|
-
() => runPromptTurn(runnerCtx, {
|
|
116710
|
-
text: ctx.instructions.full,
|
|
116711
|
-
model: sdkModel,
|
|
116712
|
-
signal: abortController.signal
|
|
116713
|
-
})
|
|
116714
|
-
);
|
|
116773
|
+
const initial = await runTurn(ctx.instructions.full);
|
|
116715
116774
|
const result = await runPostRunRetryLoop({
|
|
116716
116775
|
ctx,
|
|
116717
116776
|
initialResult: initial,
|
|
116718
116777
|
initialUsage: initial.usage,
|
|
116719
116778
|
reflectionPrompt: buildReflectionPrompt(ctx.toolState),
|
|
116720
|
-
resume: async (c2) =>
|
|
116721
|
-
runnerCtx,
|
|
116722
|
-
() => runPromptTurn(runnerCtx, {
|
|
116723
|
-
text: c2.prompt,
|
|
116724
|
-
model: sdkModel,
|
|
116725
|
-
signal: abortController.signal
|
|
116726
|
-
})
|
|
116727
|
-
)
|
|
116779
|
+
resume: async (c2) => runTurn(c2.prompt)
|
|
116728
116780
|
});
|
|
116729
116781
|
if (result.success) {
|
|
116730
116782
|
await ctx.todoTracker?.flush();
|
|
@@ -116734,7 +116786,7 @@ var opencode = agent({
|
|
|
116734
116786
|
return result;
|
|
116735
116787
|
} finally {
|
|
116736
116788
|
watchdog.stop();
|
|
116737
|
-
|
|
116789
|
+
runController.abort();
|
|
116738
116790
|
await eventLoopPromise.catch(() => {
|
|
116739
116791
|
});
|
|
116740
116792
|
}
|
|
@@ -194525,7 +194577,7 @@ function hasEnvVar2(name) {
|
|
|
194525
194577
|
return typeof val === "string" && val.length > 0;
|
|
194526
194578
|
}
|
|
194527
194579
|
function hasClaudeCodeAuth() {
|
|
194528
|
-
return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY");
|
|
194580
|
+
return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY") || hasEnvVar2("ANTHROPIC_AUTH_TOKEN");
|
|
194529
194581
|
}
|
|
194530
194582
|
function hasCodexAuth() {
|
|
194531
194583
|
return hasEnvVar2("CODEX_AUTH_JSON") || hasEnvVar2("OPENAI_API_KEY");
|
|
@@ -194618,8 +194670,11 @@ function resolveAgent(ctx) {
|
|
|
194618
194670
|
} catch {
|
|
194619
194671
|
}
|
|
194620
194672
|
}
|
|
194621
|
-
if (!ctx.model
|
|
194622
|
-
|
|
194673
|
+
if (!ctx.model) {
|
|
194674
|
+
if (hasEnvVar2("ANTHROPIC_AUTH_TOKEN") && !hasEnvVar2("ANTHROPIC_API_KEY") && !hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN")) {
|
|
194675
|
+
return agents.claude;
|
|
194676
|
+
}
|
|
194677
|
+
if (ctx.codexAgent && hasCodexAuth() && !hasClaudeCodeAuth()) return agents.codex;
|
|
194623
194678
|
}
|
|
194624
194679
|
return agents.opencode;
|
|
194625
194680
|
}
|
|
@@ -194805,7 +194860,7 @@ function hasSingleProviderAuth(agentName) {
|
|
|
194805
194860
|
if (agentName === "codex") {
|
|
194806
194861
|
return hasEnvVar3("OPENAI_API_KEY") || hasEnvVar3("CODEX_AUTH_JSON");
|
|
194807
194862
|
}
|
|
194808
|
-
return hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN");
|
|
194863
|
+
return hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("ANTHROPIC_AUTH_TOKEN") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN");
|
|
194809
194864
|
}
|
|
194810
194865
|
function validateAgentApiKey(params) {
|
|
194811
194866
|
if (params.model) {
|
|
@@ -195137,7 +195192,8 @@ var PROBES = {
|
|
|
195137
195192
|
request: (value2) => ({
|
|
195138
195193
|
url: "https://api.anthropic.com/v1/models",
|
|
195139
195194
|
headers: { "x-api-key": value2, "anthropic-version": "2023-06-01" }
|
|
195140
|
-
})
|
|
195195
|
+
}),
|
|
195196
|
+
hostConfigurable: true
|
|
195141
195197
|
},
|
|
195142
195198
|
OPENROUTER_API_KEY: {
|
|
195143
195199
|
request: (value2) => ({
|
|
@@ -195186,12 +195242,18 @@ function hasEnvVar4(name) {
|
|
|
195186
195242
|
const value2 = process.env[name];
|
|
195187
195243
|
return typeof value2 === "string" && value2.length > 0;
|
|
195188
195244
|
}
|
|
195245
|
+
var HOST_OVERRIDES = {
|
|
195246
|
+
ANTHROPIC_API_KEY: "ANTHROPIC_BASE_URL",
|
|
195247
|
+
CLAUDE_CODE_OAUTH_TOKEN: "ANTHROPIC_BASE_URL"
|
|
195248
|
+
};
|
|
195189
195249
|
function envVarsFor(model) {
|
|
195190
195250
|
return model.includes("/") ? getModelEnvVars(model) : [];
|
|
195191
195251
|
}
|
|
195192
195252
|
async function checkOne(params) {
|
|
195193
195253
|
const value2 = process.env[params.envVar];
|
|
195194
195254
|
if (!value2) return null;
|
|
195255
|
+
const hostOverride = HOST_OVERRIDES[params.envVar];
|
|
195256
|
+
if (hostOverride && hasEnvVar4(hostOverride)) return null;
|
|
195195
195257
|
if (params.envVar === "CLAUDE_CODE_OAUTH_TOKEN") {
|
|
195196
195258
|
const preflight = await preflightClaudeSubscription({
|
|
195197
195259
|
token: value2,
|
|
@@ -198424,16 +198486,14 @@ ${instructions.user}` : null,
|
|
|
198424
198486
|
const onInnerActivityTimeout = () => {
|
|
198425
198487
|
if (innerTimeoutFired) return;
|
|
198426
198488
|
innerTimeoutFired = true;
|
|
198427
|
-
log2.info(
|
|
198428
|
-
"\xBB inner activity timeout fired \u2014 stopping MCP server and starting 5min safety-net timer"
|
|
198429
|
-
);
|
|
198430
|
-
mcpHttpServer[Symbol.asyncDispose]().catch((err) => {
|
|
198431
|
-
log2.debug(
|
|
198432
|
-
`mcp server stop after inner kill failed: ${err instanceof Error ? err.message : String(err)}`
|
|
198433
|
-
);
|
|
198434
|
-
});
|
|
198489
|
+
log2.info("\xBB inner activity timeout fired \u2014 starting 5min safety-net timer");
|
|
198435
198490
|
safetyNetTimer = setTimeout(
|
|
198436
198491
|
() => {
|
|
198492
|
+
mcpHttpServer[Symbol.asyncDispose]().catch((err) => {
|
|
198493
|
+
log2.debug(
|
|
198494
|
+
`mcp server stop after inner kill failed: ${err instanceof Error ? err.message : String(err)}`
|
|
198495
|
+
);
|
|
198496
|
+
});
|
|
198437
198497
|
activityTimeout?.forceReject(
|
|
198438
198498
|
"agent still pending 5min after inner activity kill \u2014 forcing exit"
|
|
198439
198499
|
);
|
|
@@ -198442,6 +198502,13 @@ ${instructions.user}` : null,
|
|
|
198442
198502
|
);
|
|
198443
198503
|
safetyNetTimer.unref?.();
|
|
198444
198504
|
};
|
|
198505
|
+
const onTurnRecovered = () => {
|
|
198506
|
+
if (!innerTimeoutFired) return;
|
|
198507
|
+
innerTimeoutFired = false;
|
|
198508
|
+
if (safetyNetTimer) clearTimeout(safetyNetTimer);
|
|
198509
|
+
safetyNetTimer = void 0;
|
|
198510
|
+
log2.info("\xBB inner activity safety net stood down \u2014 turn recovered");
|
|
198511
|
+
};
|
|
198445
198512
|
const agentPromise = agent2.run({
|
|
198446
198513
|
payload,
|
|
198447
198514
|
resolvedModel,
|
|
@@ -198462,6 +198529,7 @@ ${instructions.user}` : null,
|
|
|
198462
198529
|
toolState,
|
|
198463
198530
|
apiToken: runContext.apiToken,
|
|
198464
198531
|
onActivityTimeout: onInnerActivityTimeout,
|
|
198532
|
+
onTurnRecovered,
|
|
198465
198533
|
onToolUse: (event) => {
|
|
198466
198534
|
const wasTracked = recordDiffReadFromToolUse({
|
|
198467
198535
|
state: primaryRepoState(toolState).diffCoverage,
|
|
@@ -198735,34 +198803,6 @@ var PULLFROG_API_URL2 = (process.env.PULLFROG_API_URL || "https://pullfrog.com")
|
|
|
198735
198803
|
function link(text, url4) {
|
|
198736
198804
|
return `\x1B]8;;${url4}\x07${text}\x1B]8;;\x07`;
|
|
198737
198805
|
}
|
|
198738
|
-
function buildProviders() {
|
|
198739
|
-
return Object.entries(providers).filter(([key]) => key !== "opencode" && key !== "openrouter").map(([key, config3]) => {
|
|
198740
|
-
const aliases = modelAliases.filter(
|
|
198741
|
-
(a2) => a2.provider === key && !a2.fallback && !a2.routing && !a2.hidden
|
|
198742
|
-
);
|
|
198743
|
-
const recommended = aliases.find((a2) => a2.preferred);
|
|
198744
|
-
const sorted = [...aliases].sort((a2, b) => {
|
|
198745
|
-
if (a2.preferred && !b.preferred) return -1;
|
|
198746
|
-
if (!a2.preferred && b.preferred) return 1;
|
|
198747
|
-
return 0;
|
|
198748
|
-
});
|
|
198749
|
-
return {
|
|
198750
|
-
id: key,
|
|
198751
|
-
name: config3.displayName,
|
|
198752
|
-
envVars: config3.envVars,
|
|
198753
|
-
models: sorted.map((a2) => ({
|
|
198754
|
-
value: a2.slug,
|
|
198755
|
-
label: a2.displayName,
|
|
198756
|
-
hint: a2 === recommended ? "recommended" : void 0
|
|
198757
|
-
}))
|
|
198758
|
-
};
|
|
198759
|
-
}).filter((p) => p.models.length > 0);
|
|
198760
|
-
}
|
|
198761
|
-
var CLI_PROVIDERS = buildProviders();
|
|
198762
|
-
function resolveModelProvider(slug2) {
|
|
198763
|
-
const providerId = slug2.split("/")[0];
|
|
198764
|
-
return CLI_PROVIDERS.find((p) => p.id === providerId) ?? null;
|
|
198765
|
-
}
|
|
198766
198806
|
var activeSpin2 = null;
|
|
198767
198807
|
function bail2(msg) {
|
|
198768
198808
|
if (activeSpin2) {
|
|
@@ -198847,15 +198887,11 @@ function openBrowser(url4) {
|
|
|
198847
198887
|
}
|
|
198848
198888
|
}
|
|
198849
198889
|
async function pullfrogApi2(ctx) {
|
|
198850
|
-
const headers = { authorization: `Bearer ${ctx.token}` };
|
|
198851
|
-
if (ctx.body) headers["content-type"] = "application/json";
|
|
198852
198890
|
const controller = new AbortController();
|
|
198853
198891
|
const timeout = setTimeout(() => controller.abort(), 3e4);
|
|
198854
198892
|
try {
|
|
198855
198893
|
const response = await fetch(`${PULLFROG_API_URL2}${ctx.path}`, {
|
|
198856
|
-
|
|
198857
|
-
headers,
|
|
198858
|
-
body: ctx.body ? JSON.stringify(ctx.body) : null,
|
|
198894
|
+
headers: { authorization: `Bearer ${ctx.token}` },
|
|
198859
198895
|
signal: controller.signal
|
|
198860
198896
|
});
|
|
198861
198897
|
const data = await response.json().catch(() => ({}));
|
|
@@ -198883,406 +198919,27 @@ async function fetchStatus2(ctx) {
|
|
|
198883
198919
|
isOrg: result.data.isOrg === true
|
|
198884
198920
|
};
|
|
198885
198921
|
}
|
|
198886
|
-
bail2(errorMsg || `
|
|
198922
|
+
bail2(errorMsg || `installation check failed (${result.status})`);
|
|
198887
198923
|
}
|
|
198888
198924
|
return {
|
|
198889
198925
|
installed: true,
|
|
198890
198926
|
isOrg: result.data.isOrg === true,
|
|
198891
|
-
installationId: typeof result.data.installationId === "number" ? result.data.installationId : null
|
|
198892
|
-
secretsAccessible: result.data.accessible !== false,
|
|
198893
|
-
repoSecrets: result.data.repoSecrets || [],
|
|
198894
|
-
orgSecrets: result.data.orgSecrets || [],
|
|
198895
|
-
pullfrogSecrets: result.data.pullfrogSecrets || [],
|
|
198896
|
-
model: result.data.repoModel ?? null,
|
|
198897
|
-
hasRuns: result.data.hasRuns === true
|
|
198898
|
-
};
|
|
198899
|
-
}
|
|
198900
|
-
async function createSession(ctx) {
|
|
198901
|
-
try {
|
|
198902
|
-
const result = await pullfrogApi2({
|
|
198903
|
-
path: "/api/cli/session",
|
|
198904
|
-
token: ctx.token,
|
|
198905
|
-
method: "POST",
|
|
198906
|
-
body: { owner: ctx.owner.toLowerCase(), repo: ctx.repo.toLowerCase() }
|
|
198907
|
-
});
|
|
198908
|
-
if (!result.ok || !result.data.id) return null;
|
|
198909
|
-
return result.data.id;
|
|
198910
|
-
} catch {
|
|
198911
|
-
return null;
|
|
198912
|
-
}
|
|
198913
|
-
}
|
|
198914
|
-
async function pollSession(ctx) {
|
|
198915
|
-
const result = await pullfrogApi2({
|
|
198916
|
-
path: `/api/cli/session/${ctx.sessionId}`,
|
|
198917
|
-
token: ctx.token
|
|
198918
|
-
});
|
|
198919
|
-
if (result.status === 410) return "expired";
|
|
198920
|
-
if (!result.ok) return "pending";
|
|
198921
|
-
return result.data.installed === true ? "installed" : "pending";
|
|
198922
|
-
}
|
|
198923
|
-
function cleanupSession(ctx) {
|
|
198924
|
-
void pullfrogApi2({
|
|
198925
|
-
path: `/api/cli/session/${ctx.sessionId}`,
|
|
198926
|
-
token: ctx.token,
|
|
198927
|
-
method: "DELETE"
|
|
198928
|
-
}).catch(() => {
|
|
198929
|
-
});
|
|
198930
|
-
}
|
|
198931
|
-
var SESSION_POLL_MS = 750;
|
|
198932
|
-
var FALLBACK_POLL_MS = 5e3;
|
|
198933
|
-
var HINT_AFTER_MS = 1e4;
|
|
198934
|
-
var TIMEOUT_MS = 3 * 60 * 1e3;
|
|
198935
|
-
function listenForKey(key) {
|
|
198936
|
-
let triggered = false;
|
|
198937
|
-
const onData = (data) => {
|
|
198938
|
-
if (data.toString().toLowerCase() === key) triggered = true;
|
|
198939
|
-
};
|
|
198940
|
-
process.stdin.setRawMode?.(true);
|
|
198941
|
-
process.stdin.resume();
|
|
198942
|
-
process.stdin.on("data", onData);
|
|
198943
|
-
return {
|
|
198944
|
-
consume() {
|
|
198945
|
-
if (!triggered) return false;
|
|
198946
|
-
triggered = false;
|
|
198947
|
-
return true;
|
|
198948
|
-
},
|
|
198949
|
-
stop() {
|
|
198950
|
-
process.stdin.removeListener("data", onData);
|
|
198951
|
-
process.stdin.setRawMode?.(false);
|
|
198952
|
-
process.stdin.pause();
|
|
198953
|
-
}
|
|
198927
|
+
installationId: typeof result.data.installationId === "number" ? result.data.installationId : null
|
|
198954
198928
|
};
|
|
198955
198929
|
}
|
|
198956
198930
|
function installationConfigUrl(ctx) {
|
|
198957
198931
|
return ctx.isOrg ? `https://github.com/organizations/${ctx.owner}/settings/installations/${ctx.installationId}` : `https://github.com/settings/installations/${ctx.installationId}`;
|
|
198958
198932
|
}
|
|
198959
|
-
|
|
198960
|
-
|
|
198961
|
-
const initial = await fetchStatus2(ctx);
|
|
198962
|
-
if (initial.installed) {
|
|
198963
|
-
activeSpin2.stop(`pullfrog app is installed on ${import_picocolors3.default.cyan(`@${ctx.owner}`)}`);
|
|
198964
|
-
if (initial.installationId) {
|
|
198965
|
-
const configUrl = installationConfigUrl({
|
|
198966
|
-
owner: ctx.owner,
|
|
198967
|
-
installationId: initial.installationId,
|
|
198968
|
-
isOrg: initial.isOrg
|
|
198969
|
-
});
|
|
198970
|
-
process.stdout.write(`${import_picocolors3.default.gray(S_BAR)} ${link(import_picocolors3.default.dim(configUrl), configUrl)}
|
|
198971
|
-
`);
|
|
198972
|
-
}
|
|
198973
|
-
return initial;
|
|
198974
|
-
}
|
|
198975
|
-
const sessionId = await createSession(ctx);
|
|
198976
|
-
if (initial.installationId) {
|
|
198977
|
-
const repoRef = import_picocolors3.default.bold(`${ctx.owner}/${ctx.repo}`);
|
|
198978
|
-
const configUrl = installationConfigUrl({
|
|
198979
|
-
owner: ctx.owner,
|
|
198980
|
-
installationId: initial.installationId,
|
|
198981
|
-
isOrg: initial.isOrg
|
|
198982
|
-
});
|
|
198983
|
-
activeSpin2.stop(`pullfrog is installed on selected repos, but ${repoRef} is not included.`);
|
|
198984
|
-
log.info(
|
|
198985
|
-
`add it under "Repository access" on the installation config page.
|
|
198986
|
-
${import_picocolors3.default.dim(configUrl)}`
|
|
198987
|
-
);
|
|
198988
|
-
const openIt = await confirm({ message: "open browser?", active: "yes", inactive: "no" });
|
|
198989
|
-
handleCancel2(openIt);
|
|
198990
|
-
if (openIt) openBrowser(configUrl);
|
|
198991
|
-
} else {
|
|
198992
|
-
activeSpin2.stop("pullfrog app not installed");
|
|
198993
|
-
const installUrl = `https://github.com/apps/${initial.appSlug}/installations/select_target?state=cli`;
|
|
198994
|
-
log.info(`opening browser to install...
|
|
198995
|
-
${import_picocolors3.default.dim(installUrl)}`);
|
|
198996
|
-
openBrowser(installUrl);
|
|
198997
|
-
}
|
|
198998
|
-
const isRepoAccessUpdate = !!initial.installationId;
|
|
198999
|
-
const baseMsg = isRepoAccessUpdate ? "once you've added the repo, onboarding will proceed automatically" : "once you've installed the app, onboarding will proceed automatically";
|
|
199000
|
-
activeSpin2.start(baseMsg);
|
|
199001
|
-
let activeSessionId = sessionId;
|
|
199002
|
-
let pollMs = activeSessionId ? SESSION_POLL_MS : FALLBACK_POLL_MS;
|
|
199003
|
-
const listener = listenForKey("r");
|
|
199004
|
-
const startedAt = Date.now();
|
|
199005
|
-
let hintShown = false;
|
|
199006
|
-
try {
|
|
199007
|
-
while (Date.now() - startedAt < TIMEOUT_MS) {
|
|
199008
|
-
await new Promise((r2) => setTimeout(r2, pollMs));
|
|
199009
|
-
if (!hintShown && Date.now() - startedAt > HINT_AFTER_MS) {
|
|
199010
|
-
activeSpin2.message(`${baseMsg} ${import_picocolors3.default.dim("(press r to recheck manually)")}`);
|
|
199011
|
-
hintShown = true;
|
|
199012
|
-
}
|
|
199013
|
-
const doneMsg = isRepoAccessUpdate ? "repo access confirmed" : "pullfrog app installed";
|
|
199014
|
-
if (listener.consume()) {
|
|
199015
|
-
activeSpin2.message("rechecking via GitHub API");
|
|
199016
|
-
try {
|
|
199017
|
-
const status = await fetchStatus2(ctx);
|
|
199018
|
-
if (status.installed) {
|
|
199019
|
-
if (activeSessionId) cleanupSession({ token: ctx.token, sessionId: activeSessionId });
|
|
199020
|
-
activeSpin2.stop(doneMsg);
|
|
199021
|
-
return status;
|
|
199022
|
-
}
|
|
199023
|
-
} catch {
|
|
199024
|
-
}
|
|
199025
|
-
activeSpin2.message(`${baseMsg} ${import_picocolors3.default.dim("(press r to recheck manually)")}`);
|
|
199026
|
-
continue;
|
|
199027
|
-
}
|
|
199028
|
-
if (activeSessionId) {
|
|
199029
|
-
try {
|
|
199030
|
-
const result = await pollSession({ token: ctx.token, sessionId: activeSessionId });
|
|
199031
|
-
if (result === "expired") {
|
|
199032
|
-
activeSessionId = null;
|
|
199033
|
-
pollMs = FALLBACK_POLL_MS;
|
|
199034
|
-
continue;
|
|
199035
|
-
}
|
|
199036
|
-
if (result === "installed") {
|
|
199037
|
-
const status = await fetchStatus2(ctx);
|
|
199038
|
-
if (status.installed) {
|
|
199039
|
-
cleanupSession({ token: ctx.token, sessionId: activeSessionId });
|
|
199040
|
-
activeSpin2.stop(doneMsg);
|
|
199041
|
-
return status;
|
|
199042
|
-
}
|
|
199043
|
-
}
|
|
199044
|
-
} catch {
|
|
199045
|
-
}
|
|
199046
|
-
} else {
|
|
199047
|
-
try {
|
|
199048
|
-
const status = await fetchStatus2(ctx);
|
|
199049
|
-
if (status.installed) {
|
|
199050
|
-
activeSpin2.stop(doneMsg);
|
|
199051
|
-
return status;
|
|
199052
|
-
}
|
|
199053
|
-
} catch {
|
|
199054
|
-
}
|
|
199055
|
-
}
|
|
199056
|
-
}
|
|
199057
|
-
} finally {
|
|
199058
|
-
listener.stop();
|
|
199059
|
-
}
|
|
199060
|
-
if (activeSessionId) cleanupSession({ token: ctx.token, sessionId: activeSessionId });
|
|
199061
|
-
bail2(
|
|
199062
|
-
isRepoAccessUpdate ? `timed out waiting for repo access.
|
|
199063
|
-
${import_picocolors3.default.dim("add the repo, then re-run:")} npx pullfrog init` : `timed out waiting for app installation.
|
|
199064
|
-
${import_picocolors3.default.dim("if your org requires admin approval, ask an admin to approve,")}
|
|
199065
|
-
${import_picocolors3.default.dim("then re-run:")} npx pullfrog init`
|
|
199066
|
-
);
|
|
198933
|
+
function consoleUrl(ctx) {
|
|
198934
|
+
return `${PULLFROG_API_URL2}/console/${ctx.owner}?repo=${encodeURIComponent(ctx.repo)}`;
|
|
199067
198935
|
}
|
|
199068
|
-
function
|
|
199069
|
-
|
|
199070
|
-
|
|
199071
|
-
try {
|
|
199072
|
-
execFileSync8("gh", ["secret", "set", ctx.name, "--org", ctx.org, "--visibility", "all"], {
|
|
199073
|
-
input: ctx.value,
|
|
199074
|
-
stdio: ["pipe", "ignore", "pipe"],
|
|
199075
|
-
encoding: "utf-8"
|
|
199076
|
-
});
|
|
199077
|
-
return { saved: true, orgFailed: false };
|
|
199078
|
-
} catch {
|
|
199079
|
-
orgFailed = true;
|
|
199080
|
-
}
|
|
199081
|
-
}
|
|
199082
|
-
try {
|
|
199083
|
-
execFileSync8("gh", ["secret", "set", ctx.name, "--repo", ctx.repoSlug], {
|
|
199084
|
-
input: ctx.value,
|
|
199085
|
-
stdio: ["pipe", "ignore", "pipe"],
|
|
199086
|
-
encoding: "utf-8"
|
|
199087
|
-
});
|
|
199088
|
-
return { saved: true, orgFailed };
|
|
199089
|
-
} catch {
|
|
199090
|
-
return { saved: false, orgFailed };
|
|
199091
|
-
}
|
|
198936
|
+
function installUrl(ctx) {
|
|
198937
|
+
const state = encodeURIComponent(`cli:${ctx.owner}/${ctx.repo}`);
|
|
198938
|
+
return `https://github.com/apps/${ctx.appSlug}/installations/select_target?state=${state}`;
|
|
199092
198939
|
}
|
|
199093
|
-
|
|
199094
|
-
|
|
199095
|
-
|
|
199096
|
-
token: ctx.token,
|
|
199097
|
-
method: "POST",
|
|
199098
|
-
body: {
|
|
199099
|
-
owner: ctx.owner,
|
|
199100
|
-
repo: ctx.repo,
|
|
199101
|
-
name: ctx.name,
|
|
199102
|
-
value: ctx.value,
|
|
199103
|
-
scope: ctx.scope
|
|
199104
|
-
}
|
|
199105
|
-
});
|
|
199106
|
-
if (result.ok && result.data.success === true) {
|
|
199107
|
-
return { saved: true, error: "" };
|
|
199108
|
-
}
|
|
199109
|
-
return { saved: false, error: result.data.error || `api returned ${result.status}` };
|
|
199110
|
-
}
|
|
199111
|
-
async function promptScope2(ctx) {
|
|
199112
|
-
const scope2 = await select({
|
|
199113
|
-
message: "secret scope",
|
|
199114
|
-
options: [
|
|
199115
|
-
{ value: "account", label: `${ctx.owner} organization`, hint: "shared across repos" },
|
|
199116
|
-
{ value: "repo", label: `${ctx.owner}/${ctx.repo} only` }
|
|
199117
|
-
]
|
|
199118
|
-
});
|
|
199119
|
-
handleCancel2(scope2);
|
|
199120
|
-
return scope2;
|
|
199121
|
-
}
|
|
199122
|
-
async function handleSecret(ctx) {
|
|
199123
|
-
const repoSecretsUrl = `https://github.com/${ctx.owner}/${ctx.repo}/settings/secrets/actions`;
|
|
199124
|
-
const matches = [];
|
|
199125
|
-
for (const v of ctx.provider.envVars) {
|
|
199126
|
-
if (ctx.secrets.pullfrogSecrets.includes(v)) matches.push({ name: v, source: "pullfrog" });
|
|
199127
|
-
else if (ctx.secrets.secretsAccessible && ctx.secrets.orgSecrets.includes(v))
|
|
199128
|
-
matches.push({ name: v, source: "org secret" });
|
|
199129
|
-
else if (ctx.secrets.secretsAccessible && ctx.secrets.repoSecrets.includes(v))
|
|
199130
|
-
matches.push({ name: v, source: "repo secret" });
|
|
199131
|
-
}
|
|
199132
|
-
if (matches.length > 0) {
|
|
199133
|
-
activeSpin2.start("");
|
|
199134
|
-
activeSpin2.stop("secrets already configured");
|
|
199135
|
-
for (const m of matches) {
|
|
199136
|
-
process.stdout.write(
|
|
199137
|
-
`${import_picocolors3.default.gray(S_BAR)} ${import_picocolors3.default.cyan(m.name)} ${import_picocolors3.default.dim(`(${m.source})`)}
|
|
199138
|
-
`
|
|
199139
|
-
);
|
|
199140
|
-
}
|
|
199141
|
-
return;
|
|
199142
|
-
}
|
|
199143
|
-
if (!ctx.secrets.secretsAccessible) {
|
|
199144
|
-
log.info(`could not verify GitHub secrets (app lacks permission)`);
|
|
199145
|
-
}
|
|
199146
|
-
const hasOAuthOption = ctx.provider.envVars.includes("CLAUDE_CODE_OAUTH_TOKEN");
|
|
199147
|
-
let envVar = ctx.provider.envVars[0];
|
|
199148
|
-
if (hasOAuthOption) {
|
|
199149
|
-
const authMethod = await select({
|
|
199150
|
-
message: "which credential do you want to use?",
|
|
199151
|
-
options: [
|
|
199152
|
-
{
|
|
199153
|
-
value: "oauth",
|
|
199154
|
-
label: "Claude Code OAuth token",
|
|
199155
|
-
hint: `run ${import_picocolors3.default.cyan("claude setup-token")} \u2014 works with Pro/Max subscriptions`
|
|
199156
|
-
},
|
|
199157
|
-
{
|
|
199158
|
-
value: "api",
|
|
199159
|
-
label: "Anthropic API key",
|
|
199160
|
-
hint: "from console.anthropic.com"
|
|
199161
|
-
}
|
|
199162
|
-
]
|
|
199163
|
-
});
|
|
199164
|
-
handleCancel2(authMethod);
|
|
199165
|
-
if (authMethod === "oauth") envVar = "CLAUDE_CODE_OAUTH_TOKEN";
|
|
199166
|
-
}
|
|
199167
|
-
const method = await select({
|
|
199168
|
-
message: `where should ${import_picocolors3.default.cyan(envVar)} be stored?`,
|
|
199169
|
-
options: [
|
|
199170
|
-
{
|
|
199171
|
-
value: "pullfrog",
|
|
199172
|
-
label: "Pullfrog",
|
|
199173
|
-
hint: "recommended \u2014 auto-injected, no workflow changes"
|
|
199174
|
-
},
|
|
199175
|
-
{
|
|
199176
|
-
value: "github",
|
|
199177
|
-
label: "GitHub Actions secret",
|
|
199178
|
-
hint: "requires env block in pullfrog.yml"
|
|
199179
|
-
}
|
|
199180
|
-
]
|
|
199181
|
-
});
|
|
199182
|
-
handleCancel2(method);
|
|
199183
|
-
const pasteLabel = envVar === "CLAUDE_CODE_OAUTH_TOKEN" ? "OAuth token" : `${ctx.provider.name} API key`;
|
|
199184
|
-
const apiKey = await password({
|
|
199185
|
-
message: `paste your ${pasteLabel} ${import_picocolors3.default.dim("(Enter to skip)")}`,
|
|
199186
|
-
mask: "*",
|
|
199187
|
-
validate: () => void 0
|
|
199188
|
-
});
|
|
199189
|
-
handleCancel2(apiKey);
|
|
199190
|
-
if (!apiKey) {
|
|
199191
|
-
log.info(
|
|
199192
|
-
`skipped \u2014 set it manually at:
|
|
199193
|
-
${import_picocolors3.default.dim(method === "pullfrog" ? `${PULLFROG_API_URL2}/console/${ctx.owner}` : repoSecretsUrl)}`
|
|
199194
|
-
);
|
|
199195
|
-
return;
|
|
199196
|
-
}
|
|
199197
|
-
if (method === "pullfrog") {
|
|
199198
|
-
const scope2 = ctx.secrets.isOrg ? await promptScope2(ctx) : "account";
|
|
199199
|
-
const target = describeSecretTarget({ owner: ctx.owner, repo: ctx.repo, scope: scope2 });
|
|
199200
|
-
activeSpin2.start(`saving ${import_picocolors3.default.cyan(envVar)} to ${target}`);
|
|
199201
|
-
let saveResult;
|
|
199202
|
-
try {
|
|
199203
|
-
saveResult = await setPullfrogSecret2({
|
|
199204
|
-
token: ctx.token,
|
|
199205
|
-
owner: ctx.owner,
|
|
199206
|
-
repo: ctx.repo,
|
|
199207
|
-
name: envVar,
|
|
199208
|
-
value: apiKey,
|
|
199209
|
-
scope: scope2
|
|
199210
|
-
});
|
|
199211
|
-
} catch (error52) {
|
|
199212
|
-
activeSpin2.stop(import_picocolors3.default.red("could not save secret"));
|
|
199213
|
-
log.warn(
|
|
199214
|
-
`${error52 instanceof Error ? error52.message : "network error"}
|
|
199215
|
-
set it manually at: ${import_picocolors3.default.dim(`${PULLFROG_API_URL2}/console/${ctx.owner}`)}`
|
|
199216
|
-
);
|
|
199217
|
-
return;
|
|
199218
|
-
}
|
|
199219
|
-
if (saveResult.saved) {
|
|
199220
|
-
activeSpin2.stop(`saved ${import_picocolors3.default.cyan(envVar)} to ${target}`);
|
|
199221
|
-
} else {
|
|
199222
|
-
activeSpin2.stop(import_picocolors3.default.red("could not save secret"));
|
|
199223
|
-
log.warn(
|
|
199224
|
-
`${saveResult.error}
|
|
199225
|
-
set it manually at: ${import_picocolors3.default.dim(`${PULLFROG_API_URL2}/console/${ctx.owner}`)}`
|
|
199226
|
-
);
|
|
199227
|
-
}
|
|
199228
|
-
return;
|
|
199229
|
-
}
|
|
199230
|
-
let org = null;
|
|
199231
|
-
if (ctx.secrets.isOrg) {
|
|
199232
|
-
const scope2 = await promptScope2(ctx);
|
|
199233
|
-
org = scope2 === "account" ? ctx.owner : null;
|
|
199234
|
-
}
|
|
199235
|
-
const secretsUrl = org ? `https://github.com/organizations/${org}/settings/secrets/actions` : repoSecretsUrl;
|
|
199236
|
-
activeSpin2.start(`saving ${envVar}`);
|
|
199237
|
-
const secretResult = setGhSecret({
|
|
199238
|
-
name: envVar,
|
|
199239
|
-
value: apiKey,
|
|
199240
|
-
org,
|
|
199241
|
-
repoSlug: `${ctx.owner}/${ctx.repo}`
|
|
199242
|
-
});
|
|
199243
|
-
if (secretResult.saved) {
|
|
199244
|
-
activeSpin2.stop(
|
|
199245
|
-
`saved ${import_picocolors3.default.cyan(envVar)} to ${org && !secretResult.orgFailed ? `${import_picocolors3.default.dim(ctx.owner)} org secret` : "GitHub Actions secret"}`
|
|
199246
|
-
);
|
|
199247
|
-
if (secretResult.orgFailed) {
|
|
199248
|
-
log.warn("org secret failed (admin access required) \u2014 saved as repo secret instead");
|
|
199249
|
-
}
|
|
199250
|
-
} else {
|
|
199251
|
-
activeSpin2.stop(import_picocolors3.default.red("could not set secret"));
|
|
199252
|
-
log.warn(`set it manually at:
|
|
199253
|
-
${import_picocolors3.default.dim(secretsUrl)}`);
|
|
199254
|
-
}
|
|
199255
|
-
}
|
|
199256
|
-
async function promptTestRun(ctx) {
|
|
199257
|
-
const proceed = await select({
|
|
199258
|
-
message: "test your installation?",
|
|
199259
|
-
options: [
|
|
199260
|
-
{ value: true, label: "yes", hint: "dispatches a test run in your GitHub Actions" },
|
|
199261
|
-
{ value: false, label: "skip" }
|
|
199262
|
-
]
|
|
199263
|
-
});
|
|
199264
|
-
handleCancel2(proceed);
|
|
199265
|
-
if (!proceed) return;
|
|
199266
|
-
activeSpin2.start("dispatching test run");
|
|
199267
|
-
const result = await pullfrogApi2({
|
|
199268
|
-
path: "/api/cli/dispatch",
|
|
199269
|
-
token: ctx.token,
|
|
199270
|
-
method: "POST",
|
|
199271
|
-
body: { owner: ctx.owner, repo: ctx.repo, prompt: "Tell me a joke" }
|
|
199272
|
-
});
|
|
199273
|
-
if (!result.ok) {
|
|
199274
|
-
activeSpin2.stop(import_picocolors3.default.red("could not dispatch"));
|
|
199275
|
-
log.warn(result.data.error || `dispatch failed (${result.status})`);
|
|
199276
|
-
return;
|
|
199277
|
-
}
|
|
199278
|
-
activeSpin2.stop("dispatched test run");
|
|
199279
|
-
if (result.data.url) {
|
|
199280
|
-
process.stdout.write(
|
|
199281
|
-
`${import_picocolors3.default.gray(S_BAR)} ${link(import_picocolors3.default.dim(result.data.url), result.data.url)}
|
|
199282
|
-
`
|
|
199283
|
-
);
|
|
199284
|
-
openBrowser(result.data.url);
|
|
199285
|
-
}
|
|
198940
|
+
function printLink(url4) {
|
|
198941
|
+
process.stdout.write(`${import_picocolors3.default.gray(S_BAR)} ${link(import_picocolors3.default.dim(url4), url4)}
|
|
198942
|
+
`);
|
|
199286
198943
|
}
|
|
199287
198944
|
async function main2() {
|
|
199288
198945
|
intro(import_picocolors3.default.bgGreen(import_picocolors3.default.black(" pullfrog ")));
|
|
@@ -199304,98 +198961,58 @@ async function main2() {
|
|
|
199304
198961
|
spin.start("detecting repository");
|
|
199305
198962
|
const remote = parseGitRemote2();
|
|
199306
198963
|
spin.stop(`detected repo ${import_picocolors3.default.cyan(`${remote.owner}/${remote.repo}`)}`);
|
|
199307
|
-
|
|
199308
|
-
|
|
199309
|
-
|
|
199310
|
-
if (
|
|
199311
|
-
|
|
199312
|
-
const resolved = resolveModelProvider(secrets.model);
|
|
199313
|
-
if (!resolved) bail2(`unknown model provider: ${secrets.model}`);
|
|
199314
|
-
provider2 = resolved;
|
|
199315
|
-
const displayAlias = resolveDisplayAlias(secrets.model);
|
|
199316
|
-
const label = displayAlias ? displayAlias.displayName : secrets.model;
|
|
198964
|
+
spin.start("checking pullfrog app installation");
|
|
198965
|
+
const status = await fetchStatus2({ token, owner: remote.owner, repo: remote.repo });
|
|
198966
|
+
const handoff = consoleUrl({ owner: remote.owner, repo: remote.repo });
|
|
198967
|
+
if (status.installed) {
|
|
198968
|
+
spin.stop(`pullfrog app is installed on ${import_picocolors3.default.cyan(`@${remote.owner}`)}`);
|
|
199317
198969
|
spin.start("");
|
|
199318
|
-
spin.stop(
|
|
199319
|
-
|
|
199320
|
-
|
|
199321
|
-
|
|
199322
|
-
|
|
199323
|
-
|
|
199324
|
-
label: cp.name
|
|
199325
|
-
}))
|
|
199326
|
-
});
|
|
199327
|
-
handleCancel2(providerId);
|
|
199328
|
-
const found = CLI_PROVIDERS.find((cp) => cp.id === providerId);
|
|
199329
|
-
if (!found) bail2(`unknown provider: ${providerId}`);
|
|
199330
|
-
provider2 = found;
|
|
199331
|
-
if (provider2.models.length === 1) {
|
|
199332
|
-
model = provider2.models[0].value;
|
|
199333
|
-
spin.start("");
|
|
199334
|
-
spin.stop(`using ${import_picocolors3.default.bold(provider2.models[0].label)}`);
|
|
199335
|
-
} else {
|
|
199336
|
-
const recommendedModel = provider2.models.find((m) => m.hint === "recommended");
|
|
199337
|
-
const options = provider2.models.map((m) => {
|
|
199338
|
-
if (m.hint) return { value: m.value, label: m.label, hint: m.hint };
|
|
199339
|
-
return { value: m.value, label: m.label };
|
|
199340
|
-
});
|
|
199341
|
-
const selected = await select(
|
|
199342
|
-
recommendedModel ? { message: "select model", initialValue: recommendedModel.value, options } : { message: "select model", options }
|
|
199343
|
-
);
|
|
199344
|
-
handleCancel2(selected);
|
|
199345
|
-
model = selected;
|
|
199346
|
-
}
|
|
199347
|
-
}
|
|
199348
|
-
await handleSecret({ token, owner: remote.owner, repo: remote.repo, provider: provider2, secrets });
|
|
199349
|
-
spin.start("creating pullfrog.yml workflow");
|
|
199350
|
-
const result = await pullfrogApi2({
|
|
199351
|
-
path: "/api/cli/setup",
|
|
199352
|
-
token,
|
|
199353
|
-
method: "POST",
|
|
199354
|
-
body: { owner: remote.owner, repo: remote.repo, model }
|
|
199355
|
-
});
|
|
199356
|
-
if (!result.ok) {
|
|
199357
|
-
bail2(result.data.error || `api returned ${result.status}`);
|
|
199358
|
-
}
|
|
199359
|
-
let skipTestRun = false;
|
|
199360
|
-
if (result.data.already_existed) {
|
|
199361
|
-
spin.stop("pullfrog.yml already exists");
|
|
199362
|
-
} else if (result.data.pull_request_url) {
|
|
199363
|
-
spin.stop("opened pull request with pullfrog.yml");
|
|
199364
|
-
process.stdout.write(
|
|
199365
|
-
`${import_picocolors3.default.gray(S_BAR)} ${link(import_picocolors3.default.dim(result.data.pull_request_url), result.data.pull_request_url)}
|
|
199366
|
-
`
|
|
198970
|
+
spin.stop("opening your dashboard");
|
|
198971
|
+
printLink(handoff);
|
|
198972
|
+
openBrowser(handoff);
|
|
198973
|
+
activeSpin2 = null;
|
|
198974
|
+
outro(
|
|
198975
|
+
`finish setup in your browser \u2014 ${import_picocolors3.default.cyan(`${remote.owner}/${remote.repo}`)} is ready.`
|
|
199367
198976
|
);
|
|
199368
|
-
|
|
199369
|
-
|
|
199370
|
-
|
|
199371
|
-
|
|
199372
|
-
|
|
199373
|
-
|
|
199374
|
-
|
|
198977
|
+
return;
|
|
198978
|
+
}
|
|
198979
|
+
if (status.installationId) {
|
|
198980
|
+
const repoRef = import_picocolors3.default.bold(`${remote.owner}/${remote.repo}`);
|
|
198981
|
+
const configUrl = installationConfigUrl({
|
|
198982
|
+
owner: remote.owner,
|
|
198983
|
+
installationId: status.installationId,
|
|
198984
|
+
isOrg: status.isOrg
|
|
199375
198985
|
});
|
|
199376
|
-
|
|
199377
|
-
|
|
199378
|
-
|
|
199379
|
-
|
|
199380
|
-
spin.stop(
|
|
199381
|
-
short ? `committed pullfrog.yml to repo ${import_picocolors3.default.dim(short)}` : "committed pullfrog.yml to repo"
|
|
198986
|
+
spin.stop(`pullfrog is installed on selected repos, but ${repoRef} is not included.`);
|
|
198987
|
+
log.info(
|
|
198988
|
+
`add it under "Repository access" on the installation config page.
|
|
198989
|
+
${import_picocolors3.default.dim(configUrl)}`
|
|
199382
198990
|
);
|
|
198991
|
+
const openIt = await confirm({ message: "open browser?", active: "yes", inactive: "no" });
|
|
198992
|
+
handleCancel2(openIt);
|
|
198993
|
+
if (openIt) openBrowser(configUrl);
|
|
198994
|
+
log.info("once the repo is added, finish setup at:");
|
|
198995
|
+
printLink(handoff);
|
|
198996
|
+
activeSpin2 = null;
|
|
198997
|
+
outro("done.");
|
|
198998
|
+
return;
|
|
199383
198999
|
}
|
|
199384
|
-
|
|
199385
|
-
|
|
199386
|
-
|
|
199387
|
-
|
|
199388
|
-
|
|
199389
|
-
|
|
199390
|
-
|
|
199391
|
-
|
|
199000
|
+
spin.stop("pullfrog app not installed");
|
|
199001
|
+
const install = installUrl({
|
|
199002
|
+
appSlug: status.appSlug,
|
|
199003
|
+
owner: remote.owner,
|
|
199004
|
+
repo: remote.repo
|
|
199005
|
+
});
|
|
199006
|
+
log.info("opening browser to install...");
|
|
199007
|
+
printLink(install);
|
|
199008
|
+
openBrowser(install);
|
|
199392
199009
|
activeSpin2 = null;
|
|
199393
|
-
outro("
|
|
199010
|
+
outro("GitHub will drop you at your dashboard to finish setup.");
|
|
199394
199011
|
}
|
|
199395
199012
|
function printInitUsage(params) {
|
|
199396
199013
|
params.stream(`usage: ${params.prog} init
|
|
199397
199014
|
`);
|
|
199398
|
-
params.stream("
|
|
199015
|
+
params.stream("install pullfrog on the current repository and open its dashboard.");
|
|
199399
199016
|
params.stream("");
|
|
199400
199017
|
params.stream("options:");
|
|
199401
199018
|
params.stream(" -h, --help show help");
|
|
@@ -199567,7 +199184,7 @@ async function runCli4(input) {
|
|
|
199567
199184
|
}
|
|
199568
199185
|
|
|
199569
199186
|
// cli.ts
|
|
199570
|
-
var VERSION10 = "0.1.
|
|
199187
|
+
var VERSION10 = "0.1.60";
|
|
199571
199188
|
var bin = basename2(process.argv[1] || "");
|
|
199572
199189
|
var PROG = bin === "pf" || bin === "pullfrog" ? bin : "pullfrog";
|
|
199573
199190
|
var rawArgs = process.argv.slice(2);
|
|
@@ -199575,7 +199192,7 @@ function printMainUsage(stream) {
|
|
|
199575
199192
|
stream(`usage: ${PROG} <command>
|
|
199576
199193
|
`);
|
|
199577
199194
|
stream("commands:");
|
|
199578
|
-
stream(" init
|
|
199195
|
+
stream(" init install pullfrog on the current repository and open its dashboard");
|
|
199579
199196
|
stream(" auth manage provider credentials for the current repository");
|
|
199580
199197
|
stream(" watch stream a PR's activity as one JSON line per event");
|
|
199581
199198
|
stream("");
|
package/dist/index.js
CHANGED
|
@@ -104420,6 +104420,11 @@ var DEFAULT_ACTIVITY_TIMEOUT_MS = 3e5;
|
|
|
104420
104420
|
var AGENT_ACTIVITY_TIMEOUT_MS = 9e5;
|
|
104421
104421
|
var AGENT_FIRST_EVENT_TIMEOUT_MS = 12e4;
|
|
104422
104422
|
var DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5e3;
|
|
104423
|
+
function watchdogBudgetMs(compiled, envVar) {
|
|
104424
|
+
const raw2 = Number(process.env[envVar]);
|
|
104425
|
+
if (!Number.isFinite(raw2) || raw2 <= 0) return compiled;
|
|
104426
|
+
return Math.min(compiled, raw2);
|
|
104427
|
+
}
|
|
104423
104428
|
var DEBUG_TS_PREFIX = /^(?:\[\d{4}-\d{2}-\d{2}T[^\]]+\]\s+)?/.source;
|
|
104424
104429
|
var ACTIVITY_NOISE_PATTERNS = [
|
|
104425
104430
|
new RegExp(`${DEBUG_TS_PREFIX}\\[mcp-proxy\\]`),
|
|
@@ -105183,7 +105188,7 @@ var import_semver = __toESM(require_semver2(), 1);
|
|
|
105183
105188
|
// package.json
|
|
105184
105189
|
var package_default = {
|
|
105185
105190
|
name: "pullfrog",
|
|
105186
|
-
version: "0.1.
|
|
105191
|
+
version: "0.1.60",
|
|
105187
105192
|
type: "module",
|
|
105188
105193
|
bin: {
|
|
105189
105194
|
pullfrog: "dist/cli.mjs",
|
|
@@ -107292,7 +107297,7 @@ var claude = agent({
|
|
|
107292
107297
|
if ((isBedrockRoute || isVertexRoute2) && specifier && effort.alias?.effort) {
|
|
107293
107298
|
applyHostedEffortCapabilities({ env: env2, modelId: specifier, levels: effort.alias.effort });
|
|
107294
107299
|
}
|
|
107295
|
-
if (env2.CLAUDE_CODE_OAUTH_TOKEN && !isBedrockRoute && env2.ANTHROPIC_API_KEY) {
|
|
107300
|
+
if (env2.CLAUDE_CODE_OAUTH_TOKEN && !isBedrockRoute && !isVertexRoute2 && !env2.ANTHROPIC_BASE_URL && !env2.ANTHROPIC_AUTH_TOKEN && env2.ANTHROPIC_API_KEY) {
|
|
107296
107301
|
const preflight = await preflightClaudeSubscription({
|
|
107297
107302
|
token: env2.CLAUDE_CODE_OAUTH_TOKEN,
|
|
107298
107303
|
model
|
|
@@ -114157,6 +114162,7 @@ function processTerminalToolPart(ctx, part, label, isOrchestrator) {
|
|
|
114157
114162
|
const callLine = inputFormatted !== "{}" ? `\xBB ${toolName}(${inputFormatted})` : `\xBB ${toolName}()`;
|
|
114158
114163
|
log.info(withLabel(label, callLine));
|
|
114159
114164
|
if (isOrchestrator) ctx.loggedToolCallIDs.add(toolId);
|
|
114165
|
+
if (isOrchestrator && toolName.startsWith("pullfrog_")) ctx.mcpToolCalls++;
|
|
114160
114166
|
if (state.status === "completed") {
|
|
114161
114167
|
log.debug(withLabel(label, ` output: ${state.output}`));
|
|
114162
114168
|
} else {
|
|
@@ -114395,21 +114401,28 @@ function formatConfigError(name, data) {
|
|
|
114395
114401
|
).join("");
|
|
114396
114402
|
return `${name}${detail}${bullets}`;
|
|
114397
114403
|
}
|
|
114404
|
+
var WATCHDOG_SALVAGE_PROMPT = "Your previous turn was cut off mid-response by a provider stall. Nothing you had not already submitted through a tool was saved. Continue from where you stopped and submit your work now \u2014 do not restart your analysis.";
|
|
114398
114405
|
function startInnerActivityWatchdog(params) {
|
|
114399
114406
|
let fired = false;
|
|
114407
|
+
let everFired = false;
|
|
114400
114408
|
const id = setInterval(() => {
|
|
114401
114409
|
if (fired) return;
|
|
114402
114410
|
const idleMs = performance7.now() - params.ctx.lastEventAt;
|
|
114403
|
-
const
|
|
114411
|
+
const compiledMs = params.ctx.sawModelOutput ? params.timeoutMs : AGENT_FIRST_EVENT_TIMEOUT_MS;
|
|
114412
|
+
const budgetMs = everFired ? compiledMs : watchdogBudgetMs(
|
|
114413
|
+
compiledMs,
|
|
114414
|
+
params.ctx.sawModelOutput ? "PULLFROG_E2E_ACTIVITY_TIMEOUT_MS" : "PULLFROG_E2E_FIRST_EVENT_TIMEOUT_MS"
|
|
114415
|
+
);
|
|
114404
114416
|
if (idleMs <= budgetMs) return;
|
|
114405
114417
|
fired = true;
|
|
114418
|
+
everFired = true;
|
|
114406
114419
|
const idleSec = Math.round(idleMs / 1e3);
|
|
114407
114420
|
params.ctx.diagnostic.idleSec = idleSec;
|
|
114408
114421
|
params.ctx.diagnostic.sawModelOutput = params.ctx.sawModelOutput;
|
|
114409
114422
|
log.info(
|
|
114410
114423
|
params.ctx.sawModelOutput ? `\xBB no opencode events for ${idleSec}s \u2014 aborting in-flight prompt and notifying harness` : `\xBB no opencode events for ${idleSec}s \u2014 the provider never returned a first token; aborting in-flight prompt and notifying harness`
|
|
114411
114424
|
);
|
|
114412
|
-
params.
|
|
114425
|
+
params.abortTurn();
|
|
114413
114426
|
try {
|
|
114414
114427
|
params.ctx.onActivityTimeout?.();
|
|
114415
114428
|
} catch (err) {
|
|
@@ -114419,7 +114432,22 @@ function startInnerActivityWatchdog(params) {
|
|
|
114419
114432
|
}
|
|
114420
114433
|
}, 5e3);
|
|
114421
114434
|
id.unref?.();
|
|
114422
|
-
return {
|
|
114435
|
+
return {
|
|
114436
|
+
stop: () => clearInterval(id),
|
|
114437
|
+
/**
|
|
114438
|
+
* Start a turn's idle budget from now. The clock must be reset with the
|
|
114439
|
+
* latch: `lastEventAt` only advances on model output, so a turn following a
|
|
114440
|
+
* stall would inherit the full stalled interval and be aborted on the very
|
|
114441
|
+
* next 5s tick — killing the salvage before the provider could answer. It
|
|
114442
|
+
* is the right semantics for an ordinary resume too, whose budget should
|
|
114443
|
+
* not be pre-spent by the post-run gate checks that ran between turns.
|
|
114444
|
+
*/
|
|
114445
|
+
armForTurn: () => {
|
|
114446
|
+
fired = false;
|
|
114447
|
+
params.ctx.lastEventAt = performance7.now();
|
|
114448
|
+
},
|
|
114449
|
+
firedThisTurn: () => fired
|
|
114450
|
+
};
|
|
114423
114451
|
}
|
|
114424
114452
|
var opencode = agent({
|
|
114425
114453
|
name: "opencode",
|
|
@@ -114531,6 +114559,7 @@ var opencode = agent({
|
|
|
114531
114559
|
variant: effort.rung,
|
|
114532
114560
|
todoTracker: ctx.todoTracker,
|
|
114533
114561
|
onActivityTimeout: ctx.onActivityTimeout,
|
|
114562
|
+
onTurnRecovered: ctx.onTurnRecovered,
|
|
114534
114563
|
onToolUse: ctx.onToolUse,
|
|
114535
114564
|
currentTurn: null,
|
|
114536
114565
|
eventCount: 0,
|
|
@@ -114539,6 +114568,7 @@ var opencode = agent({
|
|
|
114539
114568
|
promptMessageID: void 0,
|
|
114540
114569
|
taskDispatchByCallID: /* @__PURE__ */ new Map(),
|
|
114541
114570
|
loggedToolCallIDs: /* @__PURE__ */ new Set(),
|
|
114571
|
+
mcpToolCalls: 0,
|
|
114542
114572
|
recentStderr: server.recentStderr,
|
|
114543
114573
|
diagnostic: {
|
|
114544
114574
|
label: "Pullfrog",
|
|
@@ -114562,9 +114592,14 @@ var opencode = agent({
|
|
|
114562
114592
|
}
|
|
114563
114593
|
}
|
|
114564
114594
|
});
|
|
114565
|
-
const
|
|
114566
|
-
|
|
114567
|
-
|
|
114595
|
+
const runController = new AbortController();
|
|
114596
|
+
let turnController = new AbortController();
|
|
114597
|
+
const nextTurnSignal = () => {
|
|
114598
|
+
turnController = new AbortController();
|
|
114599
|
+
return AbortSignal.any([runController.signal, turnController.signal]);
|
|
114600
|
+
};
|
|
114601
|
+
const eventLoopPromise = consumeEvents(runnerCtx, runController.signal).catch((err) => {
|
|
114602
|
+
if (!runController.signal.aborted) {
|
|
114568
114603
|
log.warning(
|
|
114569
114604
|
`\xBB opencode event subscription ended: ${err instanceof Error ? err.message : String(err)}`
|
|
114570
114605
|
);
|
|
@@ -114580,31 +114615,48 @@ var opencode = agent({
|
|
|
114580
114615
|
// a long synchronous tool call (no part.updated while it runs) can't
|
|
114581
114616
|
// false-positive it.
|
|
114582
114617
|
timeoutMs: AGENT_ACTIVITY_TIMEOUT_MS,
|
|
114583
|
-
|
|
114618
|
+
abortTurn: () => turnController.abort()
|
|
114584
114619
|
});
|
|
114585
114620
|
const sdkModel = parseModel2(model);
|
|
114621
|
+
let salvagesLeft = 1;
|
|
114622
|
+
const runTurn = async (text) => {
|
|
114623
|
+
const attempt = (prompt) => {
|
|
114624
|
+
watchdog.armForTurn();
|
|
114625
|
+
return runTurnGuarded(
|
|
114626
|
+
runnerCtx,
|
|
114627
|
+
() => runPromptTurn(runnerCtx, { text: prompt, model: sdkModel, signal: nextTurnSignal() })
|
|
114628
|
+
);
|
|
114629
|
+
};
|
|
114630
|
+
const standDownIfRecovered = (turn) => {
|
|
114631
|
+
if (watchdog.firedThisTurn() && turn.success) ctx.onTurnRecovered?.();
|
|
114632
|
+
return turn;
|
|
114633
|
+
};
|
|
114634
|
+
const result = await attempt(text);
|
|
114635
|
+
if (!watchdog.firedThisTurn()) return result;
|
|
114636
|
+
if (result.success) return standDownIfRecovered(result);
|
|
114637
|
+
if (salvagesLeft <= 0) return result;
|
|
114638
|
+
salvagesLeft--;
|
|
114639
|
+
ctx.onTurnRecovered?.();
|
|
114640
|
+
log.info("\xBB activity watchdog cut the turn off \u2014 re-prompting once on the same session");
|
|
114641
|
+
const mcpCallsBefore = runnerCtx.mcpToolCalls;
|
|
114642
|
+
const salvaged = await attempt(WATCHDOG_SALVAGE_PROMPT);
|
|
114643
|
+
const usage = mergeAgentUsage(result.usage, salvaged.usage);
|
|
114644
|
+
if (runnerCtx.mcpToolCalls === mcpCallsBefore) {
|
|
114645
|
+
log.info(
|
|
114646
|
+
"\xBB salvage turn reached no Pullfrog tool \u2014 keeping the activity-timeout failure"
|
|
114647
|
+
);
|
|
114648
|
+
return { ...result, usage };
|
|
114649
|
+
}
|
|
114650
|
+
return standDownIfRecovered({ ...salvaged, usage });
|
|
114651
|
+
};
|
|
114586
114652
|
try {
|
|
114587
|
-
const initial = await
|
|
114588
|
-
runnerCtx,
|
|
114589
|
-
() => runPromptTurn(runnerCtx, {
|
|
114590
|
-
text: ctx.instructions.full,
|
|
114591
|
-
model: sdkModel,
|
|
114592
|
-
signal: abortController.signal
|
|
114593
|
-
})
|
|
114594
|
-
);
|
|
114653
|
+
const initial = await runTurn(ctx.instructions.full);
|
|
114595
114654
|
const result = await runPostRunRetryLoop({
|
|
114596
114655
|
ctx,
|
|
114597
114656
|
initialResult: initial,
|
|
114598
114657
|
initialUsage: initial.usage,
|
|
114599
114658
|
reflectionPrompt: buildReflectionPrompt(ctx.toolState),
|
|
114600
|
-
resume: async (c) =>
|
|
114601
|
-
runnerCtx,
|
|
114602
|
-
() => runPromptTurn(runnerCtx, {
|
|
114603
|
-
text: c.prompt,
|
|
114604
|
-
model: sdkModel,
|
|
114605
|
-
signal: abortController.signal
|
|
114606
|
-
})
|
|
114607
|
-
)
|
|
114659
|
+
resume: async (c) => runTurn(c.prompt)
|
|
114608
114660
|
});
|
|
114609
114661
|
if (result.success) {
|
|
114610
114662
|
await ctx.todoTracker?.flush();
|
|
@@ -114614,7 +114666,7 @@ var opencode = agent({
|
|
|
114614
114666
|
return result;
|
|
114615
114667
|
} finally {
|
|
114616
114668
|
watchdog.stop();
|
|
114617
|
-
|
|
114669
|
+
runController.abort();
|
|
114618
114670
|
await eventLoopPromise.catch(() => {
|
|
114619
114671
|
});
|
|
114620
114672
|
}
|
|
@@ -192405,7 +192457,7 @@ function hasEnvVar2(name) {
|
|
|
192405
192457
|
return typeof val === "string" && val.length > 0;
|
|
192406
192458
|
}
|
|
192407
192459
|
function hasClaudeCodeAuth() {
|
|
192408
|
-
return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY");
|
|
192460
|
+
return hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN") || hasEnvVar2("ANTHROPIC_API_KEY") || hasEnvVar2("ANTHROPIC_AUTH_TOKEN");
|
|
192409
192461
|
}
|
|
192410
192462
|
function hasCodexAuth() {
|
|
192411
192463
|
return hasEnvVar2("CODEX_AUTH_JSON") || hasEnvVar2("OPENAI_API_KEY");
|
|
@@ -192498,8 +192550,11 @@ function resolveAgent(ctx) {
|
|
|
192498
192550
|
} catch {
|
|
192499
192551
|
}
|
|
192500
192552
|
}
|
|
192501
|
-
if (!ctx.model
|
|
192502
|
-
|
|
192553
|
+
if (!ctx.model) {
|
|
192554
|
+
if (hasEnvVar2("ANTHROPIC_AUTH_TOKEN") && !hasEnvVar2("ANTHROPIC_API_KEY") && !hasEnvVar2("CLAUDE_CODE_OAUTH_TOKEN")) {
|
|
192555
|
+
return agents.claude;
|
|
192556
|
+
}
|
|
192557
|
+
if (ctx.codexAgent && hasCodexAuth() && !hasClaudeCodeAuth()) return agents.codex;
|
|
192503
192558
|
}
|
|
192504
192559
|
return agents.opencode;
|
|
192505
192560
|
}
|
|
@@ -192685,7 +192740,7 @@ function hasSingleProviderAuth(agentName) {
|
|
|
192685
192740
|
if (agentName === "codex") {
|
|
192686
192741
|
return hasEnvVar3("OPENAI_API_KEY") || hasEnvVar3("CODEX_AUTH_JSON");
|
|
192687
192742
|
}
|
|
192688
|
-
return hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN");
|
|
192743
|
+
return hasEnvVar3("ANTHROPIC_API_KEY") || hasEnvVar3("ANTHROPIC_AUTH_TOKEN") || hasEnvVar3("CLAUDE_CODE_OAUTH_TOKEN");
|
|
192689
192744
|
}
|
|
192690
192745
|
function validateAgentApiKey(params) {
|
|
192691
192746
|
if (params.model) {
|
|
@@ -193017,7 +193072,8 @@ var PROBES = {
|
|
|
193017
193072
|
request: (value2) => ({
|
|
193018
193073
|
url: "https://api.anthropic.com/v1/models",
|
|
193019
193074
|
headers: { "x-api-key": value2, "anthropic-version": "2023-06-01" }
|
|
193020
|
-
})
|
|
193075
|
+
}),
|
|
193076
|
+
hostConfigurable: true
|
|
193021
193077
|
},
|
|
193022
193078
|
OPENROUTER_API_KEY: {
|
|
193023
193079
|
request: (value2) => ({
|
|
@@ -193066,12 +193122,18 @@ function hasEnvVar4(name) {
|
|
|
193066
193122
|
const value2 = process.env[name];
|
|
193067
193123
|
return typeof value2 === "string" && value2.length > 0;
|
|
193068
193124
|
}
|
|
193125
|
+
var HOST_OVERRIDES = {
|
|
193126
|
+
ANTHROPIC_API_KEY: "ANTHROPIC_BASE_URL",
|
|
193127
|
+
CLAUDE_CODE_OAUTH_TOKEN: "ANTHROPIC_BASE_URL"
|
|
193128
|
+
};
|
|
193069
193129
|
function envVarsFor(model) {
|
|
193070
193130
|
return model.includes("/") ? getModelEnvVars(model) : [];
|
|
193071
193131
|
}
|
|
193072
193132
|
async function checkOne(params) {
|
|
193073
193133
|
const value2 = process.env[params.envVar];
|
|
193074
193134
|
if (!value2) return null;
|
|
193135
|
+
const hostOverride = HOST_OVERRIDES[params.envVar];
|
|
193136
|
+
if (hostOverride && hasEnvVar4(hostOverride)) return null;
|
|
193075
193137
|
if (params.envVar === "CLAUDE_CODE_OAUTH_TOKEN") {
|
|
193076
193138
|
const preflight = await preflightClaudeSubscription({
|
|
193077
193139
|
token: value2,
|
|
@@ -196304,16 +196366,14 @@ ${instructions.user}` : null,
|
|
|
196304
196366
|
const onInnerActivityTimeout = () => {
|
|
196305
196367
|
if (innerTimeoutFired) return;
|
|
196306
196368
|
innerTimeoutFired = true;
|
|
196307
|
-
log.info(
|
|
196308
|
-
"\xBB inner activity timeout fired \u2014 stopping MCP server and starting 5min safety-net timer"
|
|
196309
|
-
);
|
|
196310
|
-
mcpHttpServer[Symbol.asyncDispose]().catch((err) => {
|
|
196311
|
-
log.debug(
|
|
196312
|
-
`mcp server stop after inner kill failed: ${err instanceof Error ? err.message : String(err)}`
|
|
196313
|
-
);
|
|
196314
|
-
});
|
|
196369
|
+
log.info("\xBB inner activity timeout fired \u2014 starting 5min safety-net timer");
|
|
196315
196370
|
safetyNetTimer = setTimeout(
|
|
196316
196371
|
() => {
|
|
196372
|
+
mcpHttpServer[Symbol.asyncDispose]().catch((err) => {
|
|
196373
|
+
log.debug(
|
|
196374
|
+
`mcp server stop after inner kill failed: ${err instanceof Error ? err.message : String(err)}`
|
|
196375
|
+
);
|
|
196376
|
+
});
|
|
196317
196377
|
activityTimeout?.forceReject(
|
|
196318
196378
|
"agent still pending 5min after inner activity kill \u2014 forcing exit"
|
|
196319
196379
|
);
|
|
@@ -196322,6 +196382,13 @@ ${instructions.user}` : null,
|
|
|
196322
196382
|
);
|
|
196323
196383
|
safetyNetTimer.unref?.();
|
|
196324
196384
|
};
|
|
196385
|
+
const onTurnRecovered = () => {
|
|
196386
|
+
if (!innerTimeoutFired) return;
|
|
196387
|
+
innerTimeoutFired = false;
|
|
196388
|
+
if (safetyNetTimer) clearTimeout(safetyNetTimer);
|
|
196389
|
+
safetyNetTimer = void 0;
|
|
196390
|
+
log.info("\xBB inner activity safety net stood down \u2014 turn recovered");
|
|
196391
|
+
};
|
|
196325
196392
|
const agentPromise = agent2.run({
|
|
196326
196393
|
payload,
|
|
196327
196394
|
resolvedModel,
|
|
@@ -196342,6 +196409,7 @@ ${instructions.user}` : null,
|
|
|
196342
196409
|
toolState,
|
|
196343
196410
|
apiToken: runContext.apiToken,
|
|
196344
196411
|
onActivityTimeout: onInnerActivityTimeout,
|
|
196412
|
+
onTurnRecovered,
|
|
196345
196413
|
onToolUse: (event) => {
|
|
196346
196414
|
const wasTracked = recordDiffReadFromToolUse({
|
|
196347
196415
|
state: primaryRepoState(toolState).diffCoverage,
|
package/dist/internal.js
CHANGED
|
@@ -777,9 +777,16 @@ function getModelManagedCredentials(slug) {
|
|
|
777
777
|
const providerConfig = providers[parsed.provider];
|
|
778
778
|
return providerConfig?.managedCredentials?.slice() ?? [];
|
|
779
779
|
}
|
|
780
|
+
var HARNESS_ONLY_CREDENTIALS = {
|
|
781
|
+
anthropic: ["ANTHROPIC_AUTH_TOKEN"]
|
|
782
|
+
};
|
|
780
783
|
function modelHasStoredAuth(params) {
|
|
781
784
|
const slug = resolveDisplayAlias(params.model)?.slug ?? params.model;
|
|
782
|
-
const authVars = [
|
|
785
|
+
const authVars = [
|
|
786
|
+
...getModelEnvVars(slug),
|
|
787
|
+
...getModelManagedCredentials(slug),
|
|
788
|
+
...HARNESS_ONLY_CREDENTIALS[getModelProvider(slug)] ?? []
|
|
789
|
+
];
|
|
783
790
|
return authVars.some((v) => params.secretNames.includes(v));
|
|
784
791
|
}
|
|
785
792
|
var modelAliases = Object.entries(providers).flatMap(
|
|
@@ -1542,7 +1549,8 @@ var PROBES = {
|
|
|
1542
1549
|
request: (value) => ({
|
|
1543
1550
|
url: "https://api.anthropic.com/v1/models",
|
|
1544
1551
|
headers: { "x-api-key": value, "anthropic-version": "2023-06-01" }
|
|
1545
|
-
})
|
|
1552
|
+
}),
|
|
1553
|
+
hostConfigurable: true
|
|
1546
1554
|
},
|
|
1547
1555
|
OPENROUTER_API_KEY: {
|
|
1548
1556
|
request: (value) => ({
|
|
@@ -1565,7 +1573,9 @@ async function verifyClaudeSubscription(value) {
|
|
|
1565
1573
|
return preflight.status === 401 ? "dead" : "alive";
|
|
1566
1574
|
}
|
|
1567
1575
|
function isCredentialProbeable(envVar) {
|
|
1568
|
-
|
|
1576
|
+
if (envVar === "CLAUDE_CODE_OAUTH_TOKEN") return true;
|
|
1577
|
+
const probe = PROBES[envVar];
|
|
1578
|
+
return probe !== void 0 && !probe.hostConfigurable;
|
|
1569
1579
|
}
|
|
1570
1580
|
async function verifyCredential(params) {
|
|
1571
1581
|
const value = params.value.trim();
|
package/dist/utils/activity.d.ts
CHANGED
|
@@ -50,6 +50,18 @@ export declare const AGENT_ACTIVITY_TIMEOUT_MS = 900000;
|
|
|
50
50
|
*/
|
|
51
51
|
export declare const AGENT_FIRST_EVENT_TIMEOUT_MS = 120000;
|
|
52
52
|
export declare const DEFAULT_ACTIVITY_CHECK_INTERVAL_MS = 5000;
|
|
53
|
+
/**
|
|
54
|
+
* E2E affordance: shorten a watchdog budget for one dispatch, so the stall path
|
|
55
|
+
* can be exercised on demand. A real provider stall is not provocable, which is
|
|
56
|
+
* how #1085 shipped three escalations deep with its salvage branch never once
|
|
57
|
+
* run end to end.
|
|
58
|
+
*
|
|
59
|
+
* **Shorten-only, deliberately.** The value is clamped to the compiled budget,
|
|
60
|
+
* so an override can make the watchdog stricter but can never relax or disable
|
|
61
|
+
* it. That keeps this unable to manufacture the zombie run the watchdog exists
|
|
62
|
+
* to prevent, even if a customer sets it in their own workflow env.
|
|
63
|
+
*/
|
|
64
|
+
export declare function watchdogBudgetMs(compiled: number, envVar: string): number;
|
|
53
65
|
export declare const ACTIVITY_NOISE_PATTERNS: readonly RegExp[];
|
|
54
66
|
export declare function isActivityNoise(chunk: string | Uint8Array): boolean;
|
|
55
67
|
type ActivityTimeoutContext = {
|
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
* provider outage rewrite a working account's configuration.
|
|
8
8
|
*/
|
|
9
9
|
export type CredentialVerdict = "alive" | "dead" | "unknown";
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Whether asking the provider about this credential would tell us anything —
|
|
12
|
+
* the paste-time gate, where the caller stores a secret and cannot see the run
|
|
13
|
+
* env. A `hostConfigurable` credential is excluded here and stays probeable at
|
|
14
|
+
* run time, because only the runner knows whether a gateway is in play: the
|
|
15
|
+
* base URL usually lives in the workflow file, which the console never reads.
|
|
16
|
+
*/
|
|
11
17
|
export declare function isCredentialProbeable(envVar: string): boolean;
|
|
12
18
|
/**
|
|
13
19
|
* Ask the provider whether it still accepts this credential. Used at the two
|