@oh-my-pi/pi-coding-agent 13.18.0 → 14.0.2
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 +316 -1
- package/package.json +86 -24
- package/scripts/format-prompts.ts +2 -2
- package/src/autoresearch/apply-contract-to-state.ts +24 -0
- package/src/autoresearch/contract.ts +0 -44
- package/src/autoresearch/dashboard.ts +1 -2
- package/src/autoresearch/git.ts +116 -30
- package/src/autoresearch/helpers.ts +49 -0
- package/src/autoresearch/index.ts +28 -187
- package/src/autoresearch/prompt.md +26 -9
- package/src/autoresearch/state.ts +0 -6
- package/src/autoresearch/tools/init-experiment.ts +202 -117
- package/src/autoresearch/tools/log-experiment.ts +123 -178
- package/src/autoresearch/tools/run-experiment.ts +48 -10
- package/src/autoresearch/types.ts +2 -2
- package/src/capability/index.ts +4 -2
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/grep-cli.ts +8 -8
- package/src/cli/grievances-cli.ts +78 -0
- package/src/cli/read-cli.ts +67 -0
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/update-cli.ts +3 -3
- package/src/cli.ts +2 -0
- package/src/commands/grep.ts +6 -1
- package/src/commands/grievances.ts +20 -0
- package/src/commands/read.ts +33 -0
- package/src/commit/agentic/agent.ts +5 -8
- package/src/commit/agentic/index.ts +22 -26
- package/src/commit/agentic/tools/analyze-file.ts +3 -3
- package/src/commit/agentic/tools/git-file-diff.ts +3 -6
- package/src/commit/agentic/tools/git-hunk.ts +3 -3
- package/src/commit/agentic/tools/git-overview.ts +6 -9
- package/src/commit/agentic/tools/index.ts +6 -8
- package/src/commit/agentic/tools/propose-commit.ts +4 -7
- package/src/commit/agentic/tools/recent-commits.ts +3 -3
- package/src/commit/agentic/tools/split-commit.ts +4 -4
- package/src/commit/agentic/validation.ts +1 -1
- package/src/commit/analysis/conventional.ts +4 -4
- package/src/commit/analysis/summary.ts +3 -3
- package/src/commit/changelog/generate.ts +4 -4
- package/src/commit/changelog/index.ts +5 -9
- package/src/commit/map-reduce/map-phase.ts +4 -4
- package/src/commit/map-reduce/reduce-phase.ts +4 -4
- package/src/commit/pipeline.ts +13 -16
- package/src/config/keybindings.ts +7 -6
- package/src/config/prompt-templates.ts +44 -226
- package/src/config/resolve-config-value.ts +4 -2
- package/src/config/settings-schema.ts +98 -2
- package/src/config/settings.ts +25 -26
- package/src/dap/client.ts +674 -0
- package/src/dap/config.ts +150 -0
- package/src/dap/defaults.json +211 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1255 -0
- package/src/dap/types.ts +600 -0
- package/src/debug/log-viewer.ts +3 -2
- package/src/discovery/builtin.ts +1 -2
- package/src/discovery/codex.ts +2 -2
- package/src/discovery/github.ts +2 -1
- package/src/discovery/helpers.ts +2 -2
- package/src/discovery/opencode.ts +2 -2
- package/src/edit/diff.ts +818 -0
- package/src/edit/index.ts +309 -0
- package/src/edit/line-hash.ts +67 -0
- package/src/edit/modes/chunk.ts +454 -0
- package/src/{patch → edit/modes}/hashline.ts +741 -361
- package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
- package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
- package/src/{patch → edit}/normalize.ts +97 -76
- package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
- package/src/exec/bash-executor.ts +4 -2
- package/src/exec/idle-timeout-watchdog.ts +126 -0
- package/src/exec/non-interactive-env.ts +5 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +6 -18
- package/src/extensibility/custom-commands/bundled/review/index.ts +45 -43
- package/src/extensibility/custom-commands/loader.ts +1 -2
- package/src/extensibility/custom-tools/loader.ts +34 -11
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/loader.ts +9 -4
- package/src/extensibility/extensions/runner.ts +24 -1
- package/src/extensibility/extensions/types.ts +4 -2
- package/src/extensibility/hooks/loader.ts +5 -6
- package/src/extensibility/hooks/types.ts +2 -2
- package/src/extensibility/plugins/doctor.ts +2 -1
- package/src/extensibility/plugins/marketplace/fetcher.ts +2 -57
- package/src/extensibility/plugins/marketplace/source-resolver.ts +4 -4
- package/src/extensibility/slash-commands.ts +3 -7
- package/src/index.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/ipy/executor.ts +58 -17
- package/src/ipy/gateway-coordinator.ts +6 -4
- package/src/ipy/kernel.ts +45 -22
- package/src/ipy/runtime.ts +2 -2
- package/src/lsp/client.ts +7 -4
- package/src/lsp/clients/lsp-linter-client.ts +4 -4
- package/src/lsp/config.ts +2 -2
- package/src/lsp/defaults.json +688 -154
- package/src/lsp/index.ts +234 -45
- package/src/lsp/lspmux.ts +2 -2
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +12 -1
- package/src/lsp/utils.ts +8 -1
- package/src/main.ts +125 -47
- package/src/memories/index.ts +4 -5
- package/src/modes/acp/acp-agent.ts +563 -163
- package/src/modes/acp/acp-event-mapper.ts +9 -1
- package/src/modes/acp/acp-mode.ts +4 -2
- package/src/modes/components/agent-dashboard.ts +3 -4
- package/src/modes/components/diff.ts +6 -7
- package/src/modes/components/footer.ts +9 -29
- package/src/modes/components/hook-editor.ts +3 -3
- package/src/modes/components/hook-selector.ts +6 -1
- package/src/modes/components/read-tool-group.ts +6 -12
- package/src/modes/components/session-observer-overlay.ts +472 -0
- package/src/modes/components/settings-defs.ts +24 -0
- package/src/modes/components/status-line.ts +15 -61
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/btw-controller.ts +2 -2
- package/src/modes/controllers/command-controller.ts +4 -2
- package/src/modes/controllers/event-controller.ts +59 -2
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +15 -8
- package/src/modes/controllers/selector-controller.ts +26 -0
- package/src/modes/index.ts +20 -2
- package/src/modes/interactive-mode.ts +278 -69
- package/src/modes/rpc/host-tools.ts +186 -0
- package/src/modes/rpc/rpc-client.ts +178 -13
- package/src/modes/rpc/rpc-mode.ts +73 -3
- package/src/modes/rpc/rpc-types.ts +53 -1
- package/src/modes/session-observer-registry.ts +146 -0
- package/src/modes/shared.ts +0 -42
- package/src/modes/theme/theme.ts +80 -8
- package/src/modes/types.ts +4 -2
- package/src/modes/utils/keybinding-matchers.ts +9 -0
- package/src/prompts/system/custom-system-prompt.md +5 -0
- package/src/prompts/system/system-prompt.md +8 -1
- package/src/prompts/tools/chunk-edit.md +219 -0
- package/src/prompts/tools/debug.md +43 -0
- package/src/prompts/tools/grep.md +3 -0
- package/src/prompts/tools/lsp.md +5 -5
- package/src/prompts/tools/read-chunk.md +17 -0
- package/src/prompts/tools/read.md +19 -5
- package/src/sdk.ts +216 -165
- package/src/secrets/index.ts +1 -1
- package/src/secrets/obfuscator.ts +25 -17
- package/src/session/agent-session.ts +381 -286
- package/src/session/agent-storage.ts +12 -12
- package/src/session/compaction/branch-summarization.ts +3 -3
- package/src/session/compaction/compaction.ts +5 -6
- package/src/session/compaction/utils.ts +3 -3
- package/src/session/history-storage.ts +62 -19
- package/src/session/messages.ts +3 -3
- package/src/session/session-dump-format.ts +203 -0
- package/src/session/session-manager.ts +15 -5
- package/src/session/session-storage.ts +4 -2
- package/src/session/streaming-output.ts +1 -1
- package/src/session/tool-choice-queue.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +56 -8
- package/src/ssh/connection-manager.ts +2 -2
- package/src/ssh/sshfs-mount.ts +5 -5
- package/src/stt/downloader.ts +4 -4
- package/src/stt/recorder.ts +4 -4
- package/src/stt/transcriber.ts +2 -2
- package/src/system-prompt.ts +25 -13
- package/src/task/agents.ts +5 -6
- package/src/task/commands.ts +2 -5
- package/src/task/executor.ts +32 -4
- package/src/task/index.ts +91 -82
- package/src/task/template.ts +2 -2
- package/src/task/types.ts +25 -0
- package/src/task/worktree.ts +131 -149
- package/src/tools/ask.ts +2 -3
- package/src/tools/ast-edit.ts +7 -7
- package/src/tools/ast-grep.ts +7 -7
- package/src/tools/auto-generated-guard.ts +36 -41
- package/src/tools/await-tool.ts +2 -2
- package/src/tools/bash.ts +5 -23
- package/src/tools/browser.ts +4 -5
- package/src/tools/calculator.ts +2 -3
- package/src/tools/cancel-job.ts +2 -2
- package/src/tools/checkpoint.ts +3 -3
- package/src/tools/debug.ts +1007 -0
- package/src/tools/exit-plan-mode.ts +3 -3
- package/src/tools/fetch.ts +67 -3
- package/src/tools/find.ts +4 -5
- package/src/tools/fs-cache-invalidation.ts +5 -0
- package/src/tools/gemini-image.ts +13 -5
- package/src/tools/gh.ts +130 -308
- package/src/tools/grep.ts +57 -9
- package/src/tools/index.ts +44 -22
- package/src/tools/inspect-image.ts +4 -4
- package/src/tools/output-meta.ts +1 -1
- package/src/tools/python.ts +19 -6
- package/src/tools/read.ts +211 -146
- package/src/tools/render-mermaid.ts +2 -3
- package/src/tools/render-utils.ts +20 -6
- package/src/tools/renderers.ts +3 -1
- package/src/tools/report-tool-issue.ts +80 -0
- package/src/tools/resolve.ts +70 -39
- package/src/tools/search-tool-bm25.ts +2 -2
- package/src/tools/ssh.ts +2 -2
- package/src/tools/todo-write.ts +2 -2
- package/src/tools/tool-timeouts.ts +1 -0
- package/src/tools/write.ts +5 -6
- package/src/tui/tree-list.ts +3 -1
- package/src/utils/clipboard.ts +80 -0
- package/src/utils/commit-message-generator.ts +2 -3
- package/src/utils/edit-mode.ts +49 -0
- package/src/utils/external-editor.ts +11 -5
- package/src/utils/file-display-mode.ts +6 -5
- package/src/utils/file-mentions.ts +8 -7
- package/src/utils/git.ts +1400 -0
- package/src/utils/image-loading.ts +98 -0
- package/src/utils/title-generator.ts +2 -3
- package/src/utils/tools-manager.ts +6 -6
- package/src/web/scrapers/choosealicense.ts +1 -1
- package/src/web/search/index.ts +3 -3
- package/src/web/search/render.ts +6 -4
- package/src/autoresearch/command-initialize.md +0 -34
- package/src/commit/git/errors.ts +0 -9
- package/src/commit/git/index.ts +0 -210
- package/src/commit/git/operations.ts +0 -54
- package/src/patch/diff.ts +0 -433
- package/src/patch/index.ts +0 -888
- package/src/patch/parser.ts +0 -532
- package/src/patch/types.ts +0 -292
- package/src/prompts/agents/oracle.md +0 -77
- package/src/tools/gh-cli.ts +0 -125
- package/src/tools/pending-action.ts +0 -49
- package/src/utils/child-process.ts +0 -88
- package/src/utils/frontmatter.ts +0 -117
- package/src/utils/image-input.ts +0 -274
- package/src/utils/mime.ts +0 -53
- package/src/utils/prompt-format.ts +0 -170
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export type ExecutionAbortReason = "idle-timeout" | "signal";
|
|
2
|
+
|
|
3
|
+
export interface IdleTimeoutWatchdogOptions {
|
|
4
|
+
timeoutMs?: number;
|
|
5
|
+
signal?: AbortSignal;
|
|
6
|
+
hardTimeoutGraceMs: number;
|
|
7
|
+
onAbort?: (reason: ExecutionAbortReason) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class IdleTimeoutWatchdog {
|
|
11
|
+
#abortController = new AbortController();
|
|
12
|
+
#abortReason?: ExecutionAbortReason;
|
|
13
|
+
#hardTimeoutDeferred = Promise.withResolvers<"hard-timeout">();
|
|
14
|
+
#hardTimeoutGraceMs: number;
|
|
15
|
+
#hardTimeoutTimer?: NodeJS.Timeout;
|
|
16
|
+
#idleTimer?: NodeJS.Timeout;
|
|
17
|
+
#onAbort?: (reason: ExecutionAbortReason) => void;
|
|
18
|
+
#signal?: AbortSignal;
|
|
19
|
+
#signalAbortHandler?: () => void;
|
|
20
|
+
#timeoutMs?: number;
|
|
21
|
+
|
|
22
|
+
constructor(options: IdleTimeoutWatchdogOptions) {
|
|
23
|
+
this.#timeoutMs = options.timeoutMs;
|
|
24
|
+
this.#hardTimeoutGraceMs = options.hardTimeoutGraceMs;
|
|
25
|
+
this.#onAbort = options.onAbort;
|
|
26
|
+
this.#signal = options.signal;
|
|
27
|
+
|
|
28
|
+
if (this.#signal) {
|
|
29
|
+
if (this.#signal.aborted) {
|
|
30
|
+
this.#abort("signal");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.#signalAbortHandler = () => {
|
|
35
|
+
this.#abort("signal");
|
|
36
|
+
};
|
|
37
|
+
this.#signal.addEventListener("abort", this.#signalAbortHandler, { once: true });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.touch();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get abortedBySignal(): boolean {
|
|
44
|
+
return this.#abortReason === "signal";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get hardTimeoutPromise(): Promise<"hard-timeout"> {
|
|
48
|
+
return this.#hardTimeoutDeferred.promise;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get signal(): AbortSignal {
|
|
52
|
+
return this.#abortController.signal;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get timedOut(): boolean {
|
|
56
|
+
return this.#abortReason === "idle-timeout";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
touch(): void {
|
|
60
|
+
if (this.#abortReason || this.#timeoutMs === undefined || this.#timeoutMs <= 0) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.#idleTimer) {
|
|
65
|
+
clearTimeout(this.#idleTimer);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.#idleTimer = setTimeout(() => {
|
|
69
|
+
this.#abort("idle-timeout");
|
|
70
|
+
}, this.#timeoutMs);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
dispose(): void {
|
|
74
|
+
if (this.#idleTimer) {
|
|
75
|
+
clearTimeout(this.#idleTimer);
|
|
76
|
+
this.#idleTimer = undefined;
|
|
77
|
+
}
|
|
78
|
+
if (this.#hardTimeoutTimer) {
|
|
79
|
+
clearTimeout(this.#hardTimeoutTimer);
|
|
80
|
+
this.#hardTimeoutTimer = undefined;
|
|
81
|
+
}
|
|
82
|
+
if (this.#signal && this.#signalAbortHandler) {
|
|
83
|
+
this.#signal.removeEventListener("abort", this.#signalAbortHandler);
|
|
84
|
+
this.#signalAbortHandler = undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#abort(reason: ExecutionAbortReason): void {
|
|
89
|
+
if (this.#abortReason) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.#abortReason = reason;
|
|
94
|
+
|
|
95
|
+
if (this.#idleTimer) {
|
|
96
|
+
clearTimeout(this.#idleTimer);
|
|
97
|
+
this.#idleTimer = undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!this.#abortController.signal.aborted) {
|
|
101
|
+
this.#abortController.abort(reason);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.#onAbort?.(reason);
|
|
105
|
+
this.#armHardTimeout();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#armHardTimeout(): void {
|
|
109
|
+
if (this.#hardTimeoutTimer || this.#hardTimeoutGraceMs <= 0) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.#hardTimeoutTimer = setTimeout(() => {
|
|
114
|
+
this.#hardTimeoutDeferred.resolve("hard-timeout");
|
|
115
|
+
}, this.#hardTimeoutGraceMs);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function formatIdleTimeoutMessage(timeoutMs?: number): string {
|
|
120
|
+
if (timeoutMs === undefined) {
|
|
121
|
+
return "Command timed out without output";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const seconds = Math.max(1, Math.round(timeoutMs / 1000));
|
|
125
|
+
return `Command timed out after ${seconds} seconds without output`;
|
|
126
|
+
}
|
|
@@ -13,6 +13,11 @@ export const NON_INTERACTIVE_ENV: Readonly<Record<string, string>> = {
|
|
|
13
13
|
AWS_PAGER: "",
|
|
14
14
|
HOMEBREW_PAGER: "cat",
|
|
15
15
|
LESS: "FRX",
|
|
16
|
+
// Disable terminal features that can block the process.
|
|
17
|
+
TERM: "dumb",
|
|
18
|
+
GPG_TTY: "not a tty",
|
|
19
|
+
NO_COLOR: "1",
|
|
20
|
+
PYTHONUNBUFFERED: "1",
|
|
16
21
|
// Disable editor and terminal credential prompts.
|
|
17
22
|
GIT_EDITOR: "true",
|
|
18
23
|
VISUAL: "true",
|
|
@@ -1,27 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { prompt } from "@oh-my-pi/pi-utils";
|
|
2
2
|
import type { CustomCommand, CustomCommandAPI } from "../../../../extensibility/custom-commands/types";
|
|
3
3
|
import type { HookCommandContext } from "../../../../extensibility/hooks/types";
|
|
4
4
|
import ciGreenRequestTemplate from "../../../../prompts/ci-green-request.md" with { type: "text" };
|
|
5
|
+
import * as git from "../../../../utils/git";
|
|
5
6
|
|
|
6
7
|
async function getHeadTag(api: CustomCommandAPI): Promise<string | undefined> {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"HEAD",
|
|
11
|
-
"--sort=-version:refname",
|
|
12
|
-
"--format=%(refname:strip=2)",
|
|
13
|
-
"refs/tags",
|
|
14
|
-
]);
|
|
15
|
-
|
|
16
|
-
if (result.code !== 0 || result.killed) {
|
|
8
|
+
try {
|
|
9
|
+
return (await git.ref.tags(api.cwd))[0];
|
|
10
|
+
} catch {
|
|
17
11
|
return undefined;
|
|
18
12
|
}
|
|
19
|
-
|
|
20
|
-
const tag = result.stdout
|
|
21
|
-
.split("\n")
|
|
22
|
-
.map(line => line.trim())
|
|
23
|
-
.find(Boolean);
|
|
24
|
-
return tag || undefined;
|
|
25
13
|
}
|
|
26
14
|
|
|
27
15
|
export class GreenCommand implements CustomCommand {
|
|
@@ -32,6 +20,6 @@ export class GreenCommand implements CustomCommand {
|
|
|
32
20
|
|
|
33
21
|
async execute(_args: string[], _ctx: HookCommandContext): Promise<string> {
|
|
34
22
|
const headTag = await getHeadTag(this.api);
|
|
35
|
-
return
|
|
23
|
+
return prompt.render(ciGreenRequestTemplate, { headTag });
|
|
36
24
|
}
|
|
37
25
|
}
|
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
* rich context for the orchestrating agent to distribute work across
|
|
12
12
|
* multiple reviewer agents based on diff weight and locality.
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
14
|
+
import { prompt } from "@oh-my-pi/pi-utils";
|
|
15
15
|
import type { CustomCommand, CustomCommandAPI } from "../../../../extensibility/custom-commands/types";
|
|
16
16
|
import type { HookCommandContext } from "../../../../extensibility/hooks/types";
|
|
17
17
|
import reviewRequestTemplate from "../../../../prompts/review-request.md" with { type: "text" };
|
|
18
|
+
import * as git from "../../../../utils/git";
|
|
18
19
|
|
|
19
20
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
21
|
// Types
|
|
@@ -208,7 +209,7 @@ function buildReviewPrompt(mode: string, stats: DiffStats, rawDiff: string): str
|
|
|
208
209
|
hunksPreview: skipDiff ? getDiffPreview(f.hunks, linesPerFile) : "",
|
|
209
210
|
}));
|
|
210
211
|
|
|
211
|
-
return
|
|
212
|
+
return prompt.render(reviewRequestTemplate, {
|
|
212
213
|
mode,
|
|
213
214
|
files: filesWithExt,
|
|
214
215
|
excluded: stats.excluded,
|
|
@@ -258,20 +259,20 @@ export class ReviewCommand implements CustomCommand {
|
|
|
258
259
|
if (!baseBranch) return undefined;
|
|
259
260
|
|
|
260
261
|
const currentBranch = await getCurrentBranch(this.api);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
ctx.ui.notify(`Failed to get diff: ${
|
|
262
|
+
let diffText: string;
|
|
263
|
+
try {
|
|
264
|
+
diffText = await git.diff(this.api.cwd, { base: `${baseBranch}...${currentBranch}` });
|
|
265
|
+
} catch (err) {
|
|
266
|
+
ctx.ui.notify(`Failed to get diff: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
266
267
|
return undefined;
|
|
267
268
|
}
|
|
268
269
|
|
|
269
|
-
if (!
|
|
270
|
+
if (!diffText.trim()) {
|
|
270
271
|
ctx.ui.notify(`No changes between ${baseBranch} and ${currentBranch}`, "warning");
|
|
271
272
|
return undefined;
|
|
272
273
|
}
|
|
273
274
|
|
|
274
|
-
const stats = parseDiff(
|
|
275
|
+
const stats = parseDiff(diffText);
|
|
275
276
|
if (stats.files.length === 0) {
|
|
276
277
|
ctx.ui.notify("No reviewable files (all changes filtered out)", "warning");
|
|
277
278
|
return undefined;
|
|
@@ -280,7 +281,7 @@ export class ReviewCommand implements CustomCommand {
|
|
|
280
281
|
return buildReviewPrompt(
|
|
281
282
|
`Reviewing changes between \`${baseBranch}\` and \`${currentBranch}\` (PR-style)`,
|
|
282
283
|
stats,
|
|
283
|
-
|
|
284
|
+
diffText,
|
|
284
285
|
);
|
|
285
286
|
}
|
|
286
287
|
|
|
@@ -292,12 +293,19 @@ export class ReviewCommand implements CustomCommand {
|
|
|
292
293
|
return undefined;
|
|
293
294
|
}
|
|
294
295
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
296
|
+
let unstagedDiff: string;
|
|
297
|
+
let stagedDiff: string;
|
|
298
|
+
try {
|
|
299
|
+
[unstagedDiff, stagedDiff] = await Promise.all([
|
|
300
|
+
git.diff(this.api.cwd),
|
|
301
|
+
git.diff(this.api.cwd, { cached: true }),
|
|
302
|
+
]);
|
|
303
|
+
} catch (err) {
|
|
304
|
+
ctx.ui.notify(`Failed to get diff: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
305
|
+
return undefined;
|
|
306
|
+
}
|
|
299
307
|
|
|
300
|
-
const combinedDiff = [
|
|
308
|
+
const combinedDiff = [unstagedDiff, stagedDiff].filter(Boolean).join("\n");
|
|
301
309
|
|
|
302
310
|
if (!combinedDiff.trim()) {
|
|
303
311
|
ctx.ui.notify("No diff content found", "warning");
|
|
@@ -327,25 +335,26 @@ export class ReviewCommand implements CustomCommand {
|
|
|
327
335
|
// Extract commit hash from selection (format: "abc1234 message")
|
|
328
336
|
const hash = selected.split(" ")[0];
|
|
329
337
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
338
|
+
let diffText: string;
|
|
339
|
+
try {
|
|
340
|
+
diffText = await git.show(this.api.cwd, hash, { format: "" });
|
|
341
|
+
} catch (err) {
|
|
342
|
+
ctx.ui.notify(`Failed to get commit: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
334
343
|
return undefined;
|
|
335
344
|
}
|
|
336
345
|
|
|
337
|
-
if (!
|
|
346
|
+
if (!diffText.trim()) {
|
|
338
347
|
ctx.ui.notify("Commit has no diff content", "warning");
|
|
339
348
|
return undefined;
|
|
340
349
|
}
|
|
341
350
|
|
|
342
|
-
const stats = parseDiff(
|
|
351
|
+
const stats = parseDiff(diffText);
|
|
343
352
|
if (stats.files.length === 0) {
|
|
344
353
|
ctx.ui.notify("No reviewable files in commit (all changes filtered out)", "warning");
|
|
345
354
|
return undefined;
|
|
346
355
|
}
|
|
347
356
|
|
|
348
|
-
return buildReviewPrompt(`Reviewing commit \`${hash}\``, stats,
|
|
357
|
+
return buildReviewPrompt(`Reviewing commit \`${hash}\``, stats, diffText);
|
|
349
358
|
}
|
|
350
359
|
|
|
351
360
|
case 4: {
|
|
@@ -354,16 +363,21 @@ export class ReviewCommand implements CustomCommand {
|
|
|
354
363
|
if (!instructions?.trim()) return undefined;
|
|
355
364
|
|
|
356
365
|
// For custom, we still try to get current diff for context
|
|
357
|
-
|
|
358
|
-
|
|
366
|
+
let diffText: string | undefined;
|
|
367
|
+
try {
|
|
368
|
+
diffText = await git.diff(this.api.cwd, { base: "HEAD" });
|
|
369
|
+
} catch {
|
|
370
|
+
diffText = undefined;
|
|
371
|
+
}
|
|
372
|
+
const reviewDiff = diffText?.trim();
|
|
359
373
|
|
|
360
|
-
if (
|
|
361
|
-
const stats = parseDiff(
|
|
374
|
+
if (reviewDiff) {
|
|
375
|
+
const stats = parseDiff(reviewDiff);
|
|
362
376
|
// Even if all files filtered, include the custom instructions
|
|
363
377
|
return `${buildReviewPrompt(
|
|
364
378
|
`Custom review: ${instructions.split("\n")[0].slice(0, 60)}…`,
|
|
365
379
|
stats,
|
|
366
|
-
|
|
380
|
+
reviewDiff,
|
|
367
381
|
)}\n\n### Additional Instructions\n\n${instructions}`;
|
|
368
382
|
}
|
|
369
383
|
|
|
@@ -388,12 +402,7 @@ Use the Task tool with \`agent: "reviewer"\` to execute this review.`;
|
|
|
388
402
|
|
|
389
403
|
async function getGitBranches(api: CustomCommandAPI): Promise<string[]> {
|
|
390
404
|
try {
|
|
391
|
-
|
|
392
|
-
if (result.code !== 0) return [];
|
|
393
|
-
return result.stdout
|
|
394
|
-
.split("\n")
|
|
395
|
-
.map(b => b.trim())
|
|
396
|
-
.filter(Boolean);
|
|
405
|
+
return await git.branch.list(api.cwd, { all: true });
|
|
397
406
|
} catch {
|
|
398
407
|
return [];
|
|
399
408
|
}
|
|
@@ -401,8 +410,7 @@ async function getGitBranches(api: CustomCommandAPI): Promise<string[]> {
|
|
|
401
410
|
|
|
402
411
|
async function getCurrentBranch(api: CustomCommandAPI): Promise<string> {
|
|
403
412
|
try {
|
|
404
|
-
|
|
405
|
-
return result.stdout.trim() || "HEAD";
|
|
413
|
+
return (await git.branch.current(api.cwd)) ?? "HEAD";
|
|
406
414
|
} catch {
|
|
407
415
|
return "HEAD";
|
|
408
416
|
}
|
|
@@ -410,8 +418,7 @@ async function getCurrentBranch(api: CustomCommandAPI): Promise<string> {
|
|
|
410
418
|
|
|
411
419
|
async function getGitStatus(api: CustomCommandAPI): Promise<string> {
|
|
412
420
|
try {
|
|
413
|
-
|
|
414
|
-
return result.stdout;
|
|
421
|
+
return await git.status(api.cwd);
|
|
415
422
|
} catch {
|
|
416
423
|
return "";
|
|
417
424
|
}
|
|
@@ -419,12 +426,7 @@ async function getGitStatus(api: CustomCommandAPI): Promise<string> {
|
|
|
419
426
|
|
|
420
427
|
async function getRecentCommits(api: CustomCommandAPI, count: number): Promise<string[]> {
|
|
421
428
|
try {
|
|
422
|
-
|
|
423
|
-
if (result.code !== 0) return [];
|
|
424
|
-
return result.stdout
|
|
425
|
-
.split("\n")
|
|
426
|
-
.map(c => c.trim())
|
|
427
|
-
.filter(Boolean);
|
|
429
|
+
return await git.log.onelines(api.cwd, count);
|
|
428
430
|
} catch {
|
|
429
431
|
return [];
|
|
430
432
|
}
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
|
|
10
9
|
import { getAgentDir, getProjectDir, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
11
10
|
import * as typebox from "@sinclair/typebox";
|
|
12
11
|
import { getConfigDirs } from "../../config";
|
|
@@ -184,7 +183,7 @@ export async function loadCustomCommands(options: LoadCustomCommandsOptions = {}
|
|
|
184
183
|
exec: (command: string, args: string[], execOptions) =>
|
|
185
184
|
execCommand(command, args, execOptions?.cwd ?? cwd, execOptions),
|
|
186
185
|
typebox,
|
|
187
|
-
pi:
|
|
186
|
+
pi: await import("@oh-my-pi/pi-coding-agent"),
|
|
188
187
|
};
|
|
189
188
|
|
|
190
189
|
// 1. Load bundled commands first (lowest priority - can be overridden)
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* to avoid import resolution issues with custom tools loaded from user directories.
|
|
6
6
|
*/
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
-
import
|
|
8
|
+
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
9
9
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
10
10
|
import * as typebox from "@sinclair/typebox";
|
|
11
11
|
import { toolCapability } from "../../capability/tool";
|
|
@@ -14,7 +14,6 @@ import type { ExecOptions } from "../../exec/exec";
|
|
|
14
14
|
import { execCommand } from "../../exec/exec";
|
|
15
15
|
import type { HookUIContext } from "../../extensibility/hooks/types";
|
|
16
16
|
import { getAllPluginToolPaths } from "../../extensibility/plugins/loader";
|
|
17
|
-
import type { PendingActionStore } from "../../tools/pending-action";
|
|
18
17
|
import { createNoOpUIContext, resolvePath } from "../utils";
|
|
19
18
|
import type { CustomToolAPI, CustomToolFactory, LoadedCustomTool, ToolLoadError } from "./types";
|
|
20
19
|
|
|
@@ -85,7 +84,17 @@ export class CustomToolLoader {
|
|
|
85
84
|
#sharedApi: CustomToolAPI;
|
|
86
85
|
#seenNames: Set<string>;
|
|
87
86
|
|
|
88
|
-
constructor(
|
|
87
|
+
constructor(
|
|
88
|
+
pi: typeof import("@oh-my-pi/pi-coding-agent"),
|
|
89
|
+
cwd: string,
|
|
90
|
+
builtInToolNames: string[],
|
|
91
|
+
pushPendingAction?: (action: {
|
|
92
|
+
label: string;
|
|
93
|
+
sourceToolName: string;
|
|
94
|
+
apply(reason: string): Promise<AgentToolResult<unknown>>;
|
|
95
|
+
reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
|
|
96
|
+
}) => void,
|
|
97
|
+
) {
|
|
89
98
|
this.#sharedApi = {
|
|
90
99
|
cwd,
|
|
91
100
|
exec: (command: string, args: string[], options?: ExecOptions) =>
|
|
@@ -94,17 +103,16 @@ export class CustomToolLoader {
|
|
|
94
103
|
hasUI: false,
|
|
95
104
|
logger,
|
|
96
105
|
typebox,
|
|
97
|
-
pi
|
|
106
|
+
pi,
|
|
98
107
|
pushPendingAction: action => {
|
|
99
|
-
if (!
|
|
108
|
+
if (!pushPendingAction) {
|
|
100
109
|
throw new Error("Pending action store unavailable for custom tools in this runtime.");
|
|
101
110
|
}
|
|
102
|
-
|
|
111
|
+
pushPendingAction({
|
|
103
112
|
label: action.label,
|
|
104
113
|
sourceToolName: action.sourceToolName ?? "custom_tool",
|
|
105
114
|
apply: action.apply,
|
|
106
115
|
reject: action.reject,
|
|
107
|
-
details: action.details,
|
|
108
116
|
});
|
|
109
117
|
},
|
|
110
118
|
};
|
|
@@ -155,9 +163,19 @@ export async function loadCustomTools(
|
|
|
155
163
|
pathsWithSources: ToolPathWithSource[],
|
|
156
164
|
cwd: string,
|
|
157
165
|
builtInToolNames: string[],
|
|
158
|
-
|
|
166
|
+
pushPendingAction?: (action: {
|
|
167
|
+
label: string;
|
|
168
|
+
sourceToolName: string;
|
|
169
|
+
apply(reason: string): Promise<AgentToolResult<unknown>>;
|
|
170
|
+
reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
|
|
171
|
+
}) => void,
|
|
159
172
|
) {
|
|
160
|
-
const loader = new CustomToolLoader(
|
|
173
|
+
const loader = new CustomToolLoader(
|
|
174
|
+
await import("@oh-my-pi/pi-coding-agent"),
|
|
175
|
+
cwd,
|
|
176
|
+
builtInToolNames,
|
|
177
|
+
pushPendingAction,
|
|
178
|
+
);
|
|
161
179
|
await loader.load(pathsWithSources);
|
|
162
180
|
return {
|
|
163
181
|
tools: loader.tools,
|
|
@@ -182,7 +200,12 @@ export async function discoverAndLoadCustomTools(
|
|
|
182
200
|
configuredPaths: string[],
|
|
183
201
|
cwd: string,
|
|
184
202
|
builtInToolNames: string[],
|
|
185
|
-
|
|
203
|
+
pushPendingAction?: (action: {
|
|
204
|
+
label: string;
|
|
205
|
+
sourceToolName: string;
|
|
206
|
+
apply(reason: string): Promise<AgentToolResult<unknown>>;
|
|
207
|
+
reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
|
|
208
|
+
}) => void,
|
|
186
209
|
) {
|
|
187
210
|
const allPathsWithSources: ToolPathWithSource[] = [];
|
|
188
211
|
const seen = new Set<string>();
|
|
@@ -216,5 +239,5 @@ export async function discoverAndLoadCustomTools(
|
|
|
216
239
|
addPath(resolvePath(configPath, cwd), { provider: "config", providerName: "Config", level: "project" });
|
|
217
240
|
}
|
|
218
241
|
|
|
219
|
-
return loadCustomTools(allPathsWithSources, cwd, builtInToolNames,
|
|
242
|
+
return loadCustomTools(allPathsWithSources, cwd, builtInToolNames, pushPendingAction);
|
|
220
243
|
}
|
|
@@ -6,7 +6,6 @@ import * as fs from "node:fs/promises";
|
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
8
|
import type { ImageContent, Model, TextContent } from "@oh-my-pi/pi-ai";
|
|
9
|
-
import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
|
|
10
9
|
import type { KeyId } from "@oh-my-pi/pi-tui";
|
|
11
10
|
import { hasFsCode, isEacces, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
12
11
|
import type { TSchema } from "@sinclair/typebox";
|
|
@@ -102,7 +101,6 @@ export class ExtensionRuntime implements IExtensionRuntime {
|
|
|
102
101
|
class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
|
|
103
102
|
readonly logger = logger;
|
|
104
103
|
readonly typebox = TypeBox;
|
|
105
|
-
readonly pi = piCodingAgent;
|
|
106
104
|
readonly flagValues = new Map<string, boolean | string>();
|
|
107
105
|
readonly pendingProviderRegistrations: Array<{
|
|
108
106
|
name: string;
|
|
@@ -111,6 +109,7 @@ class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
|
|
|
111
109
|
}> = [];
|
|
112
110
|
|
|
113
111
|
constructor(
|
|
112
|
+
public readonly pi: typeof import("@oh-my-pi/pi-coding-agent"),
|
|
114
113
|
private readonly extension: Extension,
|
|
115
114
|
private readonly runtime: IExtensionRuntime,
|
|
116
115
|
private readonly cwd: string,
|
|
@@ -265,7 +264,13 @@ async function loadExtension(
|
|
|
265
264
|
}
|
|
266
265
|
|
|
267
266
|
const extension = createExtension(extensionPath, resolvedPath);
|
|
268
|
-
const api = new ConcreteExtensionAPI(
|
|
267
|
+
const api = new ConcreteExtensionAPI(
|
|
268
|
+
await import("@oh-my-pi/pi-coding-agent"),
|
|
269
|
+
extension,
|
|
270
|
+
runtime,
|
|
271
|
+
cwd,
|
|
272
|
+
eventBus,
|
|
273
|
+
);
|
|
269
274
|
await factory(api);
|
|
270
275
|
|
|
271
276
|
return { extension, error: null };
|
|
@@ -286,7 +291,7 @@ export async function loadExtensionFromFactory(
|
|
|
286
291
|
name = "<inline>",
|
|
287
292
|
): Promise<Extension> {
|
|
288
293
|
const extension = createExtension(name, name);
|
|
289
|
-
const api = new ConcreteExtensionAPI(extension, runtime, cwd, eventBus);
|
|
294
|
+
const api = new ConcreteExtensionAPI(await import("@oh-my-pi/pi-coding-agent"), extension, runtime, cwd, eventBus);
|
|
290
295
|
await factory(api);
|
|
291
296
|
return extension;
|
|
292
297
|
}
|
|
@@ -262,6 +262,10 @@ export class ExtensionRunner {
|
|
|
262
262
|
return allFlags;
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
+
getFlagValues(): Map<string, boolean | string> {
|
|
266
|
+
return new Map(this.runtime.flagValues);
|
|
267
|
+
}
|
|
268
|
+
|
|
265
269
|
setFlagValue(name: string, value: boolean | string): void {
|
|
266
270
|
this.runtime.flagValues.set(name, value);
|
|
267
271
|
}
|
|
@@ -695,7 +699,26 @@ export class ExtensionRunner {
|
|
|
695
699
|
|
|
696
700
|
async emitContext(messages: AgentMessage[]): Promise<AgentMessage[]> {
|
|
697
701
|
const ctx = this.createContext();
|
|
698
|
-
|
|
702
|
+
|
|
703
|
+
// Check if any extensions actually have context handlers before cloning
|
|
704
|
+
let hasContextHandlers = false;
|
|
705
|
+
for (const ext of this.extensions) {
|
|
706
|
+
if (ext.handlers.get("context")?.length) {
|
|
707
|
+
hasContextHandlers = true;
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (!hasContextHandlers) return messages;
|
|
712
|
+
|
|
713
|
+
let currentMessages: AgentMessage[];
|
|
714
|
+
try {
|
|
715
|
+
currentMessages = structuredClone(messages);
|
|
716
|
+
} catch {
|
|
717
|
+
// Messages may contain non-cloneable objects (e.g. in ToolResultMessage.details
|
|
718
|
+
// or ProviderPayload). Fall back to a shallow array clone — extensions should
|
|
719
|
+
// return new message arrays rather than mutating in place.
|
|
720
|
+
currentMessages = [...messages];
|
|
721
|
+
}
|
|
699
722
|
|
|
700
723
|
for (const ext of this.extensions) {
|
|
701
724
|
const handlers = ext.handlers.get("context");
|
|
@@ -28,11 +28,11 @@ import type { Static, TSchema } from "@sinclair/typebox";
|
|
|
28
28
|
import type { Rule } from "../../capability/rule";
|
|
29
29
|
import type { KeybindingsManager } from "../../config/keybindings";
|
|
30
30
|
import type { ModelRegistry } from "../../config/model-registry";
|
|
31
|
+
import type { EditToolDetails } from "../../edit";
|
|
31
32
|
import type { BashResult } from "../../exec/bash-executor";
|
|
32
33
|
import type { ExecOptions, ExecResult } from "../../exec/exec";
|
|
33
34
|
import type { PythonResult } from "../../ipy/executor";
|
|
34
35
|
import type { Theme } from "../../modes/theme/theme";
|
|
35
|
-
import type { EditToolDetails } from "../../patch";
|
|
36
36
|
import type { CompactionPreparation, CompactionResult } from "../../session/compaction";
|
|
37
37
|
import type { CustomMessage } from "../../session/messages";
|
|
38
38
|
import type {
|
|
@@ -81,6 +81,8 @@ export interface ExtensionUIDialogOptions {
|
|
|
81
81
|
onLeft?: () => void;
|
|
82
82
|
/** Invoked when user presses right arrow in select dialogs */
|
|
83
83
|
onRight?: () => void;
|
|
84
|
+
/** Invoked when user presses the external editor shortcut in select dialogs */
|
|
85
|
+
onExternalEditor?: () => void;
|
|
84
86
|
/** Optional footer hint text rendered by interactive selector */
|
|
85
87
|
helpText?: string;
|
|
86
88
|
}
|
|
@@ -566,7 +568,7 @@ export interface ToolExecutionEndEvent {
|
|
|
566
568
|
/** Fired when auto-compaction starts */
|
|
567
569
|
export interface AutoCompactionStartEvent {
|
|
568
570
|
type: "auto_compaction_start";
|
|
569
|
-
reason: "threshold" | "overflow";
|
|
571
|
+
reason: "threshold" | "overflow" | "idle";
|
|
570
572
|
action: "context-full" | "handoff";
|
|
571
573
|
}
|
|
572
574
|
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* Hook loader - loads TypeScript hook modules using native Bun import.
|
|
3
3
|
*/
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
|
|
6
5
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
7
6
|
import * as typebox from "@sinclair/typebox";
|
|
8
7
|
import { hookCapability } from "../../capability/hook";
|
|
@@ -87,16 +86,16 @@ export interface LoadHooksResult {
|
|
|
87
86
|
* Create a HookAPI instance that collects handlers, renderers, and commands.
|
|
88
87
|
* Returns the API, maps, and functions to set handlers later.
|
|
89
88
|
*/
|
|
90
|
-
function createHookAPI(
|
|
89
|
+
async function createHookAPI(
|
|
91
90
|
handlers: Map<string, HandlerFn[]>,
|
|
92
91
|
cwd: string,
|
|
93
|
-
): {
|
|
92
|
+
): Promise<{
|
|
94
93
|
api: HookAPI;
|
|
95
94
|
messageRenderers: Map<string, HookMessageRenderer>;
|
|
96
95
|
commands: Map<string, RegisteredCommand>;
|
|
97
96
|
setSendMessageHandler: (handler: SendMessageHandler) => void;
|
|
98
97
|
setAppendEntryHandler: (handler: AppendEntryHandler) => void;
|
|
99
|
-
} {
|
|
98
|
+
}> {
|
|
100
99
|
let sendMessageHandler: SendMessageHandler | null = null;
|
|
101
100
|
let appendEntryHandler: AppendEntryHandler | null = null;
|
|
102
101
|
const messageRenderers = new Map<string, HookMessageRenderer>();
|
|
@@ -137,7 +136,7 @@ function createHookAPI(
|
|
|
137
136
|
},
|
|
138
137
|
logger,
|
|
139
138
|
typebox,
|
|
140
|
-
pi:
|
|
139
|
+
pi: await import("@oh-my-pi/pi-coding-agent"),
|
|
141
140
|
} as HookAPI;
|
|
142
141
|
|
|
143
142
|
return {
|
|
@@ -170,7 +169,7 @@ async function loadHook(hookPath: string, cwd: string): Promise<{ hook: LoadedHo
|
|
|
170
169
|
|
|
171
170
|
// Create handlers map and API
|
|
172
171
|
const handlers = new Map<string, HandlerFn[]>();
|
|
173
|
-
const { api, messageRenderers, commands, setSendMessageHandler, setAppendEntryHandler } = createHookAPI(
|
|
172
|
+
const { api, messageRenderers, commands, setSendMessageHandler, setAppendEntryHandler } = await createHookAPI(
|
|
174
173
|
handlers,
|
|
175
174
|
cwd,
|
|
176
175
|
);
|