@oh-my-pi/pi-coding-agent 12.18.1 → 12.19.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 +47 -0
- package/package.json +7 -7
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +341 -0
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/list-models.ts +3 -17
- package/src/cli/stats-cli.ts +3 -22
- package/src/cli/web-search-cli.ts +8 -16
- package/src/commit/agentic/agent.ts +6 -9
- package/src/commit/agentic/index.ts +44 -50
- package/src/commit/agentic/state.ts +0 -9
- package/src/commit/agentic/tools/propose-commit.ts +1 -30
- package/src/commit/agentic/tools/schemas.ts +31 -0
- package/src/commit/agentic/tools/split-commit.ts +1 -30
- package/src/commit/agentic/validation.ts +1 -18
- package/src/commit/analysis/conventional.ts +3 -50
- package/src/commit/analysis/summary.ts +2 -13
- package/src/commit/changelog/detect.ts +4 -1
- package/src/commit/changelog/generate.ts +2 -25
- package/src/commit/changelog/index.ts +1 -2
- package/src/commit/cli.ts +4 -12
- package/src/commit/map-reduce/reduce-phase.ts +2 -43
- package/src/commit/pipeline.ts +7 -15
- package/src/commit/utils.ts +44 -0
- package/src/config/prompt-templates.ts +1 -81
- package/src/config/settings-schema.ts +20 -1
- package/src/config.ts +2 -3
- package/src/debug/index.ts +1 -6
- package/src/debug/system-info.ts +2 -6
- package/src/discovery/builtin.ts +5 -9
- package/src/discovery/helpers.ts +0 -26
- package/src/discovery/ssh.ts +1 -8
- package/src/exa/company.ts +8 -39
- package/src/exa/factory.ts +64 -0
- package/src/exa/index.ts +0 -16
- package/src/exa/linkedin.ts +8 -39
- package/src/exa/mcp-client.ts +0 -64
- package/src/exa/researcher.ts +17 -59
- package/src/exa/search.ts +30 -154
- package/src/extensibility/custom-tools/loader.ts +3 -41
- package/src/extensibility/extensions/loader.ts +2 -9
- package/src/extensibility/hooks/loader.ts +3 -20
- package/src/extensibility/hooks/runner.ts +3 -19
- package/src/extensibility/plugins/installer.ts +2 -1
- package/src/extensibility/plugins/loader.ts +29 -117
- package/src/extensibility/skills.ts +2 -89
- package/src/extensibility/slash-commands.ts +1 -63
- package/src/extensibility/utils.ts +38 -0
- package/src/index.ts +9 -25
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/jobs-protocol.ts +118 -0
- package/src/ipy/kernel.ts +2 -0
- package/src/lsp/config.ts +1 -5
- package/src/lsp/lspmux.ts +0 -17
- package/src/lsp/utils.ts +2 -24
- package/src/main.ts +16 -24
- package/src/mcp/client.ts +1 -46
- package/src/mcp/render.ts +8 -1
- package/src/mcp/tool-cache.ts +1 -5
- package/src/mcp/transports/http.ts +2 -7
- package/src/mcp/transports/stdio.ts +2 -7
- package/src/modes/components/bash-execution.ts +2 -16
- package/src/modes/components/extensions/inspector-panel.ts +8 -18
- package/src/modes/components/footer.ts +10 -50
- package/src/modes/components/model-selector.ts +2 -21
- package/src/modes/components/python-execution.ts +2 -16
- package/src/modes/components/settings-selector.ts +1 -10
- package/src/modes/components/status-line/segments.ts +8 -25
- package/src/modes/components/status-line.ts +14 -31
- package/src/modes/components/tool-execution.ts +8 -2
- package/src/modes/controllers/command-controller.ts +71 -30
- package/src/modes/controllers/event-controller.ts +34 -4
- package/src/modes/controllers/mcp-command-controller.ts +3 -34
- package/src/modes/controllers/selector-controller.ts +2 -2
- package/src/modes/controllers/ssh-command-controller.ts +3 -34
- package/src/modes/interactive-mode.ts +6 -2
- package/src/modes/rpc/rpc-client.ts +1 -5
- package/src/modes/shared.ts +73 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +26 -2
- package/src/patch/index.ts +4 -4
- package/src/patch/normalize.ts +22 -65
- package/src/patch/shared.ts +16 -16
- package/src/prompts/system/custom-system-prompt.md +0 -10
- package/src/prompts/system/system-prompt.md +69 -89
- package/src/prompts/tools/async-result.md +5 -0
- package/src/prompts/tools/bash.md +5 -0
- package/src/prompts/tools/cancel-job.md +7 -0
- package/src/prompts/tools/poll-jobs.md +7 -0
- package/src/prompts/tools/task.md +4 -0
- package/src/sdk.ts +70 -6
- package/src/session/agent-session.ts +40 -6
- package/src/session/agent-storage.ts +69 -278
- package/src/session/auth-storage.ts +14 -1430
- package/src/session/session-manager.ts +69 -5
- package/src/session/session-storage.ts +1 -5
- package/src/session/streaming-output.ts +637 -76
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/ssh/connection-manager.ts +4 -12
- package/src/ssh/sshfs-mount.ts +3 -7
- package/src/ssh/utils.ts +8 -0
- package/src/system-prompt.ts +24 -90
- package/src/task/executor.ts +11 -1
- package/src/task/index.ts +258 -13
- package/src/task/parallel.ts +32 -0
- package/src/task/render.ts +15 -7
- package/src/task/types.ts +5 -0
- package/src/tools/ask.ts +4 -7
- package/src/tools/bash-interactive.ts +4 -5
- package/src/tools/bash.ts +125 -41
- package/src/tools/cancel-job.ts +93 -0
- package/src/tools/fetch.ts +7 -27
- package/src/tools/find.ts +3 -3
- package/src/tools/gemini-image.ts +15 -14
- package/src/tools/grep.ts +3 -3
- package/src/tools/index.ts +13 -29
- package/src/tools/json-tree.ts +12 -1
- package/src/tools/jtd-to-json-schema.ts +10 -74
- package/src/tools/jtd-to-typescript.ts +10 -72
- package/src/tools/jtd-utils.ts +102 -0
- package/src/tools/notebook.ts +4 -9
- package/src/tools/output-meta.ts +52 -26
- package/src/tools/path-utils.ts +13 -7
- package/src/tools/poll-jobs.ts +178 -0
- package/src/tools/python.ts +32 -35
- package/src/tools/read.ts +61 -82
- package/src/tools/render-utils.ts +8 -159
- package/src/tools/ssh.ts +7 -20
- package/src/tools/submit-result.ts +1 -1
- package/src/tools/tool-errors.ts +0 -30
- package/src/tools/tool-result.ts +1 -2
- package/src/tools/write.ts +8 -10
- package/src/tui/code-cell.ts +8 -3
- package/src/tui/status-line.ts +4 -4
- package/src/tui/types.ts +0 -1
- package/src/tui/utils.ts +1 -14
- package/src/utils/command-args.ts +76 -0
- package/src/utils/file-mentions.ts +15 -19
- package/src/utils/frontmatter.ts +5 -10
- package/src/utils/shell-snapshot.ts +0 -11
- package/src/utils/title-generator.ts +0 -12
- package/src/web/scrapers/artifacthub.ts +7 -16
- package/src/web/scrapers/arxiv.ts +3 -8
- package/src/web/scrapers/aur.ts +8 -22
- package/src/web/scrapers/biorxiv.ts +5 -14
- package/src/web/scrapers/bluesky.ts +13 -36
- package/src/web/scrapers/brew.ts +5 -10
- package/src/web/scrapers/cheatsh.ts +2 -12
- package/src/web/scrapers/chocolatey.ts +63 -26
- package/src/web/scrapers/choosealicense.ts +3 -18
- package/src/web/scrapers/cisa-kev.ts +4 -18
- package/src/web/scrapers/clojars.ts +6 -33
- package/src/web/scrapers/coingecko.ts +25 -33
- package/src/web/scrapers/crates-io.ts +7 -26
- package/src/web/scrapers/crossref.ts +4 -18
- package/src/web/scrapers/devto.ts +11 -41
- package/src/web/scrapers/discogs.ts +7 -10
- package/src/web/scrapers/discourse.ts +6 -31
- package/src/web/scrapers/dockerhub.ts +12 -35
- package/src/web/scrapers/fdroid.ts +8 -33
- package/src/web/scrapers/firefox-addons.ts +10 -34
- package/src/web/scrapers/flathub.ts +7 -24
- package/src/web/scrapers/github-gist.ts +2 -12
- package/src/web/scrapers/github.ts +9 -47
- package/src/web/scrapers/gitlab.ts +130 -185
- package/src/web/scrapers/go-pkg.ts +12 -22
- package/src/web/scrapers/hackage.ts +88 -43
- package/src/web/scrapers/hackernews.ts +25 -45
- package/src/web/scrapers/hex.ts +19 -36
- package/src/web/scrapers/huggingface.ts +26 -91
- package/src/web/scrapers/iacr.ts +3 -8
- package/src/web/scrapers/jetbrains-marketplace.ts +9 -20
- package/src/web/scrapers/lemmy.ts +5 -23
- package/src/web/scrapers/lobsters.ts +16 -28
- package/src/web/scrapers/mastodon.ts +24 -43
- package/src/web/scrapers/maven.ts +6 -21
- package/src/web/scrapers/mdn.ts +7 -11
- package/src/web/scrapers/metacpan.ts +9 -41
- package/src/web/scrapers/musicbrainz.ts +4 -28
- package/src/web/scrapers/npm.ts +8 -25
- package/src/web/scrapers/nuget.ts +14 -37
- package/src/web/scrapers/nvd.ts +6 -28
- package/src/web/scrapers/ollama.ts +7 -34
- package/src/web/scrapers/open-vsx.ts +5 -19
- package/src/web/scrapers/opencorporates.ts +30 -14
- package/src/web/scrapers/openlibrary.ts +49 -33
- package/src/web/scrapers/orcid.ts +4 -18
- package/src/web/scrapers/osv.ts +7 -24
- package/src/web/scrapers/packagist.ts +9 -24
- package/src/web/scrapers/pub-dev.ts +7 -50
- package/src/web/scrapers/pubmed.ts +54 -21
- package/src/web/scrapers/pypi.ts +8 -26
- package/src/web/scrapers/rawg.ts +11 -19
- package/src/web/scrapers/readthedocs.ts +4 -9
- package/src/web/scrapers/reddit.ts +5 -15
- package/src/web/scrapers/repology.ts +8 -20
- package/src/web/scrapers/rfc.ts +5 -14
- package/src/web/scrapers/rubygems.ts +6 -21
- package/src/web/scrapers/searchcode.ts +8 -36
- package/src/web/scrapers/sec-edgar.ts +4 -18
- package/src/web/scrapers/semantic-scholar.ts +15 -35
- package/src/web/scrapers/snapcraft.ts +5 -19
- package/src/web/scrapers/sourcegraph.ts +5 -43
- package/src/web/scrapers/spdx.ts +4 -18
- package/src/web/scrapers/spotify.ts +4 -23
- package/src/web/scrapers/stackoverflow.ts +8 -13
- package/src/web/scrapers/terraform.ts +9 -37
- package/src/web/scrapers/tldr.ts +3 -7
- package/src/web/scrapers/twitter.ts +3 -7
- package/src/web/scrapers/types.ts +105 -27
- package/src/web/scrapers/utils.ts +97 -103
- package/src/web/scrapers/vimeo.ts +7 -27
- package/src/web/scrapers/vscode-marketplace.ts +8 -17
- package/src/web/scrapers/w3c.ts +6 -14
- package/src/web/scrapers/wikidata.ts +5 -19
- package/src/web/scrapers/wikipedia.ts +2 -12
- package/src/web/scrapers/youtube.ts +5 -34
- package/src/web/search/index.ts +0 -9
- package/src/web/search/providers/anthropic.ts +3 -2
- package/src/web/search/providers/brave.ts +3 -18
- package/src/web/search/providers/exa.ts +1 -12
- package/src/web/search/providers/kimi.ts +5 -44
- package/src/web/search/providers/perplexity.ts +1 -12
- package/src/web/search/providers/synthetic.ts +3 -26
- package/src/web/search/providers/utils.ts +36 -0
- package/src/web/search/providers/zai.ts +9 -50
- package/src/web/search/types.ts +0 -28
- package/src/web/search/utils.ts +17 -0
- package/src/tools/output-utils.ts +0 -63
- package/src/tools/truncate.ts +0 -385
- package/src/web/search/auth.ts +0 -178
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [12.19.0] - 2026-02-22
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- Added `poll_jobs` tool to block until background jobs complete, providing an alternative to polling `read jobs://` in loops
|
|
9
|
+
- Added `task.maxConcurrency` setting to limit the number of concurrently executing subagent tasks
|
|
10
|
+
- Added support for rendering markdown output from Python cells with proper formatting and theme styling
|
|
11
|
+
- Added async background job execution for bash commands and tasks with `async: true` parameter
|
|
12
|
+
- Added `cancel_job` tool to cancel running background jobs
|
|
13
|
+
- Added `jobs://` internal protocol to inspect background job status and results
|
|
14
|
+
- Added `/jobs` slash command to display running and recent background jobs in interactive mode
|
|
15
|
+
- Added `async.enabled` and `async.maxJobs` settings to control background job execution
|
|
16
|
+
- Added background job status indicator in status line showing count of running jobs
|
|
17
|
+
- Added support for GitLab Duo authentication provider
|
|
18
|
+
- Added clearer truncation notices across tools with consistent line/size context and continuation hints
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Updated bash and task tool guidance to recommend `poll_jobs` instead of polling `read jobs://` in loops when waiting for async results
|
|
23
|
+
- Improved parallel task execution to schedule multiple background jobs independently instead of batching all tasks into a single job, enabling true concurrent execution
|
|
24
|
+
- Enhanced task progress tracking to report per-task status (pending, running, completed, failed, aborted) with individual timing and token metrics for each background task
|
|
25
|
+
- Updated background task messaging to provide real-time progress counts (e.g., '2/5 finished') and distinguish between single and multiple task jobs
|
|
26
|
+
- Hid internal `agent__intent` parameter from tool argument displays in UI and logs to reduce visual clutter
|
|
27
|
+
- Updated Python tool to detect and handle markdown display output separately from plain text
|
|
28
|
+
- Updated bash tool to support async execution mode with streaming progress updates
|
|
29
|
+
- Updated task tool to support async execution mode for parallel subagent execution
|
|
30
|
+
- Modified subagent settings to disable async execution in child agents to prevent nesting
|
|
31
|
+
- Updated tool execution component to handle background async task state without spinner animation
|
|
32
|
+
- Changed event controller to keep background tool calls pending until async completion
|
|
33
|
+
- Updated status line width calculation to accommodate background job indicator
|
|
34
|
+
- Updated the system prompt pipeline to reduce injected environment noise and make instructions more focused on execution quality
|
|
35
|
+
- Updated system prompt/workflow guidance to emphasize root-cause fixes, code quality, and explicit handoff/testing expectations
|
|
36
|
+
- Changed default value of `todo.reminders` setting from false to true to enable todo reminders by default
|
|
37
|
+
- Improved truncation/output handling for large command results to reduce memory pressure and keep previews responsive
|
|
38
|
+
- Updated internal artifact handling so tool output artifacts stay consistent across session switches and resumes
|
|
39
|
+
|
|
40
|
+
### Removed
|
|
41
|
+
|
|
42
|
+
- Removed git context (branch, status, commit history) from system prompt — version control information is no longer injected into agent instructions
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- Fixed task progress display to hide tool count and token metrics when zero, reducing visual clutter in status output
|
|
47
|
+
- Fixed Lobsters scraper to correctly parse API responses where user fields are strings instead of objects, resolving undefined user display in story listings
|
|
48
|
+
- Fixed artifact manager caching to properly invalidate when session file changes, preventing stale artifact references
|
|
49
|
+
- Fixed truncation behavior around UTF-8 boundaries and chunked output accounting
|
|
50
|
+
- Fixed `submit_result` schema generation to use valid JSON Schema when no explicit output schema is provided
|
|
51
|
+
|
|
5
52
|
## [12.18.1] - 2026-02-21
|
|
6
53
|
### Added
|
|
7
54
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "12.
|
|
4
|
+
"version": "12.19.0",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Bölük",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@mozilla/readability": "0.6.0",
|
|
44
|
-
"@oh-my-pi/omp-stats": "12.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "12.
|
|
46
|
-
"@oh-my-pi/pi-ai": "12.
|
|
47
|
-
"@oh-my-pi/pi-natives": "12.
|
|
48
|
-
"@oh-my-pi/pi-tui": "12.
|
|
49
|
-
"@oh-my-pi/pi-utils": "12.
|
|
44
|
+
"@oh-my-pi/omp-stats": "12.19.0",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "12.19.0",
|
|
46
|
+
"@oh-my-pi/pi-ai": "12.19.0",
|
|
47
|
+
"@oh-my-pi/pi-natives": "12.19.0",
|
|
48
|
+
"@oh-my-pi/pi-tui": "12.19.0",
|
|
49
|
+
"@oh-my-pi/pi-utils": "12.19.0",
|
|
50
50
|
"@sinclair/typebox": "^0.34.48",
|
|
51
51
|
"@xterm/headless": "^6.0.0",
|
|
52
52
|
"ajv": "^8.18.0",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./job-manager";
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { logger, Snowflake } from "@oh-my-pi/pi-utils";
|
|
2
|
+
|
|
3
|
+
const DELIVERY_RETRY_BASE_MS = 500;
|
|
4
|
+
const DELIVERY_RETRY_MAX_MS = 30_000;
|
|
5
|
+
const DELIVERY_RETRY_JITTER_MS = 200;
|
|
6
|
+
const DEFAULT_RETENTION_MS = 5 * 60 * 1000;
|
|
7
|
+
const DEFAULT_MAX_RUNNING_JOBS = 15;
|
|
8
|
+
|
|
9
|
+
export interface AsyncJob {
|
|
10
|
+
id: string;
|
|
11
|
+
type: "bash" | "task";
|
|
12
|
+
status: "running" | "completed" | "failed" | "cancelled";
|
|
13
|
+
startTime: number;
|
|
14
|
+
label: string;
|
|
15
|
+
abortController: AbortController;
|
|
16
|
+
promise: Promise<void>;
|
|
17
|
+
resultText?: string;
|
|
18
|
+
errorText?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface AsyncJobManagerOptions {
|
|
22
|
+
onJobComplete: (jobId: string, text: string, job?: AsyncJob) => void | Promise<void>;
|
|
23
|
+
maxRunningJobs?: number;
|
|
24
|
+
retentionMs?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface AsyncJobDelivery {
|
|
28
|
+
jobId: string;
|
|
29
|
+
text: string;
|
|
30
|
+
attempt: number;
|
|
31
|
+
nextAttemptAt: number;
|
|
32
|
+
lastError?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AsyncJobDeliveryState {
|
|
36
|
+
queued: number;
|
|
37
|
+
delivering: boolean;
|
|
38
|
+
nextRetryAt?: number;
|
|
39
|
+
pendingJobIds: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface AsyncJobRegisterOptions {
|
|
43
|
+
id?: string;
|
|
44
|
+
onProgress?: (text: string, details?: Record<string, unknown>) => void | Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class AsyncJobManager {
|
|
48
|
+
readonly #jobs = new Map<string, AsyncJob>();
|
|
49
|
+
readonly #deliveries: AsyncJobDelivery[] = [];
|
|
50
|
+
readonly #evictionTimers = new Map<string, NodeJS.Timeout>();
|
|
51
|
+
readonly #onJobComplete: AsyncJobManagerOptions["onJobComplete"];
|
|
52
|
+
readonly #maxRunningJobs: number;
|
|
53
|
+
readonly #retentionMs: number;
|
|
54
|
+
#deliveryLoop: Promise<void> | undefined;
|
|
55
|
+
#disposed = false;
|
|
56
|
+
|
|
57
|
+
constructor(options: AsyncJobManagerOptions) {
|
|
58
|
+
this.#onJobComplete = options.onJobComplete;
|
|
59
|
+
this.#maxRunningJobs = Math.max(1, Math.floor(options.maxRunningJobs ?? DEFAULT_MAX_RUNNING_JOBS));
|
|
60
|
+
this.#retentionMs = Math.max(0, Math.floor(options.retentionMs ?? DEFAULT_RETENTION_MS));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
register(
|
|
64
|
+
type: "bash" | "task",
|
|
65
|
+
label: string,
|
|
66
|
+
run: (ctx: {
|
|
67
|
+
jobId: string;
|
|
68
|
+
signal: AbortSignal;
|
|
69
|
+
reportProgress: (text: string, details?: Record<string, unknown>) => Promise<void>;
|
|
70
|
+
}) => Promise<string>,
|
|
71
|
+
options?: AsyncJobRegisterOptions,
|
|
72
|
+
): string {
|
|
73
|
+
if (this.#disposed) {
|
|
74
|
+
throw new Error("Async job manager is disposed");
|
|
75
|
+
}
|
|
76
|
+
const runningCount = this.getRunningJobs().length;
|
|
77
|
+
if (runningCount >= this.#maxRunningJobs) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Background job limit reached (${this.#maxRunningJobs}). Wait for running jobs to finish or cancel one.`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const id = this.#resolveJobId(options?.id);
|
|
84
|
+
const abortController = new AbortController();
|
|
85
|
+
const startTime = Date.now();
|
|
86
|
+
|
|
87
|
+
const job: AsyncJob = {
|
|
88
|
+
id,
|
|
89
|
+
type,
|
|
90
|
+
status: "running",
|
|
91
|
+
startTime,
|
|
92
|
+
label,
|
|
93
|
+
abortController,
|
|
94
|
+
promise: Promise.resolve(),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const reportProgress = async (text: string, details?: Record<string, unknown>): Promise<void> => {
|
|
98
|
+
if (!options?.onProgress) return;
|
|
99
|
+
try {
|
|
100
|
+
await options.onProgress(text, details);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
logger.warn("Async job progress callback failed", {
|
|
103
|
+
jobId: id,
|
|
104
|
+
error: error instanceof Error ? error.message : String(error),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
job.promise = (async () => {
|
|
109
|
+
try {
|
|
110
|
+
const text = await run({ jobId: id, signal: abortController.signal, reportProgress });
|
|
111
|
+
if (job.status === "cancelled") {
|
|
112
|
+
job.resultText = text;
|
|
113
|
+
this.#scheduleEviction(id);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
job.status = "completed";
|
|
117
|
+
job.resultText = text;
|
|
118
|
+
this.#enqueueDelivery(id, text);
|
|
119
|
+
this.#scheduleEviction(id);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (job.status === "cancelled") {
|
|
122
|
+
job.errorText = error instanceof Error ? error.message : String(error);
|
|
123
|
+
this.#scheduleEviction(id);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const errorText = error instanceof Error ? error.message : String(error);
|
|
127
|
+
job.status = "failed";
|
|
128
|
+
job.errorText = errorText;
|
|
129
|
+
this.#enqueueDelivery(id, errorText);
|
|
130
|
+
this.#scheduleEviction(id);
|
|
131
|
+
}
|
|
132
|
+
})();
|
|
133
|
+
|
|
134
|
+
this.#jobs.set(id, job);
|
|
135
|
+
return id;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
cancel(id: string): boolean {
|
|
139
|
+
const job = this.#jobs.get(id);
|
|
140
|
+
if (!job) return false;
|
|
141
|
+
if (job.status !== "running") return false;
|
|
142
|
+
job.status = "cancelled";
|
|
143
|
+
job.abortController.abort();
|
|
144
|
+
this.#scheduleEviction(id);
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
getJob(id: string): AsyncJob | undefined {
|
|
149
|
+
return this.#jobs.get(id);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getRunningJobs(): AsyncJob[] {
|
|
153
|
+
return Array.from(this.#jobs.values()).filter(job => job.status === "running");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getRecentJobs(limit = 10): AsyncJob[] {
|
|
157
|
+
return Array.from(this.#jobs.values())
|
|
158
|
+
.filter(job => job.status !== "running")
|
|
159
|
+
.sort((a, b) => b.startTime - a.startTime)
|
|
160
|
+
.slice(0, limit);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
getAllJobs(): AsyncJob[] {
|
|
164
|
+
return Array.from(this.#jobs.values());
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
getDeliveryState(): AsyncJobDeliveryState {
|
|
168
|
+
const nextRetryAt = this.#deliveries.reduce<number | undefined>((next, delivery) => {
|
|
169
|
+
if (next === undefined) return delivery.nextAttemptAt;
|
|
170
|
+
return Math.min(next, delivery.nextAttemptAt);
|
|
171
|
+
}, undefined);
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
queued: this.#deliveries.length,
|
|
175
|
+
delivering: this.#deliveryLoop !== undefined,
|
|
176
|
+
nextRetryAt,
|
|
177
|
+
pendingJobIds: this.#deliveries.map(delivery => delivery.jobId),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
hasPendingDeliveries(): boolean {
|
|
182
|
+
return this.#deliveries.length > 0;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
cancelAll(): void {
|
|
186
|
+
for (const job of this.getRunningJobs()) {
|
|
187
|
+
job.status = "cancelled";
|
|
188
|
+
job.abortController.abort();
|
|
189
|
+
this.#scheduleEviction(job.id);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async waitForAll(): Promise<void> {
|
|
194
|
+
await Promise.all(Array.from(this.#jobs.values()).map(job => job.promise));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async drainDeliveries(options?: { timeoutMs?: number }): Promise<boolean> {
|
|
198
|
+
const timeoutMs = options?.timeoutMs;
|
|
199
|
+
const hasDeadline = timeoutMs !== undefined;
|
|
200
|
+
const deadline = hasDeadline ? Date.now() + Math.max(timeoutMs, 0) : Number.POSITIVE_INFINITY;
|
|
201
|
+
|
|
202
|
+
while (this.hasPendingDeliveries()) {
|
|
203
|
+
this.#ensureDeliveryLoop();
|
|
204
|
+
const loop = this.#deliveryLoop;
|
|
205
|
+
if (!loop) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!hasDeadline) {
|
|
210
|
+
await loop;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const remainingMs = deadline - Date.now();
|
|
215
|
+
if (remainingMs <= 0) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
await Promise.race([loop, Bun.sleep(remainingMs)]);
|
|
220
|
+
if (Date.now() >= deadline && this.hasPendingDeliveries()) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async dispose(options?: { timeoutMs?: number }): Promise<boolean> {
|
|
229
|
+
this.#disposed = true;
|
|
230
|
+
this.#clearEvictionTimers();
|
|
231
|
+
this.cancelAll();
|
|
232
|
+
await this.waitForAll();
|
|
233
|
+
const drained = await this.drainDeliveries({ timeoutMs: options?.timeoutMs ?? 3_000 });
|
|
234
|
+
this.#clearEvictionTimers();
|
|
235
|
+
this.#jobs.clear();
|
|
236
|
+
this.#deliveries.length = 0;
|
|
237
|
+
return drained;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#resolveJobId(preferredId?: string): string {
|
|
241
|
+
if (!preferredId || preferredId.trim().length === 0) {
|
|
242
|
+
return `bg_${Snowflake.next()}`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const base = preferredId.trim();
|
|
246
|
+
if (!this.#jobs.has(base)) return base;
|
|
247
|
+
|
|
248
|
+
let suffix = 2;
|
|
249
|
+
let candidate = `${base}-${suffix}`;
|
|
250
|
+
while (this.#jobs.has(candidate)) {
|
|
251
|
+
suffix += 1;
|
|
252
|
+
candidate = `${base}-${suffix}`;
|
|
253
|
+
}
|
|
254
|
+
return candidate;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#scheduleEviction(jobId: string): void {
|
|
258
|
+
if (this.#retentionMs <= 0) {
|
|
259
|
+
this.#jobs.delete(jobId);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const existing = this.#evictionTimers.get(jobId);
|
|
263
|
+
if (existing) {
|
|
264
|
+
clearTimeout(existing);
|
|
265
|
+
}
|
|
266
|
+
const timer = setTimeout(() => {
|
|
267
|
+
this.#evictionTimers.delete(jobId);
|
|
268
|
+
this.#jobs.delete(jobId);
|
|
269
|
+
}, this.#retentionMs);
|
|
270
|
+
timer.unref();
|
|
271
|
+
this.#evictionTimers.set(jobId, timer);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#clearEvictionTimers(): void {
|
|
275
|
+
for (const timer of this.#evictionTimers.values()) {
|
|
276
|
+
clearTimeout(timer);
|
|
277
|
+
}
|
|
278
|
+
this.#evictionTimers.clear();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
#enqueueDelivery(jobId: string, text: string): void {
|
|
282
|
+
this.#deliveries.push({
|
|
283
|
+
jobId,
|
|
284
|
+
text,
|
|
285
|
+
attempt: 0,
|
|
286
|
+
nextAttemptAt: Date.now(),
|
|
287
|
+
});
|
|
288
|
+
this.#ensureDeliveryLoop();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
#ensureDeliveryLoop(): void {
|
|
292
|
+
if (this.#deliveryLoop) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
this.#deliveryLoop = this.#runDeliveryLoop()
|
|
297
|
+
.catch(error => {
|
|
298
|
+
logger.error("Async job delivery loop crashed", { error: String(error) });
|
|
299
|
+
})
|
|
300
|
+
.finally(() => {
|
|
301
|
+
this.#deliveryLoop = undefined;
|
|
302
|
+
if (this.#deliveries.length > 0) {
|
|
303
|
+
this.#ensureDeliveryLoop();
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async #runDeliveryLoop(): Promise<void> {
|
|
309
|
+
while (this.#deliveries.length > 0) {
|
|
310
|
+
const delivery = this.#deliveries[0];
|
|
311
|
+
const waitMs = delivery.nextAttemptAt - Date.now();
|
|
312
|
+
if (waitMs > 0) {
|
|
313
|
+
await Bun.sleep(waitMs);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
await this.#onJobComplete(delivery.jobId, delivery.text, this.#jobs.get(delivery.jobId));
|
|
318
|
+
this.#deliveries.shift();
|
|
319
|
+
} catch (error) {
|
|
320
|
+
delivery.attempt += 1;
|
|
321
|
+
delivery.lastError = error instanceof Error ? error.message : String(error);
|
|
322
|
+
delivery.nextAttemptAt = Date.now() + this.#getRetryDelay(delivery.attempt);
|
|
323
|
+
this.#deliveries.shift();
|
|
324
|
+
this.#deliveries.push(delivery);
|
|
325
|
+
logger.warn("Async job completion delivery failed", {
|
|
326
|
+
jobId: delivery.jobId,
|
|
327
|
+
attempt: delivery.attempt,
|
|
328
|
+
nextRetryAt: delivery.nextAttemptAt,
|
|
329
|
+
error: delivery.lastError,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
#getRetryDelay(attempt: number): number {
|
|
336
|
+
const exp = Math.min(Math.max(attempt - 1, 0), 8);
|
|
337
|
+
const backoffMs = DELIVERY_RETRY_BASE_MS * 2 ** exp;
|
|
338
|
+
const jitterMs = Math.floor(Math.random() * DELIVERY_RETRY_JITTER_MS);
|
|
339
|
+
return Math.min(DELIVERY_RETRY_MAX_MS, backoffMs + jitterMs);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
@@ -8,7 +8,7 @@ import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
|
8
8
|
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
9
9
|
import chalk from "chalk";
|
|
10
10
|
import { resolveReadPath } from "../tools/path-utils";
|
|
11
|
-
import {
|
|
11
|
+
import { formatBytes } from "../tools/render-utils";
|
|
12
12
|
import { formatDimensionNote, resizeImage } from "../utils/image-resize";
|
|
13
13
|
import { detectSupportedImageMimeTypeFromFile } from "../utils/mime";
|
|
14
14
|
|
|
@@ -47,9 +47,9 @@ export async function processFileArguments(fileArgs: string[], options?: Process
|
|
|
47
47
|
const maxBytes = mimeType ? MAX_CLI_IMAGE_BYTES : MAX_CLI_TEXT_BYTES;
|
|
48
48
|
if (stat.size > maxBytes) {
|
|
49
49
|
console.error(
|
|
50
|
-
chalk.yellow(`Warning: Skipping file contents (too large: ${
|
|
50
|
+
chalk.yellow(`Warning: Skipping file contents (too large: ${formatBytes(stat.size)}): ${absolutePath}`),
|
|
51
51
|
);
|
|
52
|
-
text += `<file name="${absolutePath}">(skipped: too large, ${
|
|
52
|
+
text += `<file name="${absolutePath}">(skipped: too large, ${formatBytes(stat.size)})</file>\n`;
|
|
53
53
|
continue;
|
|
54
54
|
}
|
|
55
55
|
|
package/src/cli/list-models.ts
CHANGED
|
@@ -2,24 +2,10 @@
|
|
|
2
2
|
* List available models with optional fuzzy search
|
|
3
3
|
*/
|
|
4
4
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
5
|
+
import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
5
6
|
import type { ModelRegistry } from "../config/model-registry";
|
|
6
7
|
import { fuzzyFilter } from "../utils/fuzzy";
|
|
7
8
|
|
|
8
|
-
/**
|
|
9
|
-
* Format a number as human-readable (e.g., 200000 -> "200K", 1000000 -> "1M")
|
|
10
|
-
*/
|
|
11
|
-
function formatTokenCount(count: number): string {
|
|
12
|
-
if (count >= 1_000_000) {
|
|
13
|
-
const millions = count / 1_000_000;
|
|
14
|
-
return millions % 1 === 0 ? `${millions}M` : `${millions.toFixed(1)}M`;
|
|
15
|
-
}
|
|
16
|
-
if (count >= 1_000) {
|
|
17
|
-
const thousands = count / 1_000;
|
|
18
|
-
return thousands % 1 === 0 ? `${thousands}K` : `${thousands.toFixed(1)}K`;
|
|
19
|
-
}
|
|
20
|
-
return count.toString();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
9
|
/**
|
|
24
10
|
* List available models, optionally filtered by search pattern
|
|
25
11
|
*/
|
|
@@ -53,8 +39,8 @@ export async function listModels(modelRegistry: ModelRegistry, searchPattern?: s
|
|
|
53
39
|
const rows = filteredModels.map(m => ({
|
|
54
40
|
provider: m.provider,
|
|
55
41
|
model: m.id,
|
|
56
|
-
context:
|
|
57
|
-
maxOut:
|
|
42
|
+
context: formatNumber(m.contextWindow),
|
|
43
|
+
maxOut: formatNumber(m.maxTokens),
|
|
58
44
|
thinking: m.reasoning ? "yes" : "no",
|
|
59
45
|
images: m.input.includes("image") ? "yes" : "no",
|
|
60
46
|
}));
|
package/src/cli/stats-cli.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Handles `omp stats` subcommand for viewing AI usage statistics.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { formatDuration, formatNumber, formatPercent } from "@oh-my-pi/pi-utils";
|
|
7
8
|
import { APP_NAME } from "@oh-my-pi/pi-utils/dirs";
|
|
8
9
|
import chalk from "chalk";
|
|
9
10
|
import { openPath } from "../utils/open";
|
|
@@ -53,32 +54,12 @@ export function parseStatsArgs(args: string[]): StatsCommandArgs | undefined {
|
|
|
53
54
|
return result;
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
// =============================================================================
|
|
57
|
-
// Formatting Helpers
|
|
58
|
-
// =============================================================================
|
|
59
|
-
|
|
60
|
-
function formatNumber(n: number): string {
|
|
61
|
-
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
62
|
-
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
|
|
63
|
-
return n.toFixed(0);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
57
|
function formatCost(n: number): string {
|
|
67
58
|
if (n < 0.01) return `$${n.toFixed(4)}`;
|
|
68
59
|
if (n < 1) return `$${n.toFixed(3)}`;
|
|
69
60
|
return `$${n.toFixed(2)}`;
|
|
70
61
|
}
|
|
71
62
|
|
|
72
|
-
function formatDuration(ms: number | null): string {
|
|
73
|
-
if (ms === null) return "-";
|
|
74
|
-
if (ms < 1000) return `${ms.toFixed(0)}ms`;
|
|
75
|
-
return `${(ms / 1000).toFixed(1)}s`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function formatPercent(n: number): string {
|
|
79
|
-
return `${(n * 100).toFixed(1)}%`;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
63
|
// =============================================================================
|
|
83
64
|
// Command Handler
|
|
84
65
|
// =============================================================================
|
|
@@ -140,8 +121,8 @@ async function printStatsSummary(): Promise<void> {
|
|
|
140
121
|
console.log(` Total Tokens: ${formatNumber(overall.totalInputTokens + overall.totalOutputTokens)}`);
|
|
141
122
|
console.log(` Cache Rate: ${formatPercent(overall.cacheRate)}`);
|
|
142
123
|
console.log(` Total Cost: ${formatCost(overall.totalCost)}`);
|
|
143
|
-
console.log(` Avg Duration: ${formatDuration(overall.avgDuration)}`);
|
|
144
|
-
console.log(` Avg TTFT: ${formatDuration(overall.avgTtft)}`);
|
|
124
|
+
console.log(` Avg Duration: ${overall.avgDuration !== null ? formatDuration(overall.avgDuration) : "-"}`);
|
|
125
|
+
console.log(` Avg TTFT: ${overall.avgTtft !== null ? formatDuration(overall.avgTtft) : "-"}`);
|
|
145
126
|
if (overall.avgTokensPerSecond !== null) {
|
|
146
127
|
console.log(` Avg Tokens/s: ${overall.avgTokensPerSecond.toFixed(1)}`);
|
|
147
128
|
}
|
|
@@ -64,24 +64,24 @@ export function parseSearchArgs(args: string[]): SearchCommandArgs | undefined {
|
|
|
64
64
|
|
|
65
65
|
export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
|
|
66
66
|
if (!cmd.query) {
|
|
67
|
-
|
|
67
|
+
process.stderr.write(`${chalk.red("Error: Query is required")}\n`);
|
|
68
68
|
process.exit(1);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
if (cmd.provider && !PROVIDERS.includes(cmd.provider)) {
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
process.stderr.write(`${chalk.red(`Error: Unknown provider "${cmd.provider}"`)}\n`);
|
|
73
|
+
process.stderr.write(`${chalk.dim(`Valid providers: ${PROVIDERS.join(", ")}`)}\n`);
|
|
74
74
|
process.exit(1);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
if (cmd.recency && !RECENCY_OPTIONS.includes(cmd.recency)) {
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
process.stderr.write(`${chalk.red(`Error: Invalid recency "${cmd.recency}"`)}\n`);
|
|
79
|
+
process.stderr.write(`${chalk.dim(`Valid recency values: ${RECENCY_OPTIONS.join(", ")}`)}\n`);
|
|
80
80
|
process.exit(1);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
if (cmd.limit !== undefined && Number.isNaN(cmd.limit)) {
|
|
84
|
-
|
|
84
|
+
process.stderr.write(`${chalk.red("Error: --limit must be a number")}\n`);
|
|
85
85
|
process.exit(1);
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -104,7 +104,7 @@ export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
|
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
const width = Math.max(60, process.stdout.columns ?? 100);
|
|
107
|
-
|
|
107
|
+
process.stdout.write(`${component.render(width).join("\n")}\n`);
|
|
108
108
|
|
|
109
109
|
if (result.details?.error) {
|
|
110
110
|
process.exitCode = 1;
|
|
@@ -112,7 +112,7 @@ export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
export function printSearchHelp(): void {
|
|
115
|
-
|
|
115
|
+
process.stdout.write(`${chalk.bold(`${APP_NAME} q`)} - Test web search providers
|
|
116
116
|
|
|
117
117
|
${chalk.bold("Usage:")}
|
|
118
118
|
${APP_NAME} q [options] <query>
|
|
@@ -133,11 +133,3 @@ ${chalk.bold("Examples:")}
|
|
|
133
133
|
${APP_NAME} q --provider=brave --recency=week "latest TypeScript 5.7 changes"
|
|
134
134
|
`);
|
|
135
135
|
}
|
|
136
|
-
|
|
137
|
-
function writeStdout(message: string): void {
|
|
138
|
-
process.stdout.write(`${message}\n`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function writeStderr(message: string): void {
|
|
142
|
-
process.stderr.write(`${message}\n`);
|
|
143
|
-
}
|
|
@@ -115,7 +115,7 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
|
|
|
115
115
|
clearThinkingLine();
|
|
116
116
|
const assistantMessage = event.message as { stopReason?: string; errorMessage?: string };
|
|
117
117
|
if (assistantMessage.stopReason === "error" && assistantMessage.errorMessage) {
|
|
118
|
-
|
|
118
|
+
process.stdout.write(`● Error: ${assistantMessage.errorMessage}\n`);
|
|
119
119
|
}
|
|
120
120
|
const messageText = extractMessageText(event.message?.content ?? []);
|
|
121
121
|
if (messageText) {
|
|
@@ -130,10 +130,10 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
|
|
|
130
130
|
clearThinkingLine();
|
|
131
131
|
const toolLabel = formatToolLabel(stored.name);
|
|
132
132
|
const symbol = event.isError ? "" : "";
|
|
133
|
-
|
|
133
|
+
process.stdout.write(`${symbol} ${toolLabel}\n`);
|
|
134
134
|
const argsLines = formatToolArgs(stored.args);
|
|
135
135
|
if (argsLines.length > 0) {
|
|
136
|
-
|
|
136
|
+
process.stdout.write(`${formatToolArgsBlock(argsLines)}\n`);
|
|
137
137
|
}
|
|
138
138
|
break;
|
|
139
139
|
}
|
|
@@ -141,7 +141,7 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
|
|
|
141
141
|
if (isThinking) {
|
|
142
142
|
isThinking = false;
|
|
143
143
|
}
|
|
144
|
-
|
|
144
|
+
process.stdout.write(`● agent finished (${messageCount} messages, ${toolCalls} tools)\n`);
|
|
145
145
|
break;
|
|
146
146
|
default:
|
|
147
147
|
break;
|
|
@@ -172,10 +172,6 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
|
|
|
172
172
|
}
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
function writeStdout(message: string): void {
|
|
176
|
-
process.stdout.write(`${message}\n`);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
175
|
function extractMessagePreview(content: Array<{ type: string; text?: string }>): string | null {
|
|
180
176
|
const textBlocks = content
|
|
181
177
|
.filter(block => block.type === "text" && typeof block.text === "string")
|
|
@@ -204,7 +200,7 @@ function writeAssistantMessage(message: string): void {
|
|
|
204
200
|
}
|
|
205
201
|
for (const [index, line] of lines.entries()) {
|
|
206
202
|
const prefix = index === firstContentIndex ? "● " : " ";
|
|
207
|
-
|
|
203
|
+
process.stdout.write(`${`${prefix}${line}`.trimEnd()}\n`);
|
|
208
204
|
}
|
|
209
205
|
}
|
|
210
206
|
|
|
@@ -249,6 +245,7 @@ function formatToolArgs(args?: Record<string, unknown>): string[] {
|
|
|
249
245
|
}
|
|
250
246
|
};
|
|
251
247
|
for (const [key, value] of Object.entries(args)) {
|
|
248
|
+
if (key === "agent__intent") continue;
|
|
252
249
|
visit(value, key);
|
|
253
250
|
}
|
|
254
251
|
return lines;
|