@softov/ahpc 0.2.0 → 0.4.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/README.md +37 -6
- package/dist/src/ahp/fake.js +61 -14
- package/dist/src/ahp/live.d.ts +7 -0
- package/dist/src/ahp/live.js +156 -43
- package/dist/src/ahp/publish.js +13 -0
- package/dist/src/ahp/types.d.ts +20 -0
- package/dist/src/app.js +34 -7
- package/dist/src/blocks.d.ts +4 -74
- package/dist/src/blocks.js +10 -48
- package/dist/src/cli/main.d.ts +1 -1
- package/dist/src/cli/main.js +109 -6
- package/dist/src/connect.d.ts +2 -0
- package/dist/src/connect.js +1 -0
- package/dist/src/control.d.ts +6 -0
- package/dist/src/control.js +140 -10
- package/dist/src/flags.js +4 -1
- package/dist/src/links.d.ts +54 -0
- package/dist/src/links.js +120 -0
- package/dist/src/main.js +14 -2
- package/dist/src/mcp/http.d.ts +10 -0
- package/dist/src/mcp/http.js +110 -3
- package/dist/src/mcp/serve.d.ts +47 -9
- package/dist/src/mcp/serve.js +65 -14
- package/dist/src/mcp/stdio.d.ts +1 -0
- package/dist/src/mcp/stdio.js +7 -1
- package/dist/src/mcp/tools.d.ts +37 -2
- package/dist/src/mcp/tools.js +439 -2
- package/dist/src/resources.d.ts +13 -0
- package/dist/src/resources.js +46 -0
- package/dist/src/screens.js +95 -50
- package/dist/src/state.d.ts +42 -0
- package/dist/src/state.js +80 -1
- package/dist/src/tui.d.ts +3 -1
- package/dist/src/tui.js +29 -4
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.js +38 -0
- package/dist/src/view/creature.d.ts +0 -12
- package/dist/src/view/creature.js +0 -20
- package/dist/src/view/wire.d.ts +36 -0
- package/dist/src/view/wire.js +196 -0
- package/dist/src/wait.d.ts +11 -0
- package/dist/src/wait.js +15 -2
- package/dist/src/wire.d.ts +70 -0
- package/dist/src/wire.js +194 -0
- package/dist/src/wiretui.d.ts +22 -0
- package/dist/src/wiretui.js +69 -0
- package/package.json +6 -5
- package/dist/src/diff.d.ts +0 -44
- package/dist/src/diff.js +0 -111
- package/dist/src/view/bubble.d.ts +0 -75
- package/dist/src/view/bubble.js +0 -86
- package/dist/src/view/composer.d.ts +0 -64
- package/dist/src/view/composer.js +0 -192
- package/dist/src/view/controls.d.ts +0 -44
- package/dist/src/view/controls.js +0 -49
- package/dist/src/view/details.d.ts +0 -65
- package/dist/src/view/details.js +0 -65
- package/dist/src/view/filediff.d.ts +0 -29
- package/dist/src/view/filediff.js +0 -24
- package/dist/src/view/hitl.d.ts +0 -43
- package/dist/src/view/hitl.js +0 -171
- package/dist/src/view/icons.d.ts +0 -13
- package/dist/src/view/icons.js +0 -71
- package/dist/src/view/picker.d.ts +0 -42
- package/dist/src/view/picker.js +0 -71
- package/dist/src/view/sessionhead.d.ts +0 -41
- package/dist/src/view/sessionhead.js +0 -60
- package/dist/src/view/sessions.d.ts +0 -34
- package/dist/src/view/sessions.js +0 -61
- package/dist/src/view/toolcall.d.ts +0 -27
- package/dist/src/view/toolcall.js +0 -48
- package/dist/src/view/transcript.d.ts +0 -50
- package/dist/src/view/transcript.js +0 -60
package/dist/src/mcp/serve.d.ts
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import type { HostConnection } from '../ahp/connection.js';
|
|
2
|
-
/** The version of MCP this speaks, and
|
|
2
|
+
/** The newest version of MCP this speaks, and what it answers an unknown one with. */
|
|
3
3
|
export declare const PROTOCOL = "2025-06-18";
|
|
4
|
+
/**
|
|
5
|
+
* Every version this will serve, newest first.
|
|
6
|
+
*
|
|
7
|
+
* Both of these describe the same tools, and this server uses nothing either
|
|
8
|
+
* of them added or removed - no sessions, no resources, no sampling - so
|
|
9
|
+
* refusing the older one would be refusing a client for a difference that
|
|
10
|
+
* cannot reach it. `2024-11-05` is *not* here: its HTTP transport is a
|
|
11
|
+
* different shape, with an `endpoint` event and a second URL.
|
|
12
|
+
*/
|
|
13
|
+
export declare const SPOKEN: readonly string[];
|
|
4
14
|
/**
|
|
5
15
|
* What this server calls itself.
|
|
6
16
|
*
|
|
7
|
-
* MCP requires a version where AHP's `clientInfo` does not
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* which sits at a different depth in the published tree than it does here.
|
|
17
|
+
* MCP requires a version where AHP's `clientInfo` does not. Read from the
|
|
18
|
+
* manifest rather than written here: the literal that used to be here said
|
|
19
|
+
* 0.1 while the package said 0.2, within a day of being written.
|
|
11
20
|
*/
|
|
12
21
|
export declare const SERVER: {
|
|
13
|
-
|
|
14
|
-
|
|
22
|
+
name: string;
|
|
23
|
+
version: string;
|
|
15
24
|
};
|
|
16
25
|
/** A JSON-RPC request or notification, as far as this needs to read one. */
|
|
17
26
|
export interface Incoming {
|
|
@@ -20,6 +29,21 @@ export interface Incoming {
|
|
|
20
29
|
method?: unknown;
|
|
21
30
|
params?: unknown;
|
|
22
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* One progress notification, as `notifications/progress` puts it on the wire.
|
|
34
|
+
*
|
|
35
|
+
* `progress` MUST increase on every one, `total` is left out because an
|
|
36
|
+
* agent's reply has no length known in advance, and `message` is what a person
|
|
37
|
+
* reads - which is the tool name a session stopped on, or the host's own words
|
|
38
|
+
* for what it is doing.
|
|
39
|
+
*/
|
|
40
|
+
export interface Progress {
|
|
41
|
+
progressToken: string | number;
|
|
42
|
+
progress: number;
|
|
43
|
+
message?: string;
|
|
44
|
+
}
|
|
45
|
+
/** Somewhere to put progress while a tool runs, or nothing where nobody asked. */
|
|
46
|
+
export type Report = ((said: string) => void) | undefined;
|
|
23
47
|
/** What goes back, or nothing at all where the message was a notification. */
|
|
24
48
|
export type Outgoing = {
|
|
25
49
|
jsonrpc: '2.0';
|
|
@@ -35,7 +59,7 @@ export type Outgoing = {
|
|
|
35
59
|
result?: never;
|
|
36
60
|
});
|
|
37
61
|
/** The tools, in the shape `tools/list` puts them on the wire. */
|
|
38
|
-
export declare const listing: () => unknown;
|
|
62
|
+
export declare const listing: (groups?: readonly string[]) => unknown;
|
|
39
63
|
/**
|
|
40
64
|
* Run one tool and shape the answer the way `tools/call` wants it.
|
|
41
65
|
*
|
|
@@ -45,7 +69,7 @@ export declare const listing: () => unknown;
|
|
|
45
69
|
* fault it can only give up over. So a refusal from the host - a session that
|
|
46
70
|
* is gone, a directory it does not serve - comes back as content.
|
|
47
71
|
*/
|
|
48
|
-
export declare function call(host: HostConnection, name: string, input: unknown): Promise<unknown>;
|
|
72
|
+
export declare function call(host: HostConnection, name: string, input: unknown, report?: Report, groups?: readonly string[]): Promise<unknown>;
|
|
49
73
|
/**
|
|
50
74
|
* Answer one message.
|
|
51
75
|
*
|
|
@@ -55,4 +79,18 @@ export declare function call(host: HostConnection, name: string, input: unknown)
|
|
|
55
79
|
export declare function answer(host: HostConnection, message: Incoming, options: {
|
|
56
80
|
name: string;
|
|
57
81
|
version: string;
|
|
82
|
+
/** The opt-in tool groups this server was started with, if any. */
|
|
83
|
+
groups?: readonly string[];
|
|
84
|
+
/**
|
|
85
|
+
* Somewhere to send a notification while this is being answered.
|
|
86
|
+
*
|
|
87
|
+
* Given by a transport that has one: stdio always does, and HTTP does only
|
|
88
|
+
* once it has decided to answer with a stream. Absent means there is
|
|
89
|
+
* nowhere to say anything until the result, which is the ordinary case and
|
|
90
|
+
* why every progress path here is optional.
|
|
91
|
+
*/
|
|
92
|
+
notify?(notification: {
|
|
93
|
+
method: string;
|
|
94
|
+
params: unknown;
|
|
95
|
+
}): void;
|
|
58
96
|
}): Promise<Outgoing | undefined>;
|
package/dist/src/mcp/serve.js
CHANGED
|
@@ -13,25 +13,35 @@
|
|
|
13
13
|
* obliged to answer them, and a caller that asks is told the method is not
|
|
14
14
|
* there, which is the truth.
|
|
15
15
|
*/
|
|
16
|
-
import {
|
|
17
|
-
|
|
16
|
+
import { named, served } from './tools.js';
|
|
17
|
+
import { version } from '../version.js';
|
|
18
|
+
/** The newest version of MCP this speaks, and what it answers an unknown one with. */
|
|
18
19
|
export const PROTOCOL = '2025-06-18';
|
|
20
|
+
/**
|
|
21
|
+
* Every version this will serve, newest first.
|
|
22
|
+
*
|
|
23
|
+
* Both of these describe the same tools, and this server uses nothing either
|
|
24
|
+
* of them added or removed - no sessions, no resources, no sampling - so
|
|
25
|
+
* refusing the older one would be refusing a client for a difference that
|
|
26
|
+
* cannot reach it. `2024-11-05` is *not* here: its HTTP transport is a
|
|
27
|
+
* different shape, with an `endpoint` event and a second URL.
|
|
28
|
+
*/
|
|
29
|
+
export const SPOKEN = ['2025-06-18', '2025-03-26'];
|
|
19
30
|
/**
|
|
20
31
|
* What this server calls itself.
|
|
21
32
|
*
|
|
22
|
-
* MCP requires a version where AHP's `clientInfo` does not
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* which sits at a different depth in the published tree than it does here.
|
|
33
|
+
* MCP requires a version where AHP's `clientInfo` does not. Read from the
|
|
34
|
+
* manifest rather than written here: the literal that used to be here said
|
|
35
|
+
* 0.1 while the package said 0.2, within a day of being written.
|
|
26
36
|
*/
|
|
27
|
-
export const SERVER = { name: 'ahpc', version:
|
|
37
|
+
export const SERVER = { name: 'ahpc', version: version() };
|
|
28
38
|
const METHOD_NOT_FOUND = -32601;
|
|
29
39
|
const INVALID_PARAMS = -32602;
|
|
30
40
|
const INTERNAL = -32603;
|
|
31
41
|
const bag = (value) => (typeof value === 'object' && value !== null && !Array.isArray(value) ? value : {});
|
|
32
42
|
/** The tools, in the shape `tools/list` puts them on the wire. */
|
|
33
|
-
export const listing = () => ({
|
|
34
|
-
tools:
|
|
43
|
+
export const listing = (groups = []) => ({
|
|
44
|
+
tools: served(groups).map((tool) => ({
|
|
35
45
|
name: tool.name,
|
|
36
46
|
title: tool.title,
|
|
37
47
|
description: tool.description,
|
|
@@ -48,7 +58,7 @@ export const listing = () => ({
|
|
|
48
58
|
* fault it can only give up over. So a refusal from the host - a session that
|
|
49
59
|
* is gone, a directory it does not serve - comes back as content.
|
|
50
60
|
*/
|
|
51
|
-
export async function call(host, name, input) {
|
|
61
|
+
export async function call(host, name, input, report, groups = []) {
|
|
52
62
|
const tool = named(name);
|
|
53
63
|
if (tool === undefined) {
|
|
54
64
|
return {
|
|
@@ -56,8 +66,17 @@ export async function call(host, name, input) {
|
|
|
56
66
|
content: [{ type: 'text', text: `No tool called ${name}. Ask tools/list for what there is.` }],
|
|
57
67
|
};
|
|
58
68
|
}
|
|
69
|
+
// Exists, but this server was not started with it. Said as itself rather
|
|
70
|
+
// than as "no such tool", because the difference is one flag and only the
|
|
71
|
+
// person who started this can supply it.
|
|
72
|
+
if (tool.group !== undefined && !groups.includes(tool.group)) {
|
|
73
|
+
return {
|
|
74
|
+
isError: true,
|
|
75
|
+
content: [{ type: 'text', text: `${name} is in the ${tool.group} group, which this server was not started with. It needs --mcp-tools ${tool.group}.` }],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
59
78
|
try {
|
|
60
|
-
const answer = await tool.run(host, bag(input));
|
|
79
|
+
const answer = await tool.run(host, bag(input), report);
|
|
61
80
|
return {
|
|
62
81
|
// Both, because clients differ: the text is what a model reads and
|
|
63
82
|
// `structuredContent` is what a program does, and sending only the
|
|
@@ -75,6 +94,28 @@ export async function call(host, name, input) {
|
|
|
75
94
|
};
|
|
76
95
|
}
|
|
77
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Somewhere for a running tool to say what it is doing, if anybody asked.
|
|
99
|
+
*
|
|
100
|
+
* A caller opts in by putting a `progressToken` in `_meta` on the request, and
|
|
101
|
+
* `undefined` here means it did not - in which case a tool that reports
|
|
102
|
+
* anything is writing to nowhere, which is exactly what should happen. The
|
|
103
|
+
* counter is this closure's, because `progress` MUST increase and a tool
|
|
104
|
+
* counting for itself would be a tool that has to know about the protocol.
|
|
105
|
+
*/
|
|
106
|
+
function reporter(message, notify) {
|
|
107
|
+
const token = bag(bag(message.params)._meta).progressToken;
|
|
108
|
+
if (notify === undefined)
|
|
109
|
+
return undefined;
|
|
110
|
+
if (typeof token !== 'string' && typeof token !== 'number')
|
|
111
|
+
return undefined;
|
|
112
|
+
let count = 0;
|
|
113
|
+
return (said) => {
|
|
114
|
+
count += 1;
|
|
115
|
+
const params = { progressToken: token, progress: count, message: said };
|
|
116
|
+
notify({ method: 'notifications/progress', params });
|
|
117
|
+
};
|
|
118
|
+
}
|
|
78
119
|
/**
|
|
79
120
|
* Answer one message.
|
|
80
121
|
*
|
|
@@ -92,8 +133,18 @@ export async function answer(host, message, options) {
|
|
|
92
133
|
const ok = (result) => ({ jsonrpc: '2.0', id, result });
|
|
93
134
|
const no = (code, said) => ({ jsonrpc: '2.0', id, error: { code, message: said } });
|
|
94
135
|
if (method === 'initialize') {
|
|
136
|
+
/*
|
|
137
|
+
* The client's version where this can speak it, and the newest otherwise.
|
|
138
|
+
*
|
|
139
|
+
* `lifecycle` says a server answering a version it was not asked for is
|
|
140
|
+
* telling the client to take that one or disconnect, so answering our
|
|
141
|
+
* own regardless would refuse every client a release behind for no
|
|
142
|
+
* reason. A version this cannot speak gets the newest it can, which is
|
|
143
|
+
* the offer the client then accepts or drops.
|
|
144
|
+
*/
|
|
145
|
+
const asked = bag(message.params).protocolVersion;
|
|
95
146
|
return ok({
|
|
96
|
-
protocolVersion: PROTOCOL,
|
|
147
|
+
protocolVersion: typeof asked === 'string' && SPOKEN.includes(asked) ? asked : PROTOCOL,
|
|
97
148
|
capabilities: { tools: { listChanged: false } },
|
|
98
149
|
serverInfo: { name: options.name, version: options.version },
|
|
99
150
|
});
|
|
@@ -101,14 +152,14 @@ export async function answer(host, message, options) {
|
|
|
101
152
|
if (method === 'ping')
|
|
102
153
|
return ok({});
|
|
103
154
|
if (method === 'tools/list')
|
|
104
|
-
return ok(listing());
|
|
155
|
+
return ok(listing(options.groups));
|
|
105
156
|
if (method === 'tools/call') {
|
|
106
157
|
const params = bag(message.params);
|
|
107
158
|
const name = typeof params.name === 'string' ? params.name : '';
|
|
108
159
|
if (name === '')
|
|
109
160
|
return no(INVALID_PARAMS, 'tools/call needs a name.');
|
|
110
161
|
try {
|
|
111
|
-
return ok(await call(host, name, params.arguments));
|
|
162
|
+
return ok(await call(host, name, params.arguments, reporter(message, options.notify), options.groups));
|
|
112
163
|
}
|
|
113
164
|
catch (error) {
|
|
114
165
|
return no(INTERNAL, error instanceof Error ? error.message : String(error));
|
package/dist/src/mcp/stdio.d.ts
CHANGED
package/dist/src/mcp/stdio.js
CHANGED
|
@@ -34,7 +34,13 @@ export async function stdio(host, options) {
|
|
|
34
34
|
options.onProblem?.(`Not JSON: ${said.slice(0, 200)}`);
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
|
-
const reply = await answer(host, message,
|
|
37
|
+
const reply = await answer(host, message, {
|
|
38
|
+
...options,
|
|
39
|
+
// A notification mid-request is free here: stdout is a stream and
|
|
40
|
+
// the client is already reading lines off it. The HTTP half has to
|
|
41
|
+
// choose a response type before it can say anything at all.
|
|
42
|
+
notify: (notification) => say({ jsonrpc: '2.0', ...notification }),
|
|
43
|
+
});
|
|
38
44
|
if (reply !== undefined)
|
|
39
45
|
say(reply);
|
|
40
46
|
}).catch((error) => {
|
package/dist/src/mcp/tools.d.ts
CHANGED
|
@@ -15,10 +15,28 @@ export interface Schema {
|
|
|
15
15
|
required?: string[];
|
|
16
16
|
additionalProperties?: boolean;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* A set of tools a caller has to ask for by name.
|
|
20
|
+
*
|
|
21
|
+
* The core table - sessions, turns, attention - is what a server is for and is
|
|
22
|
+
* always served. These are not: a tool table is read by a model alongside
|
|
23
|
+
* everything else it was given, and thirty tools is a worse server than twelve
|
|
24
|
+
* for the thing almost everybody wants. So they are opt-*in*, one group at a
|
|
25
|
+
* time, and a caller that needs files says so.
|
|
26
|
+
*/
|
|
27
|
+
export type Group = 'resources' | 'terminals' | 'automations' | 'changes';
|
|
28
|
+
/** Every group there is, for `--mcp-tools` to name in its help. */
|
|
29
|
+
export declare const GROUPS: readonly Group[];
|
|
18
30
|
/** One tool, as MCP describes it and as this client runs it. */
|
|
19
31
|
export interface Tool {
|
|
20
32
|
/** The name a caller uses. MCP has no dots, so these are underscored. */
|
|
21
33
|
name: string;
|
|
34
|
+
/**
|
|
35
|
+
* The group that has to be turned on for this to be served.
|
|
36
|
+
*
|
|
37
|
+
* Absent is core: always there, never asked for.
|
|
38
|
+
*/
|
|
39
|
+
group?: Group;
|
|
22
40
|
/** A short label, for a client that shows one. */
|
|
23
41
|
title: string;
|
|
24
42
|
/** What it does and when to reach for it, written for a model. */
|
|
@@ -26,8 +44,25 @@ export interface Tool {
|
|
|
26
44
|
input: Schema;
|
|
27
45
|
/** Whether it changes anything, which some clients ask a person about. */
|
|
28
46
|
readOnly: boolean;
|
|
29
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Do it.
|
|
49
|
+
*
|
|
50
|
+
* `report` is where to say what is happening while a long one runs, and is
|
|
51
|
+
* absent unless the caller asked for progress and the transport can carry
|
|
52
|
+
* it. A tool that reports nothing is a tool that finishes quickly enough
|
|
53
|
+
* not to need to.
|
|
54
|
+
*/
|
|
55
|
+
run(host: HostConnection, input: Record<string, unknown>, report?: (said: string) => void): Promise<unknown>;
|
|
30
56
|
}
|
|
31
57
|
export declare const TOOLS: Tool[];
|
|
32
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* One tool by the name a caller used, or nothing.
|
|
60
|
+
*
|
|
61
|
+
* Over the whole table, including groups nobody turned on - whether a tool
|
|
62
|
+
* exists and whether this server serves it are different questions, and
|
|
63
|
+
* `served` answers the second. Telling somebody the tool is in a group they
|
|
64
|
+
* did not ask for is a better answer than telling them it does not exist.
|
|
65
|
+
*/
|
|
33
66
|
export declare const named: (name: string) => Tool | undefined;
|
|
67
|
+
/** The tools a server started with these groups serves. */
|
|
68
|
+
export declare const served: (groups?: readonly string[]) => Tool[];
|