@oh-my-pi/pi-coding-agent 17.2.8 → 17.2.10
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 +94 -1
- package/dist/{CHANGELOG-0b0w17se.md → CHANGELOG-rhwzpexa.md} +94 -1
- package/dist/cli.js +13216 -15614
- package/dist/types/async/job-manager.d.ts +12 -0
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +13 -0
- package/dist/types/discovery/agents.d.ts +11 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +5 -5
- package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
- package/dist/types/extensibility/extensions/loader.d.ts +2 -2
- package/dist/types/extensibility/extensions/types.d.ts +34 -8
- package/dist/types/extensibility/hooks/types.d.ts +5 -5
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +10 -0
- package/dist/types/hindsight/state.d.ts +0 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/lsp/utils.d.ts +8 -0
- package/dist/types/main.d.ts +1 -1
- package/dist/types/modes/acp/acp-agent.d.ts +1 -1
- package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
- package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -1
- package/dist/types/modes/acp/acp-mode.d.ts +1 -1
- package/dist/types/modes/components/agent-hub-projection.d.ts +38 -0
- package/dist/types/modes/components/agent-hub-renderer.d.ts +41 -0
- package/dist/types/modes/components/agent-hub.d.ts +12 -5
- package/dist/types/modes/components/model-browser.d.ts +9 -1
- package/dist/types/modes/interactive-mode.d.ts +8 -1
- package/dist/types/modes/session-observer-registry.d.ts +2 -0
- package/dist/types/registry/agent-lifecycle.d.ts +1 -1
- package/dist/types/registry/agent-registry.d.ts +47 -0
- package/dist/types/registry/persisted-agents.d.ts +3 -1
- package/dist/types/session/agent-session-types.d.ts +7 -0
- package/dist/types/session/agent-session.d.ts +8 -4
- package/dist/types/session/session-entries.d.ts +10 -0
- package/dist/types/session/session-loader.d.ts +11 -1
- package/dist/types/session/session-manager.d.ts +9 -1
- package/dist/types/session/session-tools.d.ts +10 -1
- package/dist/types/session/turn-recovery.d.ts +6 -2
- package/dist/types/slash-commands/acp-builtins.d.ts +1 -1
- package/dist/types/slash-commands/available-commands.d.ts +1 -1
- package/dist/types/task/executor.d.ts +10 -0
- package/dist/types/task/index.d.ts +2 -3
- package/dist/types/task/read-only-policy.d.ts +3 -0
- package/dist/types/task/structured-subagent.d.ts +2 -0
- package/dist/types/task/types.d.ts +4 -0
- package/dist/types/tools/bash.d.ts +3 -3
- package/dist/types/tools/browser/launch.d.ts +3 -0
- package/dist/types/tools/browser/tab-worker.d.ts +2 -2
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/read.d.ts +3 -5
- package/dist/types/tools/run-scope.d.ts +27 -1
- package/dist/types/tools/shell-tokenize.d.ts +21 -3
- package/dist/types/tools/terminal-output.d.ts +1 -1
- package/dist/types/utils/changelog.d.ts +3 -2
- package/dist/types/utils/late-cleanup.d.ts +2 -0
- package/dist/types/utils/turndown.d.ts +1 -1
- package/dist/types/vibe/runtime.d.ts +4 -3
- package/dist/types/web/scrapers/readthedocs.d.ts +0 -3
- package/dist/types/web/search/providers/browser-headers.d.ts +2 -3
- package/examples/custom-tools/README.md +1 -1
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/api-demo.ts +5 -6
- package/examples/extensions/chalk-logger.ts +1 -1
- package/examples/extensions/hello.ts +2 -2
- package/examples/extensions/reload-runtime.ts +1 -1
- package/examples/extensions/tools.ts +2 -2
- package/examples/extensions/with-deps/index.ts +1 -1
- package/examples/sdk/06-extensions.ts +1 -1
- package/package.json +15 -31
- package/scripts/bundle-dist.ts +1 -11
- package/scripts/omp +11 -2
- package/src/advisor/runtime.ts +54 -0
- package/src/async/job-manager.ts +26 -0
- package/src/cli/agents-cli.ts +1 -1
- package/src/cli/args.ts +24 -2
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/auth-gateway-cli.ts +1 -1
- package/src/cli/bench-cli.ts +1 -1
- package/src/cli/claude-trace-cli.ts +1 -1
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/dry-balance-cli.ts +1 -1
- package/src/cli/file-processor.ts +1 -1
- package/src/cli/flag-tables.ts +4 -0
- package/src/cli/grep-cli.ts +1 -1
- package/src/cli/grievances-cli.ts +1 -1
- package/src/cli/help-extra.ts +1 -1
- package/src/cli/models-cli.ts +1 -1
- package/src/cli/plugin-cli.ts +1 -1
- package/src/cli/read-cli.ts +1 -1
- package/src/cli/setup-cli.ts +1 -1
- package/src/cli/shell-cli.ts +1 -1
- package/src/cli/ssh-cli.ts +1 -1
- package/src/cli/stats-cli.ts +4 -4
- package/src/cli/tiny-models-cli.ts +1 -1
- package/src/cli/ttsr-cli.ts +1 -1
- package/src/cli/update-cli.ts +3 -57
- package/src/cli/usage-cli.ts +1 -1
- package/src/cli/web-search-cli.ts +1 -1
- package/src/cli/worktree-cli.ts +1 -1
- package/src/commands/say.ts +1 -1
- package/src/commands/token.ts +1 -1
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/cli.ts +1 -1
- package/src/config/config-file.ts +2 -2
- package/src/config/keybindings.ts +3 -3
- package/src/config/model-discovery.ts +36 -2
- package/src/config/model-registry.ts +4 -1
- package/src/config/model-resolver.ts +54 -7
- package/src/discovery/agents.ts +73 -3
- package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +5 -4
- package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -1
- package/src/discovery/claude.ts +8 -5
- package/src/discovery/cursor.ts +8 -5
- package/src/discovery/gemini.ts +11 -8
- package/src/discovery/vscode.ts +1 -0
- package/src/discovery/windsurf.ts +4 -2
- package/src/edit/hashline/filesystem.ts +7 -7
- package/src/eval/agent-bridge.ts +1 -3
- package/src/eval/jl/prelude.jl +4 -4
- package/src/eval/js/shared/prelude.txt +2 -2
- package/src/eval/py/prelude.py +0 -3
- package/src/eval/py/runner.py +38 -1
- package/src/eval/rb/prelude.rb +1 -2
- package/src/extensibility/custom-commands/loader.ts +4 -4
- package/src/extensibility/custom-commands/types.ts +5 -5
- package/src/extensibility/custom-tools/loader.ts +4 -4
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/loader.ts +7 -6
- package/src/extensibility/extensions/types.ts +39 -8
- package/src/extensibility/hooks/loader.ts +4 -5
- package/src/extensibility/hooks/types.ts +5 -5
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +23 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +53 -0
- package/src/hindsight/state.ts +2 -22
- package/src/index.ts +2 -2
- package/src/internal-urls/memory-protocol.ts +33 -0
- package/src/launch/terminal-output.ts +1 -1
- package/src/lsp/client.ts +2 -2
- package/src/lsp/utils.ts +29 -0
- package/src/main.ts +85 -21
- package/src/markit/converters/docx.ts +1 -1
- package/src/markit/converters/epub.ts +1 -1
- package/src/markit/converters/pptx.ts +1 -1
- package/src/markit/converters/xlsx.ts +1 -1
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/acp/acp-client-bridge.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +1 -1
- package/src/modes/acp/acp-mode.ts +1 -1
- package/src/modes/components/agent-hub-projection.ts +248 -0
- package/src/modes/components/agent-hub-renderer.ts +194 -0
- package/src/modes/components/agent-hub.ts +670 -192
- package/src/modes/components/agent-transcript-viewer.ts +1 -3
- package/src/modes/components/assistant-message.ts +1 -1
- package/src/modes/components/bordered-loader.ts +1 -1
- package/src/modes/components/custom-editor.ts +2 -2
- package/src/modes/components/extensions/inspector-panel.ts +11 -6
- package/src/modes/components/footer.ts +1 -3
- package/src/modes/components/hook-input.ts +1 -1
- package/src/modes/components/hook-selector.ts +2 -2
- package/src/modes/components/model-browser.ts +11 -3
- package/src/modes/components/model-hub.ts +4 -1
- package/src/modes/components/session-selector.ts +2 -2
- package/src/modes/components/status-line/segments.ts +4 -3
- package/src/modes/controllers/event-controller.ts +188 -7
- package/src/modes/controllers/extension-ui-controller.ts +4 -3
- package/src/modes/controllers/mcp-command-controller.ts +81 -20
- package/src/modes/controllers/selector-controller.ts +40 -49
- package/src/modes/controllers/streaming-reveal.ts +1 -1
- package/src/modes/interactive-mode.ts +13 -2
- package/src/modes/print-mode.ts +29 -12
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/session-observer-registry.ts +5 -0
- package/src/modes/theme/theme.ts +2 -2
- package/src/prompts/tools/computer.md +1 -1
- package/src/registry/agent-lifecycle.ts +35 -10
- package/src/registry/agent-registry.ts +65 -2
- package/src/registry/persisted-agents.ts +341 -16
- package/src/sdk.ts +11 -5
- package/src/session/agent-session-types.ts +8 -0
- package/src/session/agent-session.ts +177 -188
- package/src/session/session-entries.ts +10 -0
- package/src/session/session-listing.ts +1 -1
- package/src/session/session-loader.ts +74 -8
- package/src/session/session-manager.ts +22 -2
- package/src/session/session-paths.ts +105 -110
- package/src/session/session-tools.ts +28 -1
- package/src/session/settings-stream-fn.ts +7 -3
- package/src/session/turn-recovery.ts +236 -18
- package/src/session/unexpected-stop-classifier.ts +12 -3
- package/src/slash-commands/acp-builtins.ts +1 -1
- package/src/slash-commands/available-commands.ts +1 -1
- package/src/slash-commands/helpers/stats-dashboard.ts +2 -1
- package/src/task/agents.ts +1 -1
- package/src/task/executor.ts +173 -47
- package/src/task/index.ts +9 -28
- package/src/task/isolation-runner.ts +43 -12
- package/src/task/persisted-revive.ts +12 -5
- package/src/task/read-only-policy.ts +27 -0
- package/src/task/structured-subagent.ts +31 -6
- package/src/task/types.ts +4 -0
- package/src/tiny/worker.ts +1 -1
- package/src/tools/auto-generated-guard.ts +1 -1
- package/src/tools/bash-interactive.ts +4 -4
- package/src/tools/bash-interceptor.ts +8 -4
- package/src/tools/bash.ts +6 -6
- package/src/tools/browser/cmux/cmux-tab.ts +66 -18
- package/src/tools/browser/launch.ts +76 -19
- package/src/tools/browser/readable.ts +9 -9
- package/src/tools/browser/tab-supervisor.ts +12 -3
- package/src/tools/browser/tab-worker-entry.ts +1 -1
- package/src/tools/browser/tab-worker.ts +101 -12
- package/src/tools/fetch.ts +1 -1
- package/src/tools/glob.ts +4 -2
- package/src/tools/path-utils.ts +22 -5
- package/src/tools/read.ts +22 -5
- package/src/tools/run-scope.ts +290 -4
- package/src/tools/shell-tokenize.ts +38 -9
- package/src/tools/terminal-output.ts +1 -1
- package/src/tools/todo.ts +1 -1
- package/src/utils/changelog.ts +5 -4
- package/src/utils/clipboard.ts +10 -2
- package/src/utils/external-editor.ts +4 -2
- package/src/utils/jj.ts +1 -1
- package/src/utils/late-cleanup.ts +17 -0
- package/src/utils/turndown.ts +1 -2
- package/src/vibe/runtime.ts +202 -58
- package/src/web/scrapers/arxiv.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +1 -1
- package/src/web/scrapers/hackage.ts +1 -12
- package/src/web/scrapers/iacr.ts +1 -1
- package/src/web/scrapers/readthedocs.ts +2 -1
- package/src/web/scrapers/twitter.ts +2 -2
- package/src/web/scrapers/types.ts +1 -1
- package/src/web/scrapers/wikipedia.ts +1 -1
- package/src/web/search/providers/browser-headers.ts +12 -39
- package/src/web/search/providers/codex.ts +3 -26
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/providers/ecosia.ts +2 -2
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/firecrawl.ts +1 -1
- package/src/web/search/providers/google.ts +1 -1
- package/src/web/search/providers/mojeek.ts +2 -2
- package/src/web/search/providers/perplexity.ts +1 -1
- package/src/web/search/providers/public.ts +1 -1
- package/src/web/search/providers/startpage.ts +2 -2
- package/src/markit/converters/mammoth.d.ts +0 -24
|
@@ -110,6 +110,23 @@ const MANUAL_LOGIN_PROMPT = "Paste the authorization code (or full redirect URL)
|
|
|
110
110
|
|
|
111
111
|
export class SelectorController {
|
|
112
112
|
constructor(private ctx: InteractiveModeContext) {}
|
|
113
|
+
/**
|
|
114
|
+
* Mount a primary fullscreen menu through the one polished modal path shared
|
|
115
|
+
* by Settings, Model Hub, and Agent Hub.
|
|
116
|
+
*/
|
|
117
|
+
#showFullscreenMenu(component: Component): OverlayHandle {
|
|
118
|
+
const handle = this.ctx.ui.showOverlay(component, {
|
|
119
|
+
anchor: "bottom-center",
|
|
120
|
+
width: "100%",
|
|
121
|
+
maxHeight: "100%",
|
|
122
|
+
margin: 0,
|
|
123
|
+
fullscreen: true,
|
|
124
|
+
});
|
|
125
|
+
this.ctx.ui.setFocus(component);
|
|
126
|
+
this.ctx.ui.requestRender();
|
|
127
|
+
return handle;
|
|
128
|
+
}
|
|
129
|
+
|
|
113
130
|
#defaultRoleMutationTail = Promise.resolve();
|
|
114
131
|
|
|
115
132
|
async #acquireDefaultRoleMutation(): Promise<() => void> {
|
|
@@ -242,15 +259,7 @@ export class SelectorController {
|
|
|
242
259
|
},
|
|
243
260
|
},
|
|
244
261
|
);
|
|
245
|
-
overlayHandle = this
|
|
246
|
-
anchor: "bottom-center",
|
|
247
|
-
width: "100%",
|
|
248
|
-
maxHeight: "100%",
|
|
249
|
-
margin: 0,
|
|
250
|
-
fullscreen: true,
|
|
251
|
-
});
|
|
252
|
-
this.ctx.ui.setFocus(selector);
|
|
253
|
-
this.ctx.ui.requestRender();
|
|
262
|
+
overlayHandle = this.#showFullscreenMenu(selector);
|
|
254
263
|
});
|
|
255
264
|
}
|
|
256
265
|
|
|
@@ -1023,15 +1032,7 @@ export class SelectorController {
|
|
|
1023
1032
|
initialProviderId: hubOptions.initialProviderId,
|
|
1024
1033
|
},
|
|
1025
1034
|
);
|
|
1026
|
-
overlayHandle = this
|
|
1027
|
-
anchor: "bottom-center",
|
|
1028
|
-
width: "100%",
|
|
1029
|
-
maxHeight: "100%",
|
|
1030
|
-
margin: 0,
|
|
1031
|
-
fullscreen: true,
|
|
1032
|
-
});
|
|
1033
|
-
this.ctx.ui.setFocus(hub);
|
|
1034
|
-
this.ctx.ui.requestRender();
|
|
1035
|
+
overlayHandle = this.#showFullscreenMenu(hub);
|
|
1035
1036
|
}
|
|
1036
1037
|
|
|
1037
1038
|
/** /login round-trip for a locked provider; reopen the hub on that provider only after a successful login. */
|
|
@@ -2000,27 +2001,23 @@ export class SelectorController {
|
|
|
2000
2001
|
...this.ctx.keybindings.getKeys("app.agents.hub"),
|
|
2001
2002
|
...this.ctx.keybindings.getKeys("app.session.observe"),
|
|
2002
2003
|
];
|
|
2003
|
-
let
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
// every other selector (model, session, tree, the `ask` tool) uses —
|
|
2007
|
-
// rather than a floating overlay. A non-fullscreen overlay composited over
|
|
2008
|
-
// a live transcript strands a stale copy in native scrollback every time a
|
|
2009
|
-
// running subagent's progress grows the frame and scrolls the window; the
|
|
2010
|
-
// hub is opened mid-run, so those copies stacked into a wall of duplicate
|
|
2011
|
-
// "Agent Hub" frames bleeding the task tree behind them. As an editor-slot
|
|
2012
|
-
// component it rides the normal append-only commit path: the transcript
|
|
2013
|
-
// commits above it exactly once and the hub repaints in place.
|
|
2004
|
+
let overlayHandle: OverlayHandle | undefined;
|
|
2005
|
+
let closed = false;
|
|
2006
|
+
|
|
2014
2007
|
const done = () => {
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2008
|
+
if (closed) return;
|
|
2009
|
+
closed = true;
|
|
2010
|
+
hub.dispose();
|
|
2011
|
+
overlayHandle?.hide();
|
|
2012
|
+
// A gated empty Hub may never have been mounted. Restoring editor
|
|
2013
|
+
// focus in that case would steal focus from a menu opened meanwhile.
|
|
2014
|
+
if (overlayHandle) this.focusActiveEditorArea();
|
|
2019
2015
|
this.ctx.ui.requestRender();
|
|
2020
2016
|
};
|
|
2021
2017
|
|
|
2022
|
-
hub = new AgentHubOverlayComponent({
|
|
2018
|
+
const hub = new AgentHubOverlayComponent({
|
|
2023
2019
|
observers,
|
|
2020
|
+
settings: this.ctx.settings,
|
|
2024
2021
|
hubKeys,
|
|
2025
2022
|
expandKeys: this.ctx.keybindings.getKeys("app.tools.expand"),
|
|
2026
2023
|
onDone: done,
|
|
@@ -2038,30 +2035,24 @@ export class SelectorController {
|
|
|
2038
2035
|
});
|
|
2039
2036
|
|
|
2040
2037
|
const showReadyHub = () => {
|
|
2041
|
-
|
|
2042
|
-
//
|
|
2043
|
-
//
|
|
2044
|
-
// hub instead of treating the initial empty table as authoritative.
|
|
2038
|
+
if (closed) return;
|
|
2039
|
+
// The double-← gesture stays inert when neither live nor persisted
|
|
2040
|
+
// subagents are available, so wait for discovery before making the gate.
|
|
2045
2041
|
if (options?.requireContent && hub.isEmpty) {
|
|
2046
|
-
|
|
2042
|
+
done();
|
|
2047
2043
|
return;
|
|
2048
2044
|
}
|
|
2049
2045
|
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
this.ctx.ui.setFocus(hub);
|
|
2053
|
-
// When the hub was raised by the editor's double-← gesture, prime its own
|
|
2054
|
-
// close detector so the *next* single ← dismisses it — the two taps that
|
|
2055
|
-
// opened it were consumed by the editor's detector (issue #4780).
|
|
2046
|
+
// Prime the detector before the first frame when the editor's double-←
|
|
2047
|
+
// gesture opened the hub, so the next single ← dismisses it.
|
|
2056
2048
|
if (options?.armCloseTap) hub.armCloseTap();
|
|
2057
|
-
this
|
|
2049
|
+
overlayHandle = this.#showFullscreenMenu(hub);
|
|
2058
2050
|
};
|
|
2059
2051
|
|
|
2060
2052
|
if (options?.requireContent && hub.isEmpty) {
|
|
2061
2053
|
void hub.persistedSubagentsReady.then(showReadyHub);
|
|
2062
|
-
|
|
2054
|
+
} else {
|
|
2055
|
+
showReadyHub();
|
|
2063
2056
|
}
|
|
2064
|
-
|
|
2065
|
-
showReadyHub();
|
|
2066
2057
|
}
|
|
2067
2058
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { type Component, getSegmenter } from "@oh-my-pi/pi-tui";
|
|
3
|
-
import { LRUCache } from "
|
|
3
|
+
import { LRUCache } from "@oh-my-pi/pi-utils/lru";
|
|
4
4
|
import { formatThinkingForDisplay, hasDisplayableThinking } from "../../utils/thinking-display";
|
|
5
5
|
import type { AssistantMessageComponent } from "../components/assistant-message";
|
|
6
6
|
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
prompt,
|
|
53
53
|
setProjectDir,
|
|
54
54
|
} from "@oh-my-pi/pi-utils";
|
|
55
|
-
import chalk from "chalk";
|
|
55
|
+
import chalk from "@oh-my-pi/pi-utils/chalk";
|
|
56
56
|
import { reset as resetCapabilities } from "../capability";
|
|
57
57
|
import type { CollabGuestLink } from "../collab/guest";
|
|
58
58
|
import type { CollabHost } from "../collab/host";
|
|
@@ -4141,7 +4141,14 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4141
4141
|
this.ui.requestRender();
|
|
4142
4142
|
}
|
|
4143
4143
|
|
|
4144
|
-
/**
|
|
4144
|
+
/**
|
|
4145
|
+
* Defer transcript command panels while the agent is streaming, then mount
|
|
4146
|
+
* them at the next settle, terminal or not. A non-terminal settle is only a
|
|
4147
|
+
* scheduling pause, so resumed streaming can still land below a panel
|
|
4148
|
+
* flushed there. That is preferred over leaving it queued behind a command
|
|
4149
|
+
* the user runs during the pause, which mounts immediately and would put the
|
|
4150
|
+
* older panel out of order.
|
|
4151
|
+
*/
|
|
4145
4152
|
presentCommandOutput(content: Component | readonly Component[]): void {
|
|
4146
4153
|
if (!this.session.isStreaming) {
|
|
4147
4154
|
this.present(content);
|
|
@@ -4154,6 +4161,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
4154
4161
|
this.#pendingCommandOutputSessionId = sessionId;
|
|
4155
4162
|
const items = Array.isArray(content) ? content : [content as Component];
|
|
4156
4163
|
this.#pendingCommandOutput.push(...items);
|
|
4164
|
+
// No feedback here on purpose: mounting anything into the transcript
|
|
4165
|
+
// mid-turn (even a status line) re-renders rows below the growing live
|
|
4166
|
+
// block and duplicates them in native scrollback — the exact regression
|
|
4167
|
+
// issues #4806/#6767 pin. The queue flushes at the next settle.
|
|
4157
4168
|
}
|
|
4158
4169
|
|
|
4159
4170
|
/** Mount every command panel queued for the current session while the agent was streaming. */
|
package/src/modes/print-mode.ts
CHANGED
|
@@ -91,11 +91,29 @@ export function printableEvent(event: AgentSessionEvent): unknown {
|
|
|
91
91
|
export async function runPrintMode(session: AgentSession, options: PrintModeOptions): Promise<void> {
|
|
92
92
|
const { mode, messages = [], initialMessage, initialImages, printThoughts } = options;
|
|
93
93
|
|
|
94
|
+
// process.stdout.write is fire-and-forget: a large final record (e.g. a
|
|
95
|
+
// multi-MB agent_end) can be dropped when the process exits before the pipe
|
|
96
|
+
// drains, truncating the record mid-line while the process still exits 0.
|
|
97
|
+
// Serialize every stdout write on the previous write's completion callback so
|
|
98
|
+
// records stay ordered and honor backpressure, then block shutdown on the
|
|
99
|
+
// tail before dispose/exit. Same truncation class as issue #5309 (issue #7635).
|
|
100
|
+
let stdoutTail: Promise<void> = Promise.resolve();
|
|
101
|
+
const writeStdoutLine = (text: string): void => {
|
|
102
|
+
stdoutTail = stdoutTail.then(() => {
|
|
103
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
104
|
+
process.stdout.write(text, err => {
|
|
105
|
+
if (err) reject(err);
|
|
106
|
+
else resolve();
|
|
107
|
+
});
|
|
108
|
+
return promise;
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
|
|
94
112
|
// Emit session header for JSON mode
|
|
95
113
|
if (mode === "json") {
|
|
96
114
|
const header = session.sessionManager.getHeader();
|
|
97
115
|
if (header) {
|
|
98
|
-
|
|
116
|
+
writeStdoutLine(`${JSON.stringify(header)}\n`);
|
|
99
117
|
}
|
|
100
118
|
}
|
|
101
119
|
// Set up extensions for print mode (no UI, no command context)
|
|
@@ -172,7 +190,7 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
|
|
172
190
|
}
|
|
173
191
|
// In JSON mode, output all events
|
|
174
192
|
if (mode === "json") {
|
|
175
|
-
|
|
193
|
+
writeStdoutLine(`${JSON.stringify(printableEvent(event))}\n`);
|
|
176
194
|
}
|
|
177
195
|
});
|
|
178
196
|
|
|
@@ -186,12 +204,14 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
|
|
186
204
|
// Send initial message with attachments
|
|
187
205
|
if (initialMessage !== undefined) {
|
|
188
206
|
writeTextWorkingIndicator();
|
|
207
|
+
if (mode === "text") session.setTextOutputCommitted(false);
|
|
189
208
|
await logger.time("print:prompt:initial", () => session.prompt(initialMessage, { images: initialImages }));
|
|
190
209
|
}
|
|
191
210
|
|
|
192
211
|
// Send remaining messages
|
|
193
212
|
for (const message of messages) {
|
|
194
213
|
writeTextWorkingIndicator();
|
|
214
|
+
if (mode === "text") session.setTextOutputCommitted(false);
|
|
195
215
|
await logger.time("print:prompt:next", () => session.prompt(message));
|
|
196
216
|
}
|
|
197
217
|
|
|
@@ -243,23 +263,20 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
|
|
243
263
|
// Output text content
|
|
244
264
|
for (const content of assistantMsg.content) {
|
|
245
265
|
if (content.type === "text") {
|
|
246
|
-
|
|
266
|
+
writeStdoutLine(`${sanitizeText(content.text)}\n`);
|
|
247
267
|
} else if (printThoughts && content.type === "thinking" && content.thinking.trim().length > 0) {
|
|
248
|
-
|
|
268
|
+
writeStdoutLine(`${sanitizeText(content.thinking)}\n`);
|
|
249
269
|
}
|
|
250
270
|
}
|
|
251
271
|
}
|
|
272
|
+
session.setTextOutputCommitted(true);
|
|
252
273
|
}
|
|
253
274
|
|
|
254
275
|
await session.waitForAdvisorCatchup(PRINT_MODE_ADVISOR_DRAIN_TIMEOUT_MS);
|
|
255
276
|
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (err) reject(err);
|
|
261
|
-
else resolve();
|
|
262
|
-
});
|
|
263
|
-
});
|
|
277
|
+
// Block shutdown until every serialized stdout write (including the final
|
|
278
|
+
// agent_end and late JSON advisor events) has drained; process.exit would
|
|
279
|
+
// otherwise discard the buffered tail and truncate the last record.
|
|
280
|
+
await stdoutTail;
|
|
264
281
|
await session.dispose({ mnemopiConsolidateTimeoutMs: SHUTDOWN_CONSOLIDATE_BUDGET_MS });
|
|
265
282
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { once } from "node:events";
|
|
14
14
|
import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
15
|
-
import {
|
|
15
|
+
import { toolWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
|
|
16
16
|
import { $env, isRecord, readLines, Snowflake } from "@oh-my-pi/pi-utils";
|
|
17
17
|
import { reset as resetCapabilities } from "../../capability";
|
|
18
18
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
@@ -1095,7 +1095,7 @@ export async function runRpcMode(
|
|
|
1095
1095
|
dumpTools: session.agent.state.tools.map(tool => ({
|
|
1096
1096
|
name: tool.name,
|
|
1097
1097
|
description: tool.description,
|
|
1098
|
-
parameters:
|
|
1098
|
+
parameters: toolWireSchema(tool),
|
|
1099
1099
|
examples: tool.examples,
|
|
1100
1100
|
})),
|
|
1101
1101
|
contextUsage: session.getContextUsage(),
|
|
@@ -84,7 +84,7 @@ export async function initializeExtensions(session: AgentSession, options: Initi
|
|
|
84
84
|
session.sessionManager.appendLabelChange(targetId, label);
|
|
85
85
|
},
|
|
86
86
|
getActiveTools: () => session.getEnabledToolNames(),
|
|
87
|
-
getAllTools: () => session.
|
|
87
|
+
getAllTools: () => session.getAllToolInfos(),
|
|
88
88
|
setActiveTools: (toolNames: string[]) => session.setActiveToolsByName(toolNames),
|
|
89
89
|
getCommands: () => getSessionSlashCommands(session),
|
|
90
90
|
setModel: model => runExtensionSetModel(session, model),
|
|
@@ -91,6 +91,11 @@ export class SessionObserverRegistry {
|
|
|
91
91
|
this.#notifyListeners("main");
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/** Return one tracked session without copying or sorting the registry. */
|
|
95
|
+
getSession(id: string): ObservableSession | undefined {
|
|
96
|
+
return this.#sessions.get(id);
|
|
97
|
+
}
|
|
98
|
+
|
|
94
99
|
getSessions(): ObservableSession[] {
|
|
95
100
|
const sessions = [...this.#sessions.values()];
|
|
96
101
|
sessions.sort((a, b) => {
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -20,8 +20,8 @@ import type {
|
|
|
20
20
|
TerminalAppearance,
|
|
21
21
|
} from "@oh-my-pi/pi-tui";
|
|
22
22
|
import { adjustHsv, colorLuma, getCustomThemesDir, isEnoent, logger, relativeLuminance } from "@oh-my-pi/pi-utils";
|
|
23
|
-
import chalk from "chalk";
|
|
24
|
-
import { LRUCache } from "
|
|
23
|
+
import chalk from "@oh-my-pi/pi-utils/chalk";
|
|
24
|
+
import { LRUCache } from "@oh-my-pi/pi-utils/lru";
|
|
25
25
|
// Embed theme JSON files at build time
|
|
26
26
|
import darkThemeJson from "./dark.json" with { type: "json" };
|
|
27
27
|
import { defaultThemes } from "./defaults";
|
|
@@ -16,7 +16,7 @@ Controls the host desktop with a JS script: windows, screenshots, native input,
|
|
|
16
16
|
- Pointer `x,y` are pixels in the MOST RECENT screenshot of the SAME target (window or desktop). No screenshot of that target yet → coordinate input throws. AX coordinates (`.bounds()`, `elementAt`) are global desktop coords — two spaces, both converted automatically; never mix them.
|
|
17
17
|
- Each `.ax()` of a window starts a new ref generation; refs from the current and previous snapshot stay valid, older ones throw StaleRef — re-snapshot, don't guess.
|
|
18
18
|
- Input defaults to `delivery: "background"` — delivered to the target window without touching the user's focus, pointer, or window order. On macOS, keyboard input to an app with multiple windows throws `BackgroundUnavailable` because the OS accepts only a process id and could send keys to a different window; retry with `delivery: "foreground"` (briefly activates the target, acts, restores focus) or act through AX instead. Targets whose input stack drops other background events also throw `BackgroundUnavailable` naming the window class and event kind. Never assume a background action landed because no error was displayed — errors are how this surface reports failure.
|
|
19
|
-
- Wayland only:
|
|
19
|
+
- Wayland only: per-window native input and `raise()` are unavailable; use AX actions, or desktop input after focusing the target yourself.
|
|
20
20
|
- `read_only: true` for pure inspection — input and mutation throw, approval is lighter.
|
|
21
21
|
- Screenshots auto-display to you and save full-res to a temp path; pass `{silent: true}` in loops.
|
|
22
22
|
|
|
@@ -20,18 +20,31 @@
|
|
|
20
20
|
* a superseded revive) can never clobber a newer same-id ref.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import
|
|
23
|
+
import * as fs from "node:fs/promises";
|
|
24
|
+
import { logger, untilAborted } from "@oh-my-pi/pi-utils";
|
|
24
25
|
import type { AgentSession } from "../session/agent-session";
|
|
26
|
+
import { trackLateCleanup } from "../utils/late-cleanup";
|
|
25
27
|
import {
|
|
26
28
|
type AgentRef,
|
|
27
29
|
type AgentRefExpectation,
|
|
28
30
|
AgentRegistry,
|
|
31
|
+
getAgentTombstonePath,
|
|
29
32
|
MAIN_AGENT_ID,
|
|
30
33
|
type RegistryEvent,
|
|
31
34
|
} from "./agent-registry";
|
|
32
35
|
|
|
33
36
|
export type AgentReviver = (expected: AgentRef) => Promise<AgentSession>;
|
|
34
37
|
|
|
38
|
+
const AGENT_RELEASE_GRACE_MS = 5000;
|
|
39
|
+
|
|
40
|
+
async function persistAgentTombstone(sessionFile: string): Promise<void> {
|
|
41
|
+
try {
|
|
42
|
+
await fs.writeFile(getAgentTombstonePath(sessionFile), "", { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
/**
|
|
36
49
|
* Builds a reviver for a `parked` ref restored from disk (Agent Hub scan,
|
|
37
50
|
* collab mirror, resumed process) that carries a sessionFile but no in-memory
|
|
@@ -379,13 +392,10 @@ export class AgentLifecycleManager {
|
|
|
379
392
|
}
|
|
380
393
|
|
|
381
394
|
if (options?.tombstone) {
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
// createAgentSession's dispose wrapper (unregisterUnlessParked) preserve
|
|
387
|
-
// the ref instead of removing it, so a later persisted-subagent rescan
|
|
388
|
-
// skips it. The transcript file is left intact (history://<id>).
|
|
395
|
+
// Persist the terminal decision before detaching the session. The
|
|
396
|
+
// sidecar prevents a later discovery pass from reviving this transcript
|
|
397
|
+
// as a fresh parked ref.
|
|
398
|
+
if (ref.sessionFile) await persistAgentTombstone(ref.sessionFile);
|
|
389
399
|
this.#registry.setStatus(id, "aborted", ref);
|
|
390
400
|
}
|
|
391
401
|
const live = this.#registry.get(id) === ref ? ref.session : null;
|
|
@@ -402,11 +412,26 @@ export class AgentLifecycleManager {
|
|
|
402
412
|
}
|
|
403
413
|
|
|
404
414
|
/** Teardown everything (process exit / main session dispose). */
|
|
405
|
-
async dispose(): Promise<void> {
|
|
415
|
+
async dispose(deadlineAt: number = Date.now() + AGENT_RELEASE_GRACE_MS): Promise<void> {
|
|
406
416
|
this.#unsubscribe?.();
|
|
407
417
|
this.#unsubscribe = undefined;
|
|
408
418
|
const ids = [...new Set([...this.#adopted.keys(), ...this.#parks.keys()])];
|
|
409
|
-
await Promise.all(
|
|
419
|
+
await Promise.all(
|
|
420
|
+
ids.map(async id => {
|
|
421
|
+
const release = this.release(id).then(() => {});
|
|
422
|
+
try {
|
|
423
|
+
await untilAborted(AbortSignal.timeout(Math.max(0, deadlineAt - Date.now())), () => release);
|
|
424
|
+
} catch (error) {
|
|
425
|
+
if (Date.now() >= deadlineAt) {
|
|
426
|
+
trackLateCleanup(release, { id, resource: "adopted-agent" });
|
|
427
|
+
}
|
|
428
|
+
logger.warn("Agent cleanup exceeded its deadline", {
|
|
429
|
+
id,
|
|
430
|
+
error: error instanceof Error ? error.message : String(error),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}),
|
|
434
|
+
);
|
|
410
435
|
this.#revivals.clear();
|
|
411
436
|
this.#parks.clear();
|
|
412
437
|
this.#persistedReviverFactory = undefined;
|
|
@@ -14,6 +14,13 @@ import { oneLineLabel } from "../task/types";
|
|
|
14
14
|
|
|
15
15
|
export const MAIN_AGENT_ID = "Main";
|
|
16
16
|
|
|
17
|
+
/** Sidecar marker retained beside a child transcript after an explicit kill. */
|
|
18
|
+
export const AGENT_TOMBSTONE_SUFFIX = ".tombstone";
|
|
19
|
+
|
|
20
|
+
export function getAgentTombstonePath(sessionFile: string): string {
|
|
21
|
+
return `${sessionFile}${AGENT_TOMBSTONE_SUFFIX}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
/**
|
|
18
25
|
* - `running`: a turn is in flight.
|
|
19
26
|
* - `idle`: live AgentSession in memory, awaiting work. Finished agents are
|
|
@@ -22,6 +29,8 @@ export const MAIN_AGENT_ID = "Main";
|
|
|
22
29
|
* - `aborted`: hard-killed, terminal.
|
|
23
30
|
*/
|
|
24
31
|
export type AgentStatus = "running" | "idle" | "parked" | "aborted";
|
|
32
|
+
/** Provenance of a displayed duration: active runtime, transcript span, or unavailable. */
|
|
33
|
+
export type AgentDurationKind = "active" | "span" | "unknown";
|
|
25
34
|
/**
|
|
26
35
|
* - `main`/`sub`: the user-facing agent tree (driving agent + task subagents).
|
|
27
36
|
* - `advisor`: a passive review transcript persisted like a subagent for usage
|
|
@@ -30,6 +39,35 @@ export type AgentStatus = "running" | "idle" | "parked" | "aborted";
|
|
|
30
39
|
*/
|
|
31
40
|
export type AgentKind = "main" | "sub" | "advisor";
|
|
32
41
|
|
|
42
|
+
/** Persisted per-agent totals reconstructed from the child session transcript. */
|
|
43
|
+
export interface AgentMetricsSummary {
|
|
44
|
+
tokens: number;
|
|
45
|
+
requests: number;
|
|
46
|
+
tools: number;
|
|
47
|
+
cost: number;
|
|
48
|
+
durationMs: number;
|
|
49
|
+
durationKind?: AgentDurationKind;
|
|
50
|
+
contextTokens?: number;
|
|
51
|
+
contextWindow?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Historical identity and telemetry that remain available after the live session is disposed. */
|
|
55
|
+
export interface AgentHistorySummary {
|
|
56
|
+
agent?: string;
|
|
57
|
+
modelRole?: string;
|
|
58
|
+
resolvedModel?: string;
|
|
59
|
+
/** Whether the last resolved model was selected by retry fallback routing. */
|
|
60
|
+
resolvedModelIsFallback?: boolean;
|
|
61
|
+
metrics?: AgentMetricsSummary;
|
|
62
|
+
readOnly?: boolean;
|
|
63
|
+
/** Durable task output artifact, when the executor wrote one. */
|
|
64
|
+
outputPath?: string;
|
|
65
|
+
/** Captured isolated-worktree patch, when patch capture succeeded. */
|
|
66
|
+
patchPath?: string;
|
|
67
|
+
/** Isolated branch identity, when branch-mode capture succeeded. */
|
|
68
|
+
branchName?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
33
71
|
export interface AgentRef {
|
|
34
72
|
id: string;
|
|
35
73
|
displayName: string;
|
|
@@ -43,6 +81,8 @@ export interface AgentRef {
|
|
|
43
81
|
lastActivity: number;
|
|
44
82
|
/** Short gist of what the agent is currently doing (latest intent or tool), for the work-aware roster. Display-only. */
|
|
45
83
|
activity?: string;
|
|
84
|
+
/** Persisted identity and telemetry restored after the live observer is gone. */
|
|
85
|
+
history?: AgentHistorySummary;
|
|
46
86
|
}
|
|
47
87
|
|
|
48
88
|
export type AgentRefExpectation = AgentRef | AgentSession;
|
|
@@ -50,6 +90,7 @@ export type AgentRefExpectation = AgentRef | AgentSession;
|
|
|
50
90
|
export type RegistryEvent =
|
|
51
91
|
| { type: "registered"; ref: AgentRef }
|
|
52
92
|
| { type: "status_changed"; ref: AgentRef }
|
|
93
|
+
| { type: "metadata_changed"; ref: AgentRef }
|
|
53
94
|
| { type: "removed"; ref: AgentRef };
|
|
54
95
|
|
|
55
96
|
type RegistryListener = (event: RegistryEvent) => void;
|
|
@@ -62,6 +103,14 @@ export interface RegisterInput {
|
|
|
62
103
|
session: AgentSession | null;
|
|
63
104
|
sessionFile?: string | null;
|
|
64
105
|
status?: AgentStatus;
|
|
106
|
+
/** Last persisted task summary, when restoring a historical agent. */
|
|
107
|
+
activity?: string;
|
|
108
|
+
/** Original registration timestamp, when known from persisted history. */
|
|
109
|
+
createdAt?: number;
|
|
110
|
+
/** Last transcript activity timestamp, when known from persisted history. */
|
|
111
|
+
lastActivity?: number;
|
|
112
|
+
/** Persisted identity and telemetry restored after the live observer is gone. */
|
|
113
|
+
history?: AgentHistorySummary;
|
|
65
114
|
}
|
|
66
115
|
|
|
67
116
|
export class AgentRegistry {
|
|
@@ -96,8 +145,10 @@ export class AgentRegistry {
|
|
|
96
145
|
status: input.status ?? "running",
|
|
97
146
|
session: input.session,
|
|
98
147
|
sessionFile: input.sessionFile ?? null,
|
|
99
|
-
createdAt: now,
|
|
100
|
-
lastActivity: now,
|
|
148
|
+
createdAt: input.createdAt ?? now,
|
|
149
|
+
lastActivity: input.lastActivity ?? now,
|
|
150
|
+
activity: input.activity,
|
|
151
|
+
history: input.history,
|
|
101
152
|
};
|
|
102
153
|
this.#refs.set(ref.id, ref);
|
|
103
154
|
this.#emit({ type: "registered", ref });
|
|
@@ -116,6 +167,18 @@ export class AgentRegistry {
|
|
|
116
167
|
return current === expected && current.status === "parked" && !current.session ? current : undefined;
|
|
117
168
|
}
|
|
118
169
|
|
|
170
|
+
/** Attach transcript-derived identity and telemetry without changing lifecycle state. */
|
|
171
|
+
setHistory(id: string, history: AgentHistorySummary, expectedSessionFile?: string): boolean {
|
|
172
|
+
const ref = this.#refs.get(id);
|
|
173
|
+
if (!ref || (expectedSessionFile !== undefined && ref.sessionFile !== expectedSessionFile)) return false;
|
|
174
|
+
const definedHistory = Object.fromEntries(
|
|
175
|
+
Object.entries(history).filter(([, value]) => value !== undefined),
|
|
176
|
+
) as AgentHistorySummary;
|
|
177
|
+
ref.history = { ...ref.history, ...definedHistory };
|
|
178
|
+
this.#emit({ type: "metadata_changed", ref });
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
119
182
|
setStatus(id: string, status: AgentStatus, expected?: AgentRefExpectation): boolean {
|
|
120
183
|
const ref = this.#refs.get(id);
|
|
121
184
|
if (!ref || !this.#matchesExpected(ref, expected)) return false;
|