@caupulican/pi-adaptative 0.80.5 → 0.80.7
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 +14 -1
- package/README.md +14 -2
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/core/keybindings.d.ts +1 -1
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +1 -1
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-resolver.d.ts +7 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +85 -16
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/session-manager.d.ts +1 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +5 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +14 -8
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +40 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +539 -136
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/keybindings.md +1 -1
- package/docs/providers.md +27 -2
- package/docs/settings.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
|
@@ -10,6 +10,7 @@ import { getProviders, } from "@earendil-works/pi-ai";
|
|
|
10
10
|
import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/pi-tui";
|
|
11
11
|
import chalk from "chalk";
|
|
12
12
|
import { spawn, spawnSync } from "child_process";
|
|
13
|
+
import lockfile from "proper-lockfile";
|
|
13
14
|
import { APP_NAME, APP_TITLE, getAgentDir, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
|
|
14
15
|
import { parseSkillBlock } from "../../core/agent-session.js";
|
|
15
16
|
import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
|
|
@@ -17,12 +18,12 @@ import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
|
17
18
|
import { configureHttpDispatcher, formatHttpIdleTimeoutMs } from "../../core/http-dispatcher.js";
|
|
18
19
|
import { KeybindingsManager } from "../../core/keybindings.js";
|
|
19
20
|
import { createCompactionSummaryMessage } from "../../core/messages.js";
|
|
20
|
-
import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope } from "../../core/model-resolver.js";
|
|
21
|
+
import { cliProviderAliases, defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScope, } from "../../core/model-resolver.js";
|
|
21
22
|
import { DefaultPackageManager } from "../../core/package-manager.js";
|
|
22
23
|
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "../../core/provider-display-names.js";
|
|
23
24
|
import { getPendingReloadBlockers } from "../../core/reload-blockers.js";
|
|
24
25
|
import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
|
|
25
|
-
import { SessionManager } from "../../core/session-manager.js";
|
|
26
|
+
import { isAutoLearnSessionId, SessionManager } from "../../core/session-manager.js";
|
|
26
27
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
27
28
|
import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
|
|
28
29
|
import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/changelog.js";
|
|
@@ -145,6 +146,154 @@ const AUTONOMY_AUTO_LEARN_PRESETS = {
|
|
|
145
146
|
},
|
|
146
147
|
};
|
|
147
148
|
const AUTONOMY_MODES = ["off", "safe", "balanced", "full"];
|
|
149
|
+
const AUTO_LEARN_RESERVATION_MS = 2 * 60 * 1000;
|
|
150
|
+
export const AUTO_LEARN_HISTORY_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
151
|
+
function definedStringSet(values) {
|
|
152
|
+
const set = new Set();
|
|
153
|
+
if (!values)
|
|
154
|
+
return set;
|
|
155
|
+
for (const value of values) {
|
|
156
|
+
if (typeof value === "string" && value.length > 0)
|
|
157
|
+
set.add(value);
|
|
158
|
+
}
|
|
159
|
+
return set;
|
|
160
|
+
}
|
|
161
|
+
function isOldAutoLearnArtifact(filePath, now, retentionMs) {
|
|
162
|
+
const stats = fs.lstatSync(filePath);
|
|
163
|
+
return stats.isFile() && now - stats.mtimeMs > retentionMs;
|
|
164
|
+
}
|
|
165
|
+
function removeOldAutoLearnArtifact(filePath, result, counter) {
|
|
166
|
+
try {
|
|
167
|
+
fs.rmSync(filePath, { force: true });
|
|
168
|
+
result[counter]++;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
result.errors++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function readAutoLearnSessionIdFromFile(filePath) {
|
|
175
|
+
let fd;
|
|
176
|
+
try {
|
|
177
|
+
fd = fs.openSync(filePath, "r");
|
|
178
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
179
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
180
|
+
const firstLine = buffer.toString("utf8", 0, bytesRead).split("\n", 1)[0]?.trim();
|
|
181
|
+
if (!firstLine)
|
|
182
|
+
return undefined;
|
|
183
|
+
const header = JSON.parse(firstLine);
|
|
184
|
+
return header.type === "session" && typeof header.id === "string" ? header.id : undefined;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
if (fd !== undefined) {
|
|
191
|
+
try {
|
|
192
|
+
fs.closeSync(fd);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// Ignore close errors while pruning best-effort history artifacts.
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function getAutoLearnSessionIdFromFileName(fileName) {
|
|
201
|
+
return fileName.match(/_(auto-learn-[A-Za-z0-9._-]+)\.jsonl$/)?.[1];
|
|
202
|
+
}
|
|
203
|
+
function pruneAutoLearnSessionFiles(dir, activeSessionIds, now, retentionMs, result) {
|
|
204
|
+
let entries;
|
|
205
|
+
try {
|
|
206
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
for (const entry of entries) {
|
|
212
|
+
const filePath = path.join(dir, entry.name);
|
|
213
|
+
if (entry.isDirectory()) {
|
|
214
|
+
pruneAutoLearnSessionFiles(filePath, activeSessionIds, now, retentionMs, result);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (!entry.isFile() || !entry.name.endsWith(".jsonl"))
|
|
218
|
+
continue;
|
|
219
|
+
let shouldPrune = false;
|
|
220
|
+
try {
|
|
221
|
+
shouldPrune = isOldAutoLearnArtifact(filePath, now, retentionMs);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
result.errors++;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (!shouldPrune)
|
|
228
|
+
continue;
|
|
229
|
+
const sessionId = readAutoLearnSessionIdFromFile(filePath) ?? getAutoLearnSessionIdFromFileName(entry.name);
|
|
230
|
+
if (!sessionId || !isAutoLearnSessionId(sessionId) || activeSessionIds.has(sessionId))
|
|
231
|
+
continue;
|
|
232
|
+
removeOldAutoLearnArtifact(filePath, result, "sessionFiles");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
export function pruneAutoLearnConversationHistory(options) {
|
|
236
|
+
const result = { promptFiles: 0, logFiles: 0, sessionFiles: 0, errors: 0 };
|
|
237
|
+
const dataDir = path.resolve(options.dataDir);
|
|
238
|
+
const now = options.now ?? Date.now();
|
|
239
|
+
const retentionMs = options.retentionMs ?? AUTO_LEARN_HISTORY_RETENTION_MS;
|
|
240
|
+
const activeRunIds = definedStringSet(options.activeRunIds);
|
|
241
|
+
const activeSessionIds = definedStringSet(options.activeSessionIds);
|
|
242
|
+
if (retentionMs <= 0 || !fs.existsSync(dataDir))
|
|
243
|
+
return result;
|
|
244
|
+
let entries;
|
|
245
|
+
try {
|
|
246
|
+
entries = fs.readdirSync(dataDir, { withFileTypes: true });
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
result.errors++;
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
for (const entry of entries) {
|
|
253
|
+
if (!entry.isFile())
|
|
254
|
+
continue;
|
|
255
|
+
const promptRunId = entry.name.endsWith(".prompt.md") ? entry.name.slice(0, -".prompt.md".length) : undefined;
|
|
256
|
+
const logRunId = entry.name.endsWith(".log") ? entry.name.slice(0, -".log".length) : undefined;
|
|
257
|
+
const runId = promptRunId ?? logRunId;
|
|
258
|
+
if (!runId || activeRunIds.has(runId))
|
|
259
|
+
continue;
|
|
260
|
+
const filePath = path.join(dataDir, entry.name);
|
|
261
|
+
let shouldPrune = false;
|
|
262
|
+
try {
|
|
263
|
+
shouldPrune = isOldAutoLearnArtifact(filePath, now, retentionMs);
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
result.errors++;
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (!shouldPrune)
|
|
270
|
+
continue;
|
|
271
|
+
removeOldAutoLearnArtifact(filePath, result, promptRunId ? "promptFiles" : "logFiles");
|
|
272
|
+
}
|
|
273
|
+
pruneAutoLearnSessionFiles(path.join(dataDir, "sessions"), activeSessionIds, now, retentionMs, result);
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
export function buildAutoLearnSpawnArgs(spawnTarget, options) {
|
|
277
|
+
return [
|
|
278
|
+
...spawnTarget.argsPrefix,
|
|
279
|
+
"--print",
|
|
280
|
+
"--name",
|
|
281
|
+
options.name,
|
|
282
|
+
"--model",
|
|
283
|
+
options.modelPattern,
|
|
284
|
+
"--session-dir",
|
|
285
|
+
options.sessionDir,
|
|
286
|
+
"--session-id",
|
|
287
|
+
options.sessionId,
|
|
288
|
+
`@${options.promptPath}`,
|
|
289
|
+
];
|
|
290
|
+
}
|
|
291
|
+
export function findAutoLearnSpawnNullByteInput(command, args) {
|
|
292
|
+
if (command.includes("\0"))
|
|
293
|
+
return "command";
|
|
294
|
+
const argIndex = args.findIndex((arg) => arg.includes("\0"));
|
|
295
|
+
return argIndex === -1 ? undefined : `args[${argIndex}]`;
|
|
296
|
+
}
|
|
148
297
|
function isAnthropicSubscriptionAuthKey(apiKey) {
|
|
149
298
|
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
|
|
150
299
|
}
|
|
@@ -2110,7 +2259,6 @@ export class InteractiveMode {
|
|
|
2110
2259
|
try {
|
|
2111
2260
|
const image = await readClipboardImage();
|
|
2112
2261
|
if (!image) {
|
|
2113
|
-
this.showStatus("No image found on the clipboard");
|
|
2114
2262
|
return;
|
|
2115
2263
|
}
|
|
2116
2264
|
const label = this.nextClipboardImageLabel();
|
|
@@ -2241,13 +2389,13 @@ export class InteractiveMode {
|
|
|
2241
2389
|
this.editor.setText("");
|
|
2242
2390
|
return;
|
|
2243
2391
|
}
|
|
2244
|
-
if (text === "/login") {
|
|
2245
|
-
this.showOAuthSelector("login");
|
|
2392
|
+
if (text === "/login" || text.startsWith("/login ")) {
|
|
2393
|
+
await this.showOAuthSelector("login", text.slice("/login".length).trim() || undefined);
|
|
2246
2394
|
this.editor.setText("");
|
|
2247
2395
|
return;
|
|
2248
2396
|
}
|
|
2249
|
-
if (text === "/logout") {
|
|
2250
|
-
this.showOAuthSelector("logout");
|
|
2397
|
+
if (text === "/logout" || text.startsWith("/logout ")) {
|
|
2398
|
+
await this.showOAuthSelector("logout", text.slice("/logout".length).trim() || undefined);
|
|
2251
2399
|
this.editor.setText("");
|
|
2252
2400
|
return;
|
|
2253
2401
|
}
|
|
@@ -3473,6 +3621,14 @@ export class InteractiveMode {
|
|
|
3473
3621
|
getAutoLearnStatePath() {
|
|
3474
3622
|
return path.join(this.getAutoLearnDataDir(), "state.json");
|
|
3475
3623
|
}
|
|
3624
|
+
ensureAutoLearnStateFile() {
|
|
3625
|
+
const dir = this.getAutoLearnDataDir();
|
|
3626
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
3627
|
+
const statePath = this.getAutoLearnStatePath();
|
|
3628
|
+
if (!fs.existsSync(statePath)) {
|
|
3629
|
+
fs.writeFileSync(statePath, "{}\n", "utf-8");
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3476
3632
|
readAutoLearnState() {
|
|
3477
3633
|
try {
|
|
3478
3634
|
const statePath = this.getAutoLearnStatePath();
|
|
@@ -3489,6 +3645,52 @@ export class InteractiveMode {
|
|
|
3489
3645
|
fs.mkdirSync(dir, { recursive: true });
|
|
3490
3646
|
fs.writeFileSync(this.getAutoLearnStatePath(), `${JSON.stringify(state, null, 2)}\n`, "utf-8");
|
|
3491
3647
|
}
|
|
3648
|
+
acquireAutoLearnStateLock() {
|
|
3649
|
+
this.ensureAutoLearnStateFile();
|
|
3650
|
+
const statePath = this.getAutoLearnStatePath();
|
|
3651
|
+
const maxAttempts = 20;
|
|
3652
|
+
const delayMs = 25;
|
|
3653
|
+
let lastError;
|
|
3654
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
3655
|
+
try {
|
|
3656
|
+
return lockfile.lockSync(statePath, { realpath: false, stale: 30000 });
|
|
3657
|
+
}
|
|
3658
|
+
catch (error) {
|
|
3659
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
3660
|
+
if (code !== "ELOCKED" || attempt === maxAttempts) {
|
|
3661
|
+
throw error;
|
|
3662
|
+
}
|
|
3663
|
+
lastError = error;
|
|
3664
|
+
const start = Date.now();
|
|
3665
|
+
while (Date.now() - start < delayMs) {
|
|
3666
|
+
// Synchronous callers need a synchronous lock retry loop.
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
throw lastError instanceof Error ? lastError : new Error("Failed to acquire Auto Learn state lock");
|
|
3671
|
+
}
|
|
3672
|
+
withAutoLearnStateLock(fn) {
|
|
3673
|
+
let release;
|
|
3674
|
+
try {
|
|
3675
|
+
release = this.acquireAutoLearnStateLock();
|
|
3676
|
+
const { result, next } = fn(this.readAutoLearnState());
|
|
3677
|
+
if (next !== undefined) {
|
|
3678
|
+
this.writeAutoLearnState(next);
|
|
3679
|
+
}
|
|
3680
|
+
return result;
|
|
3681
|
+
}
|
|
3682
|
+
finally {
|
|
3683
|
+
release?.();
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
appendAutoLearnLog(logPath, message) {
|
|
3687
|
+
try {
|
|
3688
|
+
fs.appendFileSync(logPath, `${message}\n`, "utf-8");
|
|
3689
|
+
}
|
|
3690
|
+
catch {
|
|
3691
|
+
// Logging must never turn a background learner startup failure into an interactive crash.
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3492
3694
|
isAutoLearnPidAlive(pid) {
|
|
3493
3695
|
if (typeof pid !== "number" || pid <= 0)
|
|
3494
3696
|
return false;
|
|
@@ -3504,12 +3706,35 @@ export class InteractiveMode {
|
|
|
3504
3706
|
pruneAutoLearnState(state, now = Date.now()) {
|
|
3505
3707
|
const runs = { ...(state.runs ?? {}) };
|
|
3506
3708
|
for (const [id, run] of Object.entries(runs)) {
|
|
3507
|
-
if (run.expiresAt <= now
|
|
3709
|
+
if (run.expiresAt <= now) {
|
|
3710
|
+
delete runs[id];
|
|
3711
|
+
continue;
|
|
3712
|
+
}
|
|
3713
|
+
if (run.status === "reserved" && run.pid === undefined) {
|
|
3714
|
+
continue;
|
|
3715
|
+
}
|
|
3716
|
+
if (!this.isAutoLearnPidAlive(run.pid)) {
|
|
3508
3717
|
delete runs[id];
|
|
3509
3718
|
}
|
|
3510
3719
|
}
|
|
3511
3720
|
return { ...state, runs };
|
|
3512
3721
|
}
|
|
3722
|
+
pruneAutoLearnHistoryFromState(state, now = Date.now()) {
|
|
3723
|
+
const prunedState = this.pruneAutoLearnState(state, now);
|
|
3724
|
+
pruneAutoLearnConversationHistory({
|
|
3725
|
+
dataDir: this.getAutoLearnDataDir(),
|
|
3726
|
+
now,
|
|
3727
|
+
activeRunIds: Object.keys(prunedState.runs ?? {}),
|
|
3728
|
+
activeSessionIds: Object.values(prunedState.runs ?? {}).map((run) => run.sessionId),
|
|
3729
|
+
});
|
|
3730
|
+
return prunedState;
|
|
3731
|
+
}
|
|
3732
|
+
getPrunedAutoLearnState() {
|
|
3733
|
+
return this.withAutoLearnStateLock((current) => {
|
|
3734
|
+
const state = this.pruneAutoLearnHistoryFromState(current);
|
|
3735
|
+
return { result: state, next: state };
|
|
3736
|
+
});
|
|
3737
|
+
}
|
|
3513
3738
|
getAutoLearnPresetForAutonomyMode(mode, current = {}) {
|
|
3514
3739
|
const preset = AUTONOMY_AUTO_LEARN_PRESETS[mode] ?? AUTONOMY_AUTO_LEARN_PRESETS.off;
|
|
3515
3740
|
return { ...preset, model: current.model?.trim() || preset.model };
|
|
@@ -3537,64 +3762,7 @@ export class InteractiveMode {
|
|
|
3537
3762
|
getAutoLearnMessageCount() {
|
|
3538
3763
|
return this.sessionManager.getBranch().filter((entry) => entry.type === "message").length;
|
|
3539
3764
|
}
|
|
3540
|
-
|
|
3541
|
-
if (settings.model === "active") {
|
|
3542
|
-
return this.session.model ? `${this.session.model.provider}/${this.session.model.id}` : undefined;
|
|
3543
|
-
}
|
|
3544
|
-
return settings.model;
|
|
3545
|
-
}
|
|
3546
|
-
getAutoLearnSpawnTarget() {
|
|
3547
|
-
const overridePath = process.env.PI_AUTO_LEARN_CLI_PATH?.trim();
|
|
3548
|
-
if (overridePath) {
|
|
3549
|
-
return { command: overridePath, argsPrefix: [] };
|
|
3550
|
-
}
|
|
3551
|
-
const execBase = path.basename(process.execPath).toLowerCase();
|
|
3552
|
-
const isScriptRuntime = execBase === "node" || execBase === "node.exe" || execBase === "bun" || execBase === "bun.exe";
|
|
3553
|
-
if (!isScriptRuntime) {
|
|
3554
|
-
return { command: process.execPath, argsPrefix: [] };
|
|
3555
|
-
}
|
|
3556
|
-
const cliPath = process.argv[1];
|
|
3557
|
-
if (!cliPath || cliPath.startsWith("-")) {
|
|
3558
|
-
return undefined;
|
|
3559
|
-
}
|
|
3560
|
-
return { command: process.execPath, argsPrefix: [cliPath] };
|
|
3561
|
-
}
|
|
3562
|
-
validateAutoLearnModelValue(value) {
|
|
3563
|
-
const modelValue = value?.trim();
|
|
3564
|
-
if (!modelValue || modelValue === "active")
|
|
3565
|
-
return undefined;
|
|
3566
|
-
const available = this.session.modelRegistry.getAvailable();
|
|
3567
|
-
if (modelValue.includes("/")) {
|
|
3568
|
-
const [provider, modelId] = modelValue.split("/", 2);
|
|
3569
|
-
if (available.some((model) => model.provider === provider && model.id === modelId))
|
|
3570
|
-
return undefined;
|
|
3571
|
-
return `Auto Learn model "${modelValue}" is not in configured subscription/API models; saved as manual/unverified.`;
|
|
3572
|
-
}
|
|
3573
|
-
if (available.some((model) => model.id === modelValue))
|
|
3574
|
-
return undefined;
|
|
3575
|
-
return `Auto Learn model "${modelValue}" is not in configured subscription/API models; saved as manual/unverified.`;
|
|
3576
|
-
}
|
|
3577
|
-
validateSelfModificationSource(settings) {
|
|
3578
|
-
if (!settings.enabled)
|
|
3579
|
-
return undefined;
|
|
3580
|
-
const rawPath = settings.sourcePath?.trim();
|
|
3581
|
-
if (!rawPath)
|
|
3582
|
-
return "Self modification is enabled, but no pi-adaptative source path is set.";
|
|
3583
|
-
const sourcePath = resolvePath(rawPath, this.sessionManager.getCwd(), { trim: true });
|
|
3584
|
-
if (!fs.existsSync(sourcePath))
|
|
3585
|
-
return `Self modification source path does not exist: ${sourcePath}`;
|
|
3586
|
-
if (!fs.existsSync(path.join(sourcePath, "package.json"))) {
|
|
3587
|
-
return `Self modification source path has no package.json: ${sourcePath}`;
|
|
3588
|
-
}
|
|
3589
|
-
if (!fs.existsSync(path.join(sourcePath, "packages", "coding-agent"))) {
|
|
3590
|
-
return `Self modification source path does not look like pi-adaptative (missing packages/coding-agent): ${sourcePath}`;
|
|
3591
|
-
}
|
|
3592
|
-
return undefined;
|
|
3593
|
-
}
|
|
3594
|
-
evaluateAutoLearn(force = false) {
|
|
3595
|
-
const settings = this.getEffectiveAutoLearnSettings();
|
|
3596
|
-
const state = this.pruneAutoLearnState(this.readAutoLearnState());
|
|
3597
|
-
this.writeAutoLearnState(state);
|
|
3765
|
+
buildAutoLearnDecisionFromState(state, settings, force = false) {
|
|
3598
3766
|
const now = Date.now();
|
|
3599
3767
|
const tenant = this.getAutoLearnTenantKey();
|
|
3600
3768
|
const runningCount = Object.keys(state.runs ?? {}).length;
|
|
@@ -3665,6 +3833,67 @@ export class InteractiveMode {
|
|
|
3665
3833
|
runningCount,
|
|
3666
3834
|
};
|
|
3667
3835
|
}
|
|
3836
|
+
resolveAutoLearnModelPattern(settings) {
|
|
3837
|
+
if (settings.model === "active") {
|
|
3838
|
+
return this.session.model ? `${this.session.model.provider}/${this.session.model.id}` : undefined;
|
|
3839
|
+
}
|
|
3840
|
+
return settings.model;
|
|
3841
|
+
}
|
|
3842
|
+
getAutoLearnSpawnTarget() {
|
|
3843
|
+
const overridePath = process.env.PI_AUTO_LEARN_CLI_PATH?.trim();
|
|
3844
|
+
if (overridePath) {
|
|
3845
|
+
return { command: overridePath, argsPrefix: [] };
|
|
3846
|
+
}
|
|
3847
|
+
const execBase = path.basename(process.execPath).toLowerCase();
|
|
3848
|
+
const isScriptRuntime = execBase === "node" || execBase === "node.exe" || execBase === "bun" || execBase === "bun.exe";
|
|
3849
|
+
if (!isScriptRuntime) {
|
|
3850
|
+
return { command: process.execPath, argsPrefix: [] };
|
|
3851
|
+
}
|
|
3852
|
+
const cliPath = process.argv[1];
|
|
3853
|
+
if (!cliPath || cliPath.startsWith("-")) {
|
|
3854
|
+
return undefined;
|
|
3855
|
+
}
|
|
3856
|
+
return { command: process.execPath, argsPrefix: [cliPath] };
|
|
3857
|
+
}
|
|
3858
|
+
validateAutoLearnModelValue(value) {
|
|
3859
|
+
const modelValue = value?.trim();
|
|
3860
|
+
if (!modelValue || modelValue === "active")
|
|
3861
|
+
return undefined;
|
|
3862
|
+
const available = this.session.modelRegistry.getAvailable();
|
|
3863
|
+
if (modelValue.includes("/")) {
|
|
3864
|
+
const [provider, modelId] = modelValue.split("/", 2);
|
|
3865
|
+
if (available.some((model) => model.provider === provider && model.id === modelId))
|
|
3866
|
+
return undefined;
|
|
3867
|
+
return `Auto Learn model "${modelValue}" is not in configured subscription/API models; saved as manual/unverified.`;
|
|
3868
|
+
}
|
|
3869
|
+
if (available.some((model) => model.id === modelValue))
|
|
3870
|
+
return undefined;
|
|
3871
|
+
return `Auto Learn model "${modelValue}" is not in configured subscription/API models; saved as manual/unverified.`;
|
|
3872
|
+
}
|
|
3873
|
+
validateSelfModificationSource(settings) {
|
|
3874
|
+
if (!settings.enabled)
|
|
3875
|
+
return undefined;
|
|
3876
|
+
const rawPath = settings.sourcePath?.trim();
|
|
3877
|
+
if (!rawPath)
|
|
3878
|
+
return "Self modification is enabled, but no pi-adaptative source path is set.";
|
|
3879
|
+
const sourcePath = resolvePath(rawPath, this.sessionManager.getCwd(), { trim: true });
|
|
3880
|
+
if (!fs.existsSync(sourcePath))
|
|
3881
|
+
return `Self modification source path does not exist: ${sourcePath}`;
|
|
3882
|
+
if (!fs.existsSync(path.join(sourcePath, "package.json"))) {
|
|
3883
|
+
return `Self modification source path has no package.json: ${sourcePath}`;
|
|
3884
|
+
}
|
|
3885
|
+
if (!fs.existsSync(path.join(sourcePath, "packages", "coding-agent"))) {
|
|
3886
|
+
return `Self modification source path does not look like pi-adaptative (missing packages/coding-agent): ${sourcePath}`;
|
|
3887
|
+
}
|
|
3888
|
+
return undefined;
|
|
3889
|
+
}
|
|
3890
|
+
evaluateAutoLearn(force = false) {
|
|
3891
|
+
const settings = this.getEffectiveAutoLearnSettings();
|
|
3892
|
+
return this.withAutoLearnStateLock((current) => {
|
|
3893
|
+
const state = this.pruneAutoLearnHistoryFromState(current);
|
|
3894
|
+
return { result: this.buildAutoLearnDecisionFromState(state, settings, force), next: state };
|
|
3895
|
+
});
|
|
3896
|
+
}
|
|
3668
3897
|
buildAutonomyAuthorityPrompt() {
|
|
3669
3898
|
const autonomy = this.settingsManager.getAutonomySettings();
|
|
3670
3899
|
const selfModification = this.settingsManager.getSelfModificationSettings();
|
|
@@ -3699,12 +3928,98 @@ export class InteractiveMode {
|
|
|
3699
3928
|
: "run one bounded continuous-learning pass for this Pi tenant";
|
|
3700
3929
|
return `You are Pi Auto Learn running as a background learner.\n\nObjective: ${objective}.\nTrigger: ${reason}.\n\n${authorityBlock}\n\nRequired workflow:\n1. Query existing durable memory/rules first when tools allow it.\n2. Run the available Auto Learn tooling, preferably learning_run_auto, with applyHighConfidence=${settings.applyHighConfidence}.\n3. Treat the latest-turn digest as current-session evidence only; do not auto-commit one-off cues unless deterministic tooling corroborates them.\n4. In mode=full, apply safe memory/skill/user-extension/authorized-source improvements under the standing grant above; otherwise keep them proposal-gated.\n5. Never cross hard-stop boundaries from the authority policy.\n6. If the learning tools are unavailable, report BLOCKED with the missing tool names and do not improvise.\n7. Finish with PASS, BLOCKED, or FAIL and concise evidence.${reflectionBlock}`;
|
|
3701
3930
|
}
|
|
3931
|
+
reserveAutoLearnRun(params) {
|
|
3932
|
+
return this.withAutoLearnStateLock((current) => {
|
|
3933
|
+
const now = Date.now();
|
|
3934
|
+
const state = this.pruneAutoLearnHistoryFromState(current, now);
|
|
3935
|
+
const tenant = this.getAutoLearnTenantKey();
|
|
3936
|
+
if (params.cooldownKind === "reflection") {
|
|
3937
|
+
const lastReflection = state.lastReflectionByTenant?.[tenant] ?? 0;
|
|
3938
|
+
const cooldownMs = params.settings.reflectionCooldownMinutes * 60 * 1000;
|
|
3939
|
+
if (Math.max(0, lastReflection + cooldownMs - now) > 0) {
|
|
3940
|
+
return { result: { ok: false, reason: "reflection cooldown" }, next: state };
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
const decision = this.buildAutoLearnDecisionFromState(state, params.settings, params.force);
|
|
3944
|
+
if (!decision.shouldRun) {
|
|
3945
|
+
return { result: { ok: false, reason: decision.reason }, next: state };
|
|
3946
|
+
}
|
|
3947
|
+
const run = {
|
|
3948
|
+
tenant,
|
|
3949
|
+
model: params.modelPattern,
|
|
3950
|
+
reason: params.reason,
|
|
3951
|
+
startedAt: now,
|
|
3952
|
+
expiresAt: now + AUTO_LEARN_RESERVATION_MS,
|
|
3953
|
+
cwd: this.sessionManager.getCwd(),
|
|
3954
|
+
logPath: params.logPath,
|
|
3955
|
+
sessionDir: params.sessionDir,
|
|
3956
|
+
sessionId: params.sessionId,
|
|
3957
|
+
promptPath: params.promptPath,
|
|
3958
|
+
kind: params.kind,
|
|
3959
|
+
autonomyMode: this.settingsManager.getAutonomySettings().mode,
|
|
3960
|
+
authority: this.settingsManager.getAutonomySettings().mode === "full"
|
|
3961
|
+
? "standing-full-autonomous"
|
|
3962
|
+
: "proposal-gated",
|
|
3963
|
+
status: "reserved",
|
|
3964
|
+
};
|
|
3965
|
+
const next = {
|
|
3966
|
+
...state,
|
|
3967
|
+
runs: { ...(state.runs ?? {}), [params.runId]: run },
|
|
3968
|
+
};
|
|
3969
|
+
if (params.cooldownKind === "reflection") {
|
|
3970
|
+
next.lastReflectionByTenant = { ...(state.lastReflectionByTenant ?? {}), [tenant]: now };
|
|
3971
|
+
}
|
|
3972
|
+
else {
|
|
3973
|
+
next.lastLaunchByTenant = { ...(state.lastLaunchByTenant ?? {}), [tenant]: now };
|
|
3974
|
+
}
|
|
3975
|
+
return { result: { ok: true, reservation: { runId: params.runId, startedAt: now } }, next };
|
|
3976
|
+
});
|
|
3977
|
+
}
|
|
3978
|
+
releaseAutoLearnReservation(reservation, cooldownKind) {
|
|
3979
|
+
this.withAutoLearnStateLock((current) => {
|
|
3980
|
+
const state = this.pruneAutoLearnHistoryFromState(current);
|
|
3981
|
+
const tenant = this.getAutoLearnTenantKey();
|
|
3982
|
+
const runs = { ...(state.runs ?? {}) };
|
|
3983
|
+
delete runs[reservation.runId];
|
|
3984
|
+
const next = { ...state, runs };
|
|
3985
|
+
if (cooldownKind === "reflection" && next.lastReflectionByTenant?.[tenant] === reservation.startedAt) {
|
|
3986
|
+
next.lastReflectionByTenant = { ...next.lastReflectionByTenant };
|
|
3987
|
+
delete next.lastReflectionByTenant[tenant];
|
|
3988
|
+
}
|
|
3989
|
+
else if (cooldownKind !== "reflection" && next.lastLaunchByTenant?.[tenant] === reservation.startedAt) {
|
|
3990
|
+
next.lastLaunchByTenant = { ...next.lastLaunchByTenant };
|
|
3991
|
+
delete next.lastLaunchByTenant[tenant];
|
|
3992
|
+
}
|
|
3993
|
+
return { result: undefined, next };
|
|
3994
|
+
});
|
|
3995
|
+
}
|
|
3996
|
+
markAutoLearnReservationRunning(reservation, pid, settings) {
|
|
3997
|
+
this.withAutoLearnStateLock((current) => {
|
|
3998
|
+
const now = Date.now();
|
|
3999
|
+
const state = this.pruneAutoLearnHistoryFromState(current, now);
|
|
4000
|
+
const run = state.runs?.[reservation.runId];
|
|
4001
|
+
if (!run) {
|
|
4002
|
+
return { result: undefined, next: state };
|
|
4003
|
+
}
|
|
4004
|
+
return {
|
|
4005
|
+
result: undefined,
|
|
4006
|
+
next: {
|
|
4007
|
+
...state,
|
|
4008
|
+
runs: {
|
|
4009
|
+
...(state.runs ?? {}),
|
|
4010
|
+
[reservation.runId]: {
|
|
4011
|
+
...run,
|
|
4012
|
+
pid,
|
|
4013
|
+
expiresAt: now + settings.leaseMinutes * 60 * 1000,
|
|
4014
|
+
status: "running",
|
|
4015
|
+
},
|
|
4016
|
+
},
|
|
4017
|
+
},
|
|
4018
|
+
};
|
|
4019
|
+
});
|
|
4020
|
+
}
|
|
3702
4021
|
launchAutoLearn(reason, force = false, options = {}) {
|
|
3703
4022
|
const settings = this.getEffectiveAutoLearnSettings();
|
|
3704
|
-
const decision = this.evaluateAutoLearn(force);
|
|
3705
|
-
if (!decision.shouldRun) {
|
|
3706
|
-
return `Auto Learn not started: ${decision.reason}`;
|
|
3707
|
-
}
|
|
3708
4023
|
const modelPattern = this.resolveAutoLearnModelPattern(settings);
|
|
3709
4024
|
if (!modelPattern) {
|
|
3710
4025
|
return "Auto Learn not started: no active model is available for model=active.";
|
|
@@ -3718,7 +4033,6 @@ export class InteractiveMode {
|
|
|
3718
4033
|
const runId = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
|
|
3719
4034
|
const logPath = path.join(dir, `${runId}.log`);
|
|
3720
4035
|
const promptPath = path.join(dir, `${runId}.prompt.md`);
|
|
3721
|
-
const outFd = fs.openSync(logPath, "a");
|
|
3722
4036
|
const kind = options.promptKind ?? "auto";
|
|
3723
4037
|
const sessionDir = path.join(dir, "sessions");
|
|
3724
4038
|
const sessionId = `auto-learn-${kind}-${runId}`;
|
|
@@ -3727,61 +4041,96 @@ export class InteractiveMode {
|
|
|
3727
4041
|
kind,
|
|
3728
4042
|
turnDigest: options.turnDigest,
|
|
3729
4043
|
});
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
...spawnTarget.argsPrefix,
|
|
3733
|
-
"--print",
|
|
3734
|
-
"--name",
|
|
3735
|
-
`Auto Learn ${runId}`,
|
|
3736
|
-
"--model",
|
|
4044
|
+
const args = buildAutoLearnSpawnArgs(spawnTarget, {
|
|
4045
|
+
name: `Auto Learn ${runId}`,
|
|
3737
4046
|
modelPattern,
|
|
3738
|
-
"--session-dir",
|
|
3739
4047
|
sessionDir,
|
|
3740
|
-
"--session-id",
|
|
3741
4048
|
sessionId,
|
|
3742
|
-
|
|
3743
|
-
];
|
|
3744
|
-
const child = spawn(spawnTarget.command, args, {
|
|
3745
|
-
cwd: this.sessionManager.getCwd(),
|
|
3746
|
-
detached: true,
|
|
3747
|
-
stdio: ["ignore", outFd, outFd],
|
|
3748
|
-
env: { ...process.env, PI_AUTO_LEARN_CHILD: "1" },
|
|
4049
|
+
promptPath,
|
|
3749
4050
|
});
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
[this.getAutoLearnTenantKey()]: now,
|
|
3758
|
-
};
|
|
4051
|
+
const invalidSpawnInput = findAutoLearnSpawnNullByteInput(spawnTarget.command, args);
|
|
4052
|
+
if (invalidSpawnInput) {
|
|
4053
|
+
const message = `Auto Learn not started: ${invalidSpawnInput} contains a null byte.`;
|
|
4054
|
+
this.appendAutoLearnLog(logPath, message);
|
|
4055
|
+
this.autoLearnLastStatus = "start failed";
|
|
4056
|
+
this.updateAutoLearnFooter();
|
|
4057
|
+
return `${message} Log: ${logPath}`;
|
|
3759
4058
|
}
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
4059
|
+
const reservationResult = this.reserveAutoLearnRun({
|
|
4060
|
+
settings,
|
|
4061
|
+
force,
|
|
4062
|
+
cooldownKind: options.cooldownKind,
|
|
4063
|
+
runId,
|
|
4064
|
+
modelPattern,
|
|
4065
|
+
reason,
|
|
4066
|
+
logPath,
|
|
4067
|
+
sessionDir,
|
|
4068
|
+
sessionId,
|
|
4069
|
+
promptPath,
|
|
4070
|
+
kind,
|
|
4071
|
+
});
|
|
4072
|
+
if (!reservationResult.ok) {
|
|
4073
|
+
return `Auto Learn not started: ${reservationResult.reason}`;
|
|
4074
|
+
}
|
|
4075
|
+
const { reservation } = reservationResult;
|
|
4076
|
+
try {
|
|
4077
|
+
fs.writeFileSync(promptPath, prompt, "utf-8");
|
|
4078
|
+
}
|
|
4079
|
+
catch (error) {
|
|
4080
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4081
|
+
this.releaseAutoLearnReservation(reservation, options.cooldownKind);
|
|
4082
|
+
this.appendAutoLearnLog(logPath, `Auto Learn failed to write prompt file: ${message}`);
|
|
4083
|
+
this.autoLearnLastStatus = "start failed";
|
|
4084
|
+
this.updateAutoLearnFooter();
|
|
4085
|
+
return `Auto Learn not started: failed to write prompt file (${message}). Log: ${logPath}`;
|
|
4086
|
+
}
|
|
4087
|
+
let child;
|
|
4088
|
+
let outFd;
|
|
4089
|
+
try {
|
|
4090
|
+
outFd = fs.openSync(logPath, "a");
|
|
4091
|
+
const sourceSessionFile = this.sessionManager.getSessionFile();
|
|
4092
|
+
child = spawn(spawnTarget.command, args, {
|
|
3772
4093
|
cwd: this.sessionManager.getCwd(),
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
4094
|
+
detached: true,
|
|
4095
|
+
stdio: ["ignore", outFd, outFd],
|
|
4096
|
+
env: {
|
|
4097
|
+
...process.env,
|
|
4098
|
+
PI_AUTO_LEARN_CHILD: "1",
|
|
4099
|
+
...(sourceSessionFile ? { PI_AUTO_LEARN_SOURCE_SESSION_FILE: sourceSessionFile } : {}),
|
|
4100
|
+
},
|
|
4101
|
+
});
|
|
4102
|
+
child.once("error", (error) => {
|
|
4103
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4104
|
+
this.appendAutoLearnLog(logPath, `Auto Learn failed to start: ${message}`);
|
|
4105
|
+
});
|
|
4106
|
+
}
|
|
4107
|
+
catch (error) {
|
|
4108
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4109
|
+
this.releaseAutoLearnReservation(reservation, options.cooldownKind);
|
|
4110
|
+
this.appendAutoLearnLog(logPath, `Auto Learn failed to start: ${message}`);
|
|
4111
|
+
this.autoLearnLastStatus = "start failed";
|
|
4112
|
+
this.updateAutoLearnFooter();
|
|
4113
|
+
return `Auto Learn not started: failed to spawn background learner (${message}). Log: ${logPath}`;
|
|
4114
|
+
}
|
|
4115
|
+
finally {
|
|
4116
|
+
if (outFd !== undefined) {
|
|
4117
|
+
try {
|
|
4118
|
+
fs.closeSync(outFd);
|
|
4119
|
+
}
|
|
4120
|
+
catch {
|
|
4121
|
+
// The child has already been spawned or startup has already failed; ignore close errors here.
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
if (!child || typeof child.pid !== "number" || child.pid <= 0) {
|
|
4126
|
+
this.releaseAutoLearnReservation(reservation, options.cooldownKind);
|
|
4127
|
+
this.autoLearnLastStatus = "start failed";
|
|
4128
|
+
this.updateAutoLearnFooter();
|
|
4129
|
+
return `Auto Learn not started: failed to spawn background learner. Log: ${logPath}`;
|
|
4130
|
+
}
|
|
4131
|
+
const childPid = child.pid;
|
|
4132
|
+
child.unref();
|
|
4133
|
+
this.markAutoLearnReservationRunning(reservation, childPid, settings);
|
|
3785
4134
|
this.autoLearnLastStatus = `running ${modelPattern}`;
|
|
3786
4135
|
this.updateAutoLearnFooter();
|
|
3787
4136
|
return `Auto Learn started (${reason}) with ${modelPattern}. Log: ${logPath}`;
|
|
@@ -3857,8 +4206,10 @@ export class InteractiveMode {
|
|
|
3857
4206
|
evaluateAutonomyReview(messages) {
|
|
3858
4207
|
const settings = this.getEffectiveAutoLearnSettings();
|
|
3859
4208
|
const autonomy = this.settingsManager.getAutonomySettings();
|
|
3860
|
-
const state = this.
|
|
3861
|
-
|
|
4209
|
+
const state = this.withAutoLearnStateLock((current) => {
|
|
4210
|
+
const pruned = this.pruneAutoLearnHistoryFromState(current);
|
|
4211
|
+
return { result: pruned, next: pruned };
|
|
4212
|
+
});
|
|
3862
4213
|
const now = Date.now();
|
|
3863
4214
|
const tenant = this.getAutoLearnTenantKey();
|
|
3864
4215
|
const runningCount = Object.keys(state.runs ?? {}).length;
|
|
@@ -3953,7 +4304,7 @@ export class InteractiveMode {
|
|
|
3953
4304
|
formatAutoLearnStatus() {
|
|
3954
4305
|
const settings = this.getEffectiveAutoLearnSettings();
|
|
3955
4306
|
const decision = this.evaluateAutoLearn(false);
|
|
3956
|
-
const state = this.
|
|
4307
|
+
const state = this.getPrunedAutoLearnState();
|
|
3957
4308
|
const runs = Object.entries(state.runs ?? {});
|
|
3958
4309
|
const contextText = decision.contextPercent === null ? "unknown" : `${decision.contextPercent.toFixed(1)}%`;
|
|
3959
4310
|
const cooldownText = decision.cooldownRemainingMs > 0 ? `${Math.ceil(decision.cooldownRemainingMs / 60000)}m remaining` : "ready";
|
|
@@ -3967,7 +4318,7 @@ export class InteractiveMode {
|
|
|
3967
4318
|
.filter(Boolean)
|
|
3968
4319
|
.join(", ");
|
|
3969
4320
|
const sessionText = session ? `, ${session}` : "";
|
|
3970
|
-
return `- ${id}: ${run.model}, kind=${run.kind ?? "auto"}, authority=${run.authority ?? "unknown"}, pid=${run.pid ?? "?"}${sessionText}, log=${run.logPath}`;
|
|
4321
|
+
return `- ${id}: ${run.model}, kind=${run.kind ?? "auto"}, status=${run.status ?? "running"}, authority=${run.authority ?? "unknown"}, pid=${run.pid ?? "?"}${sessionText}, log=${run.logPath}`;
|
|
3971
4322
|
})
|
|
3972
4323
|
.join("\n")
|
|
3973
4324
|
: "- none";
|
|
@@ -3982,12 +4333,12 @@ export class InteractiveMode {
|
|
|
3982
4333
|
const reflectionLast = state.lastReflectionByTenant?.[this.getAutoLearnTenantKey()] ?? 0;
|
|
3983
4334
|
const reflectionCooldownRemainingMs = Math.max(0, reflectionLast + settings.reflectionCooldownMinutes * 60 * 1000 - Date.now());
|
|
3984
4335
|
const reflectionCooldownText = reflectionCooldownRemainingMs > 0 ? `${Math.ceil(reflectionCooldownRemainingMs / 60000)}m remaining` : "ready";
|
|
3985
|
-
return `Auto Learn status\nEnabled: ${settings.enabled}\nModel: ${settings.model}\nNext decision: ${decision.shouldRun ? "ready" : decision.reason}\nMessages: ${decision.messageCount}/${settings.longSessionMessages}\nContext: ${contextText}/${settings.longSessionContextPercent}%\nCooldown: ${cooldownText}\nReflection review: ${settings.reflectionReview ? "enabled" : "disabled"} (tool trigger ${settings.reflectionMinToolCalls}, cooldown ${reflectionCooldownText})\nRunning leases: ${runs.length}/${settings.maxConcurrentLearners}\nPi auto-reload blockers: ${reloadBlockers.pending ? reloadBlockers.reason : "none"}\n${reloadBlockerLines}\nRuns:\n${runLines}`;
|
|
4336
|
+
return `Auto Learn status\nEnabled: ${settings.enabled}\nModel: ${settings.model}\nNext decision: ${decision.shouldRun ? "ready" : decision.reason}\nMessages: ${decision.messageCount}/${settings.longSessionMessages}\nContext: ${contextText}/${settings.longSessionContextPercent}%\nCooldown: ${cooldownText}\nReflection review: ${settings.reflectionReview ? "enabled" : "disabled"} (tool trigger ${settings.reflectionMinToolCalls}, cooldown ${reflectionCooldownText})\nHistory retention: 7 days for internal Auto Learn prompts/logs/sessions\nRunning leases: ${runs.length}/${settings.maxConcurrentLearners}\nPi auto-reload blockers: ${reloadBlockers.pending ? reloadBlockers.reason : "none"}\n${reloadBlockerLines}\nRuns:\n${runLines}`;
|
|
3986
4337
|
}
|
|
3987
4338
|
formatAutonomyStatus() {
|
|
3988
4339
|
const autonomy = this.settingsManager.getAutonomySettings();
|
|
3989
4340
|
const settings = this.getEffectiveAutoLearnSettings();
|
|
3990
|
-
const autoLearnState = this.
|
|
4341
|
+
const autoLearnState = this.getPrunedAutoLearnState();
|
|
3991
4342
|
const running = Object.entries(autoLearnState.runs ?? {});
|
|
3992
4343
|
const safety = autonomy.mode === "full"
|
|
3993
4344
|
? "standing grant for memory, skills, user/project extensions, autonomy/autoLearn tuning, and authorized selfModification.sourcePath edits; hard stops still require explicit foreground approval"
|
|
@@ -4002,6 +4353,7 @@ export class InteractiveMode {
|
|
|
4002
4353
|
`Long-session trigger: ${settings.longSessionMessages} messages or ${settings.longSessionContextPercent}% context; cooldown=${settings.cooldownMinutes}m`,
|
|
4003
4354
|
reflectionLine,
|
|
4004
4355
|
`Running learners: ${running.length}/${settings.maxConcurrentLearners}`,
|
|
4356
|
+
"History retention: 7 days for internal Auto Learn prompts/logs/sessions",
|
|
4005
4357
|
`Standing authority: ${safety}`,
|
|
4006
4358
|
`Audit/log dir: ${this.getAutoLearnDataDir()}`,
|
|
4007
4359
|
"Use /autonomy off|safe|balanced|full to switch presets. Advanced overrides remain in /settings → Auto Learn Advanced.",
|
|
@@ -4669,6 +5021,35 @@ export class InteractiveMode {
|
|
|
4669
5021
|
}
|
|
4670
5022
|
return options.sort((a, b) => a.name.localeCompare(b.name));
|
|
4671
5023
|
}
|
|
5024
|
+
resolveAuthProviderOption(providerReference, providerOptions) {
|
|
5025
|
+
const normalized = providerReference.trim().toLowerCase();
|
|
5026
|
+
if (!normalized)
|
|
5027
|
+
return undefined;
|
|
5028
|
+
const exactMatch = providerOptions.find((provider) => {
|
|
5029
|
+
const id = provider.id.toLowerCase();
|
|
5030
|
+
const name = provider.name.toLowerCase();
|
|
5031
|
+
return id === normalized || name === normalized;
|
|
5032
|
+
});
|
|
5033
|
+
if (exactMatch)
|
|
5034
|
+
return exactMatch;
|
|
5035
|
+
const aliasTarget = cliProviderAliases[normalized] ?? normalized;
|
|
5036
|
+
return providerOptions.find((provider) => {
|
|
5037
|
+
const id = provider.id.toLowerCase();
|
|
5038
|
+
const name = provider.name.toLowerCase();
|
|
5039
|
+
return id === aliasTarget || name === aliasTarget;
|
|
5040
|
+
});
|
|
5041
|
+
}
|
|
5042
|
+
async startProviderLogin(providerOption) {
|
|
5043
|
+
if (providerOption.authType === "oauth") {
|
|
5044
|
+
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
5045
|
+
}
|
|
5046
|
+
else if (providerOption.id === BEDROCK_PROVIDER_ID) {
|
|
5047
|
+
this.showBedrockSetupDialog(providerOption.id, providerOption.name);
|
|
5048
|
+
}
|
|
5049
|
+
else {
|
|
5050
|
+
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
5051
|
+
}
|
|
5052
|
+
}
|
|
4672
5053
|
showLoginAuthTypeSelector() {
|
|
4673
5054
|
const subscriptionLabel = "Use a subscription";
|
|
4674
5055
|
const apiKeyLabel = "Use an API key";
|
|
@@ -4697,15 +5078,7 @@ export class InteractiveMode {
|
|
|
4697
5078
|
if (!providerOption) {
|
|
4698
5079
|
return;
|
|
4699
5080
|
}
|
|
4700
|
-
|
|
4701
|
-
await this.showLoginDialog(providerOption.id, providerOption.name);
|
|
4702
|
-
}
|
|
4703
|
-
else if (providerOption.id === BEDROCK_PROVIDER_ID) {
|
|
4704
|
-
this.showBedrockSetupDialog(providerOption.id, providerOption.name);
|
|
4705
|
-
}
|
|
4706
|
-
else {
|
|
4707
|
-
await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
|
|
4708
|
-
}
|
|
5081
|
+
await this.startProviderLogin(providerOption);
|
|
4709
5082
|
}, () => {
|
|
4710
5083
|
done();
|
|
4711
5084
|
this.showLoginAuthTypeSelector();
|
|
@@ -4713,8 +5086,18 @@ export class InteractiveMode {
|
|
|
4713
5086
|
return { component: selector, focus: selector };
|
|
4714
5087
|
});
|
|
4715
5088
|
}
|
|
4716
|
-
async showOAuthSelector(mode) {
|
|
5089
|
+
async showOAuthSelector(mode, providerReference) {
|
|
4717
5090
|
if (mode === "login") {
|
|
5091
|
+
if (providerReference) {
|
|
5092
|
+
const providerOptions = this.getLoginProviderOptions();
|
|
5093
|
+
const providerOption = this.resolveAuthProviderOption(providerReference, providerOptions);
|
|
5094
|
+
if (!providerOption) {
|
|
5095
|
+
this.showError(`Unknown login provider "${providerReference}". Use /login to select from available providers.`);
|
|
5096
|
+
return;
|
|
5097
|
+
}
|
|
5098
|
+
await this.startProviderLogin(providerOption);
|
|
5099
|
+
return;
|
|
5100
|
+
}
|
|
4718
5101
|
this.showLoginAuthTypeSelector();
|
|
4719
5102
|
return;
|
|
4720
5103
|
}
|
|
@@ -4723,6 +5106,26 @@ export class InteractiveMode {
|
|
|
4723
5106
|
this.showStatus("No stored credentials to remove. /logout only removes credentials saved by /login; environment variables and models.json config are unchanged.");
|
|
4724
5107
|
return;
|
|
4725
5108
|
}
|
|
5109
|
+
if (providerReference) {
|
|
5110
|
+
const providerOption = this.resolveAuthProviderOption(providerReference, providerOptions);
|
|
5111
|
+
if (!providerOption) {
|
|
5112
|
+
this.showError(`No stored credentials found for "${providerReference}". Use /logout to select a saved provider.`);
|
|
5113
|
+
return;
|
|
5114
|
+
}
|
|
5115
|
+
try {
|
|
5116
|
+
this.session.modelRegistry.authStorage.logout(providerOption.id);
|
|
5117
|
+
this.session.modelRegistry.refresh();
|
|
5118
|
+
await this.updateAvailableProviderCount();
|
|
5119
|
+
const message = providerOption.authType === "oauth"
|
|
5120
|
+
? `Logged out of ${providerOption.name}`
|
|
5121
|
+
: `Removed stored API key for ${providerOption.name}. Environment variables and models.json config are unchanged.`;
|
|
5122
|
+
this.showStatus(message);
|
|
5123
|
+
}
|
|
5124
|
+
catch (error) {
|
|
5125
|
+
this.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5126
|
+
}
|
|
5127
|
+
return;
|
|
5128
|
+
}
|
|
4726
5129
|
this.showSelector((done) => {
|
|
4727
5130
|
const selector = new OAuthSelectorComponent(mode, this.session.modelRegistry.authStorage, providerOptions, async (providerId) => {
|
|
4728
5131
|
done();
|