@wrongstack/cli 0.272.1 → 0.273.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/dist/index.js +1163 -400
- package/dist/index.js.map +1 -1
- package/package.json +13 -13
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { color, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, resolveHqDataDir, ensureHqFirstRunAuthFile, DEFAULT_HQ_REDACTION_POLICY, watchHqAuthFile, parseHqFrame, HQ_PROTOCOL_VERSION, parseHqEventPayload, scrubAndTruncateHqPreview, DefaultSecretScrubber, projectHash, wstackGlobalRoot, TOKENS, ToolRegistry, GlobalMailbox, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, normalizeTokenSavingTier, SlashCommandRegistry, attachDepWatcherBridge, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, buildTranscriptFromEvents, DEFAULT_CONTEXT_WINDOW_MODE_ID, resolveProjectDir, recentTextTurns, gatedEnhancerReasoning, enhanceUserPrompt, projectSlug, DefaultSystemPromptBuilder, mutateTasks, loadTasks, resolveContextWindowPolicy, repairToolUseAdjacency, mutatePlan, setPlanItemStatus, getPlanTemplate, loadPlan, emptyPlan, addPlanItem, savePlan, resolveProviderModelList, DefaultLogger, DefaultModelsRegistry, isStdinTTY, atomicWrite, DefaultPathResolver, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, makeAutonomyPromptContributor, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, createHqPublisherFromEnv, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, createDefaultPipelines, applyModelRuntime, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool,
|
|
2
|
+
import { color, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, resolveHqDataDir, ensureHqFirstRunAuthFile, DEFAULT_HQ_REDACTION_POLICY, watchHqAuthFile, parseHqFrame, HQ_PROTOCOL_VERSION, parseHqEventPayload, scrubAndTruncateHqPreview, DefaultSecretScrubber, resolveWstackPaths, projectHash, wstackGlobalRoot, TOKENS, ToolRegistry, GlobalMailbox, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, normalizeTokenSavingTier, SlashCommandRegistry, attachDepWatcherBridge, createFallbackModelExtension, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, SddRunRegistry, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, buildTranscriptFromEvents, DEFAULT_CONTEXT_WINDOW_MODE_ID, resolveProjectDir, recentTextTurns, gatedEnhancerReasoning, enhanceUserPrompt, projectSlug, DefaultSystemPromptBuilder, mutateTasks, loadTasks, resolveContextWindowPolicy, repairToolUseAdjacency, mutatePlan, setPlanItemStatus, getPlanTemplate, loadPlan, emptyPlan, addPlanItem, savePlan, resolveProviderModelList, DefaultLogger, DefaultModelsRegistry, isStdinTTY, atomicWrite, DefaultPathResolver, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, makeAutonomyPromptContributor, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, applyToolDescriptionModes, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, createHqPublisherFromEnv, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, createDefaultPipelines, applyModelRuntime, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, AdaptiveConcurrencyController, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, WIDE_SUBAGENT_CAPABILITIES, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, assignNickname, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, CHIMERA_REVIEW_PROMPT, setQueuedMessagesSnapshot, DefaultSessionRewinder, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, ToolAuditLog, DefaultSessionStore as DefaultSessionStore$1, ProviderRegistry, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, getContextWindowMode, AGENT_CATALOG, dispatchAgent, normalizeToolDescriptionMode, setToolDescriptionMode, getToolDescriptionMode, noOpVault, formatTodosList, formatTaskList, formatTaskProgress, formatPlan, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, readHqRuntimeFileSync, resolveHqConfig, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, onResize, InputBuilder, DefaultSecretVault, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, HQ_AUTH_FILE_VERSION, FsError, ERROR_CODES, truncate, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, ConfigError, resolveMailboxIdentity, smartDefaultFallbackChain, AutonomousCoordinator, mutateHqAuthFile, mintHqToken, readHqAuthFile, DefaultTokenCounter, isSecretField as isSecretField$1, startSessionTelemetryBridge } from '@wrongstack/core';
|
|
3
3
|
import * as fs4 from 'fs/promises';
|
|
4
4
|
import { decryptConfigSecrets, encryptConfigSecrets, DefaultSecretVault as DefaultSecretVault$1, isSecretField } from '@wrongstack/core/security';
|
|
5
5
|
import * as path4 from 'path';
|
|
@@ -8,11 +8,11 @@ import * as crypto2 from 'crypto';
|
|
|
8
8
|
import { createHash, randomUUID, randomBytes } from 'crypto';
|
|
9
9
|
import * as http from 'http';
|
|
10
10
|
import { createServer } from 'http';
|
|
11
|
-
import * as
|
|
12
|
-
import
|
|
11
|
+
import * as os7 from 'os';
|
|
12
|
+
import os7__default from 'os';
|
|
13
13
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
14
14
|
import { createRequire } from 'module';
|
|
15
|
-
import { findFreePort, AutoPhaseWebSocketHandler, WorktreeWebSocketHandler, generateAuthToken, verifyClient, handleGitInfo, handleShellOpen, handleGitDiff, handleGitChanges, handleSkillsExport, handleSkillsEdit, handleSkillsCreate, handleSkillsUpdate, handleSkillsUninstall, handleSkillsInstall, handleSkillsContent, handleMcpRestart, handleMcpDisable, handleMcpEnable, handleMcpDiscover, handleMcpSleep, handleMcpWake, handleMcpUpdate, handleMcpRemove, handleMcpAdd, handleMcpList, handleMemoryForget, handleMemoryRemember, handleMemoryList, handleCompletionRequest, createToolLspCompletionSource, handleFilesWrite, handleFilesRead, handleFilesTree, handleFilesList, createEternalSubscription, createHttpServer, registerInstance, openBrowser as openBrowser$1, unregisterInstance, estimateTokens as estimateTokens$1, stringifyContent, messagePreview, messageTokens, createCustomModeStore } from '@wrongstack/webui/server';
|
|
15
|
+
import { findFreePort, AutoPhaseWebSocketHandler, WorktreeWebSocketHandler, SpecsWebSocketHandler, SddBoardWebSocketHandler, SddWizardWebSocketHandler, buildSddWizardDeps, generateAuthToken, verifyClient, handleGitInfo, handleShellOpen, handleGitDiff, handleGitChanges, handleSkillsExport, handleSkillsEdit, handleSkillsCreate, handleSkillsUpdate, handleSkillsUninstall, handleSkillsInstall, handleSkillsContent, handleMcpRestart, handleMcpDisable, handleMcpEnable, handleMcpDiscover, handleMcpSleep, handleMcpWake, handleMcpUpdate, handleMcpRemove, handleMcpAdd, handleMcpList, handleMemoryForget, handleMemoryRemember, handleMemoryList, handleCompletionRequest, createToolLspCompletionSource, handleFilesWrite, handleFilesRead, handleFilesTree, handleFilesList, createEternalSubscription, createHttpServer, registerInstance, openBrowser as openBrowser$1, unregisterInstance, estimateTokens as estimateTokens$1, stringifyContent, messagePreview, messageTokens, createCustomModeStore } from '@wrongstack/webui/server';
|
|
16
16
|
import { toErrorMessage, readJsonObjectFile, updateJsonObjectFile, setJsonPath, jsonObjectFileExists, removeJsonPath } from '@wrongstack/core/utils';
|
|
17
17
|
import { setOAuthTokenPersister, makeProviderFromConfig, capabilitiesFor, buildProviderFactoriesFromRegistry, refreshCopilotToken, copilotBaseUrlFromToken } from '@wrongstack/providers';
|
|
18
18
|
import { getProcessRegistry, builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes, shutdownCodebaseIndexHost, TIER2_TOOLS, TIER3_TOOLS, TIER1_TOOLS, resetIndexCircuitBreaker } from '@wrongstack/tools';
|
|
@@ -657,26 +657,26 @@ function fmtDuration(ms) {
|
|
|
657
657
|
const remMin = m - h * 60;
|
|
658
658
|
return `${h}h${remMin}m`;
|
|
659
659
|
}
|
|
660
|
-
function fmtTaskResultLine(r,
|
|
660
|
+
function fmtTaskResultLine(r, color86) {
|
|
661
661
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
662
662
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
663
663
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
664
664
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
665
|
-
const errKindChip = errKind ?
|
|
666
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
665
|
+
const errKindChip = errKind ? color86.dim(` [${errKind}]`) : "";
|
|
666
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color86.dim(errTail)}` : "";
|
|
667
667
|
switch (r.status) {
|
|
668
668
|
case "success":
|
|
669
|
-
return { mark:
|
|
669
|
+
return { mark: color86.green("\u2713"), stats, tail: "" };
|
|
670
670
|
case "timeout":
|
|
671
671
|
return {
|
|
672
|
-
mark:
|
|
673
|
-
stats: `${
|
|
672
|
+
mark: color86.yellow("\u23F1"),
|
|
673
|
+
stats: `${color86.yellow("timeout")} ${stats}`,
|
|
674
674
|
tail: errSnip
|
|
675
675
|
};
|
|
676
676
|
case "stopped":
|
|
677
|
-
return { mark:
|
|
677
|
+
return { mark: color86.dim("\u2298"), stats: `${color86.dim("stopped")} ${stats}`, tail: errSnip };
|
|
678
678
|
case "failed":
|
|
679
|
-
return { mark:
|
|
679
|
+
return { mark: color86.red("\u2717"), stats: `${color86.red("failed")} ${stats}`, tail: errSnip };
|
|
680
680
|
}
|
|
681
681
|
}
|
|
682
682
|
var init_utils = __esm({
|
|
@@ -1335,6 +1335,19 @@ function sddHelp() {
|
|
|
1335
1335
|
" \u2502 /sdd cancel Cancel session \u2502",
|
|
1336
1336
|
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
1337
1337
|
"",
|
|
1338
|
+
" \u250C\u2500 \u26A1 Parallel Run \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1339
|
+
" \u2502 /sdd parallel [N] Run tasks in parallel (N slots) \u2502",
|
|
1340
|
+
" \u2502 /sdd stop Stop the active parallel run \u2502",
|
|
1341
|
+
" \u2502 /sdd retry-failed Requeue all failed tasks \u2502",
|
|
1342
|
+
" \u2502 /sdd split <id> <A ; B> Split a task into sub-tasks \u2502",
|
|
1343
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
1344
|
+
"",
|
|
1345
|
+
" \u250C\u2500 \u{1F9F9} Lifecycle (after /sdd stop) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1346
|
+
" \u2502 /sdd clean Remove run worktrees + branches \u2502",
|
|
1347
|
+
" \u2502 /sdd rollback Revert the run's merged commits \u2502",
|
|
1348
|
+
" \u2502 /sdd destroy Delete the whole SDD project \u2502",
|
|
1349
|
+
" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
|
|
1350
|
+
"",
|
|
1338
1351
|
" \u250C\u2500 \u{1F4C1} Spec History \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
|
|
1339
1352
|
" \u2502 /sdd list List saved specs \u2502",
|
|
1340
1353
|
" \u2502 /sdd show <id> Show spec details \u2502",
|
|
@@ -1626,9 +1639,92 @@ Start executing the tasks one by one.`
|
|
|
1626
1639
|
);
|
|
1627
1640
|
return { message };
|
|
1628
1641
|
}
|
|
1629
|
-
case "stop":
|
|
1642
|
+
case "stop":
|
|
1643
|
+
case "abort": {
|
|
1630
1644
|
opts.onSddParallelStop?.();
|
|
1631
|
-
return { message: "SDD parallel run stopped." };
|
|
1645
|
+
return { message: "SDD parallel run stopped. Use /sdd clean to remove worktrees, /sdd rollback to undo commits, or /sdd destroy to delete the project." };
|
|
1646
|
+
}
|
|
1647
|
+
case "clean":
|
|
1648
|
+
case "cleanup":
|
|
1649
|
+
case "worktrees": {
|
|
1650
|
+
if (!opts.onSddCleanWorktrees) {
|
|
1651
|
+
return { message: "Worktree cleanup is not available in this session." };
|
|
1652
|
+
}
|
|
1653
|
+
const removed = await opts.onSddCleanWorktrees();
|
|
1654
|
+
return {
|
|
1655
|
+
message: removed > 0 ? `Cleaned ${removed} SDD worktree${removed === 1 ? "" : "s"} (and their wstack/ap branches).` : "No SDD worktrees to clean."
|
|
1656
|
+
};
|
|
1657
|
+
}
|
|
1658
|
+
case "rollback":
|
|
1659
|
+
case "revert": {
|
|
1660
|
+
if (!opts.onSddRollback) {
|
|
1661
|
+
return { message: "Rollback is not available in this session." };
|
|
1662
|
+
}
|
|
1663
|
+
const res = await opts.onSddRollback();
|
|
1664
|
+
if (res.ok) {
|
|
1665
|
+
return {
|
|
1666
|
+
message: res.reverted > 0 ? `Rolled back ${res.reverted} run commit${res.reverted === 1 ? "" : "s"} (revert commits added \u2014 history preserved).` : "Nothing to roll back."
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
return {
|
|
1670
|
+
message: `Rollback failed${res.reverted ? ` after ${res.reverted} revert(s)` : ""}: ${res.reason ?? "unknown error"}`
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
case "destroy":
|
|
1674
|
+
case "nuke": {
|
|
1675
|
+
if (!opts.onSddDestroy) {
|
|
1676
|
+
return { message: "Destroy is not available in this session." };
|
|
1677
|
+
}
|
|
1678
|
+
const res = await opts.onSddDestroy();
|
|
1679
|
+
const builder = sddState.getBuilder();
|
|
1680
|
+
if (builder) {
|
|
1681
|
+
await builder.deleteSession().catch(() => {
|
|
1682
|
+
});
|
|
1683
|
+
sddState.setBuilder(null);
|
|
1684
|
+
}
|
|
1685
|
+
sddState.clearTaskState();
|
|
1686
|
+
const parts = [
|
|
1687
|
+
`SDD project destroyed.`,
|
|
1688
|
+
res.worktreesRemoved > 0 ? ` Removed ${res.worktreesRemoved} worktree${res.worktreesRemoved === 1 ? "" : "s"}.` : "",
|
|
1689
|
+
res.deleted.length ? ` Deleted: ${res.deleted.join(", ")}.` : "",
|
|
1690
|
+
` (Merged commits left intact \u2014 use /sdd rollback to undo them.)`
|
|
1691
|
+
].filter(Boolean);
|
|
1692
|
+
return { message: parts.join("\n") };
|
|
1693
|
+
}
|
|
1694
|
+
case "retry-failed":
|
|
1695
|
+
case "retry-all": {
|
|
1696
|
+
if (!opts.onSddRetryAllFailed) {
|
|
1697
|
+
return { message: "No active SDD parallel run to retry." };
|
|
1698
|
+
}
|
|
1699
|
+
const n = opts.onSddRetryAllFailed();
|
|
1700
|
+
return {
|
|
1701
|
+
message: n > 0 ? `Requeued ${n} failed task${n === 1 ? "" : "s"} to pending.` : "No failed tasks to retry."
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
case "split": {
|
|
1705
|
+
if (!opts.onSddSplitTask) {
|
|
1706
|
+
return { message: "No active SDD parallel run to split a task in." };
|
|
1707
|
+
}
|
|
1708
|
+
const taskId = restArgs[0];
|
|
1709
|
+
if (!taskId) {
|
|
1710
|
+
return { message: "Usage: /sdd split <task-id> <subtask ; subtask ; \u2026>" };
|
|
1711
|
+
}
|
|
1712
|
+
const subtasks = restArgs.slice(1).join(" ").split(";").map((s) => s.trim()).filter(Boolean).map((piece) => {
|
|
1713
|
+
const [title, ...rest] = piece.split("::");
|
|
1714
|
+
const t = (title ?? "").trim();
|
|
1715
|
+
const d = rest.join("::").trim();
|
|
1716
|
+
return { title: t, description: d || t };
|
|
1717
|
+
}).filter((s) => s.title);
|
|
1718
|
+
if (subtasks.length < 2) {
|
|
1719
|
+
return { message: "Provide at least two sub-tasks: /sdd split <task-id> <A ; B>" };
|
|
1720
|
+
}
|
|
1721
|
+
const ids = opts.onSddSplitTask(taskId, subtasks);
|
|
1722
|
+
if (ids === null) {
|
|
1723
|
+
return { message: `No active run, or task "${taskId}" is unknown / running (can't split).` };
|
|
1724
|
+
}
|
|
1725
|
+
return {
|
|
1726
|
+
message: `Split ${taskId} into ${ids.length} sub-task${ids.length === 1 ? "" : "s"}: ${ids.join(", ")}`
|
|
1727
|
+
};
|
|
1632
1728
|
}
|
|
1633
1729
|
case "plan":
|
|
1634
1730
|
case "impl": {
|
|
@@ -2117,20 +2213,25 @@ Start executing the tasks one by one.`
|
|
|
2117
2213
|
return { message: lines.join("\n") };
|
|
2118
2214
|
}
|
|
2119
2215
|
case "cancel": {
|
|
2120
|
-
const sessionPath = opts.paths.projectSddSession;
|
|
2121
2216
|
let deletedFromDisk = false;
|
|
2122
|
-
|
|
2123
|
-
await
|
|
2124
|
-
deletedFromDisk =
|
|
2125
|
-
}
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2217
|
+
if (opts.onSddDestroy) {
|
|
2218
|
+
const res = await opts.onSddDestroy();
|
|
2219
|
+
deletedFromDisk = res.deleted.length > 0 || res.worktreesRemoved > 0;
|
|
2220
|
+
} else {
|
|
2221
|
+
const sessionPath = opts.paths.projectSddSession;
|
|
2222
|
+
try {
|
|
2223
|
+
await fs4.unlink(sessionPath);
|
|
2224
|
+
deletedFromDisk = true;
|
|
2225
|
+
} catch {
|
|
2226
|
+
}
|
|
2227
|
+
try {
|
|
2228
|
+
await fs4.rm(opts.paths.projectSpecs, { recursive: true, force: true });
|
|
2229
|
+
} catch {
|
|
2230
|
+
}
|
|
2231
|
+
try {
|
|
2232
|
+
await fs4.rm(opts.paths.projectTaskGraphs, { recursive: true, force: true });
|
|
2233
|
+
} catch {
|
|
2234
|
+
}
|
|
2134
2235
|
}
|
|
2135
2236
|
const cancelBuilder = sddState.getBuilder();
|
|
2136
2237
|
if (cancelBuilder) {
|
|
@@ -4108,7 +4209,7 @@ async function clearHqRuntimeMarker(dataDir, url) {
|
|
|
4108
4209
|
function lanIPv4Addresses() {
|
|
4109
4210
|
const out = [];
|
|
4110
4211
|
try {
|
|
4111
|
-
const ifaces =
|
|
4212
|
+
const ifaces = os7.networkInterfaces();
|
|
4112
4213
|
for (const name of Object.keys(ifaces)) {
|
|
4113
4214
|
for (const ni of ifaces[name] ?? []) {
|
|
4114
4215
|
if (ni.family === "IPv4" && !ni.internal) out.push(ni.address);
|
|
@@ -4210,7 +4311,7 @@ async function handleApiSessions(res) {
|
|
|
4210
4311
|
}
|
|
4211
4312
|
}
|
|
4212
4313
|
async function handleApiSessionEvents(res, sessionId, limit, full, transcripts) {
|
|
4213
|
-
const { SessionRegistry, resolveWstackPaths:
|
|
4314
|
+
const { SessionRegistry, resolveWstackPaths: resolveWstackPaths6, DefaultSessionStore: DefaultSessionStore5 } = await import('@wrongstack/core');
|
|
4214
4315
|
const globalRoot = path4.dirname(resolveHqDataDir());
|
|
4215
4316
|
try {
|
|
4216
4317
|
const registry = new SessionRegistry(globalRoot);
|
|
@@ -4221,7 +4322,7 @@ async function handleApiSessionEvents(res, sessionId, limit, full, transcripts)
|
|
|
4221
4322
|
let clientType;
|
|
4222
4323
|
let projectName;
|
|
4223
4324
|
if (entry) {
|
|
4224
|
-
const paths =
|
|
4325
|
+
const paths = resolveWstackPaths6({ projectRoot: entry.projectRoot, globalRoot });
|
|
4225
4326
|
const store = new DefaultSessionStore5({ dir: paths.projectSessions });
|
|
4226
4327
|
const data = await store.load(sessionId).catch(() => null);
|
|
4227
4328
|
if (data) {
|
|
@@ -5117,7 +5218,7 @@ async function getUpdateNotification(signal, homeFn) {
|
|
|
5117
5218
|
var defaultHomeDir2, CACHE_TTL_MS;
|
|
5118
5219
|
var init_update_check = __esm({
|
|
5119
5220
|
"src/update-check.ts"() {
|
|
5120
|
-
defaultHomeDir2 = () =>
|
|
5221
|
+
defaultHomeDir2 = () => os7.homedir();
|
|
5121
5222
|
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5122
5223
|
}
|
|
5123
5224
|
});
|
|
@@ -5223,6 +5324,8 @@ async function seedConfigToMeta(opts) {
|
|
|
5223
5324
|
meta["enhanceDelayMs"] = autonomyCfg["enhanceDelayMs"] ?? 6e4;
|
|
5224
5325
|
meta["enhanceLanguage"] = autonomyCfg["enhanceLanguage"] ?? "original";
|
|
5225
5326
|
meta["nextPrediction"] = cfg.nextPrediction ?? false;
|
|
5327
|
+
meta["fallbackModels"] = cfg.fallbackModels ?? [];
|
|
5328
|
+
meta["fallbackAuto"] = cfg.fallbackAuto !== false;
|
|
5226
5329
|
meta["featureMcp"] = features["mcp"] !== false;
|
|
5227
5330
|
meta["featurePlugins"] = features["plugins"] !== false;
|
|
5228
5331
|
meta["featureMemory"] = features["memory"] !== false;
|
|
@@ -5269,8 +5372,7 @@ function createPrefsSeeding(opts) {
|
|
|
5269
5372
|
const decrypted = decryptConfigSecrets$1(parsed, vault);
|
|
5270
5373
|
const autonomy = decrypted.autonomy ?? {};
|
|
5271
5374
|
if ("autonomy" in payload) autonomy["defaultMode"] = payload["autonomy"];
|
|
5272
|
-
if ("autonomyDelayMs" in payload)
|
|
5273
|
-
autonomy["autoProceedDelayMs"] = payload["autonomyDelayMs"];
|
|
5375
|
+
if ("autonomyDelayMs" in payload) autonomy["autoProceedDelayMs"] = payload["autonomyDelayMs"];
|
|
5274
5376
|
if ("autoProceedMaxIterations" in payload)
|
|
5275
5377
|
autonomy["autoProceedMaxIterations"] = payload["autoProceedMaxIterations"];
|
|
5276
5378
|
if ("yolo" in payload) {
|
|
@@ -5282,9 +5384,10 @@ function createPrefsSeeding(opts) {
|
|
|
5282
5384
|
if ("streamFleet" in payload) autonomy["streamFleet"] = payload["streamFleet"];
|
|
5283
5385
|
if ("enhanceEnabled" in payload) autonomy["enhance"] = payload["enhanceEnabled"];
|
|
5284
5386
|
if ("enhanceDelayMs" in payload) autonomy["enhanceDelayMs"] = payload["enhanceDelayMs"];
|
|
5285
|
-
if ("enhanceLanguage" in payload)
|
|
5286
|
-
autonomy["enhanceLanguage"] = payload["enhanceLanguage"];
|
|
5387
|
+
if ("enhanceLanguage" in payload) autonomy["enhanceLanguage"] = payload["enhanceLanguage"];
|
|
5287
5388
|
if ("nextPrediction" in payload) decrypted["nextPrediction"] = payload["nextPrediction"];
|
|
5389
|
+
if ("fallbackModels" in payload) decrypted["fallbackModels"] = payload["fallbackModels"];
|
|
5390
|
+
if ("fallbackAuto" in payload) decrypted["fallbackAuto"] = payload["fallbackAuto"];
|
|
5288
5391
|
decrypted["autonomy"] = autonomy;
|
|
5289
5392
|
if ("featureMcp" in payload || "featurePlugins" in payload || "featureMemory" in payload || "featureSkills" in payload || "featureModelsRegistry" in payload) {
|
|
5290
5393
|
const features = decrypted.features ?? {};
|
|
@@ -5380,6 +5483,8 @@ var init_prefs_seeding = __esm({
|
|
|
5380
5483
|
"confirmExit",
|
|
5381
5484
|
"streamFleet",
|
|
5382
5485
|
"nextPrediction",
|
|
5486
|
+
"fallbackModels",
|
|
5487
|
+
"fallbackAuto",
|
|
5383
5488
|
"enhanceEnabled",
|
|
5384
5489
|
"enhanceDelayMs",
|
|
5385
5490
|
"enhanceLanguage",
|
|
@@ -6413,22 +6518,29 @@ var init_projects = __esm({
|
|
|
6413
6518
|
function sendResult7(ctx, ws, success, message) {
|
|
6414
6519
|
ctx.send(ws, { type: "key.operation_result", payload: { success, message } });
|
|
6415
6520
|
}
|
|
6521
|
+
function projectSavedProviders(providers) {
|
|
6522
|
+
return Object.entries(providers).map(([id, cfg]) => {
|
|
6523
|
+
const models = cfg.models;
|
|
6524
|
+
const pickedModelId = models && models.length > 0 ? models[0] : void 0;
|
|
6525
|
+
return {
|
|
6526
|
+
id,
|
|
6527
|
+
family: cfg.family ?? id,
|
|
6528
|
+
baseUrl: cfg.baseUrl,
|
|
6529
|
+
models,
|
|
6530
|
+
...pickedModelId !== void 0 ? { pickedModelId } : {},
|
|
6531
|
+
apiKeys: normalizeKeys(cfg).map((k) => ({
|
|
6532
|
+
label: k.label,
|
|
6533
|
+
maskedKey: maskedKey(k.apiKey),
|
|
6534
|
+
isActive: k.label === cfg.activeKey,
|
|
6535
|
+
createdAt: k.createdAt
|
|
6536
|
+
}))
|
|
6537
|
+
};
|
|
6538
|
+
});
|
|
6539
|
+
}
|
|
6416
6540
|
function broadcastSaved(ctx, providers) {
|
|
6417
6541
|
ctx.broadcast({
|
|
6418
6542
|
type: "providers.saved",
|
|
6419
|
-
payload: {
|
|
6420
|
-
providers: Object.entries(providers).map(([id, cfg]) => ({
|
|
6421
|
-
id,
|
|
6422
|
-
family: cfg.family,
|
|
6423
|
-
baseUrl: cfg.baseUrl,
|
|
6424
|
-
apiKeys: normalizeKeys(cfg).map((k) => ({
|
|
6425
|
-
label: k.label,
|
|
6426
|
-
maskedKey: maskedKey(k.apiKey),
|
|
6427
|
-
isActive: k.label === cfg.activeKey,
|
|
6428
|
-
createdAt: k.createdAt
|
|
6429
|
-
}))
|
|
6430
|
-
}))
|
|
6431
|
-
}
|
|
6543
|
+
payload: { providers: projectSavedProviders(providers) }
|
|
6432
6544
|
});
|
|
6433
6545
|
}
|
|
6434
6546
|
async function handleProvidersList(ctx, ws) {
|
|
@@ -6484,19 +6596,7 @@ async function handleProvidersSaved(ctx, ws) {
|
|
|
6484
6596
|
const providers = await ctx.providerStore.load();
|
|
6485
6597
|
ctx.send(ws, {
|
|
6486
6598
|
type: "providers.saved",
|
|
6487
|
-
payload: {
|
|
6488
|
-
providers: Object.entries(providers).map(([id, cfg]) => ({
|
|
6489
|
-
id,
|
|
6490
|
-
family: cfg.family,
|
|
6491
|
-
baseUrl: cfg.baseUrl,
|
|
6492
|
-
apiKeys: normalizeKeys(cfg).map((k) => ({
|
|
6493
|
-
label: k.label,
|
|
6494
|
-
maskedKey: maskedKey(k.apiKey),
|
|
6495
|
-
isActive: k.label === cfg.activeKey,
|
|
6496
|
-
createdAt: k.createdAt
|
|
6497
|
-
}))
|
|
6498
|
-
}))
|
|
6499
|
-
}
|
|
6599
|
+
payload: { providers: projectSavedProviders(providers) }
|
|
6500
6600
|
});
|
|
6501
6601
|
} catch (err) {
|
|
6502
6602
|
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
@@ -6518,6 +6618,7 @@ async function handleKeyUpsert(ctx, ws, providerId, label, apiKey) {
|
|
|
6518
6618
|
providers[providerId] = existing;
|
|
6519
6619
|
await ctx.providerStore.save(providers);
|
|
6520
6620
|
sendResult7(ctx, ws, true, `Key "${label}" saved for ${providerId}`);
|
|
6621
|
+
broadcastSaved(ctx, providers);
|
|
6521
6622
|
} catch (err) {
|
|
6522
6623
|
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
6523
6624
|
}
|
|
@@ -6542,6 +6643,7 @@ async function handleKeyDelete(ctx, ws, providerId, label) {
|
|
|
6542
6643
|
}
|
|
6543
6644
|
await ctx.providerStore.save(providers);
|
|
6544
6645
|
sendResult7(ctx, ws, true, `Key "${label}" deleted from ${providerId}`);
|
|
6646
|
+
broadcastSaved(ctx, providers);
|
|
6545
6647
|
} catch (err) {
|
|
6546
6648
|
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
6547
6649
|
}
|
|
@@ -6559,6 +6661,7 @@ async function handleKeySetActive(ctx, ws, providerId, label) {
|
|
|
6559
6661
|
providers[providerId] = existing;
|
|
6560
6662
|
await ctx.providerStore.save(providers);
|
|
6561
6663
|
sendResult7(ctx, ws, true, `Active key for ${providerId} set to "${label}"`);
|
|
6664
|
+
broadcastSaved(ctx, providers);
|
|
6562
6665
|
} catch (err) {
|
|
6563
6666
|
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
6564
6667
|
}
|
|
@@ -6588,22 +6691,7 @@ async function handleProviderAdd(ctx, ws, payload) {
|
|
|
6588
6691
|
await ctx.providerStore.save(providers);
|
|
6589
6692
|
sendResult7(ctx, ws, true, `Provider "${payload.id}" added`);
|
|
6590
6693
|
ctx.log(`[WebUI] Provider "${payload.id}" added via provider.add`);
|
|
6591
|
-
ctx
|
|
6592
|
-
type: "providers.saved",
|
|
6593
|
-
payload: {
|
|
6594
|
-
providers: Object.entries(providers).map(([id, cfg]) => ({
|
|
6595
|
-
id,
|
|
6596
|
-
family: cfg.family,
|
|
6597
|
-
baseUrl: cfg.baseUrl,
|
|
6598
|
-
apiKeys: normalizeKeys(cfg).map((k) => ({
|
|
6599
|
-
label: k.label,
|
|
6600
|
-
maskedKey: maskedKey(k.apiKey),
|
|
6601
|
-
isActive: k.label === cfg.activeKey,
|
|
6602
|
-
createdAt: k.createdAt
|
|
6603
|
-
}))
|
|
6604
|
-
}))
|
|
6605
|
-
}
|
|
6606
|
-
});
|
|
6694
|
+
broadcastSaved(ctx, providers);
|
|
6607
6695
|
} catch (err) {
|
|
6608
6696
|
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
6609
6697
|
}
|
|
@@ -6618,6 +6706,7 @@ async function handleProviderRemove(ctx, ws, providerId) {
|
|
|
6618
6706
|
delete providers[providerId];
|
|
6619
6707
|
await ctx.providerStore.save(providers);
|
|
6620
6708
|
sendResult7(ctx, ws, true, `Provider "${providerId}" removed`);
|
|
6709
|
+
broadcastSaved(ctx, providers);
|
|
6621
6710
|
} catch (err) {
|
|
6622
6711
|
sendResult7(ctx, ws, false, toErrorMessage(err));
|
|
6623
6712
|
}
|
|
@@ -7235,7 +7324,7 @@ async function runWebUI(opts) {
|
|
|
7235
7324
|
})();
|
|
7236
7325
|
return customModeStoreP;
|
|
7237
7326
|
};
|
|
7238
|
-
const autoPhaseStoreDir = opts.projectRoot ? path4.join(opts.projectRoot, ".wrongstack", "autophase") : path4.join(
|
|
7327
|
+
const autoPhaseStoreDir = opts.projectRoot ? path4.join(opts.projectRoot, ".wrongstack", "autophase") : path4.join(os7.tmpdir(), ".wrongstack", "autophase");
|
|
7239
7328
|
const autoPhaseHandler = new AutoPhaseWebSocketHandler(
|
|
7240
7329
|
opts.agent,
|
|
7241
7330
|
opts.agent.ctx,
|
|
@@ -7245,6 +7334,29 @@ async function runWebUI(opts) {
|
|
|
7245
7334
|
opts.projectRoot
|
|
7246
7335
|
);
|
|
7247
7336
|
const worktreeHandler = new WorktreeWebSocketHandler(opts.events, consoleLogger);
|
|
7337
|
+
const specsPaths = opts.projectRoot ? resolveWstackPaths({ projectRoot: opts.projectRoot }) : null;
|
|
7338
|
+
const specsHandler = new SpecsWebSocketHandler(
|
|
7339
|
+
specsPaths?.projectSpecs ?? path4.join(os7.tmpdir(), ".wrongstack", "specs"),
|
|
7340
|
+
specsPaths?.projectTaskGraphs ?? path4.join(os7.tmpdir(), ".wrongstack", "task-graphs")
|
|
7341
|
+
);
|
|
7342
|
+
const sddBoardHandler = new SddBoardWebSocketHandler(
|
|
7343
|
+
specsPaths?.projectSddBoards ?? path4.join(os7.tmpdir(), ".wrongstack", "sdd-boards"),
|
|
7344
|
+
opts.events
|
|
7345
|
+
);
|
|
7346
|
+
const sddWizardHandler = opts.sddSubagentFactory && specsPaths ? new SddWizardWebSocketHandler(
|
|
7347
|
+
buildSddWizardDeps({
|
|
7348
|
+
agent: opts.agent,
|
|
7349
|
+
events: opts.events,
|
|
7350
|
+
projectRoot: opts.projectRoot ?? process.cwd(),
|
|
7351
|
+
subagentFactory: opts.sddSubagentFactory,
|
|
7352
|
+
paths: {
|
|
7353
|
+
projectSpecs: specsPaths.projectSpecs,
|
|
7354
|
+
projectTaskGraphs: specsPaths.projectTaskGraphs,
|
|
7355
|
+
projectSddBoards: specsPaths.projectSddBoards,
|
|
7356
|
+
projectDir: specsPaths.projectDir
|
|
7357
|
+
}
|
|
7358
|
+
})
|
|
7359
|
+
) : null;
|
|
7248
7360
|
await seedConfigToMeta(opts);
|
|
7249
7361
|
const { prefSnapshot, persistPrefs } = createPrefsSeeding(opts);
|
|
7250
7362
|
const sessionStartedAt = Date.now();
|
|
@@ -8241,6 +8353,9 @@ ${text}` : text;
|
|
|
8241
8353
|
const client = { ws, sessionId: opts.session.id };
|
|
8242
8354
|
clients.set(ws, client);
|
|
8243
8355
|
autoPhaseHandler.addClient(ws);
|
|
8356
|
+
specsHandler.addClient(ws);
|
|
8357
|
+
sddBoardHandler.addClient(ws);
|
|
8358
|
+
sddWizardHandler?.addClient(ws);
|
|
8244
8359
|
worktreeHandler.addClient(ws);
|
|
8245
8360
|
let msgCount = 0;
|
|
8246
8361
|
let windowResetAt = Date.now() + 6e4;
|
|
@@ -8845,7 +8960,11 @@ ${text}` : text;
|
|
|
8845
8960
|
break;
|
|
8846
8961
|
// ── Mailbox operations — project-level inter-agent messaging (PR 8 of #30) ────
|
|
8847
8962
|
case "mailbox.messages":
|
|
8848
|
-
await handleMailboxMessages(
|
|
8963
|
+
await handleMailboxMessages(
|
|
8964
|
+
mailboxCtx,
|
|
8965
|
+
msg,
|
|
8966
|
+
ws
|
|
8967
|
+
);
|
|
8849
8968
|
break;
|
|
8850
8969
|
case "mailbox.agents":
|
|
8851
8970
|
await handleMailboxAgents(mailboxCtx, msg, ws);
|
|
@@ -8867,6 +8986,18 @@ ${text}` : text;
|
|
|
8867
8986
|
await autoPhaseHandler.handleMessage(
|
|
8868
8987
|
msg
|
|
8869
8988
|
);
|
|
8989
|
+
} else if (msgType.startsWith("specs.")) {
|
|
8990
|
+
await specsHandler.handleMessage(
|
|
8991
|
+
msg
|
|
8992
|
+
);
|
|
8993
|
+
} else if (msgType.startsWith("sdd.board.")) {
|
|
8994
|
+
await sddBoardHandler.handleMessage(
|
|
8995
|
+
msg
|
|
8996
|
+
);
|
|
8997
|
+
} else if (msgType.startsWith("sdd.spec.") || msgType.startsWith("sdd.run.")) {
|
|
8998
|
+
await sddWizardHandler?.handleMessage(
|
|
8999
|
+
msg
|
|
9000
|
+
);
|
|
8870
9001
|
} else {
|
|
8871
9002
|
console.debug(`[WebUI] Unhandled message type: ${msgType}`);
|
|
8872
9003
|
}
|
|
@@ -8918,6 +9049,12 @@ var init_webui_server = __esm({
|
|
|
8918
9049
|
|
|
8919
9050
|
// src/cli-main.ts
|
|
8920
9051
|
init_provider_config_utils();
|
|
9052
|
+
var DEFAULT_TASK_CONCURRENCY = 2;
|
|
9053
|
+
function resolveTaskConcurrency() {
|
|
9054
|
+
const raw = Number.parseInt(process.env["WRONGSTACK_AUTOPHASE_TASK_CONCURRENCY"] ?? "", 10);
|
|
9055
|
+
if (!Number.isFinite(raw)) return DEFAULT_TASK_CONCURRENCY;
|
|
9056
|
+
return Math.min(8, Math.max(1, raw));
|
|
9057
|
+
}
|
|
8921
9058
|
var WORKTREE_PHASE_CONCURRENCY = 4;
|
|
8922
9059
|
var MAX_CMD_OUTPUT = 2e5;
|
|
8923
9060
|
function gitText(args, cwd) {
|
|
@@ -9131,6 +9268,7 @@ ${res.out}` };
|
|
|
9131
9268
|
};
|
|
9132
9269
|
}
|
|
9133
9270
|
const abort = new AbortController();
|
|
9271
|
+
const usedNicknames = /* @__PURE__ */ new Set();
|
|
9134
9272
|
log(`\u{1F9E0} Planning phases for: ${goal}`);
|
|
9135
9273
|
let phases;
|
|
9136
9274
|
try {
|
|
@@ -9180,9 +9318,16 @@ ${res.out}` };
|
|
|
9180
9318
|
executeTask: async (task, phaseId, env) => {
|
|
9181
9319
|
const phase = graph.phases.get(phaseId);
|
|
9182
9320
|
const phaseName = phase?.name ?? phaseId;
|
|
9321
|
+
let agentName = task.assignee;
|
|
9322
|
+
if (!agentName) {
|
|
9323
|
+
const nick = assignNickname("executor", usedNicknames);
|
|
9324
|
+
usedNicknames.add(nick.key);
|
|
9325
|
+
agentName = nick.display.replace(/\s*\([^)]*\)\s*$/, "");
|
|
9326
|
+
active?.orchestrator.setTaskAssignee(task.id, void 0, agentName);
|
|
9327
|
+
}
|
|
9183
9328
|
return runOnce(
|
|
9184
9329
|
buildTaskPrompt(task, phaseName, goal),
|
|
9185
|
-
`autophase-${
|
|
9330
|
+
`autophase-${agentName}`.slice(0, 48),
|
|
9186
9331
|
abort.signal,
|
|
9187
9332
|
env?.cwd
|
|
9188
9333
|
);
|
|
@@ -9227,9 +9372,11 @@ ${res.out}` };
|
|
|
9227
9372
|
// With isolation, parallelizable phases run concurrently; without it,
|
|
9228
9373
|
// stay strictly sequential to protect the shared working tree.
|
|
9229
9374
|
maxConcurrentPhases: worktrees ? deps.maxConcurrentPhases ?? WORKTREE_PHASE_CONCURRENCY : 1,
|
|
9230
|
-
//
|
|
9231
|
-
//
|
|
9232
|
-
|
|
9375
|
+
// Within a phase, todos share the phase worktree. Default to a small
|
|
9376
|
+
// amount of parallelism so multiple agents genuinely pick up different
|
|
9377
|
+
// tasks at once (visible on the board); raise/lower via
|
|
9378
|
+
// WRONGSTACK_AUTOPHASE_TASK_CONCURRENCY (1 = strictly sequential).
|
|
9379
|
+
maxConcurrentTasks: resolveTaskConcurrency()
|
|
9233
9380
|
});
|
|
9234
9381
|
const onUntyped = deps.events.on;
|
|
9235
9382
|
const offUntyped = deps.events.off;
|
|
@@ -9284,6 +9431,30 @@ ${res.out}` };
|
|
|
9284
9431
|
isRunning: () => a.orchestrator.isRunning()
|
|
9285
9432
|
};
|
|
9286
9433
|
},
|
|
9434
|
+
onAutoPhaseMoveTask(taskId, toPhaseId) {
|
|
9435
|
+
if (!active) return false;
|
|
9436
|
+
const ok = active.orchestrator.moveTask(taskId, toPhaseId);
|
|
9437
|
+
if (ok) void persist(active.graph);
|
|
9438
|
+
return ok;
|
|
9439
|
+
},
|
|
9440
|
+
onAutoPhaseAssignTask(taskId, agentId, agentName) {
|
|
9441
|
+
if (!active) return false;
|
|
9442
|
+
const ok = active.orchestrator.setTaskAssignee(taskId, agentId, agentName);
|
|
9443
|
+
if (ok) void persist(active.graph);
|
|
9444
|
+
return ok;
|
|
9445
|
+
},
|
|
9446
|
+
onAutoPhaseAddTask(phaseId, spec) {
|
|
9447
|
+
if (!active) return null;
|
|
9448
|
+
const id = active.orchestrator.addTask(phaseId, spec);
|
|
9449
|
+
if (id) void persist(active.graph);
|
|
9450
|
+
return id;
|
|
9451
|
+
},
|
|
9452
|
+
onAutoPhaseRetryTask(taskId) {
|
|
9453
|
+
if (!active) return false;
|
|
9454
|
+
const ok = active.orchestrator.requeueTask(taskId);
|
|
9455
|
+
if (ok) void persist(active.graph);
|
|
9456
|
+
return ok;
|
|
9457
|
+
},
|
|
9287
9458
|
async onWorktree(action, target) {
|
|
9288
9459
|
const root = deps.projectRoot;
|
|
9289
9460
|
if (!await isGitRepo(root)) return "\u26A0 Not a git repository \u2014 worktrees unavailable.";
|
|
@@ -9855,7 +10026,7 @@ function printLaunchHints(renderer, flags, opts = {}) {
|
|
|
9855
10026
|
renderer.write(`${lines.join("\n")}
|
|
9856
10027
|
`);
|
|
9857
10028
|
}
|
|
9858
|
-
var defaultUidFn = () =>
|
|
10029
|
+
var defaultUidFn = () => os7__default.userInfo().uid;
|
|
9859
10030
|
async function getFileUid(filePath) {
|
|
9860
10031
|
try {
|
|
9861
10032
|
const stat8 = await fs4.stat(filePath);
|
|
@@ -9865,7 +10036,7 @@ async function getFileUid(filePath) {
|
|
|
9865
10036
|
}
|
|
9866
10037
|
}
|
|
9867
10038
|
async function checkConfigOwnership(homeFn, uidFn = defaultUidFn) {
|
|
9868
|
-
if (
|
|
10039
|
+
if (os7__default.platform() === "win32") return true;
|
|
9869
10040
|
const cfg = configPath(homeFn);
|
|
9870
10041
|
const fileUid = await getFileUid(cfg);
|
|
9871
10042
|
if (fileUid === void 0) return true;
|
|
@@ -9956,7 +10127,7 @@ function diffSummary(oldCfg, newCfg) {
|
|
|
9956
10127
|
}
|
|
9957
10128
|
return changes.length > 0 ? changes.slice(0, 5).join(", ") : "no changes";
|
|
9958
10129
|
}
|
|
9959
|
-
var defaultHomeDir = () =>
|
|
10130
|
+
var defaultHomeDir = () => os7__default.homedir();
|
|
9960
10131
|
function historyDir(homeFn = defaultHomeDir) {
|
|
9961
10132
|
return path4.join(homeFn(), ".wrongstack", "config.history", "entries");
|
|
9962
10133
|
}
|
|
@@ -10172,6 +10343,10 @@ async function restoreLast(homeFn = defaultHomeDir) {
|
|
|
10172
10343
|
}
|
|
10173
10344
|
|
|
10174
10345
|
// src/provider-helpers.ts
|
|
10346
|
+
function visibleModelIds(providerId, config, catalogModelIds, cfg) {
|
|
10347
|
+
const entry = cfg ?? config.providers?.[providerId];
|
|
10348
|
+
return entry?.models !== void 0 ? [...entry.models] : [...catalogModelIds];
|
|
10349
|
+
}
|
|
10175
10350
|
function hasApiKey(provider, config) {
|
|
10176
10351
|
if (provider.envVars.some((v) => !!process.env[v])) return true;
|
|
10177
10352
|
const entry = config?.providers?.[provider.id];
|
|
@@ -10206,7 +10381,7 @@ async function buildPickableProviders(modelsRegistry, config) {
|
|
|
10206
10381
|
const inherited = catalogById.get(catalogType);
|
|
10207
10382
|
const family = cfg.family ?? inherited?.family ?? "unsupported";
|
|
10208
10383
|
if (family === "unsupported") continue;
|
|
10209
|
-
const models =
|
|
10384
|
+
const models = visibleModelIds(id, config, (inherited?.models ?? []).map((m) => m.id), cfg);
|
|
10210
10385
|
out.push({ id, family, models });
|
|
10211
10386
|
}
|
|
10212
10387
|
for (const p of catalog) {
|
|
@@ -10218,7 +10393,7 @@ async function buildPickableProviders(modelsRegistry, config) {
|
|
|
10218
10393
|
return out;
|
|
10219
10394
|
}
|
|
10220
10395
|
var theme = { primary: color.amber };
|
|
10221
|
-
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ??
|
|
10396
|
+
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os7__default.homedir()) {
|
|
10222
10397
|
try {
|
|
10223
10398
|
const { atomicWrite: atomicWrite15 } = await import('@wrongstack/core');
|
|
10224
10399
|
const fs40 = await import('fs/promises');
|
|
@@ -10301,7 +10476,7 @@ ${color.bold(theme.primary("WrongStack") + color.dim(" \u2014 Provider & Model S
|
|
|
10301
10476
|
// know which models their endpoint actually serves (e.g. LM
|
|
10302
10477
|
// Studio, vLLM, or a proxy with custom model ids). Otherwise the
|
|
10303
10478
|
// catalog list keeps providing suggestions.
|
|
10304
|
-
models:
|
|
10479
|
+
models: visibleModelIds(p.id, config ?? { providers: {} }, p.models.map((m) => m.id), cfg).map((m) => p.models.find((pm) => pm.id === m) ?? { id: m, name: m })
|
|
10305
10480
|
});
|
|
10306
10481
|
} else {
|
|
10307
10482
|
merged.push(p);
|
|
@@ -10318,7 +10493,7 @@ ${color.bold(theme.primary("WrongStack") + color.dim(" \u2014 Provider & Model S
|
|
|
10318
10493
|
family: cfg.family,
|
|
10319
10494
|
apiBase: cfg.baseUrl ?? inherited?.apiBase,
|
|
10320
10495
|
envVars: cfg.envVars ?? inherited?.envVars ?? [],
|
|
10321
|
-
models:
|
|
10496
|
+
models: visibleModelIds(id, config ?? { providers: {} }, (inherited?.models ?? []).map((m) => m.id), cfg).map((m) => inherited?.models.find((pm) => pm.id === m) ?? { id: m, name: m }),
|
|
10322
10497
|
npm: inherited?.npm
|
|
10323
10498
|
});
|
|
10324
10499
|
}
|
|
@@ -12885,133 +13060,6 @@ function stripSurroundingQuotes(s) {
|
|
|
12885
13060
|
}
|
|
12886
13061
|
return s;
|
|
12887
13062
|
}
|
|
12888
|
-
function parseModelRef(ref) {
|
|
12889
|
-
const trimmed = ref.trim();
|
|
12890
|
-
const slash = trimmed.indexOf("/");
|
|
12891
|
-
if (slash !== -1) {
|
|
12892
|
-
return {
|
|
12893
|
-
provider: trimmed.slice(0, slash) || void 0,
|
|
12894
|
-
model: trimmed.slice(slash + 1).trim()
|
|
12895
|
-
};
|
|
12896
|
-
}
|
|
12897
|
-
const parts = trimmed.split(/\s+/);
|
|
12898
|
-
if (parts.length >= 2) {
|
|
12899
|
-
return { provider: parts[0], model: parts.slice(1).join(" ") };
|
|
12900
|
-
}
|
|
12901
|
-
return { model: trimmed };
|
|
12902
|
-
}
|
|
12903
|
-
function shouldFallback(err) {
|
|
12904
|
-
if (err instanceof StreamHangError) {
|
|
12905
|
-
return 599;
|
|
12906
|
-
}
|
|
12907
|
-
if (!(err instanceof ProviderError)) return null;
|
|
12908
|
-
const s = err.status;
|
|
12909
|
-
if (s === 0) return s;
|
|
12910
|
-
if (s === 429 || s === 529 || s >= 500) return s;
|
|
12911
|
-
return null;
|
|
12912
|
-
}
|
|
12913
|
-
function providerHasKey(entry) {
|
|
12914
|
-
if (!entry) return false;
|
|
12915
|
-
if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) return true;
|
|
12916
|
-
if (Array.isArray(entry.apiKeys) && entry.apiKeys.some((k) => k?.apiKey)) return true;
|
|
12917
|
-
if (Array.isArray(entry.envVars) && entry.envVars.some((v) => !!process.env[v])) return true;
|
|
12918
|
-
return false;
|
|
12919
|
-
}
|
|
12920
|
-
var SMART_DEFAULT_MAX = 4;
|
|
12921
|
-
function smartDefaultFallbackChain(config) {
|
|
12922
|
-
const leaderProvider = config.provider;
|
|
12923
|
-
const leaderModel = config.model;
|
|
12924
|
-
const providers = config.providers ?? {};
|
|
12925
|
-
const seen = /* @__PURE__ */ new Set();
|
|
12926
|
-
const sameProvider = [];
|
|
12927
|
-
const crossProvider = [];
|
|
12928
|
-
const ids = Object.keys(providers).sort(
|
|
12929
|
-
(a, b) => a === leaderProvider ? -1 : b === leaderProvider ? 1 : a.localeCompare(b)
|
|
12930
|
-
);
|
|
12931
|
-
for (const id of ids) {
|
|
12932
|
-
const entry = providers[id];
|
|
12933
|
-
if (!providerHasKey(entry)) continue;
|
|
12934
|
-
const models = entry?.models ?? [];
|
|
12935
|
-
for (const model of models) {
|
|
12936
|
-
if (id === leaderProvider && model === leaderModel) continue;
|
|
12937
|
-
const ref = `${id}/${model}`;
|
|
12938
|
-
if (seen.has(ref)) continue;
|
|
12939
|
-
seen.add(ref);
|
|
12940
|
-
(id === leaderProvider ? sameProvider : crossProvider).push(ref);
|
|
12941
|
-
}
|
|
12942
|
-
}
|
|
12943
|
-
return [...sameProvider, ...crossProvider].slice(0, SMART_DEFAULT_MAX);
|
|
12944
|
-
}
|
|
12945
|
-
function effectiveFallbackChain(config) {
|
|
12946
|
-
const explicit = config.fallbackModels ?? [];
|
|
12947
|
-
if (explicit.length > 0) return explicit;
|
|
12948
|
-
if (config.fallbackAuto === false) return [];
|
|
12949
|
-
return smartDefaultFallbackChain(config);
|
|
12950
|
-
}
|
|
12951
|
-
function createFallbackModelExtension(deps) {
|
|
12952
|
-
let dirty = false;
|
|
12953
|
-
return {
|
|
12954
|
-
name: "fallback-model",
|
|
12955
|
-
beforeRun: async (ctx) => {
|
|
12956
|
-
if (!dirty) return;
|
|
12957
|
-
const cfg = deps.getConfig();
|
|
12958
|
-
try {
|
|
12959
|
-
ctx.provider = await deps.buildProvider(cfg.provider);
|
|
12960
|
-
ctx.model = cfg.model;
|
|
12961
|
-
await deps.onModelSwitch?.(cfg.provider, cfg.model);
|
|
12962
|
-
} catch (err) {
|
|
12963
|
-
deps.logger?.warn(
|
|
12964
|
-
`fallback-model: could not restore primary "${cfg.provider}/${cfg.model}": ${err instanceof Error ? err.message : String(err)}`
|
|
12965
|
-
);
|
|
12966
|
-
}
|
|
12967
|
-
dirty = false;
|
|
12968
|
-
},
|
|
12969
|
-
wrapProviderRunner: async (ctx, request, inner) => {
|
|
12970
|
-
try {
|
|
12971
|
-
return await inner(ctx, request);
|
|
12972
|
-
} catch (firstErr) {
|
|
12973
|
-
let lastErr = firstErr;
|
|
12974
|
-
const cfg = deps.getConfig();
|
|
12975
|
-
const chain = effectiveFallbackChain(cfg);
|
|
12976
|
-
for (const ref of chain) {
|
|
12977
|
-
const status = shouldFallback(lastErr);
|
|
12978
|
-
if (status === null) break;
|
|
12979
|
-
const parsed = parseModelRef(ref);
|
|
12980
|
-
if (!parsed.model) continue;
|
|
12981
|
-
const targetProviderId = parsed.provider ?? cfg.provider;
|
|
12982
|
-
const from = { providerId: ctx.provider.id, model: ctx.model };
|
|
12983
|
-
let nextProvider;
|
|
12984
|
-
try {
|
|
12985
|
-
nextProvider = await deps.buildProvider(targetProviderId);
|
|
12986
|
-
} catch (err) {
|
|
12987
|
-
deps.logger?.warn(
|
|
12988
|
-
`fallback-model: skipping "${ref}" \u2014 cannot build provider "${targetProviderId}": ${err instanceof Error ? err.message : String(err)}`
|
|
12989
|
-
);
|
|
12990
|
-
continue;
|
|
12991
|
-
}
|
|
12992
|
-
const providerSwitched = nextProvider.id !== from.providerId;
|
|
12993
|
-
ctx.provider = nextProvider;
|
|
12994
|
-
ctx.model = parsed.model;
|
|
12995
|
-
request.model = parsed.model;
|
|
12996
|
-
dirty = true;
|
|
12997
|
-
await deps.onModelSwitch?.(targetProviderId, parsed.model);
|
|
12998
|
-
deps.events.emit("provider.fallback", {
|
|
12999
|
-
from,
|
|
13000
|
-
to: { providerId: nextProvider.id, model: parsed.model },
|
|
13001
|
-
status,
|
|
13002
|
-
providerSwitched
|
|
13003
|
-
});
|
|
13004
|
-
try {
|
|
13005
|
-
return await inner(ctx, request);
|
|
13006
|
-
} catch (err) {
|
|
13007
|
-
lastErr = err;
|
|
13008
|
-
}
|
|
13009
|
-
}
|
|
13010
|
-
throw lastErr;
|
|
13011
|
-
}
|
|
13012
|
-
}
|
|
13013
|
-
};
|
|
13014
|
-
}
|
|
13015
13063
|
|
|
13016
13064
|
// src/slash-commands/fallback.ts
|
|
13017
13065
|
async function patchGlobalConfig(globalConfigPath, mutate) {
|
|
@@ -14186,7 +14234,7 @@ function buildFleetCommand(opts) {
|
|
|
14186
14234
|
// ── /fleet terminate <subagentId> ────────────────────────────────────
|
|
14187
14235
|
case "terminate":
|
|
14188
14236
|
case "stop":
|
|
14189
|
-
return handleTerminate(opts, subargs);
|
|
14237
|
+
return await handleTerminate(opts, subargs);
|
|
14190
14238
|
// ── /fleet spawn <role> [count] ──────────────────────────────────────
|
|
14191
14239
|
case "spawn":
|
|
14192
14240
|
case "add":
|
|
@@ -14357,7 +14405,7 @@ async function handleKill(opts, subargs) {
|
|
|
14357
14405
|
return { message: msg2 };
|
|
14358
14406
|
}
|
|
14359
14407
|
if (opts.onFleetKill) {
|
|
14360
|
-
const killed = opts.onFleetKill();
|
|
14408
|
+
const killed = await opts.onFleetKill();
|
|
14361
14409
|
const msg2 = `${color.red("\u2717 Killed")} ${killed} subagent(s).`;
|
|
14362
14410
|
opts.renderer.write(msg2);
|
|
14363
14411
|
return { message: msg2 };
|
|
@@ -14366,7 +14414,7 @@ async function handleKill(opts, subargs) {
|
|
|
14366
14414
|
opts.renderer.writeWarning(msg);
|
|
14367
14415
|
return { message: msg };
|
|
14368
14416
|
}
|
|
14369
|
-
function handleTerminate(opts, subargs) {
|
|
14417
|
+
async function handleTerminate(opts, subargs) {
|
|
14370
14418
|
const targetId = subargs[0];
|
|
14371
14419
|
if (!targetId) {
|
|
14372
14420
|
const msg2 = `${color.amber("\u26A0 /fleet terminate requires a subagentId.")} Use /fleet to see active ids.`;
|
|
@@ -14378,7 +14426,7 @@ function handleTerminate(opts, subargs) {
|
|
|
14378
14426
|
opts.renderer.writeWarning(msg2);
|
|
14379
14427
|
return { message: msg2 };
|
|
14380
14428
|
}
|
|
14381
|
-
const ok = opts.onFleetTerminate(targetId);
|
|
14429
|
+
const ok = await opts.onFleetTerminate(targetId);
|
|
14382
14430
|
if (ok) {
|
|
14383
14431
|
const msg2 = `${color.green("\u2713 Terminated")} subagent ${color.bold(targetId)}.`;
|
|
14384
14432
|
opts.renderer.write(msg2);
|
|
@@ -15044,7 +15092,7 @@ function buildInterruptCommand(opts) {
|
|
|
15044
15092
|
].join("\n"),
|
|
15045
15093
|
async run() {
|
|
15046
15094
|
const aborted = opts.interruptController?.abortLeader() ?? false;
|
|
15047
|
-
const killed = opts.onFleetKill
|
|
15095
|
+
const killed = opts.onFleetKill ? await opts.onFleetKill() : 0;
|
|
15048
15096
|
if (!aborted && killed === 0) {
|
|
15049
15097
|
return { message: color.dim(" Nothing to interrupt \u2014 no run in progress.") };
|
|
15050
15098
|
}
|
|
@@ -17262,7 +17310,7 @@ async function killSession(sessionId) {
|
|
|
17262
17310
|
};
|
|
17263
17311
|
}
|
|
17264
17312
|
}
|
|
17265
|
-
function
|
|
17313
|
+
function providerHasKey(entry) {
|
|
17266
17314
|
if (!entry) return false;
|
|
17267
17315
|
if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) return true;
|
|
17268
17316
|
if (Array.isArray(entry.apiKeys) && entry.apiKeys.some((k) => k?.apiKey)) return true;
|
|
@@ -17273,7 +17321,7 @@ function keyedProviderIds(config) {
|
|
|
17273
17321
|
const ids = /* @__PURE__ */ new Set();
|
|
17274
17322
|
if (config.provider) ids.add(config.provider);
|
|
17275
17323
|
for (const [id, entry] of Object.entries(config.providers ?? {})) {
|
|
17276
|
-
if (
|
|
17324
|
+
if (providerHasKey(entry)) ids.add(id);
|
|
17277
17325
|
}
|
|
17278
17326
|
return [...ids].sort();
|
|
17279
17327
|
}
|
|
@@ -17500,7 +17548,7 @@ function buildSetModelCommand(opts) {
|
|
|
17500
17548
|
issues.push(
|
|
17501
17549
|
`${color.red("\u2717")} ${color.amber(key)}: provider "${entry.provider}" is not configured`
|
|
17502
17550
|
);
|
|
17503
|
-
} else if (!
|
|
17551
|
+
} else if (!providerHasKey(provCfg2)) {
|
|
17504
17552
|
warnings.push(
|
|
17505
17553
|
`${color.amber("\u26A0")} ${color.amber(key)}: provider "${entry.provider}" has no API key`
|
|
17506
17554
|
);
|
|
@@ -18311,7 +18359,7 @@ ${parts.join("\n")}
|
|
|
18311
18359
|
return false;
|
|
18312
18360
|
}
|
|
18313
18361
|
if (fleetRunning > 0) {
|
|
18314
|
-
const killed = opts.onFleetKill
|
|
18362
|
+
const killed = opts.onFleetKill ? await opts.onFleetKill() : 0;
|
|
18315
18363
|
if (killed > 0) {
|
|
18316
18364
|
opts.renderer.write(color.dim(` Stopped ${killed} subagent(s).
|
|
18317
18365
|
`));
|
|
@@ -20495,6 +20543,150 @@ function buildTodosCommand(opts) {
|
|
|
20495
20543
|
}
|
|
20496
20544
|
};
|
|
20497
20545
|
}
|
|
20546
|
+
function fit(text, width) {
|
|
20547
|
+
if (text.length <= width) return text.padEnd(width);
|
|
20548
|
+
return `${text.slice(0, Math.max(0, width - 3))}...`;
|
|
20549
|
+
}
|
|
20550
|
+
function formatDescriptionMode(mode) {
|
|
20551
|
+
const raw = `desc:${mode}`;
|
|
20552
|
+
return mode === "simple" ? color.amber(raw) : color.cyan(raw);
|
|
20553
|
+
}
|
|
20554
|
+
function buildToolCommand(opts) {
|
|
20555
|
+
const help = [
|
|
20556
|
+
"Usage:",
|
|
20557
|
+
" /tool Show tool description-mode overrides",
|
|
20558
|
+
" /tool list List every tool and its description mode",
|
|
20559
|
+
" /tool <name> Show one tool mode",
|
|
20560
|
+
" /tool <name> simple Use a 1-2 line tool description",
|
|
20561
|
+
" /tool <name> extend Use the full/extended tool description",
|
|
20562
|
+
"",
|
|
20563
|
+
"Modes:",
|
|
20564
|
+
" simple Short top-level description and usage hint",
|
|
20565
|
+
" extend Full description (default)",
|
|
20566
|
+
"",
|
|
20567
|
+
"Examples:",
|
|
20568
|
+
" /tool read simple",
|
|
20569
|
+
" /tool bash extend"
|
|
20570
|
+
].join("\n");
|
|
20571
|
+
function getCurrentTools() {
|
|
20572
|
+
return opts.configStore.get().tools;
|
|
20573
|
+
}
|
|
20574
|
+
function nextToolsConfig(name, mode) {
|
|
20575
|
+
const current = getCurrentTools();
|
|
20576
|
+
const descriptionMode = { ...current.descriptionMode ?? {} };
|
|
20577
|
+
if (mode === "simple") {
|
|
20578
|
+
descriptionMode[name] = "simple";
|
|
20579
|
+
} else {
|
|
20580
|
+
delete descriptionMode[name];
|
|
20581
|
+
}
|
|
20582
|
+
return { ...current, descriptionMode };
|
|
20583
|
+
}
|
|
20584
|
+
async function persistMode(name, mode) {
|
|
20585
|
+
const nextTools = nextToolsConfig(name, mode);
|
|
20586
|
+
if (!opts.paths) {
|
|
20587
|
+
opts.configStore.update({ tools: nextTools });
|
|
20588
|
+
return false;
|
|
20589
|
+
}
|
|
20590
|
+
await persistConfigSetting(
|
|
20591
|
+
{
|
|
20592
|
+
configStore: opts.configStore,
|
|
20593
|
+
globalConfigPath: opts.paths.globalConfig,
|
|
20594
|
+
inProjectConfigPath: opts.paths.inProjectConfig,
|
|
20595
|
+
vault: noOpVault
|
|
20596
|
+
},
|
|
20597
|
+
(cfg) => {
|
|
20598
|
+
cfg.tools = nextTools;
|
|
20599
|
+
}
|
|
20600
|
+
);
|
|
20601
|
+
return true;
|
|
20602
|
+
}
|
|
20603
|
+
function formatOverrides() {
|
|
20604
|
+
const configured = opts.configStore.get().tools.descriptionMode ?? {};
|
|
20605
|
+
const simple = Object.entries(configured).filter(([, mode]) => normalizeToolDescriptionMode(mode) === "simple").map(([name]) => name).sort();
|
|
20606
|
+
return [
|
|
20607
|
+
`${color.bold("Tool descriptions")} ${color.dim(`(default: desc:extend)`)}`,
|
|
20608
|
+
"",
|
|
20609
|
+
simple.length > 0 ? ` ${formatDescriptionMode("simple")}: ${simple.map((name) => color.cyan(name)).join(", ")}` : ` ${formatDescriptionMode("simple")}: ${color.dim("none")}`,
|
|
20610
|
+
"",
|
|
20611
|
+
color.dim(" /tool <name> simple \xB7 /tool <name> extend \xB7 /tool list")
|
|
20612
|
+
].join("\n");
|
|
20613
|
+
}
|
|
20614
|
+
function formatList() {
|
|
20615
|
+
const header = ` ${color.dim(fit("tool", 28))} ${color.dim(fit("owner", 28))} ` + color.dim("description");
|
|
20616
|
+
const rows = opts.toolRegistry.listWithOwner().map(({ tool }) => {
|
|
20617
|
+
const mode = getToolDescriptionMode(opts.toolRegistry, tool.name);
|
|
20618
|
+
const owner = opts.toolRegistry.ownerOf(tool.name) ?? "core";
|
|
20619
|
+
return ` ${fit(tool.name, 28)} ${color.dim(fit(`[${owner}]`, 28))} ` + formatDescriptionMode(mode);
|
|
20620
|
+
});
|
|
20621
|
+
return [
|
|
20622
|
+
`${color.bold("Tool description modes")} ${color.dim("(default: desc:extend)")}`,
|
|
20623
|
+
"",
|
|
20624
|
+
header,
|
|
20625
|
+
...rows
|
|
20626
|
+
].join("\n");
|
|
20627
|
+
}
|
|
20628
|
+
function formatOne(name) {
|
|
20629
|
+
const tool = opts.toolRegistry.get(name);
|
|
20630
|
+
if (!tool) {
|
|
20631
|
+
return `${color.red("Unknown tool")}: ${name}. Use ${color.dim("/tools")} to list registered tools.`;
|
|
20632
|
+
}
|
|
20633
|
+
const mode = getToolDescriptionMode(opts.toolRegistry, name);
|
|
20634
|
+
return [
|
|
20635
|
+
`${color.bold(name)} description mode: ${formatDescriptionMode(mode)}`,
|
|
20636
|
+
"",
|
|
20637
|
+
color.dim(tool.description)
|
|
20638
|
+
].join("\n");
|
|
20639
|
+
}
|
|
20640
|
+
return {
|
|
20641
|
+
name: "tool",
|
|
20642
|
+
category: "Config",
|
|
20643
|
+
description: "Set per-tool description detail: simple or extend.",
|
|
20644
|
+
argsHint: "[<name> simple|extend]",
|
|
20645
|
+
help,
|
|
20646
|
+
async run(args) {
|
|
20647
|
+
if (!opts.configStore) {
|
|
20648
|
+
return { message: `${color.red("Error")} config store not available.` };
|
|
20649
|
+
}
|
|
20650
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
20651
|
+
const sub = (parts[0] ?? "").toLowerCase();
|
|
20652
|
+
if (!sub) return { message: formatOverrides() };
|
|
20653
|
+
if (sub === "help" || sub === "--help") return { message: this.help ?? "" };
|
|
20654
|
+
if (sub === "list") return { message: formatList() };
|
|
20655
|
+
const name = parts[0] ?? "";
|
|
20656
|
+
const tool = opts.toolRegistry.get(name);
|
|
20657
|
+
if (!tool) {
|
|
20658
|
+
return {
|
|
20659
|
+
message: `${color.red("Unknown tool")}: ${name}. Use ${color.dim("/tools")} to list registered tools.`
|
|
20660
|
+
};
|
|
20661
|
+
}
|
|
20662
|
+
if (parts.length === 1) return { message: formatOne(name) };
|
|
20663
|
+
const mode = normalizeToolDescriptionMode(parts[1]);
|
|
20664
|
+
if (!mode) {
|
|
20665
|
+
return { message: `${color.amber("Usage:")} /tool ${name} simple|extend` };
|
|
20666
|
+
}
|
|
20667
|
+
try {
|
|
20668
|
+
const persisted = await persistMode(name, mode);
|
|
20669
|
+
setToolDescriptionMode(opts.toolRegistry, name, mode);
|
|
20670
|
+
const persistence = persisted ? color.dim("saved") : color.dim("runtime only; config paths unavailable");
|
|
20671
|
+
return {
|
|
20672
|
+
message: `${color.green("\u2713")} ${color.cyan(name)} description mode -> ${formatDescriptionMode(mode)} ${persistence}`
|
|
20673
|
+
};
|
|
20674
|
+
} catch (err) {
|
|
20675
|
+
return {
|
|
20676
|
+
message: `${color.red("Could not save tool setting")}: ${toErrorMessage(err)}`
|
|
20677
|
+
};
|
|
20678
|
+
}
|
|
20679
|
+
}
|
|
20680
|
+
};
|
|
20681
|
+
}
|
|
20682
|
+
function fit2(text, width) {
|
|
20683
|
+
if (text.length <= width) return text.padEnd(width);
|
|
20684
|
+
return `${text.slice(0, Math.max(0, width - 3))}...`;
|
|
20685
|
+
}
|
|
20686
|
+
function formatDescriptionMode2(mode) {
|
|
20687
|
+
const raw = `desc:${mode}`;
|
|
20688
|
+
return mode === "simple" ? color.amber(raw) : color.dim(raw);
|
|
20689
|
+
}
|
|
20498
20690
|
function buildToolsCommand(opts) {
|
|
20499
20691
|
return {
|
|
20500
20692
|
name: "tools",
|
|
@@ -20502,10 +20694,14 @@ function buildToolsCommand(opts) {
|
|
|
20502
20694
|
description: "List registered tools.",
|
|
20503
20695
|
async run() {
|
|
20504
20696
|
const all = opts.toolRegistry.listWithOwner();
|
|
20505
|
-
const
|
|
20506
|
-
|
|
20507
|
-
|
|
20508
|
-
|
|
20697
|
+
const header = ` ${color.dim(fit2("tool", 28))} ${color.dim(fit2("owner", 28))} ${color.dim(fit2("rw", 4))} ${color.dim(fit2("perm", 8))} ` + color.dim("description");
|
|
20698
|
+
const lines = all.map(({ tool, owner }) => {
|
|
20699
|
+
const mode = getToolDescriptionMode(opts.toolRegistry, tool.name);
|
|
20700
|
+
const rw = tool.mutating ? color.yellow(fit2("mut", 4)) : color.cyan(fit2("ro", 4));
|
|
20701
|
+
return ` ${fit2(tool.name, 28)} ${color.dim(fit2(`[${owner}]`, 28))} ${rw} ${color.dim(fit2(tool.permission, 8))} ` + formatDescriptionMode2(mode);
|
|
20702
|
+
});
|
|
20703
|
+
const msg = `${color.bold("Tools")} (${all.length}) ${color.dim("description detail via /tool <name> simple|extend")}:
|
|
20704
|
+
${header}
|
|
20509
20705
|
${lines.join("\n")}
|
|
20510
20706
|
`;
|
|
20511
20707
|
opts.renderer.write(msg);
|
|
@@ -20688,6 +20884,9 @@ function buildYoloCommand(opts) {
|
|
|
20688
20884
|
}
|
|
20689
20885
|
};
|
|
20690
20886
|
}
|
|
20887
|
+
var DEFAULT_SHADOW_PROVIDER = "anthropic";
|
|
20888
|
+
var DEFAULT_SHADOW_INTERVAL_MS = 3e4;
|
|
20889
|
+
var MIN_SHADOW_INTERVAL_MS = 5e3;
|
|
20691
20890
|
function buildShadowCommand(opts) {
|
|
20692
20891
|
return {
|
|
20693
20892
|
name: "shadow",
|
|
@@ -20697,8 +20896,8 @@ function buildShadowCommand(opts) {
|
|
|
20697
20896
|
argsHint: "<start|stop|status|hoop|model|interval> [--flag=value]",
|
|
20698
20897
|
help: [
|
|
20699
20898
|
"Usage:",
|
|
20700
|
-
" /shadow start [--
|
|
20701
|
-
"
|
|
20899
|
+
" /shadow start [--model=<provider/model>]",
|
|
20900
|
+
" Run one quiet Shadow Agent fleet check",
|
|
20702
20901
|
" /shadow stop",
|
|
20703
20902
|
" Stop the Shadow Agent",
|
|
20704
20903
|
" /shadow status",
|
|
@@ -20708,19 +20907,21 @@ function buildShadowCommand(opts) {
|
|
|
20708
20907
|
" /shadow model <provider/model>",
|
|
20709
20908
|
" Change Shadow Agent analysis model",
|
|
20710
20909
|
" /shadow interval <ms>",
|
|
20711
|
-
" Change
|
|
20910
|
+
" Change the legacy interval default kept for compatibility",
|
|
20712
20911
|
"",
|
|
20713
20912
|
"The Shadow Agent monitors:",
|
|
20714
20913
|
" \u2022 All fleet agents and their current tasks",
|
|
20715
20914
|
" \u2022 Mailbox state and message flow",
|
|
20716
20915
|
" \u2022 Spike tasks (starting/stopping instantly)",
|
|
20717
20916
|
" \u2022 Fleet-wide activity across all terminals",
|
|
20718
|
-
" \u2022 Uses deterministic rules first
|
|
20917
|
+
" \u2022 Uses deterministic host rules first; LLM only for manual/problem cases",
|
|
20918
|
+
" \u2022 Does not post routine healthy reports",
|
|
20719
20919
|
"",
|
|
20720
20920
|
"Model must be specified as provider/model, e.g. anthropic/claude-3-5-sonnet",
|
|
20921
|
+
"When omitted, Shadow uses the current leader provider/model.",
|
|
20721
20922
|
"",
|
|
20722
20923
|
"Examples:",
|
|
20723
|
-
" /shadow start
|
|
20924
|
+
" /shadow start",
|
|
20724
20925
|
" /shadow start --model=anthropic/claude-3-5-sonnet",
|
|
20725
20926
|
" /shadow status Show all agent activity",
|
|
20726
20927
|
" /shadow hoop subagent-abc123 Stop agent and notify",
|
|
@@ -20733,14 +20934,24 @@ function buildShadowCommand(opts) {
|
|
|
20733
20934
|
if (!opts.onSpawn) {
|
|
20734
20935
|
return { message: "/shadow requires a running director with subagent support." };
|
|
20735
20936
|
}
|
|
20736
|
-
const flags =
|
|
20737
|
-
|
|
20738
|
-
|
|
20739
|
-
|
|
20740
|
-
|
|
20741
|
-
|
|
20937
|
+
const flags = parseFlags2(rest);
|
|
20938
|
+
const shadowDefaults = opts.shadowController?.getDefaults?.();
|
|
20939
|
+
let intervalMs;
|
|
20940
|
+
try {
|
|
20941
|
+
intervalMs = parseInterval(
|
|
20942
|
+
flags["interval"] ?? String(shadowDefaults?.intervalMs ?? DEFAULT_SHADOW_INTERVAL_MS)
|
|
20943
|
+
);
|
|
20944
|
+
} catch (e) {
|
|
20945
|
+
return { message: `/shadow start: ${e.message}` };
|
|
20946
|
+
}
|
|
20947
|
+
const defaultModelRef = getDefaultModelRef(opts);
|
|
20948
|
+
let modelRef;
|
|
20949
|
+
const modelRaw = flags["model"];
|
|
20950
|
+
if (modelRaw === true) {
|
|
20951
|
+
return { message: "/shadow start: --model requires a provider/model value" };
|
|
20952
|
+
}
|
|
20742
20953
|
try {
|
|
20743
|
-
|
|
20954
|
+
modelRef = parseProviderModelRef(modelRaw ?? "default", defaultModelRef);
|
|
20744
20955
|
} catch (e) {
|
|
20745
20956
|
return { message: `/shadow start: ${e.message}` };
|
|
20746
20957
|
}
|
|
@@ -20755,10 +20966,10 @@ function buildShadowCommand(opts) {
|
|
|
20755
20966
|
};
|
|
20756
20967
|
}
|
|
20757
20968
|
const spawnId = await opts.onSpawn(
|
|
20758
|
-
`Shadow Agent \u2014
|
|
20969
|
+
`Shadow Agent \u2014 one-shot quiet fleet check`,
|
|
20759
20970
|
{
|
|
20760
|
-
provider:
|
|
20761
|
-
model,
|
|
20971
|
+
provider: modelRef.provider,
|
|
20972
|
+
model: modelRef.model,
|
|
20762
20973
|
tools: [
|
|
20763
20974
|
"fleet_status",
|
|
20764
20975
|
"fleet_health",
|
|
@@ -20766,30 +20977,33 @@ function buildShadowCommand(opts) {
|
|
|
20766
20977
|
"mailbox",
|
|
20767
20978
|
"mail_inbox",
|
|
20768
20979
|
"mail_send",
|
|
20769
|
-
"cron_schedule",
|
|
20770
|
-
"cron_list",
|
|
20771
|
-
"cron_cancel",
|
|
20772
|
-
"spawn_subagent",
|
|
20773
|
-
"assign_task",
|
|
20774
20980
|
"terminate_subagent"
|
|
20775
20981
|
],
|
|
20776
20982
|
name: "shadow",
|
|
20777
|
-
|
|
20983
|
+
shadowIntervalMs: intervalMs
|
|
20778
20984
|
}
|
|
20779
20985
|
);
|
|
20780
|
-
return { message: `${color.green("\u2713")} Shadow Agent
|
|
20781
|
-
${color.dim("
|
|
20782
|
-
${color.dim("Model:")} ${
|
|
20986
|
+
return { message: `${color.green("\u2713")} Shadow Agent queued: ${spawnId}
|
|
20987
|
+
${color.dim("Mode:")} one-shot quiet check
|
|
20988
|
+
${color.dim("Model:")} ${modelRef.label}` };
|
|
20783
20989
|
}
|
|
20784
20990
|
case "stop": {
|
|
20991
|
+
const activeId = opts.shadowController?.activeId;
|
|
20992
|
+
if (!activeId) {
|
|
20993
|
+
return { message: `${color.yellow("\u26A0")} No active Shadow Agent is registered for this session.` };
|
|
20994
|
+
}
|
|
20995
|
+
if (!opts.onFleetTerminate) {
|
|
20996
|
+
return { message: "/shadow stop requires fleet termination support in this session." };
|
|
20997
|
+
}
|
|
20998
|
+
const ok = await opts.onFleetTerminate(activeId);
|
|
20999
|
+
if (ok) {
|
|
21000
|
+
opts.shadowController?.clear();
|
|
21001
|
+
return { message: `${color.green("\u2713")} Shadow Agent stopped: ${activeId}` };
|
|
21002
|
+
}
|
|
20785
21003
|
return {
|
|
20786
21004
|
message: [
|
|
20787
|
-
`${color.
|
|
20788
|
-
""
|
|
20789
|
-
`It auto-starts on first LLM request and runs until session end.`,
|
|
20790
|
-
"",
|
|
20791
|
-
`To stop a specific agent: ${color.bold("/hoop <agent-id>")}`,
|
|
20792
|
-
`To stop all agents: ${color.bold("/hoop all")}`
|
|
21005
|
+
`${color.red("\u2717")} Failed to stop Shadow Agent ${color.bold(activeId)}.`,
|
|
21006
|
+
`It may already be stopped. Use ${color.bold("/shadow status")} to inspect active agents.`
|
|
20793
21007
|
].join("\n")
|
|
20794
21008
|
};
|
|
20795
21009
|
}
|
|
@@ -20807,44 +21021,71 @@ ${color.dim("Model:")} ${model}` };
|
|
|
20807
21021
|
}
|
|
20808
21022
|
const reasonFlag = extraArgs.find((f) => f.startsWith("--reason="));
|
|
20809
21023
|
const reason = reasonFlag ? reasonFlag.slice("--reason=".length) : "Agent flagged by Shadow Agent";
|
|
20810
|
-
|
|
20811
|
-
|
|
21024
|
+
if (targetId.toLowerCase() === "all") {
|
|
21025
|
+
if (!opts.onFleetKill) {
|
|
21026
|
+
return { message: "/shadow hoop all requires fleet kill support in this session." };
|
|
21027
|
+
}
|
|
21028
|
+
const killed = await opts.onFleetKill();
|
|
21029
|
+
opts.shadowController?.clear();
|
|
21030
|
+
return {
|
|
21031
|
+
message: [
|
|
21032
|
+
`${color.red("\u26A0")} HOOP: Stopped ${killed} running agent(s)`,
|
|
21033
|
+
"",
|
|
21034
|
+
`Target: ${color.bold("all")}`,
|
|
21035
|
+
`Reason: ${color.yellow(reason)}`
|
|
21036
|
+
].join("\n")
|
|
21037
|
+
};
|
|
21038
|
+
}
|
|
21039
|
+
if (!opts.onFleetTerminate) {
|
|
21040
|
+
return { message: "/shadow hoop requires fleet termination support in this session." };
|
|
21041
|
+
}
|
|
21042
|
+
const agentInfo = opts.onAgents?.(targetId) ?? "";
|
|
21043
|
+
const ok = await opts.onFleetTerminate(targetId);
|
|
21044
|
+
if (ok && opts.shadowController?.activeId === targetId) {
|
|
21045
|
+
opts.shadowController.clear();
|
|
21046
|
+
}
|
|
20812
21047
|
return {
|
|
20813
21048
|
message: [
|
|
20814
|
-
`${color.red("\u26A0")} HOOP:
|
|
21049
|
+
ok ? `${color.red("\u26A0")} HOOP: Stopped agent` : `${color.red("\u2717")} HOOP: Failed to stop agent`,
|
|
20815
21050
|
"",
|
|
20816
21051
|
`Target: ${color.bold(targetId)}`,
|
|
20817
21052
|
`Reason: ${color.yellow(reason)}`,
|
|
20818
|
-
|
|
20819
|
-
Agent info:
|
|
20820
|
-
|
|
20821
|
-
`${color.dim("Sending termination signal...")}`,
|
|
20822
|
-
`${color.dim("Notification will be sent to project mailbox.")}`
|
|
21053
|
+
agentInfo ? `
|
|
21054
|
+
Agent info:
|
|
21055
|
+
${agentInfo}` : ""
|
|
20823
21056
|
].join("\n")
|
|
20824
21057
|
};
|
|
20825
21058
|
}
|
|
20826
21059
|
case "model": {
|
|
20827
21060
|
const [modelId] = rest;
|
|
21061
|
+
const defaultModelRef = getDefaultModelRef(opts);
|
|
20828
21062
|
if (!modelId) {
|
|
20829
|
-
return { message:
|
|
21063
|
+
return { message: `/shadow model <provider/model> \u2014 change Shadow Agent analysis model.
|
|
21064
|
+
Current default: ${defaultModelRef.label}` };
|
|
20830
21065
|
}
|
|
21066
|
+
let parsed;
|
|
20831
21067
|
try {
|
|
20832
|
-
|
|
21068
|
+
parsed = parseProviderModelRef(modelId, defaultModelRef);
|
|
20833
21069
|
} catch (e) {
|
|
20834
21070
|
return { message: `/shadow model: ${e.message}` };
|
|
20835
21071
|
}
|
|
20836
|
-
|
|
21072
|
+
opts.shadowController?.setDefaults?.({ provider: parsed.provider, model: parsed.model });
|
|
21073
|
+
return { message: `/shadow model ${parsed.label}
|
|
20837
21074
|
${color.dim("Model will be applied on next /shadow start")}` };
|
|
20838
21075
|
}
|
|
20839
21076
|
case "interval": {
|
|
20840
21077
|
const [msStr] = rest;
|
|
20841
21078
|
if (!msStr) {
|
|
20842
|
-
return { message:
|
|
21079
|
+
return { message: `/shadow interval <ms> \u2014 update the legacy Shadow interval default.
|
|
21080
|
+
Current default: ${DEFAULT_SHADOW_INTERVAL_MS}ms (30 seconds)` };
|
|
20843
21081
|
}
|
|
20844
|
-
|
|
20845
|
-
|
|
20846
|
-
|
|
21082
|
+
let ms;
|
|
21083
|
+
try {
|
|
21084
|
+
ms = parseInterval(msStr);
|
|
21085
|
+
} catch (e) {
|
|
21086
|
+
return { message: `/shadow interval: ${e.message}` };
|
|
20847
21087
|
}
|
|
21088
|
+
opts.shadowController?.setDefaults?.({ intervalMs: ms });
|
|
20848
21089
|
return { message: `/shadow interval ${ms}ms
|
|
20849
21090
|
${color.dim("Interval will be applied on next /shadow start")}` };
|
|
20850
21091
|
}
|
|
@@ -20855,12 +21096,49 @@ ${color.dim("Interval will be applied on next /shadow start")}` };
|
|
|
20855
21096
|
}
|
|
20856
21097
|
};
|
|
20857
21098
|
}
|
|
20858
|
-
function
|
|
20859
|
-
|
|
20860
|
-
|
|
21099
|
+
function getDefaultModelRef(opts) {
|
|
21100
|
+
const shadowDefaults = opts.shadowController?.getDefaults?.();
|
|
21101
|
+
const liveConfig = opts.configStore?.get?.();
|
|
21102
|
+
const provider = shadowDefaults?.provider?.trim() || liveConfig?.provider?.trim() || opts.llmProvider?.id?.trim() || DEFAULT_SHADOW_PROVIDER;
|
|
21103
|
+
const model = shadowDefaults?.model?.trim() || liveConfig?.model?.trim() || opts.llmModel?.trim() || "";
|
|
21104
|
+
return {
|
|
21105
|
+
provider,
|
|
21106
|
+
model,
|
|
21107
|
+
label: model ? `${provider}/${model}` : provider
|
|
21108
|
+
};
|
|
21109
|
+
}
|
|
21110
|
+
function parseProviderModelRef(model, defaultRef) {
|
|
21111
|
+
if (model === "default") {
|
|
21112
|
+
return defaultRef;
|
|
21113
|
+
}
|
|
21114
|
+
const slash = model.indexOf("/");
|
|
21115
|
+
if (slash <= 0 || slash === model.length - 1) {
|
|
20861
21116
|
throw new Error(`Model must be in provider/model format (e.g. anthropic/claude-3-5-sonnet), got: "${model}"`);
|
|
20862
21117
|
}
|
|
20863
|
-
return
|
|
21118
|
+
return {
|
|
21119
|
+
provider: model.slice(0, slash),
|
|
21120
|
+
model: model.slice(slash + 1),
|
|
21121
|
+
label: model
|
|
21122
|
+
};
|
|
21123
|
+
}
|
|
21124
|
+
function parseInterval(value) {
|
|
21125
|
+
if (value === true || !/^\d+$/.test(value)) {
|
|
21126
|
+
throw new Error(`interval must be an integer >= ${MIN_SHADOW_INTERVAL_MS}ms`);
|
|
21127
|
+
}
|
|
21128
|
+
const ms = Number.parseInt(value, 10);
|
|
21129
|
+
if (!Number.isFinite(ms) || ms < MIN_SHADOW_INTERVAL_MS) {
|
|
21130
|
+
throw new Error(`interval must be an integer >= ${MIN_SHADOW_INTERVAL_MS}ms`);
|
|
21131
|
+
}
|
|
21132
|
+
return ms;
|
|
21133
|
+
}
|
|
21134
|
+
function parseFlags2(args) {
|
|
21135
|
+
return Object.fromEntries(
|
|
21136
|
+
args.filter((f) => f.startsWith("--")).map((f) => {
|
|
21137
|
+
const raw = f.slice(2);
|
|
21138
|
+
const eq = raw.indexOf("=");
|
|
21139
|
+
return eq >= 0 ? [raw.slice(0, eq), raw.slice(eq + 1)] : [raw, true];
|
|
21140
|
+
})
|
|
21141
|
+
);
|
|
20864
21142
|
}
|
|
20865
21143
|
|
|
20866
21144
|
// src/slash-commands/index.ts
|
|
@@ -20877,6 +21155,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
20877
21155
|
buildDoctorCommand(opts),
|
|
20878
21156
|
buildCodebaseReindexCommand(opts),
|
|
20879
21157
|
buildTechStackCommand(opts),
|
|
21158
|
+
buildToolCommand(opts),
|
|
20880
21159
|
buildToolsCommand(opts),
|
|
20881
21160
|
buildPluginCommand(opts),
|
|
20882
21161
|
buildPruneCommand(opts),
|
|
@@ -21191,11 +21470,24 @@ function buildBadges(chosen) {
|
|
|
21191
21470
|
return badges;
|
|
21192
21471
|
}
|
|
21193
21472
|
async function persistLaunchChoices(configPath2, choices) {
|
|
21473
|
+
let fileExists2 = false;
|
|
21474
|
+
try {
|
|
21475
|
+
await fs4.access(configPath2);
|
|
21476
|
+
fileExists2 = true;
|
|
21477
|
+
} catch {
|
|
21478
|
+
}
|
|
21194
21479
|
let existing = {};
|
|
21195
21480
|
try {
|
|
21196
21481
|
const raw = await fs4.readFile(configPath2, "utf8");
|
|
21197
21482
|
existing = JSON.parse(raw);
|
|
21198
|
-
} catch {
|
|
21483
|
+
} catch (err) {
|
|
21484
|
+
if (fileExists2) {
|
|
21485
|
+
throw new Error(
|
|
21486
|
+
`Refusing to overwrite corrupt config at ${configPath2} (${err.message}). Fix or move the file aside before retrying.`,
|
|
21487
|
+
{ cause: err }
|
|
21488
|
+
);
|
|
21489
|
+
}
|
|
21490
|
+
existing = {};
|
|
21199
21491
|
}
|
|
21200
21492
|
existing.yolo = choices.yolo;
|
|
21201
21493
|
existing.launch = {
|
|
@@ -24187,7 +24479,7 @@ var diagCmd = async (_args, deps) => {
|
|
|
24187
24479
|
` modelsCache: ${deps.paths.modelsCache}`,
|
|
24188
24480
|
` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
|
|
24189
24481
|
` node: ${process.version}`,
|
|
24190
|
-
` os: ${
|
|
24482
|
+
` os: ${os7.platform()} ${os7.release()}`,
|
|
24191
24483
|
` provider: ${cfg.provider ?? "<unset>"}`,
|
|
24192
24484
|
` model: ${cfg.model ?? "<unset>"}`,
|
|
24193
24485
|
` tools: ${deps.toolRegistry?.list().length ?? 0}`,
|
|
@@ -26039,6 +26331,9 @@ var projectsCmd = async (_args, deps) => {
|
|
|
26039
26331
|
return 0;
|
|
26040
26332
|
}
|
|
26041
26333
|
};
|
|
26334
|
+
|
|
26335
|
+
// src/subcommands/handlers/providers-models.ts
|
|
26336
|
+
init_provider_config_utils();
|
|
26042
26337
|
var providersCmd = async (args, deps) => {
|
|
26043
26338
|
const showAll = args.includes("--all");
|
|
26044
26339
|
const showUnsupported = args.includes("--unsupported");
|
|
@@ -26086,7 +26381,7 @@ ${color.dim(`Current: ${deps.config.provider ?? "<unset>"} / ${deps.config.model
|
|
|
26086
26381
|
return 1;
|
|
26087
26382
|
}
|
|
26088
26383
|
};
|
|
26089
|
-
function
|
|
26384
|
+
function parseFlags3(args) {
|
|
26090
26385
|
const flags = {};
|
|
26091
26386
|
for (let i = 0; i < args.length; i++) {
|
|
26092
26387
|
const a = expectDefined(args[i]);
|
|
@@ -26131,6 +26426,10 @@ function fmtPrice3(usdPer1M) {
|
|
|
26131
26426
|
}
|
|
26132
26427
|
var modelsCmd = async (args, deps) => {
|
|
26133
26428
|
const sub = args[0];
|
|
26429
|
+
if (sub === "hide") return modelsHide(args.slice(1), deps);
|
|
26430
|
+
if (sub === "show") return modelsShow(args.slice(1), deps);
|
|
26431
|
+
if (sub === "hidden") return modelsHidden(args.slice(1), deps);
|
|
26432
|
+
if (sub === "reset") return modelsReset(args.slice(1), deps);
|
|
26134
26433
|
if (sub === "add") return modelsAdd(args.slice(1), deps);
|
|
26135
26434
|
if (sub === "remove") return modelsRemove(args.slice(1), deps);
|
|
26136
26435
|
if (sub === "list") return modelsList(args.slice(1), deps);
|
|
@@ -26148,7 +26447,7 @@ var modelsCmd = async (args, deps) => {
|
|
|
26148
26447
|
return 1;
|
|
26149
26448
|
}
|
|
26150
26449
|
}
|
|
26151
|
-
const flags =
|
|
26450
|
+
const flags = parseFlags3(args);
|
|
26152
26451
|
const search = typeof flags["search"] === "string" ? flags["search"].toLowerCase() : "";
|
|
26153
26452
|
const perPage = Number(flags["per-page"]) > 0 ? Number(flags["per-page"]) : DEFAULT_PER_PAGE;
|
|
26154
26453
|
const page = Math.max(1, Number(flags["page"]) || 1);
|
|
@@ -26180,12 +26479,17 @@ var modelsCmd = async (args, deps) => {
|
|
|
26180
26479
|
`));
|
|
26181
26480
|
const userModels = deps.config.providers?.[providerId]?.models;
|
|
26182
26481
|
const catalogById = new Map(provider.models.map((m) => [m.id, m]));
|
|
26183
|
-
const
|
|
26482
|
+
const allKnownSorted = [...provider.models].sort(
|
|
26184
26483
|
(a, b) => (b.release_date ?? "").localeCompare(a.release_date ?? "")
|
|
26185
26484
|
);
|
|
26186
|
-
|
|
26187
|
-
|
|
26188
|
-
|
|
26485
|
+
const allSorted = userModels !== void 0 ? userModels.map((id) => catalogById.get(id) ?? { id, name: id }) : allKnownSorted;
|
|
26486
|
+
if (userModels !== void 0) {
|
|
26487
|
+
const hiddenCount = Math.max(0, allKnownSorted.length - userModels.length);
|
|
26488
|
+
deps.renderer.write(
|
|
26489
|
+
color.dim(`(${userModels.length} visible model(s) from your saved config${hiddenCount > 0 ? `, ${hiddenCount} hidden` : ""})
|
|
26490
|
+
`)
|
|
26491
|
+
);
|
|
26492
|
+
}
|
|
26189
26493
|
const filtered = search ? allSorted.filter((m) => m.id.toLowerCase().includes(search)) : allSorted;
|
|
26190
26494
|
const total = filtered.length;
|
|
26191
26495
|
const totalPages = Math.max(1, Math.ceil(total / perPage));
|
|
@@ -26237,6 +26541,164 @@ ${navLines.join(" \xB7 ")}
|
|
|
26237
26541
|
);
|
|
26238
26542
|
return 0;
|
|
26239
26543
|
};
|
|
26544
|
+
function aliasProviderId(configProviderId, cfg) {
|
|
26545
|
+
return cfg?.type && cfg.type !== configProviderId ? cfg.type : configProviderId;
|
|
26546
|
+
}
|
|
26547
|
+
async function getCatalogProviderForConfigProvider(providerId, deps) {
|
|
26548
|
+
const cfg = deps.config.providers?.[providerId];
|
|
26549
|
+
const lookupId = aliasProviderId(providerId, cfg);
|
|
26550
|
+
const provider = await deps.modelsRegistry.getProvider(lookupId);
|
|
26551
|
+
return { providerId: lookupId, provider };
|
|
26552
|
+
}
|
|
26553
|
+
function uniqueStrings(values) {
|
|
26554
|
+
return [...new Set(values.map((v) => v.trim()).filter(Boolean))];
|
|
26555
|
+
}
|
|
26556
|
+
async function modelsHide(args, deps) {
|
|
26557
|
+
const providerId = args[0];
|
|
26558
|
+
const modelId = args[1];
|
|
26559
|
+
if (!providerId || !modelId) {
|
|
26560
|
+
deps.renderer.writeError("Usage: wstack models hide <provider> <model>");
|
|
26561
|
+
return 1;
|
|
26562
|
+
}
|
|
26563
|
+
const saved = deps.config.providers?.[providerId];
|
|
26564
|
+
if (!saved) {
|
|
26565
|
+
deps.renderer.writeError(`Provider "${providerId}" is not configured.`);
|
|
26566
|
+
return 1;
|
|
26567
|
+
}
|
|
26568
|
+
const { providerId: lookupId, provider } = await getCatalogProviderForConfigProvider(providerId, deps);
|
|
26569
|
+
if (!provider) {
|
|
26570
|
+
deps.renderer.writeError(
|
|
26571
|
+
lookupId !== providerId ? `Alias "${providerId}" points at catalog id "${lookupId}" which is not in the cache.` : `Provider "${providerId}" not in catalog.`
|
|
26572
|
+
);
|
|
26573
|
+
return 1;
|
|
26574
|
+
}
|
|
26575
|
+
const knownIds = provider.models.map((m) => m.id);
|
|
26576
|
+
const visible = saved.models !== void 0 ? [...saved.models] : [...knownIds];
|
|
26577
|
+
if (!visible.includes(modelId)) {
|
|
26578
|
+
deps.renderer.writeInfo(`${providerId}/${modelId} is already hidden.`);
|
|
26579
|
+
return 0;
|
|
26580
|
+
}
|
|
26581
|
+
const nextVisible = visible.filter((id) => id !== modelId);
|
|
26582
|
+
await mutateConfigProviders(deps.paths.globalConfig, deps.vault, (providers) => {
|
|
26583
|
+
const p = providers[providerId];
|
|
26584
|
+
if (!p) return;
|
|
26585
|
+
p.models = nextVisible;
|
|
26586
|
+
});
|
|
26587
|
+
deps.config.providers = {
|
|
26588
|
+
...deps.config.providers ?? {},
|
|
26589
|
+
[providerId]: { ...saved, models: nextVisible }
|
|
26590
|
+
};
|
|
26591
|
+
deps.renderer.writeInfo(
|
|
26592
|
+
`Hidden ${providerId}/${modelId}. Visible: ${nextVisible.length}, hidden: ${Math.max(0, knownIds.length - nextVisible.length)}.`
|
|
26593
|
+
);
|
|
26594
|
+
if (nextVisible.length === 0) {
|
|
26595
|
+
deps.renderer.write(color.dim("This provider now has no visible models. Use `wstack models show` or `wstack models reset` to restore.\n"));
|
|
26596
|
+
}
|
|
26597
|
+
return 0;
|
|
26598
|
+
}
|
|
26599
|
+
async function modelsShow(args, deps) {
|
|
26600
|
+
const providerId = args[0];
|
|
26601
|
+
const modelId = args[1];
|
|
26602
|
+
if (!providerId || !modelId) {
|
|
26603
|
+
deps.renderer.writeError("Usage: wstack models show <provider> <model>");
|
|
26604
|
+
return 1;
|
|
26605
|
+
}
|
|
26606
|
+
const saved = deps.config.providers?.[providerId];
|
|
26607
|
+
if (!saved) {
|
|
26608
|
+
deps.renderer.writeError(`Provider "${providerId}" is not configured.`);
|
|
26609
|
+
return 1;
|
|
26610
|
+
}
|
|
26611
|
+
const { providerId: lookupId, provider } = await getCatalogProviderForConfigProvider(providerId, deps);
|
|
26612
|
+
if (!provider) {
|
|
26613
|
+
deps.renderer.writeError(
|
|
26614
|
+
lookupId !== providerId ? `Alias "${providerId}" points at catalog id "${lookupId}" which is not in the cache.` : `Provider "${providerId}" not in catalog.`
|
|
26615
|
+
);
|
|
26616
|
+
return 1;
|
|
26617
|
+
}
|
|
26618
|
+
const knownIds = provider.models.map((m) => m.id);
|
|
26619
|
+
if (saved.models === void 0) {
|
|
26620
|
+
deps.renderer.writeInfo(`${providerId}/${modelId} is already visible.`);
|
|
26621
|
+
return 0;
|
|
26622
|
+
}
|
|
26623
|
+
const nextVisible = uniqueStrings([...saved.models, modelId]);
|
|
26624
|
+
await mutateConfigProviders(deps.paths.globalConfig, deps.vault, (providers) => {
|
|
26625
|
+
const p = providers[providerId];
|
|
26626
|
+
if (!p) return;
|
|
26627
|
+
p.models = nextVisible;
|
|
26628
|
+
});
|
|
26629
|
+
deps.config.providers = {
|
|
26630
|
+
...deps.config.providers ?? {},
|
|
26631
|
+
[providerId]: { ...saved, models: nextVisible }
|
|
26632
|
+
};
|
|
26633
|
+
const isKnown = knownIds.includes(modelId);
|
|
26634
|
+
deps.renderer.writeInfo(
|
|
26635
|
+
`Visible ${providerId}/${modelId}${isKnown ? "" : " (not in current catalog)"}. Visible: ${nextVisible.length}.`
|
|
26636
|
+
);
|
|
26637
|
+
return 0;
|
|
26638
|
+
}
|
|
26639
|
+
async function modelsHidden(args, deps) {
|
|
26640
|
+
const providerId = args[0] ?? deps.config.provider;
|
|
26641
|
+
if (!providerId) {
|
|
26642
|
+
deps.renderer.writeError("Usage: wstack models hidden <provider>");
|
|
26643
|
+
return 1;
|
|
26644
|
+
}
|
|
26645
|
+
const saved = deps.config.providers?.[providerId];
|
|
26646
|
+
if (!saved) {
|
|
26647
|
+
deps.renderer.writeError(`Provider "${providerId}" is not configured.`);
|
|
26648
|
+
return 1;
|
|
26649
|
+
}
|
|
26650
|
+
const { providerId: lookupId, provider } = await getCatalogProviderForConfigProvider(providerId, deps);
|
|
26651
|
+
if (!provider) {
|
|
26652
|
+
deps.renderer.writeError(
|
|
26653
|
+
lookupId !== providerId ? `Alias "${providerId}" points at catalog id "${lookupId}" which is not in the cache.` : `Provider "${providerId}" not in catalog.`
|
|
26654
|
+
);
|
|
26655
|
+
return 1;
|
|
26656
|
+
}
|
|
26657
|
+
const knownIds = provider.models.map((m) => m.id);
|
|
26658
|
+
const visible = saved.models;
|
|
26659
|
+
const hidden = visible === void 0 ? [] : knownIds.filter((id) => !visible.includes(id));
|
|
26660
|
+
deps.renderer.write(`${color.bold("Hidden models")} ${color.dim(`(${providerId})`)}
|
|
26661
|
+
`);
|
|
26662
|
+
if (hidden.length === 0) {
|
|
26663
|
+
deps.renderer.write(color.dim("(none hidden)\n"));
|
|
26664
|
+
return 0;
|
|
26665
|
+
}
|
|
26666
|
+
for (const id of hidden) deps.renderer.write(` ${id}
|
|
26667
|
+
`);
|
|
26668
|
+
deps.renderer.write(color.dim(`
|
|
26669
|
+
Restore: wstack models show ${providerId} <model> \xB7 reset ${providerId}
|
|
26670
|
+
`));
|
|
26671
|
+
return 0;
|
|
26672
|
+
}
|
|
26673
|
+
async function modelsReset(args, deps) {
|
|
26674
|
+
const providerId = args[0];
|
|
26675
|
+
if (!providerId) {
|
|
26676
|
+
deps.renderer.writeError("Usage: wstack models reset <provider>");
|
|
26677
|
+
return 1;
|
|
26678
|
+
}
|
|
26679
|
+
const saved = deps.config.providers?.[providerId];
|
|
26680
|
+
if (!saved) {
|
|
26681
|
+
deps.renderer.writeError(`Provider "${providerId}" is not configured.`);
|
|
26682
|
+
return 1;
|
|
26683
|
+
}
|
|
26684
|
+
if (saved.models === void 0) {
|
|
26685
|
+
deps.renderer.writeInfo(`${providerId} already shows the full catalog model list.`);
|
|
26686
|
+
return 0;
|
|
26687
|
+
}
|
|
26688
|
+
await mutateConfigProviders(deps.paths.globalConfig, deps.vault, (providers) => {
|
|
26689
|
+
const p = providers[providerId];
|
|
26690
|
+
if (!p) return;
|
|
26691
|
+
delete p.models;
|
|
26692
|
+
});
|
|
26693
|
+
const nextProvider = { ...saved };
|
|
26694
|
+
delete nextProvider.models;
|
|
26695
|
+
deps.config.providers = {
|
|
26696
|
+
...deps.config.providers ?? {},
|
|
26697
|
+
[providerId]: nextProvider
|
|
26698
|
+
};
|
|
26699
|
+
deps.renderer.writeInfo(`Reset visible model list for ${providerId} to the full catalog default.`);
|
|
26700
|
+
return 0;
|
|
26701
|
+
}
|
|
26240
26702
|
async function modelsCaps(args, deps) {
|
|
26241
26703
|
const providerId = args[0] ?? deps.config.provider;
|
|
26242
26704
|
const modelId = args[1] ?? deps.config.model;
|
|
@@ -26330,7 +26792,7 @@ function parseBoolFlag(flags, key) {
|
|
|
26330
26792
|
return void 0;
|
|
26331
26793
|
}
|
|
26332
26794
|
async function modelsAdd(args, deps) {
|
|
26333
|
-
const flags =
|
|
26795
|
+
const flags = parseFlags3(args);
|
|
26334
26796
|
const pos = positionals2(args);
|
|
26335
26797
|
const modelId = pos[0];
|
|
26336
26798
|
if (!modelId) {
|
|
@@ -27183,7 +27645,7 @@ function installWarningSummary(output) {
|
|
|
27183
27645
|
}
|
|
27184
27646
|
var versionCmd = async (_args, deps) => {
|
|
27185
27647
|
deps.renderer.write(
|
|
27186
|
-
`WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${
|
|
27648
|
+
`WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os7.platform()})
|
|
27187
27649
|
`
|
|
27188
27650
|
);
|
|
27189
27651
|
return 0;
|
|
@@ -27725,6 +28187,7 @@ function registerBuiltinTools(deps) {
|
|
|
27725
28187
|
deps.toolRegistry.register(
|
|
27726
28188
|
makeMailInboxTool({ projectDir: deps.wpaths.projectDir, events: deps.events })
|
|
27727
28189
|
);
|
|
28190
|
+
applyToolDescriptionModes(deps.toolRegistry, deps.config.tools?.descriptionMode);
|
|
27728
28191
|
}
|
|
27729
28192
|
|
|
27730
28193
|
// src/cli-eternal-flag.ts
|
|
@@ -28317,7 +28780,8 @@ async function runWebUIDispatch(ctx) {
|
|
|
28317
28780
|
renderer,
|
|
28318
28781
|
onAutonomy,
|
|
28319
28782
|
onModelContextResolved,
|
|
28320
|
-
activeRecoveryLock
|
|
28783
|
+
activeRecoveryLock,
|
|
28784
|
+
sddSubagentFactory
|
|
28321
28785
|
} = ctx;
|
|
28322
28786
|
agent.disableInteractiveConfirmation();
|
|
28323
28787
|
renderer.setSilent(true);
|
|
@@ -28348,6 +28812,7 @@ async function runWebUIDispatch(ctx) {
|
|
|
28348
28812
|
modeStore,
|
|
28349
28813
|
modeId,
|
|
28350
28814
|
needsSetup,
|
|
28815
|
+
sddSubagentFactory,
|
|
28351
28816
|
// Print the "open this" banner only once the server is actually
|
|
28352
28817
|
// listening, using the RESOLVED ports. The requested port
|
|
28353
28818
|
// (flags.port) auto-advances past busy ports inside runWebUI, so a
|
|
@@ -28404,9 +28869,13 @@ var AUTO_PHASE_EVENTS = [
|
|
|
28404
28869
|
"phase.completed",
|
|
28405
28870
|
"phase.failed",
|
|
28406
28871
|
"phase.statusChange",
|
|
28872
|
+
"phase.taskStarted",
|
|
28407
28873
|
"phase.taskCompleted",
|
|
28408
28874
|
"phase.taskFailed",
|
|
28409
28875
|
"phase.taskRetrying",
|
|
28876
|
+
"phase.taskAssigned",
|
|
28877
|
+
"phase.taskAdded",
|
|
28878
|
+
"phase.taskMoved",
|
|
28410
28879
|
"phase.verifying",
|
|
28411
28880
|
"phase.verifyFailed",
|
|
28412
28881
|
"phase.repairing",
|
|
@@ -28417,6 +28886,8 @@ var AUTO_PHASE_EVENTS = [
|
|
|
28417
28886
|
"graph.failed",
|
|
28418
28887
|
"agent.assigned",
|
|
28419
28888
|
"agent.released",
|
|
28889
|
+
// Live multi-agent SDD board snapshot → TUI board overlay (Ctrl+B).
|
|
28890
|
+
"sdd.board.snapshot",
|
|
28420
28891
|
// Git-worktree isolation lifecycle → TUI worktree panel/monitor.
|
|
28421
28892
|
"worktree.allocated",
|
|
28422
28893
|
"worktree.committed",
|
|
@@ -29539,6 +30010,13 @@ async function runRepl(opts) {
|
|
|
29539
30010
|
interrupts = 0;
|
|
29540
30011
|
return;
|
|
29541
30012
|
}
|
|
30013
|
+
const sddRun = opts.getSddRun?.();
|
|
30014
|
+
if (sddRun?.isRunning()) {
|
|
30015
|
+
sddRun.stop();
|
|
30016
|
+
opts.renderer.writeWarning("SDD run stop requested. Press Ctrl+C again to exit.");
|
|
30017
|
+
interrupts = 0;
|
|
30018
|
+
return;
|
|
30019
|
+
}
|
|
29542
30020
|
if (activeCtrl) {
|
|
29543
30021
|
activeCtrl.abort();
|
|
29544
30022
|
const killed = opts.onInterruptFleet?.() ?? 0;
|
|
@@ -30387,6 +30865,7 @@ async function execute(deps) {
|
|
|
30387
30865
|
tokenCounter,
|
|
30388
30866
|
config,
|
|
30389
30867
|
configStore,
|
|
30868
|
+
sddSubagentFactory,
|
|
30390
30869
|
renderer,
|
|
30391
30870
|
reader,
|
|
30392
30871
|
session,
|
|
@@ -30438,6 +30917,7 @@ async function execute(deps) {
|
|
|
30438
30917
|
onValidateAutoProceed,
|
|
30439
30918
|
getEternalEngine,
|
|
30440
30919
|
getParallelEngine,
|
|
30920
|
+
getSddRun,
|
|
30441
30921
|
subscribeEternalIteration,
|
|
30442
30922
|
subscribeEternalStage,
|
|
30443
30923
|
skillLoader,
|
|
@@ -30716,6 +31196,7 @@ async function execute(deps) {
|
|
|
30716
31196
|
// can access them (entry.tsx parses from rendered messages).
|
|
30717
31197
|
setSuggestions,
|
|
30718
31198
|
getEternalEngine,
|
|
31199
|
+
getSddRun,
|
|
30719
31200
|
subscribeEternalIteration,
|
|
30720
31201
|
subscribeEternalStage,
|
|
30721
31202
|
subscribeAutoPhase,
|
|
@@ -30953,7 +31434,8 @@ async function execute(deps) {
|
|
|
30953
31434
|
renderer,
|
|
30954
31435
|
onAutonomy,
|
|
30955
31436
|
activeRecoveryLock,
|
|
30956
|
-
onModelContextResolved
|
|
31437
|
+
onModelContextResolved,
|
|
31438
|
+
sddSubagentFactory
|
|
30957
31439
|
});
|
|
30958
31440
|
} else {
|
|
30959
31441
|
code = await runRepl({
|
|
@@ -30983,6 +31465,7 @@ async function execute(deps) {
|
|
|
30983
31465
|
autoProceedMaxIterations,
|
|
30984
31466
|
getEternalEngine,
|
|
30985
31467
|
getParallelEngine,
|
|
31468
|
+
getSddRun,
|
|
30986
31469
|
skillLoader,
|
|
30987
31470
|
agentsMonitorController,
|
|
30988
31471
|
fleetStreamController,
|
|
@@ -31096,6 +31579,19 @@ function buildRoutingRunner(config, host) {
|
|
|
31096
31579
|
}
|
|
31097
31580
|
|
|
31098
31581
|
// src/fleet/host.ts
|
|
31582
|
+
function buildShadowAgentTaskDescription(reason) {
|
|
31583
|
+
return `Shadow Agent one-shot fleet monitor. Reason: ${reason}.
|
|
31584
|
+
|
|
31585
|
+
Run exactly one quiet pass:
|
|
31586
|
+
- Call fleet_status and fleet_health.
|
|
31587
|
+
- Inspect mail_inbox only for explicit hoop/shadow control messages.
|
|
31588
|
+
- Do not broadcast startup, heartbeat, shutdown, or healthy status messages.
|
|
31589
|
+
- Use mail_send only for high/critical anomalies or explicit control replies.
|
|
31590
|
+
- Terminate agents only when explicitly commanded.
|
|
31591
|
+
- If the fleet is healthy and no command needs a reply, keep the final answer to "shadow: quiet".
|
|
31592
|
+
|
|
31593
|
+
The host will stop this Shadow Agent after this pass. Do not schedule follow-up work.`;
|
|
31594
|
+
}
|
|
31099
31595
|
var MultiAgentHost = class _MultiAgentHost {
|
|
31100
31596
|
constructor(deps, opts = {}) {
|
|
31101
31597
|
this.deps = deps;
|
|
@@ -31111,10 +31607,9 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
31111
31607
|
* critic.evaluation) onto the fleet bus without needing the tool registered
|
|
31112
31608
|
* in the host's ToolRegistry. */
|
|
31113
31609
|
fleetEmitTool;
|
|
31114
|
-
/**
|
|
31115
|
-
*
|
|
31116
|
-
|
|
31117
|
-
fleetStatusTool;
|
|
31610
|
+
/** Director-owned tools available to scoped subagents even when the leader
|
|
31611
|
+
* ToolRegistry was not populated because director mode was promoted lazily. */
|
|
31612
|
+
directorToolsByName = /* @__PURE__ */ new Map();
|
|
31118
31613
|
/** Lazily built alongside the director — produces per-subagent JSONL
|
|
31119
31614
|
* writers under `<sessionsRoot>/<runId>/`. Null without sessionsRoot. */
|
|
31120
31615
|
sessionFactory;
|
|
@@ -31142,6 +31637,23 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
31142
31637
|
* adaptiveConcurrency.enabled = true. Monitors FleetBus for 429 errors and
|
|
31143
31638
|
* automatically adjusts maxConcurrent to prevent rate limiting. */
|
|
31144
31639
|
adaptiveConcurrencyController;
|
|
31640
|
+
/** Active Shadow Agent spawned by the host or /shadow start. */
|
|
31641
|
+
shadowAgentId = null;
|
|
31642
|
+
/** Assigned monitoring task for the active Shadow Agent. */
|
|
31643
|
+
shadowTaskId = null;
|
|
31644
|
+
/** All internal Shadow Agent startup/heartbeat task ids, excluded from fleet summaries. */
|
|
31645
|
+
shadowTaskIds = /* @__PURE__ */ new Set();
|
|
31646
|
+
/** Shadow task ids assigned but not yet completed. Prevents heartbeat backlog. */
|
|
31647
|
+
shadowOutstandingTaskIds = /* @__PURE__ */ new Set();
|
|
31648
|
+
/** Shadow task ids whose subagent should stop immediately after the one-shot pass. */
|
|
31649
|
+
shadowStopAfterTaskIds = /* @__PURE__ */ new Set();
|
|
31650
|
+
shadowHeartbeatIntervalMs = 3e4;
|
|
31651
|
+
/** Suppresses buildDirector() auto-start while /shadow start is explicitly spawning one. */
|
|
31652
|
+
shadowAutoStartSuppressions = 0;
|
|
31653
|
+
shadowObservedWorkDepth = 0;
|
|
31654
|
+
shadowPassInFlight = false;
|
|
31655
|
+
shadowQueuedProblem = null;
|
|
31656
|
+
shadowActivityOffHandles = [];
|
|
31145
31657
|
/**
|
|
31146
31658
|
* Force the lazy build path to run *now* and return the live Director,
|
|
31147
31659
|
* or null when director mode is off. Used by the CLI to register the
|
|
@@ -31224,6 +31736,14 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
31224
31736
|
});
|
|
31225
31737
|
this.director.on("task.completed", ({ task, result }) => {
|
|
31226
31738
|
this.fleetManager?.removePendingTask(task.id);
|
|
31739
|
+
const isShadowTask = this.shadowTaskIds.has(task.id);
|
|
31740
|
+
if (isShadowTask) {
|
|
31741
|
+
this.shadowOutstandingTaskIds.delete(task.id);
|
|
31742
|
+
if (this.shadowStopAfterTaskIds.delete(task.id)) {
|
|
31743
|
+
void this.stopShadowAfterTask(result.subagentId);
|
|
31744
|
+
}
|
|
31745
|
+
return;
|
|
31746
|
+
}
|
|
31227
31747
|
this.emitLifecycleCompleted(task.id, result);
|
|
31228
31748
|
const monitor = this.opts.agentMonitor;
|
|
31229
31749
|
if (monitor) {
|
|
@@ -31292,17 +31812,33 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
31292
31812
|
task,
|
|
31293
31813
|
subagentId
|
|
31294
31814
|
}) => {
|
|
31815
|
+
if (this.shadowTaskIds.has(task.id)) return;
|
|
31295
31816
|
this.deps.events.emit("subagent.task_started", {
|
|
31296
31817
|
subagentId,
|
|
31297
31818
|
taskId: task.id,
|
|
31298
31819
|
description: task.description
|
|
31299
31820
|
});
|
|
31300
31821
|
};
|
|
31822
|
+
const coordinatorSubagentStoppedHandler = ({ subagentId }) => {
|
|
31823
|
+
this.clearShadowAgent(subagentId);
|
|
31824
|
+
};
|
|
31825
|
+
this.directorOffHandles.push(
|
|
31826
|
+
this.director.fleet.filter("subagent.removed", (e) => {
|
|
31827
|
+
const payload = e.payload;
|
|
31828
|
+
this.clearShadowAgent(payload.subagentId ?? e.subagentId);
|
|
31829
|
+
})
|
|
31830
|
+
);
|
|
31301
31831
|
const coordinator = this.getCoordinator();
|
|
31302
31832
|
coordinator.on("task.assigned", coordinatorTaskAssignedHandler);
|
|
31303
|
-
|
|
31833
|
+
coordinator.on("subagent.stopped", coordinatorSubagentStoppedHandler);
|
|
31834
|
+
this.coordinatorOffHandle = () => {
|
|
31835
|
+
coordinator.off("task.assigned", coordinatorTaskAssignedHandler);
|
|
31836
|
+
coordinator.off("subagent.stopped", coordinatorSubagentStoppedHandler);
|
|
31837
|
+
};
|
|
31304
31838
|
this.fleetEmitTool = makeFleetEmitTool(this.director);
|
|
31305
|
-
this.
|
|
31839
|
+
this.directorToolsByName = new Map(
|
|
31840
|
+
this.director.tools(FLEET_ROSTER).map((tool) => [tool.name, tool])
|
|
31841
|
+
);
|
|
31306
31842
|
const adaptiveConfig = this.deps.configStore.get().adaptiveConcurrency;
|
|
31307
31843
|
if (adaptiveConfig?.enabled) {
|
|
31308
31844
|
this.adaptiveConcurrencyController = new AdaptiveConcurrencyController(
|
|
@@ -31316,55 +31852,135 @@ var MultiAgentHost = class _MultiAgentHost {
|
|
|
31316
31852
|
this.getCoordinator().setRunner(runner);
|
|
31317
31853
|
this.directorRunnerSet = true;
|
|
31318
31854
|
}
|
|
31319
|
-
|
|
31855
|
+
this.armShadowAgentIfNeeded();
|
|
31320
31856
|
}
|
|
31321
31857
|
/**
|
|
31322
|
-
*
|
|
31323
|
-
*
|
|
31858
|
+
* Arm host-owned Shadow observation. Healthy work windows stay fully
|
|
31859
|
+
* deterministic; a one-shot LLM Shadow pass runs only after problematic work
|
|
31860
|
+
* finishes, or when the user explicitly invokes /shadow start.
|
|
31324
31861
|
*/
|
|
31325
|
-
|
|
31326
|
-
|
|
31862
|
+
armShadowAgentIfNeeded() {
|
|
31863
|
+
if (this.shadowActivityOffHandles.length > 0) return;
|
|
31864
|
+
this.shadowActivityOffHandles.push(
|
|
31865
|
+
this.deps.events.on("agent.run.started", () => this.noteShadowWorkStarted()),
|
|
31866
|
+
this.deps.events.on("agent.run.completed", (e) => {
|
|
31867
|
+
const problem = e.status === "failed" || e.status === "max_iterations" ? `leader run ended with ${e.status}` : void 0;
|
|
31868
|
+
this.noteShadowWorkCompleted(problem);
|
|
31869
|
+
}),
|
|
31870
|
+
this.deps.events.on("subagent.task_started", () => this.noteShadowWorkStarted()),
|
|
31871
|
+
this.deps.events.on("subagent.task_completed", (e) => {
|
|
31872
|
+
const problem = e.status === "failed" || e.status === "timeout" ? `subagent ${e.subagentId} task ${e.taskId} ended with ${e.status}${e.error?.message ? `: ${e.error.message}` : ""}` : void 0;
|
|
31873
|
+
this.noteShadowWorkCompleted(problem);
|
|
31874
|
+
})
|
|
31875
|
+
);
|
|
31876
|
+
}
|
|
31877
|
+
recordShadowAgent(subagentId, taskId, intervalMs = this.shadowHeartbeatIntervalMs) {
|
|
31878
|
+
this.shadowAgentId = subagentId;
|
|
31879
|
+
this.shadowTaskId = taskId;
|
|
31880
|
+
this.shadowHeartbeatIntervalMs = intervalMs;
|
|
31881
|
+
this.markShadowTask(taskId);
|
|
31882
|
+
this.opts.onShadowAgentStarted?.(subagentId);
|
|
31883
|
+
}
|
|
31884
|
+
clearShadowAgent(subagentId) {
|
|
31885
|
+
if (subagentId && this.shadowAgentId !== subagentId) return;
|
|
31886
|
+
const stoppedId = this.shadowAgentId;
|
|
31887
|
+
this.shadowAgentId = null;
|
|
31888
|
+
this.shadowTaskId = null;
|
|
31889
|
+
this.shadowOutstandingTaskIds.clear();
|
|
31890
|
+
this.shadowStopAfterTaskIds.clear();
|
|
31891
|
+
if (stoppedId) this.opts.onShadowAgentStopped?.(stoppedId);
|
|
31892
|
+
}
|
|
31893
|
+
markShadowTask(taskId) {
|
|
31894
|
+
this.shadowTaskIds.add(taskId);
|
|
31895
|
+
this.shadowOutstandingTaskIds.add(taskId);
|
|
31896
|
+
}
|
|
31897
|
+
isActiveSubagent(subagentId) {
|
|
31898
|
+
if (!this.director) return false;
|
|
31899
|
+
const status = this.getCoordinator().getStatus().subagents.find((a) => a.id === subagentId)?.status;
|
|
31900
|
+
return status === "running" || status === "idle";
|
|
31901
|
+
}
|
|
31902
|
+
noteShadowWorkStarted() {
|
|
31903
|
+
if (this.shadowAutoStartSuppressions > 0) return;
|
|
31904
|
+
this.shadowObservedWorkDepth++;
|
|
31905
|
+
}
|
|
31906
|
+
noteShadowWorkCompleted(problem) {
|
|
31907
|
+
if (problem) {
|
|
31908
|
+
this.shadowQueuedProblem = this.shadowQueuedProblem ? `${this.shadowQueuedProblem}; ${problem}` : problem;
|
|
31909
|
+
}
|
|
31910
|
+
if (this.shadowObservedWorkDepth > 0) {
|
|
31911
|
+
this.shadowObservedWorkDepth--;
|
|
31912
|
+
}
|
|
31913
|
+
if (this.shadowObservedWorkDepth === 0 && this.shadowQueuedProblem) {
|
|
31914
|
+
const queued = this.shadowQueuedProblem;
|
|
31915
|
+
this.shadowQueuedProblem = null;
|
|
31916
|
+
this.requestShadowPass(queued);
|
|
31917
|
+
}
|
|
31918
|
+
}
|
|
31919
|
+
requestShadowPass(reason) {
|
|
31920
|
+
if (!this.director) return;
|
|
31921
|
+
if (this.shadowObservedWorkDepth > 0) {
|
|
31922
|
+
this.shadowQueuedProblem = this.shadowQueuedProblem ? `${this.shadowQueuedProblem}; ${reason}` : reason;
|
|
31923
|
+
return;
|
|
31924
|
+
}
|
|
31925
|
+
if (this.shadowPassInFlight || this.shadowAgentId && this.isActiveSubagent(this.shadowAgentId)) {
|
|
31926
|
+
this.shadowQueuedProblem = this.shadowQueuedProblem ? `${this.shadowQueuedProblem}; ${reason}` : reason;
|
|
31927
|
+
return;
|
|
31928
|
+
}
|
|
31929
|
+
this.shadowPassInFlight = true;
|
|
31930
|
+
queueMicrotask(() => {
|
|
31931
|
+
void this.runShadowPass(reason);
|
|
31932
|
+
});
|
|
31933
|
+
}
|
|
31934
|
+
async runShadowPass(reason) {
|
|
31327
31935
|
try {
|
|
31328
|
-
|
|
31329
|
-
|
|
31330
|
-
|
|
31331
|
-
|
|
31332
|
-
|
|
31333
|
-
|
|
31334
|
-
|
|
31335
|
-
|
|
31336
|
-
|
|
31337
|
-
|
|
31338
|
-
|
|
31339
|
-
|
|
31340
|
-
|
|
31341
|
-
|
|
31342
|
-
|
|
31343
|
-
|
|
31344
|
-
|
|
31345
|
-
|
|
31346
|
-
|
|
31347
|
-
|
|
31348
|
-
|
|
31349
|
-
|
|
31350
|
-
|
|
31351
|
-
|
|
31352
|
-
|
|
31353
|
-
|
|
31354
|
-
|
|
31355
|
-
|
|
31356
|
-
|
|
31357
|
-
|
|
31358
|
-
|
|
31359
|
-
|
|
31360
|
-
|
|
31361
|
-
|
|
31362
|
-
|
|
31363
|
-
|
|
31364
|
-
|
|
31365
|
-
|
|
31366
|
-
|
|
31367
|
-
|
|
31936
|
+
if (!this.director) return;
|
|
31937
|
+
if (this.shadowObservedWorkDepth > 0) {
|
|
31938
|
+
this.shadowQueuedProblem = this.shadowQueuedProblem ? `${this.shadowQueuedProblem}; ${reason}` : reason;
|
|
31939
|
+
return;
|
|
31940
|
+
}
|
|
31941
|
+
const liveConfig = this.deps.configStore.get();
|
|
31942
|
+
await this._spawnAndAssign(
|
|
31943
|
+
{
|
|
31944
|
+
name: "shadow",
|
|
31945
|
+
role: "shadow-agent",
|
|
31946
|
+
provider: liveConfig.provider,
|
|
31947
|
+
model: liveConfig.model,
|
|
31948
|
+
tools: [
|
|
31949
|
+
"fleet_status",
|
|
31950
|
+
"fleet_health",
|
|
31951
|
+
"fleet_usage",
|
|
31952
|
+
"mailbox",
|
|
31953
|
+
"mail_inbox",
|
|
31954
|
+
"mail_send",
|
|
31955
|
+
"terminate_subagent"
|
|
31956
|
+
]
|
|
31957
|
+
},
|
|
31958
|
+
buildShadowAgentTaskDescription(reason),
|
|
31959
|
+
{
|
|
31960
|
+
internalTask: true,
|
|
31961
|
+
stopShadowAfterTask: true,
|
|
31962
|
+
shadowIntervalMs: this.shadowHeartbeatIntervalMs
|
|
31963
|
+
}
|
|
31964
|
+
);
|
|
31965
|
+
} finally {
|
|
31966
|
+
this.shadowPassInFlight = false;
|
|
31967
|
+
if (this.shadowObservedWorkDepth === 0 && this.shadowQueuedProblem) {
|
|
31968
|
+
const queued = this.shadowQueuedProblem;
|
|
31969
|
+
this.shadowQueuedProblem = null;
|
|
31970
|
+
this.requestShadowPass(queued);
|
|
31971
|
+
}
|
|
31972
|
+
}
|
|
31973
|
+
}
|
|
31974
|
+
async stopShadowAfterTask(subagentId) {
|
|
31975
|
+
try {
|
|
31976
|
+
await this.getCoordinator().stop(subagentId);
|
|
31977
|
+
} finally {
|
|
31978
|
+
this.clearShadowAgent(subagentId);
|
|
31979
|
+
if (this.shadowObservedWorkDepth === 0 && this.shadowQueuedProblem) {
|
|
31980
|
+
const queued = this.shadowQueuedProblem;
|
|
31981
|
+
this.shadowQueuedProblem = null;
|
|
31982
|
+
this.requestShadowPass(queued);
|
|
31983
|
+
}
|
|
31368
31984
|
}
|
|
31369
31985
|
}
|
|
31370
31986
|
/**
|
|
@@ -31453,20 +32069,6 @@ Start your heartbeat loop now.`,
|
|
|
31453
32069
|
};
|
|
31454
32070
|
}
|
|
31455
32071
|
const tools = subCfg.tools ? [...subCfg.tools] : void 0;
|
|
31456
|
-
let injectedFleetEmit;
|
|
31457
|
-
let injectedFleetStatus;
|
|
31458
|
-
if (tools) {
|
|
31459
|
-
const emitIdx = tools.indexOf("fleet_emit");
|
|
31460
|
-
if (emitIdx >= 0 && this.fleetEmitTool) {
|
|
31461
|
-
tools.splice(emitIdx, 1);
|
|
31462
|
-
injectedFleetEmit = this.fleetEmitTool;
|
|
31463
|
-
}
|
|
31464
|
-
const statusIdx = tools.indexOf("fleet_status");
|
|
31465
|
-
if (statusIdx >= 0 && this.fleetStatusTool) {
|
|
31466
|
-
tools.splice(statusIdx, 1);
|
|
31467
|
-
injectedFleetStatus = this.fleetStatusTool;
|
|
31468
|
-
}
|
|
31469
|
-
}
|
|
31470
32072
|
const ctx = new Context({
|
|
31471
32073
|
systemPrompt: baseSystem,
|
|
31472
32074
|
provider,
|
|
@@ -31487,8 +32089,6 @@ Start your heartbeat loop now.`,
|
|
|
31487
32089
|
});
|
|
31488
32090
|
if (subCfg.role) ctx.meta["agentRole"] = subCfg.role;
|
|
31489
32091
|
const baseRegistry = this.subagentToolRegistry(tools);
|
|
31490
|
-
if (injectedFleetEmit) baseRegistry.register(injectedFleetEmit);
|
|
31491
|
-
if (injectedFleetStatus) baseRegistry.register(injectedFleetStatus);
|
|
31492
32092
|
const subAllowedCaps = this.resolveSubagentCapabilities(subCfg);
|
|
31493
32093
|
const toolExecutor = new ToolExecutor(baseRegistry, {
|
|
31494
32094
|
permissionPolicy: new AutoApprovePermissionPolicy(subAllowedCaps),
|
|
@@ -31516,7 +32116,13 @@ Start your heartbeat loop now.`,
|
|
|
31516
32116
|
});
|
|
31517
32117
|
agent.extensions.register(
|
|
31518
32118
|
createFallbackModelExtension({
|
|
31519
|
-
|
|
32119
|
+
// A per-task `fallbackModels` (set from the SDD board) overrides the
|
|
32120
|
+
// leader's chain for this subagent; otherwise it inherits the config's
|
|
32121
|
+
// explicit list or smart default. Mirrors the runtime light factory.
|
|
32122
|
+
getConfig: () => {
|
|
32123
|
+
const live = this.deps.configStore.get();
|
|
32124
|
+
return subCfg.fallbackModels?.length ? { ...live, fallbackModels: subCfg.fallbackModels } : live;
|
|
32125
|
+
},
|
|
31520
32126
|
buildProvider: (id) => this.buildSubagentProvider(config, id, effModel),
|
|
31521
32127
|
events
|
|
31522
32128
|
})
|
|
@@ -31659,7 +32265,15 @@ Start your heartbeat loop now.`,
|
|
|
31659
32265
|
const all = this.deps.toolRegistry.list();
|
|
31660
32266
|
if (!allow || allow.length === 0) return all;
|
|
31661
32267
|
const allowSet = new Set(allow);
|
|
31662
|
-
|
|
32268
|
+
const result = /* @__PURE__ */ new Map();
|
|
32269
|
+
for (const tool of all) {
|
|
32270
|
+
if (allowSet.has(tool.name)) result.set(tool.name, tool);
|
|
32271
|
+
}
|
|
32272
|
+
for (const name of allowSet) {
|
|
32273
|
+
const directorTool = this.directorToolsByName.get(name);
|
|
32274
|
+
if (directorTool && !result.has(name)) result.set(name, directorTool);
|
|
32275
|
+
}
|
|
32276
|
+
return Array.from(result.values());
|
|
31663
32277
|
}
|
|
31664
32278
|
/**
|
|
31665
32279
|
* Resolve the capability allowlist for a subagent's auto-approve policy.
|
|
@@ -31707,17 +32321,36 @@ Start your heartbeat loop now.`,
|
|
|
31707
32321
|
* the full tool registry.
|
|
31708
32322
|
*/
|
|
31709
32323
|
async spawn(description, opts) {
|
|
31710
|
-
|
|
32324
|
+
const isShadowSpawn = opts?.name === "shadow";
|
|
32325
|
+
if (isShadowSpawn) this.shadowAutoStartSuppressions++;
|
|
32326
|
+
try {
|
|
32327
|
+
await this.buildDirector();
|
|
32328
|
+
} finally {
|
|
32329
|
+
if (isShadowSpawn) this.shadowAutoStartSuppressions--;
|
|
32330
|
+
}
|
|
32331
|
+
if (isShadowSpawn && this.shadowAgentId && this.isActiveSubagent(this.shadowAgentId)) {
|
|
32332
|
+
return { subagentId: this.shadowAgentId, taskId: this.shadowTaskId ?? "shadow-active" };
|
|
32333
|
+
}
|
|
31711
32334
|
const subagentConfig = {
|
|
31712
32335
|
name: opts?.name ?? "adhoc",
|
|
31713
|
-
role: "general",
|
|
32336
|
+
role: isShadowSpawn ? "shadow-agent" : "general",
|
|
31714
32337
|
provider: opts?.provider,
|
|
31715
32338
|
model: opts?.model,
|
|
31716
32339
|
tools: opts?.tools,
|
|
31717
32340
|
allowedCapabilities: opts?.allowedCapabilities
|
|
31718
32341
|
};
|
|
31719
|
-
const { subagentId, taskId } = await this._spawnAndAssign(
|
|
31720
|
-
|
|
32342
|
+
const { subagentId, taskId } = await this._spawnAndAssign(
|
|
32343
|
+
subagentConfig,
|
|
32344
|
+
description,
|
|
32345
|
+
{
|
|
32346
|
+
internalTask: isShadowSpawn,
|
|
32347
|
+
stopShadowAfterTask: isShadowSpawn,
|
|
32348
|
+
shadowIntervalMs: opts?.shadowIntervalMs
|
|
32349
|
+
}
|
|
32350
|
+
);
|
|
32351
|
+
if (!isShadowSpawn) {
|
|
32352
|
+
this.fleetManager?.addPendingTask(taskId, subagentId, description);
|
|
32353
|
+
}
|
|
31721
32354
|
return { subagentId, taskId };
|
|
31722
32355
|
}
|
|
31723
32356
|
/**
|
|
@@ -31750,11 +32383,32 @@ Start your heartbeat loop now.`,
|
|
|
31750
32383
|
* Returns `{ subagentId, taskId }`. Caller holds `pending` tracking
|
|
31751
32384
|
* and event emission — the helper only talks to the coordinator.
|
|
31752
32385
|
*/
|
|
31753
|
-
async _spawnAndAssign(subagentConfig, description = "") {
|
|
32386
|
+
async _spawnAndAssign(subagentConfig, description = "", opts) {
|
|
31754
32387
|
const taskId = randomUUID();
|
|
31755
32388
|
if (!this.director) throw new Error("Director is not initialized");
|
|
31756
32389
|
const subagentId = await this.director.spawn(subagentConfig);
|
|
31757
|
-
|
|
32390
|
+
const task = { id: taskId, description, subagentId };
|
|
32391
|
+
if (opts?.internalTask) {
|
|
32392
|
+
this.markShadowTask(taskId);
|
|
32393
|
+
if (opts.stopShadowAfterTask) this.shadowStopAfterTaskIds.add(taskId);
|
|
32394
|
+
if (subagentConfig.name === "shadow" || subagentConfig.role === "shadow-agent") {
|
|
32395
|
+
this.recordShadowAgent(subagentId, taskId, opts.shadowIntervalMs);
|
|
32396
|
+
}
|
|
32397
|
+
try {
|
|
32398
|
+
await this.director.assignInternal(task);
|
|
32399
|
+
} catch (err) {
|
|
32400
|
+
this.shadowTaskIds.delete(taskId);
|
|
32401
|
+
this.shadowOutstandingTaskIds.delete(taskId);
|
|
32402
|
+
this.shadowStopAfterTaskIds.delete(taskId);
|
|
32403
|
+
if (subagentConfig.name === "shadow" || subagentConfig.role === "shadow-agent") {
|
|
32404
|
+
this.clearShadowAgent(subagentId);
|
|
32405
|
+
await this.director.remove(subagentId).catch(() => void 0);
|
|
32406
|
+
}
|
|
32407
|
+
throw err;
|
|
32408
|
+
}
|
|
32409
|
+
} else {
|
|
32410
|
+
await this.director.assign(task);
|
|
32411
|
+
}
|
|
31758
32412
|
return { subagentId, taskId };
|
|
31759
32413
|
}
|
|
31760
32414
|
/**
|
|
@@ -31791,7 +32445,7 @@ Start your heartbeat loop now.`,
|
|
|
31791
32445
|
}
|
|
31792
32446
|
const fleetStatus = this.fleetManager?.getFleetStatus() ?? { pending: []};
|
|
31793
32447
|
const pending = fleetStatus.pending.filter((p) => activeSubagentIds.has(p.subagentId));
|
|
31794
|
-
const completed = this.director ? this.director.completedResults() : [];
|
|
32448
|
+
const completed = this.director ? this.director.completedResults().filter((r) => !this.shadowTaskIds.has(r.taskId)) : [];
|
|
31795
32449
|
const completedCount = completed.length;
|
|
31796
32450
|
const liveCount = live.filter((s) => s.status === "running" || s.status === "idle").length;
|
|
31797
32451
|
const summary = !this.director ? "No subagents have been spawned." : liveCount > 0 ? `${pending.length} pending, ${liveCount} active, ${completedCount} completed.` : `${pending.length} pending, ${completedCount} completed.`;
|
|
@@ -31808,7 +32462,7 @@ Start your heartbeat loop now.`,
|
|
|
31808
32462
|
* the table renders the most interesting subagent at the top.
|
|
31809
32463
|
*/
|
|
31810
32464
|
usage() {
|
|
31811
|
-
const completed = this.director ? this.director.completedResults() : [];
|
|
32465
|
+
const completed = this.director ? this.director.completedResults().filter((r) => !this.shadowTaskIds.has(r.taskId)) : [];
|
|
31812
32466
|
const bySubagent = /* @__PURE__ */ new Map();
|
|
31813
32467
|
for (const r of completed) {
|
|
31814
32468
|
const cur = bySubagent.get(r.subagentId) ?? {
|
|
@@ -31912,9 +32566,11 @@ Start your heartbeat loop now.`,
|
|
|
31912
32566
|
async kill(subagentId) {
|
|
31913
32567
|
if (!this.director) return false;
|
|
31914
32568
|
await this.getCoordinator().stop(subagentId);
|
|
32569
|
+
if (this.shadowAgentId === subagentId) this.clearShadowAgent(subagentId);
|
|
31915
32570
|
return true;
|
|
31916
32571
|
}
|
|
31917
32572
|
async stopAll() {
|
|
32573
|
+
this.clearShadowAgent();
|
|
31918
32574
|
if (this.director) {
|
|
31919
32575
|
await this.getCoordinator().stopAll();
|
|
31920
32576
|
}
|
|
@@ -31958,6 +32614,11 @@ Start your heartbeat loop now.`,
|
|
|
31958
32614
|
* Safe to call multiple times — subsequent calls are no-ops.
|
|
31959
32615
|
*/
|
|
31960
32616
|
async dispose() {
|
|
32617
|
+
this.clearShadowAgent();
|
|
32618
|
+
for (const off of this.shadowActivityOffHandles) {
|
|
32619
|
+
off();
|
|
32620
|
+
}
|
|
32621
|
+
this.shadowActivityOffHandles.length = 0;
|
|
31961
32622
|
for (const off of this.directorOffHandles) {
|
|
31962
32623
|
off();
|
|
31963
32624
|
}
|
|
@@ -33690,6 +34351,22 @@ async function main(argv) {
|
|
|
33690
34351
|
}
|
|
33691
34352
|
});
|
|
33692
34353
|
brainMonitor.start();
|
|
34354
|
+
let shadowDefaults = {};
|
|
34355
|
+
const shadowController = {
|
|
34356
|
+
activeId: null,
|
|
34357
|
+
register(id) {
|
|
34358
|
+
this.activeId = id;
|
|
34359
|
+
},
|
|
34360
|
+
clear() {
|
|
34361
|
+
this.activeId = null;
|
|
34362
|
+
},
|
|
34363
|
+
getDefaults() {
|
|
34364
|
+
return { ...shadowDefaults };
|
|
34365
|
+
},
|
|
34366
|
+
setDefaults(defaults) {
|
|
34367
|
+
shadowDefaults = { ...shadowDefaults, ...defaults };
|
|
34368
|
+
}
|
|
34369
|
+
};
|
|
33693
34370
|
const multiAgentHost = new MultiAgentHost(
|
|
33694
34371
|
{
|
|
33695
34372
|
container,
|
|
@@ -33718,7 +34395,11 @@ async function main(argv) {
|
|
|
33718
34395
|
getLeaderMaxContext: () => effectiveMaxContext,
|
|
33719
34396
|
brain,
|
|
33720
34397
|
agentMonitor,
|
|
33721
|
-
traceId: sessResult.traceId
|
|
34398
|
+
traceId: sessResult.traceId,
|
|
34399
|
+
onShadowAgentStarted: (subagentId) => shadowController.register(subagentId),
|
|
34400
|
+
onShadowAgentStopped: (subagentId) => {
|
|
34401
|
+
if (shadowController.activeId === subagentId) shadowController.clear();
|
|
34402
|
+
}
|
|
33722
34403
|
}
|
|
33723
34404
|
);
|
|
33724
34405
|
toolRegistry.register(
|
|
@@ -33903,15 +34584,7 @@ async function main(argv) {
|
|
|
33903
34584
|
`)
|
|
33904
34585
|
});
|
|
33905
34586
|
const coordinatorController = {};
|
|
33906
|
-
const
|
|
33907
|
-
activeId: null,
|
|
33908
|
-
register(id) {
|
|
33909
|
-
this.activeId = id;
|
|
33910
|
-
},
|
|
33911
|
-
clear() {
|
|
33912
|
-
this.activeId = null;
|
|
33913
|
-
}
|
|
33914
|
-
};
|
|
34587
|
+
const sddRunRegistry = new SddRunRegistry();
|
|
33915
34588
|
const slashCmds = buildBuiltinSlashCommands({
|
|
33916
34589
|
registry: slashRegistry,
|
|
33917
34590
|
toolRegistry,
|
|
@@ -33971,9 +34644,6 @@ async function main(argv) {
|
|
|
33971
34644
|
},
|
|
33972
34645
|
onSpawn: async (description, spawnOpts) => {
|
|
33973
34646
|
const { subagentId, taskId } = await multiAgentHost.spawn(description, spawnOpts);
|
|
33974
|
-
if (shadowController && spawnOpts?.name === "shadow") {
|
|
33975
|
-
shadowController.register(subagentId);
|
|
33976
|
-
}
|
|
33977
34647
|
const tags = [];
|
|
33978
34648
|
if (spawnOpts?.provider) tags.push(spawnOpts.provider);
|
|
33979
34649
|
if (spawnOpts?.model) tags.push(spawnOpts.model);
|
|
@@ -34156,14 +34826,14 @@ ${color.dim("\u2500".repeat(40))}` : "";
|
|
|
34156
34826
|
if (!director) return null;
|
|
34157
34827
|
return director.snapshot();
|
|
34158
34828
|
},
|
|
34159
|
-
onFleetKill: () => {
|
|
34829
|
+
onFleetKill: async () => {
|
|
34160
34830
|
if (!director) return 0;
|
|
34161
34831
|
const s = director.status();
|
|
34162
34832
|
let killed = 0;
|
|
34163
34833
|
for (const sa of s.subagents) {
|
|
34164
34834
|
if (sa.status === "running" || sa.status === "idle") {
|
|
34165
34835
|
try {
|
|
34166
|
-
director.remove(sa.id);
|
|
34836
|
+
await director.remove(sa.id);
|
|
34167
34837
|
killed++;
|
|
34168
34838
|
} catch {
|
|
34169
34839
|
}
|
|
@@ -34171,10 +34841,10 @@ ${color.dim("\u2500".repeat(40))}` : "";
|
|
|
34171
34841
|
}
|
|
34172
34842
|
return killed;
|
|
34173
34843
|
},
|
|
34174
|
-
onFleetTerminate: (subagentId) => {
|
|
34844
|
+
onFleetTerminate: async (subagentId) => {
|
|
34175
34845
|
if (!director) return false;
|
|
34176
34846
|
try {
|
|
34177
|
-
director.terminate(subagentId);
|
|
34847
|
+
await director.terminate(subagentId);
|
|
34178
34848
|
return true;
|
|
34179
34849
|
} catch {
|
|
34180
34850
|
return false;
|
|
@@ -34591,7 +35261,6 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
34591
35261
|
context.model
|
|
34592
35262
|
),
|
|
34593
35263
|
onSddParallelRun: async (opts) => {
|
|
34594
|
-
const { SddParallelRun } = await import('@wrongstack/core');
|
|
34595
35264
|
const sdd = await Promise.resolve().then(() => (init_sdd(), sdd_exports));
|
|
34596
35265
|
const tracker2 = sdd.getTaskTracker();
|
|
34597
35266
|
const builder = sdd.getActiveBuilder();
|
|
@@ -34610,14 +35279,58 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
34610
35279
|
if (!graph) {
|
|
34611
35280
|
return "No task graph found for the current SDD session.";
|
|
34612
35281
|
}
|
|
34613
|
-
const
|
|
34614
|
-
|
|
35282
|
+
const core = await import('@wrongstack/core');
|
|
35283
|
+
let worktrees;
|
|
35284
|
+
if (process.env["WRONGSTACK_SDD_WORKTREES"] !== "0") {
|
|
35285
|
+
const { spawnSync } = await import('child_process');
|
|
35286
|
+
const inGit = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
35287
|
+
cwd: projectRoot,
|
|
35288
|
+
encoding: "utf8",
|
|
35289
|
+
windowsHide: true
|
|
35290
|
+
}).stdout?.trim() === "true";
|
|
35291
|
+
if (inGit) worktrees = new core.WorktreeManager({ projectRoot, events });
|
|
35292
|
+
}
|
|
35293
|
+
const boardStore = new core.SddBoardStore({ baseDir: wpaths.projectSddBoards });
|
|
35294
|
+
const verifyTask = core.makeCommandVerifier();
|
|
35295
|
+
const sddSupervisor = new core.SddSupervisor({
|
|
35296
|
+
brain,
|
|
35297
|
+
reassignModels: core.effectiveFallbackChain(config)
|
|
35298
|
+
});
|
|
35299
|
+
const sddSubagentFactory = multiAgentHost.makeSubagentFactory(config);
|
|
35300
|
+
const conflictMode = process.env["WRONGSTACK_SDD_CONFLICT_RESOLVER"];
|
|
35301
|
+
const conflictResolver = conflictMode === "prefer-incoming" ? core.makePreferSideConflictResolver("incoming") : conflictMode === "prefer-base" ? core.makePreferSideConflictResolver("base") : conflictMode === "llm" ? core.makeLlmConflictResolver({
|
|
35302
|
+
run: async (prompt) => {
|
|
35303
|
+
const r = await sddSubagentFactory({
|
|
35304
|
+
id: `sdd-conflict-${Date.now()}`,
|
|
35305
|
+
role: "executor",
|
|
35306
|
+
name: "Conflict Resolver",
|
|
35307
|
+
disabledTools: ["delegate"],
|
|
35308
|
+
// Only returns the resolved text; the core helper writes the
|
|
35309
|
+
// file. Keep it on the read-only capability floor.
|
|
35310
|
+
allowedCapabilities: ["fs.read", "net.outbound"]
|
|
35311
|
+
});
|
|
35312
|
+
try {
|
|
35313
|
+
const res = await r.agent.run([{ type: "text", text: prompt }]);
|
|
35314
|
+
return res.finalText ?? "";
|
|
35315
|
+
} finally {
|
|
35316
|
+
await r.dispose?.();
|
|
35317
|
+
}
|
|
35318
|
+
}
|
|
35319
|
+
}) : void 0;
|
|
35320
|
+
const handle = core.startSddRun({
|
|
34615
35321
|
tracker: tracker2,
|
|
34616
35322
|
graph,
|
|
34617
35323
|
agent,
|
|
34618
35324
|
projectRoot,
|
|
35325
|
+
events,
|
|
34619
35326
|
parallelSlots: opts?.parallelSlots,
|
|
34620
|
-
subagentFactory,
|
|
35327
|
+
subagentFactory: sddSubagentFactory,
|
|
35328
|
+
worktrees,
|
|
35329
|
+
boardStore,
|
|
35330
|
+
registry: sddRunRegistry,
|
|
35331
|
+
verifyTask,
|
|
35332
|
+
conflictResolver,
|
|
35333
|
+
superviseFailure: sddSupervisor.superviseFailure,
|
|
34621
35334
|
onProgress: (p) => {
|
|
34622
35335
|
renderer.write(
|
|
34623
35336
|
` \u2591 wave ${p.wave + 1} \xB7 ${p.completed}/${p.total} tasks \xB7 ${p.percent}% done
|
|
@@ -34625,27 +35338,70 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
34625
35338
|
);
|
|
34626
35339
|
}
|
|
34627
35340
|
});
|
|
34628
|
-
globalThis.__sddParallelRun = run;
|
|
34629
|
-
|
|
34630
|
-
|
|
34631
|
-
|
|
34632
|
-
|
|
34633
|
-
|
|
34634
|
-
|
|
34635
|
-
|
|
34636
|
-
|
|
34637
|
-
|
|
34638
|
-
|
|
35341
|
+
globalThis.__sddParallelRun = handle.run;
|
|
35342
|
+
try {
|
|
35343
|
+
const result = await handle.completion;
|
|
35344
|
+
const lines = [
|
|
35345
|
+
`SDD parallel run complete:`,
|
|
35346
|
+
` ${result.totalWaves} waves \xB7 ${result.totalCompleted} done \xB7 ${result.totalFailed} failed`,
|
|
35347
|
+
` ${(result.totalDurationMs / 1e3).toFixed(1)}s total`
|
|
35348
|
+
];
|
|
35349
|
+
if (result.deadlocked) lines.push(color.red(" \u26A0 deadlock \u2014 tasks blocked by failed tasks."));
|
|
35350
|
+
if (result.stopRequested) lines.push(color.yellow(" \u26A1 stopped by user."));
|
|
35351
|
+
return lines.join("\n");
|
|
35352
|
+
} finally {
|
|
35353
|
+
globalThis.__sddParallelRun = void 0;
|
|
35354
|
+
}
|
|
34639
35355
|
},
|
|
34640
35356
|
onSddParallelStop: () => {
|
|
34641
35357
|
const run = globalThis.__sddParallelRun;
|
|
34642
35358
|
run?.stop();
|
|
34643
35359
|
},
|
|
35360
|
+
onSddRetryAllFailed: () => sddRunRegistry.getActive()?.retryAllFailed() ?? 0,
|
|
35361
|
+
onSddSplitTask: (taskId, subtasks) => {
|
|
35362
|
+
const active = sddRunRegistry.getActive();
|
|
35363
|
+
if (!active) return null;
|
|
35364
|
+
const snap = active.snapshot();
|
|
35365
|
+
const match = snap.tasks.find((t) => t.id === taskId || t.shortId === taskId);
|
|
35366
|
+
const ids = active.splitTask(match?.id ?? taskId, subtasks);
|
|
35367
|
+
return ids.length ? ids : null;
|
|
35368
|
+
},
|
|
35369
|
+
// ── SDD lifecycle: clean worktrees · rollback commits · destroy project ──
|
|
35370
|
+
onSddCleanWorktrees: async () => {
|
|
35371
|
+
const active = sddRunRegistry.getActive();
|
|
35372
|
+
if (active) return active.cleanupWorktrees();
|
|
35373
|
+
const core = await import('@wrongstack/core');
|
|
35374
|
+
const { removed } = await core.cleanupSddWorktrees(projectRoot);
|
|
35375
|
+
return removed;
|
|
35376
|
+
},
|
|
35377
|
+
onSddRollback: async () => {
|
|
35378
|
+
const active = sddRunRegistry.getActive();
|
|
35379
|
+
if (active) return active.rollback();
|
|
35380
|
+
const core = await import('@wrongstack/core');
|
|
35381
|
+
return core.rollbackSddRunFromDisk({ projectRoot, boardsDir: wpaths.projectSddBoards });
|
|
35382
|
+
},
|
|
35383
|
+
onSddDestroy: async () => {
|
|
35384
|
+
sddRunRegistry.getActive()?.stop();
|
|
35385
|
+
const core = await import('@wrongstack/core');
|
|
35386
|
+
return core.destroySddProject({
|
|
35387
|
+
projectRoot,
|
|
35388
|
+
paths: {
|
|
35389
|
+
projectSpecs: wpaths.projectSpecs,
|
|
35390
|
+
projectTaskGraphs: wpaths.projectTaskGraphs,
|
|
35391
|
+
projectSddSession: wpaths.projectSddSession,
|
|
35392
|
+
projectSddBoards: wpaths.projectSddBoards
|
|
35393
|
+
}
|
|
35394
|
+
});
|
|
35395
|
+
},
|
|
34644
35396
|
onAutoPhaseStart: autoPhaseHost.onAutoPhaseStart,
|
|
34645
35397
|
onAutoPhasePause: autoPhaseHost.onAutoPhasePause,
|
|
34646
35398
|
onAutoPhaseResume: autoPhaseHost.onAutoPhaseResume,
|
|
34647
35399
|
onAutoPhaseStop: autoPhaseHost.onAutoPhaseStop,
|
|
34648
35400
|
getAutoPhaseRunner: autoPhaseHost.getAutoPhaseRunner,
|
|
35401
|
+
onAutoPhaseMoveTask: autoPhaseHost.onAutoPhaseMoveTask,
|
|
35402
|
+
onAutoPhaseAssignTask: autoPhaseHost.onAutoPhaseAssignTask,
|
|
35403
|
+
onAutoPhaseAddTask: autoPhaseHost.onAutoPhaseAddTask,
|
|
35404
|
+
onAutoPhaseRetryTask: autoPhaseHost.onAutoPhaseRetryTask,
|
|
34649
35405
|
onWorktree: autoPhaseHost.onWorktree
|
|
34650
35406
|
});
|
|
34651
35407
|
for (const cmd of slashCmds) slashRegistry.register(cmd);
|
|
@@ -34683,6 +35439,9 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
34683
35439
|
tokenCounter,
|
|
34684
35440
|
config,
|
|
34685
35441
|
configStore,
|
|
35442
|
+
// Real director-backed per-task agent factory — threaded to the CLI-hosted
|
|
35443
|
+
// WebUI so its "New SDD Project" wizard runs the same multi-agent fleet.
|
|
35444
|
+
sddSubagentFactory: multiAgentHost.makeSubagentFactory(config),
|
|
34686
35445
|
// Project-scoped mailbox — the AutonomousCoordinator in execution.ts
|
|
34687
35446
|
// subscribes to it so goals/tasks/knowledge are shared with other
|
|
34688
35447
|
// terminals working on the same project.
|
|
@@ -34834,6 +35593,10 @@ Reply YES to auto-proceed, NO to wait for human input.`
|
|
|
34834
35593
|
},
|
|
34835
35594
|
getEternalEngine: () => eternalEngine,
|
|
34836
35595
|
getParallelEngine: () => parallelEngine,
|
|
35596
|
+
// Active SDD parallel run (if any). Lets the REPL/TUI SIGINT handler stop a
|
|
35597
|
+
// running `/sdd parallel` mid-flight — without this the run is unreachable
|
|
35598
|
+
// from Ctrl+C (it has its own coordinator, not the autonomy engines).
|
|
35599
|
+
getSddRun: () => sddRunRegistry.getActive(),
|
|
34837
35600
|
subscribeEternalIteration: (fn) => {
|
|
34838
35601
|
eternalListeners.add(fn);
|
|
34839
35602
|
return () => eternalListeners.delete(fn);
|