@openeryc/pi-coding-agent 0.75.56 → 0.75.58
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 +18 -0
- package/dist/core/keybindings.d.ts +10 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +8 -0
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-registry.d.ts +6 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +38 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +7 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +14 -4
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/model-hub.d.ts +2 -0
- package/dist/modes/interactive/components/model-hub.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-hub.js +30 -8
- package/dist/modes/interactive/components/model-hub.js.map +1 -1
- package/dist/modes/interactive/goal-runner-utils.d.ts +11 -0
- package/dist/modes/interactive/goal-runner-utils.d.ts.map +1 -0
- package/dist/modes/interactive/goal-runner-utils.js +39 -0
- package/dist/modes/interactive/goal-runner-utils.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +77 -29
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/models.md +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -62,6 +62,7 @@ import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
|
62
62
|
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
63
63
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
64
64
|
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
|
65
|
+
import { isNearDuplicateGoalOutput, normalizeGoalOutput } from "./goal-runner-utils.js";
|
|
65
66
|
import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
|
|
66
67
|
function isExpandable(obj) {
|
|
67
68
|
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
@@ -181,9 +182,13 @@ export class InteractiveMode {
|
|
|
181
182
|
goalRunnerAbortController = undefined;
|
|
182
183
|
goalRunnerRetryAttempt = 0;
|
|
183
184
|
goalRunnerStatusDisplayed = false;
|
|
185
|
+
/** Normalized assistant texts from recent goal turns (for duplicate detection). */
|
|
186
|
+
goalRunnerRecentOutputs = [];
|
|
184
187
|
GOAL_RUNNER_MAX_RETRIES = 10;
|
|
185
188
|
GOAL_RUNNER_BASE_DELAY_MS = 3000;
|
|
186
189
|
GOAL_RUNNER_TURN_DELAY_MS = 800;
|
|
190
|
+
/** Pause when this many consecutive turns look like near-duplicates. */
|
|
191
|
+
GOAL_RUNNER_MAX_DUPLICATE_STREAK = 3;
|
|
187
192
|
// Extension UI state
|
|
188
193
|
extensionSelector = undefined;
|
|
189
194
|
extensionInput = undefined;
|
|
@@ -4003,11 +4008,10 @@ export class InteractiveMode {
|
|
|
4003
4008
|
this.showModelHub("manage");
|
|
4004
4009
|
return;
|
|
4005
4010
|
}
|
|
4006
|
-
// models.json apiKey is opaque (env/command/literal); do not surface the raw value
|
|
4007
4011
|
existing = {
|
|
4008
4012
|
providerId: options.providerId,
|
|
4009
4013
|
baseUrl: listed.baseUrl ?? "",
|
|
4010
|
-
apiKey: "",
|
|
4014
|
+
apiKey: listed.apiKey ?? "",
|
|
4011
4015
|
models: listed.modelIds.map((id) => ({ id, name: id })),
|
|
4012
4016
|
};
|
|
4013
4017
|
}
|
|
@@ -4032,31 +4036,33 @@ export class InteractiveMode {
|
|
|
4032
4036
|
this.ui.requestRender();
|
|
4033
4037
|
};
|
|
4034
4038
|
try {
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4039
|
+
const rawName = (await dialog.showPrompt(isEdit ? "Provider name/id:" : "Provider name/id (e.g. ollama, agnes):", isEdit
|
|
4040
|
+
? { value: existing?.providerId ?? "" }
|
|
4041
|
+
: { placeholder: "ollama", value: existing?.providerId ?? "ollama" })).trim();
|
|
4042
|
+
const providerId = slugifyProviderId(rawName);
|
|
4043
|
+
if (!providerId) {
|
|
4044
|
+
throw new Error("Provider name cannot be empty.");
|
|
4045
|
+
}
|
|
4046
|
+
if (BUILT_IN_MODEL_PROVIDERS.has(providerId) && options.target === "openai_compatible") {
|
|
4047
|
+
throw new Error(`"${providerId}" is a built-in provider. Use a different name, or add it via Manage → models.json override.`);
|
|
4048
|
+
}
|
|
4049
|
+
const baseUrl = (await dialog.showPrompt("Enter base URL:", isEdit || existing?.baseUrl
|
|
4050
|
+
? { value: existing?.baseUrl || "http://localhost:11434/v1" }
|
|
4051
|
+
: { placeholder: "http://localhost:11434/v1", value: "http://localhost:11434/v1" })).trim();
|
|
4047
4052
|
if (!baseUrl) {
|
|
4048
4053
|
throw new Error("Base URL cannot be empty.");
|
|
4049
4054
|
}
|
|
4050
4055
|
const apiKeyPrompt = options.target === "models_json"
|
|
4051
|
-
?
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4056
|
+
? isEdit
|
|
4057
|
+
? "API key (literal/env/!command; blank keeps current):"
|
|
4058
|
+
: "API key (literal, env var name, or !command; blank = not-needed):"
|
|
4059
|
+
: isEdit
|
|
4060
|
+
? "API key (blank keeps current; type not-needed for local):"
|
|
4061
|
+
: "Enter API key (or leave blank for local endpoints):";
|
|
4062
|
+
const apiKeyCurrent = existing?.apiKey === "not-needed" ? "" : (existing?.apiKey ?? "");
|
|
4063
|
+
const apiKey = (await dialog.showPrompt(apiKeyPrompt, isEdit || apiKeyCurrent ? { value: apiKeyCurrent } : { placeholder: "sk-... or leave blank" })).trim();
|
|
4064
|
+
// Blank on edit keeps the existing apiKey (models.json and openai_compatible).
|
|
4065
|
+
const effectiveApiKey = apiKey ? apiKey : isEdit ? undefined : "not-needed";
|
|
4060
4066
|
dialog.showInfo([theme.fg("muted", "Discovering models from /models ...")]);
|
|
4061
4067
|
this.ui.requestRender();
|
|
4062
4068
|
const discovered = await fetchOpenAICompatibleModels(baseUrl, effectiveApiKey);
|
|
@@ -4123,7 +4129,7 @@ export class InteractiveMode {
|
|
|
4123
4129
|
]);
|
|
4124
4130
|
this.ui.requestRender();
|
|
4125
4131
|
const defaultIds = existing?.models.map((m) => m.id).join(", ") || "llama3.1";
|
|
4126
|
-
const modelNames = (await dialog.showPrompt("Enter model name(s) (comma-separated):", defaultIds)).trim();
|
|
4132
|
+
const modelNames = (await dialog.showPrompt("Enter model name(s) (comma-separated):", { value: defaultIds })).trim();
|
|
4127
4133
|
if (!modelNames) {
|
|
4128
4134
|
throw new Error("At least one model name is required.");
|
|
4129
4135
|
}
|
|
@@ -4134,8 +4140,8 @@ export class InteractiveMode {
|
|
|
4134
4140
|
.map((id) => ({ id, name: id }));
|
|
4135
4141
|
}
|
|
4136
4142
|
if (options.target === "openai_compatible") {
|
|
4137
|
-
const storedKey = effectiveApiKey ?? "not-needed";
|
|
4138
|
-
//
|
|
4143
|
+
const storedKey = effectiveApiKey ?? (isEdit && existing?.apiKey ? existing.apiKey : "not-needed");
|
|
4144
|
+
// Rename: drop old id if the name changed
|
|
4139
4145
|
if (isEdit && existing && existing.providerId !== providerId) {
|
|
4140
4146
|
this.session.modelRegistry.authStorage.remove(existing.providerId);
|
|
4141
4147
|
}
|
|
@@ -4156,7 +4162,7 @@ export class InteractiveMode {
|
|
|
4156
4162
|
await this.completeProviderAuthentication(providerId, baseUrl, "api_key", previousModel);
|
|
4157
4163
|
}
|
|
4158
4164
|
else {
|
|
4159
|
-
|
|
4165
|
+
const writeConfig = {
|
|
4160
4166
|
baseUrl,
|
|
4161
4167
|
...(effectiveApiKey !== undefined ? { apiKey: effectiveApiKey } : {}),
|
|
4162
4168
|
api: "openai-completions",
|
|
@@ -4166,7 +4172,13 @@ export class InteractiveMode {
|
|
|
4166
4172
|
reasoning: false,
|
|
4167
4173
|
input: ["text"],
|
|
4168
4174
|
})),
|
|
4169
|
-
}
|
|
4175
|
+
};
|
|
4176
|
+
if (isEdit && existing && existing.providerId !== providerId) {
|
|
4177
|
+
this.session.modelRegistry.renameProviderInModelsJson(existing.providerId, providerId, writeConfig);
|
|
4178
|
+
}
|
|
4179
|
+
else {
|
|
4180
|
+
this.session.modelRegistry.upsertProviderInModelsJson(providerId, writeConfig);
|
|
4181
|
+
}
|
|
4170
4182
|
restoreEditor();
|
|
4171
4183
|
await this.updateAvailableProviderCount();
|
|
4172
4184
|
// Prefer selecting first model if no real model yet
|
|
@@ -4601,6 +4613,9 @@ export class InteractiveMode {
|
|
|
4601
4613
|
const lower = goal.toLowerCase();
|
|
4602
4614
|
if (lower === "on" || lower === "off") {
|
|
4603
4615
|
this.goalRunnerEnabled = lower === "on";
|
|
4616
|
+
if (lower === "on") {
|
|
4617
|
+
this.goalRunnerRecentOutputs = [];
|
|
4618
|
+
}
|
|
4604
4619
|
const currentGoal = this.session.sessionGoal;
|
|
4605
4620
|
if (currentGoal) {
|
|
4606
4621
|
const label = lower === "on" ? theme.fg("success", "enabled") : theme.fg("warning", "paused");
|
|
@@ -4615,6 +4630,7 @@ export class InteractiveMode {
|
|
|
4615
4630
|
}
|
|
4616
4631
|
this.session.setSessionGoal(goal);
|
|
4617
4632
|
this.goalRunnerEnabled = true;
|
|
4633
|
+
this.goalRunnerRecentOutputs = [];
|
|
4618
4634
|
this.chatContainer.addChild(new Spacer(1));
|
|
4619
4635
|
this.chatContainer.addChild(new Text(theme.fg("dim", `Session goal set: ${goal}`), 1, 0));
|
|
4620
4636
|
this.ui.requestRender();
|
|
@@ -4653,9 +4669,41 @@ export class InteractiveMode {
|
|
|
4653
4669
|
this.showStatus(`Pursuing goal: "${goal}" (${keyText("app.interrupt")} to pause)`);
|
|
4654
4670
|
}
|
|
4655
4671
|
try {
|
|
4656
|
-
await this.session.prompt(`Continue pursuing the goal: "${goal}". If you have fully completed this goal, report what was accomplished. If you cannot make further progress, explain what blocked you.`);
|
|
4672
|
+
await this.session.prompt(`Continue pursuing the goal: "${goal}". If you have fully completed this goal, report what was accomplished. If you cannot make further progress, explain what blocked you. Do not repeat the same status/report as previous turns — either make concrete progress or clearly state that the goal is done/blocked.`);
|
|
4657
4673
|
// Successful turn completed, reset retry counter
|
|
4658
4674
|
this.goalRunnerRetryAttempt = 0;
|
|
4675
|
+
// Auto-pause when the model keeps emitting near-duplicate content.
|
|
4676
|
+
const lastText = this.session.getLastAssistantText();
|
|
4677
|
+
if (lastText) {
|
|
4678
|
+
const normalized = normalizeGoalOutput(lastText);
|
|
4679
|
+
const prev = this.goalRunnerRecentOutputs[this.goalRunnerRecentOutputs.length - 1];
|
|
4680
|
+
if (prev && isNearDuplicateGoalOutput(prev, normalized)) {
|
|
4681
|
+
this.goalRunnerRecentOutputs.push(normalized);
|
|
4682
|
+
}
|
|
4683
|
+
else {
|
|
4684
|
+
this.goalRunnerRecentOutputs = [normalized];
|
|
4685
|
+
}
|
|
4686
|
+
// Keep a small window
|
|
4687
|
+
if (this.goalRunnerRecentOutputs.length > this.GOAL_RUNNER_MAX_DUPLICATE_STREAK) {
|
|
4688
|
+
this.goalRunnerRecentOutputs = this.goalRunnerRecentOutputs.slice(-this.GOAL_RUNNER_MAX_DUPLICATE_STREAK);
|
|
4689
|
+
}
|
|
4690
|
+
if (this.goalRunnerRecentOutputs.length >= this.GOAL_RUNNER_MAX_DUPLICATE_STREAK) {
|
|
4691
|
+
const window = this.goalRunnerRecentOutputs;
|
|
4692
|
+
let allDup = true;
|
|
4693
|
+
for (let i = 1; i < window.length; i++) {
|
|
4694
|
+
if (!isNearDuplicateGoalOutput(window[i - 1], window[i])) {
|
|
4695
|
+
allDup = false;
|
|
4696
|
+
break;
|
|
4697
|
+
}
|
|
4698
|
+
}
|
|
4699
|
+
if (allDup) {
|
|
4700
|
+
this.goalRunnerEnabled = false;
|
|
4701
|
+
this.goalRunnerRecentOutputs = [];
|
|
4702
|
+
this.showStatus(`Goal runner paused: model repeated similar output ${this.GOAL_RUNNER_MAX_DUPLICATE_STREAK} turns in a row. Use /goal on to resume, or refine the goal.`);
|
|
4703
|
+
break;
|
|
4704
|
+
}
|
|
4705
|
+
}
|
|
4706
|
+
}
|
|
4659
4707
|
}
|
|
4660
4708
|
catch (error) {
|
|
4661
4709
|
this.goalRunnerRetryAttempt++;
|