@oh-my-pi/pi-coding-agent 18.12.4 → 18.12.5
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 +14 -0
- package/dist/{CHANGELOG-hbjbbw09.md → CHANGELOG-1fn0b39a.md} +14 -0
- package/dist/cli.js +2909 -2908
- package/dist/{models.json-n9fhhcz4.zst → models.json-sp6nans3.zst} +0 -0
- package/dist/{rules.json-7psps2xd.zst → rules.json-5h0sq8vp.zst} +0 -0
- package/dist/types/tools/conflict-detect.d.ts +18 -10
- package/package.json +12 -12
- package/src/cli/update-cli.ts +10 -5
- package/src/discovery/builtin-rules/ts-import-type.md +1 -1
- package/src/discovery/builtin-rules/ts-set-map.md +1 -1
- package/src/session/settings.ts +3 -2
- package/src/task/index.ts +13 -2
- package/src/tools/conflict-detect.ts +28 -12
- package/src/tools/read.ts +12 -7
|
Binary file
|
|
Binary file
|
|
@@ -30,20 +30,28 @@ import type { ToolSession } from "./index.js";
|
|
|
30
30
|
* the read.
|
|
31
31
|
*/
|
|
32
32
|
export declare function scanConflictLines(lines: readonly string[], firstLineNumber: number): ConflictBlock[];
|
|
33
|
+
/** Result of {@link scanFileForConflicts}. */
|
|
34
|
+
export interface ConflictFileScan {
|
|
35
|
+
blocks: ConflictBlock[];
|
|
36
|
+
/** First line the byte cap left unscanned (possibly partial); unset when the whole file was scanned. */
|
|
37
|
+
truncatedAtLine?: number;
|
|
38
|
+
}
|
|
33
39
|
/**
|
|
34
40
|
* Scan a whole file for unresolved conflict blocks.
|
|
35
41
|
*
|
|
36
42
|
* Reads at most `maxBytes` (default 10 MB) so this stays cheap on
|
|
37
|
-
* pathological files. Files truncated by the cap report
|
|
38
|
-
*
|
|
39
|
-
*
|
|
43
|
+
* pathological files. Files truncated by the cap report the line the scan
|
|
44
|
+
* stopped at; only complete blocks within the scanned prefix are returned,
|
|
45
|
+
* so trailing partial markers never invent fake blocks.
|
|
40
46
|
*/
|
|
41
47
|
export declare function scanFileForConflicts(absolutePath: string, options?: {
|
|
42
48
|
maxBytes?: number;
|
|
43
|
-
}): Promise<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
}): Promise<ConflictFileScan>;
|
|
50
|
+
/**
|
|
51
|
+
* Note for a scan the byte cap stopped at `line`: `:conflicts` cannot list or
|
|
52
|
+
* register blocks from there on, but a ranged read that shows them registers them.
|
|
53
|
+
*/
|
|
54
|
+
export declare function formatScanCapNote(displayPath: string, line: number): string;
|
|
47
55
|
/** Per-session log of conflict regions surfaced by `read`. */
|
|
48
56
|
export declare class ConflictHistory {
|
|
49
57
|
#private;
|
|
@@ -180,8 +188,8 @@ export interface FormatConflictWarningOptions {
|
|
|
180
188
|
totalInFile?: number;
|
|
181
189
|
/** Display path used inside the `:conflicts` hint. */
|
|
182
190
|
displayPath?: string;
|
|
183
|
-
/**
|
|
184
|
-
|
|
191
|
+
/** Line where the underlying file scan hit its byte cap, if it did. */
|
|
192
|
+
truncatedAtLine?: number;
|
|
185
193
|
}
|
|
186
194
|
export declare function formatConflictWarning(entries: readonly ConflictEntry[], options?: FormatConflictWarningOptions): string;
|
|
187
195
|
/**
|
|
@@ -191,5 +199,5 @@ export declare function formatConflictWarning(entries: readonly ConflictEntry[],
|
|
|
191
199
|
*/
|
|
192
200
|
export declare function formatConflictSummary(entries: readonly ConflictEntry[], options?: {
|
|
193
201
|
displayPath: string;
|
|
194
|
-
|
|
202
|
+
truncatedAtLine?: number;
|
|
195
203
|
}): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "18.12.
|
|
3
|
+
"version": "18.12.5",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -543,17 +543,17 @@
|
|
|
543
543
|
},
|
|
544
544
|
"dependencies": {
|
|
545
545
|
"@babel/parser": "^7.29.7",
|
|
546
|
-
"@oh-my-pi/omp-stats": "18.12.
|
|
547
|
-
"@oh-my-pi/omptype": "18.12.
|
|
548
|
-
"@oh-my-pi/pi-agent-core": "18.12.
|
|
549
|
-
"@oh-my-pi/pi-ai": "18.12.
|
|
550
|
-
"@oh-my-pi/pi-catalog": "18.12.
|
|
551
|
-
"@oh-my-pi/pi-mnemopi": "18.12.
|
|
552
|
-
"@oh-my-pi/pi-natives": "18.12.
|
|
553
|
-
"@oh-my-pi/pi-tui": "18.12.
|
|
554
|
-
"@oh-my-pi/pi-utils": "18.12.
|
|
555
|
-
"@oh-my-pi/pi-wire": "18.12.
|
|
556
|
-
"@oh-my-pi/snapcompact": "18.12.
|
|
546
|
+
"@oh-my-pi/omp-stats": "18.12.5",
|
|
547
|
+
"@oh-my-pi/omptype": "18.12.5",
|
|
548
|
+
"@oh-my-pi/pi-agent-core": "18.12.5",
|
|
549
|
+
"@oh-my-pi/pi-ai": "18.12.5",
|
|
550
|
+
"@oh-my-pi/pi-catalog": "18.12.5",
|
|
551
|
+
"@oh-my-pi/pi-mnemopi": "18.12.5",
|
|
552
|
+
"@oh-my-pi/pi-natives": "18.12.5",
|
|
553
|
+
"@oh-my-pi/pi-tui": "18.12.5",
|
|
554
|
+
"@oh-my-pi/pi-utils": "18.12.5",
|
|
555
|
+
"@oh-my-pi/pi-wire": "18.12.5",
|
|
556
|
+
"@oh-my-pi/snapcompact": "18.12.5",
|
|
557
557
|
"@opentelemetry/api": "^1.9.1",
|
|
558
558
|
"@opentelemetry/api-logs": "^0.220.0",
|
|
559
559
|
"@opentelemetry/context-async-hooks": "^2.9.0",
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -739,7 +739,6 @@ async function fetchLatestManifest(
|
|
|
739
739
|
}
|
|
740
740
|
return manifest;
|
|
741
741
|
};
|
|
742
|
-
const isPackument = (body: unknown): boolean => isRecord(body) && isRecord(body["dist-tags"]);
|
|
743
742
|
|
|
744
743
|
let response = await get(npmRegistryPackageUrl(registry, pkg, tag));
|
|
745
744
|
let data: unknown;
|
|
@@ -752,10 +751,16 @@ async function fetchLatestManifest(
|
|
|
752
751
|
if (!origin || !(err instanceof SyntaxError)) throw err;
|
|
753
752
|
useFullPackument = true;
|
|
754
753
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
754
|
+
const distTags = isRecord(data) ? data["dist-tags"] : undefined;
|
|
755
|
+
if (isRecord(distTags)) {
|
|
756
|
+
if (typeof distTags[tag] === "string" || !origin) {
|
|
757
|
+
// Some registries answer the dist-tag shortcut with a full packument.
|
|
758
|
+
data = fromPackument(data);
|
|
759
|
+
} else {
|
|
760
|
+
// Artifactory can add empty packument fields to a tagged manifest;
|
|
761
|
+
// its full packument may select a different, installable version.
|
|
762
|
+
useFullPackument = true;
|
|
763
|
+
}
|
|
759
764
|
} else if (origin && !(isRecord(data) && typeof data.version === "string")) {
|
|
760
765
|
useFullPackument = true;
|
|
761
766
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: "Use `import type`, not `import('pkg').Type` in type positions"
|
|
3
3
|
condition:
|
|
4
4
|
- "\\btypeof\\s+import\\("
|
|
5
|
-
- "\\bimport\\((?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:\"[^\"\\n]*\"|'[^'\\n]*'|\\x60[^\\x60]*\\x60)(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:,[^()]*)?\\)\\s*(?:\\[\\s*[\"']|\\.\\s*(?!(?:then|catch|finally)\\
|
|
5
|
+
- "\\bimport\\((?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:\"[^\"\\n]*\"|'[^'\\n]*'|\\x60[^\\x60]*\\x60)(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:,[^()]*)?\\)\\s*(?:\\[\\s*[\"']|\\.\\s*(?!(?:then|catch|finally)(?![\\w$\\u0080-\\uffff]))[A-Za-z_$\\u0080-\\uffff][\\w$\\u0080-\\uffff]*(?=[^\\w$\\u0080-\\uffff]))"
|
|
6
6
|
scope: "tool:edit(*.ts), tool:edit(*.tsx), tool:write(*.ts), tool:write(*.tsx)"
|
|
7
7
|
interruptMode: never
|
|
8
8
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: Prefer Record<K, V> for small static literals; use Set/Map for anything dynamic
|
|
3
3
|
condition:
|
|
4
4
|
- "\\bnew\\s+Set\\s*(?:<(?:[^<>;()=]|=>?|<[^<>;]*>|\\([^()]*\\))*>)?\\s*\\((?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*\\[(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:\"(?:[^\"\\\\\\n]|\\\\.)*\"|'(?:[^'\\\\\\n]|\\\\.)*'|\\x60(?:[^\\x60$\\\\]|\\\\[\\s\\S]|\\$(?!\\{))*\\x60)(?:(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*,(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:\"(?:[^\"\\\\\\n]|\\\\.)*\"|'(?:[^'\\\\\\n]|\\\\.)*'|\\x60(?:[^\\x60$\\\\]|\\\\[\\s\\S]|\\$(?!\\{))*\\x60))*(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*,?(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*\\]"
|
|
5
|
-
- "\\bnew\\s+Map\\s*(?:<(?:[^<>;()=]|=>?|<[^<>;]*>|\\([^()]*\\))*>)?\\s*\\((?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*\\[(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*\\[(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:\"(?:[^\"\\\\\\n]|\\\\.)*\"|'(?:[^'\\\\\\n]|\\\\.)*'|\\x60(?:[^\\x60$\\\\]|\\\\[\\s\\S]|\\$(?!\\{))*\\x60)(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*,(?:[^\\[\\]\"'\\x60]|\"(?:[^\"\\\\\\n]|\\\\.)*\"|'(?:[^'\\\\\\n]|\\\\.)*'|\\x60(?:[^\\x60\\\\]|\\\\[\\s\\S])*\\x60|\\[[^\\[\\]]*\\])*\\](?:(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*,(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*\\[(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:\"(?:[^\"\\\\\\n]|\\\\.)*\"|'(?:[^'\\\\\\n]|\\\\.)*'|\\x60(?:[^\\x60$\\\\]|\\\\[\\s\\S]|\\$(?!\\{))*\\x60)(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*,(?:[^\\[\\]\"'\\x60]|\"(?:[^\"\\\\\\n]|\\\\.)*\"|'(?:[^'\\\\\\n]|\\\\.)*'|\\x60(?:[^\\x60\\\\]|\\\\[\\s\\S])*\\x60|\\[[^\\[\\]]*\\])*\\])*(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*,?(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*\\]"
|
|
5
|
+
- "\\bnew\\s+Map(?![^\\n]{1000})\\s*(?:<(?:[^<>;()=]|=>?|<[^<>;]*>|\\([^()]*\\))*>)?\\s*\\((?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*\\[(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*\\[(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:\"(?:[^\"\\\\\\n]|\\\\.)*\"|'(?:[^'\\\\\\n]|\\\\.)*'|\\x60(?:[^\\x60$\\\\]|\\\\[\\s\\S]|\\$(?!\\{))*\\x60)(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*,(?:[^\\[\\]\"'\\x60]|\"(?:[^\"\\\\\\n]|\\\\.)*\"|'(?:[^'\\\\\\n]|\\\\.)*'|\\x60(?:[^\\x60\\\\]|\\\\[\\s\\S])*\\x60|\\[[^\\[\\]]*\\])*\\](?:\\s*as\\s+const\\b)?(?:(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*,(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*\\[(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:\"(?:[^\"\\\\\\n]|\\\\.)*\"|'(?:[^'\\\\\\n]|\\\\.)*'|\\x60(?:[^\\x60$\\\\]|\\\\[\\s\\S]|\\$(?!\\{))*\\x60)(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*,(?:[^\\[\\]\"'\\x60]|\"(?:[^\"\\\\\\n]|\\\\.)*\"|'(?:[^'\\\\\\n]|\\\\.)*'|\\x60(?:[^\\x60\\\\]|\\\\[\\s\\S])*\\x60|\\[[^\\[\\]]*\\])*\\](?:\\s*as\\s+const\\b)?)*(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*,?(?:\\s|//.*|/\\*(?:[^*]|\\*(?!/))*\\*/)*\\]"
|
|
6
6
|
scope: "tool:edit(**/*.{ts,tsx}), tool:write(**/*.{ts,tsx})"
|
|
7
7
|
interruptMode: never
|
|
8
8
|
---
|
package/src/session/settings.ts
CHANGED
|
@@ -1167,8 +1167,9 @@ export const cfgProvidersCacheRetention = register({
|
|
|
1167
1167
|
},
|
|
1168
1168
|
{
|
|
1169
1169
|
value: "long",
|
|
1170
|
-
label: "Long (1h)",
|
|
1171
|
-
description:
|
|
1170
|
+
label: "Long (1h+)",
|
|
1171
|
+
description:
|
|
1172
|
+
"Longer TTL where supported (Anthropic/Bedrock 1h, OpenAI GPT-5.6+ Responses 12h); pricier writes, only warmed during active runs",
|
|
1172
1173
|
},
|
|
1173
1174
|
{ value: "none", label: "Off", description: "Disable prompt caching and cache-affinity routing" },
|
|
1174
1175
|
],
|
package/src/task/index.ts
CHANGED
|
@@ -792,6 +792,17 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
792
792
|
};
|
|
793
793
|
}
|
|
794
794
|
|
|
795
|
+
/**
|
|
796
|
+
* Thinking override for a spawn. `task.enableEffort` gates the caller's
|
|
797
|
+
* `effort` at runtime, not only in the advertised schema: stale transcripts
|
|
798
|
+
* and lenient argument fallbacks still deliver the field, and honoring it
|
|
799
|
+
* would override the agent's configured thinking selector.
|
|
800
|
+
*/
|
|
801
|
+
#effortParams(params: TaskParams): Pick<StructuredSubagentRequest, "effort"> {
|
|
802
|
+
if (params.effort === undefined || !cfgTaskEnableEffort.get(this.session.settings)) return {};
|
|
803
|
+
return { effort: params.effort };
|
|
804
|
+
}
|
|
805
|
+
|
|
795
806
|
#seanceSchemaFields() {
|
|
796
807
|
const spawnPolicy = resolveSpawnPolicy(this.session.getSessionSpawns());
|
|
797
808
|
const planMode = this.session.getPlanModeState?.()?.enabled === true;
|
|
@@ -911,7 +922,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
911
922
|
sourceSession: params.sourceSession,
|
|
912
923
|
model: params.model,
|
|
913
924
|
...this.#resultParams(params),
|
|
914
|
-
...(params
|
|
925
|
+
...this.#effortParams(params),
|
|
915
926
|
...("isolated" in params ? { isolation: { requested: params.isolated } } : {}),
|
|
916
927
|
blockedAgent: this.#blockedAgent,
|
|
917
928
|
enableLsp: (this.session.enableLsp ?? true) && cfgTaskEnableLsp.get(this.session.settings),
|
|
@@ -1738,7 +1749,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
1738
1749
|
sourceSession: params.sourceSession,
|
|
1739
1750
|
model: params.model,
|
|
1740
1751
|
...this.#resultParams(params),
|
|
1741
|
-
...(params
|
|
1752
|
+
...this.#effortParams(params),
|
|
1742
1753
|
solutionSpace: params.solutionSpace,
|
|
1743
1754
|
...(params.tools?.length
|
|
1744
1755
|
? {
|
|
@@ -123,18 +123,25 @@ export function scanConflictLines(lines: readonly string[], firstLineNumber: num
|
|
|
123
123
|
|
|
124
124
|
const SCAN_FILE_DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
|
|
125
125
|
|
|
126
|
+
/** Result of {@link scanFileForConflicts}. */
|
|
127
|
+
export interface ConflictFileScan {
|
|
128
|
+
blocks: ConflictBlock[];
|
|
129
|
+
/** First line the byte cap left unscanned (possibly partial); unset when the whole file was scanned. */
|
|
130
|
+
truncatedAtLine?: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
126
133
|
/**
|
|
127
134
|
* Scan a whole file for unresolved conflict blocks.
|
|
128
135
|
*
|
|
129
136
|
* Reads at most `maxBytes` (default 10 MB) so this stays cheap on
|
|
130
|
-
* pathological files. Files truncated by the cap report
|
|
131
|
-
*
|
|
132
|
-
*
|
|
137
|
+
* pathological files. Files truncated by the cap report the line the scan
|
|
138
|
+
* stopped at; only complete blocks within the scanned prefix are returned,
|
|
139
|
+
* so trailing partial markers never invent fake blocks.
|
|
133
140
|
*/
|
|
134
141
|
export async function scanFileForConflicts(
|
|
135
142
|
absolutePath: string,
|
|
136
143
|
options: { maxBytes?: number } = {},
|
|
137
|
-
): Promise<
|
|
144
|
+
): Promise<ConflictFileScan> {
|
|
138
145
|
const maxBytes = options.maxBytes ?? SCAN_FILE_DEFAULT_MAX_BYTES;
|
|
139
146
|
const file = Bun.file(absolutePath);
|
|
140
147
|
const size = file.size;
|
|
@@ -144,7 +151,16 @@ export async function scanFileForConflicts(
|
|
|
144
151
|
// `split("\n")` over a truncated read may leave a partial last line; the
|
|
145
152
|
// scanner already tolerates an unclosed opener, so no extra trimming.
|
|
146
153
|
const lines = text.split("\n");
|
|
147
|
-
|
|
154
|
+
const blocks = scanConflictLines(lines, 1);
|
|
155
|
+
return truncated ? { blocks, truncatedAtLine: lines.length } : { blocks };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Note for a scan the byte cap stopped at `line`: `:conflicts` cannot list or
|
|
160
|
+
* register blocks from there on, but a ranged read that shows them registers them.
|
|
161
|
+
*/
|
|
162
|
+
export function formatScanCapNote(displayPath: string, line: number): string {
|
|
163
|
+
return `- note: the conflict scan stopped at the byte cap at line ${line}; blocks from there on are not counted or registered here — range-read \`${displayPath}:${line}-\` to show and register them.`;
|
|
148
164
|
}
|
|
149
165
|
|
|
150
166
|
/**
|
|
@@ -593,8 +609,8 @@ export interface FormatConflictWarningOptions {
|
|
|
593
609
|
totalInFile?: number;
|
|
594
610
|
/** Display path used inside the `:conflicts` hint. */
|
|
595
611
|
displayPath?: string;
|
|
596
|
-
/**
|
|
597
|
-
|
|
612
|
+
/** Line where the underlying file scan hit its byte cap, if it did. */
|
|
613
|
+
truncatedAtLine?: number;
|
|
598
614
|
}
|
|
599
615
|
|
|
600
616
|
export function formatConflictWarning(
|
|
@@ -615,8 +631,8 @@ export function formatConflictWarning(
|
|
|
615
631
|
} else {
|
|
616
632
|
out.push(`⚠ ${total} unresolved ${word} detected`);
|
|
617
633
|
}
|
|
618
|
-
if (options.
|
|
619
|
-
out.push(
|
|
634
|
+
if (options.truncatedAtLine !== undefined) {
|
|
635
|
+
out.push(formatScanCapNote(options.displayPath ?? "<file>", options.truncatedAtLine));
|
|
620
636
|
}
|
|
621
637
|
|
|
622
638
|
const oursLabel = pickLabel(entries, e => e.oursLabel);
|
|
@@ -679,14 +695,14 @@ export function formatConflictWarning(
|
|
|
679
695
|
*/
|
|
680
696
|
export function formatConflictSummary(
|
|
681
697
|
entries: readonly ConflictEntry[],
|
|
682
|
-
options: { displayPath: string;
|
|
698
|
+
options: { displayPath: string; truncatedAtLine?: number } = { displayPath: "" },
|
|
683
699
|
): string {
|
|
684
700
|
const lines: string[] = [];
|
|
685
701
|
const total = entries.length;
|
|
686
702
|
const word = total === 1 ? "conflict" : "conflicts";
|
|
687
703
|
lines.push(`⚠ ${total} unresolved ${word} in ${options.displayPath || "<file>"}`);
|
|
688
|
-
if (options.
|
|
689
|
-
lines.push(
|
|
704
|
+
if (options.truncatedAtLine !== undefined) {
|
|
705
|
+
lines.push(formatScanCapNote(options.displayPath || "<file>", options.truncatedAtLine));
|
|
690
706
|
}
|
|
691
707
|
const oursLabel = pickLabel(entries, e => e.oursLabel);
|
|
692
708
|
const theirsLabel = pickLabel(entries, e => e.theirsLabel);
|
package/src/tools/read.ts
CHANGED
|
@@ -77,6 +77,7 @@ import { buildDirectoryTree, type DirectoryTree } from "../workspace-tree";
|
|
|
77
77
|
import {
|
|
78
78
|
formatConflictSummary,
|
|
79
79
|
formatConflictWarning,
|
|
80
|
+
formatScanCapNote,
|
|
80
81
|
getConflictHistory,
|
|
81
82
|
recoverConflictUriPrefix,
|
|
82
83
|
scanConflictLines,
|
|
@@ -2760,18 +2761,18 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2760
2761
|
// Cheap full-file scan only when the window already showed
|
|
2761
2762
|
// at least one conflict — otherwise pay nothing on clean files.
|
|
2762
2763
|
let totalInFile = entries.length;
|
|
2763
|
-
let
|
|
2764
|
+
let truncatedAtLine: number | undefined;
|
|
2764
2765
|
try {
|
|
2765
2766
|
const fileScan = await scanFileForConflicts(absolutePath);
|
|
2766
2767
|
totalInFile = Math.max(entries.length, fileScan.blocks.length);
|
|
2767
|
-
|
|
2768
|
+
truncatedAtLine = fileScan.truncatedAtLine;
|
|
2768
2769
|
} catch {
|
|
2769
2770
|
// Best-effort enrichment; fall back to window-only count.
|
|
2770
2771
|
}
|
|
2771
2772
|
outputText += formatConflictWarning(entries, {
|
|
2772
2773
|
totalInFile,
|
|
2773
2774
|
displayPath: displayPathForWarning,
|
|
2774
|
-
|
|
2775
|
+
truncatedAtLine,
|
|
2775
2776
|
});
|
|
2776
2777
|
details.conflictCount = entries.length;
|
|
2777
2778
|
}
|
|
@@ -2853,10 +2854,14 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2853
2854
|
}),
|
|
2854
2855
|
);
|
|
2855
2856
|
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2857
|
+
let summary: string;
|
|
2858
|
+
if (entries.length > 0) {
|
|
2859
|
+
summary = formatConflictSummary(entries, { displayPath, truncatedAtLine: scan.truncatedAtLine });
|
|
2860
|
+
} else if (scan.truncatedAtLine !== undefined) {
|
|
2861
|
+
summary = `No unresolved git merge conflicts before line ${scan.truncatedAtLine} of ${displayPath}.\n${formatScanCapNote(displayPath, scan.truncatedAtLine)}`;
|
|
2862
|
+
} else {
|
|
2863
|
+
summary = `No unresolved git merge conflicts in ${displayPath}.`;
|
|
2864
|
+
}
|
|
2860
2865
|
|
|
2861
2866
|
const details: ReadToolDetails = {
|
|
2862
2867
|
resolvedPath: absolutePath,
|