@fleetagent/pi-coding-agent 0.0.1 → 0.0.4

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.
Files changed (125) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/core/agent-session.d.ts +32 -7
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +225 -12
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/compaction/branch-summarization.d.ts +2 -2
  7. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  8. package/dist/core/compaction/branch-summarization.js.map +1 -1
  9. package/dist/core/compaction/compaction.d.ts +2 -2
  10. package/dist/core/compaction/compaction.d.ts.map +1 -1
  11. package/dist/core/compaction/compaction.js +1 -1
  12. package/dist/core/compaction/compaction.js.map +1 -1
  13. package/dist/core/extensions/runner.d.ts +3 -3
  14. package/dist/core/extensions/runner.d.ts.map +1 -1
  15. package/dist/core/extensions/runner.js +5 -5
  16. package/dist/core/extensions/runner.js.map +1 -1
  17. package/dist/core/extensions/types.d.ts +15 -8
  18. package/dist/core/extensions/types.d.ts.map +1 -1
  19. package/dist/core/extensions/types.js.map +1 -1
  20. package/dist/core/footer-data-provider.d.ts +1 -1
  21. package/dist/core/footer-data-provider.d.ts.map +1 -1
  22. package/dist/core/footer-data-provider.js +1 -1
  23. package/dist/core/footer-data-provider.js.map +1 -1
  24. package/dist/core/messages.d.ts +1 -0
  25. package/dist/core/messages.d.ts.map +1 -1
  26. package/dist/core/messages.js +4 -0
  27. package/dist/core/messages.js.map +1 -1
  28. package/dist/core/pi-agent.d.ts +5 -3
  29. package/dist/core/pi-agent.d.ts.map +1 -1
  30. package/dist/core/pi-agent.js +64 -18
  31. package/dist/core/pi-agent.js.map +1 -1
  32. package/dist/core/session/in-memory-session-manager.d.ts.map +1 -1
  33. package/dist/core/session/in-memory-session-manager.js +5 -7
  34. package/dist/core/session/in-memory-session-manager.js.map +1 -1
  35. package/dist/core/session/in-memory-session.d.ts +3 -1
  36. package/dist/core/session/in-memory-session.d.ts.map +1 -1
  37. package/dist/core/session/in-memory-session.js +5 -2
  38. package/dist/core/session/in-memory-session.js.map +1 -1
  39. package/dist/core/session/index.d.ts +2 -2
  40. package/dist/core/session/index.d.ts.map +1 -1
  41. package/dist/core/session/index.js.map +1 -1
  42. package/dist/core/session/jsonl-helpers.d.ts.map +1 -1
  43. package/dist/core/session/jsonl-helpers.js +4 -4
  44. package/dist/core/session/jsonl-helpers.js.map +1 -1
  45. package/dist/core/session/local-session-manager.d.ts.map +1 -1
  46. package/dist/core/session/local-session-manager.js +12 -11
  47. package/dist/core/session/local-session-manager.js.map +1 -1
  48. package/dist/core/session/local-session.d.ts +3 -1
  49. package/dist/core/session/local-session.d.ts.map +1 -1
  50. package/dist/core/session/local-session.js +7 -2
  51. package/dist/core/session/local-session.js.map +1 -1
  52. package/dist/core/session/remote-session-client.d.ts +6 -1
  53. package/dist/core/session/remote-session-client.d.ts.map +1 -1
  54. package/dist/core/session/remote-session-client.js.map +1 -1
  55. package/dist/core/session/remote-session-manager.d.ts.map +1 -1
  56. package/dist/core/session/remote-session-manager.js +28 -7
  57. package/dist/core/session/remote-session-manager.js.map +1 -1
  58. package/dist/core/session/remote-session.d.ts +3 -0
  59. package/dist/core/session/remote-session.d.ts.map +1 -1
  60. package/dist/core/session/remote-session.js +4 -1
  61. package/dist/core/session/remote-session.js.map +1 -1
  62. package/dist/core/session/session.d.ts +9 -3
  63. package/dist/core/session/session.d.ts.map +1 -1
  64. package/dist/core/session/session.js +64 -10
  65. package/dist/core/session/session.js.map +1 -1
  66. package/dist/core/session/stores/in-memory-session-store.d.ts +6 -14
  67. package/dist/core/session/stores/in-memory-session-store.d.ts.map +1 -1
  68. package/dist/core/session/stores/in-memory-session-store.js +8 -34
  69. package/dist/core/session/stores/in-memory-session-store.js.map +1 -1
  70. package/dist/core/session/stores/jsonl-session-store.d.ts +14 -14
  71. package/dist/core/session/stores/jsonl-session-store.d.ts.map +1 -1
  72. package/dist/core/session/stores/jsonl-session-store.js +153 -162
  73. package/dist/core/session/stores/jsonl-session-store.js.map +1 -1
  74. package/dist/core/session/stores/remote-session-store.d.ts +4 -6
  75. package/dist/core/session/stores/remote-session-store.d.ts.map +1 -1
  76. package/dist/core/session/stores/remote-session-store.js +18 -30
  77. package/dist/core/session/stores/remote-session-store.js.map +1 -1
  78. package/dist/core/session/stores/session-store.d.ts +1 -15
  79. package/dist/core/session/stores/session-store.d.ts.map +1 -1
  80. package/dist/core/session/stores/session-store.js.map +1 -1
  81. package/dist/core/session-cwd.d.ts +2 -2
  82. package/dist/core/session-cwd.d.ts.map +1 -1
  83. package/dist/core/session-cwd.js +5 -5
  84. package/dist/core/session-cwd.js.map +1 -1
  85. package/dist/index.d.ts +1 -1
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js.map +1 -1
  88. package/dist/modes/interactive/interactive-mode.d.ts +1 -1
  89. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  90. package/dist/modes/interactive/interactive-mode.js +39 -37
  91. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  92. package/docs/extensions.md +35 -32
  93. package/docs/index.md +1 -1
  94. package/docs/sdk.md +2 -0
  95. package/docs/session-format.md +21 -21
  96. package/docs/sessions.md +2 -2
  97. package/docs/tui.md +1 -1
  98. package/examples/README.md +3 -0
  99. package/examples/extensions/README.md +1 -1
  100. package/examples/extensions/auto-commit-on-exit.ts +1 -1
  101. package/examples/extensions/bookmark.ts +3 -3
  102. package/examples/extensions/confirm-destructive.ts +1 -1
  103. package/examples/extensions/custom-compaction.ts +1 -1
  104. package/examples/extensions/custom-footer.ts +2 -2
  105. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  106. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  107. package/examples/extensions/git-checkpoint.ts +1 -1
  108. package/examples/extensions/handoff.ts +2 -2
  109. package/examples/extensions/plan-mode/index.ts +1 -1
  110. package/examples/extensions/preset.ts +1 -1
  111. package/examples/extensions/qna.ts +1 -1
  112. package/examples/extensions/sandbox/package.json +1 -1
  113. package/examples/extensions/snake.ts +1 -1
  114. package/examples/extensions/space-invaders.ts +1 -1
  115. package/examples/extensions/summarize.ts +1 -1
  116. package/examples/extensions/tic-tac-toe.ts +1 -1
  117. package/examples/extensions/todo.ts +1 -1
  118. package/examples/extensions/tools.ts +1 -1
  119. package/examples/extensions/with-deps/package.json +1 -1
  120. package/examples/remote-session-server/README.md +66 -0
  121. package/examples/remote-session-server/server.ts +359 -0
  122. package/examples/sdk/11-sessions.ts +3 -3
  123. package/examples/sdk/13-session-runtime.ts +6 -6
  124. package/npm-shrinkwrap.json +12 -12
  125. package/package.json +4 -4
@@ -0,0 +1,66 @@
1
+ # Remote session storage server
2
+
3
+ Example Hono server for pi remote sessions. It stores each session as a JSONL file for easy inspection and uses a static bearer token.
4
+
5
+ ## Run
6
+
7
+ ```bash
8
+ PI_REMOTE_SESSION_TOKEN=dev-token \
9
+ PI_REMOTE_SESSION_DIR=/tmp/pi-remote-sessions \
10
+ npx tsx packages/coding-agent/examples/remote-session-server/server.ts
11
+ ```
12
+
13
+ The server listens on `http://localhost:8787` by default. Override with `PORT`. Requests and session operations are logged to stdout.
14
+
15
+ ## Use from pi CLI
16
+
17
+ From the repo root:
18
+
19
+ ```bash
20
+ PI_REMOTE_SESSION_BASE_URL=http://localhost:8787 \
21
+ PI_REMOTE_SESSION_TOKEN=dev-token \
22
+ ./pi-test.sh
23
+ ```
24
+
25
+ Equivalent flags:
26
+
27
+ ```bash
28
+ ./pi-test.sh --remote-session-base-url http://localhost:8787 --remote-session-token dev-token
29
+ ```
30
+
31
+ Optional project id:
32
+
33
+ ```bash
34
+ PI_REMOTE_PROJECT_ID=my-project \
35
+ PI_REMOTE_SESSION_BASE_URL=http://localhost:8787 \
36
+ PI_REMOTE_SESSION_TOKEN=dev-token \
37
+ ./pi-test.sh
38
+ ```
39
+
40
+ ## Use from the SDK
41
+
42
+ ```ts
43
+ import { PiAgent, RemoteSessionManager } from "@fleetagent/pi-coding-agent";
44
+
45
+ const sessionManager = new RemoteSessionManager({
46
+ baseUrl: "http://localhost:8787",
47
+ token: "dev-token",
48
+ cwd: process.cwd(),
49
+ });
50
+
51
+ const pi = await PiAgent.create({ sessionManager });
52
+ await pi.prompt("hello from a remote session");
53
+ ```
54
+
55
+ Sessions are stored as `<session-id>.jsonl` under `PI_REMOTE_SESSION_DIR`.
56
+
57
+ ## API covered
58
+
59
+ - `POST /v1/sessions`
60
+ - `GET /v1/sessions`
61
+ - `GET /v1/sessions/recent`
62
+ - `GET /v1/sessions/:id`
63
+ - `POST /v1/sessions/:id/entries`
64
+ - `PUT /v1/sessions/:id/snapshot`
65
+ - `POST /v1/sessions/:id/fork`
66
+ - `POST /v1/sessions/import-jsonl`
@@ -0,0 +1,359 @@
1
+ import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
2
+ import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import {
5
+ CURRENT_SESSION_VERSION,
6
+ type FileEntry,
7
+ type SessionEntry,
8
+ type SessionHeader,
9
+ type SessionInfo,
10
+ } from "@fleetagent/pi-coding-agent";
11
+ import { serve } from "@hono/node-server";
12
+ import { Hono } from "hono";
13
+
14
+ interface CreateSessionRequest {
15
+ id?: string;
16
+ cwd: string;
17
+ projectId?: string;
18
+ parentSession?: string;
19
+ metadata?: Record<string, unknown>;
20
+ }
21
+
22
+ interface AppendEntriesRequest {
23
+ baseEtag?: string;
24
+ entries: FileEntry[];
25
+ }
26
+
27
+ interface ReplaceSnapshotRequest {
28
+ baseEtag?: string;
29
+ entries: FileEntry[];
30
+ }
31
+
32
+ interface ForkSessionRequest {
33
+ cwd: string;
34
+ projectId?: string;
35
+ leafId?: string;
36
+ }
37
+
38
+ interface ImportJsonlRequest {
39
+ cwd: string;
40
+ projectId?: string;
41
+ sourceName?: string;
42
+ entries: FileEntry[];
43
+ metadata?: Record<string, unknown>;
44
+ }
45
+
46
+ interface RemoteSessionSnapshot {
47
+ reference: string;
48
+ id: string;
49
+ version?: number;
50
+ entries: FileEntry[];
51
+ etag: string;
52
+ }
53
+
54
+ const port = Number.parseInt(process.env.PORT ?? "8787", 10);
55
+ const token = process.env.PI_REMOTE_SESSION_TOKEN ?? "dev-token";
56
+ const dataDir = process.env.PI_REMOTE_SESSION_DIR ?? join(process.cwd(), ".pi-remote-sessions");
57
+ const app = new Hono();
58
+
59
+ function log(message: string, details?: Record<string, unknown>): void {
60
+ const suffix = details ? ` ${JSON.stringify(details)}` : "";
61
+ console.log(`[${new Date().toISOString()}] ${message}${suffix}`);
62
+ }
63
+
64
+ function isRecord(value: unknown): value is Record<string, unknown> {
65
+ return typeof value === "object" && value !== null && !Array.isArray(value);
66
+ }
67
+
68
+ function isSessionId(value: string): boolean {
69
+ return /^[A-Za-z0-9._-]+$/.test(value);
70
+ }
71
+
72
+ function stripRemotePrefix(reference: string): string {
73
+ return reference.startsWith("remote:") ? reference.slice("remote:".length) : reference;
74
+ }
75
+
76
+ function assertSessionId(value: string): string {
77
+ const id = stripRemotePrefix(value);
78
+ if (!isSessionId(id)) {
79
+ throw new Error(`Invalid session id: ${value}`);
80
+ }
81
+ return id;
82
+ }
83
+
84
+ function createSessionId(): string {
85
+ return randomUUID();
86
+ }
87
+
88
+ function sessionPath(sessionId: string): string {
89
+ return join(dataDir, `${assertSessionId(sessionId)}.jsonl`);
90
+ }
91
+
92
+ function referenceFor(sessionId: string): string {
93
+ return `remote:${sessionId}`;
94
+ }
95
+
96
+ function etagFor(entries: FileEntry[]): string {
97
+ return createHash("sha256")
98
+ .update(entries.map((entry) => JSON.stringify(entry)).join("\n"))
99
+ .digest("hex");
100
+ }
101
+
102
+ function writeJsonl(entries: FileEntry[]): string {
103
+ return `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`;
104
+ }
105
+
106
+ function parseJsonl(content: string): FileEntry[] {
107
+ const entries: FileEntry[] = [];
108
+ for (const line of content.split("\n")) {
109
+ if (!line.trim()) continue;
110
+ entries.push(JSON.parse(line) as FileEntry);
111
+ }
112
+ return entries;
113
+ }
114
+
115
+ async function loadEntries(sessionId: string): Promise<FileEntry[]> {
116
+ return parseJsonl(await readFile(sessionPath(sessionId), "utf8"));
117
+ }
118
+
119
+ async function saveEntries(sessionId: string, entries: FileEntry[]): Promise<void> {
120
+ await mkdir(dataDir, { recursive: true });
121
+ await writeFile(sessionPath(sessionId), writeJsonl(entries), "utf8");
122
+ }
123
+
124
+ function snapshotFor(sessionId: string, entries: FileEntry[]): RemoteSessionSnapshot {
125
+ const header = entries[0];
126
+ return {
127
+ reference: referenceFor(sessionId),
128
+ id: sessionId,
129
+ version: header?.type === "session" ? header.version : undefined,
130
+ entries,
131
+ etag: etagFor(entries),
132
+ };
133
+ }
134
+
135
+ function validateBaseEtag(entries: FileEntry[], baseEtag: string | undefined): boolean {
136
+ return baseEtag === undefined || baseEtag === etagFor(entries);
137
+ }
138
+
139
+ function makeHeader(id: string, cwd: string, parentSession?: string): SessionHeader {
140
+ return {
141
+ type: "session",
142
+ version: CURRENT_SESSION_VERSION,
143
+ id,
144
+ timestamp: new Date().toISOString(),
145
+ cwd,
146
+ parentSession,
147
+ };
148
+ }
149
+
150
+ function getBranch(entries: FileEntry[], leafId: string | undefined): SessionEntry[] {
151
+ const sessionEntries = entries.filter((entry): entry is SessionEntry => entry.type !== "session");
152
+ if (leafId === undefined) {
153
+ return sessionEntries.filter((entry) => entry.type !== "label");
154
+ }
155
+
156
+ const byId = new Map(sessionEntries.map((entry) => [entry.id, entry]));
157
+ const path: SessionEntry[] = [];
158
+ let current: string | null | undefined = leafId;
159
+ while (current) {
160
+ const entry = byId.get(current);
161
+ if (!entry) {
162
+ throw new Error(`Entry ${leafId} not found`);
163
+ }
164
+ path.unshift(entry);
165
+ current = entry.parentId;
166
+ }
167
+ return path.filter((entry) => entry.type !== "label");
168
+ }
169
+
170
+ function forkEntries(sourceEntries: FileEntry[], header: SessionHeader, leafId: string | undefined): FileEntry[] {
171
+ const path = getBranch(sourceEntries, leafId);
172
+ const pathIds = new Set(path.map((entry) => entry.id));
173
+ const labelEntries = sourceEntries.filter(
174
+ (entry): entry is SessionEntry & { type: "label"; targetId: string } =>
175
+ entry.type === "label" && pathIds.has(entry.targetId),
176
+ );
177
+ return [header, ...structuredClone(path), ...structuredClone(labelEntries)];
178
+ }
179
+
180
+ function isMessageWithContent(value: unknown): value is { role?: string; content?: unknown } {
181
+ return isRecord(value) && "content" in value;
182
+ }
183
+
184
+ function extractText(value: unknown): string {
185
+ if (typeof value === "string") return value;
186
+ if (!Array.isArray(value)) return "";
187
+ return value
188
+ .filter(
189
+ (part): part is { type: string; text: string } =>
190
+ isRecord(part) && part.type === "text" && typeof part.text === "string",
191
+ )
192
+ .map((part) => part.text)
193
+ .join(" ");
194
+ }
195
+
196
+ function sessionInfo(sessionId: string, entries: FileEntry[], modified: Date): SessionInfo | null {
197
+ const header = entries[0];
198
+ if (!header || header.type !== "session") return null;
199
+
200
+ let name: string | undefined;
201
+ let messageCount = 0;
202
+ let firstMessage = "";
203
+ const allMessages: string[] = [];
204
+ for (const entry of entries) {
205
+ if (entry.type === "session_info") {
206
+ name = entry.name?.trim() || undefined;
207
+ }
208
+ if (entry.type !== "message") continue;
209
+ messageCount++;
210
+ const message = entry.message;
211
+ if (!isMessageWithContent(message)) continue;
212
+ if (message.role !== "user" && message.role !== "assistant") continue;
213
+ const text = extractText(message.content);
214
+ if (!text) continue;
215
+ allMessages.push(text);
216
+ if (!firstMessage && message.role === "user") {
217
+ firstMessage = text;
218
+ }
219
+ }
220
+
221
+ return {
222
+ reference: referenceFor(sessionId),
223
+ path: referenceFor(sessionId),
224
+ id: sessionId,
225
+ cwd: header.cwd,
226
+ name,
227
+ parentSessionPath: header.parentSession,
228
+ created: new Date(header.timestamp),
229
+ modified,
230
+ messageCount,
231
+ firstMessage: firstMessage || "(no messages)",
232
+ allMessagesText: allMessages.join(" "),
233
+ };
234
+ }
235
+
236
+ function authorized(authHeader: string | undefined): boolean {
237
+ const prefix = "Bearer ";
238
+ if (!authHeader?.startsWith(prefix)) return false;
239
+ const received = Buffer.from(authHeader.slice(prefix.length));
240
+ const expected = Buffer.from(token);
241
+ return received.length === expected.length && timingSafeEqual(received, expected);
242
+ }
243
+
244
+ app.use("/v1/*", async (ctx, next) => {
245
+ const started = Date.now();
246
+ const method = ctx.req.method;
247
+ const path = new URL(ctx.req.url).pathname;
248
+ log("request", { method, path });
249
+
250
+ if (!authorized(ctx.req.header("Authorization"))) {
251
+ log("response", { method, path, status: 401, durationMs: Date.now() - started });
252
+ return ctx.text("Unauthorized", 401);
253
+ }
254
+
255
+ await next();
256
+ log("response", { method, path, status: ctx.res.status, durationMs: Date.now() - started });
257
+ });
258
+
259
+ app.get("/health", (ctx) => ctx.json({ ok: true, dataDir }));
260
+
261
+ app.post("/v1/sessions", async (ctx) => {
262
+ const body = (await ctx.req.json()) as CreateSessionRequest;
263
+ const id = assertSessionId(body.id ?? createSessionId());
264
+ const entries: FileEntry[] = [makeHeader(id, body.cwd, body.parentSession)];
265
+ log("create session", { id, cwd: body.cwd, parentSession: body.parentSession, projectId: body.projectId });
266
+ await saveEntries(id, entries);
267
+ return ctx.json(snapshotFor(id, entries));
268
+ });
269
+
270
+ app.get("/v1/sessions", async (ctx) => {
271
+ await mkdir(dataDir, { recursive: true });
272
+ const files = (await readdir(dataDir)).filter((file) => file.endsWith(".jsonl"));
273
+ const sessions: SessionInfo[] = [];
274
+ for (const file of files) {
275
+ const id = file.slice(0, -".jsonl".length);
276
+ const path = sessionPath(id);
277
+ const entries = parseJsonl(await readFile(path, "utf8"));
278
+ const info = sessionInfo(id, entries, (await stat(path)).mtime);
279
+ if (info) sessions.push(info);
280
+ }
281
+ log("list sessions", { count: sessions.length });
282
+ return ctx.json({ sessions: sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime()) });
283
+ });
284
+
285
+ app.get("/v1/sessions/recent", async (ctx) => {
286
+ await mkdir(dataDir, { recursive: true });
287
+ const files = (await readdir(dataDir)).filter((file) => file.endsWith(".jsonl"));
288
+ let recent: { id: string; modified: Date } | undefined;
289
+ for (const file of files) {
290
+ const id = file.slice(0, -".jsonl".length);
291
+ const modified = (await stat(sessionPath(id))).mtime;
292
+ if (!recent || modified > recent.modified) {
293
+ recent = { id, modified };
294
+ }
295
+ }
296
+ if (!recent) return ctx.text("No sessions", 404);
297
+ const entries = await loadEntries(recent.id);
298
+ log("recent session", { id: recent.id, entries: entries.length });
299
+ return ctx.json(snapshotFor(recent.id, entries));
300
+ });
301
+
302
+ app.get("/v1/sessions/:id", async (ctx) => {
303
+ const id = assertSessionId(ctx.req.param("id"));
304
+ const entries = await loadEntries(id);
305
+ log("open session", { id, entries: entries.length });
306
+ return ctx.json(snapshotFor(id, entries));
307
+ });
308
+
309
+ app.post("/v1/sessions/:id/entries", async (ctx) => {
310
+ const id = assertSessionId(ctx.req.param("id"));
311
+ const body = (await ctx.req.json()) as AppendEntriesRequest;
312
+ const entries = await loadEntries(id);
313
+ if (!validateBaseEtag(entries, body.baseEtag)) return ctx.text("ETag mismatch", 409);
314
+ const nextEntries = [...entries, ...body.entries];
315
+ log("append entries", {
316
+ id,
317
+ entries: body.entries.length,
318
+ totalEntries: nextEntries.length,
319
+ baseEtag: body.baseEtag,
320
+ });
321
+ await saveEntries(id, nextEntries);
322
+ return ctx.json({ accepted: body.entries.length, etag: etagFor(nextEntries) });
323
+ });
324
+
325
+ app.put("/v1/sessions/:id/snapshot", async (ctx) => {
326
+ const id = assertSessionId(ctx.req.param("id"));
327
+ const body = (await ctx.req.json()) as ReplaceSnapshotRequest;
328
+ const entries = await loadEntries(id);
329
+ if (!validateBaseEtag(entries, body.baseEtag)) return ctx.text("ETag mismatch", 409);
330
+ log("replace snapshot", { id, entries: body.entries.length, baseEtag: body.baseEtag });
331
+ await saveEntries(id, body.entries);
332
+ return ctx.json({ etag: etagFor(body.entries) });
333
+ });
334
+
335
+ app.post("/v1/sessions/:id/fork", async (ctx) => {
336
+ const sourceId = assertSessionId(ctx.req.param("id"));
337
+ const body = (await ctx.req.json()) as ForkSessionRequest;
338
+ const forkId = createSessionId();
339
+ const sourceEntries = await loadEntries(sourceId);
340
+ const entries = forkEntries(sourceEntries, makeHeader(forkId, body.cwd, referenceFor(sourceId)), body.leafId);
341
+ log("fork session", { sourceId, forkId, leafId: body.leafId, entries: entries.length, projectId: body.projectId });
342
+ await saveEntries(forkId, entries);
343
+ return ctx.json(snapshotFor(forkId, entries));
344
+ });
345
+
346
+ app.post("/v1/sessions/import-jsonl", async (ctx) => {
347
+ const body = (await ctx.req.json()) as ImportJsonlRequest;
348
+ const id = assertSessionId(body.entries[0]?.type === "session" ? body.entries[0].id : createSessionId());
349
+ const entries = body.entries[0]?.type === "session" ? body.entries : [makeHeader(id, body.cwd), ...body.entries];
350
+ log("import jsonl", { id, sourceName: body.sourceName, entries: entries.length, projectId: body.projectId });
351
+ await saveEntries(id, entries);
352
+ return ctx.json(snapshotFor(id, entries));
353
+ });
354
+
355
+ serve({ fetch: app.fetch, port }, (info) => {
356
+ console.log(`Remote session server listening on http://localhost:${info.port}`);
357
+ console.log(`Data directory: ${dataDir}`);
358
+ console.log(`Bearer token: ${token}`);
359
+ });
@@ -11,14 +11,14 @@ const cwd = process.cwd();
11
11
  // In-memory (no persistence)
12
12
  const inMemoryPi = await PiAgent.create({ sessionManager: new InMemorySessionManager(cwd) });
13
13
  const inMemory = await inMemoryPi.createAgentSession();
14
- console.log("In-memory session:", inMemory.sessionFile ?? "(none)");
14
+ console.log("In-memory session:", inMemory.sessionReference ?? "(none)");
15
15
  await inMemoryPi.dispose();
16
16
 
17
17
  // New persistent session
18
18
  const newSessionManager = new LocalSessionManager({ cwd });
19
19
  const newSessionPi = await PiAgent.create({ cwd, sessionManager: newSessionManager });
20
20
  const newSession = await newSessionPi.createAgentSession();
21
- console.log("New session file:", newSession.sessionFile);
21
+ console.log("New session reference:", newSession.sessionReference);
22
22
  await newSessionPi.dispose();
23
23
 
24
24
  // Continue most recent session (or create new if none)
@@ -26,7 +26,7 @@ const continuedSessionManager = new LocalSessionManager({ cwd });
26
26
  const continuedPi = await PiAgent.create({ cwd, sessionManager: continuedSessionManager });
27
27
  const continued = await continuedPi.createAgentSession({ session: continuedSessionManager.continueRecent() });
28
28
  if (continuedPi.modelFallbackMessage) console.log("Note:", continuedPi.modelFallbackMessage);
29
- console.log("Continued session:", continued.sessionFile);
29
+ console.log("Continued session:", continued.sessionReference);
30
30
  await continuedPi.dispose();
31
31
 
32
32
  // List and open specific session
@@ -32,17 +32,17 @@ async function bindSession() {
32
32
 
33
33
  await pi.createAgentSession();
34
34
  let session = await bindSession();
35
- const originalSessionFile = session.sessionFile;
36
- console.log("Initial session:", originalSessionFile);
35
+ const originalSessionReference = session.sessionReference;
36
+ console.log("Initial session:", originalSessionReference);
37
37
 
38
38
  await pi.newSession();
39
39
  session = await bindSession();
40
- console.log("After newSession():", session.sessionFile);
40
+ console.log("After newSession():", session.sessionReference);
41
41
 
42
- if (originalSessionFile) {
43
- await pi.switchSession(originalSessionFile);
42
+ if (originalSessionReference) {
43
+ await pi.switchSession(originalSessionReference);
44
44
  session = await bindSession();
45
- console.log("After switchSession():", session.sessionFile);
45
+ console.log("After switchSession():", session.sessionReference);
46
46
  }
47
47
 
48
48
  unsubscribe?.();
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@fleetagent/pi-coding-agent",
3
- "version": "0.0.1",
3
+ "version": "0.0.4",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@fleetagent/pi-coding-agent",
9
- "version": "0.0.1",
9
+ "version": "0.0.4",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@fleetagent/pi-agent-core": "^0.0.1",
13
- "@fleetagent/pi-ai": "^0.0.1",
14
- "@fleetagent/pi-tui": "^0.0.1",
12
+ "@fleetagent/pi-agent-core": "^0.0.4",
13
+ "@fleetagent/pi-ai": "^0.0.4",
14
+ "@fleetagent/pi-tui": "^0.0.4",
15
15
  "@silvia-odwyer/photon-node": "0.3.4",
16
16
  "chalk": "5.6.2",
17
17
  "cross-spawn": "7.0.6",
@@ -473,11 +473,11 @@
473
473
  }
474
474
  },
475
475
  "node_modules/@fleetagent/pi-agent-core": {
476
- "version": "0.0.1",
477
- "resolved": "https://registry.npmjs.org/@fleetagent/pi-agent-core/-/pi-agent-core-0.0.1.tgz",
476
+ "version": "0.0.4",
477
+ "resolved": "https://registry.npmjs.org/@fleetagent/pi-agent-core/-/pi-agent-core-0.0.4.tgz",
478
478
  "license": "MIT",
479
479
  "dependencies": {
480
- "@fleetagent/pi-ai": "^0.0.1",
480
+ "@fleetagent/pi-ai": "^0.0.4",
481
481
  "ignore": "7.0.5",
482
482
  "typebox": "1.1.38",
483
483
  "yaml": "2.9.0"
@@ -487,8 +487,8 @@
487
487
  }
488
488
  },
489
489
  "node_modules/@fleetagent/pi-ai": {
490
- "version": "0.0.1",
491
- "resolved": "https://registry.npmjs.org/@fleetagent/pi-ai/-/pi-ai-0.0.1.tgz",
490
+ "version": "0.0.4",
491
+ "resolved": "https://registry.npmjs.org/@fleetagent/pi-ai/-/pi-ai-0.0.4.tgz",
492
492
  "license": "MIT",
493
493
  "dependencies": {
494
494
  "@anthropic-ai/sdk": "0.91.1",
@@ -509,8 +509,8 @@
509
509
  }
510
510
  },
511
511
  "node_modules/@fleetagent/pi-tui": {
512
- "version": "0.0.1",
513
- "resolved": "https://registry.npmjs.org/@fleetagent/pi-tui/-/pi-tui-0.0.1.tgz",
512
+ "version": "0.0.4",
513
+ "resolved": "https://registry.npmjs.org/@fleetagent/pi-tui/-/pi-tui-0.0.4.tgz",
514
514
  "license": "MIT",
515
515
  "dependencies": {
516
516
  "get-east-asian-width": "1.6.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetagent/pi-coding-agent",
3
- "version": "0.0.1",
3
+ "version": "0.0.4",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -39,9 +39,9 @@
39
39
  "prepublishOnly": "npm run clean && npm run build && npm run shrinkwrap"
40
40
  },
41
41
  "dependencies": {
42
- "@fleetagent/pi-agent-core": "^0.0.1",
43
- "@fleetagent/pi-ai": "^0.0.1",
44
- "@fleetagent/pi-tui": "^0.0.1",
42
+ "@fleetagent/pi-agent-core": "^0.0.4",
43
+ "@fleetagent/pi-ai": "^0.0.4",
44
+ "@fleetagent/pi-tui": "^0.0.4",
45
45
  "@silvia-odwyer/photon-node": "0.3.4",
46
46
  "chalk": "5.6.2",
47
47
  "cross-spawn": "7.0.6",