@oh-my-pi/pi-coding-agent 12.18.3 → 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
|
@@ -176,6 +176,14 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
176
176
|
runtime.ctx.editor.setText("");
|
|
177
177
|
},
|
|
178
178
|
},
|
|
179
|
+
{
|
|
180
|
+
name: "jobs",
|
|
181
|
+
description: "Show async background jobs status",
|
|
182
|
+
handle: async (_command, runtime) => {
|
|
183
|
+
await runtime.ctx.handleJobsCommand();
|
|
184
|
+
runtime.ctx.editor.setText("");
|
|
185
|
+
},
|
|
186
|
+
},
|
|
179
187
|
{
|
|
180
188
|
name: "usage",
|
|
181
189
|
description: "Show provider usage and limits",
|
|
@@ -3,6 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { getRemoteHostDir, getSshControlDir } from "@oh-my-pi/pi-utils/dirs";
|
|
5
5
|
import { $ } from "bun";
|
|
6
|
+
import { buildSshTarget, sanitizeHostName } from "./utils";
|
|
6
7
|
|
|
7
8
|
export interface SSHConnectionTarget {
|
|
8
9
|
name: string;
|
|
@@ -42,11 +43,6 @@ function ensureControlDir() {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
function sanitizeHostName(name: string): string {
|
|
46
|
-
const sanitized = name.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
47
|
-
return sanitized.length > 0 ? sanitized : "host";
|
|
48
|
-
}
|
|
49
|
-
|
|
50
46
|
function getHostInfoPath(name: string): string {
|
|
51
47
|
return path.join(HOST_INFO_DIR, `${sanitizeHostName(name)}.json`);
|
|
52
48
|
}
|
|
@@ -71,10 +67,6 @@ async function validateKeyPermissions(keyPath?: string): Promise<void> {
|
|
|
71
67
|
}
|
|
72
68
|
}
|
|
73
69
|
|
|
74
|
-
function buildSshTarget(host: SSHConnectionTarget): string {
|
|
75
|
-
return host.username ? `${host.username}@${host.host}` : host.host;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
70
|
function buildCommonArgs(host: SSHConnectionTarget): string[] {
|
|
79
71
|
const args = [
|
|
80
72
|
"-o",
|
|
@@ -367,7 +359,7 @@ export async function ensureHostInfo(host: SSHConnectionTarget): Promise<SSHHost
|
|
|
367
359
|
|
|
368
360
|
export async function buildRemoteCommand(host: SSHConnectionTarget, command: string): Promise<string[]> {
|
|
369
361
|
await validateKeyPermissions(host.keyPath);
|
|
370
|
-
return [...buildCommonArgs(host), buildSshTarget(host), command];
|
|
362
|
+
return [...buildCommonArgs(host), buildSshTarget(host.username, host.host), command];
|
|
371
363
|
}
|
|
372
364
|
|
|
373
365
|
export async function ensureConnection(host: SSHConnectionTarget): Promise<void> {
|
|
@@ -383,7 +375,7 @@ export async function ensureConnection(host: SSHConnectionTarget): Promise<void>
|
|
|
383
375
|
ensureControlDir();
|
|
384
376
|
await validateKeyPermissions(host.keyPath);
|
|
385
377
|
|
|
386
|
-
const target = buildSshTarget(host);
|
|
378
|
+
const target = buildSshTarget(host.username, host.host);
|
|
387
379
|
const check = await runSshSync(["-O", "check", ...buildCommonArgs(host), target]);
|
|
388
380
|
if (check.exitCode === 0) {
|
|
389
381
|
activeHosts.set(key, host);
|
|
@@ -414,7 +406,7 @@ export async function ensureConnection(host: SSHConnectionTarget): Promise<void>
|
|
|
414
406
|
}
|
|
415
407
|
|
|
416
408
|
async function closeConnectionInternal(host: SSHConnectionTarget): Promise<void> {
|
|
417
|
-
const target = buildSshTarget(host);
|
|
409
|
+
const target = buildSshTarget(host.username, host.host);
|
|
418
410
|
await runSshSync(["-O", "exit", ...buildCommonArgs(host), target]);
|
|
419
411
|
}
|
|
420
412
|
|
package/src/ssh/sshfs-mount.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import { getRemoteDir } from "@oh-my-pi/pi-utils/dirs";
|
|
4
4
|
import { $ } from "bun";
|
|
5
5
|
import { getControlDir, getControlPathTemplate, type SSHConnectionTarget } from "./connection-manager";
|
|
6
|
+
import { buildSshTarget, sanitizeHostName } from "./utils";
|
|
6
7
|
|
|
7
8
|
const REMOTE_DIR = getRemoteDir();
|
|
8
9
|
const CONTROL_DIR = getControlDir();
|
|
@@ -20,18 +21,13 @@ async function ensureDir(path: string, mode = 0o700): Promise<void> {
|
|
|
20
21
|
|
|
21
22
|
function getMountName(host: SSHConnectionTarget): string {
|
|
22
23
|
const raw = (host.name ?? host.host).trim();
|
|
23
|
-
|
|
24
|
-
return sanitized.length > 0 ? sanitized : "remote";
|
|
24
|
+
return sanitizeHostName(raw);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function getMountPath(host: SSHConnectionTarget): string {
|
|
28
28
|
return path.join(REMOTE_DIR, getMountName(host));
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function buildSshTarget(host: SSHConnectionTarget): string {
|
|
32
|
-
return host.username ? `${host.username}@${host.host}` : host.host;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
31
|
function buildSshfsArgs(host: SSHConnectionTarget): string[] {
|
|
36
32
|
const args = [
|
|
37
33
|
"-o",
|
|
@@ -98,7 +94,7 @@ export async function mountRemote(host: SSHConnectionTarget, remotePath = "/"):
|
|
|
98
94
|
return mountPath;
|
|
99
95
|
}
|
|
100
96
|
|
|
101
|
-
const target = `${buildSshTarget(host)}:${remotePath}`;
|
|
97
|
+
const target = `${buildSshTarget(host.username, host.host)}:${remotePath}`;
|
|
102
98
|
const args = buildSshfsArgs(host);
|
|
103
99
|
const result = await $`sshfs ${args} ${target} ${mountPath}`.nothrow();
|
|
104
100
|
|
package/src/ssh/utils.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function sanitizeHostName(name: string): string {
|
|
2
|
+
const sanitized = name.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
3
|
+
return sanitized.length > 0 ? sanitized : "remote";
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function buildSshTarget(username: string | undefined, host: string): string {
|
|
7
|
+
return username ? `${username}@${host}` : host;
|
|
8
|
+
}
|
package/src/system-prompt.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
-
import { $env, hasFsCode, isEnoent, logger
|
|
8
|
+
import { $env, hasFsCode, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import { getGpuCachePath, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
10
10
|
import { $ } from "bun";
|
|
11
11
|
import { contextFileCapability } from "./capability/context-file";
|
|
@@ -18,14 +18,6 @@ import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md
|
|
|
18
18
|
import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" };
|
|
19
19
|
import type { ToolName } from "./tools";
|
|
20
20
|
|
|
21
|
-
interface GitContext {
|
|
22
|
-
isRepo: boolean;
|
|
23
|
-
currentBranch: string;
|
|
24
|
-
mainBranch: string;
|
|
25
|
-
status: string;
|
|
26
|
-
commits: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
21
|
type PreloadedSkill = { name: string; content: string };
|
|
30
22
|
|
|
31
23
|
async function loadPreloadedSkillContents(preloadedSkills: Skill[]): Promise<PreloadedSkill[]> {
|
|
@@ -44,54 +36,6 @@ async function loadPreloadedSkillContents(preloadedSkills: Skill[]): Promise<Pre
|
|
|
44
36
|
return contents;
|
|
45
37
|
}
|
|
46
38
|
|
|
47
|
-
/**
|
|
48
|
-
* Load git context for the system prompt.
|
|
49
|
-
* Returns structured git data or null if not in a git repo.
|
|
50
|
-
*/
|
|
51
|
-
export async function loadGitContext(cwd: string): Promise<GitContext | null> {
|
|
52
|
-
const timeout = 3000;
|
|
53
|
-
const abortSignal = AbortSignal.timeout(timeout);
|
|
54
|
-
|
|
55
|
-
const git = async (...args: string[]): Promise<string | null> => {
|
|
56
|
-
const proc = Bun.spawn(["git", ...args], {
|
|
57
|
-
cwd,
|
|
58
|
-
stdout: "pipe",
|
|
59
|
-
stderr: "ignore",
|
|
60
|
-
timeout: timeout,
|
|
61
|
-
killSignal: "SIGKILL",
|
|
62
|
-
});
|
|
63
|
-
return untilAborted(abortSignal, async () => {
|
|
64
|
-
const exitCode = await proc.exited;
|
|
65
|
-
const stdout = await proc.stdout.text();
|
|
66
|
-
return exitCode === 0 ? stdout.trim() : null;
|
|
67
|
-
});
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Check if inside a git repo
|
|
71
|
-
const isGitRepo = await git("rev-parse", "--is-inside-work-tree");
|
|
72
|
-
if (isGitRepo !== "true") return null;
|
|
73
|
-
const currentBranch = await git("rev-parse", "--abbrev-ref", "HEAD");
|
|
74
|
-
if (!currentBranch) return null;
|
|
75
|
-
let mainBranch = "main";
|
|
76
|
-
const mainExists = await git("rev-parse", "--verify", "main");
|
|
77
|
-
if (mainExists === null) {
|
|
78
|
-
const masterExists = await git("rev-parse", "--verify", "master");
|
|
79
|
-
if (masterExists !== null) mainBranch = "master";
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const [status, commits] = await Promise.all([
|
|
83
|
-
git("status", "--porcelain", "--untracked-files=no"),
|
|
84
|
-
git("log", "--oneline", "-5"),
|
|
85
|
-
]);
|
|
86
|
-
return {
|
|
87
|
-
isRepo: true,
|
|
88
|
-
currentBranch,
|
|
89
|
-
mainBranch,
|
|
90
|
-
status: status === "" ? "(clean)" : (status ?? "(status unavailable)"),
|
|
91
|
-
commits: commits && commits.length > 0 ? commits : "(no commits)",
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
39
|
function firstNonEmpty(...values: (string | undefined | null)[]): string | null {
|
|
96
40
|
for (const value of values) {
|
|
97
41
|
const trimmed = value?.trim();
|
|
@@ -333,9 +277,7 @@ function getSystemInfoCachePath(): string {
|
|
|
333
277
|
async function loadGpuCache(): Promise<GpuCache | null> {
|
|
334
278
|
try {
|
|
335
279
|
const cachePath = getSystemInfoCachePath();
|
|
336
|
-
const
|
|
337
|
-
if (!(await file.exists())) return null;
|
|
338
|
-
const content = await file.json();
|
|
280
|
+
const content = await Bun.file(cachePath).json();
|
|
339
281
|
return content as GpuCache;
|
|
340
282
|
} catch {
|
|
341
283
|
return null;
|
|
@@ -501,7 +443,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
501
443
|
const resolvedCwd = cwd ?? getProjectDir();
|
|
502
444
|
const preloadedSkills = providedPreloadedSkills;
|
|
503
445
|
|
|
504
|
-
const prepPromise = (
|
|
446
|
+
const prepPromise = (() => {
|
|
505
447
|
const systemPromptCustomizationPromise = logger.timeAsync("loadSystemPromptFiles", loadSystemPromptFiles, {
|
|
506
448
|
cwd: resolvedCwd,
|
|
507
449
|
});
|
|
@@ -516,20 +458,10 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
516
458
|
? loadSkills({ ...skillsSettings, cwd: resolvedCwd }).then(result => result.skills)
|
|
517
459
|
: Promise.resolve([]);
|
|
518
460
|
const preloadedSkillContentsPromise = preloadedSkills
|
|
519
|
-
?
|
|
461
|
+
? logger.timeAsync("loadPreloadedSkills", loadPreloadedSkillContents, preloadedSkills)
|
|
520
462
|
: [];
|
|
521
|
-
const gitPromise = logger.timeAsync("loadGitContext", loadGitContext, resolvedCwd);
|
|
522
463
|
|
|
523
|
-
|
|
524
|
-
resolvedCustomPrompt,
|
|
525
|
-
resolvedAppendPrompt,
|
|
526
|
-
systemPromptCustomization,
|
|
527
|
-
contextFiles,
|
|
528
|
-
agentsMdSearch,
|
|
529
|
-
skills,
|
|
530
|
-
preloadedSkillContents,
|
|
531
|
-
git,
|
|
532
|
-
] = await Promise.all([
|
|
464
|
+
return Promise.all([
|
|
533
465
|
resolvePromptInput(customPrompt, "system prompt"),
|
|
534
466
|
resolvePromptInput(appendSystemPrompt, "append system prompt"),
|
|
535
467
|
systemPromptCustomizationPromise,
|
|
@@ -537,19 +469,25 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
537
469
|
agentsMdSearchPromise,
|
|
538
470
|
skillsPromise,
|
|
539
471
|
preloadedSkillContentsPromise,
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
472
|
+
]).then(
|
|
473
|
+
([
|
|
474
|
+
resolvedCustomPrompt,
|
|
475
|
+
resolvedAppendPrompt,
|
|
476
|
+
systemPromptCustomization,
|
|
477
|
+
contextFiles,
|
|
478
|
+
agentsMdSearch,
|
|
479
|
+
skills,
|
|
480
|
+
preloadedSkillContents,
|
|
481
|
+
]) => ({
|
|
482
|
+
resolvedCustomPrompt,
|
|
483
|
+
resolvedAppendPrompt,
|
|
484
|
+
systemPromptCustomization,
|
|
485
|
+
contextFiles,
|
|
486
|
+
agentsMdSearch,
|
|
487
|
+
skills,
|
|
488
|
+
preloadedSkillContents,
|
|
489
|
+
}),
|
|
490
|
+
);
|
|
553
491
|
})();
|
|
554
492
|
|
|
555
493
|
const prepResult = await Promise.race([
|
|
@@ -571,7 +509,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
571
509
|
};
|
|
572
510
|
let skills: Skill[] = providedSkills ?? [];
|
|
573
511
|
let preloadedSkillContents: PreloadedSkill[] = [];
|
|
574
|
-
let git: GitContext | null = null;
|
|
575
512
|
|
|
576
513
|
if (prepResult.type === "timeout") {
|
|
577
514
|
logger.warn("System prompt preparation timed out; using minimal startup context", {
|
|
@@ -595,7 +532,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
595
532
|
agentsMdSearch = prepResult.value.agentsMdSearch;
|
|
596
533
|
skills = prepResult.value.skills;
|
|
597
534
|
preloadedSkillContents = prepResult.value.preloadedSkillContents;
|
|
598
|
-
git = prepResult.value.git;
|
|
599
535
|
}
|
|
600
536
|
|
|
601
537
|
const now = new Date();
|
|
@@ -648,7 +584,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
648
584
|
appendPrompt: resolvedAppendPrompt ?? "",
|
|
649
585
|
contextFiles,
|
|
650
586
|
agentsMdSearch,
|
|
651
|
-
git,
|
|
652
587
|
skills: filteredSkills,
|
|
653
588
|
preloadedSkills: preloadedSkillContents,
|
|
654
589
|
rules: rules ?? [],
|
|
@@ -667,7 +602,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
667
602
|
systemPromptCustomization: systemPromptCustomization ?? "",
|
|
668
603
|
contextFiles,
|
|
669
604
|
agentsMdSearch,
|
|
670
|
-
git,
|
|
671
605
|
skills: filteredSkills,
|
|
672
606
|
preloadedSkills: preloadedSkillContents,
|
|
673
607
|
rules: rules ?? [],
|
package/src/task/executor.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { ModelRegistry } from "../config/model-registry";
|
|
|
13
13
|
import { resolveModelOverride } from "../config/model-resolver";
|
|
14
14
|
import { type PromptTemplate, renderPromptTemplate } from "../config/prompt-templates";
|
|
15
15
|
import { Settings } from "../config/settings";
|
|
16
|
+
import { SETTINGS_SCHEMA, type SettingPath } from "../config/settings-schema";
|
|
16
17
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
17
18
|
import type { Skill } from "../extensibility/skills";
|
|
18
19
|
import { callTool } from "../mcp/client";
|
|
@@ -442,6 +443,14 @@ function createMCPProxyTools(mcpManager: MCPManager): CustomTool<TSchema>[] {
|
|
|
442
443
|
});
|
|
443
444
|
}
|
|
444
445
|
|
|
446
|
+
function createSubagentSettings(baseSettings: Settings): Settings {
|
|
447
|
+
const snapshot: Partial<Record<SettingPath, unknown>> = {};
|
|
448
|
+
for (const key of Object.keys(SETTINGS_SCHEMA) as SettingPath[]) {
|
|
449
|
+
snapshot[key] = baseSettings.get(key);
|
|
450
|
+
}
|
|
451
|
+
return Settings.isolated({ ...snapshot, "async.enabled": false });
|
|
452
|
+
}
|
|
453
|
+
|
|
445
454
|
/**
|
|
446
455
|
* Run a single agent in-process.
|
|
447
456
|
*/
|
|
@@ -507,6 +516,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
507
516
|
}
|
|
508
517
|
|
|
509
518
|
const settings = options.settings ?? Settings.isolated();
|
|
519
|
+
const subagentSettings = createSubagentSettings(settings);
|
|
510
520
|
const maxRecursionDepth = settings.get("task.maxRecursionDepth") ?? 2;
|
|
511
521
|
const parentDepth = options.taskDepth ?? 0;
|
|
512
522
|
const childDepth = parentDepth + 1;
|
|
@@ -932,7 +942,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
932
942
|
cwd: worktree ?? cwd,
|
|
933
943
|
authStorage,
|
|
934
944
|
modelRegistry,
|
|
935
|
-
settings,
|
|
945
|
+
settings: subagentSettings,
|
|
936
946
|
model,
|
|
937
947
|
thinkingLevel: effectiveThinkingLevel,
|
|
938
948
|
toolNames,
|
package/src/task/index.ts
CHANGED
|
@@ -26,13 +26,13 @@ import type { Theme } from "../modes/theme/theme";
|
|
|
26
26
|
import planModeSubagentPrompt from "../prompts/system/plan-mode-subagent.md" with { type: "text" };
|
|
27
27
|
import taskDescriptionTemplate from "../prompts/tools/task.md" with { type: "text" };
|
|
28
28
|
import taskSummaryTemplate from "../prompts/tools/task-summary.md" with { type: "text" };
|
|
29
|
-
import { formatDuration } from "../tools/render-utils";
|
|
29
|
+
import { formatBytes, formatDuration } from "../tools/render-utils";
|
|
30
30
|
// Import review tools for side effects (registers subagent tool handlers)
|
|
31
31
|
import "../tools/review";
|
|
32
32
|
import { discoverAgents, getAgent } from "./discovery";
|
|
33
33
|
import { runSubprocess } from "./executor";
|
|
34
34
|
import { AgentOutputManager } from "./output-manager";
|
|
35
|
-
import { mapWithConcurrencyLimit } from "./parallel";
|
|
35
|
+
import { mapWithConcurrencyLimit, Semaphore } from "./parallel";
|
|
36
36
|
import { renderCall, renderResult } from "./render";
|
|
37
37
|
import { renderTemplate } from "./template";
|
|
38
38
|
import {
|
|
@@ -55,13 +55,6 @@ import {
|
|
|
55
55
|
type WorktreeBaseline,
|
|
56
56
|
} from "./worktree";
|
|
57
57
|
|
|
58
|
-
/** Format byte count for display */
|
|
59
|
-
function formatBytes(bytes: number): string {
|
|
60
|
-
if (bytes < 1024) return `${bytes}B`;
|
|
61
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}K`;
|
|
62
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
58
|
function createUsageTotals(): Usage {
|
|
66
59
|
return {
|
|
67
60
|
input: 0,
|
|
@@ -116,6 +109,7 @@ function renderDescription(
|
|
|
116
109
|
agents: AgentDefinition[],
|
|
117
110
|
maxConcurrency: number,
|
|
118
111
|
isolationEnabled: boolean,
|
|
112
|
+
asyncEnabled: boolean,
|
|
119
113
|
disabledAgents: string[],
|
|
120
114
|
): string {
|
|
121
115
|
const filteredAgents = disabledAgents.length > 0 ? agents.filter(a => !disabledAgents.includes(a.name)) : agents;
|
|
@@ -123,6 +117,7 @@ function renderDescription(
|
|
|
123
117
|
agents: filteredAgents,
|
|
124
118
|
MAX_CONCURRENCY: maxConcurrency,
|
|
125
119
|
isolationEnabled,
|
|
120
|
+
asyncEnabled,
|
|
126
121
|
});
|
|
127
122
|
}
|
|
128
123
|
|
|
@@ -150,7 +145,13 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
150
145
|
const disabledAgents = this.session.settings.get("task.disabledAgents") as string[];
|
|
151
146
|
const maxConcurrency = this.session.settings.get("task.maxConcurrency");
|
|
152
147
|
const isolationEnabled = this.session.settings.get("task.isolation.enabled");
|
|
153
|
-
return renderDescription(
|
|
148
|
+
return renderDescription(
|
|
149
|
+
this.#discoveredAgents,
|
|
150
|
+
maxConcurrency,
|
|
151
|
+
isolationEnabled,
|
|
152
|
+
this.session.settings.get("async.enabled"),
|
|
153
|
+
disabledAgents,
|
|
154
|
+
);
|
|
154
155
|
}
|
|
155
156
|
private constructor(
|
|
156
157
|
private readonly session: ToolSession,
|
|
@@ -176,6 +177,245 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
176
177
|
params: TaskParams,
|
|
177
178
|
signal?: AbortSignal,
|
|
178
179
|
onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
|
|
180
|
+
): Promise<AgentToolResult<TaskToolDetails>> {
|
|
181
|
+
const asyncEnabled = this.session.settings.get("async.enabled");
|
|
182
|
+
if (!asyncEnabled) {
|
|
183
|
+
return this.#executeSync(_toolCallId, params, signal, onUpdate);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const manager = this.session.asyncJobManager;
|
|
187
|
+
if (!manager) {
|
|
188
|
+
return {
|
|
189
|
+
content: [{ type: "text", text: "Async execution is enabled but no async job manager is available." }],
|
|
190
|
+
details: { projectAgentsDir: null, results: [], totalDurationMs: 0 },
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const taskItems = params.tasks ?? [];
|
|
195
|
+
if (taskItems.length === 0) {
|
|
196
|
+
return this.#executeSync(_toolCallId, params, signal, onUpdate);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const outputManager =
|
|
200
|
+
this.session.agentOutputManager ?? new AgentOutputManager(this.session.getArtifactsDir ?? (() => null));
|
|
201
|
+
const uniqueIds = await outputManager.allocateBatch(taskItems.map(t => t.id));
|
|
202
|
+
const fallbackAgentSource =
|
|
203
|
+
this.#discoveredAgents.find(agent => agent.name === params.agent)?.source ?? "bundled";
|
|
204
|
+
const renderedTasks = taskItems.map(taskItem => renderTemplate(params.context, taskItem));
|
|
205
|
+
const progressByTaskId = new Map<string, AgentProgress>();
|
|
206
|
+
for (let index = 0; index < renderedTasks.length; index++) {
|
|
207
|
+
const renderedTask = renderedTasks[index];
|
|
208
|
+
progressByTaskId.set(renderedTask.id, {
|
|
209
|
+
index,
|
|
210
|
+
id: renderedTask.id,
|
|
211
|
+
agent: params.agent,
|
|
212
|
+
agentSource: fallbackAgentSource,
|
|
213
|
+
status: "pending",
|
|
214
|
+
task: renderedTask.task,
|
|
215
|
+
description: renderedTask.description,
|
|
216
|
+
recentTools: [],
|
|
217
|
+
recentOutput: [],
|
|
218
|
+
toolCount: 0,
|
|
219
|
+
tokens: 0,
|
|
220
|
+
durationMs: 0,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const startedJobs: Array<{ jobId: string; taskId: string }> = [];
|
|
225
|
+
const failedSchedules: string[] = [];
|
|
226
|
+
let completedJobs = 0;
|
|
227
|
+
let failedJobs = 0;
|
|
228
|
+
|
|
229
|
+
const getProgressSnapshot = (): AgentProgress[] => {
|
|
230
|
+
return Array.from(progressByTaskId.values())
|
|
231
|
+
.sort((a, b) => a.index - b.index)
|
|
232
|
+
.map(progress => structuredClone(progress));
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const buildAsyncDetails = (state: "running" | "completed" | "failed", jobId: string): TaskToolDetails => ({
|
|
236
|
+
projectAgentsDir: null,
|
|
237
|
+
results: [],
|
|
238
|
+
totalDurationMs: 0,
|
|
239
|
+
progress: getProgressSnapshot(),
|
|
240
|
+
async: { state, jobId, type: "task" },
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const emitAsyncUpdate = (state: "running" | "completed" | "failed", text: string): void => {
|
|
244
|
+
const primaryJobId = startedJobs[0]?.jobId ?? "task";
|
|
245
|
+
onUpdate?.({
|
|
246
|
+
content: [{ type: "text", text }],
|
|
247
|
+
details: buildAsyncDetails(state, primaryJobId),
|
|
248
|
+
});
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const maxConcurrency = this.session.settings.get("task.maxConcurrency");
|
|
252
|
+
const semaphore = new Semaphore(maxConcurrency);
|
|
253
|
+
|
|
254
|
+
for (let i = 0; i < taskItems.length; i++) {
|
|
255
|
+
const taskItem = taskItems[i];
|
|
256
|
+
if (signal?.aborted) {
|
|
257
|
+
failedSchedules.push(`${taskItem.id}: cancelled before scheduling`);
|
|
258
|
+
const progress = progressByTaskId.get(taskItem.id);
|
|
259
|
+
if (progress) {
|
|
260
|
+
progress.status = "aborted";
|
|
261
|
+
}
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const uniqueId = uniqueIds[i];
|
|
266
|
+
const singleParams: TaskParams = { ...params, tasks: [taskItem] };
|
|
267
|
+
const label = uniqueId;
|
|
268
|
+
try {
|
|
269
|
+
const jobId = manager.register(
|
|
270
|
+
"task",
|
|
271
|
+
label,
|
|
272
|
+
async ({ signal: runSignal, reportProgress }) => {
|
|
273
|
+
const startedAt = Date.now();
|
|
274
|
+
const progress = progressByTaskId.get(taskItem.id);
|
|
275
|
+
await semaphore.acquire();
|
|
276
|
+
if (runSignal.aborted) {
|
|
277
|
+
semaphore.release();
|
|
278
|
+
if (progress) {
|
|
279
|
+
progress.status = "aborted";
|
|
280
|
+
}
|
|
281
|
+
throw new Error("Aborted before execution");
|
|
282
|
+
}
|
|
283
|
+
if (progress) {
|
|
284
|
+
progress.status = "running";
|
|
285
|
+
}
|
|
286
|
+
await reportProgress(
|
|
287
|
+
`Running background task ${taskItem.id}...`,
|
|
288
|
+
buildAsyncDetails("running", startedJobs[0]?.jobId ?? label) as unknown as Record<string, unknown>,
|
|
289
|
+
);
|
|
290
|
+
try {
|
|
291
|
+
const result = await this.#executeSync(_toolCallId, singleParams, runSignal, undefined, [
|
|
292
|
+
uniqueId,
|
|
293
|
+
]);
|
|
294
|
+
const finalText = result.content.find(part => part.type === "text")?.text ?? "(no output)";
|
|
295
|
+
const singleResult = result.details?.results[0];
|
|
296
|
+
if (progress) {
|
|
297
|
+
progress.status = singleResult?.aborted
|
|
298
|
+
? "aborted"
|
|
299
|
+
: (singleResult?.exitCode ?? 0) === 0
|
|
300
|
+
? "completed"
|
|
301
|
+
: "failed";
|
|
302
|
+
progress.durationMs = singleResult?.durationMs ?? Math.max(0, Date.now() - startedAt);
|
|
303
|
+
progress.tokens = singleResult?.tokens ?? 0;
|
|
304
|
+
progress.extractedToolData = singleResult?.extractedToolData;
|
|
305
|
+
}
|
|
306
|
+
completedJobs += 1;
|
|
307
|
+
if (singleResult && ((singleResult.aborted ?? false) || singleResult.exitCode !== 0)) {
|
|
308
|
+
failedJobs += 1;
|
|
309
|
+
}
|
|
310
|
+
const remaining = taskItems.length - completedJobs;
|
|
311
|
+
const isDone = remaining === 0;
|
|
312
|
+
await reportProgress(
|
|
313
|
+
isDone
|
|
314
|
+
? `Background task batch complete: ${completedJobs}/${taskItems.length} finished.`
|
|
315
|
+
: `Background task batch progress: ${completedJobs}/${taskItems.length} finished (${remaining} running).`,
|
|
316
|
+
buildAsyncDetails(
|
|
317
|
+
isDone ? (failedJobs > 0 || failedSchedules.length > 0 ? "failed" : "completed") : "running",
|
|
318
|
+
startedJobs[0]?.jobId ?? label,
|
|
319
|
+
) as unknown as Record<string, unknown>,
|
|
320
|
+
);
|
|
321
|
+
if (isDone) {
|
|
322
|
+
emitAsyncUpdate(
|
|
323
|
+
failedJobs > 0 || failedSchedules.length > 0 ? "failed" : "completed",
|
|
324
|
+
`Background task batch complete: ${completedJobs}/${taskItems.length} finished.`,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
return finalText;
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if (progress) {
|
|
330
|
+
progress.status = "failed";
|
|
331
|
+
progress.durationMs = Math.max(0, Date.now() - startedAt);
|
|
332
|
+
}
|
|
333
|
+
completedJobs += 1;
|
|
334
|
+
failedJobs += 1;
|
|
335
|
+
const remaining = taskItems.length - completedJobs;
|
|
336
|
+
const isDone = remaining === 0;
|
|
337
|
+
await reportProgress(
|
|
338
|
+
isDone
|
|
339
|
+
? `Background task batch complete with failures: ${failedJobs} failed.`
|
|
340
|
+
: `Background task batch progress: ${completedJobs}/${taskItems.length} finished (${remaining} running).`,
|
|
341
|
+
buildAsyncDetails(
|
|
342
|
+
isDone ? "failed" : "running",
|
|
343
|
+
startedJobs[0]?.jobId ?? label,
|
|
344
|
+
) as unknown as Record<string, unknown>,
|
|
345
|
+
);
|
|
346
|
+
if (isDone) {
|
|
347
|
+
emitAsyncUpdate(
|
|
348
|
+
"failed",
|
|
349
|
+
`Background task batch complete with failures: ${failedJobs} failed.`,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
throw error;
|
|
353
|
+
} finally {
|
|
354
|
+
semaphore.release();
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
id: label,
|
|
359
|
+
onProgress: (text, details) => {
|
|
360
|
+
const progressDetails =
|
|
361
|
+
(details as TaskToolDetails | undefined) ??
|
|
362
|
+
buildAsyncDetails("running", startedJobs[0]?.jobId ?? label);
|
|
363
|
+
onUpdate?.({ content: [{ type: "text", text }], details: progressDetails });
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
);
|
|
367
|
+
startedJobs.push({ jobId, taskId: taskItem.id });
|
|
368
|
+
} catch (error) {
|
|
369
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
370
|
+
failedSchedules.push(`${taskItem.id}: ${message}`);
|
|
371
|
+
const progress = progressByTaskId.get(taskItem.id);
|
|
372
|
+
if (progress) {
|
|
373
|
+
progress.status = "failed";
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (startedJobs.length === 0) {
|
|
379
|
+
const failureText = `Failed to start background task jobs: ${failedSchedules.join("; ")}`;
|
|
380
|
+
return {
|
|
381
|
+
content: [{ type: "text", text: failureText }],
|
|
382
|
+
details: { projectAgentsDir: null, results: [], totalDurationMs: 0 },
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
emitAsyncUpdate(
|
|
387
|
+
"running",
|
|
388
|
+
`Launching ${startedJobs.length} background ${startedJobs.length === 1 ? "task" : "tasks"}...`,
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
const scheduleFailureSummary =
|
|
392
|
+
failedSchedules.length > 0
|
|
393
|
+
? ` Failed to schedule ${failedSchedules.length} task${failedSchedules.length === 1 ? "" : "s"}.`
|
|
394
|
+
: "";
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
content: [
|
|
398
|
+
{
|
|
399
|
+
type: "text",
|
|
400
|
+
text: `Started ${startedJobs.length} background task job${startedJobs.length === 1 ? "" : "s"} using ${params.agent}.${scheduleFailureSummary} Results will be delivered when complete.`,
|
|
401
|
+
},
|
|
402
|
+
],
|
|
403
|
+
details: {
|
|
404
|
+
projectAgentsDir: null,
|
|
405
|
+
results: [],
|
|
406
|
+
totalDurationMs: 0,
|
|
407
|
+
progress: getProgressSnapshot(),
|
|
408
|
+
async: { state: "running", jobId: startedJobs[0].jobId, type: "task" },
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async #executeSync(
|
|
414
|
+
_toolCallId: string,
|
|
415
|
+
params: TaskParams,
|
|
416
|
+
signal?: AbortSignal,
|
|
417
|
+
onUpdate?: AgentToolUpdateCallback<TaskToolDetails>,
|
|
418
|
+
preAllocatedIds?: string[],
|
|
179
419
|
): Promise<AgentToolResult<TaskToolDetails>> {
|
|
180
420
|
const startTime = Date.now();
|
|
181
421
|
const { agents, projectAgentsDir } = await discoverAgents(this.session.cwd);
|
|
@@ -427,9 +667,14 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
427
667
|
|
|
428
668
|
// Build full prompts with context prepended
|
|
429
669
|
// Allocate unique IDs across the session to prevent artifact collisions
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
670
|
+
let uniqueIds: string[];
|
|
671
|
+
if (preAllocatedIds && preAllocatedIds.length === tasks.length) {
|
|
672
|
+
uniqueIds = preAllocatedIds;
|
|
673
|
+
} else {
|
|
674
|
+
const outputManager =
|
|
675
|
+
this.session.agentOutputManager ?? new AgentOutputManager(this.session.getArtifactsDir ?? (() => null));
|
|
676
|
+
uniqueIds = await outputManager.allocateBatch(tasks.map(t => t.id));
|
|
677
|
+
}
|
|
433
678
|
const tasksWithUniqueIds = tasks.map((t, i) => ({ ...t, id: uniqueIds[i] }));
|
|
434
679
|
|
|
435
680
|
// Build full prompts with context prepended
|