@fleetagent/pi-coding-agent 0.0.1 → 0.0.2
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/CHANGELOG.md +16 -0
- package/dist/core/agent-session.d.ts +5 -5
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +8 -11
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +2 -2
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +2 -2
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +1 -1
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +3 -3
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +5 -5
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +15 -8
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/pi-agent.d.ts +5 -3
- package/dist/core/pi-agent.d.ts.map +1 -1
- package/dist/core/pi-agent.js +64 -18
- package/dist/core/pi-agent.js.map +1 -1
- package/dist/core/session/in-memory-session-manager.d.ts.map +1 -1
- package/dist/core/session/in-memory-session-manager.js +5 -7
- package/dist/core/session/in-memory-session-manager.js.map +1 -1
- package/dist/core/session/in-memory-session.d.ts +3 -1
- package/dist/core/session/in-memory-session.d.ts.map +1 -1
- package/dist/core/session/in-memory-session.js +5 -2
- package/dist/core/session/in-memory-session.js.map +1 -1
- package/dist/core/session/index.d.ts +2 -2
- package/dist/core/session/index.d.ts.map +1 -1
- package/dist/core/session/index.js.map +1 -1
- package/dist/core/session/jsonl-helpers.d.ts.map +1 -1
- package/dist/core/session/jsonl-helpers.js +4 -4
- package/dist/core/session/jsonl-helpers.js.map +1 -1
- package/dist/core/session/local-session-manager.d.ts.map +1 -1
- package/dist/core/session/local-session-manager.js +12 -11
- package/dist/core/session/local-session-manager.js.map +1 -1
- package/dist/core/session/local-session.d.ts +3 -1
- package/dist/core/session/local-session.d.ts.map +1 -1
- package/dist/core/session/local-session.js +7 -2
- package/dist/core/session/local-session.js.map +1 -1
- package/dist/core/session/remote-session-client.d.ts +6 -1
- package/dist/core/session/remote-session-client.d.ts.map +1 -1
- package/dist/core/session/remote-session-client.js.map +1 -1
- package/dist/core/session/remote-session-manager.d.ts.map +1 -1
- package/dist/core/session/remote-session-manager.js +28 -7
- package/dist/core/session/remote-session-manager.js.map +1 -1
- package/dist/core/session/remote-session.d.ts +3 -0
- package/dist/core/session/remote-session.d.ts.map +1 -1
- package/dist/core/session/remote-session.js +4 -1
- package/dist/core/session/remote-session.js.map +1 -1
- package/dist/core/session/session.d.ts +9 -3
- package/dist/core/session/session.d.ts.map +1 -1
- package/dist/core/session/session.js +64 -10
- package/dist/core/session/session.js.map +1 -1
- package/dist/core/session/stores/in-memory-session-store.d.ts +6 -14
- package/dist/core/session/stores/in-memory-session-store.d.ts.map +1 -1
- package/dist/core/session/stores/in-memory-session-store.js +8 -34
- package/dist/core/session/stores/in-memory-session-store.js.map +1 -1
- package/dist/core/session/stores/jsonl-session-store.d.ts +14 -14
- package/dist/core/session/stores/jsonl-session-store.d.ts.map +1 -1
- package/dist/core/session/stores/jsonl-session-store.js +153 -162
- package/dist/core/session/stores/jsonl-session-store.js.map +1 -1
- package/dist/core/session/stores/remote-session-store.d.ts +4 -6
- package/dist/core/session/stores/remote-session-store.d.ts.map +1 -1
- package/dist/core/session/stores/remote-session-store.js +18 -30
- package/dist/core/session/stores/remote-session-store.js.map +1 -1
- package/dist/core/session/stores/session-store.d.ts +1 -15
- package/dist/core/session/stores/session-store.d.ts.map +1 -1
- package/dist/core/session/stores/session-store.js.map +1 -1
- package/dist/core/session-cwd.d.ts +2 -2
- package/dist/core/session-cwd.d.ts.map +1 -1
- package/dist/core/session-cwd.js +5 -5
- package/dist/core/session-cwd.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +39 -37
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/extensions.md +35 -32
- package/docs/index.md +1 -1
- package/docs/sdk.md +2 -0
- package/docs/session-format.md +21 -21
- package/docs/sessions.md +2 -2
- package/docs/tui.md +1 -1
- package/examples/README.md +3 -0
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/auto-commit-on-exit.ts +1 -1
- package/examples/extensions/bookmark.ts +3 -3
- package/examples/extensions/confirm-destructive.ts +1 -1
- package/examples/extensions/custom-compaction.ts +1 -1
- package/examples/extensions/custom-footer.ts +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/git-checkpoint.ts +1 -1
- package/examples/extensions/handoff.ts +2 -2
- package/examples/extensions/plan-mode/index.ts +1 -1
- package/examples/extensions/preset.ts +1 -1
- package/examples/extensions/qna.ts +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/snake.ts +1 -1
- package/examples/extensions/space-invaders.ts +1 -1
- package/examples/extensions/summarize.ts +1 -1
- package/examples/extensions/tic-tac-toe.ts +1 -1
- package/examples/extensions/todo.ts +1 -1
- package/examples/extensions/tools.ts +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/remote-session-server/README.md +66 -0
- package/examples/remote-session-server/server.ts +359 -0
- package/examples/sdk/11-sessions.ts +3 -3
- package/examples/sdk/13-session-runtime.ts +6 -6
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
package/docs/extensions.md
CHANGED
|
@@ -306,13 +306,13 @@ user sends another prompt ◄─────────────────
|
|
|
306
306
|
/new (new session) or /resume (switch session)
|
|
307
307
|
├─► session_before_switch (can cancel)
|
|
308
308
|
├─► session_shutdown
|
|
309
|
-
├─► session_start { reason: "new" | "resume",
|
|
309
|
+
├─► session_start { reason: "new" | "resume", previousSessionReference? }
|
|
310
310
|
└─► resources_discover { reason: "startup" }
|
|
311
311
|
|
|
312
312
|
/fork or /clone
|
|
313
313
|
├─► session_before_fork (can cancel)
|
|
314
314
|
├─► session_shutdown
|
|
315
|
-
├─► session_start { reason: "fork",
|
|
315
|
+
├─► session_start { reason: "fork", previousSessionReference }
|
|
316
316
|
└─► resources_discover { reason: "startup" }
|
|
317
317
|
|
|
318
318
|
/compact or auto-compaction
|
|
@@ -355,7 +355,7 @@ pi.on("resources_discover", async (event, _ctx) => {
|
|
|
355
355
|
|
|
356
356
|
### Session Events
|
|
357
357
|
|
|
358
|
-
See [Session Format](session-format.md) for session storage internals and
|
|
358
|
+
See [Session Format](session-format.md) for session storage internals, active `Session` APIs, and `SessionManager` lifecycle APIs.
|
|
359
359
|
|
|
360
360
|
#### session_start
|
|
361
361
|
|
|
@@ -364,8 +364,9 @@ Fired when a session is started, loaded, or reloaded.
|
|
|
364
364
|
```typescript
|
|
365
365
|
pi.on("session_start", async (event, ctx) => {
|
|
366
366
|
// event.reason - "startup" | "reload" | "new" | "resume" | "fork"
|
|
367
|
-
// event.
|
|
368
|
-
|
|
367
|
+
// event.previousSessionReference - present for "new", "resume", and "fork"
|
|
368
|
+
// event.previousSessionFile - deprecated alias for local-file-oriented compatibility
|
|
369
|
+
ctx.ui.notify(`Session: ${ctx.session.getSessionReference() ?? "ephemeral"}`, "info");
|
|
369
370
|
});
|
|
370
371
|
```
|
|
371
372
|
|
|
@@ -376,7 +377,8 @@ Fired before starting a new session (`/new`) or switching sessions (`/resume`).
|
|
|
376
377
|
```typescript
|
|
377
378
|
pi.on("session_before_switch", async (event, ctx) => {
|
|
378
379
|
// event.reason - "new" or "resume"
|
|
379
|
-
// event.
|
|
380
|
+
// event.targetSessionReference - session we're switching to (only for "resume")
|
|
381
|
+
// event.targetSessionFile - deprecated alias for local-file-oriented compatibility
|
|
380
382
|
|
|
381
383
|
if (event.reason === "new") {
|
|
382
384
|
const ok = await ctx.ui.confirm("Clear?", "Delete all messages?");
|
|
@@ -385,7 +387,7 @@ pi.on("session_before_switch", async (event, ctx) => {
|
|
|
385
387
|
});
|
|
386
388
|
```
|
|
387
389
|
|
|
388
|
-
After a successful switch or new-session action, pi emits `session_shutdown` for the old extension instance, reloads and rebinds extensions for the new session, then emits `session_start` with `reason: "new" | "resume"` and `
|
|
390
|
+
After a successful switch or new-session action, pi emits `session_shutdown` for the old extension instance, reloads and rebinds extensions for the new session, then emits `session_start` with `reason: "new" | "resume"` and `previousSessionReference`.
|
|
389
391
|
Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `session_start`.
|
|
390
392
|
|
|
391
393
|
#### session_before_fork
|
|
@@ -402,7 +404,7 @@ pi.on("session_before_fork", async (event, ctx) => {
|
|
|
402
404
|
});
|
|
403
405
|
```
|
|
404
406
|
|
|
405
|
-
After a successful fork or clone, pi emits `session_shutdown` for the old extension instance, reloads and rebinds extensions for the new session, then emits `session_start` with `reason: "fork"` and `
|
|
407
|
+
After a successful fork or clone, pi emits `session_shutdown` for the old extension instance, reloads and rebinds extensions for the new session, then emits `session_start` with `reason: "fork"` and `previousSessionReference`.
|
|
406
408
|
Do cleanup work in `session_shutdown`, then reestablish any in-memory state in `session_start`.
|
|
407
409
|
|
|
408
410
|
#### session_before_compact / session_compact
|
|
@@ -456,7 +458,8 @@ Fired before an extension runtime is torn down.
|
|
|
456
458
|
```typescript
|
|
457
459
|
pi.on("session_shutdown", async (event, ctx) => {
|
|
458
460
|
// event.reason - "quit" | "reload" | "new" | "resume" | "fork"
|
|
459
|
-
// event.
|
|
461
|
+
// event.targetSessionReference - destination session for session replacement flows
|
|
462
|
+
// event.targetSessionFile - deprecated alias for local-file-oriented compatibility
|
|
460
463
|
// Cleanup, save state, etc.
|
|
461
464
|
});
|
|
462
465
|
```
|
|
@@ -675,9 +678,9 @@ Use this to update extension UI when `pi.setThinkingLevel()`, model changes, or
|
|
|
675
678
|
|
|
676
679
|
Fired after `tool_execution_start`, before the tool executes. **Can block.** Use `isToolCallEventType` to narrow and get typed inputs.
|
|
677
680
|
|
|
678
|
-
Before `tool_call` runs, pi waits for previously emitted Agent events to finish draining through `AgentSession`. This means `ctx.
|
|
681
|
+
Before `tool_call` runs, pi waits for previously emitted Agent events to finish draining through `AgentSession`. This means `ctx.session` is up to date through the current assistant tool-calling message.
|
|
679
682
|
|
|
680
|
-
In the default parallel tool execution mode, sibling tool calls from the same assistant message are preflighted sequentially, then executed concurrently. `tool_call` is not guaranteed to see sibling tool results from that same assistant message in `ctx.
|
|
683
|
+
In the default parallel tool execution mode, sibling tool calls from the same assistant message are preflighted sequentially, then executed concurrently. `tool_call` is not guaranteed to see sibling tool results from that same assistant message in `ctx.session`.
|
|
681
684
|
|
|
682
685
|
`event.input` is mutable. Mutate it in place to patch tool arguments before execution.
|
|
683
686
|
|
|
@@ -865,16 +868,16 @@ UI methods for user interaction. See [Custom UI](#custom-ui) for full details.
|
|
|
865
868
|
|
|
866
869
|
Current working directory.
|
|
867
870
|
|
|
868
|
-
### ctx.
|
|
871
|
+
### ctx.session
|
|
869
872
|
|
|
870
|
-
Read-only access to session state. See [Session Format](session-format.md) for the full
|
|
873
|
+
Read-only access to active session state. See [Session Format](session-format.md) for the full `Session` API and entry types.
|
|
871
874
|
|
|
872
875
|
For `tool_call`, this state is synchronized through the current assistant message before handlers run. In parallel tool execution mode it is still not guaranteed to include sibling tool results from the same assistant message.
|
|
873
876
|
|
|
874
877
|
```typescript
|
|
875
|
-
ctx.
|
|
876
|
-
ctx.
|
|
877
|
-
ctx.
|
|
878
|
+
ctx.session.getEntries() // All entries
|
|
879
|
+
ctx.session.getBranch() // Current branch
|
|
880
|
+
ctx.session.getLeafId() // Current leaf entry ID
|
|
878
881
|
```
|
|
879
882
|
|
|
880
883
|
### ctx.modelRegistry / ctx.model
|
|
@@ -993,13 +996,13 @@ pi.registerCommand("my-cmd", {
|
|
|
993
996
|
Create a new session:
|
|
994
997
|
|
|
995
998
|
```typescript
|
|
996
|
-
const parentSession = ctx.
|
|
999
|
+
const parentSession = ctx.session.getSessionReference();
|
|
997
1000
|
const kickoff = "Continue in the replacement session";
|
|
998
1001
|
|
|
999
1002
|
const result = await ctx.newSession({
|
|
1000
1003
|
parentSession,
|
|
1001
|
-
setup: async (
|
|
1002
|
-
|
|
1004
|
+
setup: async (session) => {
|
|
1005
|
+
session.appendMessage({
|
|
1003
1006
|
role: "user",
|
|
1004
1007
|
content: [{ type: "text", text: "Context from previous session..." }],
|
|
1005
1008
|
timestamp: Date.now(),
|
|
@@ -1018,7 +1021,7 @@ if (result.cancelled) {
|
|
|
1018
1021
|
|
|
1019
1022
|
Options:
|
|
1020
1023
|
- `parentSession`: parent session file to record in the new session header
|
|
1021
|
-
- `setup`: mutate the new
|
|
1024
|
+
- `setup`: mutate the new active `Session` before `withSession` runs
|
|
1022
1025
|
- `withSession`: run post-switch work against a fresh replacement-session context. Do not use captured old `pi` / command `ctx`; see [Session replacement lifecycle and footguns](#session-replacement-lifecycle-and-footguns).
|
|
1023
1026
|
|
|
1024
1027
|
### ctx.fork(entryId, options?)
|
|
@@ -1084,19 +1087,19 @@ if (result.cancelled) {
|
|
|
1084
1087
|
Options:
|
|
1085
1088
|
- `withSession`: run post-switch work against a fresh replacement-session context. Do not use captured old `pi` / command `ctx`; see [Session replacement lifecycle and footguns](#session-replacement-lifecycle-and-footguns).
|
|
1086
1089
|
|
|
1087
|
-
To discover available sessions,
|
|
1090
|
+
To discover available sessions, create a `LocalSessionManager` and call `list()` or `listAll()`:
|
|
1088
1091
|
|
|
1089
1092
|
```typescript
|
|
1090
|
-
import {
|
|
1093
|
+
import { LocalSessionManager } from "@fleetagent/pi-coding-agent";
|
|
1091
1094
|
|
|
1092
1095
|
pi.registerCommand("switch", {
|
|
1093
1096
|
description: "Switch to another session",
|
|
1094
1097
|
handler: async (args, ctx) => {
|
|
1095
|
-
const sessions = await LocalSessionManager
|
|
1098
|
+
const sessions = await new LocalSessionManager({ cwd: ctx.cwd }).list();
|
|
1096
1099
|
if (sessions.length === 0) return;
|
|
1097
1100
|
const choice = await ctx.ui.select(
|
|
1098
1101
|
"Pick session:",
|
|
1099
|
-
sessions.map(s => s.
|
|
1102
|
+
sessions.map(s => s.reference),
|
|
1100
1103
|
);
|
|
1101
1104
|
if (choice) {
|
|
1102
1105
|
await ctx.switchSession(choice, {
|
|
@@ -1117,7 +1120,7 @@ Lifecycle and footguns:
|
|
|
1117
1120
|
- `withSession` runs only after the old session has emitted `session_shutdown`, the old runtime has been torn down, the replacement session has been rebound, and the new extension instance has already received `session_start`.
|
|
1118
1121
|
- The callback still executes in the original closure, not inside the new extension instance. That means your old extension instance may already have run its shutdown cleanup before `withSession` starts.
|
|
1119
1122
|
- Captured old `pi` / old command `ctx` session-bound objects are stale after replacement and will throw if used. Use only the `ctx` passed to `withSession` for session-bound work.
|
|
1120
|
-
- Previously extracted raw objects are still your responsibility. For example, if you capture `const
|
|
1123
|
+
- Previously extracted raw objects are still your responsibility. For example, if you capture `const oldSession = ctx.session` before replacement, `oldSession` is still the old `Session` object. Do not reuse it after replacement.
|
|
1121
1124
|
- Code in `withSession` should assume any state invalidated by your `session_shutdown` handler is already gone. Only capture plain data that survives shutdown cleanly, such as strings, ids, and serialized config.
|
|
1122
1125
|
|
|
1123
1126
|
Safe pattern:
|
|
@@ -1140,11 +1143,11 @@ Unsafe pattern:
|
|
|
1140
1143
|
```typescript
|
|
1141
1144
|
pi.registerCommand("handoff", {
|
|
1142
1145
|
handler: async (_args, ctx) => {
|
|
1143
|
-
const
|
|
1146
|
+
const oldSession = ctx.session;
|
|
1144
1147
|
await ctx.newSession({
|
|
1145
1148
|
withSession: async (_ctx) => {
|
|
1146
1149
|
// stale old objects: do not do this
|
|
1147
|
-
|
|
1150
|
+
oldSession.getSessionReference();
|
|
1148
1151
|
pi.sendUserMessage("wrong");
|
|
1149
1152
|
},
|
|
1150
1153
|
});
|
|
@@ -1325,7 +1328,7 @@ pi.appendEntry("my-state", { count: 42 });
|
|
|
1325
1328
|
|
|
1326
1329
|
// Restore on reload
|
|
1327
1330
|
pi.on("session_start", async (_event, ctx) => {
|
|
1328
|
-
for (const entry of ctx.
|
|
1331
|
+
for (const entry of ctx.session.getEntries()) {
|
|
1329
1332
|
if (entry.type === "custom" && entry.customType === "my-state") {
|
|
1330
1333
|
// Reconstruct from entry.data
|
|
1331
1334
|
}
|
|
@@ -1363,8 +1366,8 @@ pi.setLabel(entryId, "checkpoint-before-refactor");
|
|
|
1363
1366
|
// Clear a label
|
|
1364
1367
|
pi.setLabel(entryId, undefined);
|
|
1365
1368
|
|
|
1366
|
-
// Read labels via
|
|
1367
|
-
const label = ctx.
|
|
1369
|
+
// Read labels via the active session
|
|
1370
|
+
const label = ctx.session.getLabel(entryId);
|
|
1368
1371
|
```
|
|
1369
1372
|
|
|
1370
1373
|
Labels persist in the session and survive restarts. Use them to mark important points (turns, checkpoints) in the conversation tree.
|
|
@@ -1379,7 +1382,7 @@ If multiple extensions register the same command name, pi keeps them all and ass
|
|
|
1379
1382
|
pi.registerCommand("stats", {
|
|
1380
1383
|
description: "Show session statistics",
|
|
1381
1384
|
handler: async (args, ctx) => {
|
|
1382
|
-
const count = ctx.
|
|
1385
|
+
const count = ctx.session.getEntries().length;
|
|
1383
1386
|
ctx.ui.notify(`${count} entries`, "info");
|
|
1384
1387
|
}
|
|
1385
1388
|
});
|
|
@@ -1634,7 +1637,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1634
1637
|
// Reconstruct state from session
|
|
1635
1638
|
pi.on("session_start", async (_event, ctx) => {
|
|
1636
1639
|
items = [];
|
|
1637
|
-
for (const entry of ctx.
|
|
1640
|
+
for (const entry of ctx.session.getBranch()) {
|
|
1638
1641
|
if (entry.type === "message" && entry.message.role === "toolResult") {
|
|
1639
1642
|
if (entry.message.toolName === "my_tool") {
|
|
1640
1643
|
items = entry.message.details?.items ?? [];
|
package/docs/index.md
CHANGED
|
@@ -65,7 +65,7 @@ For the full first-run flow, see [Quickstart](quickstart.md).
|
|
|
65
65
|
|
|
66
66
|
## Reference
|
|
67
67
|
|
|
68
|
-
- [Session format](session-format.md) - JSONL session file format, entry types, and
|
|
68
|
+
- [Session format](session-format.md) - JSONL session file format, entry types, and session APIs.
|
|
69
69
|
|
|
70
70
|
## Platform setup
|
|
71
71
|
|
package/docs/sdk.md
CHANGED
|
@@ -69,6 +69,8 @@ Use `pi.createAgentSession({ session })` when you already have an opened or crea
|
|
|
69
69
|
|
|
70
70
|
`AgentSession` owns active conversation behavior: prompts, message history, model state, compaction, event streaming, and in-place tree navigation.
|
|
71
71
|
|
|
72
|
+
Use `session.sessionReference` when you need the backend-neutral active session reference. For local JSONL sessions this is the session file path; for in-memory sessions it is `undefined`. `session.sessionFile` remains available as a local-file-oriented alias.
|
|
73
|
+
|
|
72
74
|
Session replacement APIs such as new-session, resume, fork, clone, and import live on `PiAgent`, not on `AgentSession`.
|
|
73
75
|
|
|
74
76
|
### SessionManager and Session
|
package/docs/session-format.md
CHANGED
|
@@ -29,7 +29,7 @@ Existing sessions are automatically migrated to the current version (v3) when lo
|
|
|
29
29
|
## Source Files
|
|
30
30
|
|
|
31
31
|
Source on GitHub ([pi-mono](https://github.com/earendil-works/pi-mono)):
|
|
32
|
-
- [`packages/coding-agent/src/core/session-manager.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts) - Session entry types and SessionManager
|
|
32
|
+
- [`packages/coding-agent/src/core/session-manager.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/session-manager.ts) - Session entry types, `Session`, and `SessionManager`
|
|
33
33
|
- [`packages/coding-agent/src/core/messages.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/src/core/messages.ts) - Extended message types (BashExecutionMessage, CustomMessage, etc.)
|
|
34
34
|
- [`packages/ai/src/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/ai/src/types.ts) - Base message types (UserMessage, AssistantMessage, ToolResultMessage)
|
|
35
35
|
- [`packages/agent/src/types.ts`](https://github.com/earendil-works/pi-mono/blob/main/packages/agent/src/types.ts) - AgentMessage union type
|
|
@@ -358,27 +358,27 @@ for (const line of lines) {
|
|
|
358
358
|
}
|
|
359
359
|
```
|
|
360
360
|
|
|
361
|
-
##
|
|
361
|
+
## Programmatic APIs
|
|
362
362
|
|
|
363
|
-
|
|
363
|
+
`SessionManager` handles lifecycle and discovery. It creates, opens, forks, imports, and lists `Session` objects. `Session` represents one active conversation tree and owns entry append/read/navigation operations.
|
|
364
364
|
|
|
365
|
-
###
|
|
366
|
-
- `
|
|
367
|
-
- `
|
|
368
|
-
- `
|
|
369
|
-
- `
|
|
370
|
-
- `
|
|
365
|
+
### SessionManager Methods
|
|
366
|
+
- `create(options?)` - Create a new session
|
|
367
|
+
- `openReference(reference, options?)` - Open an existing session reference
|
|
368
|
+
- `continueRecent()` - Continue the most recent session or create a new one
|
|
369
|
+
- `forkFrom(reference)` - Fork from an existing session reference
|
|
370
|
+
- `forkSession(source, targetLeafId)` - Fork from an active `Session`
|
|
371
|
+
- `importJsonl(inputPath, options?)` - Import a JSONL session file
|
|
372
|
+
- `list(onProgress?)` - List sessions for the manager's scope
|
|
373
|
+
- `listAll(onProgress?)` - List all sessions across scopes supported by the backend
|
|
371
374
|
|
|
372
|
-
###
|
|
373
|
-
- `
|
|
374
|
-
- `
|
|
375
|
+
### Session Methods - Lifecycle Helpers
|
|
376
|
+
- `createSubSession(options?)` - Create a child session via the session's private manager
|
|
377
|
+
- `forkSubSession(targetLeafId)` - Fork the active session via the session's private manager
|
|
378
|
+
- `copyBranchFrom(source, leafId, parentSession?)` - Copy one branch into this session
|
|
379
|
+
- `createBranchedSession(leafId)` - Extract branch to a new session reference; this mutates the current `Session` to point at the new reference
|
|
375
380
|
|
|
376
|
-
###
|
|
377
|
-
- `newSession(options?)` - Start a new session (options: `{ parentSession?: string }`)
|
|
378
|
-
- `setSessionFile(path)` - Switch to a different session file
|
|
379
|
-
- `createBranchedSession(leafId)` - Extract branch to new session file
|
|
380
|
-
|
|
381
|
-
### Instance Methods - Appending (all return entry ID)
|
|
381
|
+
### Session Methods - Appending (all return entry ID)
|
|
382
382
|
- `appendMessage(message)` - Add message
|
|
383
383
|
- `appendThinkingLevelChange(level)` - Record thinking change
|
|
384
384
|
- `appendModelChange(provider, modelId)` - Record model change
|
|
@@ -388,7 +388,7 @@ Key methods for working with sessions programmatically.
|
|
|
388
388
|
- `appendCustomMessageEntry(customType, content, display, details?)` - Extension message (in context)
|
|
389
389
|
- `appendLabelChange(targetId, label)` - Set/clear label
|
|
390
390
|
|
|
391
|
-
###
|
|
391
|
+
### Session Methods - Tree Navigation
|
|
392
392
|
- `getLeafId()` - Current position
|
|
393
393
|
- `getLeafEntry()` - Get current leaf entry
|
|
394
394
|
- `getEntry(id)` - Get entry by ID
|
|
@@ -400,7 +400,7 @@ Key methods for working with sessions programmatically.
|
|
|
400
400
|
- `resetLeaf()` - Reset leaf to null (before any entries)
|
|
401
401
|
- `branchWithSummary(entryId, summary, details?, fromHook?)` - Branch with context summary
|
|
402
402
|
|
|
403
|
-
###
|
|
403
|
+
### Session Methods - Context & Info
|
|
404
404
|
- `buildSessionContext()` - Get messages, thinkingLevel, and model for LLM
|
|
405
405
|
- `getEntries()` - All entries (excluding header)
|
|
406
406
|
- `getHeader()` - Session header metadata
|
|
@@ -408,5 +408,5 @@ Key methods for working with sessions programmatically.
|
|
|
408
408
|
- `getCwd()` - Working directory
|
|
409
409
|
- `getSessionDir()` - Session storage directory
|
|
410
410
|
- `getSessionId()` - Session UUID
|
|
411
|
-
- `
|
|
411
|
+
- `getSessionReference()` - Backend session reference (local file path for JSONL, remote reference for remote sessions, undefined for in-memory)
|
|
412
412
|
- `isPersisted()` - Whether session is saved to disk
|
package/docs/sessions.md
CHANGED
|
@@ -16,7 +16,7 @@ pi --fork <path|id> # Fork a session file or partial session ID into a new se
|
|
|
16
16
|
|
|
17
17
|
Use `/session` in interactive mode to see the current session file, session ID, message count, tokens, and cost.
|
|
18
18
|
|
|
19
|
-
For the JSONL file format and SessionManager API, see [Session Format](session-format.md).
|
|
19
|
+
For the JSONL file format, active `Session` API, and `SessionManager` lifecycle API, see [Session Format](session-format.md).
|
|
20
20
|
|
|
21
21
|
## Session Commands
|
|
22
22
|
|
|
@@ -134,4 +134,4 @@ See [Compaction](compaction.md) for branch summarization internals and extension
|
|
|
134
134
|
|
|
135
135
|
Session files are JSONL and contain message entries, model changes, thinking-level changes, labels, compactions, branch summaries, and extension entries.
|
|
136
136
|
|
|
137
|
-
For parsers, extensions, SDK usage, and the full
|
|
137
|
+
For parsers, extensions, SDK usage, and the full session APIs, see [Session Format](session-format.md).
|
package/docs/tui.md
CHANGED
|
@@ -813,7 +813,7 @@ ctx.ui.setFooter((tui, theme, footerData) => ({
|
|
|
813
813
|
ctx.ui.setFooter(undefined); // restore default
|
|
814
814
|
```
|
|
815
815
|
|
|
816
|
-
Token stats available via `ctx.
|
|
816
|
+
Token stats available via `ctx.session.getBranch()` and `ctx.model`.
|
|
817
817
|
|
|
818
818
|
**Examples:** [custom-footer.ts](../examples/extensions/custom-footer.ts)
|
|
819
819
|
|
package/examples/README.md
CHANGED
|
@@ -18,6 +18,9 @@ Example extensions demonstrating:
|
|
|
18
18
|
- External integrations (SSH, file watchers, system theme sync)
|
|
19
19
|
- Custom providers (Anthropic with custom streaming, GitLab Duo)
|
|
20
20
|
|
|
21
|
+
### [remote-session-server/](remote-session-server/)
|
|
22
|
+
Hono-based remote session storage server. It uses static bearer auth and stores sessions as JSONL files for debugging the remote session protocol end-to-end.
|
|
23
|
+
|
|
21
24
|
## Documentation
|
|
22
25
|
|
|
23
26
|
- [SDK Reference](sdk/README.md)
|
|
@@ -198,7 +198,7 @@ return {
|
|
|
198
198
|
|
|
199
199
|
// Reconstruct on session events
|
|
200
200
|
pi.on("session_start", async (_event, ctx) => {
|
|
201
|
-
for (const entry of ctx.
|
|
201
|
+
for (const entry of ctx.session.getBranch()) {
|
|
202
202
|
if (entry.type === "message" && entry.message.toolName === "my_tool") {
|
|
203
203
|
const details = entry.message.details;
|
|
204
204
|
// Reconstruct state from details
|
|
@@ -18,7 +18,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
// Find the last assistant message for commit context
|
|
21
|
-
const entries = ctx.
|
|
21
|
+
const entries = ctx.session.getEntries();
|
|
22
22
|
let lastAssistantText = "";
|
|
23
23
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
24
24
|
const entry = entries[i];
|
|
@@ -16,7 +16,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
16
16
|
const label = args.trim() || `bookmark-${Date.now()}`;
|
|
17
17
|
|
|
18
18
|
// Find the last assistant message entry
|
|
19
|
-
const entries = ctx.
|
|
19
|
+
const entries = ctx.session.getEntries();
|
|
20
20
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
21
21
|
const entry = entries[i];
|
|
22
22
|
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
@@ -34,10 +34,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
34
34
|
pi.registerCommand("unbookmark", {
|
|
35
35
|
description: "Remove bookmark from last labeled entry",
|
|
36
36
|
handler: async (_args, ctx) => {
|
|
37
|
-
const entries = ctx.
|
|
37
|
+
const entries = ctx.session.getEntries();
|
|
38
38
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
39
39
|
const entry = entries[i];
|
|
40
|
-
const label = ctx.
|
|
40
|
+
const label = ctx.session.getLabel(entry.id);
|
|
41
41
|
if (label) {
|
|
42
42
|
pi.setLabel(entry.id, undefined);
|
|
43
43
|
ctx.ui.notify(`Removed bookmark: ${label}`, "info");
|
|
@@ -25,7 +25,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// reason === "resume" - check if there are unsaved changes (messages since last assistant response)
|
|
28
|
-
const entries = ctx.
|
|
28
|
+
const entries = ctx.session.getEntries();
|
|
29
29
|
const hasUnsavedWork = entries.some(
|
|
30
30
|
(e): e is SessionMessageEntry => e.type === "message" && e.message.role === "user",
|
|
31
31
|
);
|
|
@@ -108,7 +108,7 @@ ${conversationText}
|
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
// Return compaction content -
|
|
111
|
+
// Return compaction content - Session adds id/parentId
|
|
112
112
|
// Use firstKeptEntryId from preparation to keep recent messages
|
|
113
113
|
return {
|
|
114
114
|
compaction: {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - getGitBranch(): current git branch
|
|
6
6
|
* - getExtensionStatuses(): texts from ctx.ui.setStatus()
|
|
7
7
|
*
|
|
8
|
-
* Token stats come from ctx.
|
|
8
|
+
* Token stats come from ctx.session/ctx.model (already accessible).
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import type { AssistantMessage } from "@fleetagent/pi-ai";
|
|
@@ -32,7 +32,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
32
32
|
let input = 0,
|
|
33
33
|
output = 0,
|
|
34
34
|
cost = 0;
|
|
35
|
-
for (const e of ctx.
|
|
35
|
+
for (const e of ctx.session.getBranch()) {
|
|
36
36
|
if (e.type === "message" && e.message.role === "assistant") {
|
|
37
37
|
const m = e.message as AssistantMessage;
|
|
38
38
|
input += m.usage.input;
|
|
@@ -13,7 +13,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
13
13
|
|
|
14
14
|
// Track the current entry ID when user messages are saved
|
|
15
15
|
pi.on("tool_result", async (_event, ctx) => {
|
|
16
|
-
const leaf = ctx.
|
|
16
|
+
const leaf = ctx.session.getLeafEntry();
|
|
17
17
|
if (leaf) currentEntryId = leaf.id;
|
|
18
18
|
});
|
|
19
19
|
|
|
@@ -99,7 +99,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
99
99
|
|
|
100
100
|
// Gather conversation context from current branch. If the branch was compacted,
|
|
101
101
|
// include the compaction summary plus entries from firstKeptEntryId onward.
|
|
102
|
-
const messages = getHandoffMessages(ctx.
|
|
102
|
+
const messages = getHandoffMessages(ctx.session.getBranch());
|
|
103
103
|
|
|
104
104
|
if (messages.length === 0) {
|
|
105
105
|
ctx.ui.notify("No conversation to hand off", "error");
|
|
@@ -109,7 +109,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
109
109
|
// Convert to LLM format and serialize
|
|
110
110
|
const llmMessages = convertToLlm(messages);
|
|
111
111
|
const conversationText = serializeConversation(llmMessages);
|
|
112
|
-
const currentSessionFile = ctx.
|
|
112
|
+
const currentSessionFile = ctx.session.getSessionReference();
|
|
113
113
|
|
|
114
114
|
// Generate the handoff prompt with loader UI
|
|
115
115
|
const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
|
|
@@ -293,7 +293,7 @@ After completing a step, include a [DONE:n] tag in your response.`,
|
|
|
293
293
|
planModeEnabled = true;
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
-
const entries = ctx.
|
|
296
|
+
const entries = ctx.session.getEntries();
|
|
297
297
|
|
|
298
298
|
// Restore persisted state
|
|
299
299
|
const planModeEntry = entries
|
|
@@ -404,7 +404,7 @@ export default function presetExtension(pi: ExtensionAPI) {
|
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
// Restore preset from session state
|
|
407
|
-
const entries = ctx.
|
|
407
|
+
const entries = ctx.session.getEntries();
|
|
408
408
|
const presetEntry = entries
|
|
409
409
|
.filter((e: { type: string; customType?: string }) => e.type === "custom" && e.customType === "preset-state")
|
|
410
410
|
.pop() as { data?: { name: string } } | undefined;
|
|
@@ -42,7 +42,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// Find the last assistant message on the current branch
|
|
45
|
-
const branch = ctx.
|
|
45
|
+
const branch = ctx.session.getBranch();
|
|
46
46
|
let lastAssistantText: string | undefined;
|
|
47
47
|
|
|
48
48
|
for (let i = branch.length - 1; i >= 0; i--) {
|
|
@@ -317,7 +317,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
// Load saved state from session
|
|
320
|
-
const entries = ctx.
|
|
320
|
+
const entries = ctx.session.getEntries();
|
|
321
321
|
let savedState: GameState | undefined;
|
|
322
322
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
323
323
|
const entry = entries[i];
|
|
@@ -535,7 +535,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
535
535
|
}
|
|
536
536
|
|
|
537
537
|
// Load saved state from session
|
|
538
|
-
const entries = ctx.
|
|
538
|
+
const entries = ctx.session.getEntries();
|
|
539
539
|
let savedState: GameState | undefined;
|
|
540
540
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
541
541
|
const entry = entries[i];
|
|
@@ -146,7 +146,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
146
146
|
pi.registerCommand("summarize", {
|
|
147
147
|
description: "Summarize the current conversation in a custom UI",
|
|
148
148
|
handler: async (_args, ctx) => {
|
|
149
|
-
const branch = ctx.
|
|
149
|
+
const branch = ctx.session.getBranch();
|
|
150
150
|
const conversationText = buildConversationText(branch);
|
|
151
151
|
|
|
152
152
|
if (!conversationText.trim()) {
|
|
@@ -628,7 +628,7 @@ function reconstructState(ctx: ExtensionContext): void {
|
|
|
628
628
|
gameState = createInitialState();
|
|
629
629
|
gameActive = false;
|
|
630
630
|
|
|
631
|
-
for (const entry of ctx.
|
|
631
|
+
for (const entry of ctx.session.getBranch()) {
|
|
632
632
|
if (entry.type !== "message") continue;
|
|
633
633
|
const msg = entry.message;
|
|
634
634
|
if (msg.role !== "toolResult") continue;
|
|
@@ -115,7 +115,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
115
115
|
todos = [];
|
|
116
116
|
nextId = 1;
|
|
117
117
|
|
|
118
|
-
for (const entry of ctx.
|
|
118
|
+
for (const entry of ctx.session.getBranch()) {
|
|
119
119
|
if (entry.type !== "message") continue;
|
|
120
120
|
const msg = entry.message;
|
|
121
121
|
if (msg.role !== "toolResult" || msg.toolName !== "todo") continue;
|
|
@@ -40,7 +40,7 @@ export default function toolsExtension(pi: ExtensionAPI) {
|
|
|
40
40
|
allTools = pi.getAllTools();
|
|
41
41
|
|
|
42
42
|
// Get entries in current branch only
|
|
43
|
-
const branchEntries = ctx.
|
|
43
|
+
const branchEntries = ctx.session.getBranch();
|
|
44
44
|
let savedTools: string[] | undefined;
|
|
45
45
|
|
|
46
46
|
for (const entry of branchEntries) {
|