u-foo 2.3.32 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -213
- package/README.zh-CN.md +151 -197
- package/SKILLS/ufoo/SKILL.md +8 -8
- package/bin/uagy.js +69 -0
- package/bin/uclaude.js +2 -2
- package/bin/ucode.js +4 -4
- package/bin/ucodex.js +2 -2
- package/bin/ufoo.js +5 -23
- package/modules/AGENTS.template.md +1 -1
- package/modules/bus/SKILLS/ubus/SKILL.md +35 -10
- package/package.json +5 -5
- package/scripts/chat-app-smoke.js +1 -1
- package/scripts/global-chat-switch-benchmark.js +5 -5
- package/scripts/ink-demo.js +1 -1
- package/scripts/ink-smoke.js +1 -1
- package/scripts/ucode-app-smoke.js +1 -1
- package/src/{agent → agents/activity}/activityDetector.js +39 -2
- package/src/{agent → agents/activity}/activityStatePublisher.js +1 -1
- package/src/{agent → agents/activity}/activityStateWriter.js +2 -2
- package/src/{agent → agents/activity}/activityTracker.js +1 -1
- package/src/agents/activity/index.js +8 -0
- package/src/{agent → agents/controller}/controllerToolExecutor.js +4 -4
- package/src/agents/controller/index.js +8 -0
- package/src/{agent → agents/controller}/loopObservability.js +2 -2
- package/src/{agent → agents/controller}/loopRuntime.js +1 -1
- package/src/{agent → agents/controller}/ufooAgent.js +9 -9
- package/src/agents/index.js +10 -0
- package/src/agents/internal/index.js +3 -0
- package/src/{agent → agents/internal}/internalRunner.js +45 -22
- package/src/agents/launch/agyConversation.js +159 -0
- package/src/agents/launch/index.js +12 -0
- package/src/{agent → agents/launch}/launchEnvironment.js +2 -3
- package/src/{agent → agents/launch}/launcher.js +64 -21
- package/src/{agent → agents/launch}/notifier.js +23 -12
- package/src/{agent → agents/launch}/ptyRunner.js +44 -12
- package/src/{agent → agents/launch}/ptyWrapper.js +2 -2
- package/src/{agent → agents/launch}/publisherRouting.js +1 -1
- package/src/{agent → agents/launch}/readyDetector.js +23 -0
- package/src/{agent → agents/prompts}/defaultBootstrap.js +63 -4
- package/src/{group/bootstrap.js → agents/prompts/groupBootstrap.js} +41 -6
- package/src/agents/prompts/index.js +8 -0
- package/src/{code/prompts → agents/prompts/native}/index.js +1 -1
- package/src/{agent → agents/providers}/claudeThreadProvider.js +1 -1
- package/src/{agent → agents/providers}/codexThreadProvider.js +1 -1
- package/src/{agent → agents/providers}/directAuthStatus.js +184 -1
- package/src/agents/providers/index.js +13 -0
- package/src/{agent → agents/providers}/upstreamTransport.js +2 -2
- package/src/{chat → app/chat}/agentSockets.js +1 -1
- package/src/{chat → app/chat}/commandExecutor.js +63 -26
- package/src/{chat → app/chat}/commands.js +119 -5
- package/src/{chat → app/chat}/daemonConnection.js +1 -1
- package/src/{chat → app/chat}/daemonMessageRouter.js +45 -3
- package/src/{chat → app/chat}/daemonReconnect.js +3 -0
- package/src/{chat → app/chat}/dashboardView.js +2 -1
- package/src/app/chat/index.js +6 -0
- package/src/{chat → app/chat}/inputSubmitHandler.js +4 -13
- package/src/{chat → app/chat}/internalAgentLogHistory.js +1 -1
- package/src/app/chat/multiWindow/index.js +268 -0
- package/src/app/chat/multiWindow/paneLayout.js +84 -0
- package/src/app/chat/multiWindow/paneManager.js +299 -0
- package/src/app/chat/multiWindow/renderer.js +384 -0
- package/src/app/chat/multiWindow/virtualTerminal.js +327 -0
- package/src/{chat → app/chat}/transport.js +1 -1
- package/src/{cli → app/cli}/ctxCoreCommands.js +3 -3
- package/src/{doctor/index.js → app/cli/features/doctor.js} +1 -1
- package/src/{init/index.js → app/cli/features/init.js} +14 -32
- package/src/{cli → app/cli}/groupCoreCommands.js +2 -2
- package/src/app/cli/index.js +9 -0
- package/src/{cli → app/cli}/onlineCoreCommands.js +5 -5
- package/src/{cli.js → app/cli/run.js} +59 -57
- package/src/app/index.js +6 -0
- package/src/code/agent.js +10 -9
- package/src/code/index.js +2 -0
- package/src/code/launcher/index.js +9 -0
- package/src/{agent → code/launcher}/ucode.js +7 -8
- package/src/{agent → code/launcher}/ucodeBootstrap.js +3 -3
- package/src/{agent → code/launcher}/ucodeBuild.js +2 -2
- package/src/{agent → code/launcher}/ucodeDoctor.js +2 -2
- package/src/{agent → code/launcher}/ucodeRuntimeConfig.js +1 -2
- package/src/code/nativeRunner.js +4 -4
- package/src/code/tui.js +3 -1454
- package/src/config.js +15 -2
- package/src/{bus → coordination/bus}/activate.js +2 -2
- package/src/{bus → coordination/bus}/daemon.js +15 -5
- package/src/coordination/bus/envelope.js +173 -0
- package/src/{bus → coordination/bus}/index.js +7 -3
- package/src/{bus → coordination/bus}/inject.js +11 -3
- package/src/{bus → coordination/bus}/message.js +1 -1
- package/src/coordination/bus/messageMeta.js +130 -0
- package/src/coordination/bus/promptEnvelope.js +65 -0
- package/src/{bus → coordination/bus}/shake.js +1 -1
- package/src/{bus → coordination/bus}/store.js +3 -3
- package/src/{bus → coordination/bus}/subscriber.js +2 -2
- package/src/{bus → coordination/bus}/utils.js +2 -2
- package/src/{history → coordination/history}/inputTimeline.js +5 -5
- package/src/coordination/index.js +10 -0
- package/src/{memory → coordination/memory}/historySearch.js +1 -1
- package/src/{memory → coordination/memory}/index.js +3 -3
- package/src/{report → coordination/report}/store.js +2 -2
- package/src/{status → coordination/status}/index.js +3 -3
- package/src/online/bridge.js +2 -2
- package/src/{controller → orchestration/controller}/flags.js +1 -1
- package/src/{controller → orchestration/controller}/gateRouter.js +1 -1
- package/src/orchestration/controller/index.js +10 -0
- package/src/{controller → orchestration/controller}/shadowGuard.js +1 -1
- package/src/orchestration/groups/bootstrap.js +3 -0
- package/src/orchestration/groups/index.js +10 -0
- package/src/orchestration/groups/promptProfiles.js +3 -0
- package/src/{group → orchestration/groups}/templates.js +1 -1
- package/src/{group → orchestration/groups}/validateTemplate.js +1 -1
- package/src/orchestration/index.js +7 -0
- package/src/orchestration/solo/index.js +3 -0
- package/src/{daemon → runtime/daemon}/agentProcessManager.js +1 -1
- package/src/{daemon → runtime/daemon}/cronOps.js +3 -2
- package/src/{daemon → runtime/daemon}/groupOrchestrator.js +26 -9
- package/src/{daemon → runtime/daemon}/index.js +105 -53
- package/src/{daemon → runtime/daemon}/ipcServer.js +1 -1
- package/src/{daemon → runtime/daemon}/nicknameScope.js +6 -3
- package/src/{daemon → runtime/daemon}/ops.js +48 -61
- package/src/{daemon → runtime/daemon}/promptLoop.js +1 -1
- package/src/{daemon → runtime/daemon}/promptRequest.js +7 -7
- package/src/runtime/daemon/providerSessions.js +230 -0
- package/src/{daemon → runtime/daemon}/reporting.js +4 -4
- package/src/{daemon → runtime/daemon}/run.js +4 -4
- package/src/{daemon → runtime/daemon}/soloBootstrap.js +7 -7
- package/src/{daemon → runtime/daemon}/status.js +5 -5
- package/src/runtime/index.js +10 -0
- package/src/{projects → runtime/projects}/registry.js +1 -1
- package/src/{terminal → runtime/terminal}/adapterRouter.js +0 -10
- package/src/{terminal → runtime/terminal}/adapters/internalAdapter.js +0 -4
- package/src/tools/handlers/common.js +1 -1
- package/src/tools/handlers/listAgents.js +1 -1
- package/src/tools/handlers/memory.js +3 -3
- package/src/tools/handlers/readBusSummary.js +1 -1
- package/src/tools/handlers/readOpenDecisions.js +1 -1
- package/src/tools/handlers/readProjectRegistry.js +1 -1
- package/src/tools/handlers/readPromptHistory.js +2 -2
- package/src/tools/schemaFixtures.js +1 -1
- package/src/ui/MIGRATION.md +42 -88
- package/src/ui/format/index.js +5 -28
- package/src/ui/index.js +1 -1
- package/src/ui/{components → ink}/ChatApp.js +812 -88
- package/src/ui/ink/DashboardBar.js +685 -0
- package/src/ui/{components → ink}/MultilineInput.js +230 -5
- package/src/ui/{components → ink}/UcodeApp.js +16 -7
- package/src/ui/{components → ink}/agentMirror.js +24 -19
- package/src/ui/{components → ink}/chatReducer.js +29 -7
- package/src/bus/messageMeta.js +0 -52
- package/src/chat/agentViewController.js +0 -1072
- package/src/chat/chatLogController.js +0 -138
- package/src/chat/completionController.js +0 -533
- package/src/chat/dashboardKeyController.js +0 -533
- package/src/chat/index.js +0 -2222
- package/src/chat/inputHistoryController.js +0 -135
- package/src/chat/inputListenerController.js +0 -470
- package/src/chat/layout.js +0 -186
- package/src/chat/pasteController.js +0 -81
- package/src/chat/statusLineController.js +0 -223
- package/src/chat/streamTracker.js +0 -156
- package/src/code/config +0 -0
- package/src/daemon/providerSessions.js +0 -488
- package/src/terminal/adapters/internalPtyAdapter.js +0 -42
- package/src/ui/components/DashboardBar.js +0 -417
- /package/src/{code/prompts → agents/prompts/native}/actions.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/efficiency.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/environment.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/identity.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/safety.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/sections.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/system.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/tasks.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/bash.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/edit.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/read.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/toolDescriptions/write.js +0 -0
- /package/src/{code/prompts → agents/prompts/native}/ufoo.js +0 -0
- /package/src/{group → agents/prompts}/promptProfiles.js +0 -0
- /package/src/{agent → agents/providers}/claudeEventTranslator.js +0 -0
- /package/src/{agent → agents/providers}/claudeOauthTokenReader.js +0 -0
- /package/src/{agent → agents/providers}/claudeSessionFiles.js +0 -0
- /package/src/{agent → agents/providers}/codexEventTranslator.js +0 -0
- /package/src/{agent → agents/providers}/credentials/claude.js +0 -0
- /package/src/{agent → agents/providers}/credentials/codex.js +0 -0
- /package/src/{agent → agents/providers}/credentials/index.js +0 -0
- /package/src/{chat → app/chat}/agentBar.js +0 -0
- /package/src/{chat → app/chat}/agentDirectory.js +0 -0
- /package/src/{chat → app/chat}/cronScheduler.js +0 -0
- /package/src/{chat → app/chat}/daemonCoordinator.js +0 -0
- /package/src/{chat → app/chat}/daemonTransport.js +0 -0
- /package/src/{chat → app/chat}/daemonTransportDefaults.js +0 -0
- /package/src/{chat → app/chat}/inputMath.js +0 -0
- /package/src/{chat → app/chat}/projectCloseController.js +0 -0
- /package/src/{chat → app/chat}/rawKeyMap.js +0 -0
- /package/src/{chat → app/chat}/settingsController.js +0 -0
- /package/src/{chat → app/chat}/shellCommand.js +0 -0
- /package/src/{chat → app/chat}/text.js +0 -0
- /package/src/{chat → app/chat}/transientAgentState.js +0 -0
- /package/src/{cli → app/cli}/busCoreCommands.js +0 -0
- /package/src/{skills/index.js → app/cli/features/skills.js} +0 -0
- /package/src/{bus → coordination/bus}/nickname.js +0 -0
- /package/src/{bus → coordination/bus}/queue.js +0 -0
- /package/src/{context → coordination/context}/decisions.js +0 -0
- /package/src/{context → coordination/context}/doctor.js +0 -0
- /package/src/{context → coordination/context}/index.js +0 -0
- /package/src/{context → coordination/context}/sync.js +0 -0
- /package/src/{ufoo → coordination/state}/agentRegistryDiagnostics.js +0 -0
- /package/src/{ufoo → coordination/state}/agentsStore.js +0 -0
- /package/src/{ufoo → coordination/state}/paths.js +0 -0
- /package/src/{controller → orchestration/controller}/launchRouting.js +0 -0
- /package/src/{controller → orchestration/controller}/routerFastPath.js +0 -0
- /package/src/{controller → orchestration/controller}/routerFinalize.js +0 -0
- /package/src/{group → orchestration/groups}/diagram.js +0 -0
- /package/src/{group → orchestration/groups}/templateValidation.js +0 -0
- /package/src/{solo → orchestration/solo}/commands.js +0 -0
- /package/src/{shared → runtime/contracts}/eventContract.js +0 -0
- /package/src/{shared → runtime/contracts}/ptySocketContract.js +0 -0
- /package/src/{providerapi → runtime/privacy}/redactor.js +0 -0
- /package/src/{providerapi → runtime/privacy}/shadowDiff.js +0 -0
- /package/src/{utils → runtime/process}/nodeExecutable.js +0 -0
- /package/src/{projects → runtime/projects}/identity.js +0 -0
- /package/src/{projects → runtime/projects}/index.js +0 -0
- /package/src/{projects → runtime/projects}/projectId.js +0 -0
- /package/src/{projects → runtime/projects}/runtimes.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapterContract.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/externalAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/hostAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/internalQueueAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/terminalAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/adapters/tmuxAdapter.js +0 -0
- /package/src/{terminal → runtime/terminal}/detect.js +0 -0
- /package/src/{terminal → runtime/terminal}/index.js +0 -0
- /package/src/{terminal → runtime/terminal}/iterm2.js +0 -0
- /package/src/{utils → ui/format}/banner.js +0 -0
- /package/src/{shared → ui/format}/markdownRenderer.js +0 -0
- /package/src/ui/{components → ink}/InkDemo.js +0 -0
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Ink-based chat TUI
|
|
5
|
-
* src/chat/index.js but rendered via React + ink.
|
|
4
|
+
* Ink-based chat TUI rendered via React + ink.
|
|
6
5
|
*
|
|
7
|
-
* Activation:
|
|
8
|
-
* legacy blessed renderer while it remains available as a fallback.
|
|
6
|
+
* Activation: this is the only chat TUI.
|
|
9
7
|
*
|
|
10
8
|
* Coverage today: layout shell + dashboard bar (5 modes: projects, agents,
|
|
11
9
|
* mode, provider, cron) + multiline editor + status line +
|
|
@@ -27,15 +25,15 @@ const { createDashboardBar } = require("./DashboardBar");
|
|
|
27
25
|
const { reducer, createInitialState } = require("./chatReducer");
|
|
28
26
|
|
|
29
27
|
function bootstrapEnvironment(projectRoot, options = {}) {
|
|
30
|
-
//
|
|
31
|
-
//
|
|
28
|
+
// Ensure ufoo dirs exist and that we have a stable subscriber ID.
|
|
29
|
+
// We deliberately keep the
|
|
32
30
|
// non-UI side-effects in their own helper so unit tests can assert on
|
|
33
31
|
// them without importing ink.
|
|
34
|
-
const { canonicalProjectRoot } = require("../../projects");
|
|
35
|
-
const { getUfooPaths } = require("../../
|
|
36
|
-
const UfooInit = require("../../init");
|
|
37
|
-
const { isRunning } = require("../../daemon");
|
|
38
|
-
const { startDaemon } = require("../../chat/transport");
|
|
32
|
+
const { canonicalProjectRoot } = require("../../runtime/projects");
|
|
33
|
+
const { getUfooPaths } = require("../../coordination/state/paths");
|
|
34
|
+
const UfooInit = require("../../app/cli/features/init");
|
|
35
|
+
const { isRunning } = require("../../runtime/daemon");
|
|
36
|
+
const { startDaemon } = require("../../app/chat/transport");
|
|
39
37
|
|
|
40
38
|
const globalMode = options && options.globalMode === true;
|
|
41
39
|
let activeProjectRoot = projectRoot;
|
|
@@ -67,7 +65,7 @@ function bootstrapEnvironment(projectRoot, options = {}) {
|
|
|
67
65
|
|
|
68
66
|
async function ensureSubscriberId(projectRoot) {
|
|
69
67
|
if (process.env.UFOO_SUBSCRIBER_ID) return;
|
|
70
|
-
const { getUfooPaths } = require("../../
|
|
68
|
+
const { getUfooPaths } = require("../../coordination/state/paths");
|
|
71
69
|
const sessionFile = path.join(getUfooPaths(projectRoot).ufooDir, "chat", "session-id.txt");
|
|
72
70
|
const sessionDir = path.dirname(sessionFile);
|
|
73
71
|
fs.mkdirSync(sessionDir, { recursive: true });
|
|
@@ -82,7 +80,7 @@ async function ensureSubscriberId(projectRoot) {
|
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
function inputHistoryFilePath(projectRoot, options = {}) {
|
|
85
|
-
const { getUfooPaths } = require("../../
|
|
83
|
+
const { getUfooPaths } = require("../../coordination/state/paths");
|
|
86
84
|
const { globalMode } = options || {};
|
|
87
85
|
if (globalMode) {
|
|
88
86
|
const os = require("os");
|
|
@@ -95,7 +93,7 @@ function inputHistoryFilePath(projectRoot, options = {}) {
|
|
|
95
93
|
}
|
|
96
94
|
|
|
97
95
|
function chatHistoryFilePath(projectRoot, options = {}) {
|
|
98
|
-
const { getUfooPaths } = require("../../
|
|
96
|
+
const { getUfooPaths } = require("../../coordination/state/paths");
|
|
99
97
|
const { globalMode } = options || {};
|
|
100
98
|
if (globalMode) {
|
|
101
99
|
const os = require("os");
|
|
@@ -109,13 +107,48 @@ function chatHistoryFilePath(projectRoot, options = {}) {
|
|
|
109
107
|
|
|
110
108
|
function projectRootToId(projectRoot) {
|
|
111
109
|
try {
|
|
112
|
-
const { buildProjectId } = require("../../projects");
|
|
110
|
+
const { buildProjectId } = require("../../runtime/projects");
|
|
113
111
|
return buildProjectId(projectRoot || process.cwd());
|
|
114
112
|
} catch {
|
|
115
113
|
return crypto.createHash("sha256").update(String(projectRoot || "")).digest("hex").slice(0, 16);
|
|
116
114
|
}
|
|
117
115
|
}
|
|
118
116
|
|
|
117
|
+
function resolveInjectSockPathForAgent(projectRoot, agentId) {
|
|
118
|
+
const { getUfooPaths } = require("../../coordination/state/paths");
|
|
119
|
+
const { subscriberToSafeName } = require("../../coordination/bus/utils");
|
|
120
|
+
const safeName = subscriberToSafeName(agentId);
|
|
121
|
+
return path.join(getUfooPaths(projectRoot || process.cwd()).busQueuesDir, safeName, "inject.sock");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function createInkMultiWindowToggle({
|
|
125
|
+
getController = () => null,
|
|
126
|
+
setActive = () => {},
|
|
127
|
+
logMessage = () => {},
|
|
128
|
+
} = {}) {
|
|
129
|
+
return () => {
|
|
130
|
+
const controller = typeof getController === "function" ? getController() : null;
|
|
131
|
+
if (!controller || typeof controller.enter !== "function" || typeof controller.exit !== "function") {
|
|
132
|
+
logMessage("error", "✗ Multi-window mode is not available");
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (typeof controller.isActive === "function" && controller.isActive()) {
|
|
137
|
+
controller.exit();
|
|
138
|
+
setActive(false);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
setActive(true);
|
|
143
|
+
if (!controller.enter()) {
|
|
144
|
+
setActive(false);
|
|
145
|
+
logMessage("info", "No active agents for multi-window mode");
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
119
152
|
function loadChatHistory(projectRoot, cap = 200, options = {}) {
|
|
120
153
|
const file = chatHistoryFilePath(projectRoot, options);
|
|
121
154
|
try {
|
|
@@ -205,6 +238,11 @@ function chatHistoryOptionsForScope({ globalMode = false, globalScope = "control
|
|
|
205
238
|
}
|
|
206
239
|
|
|
207
240
|
function getAgentLabelFor(meta, agentId) {
|
|
241
|
+
// Prefer the project-stripped display nickname so the dashboard never shows
|
|
242
|
+
// the scoped form ("neptune-builder"); fall back to the raw nickname (which
|
|
243
|
+
// may itself be unscoped depending on write path) and finally to a short
|
|
244
|
+
// form of the subscriber id.
|
|
245
|
+
if (meta && meta.display_nickname) return meta.display_nickname;
|
|
208
246
|
if (meta && meta.nickname) return meta.nickname;
|
|
209
247
|
if (!agentId) return "";
|
|
210
248
|
const colon = agentId.indexOf(":");
|
|
@@ -224,7 +262,7 @@ function buildActiveAgentLabelMap(activeAgents = [], activeAgentMeta = new Map()
|
|
|
224
262
|
}
|
|
225
263
|
|
|
226
264
|
function resolveActiveAgentId(label, activeAgents = [], activeAgentMeta = new Map()) {
|
|
227
|
-
const { resolveAgentId } = require("../../chat/agentDirectory");
|
|
265
|
+
const { resolveAgentId } = require("../../app/chat/agentDirectory");
|
|
228
266
|
const metaMap = activeAgentMeta instanceof Map ? activeAgentMeta : new Map();
|
|
229
267
|
return resolveAgentId({
|
|
230
268
|
label,
|
|
@@ -258,7 +296,7 @@ function buildDirectBusSendRequest({
|
|
|
258
296
|
};
|
|
259
297
|
}
|
|
260
298
|
|
|
261
|
-
const { parseAtTarget } = require("../../chat/commands");
|
|
299
|
+
const { parseAtTarget } = require("../../app/chat/commands");
|
|
262
300
|
const atTarget = parseAtTarget(trimmed);
|
|
263
301
|
if (!atTarget || !atTarget.message) return null;
|
|
264
302
|
const target = resolveActiveAgentId(atTarget.target, activeAgents, activeAgentMeta) || atTarget.target;
|
|
@@ -284,7 +322,7 @@ function resolveAgentEnterRequest({
|
|
|
284
322
|
? settings.launchMode
|
|
285
323
|
: "";
|
|
286
324
|
const launchMode = String(meta.launch_mode || meta.launchMode || configuredLaunchMode || "").trim();
|
|
287
|
-
const { createTerminalAdapterRouter } = require("../../terminal/adapterRouter");
|
|
325
|
+
const { createTerminalAdapterRouter } = require("../../runtime/terminal/adapterRouter");
|
|
288
326
|
const adapter = createTerminalAdapterRouter().getAdapter({ launchMode, agentId: id, meta });
|
|
289
327
|
const caps = adapter && adapter.capabilities ? adapter.capabilities : {};
|
|
290
328
|
|
|
@@ -299,8 +337,26 @@ function resolveAgentEnterRequest({
|
|
|
299
337
|
};
|
|
300
338
|
}
|
|
301
339
|
|
|
340
|
+
function resolveDashboardAgentEnterAction(enterRequest = {}) {
|
|
341
|
+
if (!enterRequest || typeof enterRequest !== "object") return "none";
|
|
342
|
+
if (enterRequest.useBus) return "internal";
|
|
343
|
+
if (enterRequest.supportsActivate) return "activate";
|
|
344
|
+
return "agent-view";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function buildEmptyProjectsDownActions(state = {}, displayAgents = []) {
|
|
348
|
+
if (!state.emptyProjectsDownArmed) {
|
|
349
|
+
return [{ type: "projects/armEmptyDown" }];
|
|
350
|
+
}
|
|
351
|
+
const actions = [{ type: "view/set", view: "agents" }];
|
|
352
|
+
if (displayAgents.length > 0 && state.selectedAgentIndex < 0) {
|
|
353
|
+
actions.push({ type: "agents/select", index: 0 });
|
|
354
|
+
}
|
|
355
|
+
return actions;
|
|
356
|
+
}
|
|
357
|
+
|
|
302
358
|
function buildPromptIpcRequest(text) {
|
|
303
|
-
const { IPC_REQUEST_TYPES } = require("../../
|
|
359
|
+
const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
|
|
304
360
|
return {
|
|
305
361
|
type: IPC_REQUEST_TYPES.PROMPT,
|
|
306
362
|
text,
|
|
@@ -323,6 +379,46 @@ function normalizeInkLogLines(text = "") {
|
|
|
323
379
|
return clean.split(/\r?\n/);
|
|
324
380
|
}
|
|
325
381
|
|
|
382
|
+
function stripMarkdownDecorators(text = "") {
|
|
383
|
+
return String(text || "")
|
|
384
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
385
|
+
.replace(/`([^`]+)`/g, "$1");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function classifyChatLogLine(text = "") {
|
|
389
|
+
const raw = stripBlessedTags(text).replace(/\r/g, "");
|
|
390
|
+
const clean = stripMarkdownDecorators(raw);
|
|
391
|
+
const trimmed = clean.trim();
|
|
392
|
+
if (!trimmed) return { kind: "spacer", marker: " ", speaker: "", body: " " };
|
|
393
|
+
if (/^[█▀▄ ]+$/.test(trimmed) || /^ufoo chat/i.test(trimmed)) {
|
|
394
|
+
return { kind: "banner", marker: " ", speaker: "", body: clean };
|
|
395
|
+
}
|
|
396
|
+
if (/^───.*───$/.test(trimmed)) {
|
|
397
|
+
return { kind: "divider", marker: "─", speaker: "", body: clean };
|
|
398
|
+
}
|
|
399
|
+
if (/^(error:|✗|failed\b)/i.test(trimmed)) {
|
|
400
|
+
return { kind: "error", marker: "!", speaker: "error", body: clean.replace(/^(error:\s*)/i, "") };
|
|
401
|
+
}
|
|
402
|
+
if (/^(✓|✔|done\b|closed\b)/i.test(trimmed)) {
|
|
403
|
+
return { kind: "success", marker: "✓", speaker: "", body: clean.replace(/^[✓✔]\s*/, "") };
|
|
404
|
+
}
|
|
405
|
+
const dotMatch = clean.match(/^([^·:\n]{1,42})\s+·\s+(.*)$/);
|
|
406
|
+
if (dotMatch) {
|
|
407
|
+
const speaker = dotMatch[1].trim();
|
|
408
|
+
const lower = speaker.toLowerCase();
|
|
409
|
+
const kind = lower === "ufoo" ? "assistant" : "agent";
|
|
410
|
+
return { kind, marker: kind === "assistant" ? "◆" : "●", speaker, body: dotMatch[2] || " " };
|
|
411
|
+
}
|
|
412
|
+
const colonMatch = clean.match(/^([A-Za-z0-9_.:@/-]{1,42}):\s+(.*)$/);
|
|
413
|
+
if (colonMatch) {
|
|
414
|
+
return { kind: "agent", marker: "●", speaker: colonMatch[1], body: colonMatch[2] || " " };
|
|
415
|
+
}
|
|
416
|
+
if (/^(CHAT|UCODE)\s+·/i.test(trimmed)) {
|
|
417
|
+
return { kind: "meta", marker: "·", speaker: "", body: clean };
|
|
418
|
+
}
|
|
419
|
+
return { kind: "plain", marker: "│", speaker: "", body: clean };
|
|
420
|
+
}
|
|
421
|
+
|
|
326
422
|
function createInkStreamState({
|
|
327
423
|
dispatch,
|
|
328
424
|
appendHistory,
|
|
@@ -661,7 +757,7 @@ function createInternalAgentViewState({
|
|
|
661
757
|
} = {}) {
|
|
662
758
|
let history = [];
|
|
663
759
|
try {
|
|
664
|
-
const { loadInternalAgentLogHistory } = require("../../chat/internalAgentLogHistory");
|
|
760
|
+
const { loadInternalAgentLogHistory } = require("../../app/chat/internalAgentLogHistory");
|
|
665
761
|
history = loadInternalAgentLogHistory(projectRoot || process.cwd(), agentId, {
|
|
666
762
|
maxEvents: 400,
|
|
667
763
|
maxLines: 1000,
|
|
@@ -838,7 +934,7 @@ function resolveProjectRowRoot(row = {}) {
|
|
|
838
934
|
const raw = String((row && (row.root || row.project_root)) || "").trim();
|
|
839
935
|
if (!raw) return "";
|
|
840
936
|
try {
|
|
841
|
-
const { canonicalProjectRoot } = require("../../projects");
|
|
937
|
+
const { canonicalProjectRoot } = require("../../runtime/projects");
|
|
842
938
|
return canonicalProjectRoot(raw);
|
|
843
939
|
} catch {
|
|
844
940
|
return path.resolve(raw);
|
|
@@ -851,7 +947,7 @@ function loadGlobalProjectRows(activeProjectRoot = "") {
|
|
|
851
947
|
filterVisibleProjectRuntimes,
|
|
852
948
|
isGlobalControllerProjectRoot,
|
|
853
949
|
markProjectStopped,
|
|
854
|
-
} = require("../../projects");
|
|
950
|
+
} = require("../../runtime/projects");
|
|
855
951
|
let rows = listProjectRuntimes({ validate: true, cleanupTmp: true }) || [];
|
|
856
952
|
for (const row of rows) {
|
|
857
953
|
const status = String((row && row.status) || "").trim().toLowerCase();
|
|
@@ -874,8 +970,8 @@ function loadGlobalProjectRows(activeProjectRoot = "") {
|
|
|
874
970
|
function readProjectAgentSnapshot(projectRoot = "") {
|
|
875
971
|
if (!projectRoot) return { agents: [], metaMap: new Map() };
|
|
876
972
|
try {
|
|
877
|
-
const { buildStatus } = require("../../daemon/status");
|
|
878
|
-
const { buildAgentMaps } = require("../../chat/agentDirectory");
|
|
973
|
+
const { buildStatus } = require("../../runtime/daemon/status");
|
|
974
|
+
const { buildAgentMaps } = require("../../app/chat/agentDirectory");
|
|
879
975
|
const status = buildStatus(projectRoot);
|
|
880
976
|
const activeIds = Array.isArray(status.active) ? status.active : [];
|
|
881
977
|
const metaList = Array.isArray(status.active_meta) ? status.active_meta : [];
|
|
@@ -900,6 +996,49 @@ function readProjectAgentSnapshot(projectRoot = "") {
|
|
|
900
996
|
}
|
|
901
997
|
}
|
|
902
998
|
|
|
999
|
+
function isCJK(ch) {
|
|
1000
|
+
if (!ch) return false;
|
|
1001
|
+
const code = ch.codePointAt(0);
|
|
1002
|
+
return (code >= 0x2e80 && code <= 0x9fff) ||
|
|
1003
|
+
(code >= 0xac00 && code <= 0xd7af) ||
|
|
1004
|
+
(code >= 0xf900 && code <= 0xfaff) ||
|
|
1005
|
+
(code >= 0xfe30 && code <= 0xfe4f) ||
|
|
1006
|
+
(code >= 0x20000 && code <= 0x2fa1f);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
function inferStatusType(text = "", requestedType = "") {
|
|
1010
|
+
const type = String(requestedType || "").trim().toLowerCase();
|
|
1011
|
+
if (type === "done" || type === "success" || type === "error" || type === "idle") return type;
|
|
1012
|
+
const clean = stripBlessedTags(String(text || "")).trim();
|
|
1013
|
+
if (/^[✓✔]/.test(clean) || /\bdone\b/i.test(clean) || /\bprocessed\b/i.test(clean)) return "done";
|
|
1014
|
+
if (/^[✗!]/.test(clean) || /\berror\b/i.test(clean) || /\bfailed\b/i.test(clean)) return "error";
|
|
1015
|
+
return type || "typing";
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
function isAnimatedStatusType(type = "") {
|
|
1019
|
+
const value = String(type || "").trim().toLowerCase();
|
|
1020
|
+
return value !== "done" && value !== "success" && value !== "error" && value !== "idle" && value !== "none";
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function inkKeyToRaw(input, key) {
|
|
1024
|
+
if (key.ctrl && input) {
|
|
1025
|
+
const code = input.charCodeAt(0) - 96;
|
|
1026
|
+
if (code >= 1 && code <= 26) return String.fromCharCode(code);
|
|
1027
|
+
return "";
|
|
1028
|
+
}
|
|
1029
|
+
if (key.return) return "\r";
|
|
1030
|
+
if (key.escape) return "\x1b";
|
|
1031
|
+
if (key.backspace || key.delete) return "\x7f";
|
|
1032
|
+
if (key.tab) return "\t";
|
|
1033
|
+
if (key.upArrow) return "\x1b[A";
|
|
1034
|
+
if (key.downArrow) return "\x1b[B";
|
|
1035
|
+
if (key.rightArrow) return "\x1b[C";
|
|
1036
|
+
if (key.leftArrow) return "\x1b[D";
|
|
1037
|
+
if (input && !key.meta) return input;
|
|
1038
|
+
if (key.meta && input) return `\x1b${input}`;
|
|
1039
|
+
return "";
|
|
1040
|
+
}
|
|
1041
|
+
|
|
903
1042
|
function createChatApp({ React, ink, props, interactive = true }) {
|
|
904
1043
|
const { useReducer, useEffect, useState, useCallback, useRef } = React;
|
|
905
1044
|
const { Box, Text, Static, useInput, useApp, useStdout } = ink;
|
|
@@ -933,9 +1072,18 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
933
1072
|
const [spinnerTick, setSpinnerTick] = useState(0);
|
|
934
1073
|
const [currentProjectRoot, setCurrentProjectRoot] = useState(props.activeProjectRoot || props.projectRoot || "");
|
|
935
1074
|
const [internalAgentView, setInternalAgentView] = useState(() => createInternalAgentViewState());
|
|
1075
|
+
const [multiWindowActive, setMultiWindowActive] = useState(false);
|
|
1076
|
+
const [mwCursor, setMwCursor] = useState(0);
|
|
1077
|
+
const [mwTerminalFocused, setMwTerminalFocused] = useState(false);
|
|
1078
|
+
const mwTerminalFocusedRef = useRef(false);
|
|
1079
|
+
const mwLastInputRef = useRef({ char: "", time: 0 });
|
|
936
1080
|
const stateRef = useRef(state);
|
|
1081
|
+
const sizeRef = useRef(size);
|
|
937
1082
|
const currentProjectRootRef = useRef(currentProjectRoot);
|
|
938
1083
|
const internalAgentViewRef = useRef(internalAgentView);
|
|
1084
|
+
const multiWindowControllerRef = useRef(null);
|
|
1085
|
+
const multiWindowChromeRef = useRef({ statusText: "", promptPrefix: "› ", draft: "", dashboardLines: [] });
|
|
1086
|
+
const multiWindowWatchedInternalAgentsRef = useRef(new Set());
|
|
939
1087
|
const pendingRef = useRef(null);
|
|
940
1088
|
const streamStateRef = useRef(null);
|
|
941
1089
|
const historyScopeRef = useRef(null);
|
|
@@ -952,6 +1100,10 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
952
1100
|
stateRef.current = state;
|
|
953
1101
|
}, [state]);
|
|
954
1102
|
|
|
1103
|
+
useEffect(() => {
|
|
1104
|
+
sizeRef.current = size;
|
|
1105
|
+
}, [size]);
|
|
1106
|
+
|
|
955
1107
|
useEffect(() => {
|
|
956
1108
|
currentProjectRootRef.current = currentProjectRoot;
|
|
957
1109
|
}, [currentProjectRoot]);
|
|
@@ -975,7 +1127,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
975
1127
|
type: "status/set",
|
|
976
1128
|
payload: {
|
|
977
1129
|
message: clean,
|
|
978
|
-
type: options.type || "typing",
|
|
1130
|
+
type: inferStatusType(clean, options.type || "typing"),
|
|
979
1131
|
showTimer: options.showTimer === true,
|
|
980
1132
|
startedAt: options.startedAt || Date.now(),
|
|
981
1133
|
},
|
|
@@ -1009,6 +1161,151 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1009
1161
|
});
|
|
1010
1162
|
}
|
|
1011
1163
|
|
|
1164
|
+
const getMultiWindowController = useCallback(() => {
|
|
1165
|
+
if (multiWindowControllerRef.current) return multiWindowControllerRef.current;
|
|
1166
|
+
const processStdout = stdout || (typeof process !== "undefined" ? process.stdout : null);
|
|
1167
|
+
if (!processStdout || typeof processStdout.write !== "function") return null;
|
|
1168
|
+
|
|
1169
|
+
const originalWrite = processStdout.write.bind(processStdout);
|
|
1170
|
+
const { createMultiWindowController } = require("../../app/chat/multiWindow");
|
|
1171
|
+
multiWindowControllerRef.current = createMultiWindowController({
|
|
1172
|
+
processStdout: { write: originalWrite, rows: processStdout.rows, columns: processStdout.columns },
|
|
1173
|
+
getRows: () => {
|
|
1174
|
+
const currentSize = sizeRef.current || {};
|
|
1175
|
+
return currentSize.rows || processStdout.rows || 24;
|
|
1176
|
+
},
|
|
1177
|
+
getCols: () => {
|
|
1178
|
+
const currentSize = sizeRef.current || {};
|
|
1179
|
+
return currentSize.cols || processStdout.columns || 80;
|
|
1180
|
+
},
|
|
1181
|
+
getInjectSockPath: (agentId) =>
|
|
1182
|
+
resolveInjectSockPathForAgent(currentProjectRootRef.current || props.projectRoot, agentId),
|
|
1183
|
+
getActiveAgents: () => {
|
|
1184
|
+
const current = stateRef.current || {};
|
|
1185
|
+
return Array.isArray(current.agents) ? current.agents : [];
|
|
1186
|
+
},
|
|
1187
|
+
getAgentPaneOptions: (agentId) => {
|
|
1188
|
+
const current = stateRef.current || {};
|
|
1189
|
+
const enterRequest = resolveAgentEnterRequest({
|
|
1190
|
+
agentId,
|
|
1191
|
+
projectRoot: currentProjectRootRef.current || props.projectRoot,
|
|
1192
|
+
activeAgentMeta: current.activeAgentMeta,
|
|
1193
|
+
settings: current.settings,
|
|
1194
|
+
});
|
|
1195
|
+
if (!enterRequest || !enterRequest.useBus) return { mode: "socket" };
|
|
1196
|
+
const metaMap = current.activeAgentMeta instanceof Map ? current.activeAgentMeta : new Map();
|
|
1197
|
+
const agentMeta = metaMap.get(agentId) || {};
|
|
1198
|
+
let initialLines = [];
|
|
1199
|
+
try {
|
|
1200
|
+
const { loadInternalAgentLogHistory } = require("../../app/chat/internalAgentLogHistory");
|
|
1201
|
+
initialLines = loadInternalAgentLogHistory(currentProjectRootRef.current || props.projectRoot, agentId, {
|
|
1202
|
+
maxEvents: 200,
|
|
1203
|
+
maxLines: 200,
|
|
1204
|
+
});
|
|
1205
|
+
} catch { initialLines = []; }
|
|
1206
|
+
return {
|
|
1207
|
+
mode: "internal",
|
|
1208
|
+
initialLines: [
|
|
1209
|
+
`ufoo internal agent · ${getAgentLabelFor(agentMeta, agentId)}`,
|
|
1210
|
+
`agent: ${agentId}`,
|
|
1211
|
+
"",
|
|
1212
|
+
...initialLines,
|
|
1213
|
+
],
|
|
1214
|
+
};
|
|
1215
|
+
},
|
|
1216
|
+
getChatLogLines: () => {
|
|
1217
|
+
const current = stateRef.current || {};
|
|
1218
|
+
return Array.isArray(current.logLines)
|
|
1219
|
+
? current.logLines.map((item) => String((item && item.text) || ""))
|
|
1220
|
+
: [];
|
|
1221
|
+
},
|
|
1222
|
+
getStatusText: () => {
|
|
1223
|
+
const chrome = multiWindowChromeRef.current;
|
|
1224
|
+
return chrome ? chrome.statusText : "";
|
|
1225
|
+
},
|
|
1226
|
+
getPromptPrefix: () => {
|
|
1227
|
+
const chrome = multiWindowChromeRef.current;
|
|
1228
|
+
return chrome ? chrome.promptPrefix : "› ";
|
|
1229
|
+
},
|
|
1230
|
+
getCurrentDraft: () => {
|
|
1231
|
+
const chrome = multiWindowChromeRef.current;
|
|
1232
|
+
return chrome ? chrome.draft : "";
|
|
1233
|
+
},
|
|
1234
|
+
getCursorPos: () => {
|
|
1235
|
+
const chrome = multiWindowChromeRef.current;
|
|
1236
|
+
return chrome ? chrome.cursor : 0;
|
|
1237
|
+
},
|
|
1238
|
+
getCompletions: () => {
|
|
1239
|
+
const chrome = multiWindowChromeRef.current;
|
|
1240
|
+
if (!chrome || !chrome.completions || chrome.completions.length === 0) {
|
|
1241
|
+
return { items: [], index: -1, windowStart: 0, pageSize: 8 };
|
|
1242
|
+
}
|
|
1243
|
+
return {
|
|
1244
|
+
items: chrome.completions,
|
|
1245
|
+
index: chrome.completionIndex,
|
|
1246
|
+
windowStart: chrome.completionWindowStart,
|
|
1247
|
+
pageSize: chrome.completionPageSize || 8,
|
|
1248
|
+
};
|
|
1249
|
+
},
|
|
1250
|
+
getAgentLabel: (id) => {
|
|
1251
|
+
const current = stateRef.current || {};
|
|
1252
|
+
const metaMap = current.activeAgentMeta || new Map();
|
|
1253
|
+
return getAgentLabelFor(metaMap.get(id), id);
|
|
1254
|
+
},
|
|
1255
|
+
getInternalPaneInfo: (id) => {
|
|
1256
|
+
const current = stateRef.current || {};
|
|
1257
|
+
const metaMap = current.activeAgentMeta instanceof Map ? current.activeAgentMeta : new Map();
|
|
1258
|
+
const meta = metaMap.get(id) || {};
|
|
1259
|
+
const status = internalStatusLabel(meta.activity_state || meta.state || "");
|
|
1260
|
+
const detail = String(meta.activity_detail || meta.detail || meta.status_text || "").trim();
|
|
1261
|
+
return {
|
|
1262
|
+
status,
|
|
1263
|
+
detail,
|
|
1264
|
+
input: "",
|
|
1265
|
+
cursor: 0,
|
|
1266
|
+
};
|
|
1267
|
+
},
|
|
1268
|
+
getDashboardLines: () => {
|
|
1269
|
+
const chrome = multiWindowChromeRef.current;
|
|
1270
|
+
return chrome ? chrome.dashboardLines : [];
|
|
1271
|
+
},
|
|
1272
|
+
getTerminalFocused: () => mwTerminalFocusedRef.current,
|
|
1273
|
+
freezeScreen: (frozen) => {
|
|
1274
|
+
if (frozen) {
|
|
1275
|
+
processStdout.write = () => true;
|
|
1276
|
+
} else {
|
|
1277
|
+
processStdout.write = originalWrite;
|
|
1278
|
+
}
|
|
1279
|
+
},
|
|
1280
|
+
restoreTerminal: () => {
|
|
1281
|
+
const rows = processStdout.rows || 24;
|
|
1282
|
+
originalWrite(`\x1b[1;${rows}r`);
|
|
1283
|
+
originalWrite("\x1b[2J\x1b[H");
|
|
1284
|
+
},
|
|
1285
|
+
onInternalSubmit: (agentId, message) => {
|
|
1286
|
+
sendInternalAgentMessage(agentId, message);
|
|
1287
|
+
},
|
|
1288
|
+
onExit: () => {
|
|
1289
|
+
setMultiWindowActive(false);
|
|
1290
|
+
},
|
|
1291
|
+
});
|
|
1292
|
+
return multiWindowControllerRef.current;
|
|
1293
|
+
}, [props.projectRoot, stdout]);
|
|
1294
|
+
|
|
1295
|
+
const toggleMultiWindow = useCallback(() => createInkMultiWindowToggle({
|
|
1296
|
+
getController: getMultiWindowController,
|
|
1297
|
+
setActive: setMultiWindowActive,
|
|
1298
|
+
logMessage: logInkMessage,
|
|
1299
|
+
})(), [getMultiWindowController, logInkMessage]);
|
|
1300
|
+
|
|
1301
|
+
useEffect(() => () => {
|
|
1302
|
+
const controller = multiWindowControllerRef.current;
|
|
1303
|
+
if (controller && typeof controller.exit === "function") {
|
|
1304
|
+
try { controller.exit(); } catch { /* ignore */ }
|
|
1305
|
+
}
|
|
1306
|
+
multiWindowControllerRef.current = null;
|
|
1307
|
+
}, []);
|
|
1308
|
+
|
|
1012
1309
|
useEffect(() => {
|
|
1013
1310
|
internalAgentViewRef.current = internalAgentView;
|
|
1014
1311
|
}, [internalAgentView]);
|
|
@@ -1034,7 +1331,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1034
1331
|
const sendInternalAgentWatch = (agentId, enabled) => {
|
|
1035
1332
|
if (!agentId || !props.daemonConnection || typeof props.daemonConnection.send !== "function") return;
|
|
1036
1333
|
try {
|
|
1037
|
-
const { IPC_REQUEST_TYPES } = require("../../
|
|
1334
|
+
const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
|
|
1038
1335
|
props.daemonConnection.send({
|
|
1039
1336
|
type: IPC_REQUEST_TYPES.BUS_WATCH,
|
|
1040
1337
|
agent_id: agentId,
|
|
@@ -1043,10 +1340,49 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1043
1340
|
} catch { /* ignore */ }
|
|
1044
1341
|
};
|
|
1045
1342
|
|
|
1343
|
+
const reconcileMultiWindowInternalWatches = useCallback(() => {
|
|
1344
|
+
const current = stateRef.current || {};
|
|
1345
|
+
const agents = Array.isArray(current.agents) ? current.agents : [];
|
|
1346
|
+
const next = new Set();
|
|
1347
|
+
if (multiWindowActive) {
|
|
1348
|
+
for (const agentId of agents) {
|
|
1349
|
+
const enterRequest = resolveAgentEnterRequest({
|
|
1350
|
+
agentId,
|
|
1351
|
+
projectRoot: currentProjectRootRef.current || props.projectRoot,
|
|
1352
|
+
activeAgentMeta: current.activeAgentMeta,
|
|
1353
|
+
settings: current.settings,
|
|
1354
|
+
});
|
|
1355
|
+
if (enterRequest && enterRequest.useBus) next.add(agentId);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
const previous = multiWindowWatchedInternalAgentsRef.current;
|
|
1359
|
+
for (const agentId of next) {
|
|
1360
|
+
if (!previous.has(agentId)) sendInternalAgentWatch(agentId, true);
|
|
1361
|
+
}
|
|
1362
|
+
for (const agentId of previous) {
|
|
1363
|
+
if (!next.has(agentId)) sendInternalAgentWatch(agentId, false);
|
|
1364
|
+
}
|
|
1365
|
+
multiWindowWatchedInternalAgentsRef.current = next;
|
|
1366
|
+
}, [multiWindowActive, props.projectRoot, props.daemonConnection]);
|
|
1367
|
+
|
|
1368
|
+
useEffect(() => {
|
|
1369
|
+
if (!multiWindowActive) return;
|
|
1370
|
+
const controller = multiWindowControllerRef.current;
|
|
1371
|
+
if (!controller) return;
|
|
1372
|
+
reconcileMultiWindowInternalWatches();
|
|
1373
|
+
if (typeof controller.syncAgents === "function") controller.syncAgents();
|
|
1374
|
+
if (typeof controller.renderAll === "function") controller.renderAll();
|
|
1375
|
+
}, [multiWindowActive, state.agents, state.logLines, state.draft, state.status, size.cols, size.rows, mwCursor, state.focusMode, state.dashboardView, state.selectedAgentIndex, state.selectedProjectIndex, state.selectedModeIndex, state.selectedProviderIndex, state.selectedCronIndex, mwTerminalFocused, reconcileMultiWindowInternalWatches]);
|
|
1376
|
+
|
|
1377
|
+
useEffect(() => {
|
|
1378
|
+
if (multiWindowActive) return;
|
|
1379
|
+
reconcileMultiWindowInternalWatches();
|
|
1380
|
+
}, [multiWindowActive, reconcileMultiWindowInternalWatches]);
|
|
1381
|
+
|
|
1046
1382
|
const sendInternalAgentMessage = (agentId, message) => {
|
|
1047
1383
|
if (!agentId || !message || !props.daemonConnection || typeof props.daemonConnection.send !== "function") return;
|
|
1048
1384
|
try {
|
|
1049
|
-
const { IPC_REQUEST_TYPES } = require("../../
|
|
1385
|
+
const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
|
|
1050
1386
|
props.daemonConnection.send({
|
|
1051
1387
|
type: IPC_REQUEST_TYPES.BUS_SEND,
|
|
1052
1388
|
target: agentId,
|
|
@@ -1070,6 +1406,74 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1070
1406
|
return aliases.has(text);
|
|
1071
1407
|
};
|
|
1072
1408
|
|
|
1409
|
+
const buildInternalAgentAliases = (agentId) => {
|
|
1410
|
+
const current = stateRef.current || {};
|
|
1411
|
+
const metaMap = current.activeAgentMeta instanceof Map ? current.activeAgentMeta : new Map();
|
|
1412
|
+
const meta = metaMap.get(agentId) || {};
|
|
1413
|
+
return new Set([
|
|
1414
|
+
agentId,
|
|
1415
|
+
meta.nickname,
|
|
1416
|
+
meta.scoped_nickname,
|
|
1417
|
+
meta.display_nickname,
|
|
1418
|
+
meta.fullId,
|
|
1419
|
+
].filter(Boolean).map(String));
|
|
1420
|
+
};
|
|
1421
|
+
|
|
1422
|
+
const writeMultiWindowInternalEvent = useCallback((data = {}) => {
|
|
1423
|
+
const controller = multiWindowControllerRef.current;
|
|
1424
|
+
if (!multiWindowActive || !controller || typeof controller.writeToPane !== "function") return false;
|
|
1425
|
+
const watched = multiWindowWatchedInternalAgentsRef.current;
|
|
1426
|
+
if (!watched || watched.size === 0) return false;
|
|
1427
|
+
|
|
1428
|
+
let handled = false;
|
|
1429
|
+
for (const agentId of watched) {
|
|
1430
|
+
const aliases = buildInternalAgentAliases(agentId);
|
|
1431
|
+
const publisher = String(data.publisher || (data.event === "broadcast" ? "broadcast" : "bus"));
|
|
1432
|
+
const target = String(data.target || data.subscriber || "");
|
|
1433
|
+
const fromAgent = aliases.has(publisher);
|
|
1434
|
+
const toAgent = aliases.has(target) || aliases.has(String(data.subscriber || ""));
|
|
1435
|
+
if (!fromAgent && !toAgent) continue;
|
|
1436
|
+
if (data.silent) {
|
|
1437
|
+
handled = true;
|
|
1438
|
+
continue;
|
|
1439
|
+
}
|
|
1440
|
+
if (data.source === "chat-internal-agent-view" && toAgent && !fromAgent) {
|
|
1441
|
+
handled = true;
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1444
|
+
if (data.event === "activity_state_changed") {
|
|
1445
|
+
const state = internalStatusLabel(data.state || data.activity_state || "");
|
|
1446
|
+
const detail = String(data.detail || (data.data && data.data.detail) || data.message || "").trim();
|
|
1447
|
+
controller.writeToPane(agentId, `\r\n[${state}${detail ? ` · ${detail}` : ""}]\r\n`);
|
|
1448
|
+
handled = true;
|
|
1449
|
+
continue;
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
const { displayMessage, streamPayload } = parseInternalBusPayload(data.message || "");
|
|
1453
|
+
if (streamPayload) {
|
|
1454
|
+
if (!fromAgent) {
|
|
1455
|
+
handled = true;
|
|
1456
|
+
continue;
|
|
1457
|
+
}
|
|
1458
|
+
const delta = typeof streamPayload.delta === "string"
|
|
1459
|
+
? streamPayload.delta.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\r/g, "\n")
|
|
1460
|
+
: "";
|
|
1461
|
+
if (delta) controller.writeToPane(agentId, delta);
|
|
1462
|
+
if (streamPayload.done) controller.writeToPane(agentId, "\r\n");
|
|
1463
|
+
handled = true;
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
if (!displayMessage) {
|
|
1467
|
+
handled = true;
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
const prefix = fromAgent ? "* " : "> ";
|
|
1471
|
+
controller.writeToPane(agentId, `${prefix}${displayMessage.replace(/\n/g, `\r\n `)}\r\n`);
|
|
1472
|
+
handled = true;
|
|
1473
|
+
}
|
|
1474
|
+
return handled;
|
|
1475
|
+
}, [multiWindowActive]);
|
|
1476
|
+
|
|
1073
1477
|
const handleInternalStatus = (data = {}) => {
|
|
1074
1478
|
const view = internalAgentViewRef.current;
|
|
1075
1479
|
if (!view || !view.agentId) return;
|
|
@@ -1168,7 +1572,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1168
1572
|
|
|
1169
1573
|
const requestDaemonStatus = useCallback(() => {
|
|
1170
1574
|
try {
|
|
1171
|
-
const { IPC_REQUEST_TYPES } = require("../../
|
|
1575
|
+
const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
|
|
1172
1576
|
const conn = props.daemonConnection;
|
|
1173
1577
|
if (conn && typeof conn.send === "function") conn.send({ type: IPC_REQUEST_TYPES.STATUS });
|
|
1174
1578
|
} catch { /* ignore */ }
|
|
@@ -1177,7 +1581,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1177
1581
|
const updateDashboardFromStatus = useCallback((data = {}) => {
|
|
1178
1582
|
const activeIds = Array.isArray(data.active) ? data.active : [];
|
|
1179
1583
|
const metaList = Array.isArray(data.active_meta) ? data.active_meta : [];
|
|
1180
|
-
const { buildAgentMaps } = require("../../chat/agentDirectory");
|
|
1584
|
+
const { buildAgentMaps } = require("../../app/chat/agentDirectory");
|
|
1181
1585
|
const { labelMap, metaMap } = buildAgentMaps(activeIds, metaList);
|
|
1182
1586
|
const agentsForDispatch = activeIds.map((id) => {
|
|
1183
1587
|
const meta = metaMap.get(id) || {};
|
|
@@ -1210,8 +1614,8 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1210
1614
|
if (!conn || typeof conn.connect !== "function" || typeof setHandler !== "function") {
|
|
1211
1615
|
return undefined;
|
|
1212
1616
|
}
|
|
1213
|
-
const { IPC_RESPONSE_TYPES } = require("../../
|
|
1214
|
-
const { createDaemonMessageRouter } = require("../../chat/daemonMessageRouter");
|
|
1617
|
+
const { IPC_RESPONSE_TYPES } = require("../../runtime/contracts/eventContract");
|
|
1618
|
+
const { createDaemonMessageRouter } = require("../../app/chat/daemonMessageRouter");
|
|
1215
1619
|
const streamState = streamStateRef.current;
|
|
1216
1620
|
const router = createDaemonMessageRouter({
|
|
1217
1621
|
escapeBlessed: (value) => String(value == null ? "" : value),
|
|
@@ -1227,7 +1631,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1227
1631
|
});
|
|
1228
1632
|
},
|
|
1229
1633
|
enqueueBusStatus: (item = {}) => setStatusText(item.text || "Processing bus message", { type: "typing" }),
|
|
1230
|
-
resolveBusStatus: (item = {}) => setStatusText(item.text || "Bus message processed", { type: "
|
|
1634
|
+
resolveBusStatus: (item = {}) => setStatusText(item.text || "Bus message processed", { type: "done" }),
|
|
1231
1635
|
getPending: () => pendingRef.current,
|
|
1232
1636
|
setPending: (value) => { pendingRef.current = value || null; },
|
|
1233
1637
|
resolveAgentDisplayName: (value) => {
|
|
@@ -1306,13 +1710,16 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1306
1710
|
requestDaemonStatus();
|
|
1307
1711
|
return;
|
|
1308
1712
|
}
|
|
1713
|
+
if (msg.type === IPC_RESPONSE_TYPES.BUS) {
|
|
1714
|
+
writeMultiWindowInternalEvent(msg.data || {});
|
|
1715
|
+
}
|
|
1309
1716
|
router.handleMessage(msg);
|
|
1310
1717
|
});
|
|
1311
1718
|
conn.connect();
|
|
1312
1719
|
return () => {
|
|
1313
1720
|
try { if (typeof conn.close === "function") conn.close(); } catch { /* ignore */ }
|
|
1314
1721
|
};
|
|
1315
|
-
}, [interactive, logInkMessage, requestDaemonStatus, setStatusText, updateDashboardFromStatus]);
|
|
1722
|
+
}, [interactive, logInkMessage, requestDaemonStatus, setStatusText, updateDashboardFromStatus, writeMultiWindowInternalEvent]);
|
|
1316
1723
|
|
|
1317
1724
|
// commandExecutor wiring. The blessed implementation reuses this
|
|
1318
1725
|
// module to dispatch every slash command (~30 callbacks). We adapt
|
|
@@ -1323,10 +1730,10 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1323
1730
|
const commandExecutorRef = useRef(null);
|
|
1324
1731
|
useEffect(() => {
|
|
1325
1732
|
if (!interactive) return undefined;
|
|
1326
|
-
const { createCommandExecutor } = require("../../chat/commandExecutor");
|
|
1327
|
-
const { parseCommand: parseCmd } = require("../../chat/commands");
|
|
1328
|
-
const { startDaemon: transportStartDaemon, stopDaemon: transportStopDaemon } = require("../../chat/transport");
|
|
1329
|
-
const AgentActivator = require("../../bus/activate");
|
|
1733
|
+
const { createCommandExecutor } = require("../../app/chat/commandExecutor");
|
|
1734
|
+
const { parseCommand: parseCmd } = require("../../app/chat/commands");
|
|
1735
|
+
const { startDaemon: transportStartDaemon, stopDaemon: transportStopDaemon } = require("../../app/chat/transport");
|
|
1736
|
+
const AgentActivator = require("../../coordination/bus/activate");
|
|
1330
1737
|
const conn = props.daemonConnection;
|
|
1331
1738
|
|
|
1332
1739
|
try {
|
|
@@ -1338,6 +1745,31 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1338
1745
|
logMessage: logInkMessage,
|
|
1339
1746
|
resolveStatusLine: (text) => setStatusText(text),
|
|
1340
1747
|
renderScreen: () => {},
|
|
1748
|
+
clearLog: () => {
|
|
1749
|
+
// Clear the persisted chat history file so reopening the chat
|
|
1750
|
+
// doesn't reload old messages.
|
|
1751
|
+
try {
|
|
1752
|
+
const root = currentProjectRootRef.current || props.projectRoot;
|
|
1753
|
+
const historyOptions = chatHistoryOptionsForScope({
|
|
1754
|
+
globalMode: props.globalMode,
|
|
1755
|
+
globalScope: (stateRef.current && stateRef.current.globalScope) || "controller",
|
|
1756
|
+
});
|
|
1757
|
+
const file = chatHistoryFilePath(root, historyOptions);
|
|
1758
|
+
if (file && fs.existsSync(file)) fs.writeFileSync(file, "");
|
|
1759
|
+
} catch { /* ignore */ }
|
|
1760
|
+
// ink redraws by erasing only as many lines as the last frame
|
|
1761
|
+
// emitted. After log/clear the next frame is shorter, so the
|
|
1762
|
+
// older log lines remain in the terminal scrollback. Wipe the
|
|
1763
|
+
// visible screen + scrollback first, then dispatch — ink will
|
|
1764
|
+
// repaint the (now small) frame onto a clean buffer.
|
|
1765
|
+
try {
|
|
1766
|
+
const out = (typeof process !== "undefined" && process.stdout) || null;
|
|
1767
|
+
if (out && out.isTTY && typeof out.write === "function") {
|
|
1768
|
+
out.write("\x1b[2J\x1b[3J\x1b[H");
|
|
1769
|
+
}
|
|
1770
|
+
} catch { /* ignore */ }
|
|
1771
|
+
dispatch({ type: "log/clear" });
|
|
1772
|
+
},
|
|
1341
1773
|
getActiveAgents: () => (stateRef.current && stateRef.current.agents) || [],
|
|
1342
1774
|
getActiveAgentMetaMap: () => (stateRef.current && stateRef.current.activeAgentMeta) || new Map(),
|
|
1343
1775
|
getAgentLabel: (id) => {
|
|
@@ -1372,7 +1804,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1372
1804
|
requestStatus: requestDaemonStatus,
|
|
1373
1805
|
requestCron: (payload = {}) => {
|
|
1374
1806
|
try {
|
|
1375
|
-
const { IPC_REQUEST_TYPES } = require("../../
|
|
1807
|
+
const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
|
|
1376
1808
|
if (conn && typeof conn.send === "function") {
|
|
1377
1809
|
conn.send({ type: IPC_REQUEST_TYPES.CRON, ...payload });
|
|
1378
1810
|
}
|
|
@@ -1399,12 +1831,13 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1399
1831
|
}
|
|
1400
1832
|
return switchProject(targetRoot, { focusInput: true });
|
|
1401
1833
|
},
|
|
1834
|
+
toggleMultiWindow,
|
|
1402
1835
|
});
|
|
1403
1836
|
} catch (err) {
|
|
1404
1837
|
dispatch({ type: "log/append", text: `Error: command executor unavailable (${err && err.message ? err.message : err})` });
|
|
1405
1838
|
}
|
|
1406
1839
|
return undefined;
|
|
1407
|
-
}, [interactive, logInkMessage, requestDaemonStatus, setStatusText]);
|
|
1840
|
+
}, [interactive, logInkMessage, requestDaemonStatus, setStatusText, toggleMultiWindow]);
|
|
1408
1841
|
|
|
1409
1842
|
// Periodic STATUS poll to keep the agents footer fresh, mirroring
|
|
1410
1843
|
// blessed's requestStatus on a timer.
|
|
@@ -1412,7 +1845,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1412
1845
|
if (!interactive) return undefined;
|
|
1413
1846
|
const conn = props.daemonConnection;
|
|
1414
1847
|
if (!conn || typeof conn.send !== "function") return undefined;
|
|
1415
|
-
const { IPC_REQUEST_TYPES } = require("../../
|
|
1848
|
+
const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
|
|
1416
1849
|
const tick = () => {
|
|
1417
1850
|
try { conn.send({ type: IPC_REQUEST_TYPES.STATUS }); } catch { /* ignore */ }
|
|
1418
1851
|
};
|
|
@@ -1448,7 +1881,8 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1448
1881
|
useEffect(() => {
|
|
1449
1882
|
const internalStatus = state.viewingAgentId ? internalStatusLabel(internalAgentView.status) : "ready";
|
|
1450
1883
|
const internalActive = internalStatus !== "ready";
|
|
1451
|
-
|
|
1884
|
+
const statusAnimated = state.status.message && isAnimatedStatusType(state.status.type);
|
|
1885
|
+
if ((!statusAnimated) && !internalActive) return undefined;
|
|
1452
1886
|
const timer = setInterval(() => setSpinnerTick((t) => t + 1), 100);
|
|
1453
1887
|
return () => clearInterval(timer);
|
|
1454
1888
|
}, [state.status.message, state.status.type, state.viewingAgentId, internalAgentView.status]);
|
|
@@ -1498,7 +1932,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1498
1932
|
|
|
1499
1933
|
const clearUfooAgentIdentity = useCallback(() => {
|
|
1500
1934
|
try {
|
|
1501
|
-
const { getUfooPaths } = require("../../
|
|
1935
|
+
const { getUfooPaths } = require("../../coordination/state/paths");
|
|
1502
1936
|
const agentDir = getUfooPaths(props.projectRoot).agentDir;
|
|
1503
1937
|
fs.rmSync(path.join(agentDir, "ufoo-agent.json"), { force: true });
|
|
1504
1938
|
fs.rmSync(path.join(agentDir, "ufoo-agent.history.jsonl"), { force: true });
|
|
@@ -1526,7 +1960,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1526
1960
|
const sendCronStop = useCallback((taskId) => {
|
|
1527
1961
|
if (!taskId || !props.daemonConnection || typeof props.daemonConnection.send !== "function") return;
|
|
1528
1962
|
try {
|
|
1529
|
-
const { IPC_REQUEST_TYPES } = require("../../
|
|
1963
|
+
const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
|
|
1530
1964
|
props.daemonConnection.send({ type: IPC_REQUEST_TYPES.CRON, operation: "stop", id: taskId });
|
|
1531
1965
|
} catch (err) {
|
|
1532
1966
|
dispatch({ type: "log/append", text: `Error: ${err && err.message ? err.message : err}` });
|
|
@@ -1538,7 +1972,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1538
1972
|
if (!root) return { ok: false, error: "project root unavailable" };
|
|
1539
1973
|
if (props.globalMode && props.env && typeof props.env.isRunning === "function" && !props.env.isRunning(root)) {
|
|
1540
1974
|
try {
|
|
1541
|
-
const { markProjectStopped } = require("../../projects");
|
|
1975
|
+
const { markProjectStopped } = require("../../runtime/projects");
|
|
1542
1976
|
markProjectStopped(root);
|
|
1543
1977
|
} catch { /* ignore */ }
|
|
1544
1978
|
refreshGlobalProjects(currentProjectRoot);
|
|
@@ -1565,7 +1999,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1565
1999
|
dispatch({ type: "log/appendMany", lines: persisted });
|
|
1566
2000
|
}
|
|
1567
2001
|
if (props.daemonCoordinator && typeof props.daemonCoordinator.switchProject === "function") {
|
|
1568
|
-
const { socketPath } = require("../../daemon");
|
|
2002
|
+
const { socketPath } = require("../../runtime/daemon");
|
|
1569
2003
|
const res = await Promise.resolve(props.daemonCoordinator.switchProject({
|
|
1570
2004
|
projectRoot: root,
|
|
1571
2005
|
sockPath: socketPath(root),
|
|
@@ -1586,7 +2020,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1586
2020
|
refreshGlobalProjects(root);
|
|
1587
2021
|
if (focusInput) dispatch({ type: "focus/set", mode: "input" });
|
|
1588
2022
|
try {
|
|
1589
|
-
const { IPC_REQUEST_TYPES } = require("../../
|
|
2023
|
+
const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
|
|
1590
2024
|
if (props.daemonConnection && typeof props.daemonConnection.send === "function") {
|
|
1591
2025
|
props.daemonConnection.send({ type: IPC_REQUEST_TYPES.STATUS });
|
|
1592
2026
|
}
|
|
@@ -1610,7 +2044,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1610
2044
|
const root = props.activeProjectRoot || props.projectRoot || "";
|
|
1611
2045
|
if (!root) return { ok: false, error: "controller root unavailable" };
|
|
1612
2046
|
if (props.daemonCoordinator && typeof props.daemonCoordinator.switchProject === "function") {
|
|
1613
|
-
const { socketPath } = require("../../daemon");
|
|
2047
|
+
const { socketPath } = require("../../runtime/daemon");
|
|
1614
2048
|
const res = await Promise.resolve(props.daemonCoordinator.switchProject({
|
|
1615
2049
|
projectRoot: root,
|
|
1616
2050
|
sockPath: socketPath(root),
|
|
@@ -1643,7 +2077,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1643
2077
|
const snapshot = readProjectAgentSnapshot(root);
|
|
1644
2078
|
dispatch({ type: "agents/set", list: snapshot.agents.map((id) => snapshot.metaMap.get(id) || { fullId: id }) });
|
|
1645
2079
|
try {
|
|
1646
|
-
const { IPC_REQUEST_TYPES } = require("../../
|
|
2080
|
+
const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
|
|
1647
2081
|
if (props.daemonConnection && typeof props.daemonConnection.send === "function") {
|
|
1648
2082
|
props.daemonConnection.send({ type: IPC_REQUEST_TYPES.STATUS });
|
|
1649
2083
|
}
|
|
@@ -1682,7 +2116,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1682
2116
|
dispatch({ type: "log/append", text: "Error: project switching unavailable" });
|
|
1683
2117
|
return;
|
|
1684
2118
|
}
|
|
1685
|
-
const { socketPath } = require("../../daemon");
|
|
2119
|
+
const { socketPath } = require("../../runtime/daemon");
|
|
1686
2120
|
const switched = await Promise.resolve(props.daemonCoordinator.switchProject({
|
|
1687
2121
|
projectRoot: fallback,
|
|
1688
2122
|
sockPath: socketPath(fallback),
|
|
@@ -1697,8 +2131,8 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1697
2131
|
dispatch({ type: "scope/set", scope: "project" });
|
|
1698
2132
|
}
|
|
1699
2133
|
|
|
1700
|
-
const { stopDaemon } = require("../../chat/transport");
|
|
1701
|
-
const { isRunning } = require("../../daemon");
|
|
2134
|
+
const { stopDaemon } = require("../../app/chat/transport");
|
|
2135
|
+
const { isRunning } = require("../../runtime/daemon");
|
|
1702
2136
|
stopDaemon(targetRoot, { source: `ink-project-close:${targetRoot}` });
|
|
1703
2137
|
refreshGlobalProjects(activeRoot);
|
|
1704
2138
|
if (isRunning(targetRoot)) {
|
|
@@ -1726,12 +2160,12 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1726
2160
|
if (!switched || switched.ok !== true) return;
|
|
1727
2161
|
}
|
|
1728
2162
|
dispatch({ type: "draft/clear" });
|
|
1729
|
-
const { createInputSubmitHandler } = require("../../chat/inputSubmitHandler");
|
|
1730
|
-
const { parseAtTarget } = require("../../chat/commands");
|
|
1731
|
-
const { resolveAgentId } = require("../../chat/agentDirectory");
|
|
1732
|
-
const { subscriberToSafeName } = require("../../bus/utils");
|
|
1733
|
-
const { getUfooPaths } = require("../../
|
|
1734
|
-
const { createTerminalAdapterRouter } = require("../../terminal/adapterRouter");
|
|
2163
|
+
const { createInputSubmitHandler } = require("../../app/chat/inputSubmitHandler");
|
|
2164
|
+
const { parseAtTarget } = require("../../app/chat/commands");
|
|
2165
|
+
const { resolveAgentId } = require("../../app/chat/agentDirectory");
|
|
2166
|
+
const { subscriberToSafeName } = require("../../coordination/bus/utils");
|
|
2167
|
+
const { getUfooPaths } = require("../../coordination/state/paths");
|
|
2168
|
+
const { createTerminalAdapterRouter } = require("../../runtime/terminal/adapterRouter");
|
|
1735
2169
|
const submitState = {};
|
|
1736
2170
|
Object.defineProperties(submitState, {
|
|
1737
2171
|
targetAgent: {
|
|
@@ -1814,7 +2248,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1814
2248
|
return createTerminalAdapterRouter().getAdapter({ launchMode, agentId, meta });
|
|
1815
2249
|
},
|
|
1816
2250
|
activateAgent: async (agentId) => {
|
|
1817
|
-
const AgentActivator = require("../../bus/activate");
|
|
2251
|
+
const AgentActivator = require("../../coordination/bus/activate");
|
|
1818
2252
|
const activator = new AgentActivator(currentProjectRoot || props.projectRoot);
|
|
1819
2253
|
await activator.activate(agentId);
|
|
1820
2254
|
},
|
|
@@ -1831,7 +2265,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1831
2265
|
renderScreen: () => {},
|
|
1832
2266
|
getShellCwd: () => activeChatHistoryRoot,
|
|
1833
2267
|
runShellCommand: async (shellCommand, options = {}) => {
|
|
1834
|
-
const { runShellCommand } = require("../../chat/shellCommand");
|
|
2268
|
+
const { runShellCommand } = require("../../app/chat/shellCommand");
|
|
1835
2269
|
return runShellCommand(shellCommand, options);
|
|
1836
2270
|
},
|
|
1837
2271
|
});
|
|
@@ -1927,7 +2361,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1927
2361
|
// with "/" or "@". Tab/Enter accept the highlighted entry, ↑↓ move the
|
|
1928
2362
|
// selection. The list reuses the pure buildCompletions helper from
|
|
1929
2363
|
// src/ui/format so jest can pin the source list without rendering ink.
|
|
1930
|
-
const { COMMAND_REGISTRY, COMMAND_TREE } = require("../../chat/commands");
|
|
2364
|
+
const { COMMAND_REGISTRY, COMMAND_TREE } = require("../../app/chat/commands");
|
|
1931
2365
|
const agentLabels = displayAgents.map((id) =>
|
|
1932
2366
|
getAgentLabelFor(displayAgentMeta.get(id), id)
|
|
1933
2367
|
);
|
|
@@ -1938,7 +2372,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1938
2372
|
if (!dynamicSourcesRef.current) {
|
|
1939
2373
|
const sources = { groupTemplates: [], soloProfiles: [] };
|
|
1940
2374
|
try {
|
|
1941
|
-
const { loadTemplateRegistry } = require("../../
|
|
2375
|
+
const { loadTemplateRegistry } = require("../../orchestration/groups/templates");
|
|
1942
2376
|
const reg = typeof loadTemplateRegistry === "function" ? loadTemplateRegistry(props.projectRoot) : null;
|
|
1943
2377
|
if (reg && Array.isArray(reg.templates)) {
|
|
1944
2378
|
sources.groupTemplates = reg.templates.map((item) => ({
|
|
@@ -1950,8 +2384,8 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1950
2384
|
}
|
|
1951
2385
|
} catch { /* ignore */ }
|
|
1952
2386
|
try {
|
|
1953
|
-
const { loadPromptProfileRegistry } = require("../../
|
|
1954
|
-
const { buildPromptProfileCandidates } = require("../../solo/commands");
|
|
2387
|
+
const { loadPromptProfileRegistry } = require("../../orchestration/groups/promptProfiles");
|
|
2388
|
+
const { buildPromptProfileCandidates } = require("../../orchestration/solo/commands");
|
|
1955
2389
|
const reg = typeof loadPromptProfileRegistry === "function" ? loadPromptProfileRegistry(props.projectRoot) : null;
|
|
1956
2390
|
if (reg && typeof buildPromptProfileCandidates === "function") {
|
|
1957
2391
|
sources.soloProfiles = buildPromptProfileCandidates(reg) || [];
|
|
@@ -1995,6 +2429,9 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
1995
2429
|
setCompletionWindowStart(Math.max(0, completions.length - POPUP_PAGE_SIZE));
|
|
1996
2430
|
}
|
|
1997
2431
|
}, [completions.length, completionIndex, completionWindowStart]);
|
|
2432
|
+
useEffect(() => {
|
|
2433
|
+
if (multiWindowActive) setMwCursor(String(state.draft || "").length);
|
|
2434
|
+
}, [draftVersion]);
|
|
1998
2435
|
const completionsOpen = completions.length > 0 && state.draft !== completionSuppressedDraft;
|
|
1999
2436
|
const acceptCompletion = useCallback(() => {
|
|
2000
2437
|
if (!completionsOpen) return false;
|
|
@@ -2028,6 +2465,18 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
2028
2465
|
};
|
|
2029
2466
|
};
|
|
2030
2467
|
|
|
2468
|
+
const activateExternalAgent = (agentId) => {
|
|
2469
|
+
const id = String(agentId || "").trim();
|
|
2470
|
+
if (!id) return;
|
|
2471
|
+
try {
|
|
2472
|
+
const AgentActivator = require("../../coordination/bus/activate");
|
|
2473
|
+
const activator = new AgentActivator(currentProjectRoot || props.projectRoot);
|
|
2474
|
+
void activator.activate(id);
|
|
2475
|
+
} catch (err) {
|
|
2476
|
+
logInkMessage("error", `✗ Failed to activate ${id}: ${err && err.message ? err.message : "unknown error"}`);
|
|
2477
|
+
}
|
|
2478
|
+
};
|
|
2479
|
+
|
|
2031
2480
|
const enterInternalAgentView = (enterRequest = {}) => {
|
|
2032
2481
|
const agentId = String(enterRequest.agentId || "").trim();
|
|
2033
2482
|
if (!agentId) return;
|
|
@@ -2049,7 +2498,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
2049
2498
|
dispatch({ type: "agents/clearTarget" });
|
|
2050
2499
|
sendInternalAgentWatch(agentId, true);
|
|
2051
2500
|
try {
|
|
2052
|
-
const { IPC_REQUEST_TYPES } = require("../../
|
|
2501
|
+
const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
|
|
2053
2502
|
if (props.daemonConnection && typeof props.daemonConnection.send === "function") {
|
|
2054
2503
|
props.daemonConnection.send({ type: IPC_REQUEST_TYPES.STATUS });
|
|
2055
2504
|
}
|
|
@@ -2120,10 +2569,19 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
2120
2569
|
return true;
|
|
2121
2570
|
}
|
|
2122
2571
|
const payload = buildAgentEnterPayload(agentId);
|
|
2123
|
-
|
|
2572
|
+
const action = resolveDashboardAgentEnterAction(payload);
|
|
2573
|
+
if (action === "internal") {
|
|
2124
2574
|
enterInternalAgentView(payload);
|
|
2125
2575
|
return true;
|
|
2126
2576
|
}
|
|
2577
|
+
if (action === "activate") {
|
|
2578
|
+
if (state.viewingAgentId) sendInternalAgentWatch(state.viewingAgentId, false);
|
|
2579
|
+
dispatch({ type: "agentView/exit" });
|
|
2580
|
+
dispatch({ type: "view/set", view: "agents" });
|
|
2581
|
+
dispatch({ type: "focus/set", mode: "input" });
|
|
2582
|
+
activateExternalAgent(agentId);
|
|
2583
|
+
return true;
|
|
2584
|
+
}
|
|
2127
2585
|
if (payload && typeof props.requestEnterAgentView === "function") {
|
|
2128
2586
|
if (state.viewingAgentId) sendInternalAgentWatch(state.viewingAgentId, false);
|
|
2129
2587
|
props.requestEnterAgentView(agentId, payload);
|
|
@@ -2136,7 +2594,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
2136
2594
|
const agentId = displayAgents[currentIndex - 1];
|
|
2137
2595
|
if (!agentId) return true;
|
|
2138
2596
|
try {
|
|
2139
|
-
const { IPC_REQUEST_TYPES } = require("../../
|
|
2597
|
+
const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
|
|
2140
2598
|
if (props.daemonConnection && typeof props.daemonConnection.send === "function") {
|
|
2141
2599
|
props.daemonConnection.send({ type: IPC_REQUEST_TYPES.CLOSE_AGENT, agent_id: agentId });
|
|
2142
2600
|
}
|
|
@@ -2248,6 +2706,58 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
2248
2706
|
};
|
|
2249
2707
|
|
|
2250
2708
|
useInput((input, key) => {
|
|
2709
|
+
if (multiWindowActive) {
|
|
2710
|
+
const controller = multiWindowControllerRef.current;
|
|
2711
|
+
const termFocused = mwTerminalFocusedRef.current;
|
|
2712
|
+
if (key.ctrl && input === "q") {
|
|
2713
|
+
if (controller && typeof controller.handleKey === "function") {
|
|
2714
|
+
controller.handleKey({ name: "q", ctrl: true, sequence: "" });
|
|
2715
|
+
}
|
|
2716
|
+
mwTerminalFocusedRef.current = false;
|
|
2717
|
+
setMwTerminalFocused(false);
|
|
2718
|
+
return;
|
|
2719
|
+
}
|
|
2720
|
+
if (key.ctrl && input === "w") {
|
|
2721
|
+
const agents = controller ? controller.getAgentIds() : [];
|
|
2722
|
+
if (agents.length === 0) return;
|
|
2723
|
+
if (!termFocused) {
|
|
2724
|
+
if (controller) controller.focusAgent(agents[0]);
|
|
2725
|
+
mwTerminalFocusedRef.current = true;
|
|
2726
|
+
setMwTerminalFocused(true);
|
|
2727
|
+
} else {
|
|
2728
|
+
const current = controller ? controller.getFocused() : null;
|
|
2729
|
+
const idx = current ? agents.indexOf(current) : -1;
|
|
2730
|
+
if (idx >= 0 && idx < agents.length - 1) {
|
|
2731
|
+
controller.focusAgent(agents[idx + 1]);
|
|
2732
|
+
} else {
|
|
2733
|
+
mwTerminalFocusedRef.current = false;
|
|
2734
|
+
setMwTerminalFocused(false);
|
|
2735
|
+
if (controller) controller.focusAgent(agents[0]);
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
if (controller) controller.renderAll();
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
if (termFocused && controller && typeof controller.sendInput === "function") {
|
|
2742
|
+
const now = Date.now();
|
|
2743
|
+
const last = mwLastInputRef.current;
|
|
2744
|
+
if (input === " " && !key.ctrl && !key.meta && isCJK(last.char) && now - last.time < 150) {
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
const raw = inkKeyToRaw(input, key);
|
|
2748
|
+
if (raw) {
|
|
2749
|
+
const cleaned = raw.length > 1 && /[⺀-鿿가-豈-︰-﹏]/.test(raw)
|
|
2750
|
+
? raw.replace(/ +$/, "")
|
|
2751
|
+
: raw;
|
|
2752
|
+
if (cleaned) {
|
|
2753
|
+
controller.sendInput(cleaned);
|
|
2754
|
+
const lastChar = cleaned[cleaned.length - 1];
|
|
2755
|
+
mwLastInputRef.current = { char: lastChar, time: now };
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2251
2761
|
if (key.ctrl && input === "c") { exit(); return; }
|
|
2252
2762
|
if (key.ctrl && input === "o") { dispatch({ type: "merge/expand" }); return; }
|
|
2253
2763
|
if (state.viewingAgentId) {
|
|
@@ -2323,14 +2833,33 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
2323
2833
|
&& state.agentSelectionMode
|
|
2324
2834
|
&& state.selectedAgentIndex >= 0) {
|
|
2325
2835
|
const agentId = displayAgents[state.selectedAgentIndex];
|
|
2326
|
-
if (agentId &&
|
|
2836
|
+
if (agentId && multiWindowActive) {
|
|
2837
|
+
const controller = multiWindowControllerRef.current;
|
|
2838
|
+
if (controller && typeof controller.focusAgent === "function") {
|
|
2839
|
+
controller.focusAgent(agentId);
|
|
2840
|
+
}
|
|
2841
|
+
setMwTerminalFocused(true);
|
|
2842
|
+
mwTerminalFocusedRef.current = true;
|
|
2843
|
+
dispatch({ type: "focus/set", mode: "input" });
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2846
|
+
if (agentId) {
|
|
2327
2847
|
const enterPayload = buildAgentEnterPayload(agentId);
|
|
2328
|
-
|
|
2848
|
+
const action = resolveDashboardAgentEnterAction(enterPayload);
|
|
2849
|
+
if (action === "internal") {
|
|
2329
2850
|
enterInternalAgentView(enterPayload);
|
|
2330
2851
|
return;
|
|
2331
2852
|
}
|
|
2332
|
-
|
|
2333
|
-
|
|
2853
|
+
if (action === "activate") {
|
|
2854
|
+
dispatch({ type: "agents/clearTarget" });
|
|
2855
|
+
dispatch({ type: "focus/set", mode: "input" });
|
|
2856
|
+
activateExternalAgent(agentId);
|
|
2857
|
+
return;
|
|
2858
|
+
}
|
|
2859
|
+
if (typeof props.requestEnterAgentView === "function") {
|
|
2860
|
+
props.requestEnterAgentView(agentId, enterPayload);
|
|
2861
|
+
exit();
|
|
2862
|
+
}
|
|
2334
2863
|
}
|
|
2335
2864
|
return;
|
|
2336
2865
|
}
|
|
@@ -2339,10 +2868,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
2339
2868
|
// Ctrl+X stops it.
|
|
2340
2869
|
if (state.focusMode === "dashboard" && state.dashboardView === "projects" && state.projects.length === 0) {
|
|
2341
2870
|
if (key.downArrow) {
|
|
2342
|
-
|
|
2343
|
-
if (displayAgents.length > 0 && state.selectedAgentIndex < 0) {
|
|
2344
|
-
dispatch({ type: "agents/select", index: 0 });
|
|
2345
|
-
}
|
|
2871
|
+
for (const action of buildEmptyProjectsDownActions(state, displayAgents)) dispatch(action);
|
|
2346
2872
|
return;
|
|
2347
2873
|
}
|
|
2348
2874
|
if (key.upArrow || key.return || key.escape) {
|
|
@@ -2444,7 +2970,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
2444
2970
|
|
|
2445
2971
|
// Dashboard focus on agents/mode/provider/cron — ↑↓ flip between
|
|
2446
2972
|
// sibling views, ←/→ pick within the active view, Esc returns to
|
|
2447
|
-
// the input.
|
|
2973
|
+
// the input.
|
|
2448
2974
|
if (state.focusMode === "dashboard"
|
|
2449
2975
|
&& (state.dashboardView === "agents"
|
|
2450
2976
|
|| state.dashboardView === "mode"
|
|
@@ -2470,7 +2996,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
2470
2996
|
if (state.selectedAgentIndex >= 0 && state.selectedAgentIndex < displayAgents.length) {
|
|
2471
2997
|
const agentId = displayAgents[state.selectedAgentIndex];
|
|
2472
2998
|
try {
|
|
2473
|
-
const { IPC_REQUEST_TYPES } = require("../../
|
|
2999
|
+
const { IPC_REQUEST_TYPES } = require("../../runtime/contracts/eventContract");
|
|
2474
3000
|
if (props.daemonConnection && typeof props.daemonConnection.send === "function") {
|
|
2475
3001
|
props.daemonConnection.send({ type: IPC_REQUEST_TYPES.CLOSE_AGENT, agent_id: agentId });
|
|
2476
3002
|
}
|
|
@@ -2573,16 +3099,188 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
2573
3099
|
if (key.return) { dispatch({ type: "focus/set", mode: "input" }); return; }
|
|
2574
3100
|
}
|
|
2575
3101
|
}
|
|
3102
|
+
|
|
3103
|
+
// Multi-window typing handler: replicates MultilineInput's key handling
|
|
3104
|
+
// so both modes share the same input behavior.
|
|
3105
|
+
if (multiWindowActive && state.focusMode !== "dashboard") {
|
|
3106
|
+
const intercepted = completionsOpen && (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.return);
|
|
3107
|
+
if (intercepted) return;
|
|
3108
|
+
if (key.return) {
|
|
3109
|
+
if (key.meta) {
|
|
3110
|
+
const before = (state.draft || "").slice(0, mwCursor);
|
|
3111
|
+
const after = (state.draft || "").slice(mwCursor);
|
|
3112
|
+
dispatch({ type: "draft/set", value: `${before}\n${after}` });
|
|
3113
|
+
setMwCursor(mwCursor + 1);
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3116
|
+
const value = String(state.draft || "").trim();
|
|
3117
|
+
if (value) { submit(value); setMwCursor(0); }
|
|
3118
|
+
return;
|
|
3119
|
+
}
|
|
3120
|
+
if (key.escape) {
|
|
3121
|
+
if (state.agentSelectionMode) { dispatch({ type: "agents/clearTarget" }); return; }
|
|
3122
|
+
if (state.draft) { dispatch({ type: "draft/clear" }); setMwCursor(0); }
|
|
3123
|
+
else if (state.status && state.status.message) { dispatch({ type: "status/idle" }); }
|
|
3124
|
+
return;
|
|
3125
|
+
}
|
|
3126
|
+
if (key.ctrl) {
|
|
3127
|
+
if (input === "a") { setMwCursor(fmt.moveCursorToVisualLineBoundary({ cursorPos: mwCursor, inputValue: state.draft || "", width: inputWidth, boundary: "start" })); return; }
|
|
3128
|
+
if (input === "e") { setMwCursor(fmt.moveCursorToVisualLineBoundary({ cursorPos: mwCursor, inputValue: state.draft || "", width: inputWidth, boundary: "end" })); return; }
|
|
3129
|
+
if (input === "b") { setMwCursor(fmt.moveCursorHorizontally(mwCursor, state.draft || "", "left")); return; }
|
|
3130
|
+
if (input === "f") { setMwCursor(fmt.moveCursorHorizontally(mwCursor, state.draft || "", "right")); return; }
|
|
3131
|
+
if (input === "d") { const d = state.draft || ""; if (mwCursor < d.length) { dispatch({ type: "draft/set", value: d.slice(0, mwCursor) + d.slice(mwCursor + 1) }); } return; }
|
|
3132
|
+
if (input === "h") { const d = state.draft || ""; if (mwCursor > 0) { dispatch({ type: "draft/set", value: d.slice(0, mwCursor - 1) + d.slice(mwCursor) }); setMwCursor(mwCursor - 1); } return; }
|
|
3133
|
+
if (input === "k") { dispatch({ type: "draft/set", value: (state.draft || "").slice(0, mwCursor) }); return; }
|
|
3134
|
+
if (input === "u") { dispatch({ type: "draft/set", value: (state.draft || "").slice(mwCursor) }); setMwCursor(0); return; }
|
|
3135
|
+
if (input === "w") { const r = fmt.deleteWordBeforeCursor(state.draft || "", mwCursor); dispatch({ type: "draft/set", value: r.value }); setMwCursor(r.cursorPos); return; }
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
if (key.meta) {
|
|
3139
|
+
if (input === "b") { setMwCursor(fmt.moveCursorByWord(state.draft || "", mwCursor, "backward")); return; }
|
|
3140
|
+
if (input === "f") { setMwCursor(fmt.moveCursorByWord(state.draft || "", mwCursor, "forward")); return; }
|
|
3141
|
+
if (input === "d") { const end = fmt.moveCursorByWord(state.draft || "", mwCursor, "forward"); const d = state.draft || ""; dispatch({ type: "draft/set", value: d.slice(0, mwCursor) + d.slice(end) }); return; }
|
|
3142
|
+
}
|
|
3143
|
+
if (key.backspace || key.delete) {
|
|
3144
|
+
const d = state.draft || "";
|
|
3145
|
+
if (key.meta || key.ctrl) { const r = fmt.deleteWordBeforeCursor(d, mwCursor); dispatch({ type: "draft/set", value: r.value }); setMwCursor(r.cursorPos); }
|
|
3146
|
+
else if (mwCursor > 0) { dispatch({ type: "draft/set", value: d.slice(0, mwCursor - 1) + d.slice(mwCursor) }); setMwCursor(mwCursor - 1); }
|
|
3147
|
+
return;
|
|
3148
|
+
}
|
|
3149
|
+
if (key.leftArrow) {
|
|
3150
|
+
if (!state.draft && typeof onArrowSideAtEmpty === "function") { onArrowSideAtEmpty("left"); return; }
|
|
3151
|
+
setMwCursor(fmt.moveCursorHorizontally(mwCursor, state.draft || "", "left"));
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3154
|
+
if (key.rightArrow) {
|
|
3155
|
+
if (!state.draft && typeof onArrowSideAtEmpty === "function") { onArrowSideAtEmpty("right"); return; }
|
|
3156
|
+
setMwCursor(fmt.moveCursorHorizontally(mwCursor, state.draft || "", "right"));
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
if (key.upArrow) { onArrowUpAtTop(); return; }
|
|
3160
|
+
if (key.downArrow) { onArrowDownAtBottom(state.draft); return; }
|
|
3161
|
+
if (input && !key.ctrl && !key.meta) {
|
|
3162
|
+
const filtered = input.replace(/[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]/g, "");
|
|
3163
|
+
if (filtered) {
|
|
3164
|
+
const d = state.draft || "";
|
|
3165
|
+
dispatch({ type: "draft/set", value: d.slice(0, mwCursor) + filtered + d.slice(mwCursor) });
|
|
3166
|
+
setMwCursor(mwCursor + filtered.length);
|
|
3167
|
+
}
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
2576
3172
|
}, { isActive: interactive });
|
|
2577
3173
|
|
|
2578
3174
|
const statusText = computeStatusText(state.status, spinnerTick);
|
|
2579
3175
|
const inputWidth = Math.max(20, (size.cols || 80) - 4);
|
|
2580
3176
|
const promptPrefix = (() => {
|
|
2581
3177
|
const projectPrefix = inCommittedProjectScope && currentProjectLabel ? `${currentProjectLabel} ` : "";
|
|
2582
|
-
|
|
3178
|
+
const visibleTargetAgentLabel = state.focusMode === "dashboard" && state.dashboardView !== "agents"
|
|
3179
|
+
? ""
|
|
3180
|
+
: targetAgentLabel;
|
|
3181
|
+
if (visibleTargetAgentLabel) return `${projectPrefix}›@${visibleTargetAgentLabel} `;
|
|
2583
3182
|
return `${projectPrefix}› `;
|
|
2584
3183
|
})();
|
|
2585
3184
|
|
|
3185
|
+
if (multiWindowActive) {
|
|
3186
|
+
const { renderDashboardLines } = require("./DashboardBar");
|
|
3187
|
+
const clampedCursor = fmt.clampCursorPos(mwCursor, state.draft || "");
|
|
3188
|
+
multiWindowChromeRef.current = {
|
|
3189
|
+
statusText,
|
|
3190
|
+
promptPrefix,
|
|
3191
|
+
draft: state.draft || "",
|
|
3192
|
+
cursor: clampedCursor,
|
|
3193
|
+
completions: completionsOpen ? completions : [],
|
|
3194
|
+
completionIndex: completionsOpen ? completionIndex : -1,
|
|
3195
|
+
completionWindowStart: completionsOpen ? completionWindowStart : 0,
|
|
3196
|
+
completionPageSize: POPUP_PAGE_SIZE,
|
|
3197
|
+
dashboardLines: renderDashboardLines({
|
|
3198
|
+
dashboardView: state.dashboardView,
|
|
3199
|
+
focusMode: state.focusMode,
|
|
3200
|
+
globalMode: state.globalMode,
|
|
3201
|
+
globalScope: state.globalScope,
|
|
3202
|
+
activeAgents: displayAgents,
|
|
3203
|
+
activeAgentMeta: displayAgentMeta,
|
|
3204
|
+
activeAgentId: targetAgentId || "",
|
|
3205
|
+
selectedAgentIndex: state.selectedAgentIndex,
|
|
3206
|
+
agentListWindowStart: state.agentListWindowStart,
|
|
3207
|
+
projectListWindowStart: state.projectListWindowStart,
|
|
3208
|
+
maxProjectWindow: 5,
|
|
3209
|
+
maxWidth: Math.max(20, size.cols || 80),
|
|
3210
|
+
getAgentLabel: (id) => getAgentLabelFor(displayAgentMeta.get(id), id),
|
|
3211
|
+
getAgentState: (id) => {
|
|
3212
|
+
const meta = displayAgentMeta.get(id);
|
|
3213
|
+
return meta && typeof meta.activity_state === "string" ? meta.activity_state : "";
|
|
3214
|
+
},
|
|
3215
|
+
launchMode: state.settings.launchMode,
|
|
3216
|
+
agentProvider: state.settings.agentProvider,
|
|
3217
|
+
modeOptions: state.modeOptions,
|
|
3218
|
+
selectedModeIndex: state.selectedModeIndex,
|
|
3219
|
+
providerOptions: state.providerOptions,
|
|
3220
|
+
selectedProviderIndex: state.selectedProviderIndex,
|
|
3221
|
+
cronTasks: state.cronTasks,
|
|
3222
|
+
selectedCronIndex: state.selectedCronIndex,
|
|
3223
|
+
projects: state.projects,
|
|
3224
|
+
selectedProjectIndex: state.selectedProjectIndex,
|
|
3225
|
+
activeProjectRoot: currentProjectRoot,
|
|
3226
|
+
dashHints: buildDashHints(state, targetAgentLabel),
|
|
3227
|
+
}),
|
|
3228
|
+
};
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
useEffect(() => {
|
|
3232
|
+
if (!multiWindowActive) return;
|
|
3233
|
+
const controller = multiWindowControllerRef.current;
|
|
3234
|
+
if (controller && typeof controller.renderAll === "function") {
|
|
3235
|
+
controller.renderAll();
|
|
3236
|
+
}
|
|
3237
|
+
}, [multiWindowActive, completionsOpen, completions.length, completionIndex, completionWindowStart]);
|
|
3238
|
+
|
|
3239
|
+
if (multiWindowActive) {
|
|
3240
|
+
return null;
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
const renderChatLogLine = (item) => {
|
|
3244
|
+
const row = classifyChatLogLine((item && item.text) || "");
|
|
3245
|
+
const key = item && item.id ? item.id : `log-${row.body}`;
|
|
3246
|
+
if (row.kind === "spacer") {
|
|
3247
|
+
return h(Text, { key, color: "gray" }, " ");
|
|
3248
|
+
}
|
|
3249
|
+
const palette = {
|
|
3250
|
+
assistant: { marker: "cyan", speaker: "white", body: undefined, bold: true },
|
|
3251
|
+
agent: { marker: "cyan", speaker: "cyan", body: undefined, bold: false },
|
|
3252
|
+
error: { marker: "red", speaker: "red", body: "red", bold: true },
|
|
3253
|
+
success: { marker: "green", speaker: "green", body: "green", bold: false },
|
|
3254
|
+
divider: { marker: "gray", speaker: "gray", body: "gray", bold: false },
|
|
3255
|
+
banner: { marker: "cyan", speaker: "cyan", body: "cyan", bold: true },
|
|
3256
|
+
meta: { marker: "gray", speaker: "gray", body: "gray", bold: false },
|
|
3257
|
+
plain: { marker: "gray", speaker: "gray", body: undefined, bold: false },
|
|
3258
|
+
};
|
|
3259
|
+
const colors = palette[row.kind] || palette.plain;
|
|
3260
|
+
if (row.kind === "divider") {
|
|
3261
|
+
return h(Box, { key, marginBottom: 1 },
|
|
3262
|
+
h(Text, { color: colors.body, wrap: "truncate" }, row.body),
|
|
3263
|
+
);
|
|
3264
|
+
}
|
|
3265
|
+
if (row.kind === "banner") {
|
|
3266
|
+
return h(Box, { key, marginBottom: 1 },
|
|
3267
|
+
h(Text, { color: colors.body, bold: true, wrap: "truncate" }, row.body),
|
|
3268
|
+
);
|
|
3269
|
+
}
|
|
3270
|
+
return h(Box, { key, width: "100%", marginBottom: 1 },
|
|
3271
|
+
h(Box, { width: 2 },
|
|
3272
|
+
h(Text, { color: colors.marker, bold: row.kind === "error" }, row.marker || " "),
|
|
3273
|
+
),
|
|
3274
|
+
row.speaker
|
|
3275
|
+
? h(Text, { color: colors.speaker, bold: colors.bold }, row.speaker)
|
|
3276
|
+
: null,
|
|
3277
|
+
row.speaker
|
|
3278
|
+
? h(Text, { color: "gray" }, " · ")
|
|
3279
|
+
: null,
|
|
3280
|
+
h(Text, { color: colors.body, wrap: "wrap" }, row.body || " "),
|
|
3281
|
+
);
|
|
3282
|
+
};
|
|
3283
|
+
|
|
2586
3284
|
if (state.viewingAgentId) {
|
|
2587
3285
|
const maxWidth = Math.max(20, size.cols || 80);
|
|
2588
3286
|
const logRows = Math.max(1, (size.rows || 24) - 5);
|
|
@@ -2659,7 +3357,7 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
2659
3357
|
|
|
2660
3358
|
return h(Box, { flexDirection: "column", width: "100%" },
|
|
2661
3359
|
h(Box, { flexDirection: "column", width: "100%" },
|
|
2662
|
-
...state.logLines.map(
|
|
3360
|
+
...state.logLines.map(renderChatLogLine),
|
|
2663
3361
|
),
|
|
2664
3362
|
state.activeMerge ? h(Box, null,
|
|
2665
3363
|
h(Text, { color: state.activeMerge.entries.some((e) => e.isError) ? "red" : "cyan" },
|
|
@@ -2671,10 +3369,10 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
2671
3369
|
const prefix = state.activeStream.publisher
|
|
2672
3370
|
? `${state.activeStream.publisher}: `
|
|
2673
3371
|
: "";
|
|
2674
|
-
return lines.map((line, idx) =>
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
}
|
|
3372
|
+
return lines.map((line, idx) => renderChatLogLine({
|
|
3373
|
+
id: `s-${idx}`,
|
|
3374
|
+
text: idx === 0 ? `${prefix}${line}` : ` ${line}`,
|
|
3375
|
+
}));
|
|
2678
3376
|
})(),
|
|
2679
3377
|
) : null,
|
|
2680
3378
|
h(Box, { marginTop: 1, width: "100%" },
|
|
@@ -2739,6 +3437,15 @@ function createChatApp({ React, ink, props, interactive = true }) {
|
|
|
2739
3437
|
interceptArrowsAndEnter: completionsOpen,
|
|
2740
3438
|
placeholder: "",
|
|
2741
3439
|
promptPrefix,
|
|
3440
|
+
// Dashboard renders 2 rows in global mode (always shows the
|
|
3441
|
+
// projects rail) or when an agents/mode/provider/cron view is
|
|
3442
|
+
// focused; otherwise it's a single summary row. Telling
|
|
3443
|
+
// MultilineInput how many UI rows live below it lets the IME
|
|
3444
|
+
// composition popup follow the on-screen caret instead of
|
|
3445
|
+
// appearing at the bottom-right of the terminal.
|
|
3446
|
+
linesBelowInput: props.globalMode
|
|
3447
|
+
? 2
|
|
3448
|
+
: (state.focusMode === "dashboard" ? 2 : 1),
|
|
2742
3449
|
}),
|
|
2743
3450
|
),
|
|
2744
3451
|
h(DashboardBar, {
|
|
@@ -2795,6 +3502,15 @@ function computeStatusText(status, spinnerTick) {
|
|
|
2795
3502
|
const message = String((status && status.message) || "");
|
|
2796
3503
|
if (!message) return "CHAT · Ready";
|
|
2797
3504
|
const type = String((status && status.type) || "thinking");
|
|
3505
|
+
if (type === "done" || type === "success") {
|
|
3506
|
+
const clean = stripBlessedTags(message).trim();
|
|
3507
|
+
return /^[✓✔]/.test(clean) ? clean : `✓ ${clean}`;
|
|
3508
|
+
}
|
|
3509
|
+
if (type === "error") {
|
|
3510
|
+
const clean = stripBlessedTags(message).trim();
|
|
3511
|
+
return /^[✗!]/.test(clean) ? clean : `✗ ${clean}`;
|
|
3512
|
+
}
|
|
3513
|
+
if (!isAnimatedStatusType(type)) return stripBlessedTags(message).trim() || "CHAT · Ready";
|
|
2798
3514
|
const indicators = fmt.STATUS_INDICATORS[type] || fmt.STATUS_INDICATORS.thinking;
|
|
2799
3515
|
const indicator = indicators[Math.max(0, Math.floor(Number(spinnerTick) || 0)) % indicators.length];
|
|
2800
3516
|
const startedAt = Number.isFinite(status && status.startedAt) ? status.startedAt : 0;
|
|
@@ -2823,12 +3539,12 @@ async function runChatInk(projectRoot, options = {}) {
|
|
|
2823
3539
|
env.startDaemon(projectRoot);
|
|
2824
3540
|
}
|
|
2825
3541
|
|
|
2826
|
-
const { socketPath } = require("../../daemon");
|
|
2827
|
-
const { connectWithRetry } = require("../../chat/transport");
|
|
2828
|
-
const { createDaemonTransport } = require("../../chat/daemonTransport");
|
|
2829
|
-
const { createDaemonConnection } = require("../../chat/daemonConnection");
|
|
2830
|
-
const { createDaemonCoordinator } = require("../../chat/daemonCoordinator");
|
|
2831
|
-
const { startDaemon, stopDaemon } = require("../../chat/transport");
|
|
3542
|
+
const { socketPath } = require("../../runtime/daemon");
|
|
3543
|
+
const { connectWithRetry } = require("../../app/chat/transport");
|
|
3544
|
+
const { createDaemonTransport } = require("../../app/chat/daemonTransport");
|
|
3545
|
+
const { createDaemonConnection } = require("../../app/chat/daemonConnection");
|
|
3546
|
+
const { createDaemonCoordinator } = require("../../app/chat/daemonCoordinator");
|
|
3547
|
+
const { startDaemon, stopDaemon } = require("../../app/chat/transport");
|
|
2832
3548
|
const { loadConfig } = require("../../config");
|
|
2833
3549
|
const { startAgentMirror, startInternalAgentMirror } = require("./agentMirror");
|
|
2834
3550
|
const sock = socketPath(projectRoot);
|
|
@@ -2939,10 +3655,18 @@ module.exports = {
|
|
|
2939
3655
|
buildDirectBusSendRequest,
|
|
2940
3656
|
buildPromptIpcRequest,
|
|
2941
3657
|
chatHistoryOptionsForScope,
|
|
3658
|
+
classifyChatLogLine,
|
|
3659
|
+
createInkMultiWindowToggle,
|
|
2942
3660
|
resolveActiveAgentId,
|
|
3661
|
+
resolveInjectSockPathForAgent,
|
|
2943
3662
|
resolveAgentEnterRequest,
|
|
3663
|
+
resolveDashboardAgentEnterAction,
|
|
3664
|
+
buildEmptyProjectsDownActions,
|
|
2944
3665
|
buildInternalLogRows,
|
|
3666
|
+
computeStatusText,
|
|
2945
3667
|
computeInternalStatusText,
|
|
3668
|
+
inferStatusType,
|
|
3669
|
+
isAnimatedStatusType,
|
|
2946
3670
|
resolveInternalKeyName,
|
|
2947
3671
|
isInternalViewingAgent,
|
|
2948
3672
|
applyInternalAgentTermWrite,
|