@mcpbridgeapp/cli 0.0.0-stage → 0.1.0
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 +346 -3
- 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 +95 -6
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* The wire protocol between a local agent (`mcpbridgeapp connect`, running where the AI CLIs
|
|
4
|
+
* are signed in) and a relay (the hosted endpoint clients talk to). JSON messages over a
|
|
5
|
+
* WebSocket the agent opens outbound, so nothing on the user's machine listens.
|
|
6
|
+
*
|
|
7
|
+
* The relay only ever forwards: it never sees a credential, only prompts and answers.
|
|
8
|
+
*/
|
|
9
|
+
export const PROTOCOL_VERSION = 1;
|
|
10
|
+
export const AgentStatusSchema = z.object({
|
|
11
|
+
name: z.string(),
|
|
12
|
+
description: z.string(),
|
|
13
|
+
requires: z.string(),
|
|
14
|
+
available: z.boolean(),
|
|
15
|
+
readsFiles: z.boolean(),
|
|
16
|
+
detail: z.string(),
|
|
17
|
+
// optional, so an older agent's hello still parses
|
|
18
|
+
status: z.string().optional(),
|
|
19
|
+
writesFiles: z.boolean().optional(),
|
|
20
|
+
runsCommands: z.boolean().optional(),
|
|
21
|
+
timeoutSeconds: z.number().optional(),
|
|
22
|
+
lastError: z.string().optional(),
|
|
23
|
+
lastErrorAt: z.string().optional(),
|
|
24
|
+
retryAt: z.string().optional(),
|
|
25
|
+
lastSuccessAt: z.string().optional(),
|
|
26
|
+
calls: z.number().optional(),
|
|
27
|
+
});
|
|
28
|
+
export const TOOL_NAME = z.enum(['list_agents', 'ask_agent', 'start_task', 'get_task']);
|
|
29
|
+
// ---- agent -> relay ---------------------------------------------------------
|
|
30
|
+
/** First message after the socket opens. */
|
|
31
|
+
export const HelloMessage = z.object({
|
|
32
|
+
type: z.literal('hello'),
|
|
33
|
+
protocol: z.literal(PROTOCOL_VERSION),
|
|
34
|
+
agentVersion: z.string(),
|
|
35
|
+
device: z.string().min(1).max(80),
|
|
36
|
+
agents: z.array(AgentStatusSchema),
|
|
37
|
+
});
|
|
38
|
+
/** A refreshed agent list, so a dashboard can show what is installed without a call. */
|
|
39
|
+
export const AgentsMessage = z.object({
|
|
40
|
+
type: z.literal('agents'),
|
|
41
|
+
agents: z.array(AgentStatusSchema),
|
|
42
|
+
});
|
|
43
|
+
/** The answer to a call. `ok: false` means the tool itself threw; `payload` is the message. */
|
|
44
|
+
export const ResultMessage = z.object({
|
|
45
|
+
type: z.literal('result'),
|
|
46
|
+
id: z.string(),
|
|
47
|
+
ok: z.boolean(),
|
|
48
|
+
payload: z.unknown(),
|
|
49
|
+
});
|
|
50
|
+
export const AgentToRelay = z.discriminatedUnion('type', [HelloMessage, AgentsMessage, ResultMessage]);
|
|
51
|
+
// ---- relay -> agent ---------------------------------------------------------
|
|
52
|
+
export const WelcomeMessage = z.object({
|
|
53
|
+
type: z.literal('welcome'),
|
|
54
|
+
/** what the relay will call this device, which may differ if the name was taken */
|
|
55
|
+
device: z.string(),
|
|
56
|
+
});
|
|
57
|
+
export const CallMessage = z.object({
|
|
58
|
+
type: z.literal('call'),
|
|
59
|
+
id: z.string(),
|
|
60
|
+
tool: TOOL_NAME,
|
|
61
|
+
args: z.record(z.string(), z.unknown()),
|
|
62
|
+
});
|
|
63
|
+
/** Sent before the relay closes the socket on purpose, so the agent knows not to retry. */
|
|
64
|
+
export const GoodbyeMessage = z.object({
|
|
65
|
+
type: z.literal('goodbye'),
|
|
66
|
+
reason: z.string(),
|
|
67
|
+
retry: z.boolean(),
|
|
68
|
+
});
|
|
69
|
+
export const RelayToAgent = z.discriminatedUnion('type', [WelcomeMessage, CallMessage, GoodbyeMessage]);
|
|
70
|
+
export function parseAgentMessage(raw) {
|
|
71
|
+
const result = AgentToRelay.safeParse(tryJson(raw));
|
|
72
|
+
return result.success ? result.data : null;
|
|
73
|
+
}
|
|
74
|
+
export function parseRelayMessage(raw) {
|
|
75
|
+
const result = RelayToAgent.safeParse(tryJson(raw));
|
|
76
|
+
return result.success ? result.data : null;
|
|
77
|
+
}
|
|
78
|
+
function tryJson(raw) {
|
|
79
|
+
try {
|
|
80
|
+
return JSON.parse(raw);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What must not sit in config.json: API keys and the relay token.
|
|
3
|
+
*
|
|
4
|
+
* <p>config.json is the file people paste into issues and copy between machines, so it
|
|
5
|
+
* names keys by environment variable (`apiKeyEnv`) and never holds one. The values live
|
|
6
|
+
* here instead, next to it, readable only by the user where the platform allows. At
|
|
7
|
+
* startup they are copied into the process environment for any variable not already set,
|
|
8
|
+
* so a key saved once works in every window -- no `setx`, no terminal restart -- and a
|
|
9
|
+
* variable exported the usual way still wins.
|
|
10
|
+
*/
|
|
11
|
+
export interface Secrets {
|
|
12
|
+
/** environment variable name -> value */
|
|
13
|
+
keys: Record<string, string>;
|
|
14
|
+
relay?: {
|
|
15
|
+
url: string;
|
|
16
|
+
token: string;
|
|
17
|
+
device?: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export declare function secretsPath(): string;
|
|
21
|
+
export declare function loadSecrets(path?: string): Secrets;
|
|
22
|
+
/** Written whole, through a temporary file, so a crash mid-write cannot leave half a file. */
|
|
23
|
+
export declare function saveSecrets(secrets: Secrets, path?: string): void;
|
|
24
|
+
/** Saved keys become environment variables, without overriding one already exported. */
|
|
25
|
+
export declare function applySecretsToEnv(secrets: Secrets, env?: NodeJS.ProcessEnv): void;
|
|
26
|
+
/** Enough to recognise a key on screen without showing it. */
|
|
27
|
+
export declare function maskSecret(value: string | undefined): string | null;
|
package/dist/secrets.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { configPath } from './config.js';
|
|
4
|
+
export function secretsPath() {
|
|
5
|
+
return process.env['MCPBRIDGE_SECRETS'] ?? join(dirname(configPath()), 'secrets.json');
|
|
6
|
+
}
|
|
7
|
+
export function loadSecrets(path = secretsPath()) {
|
|
8
|
+
let raw;
|
|
9
|
+
try {
|
|
10
|
+
raw = readFileSync(path, 'utf8');
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return { keys: {} };
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
const keys = Object.fromEntries(Object.entries(parsed.keys ?? {}).filter((e) => typeof e[1] === 'string'));
|
|
18
|
+
const relay = parsed.relay && typeof parsed.relay.url === 'string' && typeof parsed.relay.token === 'string'
|
|
19
|
+
? parsed.relay
|
|
20
|
+
: undefined;
|
|
21
|
+
return relay ? { keys, relay } : { keys };
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw new Error(`${path} is not valid JSON: ${error.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Written whole, through a temporary file, so a crash mid-write cannot leave half a file. */
|
|
28
|
+
export function saveSecrets(secrets, path = secretsPath()) {
|
|
29
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
30
|
+
const temporary = `${path}.tmp`;
|
|
31
|
+
writeFileSync(temporary, `${JSON.stringify(secrets, null, 2)}\n`, { mode: 0o600 });
|
|
32
|
+
renameSync(temporary, path);
|
|
33
|
+
try {
|
|
34
|
+
chmodSync(path, 0o600);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Windows has no POSIX modes; the user profile directory is what protects it there
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Saved keys become environment variables, without overriding one already exported. */
|
|
41
|
+
export function applySecretsToEnv(secrets, env = process.env) {
|
|
42
|
+
for (const [name, value] of Object.entries(secrets.keys)) {
|
|
43
|
+
if (!env[name])
|
|
44
|
+
env[name] = value;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Enough to recognise a key on screen without showing it. */
|
|
48
|
+
export function maskSecret(value) {
|
|
49
|
+
if (!value)
|
|
50
|
+
return null;
|
|
51
|
+
return value.length <= 8 ? '••••' : `••••${value.slice(-4)}`;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { AgentRegistry } from './agents/registry.js';
|
|
3
|
+
import type { ConcurrencyLimit } from './limit.js';
|
|
4
|
+
import type { TaskStore } from './tasks.js';
|
|
5
|
+
export declare function createServer(registry: AgentRegistry, tasks: TaskStore, limit: ConcurrencyLimit): McpServer;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { executeTool } from './tools.js';
|
|
4
|
+
const PACKAGE_NAME = 'mcpbridgeapp';
|
|
5
|
+
const PACKAGE_VERSION = '0.1.0';
|
|
6
|
+
function json(value) {
|
|
7
|
+
return { content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] };
|
|
8
|
+
}
|
|
9
|
+
// coerced: a client holding an older copy of this schema, or one that types every
|
|
10
|
+
// argument as text, sends "120" -- refusing that over a type is no help to anyone
|
|
11
|
+
const TIMEOUT_ARG = z.coerce
|
|
12
|
+
.number()
|
|
13
|
+
.int()
|
|
14
|
+
.min(10)
|
|
15
|
+
.max(3600)
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Stop the agent after this many seconds. Only shortens its configured timeout.');
|
|
18
|
+
export function createServer(registry, tasks, limit) {
|
|
19
|
+
const context = { registry, tasks, limit };
|
|
20
|
+
const server = new McpServer({ name: PACKAGE_NAME, version: PACKAGE_VERSION }, {
|
|
21
|
+
instructions: [
|
|
22
|
+
'MCP Bridge hands work to other AI agents installed on this machine.',
|
|
23
|
+
'Call list_agents first: an agent can be configured without being available, and',
|
|
24
|
+
'"available": false says why. Then ask_agent to delegate and get the answer in the',
|
|
25
|
+
'same call. The agent you delegate to cannot see this conversation or these files,',
|
|
26
|
+
'so write a self-contained prompt and pass workingDirectory for anything about code.',
|
|
27
|
+
'Agents are read-only unless list_agents says otherwise ("writesFiles", "runsCommands"):',
|
|
28
|
+
'shape the task to what the agent may do. Each call stops at the agent\'s "timeoutSeconds".',
|
|
29
|
+
'For work that would outlast this request, use start_task and poll get_task; only a few',
|
|
30
|
+
'run at once, and the rest wait as "queued".',
|
|
31
|
+
].join(' '),
|
|
32
|
+
});
|
|
33
|
+
server.registerTool('list_agents', {
|
|
34
|
+
title: 'List delegable agents',
|
|
35
|
+
description: 'List the AI agents this bridge can delegate to, and what to expect from each. ' +
|
|
36
|
+
'"available" is true only when the command was found and no recent call hit a durable ' +
|
|
37
|
+
'failure. "status" says which: "ready", "unavailable", or what an earlier call ran into -- ' +
|
|
38
|
+
'"quota-exhausted", "sign-in-failed", "directory-not-trusted" -- with "lastError", ' +
|
|
39
|
+
'"lastErrorAt" and, when the agent said so, "retryAt". A "ready" agent that has never ' +
|
|
40
|
+
'been called ("calls": 0) is still unproven: its account may not work. ' +
|
|
41
|
+
'"detail" carries the reason. ' + 'Agents are read-only unless their owner allowed more: "writesFiles" and "runsCommands" ' +
|
|
42
|
+
'say whether one may change files or run commands such as tests. If neither is true, ask ' +
|
|
43
|
+
'it to read and report, not to write a file or run something. ' +
|
|
44
|
+
'"timeoutSeconds" is how long one call may run. "readsFiles" says whether passing ' +
|
|
45
|
+
'workingDirectory actually lets that agent see your files — when it is false, put the ' +
|
|
46
|
+
'content in the prompt instead.',
|
|
47
|
+
inputSchema: {},
|
|
48
|
+
}, async () => json(await executeTool('list_agents', {}, context)));
|
|
49
|
+
server.registerTool('ask_agent', {
|
|
50
|
+
title: 'Delegate a task and wait',
|
|
51
|
+
description: 'Hand a task to another AI agent and wait for its answer. One call does the whole ' +
|
|
52
|
+
'thing. The agent starts fresh with no knowledge of this conversation and cannot see ' +
|
|
53
|
+
'your files unless you point it at them, so make the prompt self-contained and pass ' +
|
|
54
|
+
'workingDirectory when the task is about code in a project. If "succeeded" is false, ' +
|
|
55
|
+
'read "diagnostics" -- that includes an agent that ran but says it could not do the ' +
|
|
56
|
+
'work. If "notes" is set, read it before trusting "output". Each call is bounded by ' +
|
|
57
|
+
'the agent\'s timeout (5 minutes unless configured); pass timeoutSeconds for less. ' +
|
|
58
|
+
'For a job long enough to outlast this request, use start_task.',
|
|
59
|
+
inputSchema: {
|
|
60
|
+
agent: z.string().describe('Which agent to use, exactly as list_agents names it, e.g. "gemini".'),
|
|
61
|
+
prompt: z.string().min(1).describe('The full self-contained task or question for that agent.'),
|
|
62
|
+
workingDirectory: z
|
|
63
|
+
.string()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe('Absolute path to run the agent in, so it can read the project files.'),
|
|
66
|
+
timeoutSeconds: TIMEOUT_ARG,
|
|
67
|
+
},
|
|
68
|
+
}, async (args) => json(await executeTool('ask_agent', args, context)));
|
|
69
|
+
server.registerTool('start_task', {
|
|
70
|
+
title: 'Delegate without waiting',
|
|
71
|
+
description: 'Start a delegated task and return its id immediately, for work that would outlast ' +
|
|
72
|
+
'this request. Poll get_task with the id. Prefer ask_agent for anything that finishes ' +
|
|
73
|
+
'in a minute or two — it is one call instead of several. ' + 'Only a few delegations run at once (two by default); more wait their turn, shown as ' +
|
|
74
|
+
'"queued" by get_task, and several tasks on one agent spend its account\'s quota together. ' +
|
|
75
|
+
'An agent whose last call hit its usage limit or a failed sign-in is not started again ' +
|
|
76
|
+
'until that clears; the result says so at once.',
|
|
77
|
+
inputSchema: {
|
|
78
|
+
agent: z.string().describe('Which agent to use, exactly as list_agents names it.'),
|
|
79
|
+
prompt: z.string().min(1).describe('The full self-contained task for that agent.'),
|
|
80
|
+
workingDirectory: z.string().optional().describe('Absolute path to run the agent in.'),
|
|
81
|
+
timeoutSeconds: TIMEOUT_ARG,
|
|
82
|
+
},
|
|
83
|
+
}, async (args) => json(await executeTool('start_task', args, context)));
|
|
84
|
+
server.registerTool('get_task', {
|
|
85
|
+
title: 'Check a delegated task',
|
|
86
|
+
description: 'Look up a task started with start_task. "queued" means it is waiting for a free slot ' +
|
|
87
|
+
'("queuePosition" ahead of it) and its timeout has not started; "running" means the agent ' +
|
|
88
|
+
'is working, with its latest output in "outputSoFar" and the time of it in "lastOutputAt" ' +
|
|
89
|
+
'-- a long silence there is the sign of a stuck agent. Wait between checks rather than ' +
|
|
90
|
+
'polling in a tight loop. Once "finished", "result" holds the same shape ask_agent returns.',
|
|
91
|
+
inputSchema: {
|
|
92
|
+
id: z.string().describe('The task id returned by start_task.'),
|
|
93
|
+
},
|
|
94
|
+
}, async (args) => json(await executeTool('get_task', args, context)));
|
|
95
|
+
return server;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/tasks.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Agent, AgentResult, RunOptions } from './agents/types.js';
|
|
2
|
+
import type { ConcurrencyLimit } from './limit.js';
|
|
3
|
+
/**
|
|
4
|
+
* What get_task shows. Not the prompt: the caller wrote it and has it, and handing it back
|
|
5
|
+
* on every poll spends the caller's context for nothing.
|
|
6
|
+
*/
|
|
7
|
+
export interface Task {
|
|
8
|
+
id: string;
|
|
9
|
+
agent: string;
|
|
10
|
+
/**
|
|
11
|
+
* "queued" while it waits for one of the concurrent slots -- its timeout has not started
|
|
12
|
+
* yet -- then "running", then "finished". A queued task was reported as running before,
|
|
13
|
+
* which made one waiting behind others look like an agent hung for fifteen minutes.
|
|
14
|
+
*/
|
|
15
|
+
status: 'queued' | 'running' | 'finished';
|
|
16
|
+
/** how many tasks are ahead of it while queued */
|
|
17
|
+
queuePosition?: number;
|
|
18
|
+
createdAt: string;
|
|
19
|
+
startedAt?: string;
|
|
20
|
+
finishedAt?: string;
|
|
21
|
+
/** the most recent output, while it runs: proof of life, and often most of an answer */
|
|
22
|
+
outputSoFar?: string;
|
|
23
|
+
lastOutputAt?: string;
|
|
24
|
+
/** a caveat known when the task was accepted, e.g. a workingDirectory the agent cannot use */
|
|
25
|
+
notes?: string;
|
|
26
|
+
result?: AgentResult;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Tasks for work that outlives a single tool call. ask_agent is the normal path; this
|
|
30
|
+
* exists because a long delegation otherwise dies on the caller's request timeout, which
|
|
31
|
+
* is a property of the client and not something the bridge can raise.
|
|
32
|
+
*/
|
|
33
|
+
export declare class TaskStore {
|
|
34
|
+
private readonly limit;
|
|
35
|
+
private readonly tasks;
|
|
36
|
+
/** queued task ids in the order the limit will release them */
|
|
37
|
+
private readonly queue;
|
|
38
|
+
constructor(limit: ConcurrencyLimit);
|
|
39
|
+
start(agent: Agent, prompt: string, options: RunOptions, notes?: string): Task;
|
|
40
|
+
get(id: string): Task | undefined;
|
|
41
|
+
list(): Task[];
|
|
42
|
+
/** A copy with the queue position filled in, so callers never hold the live object. */
|
|
43
|
+
private view;
|
|
44
|
+
private dequeue;
|
|
45
|
+
private finish;
|
|
46
|
+
/** Keeps the store from growing without bound in a long-lived process. */
|
|
47
|
+
private prune;
|
|
48
|
+
}
|
package/dist/tasks.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { stripAnsi } from './agents/types.js';
|
|
3
|
+
const KEEP_FINISHED_MS = 60 * 60 * 1000;
|
|
4
|
+
const MAX_TASKS = 200;
|
|
5
|
+
const PROGRESS_CHARS = 2_000;
|
|
6
|
+
/**
|
|
7
|
+
* Tasks for work that outlives a single tool call. ask_agent is the normal path; this
|
|
8
|
+
* exists because a long delegation otherwise dies on the caller's request timeout, which
|
|
9
|
+
* is a property of the client and not something the bridge can raise.
|
|
10
|
+
*/
|
|
11
|
+
export class TaskStore {
|
|
12
|
+
limit;
|
|
13
|
+
tasks = new Map();
|
|
14
|
+
/** queued task ids in the order the limit will release them */
|
|
15
|
+
queue = [];
|
|
16
|
+
constructor(limit) {
|
|
17
|
+
this.limit = limit;
|
|
18
|
+
}
|
|
19
|
+
start(agent, prompt, options, notes) {
|
|
20
|
+
this.prune();
|
|
21
|
+
const task = {
|
|
22
|
+
id: randomUUID(),
|
|
23
|
+
agent: agent.name,
|
|
24
|
+
status: 'queued',
|
|
25
|
+
createdAt: new Date().toISOString(),
|
|
26
|
+
...(notes ? { notes } : {}),
|
|
27
|
+
};
|
|
28
|
+
this.tasks.set(task.id, task);
|
|
29
|
+
this.queue.push(task.id);
|
|
30
|
+
const onOutput = (chunk) => {
|
|
31
|
+
const text = `${task.outputSoFar ?? ''}${stripAnsi(chunk)}`;
|
|
32
|
+
task.outputSoFar = text.length > PROGRESS_CHARS ? `…${text.slice(-PROGRESS_CHARS)}` : text;
|
|
33
|
+
task.lastOutputAt = new Date().toISOString();
|
|
34
|
+
};
|
|
35
|
+
// deliberately not awaited: the point is to return an id now
|
|
36
|
+
void this.limit
|
|
37
|
+
.run(() => {
|
|
38
|
+
this.dequeue(task.id);
|
|
39
|
+
task.status = 'running';
|
|
40
|
+
task.startedAt = new Date().toISOString();
|
|
41
|
+
return agent.run(prompt, { ...options, onOutput });
|
|
42
|
+
})
|
|
43
|
+
.then((result) => this.finish(task.id, result))
|
|
44
|
+
.catch((error) => this.finish(task.id, {
|
|
45
|
+
agent: agent.name,
|
|
46
|
+
succeeded: false,
|
|
47
|
+
output: '',
|
|
48
|
+
diagnostics: `the task threw: ${error.message}`,
|
|
49
|
+
notes: '',
|
|
50
|
+
durationMs: 0,
|
|
51
|
+
timedOut: false,
|
|
52
|
+
outputTruncated: false,
|
|
53
|
+
}));
|
|
54
|
+
return this.view(task);
|
|
55
|
+
}
|
|
56
|
+
get(id) {
|
|
57
|
+
const task = this.tasks.get(id);
|
|
58
|
+
return task ? this.view(task) : undefined;
|
|
59
|
+
}
|
|
60
|
+
list() {
|
|
61
|
+
return [...this.tasks.values()].map((t) => this.view(t)).sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
62
|
+
}
|
|
63
|
+
/** A copy with the queue position filled in, so callers never hold the live object. */
|
|
64
|
+
view(task) {
|
|
65
|
+
const copy = { ...task };
|
|
66
|
+
if (task.status === 'queued')
|
|
67
|
+
copy.queuePosition = this.queue.indexOf(task.id);
|
|
68
|
+
if (task.status === 'finished')
|
|
69
|
+
delete copy.outputSoFar;
|
|
70
|
+
return copy;
|
|
71
|
+
}
|
|
72
|
+
dequeue(id) {
|
|
73
|
+
const index = this.queue.indexOf(id);
|
|
74
|
+
if (index !== -1)
|
|
75
|
+
this.queue.splice(index, 1);
|
|
76
|
+
}
|
|
77
|
+
finish(id, result) {
|
|
78
|
+
const task = this.tasks.get(id);
|
|
79
|
+
if (!task)
|
|
80
|
+
return;
|
|
81
|
+
this.dequeue(id);
|
|
82
|
+
task.status = 'finished';
|
|
83
|
+
task.result = result;
|
|
84
|
+
task.finishedAt = new Date().toISOString();
|
|
85
|
+
}
|
|
86
|
+
/** Keeps the store from growing without bound in a long-lived process. */
|
|
87
|
+
prune() {
|
|
88
|
+
const cutoff = Date.now() - KEEP_FINISHED_MS;
|
|
89
|
+
for (const [id, task] of this.tasks) {
|
|
90
|
+
if (task.finishedAt && Date.parse(task.finishedAt) < cutoff)
|
|
91
|
+
this.tasks.delete(id);
|
|
92
|
+
}
|
|
93
|
+
while (this.tasks.size >= MAX_TASKS) {
|
|
94
|
+
const oldestFinished = [...this.tasks.values()]
|
|
95
|
+
.filter((t) => t.status === 'finished')
|
|
96
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt))[0];
|
|
97
|
+
if (!oldestFinished)
|
|
98
|
+
break;
|
|
99
|
+
this.tasks.delete(oldestFinished.id);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=tasks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks.js","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { AgentRegistry } from './agents/registry.js';
|
|
2
|
+
import type { ConcurrencyLimit } from './limit.js';
|
|
3
|
+
import type { TaskStore } from './tasks.js';
|
|
4
|
+
/**
|
|
5
|
+
* The four tools, as plain functions over plain JSON. Both front doors — the MCP server a
|
|
6
|
+
* client talks to directly, and the relay connection that forwards calls from a hosted
|
|
7
|
+
* endpoint — run exactly this, so a tool cannot behave differently depending on how the
|
|
8
|
+
* request arrived.
|
|
9
|
+
*/
|
|
10
|
+
export declare const TOOL_NAMES: readonly ["list_agents", "ask_agent", "start_task", "get_task"];
|
|
11
|
+
export type ToolName = (typeof TOOL_NAMES)[number];
|
|
12
|
+
export interface ToolContext {
|
|
13
|
+
registry: AgentRegistry;
|
|
14
|
+
tasks: TaskStore;
|
|
15
|
+
limit: ConcurrencyLimit;
|
|
16
|
+
}
|
|
17
|
+
export interface DelegationArgs {
|
|
18
|
+
agent: string;
|
|
19
|
+
prompt: string;
|
|
20
|
+
workingDirectory?: string;
|
|
21
|
+
/** the caller's bound on this one call; only ever shortens the agent's own timeout */
|
|
22
|
+
timeoutSeconds?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function executeTool(name: ToolName, args: Record<string, unknown>, { registry, tasks, limit }: ToolContext): Promise<unknown>;
|
|
25
|
+
export declare function isToolName(name: string): name is ToolName;
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The four tools, as plain functions over plain JSON. Both front doors — the MCP server a
|
|
3
|
+
* client talks to directly, and the relay connection that forwards calls from a hosted
|
|
4
|
+
* endpoint — run exactly this, so a tool cannot behave differently depending on how the
|
|
5
|
+
* request arrived.
|
|
6
|
+
*/
|
|
7
|
+
export const TOOL_NAMES = ['list_agents', 'ask_agent', 'start_task', 'get_task'];
|
|
8
|
+
export async function executeTool(name, args, { registry, tasks, limit }) {
|
|
9
|
+
switch (name) {
|
|
10
|
+
case 'list_agents':
|
|
11
|
+
return registry.statuses();
|
|
12
|
+
case 'ask_agent': {
|
|
13
|
+
const parsed = delegation(args);
|
|
14
|
+
const target = registry.find(parsed.agent);
|
|
15
|
+
if (!target)
|
|
16
|
+
return unknownAgent(parsed.agent, registry);
|
|
17
|
+
return limit.run(() => target.run(parsed.prompt, runOptions(parsed)));
|
|
18
|
+
}
|
|
19
|
+
case 'start_task': {
|
|
20
|
+
const parsed = delegation(args);
|
|
21
|
+
const target = registry.find(parsed.agent);
|
|
22
|
+
if (!target)
|
|
23
|
+
return { started: false, ...unknownAgent(parsed.agent, registry) };
|
|
24
|
+
// said when the task is accepted, not only in the result minutes later
|
|
25
|
+
const ignored = parsed.workingDirectory && !target.readsFiles
|
|
26
|
+
? `workingDirectory will be ignored: ${target.name} cannot read files, so it sees only the prompt.`
|
|
27
|
+
: undefined;
|
|
28
|
+
const task = tasks.start(target, parsed.prompt, runOptions(parsed), ignored);
|
|
29
|
+
return {
|
|
30
|
+
started: true, id: task.id, agent: task.agent, status: task.status,
|
|
31
|
+
...(task.queuePosition !== undefined ? { queuePosition: task.queuePosition } : {}),
|
|
32
|
+
...(ignored ? { notes: ignored } : {}),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
case 'get_task': {
|
|
36
|
+
const id = String(args['id'] ?? '');
|
|
37
|
+
const task = tasks.get(id);
|
|
38
|
+
return task ?? { id, found: false, diagnostics: 'no task with that id; it may have expired' };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function isToolName(name) {
|
|
43
|
+
return TOOL_NAMES.includes(name);
|
|
44
|
+
}
|
|
45
|
+
function delegation(args) {
|
|
46
|
+
const agent = String(args['agent'] ?? '');
|
|
47
|
+
const prompt = String(args['prompt'] ?? '');
|
|
48
|
+
const wd = args['workingDirectory'];
|
|
49
|
+
const seconds = Number(args['timeoutSeconds']);
|
|
50
|
+
return {
|
|
51
|
+
agent, prompt,
|
|
52
|
+
...(typeof wd === 'string' && wd ? { workingDirectory: wd } : {}),
|
|
53
|
+
...(Number.isFinite(seconds) && seconds > 0 ? { timeoutSeconds: Math.min(seconds, 3_600) } : {}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function runOptions(args) {
|
|
57
|
+
return {
|
|
58
|
+
...(args.workingDirectory ? { cwd: args.workingDirectory } : {}),
|
|
59
|
+
...(args.timeoutSeconds ? { timeoutMs: args.timeoutSeconds * 1000 } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function unknownAgent(agent, registry) {
|
|
63
|
+
return {
|
|
64
|
+
agent,
|
|
65
|
+
succeeded: false,
|
|
66
|
+
output: '',
|
|
67
|
+
diagnostics: `no agent named "${agent}" is configured. Available: ${registry.names().join(', ')}`,
|
|
68
|
+
notes: '',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The control panel, as one self-contained page: no build step, no external requests
|
|
3
|
+
* (the Content-Security-Policy forbids them), and every value from the server is set with
|
|
4
|
+
* textContent, never innerHTML, so an agent's output or a provider's error text cannot
|
|
5
|
+
* become markup.
|
|
6
|
+
*/
|
|
7
|
+
export declare function uiPage(): string;
|