u-foo 1.0.6 → 1.1.9
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 +44 -4
- package/SKILLS/ufoo/SKILL.md +17 -2
- package/SKILLS/uinit/SKILL.md +8 -3
- package/bin/ucode-core.js +15 -0
- package/bin/ucode.js +125 -0
- package/bin/ufoo-assistant-agent.js +5 -0
- package/bin/ufoo-engine.js +25 -0
- package/bin/ufoo.js +4 -0
- package/modules/AGENTS.template.md +14 -4
- package/modules/bus/README.md +8 -5
- package/modules/bus/SKILLS/ubus/SKILL.md +5 -4
- package/modules/context/SKILLS/uctx/SKILL.md +3 -1
- package/modules/online/SKILLS/ufoo-online/SKILL.md +144 -0
- package/package.json +12 -3
- package/scripts/import-pi-mono.js +124 -0
- package/scripts/postinstall.js +20 -49
- package/scripts/sync-claude-skills.sh +21 -0
- package/src/agent/cliRunner.js +524 -31
- package/src/agent/internalRunner.js +76 -9
- package/src/agent/launcher.js +97 -45
- package/src/agent/normalizeOutput.js +1 -1
- package/src/agent/notifier.js +144 -4
- package/src/agent/ptyRunner.js +480 -10
- package/src/agent/ptyWrapper.js +28 -3
- package/src/agent/readyDetector.js +16 -0
- package/src/agent/ucode.js +443 -0
- package/src/agent/ucodeBootstrap.js +113 -0
- package/src/agent/ucodeBuild.js +67 -0
- package/src/agent/ucodeDoctor.js +184 -0
- package/src/agent/ucodeRuntimeConfig.js +129 -0
- package/src/agent/ufooAgent.js +11 -2
- package/src/assistant/agent.js +260 -0
- package/src/assistant/bridge.js +172 -0
- package/src/assistant/engine.js +252 -0
- package/src/assistant/stdio.js +58 -0
- package/src/assistant/ufooEngineCli.js +306 -0
- package/src/bus/activate.js +27 -11
- package/src/bus/daemon.js +133 -5
- package/src/bus/index.js +137 -80
- package/src/bus/inject.js +47 -17
- package/src/bus/message.js +145 -17
- package/src/bus/nickname.js +3 -1
- package/src/bus/queue.js +6 -1
- package/src/bus/store.js +189 -0
- package/src/bus/subscriber.js +20 -4
- package/src/bus/utils.js +9 -3
- package/src/chat/agentBar.js +117 -0
- package/src/chat/agentDirectory.js +88 -0
- package/src/chat/agentSockets.js +225 -0
- package/src/chat/agentViewController.js +298 -0
- package/src/chat/chatLogController.js +115 -0
- package/src/chat/commandExecutor.js +700 -0
- package/src/chat/commands.js +132 -0
- package/src/chat/completionController.js +414 -0
- package/src/chat/cronScheduler.js +160 -0
- package/src/chat/daemonConnection.js +166 -0
- package/src/chat/daemonCoordinator.js +64 -0
- package/src/chat/daemonMessageRouter.js +257 -0
- package/src/chat/daemonReconnect.js +41 -0
- package/src/chat/daemonTransport.js +36 -0
- package/src/chat/daemonTransportDefaults.js +10 -0
- package/src/chat/dashboardKeyController.js +480 -0
- package/src/chat/dashboardView.js +154 -0
- package/src/chat/index.js +935 -2909
- package/src/chat/inputHistoryController.js +105 -0
- package/src/chat/inputListenerController.js +304 -0
- package/src/chat/inputMath.js +104 -0
- package/src/chat/inputSubmitHandler.js +171 -0
- package/src/chat/layout.js +165 -0
- package/src/chat/pasteController.js +81 -0
- package/src/chat/rawKeyMap.js +42 -0
- package/src/chat/settingsController.js +132 -0
- package/src/chat/statusLineController.js +177 -0
- package/src/chat/streamTracker.js +138 -0
- package/src/chat/text.js +70 -0
- package/src/chat/transport.js +61 -0
- package/src/cli/busCoreCommands.js +59 -0
- package/src/cli/ctxCoreCommands.js +199 -0
- package/src/cli/onlineCoreCommands.js +379 -0
- package/src/cli.js +741 -238
- package/src/code/README.md +29 -0
- package/src/code/UCODE_PROMPT.md +32 -0
- package/src/code/agent.js +1651 -0
- package/src/code/cli.js +158 -0
- package/src/code/config +0 -0
- package/src/code/dispatch.js +42 -0
- package/src/code/index.js +70 -0
- package/src/code/nativeRunner.js +1213 -0
- package/src/code/runtime.js +154 -0
- package/src/code/sessionStore.js +162 -0
- package/src/code/taskDecomposer.js +269 -0
- package/src/code/tools/bash.js +53 -0
- package/src/code/tools/common.js +42 -0
- package/src/code/tools/edit.js +70 -0
- package/src/code/tools/read.js +44 -0
- package/src/code/tools/write.js +35 -0
- package/src/code/tui.js +1580 -0
- package/src/config.js +47 -1
- package/src/context/decisions.js +12 -2
- package/src/context/index.js +18 -1
- package/src/context/sync.js +127 -0
- package/src/daemon/agentProcessManager.js +74 -0
- package/src/daemon/cronOps.js +241 -0
- package/src/daemon/index.js +661 -488
- package/src/daemon/ipcServer.js +99 -0
- package/src/daemon/ops.js +417 -179
- package/src/daemon/promptLoop.js +319 -0
- package/src/daemon/promptRequest.js +101 -0
- package/src/daemon/providerSessions.js +32 -17
- package/src/daemon/reporting.js +90 -0
- package/src/daemon/run.js +2 -5
- package/src/daemon/status.js +24 -1
- package/src/init/index.js +68 -14
- package/src/online/bridge.js +663 -0
- package/src/online/client.js +245 -0
- package/src/online/runner.js +253 -0
- package/src/online/server.js +992 -0
- package/src/online/tokens.js +103 -0
- package/src/report/store.js +331 -0
- package/src/shared/eventContract.js +35 -0
- package/src/shared/ptySocketContract.js +21 -0
- package/src/status/index.js +50 -17
- package/src/terminal/adapterContract.js +87 -0
- package/src/terminal/adapterRouter.js +84 -0
- package/src/terminal/adapters/externalAdapter.js +14 -0
- package/src/terminal/adapters/internalAdapter.js +13 -0
- package/src/terminal/adapters/internalPtyAdapter.js +42 -0
- package/src/terminal/adapters/internalQueueAdapter.js +37 -0
- package/src/terminal/adapters/terminalAdapter.js +31 -0
- package/src/terminal/adapters/tmuxAdapter.js +30 -0
- package/src/ufoo/agentsStore.js +69 -3
- package/src/utils/banner.js +5 -2
- package/scripts/.archived/bash-to-js-migration/README.md +0 -46
- package/scripts/.archived/bash-to-js-migration/banner.sh +0 -89
- package/scripts/.archived/bash-to-js-migration/bus-alert.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus-autotrigger.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus-daemon.sh +0 -231
- package/scripts/.archived/bash-to-js-migration/bus-inject.sh +0 -176
- package/scripts/.archived/bash-to-js-migration/bus-listen.sh +0 -6
- package/scripts/.archived/bash-to-js-migration/bus.sh +0 -986
- package/scripts/.archived/bash-to-js-migration/context-decisions.sh +0 -167
- package/scripts/.archived/bash-to-js-migration/context-doctor.sh +0 -72
- package/scripts/.archived/bash-to-js-migration/context-lint.sh +0 -110
- package/scripts/.archived/bash-to-js-migration/doctor.sh +0 -22
- package/scripts/.archived/bash-to-js-migration/init.sh +0 -247
- package/scripts/.archived/bash-to-js-migration/skills.sh +0 -113
- package/scripts/.archived/bash-to-js-migration/status.sh +0 -125
- package/scripts/banner.sh +0 -2
- package/src/bus/API_DESIGN.md +0 -204
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const { IPC_REQUEST_TYPES } = require("../shared/eventContract");
|
|
2
|
+
|
|
3
|
+
function createDaemonConnection(options = {}) {
|
|
4
|
+
const {
|
|
5
|
+
connectClient,
|
|
6
|
+
handleMessage,
|
|
7
|
+
queueStatusLine,
|
|
8
|
+
resolveStatusLine,
|
|
9
|
+
logMessage,
|
|
10
|
+
} = options;
|
|
11
|
+
|
|
12
|
+
let client = null;
|
|
13
|
+
let reconnectPromise = null;
|
|
14
|
+
let exitRequested = false;
|
|
15
|
+
let connectionLostNotified = false;
|
|
16
|
+
const pendingRequests = [];
|
|
17
|
+
const MAX_PENDING_REQUESTS = 50;
|
|
18
|
+
|
|
19
|
+
function enqueueRequest(req) {
|
|
20
|
+
if (!req || req.type === IPC_REQUEST_TYPES.STATUS) return;
|
|
21
|
+
pendingRequests.push(req);
|
|
22
|
+
if (pendingRequests.length > MAX_PENDING_REQUESTS) {
|
|
23
|
+
pendingRequests.shift();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function flushPendingRequests() {
|
|
28
|
+
if (!client || client.destroyed) return;
|
|
29
|
+
while (pendingRequests.length > 0) {
|
|
30
|
+
const req = pendingRequests.shift();
|
|
31
|
+
client.write(`${JSON.stringify(req)}\n`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function detachClient(target = client) {
|
|
36
|
+
if (!target) return;
|
|
37
|
+
target.removeAllListeners("data");
|
|
38
|
+
target.removeAllListeners("close");
|
|
39
|
+
target.removeAllListeners("error");
|
|
40
|
+
if (target === client) {
|
|
41
|
+
client = null;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
target.end();
|
|
45
|
+
target.destroy();
|
|
46
|
+
} catch {
|
|
47
|
+
// ignore
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function attachClient(newClient) {
|
|
52
|
+
if (!newClient) return;
|
|
53
|
+
detachClient();
|
|
54
|
+
client = newClient;
|
|
55
|
+
connectionLostNotified = false;
|
|
56
|
+
let buffer = "";
|
|
57
|
+
client.on("data", (data) => {
|
|
58
|
+
buffer += data.toString("utf8");
|
|
59
|
+
const lines = buffer.split(/\r?\n/);
|
|
60
|
+
buffer = lines.pop() || "";
|
|
61
|
+
for (const line of lines.filter((l) => l.trim())) {
|
|
62
|
+
try {
|
|
63
|
+
const msg = JSON.parse(line);
|
|
64
|
+
const shouldStop = handleMessage(msg);
|
|
65
|
+
if (shouldStop) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// ignore
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
const handleDisconnect = () => {
|
|
74
|
+
if (client === newClient) {
|
|
75
|
+
client = null;
|
|
76
|
+
}
|
|
77
|
+
if (exitRequested) return;
|
|
78
|
+
if (!connectionLostNotified) {
|
|
79
|
+
connectionLostNotified = true;
|
|
80
|
+
logMessage("status", "{white-fg}✗{/white-fg} Daemon disconnected");
|
|
81
|
+
}
|
|
82
|
+
void ensureConnected();
|
|
83
|
+
};
|
|
84
|
+
client.on("close", handleDisconnect);
|
|
85
|
+
client.on("error", handleDisconnect);
|
|
86
|
+
flushPendingRequests();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function ensureConnected() {
|
|
90
|
+
if (client && !client.destroyed) return true;
|
|
91
|
+
if (exitRequested) return false;
|
|
92
|
+
if (reconnectPromise) return reconnectPromise;
|
|
93
|
+
queueStatusLine("Reconnecting to daemon");
|
|
94
|
+
logMessage("status", "{white-fg}⚙{/white-fg} Reconnecting to daemon...");
|
|
95
|
+
reconnectPromise = (async () => {
|
|
96
|
+
const newClient = await connectClient();
|
|
97
|
+
if (!newClient) {
|
|
98
|
+
resolveStatusLine("{gray-fg}✗{/gray-fg} Daemon offline");
|
|
99
|
+
logMessage("error", "{white-fg}✗{/white-fg} Failed to reconnect to daemon");
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
attachClient(newClient);
|
|
103
|
+
connectionLostNotified = false;
|
|
104
|
+
resolveStatusLine("{gray-fg}✓{/gray-fg} Daemon reconnected");
|
|
105
|
+
requestStatus();
|
|
106
|
+
return true;
|
|
107
|
+
})();
|
|
108
|
+
try {
|
|
109
|
+
return await reconnectPromise;
|
|
110
|
+
} finally {
|
|
111
|
+
reconnectPromise = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function connect() {
|
|
116
|
+
if (client && !client.destroyed) return true;
|
|
117
|
+
const newClient = await connectClient();
|
|
118
|
+
if (!newClient) return false;
|
|
119
|
+
attachClient(newClient);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function send(req) {
|
|
124
|
+
if (!client || client.destroyed) {
|
|
125
|
+
enqueueRequest(req);
|
|
126
|
+
void ensureConnected();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
client.write(`${JSON.stringify(req)}\n`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function requestStatus() {
|
|
133
|
+
send({ type: IPC_REQUEST_TYPES.STATUS });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function close() {
|
|
137
|
+
detachClient();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function markExit() {
|
|
141
|
+
exitRequested = true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getState() {
|
|
145
|
+
return {
|
|
146
|
+
client,
|
|
147
|
+
reconnectPromise,
|
|
148
|
+
pendingRequestCount: pendingRequests.length,
|
|
149
|
+
exitRequested,
|
|
150
|
+
connectionLostNotified,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
connect,
|
|
156
|
+
send,
|
|
157
|
+
requestStatus,
|
|
158
|
+
close,
|
|
159
|
+
markExit,
|
|
160
|
+
getState,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = {
|
|
165
|
+
createDaemonConnection,
|
|
166
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const { createDaemonConnection } = require("./daemonConnection");
|
|
2
|
+
const { restartDaemonFlow } = require("./daemonReconnect");
|
|
3
|
+
|
|
4
|
+
function createDaemonCoordinator(options = {}) {
|
|
5
|
+
const {
|
|
6
|
+
projectRoot,
|
|
7
|
+
daemonTransport,
|
|
8
|
+
connectClient,
|
|
9
|
+
handleMessage,
|
|
10
|
+
queueStatusLine,
|
|
11
|
+
resolveStatusLine,
|
|
12
|
+
logMessage,
|
|
13
|
+
stopDaemon,
|
|
14
|
+
startDaemon,
|
|
15
|
+
daemonConnection,
|
|
16
|
+
restartDaemon,
|
|
17
|
+
} = options;
|
|
18
|
+
|
|
19
|
+
const connectClientFn = connectClient
|
|
20
|
+
|| (daemonTransport && typeof daemonTransport.connectClient === "function"
|
|
21
|
+
? daemonTransport.connectClient.bind(daemonTransport)
|
|
22
|
+
: null);
|
|
23
|
+
|
|
24
|
+
if (!daemonConnection && !connectClientFn) {
|
|
25
|
+
throw new Error("createDaemonCoordinator requires connectClient, daemonTransport, or daemonConnection");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const connection = daemonConnection || createDaemonConnection({
|
|
29
|
+
connectClient: connectClientFn,
|
|
30
|
+
handleMessage,
|
|
31
|
+
queueStatusLine,
|
|
32
|
+
resolveStatusLine,
|
|
33
|
+
logMessage,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const restart = restartDaemon || restartDaemonFlow({
|
|
37
|
+
projectRoot,
|
|
38
|
+
stopDaemon,
|
|
39
|
+
startDaemon,
|
|
40
|
+
daemonConnection: connection,
|
|
41
|
+
logMessage,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function isConnected() {
|
|
45
|
+
if (!connection || typeof connection.getState !== "function") return false;
|
|
46
|
+
const state = connection.getState();
|
|
47
|
+
return Boolean(state && state.client && !state.client.destroyed);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
connect: () => connection.connect(),
|
|
52
|
+
requestStatus: () => connection.requestStatus(),
|
|
53
|
+
send: (req) => connection.send(req),
|
|
54
|
+
restart: () => restart(),
|
|
55
|
+
close: () => connection.close(),
|
|
56
|
+
markExit: () => connection.markExit(),
|
|
57
|
+
isConnected,
|
|
58
|
+
getState: () => (typeof connection.getState === "function" ? connection.getState() : null),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
createDaemonCoordinator,
|
|
64
|
+
};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
const { IPC_RESPONSE_TYPES, BUS_STATUS_PHASES } = require("../shared/eventContract");
|
|
2
|
+
|
|
3
|
+
function createDaemonMessageRouter(options = {}) {
|
|
4
|
+
const {
|
|
5
|
+
escapeBlessed = (value) => String(value || ""),
|
|
6
|
+
stripBlessedTags = (value) => String(value || "").replace(/\{[^}]+\}/g, ""),
|
|
7
|
+
logMessage = () => {},
|
|
8
|
+
renderScreen = () => {},
|
|
9
|
+
updateDashboard = () => {},
|
|
10
|
+
requestStatus = () => {},
|
|
11
|
+
resolveStatusLine = () => {},
|
|
12
|
+
enqueueBusStatus = () => {},
|
|
13
|
+
resolveBusStatus = () => {},
|
|
14
|
+
getPending = () => null,
|
|
15
|
+
setPending = () => {},
|
|
16
|
+
resolveAgentDisplayName = (value) => value,
|
|
17
|
+
getCurrentView = () => "main",
|
|
18
|
+
isAgentViewUsesBus = () => false,
|
|
19
|
+
getViewingAgent = () => "",
|
|
20
|
+
writeToAgentTerm = () => {},
|
|
21
|
+
consumePendingDelivery = () => false,
|
|
22
|
+
getPendingState = () => null,
|
|
23
|
+
beginStream = () => null,
|
|
24
|
+
appendStreamDelta = () => {},
|
|
25
|
+
finalizeStream = () => {},
|
|
26
|
+
hasStream = () => false,
|
|
27
|
+
} = options;
|
|
28
|
+
|
|
29
|
+
function normalizeDisplayMessage(raw) {
|
|
30
|
+
let displayMessage = raw || "";
|
|
31
|
+
let streamPayload = null;
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(raw);
|
|
34
|
+
if (parsed && typeof parsed === "object" && parsed.reply) {
|
|
35
|
+
displayMessage = parsed.reply;
|
|
36
|
+
} else if (parsed && typeof parsed === "object" && parsed.stream) {
|
|
37
|
+
streamPayload = parsed;
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// Not JSON, keep original.
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof displayMessage === "string") {
|
|
44
|
+
displayMessage = displayMessage.replace(/\\n/g, "\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { displayMessage, streamPayload };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function handleStatusMessage(msg) {
|
|
51
|
+
const data = msg.data || {};
|
|
52
|
+
if (typeof data.phase === "string") {
|
|
53
|
+
const text = data.text || "";
|
|
54
|
+
const item = { key: data.key, text };
|
|
55
|
+
if (data.phase === BUS_STATUS_PHASES.START) {
|
|
56
|
+
enqueueBusStatus(item);
|
|
57
|
+
} else if (data.phase === BUS_STATUS_PHASES.DONE || data.phase === BUS_STATUS_PHASES.ERROR) {
|
|
58
|
+
resolveBusStatus(item);
|
|
59
|
+
if (text) {
|
|
60
|
+
const prefix = data.phase === BUS_STATUS_PHASES.ERROR
|
|
61
|
+
? "{white-fg}✗{/white-fg}"
|
|
62
|
+
: "{white-fg}✓{/white-fg}";
|
|
63
|
+
logMessage("status", `${prefix} ${escapeBlessed(text)}`, data);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
enqueueBusStatus(item);
|
|
67
|
+
}
|
|
68
|
+
renderScreen();
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
updateDashboard(data);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function handleResponseMessage(msg) {
|
|
77
|
+
const payload = msg.data || {};
|
|
78
|
+
if (payload.reply) {
|
|
79
|
+
resolveStatusLine(`{gray-fg}←{/gray-fg} ${escapeBlessed(payload.reply)}`);
|
|
80
|
+
logMessage("reply", `{white-fg}←{/white-fg} ${escapeBlessed(payload.reply)}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (payload.recoverable && typeof payload.recoverable === "object") {
|
|
84
|
+
const recoverableList = Array.isArray(payload.recoverable.recoverable)
|
|
85
|
+
? payload.recoverable.recoverable
|
|
86
|
+
: [];
|
|
87
|
+
const skippedList = Array.isArray(payload.recoverable.skipped)
|
|
88
|
+
? payload.recoverable.skipped
|
|
89
|
+
: [];
|
|
90
|
+
|
|
91
|
+
if (recoverableList.length > 0) {
|
|
92
|
+
logMessage("system", "{cyan-fg}Recoverable agents:{/cyan-fg}");
|
|
93
|
+
recoverableList.forEach((item) => {
|
|
94
|
+
const nickname = item.nickname ? ` (${item.nickname})` : "";
|
|
95
|
+
const meta = item.launchMode ? ` [${item.agent}/${item.launchMode}]` : ` [${item.agent}]`;
|
|
96
|
+
logMessage("system", ` • ${escapeBlessed(`${item.id}${nickname}${meta}`)}`);
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
logMessage("system", "{gray-fg}No recoverable agents{/gray-fg}");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (skippedList.length > 0) {
|
|
103
|
+
logMessage("system", "{gray-fg}Skipped:{/gray-fg}");
|
|
104
|
+
skippedList.forEach((item) => {
|
|
105
|
+
const reason = item && item.reason ? item.reason : "skipped";
|
|
106
|
+
const id = item && item.id ? item.id : "unknown";
|
|
107
|
+
logMessage("system", ` - ${escapeBlessed(`${id}: ${reason}`)}`);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (payload.dispatch && payload.dispatch.length > 0) {
|
|
113
|
+
const targets = payload.dispatch.map((d) => d.target || d).join(", ");
|
|
114
|
+
logMessage("dispatch", `{white-fg}→{/white-fg} Dispatched to: ${escapeBlessed(targets)}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (
|
|
118
|
+
payload.disambiguate &&
|
|
119
|
+
Array.isArray(payload.disambiguate.candidates) &&
|
|
120
|
+
payload.disambiguate.candidates.length > 0
|
|
121
|
+
) {
|
|
122
|
+
const pending = getPending();
|
|
123
|
+
setPending({ disambiguate: payload.disambiguate, original: pending && pending.original });
|
|
124
|
+
const prompt = payload.disambiguate.prompt || "Choose target:";
|
|
125
|
+
resolveStatusLine(`{gray-fg}?{/gray-fg} ${escapeBlessed(prompt)}`);
|
|
126
|
+
logMessage("disambiguate", `{white-fg}?{/white-fg} ${escapeBlessed(prompt)}`);
|
|
127
|
+
payload.disambiguate.candidates.forEach((candidate, index) => {
|
|
128
|
+
logMessage(
|
|
129
|
+
"disambiguate",
|
|
130
|
+
` {cyan-fg}${index + 1}){/cyan-fg} ${escapeBlessed(candidate.agent_id)} {gray-fg}— ${escapeBlessed(candidate.reason || "")}{/gray-fg}`
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
} else {
|
|
134
|
+
setPending(null);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!payload.reply && !payload.disambiguate) {
|
|
138
|
+
resolveStatusLine("{gray-fg}✓{/gray-fg} Done");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (Array.isArray(payload.ops) && payload.ops.length > 0) {
|
|
142
|
+
const hasStateMutation = payload.ops.some((op) =>
|
|
143
|
+
op && (op.action === "close" || op.action === "launch" || op.action === "rename" || op.action === "cron")
|
|
144
|
+
);
|
|
145
|
+
if (hasStateMutation) {
|
|
146
|
+
requestStatus();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
renderScreen();
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function handleBusMessage(msg) {
|
|
155
|
+
const data = msg.data || {};
|
|
156
|
+
const prefix = data.event === "broadcast" ? "{gray-fg}⇢{/gray-fg}" : "{gray-fg}↔{/gray-fg}";
|
|
157
|
+
const publisher = data.publisher && data.publisher !== "unknown"
|
|
158
|
+
? data.publisher
|
|
159
|
+
: (data.event === "broadcast" ? "broadcast" : "bus");
|
|
160
|
+
|
|
161
|
+
const { displayMessage, streamPayload } = normalizeDisplayMessage(data.message || "");
|
|
162
|
+
|
|
163
|
+
const isAgentViewTarget =
|
|
164
|
+
getCurrentView() === "agent" &&
|
|
165
|
+
isAgentViewUsesBus() &&
|
|
166
|
+
getViewingAgent() &&
|
|
167
|
+
publisher === getViewingAgent();
|
|
168
|
+
|
|
169
|
+
const displayName = resolveAgentDisplayName(publisher);
|
|
170
|
+
|
|
171
|
+
if (isAgentViewTarget) {
|
|
172
|
+
if (streamPayload) {
|
|
173
|
+
const delta = typeof streamPayload.delta === "string"
|
|
174
|
+
? streamPayload.delta.replace(/\\n/g, "\n")
|
|
175
|
+
: "";
|
|
176
|
+
if (delta) writeToAgentTerm(delta);
|
|
177
|
+
} else if (displayMessage) {
|
|
178
|
+
writeToAgentTerm(`${displayMessage}\r\n`);
|
|
179
|
+
}
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (data.event === "delivery" && consumePendingDelivery(publisher, displayName)) {
|
|
184
|
+
const ok = (data.status || "").toLowerCase() !== "error";
|
|
185
|
+
const detail = typeof data.message === "string" && data.message
|
|
186
|
+
? data.message
|
|
187
|
+
: (ok ? `Delivered to @${displayName}` : `Delivery failed to @${displayName}`);
|
|
188
|
+
if (ok) {
|
|
189
|
+
logMessage("status", `{white-fg}✓{/white-fg} ${escapeBlessed(detail)}`);
|
|
190
|
+
} else {
|
|
191
|
+
logMessage("error", `{white-fg}✗{/white-fg} ${escapeBlessed(detail)}`);
|
|
192
|
+
}
|
|
193
|
+
requestStatus();
|
|
194
|
+
renderScreen();
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const pendingBeforeMessage = getPendingState(publisher, displayName);
|
|
199
|
+
const prefixLabel = `${prefix} {gray-fg}${escapeBlessed(displayName)}{/gray-fg}: `;
|
|
200
|
+
const continuationPrefix = " ".repeat(stripBlessedTags(prefixLabel).length);
|
|
201
|
+
|
|
202
|
+
if (streamPayload) {
|
|
203
|
+
const delta = typeof streamPayload.delta === "string"
|
|
204
|
+
? streamPayload.delta.replace(/\\n/g, "\n")
|
|
205
|
+
: "";
|
|
206
|
+
const state = beginStream(publisher, prefixLabel, continuationPrefix, data);
|
|
207
|
+
if (delta) appendStreamDelta(state, delta);
|
|
208
|
+
if (streamPayload.done) {
|
|
209
|
+
finalizeStream(publisher, data, streamPayload.reason || "");
|
|
210
|
+
if (data.event === "message" && pendingBeforeMessage) {
|
|
211
|
+
consumePendingDelivery(publisher, displayName);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
if (hasStream(publisher)) {
|
|
216
|
+
finalizeStream(publisher, data, "interrupted");
|
|
217
|
+
}
|
|
218
|
+
const line = `${prefixLabel}${escapeBlessed(displayMessage)}`;
|
|
219
|
+
logMessage("bus", line, data);
|
|
220
|
+
if (data.event === "message" && pendingBeforeMessage) {
|
|
221
|
+
consumePendingDelivery(publisher, displayName);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (data.event === "agent_renamed" || data.event === "message") {
|
|
226
|
+
requestStatus();
|
|
227
|
+
}
|
|
228
|
+
renderScreen();
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function handleErrorMessage(msg) {
|
|
233
|
+
resolveStatusLine(`{gray-fg}✗{/gray-fg} Error: ${msg.error}`);
|
|
234
|
+
logMessage("error", `{white-fg}✗{/white-fg} Error: ${msg.error}`);
|
|
235
|
+
renderScreen();
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function handleMessage(msg) {
|
|
240
|
+
if (!msg || typeof msg !== "object") return false;
|
|
241
|
+
|
|
242
|
+
if (msg.type === IPC_RESPONSE_TYPES.STATUS) return handleStatusMessage(msg);
|
|
243
|
+
if (msg.type === IPC_RESPONSE_TYPES.RESPONSE) return handleResponseMessage(msg);
|
|
244
|
+
if (msg.type === IPC_RESPONSE_TYPES.BUS) return handleBusMessage(msg);
|
|
245
|
+
if (msg.type === IPC_RESPONSE_TYPES.ERROR) return handleErrorMessage(msg);
|
|
246
|
+
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
handleMessage,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = {
|
|
256
|
+
createDaemonMessageRouter,
|
|
257
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
function resolveDaemonConnection(daemonConnection) {
|
|
2
|
+
return typeof daemonConnection === "function" ? daemonConnection() : daemonConnection;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function restartDaemonFlow(options = {}) {
|
|
6
|
+
const {
|
|
7
|
+
projectRoot,
|
|
8
|
+
stopDaemon,
|
|
9
|
+
startDaemon,
|
|
10
|
+
daemonConnection,
|
|
11
|
+
logMessage,
|
|
12
|
+
} = options;
|
|
13
|
+
|
|
14
|
+
let restartInProgress = false;
|
|
15
|
+
|
|
16
|
+
return async function restartDaemon() {
|
|
17
|
+
if (restartInProgress) return;
|
|
18
|
+
restartInProgress = true;
|
|
19
|
+
logMessage("status", "{white-fg}⚙{/white-fg} Restarting daemon...");
|
|
20
|
+
try {
|
|
21
|
+
const connection = resolveDaemonConnection(daemonConnection);
|
|
22
|
+
if (connection) {
|
|
23
|
+
connection.close();
|
|
24
|
+
}
|
|
25
|
+
stopDaemon(projectRoot);
|
|
26
|
+
startDaemon(projectRoot);
|
|
27
|
+
const connected = connection ? await connection.connect() : false;
|
|
28
|
+
if (connected) {
|
|
29
|
+
logMessage("status", "{white-fg}✓{/white-fg} Daemon reconnected");
|
|
30
|
+
} else {
|
|
31
|
+
logMessage("error", "{white-fg}✗{/white-fg} Failed to reconnect to daemon");
|
|
32
|
+
}
|
|
33
|
+
} finally {
|
|
34
|
+
restartInProgress = false;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
restartDaemonFlow,
|
|
41
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const { DAEMON_TRANSPORT_DEFAULTS } = require("./daemonTransportDefaults");
|
|
2
|
+
|
|
3
|
+
function createDaemonTransport(options = {}) {
|
|
4
|
+
const {
|
|
5
|
+
projectRoot,
|
|
6
|
+
sockPath,
|
|
7
|
+
isRunning = () => true,
|
|
8
|
+
startDaemon = () => {},
|
|
9
|
+
connectWithRetry = async () => null,
|
|
10
|
+
primaryRetries = DAEMON_TRANSPORT_DEFAULTS.primaryRetries,
|
|
11
|
+
secondaryRetries = DAEMON_TRANSPORT_DEFAULTS.secondaryRetries,
|
|
12
|
+
retryDelayMs = DAEMON_TRANSPORT_DEFAULTS.retryDelayMs,
|
|
13
|
+
restartDelayMs = DAEMON_TRANSPORT_DEFAULTS.restartDelayMs,
|
|
14
|
+
} = options;
|
|
15
|
+
|
|
16
|
+
async function connectClient() {
|
|
17
|
+
let client = await connectWithRetry(sockPath, primaryRetries, retryDelayMs);
|
|
18
|
+
if (!client) {
|
|
19
|
+
// Retry once with a fresh daemon start and longer wait.
|
|
20
|
+
if (!isRunning(projectRoot)) {
|
|
21
|
+
startDaemon(projectRoot);
|
|
22
|
+
await new Promise((resolve) => setTimeout(resolve, restartDelayMs));
|
|
23
|
+
}
|
|
24
|
+
client = await connectWithRetry(sockPath, secondaryRetries, retryDelayMs);
|
|
25
|
+
}
|
|
26
|
+
return client;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
connectClient,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
createDaemonTransport,
|
|
36
|
+
};
|