@oh-my-pi/pi-coding-agent 15.12.0 → 15.12.1
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 +10 -0
- package/dist/cli.js +37 -37
- package/dist/types/config/settings-schema.d.ts +12 -1
- package/dist/types/tools/tool-result.d.ts +2 -0
- package/package.json +12 -12
- package/src/config/settings-schema.ts +14 -1
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/lsp/index.ts +11 -0
- package/src/session/agent-session.ts +22 -12
- package/src/tools/ast-grep.ts +3 -1
- package/src/tools/find.ts +3 -1
- package/src/tools/gh.ts +20 -6
- package/src/tools/irc.ts +4 -0
- package/src/tools/job.ts +12 -4
- package/src/tools/memory-recall.ts +2 -0
- package/src/tools/search.ts +3 -1
- package/src/tools/tool-result.ts +8 -0
package/src/lsp/index.ts
CHANGED
|
@@ -2132,6 +2132,11 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
2132
2132
|
const position = { line: resolvedLine - 1, character: resolvedCharacter };
|
|
2133
2133
|
|
|
2134
2134
|
let output: string;
|
|
2135
|
+
// Set on bare empty-lookup outcomes (no definition/references/…): the
|
|
2136
|
+
// result carries no information once consumed, so compaction may elide
|
|
2137
|
+
// it. Clean diagnostics runs are NOT useless — they are verification
|
|
2138
|
+
// evidence.
|
|
2139
|
+
let useless = false;
|
|
2135
2140
|
|
|
2136
2141
|
if (needsProjectIndex && !isRustAnalyzerServer) {
|
|
2137
2142
|
await waitForProjectLoaded(client, signal);
|
|
@@ -2157,6 +2162,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
2157
2162
|
|
|
2158
2163
|
if (locations.length === 0) {
|
|
2159
2164
|
output = "No definition found";
|
|
2165
|
+
useless = true;
|
|
2160
2166
|
} else {
|
|
2161
2167
|
const lines = await Promise.all(
|
|
2162
2168
|
locations.map(location => formatLocationWithContext(location, this.session.cwd)),
|
|
@@ -2181,6 +2187,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
2181
2187
|
|
|
2182
2188
|
if (locations.length === 0) {
|
|
2183
2189
|
output = "No type definition found";
|
|
2190
|
+
useless = true;
|
|
2184
2191
|
} else {
|
|
2185
2192
|
const lines = await Promise.all(
|
|
2186
2193
|
locations.map(location => formatLocationWithContext(location, this.session.cwd)),
|
|
@@ -2205,6 +2212,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
2205
2212
|
|
|
2206
2213
|
if (locations.length === 0) {
|
|
2207
2214
|
output = "No implementation found";
|
|
2215
|
+
useless = true;
|
|
2208
2216
|
} else {
|
|
2209
2217
|
const lines = await Promise.all(
|
|
2210
2218
|
locations.map(location => formatLocationWithContext(location, this.session.cwd)),
|
|
@@ -2242,6 +2250,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
2242
2250
|
|
|
2243
2251
|
if (!result || result.length === 0) {
|
|
2244
2252
|
output = "No references found";
|
|
2253
|
+
useless = true;
|
|
2245
2254
|
} else {
|
|
2246
2255
|
const contextualReferences = result.slice(0, REFERENCE_CONTEXT_LIMIT);
|
|
2247
2256
|
const plainReferences = result.slice(REFERENCE_CONTEXT_LIMIT);
|
|
@@ -2381,6 +2390,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
2381
2390
|
|
|
2382
2391
|
if (!result || result.length === 0) {
|
|
2383
2392
|
output = "No symbols found";
|
|
2393
|
+
useless = true;
|
|
2384
2394
|
} else {
|
|
2385
2395
|
const relPath = formatPathRelativeToCwd(targetFile, this.session.cwd);
|
|
2386
2396
|
if ("selectionRange" in result[0]) {
|
|
@@ -2444,6 +2454,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
2444
2454
|
return {
|
|
2445
2455
|
content: [{ type: "text", text: output }],
|
|
2446
2456
|
details: { serverName, action, success: true, request: params },
|
|
2457
|
+
...(useless ? { useless: true } : {}),
|
|
2447
2458
|
};
|
|
2448
2459
|
} catch (err) {
|
|
2449
2460
|
if (err instanceof ToolError) throw err;
|
|
@@ -6155,7 +6155,13 @@ export class AgentSession {
|
|
|
6155
6155
|
|
|
6156
6156
|
async #pruneToolOutputs(): Promise<{ prunedCount: number; tokensSaved: number } | undefined> {
|
|
6157
6157
|
const branchEntries = this.sessionManager.getBranch();
|
|
6158
|
-
const result = pruneToolOutputs(
|
|
6158
|
+
const result = pruneToolOutputs(
|
|
6159
|
+
branchEntries,
|
|
6160
|
+
this.#withPlanProtection({
|
|
6161
|
+
...DEFAULT_PRUNE_CONFIG,
|
|
6162
|
+
pruneUseless: this.settings.getGroup("compaction").dropUseless,
|
|
6163
|
+
}),
|
|
6164
|
+
);
|
|
6159
6165
|
if (result.prunedCount === 0) {
|
|
6160
6166
|
return undefined;
|
|
6161
6167
|
}
|
|
@@ -6169,19 +6175,22 @@ export class AgentSession {
|
|
|
6169
6175
|
}
|
|
6170
6176
|
|
|
6171
6177
|
/**
|
|
6172
|
-
* Per-turn
|
|
6173
|
-
* the same file has made stale
|
|
6174
|
-
*
|
|
6175
|
-
*
|
|
6176
|
-
*
|
|
6178
|
+
* Per-turn stale-result pass: prune older `read` results that a newer read
|
|
6179
|
+
* of the same file has made stale, plus results their tool flagged
|
|
6180
|
+
* contextually useless. Cache-aware (only fires when the suffix after a
|
|
6181
|
+
* candidate is small or the session has been idle long enough that the
|
|
6182
|
+
* provider prompt cache is cold), so it is cheap to run every turn. Gated
|
|
6183
|
+
* on the `compaction.supersedeReads` and `compaction.dropUseless` settings.
|
|
6177
6184
|
*/
|
|
6178
|
-
async #
|
|
6179
|
-
|
|
6185
|
+
async #pruneStaleToolResults(): Promise<{ prunedCount: number; tokensSaved: number } | undefined> {
|
|
6186
|
+
const { supersedeReads, dropUseless } = this.settings.getGroup("compaction");
|
|
6187
|
+
if (!supersedeReads && !dropUseless) return undefined;
|
|
6180
6188
|
const branchEntries = this.sessionManager.getBranch();
|
|
6181
6189
|
const result = pruneSupersededToolResults(
|
|
6182
6190
|
branchEntries,
|
|
6183
6191
|
this.#withPlanProtection({
|
|
6184
|
-
supersedeKey: readToolSupersedeKey,
|
|
6192
|
+
supersedeKey: supersedeReads ? readToolSupersedeKey : undefined,
|
|
6193
|
+
pruneUseless: dropUseless,
|
|
6185
6194
|
protectedTools: [...DEFAULT_PRUNE_CONFIG.protectedTools],
|
|
6186
6195
|
}),
|
|
6187
6196
|
);
|
|
@@ -6861,9 +6870,10 @@ export class AgentSession {
|
|
|
6861
6870
|
return false;
|
|
6862
6871
|
}
|
|
6863
6872
|
|
|
6864
|
-
//
|
|
6865
|
-
// (bails when no candidate) and independent of the compaction
|
|
6866
|
-
|
|
6873
|
+
// Stale-result pass runs every turn, before any threshold gating: it is
|
|
6874
|
+
// cheap (bails when no candidate) and independent of the compaction
|
|
6875
|
+
// setting.
|
|
6876
|
+
const supersedeResult = await this.#pruneStaleToolResults();
|
|
6867
6877
|
|
|
6868
6878
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
6869
6879
|
if (!compactionSettings.enabled || compactionSettings.strategy === "off") return false;
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -221,7 +221,9 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
221
221
|
const parseMessage = cappedParseErrors.length
|
|
222
222
|
? `\n${formatParseErrors(cappedParseErrors, parseErrorsTotal).join("\n")}`
|
|
223
223
|
: "";
|
|
224
|
-
|
|
224
|
+
// Zero matches is useless even with parse issues: the follow-up
|
|
225
|
+
// call has already corrected course by the time compaction runs.
|
|
226
|
+
return toolResult(baseDetails).text(`${noMatchMessage}${parseMessage}`).useless().done();
|
|
225
227
|
}
|
|
226
228
|
|
|
227
229
|
const useHashLines = resolveFileDisplayMode(this.session).hashLines;
|
package/src/tools/find.ts
CHANGED
|
@@ -239,7 +239,9 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
239
239
|
const parts = ["No files found matching pattern"];
|
|
240
240
|
if (notice) parts.push(notice);
|
|
241
241
|
if (missingPathsNote) parts.push(missingPathsNote);
|
|
242
|
-
|
|
242
|
+
// Zero results is useless regardless of notices: the follow-up
|
|
243
|
+
// call has already corrected course by the time compaction runs.
|
|
244
|
+
return toolResult(details).text(parts.join("\n")).useless().done();
|
|
243
245
|
}
|
|
244
246
|
|
|
245
247
|
const listLimit = applyListLimit(files, { limit: effectiveLimit });
|
package/src/tools/gh.ts
CHANGED
|
@@ -2452,7 +2452,7 @@ function buildTextResult(
|
|
|
2452
2452
|
text: string,
|
|
2453
2453
|
sourceUrl?: string,
|
|
2454
2454
|
details?: GhToolDetails,
|
|
2455
|
-
options?: { artifactId?: string; artifactLabel?: string },
|
|
2455
|
+
options?: { artifactId?: string; artifactLabel?: string; useless?: boolean },
|
|
2456
2456
|
): AgentToolResult<GhToolDetails> {
|
|
2457
2457
|
const builder = toolResult<GhToolDetails>(details).text(
|
|
2458
2458
|
appendArtifactReference(text, options?.artifactId, options?.artifactLabel ?? "Saved artifact"),
|
|
@@ -2460,6 +2460,9 @@ function buildTextResult(
|
|
|
2460
2460
|
if (sourceUrl) {
|
|
2461
2461
|
builder.sourceUrl(sourceUrl);
|
|
2462
2462
|
}
|
|
2463
|
+
if (options?.useless) {
|
|
2464
|
+
builder.useless();
|
|
2465
|
+
}
|
|
2463
2466
|
return builder.done();
|
|
2464
2467
|
}
|
|
2465
2468
|
|
|
@@ -3405,7 +3408,9 @@ async function executeSearchIssues(
|
|
|
3405
3408
|
|
|
3406
3409
|
const response = await git.github.json<GhApiSearchResponse<GhApiSearchIssueItem>>(session.cwd, args, signal);
|
|
3407
3410
|
const items = (response.items ?? []).map(apiIssueToSearchResult);
|
|
3408
|
-
return buildTextResult(formatSearchResults("issues", displayQuery, repo, items)
|
|
3411
|
+
return buildTextResult(formatSearchResults("issues", displayQuery, repo, items), undefined, undefined, {
|
|
3412
|
+
useless: items.length === 0,
|
|
3413
|
+
});
|
|
3409
3414
|
}
|
|
3410
3415
|
|
|
3411
3416
|
async function executeSearchPrs(
|
|
@@ -3423,7 +3428,9 @@ async function executeSearchPrs(
|
|
|
3423
3428
|
|
|
3424
3429
|
const response = await git.github.json<GhApiSearchResponse<GhApiSearchIssueItem>>(session.cwd, args, signal);
|
|
3425
3430
|
const items = (response.items ?? []).map(apiIssueToSearchResult);
|
|
3426
|
-
return buildTextResult(formatSearchResults("pull requests", displayQuery, repo, items)
|
|
3431
|
+
return buildTextResult(formatSearchResults("pull requests", displayQuery, repo, items), undefined, undefined, {
|
|
3432
|
+
useless: items.length === 0,
|
|
3433
|
+
});
|
|
3427
3434
|
}
|
|
3428
3435
|
|
|
3429
3436
|
async function executeSearchCode(
|
|
@@ -3442,7 +3449,9 @@ async function executeSearchCode(
|
|
|
3442
3449
|
|
|
3443
3450
|
const response = await git.github.json<GhApiSearchResponse<GhApiSearchCodeItem>>(session.cwd, args, signal);
|
|
3444
3451
|
const items = (response.items ?? []).map(apiCodeToSearchResult);
|
|
3445
|
-
return buildTextResult(formatSearchCodeResults(query, repo, items)
|
|
3452
|
+
return buildTextResult(formatSearchCodeResults(query, repo, items), undefined, undefined, {
|
|
3453
|
+
useless: items.length === 0,
|
|
3454
|
+
});
|
|
3446
3455
|
}
|
|
3447
3456
|
|
|
3448
3457
|
async function executeSearchCommits(
|
|
@@ -3460,7 +3469,9 @@ async function executeSearchCommits(
|
|
|
3460
3469
|
|
|
3461
3470
|
const response = await git.github.json<GhApiSearchResponse<GhApiSearchCommitItem>>(session.cwd, args, signal);
|
|
3462
3471
|
const items = (response.items ?? []).map(apiCommitToSearchResult);
|
|
3463
|
-
return buildTextResult(formatSearchCommitsResults(displayQuery, repo, items)
|
|
3472
|
+
return buildTextResult(formatSearchCommitsResults(displayQuery, repo, items), undefined, undefined, {
|
|
3473
|
+
useless: items.length === 0,
|
|
3474
|
+
});
|
|
3464
3475
|
}
|
|
3465
3476
|
|
|
3466
3477
|
async function executeSearchRepos(
|
|
@@ -3476,7 +3487,9 @@ async function executeSearchRepos(
|
|
|
3476
3487
|
|
|
3477
3488
|
const response = await git.github.json<GhApiSearchResponse<GhApiSearchRepoItem>>(session.cwd, args, signal);
|
|
3478
3489
|
const items = (response.items ?? []).map(apiRepoToSearchResult);
|
|
3479
|
-
return buildTextResult(formatSearchReposResults(query, items)
|
|
3490
|
+
return buildTextResult(formatSearchReposResults(query, items), undefined, undefined, {
|
|
3491
|
+
useless: items.length === 0,
|
|
3492
|
+
});
|
|
3480
3493
|
}
|
|
3481
3494
|
|
|
3482
3495
|
async function executeRunWatch(
|
|
@@ -3751,6 +3764,7 @@ async function executeRunWatch(
|
|
|
3751
3764
|
`No workflow runs found for ${repo}@${formatShortSha(headSha) ?? headSha} after ${elapsedSec}s (${pollCount} polls). The commit may not trigger any GitHub Actions workflows, or Actions may be disabled for this repository. Pass \`run\` to watch a specific run.`,
|
|
3752
3765
|
undefined,
|
|
3753
3766
|
buildCommitRunWatchDetails(repo, headSha, branch, runs, { state: "completed", pollCount }),
|
|
3767
|
+
{ useless: true },
|
|
3754
3768
|
);
|
|
3755
3769
|
}
|
|
3756
3770
|
await scheduler.wait(currentIntervalSeconds() * 1000, { signal });
|
package/src/tools/irc.ts
CHANGED
|
@@ -310,6 +310,8 @@ export class IrcTool implements AgentTool<typeof ircSchema, IrcDetails> {
|
|
|
310
310
|
return {
|
|
311
311
|
content: [{ type: "text", text: `No message${filterNote} within ${formatDuration(timeoutMs)}.` }],
|
|
312
312
|
details: { op: "wait", from: senderId, waited: null },
|
|
313
|
+
// A clean wait timeout carries no information once consumed.
|
|
314
|
+
useless: true,
|
|
313
315
|
};
|
|
314
316
|
}
|
|
315
317
|
return {
|
|
@@ -324,6 +326,8 @@ export class IrcTool implements AgentTool<typeof ircSchema, IrcDetails> {
|
|
|
324
326
|
return {
|
|
325
327
|
content: [{ type: "text", text: "Inbox empty." }],
|
|
326
328
|
details: { op: "inbox", from: senderId, inbox: [] },
|
|
329
|
+
// An empty inbox drain carries no information once consumed.
|
|
330
|
+
useless: true,
|
|
327
331
|
};
|
|
328
332
|
}
|
|
329
333
|
const header = params.peek ? `${messages.length} unread message(s):` : `${messages.length} message(s):`;
|
package/src/tools/job.ts
CHANGED
|
@@ -171,6 +171,9 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
|
|
171
171
|
return {
|
|
172
172
|
content: [{ type: "text", text: message }],
|
|
173
173
|
details: { jobs: [] },
|
|
174
|
+
// Nothing found / nothing to wait for is noise once consumed —
|
|
175
|
+
// the follow-up call has already corrected course.
|
|
176
|
+
useless: true,
|
|
174
177
|
};
|
|
175
178
|
}
|
|
176
179
|
|
|
@@ -334,12 +337,17 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
|
|
334
337
|
}
|
|
335
338
|
}
|
|
336
339
|
|
|
340
|
+
const details: JobToolDetails = {
|
|
341
|
+
jobs: jobResults,
|
|
342
|
+
...(cancelOutcomes.length ? { cancelled: cancelOutcomes.map(({ id, status }) => ({ id, status })) } : {}),
|
|
343
|
+
};
|
|
337
344
|
return {
|
|
338
345
|
content: [{ type: "text", text: lines.join("\n").trimEnd() }],
|
|
339
|
-
details
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
346
|
+
details,
|
|
347
|
+
// A poll where everything is still running carries no new information
|
|
348
|
+
// once a later poll exists — same predicate the TUI uses to displace
|
|
349
|
+
// stale waiting frames.
|
|
350
|
+
...(isWaitingPollDetails(details) ? { useless: true } : {}),
|
|
343
351
|
};
|
|
344
352
|
}
|
|
345
353
|
}
|
|
@@ -43,6 +43,7 @@ export class MemoryRecallTool implements AgentTool<typeof memoryRecallSchema> {
|
|
|
43
43
|
return {
|
|
44
44
|
content: [{ type: "text", text: "No relevant memories found." }],
|
|
45
45
|
details: {},
|
|
46
|
+
useless: true,
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
const formatted = state.formatScopedRecallWithIds(results);
|
|
@@ -79,6 +80,7 @@ export class MemoryRecallTool implements AgentTool<typeof memoryRecallSchema> {
|
|
|
79
80
|
return {
|
|
80
81
|
content: [{ type: "text", text: "No relevant memories found." }],
|
|
81
82
|
details: {},
|
|
83
|
+
useless: true,
|
|
82
84
|
};
|
|
83
85
|
}
|
|
84
86
|
const formatted = formatMemories(results);
|
package/src/tools/search.ts
CHANGED
|
@@ -1124,7 +1124,9 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
|
|
|
1124
1124
|
? `No more results (${totalFilesLabel} files total; skip=${normalizedSkip} is past the end)`
|
|
1125
1125
|
: "No matches found";
|
|
1126
1126
|
const text = warningNote ? `${noMatchText}\n${warningNote}` : noMatchText;
|
|
1127
|
-
|
|
1127
|
+
// Zero matches is useless regardless of warnings: by the time
|
|
1128
|
+
// compaction runs, the follow-up call has already corrected course.
|
|
1129
|
+
return toolResult(details).text(text).useless().done();
|
|
1128
1130
|
}
|
|
1129
1131
|
const outputLines: string[] = [];
|
|
1130
1132
|
let linesTruncated = false;
|
package/src/tools/tool-result.ts
CHANGED
|
@@ -13,6 +13,7 @@ export class ToolResultBuilder<TDetails extends DetailsWithMeta> {
|
|
|
13
13
|
#meta = outputMeta();
|
|
14
14
|
#content: ToolContent = [];
|
|
15
15
|
#isError = false;
|
|
16
|
+
#useless = false;
|
|
16
17
|
|
|
17
18
|
constructor(details?: TDetails) {
|
|
18
19
|
this.#details = details ?? ({} as TDetails);
|
|
@@ -74,6 +75,12 @@ export class ToolResultBuilder<TDetails extends DetailsWithMeta> {
|
|
|
74
75
|
return this;
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
/** Marks the result contextually useless — compaction may elide it once consumed. */
|
|
79
|
+
useless(value = true): this {
|
|
80
|
+
this.#useless = value;
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
|
|
77
84
|
done(): AgentToolResult<TDetails> {
|
|
78
85
|
const meta = this.#meta.get();
|
|
79
86
|
if (meta) {
|
|
@@ -85,6 +92,7 @@ export class ToolResultBuilder<TDetails extends DetailsWithMeta> {
|
|
|
85
92
|
content: this.#content,
|
|
86
93
|
details: hasDetails ? this.#details : undefined,
|
|
87
94
|
...(this.#isError ? { isError: true } : {}),
|
|
95
|
+
...(this.#useless && !this.#isError ? { useless: true } : {}),
|
|
88
96
|
};
|
|
89
97
|
}
|
|
90
98
|
}
|