@nklisch/pi-enhanced 0.2.6 → 0.3.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/CHANGELOG.md +16 -0
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.linux-x64-gnu.node +0 -0
- package/node_modules/@nklisch/pi-clearance/native/clearance-core.win32-x64-msvc.node +0 -0
- package/node_modules/@nklisch/pi-plugins/README.md +4 -2
- package/node_modules/@nklisch/pi-plugins/dist/hooks.js +81 -8
- package/node_modules/@nklisch/pi-plugins/dist/hooks.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/mcp.js +32 -1
- package/node_modules/@nklisch/pi-plugins/dist/mcp.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.d.ts +10 -4
- package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js +144 -10
- package/node_modules/@nklisch/pi-plugins/dist/pi/plugin-manager.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js +40 -14
- package/node_modules/@nklisch/pi-plugins/dist/runtime-discovery.js.map +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/CHANGELOG.md +25 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/README.md +75 -47
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/public.d.ts +171 -140
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/dist/settings.d.ts +2 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/FORK-MAINTENANCE.md +8 -7
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/VISION.md +12 -10
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/architecture/architecture.md +112 -105
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/comparison-with-upstream.md +3 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/docs/decisions/0004-reconsider-ui-direction.md +5 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/package.json +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/custom-agents.ts +31 -4
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/default-agents.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/config/invocation-config.ts +7 -4
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/index.ts +18 -9
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/layered-settings.ts +2 -2
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/child-lifecycle.ts +10 -63
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/concurrency-limiter.ts +122 -76
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/create-subagent-session.ts +3 -5
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/lifecycle-interceptor.ts +4 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/run-listeners.ts +2 -23
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-manager.ts +177 -252
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-session.ts +90 -280
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent-state.ts +153 -223
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/subagent.ts +569 -683
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/lifecycle/workspace.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/notification.ts +78 -119
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/renderer.ts +15 -42
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/observation/subagent-events-observer.ts +44 -95
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service-adapter.ts +157 -90
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/service/service.ts +60 -68
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/conversation.ts +3 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query-source.ts +28 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query.ts +327 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/settings.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/agent-tool.ts +171 -249
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-tool.ts +51 -92
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/helpers.ts +9 -6
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/list-tool.ts +46 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/parent-tool-registry.ts +17 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/query-session-tool.ts +330 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/result-renderer.ts +27 -111
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/resume-tool.ts +105 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/spawn-config.ts +26 -8
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/steer-tool.ts +27 -111
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/stop-tool.ts +53 -0
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/types.ts +11 -5
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/agent-widget.ts +33 -27
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/display.ts +6 -3
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigation.ts +121 -10
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/session-navigator.ts +430 -112
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/subagents-settings.ts +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/ui/widget-renderer.ts +8 -9
- package/node_modules/@nklisch/pi-plugins/package.json +2 -2
- package/package.json +1 -1
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/background-spawner.ts +0 -81
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/foreground-runner.ts +0 -149
- package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/tools/get-result-report.ts +0 -77
package/node_modules/@nklisch/pi-plugins/node_modules/@nklisch/pi-subagents/src/session/query.ts
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, bounded projection and query model for child-session transcripts.
|
|
3
|
+
*
|
|
4
|
+
* The projection deliberately has only two entry families. Tool results enrich
|
|
5
|
+
* their matching call instead of becoming a second row, which keeps the tool
|
|
6
|
+
* and the transcript UI consistent when a call is still pending or finishes
|
|
7
|
+
* out of order.
|
|
8
|
+
*/
|
|
9
|
+
import type { SessionMessage } from "#src/types";
|
|
10
|
+
|
|
11
|
+
export type SessionQueryKind = "all" | "messages" | "tool_calls" | "tool_results";
|
|
12
|
+
export type SessionQueryOrder = "newest" | "oldest";
|
|
13
|
+
/** Overlay family filter; tool query parameters continue to use `kind`. */
|
|
14
|
+
export type SessionQueryEntryFamily = "all" | "tools";
|
|
15
|
+
export type ToolCallState = "pending" | "completed" | "failed";
|
|
16
|
+
|
|
17
|
+
export const DEFAULT_QUERY_LIMIT = 20;
|
|
18
|
+
export const MIN_QUERY_LIMIT = 1;
|
|
19
|
+
export const MAX_QUERY_LIMIT = 50;
|
|
20
|
+
|
|
21
|
+
/** Caps are character caps: transcript content is text, not a byte protocol. */
|
|
22
|
+
export const QUERY_SEARCH_FIELD_CAP = 8_192;
|
|
23
|
+
export const MESSAGE_EXCERPT_CAP = 2_000;
|
|
24
|
+
export const TOOL_ARGUMENTS_EXCERPT_CAP = 2_000;
|
|
25
|
+
export const TOOL_RESULT_EXCERPT_CAP = 4_000;
|
|
26
|
+
|
|
27
|
+
export interface QueryTruncation {
|
|
28
|
+
/** Search fields capped before matching, by field name. */
|
|
29
|
+
readonly searchFields: readonly string[];
|
|
30
|
+
/** Returned excerpts capped for the caller, by field name. */
|
|
31
|
+
readonly excerpts: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface QueryEntryBase {
|
|
35
|
+
/** Stable only within this projection; it is never written to a session. */
|
|
36
|
+
readonly id: string;
|
|
37
|
+
/** Position of the originating SessionMessage in the source snapshot. */
|
|
38
|
+
readonly sourceIndex: number;
|
|
39
|
+
readonly timestamp?: number;
|
|
40
|
+
readonly truncation: QueryTruncation;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface MessageQueryEntry extends QueryEntryBase {
|
|
44
|
+
readonly kind: "message";
|
|
45
|
+
readonly role: "user" | "assistant";
|
|
46
|
+
/** Bounded visible message text. */
|
|
47
|
+
readonly text: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ToolCallQueryEntry extends QueryEntryBase {
|
|
51
|
+
readonly kind: "tool_call";
|
|
52
|
+
/** Undefined for a native bash-execution message, which has no call id. */
|
|
53
|
+
readonly toolCallId?: string;
|
|
54
|
+
readonly toolName: string;
|
|
55
|
+
/** Bounded JSON arguments (or the command for a bash execution). */
|
|
56
|
+
readonly arguments: string;
|
|
57
|
+
readonly state: ToolCallState;
|
|
58
|
+
/** Bounded result text; undefined means pending/no correlated result, while `""` is a completed empty result. */
|
|
59
|
+
readonly result?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type SessionQueryEntry = MessageQueryEntry | ToolCallQueryEntry;
|
|
63
|
+
|
|
64
|
+
export interface SessionQueryOptions {
|
|
65
|
+
readonly query?: string;
|
|
66
|
+
readonly kind?: SessionQueryKind;
|
|
67
|
+
readonly order?: SessionQueryOrder;
|
|
68
|
+
readonly limit?: number;
|
|
69
|
+
/** Restrict the result to the overlay's tool family without changing search fields. */
|
|
70
|
+
readonly entryFamily?: SessionQueryEntryFamily;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface SessionQueryResult {
|
|
74
|
+
readonly entries: readonly SessionQueryEntry[];
|
|
75
|
+
/** Number of entries matching before the result limit is applied. */
|
|
76
|
+
readonly totalMatches: number;
|
|
77
|
+
readonly omittedCount: number;
|
|
78
|
+
readonly hasMore: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface SearchableEntry {
|
|
82
|
+
entry: SessionQueryEntry;
|
|
83
|
+
searchable: Readonly<Record<string, string>>;
|
|
84
|
+
rawSearchable: Readonly<Record<string, string>>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface BoundedText {
|
|
88
|
+
readonly value: string;
|
|
89
|
+
readonly truncated: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Project raw Pi messages into the stable two-family query algebra. */
|
|
93
|
+
export function projectSessionMessages(messages: readonly SessionMessage[]): readonly SessionQueryEntry[] {
|
|
94
|
+
return projectSearchableMessages(messages).map(({ entry }) => entry);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function projectSearchableMessages(messages: readonly SessionMessage[]): SearchableEntry[] {
|
|
98
|
+
const projected: SearchableEntry[] = [];
|
|
99
|
+
const calls = new Map<string, SearchableEntry>();
|
|
100
|
+
|
|
101
|
+
for (let sourceIndex = 0; sourceIndex < messages.length; sourceIndex++) {
|
|
102
|
+
const message = messages[sourceIndex]!;
|
|
103
|
+
if (message.role === "user" || message.role === "assistant") {
|
|
104
|
+
const text = visibleText(message.content);
|
|
105
|
+
if (text.trim()) {
|
|
106
|
+
const bounded = bound(text, MESSAGE_EXCERPT_CAP);
|
|
107
|
+
const rawSearchable = { text };
|
|
108
|
+
const searchable = searchFields(rawSearchable);
|
|
109
|
+
projected.push({
|
|
110
|
+
entry: {
|
|
111
|
+
kind: "message",
|
|
112
|
+
id: messageIdentity(message.role, sourceIndex, message.timestamp),
|
|
113
|
+
sourceIndex,
|
|
114
|
+
...(timestampOf(message) === undefined ? {} : { timestamp: timestampOf(message) }),
|
|
115
|
+
role: message.role,
|
|
116
|
+
text: bounded.value,
|
|
117
|
+
truncation: truncationFor(rawSearchable, { text: bounded }),
|
|
118
|
+
},
|
|
119
|
+
searchable,
|
|
120
|
+
rawSearchable,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (message.role === "assistant") {
|
|
124
|
+
for (const content of message.content) {
|
|
125
|
+
if (content.type !== "toolCall") continue;
|
|
126
|
+
const callId = content.id;
|
|
127
|
+
const args = stringifyArguments(content.arguments);
|
|
128
|
+
const boundedArgs = bound(args, TOOL_ARGUMENTS_EXCERPT_CAP);
|
|
129
|
+
const rawSearchable = { toolName: content.name, toolCallId: callId, arguments: args };
|
|
130
|
+
const searchable = searchFields(rawSearchable);
|
|
131
|
+
const call: SearchableEntry = {
|
|
132
|
+
entry: {
|
|
133
|
+
kind: "tool_call",
|
|
134
|
+
id: callId,
|
|
135
|
+
sourceIndex,
|
|
136
|
+
...(timestampOf(message) === undefined ? {} : { timestamp: timestampOf(message) }),
|
|
137
|
+
toolCallId: callId,
|
|
138
|
+
toolName: content.name,
|
|
139
|
+
arguments: boundedArgs.value,
|
|
140
|
+
state: "pending",
|
|
141
|
+
truncation: truncationFor(rawSearchable, { arguments: boundedArgs }),
|
|
142
|
+
},
|
|
143
|
+
searchable,
|
|
144
|
+
rawSearchable,
|
|
145
|
+
};
|
|
146
|
+
projected.push(call);
|
|
147
|
+
calls.set(callId, call);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (message.role === "bashExecution") {
|
|
154
|
+
const args = message.command;
|
|
155
|
+
const state: ToolCallState = message.exitCode === undefined && !message.cancelled
|
|
156
|
+
? "pending"
|
|
157
|
+
: message.cancelled || message.exitCode !== 0
|
|
158
|
+
? "failed"
|
|
159
|
+
: "completed";
|
|
160
|
+
// An empty output after completion is still a result. Keeping `""` rather
|
|
161
|
+
// than collapsing it to undefined lets tool_results distinguish a call
|
|
162
|
+
// that completed silently from one that is still pending.
|
|
163
|
+
const result = state === "pending" ? undefined : message.output ?? "";
|
|
164
|
+
const boundedArgs = bound(args, TOOL_ARGUMENTS_EXCERPT_CAP);
|
|
165
|
+
const boundedResult = result === undefined ? undefined : bound(result, TOOL_RESULT_EXCERPT_CAP);
|
|
166
|
+
const rawSearchable = { toolName: "bash", arguments: args, ...(result === undefined ? {} : { result }) };
|
|
167
|
+
const searchable = searchFields(rawSearchable);
|
|
168
|
+
projected.push({
|
|
169
|
+
entry: {
|
|
170
|
+
kind: "tool_call",
|
|
171
|
+
id: `bash:${sourceIndex}`,
|
|
172
|
+
sourceIndex,
|
|
173
|
+
...(timestampOf(message) === undefined ? {} : { timestamp: timestampOf(message) }),
|
|
174
|
+
toolName: "bash",
|
|
175
|
+
arguments: boundedArgs.value,
|
|
176
|
+
state,
|
|
177
|
+
...(boundedResult === undefined ? {} : { result: boundedResult.value }),
|
|
178
|
+
truncation: truncationFor(rawSearchable, {
|
|
179
|
+
arguments: boundedArgs,
|
|
180
|
+
...(boundedResult === undefined ? {} : { result: boundedResult }),
|
|
181
|
+
}),
|
|
182
|
+
},
|
|
183
|
+
searchable,
|
|
184
|
+
rawSearchable,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Correlate after projection so out-of-order results work and an orphan
|
|
190
|
+
// result never creates a duplicate row.
|
|
191
|
+
for (const message of messages) {
|
|
192
|
+
if (message.role !== "toolResult") continue;
|
|
193
|
+
const call = calls.get(message.toolCallId);
|
|
194
|
+
if (!call) continue;
|
|
195
|
+
const current = call.entry as ToolCallQueryEntry;
|
|
196
|
+
const resultText = visibleText(message.content);
|
|
197
|
+
const boundedResult = bound(resultText, TOOL_RESULT_EXCERPT_CAP);
|
|
198
|
+
const rawSearchable = { ...call.rawSearchable, result: resultText };
|
|
199
|
+
const searchable = searchFields(rawSearchable);
|
|
200
|
+
const updated: ToolCallQueryEntry = {
|
|
201
|
+
...current,
|
|
202
|
+
state: message.isError ? "failed" : "completed",
|
|
203
|
+
// Preserve an empty but completed result as `""`; undefined means that
|
|
204
|
+
// no correlated result has arrived yet.
|
|
205
|
+
result: boundedResult.value,
|
|
206
|
+
truncation: truncationFor(rawSearchable, {
|
|
207
|
+
arguments: bound(call.rawSearchable.arguments ?? current.arguments, TOOL_ARGUMENTS_EXCERPT_CAP),
|
|
208
|
+
result: boundedResult,
|
|
209
|
+
}),
|
|
210
|
+
};
|
|
211
|
+
call.entry = updated;
|
|
212
|
+
call.searchable = searchable;
|
|
213
|
+
call.rawSearchable = rawSearchable;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return projected;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Query a fresh projection; no derived state is retained between calls. */
|
|
220
|
+
export function querySession(
|
|
221
|
+
messages: readonly SessionMessage[],
|
|
222
|
+
options: SessionQueryOptions = {},
|
|
223
|
+
): SessionQueryResult {
|
|
224
|
+
const limit = normalizeLimit(options.limit);
|
|
225
|
+
const kind = options.kind ?? "all";
|
|
226
|
+
const order = options.order ?? "newest";
|
|
227
|
+
const query = options.query ?? "";
|
|
228
|
+
const needle = query.toLowerCase();
|
|
229
|
+
const projected = projectSearchableMessages(messages);
|
|
230
|
+
const matches = projected
|
|
231
|
+
.filter(({ entry }) => options.entryFamily !== "tools" || entry.kind === "tool_call")
|
|
232
|
+
.filter(({ entry }) => matchesKind(entry, kind))
|
|
233
|
+
.filter(({ searchable }) => matchesQuery(searchable, needle, kind))
|
|
234
|
+
.map(({ entry }) => entry);
|
|
235
|
+
const ordered = order === "newest" ? [...matches].reverse() : matches;
|
|
236
|
+
const entries = ordered.slice(0, limit);
|
|
237
|
+
return {
|
|
238
|
+
entries,
|
|
239
|
+
totalMatches: matches.length,
|
|
240
|
+
omittedCount: Math.max(0, matches.length - entries.length),
|
|
241
|
+
hasMore: matches.length > entries.length,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function normalizeLimit(limit: number | undefined): number {
|
|
246
|
+
const value = limit ?? DEFAULT_QUERY_LIMIT;
|
|
247
|
+
if (!Number.isInteger(value) || value < MIN_QUERY_LIMIT || value > MAX_QUERY_LIMIT) {
|
|
248
|
+
throw new RangeError(`limit must be an integer from ${MIN_QUERY_LIMIT} to ${MAX_QUERY_LIMIT}`);
|
|
249
|
+
}
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function matchesKind(entry: SessionQueryEntry, kind: SessionQueryKind): boolean {
|
|
254
|
+
if (kind === "all") return true;
|
|
255
|
+
if (kind === "messages") return entry.kind === "message";
|
|
256
|
+
if (kind === "tool_calls") return entry.kind === "tool_call";
|
|
257
|
+
return entry.kind === "tool_call" && entry.result !== undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function matchesQuery(
|
|
261
|
+
searchable: Readonly<Record<string, string>>,
|
|
262
|
+
needle: string,
|
|
263
|
+
kind: SessionQueryKind,
|
|
264
|
+
): boolean {
|
|
265
|
+
if (!needle) return true;
|
|
266
|
+
if (kind === "messages") return searchable.text?.toLowerCase().includes(needle) ?? false;
|
|
267
|
+
if (kind === "tool_calls") {
|
|
268
|
+
return [searchable.toolName, searchable.toolCallId, searchable.arguments]
|
|
269
|
+
.filter((value): value is string => value !== undefined)
|
|
270
|
+
.some((value) => value.toLowerCase().includes(needle));
|
|
271
|
+
}
|
|
272
|
+
if (kind === "tool_results") return searchable.result?.toLowerCase().includes(needle) ?? false;
|
|
273
|
+
return Object.values(searchable).some((value) => value.toLowerCase().includes(needle));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function visibleText(content: unknown): string {
|
|
277
|
+
if (typeof content === "string") return content;
|
|
278
|
+
if (!Array.isArray(content)) return "";
|
|
279
|
+
return content
|
|
280
|
+
.filter((part): part is { type: "text"; text?: string } =>
|
|
281
|
+
typeof part === "object" && part !== null && (part as { type?: unknown }).type === "text",
|
|
282
|
+
)
|
|
283
|
+
.map((part) => part.text ?? "")
|
|
284
|
+
.join("\n");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function stringifyArguments(value: unknown): string {
|
|
288
|
+
if (typeof value === "string") return value;
|
|
289
|
+
try {
|
|
290
|
+
return JSON.stringify(value) ?? "";
|
|
291
|
+
} catch {
|
|
292
|
+
return String(value);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function timestampOf(message: SessionMessage): number | undefined {
|
|
297
|
+
return typeof message.timestamp === "number" ? message.timestamp : undefined;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function messageIdentity(role: "user" | "assistant", sourceIndex: number, timestamp: unknown): string {
|
|
301
|
+
return `message:${role}:${sourceIndex}${typeof timestamp === "number" ? `:${timestamp}` : ""}`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function capForSearch(value: string): string {
|
|
305
|
+
return value.length > QUERY_SEARCH_FIELD_CAP ? value.slice(0, QUERY_SEARCH_FIELD_CAP) : value;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function searchFields(fields: Readonly<Record<string, string>>): Readonly<Record<string, string>> {
|
|
309
|
+
return Object.fromEntries(Object.entries(fields).map(([name, value]) => [name, capForSearch(value)]));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function bound(value: string, cap: number): BoundedText {
|
|
313
|
+
return value.length > cap ? { value: value.slice(0, cap), truncated: true } : { value, truncated: false };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function truncationFor(
|
|
317
|
+
rawSearchable: Readonly<Record<string, string>>,
|
|
318
|
+
excerpts: Readonly<Record<string, BoundedText>>,
|
|
319
|
+
): QueryTruncation {
|
|
320
|
+
const searchFields = Object.entries(rawSearchable)
|
|
321
|
+
.filter(([, value]) => value.length > QUERY_SEARCH_FIELD_CAP)
|
|
322
|
+
.map(([name]) => name);
|
|
323
|
+
const excerptFields = Object.entries(excerpts)
|
|
324
|
+
.filter(([, value]) => value.truncated)
|
|
325
|
+
.map(([name]) => name);
|
|
326
|
+
return { searchFields, excerpts: excerptFields };
|
|
327
|
+
}
|
|
@@ -10,7 +10,7 @@ export interface SubagentsSettings {
|
|
|
10
10
|
/**
|
|
11
11
|
* 0 = unlimited — the extension's single source of truth for that convention:
|
|
12
12
|
* `normalizeMaxTurns()` in turn-limits.ts treats 0 → `undefined`, and the
|
|
13
|
-
* `/
|
|
13
|
+
* `/subagents:settings` input prompt explicitly says "0 = unlimited".
|
|
14
14
|
*/
|
|
15
15
|
defaultMaxTurns?: number;
|
|
16
16
|
graceTurns?: number;
|