pi-sessions 0.18.0 → 0.19.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.
Files changed (34) hide show
  1. package/README.md +44 -40
  2. package/extensions/hosts/contract.ts +10 -46
  3. package/extensions/hosts/registry.ts +4 -78
  4. package/extensions/pi-sessions.ts +27 -53
  5. package/extensions/session-handoff/board-loading.ts +4 -1
  6. package/extensions/session-handoff/board-view-model.ts +1 -0
  7. package/extensions/session-handoff/board.ts +4 -1
  8. package/extensions/session-handoff/install.ts +47 -33
  9. package/extensions/session-handoff/launch/backend.ts +16 -4
  10. package/extensions/session-handoff/launch-options.ts +2 -17
  11. package/extensions/session-handoff/launch-target.ts +4 -9
  12. package/extensions/session-handoff/receipt.ts +1 -1
  13. package/extensions/session-handoff/tool-presenter.ts +1 -1
  14. package/extensions/session-handoff/tool-schema.ts +1 -9
  15. package/extensions/session-handoff/tool-view-model.ts +6 -2
  16. package/extensions/session-handoff/tool.ts +2 -2
  17. package/extensions/session-messaging/directory-list.ts +115 -0
  18. package/extensions/session-messaging/directory.ts +114 -0
  19. package/extensions/session-messaging/message-router.ts +3 -8
  20. package/extensions/session-messaging/pi/directory-tools.ts +41 -0
  21. package/extensions/session-messaging/pi/install.ts +102 -0
  22. package/extensions/session-messaging/pi/message-contracts.ts +4 -6
  23. package/extensions/session-messaging/pi/reachable-contract.ts +3 -4
  24. package/extensions/session-messaging/pi/reachable-tool.ts +25 -175
  25. package/extensions/session-messaging/pi/send-message-presenter.ts +2 -1
  26. package/extensions/session-messaging/pi/tools.ts +49 -91
  27. package/extensions/session-search/extract.ts +12 -2
  28. package/extensions/shared/lineage.ts +8 -0
  29. package/extensions/shared/session-index/sync.ts +3 -0
  30. package/extensions/shared/settings.ts +1 -1
  31. package/extensions/subagents/install.ts +0 -27
  32. package/extensions/subagents/launch-target.ts +0 -1
  33. package/extensions/subagents/reconcile.ts +10 -2
  34. package/package.json +5 -5
@@ -23,6 +23,7 @@ import {
23
23
  type HandoffSessionMetadata,
24
24
  parseHandoffSessionMetadata,
25
25
  } from "../session-handoff/metadata.ts";
26
+ import { LINEAGE_CUSTOM_TYPE, LINEAGE_SCHEMA, type SessionLineage } from "../shared/lineage.ts";
26
27
  import type { SessionOrigin } from "../shared/session-index/index.ts";
27
28
  import { contentToText, isRecord } from "../shared/text.ts";
28
29
  import { safeParseTypeBoxValue } from "../shared/typebox.ts";
@@ -97,6 +98,7 @@ export interface SessionEntryScan {
97
98
  sessionNameEntryId?: string | undefined;
98
99
  sessionNameTs?: string | undefined;
99
100
  handoffMetadata?: DurableHandoffMetadataRecord | undefined;
101
+ lineage?: SessionLineage | undefined;
100
102
  }
101
103
 
102
104
  export interface SessionTailBaseline {
@@ -194,7 +196,10 @@ export function extractSessionRecord(sessionPath: string): ExtractedSessionRecor
194
196
  chunks.push(...scan.chunks);
195
197
  appendDurableHandoffMetadataChunks(chunks, scan.handoffMetadata);
196
198
 
197
- const parentSessionPath = normalizeParentSessionPath(header.parentSession);
199
+ const lineage = header.parentSession ? undefined : scan.lineage;
200
+ const parentSessionPath = normalizeParentSessionPath(
201
+ header.parentSession ?? lineage?.parentSessionFile,
202
+ );
198
203
  const parentSession = parentSessionPath
199
204
  ? inspectParentSession(parentSessionPath, header.id, sessionPath)
200
205
  : undefined;
@@ -211,7 +216,7 @@ export function extractSessionRecord(sessionPath: string): ExtractedSessionRecor
211
216
  messageCount: scan.messageCount,
212
217
  entryCount: scan.entryCount,
213
218
  parentSessionPath,
214
- parentSessionId: parentSession?.sessionId,
219
+ parentSessionId: lineage?.parentSessionId ?? parentSession?.sessionId,
215
220
  sessionOrigin: deriveSessionOrigin(
216
221
  parentSessionPath,
217
222
  parentSession?.launchedSubagent ?? false,
@@ -306,6 +311,7 @@ export function scanSessionEntries(
306
311
  let sessionNameEntryId: string | undefined;
307
312
  let sessionNameTs: string | undefined;
308
313
  let handoffMetadata: DurableHandoffMetadataRecord | undefined;
314
+ let lineage: SessionLineage | undefined;
309
315
 
310
316
  for (const entry of entries) {
311
317
  if (!isSessionEntry(entry)) continue;
@@ -336,6 +342,9 @@ export function scanSessionEntries(
336
342
  continue;
337
343
  }
338
344
  case "custom": {
345
+ if (entry.customType === LINEAGE_CUSTOM_TYPE && !lineage) {
346
+ lineage = safeParseTypeBoxValue(LINEAGE_SCHEMA, entry.data);
347
+ }
339
348
  const nextHandoffMetadata = extractHandoffMetadata(entry, entryTs);
340
349
  if (nextHandoffMetadata && !handoffMetadata) {
341
350
  handoffMetadata = nextHandoffMetadata;
@@ -393,6 +402,7 @@ export function scanSessionEntries(
393
402
  sessionNameEntryId,
394
403
  sessionNameTs,
395
404
  handoffMetadata,
405
+ lineage,
396
406
  };
397
407
  }
398
408
 
@@ -0,0 +1,8 @@
1
+ import { type Static, Type } from "typebox";
2
+
3
+ export const LINEAGE_CUSTOM_TYPE = "pi-sessions.lineage";
4
+ export const LINEAGE_SCHEMA = Type.Object({
5
+ parentSessionId: Type.String({ minLength: 1 }),
6
+ parentSessionFile: Type.Optional(Type.String({ minLength: 1 })),
7
+ });
8
+ export type SessionLineage = Static<typeof LINEAGE_SCHEMA>;
@@ -231,6 +231,9 @@ function applyTailSync(
231
231
  sessionOrigin?: SessionOrigin,
232
232
  ): boolean {
233
233
  return db.transaction((): boolean => {
234
+ // A lineage entry can arrive after the initial header sync. Re-read the header to
235
+ // preserve its precedence and rebuild lineage through the normal full-sync path.
236
+ if (tail.scan.lineage) return false;
234
237
  // Another process may have advanced the index between our baseline read
235
238
  // and this transaction; the tail deltas would then double-count.
236
239
  const current = getSessionRowByPath(db, baseline.sessionPath);
@@ -10,7 +10,7 @@ import { parseTypeBoxValue } from "./typebox.ts";
10
10
 
11
11
  export const DEFAULT_AUTO_TITLE_REFRESH_TURNS = 4;
12
12
  export const DEFAULT_AUTO_TITLE_TIMEOUT_SECONDS = 15;
13
- export const DEFAULT_AUTO_TITLE_TOKEN_BUDGET = 64;
13
+ export const DEFAULT_AUTO_TITLE_TOKEN_BUDGET = 512;
14
14
  export const DEFAULT_AUTO_TITLE_PROMPT = `Name this coding session (under 80 chars). Be specific to what is being discussed. Your exact output will be displayed to the user, so make sure that it contains ONLY the title itself and nothing else.`;
15
15
  const SESSION_FILE_SETTINGS_SCHEMA = Type.Object({
16
16
  messaging: Type.Optional(
@@ -10,10 +10,6 @@ import type {
10
10
  SendMessageResult,
11
11
  } from "../session-messaging/install.ts";
12
12
  import type { SessionWaker } from "../session-messaging/message-router.ts";
13
- import {
14
- createSessionCancelTool,
15
- createSessionSendMessageTool,
16
- } from "../session-messaging/pi/tools.ts";
17
13
  import type { SessionLifecycle } from "../shared/composition.ts";
18
14
  import type { CompactionThresholdSettings, SessionSettings } from "../shared/settings.ts";
19
15
  import { isTmuxInstalled } from "../shared/tmux.ts";
@@ -82,7 +78,6 @@ export function installSubagents(
82
78
  messaging: MessagingHandle;
83
79
  readCompactionSettings: (cwd: string) => CompactionThresholdSettings;
84
80
  sendMessage(request: SendMessageRequest): Promise<SendMessageResult>;
85
- hasHostedSessions?: () => boolean;
86
81
  },
87
82
  ): SubagentsHandle {
88
83
  pi.registerMessageRenderer(SUBAGENT_REPORT_MESSAGE_CUSTOM_TYPE, renderSubagentReportMessage);
@@ -225,28 +220,9 @@ export function installSubagents(
225
220
  }
226
221
  };
227
222
 
228
- const registerMessagingTools = (): void => {
229
- const isSubagent = handle.getParentSessionId() !== undefined;
230
- pi.registerTool(
231
- createSessionSendMessageTool(handle, {
232
- role: isSubagent ? { kind: "subagent" } : { kind: "wakeCapable" },
233
- hostedSessions: deps.hasHostedSessions?.() ?? false,
234
- getCachedRelationTo: deps.messaging.getCachedRelationTo,
235
- getParentSessionId: () => handle.getParentSessionId(),
236
- }),
237
- );
238
- pi.registerTool(
239
- createSessionCancelTool(handle, {
240
- role: isSubagent ? { kind: "subagent" } : { kind: "plain" },
241
- getParentSessionId: () => handle.getParentSessionId(),
242
- }),
243
- );
244
- };
245
-
246
223
  pi.on("session_tree", async (_event, ctx) => {
247
224
  settledChildLifecycle.cancel();
248
225
  refreshChildSession();
249
- registerMessagingTools();
250
226
  try {
251
227
  await reconciler.reconcileAndRestoreSuspended();
252
228
  } catch (error) {
@@ -303,9 +279,6 @@ export function installSubagents(
303
279
  reconciler.beginSession();
304
280
  current = { parent };
305
281
  refreshChildSession();
306
- if (identity) {
307
- registerMessagingTools();
308
- }
309
282
  try {
310
283
  if (_event.reason === "reload") {
311
284
  await reconciler.reconcile();
@@ -19,7 +19,6 @@ export function createSubagentLaunchTarget(
19
19
  value: SUBAGENT_LAUNCH,
20
20
  description:
21
21
  "'subagent' delegates one task to a detached tmux worker and requests a report by default.",
22
- requestResponseDefault: true,
23
22
  bootstrapMode: "automatic",
24
23
  approveProjectTrust: true,
25
24
  describeSubagentChild(input) {
@@ -8,7 +8,12 @@ import {
8
8
  type TmuxExecutor,
9
9
  tmuxSessionName,
10
10
  } from "../shared/tmux.ts";
11
- import { classifySubagent, type SubagentEvidence, type SubagentState } from "./classify.ts";
11
+ import {
12
+ classifySubagent,
13
+ isRunningSubagentState,
14
+ type SubagentEvidence,
15
+ type SubagentState,
16
+ } from "./classify.ts";
12
17
  import {
13
18
  type ChildSubagentLifecycle,
14
19
  collectParentLedger,
@@ -220,8 +225,11 @@ export class SubagentReconciler {
220
225
  continue;
221
226
  }
222
227
 
228
+ // A running child writes its report to its own transcript before sending it live; recovering it in
229
+ // between would deliver it as recovered. Once the child stops, recovery is the report's only path.
230
+ const reports = isRunningSubagentState(child.state) ? [] : child.reports;
223
231
  let reportLedger: ParentSubagentLedger | undefined;
224
- for (const report of child.reports) {
232
+ for (const report of reports) {
225
233
  if (this.hasSentReport(report.reportId)) {
226
234
  continue;
227
235
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-sessions",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "type": "module",
5
5
  "description": "Pi session search, ask, handoff, messaging, auto-titling, and indexing tools",
6
6
  "license": "MIT",
@@ -38,10 +38,10 @@
38
38
  },
39
39
  "devDependencies": {
40
40
  "@biomejs/biome": "^2.5.10",
41
- "@earendil-works/pi-agent-core": "^1.1.0",
42
- "@earendil-works/pi-ai": "^1.1.0",
43
- "@earendil-works/pi-coding-agent": "^1.1.0",
44
- "@earendil-works/pi-tui": "^1.1.0",
41
+ "@earendil-works/pi-agent-core": "^1.2.0",
42
+ "@earendil-works/pi-ai": "^1.2.0",
43
+ "@earendil-works/pi-coding-agent": "^1.2.0",
44
+ "@earendil-works/pi-tui": "^1.2.0",
45
45
  "@types/node": "^24.13.3",
46
46
  "strip-ansi": "^7.2.0",
47
47
  "typebox": "1.3.27",