@oh-my-pi/pi-coding-agent 13.2.0 → 13.3.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 +54 -1
- package/package.json +7 -7
- package/scripts/format-prompts.ts +33 -14
- package/scripts/generate-docs-index.ts +2 -2
- package/src/capability/index.ts +1 -2
- package/src/cli/args.ts +3 -3
- package/src/cli/config-cli.ts +1 -1
- package/src/cli/file-processor.ts +1 -2
- package/src/cli/grep-cli.ts +1 -1
- package/src/cli/jupyter-cli.ts +1 -1
- package/src/cli/plugin-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 +1 -2
- package/src/cli/update-cli.ts +1 -2
- package/src/cli/web-search-cli.ts +1 -1
- package/src/cli.ts +1 -1
- package/src/commands/launch.ts +2 -1
- package/src/commit/agentic/agent.ts +2 -1
- package/src/commit/agentic/index.ts +1 -2
- package/src/commit/agentic/prompts/system.md +3 -3
- package/src/commit/agentic/tools/propose-changelog.ts +30 -19
- package/src/commit/changelog/generate.ts +16 -6
- package/src/commit/changelog/index.ts +2 -1
- package/src/commit/pipeline.ts +1 -2
- package/src/commit/prompts/reduce-system.md +1 -1
- package/src/commit/types.ts +10 -1
- package/src/config/keybindings.ts +1 -2
- package/src/config/model-registry.ts +1 -1
- package/src/config/prompt-templates.ts +14 -2
- package/src/config/settings-schema.ts +36 -4
- package/src/config/settings.ts +19 -2
- package/src/config.ts +1 -2
- package/src/debug/index.ts +1 -1
- package/src/debug/report-bundle.ts +1 -2
- package/src/debug/system-info.ts +1 -2
- package/src/discovery/agents.ts +2 -2
- package/src/discovery/builtin.ts +8 -9
- package/src/discovery/claude-plugins.ts +2 -2
- package/src/discovery/claude.ts +30 -12
- package/src/discovery/codex.ts +3 -3
- package/src/discovery/cursor.ts +5 -4
- package/src/discovery/gemini.ts +5 -5
- package/src/discovery/helpers.ts +47 -69
- package/src/discovery/mcp-json.ts +3 -3
- package/src/discovery/opencode.ts +7 -8
- package/src/discovery/ssh.ts +3 -3
- package/src/discovery/vscode.ts +3 -2
- package/src/discovery/windsurf.ts +3 -2
- package/src/exa/company.ts +1 -1
- package/src/exa/factory.ts +1 -6
- package/src/exa/linkedin.ts +1 -1
- package/src/exa/mcp-client.ts +19 -8
- package/src/exa/search.ts +2 -2
- package/src/exa/types.ts +3 -3
- package/src/exec/bash-executor.ts +2 -1
- package/src/exec/non-interactive-env.ts +43 -0
- package/src/export/custom-share.ts +1 -1
- package/src/export/html/index.ts +1 -2
- package/src/extensibility/custom-commands/loader.ts +1 -2
- package/src/extensibility/plugins/installer.ts +1 -2
- package/src/extensibility/plugins/loader.ts +1 -2
- package/src/extensibility/plugins/manager.ts +3 -2
- package/src/extensibility/skills.ts +59 -115
- package/src/index.ts +1 -3
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/ipy/executor.ts +1 -2
- package/src/ipy/gateway-coordinator.ts +1 -2
- package/src/ipy/modules.ts +1 -1
- package/src/ipy/runtime.ts +2 -3
- package/src/main.ts +1 -2
- package/src/mcp/config.ts +2 -2
- package/src/mcp/transports/stdio.ts +1 -2
- package/src/memories/index.ts +1 -2
- package/src/modes/components/extensions/extension-dashboard.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +8 -2
- package/src/modes/components/footer.ts +1 -2
- package/src/modes/components/settings-defs.ts +17 -1
- package/src/modes/components/status-line/segments.ts +1 -2
- package/src/modes/components/status-line.ts +7 -5
- package/src/modes/components/tool-execution.ts +3 -10
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/command-controller.ts +1 -2
- package/src/modes/controllers/mcp-command-controller.ts +5 -4
- package/src/modes/controllers/selector-controller.ts +22 -1
- package/src/modes/controllers/ssh-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +11 -3
- package/src/modes/oauth-manual-input.ts +42 -0
- package/src/modes/shared.ts +1 -2
- package/src/modes/theme/theme.ts +1 -2
- package/src/modes/types.ts +2 -0
- package/src/patch/hashline.ts +19 -1
- package/src/patch/index.ts +1 -25
- package/src/prompts/agents/designer.md +7 -10
- package/src/prompts/agents/explore.md +15 -23
- package/src/prompts/agents/init.md +23 -23
- package/src/prompts/agents/plan.md +14 -77
- package/src/prompts/agents/reviewer.md +6 -5
- package/src/prompts/agents/task.md +13 -11
- package/src/prompts/compaction/branch-summary.md +3 -3
- package/src/prompts/compaction/compaction-short-summary.md +7 -7
- package/src/prompts/compaction/compaction-summary-context.md +1 -1
- package/src/prompts/compaction/compaction-summary.md +5 -5
- package/src/prompts/compaction/compaction-turn-prefix.md +3 -3
- package/src/prompts/compaction/compaction-update-summary.md +11 -11
- package/src/prompts/memories/consolidation.md +5 -5
- package/src/prompts/memories/read-path.md +6 -6
- package/src/prompts/memories/stage_one_input.md +1 -1
- package/src/prompts/memories/stage_one_system.md +5 -5
- package/src/prompts/review-request.md +4 -4
- package/src/prompts/system/agent-creation-architect.md +17 -17
- package/src/prompts/system/agent-creation-user.md +2 -2
- package/src/prompts/system/commit-message-system.md +2 -0
- package/src/prompts/system/custom-system-prompt.md +4 -4
- package/src/prompts/system/plan-mode-active.md +20 -20
- package/src/prompts/system/plan-mode-approved.md +7 -7
- package/src/prompts/system/plan-mode-reference.md +2 -2
- package/src/prompts/system/plan-mode-subagent.md +8 -8
- package/src/prompts/system/subagent-submit-reminder.md +5 -5
- package/src/prompts/system/subagent-system-prompt.md +29 -22
- package/src/prompts/system/subagent-user-prompt.md +7 -3
- package/src/prompts/system/summarization-system.md +1 -1
- package/src/prompts/system/system-prompt.md +214 -226
- package/src/prompts/system/title-system.md +2 -2
- package/src/prompts/system/ttsr-interrupt.md +1 -1
- package/src/prompts/system/web-search.md +16 -16
- package/src/prompts/tools/ask.md +1 -3
- package/src/prompts/tools/await.md +2 -4
- package/src/prompts/tools/bash.md +5 -7
- package/src/prompts/tools/browser.md +4 -6
- package/src/prompts/tools/calculator.md +1 -3
- package/src/prompts/tools/cancel-job.md +2 -4
- package/src/prompts/tools/exit-plan-mode.md +7 -7
- package/src/prompts/tools/fetch.md +0 -2
- package/src/prompts/tools/find.md +3 -5
- package/src/prompts/tools/gemini-image.md +6 -22
- package/src/prompts/tools/grep.md +4 -6
- package/src/prompts/tools/hashline.md +56 -15
- package/src/prompts/tools/lsp.md +1 -3
- package/src/prompts/tools/patch.md +7 -9
- package/src/prompts/tools/python.md +10 -14
- package/src/prompts/tools/read.md +0 -2
- package/src/prompts/tools/replace.md +5 -7
- package/src/prompts/tools/ssh.md +3 -5
- package/src/prompts/tools/task-summary.md +4 -4
- package/src/prompts/tools/task.md +7 -9
- package/src/prompts/tools/todo-write.md +7 -9
- package/src/prompts/tools/web-search.md +3 -5
- package/src/prompts/tools/write.md +3 -5
- package/src/sdk.ts +4 -2
- package/src/session/agent-session.ts +10 -26
- package/src/session/agent-storage.ts +1 -2
- package/src/session/history-storage.ts +1 -2
- package/src/session/session-manager.ts +10 -2
- package/src/slash-commands/builtin-registry.ts +26 -1
- package/src/ssh/connection-manager.ts +11 -2
- package/src/ssh/sshfs-mount.ts +7 -1
- package/src/system-prompt.ts +29 -103
- package/src/task/agents.ts +1 -1
- package/src/task/index.ts +211 -70
- package/src/task/render.ts +24 -8
- package/src/task/types.ts +6 -1
- package/src/task/worktree.ts +394 -32
- package/src/tools/ask.ts +0 -1
- package/src/tools/bash-interactive.ts +2 -45
- package/src/tools/bash.ts +5 -5
- package/src/tools/browser.ts +1 -2
- package/src/tools/gemini-image.ts +8 -28
- package/src/tools/json-tree.ts +2 -1
- package/src/tools/python.ts +1 -1
- package/src/tools/read.ts +1 -2
- package/src/tools/submit-result.ts +22 -23
- package/src/utils/commit-message-generator.ts +132 -0
- package/src/utils/tools-manager.ts +1 -2
- package/src/web/scrapers/artifacthub.ts +2 -1
- package/src/web/scrapers/aur.ts +2 -1
- package/src/web/scrapers/biorxiv.ts +2 -1
- package/src/web/scrapers/bluesky.ts +2 -1
- package/src/web/scrapers/chocolatey.ts +2 -1
- package/src/web/scrapers/cisa-kev.ts +2 -1
- package/src/web/scrapers/clojars.ts +2 -1
- package/src/web/scrapers/coingecko.ts +2 -1
- package/src/web/scrapers/crates-io.ts +2 -1
- package/src/web/scrapers/crossref.ts +2 -1
- package/src/web/scrapers/discogs.ts +3 -1
- package/src/web/scrapers/discourse.ts +2 -1
- package/src/web/scrapers/dockerhub.ts +2 -1
- package/src/web/scrapers/fdroid.ts +2 -1
- package/src/web/scrapers/firefox-addons.ts +2 -1
- package/src/web/scrapers/flathub.ts +2 -1
- package/src/web/scrapers/gitlab.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +2 -1
- package/src/web/scrapers/hackage.ts +2 -1
- package/src/web/scrapers/hackernews.ts +2 -1
- package/src/web/scrapers/hex.ts +2 -1
- package/src/web/scrapers/huggingface.ts +2 -1
- package/src/web/scrapers/jetbrains-marketplace.ts +2 -1
- package/src/web/scrapers/lemmy.ts +2 -1
- package/src/web/scrapers/lobsters.ts +2 -1
- package/src/web/scrapers/mastodon.ts +2 -1
- package/src/web/scrapers/maven.ts +2 -1
- package/src/web/scrapers/mdn.ts +2 -1
- package/src/web/scrapers/metacpan.ts +2 -1
- package/src/web/scrapers/musicbrainz.ts +3 -1
- package/src/web/scrapers/npm.ts +2 -1
- package/src/web/scrapers/nuget.ts +2 -1
- package/src/web/scrapers/nvd.ts +2 -1
- package/src/web/scrapers/ollama.ts +2 -1
- package/src/web/scrapers/open-vsx.ts +2 -1
- package/src/web/scrapers/opencorporates.ts +2 -1
- package/src/web/scrapers/openlibrary.ts +2 -1
- package/src/web/scrapers/orcid.ts +3 -1
- package/src/web/scrapers/osv.ts +2 -1
- package/src/web/scrapers/packagist.ts +2 -1
- package/src/web/scrapers/pub-dev.ts +2 -1
- package/src/web/scrapers/pubmed.ts +2 -1
- package/src/web/scrapers/pypi.ts +2 -1
- package/src/web/scrapers/rawg.ts +2 -8
- package/src/web/scrapers/reddit.ts +2 -1
- package/src/web/scrapers/repology.ts +2 -1
- package/src/web/scrapers/rfc.ts +2 -1
- package/src/web/scrapers/rubygems.ts +2 -1
- package/src/web/scrapers/searchcode.ts +2 -1
- package/src/web/scrapers/sec-edgar.ts +2 -1
- package/src/web/scrapers/semantic-scholar.ts +2 -1
- package/src/web/scrapers/snapcraft.ts +2 -1
- package/src/web/scrapers/sourcegraph.ts +2 -1
- package/src/web/scrapers/spdx.ts +2 -1
- package/src/web/scrapers/stackoverflow.ts +2 -1
- package/src/web/scrapers/terraform.ts +2 -1
- package/src/web/scrapers/types.ts +0 -11
- package/src/web/scrapers/vimeo.ts +2 -1
- package/src/web/scrapers/vscode-marketplace.ts +2 -1
- package/src/web/scrapers/w3c.ts +2 -1
- package/src/web/scrapers/wikidata.ts +2 -1
- package/src/web/search/index.ts +10 -14
- package/src/web/search/provider.ts +2 -2
- package/src/web/search/providers/codex.ts +1 -2
- package/src/web/search/providers/exa.ts +42 -10
- package/src/web/search/providers/gemini.ts +1 -1
- package/src/web/search/providers/perplexity.ts +20 -9
- package/src/web/search/providers/utils.ts +1 -1
package/src/task/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ import taskSummaryTemplate from "../prompts/tools/task-summary.md" with { type:
|
|
|
29
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
|
+
import { generateCommitMessage } from "../utils/commit-message-generator";
|
|
32
33
|
import { discoverAgents, getAgent } from "./discovery";
|
|
33
34
|
import { runSubprocess } from "./executor";
|
|
34
35
|
import { AgentOutputManager } from "./output-manager";
|
|
@@ -47,11 +48,17 @@ import {
|
|
|
47
48
|
} from "./types";
|
|
48
49
|
import {
|
|
49
50
|
applyBaseline,
|
|
51
|
+
applyNestedPatches,
|
|
50
52
|
captureBaseline,
|
|
51
53
|
captureDeltaPatch,
|
|
54
|
+
cleanupFuseOverlay,
|
|
55
|
+
cleanupTaskBranches,
|
|
52
56
|
cleanupWorktree,
|
|
57
|
+
commitToBranch,
|
|
58
|
+
ensureFuseOverlay,
|
|
53
59
|
ensureWorktree,
|
|
54
60
|
getRepoRoot,
|
|
61
|
+
mergeTaskBranches,
|
|
55
62
|
type WorktreeBaseline,
|
|
56
63
|
} from "./worktree";
|
|
57
64
|
|
|
@@ -145,11 +152,11 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
145
152
|
get description(): string {
|
|
146
153
|
const disabledAgents = this.session.settings.get("task.disabledAgents") as string[];
|
|
147
154
|
const maxConcurrency = this.session.settings.get("task.maxConcurrency");
|
|
148
|
-
const
|
|
155
|
+
const isolationMode = this.session.settings.get("task.isolation.mode");
|
|
149
156
|
return renderDescription(
|
|
150
157
|
this.#discoveredAgents,
|
|
151
158
|
maxConcurrency,
|
|
152
|
-
|
|
159
|
+
isolationMode !== "none",
|
|
153
160
|
this.session.settings.get("async.enabled"),
|
|
154
161
|
disabledAgents,
|
|
155
162
|
);
|
|
@@ -168,9 +175,9 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
168
175
|
* Create a TaskTool instance with async agent discovery.
|
|
169
176
|
*/
|
|
170
177
|
static async create(session: ToolSession): Promise<TaskTool> {
|
|
171
|
-
const
|
|
178
|
+
const isolationMode = session.settings.get("task.isolation.mode");
|
|
172
179
|
const { agents } = await discoverAgents(session.cwd);
|
|
173
|
-
return new TaskTool(session, agents,
|
|
180
|
+
return new TaskTool(session, agents, isolationMode !== "none");
|
|
174
181
|
}
|
|
175
182
|
|
|
176
183
|
async execute(
|
|
@@ -422,18 +429,20 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
422
429
|
const startTime = Date.now();
|
|
423
430
|
const { agents, projectAgentsDir } = await discoverAgents(this.session.cwd);
|
|
424
431
|
const { agent: agentName, context, schema: outputSchema } = params;
|
|
425
|
-
const
|
|
432
|
+
const isolationMode = this.session.settings.get("task.isolation.mode");
|
|
426
433
|
const isolationRequested = "isolated" in params ? params.isolated === true : false;
|
|
427
|
-
const isIsolated =
|
|
434
|
+
const isIsolated = isolationMode !== "none" && isolationRequested;
|
|
435
|
+
const mergeMode = this.session.settings.get("task.isolation.merge");
|
|
436
|
+
const commitStyle = this.session.settings.get("task.isolation.commits");
|
|
428
437
|
const maxConcurrency = this.session.settings.get("task.maxConcurrency");
|
|
429
438
|
const taskDepth = this.session.taskDepth ?? 0;
|
|
430
439
|
|
|
431
|
-
if (
|
|
440
|
+
if (isolationMode === "none" && "isolated" in params) {
|
|
432
441
|
return {
|
|
433
442
|
content: [
|
|
434
443
|
{
|
|
435
444
|
type: "text",
|
|
436
|
-
text: "Task isolation is disabled. Remove the isolated argument to
|
|
445
|
+
text: "Task isolation is disabled. Remove the isolated argument or set task.isolation.mode to 'worktree' or 'fuse-overlay'.",
|
|
437
446
|
},
|
|
438
447
|
],
|
|
439
448
|
details: {
|
|
@@ -789,16 +798,23 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
789
798
|
}
|
|
790
799
|
|
|
791
800
|
const taskStart = Date.now();
|
|
792
|
-
let
|
|
801
|
+
let isolationDir: string | undefined;
|
|
793
802
|
try {
|
|
794
803
|
if (!repoRoot || !baseline) {
|
|
795
804
|
throw new Error("Isolated task execution not initialized.");
|
|
796
805
|
}
|
|
797
|
-
|
|
798
|
-
|
|
806
|
+
const taskBaseline = structuredClone(baseline);
|
|
807
|
+
|
|
808
|
+
if (isolationMode === "fuse-overlay") {
|
|
809
|
+
isolationDir = await ensureFuseOverlay(repoRoot, task.id);
|
|
810
|
+
} else {
|
|
811
|
+
isolationDir = await ensureWorktree(repoRoot, task.id);
|
|
812
|
+
await applyBaseline(isolationDir, taskBaseline);
|
|
813
|
+
}
|
|
814
|
+
|
|
799
815
|
const result = await runSubprocess({
|
|
800
816
|
cwd: this.session.cwd,
|
|
801
|
-
worktree:
|
|
817
|
+
worktree: isolationDir,
|
|
802
818
|
agent,
|
|
803
819
|
task: task.task,
|
|
804
820
|
description: task.description,
|
|
@@ -830,13 +846,56 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
830
846
|
preloadedSkills: task.preloadedSkills,
|
|
831
847
|
promptTemplates,
|
|
832
848
|
});
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
849
|
+
if (mergeMode === "branch" && result.exitCode === 0) {
|
|
850
|
+
try {
|
|
851
|
+
const commitMsg =
|
|
852
|
+
commitStyle === "ai" && this.session.modelRegistry
|
|
853
|
+
? async (diff: string) => {
|
|
854
|
+
const smolModel = this.session.settings.getModelRole("smol");
|
|
855
|
+
return generateCommitMessage(
|
|
856
|
+
diff,
|
|
857
|
+
this.session.modelRegistry!,
|
|
858
|
+
smolModel,
|
|
859
|
+
this.session.getSessionId?.() ?? undefined,
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
: undefined;
|
|
863
|
+
const commitResult = await commitToBranch(
|
|
864
|
+
isolationDir,
|
|
865
|
+
taskBaseline,
|
|
866
|
+
task.id,
|
|
867
|
+
task.description,
|
|
868
|
+
commitMsg,
|
|
869
|
+
);
|
|
870
|
+
return {
|
|
871
|
+
...result,
|
|
872
|
+
branchName: commitResult?.branchName,
|
|
873
|
+
nestedPatches: commitResult?.nestedPatches,
|
|
874
|
+
};
|
|
875
|
+
} catch (mergeErr) {
|
|
876
|
+
// Agent succeeded but branch commit failed — clean up stale branch
|
|
877
|
+
const branchName = `omp/task/${task.id}`;
|
|
878
|
+
await $`git branch -D ${branchName}`.cwd(repoRoot).quiet().nothrow();
|
|
879
|
+
const msg = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
|
|
880
|
+
return { ...result, error: `Merge failed: ${msg}` };
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (result.exitCode === 0) {
|
|
884
|
+
try {
|
|
885
|
+
const delta = await captureDeltaPatch(isolationDir, taskBaseline);
|
|
886
|
+
const patchPath = path.join(effectiveArtifactsDir, `${task.id}.patch`);
|
|
887
|
+
await Bun.write(patchPath, delta.rootPatch);
|
|
888
|
+
return {
|
|
889
|
+
...result,
|
|
890
|
+
patchPath,
|
|
891
|
+
nestedPatches: delta.nestedPatches,
|
|
892
|
+
};
|
|
893
|
+
} catch (patchErr) {
|
|
894
|
+
const msg = patchErr instanceof Error ? patchErr.message : String(patchErr);
|
|
895
|
+
return { ...result, error: `Patch capture failed: ${msg}` };
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return result;
|
|
840
899
|
} catch (err) {
|
|
841
900
|
const message = err instanceof Error ? err.message : String(err);
|
|
842
901
|
return {
|
|
@@ -856,8 +915,12 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
856
915
|
error: message,
|
|
857
916
|
};
|
|
858
917
|
} finally {
|
|
859
|
-
if (
|
|
860
|
-
|
|
918
|
+
if (isolationDir) {
|
|
919
|
+
if (isolationMode === "fuse-overlay") {
|
|
920
|
+
await cleanupFuseOverlay(isolationDir);
|
|
921
|
+
} else {
|
|
922
|
+
await cleanupWorktree(isolationDir);
|
|
923
|
+
}
|
|
861
924
|
}
|
|
862
925
|
}
|
|
863
926
|
};
|
|
@@ -917,74 +980,152 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
917
980
|
}
|
|
918
981
|
}
|
|
919
982
|
|
|
920
|
-
let
|
|
921
|
-
let
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
983
|
+
let mergeSummary = "";
|
|
984
|
+
let changesApplied: boolean | null = null;
|
|
985
|
+
let mergedBranchesForNestedPatches: Set<string> | null = null;
|
|
986
|
+
if (isIsolated && repoRoot) {
|
|
987
|
+
if (mergeMode === "branch") {
|
|
988
|
+
// Branch mode: merge task branches sequentially
|
|
989
|
+
const branchEntries = results
|
|
990
|
+
.filter(r => r.branchName && r.exitCode === 0 && !r.aborted)
|
|
991
|
+
.map(r => ({ branchName: r.branchName!, taskId: r.id, description: r.description }));
|
|
992
|
+
|
|
993
|
+
if (branchEntries.length === 0) {
|
|
994
|
+
changesApplied = true;
|
|
995
|
+
} else {
|
|
996
|
+
const mergeResult = await mergeTaskBranches(repoRoot, branchEntries);
|
|
997
|
+
mergedBranchesForNestedPatches = new Set(mergeResult.merged);
|
|
998
|
+
changesApplied = mergeResult.failed.length === 0;
|
|
999
|
+
|
|
1000
|
+
if (changesApplied) {
|
|
1001
|
+
mergeSummary = `\n\nMerged ${mergeResult.merged.length} branch${mergeResult.merged.length === 1 ? "" : "es"}: ${mergeResult.merged.join(", ")}`;
|
|
1002
|
+
} else {
|
|
1003
|
+
const mergedPart =
|
|
1004
|
+
mergeResult.merged.length > 0 ? `Merged: ${mergeResult.merged.join(", ")}.\n` : "";
|
|
1005
|
+
const failedPart = `Failed: ${mergeResult.failed.join(", ")}.`;
|
|
1006
|
+
const conflictPart = mergeResult.conflict ? `\nConflict: ${mergeResult.conflict}` : "";
|
|
1007
|
+
mergeSummary = `\n\n<system-notification>Branch merge failed. ${mergedPart}${failedPart}${conflictPart}\nUnmerged branches remain for manual resolution.</system-notification>`;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Clean up merged branches (keep failed ones for manual resolution)
|
|
1012
|
+
const allBranches = branchEntries.map(b => b.branchName);
|
|
1013
|
+
if (changesApplied) {
|
|
1014
|
+
await cleanupTaskBranches(repoRoot, allBranches);
|
|
1015
|
+
}
|
|
927
1016
|
} else {
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
);
|
|
934
|
-
const nonEmptyPatches = patchStats.filter(patch => patch.size > 0).map(patch => patch.patchPath);
|
|
935
|
-
if (nonEmptyPatches.length === 0) {
|
|
936
|
-
patchesApplied = true;
|
|
1017
|
+
// Patch mode: combine and apply patches
|
|
1018
|
+
const patchesInOrder = results.map(result => result.patchPath).filter(Boolean) as string[];
|
|
1019
|
+
const missingPatch = results.some(result => !result.patchPath);
|
|
1020
|
+
if (missingPatch) {
|
|
1021
|
+
changesApplied = false;
|
|
937
1022
|
} else {
|
|
938
|
-
const
|
|
939
|
-
|
|
1023
|
+
const patchStats = await Promise.all(
|
|
1024
|
+
patchesInOrder.map(async patchPath => ({
|
|
1025
|
+
patchPath,
|
|
1026
|
+
size: (await fs.stat(patchPath)).size,
|
|
1027
|
+
})),
|
|
940
1028
|
);
|
|
941
|
-
const
|
|
942
|
-
if (
|
|
943
|
-
|
|
1029
|
+
const nonEmptyPatches = patchStats.filter(patch => patch.size > 0).map(patch => patch.patchPath);
|
|
1030
|
+
if (nonEmptyPatches.length === 0) {
|
|
1031
|
+
changesApplied = true;
|
|
944
1032
|
} else {
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
const
|
|
1033
|
+
const patchTexts = await Promise.all(
|
|
1034
|
+
nonEmptyPatches.map(async patchPath => Bun.file(patchPath).text()),
|
|
1035
|
+
);
|
|
1036
|
+
const combinedPatch = patchTexts.map(text => (text.endsWith("\n") ? text : `${text}\n`)).join("");
|
|
1037
|
+
if (!combinedPatch.trim()) {
|
|
1038
|
+
changesApplied = true;
|
|
1039
|
+
} else {
|
|
1040
|
+
const combinedPatchPath = path.join(os.tmpdir(), `omp-task-combined-${Snowflake.next()}.patch`);
|
|
1041
|
+
try {
|
|
1042
|
+
await Bun.write(combinedPatchPath, combinedPatch);
|
|
1043
|
+
const checkResult = await $`git apply --check --binary ${combinedPatchPath}`
|
|
956
1044
|
.cwd(repoRoot)
|
|
957
1045
|
.quiet()
|
|
958
1046
|
.nothrow();
|
|
959
|
-
|
|
1047
|
+
if (checkResult.exitCode !== 0) {
|
|
1048
|
+
changesApplied = false;
|
|
1049
|
+
} else {
|
|
1050
|
+
const applyResult = await $`git apply --binary ${combinedPatchPath}`
|
|
1051
|
+
.cwd(repoRoot)
|
|
1052
|
+
.quiet()
|
|
1053
|
+
.nothrow();
|
|
1054
|
+
changesApplied = applyResult.exitCode === 0;
|
|
1055
|
+
}
|
|
1056
|
+
} finally {
|
|
1057
|
+
await fs.rm(combinedPatchPath, { force: true });
|
|
960
1058
|
}
|
|
961
|
-
} finally {
|
|
962
|
-
await fs.rm(combinedPatchPath, { force: true });
|
|
963
1059
|
}
|
|
964
1060
|
}
|
|
965
1061
|
}
|
|
1062
|
+
|
|
1063
|
+
if (changesApplied) {
|
|
1064
|
+
mergeSummary = "\n\nApplied patches: yes";
|
|
1065
|
+
} else {
|
|
1066
|
+
const notification =
|
|
1067
|
+
"<system-notification>Patches were not applied and must be handled manually.</system-notification>";
|
|
1068
|
+
const patchList =
|
|
1069
|
+
patchPaths.length > 0
|
|
1070
|
+
? `\n\nPatch artifacts:\n${patchPaths.map(patch => `- ${patch}`).join("\n")}`
|
|
1071
|
+
: "";
|
|
1072
|
+
mergeSummary = `\n\n${notification}${patchList}`;
|
|
1073
|
+
}
|
|
966
1074
|
}
|
|
1075
|
+
}
|
|
967
1076
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1077
|
+
// Apply nested repo patches (separate from parent git)
|
|
1078
|
+
if (isIsolated && repoRoot && (mergeMode === "branch" || changesApplied !== false)) {
|
|
1079
|
+
const allNestedPatches = results
|
|
1080
|
+
.filter(r => {
|
|
1081
|
+
if (!r.nestedPatches || r.nestedPatches.length === 0 || r.exitCode !== 0 || r.aborted) {
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
if (mergeMode !== "branch") {
|
|
1085
|
+
return true;
|
|
1086
|
+
}
|
|
1087
|
+
if (!r.branchName || !mergedBranchesForNestedPatches) {
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
1090
|
+
return mergedBranchesForNestedPatches.has(r.branchName);
|
|
1091
|
+
})
|
|
1092
|
+
.flatMap(r => r.nestedPatches!);
|
|
1093
|
+
if (allNestedPatches.length > 0) {
|
|
1094
|
+
try {
|
|
1095
|
+
const commitMsg =
|
|
1096
|
+
commitStyle === "ai" && this.session.modelRegistry
|
|
1097
|
+
? async (diff: string) => {
|
|
1098
|
+
const smolModel = this.session.settings.getModelRole("smol");
|
|
1099
|
+
return generateCommitMessage(
|
|
1100
|
+
diff,
|
|
1101
|
+
this.session.modelRegistry!,
|
|
1102
|
+
smolModel,
|
|
1103
|
+
this.session.getSessionId?.() ?? undefined,
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
: undefined;
|
|
1107
|
+
await applyNestedPatches(repoRoot, allNestedPatches, commitMsg);
|
|
1108
|
+
} catch {
|
|
1109
|
+
// Nested patch failures are non-fatal to the parent merge
|
|
1110
|
+
mergeSummary +=
|
|
1111
|
+
"\n\n<system-notification>Some nested repository patches failed to apply.</system-notification>";
|
|
1112
|
+
}
|
|
978
1113
|
}
|
|
979
1114
|
}
|
|
980
1115
|
|
|
981
1116
|
// Build final output - match plugin format
|
|
982
|
-
const successCount = results.filter(r => r.exitCode === 0).length;
|
|
1117
|
+
const successCount = results.filter(r => r.exitCode === 0 && !r.error).length;
|
|
983
1118
|
const cancelledCount = results.filter(r => r.aborted).length;
|
|
984
1119
|
const totalDuration = Date.now() - startTime;
|
|
985
1120
|
|
|
986
1121
|
const summaries = results.map(r => {
|
|
987
|
-
const status = r.aborted
|
|
1122
|
+
const status = r.aborted
|
|
1123
|
+
? "cancelled"
|
|
1124
|
+
: r.exitCode === 0 && r.error
|
|
1125
|
+
? "merge failed"
|
|
1126
|
+
: r.exitCode === 0
|
|
1127
|
+
? "completed"
|
|
1128
|
+
: `failed (exit ${r.exitCode})`;
|
|
988
1129
|
const output = r.output.trim() || r.stderr.trim() || "(no output)";
|
|
989
1130
|
const outputCharCount = r.outputMeta?.charCount ?? output.length;
|
|
990
1131
|
const fullOutputThreshold = 5000;
|
|
@@ -1021,12 +1162,12 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
|
|
|
1021
1162
|
summaries,
|
|
1022
1163
|
outputIds,
|
|
1023
1164
|
agentName,
|
|
1024
|
-
|
|
1165
|
+
mergeSummary,
|
|
1025
1166
|
});
|
|
1026
1167
|
|
|
1027
1168
|
// Cleanup temp directory if used
|
|
1028
1169
|
const shouldCleanupTempArtifacts =
|
|
1029
|
-
tempArtifactsDir && (!isIsolated ||
|
|
1170
|
+
tempArtifactsDir && (!isIsolated || changesApplied === true || changesApplied === null);
|
|
1030
1171
|
if (shouldCleanupTempArtifacts) {
|
|
1031
1172
|
await fs.rm(tempArtifactsDir, { recursive: true, force: true });
|
|
1032
1173
|
}
|
package/src/task/render.ts
CHANGED
|
@@ -728,7 +728,8 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
728
728
|
result.output,
|
|
729
729
|
);
|
|
730
730
|
const aborted = result.aborted ?? false;
|
|
731
|
-
const
|
|
731
|
+
const mergeFailed = !aborted && result.exitCode === 0 && !!result.error;
|
|
732
|
+
const success = !aborted && result.exitCode === 0 && !result.error;
|
|
732
733
|
const needsWarning = Boolean(missingCompleteWarning) && success;
|
|
733
734
|
const icon = aborted
|
|
734
735
|
? theme.status.aborted
|
|
@@ -737,8 +738,16 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
737
738
|
: success
|
|
738
739
|
? theme.status.success
|
|
739
740
|
: theme.status.error;
|
|
740
|
-
const iconColor = needsWarning ? "warning" : success ? "success" : "error";
|
|
741
|
-
const statusText = aborted
|
|
741
|
+
const iconColor = needsWarning ? "warning" : success ? "success" : mergeFailed ? "warning" : "error";
|
|
742
|
+
const statusText = aborted
|
|
743
|
+
? "aborted"
|
|
744
|
+
: needsWarning
|
|
745
|
+
? "warning"
|
|
746
|
+
: success
|
|
747
|
+
? "done"
|
|
748
|
+
: mergeFailed
|
|
749
|
+
? "merge failed"
|
|
750
|
+
: "failed";
|
|
742
751
|
|
|
743
752
|
// Main status line: id: description [status] · stats · ⟨agent⟩
|
|
744
753
|
const description = result.description?.trim();
|
|
@@ -847,11 +856,13 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
847
856
|
|
|
848
857
|
if (result.patchPath && !aborted && result.exitCode === 0) {
|
|
849
858
|
lines.push(`${continuePrefix}${theme.fg("dim", `Patch: ${result.patchPath}`)}`);
|
|
859
|
+
} else if (result.branchName && !aborted && result.exitCode === 0) {
|
|
860
|
+
lines.push(`${continuePrefix}${theme.fg("dim", `Branch: ${result.branchName}`)}`);
|
|
850
861
|
}
|
|
851
862
|
|
|
852
863
|
// Error message
|
|
853
|
-
if (result.error && !success) {
|
|
854
|
-
lines.push(`${continuePrefix}${theme.fg("error", truncateToWidth(result.error, 70))}`);
|
|
864
|
+
if (result.error && (!success || mergeFailed)) {
|
|
865
|
+
lines.push(`${continuePrefix}${theme.fg(mergeFailed ? "warning" : "error", truncateToWidth(result.error, 70))}`);
|
|
855
866
|
}
|
|
856
867
|
|
|
857
868
|
return lines;
|
|
@@ -902,15 +913,20 @@ export function renderResult(
|
|
|
902
913
|
});
|
|
903
914
|
|
|
904
915
|
const abortedCount = details.results.filter(r => r.aborted).length;
|
|
905
|
-
const
|
|
906
|
-
const
|
|
916
|
+
const mergeFailedCount = details.results.filter(r => !r.aborted && r.exitCode === 0 && r.error).length;
|
|
917
|
+
const successCount = details.results.filter(r => !r.aborted && r.exitCode === 0 && !r.error).length;
|
|
918
|
+
const failCount = details.results.length - successCount - mergeFailedCount - abortedCount;
|
|
907
919
|
let summary = `${theme.fg("dim", "Total:")} `;
|
|
908
920
|
if (abortedCount > 0) {
|
|
909
921
|
summary += theme.fg("error", `${abortedCount} aborted`);
|
|
910
|
-
if (successCount > 0 || failCount > 0) summary += theme.sep.dot;
|
|
922
|
+
if (successCount > 0 || mergeFailedCount > 0 || failCount > 0) summary += theme.sep.dot;
|
|
911
923
|
}
|
|
912
924
|
if (successCount > 0) {
|
|
913
925
|
summary += theme.fg("success", `${successCount} succeeded`);
|
|
926
|
+
if (mergeFailedCount > 0 || failCount > 0) summary += theme.sep.dot;
|
|
927
|
+
}
|
|
928
|
+
if (mergeFailedCount > 0) {
|
|
929
|
+
summary += theme.fg("warning", `${mergeFailedCount} merge failed`);
|
|
914
930
|
if (failCount > 0) summary += theme.sep.dot;
|
|
915
931
|
}
|
|
916
932
|
if (failCount > 0) {
|
package/src/task/types.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
|
2
2
|
import type { Usage } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { type Static, Type } from "@sinclair/typebox";
|
|
5
|
+
import type { NestedRepoPatch } from "./worktree";
|
|
5
6
|
|
|
6
7
|
/** Source of an agent definition */
|
|
7
8
|
export type AgentSource = "bundled" | "user" | "project";
|
|
@@ -77,7 +78,7 @@ const createTaskSchema = (options: { isolationEnabled: boolean }) => {
|
|
|
77
78
|
...properties,
|
|
78
79
|
isolated: Type.Optional(
|
|
79
80
|
Type.Boolean({
|
|
80
|
-
description: "Run in isolated
|
|
81
|
+
description: "Run in isolated environment; returns patches. Use when tasks edit overlapping files.",
|
|
81
82
|
}),
|
|
82
83
|
),
|
|
83
84
|
});
|
|
@@ -179,6 +180,10 @@ export interface SingleResult {
|
|
|
179
180
|
outputPath?: string;
|
|
180
181
|
/** Patch path for isolated worktree output */
|
|
181
182
|
patchPath?: string;
|
|
183
|
+
/** Branch name for isolated branch-mode output */
|
|
184
|
+
branchName?: string;
|
|
185
|
+
/** Nested repo patches to apply after parent merge */
|
|
186
|
+
nestedPatches?: NestedRepoPatch[];
|
|
182
187
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
183
188
|
extractedToolData?: Record<string, unknown[]>;
|
|
184
189
|
/** Output metadata for agent:// URL integration */
|