@oh-my-pi/pi-coding-agent 13.17.6 → 13.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/package.json +7 -11
  3. package/src/autoresearch/git.ts +25 -30
  4. package/src/autoresearch/tools/log-experiment.ts +61 -74
  5. package/src/cli/args.ts +0 -1
  6. package/src/commit/agentic/agent.ts +0 -3
  7. package/src/commit/agentic/index.ts +19 -22
  8. package/src/commit/agentic/tools/git-file-diff.ts +3 -6
  9. package/src/commit/agentic/tools/git-hunk.ts +3 -3
  10. package/src/commit/agentic/tools/git-overview.ts +6 -9
  11. package/src/commit/agentic/tools/index.ts +6 -8
  12. package/src/commit/agentic/tools/propose-commit.ts +4 -7
  13. package/src/commit/agentic/tools/recent-commits.ts +3 -3
  14. package/src/commit/agentic/tools/split-commit.ts +4 -4
  15. package/src/commit/changelog/index.ts +5 -9
  16. package/src/commit/pipeline.ts +10 -12
  17. package/src/config/keybindings.ts +7 -6
  18. package/src/config/settings-schema.ts +45 -1
  19. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +4 -16
  20. package/src/extensibility/custom-commands/bundled/review/index.ts +43 -41
  21. package/src/extensibility/custom-tools/types.ts +1 -1
  22. package/src/extensibility/extensions/types.ts +3 -1
  23. package/src/extensibility/hooks/types.ts +1 -1
  24. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -57
  25. package/src/extensibility/plugins/marketplace/source-resolver.ts +4 -4
  26. package/src/index.ts +1 -0
  27. package/src/internal-urls/types.ts +1 -1
  28. package/src/main.ts +24 -2
  29. package/src/modes/acp/acp-event-mapper.ts +0 -1
  30. package/src/modes/components/footer.ts +9 -29
  31. package/src/modes/components/hook-editor.ts +3 -3
  32. package/src/modes/components/hook-selector.ts +6 -1
  33. package/src/modes/components/session-observer-overlay.ts +472 -0
  34. package/src/modes/components/settings-defs.ts +19 -0
  35. package/src/modes/components/status-line.ts +15 -61
  36. package/src/modes/controllers/command-controller.ts +1 -0
  37. package/src/modes/controllers/event-controller.ts +59 -2
  38. package/src/modes/controllers/extension-ui-controller.ts +1 -0
  39. package/src/modes/controllers/input-controller.ts +3 -0
  40. package/src/modes/controllers/selector-controller.ts +26 -0
  41. package/src/modes/interactive-mode.ts +195 -43
  42. package/src/modes/session-observer-registry.ts +146 -0
  43. package/src/modes/shared.ts +0 -42
  44. package/src/modes/types.ts +2 -0
  45. package/src/modes/utils/keybinding-matchers.ts +9 -0
  46. package/src/prompts/agents/designer.md +1 -1
  47. package/src/prompts/agents/explore.md +1 -1
  48. package/src/prompts/agents/librarian.md +1 -1
  49. package/src/prompts/agents/oracle.md +1 -1
  50. package/src/prompts/agents/plan.md +1 -1
  51. package/src/prompts/agents/reviewer.md +1 -1
  52. package/src/prompts/system/custom-system-prompt.md +5 -0
  53. package/src/prompts/system/system-prompt.md +6 -0
  54. package/src/prompts/tools/read.md +27 -18
  55. package/src/sdk.ts +28 -13
  56. package/src/secrets/index.ts +1 -1
  57. package/src/secrets/obfuscator.ts +24 -16
  58. package/src/session/agent-session.ts +75 -30
  59. package/src/session/artifacts.ts +2 -2
  60. package/src/session/session-manager.ts +15 -5
  61. package/src/system-prompt.ts +4 -0
  62. package/src/task/executor.ts +28 -0
  63. package/src/task/index.ts +89 -79
  64. package/src/task/types.ts +25 -0
  65. package/src/task/worktree.ts +127 -145
  66. package/src/tools/exit-plan-mode.ts +1 -0
  67. package/src/tools/fetch.ts +173 -98
  68. package/src/tools/gh.ts +120 -297
  69. package/src/tools/index.ts +0 -4
  70. package/src/tools/path-utils.ts +12 -1
  71. package/src/tools/read.ts +74 -85
  72. package/src/tools/renderers.ts +0 -2
  73. package/src/utils/external-editor.ts +11 -5
  74. package/src/utils/git.ts +1400 -0
  75. package/src/web/search/render.ts +6 -4
  76. package/src/commit/git/errors.ts +0 -9
  77. package/src/commit/git/index.ts +0 -210
  78. package/src/commit/git/operations.ts +0 -54
  79. package/src/prompts/tools/fetch.md +0 -11
  80. package/src/tools/gh-cli.ts +0 -125
package/src/task/index.ts CHANGED
@@ -18,7 +18,6 @@ import path from "node:path";
18
18
  import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
19
19
  import type { Usage } from "@oh-my-pi/pi-ai";
20
20
  import { $env, Snowflake } from "@oh-my-pi/pi-utils";
21
- import { $ } from "bun";
22
21
  import type { ToolSession } from "..";
23
22
  import { resolveAgentModelPatterns } from "../config/model-resolver";
24
23
  import { renderPromptTemplate } from "../config/prompt-templates";
@@ -30,6 +29,7 @@ import { formatBytes, formatDuration } from "../tools/render-utils";
30
29
  // Import review tools for side effects (registers subagent tool handlers)
31
30
  import "../tools/review";
32
31
  import { generateCommitMessage } from "../utils/commit-message-generator";
32
+ import * as git from "../utils/git";
33
33
  import { discoverAgents, getAgent } from "./discovery";
34
34
  import { runSubprocess } from "./executor";
35
35
  import { resolveIsolationBackendForTaskExecution } from "./isolation-backend";
@@ -109,8 +109,21 @@ export { loadBundledAgents as BUNDLED_AGENTS } from "./agents";
109
109
  export { discoverCommands, expandCommand, getCommand } from "./commands";
110
110
  export { discoverAgents, getAgent } from "./discovery";
111
111
  export { AgentOutputManager } from "./output-manager";
112
- export type { AgentDefinition, AgentProgress, SingleResult, TaskParams, TaskToolDetails } from "./types";
113
- export { taskSchema } from "./types";
112
+ export type {
113
+ AgentDefinition,
114
+ AgentProgress,
115
+ SingleResult,
116
+ SubagentLifecyclePayload,
117
+ SubagentProgressPayload,
118
+ TaskParams,
119
+ TaskToolDetails,
120
+ } from "./types";
121
+ export {
122
+ TASK_SUBAGENT_EVENT_CHANNEL,
123
+ TASK_SUBAGENT_LIFECYCLE_CHANNEL,
124
+ TASK_SUBAGENT_PROGRESS_CHANNEL,
125
+ taskSchema,
126
+ } from "./types";
114
127
 
115
128
  /**
116
129
  * Render the tool description from a cached agent list and current settings.
@@ -496,7 +509,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
496
509
  }
497
510
 
498
511
  const planModeState = this.session.getPlanModeState?.();
499
- const planModeTools = ["read", "grep", "find", "ls", "lsp", "fetch", "web_search"];
512
+ const planModeTools = ["read", "grep", "find", "ls", "lsp", "web_search"];
500
513
  const effectiveAgent: typeof agent = planModeState?.enabled
501
514
  ? {
502
515
  ...agent,
@@ -766,7 +779,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
766
779
  contextFile: contextFilePath,
767
780
  enableLsp: false,
768
781
  signal,
769
- eventBus: undefined,
782
+ eventBus: this.session.eventBus,
770
783
  onProgress: progress => {
771
784
  progressMap.set(index, {
772
785
  ...structuredClone(progress),
@@ -820,7 +833,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
820
833
  contextFile: contextFilePath,
821
834
  enableLsp: false,
822
835
  signal,
823
- eventBus: undefined,
836
+ eventBus: this.session.eventBus,
824
837
  onProgress: progress => {
825
838
  progressMap.set(index, {
826
839
  ...structuredClone(progress),
@@ -864,7 +877,7 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
864
877
  } catch (mergeErr) {
865
878
  // Agent succeeded but branch commit failed — clean up stale branch
866
879
  const branchName = `omp/task/${task.id}`;
867
- await $`git branch -D ${branchName}`.cwd(repoRoot).quiet().nothrow();
880
+ await git.branch.tryDelete(repoRoot, branchName);
868
881
  const msg = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
869
882
  return { ...result, error: `Merge failed: ${msg}` };
870
883
  }
@@ -978,93 +991,90 @@ export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
978
991
  let changesApplied: boolean | null = null;
979
992
  let mergedBranchesForNestedPatches: Set<string> | null = null;
980
993
  if (isIsolated && repoRoot) {
981
- if (mergeMode === "branch") {
982
- // Branch mode: merge task branches sequentially
983
- const branchEntries = results
984
- .filter(r => r.branchName && r.exitCode === 0 && !r.aborted)
985
- .map(r => ({ branchName: r.branchName!, taskId: r.id, description: r.description }));
986
-
987
- if (branchEntries.length === 0) {
988
- changesApplied = true;
989
- } else {
990
- const mergeResult = await mergeTaskBranches(repoRoot, branchEntries);
991
- mergedBranchesForNestedPatches = new Set(mergeResult.merged);
992
- changesApplied = mergeResult.failed.length === 0;
994
+ try {
995
+ if (mergeMode === "branch") {
996
+ // Branch mode: merge task branches sequentially
997
+ const branchEntries = results
998
+ .filter(r => r.branchName && r.exitCode === 0 && !r.aborted)
999
+ .map(r => ({ branchName: r.branchName!, taskId: r.id, description: r.description }));
993
1000
 
994
- if (changesApplied) {
995
- mergeSummary = `\n\nMerged ${mergeResult.merged.length} branch${mergeResult.merged.length === 1 ? "" : "es"}: ${mergeResult.merged.join(", ")}`;
1001
+ if (branchEntries.length === 0) {
1002
+ changesApplied = true;
996
1003
  } else {
997
- const mergedPart =
998
- mergeResult.merged.length > 0 ? `Merged: ${mergeResult.merged.join(", ")}.\n` : "";
999
- const failedPart = `Failed: ${mergeResult.failed.join(", ")}.`;
1000
- const conflictPart = mergeResult.conflict ? `\nConflict: ${mergeResult.conflict}` : "";
1001
- mergeSummary = `\n\n<system-notification>Branch merge failed. ${mergedPart}${failedPart}${conflictPart}\nUnmerged branches remain for manual resolution.</system-notification>`;
1004
+ const mergeResult = await mergeTaskBranches(repoRoot, branchEntries);
1005
+ mergedBranchesForNestedPatches = new Set(mergeResult.merged);
1006
+ changesApplied = mergeResult.failed.length === 0;
1007
+
1008
+ if (changesApplied) {
1009
+ mergeSummary = `\n\nMerged ${mergeResult.merged.length} branch${mergeResult.merged.length === 1 ? "" : "es"}: ${mergeResult.merged.join(", ")}`;
1010
+ } else {
1011
+ const mergedPart =
1012
+ mergeResult.merged.length > 0 ? `Merged: ${mergeResult.merged.join(", ")}.\n` : "";
1013
+ const failedPart = `Failed: ${mergeResult.failed.join(", ")}.`;
1014
+ const conflictPart = mergeResult.conflict ? `\nConflict: ${mergeResult.conflict}` : "";
1015
+ mergeSummary = `\n\n<system-notification>Branch merge failed. ${mergedPart}${failedPart}${conflictPart}\nUnmerged branches remain for manual resolution.</system-notification>`;
1016
+ }
1002
1017
  }
1003
- }
1004
1018
 
1005
- // Clean up merged branches (keep failed ones for manual resolution)
1006
- const allBranches = branchEntries.map(b => b.branchName);
1007
- if (changesApplied) {
1008
- await cleanupTaskBranches(repoRoot, allBranches);
1009
- }
1010
- } else {
1011
- // Patch mode: combine and apply patches
1012
- const patchesInOrder = results.map(result => result.patchPath).filter(Boolean) as string[];
1013
- const missingPatch = results.some(result => !result.patchPath);
1014
- if (missingPatch) {
1015
- changesApplied = false;
1019
+ // Clean up merged branches (keep failed ones for manual resolution)
1020
+ const allBranches = branchEntries.map(b => b.branchName);
1021
+ if (changesApplied) {
1022
+ await cleanupTaskBranches(repoRoot, allBranches);
1023
+ }
1016
1024
  } else {
1017
- const patchStats = await Promise.all(
1018
- patchesInOrder.map(async patchPath => ({
1019
- patchPath,
1020
- size: (await fs.stat(patchPath)).size,
1021
- })),
1022
- );
1023
- const nonEmptyPatches = patchStats.filter(patch => patch.size > 0).map(patch => patch.patchPath);
1024
- if (nonEmptyPatches.length === 0) {
1025
- changesApplied = true;
1025
+ // Patch mode: combine and apply patches
1026
+ const patchesInOrder = results.map(result => result.patchPath).filter(Boolean) as string[];
1027
+ const missingPatch = results.some(result => !result.patchPath);
1028
+ if (missingPatch) {
1029
+ changesApplied = false;
1026
1030
  } else {
1027
- const patchTexts = await Promise.all(
1028
- nonEmptyPatches.map(async patchPath => Bun.file(patchPath).text()),
1031
+ const patchStats = await Promise.all(
1032
+ patchesInOrder.map(async patchPath => ({
1033
+ patchPath,
1034
+ size: (await fs.stat(patchPath)).size,
1035
+ })),
1029
1036
  );
1030
- const combinedPatch = patchTexts.map(text => (text.endsWith("\n") ? text : `${text}\n`)).join("");
1031
- if (!combinedPatch.trim()) {
1037
+ const nonEmptyPatches = patchStats.filter(patch => patch.size > 0).map(patch => patch.patchPath);
1038
+ if (nonEmptyPatches.length === 0) {
1032
1039
  changesApplied = true;
1033
1040
  } else {
1034
- const combinedPatchPath = path.join(os.tmpdir(), `omp-task-combined-${Snowflake.next()}.patch`);
1035
- try {
1036
- await Bun.write(combinedPatchPath, combinedPatch);
1037
- const checkResult = await $`git apply --check --binary ${combinedPatchPath}`
1038
- .cwd(repoRoot)
1039
- .quiet()
1040
- .nothrow();
1041
- if (checkResult.exitCode !== 0) {
1042
- changesApplied = false;
1043
- } else {
1044
- const applyResult = await $`git apply --binary ${combinedPatchPath}`
1045
- .cwd(repoRoot)
1046
- .quiet()
1047
- .nothrow();
1048
- changesApplied = applyResult.exitCode === 0;
1041
+ const patchTexts = await Promise.all(
1042
+ nonEmptyPatches.map(async patchPath => Bun.file(patchPath).text()),
1043
+ );
1044
+ const combinedPatch = patchTexts
1045
+ .map(text => (text.endsWith("\n") ? text : `${text}\n`))
1046
+ .join("");
1047
+ if (!combinedPatch.trim()) {
1048
+ changesApplied = true;
1049
+ } else {
1050
+ changesApplied = await git.patch.canApplyText(repoRoot, combinedPatch);
1051
+ if (changesApplied) {
1052
+ try {
1053
+ await git.patch.applyText(repoRoot, combinedPatch);
1054
+ } catch {
1055
+ changesApplied = false;
1056
+ }
1049
1057
  }
1050
- } finally {
1051
- await fs.rm(combinedPatchPath, { force: true });
1052
1058
  }
1053
1059
  }
1054
1060
  }
1055
- }
1056
1061
 
1057
- if (changesApplied) {
1058
- mergeSummary = "\n\nApplied patches: yes";
1059
- } else {
1060
- const notification =
1061
- "<system-notification>Patches were not applied and must be handled manually.</system-notification>";
1062
- const patchList =
1063
- patchPaths.length > 0
1064
- ? `\n\nPatch artifacts:\n${patchPaths.map(patch => `- ${patch}`).join("\n")}`
1065
- : "";
1066
- mergeSummary = `\n\n${notification}${patchList}`;
1062
+ if (changesApplied) {
1063
+ mergeSummary = "\n\nApplied patches: yes";
1064
+ } else {
1065
+ const notification =
1066
+ "<system-notification>Patches were not applied and must be handled manually.</system-notification>";
1067
+ const patchList =
1068
+ patchPaths.length > 0
1069
+ ? `\n\nPatch artifacts:\n${patchPaths.map(patch => `- ${patch}`).join("\n")}`
1070
+ : "";
1071
+ mergeSummary = `\n\n${notification}${patchList}`;
1072
+ }
1067
1073
  }
1074
+ } catch (mergeErr) {
1075
+ const msg = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
1076
+ changesApplied = false;
1077
+ mergeSummary = `\n\n<system-notification>Merge phase failed: ${msg}\nTask outputs are preserved but changes were not applied.</system-notification>`;
1068
1078
  }
1069
1079
  }
1070
1080
 
package/src/task/types.ts CHANGED
@@ -31,6 +31,31 @@ export const TASK_SUBAGENT_EVENT_CHANNEL = "task:subagent:event";
31
31
  /** EventBus channel for aggregated subagent progress */
32
32
  export const TASK_SUBAGENT_PROGRESS_CHANNEL = "task:subagent:progress";
33
33
 
34
+ /** EventBus channel for subagent lifecycle (start/end) */
35
+ export const TASK_SUBAGENT_LIFECYCLE_CHANNEL = "task:subagent:lifecycle";
36
+
37
+ /** Payload emitted on TASK_SUBAGENT_PROGRESS_CHANNEL */
38
+ export interface SubagentProgressPayload {
39
+ index: number;
40
+ agent: string;
41
+ agentSource: AgentSource;
42
+ task: string;
43
+ assignment?: string;
44
+ progress: AgentProgress;
45
+ sessionFile?: string;
46
+ }
47
+
48
+ /** Payload emitted on TASK_SUBAGENT_LIFECYCLE_CHANNEL */
49
+ export interface SubagentLifecyclePayload {
50
+ id: string;
51
+ agent: string;
52
+ agentSource: AgentSource;
53
+ description?: string;
54
+ status: "started" | "completed" | "failed" | "aborted";
55
+ sessionFile?: string;
56
+ index: number;
57
+ }
58
+
34
59
  /** Single task item for parallel execution */
35
60
  export const taskItemSchema = Type.Object({
36
61
  id: Type.String({