pi-sessions 0.6.0 → 0.7.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/README.md +22 -11
- package/extensions/session-ask/agent.ts +400 -0
- package/extensions/session-ask/navigate.ts +880 -0
- package/extensions/session-ask.ts +79 -131
- package/extensions/session-auto-title/model.ts +3 -11
- package/extensions/session-handoff/picker.ts +50 -4
- package/extensions/session-handoff/query.ts +74 -56
- package/extensions/session-handoff/refs.ts +12 -18
- package/extensions/session-handoff.ts +36 -27
- package/extensions/session-messaging/broker/process.ts +23 -26
- package/extensions/session-messaging/broker/spawn.ts +7 -2
- package/extensions/session-messaging/pi/incoming-runtime.ts +2 -18
- package/extensions/session-messaging/pi/message-contracts.ts +9 -10
- package/extensions/session-messaging/pi/message-view.ts +12 -1
- package/extensions/session-messaging/pi/service.ts +117 -62
- package/extensions/session-messaging/pi/tools.ts +4 -56
- package/extensions/session-search/extract.ts +86 -436
- package/extensions/session-search/hooks.ts +17 -25
- package/extensions/session-search/normalize.ts +1 -1
- package/extensions/session-search/reindex.ts +1 -0
- package/extensions/session-search.ts +157 -128
- package/extensions/shared/model.ts +18 -0
- package/extensions/shared/session-broker/active.ts +26 -0
- package/extensions/{session-messaging/pi → shared/session-broker}/client.ts +16 -19
- package/extensions/{session-messaging/shared → shared/session-broker}/framing.ts +1 -1
- package/extensions/{session-messaging/shared → shared/session-broker}/protocol.ts +5 -12
- package/extensions/shared/session-index/access.ts +128 -0
- package/extensions/shared/session-index/common.ts +43 -48
- package/extensions/shared/session-index/index.ts +1 -0
- package/extensions/shared/session-index/lineage.ts +10 -0
- package/extensions/shared/session-index/query/ast.ts +40 -0
- package/extensions/shared/session-index/query/compiler.ts +146 -0
- package/extensions/shared/session-index/query/lexer.ts +140 -0
- package/extensions/shared/session-index/query/parser.ts +178 -0
- package/extensions/shared/session-index/schema.ts +22 -6
- package/extensions/shared/session-index/scoring.ts +80 -0
- package/extensions/shared/session-index/search.ts +552 -278
- package/extensions/shared/session-index/sqlite.ts +16 -9
- package/extensions/shared/session-index/store.ts +12 -21
- package/extensions/shared/settings.ts +61 -4
- package/extensions/shared/text.ts +50 -0
- package/package.json +1 -1
- /package/extensions/{session-messaging/shared → shared/session-broker}/socket-path.ts +0 -0
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { parseSessionFile } from "../session-search/extract.ts";
|
|
3
3
|
import {
|
|
4
|
-
getIndexStatus,
|
|
5
4
|
getSessionById,
|
|
6
|
-
INDEX_SCHEMA_VERSION,
|
|
7
|
-
openIndexDatabase,
|
|
8
5
|
type SessionLineageRow,
|
|
9
6
|
type SessionOrigin,
|
|
7
|
+
withSessionIndex,
|
|
10
8
|
} from "../shared/session-index/index.ts";
|
|
11
9
|
|
|
12
10
|
const HANDOFF_REF_PREFIX = "@handoff/";
|
|
@@ -103,24 +101,20 @@ function resolveIndexedReference(
|
|
|
103
101
|
return { error: `Invalid session reference: ${input}. ${SESSION_REFERENCE_HELP}` };
|
|
104
102
|
}
|
|
105
103
|
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
try {
|
|
105
|
+
return withSessionIndex(options.indexPath, { mode: "read", required: true }, ({ db }) => {
|
|
106
|
+
const exactMatch = getSessionById(db, value);
|
|
107
|
+
if (exactMatch) {
|
|
108
|
+
return { resolved: buildResolvedReference(input, kind, exactMatch) };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { error: `No session found for reference: ${input}. ${SESSION_REFERENCE_HELP}` };
|
|
112
|
+
});
|
|
113
|
+
} catch {
|
|
108
114
|
return {
|
|
109
|
-
error: `Session reference resolution requires a current index at ${
|
|
115
|
+
error: `Session reference resolution requires a current index at ${options.indexPath}. Run /session-index and press r to rebuild it.`,
|
|
110
116
|
};
|
|
111
117
|
}
|
|
112
|
-
|
|
113
|
-
const db = openIndexDatabase(status.dbPath, { create: false });
|
|
114
|
-
try {
|
|
115
|
-
const exactMatch = getSessionById(db, value);
|
|
116
|
-
if (exactMatch) {
|
|
117
|
-
return { resolved: buildResolvedReference(input, kind, exactMatch) };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return { error: `No session found for reference: ${input}. ${SESSION_REFERENCE_HELP}` };
|
|
121
|
-
} finally {
|
|
122
|
-
db.close();
|
|
123
|
-
}
|
|
124
118
|
}
|
|
125
119
|
|
|
126
120
|
function buildResolvedReference(
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
ExtensionUIContext,
|
|
10
10
|
} from "@earendil-works/pi-coding-agent";
|
|
11
11
|
import { buildSessionContext, SessionManager } from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import { Key, matchesKey } from "@earendil-works/pi-tui";
|
|
12
|
+
import { Key, matchesKey, Text } from "@earendil-works/pi-tui";
|
|
13
13
|
import { Type } from "typebox";
|
|
14
14
|
import {
|
|
15
15
|
generateHandoffDraft,
|
|
@@ -62,7 +62,6 @@ interface HandoffToolParams {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
interface HandoffToolDetails {
|
|
65
|
-
error: boolean;
|
|
66
65
|
sessionId?: string | undefined;
|
|
67
66
|
title?: string | undefined;
|
|
68
67
|
splitDirection?: HandoffSplitDirection | undefined;
|
|
@@ -90,6 +89,7 @@ export default function sessionHandoffExtension(pi: ExtensionAPI): void {
|
|
|
90
89
|
"Use session_handoff only when it is clear the work should be forked to a new context.",
|
|
91
90
|
"Prefer using session_handoff by direction of the user, not as an unsolicited default.",
|
|
92
91
|
"The goal should capture enough detail to encompass the ask and include any directions the next sessions should consider.",
|
|
92
|
+
"Only request a response when there is a specific ask-and-response expectation: the user asked for a report back, or the current session needs the child result before it can continue. Do not set requestResponse for independent or unrelated background work; most split-off tasks should run without reporting back.",
|
|
93
93
|
"Only capable of a background handoff; to replace the current session, tell the user to run /handoff instead.",
|
|
94
94
|
],
|
|
95
95
|
executionMode: "sequential",
|
|
@@ -118,6 +118,23 @@ export default function sessionHandoffExtension(pi: ExtensionAPI): void {
|
|
|
118
118
|
}),
|
|
119
119
|
),
|
|
120
120
|
}),
|
|
121
|
+
renderResult(result, _options, theme, context) {
|
|
122
|
+
const text = getFirstText(result);
|
|
123
|
+
if (context.isError) {
|
|
124
|
+
return new Text(theme.fg("error", text), 0, 0);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const details = result.details as HandoffToolDetails | undefined;
|
|
128
|
+
if (!details?.sessionId || !details.splitDirection || !details.cwd) {
|
|
129
|
+
return new Text(text, 0, 0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return new Text(
|
|
133
|
+
`Started handoff session ${theme.bold(details.sessionId)} (${details.splitDirection}) in ${theme.fg("dim", details.cwd)}.`,
|
|
134
|
+
0,
|
|
135
|
+
0,
|
|
136
|
+
);
|
|
137
|
+
},
|
|
121
138
|
async execute(_toolCallId, params: HandoffToolParams, _signal, _onUpdate, ctx) {
|
|
122
139
|
return executeSessionHandoffTool(pi, params, ctx, identifiedGhosttyTerminalId);
|
|
123
140
|
},
|
|
@@ -361,30 +378,30 @@ async function executeSessionHandoffTool(
|
|
|
361
378
|
) {
|
|
362
379
|
const goal = params.goal.trim();
|
|
363
380
|
if (!goal) {
|
|
364
|
-
|
|
381
|
+
throw new Error("session_handoff requires a goal.");
|
|
365
382
|
}
|
|
366
383
|
|
|
367
384
|
if (!ctx.model) {
|
|
368
|
-
|
|
385
|
+
throw new Error("No model selected.");
|
|
369
386
|
}
|
|
370
387
|
|
|
371
388
|
const preflightError = await validateSplitHandoffPrerequisites(pi, ctx);
|
|
372
389
|
if (preflightError) {
|
|
373
|
-
|
|
390
|
+
throw new Error(preflightError);
|
|
374
391
|
}
|
|
375
392
|
|
|
376
393
|
if (!terminalId) {
|
|
377
|
-
|
|
394
|
+
throw new Error(NO_IDENTIFIED_TERMINAL_MESSAGE);
|
|
378
395
|
}
|
|
379
396
|
|
|
380
397
|
const targetCwd = resolveHandoffCwd(ctx.cwd, params.cwd);
|
|
381
398
|
if (targetCwd.error) {
|
|
382
|
-
|
|
399
|
+
throw new Error(targetCwd.message);
|
|
383
400
|
}
|
|
384
401
|
|
|
385
402
|
const parentSessionFile = ctx.sessionManager.getSessionFile();
|
|
386
403
|
if (!parentSessionFile) {
|
|
387
|
-
|
|
404
|
+
throw new Error("Handoff requires a persisted current session.");
|
|
388
405
|
}
|
|
389
406
|
|
|
390
407
|
const sessionContext = buildSessionContext(
|
|
@@ -392,7 +409,7 @@ async function executeSessionHandoffTool(
|
|
|
392
409
|
ctx.sessionManager.getLeafId(),
|
|
393
410
|
);
|
|
394
411
|
if (sessionContext.messages.length === 0) {
|
|
395
|
-
|
|
412
|
+
throw new Error("No conversation to hand off.");
|
|
396
413
|
}
|
|
397
414
|
|
|
398
415
|
const requestResponse = params.requestResponse ?? false;
|
|
@@ -425,7 +442,7 @@ async function executeSessionHandoffTool(
|
|
|
425
442
|
});
|
|
426
443
|
|
|
427
444
|
if (!launchResult.success) {
|
|
428
|
-
|
|
445
|
+
throw new Error(
|
|
429
446
|
`${launchResult.error} Created handoff session ${createdSession.sessionId}; start it manually with: ${buildPiResumeCommand(
|
|
430
447
|
ctx.sessionManager.getSessionDir(),
|
|
431
448
|
createdSession.sessionId,
|
|
@@ -433,17 +450,10 @@ async function executeSessionHandoffTool(
|
|
|
433
450
|
TOOL_HANDOFF_PROVISIONAL_TITLE,
|
|
434
451
|
model,
|
|
435
452
|
)}`,
|
|
436
|
-
{
|
|
437
|
-
sessionId: createdSession.sessionId,
|
|
438
|
-
title: TOOL_HANDOFF_PROVISIONAL_TITLE,
|
|
439
|
-
splitDirection: params.splitDirection,
|
|
440
|
-
cwd: targetCwd.path,
|
|
441
|
-
},
|
|
442
453
|
);
|
|
443
454
|
}
|
|
444
455
|
|
|
445
456
|
const details: HandoffToolDetails = {
|
|
446
|
-
error: false,
|
|
447
457
|
sessionId: createdSession.sessionId,
|
|
448
458
|
title: TOOL_HANDOFF_PROVISIONAL_TITLE,
|
|
449
459
|
splitDirection: params.splitDirection,
|
|
@@ -454,7 +464,7 @@ async function executeSessionHandoffTool(
|
|
|
454
464
|
content: [
|
|
455
465
|
{
|
|
456
466
|
type: "text" as const,
|
|
457
|
-
text:
|
|
467
|
+
text: formatHandoffToolResultForModel(details, requestResponse),
|
|
458
468
|
},
|
|
459
469
|
],
|
|
460
470
|
details,
|
|
@@ -527,16 +537,15 @@ async function startChildGeneratedHandoff(
|
|
|
527
537
|
}
|
|
528
538
|
}
|
|
529
539
|
|
|
530
|
-
function
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
};
|
|
540
|
+
function formatHandoffToolResultForModel(
|
|
541
|
+
details: HandoffToolDetails,
|
|
542
|
+
requestResponse: boolean,
|
|
543
|
+
): string {
|
|
544
|
+
return JSON.stringify({ ...details, requestResponse }, null, 2);
|
|
545
|
+
}
|
|
535
546
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
details: resultDetails,
|
|
539
|
-
};
|
|
547
|
+
function getFirstText(result: { content: Array<{ type: string; text?: string }> }): string {
|
|
548
|
+
return result.content.find((item) => item.type === "text")?.text ?? "";
|
|
540
549
|
}
|
|
541
550
|
|
|
542
551
|
function resolveHandoffCwd(
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { mkdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { createServer, type Socket } from "node:net";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { readFrames, writeFrame } from "
|
|
4
|
+
import { readFrames, writeFrame } from "../../shared/session-broker/framing.ts";
|
|
5
5
|
import {
|
|
6
6
|
CLIENT_FRAME_SCHEMA,
|
|
7
7
|
type SessionMessagingClientFrame,
|
|
8
8
|
type SessionMessagingIncomingAckClientFrame,
|
|
9
9
|
type SessionMessagingSendClientFrame,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
} from "../../shared/session-broker/protocol.ts";
|
|
11
|
+
import {
|
|
12
|
+
getSessionMessagingDir,
|
|
13
|
+
getSessionMessagingSocketPath,
|
|
14
|
+
} from "../../shared/session-broker/socket-path.ts";
|
|
13
15
|
|
|
14
16
|
const SEND_TIMEOUT_MS = 10_000;
|
|
15
17
|
const EXIT_IDLE_MS = 5_000;
|
|
@@ -19,11 +21,6 @@ const messagingDir = getSessionMessagingDir();
|
|
|
19
21
|
const socketPath = getSessionMessagingSocketPath();
|
|
20
22
|
const pidPath = join(messagingDir, "broker.pid");
|
|
21
23
|
|
|
22
|
-
interface ConnectedSession {
|
|
23
|
-
socket: Socket;
|
|
24
|
-
session: SessionMessagingSessionInfo;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
24
|
interface PendingSend {
|
|
28
25
|
source: Socket;
|
|
29
26
|
timeout: NodeJS.Timeout;
|
|
@@ -31,7 +28,7 @@ interface PendingSend {
|
|
|
31
28
|
targetSessionId: string;
|
|
32
29
|
}
|
|
33
30
|
|
|
34
|
-
const sessions = new Map<string,
|
|
31
|
+
const sessions = new Map<string, Socket>();
|
|
35
32
|
const pendingSends = new Map<string, PendingSend>();
|
|
36
33
|
let idleTimer: NodeJS.Timeout | undefined;
|
|
37
34
|
let server: ReturnType<typeof createServer> | undefined;
|
|
@@ -63,9 +60,9 @@ function unregister(sessionId: string | undefined): void {
|
|
|
63
60
|
if (sessions.size === 0) scheduleIdleExit();
|
|
64
61
|
}
|
|
65
62
|
|
|
66
|
-
function resolveTarget(target: string): {
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
63
|
+
function resolveTarget(target: string): { sessionId: string; socket: Socket } | { error: string } {
|
|
64
|
+
const socket = sessions.get(target);
|
|
65
|
+
if (socket) return { sessionId: target, socket };
|
|
69
66
|
|
|
70
67
|
return { error: `No live session found for id: ${target}` };
|
|
71
68
|
}
|
|
@@ -84,8 +81,8 @@ function handleFrame(
|
|
|
84
81
|
|
|
85
82
|
switch (message.type) {
|
|
86
83
|
case "register": {
|
|
87
|
-
const existing = sessions.get(message.
|
|
88
|
-
if (existing && existing
|
|
84
|
+
const existing = sessions.get(message.sessionId);
|
|
85
|
+
if (existing && existing !== socket && !existing.destroyed) {
|
|
89
86
|
writeFrame(socket, {
|
|
90
87
|
type: "register_failed",
|
|
91
88
|
reason:
|
|
@@ -96,10 +93,10 @@ function handleFrame(
|
|
|
96
93
|
}
|
|
97
94
|
|
|
98
95
|
clearIdleExit();
|
|
99
|
-
setSessionId(message.
|
|
100
|
-
sessions.set(message.
|
|
96
|
+
setSessionId(message.sessionId);
|
|
97
|
+
sessions.set(message.sessionId, socket);
|
|
101
98
|
socket.setKeepAlive(true, KEEPALIVE_DELAY_MS);
|
|
102
|
-
writeFrame(socket, { type: "registered",
|
|
99
|
+
writeFrame(socket, { type: "registered", sessionId: message.sessionId });
|
|
103
100
|
break;
|
|
104
101
|
}
|
|
105
102
|
|
|
@@ -113,7 +110,7 @@ function handleFrame(
|
|
|
113
110
|
writeFrame(socket, {
|
|
114
111
|
type: "sessions",
|
|
115
112
|
requestId: message.requestId,
|
|
116
|
-
|
|
113
|
+
sessionIds: [...sessions.keys()],
|
|
117
114
|
});
|
|
118
115
|
break;
|
|
119
116
|
|
|
@@ -135,7 +132,7 @@ function handleSend(
|
|
|
135
132
|
const source = currentSessionId ? sessions.get(currentSessionId) : undefined;
|
|
136
133
|
const { messageId } = message;
|
|
137
134
|
|
|
138
|
-
if (!source) {
|
|
135
|
+
if (!currentSessionId || !source) {
|
|
139
136
|
writeFrame(socket, {
|
|
140
137
|
type: "send_result",
|
|
141
138
|
requestId: message.requestId,
|
|
@@ -158,7 +155,7 @@ function handleSend(
|
|
|
158
155
|
return;
|
|
159
156
|
}
|
|
160
157
|
|
|
161
|
-
if (resolved.
|
|
158
|
+
if (resolved.sessionId === currentSessionId) {
|
|
162
159
|
writeFrame(socket, {
|
|
163
160
|
type: "send_result",
|
|
164
161
|
requestId: message.requestId,
|
|
@@ -183,16 +180,16 @@ function handleSend(
|
|
|
183
180
|
source: socket,
|
|
184
181
|
timeout,
|
|
185
182
|
messageId,
|
|
186
|
-
targetSessionId: resolved.
|
|
183
|
+
targetSessionId: resolved.sessionId,
|
|
187
184
|
});
|
|
188
185
|
|
|
189
|
-
writeFrame(resolved.
|
|
186
|
+
writeFrame(resolved.socket, {
|
|
190
187
|
type: "incoming",
|
|
191
188
|
requestId: message.requestId,
|
|
192
189
|
message: {
|
|
193
190
|
messageId,
|
|
194
|
-
source:
|
|
195
|
-
target: resolved.
|
|
191
|
+
source: currentSessionId,
|
|
192
|
+
target: resolved.sessionId,
|
|
196
193
|
body: message.body,
|
|
197
194
|
...(message.requestResponse === undefined
|
|
198
195
|
? {}
|
|
@@ -274,7 +271,7 @@ async function readConnection(
|
|
|
274
271
|
}
|
|
275
272
|
|
|
276
273
|
function shutdown(): void {
|
|
277
|
-
for (const
|
|
274
|
+
for (const socket of sessions.values()) socket.end();
|
|
278
275
|
sessions.clear();
|
|
279
276
|
for (const pending of pendingSends.values()) clearTimeout(pending.timeout);
|
|
280
277
|
pendingSends.clear();
|
|
@@ -3,7 +3,10 @@ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "
|
|
|
3
3
|
import net from "node:net";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getSessionMessagingDir,
|
|
8
|
+
getSessionMessagingSocketPath,
|
|
9
|
+
} from "../../shared/session-broker/socket-path.ts";
|
|
7
10
|
|
|
8
11
|
const BROKER_START_TIMEOUT_MS = 5_000;
|
|
9
12
|
const BROKER_CONNECT_TIMEOUT_MS = 1_000;
|
|
@@ -107,7 +110,9 @@ async function waitForBroker(): Promise<void> {
|
|
|
107
110
|
function acquireSpawnLock(): boolean {
|
|
108
111
|
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
109
112
|
try {
|
|
110
|
-
writeFileSync(brokerSpawnLockPath, `${process.pid}\n${Date.now()}\n`, {
|
|
113
|
+
writeFileSync(brokerSpawnLockPath, `${process.pid}\n${Date.now()}\n`, {
|
|
114
|
+
flag: "wx",
|
|
115
|
+
});
|
|
111
116
|
return true;
|
|
112
117
|
} catch (error) {
|
|
113
118
|
if (!(error instanceof Error) || (error as NodeJS.ErrnoException).code !== "EEXIST") {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { SessionLineageRelation } from "../../shared/session-index/index.ts";
|
|
3
2
|
import { safeParseTypeBoxValue } from "../../shared/typebox.ts";
|
|
4
|
-
import type { SessionMessagePayload } from "../shared/protocol.ts";
|
|
5
3
|
import { RECEIVED_MESSAGE_ENTRY_SCHEMA, type ReceivedMessageEntry } from "./message-contracts.ts";
|
|
4
|
+
|
|
6
5
|
export const MESSAGE_RECEIVED_CUSTOM_TYPE = "pi-sessions.message_received";
|
|
7
6
|
export const SESSION_MESSAGE_CUSTOM_TYPE = "pi-sessions.session_message";
|
|
8
7
|
|
|
@@ -39,17 +38,13 @@ export class IncomingSessionMessageRuntime {
|
|
|
39
38
|
});
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
deliver(
|
|
43
|
-
message: SessionMessagePayload,
|
|
44
|
-
relation: SessionLineageRelation | undefined,
|
|
45
|
-
): DeliveryResult {
|
|
41
|
+
deliver(received: ReceivedMessageEntry): DeliveryResult {
|
|
46
42
|
const ctx = this.context;
|
|
47
43
|
if (!ctx) {
|
|
48
44
|
return { delivered: false, error: "Target session is not ready." };
|
|
49
45
|
}
|
|
50
46
|
|
|
51
47
|
try {
|
|
52
|
-
const received = buildReceivedMessageEntry(message, relation);
|
|
53
48
|
this.actions.appendEntry(MESSAGE_RECEIVED_CUSTOM_TYPE, received);
|
|
54
49
|
this.inject(ctx, received);
|
|
55
50
|
return { delivered: true };
|
|
@@ -106,17 +101,6 @@ export class IncomingSessionMessageRuntime {
|
|
|
106
101
|
}
|
|
107
102
|
}
|
|
108
103
|
|
|
109
|
-
function buildReceivedMessageEntry(
|
|
110
|
-
message: SessionMessagePayload,
|
|
111
|
-
relation: SessionLineageRelation | undefined,
|
|
112
|
-
): ReceivedMessageEntry {
|
|
113
|
-
return {
|
|
114
|
-
...message,
|
|
115
|
-
receivedAt: new Date().toISOString(),
|
|
116
|
-
...(relation === undefined ? {} : { relation }),
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
104
|
function formatIncomingMessageForModel(received: ReceivedMessageEntry): string {
|
|
121
105
|
const title = received.source.sessionName?.trim();
|
|
122
106
|
const titlePart = title ? ` "${title}"` : "";
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { type Static, Type } from "typebox";
|
|
2
2
|
|
|
3
|
+
const RECEIVED_MESSAGE_ENDPOINT_SCHEMA = Type.Object({
|
|
4
|
+
sessionId: Type.String(),
|
|
5
|
+
sessionName: Type.Optional(Type.String()),
|
|
6
|
+
cwd: Type.Optional(Type.String()),
|
|
7
|
+
});
|
|
8
|
+
|
|
3
9
|
export const SEND_MESSAGE_PARAMS = Type.Object({
|
|
4
10
|
session: Type.String({ description: "Pi session UUID." }),
|
|
5
11
|
message: Type.String({
|
|
@@ -15,16 +21,8 @@ export const SEND_MESSAGE_PARAMS = Type.Object({
|
|
|
15
21
|
|
|
16
22
|
export const RECEIVED_MESSAGE_ENTRY_SCHEMA = Type.Object({
|
|
17
23
|
messageId: Type.String(),
|
|
18
|
-
source:
|
|
19
|
-
|
|
20
|
-
sessionName: Type.Optional(Type.String()),
|
|
21
|
-
cwd: Type.String(),
|
|
22
|
-
}),
|
|
23
|
-
target: Type.Object({
|
|
24
|
-
sessionId: Type.String(),
|
|
25
|
-
sessionName: Type.Optional(Type.String()),
|
|
26
|
-
cwd: Type.String(),
|
|
27
|
-
}),
|
|
24
|
+
source: RECEIVED_MESSAGE_ENDPOINT_SCHEMA,
|
|
25
|
+
target: RECEIVED_MESSAGE_ENDPOINT_SCHEMA,
|
|
28
26
|
body: Type.String(),
|
|
29
27
|
sentAt: Type.String(),
|
|
30
28
|
receivedAt: Type.String(),
|
|
@@ -34,4 +32,5 @@ export const RECEIVED_MESSAGE_ENTRY_SCHEMA = Type.Object({
|
|
|
34
32
|
});
|
|
35
33
|
|
|
36
34
|
export type SendMessageParams = Static<typeof SEND_MESSAGE_PARAMS>;
|
|
35
|
+
export type ReceivedMessageEndpoint = Static<typeof RECEIVED_MESSAGE_ENDPOINT_SCHEMA>;
|
|
37
36
|
export type ReceivedMessageEntry = Static<typeof RECEIVED_MESSAGE_ENTRY_SCHEMA>;
|
|
@@ -14,6 +14,7 @@ interface MessageViewOptions {
|
|
|
14
14
|
status: "sending" | "received";
|
|
15
15
|
targetSessionId?: string | undefined;
|
|
16
16
|
sourceSessionId?: string | undefined;
|
|
17
|
+
sourceSessionName?: string | undefined;
|
|
17
18
|
message: string;
|
|
18
19
|
requestResponse?: boolean | undefined;
|
|
19
20
|
relation?: string | undefined;
|
|
@@ -41,6 +42,7 @@ export function createReceivedSessionMessageComponent(
|
|
|
41
42
|
expanded,
|
|
42
43
|
status: "received",
|
|
43
44
|
sourceSessionId: received.source.sessionId,
|
|
45
|
+
sourceSessionName: received.source.sessionName,
|
|
44
46
|
message: received.body,
|
|
45
47
|
requestResponse: received.requestResponse,
|
|
46
48
|
relation: received.relation,
|
|
@@ -84,12 +86,21 @@ function formatHeader(options: MessageViewOptions, theme: MessageViewTheme): str
|
|
|
84
86
|
const metadataHint = metadata.length > 0 ? theme.fg("muted", ` (${metadata.join(", ")})`) : "";
|
|
85
87
|
switch (options.status) {
|
|
86
88
|
case "received":
|
|
87
|
-
return `${name} ${theme.fg("muted", `from ${options
|
|
89
|
+
return `${name} ${theme.fg("muted", `from ${formatSourceLabel(options)}`)}${metadataHint}`;
|
|
88
90
|
case "sending":
|
|
89
91
|
return `${name} ${theme.fg("muted", `to ${options.targetSessionId ?? "[target pending]"}`)}${metadataHint}`;
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
function formatSourceLabel(options: MessageViewOptions): string {
|
|
96
|
+
if (!options.sourceSessionId) {
|
|
97
|
+
return "[source unknown]";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const title = options.sourceSessionName?.trim();
|
|
101
|
+
return title ? `${title} (${options.sourceSessionId})` : options.sourceSessionId;
|
|
102
|
+
}
|
|
103
|
+
|
|
93
104
|
function formatKeyHint(
|
|
94
105
|
keybinding: Keybinding,
|
|
95
106
|
description: string,
|