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,319 @@
|
|
|
1
|
+
function buildAssistantContinuationPrompt({
|
|
2
|
+
originalPrompt,
|
|
3
|
+
previousReply,
|
|
4
|
+
reports,
|
|
5
|
+
}) {
|
|
6
|
+
const lines = [];
|
|
7
|
+
lines.push(`User: ${originalPrompt}`);
|
|
8
|
+
if (previousReply) {
|
|
9
|
+
lines.push("");
|
|
10
|
+
lines.push(`Your previous reply draft: ${previousReply}`);
|
|
11
|
+
}
|
|
12
|
+
lines.push("");
|
|
13
|
+
lines.push("Assistant execution reports (JSON):");
|
|
14
|
+
lines.push(JSON.stringify(reports, null, 2));
|
|
15
|
+
lines.push("");
|
|
16
|
+
lines.push("Using these reports, return the final JSON response.");
|
|
17
|
+
return lines.join("\n");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizePayload(payload) {
|
|
21
|
+
if (!payload || typeof payload !== "object") {
|
|
22
|
+
return { reply: "", dispatch: [], ops: [] };
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
...payload,
|
|
26
|
+
reply: typeof payload.reply === "string" ? payload.reply : "",
|
|
27
|
+
dispatch: Array.isArray(payload.dispatch) ? payload.dispatch : [],
|
|
28
|
+
ops: Array.isArray(payload.ops) ? payload.ops : [],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function annotateAssistantFailureFallback(payload, assistantResult) {
|
|
33
|
+
if (!payload || typeof payload !== "object") return payload;
|
|
34
|
+
const dispatchCount = Array.isArray(payload.dispatch) ? payload.dispatch.length : 0;
|
|
35
|
+
const opsCount = Array.isArray(payload.ops) ? payload.ops.length : 0;
|
|
36
|
+
if (dispatchCount > 0 || opsCount > 0) return payload;
|
|
37
|
+
|
|
38
|
+
const error = assistantResult && typeof assistantResult.error === "string" && assistantResult.error
|
|
39
|
+
? assistantResult.error
|
|
40
|
+
: "assistant task failed";
|
|
41
|
+
const note = `Assistant execution failed: ${error}. No action was applied.`;
|
|
42
|
+
const reply = typeof payload.reply === "string" && payload.reply
|
|
43
|
+
? `${payload.reply}\n${note}`
|
|
44
|
+
: note;
|
|
45
|
+
return {
|
|
46
|
+
...payload,
|
|
47
|
+
reply,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function extractAssistantCall(payload) {
|
|
52
|
+
if (!payload || typeof payload !== "object") {
|
|
53
|
+
return { assistantCall: null, ops: [] };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ops = Array.isArray(payload.ops) ? payload.ops : [];
|
|
57
|
+
let assistantCall = payload.assistant_call || null;
|
|
58
|
+
const normalOps = [];
|
|
59
|
+
|
|
60
|
+
for (const op of ops) {
|
|
61
|
+
if (op && op.action === "assistant_call") {
|
|
62
|
+
if (!assistantCall) assistantCall = op;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (op) normalOps.push(op);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { assistantCall, ops: normalOps };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeAssistantCall(call) {
|
|
72
|
+
if (!call) return null;
|
|
73
|
+
if (typeof call === "string") {
|
|
74
|
+
return { task: call, kind: "mixed", context: "", expect: "" };
|
|
75
|
+
}
|
|
76
|
+
if (typeof call !== "object") return null;
|
|
77
|
+
const task = typeof call.task === "string" ? call.task : "";
|
|
78
|
+
if (!task) return null;
|
|
79
|
+
return {
|
|
80
|
+
task,
|
|
81
|
+
kind: typeof call.kind === "string" ? call.kind : "mixed",
|
|
82
|
+
context: typeof call.context === "string" ? call.context : "",
|
|
83
|
+
expect: typeof call.expect === "string" ? call.expect : "",
|
|
84
|
+
provider: typeof call.provider === "string" ? call.provider : "",
|
|
85
|
+
model: typeof call.model === "string" ? call.model : "",
|
|
86
|
+
timeoutMs: Number.isFinite(call.timeout_ms) ? call.timeout_ms : null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function buildAssistantReport(call, result) {
|
|
91
|
+
return {
|
|
92
|
+
kind: call.kind,
|
|
93
|
+
task: call.task,
|
|
94
|
+
ok: result && result.ok !== false,
|
|
95
|
+
summary: result && typeof result.summary === "string" ? result.summary : "",
|
|
96
|
+
error: result && typeof result.error === "string" ? result.error : "",
|
|
97
|
+
artifacts: result && Array.isArray(result.artifacts) ? result.artifacts : [],
|
|
98
|
+
logs: result && Array.isArray(result.logs) ? result.logs : [],
|
|
99
|
+
metrics: result && typeof result.metrics === "object" ? result.metrics : {},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function createAssistantTaskId() {
|
|
104
|
+
return `assistant-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function emitAssistantReport(reportTaskStatus, payload) {
|
|
108
|
+
if (typeof reportTaskStatus !== "function") return;
|
|
109
|
+
try {
|
|
110
|
+
await reportTaskStatus(payload);
|
|
111
|
+
} catch {
|
|
112
|
+
// best effort: reporting must not break prompt flow
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function finalizePromptRun({
|
|
117
|
+
projectRoot,
|
|
118
|
+
payload,
|
|
119
|
+
processManager,
|
|
120
|
+
dispatchMessages,
|
|
121
|
+
handleOps,
|
|
122
|
+
markPending,
|
|
123
|
+
}) {
|
|
124
|
+
for (const item of payload.dispatch || []) {
|
|
125
|
+
if (item && item.target && item.target !== "broadcast") {
|
|
126
|
+
markPending(item.target);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await dispatchMessages(projectRoot, payload.dispatch || []);
|
|
131
|
+
const opsResults = await handleOps(projectRoot, payload.ops || [], processManager);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
ok: true,
|
|
135
|
+
payload,
|
|
136
|
+
opsResults,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function runPromptWithAssistant({
|
|
141
|
+
projectRoot,
|
|
142
|
+
prompt,
|
|
143
|
+
provider,
|
|
144
|
+
model,
|
|
145
|
+
processManager = null,
|
|
146
|
+
runUfooAgent,
|
|
147
|
+
runAssistantTask,
|
|
148
|
+
dispatchMessages,
|
|
149
|
+
handleOps,
|
|
150
|
+
markPending = () => {},
|
|
151
|
+
reportTaskStatus = () => {},
|
|
152
|
+
maxAssistantLoops = 2,
|
|
153
|
+
log = () => {},
|
|
154
|
+
}) {
|
|
155
|
+
const firstResult = await runUfooAgent({
|
|
156
|
+
projectRoot,
|
|
157
|
+
prompt: prompt || "",
|
|
158
|
+
provider,
|
|
159
|
+
model,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (!firstResult || !firstResult.ok) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
error: (firstResult && firstResult.error) || "agent failed",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const firstPayload = normalizePayload(firstResult.payload);
|
|
170
|
+
const extractedFirst = extractAssistantCall(firstPayload);
|
|
171
|
+
const assistantCall = normalizeAssistantCall(extractedFirst.assistantCall);
|
|
172
|
+
const basePayload = {
|
|
173
|
+
...firstPayload,
|
|
174
|
+
ops: extractedFirst.ops,
|
|
175
|
+
};
|
|
176
|
+
delete basePayload.assistant_call;
|
|
177
|
+
|
|
178
|
+
if (!assistantCall || maxAssistantLoops < 1) {
|
|
179
|
+
return finalizePromptRun({
|
|
180
|
+
projectRoot,
|
|
181
|
+
payload: basePayload,
|
|
182
|
+
processManager,
|
|
183
|
+
dispatchMessages,
|
|
184
|
+
handleOps,
|
|
185
|
+
markPending,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const assistantTaskId = createAssistantTaskId();
|
|
190
|
+
await emitAssistantReport(reportTaskStatus, {
|
|
191
|
+
phase: "start",
|
|
192
|
+
source: "assistant",
|
|
193
|
+
agent_id: "ufoo-assistant-agent",
|
|
194
|
+
scope: "private",
|
|
195
|
+
controller_id: "ufoo-agent",
|
|
196
|
+
task_id: assistantTaskId,
|
|
197
|
+
message: assistantCall.task,
|
|
198
|
+
summary: "",
|
|
199
|
+
error: "",
|
|
200
|
+
ok: true,
|
|
201
|
+
meta: {
|
|
202
|
+
kind: assistantCall.kind,
|
|
203
|
+
provider: assistantCall.provider || provider || "",
|
|
204
|
+
model: assistantCall.model || model || "",
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
let assistantResult;
|
|
209
|
+
try {
|
|
210
|
+
assistantResult = await runAssistantTask({
|
|
211
|
+
projectRoot,
|
|
212
|
+
provider: assistantCall.provider || "",
|
|
213
|
+
fallbackProvider: provider,
|
|
214
|
+
model: assistantCall.model || model,
|
|
215
|
+
task: assistantCall.task,
|
|
216
|
+
kind: assistantCall.kind,
|
|
217
|
+
context: assistantCall.context,
|
|
218
|
+
expect: assistantCall.expect,
|
|
219
|
+
timeoutMs: assistantCall.timeoutMs || undefined,
|
|
220
|
+
});
|
|
221
|
+
} catch (err) {
|
|
222
|
+
assistantResult = {
|
|
223
|
+
ok: false,
|
|
224
|
+
summary: "",
|
|
225
|
+
artifacts: [],
|
|
226
|
+
logs: [],
|
|
227
|
+
error: err && err.message ? err.message : "assistant task failed",
|
|
228
|
+
metrics: {},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
await emitAssistantReport(reportTaskStatus, {
|
|
233
|
+
phase: assistantResult && assistantResult.ok === false ? "error" : "done",
|
|
234
|
+
source: "assistant",
|
|
235
|
+
agent_id: "ufoo-assistant-agent",
|
|
236
|
+
scope: "private",
|
|
237
|
+
controller_id: "ufoo-agent",
|
|
238
|
+
task_id: assistantTaskId,
|
|
239
|
+
message: assistantCall.task,
|
|
240
|
+
summary: assistantResult && typeof assistantResult.summary === "string" ? assistantResult.summary : "",
|
|
241
|
+
error: assistantResult && typeof assistantResult.error === "string" ? assistantResult.error : "",
|
|
242
|
+
ok: assistantResult && assistantResult.ok !== false,
|
|
243
|
+
meta: {
|
|
244
|
+
kind: assistantCall.kind,
|
|
245
|
+
provider: assistantCall.provider || provider || "",
|
|
246
|
+
model: assistantCall.model || model || "",
|
|
247
|
+
metrics: assistantResult && assistantResult.metrics && typeof assistantResult.metrics === "object"
|
|
248
|
+
? assistantResult.metrics
|
|
249
|
+
: {},
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (!assistantResult || assistantResult.ok === false) {
|
|
254
|
+
log("assistant-loop fallback to round1 payload");
|
|
255
|
+
const fallbackPayload = annotateAssistantFailureFallback(basePayload, assistantResult);
|
|
256
|
+
return finalizePromptRun({
|
|
257
|
+
projectRoot,
|
|
258
|
+
payload: fallbackPayload,
|
|
259
|
+
processManager,
|
|
260
|
+
dispatchMessages,
|
|
261
|
+
handleOps,
|
|
262
|
+
markPending,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const reports = [buildAssistantReport(assistantCall, assistantResult)];
|
|
267
|
+
const continuationPrompt = buildAssistantContinuationPrompt({
|
|
268
|
+
originalPrompt: prompt || "",
|
|
269
|
+
previousReply: basePayload.reply || "",
|
|
270
|
+
reports,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const secondResult = await runUfooAgent({
|
|
274
|
+
projectRoot,
|
|
275
|
+
prompt: continuationPrompt,
|
|
276
|
+
provider,
|
|
277
|
+
model,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if (!secondResult || !secondResult.ok) {
|
|
281
|
+
log("assistant-loop fallback to round1 payload (round2 failed)");
|
|
282
|
+
return finalizePromptRun({
|
|
283
|
+
projectRoot,
|
|
284
|
+
payload: basePayload,
|
|
285
|
+
processManager,
|
|
286
|
+
dispatchMessages,
|
|
287
|
+
handleOps,
|
|
288
|
+
markPending,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const secondPayload = normalizePayload(secondResult.payload);
|
|
293
|
+
const extractedSecond = extractAssistantCall(secondPayload);
|
|
294
|
+
const finalPayload = {
|
|
295
|
+
...secondPayload,
|
|
296
|
+
ops: extractedSecond.ops,
|
|
297
|
+
assistant: { runs: reports },
|
|
298
|
+
};
|
|
299
|
+
delete finalPayload.assistant_call;
|
|
300
|
+
|
|
301
|
+
return finalizePromptRun({
|
|
302
|
+
projectRoot,
|
|
303
|
+
payload: finalPayload,
|
|
304
|
+
processManager,
|
|
305
|
+
dispatchMessages,
|
|
306
|
+
handleOps,
|
|
307
|
+
markPending,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
module.exports = {
|
|
312
|
+
runPromptWithAssistant,
|
|
313
|
+
buildAssistantContinuationPrompt,
|
|
314
|
+
normalizePayload,
|
|
315
|
+
annotateAssistantFailureFallback,
|
|
316
|
+
extractAssistantCall,
|
|
317
|
+
normalizeAssistantCall,
|
|
318
|
+
buildAssistantReport,
|
|
319
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { IPC_RESPONSE_TYPES } = require("../shared/eventContract");
|
|
4
|
+
const {
|
|
5
|
+
listControllerInboxEntries,
|
|
6
|
+
consumeControllerInboxEntries,
|
|
7
|
+
} = require("../report/store");
|
|
8
|
+
|
|
9
|
+
function buildPromptWithPrivateReports(prompt = "", reports = []) {
|
|
10
|
+
if (!Array.isArray(reports) || reports.length === 0) {
|
|
11
|
+
return prompt;
|
|
12
|
+
}
|
|
13
|
+
const lines = [];
|
|
14
|
+
lines.push(prompt || "");
|
|
15
|
+
lines.push("");
|
|
16
|
+
lines.push("Private runtime reports for ufoo-agent (JSON):");
|
|
17
|
+
lines.push(JSON.stringify(reports, null, 2));
|
|
18
|
+
lines.push("");
|
|
19
|
+
lines.push("Use these runtime reports when deciding reply/dispatch/ops.");
|
|
20
|
+
return lines.join("\n");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function handlePromptRequest(options = {}) {
|
|
24
|
+
const {
|
|
25
|
+
projectRoot,
|
|
26
|
+
req = {},
|
|
27
|
+
socket,
|
|
28
|
+
provider,
|
|
29
|
+
model,
|
|
30
|
+
processManager = null,
|
|
31
|
+
runPromptWithAssistant,
|
|
32
|
+
runUfooAgent,
|
|
33
|
+
runAssistantTask,
|
|
34
|
+
dispatchMessages,
|
|
35
|
+
handleOps,
|
|
36
|
+
markPending = () => {},
|
|
37
|
+
reportTaskStatus = () => {},
|
|
38
|
+
log = () => {},
|
|
39
|
+
} = options;
|
|
40
|
+
|
|
41
|
+
log(`prompt ${String(req.text || "").slice(0, 200)}`);
|
|
42
|
+
const privateReports = listControllerInboxEntries(projectRoot, "ufoo-agent", { num: 100 });
|
|
43
|
+
const promptText = buildPromptWithPrivateReports(req.text || "", privateReports);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const handled = await runPromptWithAssistant({
|
|
47
|
+
projectRoot,
|
|
48
|
+
prompt: promptText,
|
|
49
|
+
provider,
|
|
50
|
+
model,
|
|
51
|
+
processManager,
|
|
52
|
+
runUfooAgent,
|
|
53
|
+
runAssistantTask,
|
|
54
|
+
dispatchMessages,
|
|
55
|
+
handleOps,
|
|
56
|
+
markPending,
|
|
57
|
+
reportTaskStatus,
|
|
58
|
+
maxAssistantLoops: 2,
|
|
59
|
+
log,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!handled.ok) {
|
|
63
|
+
log(`agent-fail ${handled.error || "agent failed"}`);
|
|
64
|
+
socket.write(
|
|
65
|
+
`${JSON.stringify({
|
|
66
|
+
type: IPC_RESPONSE_TYPES.ERROR,
|
|
67
|
+
error: handled.error || "agent failed",
|
|
68
|
+
})}\n`,
|
|
69
|
+
);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
consumeControllerInboxEntries(projectRoot, "ufoo-agent", privateReports);
|
|
74
|
+
|
|
75
|
+
const payload = handled.payload || {};
|
|
76
|
+
const opsResults = handled.opsResults || [];
|
|
77
|
+
log(`ok reply=${Boolean(payload.reply)} dispatch=${(payload.dispatch || []).length} ops=${(payload.ops || []).length}`);
|
|
78
|
+
socket.write(
|
|
79
|
+
`${JSON.stringify({
|
|
80
|
+
type: IPC_RESPONSE_TYPES.RESPONSE,
|
|
81
|
+
data: payload,
|
|
82
|
+
opsResults,
|
|
83
|
+
})}\n`,
|
|
84
|
+
);
|
|
85
|
+
return true;
|
|
86
|
+
} catch (err) {
|
|
87
|
+
log(`error ${err.message || String(err)}`);
|
|
88
|
+
socket.write(
|
|
89
|
+
`${JSON.stringify({
|
|
90
|
+
type: IPC_RESPONSE_TYPES.ERROR,
|
|
91
|
+
error: err.message || String(err),
|
|
92
|
+
})}\n`,
|
|
93
|
+
);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
handlePromptRequest,
|
|
100
|
+
buildPromptWithPrivateReports,
|
|
101
|
+
};
|
|
@@ -14,11 +14,16 @@ function buildProbeMarker(nickname) {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Build probe command:
|
|
17
|
+
* Build probe command:
|
|
18
|
+
* - claude-code: /ufoo <nickname>
|
|
19
|
+
* - codex: $ufoo <nickname>
|
|
18
20
|
*/
|
|
19
21
|
function buildProbeCommand(agentType, nickname) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
+
const marker = String(nickname || "").trim();
|
|
23
|
+
if (agentType === "claude-code") {
|
|
24
|
+
return `/ufoo ${marker}`;
|
|
25
|
+
}
|
|
26
|
+
return `$ufoo ${marker}`;
|
|
22
27
|
}
|
|
23
28
|
|
|
24
29
|
function readLines(filePath) {
|
|
@@ -30,22 +35,30 @@ function readLines(filePath) {
|
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
|
|
38
|
+
function escapeRegExp(value = "") {
|
|
39
|
+
return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function containsProbeCommand(text, marker) {
|
|
43
|
+
if (!text || !marker) return false;
|
|
44
|
+
const escapedMarker = escapeRegExp(marker);
|
|
45
|
+
const pattern = `(?:^|[\\s"'\\\`])(?:\\/ufoo|\\$ufoo|ufoo)\\s+${escapedMarker}(?=$|[\\s"'\\\`.,:;!?\\]\\)\\}])`;
|
|
46
|
+
const re = new RegExp(pattern);
|
|
47
|
+
return re.test(String(text));
|
|
48
|
+
}
|
|
49
|
+
|
|
33
50
|
/**
|
|
34
51
|
* Check if a history record contains our probe marker
|
|
35
|
-
* Searches for
|
|
52
|
+
* Searches for probe marker command patterns:
|
|
53
|
+
* - "/ufoo <marker>" (claude)
|
|
54
|
+
* - "$ufoo <marker>" (codex)
|
|
55
|
+
* - "ufoo <marker>" (legacy compatibility)
|
|
36
56
|
*/
|
|
37
57
|
function recordContainsMarker(record, marker, rawLine) {
|
|
38
58
|
if (!marker) return false;
|
|
39
59
|
|
|
40
|
-
// Build both possible patterns
|
|
41
|
-
const patterns = [`/ufoo ${marker}`, `ufoo ${marker}`];
|
|
42
|
-
|
|
43
60
|
// Check raw line first (fastest)
|
|
44
|
-
if (rawLine)
|
|
45
|
-
for (const pattern of patterns) {
|
|
46
|
-
if (rawLine.includes(pattern)) return true;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
61
|
+
if (containsProbeCommand(rawLine, marker)) return true;
|
|
49
62
|
|
|
50
63
|
if (!record || typeof record !== "object") return false;
|
|
51
64
|
|
|
@@ -61,11 +74,7 @@ function recordContainsMarker(record, marker, rawLine) {
|
|
|
61
74
|
];
|
|
62
75
|
|
|
63
76
|
for (const field of fields) {
|
|
64
|
-
if (
|
|
65
|
-
for (const pattern of patterns) {
|
|
66
|
-
if (field.includes(pattern)) return true;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
77
|
+
if (containsProbeCommand(field, marker)) return true;
|
|
69
78
|
}
|
|
70
79
|
return false;
|
|
71
80
|
}
|
|
@@ -288,4 +297,10 @@ function scheduleProviderSessionProbe({
|
|
|
288
297
|
module.exports = {
|
|
289
298
|
scheduleProviderSessionProbe,
|
|
290
299
|
loadProviderSessionCache,
|
|
300
|
+
__private: {
|
|
301
|
+
buildProbeCommand,
|
|
302
|
+
recordContainsMarker,
|
|
303
|
+
containsProbeCommand,
|
|
304
|
+
escapeRegExp,
|
|
305
|
+
},
|
|
291
306
|
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const { BUS_STATUS_PHASES } = require("../shared/eventContract");
|
|
3
|
+
const {
|
|
4
|
+
REPORT_PHASES,
|
|
5
|
+
normalizeReportInput,
|
|
6
|
+
appendReport,
|
|
7
|
+
updateReportState,
|
|
8
|
+
appendControllerInboxEntry,
|
|
9
|
+
} = require("../report/store");
|
|
10
|
+
const { getUfooPaths } = require("../ufoo/paths");
|
|
11
|
+
|
|
12
|
+
function resolveAgentDisplayName(projectRoot, agentId) {
|
|
13
|
+
if (!agentId) return "unknown-agent";
|
|
14
|
+
try {
|
|
15
|
+
const busPath = getUfooPaths(projectRoot).agentsFile;
|
|
16
|
+
const bus = JSON.parse(fs.readFileSync(busPath, "utf8"));
|
|
17
|
+
const meta = bus && bus.agents ? bus.agents[agentId] : null;
|
|
18
|
+
if (meta && typeof meta.nickname === "string" && meta.nickname.trim()) {
|
|
19
|
+
return meta.nickname.trim();
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
// ignore
|
|
23
|
+
}
|
|
24
|
+
return agentId;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function toStatusPhase(reportPhase) {
|
|
28
|
+
if (reportPhase === REPORT_PHASES.START || reportPhase === REPORT_PHASES.PROGRESS) {
|
|
29
|
+
return BUS_STATUS_PHASES.START;
|
|
30
|
+
}
|
|
31
|
+
if (reportPhase === REPORT_PHASES.ERROR) return BUS_STATUS_PHASES.ERROR;
|
|
32
|
+
return BUS_STATUS_PHASES.DONE;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatStatusText(displayName, entry) {
|
|
36
|
+
if (entry.phase === REPORT_PHASES.START) {
|
|
37
|
+
const detail = entry.message || entry.summary || entry.task_id;
|
|
38
|
+
return `${displayName} ${detail}`;
|
|
39
|
+
}
|
|
40
|
+
if (entry.phase === REPORT_PHASES.PROGRESS) {
|
|
41
|
+
const detail = entry.message || entry.summary || entry.task_id;
|
|
42
|
+
return `${displayName} progress: ${detail}`;
|
|
43
|
+
}
|
|
44
|
+
if (entry.phase === REPORT_PHASES.ERROR) {
|
|
45
|
+
const detail = entry.error || entry.summary || entry.message || entry.task_id;
|
|
46
|
+
return `${displayName} failed: ${detail}`;
|
|
47
|
+
}
|
|
48
|
+
const detail = entry.summary || entry.message || entry.task_id;
|
|
49
|
+
return `${displayName} done: ${detail}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildReportStatus(entry, displayName) {
|
|
53
|
+
return {
|
|
54
|
+
phase: toStatusPhase(entry.phase),
|
|
55
|
+
key: `report:${entry.agent_id}:${entry.task_id}`,
|
|
56
|
+
text: formatStatusText(displayName, entry),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function publishToPrivateController(projectRoot, entry) {
|
|
61
|
+
if (!entry || !entry.controller_id) return;
|
|
62
|
+
appendControllerInboxEntry(projectRoot, entry.controller_id, entry);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function recordAgentReport({
|
|
66
|
+
projectRoot,
|
|
67
|
+
report,
|
|
68
|
+
onStatus = () => {},
|
|
69
|
+
log = () => {},
|
|
70
|
+
}) {
|
|
71
|
+
const entry = normalizeReportInput(report);
|
|
72
|
+
appendReport(projectRoot, entry);
|
|
73
|
+
const state = updateReportState(projectRoot, entry);
|
|
74
|
+
publishToPrivateController(projectRoot, entry);
|
|
75
|
+
const displayName = resolveAgentDisplayName(projectRoot, entry.agent_id);
|
|
76
|
+
if (entry.scope !== "private") {
|
|
77
|
+
onStatus(buildReportStatus(entry, displayName));
|
|
78
|
+
}
|
|
79
|
+
log(`report ${entry.phase} scope=${entry.scope} agent=${entry.agent_id} task=${entry.task_id}`);
|
|
80
|
+
return { entry, state };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = {
|
|
84
|
+
recordAgentReport,
|
|
85
|
+
resolveAgentDisplayName,
|
|
86
|
+
toStatusPhase,
|
|
87
|
+
formatStatusText,
|
|
88
|
+
buildReportStatus,
|
|
89
|
+
publishToPrivateController,
|
|
90
|
+
};
|
package/src/daemon/run.js
CHANGED
|
@@ -46,9 +46,7 @@ function runDaemonCli(argv) {
|
|
|
46
46
|
// Start fresh daemon
|
|
47
47
|
if (!process.env.UFOO_DAEMON_CHILD) {
|
|
48
48
|
const { spawn } = require("child_process");
|
|
49
|
-
const forceResume = launchMode !== "terminal";
|
|
50
49
|
const childEnv = { ...process.env, UFOO_DAEMON_CHILD: "1" };
|
|
51
|
-
if (forceResume) childEnv.UFOO_FORCE_RESUME = "1";
|
|
52
50
|
const child = spawn(process.execPath, [path.join(__dirname, "..", "..", "bin", "ufoo.js"), "daemon", "start"], {
|
|
53
51
|
detached: true,
|
|
54
52
|
stdio: "ignore",
|
|
@@ -58,9 +56,8 @@ function runDaemonCli(argv) {
|
|
|
58
56
|
child.unref();
|
|
59
57
|
return;
|
|
60
58
|
}
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
startDaemon({ projectRoot, provider, model, resumeMode: forceResume ? "force" : "none" });
|
|
59
|
+
// Manual restart does not auto-resume; crash-recovery is handled on next auto start with stale lock detection.
|
|
60
|
+
startDaemon({ projectRoot, provider, model, resumeMode: "none" });
|
|
64
61
|
return;
|
|
65
62
|
}
|
|
66
63
|
if (cmd === "status" || cmd === "--status") {
|
package/src/daemon/status.js
CHANGED
|
@@ -2,6 +2,7 @@ const fs = require("fs");
|
|
|
2
2
|
const path = require("path");
|
|
3
3
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
4
4
|
const { isMetaActive } = require("../bus/utils");
|
|
5
|
+
const { readReportSummary } = require("../report/store");
|
|
5
6
|
|
|
6
7
|
function readBus(projectRoot) {
|
|
7
8
|
const busPath = getUfooPaths(projectRoot).agentsFile;
|
|
@@ -60,11 +61,28 @@ function isHiddenSubscriber(id, meta) {
|
|
|
60
61
|
return false;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
function
|
|
64
|
+
function normalizeCronTasks(raw = []) {
|
|
65
|
+
const items = Array.isArray(raw) ? raw : [];
|
|
66
|
+
return items.map((task) => ({
|
|
67
|
+
id: String(task && task.id ? task.id : ""),
|
|
68
|
+
intervalMs: Number(task && task.intervalMs ? task.intervalMs : 0) || 0,
|
|
69
|
+
interval: String(task && task.interval ? task.interval : ""),
|
|
70
|
+
targets: Array.isArray(task && task.targets) ? task.targets.slice() : [],
|
|
71
|
+
prompt: String(task && task.prompt ? task.prompt : ""),
|
|
72
|
+
summary: String(task && task.summary ? task.summary : ""),
|
|
73
|
+
createdAt: Number(task && task.createdAt ? task.createdAt : 0) || 0,
|
|
74
|
+
lastRunAt: Number(task && task.lastRunAt ? task.lastRunAt : 0) || 0,
|
|
75
|
+
tickCount: Number(task && task.tickCount ? task.tickCount : 0) || 0,
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildStatus(projectRoot, options = {}) {
|
|
64
80
|
const bus = readBus(projectRoot);
|
|
65
81
|
const decisions = readDecisions(projectRoot);
|
|
66
82
|
const unread = readUnread(projectRoot);
|
|
83
|
+
const reports = readReportSummary(projectRoot);
|
|
67
84
|
const subscribers = bus ? Object.keys(bus.agents || {}) : [];
|
|
85
|
+
const cronTasks = normalizeCronTasks(options.cronTasks || []);
|
|
68
86
|
|
|
69
87
|
const activeEntries = bus
|
|
70
88
|
? Object.entries(bus.agents || {})
|
|
@@ -89,6 +107,11 @@ function buildStatus(projectRoot) {
|
|
|
89
107
|
active_meta: activeMeta,
|
|
90
108
|
unread,
|
|
91
109
|
decisions,
|
|
110
|
+
reports,
|
|
111
|
+
cron: {
|
|
112
|
+
count: cronTasks.length,
|
|
113
|
+
tasks: cronTasks,
|
|
114
|
+
},
|
|
92
115
|
};
|
|
93
116
|
}
|
|
94
117
|
|