@mcpbridgeapp/cli 0.0.0-stage → 0.1.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 +345 -2
- package/dist/agents/broken.d.ts +24 -0
- package/dist/agents/broken.js +31 -0
- package/dist/agents/broken.js.map +1 -0
- package/dist/agents/cli.d.ts +47 -0
- package/dist/agents/cli.js +270 -0
- package/dist/agents/cli.js.map +1 -0
- package/dist/agents/health.d.ts +38 -0
- package/dist/agents/health.js +53 -0
- package/dist/agents/health.js.map +1 -0
- package/dist/agents/ollama.d.ts +27 -0
- package/dist/agents/ollama.js +118 -0
- package/dist/agents/ollama.js.map +1 -0
- package/dist/agents/openai.d.ts +35 -0
- package/dist/agents/openai.js +168 -0
- package/dist/agents/openai.js.map +1 -0
- package/dist/agents/outcome.d.ts +34 -0
- package/dist/agents/outcome.js +96 -0
- package/dist/agents/outcome.js.map +1 -0
- package/dist/agents/permissions.d.ts +44 -0
- package/dist/agents/permissions.js +68 -0
- package/dist/agents/permissions.js.map +1 -0
- package/dist/agents/registry.d.ts +56 -0
- package/dist/agents/registry.js +167 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/agents/shim.d.ts +35 -0
- package/dist/agents/shim.js +53 -0
- package/dist/agents/shim.js.map +1 -0
- package/dist/agents/types.d.ts +69 -0
- package/dist/agents/types.js +28 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/config.d.ts +99 -0
- package/dist/config.js +78 -0
- package/dist/config.js.map +1 -0
- package/dist/connect.d.ts +35 -0
- package/dist/connect.js +124 -0
- package/dist/connect.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +269 -0
- package/dist/index.js.map +1 -0
- package/dist/limit.d.ts +14 -0
- package/dist/limit.js +33 -0
- package/dist/limit.js.map +1 -0
- package/dist/protocol.d.ts +175 -0
- package/dist/protocol.js +86 -0
- package/dist/protocol.js.map +1 -0
- package/dist/secrets.d.ts +27 -0
- package/dist/secrets.js +53 -0
- package/dist/secrets.js.map +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.js +97 -0
- package/dist/server.js.map +1 -0
- package/dist/tasks.d.ts +48 -0
- package/dist/tasks.js +103 -0
- package/dist/tasks.js.map +1 -0
- package/dist/tools.d.ts +25 -0
- package/dist/tools.js +71 -0
- package/dist/tools.js.map +1 -0
- package/dist/ui/page.d.ts +7 -0
- package/dist/ui/page.js +428 -0
- package/dist/ui/page.js.map +1 -0
- package/dist/ui/presets.d.ts +22 -0
- package/dist/ui/presets.js +57 -0
- package/dist/ui/presets.js.map +1 -0
- package/dist/ui/server.d.ts +38 -0
- package/dist/ui/server.js +567 -0
- package/dist/ui/server.js.map +1 -0
- package/package.json +93 -4
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { effectiveTimeout, failed, truncate } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* An agent reached over an OpenAI-compatible chat-completions API.
|
|
4
|
+
*
|
|
5
|
+
* <p>This is the only door some providers leave open — xAI publishes no CLI, so Grok is
|
|
6
|
+
* reachable this way or not at all. One adapter serves all of them because the request
|
|
7
|
+
* shape is the same; only the base URL, model and key change.
|
|
8
|
+
*
|
|
9
|
+
* <p>The key is never put in a result, a note or a log: errors quote the provider's
|
|
10
|
+
* message, never the request.
|
|
11
|
+
*/
|
|
12
|
+
export class OpenAiAgent {
|
|
13
|
+
name;
|
|
14
|
+
config;
|
|
15
|
+
maxOutputChars;
|
|
16
|
+
requires;
|
|
17
|
+
// an HTTP model sees only what the prompt carries
|
|
18
|
+
readsFiles = false;
|
|
19
|
+
writesFiles = false;
|
|
20
|
+
runsCommands = false;
|
|
21
|
+
get timeoutMs() {
|
|
22
|
+
return this.config.timeoutMs ?? 300_000;
|
|
23
|
+
}
|
|
24
|
+
constructor(name, config, maxOutputChars) {
|
|
25
|
+
this.name = name;
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.maxOutputChars = maxOutputChars;
|
|
28
|
+
if (!config.baseUrl)
|
|
29
|
+
throw new Error(`agent "${name}": baseUrl is required`);
|
|
30
|
+
this.requires = this.endpoint;
|
|
31
|
+
}
|
|
32
|
+
get endpoint() {
|
|
33
|
+
return `${this.config.baseUrl.replace(/\/$/, '')}/chat/completions`;
|
|
34
|
+
}
|
|
35
|
+
get description() {
|
|
36
|
+
const model = this.config.model ?? 'no model set';
|
|
37
|
+
return `${this.config.description ?? 'A model over an OpenAI-compatible API'} (${model})`;
|
|
38
|
+
}
|
|
39
|
+
/** Resolved at call time, so a key exported after startup is picked up. */
|
|
40
|
+
key() {
|
|
41
|
+
if (this.config.apiKeyEnv)
|
|
42
|
+
return process.env[this.config.apiKeyEnv] || undefined;
|
|
43
|
+
return this.config.apiKey || undefined;
|
|
44
|
+
}
|
|
45
|
+
missingKeyDetail() {
|
|
46
|
+
return this.config.apiKeyEnv
|
|
47
|
+
? `no API key: set the ${this.config.apiKeyEnv} environment variable`
|
|
48
|
+
: `no API key: add "apiKeyEnv" naming an environment variable that holds one`;
|
|
49
|
+
}
|
|
50
|
+
async probe() {
|
|
51
|
+
if (!this.key()) {
|
|
52
|
+
return { available: false, detail: this.missingKeyDetail() };
|
|
53
|
+
}
|
|
54
|
+
// a key alone does not prove the endpoint answers, and /models is optional on some
|
|
55
|
+
// providers, so a refusal there is not treated as a failure
|
|
56
|
+
const models = await this.listModels();
|
|
57
|
+
if (!this.config.model) {
|
|
58
|
+
return {
|
|
59
|
+
available: false,
|
|
60
|
+
detail: models?.length
|
|
61
|
+
? `no model set. This endpoint offers: ${models.slice(0, 10).join(', ')}`
|
|
62
|
+
: 'no model set — add "model" with an id from the provider\'s documentation',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (models === null) {
|
|
66
|
+
return { available: true, detail: `key set for ${this.config.model}; endpoint not verified` };
|
|
67
|
+
}
|
|
68
|
+
if (models.length > 0 && !models.includes(this.config.model)) {
|
|
69
|
+
return {
|
|
70
|
+
available: false,
|
|
71
|
+
detail: `the endpoint does not list "${this.config.model}". Available: ${models.slice(0, 8).join(', ')}`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { available: true, detail: `${this.config.model} available at ${this.config.baseUrl}` };
|
|
75
|
+
}
|
|
76
|
+
async run(prompt, options) {
|
|
77
|
+
const started = Date.now();
|
|
78
|
+
const key = this.key();
|
|
79
|
+
if (!key)
|
|
80
|
+
return failed(this.name, this.missingKeyDetail(), Date.now() - started);
|
|
81
|
+
if (!this.config.model) {
|
|
82
|
+
return failed(this.name, `no model set for "${this.name}". Add "model" to its config; run \`mcpbridgeapp --agents\` to ` +
|
|
83
|
+
`see what this endpoint offers.`, Date.now() - started);
|
|
84
|
+
}
|
|
85
|
+
const note = options.cwd
|
|
86
|
+
? `workingDirectory (${options.cwd}) was ignored: ${this.name} is a model behind an HTTP ` +
|
|
87
|
+
`API and cannot read files. Inline what it should see in the prompt, or delegate to an ` +
|
|
88
|
+
`agent that list_agents reports with "readsFiles": true.`
|
|
89
|
+
: '';
|
|
90
|
+
const timeoutMs = effectiveTimeout(this.config.timeoutMs, options);
|
|
91
|
+
const controller = new AbortController();
|
|
92
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
93
|
+
try {
|
|
94
|
+
const response = await fetch(this.endpoint, {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: {
|
|
97
|
+
'Content-Type': 'application/json',
|
|
98
|
+
Authorization: `Bearer ${key}`,
|
|
99
|
+
...(this.config.headers ?? {}),
|
|
100
|
+
},
|
|
101
|
+
body: JSON.stringify({
|
|
102
|
+
model: this.config.model,
|
|
103
|
+
messages: [{ role: 'user', content: prompt }],
|
|
104
|
+
stream: false,
|
|
105
|
+
...(this.config.maxOutputTokens ? { max_tokens: this.config.maxOutputTokens } : {}),
|
|
106
|
+
}),
|
|
107
|
+
signal: controller.signal,
|
|
108
|
+
});
|
|
109
|
+
const payload = (await response.json().catch(() => ({})));
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
const reason = payload.error?.message ?? `HTTP ${response.status}`;
|
|
112
|
+
return failed(this.name, response.status === 401 || response.status === 403
|
|
113
|
+
? `the API key was rejected (${reason}). Check ${this.config.apiKeyEnv ?? 'the configured key'}.`
|
|
114
|
+
: `${this.name} returned ${response.status}: ${truncate(reason, 500).text}`, Date.now() - started);
|
|
115
|
+
}
|
|
116
|
+
const body = truncate(payload.choices?.[0]?.message?.content ?? '', this.maxOutputChars);
|
|
117
|
+
const usage = payload.usage;
|
|
118
|
+
// these calls cost money, so what they spent is worth telling the caller
|
|
119
|
+
const spend = usage?.total_tokens
|
|
120
|
+
? `used ${usage.total_tokens} tokens (${usage.prompt_tokens ?? '?'} in, ${usage.completion_tokens ?? '?'} out).`
|
|
121
|
+
: '';
|
|
122
|
+
const cut = payload.choices?.[0]?.finish_reason === 'length'
|
|
123
|
+
? 'The model stopped at its output limit, so the answer may be incomplete; raise maxOutputTokens.'
|
|
124
|
+
: '';
|
|
125
|
+
return {
|
|
126
|
+
agent: this.name,
|
|
127
|
+
succeeded: true,
|
|
128
|
+
output: body.text,
|
|
129
|
+
diagnostics: '',
|
|
130
|
+
notes: [note, spend, cut].filter(Boolean).join(' '),
|
|
131
|
+
durationMs: Date.now() - started,
|
|
132
|
+
timedOut: false,
|
|
133
|
+
outputTruncated: body.truncated,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
const aborted = controller.signal.aborted;
|
|
138
|
+
return failed(this.name, aborted
|
|
139
|
+
? `${this.name} did not answer within ${timeoutMs}ms. Try a smaller task.`
|
|
140
|
+
: `could not reach ${this.config.baseUrl}: ${error.message}`, Date.now() - started, aborted);
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** null means the endpoint does not offer a usable /models listing. */
|
|
147
|
+
async listModels() {
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
const timer = setTimeout(() => controller.abort(), 5_000);
|
|
150
|
+
try {
|
|
151
|
+
const response = await fetch(`${this.config.baseUrl.replace(/\/$/, '')}/models`, {
|
|
152
|
+
headers: { Authorization: `Bearer ${this.key() ?? ''}`, ...(this.config.headers ?? {}) },
|
|
153
|
+
signal: controller.signal,
|
|
154
|
+
});
|
|
155
|
+
if (!response.ok)
|
|
156
|
+
return null;
|
|
157
|
+
const payload = (await response.json());
|
|
158
|
+
return (payload.data ?? []).map((m) => m.id).filter((id) => Boolean(id));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
clearTimeout(timer);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../src/agents/openai.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading what an agent's run actually amounted to, beyond its exit code.
|
|
3
|
+
*
|
|
4
|
+
* <p>A field report from a real session found the gap: an agent whose every tool call was
|
|
5
|
+
* blocked by a broken hook exited 0 after 75 seconds with a polite paragraph explaining
|
|
6
|
+
* that it had read nothing, and the bridge reported `succeeded: true`. A caller that
|
|
7
|
+
* branches on that field -- which is what it is for -- would have recorded a non-answer
|
|
8
|
+
* as an answer. The same report found a failed call returning ~350 words of the caller's
|
|
9
|
+
* own prompt in `diagnostics`, plus the CLI banner, plus the error twice. These helpers
|
|
10
|
+
* address both, without pretending to understand the agent's reasoning.
|
|
11
|
+
*/
|
|
12
|
+
export type FailureKind = 'quota' | 'auth' | 'trust' | 'other';
|
|
13
|
+
/** What kind of failure this was, so a durable one can be remembered and shown up front. */
|
|
14
|
+
export declare function classifyFailure(text: string): {
|
|
15
|
+
kind: FailureKind;
|
|
16
|
+
retryAt?: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* stderr as worth reading: the caller's own prompt removed (some CLIs echo it back),
|
|
20
|
+
* repeated lines dropped, and -- when it is long -- the lines that look like errors kept
|
|
21
|
+
* ahead of the banner, within a hard cap.
|
|
22
|
+
*/
|
|
23
|
+
export declare function cleanDiagnostics(stderr: string, prompt: string, maxChars?: number): string;
|
|
24
|
+
export declare function selfReportedFailure(output: string): string | null;
|
|
25
|
+
/**
|
|
26
|
+
* An answer that is nothing but an announcement -- "I'll read payment.ts and report only
|
|
27
|
+
* what the file actually does." -- with nothing after it. That is what an agent leaves
|
|
28
|
+
* when it stops at its first tool step, typically one it was not allowed to take, and it
|
|
29
|
+
* read as `succeeded: true` with no answer in it. Short, opening with an intention, and
|
|
30
|
+
* without a single line that looks like a finding.
|
|
31
|
+
*/
|
|
32
|
+
export declare function onlyAnnounced(output: string): boolean;
|
|
33
|
+
/** Errors a CLI printed while still exiting 0 -- a failing hook is the case that prompted this. */
|
|
34
|
+
export declare function troubleOnSuccess(stderr: string): string | null;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { stripAnsi } from './types.js';
|
|
2
|
+
const QUOTA = /usage limit|rate[ -]?limit|quota|insufficient[ _]balance|balance (is )?exhausted|payment required|\b402\b|\b429\b|too many requests|out of credits|credits? (are )?exhausted/i;
|
|
3
|
+
const AUTH = /not (logged|signed) in|please (log|sign) ?in|authenticat|unauthori[sz]ed|\b401\b|ineligible|invalid api key|api key (was )?rejected|login required|session expired|re-?authenticate/i;
|
|
4
|
+
const TRUST = /trusted (directory|folder|workspace)|skip-trust|TRUST_WORKSPACE/i;
|
|
5
|
+
/** What kind of failure this was, so a durable one can be remembered and shown up front. */
|
|
6
|
+
export function classifyFailure(text) {
|
|
7
|
+
const retry = /try again (?:at|in|after) ([^.\n"]{1,40})/i.exec(text)?.[1]?.trim();
|
|
8
|
+
// quota first: "you've hit your usage limit, log in to upgrade" is a quota problem
|
|
9
|
+
const kind = QUOTA.test(text) ? 'quota' : TRUST.test(text) ? 'trust' : AUTH.test(text) ? 'auth' : 'other';
|
|
10
|
+
return retry ? { kind, retryAt: retry } : { kind };
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* stderr as worth reading: the caller's own prompt removed (some CLIs echo it back),
|
|
14
|
+
* repeated lines dropped, and -- when it is long -- the lines that look like errors kept
|
|
15
|
+
* ahead of the banner, within a hard cap.
|
|
16
|
+
*/
|
|
17
|
+
export function cleanDiagnostics(stderr, prompt, maxChars = 1_500) {
|
|
18
|
+
const promptLines = new Set(prompt.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length >= 12));
|
|
19
|
+
const flatPrompt = prompt.trim();
|
|
20
|
+
const seen = new Set();
|
|
21
|
+
const lines = [];
|
|
22
|
+
for (const raw of stripAnsi(stderr).split(/\r?\n/)) {
|
|
23
|
+
let line = raw.trimEnd();
|
|
24
|
+
if (flatPrompt.length >= 12 && line.includes(flatPrompt))
|
|
25
|
+
line = line.replace(flatPrompt, '[your prompt]');
|
|
26
|
+
const key = line.trim();
|
|
27
|
+
if (!key || promptLines.has(key) || seen.has(key))
|
|
28
|
+
continue;
|
|
29
|
+
seen.add(key);
|
|
30
|
+
lines.push(line);
|
|
31
|
+
}
|
|
32
|
+
const joined = lines.join('\n');
|
|
33
|
+
if (joined.length <= maxChars)
|
|
34
|
+
return joined;
|
|
35
|
+
const important = lines.filter((l) => /error|fail|denied|refus|limit|quota|cannot|can't|not (found|logged|signed|trusted)|invalid|unauthori|forbidden|expired|exhausted/i.test(l));
|
|
36
|
+
const tail = lines.slice(-5).filter((l) => !important.includes(l));
|
|
37
|
+
const picked = [...important, ...tail].join('\n');
|
|
38
|
+
const body = picked.length > 0 ? picked : joined;
|
|
39
|
+
return body.length <= maxChars ? body : `${body.slice(0, maxChars)}\n...[trimmed]`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The agent saying, in its own words, that it could not do the work. Deliberately narrow
|
|
43
|
+
* -- first person, about reading or running things -- so an answer that merely discusses
|
|
44
|
+
* a permissions error in someone's code is not mistaken for one.
|
|
45
|
+
*/
|
|
46
|
+
const SELF_REPORTED = [
|
|
47
|
+
/\bI(?:'m| am| was)? (?:currently |still )?(?:unable|not able|blocked|prevented|not permitted) (?:from |to )?(?:read|access|inspect|open|view|run|use|execute|see)\w*/i,
|
|
48
|
+
/\bI (?:cannot|can't|could not|couldn't) (?:read|access|inspect|open|view|run|use|execute|see) (?:any|the|your|these|those|this)\b/i,
|
|
49
|
+
/\bevery tool (?:call|execution|invocation)s? (?:is |are |was |were )?(?:failing|blocked|failed)/i,
|
|
50
|
+
/\b(?:all|my) tool (?:calls|executions) (?:are|were|keep) (?:failing|blocked|failed)/i,
|
|
51
|
+
];
|
|
52
|
+
export function selfReportedFailure(output) {
|
|
53
|
+
const head = output.slice(0, 4_000);
|
|
54
|
+
for (const pattern of SELF_REPORTED) {
|
|
55
|
+
const match = pattern.exec(head);
|
|
56
|
+
if (!match)
|
|
57
|
+
continue;
|
|
58
|
+
// quote the sentence it sits in, so the caller sees the agent's own reason
|
|
59
|
+
const start = Math.max(head.lastIndexOf('.', match.index) + 1, head.lastIndexOf('\n', match.index) + 1, 0);
|
|
60
|
+
const endDot = head.indexOf('.', match.index + match[0].length);
|
|
61
|
+
const end = endDot === -1 ? Math.min(head.length, match.index + 240) : endDot + 1;
|
|
62
|
+
return head.slice(start, end).trim().slice(0, 400);
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* An answer that is nothing but an announcement -- "I'll read payment.ts and report only
|
|
68
|
+
* what the file actually does." -- with nothing after it. That is what an agent leaves
|
|
69
|
+
* when it stops at its first tool step, typically one it was not allowed to take, and it
|
|
70
|
+
* read as `succeeded: true` with no answer in it. Short, opening with an intention, and
|
|
71
|
+
* without a single line that looks like a finding.
|
|
72
|
+
*/
|
|
73
|
+
export function onlyAnnounced(output) {
|
|
74
|
+
const text = output.trim();
|
|
75
|
+
if (!text || text.length > 500)
|
|
76
|
+
return false;
|
|
77
|
+
// an intention to go and look at something, not "I'll be brief: ..." followed by an answer
|
|
78
|
+
if (!PREAMBLE_OPENING.test(text))
|
|
79
|
+
return false;
|
|
80
|
+
// anything with structure or code is an answer, however it opens
|
|
81
|
+
if (/```|^\s*[-*•]\s|^\s*\d+[.)]\s|\w\.\w+:\d+|\n\s*\n/m.test(text))
|
|
82
|
+
return false;
|
|
83
|
+
const sentences = text.split(/(?<=[.!?])\s+/).filter((s) => s.trim());
|
|
84
|
+
return sentences.every((s) => INTENTION.test(s) || s.length < 60);
|
|
85
|
+
}
|
|
86
|
+
const STEP = '(?:read|check|look|inspect|review|confirm|examine|open|run|analy[sz]e|go through|explore|search|scan|start|begin|investigate|verify|find|trace|walk through|take a look)';
|
|
87
|
+
const PREAMBLE_OPENING = new RegExp(`^(?:(?:okay|ok|sure|alright|first|now|great),?\\s+)?(?:I'll|I will|I'm going to|I am going to|Let me|I need to|I'm about to)\\s+(?:first\\s+|now\\s+|start by\\s+|begin by\\s+)?${STEP}`, 'i');
|
|
88
|
+
const INTENTION = new RegExp(`\\b(?:I'll|I will|I'm going to|I am going to|Let me|I need to)\\s+(?:\\w+\\s+){0,3}?${STEP}`, 'i');
|
|
89
|
+
/** Errors a CLI printed while still exiting 0 -- a failing hook is the case that prompted this. */
|
|
90
|
+
export function troubleOnSuccess(stderr) {
|
|
91
|
+
const lines = stripAnsi(stderr).split(/\r?\n/).filter((l) => /PreToolUse|PostToolUse|hook.*(fail|error)|(fail|error).*hook|Cannot find module|MODULE_NOT_FOUND|tool (call|execution) failed/i.test(l));
|
|
92
|
+
if (lines.length === 0)
|
|
93
|
+
return null;
|
|
94
|
+
return [...new Set(lines.map((l) => l.trim()))].slice(0, 5).join('\n').slice(0, 800);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=outcome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outcome.js","sourceRoot":"","sources":["../../src/agents/outcome.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { CliAgentConfig } from '../config.js';
|
|
2
|
+
/**
|
|
3
|
+
* What a coding CLI may do when it runs unattended, and the flags that grant it.
|
|
4
|
+
*
|
|
5
|
+
* <p>Every one of these CLIs is cautious by default when there is no person to approve a
|
|
6
|
+
* step: Codex runs in a read-only sandbox, Antigravity asks before each tool, Claude Code denies edits and shell commands it
|
|
7
|
+
* was not pre-approved for, Grok Build and Gemini CLI stop at their approval prompts. That
|
|
8
|
+
* is the right default for a bridge any connected client can call -- but a second field
|
|
9
|
+
* report showed the cost of leaving it unsaid: five detailed briefs asking agents to write
|
|
10
|
+
* a test file and run it, against agents that could do neither, discovered only because
|
|
11
|
+
* one of them happened to explain itself. So the bridge now says, per agent, whether it
|
|
12
|
+
* can write files and run commands, and lets the owner switch that on per agent.
|
|
13
|
+
*
|
|
14
|
+
* <p>The flags are each CLI's documented ones at the time of writing. A CLI the bridge
|
|
15
|
+
* does not know gets no flags; its owner can declare what it can do with "capabilities".
|
|
16
|
+
*/
|
|
17
|
+
export interface Allow {
|
|
18
|
+
/** let it create and change files in the working directory */
|
|
19
|
+
edits?: boolean;
|
|
20
|
+
/** let it run shell commands (tests, builds) */
|
|
21
|
+
commands?: boolean;
|
|
22
|
+
/** treat the working directory as trusted, for CLIs that refuse untrusted ones */
|
|
23
|
+
trustDirectory?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface Permissions {
|
|
26
|
+
/** appended after the configured command */
|
|
27
|
+
args: string[];
|
|
28
|
+
env: Record<string, string>;
|
|
29
|
+
writesFiles: boolean;
|
|
30
|
+
runsCommands: boolean;
|
|
31
|
+
/** which switches mean anything for this CLI, so the control panel shows only those */
|
|
32
|
+
supports: {
|
|
33
|
+
edits: boolean;
|
|
34
|
+
commands: boolean;
|
|
35
|
+
trustDirectory: boolean;
|
|
36
|
+
};
|
|
37
|
+
/** e.g. that one switch implies the other for this CLI */
|
|
38
|
+
note: string;
|
|
39
|
+
}
|
|
40
|
+
type Known = 'claude' | 'codex' | 'grok' | 'gemini' | 'agy';
|
|
41
|
+
/** The CLI a command runs, by executable name, whatever path or extension it has. */
|
|
42
|
+
export declare function knownCli(executable: string): Known | null;
|
|
43
|
+
export declare function permissionsFor(config: CliAgentConfig): Permissions;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
/** The CLI a command runs, by executable name, whatever path or extension it has. */
|
|
3
|
+
export function knownCli(executable) {
|
|
4
|
+
const name = basename(executable.replace(/\\/g, '/')).toLowerCase().replace(/\.(exe|cmd|bat|ps1)$/, '');
|
|
5
|
+
return name === 'claude' || name === 'codex' || name === 'grok' || name === 'gemini' || name === 'agy' ? name : null;
|
|
6
|
+
}
|
|
7
|
+
export function permissionsFor(config) {
|
|
8
|
+
const allow = config.allow ?? {};
|
|
9
|
+
const declared = config.capabilities ?? {};
|
|
10
|
+
const none = { args: [], env: {}, note: '' };
|
|
11
|
+
switch (knownCli(config.command[0] ?? '')) {
|
|
12
|
+
case 'claude': {
|
|
13
|
+
const args = [
|
|
14
|
+
...(allow.edits ? ['--permission-mode', 'acceptEdits'] : []),
|
|
15
|
+
...(allow.commands ? ['--allowedTools', 'Bash'] : []),
|
|
16
|
+
];
|
|
17
|
+
return {
|
|
18
|
+
args, env: {}, writesFiles: Boolean(allow.edits), runsCommands: Boolean(allow.commands),
|
|
19
|
+
supports: { edits: true, commands: true, trustDirectory: false }, note: '',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
case 'codex': {
|
|
23
|
+
// one sandbox switch covers both: workspace-write lets it change files and run
|
|
24
|
+
// commands inside the working directory, still without network access
|
|
25
|
+
const on = Boolean(allow.edits || allow.commands);
|
|
26
|
+
return {
|
|
27
|
+
args: on ? ['--sandbox', 'workspace-write'] : [], env: {}, writesFiles: on, runsCommands: on,
|
|
28
|
+
supports: { edits: true, commands: true, trustDirectory: false },
|
|
29
|
+
note: 'For Codex, edits and commands are one sandbox setting: turning on either turns on both.',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
case 'grok': {
|
|
33
|
+
const on = Boolean(allow.edits || allow.commands);
|
|
34
|
+
return {
|
|
35
|
+
args: on ? ['--always-approve'] : [], env: {}, writesFiles: on, runsCommands: on,
|
|
36
|
+
supports: { edits: true, commands: true, trustDirectory: false },
|
|
37
|
+
note: 'Grok Build approves everything or nothing: turning on either turns on both.',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
case 'gemini': {
|
|
41
|
+
const mode = allow.commands ? 'yolo' : allow.edits ? 'auto_edit' : null;
|
|
42
|
+
return {
|
|
43
|
+
args: mode ? ['--approval-mode', mode] : [],
|
|
44
|
+
env: allow.trustDirectory ? { GEMINI_CLI_TRUST_WORKSPACE: 'true' } : {},
|
|
45
|
+
writesFiles: mode !== null, runsCommands: mode === 'yolo',
|
|
46
|
+
supports: { edits: true, commands: true, trustDirectory: true },
|
|
47
|
+
note: 'Allowing commands also allows edits for Gemini CLI.',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
case 'agy': {
|
|
51
|
+
// Antigravity: accept-edits mode approves file changes only; skipping permissions
|
|
52
|
+
// approves every tool, commands included
|
|
53
|
+
const args = allow.commands ? ['--dangerously-skip-permissions'] : allow.edits ? ['--mode', 'accept-edits'] : [];
|
|
54
|
+
return {
|
|
55
|
+
args, env: {}, writesFiles: args.length > 0, runsCommands: Boolean(allow.commands),
|
|
56
|
+
supports: { edits: true, commands: true, trustDirectory: false },
|
|
57
|
+
note: 'Allowing commands also allows edits for Antigravity.',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
default:
|
|
61
|
+
return {
|
|
62
|
+
...none,
|
|
63
|
+
writesFiles: Boolean(declared.writesFiles), runsCommands: Boolean(declared.runsCommands),
|
|
64
|
+
supports: { edits: false, commands: false, trustDirectory: false },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/agents/permissions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Config } from '../config.js';
|
|
2
|
+
import { HealthStore, type AgentState } from './health.js';
|
|
3
|
+
import { type Agent } from './types.js';
|
|
4
|
+
export interface AgentStatus {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
requires: string;
|
|
8
|
+
/**
|
|
9
|
+
* Whether the agent can be started — its command resolves, or its endpoint has a key.
|
|
10
|
+
* Not whether its account works: checking that would mean spending a call on every
|
|
11
|
+
* listing, so a signed-out CLI lists as available and explains itself when asked.
|
|
12
|
+
*/
|
|
13
|
+
available: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* What to expect from a call, combining the probe with what earlier calls showed:
|
|
16
|
+
* "ready", "unavailable" (not installed or not configured), or a remembered durable
|
|
17
|
+
* failure -- "quota-exhausted", "sign-in-failed", "directory-not-trusted". Anything but
|
|
18
|
+
* "ready" also makes `available` false.
|
|
19
|
+
*/
|
|
20
|
+
status: AgentState;
|
|
21
|
+
/** whether pointing it at a directory actually lets it read those files */
|
|
22
|
+
readsFiles: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Whether, as configured, it may change files and run commands. Both are off unless the
|
|
25
|
+
* owner allowed them for this agent; a task that needs either belongs with an agent
|
|
26
|
+
* that has it, or should ask only for a reading and a report.
|
|
27
|
+
*/
|
|
28
|
+
writesFiles: boolean;
|
|
29
|
+
runsCommands: boolean;
|
|
30
|
+
/** the longest one call may run before it is stopped */
|
|
31
|
+
timeoutSeconds: number;
|
|
32
|
+
detail: string;
|
|
33
|
+
/** the most recent failed call, if any, and when; ISO timestamps */
|
|
34
|
+
lastError?: string;
|
|
35
|
+
lastErrorAt?: string;
|
|
36
|
+
/** when the agent said, e.g., "try again at 4:22 AM" */
|
|
37
|
+
retryAt?: string;
|
|
38
|
+
lastSuccessAt?: string;
|
|
39
|
+
calls: number;
|
|
40
|
+
}
|
|
41
|
+
export declare class AgentRegistry {
|
|
42
|
+
readonly health: HealthStore;
|
|
43
|
+
private readonly agents;
|
|
44
|
+
/**
|
|
45
|
+
* The health store is passed in so it can outlive a registry: the control panel rebuilds
|
|
46
|
+
* the registry whenever the config changes, and what was learned must survive that.
|
|
47
|
+
*/
|
|
48
|
+
private readonly misconfigured;
|
|
49
|
+
constructor(config: Config, health?: HealthStore);
|
|
50
|
+
/** Why an agent's configuration was rejected, or null if it was accepted. */
|
|
51
|
+
configError(name: string): string | null;
|
|
52
|
+
find(name: string): Agent | undefined;
|
|
53
|
+
names(): string[];
|
|
54
|
+
/** Probes every agent at once, since each probe is a filesystem or socket check. */
|
|
55
|
+
statuses(): Promise<AgentStatus[]>;
|
|
56
|
+
}
|