@zetaloop/chappie 0.5.0 → 1.0.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/src/config.ts DELETED
@@ -1,20 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import * as z from "zod";
4
-
5
- const configSchema = z.object({
6
- ask: z.boolean().optional(),
7
- connect: z.string().min(1).optional(),
8
- listen: z.union([z.boolean(), z.number().int().min(1).max(65535)]).optional(),
9
- });
10
-
11
- export async function readConfig(agentDir: string) {
12
- let contents: string;
13
- try {
14
- contents = await readFile(join(agentDir, "chappie.json"), "utf8");
15
- } catch (error) {
16
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
17
- throw error;
18
- }
19
- return configSchema.parse(JSON.parse(contents));
20
- }
package/src/delivery.ts DELETED
@@ -1,54 +0,0 @@
1
- import type { ToolResultMessage } from "@earendil-works/pi-ai";
2
- import type { Source } from "./activity.ts";
3
- import {
4
- contentWithImageReferences,
5
- resourceDescriptors,
6
- } from "./resources.ts";
7
-
8
- export interface DeliveryRecord extends Source {
9
- id: string;
10
- sessionId: string;
11
- cwd: string;
12
- toolResults: ToolResultMessage[];
13
- error?: string;
14
- }
15
-
16
- export function toolResultsContent(
17
- toolResults: ToolResultMessage[],
18
- sessionId: string,
19
- ) {
20
- return toolResults.flatMap((result) => [
21
- {
22
- type: "text" as const,
23
- text: JSON.stringify({
24
- toolCallId: result.toolCallId,
25
- toolName: result.toolName,
26
- isError: result.isError,
27
- }),
28
- },
29
- ...contentWithImageReferences(sessionId, result.content),
30
- ...resourceDescriptors(result.details).map((resource) => ({
31
- type: "resource_link" as const,
32
- uri: resource.uri,
33
- name: resource.name,
34
- mimeType: resource.mimeType,
35
- size: resource.size,
36
- })),
37
- ]);
38
- }
39
-
40
- export function deliveryContent(deliveries: DeliveryRecord[]) {
41
- return deliveries.flatMap((delivery) => [
42
- {
43
- type: "text" as const,
44
- text: JSON.stringify({
45
- deferredResult: delivery.id,
46
- requestId: delivery.requestId,
47
- sessionId: delivery.sessionId,
48
- cwd: delivery.cwd,
49
- error: delivery.error,
50
- }),
51
- },
52
- ...toolResultsContent(delivery.toolResults, delivery.sessionId),
53
- ]);
54
- }
package/src/history.ts DELETED
@@ -1,136 +0,0 @@
1
- import type { SessionEntry } from "@earendil-works/pi-coding-agent";
2
- import * as z from "zod";
3
- import type { Activity, Source } from "./activity.ts";
4
- import { toolResultsContent } from "./delivery.ts";
5
- import type { ProviderOutput } from "./provider.ts";
6
- import { contentWithImageReferences, rememberImages } from "./resources.ts";
7
-
8
- export const historyInput = z.object({
9
- limit: z
10
- .number()
11
- .int()
12
- .min(1)
13
- .default(20)
14
- .describe("Maximum number of history entries"),
15
- before: z
16
- .string()
17
- .min(1)
18
- .optional()
19
- .describe("Read entries before this entry ID"),
20
- after: z
21
- .string()
22
- .min(1)
23
- .optional()
24
- .describe("Read entries after this entry ID, starting with the earliest"),
25
- wait: z
26
- .boolean()
27
- .optional()
28
- .describe(
29
- "Wait up to 30 seconds for new entries when caught up; before reads return immediately",
30
- ),
31
- observer: z
32
- .boolean()
33
- .optional()
34
- .describe("Read work progress as an observer"),
35
- });
36
-
37
- export type HistoryRange = z.infer<typeof historyInput>;
38
- export type HistoryResult = ReturnType<typeof historyResult>;
39
-
40
- export const historyInstructions =
41
- "When resuming work, read recent history to recover progress, then continue from the current request.";
42
-
43
- export function historyResult(
44
- branch: SessionEntry[],
45
- sessionId: string,
46
- { limit, before, after }: HistoryRange,
47
- ) {
48
- const start = after ? entryIndex(branch, after) + 1 : 0;
49
- const end = before ? entryIndex(branch, before) : branch.length;
50
- if (start > end) throw new Error("History after must precede before");
51
- const entries = branch.slice(start, end).filter((entry) => {
52
- switch (entry.type) {
53
- case "message":
54
- return (
55
- entry.message.role !== "custom" ||
56
- entry.message.customType !== "chappie.request"
57
- );
58
- case "custom_message":
59
- return entry.customType !== "chappie.request";
60
- case "custom":
61
- return (
62
- entry.customType === "chappie.notice" &&
63
- (entry.data as Activity | undefined)?.event !== "history"
64
- );
65
- case "compaction":
66
- case "branch_summary":
67
- return true;
68
- default:
69
- return false;
70
- }
71
- });
72
- const selected = after ? entries.slice(0, limit) : entries.slice(-limit);
73
- const sources = new Map<string, Source>();
74
- for (const entry of branch) {
75
- if (entry.type !== "message" || entry.message.role !== "assistant")
76
- continue;
77
- const message = entry.message as ProviderOutput["message"];
78
- if (!message.chappie) continue;
79
- for (const block of message.content) {
80
- if (block.type === "toolCall") sources.set(block.id, message.chappie);
81
- }
82
- }
83
- return {
84
- count: selected.length,
85
- hasMore: entries.length > selected.length,
86
- content: selected.flatMap((entry) =>
87
- entryContent(entry, sessionId, sources),
88
- ),
89
- };
90
- }
91
-
92
- function entryIndex(entries: SessionEntry[], id: string): number {
93
- const index = entries.findIndex((entry) => entry.id === id);
94
- if (index === -1)
95
- throw new Error(`History entry ${id} is not on the current branch`);
96
- return index;
97
- }
98
-
99
- function entryContent(
100
- entry: SessionEntry,
101
- sessionId: string,
102
- sources: Map<string, Source>,
103
- ): ReturnType<typeof toolResultsContent> {
104
- const record: Record<string, unknown> = { ...entry };
105
- const message = entry.type === "message" ? entry.message : entry;
106
- if (!("content" in message)) {
107
- return [{ type: "text", text: JSON.stringify(record) }];
108
- }
109
- const { content, ...metadata } = message;
110
- if (entry.type === "message") {
111
- const source =
112
- entry.message.role === "toolResult"
113
- ? sources.get(entry.message.toolCallId)
114
- : undefined;
115
- record.message = { ...metadata, ...(source ? { chappie: source } : {}) };
116
- } else delete record.content;
117
- const header = { type: "text" as const, text: JSON.stringify(record) };
118
- if (entry.type === "message" && entry.message.role === "toolResult") {
119
- rememberImages(sessionId, entry.message.content);
120
- return [header, ...toolResultsContent([entry.message], sessionId)];
121
- }
122
- const blocks =
123
- typeof content === "string"
124
- ? [{ type: "text" as const, text: content }]
125
- : content;
126
- return [
127
- header,
128
- ...blocks.flatMap((block): ReturnType<typeof toolResultsContent> => {
129
- if (block.type === "text" || block.type === "image") {
130
- rememberImages(sessionId, [block]);
131
- return contentWithImageReferences(sessionId, [block]);
132
- }
133
- return [{ type: "text", text: JSON.stringify(block) }];
134
- }),
135
- ];
136
- }
package/src/index.ts DELETED
@@ -1,42 +0,0 @@
1
- import {
2
- type ExtensionAPI,
3
- getAgentDir,
4
- } from "@earendil-works/pi-coding-agent";
5
-
6
- export default async function chappie(pi: ExtensionAPI): Promise<void> {
7
- pi.registerFlag("chappie", {
8
- description: "Serve Chappie over MCP",
9
- type: "boolean",
10
- });
11
-
12
- if (process.argv.includes("--chappie")) {
13
- try {
14
- const { serveChappie } = await import("./stdio.ts");
15
- await serveChappie();
16
- } catch (error) {
17
- console.error(error instanceof Error ? error.message : String(error));
18
- process.exit(1);
19
- }
20
- }
21
-
22
- const agentDir = getAgentDir();
23
- const [
24
- { readConfig },
25
- { createChappieProvider },
26
- { LocalSession },
27
- { transfer },
28
- ] = await Promise.all([
29
- import("./config.ts"),
30
- import("./provider.ts"),
31
- import("./session.ts"),
32
- import("./transfer.ts"),
33
- ]);
34
- const config = await readConfig(agentDir);
35
- const session = new LocalSession(pi, agentDir, config.connect);
36
- session.install();
37
- pi.registerTool({
38
- ...transfer,
39
- execute: (...args) => session.transfer(...args),
40
- });
41
- pi.registerProvider(createChappieProvider((output) => session.start(output)));
42
- }
@@ -1,17 +0,0 @@
1
- Chappie connects this ChatGPT conversation to Pi sessions on one or more devices. Resume work with init using the task's Pi sessionId, including in a new chat or branch. When only a project or session name is known, find its device/cwd/name in sessions, then call init. If the target is absent or ambiguous, ask the user to resolve it. Pi sessions appear while the chappie/chatgpt provider is selected.
2
-
3
- init sets this chat's default Pi session. An execution tool also establishes a default on first use: sessionId selects its target, or omitting it selects the first online session with no saved bindings. These sessions may already contain work. Once a default exists, another tool's sessionId selects only that call's target. Defaults survive broker restarts, and several chats can share one Pi session. When globalAgents is present, read and follow the instructions at globalAgents.path. Follow initialization.instructions for your participation in the current task. When executing work, read recent history to recover progress, then continue from the current request.
4
-
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.
6
-
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.
8
-
9
- Respond promptly to new Pi user input with a substantive reply, interaction, or immediate action that makes the response apparent in Pi before continuing lengthy work. Address inputs received together in one response; an immediate answer or result serves as its own acknowledgment.
10
-
11
- When executing work, use chat for assistant messages in Pi, including progress, explanations, and completion results. Use read, bash, edit, write, and transfer directly. init.tools is a Pi tool catalog; tools returns full definitions for call. Invoke Chappie's MCP tools directly. Each call array is one native Pi batch; separate calls are separate Pi turns. For Pi interaction, call an installed interactive tool.
12
-
13
- transfer pairs files from ChatGPT with Pi destination paths in order. To copy between Pi sessions, use paths on the selected source session and to: { sessionId, paths } for the destination. Each side resolves paths in its own working directory. Omit files and to to return resource links for Pi paths or chappie:// image references. Relative paths use the Pi working directory; overwrite: true replaces existing targets. The host may request confirmation when retrieving exported bytes.
14
-
15
- Tool results identify the executing Pi sessionId and cwd. A shell command can access another directory without changing its Pi session. structuredContent.text includes tool output, new Pi input, webAnswer, and deferred results; images and resources are native content blocks. ChatGPT truncates tool responses exceeding 10,000 tokens. Continue from received results rather than repeating work. Host deadlines include queueing and execution; use local persistent processes for longer work.
16
-
17
- Use history to read the current Pi branch with entry IDs and timestamps. History defaults to the last 20 entries; before pages backward and after pages forward. Use after with wait: true to follow progress: available entries return immediately, otherwise the call waits up to 30 seconds. An empty result means no new entries. Set observer: true when reading as an observer. Read the work's completion message to form the final reply. History returns saved contents separately from new Pi input.