@oh-my-pi/pi-coding-agent 14.0.4 → 14.1.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 +83 -0
- package/package.json +11 -8
- package/src/async/index.ts +1 -0
- package/src/async/support.ts +5 -0
- package/src/cli/list-models.ts +96 -57
- package/src/commit/model-selection.ts +16 -13
- package/src/config/model-equivalence.ts +674 -0
- package/src/config/model-registry.ts +182 -13
- package/src/config/model-resolver.ts +203 -74
- package/src/config/settings-schema.ts +23 -0
- package/src/config/settings.ts +9 -2
- package/src/dap/session.ts +31 -39
- package/src/debug/log-formatting.ts +2 -2
- package/src/edit/modes/chunk.ts +8 -3
- package/src/export/html/template.css +82 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +612 -97
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/internal-urls/jobs-protocol.ts +2 -1
- package/src/lsp/client.ts +5 -3
- package/src/lsp/index.ts +4 -9
- package/src/lsp/utils.ts +26 -0
- package/src/main.ts +6 -1
- package/src/memories/index.ts +7 -6
- package/src/modes/components/diff.ts +1 -1
- package/src/modes/components/model-selector.ts +221 -64
- package/src/modes/controllers/command-controller.ts +18 -0
- package/src/modes/controllers/event-controller.ts +438 -426
- package/src/modes/controllers/selector-controller.ts +13 -5
- package/src/modes/theme/mermaid-cache.ts +5 -7
- package/src/priority.json +8 -0
- package/src/prompts/agents/designer.md +1 -2
- package/src/prompts/system/system-prompt.md +5 -1
- package/src/prompts/tools/bash.md +15 -0
- package/src/prompts/tools/cancel-job.md +1 -1
- package/src/prompts/tools/chunk-edit.md +39 -40
- package/src/prompts/tools/read-chunk.md +13 -1
- package/src/prompts/tools/read.md +9 -0
- package/src/prompts/tools/write.md +1 -0
- package/src/sdk.ts +7 -4
- package/src/session/agent-session.ts +33 -6
- package/src/session/compaction/compaction.ts +1 -1
- package/src/task/executor.ts +5 -1
- package/src/tools/await-tool.ts +2 -1
- package/src/tools/bash.ts +221 -56
- package/src/tools/browser.ts +84 -21
- package/src/tools/cancel-job.ts +2 -1
- package/src/tools/fetch.ts +1 -1
- package/src/tools/find.ts +40 -94
- package/src/tools/gemini-image.ts +1 -0
- package/src/tools/inspect-image.ts +1 -1
- package/src/tools/read.ts +218 -1
- package/src/tools/render-utils.ts +1 -1
- package/src/tools/sqlite-reader.ts +623 -0
- package/src/tools/write.ts +187 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/git.ts +24 -1
- package/src/utils/image-resize.ts +73 -37
- package/src/utils/title-generator.ts +1 -1
- package/src/web/scrapers/types.ts +50 -32
- package/src/web/search/providers/codex.ts +21 -2
package/src/dap/session.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import
|
|
2
|
+
import * as timers from "node:timers/promises";
|
|
3
|
+
import { logger, ptree, untilAborted } from "@oh-my-pi/pi-utils";
|
|
3
4
|
import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
|
|
4
5
|
import { DapClient } from "./client";
|
|
5
6
|
import type {
|
|
@@ -154,27 +155,10 @@ function buildSummary(session: DapSession): DapSessionSummary {
|
|
|
154
155
|
};
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
async function raceAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
158
|
-
if (!signal) return promise;
|
|
159
|
-
if (signal.aborted) {
|
|
160
|
-
throw signal.reason instanceof Error ? signal.reason : new Error("Operation aborted");
|
|
161
|
-
}
|
|
162
|
-
const { promise: abortPromise, reject } = Promise.withResolvers<never>();
|
|
163
|
-
const onAbort = () => {
|
|
164
|
-
reject(signal.reason instanceof Error ? signal.reason : new Error("Operation aborted"));
|
|
165
|
-
};
|
|
166
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
167
|
-
try {
|
|
168
|
-
return await Promise.race([promise, abortPromise]);
|
|
169
|
-
} finally {
|
|
170
|
-
signal.removeEventListener("abort", onAbort);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
158
|
export class DapSessionManager {
|
|
175
159
|
#sessions = new Map<string, DapSession>();
|
|
176
160
|
#activeSessionId: string | null = null;
|
|
177
|
-
#
|
|
161
|
+
#cleanupLoopPromise?: Promise<void>;
|
|
178
162
|
#nextId = 0;
|
|
179
163
|
|
|
180
164
|
constructor() {
|
|
@@ -235,7 +219,7 @@ export class DapSessionManager {
|
|
|
235
219
|
// Try to capture initial stopped state (e.g. stopOnEntry).
|
|
236
220
|
// Timeout is acceptable — the program may simply be running.
|
|
237
221
|
try {
|
|
238
|
-
await
|
|
222
|
+
await untilAborted(signal, initialStopPromise);
|
|
239
223
|
if (session.status === "stopped") {
|
|
240
224
|
await this.#fetchTopFrame(session, signal, Math.min(timeoutMs, STOP_CAPTURE_TIMEOUT_MS));
|
|
241
225
|
}
|
|
@@ -283,7 +267,7 @@ export class DapSessionManager {
|
|
|
283
267
|
await this.#completeConfigurationHandshake(session, signal, timeoutMs);
|
|
284
268
|
await attachPromise;
|
|
285
269
|
try {
|
|
286
|
-
await
|
|
270
|
+
await untilAborted(signal, initialStopPromise);
|
|
287
271
|
if (session.status === "stopped") {
|
|
288
272
|
await this.#fetchTopFrame(session, signal, Math.min(timeoutMs, STOP_CAPTURE_TIMEOUT_MS));
|
|
289
273
|
}
|
|
@@ -696,9 +680,9 @@ export class DapSessionManager {
|
|
|
696
680
|
// between the request and here. Wait for it, but tolerate timeout if the
|
|
697
681
|
// session already transitioned.
|
|
698
682
|
try {
|
|
699
|
-
await
|
|
700
|
-
session.client.waitForEvent<DapStoppedEventBody>("stopped", undefined, signal, timeoutMs),
|
|
683
|
+
await untilAborted(
|
|
701
684
|
signal,
|
|
685
|
+
session.client.waitForEvent<DapStoppedEventBody>("stopped", undefined, signal, timeoutMs),
|
|
702
686
|
);
|
|
703
687
|
} catch {
|
|
704
688
|
// Timeout or abort — report current state regardless
|
|
@@ -833,16 +817,16 @@ export class DapSessionManager {
|
|
|
833
817
|
session.lastUsedAt = Date.now();
|
|
834
818
|
if (session.status !== "terminated") {
|
|
835
819
|
if (session.capabilities?.supportsTerminateRequest) {
|
|
836
|
-
await
|
|
837
|
-
session.client.sendRequest("terminate", undefined, signal, timeoutMs).catch(() => undefined),
|
|
820
|
+
await untilAborted(
|
|
838
821
|
signal,
|
|
822
|
+
session.client.sendRequest("terminate", undefined, signal, timeoutMs).catch(() => undefined),
|
|
839
823
|
);
|
|
840
824
|
}
|
|
841
|
-
await
|
|
825
|
+
await untilAborted(
|
|
826
|
+
signal,
|
|
842
827
|
session.client
|
|
843
828
|
.sendRequest("disconnect", { terminateDebuggee: true }, signal, timeoutMs)
|
|
844
829
|
.catch(() => undefined),
|
|
845
|
-
signal,
|
|
846
830
|
);
|
|
847
831
|
}
|
|
848
832
|
session.status = "terminated";
|
|
@@ -852,22 +836,30 @@ export class DapSessionManager {
|
|
|
852
836
|
}
|
|
853
837
|
|
|
854
838
|
#startCleanupTimer(): void {
|
|
855
|
-
if (this.#
|
|
856
|
-
this.#
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
839
|
+
if (this.#cleanupLoopPromise) return;
|
|
840
|
+
this.#cleanupLoopPromise = this.#runCleanupLoop();
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
async #runCleanupLoop(): Promise<void> {
|
|
844
|
+
for await (const _ of timers.setInterval(CLEANUP_INTERVAL_MS, null, { ref: false })) {
|
|
845
|
+
try {
|
|
846
|
+
this.#cleanupIdleSessions();
|
|
847
|
+
} catch (error) {
|
|
848
|
+
logger.error("DAP idle session cleanup failed", { error: toErrorMessage(error) });
|
|
849
|
+
}
|
|
850
|
+
}
|
|
860
851
|
}
|
|
861
852
|
|
|
862
|
-
|
|
853
|
+
#cleanupIdleSessions(): void {
|
|
854
|
+
if (this.#sessions.size === 0) return;
|
|
863
855
|
const now = Date.now();
|
|
864
|
-
for (const session of
|
|
856
|
+
for (const session of this.#sessions.values()) {
|
|
865
857
|
if (
|
|
866
858
|
session.status === "terminated" ||
|
|
867
859
|
now - session.lastUsedAt > IDLE_TIMEOUT_MS ||
|
|
868
860
|
!session.client.isAlive()
|
|
869
861
|
) {
|
|
870
|
-
|
|
862
|
+
this.#disposeSession(session);
|
|
871
863
|
}
|
|
872
864
|
}
|
|
873
865
|
}
|
|
@@ -1006,7 +998,7 @@ export class DapSessionManager {
|
|
|
1006
998
|
// Wait for the initialized event if we haven't seen it yet.
|
|
1007
999
|
if (!session.initializedSeen) {
|
|
1008
1000
|
try {
|
|
1009
|
-
await
|
|
1001
|
+
await untilAborted(signal, session.client.waitForEvent("initialized", undefined, signal, timeoutMs));
|
|
1010
1002
|
} catch {
|
|
1011
1003
|
// Adapter may not send initialized (e.g. it already terminated).
|
|
1012
1004
|
// Proceed anyway — the launch/attach response will surface any real error.
|
|
@@ -1100,7 +1092,7 @@ export class DapSessionManager {
|
|
|
1100
1092
|
timeoutMs: number = 30_000,
|
|
1101
1093
|
): Promise<DapContinueOutcome> {
|
|
1102
1094
|
try {
|
|
1103
|
-
await
|
|
1095
|
+
await untilAborted(signal, outcomePromise);
|
|
1104
1096
|
if (session.status === "stopped") {
|
|
1105
1097
|
await this.#fetchTopFrame(session, signal, Math.min(timeoutMs, 5_000));
|
|
1106
1098
|
}
|
|
@@ -1243,12 +1235,12 @@ export class DapSessionManager {
|
|
|
1243
1235
|
return session;
|
|
1244
1236
|
}
|
|
1245
1237
|
|
|
1246
|
-
|
|
1238
|
+
#disposeSession(session: DapSession) {
|
|
1247
1239
|
if (this.#activeSessionId === session.id) {
|
|
1248
1240
|
this.#activeSessionId = null;
|
|
1249
1241
|
}
|
|
1250
1242
|
this.#sessions.delete(session.id);
|
|
1251
|
-
|
|
1243
|
+
void session.client.dispose().catch(() => {});
|
|
1252
1244
|
}
|
|
1253
1245
|
}
|
|
1254
1246
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { sanitizeText
|
|
2
|
-
import { replaceTabs, truncateToWidth } from "../tools/render-utils";
|
|
1
|
+
import { sanitizeText } from "@oh-my-pi/pi-natives";
|
|
2
|
+
import { replaceTabs, truncateToWidth, wrapTextWithAnsi } from "../tools/render-utils";
|
|
3
3
|
|
|
4
4
|
export function formatDebugLogLine(line: string, maxWidth: number): string {
|
|
5
5
|
const sanitized = sanitizeText(line);
|
package/src/edit/modes/chunk.ts
CHANGED
|
@@ -332,7 +332,7 @@ export const chunkToolEditSchema = Type.Object({
|
|
|
332
332
|
op: StringEnum(CHUNK_OP_VALUES),
|
|
333
333
|
sel: Type.String({
|
|
334
334
|
description:
|
|
335
|
-
"Chunk selector.
|
|
335
|
+
"Chunk selector. Use 'path~' or 'path^' for insertions, 'path#CRC~' or 'path#CRC^' for replace, or omit the suffix to target the full chunk.",
|
|
336
336
|
}),
|
|
337
337
|
content: Type.String({
|
|
338
338
|
description:
|
|
@@ -443,6 +443,12 @@ export async function executeChunkMode(
|
|
|
443
443
|
}
|
|
444
444
|
const normalizedOperations = normalizeChunkEditOperations(edits);
|
|
445
445
|
|
|
446
|
+
if (!sourceExists && normalizedOperations.some(op => op.sel)) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
`File does not exist: ${path}. Cannot resolve chunk selectors on a non-existent file. Use the write tool to create a new file, or check the path for typos.`,
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
446
452
|
const chunkResult = applyChunkEdits({
|
|
447
453
|
source: rawContent,
|
|
448
454
|
language: chunkLanguage,
|
|
@@ -453,9 +459,8 @@ export async function executeChunkMode(
|
|
|
453
459
|
});
|
|
454
460
|
|
|
455
461
|
if (!chunkResult.changed) {
|
|
456
|
-
const responseText = `[No changes needed — content already matches.]\n\n${chunkResult.responseText}`;
|
|
457
462
|
return {
|
|
458
|
-
content: [{ type: "text", text:
|
|
463
|
+
content: [{ type: "text", text: "[No changes needed \u2014 content already matches.]" }],
|
|
459
464
|
details: {
|
|
460
465
|
diff: "",
|
|
461
466
|
op: sourceExists ? "update" : "create",
|
|
@@ -705,6 +705,88 @@
|
|
|
705
705
|
color: var(--error);
|
|
706
706
|
}
|
|
707
707
|
|
|
708
|
+
/* Tool renderer extras */
|
|
709
|
+
.tool-meta {
|
|
710
|
+
margin-top: 4px;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.tool-badge {
|
|
714
|
+
display: inline-block;
|
|
715
|
+
padding: 0 6px;
|
|
716
|
+
margin-right: 4px;
|
|
717
|
+
border-radius: 3px;
|
|
718
|
+
background: rgba(255, 255, 255, 0.06);
|
|
719
|
+
color: var(--dim);
|
|
720
|
+
font-size: 11px;
|
|
721
|
+
font-weight: normal;
|
|
722
|
+
vertical-align: baseline;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
.tool-pattern {
|
|
726
|
+
color: var(--warning);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
.tool-args {
|
|
730
|
+
margin-top: 4px;
|
|
731
|
+
color: var(--toolOutput);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
.tool-arg {
|
|
735
|
+
display: block;
|
|
736
|
+
line-height: var(--line-height);
|
|
737
|
+
white-space: pre-wrap;
|
|
738
|
+
word-break: break-word;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.tool-arg-key {
|
|
742
|
+
color: var(--dim);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.tool-arg-val {
|
|
746
|
+
color: var(--text);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.tool-cell {
|
|
750
|
+
margin-top: var(--line-height);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.tool-cell-title {
|
|
754
|
+
color: var(--dim);
|
|
755
|
+
font-size: 11px;
|
|
756
|
+
margin-bottom: 2px;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/* Todo write tree */
|
|
760
|
+
.todo-tree {
|
|
761
|
+
margin-top: var(--line-height);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.todo-phase {
|
|
765
|
+
margin-top: 6px;
|
|
766
|
+
color: var(--accent);
|
|
767
|
+
font-weight: bold;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.todo-task {
|
|
771
|
+
padding-left: 12px;
|
|
772
|
+
line-height: var(--line-height);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
.todo-icon {
|
|
776
|
+
display: inline-block;
|
|
777
|
+
width: 14px;
|
|
778
|
+
text-align: center;
|
|
779
|
+
color: var(--dim);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
.todo-completed { color: var(--toolDiffAdded); }
|
|
783
|
+
.todo-completed .todo-icon { color: var(--toolDiffAdded); }
|
|
784
|
+
.todo-in_progress { color: var(--warning); }
|
|
785
|
+
.todo-in_progress .todo-icon { color: var(--warning); }
|
|
786
|
+
.todo-abandoned { color: var(--toolDiffRemoved); }
|
|
787
|
+
.todo-abandoned .todo-icon { color: var(--toolDiffRemoved); }
|
|
788
|
+
.todo-pending { color: var(--toolOutput); }
|
|
789
|
+
|
|
708
790
|
/* Images */
|
|
709
791
|
.message-images {
|
|
710
792
|
margin-bottom: 12px;
|