jeopi-cli 16.4.2 → 16.4.4
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 +74 -0
- package/dist/cli.js +3033 -2991
- package/dist/types/advisor/advise-tool.d.ts +5 -1
- package/dist/types/advisor/config.d.ts +4 -3
- package/dist/types/advisor/runtime.d.ts +15 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/update-cli.d.ts +4 -1
- package/dist/types/cli/usage-error.d.ts +4 -0
- package/dist/types/commit/agentic/agent.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/model-resolver.d.ts +7 -1
- package/dist/types/config/models-config.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +3 -13
- package/dist/types/eval/py/tool-bridge.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +3 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/internal-urls/memory-protocol.d.ts +6 -6
- package/dist/types/internal-urls/types.d.ts +7 -0
- package/dist/types/irc/bus.d.ts +4 -0
- package/dist/types/lsp/deferred-diagnostics.d.ts +11 -0
- package/dist/types/modes/components/move-overlay.d.ts +1 -1
- package/dist/types/modes/components/plan-review-overlay.d.ts +2 -0
- package/dist/types/modes/components/welcome.d.ts +4 -0
- package/dist/types/modes/magic-keyword-boundary.d.ts +9 -0
- package/dist/types/modes/orchestrate.d.ts +1 -1
- package/dist/types/modes/rpc/host-tools.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +27 -7
- package/dist/types/modes/ultrathink.d.ts +1 -1
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +9 -0
- package/dist/types/modes/workflow.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +6 -0
- package/dist/types/session/auth-broker-config.d.ts +2 -2
- package/dist/types/tools/bash-skill-urls.d.ts +3 -2
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +3 -1
- package/dist/types/tools/browser/cmux/rpc.d.ts +20 -0
- package/dist/types/tools/browser/launch.d.ts +1 -0
- package/dist/types/tools/browser/run-cancellation.d.ts +17 -2
- package/dist/types/tools/browser/run-output.d.ts +25 -0
- package/dist/types/tools/browser/tab-worker.d.ts +22 -0
- package/dist/types/utils/changelog.d.ts +35 -1
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +13 -0
- package/src/advisor/__tests__/config.test.ts +36 -0
- package/src/advisor/advise-tool.ts +6 -1
- package/src/advisor/config.ts +11 -8
- package/src/advisor/runtime.ts +127 -0
- package/src/autolearn/controller.ts +15 -3
- package/src/cli/args.ts +12 -0
- package/src/cli/flag-tables.ts +21 -7
- package/src/cli/update-cli.ts +63 -13
- package/src/cli/usage-error.ts +7 -0
- package/src/commands/acp.ts +11 -2
- package/src/commands/launch.ts +12 -3
- package/src/commit/agentic/agent.ts +4 -0
- package/src/commit/agentic/index.ts +46 -21
- package/src/config/config-file.ts +30 -12
- package/src/config/model-registry.ts +7 -7
- package/src/config/model-resolver.ts +24 -6
- package/src/config/models-config.ts +1 -1
- package/src/config/settings-schema.ts +3 -15
- package/src/edit/index.ts +10 -76
- package/src/edit/renderer.ts +13 -10
- package/src/eval/__tests__/agent-bridge.test.ts +19 -14
- package/src/eval/__tests__/bridge-timeout.test.ts +60 -0
- package/src/eval/executor-base.ts +84 -7
- package/src/eval/js/shared/rewrite-imports.ts +31 -13
- package/src/eval/py/tool-bridge.ts +13 -14
- package/src/exec/non-interactive-env.ts +0 -1
- package/src/export/ttsr.ts +0 -3
- package/src/extensibility/custom-tools/loader.ts +70 -37
- package/src/extensibility/custom-tools/types.ts +3 -0
- package/src/extensibility/extensions/runner.ts +3 -0
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +9 -1
- package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +30 -3
- package/src/extensibility/plugins/manager.ts +21 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/internal-urls/memory-protocol.ts +13 -8
- package/src/internal-urls/skill-protocol.ts +1 -1
- package/src/internal-urls/types.ts +7 -0
- package/src/irc/bus.ts +48 -18
- package/src/lsp/client.ts +7 -1
- package/src/lsp/deferred-diagnostics.ts +66 -0
- package/src/main.ts +13 -13
- package/src/mcp/tool-bridge.ts +57 -6
- package/src/mcp/transports/stdio.test.ts +45 -1
- package/src/mcp/transports/stdio.ts +6 -3
- package/src/modes/acp/acp-agent.ts +73 -2
- package/src/modes/acp/acp-event-mapper.ts +5 -0
- package/src/modes/acp/acp-mode.ts +11 -0
- package/src/modes/components/__tests__/move-overlay.test.ts +16 -1
- package/src/modes/components/advisor-config.ts +15 -7
- package/src/modes/components/chat-transcript-builder.ts +31 -11
- package/src/modes/components/move-overlay.ts +2 -3
- package/src/modes/components/plan-review-overlay.ts +7 -0
- package/src/modes/components/tips.txt +1 -1
- package/src/modes/components/welcome.ts +13 -14
- package/src/modes/controllers/command-controller.ts +8 -10
- package/src/modes/controllers/event-controller.ts +58 -42
- package/src/modes/controllers/input-controller.ts +9 -6
- package/src/modes/controllers/selector-controller.ts +3 -2
- package/src/modes/controllers/tan-command-controller.ts +40 -1
- package/src/modes/interactive-mode.ts +13 -0
- package/src/modes/magic-keyword-boundary.ts +23 -0
- package/src/modes/orchestrate.ts +6 -5
- package/src/modes/print-mode.ts +9 -0
- package/src/modes/rpc/host-tools.ts +13 -0
- package/src/modes/rpc/rpc-mode.ts +130 -56
- package/src/modes/ultrathink.ts +6 -5
- package/src/modes/utils/transcript-render-helpers.ts +44 -0
- package/src/modes/utils/ui-helpers.ts +16 -1
- package/src/modes/workflow.ts +5 -4
- package/src/prompts/system/eager-task.md +2 -2
- package/src/prompts/system/tan-context-switch.md +17 -0
- package/src/prompts/tools/browser.md +5 -5
- package/src/sdk.ts +3 -0
- package/src/session/agent-session.ts +267 -36
- package/src/session/auth-broker-config.ts +4 -4
- package/src/session/snapcompact-inline.ts +3 -19
- package/src/session/streaming-output.ts +40 -12
- package/src/slash-commands/builtin-registry.ts +8 -8
- package/src/task/executor.ts +8 -0
- package/src/task/render.ts +51 -44
- package/src/tools/bash-skill-urls.ts +7 -4
- package/src/tools/bash.ts +30 -33
- package/src/tools/browser/cmux/cmux-tab.ts +49 -50
- package/src/tools/browser/cmux/rpc.ts +50 -0
- package/src/tools/browser/launch.ts +8 -4
- package/src/tools/browser/run-cancellation.ts +44 -5
- package/src/tools/browser/run-output.ts +76 -0
- package/src/tools/browser/tab-worker.ts +280 -133
- package/src/tools/eval-render.ts +16 -14
- package/src/tools/glob.ts +20 -6
- package/src/tools/grep.ts +1 -0
- package/src/tools/image-gen.ts +3 -4
- package/src/tools/irc.ts +27 -13
- package/src/tools/path-utils.ts +6 -1
- package/src/tools/read.ts +14 -7
- package/src/tools/write.ts +22 -5
- package/src/tools/yield.ts +29 -1
- package/src/utils/changelog.ts +106 -9
- package/src/utils/git.ts +0 -1
- package/src/utils/title-generator.ts +72 -7
- package/src/web/search/index.ts +14 -6
- package/src/web/search/providers/perplexity.ts +18 -2
- package/dist/types/tools/bash-command-fixup.d.ts +0 -11
- package/src/tools/bash-command-fixup.ts +0 -37
package/src/cli/update-cli.ts
CHANGED
|
@@ -112,6 +112,19 @@ async function getBunGlobalBinDir(): Promise<string | undefined> {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
async function getNpmGlobalBinDir(): Promise<string | undefined> {
|
|
116
|
+
if (!$which("npm")) return undefined;
|
|
117
|
+
try {
|
|
118
|
+
const result = await $`npm prefix -g`.quiet().nothrow();
|
|
119
|
+
if (result.exitCode !== 0) return undefined;
|
|
120
|
+
const prefix = result.text().trim();
|
|
121
|
+
if (prefix.length === 0) return undefined;
|
|
122
|
+
return process.platform === "win32" ? prefix : path.join(prefix, "bin");
|
|
123
|
+
} catch {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
115
128
|
async function getHomebrewFormulaPrefix(): Promise<string | undefined> {
|
|
116
129
|
if (!$which("brew")) return undefined;
|
|
117
130
|
for (const formula of [HOMEBREW_FORMULA, APP_NAME]) {
|
|
@@ -192,26 +205,36 @@ function isPathInDirectory(filePath: string, directoryPath: string): boolean {
|
|
|
192
205
|
return isPathInDirectoryLexical(resolvedFile, dirReal);
|
|
193
206
|
}
|
|
194
207
|
|
|
195
|
-
type UpdateMethod = "brew" | "mise" | "bun" | "binary";
|
|
208
|
+
type UpdateMethod = "brew" | "mise" | "bun" | "npm" | "binary";
|
|
196
209
|
|
|
197
210
|
interface UpdateMethodResolutionOptions {
|
|
198
211
|
homebrewPrefix?: string;
|
|
199
212
|
miseBinDirs?: readonly string[];
|
|
200
213
|
miseDataDir?: string;
|
|
214
|
+
npmBinDir?: string;
|
|
201
215
|
}
|
|
202
216
|
|
|
203
|
-
type UpdateTarget =
|
|
217
|
+
type UpdateTarget =
|
|
218
|
+
| { method: "brew" }
|
|
219
|
+
| { method: "mise" }
|
|
220
|
+
| { method: "bun" }
|
|
221
|
+
| { method: "npm" }
|
|
222
|
+
| { method: "binary"; path: string };
|
|
204
223
|
|
|
205
224
|
function resolveUpdateMethod(
|
|
206
225
|
jeopiPath: string,
|
|
207
226
|
bunBinDir: string | undefined,
|
|
208
227
|
options: UpdateMethodResolutionOptions = {},
|
|
209
228
|
): UpdateMethod {
|
|
210
|
-
const { homebrewPrefix, miseBinDirs = [], miseDataDir } = options;
|
|
229
|
+
const { homebrewPrefix, miseBinDirs = [], miseDataDir, npmBinDir } = options;
|
|
230
|
+
const launcherExtension = path.extname(jeopiPath).toLowerCase();
|
|
231
|
+
const isWindowsScriptLauncher =
|
|
232
|
+
launcherExtension === ".cmd" || launcherExtension === ".ps1" || launcherExtension === ".bat";
|
|
211
233
|
if (homebrewPrefix && isPathInDirectory(jeopiPath, path.join(homebrewPrefix, "bin"))) return "brew";
|
|
212
234
|
if (miseBinDirs.some(dir => isPathInDirectory(jeopiPath, dir))) return "mise";
|
|
213
235
|
if (miseDataDir && isPathInDirectory(jeopiPath, path.join(miseDataDir, "shims"))) return "mise";
|
|
214
236
|
if (bunBinDir && isPathInDirectory(jeopiPath, bunBinDir)) return "bun";
|
|
237
|
+
if ((npmBinDir && isPathInDirectory(jeopiPath, npmBinDir)) || isWindowsScriptLauncher) return "npm";
|
|
215
238
|
return "binary";
|
|
216
239
|
}
|
|
217
240
|
|
|
@@ -224,6 +247,7 @@ export function resolveUpdateMethodForTest(
|
|
|
224
247
|
}
|
|
225
248
|
async function resolveUpdateTarget(): Promise<UpdateTarget> {
|
|
226
249
|
const bunBinDir = await getBunGlobalBinDir();
|
|
250
|
+
const npmBinDir = await getNpmGlobalBinDir();
|
|
227
251
|
const homebrewPrefix = await getHomebrewFormulaPrefix();
|
|
228
252
|
const miseAvailable = $which("mise") !== undefined;
|
|
229
253
|
const miseBinDirs = miseAvailable ? await getMiseBinDirs() : [];
|
|
@@ -231,7 +255,7 @@ async function resolveUpdateTarget(): Promise<UpdateTarget> {
|
|
|
231
255
|
const jeopiPath = resolveJeopiPath();
|
|
232
256
|
|
|
233
257
|
if (jeopiPath) {
|
|
234
|
-
const method = resolveUpdateMethod(jeopiPath, bunBinDir, { homebrewPrefix, miseBinDirs, miseDataDir });
|
|
258
|
+
const method = resolveUpdateMethod(jeopiPath, bunBinDir, { homebrewPrefix, miseBinDirs, miseDataDir, npmBinDir });
|
|
235
259
|
if (method === "binary") return { method, path: jeopiPath };
|
|
236
260
|
return { method };
|
|
237
261
|
}
|
|
@@ -874,6 +898,15 @@ export async function replaceBinaryForUpdate(options: BinaryReplacementOptions):
|
|
|
874
898
|
}
|
|
875
899
|
}
|
|
876
900
|
|
|
901
|
+
function buildVersionedPackageInstallArgs(expectedVersion: string, nativeTag: string): string[] {
|
|
902
|
+
const args = [`${NPM_PACKAGE}@${expectedVersion}`, `${NATIVES_PACKAGE}@${expectedVersion}`];
|
|
903
|
+
const leafPackage = NATIVE_LEAF_PACKAGES.get(nativeTag);
|
|
904
|
+
if (leafPackage) {
|
|
905
|
+
args.push(`${leafPackage}@${expectedVersion}`);
|
|
906
|
+
}
|
|
907
|
+
return args;
|
|
908
|
+
}
|
|
909
|
+
|
|
877
910
|
/**
|
|
878
911
|
* Build the bun argv used to globally install a specific jeopi version.
|
|
879
912
|
*
|
|
@@ -905,19 +938,23 @@ export async function replaceBinaryForUpdate(options: BinaryReplacementOptions):
|
|
|
905
938
|
* See #1824.
|
|
906
939
|
*/
|
|
907
940
|
export function buildBunInstallArgs(expectedVersion: string, nativeTag: string = currentNativeTag()): string[] {
|
|
908
|
-
|
|
941
|
+
return [
|
|
909
942
|
"install",
|
|
910
943
|
"-g",
|
|
911
944
|
"--no-cache",
|
|
912
945
|
`--registry=${NPM_REGISTRY}`,
|
|
913
|
-
|
|
914
|
-
|
|
946
|
+
...buildVersionedPackageInstallArgs(expectedVersion, nativeTag),
|
|
947
|
+
];
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/** Build the npm argv used to update npm-managed global installs. */
|
|
951
|
+
export function buildNpmInstallArgs(expectedVersion: string, nativeTag: string = currentNativeTag()): string[] {
|
|
952
|
+
return [
|
|
953
|
+
"install",
|
|
954
|
+
"-g",
|
|
955
|
+
`--registry=${NPM_REGISTRY}`,
|
|
956
|
+
...buildVersionedPackageInstallArgs(expectedVersion, nativeTag),
|
|
915
957
|
];
|
|
916
|
-
const leafPackage = NATIVE_LEAF_PACKAGES.get(nativeTag);
|
|
917
|
-
if (leafPackage) {
|
|
918
|
-
args.push(`${leafPackage}@${expectedVersion}`);
|
|
919
|
-
}
|
|
920
|
-
return args;
|
|
921
958
|
}
|
|
922
959
|
|
|
923
960
|
export function buildHomebrewUpdateArgs(force: boolean): string[] {
|
|
@@ -933,7 +970,7 @@ export function buildMiseForceInstallArgs(expectedVersion: string): string[] {
|
|
|
933
970
|
}
|
|
934
971
|
|
|
935
972
|
/**
|
|
936
|
-
* Update via
|
|
973
|
+
* Update via package manager.
|
|
937
974
|
*/
|
|
938
975
|
async function updateViaBun(expectedVersion: string): Promise<void> {
|
|
939
976
|
console.log(chalk.dim("Updating via bun..."));
|
|
@@ -954,6 +991,17 @@ async function updateViaBun(expectedVersion: string): Promise<void> {
|
|
|
954
991
|
}
|
|
955
992
|
}
|
|
956
993
|
|
|
994
|
+
async function updateViaNpm(expectedVersion: string): Promise<void> {
|
|
995
|
+
console.log(chalk.dim("Updating via npm..."));
|
|
996
|
+
const args = buildNpmInstallArgs(expectedVersion);
|
|
997
|
+
const result = await $`npm ${args}`.nothrow();
|
|
998
|
+
if (result.exitCode !== 0) {
|
|
999
|
+
throw new Error(`npm install failed with exit code ${result.exitCode}`);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
await printVerification(expectedVersion);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
957
1005
|
async function updateViaHomebrew(expectedVersion: string, force: boolean): Promise<void> {
|
|
958
1006
|
console.log(chalk.dim("Updating Homebrew formulae..."));
|
|
959
1007
|
const update = await $`brew update`.nothrow();
|
|
@@ -1089,6 +1137,8 @@ export async function runUpdateCommand(opts: { force: boolean; check: boolean })
|
|
|
1089
1137
|
await updateViaMise(release.version, opts.force);
|
|
1090
1138
|
} else if (target.method === "bun") {
|
|
1091
1139
|
await updateViaBun(release.version);
|
|
1140
|
+
} else if (target.method === "npm") {
|
|
1141
|
+
await updateViaNpm(release.version);
|
|
1092
1142
|
} else {
|
|
1093
1143
|
await updateViaBinaryAt(target.path, release.version);
|
|
1094
1144
|
}
|
package/src/commands/acp.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* ACP terminal-auth flag asks the same command to open the interactive TUI.
|
|
6
6
|
*/
|
|
7
7
|
import { Command } from "jeopi-utils/cli";
|
|
8
|
-
import { parseArgs } from "../cli/args";
|
|
8
|
+
import { type Args as ParsedArgs, parseArgs, reportCliUsageError } from "../cli/args";
|
|
9
9
|
import { runRootCommand } from "../main";
|
|
10
10
|
import { prepareAcpTerminalAuthArgs } from "../modes/acp/terminal-auth";
|
|
11
11
|
|
|
@@ -15,7 +15,16 @@ export default class Acp extends Command {
|
|
|
15
15
|
|
|
16
16
|
async run(): Promise<void> {
|
|
17
17
|
const { args, terminalAuth } = prepareAcpTerminalAuthArgs(this.argv);
|
|
18
|
-
|
|
18
|
+
let parsed: ParsedArgs;
|
|
19
|
+
try {
|
|
20
|
+
parsed = parseArgs(args);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (reportCliUsageError(error)) {
|
|
23
|
+
process.exitCode = 2;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
19
28
|
if (!terminalAuth) {
|
|
20
29
|
parsed.mode = "acp";
|
|
21
30
|
}
|
package/src/commands/launch.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { APP_NAME } from "jeopi-utils";
|
|
6
6
|
import { Args, Command, Flags } from "jeopi-utils/cli";
|
|
7
|
-
import { parseArgs } from "../cli/args";
|
|
7
|
+
import { type Args as ParsedArgs, parseArgs, reportCliUsageError } from "../cli/args";
|
|
8
8
|
import { runRootCommand } from "../main";
|
|
9
9
|
import { prepareAcpTerminalAuthArgs } from "../modes/acp/terminal-auth";
|
|
10
10
|
import { CLI_THINKING_LEVELS } from "../thinking";
|
|
@@ -140,7 +140,7 @@ export default class Index extends Command {
|
|
|
140
140
|
description: "Include thinking blocks in print mode text output",
|
|
141
141
|
}),
|
|
142
142
|
"max-time": Flags.string({
|
|
143
|
-
description: "Stop the session after this
|
|
143
|
+
description: "Stop the session after this duration (e.g., 600, 10m, 1h)",
|
|
144
144
|
}),
|
|
145
145
|
// `--auto-approve` / `--yolo`: declared here so oclif's auto-generated `--help` lists it.
|
|
146
146
|
// Runtime parsing happens in `cli/args.ts parseArgs` (line 176 in that file) — `runRootCommand`
|
|
@@ -176,7 +176,16 @@ export default class Index extends Command {
|
|
|
176
176
|
|
|
177
177
|
async run(): Promise<void> {
|
|
178
178
|
const { args } = prepareAcpTerminalAuthArgs(this.argv);
|
|
179
|
-
|
|
179
|
+
let parsed: ParsedArgs;
|
|
180
|
+
try {
|
|
181
|
+
parsed = parseArgs(args);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (reportCliUsageError(error)) {
|
|
184
|
+
process.exitCode = 2;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
180
189
|
await runRootCommand(parsed, args);
|
|
181
190
|
}
|
|
182
191
|
}
|
|
@@ -29,6 +29,7 @@ export interface CommitAgentInput {
|
|
|
29
29
|
requireChangelog: boolean;
|
|
30
30
|
diffText?: string;
|
|
31
31
|
existingChangelogEntries?: ExistingChangelogEntries[];
|
|
32
|
+
onComplete?: (state: CommitAgentState) => Promise<void> | void;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export interface ExistingChangelogEntries {
|
|
@@ -175,6 +176,9 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
|
|
|
175
176
|
});
|
|
176
177
|
}
|
|
177
178
|
|
|
179
|
+
if (input.onComplete) {
|
|
180
|
+
await input.onComplete(state);
|
|
181
|
+
}
|
|
178
182
|
return state;
|
|
179
183
|
} finally {
|
|
180
184
|
unsubscribe();
|
|
@@ -6,7 +6,7 @@ import { detectChangelogBoundaries } from "../../commit/changelog/detect";
|
|
|
6
6
|
import { parseUnreleasedSection } from "../../commit/changelog/parse";
|
|
7
7
|
import { formatCommitMessage } from "../../commit/message";
|
|
8
8
|
import { resolvePrimaryModel, resolveSmolModel } from "../../commit/model-selection";
|
|
9
|
-
import type { CommitCommandArgs, ConventionalAnalysis } from "../../commit/types";
|
|
9
|
+
import type { CommitCommandArgs, ConventionalAnalysis, NumstatEntry } from "../../commit/types";
|
|
10
10
|
import { ModelRegistry } from "../../config/model-registry";
|
|
11
11
|
import { Settings } from "../../config/settings";
|
|
12
12
|
import { discoverAuthStorage, discoverContextFiles } from "../../sdk";
|
|
@@ -121,11 +121,10 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
process.stdout.write("● Starting commit agent...\n");
|
|
124
|
-
let
|
|
125
|
-
let usedFallback = false;
|
|
124
|
+
let agentSessionCompleted = false;
|
|
126
125
|
|
|
127
126
|
try {
|
|
128
|
-
|
|
127
|
+
await runCommitAgentSession({
|
|
129
128
|
cwd,
|
|
130
129
|
model: agentModel,
|
|
131
130
|
thinkingLevel: agentThinkingLevel,
|
|
@@ -138,42 +137,67 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
138
137
|
requireChangelog: !args.noChangelog && changelogTargets.length > 0,
|
|
139
138
|
diffText: diff,
|
|
140
139
|
existingChangelogEntries,
|
|
140
|
+
onComplete: async commitState => {
|
|
141
|
+
agentSessionCompleted = true;
|
|
142
|
+
await completeAgentCommitState(commitState, {
|
|
143
|
+
cwd,
|
|
144
|
+
dryRun: args.dryRun,
|
|
145
|
+
push: args.push,
|
|
146
|
+
noChangelog: args.noChangelog,
|
|
147
|
+
changelogTargets,
|
|
148
|
+
numstat,
|
|
149
|
+
});
|
|
150
|
+
},
|
|
141
151
|
});
|
|
152
|
+
return;
|
|
142
153
|
} catch (error) {
|
|
154
|
+
if (agentSessionCompleted) {
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
143
157
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
144
158
|
process.stderr.write(`Agent error: ${errorMessage}\n`);
|
|
145
159
|
if (error instanceof Error && error.stack && $env.DEBUG) {
|
|
146
160
|
process.stderr.write(`${error.stack}\n`);
|
|
147
161
|
}
|
|
148
162
|
process.stdout.write("● Using fallback commit generation...\n");
|
|
149
|
-
|
|
150
|
-
|
|
163
|
+
const fallbackProposal = generateFallbackProposal(numstat);
|
|
164
|
+
await runSingleCommit(fallbackProposal, { cwd, dryRun: args.dryRun, push: args.push });
|
|
165
|
+
return;
|
|
151
166
|
}
|
|
167
|
+
}
|
|
152
168
|
|
|
153
|
-
|
|
169
|
+
async function completeAgentCommitState(
|
|
170
|
+
commitState: CommitAgentState,
|
|
171
|
+
ctx: CommitExecutionContext & {
|
|
172
|
+
noChangelog: boolean;
|
|
173
|
+
changelogTargets: string[];
|
|
174
|
+
numstat: NumstatEntry[];
|
|
175
|
+
},
|
|
176
|
+
): Promise<void> {
|
|
177
|
+
let usedFallback = false;
|
|
178
|
+
if (!commitState.proposal && !commitState.splitProposal) {
|
|
154
179
|
if ($env.PI_COMMIT_NO_FALLBACK?.toLowerCase() !== "true") {
|
|
155
180
|
process.stdout.write("● Agent did not provide proposal, using fallback...\n");
|
|
156
|
-
commitState.proposal = generateFallbackProposal(numstat);
|
|
181
|
+
commitState.proposal = generateFallbackProposal(ctx.numstat);
|
|
157
182
|
usedFallback = true;
|
|
158
183
|
}
|
|
159
184
|
}
|
|
160
185
|
|
|
161
186
|
let updatedChangelogFiles: string[] = [];
|
|
162
|
-
if (!
|
|
187
|
+
if (!ctx.noChangelog && ctx.changelogTargets.length > 0 && !usedFallback) {
|
|
163
188
|
if (!commitState.changelogProposal) {
|
|
164
|
-
|
|
165
|
-
return;
|
|
189
|
+
throw new Error("Commit agent did not provide changelog entries.");
|
|
166
190
|
}
|
|
167
191
|
process.stdout.write("● Applying changelog entries...\n");
|
|
168
192
|
const updated = await applyChangelogProposals({
|
|
169
|
-
cwd,
|
|
193
|
+
cwd: ctx.cwd,
|
|
170
194
|
proposals: commitState.changelogProposal.entries,
|
|
171
|
-
dryRun:
|
|
195
|
+
dryRun: ctx.dryRun,
|
|
172
196
|
onProgress: message => {
|
|
173
197
|
process.stdout.write(` ├─ ${message}\n`);
|
|
174
198
|
},
|
|
175
199
|
});
|
|
176
|
-
updatedChangelogFiles = updated.map(filePath => path.relative(cwd, filePath));
|
|
200
|
+
updatedChangelogFiles = updated.map(filePath => path.relative(ctx.cwd, filePath));
|
|
177
201
|
if (updated.length > 0) {
|
|
178
202
|
for (const filePath of updated) {
|
|
179
203
|
process.stdout.write(` └─ ${filePath}\n`);
|
|
@@ -184,21 +208,21 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
184
208
|
}
|
|
185
209
|
|
|
186
210
|
if (commitState.proposal) {
|
|
187
|
-
await runSingleCommit(commitState.proposal,
|
|
211
|
+
await runSingleCommit(commitState.proposal, ctx);
|
|
188
212
|
return;
|
|
189
213
|
}
|
|
190
214
|
|
|
191
215
|
if (commitState.splitProposal) {
|
|
192
216
|
await runSplitCommit(commitState.splitProposal, {
|
|
193
|
-
cwd,
|
|
194
|
-
dryRun:
|
|
195
|
-
push:
|
|
217
|
+
cwd: ctx.cwd,
|
|
218
|
+
dryRun: ctx.dryRun,
|
|
219
|
+
push: ctx.push,
|
|
196
220
|
additionalFiles: updatedChangelogFiles,
|
|
197
221
|
});
|
|
198
222
|
return;
|
|
199
223
|
}
|
|
200
224
|
|
|
201
|
-
|
|
225
|
+
throw new Error("Commit agent did not provide a proposal.");
|
|
202
226
|
}
|
|
203
227
|
|
|
204
228
|
async function runSingleCommit(proposal: CommitProposal, ctx: CommitExecutionContext): Promise<void> {
|
|
@@ -211,6 +235,7 @@ async function runSingleCommit(proposal: CommitProposal, ctx: CommitExecutionCon
|
|
|
211
235
|
process.stdout.write(`${commitMessage}\n`);
|
|
212
236
|
return;
|
|
213
237
|
}
|
|
238
|
+
process.stdout.write("● Creating commit...\n");
|
|
214
239
|
await git.commit(ctx.cwd, commitMessage);
|
|
215
240
|
process.stdout.write("Commit created.\n");
|
|
216
241
|
if (ctx.push) {
|
|
@@ -233,8 +258,7 @@ async function runSplitCommit(
|
|
|
233
258
|
const plannedFiles = new Set(plan.commits.flatMap(commit => commit.changes.map(change => change.path)));
|
|
234
259
|
const missingFiles = stagedFiles.filter(file => !plannedFiles.has(file));
|
|
235
260
|
if (missingFiles.length > 0) {
|
|
236
|
-
|
|
237
|
-
return;
|
|
261
|
+
throw new Error(`Split commit plan missing staged files: ${missingFiles.join(", ")}`);
|
|
238
262
|
}
|
|
239
263
|
|
|
240
264
|
if (ctx.dryRun) {
|
|
@@ -266,6 +290,7 @@ async function runSplitCommit(
|
|
|
266
290
|
throw new Error(order.error);
|
|
267
291
|
}
|
|
268
292
|
|
|
293
|
+
process.stdout.write("● Creating split commits...\n");
|
|
269
294
|
const stagedDiff = await git.diff(ctx.cwd, { cached: true });
|
|
270
295
|
await git.stage.reset(ctx.cwd);
|
|
271
296
|
for (const commitIndex of order) {
|
|
@@ -11,9 +11,9 @@ interface ConfigSchemaError {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* Module-private cache of
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Module-private cache of JSON → YAML migrations this process already ran.
|
|
15
|
+
* Prevents `ConfigFile.relocate()` / repeated `tryLoad()` calls from re-running
|
|
16
|
+
* the migration over and over on the boot path.
|
|
17
17
|
*/
|
|
18
18
|
const migratedPaths = new Set<string>();
|
|
19
19
|
|
|
@@ -123,6 +123,7 @@ export type LoadResult<T> =
|
|
|
123
123
|
|
|
124
124
|
export class ConfigFile<T> implements IConfigFile<T> {
|
|
125
125
|
readonly #basePath: string;
|
|
126
|
+
readonly #yamlFallbackPath: string | null;
|
|
126
127
|
readonly #jsonMigrationPath: string | null;
|
|
127
128
|
#cache?: LoadResult<T>;
|
|
128
129
|
#auxValidate?: (value: T) => void;
|
|
@@ -134,13 +135,17 @@ export class ConfigFile<T> implements IConfigFile<T> {
|
|
|
134
135
|
) {
|
|
135
136
|
this.#basePath = configPath;
|
|
136
137
|
if (configPath.endsWith(".yml")) {
|
|
138
|
+
this.#yamlFallbackPath = `${configPath.slice(0, -4)}.yaml`;
|
|
137
139
|
this.#jsonMigrationPath = `${configPath.slice(0, -4)}.json`;
|
|
138
140
|
} else if (configPath.endsWith(".yaml")) {
|
|
141
|
+
this.#yamlFallbackPath = null;
|
|
139
142
|
this.#jsonMigrationPath = `${configPath.slice(0, -5)}.json`;
|
|
140
143
|
} else if (configPath.endsWith(".json") || configPath.endsWith(".jsonc")) {
|
|
144
|
+
this.#yamlFallbackPath = null;
|
|
141
145
|
// JSON configs are still supported without migration.
|
|
142
146
|
this.#jsonMigrationPath = null;
|
|
143
147
|
} else {
|
|
148
|
+
this.#yamlFallbackPath = null;
|
|
144
149
|
throw new Error(`Invalid config file path: ${configPath}`);
|
|
145
150
|
}
|
|
146
151
|
}
|
|
@@ -150,9 +155,21 @@ export class ConfigFile<T> implements IConfigFile<T> {
|
|
|
150
155
|
* Sync callers (tests, settings init) hit this implicitly via {@link tryLoad}.
|
|
151
156
|
*/
|
|
152
157
|
#ensureMigrated(): void {
|
|
153
|
-
if (this.#jsonMigrationPath)
|
|
154
|
-
|
|
158
|
+
if (!this.#jsonMigrationPath) return;
|
|
159
|
+
if (this.#yamlFallbackPath && !fs.existsSync(this.#basePath) && fs.existsSync(this.#yamlFallbackPath)) {
|
|
160
|
+
return;
|
|
155
161
|
}
|
|
162
|
+
migrateJsonToYml(this.#jsonMigrationPath, this.#basePath);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
#resolveReadPath(): string {
|
|
166
|
+
if (fs.existsSync(this.#basePath)) {
|
|
167
|
+
return this.#basePath;
|
|
168
|
+
}
|
|
169
|
+
if (this.#yamlFallbackPath && fs.existsSync(this.#yamlFallbackPath)) {
|
|
170
|
+
return this.#yamlFallbackPath;
|
|
171
|
+
}
|
|
172
|
+
return this.#basePath;
|
|
156
173
|
}
|
|
157
174
|
|
|
158
175
|
relocate(configPath?: string): ConfigFile<T> {
|
|
@@ -165,7 +182,7 @@ export class ConfigFile<T> implements IConfigFile<T> {
|
|
|
165
182
|
|
|
166
183
|
getMtimeMs(): number | null {
|
|
167
184
|
try {
|
|
168
|
-
return fs.statSync(this
|
|
185
|
+
return fs.statSync(this.#resolveReadPath()).mtimeMs;
|
|
169
186
|
} catch (err) {
|
|
170
187
|
if (isEnoent(err)) return null;
|
|
171
188
|
throw err;
|
|
@@ -211,12 +228,13 @@ export class ConfigFile<T> implements IConfigFile<T> {
|
|
|
211
228
|
#parseContent(content: string): LoadResult<T> {
|
|
212
229
|
try {
|
|
213
230
|
let parsed: unknown;
|
|
214
|
-
|
|
231
|
+
const readPath = this.#resolveReadPath();
|
|
232
|
+
if (readPath.endsWith(".json") || readPath.endsWith(".jsonc")) {
|
|
215
233
|
parsed = JSONC.parse(content);
|
|
216
|
-
} else if (
|
|
234
|
+
} else if (readPath.endsWith(".yml") || readPath.endsWith(".yaml")) {
|
|
217
235
|
parsed = YAML.parse(content);
|
|
218
236
|
} else {
|
|
219
|
-
throw new Error(`Invalid config file path: ${
|
|
237
|
+
throw new Error(`Invalid config file path: ${readPath}`);
|
|
220
238
|
}
|
|
221
239
|
|
|
222
240
|
const checked = this.schema(parsed);
|
|
@@ -255,7 +273,7 @@ export class ConfigFile<T> implements IConfigFile<T> {
|
|
|
255
273
|
|
|
256
274
|
let content: string;
|
|
257
275
|
try {
|
|
258
|
-
content = fs.readFileSync(this
|
|
276
|
+
content = fs.readFileSync(this.#resolveReadPath(), "utf-8").trim();
|
|
259
277
|
} catch (error) {
|
|
260
278
|
if (isEnoent(error)) {
|
|
261
279
|
return this.#storeCache({ status: "not-found" });
|
|
@@ -275,7 +293,7 @@ export class ConfigFile<T> implements IConfigFile<T> {
|
|
|
275
293
|
|
|
276
294
|
let content: string;
|
|
277
295
|
try {
|
|
278
|
-
content = (await Bun.file(this
|
|
296
|
+
content = (await Bun.file(this.#resolveReadPath()).text()).trim();
|
|
279
297
|
} catch (error) {
|
|
280
298
|
if (isEnoent(error)) {
|
|
281
299
|
return this.#storeCache({ status: "not-found" });
|
|
@@ -306,7 +324,7 @@ export class ConfigFile<T> implements IConfigFile<T> {
|
|
|
306
324
|
}
|
|
307
325
|
|
|
308
326
|
path(): string {
|
|
309
|
-
return this.#
|
|
327
|
+
return this.#resolveReadPath();
|
|
310
328
|
}
|
|
311
329
|
|
|
312
330
|
invalidate() {
|
|
@@ -234,7 +234,7 @@ export interface ProviderDiscoveryState {
|
|
|
234
234
|
error?: string;
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
/** Result of loading custom models
|
|
237
|
+
/** Result of loading custom models config. */
|
|
238
238
|
interface CustomModelsResult {
|
|
239
239
|
models?: CustomModelOverlay[];
|
|
240
240
|
overrides?: Map<string, ProviderOverride>;
|
|
@@ -271,7 +271,7 @@ interface CommandApiKeyResolution {
|
|
|
271
271
|
value?: string;
|
|
272
272
|
}
|
|
273
273
|
/**
|
|
274
|
-
* Resolve a models.yml secret/config value to an actual value.
|
|
274
|
+
* Resolve a models.yml/models.yaml secret/config value to an actual value.
|
|
275
275
|
* `!cmd` runs a shell command and returns trimmed stdout, otherwise env vars are
|
|
276
276
|
* checked first and the input falls back to a literal value.
|
|
277
277
|
*/
|
|
@@ -784,7 +784,7 @@ export class ModelRegistry {
|
|
|
784
784
|
}
|
|
785
785
|
|
|
786
786
|
/**
|
|
787
|
-
* Reload models from disk (built-in + custom
|
|
787
|
+
* Reload models from disk (built-in + custom config).
|
|
788
788
|
*/
|
|
789
789
|
async refresh(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
|
|
790
790
|
this.#reloadStaticModels();
|
|
@@ -889,7 +889,7 @@ export class ModelRegistry {
|
|
|
889
889
|
#reloadStaticModels(): void {
|
|
890
890
|
const currentMtime = this.#modelsConfigFile.getMtimeMs();
|
|
891
891
|
if (currentMtime !== null && currentMtime === this.#lastStaticLoadMtime) {
|
|
892
|
-
//
|
|
892
|
+
// Models config unchanged since last load; reloading would be redundant.
|
|
893
893
|
return;
|
|
894
894
|
}
|
|
895
895
|
this.#modelsConfigFile.invalidate();
|
|
@@ -913,14 +913,14 @@ export class ModelRegistry {
|
|
|
913
913
|
}
|
|
914
914
|
|
|
915
915
|
/**
|
|
916
|
-
* Get any error from loading models
|
|
916
|
+
* Get any error from loading custom models config (undefined if no error).
|
|
917
917
|
*/
|
|
918
918
|
getError(): ConfigError | undefined {
|
|
919
919
|
return this.#configError;
|
|
920
920
|
}
|
|
921
921
|
|
|
922
922
|
#loadModels() {
|
|
923
|
-
// Load custom
|
|
923
|
+
// Load custom config first (to know which providers to override).
|
|
924
924
|
const {
|
|
925
925
|
models: customModels = [],
|
|
926
926
|
overrides = new Map(),
|
|
@@ -1823,7 +1823,7 @@ export class ModelRegistry {
|
|
|
1823
1823
|
|
|
1824
1824
|
/**
|
|
1825
1825
|
* Get all models (built-in + custom).
|
|
1826
|
-
* If
|
|
1826
|
+
* If custom config had errors, returns only built-in models.
|
|
1827
1827
|
*/
|
|
1828
1828
|
getAll(): Model<Api>[] {
|
|
1829
1829
|
return this.#models;
|
|
@@ -1149,20 +1149,32 @@ export function resolveModelOverride(
|
|
|
1149
1149
|
modelPatterns: string[],
|
|
1150
1150
|
modelRegistry: ModelLookupRegistry,
|
|
1151
1151
|
settings?: Settings,
|
|
1152
|
-
): {
|
|
1152
|
+
): {
|
|
1153
|
+
model?: Model<Api>;
|
|
1154
|
+
thinkingLevel?: ConfiguredThinkingLevel;
|
|
1155
|
+
explicitThinkingLevel: boolean;
|
|
1156
|
+
warning?: string;
|
|
1157
|
+
} {
|
|
1153
1158
|
if (modelPatterns.length === 0) return { explicitThinkingLevel: false };
|
|
1154
1159
|
const availableModels = modelRegistry.getAvailable();
|
|
1155
1160
|
const matchPreferences = getModelMatchPreferences(settings);
|
|
1161
|
+
let warning: string | undefined;
|
|
1156
1162
|
for (const pattern of modelPatterns) {
|
|
1157
|
-
const {
|
|
1163
|
+
const {
|
|
1164
|
+
model,
|
|
1165
|
+
thinkingLevel,
|
|
1166
|
+
explicitThinkingLevel,
|
|
1167
|
+
warning: patternWarning,
|
|
1168
|
+
} = resolveModelRoleValue(pattern, availableModels, {
|
|
1158
1169
|
settings,
|
|
1159
1170
|
matchPreferences,
|
|
1160
1171
|
});
|
|
1161
1172
|
if (model) {
|
|
1162
|
-
return { model, thinkingLevel, explicitThinkingLevel };
|
|
1173
|
+
return { model, thinkingLevel, explicitThinkingLevel, warning: patternWarning };
|
|
1163
1174
|
}
|
|
1175
|
+
if (!warning && patternWarning) warning = patternWarning;
|
|
1164
1176
|
}
|
|
1165
|
-
return { explicitThinkingLevel: false };
|
|
1177
|
+
return { explicitThinkingLevel: false, warning };
|
|
1166
1178
|
}
|
|
1167
1179
|
|
|
1168
1180
|
/**
|
|
@@ -1176,6 +1188,10 @@ export function resolveModelOverride(
|
|
|
1176
1188
|
* `modelRoles.task` pointing at an unqualified id whose only available
|
|
1177
1189
|
* provider variant has no configured credentials — see #985).
|
|
1178
1190
|
*
|
|
1191
|
+
* `sessionId` is forwarded to `getApiKey` so that session-sticky OAuth
|
|
1192
|
+
* credentials resolve correctly during the pre-flight auth check. Without it,
|
|
1193
|
+
* providers with multiple OAuth accounts may return `undefined` even though
|
|
1194
|
+
* the credential is usable once the subagent session starts — see #5325.
|
|
1179
1195
|
* Keyless-by-design providers (llama.cpp, ollama, lm-studio) advertise the
|
|
1180
1196
|
* `kNoAuth` sentinel from `getApiKey` to signal that they do not require
|
|
1181
1197
|
* credentials. Those are treated as authenticated here so an explicitly
|
|
@@ -1191,18 +1207,20 @@ export async function resolveModelOverrideWithAuthFallback(
|
|
|
1191
1207
|
parentActiveModelPattern: string | undefined,
|
|
1192
1208
|
modelRegistry: ModelLookupRegistry & Pick<ModelRegistry, "getApiKey">,
|
|
1193
1209
|
settings?: Settings,
|
|
1210
|
+
sessionId?: string,
|
|
1194
1211
|
): Promise<{
|
|
1195
1212
|
model?: Model<Api>;
|
|
1196
1213
|
thinkingLevel?: ConfiguredThinkingLevel;
|
|
1197
1214
|
explicitThinkingLevel: boolean;
|
|
1198
1215
|
authFallbackUsed: boolean;
|
|
1216
|
+
warning?: string;
|
|
1199
1217
|
}> {
|
|
1200
1218
|
const primary = resolveModelOverride(modelPatterns, modelRegistry, settings);
|
|
1201
1219
|
if (!primary.model || !parentActiveModelPattern) {
|
|
1202
1220
|
return { ...primary, authFallbackUsed: false };
|
|
1203
1221
|
}
|
|
1204
1222
|
|
|
1205
|
-
const primaryKey = await modelRegistry.getApiKey(primary.model);
|
|
1223
|
+
const primaryKey = await modelRegistry.getApiKey(primary.model, sessionId);
|
|
1206
1224
|
if (primaryKey === kNoAuth || isAuthenticated(primaryKey)) {
|
|
1207
1225
|
return { ...primary, authFallbackUsed: false };
|
|
1208
1226
|
}
|
|
@@ -1214,7 +1232,7 @@ export async function resolveModelOverrideWithAuthFallback(
|
|
|
1214
1232
|
if (modelsAreEqual(fallback.model, primary.model)) {
|
|
1215
1233
|
return { ...primary, authFallbackUsed: false };
|
|
1216
1234
|
}
|
|
1217
|
-
const fallbackKey = await modelRegistry.getApiKey(fallback.model);
|
|
1235
|
+
const fallbackKey = await modelRegistry.getApiKey(fallback.model, sessionId);
|
|
1218
1236
|
if (!isAuthenticated(fallbackKey)) {
|
|
1219
1237
|
return { ...primary, authFallbackUsed: false };
|
|
1220
1238
|
}
|