pi-sessions 0.5.1 → 0.6.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 +19 -9
- package/extensions/session-ask.ts +4 -4
- package/extensions/session-auto-title/command.ts +1 -1
- package/extensions/session-auto-title/controller.ts +3 -3
- package/extensions/session-auto-title/generate.ts +2 -2
- package/extensions/session-auto-title/model.ts +1 -1
- package/extensions/session-auto-title/retitle.ts +5 -5
- package/extensions/session-auto-title/state.ts +1 -1
- package/extensions/session-auto-title/wizard.ts +4 -4
- package/extensions/session-auto-title.ts +8 -8
- package/extensions/session-handoff/extract.ts +18 -5
- package/extensions/session-handoff/metadata.ts +4 -1
- package/extensions/session-handoff/picker.ts +2 -2
- package/extensions/session-handoff/query.ts +6 -8
- package/extensions/session-handoff/refs.ts +2 -2
- package/extensions/session-handoff/spawn.ts +1 -1
- package/extensions/session-handoff.ts +18 -8
- package/extensions/session-hooks.ts +3 -3
- package/extensions/session-index.ts +4 -4
- package/extensions/session-messaging/broker/process.ts +305 -0
- package/extensions/session-messaging/broker/spawn.ts +159 -0
- package/extensions/session-messaging/pi/client.ts +256 -0
- package/extensions/session-messaging/pi/incoming-runtime.ts +130 -0
- package/extensions/session-messaging/pi/message-contracts.ts +37 -0
- package/extensions/session-messaging/pi/message-view.ts +120 -0
- package/extensions/session-messaging/pi/renderer.ts +16 -0
- package/extensions/session-messaging/pi/service.ts +315 -0
- package/extensions/session-messaging/pi/tools.ts +144 -0
- package/extensions/session-messaging/shared/framing.ts +72 -0
- package/extensions/session-messaging/shared/protocol.ts +102 -0
- package/extensions/session-messaging/shared/socket-path.ts +30 -0
- package/extensions/session-messaging.ts +30 -0
- package/extensions/session-search/extract.ts +4 -4
- package/extensions/session-search/hooks.ts +3 -3
- package/extensions/session-search/reindex.ts +2 -2
- package/extensions/session-search.ts +4 -4
- package/extensions/shared/session-index/common.ts +3 -3
- package/extensions/shared/session-index/index.ts +5 -5
- package/extensions/shared/session-index/lineage.ts +9 -2
- package/extensions/shared/session-index/schema.ts +3 -3
- package/extensions/shared/session-index/search.ts +4 -4
- package/extensions/shared/session-index/store.ts +2 -2
- package/extensions/shared/settings.ts +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
3
|
+
import { Type } from "typebox";
|
|
4
|
+
import { SEND_MESSAGE_PARAMS, type SendMessageParams } from "./message-contracts.ts";
|
|
5
|
+
import { formatSendMessageCall } from "./message-view.ts";
|
|
6
|
+
import type { LiveSessionRow, SessionMessagingService } from "./service.ts";
|
|
7
|
+
|
|
8
|
+
interface ListLiveDetails {
|
|
9
|
+
error?: boolean | undefined;
|
|
10
|
+
sessions: LiveSessionRow[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function registerSessionMessagingTools(
|
|
14
|
+
pi: ExtensionAPI,
|
|
15
|
+
service: SessionMessagingService,
|
|
16
|
+
): void {
|
|
17
|
+
pi.registerTool({
|
|
18
|
+
name: "session_list_live",
|
|
19
|
+
label: "List Live Pi Sessions",
|
|
20
|
+
description: "List other Pi sessions that are currently active.",
|
|
21
|
+
promptSnippet: "List other Pi sessions that are currently active.",
|
|
22
|
+
parameters: Type.Object({}),
|
|
23
|
+
renderResult(result, _options, theme, context) {
|
|
24
|
+
if (context.isError) {
|
|
25
|
+
return new Text(`\n${theme.fg("error", getFirstText(result))}`, 0, 0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const details = result.details as ListLiveDetails | undefined;
|
|
29
|
+
return new Text(`\n${formatLiveSessionsForDisplay(details?.sessions ?? [], theme)}`, 0, 0);
|
|
30
|
+
},
|
|
31
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
32
|
+
try {
|
|
33
|
+
const sessions = await service.listLiveSessions(ctx);
|
|
34
|
+
return {
|
|
35
|
+
content: [{ type: "text" as const, text: formatLiveSessions(sessions) }],
|
|
36
|
+
details: { sessions } satisfies ListLiveDetails,
|
|
37
|
+
};
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new Error(formatError(error));
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
pi.registerTool({
|
|
45
|
+
name: "session_send_message",
|
|
46
|
+
label: "Send Message to Session",
|
|
47
|
+
description: "Send a message to another live pi session",
|
|
48
|
+
promptSnippet: "Send a message to another live pi session",
|
|
49
|
+
promptGuidelines: ["Use session_list_live to discover targetable sessions."],
|
|
50
|
+
parameters: SEND_MESSAGE_PARAMS,
|
|
51
|
+
renderCall(args, theme, context) {
|
|
52
|
+
const component = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
|
|
53
|
+
component.setText(
|
|
54
|
+
formatSendMessageCall(
|
|
55
|
+
args,
|
|
56
|
+
context.expanded,
|
|
57
|
+
"sending",
|
|
58
|
+
theme,
|
|
59
|
+
service.getCachedRelationTo(args?.session),
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
return component;
|
|
63
|
+
},
|
|
64
|
+
renderResult(result, _options, theme, context) {
|
|
65
|
+
if (!context.isError) {
|
|
66
|
+
return new Text("", 0, 0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const output = getFirstText(result);
|
|
70
|
+
return new Text(output ? `\n${theme.fg("error", output)}` : "", 0, 0);
|
|
71
|
+
},
|
|
72
|
+
async execute(toolCallId, params: SendMessageParams, _signal, _onUpdate, _ctx) {
|
|
73
|
+
const target = params.session.trim();
|
|
74
|
+
if (!target) {
|
|
75
|
+
throw new Error("session_send_message requires a target session id.");
|
|
76
|
+
}
|
|
77
|
+
if (target.startsWith("@session:")) {
|
|
78
|
+
throw new Error("Use the bare session UUID, not an @session token.");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const body = params.message.trim();
|
|
82
|
+
if (!body) {
|
|
83
|
+
throw new Error("session_send_message requires a non-empty message.");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const result = await service.sendMessage({
|
|
88
|
+
target,
|
|
89
|
+
body,
|
|
90
|
+
requestResponse: params.requestResponse,
|
|
91
|
+
sourceToolCallId: toolCallId,
|
|
92
|
+
});
|
|
93
|
+
if (!result.delivered) {
|
|
94
|
+
throw new Error(result.error ?? "Message was not delivered.");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
content: [
|
|
99
|
+
{
|
|
100
|
+
type: "text" as const,
|
|
101
|
+
text: `Message delivered to ${target}.`,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
details: {
|
|
105
|
+
delivered: true,
|
|
106
|
+
messageId: result.messageId,
|
|
107
|
+
targetSessionId: target,
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new Error(formatError(error));
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function formatLiveSessions(sessions: LiveSessionRow[]): string {
|
|
118
|
+
return JSON.stringify({ sessions }, null, 2);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function formatLiveSessionsForDisplay(
|
|
122
|
+
sessions: LiveSessionRow[],
|
|
123
|
+
theme: { fg(token: string, text: string): string },
|
|
124
|
+
): string {
|
|
125
|
+
if (sessions.length === 0) {
|
|
126
|
+
return "No other live sessions found.";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return sessions
|
|
130
|
+
.map((session) => {
|
|
131
|
+
const title = session.sessionName?.trim() || "[untitled]";
|
|
132
|
+
const relation = session.relation ? ` (${session.relation})` : "";
|
|
133
|
+
return `${session.sessionId}${relation} - ${title}\n${theme.fg("dim", session.cwd)}`;
|
|
134
|
+
})
|
|
135
|
+
.join("\n\n");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getFirstText(result: { content: Array<{ type: string; text?: string }> }): string {
|
|
139
|
+
return result.content.find((item) => item.type === "text")?.text ?? "";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function formatError(error: unknown): string {
|
|
143
|
+
return error instanceof Error && error.message.trim() ? error.message : String(error);
|
|
144
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Socket } from "node:net";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import type { Static, TSchema } from "typebox";
|
|
4
|
+
import { parseTypeBoxValue } from "../../shared/typebox.ts";
|
|
5
|
+
|
|
6
|
+
const MAX_FRAME_BYTES = 256 * 1024;
|
|
7
|
+
|
|
8
|
+
export function writeFrame<T>(socket: Socket, frame: T): void {
|
|
9
|
+
socket.write(`${JSON.stringify(frame)}\n`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function* readFrames<T extends TSchema>(
|
|
13
|
+
socket: Socket,
|
|
14
|
+
schema: T,
|
|
15
|
+
context: string,
|
|
16
|
+
): AsyncGenerator<Static<T>> {
|
|
17
|
+
const reader = createInterface({ input: socket, crlfDelay: Number.POSITIVE_INFINITY });
|
|
18
|
+
const lines: string[] = [];
|
|
19
|
+
const waiters: Array<() => void> = [];
|
|
20
|
+
let error: Error | undefined;
|
|
21
|
+
let closed = false;
|
|
22
|
+
|
|
23
|
+
const wake = (): void => {
|
|
24
|
+
const waiter = waiters.shift();
|
|
25
|
+
waiter?.();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
reader.on("line", (line) => {
|
|
29
|
+
lines.push(line);
|
|
30
|
+
wake();
|
|
31
|
+
});
|
|
32
|
+
reader.on("error", (nextError) => {
|
|
33
|
+
error = nextError instanceof Error ? nextError : new Error(String(nextError));
|
|
34
|
+
closed = true;
|
|
35
|
+
wake();
|
|
36
|
+
});
|
|
37
|
+
reader.on("close", () => {
|
|
38
|
+
closed = true;
|
|
39
|
+
wake();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
while (true) {
|
|
44
|
+
while (lines.length === 0 && !closed) {
|
|
45
|
+
await new Promise<void>((resolve) => waiters.push(resolve));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const line = lines.shift();
|
|
49
|
+
if (line === undefined) {
|
|
50
|
+
if (error) {
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (Buffer.byteLength(line, "utf8") > MAX_FRAME_BYTES) {
|
|
57
|
+
throw new Error("Session messaging frame exceeds maximum size.");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let parsed: unknown;
|
|
61
|
+
try {
|
|
62
|
+
parsed = JSON.parse(line) as unknown;
|
|
63
|
+
} catch (parseError) {
|
|
64
|
+
throw parseError instanceof Error ? parseError : new Error(String(parseError));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
yield parseTypeBoxValue(schema, parsed, context);
|
|
68
|
+
}
|
|
69
|
+
} finally {
|
|
70
|
+
reader.close();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type Static, Type } from "typebox";
|
|
2
|
+
|
|
3
|
+
const SESSION_INFO_SCHEMA = Type.Object({
|
|
4
|
+
sessionId: Type.String(),
|
|
5
|
+
sessionName: Type.Optional(Type.String()),
|
|
6
|
+
cwd: Type.String(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const SESSION_MESSAGE_PAYLOAD_SCHEMA = Type.Object({
|
|
10
|
+
messageId: Type.String(),
|
|
11
|
+
source: SESSION_INFO_SCHEMA,
|
|
12
|
+
target: SESSION_INFO_SCHEMA,
|
|
13
|
+
body: Type.String(),
|
|
14
|
+
requestResponse: Type.Optional(Type.Boolean()),
|
|
15
|
+
sentAt: Type.String(),
|
|
16
|
+
sourceToolCallId: Type.Optional(Type.String()),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const REGISTER_CLIENT_FRAME_SCHEMA = Type.Object({
|
|
20
|
+
type: Type.Literal("register"),
|
|
21
|
+
session: SESSION_INFO_SCHEMA,
|
|
22
|
+
});
|
|
23
|
+
const UNREGISTER_CLIENT_FRAME_SCHEMA = Type.Object({
|
|
24
|
+
type: Type.Literal("unregister"),
|
|
25
|
+
});
|
|
26
|
+
const LIST_CLIENT_FRAME_SCHEMA = Type.Object({
|
|
27
|
+
type: Type.Literal("list"),
|
|
28
|
+
requestId: Type.String(),
|
|
29
|
+
});
|
|
30
|
+
const SEND_CLIENT_FRAME_SCHEMA = Type.Object({
|
|
31
|
+
type: Type.Literal("send"),
|
|
32
|
+
requestId: Type.String(),
|
|
33
|
+
messageId: Type.String(),
|
|
34
|
+
target: Type.String(),
|
|
35
|
+
body: Type.String(),
|
|
36
|
+
requestResponse: Type.Optional(Type.Boolean()),
|
|
37
|
+
sentAt: Type.String(),
|
|
38
|
+
sourceToolCallId: Type.Optional(Type.String()),
|
|
39
|
+
});
|
|
40
|
+
const INCOMING_ACK_CLIENT_FRAME_SCHEMA = Type.Object({
|
|
41
|
+
type: Type.Literal("incoming_ack"),
|
|
42
|
+
requestId: Type.String(),
|
|
43
|
+
messageId: Type.String(),
|
|
44
|
+
delivered: Type.Boolean(),
|
|
45
|
+
error: Type.Optional(Type.String()),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const REGISTERED_BROKER_FRAME_SCHEMA = Type.Object({
|
|
49
|
+
type: Type.Literal("registered"),
|
|
50
|
+
session: SESSION_INFO_SCHEMA,
|
|
51
|
+
});
|
|
52
|
+
const REGISTER_FAILED_BROKER_FRAME_SCHEMA = Type.Object({
|
|
53
|
+
type: Type.Literal("register_failed"),
|
|
54
|
+
reason: Type.String(),
|
|
55
|
+
});
|
|
56
|
+
const SESSIONS_BROKER_FRAME_SCHEMA = Type.Object({
|
|
57
|
+
type: Type.Literal("sessions"),
|
|
58
|
+
requestId: Type.String(),
|
|
59
|
+
sessions: Type.Array(SESSION_INFO_SCHEMA),
|
|
60
|
+
});
|
|
61
|
+
const INCOMING_BROKER_FRAME_SCHEMA = Type.Object({
|
|
62
|
+
type: Type.Literal("incoming"),
|
|
63
|
+
requestId: Type.String(),
|
|
64
|
+
message: SESSION_MESSAGE_PAYLOAD_SCHEMA,
|
|
65
|
+
});
|
|
66
|
+
const SEND_RESULT_BROKER_FRAME_SCHEMA = Type.Object({
|
|
67
|
+
type: Type.Literal("send_result"),
|
|
68
|
+
requestId: Type.String(),
|
|
69
|
+
messageId: Type.String(),
|
|
70
|
+
delivered: Type.Boolean(),
|
|
71
|
+
error: Type.Optional(Type.String()),
|
|
72
|
+
});
|
|
73
|
+
const ERROR_BROKER_FRAME_SCHEMA = Type.Object({
|
|
74
|
+
type: Type.Literal("error"),
|
|
75
|
+
message: Type.String(),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export const CLIENT_FRAME_SCHEMA = Type.Union([
|
|
79
|
+
REGISTER_CLIENT_FRAME_SCHEMA,
|
|
80
|
+
UNREGISTER_CLIENT_FRAME_SCHEMA,
|
|
81
|
+
LIST_CLIENT_FRAME_SCHEMA,
|
|
82
|
+
SEND_CLIENT_FRAME_SCHEMA,
|
|
83
|
+
INCOMING_ACK_CLIENT_FRAME_SCHEMA,
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
export const BROKER_FRAME_SCHEMA = Type.Union([
|
|
87
|
+
REGISTERED_BROKER_FRAME_SCHEMA,
|
|
88
|
+
REGISTER_FAILED_BROKER_FRAME_SCHEMA,
|
|
89
|
+
SESSIONS_BROKER_FRAME_SCHEMA,
|
|
90
|
+
INCOMING_BROKER_FRAME_SCHEMA,
|
|
91
|
+
SEND_RESULT_BROKER_FRAME_SCHEMA,
|
|
92
|
+
ERROR_BROKER_FRAME_SCHEMA,
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
export type SessionMessagingSessionInfo = Static<typeof SESSION_INFO_SCHEMA>;
|
|
96
|
+
export type SessionMessagePayload = Static<typeof SESSION_MESSAGE_PAYLOAD_SCHEMA>;
|
|
97
|
+
export type SessionMessagingSendClientFrame = Static<typeof SEND_CLIENT_FRAME_SCHEMA>;
|
|
98
|
+
export type SessionMessagingIncomingAckClientFrame = Static<
|
|
99
|
+
typeof INCOMING_ACK_CLIENT_FRAME_SCHEMA
|
|
100
|
+
>;
|
|
101
|
+
export type SessionMessagingClientFrame = Static<typeof CLIENT_FRAME_SCHEMA>;
|
|
102
|
+
export type SessionMessagingBrokerFrame = Static<typeof BROKER_FRAME_SCHEMA>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
function sanitizePipeSegment(value: string): string {
|
|
5
|
+
return (
|
|
6
|
+
value
|
|
7
|
+
.replace(/[^a-zA-Z0-9]+/g, "-")
|
|
8
|
+
.replace(/^-+|-+$/g, "")
|
|
9
|
+
.toLowerCase() || "default"
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getSessionMessagingDir(homeDir: string = homedir()): string {
|
|
14
|
+
return (
|
|
15
|
+
process.env.PI_SESSIONS_MESSAGING_DIR ??
|
|
16
|
+
join(homeDir, ".pi", "agent", "pi-sessions", "messaging")
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getSessionMessagingSocketPath(
|
|
21
|
+
platform: NodeJS.Platform = process.platform,
|
|
22
|
+
homeDir: string = homedir(),
|
|
23
|
+
): string {
|
|
24
|
+
const messagingDir = getSessionMessagingDir(homeDir);
|
|
25
|
+
if (platform === "win32") {
|
|
26
|
+
return `\\\\.\\pipe\\pi-sessions-messaging-${sanitizePipeSegment(messagingDir)}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return join(messagingDir, "broker.sock");
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { IncomingSessionMessageRuntime } from "./session-messaging/pi/incoming-runtime.ts";
|
|
3
|
+
import { registerSessionMessagingRenderer } from "./session-messaging/pi/renderer.ts";
|
|
4
|
+
import { SessionMessagingService } from "./session-messaging/pi/service.ts";
|
|
5
|
+
import { registerSessionMessagingTools } from "./session-messaging/pi/tools.ts";
|
|
6
|
+
import { loadSettings } from "./shared/settings.ts";
|
|
7
|
+
|
|
8
|
+
export default function sessionMessagingExtension(pi: ExtensionAPI): void {
|
|
9
|
+
const settings = loadSettings();
|
|
10
|
+
const incomingRuntime = new IncomingSessionMessageRuntime(pi);
|
|
11
|
+
const service = new SessionMessagingService(
|
|
12
|
+
settings.index.path,
|
|
13
|
+
incomingRuntime,
|
|
14
|
+
pi.appendEntry.bind(pi),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
18
|
+
incomingRuntime.replayPending(ctx);
|
|
19
|
+
try {
|
|
20
|
+
await service.start(ctx);
|
|
21
|
+
} catch {}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
pi.on("session_shutdown", () => {
|
|
25
|
+
service.stop();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
registerSessionMessagingTools(pi, service);
|
|
29
|
+
registerSessionMessagingRenderer(pi);
|
|
30
|
+
}
|
|
@@ -23,16 +23,16 @@ import {
|
|
|
23
23
|
HANDOFF_METADATA_CUSTOM_TYPE,
|
|
24
24
|
type HandoffSessionMetadata,
|
|
25
25
|
parseHandoffSessionMetadata,
|
|
26
|
-
} from "../session-handoff/metadata.
|
|
27
|
-
import type { SessionOrigin } from "../shared/session-index/index.
|
|
28
|
-
import { safeParseTypeBoxValue } from "../shared/typebox.
|
|
26
|
+
} from "../session-handoff/metadata.ts";
|
|
27
|
+
import type { SessionOrigin } from "../shared/session-index/index.ts";
|
|
28
|
+
import { safeParseTypeBoxValue } from "../shared/typebox.ts";
|
|
29
29
|
import {
|
|
30
30
|
deriveSessionRepoRoots,
|
|
31
31
|
type FileTouchOp,
|
|
32
32
|
type FileTouchSource,
|
|
33
33
|
normalizePathRecord,
|
|
34
34
|
type PathScope,
|
|
35
|
-
} from "./normalize.
|
|
35
|
+
} from "./normalize.ts";
|
|
36
36
|
|
|
37
37
|
export interface SearchTextChunk {
|
|
38
38
|
entryId?: string | undefined;
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
type SessionRow,
|
|
23
23
|
setMetadata,
|
|
24
24
|
upsertSession,
|
|
25
|
-
} from "../shared/session-index/index.
|
|
25
|
+
} from "../shared/session-index/index.ts";
|
|
26
26
|
import {
|
|
27
27
|
createSessionNameChunk,
|
|
28
28
|
type ExtractedSessionRecord,
|
|
@@ -30,8 +30,8 @@ import {
|
|
|
30
30
|
extractSessionRecord,
|
|
31
31
|
extractSessionTail,
|
|
32
32
|
type SessionFileTouch,
|
|
33
|
-
} from "./extract.
|
|
34
|
-
import { deriveSessionRepoRoots } from "./normalize.
|
|
33
|
+
} from "./extract.ts";
|
|
34
|
+
import { deriveSessionRepoRoots } from "./normalize.ts";
|
|
35
35
|
|
|
36
36
|
const TOOL_RESULT_TEXT_LIMIT = 500;
|
|
37
37
|
|
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
type SessionIndexDatabase,
|
|
12
12
|
setMetadata,
|
|
13
13
|
upsertSession,
|
|
14
|
-
} from "../shared/session-index/index.
|
|
15
|
-
import { type ExtractedSessionRecord, extractSessionRecord } from "./extract.
|
|
14
|
+
} from "../shared/session-index/index.ts";
|
|
15
|
+
import { type ExtractedSessionRecord, extractSessionRecord } from "./extract.ts";
|
|
16
16
|
|
|
17
17
|
export interface ReindexOptions {
|
|
18
18
|
indexPath: string;
|
|
@@ -5,7 +5,7 @@ import { Type } from "typebox";
|
|
|
5
5
|
import {
|
|
6
6
|
stripSearchSnippetMarkers,
|
|
7
7
|
transformSearchSnippetMatches,
|
|
8
|
-
} from "./shared/search-snippet.
|
|
8
|
+
} from "./shared/search-snippet.ts";
|
|
9
9
|
import {
|
|
10
10
|
getIndexStatus,
|
|
11
11
|
INDEX_SCHEMA_VERSION,
|
|
@@ -14,9 +14,9 @@ import {
|
|
|
14
14
|
type SearchSessionsParams,
|
|
15
15
|
type SessionIndexStatus,
|
|
16
16
|
searchSessions,
|
|
17
|
-
} from "./shared/session-index/index.
|
|
18
|
-
import { formatSessionTitleOrShortId } from "./shared/session-ui.
|
|
19
|
-
import { loadSettings } from "./shared/settings.
|
|
17
|
+
} from "./shared/session-index/index.ts";
|
|
18
|
+
import { formatSessionTitleOrShortId } from "./shared/session-ui.ts";
|
|
19
|
+
import { loadSettings } from "./shared/settings.ts";
|
|
20
20
|
|
|
21
21
|
interface SessionSearchToolParams {
|
|
22
22
|
query?: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
-
import type { FileTouchOp, FileTouchSource, PathScope } from "../../session-search/normalize.
|
|
3
|
-
import { safeParseTypeBoxJson } from "../typebox.
|
|
4
|
-
import type { SqliteDatabase } from "./sqlite.
|
|
2
|
+
import type { FileTouchOp, FileTouchSource, PathScope } from "../../session-search/normalize.ts";
|
|
3
|
+
import { safeParseTypeBoxJson } from "../typebox.ts";
|
|
4
|
+
import type { SqliteDatabase } from "./sqlite.ts";
|
|
5
5
|
|
|
6
6
|
export const INDEX_SCHEMA_VERSION = 8;
|
|
7
7
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export * from "./common.
|
|
2
|
-
export * from "./lineage.
|
|
3
|
-
export * from "./schema.
|
|
4
|
-
export * from "./search.
|
|
5
|
-
export * from "./store.
|
|
1
|
+
export * from "./common.ts";
|
|
2
|
+
export * from "./lineage.ts";
|
|
3
|
+
export * from "./schema.ts";
|
|
4
|
+
export * from "./search.ts";
|
|
5
|
+
export * from "./store.ts";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Static, Type } from "typebox";
|
|
2
|
-
import { parseTypeBoxRows, parseTypeBoxValue } from "../typebox.
|
|
2
|
+
import { parseTypeBoxRows, parseTypeBoxValue } from "../typebox.ts";
|
|
3
3
|
import {
|
|
4
4
|
NULLABLE_STRING_SCHEMA,
|
|
5
5
|
parseRepoRoots,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type SessionLineageRelation,
|
|
10
10
|
type SessionLineageRow,
|
|
11
11
|
type SessionRelatedSessionRow,
|
|
12
|
-
} from "./common.
|
|
12
|
+
} from "./common.ts";
|
|
13
13
|
|
|
14
14
|
const SESSION_GRAPH_ROW_SCHEMA = Type.Object({
|
|
15
15
|
sessionId: Type.String(),
|
|
@@ -160,6 +160,13 @@ export function getLineageSessions(
|
|
|
160
160
|
return queryRelatedSessions(db, sessionId);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
export function getLineageRelationMap(
|
|
164
|
+
db: SessionIndexDatabase,
|
|
165
|
+
sessionId: string,
|
|
166
|
+
): Map<string, SessionLineageRelation> {
|
|
167
|
+
return new Map(getLineageSessions(db, sessionId).map((row) => [row.sessionId, row.relation]));
|
|
168
|
+
}
|
|
169
|
+
|
|
163
170
|
export function getParentSession(
|
|
164
171
|
db: SessionIndexDatabase,
|
|
165
172
|
sessionId: string,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { parseTypeBoxValue } from "../typebox.
|
|
2
|
+
import { parseTypeBoxValue } from "../typebox.ts";
|
|
3
3
|
import {
|
|
4
4
|
INDEX_SCHEMA_VERSION,
|
|
5
5
|
METADATA_ROW_SCHEMA,
|
|
6
6
|
ROW_COUNT_SCHEMA,
|
|
7
7
|
type SessionIndexDatabase,
|
|
8
8
|
type SessionIndexStatus,
|
|
9
|
-
} from "./common.
|
|
10
|
-
import { openSqlite } from "./sqlite.
|
|
9
|
+
} from "./common.ts";
|
|
10
|
+
import { openSqlite } from "./sqlite.ts";
|
|
11
11
|
|
|
12
12
|
export function ensureIndexDir(dir: string): string {
|
|
13
13
|
if (!existsSync(dir)) {
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
type FileTouchOp,
|
|
5
5
|
matchesRepoRoot,
|
|
6
6
|
normalizeSearchPath,
|
|
7
|
-
} from "../../session-search/normalize.
|
|
7
|
+
} from "../../session-search/normalize.ts";
|
|
8
8
|
import {
|
|
9
9
|
SEARCH_SNIPPET_ELLIPSIS,
|
|
10
10
|
SEARCH_SNIPPET_MATCH_END,
|
|
11
11
|
SEARCH_SNIPPET_MATCH_START,
|
|
12
|
-
} from "../search-snippet.
|
|
13
|
-
import { parseTypeBoxRows } from "../typebox.
|
|
12
|
+
} from "../search-snippet.ts";
|
|
13
|
+
import { parseTypeBoxRows } from "../typebox.ts";
|
|
14
14
|
import {
|
|
15
15
|
boostIndependentHits,
|
|
16
16
|
buildFtsQuery,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
type SessionIndexDatabase,
|
|
27
27
|
sanitizeFilterValues,
|
|
28
28
|
tokenizeSearchTerms,
|
|
29
|
-
} from "./common.
|
|
29
|
+
} from "./common.ts";
|
|
30
30
|
|
|
31
31
|
const SESSION_LIST_ROW_SCHEMA = Type.Object({
|
|
32
32
|
sessionId: Type.String(),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Static, Type } from "typebox";
|
|
2
|
-
import { parseTypeBoxValue } from "../typebox.
|
|
2
|
+
import { parseTypeBoxValue } from "../typebox.ts";
|
|
3
3
|
import {
|
|
4
4
|
compactSessionId,
|
|
5
5
|
INDEX_SCHEMA_VERSION,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type SessionIndexDatabase,
|
|
11
11
|
type SessionRow,
|
|
12
12
|
type SessionTextChunkRow,
|
|
13
|
-
} from "./common.
|
|
13
|
+
} from "./common.ts";
|
|
14
14
|
|
|
15
15
|
const SESSION_ROW_QUERY_SCHEMA = Type.Object({
|
|
16
16
|
sessionId: Type.String(),
|
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import type { KeyId } from "@earendil-works/pi-tui";
|
|
5
5
|
import { type Static, Type } from "typebox";
|
|
6
|
-
import { parseTypeBoxValue } from "./typebox.
|
|
6
|
+
import { parseTypeBoxValue } from "./typebox.ts";
|
|
7
7
|
|
|
8
8
|
export const DEFAULT_AUTO_TITLE_REFRESH_TURNS = 4;
|
|
9
9
|
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.`;
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-sessions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Pi session search, ask, handoff, auto-titling, and indexing tools",
|
|
5
|
+
"description": "Pi session search, ask, handoff, messaging, auto-titling, and indexing tools",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"pi-package"
|
|
17
17
|
],
|
|
18
18
|
"engines": {
|
|
19
|
-
"node": ">=
|
|
19
|
+
"node": ">=24 <26"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"extensions",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"./extensions/session-handoff.ts",
|
|
30
30
|
"./extensions/session-hooks.ts",
|
|
31
31
|
"./extensions/session-index.ts",
|
|
32
|
+
"./extensions/session-messaging.ts",
|
|
32
33
|
"./extensions/session-search.ts"
|
|
33
34
|
]
|
|
34
35
|
},
|