pi-sessions 0.8.0 → 0.9.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 +70 -37
- package/extensions/pi-sessions.ts +125 -0
- package/extensions/session-ask/agent.ts +34 -14
- package/extensions/{session-ask.ts → session-ask/install.ts} +73 -90
- package/extensions/session-ask/navigate.ts +3 -1
- package/extensions/session-ask/presenter.ts +23 -0
- package/extensions/session-ask/renderer.ts +63 -0
- package/extensions/session-ask/tool-contract.ts +27 -0
- package/extensions/session-ask/view-model.ts +18 -0
- package/extensions/session-auto-title/generate.ts +18 -18
- package/extensions/{session-auto-title.ts → session-auto-title/install.ts} +60 -53
- package/extensions/session-auto-title/model.ts +25 -17
- package/extensions/session-auto-title/retitle.ts +17 -18
- package/extensions/session-auto-title/wizard.ts +12 -11
- package/extensions/session-handoff/board-view-model.ts +181 -0
- package/extensions/session-handoff/board.ts +663 -0
- package/extensions/session-handoff/bootstrap.ts +174 -0
- package/extensions/session-handoff/extract.ts +101 -122
- package/extensions/session-handoff/install.ts +225 -0
- package/extensions/session-handoff/kickoff.ts +92 -0
- package/extensions/session-handoff/launch/backend.ts +16 -0
- package/extensions/session-handoff/launch/deferred.ts +20 -0
- package/extensions/session-handoff/launch/ghostty.ts +99 -0
- package/extensions/session-handoff/launch/resolution.ts +57 -0
- package/extensions/session-handoff/launch/shell.ts +7 -0
- package/extensions/session-handoff/launch/tmux.ts +31 -0
- package/extensions/session-handoff/launch-options.ts +61 -0
- package/extensions/session-handoff/launch-target.ts +91 -0
- package/extensions/session-handoff/metadata.ts +152 -63
- package/extensions/session-handoff/model.ts +58 -0
- package/extensions/session-handoff/query.ts +0 -1
- package/extensions/session-handoff/receipt.ts +96 -0
- package/extensions/session-handoff/review.ts +2 -28
- package/extensions/session-handoff/spawn.ts +76 -181
- package/extensions/session-handoff/strong-modal.ts +13 -0
- package/extensions/session-handoff/tool-contract.ts +12 -0
- package/extensions/session-handoff/tool-presenter.ts +35 -0
- package/extensions/session-handoff/tool-renderer.ts +66 -0
- package/extensions/session-handoff/tool-schema.ts +54 -0
- package/extensions/session-handoff/tool-view-model.ts +49 -0
- package/extensions/session-handoff/tool.ts +206 -0
- package/extensions/session-handoff/ui.ts +66 -0
- package/extensions/session-hooks/install.ts +75 -0
- package/extensions/{session-index.ts → session-index/install.ts} +9 -7
- package/extensions/session-messaging/broker/process.ts +21 -28
- package/extensions/session-messaging/install.ts +78 -0
- package/extensions/session-messaging/pi/cancel-session-presenter.ts +64 -0
- package/extensions/session-messaging/pi/incoming-message-presenter.ts +32 -0
- package/extensions/session-messaging/pi/incoming-message-view-model.ts +20 -0
- package/extensions/session-messaging/pi/incoming-runtime.ts +27 -23
- package/extensions/session-messaging/pi/message-contracts.ts +32 -0
- package/extensions/session-messaging/pi/renderer.ts +19 -12
- package/extensions/session-messaging/pi/send-message-presenter.ts +39 -0
- package/extensions/session-messaging/pi/send-message-view-model.ts +54 -0
- package/extensions/session-messaging/pi/service.ts +262 -81
- package/extensions/session-messaging/pi/tools.ts +184 -40
- package/extensions/session-search/extract.ts +60 -14
- package/extensions/session-search/hooks.ts +10 -13
- package/extensions/session-search/install.ts +373 -0
- package/extensions/session-search/renderer.ts +195 -0
- package/extensions/session-search/tool-contract.ts +51 -0
- package/extensions/shared/composition.ts +20 -0
- package/extensions/shared/errors.ts +3 -0
- package/extensions/shared/model-resolution.ts +73 -0
- package/extensions/shared/model-runtime.ts +33 -0
- package/extensions/shared/model.ts +7 -10
- package/extensions/shared/rendering/collapsible-text.ts +89 -0
- package/extensions/shared/rendering/expandable-content-layout.ts +84 -0
- package/extensions/shared/rendering/theme.ts +5 -0
- package/extensions/shared/session-broker/client.ts +30 -29
- package/extensions/shared/session-broker/protocol.ts +128 -10
- package/extensions/shared/session-index/common.ts +5 -5
- package/extensions/shared/session-index/lineage.ts +0 -3
- package/extensions/shared/session-index/schema.ts +0 -1
- package/extensions/shared/session-index/scoring.ts +1 -2
- package/extensions/shared/session-index/search.ts +11 -4
- package/extensions/shared/session-index/store.ts +4 -9
- package/extensions/shared/settings.ts +76 -38
- package/extensions/shared/text.ts +5 -0
- package/extensions/shared/thinking-levels.ts +22 -0
- package/extensions/shared/tmux.ts +205 -0
- package/extensions/subagents/cancel.ts +111 -0
- package/extensions/subagents/classify.ts +52 -0
- package/extensions/subagents/install.ts +344 -0
- package/extensions/subagents/launch-target.ts +75 -0
- package/extensions/subagents/ledger.ts +217 -0
- package/extensions/subagents/reconcile.ts +386 -0
- package/extensions/subagents/report-message-presenter.ts +36 -0
- package/extensions/subagents/report-message-renderer.ts +21 -0
- package/extensions/subagents/report-message-view-model.ts +29 -0
- package/extensions/subagents/report.ts +159 -0
- package/extensions/subagents/roster.ts +262 -0
- package/extensions/subagents/wake.ts +173 -0
- package/images/handoff-board-subagents.png +0 -0
- package/images/handoff-board-user-sessions.png +0 -0
- package/images/handoff-subagent-report.png +0 -0
- package/images/session-handoff-tool.png +0 -0
- package/package.json +10 -16
- package/extensions/session-handoff.ts +0 -741
- package/extensions/session-hooks.ts +0 -75
- package/extensions/session-messaging/pi/message-view.ts +0 -131
- package/extensions/session-messaging.ts +0 -30
- package/extensions/session-search.ts +0 -419
- package/extensions/shared/session-broker/active.ts +0 -26
|
@@ -1,51 +1,119 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { defineTool, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Text } from "@earendil-works/pi-tui";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
import { formatError } from "../../shared/errors.ts";
|
|
4
|
+
import { ExpandableContentLayout } from "../../shared/rendering/expandable-content-layout.ts";
|
|
5
|
+
import { safeParseTypeBoxValue } from "../../shared/typebox.ts";
|
|
6
|
+
import {
|
|
7
|
+
buildCancelSessionModelText,
|
|
8
|
+
buildCancelSessionUserError,
|
|
9
|
+
buildCancelSessionUserText,
|
|
10
|
+
buildDeadSessionError,
|
|
11
|
+
buildUnknownCancellationError,
|
|
12
|
+
isConfirmedManagedCancellation,
|
|
13
|
+
} from "./cancel-session-presenter.ts";
|
|
14
|
+
import {
|
|
15
|
+
CANCEL_SESSION_PARAMS,
|
|
16
|
+
CANCEL_SESSION_TOOL_DETAILS_SCHEMA,
|
|
17
|
+
type CancelSessionParams,
|
|
18
|
+
type CancelSessionToolDetails,
|
|
19
|
+
SEND_MESSAGE_PARAMS,
|
|
20
|
+
SEND_MESSAGE_TOOL_DETAILS_SCHEMA,
|
|
21
|
+
type SendMessageParams,
|
|
22
|
+
type SendMessageToolDetails,
|
|
23
|
+
} from "./message-contracts.ts";
|
|
24
|
+
import { buildSendMessagePresentation } from "./send-message-presenter.ts";
|
|
25
|
+
import { buildDeliveredMessageView, buildSendingMessageView } from "./send-message-view-model.ts";
|
|
26
|
+
import type { CancelSessionResult, SendMessageRequest, SendMessageResult } from "./service.ts";
|
|
27
|
+
|
|
28
|
+
interface SendMessageService {
|
|
29
|
+
sendMessage(request: SendMessageRequest): Promise<SendMessageResult>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ManagedCancelSessionResult {
|
|
33
|
+
kind: "managed";
|
|
34
|
+
accepted: true;
|
|
35
|
+
target: {
|
|
36
|
+
sessionId: string;
|
|
37
|
+
sessionName?: string;
|
|
38
|
+
cwd?: string;
|
|
39
|
+
};
|
|
40
|
+
state: string;
|
|
41
|
+
cancelId?: string;
|
|
42
|
+
relation?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface CancelSessionService {
|
|
46
|
+
cancelSession(sessionId: string): Promise<CancelSessionResult | ManagedCancelSessionResult>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface SessionSendMessageToolOptions {
|
|
50
|
+
wakeCapable: boolean;
|
|
51
|
+
getCachedRelationTo(sessionId: string | undefined): string | undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface SendMessageRendererState {
|
|
55
|
+
callComponent?: ExpandableContentLayout | undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createSessionSendMessageTool(
|
|
59
|
+
service: SendMessageService,
|
|
60
|
+
options: SessionSendMessageToolOptions,
|
|
61
|
+
): ToolDefinition {
|
|
62
|
+
const description = options.wakeCapable
|
|
63
|
+
? "Send a message to another live pi session or a subagent."
|
|
64
|
+
: "Send a message to another live pi session.";
|
|
65
|
+
const promptSnippet = options.wakeCapable
|
|
66
|
+
? "Send a message to another pi session or a subagent"
|
|
67
|
+
: "Send a message to another live pi session";
|
|
68
|
+
const promptGuidelines = options.wakeCapable
|
|
69
|
+
? [
|
|
70
|
+
"Use session_search with live: true to discover live sessions.",
|
|
71
|
+
"It is always possible to session_send_message to an owned subagent",
|
|
72
|
+
]
|
|
73
|
+
: [
|
|
74
|
+
"Before session_send_message, use session_search with live: true to list all live sessions and find the target.",
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
return defineTool({
|
|
12
78
|
name: "session_send_message",
|
|
13
79
|
label: "Send Message to Session",
|
|
14
|
-
description
|
|
15
|
-
promptSnippet
|
|
16
|
-
promptGuidelines
|
|
17
|
-
"Before session_send_message, use session_search with live: true (no other filters) to list all live sessions and find the target.",
|
|
18
|
-
],
|
|
80
|
+
description,
|
|
81
|
+
promptSnippet,
|
|
82
|
+
promptGuidelines,
|
|
19
83
|
parameters: SEND_MESSAGE_PARAMS,
|
|
20
84
|
renderCall(args, theme, context) {
|
|
21
|
-
const
|
|
22
|
-
component.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
85
|
+
const state = context.state as SendMessageRendererState;
|
|
86
|
+
const component = state.callComponent ?? new ExpandableContentLayout(theme);
|
|
87
|
+
state.callComponent = component;
|
|
88
|
+
component.update(
|
|
89
|
+
buildSendMessagePresentation(
|
|
90
|
+
buildSendingMessageView(args, options.getCachedRelationTo(args?.session)),
|
|
27
91
|
theme,
|
|
28
|
-
service.getCachedRelationTo(args?.session),
|
|
29
92
|
),
|
|
93
|
+
context.expanded,
|
|
30
94
|
);
|
|
31
95
|
return component;
|
|
32
96
|
},
|
|
33
|
-
renderResult(result,
|
|
34
|
-
if (
|
|
35
|
-
|
|
97
|
+
renderResult(result, options, theme, context) {
|
|
98
|
+
if (context.isError) {
|
|
99
|
+
const output = getFirstText(result);
|
|
100
|
+
return new Text(output ? `\n${theme.fg("error", output)}` : "", 0, 0);
|
|
36
101
|
}
|
|
37
102
|
|
|
38
|
-
const
|
|
39
|
-
|
|
103
|
+
const details = safeParseTypeBoxValue(SEND_MESSAGE_TOOL_DETAILS_SCHEMA, result.details);
|
|
104
|
+
const state = context.state as SendMessageRendererState;
|
|
105
|
+
if (!details || !state.callComponent) {
|
|
106
|
+
return new Text(getFirstText(result), 0, 0);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
state.callComponent.update(
|
|
110
|
+
buildSendMessagePresentation(buildDeliveredMessageView(context.args, details), theme),
|
|
111
|
+
options.expanded,
|
|
112
|
+
);
|
|
113
|
+
return new Text("", 0, 0);
|
|
40
114
|
},
|
|
41
115
|
async execute(toolCallId, params: SendMessageParams, _signal, _onUpdate, _ctx) {
|
|
42
|
-
const target = params.session
|
|
43
|
-
if (!target) {
|
|
44
|
-
throw new Error("session_send_message requires a target session id.");
|
|
45
|
-
}
|
|
46
|
-
if (target.startsWith("@session:")) {
|
|
47
|
-
throw new Error("Use the bare session UUID, not an @session token.");
|
|
48
|
-
}
|
|
116
|
+
const target = parseSessionTarget(params.session, "session_send_message");
|
|
49
117
|
|
|
50
118
|
const body = params.message.trim();
|
|
51
119
|
if (!body) {
|
|
@@ -73,8 +141,9 @@ export function registerSessionMessagingTools(
|
|
|
73
141
|
details: {
|
|
74
142
|
delivered: true,
|
|
75
143
|
messageId: result.messageId,
|
|
76
|
-
|
|
77
|
-
|
|
144
|
+
target: result.target,
|
|
145
|
+
...(result.relation === undefined ? {} : { relation: result.relation }),
|
|
146
|
+
} satisfies SendMessageToolDetails,
|
|
78
147
|
};
|
|
79
148
|
} catch (error) {
|
|
80
149
|
throw new Error(formatError(error));
|
|
@@ -83,10 +152,85 @@ export function registerSessionMessagingTools(
|
|
|
83
152
|
});
|
|
84
153
|
}
|
|
85
154
|
|
|
86
|
-
function
|
|
87
|
-
return
|
|
155
|
+
export function createSessionCancelTool(service: CancelSessionService): ToolDefinition {
|
|
156
|
+
return defineTool<typeof CANCEL_SESSION_PARAMS, CancelSessionToolDetails>({
|
|
157
|
+
name: "session_cancel",
|
|
158
|
+
label: "Cancel a running session",
|
|
159
|
+
description: "Cancel another running pi session",
|
|
160
|
+
promptSnippet: "Cancel another running pi session",
|
|
161
|
+
promptGuidelines: ["Only cancel a user session when the user directs it."],
|
|
162
|
+
parameters: CANCEL_SESSION_PARAMS,
|
|
163
|
+
renderResult(result, options, theme, context) {
|
|
164
|
+
const output = getFirstText(result);
|
|
165
|
+
if (context.isError) {
|
|
166
|
+
const error = buildCancelSessionUserError(output, options.expanded);
|
|
167
|
+
return new Text(error ? theme.fg("error", error) : "", 0, 0);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const details = safeParseTypeBoxValue(CANCEL_SESSION_TOOL_DETAILS_SCHEMA, result.details);
|
|
171
|
+
if (!details) {
|
|
172
|
+
return new Text(output, 0, 0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let text = theme.fg("toolOutput", buildCancelSessionUserText(details));
|
|
176
|
+
if (options.expanded && details.target.sessionName) {
|
|
177
|
+
text += `\n${theme.fg("dim", `${theme.bold("session")} ${details.target.sessionId}`)}`;
|
|
178
|
+
}
|
|
179
|
+
return new Text(text, 0, 0);
|
|
180
|
+
},
|
|
181
|
+
async execute(_toolCallId, params: CancelSessionParams) {
|
|
182
|
+
const target = parseSessionTarget(params.session, "session_cancel");
|
|
183
|
+
const result = await service.cancelSession(target);
|
|
184
|
+
if (result.kind === "managed") {
|
|
185
|
+
const details: CancelSessionToolDetails = {
|
|
186
|
+
kind: "managed",
|
|
187
|
+
accepted: true,
|
|
188
|
+
target: result.target,
|
|
189
|
+
state: result.state,
|
|
190
|
+
...(result.cancelId === undefined ? {} : { cancelId: result.cancelId }),
|
|
191
|
+
...(result.relation === undefined ? {} : { relation: result.relation }),
|
|
192
|
+
};
|
|
193
|
+
if (!isConfirmedManagedCancellation(result.state)) {
|
|
194
|
+
throw new Error(buildUnknownCancellationError(result.target));
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
content: [{ type: "text" as const, text: buildCancelSessionModelText(details) }],
|
|
198
|
+
details,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (!result.delivered) {
|
|
202
|
+
if (result.reason === "no_session") {
|
|
203
|
+
throw new Error(buildDeadSessionError(target));
|
|
204
|
+
}
|
|
205
|
+
throw new Error(result.error ?? "Cancellation was not delivered.");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const details: CancelSessionToolDetails = {
|
|
209
|
+
kind: "transport",
|
|
210
|
+
delivered: true,
|
|
211
|
+
cancelId: result.cancelId,
|
|
212
|
+
target: result.target,
|
|
213
|
+
...(result.relation === undefined ? {} : { relation: result.relation }),
|
|
214
|
+
};
|
|
215
|
+
return {
|
|
216
|
+
content: [{ type: "text" as const, text: buildCancelSessionModelText(details) }],
|
|
217
|
+
details,
|
|
218
|
+
};
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function parseSessionTarget(raw: string, toolName: string): string {
|
|
224
|
+
const target = raw.trim();
|
|
225
|
+
if (!target) {
|
|
226
|
+
throw new Error(`${toolName} requires a target session id.`);
|
|
227
|
+
}
|
|
228
|
+
if (target.startsWith("@session:")) {
|
|
229
|
+
throw new Error("Use the bare session UUID, not an @session token.");
|
|
230
|
+
}
|
|
231
|
+
return target;
|
|
88
232
|
}
|
|
89
233
|
|
|
90
|
-
function
|
|
91
|
-
return
|
|
234
|
+
function getFirstText(result: { content: Array<{ type: string; text?: string }> }): string {
|
|
235
|
+
return result.content.find((item) => item.type === "text")?.text ?? "";
|
|
92
236
|
}
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import type { SessionOrigin } from "../shared/session-index/index.ts";
|
|
27
27
|
import { contentToText, isRecord } from "../shared/text.ts";
|
|
28
28
|
import { safeParseTypeBoxValue } from "../shared/typebox.ts";
|
|
29
|
+
import { SUBAGENT_LAUNCHED_CUSTOM_TYPE, SUBAGENT_LAUNCHED_SCHEMA } from "../subagents/ledger.ts";
|
|
29
30
|
import {
|
|
30
31
|
deriveSessionRepoRoots,
|
|
31
32
|
type FileTouchOp,
|
|
@@ -78,7 +79,6 @@ export interface ExtractedSessionRecord {
|
|
|
78
79
|
parentSessionId?: string | undefined;
|
|
79
80
|
sessionOrigin?: SessionOrigin | undefined;
|
|
80
81
|
handoffGoal?: string | undefined;
|
|
81
|
-
handoffNextTask?: string | undefined;
|
|
82
82
|
indexedFileSize: number;
|
|
83
83
|
indexedFileMtimeMs: number;
|
|
84
84
|
indexedFileAnchor: string;
|
|
@@ -181,6 +181,9 @@ export function extractSessionRecord(sessionPath: string): ExtractedSessionRecor
|
|
|
181
181
|
appendDurableHandoffMetadataChunks(chunks, scan.handoffMetadata);
|
|
182
182
|
|
|
183
183
|
const parentSessionPath = normalizeParentSessionPath(parsed.header.parentSession);
|
|
184
|
+
const parentSession = parentSessionPath
|
|
185
|
+
? inspectParentSession(parentSessionPath, parsed.header.id, sessionPath)
|
|
186
|
+
: undefined;
|
|
184
187
|
|
|
185
188
|
return {
|
|
186
189
|
sessionId: parsed.header.id,
|
|
@@ -194,10 +197,13 @@ export function extractSessionRecord(sessionPath: string): ExtractedSessionRecor
|
|
|
194
197
|
messageCount: scan.messageCount,
|
|
195
198
|
entryCount: scan.entryCount,
|
|
196
199
|
parentSessionPath,
|
|
197
|
-
parentSessionId:
|
|
198
|
-
sessionOrigin:
|
|
200
|
+
parentSessionId: parentSession?.sessionId,
|
|
201
|
+
sessionOrigin: deriveSessionOrigin(
|
|
202
|
+
parentSessionPath,
|
|
203
|
+
parentSession?.launchedSubagent ?? false,
|
|
204
|
+
scan.handoffMetadata?.metadata,
|
|
205
|
+
),
|
|
199
206
|
handoffGoal: scan.handoffMetadata?.metadata.goal,
|
|
200
|
-
handoffNextTask: scan.handoffMetadata?.metadata.nextTask,
|
|
201
207
|
indexedFileSize: slice.consumedBytes,
|
|
202
208
|
indexedFileMtimeMs,
|
|
203
209
|
indexedFileAnchor: buildFileAnchor(buffer, slice.consumedBytes),
|
|
@@ -429,7 +435,7 @@ export function parseSessionContent(raw: string): ParsedSessionFile | undefined
|
|
|
429
435
|
}
|
|
430
436
|
|
|
431
437
|
const header = fileEntries[0];
|
|
432
|
-
if (
|
|
438
|
+
if (header?.type !== "session") {
|
|
433
439
|
return undefined;
|
|
434
440
|
}
|
|
435
441
|
|
|
@@ -458,23 +464,66 @@ function normalizeParentSessionPath(parentSession: string | undefined): string |
|
|
|
458
464
|
return trimmed.length > 0 ? trimmed : undefined;
|
|
459
465
|
}
|
|
460
466
|
|
|
461
|
-
|
|
467
|
+
interface ParentSessionInspection {
|
|
468
|
+
sessionId: string;
|
|
469
|
+
launchedSubagent: boolean;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export function inferSessionOrigin(
|
|
473
|
+
sessionId: string,
|
|
474
|
+
sessionPath: string,
|
|
475
|
+
parentSessionPath: string | undefined,
|
|
476
|
+
handoffMetadata: HandoffSessionMetadata | undefined,
|
|
477
|
+
): SessionOrigin | undefined {
|
|
478
|
+
const parentSession = parentSessionPath
|
|
479
|
+
? inspectParentSession(parentSessionPath, sessionId, sessionPath)
|
|
480
|
+
: undefined;
|
|
481
|
+
return deriveSessionOrigin(
|
|
482
|
+
parentSessionPath,
|
|
483
|
+
parentSession?.launchedSubagent ?? false,
|
|
484
|
+
handoffMetadata,
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function inspectParentSession(
|
|
489
|
+
parentSessionPath: string,
|
|
490
|
+
childSessionId: string,
|
|
491
|
+
childSessionPath: string,
|
|
492
|
+
): ParentSessionInspection | undefined {
|
|
462
493
|
try {
|
|
463
|
-
const
|
|
464
|
-
|
|
494
|
+
const parent = parseSessionFile(parentSessionPath);
|
|
495
|
+
if (!parent) {
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const launchedSubagent = parent.entries.some((entry) => {
|
|
500
|
+
if (entry.type !== "custom" || entry.customType !== SUBAGENT_LAUNCHED_CUSTOM_TYPE) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
const launch = safeParseTypeBoxValue(SUBAGENT_LAUNCHED_SCHEMA, entry.data);
|
|
504
|
+
return (
|
|
505
|
+
launch?.writerSessionId === parent.header.id &&
|
|
506
|
+
launch.childSessionId === childSessionId &&
|
|
507
|
+
launch.childSessionFile === childSessionPath
|
|
508
|
+
);
|
|
509
|
+
});
|
|
510
|
+
return { sessionId: parent.header.id, launchedSubagent };
|
|
465
511
|
} catch {
|
|
466
512
|
return undefined;
|
|
467
513
|
}
|
|
468
514
|
}
|
|
469
515
|
|
|
470
|
-
function
|
|
516
|
+
function deriveSessionOrigin(
|
|
471
517
|
parentSessionPath: string | undefined,
|
|
518
|
+
launchedSubagent: boolean,
|
|
472
519
|
handoffMetadata: HandoffSessionMetadata | undefined,
|
|
473
520
|
): SessionOrigin | undefined {
|
|
474
521
|
if (!parentSessionPath) {
|
|
475
522
|
return undefined;
|
|
476
523
|
}
|
|
477
|
-
|
|
524
|
+
if (launchedSubagent) {
|
|
525
|
+
return "subagent";
|
|
526
|
+
}
|
|
478
527
|
return handoffMetadata?.origin === "handoff" ? "handoff" : "unknown_child";
|
|
479
528
|
}
|
|
480
529
|
|
|
@@ -507,10 +556,7 @@ function appendDurableHandoffMetadataChunks(
|
|
|
507
556
|
}
|
|
508
557
|
|
|
509
558
|
const { entryId, ts, metadata } = durableHandoffMetadata;
|
|
510
|
-
chunks.push(
|
|
511
|
-
createMetadataChunk(entryId, ts, "handoff_goal", metadata.goal),
|
|
512
|
-
createMetadataChunk(entryId, ts, "handoff_next_task", metadata.nextTask),
|
|
513
|
-
);
|
|
559
|
+
chunks.push(createMetadataChunk(entryId, ts, "handoff_goal", metadata.goal));
|
|
514
560
|
}
|
|
515
561
|
|
|
516
562
|
function createMetadataChunk(
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
type ExtractedSessionTail,
|
|
28
28
|
extractSessionRecord,
|
|
29
29
|
extractSessionTail,
|
|
30
|
+
inferSessionOrigin,
|
|
30
31
|
type SessionFileTouch,
|
|
31
32
|
} from "./extract.ts";
|
|
32
33
|
import { deriveSessionRepoRoots } from "./normalize.ts";
|
|
@@ -361,14 +362,6 @@ function applyTailSync(
|
|
|
361
362
|
sourceKind: "handoff_goal",
|
|
362
363
|
text: metadata.goal,
|
|
363
364
|
});
|
|
364
|
-
insertTextChunk(db, {
|
|
365
|
-
sessionId: baseline.sessionId,
|
|
366
|
-
entryId,
|
|
367
|
-
entryType: "custom",
|
|
368
|
-
ts,
|
|
369
|
-
sourceKind: "handoff_next_task",
|
|
370
|
-
text: metadata.nextTask,
|
|
371
|
-
});
|
|
372
365
|
}
|
|
373
366
|
|
|
374
367
|
for (const chunk of scan.chunks) {
|
|
@@ -392,10 +385,15 @@ function buildTailSessionRow(
|
|
|
392
385
|
): SessionRow {
|
|
393
386
|
const scan = tail.scan;
|
|
394
387
|
const tailHandoffMetadata = baseline.handoffGoal ? undefined : scan.handoffMetadata?.metadata;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
388
|
+
let tailOrigin: SessionOrigin | undefined;
|
|
389
|
+
if (baseline.sessionOrigin !== "subagent" && baseline.sessionOrigin !== "fork") {
|
|
390
|
+
tailOrigin = inferSessionOrigin(
|
|
391
|
+
baseline.sessionId,
|
|
392
|
+
baseline.sessionPath,
|
|
393
|
+
baseline.parentSessionPath,
|
|
394
|
+
tailHandoffMetadata,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
399
397
|
const nextOrigin = resolveSessionOrigin(sessionOrigin, tailOrigin, baseline.sessionOrigin);
|
|
400
398
|
|
|
401
399
|
return {
|
|
@@ -416,7 +414,6 @@ function buildTailSessionRow(
|
|
|
416
414
|
parentSessionId: baseline.parentSessionId,
|
|
417
415
|
sessionOrigin: baseline.parentSessionPath ? (nextOrigin ?? "unknown_child") : undefined,
|
|
418
416
|
handoffGoal: baseline.handoffGoal ?? tailHandoffMetadata?.goal,
|
|
419
|
-
handoffNextTask: baseline.handoffNextTask ?? tailHandoffMetadata?.nextTask,
|
|
420
417
|
indexedFileSize: tail.indexedFileSize,
|
|
421
418
|
indexedFileMtimeMs: tail.indexedFileMtimeMs,
|
|
422
419
|
indexedFileAnchor: tail.indexedFileAnchor,
|