@runfusion/fusion 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +2113 -1017
- package/dist/client/assets/AgentDetailView-CDZED6Dy.css +1 -0
- package/dist/client/assets/AgentDetailView-zycSdnO8.js +28 -0
- package/dist/client/assets/AgentsView-DoQkkDLf.css +1 -0
- package/dist/client/assets/AgentsView-pO7WiBS5.js +522 -0
- package/dist/client/assets/ChatView-BOd-sxbT.js +1 -0
- package/dist/client/assets/DevServerView-09GQf34f.js +11 -0
- package/dist/client/assets/DevServerView-ZeBGQkLI.css +1 -0
- package/dist/client/assets/DirectoryPicker-CcdN1Zs7.js +1 -0
- package/dist/client/assets/DocumentsView-CS8aiwtz.js +1 -0
- package/dist/client/assets/DocumentsView-Co9to4Zp.css +1 -0
- package/dist/client/assets/InsightsView-Bu9Cv8Ol.js +11 -0
- package/dist/client/assets/InsightsView-Egu71gmh.css +1 -0
- package/dist/client/assets/MemoryView-CtqgDtV9.js +2 -0
- package/dist/client/assets/MemoryView-DhinauGs.css +1 -0
- package/dist/client/assets/NodesView-BInPcedy.js +14 -0
- package/dist/client/assets/NodesView-DlQZHGXA.css +1 -0
- package/dist/client/assets/PiExtensionsManager-COxkYM2m.js +11 -0
- package/dist/client/assets/PiExtensionsManager-CPgmJgDk.css +1 -0
- package/dist/client/assets/PluginManager-CXUWZBOc.js +1 -0
- package/dist/client/assets/PluginManager-D64RIzmL.css +1 -0
- package/dist/client/assets/RoadmapsView-BOYnyMCh.css +1 -0
- package/dist/client/assets/RoadmapsView-BbCexaoi.js +6 -0
- package/dist/client/assets/SetupWizardModal-Cakxqkad.js +1 -0
- package/dist/client/assets/SkillsView-Cytf009Z.css +1 -0
- package/dist/client/assets/SkillsView-D3iqYCVf.js +1 -0
- package/dist/client/assets/folder-open-kO5Hsk66.js +6 -0
- package/dist/client/assets/index-BiSuUXCa.css +1 -0
- package/dist/client/assets/index-y194HxzU.js +644 -0
- package/dist/client/assets/upload-DHBQat92.js +6 -0
- package/dist/client/index.html +2 -2
- package/dist/extension.js +175 -66
- package/dist/pi-claude-cli/src/__tests__/control-handler.test.ts +191 -0
- package/dist/pi-claude-cli/src/__tests__/event-bridge.test.ts +1244 -0
- package/dist/pi-claude-cli/src/__tests__/mcp-config.test.ts +272 -0
- package/dist/pi-claude-cli/src/__tests__/process-manager.test.ts +619 -0
- package/dist/pi-claude-cli/src/__tests__/prompt-builder.test.ts +1067 -0
- package/dist/pi-claude-cli/src/__tests__/provider.test.ts +1902 -0
- package/dist/pi-claude-cli/src/__tests__/stream-parser.test.ts +188 -0
- package/dist/pi-claude-cli/src/__tests__/thinking-config.test.ts +141 -0
- package/dist/pi-claude-cli/src/__tests__/tool-mapping.test.ts +252 -0
- package/package.json +11 -5
- package/dist/client/assets/index-BuenKJX0.css +0 -1
- package/dist/client/assets/index-CjGu8HRV.js +0 -1250
package/dist/bin.js
CHANGED
|
@@ -61,6 +61,7 @@ var init_settings_schema = __esm({
|
|
|
61
61
|
defaultThinkingLevel: void 0,
|
|
62
62
|
ntfyEnabled: false,
|
|
63
63
|
ntfyTopic: void 0,
|
|
64
|
+
ntfyBaseUrl: void 0,
|
|
64
65
|
ntfyEvents: ["in-review", "merged", "failed", "awaiting-approval", "awaiting-user-review", "planning-awaiting-input"],
|
|
65
66
|
ntfyDashboardHost: void 0,
|
|
66
67
|
defaultProjectId: void 0,
|
|
@@ -18902,7 +18903,7 @@ var require_luxon = __commonJS({
|
|
|
18902
18903
|
if (this.rtf) {
|
|
18903
18904
|
return this.rtf.format(count, unit);
|
|
18904
18905
|
} else {
|
|
18905
|
-
return
|
|
18906
|
+
return formatRelativeTime2(unit, count, this.opts.numeric, this.opts.style !== "long");
|
|
18906
18907
|
}
|
|
18907
18908
|
}
|
|
18908
18909
|
formatToParts(count, unit) {
|
|
@@ -19993,7 +19994,7 @@ var require_luxon = __commonJS({
|
|
|
19993
19994
|
function eraForDateTime(dt, length) {
|
|
19994
19995
|
return eras(length)[dt.year < 0 ? 0 : 1];
|
|
19995
19996
|
}
|
|
19996
|
-
function
|
|
19997
|
+
function formatRelativeTime2(unit, count, numeric = "always", narrow = false) {
|
|
19997
19998
|
const units = {
|
|
19998
19999
|
years: ["year", "yr."],
|
|
19999
20000
|
quarters: ["quarter", "qtr."],
|
|
@@ -51446,10 +51447,10 @@ var require_lib = __commonJS({
|
|
|
51446
51447
|
} else {
|
|
51447
51448
|
terminalCtor = require_unixTerminal().UnixTerminal;
|
|
51448
51449
|
}
|
|
51449
|
-
function
|
|
51450
|
+
function spawn8(file, args, opt) {
|
|
51450
51451
|
return new terminalCtor(file, args, opt);
|
|
51451
51452
|
}
|
|
51452
|
-
exports.spawn =
|
|
51453
|
+
exports.spawn = spawn8;
|
|
51453
51454
|
function fork2(file, args, opt) {
|
|
51454
51455
|
return new terminalCtor(file, args, opt);
|
|
51455
51456
|
}
|
|
@@ -59779,7 +59780,7 @@ var init_roadmap_routes = __esm({
|
|
|
59779
59780
|
}
|
|
59780
59781
|
});
|
|
59781
59782
|
|
|
59782
|
-
// ../engine/src/logger.
|
|
59783
|
+
// ../engine/src/logger.ts
|
|
59783
59784
|
function withSeverityMarker2(level, payload) {
|
|
59784
59785
|
return `${LOG_LEVEL_MARKER_PREFIX2}${level}${LOG_LEVEL_MARKER_SUFFIX2}${payload}`;
|
|
59785
59786
|
}
|
|
@@ -59787,13 +59788,13 @@ function createLogger2(prefix) {
|
|
|
59787
59788
|
const tag = `[${prefix}]`;
|
|
59788
59789
|
return {
|
|
59789
59790
|
log(message, ...args) {
|
|
59790
|
-
|
|
59791
|
+
console.error(withSeverityMarker2("info", `${tag} ${message}`), ...args);
|
|
59791
59792
|
},
|
|
59792
59793
|
warn(message, ...args) {
|
|
59793
|
-
|
|
59794
|
+
console.warn(withSeverityMarker2("warn", `${tag} ${message}`), ...args);
|
|
59794
59795
|
},
|
|
59795
59796
|
error(message, ...args) {
|
|
59796
|
-
|
|
59797
|
+
console.error(withSeverityMarker2("error", `${tag} ${message}`), ...args);
|
|
59797
59798
|
}
|
|
59798
59799
|
};
|
|
59799
59800
|
}
|
|
@@ -59819,7 +59820,7 @@ ${stack}` : message2;
|
|
|
59819
59820
|
}
|
|
59820
59821
|
var LOG_LEVEL_MARKER_PREFIX2, LOG_LEVEL_MARKER_SUFFIX2, schedulerLog, executorLog, triageLog, piLog, extensionsLog, mergerLog, worktreePoolLog, reviewerLog, prMonitorLog, runtimeLog, ipcLog, projectManagerLog, hybridExecutorLog, autopilotLog, heartbeatLog, remoteNodeLog, nodeHealthMonitorLog, peerExchangeLog;
|
|
59821
59822
|
var init_logger2 = __esm({
|
|
59822
|
-
"../engine/src/logger.
|
|
59823
|
+
"../engine/src/logger.ts"() {
|
|
59823
59824
|
"use strict";
|
|
59824
59825
|
LOG_LEVEL_MARKER_PREFIX2 = "\0fnlvl=";
|
|
59825
59826
|
LOG_LEVEL_MARKER_SUFFIX2 = "\0";
|
|
@@ -60978,7 +60979,7 @@ var init_concurrency = __esm({
|
|
|
60978
60979
|
}
|
|
60979
60980
|
});
|
|
60980
60981
|
|
|
60981
|
-
// ../engine/src/skill-resolver.
|
|
60982
|
+
// ../engine/src/skill-resolver.ts
|
|
60982
60983
|
import { existsSync as existsSync19, readFileSync as readFileSync5 } from "node:fs";
|
|
60983
60984
|
import { join as join25 } from "node:path";
|
|
60984
60985
|
function readJsonObject(path4) {
|
|
@@ -61102,9 +61103,13 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
61102
61103
|
let filteredSkills;
|
|
61103
61104
|
if (hasRequestedNames) {
|
|
61104
61105
|
const requestedNamesLower = new Set(requestedSkillNames.map((n) => n.toLowerCase()));
|
|
61105
|
-
filteredSkills = base.skills.filter(
|
|
61106
|
+
filteredSkills = base.skills.filter(
|
|
61107
|
+
(skill) => requestedNamesLower.has(skill.name.toLowerCase()) && !excludedSkillPaths.has(skill.filePath)
|
|
61108
|
+
);
|
|
61106
61109
|
} else if (hasPatterns) {
|
|
61107
|
-
filteredSkills = base.skills.filter(
|
|
61110
|
+
filteredSkills = base.skills.filter(
|
|
61111
|
+
(skill) => allowedSkillPaths.has(skill.filePath) && !excludedSkillPaths.has(skill.filePath)
|
|
61112
|
+
);
|
|
61108
61113
|
} else if (hasExcluded) {
|
|
61109
61114
|
filteredSkills = base.skills.filter((skill) => !excludedSkillPaths.has(skill.filePath));
|
|
61110
61115
|
} else {
|
|
@@ -61144,6 +61149,7 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
61144
61149
|
}
|
|
61145
61150
|
}
|
|
61146
61151
|
if (newDiagnostics.length > 0) {
|
|
61152
|
+
const _purpose = sessionPurpose ? `[${sessionPurpose}]` : "skills";
|
|
61147
61153
|
for (const diag of newDiagnostics) {
|
|
61148
61154
|
piLog.warn(`[skills] ${diag.type}: ${diag.message}`);
|
|
61149
61155
|
}
|
|
@@ -61155,21 +61161,20 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
61155
61161
|
};
|
|
61156
61162
|
}
|
|
61157
61163
|
var init_skill_resolver = __esm({
|
|
61158
|
-
"../engine/src/skill-resolver.
|
|
61164
|
+
"../engine/src/skill-resolver.ts"() {
|
|
61159
61165
|
"use strict";
|
|
61160
61166
|
init_logger2();
|
|
61161
61167
|
}
|
|
61162
61168
|
});
|
|
61163
61169
|
|
|
61164
|
-
// ../engine/src/context-limit-detector.
|
|
61170
|
+
// ../engine/src/context-limit-detector.ts
|
|
61165
61171
|
function isContextLimitError(message) {
|
|
61166
|
-
if (!message)
|
|
61167
|
-
return false;
|
|
61172
|
+
if (!message) return false;
|
|
61168
61173
|
return CONTEXT_OVERFLOW_PATTERNS.some((pattern) => pattern.test(message));
|
|
61169
61174
|
}
|
|
61170
61175
|
var CONTEXT_OVERFLOW_PATTERNS;
|
|
61171
61176
|
var init_context_limit_detector = __esm({
|
|
61172
|
-
"../engine/src/context-limit-detector.
|
|
61177
|
+
"../engine/src/context-limit-detector.ts"() {
|
|
61173
61178
|
"use strict";
|
|
61174
61179
|
CONTEXT_OVERFLOW_PATTERNS = [
|
|
61175
61180
|
// Anthropic: "prompt is too long: X tokens > Y maximum"
|
|
@@ -61206,14 +61211,14 @@ var init_context_limit_detector = __esm({
|
|
|
61206
61211
|
}
|
|
61207
61212
|
});
|
|
61208
61213
|
|
|
61209
|
-
// ../engine/src/auth-storage.
|
|
61214
|
+
// ../engine/src/auth-storage.ts
|
|
61210
61215
|
import { existsSync as existsSync20, readFileSync as readFileSync6 } from "node:fs";
|
|
61211
61216
|
import { homedir as homedir5 } from "node:os";
|
|
61212
61217
|
import { join as join26 } from "node:path";
|
|
61213
61218
|
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
61214
61219
|
import { getOAuthProvider } from "@mariozechner/pi-ai/oauth";
|
|
61215
61220
|
function getHomeDir2() {
|
|
61216
|
-
return
|
|
61221
|
+
return process.env.HOME || process.env.USERPROFILE || homedir5();
|
|
61217
61222
|
}
|
|
61218
61223
|
function getFusionAuthPath2(home = getHomeDir2()) {
|
|
61219
61224
|
return join26(home, ".fusion", "agent", "auth.json");
|
|
@@ -61257,9 +61262,8 @@ function readLegacyCredentials(authPaths = getLegacyAuthPaths()) {
|
|
|
61257
61262
|
return credentials;
|
|
61258
61263
|
}
|
|
61259
61264
|
function resolveStoredApiKey(key) {
|
|
61260
|
-
if (!key)
|
|
61261
|
-
|
|
61262
|
-
return globalThis.process.env[key] ?? key;
|
|
61265
|
+
if (!key) return void 0;
|
|
61266
|
+
return process.env[key] ?? key;
|
|
61263
61267
|
}
|
|
61264
61268
|
function resolveOAuthApiKey(providerId, credential) {
|
|
61265
61269
|
if (credential.type !== "oauth" || typeof credential.access !== "string" || typeof credential.refresh !== "string" || typeof credential.expires !== "number" || Date.now() >= credential.expires) {
|
|
@@ -61305,8 +61309,7 @@ function createFusionAuthStorage() {
|
|
|
61305
61309
|
if (prop === "getApiKey") {
|
|
61306
61310
|
return async (provider) => {
|
|
61307
61311
|
const primaryKey = await target.getApiKey(provider);
|
|
61308
|
-
if (primaryKey)
|
|
61309
|
-
return primaryKey;
|
|
61312
|
+
if (primaryKey) return primaryKey;
|
|
61310
61313
|
return resolveStoredCredentialApiKey(provider, legacyCredentials[provider]);
|
|
61311
61314
|
};
|
|
61312
61315
|
}
|
|
@@ -61315,12 +61318,12 @@ function createFusionAuthStorage() {
|
|
|
61315
61318
|
});
|
|
61316
61319
|
}
|
|
61317
61320
|
var init_auth_storage = __esm({
|
|
61318
|
-
"../engine/src/auth-storage.
|
|
61321
|
+
"../engine/src/auth-storage.ts"() {
|
|
61319
61322
|
"use strict";
|
|
61320
61323
|
}
|
|
61321
61324
|
});
|
|
61322
61325
|
|
|
61323
|
-
// ../engine/src/pi.
|
|
61326
|
+
// ../engine/src/pi.ts
|
|
61324
61327
|
var pi_exports = {};
|
|
61325
61328
|
__export(pi_exports, {
|
|
61326
61329
|
COMPACTION_FALLBACK_INSTRUCTIONS: () => COMPACTION_FALLBACK_INSTRUCTIONS,
|
|
@@ -61334,19 +61337,35 @@ import { existsSync as existsSync21, readFileSync as readFileSync7 } from "node:
|
|
|
61334
61337
|
import { exec } from "node:child_process";
|
|
61335
61338
|
import { promisify as promisify2 } from "node:util";
|
|
61336
61339
|
import { basename as basename9, dirname as dirname9, join as join27, relative as relative4, isAbsolute as isAbsolute6, resolve as resolve12 } from "node:path";
|
|
61337
|
-
import {
|
|
61340
|
+
import {
|
|
61341
|
+
createAgentSession,
|
|
61342
|
+
createCodingTools,
|
|
61343
|
+
createExtensionRuntime,
|
|
61344
|
+
createReadOnlyTools,
|
|
61345
|
+
DefaultResourceLoader,
|
|
61346
|
+
DefaultPackageManager,
|
|
61347
|
+
discoverAndLoadExtensions,
|
|
61348
|
+
ModelRegistry,
|
|
61349
|
+
SessionManager,
|
|
61350
|
+
SettingsManager
|
|
61351
|
+
} from "@mariozechner/pi-coding-agent";
|
|
61338
61352
|
function getSessionStateError(session) {
|
|
61339
|
-
const
|
|
61353
|
+
const state = session.state;
|
|
61354
|
+
const error = state?.errorMessage ?? state?.error;
|
|
61340
61355
|
return typeof error === "string" ? error : "";
|
|
61341
61356
|
}
|
|
61342
61357
|
function clearSessionStateError(session) {
|
|
61343
61358
|
const state = session.state;
|
|
61344
|
-
if (!state || typeof state !== "object"
|
|
61359
|
+
if (!state || typeof state !== "object") {
|
|
61345
61360
|
return;
|
|
61346
61361
|
}
|
|
61347
|
-
|
|
61348
|
-
|
|
61349
|
-
|
|
61362
|
+
for (const key of ["errorMessage", "error"]) {
|
|
61363
|
+
if (key in state) {
|
|
61364
|
+
try {
|
|
61365
|
+
state[key] = void 0;
|
|
61366
|
+
} catch {
|
|
61367
|
+
}
|
|
61368
|
+
}
|
|
61350
61369
|
}
|
|
61351
61370
|
}
|
|
61352
61371
|
async function promptSessionAndCheck(session, prompt, options) {
|
|
@@ -61358,6 +61377,29 @@ async function promptSessionAndCheck(session, prompt, options) {
|
|
|
61358
61377
|
}
|
|
61359
61378
|
const stateError = getSessionStateError(session);
|
|
61360
61379
|
if (stateError) {
|
|
61380
|
+
if (/Cannot read propert(y|ies) of (undefined|null)/i.test(stateError)) {
|
|
61381
|
+
try {
|
|
61382
|
+
const messages = session.agent?.state?.messages ?? session.state?.messages;
|
|
61383
|
+
if (Array.isArray(messages)) {
|
|
61384
|
+
const recent = messages.slice(-6).map((m, idx) => {
|
|
61385
|
+
const i = messages.length - 6 + idx;
|
|
61386
|
+
const content = m?.content;
|
|
61387
|
+
return {
|
|
61388
|
+
index: i < 0 ? idx : i,
|
|
61389
|
+
role: m?.role,
|
|
61390
|
+
contentType: Array.isArray(content) ? `array(len=${content.length})` : typeof content,
|
|
61391
|
+
toolName: m.toolName,
|
|
61392
|
+
stopReason: m.stopReason
|
|
61393
|
+
};
|
|
61394
|
+
});
|
|
61395
|
+
piLog.error(`pi state error \u2014 transcript tail (${messages.length} msgs total): ${JSON.stringify(recent)}`);
|
|
61396
|
+
} else {
|
|
61397
|
+
piLog.error(`pi state error \u2014 state.messages is not an array: ${typeof messages}`);
|
|
61398
|
+
}
|
|
61399
|
+
} catch (inspectErr) {
|
|
61400
|
+
piLog.warn(`pi state error \u2014 failed to inspect transcript: ${inspectErr instanceof Error ? inspectErr.message : String(inspectErr)}`);
|
|
61401
|
+
}
|
|
61402
|
+
}
|
|
61361
61403
|
throw new Error(stateError);
|
|
61362
61404
|
}
|
|
61363
61405
|
}
|
|
@@ -61409,8 +61451,7 @@ async function promptWithFallback(session, prompt, options) {
|
|
|
61409
61451
|
}
|
|
61410
61452
|
function describeModel(session) {
|
|
61411
61453
|
const model = session.model;
|
|
61412
|
-
if (!model)
|
|
61413
|
-
return "unknown model";
|
|
61454
|
+
if (!model) return "unknown model";
|
|
61414
61455
|
return `${model.provider}/${model.id}`;
|
|
61415
61456
|
}
|
|
61416
61457
|
function compactMarkdownMemorySection(sectionBody) {
|
|
@@ -61463,7 +61504,9 @@ async function retryWithCompactedPromptMemory(session, prompt, options) {
|
|
|
61463
61504
|
if (!compactedPrompt) {
|
|
61464
61505
|
return { recovered: false };
|
|
61465
61506
|
}
|
|
61466
|
-
piLog.log(
|
|
61507
|
+
piLog.log(
|
|
61508
|
+
`promptWithFallback: retrying with compacted prompt memory (${prompt.length} \u2192 ${compactedPrompt.length} chars)`
|
|
61509
|
+
);
|
|
61467
61510
|
try {
|
|
61468
61511
|
await promptSessionAndCheck(session, compactedPrompt, options);
|
|
61469
61512
|
piLog.log("promptWithFallback: prompt completed after prompt-memory compaction");
|
|
@@ -61480,7 +61523,7 @@ async function flushMemoryBeforeSessionCompaction(session) {
|
|
|
61480
61523
|
}
|
|
61481
61524
|
const flushPrompt = [
|
|
61482
61525
|
"Before context compaction, preserve only unresolved durable memory if needed.",
|
|
61483
|
-
"If
|
|
61526
|
+
"If fn_memory_append is available and you learned reusable project decisions, conventions, pitfalls, or open loops that are not already saved, append them now.",
|
|
61484
61527
|
'Use layer="long-term" for durable facts and layer="daily" for running notes/open loops.',
|
|
61485
61528
|
"If there is nothing durable to save, reply exactly: NONE."
|
|
61486
61529
|
].join("\n");
|
|
@@ -61525,7 +61568,9 @@ function resolveConfiguredModel(modelRegistry, kind, provider, modelId) {
|
|
|
61525
61568
|
piLog.warn(`${kind} model ${provider}/${modelId} not in registry; using provider base model as template`);
|
|
61526
61569
|
return { ...baseModel, id: modelId, name: modelId };
|
|
61527
61570
|
}
|
|
61528
|
-
throw new Error(
|
|
61571
|
+
throw new Error(
|
|
61572
|
+
`Configured ${kind} model ${provider}/${modelId} was not found in the pi model registry. Open Settings and choose a model from /api/models, or update your pi model configuration.`
|
|
61573
|
+
);
|
|
61529
61574
|
}
|
|
61530
61575
|
function isRetryableModelSelectionError(message) {
|
|
61531
61576
|
const normalized = message.toLowerCase();
|
|
@@ -61570,11 +61615,18 @@ function normalizeAssistantOrToolResultMessage(message) {
|
|
|
61570
61615
|
return false;
|
|
61571
61616
|
}
|
|
61572
61617
|
const role = message.role;
|
|
61573
|
-
if (role !== "assistant" && role !== "toolResult") {
|
|
61618
|
+
if (role !== "assistant" && role !== "toolResult" && role !== "user") {
|
|
61574
61619
|
return false;
|
|
61575
61620
|
}
|
|
61576
|
-
|
|
61577
|
-
|
|
61621
|
+
const obj = message;
|
|
61622
|
+
if (role === "user") {
|
|
61623
|
+
if (typeof obj.content !== "string" && !Array.isArray(obj.content)) {
|
|
61624
|
+
obj.content = [];
|
|
61625
|
+
}
|
|
61626
|
+
return true;
|
|
61627
|
+
}
|
|
61628
|
+
if (!Array.isArray(obj.content)) {
|
|
61629
|
+
obj.content = [];
|
|
61578
61630
|
}
|
|
61579
61631
|
return true;
|
|
61580
61632
|
}
|
|
@@ -61631,6 +61683,12 @@ function installMessageContentGuard(session, sessionManager) {
|
|
|
61631
61683
|
if (session.__fusionMessageContentGuardInstalled) {
|
|
61632
61684
|
return;
|
|
61633
61685
|
}
|
|
61686
|
+
const existingMessages = session.agent?.state?.messages;
|
|
61687
|
+
if (Array.isArray(existingMessages)) {
|
|
61688
|
+
for (const candidate of existingMessages) {
|
|
61689
|
+
normalizeAssistantOrToolResultMessage(candidate);
|
|
61690
|
+
}
|
|
61691
|
+
}
|
|
61634
61692
|
if (typeof session.subscribe === "function") {
|
|
61635
61693
|
session.subscribe((event) => {
|
|
61636
61694
|
if (!event || typeof event !== "object" || event.type !== "message_end") {
|
|
@@ -61673,8 +61731,8 @@ function createReadOnlyPiSettingsView(cwd, agentDir) {
|
|
|
61673
61731
|
const fusionProjectSettings = readJsonObject2(join27(projectRoot, ".fusion", "settings.json"));
|
|
61674
61732
|
const mergedSettings = { ...globalSettings, ...fusionProjectSettings };
|
|
61675
61733
|
return {
|
|
61676
|
-
getGlobalSettings: () =>
|
|
61677
|
-
getProjectSettings: () =>
|
|
61734
|
+
getGlobalSettings: () => structuredClone(globalSettings),
|
|
61735
|
+
getProjectSettings: () => structuredClone(fusionProjectSettings),
|
|
61678
61736
|
getNpmCommand: () => Array.isArray(mergedSettings.npmCommand) ? [...mergedSettings.npmCommand] : void 0
|
|
61679
61737
|
};
|
|
61680
61738
|
}
|
|
@@ -61701,7 +61759,11 @@ async function registerExtensionProviders(cwd, modelRegistry) {
|
|
|
61701
61759
|
});
|
|
61702
61760
|
const resolvedPaths = await packageManager.resolve();
|
|
61703
61761
|
const packageExtensionPaths = resolvedPaths.extensions.filter((resource) => resource.enabled).map((resource) => resource.path);
|
|
61704
|
-
const extensionsResult = await discoverAndLoadExtensions(
|
|
61762
|
+
const extensionsResult = await discoverAndLoadExtensions(
|
|
61763
|
+
[...getEnabledPiExtensionPaths(cwd), ...packageExtensionPaths],
|
|
61764
|
+
cwd,
|
|
61765
|
+
join27(resolvePiExtensionProjectRoot(cwd), ".fusion", "disabled-auto-extension-discovery")
|
|
61766
|
+
);
|
|
61705
61767
|
for (const { path: path4, error } of extensionsResult.errors) {
|
|
61706
61768
|
extensionsLog.warn(`Failed to load ${path4}: ${error}`);
|
|
61707
61769
|
}
|
|
@@ -61736,7 +61798,9 @@ async function isRegisteredGitWorktree(projectRoot, worktreePath) {
|
|
|
61736
61798
|
encoding: "utf-8"
|
|
61737
61799
|
});
|
|
61738
61800
|
const resolvedWorktree = resolve12(worktreePath);
|
|
61739
|
-
return stdout.split("\n").some(
|
|
61801
|
+
return stdout.split("\n").some(
|
|
61802
|
+
(line) => line.startsWith("worktree ") && resolve12(line.slice("worktree ".length)) === resolvedWorktree
|
|
61803
|
+
);
|
|
61740
61804
|
} catch {
|
|
61741
61805
|
return false;
|
|
61742
61806
|
}
|
|
@@ -61763,7 +61827,7 @@ async function assertValidWorktreeSession(cwd, projectRoot) {
|
|
|
61763
61827
|
throw new Error(`Refusing to start coding agent in unregistered git worktree: ${cwd}`);
|
|
61764
61828
|
}
|
|
61765
61829
|
}
|
|
61766
|
-
function isWorktreeAllowedPath(worktreePath, projectRoot, requestedPath) {
|
|
61830
|
+
function isWorktreeAllowedPath(worktreePath, projectRoot, requestedPath, toolName) {
|
|
61767
61831
|
const worktreeResolved = resolve12(worktreePath);
|
|
61768
61832
|
const projectRootResolved = resolve12(projectRoot);
|
|
61769
61833
|
const requestedResolved = isAbsolute6(requestedPath) ? resolve12(requestedPath) : resolve12(worktreeResolved, requestedPath);
|
|
@@ -61778,8 +61842,20 @@ function isWorktreeAllowedPath(worktreePath, projectRoot, requestedPath) {
|
|
|
61778
61842
|
if (relToProjectRoot.match(/^\.fusion\/tasks\/[^/]+\/attachments\//)) {
|
|
61779
61843
|
return true;
|
|
61780
61844
|
}
|
|
61845
|
+
const readOnlyTools = /* @__PURE__ */ new Set(["read", "glob", "grep"]);
|
|
61846
|
+
if (toolName && readOnlyTools.has(toolName) && /^\.fusion\/tasks\/[^/]+\/(PROMPT\.md|task\.json)$/.test(relToProjectRoot)) {
|
|
61847
|
+
return true;
|
|
61848
|
+
}
|
|
61781
61849
|
return false;
|
|
61782
61850
|
}
|
|
61851
|
+
function boundaryRejection(message) {
|
|
61852
|
+
return {
|
|
61853
|
+
content: [{ type: "text", text: message }],
|
|
61854
|
+
isError: true,
|
|
61855
|
+
ok: false,
|
|
61856
|
+
error: message
|
|
61857
|
+
};
|
|
61858
|
+
}
|
|
61783
61859
|
function wrapToolsWithBoundary(tools, worktreePath, projectRoot) {
|
|
61784
61860
|
if (!worktreePath || !projectRoot) {
|
|
61785
61861
|
return tools;
|
|
@@ -61793,21 +61869,21 @@ function wrapToolsWithBoundary(tools, worktreePath, projectRoot) {
|
|
|
61793
61869
|
return {
|
|
61794
61870
|
...tool,
|
|
61795
61871
|
execute: async (...args) => {
|
|
61872
|
+
const _toolCallId = args[0];
|
|
61796
61873
|
const params = args[1];
|
|
61874
|
+
const _signal = args[2];
|
|
61797
61875
|
const pathArg = params.path;
|
|
61798
|
-
if (pathArg && !isWorktreeAllowedPath(worktreePath, projectRoot, pathArg)) {
|
|
61876
|
+
if (pathArg && !isWorktreeAllowedPath(worktreePath, projectRoot, pathArg, tool.name)) {
|
|
61799
61877
|
const relToProject = relative4(projectRoot, pathArg);
|
|
61800
|
-
return
|
|
61801
|
-
|
|
61802
|
-
|
|
61803
|
-
};
|
|
61878
|
+
return boundaryRejection(
|
|
61879
|
+
`Path "${relToProject}" is outside the worktree boundary. Coding agents can only modify files inside the current worktree. Exceptions (read-only): .fusion/memory/, .fusion/tasks/*/attachments/, and .fusion/tasks/*/{PROMPT.md,task.json} for dependency context.`
|
|
61880
|
+
);
|
|
61804
61881
|
}
|
|
61805
61882
|
const cwdArg = params.cwd;
|
|
61806
|
-
if (tool.name === "bash" && cwdArg && !isWorktreeAllowedPath(worktreePath, projectRoot, cwdArg)) {
|
|
61807
|
-
return
|
|
61808
|
-
|
|
61809
|
-
|
|
61810
|
-
};
|
|
61883
|
+
if (tool.name === "bash" && cwdArg && !isWorktreeAllowedPath(worktreePath, projectRoot, cwdArg, tool.name)) {
|
|
61884
|
+
return boundaryRejection(
|
|
61885
|
+
`Working directory is outside the worktree boundary. Commands must run inside the worktree.`
|
|
61886
|
+
);
|
|
61811
61887
|
}
|
|
61812
61888
|
return originalExecute(...args);
|
|
61813
61889
|
}
|
|
@@ -61817,7 +61893,7 @@ function wrapToolsWithBoundary(tools, worktreePath, projectRoot) {
|
|
|
61817
61893
|
async function createFnAgent5(options) {
|
|
61818
61894
|
piLog.log(`createFnAgent called (cwd=${options.cwd}, tools=${options.tools}, provider=${options.defaultProvider}, model=${options.defaultModelId})`);
|
|
61819
61895
|
const authStorage = createFusionAuthStorage();
|
|
61820
|
-
const modelRegistry =
|
|
61896
|
+
const modelRegistry = ModelRegistry.create(authStorage, getModelRegistryModelsPath());
|
|
61821
61897
|
await registerExtensionProviders(options.cwd, modelRegistry);
|
|
61822
61898
|
const tools = options.tools === "readonly" ? createReadOnlyTools(options.cwd) : createCodingTools(options.cwd);
|
|
61823
61899
|
const worktreePath = options.cwd;
|
|
@@ -61830,8 +61906,18 @@ async function createFnAgent5(options) {
|
|
|
61830
61906
|
compaction: { enabled: true },
|
|
61831
61907
|
retry: { enabled: true, maxRetries: 3 }
|
|
61832
61908
|
});
|
|
61833
|
-
const selectedModel = resolveConfiguredModel(
|
|
61834
|
-
|
|
61909
|
+
const selectedModel = resolveConfiguredModel(
|
|
61910
|
+
modelRegistry,
|
|
61911
|
+
"primary",
|
|
61912
|
+
options.defaultProvider,
|
|
61913
|
+
options.defaultModelId
|
|
61914
|
+
);
|
|
61915
|
+
const fallbackModel = resolveConfiguredModel(
|
|
61916
|
+
modelRegistry,
|
|
61917
|
+
"fallback",
|
|
61918
|
+
options.fallbackProvider,
|
|
61919
|
+
options.fallbackModelId
|
|
61920
|
+
);
|
|
61835
61921
|
let effectiveSkillSelection = options.skillSelection;
|
|
61836
61922
|
if (!effectiveSkillSelection && options.skills && options.skills.length > 0) {
|
|
61837
61923
|
piLog.log(`Using skills from convenience parameter: [${options.skills.join(", ")}]`);
|
|
@@ -61857,6 +61943,7 @@ async function createFnAgent5(options) {
|
|
|
61857
61943
|
}
|
|
61858
61944
|
const resourceLoader = new DefaultResourceLoader({
|
|
61859
61945
|
cwd: options.cwd,
|
|
61946
|
+
agentDir: getFusionAgentDir(),
|
|
61860
61947
|
settingsManager,
|
|
61861
61948
|
systemPromptOverride: () => options.systemPrompt,
|
|
61862
61949
|
appendSystemPromptOverride: () => [],
|
|
@@ -61866,13 +61953,17 @@ async function createFnAgent5(options) {
|
|
|
61866
61953
|
const sessionManager = options.sessionManager ?? SessionManager.inMemory();
|
|
61867
61954
|
normalizeSessionHistoryEntries(sessionManager);
|
|
61868
61955
|
const createSessionWithModel = async (modelOverride) => {
|
|
61956
|
+
const customToolList = [
|
|
61957
|
+
...wrappedTools,
|
|
61958
|
+
...options.customTools ?? []
|
|
61959
|
+
];
|
|
61869
61960
|
return createAgentSession({
|
|
61870
61961
|
cwd: options.cwd,
|
|
61871
61962
|
authStorage,
|
|
61872
61963
|
modelRegistry,
|
|
61873
61964
|
resourceLoader,
|
|
61874
|
-
|
|
61875
|
-
customTools:
|
|
61965
|
+
noTools: "builtin",
|
|
61966
|
+
customTools: customToolList,
|
|
61876
61967
|
sessionManager,
|
|
61877
61968
|
settingsManager,
|
|
61878
61969
|
...modelOverride ? { model: modelOverride } : {}
|
|
@@ -61896,7 +61987,7 @@ async function createFnAgent5(options) {
|
|
|
61896
61987
|
const { session } = sessionResult;
|
|
61897
61988
|
installToolResultContentGuard(session);
|
|
61898
61989
|
installMessageContentGuard(session, sessionManager);
|
|
61899
|
-
session.__fusionMemoryAppendAvailable = options.customTools?.some((tool) => tool.name ===
|
|
61990
|
+
session.__fusionMemoryAppendAvailable = options.customTools?.some((tool) => tool.name === FN_MEMORY_APPEND_TOOL_NAME) === true;
|
|
61900
61991
|
const promptableSession = session;
|
|
61901
61992
|
promptableSession.promptWithFallback = async (prompt, promptOptions) => {
|
|
61902
61993
|
try {
|
|
@@ -61946,8 +62037,11 @@ async function createFnAgent5(options) {
|
|
|
61946
62037
|
const fallbackSessionResult = await createSessionWithModel(fallbackModel);
|
|
61947
62038
|
const fallbackSession = fallbackSessionResult.session;
|
|
61948
62039
|
installToolResultContentGuard(fallbackSession);
|
|
61949
|
-
installMessageContentGuard(
|
|
61950
|
-
|
|
62040
|
+
installMessageContentGuard(
|
|
62041
|
+
fallbackSession,
|
|
62042
|
+
sessionManager
|
|
62043
|
+
);
|
|
62044
|
+
fallbackSession.__fusionMemoryAppendAvailable = options.customTools?.some((tool) => tool.name === FN_MEMORY_APPEND_TOOL_NAME) === true;
|
|
61951
62045
|
if (options.defaultThinkingLevel) {
|
|
61952
62046
|
fallbackSession.setThinkingLevel(options.defaultThinkingLevel);
|
|
61953
62047
|
}
|
|
@@ -62029,9 +62123,9 @@ async function createFnAgent5(options) {
|
|
|
62029
62123
|
});
|
|
62030
62124
|
return { session: promptableSession, sessionFile: promptableSession.sessionFile };
|
|
62031
62125
|
}
|
|
62032
|
-
var execAsync, COMPACTION_FALLBACK_INSTRUCTIONS, MAX_COMPACTED_PROMPT_MEMORY_CHARS;
|
|
62126
|
+
var execAsync, FN_MEMORY_APPEND_TOOL_NAME, COMPACTION_FALLBACK_INSTRUCTIONS, MAX_COMPACTED_PROMPT_MEMORY_CHARS;
|
|
62033
62127
|
var init_pi = __esm({
|
|
62034
|
-
"../engine/src/pi.
|
|
62128
|
+
"../engine/src/pi.ts"() {
|
|
62035
62129
|
"use strict";
|
|
62036
62130
|
init_src();
|
|
62037
62131
|
init_skill_resolver();
|
|
@@ -62039,6 +62133,7 @@ var init_pi = __esm({
|
|
|
62039
62133
|
init_auth_storage();
|
|
62040
62134
|
init_logger2();
|
|
62041
62135
|
execAsync = promisify2(exec);
|
|
62136
|
+
FN_MEMORY_APPEND_TOOL_NAME = "fn_memory_append";
|
|
62042
62137
|
COMPACTION_FALLBACK_INSTRUCTIONS = [
|
|
62043
62138
|
"Summarize all completed steps concisely.",
|
|
62044
62139
|
"Preserve the current step number and any in-progress work details.",
|
|
@@ -75492,6 +75587,13 @@ function formatTaskIdentifier(task) {
|
|
|
75492
75587
|
const snippet = task.description.length > maxLen ? task.description.slice(0, maxLen) + "..." : task.description;
|
|
75493
75588
|
return `${task.id}: ${snippet}`;
|
|
75494
75589
|
}
|
|
75590
|
+
function resolveNtfyBaseUrl(baseUrl, fallback2 = DEFAULT_NTFY_BASE_URL) {
|
|
75591
|
+
const trimmed = baseUrl?.trim();
|
|
75592
|
+
if (!trimmed) {
|
|
75593
|
+
return fallback2;
|
|
75594
|
+
}
|
|
75595
|
+
return trimmed.replace(/\/+$/, "");
|
|
75596
|
+
}
|
|
75495
75597
|
function resolveNtfyEvents(events) {
|
|
75496
75598
|
return events ? [...events] : [...DEFAULT_NTFY_EVENTS];
|
|
75497
75599
|
}
|
|
@@ -75515,7 +75617,7 @@ function buildNtfyClickUrl(options) {
|
|
|
75515
75617
|
return query ? `${normalizedHost}/?${query}` : `${normalizedHost}/`;
|
|
75516
75618
|
}
|
|
75517
75619
|
async function sendNtfyNotification({
|
|
75518
|
-
ntfyBaseUrl
|
|
75620
|
+
ntfyBaseUrl,
|
|
75519
75621
|
topic,
|
|
75520
75622
|
title,
|
|
75521
75623
|
message,
|
|
@@ -75532,7 +75634,8 @@ async function sendNtfyNotification({
|
|
|
75532
75634
|
if (clickUrl) {
|
|
75533
75635
|
headers.Click = clickUrl;
|
|
75534
75636
|
}
|
|
75535
|
-
const
|
|
75637
|
+
const resolvedBaseUrl = resolveNtfyBaseUrl(ntfyBaseUrl);
|
|
75638
|
+
const response = await fetch(`${resolvedBaseUrl}/${topic}`, {
|
|
75536
75639
|
method: "POST",
|
|
75537
75640
|
headers,
|
|
75538
75641
|
body: message,
|
|
@@ -75548,11 +75651,12 @@ async function sendNtfyNotification({
|
|
|
75548
75651
|
schedulerLog.log(`Failed to send ntfy notification: ${err}`);
|
|
75549
75652
|
}
|
|
75550
75653
|
}
|
|
75551
|
-
var DEFAULT_NTFY_EVENTS, NtfyNotifier;
|
|
75654
|
+
var DEFAULT_NTFY_BASE_URL, DEFAULT_NTFY_EVENTS, NtfyNotifier;
|
|
75552
75655
|
var init_notifier = __esm({
|
|
75553
75656
|
"../engine/src/notifier.ts"() {
|
|
75554
75657
|
"use strict";
|
|
75555
75658
|
init_logger2();
|
|
75659
|
+
DEFAULT_NTFY_BASE_URL = "https://ntfy.sh";
|
|
75556
75660
|
DEFAULT_NTFY_EVENTS = [
|
|
75557
75661
|
"in-review",
|
|
75558
75662
|
"merged",
|
|
@@ -75564,7 +75668,8 @@ var init_notifier = __esm({
|
|
|
75564
75668
|
NtfyNotifier = class {
|
|
75565
75669
|
constructor(store, options = {}) {
|
|
75566
75670
|
this.store = store;
|
|
75567
|
-
this.
|
|
75671
|
+
this.defaultNtfyBaseUrl = resolveNtfyBaseUrl(options.ntfyBaseUrl);
|
|
75672
|
+
this.ntfyBaseUrl = this.defaultNtfyBaseUrl;
|
|
75568
75673
|
this.projectId = options.projectId;
|
|
75569
75674
|
}
|
|
75570
75675
|
config = {
|
|
@@ -75574,6 +75679,7 @@ var init_notifier = __esm({
|
|
|
75574
75679
|
events: [...DEFAULT_NTFY_EVENTS]
|
|
75575
75680
|
};
|
|
75576
75681
|
ntfyBaseUrl;
|
|
75682
|
+
defaultNtfyBaseUrl;
|
|
75577
75683
|
projectId;
|
|
75578
75684
|
notifiedEvents = /* @__PURE__ */ new Set();
|
|
75579
75685
|
abortController = null;
|
|
@@ -75712,7 +75818,7 @@ var init_notifier = __esm({
|
|
|
75712
75818
|
};
|
|
75713
75819
|
handleSettingsUpdated = (data) => {
|
|
75714
75820
|
const { settings, previous } = data;
|
|
75715
|
-
if (settings.ntfyEnabled !== previous.ntfyEnabled || settings.ntfyTopic !== previous.ntfyTopic || settings.ntfyDashboardHost !== previous.ntfyDashboardHost || JSON.stringify(settings.ntfyEvents) !== JSON.stringify(previous.ntfyEvents)) {
|
|
75821
|
+
if (settings.ntfyEnabled !== previous.ntfyEnabled || settings.ntfyTopic !== previous.ntfyTopic || settings.ntfyBaseUrl !== previous.ntfyBaseUrl || settings.ntfyDashboardHost !== previous.ntfyDashboardHost || JSON.stringify(settings.ntfyEvents) !== JSON.stringify(previous.ntfyEvents)) {
|
|
75716
75822
|
const wasEnabled = this.config.enabled;
|
|
75717
75823
|
this.loadConfig(settings);
|
|
75718
75824
|
if (this.config.enabled && !wasEnabled) {
|
|
@@ -75721,6 +75827,8 @@ var init_notifier = __esm({
|
|
|
75721
75827
|
schedulerLog.log("NtfyNotifier disabled");
|
|
75722
75828
|
} else if (this.config.topic !== previous.ntfyTopic) {
|
|
75723
75829
|
schedulerLog.log("NtfyNotifier topic updated");
|
|
75830
|
+
} else if (this.ntfyBaseUrl !== resolveNtfyBaseUrl(previous.ntfyBaseUrl)) {
|
|
75831
|
+
schedulerLog.log("NtfyNotifier base URL updated");
|
|
75724
75832
|
} else if (this.config.dashboardHost !== previous.ntfyDashboardHost) {
|
|
75725
75833
|
schedulerLog.log("NtfyNotifier dashboard host updated");
|
|
75726
75834
|
} else if (JSON.stringify(this.config.events) !== JSON.stringify(previous.ntfyEvents)) {
|
|
@@ -75735,6 +75843,7 @@ var init_notifier = __esm({
|
|
|
75735
75843
|
dashboardHost: settings.ntfyDashboardHost,
|
|
75736
75844
|
events: resolveNtfyEvents(settings.ntfyEvents)
|
|
75737
75845
|
};
|
|
75846
|
+
this.ntfyBaseUrl = resolveNtfyBaseUrl(settings.ntfyBaseUrl, this.defaultNtfyBaseUrl);
|
|
75738
75847
|
}
|
|
75739
75848
|
isEventEnabled(event) {
|
|
75740
75849
|
return isNtfyEventEnabled(this.config.events, event);
|
|
@@ -84390,6 +84499,7 @@ __export(src_exports2, {
|
|
|
84390
84499
|
createTaskDocumentWriteTool: () => createTaskDocumentWriteTool,
|
|
84391
84500
|
createTaskLogTool: () => createTaskLogTool,
|
|
84392
84501
|
describeAgentModel: () => describeAgentModel,
|
|
84502
|
+
describeModel: () => describeModel,
|
|
84393
84503
|
getDefaultPiRuntime: () => getDefaultPiRuntime,
|
|
84394
84504
|
isNtfyEventEnabled: () => isNtfyEventEnabled,
|
|
84395
84505
|
isUsageLimitError: () => isUsageLimitError,
|
|
@@ -112899,7 +113009,7 @@ var require_headers = __commonJS({
|
|
|
112899
113009
|
function isGNU(buf) {
|
|
112900
113010
|
return b4a.equals(GNU_MAGIC, buf.subarray(MAGIC_OFFSET, MAGIC_OFFSET + 6)) && b4a.equals(GNU_VER, buf.subarray(VERSION_OFFSET, VERSION_OFFSET + 2));
|
|
112901
113011
|
}
|
|
112902
|
-
function
|
|
113012
|
+
function clamp3(index2, len, defaultValue) {
|
|
112903
113013
|
if (typeof index2 !== "number") return defaultValue;
|
|
112904
113014
|
index2 = ~~index2;
|
|
112905
113015
|
if (index2 >= len) return len;
|
|
@@ -113018,7 +113128,7 @@ var require_headers = __commonJS({
|
|
|
113018
113128
|
return parse256(val);
|
|
113019
113129
|
} else {
|
|
113020
113130
|
while (offset < val.length && val[offset] === 32) offset++;
|
|
113021
|
-
const end =
|
|
113131
|
+
const end = clamp3(indexOf(val, 32, offset, val.length), val.length, val.length);
|
|
113022
113132
|
while (offset < end && val[offset] === 0) offset++;
|
|
113023
113133
|
if (end === offset) return 0;
|
|
113024
113134
|
return parseInt(b4a.toString(val.subarray(offset, end)), 8);
|
|
@@ -117304,6 +117414,22 @@ function createApiRoutes(store, options) {
|
|
|
117304
117414
|
}
|
|
117305
117415
|
});
|
|
117306
117416
|
router.post("/settings/test-ntfy", async (req, res) => {
|
|
117417
|
+
const normalizeNtfyBaseUrl = (value, source) => {
|
|
117418
|
+
const trimmed = value.trim();
|
|
117419
|
+
if (!trimmed) {
|
|
117420
|
+
throw badRequest("ntfy server URL cannot be empty");
|
|
117421
|
+
}
|
|
117422
|
+
let parsed;
|
|
117423
|
+
try {
|
|
117424
|
+
parsed = new URL(trimmed);
|
|
117425
|
+
} catch {
|
|
117426
|
+
throw badRequest(`ntfy server URL from ${source} must be a valid URL`);
|
|
117427
|
+
}
|
|
117428
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
117429
|
+
throw badRequest("ntfy server URL must use http:// or https://");
|
|
117430
|
+
}
|
|
117431
|
+
return trimmed.replace(/\/+$/, "");
|
|
117432
|
+
};
|
|
117307
117433
|
try {
|
|
117308
117434
|
const { store: scopedStore } = await getProjectContext2(req);
|
|
117309
117435
|
const settings = await scopedStore.getSettings();
|
|
@@ -117314,7 +117440,13 @@ function createApiRoutes(store, options) {
|
|
|
117314
117440
|
if (!topic || !/^[a-zA-Z0-9_-]{1,64}$/.test(topic)) {
|
|
117315
117441
|
throw badRequest("ntfy topic is not configured or invalid");
|
|
117316
117442
|
}
|
|
117317
|
-
const
|
|
117443
|
+
const overrideValue = req.body?.ntfyBaseUrl;
|
|
117444
|
+
if (overrideValue !== void 0 && overrideValue !== null && typeof overrideValue !== "string") {
|
|
117445
|
+
throw badRequest("ntfy server URL must be a string");
|
|
117446
|
+
}
|
|
117447
|
+
const requestOverride = typeof overrideValue === "string" && overrideValue.trim() ? normalizeNtfyBaseUrl(overrideValue, "request") : void 0;
|
|
117448
|
+
const storedServer = typeof settings.ntfyBaseUrl === "string" && settings.ntfyBaseUrl.trim() ? normalizeNtfyBaseUrl(settings.ntfyBaseUrl, "settings") : void 0;
|
|
117449
|
+
const ntfyBaseUrl = requestOverride ?? storedServer ?? "https://ntfy.sh";
|
|
117318
117450
|
const url = `${ntfyBaseUrl}/${topic}`;
|
|
117319
117451
|
const response = await fetch(url, {
|
|
117320
117452
|
method: "POST",
|
|
@@ -117326,7 +117458,7 @@ function createApiRoutes(store, options) {
|
|
|
117326
117458
|
body: "Fusion test notification \u2014 your notifications are working!"
|
|
117327
117459
|
});
|
|
117328
117460
|
if (!response.ok) {
|
|
117329
|
-
throw new ApiError(502, `ntfy
|
|
117461
|
+
throw new ApiError(502, `ntfy server returned ${response.status}: ${response.statusText}`);
|
|
117330
117462
|
}
|
|
117331
117463
|
res.json({ success: true });
|
|
117332
117464
|
} catch (err) {
|
|
@@ -136126,50 +136258,152 @@ var init_claude_cli_extension = __esm({
|
|
|
136126
136258
|
}
|
|
136127
136259
|
});
|
|
136128
136260
|
|
|
136129
|
-
// src/commands/dashboard-tui.ts
|
|
136130
|
-
|
|
136131
|
-
|
|
136132
|
-
|
|
136133
|
-
|
|
136134
|
-
|
|
136135
|
-
|
|
136136
|
-
|
|
136137
|
-
|
|
136138
|
-
|
|
136139
|
-
|
|
136140
|
-
|
|
136141
|
-
|
|
136142
|
-
|
|
136143
|
-
|
|
136144
|
-
|
|
136145
|
-
}
|
|
136146
|
-
|
|
136147
|
-
|
|
136148
|
-
|
|
136149
|
-
|
|
136150
|
-
|
|
136261
|
+
// src/commands/dashboard-tui/log-ring-buffer.ts
|
|
136262
|
+
var MAX_LOG_ENTRIES, LogRingBuffer;
|
|
136263
|
+
var init_log_ring_buffer = __esm({
|
|
136264
|
+
"src/commands/dashboard-tui/log-ring-buffer.ts"() {
|
|
136265
|
+
"use strict";
|
|
136266
|
+
MAX_LOG_ENTRIES = 1e3;
|
|
136267
|
+
LogRingBuffer = class {
|
|
136268
|
+
entries = [];
|
|
136269
|
+
count = 0;
|
|
136270
|
+
push(entry) {
|
|
136271
|
+
if (this.entries.length < MAX_LOG_ENTRIES) {
|
|
136272
|
+
this.entries.push(entry);
|
|
136273
|
+
} else {
|
|
136274
|
+
this.entries[this.count % MAX_LOG_ENTRIES] = entry;
|
|
136275
|
+
}
|
|
136276
|
+
this.count++;
|
|
136277
|
+
}
|
|
136278
|
+
getAll() {
|
|
136279
|
+
if (this.count <= MAX_LOG_ENTRIES) {
|
|
136280
|
+
return this.entries.slice();
|
|
136281
|
+
}
|
|
136282
|
+
const start = this.count % MAX_LOG_ENTRIES;
|
|
136283
|
+
return [
|
|
136284
|
+
...this.entries.slice(start),
|
|
136285
|
+
...this.entries.slice(0, start)
|
|
136286
|
+
];
|
|
136287
|
+
}
|
|
136288
|
+
clear() {
|
|
136289
|
+
this.entries = [];
|
|
136290
|
+
this.count = 0;
|
|
136291
|
+
}
|
|
136292
|
+
get total() {
|
|
136293
|
+
return this.count;
|
|
136294
|
+
}
|
|
136295
|
+
};
|
|
136296
|
+
}
|
|
136297
|
+
});
|
|
136298
|
+
|
|
136299
|
+
// src/commands/dashboard-tui/state.ts
|
|
136300
|
+
var SECTION_ORDER;
|
|
136301
|
+
var init_state = __esm({
|
|
136302
|
+
"src/commands/dashboard-tui/state.ts"() {
|
|
136303
|
+
"use strict";
|
|
136304
|
+
SECTION_ORDER = ["system", "logs", "utilities", "stats", "settings"];
|
|
136305
|
+
}
|
|
136306
|
+
});
|
|
136307
|
+
|
|
136308
|
+
// src/commands/dashboard-tui/logo.ts
|
|
136309
|
+
var FUSION_LOGO_LINES, FUSION_TAGLINE;
|
|
136310
|
+
var init_logo = __esm({
|
|
136311
|
+
"src/commands/dashboard-tui/logo.ts"() {
|
|
136312
|
+
"use strict";
|
|
136313
|
+
FUSION_LOGO_LINES = [
|
|
136314
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
136315
|
+
"\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551",
|
|
136316
|
+
"\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551",
|
|
136317
|
+
"\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551",
|
|
136318
|
+
"\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551",
|
|
136319
|
+
"\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D"
|
|
136320
|
+
];
|
|
136321
|
+
FUSION_TAGLINE = "AI coding agent dashboard";
|
|
136322
|
+
}
|
|
136323
|
+
});
|
|
136324
|
+
|
|
136325
|
+
// src/commands/dashboard-tui/hooks/use-projects.ts
|
|
136326
|
+
import { useState, useEffect, useCallback } from "react";
|
|
136327
|
+
function useProjects(interactiveData) {
|
|
136328
|
+
const [projects, setProjects] = useState([]);
|
|
136329
|
+
const [loading, setLoading] = useState(false);
|
|
136330
|
+
const [error, setError] = useState(null);
|
|
136331
|
+
useEffect(() => {
|
|
136332
|
+
if (!interactiveData) return;
|
|
136333
|
+
setLoading(true);
|
|
136334
|
+
setError(null);
|
|
136335
|
+
interactiveData.listProjects().then((p) => {
|
|
136336
|
+
setProjects(p);
|
|
136337
|
+
setLoading(false);
|
|
136338
|
+
}).catch((err) => {
|
|
136339
|
+
setProjects([]);
|
|
136340
|
+
setLoading(false);
|
|
136341
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
136342
|
+
});
|
|
136343
|
+
}, [interactiveData]);
|
|
136344
|
+
return { projects, loading, error };
|
|
136345
|
+
}
|
|
136346
|
+
function useTasks(interactiveData, selectedProject) {
|
|
136347
|
+
const [tasks, setTasks] = useState([]);
|
|
136348
|
+
const [loading, setLoading] = useState(false);
|
|
136349
|
+
const [error, setError] = useState(null);
|
|
136350
|
+
const [reloadTick, setReloadTick] = useState(0);
|
|
136351
|
+
const refresh = useCallback(() => setReloadTick((n) => n + 1), []);
|
|
136352
|
+
useEffect(() => {
|
|
136353
|
+
if (!interactiveData || !selectedProject) {
|
|
136354
|
+
setTasks([]);
|
|
136355
|
+
setLoading(false);
|
|
136356
|
+
setError(null);
|
|
136357
|
+
return;
|
|
136358
|
+
}
|
|
136359
|
+
setLoading(true);
|
|
136360
|
+
setError(null);
|
|
136361
|
+
interactiveData.listTasks(selectedProject.path).then((t) => {
|
|
136362
|
+
setTasks(t);
|
|
136363
|
+
setLoading(false);
|
|
136364
|
+
}).catch((err) => {
|
|
136365
|
+
setTasks([]);
|
|
136366
|
+
setLoading(false);
|
|
136367
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
136368
|
+
});
|
|
136369
|
+
}, [interactiveData, selectedProject, reloadTick]);
|
|
136370
|
+
return { tasks, loading, error, refresh };
|
|
136151
136371
|
}
|
|
136152
|
-
|
|
136153
|
-
|
|
136154
|
-
|
|
136155
|
-
|
|
136156
|
-
|
|
136157
|
-
|
|
136158
|
-
|
|
136159
|
-
|
|
136160
|
-
|
|
136161
|
-
|
|
136162
|
-
|
|
136163
|
-
|
|
136164
|
-
|
|
136165
|
-
|
|
136166
|
-
|
|
136167
|
-
|
|
136168
|
-
|
|
136169
|
-
|
|
136170
|
-
|
|
136171
|
-
|
|
136172
|
-
|
|
136372
|
+
var init_use_projects = __esm({
|
|
136373
|
+
"src/commands/dashboard-tui/hooks/use-projects.ts"() {
|
|
136374
|
+
"use strict";
|
|
136375
|
+
}
|
|
136376
|
+
});
|
|
136377
|
+
|
|
136378
|
+
// src/commands/dashboard-tui/app.tsx
|
|
136379
|
+
var app_exports = {};
|
|
136380
|
+
__export(app_exports, {
|
|
136381
|
+
DashboardApp: () => DashboardApp
|
|
136382
|
+
});
|
|
136383
|
+
import { useState as useState2, useSyncExternalStore, useCallback as useCallback2, useEffect as useEffect2 } from "react";
|
|
136384
|
+
import { Box, Text, useInput, useApp, useStdout } from "ink";
|
|
136385
|
+
import Spinner from "ink-spinner";
|
|
136386
|
+
import TextInput from "ink-text-input";
|
|
136387
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
136388
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
136389
|
+
function openInBrowser(url) {
|
|
136390
|
+
let cmd;
|
|
136391
|
+
let args;
|
|
136392
|
+
if (process.platform === "darwin") {
|
|
136393
|
+
cmd = "open";
|
|
136394
|
+
args = [url];
|
|
136395
|
+
} else if (process.platform === "win32") {
|
|
136396
|
+
cmd = "cmd";
|
|
136397
|
+
args = ["/c", "start", "", url];
|
|
136398
|
+
} else {
|
|
136399
|
+
cmd = "xdg-open";
|
|
136400
|
+
args = [url];
|
|
136401
|
+
}
|
|
136402
|
+
try {
|
|
136403
|
+
const child = spawn5(cmd, args, { detached: true, stdio: "ignore" });
|
|
136404
|
+
child.unref();
|
|
136405
|
+
} catch {
|
|
136406
|
+
}
|
|
136173
136407
|
}
|
|
136174
136408
|
function formatTimestamp3(date) {
|
|
136175
136409
|
const h = date.getHours().toString().padStart(2, "0");
|
|
@@ -136188,170 +136422,1532 @@ function formatUptime(ms) {
|
|
|
136188
136422
|
if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
|
|
136189
136423
|
return `${seconds}s`;
|
|
136190
136424
|
}
|
|
136191
|
-
function
|
|
136192
|
-
|
|
136425
|
+
function formatRelativeTime(iso) {
|
|
136426
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
136427
|
+
const s = Math.floor(diff / 1e3);
|
|
136428
|
+
if (s < 60) return `${s}s ago`;
|
|
136429
|
+
const m = Math.floor(s / 60);
|
|
136430
|
+
if (m < 60) return `${m}m ago`;
|
|
136431
|
+
const h = Math.floor(m / 60);
|
|
136432
|
+
return `${h}h ago`;
|
|
136433
|
+
}
|
|
136434
|
+
function logoColor(index2) {
|
|
136435
|
+
return LOGO_COLORS[Math.min(index2, LOGO_COLORS.length - 1)];
|
|
136436
|
+
}
|
|
136437
|
+
function AnimatedFusionLogo() {
|
|
136438
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", alignItems: "center", children: FUSION_LOGO_LINES.map((line, i) => /* @__PURE__ */ jsx(Text, { color: logoColor(i), bold: true, children: line }, i)) });
|
|
136439
|
+
}
|
|
136440
|
+
function SplashScreen({ loadingStatus }) {
|
|
136441
|
+
const { stdout } = useStdout();
|
|
136442
|
+
const cols = stdout?.columns ?? 80;
|
|
136443
|
+
const rows = stdout?.rows ?? 24;
|
|
136444
|
+
const compact = cols < SPLASH_MIN_COLS || rows < SPLASH_MIN_ROWS;
|
|
136445
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [
|
|
136446
|
+
compact ? /* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: "FUSION" }) : /* @__PURE__ */ jsx(AnimatedFusionLogo, {}),
|
|
136447
|
+
/* @__PURE__ */ jsx(Text, { color: "blueBright", dimColor: true, children: FUSION_TAGLINE }),
|
|
136448
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
136449
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136450
|
+
/* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
|
|
136451
|
+
/* @__PURE__ */ jsx(Text, { color: "blueBright", dimColor: true, children: loadingStatus })
|
|
136452
|
+
] })
|
|
136453
|
+
] });
|
|
136454
|
+
}
|
|
136455
|
+
function MiniLogo() {
|
|
136456
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 0, children: /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "FUSION" }) });
|
|
136457
|
+
}
|
|
136458
|
+
function Panel({ title, isFocused, children, flexGrow, flexShrink, width }) {
|
|
136459
|
+
return /* @__PURE__ */ jsxs(
|
|
136460
|
+
Box,
|
|
136461
|
+
{
|
|
136462
|
+
borderStyle: "round",
|
|
136463
|
+
borderColor: isFocused ? "cyan" : "gray",
|
|
136464
|
+
flexDirection: "column",
|
|
136465
|
+
flexGrow,
|
|
136466
|
+
flexShrink,
|
|
136467
|
+
width,
|
|
136468
|
+
overflow: "hidden",
|
|
136469
|
+
children: [
|
|
136470
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: isFocused, color: isFocused ? "cyan" : void 0, dimColor: !isFocused, children: title }) }),
|
|
136471
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children })
|
|
136472
|
+
]
|
|
136473
|
+
}
|
|
136474
|
+
);
|
|
136193
136475
|
}
|
|
136194
|
-
function
|
|
136195
|
-
|
|
136196
|
-
|
|
136197
|
-
|
|
136198
|
-
|
|
136199
|
-
|
|
136200
|
-
|
|
136201
|
-
|
|
136202
|
-
|
|
136203
|
-
|
|
136204
|
-
|
|
136205
|
-
|
|
136206
|
-
|
|
136207
|
-
|
|
136476
|
+
function SystemPanel({ state, isFocused }) {
|
|
136477
|
+
const info = state.systemInfo;
|
|
136478
|
+
return /* @__PURE__ */ jsx(Panel, { title: "System", isFocused, flexGrow: 1, children: !info ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "System information not available." }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
136479
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136480
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Host:" }),
|
|
136481
|
+
/* @__PURE__ */ jsx(Text, { children: info.host })
|
|
136482
|
+
] }),
|
|
136483
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136484
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Port:" }),
|
|
136485
|
+
/* @__PURE__ */ jsx(Text, { children: info.port })
|
|
136486
|
+
] }),
|
|
136487
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136488
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "URL:" }),
|
|
136489
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: info.baseUrl })
|
|
136490
|
+
] }),
|
|
136491
|
+
info.authEnabled ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
136492
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136493
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Auth:" }),
|
|
136494
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", children: "bearer token required" })
|
|
136495
|
+
] }),
|
|
136496
|
+
info.authToken && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136497
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Token:" }),
|
|
136498
|
+
/* @__PURE__ */ jsx(Text, { wrap: "truncate", color: "yellow", children: info.authToken })
|
|
136499
|
+
] }),
|
|
136500
|
+
info.tokenizedUrl && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136501
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Open:" }),
|
|
136502
|
+
/* @__PURE__ */ jsx(Text, { wrap: "truncate", color: "cyanBright", children: info.tokenizedUrl })
|
|
136503
|
+
] }),
|
|
136504
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136505
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press" }),
|
|
136506
|
+
/* @__PURE__ */ jsx(Text, { color: "cyanBright", bold: true, children: "[Enter]" }),
|
|
136507
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "to open in browser" })
|
|
136508
|
+
] })
|
|
136509
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
136510
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136511
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Auth:" }),
|
|
136512
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", children: "no auth" })
|
|
136513
|
+
] }),
|
|
136514
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136515
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press" }),
|
|
136516
|
+
/* @__PURE__ */ jsx(Text, { color: "cyanBright", bold: true, children: "[Enter]" }),
|
|
136517
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "to open in browser" })
|
|
136518
|
+
] })
|
|
136519
|
+
] }),
|
|
136520
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136521
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Engine:" }),
|
|
136522
|
+
info.engineMode === "dev" && /* @__PURE__ */ jsx(Text, { color: "yellow", children: "dev" }),
|
|
136523
|
+
info.engineMode === "paused" && /* @__PURE__ */ jsx(Text, { color: "yellow", children: "paused" }),
|
|
136524
|
+
info.engineMode === "active" && /* @__PURE__ */ jsx(Text, { color: "green", children: "active" })
|
|
136525
|
+
] }),
|
|
136526
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136527
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Watcher:" }),
|
|
136528
|
+
info.fileWatcher ? /* @__PURE__ */ jsx(Text, { color: "green", children: "active" }) : /* @__PURE__ */ jsx(Text, { color: "red", children: "inactive" })
|
|
136529
|
+
] }),
|
|
136530
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136531
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Uptime:" }),
|
|
136532
|
+
/* @__PURE__ */ jsx(Text, { children: formatUptime(Date.now() - info.startTimeMs) })
|
|
136533
|
+
] })
|
|
136534
|
+
] }) });
|
|
136535
|
+
}
|
|
136536
|
+
function StatsPanel({ state, isFocused }) {
|
|
136537
|
+
const stats = state.taskStats;
|
|
136538
|
+
return /* @__PURE__ */ jsx(Panel, { title: "Stats", isFocused, flexGrow: 1, children: !stats ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Statistics not available." }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
136539
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136540
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Total:" }),
|
|
136541
|
+
/* @__PURE__ */ jsx(Text, { children: stats.total })
|
|
136542
|
+
] }),
|
|
136543
|
+
Object.entries(stats.byColumn).map(([col, count]) => {
|
|
136544
|
+
const name = col.replace(/-/g, " ");
|
|
136545
|
+
const isActive = (col === "in-progress" || col === "in-review") && count > 0;
|
|
136546
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginLeft: 1, children: [
|
|
136547
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
136548
|
+
name,
|
|
136549
|
+
":"
|
|
136550
|
+
] }),
|
|
136551
|
+
/* @__PURE__ */ jsx(Text, { color: isActive ? "green" : void 0, children: count })
|
|
136552
|
+
] }, col);
|
|
136553
|
+
}),
|
|
136554
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
136555
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Agents:" }),
|
|
136556
|
+
/* @__PURE__ */ jsxs(Box, { marginLeft: 1, flexDirection: "column", children: [
|
|
136557
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
136558
|
+
"idle: ",
|
|
136559
|
+
/* @__PURE__ */ jsx(Text, { color: "white", children: stats.agents.idle })
|
|
136560
|
+
] }),
|
|
136561
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
136562
|
+
"active: ",
|
|
136563
|
+
/* @__PURE__ */ jsx(Text, { color: "green", children: stats.agents.active })
|
|
136564
|
+
] }),
|
|
136565
|
+
/* @__PURE__ */ jsxs(Text, { color: stats.agents.error > 0 ? "red" : void 0, dimColor: stats.agents.error === 0, children: [
|
|
136566
|
+
"error: ",
|
|
136567
|
+
stats.agents.error
|
|
136568
|
+
] })
|
|
136569
|
+
] })
|
|
136570
|
+
] }) });
|
|
136571
|
+
}
|
|
136572
|
+
function SettingsPanel({ state, isFocused }) {
|
|
136573
|
+
const s = state.settings;
|
|
136574
|
+
return /* @__PURE__ */ jsx(Panel, { title: "Settings", isFocused, flexGrow: 1, children: !s ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Settings not available." }) : /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: [
|
|
136575
|
+
["maxConcurrent", s.maxConcurrent.toString()],
|
|
136576
|
+
["maxWorktrees", s.maxWorktrees.toString()],
|
|
136577
|
+
["autoMerge", s.autoMerge ? "enabled" : "disabled"],
|
|
136578
|
+
["mergeStrategy", s.mergeStrategy],
|
|
136579
|
+
["pollMs", `${s.pollIntervalMs}`],
|
|
136580
|
+
["paused", s.enginePaused ? "yes" : "no"],
|
|
136581
|
+
["globalPause", s.globalPause ? "yes" : "no"]
|
|
136582
|
+
].map(([key, value]) => {
|
|
136583
|
+
const isEnabled = value === "enabled" || value === "yes";
|
|
136584
|
+
const isDisabled = value === "disabled" || value === "no";
|
|
136585
|
+
const color = isEnabled ? "green" : isDisabled ? "yellow" : void 0;
|
|
136586
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136587
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: key }),
|
|
136588
|
+
/* @__PURE__ */ jsx(Text, { color, children: value })
|
|
136589
|
+
] }, key);
|
|
136590
|
+
}) }) });
|
|
136591
|
+
}
|
|
136592
|
+
function LevelBadge({ level }) {
|
|
136593
|
+
if (level === "error") return /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2717" });
|
|
136594
|
+
if (level === "warn") return /* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u26A0" });
|
|
136595
|
+
return /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" });
|
|
136596
|
+
}
|
|
136597
|
+
function LogsPanel({
|
|
136598
|
+
state,
|
|
136599
|
+
isFocused,
|
|
136600
|
+
availableRows
|
|
136601
|
+
}) {
|
|
136602
|
+
const { logsSeverityFilter, logsWrapEnabled, logsExpandedMode, selectedLogIndex } = state;
|
|
136603
|
+
const entries = logsSeverityFilter === "all" ? state.logEntries : state.logEntries.filter((e) => e.level === logsSeverityFilter);
|
|
136604
|
+
const cursor = entries.length === 0 ? 0 : Math.min(Math.max(selectedLogIndex, 0), entries.length - 1);
|
|
136605
|
+
const rowBudget = Math.max(1, availableRows);
|
|
136606
|
+
const visibleStart = Math.max(0, Math.min(
|
|
136607
|
+
cursor - Math.floor(rowBudget / 2),
|
|
136608
|
+
entries.length - rowBudget
|
|
136609
|
+
));
|
|
136610
|
+
const visibleEnd = Math.min(entries.length, visibleStart + rowBudget);
|
|
136611
|
+
const visibleEntries = entries.slice(visibleStart, visibleEnd);
|
|
136612
|
+
const hiddenAbove = visibleStart;
|
|
136613
|
+
const hiddenBelow = entries.length - visibleEnd;
|
|
136614
|
+
return /* @__PURE__ */ jsx(Panel, { title: `Logs (${state.logEntries.length}/1000)`, isFocused, flexGrow: 1, children: logsExpandedMode && entries[cursor] ? /* @__PURE__ */ jsx(
|
|
136615
|
+
ExpandedLog,
|
|
136616
|
+
{
|
|
136617
|
+
entry: entries[cursor],
|
|
136618
|
+
index: cursor,
|
|
136619
|
+
total: entries.length
|
|
136620
|
+
}
|
|
136621
|
+
) : entries.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No log entries yet." }) : entries.length !== state.logEntries.length && entries.length === 0 ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
136622
|
+
"No entries match filter ",
|
|
136623
|
+
logsSeverityFilter.toUpperCase(),
|
|
136624
|
+
"."
|
|
136625
|
+
] }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
136626
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginBottom: 0, children: [
|
|
136627
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
136628
|
+
"[w] wrap ",
|
|
136629
|
+
logsWrapEnabled ? "on" : "off"
|
|
136630
|
+
] }),
|
|
136631
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
136632
|
+
"[f] ",
|
|
136633
|
+
logsSeverityFilter
|
|
136634
|
+
] }),
|
|
136635
|
+
hiddenAbove > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
136636
|
+
"\u2191 ",
|
|
136637
|
+
hiddenAbove,
|
|
136638
|
+
" more"
|
|
136639
|
+
] }),
|
|
136640
|
+
hiddenBelow > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
136641
|
+
"\u2193 ",
|
|
136642
|
+
hiddenBelow,
|
|
136643
|
+
" more"
|
|
136644
|
+
] })
|
|
136645
|
+
] }),
|
|
136646
|
+
visibleEntries.map((entry, displayIdx) => {
|
|
136647
|
+
const absoluteIndex = visibleStart + displayIdx;
|
|
136648
|
+
const isSelected = absoluteIndex === cursor;
|
|
136649
|
+
const bg = isSelected ? "blue" : void 0;
|
|
136650
|
+
const fg = isSelected ? "whiteBright" : void 0;
|
|
136651
|
+
const ts = formatTimestamp3(entry.timestamp);
|
|
136652
|
+
const lvl = entry.level === "error" ? "\u2717" : entry.level === "warn" ? "\u26A0" : "\u2713";
|
|
136653
|
+
const lvlColor = entry.level === "error" ? "red" : entry.level === "warn" ? "yellow" : "green";
|
|
136654
|
+
const prefixSlot = entry.prefix ? `[${entry.prefix}]`.slice(0, PREFIX_WIDTH).padEnd(PREFIX_WIDTH) : " ".repeat(PREFIX_WIDTH);
|
|
136655
|
+
const marker = isSelected ? "\u25B6 " : " ";
|
|
136656
|
+
return /* @__PURE__ */ jsxs(
|
|
136657
|
+
Text,
|
|
136658
|
+
{
|
|
136659
|
+
backgroundColor: bg,
|
|
136660
|
+
wrap: logsWrapEnabled ? "wrap" : "truncate-end",
|
|
136661
|
+
children: [
|
|
136662
|
+
/* @__PURE__ */ jsx(Text, { color: isSelected ? "cyanBright" : "gray", bold: isSelected, children: marker }),
|
|
136663
|
+
/* @__PURE__ */ jsxs(Text, { color: fg, dimColor: !isSelected, children: [
|
|
136664
|
+
ts,
|
|
136665
|
+
" "
|
|
136666
|
+
] }),
|
|
136667
|
+
/* @__PURE__ */ jsx(Text, { color: lvlColor, children: lvl }),
|
|
136668
|
+
/* @__PURE__ */ jsx(Text, { color: fg, dimColor: !isSelected, children: ` ${prefixSlot} ` }),
|
|
136669
|
+
/* @__PURE__ */ jsx(Text, { color: fg, bold: isSelected, children: entry.message })
|
|
136670
|
+
]
|
|
136671
|
+
},
|
|
136672
|
+
`${entry.timestamp.getTime()}-${displayIdx}`
|
|
136673
|
+
);
|
|
136674
|
+
})
|
|
136675
|
+
] }) });
|
|
136676
|
+
}
|
|
136677
|
+
function ExpandedLog({ entry, index: index2, total }) {
|
|
136678
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
136679
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
136680
|
+
"Entry ",
|
|
136681
|
+
index2 + 1,
|
|
136682
|
+
"/",
|
|
136683
|
+
total,
|
|
136684
|
+
" \xB7 [Enter/Esc] close"
|
|
136685
|
+
] }),
|
|
136686
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
136687
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136688
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Time:" }),
|
|
136689
|
+
/* @__PURE__ */ jsx(Text, { children: formatTimestamp3(entry.timestamp) })
|
|
136690
|
+
] }),
|
|
136691
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136692
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Level:" }),
|
|
136693
|
+
/* @__PURE__ */ jsx(LevelBadge, { level: entry.level }),
|
|
136694
|
+
/* @__PURE__ */ jsx(Text, { children: entry.level.toUpperCase() })
|
|
136695
|
+
] }),
|
|
136696
|
+
entry.prefix && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136697
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Prefix:" }),
|
|
136698
|
+
/* @__PURE__ */ jsx(Text, { children: entry.prefix })
|
|
136699
|
+
] }),
|
|
136700
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
136701
|
+
/* @__PURE__ */ jsx(Text, { wrap: "wrap", children: entry.message })
|
|
136702
|
+
] });
|
|
136703
|
+
}
|
|
136704
|
+
function UtilitiesPanel({ isFocused }) {
|
|
136705
|
+
const actions = [
|
|
136706
|
+
{ key: "r", label: "Refresh Stats" },
|
|
136707
|
+
{ key: "c", label: "Clear Logs" },
|
|
136708
|
+
{ key: "t", label: "Toggle Engine Pause" },
|
|
136709
|
+
{ key: "?", label: "Help" }
|
|
136710
|
+
];
|
|
136711
|
+
return /* @__PURE__ */ jsx(Panel, { title: "Utilities", isFocused, flexShrink: 0, children: /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: actions.map((action) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136712
|
+
/* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
136713
|
+
"[",
|
|
136714
|
+
action.key,
|
|
136715
|
+
"]"
|
|
136716
|
+
] }),
|
|
136717
|
+
/* @__PURE__ */ jsx(Text, { children: action.label })
|
|
136718
|
+
] }, action.key)) }) });
|
|
136719
|
+
}
|
|
136720
|
+
function HelpOverlay() {
|
|
136721
|
+
const shortcuts = [
|
|
136722
|
+
["[b]", "Board view (interactive mode)"],
|
|
136723
|
+
["[a]", "Agents view"],
|
|
136724
|
+
["[g]", "Settings view"],
|
|
136725
|
+
["[s]", "Status mode"],
|
|
136726
|
+
["[1] / [2] / [3]", "Board / Agents / Settings (interactive)"],
|
|
136727
|
+
["[Tab]", "Cycle focused panel forward"],
|
|
136728
|
+
["[Shift+Tab]", "Cycle focused panel backward"],
|
|
136729
|
+
["[1-5]", "Jump to panel by number (status mode)"],
|
|
136730
|
+
["[\u2192] / [n]", "Next panel (status mode)"],
|
|
136731
|
+
["[\u2190] / [p]", "Previous panel (status mode)"],
|
|
136732
|
+
["[r]", "Refresh stats (Utilities)"],
|
|
136733
|
+
["[c]", "Clear logs (Utilities)"],
|
|
136734
|
+
["[t]", "Toggle engine pause (Utilities)"],
|
|
136735
|
+
["[\u2191/\u2193/k/j]", "Navigate log entries (Logs)"],
|
|
136736
|
+
["[Home/End]", "First/last log entry (Logs)"],
|
|
136737
|
+
["[Enter/Space/e]", "Expand log entry (Logs)"],
|
|
136738
|
+
["[w]", "Toggle word wrap (Logs)"],
|
|
136739
|
+
["[f]", "Cycle severity filter (Logs)"],
|
|
136740
|
+
["[s/x]", "Start/stop agent (Agents view)"],
|
|
136741
|
+
["[D]", "Delete agent \u2014 requires confirm (Agents view)"],
|
|
136742
|
+
["[r]", "Refresh agent detail (Agents view)"],
|
|
136743
|
+
["[Space]", "Toggle boolean (Settings view)"],
|
|
136744
|
+
["[+/-]", "Adjust number (Settings view)"],
|
|
136745
|
+
["[?] / [h]", "Toggle help"],
|
|
136746
|
+
["[q]", "Quit"],
|
|
136747
|
+
["[Ctrl+C]", "Force quit"]
|
|
136748
|
+
];
|
|
136749
|
+
const rowKeyWidth = 22;
|
|
136750
|
+
const rowDescWidth = Math.max(...shortcuts.map(([, d]) => d.length));
|
|
136751
|
+
const innerWidth = rowKeyWidth + 2 + rowDescWidth + 2;
|
|
136752
|
+
const titleRow = " KEYBOARD SHORTCUTS".padEnd(innerWidth);
|
|
136753
|
+
return /* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", backgroundColor: "black", children: [
|
|
136754
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor: "black", bold: true, color: "cyanBright", children: titleRow }),
|
|
136755
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor: "black", children: " " }),
|
|
136756
|
+
shortcuts.map(([key, desc]) => {
|
|
136757
|
+
const keyCell = ` ${key.padEnd(rowKeyWidth - 1)} `;
|
|
136758
|
+
const descCell = ` ${desc.padEnd(rowDescWidth)} `;
|
|
136759
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
|
|
136760
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor: "black", color: "yellow", children: keyCell }),
|
|
136761
|
+
/* @__PURE__ */ jsx(Text, { backgroundColor: "black", color: "white", children: descCell })
|
|
136762
|
+
] }, key);
|
|
136763
|
+
})
|
|
136764
|
+
] });
|
|
136765
|
+
}
|
|
136766
|
+
function StatusModeGrid({
|
|
136767
|
+
state,
|
|
136768
|
+
rows,
|
|
136769
|
+
controller
|
|
136770
|
+
}) {
|
|
136771
|
+
const focused = state.activeSection;
|
|
136772
|
+
const bodyRows = Math.max(8, rows - 7);
|
|
136773
|
+
const logsAvailableRows = Math.max(4, bodyRows - 4);
|
|
136774
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
|
|
136775
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, paddingX: 1, paddingY: 0, children: [
|
|
136776
|
+
/* @__PURE__ */ jsx(MiniLogo, {}),
|
|
136777
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
136778
|
+
SECTION_ORDER.map((section, i) => {
|
|
136779
|
+
const isActive = section === focused;
|
|
136780
|
+
const label = section.charAt(0).toUpperCase() + section.slice(1);
|
|
136781
|
+
return /* @__PURE__ */ jsx(Box, { marginRight: 1, children: isActive ? /* @__PURE__ */ jsx(Text, { backgroundColor: "cyan", color: "black", bold: true, children: ` [${i + 1}] ${label} ` }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: `[${i + 1}] ${label}` }) }, section);
|
|
136782
|
+
}),
|
|
136783
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
|
|
136784
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "[b] board [a] agents [g] settings [?] help [q] quit" })
|
|
136785
|
+
] }),
|
|
136786
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [
|
|
136787
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
|
|
136788
|
+
/* @__PURE__ */ jsx(SystemPanel, { state, isFocused: focused === "system" }),
|
|
136789
|
+
/* @__PURE__ */ jsx(StatsPanel, { state, isFocused: focused === "stats" }),
|
|
136790
|
+
/* @__PURE__ */ jsx(SettingsPanel, { state, isFocused: focused === "settings" })
|
|
136791
|
+
] }),
|
|
136792
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 2, overflow: "hidden", children: [
|
|
136793
|
+
/* @__PURE__ */ jsx(
|
|
136794
|
+
LogsPanel,
|
|
136795
|
+
{
|
|
136796
|
+
state,
|
|
136797
|
+
isFocused: focused === "logs",
|
|
136798
|
+
availableRows: logsAvailableRows
|
|
136799
|
+
}
|
|
136800
|
+
),
|
|
136801
|
+
/* @__PURE__ */ jsx(UtilitiesPanel, { isFocused: focused === "utilities" })
|
|
136802
|
+
] })
|
|
136803
|
+
] }),
|
|
136804
|
+
/* @__PURE__ */ jsx(StatusBar, { state, controller })
|
|
136805
|
+
] });
|
|
136806
|
+
}
|
|
136807
|
+
function StatusModeSingle({
|
|
136808
|
+
state,
|
|
136809
|
+
controller
|
|
136810
|
+
}) {
|
|
136811
|
+
const focused = state.activeSection;
|
|
136812
|
+
const activePanel = () => {
|
|
136813
|
+
switch (focused) {
|
|
136814
|
+
case "system":
|
|
136815
|
+
return /* @__PURE__ */ jsx(SystemPanel, { state, isFocused: true });
|
|
136816
|
+
case "logs":
|
|
136817
|
+
return /* @__PURE__ */ jsx(LogsPanel, { state, isFocused: true, availableRows: Math.max(4, (process.stdout.rows ?? 24) - 8) });
|
|
136818
|
+
case "utilities":
|
|
136819
|
+
return /* @__PURE__ */ jsx(UtilitiesPanel, { isFocused: true });
|
|
136820
|
+
case "stats":
|
|
136821
|
+
return /* @__PURE__ */ jsx(StatsPanel, { state, isFocused: true });
|
|
136822
|
+
case "settings":
|
|
136823
|
+
return /* @__PURE__ */ jsx(SettingsPanel, { state, isFocused: true });
|
|
136824
|
+
}
|
|
136825
|
+
};
|
|
136826
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
|
|
136827
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, paddingX: 1, children: [
|
|
136828
|
+
/* @__PURE__ */ jsx(MiniLogo, {}),
|
|
136829
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
136830
|
+
SECTION_ORDER.map((section, i) => {
|
|
136831
|
+
const isActive = section === focused;
|
|
136832
|
+
const label = section.charAt(0).toUpperCase() + section.slice(1);
|
|
136833
|
+
return /* @__PURE__ */ jsx(Box, { marginRight: 1, children: isActive ? /* @__PURE__ */ jsx(Text, { backgroundColor: "cyan", color: "black", bold: true, children: ` [${i + 1}] ${label} ` }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: `[${i + 1}] ${label}` }) }, section);
|
|
136834
|
+
})
|
|
136835
|
+
] }),
|
|
136836
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: activePanel() }),
|
|
136837
|
+
/* @__PURE__ */ jsx(StatusBar, { state, controller })
|
|
136838
|
+
] });
|
|
136839
|
+
}
|
|
136840
|
+
function StatusBar({ state, controller: _controller }) {
|
|
136841
|
+
const { systemInfo, activeSection } = state;
|
|
136842
|
+
const hotkeys = [];
|
|
136843
|
+
if (activeSection === "logs") {
|
|
136844
|
+
hotkeys.push("\u2191\u2193 navigate", "w wrap", "f filter", "Enter expand");
|
|
136845
|
+
} else if (activeSection === "utilities") {
|
|
136846
|
+
hotkeys.push("r refresh", "c clear logs", "t toggle pause");
|
|
136847
|
+
} else {
|
|
136848
|
+
hotkeys.push("Tab cycle panel", "1-5 jump");
|
|
136849
|
+
}
|
|
136850
|
+
const statusParts = [];
|
|
136851
|
+
if (systemInfo) {
|
|
136852
|
+
statusParts.push(systemInfo.baseUrl);
|
|
136853
|
+
statusParts.push(formatUptime(Date.now() - systemInfo.startTimeMs));
|
|
136854
|
+
}
|
|
136855
|
+
return /* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", paddingX: 1, children: [
|
|
136856
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: hotkeys.join(" \xB7 ") }),
|
|
136857
|
+
statusParts.length > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: statusParts.join(" | ") })
|
|
136858
|
+
] });
|
|
136859
|
+
}
|
|
136860
|
+
function InteractiveHeader({ activeView }) {
|
|
136861
|
+
const tabs = [
|
|
136862
|
+
{ key: "b", label: "Board", view: "board" },
|
|
136863
|
+
{ key: "a", label: "Agents", view: "agents" },
|
|
136864
|
+
{ key: "g", label: "Settings", view: "settings" }
|
|
136865
|
+
];
|
|
136866
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, paddingX: 1, children: [
|
|
136867
|
+
/* @__PURE__ */ jsx(MiniLogo, {}),
|
|
136868
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
|
|
136869
|
+
tabs.map(({ key, label, view }) => {
|
|
136870
|
+
const isActive = view === activeView;
|
|
136871
|
+
return isActive ? /* @__PURE__ */ jsx(Text, { backgroundColor: "cyan", color: "black", bold: true, children: ` [${key}] ${label} ` }, view) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: `[${key}] ${label}` }, view);
|
|
136872
|
+
}),
|
|
136873
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
|
|
136874
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "[s] status [?] help [q] quit" })
|
|
136875
|
+
] });
|
|
136876
|
+
}
|
|
136877
|
+
function columnLabel(col) {
|
|
136878
|
+
return col.replace(/-/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
|
|
136879
|
+
}
|
|
136880
|
+
function TaskCard({
|
|
136881
|
+
task,
|
|
136882
|
+
selected,
|
|
136883
|
+
width
|
|
136884
|
+
}) {
|
|
136885
|
+
const accent = COLUMN_COLORS[task.column] ?? "white";
|
|
136886
|
+
const borderColor = selected ? "cyanBright" : "gray";
|
|
136887
|
+
const titleColor = selected ? "whiteBright" : void 0;
|
|
136888
|
+
const shortId = task.id.length > 10 ? task.id.slice(0, 8) : task.id;
|
|
136889
|
+
return /* @__PURE__ */ jsxs(
|
|
136890
|
+
Box,
|
|
136891
|
+
{
|
|
136892
|
+
borderStyle: "round",
|
|
136893
|
+
borderColor,
|
|
136894
|
+
flexDirection: "column",
|
|
136895
|
+
paddingX: 1,
|
|
136896
|
+
width,
|
|
136897
|
+
flexShrink: 0,
|
|
136898
|
+
children: [
|
|
136899
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [
|
|
136900
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: shortId }),
|
|
136901
|
+
task.agentState && /* @__PURE__ */ jsxs(Text, { color: accent, children: [
|
|
136902
|
+
"\u25CF ",
|
|
136903
|
+
task.agentState
|
|
136904
|
+
] })
|
|
136905
|
+
] }),
|
|
136906
|
+
/* @__PURE__ */ jsx(Text, { bold: selected, color: titleColor, wrap: "truncate-end", children: task.title ?? task.id })
|
|
136907
|
+
]
|
|
136908
|
+
}
|
|
136909
|
+
);
|
|
136910
|
+
}
|
|
136911
|
+
function KanbanColumnView({
|
|
136912
|
+
column,
|
|
136913
|
+
tasks,
|
|
136914
|
+
isFocused,
|
|
136915
|
+
selectedIndex,
|
|
136916
|
+
width
|
|
136917
|
+
}) {
|
|
136918
|
+
const accent = COLUMN_COLORS[column];
|
|
136919
|
+
const headerColor = isFocused ? "whiteBright" : accent;
|
|
136920
|
+
const cardWidth = Math.max(12, width - 4);
|
|
136921
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, flexShrink: 0, paddingX: 1, children: [
|
|
136922
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136923
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: headerColor, backgroundColor: isFocused ? accent : void 0, children: ` ${columnLabel(column).toUpperCase()} ` }),
|
|
136924
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: tasks.length })
|
|
136925
|
+
] }),
|
|
136926
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
136927
|
+
tasks.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2014" }) : /* @__PURE__ */ jsx(Box, { flexDirection: "column", gap: 0, children: tasks.map((task, i) => /* @__PURE__ */ jsx(
|
|
136928
|
+
TaskCard,
|
|
136929
|
+
{
|
|
136930
|
+
task,
|
|
136931
|
+
selected: isFocused && i === selectedIndex,
|
|
136932
|
+
width: cardWidth
|
|
136933
|
+
},
|
|
136934
|
+
task.id
|
|
136935
|
+
)) })
|
|
136936
|
+
] });
|
|
136937
|
+
}
|
|
136938
|
+
function ProjectSelector({
|
|
136939
|
+
open,
|
|
136940
|
+
projects,
|
|
136941
|
+
selectedIndex,
|
|
136942
|
+
onSelect: _onSelect
|
|
136943
|
+
}) {
|
|
136944
|
+
const current = projects[selectedIndex] ?? null;
|
|
136945
|
+
if (!open) {
|
|
136946
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
136947
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Project:" }),
|
|
136948
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: current?.name ?? "(none)" }),
|
|
136949
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "[p] change" })
|
|
136950
|
+
] });
|
|
136951
|
+
}
|
|
136952
|
+
return /* @__PURE__ */ jsxs(
|
|
136953
|
+
Box,
|
|
136954
|
+
{
|
|
136955
|
+
borderStyle: "round",
|
|
136956
|
+
borderColor: "cyan",
|
|
136957
|
+
flexDirection: "column",
|
|
136958
|
+
paddingX: 1,
|
|
136959
|
+
backgroundColor: "black",
|
|
136960
|
+
width: Math.max(30, ...projects.map((p) => p.name.length + 4)),
|
|
136961
|
+
children: [
|
|
136962
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", backgroundColor: "black", children: "Pick a project" }),
|
|
136963
|
+
projects.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, backgroundColor: "black", children: "(no projects registered)" }) : projects.map((p, i) => {
|
|
136964
|
+
const isSel = i === selectedIndex;
|
|
136965
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, backgroundColor: "black", children: [
|
|
136966
|
+
/* @__PURE__ */ jsx(Text, { color: isSel ? "cyanBright" : "gray", backgroundColor: "black", children: isSel ? "\u25B6" : " " }),
|
|
136967
|
+
/* @__PURE__ */ jsx(Text, { bold: isSel, color: isSel ? "whiteBright" : void 0, backgroundColor: "black", children: p.name })
|
|
136968
|
+
] }, p.id);
|
|
136969
|
+
}),
|
|
136970
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
136971
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, backgroundColor: "black", children: "\u2191\u2193 navigate \xB7 Enter select \xB7 Esc cancel" })
|
|
136972
|
+
]
|
|
136973
|
+
}
|
|
136974
|
+
);
|
|
136975
|
+
}
|
|
136976
|
+
function TaskDetailScreen({ task }) {
|
|
136977
|
+
const accent = COLUMN_COLORS[task.column] ?? "white";
|
|
136978
|
+
return /* @__PURE__ */ jsxs(
|
|
136979
|
+
Box,
|
|
136980
|
+
{
|
|
136981
|
+
borderStyle: "round",
|
|
136982
|
+
borderColor: "cyan",
|
|
136983
|
+
flexDirection: "column",
|
|
136984
|
+
paddingX: 2,
|
|
136985
|
+
paddingY: 1,
|
|
136986
|
+
flexGrow: 1,
|
|
136987
|
+
children: [
|
|
136988
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: task.id }),
|
|
136989
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
136990
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "whiteBright", wrap: "wrap", children: task.title ?? task.id }),
|
|
136991
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
136992
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
|
|
136993
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
136994
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Column " }),
|
|
136995
|
+
/* @__PURE__ */ jsx(Text, { color: accent, bold: true, children: columnLabel(task.column) })
|
|
136996
|
+
] }),
|
|
136997
|
+
task.agentState && /* @__PURE__ */ jsxs(Box, { children: [
|
|
136998
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Agent " }),
|
|
136999
|
+
/* @__PURE__ */ jsx(Text, { color: accent, bold: true, children: task.agentState })
|
|
137000
|
+
] })
|
|
137001
|
+
] }),
|
|
137002
|
+
task.description && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
137003
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137004
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500 Description \u2500\u2500\u2500\u2500" }),
|
|
137005
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137006
|
+
/* @__PURE__ */ jsx(Text, { wrap: "wrap", children: task.description })
|
|
137007
|
+
] }),
|
|
137008
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
|
|
137009
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "[Esc / Backspace] back to board \xB7 [q] quit" })
|
|
137010
|
+
]
|
|
137011
|
+
}
|
|
137012
|
+
);
|
|
137013
|
+
}
|
|
137014
|
+
function clamp2(n, min, max) {
|
|
137015
|
+
return Math.max(min, Math.min(max, n));
|
|
137016
|
+
}
|
|
137017
|
+
function groupTasksByColumn(tasks) {
|
|
137018
|
+
const out = {
|
|
137019
|
+
todo: [],
|
|
137020
|
+
"in-progress": [],
|
|
137021
|
+
"in-review": [],
|
|
137022
|
+
done: []
|
|
137023
|
+
};
|
|
137024
|
+
for (const task of tasks) {
|
|
137025
|
+
const col = KANBAN_COLUMNS.includes(task.column) ? task.column : "todo";
|
|
137026
|
+
out[col].push(task);
|
|
137027
|
+
}
|
|
137028
|
+
return out;
|
|
137029
|
+
}
|
|
137030
|
+
function BoardView({ state }) {
|
|
137031
|
+
const { stdout } = useStdout();
|
|
137032
|
+
const cols = stdout?.columns ?? 80;
|
|
137033
|
+
const columnWidth = Math.max(20, Math.floor((cols - 2) / KANBAN_COLUMNS.length));
|
|
137034
|
+
const [subView, setSubView] = useState2("board");
|
|
137035
|
+
const [projectIndex, setProjectIndex] = useState2(0);
|
|
137036
|
+
const [colIndex, setColIndex] = useState2(0);
|
|
137037
|
+
const [rowByColumn, setRowByColumn] = useState2({
|
|
137038
|
+
todo: 0,
|
|
137039
|
+
"in-progress": 0,
|
|
137040
|
+
"in-review": 0,
|
|
137041
|
+
done: 0
|
|
137042
|
+
});
|
|
137043
|
+
const [pickerOriginal, setPickerOriginal] = useState2(0);
|
|
137044
|
+
const [newTaskTitle, setNewTaskTitle] = useState2("");
|
|
137045
|
+
const [createError, setCreateError] = useState2(null);
|
|
137046
|
+
const [creating, setCreating] = useState2(false);
|
|
137047
|
+
const projectsState = useProjects(state.interactiveData);
|
|
137048
|
+
const selectedProject = projectsState.projects[projectIndex] ?? null;
|
|
137049
|
+
const tasksState = useTasks(state.interactiveData, selectedProject);
|
|
137050
|
+
const grouped = groupTasksByColumn(tasksState.tasks);
|
|
137051
|
+
const focusedColumn = KANBAN_COLUMNS[colIndex];
|
|
137052
|
+
const focusedTasks = grouped[focusedColumn];
|
|
137053
|
+
const focusedRow = clamp2(rowByColumn[focusedColumn] ?? 0, 0, Math.max(0, focusedTasks.length - 1));
|
|
137054
|
+
const selectedTask = focusedTasks[focusedRow] ?? null;
|
|
137055
|
+
useInput((input, key) => {
|
|
137056
|
+
if (subView === "picker") {
|
|
137057
|
+
if (key.upArrow || input === "k") {
|
|
137058
|
+
setProjectIndex((p) => Math.max(0, p - 1));
|
|
137059
|
+
return;
|
|
137060
|
+
}
|
|
137061
|
+
if (key.downArrow || input === "j") {
|
|
137062
|
+
setProjectIndex((p) => Math.min(projectsState.projects.length - 1, p + 1));
|
|
137063
|
+
return;
|
|
137064
|
+
}
|
|
137065
|
+
if (key.return) {
|
|
137066
|
+
setSubView("board");
|
|
137067
|
+
return;
|
|
137068
|
+
}
|
|
137069
|
+
if (key.escape) {
|
|
137070
|
+
setProjectIndex(pickerOriginal);
|
|
137071
|
+
setSubView("board");
|
|
137072
|
+
return;
|
|
137073
|
+
}
|
|
137074
|
+
return;
|
|
137075
|
+
}
|
|
137076
|
+
if (subView === "detail") {
|
|
137077
|
+
if (key.escape || key.backspace || input === "h") {
|
|
137078
|
+
setSubView("board");
|
|
137079
|
+
}
|
|
137080
|
+
return;
|
|
137081
|
+
}
|
|
137082
|
+
if (subView === "create") {
|
|
137083
|
+
if (key.escape) {
|
|
137084
|
+
setSubView("board");
|
|
137085
|
+
setNewTaskTitle("");
|
|
137086
|
+
setCreateError(null);
|
|
137087
|
+
}
|
|
137088
|
+
return;
|
|
137089
|
+
}
|
|
137090
|
+
if (input === "p" || input === "P") {
|
|
137091
|
+
setPickerOriginal(projectIndex);
|
|
137092
|
+
setSubView("picker");
|
|
137093
|
+
return;
|
|
137094
|
+
}
|
|
137095
|
+
if (input === "n" || input === "N") {
|
|
137096
|
+
if (!selectedProject) return;
|
|
137097
|
+
setNewTaskTitle("");
|
|
137098
|
+
setCreateError(null);
|
|
137099
|
+
setSubView("create");
|
|
137100
|
+
return;
|
|
137101
|
+
}
|
|
137102
|
+
if (key.return) {
|
|
137103
|
+
if (selectedTask) setSubView("detail");
|
|
137104
|
+
return;
|
|
137105
|
+
}
|
|
137106
|
+
if (key.leftArrow || input === "h") {
|
|
137107
|
+
setColIndex((c) => Math.max(0, c - 1));
|
|
137108
|
+
return;
|
|
137109
|
+
}
|
|
137110
|
+
if (key.rightArrow || input === "l") {
|
|
137111
|
+
setColIndex((c) => Math.min(KANBAN_COLUMNS.length - 1, c + 1));
|
|
137112
|
+
return;
|
|
137113
|
+
}
|
|
137114
|
+
if (key.upArrow || input === "k") {
|
|
137115
|
+
setRowByColumn((m) => ({
|
|
137116
|
+
...m,
|
|
137117
|
+
[focusedColumn]: Math.max(0, (m[focusedColumn] ?? 0) - 1)
|
|
137118
|
+
}));
|
|
137119
|
+
return;
|
|
137120
|
+
}
|
|
137121
|
+
if (key.downArrow || input === "j") {
|
|
137122
|
+
setRowByColumn((m) => {
|
|
137123
|
+
const len = grouped[focusedColumn].length;
|
|
137124
|
+
return { ...m, [focusedColumn]: Math.min(Math.max(0, len - 1), (m[focusedColumn] ?? 0) + 1) };
|
|
137125
|
+
});
|
|
137126
|
+
return;
|
|
137127
|
+
}
|
|
137128
|
+
});
|
|
137129
|
+
const hintText = subView === "picker" ? "\u2191\u2193 pick \xB7 Enter confirm \xB7 Esc cancel" : subView === "detail" ? "Esc back \xB7 q quit" : subView === "create" ? "type a task title \xB7 Enter create \xB7 Esc cancel" : "\u2190\u2192 column \xB7 \u2191\u2193 task \xB7 Enter open \xB7 n new \xB7 p project";
|
|
137130
|
+
const submitNewTask = async () => {
|
|
137131
|
+
const title = newTaskTitle.trim();
|
|
137132
|
+
if (!title) {
|
|
137133
|
+
setCreateError("Title cannot be empty");
|
|
137134
|
+
return;
|
|
137135
|
+
}
|
|
137136
|
+
if (!state.interactiveData || !selectedProject) {
|
|
137137
|
+
setCreateError("No project selected");
|
|
137138
|
+
return;
|
|
137139
|
+
}
|
|
137140
|
+
setCreating(true);
|
|
137141
|
+
setCreateError(null);
|
|
137142
|
+
try {
|
|
137143
|
+
await state.interactiveData.createTask(selectedProject.path, { title });
|
|
137144
|
+
setNewTaskTitle("");
|
|
137145
|
+
setSubView("board");
|
|
137146
|
+
tasksState.refresh();
|
|
137147
|
+
} catch (err) {
|
|
137148
|
+
setCreateError(err instanceof Error ? err.message : String(err));
|
|
137149
|
+
} finally {
|
|
137150
|
+
setCreating(false);
|
|
137151
|
+
}
|
|
137152
|
+
};
|
|
137153
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
|
|
137154
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, paddingX: 1, children: [
|
|
137155
|
+
/* @__PURE__ */ jsx(
|
|
137156
|
+
ProjectSelector,
|
|
137157
|
+
{
|
|
137158
|
+
open: subView === "picker",
|
|
137159
|
+
projects: projectsState.projects,
|
|
137160
|
+
selectedIndex: projectIndex,
|
|
137161
|
+
onSelect: setProjectIndex
|
|
137162
|
+
}
|
|
137163
|
+
),
|
|
137164
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
|
|
137165
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: hintText })
|
|
137166
|
+
] }),
|
|
137167
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137168
|
+
subView === "create" ? /* @__PURE__ */ jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: /* @__PURE__ */ jsxs(
|
|
137169
|
+
Box,
|
|
137170
|
+
{
|
|
137171
|
+
borderStyle: "round",
|
|
137172
|
+
borderColor: "cyan",
|
|
137173
|
+
flexDirection: "column",
|
|
137174
|
+
paddingX: 2,
|
|
137175
|
+
paddingY: 1,
|
|
137176
|
+
width: Math.min(80, Math.max(40, cols - 8)),
|
|
137177
|
+
children: [
|
|
137178
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: "New Task" }),
|
|
137179
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
137180
|
+
"Project: ",
|
|
137181
|
+
selectedProject?.name ?? "(none)"
|
|
137182
|
+
] }),
|
|
137183
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137184
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Title" }),
|
|
137185
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
137186
|
+
/* @__PURE__ */ jsx(Text, { color: "cyanBright", children: "\u25B8 " }),
|
|
137187
|
+
/* @__PURE__ */ jsx(
|
|
137188
|
+
TextInput,
|
|
137189
|
+
{
|
|
137190
|
+
value: newTaskTitle,
|
|
137191
|
+
onChange: setNewTaskTitle,
|
|
137192
|
+
onSubmit: () => void submitNewTask(),
|
|
137193
|
+
placeholder: "What needs doing?"
|
|
137194
|
+
}
|
|
137195
|
+
)
|
|
137196
|
+
] }),
|
|
137197
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137198
|
+
createError && /* @__PURE__ */ jsx(Text, { color: "red", children: createError }),
|
|
137199
|
+
creating ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
137200
|
+
/* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
|
|
137201
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Creating\u2026" })
|
|
137202
|
+
] }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Enter to create \xB7 Esc to cancel" })
|
|
137203
|
+
]
|
|
137204
|
+
}
|
|
137205
|
+
) }) : subView === "detail" && selectedTask ? /* @__PURE__ */ jsx(Box, { flexGrow: 1, paddingX: 1, children: /* @__PURE__ */ jsx(TaskDetailScreen, { task: selectedTask }) }) : tasksState.loading ? /* @__PURE__ */ jsxs(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, gap: 1, children: [
|
|
137206
|
+
/* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
|
|
137207
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading tasks\u2026" })
|
|
137208
|
+
] }) : tasksState.tasks.length === 0 ? /* @__PURE__ */ jsxs(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, flexDirection: "column", children: [
|
|
137209
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "No tasks in this project." }),
|
|
137210
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press [p] to switch projects." })
|
|
137211
|
+
] }) : /* @__PURE__ */ jsx(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: KANBAN_COLUMNS.map((col, i) => /* @__PURE__ */ jsx(
|
|
137212
|
+
KanbanColumnView,
|
|
137213
|
+
{
|
|
137214
|
+
column: col,
|
|
137215
|
+
tasks: grouped[col],
|
|
137216
|
+
isFocused: i === colIndex,
|
|
137217
|
+
selectedIndex: rowByColumn[col] ?? 0,
|
|
137218
|
+
width: columnWidth
|
|
137219
|
+
},
|
|
137220
|
+
col
|
|
137221
|
+
)) })
|
|
137222
|
+
] });
|
|
137223
|
+
}
|
|
137224
|
+
function agentStateColor(state) {
|
|
137225
|
+
switch (state) {
|
|
137226
|
+
case "active":
|
|
137227
|
+
return "cyan";
|
|
137228
|
+
case "running":
|
|
137229
|
+
return "green";
|
|
137230
|
+
case "error":
|
|
137231
|
+
return "red";
|
|
137232
|
+
default:
|
|
137233
|
+
return "gray";
|
|
137234
|
+
}
|
|
137235
|
+
}
|
|
137236
|
+
function heartbeatFreshness(lastHeartbeatAt) {
|
|
137237
|
+
if (!lastHeartbeatAt) return { fresh: false, label: "never" };
|
|
137238
|
+
const ageMs = Date.now() - new Date(lastHeartbeatAt).getTime();
|
|
137239
|
+
return { fresh: ageMs < 5 * 60 * 1e3, label: formatRelativeTime(lastHeartbeatAt) };
|
|
137240
|
+
}
|
|
137241
|
+
function AgentsView({ state }) {
|
|
137242
|
+
const { stdout } = useStdout();
|
|
137243
|
+
const cols = stdout?.columns ?? 80;
|
|
137244
|
+
const isNarrow = cols < 80;
|
|
137245
|
+
const [selectedIndex, setSelectedIndex] = useState2(0);
|
|
137246
|
+
const [agents, setAgents] = useState2([]);
|
|
137247
|
+
const [detail, setDetail] = useState2(null);
|
|
137248
|
+
const [loadingDetail, setLoadingDetail] = useState2(false);
|
|
137249
|
+
const [subView, setSubView] = useState2("list");
|
|
137250
|
+
const [statusMsg, setStatusMsg] = useState2(null);
|
|
137251
|
+
const [detailFocused, setDetailFocused] = useState2(false);
|
|
137252
|
+
const data = state.interactiveData;
|
|
137253
|
+
useEffect2(() => {
|
|
137254
|
+
if (!data) return;
|
|
137255
|
+
data.listAgents().then(setAgents).catch(() => setAgents([]));
|
|
137256
|
+
}, [data]);
|
|
137257
|
+
const selectedAgent = agents[selectedIndex] ?? null;
|
|
137258
|
+
useEffect2(() => {
|
|
137259
|
+
if (!data || !selectedAgent) {
|
|
137260
|
+
setDetail(null);
|
|
137261
|
+
return;
|
|
137262
|
+
}
|
|
137263
|
+
setLoadingDetail(true);
|
|
137264
|
+
data.getAgentDetail(selectedAgent.id).then((d) => {
|
|
137265
|
+
setDetail(d);
|
|
137266
|
+
setLoadingDetail(false);
|
|
137267
|
+
}).catch(() => {
|
|
137268
|
+
setDetail(null);
|
|
137269
|
+
setLoadingDetail(false);
|
|
137270
|
+
});
|
|
137271
|
+
}, [data, selectedAgent?.id]);
|
|
137272
|
+
function refreshDetail() {
|
|
137273
|
+
if (!data || !selectedAgent) return;
|
|
137274
|
+
setLoadingDetail(true);
|
|
137275
|
+
data.getAgentDetail(selectedAgent.id).then((d) => {
|
|
137276
|
+
setDetail(d);
|
|
137277
|
+
setLoadingDetail(false);
|
|
137278
|
+
}).catch(() => {
|
|
137279
|
+
setDetail(null);
|
|
137280
|
+
setLoadingDetail(false);
|
|
136208
137281
|
});
|
|
136209
137282
|
}
|
|
136210
|
-
|
|
136211
|
-
|
|
136212
|
-
const
|
|
136213
|
-
|
|
136214
|
-
|
|
136215
|
-
|
|
137283
|
+
async function refreshList() {
|
|
137284
|
+
if (!data) return;
|
|
137285
|
+
const list = await data.listAgents().catch(() => []);
|
|
137286
|
+
setAgents(list);
|
|
137287
|
+
setSelectedIndex((i) => Math.min(i, Math.max(0, list.length - 1)));
|
|
137288
|
+
}
|
|
137289
|
+
useInput((input, key) => {
|
|
137290
|
+
if (subView === "confirm-delete") {
|
|
137291
|
+
if (input === "y" || input === "Y") {
|
|
137292
|
+
if (!data || !selectedAgent) {
|
|
137293
|
+
setSubView("list");
|
|
137294
|
+
return;
|
|
137295
|
+
}
|
|
137296
|
+
data.deleteAgent(selectedAgent.id).then(() => {
|
|
137297
|
+
setStatusMsg(`Deleted agent ${selectedAgent.name}`);
|
|
137298
|
+
return refreshList();
|
|
137299
|
+
}).catch((err) => setStatusMsg(`Error: ${err instanceof Error ? err.message : String(err)}`)).finally(() => setSubView("list"));
|
|
137300
|
+
return;
|
|
137301
|
+
}
|
|
137302
|
+
setSubView("list");
|
|
137303
|
+
return;
|
|
136216
137304
|
}
|
|
136217
|
-
|
|
136218
|
-
|
|
136219
|
-
|
|
136220
|
-
|
|
137305
|
+
if (key.tab) {
|
|
137306
|
+
setDetailFocused((f) => !f);
|
|
137307
|
+
return;
|
|
137308
|
+
}
|
|
137309
|
+
if (!detailFocused) {
|
|
137310
|
+
if (key.upArrow || input === "k") {
|
|
137311
|
+
setSelectedIndex((i) => Math.max(0, i - 1));
|
|
137312
|
+
return;
|
|
137313
|
+
}
|
|
137314
|
+
if (key.downArrow || input === "j") {
|
|
137315
|
+
setSelectedIndex((i) => Math.min(agents.length - 1, i + 1));
|
|
137316
|
+
return;
|
|
137317
|
+
}
|
|
137318
|
+
}
|
|
137319
|
+
if (input === "s") {
|
|
137320
|
+
if (!data || !selectedAgent) return;
|
|
137321
|
+
data.updateAgentState(selectedAgent.id, "active").then(() => {
|
|
137322
|
+
setStatusMsg("Agent started");
|
|
137323
|
+
return refreshList();
|
|
137324
|
+
}).catch((err) => setStatusMsg(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
137325
|
+
return;
|
|
137326
|
+
}
|
|
137327
|
+
if (input === "x") {
|
|
137328
|
+
if (!data || !selectedAgent) return;
|
|
137329
|
+
data.updateAgentState(selectedAgent.id, "idle").then(() => {
|
|
137330
|
+
setStatusMsg("Agent stopped");
|
|
137331
|
+
return refreshList();
|
|
137332
|
+
}).catch((err) => setStatusMsg(`Error: ${err instanceof Error ? err.message : String(err)}`));
|
|
137333
|
+
return;
|
|
137334
|
+
}
|
|
137335
|
+
if (input === "D") {
|
|
137336
|
+
if (selectedAgent) setSubView("confirm-delete");
|
|
137337
|
+
return;
|
|
137338
|
+
}
|
|
137339
|
+
if (input === "r") {
|
|
137340
|
+
refreshDetail();
|
|
137341
|
+
return;
|
|
137342
|
+
}
|
|
137343
|
+
});
|
|
137344
|
+
if (subView === "confirm-delete" && selectedAgent) {
|
|
137345
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, justifyContent: "center", alignItems: "center", children: /* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor: "red", flexDirection: "column", paddingX: 2, paddingY: 1, children: [
|
|
137346
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "red", children: "Delete agent?" }),
|
|
137347
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137348
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
137349
|
+
"Agent: ",
|
|
137350
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "whiteBright", children: selectedAgent.name })
|
|
137351
|
+
] }),
|
|
137352
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
137353
|
+
"ID: ",
|
|
137354
|
+
selectedAgent.id
|
|
137355
|
+
] }),
|
|
137356
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137357
|
+
/* @__PURE__ */ jsx(Text, { children: "[y] confirm delete [any other key] cancel" })
|
|
137358
|
+
] }) });
|
|
137359
|
+
}
|
|
137360
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
|
|
137361
|
+
statusMsg && /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: statusMsg }) }),
|
|
137362
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: isNarrow ? "column" : "row", flexGrow: 1, overflow: "hidden", children: [
|
|
137363
|
+
/* @__PURE__ */ jsxs(
|
|
137364
|
+
Box,
|
|
137365
|
+
{
|
|
137366
|
+
borderStyle: "round",
|
|
137367
|
+
borderColor: detailFocused ? "gray" : "cyan",
|
|
137368
|
+
flexDirection: "column",
|
|
137369
|
+
width: isNarrow ? void 0 : "30%",
|
|
137370
|
+
flexGrow: isNarrow ? 1 : 0,
|
|
137371
|
+
flexShrink: 0,
|
|
137372
|
+
overflow: "hidden",
|
|
137373
|
+
children: [
|
|
137374
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { bold: !detailFocused, color: !detailFocused ? "cyan" : void 0, dimColor: detailFocused, children: [
|
|
137375
|
+
"Agents (",
|
|
137376
|
+
agents.length,
|
|
137377
|
+
")"
|
|
137378
|
+
] }) }),
|
|
137379
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: agents.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No agents found." }) : agents.map((agent, i) => {
|
|
137380
|
+
const isSel = i === selectedIndex;
|
|
137381
|
+
const { fresh, label } = heartbeatFreshness(agent.lastHeartbeatAt);
|
|
137382
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
137383
|
+
/* @__PURE__ */ jsx(Text, { color: isSel ? "cyanBright" : "gray", children: isSel ? "\u25B6" : " " }),
|
|
137384
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
|
|
137385
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
137386
|
+
/* @__PURE__ */ jsx(Text, { bold: isSel, color: isSel ? "whiteBright" : void 0, wrap: "truncate", children: agent.name }),
|
|
137387
|
+
/* @__PURE__ */ jsx(Text, { color: agentStateColor(agent.state), children: agent.state })
|
|
137388
|
+
] }),
|
|
137389
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
137390
|
+
/* @__PURE__ */ jsx(Text, { color: fresh ? "green" : "gray", dimColor: true, children: "\u25CF" }),
|
|
137391
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: label })
|
|
137392
|
+
] })
|
|
137393
|
+
] })
|
|
137394
|
+
] }, agent.id);
|
|
137395
|
+
}) })
|
|
137396
|
+
]
|
|
137397
|
+
}
|
|
137398
|
+
),
|
|
137399
|
+
/* @__PURE__ */ jsxs(
|
|
137400
|
+
Box,
|
|
137401
|
+
{
|
|
137402
|
+
borderStyle: "round",
|
|
137403
|
+
borderColor: detailFocused ? "cyan" : "gray",
|
|
137404
|
+
flexDirection: "column",
|
|
137405
|
+
flexGrow: 1,
|
|
137406
|
+
overflow: "hidden",
|
|
137407
|
+
children: [
|
|
137408
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: detailFocused, color: detailFocused ? "cyan" : void 0, dimColor: !detailFocused, children: "Agent Detail" }) }),
|
|
137409
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: !selectedAgent ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Select an agent from the list." }) : loadingDetail ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
137410
|
+
/* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
|
|
137411
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading\u2026" })
|
|
137412
|
+
] }) : !detail ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Could not load agent detail." }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
137413
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "whiteBright", children: detail.name }),
|
|
137414
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: detail.id }),
|
|
137415
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137416
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
137417
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "State:" }),
|
|
137418
|
+
/* @__PURE__ */ jsx(Text, { color: agentStateColor(detail.state), bold: true, children: detail.state })
|
|
137419
|
+
] }),
|
|
137420
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
137421
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Role:" }),
|
|
137422
|
+
/* @__PURE__ */ jsx(Text, { children: detail.role })
|
|
137423
|
+
] }),
|
|
137424
|
+
detail.title && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
137425
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Title:" }),
|
|
137426
|
+
/* @__PURE__ */ jsx(Text, { children: detail.title })
|
|
137427
|
+
] }),
|
|
137428
|
+
detail.taskId && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
137429
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Task:" }),
|
|
137430
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: detail.taskId })
|
|
137431
|
+
] }),
|
|
137432
|
+
detail.capabilities.length > 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
137433
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Caps:" }),
|
|
137434
|
+
/* @__PURE__ */ jsx(Text, { children: detail.capabilities.join(", ") })
|
|
137435
|
+
] }),
|
|
137436
|
+
detail.recentRuns.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
137437
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137438
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Recent runs (latest first):" }),
|
|
137439
|
+
detail.recentRuns.slice(0, 5).map((run) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginLeft: 1, children: [
|
|
137440
|
+
/* @__PURE__ */ jsx(Text, { color: run.status === "completed" ? "green" : run.status === "failed" ? "red" : "yellow", children: run.status.slice(0, 4) }),
|
|
137441
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: run.startedAt.slice(11, 19) }),
|
|
137442
|
+
run.triggerDetail && /* @__PURE__ */ jsx(Text, { dimColor: true, children: run.triggerDetail })
|
|
137443
|
+
] }, run.id))
|
|
137444
|
+
] })
|
|
137445
|
+
] }) })
|
|
137446
|
+
]
|
|
137447
|
+
}
|
|
137448
|
+
)
|
|
137449
|
+
] }),
|
|
137450
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[s] start [x] stop [D] delete [r] refresh [Tab] focus \u2191\u2193 select" }) })
|
|
137451
|
+
] });
|
|
137452
|
+
}
|
|
137453
|
+
function SettingsInteractiveView({ state }) {
|
|
137454
|
+
const [selectedIndex, setSelectedIndex] = useState2(0);
|
|
137455
|
+
const [localSettings, setLocalSettings] = useState2(null);
|
|
137456
|
+
const [models, setModels] = useState2([]);
|
|
137457
|
+
const [saving, setSaving] = useState2(false);
|
|
137458
|
+
const [statusMsg, setStatusMsg] = useState2(null);
|
|
137459
|
+
const [detailFocused, setDetailFocused] = useState2(false);
|
|
137460
|
+
const data = state.interactiveData;
|
|
137461
|
+
useEffect2(() => {
|
|
137462
|
+
if (!data) return;
|
|
137463
|
+
data.getSettings().then(setLocalSettings).catch(() => {
|
|
137464
|
+
});
|
|
137465
|
+
setModels(data.listModels());
|
|
137466
|
+
}, [data]);
|
|
137467
|
+
const selectedDef = SETTING_DEFS[selectedIndex];
|
|
137468
|
+
async function saveField(partial) {
|
|
137469
|
+
if (!data || !localSettings) return;
|
|
137470
|
+
setSaving(true);
|
|
137471
|
+
try {
|
|
137472
|
+
await data.updateSettings(partial);
|
|
137473
|
+
const updated = await data.getSettings();
|
|
137474
|
+
setLocalSettings(updated);
|
|
137475
|
+
setStatusMsg("Saved");
|
|
137476
|
+
} catch (err) {
|
|
137477
|
+
setStatusMsg(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
137478
|
+
} finally {
|
|
137479
|
+
setSaving(false);
|
|
136221
137480
|
}
|
|
136222
137481
|
}
|
|
136223
|
-
|
|
136224
|
-
|
|
136225
|
-
|
|
136226
|
-
|
|
136227
|
-
}
|
|
136228
|
-
|
|
136229
|
-
|
|
136230
|
-
|
|
136231
|
-
|
|
136232
|
-
|
|
136233
|
-
|
|
136234
|
-
|
|
136235
|
-
return
|
|
136236
|
-
} catch {
|
|
136237
|
-
return String(arg);
|
|
137482
|
+
useInput((input, key) => {
|
|
137483
|
+
if (key.tab) {
|
|
137484
|
+
setDetailFocused((f) => !f);
|
|
137485
|
+
return;
|
|
137486
|
+
}
|
|
137487
|
+
if (!detailFocused) {
|
|
137488
|
+
if (key.upArrow || input === "k") {
|
|
137489
|
+
setSelectedIndex((i) => Math.max(0, i - 1));
|
|
137490
|
+
return;
|
|
137491
|
+
}
|
|
137492
|
+
if (key.downArrow || input === "j") {
|
|
137493
|
+
setSelectedIndex((i) => Math.min(SETTING_DEFS.length - 1, i + 1));
|
|
137494
|
+
return;
|
|
136238
137495
|
}
|
|
137496
|
+
return;
|
|
136239
137497
|
}
|
|
136240
|
-
|
|
136241
|
-
|
|
136242
|
-
|
|
136243
|
-
|
|
136244
|
-
|
|
136245
|
-
|
|
136246
|
-
|
|
136247
|
-
|
|
137498
|
+
if (!selectedDef || !localSettings) return;
|
|
137499
|
+
if (selectedDef.type === "boolean" && input === " ") {
|
|
137500
|
+
const current = localSettings[selectedDef.key];
|
|
137501
|
+
const updated = { ...localSettings, [selectedDef.key]: !current };
|
|
137502
|
+
setLocalSettings(updated);
|
|
137503
|
+
void saveField({ [selectedDef.key]: !current });
|
|
137504
|
+
return;
|
|
137505
|
+
}
|
|
137506
|
+
if (selectedDef.type === "number") {
|
|
137507
|
+
const current = localSettings[selectedDef.key];
|
|
137508
|
+
if (input === "+" || input === "=") {
|
|
137509
|
+
const step = selectedDef.key === "pollIntervalMs" ? 5e3 : 1;
|
|
137510
|
+
const updated = { ...localSettings, [selectedDef.key]: current + step };
|
|
137511
|
+
setLocalSettings(updated);
|
|
137512
|
+
void saveField({ [selectedDef.key]: current + step });
|
|
137513
|
+
return;
|
|
137514
|
+
}
|
|
137515
|
+
if (input === "-" || input === "_") {
|
|
137516
|
+
const step = selectedDef.key === "pollIntervalMs" ? 5e3 : 1;
|
|
137517
|
+
const newVal = Math.max(0, current - step);
|
|
137518
|
+
const updated = { ...localSettings, [selectedDef.key]: newVal };
|
|
137519
|
+
setLocalSettings(updated);
|
|
137520
|
+
void saveField({ [selectedDef.key]: newVal });
|
|
137521
|
+
return;
|
|
137522
|
+
}
|
|
137523
|
+
}
|
|
137524
|
+
if (selectedDef.type === "enum" && selectedDef.options) {
|
|
137525
|
+
const current = localSettings[selectedDef.key];
|
|
137526
|
+
const idx = selectedDef.options.indexOf(current);
|
|
137527
|
+
if (key.rightArrow || input === "l") {
|
|
137528
|
+
const next = selectedDef.options[(idx + 1) % selectedDef.options.length];
|
|
137529
|
+
const updated = { ...localSettings, [selectedDef.key]: next };
|
|
137530
|
+
setLocalSettings(updated);
|
|
137531
|
+
void saveField({ [selectedDef.key]: next });
|
|
137532
|
+
return;
|
|
137533
|
+
}
|
|
137534
|
+
if (key.leftArrow || input === "h") {
|
|
137535
|
+
const prev = selectedDef.options[(idx - 1 + selectedDef.options.length) % selectedDef.options.length];
|
|
137536
|
+
const updated = { ...localSettings, [selectedDef.key]: prev };
|
|
137537
|
+
setLocalSettings(updated);
|
|
137538
|
+
void saveField({ [selectedDef.key]: prev });
|
|
137539
|
+
return;
|
|
137540
|
+
}
|
|
137541
|
+
}
|
|
137542
|
+
});
|
|
137543
|
+
function renderValue(def, settings) {
|
|
137544
|
+
const v = settings[def.key];
|
|
137545
|
+
if (def.type === "boolean") {
|
|
137546
|
+
return /* @__PURE__ */ jsx(Text, { color: v ? "green" : "yellow", children: v ? "enabled" : "disabled" });
|
|
137547
|
+
}
|
|
137548
|
+
if (def.type === "enum") {
|
|
137549
|
+
return /* @__PURE__ */ jsx(Text, { color: "cyan", children: String(v) });
|
|
137550
|
+
}
|
|
137551
|
+
return /* @__PURE__ */ jsx(Text, { children: String(v) });
|
|
136248
137552
|
}
|
|
136249
|
-
return {
|
|
136250
|
-
}
|
|
136251
|
-
|
|
136252
|
-
|
|
136253
|
-
|
|
136254
|
-
|
|
136255
|
-
|
|
136256
|
-
|
|
136257
|
-
|
|
136258
|
-
|
|
136259
|
-
|
|
136260
|
-
|
|
136261
|
-
|
|
136262
|
-
|
|
136263
|
-
|
|
136264
|
-
|
|
136265
|
-
|
|
136266
|
-
|
|
136267
|
-
|
|
136268
|
-
|
|
136269
|
-
|
|
136270
|
-
|
|
136271
|
-
|
|
136272
|
-
} else {
|
|
136273
|
-
this.entries[this.count % MAX_LOG_ENTRIES] = entry;
|
|
137553
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
|
|
137554
|
+
statusMsg && /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: saving ? "Saving\u2026" : statusMsg }) }),
|
|
137555
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [
|
|
137556
|
+
/* @__PURE__ */ jsxs(
|
|
137557
|
+
Box,
|
|
137558
|
+
{
|
|
137559
|
+
borderStyle: "round",
|
|
137560
|
+
borderColor: detailFocused ? "gray" : "cyan",
|
|
137561
|
+
flexDirection: "column",
|
|
137562
|
+
width: "35%",
|
|
137563
|
+
overflow: "hidden",
|
|
137564
|
+
children: [
|
|
137565
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: !detailFocused, color: !detailFocused ? "cyan" : void 0, dimColor: detailFocused, children: "Settings" }) }),
|
|
137566
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: !localSettings ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading\u2026" }) : SETTING_DEFS.map((def, i) => {
|
|
137567
|
+
const isSel = i === selectedIndex;
|
|
137568
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
137569
|
+
/* @__PURE__ */ jsx(Text, { color: isSel ? "cyanBright" : "gray", children: isSel ? "\u25B6" : " " }),
|
|
137570
|
+
/* @__PURE__ */ jsx(Text, { bold: isSel, color: isSel ? "whiteBright" : void 0, wrap: "truncate", children: def.label }),
|
|
137571
|
+
/* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
|
|
137572
|
+
renderValue(def, localSettings)
|
|
137573
|
+
] }, def.key);
|
|
137574
|
+
}) })
|
|
137575
|
+
]
|
|
136274
137576
|
}
|
|
136275
|
-
|
|
137577
|
+
),
|
|
137578
|
+
/* @__PURE__ */ jsxs(
|
|
137579
|
+
Box,
|
|
137580
|
+
{
|
|
137581
|
+
borderStyle: "round",
|
|
137582
|
+
borderColor: detailFocused ? "cyan" : "gray",
|
|
137583
|
+
flexDirection: "column",
|
|
137584
|
+
flexGrow: 1,
|
|
137585
|
+
overflow: "hidden",
|
|
137586
|
+
children: [
|
|
137587
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: detailFocused, color: detailFocused ? "cyan" : void 0, dimColor: !detailFocused, children: "Edit / Models" }) }),
|
|
137588
|
+
/* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: !localSettings ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading settings\u2026" }) : !selectedDef ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
137589
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "whiteBright", children: selectedDef.label }),
|
|
137590
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137591
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
137592
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Current:" }),
|
|
137593
|
+
renderValue(selectedDef, localSettings)
|
|
137594
|
+
] }),
|
|
137595
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137596
|
+
selectedDef.type === "boolean" && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[Space] toggle" }),
|
|
137597
|
+
selectedDef.type === "number" && /* @__PURE__ */ jsx(Text, { dimColor: true, children: selectedDef.key === "pollIntervalMs" ? "[+/-] adjust by 5000ms" : "[+/-] adjust by 1" }),
|
|
137598
|
+
selectedDef.type === "enum" && selectedDef.options && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
137599
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "[\u2190/\u2192] cycle options:" }),
|
|
137600
|
+
selectedDef.options.map((opt) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginLeft: 1, children: [
|
|
137601
|
+
/* @__PURE__ */ jsx(Text, { color: localSettings[selectedDef.key] === opt ? "cyanBright" : "gray", children: localSettings[selectedDef.key] === opt ? "\u25B6" : " " }),
|
|
137602
|
+
/* @__PURE__ */ jsx(Text, { children: opt })
|
|
137603
|
+
] }, opt))
|
|
137604
|
+
] }),
|
|
137605
|
+
models.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
137606
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137607
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500 Available Models \u2500\u2500\u2500\u2500" }),
|
|
137608
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "Configure default model in web dashboard" }),
|
|
137609
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137610
|
+
models.slice(0, 8).map((m) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
137611
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: m.provider }),
|
|
137612
|
+
/* @__PURE__ */ jsx(Text, { wrap: "truncate", children: m.name }),
|
|
137613
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
137614
|
+
Math.round(m.contextWindow / 1e3),
|
|
137615
|
+
"k ctx"
|
|
137616
|
+
] })
|
|
137617
|
+
] }, `${m.provider}/${m.id}`)),
|
|
137618
|
+
models.length > 8 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
137619
|
+
"\u2026 and ",
|
|
137620
|
+
models.length - 8,
|
|
137621
|
+
" more"
|
|
137622
|
+
] })
|
|
137623
|
+
] })
|
|
137624
|
+
] }) })
|
|
137625
|
+
]
|
|
137626
|
+
}
|
|
137627
|
+
)
|
|
137628
|
+
] }),
|
|
137629
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[Tab] switch panel \u2191\u2193 select setting [Space] toggle bool [+/-] adjust num [\u2190/\u2192] cycle enum" }) })
|
|
137630
|
+
] });
|
|
137631
|
+
}
|
|
137632
|
+
function InteractiveMode({ state }) {
|
|
137633
|
+
if (state.interactiveData === null) {
|
|
137634
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
|
|
137635
|
+
/* @__PURE__ */ jsx(InteractiveHeader, { activeView: state.interactiveView }),
|
|
137636
|
+
/* @__PURE__ */ jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Interactive mode unavailable \u2014 no data source" }) })
|
|
137637
|
+
] });
|
|
137638
|
+
}
|
|
137639
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
|
|
137640
|
+
/* @__PURE__ */ jsx(InteractiveHeader, { activeView: state.interactiveView }),
|
|
137641
|
+
/* @__PURE__ */ jsx(Box, { height: 1 }),
|
|
137642
|
+
/* @__PURE__ */ jsxs(Box, { flexGrow: 1, overflow: "hidden", children: [
|
|
137643
|
+
state.interactiveView === "board" && /* @__PURE__ */ jsx(BoardView, { state }),
|
|
137644
|
+
state.interactiveView === "agents" && /* @__PURE__ */ jsx(AgentsView, { state }),
|
|
137645
|
+
state.interactiveView === "settings" && /* @__PURE__ */ jsx(SettingsInteractiveView, { state })
|
|
137646
|
+
] })
|
|
137647
|
+
] });
|
|
137648
|
+
}
|
|
137649
|
+
function DashboardApp({ controller }) {
|
|
137650
|
+
const { exit } = useApp();
|
|
137651
|
+
const { stdout } = useStdout();
|
|
137652
|
+
const cols = stdout?.columns ?? 80;
|
|
137653
|
+
const rows = stdout?.rows ?? 24;
|
|
137654
|
+
const state = useSyncExternalStore(
|
|
137655
|
+
useCallback2((cb) => controller.subscribe(cb), [controller]),
|
|
137656
|
+
useCallback2(() => controller.getSnapshot(), [controller])
|
|
137657
|
+
);
|
|
137658
|
+
useInput((input, key) => {
|
|
137659
|
+
if (input === "q" || input === "Q" || key.ctrl && input === "c") {
|
|
137660
|
+
void controller.stop();
|
|
137661
|
+
exit();
|
|
137662
|
+
process.exit(0);
|
|
137663
|
+
}
|
|
137664
|
+
if (input === "b" || input === "B") {
|
|
137665
|
+
controller.setMode("interactive");
|
|
137666
|
+
controller.setInteractiveView("board");
|
|
137667
|
+
return;
|
|
137668
|
+
}
|
|
137669
|
+
if (input === "a" || input === "A") {
|
|
137670
|
+
controller.setMode("interactive");
|
|
137671
|
+
controller.setInteractiveView("agents");
|
|
137672
|
+
return;
|
|
137673
|
+
}
|
|
137674
|
+
if (input === "g" || input === "G") {
|
|
137675
|
+
controller.setMode("interactive");
|
|
137676
|
+
controller.setInteractiveView("settings");
|
|
137677
|
+
return;
|
|
137678
|
+
}
|
|
137679
|
+
if (input === "s" || input === "S") {
|
|
137680
|
+
if (state.mode === "interactive") {
|
|
137681
|
+
controller.setMode("status");
|
|
137682
|
+
return;
|
|
136276
137683
|
}
|
|
136277
|
-
|
|
136278
|
-
|
|
136279
|
-
|
|
137684
|
+
}
|
|
137685
|
+
if (state.mode === "interactive") {
|
|
137686
|
+
if (input === "1") {
|
|
137687
|
+
controller.setInteractiveView("board");
|
|
137688
|
+
return;
|
|
137689
|
+
}
|
|
137690
|
+
if (input === "2") {
|
|
137691
|
+
controller.setInteractiveView("agents");
|
|
137692
|
+
return;
|
|
137693
|
+
}
|
|
137694
|
+
if (input === "3") {
|
|
137695
|
+
controller.setInteractiveView("settings");
|
|
137696
|
+
return;
|
|
137697
|
+
}
|
|
137698
|
+
return;
|
|
137699
|
+
}
|
|
137700
|
+
if (input === "?" || input === "h" || input === "H") {
|
|
137701
|
+
controller.setShowHelp(!state.showHelp);
|
|
137702
|
+
return;
|
|
137703
|
+
}
|
|
137704
|
+
if (key.return && state.activeSection === "system" && state.systemInfo) {
|
|
137705
|
+
const url = state.systemInfo.tokenizedUrl ?? state.systemInfo.baseUrl;
|
|
137706
|
+
openInBrowser(url);
|
|
137707
|
+
return;
|
|
137708
|
+
}
|
|
137709
|
+
if (input >= "1" && input <= "5") {
|
|
137710
|
+
const section = SECTION_ORDER[parseInt(input, 10) - 1];
|
|
137711
|
+
if (section) {
|
|
137712
|
+
controller.setActiveSection(section);
|
|
137713
|
+
}
|
|
137714
|
+
return;
|
|
137715
|
+
}
|
|
137716
|
+
if (key.tab) {
|
|
137717
|
+
const shift = key.shift;
|
|
137718
|
+
const idx = PANEL_ORDER.indexOf(state.activeSection);
|
|
137719
|
+
if (shift) {
|
|
137720
|
+
controller.setActiveSection(PANEL_ORDER[(idx - 1 + PANEL_ORDER.length) % PANEL_ORDER.length]);
|
|
137721
|
+
} else {
|
|
137722
|
+
controller.setActiveSection(PANEL_ORDER[(idx + 1) % PANEL_ORDER.length]);
|
|
137723
|
+
}
|
|
137724
|
+
return;
|
|
137725
|
+
}
|
|
137726
|
+
if (key.rightArrow || input === "n" || input === "N") {
|
|
137727
|
+
if (state.activeSection !== "logs" || !state.logsExpandedMode) {
|
|
137728
|
+
controller.cycleSection(1);
|
|
137729
|
+
}
|
|
137730
|
+
return;
|
|
137731
|
+
}
|
|
137732
|
+
if (key.leftArrow || input === "p" || input === "P") {
|
|
137733
|
+
if (state.activeSection !== "logs" || !state.logsExpandedMode) {
|
|
137734
|
+
controller.cycleSection(-1);
|
|
137735
|
+
}
|
|
137736
|
+
return;
|
|
137737
|
+
}
|
|
137738
|
+
if (state.activeSection === "utilities") {
|
|
137739
|
+
void controller.handleUtilityAction(input);
|
|
137740
|
+
return;
|
|
137741
|
+
}
|
|
137742
|
+
if (state.activeSection === "logs") {
|
|
137743
|
+
const filteredEntries = controller.getFilteredLogEntries();
|
|
137744
|
+
if (key.escape) {
|
|
137745
|
+
if (state.logsExpandedMode) {
|
|
137746
|
+
controller.setLogsExpandedMode(false);
|
|
137747
|
+
controller.setShowHelp(false);
|
|
137748
|
+
} else if (state.showHelp) {
|
|
137749
|
+
controller.setShowHelp(false);
|
|
136280
137750
|
}
|
|
136281
|
-
|
|
136282
|
-
return [
|
|
136283
|
-
...this.entries.slice(start),
|
|
136284
|
-
...this.entries.slice(0, start)
|
|
136285
|
-
];
|
|
137751
|
+
return;
|
|
136286
137752
|
}
|
|
136287
|
-
|
|
136288
|
-
|
|
136289
|
-
|
|
137753
|
+
if (key.return || input === " " || input === "e" || input === "E") {
|
|
137754
|
+
if (filteredEntries.length > 0) {
|
|
137755
|
+
controller.setLogsExpandedMode(!state.logsExpandedMode);
|
|
137756
|
+
}
|
|
137757
|
+
return;
|
|
136290
137758
|
}
|
|
136291
|
-
|
|
136292
|
-
|
|
137759
|
+
if (input === "w" || input === "W") {
|
|
137760
|
+
controller.setLogsWrapEnabled(!state.logsWrapEnabled);
|
|
137761
|
+
return;
|
|
136293
137762
|
}
|
|
136294
|
-
|
|
136295
|
-
|
|
137763
|
+
if (input === "f" || input === "F") {
|
|
137764
|
+
controller.cycleSeverityFilter();
|
|
137765
|
+
return;
|
|
137766
|
+
}
|
|
137767
|
+
if (key.upArrow || input === "k" || input === "K") {
|
|
137768
|
+
if (state.selectedLogIndex > 0) {
|
|
137769
|
+
controller.setSelectedLogIndex(state.selectedLogIndex - 1);
|
|
137770
|
+
}
|
|
137771
|
+
return;
|
|
137772
|
+
}
|
|
137773
|
+
if (key.downArrow || input === "j" || input === "J") {
|
|
137774
|
+
if (state.selectedLogIndex < filteredEntries.length - 1) {
|
|
137775
|
+
controller.setSelectedLogIndex(state.selectedLogIndex + 1);
|
|
137776
|
+
}
|
|
137777
|
+
return;
|
|
137778
|
+
}
|
|
137779
|
+
if (key.home) {
|
|
137780
|
+
controller.setSelectedLogIndex(0);
|
|
137781
|
+
return;
|
|
137782
|
+
}
|
|
137783
|
+
if (key.end) {
|
|
137784
|
+
controller.setSelectedLogIndex(Math.max(0, filteredEntries.length - 1));
|
|
137785
|
+
return;
|
|
137786
|
+
}
|
|
137787
|
+
}
|
|
137788
|
+
});
|
|
137789
|
+
if (!state.systemInfo) {
|
|
137790
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", height: rows, children: /* @__PURE__ */ jsx(SplashScreen, { loadingStatus: state.loadingStatus }) });
|
|
137791
|
+
}
|
|
137792
|
+
const isNarrow = cols < 80 || rows < 20;
|
|
137793
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: rows, children: [
|
|
137794
|
+
state.mode === "interactive" ? /* @__PURE__ */ jsx(InteractiveMode, { state }) : isNarrow ? /* @__PURE__ */ jsx(StatusModeSingle, { state, controller }) : /* @__PURE__ */ jsx(StatusModeGrid, { state, rows, controller }),
|
|
137795
|
+
state.showHelp && /* @__PURE__ */ jsx(Box, { position: "absolute", marginTop: 3, marginLeft: 4, children: /* @__PURE__ */ jsx(HelpOverlay, {}) })
|
|
137796
|
+
] });
|
|
137797
|
+
}
|
|
137798
|
+
var LOGO_COLORS, SPLASH_MIN_COLS, SPLASH_MIN_ROWS, PREFIX_WIDTH, PANEL_ORDER, KANBAN_COLUMNS, COLUMN_COLORS, SETTING_DEFS;
|
|
137799
|
+
var init_app = __esm({
|
|
137800
|
+
"src/commands/dashboard-tui/app.tsx"() {
|
|
137801
|
+
"use strict";
|
|
137802
|
+
init_state();
|
|
137803
|
+
init_logo();
|
|
137804
|
+
init_use_projects();
|
|
137805
|
+
LOGO_COLORS = ["whiteBright", "cyanBright", "cyan", "blueBright", "blue", "blue"];
|
|
137806
|
+
SPLASH_MIN_COLS = 56;
|
|
137807
|
+
SPLASH_MIN_ROWS = 20;
|
|
137808
|
+
PREFIX_WIDTH = 14;
|
|
137809
|
+
PANEL_ORDER = ["system", "logs", "utilities", "stats", "settings"];
|
|
137810
|
+
KANBAN_COLUMNS = ["todo", "in-progress", "in-review", "done"];
|
|
137811
|
+
COLUMN_COLORS = {
|
|
137812
|
+
todo: "yellow",
|
|
137813
|
+
"in-progress": "cyan",
|
|
137814
|
+
"in-review": "magenta",
|
|
137815
|
+
done: "green"
|
|
137816
|
+
};
|
|
137817
|
+
SETTING_DEFS = [
|
|
137818
|
+
{ key: "maxConcurrent", label: "Max Concurrent", type: "number" },
|
|
137819
|
+
{ key: "maxWorktrees", label: "Max Worktrees", type: "number" },
|
|
137820
|
+
{ key: "autoMerge", label: "Auto Merge", type: "boolean" },
|
|
137821
|
+
{ key: "mergeStrategy", label: "Merge Strategy", type: "enum", options: ["direct", "squash", "rebase"] },
|
|
137822
|
+
{ key: "pollIntervalMs", label: "Poll Interval (ms)", type: "number" },
|
|
137823
|
+
{ key: "enginePaused", label: "Engine Paused", type: "boolean" },
|
|
137824
|
+
{ key: "globalPause", label: "Global Pause", type: "boolean" }
|
|
137825
|
+
];
|
|
137826
|
+
}
|
|
137827
|
+
});
|
|
137828
|
+
|
|
137829
|
+
// src/commands/dashboard-tui/controller.ts
|
|
137830
|
+
var DashboardTUI;
|
|
137831
|
+
var init_controller = __esm({
|
|
137832
|
+
"src/commands/dashboard-tui/controller.ts"() {
|
|
137833
|
+
"use strict";
|
|
137834
|
+
init_log_ring_buffer();
|
|
137835
|
+
init_state();
|
|
136296
137836
|
DashboardTUI = class {
|
|
136297
|
-
|
|
137837
|
+
// State fields mirror the original private layout so tests can access them.
|
|
137838
|
+
activeSection = "logs";
|
|
137839
|
+
// Named `logBuffer` to match what captureConsole tests access via
|
|
137840
|
+
// `(tui as unknown as { logBuffer: LogRingBuffer }).logBuffer`.
|
|
136298
137841
|
logBuffer;
|
|
136299
137842
|
systemInfo = null;
|
|
136300
137843
|
taskStats = null;
|
|
136301
137844
|
settings = null;
|
|
136302
137845
|
callbacks = null;
|
|
136303
137846
|
isRunning = false;
|
|
136304
|
-
rl = null;
|
|
136305
|
-
originalHandlers = /* @__PURE__ */ new Map();
|
|
136306
|
-
lastRenderHeight = 0;
|
|
136307
137847
|
showHelp = false;
|
|
136308
|
-
|
|
136309
|
-
resizeHandler = null;
|
|
136310
|
-
// Logs interaction state
|
|
136311
|
-
selectedLogIndex = 0;
|
|
136312
|
-
logsViewportStart = 0;
|
|
137848
|
+
logsSeverityFilter = "all";
|
|
136313
137849
|
logsWrapEnabled = false;
|
|
136314
137850
|
logsExpandedMode = false;
|
|
136315
|
-
|
|
137851
|
+
selectedLogIndex = 0;
|
|
137852
|
+
logsViewportStart = 0;
|
|
137853
|
+
loadingStatus = "Starting\u2026";
|
|
137854
|
+
mode = "status";
|
|
137855
|
+
interactiveData = null;
|
|
137856
|
+
interactiveView = "board";
|
|
137857
|
+
// Subscribers registered by the Ink App component.
|
|
137858
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
137859
|
+
// Cached snapshot — useSyncExternalStore compares by Object.is, so we must
|
|
137860
|
+
// return the same reference between renders unless state actually changed.
|
|
137861
|
+
// notify() invalidates this; getSnapshot() rebuilds on demand.
|
|
137862
|
+
cachedSnapshot = null;
|
|
137863
|
+
// Ink instance — set when start() is called.
|
|
137864
|
+
inkInstance = null;
|
|
137865
|
+
// Uptime ticker to keep footer time live.
|
|
137866
|
+
uptimeTimer = null;
|
|
136316
137867
|
constructor() {
|
|
136317
137868
|
this.logBuffer = new LogRingBuffer();
|
|
136318
137869
|
}
|
|
136319
|
-
// ──
|
|
136320
|
-
|
|
137870
|
+
// ── Subscription API (for Ink App) ────────────────────────────────────────
|
|
137871
|
+
subscribe(callback) {
|
|
137872
|
+
this.subscribers.add(callback);
|
|
137873
|
+
return () => this.subscribers.delete(callback);
|
|
137874
|
+
}
|
|
137875
|
+
getSnapshot() {
|
|
137876
|
+
if (this.cachedSnapshot) return this.cachedSnapshot;
|
|
137877
|
+
this.cachedSnapshot = {
|
|
137878
|
+
activeSection: this.activeSection,
|
|
137879
|
+
logEntries: this.logBuffer.getAll(),
|
|
137880
|
+
systemInfo: this.systemInfo,
|
|
137881
|
+
taskStats: this.taskStats,
|
|
137882
|
+
settings: this.settings,
|
|
137883
|
+
callbacks: this.callbacks,
|
|
137884
|
+
showHelp: this.showHelp,
|
|
137885
|
+
logsSeverityFilter: this.logsSeverityFilter,
|
|
137886
|
+
logsWrapEnabled: this.logsWrapEnabled,
|
|
137887
|
+
logsExpandedMode: this.logsExpandedMode,
|
|
137888
|
+
selectedLogIndex: this.selectedLogIndex,
|
|
137889
|
+
logsViewportStart: this.logsViewportStart,
|
|
137890
|
+
loadingStatus: this.loadingStatus,
|
|
137891
|
+
mode: this.mode,
|
|
137892
|
+
interactiveData: this.interactiveData,
|
|
137893
|
+
interactiveView: this.interactiveView
|
|
137894
|
+
};
|
|
137895
|
+
return this.cachedSnapshot;
|
|
137896
|
+
}
|
|
137897
|
+
notify() {
|
|
137898
|
+
this.cachedSnapshot = null;
|
|
137899
|
+
for (const cb of this.subscribers) cb();
|
|
137900
|
+
}
|
|
137901
|
+
// ── Public API (unchanged from original DashboardTUI) ─────────────────────
|
|
136321
137902
|
get running() {
|
|
136322
137903
|
return this.isRunning;
|
|
136323
137904
|
}
|
|
136324
137905
|
setCallbacks(callbacks) {
|
|
136325
137906
|
this.callbacks = callbacks;
|
|
137907
|
+
this.notify();
|
|
136326
137908
|
}
|
|
136327
137909
|
setSystemInfo(info) {
|
|
136328
137910
|
this.systemInfo = info;
|
|
136329
|
-
this.
|
|
137911
|
+
this.notify();
|
|
136330
137912
|
}
|
|
136331
137913
|
setTaskStats(stats) {
|
|
136332
137914
|
this.taskStats = stats;
|
|
136333
|
-
this.
|
|
137915
|
+
this.notify();
|
|
136334
137916
|
}
|
|
136335
137917
|
setSettings(settings) {
|
|
136336
137918
|
this.settings = settings;
|
|
136337
|
-
this.
|
|
137919
|
+
this.notify();
|
|
137920
|
+
}
|
|
137921
|
+
setLoadingStatus(text) {
|
|
137922
|
+
this.loadingStatus = text;
|
|
137923
|
+
this.notify();
|
|
137924
|
+
}
|
|
137925
|
+
setInteractiveData(data) {
|
|
137926
|
+
this.interactiveData = data;
|
|
137927
|
+
this.notify();
|
|
137928
|
+
}
|
|
137929
|
+
setInteractiveView(view) {
|
|
137930
|
+
this.interactiveView = view;
|
|
137931
|
+
this.notify();
|
|
136338
137932
|
}
|
|
136339
137933
|
addLog(entry) {
|
|
136340
|
-
this.
|
|
136341
|
-
|
|
136342
|
-
|
|
136343
|
-
|
|
136344
|
-
|
|
136345
|
-
|
|
137934
|
+
const beforeCount = this.getFilteredLogEntries().length;
|
|
137935
|
+
const wasAtTail = beforeCount === 0 || this.selectedLogIndex === beforeCount - 1;
|
|
137936
|
+
this.logBuffer.push({ ...entry, timestamp: /* @__PURE__ */ new Date() });
|
|
137937
|
+
const after = this.getFilteredLogEntries();
|
|
137938
|
+
if (wasAtTail) {
|
|
137939
|
+
this.selectedLogIndex = Math.max(0, after.length - 1);
|
|
137940
|
+
} else {
|
|
137941
|
+
this.clampSelectedLogIndex(after);
|
|
137942
|
+
}
|
|
137943
|
+
this.notify();
|
|
136346
137944
|
}
|
|
136347
|
-
/**
|
|
136348
|
-
* Clear logs and reset selection state.
|
|
136349
|
-
*/
|
|
136350
137945
|
clearLogs() {
|
|
136351
137946
|
this.logBuffer.clear();
|
|
136352
137947
|
this.selectedLogIndex = 0;
|
|
136353
137948
|
this.logsViewportStart = 0;
|
|
136354
137949
|
this.logsExpandedMode = false;
|
|
137950
|
+
this.notify();
|
|
136355
137951
|
}
|
|
136356
137952
|
log(message, prefix) {
|
|
136357
137953
|
this.addLog({ level: "info", message, prefix });
|
|
@@ -136362,251 +137958,56 @@ var init_dashboard_tui = __esm({
|
|
|
136362
137958
|
error(message, prefix) {
|
|
136363
137959
|
this.addLog({ level: "error", message, prefix });
|
|
136364
137960
|
}
|
|
136365
|
-
|
|
136366
|
-
|
|
136367
|
-
this.
|
|
136368
|
-
|
|
136369
|
-
|
|
136370
|
-
this.saveSignalHandlers();
|
|
136371
|
-
this.rl = readline.createInterface({
|
|
136372
|
-
input: process.stdin,
|
|
136373
|
-
output: process.stdout,
|
|
136374
|
-
terminal: true
|
|
136375
|
-
});
|
|
136376
|
-
process.stdin.setRawMode?.(true);
|
|
136377
|
-
process.stdin.resume();
|
|
136378
|
-
process.stdin.setEncoding("utf8");
|
|
136379
|
-
readline.emitKeypressEvents(process.stdin);
|
|
136380
|
-
process.stdin.on("keypress", (str, key) => {
|
|
136381
|
-
if (str) {
|
|
136382
|
-
this.handleKeypress(str);
|
|
136383
|
-
} else if (key.ctrl && key.name === "c") {
|
|
136384
|
-
this.handleKeypress("");
|
|
136385
|
-
} else if (key.name === "return" || key.name === "enter") {
|
|
136386
|
-
this.handleKeypress("\r");
|
|
136387
|
-
} else if (key.name === "right") {
|
|
136388
|
-
this.handleKeypress("\x1B[C");
|
|
136389
|
-
} else if (key.name === "left") {
|
|
136390
|
-
this.handleKeypress("\x1B[D");
|
|
136391
|
-
} else if (key.name === "up") {
|
|
136392
|
-
this.handleKeypress("\x1B[A");
|
|
136393
|
-
} else if (key.name === "down") {
|
|
136394
|
-
this.handleKeypress("\x1B[B");
|
|
136395
|
-
} else if (key.name === "escape") {
|
|
136396
|
-
this.handleKeypress("\x1B");
|
|
136397
|
-
} else if (key.name === "home") {
|
|
136398
|
-
this.handleKeypress("Home");
|
|
136399
|
-
} else if (key.name === "end") {
|
|
136400
|
-
this.handleKeypress("End");
|
|
136401
|
-
} else if (key.name === "space") {
|
|
136402
|
-
this.handleKeypress(" ");
|
|
136403
|
-
}
|
|
136404
|
-
});
|
|
136405
|
-
this.uptimeTimer = setInterval(() => {
|
|
136406
|
-
if (this.isRunning) {
|
|
136407
|
-
this.renderFooter();
|
|
136408
|
-
}
|
|
136409
|
-
}, 5e3);
|
|
136410
|
-
this.resizeHandler = () => {
|
|
136411
|
-
if (this.isRunning) {
|
|
136412
|
-
this.render();
|
|
136413
|
-
}
|
|
136414
|
-
};
|
|
136415
|
-
process.stdout.on("resize", this.resizeHandler);
|
|
136416
|
-
this.render();
|
|
136417
|
-
}
|
|
136418
|
-
async stop() {
|
|
136419
|
-
if (!this.isRunning) return;
|
|
136420
|
-
this.isRunning = false;
|
|
136421
|
-
if (this.uptimeTimer) {
|
|
136422
|
-
clearInterval(this.uptimeTimer);
|
|
136423
|
-
this.uptimeTimer = null;
|
|
136424
|
-
}
|
|
136425
|
-
if (this.resizeHandler) {
|
|
136426
|
-
process.stdout.off("resize", this.resizeHandler);
|
|
136427
|
-
this.resizeHandler = null;
|
|
136428
|
-
}
|
|
136429
|
-
this.restoreTerminal();
|
|
136430
|
-
this.restoreSignalHandlers();
|
|
136431
|
-
if (this.rl) {
|
|
136432
|
-
this.rl.close();
|
|
136433
|
-
this.rl = null;
|
|
136434
|
-
}
|
|
136435
|
-
}
|
|
136436
|
-
// ── Private: Signal Handling ───────────────────────────────────────────────
|
|
136437
|
-
saveSignalHandlers() {
|
|
136438
|
-
const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
136439
|
-
for (const sig of signals) {
|
|
136440
|
-
const listeners = process.listeners(sig);
|
|
136441
|
-
if (listeners.length > 0) {
|
|
136442
|
-
this.originalHandlers.set(sig, listeners[listeners.length - 1]);
|
|
136443
|
-
}
|
|
136444
|
-
}
|
|
137961
|
+
// ── State helpers called from Ink App ────────────────────────────────────
|
|
137962
|
+
setActiveSection(section) {
|
|
137963
|
+
this.activeSection = section;
|
|
137964
|
+
this.showHelp = false;
|
|
137965
|
+
this.notify();
|
|
136445
137966
|
}
|
|
136446
|
-
|
|
136447
|
-
|
|
136448
|
-
|
|
136449
|
-
}
|
|
136450
|
-
this.originalHandlers.clear();
|
|
137967
|
+
setShowHelp(show) {
|
|
137968
|
+
this.showHelp = show;
|
|
137969
|
+
this.notify();
|
|
136451
137970
|
}
|
|
136452
|
-
|
|
136453
|
-
|
|
136454
|
-
|
|
136455
|
-
disableAlternateScreen();
|
|
136456
|
-
process.stdout.write("\n");
|
|
136457
|
-
process.stdin.pause?.();
|
|
136458
|
-
process.stdin.setRawMode?.(false);
|
|
137971
|
+
setLogsWrapEnabled(enabled) {
|
|
137972
|
+
this.logsWrapEnabled = enabled;
|
|
137973
|
+
this.notify();
|
|
136459
137974
|
}
|
|
136460
|
-
|
|
136461
|
-
|
|
136462
|
-
|
|
136463
|
-
void this.stop();
|
|
136464
|
-
process.exit(0);
|
|
136465
|
-
return;
|
|
136466
|
-
}
|
|
136467
|
-
if (key === "q" || key === "Q") {
|
|
136468
|
-
void this.stop();
|
|
136469
|
-
process.exit(0);
|
|
136470
|
-
return;
|
|
136471
|
-
}
|
|
136472
|
-
if (key === "?" || key === "h" || key === "H") {
|
|
136473
|
-
this.showHelp = !this.showHelp;
|
|
136474
|
-
this.render();
|
|
136475
|
-
return;
|
|
136476
|
-
}
|
|
136477
|
-
if (key >= "1" && key <= "5") {
|
|
136478
|
-
const index2 = parseInt(key, 10) - 1;
|
|
136479
|
-
if (index2 >= 0 && index2 < SECTION_ORDER.length) {
|
|
136480
|
-
this.activeSection = SECTION_ORDER[index2];
|
|
136481
|
-
this.showHelp = false;
|
|
136482
|
-
this.render();
|
|
136483
|
-
}
|
|
136484
|
-
return;
|
|
136485
|
-
}
|
|
136486
|
-
if (key === "\x1B[C" || key === "n" || key === "N") {
|
|
136487
|
-
const currentIndex = SECTION_ORDER.indexOf(this.activeSection);
|
|
136488
|
-
this.activeSection = SECTION_ORDER[(currentIndex + 1) % SECTION_ORDER.length];
|
|
136489
|
-
this.showHelp = false;
|
|
136490
|
-
this.render();
|
|
136491
|
-
return;
|
|
136492
|
-
}
|
|
136493
|
-
if (key === "\x1B[D" || key === "p" || key === "P") {
|
|
136494
|
-
const currentIndex = SECTION_ORDER.indexOf(this.activeSection);
|
|
136495
|
-
this.activeSection = SECTION_ORDER[(currentIndex - 1 + SECTION_ORDER.length) % SECTION_ORDER.length];
|
|
136496
|
-
this.showHelp = false;
|
|
136497
|
-
this.render();
|
|
136498
|
-
return;
|
|
136499
|
-
}
|
|
136500
|
-
if (this.activeSection === "utilities") {
|
|
136501
|
-
this.handleUtilityKeypress(key);
|
|
136502
|
-
return;
|
|
136503
|
-
}
|
|
136504
|
-
if (this.activeSection === "logs") {
|
|
136505
|
-
this.handleLogsKeypress(key);
|
|
136506
|
-
return;
|
|
136507
|
-
}
|
|
137975
|
+
setLogsExpandedMode(expanded) {
|
|
137976
|
+
this.logsExpandedMode = expanded;
|
|
137977
|
+
this.notify();
|
|
136508
137978
|
}
|
|
136509
|
-
|
|
137979
|
+
setSelectedLogIndex(index2) {
|
|
136510
137980
|
const entries = this.getFilteredLogEntries();
|
|
136511
|
-
|
|
136512
|
-
|
|
136513
|
-
if (this.logsExpandedMode) {
|
|
136514
|
-
this.logsExpandedMode = false;
|
|
136515
|
-
this.showHelp = false;
|
|
136516
|
-
this.render();
|
|
136517
|
-
} else if (this.showHelp) {
|
|
136518
|
-
this.showHelp = false;
|
|
136519
|
-
this.render();
|
|
136520
|
-
}
|
|
136521
|
-
return;
|
|
136522
|
-
}
|
|
136523
|
-
if (key === "\r") {
|
|
136524
|
-
if (entries.length === 0) {
|
|
136525
|
-
return;
|
|
136526
|
-
}
|
|
136527
|
-
this.logsExpandedMode = !this.logsExpandedMode;
|
|
136528
|
-
this.render();
|
|
136529
|
-
return;
|
|
136530
|
-
}
|
|
136531
|
-
if (key === "w" || key === "W") {
|
|
136532
|
-
this.logsWrapEnabled = !this.logsWrapEnabled;
|
|
136533
|
-
this.render();
|
|
136534
|
-
return;
|
|
136535
|
-
}
|
|
136536
|
-
if (key === "f" || key === "F") {
|
|
136537
|
-
this.cycleLogsSeverityFilter();
|
|
136538
|
-
this.clampSelectedLogIndex(this.getFilteredLogEntries());
|
|
136539
|
-
this.logsViewportStart = 0;
|
|
136540
|
-
this.render();
|
|
136541
|
-
return;
|
|
136542
|
-
}
|
|
136543
|
-
if (key === "\x1B[A" || key === "k" || key === "K") {
|
|
136544
|
-
if (entries.length === 0) return;
|
|
136545
|
-
if (this.selectedLogIndex > 0) {
|
|
136546
|
-
this.selectedLogIndex--;
|
|
136547
|
-
this.render();
|
|
136548
|
-
}
|
|
136549
|
-
return;
|
|
136550
|
-
}
|
|
136551
|
-
if (key === "\x1B[B" || key === "j" || key === "J") {
|
|
136552
|
-
if (entries.length === 0) return;
|
|
136553
|
-
if (this.selectedLogIndex < maxIndex) {
|
|
136554
|
-
this.selectedLogIndex++;
|
|
136555
|
-
this.render();
|
|
136556
|
-
}
|
|
136557
|
-
return;
|
|
136558
|
-
}
|
|
136559
|
-
if (key === "Home") {
|
|
136560
|
-
if (entries.length === 0) return;
|
|
136561
|
-
if (this.selectedLogIndex !== 0) {
|
|
136562
|
-
this.selectedLogIndex = 0;
|
|
136563
|
-
this.render();
|
|
136564
|
-
}
|
|
136565
|
-
return;
|
|
136566
|
-
}
|
|
136567
|
-
if (key === "End") {
|
|
136568
|
-
if (entries.length === 0) return;
|
|
136569
|
-
if (this.selectedLogIndex !== maxIndex) {
|
|
136570
|
-
this.selectedLogIndex = maxIndex;
|
|
136571
|
-
this.render();
|
|
136572
|
-
}
|
|
136573
|
-
return;
|
|
136574
|
-
}
|
|
136575
|
-
if (key === " " || key === "e" || key === "E") {
|
|
136576
|
-
if (entries.length === 0) {
|
|
136577
|
-
return;
|
|
136578
|
-
}
|
|
136579
|
-
this.logsExpandedMode = !this.logsExpandedMode;
|
|
136580
|
-
this.render();
|
|
136581
|
-
return;
|
|
136582
|
-
}
|
|
137981
|
+
this.selectedLogIndex = this.clampIndex(index2, entries.length);
|
|
137982
|
+
this.notify();
|
|
136583
137983
|
}
|
|
136584
|
-
|
|
136585
|
-
|
|
136586
|
-
|
|
136587
|
-
return entries;
|
|
136588
|
-
}
|
|
136589
|
-
return entries.filter((entry) => entry.level === this.logsSeverityFilter);
|
|
137984
|
+
setLogsViewportStart(start) {
|
|
137985
|
+
this.logsViewportStart = start;
|
|
137986
|
+
this.notify();
|
|
136590
137987
|
}
|
|
136591
|
-
|
|
136592
|
-
|
|
136593
|
-
|
|
136594
|
-
this.logsExpandedMode = false;
|
|
136595
|
-
return;
|
|
136596
|
-
}
|
|
136597
|
-
if (this.selectedLogIndex >= entries.length) {
|
|
136598
|
-
this.selectedLogIndex = entries.length - 1;
|
|
136599
|
-
}
|
|
136600
|
-
if (this.selectedLogIndex < 0) {
|
|
136601
|
-
this.selectedLogIndex = 0;
|
|
136602
|
-
}
|
|
137988
|
+
setMode(mode) {
|
|
137989
|
+
this.mode = mode;
|
|
137990
|
+
this.notify();
|
|
136603
137991
|
}
|
|
136604
|
-
|
|
137992
|
+
cycleSection(direction) {
|
|
137993
|
+
const idx = SECTION_ORDER.indexOf(this.activeSection);
|
|
137994
|
+
this.activeSection = SECTION_ORDER[(idx + direction + SECTION_ORDER.length) % SECTION_ORDER.length];
|
|
137995
|
+
this.showHelp = false;
|
|
137996
|
+
this.notify();
|
|
137997
|
+
}
|
|
137998
|
+
cycleSeverityFilter() {
|
|
136605
137999
|
const order = ["all", "info", "warn", "error"];
|
|
136606
|
-
const
|
|
136607
|
-
this.logsSeverityFilter = order[(
|
|
138000
|
+
const idx = order.indexOf(this.logsSeverityFilter);
|
|
138001
|
+
this.logsSeverityFilter = order[(idx + 1) % order.length];
|
|
138002
|
+
this.clampSelectedLogIndex(this.getFilteredLogEntries());
|
|
138003
|
+
this.logsViewportStart = 0;
|
|
138004
|
+
this.notify();
|
|
136608
138005
|
}
|
|
136609
|
-
|
|
138006
|
+
getFilteredLogEntries() {
|
|
138007
|
+
const all = this.logBuffer.getAll();
|
|
138008
|
+
return this.logsSeverityFilter === "all" ? all : all.filter((e) => e.level === this.logsSeverityFilter);
|
|
138009
|
+
}
|
|
138010
|
+
async handleUtilityAction(key) {
|
|
136610
138011
|
if (!this.callbacks) return;
|
|
136611
138012
|
switch (key.toLowerCase()) {
|
|
136612
138013
|
case "r":
|
|
@@ -136627,517 +138028,83 @@ var init_dashboard_tui = __esm({
|
|
|
136627
138028
|
break;
|
|
136628
138029
|
}
|
|
136629
138030
|
}
|
|
136630
|
-
// ──
|
|
136631
|
-
|
|
136632
|
-
if (
|
|
136633
|
-
|
|
136634
|
-
|
|
136635
|
-
|
|
136636
|
-
|
|
136637
|
-
this.
|
|
136638
|
-
|
|
136639
|
-
|
|
136640
|
-
|
|
136641
|
-
|
|
136642
|
-
|
|
136643
|
-
const cols = process.stdout.columns || 80;
|
|
136644
|
-
const title = colorize(" fusion ", "cyan");
|
|
136645
|
-
const titleLen = visibleLength(title);
|
|
136646
|
-
process.stdout.write(title);
|
|
136647
|
-
if (cols >= 70) {
|
|
136648
|
-
for (let i = 0; i < SECTION_ORDER.length; i++) {
|
|
136649
|
-
const section = SECTION_ORDER[i];
|
|
136650
|
-
const isActive = section === this.activeSection;
|
|
136651
|
-
const num = (i + 1).toString();
|
|
136652
|
-
const label = section.charAt(0).toUpperCase() + section.slice(1);
|
|
136653
|
-
const tabText = `[${num}] ${label}`;
|
|
136654
|
-
const style = isActive ? "brightBlue" : "dim";
|
|
136655
|
-
process.stdout.write(colorize(` ${tabText} `, style));
|
|
136656
|
-
}
|
|
136657
|
-
} else if (cols >= 40) {
|
|
136658
|
-
const shortLabels = {
|
|
136659
|
-
logs: "L",
|
|
136660
|
-
system: "S",
|
|
136661
|
-
utilities: "U",
|
|
136662
|
-
stats: "St",
|
|
136663
|
-
settings: "Se"
|
|
136664
|
-
};
|
|
136665
|
-
for (let i = 0; i < SECTION_ORDER.length; i++) {
|
|
136666
|
-
const section = SECTION_ORDER[i];
|
|
136667
|
-
const isActive = section === this.activeSection;
|
|
136668
|
-
const num = (i + 1).toString();
|
|
136669
|
-
const shortLabel = shortLabels[section];
|
|
136670
|
-
const tabText = `[${num}]${shortLabel}`;
|
|
136671
|
-
const style = isActive ? "brightBlue" : "dim";
|
|
136672
|
-
process.stdout.write(colorize(` ${tabText} `, style));
|
|
136673
|
-
}
|
|
136674
|
-
} else {
|
|
136675
|
-
const activeIndex = SECTION_ORDER.indexOf(this.activeSection);
|
|
136676
|
-
const activeLabel = this.activeSection.charAt(0).toUpperCase() + this.activeSection.slice(1);
|
|
136677
|
-
process.stdout.write(colorize(` [${activeIndex + 1}]${activeLabel} `, "brightBlue"));
|
|
136678
|
-
process.stdout.write(colorize(" [n/p]nav ", "dim"));
|
|
136679
|
-
}
|
|
136680
|
-
const tabsLength = SECTION_ORDER.reduce((acc, s, i) => {
|
|
136681
|
-
let label;
|
|
136682
|
-
if (cols >= 70) {
|
|
136683
|
-
label = s.charAt(0).toUpperCase() + s.slice(1);
|
|
136684
|
-
} else if (cols >= 40) {
|
|
136685
|
-
const shortLabels = {
|
|
136686
|
-
logs: "L",
|
|
136687
|
-
system: "S",
|
|
136688
|
-
utilities: "U",
|
|
136689
|
-
stats: "St",
|
|
136690
|
-
settings: "Se"
|
|
136691
|
-
};
|
|
136692
|
-
label = shortLabels[s];
|
|
136693
|
-
} else {
|
|
136694
|
-
label = s.charAt(0).toUpperCase() + s.slice(1);
|
|
136695
|
-
}
|
|
136696
|
-
const tabText = `[${i + 1}]${label} `;
|
|
136697
|
-
return acc + tabText.length;
|
|
136698
|
-
}, 0);
|
|
136699
|
-
const headerLen = titleLen + tabsLength;
|
|
136700
|
-
const remaining = cols - headerLen;
|
|
136701
|
-
if (remaining > 0) {
|
|
136702
|
-
process.stdout.write(" ".repeat(remaining));
|
|
136703
|
-
}
|
|
136704
|
-
process.stdout.write("\n");
|
|
136705
|
-
process.stdout.write(colorize("\u2500".repeat(Math.max(20, cols)), "dim") + "\n");
|
|
136706
|
-
}
|
|
136707
|
-
renderSection() {
|
|
136708
|
-
switch (this.activeSection) {
|
|
136709
|
-
case "logs":
|
|
136710
|
-
this.renderLogsSection();
|
|
136711
|
-
break;
|
|
136712
|
-
case "system":
|
|
136713
|
-
this.renderSystemSection();
|
|
136714
|
-
break;
|
|
136715
|
-
case "utilities":
|
|
136716
|
-
this.renderUtilitiesSection();
|
|
136717
|
-
break;
|
|
136718
|
-
case "stats":
|
|
136719
|
-
this.renderStatsSection();
|
|
136720
|
-
break;
|
|
136721
|
-
case "settings":
|
|
136722
|
-
this.renderSettingsSection();
|
|
136723
|
-
break;
|
|
136724
|
-
}
|
|
136725
|
-
}
|
|
136726
|
-
getFooterTopRow(totalRows) {
|
|
136727
|
-
return Math.max(1, totalRows - 2);
|
|
136728
|
-
}
|
|
136729
|
-
getLogsListRowBudget(totalRows) {
|
|
136730
|
-
const firstLogBodyRow = 8;
|
|
136731
|
-
const footerTopRow = this.getFooterTopRow(totalRows);
|
|
136732
|
-
return Math.max(0, footerTopRow - firstLogBodyRow);
|
|
138031
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
138032
|
+
async start() {
|
|
138033
|
+
if (this.isRunning) return;
|
|
138034
|
+
this.isRunning = true;
|
|
138035
|
+
const { render } = await import("ink");
|
|
138036
|
+
const { createElement } = await import("react");
|
|
138037
|
+
const { DashboardApp: DashboardApp2 } = await Promise.resolve().then(() => (init_app(), app_exports));
|
|
138038
|
+
this.inkInstance = render(
|
|
138039
|
+
createElement(DashboardApp2, { controller: this })
|
|
138040
|
+
);
|
|
138041
|
+
this.uptimeTimer = setInterval(() => {
|
|
138042
|
+
if (this.isRunning) this.notify();
|
|
138043
|
+
}, 5e3);
|
|
136733
138044
|
}
|
|
136734
|
-
|
|
136735
|
-
if (!this.
|
|
136736
|
-
|
|
138045
|
+
async stop() {
|
|
138046
|
+
if (!this.isRunning) return;
|
|
138047
|
+
this.isRunning = false;
|
|
138048
|
+
if (this.uptimeTimer) {
|
|
138049
|
+
clearInterval(this.uptimeTimer);
|
|
138050
|
+
this.uptimeTimer = null;
|
|
136737
138051
|
}
|
|
136738
|
-
|
|
136739
|
-
|
|
136740
|
-
|
|
136741
|
-
}
|
|
136742
|
-
getLogsViewportWindow(entries, rowBudget, cols) {
|
|
136743
|
-
if (entries.length === 0) {
|
|
136744
|
-
this.logsViewportStart = 0;
|
|
136745
|
-
return { start: 0, end: 0 };
|
|
136746
|
-
}
|
|
136747
|
-
const maxStart = Math.max(0, entries.length - 1);
|
|
136748
|
-
const safeSelectedIndex = Math.max(0, Math.min(this.selectedLogIndex, entries.length - 1));
|
|
136749
|
-
let start = Math.max(0, Math.min(this.logsViewportStart, maxStart));
|
|
136750
|
-
if (safeSelectedIndex < start) {
|
|
136751
|
-
start = safeSelectedIndex;
|
|
136752
|
-
}
|
|
136753
|
-
const measureWindow = (startIndex) => {
|
|
136754
|
-
let rowsUsed = 0;
|
|
136755
|
-
let end = startIndex;
|
|
136756
|
-
while (end < entries.length) {
|
|
136757
|
-
const entryRows = this.getLogEntryRowCount(entries[end], cols);
|
|
136758
|
-
if (end > startIndex && rowsUsed + entryRows > rowBudget) {
|
|
136759
|
-
break;
|
|
136760
|
-
}
|
|
136761
|
-
rowsUsed += entryRows;
|
|
136762
|
-
end++;
|
|
136763
|
-
if (rowsUsed >= rowBudget) {
|
|
136764
|
-
break;
|
|
136765
|
-
}
|
|
136766
|
-
}
|
|
136767
|
-
return { end };
|
|
136768
|
-
};
|
|
136769
|
-
let window2 = measureWindow(start);
|
|
136770
|
-
while (safeSelectedIndex >= window2.end && start < maxStart) {
|
|
136771
|
-
start++;
|
|
136772
|
-
window2 = measureWindow(start);
|
|
138052
|
+
if (this.inkInstance) {
|
|
138053
|
+
this.inkInstance.unmount();
|
|
138054
|
+
this.inkInstance = null;
|
|
136773
138055
|
}
|
|
136774
|
-
this.logsViewportStart = start;
|
|
136775
|
-
return { start, end: window2.end };
|
|
136776
138056
|
}
|
|
136777
|
-
|
|
136778
|
-
|
|
136779
|
-
const rows = process.stdout.rows ?? 38;
|
|
136780
|
-
const allEntries = this.logBuffer.getAll();
|
|
136781
|
-
const entries = this.getFilteredLogEntries();
|
|
136782
|
-
const rowBudget = this.getLogsListRowBudget(rows);
|
|
136783
|
-
process.stdout.write(colorize("\n LOGS\n", "bold"));
|
|
136784
|
-
process.stdout.write(colorize(` Ring buffer: ${this.logBuffer.total}/${MAX_LOG_ENTRIES} entries
|
|
136785
|
-
`, "dim"));
|
|
136786
|
-
if (allEntries.length === 0) {
|
|
136787
|
-
process.stdout.write(colorize(" No log entries yet.\n", "dim"));
|
|
136788
|
-
return;
|
|
136789
|
-
}
|
|
138057
|
+
// ── Private helpers ────────────────────────────────────────────────────────
|
|
138058
|
+
clampSelectedLogIndex(entries) {
|
|
136790
138059
|
if (entries.length === 0) {
|
|
136791
|
-
|
|
136792
|
-
|
|
136793
|
-
`, "dim"));
|
|
136794
|
-
process.stdout.write(colorize(" Press [f] to cycle severity filter.\n", "dim"));
|
|
136795
|
-
return;
|
|
136796
|
-
}
|
|
136797
|
-
const safeSelectedIndex = Math.min(this.selectedLogIndex, Math.max(0, entries.length - 1));
|
|
136798
|
-
if (safeSelectedIndex !== this.selectedLogIndex) {
|
|
136799
|
-
this.selectedLogIndex = safeSelectedIndex;
|
|
136800
|
-
}
|
|
136801
|
-
if (this.logsExpandedMode) {
|
|
136802
|
-
this.renderLogsExpandedPane(entries[safeSelectedIndex], safeSelectedIndex, entries.length);
|
|
136803
|
-
return;
|
|
136804
|
-
}
|
|
136805
|
-
const wrapIndicator = this.logsWrapEnabled ? colorize(" [w] wrap on", "dim") : colorize(" [w] wrap off", "dim");
|
|
136806
|
-
const filterIndicator = colorize(` [f] filter ${this.logsSeverityFilter}`, "dim");
|
|
136807
|
-
process.stdout.write(`${wrapIndicator}${filterIndicator}
|
|
136808
|
-
|
|
136809
|
-
`);
|
|
136810
|
-
if (rowBudget === 0) {
|
|
136811
|
-
process.stdout.write(colorize(" Terminal too short \u2014 expand terminal to view logs.\n", "dim"));
|
|
136812
|
-
return;
|
|
136813
|
-
}
|
|
136814
|
-
const { start: startIndex, end: endIndex } = this.getLogsViewportWindow(entries, rowBudget, cols);
|
|
136815
|
-
const visibleEntries = entries.slice(startIndex, endIndex);
|
|
136816
|
-
const visibleReversed = [...visibleEntries].reverse();
|
|
136817
|
-
const selectedDisplayIndex = safeSelectedIndex >= startIndex && safeSelectedIndex < endIndex ? visibleEntries.length - 1 - (safeSelectedIndex - startIndex) : -1;
|
|
136818
|
-
const prefixLen = 30;
|
|
136819
|
-
const availableWidth = Math.max(8, cols - prefixLen);
|
|
136820
|
-
let remainingRows = rowBudget;
|
|
136821
|
-
for (let displayIdx = 0; displayIdx < visibleReversed.length && remainingRows > 0; displayIdx++) {
|
|
136822
|
-
const entry = visibleReversed[displayIdx];
|
|
136823
|
-
const isSelected = displayIdx === selectedDisplayIndex;
|
|
136824
|
-
const selector = isSelected ? colorize("\u25B8 ", "brightGreen") : " ";
|
|
136825
|
-
const ts = colorize(formatTimestamp3(entry.timestamp), "dim");
|
|
136826
|
-
const prefix = entry.prefix ? colorize(`[${entry.prefix}]`, "gray") : "";
|
|
136827
|
-
const levelChar = entry.level === "error" ? colorize("\u2717", "brightRed") : entry.level === "warn" ? colorize("\u26A0", "brightYellow") : colorize("\u2713", "brightGreen");
|
|
136828
|
-
if (this.logsWrapEnabled) {
|
|
136829
|
-
const wrappedLines = this.wrapText(entry.message, availableWidth);
|
|
136830
|
-
const lineBudget = Math.max(1, remainingRows);
|
|
136831
|
-
const renderedLines = wrappedLines.slice(0, lineBudget);
|
|
136832
|
-
const firstLine = `${selector}${ts} ${levelChar} ${prefix ? prefix + " " : ""}${renderedLines[0] ?? ""}`;
|
|
136833
|
-
process.stdout.write(visibleTruncate(firstLine, cols - 1) + "\n");
|
|
136834
|
-
remainingRows--;
|
|
136835
|
-
for (let i = 1; i < renderedLines.length && remainingRows > 0; i++) {
|
|
136836
|
-
const continuation = ` ${renderedLines[i]}`;
|
|
136837
|
-
process.stdout.write(visibleTruncate(continuation, cols - 1) + "\n");
|
|
136838
|
-
remainingRows--;
|
|
136839
|
-
}
|
|
136840
|
-
} else {
|
|
136841
|
-
const messageWidth = Math.max(8, cols - prefixLen);
|
|
136842
|
-
const message = visibleTruncate(entry.message, messageWidth);
|
|
136843
|
-
const line = `${selector}${ts} ${levelChar} ${prefix ? prefix + " " : ""}${message}`;
|
|
136844
|
-
process.stdout.write(visibleTruncate(line, cols - 1) + "\n");
|
|
136845
|
-
remainingRows--;
|
|
136846
|
-
}
|
|
136847
|
-
}
|
|
136848
|
-
}
|
|
136849
|
-
/**
|
|
136850
|
-
* Render the expanded log entry detail pane.
|
|
136851
|
-
* Replaces the normal list view with a focused view of a single entry.
|
|
136852
|
-
*/
|
|
136853
|
-
renderLogsExpandedPane(entry, index2, total) {
|
|
136854
|
-
const cols = process.stdout.columns || 80;
|
|
136855
|
-
const rows = process.stdout.rows ?? 24;
|
|
136856
|
-
const maxContentRows = Math.max(1, rows - 12);
|
|
136857
|
-
process.stdout.write(colorize(" EXPANDED LOG ENTRY\n", "bold"));
|
|
136858
|
-
const navHint = colorize(` Entry ${index2 + 1} of ${total} | [\u2191/k] older [\u2193/j] newer [Enter/Esc] close
|
|
136859
|
-
`, "dim");
|
|
136860
|
-
process.stdout.write(navHint);
|
|
136861
|
-
process.stdout.write(colorize(" " + "\u2500".repeat(Math.max(20, cols - 4)) + "\n", "dim"));
|
|
136862
|
-
const ts = formatTimestamp3(entry.timestamp);
|
|
136863
|
-
const levelLabel = entry.level === "error" ? colorize("ERROR", "brightRed") : entry.level === "warn" ? colorize("WARN", "brightYellow") : colorize("INFO", "brightGreen");
|
|
136864
|
-
process.stdout.write(colorize(` Timestamp: `, "gray") + colorize(ts, "white") + "\n");
|
|
136865
|
-
process.stdout.write(colorize(` Level: `, "gray") + levelLabel + "\n");
|
|
136866
|
-
if (entry.prefix) {
|
|
136867
|
-
process.stdout.write(colorize(` Prefix: `, "gray") + colorize(entry.prefix, "dim") + "\n");
|
|
136868
|
-
}
|
|
136869
|
-
process.stdout.write("\n");
|
|
136870
|
-
process.stdout.write(colorize(" MESSAGE\n", "bold"));
|
|
136871
|
-
const messageIndent = " ";
|
|
136872
|
-
const availableWidth = Math.max(8, cols - messageIndent.length);
|
|
136873
|
-
const wrappedMessage = this.wrapText(entry.message, availableWidth);
|
|
136874
|
-
let linesPrinted = 5;
|
|
136875
|
-
for (const line of wrappedMessage) {
|
|
136876
|
-
if (linesPrinted >= maxContentRows) {
|
|
136877
|
-
process.stdout.write(colorize(`
|
|
136878
|
-
... (truncated)
|
|
136879
|
-
`, "dim"));
|
|
136880
|
-
break;
|
|
136881
|
-
}
|
|
136882
|
-
process.stdout.write(messageIndent + line + "\n");
|
|
136883
|
-
linesPrinted++;
|
|
136884
|
-
}
|
|
136885
|
-
const footerHint = colorize(`
|
|
136886
|
-
[Esc] or [Enter] to close expanded view
|
|
136887
|
-
`, "dim");
|
|
136888
|
-
process.stdout.write(footerHint);
|
|
136889
|
-
}
|
|
136890
|
-
/**
|
|
136891
|
-
* Wrap text to fit within available width, returning an array of lines.
|
|
136892
|
-
* Respects ANSI escape sequences via visibleLength.
|
|
136893
|
-
*/
|
|
136894
|
-
wrapText(text, maxWidth) {
|
|
136895
|
-
if (maxWidth <= 0) return [""];
|
|
136896
|
-
if (visibleLength(text) <= maxWidth) return [text];
|
|
136897
|
-
const lines = [];
|
|
136898
|
-
let remaining = text;
|
|
136899
|
-
while (visibleLength(remaining) > maxWidth) {
|
|
136900
|
-
let breakIdx = 0;
|
|
136901
|
-
for (let i = 0; i < remaining.length; i++) {
|
|
136902
|
-
const char = remaining[i];
|
|
136903
|
-
if (char === " " || char === " ") {
|
|
136904
|
-
if (visibleLength(remaining.substring(0, i)) <= maxWidth) {
|
|
136905
|
-
breakIdx = i;
|
|
136906
|
-
}
|
|
136907
|
-
}
|
|
136908
|
-
if (visibleLength(remaining.substring(0, i + 1)) > maxWidth) {
|
|
136909
|
-
break;
|
|
136910
|
-
}
|
|
136911
|
-
}
|
|
136912
|
-
if (breakIdx === 0) {
|
|
136913
|
-
const firstTokenMatch = remaining.match(/^(\S+)/);
|
|
136914
|
-
if (firstTokenMatch) {
|
|
136915
|
-
const firstToken = firstTokenMatch[1];
|
|
136916
|
-
if (visibleLength(firstToken) > maxWidth) {
|
|
136917
|
-
const chunkSize = Math.max(1, maxWidth - 1);
|
|
136918
|
-
let chunkStart = 0;
|
|
136919
|
-
while (chunkStart < firstToken.length) {
|
|
136920
|
-
const chunk = firstToken.substring(chunkStart, chunkStart + chunkSize);
|
|
136921
|
-
lines.push(chunk);
|
|
136922
|
-
chunkStart += chunkSize;
|
|
136923
|
-
}
|
|
136924
|
-
remaining = remaining.substring(firstToken.length).trimStart();
|
|
136925
|
-
continue;
|
|
136926
|
-
}
|
|
136927
|
-
}
|
|
136928
|
-
breakIdx = Math.min(maxWidth, remaining.length);
|
|
136929
|
-
}
|
|
136930
|
-
lines.push(remaining.substring(0, breakIdx).trimEnd());
|
|
136931
|
-
remaining = remaining.substring(breakIdx).trimStart();
|
|
136932
|
-
}
|
|
136933
|
-
if (remaining.length > 0) {
|
|
136934
|
-
lines.push(remaining);
|
|
136935
|
-
}
|
|
136936
|
-
return lines.length > 0 ? lines : [""];
|
|
136937
|
-
}
|
|
136938
|
-
renderSystemSection() {
|
|
136939
|
-
if (!this.systemInfo) {
|
|
136940
|
-
process.stdout.write(colorize("\n System information not available.\n", "dim"));
|
|
136941
|
-
return;
|
|
136942
|
-
}
|
|
136943
|
-
const cols = process.stdout.columns || 80;
|
|
136944
|
-
const info = this.systemInfo;
|
|
136945
|
-
const rows = [];
|
|
136946
|
-
rows.push(colorize("\n SYSTEM INFORMATION\n", "bold"));
|
|
136947
|
-
rows.push("");
|
|
136948
|
-
const labelWidth = 12;
|
|
136949
|
-
const availableValueWidth = Math.max(8, cols - labelWidth - 1);
|
|
136950
|
-
rows.push(` ${colorize("Host:", "white")} ${info.host}`);
|
|
136951
|
-
rows.push(` ${colorize("Port:", "white")} ${info.port}`);
|
|
136952
|
-
rows.push(` ${colorize("URL:", "white")} ${colorize(visibleTruncate(info.baseUrl, availableValueWidth), "brightCyan")}`);
|
|
136953
|
-
rows.push("");
|
|
136954
|
-
if (info.authEnabled) {
|
|
136955
|
-
rows.push(` ${colorize("Auth:", "white")} ${colorize("bearer token required", "yellow")}`);
|
|
136956
|
-
if (info.authToken) {
|
|
136957
|
-
rows.push(` ${colorize("Token:", "white")} ${visibleTruncate(info.authToken, availableValueWidth)}`);
|
|
136958
|
-
}
|
|
136959
|
-
if (info.tokenizedUrl) {
|
|
136960
|
-
rows.push(` ${colorize("Open:", "white")} ${visibleTruncate(info.tokenizedUrl, availableValueWidth)}`);
|
|
136961
|
-
rows.push(colorize(" (browser stores token, click once)", "dim"));
|
|
136962
|
-
}
|
|
136963
|
-
} else {
|
|
136964
|
-
rows.push(` ${colorize("Auth:", "white")} ${colorize("disabled (--no-auth)", "dim")}`);
|
|
136965
|
-
}
|
|
136966
|
-
rows.push("");
|
|
136967
|
-
rows.push(` ${colorize("AI Engine:", "white")} ${info.engineMode === "dev" ? colorize("\u2717 disabled (dev mode)", "yellow") : info.engineMode === "paused" ? colorize("\u23F8 paused", "brightYellow") : colorize("\u2713 active", "brightGreen")}`);
|
|
136968
|
-
rows.push(` ${colorize("File Watcher:", "white")} ${info.fileWatcher ? colorize("\u2713 active", "brightGreen") : colorize("\u2717 inactive", "brightRed")}`);
|
|
136969
|
-
rows.push(` ${colorize("Uptime:", "white")} ${formatUptime(Date.now() - info.startTimeMs)}`);
|
|
136970
|
-
for (const row of rows) {
|
|
136971
|
-
process.stdout.write(visibleTruncate(row, cols) + "\n");
|
|
136972
|
-
}
|
|
136973
|
-
}
|
|
136974
|
-
renderUtilitiesSection() {
|
|
136975
|
-
const cols = process.stdout.columns || 80;
|
|
136976
|
-
const actions = [
|
|
136977
|
-
{ id: "refresh", label: "Refresh Stats", key: "r", description: "Re-fetch task and agent counts" },
|
|
136978
|
-
{ id: "clear", label: "Clear Logs", key: "c", description: "Clear the log ring buffer" },
|
|
136979
|
-
{ id: "pause", label: "Toggle Engine Pause", key: "t", description: "Pause/resume AI engine automation" },
|
|
136980
|
-
{ id: "help", label: "Help", key: "?", description: "Show keyboard shortcuts" }
|
|
136981
|
-
];
|
|
136982
|
-
process.stdout.write(colorize("\n UTILITIES\n", "bold"));
|
|
136983
|
-
process.stdout.write(colorize(" Press key to execute action\n\n", "dim"));
|
|
136984
|
-
const prefixWidth = 2 + 3 + 1 + 20 + 3;
|
|
136985
|
-
const descriptionWidth = Math.max(8, cols - prefixWidth - 1);
|
|
136986
|
-
for (const action of actions) {
|
|
136987
|
-
const keyDisplay = colorize(`[${action.key}]`, "brightYellow");
|
|
136988
|
-
const label = colorize(action.label.padEnd(20), "white");
|
|
136989
|
-
const description = visibleTruncate(action.description, descriptionWidth);
|
|
136990
|
-
const line = ` ${keyDisplay} ${label} - ${description}`;
|
|
136991
|
-
process.stdout.write(visibleTruncate(line, cols - 1) + "\n");
|
|
136992
|
-
}
|
|
136993
|
-
}
|
|
136994
|
-
renderStatsSection() {
|
|
136995
|
-
const cols = process.stdout.columns || 80;
|
|
136996
|
-
if (!this.taskStats) {
|
|
136997
|
-
process.stdout.write(colorize("\n Statistics not available.\n", "dim"));
|
|
136998
|
-
return;
|
|
136999
|
-
}
|
|
137000
|
-
const stats = this.taskStats;
|
|
137001
|
-
const rows = [];
|
|
137002
|
-
rows.push(colorize("\n STATISTICS\n", "bold"));
|
|
137003
|
-
rows.push("");
|
|
137004
|
-
rows.push(` ${colorize("Total Tasks:", "white")} ${stats.total}`);
|
|
137005
|
-
rows.push("");
|
|
137006
|
-
rows.push(` ${colorize("By Column:", "dim")}`);
|
|
137007
|
-
for (const [column, count] of Object.entries(stats.byColumn)) {
|
|
137008
|
-
const colName = column.replace("-", " ").replace(/\b\w/g, (l) => l.toUpperCase());
|
|
137009
|
-
const activeMark = (column === "in-progress" || column === "in-review") && count > 0 ? colorize(" \u25CF", "brightGreen") : "";
|
|
137010
|
-
rows.push(` ${colName}: ${count}${activeMark}`);
|
|
137011
|
-
}
|
|
137012
|
-
rows.push("");
|
|
137013
|
-
rows.push(` ${colorize("Active Tasks:", "white")} ${stats.active} (in-progress + in-review)`);
|
|
137014
|
-
rows.push("");
|
|
137015
|
-
rows.push(` ${colorize("Agents:", "dim")}`);
|
|
137016
|
-
rows.push(` Idle: ${stats.agents.idle}`);
|
|
137017
|
-
rows.push(` Active: ${stats.agents.active}`);
|
|
137018
|
-
rows.push(` Running: ${stats.agents.running}`);
|
|
137019
|
-
rows.push(` Error: ${stats.agents.error}`);
|
|
137020
|
-
for (const row of rows) {
|
|
137021
|
-
process.stdout.write(visibleTruncate(row, cols) + "\n");
|
|
137022
|
-
}
|
|
137023
|
-
}
|
|
137024
|
-
renderSettingsSection() {
|
|
137025
|
-
const cols = process.stdout.columns || 80;
|
|
137026
|
-
if (!this.settings) {
|
|
137027
|
-
process.stdout.write(colorize("\n Settings not available.\n", "dim"));
|
|
138060
|
+
this.selectedLogIndex = 0;
|
|
138061
|
+
this.logsExpandedMode = false;
|
|
137028
138062
|
return;
|
|
137029
138063
|
}
|
|
137030
|
-
|
|
137031
|
-
|
|
137032
|
-
rows.push(colorize("\n SETTINGS\n", "bold"));
|
|
137033
|
-
rows.push("");
|
|
137034
|
-
const settingsList = [
|
|
137035
|
-
["maxConcurrent", s.maxConcurrent.toString()],
|
|
137036
|
-
["maxWorktrees", s.maxWorktrees.toString()],
|
|
137037
|
-
["autoMerge", s.autoMerge ? "enabled" : "disabled"],
|
|
137038
|
-
["mergeStrategy", s.mergeStrategy],
|
|
137039
|
-
["pollIntervalMs", `${s.pollIntervalMs}ms`],
|
|
137040
|
-
["enginePaused", s.enginePaused ? "yes" : "no"],
|
|
137041
|
-
["globalPause", s.globalPause ? "yes" : "no"]
|
|
137042
|
-
];
|
|
137043
|
-
const keyWidth = Math.max(...settingsList.map(([k]) => k.length));
|
|
137044
|
-
for (const [key, value] of settingsList) {
|
|
137045
|
-
const keyPad = key.padEnd(keyWidth);
|
|
137046
|
-
const isEnabled = value === "enabled" || value === "yes";
|
|
137047
|
-
const isDisabled = value === "disabled" || value === "no";
|
|
137048
|
-
const valueColor = isEnabled ? "brightGreen" : isDisabled ? "brightYellow" : "white";
|
|
137049
|
-
rows.push(` ${colorize(keyPad, "gray")} ${colorize(value, valueColor)}`);
|
|
138064
|
+
if (this.selectedLogIndex >= entries.length) {
|
|
138065
|
+
this.selectedLogIndex = entries.length - 1;
|
|
137050
138066
|
}
|
|
137051
|
-
|
|
137052
|
-
|
|
137053
|
-
}
|
|
137054
|
-
}
|
|
137055
|
-
renderFooter() {
|
|
137056
|
-
const cols = process.stdout.columns || 80;
|
|
137057
|
-
const footerY = Math.max(1, (process.stdout.rows ?? 22) - 2);
|
|
137058
|
-
moveCursorTo(1, footerY);
|
|
137059
|
-
clearLine();
|
|
137060
|
-
const status = this.systemInfo ? `${this.systemInfo.baseUrl} | ${formatUptime(Date.now() - this.systemInfo.startTimeMs)}` : "";
|
|
137061
|
-
const left = colorize("Press ? for help", "dim");
|
|
137062
|
-
const right = colorize(visibleTruncate(status, Math.max(20, cols - 20)), "dim");
|
|
137063
|
-
const leftLen = visibleLength(left);
|
|
137064
|
-
const rightLen = visibleLength(right);
|
|
137065
|
-
process.stdout.write(left);
|
|
137066
|
-
const padding = Math.max(1, cols - leftLen - rightLen - 2);
|
|
137067
|
-
process.stdout.write(" ".repeat(padding));
|
|
137068
|
-
process.stdout.write(right);
|
|
137069
|
-
process.stdout.write("\n");
|
|
137070
|
-
}
|
|
137071
|
-
/**
|
|
137072
|
-
* Build help overlay lines as an array of strings.
|
|
137073
|
-
* Uses dynamic visibleLength calculation to ensure consistent box width.
|
|
137074
|
-
* All box-drawing rows (including borders) have the same total visible width.
|
|
137075
|
-
*
|
|
137076
|
-
* @param boxWidth - The interior content width (excluding the two box characters)
|
|
137077
|
-
* @param useBoxDrawing - Whether to use box-drawing characters (true) or compact text (false)
|
|
137078
|
-
* @returns Array of help lines
|
|
137079
|
-
*/
|
|
137080
|
-
buildHelpLines(boxWidth, useBoxDrawing) {
|
|
137081
|
-
if (useBoxDrawing) {
|
|
137082
|
-
const boxRow = (content) => {
|
|
137083
|
-
const padding = Math.max(0, boxWidth - visibleLength(content));
|
|
137084
|
-
return "\u2502" + content + " ".repeat(padding) + "\u2502";
|
|
137085
|
-
};
|
|
137086
|
-
return [
|
|
137087
|
-
"\u250C" + "\u2500".repeat(boxWidth) + "\u2510",
|
|
137088
|
-
boxRow(centerText("KEYBOARD SHORTCUTS", boxWidth, " ")),
|
|
137089
|
-
"\u251C" + "\u2500".repeat(boxWidth) + "\u2524",
|
|
137090
|
-
boxRow(" [1-5] Switch to tab by number"),
|
|
137091
|
-
boxRow(" [n] / \u2192 Next tab"),
|
|
137092
|
-
boxRow(" [p] / \u2190 Previous tab"),
|
|
137093
|
-
boxRow(" [r] Refresh stats (Utilities)"),
|
|
137094
|
-
boxRow(" [c] Clear logs (Utilities)"),
|
|
137095
|
-
boxRow(" [t] Toggle engine pause (Utilities)"),
|
|
137096
|
-
boxRow(" [\u2191/\u2193/k/j] Navigate log entries (Logs)"),
|
|
137097
|
-
boxRow(" [Home/End] First/last log entry (Logs)"),
|
|
137098
|
-
boxRow(" [Enter/Space/e] Expand log (Logs)"),
|
|
137099
|
-
boxRow(" [w] Toggle word wrap (Logs)"),
|
|
137100
|
-
boxRow(" [f] Cycle severity filter (Logs)"),
|
|
137101
|
-
boxRow(" [?] / [h] Toggle help"),
|
|
137102
|
-
boxRow(" [q] Quit"),
|
|
137103
|
-
boxRow(" [Ctrl+C] Force quit"),
|
|
137104
|
-
"\u2514" + "\u2500".repeat(boxWidth) + "\u2518"
|
|
137105
|
-
];
|
|
137106
|
-
} else {
|
|
137107
|
-
return [
|
|
137108
|
-
"KEYBOARD SHORTCUTS",
|
|
137109
|
-
" [1-5] Switch tab | [n/p] Next/Prev | [q] Quit",
|
|
137110
|
-
" [\u2191\u2193/k/j] Navigate logs | [Home/End] First/Last (Logs)",
|
|
137111
|
-
" [Enter/Space/e] Expand log | [w] Toggle wrap (Logs)",
|
|
137112
|
-
" [f] Cycle severity filter (Logs)",
|
|
137113
|
-
" [r] Refresh | [c] Clear logs | [t] Toggle engine",
|
|
137114
|
-
" [?/h] Help | [Ctrl+C] Force quit"
|
|
137115
|
-
];
|
|
138067
|
+
if (this.selectedLogIndex < 0) {
|
|
138068
|
+
this.selectedLogIndex = 0;
|
|
137116
138069
|
}
|
|
137117
138070
|
}
|
|
137118
|
-
|
|
137119
|
-
|
|
137120
|
-
|
|
137121
|
-
const boxWidth = Math.min(62, Math.max(cols - 4, 20));
|
|
137122
|
-
const useBoxDrawing = cols >= boxWidth + 4;
|
|
137123
|
-
const rawHelpLines = this.buildHelpLines(boxWidth, useBoxDrawing);
|
|
137124
|
-
const compactBoxWidth = useBoxDrawing ? boxWidth : Math.max(...rawHelpLines.map(visibleLength));
|
|
137125
|
-
const boxHeight = rawHelpLines.length;
|
|
137126
|
-
const safeStartX = Math.max(1, Math.floor((cols - compactBoxWidth) / 2));
|
|
137127
|
-
const safeStartY = Math.max(1, Math.floor((rows - boxHeight) / 2));
|
|
137128
|
-
const clearTop = Math.max(1, safeStartY - 1);
|
|
137129
|
-
const clearBottom = Math.min(rows, safeStartY + boxHeight);
|
|
137130
|
-
for (let y = clearTop; y <= clearBottom; y++) {
|
|
137131
|
-
moveCursorTo(1, y);
|
|
137132
|
-
clearLine();
|
|
137133
|
-
}
|
|
137134
|
-
for (let i = 0; i < rawHelpLines.length; i++) {
|
|
137135
|
-
const color = i === 0 || i === 2 || i === rawHelpLines.length - 1 ? "brightBlue" : "white";
|
|
137136
|
-
moveCursorTo(safeStartX, safeStartY + i);
|
|
137137
|
-
process.stdout.write(colorize(rawHelpLines[i], color));
|
|
137138
|
-
}
|
|
138071
|
+
clampIndex(index2, length) {
|
|
138072
|
+
if (length === 0) return 0;
|
|
138073
|
+
return Math.max(0, Math.min(index2, length - 1));
|
|
137139
138074
|
}
|
|
137140
138075
|
};
|
|
138076
|
+
}
|
|
138077
|
+
});
|
|
138078
|
+
|
|
138079
|
+
// src/commands/dashboard-tui/log-sink.ts
|
|
138080
|
+
function formatConsoleArgs(args, fallbackLevel = "info") {
|
|
138081
|
+
const stringified = args.map((arg) => {
|
|
138082
|
+
if (typeof arg === "string") return arg;
|
|
138083
|
+
if (arg instanceof Error) return arg.stack ?? arg.message;
|
|
138084
|
+
if (arg === null || arg === void 0) return String(arg);
|
|
138085
|
+
if (typeof arg === "object") {
|
|
138086
|
+
try {
|
|
138087
|
+
return JSON.stringify(arg);
|
|
138088
|
+
} catch {
|
|
138089
|
+
return String(arg);
|
|
138090
|
+
}
|
|
138091
|
+
}
|
|
138092
|
+
return String(arg);
|
|
138093
|
+
}).join(" ");
|
|
138094
|
+
const markerMatch = stringified.match(LOG_LEVEL_MARKER_REGEX);
|
|
138095
|
+
const level = markerMatch?.[1];
|
|
138096
|
+
const withoutMarker = markerMatch ? stringified.replace(LOG_LEVEL_MARKER_REGEX, "") : stringified;
|
|
138097
|
+
const match = withoutMarker.match(/^\[([^\]]+)\]\s*(.*)$/s);
|
|
138098
|
+
if (match) {
|
|
138099
|
+
return { prefix: match[1], message: match[2], level: level ?? fallbackLevel };
|
|
138100
|
+
}
|
|
138101
|
+
return { message: withoutMarker, level: level ?? fallbackLevel };
|
|
138102
|
+
}
|
|
138103
|
+
var LOG_LEVEL_MARKER_REGEX, DashboardLogSink;
|
|
138104
|
+
var init_log_sink = __esm({
|
|
138105
|
+
"src/commands/dashboard-tui/log-sink.ts"() {
|
|
138106
|
+
"use strict";
|
|
138107
|
+
LOG_LEVEL_MARKER_REGEX = /^\u0000fnlvl=(info|warn|error)\u0000\s*/;
|
|
137141
138108
|
DashboardLogSink = class {
|
|
137142
138109
|
tui = null;
|
|
137143
138110
|
isTTY;
|
|
@@ -137180,19 +138147,6 @@ var init_dashboard_tui = __esm({
|
|
|
137180
138147
|
console.error(line);
|
|
137181
138148
|
}
|
|
137182
138149
|
}
|
|
137183
|
-
/**
|
|
137184
|
-
* Monkey-patch `console.log/warn/error` so everything (including the engine's
|
|
137185
|
-
* createLogger() output, which writes directly to console.error) surfaces in
|
|
137186
|
-
* the TUI's log ring buffer. Structured logger calls carry an internal
|
|
137187
|
-
* severity marker so `logger.log(...)` still lands as info even when routed
|
|
137188
|
-
* through console.error transport. Without capture, most runtime logs render
|
|
137189
|
-
* beneath the alt-screen TUI and are immediately overwritten on the next
|
|
137190
|
-
* render, leaving the Logs tab nearly empty.
|
|
137191
|
-
*
|
|
137192
|
-
* Messages that start with `[prefix] rest` are unpacked so the TUI stores
|
|
137193
|
-
* `prefix="prefix"` and `message="rest"`. Idempotent; call `releaseConsole()`
|
|
137194
|
-
* on TUI shutdown to restore the originals.
|
|
137195
|
-
*/
|
|
137196
138150
|
captureConsole() {
|
|
137197
138151
|
if (this.originalConsole) return;
|
|
137198
138152
|
this.originalConsole = {
|
|
@@ -137213,7 +138167,6 @@ var init_dashboard_tui = __esm({
|
|
|
137213
138167
|
this.writeCapturedConsoleLog(level, message, prefix);
|
|
137214
138168
|
};
|
|
137215
138169
|
}
|
|
137216
|
-
/** Restore console.log/warn/error to their pre-capture implementations. */
|
|
137217
138170
|
releaseConsole() {
|
|
137218
138171
|
if (!this.originalConsole) return;
|
|
137219
138172
|
console.log = this.originalConsole.log;
|
|
@@ -137233,7 +138186,27 @@ var init_dashboard_tui = __esm({
|
|
|
137233
138186
|
this.log(message, prefix);
|
|
137234
138187
|
}
|
|
137235
138188
|
};
|
|
137236
|
-
|
|
138189
|
+
}
|
|
138190
|
+
});
|
|
138191
|
+
|
|
138192
|
+
// src/commands/dashboard-tui/utils.ts
|
|
138193
|
+
function isTTYAvailable() {
|
|
138194
|
+
return Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
138195
|
+
}
|
|
138196
|
+
var init_utils = __esm({
|
|
138197
|
+
"src/commands/dashboard-tui/utils.ts"() {
|
|
138198
|
+
"use strict";
|
|
138199
|
+
}
|
|
138200
|
+
});
|
|
138201
|
+
|
|
138202
|
+
// src/commands/dashboard-tui/index.ts
|
|
138203
|
+
var init_dashboard_tui = __esm({
|
|
138204
|
+
"src/commands/dashboard-tui/index.ts"() {
|
|
138205
|
+
"use strict";
|
|
138206
|
+
init_controller();
|
|
138207
|
+
init_log_sink();
|
|
138208
|
+
init_log_ring_buffer();
|
|
138209
|
+
init_utils();
|
|
137237
138210
|
}
|
|
137238
138211
|
});
|
|
137239
138212
|
|
|
@@ -137473,6 +138446,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
137473
138446
|
}
|
|
137474
138447
|
});
|
|
137475
138448
|
await tui.start();
|
|
138449
|
+
tui.setLoadingStatus("Initializing task store\u2026");
|
|
137476
138450
|
logSink.setTUI(tui);
|
|
137477
138451
|
logSink.captureConsole();
|
|
137478
138452
|
}
|
|
@@ -137593,8 +138567,10 @@ async function runDashboard(port, opts = {}) {
|
|
|
137593
138567
|
}
|
|
137594
138568
|
const automationStore = new AutomationStore(cwd);
|
|
137595
138569
|
await automationStore.init();
|
|
138570
|
+
if (tui) tui.setLoadingStatus("Initializing agent store\u2026");
|
|
137596
138571
|
agentStore = new AgentStore({ rootDir: store.getFusionDir() });
|
|
137597
138572
|
await agentStore.init();
|
|
138573
|
+
if (tui) tui.setLoadingStatus("Starting engine\u2026");
|
|
137598
138574
|
if (tui && isTTY) {
|
|
137599
138575
|
registerHandler(store, "task:created", scheduleStatsRefresh);
|
|
137600
138576
|
registerHandler(store, "task:moved", scheduleStatsRefresh);
|
|
@@ -138203,6 +139179,120 @@ async function runDashboard(port, opts = {}) {
|
|
|
138203
139179
|
active,
|
|
138204
139180
|
agents: agentStats
|
|
138205
139181
|
});
|
|
139182
|
+
if (centralCoreForMesh) {
|
|
139183
|
+
const centralCore = centralCoreForMesh;
|
|
139184
|
+
const projectStores = /* @__PURE__ */ new Map();
|
|
139185
|
+
tui.setInteractiveData({
|
|
139186
|
+
listProjects: async () => {
|
|
139187
|
+
const projects = await centralCore.listProjects();
|
|
139188
|
+
return projects.map((p) => ({ id: p.id, name: p.name, path: p.path }));
|
|
139189
|
+
},
|
|
139190
|
+
listTasks: async (projectPath) => {
|
|
139191
|
+
let projectStore = projectStores.get(projectPath);
|
|
139192
|
+
if (!projectStore) {
|
|
139193
|
+
projectStore = projectPath === cwd ? store : new TaskStore(projectPath);
|
|
139194
|
+
if (projectPath !== cwd) await projectStore.init();
|
|
139195
|
+
projectStores.set(projectPath, projectStore);
|
|
139196
|
+
}
|
|
139197
|
+
const tasks2 = await projectStore.listTasks({ slim: true, includeArchived: false });
|
|
139198
|
+
return tasks2.map((t) => ({
|
|
139199
|
+
id: t.id,
|
|
139200
|
+
title: t.title,
|
|
139201
|
+
description: t.description ?? "",
|
|
139202
|
+
column: t.column,
|
|
139203
|
+
agentState: t.agentState
|
|
139204
|
+
}));
|
|
139205
|
+
},
|
|
139206
|
+
createTask: async (projectPath, input) => {
|
|
139207
|
+
let projectStore = projectStores.get(projectPath);
|
|
139208
|
+
if (!projectStore) {
|
|
139209
|
+
projectStore = projectPath === cwd ? store : new TaskStore(projectPath);
|
|
139210
|
+
if (projectPath !== cwd) await projectStore.init();
|
|
139211
|
+
projectStores.set(projectPath, projectStore);
|
|
139212
|
+
}
|
|
139213
|
+
const created = await projectStore.createTask({
|
|
139214
|
+
title: input.title,
|
|
139215
|
+
description: input.description ?? input.title
|
|
139216
|
+
});
|
|
139217
|
+
return {
|
|
139218
|
+
id: created.id,
|
|
139219
|
+
title: created.title,
|
|
139220
|
+
description: created.description ?? "",
|
|
139221
|
+
column: created.column,
|
|
139222
|
+
agentState: created.agentState
|
|
139223
|
+
};
|
|
139224
|
+
},
|
|
139225
|
+
listAgents: async () => {
|
|
139226
|
+
const list = await agentStore.listAgents();
|
|
139227
|
+
return list.map((a) => ({
|
|
139228
|
+
id: a.id,
|
|
139229
|
+
name: a.name,
|
|
139230
|
+
state: a.state,
|
|
139231
|
+
role: a.role,
|
|
139232
|
+
taskId: a.taskId,
|
|
139233
|
+
lastHeartbeatAt: a.lastHeartbeatAt
|
|
139234
|
+
}));
|
|
139235
|
+
},
|
|
139236
|
+
getAgentDetail: async (id) => {
|
|
139237
|
+
const d = await agentStore.getAgentDetail(id, 10);
|
|
139238
|
+
if (!d) return null;
|
|
139239
|
+
return {
|
|
139240
|
+
id: d.id,
|
|
139241
|
+
name: d.name,
|
|
139242
|
+
state: d.state,
|
|
139243
|
+
role: d.role,
|
|
139244
|
+
taskId: d.taskId,
|
|
139245
|
+
lastHeartbeatAt: d.lastHeartbeatAt,
|
|
139246
|
+
title: d.title,
|
|
139247
|
+
capabilities: [d.role],
|
|
139248
|
+
recentRuns: d.completedRuns.slice(0, 10).map((r) => ({
|
|
139249
|
+
id: r.id,
|
|
139250
|
+
startedAt: r.startedAt,
|
|
139251
|
+
endedAt: r.endedAt,
|
|
139252
|
+
status: r.status,
|
|
139253
|
+
triggerDetail: r.triggerDetail
|
|
139254
|
+
}))
|
|
139255
|
+
};
|
|
139256
|
+
},
|
|
139257
|
+
updateAgentState: async (id, state) => {
|
|
139258
|
+
await agentStore.updateAgentState(id, state);
|
|
139259
|
+
},
|
|
139260
|
+
deleteAgent: async (id) => {
|
|
139261
|
+
await agentStore.deleteAgent(id);
|
|
139262
|
+
},
|
|
139263
|
+
getSettings: async () => {
|
|
139264
|
+
const s = await store.getSettings();
|
|
139265
|
+
return {
|
|
139266
|
+
maxConcurrent: s.maxConcurrent ?? 1,
|
|
139267
|
+
maxWorktrees: s.maxWorktrees ?? 2,
|
|
139268
|
+
autoMerge: s.autoMerge ?? false,
|
|
139269
|
+
mergeStrategy: s.mergeStrategy ?? "direct",
|
|
139270
|
+
pollIntervalMs: s.pollIntervalMs ?? 6e4,
|
|
139271
|
+
enginePaused: s.enginePaused ?? false,
|
|
139272
|
+
globalPause: s.globalPause ?? false
|
|
139273
|
+
};
|
|
139274
|
+
},
|
|
139275
|
+
updateSettings: async (partial) => {
|
|
139276
|
+
const mapped = {};
|
|
139277
|
+
if (partial.maxConcurrent !== void 0) mapped.maxConcurrent = partial.maxConcurrent;
|
|
139278
|
+
if (partial.maxWorktrees !== void 0) mapped.maxWorktrees = partial.maxWorktrees;
|
|
139279
|
+
if (partial.autoMerge !== void 0) mapped.autoMerge = partial.autoMerge;
|
|
139280
|
+
if (partial.mergeStrategy !== void 0) mapped.mergeStrategy = partial.mergeStrategy;
|
|
139281
|
+
if (partial.pollIntervalMs !== void 0) mapped.pollIntervalMs = partial.pollIntervalMs;
|
|
139282
|
+
if (partial.enginePaused !== void 0) mapped.enginePaused = partial.enginePaused;
|
|
139283
|
+
if (partial.globalPause !== void 0) mapped.globalPause = partial.globalPause;
|
|
139284
|
+
await store.updateSettings(mapped);
|
|
139285
|
+
},
|
|
139286
|
+
listModels: () => {
|
|
139287
|
+
return modelRegistry.getAll().map((m) => ({
|
|
139288
|
+
id: m.id,
|
|
139289
|
+
name: m.name,
|
|
139290
|
+
provider: m.provider ?? "unknown",
|
|
139291
|
+
contextWindow: m.contextWindow ?? 0
|
|
139292
|
+
}));
|
|
139293
|
+
}
|
|
139294
|
+
});
|
|
139295
|
+
}
|
|
138206
139296
|
tui.log(`Dashboard started at ${baseUrl}`);
|
|
138207
139297
|
if (engineMode === "active") {
|
|
138208
139298
|
tui.log("AI engine active");
|
|
@@ -138342,7 +139432,7 @@ __export(node_exports, {
|
|
|
138342
139432
|
runNodeRemove: () => runNodeRemove,
|
|
138343
139433
|
runNodeShow: () => runNodeShow
|
|
138344
139434
|
});
|
|
138345
|
-
import { createInterface as
|
|
139435
|
+
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
138346
139436
|
function maskApiKey(key) {
|
|
138347
139437
|
if (!key) return "none";
|
|
138348
139438
|
if (key.length < 4) return "****";
|
|
@@ -138537,7 +139627,7 @@ async function runNodeDisconnect(name, options = {}) {
|
|
|
138537
139627
|
process.exit(1);
|
|
138538
139628
|
}
|
|
138539
139629
|
if (!options.force) {
|
|
138540
|
-
const rl =
|
|
139630
|
+
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
138541
139631
|
const answer = await rl.question(`Disconnect node '${node.name}'? [y/N] `);
|
|
138542
139632
|
rl.close();
|
|
138543
139633
|
if (answer.trim().toLowerCase() !== "y") {
|
|
@@ -139766,13 +140856,13 @@ var desktop_exports = {};
|
|
|
139766
140856
|
__export(desktop_exports, {
|
|
139767
140857
|
runDesktop: () => runDesktop
|
|
139768
140858
|
});
|
|
139769
|
-
import { spawn as
|
|
140859
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
139770
140860
|
import { once as once2 } from "node:events";
|
|
139771
140861
|
import { join as join51 } from "node:path";
|
|
139772
140862
|
import { createRequire as createRequire3 } from "node:module";
|
|
139773
140863
|
function runCommand(command, args, cwd) {
|
|
139774
140864
|
return new Promise((resolve31, reject2) => {
|
|
139775
|
-
const child =
|
|
140865
|
+
const child = spawn6(command, args, {
|
|
139776
140866
|
cwd,
|
|
139777
140867
|
stdio: "inherit",
|
|
139778
140868
|
env: process.env
|
|
@@ -139857,7 +140947,7 @@ async function runDesktop(options = {}) {
|
|
|
139857
140947
|
electronEnv.FUSION_DASHBOARD_URL = process.env.FUSION_DASHBOARD_URL ?? "http://localhost:5173";
|
|
139858
140948
|
electronEnv.NODE_ENV = "development";
|
|
139859
140949
|
}
|
|
139860
|
-
const electronProcess =
|
|
140950
|
+
const electronProcess = spawn6(electronBinary, electronArgs, {
|
|
139861
140951
|
cwd: rootDir,
|
|
139862
140952
|
stdio: "inherit",
|
|
139863
140953
|
env: electronEnv
|
|
@@ -139929,7 +141019,7 @@ __export(task_exports, {
|
|
|
139929
141019
|
runTaskUnpause: () => runTaskUnpause,
|
|
139930
141020
|
runTaskUpdate: () => runTaskUpdate
|
|
139931
141021
|
});
|
|
139932
|
-
import { createInterface as
|
|
141022
|
+
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
139933
141023
|
import { watchFile, unwatchFile, statSync as statSync6, existsSync as existsSync37, readFileSync as readFileSync14 } from "node:fs";
|
|
139934
141024
|
import { join as join52 } from "node:path";
|
|
139935
141025
|
function asLocalProjectContext(store) {
|
|
@@ -139998,7 +141088,7 @@ async function runTaskCreate(descriptionArg, attachFiles, depends, projectName)
|
|
|
139998
141088
|
let description = descriptionArg;
|
|
139999
141089
|
const projectContext = await getProjectContext(projectName);
|
|
140000
141090
|
if (!description) {
|
|
140001
|
-
const rl =
|
|
141091
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
140002
141092
|
description = await rl.question("Task description: ");
|
|
140003
141093
|
rl.close();
|
|
140004
141094
|
}
|
|
@@ -140334,7 +141424,7 @@ async function runTaskRefine(id, feedbackArg, projectName) {
|
|
|
140334
141424
|
const store = await getStore(projectName);
|
|
140335
141425
|
let feedback = feedbackArg;
|
|
140336
141426
|
if (feedback === void 0) {
|
|
140337
|
-
const rl =
|
|
141427
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
140338
141428
|
feedback = await rl.question("What needs to be refined? ");
|
|
140339
141429
|
rl.close();
|
|
140340
141430
|
}
|
|
@@ -140405,7 +141495,7 @@ async function runTaskDelete(id, force, projectName) {
|
|
|
140405
141495
|
return;
|
|
140406
141496
|
}
|
|
140407
141497
|
if (!force) {
|
|
140408
|
-
const rl =
|
|
141498
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
140409
141499
|
const answer = await rl.question(`Are you sure you want to delete ${id}? [y/N] `);
|
|
140410
141500
|
rl.close();
|
|
140411
141501
|
const trimmed = answer.trim().toLowerCase();
|
|
@@ -140469,7 +141559,7 @@ async function runTaskImportGitHubInteractive(ownerRepo, options = {}, projectNa
|
|
|
140469
141559
|
console.log(` ${i + 1}. #${issue.number} ${issue.title.slice(0, 80)}${issue.title.length > 80 ? "\u2026" : ""}${status}`);
|
|
140470
141560
|
}
|
|
140471
141561
|
console.log();
|
|
140472
|
-
const rl =
|
|
141562
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
140473
141563
|
let selectedIndices = [];
|
|
140474
141564
|
let validInput = false;
|
|
140475
141565
|
while (!validInput) {
|
|
@@ -140612,7 +141702,7 @@ async function runTaskComment(id, message, author = "user", projectName) {
|
|
|
140612
141702
|
const store = await getStore(projectName);
|
|
140613
141703
|
let text = message;
|
|
140614
141704
|
if (text === void 0) {
|
|
140615
|
-
const rl =
|
|
141705
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
140616
141706
|
text = await rl.question("Comment: ");
|
|
140617
141707
|
rl.close();
|
|
140618
141708
|
}
|
|
@@ -140655,7 +141745,7 @@ async function runTaskSteer(id, message, projectName) {
|
|
|
140655
141745
|
const store = await getStore(projectName);
|
|
140656
141746
|
let text = message;
|
|
140657
141747
|
if (text === void 0) {
|
|
140658
|
-
const rl =
|
|
141748
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
140659
141749
|
text = await rl.question("Message: ");
|
|
140660
141750
|
rl.close();
|
|
140661
141751
|
}
|
|
@@ -140786,7 +141876,7 @@ async function promptText(question) {
|
|
|
140786
141876
|
console.log(` ${question.description}`);
|
|
140787
141877
|
}
|
|
140788
141878
|
console.log(" (Enter your response. Type DONE on its own line when finished):\n");
|
|
140789
|
-
const rl =
|
|
141879
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
140790
141880
|
const lines = [];
|
|
140791
141881
|
return new Promise((resolve31) => {
|
|
140792
141882
|
const askLine = () => {
|
|
@@ -140820,7 +141910,7 @@ async function promptSingleSelect(question) {
|
|
|
140820
141910
|
console.log(` ${opt.description}`);
|
|
140821
141911
|
}
|
|
140822
141912
|
}
|
|
140823
|
-
const rl =
|
|
141913
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
140824
141914
|
while (true) {
|
|
140825
141915
|
const answer = await rl.question("\n Select (1-" + question.options.length + "): ");
|
|
140826
141916
|
const num = parseInt(answer.trim(), 10);
|
|
@@ -140848,7 +141938,7 @@ async function promptMultiSelect(question) {
|
|
|
140848
141938
|
console.log(` ${opt.description}`);
|
|
140849
141939
|
}
|
|
140850
141940
|
}
|
|
140851
|
-
const rl =
|
|
141941
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
140852
141942
|
while (true) {
|
|
140853
141943
|
const answer = await rl.question("\n Select (comma-separated): ");
|
|
140854
141944
|
const nums = answer.split(",").map((s) => parseInt(s.trim(), 10)).filter((n) => !isNaN(n));
|
|
@@ -140871,7 +141961,7 @@ async function promptConfirm(question) {
|
|
|
140871
141961
|
if (question.description) {
|
|
140872
141962
|
console.log(` ${question.description}`);
|
|
140873
141963
|
}
|
|
140874
|
-
const rl =
|
|
141964
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
140875
141965
|
const answer = await rl.question("\n [Y/n]: ");
|
|
140876
141966
|
rl.close();
|
|
140877
141967
|
const trimmed = answer.trim().toLowerCase();
|
|
@@ -140936,7 +142026,7 @@ function wrapText(text, width) {
|
|
|
140936
142026
|
async function runTaskPlan(initialPlanArg, yesFlag = false, projectName) {
|
|
140937
142027
|
let initialPlan = initialPlanArg;
|
|
140938
142028
|
if (!initialPlan) {
|
|
140939
|
-
const rl =
|
|
142029
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
140940
142030
|
console.log("\n Let's plan your task. What would you like to accomplish?\n");
|
|
140941
142031
|
initialPlan = await rl.question(" Describe your idea: ");
|
|
140942
142032
|
rl.close();
|
|
@@ -141037,7 +142127,7 @@ async function runTaskPlan(initialPlanArg, yesFlag = false, projectName) {
|
|
|
141037
142127
|
displaySummary(result.data);
|
|
141038
142128
|
let confirmed = yesFlag;
|
|
141039
142129
|
if (!yesFlag) {
|
|
141040
|
-
const rl =
|
|
142130
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
141041
142131
|
const answer = await rl.question(" Create this task? [Y/n]: ");
|
|
141042
142132
|
rl.close();
|
|
141043
142133
|
const trimmed = answer.trim().toLowerCase();
|
|
@@ -141521,7 +142611,7 @@ __export(git_exports, {
|
|
|
141521
142611
|
});
|
|
141522
142612
|
import { exec as exec10 } from "node:child_process";
|
|
141523
142613
|
import { promisify as promisify12 } from "node:util";
|
|
141524
|
-
import { createInterface as
|
|
142614
|
+
import { createInterface as createInterface4 } from "node:readline/promises";
|
|
141525
142615
|
async function resolveGitCwd(projectName) {
|
|
141526
142616
|
if (projectName) {
|
|
141527
142617
|
return (await resolveProject(projectName)).projectPath;
|
|
@@ -141709,7 +142799,7 @@ async function runGitPull(options = {}) {
|
|
|
141709
142799
|
console.log();
|
|
141710
142800
|
console.log(" \u26A0 Warning: You have uncommitted changes.");
|
|
141711
142801
|
console.log(` Branch: ${status.branch}`);
|
|
141712
|
-
const rl =
|
|
142802
|
+
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
141713
142803
|
const answer = await rl.question(" Continue with pull? [y/N] ");
|
|
141714
142804
|
rl.close();
|
|
141715
142805
|
const trimmed = answer.trim().toLowerCase();
|
|
@@ -141760,7 +142850,7 @@ async function runGitPush(options = {}) {
|
|
|
141760
142850
|
}
|
|
141761
142851
|
if (!options.skipConfirm) {
|
|
141762
142852
|
console.log();
|
|
141763
|
-
const rl =
|
|
142853
|
+
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
141764
142854
|
const answer = await rl.question(` Push branch ${status.branch} to remote? [Y/n] `);
|
|
141765
142855
|
rl.close();
|
|
141766
142856
|
const trimmed = answer.trim().toLowerCase();
|
|
@@ -141897,7 +142987,7 @@ var init_backup2 = __esm({
|
|
|
141897
142987
|
// src/project-resolver.ts
|
|
141898
142988
|
import { existsSync as existsSync39, statSync as statSync7 } from "node:fs";
|
|
141899
142989
|
import { dirname as dirname21, resolve as resolve25, normalize as normalize5 } from "node:path";
|
|
141900
|
-
import { createInterface as
|
|
142990
|
+
import { createInterface as createInterface5 } from "node:readline/promises";
|
|
141901
142991
|
async function getCentralCore() {
|
|
141902
142992
|
if (!centralCoreInstance) {
|
|
141903
142993
|
centralCoreInstance = new CentralCore();
|
|
@@ -141928,7 +143018,7 @@ function findKbDir(startPath) {
|
|
|
141928
143018
|
return null;
|
|
141929
143019
|
}
|
|
141930
143020
|
async function promptProjectSelection(projects, message = "Select a project:") {
|
|
141931
|
-
const rl =
|
|
143021
|
+
const rl = createInterface5({ input: process.stdin, output: process.stdout });
|
|
141932
143022
|
console.log(`
|
|
141933
143023
|
${message}`);
|
|
141934
143024
|
for (let i = 0; i < projects.length; i++) {
|
|
@@ -141945,7 +143035,7 @@ async function promptProjectSelection(projects, message = "Select a project:") {
|
|
|
141945
143035
|
}
|
|
141946
143036
|
}
|
|
141947
143037
|
async function promptConfirm2(message, defaultYes = false) {
|
|
141948
|
-
const rl =
|
|
143038
|
+
const rl = createInterface5({ input: process.stdin, output: process.stdout });
|
|
141949
143039
|
const prompt = defaultYes ? "[Y/n]" : "[y/N]";
|
|
141950
143040
|
const answer = await rl.question(` ${message} ${prompt}: `);
|
|
141951
143041
|
rl.close();
|
|
@@ -142005,7 +143095,7 @@ Run \`fn project remove ` + match.name + "` to clean up the registry entry.",
|
|
|
142005
143095
|
Found fn project at ${fusionDir} but it's not registered.`);
|
|
142006
143096
|
const shouldRegister = await promptConfirm2("Register this project now?", true);
|
|
142007
143097
|
if (shouldRegister) {
|
|
142008
|
-
const rl =
|
|
143098
|
+
const rl = createInterface5({ input: process.stdin, output: process.stdout });
|
|
142009
143099
|
const defaultName = fusionDir.split("/").pop() || "unnamed";
|
|
142010
143100
|
const name = await rl.question(` Project name [${defaultName}]: `);
|
|
142011
143101
|
rl.close();
|
|
@@ -142167,12 +143257,12 @@ __export(mission_exports, {
|
|
|
142167
143257
|
runMissionShow: () => runMissionShow,
|
|
142168
143258
|
runSliceAdd: () => runSliceAdd
|
|
142169
143259
|
});
|
|
142170
|
-
import { createInterface as
|
|
143260
|
+
import { createInterface as createInterface6 } from "node:readline/promises";
|
|
142171
143261
|
async function promptForTitleAndDescription(titleArg, titlePrompt, descriptionPrompt) {
|
|
142172
143262
|
let title = titleArg;
|
|
142173
143263
|
let description;
|
|
142174
143264
|
if (!title) {
|
|
142175
|
-
const rl =
|
|
143265
|
+
const rl = createInterface6({ input: process.stdin, output: process.stdout });
|
|
142176
143266
|
title = await rl.question(titlePrompt);
|
|
142177
143267
|
if (!title?.trim()) {
|
|
142178
143268
|
rl.close();
|
|
@@ -142301,7 +143391,7 @@ async function runMissionDelete(id, force, projectName) {
|
|
|
142301
143391
|
process.exit(1);
|
|
142302
143392
|
}
|
|
142303
143393
|
if (!force) {
|
|
142304
|
-
const rl =
|
|
143394
|
+
const rl = createInterface6({ input: process.stdin, output: process.stdout });
|
|
142305
143395
|
const answer = await rl.question(`Are you sure you want to delete ${id}: "${mission.title}"? [y/N] `);
|
|
142306
143396
|
rl.close();
|
|
142307
143397
|
const trimmed = answer.trim().toLowerCase();
|
|
@@ -142402,7 +143492,7 @@ async function runFeatureAdd(sliceId, titleArg, descriptionArg, acceptanceCriter
|
|
|
142402
143492
|
let description = descriptionArg?.trim() || void 0;
|
|
142403
143493
|
let acceptanceCriteria = acceptanceCriteriaArg?.trim() || void 0;
|
|
142404
143494
|
if (!title) {
|
|
142405
|
-
const rl =
|
|
143495
|
+
const rl = createInterface6({ input: process.stdin, output: process.stdout });
|
|
142406
143496
|
title = await rl.question("Feature title: ");
|
|
142407
143497
|
if (!title?.trim()) {
|
|
142408
143498
|
rl.close();
|
|
@@ -142496,7 +143586,7 @@ __export(project_exports, {
|
|
|
142496
143586
|
});
|
|
142497
143587
|
import { resolve as resolve26, isAbsolute as isAbsolute13, relative as relative11, basename as basename13 } from "node:path";
|
|
142498
143588
|
import { existsSync as existsSync40, statSync as statSync8 } from "node:fs";
|
|
142499
|
-
import { createInterface as
|
|
143589
|
+
import { createInterface as createInterface7 } from "node:readline/promises";
|
|
142500
143590
|
function formatDisplayPath(projectPath) {
|
|
142501
143591
|
const rel = relative11(process.cwd(), projectPath);
|
|
142502
143592
|
if (rel && !rel.startsWith("..") && rel !== "") {
|
|
@@ -142617,7 +143707,7 @@ async function runProjectAdd(name, path4, options = {}) {
|
|
|
142617
143707
|
let projectName = name;
|
|
142618
143708
|
let projectPath = path4;
|
|
142619
143709
|
if (!projectName || !projectPath || options.interactive) {
|
|
142620
|
-
const rl =
|
|
143710
|
+
const rl = createInterface7({ input: process.stdin, output: process.stdout });
|
|
142621
143711
|
if (!projectPath) {
|
|
142622
143712
|
const defaultPath = process.cwd();
|
|
142623
143713
|
const pathInput = await rl.question(` Project path [${defaultPath}]: `);
|
|
@@ -142743,7 +143833,7 @@ async function runProjectRemove(name, options = {}) {
|
|
|
142743
143833
|
process.exit(1);
|
|
142744
143834
|
}
|
|
142745
143835
|
if (!options.force) {
|
|
142746
|
-
const rl =
|
|
143836
|
+
const rl = createInterface7({ input: process.stdin, output: process.stdout });
|
|
142747
143837
|
const answer = await rl.question(`Unregister project '${project.name}'? [y/N] `);
|
|
142748
143838
|
rl.close();
|
|
142749
143839
|
if (answer.trim().toLowerCase() !== "y") {
|
|
@@ -143805,7 +144895,7 @@ __export(plugin_exports, {
|
|
|
143805
144895
|
import { existsSync as existsSync44 } from "node:fs";
|
|
143806
144896
|
import { join as join56 } from "node:path";
|
|
143807
144897
|
import { readFile as readFile23 } from "node:fs/promises";
|
|
143808
|
-
import * as
|
|
144898
|
+
import * as readline from "node:readline";
|
|
143809
144899
|
async function getProjectPath6(projectName) {
|
|
143810
144900
|
if (projectName) {
|
|
143811
144901
|
const context = await resolveProject(projectName);
|
|
@@ -143945,7 +145035,7 @@ async function runPluginUninstall(id, options) {
|
|
|
143945
145035
|
console.log(` This will stop and remove the plugin.`);
|
|
143946
145036
|
console.log();
|
|
143947
145037
|
const response = await new Promise((resolve31) => {
|
|
143948
|
-
const rl =
|
|
145038
|
+
const rl = readline.createInterface({
|
|
143949
145039
|
input: process.stdin,
|
|
143950
145040
|
output: process.stdout
|
|
143951
145041
|
});
|
|
@@ -144216,7 +145306,7 @@ __export(skills_exports, {
|
|
|
144216
145306
|
runSkillsSearch: () => runSkillsSearch,
|
|
144217
145307
|
searchSkills: () => searchSkills
|
|
144218
145308
|
});
|
|
144219
|
-
import { spawn as
|
|
145309
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
144220
145310
|
async function searchSkills(query, limit = 10) {
|
|
144221
145311
|
const url = `${SKILLS_API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`;
|
|
144222
145312
|
try {
|
|
@@ -144294,7 +145384,7 @@ async function runSkillsInstall(args, options) {
|
|
|
144294
145384
|
npxArgs.push("--skill", options.skill);
|
|
144295
145385
|
}
|
|
144296
145386
|
npxArgs.push("-y", "-a", "pi");
|
|
144297
|
-
const child =
|
|
145387
|
+
const child = spawn7("npx", npxArgs, {
|
|
144298
145388
|
cwd: process.cwd(),
|
|
144299
145389
|
stdio: "inherit"
|
|
144300
145390
|
});
|
|
@@ -144628,6 +145718,7 @@ var HELP = `
|
|
|
144628
145718
|
fn \u2014 AI-orchestrated task board
|
|
144629
145719
|
|
|
144630
145720
|
Usage:
|
|
145721
|
+
fn Launch the dashboard (same as fn dashboard)
|
|
144631
145722
|
fn init [opts] Initialize a new fn project in the current directory
|
|
144632
145723
|
fn dashboard Start the board web UI
|
|
144633
145724
|
fn dashboard --paused Start with automation paused
|
|
@@ -144791,10 +145882,15 @@ function getFlagValueNumber(args, flag) {
|
|
|
144791
145882
|
}
|
|
144792
145883
|
async function main() {
|
|
144793
145884
|
const { cleanedArgs: args, projectName } = extractGlobalProjectFlag(process.argv.slice(2));
|
|
144794
|
-
if (args.
|
|
145885
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
144795
145886
|
console.log(HELP);
|
|
144796
145887
|
process.exit(0);
|
|
144797
145888
|
}
|
|
145889
|
+
if (args.length === 0) {
|
|
145890
|
+
const { runDashboard: runDashboard3 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
|
|
145891
|
+
await runDashboard3(4040);
|
|
145892
|
+
return;
|
|
145893
|
+
}
|
|
144798
145894
|
const command = args[0];
|
|
144799
145895
|
const {
|
|
144800
145896
|
runDashboard: runDashboard2,
|