@zetaloop/chappie 0.2.0 → 0.2.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/README.md +1 -1
- package/docs/tools.md +14 -8
- package/package.json +1 -1
- package/src/broker.ts +48 -16
- package/src/delivery.ts +2 -0
- package/src/instructions.md +5 -3
- package/src/ipc.ts +2 -1
- package/src/server.ts +60 -23
- package/src/session.ts +32 -38
- package/src/state.ts +6 -2
- package/src/tools.ts +3 -1
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Start otunnel with this configuration and add its tunnel as a developer-mode app
|
|
|
25
25
|
pi --provider chappie --model chatgpt
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Open Pi with the Chappie provider, then ask ChatGPT to call `init`.
|
|
28
|
+
Open Pi with the Chappie provider, then ask ChatGPT to call `init`. A chat without a default is paired with the first online, unbound Pi session. To resume work in a new chat or branch, use `init` with the original Pi session ID; `sessions` lists projects when the target ID is unknown.
|
|
29
29
|
|
|
30
30
|
## Usage
|
|
31
31
|
|
package/docs/tools.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
| `init` | Connect to a Pi session and read its environment, tool catalog, skills, global `AGENTS.md`, and pending input. |
|
|
6
6
|
| `sessions` | List connected Pi sessions and the chat's default session. |
|
|
7
7
|
| `tools` | Read complete definitions for selected active tools. |
|
|
8
|
-
| `chat` |
|
|
8
|
+
| `chat` | Display Markdown as an assistant message in Pi. |
|
|
9
9
|
| `call` | Run one or more tools as a Pi batch. |
|
|
10
10
|
| `read` | Read local text or images. |
|
|
11
11
|
| `bash` | Execute a shell command. |
|
|
@@ -23,16 +23,20 @@ Use `chat` for progress or results that should appear in Pi. When a Pi user deci
|
|
|
23
23
|
|
|
24
24
|
## Sessions
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
For a new task without a specific target, call `init` with `{}` to reuse this chat's default or allocate the first online, unbound Pi session. Sessions register while `chappie/chatgpt` is selected. They can be blank or already contain a task; a remote operation starts a turn when Pi is idle.
|
|
27
27
|
|
|
28
|
-
`sessions` lists session IDs, working directories, names, and
|
|
28
|
+
`sessions` lists session IDs, working directories, names, status, and `bindingCount`, the number of saved chat defaults pointing to each session. Zero means the session can be allocated automatically. The count includes closed chats; execution status describes Pi activity: `ready` accepts provider output, `executing` handles an operation, and `idle` starts a turn on the next operation.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
`init.selection` reports how the target was chosen: `existing` reuses this chat's default, `explicit` uses the supplied session ID, and `automatic` allocates the first online session with no saved bindings. Explicit selection also accepts sessions already used by other chats.
|
|
31
|
+
|
|
32
|
+
To continue existing work in a new chat or branch, pass the Pi session ID associated with that task in the inherited context:
|
|
31
33
|
|
|
32
34
|
```json
|
|
33
35
|
{ "sessionId": "<session-id>" }
|
|
34
36
|
```
|
|
35
37
|
|
|
38
|
+
This establishes the new chat's default, even when another chat already uses the same Pi session. For a requested project or session without a known ID, select it from `sessions` by working directory or name. When the target is absent or ambiguous, clarify the intended session before running tools. A session being the only one online does not establish that it is the requested target.
|
|
39
|
+
|
|
36
40
|
The optional `sessionId` on other tools selects a session for that operation. For example, `read` can inspect another project:
|
|
37
41
|
|
|
38
42
|
```json
|
|
@@ -45,7 +49,7 @@ Several chats can select the same Pi session, and one chat can address several s
|
|
|
45
49
|
|
|
46
50
|
## Tool calls
|
|
47
51
|
|
|
48
|
-
`read`, `bash`, `edit`, and `write` accept Pi's tool parameters plus `sessionId`. Their descriptions provide the current schemas. `init` lists
|
|
52
|
+
`read`, `bash`, `edit`, and `write` accept Pi's tool parameters plus `sessionId`. Their descriptions provide the current schemas. The catalog in `init.tools` lists Pi's native and extension tools available through `call`. Chappie's `init`, `sessions`, `tools`, and `chat` are separate top-level MCP tools. Load complete definitions for installed extension tools before calling them:
|
|
49
53
|
|
|
50
54
|
```json
|
|
51
55
|
{ "names": ["ask_user", "ctx_search"] }
|
|
@@ -66,7 +70,9 @@ A single extension tool uses a one-item `calls` array. To request a batch:
|
|
|
66
70
|
|
|
67
71
|
Each batch returns its results together. Pi determines how its tools run within the batch. Separate calls run in order within one Pi session; different sessions can work independently.
|
|
68
72
|
|
|
69
|
-
Extension tools execute through Pi, including their interactive prompts.
|
|
73
|
+
Extension tools execute through Pi, including their interactive prompts. Each batch starts with its executing `sessionId` and Pi `cwd`, followed by each tool's name, call ID, error status, and original text or image content. The directory is captured when the batch starts; a shell `cd` changes that command's working directory, while the operation stays in the same Pi session.
|
|
74
|
+
|
|
75
|
+
Every tool declares a `{ text: string }` output. `structuredContent.text` contains the complete text in result order, including Pi input, deferred results, and image references. The same text remains in `content` alongside native images and resource links.
|
|
70
76
|
|
|
71
77
|
ChatGPT file inputs use the direct `transfer` tool. Its top-level `files` parameter lets the host prepare the files before sending them to Pi.
|
|
72
78
|
|
|
@@ -78,11 +84,11 @@ Call `chat` to display a reply in Pi:
|
|
|
78
84
|
{ "text": "Updated the parser and its callers." }
|
|
79
85
|
```
|
|
80
86
|
|
|
81
|
-
Each call completes one assistant message. Later operations start another turn when Pi is idle. The result returns the target
|
|
87
|
+
Pi renders the supplied Markdown, including fenced code blocks, and appends the message to the session transcript. Each call completes one assistant message. Later operations start another turn when Pi is idle. The result returns the target `sessionId`, Pi `cwd`, and any new Pi input without repeating the message text.
|
|
82
88
|
|
|
83
89
|
User messages consumed by Pi accompany later Chappie replies, including images. Steering is delivered when Pi consumes it; follow-up uses Pi's normal follow-up timing.
|
|
84
90
|
|
|
85
|
-
Explicit cancellation removes a queued request or asks Pi to stop its active batch. Available results from that batch accompany a later reply to the originating chat. `sessions` can retrieve them before another tool call. When ChatGPT stops without sending cancellation, local execution continues.
|
|
91
|
+
Explicit cancellation removes a queued request or asks Pi to stop its active batch. Available results from that batch accompany a later reply to the originating chat, with the original session ID and working directory. `sessions` can retrieve them before another tool call. When ChatGPT stops without sending cancellation, local execution continues.
|
|
86
92
|
|
|
87
93
|
Host request deadlines include time spent in the queue. Use local facilities such as tmux for work intended to outlive one call.
|
|
88
94
|
|
package/package.json
CHANGED
package/src/broker.ts
CHANGED
|
@@ -42,6 +42,7 @@ interface ChangeWaiter {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export interface InitializedSession extends Omit<SessionInspection, "tools"> {
|
|
45
|
+
selection: "existing" | "explicit" | "automatic";
|
|
45
46
|
globalAgents?: string;
|
|
46
47
|
inputs: SessionInput[];
|
|
47
48
|
tools: { name: string; description: string }[];
|
|
@@ -53,11 +54,13 @@ export interface InspectedSession extends SessionInspection {
|
|
|
53
54
|
|
|
54
55
|
export interface ChatResult {
|
|
55
56
|
sessionId: string;
|
|
57
|
+
cwd: string;
|
|
56
58
|
inputs: SessionInput[];
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
export interface CallResult {
|
|
60
62
|
sessionId: string;
|
|
63
|
+
cwd: string;
|
|
61
64
|
toolResults: ToolResultMessage[];
|
|
62
65
|
inputs: SessionInput[];
|
|
63
66
|
}
|
|
@@ -101,12 +104,16 @@ export class Broker {
|
|
|
101
104
|
await this.#ipc.close();
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
listSessions(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
listSessions(
|
|
108
|
+
sessionId?: string,
|
|
109
|
+
): (SessionDescription & { bindingCount: number })[] {
|
|
110
|
+
const counts = this.#state.bindingCounts();
|
|
111
|
+
return [...this.#sessions.values()]
|
|
112
|
+
.filter(({ description }) => !sessionId || description.id === sessionId)
|
|
113
|
+
.map(({ description }) => ({
|
|
114
|
+
...description,
|
|
115
|
+
bindingCount: counts.get(description.id) ?? 0,
|
|
116
|
+
}));
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
binding(chatId: string): string | undefined {
|
|
@@ -118,11 +125,17 @@ export class Broker {
|
|
|
118
125
|
sessionId: string | undefined,
|
|
119
126
|
signal: AbortSignal,
|
|
120
127
|
): Promise<InitializedSession> {
|
|
121
|
-
const target = await this.#selectSession(
|
|
128
|
+
const { sessionId: target, selection } = await this.#selectSession(
|
|
129
|
+
chatId,
|
|
130
|
+
sessionId,
|
|
131
|
+
signal,
|
|
132
|
+
true,
|
|
133
|
+
);
|
|
122
134
|
const { inspection, inputs } = await this.#inspect(target, signal);
|
|
123
135
|
const globalAgents = await this.#readGlobalAgents();
|
|
124
136
|
await this.#ackInputs(target, inputs);
|
|
125
137
|
return {
|
|
138
|
+
selection,
|
|
126
139
|
...inspection,
|
|
127
140
|
tools: inspection.tools.map(({ name, description }) => ({
|
|
128
141
|
name,
|
|
@@ -139,7 +152,12 @@ export class Broker {
|
|
|
139
152
|
text: string,
|
|
140
153
|
signal: AbortSignal,
|
|
141
154
|
): Promise<ChatResult> {
|
|
142
|
-
const target = await this.#selectSession(
|
|
155
|
+
const { sessionId: target } = await this.#selectSession(
|
|
156
|
+
chatId,
|
|
157
|
+
sessionId,
|
|
158
|
+
signal,
|
|
159
|
+
false,
|
|
160
|
+
);
|
|
143
161
|
const result = await this.#request(
|
|
144
162
|
target,
|
|
145
163
|
(id) => ({ type: "chat", id, chatId, sessionId: target, text }),
|
|
@@ -147,7 +165,7 @@ export class Broker {
|
|
|
147
165
|
);
|
|
148
166
|
if ("message" in result) {
|
|
149
167
|
await this.#ackInputs(target, result.inputs);
|
|
150
|
-
return { sessionId: target, inputs: result.inputs };
|
|
168
|
+
return { sessionId: target, cwd: result.cwd, inputs: result.inputs };
|
|
151
169
|
}
|
|
152
170
|
throw new Error("Pi session returned no assistant message");
|
|
153
171
|
}
|
|
@@ -158,7 +176,12 @@ export class Broker {
|
|
|
158
176
|
names: string[] | undefined,
|
|
159
177
|
signal: AbortSignal,
|
|
160
178
|
): Promise<InspectedSession> {
|
|
161
|
-
const target = await this.#selectSession(
|
|
179
|
+
const { sessionId: target } = await this.#selectSession(
|
|
180
|
+
chatId,
|
|
181
|
+
sessionId,
|
|
182
|
+
signal,
|
|
183
|
+
false,
|
|
184
|
+
);
|
|
162
185
|
const { inspection, inputs } = await this.#inspect(target, signal);
|
|
163
186
|
await this.#ackInputs(target, inputs);
|
|
164
187
|
const selected = names ? new Set(names) : undefined;
|
|
@@ -177,7 +200,12 @@ export class Broker {
|
|
|
177
200
|
calls: ToolInput[],
|
|
178
201
|
signal: AbortSignal,
|
|
179
202
|
): Promise<CallResult> {
|
|
180
|
-
const target = await this.#selectSession(
|
|
203
|
+
const { sessionId: target } = await this.#selectSession(
|
|
204
|
+
chatId,
|
|
205
|
+
sessionId,
|
|
206
|
+
signal,
|
|
207
|
+
false,
|
|
208
|
+
);
|
|
181
209
|
const toolCalls: ToolCall[] = calls.map((call) => ({
|
|
182
210
|
type: "toolCall",
|
|
183
211
|
id: `chappie-${randomUUID()}`,
|
|
@@ -199,6 +227,7 @@ export class Broker {
|
|
|
199
227
|
await this.#ackInputs(target, result.inputs);
|
|
200
228
|
return {
|
|
201
229
|
sessionId: target,
|
|
230
|
+
cwd: result.cwd,
|
|
202
231
|
toolResults: result.toolResults,
|
|
203
232
|
inputs: result.inputs,
|
|
204
233
|
};
|
|
@@ -292,31 +321,34 @@ export class Broker {
|
|
|
292
321
|
requestedId: string | undefined,
|
|
293
322
|
signal: AbortSignal,
|
|
294
323
|
bindRequested: boolean,
|
|
295
|
-
): Promise<
|
|
324
|
+
): Promise<{
|
|
325
|
+
sessionId: string;
|
|
326
|
+
selection: InitializedSession["selection"];
|
|
327
|
+
}> {
|
|
296
328
|
if (requestedId) {
|
|
297
329
|
await this.#waitForSession(requestedId, signal);
|
|
298
330
|
if (bindRequested && this.#state.binding(chatId) !== requestedId) {
|
|
299
331
|
await this.#state.setBinding(chatId, requestedId);
|
|
300
332
|
this.#notifyChange();
|
|
301
333
|
}
|
|
302
|
-
return requestedId;
|
|
334
|
+
return { sessionId: requestedId, selection: "explicit" };
|
|
303
335
|
}
|
|
304
336
|
|
|
305
337
|
const boundId = this.#state.binding(chatId);
|
|
306
338
|
if (boundId) {
|
|
307
339
|
await this.#waitForSession(boundId, signal);
|
|
308
|
-
return boundId;
|
|
340
|
+
return { sessionId: boundId, selection: "existing" };
|
|
309
341
|
}
|
|
310
342
|
|
|
311
343
|
for (;;) {
|
|
312
|
-
const occupied = this.#state.
|
|
344
|
+
const occupied = this.#state.bindingCounts();
|
|
313
345
|
const candidate = [...this.#sessions.keys()].find(
|
|
314
346
|
(sessionId) => !occupied.has(sessionId),
|
|
315
347
|
);
|
|
316
348
|
if (candidate) {
|
|
317
349
|
await this.#state.setBinding(chatId, candidate);
|
|
318
350
|
this.#notifyChange();
|
|
319
|
-
return candidate;
|
|
351
|
+
return { sessionId: candidate, selection: "automatic" };
|
|
320
352
|
}
|
|
321
353
|
await this.#waitForChange(signal);
|
|
322
354
|
}
|
package/src/delivery.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface DeliveryRecord {
|
|
|
9
9
|
id: string;
|
|
10
10
|
chatId: string;
|
|
11
11
|
sessionId: string;
|
|
12
|
+
cwd: string;
|
|
12
13
|
sessionFile?: string;
|
|
13
14
|
toolCallIds: string[];
|
|
14
15
|
inlineResults?: ToolResultMessage[];
|
|
@@ -57,6 +58,7 @@ export function deliveryContent(deliveries: ResolvedDelivery[]) {
|
|
|
57
58
|
text: JSON.stringify({
|
|
58
59
|
deferredResult: delivery.id,
|
|
59
60
|
sessionId: delivery.sessionId,
|
|
61
|
+
cwd: delivery.cwd,
|
|
60
62
|
error: delivery.error,
|
|
61
63
|
}),
|
|
62
64
|
},
|
package/src/instructions.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
Chappie connects this ChatGPT conversation to local Pi sessions.
|
|
1
|
+
Chappie connects this ChatGPT conversation to local Pi sessions. To continue existing work, call init with the Pi sessionId associated with that task in the context. A new chat or branch establishes its own binding to that ID. For a requested project or session without a known ID, use sessions to match its cwd or name, then init with the chosen ID. If the intended target is absent or ambiguous, resolve the selection with the user before starting work. Pi sessions register while the chappie/chatgpt provider is selected.
|
|
2
|
+
|
|
3
|
+
For work without a specific target, init without sessionId reuses the current binding or allocates the first online session with bindingCount zero. These sessions may be blank or already contain a task. Saved bindings persist across broker restarts; explicit init(sessionId) can share a session already used by another chat. init reports its selection as existing, explicit, or automatic. A sessionId on any other tool affects only that operation.
|
|
2
4
|
|
|
3
5
|
The active model is this existing ChatGPT conversation. A Pi tool that starts another chappie/chatgpt agent has no ChatGPT conversation to attach to and will wait indefinitely. Subagents targeting another configured model keep that provider's normal behavior.
|
|
4
6
|
|
|
5
7
|
Prefer ChatGPT's web search, connectors, and cloud tools for remote research and cloud-side work. Use Chappie for local files, processes, Pi extensions, and Pi user interfaces. Pi project-memory tools operate on their local stores; Pi context-reduction tools do not change this ChatGPT conversation.
|
|
6
8
|
|
|
7
|
-
Use chat to
|
|
9
|
+
Use chat to display Markdown progress and final messages in Pi, including code examples. Use read, bash, edit, and write directly. init.tools lists Pi's native and extension tools for call; Chappie's init, sessions, tools, and chat are separate top-level MCP tools. Use tools with names to load complete definitions before calling other Pi tools. Use an installed interactive tool through call when input is needed in Pi. Separate calls remain separate Pi turns; use a call array when tools should share a batch.
|
|
8
10
|
|
|
9
11
|
Use transfer with paths and files to copy ChatGPT files into Pi. Omit files to expose existing Pi paths or chappie:// image references as MCP resources. Relative paths use the Pi working directory, existing targets require overwrite: true, and resource materialization may require host confirmation.
|
|
10
12
|
|
|
11
|
-
Tool replies
|
|
13
|
+
Tool replies identify the executing sessionId and Pi cwd. A shell command can access another directory while its session and transcript remain the same. Replies provide their complete text in structuredContent.text, including user input consumed by Pi and results from an earlier explicitly cancelled request. Images and file resources accompany the text as native content blocks. Continue from those results instead of repeating completed work. Host request deadlines include queueing and execution; use local persistent-process facilities for work intended to outlive one request.
|
package/src/ipc.ts
CHANGED
|
@@ -44,9 +44,10 @@ export interface SessionInput {
|
|
|
44
44
|
|
|
45
45
|
export type SessionResult =
|
|
46
46
|
| { inspection: SessionInspection; inputs: SessionInput[] }
|
|
47
|
-
| { message: AssistantMessage; inputs: SessionInput[] }
|
|
47
|
+
| { message: AssistantMessage; cwd: string; inputs: SessionInput[] }
|
|
48
48
|
| {
|
|
49
49
|
message: AssistantMessage;
|
|
50
|
+
cwd: string;
|
|
50
51
|
toolResults: ToolResultMessage[];
|
|
51
52
|
inputs: SessionInput[];
|
|
52
53
|
}
|
package/src/server.ts
CHANGED
|
@@ -16,6 +16,14 @@ const instructions = readFileSync(
|
|
|
16
16
|
"utf8",
|
|
17
17
|
).trim();
|
|
18
18
|
|
|
19
|
+
const outputSchema = z.object({
|
|
20
|
+
text: z
|
|
21
|
+
.string()
|
|
22
|
+
.describe(
|
|
23
|
+
"Complete text output, including Pi user input and deferred results. Images and file resources accompany it as native content blocks.",
|
|
24
|
+
),
|
|
25
|
+
});
|
|
26
|
+
|
|
19
27
|
interface RequestContext {
|
|
20
28
|
mcpReq: {
|
|
21
29
|
_meta?: Record<string, unknown>;
|
|
@@ -37,14 +45,18 @@ export function createServer(broker: Broker): McpServer {
|
|
|
37
45
|
{
|
|
38
46
|
title: "Connect to Pi",
|
|
39
47
|
description:
|
|
40
|
-
"
|
|
48
|
+
"Bind this chat to a Pi session. To resume work or a ChatGPT branch, pass that task's Pi sessionId from context; use sessions to locate a requested target when its ID is unknown. Omit sessionId to reuse this chat's default or allocate the first online, unbound session. Returns the selection source.",
|
|
49
|
+
outputSchema,
|
|
41
50
|
inputSchema: z.object({
|
|
42
51
|
sessionId: z
|
|
43
52
|
.string()
|
|
44
53
|
.optional()
|
|
45
|
-
.describe(
|
|
54
|
+
.describe(
|
|
55
|
+
"Pi session ID to set as this chat's default, including an already-bound session when resuming work",
|
|
56
|
+
),
|
|
46
57
|
}),
|
|
47
58
|
annotations: {
|
|
59
|
+
readOnlyHint: false,
|
|
48
60
|
destructiveHint: false,
|
|
49
61
|
openWorldHint: false,
|
|
50
62
|
},
|
|
@@ -64,28 +76,38 @@ export function createServer(broker: Broker): McpServer {
|
|
|
64
76
|
"chat",
|
|
65
77
|
{
|
|
66
78
|
title: "Reply in Pi",
|
|
67
|
-
description:
|
|
79
|
+
description:
|
|
80
|
+
"Display the supplied Markdown in Pi and append it to the session transcript. Code blocks are displayed as text.",
|
|
81
|
+
outputSchema,
|
|
68
82
|
inputSchema: z.object({
|
|
69
|
-
text: z
|
|
83
|
+
text: z
|
|
84
|
+
.string()
|
|
85
|
+
.min(1)
|
|
86
|
+
.describe("Markdown message, including prose and code examples"),
|
|
70
87
|
sessionId: z
|
|
71
88
|
.string()
|
|
72
89
|
.optional()
|
|
73
90
|
.describe("Pi session for this operation only"),
|
|
74
91
|
}),
|
|
75
92
|
annotations: {
|
|
93
|
+
readOnlyHint: false,
|
|
76
94
|
destructiveHint: false,
|
|
77
95
|
openWorldHint: false,
|
|
78
96
|
},
|
|
79
97
|
},
|
|
80
98
|
async (args, context) => {
|
|
81
99
|
const chatId = requireChatId(context);
|
|
82
|
-
const { sessionId, inputs } = await broker.chat(
|
|
100
|
+
const { sessionId, cwd, inputs } = await broker.chat(
|
|
83
101
|
chatId,
|
|
84
102
|
args.sessionId,
|
|
85
103
|
args.text,
|
|
86
104
|
context.mcpReq.signal,
|
|
87
105
|
);
|
|
88
|
-
return finishResult(
|
|
106
|
+
return finishResult(
|
|
107
|
+
broker,
|
|
108
|
+
context,
|
|
109
|
+
textResult({ sessionId, cwd }, inputs),
|
|
110
|
+
);
|
|
89
111
|
},
|
|
90
112
|
);
|
|
91
113
|
|
|
@@ -94,7 +116,8 @@ export function createServer(broker: Broker): McpServer {
|
|
|
94
116
|
{
|
|
95
117
|
title: "Pi tools",
|
|
96
118
|
description:
|
|
97
|
-
"Return complete definitions for active Pi tools. Provide names to inspect
|
|
119
|
+
"Return complete definitions for active Pi tools invoked through call. Provide names to inspect selected tools. Chappie's own MCP controls, including init, sessions, and chat, are exposed separately.",
|
|
120
|
+
outputSchema,
|
|
98
121
|
inputSchema: z.object({
|
|
99
122
|
names: z
|
|
100
123
|
.array(z.string())
|
|
@@ -137,6 +160,7 @@ export function createServer(broker: Broker): McpServer {
|
|
|
137
160
|
title: "Call Pi tools",
|
|
138
161
|
description:
|
|
139
162
|
"Execute one or more active Pi tools as one native batch. Arguments must match definitions returned by tools.",
|
|
163
|
+
outputSchema,
|
|
140
164
|
inputSchema: z.object({
|
|
141
165
|
calls: z
|
|
142
166
|
.array(
|
|
@@ -152,6 +176,7 @@ export function createServer(broker: Broker): McpServer {
|
|
|
152
176
|
.describe("Pi session for this operation only"),
|
|
153
177
|
}),
|
|
154
178
|
annotations: {
|
|
179
|
+
readOnlyHint: false,
|
|
155
180
|
destructiveHint: true,
|
|
156
181
|
openWorldHint: true,
|
|
157
182
|
},
|
|
@@ -166,7 +191,12 @@ export function createServer(broker: Broker): McpServer {
|
|
|
166
191
|
return finishResult(
|
|
167
192
|
broker,
|
|
168
193
|
context,
|
|
169
|
-
toolResult(
|
|
194
|
+
toolResult(
|
|
195
|
+
result.toolResults,
|
|
196
|
+
result.sessionId,
|
|
197
|
+
result.cwd,
|
|
198
|
+
result.inputs,
|
|
199
|
+
),
|
|
170
200
|
);
|
|
171
201
|
},
|
|
172
202
|
);
|
|
@@ -177,6 +207,7 @@ export function createServer(broker: Broker): McpServer {
|
|
|
177
207
|
{
|
|
178
208
|
title: tool.name,
|
|
179
209
|
description: tool.description,
|
|
210
|
+
outputSchema,
|
|
180
211
|
inputSchema: tool.inputSchema,
|
|
181
212
|
annotations: {
|
|
182
213
|
readOnlyHint: tool.name === "read",
|
|
@@ -204,7 +235,12 @@ export function createServer(broker: Broker): McpServer {
|
|
|
204
235
|
return finishResult(
|
|
205
236
|
broker,
|
|
206
237
|
context,
|
|
207
|
-
toolResult(
|
|
238
|
+
toolResult(
|
|
239
|
+
result.toolResults,
|
|
240
|
+
result.sessionId,
|
|
241
|
+
result.cwd,
|
|
242
|
+
result.inputs,
|
|
243
|
+
),
|
|
208
244
|
);
|
|
209
245
|
},
|
|
210
246
|
);
|
|
@@ -215,7 +251,8 @@ export function createServer(broker: Broker): McpServer {
|
|
|
215
251
|
{
|
|
216
252
|
title: "Local sessions",
|
|
217
253
|
description:
|
|
218
|
-
"List online Pi sessions and
|
|
254
|
+
"List online Pi sessions, their saved binding counts, and this conversation's default. A zero bindingCount permits automatic pairing; status describes execution. Offline sessions do not delay the listing.",
|
|
255
|
+
outputSchema,
|
|
219
256
|
inputSchema: z.object({
|
|
220
257
|
sessionId: z
|
|
221
258
|
.string()
|
|
@@ -241,7 +278,7 @@ export function createServer(broker: Broker): McpServer {
|
|
|
241
278
|
},
|
|
242
279
|
inputs,
|
|
243
280
|
);
|
|
244
|
-
return
|
|
281
|
+
return finishResult(broker, context, result);
|
|
245
282
|
},
|
|
246
283
|
);
|
|
247
284
|
|
|
@@ -283,19 +320,19 @@ function textResult(
|
|
|
283
320
|
};
|
|
284
321
|
}
|
|
285
322
|
|
|
286
|
-
async function finishResult<
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
):
|
|
291
|
-
const
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
323
|
+
async function finishResult<
|
|
324
|
+
T extends { content: ReturnType<typeof toolResult>["content"] },
|
|
325
|
+
>(broker: Broker, context: RequestContext, result: T) {
|
|
326
|
+
const chatId = requestChatId(context);
|
|
327
|
+
const deliveries = chatId ? await broker.deliveries(chatId) : [];
|
|
328
|
+
const content = [...result.content, ...deliveryContent(deliveries)];
|
|
329
|
+
const structuredContent = {
|
|
330
|
+
text: content
|
|
331
|
+
.flatMap((block) => (block.type === "text" ? [block.text] : []))
|
|
332
|
+
.join("\n"),
|
|
333
|
+
};
|
|
297
334
|
await broker.acknowledgeDeliveries(deliveries, context.mcpReq.signal);
|
|
298
|
-
return
|
|
335
|
+
return { ...result, content, structuredContent };
|
|
299
336
|
}
|
|
300
337
|
|
|
301
338
|
function requestChatId(context: RequestContext): string | undefined {
|
package/src/session.ts
CHANGED
|
@@ -35,6 +35,7 @@ interface StoreRequest {
|
|
|
35
35
|
|
|
36
36
|
interface ActiveRequest {
|
|
37
37
|
request: RemoteRequest;
|
|
38
|
+
session: SessionDescription;
|
|
38
39
|
message: AssistantMessage;
|
|
39
40
|
completed: boolean;
|
|
40
41
|
cancelled: boolean;
|
|
@@ -298,6 +299,7 @@ export class LocalSession {
|
|
|
298
299
|
this.#queue.shift();
|
|
299
300
|
this.#active = {
|
|
300
301
|
request,
|
|
302
|
+
session: this.#description(),
|
|
301
303
|
message: output.message,
|
|
302
304
|
completed: false,
|
|
303
305
|
cancelled: false,
|
|
@@ -342,7 +344,7 @@ export class LocalSession {
|
|
|
342
344
|
this.#context = context;
|
|
343
345
|
const active = this.#active;
|
|
344
346
|
if (!active || message !== active.message) return;
|
|
345
|
-
const sessionId =
|
|
347
|
+
const sessionId = active.session.id;
|
|
346
348
|
for (const result of toolResults) rememberImages(sessionId, result.content);
|
|
347
349
|
active.completed = true;
|
|
348
350
|
active.toolResults = toolResults;
|
|
@@ -354,44 +356,36 @@ export class LocalSession {
|
|
|
354
356
|
this.#collectInputs();
|
|
355
357
|
const inputs = this.#inputs();
|
|
356
358
|
if (active.cancelled) {
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
active.request.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
await this.#flushDeliveries().catch(() => {});
|
|
377
|
-
}
|
|
359
|
+
const sessionFile = active.session.sessionFile;
|
|
360
|
+
const delivery: DeliveryRecord = {
|
|
361
|
+
id: randomUUID(),
|
|
362
|
+
chatId: active.request.chatId,
|
|
363
|
+
sessionId: active.session.id,
|
|
364
|
+
cwd: active.session.cwd,
|
|
365
|
+
toolCallIds:
|
|
366
|
+
active.request.type === "call"
|
|
367
|
+
? active.request.calls.map(({ id }) => id)
|
|
368
|
+
: [],
|
|
369
|
+
...(sessionFile
|
|
370
|
+
? { sessionFile }
|
|
371
|
+
: { inlineResults: active.toolResults }),
|
|
372
|
+
...(active.message.errorMessage
|
|
373
|
+
? { error: active.message.errorMessage }
|
|
374
|
+
: { error: "Request cancelled" }),
|
|
375
|
+
};
|
|
376
|
+
this.#deliveries.set(delivery.id, delivery);
|
|
377
|
+
await this.#flushDeliveries().catch(() => {});
|
|
378
378
|
} else {
|
|
379
|
-
await this.#connection?.send(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
type: "result",
|
|
390
|
-
id: active.request.id,
|
|
391
|
-
message: active.message,
|
|
392
|
-
inputs,
|
|
393
|
-
},
|
|
394
|
-
);
|
|
379
|
+
await this.#connection?.send({
|
|
380
|
+
type: "result",
|
|
381
|
+
id: active.request.id,
|
|
382
|
+
cwd: active.session.cwd,
|
|
383
|
+
message: active.message,
|
|
384
|
+
inputs,
|
|
385
|
+
...(active.request.type === "call"
|
|
386
|
+
? { toolResults: active.toolResults }
|
|
387
|
+
: {}),
|
|
388
|
+
});
|
|
395
389
|
}
|
|
396
390
|
this.#active = undefined;
|
|
397
391
|
this.#status = "idle";
|
package/src/state.ts
CHANGED
|
@@ -40,8 +40,12 @@ export class State {
|
|
|
40
40
|
return this.#bindings.get(chatId);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
bindingCounts(): Map<string, number> {
|
|
44
|
+
const counts = new Map<string, number>();
|
|
45
|
+
for (const sessionId of this.#bindings.values()) {
|
|
46
|
+
counts.set(sessionId, (counts.get(sessionId) ?? 0) + 1);
|
|
47
|
+
}
|
|
48
|
+
return counts;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
setBinding(chatId: string, sessionId: string): Promise<void> {
|
package/src/tools.ts
CHANGED
|
@@ -38,10 +38,12 @@ export const directTools = definitions.map((definition) => ({
|
|
|
38
38
|
export function toolResult(
|
|
39
39
|
toolResults: ToolResultMessage[],
|
|
40
40
|
sessionId: string,
|
|
41
|
+
cwd: string,
|
|
41
42
|
inputs: SessionInput[] = [],
|
|
42
43
|
) {
|
|
43
44
|
return {
|
|
44
45
|
content: [
|
|
46
|
+
{ type: "text" as const, text: JSON.stringify({ sessionId, cwd }) },
|
|
45
47
|
...toolResults.flatMap((result) => [
|
|
46
48
|
{
|
|
47
49
|
type: "text" as const,
|
|
@@ -70,7 +72,7 @@ export function inputContent(inputs: SessionInput[]) {
|
|
|
70
72
|
return inputs.flatMap(({ id, sessionId, message }) => [
|
|
71
73
|
{
|
|
72
74
|
type: "text" as const,
|
|
73
|
-
text: JSON.stringify({ piInput: id }),
|
|
75
|
+
text: JSON.stringify({ piInput: id, sessionId }),
|
|
74
76
|
},
|
|
75
77
|
...(typeof message.content === "string"
|
|
76
78
|
? [{ type: "text" as const, text: message.content }]
|