autodev-cli 1.4.85 → 1.4.93
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/out/agentBackup/layout.js +1 -0
- package/out/agentBackup/layout.js.map +1 -1
- package/out/cliExit.d.ts +43 -1
- package/out/cliExit.js +140 -2
- package/out/cliExit.js.map +1 -1
- package/out/commands/mcpOperate.js +368 -28
- package/out/commands/mcpOperate.js.map +1 -1
- package/out/configManager.d.ts +7 -0
- package/out/configManager.js +36 -0
- package/out/configManager.js.map +1 -1
- package/out/core/modelInfo.d.ts +28 -0
- package/out/core/modelInfo.js +97 -0
- package/out/core/modelInfo.js.map +1 -0
- package/out/core/projectSkills.d.ts +44 -0
- package/out/core/projectSkills.js +266 -0
- package/out/core/projectSkills.js.map +1 -0
- package/out/core/redactSecrets.d.ts +15 -0
- package/out/core/redactSecrets.js +110 -0
- package/out/core/redactSecrets.js.map +1 -0
- package/out/core/settingsLoader.d.ts +9 -0
- package/out/core/settingsLoader.js +1 -0
- package/out/core/settingsLoader.js.map +1 -1
- package/out/taskLoop.d.ts +13 -0
- package/out/taskLoop.js +120 -4
- package/out/taskLoop.js.map +1 -1
- package/out/todoWriteManager.d.ts +3 -0
- package/out/todoWriteManager.js +5 -0
- package/out/todoWriteManager.js.map +1 -1
- package/out/webSocketPoller.d.ts +3 -0
- package/out/webSocketPoller.js +11 -0
- package/out/webSocketPoller.js.map +1 -1
- package/out/webhookPoller.d.ts +1 -0
- package/out/webhookPoller.js +5 -0
- package/out/webhookPoller.js.map +1 -1
- package/package.json +2 -2
package/out/taskLoop.js
CHANGED
|
@@ -61,7 +61,9 @@ const periodicActions_1 = require("./periodicActions");
|
|
|
61
61
|
const discordGateway_1 = require("./discordGateway");
|
|
62
62
|
const webhookPoller_1 = require("./webhookPoller");
|
|
63
63
|
const emailPoller_1 = require("./emailPoller");
|
|
64
|
+
const modelInfo_1 = require("./core/modelInfo");
|
|
64
65
|
const projectMcp_1 = require("./core/projectMcp");
|
|
66
|
+
const projectSkills_1 = require("./core/projectSkills");
|
|
65
67
|
const configManager_1 = require("./configManager");
|
|
66
68
|
const agentBackup_1 = require("./agentBackup");
|
|
67
69
|
const version_1 = require("./version");
|
|
@@ -71,6 +73,7 @@ const version_1 = require("./version");
|
|
|
71
73
|
const rateLimit_1 = require("./rateLimit");
|
|
72
74
|
const cliExit_1 = require("./cliExit");
|
|
73
75
|
const liveNarration_1 = require("./core/liveNarration");
|
|
76
|
+
const redactSecrets_1 = require("./core/redactSecrets");
|
|
74
77
|
class ContextLengthError extends Error {
|
|
75
78
|
rawMessage;
|
|
76
79
|
constructor(rawMessage) {
|
|
@@ -202,6 +205,11 @@ class TaskLoopRunner {
|
|
|
202
205
|
/** Dispatch attempt counter per task key (id or text). After 3 failed attempts the
|
|
203
206
|
* loop force-marks the task done so it doesn't block the queue indefinitely. */
|
|
204
207
|
_taskAttempts = new Map();
|
|
208
|
+
/** Task keys (id or text) flagged as provider hard-failures this session.
|
|
209
|
+
* These must NOT be auto-completed by the give_up / stranded-[~] heuristics —
|
|
210
|
+
* a blocked task stays [~] + reported failed until it is retried. Cleared when
|
|
211
|
+
* a fresh [ ] dispatch of the same task begins (provider presumably recovered). */
|
|
212
|
+
_blockedTasks = new Set();
|
|
205
213
|
/** Counts completed tasks since the last auto-compact run. */
|
|
206
214
|
_autoCompactCounter = 0;
|
|
207
215
|
/** Counts completed tasks since the last session reset. */
|
|
@@ -344,6 +352,7 @@ class TaskLoopRunner {
|
|
|
344
352
|
this._iterations = 0;
|
|
345
353
|
this._compactedTaskLines.clear();
|
|
346
354
|
this._taskAttempts.clear();
|
|
355
|
+
this._blockedTasks.clear();
|
|
347
356
|
this._autoCompactCounter = 0;
|
|
348
357
|
this._resetSessionCounter = 0;
|
|
349
358
|
this._profileSentCounter = 0;
|
|
@@ -387,7 +396,13 @@ class TaskLoopRunner {
|
|
|
387
396
|
// `cliVersion` rides in the meta merged into every webhook frame — notably
|
|
388
397
|
// the agent_online (hello) frame — so pixel-office can record which CLI
|
|
389
398
|
// version each agent runs (surfacing stale, steer-incapable agents).
|
|
390
|
-
|
|
399
|
+
// `model` — the effective friendly model name (Opus 4.8 / kimi-k2.7-code /
|
|
400
|
+
// grok-code). Resolved from the provider's configured override; undefined
|
|
401
|
+
// when the provider runs its account default (office gates the field). Rides
|
|
402
|
+
// in setMeta so it merges into every frame, notably agent_online, where the
|
|
403
|
+
// office records it against the agent for the per-agent model badge.
|
|
404
|
+
const effectiveModel = (0, modelInfo_1.resolveConfiguredModel)(settings);
|
|
405
|
+
this._webhook?.setMeta({ provider: settings.provider, cliVersion: version_1.CLI_VERSION, model: effectiveModel, workDir: root, hostname: this._hostname, gitRepo: this._gitRepo, gitBranch: this._gitBranch });
|
|
391
406
|
this._discordPoller = (settings.discordToken && settings.discordChannelId && settings.discordOwners)
|
|
392
407
|
? new discordPoller_1.DiscordPoller(settings.discordToken, settings.discordChannelId, settings.discordOwners)
|
|
393
408
|
: null;
|
|
@@ -434,6 +449,7 @@ class TaskLoopRunner {
|
|
|
434
449
|
// mis-detected as 'claude' by the office's hook-based fallback.
|
|
435
450
|
provider: this._settings?.provider,
|
|
436
451
|
cliVersion: version_1.CLI_VERSION,
|
|
452
|
+
model: this._settings ? (0, modelInfo_1.resolveConfiguredModel)(this._settings) : undefined,
|
|
437
453
|
hostname: this._hostname,
|
|
438
454
|
workDir: this._workspaceRoot ?? '',
|
|
439
455
|
gitRepo: this._gitRepo,
|
|
@@ -485,6 +501,7 @@ class TaskLoopRunner {
|
|
|
485
501
|
this._webhookPoller.setOnSteer((text, onDelivered) => void this._handleSteer(text, onDelivered));
|
|
486
502
|
this._webhookPoller.setOnCommand((cmd) => this._handleCommand(cmd));
|
|
487
503
|
this._webhookPoller.setOnMcpUpdate((entries) => this._handleMcpUpdate(entries));
|
|
504
|
+
this._webhookPoller.setOnSkillUpdate((skills) => this._handleSkillUpdate(skills));
|
|
488
505
|
this._webhookPoller.setOnExportRequest((agentId) => void this._handleExportRequest(agentId));
|
|
489
506
|
this._webhookPoller.setOnRestoreRequest((agentId, downloadUrl) => void this._handleRestoreRequest(agentId, downloadUrl));
|
|
490
507
|
this._webhookPoller.setOnExportConfig((exportEnabled, exportDailyBackup, agentId) => this._handleExportConfig(exportEnabled, exportDailyBackup, agentId));
|
|
@@ -556,6 +573,7 @@ class TaskLoopRunner {
|
|
|
556
573
|
this._notifyWebhook('agent_online', {
|
|
557
574
|
provider: settings.provider,
|
|
558
575
|
cliVersion: version_1.CLI_VERSION,
|
|
576
|
+
model: effectiveModel,
|
|
559
577
|
hostname: this._hostname,
|
|
560
578
|
workDir: root,
|
|
561
579
|
gitRepo: this._gitRepo,
|
|
@@ -704,6 +722,44 @@ class TaskLoopRunner {
|
|
|
704
722
|
}
|
|
705
723
|
void this.restart();
|
|
706
724
|
}
|
|
725
|
+
/**
|
|
726
|
+
* Handle skill_update pushed from pixel-office: validate + write the agent's
|
|
727
|
+
* FULL effective skill set to `.claude/skills/<slug>/SKILL.md`. Skills
|
|
728
|
+
* live-reload (Claude re-reads them each run), so this does NOT restart the
|
|
729
|
+
* loop — it just sanitizes, full-replaces on disk, folds a prose block into
|
|
730
|
+
* AGENTS.md for non-Claude providers, and reports the applied names.
|
|
731
|
+
*/
|
|
732
|
+
_handleSkillUpdate(skills) {
|
|
733
|
+
const root = this._workspaceRoot;
|
|
734
|
+
if (!root)
|
|
735
|
+
return;
|
|
736
|
+
// Opt-in gate: remote-supplied skills become instruction files a running
|
|
737
|
+
// Claude agent live-reads. Ignore unless explicitly enabled, mirroring
|
|
738
|
+
// mcpUpdateEnabled / enableFileBrowser / gitEnabled.
|
|
739
|
+
if (!this._settings?.skillUpdateEnabled) {
|
|
740
|
+
this._cb?.log('🔒 skill_update ignored — skillUpdateEnabled is off (set it in .autodev/settings.json to allow)');
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
this._cb?.log('🧩 skill_update received — validating and writing .claude/skills…');
|
|
744
|
+
const { safe, rejected } = (0, projectSkills_1.sanitizeRemoteSkills)(root, skills);
|
|
745
|
+
if (rejected.length) {
|
|
746
|
+
this._cb?.log(`⚠️ skill_update dropped ${rejected.length} entr${rejected.length === 1 ? 'y' : 'ies'}: ${rejected.map(r => `${r.name} (${r.reason})`).join(', ')}`);
|
|
747
|
+
}
|
|
748
|
+
try {
|
|
749
|
+
// Full-replace: writes the safe set and prunes previously-synced skills
|
|
750
|
+
// that are gone (even when `safe` is empty — that clears them all).
|
|
751
|
+
const { written, removed } = (0, projectSkills_1.saveProjectSkills)(root, safe);
|
|
752
|
+
// Non-Claude providers can't load SKILL.md — fold a prose block into AGENTS.md.
|
|
753
|
+
if (!(0, projectSkills_1.providerConsumesSkills)(this._settings?.provider)) {
|
|
754
|
+
(0, projectSkills_1.foldSkillsIntoProfile)(root, safe);
|
|
755
|
+
}
|
|
756
|
+
void configManager_1.ConfigManager.reportProjectSkills(root, written, (m) => this._cb?.log(m));
|
|
757
|
+
this._cb?.log(`✅ skills synced — wrote ${written.length}, removed ${removed.length} (.claude/skills). Live-reloads on the next run — no restart.`);
|
|
758
|
+
}
|
|
759
|
+
catch (err) {
|
|
760
|
+
this._cb?.log(`⚠️ skill update failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
707
763
|
/** Handle export_request from pixel-office: create backup zip and upload. */
|
|
708
764
|
async _handleExportRequest(agentId) {
|
|
709
765
|
const root = this._workspaceRoot;
|
|
@@ -1162,7 +1218,10 @@ class TaskLoopRunner {
|
|
|
1162
1218
|
if (sessionName) {
|
|
1163
1219
|
ev._session_name = sessionName;
|
|
1164
1220
|
}
|
|
1165
|
-
|
|
1221
|
+
// Redact secrets from the whole hook object (tool_input/tool_response/
|
|
1222
|
+
// assistant narration/file contents) before it leaves the machine —
|
|
1223
|
+
// this stream is stored + rendered in the office feed/chat.
|
|
1224
|
+
this._webhookPoller.sendFrame({ type: 'hook_event', data: (0, redactSecrets_1.redactDeep)(ev) });
|
|
1166
1225
|
}
|
|
1167
1226
|
catch { /* skip malformed lines */ }
|
|
1168
1227
|
}
|
|
@@ -1382,7 +1441,10 @@ class TaskLoopRunner {
|
|
|
1382
1441
|
this._cb?.log(`⏳ opencode-cli exited cleanly with [~] task — waiting for external response…`);
|
|
1383
1442
|
}
|
|
1384
1443
|
else {
|
|
1385
|
-
|
|
1444
|
+
// Exclude tasks flagged as provider hard-failures: those were left
|
|
1445
|
+
// [~] ON PURPOSE to signal "blocked / needs attention". Auto-marking
|
|
1446
|
+
// them done here would re-introduce the exact false-green we fixed.
|
|
1447
|
+
const stranded = tasks.filter(t => t.status === 'in-progress' && !this._blockedTasks.has(t.id ?? t.text));
|
|
1386
1448
|
for (const t of stranded) {
|
|
1387
1449
|
// Auto-mark as [x] done rather than resetting to [ ].
|
|
1388
1450
|
// Resetting to [ ] causes an infinite loop: the agent picks it
|
|
@@ -1417,6 +1479,10 @@ class TaskLoopRunner {
|
|
|
1417
1479
|
// If the agent hasn't marked it done after 3 attempts, force-mark it
|
|
1418
1480
|
// ourselves and move on so the queue doesn't get stuck.
|
|
1419
1481
|
const taskKey = task.id ?? task.text;
|
|
1482
|
+
// A fresh [ ] dispatch means this task is being retried — the provider
|
|
1483
|
+
// has presumably recovered, so clear any prior hard-failure flag and let
|
|
1484
|
+
// it complete (or fail) honestly on its own merits this time.
|
|
1485
|
+
this._blockedTasks.delete(taskKey);
|
|
1420
1486
|
const attempts = (this._taskAttempts.get(taskKey) ?? 0) + 1;
|
|
1421
1487
|
this._taskAttempts.set(taskKey, attempts);
|
|
1422
1488
|
const maxAttempts = settings.maxTaskAttempts ?? 3;
|
|
@@ -2614,10 +2680,60 @@ class TaskLoopRunner {
|
|
|
2614
2680
|
return;
|
|
2615
2681
|
}
|
|
2616
2682
|
}
|
|
2617
|
-
|
|
2683
|
+
// opencode's SDK path surfaces a session.error as an `[ERROR] …` line in
|
|
2684
|
+
// its stdout capture yet still writes exit-code 0 — so the process exit
|
|
2685
|
+
// code alone can't see it. Pass that sentinel through so a session.error
|
|
2686
|
+
// ("No model available" on a copilot-backed free plan, etc.) is treated
|
|
2687
|
+
// as the provider hard-failure it is, not a completed task.
|
|
2688
|
+
const stdoutError = ((isOpenCodeCli || isOpencodeSdk) && /\[ERROR\]/.test(exitStdout))
|
|
2689
|
+
? 'session-error'
|
|
2690
|
+
: null;
|
|
2691
|
+
const decision = exitHandler?.decide({ exitedNonZero, stdoutError })
|
|
2692
|
+
?? { kind: 'remind' };
|
|
2618
2693
|
if (decision.kind === 'done') {
|
|
2619
2694
|
return;
|
|
2620
2695
|
}
|
|
2696
|
+
if (decision.kind === 'hard_fail') {
|
|
2697
|
+
// The PROVIDER hard-failed (reauth / session error / watchdog-no-output
|
|
2698
|
+
// / crash / model unavailable) and produced no real work. Do NOT mark
|
|
2699
|
+
// the task [x] done — that would report an outage as SUCCESS. Instead
|
|
2700
|
+
// flag it [~] blocked, report an honest failure to the office (task_fail
|
|
2701
|
+
// → TASK_STATE_FAILED, "needs attention") and record a StopFailure hook
|
|
2702
|
+
// so external monitors see it too. The task stays claimable/retryable.
|
|
2703
|
+
const reason = decision.reason;
|
|
2704
|
+
this._failedCount++;
|
|
2705
|
+
const taskKey = task.id ?? task.text;
|
|
2706
|
+
this._blockedTasks.add(taskKey);
|
|
2707
|
+
// Drop the attempt counter so a later retry gets a clean run rather
|
|
2708
|
+
// than being force-marked done on its first re-dispatch.
|
|
2709
|
+
this._taskAttempts.delete(taskKey);
|
|
2710
|
+
this._cb?.log(`⛔ Provider hard-failure (${reason}) — NOT marking done; flagging task as blocked/needs-attention: ${discordLabel(task.text)}`);
|
|
2711
|
+
await todoWriteManager_1.todoWriter.markInProgress(todoPath, task).catch(() => { });
|
|
2712
|
+
const duration = Math.round((Date.now() - taskStartTime) / 1000);
|
|
2713
|
+
const errText = `provider hard-failure: ${reason} (task not completed — needs attention)`;
|
|
2714
|
+
this._notifyWebhook('task_fail', {
|
|
2715
|
+
iteration: this._iterations,
|
|
2716
|
+
task: { text: task.text, id: task.id },
|
|
2717
|
+
duration,
|
|
2718
|
+
error: errText,
|
|
2719
|
+
reason,
|
|
2720
|
+
workDir: this._workspaceRoot,
|
|
2721
|
+
gitRepo: this._gitRepo,
|
|
2722
|
+
gitBranch: this._gitBranch,
|
|
2723
|
+
});
|
|
2724
|
+
this._notifyDiscord(`⛔ **Provider failure — task blocked (not done):**\n${discordLabel(task.text)}\n\`${reason}\``);
|
|
2725
|
+
if (this._workspaceRoot) {
|
|
2726
|
+
(0, liveNarration_1.appendHookEventLine)(this._workspaceRoot, {
|
|
2727
|
+
hook_event_name: 'StopFailure', event_type: 'stop_failure',
|
|
2728
|
+
provider: activeProvider, cwd: this._workspaceRoot, error: reason,
|
|
2729
|
+
title: 'Provider failure', message: errText, tool_name: '',
|
|
2730
|
+
timestamp: new Date().toISOString(),
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
cleanup();
|
|
2734
|
+
resolve();
|
|
2735
|
+
return;
|
|
2736
|
+
}
|
|
2621
2737
|
if (decision.kind === 'deferred') {
|
|
2622
2738
|
this._cb?.log(`↩️ CLI exited with task [~] deferred — moving to next pending task: ${discordLabel(task.text)}`);
|
|
2623
2739
|
cleanup();
|