@wrongstack/cli 0.283.0 → 0.283.1

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { CODEX_MODELS, color, ToolCapabilities, PHASE_EVENT_NAMES, readBundledInstructionText, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, ConfigError, resolveHqDataDir, ensureHqFirstRunAuthFile, DEFAULT_HQ_REDACTION_POLICY, createHqPersistence, HqCommandAuditLog, HqAlertEngine, toAlertMessage, validateHqCommand, watchHqAuthFile, parseHqFrame, HQ_PROTOCOL_VERSION, parseHqEventPayload, scrubAndTruncateHqPreview, FetchError, removeColumn, addColumn, getTask, removeTask, getBoard, assignTask, updateTaskAssignment, listBoards, updateGoalMetricOnTask, addGoalMetricToTask, releaseTaskClaim, claimReadyTask, getTaskChain, setTaskChain, copyTaskToBoard, transferTaskToBoard, moveTask, updateTask, mergeTasks, splitTask, addTask, syncBoardFromTaskGraph, deserializeTaskGraph, exportBoardToTaskGraph, serializeTaskGraph, getKanbanOrchestrationSnapshot, listReadyTasks, generateBoardFromDescription, createBoard, parseLinesIntoTasks, removeBoard, duplicateBoard, updateBoard, wstackGlobalRoot, DefaultSecretScrubber, resolveWstackPaths, watchProviderConfig, TOKENS, PromptUsageStore, ToolRegistry, GlobalMailbox, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, shellHooksEqual, countShellHooks, normalizeTokenSavingTier, allServers as allServers$1, SlashCommandRegistry, attachDepWatcherBridge, createFallbackModelExtension, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, SddRunRegistry, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, buildTranscriptFromEvents, DefaultLogger, DefaultModelsRegistry, isStdinTTY, FsError, atomicWrite, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, sessionScopedPath as sessionScopedPath$1, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, makeFleetStatusTool, applyToolDescriptionModes, applyToolResultRenderModes, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, createHqPublisherFromEnv, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, applyModelRuntime, installDesignStudioMiddleware, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, WorktreeManager, Director, makeFleetEmitTool, AdaptiveConcurrencyController, resolveSubagentModelTarget, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, mergeModelRuntime, ToolValidationError, WIDE_SUBAGENT_CAPABILITIES, resolveProjectDir, FleetSupervisor, AgentError, PhaseStore, PhaseOrchestrator, assignNickname, AutoPhasePlanner, PhaseGraphBuilder, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, CHIMERA_REVIEW_PROMPT, setQueuedMessagesSnapshot, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, ProviderRegistry, makePreferSideConflictResolver, makeAgentSubagentRunner, NULL_FLEET_BUS, ERROR_CODES, buildChildEnv, exportBoardAsMarkdown, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENT_CATALOG, dispatchAgent, normalizeToolDescriptionMode, getToolDescriptionMode, noOpVault, formatTodosList, loadTasks, formatTaskList, formatTaskProgress, mutateTasks, emptyPlan, addPlanItem, savePlan, formatPlan, recordCompletedWorkEvidence, saveCompletedWorkCheckpoint, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, getDesignKitLoader, getDesignState, clearActiveKit, clearPersistedActiveKit, recordOverrides, setDesignOverrides, loadActiveKit, isDesignStack, applyTokenOverrides, materializeTokens, setActiveKit, readHqRuntimeFileSync, resolveHqConfig, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, resolveModelMatrix, resolveModelTargetFromEntry, fallbackProfileChain, parseModelRef, normalizeModelRef, onResize, hasOpenTodos, InputBuilder, detectContinueIntent, resolveContinuation, DefaultSecretVault as DefaultSecretVault$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, DEFAULT_CONTEXT_WINDOW_MODE_ID, HQ_AUTH_FILE_VERSION, truncate, createBoardFromTaskGraph, addDependency, addNoteToTask, addCheckToTask, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, normalizeToolResultRenderMode, getToolResultRenderMode, setToolResultRenderMode, resolveMailboxIdentity, smartDefaultFallbackChain, AutonomousCoordinator, mutateHqAuthFile, mintHqToken, readHqAuthFile, areDependenciesMet, findBlockedTasks, renderInstructionTemplate, recentTextTurns, gatedEnhancerReasoning, enhanceUserPrompt, mutatePlan, setPlanItemStatus, getPlanTemplate, resolveProviderModelList, ParseError, startSessionTelemetryBridge, startFleetTelemetryBridge, startBrainTelemetryBridge, startWorktreeTelemetryBridge, startToolTelemetryBridge, startCostTelemetryBridge } from '@wrongstack/core';
2
+ import { CODEX_MODELS, color, ToolCapabilities, PHASE_EVENT_NAMES, readBundledInstructionText, writeErr, loadPlugins, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, withFileLock, ConfigError, resolveHqDataDir, ensureHqFirstRunAuthFile, DEFAULT_HQ_REDACTION_POLICY, createHqPersistence, HqCommandAuditLog, HqAlertEngine, toAlertMessage, validateHqCommand, watchHqAuthFile, parseHqFrame, HQ_PROTOCOL_VERSION, parseHqEventPayload, scrubAndTruncateHqPreview, FetchError, removeColumn, addColumn, getTask, removeTask, getBoard, assignTask, updateTaskAssignment, listBoards, updateGoalMetricOnTask, addGoalMetricToTask, releaseTaskClaim, claimReadyTask, getTaskChain, setTaskChain, copyTaskToBoard, transferTaskToBoard, moveTask, updateTask, mergeTasks, splitTask, addTask, syncBoardFromTaskGraph, deserializeTaskGraph, exportBoardToTaskGraph, serializeTaskGraph, getKanbanOrchestrationSnapshot, listReadyTasks, generateBoardFromDescription, createBoard, parseLinesIntoTasks, removeBoard, duplicateBoard, updateBoard, wstackGlobalRoot, DefaultSecretScrubber, resolveWstackPaths, watchProviderConfig, TOKENS, PromptUsageStore, ToolRegistry, GlobalMailbox, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, shellHooksEqual, countShellHooks, normalizeTokenSavingTier, allServers as allServers$1, SlashCommandRegistry, attachDepWatcherBridge, createFallbackModelExtension, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, createTieredBrainArbiter, DefaultBrainArbiter, BrainMonitor, mailboxSessionTag, createDelegateTool, FLEET_ROSTER, createMcpControlTool, startTechStackConsumer, startPackageOutdatedWatcher, SddRunRegistry, recordFileAction, createAutonomyBrain, DefaultPluginAPI, SpecVersioning, buildTranscriptFromEvents, DefaultLogger, DefaultModelsRegistry, isStdinTTY, FsError, atomicWrite, EventBus, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, sessionScopedPath as sessionScopedPath$1, createContextManagerTool, makeMailboxTool, makeMailSendTool, makeMailInboxTool, makeFleetStatusTool, applyToolDescriptionModes, applyToolResultRenderModes, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, createHqPublisherFromEnv, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, Context, QueueStore, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, applyModelRuntime, installDesignStudioMiddleware, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, FleetManager, makeDirectorSessionFactory, WorktreeManager, Director, makeFleetEmitTool, AdaptiveConcurrencyController, resolveSubagentModelTarget, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, mergeModelRuntime, ToolValidationError, WIDE_SUBAGENT_CAPABILITIES, resolveProjectDir, FleetSupervisor, AgentError, PhaseStore, PhaseOrchestrator, assignNickname, AutoPhasePlanner, PhaseGraphBuilder, makeLLMClassifier, writeOut, ParallelEternalEngine, EternalAutonomyEngine, CHIMERA_REVIEW_PROMPT, setQueuedMessagesSnapshot, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, ProviderRegistry, makePreferSideConflictResolver, makeAgentSubagentRunner, NULL_FLEET_BUS, ERROR_CODES, buildChildEnv, exportBoardAsMarkdown, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENT_CATALOG, dispatchAgent, normalizeToolDescriptionMode, getToolDescriptionMode, noOpVault, formatTodosList, loadTasks, formatTaskList, formatTaskProgress, mutateTasks, emptyPlan, addPlanItem, savePlan, formatPlan, recordCompletedWorkEvidence, saveCompletedWorkCheckpoint, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, getDesignKitLoader, getDesignState, clearActiveKit, clearPersistedActiveKit, recordOverrides, setDesignOverrides, loadActiveKit, isDesignStack, applyTokenOverrides, materializeTokens, setActiveKit, readHqRuntimeFileSync, resolveHqConfig, MATRIX_PHASE_KEYS, matrixKeyKind, phaseForRole, resolveModelMatrix, resolveModelTargetFromEntry, fallbackProfileChain, parseModelRef, normalizeModelRef, onResize, hasOpenTodos, InputBuilder, detectContinueIntent, resolveContinuation, DefaultSecretVault as DefaultSecretVault$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, DEFAULT_CONTEXT_WINDOW_MODE_ID, HQ_AUTH_FILE_VERSION, stripAnsi, truncate, createBoardFromTaskGraph, addDependency, addNoteToTask, addCheckToTask, estimateMessageTokens, AGENTS_BY_PHASE, validateAgainstSchema, normalizeToolResultRenderMode, getToolResultRenderMode, setToolResultRenderMode, resolveMailboxIdentity, smartDefaultFallbackChain, AutonomousCoordinator, mutateHqAuthFile, mintHqToken, readHqAuthFile, areDependenciesMet, findBlockedTasks, renderInstructionTemplate, recentTextTurns, gatedEnhancerReasoning, enhanceUserPrompt, mutatePlan, setPlanItemStatus, getPlanTemplate, resolveProviderModelList, ParseError, startSessionTelemetryBridge, startFleetTelemetryBridge, startBrainTelemetryBridge, startWorktreeTelemetryBridge, startToolTelemetryBridge, startCostTelemetryBridge } from '@wrongstack/core';
3
3
  import * as fs2 from 'fs/promises';
4
4
  import { access, readFile } from 'fs/promises';
5
5
  import { DefaultSecretVault, decryptConfigSecrets, encryptConfigSecrets, isSecretField } from '@wrongstack/core/security';
@@ -4219,7 +4219,66 @@ var init_hq_dashboard_html = __esm({
4219
4219
  .subbadge { font-size: 9px; text-transform: uppercase; letter-spacing: 0.5px; padding: 1px 6px; border-radius: 999px; background: rgba(163,113,247,0.18); color: var(--purple); font-weight: 700; }
4220
4220
  .chat-meta { margin-left: auto; font-size: 11px; color: var(--dim); }
4221
4221
  .chat-body { flex: 1; overflow-y: auto; padding: 16px 22px; }
4222
- .bub.live { border-color: rgba(63,185,80,0.5); box-shadow: 0 0 0 1px rgba(63,185,80,0.15); }
4222
+ .cmd-dock { border-top: 1px solid var(--border); background: var(--bg2); padding: 10px 14px; display: grid; gap: 8px; }
4223
+ .cmd-row { display: flex; gap: 8px; align-items: center; }
4224
+ .cmd-target { min-width: 220px; max-width: 44%; flex: 0 1 360px; background: var(--inset); border: 1px solid var(--border); color: var(--text); border-radius: 7px; padding: 7px 9px; font-size: 12px; }
4225
+ .cmd-type { flex: 0 0 auto; min-width: 84px; background: var(--inset); border: 1px solid var(--border); color: var(--text); border-radius: 7px; padding: 7px 9px; font-size: 12px; font-weight: 700; }
4226
+ .cmd-type:disabled { opacity: 0.55; cursor: not-allowed; }
4227
+ .cmd-text { flex: 1; min-height: 38px; max-height: 120px; resize: vertical; background: var(--inset); border: 1px solid var(--border); color: var(--text); border-radius: 7px; padding: 8px 10px; font: 12.5px/1.4 ui-sans-serif, system-ui, sans-serif; }
4228
+ .cmd-target:focus, .cmd-type:focus, .cmd-text:focus { outline: none; border-color: var(--accent); }
4229
+ .cmd-send { flex: 0 0 auto; background: var(--accent); color: #04121f; border: 1px solid var(--accent); border-radius: 7px; padding: 8px 12px; font-size: 12px; font-weight: 800; cursor: pointer; }
4230
+ .cmd-send:disabled { opacity: 0.5; cursor: not-allowed; }
4231
+ .cmd-status { font-size: 11px; color: var(--dim); min-height: 14px; }
4232
+ .cmd-status.ok { color: var(--green); }
4233
+ .cmd-status.err { color: var(--red); }
4234
+ .cmd-history { border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); background: var(--panel); padding: 10px 18px; }
4235
+ .cmd-hhead { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
4236
+ .cmd-htitle { font-size: 11px; font-weight: 800; color: var(--bright); text-transform: uppercase; letter-spacing: 0.6px; }
4237
+ .cmd-hnote { font-size: 10.5px; color: var(--dim); }
4238
+ .cmd-hrefresh { margin-left: auto; background: var(--inset); border: 1px solid var(--border); border-radius: 7px; color: var(--muted); cursor: pointer; font-size: 11px; padding: 5px 9px; }
4239
+ .cmd-hrefresh:hover { border-color: var(--accent); color: var(--text); }
4240
+ .cmd-hlist { display: grid; gap: 5px; }
4241
+ .cmd-hrow { display: grid; grid-template-columns: 92px 82px minmax(0, 1fr) 88px; gap: 8px; align-items: center; font-size: 11.5px; color: var(--muted); background: var(--inset); border: 1px solid var(--border); border-radius: 7px; padding: 6px 8px; }
4242
+ .cmd-htype { color: var(--bright); font-weight: 700; }
4243
+ .cmd-hstatus { text-transform: uppercase; font-size: 9.5px; font-weight: 800; letter-spacing: 0.5px; color: var(--amber); }
4244
+ .cmd-hstatus.acked { color: var(--green); }
4245
+ .cmd-hstatus.delivered { color: var(--cyan); }
4246
+ .cmd-hstatus.failed, .cmd-hstatus.rejected { color: var(--red); }
4247
+ .cmd-hclient, .cmd-hmsg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4248
+ .cmd-history .side-empty { padding: 12px; font-size: 11.5px; }
4249
+ .timeline-wrap { flex: 1; min-height: 0; display: flex; flex-direction: column; background: var(--bg2); }
4250
+ .timeline-head { padding: 12px 18px; border-bottom: 1px solid var(--border); display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
4251
+ .timeline-title { font-size: 13px; font-weight: 800; color: var(--bright); }
4252
+ .timeline-note { font-size: 11px; color: var(--dim); }
4253
+ .timeline-filters { margin-left: auto; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
4254
+ .timeline-input, .timeline-select { background: var(--inset); border: 1px solid var(--border); color: var(--text); border-radius: 7px; padding: 6px 9px; font-size: 12px; }
4255
+ .timeline-input { width: 260px; }
4256
+ .timeline-input:focus, .timeline-select:focus { outline: none; border-color: var(--accent); }
4257
+ .timeline-btn { background: var(--inset); border: 1px solid var(--border); color: var(--muted); border-radius: 7px; padding: 6px 10px; font-size: 12px; cursor: pointer; }
4258
+ .timeline-btn:hover { color: var(--text); border-color: var(--accent); }
4259
+ .timeline-list { flex: 1; min-height: 0; overflow-y: auto; padding: 12px 18px 18px; display: grid; gap: 8px; align-content: start; }
4260
+ .timeline-row { display: grid; grid-template-columns: 34px 92px minmax(170px, 260px) minmax(0, 1fr); gap: 10px; border: 1px solid var(--border); border-radius: 10px; padding: 9px 10px; cursor: pointer; background: var(--panel); align-items: start; }
4261
+ .timeline-row:hover { background: var(--panel2); border-color: var(--accent); }
4262
+ .timeline-row.tool { border-color: rgba(57,208,216,0.22); background: rgba(57,208,216,0.045); }
4263
+ .timeline-row.assistant { border-color: rgba(163,113,247,0.20); }
4264
+ .timeline-row.error { border-color: rgba(248,81,73,0.32); background: rgba(248,81,73,0.06); }
4265
+ .timeline-mark { width: 28px; height: 28px; border-radius: 9px; display: grid; place-items: center; background: var(--inset); border: 1px solid var(--border); color: var(--muted); font-size: 14px; }
4266
+ .timeline-row.assistant .timeline-mark { color: var(--purple); border-color: rgba(163,113,247,0.35); background: rgba(163,113,247,0.12); }
4267
+ .timeline-row.tool .timeline-mark { color: var(--cyan); border-color: rgba(57,208,216,0.35); background: rgba(57,208,216,0.11); }
4268
+ .timeline-row.user .timeline-mark { color: var(--accent); border-color: rgba(88,166,255,0.35); background: rgba(88,166,255,0.11); }
4269
+ .timeline-row.error .timeline-mark { color: var(--red); border-color: rgba(248,81,73,0.38); background: rgba(248,81,73,0.12); }
4270
+ .timeline-time { color: var(--dim); font-size: 11px; font-variant-numeric: tabular-nums; }
4271
+ .timeline-who { min-width: 0; }
4272
+ .timeline-agent { color: var(--bright); font-weight: 700; font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4273
+ .timeline-project { color: var(--muted); font-size: 10.5px; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4274
+ .timeline-msg { min-width: 0; color: var(--text); font-size: 12.5px; line-height: 1.45; white-space: pre-wrap; word-break: break-word; max-height: 86px; overflow: hidden; }
4275
+ .timeline-role { display: inline-block; margin-right: 6px; color: var(--dim); text-transform: uppercase; font-size: 9.5px; letter-spacing: 0.5px; font-weight: 800; }
4276
+ .timeline-role.user { color: var(--accent); }
4277
+ .timeline-role.assistant { color: var(--purple); }
4278
+ .timeline-role.tool { color: var(--cyan); }
4279
+ .timeline-role.error { color: var(--red); }
4280
+ .timeline-toolchip { display: inline-block; margin-right: 6px; border: 1px solid rgba(57,208,216,0.32); background: rgba(57,208,216,0.08); color: var(--cyan); border-radius: 999px; padding: 1px 7px; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 10px; }
4281
+ .bub.live .bub-card { border-color: rgba(63,185,80,0.5); box-shadow: 0 0 0 1px rgba(63,185,80,0.15); }
4223
4282
  .bub.live .live-dot { margin-left: auto; color: var(--green); font-size: 9px; animation: livepulse 1.4s ease-in-out infinite; }
4224
4283
  @keyframes livepulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
4225
4284
  .caret { display: inline-block; width: 7px; height: 13px; margin-left: 2px; background: var(--green); vertical-align: text-bottom; animation: blink 1s steps(2, start) infinite; }
@@ -4243,7 +4302,9 @@ var init_hq_dashboard_html = __esm({
4243
4302
  .fnode.terminal.k-repl { border-left-color: var(--amber); }
4244
4303
  .fnode.terminal.k-webui { border-left-color: var(--accent); }
4245
4304
  .fnode.terminal.k-cli { border-left-color: var(--cyan); }
4246
- .fnode.agent { width: 220px; }
4305
+ .fnode.agent { width: 196px; padding: 9px 10px; }
4306
+ .fnode.agent .fnode-sub { max-height: 30px; white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
4307
+ .fnode.agent .fnode-chips { max-height: 34px; overflow: hidden; }
4247
4308
  .fnode.agent.s-running, .fnode.agent.s-streaming { border-color: var(--green); box-shadow: 0 0 0 1px rgba(63,185,80,0.25), 0 10px 22px rgba(0,0,0,0.4); animation: pulse 1.8s ease-in-out infinite; }
4248
4309
  .fnode.agent.s-waiting_user { border-color: var(--amber); }
4249
4310
  .fnode.agent.s-error { border-color: var(--red); }
@@ -4265,30 +4326,82 @@ var init_hq_dashboard_html = __esm({
4265
4326
  .side-close:hover { background: var(--panel); color: var(--text); }
4266
4327
  .side-body { flex: 1; overflow-y: auto; padding: 14px 16px; }
4267
4328
  .side-empty { margin: auto; color: var(--dim); font-style: italic; text-align: center; padding: 40px 24px; }
4268
- .bub { margin-bottom: 12px; border-radius: 10px; padding: 9px 12px; font-size: 12.5px; line-height: 1.5; border: 1px solid var(--border); }
4269
- .bub .bub-meta { font-size: 10px; color: var(--dim); margin-bottom: 4px; display: flex; gap: 8px; align-items: center; text-transform: uppercase; letter-spacing: 0.5px; }
4270
- .bub .bub-role { font-weight: 700; }
4271
- .bub.user { background: rgba(88,166,255,0.08); border-color: rgba(88,166,255,0.25); }
4272
- .bub.user .bub-role { color: var(--accent); }
4273
- .bub.assistant { background: var(--panel); }
4329
+ .bub { margin-bottom: 14px; display: flex; gap: 10px; align-items: flex-start; font-size: 13px; line-height: 1.55; }
4330
+ .bub.user { flex-direction: row-reverse; }
4331
+ .bub-avatar { flex: 0 0 32px; width: 32px; height: 32px; border-radius: 10px; display: grid; place-items: center; font-size: 15px; border: 1px solid var(--border2); background: var(--panel); color: var(--muted); box-shadow: 0 0 0 3px rgba(13,17,23,0.65); }
4332
+ .bub.assistant .bub-avatar { background: rgba(163,113,247,0.16); color: var(--purple); border-color: rgba(163,113,247,0.35); }
4333
+ .bub.user .bub-avatar { background: var(--accent); color: #04121f; border-color: rgba(88,166,255,0.7); }
4334
+ .bub.tool .bub-avatar { background: rgba(57,208,216,0.13); color: var(--cyan); border-color: rgba(57,208,216,0.35); }
4335
+ .bub.error .bub-avatar { background: rgba(248,81,73,0.14); color: var(--red); border-color: rgba(248,81,73,0.4); }
4336
+ .bub.system .bub-avatar { background: var(--inset); color: var(--dim); }
4337
+ .bub-content { min-width: 0; flex: 1; max-width: 860px; display: flex; flex-direction: column; gap: 5px; }
4338
+ .bub.user .bub-content { align-items: flex-end; }
4339
+ .bub-head { display: flex; align-items: center; gap: 8px; padding: 0 2px; color: var(--dim); font-size: 10.5px; }
4340
+ .bub-role { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.6px; color: var(--muted); }
4274
4341
  .bub.assistant .bub-role { color: var(--purple); }
4275
- .bub.tool { background: rgba(57,208,216,0.05); border-color: rgba(57,208,216,0.18); }
4342
+ .bub.user .bub-role { color: var(--accent); }
4276
4343
  .bub.tool .bub-role { color: var(--cyan); }
4277
- .bub.error { background: rgba(248,81,73,0.08); border-color: rgba(248,81,73,0.3); }
4278
4344
  .bub.error .bub-role { color: var(--red); }
4279
- .bub.system { background: transparent; border-style: dashed; color: var(--muted); }
4280
- .bub pre { margin: 0; white-space: pre-wrap; word-break: break-word; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11.5px; max-height: 320px; overflow: auto; }
4345
+ .bub-card { width: fit-content; max-width: 100%; border: 1px solid var(--border); border-radius: 14px; padding: 10px 12px; background: var(--panel); color: var(--text); box-shadow: 0 8px 20px rgba(0,0,0,0.18); }
4346
+ .bub.assistant .bub-card { background: var(--panel); border-color: var(--border2); border-bottom-left-radius: 5px; }
4347
+ .bub.user .bub-card { background: rgba(88,166,255,0.16); border-color: rgba(88,166,255,0.35); border-bottom-right-radius: 5px; }
4348
+ .bub.tool .bub-card { background: rgba(57,208,216,0.06); border-color: rgba(57,208,216,0.22); border-bottom-left-radius: 5px; }
4349
+ .bub.error .bub-card { background: rgba(248,81,73,0.08); border-color: rgba(248,81,73,0.32); color: var(--text); }
4350
+ .bub.system .bub-card { background: transparent; border-style: dashed; color: var(--muted); box-shadow: none; }
4351
+ .bub pre { margin: 0; white-space: pre-wrap; word-break: break-word; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11.5px; max-height: 360px; overflow: auto; }
4281
4352
  .bub .txt { white-space: pre-wrap; word-break: break-word; }
4353
+ .assistant-body, .txt { word-break: break-word; }
4354
+ .msg-md { display: grid; gap: 8px; }
4355
+ .msg-md p { margin: 0; white-space: pre-wrap; }
4356
+ .md-heading { font-weight: 800; color: var(--bright); margin-top: 2px; }
4357
+ .md-bullet { display: grid; grid-template-columns: 14px minmax(0, 1fr); gap: 6px; align-items: start; }
4358
+ .md-bullet-dot { color: var(--accent); }
4359
+ .md-inline { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 0.92em; padding: 1px 4px; border-radius: 5px; background: var(--inset); border: 1px solid var(--border); color: var(--cyan); }
4360
+ .md-codewrap { border: 1px solid var(--border); border-radius: 9px; overflow: hidden; background: var(--bg2); }
4361
+ .md-codelabel { display: flex; align-items: center; gap: 6px; padding: 5px 9px; border-bottom: 1px solid var(--border); color: var(--dim); font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; }
4362
+ .md-code { margin: 0; padding: 10px 11px; max-height: 360px; overflow: auto; white-space: pre; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11.5px; line-height: 1.45; color: var(--text); }
4363
+ .tool-card { display: grid; gap: 8px; min-width: min(520px, 100%); }
4364
+ .tool-head { display: flex; align-items: center; gap: 8px; color: var(--text); }
4365
+ .tool-name { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 12px; font-weight: 800; color: var(--cyan); }
4366
+ .tool-status { margin-left: auto; font-size: 10px; text-transform: uppercase; letter-spacing: 0.6px; color: var(--green); font-weight: 800; }
4367
+ .tool-status.error { color: var(--red); }
4368
+ .tool-duration { font-size: 10.5px; color: var(--dim); font-variant-numeric: tabular-nums; }
4369
+ .tool-group { margin: 0 0 14px 42px; max-width: 860px; border: 1px solid var(--border); border-radius: 12px; background: rgba(57,208,216,0.045); overflow: hidden; }
4370
+ .tool-group > summary { cursor: pointer; list-style: none; display: flex; align-items: center; gap: 8px; padding: 9px 11px; color: var(--text); user-select: none; }
4371
+ .tool-group > summary::-webkit-details-marker { display: none; }
4372
+ .tool-group > summary::before { content: '\u25B8'; color: var(--dim); font-size: 10px; }
4373
+ .tool-group[open] > summary::before { content: '\u25BE'; }
4374
+ .tool-group-title { font-weight: 800; color: var(--cyan); font-size: 12px; }
4375
+ .tool-group-meta { color: var(--muted); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4376
+ .tool-group-status { margin-left: auto; font-size: 10px; text-transform: uppercase; font-weight: 800; letter-spacing: 0.5px; color: var(--green); }
4377
+ .tool-group-status.error { color: var(--red); }
4378
+ .tool-group-body { padding: 10px 12px 2px; border-top: 1px solid var(--border); background: rgba(13,17,23,0.32); }
4379
+ .tool-group-body .bub { margin-bottom: 10px; }
4380
+ .tool-group-body .bub-content { max-width: 100%; }
4381
+ .tool-group-body .bub-avatar { width: 26px; height: 26px; flex-basis: 26px; border-radius: 8px; font-size: 12px; }
4282
4382
  .bub-sublabel { font-size: 9px; text-transform: uppercase; letter-spacing: 0.6px; color: var(--dim); margin: 4px 0 2px; }
4283
- .bub-fold { margin-top: 6px; }
4284
- .bub-fold > summary { cursor: pointer; list-style: none; font-size: 11px; color: var(--cyan); padding: 4px 9px; background: var(--inset); border: 1px solid var(--border); border-radius: 6px; user-select: none; display: flex; align-items: center; gap: 6px; font-variant-numeric: tabular-nums; }
4383
+ .bub-fold { margin-top: 0; }
4384
+ .bub-fold > summary { cursor: pointer; list-style: none; font-size: 11px; color: var(--cyan); padding: 6px 9px; background: var(--inset); border: 1px solid var(--border); border-radius: 8px; user-select: none; display: flex; align-items: center; gap: 6px; font-variant-numeric: tabular-nums; }
4285
4385
  .bub-fold > summary::-webkit-details-marker { display: none; }
4286
4386
  .bub-fold > summary::before { content: '\u25B8'; color: var(--dim); font-size: 9px; }
4287
4387
  .bub-fold[open] > summary::before { content: '\u25BE'; }
4288
4388
  .bub-fold > summary:hover { border-color: var(--accent); color: var(--text); }
4289
4389
  .bub-fold[open] > summary { border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
4290
- .bub-fold > pre { margin: 0; border: 1px solid var(--border); border-top: none; border-radius: 0 0 6px 6px; padding: 8px 9px; background: var(--bg2); }
4390
+ .bub-fold > pre { margin: 0; border: 1px solid var(--border); border-top: none; border-radius: 0 0 8px 8px; padding: 9px 10px; background: var(--bg2); }
4291
4391
  .bub-argpre { color: var(--cyan); }
4392
+ .tool-output { border: 1px solid var(--border); border-top: none; border-radius: 0 0 8px 8px; background: var(--bg2); overflow: hidden; }
4393
+ .tool-output-head { display: flex; align-items: center; gap: 8px; padding: 6px 9px; border-bottom: 1px solid var(--border); color: var(--dim); font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; }
4394
+ .tool-output-kind { color: var(--cyan); font-family: ui-monospace, "SF Mono", Menlo, monospace; font-weight: 800; }
4395
+ .tool-output-meta { margin-left: auto; font-variant-numeric: tabular-nums; }
4396
+ .tool-output pre { margin: 0; padding: 9px 10px; max-height: 380px; overflow: auto; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11.5px; line-height: 1.45; }
4397
+ .tool-output.error pre { color: var(--red); }
4398
+ .tool-output.bash pre { white-space: pre-wrap; word-break: break-word; }
4399
+ .tool-output.json pre, .tool-output.numbered pre { white-space: pre; }
4400
+ .tool-output-footer { display: flex; gap: 12px; border-top: 1px solid var(--border); padding: 5px 9px; background: rgba(255,255,255,0.02); color: var(--dim); font-size: 10.5px; font-variant-numeric: tabular-nums; }
4401
+ .tool-output-footer.bad { color: var(--red); }
4402
+ .line-view { display: flex; max-height: 380px; overflow: auto; }
4403
+ .line-gutter { flex: 0 0 auto; min-width: 38px; padding: 9px 8px; border-right: 1px solid var(--border); background: rgba(255,255,255,0.025); color: var(--dim); text-align: right; user-select: none; font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 11.5px; line-height: 1.45; white-space: pre; }
4404
+ .line-view pre { flex: 1; max-height: none; min-width: 0; }
4292
4405
  .loading { color: var(--muted); font-style: italic; padding: 20px 0; }
4293
4406
 
4294
4407
  /* Mailbox tab (demoted) */
@@ -4314,7 +4427,8 @@ var init_hq_dashboard_html = __esm({
4314
4427
  /* shared data store (framework-agnostic) */
4315
4428
  var Store = {
4316
4429
  snapshot: null, connected: false, tab: 'console', theme: 'dark',
4317
- selected: null, transcripts: {}, agentMsgs: {}, listeners: new Set(),
4430
+ selected: null, transcripts: {}, agentMsgs: {}, timeline: [], timelineSeen: {}, timelineLoaded: false,
4431
+ commands: [], commandsLoaded: false, commandsLoading: false, listeners: new Set(),
4318
4432
  emit: function(){ this.listeners.forEach(function(l){ try { l(); } catch(e){} }); },
4319
4433
  subscribe: function(l){ this.listeners.add(l); var s=this; return function(){ s.listeners.delete(l); }; },
4320
4434
  set: function(p){ Object.assign(this, p); this.emit(); }
@@ -4328,6 +4442,106 @@ function fmtAgo(iso){ if(!iso) return ''; var d=new Date(iso).getTime(); if(isNa
4328
4442
  function fmtElapsed(iso){ if(!iso) return ''; var d=new Date(iso).getTime(); if(isNaN(d)) return ''; var s=Math.max(0,Math.floor((Date.now()-d)/1000)); if(s<60) return s+'s'; var m=Math.floor(s/60); if(m<60) return m+'m '+(s%60)+'s'; var h=Math.floor(m/60); if(h<24) return h+'h '+(m%60)+'m'; return Math.floor(h/24)+'d '+(h%24)+'h'; }
4329
4443
  function fmtNum(n){ n=Number(n)||0; if(n>=1e6) return (n/1e6).toFixed(1)+'M'; if(n>=1e3) return (n/1e3).toFixed(1)+'k'; return String(n); }
4330
4444
  function esc(s){ if(s==null) return ''; return String(s); }
4445
+ function cacheKey(agentId, projectId, clientId){ return (projectId||'') + '||' + (clientId||'') + '||' + agentId; }
4446
+ function findSession(snap, sessionId){ return ((snap && snap.liveSessions) || []).filter(function(s){ return s.sessionId === sessionId; })[0] || null; }
4447
+ function findAgent(session, agentId){ return session ? ((session.agents||[]).filter(function(a){ return a.id === agentId; })[0] || null) : null; }
4448
+ function clientIdForSession(snap, session){
4449
+ if(!session) return '';
4450
+ if(session.clientId) return session.clientId;
4451
+ var clients = (snap && snap.clients) || [];
4452
+ for(var i=0;i<clients.length;i++){
4453
+ var c = clients[i];
4454
+ if(c.projectId === session.projectId && c.machineId === session.machineId && c.kind === session.clientKind && (!session.pid || !c.pid || c.pid === session.pid)) return c.clientId || '';
4455
+ }
4456
+ return '';
4457
+ }
4458
+
4459
+ function timelineKey(e){
4460
+ return [e.source||'', e.projectId||'', e.clientId||'', e.sessionId||'', e.agentId||'', e.ts||'', e.role||'', e.tool||'', String(e.text||'').slice(0,80)].join('|');
4461
+ }
4462
+ function addTimelineEntries(entries){
4463
+ if(!entries || !entries.length) return;
4464
+ var changed = false;
4465
+ for(var i=0;i<entries.length;i++){
4466
+ var e = entries[i];
4467
+ var key = timelineKey(e);
4468
+ if(Store.timelineSeen[key]) continue;
4469
+ Store.timelineSeen[key] = 1;
4470
+ Store.timeline.push(e);
4471
+ changed = true;
4472
+ }
4473
+ if(!changed) return;
4474
+ Store.timeline.sort(function(a,b){ return (Date.parse(b.ts||'')||0) - (Date.parse(a.ts||'')||0); });
4475
+ if(Store.timeline.length > 1500) Store.timeline = Store.timeline.slice(0, 1500);
4476
+ Store.emit();
4477
+ }
4478
+ function timelineEntriesFromEvent(ev){
4479
+ var out = [];
4480
+ if(!ev || !ev.payload) return out;
4481
+ if(ev.type === 'session.transcript' && ev.payload.sessionId && Array.isArray(ev.payload.entries)){
4482
+ ev.payload.entries.forEach(function(entry){
4483
+ out.push({
4484
+ source: 'session', projectId: ev.projectId, clientId: ev.clientId, sessionId: ev.payload.sessionId,
4485
+ agentId: entry.agentId || 'leader', agentName: entry.agentId || 'leader',
4486
+ ts: entry.ts || ev.timestamp, role: entry.role || 'assistant', tool: entry.tool, text: entry.text || ''
4487
+ });
4488
+ });
4489
+ } else if(ev.type === 'agent.message' && ev.payload.subagentId){
4490
+ out.push({
4491
+ source: 'agent', projectId: ev.projectId, clientId: ev.clientId, sessionId: ev.sessionId || null,
4492
+ agentId: ev.payload.subagentId, agentName: ev.payload.agentName || ev.payload.subagentId,
4493
+ ts: ev.payload.ts || ev.timestamp, role: agentMsgRole(ev.payload.kind), tool: ev.payload.toolName, text: ev.payload.content || ''
4494
+ });
4495
+ } else if(ev.type === 'agent.status' && ev.payload.subagentId){
4496
+ out.push({
4497
+ source: 'agent', projectId: ev.projectId, clientId: ev.clientId, sessionId: ev.sessionId || null,
4498
+ agentId: ev.payload.subagentId, agentName: ev.payload.agentName || ev.payload.subagentId,
4499
+ ts: ev.payload.ts || ev.timestamp, role: 'system', text: (ev.payload.status||'status') + (ev.payload.summary ? (': '+ev.payload.summary) : (ev.payload.task ? (': '+ev.payload.task) : ''))
4500
+ });
4501
+ }
4502
+ return out;
4503
+ }
4504
+ function timelineAgentValue(projectId, clientId, agentId){ return [projectId||'', clientId||'', agentId||''].join('||'); }
4505
+ function parseTimelineAgentValue(value){
4506
+ if(!value || value === 'all') return null;
4507
+ var parts = String(value).split('||');
4508
+ return { projectId: parts[0] || '', clientId: parts[1] || '', agentId: parts[2] || '' };
4509
+ }
4510
+ function timelineFilterQuery(filters){
4511
+ var f = filters || {};
4512
+ var q = [];
4513
+ function add(k,v){ if(v && v !== 'all') q.push(encodeURIComponent(k) + '=' + encodeURIComponent(v)); }
4514
+ add('projectId', f.projectId);
4515
+ add('clientId', f.clientId);
4516
+ add('agentId', f.agentId);
4517
+ add('source', f.source);
4518
+ add('role', f.role);
4519
+ add('q', f.q);
4520
+ return q.length ? ('&' + q.join('&')) : '';
4521
+ }
4522
+ function loadRecentTimeline(filters, force){
4523
+ var f = typeof filters === 'string' ? { projectId: filters } : (filters || {});
4524
+ if(Store.timelineLoaded && !force) return;
4525
+ Store.timelineLoaded = true;
4526
+ var path = '/api/timeline?limit=800' + timelineFilterQuery(f);
4527
+ fetch(withTok(path))
4528
+ .then(function(r){ return r.ok ? r.json() : null; })
4529
+ .then(function(d){
4530
+ if(d && Array.isArray(d.entries)){
4531
+ addTimelineEntries(d.entries);
4532
+ return;
4533
+ }
4534
+ return fetch(withTok('/api/events?limit=800'))
4535
+ .then(function(r){ return r.ok ? r.json() : null; })
4536
+ .then(function(fallback){
4537
+ var events = fallback && Array.isArray(fallback.events) ? fallback.events : [];
4538
+ var batch = [];
4539
+ events.forEach(function(ev){ batch = batch.concat(timelineEntriesFromEvent(ev)); });
4540
+ addTimelineEntries(batch);
4541
+ });
4542
+ })
4543
+ .catch(function(){});
4544
+ }
4331
4545
 
4332
4546
  function loadTranscript(sessionId){
4333
4547
  if(!sessionId) return;
@@ -4349,17 +4563,24 @@ function applyThemeClass(t){ try { document.body.className = (t==='light'?'light
4349
4563
  function initTheme(){ var t='dark'; try { t = localStorage.getItem('hq.theme') || 'dark'; } catch(e){} Store.theme = t; applyThemeClass(t); }
4350
4564
  function toggleTheme(){ var t = (Store.theme==='light'?'dark':'light'); Store.theme = t; applyThemeClass(t); try { localStorage.setItem('hq.theme', t); } catch(e){} Store.emit(); }
4351
4565
 
4352
- function loadAgentMessages(agentId){
4566
+ function loadAgentMessages(agentId, session){
4353
4567
  if(!agentId) return;
4354
- fetch(withTok('/api/agents/'+encodeURIComponent(agentId)+'/messages?full=1'))
4568
+ var projectId = session && session.projectId ? session.projectId : '';
4569
+ var clientId = clientIdForSession(Store.snapshot, session);
4570
+ var key = cacheKey(agentId, projectId, clientId);
4571
+ var url = '/api/agents/'+encodeURIComponent(agentId)+'/messages?full=1';
4572
+ if(projectId) url += '&projectId=' + encodeURIComponent(projectId);
4573
+ if(clientId) url += '&clientId=' + encodeURIComponent(clientId);
4574
+ fetch(withTok(url))
4355
4575
  .then(function(r){ return r.ok ? r.json() : null; })
4356
- .then(function(d){ if(d && Array.isArray(d.entries)){ Store.agentMsgs[agentId] = d.entries; Store.emit(); } })
4576
+ .then(function(d){ if(d && Array.isArray(d.entries)){ Store.agentMsgs[key] = d.entries; Store.emit(); } })
4357
4577
  .catch(function(){});
4358
4578
  }
4359
4579
 
4360
4580
  function selectSession(sessionId, agentId){
4361
4581
  Store.selected = { sessionId: sessionId, agentId: agentId || null };
4362
- if(agentId && agentId !== 'leader'){ loadAgentMessages(agentId); }
4582
+ var session = findSession(Store.snapshot, sessionId);
4583
+ if(agentId && agentId !== 'leader'){ loadAgentMessages(agentId, session); }
4363
4584
  else { loadTranscript(sessionId); }
4364
4585
  Store.emit();
4365
4586
  }
@@ -4378,7 +4599,7 @@ function connectWs(){
4378
4599
  if(reconnect){
4379
4600
  var sel = Store.selected;
4380
4601
  if(sel && sel.sessionId){
4381
- if(sel.agentId && sel.agentId !== 'leader') loadAgentMessages(sel.agentId);
4602
+ if(sel.agentId && sel.agentId !== 'leader') loadAgentMessages(sel.agentId, findSession(Store.snapshot, sel.sessionId));
4382
4603
  else loadTranscript(sel.sessionId);
4383
4604
  }
4384
4605
  }
@@ -4392,7 +4613,7 @@ function connectWs(){
4392
4613
  ws.onerror = function(){ try { ws.close(); } catch(e){} };
4393
4614
  }
4394
4615
 
4395
- function agentMsgRole(kind){ return kind==='tool_use'?'tool' : kind==='error'?'error' : kind==='status'?'system' : 'assistant'; }
4616
+ function agentMsgRole(kind){ return (kind==='tool_use'||kind==='tool_result')?'tool' : kind==='error'?'error' : kind==='status'?'system' : 'assistant'; }
4396
4617
 
4397
4618
  // Append streamed entries into a transcript cache, merging a tool RESULT
4398
4619
  // (toolUseId, no args) into the matching args entry so a tool's call + result
@@ -4420,6 +4641,7 @@ function appendEntries(cache, news){
4420
4641
 
4421
4642
  function handleEvent(ev){
4422
4643
  if(!ev) return;
4644
+ addTimelineEntries(timelineEntriesFromEvent(ev));
4423
4645
  if(ev.type === 'session.transcript' && ev.payload && ev.payload.sessionId){
4424
4646
  var sid = ev.payload.sessionId;
4425
4647
  var c = Store.transcripts[sid];
@@ -4433,15 +4655,17 @@ function handleEvent(ev){
4433
4655
  // agent card id. Lets clicking a subagent show ITS own live history.
4434
4656
  if(ev.type === 'agent.message' && ev.payload && ev.payload.subagentId){
4435
4657
  var p = ev.payload;
4436
- var arr = Store.agentMsgs[p.subagentId] || (Store.agentMsgs[p.subagentId] = []);
4658
+ var k = cacheKey(p.subagentId, ev.projectId, ev.clientId);
4659
+ var arr = Store.agentMsgs[k] || (Store.agentMsgs[k] = []);
4437
4660
  arr.push({ ts: p.ts, role: agentMsgRole(p.kind), text: p.content || '', tool: p.toolName });
4438
- if(arr.length > 4000) Store.agentMsgs[p.subagentId] = arr.slice(-4000);
4661
+ if(arr.length > 4000) Store.agentMsgs[k] = arr.slice(-4000);
4439
4662
  Store.emit();
4440
4663
  return;
4441
4664
  }
4442
4665
  if(ev.type === 'agent.status' && ev.payload && ev.payload.subagentId){
4443
4666
  var sp = ev.payload;
4444
- var a2 = Store.agentMsgs[sp.subagentId] || (Store.agentMsgs[sp.subagentId] = []);
4667
+ var k2 = cacheKey(sp.subagentId, ev.projectId, ev.clientId);
4668
+ var a2 = Store.agentMsgs[k2] || (Store.agentMsgs[k2] = []);
4445
4669
  a2.push({ ts: sp.ts, role: 'system', text: '\u2014 ' + (sp.status||'') + (sp.summary ? (': '+sp.summary) : (sp.task ? (': '+sp.task) : '')) });
4446
4670
  Store.emit();
4447
4671
  return;
@@ -4456,6 +4680,101 @@ function machineKey(hostname, machineId){
4456
4680
  return hn ? ('host:' + hn.toLowerCase()) : ('mid:' + (machineId || 'local'));
4457
4681
  }
4458
4682
 
4683
+ function postHqCommand(clientId, type, payload){
4684
+ return fetch(withTok('/api/command'), {
4685
+ method: 'POST',
4686
+ headers: { 'Content-Type': 'application/json' },
4687
+ body: JSON.stringify({ clientId: clientId, type: type, payload: payload })
4688
+ }).then(function(r){
4689
+ return r.json().catch(function(){ return {}; }).then(function(body){
4690
+ if(!r.ok){ throw new Error(body && body.error ? body.error : ('HTTP ' + r.status)); }
4691
+ return body;
4692
+ });
4693
+ });
4694
+ }
4695
+ /* Direct mailbox write \u2014 used when the target has no live command-capable
4696
+ client. The prompt lands in the project mailbox regardless of whether any
4697
+ agent loop is running; the server resolves the projectRoot from sessionId/
4698
+ projectId, so no filesystem path leaves the browser. */
4699
+ function postHqMailboxSend(target, type, opts){
4700
+ return fetch(withTok('/api/mailbox-send'), {
4701
+ method: 'POST',
4702
+ headers: { 'Content-Type': 'application/json' },
4703
+ body: JSON.stringify({
4704
+ sessionId: target.sessionId, projectId: target.projectId,
4705
+ type: type, to: target.to, subject: opts.subject, body: opts.body, priority: opts.priority
4706
+ })
4707
+ }).then(function(r){
4708
+ return r.json().catch(function(){ return {}; }).then(function(body){
4709
+ if(!r.ok){ throw new Error(body && body.error ? body.error : ('HTTP ' + r.status)); }
4710
+ return body;
4711
+ });
4712
+ });
4713
+ }
4714
+ function loadCommands(force){
4715
+ if(Store.commandsLoading) return;
4716
+ if(Store.commandsLoaded && !force) return;
4717
+ Store.commandsLoading = true;
4718
+ fetch(withTok('/api/commands?limit=80'))
4719
+ .then(function(r){ return r.ok ? r.json() : null; })
4720
+ .then(function(d){
4721
+ var commands = d && Array.isArray(d.commands) ? d.commands.slice() : [];
4722
+ commands.sort(function(a,b){ return (Date.parse(b.enqueuedAt||'')||0) - (Date.parse(a.enqueuedAt||'')||0); });
4723
+ Store.commands = commands;
4724
+ Store.commandsLoaded = true;
4725
+ Store.commandsLoading = false;
4726
+ Store.emit();
4727
+ })
4728
+ .catch(function(){ Store.commandsLoading = false; Store.commandsLoaded = true; Store.emit(); });
4729
+ }
4730
+
4731
+ function buildCommandTargets(snap){
4732
+ var targets = [];
4733
+ var seenProjects = {};
4734
+ ((snap && snap.liveSessions) || []).forEach(function(s){
4735
+ // A live client means the fast control-plane path (/api/command) is
4736
+ // available. Without one, targets still work via the direct mailbox
4737
+ // route (/api/mailbox-send) \u2014 the send simply lands in the project
4738
+ // mailbox and is picked up whenever an agent next runs. direct=true
4739
+ // marks those; the offline suffix hints at the fallback path.
4740
+ var clientId = clientIdForSession(snap, s);
4741
+ var direct = !clientId;
4742
+ var projName = s.projectName || s.projectId;
4743
+ var offlineSuffix = direct ? ' \xB7 (offline \u2192 mailbox)' : '';
4744
+ var projectKey = s.projectId + '|' + (clientId || 'nocli');
4745
+ if(!seenProjects[projectKey]){
4746
+ seenProjects[projectKey] = 1;
4747
+ targets.push({
4748
+ value: 'project|' + projectKey, kind: 'broadcast', clientId: clientId || null, direct: direct,
4749
+ to: 'all', sessionId: s.sessionId, projectId: s.projectId,
4750
+ label: 'Broadcast project \xB7 ' + projName + offlineSuffix
4751
+ });
4752
+ }
4753
+ targets.push({
4754
+ value: s.sessionId + '|leader', kind: 'agent', clientId: clientId || null, direct: direct,
4755
+ to: 'leader', sessionId: s.sessionId, projectId: s.projectId, agentId: 'leader',
4756
+ label: projName + ' \xB7 ' + (s.clientKind||'cli').toUpperCase() + ' \xB7 leader' + offlineSuffix
4757
+ });
4758
+ (s.agents||[]).forEach(function(a){
4759
+ if(a.id === 'leader') return;
4760
+ targets.push({
4761
+ value: s.sessionId + '|' + a.id, kind: 'agent', clientId: clientId || null, direct: direct,
4762
+ to: a.id, sessionId: s.sessionId, projectId: s.projectId, agentId: a.id,
4763
+ label: projName + ' \xB7 ' + (a.name || a.id) + offlineSuffix
4764
+ });
4765
+ });
4766
+ });
4767
+ return targets;
4768
+ }
4769
+
4770
+ function selectedTargetValue(snap){
4771
+ var sel = Store.selected;
4772
+ if(!sel || !sel.sessionId) return '';
4773
+ var session = findSession(snap, sel.sessionId);
4774
+ if(!session) return '';
4775
+ return sel.agentId ? (sel.sessionId + '|' + sel.agentId) : (sel.sessionId + '|leader');
4776
+ }
4777
+
4459
4778
  /* fleet tree (shared) */
4460
4779
  function buildTree(snap){
4461
4780
  var sessions = (snap && snap.liveSessions) || [];
@@ -4583,11 +4902,13 @@ function projNode(id, p, machine){
4583
4902
  }
4584
4903
  function termNode(id, t, sel){
4585
4904
  var synthetic = !!t.synthetic;
4905
+ var agentCount = t.agentCount || (t.agents ? t.agents.length : 0);
4586
4906
  return fleetNode(id, 'terminal', {
4587
4907
  termKind: t.clientKind, status: t.status, icon: kindIcon(t.clientKind),
4588
4908
  label: (t.clientKind||'cli').toUpperCase() + ' \xB7 ' + shortId(t.sessionId),
4589
4909
  sub: t.status + (t.pid ? (' \xB7 pid '+t.pid) : '') + (synthetic ? ' \xB7 waiting for session telemetry' : ''),
4590
- chips: synthetic ? ['connected', '0 agents'] : [ (t.agentCount||(t.agents?t.agents.length:0))+' agents' ],
4910
+ agentCount: agentCount,
4911
+ chips: synthetic ? ['connected', '0 agents'] : [ agentCount+' agents' ],
4591
4912
  clickable: !synthetic, selected: !synthetic && !!(sel && sel.sessionId === t.sessionId && !sel.agentId),
4592
4913
  onClick: synthetic ? undefined : function(){ selectSession(t.sessionId); }
4593
4914
  });
@@ -4603,6 +4924,7 @@ function agentNode(id, a, t, sel){
4603
4924
  chips.push((a.iterations||0)+' it');
4604
4925
  if(typeof a.costUsd === 'number' && a.costUsd > 0) chips.push('$'+a.costUsd.toFixed(2));
4605
4926
  return fleetNode(id, 'agent', {
4927
+ parentTerminalId: 'terminal:' + t.sessionId,
4606
4928
  status: a.status, label: a.name || a.id,
4607
4929
  sub: a.status + (a.partialText ? (' \xB7 '+String(a.partialText).slice(0,44)) : ''),
4608
4930
  chips: chips, clickable: true,
@@ -4613,21 +4935,94 @@ function agentNode(id, a, t, sel){
4613
4935
  function kindIcon(k){ return k==='tui'?'\u{1F5B3}':k==='webui'?'\u{1F310}':k==='repl'?'\u2328\uFE0F':'\u25B7'; }
4614
4936
  function miniColor(n){ var k=n.data&&n.data.kind; return k==='machine'?'#a371f7':k==='project'?'#58a6ff':k==='agent'?'#3fb950':'#39d0d8'; }
4615
4937
 
4616
- // Node footprints (width \xD7 height) for dagre sizing.
4617
- var NODE_SIZE = { machine: [220, 78], project: [220, 78], terminal: [220, 70], agent: [230, 92] };
4618
- // Auto-layout the logical nodes into a clean tree using dagre.
4938
+ // Node footprints (width \xD7 height) for graph sizing. Agents are compact because
4939
+ // subagent fleets can be large; they are clustered around their terminal rather
4940
+ // than laid out as a long tree rank.
4941
+ var NODE_SIZE = { machine: [220, 78], project: [220, 78], terminal: [220, 70], agent: [196, 78] };
4942
+ function agentGrid(count, dir){
4943
+ if(!count) return { cols: 0, rows: 0, width: 0, height: 0 };
4944
+ var cols = dir === 'TB'
4945
+ ? Math.min(4, Math.max(1, Math.ceil(Math.sqrt(count))))
4946
+ : (count > 10 ? 4 : (count > 3 ? 2 : 1));
4947
+ var rows = Math.ceil(count / cols);
4948
+ var s = NODE_SIZE.agent;
4949
+ return { cols: cols, rows: rows, width: cols*s[0] + Math.max(0, cols-1)*22, height: rows*s[1] + Math.max(0, rows-1)*18 };
4950
+ }
4951
+ function actualNodeSize(n){ return NODE_SIZE[n.data.kind] || [210, 64]; }
4952
+ function graphNodeSize(n, dir){
4953
+ var base = actualNodeSize(n);
4954
+ if(n.data.kind !== 'terminal') return base;
4955
+ var count = n.data.agentCount || 0;
4956
+ if(!count) return base;
4957
+ var grid = agentGrid(count, dir);
4958
+ if(dir === 'TB') return [Math.max(base[0], grid.width), base[1] + 58 + grid.height];
4959
+ return [base[0] + 70 + grid.width, Math.max(base[1], grid.height)];
4960
+ }
4961
+ function terminalActualPosition(center, n, graphSize, actualSize, dir){
4962
+ if(n.data.kind !== 'terminal' || !(n.data.agentCount > 0)){
4963
+ return { x: center.x - actualSize[0]/2, y: center.y - actualSize[1]/2 };
4964
+ }
4965
+ if(dir === 'TB'){
4966
+ return { x: center.x - actualSize[0]/2, y: center.y - graphSize[1]/2 };
4967
+ }
4968
+ return { x: center.x - graphSize[0]/2, y: center.y - actualSize[1]/2 };
4969
+ }
4970
+ function positionAgentsAroundTerminals(nodes, positioned, dir){
4971
+ var byTerm = {};
4972
+ nodes.forEach(function(n){
4973
+ if(n.data.kind === 'agent' && n.data.parentTerminalId){
4974
+ (byTerm[n.data.parentTerminalId] || (byTerm[n.data.parentTerminalId] = [])).push(n);
4975
+ }
4976
+ });
4977
+ Object.keys(byTerm).forEach(function(termId){
4978
+ var term = positioned[termId]; if(!term) return;
4979
+ var agents = byTerm[termId];
4980
+ var grid = agentGrid(agents.length, dir);
4981
+ var termSize = actualNodeSize(term);
4982
+ var agentSize = NODE_SIZE.agent;
4983
+ var termCenter = { x: term.position.x + termSize[0]/2, y: term.position.y + termSize[1]/2 };
4984
+ var startX, startY;
4985
+ if(dir === 'TB'){
4986
+ startX = termCenter.x - grid.width/2;
4987
+ startY = term.position.y + termSize[1] + 58;
4988
+ } else {
4989
+ startX = term.position.x + termSize[0] + 70;
4990
+ startY = termCenter.y - grid.height/2;
4991
+ }
4992
+ agents.forEach(function(agent, index){
4993
+ var col = index % grid.cols;
4994
+ var row = Math.floor(index / grid.cols);
4995
+ positioned[agent.id] = Object.assign({}, agent, {
4996
+ position: {
4997
+ x: startX + col * (agentSize[0] + 22),
4998
+ y: startY + row * (agentSize[1] + 18)
4999
+ }
5000
+ });
5001
+ });
5002
+ });
5003
+ }
5004
+ // Auto-layout the logical nodes into a clean spine, then cluster agents around
5005
+ // the terminal they belong to so large fleets stay readable.
4619
5006
  function layoutTree(nodes, edges, dir, dagre){
4620
5007
  if(!dagre || !nodes.length) return nodes;
4621
5008
  var g = new dagre.graphlib.Graph();
4622
5009
  g.setGraph({ rankdir: dir || 'LR', nodesep: 26, ranksep: 90, marginx: 30, marginy: 30, ranker: 'tight-tree' });
4623
5010
  g.setDefaultEdgeLabel(function(){ return {}; });
4624
- nodes.forEach(function(n){ var s = NODE_SIZE[n.data.kind] || [210, 64]; g.setNode(n.id, { width: s[0], height: s[1] }); });
4625
- edges.forEach(function(e){ if(g.hasNode(e.source) && g.hasNode(e.target)) g.setEdge(e.source, e.target); });
5011
+ var spine = nodes.filter(function(n){ return n.data.kind !== 'agent'; });
5012
+ spine.forEach(function(n){ var s = graphNodeSize(n, dir); g.setNode(n.id, { width: s[0], height: s[1] }); });
5013
+ edges.forEach(function(e){
5014
+ if(g.hasNode(e.source) && g.hasNode(e.target)) g.setEdge(e.source, e.target);
5015
+ });
4626
5016
  dagre.layout(g);
4627
- return nodes.map(function(n){
4628
- var p = g.node(n.id); if(!p) return n;
4629
- return Object.assign({}, n, { position: { x: p.x - p.width/2, y: p.y - p.height/2 } });
5017
+ var positioned = {};
5018
+ spine.forEach(function(n){
5019
+ var p = g.node(n.id); if(!p){ positioned[n.id] = n; return; }
5020
+ var actual = actualNodeSize(n);
5021
+ var graph = graphNodeSize(n, dir);
5022
+ positioned[n.id] = Object.assign({}, n, { position: terminalActualPosition(p, n, graph, actual, dir) });
4630
5023
  });
5024
+ positionAgentsAroundTerminals(nodes, positioned, dir);
5025
+ return nodes.map(function(n){ return positioned[n.id] || n; });
4631
5026
  }
4632
5027
 
4633
5028
  /* React app (preferred) */
@@ -4715,24 +5110,505 @@ async function boot(){
4715
5110
  h('pre', { className: preClass || null }, content)
4716
5111
  );
4717
5112
  }
5113
+ function foldContent(key, summary, child){
5114
+ return h('details', { key: key, className: 'bub-fold' },
5115
+ h('summary', null, summary),
5116
+ child
5117
+ );
5118
+ }
5119
+ function roleIcon(role){
5120
+ if(role === 'user') return '\u{1F464}';
5121
+ if(role === 'tool') return '\u2318';
5122
+ if(role === 'error') return '!';
5123
+ if(role === 'system') return 'i';
5124
+ return '\u{1F916}';
5125
+ }
5126
+ function roleLabel(role){
5127
+ if(role === 'user') return 'You';
5128
+ if(role === 'tool') return 'Tool';
5129
+ if(role === 'error') return 'Error';
5130
+ if(role === 'system') return 'System';
5131
+ return 'Assistant';
5132
+ }
5133
+ function countLines(text){
5134
+ if(!text) return 0;
5135
+ return String(text).split('\\n').length;
5136
+ }
5137
+ function toolSummary(e){
5138
+ var parts = [];
5139
+ if(e.toolInput) parts.push('input ' + e.toolInput.length + ' chars');
5140
+ if(e.text) parts.push(countLines(e.text) + ' lines');
5141
+ if(e.durationMs!=null) parts.push(e.durationMs + 'ms');
5142
+ return parts.join(' \xB7 ');
5143
+ }
5144
+ function detectToolShape(tool, result){
5145
+ var text = String(result || '');
5146
+ var trimmed = text.trim();
5147
+ function numberedLine(line){
5148
+ var t = String(line || '').trim();
5149
+ var arrow = t.indexOf('\u2192');
5150
+ if(arrow <= 0) return false;
5151
+ var n = t.slice(0, arrow).trim();
5152
+ if(!n) return false;
5153
+ for(var i=0;i<n.length;i++){ var c = n.charCodeAt(i); if(c < 48 || c > 57) return false; }
5154
+ return true;
5155
+ }
5156
+ if(text.split('\\n').slice(0, 8).some(numberedLine)) return { kind: 'numbered', text: text };
5157
+ if((trimmed.indexOf('{') === 0 && trimmed.lastIndexOf('}') === trimmed.length - 1) || (trimmed.indexOf('[') === 0 && trimmed.lastIndexOf(']') === trimmed.length - 1)){
5158
+ try { return { kind: 'json', text: JSON.stringify(JSON.parse(trimmed), null, 2) }; } catch(_e){}
5159
+ }
5160
+ var toolLower = String(tool || '').toLowerCase();
5161
+ var isBash = ['bash','shell','exec','run','tsc','pnpm','npm','yarn'].some(function(prefix){ return toolLower.indexOf(prefix) === 0; });
5162
+ var lines = text.split('\\n');
5163
+ var last = lines.length ? String(lines[lines.length - 1] || '').trim().toLowerCase() : '';
5164
+ var exitCode = null;
5165
+ if(last.indexOf('exit') === 0 || last.indexOf('[exit') === 0){
5166
+ var digits = '';
5167
+ for(var j=0;j<last.length;j++){ var ch = last.charCodeAt(j); if(ch >= 48 && ch <= 57) digits += last[j]; }
5168
+ if(digits) exitCode = Number(digits);
5169
+ }
5170
+ if(isBash || exitCode !== null){
5171
+ return { kind: 'bash', text: exitCode !== null ? lines.slice(0, -1).join('\\n').trimEnd() : text, exitCode: exitCode };
5172
+ }
5173
+ return { kind: 'plain', text: text };
5174
+ }
5175
+ function renderLineView(text, numbered){
5176
+ var lines = String(text || '').split('\\n');
5177
+ if(!numbered) return h('pre', null, text || '');
5178
+ return h('div', { className: 'line-view' },
5179
+ h('pre', { className: 'line-gutter', 'aria-hidden': true }, lines.map(function(_l, i){ return String(i + 1); }).join('\\n')),
5180
+ h('pre', null, text || '')
5181
+ );
5182
+ }
5183
+ function renderToolOutput(e){
5184
+ var shape = detectToolShape(e.tool, e.text);
5185
+ var lineCount = countLines(shape.text);
5186
+ var cls = 'tool-output ' + shape.kind + (e.isError || e.role === 'error' ? ' error' : '');
5187
+ var footer = null;
5188
+ if(shape.kind === 'bash' && (shape.exitCode !== null || e.durationMs != null)){
5189
+ footer = h('div', { className: 'tool-output-footer ' + (shape.exitCode && shape.exitCode !== 0 ? 'bad' : '') },
5190
+ shape.exitCode !== null ? h('span', null, 'exit code ', h('strong', null, String(shape.exitCode))) : null,
5191
+ e.durationMs != null ? h('span', null, e.durationMs + 'ms') : null
5192
+ );
5193
+ }
5194
+ return h('div', { className: cls },
5195
+ h('div', { className: 'tool-output-head' },
5196
+ h('span', { className: 'tool-output-kind' }, shape.kind === 'plain' ? 'output' : shape.kind),
5197
+ h('span', { className: 'tool-output-meta' }, lineCount + ' lines')
5198
+ ),
5199
+ renderLineView(shape.text, shape.kind === 'numbered' || shape.kind === 'bash'),
5200
+ footer
5201
+ );
5202
+ }
5203
+ function renderInlineText(text, keyBase){
5204
+ var s = String(text || '');
5205
+ var bt = String.fromCharCode(96);
5206
+ var out = [];
5207
+ var pos = 0, n = 0;
5208
+ while(pos < s.length){
5209
+ var start = s.indexOf(bt, pos);
5210
+ if(start < 0){ out.push(s.slice(pos)); break; }
5211
+ var end = s.indexOf(bt, start + 1);
5212
+ if(end < 0){ out.push(s.slice(pos)); break; }
5213
+ if(start > pos) out.push(s.slice(pos, start));
5214
+ out.push(h('code', { key: keyBase + '-ic-' + n++, className: 'md-inline' }, s.slice(start + 1, end)));
5215
+ pos = end + 1;
5216
+ }
5217
+ return out.length ? out : s;
5218
+ }
5219
+ function renderMessageText(text, role){
5220
+ var raw = String(text || '');
5221
+ if(!raw) return h('div', { className: 'msg-md empty' }, '');
5222
+ var fence = String.fromCharCode(96) + String.fromCharCode(96) + String.fromCharCode(96);
5223
+ var lines = raw.split('\\n');
5224
+ var blocks = [];
5225
+ var para = [];
5226
+ var code = [];
5227
+ var lang = '';
5228
+ var inCode = false;
5229
+ function flushPara(){
5230
+ if(!para.length) return;
5231
+ var content = para.join(' ');
5232
+ blocks.push(h('p', { key: 'p-' + blocks.length }, renderInlineText(content, 'p-' + blocks.length)));
5233
+ para = [];
5234
+ }
5235
+ function flushCode(){
5236
+ blocks.push(h('div', { key: 'c-' + blocks.length, className: 'md-codewrap' },
5237
+ h('div', { className: 'md-codelabel' }, lang || 'code'),
5238
+ h('pre', { className: 'md-code' }, code.join('\\n'))
5239
+ ));
5240
+ code = [];
5241
+ lang = '';
5242
+ }
5243
+ for(var i=0;i<lines.length;i++){
5244
+ var line = lines[i];
5245
+ if(line.slice(0,3) === fence){
5246
+ if(inCode){ flushCode(); inCode = false; }
5247
+ else { flushPara(); inCode = true; lang = line.slice(3).trim(); code = []; }
5248
+ continue;
5249
+ }
5250
+ if(inCode){ code.push(line); continue; }
5251
+ if(!line.trim()){ flushPara(); continue; }
5252
+ if(line.indexOf('- ') === 0 || line.indexOf('* ') === 0){
5253
+ flushPara();
5254
+ blocks.push(h('div', { key: 'b-' + blocks.length, className: 'md-bullet' },
5255
+ h('span', { className: 'md-bullet-dot' }, '\u2022'),
5256
+ h('span', null, renderInlineText(line.slice(2).trim(), 'b-' + blocks.length))
5257
+ ));
5258
+ continue;
5259
+ }
5260
+ if(line.indexOf('# ') === 0 || line.indexOf('## ') === 0 || line.indexOf('### ') === 0){
5261
+ flushPara();
5262
+ blocks.push(h('div', { key: 'h-' + blocks.length, className: 'md-heading' }, line.replace(/^#+\\s*/, '')));
5263
+ continue;
5264
+ }
5265
+ para.push(line);
5266
+ }
5267
+ if(inCode) flushCode();
5268
+ flushPara();
5269
+ return h('div', { className: (role === 'assistant' ? 'assistant-body ' : '') + 'msg-md' }, blocks);
5270
+ }
4718
5271
  function Bubble(p){
4719
5272
  var e = p.e;
4720
5273
  var isToolish = e.role === 'tool' || e.role === 'error';
4721
5274
  var bodyEls = [];
4722
5275
  if(isToolish){
4723
- // Tool args + result are collapsed by default \u2014 click to expand.
4724
- if(e.toolInput){ bodyEls.push(fold('a', '\u2192 args \xB7 ' + e.toolInput.length + ' chars', e.toolInput, 'bub-argpre')); }
4725
- if(e.text){ bodyEls.push(fold('o', (e.isError?'\u26A0 error':'\u2190 result') + ' \xB7 ' + e.text.length + ' chars' + (e.durationMs!=null?(' \xB7 '+e.durationMs+'ms'):''), e.text)); }
4726
- if(!e.toolInput && !e.text){ bodyEls.push(h('div', { key:'n', className:'bub-sublabel' }, '(no output)')); }
5276
+ bodyEls.push(h('div', { key: 'th', className: 'tool-head' },
5277
+ h('span', { className: 'tool-name' }, e.tool || 'tool'),
5278
+ h('span', { className: 'tool-duration' }, toolSummary(e)),
5279
+ h('span', { className: 'tool-status ' + (e.isError || e.role === 'error' ? 'error' : '') }, e.isError || e.role === 'error' ? 'failed' : 'done')
5280
+ ));
5281
+ if(e.toolInput){ bodyEls.push(fold('a', 'Input \xB7 ' + e.toolInput.length + ' chars', e.toolInput, 'bub-argpre')); }
5282
+ if(e.text){ bodyEls.push(foldContent('o', (e.isError||e.role==='error'?'Error output':'Output') + ' \xB7 ' + countLines(e.text) + ' lines', renderToolOutput(e))); }
5283
+ if(!e.toolInput && !e.text){ bodyEls.push(h('div', { key:'n', className:'bub-sublabel' }, 'no output')); }
4727
5284
  } else {
4728
- bodyEls.push(h('div', { key:'t', className: 'txt' }, e.text || ''));
5285
+ bodyEls.push(h('div', { key:'t', className: 'txt' }, renderMessageText(e.text || '', e.role)));
4729
5286
  }
4730
5287
  return h('div', { className: 'bub ' + e.role },
4731
- h('div', { className: 'bub-meta' },
4732
- h('span', { className: 'bub-role' }, e.role + (e.tool ? (' \xB7 '+e.tool) : '')),
4733
- h('span', null, fmtTime(e.ts))
5288
+ h('div', { className: 'bub-avatar' }, roleIcon(e.role)),
5289
+ h('div', { className: 'bub-content' },
5290
+ h('div', { className: 'bub-head' },
5291
+ h('span', { className: 'bub-role' }, isToolish && e.tool ? e.tool : roleLabel(e.role)),
5292
+ h('span', null, fmtTime(e.ts))
5293
+ ),
5294
+ h('div', { className: 'bub-card' },
5295
+ isToolish ? h('div', { className: 'tool-card' }, bodyEls) : bodyEls
5296
+ )
4734
5297
  ),
4735
- bodyEls
5298
+ );
5299
+ }
5300
+ function isToolEntry(e){ return e && (e.role === 'tool' || e.role === 'error'); }
5301
+ function ToolGroup(p){
5302
+ var tools = p.tools || [];
5303
+ var names = [];
5304
+ var seen = {};
5305
+ var errored = false;
5306
+ var totalMs = 0;
5307
+ tools.forEach(function(t){
5308
+ if(t.tool && !seen[t.tool]){ seen[t.tool] = 1; names.push(t.tool); }
5309
+ if(t.isError || t.role === 'error') errored = true;
5310
+ if(typeof t.durationMs === 'number') totalMs += t.durationMs;
5311
+ });
5312
+ var preview = names.slice(0, 4).join(', ') + (names.length > 4 ? ' +' + (names.length - 4) : '');
5313
+ var meta = preview || tools.map(function(t){ return t.role; }).join(', ');
5314
+ if(totalMs > 0) meta += ' \xB7 ' + totalMs + 'ms';
5315
+ return h('details', { className: 'tool-group', open: p.defaultOpen ? true : undefined },
5316
+ h('summary', null,
5317
+ h('span', { className: 'tool-group-title' }, tools.length + ' tool calls'),
5318
+ h('span', { className: 'tool-group-meta' }, meta),
5319
+ h('span', { className: 'tool-group-status ' + (errored ? 'error' : '') }, errored ? 'failed' : 'done')
5320
+ ),
5321
+ h('div', { className: 'tool-group-body' },
5322
+ tools.map(function(t, i){ return h(Bubble, { key: i, e: t }); })
5323
+ )
5324
+ );
5325
+ }
5326
+
5327
+ function PromptDock(p){
5328
+ var targets = buildCommandTargets(p.snap);
5329
+ var preferred = selectedTargetValue(p.snap) || (targets[0] && targets[0].value) || '';
5330
+ var tvState = React.useState(preferred); var targetValue = tvState[0], setTargetValue = tvState[1];
5331
+ var textState = React.useState(''); var text = textState[0], setText = textState[1];
5332
+ var sendState = React.useState(false); var sending = sendState[0], setSending = sendState[1];
5333
+ var stState = React.useState({ text: '', cls: '' }); var status = stState[0], setStatus = stState[1];
5334
+ var typeState = React.useState('steer'); var sendType = typeState[0], setSendType = typeState[1];
5335
+ React.useEffect(function(){ if(preferred) setTargetValue(preferred); }, [preferred]);
5336
+ var target = targets.filter(function(t){ return t.value === targetValue; })[0] || targets[0] || null;
5337
+ // Broadcast targets always fan out project-wide; agent targets use the
5338
+ // selected send-type (steer / btw / queue). All types are written to the
5339
+ // project mailbox by the client dispatcher, so they land even when no
5340
+ // agent loop is actively running. HQ prompts are raw and bypass prompt
5341
+ // refinement \u2014 a steer/btw/queue is already a directive, not user input.
5342
+ var effectiveType = target && target.kind === 'broadcast' ? 'broadcast' : sendType;
5343
+ // Derive the subject from the send-type so the two never drift (a steer
5344
+ // must not carry a "queue"-flavored subject). Single source of truth,
5345
+ // computed alongside effectiveType.
5346
+ var effectiveSubject =
5347
+ effectiveType === 'steer' ? 'HQ steer' :
5348
+ effectiveType === 'btw' ? 'HQ note' :
5349
+ effectiveType === 'broadcast' ? 'HQ broadcast' :
5350
+ 'HQ prompt'; // queue (and any future fallthrough)
5351
+ function submit(){
5352
+ if(!target || sending) return;
5353
+ var body = text.trim();
5354
+ if(!body){ setStatus({ text: 'Write a prompt or note first.', cls: 'err' }); return; }
5355
+ setSending(true);
5356
+ // When the target has a live command-capable client, go through the
5357
+ // control plane (/api/command). Otherwise fall back to a direct mailbox
5358
+ // write (/api/mailbox-send) so the prompt still goes out immediately \u2014
5359
+ // it lands in the project mailbox and the next agent to run picks it up.
5360
+ var useDirect = target.direct || !target.clientId;
5361
+ setStatus({ text: useDirect ? 'Delivering to mailbox\u2026' : 'Queueing command\u2026', cls: '' });
5362
+ var sendPromise;
5363
+ if(useDirect){
5364
+ sendPromise = postHqMailboxSend(target, effectiveType, { subject: effectiveSubject, body: body, priority: 'high' });
5365
+ } else {
5366
+ var payload = target.kind === 'broadcast'
5367
+ ? { subject: effectiveSubject, body: body, priority: 'high' }
5368
+ : { to: target.to, subject: effectiveSubject, body: body, priority: 'high' };
5369
+ sendPromise = postHqCommand(target.clientId, effectiveType, payload);
5370
+ }
5371
+ sendPromise
5372
+ .then(function(res){
5373
+ setText('');
5374
+ var okMsg = useDirect
5375
+ ? 'Delivered to mailbox (' + (res.messageId ? shortId(res.messageId) : 'ok') + ') for ' + target.label
5376
+ : 'Queued ' + (res.commandId ? shortId(res.commandId) : 'command') + ' for ' + target.label;
5377
+ setStatus({ text: okMsg, cls: 'ok' });
5378
+ loadCommands(true);
5379
+ setTimeout(function(){ loadCommands(true); }, 2500);
5380
+ })
5381
+ .catch(function(err){ setStatus({ text: err && err.message ? err.message : String(err), cls: 'err' }); })
5382
+ .finally(function(){ setSending(false); });
5383
+ }
5384
+ var isBroadcast = target && target.kind === 'broadcast';
5385
+ var typeOptions = [
5386
+ { value: 'steer', label: 'Steer' },
5387
+ { value: 'btw', label: 'BTW' },
5388
+ { value: 'queue', label: 'Queue' }
5389
+ ];
5390
+ var sendLabel = isBroadcast ? 'Broadcast'
5391
+ : effectiveType === 'btw' ? 'Send BTW'
5392
+ : effectiveType === 'queue' ? 'Queue'
5393
+ : 'Send steer';
5394
+ return h('div', { className: 'cmd-dock' },
5395
+ h('div', { className: 'cmd-row' },
5396
+ h('select', { className: 'cmd-target', value: target ? target.value : '', disabled: !targets.length || sending, onChange: function(e){ setTargetValue(e.target.value); } },
5397
+ targets.length ? targets.map(function(t){ return h('option', { key: t.value, value: t.value }, t.label); }) : h('option', { value: '' }, 'No command-capable client')
5398
+ ),
5399
+ h('select', {
5400
+ className: 'cmd-type',
5401
+ value: isBroadcast ? 'broadcast' : sendType,
5402
+ disabled: !target || sending || isBroadcast,
5403
+ title: isBroadcast ? 'Broadcast fans out to the whole project' : 'How the prompt reaches the agent',
5404
+ onChange: function(e){ setSendType(e.target.value); }
5405
+ },
5406
+ isBroadcast
5407
+ ? h('option', { value: 'broadcast' }, 'Broadcast')
5408
+ : typeOptions.map(function(o){ return h('option', { key: o.value, value: o.value }, o.label); })
5409
+ ),
5410
+ h('textarea', {
5411
+ className: 'cmd-text',
5412
+ value: text,
5413
+ disabled: !target || sending,
5414
+ placeholder: isBroadcast ? 'Broadcast a prompt to this project\u2026' : 'Send a prompt to the selected agent\u2026',
5415
+ onChange: function(e){ setText(e.target.value); },
5416
+ onKeyDown: function(e){ if((e.ctrlKey || e.metaKey) && e.key === 'Enter'){ e.preventDefault(); submit(); } }
5417
+ }),
5418
+ h('button', { className: 'cmd-send', disabled: !target || sending || !text.trim(), onClick: submit }, sending ? 'Sending\u2026' : sendLabel)
5419
+ ),
5420
+ h('div', { className: 'cmd-status ' + (status.cls||'') }, status.text || 'Ctrl+Enter sends raw (no refine) via the project mailbox. Steer = change course now \xB7 BTW = FYI context \xB7 Queue = waits its turn.')
5421
+ );
5422
+ }
5423
+
5424
+ function clientLabelForCommand(snap, clientId){
5425
+ var clients = (snap && snap.clients) || [];
5426
+ for(var i=0;i<clients.length;i++){
5427
+ var c = clients[i];
5428
+ if(c.clientId === clientId){
5429
+ var project = projectNameFor(snap, c.projectId);
5430
+ return project + ' \xB7 ' + String(c.kind || 'cli').toUpperCase();
5431
+ }
5432
+ }
5433
+ return shortId(clientId);
5434
+ }
5435
+ function commandStatus(c){
5436
+ return c.ackStatus || c.status || 'queued';
5437
+ }
5438
+ function CommandHistory(p){
5439
+ React.useEffect(function(){
5440
+ loadCommands(false);
5441
+ var id = setInterval(function(){ loadCommands(true); }, 4000);
5442
+ return function(){ clearInterval(id); };
5443
+ }, []);
5444
+ var rows = (Store.commands || []).slice(0, 8);
5445
+ return h('div', { className: 'cmd-history' },
5446
+ h('div', { className: 'cmd-hhead' },
5447
+ h('div', { className: 'cmd-htitle' }, 'Control Queue'),
5448
+ h('div', { className: 'cmd-hnote' }, rows.length ? 'recent HQ commands and acknowledgements' : (Store.commandsLoading ? 'loading command audit' : 'no commands queued yet')),
5449
+ h('button', { className: 'cmd-hrefresh', onClick: function(){ loadCommands(true); } }, 'Refresh')
5450
+ ),
5451
+ rows.length ? h('div', { className: 'cmd-hlist' },
5452
+ rows.map(function(c){
5453
+ var st = commandStatus(c);
5454
+ return h('div', { key: c.commandId, className: 'cmd-hrow' },
5455
+ h('div', { className: 'cmd-htype' }, c.type || 'command'),
5456
+ h('div', { className: 'cmd-hstatus ' + st }, st),
5457
+ h('div', { className: 'cmd-hclient' }, clientLabelForCommand(p.snap, c.clientId)),
5458
+ h('div', { className: 'cmd-hmsg', title: c.ackMessage || c.commandId }, c.ackMessage || fmtAgo(c.ackedAt || c.enqueuedAt))
5459
+ );
5460
+ })
5461
+ ) : h('div', { className: 'side-empty' }, Store.commandsLoading ? 'Loading command history\u2026' : 'No HQ control commands have been queued in this server process.')
5462
+ );
5463
+ }
5464
+
5465
+ function projectNameFor(snap, projectId){
5466
+ var p = ((snap && snap.projects) || []).filter(function(x){ return x.projectId === projectId; })[0];
5467
+ if(p) return p.projectName || p.projectId;
5468
+ var s = ((snap && snap.liveSessions) || []).filter(function(x){ return x.projectId === projectId; })[0];
5469
+ return s ? (s.projectName || s.projectId) : (projectId || 'unknown');
5470
+ }
5471
+ function sessionForTimelineEntry(snap, e){
5472
+ if(e.sessionId){
5473
+ var exact = findSession(snap, e.sessionId);
5474
+ if(exact) return exact;
5475
+ }
5476
+ var sessions = (snap && snap.liveSessions) || [];
5477
+ for(var i=0;i<sessions.length;i++){
5478
+ var s = sessions[i];
5479
+ if(s.projectId === e.projectId && clientIdForSession(snap, s) === e.clientId){
5480
+ if(!e.agentId || (s.agents||[]).some(function(a){ return a.id === e.agentId; })) return s;
5481
+ }
5482
+ }
5483
+ return null;
5484
+ }
5485
+ function agentNameForEntry(snap, e){
5486
+ var session = sessionForTimelineEntry(snap, e);
5487
+ var ag = session && e.agentId ? findAgent(session, e.agentId) : null;
5488
+ return (ag && (ag.name || ag.id)) || e.agentName || e.agentId || 'leader';
5489
+ }
5490
+ function timelinePreviewText(e){
5491
+ var text = String(e.text || '');
5492
+ if(e.role === 'tool'){
5493
+ var lines = text.split('\\n');
5494
+ return (lines[0] || 'tool output') + (lines.length > 1 ? (' +' + (lines.length - 1) + ' lines') : '');
5495
+ }
5496
+ return text;
5497
+ }
5498
+ function timelineAgentOptions(snap, project){
5499
+ var opts = [];
5500
+ var seen = {};
5501
+ function add(projectId, clientId, agentId, label){
5502
+ if(!agentId) return;
5503
+ if(project !== 'all' && projectId !== project) return;
5504
+ var value = timelineAgentValue(projectId, clientId, agentId);
5505
+ if(seen[value]) return;
5506
+ seen[value] = 1;
5507
+ opts.push({ value: value, projectId: projectId, clientId: clientId, agentId: agentId, label: label });
5508
+ }
5509
+ ((snap && snap.liveSessions) || []).forEach(function(s){
5510
+ var cid = clientIdForSession(snap, s);
5511
+ add(s.projectId, cid, 'leader', (s.projectName || s.projectId) + ' \xB7 leader');
5512
+ (s.agents||[]).forEach(function(a){
5513
+ add(s.projectId, cid, a.id, (s.projectName || s.projectId) + ' \xB7 ' + (a.name || a.id));
5514
+ });
5515
+ });
5516
+ Store.timeline.forEach(function(e){
5517
+ add(e.projectId, e.clientId, e.agentId, projectNameFor(snap, e.projectId) + ' \xB7 ' + (e.agentName || e.agentId));
5518
+ });
5519
+ opts.sort(function(a,b){ return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); });
5520
+ return opts;
5521
+ }
5522
+
5523
+ function GlobalTimelineView(p){
5524
+ var qState = React.useState(''); var q = qState[0], setQ = qState[1];
5525
+ var prState = React.useState('all'); var project = prState[0], setProject = prState[1];
5526
+ var srcState = React.useState('all'); var source = srcState[0], setSource = srcState[1];
5527
+ var roleState = React.useState('all'); var role = roleState[0], setRole = roleState[1];
5528
+ var agState = React.useState('all'); var agent = agState[0], setAgent = agState[1];
5529
+ var agentFilter = parseTimelineAgentValue(agent);
5530
+ var requestFilters = {
5531
+ projectId: project === 'all' ? '' : project,
5532
+ source: source === 'all' ? '' : source,
5533
+ role: role === 'all' ? '' : role,
5534
+ clientId: agentFilter ? agentFilter.clientId : '',
5535
+ agentId: agentFilter ? agentFilter.agentId : ''
5536
+ };
5537
+ React.useEffect(function(){ loadRecentTimeline(requestFilters, true); }, [project, source, role, agent]);
5538
+ var ql = q.trim().toLowerCase();
5539
+ var projects = (p.snap && p.snap.projects) || [];
5540
+ var agents = timelineAgentOptions(p.snap, project);
5541
+ var entries = Store.timeline.filter(function(e){
5542
+ if(project !== 'all' && e.projectId !== project) return false;
5543
+ if(source !== 'all' && e.source !== source) return false;
5544
+ if(role !== 'all' && e.role !== role) return false;
5545
+ if(agentFilter && (e.projectId !== agentFilter.projectId || e.clientId !== agentFilter.clientId || e.agentId !== agentFilter.agentId)) return false;
5546
+ if(!ql) return true;
5547
+ var hay = [e.text, e.role, e.tool, e.agentName, e.agentId, projectNameFor(p.snap, e.projectId)].join(' ').toLowerCase();
5548
+ return hay.indexOf(ql) >= 0;
5549
+ }).slice(0, 500);
5550
+ function openEntry(e){
5551
+ var session = sessionForTimelineEntry(p.snap, e);
5552
+ if(!session) return;
5553
+ selectSession(session.sessionId, e.agentId && e.agentId !== 'leader' ? e.agentId : null);
5554
+ Store.set({ tab: 'console' });
5555
+ }
5556
+ return h('div', { className: 'timeline-wrap' },
5557
+ h('div', { className: 'timeline-head' },
5558
+ h('div', null,
5559
+ h('div', { className: 'timeline-title' }, 'All Agent Timelines'),
5560
+ h('div', { className: 'timeline-note' }, entries.length + ' visible events from recent HQ telemetry across projects')
5561
+ ),
5562
+ h('div', { className: 'timeline-filters' },
5563
+ h('select', { className: 'timeline-select', value: project, onChange: function(e){ setProject(e.target.value); setAgent('all'); } },
5564
+ h('option', { value: 'all' }, 'All projects'),
5565
+ projects.map(function(pr){ return h('option', { key: pr.projectId, value: pr.projectId }, pr.projectName || pr.projectId); })
5566
+ ),
5567
+ h('select', { className: 'timeline-select', value: agent, onChange: function(e){ setAgent(e.target.value); } },
5568
+ h('option', { value: 'all' }, 'All agents'),
5569
+ agents.map(function(a){ return h('option', { key: a.value, value: a.value }, a.label); })
5570
+ ),
5571
+ h('select', { className: 'timeline-select', value: source, onChange: function(e){ setSource(e.target.value); } },
5572
+ h('option', { value: 'all' }, 'All sources'),
5573
+ h('option', { value: 'session' }, 'Leader/session'),
5574
+ h('option', { value: 'agent' }, 'Subagent')
5575
+ ),
5576
+ h('select', { className: 'timeline-select', value: role, onChange: function(e){ setRole(e.target.value); } },
5577
+ h('option', { value: 'all' }, 'All roles'),
5578
+ h('option', { value: 'user' }, 'User'),
5579
+ h('option', { value: 'assistant' }, 'Assistant'),
5580
+ h('option', { value: 'tool' }, 'Tool'),
5581
+ h('option', { value: 'system' }, 'System'),
5582
+ h('option', { value: 'error' }, 'Error')
5583
+ ),
5584
+ h('input', { className: 'timeline-input', placeholder: 'Filter text / agent / tool\u2026', value: q, onChange: function(e){ setQ(e.target.value); } }),
5585
+ h('button', { className: 'timeline-btn', onClick: function(){
5586
+ var rf = Object.assign({}, requestFilters, { q: q.trim() });
5587
+ loadRecentTimeline(rf, true);
5588
+ } }, 'Refresh')
5589
+ )
5590
+ ),
5591
+ h(PromptDock, { snap: p.snap }),
5592
+ h(CommandHistory, { snap: p.snap }),
5593
+ h('div', { className: 'timeline-list' },
5594
+ entries.length ? entries.map(function(e, i){
5595
+ var pn = projectNameFor(p.snap, e.projectId);
5596
+ var an = agentNameForEntry(p.snap, e);
5597
+ return h('div', { key: i, className: 'timeline-row ' + (e.role || '') + ' ' + (e.source || ''), onClick: function(){ openEntry(e); } },
5598
+ h('div', { className: 'timeline-mark' }, roleIcon(e.role)),
5599
+ h('div', { className: 'timeline-time' }, fmtTime(e.ts), h('div', null, fmtAgo(e.ts))),
5600
+ h('div', { className: 'timeline-who' },
5601
+ h('div', { className: 'timeline-agent' }, an),
5602
+ h('div', { className: 'timeline-project' }, pn + (e.source ? (' \xB7 ' + e.source) : ''))
5603
+ ),
5604
+ h('div', { className: 'timeline-msg' },
5605
+ h('span', { className: 'timeline-role ' + (e.role||'') }, e.role || 'event'),
5606
+ e.tool ? h('span', { className: 'timeline-toolchip' }, e.tool) : null,
5607
+ timelinePreviewText(e)
5608
+ )
5609
+ );
5610
+ }) : h('div', { className: 'side-empty' }, Store.timelineLoaded ? 'No timeline events yet.' : 'Loading recent HQ timeline\u2026')
5611
+ )
4736
5612
  );
4737
5613
  }
4738
5614
 
@@ -4819,15 +5695,15 @@ async function boot(){
4819
5695
 
4820
5696
  var toolbar = h('div', { className: 'gtoolbar' },
4821
5697
  h('div', { className: 'tgroup' },
4822
- h(ToolBtn, { label: '\u2B0C LR', title: 'Left \u2192 right tree', active: dir==='LR', onClick: function(){ setDir('LR'); } }),
4823
- h(ToolBtn, { label: '\u2B0D TB', title: 'Top \u2192 bottom tree', active: dir==='TB', onClick: function(){ setDir('TB'); } })
5698
+ h(ToolBtn, { label: '\u2B0C LR', title: 'Left \u2192 right clustered layout', active: dir==='LR', onClick: function(){ setDir('LR'); } }),
5699
+ h(ToolBtn, { label: '\u2B0D TB', title: 'Top \u2192 bottom clustered layout', active: dir==='TB', onClick: function(){ setDir('TB'); } })
4824
5700
  ),
4825
5701
  h('div', { className: 'tgroup' },
4826
5702
  h(ToolBtn, { label: '\u{1F5A5}\uFE0F Machine', title: 'Group under the machine', active: groupBy==='machine', onClick: function(){ setGroupBy('machine'); } }),
4827
5703
  h(ToolBtn, { label: '\u{1F4C1} Project', title: 'Group by project', active: groupBy==='project', onClick: function(){ setGroupBy('project'); } })
4828
5704
  ),
4829
5705
  h('div', { className: 'tgroup' },
4830
- h(ToolBtn, { label: '\u2728 Auto-arrange', title: 'Re-arrange the tree', onClick: function(){ applyLayout(nodes, edges); } }),
5706
+ h(ToolBtn, { label: '\u2728 Auto-arrange', title: 'Re-arrange with terminal-centered agent clusters', onClick: function(){ applyLayout(nodes, edges); } }),
4831
5707
  h(ToolBtn, { label: '\u22A1 Fit', title: 'Fit to screen', onClick: function(){ if(rfRef.current) rfRef.current.fitView({ padding: 0.18, duration: 400 }); } })
4832
5708
  ),
4833
5709
  h('div', { className: 'glegend' },
@@ -5017,11 +5893,16 @@ async function boot(){
5017
5893
 
5018
5894
  function LiveBubble(p){
5019
5895
  return h('div', { className: 'bub assistant live', key: 'live' },
5020
- h('div', { className: 'bub-meta' },
5021
- h('span', { className: 'bub-role' }, p.tool ? ('tool \xB7 '+p.tool) : 'assistant'),
5022
- h('span', { className: 'live-dot' }, '\u25CF streaming')
5023
- ),
5024
- h('div', { className: 'txt' }, p.text, h('span', { className: 'caret' }))
5896
+ h('div', { className: 'bub-avatar' }, p.tool ? '\u2318' : '\u{1F916}'),
5897
+ h('div', { className: 'bub-content' },
5898
+ h('div', { className: 'bub-head' },
5899
+ h('span', { className: 'bub-role' }, p.tool ? p.tool : 'Assistant'),
5900
+ h('span', { className: 'live-dot' }, '\u25CF streaming')
5901
+ ),
5902
+ h('div', { className: 'bub-card' },
5903
+ h('div', { className: 'assistant-body' }, renderMessageText(p.text || '', 'assistant'), h('span', { className: 'caret' }))
5904
+ )
5905
+ )
5025
5906
  );
5026
5907
  }
5027
5908
 
@@ -5029,14 +5910,14 @@ async function boot(){
5029
5910
  var sel = Store.selected;
5030
5911
  var bodyRef = React.useRef(null);
5031
5912
  var stickRef = React.useRef(true);
5032
- // A non-leader agent is a subagent (shadow) \u2014 show ITS own buffered stream,
5033
- // keyed by subagentId (== agent card id). The leader / a bare terminal show
5034
- // the session's full on-disk transcript.
5913
+ // A non-leader agent is a subagent (shadow) \u2014 show ITS own buffered stream.
5914
+ // The leader / a bare terminal show the session's full on-disk transcript.
5915
+ var session = (p.snap && p.snap.liveSessions || []).filter(function(s){ return s.sessionId===sel.sessionId; })[0];
5035
5916
  var isSub = !!(sel && sel.agentId && sel.agentId !== 'leader');
5036
- var subMsgs = isSub ? (Store.agentMsgs[sel.agentId] || []) : null;
5917
+ var subKey = isSub ? cacheKey(sel.agentId, session && session.projectId, clientIdForSession(p.snap, session)) : '';
5918
+ var subMsgs = isSub ? (Store.agentMsgs[subKey] || Store.agentMsgs[sel.agentId] || []) : null;
5037
5919
  var tc = (sel && !isSub) ? Store.transcripts[sel.sessionId] : null;
5038
5920
  var entries = isSub ? subMsgs : (tc ? tc.entries : []);
5039
- var session = (p.snap && p.snap.liveSessions || []).filter(function(s){ return s.sessionId===sel.sessionId; })[0];
5040
5921
  var agentsList = session ? (session.agents||[]) : [];
5041
5922
  var ag = sel.agentId ? agentsList.filter(function(a){ return a.id===sel.agentId; })[0]
5042
5923
  : (agentsList.filter(function(a){ return a.id==='leader'; })[0] || agentsList[0]);
@@ -5062,7 +5943,17 @@ async function boot(){
5062
5943
  if(!isSub && tc && tc.loading && !entries.length && !liveText){ bodyEls.push(h('div', { key:'l', className: 'loading' }, 'Loading full chat history\u2026')); }
5063
5944
  else if(!entries.length && !liveText){ bodyEls.push(h('div', { key:'e', className: 'side-empty' }, isSub ? ('No messages from ' + (ag?ag.name:'this subagent') + ' yet \u2014 its conversation streams here live as it works.') : ((tc&&tc.error)?'Could not load history.':'No transcript yet for this terminal.'))); }
5064
5945
  else {
5065
- for(var i=0;i<entries.length;i++){ bodyEls.push(h(Bubble, { key: i, e: entries[i] })); }
5946
+ for(var i=0;i<entries.length;i++){
5947
+ if(isToolEntry(entries[i])){
5948
+ var group = [], start = i;
5949
+ while(i < entries.length && isToolEntry(entries[i])){ group.push(entries[i]); i++; }
5950
+ if(group.length > 1) bodyEls.push(h(ToolGroup, { key: 'tg-' + start, tools: group, defaultOpen: i >= entries.length }));
5951
+ else bodyEls.push(h(Bubble, { key: start, e: group[0] }));
5952
+ i--;
5953
+ } else {
5954
+ bodyEls.push(h(Bubble, { key: i, e: entries[i] }));
5955
+ }
5956
+ }
5066
5957
  if(liveText || (streaming && !isSub && liveTool)){ bodyEls.push(h(LiveBubble, { text: liveText, tool: liveTool })); }
5067
5958
  }
5068
5959
  return h('div', { className: 'chatview' },
@@ -5072,7 +5963,8 @@ async function boot(){
5072
5963
  ag ? h('span', { className: 'chat-agent' }, h('span', { className: 'dot '+(ag.status||'idle') }), ag.name || ag.id, isSub ? h('span', { className: 'subbadge' }, 'subagent') : null) : null,
5073
5964
  h('span', { className: 'chat-meta' }, metaText)
5074
5965
  ),
5075
- h('div', { className: 'chat-body', ref: bodyRef, onScroll: onScroll }, bodyEls)
5966
+ h('div', { className: 'chat-body', ref: bodyRef, onScroll: onScroll }, bodyEls),
5967
+ h(PromptDock, { snap: p.snap })
5076
5968
  );
5077
5969
  }
5078
5970
 
@@ -5106,11 +5998,13 @@ async function boot(){
5106
5998
  h(TopBar, { snap: snap, connected: s.connected }),
5107
5999
  h('div', { className: 'hq-tabs' },
5108
6000
  tabBtn('console', '\u{1F6F0}\uFE0F Console'),
6001
+ tabBtn('timeline', '\u{1F9FE} Timeline'),
5109
6002
  tabBtn('map', '\u{1F9ED} Map'),
5110
6003
  h('div', { className: 'hq-tab' + (tab==='mailbox'?' active':''), onClick: function(){ Store.set({ tab:'mailbox' }); } }, '\u{1F4EC} Mailbox', unread? h('span', { className:'badge' }, unread):null)
5111
6004
  ),
5112
6005
  h('div', { className: 'hq-body' },
5113
6006
  tab==='map' ? h(FleetView, { snap: snap }) :
6007
+ tab==='timeline' ? h(GlobalTimelineView, { snap: snap }) :
5114
6008
  tab==='mailbox' ? h(MailboxView, { snap: snap }) :
5115
6009
  h(ConsoleView, { snap: snap })
5116
6010
  )
@@ -5124,6 +6018,8 @@ async function boot(){
5124
6018
  });
5125
6019
  connectWs();
5126
6020
  primeSnapshot();
6021
+ loadRecentTimeline();
6022
+ loadCommands();
5127
6023
  }
5128
6024
 
5129
6025
  function primeSnapshot(){
@@ -5177,14 +6073,15 @@ function renderFallback(){
5177
6073
  if(tc.loading && !tc.entries.length){ html += '<div class="loading">Loading full chat history\u2026</div>'; }
5178
6074
  else if(!tc.entries.length){ html += '<div class="side-empty">No transcript yet.</div>'; }
5179
6075
  else { tc.entries.forEach(function(e){
5180
- var head = '<div class="bub '+escAttr(e.role)+'"><div class="bub-meta"><span class="bub-role">'+escAttr(e.role)+(e.tool?(' \xB7 '+escAttr(e.tool)):'')+'</span><span>'+fmtTime(e.ts)+'</span></div>';
6076
+ var head = '<div class="bub '+escAttr(e.role)+'"><div class="bub-avatar">'+escAttr(roleIcon(e.role))+'</div><div class="bub-content"><div class="bub-head"><span class="bub-role">'+escAttr((e.role==='tool'||e.role==='error')&&e.tool?e.tool:roleLabel(e.role))+'</span><span>'+fmtTime(e.ts)+'</span></div><div class="bub-card">';
5181
6077
  var body;
5182
6078
  if(e.role==='tool' || e.role==='error'){
5183
- body = '';
5184
- if(e.toolInput) body += '<details class="bub-fold"><summary>\u2192 args \xB7 '+e.toolInput.length+' chars</summary><pre class="bub-argpre">'+escAttr(e.toolInput)+'</pre></details>';
5185
- if(e.text) body += '<details class="bub-fold"><summary>'+(e.isError?'\u26A0 error':'\u2190 result')+' \xB7 '+e.text.length+' chars</summary><pre>'+escAttr(e.text)+'</pre></details>';
5186
- } else { body = '<div class="txt">'+escAttr(e.text||'')+'</div>'; }
5187
- html += head + body + '</div>';
6079
+ body = '<div class="tool-card"><div class="tool-head"><span class="tool-name">'+escAttr(e.tool||'tool')+'</span><span class="tool-duration">'+escAttr(toolSummary(e))+'</span><span class="tool-status '+escAttr(e.isError||e.role==='error'?'error':'')+'">'+escAttr(e.isError||e.role==='error'?'failed':'done')+'</span></div>';
6080
+ if(e.toolInput) body += '<details class="bub-fold"><summary>Input \xB7 '+e.toolInput.length+' chars</summary><pre class="bub-argpre">'+escAttr(e.toolInput)+'</pre></details>';
6081
+ if(e.text) body += '<details class="bub-fold"><summary>'+(e.isError?'Error output':'Output')+' \xB7 '+countLines(e.text)+' lines</summary><pre>'+escAttr(e.text)+'</pre></details>';
6082
+ body += '</div>';
6083
+ } else { body = '<div class="'+escAttr(e.role==='assistant'?'assistant-body':'txt')+'">'+escAttr(e.text||'')+'</div>'; }
6084
+ html += head + body + '</div></div></div>';
5188
6085
  }); }
5189
6086
  html += '</div></div>';
5190
6087
  }
@@ -5561,6 +6458,7 @@ function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth) {
5561
6458
  const agentMessages = /* @__PURE__ */ new Map();
5562
6459
  const persistence = createHqPersistence(dataDir);
5563
6460
  const auditLog = new HqCommandAuditLog();
6461
+ const hqSessionTag = randomUUID().slice(0, 8);
5564
6462
  const alertEngine = new HqAlertEngine({
5565
6463
  onAlert: (alert) => {
5566
6464
  const msg = toAlertMessage(alert);
@@ -5750,6 +6648,98 @@ function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth) {
5750
6648
  res.end(JSON.stringify({ commandId, queued: true, clientId: target.clientId }));
5751
6649
  return;
5752
6650
  }
6651
+ if (url.pathname === "/api/mailbox-send" && req2.method === "POST") {
6652
+ const suppliedToken = extractBrowserToken(req2, url);
6653
+ const inBrowserTokenMode = mutableAuth.browserTokens.size > 0;
6654
+ if (inBrowserTokenMode && (!suppliedToken || !mutableAuth.browserTokens.has(suppliedToken))) {
6655
+ res.writeHead(401, { "Content-Type": "application/json" });
6656
+ res.end(JSON.stringify({ error: "unauthorized" }));
6657
+ return;
6658
+ }
6659
+ const tokenObj = suppliedToken !== void 0 ? mutableAuth.browserTokenObjs.get(suppliedToken) : void 0;
6660
+ const canEnqueue = !inBrowserTokenMode || tokenObj?.capabilities === void 0 || tokenObj.capabilities.includes("control.enqueue");
6661
+ if (!canEnqueue) {
6662
+ res.writeHead(403, { "Content-Type": "application/json" });
6663
+ res.end(JSON.stringify({ error: "forbidden: token lacks control.enqueue capability" }));
6664
+ return;
6665
+ }
6666
+ let mbody;
6667
+ try {
6668
+ mbody = JSON.parse(await readRequestBody(req2));
6669
+ } catch {
6670
+ res.writeHead(400, { "Content-Type": "application/json" });
6671
+ res.end(JSON.stringify({ error: "invalid json body" }));
6672
+ return;
6673
+ }
6674
+ if (typeof mbody.type !== "string" || typeof mbody.body !== "string") {
6675
+ res.writeHead(400, { "Content-Type": "application/json" });
6676
+ res.end(JSON.stringify({ error: "missing type or body" }));
6677
+ return;
6678
+ }
6679
+ if (typeof mbody.sessionId !== "string" && typeof mbody.projectId !== "string") {
6680
+ res.writeHead(400, { "Content-Type": "application/json" });
6681
+ res.end(JSON.stringify({ error: "missing sessionId or projectId" }));
6682
+ return;
6683
+ }
6684
+ const { SessionRegistry } = await import('@wrongstack/core');
6685
+ const mbGlobalRoot = path5.dirname(dataDir);
6686
+ let projectRoot;
6687
+ try {
6688
+ const registry = new SessionRegistry(mbGlobalRoot);
6689
+ if (typeof mbody.sessionId === "string") {
6690
+ const entry = await registry.get(mbody.sessionId).catch(() => null);
6691
+ projectRoot = entry?.projectRoot;
6692
+ }
6693
+ if (projectRoot === void 0 && typeof mbody.projectId === "string") {
6694
+ const all = await registry.list().catch(() => []);
6695
+ const match = all.find((e) => e.projectSlug === mbody.projectId);
6696
+ projectRoot = match?.projectRoot;
6697
+ }
6698
+ } catch {
6699
+ projectRoot = void 0;
6700
+ }
6701
+ if (typeof projectRoot !== "string" || projectRoot.length === 0) {
6702
+ res.writeHead(404, { "Content-Type": "application/json" });
6703
+ res.end(JSON.stringify({ error: "could not resolve target project mailbox" }));
6704
+ return;
6705
+ }
6706
+ const to = typeof mbody.to === "string" ? mbody.to : "leader";
6707
+ const subject = typeof mbody.subject === "string" ? mbody.subject : "HQ prompt";
6708
+ const priority = mbody.priority === "high" ? "high" : mbody.priority === "low" ? "low" : "normal";
6709
+ const validated = validateHqCommand({
6710
+ commandId: randomUUID(),
6711
+ type: mbody.type,
6712
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6713
+ payload: { to, subject, body: mbody.body, priority },
6714
+ requiresAck: false
6715
+ });
6716
+ const mailboxType = validated?.type === "steer" || validated?.type === "btw" ? validated.type : validated?.type === "queue" ? "note" : validated?.type === "broadcast" ? "broadcast" : void 0;
6717
+ if (mailboxType === void 0) {
6718
+ res.writeHead(400, { "Content-Type": "application/json" });
6719
+ res.end(JSON.stringify({ error: "unrecognized or malformed mailbox message", type: mbody.type }));
6720
+ return;
6721
+ }
6722
+ try {
6723
+ const { GlobalMailbox: GlobalMailbox9, resolveProjectDir: resolveProjectDir10 } = await import('@wrongstack/core');
6724
+ const projectDir = resolveProjectDir10(projectRoot, mbGlobalRoot);
6725
+ const mailbox = new GlobalMailbox9(projectDir);
6726
+ const from = `hq@${hqSessionTag}`;
6727
+ const sent = await mailbox.send({
6728
+ from,
6729
+ to: mailboxType === "broadcast" ? "all" : to,
6730
+ type: mailboxType,
6731
+ subject,
6732
+ body: mbody.body,
6733
+ priority
6734
+ });
6735
+ res.writeHead(202, { "Content-Type": "application/json" });
6736
+ res.end(JSON.stringify({ delivered: true, messageId: sent?.id, to, type: mailboxType }));
6737
+ } catch (err) {
6738
+ res.writeHead(500, { "Content-Type": "application/json" });
6739
+ res.end(JSON.stringify({ error: "mailbox write failed", detail: String(err) }));
6740
+ }
6741
+ return;
6742
+ }
5753
6743
  if (url.pathname === "/api/commands" && req2.method === "GET") {
5754
6744
  const rawLimit = Number.parseInt(url.searchParams.get("limit") ?? "200", 10);
5755
6745
  const limit = Math.min(1e3, Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 200));
@@ -12915,6 +13905,38 @@ async function runOAuthLoginMenu(deps) {
12915
13905
  const pick = await deps.reader.readLine(` ${color.amber("?")} Pick ${color.dim("(or b to go back)")}: `);
12916
13906
  await runOAuthLoginChoice(deps, pick);
12917
13907
  }
13908
+
13909
+ // src/auth-menu/local-presets.ts
13910
+ var LOCAL_LLM_PRESETS = [
13911
+ {
13912
+ id: "omniroute",
13913
+ label: "OmniRoute",
13914
+ defaultBaseUrl: "http://localhost:20128/v1",
13915
+ noAuth: true,
13916
+ hint: "WrongStack local gateway \u2014 port 20128, no auth, auto-discovers models"
13917
+ },
13918
+ {
13919
+ id: "ollama",
13920
+ label: "Ollama",
13921
+ defaultBaseUrl: "http://localhost:11434/v1",
13922
+ noAuth: true,
13923
+ hint: "https://ollama.com \u2014 port 11434, no auth"
13924
+ },
13925
+ {
13926
+ id: "vllm",
13927
+ label: "vLLM",
13928
+ defaultBaseUrl: "http://localhost:8000/v1",
13929
+ noAuth: false,
13930
+ hint: "https://docs.vllm.ai \u2014 port 8000, optional Bearer"
13931
+ },
13932
+ {
13933
+ id: "lmstudio",
13934
+ label: "LM Studio",
13935
+ defaultBaseUrl: "http://localhost:1234/v1",
13936
+ noAuth: false,
13937
+ hint: "https://lmstudio.ai \u2014 port 1234, optional Bearer"
13938
+ }
13939
+ ];
12918
13940
  var defaultUidFn = () => os9__default.userInfo().uid;
12919
13941
  async function getFileUid(filePath) {
12920
13942
  try {
@@ -13231,38 +14253,6 @@ async function restoreLast(homeFn = defaultHomeDir) {
13231
14253
  return { ok: true };
13232
14254
  }
13233
14255
 
13234
- // src/auth-menu/local-presets.ts
13235
- var LOCAL_LLM_PRESETS = [
13236
- {
13237
- id: "omniroute",
13238
- label: "OmniRoute",
13239
- defaultBaseUrl: "http://localhost:20128/v1",
13240
- noAuth: true,
13241
- hint: "WrongStack local gateway \u2014 port 20128, no auth, auto-discovers models"
13242
- },
13243
- {
13244
- id: "ollama",
13245
- label: "Ollama",
13246
- defaultBaseUrl: "http://localhost:11434/v1",
13247
- noAuth: true,
13248
- hint: "https://ollama.com \u2014 port 11434, no auth"
13249
- },
13250
- {
13251
- id: "vllm",
13252
- label: "vLLM",
13253
- defaultBaseUrl: "http://localhost:8000/v1",
13254
- noAuth: false,
13255
- hint: "https://docs.vllm.ai \u2014 port 8000, optional Bearer"
13256
- },
13257
- {
13258
- id: "lmstudio",
13259
- label: "LM Studio",
13260
- defaultBaseUrl: "http://localhost:1234/v1",
13261
- noAuth: false,
13262
- hint: "https://lmstudio.ai \u2014 port 1234, optional Bearer"
13263
- }
13264
- ];
13265
-
13266
14256
  // src/provider-helpers.ts
13267
14257
  function visibleModelIds(providerId, config, catalogModelIds, cfg) {
13268
14258
  const entry = cfg ?? config.providers?.[providerId];
@@ -13334,7 +14324,62 @@ async function buildPickableProviders(modelsRegistry, config) {
13334
14324
  }
13335
14325
 
13336
14326
  // src/picker.ts
13337
- var theme = { primary: color.amber };
14327
+ var theme = {
14328
+ accent: color.amber,
14329
+ chrome: color.gray,
14330
+ cursor: (s) => color.amber(color.bold(s)),
14331
+ ctx: color.cyan,
14332
+ cost: color.green,
14333
+ caps: color.magenta
14334
+ };
14335
+ var BOX = {
14336
+ tl: "\u256D",
14337
+ tr: "\u256E",
14338
+ bl: "\u2570",
14339
+ br: "\u256F",
14340
+ h: "\u2500",
14341
+ v: "\u2502"
14342
+ };
14343
+ var PICKER_MIN_WIDTH = 74;
14344
+ var PICKER_MAX_WIDTH = 100;
14345
+ function pickerWidth() {
14346
+ const cols = process.stdout.columns;
14347
+ if (typeof cols !== "number" || !Number.isFinite(cols) || cols <= 0) {
14348
+ return PICKER_MIN_WIDTH;
14349
+ }
14350
+ const inner = cols - 2;
14351
+ return Math.max(PICKER_MIN_WIDTH, Math.min(PICKER_MAX_WIDTH, inner));
14352
+ }
14353
+ function visibleWidth(s) {
14354
+ return stripAnsi(s).length;
14355
+ }
14356
+ function padVisible(s, width) {
14357
+ const w = visibleWidth(s);
14358
+ if (w === width) return s;
14359
+ if (w < width) return s + " ".repeat(width - w);
14360
+ const plain = stripAnsi(s);
14361
+ return `${plain.slice(0, Math.max(0, width - 1))}\u2026`;
14362
+ }
14363
+ function boxTop(title) {
14364
+ const width = pickerWidth();
14365
+ const label = ` ${title} `;
14366
+ const pad = Math.max(0, width - visibleWidth(label) - 1);
14367
+ return theme.chrome(
14368
+ `${BOX.tl}${BOX.h}${theme.accent(color.bold(label))}${theme.chrome(BOX.h.repeat(pad))}${BOX.tr}`
14369
+ );
14370
+ }
14371
+ function boxBottom() {
14372
+ return theme.chrome(`${BOX.bl}${BOX.h.repeat(pickerWidth())}${BOX.br}`);
14373
+ }
14374
+ function boxRow(content) {
14375
+ return `${theme.chrome(BOX.v)} ${padVisible(content, pickerWidth() - 2)} ${theme.chrome(BOX.v)}`;
14376
+ }
14377
+ function boxDivider() {
14378
+ return theme.chrome(`${BOX.v}${BOX.h.repeat(pickerWidth())}${BOX.v}`);
14379
+ }
14380
+ function keyHints(pairs) {
14381
+ return pairs.map(([k, label]) => `${theme.accent(k)} ${color.dim(label)}`).join(color.dim(" \xB7 "));
14382
+ }
13338
14383
  var CODEX_PROVIDER_ID2 = "openai-codex";
13339
14384
  var CODEX_PICKER_HEADER = "Select Model and Effort";
13340
14385
  var CODEX_LEGACY_NOTE = "Access legacy models by running `wstack -m <model_name>` or in your `config.json`.";
@@ -13428,38 +14473,55 @@ function renderLiveProviderList(query, filtered, selectedIdx, savedSet) {
13428
14473
  const ordered = orderProvidersForDisplay(filtered);
13429
14474
  const scrollOffset = Math.max(0, selectedIdx - LIVE_PICKER_MAX_VISIBLE + 1);
13430
14475
  const visible = ordered.slice(scrollOffset, scrollOffset + LIVE_PICKER_MAX_VISIBLE);
13431
- let out = `? Select provider: ${query}
13432
- `;
14476
+ const lines = [];
14477
+ lines.push(boxTop("Select a provider"));
14478
+ const caret = query.length > 0 ? color.dim("\u258F") : theme.cursor("\u258F");
14479
+ lines.push(boxRow(`${theme.accent("\u276F")} ${color.dim("Select provider:")} ${query}${caret}`));
14480
+ lines.push(boxDivider());
13433
14481
  if (scrollOffset > 0) {
13434
- out += ` \u2026 ${scrollOffset} more \u2191
13435
- `;
14482
+ lines.push(boxRow(color.dim(` ${scrollOffset} more above \u2191`)));
13436
14483
  }
13437
14484
  let flat = 0;
13438
14485
  let lastFamily = "";
13439
14486
  for (const p of visible) {
13440
14487
  if (p.family !== lastFamily) {
13441
- out += ` ${p.family}
13442
- `;
14488
+ lines.push(boxRow(`${theme.accent("\xB7")} ${color.dim(p.family.toUpperCase())}`));
13443
14489
  lastFamily = p.family;
13444
14490
  }
13445
- const sel = flat === selectedIdx - scrollOffset ? "\u25B6" : " ";
14491
+ const selected = flat === selectedIdx - scrollOffset;
14492
+ const sel = selected ? theme.cursor("\u25B6") : " ";
13446
14493
  let marker;
13447
14494
  if (savedSet) {
13448
14495
  const mark = savedSet.has(p.id) ? color.cyan("\u25C9") : color.dim("\u25CB");
13449
- marker = `${sel} ${mark} `;
14496
+ marker = `${sel} ${mark}`;
13450
14497
  } else {
13451
- marker = `${sel} `;
14498
+ marker = `${sel}`;
13452
14499
  }
13453
- out += `${marker}${p.id.padEnd(24)} ${p.name}
13454
- `;
14500
+ const id = selected ? theme.accent(color.bold(p.id)) : p.id;
14501
+ const name = selected ? p.name : color.dim(p.name);
14502
+ lines.push(boxRow(`${marker} ${padVisible(id, 24)} ${name}`));
13455
14503
  flat++;
13456
14504
  }
13457
14505
  if (scrollOffset + LIVE_PICKER_MAX_VISIBLE < ordered.length) {
13458
- out += ` \u2026 ${ordered.length - scrollOffset - LIVE_PICKER_MAX_VISIBLE} more \u2193
13459
- `;
14506
+ lines.push(
14507
+ boxRow(
14508
+ color.dim(` ${ordered.length - scrollOffset - LIVE_PICKER_MAX_VISIBLE} more below \u2193`)
14509
+ )
14510
+ );
13460
14511
  }
13461
- out += " \u2191\u2193 scroll \xB7 Enter select \xB7 Esc clear \xB7 Ctrl+C quit";
13462
- return out;
14512
+ lines.push(boxDivider());
14513
+ lines.push(
14514
+ boxRow(
14515
+ keyHints([
14516
+ ["\u2191\u2193", "scroll"],
14517
+ ["Enter", "select"],
14518
+ ["Esc", "clear"],
14519
+ ["Ctrl+C", "quit"]
14520
+ ])
14521
+ )
14522
+ );
14523
+ lines.push(boxBottom());
14524
+ return lines.join("\n");
13463
14525
  }
13464
14526
  function filterModels(query, models) {
13465
14527
  const q = query.trim().toLowerCase();
@@ -13471,28 +14533,48 @@ function renderLiveModelList(query, filtered, selectedIdx, header) {
13471
14533
  (a, b) => (b.release_date ?? "").localeCompare(a.release_date ?? "")
13472
14534
  );
13473
14535
  const visible = ordered.slice(0, LIVE_PICKER_MAX_VISIBLE);
13474
- let out = ` ${header}
13475
- ? Select model: ${query}
13476
- `;
14536
+ const lines = [];
14537
+ lines.push(boxTop("Select a model"));
14538
+ lines.push(boxRow(color.dim(header)));
14539
+ const caret = query.length > 0 ? color.dim("\u258F") : theme.cursor("\u258F");
14540
+ lines.push(boxRow(`${theme.accent("\u276F")} ${color.dim("Select model:")} ${query}${caret}`));
14541
+ lines.push(boxDivider());
13477
14542
  let flat = 0;
13478
14543
  for (const m of visible) {
13479
- const ctx = m.limit?.context ? `${(m.limit.context / 1e3).toFixed(0)}k`.padStart(6) : " ?";
13480
- const cost = m.cost?.input !== void 0 ? `$${m.cost.input}/$${m.cost.output ?? "?"}` : "";
13481
- const caps = [];
13482
- if (m.tool_call) caps.push("tools");
13483
- if (m.reasoning) caps.push("reason");
13484
- if (m.modalities?.input?.includes("image")) caps.push("vision");
13485
- const marker = flat === selectedIdx ? "\u25B6 " : " ";
13486
- out += `${marker}${m.id.padEnd(34)}${ctx} ${cost.padEnd(12)} ${caps.join(",")}
13487
- `;
14544
+ const selected = flat === selectedIdx;
14545
+ const marker = selected ? theme.cursor("\u25B6") : " ";
14546
+ const ctxRaw = m.limit?.context ? `${(m.limit.context / 1e3).toFixed(0)}k` : "?";
14547
+ const ctx = theme.ctx(ctxRaw.padStart(6));
14548
+ const costRaw = m.cost?.input !== void 0 ? `$${m.cost.input}/$${m.cost.output ?? "?"}` : "";
14549
+ const cost = costRaw ? theme.cost(costRaw) : "";
14550
+ const capTags = [];
14551
+ if (m.tool_call) capTags.push("tools");
14552
+ if (m.reasoning) capTags.push("reason");
14553
+ if (m.modalities?.input?.includes("image")) capTags.push("vision");
14554
+ const caps = capTags.map((c) => theme.caps(c)).join(color.dim(" "));
14555
+ const id = selected ? theme.accent(color.bold(m.id)) : m.id;
14556
+ const row = `${padVisible(id, 34)} ${ctx} ${padVisible(cost, 12)} ${caps}`;
14557
+ lines.push(boxRow(`${marker} ${row}`));
13488
14558
  flat++;
13489
14559
  }
13490
14560
  if (ordered.length > LIVE_PICKER_MAX_VISIBLE) {
13491
- out += ` \u2026 ${ordered.length - LIVE_PICKER_MAX_VISIBLE} more \u2014 type to filter
13492
- `;
14561
+ lines.push(
14562
+ boxRow(color.dim(` \u2026 ${ordered.length - LIVE_PICKER_MAX_VISIBLE} more \u2014 type to filter`))
14563
+ );
13493
14564
  }
13494
- out += " \u2191\u2193 move \xB7 Enter select \xB7 Esc clear \xB7 Ctrl+C quit";
13495
- return out;
14565
+ lines.push(boxDivider());
14566
+ lines.push(
14567
+ boxRow(
14568
+ keyHints([
14569
+ ["\u2191\u2193", "move"],
14570
+ ["Enter", "select"],
14571
+ ["Esc", "clear"],
14572
+ ["Ctrl+C", "quit"]
14573
+ ])
14574
+ )
14575
+ );
14576
+ lines.push(boxBottom());
14577
+ return lines.join("\n");
13496
14578
  }
13497
14579
  function codexPickerPreamble(provider) {
13498
14580
  if (provider.id !== CODEX_PROVIDER_ID2) return "";
@@ -13549,11 +14631,19 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
13549
14631
  return false;
13550
14632
  }
13551
14633
  }
13552
- var QUERY_PREFIX = "? Select provider: ";
14634
+ var QUERY_MARKER = "Select provider:";
13553
14635
  function cursorToQuery(frame, query) {
13554
- const newlines = (frame.match(/\n/g) ?? []).length;
13555
- const col = QUERY_PREFIX.length + query.length + 1;
13556
- writeOut(`\x1B[${newlines}A\x1B[${col}G`);
14636
+ const rows = frame.split("\n");
14637
+ const lastIdx = rows.length - 1;
14638
+ const queryIdx = rows.findIndex((r) => r.includes(QUERY_MARKER));
14639
+ if (queryIdx < 0) return;
14640
+ const up = lastIdx - queryIdx;
14641
+ const queryLine = rows[queryIdx] ?? "";
14642
+ const plain = stripAnsi(queryLine);
14643
+ const markerAt = plain.indexOf(QUERY_MARKER);
14644
+ const col = markerAt + QUERY_MARKER.length + 1 + query.length + 1;
14645
+ if (up > 0) writeOut(`\x1B[${up}A`);
14646
+ writeOut(`\x1B[${col}G`);
13557
14647
  }
13558
14648
  async function runLiveProviderPicker(displayList, savedSet) {
13559
14649
  const stdin = process.stdin;
@@ -13625,10 +14715,11 @@ async function runPicker(deps) {
13625
14715
  const { modelsRegistry, renderer, reader, config, defaultProvider, defaultModel } = deps;
13626
14716
  renderer.write(
13627
14717
  `
13628
- ${color.bold(theme.primary("WrongStack") + color.dim(" \u2014 Provider & Model Selection"))}
14718
+ ${theme.accent(color.bold("\u258C WrongStack"))} ${color.dim("Provider & Model Selection")}
13629
14719
  `
13630
14720
  );
13631
- renderer.write(color.dim("Loading provider catalog\u2026\n"));
14721
+ renderer.write(color.dim(`${theme.chrome("\xB7")} Loading provider catalog\u2026
14722
+ `));
13632
14723
  let providers;
13633
14724
  try {
13634
14725
  providers = await modelsRegistry.listProviders();
@@ -13687,9 +14778,7 @@ ${color.bold(theme.primary("WrongStack") + color.dim(" \u2014 Provider & Model S
13687
14778
  npm: inherited?.npm
13688
14779
  });
13689
14780
  }
13690
- const keyed = merged.filter(
13691
- (p) => hasApiKey(p, config) || isKeylessLocalProvider(p)
13692
- );
14781
+ const keyed = merged.filter((p) => hasApiKey(p, config) || isKeylessLocalProvider(p));
13693
14782
  let displayList = keyed;
13694
14783
  let showingFallback = false;
13695
14784
  if (keyed.length === 0) {
@@ -13732,12 +14821,14 @@ ${color.bold(theme.primary("WrongStack") + color.dim(" \u2014 Provider & Model S
13732
14821
  let idx = 1;
13733
14822
  let defaultIdx;
13734
14823
  renderer.write("\n");
14824
+ renderer.write(`${boxTop("Select a provider")}
14825
+ `);
13735
14826
  for (const fam of familyOrder) {
13736
14827
  const list = [...families.get(fam) ?? []].sort(
13737
14828
  (a, b) => a.id.toLowerCase().localeCompare(b.id.toLowerCase())
13738
14829
  );
13739
14830
  if (!list || list.length === 0) continue;
13740
- renderer.write(` ${color.bold(fam)}
14831
+ renderer.write(`${boxRow(`${theme.accent("\xB7")} ${color.dim(fam.toUpperCase())}`)}
13741
14832
  `);
13742
14833
  for (const p of list) {
13743
14834
  const envFound = p.envVars.some((v) => !!process.env[v]);
@@ -13746,27 +14837,29 @@ ${color.bold(theme.primary("WrongStack") + color.dim(" \u2014 Provider & Model S
13746
14837
  const marker = envFound ? color.green("\u25CF") : configKey ? color.cyan("\u25C9") : color.dim("\u25CB");
13747
14838
  const isDefault = p.id === defaultProvider;
13748
14839
  if (isDefault) defaultIdx = idx;
13749
- const idLabel = isDefault ? color.bold(p.id) : p.id;
14840
+ const idLabel = isDefault ? theme.accent(color.bold(p.id)) : p.id;
13750
14841
  const suffix = isDefault ? color.dim(" (default)") : "";
13751
14842
  renderer.write(
13752
- ` ${color.dim(`${idx}.`.padStart(4))} ${marker} ${idLabel.padEnd(22)} ${color.dim(p.name)}${suffix}
14843
+ `${boxRow(`${color.dim(`${idx}.`.padStart(4))} ${marker} ${padVisible(idLabel, 22)} ${color.dim(p.name)}${suffix}`)}
13753
14844
  `
13754
14845
  );
13755
14846
  ordered.push({ provider: p, index: idx });
13756
14847
  idx++;
13757
14848
  }
13758
14849
  }
14850
+ renderer.write(`${boxDivider()}
14851
+ `);
13759
14852
  if (showingFallback) {
13760
14853
  renderer.write(
13761
- `
13762
- ${color.yellow("\u26A0 No API keys detected.")} ${color.dim("Pick a provider, then run `wstack auth <provider>` to add one.")}
14854
+ `${boxRow(`${color.yellow("\u26A0 No API keys detected.")} ${color.dim("Run `wstack auth <provider>` after picking.")}`)}
13763
14855
  `
13764
14856
  );
13765
14857
  } else {
13766
- renderer.write(`
13767
- ${color.dim("\u25CF = env key \u25C9 = stored in config \u25CB = no key")}
14858
+ renderer.write(`${boxRow(color.dim("\u25CF env key \u25C9 stored in config \u25CB no key"))}
13768
14859
  `);
13769
14860
  }
14861
+ renderer.write(`${boxBottom()}
14862
+ `);
13770
14863
  const defaultHint = defaultIdx !== void 0 && defaultProvider ? ` ${color.dim(`[Enter = ${defaultProvider}]`)}` : "";
13771
14864
  const providerAnswer = (await reader.readLine(
13772
14865
  `
@@ -13872,8 +14965,13 @@ async function runLiveModelPicker(provider, defaultModel) {
13872
14965
  }
13873
14966
  async function pickModel(provider, registry, renderer, reader, defaultModel) {
13874
14967
  renderer.write(`
13875
- ${color.bold(provider.name)} ${color.dim(`(${provider.id})`)} models:
13876
-
14968
+ ${boxTop("Select a model")}
14969
+ `);
14970
+ renderer.write(
14971
+ `${boxRow(`${color.bold(provider.name)} ${color.dim(`(${provider.id})`)} models:`)}
14972
+ `
14973
+ );
14974
+ renderer.write(`${boxBottom()}
13877
14975
  `);
13878
14976
  const codexBlock = codexPickerPreamble(provider);
13879
14977
  if (codexBlock) renderer.write(codexBlock);
@@ -13886,20 +14984,16 @@ async function pickModel(provider, registry, renderer, reader, defaultModel) {
13886
14984
  " No models listed yet for this provider \u2014 type a model id to use it (local servers auto-discover models at launch).\n"
13887
14985
  )
13888
14986
  );
13889
- const typed = (await reader.readLine(
13890
- `
13891
- ${color.amber("?")} Model id ${color.dim("(or Enter to cancel)")}: `
13892
- )).trim();
14987
+ const typed = (await reader.readLine(`
14988
+ ${color.amber("?")} Model id ${color.dim("(or Enter to cancel)")}: `)).trim();
13893
14989
  if (!typed) {
13894
14990
  renderer.write(color.dim("Cancelled.\n"));
13895
14991
  return void 0;
13896
14992
  }
13897
- renderer.write(
13898
- `
14993
+ renderer.write(`
13899
14994
  ${color.green("\u2713")} ${color.bold(provider.id)} / ${color.bold(typed)}
13900
14995
 
13901
- `
13902
- );
14996
+ `);
13903
14997
  return { provider: provider.id, model: typed };
13904
14998
  }
13905
14999
  if (process.stdin.isTTY) {
@@ -13921,24 +15015,33 @@ ${color.amber("?")} Model id ${color.dim("(or Enter to cancel)")}: `
13921
15015
  let offset = 0;
13922
15016
  while (offset < models.length) {
13923
15017
  const page = models.slice(offset, offset + pageSize);
15018
+ const pageFrom = offset + 1;
15019
+ const pageTo = Math.min(offset + page.length, models.length);
15020
+ renderer.write(`${boxTop(`Models ${pageFrom}\u2013${pageTo} of ${models.length}`)}
15021
+ `);
13924
15022
  for (let i = 0; i < page.length; i++) {
13925
15023
  const m = expectDefined(page[i]);
13926
15024
  const num = offset + i + 1;
13927
- const ctx = m.limit?.context ? `${(m.limit.context / 1e3).toFixed(0)}k`.padStart(6) : " ?";
15025
+ const ctxRaw = m.limit?.context ? `${(m.limit.context / 1e3).toFixed(0)}k` : "?";
15026
+ const ctx = theme.ctx(ctxRaw.padStart(6));
13928
15027
  const cost = m.cost?.input !== void 0 ? `$${m.cost.input}/$${m.cost.output ?? "?"}` : "";
13929
- const caps = [];
13930
- if (m.tool_call) caps.push("tools");
13931
- if (m.reasoning) caps.push("reason");
13932
- if (m.modalities?.input?.includes("image")) caps.push("vision");
13933
- const capStr = caps.length > 0 ? color.dim(caps.join(",")) : "";
15028
+ const costCol = cost ? theme.cost(cost) : "";
15029
+ const capTags = [];
15030
+ if (m.tool_call) capTags.push("tools");
15031
+ if (m.reasoning) capTags.push("reason");
15032
+ if (m.modalities?.input?.includes("image")) capTags.push("vision");
15033
+ const capStr = capTags.map((c) => theme.caps(c)).join(color.dim(" "));
13934
15034
  const isDefault = m.id === defaultModel;
13935
- const idLabel = isDefault ? color.bold(m.id) : m.id;
15035
+ const idLabel = isDefault ? theme.accent(color.bold(m.id)) : m.id;
13936
15036
  const suffix = isDefault ? color.dim(" (default)") : "";
15037
+ const num5 = color.dim(`${num}.`.padStart(5));
13937
15038
  renderer.write(
13938
- ` ${color.dim(`${num}.`.padStart(5))} ${idLabel.padEnd(44)} ${color.dim(ctx)} ${color.dim(cost.padEnd(14))} ${capStr}${suffix}
15039
+ `${boxRow(`${num5} ${padVisible(idLabel, 34)} ${ctx} ${padVisible(costCol, 11)} ${capStr}${suffix}`)}
13939
15040
  `
13940
15041
  );
13941
15042
  }
15043
+ renderer.write(`${boxBottom()}
15044
+ `);
13942
15045
  offset += pageSize;
13943
15046
  if (offset < models.length) {
13944
15047
  const more = (await reader.readLine(
@@ -14710,14 +15813,14 @@ async function promptOptionalKey(deps, preset) {
14710
15813
 
14711
15814
  // src/auth-menu/panel-service.ts
14712
15815
  var ANSI_RE = /\u001b\[[0-9;]*m/g;
14713
- function stripAnsi(text) {
15816
+ function stripAnsi2(text) {
14714
15817
  return text.replace(ANSI_RE, "");
14715
15818
  }
14716
15819
  function cleanPrompt(prompt) {
14717
- return stripAnsi(prompt).replace(/[\r\n]+/g, " ").replace(/^\s*\?\s*/, "").replace(/[:\s]+$/, "").trim();
15820
+ return stripAnsi2(prompt).replace(/[\r\n]+/g, " ").replace(/^\s*\?\s*/, "").replace(/[:\s]+$/, "").trim();
14718
15821
  }
14719
15822
  function emitLines(io, raw, prefix = "") {
14720
- for (const line of stripAnsi(raw).split(/\r?\n/)) {
15823
+ for (const line of stripAnsi2(raw).split(/\r?\n/)) {
14721
15824
  const text = line.trim();
14722
15825
  if (text.length > 0) io.onLog(prefix + text);
14723
15826
  }
@@ -40334,6 +41437,32 @@ async function dispatch(controller, commandId, type, payload) {
40334
41437
  await mailbox.send({ from, to, type: "steer", subject, body, priority });
40335
41438
  return { commandId, status: "accepted", message: `steered ${to}` };
40336
41439
  }
41440
+ case "btw": {
41441
+ const mailbox = controller.steerMailbox;
41442
+ if (mailbox === void 0) {
41443
+ return { commandId, status: "rejected", message: "no mailbox available" };
41444
+ }
41445
+ const to = typeof payload["to"] === "string" ? payload["to"] : "leader";
41446
+ const subject = typeof payload["subject"] === "string" ? payload["subject"] : "HQ note";
41447
+ const body = typeof payload["body"] === "string" ? payload["body"] : "";
41448
+ const priority = payload["priority"] === "high" ? "high" : payload["priority"] === "low" ? "low" : "normal";
41449
+ const from = `hq@${controller.sessionTag()}`;
41450
+ await mailbox.send({ from, to, type: "btw", subject, body, priority });
41451
+ return { commandId, status: "accepted", message: `noted ${to}` };
41452
+ }
41453
+ case "queue": {
41454
+ const mailbox = controller.steerMailbox;
41455
+ if (mailbox === void 0) {
41456
+ return { commandId, status: "rejected", message: "no mailbox available" };
41457
+ }
41458
+ const to = typeof payload["to"] === "string" ? payload["to"] : "leader";
41459
+ const subject = typeof payload["subject"] === "string" ? payload["subject"] : "HQ prompt";
41460
+ const body = typeof payload["body"] === "string" ? payload["body"] : "";
41461
+ const priority = payload["priority"] === "high" ? "high" : payload["priority"] === "low" ? "low" : "normal";
41462
+ const from = `hq@${controller.sessionTag()}`;
41463
+ await mailbox.send({ from, to, type: "note", subject, body, priority });
41464
+ return { commandId, status: "accepted", message: `queued for ${to}` };
41465
+ }
40337
41466
  case "broadcast": {
40338
41467
  const mailbox = controller.steerMailbox;
40339
41468
  if (mailbox === void 0) {