@resolflow/cli 0.0.0-stage → 0.1.0-beta.1
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/LICENSE +21 -0
- package/README.md +201 -2
- package/dist/adapters/antigravity.js +29 -0
- package/dist/adapters/claude-code.js +302 -0
- package/dist/adapters/codex.js +247 -0
- package/dist/adapters/coding-agent.js +278 -0
- package/dist/adapters/openai-compatible.js +60 -0
- package/dist/adapters/types.js +9 -0
- package/dist/agents/auth.js +109 -0
- package/dist/agents/daily-budget.js +170 -0
- package/dist/agents/errors.js +60 -0
- package/dist/agents/run-process.js +183 -0
- package/dist/agents/tools.js +175 -0
- package/dist/api.js +59 -0
- package/dist/commands.js +511 -0
- package/dist/config.js +104 -0
- package/dist/daemon.js +317 -0
- package/dist/index.js +88 -0
- package/dist/local-model.js +147 -0
- package/dist/local-ui.js +120 -0
- package/dist/log.js +35 -0
- package/dist/protocol.js +23 -0
- package/dist/run-log.js +78 -0
- package/dist/runtime.js +38 -0
- package/dist/secrets.js +207 -0
- package/dist/update-check.js +180 -0
- package/dist/urls.js +69 -0
- package/package.json +62 -4
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loginAllowed = exports.POLICY_OFF = exports.GEMINI_LOGIN_MESSAGE = exports.VENDOR_TERMS = exports.TOOL_ARG = exports.LOGIN_TERMS_VERSION = exports.isLoginTool = exports.LOGIN_TOOLS = void 0;
|
|
37
|
+
exports.notAvailableMessage = notAvailableMessage;
|
|
38
|
+
exports.loginTermsText = loginTermsText;
|
|
39
|
+
exports.authModeOf = authModeOf;
|
|
40
|
+
exports.readPolicy = readPolicy;
|
|
41
|
+
exports.codexHomeOf = codexHomeOf;
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
exports.LOGIN_TOOLS = ['claude-code', 'codex'];
|
|
44
|
+
const isLoginTool = (id) => id === 'claude-code' || id === 'codex';
|
|
45
|
+
exports.isLoginTool = isLoginTool;
|
|
46
|
+
/** The version of the summary the person accepted; a new version asks again. */
|
|
47
|
+
exports.LOGIN_TERMS_VERSION = '2026-10-24';
|
|
48
|
+
/*
|
|
49
|
+
* Codex jobs from Resolflow are opt-in on the device (CODEX_OPT_IN_ENV in tools.ts, RF-ce19e014: its read-only
|
|
50
|
+
* sandbox does not stop reads outside the job folder). A Codex login needs the person's real CODEX_HOME, so it is
|
|
51
|
+
* offered only behind the same opt-in (exactly "1").
|
|
52
|
+
*/
|
|
53
|
+
/** The CLI tool name the person types (`resolflow config agent-key claude`). */
|
|
54
|
+
exports.TOOL_ARG = { 'claude-code': 'claude', codex: 'codex' };
|
|
55
|
+
exports.VENDOR_TERMS = {
|
|
56
|
+
'claude-code': {
|
|
57
|
+
name: 'Anthropic',
|
|
58
|
+
urls: ['https://code.claude.com/docs/en/legal-and-compliance', 'https://www.anthropic.com/legal/consumer-terms'],
|
|
59
|
+
},
|
|
60
|
+
codex: { name: 'OpenAI', urls: ['https://learn.chatgpt.com/docs/auth', 'https://openai.com/policies/terms-of-use'] },
|
|
61
|
+
};
|
|
62
|
+
exports.GEMINI_LOGIN_MESSAGE = "CLI login for Gemini / Antigravity is not available: Google's terms do not allow third-party use of a Gemini CLI login.";
|
|
63
|
+
function notAvailableMessage(def, id) {
|
|
64
|
+
return `CLI login for ${def.label} is not available: it is turned off by the Resolflow administrator. Keep using an API key (\`resolflow config agent-key ${exports.TOOL_ARG[id]}\`).`;
|
|
65
|
+
}
|
|
66
|
+
/** The summary shown before the person types `yes` (English, like the rest of the CLI). */
|
|
67
|
+
function loginTermsText(def, id) {
|
|
68
|
+
const v = exports.VENDOR_TERMS[id];
|
|
69
|
+
return [
|
|
70
|
+
`Use your existing ${def.label} login for Resolflow jobs on this machine (terms summary v${exports.LOGIN_TERMS_VERSION}):`,
|
|
71
|
+
` - ${def.label} runs as installed (the unmodified ${v.name} binary) and reads its own login. Resolflow never reads,`,
|
|
72
|
+
' copies, logs or uploads your login files or tokens; Resolflow only learns that this tool uses "login".',
|
|
73
|
+
' - Jobs use YOUR plan and its usage limits (not Resolflow credits, not an API key).',
|
|
74
|
+
' - Only work you start yourself: background (system-triggered) runs never use your login.',
|
|
75
|
+
` - ${v.name}'s terms apply to you and are not changed by this choice; ${v.name} can limit or block such use.`,
|
|
76
|
+
` Read them: ${v.urls.join(' · ')}`,
|
|
77
|
+
' - Your own tool settings load in this mode; Resolflow switches off what it can (hooks, plugins, MCP servers,',
|
|
78
|
+
' tools), see the CLI README. The Resolflow administrator can switch this off for everyone at any time.',
|
|
79
|
+
` Undo: resolflow config agent-auth ${exports.TOOL_ARG[id]} api_key`,
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The mode this machine uses for a tool: `login` only when the person chose it here AND accepted the current terms
|
|
84
|
+
* summary; everything else (no choice, an older summary, a typo in the file) is `api_key`.
|
|
85
|
+
*/
|
|
86
|
+
function authModeOf(cfg, id) {
|
|
87
|
+
if (!(0, exports.isLoginTool)(id))
|
|
88
|
+
return 'api_key';
|
|
89
|
+
return cfg.agentAuth?.[id] === 'login' && cfg.agentAuthTerms?.[id]?.version === exports.LOGIN_TERMS_VERSION ? 'login' : 'api_key';
|
|
90
|
+
}
|
|
91
|
+
/** A policy received from the server: only `true` allows; anything else (missing, junk) is off. */
|
|
92
|
+
function readPolicy(raw) {
|
|
93
|
+
const c = raw && typeof raw === 'object' ? raw.cliLogin : undefined;
|
|
94
|
+
const r = c && typeof c === 'object' ? c : {};
|
|
95
|
+
return { 'claude-code': r['claude-code'] === true, codex: r.codex === true };
|
|
96
|
+
}
|
|
97
|
+
exports.POLICY_OFF = Object.freeze({ 'claude-code': false, codex: false });
|
|
98
|
+
const loginAllowed = (ref, id) => ref?.current?.[id] === true;
|
|
99
|
+
exports.loginAllowed = loginAllowed;
|
|
100
|
+
/**
|
|
101
|
+
* Codex in login mode: the person's own CODEX_HOME (where `codex login` keeps auth.json) — only the path is passed
|
|
102
|
+
* to the Codex binary; this CLI never opens anything in it.
|
|
103
|
+
*/
|
|
104
|
+
function codexHomeOf(env) {
|
|
105
|
+
if (env.CODEX_HOME && path.isAbsolute(env.CODEX_HOME))
|
|
106
|
+
return env.CODEX_HOME;
|
|
107
|
+
const home = env.HOME || env.USERPROFILE;
|
|
108
|
+
return home && path.isAbsolute(home) ? path.join(home, '.codex') : null;
|
|
109
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DailyBudget = exports.SPEND_FILE = exports.MIN_JOB_BUDGET_USD = exports.DEFAULT_DAILY_BUDGET_USD = void 0;
|
|
37
|
+
exports.localDay = localDay;
|
|
38
|
+
exports.dailyLimitOf = dailyLimitOf;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const config_1 = require("../config");
|
|
42
|
+
/**
|
|
43
|
+
* Daily spending ceiling for local coding agent jobs (RF-27cf99e3, L9).
|
|
44
|
+
*
|
|
45
|
+
* Every Claude Code job reports `total_cost_usd`; the sum for the current LOCAL day is kept in
|
|
46
|
+
* `<config dir>/agent-spend.json` (0600). Once the sum reaches the ceiling, new jobs are refused with
|
|
47
|
+
* `budget_exceeded`; below it, a job's own `--max-budget-usd` is lowered to what is left (never below 0.01).
|
|
48
|
+
* Codex reports no cost, so it is not counted. 0 = no daily ceiling.
|
|
49
|
+
*/
|
|
50
|
+
exports.DEFAULT_DAILY_BUDGET_USD = 10;
|
|
51
|
+
exports.MIN_JOB_BUDGET_USD = 0.01;
|
|
52
|
+
exports.SPEND_FILE = 'agent-spend.json';
|
|
53
|
+
/** YYYY-MM-DD of `d` in this machine's local time zone. */
|
|
54
|
+
function localDay(d) {
|
|
55
|
+
const p = (n) => String(n).padStart(2, '0');
|
|
56
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
57
|
+
}
|
|
58
|
+
const DAY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
59
|
+
const round2 = (n) => Math.round(n * 100) / 100;
|
|
60
|
+
// The stored total keeps sub-cent costs (many cheap jobs add up); only what is shown or reserved is rounded.
|
|
61
|
+
const round6 = (n) => Math.round(n * 1e6) / 1e6;
|
|
62
|
+
/** Round down to whole cents (what is left must never be rounded up). */
|
|
63
|
+
const floor2 = (n) => Math.floor(n * 100 + 1e-9) / 100;
|
|
64
|
+
/** A daily ceiling from config: a finite number >= 0, else the default. */
|
|
65
|
+
function dailyLimitOf(v) {
|
|
66
|
+
return typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : exports.DEFAULT_DAILY_BUDGET_USD;
|
|
67
|
+
}
|
|
68
|
+
class DailyBudget {
|
|
69
|
+
dir;
|
|
70
|
+
opts;
|
|
71
|
+
file;
|
|
72
|
+
now;
|
|
73
|
+
/** USD held by jobs still running (so concurrent jobs cannot together overshoot the ceiling). */
|
|
74
|
+
held = 0;
|
|
75
|
+
warnedFor = null;
|
|
76
|
+
constructor(dir, opts) {
|
|
77
|
+
this.dir = dir;
|
|
78
|
+
this.opts = opts;
|
|
79
|
+
this.file = path.join(dir, exports.SPEND_FILE);
|
|
80
|
+
this.now = opts.now ?? (() => new Date());
|
|
81
|
+
}
|
|
82
|
+
/** Spent today (0 when the file is missing, from another day, or unreadable — the last one with a warning). */
|
|
83
|
+
spentToday() {
|
|
84
|
+
const today = localDay(this.now());
|
|
85
|
+
let raw;
|
|
86
|
+
try {
|
|
87
|
+
raw = fs.readFileSync(this.file, 'utf8');
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
if (err.code !== 'ENOENT')
|
|
91
|
+
this.warnOnce(`could not read ${exports.SPEND_FILE} (${err.code ?? 'error'})`);
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const v = JSON.parse(raw);
|
|
96
|
+
if (!v || typeof v !== 'object' || typeof v.day !== 'string' || !DAY_RE.test(v.day) || typeof v.spentUsd !== 'number' || !Number.isFinite(v.spentUsd) || v.spentUsd < 0) {
|
|
97
|
+
throw new Error('bad shape');
|
|
98
|
+
}
|
|
99
|
+
return v.day === today ? v.spentUsd : 0;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
this.warnOnce(`${exports.SPEND_FILE} is damaged`);
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
snapshot() {
|
|
107
|
+
const limitUsd = dailyLimitOf(this.opts.limit());
|
|
108
|
+
const spentUsd = this.spentToday();
|
|
109
|
+
return { day: localDay(this.now()), spentUsd, limitUsd, remainingUsd: limitUsd === 0 ? null : Math.max(0, round2(limitUsd - spentUsd)) };
|
|
110
|
+
}
|
|
111
|
+
/** Add a reported cost to today's total (a day change resets it). */
|
|
112
|
+
add(usd) {
|
|
113
|
+
if (!(typeof usd === 'number' && Number.isFinite(usd) && usd > 0))
|
|
114
|
+
return;
|
|
115
|
+
const day = localDay(this.now());
|
|
116
|
+
const spentUsd = round6(this.spentToday() + usd);
|
|
117
|
+
(0, config_1.writePrivateFile)(this.file, JSON.stringify({ day, spentUsd }) + '\n');
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Hold budget for a job that asked for `jobBudgetUsd`. Refused (null) when what is left today — minus what
|
|
121
|
+
* running jobs hold — is below the 0.01 USD minimum; otherwise the job's budget is lowered to what is left.
|
|
122
|
+
*/
|
|
123
|
+
reserve(jobBudgetUsd) {
|
|
124
|
+
const s = this.snapshot();
|
|
125
|
+
let budget = jobBudgetUsd;
|
|
126
|
+
if (s.remainingUsd !== null) {
|
|
127
|
+
const left = floor2(s.remainingUsd - this.held);
|
|
128
|
+
if (left < exports.MIN_JOB_BUDGET_USD)
|
|
129
|
+
return null;
|
|
130
|
+
budget = Math.max(exports.MIN_JOB_BUDGET_USD, Math.min(jobBudgetUsd, left));
|
|
131
|
+
}
|
|
132
|
+
let open = true;
|
|
133
|
+
const hold = budget;
|
|
134
|
+
this.held += hold;
|
|
135
|
+
return {
|
|
136
|
+
jobBudgetUsd: budget,
|
|
137
|
+
charge: (usd) => {
|
|
138
|
+
if (!open || !(typeof usd === 'number' && Number.isFinite(usd) && usd > 0))
|
|
139
|
+
return;
|
|
140
|
+
try {
|
|
141
|
+
this.add(usd);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
this.opts.warn?.(`Could not save today's agent spend (${err.message}).`);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
release: () => {
|
|
148
|
+
if (!open)
|
|
149
|
+
return;
|
|
150
|
+
open = false;
|
|
151
|
+
this.held = Math.max(0, this.held - hold);
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** Human line for `resolflow status` / `resolflow agents`. */
|
|
156
|
+
describe() {
|
|
157
|
+
const s = this.snapshot();
|
|
158
|
+
return s.limitUsd === 0
|
|
159
|
+
? `Agent spend today: ${s.spentUsd.toFixed(2)} USD (no daily limit)`
|
|
160
|
+
: `Agent spend today: ${s.spentUsd.toFixed(2)} / ${s.limitUsd.toFixed(2)} USD`;
|
|
161
|
+
}
|
|
162
|
+
warnOnce(what) {
|
|
163
|
+
const key = `${localDay(this.now())}:${what}`;
|
|
164
|
+
if (this.warnedFor === key)
|
|
165
|
+
return;
|
|
166
|
+
this.warnedFor = key;
|
|
167
|
+
this.opts.warn?.(`${what}: counting today's agent spend from 0.`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
exports.DailyBudget = DailyBudget;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HARD_AGENT_KINDS = exports.LOGIN_INVALID_MESSAGE = exports.AGENT_ERROR_MESSAGES = void 0;
|
|
4
|
+
exports.claudeErrorKind = claudeErrorKind;
|
|
5
|
+
exports.classifyAgentErrorText = classifyAgentErrorText;
|
|
6
|
+
exports.AGENT_ERROR_MESSAGES = {
|
|
7
|
+
invalid_key: 'The coding agent has no valid API key on this device. Run: resolflow config agent-key <tool>',
|
|
8
|
+
rate_limited: 'The coding agent was rate limited by its provider; try again shortly.',
|
|
9
|
+
quota: 'The provider account of the coding agent has no credit or quota left.',
|
|
10
|
+
model_not_found: 'The coding agent does not know this model.',
|
|
11
|
+
budget_exceeded: 'The coding agent reached the budget or turn cap of this job.',
|
|
12
|
+
timeout: 'The coding agent took longer than the time cap of this job.',
|
|
13
|
+
unsupported: 'This coding agent cannot run this job on this device (update it, or the resolflow CLI).',
|
|
14
|
+
cli_login_disabled: 'Using a CLI login is turned off by the Resolflow administrator. On that machine switch the tool to an API key: `resolflow config agent-auth <tool> api_key` and `resolflow config agent-key <tool>`.',
|
|
15
|
+
device_error: 'The coding agent failed on this device.',
|
|
16
|
+
};
|
|
17
|
+
/** invalid_key while the tool runs on the person's CLI login (RF-90e75d6d): it is the login, not a key. */
|
|
18
|
+
exports.LOGIN_INVALID_MESSAGE = 'The coding agent is not signed in on this device, or its login was rejected. Sign in with the tool itself, or switch to an API key: resolflow config agent-auth <tool> api_key';
|
|
19
|
+
/** Claude Code `error` categories (assistant message `error`, `system/api_retry` `error`). */
|
|
20
|
+
function claudeErrorKind(category) {
|
|
21
|
+
switch (category) {
|
|
22
|
+
case 'authentication_failed':
|
|
23
|
+
case 'oauth_org_not_allowed':
|
|
24
|
+
return 'invalid_key';
|
|
25
|
+
case 'billing_error':
|
|
26
|
+
case 'account_on_hold':
|
|
27
|
+
return 'quota';
|
|
28
|
+
case 'rate_limit':
|
|
29
|
+
case 'overloaded':
|
|
30
|
+
return 'rate_limited';
|
|
31
|
+
case 'model_not_found':
|
|
32
|
+
return 'model_not_found';
|
|
33
|
+
case 'max_output_tokens':
|
|
34
|
+
return 'budget_exceeded';
|
|
35
|
+
case 'invalid_request':
|
|
36
|
+
case 'server_error':
|
|
37
|
+
case 'cloud_credential_error':
|
|
38
|
+
case 'unknown':
|
|
39
|
+
return 'device_error';
|
|
40
|
+
default:
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Kinds that end a run at once even while the tool would retry (a person must act). */
|
|
45
|
+
exports.HARD_AGENT_KINDS = new Set(['invalid_key', 'quota', 'model_not_found']);
|
|
46
|
+
/** The kind of a free-text error (Codex `turn.failed` / `error`, stderr of a failed start). */
|
|
47
|
+
function classifyAgentErrorText(text) {
|
|
48
|
+
const t = String(text ?? '').slice(0, 4000);
|
|
49
|
+
if (/\b401\b|unauthori[sz]ed|invalid[_ ]?(x-)?api[_ -]?key|incorrect api key|api key (is )?(missing|not (set|found|valid))|authentication(_error| failed| required)|not logged in|please (run )?\/?login|no api key/i.test(t))
|
|
50
|
+
return 'invalid_key';
|
|
51
|
+
if (/insufficient_quota|quota|billing|credit balance|payment required|\b402\b|account (is )?on hold/i.test(t))
|
|
52
|
+
return 'quota';
|
|
53
|
+
if (/\b429\b|rate[_ -]?limit|too many requests|overloaded|\b529\b/i.test(t))
|
|
54
|
+
return 'rate_limited';
|
|
55
|
+
if (/model[^.\n]{0,80}(not found|does not exist|not exist|is not supported|unknown|not available)|no such model|unknown model|model_not_found/i.test(t))
|
|
56
|
+
return 'model_not_found';
|
|
57
|
+
if (/unknown option|unexpected argument|unrecognized (option|argument)|error: unknown|invalid value for/i.test(t))
|
|
58
|
+
return 'unsupported';
|
|
59
|
+
return 'device_error';
|
|
60
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runProcess = runProcess;
|
|
37
|
+
exports.withEmptyTempDir = withEmptyTempDir;
|
|
38
|
+
exports.writeJobFile = writeJobFile;
|
|
39
|
+
exports.agentEnv = agentEnv;
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const STDERR_TAIL = 4096;
|
|
45
|
+
const MAX_LINE = 4 * 1024 * 1024;
|
|
46
|
+
function runProcess(spec) {
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
const grace = spec.graceMs ?? 5_000;
|
|
49
|
+
let stderrTail = '';
|
|
50
|
+
let buf = '';
|
|
51
|
+
let aborted = false;
|
|
52
|
+
let timedOut = false;
|
|
53
|
+
let settled = false;
|
|
54
|
+
const timers = [];
|
|
55
|
+
const child = (0, child_process_1.spawn)(spec.bin, spec.args, {
|
|
56
|
+
cwd: spec.cwd,
|
|
57
|
+
env: spec.env,
|
|
58
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
59
|
+
shell: false,
|
|
60
|
+
detached: process.platform !== 'win32',
|
|
61
|
+
windowsHide: true,
|
|
62
|
+
});
|
|
63
|
+
const killGroup = (sig) => {
|
|
64
|
+
if (child.exitCode !== null || child.signalCode !== null || !child.pid)
|
|
65
|
+
return;
|
|
66
|
+
try {
|
|
67
|
+
if (process.platform !== 'win32')
|
|
68
|
+
process.kill(-child.pid, sig);
|
|
69
|
+
else
|
|
70
|
+
child.kill(sig);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
try {
|
|
74
|
+
child.kill(sig);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
/* gone */
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const stop = () => {
|
|
82
|
+
killGroup('SIGINT');
|
|
83
|
+
const t1 = setTimeout(() => killGroup('SIGTERM'), grace);
|
|
84
|
+
const t2 = setTimeout(() => killGroup('SIGKILL'), grace * 2);
|
|
85
|
+
timers.push(t1, t2);
|
|
86
|
+
};
|
|
87
|
+
const onAbort = () => {
|
|
88
|
+
if (settled || aborted || timedOut)
|
|
89
|
+
return;
|
|
90
|
+
aborted = true;
|
|
91
|
+
stop();
|
|
92
|
+
};
|
|
93
|
+
if (spec.signal.aborted)
|
|
94
|
+
queueMicrotask(onAbort);
|
|
95
|
+
else
|
|
96
|
+
spec.signal.addEventListener('abort', onAbort, { once: true });
|
|
97
|
+
const wall = setTimeout(() => {
|
|
98
|
+
if (settled || aborted)
|
|
99
|
+
return;
|
|
100
|
+
timedOut = true;
|
|
101
|
+
stop();
|
|
102
|
+
}, Math.max(1, spec.timeoutMs));
|
|
103
|
+
timers.push(wall);
|
|
104
|
+
const flushLines = (final) => {
|
|
105
|
+
let nl;
|
|
106
|
+
while ((nl = buf.indexOf('\n')) >= 0) {
|
|
107
|
+
const line = buf.slice(0, nl).replace(/\r$/, '');
|
|
108
|
+
buf = buf.slice(nl + 1);
|
|
109
|
+
if (line.trim())
|
|
110
|
+
safeLine(line);
|
|
111
|
+
}
|
|
112
|
+
if (final && buf.trim())
|
|
113
|
+
safeLine(buf);
|
|
114
|
+
if (final)
|
|
115
|
+
buf = '';
|
|
116
|
+
if (buf.length > MAX_LINE)
|
|
117
|
+
buf = ''; // a runaway line without newline: dropped, never buffered forever
|
|
118
|
+
};
|
|
119
|
+
const safeLine = (line) => {
|
|
120
|
+
try {
|
|
121
|
+
spec.onLine(line);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
/* a parser error never kills the job runner */
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
child.stdout?.setEncoding('utf8');
|
|
128
|
+
child.stdout?.on('data', (d) => {
|
|
129
|
+
buf += d;
|
|
130
|
+
flushLines(false);
|
|
131
|
+
});
|
|
132
|
+
child.stderr?.setEncoding('utf8');
|
|
133
|
+
child.stderr?.on('data', (d) => {
|
|
134
|
+
stderrTail = (stderrTail + d).slice(-STDERR_TAIL);
|
|
135
|
+
});
|
|
136
|
+
child.stdin?.on('error', () => undefined); // the agent may exit before reading everything
|
|
137
|
+
if (spec.stdin !== undefined)
|
|
138
|
+
child.stdin?.write(spec.stdin);
|
|
139
|
+
child.stdin?.end();
|
|
140
|
+
const finish = (o) => {
|
|
141
|
+
if (settled)
|
|
142
|
+
return;
|
|
143
|
+
settled = true;
|
|
144
|
+
for (const t of timers)
|
|
145
|
+
clearTimeout(t);
|
|
146
|
+
spec.signal.removeEventListener('abort', onAbort);
|
|
147
|
+
flushLines(true);
|
|
148
|
+
resolve({ ...o, stderrTail, aborted, timedOut });
|
|
149
|
+
};
|
|
150
|
+
child.on('error', (err) => finish({ code: null, signal: null, spawnError: err.code ?? 'spawn_failed' }));
|
|
151
|
+
child.on('close', (code, signal) => finish({ code, signal }));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/** An empty private temp dir for one job, removed afterwards (Mode A: the agent never sees a repo or project config). */
|
|
155
|
+
async function withEmptyTempDir(fn) {
|
|
156
|
+
const dir = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'resolflow-job-'));
|
|
157
|
+
if (process.platform !== 'win32')
|
|
158
|
+
fs.chmodSync(dir, 0o700);
|
|
159
|
+
try {
|
|
160
|
+
return await fn(dir);
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** Write a private file (0600) inside the job dir. */
|
|
167
|
+
function writeJobFile(dir, name, content) {
|
|
168
|
+
const file = path.join(dir, name);
|
|
169
|
+
fs.writeFileSync(file, content, { mode: 0o600 });
|
|
170
|
+
return file;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* The environment of an agent process: a short allow-list from this process (PATH, HOME, locale, temp, proxy
|
|
174
|
+
* settings) plus what the adapter adds (the API key). Nothing else leaks in — no other secrets of the shell.
|
|
175
|
+
*/
|
|
176
|
+
const PASS_ENV = ['PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'LANG', 'LC_ALL', 'LC_CTYPE', 'TERM', 'TMPDIR', 'TMP', 'TEMP', 'SYSTEMROOT', 'APPDATA', 'LOCALAPPDATA', 'USERPROFILE', 'HTTPS_PROXY', 'HTTP_PROXY', 'NO_PROXY', 'https_proxy', 'http_proxy', 'no_proxy', 'NODE_EXTRA_CA_CERTS', 'SSL_CERT_FILE'];
|
|
177
|
+
function agentEnv(extra, from = process.env) {
|
|
178
|
+
const env = {};
|
|
179
|
+
for (const k of PASS_ENV)
|
|
180
|
+
if (from[k] !== undefined)
|
|
181
|
+
env[k] = from[k];
|
|
182
|
+
return { ...env, NO_COLOR: '1', ...extra };
|
|
183
|
+
}
|