@caupulican/pi-adaptative 0.78.4 → 0.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/core/settings-manager.d.ts +2 -2
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +16 -2
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +13 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +346 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +24 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +419 -2
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/settings.md +4 -0
- 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 +15 -18
- package/package.json +4 -4
|
@@ -28,7 +28,7 @@ import { getChangelogPath, getNewEntries, parseChangelog } from "../../utils/cha
|
|
|
28
28
|
import { copyToClipboard } from "../../utils/clipboard.js";
|
|
29
29
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
30
30
|
import { parseGitUrl } from "../../utils/git.js";
|
|
31
|
-
import { getCwdRelativePath } from "../../utils/paths.js";
|
|
31
|
+
import { getCwdRelativePath, resolvePath } from "../../utils/paths.js";
|
|
32
32
|
import { getPiUserAgent } from "../../utils/pi-user-agent.js";
|
|
33
33
|
import { killTrackedDetachedChildren } from "../../utils/shell.js";
|
|
34
34
|
import { ensureTool } from "../../utils/tools-manager.js";
|
|
@@ -88,6 +88,15 @@ function isDeadTerminalError(error) {
|
|
|
88
88
|
return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
|
|
89
89
|
}
|
|
90
90
|
const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage.";
|
|
91
|
+
const AUTO_LEARN_DEFAULTS = {
|
|
92
|
+
model: "active",
|
|
93
|
+
longSessionMessages: 32,
|
|
94
|
+
longSessionContextPercent: 70,
|
|
95
|
+
cooldownMinutes: 120,
|
|
96
|
+
leaseMinutes: 90,
|
|
97
|
+
maxConcurrentLearners: 2,
|
|
98
|
+
applyHighConfidence: false,
|
|
99
|
+
};
|
|
91
100
|
function isAnthropicSubscriptionAuthKey(apiKey) {
|
|
92
101
|
return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
|
|
93
102
|
}
|
|
@@ -188,6 +197,8 @@ export class InteractiveMode {
|
|
|
188
197
|
// Auto-compaction state
|
|
189
198
|
autoCompactionLoader = undefined;
|
|
190
199
|
autoCompactionEscapeHandler;
|
|
200
|
+
// Auto Learn background runner state
|
|
201
|
+
autoLearnLastStatus = "idle";
|
|
191
202
|
// Auto-retry state
|
|
192
203
|
retryLoader = undefined;
|
|
193
204
|
retryCountdown = undefined;
|
|
@@ -504,8 +515,9 @@ export class InteractiveMode {
|
|
|
504
515
|
this.footerDataProvider.onBranchChange(() => {
|
|
505
516
|
this.ui.requestRender();
|
|
506
517
|
});
|
|
507
|
-
// Initialize available provider count for footer display
|
|
518
|
+
// Initialize available provider count and Auto Learn status for footer display
|
|
508
519
|
await this.updateAvailableProviderCount();
|
|
520
|
+
this.updateAutoLearnFooter();
|
|
509
521
|
}
|
|
510
522
|
/**
|
|
511
523
|
* Update terminal title with session name and cwd.
|
|
@@ -2050,6 +2062,11 @@ export class InteractiveMode {
|
|
|
2050
2062
|
this.editor.setText("");
|
|
2051
2063
|
return;
|
|
2052
2064
|
}
|
|
2065
|
+
if (text === "/auto-learn" || text.startsWith("/auto-learn ")) {
|
|
2066
|
+
this.handleAutoLearnCommand(text);
|
|
2067
|
+
this.editor.setText("");
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2053
2070
|
if (text === "/scoped-models") {
|
|
2054
2071
|
this.editor.setText("");
|
|
2055
2072
|
await this.showModelsSelector();
|
|
@@ -2371,6 +2388,7 @@ export class InteractiveMode {
|
|
|
2371
2388
|
break;
|
|
2372
2389
|
}
|
|
2373
2390
|
case "agent_end":
|
|
2391
|
+
this.maybeStartAutoLearn();
|
|
2374
2392
|
if (this.settingsManager.getShowTerminalProgress()) {
|
|
2375
2393
|
this.ui.terminal.setProgress(false);
|
|
2376
2394
|
}
|
|
@@ -3260,8 +3278,382 @@ export class InteractiveMode {
|
|
|
3260
3278
|
this.ui.setFocus(focus);
|
|
3261
3279
|
this.ui.requestRender();
|
|
3262
3280
|
}
|
|
3281
|
+
getAutoLearnModelAuthPriority(model) {
|
|
3282
|
+
if (this.session.model && model.provider === this.session.model.provider && model.id === this.session.model.id) {
|
|
3283
|
+
return 0;
|
|
3284
|
+
}
|
|
3285
|
+
const credential = this.session.modelRegistry.authStorage.get(model.provider);
|
|
3286
|
+
if (credential?.type === "oauth")
|
|
3287
|
+
return 1;
|
|
3288
|
+
if (credential?.type === "api_key")
|
|
3289
|
+
return 2;
|
|
3290
|
+
const authStatus = this.session.modelRegistry.getProviderAuthStatus(model.provider);
|
|
3291
|
+
switch (authStatus.source) {
|
|
3292
|
+
case "runtime":
|
|
3293
|
+
return 3;
|
|
3294
|
+
case "environment":
|
|
3295
|
+
return 4;
|
|
3296
|
+
case "models_json_key":
|
|
3297
|
+
case "models_json_command":
|
|
3298
|
+
case "fallback":
|
|
3299
|
+
return 5;
|
|
3300
|
+
default:
|
|
3301
|
+
return 6;
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
getAutoLearnModelAuthLabel(model) {
|
|
3305
|
+
const credential = this.session.modelRegistry.authStorage.get(model.provider);
|
|
3306
|
+
if (credential?.type === "oauth")
|
|
3307
|
+
return "subscription";
|
|
3308
|
+
if (credential?.type === "api_key")
|
|
3309
|
+
return "API key";
|
|
3310
|
+
const authStatus = this.session.modelRegistry.getProviderAuthStatus(model.provider);
|
|
3311
|
+
switch (authStatus.source) {
|
|
3312
|
+
case "runtime":
|
|
3313
|
+
return authStatus.label ? `runtime ${authStatus.label}` : "runtime API key";
|
|
3314
|
+
case "environment":
|
|
3315
|
+
return authStatus.label ? `env ${authStatus.label}` : "environment API key";
|
|
3316
|
+
case "models_json_key":
|
|
3317
|
+
return "models.json API key";
|
|
3318
|
+
case "models_json_command":
|
|
3319
|
+
return "models.json command";
|
|
3320
|
+
case "fallback":
|
|
3321
|
+
return authStatus.label ?? "custom provider config";
|
|
3322
|
+
default:
|
|
3323
|
+
return "configured";
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
getAutoLearnModelOptions() {
|
|
3327
|
+
this.session.modelRegistry.refresh();
|
|
3328
|
+
const availableModels = this.session.modelRegistry.getAvailable();
|
|
3329
|
+
const sortedModels = [...availableModels].sort((a, b) => {
|
|
3330
|
+
const priorityDelta = this.getAutoLearnModelAuthPriority(a) - this.getAutoLearnModelAuthPriority(b);
|
|
3331
|
+
if (priorityDelta !== 0)
|
|
3332
|
+
return priorityDelta;
|
|
3333
|
+
const providerDelta = this.session.modelRegistry
|
|
3334
|
+
.getProviderDisplayName(a.provider)
|
|
3335
|
+
.localeCompare(this.session.modelRegistry.getProviderDisplayName(b.provider));
|
|
3336
|
+
if (providerDelta !== 0)
|
|
3337
|
+
return providerDelta;
|
|
3338
|
+
return a.id.localeCompare(b.id);
|
|
3339
|
+
});
|
|
3340
|
+
return sortedModels.map((model) => {
|
|
3341
|
+
const providerName = this.session.modelRegistry.getProviderDisplayName(model.provider);
|
|
3342
|
+
const authLabel = this.getAutoLearnModelAuthLabel(model);
|
|
3343
|
+
const modelPattern = `${model.provider}/${model.id}`;
|
|
3344
|
+
const currentLabel = this.session.model && model.provider === this.session.model.provider && model.id === this.session.model.id
|
|
3345
|
+
? " · current"
|
|
3346
|
+
: "";
|
|
3347
|
+
const displayName = model.name && model.name !== model.id ? ` · ${model.name}` : "";
|
|
3348
|
+
return {
|
|
3349
|
+
value: modelPattern,
|
|
3350
|
+
label: modelPattern,
|
|
3351
|
+
description: `${providerName} · ${authLabel}${currentLabel}${displayName}`,
|
|
3352
|
+
};
|
|
3353
|
+
});
|
|
3354
|
+
}
|
|
3355
|
+
getAutoLearnDataDir() {
|
|
3356
|
+
return path.join(getAgentDir(), "auto-learn");
|
|
3357
|
+
}
|
|
3358
|
+
getAutoLearnStatePath() {
|
|
3359
|
+
return path.join(this.getAutoLearnDataDir(), "state.json");
|
|
3360
|
+
}
|
|
3361
|
+
readAutoLearnState() {
|
|
3362
|
+
try {
|
|
3363
|
+
const statePath = this.getAutoLearnStatePath();
|
|
3364
|
+
if (!fs.existsSync(statePath))
|
|
3365
|
+
return {};
|
|
3366
|
+
return JSON.parse(fs.readFileSync(statePath, "utf-8"));
|
|
3367
|
+
}
|
|
3368
|
+
catch {
|
|
3369
|
+
return {};
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
writeAutoLearnState(state) {
|
|
3373
|
+
const dir = this.getAutoLearnDataDir();
|
|
3374
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
3375
|
+
fs.writeFileSync(this.getAutoLearnStatePath(), `${JSON.stringify(state, null, 2)}\n`, "utf-8");
|
|
3376
|
+
}
|
|
3377
|
+
isAutoLearnPidAlive(pid) {
|
|
3378
|
+
if (typeof pid !== "number" || pid <= 0)
|
|
3379
|
+
return false;
|
|
3380
|
+
try {
|
|
3381
|
+
process.kill(pid, 0);
|
|
3382
|
+
return true;
|
|
3383
|
+
}
|
|
3384
|
+
catch (error) {
|
|
3385
|
+
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
3386
|
+
return code === "EPERM";
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
pruneAutoLearnState(state, now = Date.now()) {
|
|
3390
|
+
const runs = { ...(state.runs ?? {}) };
|
|
3391
|
+
for (const [id, run] of Object.entries(runs)) {
|
|
3392
|
+
if (run.expiresAt <= now || !this.isAutoLearnPidAlive(run.pid)) {
|
|
3393
|
+
delete runs[id];
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
return { ...state, runs };
|
|
3397
|
+
}
|
|
3398
|
+
getEffectiveAutoLearnSettings() {
|
|
3399
|
+
const settings = this.settingsManager.getAutoLearnSettings();
|
|
3400
|
+
return {
|
|
3401
|
+
enabled: settings.enabled ?? false,
|
|
3402
|
+
model: settings.model?.trim() || AUTO_LEARN_DEFAULTS.model,
|
|
3403
|
+
longSessionMessages: settings.longSessionMessages ?? AUTO_LEARN_DEFAULTS.longSessionMessages,
|
|
3404
|
+
longSessionContextPercent: settings.longSessionContextPercent ?? AUTO_LEARN_DEFAULTS.longSessionContextPercent,
|
|
3405
|
+
cooldownMinutes: settings.cooldownMinutes ?? AUTO_LEARN_DEFAULTS.cooldownMinutes,
|
|
3406
|
+
leaseMinutes: settings.leaseMinutes ?? AUTO_LEARN_DEFAULTS.leaseMinutes,
|
|
3407
|
+
maxConcurrentLearners: settings.maxConcurrentLearners ?? AUTO_LEARN_DEFAULTS.maxConcurrentLearners,
|
|
3408
|
+
applyHighConfidence: settings.applyHighConfidence ?? AUTO_LEARN_DEFAULTS.applyHighConfidence,
|
|
3409
|
+
};
|
|
3410
|
+
}
|
|
3411
|
+
getAutoLearnTenantKey() {
|
|
3412
|
+
return `${this.sessionManager.getCwd()}::${this.session.sessionId}`;
|
|
3413
|
+
}
|
|
3414
|
+
getAutoLearnMessageCount() {
|
|
3415
|
+
return this.sessionManager.getBranch().filter((entry) => entry.type === "message").length;
|
|
3416
|
+
}
|
|
3417
|
+
resolveAutoLearnModelPattern(settings) {
|
|
3418
|
+
if (settings.model === "active") {
|
|
3419
|
+
return this.session.model ? `${this.session.model.provider}/${this.session.model.id}` : undefined;
|
|
3420
|
+
}
|
|
3421
|
+
return settings.model;
|
|
3422
|
+
}
|
|
3423
|
+
getAutoLearnSpawnTarget() {
|
|
3424
|
+
const overridePath = process.env.PI_AUTO_LEARN_CLI_PATH?.trim();
|
|
3425
|
+
if (overridePath) {
|
|
3426
|
+
return { command: overridePath, argsPrefix: [] };
|
|
3427
|
+
}
|
|
3428
|
+
const execBase = path.basename(process.execPath).toLowerCase();
|
|
3429
|
+
const isScriptRuntime = execBase === "node" || execBase === "node.exe" || execBase === "bun" || execBase === "bun.exe";
|
|
3430
|
+
if (!isScriptRuntime) {
|
|
3431
|
+
return { command: process.execPath, argsPrefix: [] };
|
|
3432
|
+
}
|
|
3433
|
+
const cliPath = process.argv[1];
|
|
3434
|
+
if (!cliPath || cliPath.startsWith("-")) {
|
|
3435
|
+
return undefined;
|
|
3436
|
+
}
|
|
3437
|
+
return { command: process.execPath, argsPrefix: [cliPath] };
|
|
3438
|
+
}
|
|
3439
|
+
validateAutoLearnModelValue(value) {
|
|
3440
|
+
const modelValue = value?.trim();
|
|
3441
|
+
if (!modelValue || modelValue === "active")
|
|
3442
|
+
return undefined;
|
|
3443
|
+
const available = this.session.modelRegistry.getAvailable();
|
|
3444
|
+
if (modelValue.includes("/")) {
|
|
3445
|
+
const [provider, modelId] = modelValue.split("/", 2);
|
|
3446
|
+
if (available.some((model) => model.provider === provider && model.id === modelId))
|
|
3447
|
+
return undefined;
|
|
3448
|
+
return `Auto Learn model "${modelValue}" is not in configured subscription/API models; saved as manual/unverified.`;
|
|
3449
|
+
}
|
|
3450
|
+
if (available.some((model) => model.id === modelValue))
|
|
3451
|
+
return undefined;
|
|
3452
|
+
return `Auto Learn model "${modelValue}" is not in configured subscription/API models; saved as manual/unverified.`;
|
|
3453
|
+
}
|
|
3454
|
+
validateSelfModificationSource(settings) {
|
|
3455
|
+
if (!settings.enabled)
|
|
3456
|
+
return undefined;
|
|
3457
|
+
const rawPath = settings.sourcePath?.trim();
|
|
3458
|
+
if (!rawPath)
|
|
3459
|
+
return "Self modification is enabled, but no pi-adaptative source path is set.";
|
|
3460
|
+
const sourcePath = resolvePath(rawPath, this.sessionManager.getCwd(), { trim: true });
|
|
3461
|
+
if (!fs.existsSync(sourcePath))
|
|
3462
|
+
return `Self modification source path does not exist: ${sourcePath}`;
|
|
3463
|
+
if (!fs.existsSync(path.join(sourcePath, "package.json"))) {
|
|
3464
|
+
return `Self modification source path has no package.json: ${sourcePath}`;
|
|
3465
|
+
}
|
|
3466
|
+
if (!fs.existsSync(path.join(sourcePath, "packages", "coding-agent"))) {
|
|
3467
|
+
return `Self modification source path does not look like pi-adaptative (missing packages/coding-agent): ${sourcePath}`;
|
|
3468
|
+
}
|
|
3469
|
+
return undefined;
|
|
3470
|
+
}
|
|
3471
|
+
evaluateAutoLearn(force = false) {
|
|
3472
|
+
const settings = this.getEffectiveAutoLearnSettings();
|
|
3473
|
+
const state = this.pruneAutoLearnState(this.readAutoLearnState());
|
|
3474
|
+
this.writeAutoLearnState(state);
|
|
3475
|
+
const now = Date.now();
|
|
3476
|
+
const tenant = this.getAutoLearnTenantKey();
|
|
3477
|
+
const runningCount = Object.keys(state.runs ?? {}).length;
|
|
3478
|
+
const lastLaunch = state.lastLaunchByTenant?.[tenant] ?? 0;
|
|
3479
|
+
const cooldownMs = settings.cooldownMinutes * 60 * 1000;
|
|
3480
|
+
const cooldownRemainingMs = Math.max(0, lastLaunch + cooldownMs - now);
|
|
3481
|
+
const messageCount = this.getAutoLearnMessageCount();
|
|
3482
|
+
const contextPercent = this.session.getContextUsage()?.percent ?? null;
|
|
3483
|
+
if (!settings.enabled && !force) {
|
|
3484
|
+
return {
|
|
3485
|
+
shouldRun: false,
|
|
3486
|
+
reason: "disabled",
|
|
3487
|
+
messageCount,
|
|
3488
|
+
contextPercent,
|
|
3489
|
+
cooldownRemainingMs,
|
|
3490
|
+
runningCount,
|
|
3491
|
+
};
|
|
3492
|
+
}
|
|
3493
|
+
if (runningCount >= settings.maxConcurrentLearners) {
|
|
3494
|
+
return {
|
|
3495
|
+
shouldRun: false,
|
|
3496
|
+
reason: `max learners running (${runningCount}/${settings.maxConcurrentLearners})`,
|
|
3497
|
+
messageCount,
|
|
3498
|
+
contextPercent,
|
|
3499
|
+
cooldownRemainingMs,
|
|
3500
|
+
runningCount,
|
|
3501
|
+
};
|
|
3502
|
+
}
|
|
3503
|
+
if (!force && cooldownRemainingMs > 0) {
|
|
3504
|
+
return {
|
|
3505
|
+
shouldRun: false,
|
|
3506
|
+
reason: "cooldown",
|
|
3507
|
+
messageCount,
|
|
3508
|
+
contextPercent,
|
|
3509
|
+
cooldownRemainingMs,
|
|
3510
|
+
runningCount,
|
|
3511
|
+
};
|
|
3512
|
+
}
|
|
3513
|
+
if (force) {
|
|
3514
|
+
return { shouldRun: true, reason: "manual", messageCount, contextPercent, cooldownRemainingMs, runningCount };
|
|
3515
|
+
}
|
|
3516
|
+
if (messageCount >= settings.longSessionMessages) {
|
|
3517
|
+
return {
|
|
3518
|
+
shouldRun: true,
|
|
3519
|
+
reason: `message trigger (${messageCount}/${settings.longSessionMessages})`,
|
|
3520
|
+
messageCount,
|
|
3521
|
+
contextPercent,
|
|
3522
|
+
cooldownRemainingMs,
|
|
3523
|
+
runningCount,
|
|
3524
|
+
};
|
|
3525
|
+
}
|
|
3526
|
+
if (contextPercent !== null && contextPercent >= settings.longSessionContextPercent) {
|
|
3527
|
+
return {
|
|
3528
|
+
shouldRun: true,
|
|
3529
|
+
reason: `context trigger (${contextPercent.toFixed(1)}%/${settings.longSessionContextPercent}%)`,
|
|
3530
|
+
messageCount,
|
|
3531
|
+
contextPercent,
|
|
3532
|
+
cooldownRemainingMs,
|
|
3533
|
+
runningCount,
|
|
3534
|
+
};
|
|
3535
|
+
}
|
|
3536
|
+
return {
|
|
3537
|
+
shouldRun: false,
|
|
3538
|
+
reason: "thresholds not met",
|
|
3539
|
+
messageCount,
|
|
3540
|
+
contextPercent,
|
|
3541
|
+
cooldownRemainingMs,
|
|
3542
|
+
runningCount,
|
|
3543
|
+
};
|
|
3544
|
+
}
|
|
3545
|
+
buildAutoLearnPrompt(reason, settings) {
|
|
3546
|
+
return `You are Pi Auto Learn running as a background learner.\n\nObjective: run one bounded continuous-learning pass for this Pi tenant.\nTrigger: ${reason}.\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. Keep tooling/core/source changes proposal/approval-gated; do not publish, tag, release, or modify Pi source from this background run.\n4. If the learning tools are unavailable, report BLOCKED with the missing tool names and do not improvise.\n5. Finish with PASS, BLOCKED, or FAIL and concise evidence.`;
|
|
3547
|
+
}
|
|
3548
|
+
launchAutoLearn(reason, force = false) {
|
|
3549
|
+
const settings = this.getEffectiveAutoLearnSettings();
|
|
3550
|
+
const decision = this.evaluateAutoLearn(force);
|
|
3551
|
+
if (!decision.shouldRun) {
|
|
3552
|
+
return `Auto Learn not started: ${decision.reason}`;
|
|
3553
|
+
}
|
|
3554
|
+
const modelPattern = this.resolveAutoLearnModelPattern(settings);
|
|
3555
|
+
if (!modelPattern) {
|
|
3556
|
+
return "Auto Learn not started: no active model is available for model=active.";
|
|
3557
|
+
}
|
|
3558
|
+
const spawnTarget = this.getAutoLearnSpawnTarget();
|
|
3559
|
+
if (!spawnTarget) {
|
|
3560
|
+
return "Auto Learn not started: could not resolve current pi CLI path.";
|
|
3561
|
+
}
|
|
3562
|
+
const dir = this.getAutoLearnDataDir();
|
|
3563
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
3564
|
+
const runId = `${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
|
|
3565
|
+
const logPath = path.join(dir, `${runId}.log`);
|
|
3566
|
+
const outFd = fs.openSync(logPath, "a");
|
|
3567
|
+
const prompt = this.buildAutoLearnPrompt(reason, settings);
|
|
3568
|
+
const args = [
|
|
3569
|
+
...spawnTarget.argsPrefix,
|
|
3570
|
+
"--print",
|
|
3571
|
+
"--name",
|
|
3572
|
+
`Auto Learn ${runId}`,
|
|
3573
|
+
"--model",
|
|
3574
|
+
modelPattern,
|
|
3575
|
+
prompt,
|
|
3576
|
+
];
|
|
3577
|
+
const child = spawn(spawnTarget.command, args, {
|
|
3578
|
+
cwd: this.sessionManager.getCwd(),
|
|
3579
|
+
detached: true,
|
|
3580
|
+
stdio: ["ignore", outFd, outFd],
|
|
3581
|
+
env: { ...process.env, PI_AUTO_LEARN_CHILD: "1" },
|
|
3582
|
+
});
|
|
3583
|
+
child.unref();
|
|
3584
|
+
fs.closeSync(outFd);
|
|
3585
|
+
const now = Date.now();
|
|
3586
|
+
const state = this.pruneAutoLearnState(this.readAutoLearnState(), now);
|
|
3587
|
+
state.lastLaunchByTenant = { ...(state.lastLaunchByTenant ?? {}), [this.getAutoLearnTenantKey()]: now };
|
|
3588
|
+
state.runs = {
|
|
3589
|
+
...(state.runs ?? {}),
|
|
3590
|
+
[runId]: {
|
|
3591
|
+
tenant: this.getAutoLearnTenantKey(),
|
|
3592
|
+
pid: child.pid,
|
|
3593
|
+
model: modelPattern,
|
|
3594
|
+
reason,
|
|
3595
|
+
startedAt: now,
|
|
3596
|
+
expiresAt: now + settings.leaseMinutes * 60 * 1000,
|
|
3597
|
+
cwd: this.sessionManager.getCwd(),
|
|
3598
|
+
logPath,
|
|
3599
|
+
},
|
|
3600
|
+
};
|
|
3601
|
+
this.writeAutoLearnState(state);
|
|
3602
|
+
this.autoLearnLastStatus = `running ${modelPattern}`;
|
|
3603
|
+
this.updateAutoLearnFooter();
|
|
3604
|
+
return `Auto Learn started (${reason}) with ${modelPattern}. Log: ${logPath}`;
|
|
3605
|
+
}
|
|
3606
|
+
maybeStartAutoLearn() {
|
|
3607
|
+
if (process.env.PI_AUTO_LEARN_CHILD === "1")
|
|
3608
|
+
return;
|
|
3609
|
+
const decision = this.evaluateAutoLearn(false);
|
|
3610
|
+
if (!decision.shouldRun) {
|
|
3611
|
+
this.autoLearnLastStatus = decision.reason;
|
|
3612
|
+
this.updateAutoLearnFooter();
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
const message = this.launchAutoLearn(decision.reason, false);
|
|
3616
|
+
this.showStatus(message);
|
|
3617
|
+
}
|
|
3618
|
+
updateAutoLearnFooter() {
|
|
3619
|
+
const settings = this.getEffectiveAutoLearnSettings();
|
|
3620
|
+
if (!settings.enabled) {
|
|
3621
|
+
this.footerDataProvider.setExtensionStatus("auto-learn", undefined);
|
|
3622
|
+
return;
|
|
3623
|
+
}
|
|
3624
|
+
this.footerDataProvider.setExtensionStatus("auto-learn", theme.fg("accent", `learn: ${this.autoLearnLastStatus}`));
|
|
3625
|
+
this.footer.invalidate();
|
|
3626
|
+
this.ui.requestRender();
|
|
3627
|
+
}
|
|
3628
|
+
formatAutoLearnStatus() {
|
|
3629
|
+
const settings = this.getEffectiveAutoLearnSettings();
|
|
3630
|
+
const decision = this.evaluateAutoLearn(false);
|
|
3631
|
+
const state = this.pruneAutoLearnState(this.readAutoLearnState());
|
|
3632
|
+
const runs = Object.entries(state.runs ?? {});
|
|
3633
|
+
const contextText = decision.contextPercent === null ? "unknown" : `${decision.contextPercent.toFixed(1)}%`;
|
|
3634
|
+
const cooldownText = decision.cooldownRemainingMs > 0 ? `${Math.ceil(decision.cooldownRemainingMs / 60000)}m remaining` : "ready";
|
|
3635
|
+
const runLines = runs.length
|
|
3636
|
+
? runs.map(([id, run]) => `- ${id}: ${run.model}, pid=${run.pid ?? "?"}, log=${run.logPath}`).join("\n")
|
|
3637
|
+
: "- none";
|
|
3638
|
+
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}\nRunning leases: ${runs.length}/${settings.maxConcurrentLearners}\nRuns:\n${runLines}`;
|
|
3639
|
+
}
|
|
3640
|
+
handleAutoLearnCommand(text) {
|
|
3641
|
+
const action = text.slice("/auto-learn".length).trim() || "status";
|
|
3642
|
+
if (action === "run" || action === "now" || action === "run-now") {
|
|
3643
|
+
this.showStatus(this.launchAutoLearn("manual", true));
|
|
3644
|
+
return;
|
|
3645
|
+
}
|
|
3646
|
+
if (action === "status") {
|
|
3647
|
+
this.chatContainer.addChild(new Spacer(1));
|
|
3648
|
+
this.chatContainer.addChild(new Text(this.formatAutoLearnStatus(), 1, 0));
|
|
3649
|
+
this.ui.requestRender();
|
|
3650
|
+
return;
|
|
3651
|
+
}
|
|
3652
|
+
this.showStatus("Usage: /auto-learn [status|run]");
|
|
3653
|
+
}
|
|
3263
3654
|
showSettingsSelector() {
|
|
3264
3655
|
this.showSelector((done) => {
|
|
3656
|
+
const projectSettings = this.settingsManager.getProjectSettings();
|
|
3265
3657
|
const selector = new SettingsSelectorComponent({
|
|
3266
3658
|
autoCompact: this.session.autoCompactionEnabled,
|
|
3267
3659
|
showImages: this.settingsManager.getShowImages(),
|
|
@@ -3289,6 +3681,14 @@ export class InteractiveMode {
|
|
|
3289
3681
|
clearOnShrink: this.settingsManager.getClearOnShrink(),
|
|
3290
3682
|
showTerminalProgress: this.settingsManager.getShowTerminalProgress(),
|
|
3291
3683
|
warnings: this.settingsManager.getWarnings(),
|
|
3684
|
+
selfModification: this.settingsManager.getSelfModificationSettings(),
|
|
3685
|
+
selfModificationScope: projectSettings.selfModification ? "project" : "global",
|
|
3686
|
+
autoLearn: this.settingsManager.getAutoLearnSettings(),
|
|
3687
|
+
autoLearnScope: projectSettings.autoLearn ? "project" : "global",
|
|
3688
|
+
autoLearnModelOptions: this.getAutoLearnModelOptions(),
|
|
3689
|
+
currentModelPattern: this.session.model
|
|
3690
|
+
? `${this.session.model.provider}/${this.session.model.id}`
|
|
3691
|
+
: undefined,
|
|
3292
3692
|
}, {
|
|
3293
3693
|
onAutoCompactChange: (enabled) => {
|
|
3294
3694
|
this.session.setAutoCompactionEnabled(enabled);
|
|
@@ -3409,6 +3809,23 @@ export class InteractiveMode {
|
|
|
3409
3809
|
onWarningsChange: (warnings) => {
|
|
3410
3810
|
this.settingsManager.setWarnings(warnings);
|
|
3411
3811
|
},
|
|
3812
|
+
onSelfModificationChange: (settings, scope) => {
|
|
3813
|
+
this.settingsManager.setSelfModificationSettings(settings, scope);
|
|
3814
|
+
const validationMessage = this.validateSelfModificationSource(settings);
|
|
3815
|
+
if (validationMessage) {
|
|
3816
|
+
this.showWarning(validationMessage);
|
|
3817
|
+
}
|
|
3818
|
+
this.showStatus(`Self modification settings saved to ${scope}. Start a new session or /reload for system-prompt guardrails to fully refresh.`);
|
|
3819
|
+
},
|
|
3820
|
+
onAutoLearnChange: (settings, scope) => {
|
|
3821
|
+
this.settingsManager.setAutoLearnSettings(settings, scope);
|
|
3822
|
+
const validationMessage = this.validateAutoLearnModelValue(settings.model);
|
|
3823
|
+
if (validationMessage) {
|
|
3824
|
+
this.showWarning(validationMessage);
|
|
3825
|
+
}
|
|
3826
|
+
this.updateAutoLearnFooter();
|
|
3827
|
+
this.showStatus(`Auto Learn settings saved to ${scope}. Use /auto-learn status or /auto-learn run.`);
|
|
3828
|
+
},
|
|
3412
3829
|
onCancel: () => {
|
|
3413
3830
|
done();
|
|
3414
3831
|
this.ui.requestRender();
|