From 2787b6dff70d0ce5cd7bbd9133d88b05cb9229ce Mon Sep 17 00:00:00 2001 From: can1357 Date: Tue, 23 Jun 2026 08:08:31 +0200 Subject: [PATCH] chore: update changelogs --- packages/coding-agent/CHANGELOG.md | 17 +- ...selector-controller-session-delete.test.ts | 284 ------------------ packages/coding-agent/test/tools.test.ts | 2 +- .../test/tools/inspect-image.test.ts | 2 +- .../test/utils/image-resize.test.ts | 10 +- packages/collab-web/CHANGELOG.md | 2 +- 6 files changed, 17 insertions(+), 300 deletions(-) delete mode 100644 packages/coding-agent/test/modes/controllers/selector-controller-session-delete.test.ts diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index a50ea4dcd..5f093ed04 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,6 +1,7 @@ # Changelog ## [Unreleased] + ### Breaking Changes - Renamed the eval `agent()` helper parameters `agent_type` → `agent` and `return_handle` → `handle` across every workflow runtime (Python, JavaScript, Ruby, Julia), so the names are identical in every language (no camelCase/snake_case split) and the agent-selection parameter matches the `task` tool's `agent`. The `__agent__` eval bridge wire protocol was renamed to match. @@ -22,13 +23,6 @@ - Made `eval` an essential built-in tool (`loadMode: "essential"`, added to the default essential tool set) so it stays active under `tools.discoveryMode: "all"` instead of being hidden behind `search_tool_bm25`. - Made the `--resume` session picker fullscreen on the terminal's alternate screen, so the list scrolls with the mouse wheel and a row resumes its session on left click. Rows are hit-tested against the live scroll window, and the keybinding hint + bottom border are now pinned to the screen bottom instead of drifting up and down as the visible window changes height. -### Removed - -- Removed `append`, `tree`, and `diff` eval helper functions from Python, JavaScript, and Ruby -- Removed `sort`, `uniq`, and `counter` text processing eval helpers from Python, JavaScript, and Ruby -- Removed the `append(path, content)`, `tree(path, max_depth?, show_hidden?)`, and `diff(a, b)` eval prelude helpers from every workflow runtime (Python, JavaScript, Ruby, Julia), along with their status renderers, icon entries, and tool/`docs` references. Use `write`/`read` for file mutation and `tool.(...)` for richer filesystem operations. -- Removed the `sort(text, reverse?, unique?)`, `uniq(text, count?)`, and `counter(items, limit?, reverse?)` eval text helpers from the Python, JavaScript, and Ruby prelude surfaces (Julia never defined them), along with the JS `HelperBundle`/`HelperOptions` members and `docs` references. Sort/dedupe/count inline in cell code instead. - ### Fixed - Fixed `local://` URLs decoding images as corrupted text (mojibake) instead of showing the image @@ -47,6 +41,13 @@ - Fixed `/goal` threshold auto-compaction skipping real sessions through three paths: per-turn supersede/drop-useless pruning no longer deflates the threshold trigger below the last provider-billed context; active-goal text stops now attempt threshold maintenance before unexpected-stop retry continuations can return from post-turn handling; and empty `toolUse` stops keep the existing cleanup pass that strips the orphan assistant from active context + session history before any compaction continuation. Active-goal compaction continuations now also resolve completed retry gates before returning, preventing `isRetrying` from staying stuck after a retry succeeds over the threshold. Added `agent_end maintenance routing` and `Auto-compaction threshold decision` debug logs so future no-start reports identify the exact early-return branch and the billed/stored/resolved/post-maintenance token counts that fed `shouldCompact`. ([#3174](https://github.com/can1357/oh-my-pi/issues/3174)) - Fixed active `/goal` runs that never reached `agent_end` because the model kept emitting tool calls inside one agent run. Threshold maintenance now runs between tool-call turns, compacts the live loop context in place, and suppresses queued continuations that would race the still-running goal loop. ([#3174](https://github.com/can1357/oh-my-pi/issues/3174)) +### Removed + +- Removed `append`, `tree`, and `diff` eval helper functions from Python, JavaScript, and Ruby +- Removed `sort`, `uniq`, and `counter` text processing eval helpers from Python, JavaScript, and Ruby +- Removed the `append(path, content)`, `tree(path, max_depth?, show_hidden?)`, and `diff(a, b)` eval prelude helpers from every workflow runtime (Python, JavaScript, Ruby, Julia), along with their status renderers, icon entries, and tool/`docs` references. Use `write`/`read` for file mutation and `tool.(...)` for richer filesystem operations. +- Removed the `sort(text, reverse?, unique?)`, `uniq(text, count?)`, and `counter(items, limit?, reverse?)` eval text helpers from the Python, JavaScript, and Ruby prelude surfaces (Julia never defined them), along with the JS `HelperBundle`/`HelperOptions` members and `docs` references. Sort/dedupe/count inline in cell code instead. + ## [16.1.15] - 2026-06-22 ### Added @@ -12385,4 +12386,4 @@ Initial public release. ## [0.7.6] - 2025-11-13 -Previous releases did not maintain a changelog. \ No newline at end of file +Previous releases did not maintain a changelog. diff --git a/packages/coding-agent/test/modes/controllers/selector-controller-session-delete.test.ts b/packages/coding-agent/test/modes/controllers/selector-controller-session-delete.test.ts deleted file mode 100644 index 87d8f9870..000000000 --- a/packages/coding-agent/test/modes/controllers/selector-controller-session-delete.test.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "bun:test"; -import { SessionSelectorComponent } from "@oh-my-pi/pi-coding-agent/modes/components/session-selector"; -import { SelectorController } from "@oh-my-pi/pi-coding-agent/modes/controllers/selector-controller"; -import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme"; -import type { InteractiveModeContext } from "@oh-my-pi/pi-coding-agent/modes/types"; -import type { SessionInfo } from "@oh-my-pi/pi-coding-agent/session/session-listing"; -import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; -import { FileSessionStorage } from "@oh-my-pi/pi-coding-agent/session/session-storage"; - -type TestContext = InteractiveModeContext & { - editorContainer: { - children: unknown[]; - clear: () => void; - addChild: (child: unknown) => void; - }; -}; - -function makeSessionInfo(path: string): SessionInfo { - return { - path, - id: path, - cwd: "/tmp/project", - title: "Active session", - created: new Date("2025-01-01T00:00:00Z"), - modified: new Date("2025-01-01T00:00:00Z"), - messageCount: 1, - size: 0, - firstMessage: "hello", - allMessagesText: "hello", - }; -} - -function createContext(currentSessionFile: string): { - ctx: TestContext; - calls: string[]; - setCurrentSessionFile: (path: string) => void; - showHookConfirm: (title: string, message: string) => Promise; - newSession: () => Promise; -} { - const calls: string[] = []; - let sessionFile = currentSessionFile; - const editorContainer = { - children: [] as unknown[], - clear() { - this.children = []; - calls.push("editorContainer.clear"); - }, - addChild(child: unknown) { - this.children.push(child); - calls.push("editorContainer.addChild"); - }, - }; - const showHookConfirm = vi.fn(async () => true); - const newSession = vi.fn(async () => { - calls.push("session.newSession"); - sessionFile = "/tmp/project/sessions/detached.jsonl"; - return true; - }); - const session = { - newSession, - switchSession: vi.fn(async () => true), - }; - const ctx = { - editorContainer, - editor: {}, - ui: { - setFocus: vi.fn(), - requestRender: vi.fn(() => { - calls.push("ui.requestRender"); - }), - terminal: { columns: 120 }, - }, - session, - get viewSession() { - return session; - }, - sessionManager: { - getCwd: () => "/tmp/project", - getSessionDir: () => "/tmp/project/sessions", - getSessionFile: () => sessionFile, - }, - statusContainer: { - clear: vi.fn(() => { - calls.push("statusContainer.clear"); - }), - }, - pendingMessagesContainer: { - clear: vi.fn(() => { - calls.push("pendingMessagesContainer.clear"); - }), - }, - compactionQueuedMessages: [] as unknown[], - streamingComponent: { active: true }, - streamingMessage: { active: true }, - pendingTools: { - clear: vi.fn(() => { - calls.push("pendingTools.clear"); - }), - }, - loadingAnimation: { - stop: vi.fn(() => { - calls.push("loadingAnimation.stop"); - }), - }, - statusLine: { - invalidate: vi.fn(() => { - calls.push("statusLine.invalidate"); - }), - setSessionStartTime: vi.fn(() => { - calls.push("statusLine.setSessionStartTime"); - }), - }, - updateEditorTopBorder: vi.fn(() => { - calls.push("updateEditorTopBorder"); - }), - updateEditorBorderColor: vi.fn(() => { - calls.push("updateEditorBorderColor"); - }), - renderInitialMessages: vi.fn(() => { - calls.push("renderInitialMessages"); - }), - reloadTodos: vi.fn(async () => { - calls.push("reloadTodos"); - }), - showStatus: vi.fn((message: string) => { - calls.push(`showStatus:${message}`); - }), - showError: vi.fn(), - showHookConfirm, - shutdown: vi.fn(async () => undefined), - clearTransientSessionUi() { - ctx.loadingAnimation?.stop(); - ctx.statusContainer.clear(); - ctx.pendingMessagesContainer.clear(); - ctx.pendingTools.clear(); - }, - } as unknown as TestContext; - - return { - ctx, - calls, - setCurrentSessionFile(path: string) { - sessionFile = path; - }, - showHookConfirm, - newSession, - }; -} - -function renderText(selector: SessionSelectorComponent): string { - return selector.render(120).join("\n"); -} - -beforeAll(() => { - initTheme(); -}); - -describe("SelectorController session deletion", () => { - beforeEach(() => { - vi.spyOn(SessionManager, "list").mockResolvedValue([]); - vi.spyOn(SessionManager, "listAll").mockResolvedValue([]); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - it("detaches the active session before selector deletion removes it", async () => { - const activeSession = makeSessionInfo("/tmp/project/sessions/active.jsonl"); - const { ctx, calls } = createContext(activeSession.path); - vi.spyOn(SessionManager, "list").mockResolvedValue([activeSession]); - const deleteSessionWithArtifacts = vi - .spyOn(FileSessionStorage.prototype, "deleteSessionWithArtifacts") - .mockImplementation(async sessionPath => { - calls.push(`delete:${sessionPath}`); - }); - const controller = new SelectorController(ctx); - - await controller.showSessionSelector(); - const selector = ctx.editorContainer.children[0]; - if (!(selector instanceof SessionSelectorComponent)) { - throw new Error("Expected session selector component"); - } - - const sessionList = selector.getSessionList() as unknown as { - onDeleteRequest?: (session: SessionInfo) => void; - }; - sessionList.onDeleteRequest?.(activeSession); - selector.handleInput("\n"); - await Bun.sleep(0); - - expect(deleteSessionWithArtifacts).toHaveBeenCalledWith(activeSession.path); - expect(calls).toEqual([ - "editorContainer.clear", - "editorContainer.addChild", - "ui.requestRender", - "session.newSession", - "loadingAnimation.stop", - "statusContainer.clear", - "pendingMessagesContainer.clear", - "pendingTools.clear", - "statusLine.invalidate", - "statusLine.setSessionStartTime", - "updateEditorTopBorder", - "updateEditorBorderColor", - "renderInitialMessages", - "reloadTodos", - "ui.requestRender", - `delete:${activeSession.path}`, - "ui.requestRender", - ]); - expect(ctx.sessionManager.getSessionFile()).toBe("/tmp/project/sessions/detached.jsonl"); - }); - - it("shows inline selector errors when session deletion fails after detach", async () => { - const activeSession = makeSessionInfo("/tmp/project/sessions/active.jsonl"); - const { ctx, newSession } = createContext(activeSession.path); - vi.spyOn(SessionManager, "list").mockResolvedValue([activeSession]); - const deleteSessionWithArtifacts = vi - .spyOn(FileSessionStorage.prototype, "deleteSessionWithArtifacts") - .mockRejectedValue(new Error("disk failed")); - const controller = new SelectorController(ctx); - - await controller.showSessionSelector(); - const selector = ctx.editorContainer.children[0]; - if (!(selector instanceof SessionSelectorComponent)) { - throw new Error("Expected session selector component"); - } - - const sessionList = selector.getSessionList() as unknown as { - onDeleteRequest?: (session: SessionInfo) => void; - }; - sessionList.onDeleteRequest?.(activeSession); - selector.handleInput("\n"); - await Bun.sleep(0); - - expect(newSession).toHaveBeenCalledTimes(1); - expect(deleteSessionWithArtifacts).toHaveBeenCalledWith(activeSession.path); - expect(ctx.showError).not.toHaveBeenCalled(); - expect(ctx.sessionManager.getSessionFile()).toBe("/tmp/project/sessions/detached.jsonl"); - expect(renderText(selector)).toContain("Error: Failed to delete session: disk failed"); - }); - - it("creates a fresh session before deleting via slash command and then shows the selector", async () => { - const activeSessionPath = "/tmp/project/sessions/active.jsonl"; - const { ctx, calls, showHookConfirm, newSession } = createContext(activeSessionPath); - const deleteSessionWithArtifacts = vi - .spyOn(FileSessionStorage.prototype, "deleteSessionWithArtifacts") - .mockImplementation(async sessionPath => { - calls.push(`delete:${sessionPath}`); - }); - const exists = vi.spyOn(FileSessionStorage.prototype, "exists").mockResolvedValue(true); - const controller = new SelectorController(ctx); - - await controller.handleSessionDeleteCommand(); - - expect(exists).toHaveBeenCalledWith(activeSessionPath); - expect(showHookConfirm).toHaveBeenCalledWith( - "Delete Session", - "This will permanently delete the current session.\nYou will be returned to the session selector.", - ); - expect(newSession).toHaveBeenCalledTimes(1); - expect(deleteSessionWithArtifacts).toHaveBeenCalledWith(activeSessionPath); - expect(calls).toEqual([ - "session.newSession", - "loadingAnimation.stop", - "statusContainer.clear", - "pendingMessagesContainer.clear", - "pendingTools.clear", - "statusLine.invalidate", - "statusLine.setSessionStartTime", - "updateEditorTopBorder", - "updateEditorBorderColor", - "renderInitialMessages", - "reloadTodos", - "ui.requestRender", - `delete:${activeSessionPath}`, - "showStatus:Session deleted", - "editorContainer.clear", - "editorContainer.addChild", - "ui.requestRender", - ]); - }); -}); diff --git a/packages/coding-agent/test/tools.test.ts b/packages/coding-agent/test/tools.test.ts index 0720f31c9..cae087d36 100644 --- a/packages/coding-agent/test/tools.test.ts +++ b/packages/coding-agent/test/tools.test.ts @@ -803,7 +803,7 @@ describe("Coding Agent Tools", () => { fs.writeFileSync(testFile, pngBuffer); const legacyReadTool = wrapToolWithMetaNotice( - new ReadTool(createTestToolSession(testDir, Settings.isolated({ "inspect_image.enabled": false }))), + new ReadTool(createTestToolSession(testDir, Settings.isolated({ "inspect_image.enabled": false, "images.autoResize": false }))), ); const result = await legacyReadTool.execute("test-call-img-1", { path: testFile }); diff --git a/packages/coding-agent/test/tools/inspect-image.test.ts b/packages/coding-agent/test/tools/inspect-image.test.ts index f3149ab54..3393cd57f 100644 --- a/packages/coding-agent/test/tools/inspect-image.test.ts +++ b/packages/coding-agent/test/tools/inspect-image.test.ts @@ -161,7 +161,7 @@ describe("InspectImageTool", () => { const stub = createCompleteSimpleSuccessStub("Attached image inspected"); const missingCwd = path.join(testDir, "missing-cwd"); const tool = new InspectImageTool( - createSession(missingCwd, visionModel, "test-key", Settings.isolated(), { + createSession(missingCwd, visionModel, "test-key", Settings.isolated({ "images.autoResize": false }), { imageAttachments: [{ label: "Image #1", uri: "attachment://1", image }], }), stub.fn, diff --git a/packages/coding-agent/test/utils/image-resize.test.ts b/packages/coding-agent/test/utils/image-resize.test.ts index 7a092cf6b..7e145f277 100644 --- a/packages/coding-agent/test/utils/image-resize.test.ts +++ b/packages/coding-agent/test/utils/image-resize.test.ts @@ -25,8 +25,8 @@ async function makeRedWebP(width: number, height: number): Promise { // its input, so a single decodable source per shape serves every test. Real image // encode/decode is the only cost here, so each source is the smallest solid-red // image that still crosses the threshold under test: -// - oversizedPng: a thin strip whose long edge exceeds the 1568 default cap, so -// re-encodes touch ~1568×98 px instead of 1568×1568. A uniform red keeps format +// - oversizedPng: a strip whose long edge exceeds the 1568 default cap, so +// re-encodes touch ~1568×392 px instead of 1568×1568. A uniform red keeps format // selection deterministic (WebP is always smallest; PNG always beats JPEG), so // the strip exercises the same format/budget logic as a large square. // - smallPng / smallWebp: 200×200, comfortably inside every default cap (fast path). @@ -36,7 +36,7 @@ let smallWebp: string; beforeAll(async () => { [oversizedPng, smallPng, smallWebp] = await Promise.all([ - makeRedPng(1600, 100), + makeRedPng(1600, 400), makeRedPng(200, 200), makeRedWebP(200, 200), ]); @@ -50,8 +50,8 @@ describe("resizeImage defaults", () => { expect(result.wasResized).toBe(true); expect(result.width).toBeLessThanOrEqual(1568); expect(result.height).toBeLessThanOrEqual(1568); - // Aspect ratio of the 1600x100 source preserved (with rounding tolerance). - expect(Math.abs(result.width / result.height - 1600 / 100)).toBeLessThan(0.01); + // Aspect ratio of the 1600x400 source preserved (with rounding tolerance). + expect(Math.abs(result.width / result.height - 1600 / 400)).toBeLessThan(0.01); }); it("preserves inputs already within budget and dimensions (fast path)", async () => { diff --git a/packages/collab-web/CHANGELOG.md b/packages/collab-web/CHANGELOG.md index a8f40829b..981da4464 100644 --- a/packages/collab-web/CHANGELOG.md +++ b/packages/collab-web/CHANGELOG.md @@ -130,4 +130,4 @@ ### Security -- Hardened transcript Markdown rendering by escaping embedded HTML and allowing only safe link schemes \ No newline at end of file +- Hardened transcript Markdown rendering by escaping embedded HTML and allowing only safe link schemes