u-foo 1.9.8 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -4
- package/src/agent/claudeEventTranslator.js +267 -0
- package/src/agent/claudeOauthTokenReader.js +52 -0
- package/src/agent/claudeThreadProvider.js +343 -0
- package/src/agent/cliRunner.js +4 -16
- package/src/agent/codexEventTranslator.js +78 -0
- package/src/agent/codexThreadProvider.js +181 -0
- package/src/agent/controllerToolExecutor.js +233 -0
- package/src/agent/credentials/claude.js +324 -0
- package/src/agent/credentials/codex.js +203 -0
- package/src/agent/credentials/index.js +106 -0
- package/src/agent/defaultBootstrap.js +128 -5
- package/src/agent/internalRunner.js +333 -2
- package/src/agent/loopObservability.js +190 -0
- package/src/agent/loopRuntime.js +457 -0
- package/src/agent/ufooAgent.js +178 -120
- package/src/agent/upstreamTransport.js +464 -0
- package/src/bus/utils.js +3 -2
- package/src/chat/dashboardView.js +51 -1
- package/src/chat/index.js +3 -1
- package/src/config.js +53 -17
- package/src/controller/flags.js +160 -0
- package/src/controller/gateRouter.js +201 -0
- package/src/controller/routerFastPath.js +22 -0
- package/src/controller/shadowGuard.js +280 -0
- package/src/daemon/index.js +2 -3
- package/src/daemon/promptLoop.js +33 -224
- package/src/daemon/promptRequest.js +360 -5
- package/src/daemon/status.js +2 -0
- package/src/history/inputTimeline.js +9 -4
- package/src/memory/index.js +24 -0
- package/src/providerapi/redactor.js +87 -0
- package/src/providerapi/shadowDiff.js +174 -0
- package/src/report/store.js +4 -3
- package/src/tools/handlers/ackBus.js +26 -0
- package/src/tools/handlers/common.js +64 -0
- package/src/tools/handlers/dispatchMessage.js +81 -0
- package/src/tools/handlers/listAgents.js +14 -0
- package/src/tools/handlers/readBusSummary.js +34 -0
- package/src/tools/handlers/readOpenDecisions.js +26 -0
- package/src/tools/handlers/readProjectRegistry.js +20 -0
- package/src/tools/handlers/readPromptHistory.js +123 -0
- package/src/tools/handlers/tier2.js +134 -0
- package/src/tools/index.js +55 -0
- package/src/tools/registry.js +69 -0
- package/src/tools/schemaFixtures.js +415 -0
- package/src/tools/tier0/listAgents.js +14 -0
- package/src/tools/tier0/readBusSummary.js +14 -0
- package/src/tools/tier0/readOpenDecisions.js +14 -0
- package/src/tools/tier0/readProjectRegistry.js +14 -0
- package/src/tools/tier0/readPromptHistory.js +14 -0
- package/src/tools/tier1/ackBus.js +14 -0
- package/src/tools/tier1/dispatchMessage.js +14 -0
- package/src/tools/tier1/routeAgent.js +14 -0
- package/src/tools/tier2/closeAgent.js +14 -0
- package/src/tools/tier2/launchAgent.js +14 -0
- package/src/tools/tier2/manageCron.js +14 -0
- package/src/tools/tier2/renameAgent.js +14 -0
- package/src/tools/types.js +75 -0
- package/src/tools/unimplemented.js +13 -0
- package/src/ufoo/paths.js +4 -0
- package/bin/ufoo-assistant-agent.js +0 -5
- package/bin/ufoo-engine.js +0 -25
- package/src/assistant/agent.js +0 -261
- package/src/assistant/bridge.js +0 -178
- package/src/assistant/constants.js +0 -15
- package/src/assistant/engine.js +0 -252
- package/src/assistant/stdio.js +0 -58
- package/src/assistant/ufooEngineCli.js +0 -312
|
@@ -6,6 +6,29 @@ const {
|
|
|
6
6
|
consumeControllerInboxEntries,
|
|
7
7
|
} = require("../report/store");
|
|
8
8
|
const { isGlobalControllerProjectRoot } = require("../projects");
|
|
9
|
+
const {
|
|
10
|
+
resolveGateRouterConfig,
|
|
11
|
+
shouldUseGateRouter,
|
|
12
|
+
} = require("../controller/gateRouter");
|
|
13
|
+
const {
|
|
14
|
+
CONTROLLER_MODES,
|
|
15
|
+
applyControllerModeForMessage,
|
|
16
|
+
resolveControllerMode,
|
|
17
|
+
} = require("../controller/flags");
|
|
18
|
+
const {
|
|
19
|
+
resolveLoopRuntimeOptions,
|
|
20
|
+
runPromptWithControllerLoop,
|
|
21
|
+
} = require("../agent/loopRuntime");
|
|
22
|
+
const {
|
|
23
|
+
appendShadowDiff,
|
|
24
|
+
createLoopObserver,
|
|
25
|
+
} = require("../agent/loopObservability");
|
|
26
|
+
const {
|
|
27
|
+
DEFAULT_SHADOW_SAMPLING_RATE,
|
|
28
|
+
createShadowBudgetBreaker,
|
|
29
|
+
createShadowGuard,
|
|
30
|
+
shouldSampleShadow,
|
|
31
|
+
} = require("../controller/shadowGuard");
|
|
9
32
|
|
|
10
33
|
function normalizeProjectRoute(route) {
|
|
11
34
|
if (!route || typeof route !== "object") return null;
|
|
@@ -63,6 +86,29 @@ function buildPromptWithPrivateReports(prompt = "", reports = [], requestMeta =
|
|
|
63
86
|
return lines.join("\n");
|
|
64
87
|
}
|
|
65
88
|
|
|
89
|
+
function normalizeMessageId(req = {}) {
|
|
90
|
+
return String(req.request_id || req.message_id || req.msg_id || req.id || "").trim();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function summarizeShadowPayload(payload = {}) {
|
|
94
|
+
if (!payload || typeof payload !== "object") {
|
|
95
|
+
return {
|
|
96
|
+
reply_present: false,
|
|
97
|
+
dispatch_count: 0,
|
|
98
|
+
ops_count: 0,
|
|
99
|
+
terminal_reason: "",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
reply_present: Boolean(payload.reply),
|
|
104
|
+
dispatch_count: Array.isArray(payload.dispatch) ? payload.dispatch.length : 0,
|
|
105
|
+
ops_count: Array.isArray(payload.ops) ? payload.ops.length : 0,
|
|
106
|
+
terminal_reason: payload.loop && typeof payload.loop === "object"
|
|
107
|
+
? String(payload.loop.terminal_reason || "").trim()
|
|
108
|
+
: "",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
66
112
|
async function handlePromptRequest(options = {}) {
|
|
67
113
|
const {
|
|
68
114
|
projectRoot,
|
|
@@ -72,10 +118,12 @@ async function handlePromptRequest(options = {}) {
|
|
|
72
118
|
model,
|
|
73
119
|
processManager = null,
|
|
74
120
|
runPromptWithAssistant,
|
|
121
|
+
runPromptWithControllerLoop: injectedLoopRunner = runPromptWithControllerLoop,
|
|
75
122
|
runUfooAgent,
|
|
76
|
-
|
|
123
|
+
runUfooRouteAgent,
|
|
77
124
|
dispatchMessages,
|
|
78
125
|
handleOps,
|
|
126
|
+
ackBus,
|
|
79
127
|
markPending = () => {},
|
|
80
128
|
reportTaskStatus = () => {},
|
|
81
129
|
forwardProjectPrompt = null,
|
|
@@ -84,8 +132,40 @@ async function handlePromptRequest(options = {}) {
|
|
|
84
132
|
|
|
85
133
|
log(`prompt ${String(req.text || "").slice(0, 200)}`);
|
|
86
134
|
const requestMeta = req.request_meta && typeof req.request_meta === "object" ? req.request_meta : {};
|
|
135
|
+
const messageId = normalizeMessageId(req);
|
|
136
|
+
const requestedControllerMode = String(
|
|
137
|
+
requestMeta.controller_mode || requestMeta.agent_execution_path || ""
|
|
138
|
+
).trim();
|
|
139
|
+
let controllerMode = resolveControllerMode({
|
|
140
|
+
projectRoot,
|
|
141
|
+
requestedMode: requestedControllerMode,
|
|
142
|
+
});
|
|
87
143
|
const isGlobalController = isGlobalControllerProjectRoot(projectRoot);
|
|
88
144
|
const forcedProjectRoot = String(requestMeta.force_project_root || "").trim();
|
|
145
|
+
const resolvedLoopRuntime = resolveLoopRuntimeOptions();
|
|
146
|
+
if (controllerMode === CONTROLLER_MODES.LEGACY && resolvedLoopRuntime.enabled) {
|
|
147
|
+
controllerMode = CONTROLLER_MODES.LOOP;
|
|
148
|
+
}
|
|
149
|
+
const controllerObserver = createLoopObserver({
|
|
150
|
+
projectRoot,
|
|
151
|
+
enabled: true,
|
|
152
|
+
defaults: {
|
|
153
|
+
controller_mode: controllerMode,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
const appliedControllerMode = applyControllerModeForMessage({
|
|
157
|
+
projectRoot,
|
|
158
|
+
nextMode: controllerMode,
|
|
159
|
+
messageId,
|
|
160
|
+
});
|
|
161
|
+
if (appliedControllerMode.transition) {
|
|
162
|
+
controllerObserver.emit("controller.flag.transition", appliedControllerMode.transition);
|
|
163
|
+
}
|
|
164
|
+
const loopRuntime = {
|
|
165
|
+
...resolvedLoopRuntime,
|
|
166
|
+
enabled: controllerMode === CONTROLLER_MODES.LOOP,
|
|
167
|
+
};
|
|
168
|
+
const shadowEnabled = controllerMode === CONTROLLER_MODES.SHADOW;
|
|
89
169
|
|
|
90
170
|
if (isGlobalController && forcedProjectRoot) {
|
|
91
171
|
try {
|
|
@@ -131,26 +211,166 @@ async function handlePromptRequest(options = {}) {
|
|
|
131
211
|
}
|
|
132
212
|
|
|
133
213
|
const privateReports = listControllerInboxEntries(projectRoot, "ufoo-agent", { num: 100 });
|
|
134
|
-
const promptText = buildPromptWithPrivateReports(req.text || "", privateReports, requestMeta);
|
|
135
214
|
const useGlobalProjectRouter = isGlobalController;
|
|
215
|
+
const promptRunner = runPromptWithAssistant;
|
|
216
|
+
const ufooAgentOptions = useGlobalProjectRouter ? { routingMode: "global-router" } : { controllerMode };
|
|
217
|
+
let nextRequestMeta = requestMeta;
|
|
218
|
+
if (!Object.prototype.hasOwnProperty.call(nextRequestMeta, "agent_execution_path") && controllerMode !== CONTROLLER_MODES.LEGACY) {
|
|
219
|
+
nextRequestMeta = {
|
|
220
|
+
...nextRequestMeta,
|
|
221
|
+
agent_execution_path: controllerMode,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const logGateRouterEvent = (event, details = {}) => {
|
|
226
|
+
controllerObserver.emit(event, details);
|
|
227
|
+
if (typeof log !== "function") return;
|
|
228
|
+
log(`event ${JSON.stringify({ event, ...details })}`);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const attachGateRouterMeta = (reason, detail = {}) => {
|
|
232
|
+
nextRequestMeta = {
|
|
233
|
+
...nextRequestMeta,
|
|
234
|
+
gate_router: {
|
|
235
|
+
attempted: true,
|
|
236
|
+
reason,
|
|
237
|
+
...detail,
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
controllerObserver.emit("controller.prompt_path_selected", {
|
|
243
|
+
applied_from_msg_id: messageId,
|
|
244
|
+
shadow_enabled: shadowEnabled,
|
|
245
|
+
loop_enabled: loopRuntime.enabled,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const gateRouterEligibility = shouldUseGateRouter({
|
|
249
|
+
projectRoot,
|
|
250
|
+
prompt: req.text || "",
|
|
251
|
+
requestMeta: nextRequestMeta,
|
|
252
|
+
});
|
|
253
|
+
const gateRouterConfig = gateRouterEligibility.enabled
|
|
254
|
+
? resolveGateRouterConfig({
|
|
255
|
+
projectRoot,
|
|
256
|
+
requestMeta,
|
|
257
|
+
})
|
|
258
|
+
: null;
|
|
259
|
+
|
|
260
|
+
if (!useGlobalProjectRouter && gateRouterEligibility.enabled && typeof runUfooRouteAgent === "function") {
|
|
261
|
+
logGateRouterEvent("controller.gate_router_attempted", {
|
|
262
|
+
flag: gateRouterEligibility.executionPath,
|
|
263
|
+
intent_reason: gateRouterEligibility.intent.reason,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const routed = await runUfooRouteAgent({
|
|
267
|
+
projectRoot,
|
|
268
|
+
prompt: req.text || "",
|
|
269
|
+
provider: gateRouterConfig.provider,
|
|
270
|
+
model: gateRouterConfig.model,
|
|
271
|
+
timeoutMs: gateRouterConfig.timeoutMs,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (!routed || routed.ok !== true) {
|
|
275
|
+
attachGateRouterMeta("provider_error", {
|
|
276
|
+
error: routed && routed.error ? routed.error : "route_agent_failed",
|
|
277
|
+
});
|
|
278
|
+
logGateRouterEvent("controller.gate_router_upgraded", {
|
|
279
|
+
reason: "provider_error",
|
|
280
|
+
fallback_used: "main_router",
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
const route = routed.route || {};
|
|
284
|
+
const canDispatch = route.decision === "direct_dispatch"
|
|
285
|
+
&& route.target && route.target !== "unknown"
|
|
286
|
+
&& route.confidence >= gateRouterConfig.confidenceThreshold;
|
|
287
|
+
|
|
288
|
+
if (!canDispatch) {
|
|
289
|
+
const upgradeReason = route.decision && route.decision !== "direct_dispatch"
|
|
290
|
+
? route.decision
|
|
291
|
+
: "low_confidence";
|
|
292
|
+
attachGateRouterMeta(upgradeReason, {
|
|
293
|
+
decision: route.decision || "",
|
|
294
|
+
target: route.target || "unknown",
|
|
295
|
+
confidence: Number(route.confidence || 0),
|
|
296
|
+
route_reason: route.reason || "",
|
|
297
|
+
});
|
|
298
|
+
logGateRouterEvent("controller.gate_router_upgraded", {
|
|
299
|
+
reason: upgradeReason,
|
|
300
|
+
decision: route.decision || "",
|
|
301
|
+
target: route.target || "unknown",
|
|
302
|
+
confidence: Number(route.confidence || 0),
|
|
303
|
+
fallback_used: "main_router",
|
|
304
|
+
});
|
|
305
|
+
} else {
|
|
306
|
+
const payload = {
|
|
307
|
+
reply: "",
|
|
308
|
+
dispatch: [{
|
|
309
|
+
target: route.target,
|
|
310
|
+
message: route.message || req.text || "",
|
|
311
|
+
injection_mode: route.injection_mode || "immediate",
|
|
312
|
+
source: "ufoo-agent-gate-router",
|
|
313
|
+
}],
|
|
314
|
+
ops: [],
|
|
315
|
+
};
|
|
316
|
+
try {
|
|
317
|
+
markPending(route.target);
|
|
318
|
+
await dispatchMessages(projectRoot, payload.dispatch);
|
|
319
|
+
consumeControllerInboxEntries(projectRoot, "ufoo-agent", privateReports);
|
|
320
|
+
logGateRouterEvent("controller.gate_router_completed", {
|
|
321
|
+
target: route.target,
|
|
322
|
+
confidence: Number(route.confidence || 0),
|
|
323
|
+
provider: routed.meta && routed.meta.provider ? routed.meta.provider : "",
|
|
324
|
+
model: routed.meta && routed.meta.model ? routed.meta.model : "",
|
|
325
|
+
fallback_used: "none",
|
|
326
|
+
});
|
|
327
|
+
socket.write(
|
|
328
|
+
`${JSON.stringify({
|
|
329
|
+
type: IPC_RESPONSE_TYPES.RESPONSE,
|
|
330
|
+
data: payload,
|
|
331
|
+
opsResults: [],
|
|
332
|
+
})}\n`,
|
|
333
|
+
);
|
|
334
|
+
return true;
|
|
335
|
+
} catch (err) {
|
|
336
|
+
attachGateRouterMeta("dispatch_failed", {
|
|
337
|
+
target: route.target,
|
|
338
|
+
confidence: Number(route.confidence || 0),
|
|
339
|
+
route_reason: route.reason || "",
|
|
340
|
+
error: err && err.message ? err.message : String(err),
|
|
341
|
+
});
|
|
342
|
+
logGateRouterEvent("controller.gate_router_upgraded", {
|
|
343
|
+
reason: "dispatch_failed",
|
|
344
|
+
target: route.target,
|
|
345
|
+
confidence: Number(route.confidence || 0),
|
|
346
|
+
fallback_used: "main_router",
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const promptText = buildPromptWithPrivateReports(req.text || "", privateReports, nextRequestMeta);
|
|
136
354
|
|
|
137
355
|
try {
|
|
138
|
-
const handled = await
|
|
356
|
+
const handled = await promptRunner({
|
|
139
357
|
projectRoot,
|
|
140
358
|
prompt: promptText,
|
|
141
359
|
provider,
|
|
142
360
|
model,
|
|
143
361
|
processManager,
|
|
144
362
|
runUfooAgent,
|
|
145
|
-
|
|
363
|
+
runPromptWithControllerLoop: injectedLoopRunner,
|
|
146
364
|
dispatchMessages,
|
|
147
365
|
handleOps,
|
|
366
|
+
ackBus,
|
|
148
367
|
markPending,
|
|
149
368
|
reportTaskStatus,
|
|
150
369
|
maxAssistantLoops: 2,
|
|
151
370
|
log,
|
|
152
|
-
ufooAgentOptions
|
|
371
|
+
ufooAgentOptions,
|
|
153
372
|
finalizeLocally: !useGlobalProjectRouter,
|
|
373
|
+
loopRuntime,
|
|
154
374
|
});
|
|
155
375
|
|
|
156
376
|
if (!handled.ok) {
|
|
@@ -164,6 +384,124 @@ async function handlePromptRequest(options = {}) {
|
|
|
164
384
|
return false;
|
|
165
385
|
}
|
|
166
386
|
|
|
387
|
+
let shadowResult = null;
|
|
388
|
+
if (shadowEnabled && !useGlobalProjectRouter && typeof injectedLoopRunner === "function") {
|
|
389
|
+
const shadowObserver = createLoopObserver({
|
|
390
|
+
projectRoot,
|
|
391
|
+
enabled: true,
|
|
392
|
+
defaults: {
|
|
393
|
+
controller_mode: controllerMode,
|
|
394
|
+
shadow_only: true,
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const shadowSamplingRate = Number.isFinite(Number(requestMeta.shadow_sampling_rate))
|
|
399
|
+
? Math.max(0, Math.min(1, Number(requestMeta.shadow_sampling_rate)))
|
|
400
|
+
: DEFAULT_SHADOW_SAMPLING_RATE;
|
|
401
|
+
const sampling = shouldSampleShadow({ messageId, samplingRate: shadowSamplingRate });
|
|
402
|
+
const budgetBreaker = createShadowBudgetBreaker({ projectRoot });
|
|
403
|
+
const budgetStatus = budgetBreaker.check();
|
|
404
|
+
|
|
405
|
+
if (!sampling.sampled) {
|
|
406
|
+
shadowObserver.emit("controller.shadow.skipped", {
|
|
407
|
+
applied_from_msg_id: messageId,
|
|
408
|
+
reason: "sampling_excluded",
|
|
409
|
+
sampling_rate: sampling.rate,
|
|
410
|
+
});
|
|
411
|
+
} else if (!budgetStatus.allowed) {
|
|
412
|
+
shadowObserver.emit("controller.shadow.skipped", {
|
|
413
|
+
applied_from_msg_id: messageId,
|
|
414
|
+
reason: budgetStatus.reason,
|
|
415
|
+
sampling_rate: sampling.rate,
|
|
416
|
+
});
|
|
417
|
+
} else {
|
|
418
|
+
const shadowGuard = createShadowGuard({ projectRoot });
|
|
419
|
+
const noOpExecutors = shadowGuard.buildNoOpExecutors();
|
|
420
|
+
const beforeSnapshot = shadowGuard.takeSnapshot();
|
|
421
|
+
|
|
422
|
+
shadowObserver.emit("controller.shadow.started", {
|
|
423
|
+
applied_from_msg_id: messageId,
|
|
424
|
+
primary_mode: CONTROLLER_MODES.LEGACY,
|
|
425
|
+
candidate_mode: CONTROLLER_MODES.LOOP,
|
|
426
|
+
sampling_rate: sampling.rate,
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
shadowResult = await injectedLoopRunner({
|
|
431
|
+
projectRoot,
|
|
432
|
+
prompt: promptText,
|
|
433
|
+
provider,
|
|
434
|
+
model,
|
|
435
|
+
processManager,
|
|
436
|
+
runUfooAgent,
|
|
437
|
+
dispatchMessages: noOpExecutors.dispatchMessages,
|
|
438
|
+
handleOps: noOpExecutors.handleOps,
|
|
439
|
+
ackBus: noOpExecutors.ackBus,
|
|
440
|
+
markPending: noOpExecutors.markPending,
|
|
441
|
+
reportTaskStatus,
|
|
442
|
+
maxAssistantLoops: 2,
|
|
443
|
+
log,
|
|
444
|
+
ufooAgentOptions,
|
|
445
|
+
finalizeLocally: false,
|
|
446
|
+
loopRuntime: {
|
|
447
|
+
...resolvedLoopRuntime,
|
|
448
|
+
enabled: true,
|
|
449
|
+
},
|
|
450
|
+
observer: shadowObserver,
|
|
451
|
+
observabilityDefaults: {
|
|
452
|
+
controller_mode: controllerMode,
|
|
453
|
+
shadow_only: true,
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
} catch (shadowErr) {
|
|
457
|
+
shadowResult = {
|
|
458
|
+
ok: false,
|
|
459
|
+
error: shadowErr && shadowErr.message ? shadowErr.message : String(shadowErr),
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const assertion = shadowGuard.assertNoSideEffects(beforeSnapshot);
|
|
464
|
+
if (!assertion.ok) {
|
|
465
|
+
shadowObserver.emit("controller.shadow.violation", {
|
|
466
|
+
applied_from_msg_id: messageId,
|
|
467
|
+
violations: assertion.violations,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const loopSummary = shadowResult && shadowResult.payload && shadowResult.payload.loop
|
|
472
|
+
? shadowResult.payload.loop
|
|
473
|
+
: null;
|
|
474
|
+
const totalInputTokens = loopSummary && typeof loopSummary.total_tokens === "number"
|
|
475
|
+
? loopSummary.total_tokens
|
|
476
|
+
: 0;
|
|
477
|
+
budgetBreaker.record({ inputTokens: totalInputTokens });
|
|
478
|
+
|
|
479
|
+
const diffFile = appendShadowDiff(projectRoot, {
|
|
480
|
+
event: "controller.shadow.diff",
|
|
481
|
+
request_id: messageId,
|
|
482
|
+
primary_mode: CONTROLLER_MODES.LEGACY,
|
|
483
|
+
candidate_mode: CONTROLLER_MODES.LOOP,
|
|
484
|
+
sampling_rate: sampling.rate,
|
|
485
|
+
primary: summarizeShadowPayload(handled.payload),
|
|
486
|
+
shadow: shadowResult && shadowResult.ok
|
|
487
|
+
? summarizeShadowPayload(shadowResult.payload)
|
|
488
|
+
: {
|
|
489
|
+
ok: false,
|
|
490
|
+
error: shadowResult && shadowResult.error ? String(shadowResult.error) : "shadow_run_failed",
|
|
491
|
+
},
|
|
492
|
+
side_effects_ok: assertion.ok,
|
|
493
|
+
side_effect_violations: assertion.violations,
|
|
494
|
+
});
|
|
495
|
+
shadowObserver.emit("controller.shadow.completed", {
|
|
496
|
+
applied_from_msg_id: messageId,
|
|
497
|
+
ok: shadowResult && shadowResult.ok === true,
|
|
498
|
+
diff_file: diffFile || "",
|
|
499
|
+
side_effects_ok: assertion.ok,
|
|
500
|
+
terminal_reason: loopSummary ? String(loopSummary.terminal_reason || "") : "",
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
167
505
|
if (useGlobalProjectRouter) {
|
|
168
506
|
const route = normalizeProjectRoute(handled.payload && handled.payload.project_route);
|
|
169
507
|
if (route) {
|
|
@@ -216,6 +554,17 @@ async function handlePromptRequest(options = {}) {
|
|
|
216
554
|
}
|
|
217
555
|
const opsResults = handled.opsResults || [];
|
|
218
556
|
log(`ok reply=${Boolean(payload.reply)} dispatch=${(payload.dispatch || []).length} ops=${(payload.ops || []).length}`);
|
|
557
|
+
controllerObserver.emit("controller.prompt_completed", {
|
|
558
|
+
applied_from_msg_id: messageId,
|
|
559
|
+
ok: true,
|
|
560
|
+
shadow_enabled: shadowEnabled,
|
|
561
|
+
shadow_ok: shadowResult ? shadowResult.ok === true : false,
|
|
562
|
+
dispatch_count: Array.isArray(payload.dispatch) ? payload.dispatch.length : 0,
|
|
563
|
+
ops_count: Array.isArray(payload.ops) ? payload.ops.length : 0,
|
|
564
|
+
terminal_reason: payload.loop && typeof payload.loop === "object"
|
|
565
|
+
? String(payload.loop.terminal_reason || "").trim()
|
|
566
|
+
: "",
|
|
567
|
+
});
|
|
219
568
|
socket.write(
|
|
220
569
|
`${JSON.stringify({
|
|
221
570
|
type: IPC_RESPONSE_TYPES.RESPONSE,
|
|
@@ -226,6 +575,12 @@ async function handlePromptRequest(options = {}) {
|
|
|
226
575
|
return true;
|
|
227
576
|
} catch (err) {
|
|
228
577
|
log(`error ${err.message || String(err)}`);
|
|
578
|
+
controllerObserver.emit("controller.prompt_completed", {
|
|
579
|
+
applied_from_msg_id: messageId,
|
|
580
|
+
ok: false,
|
|
581
|
+
error: err.message || String(err),
|
|
582
|
+
shadow_enabled: shadowEnabled,
|
|
583
|
+
});
|
|
229
584
|
socket.write(
|
|
230
585
|
`${JSON.stringify({
|
|
231
586
|
type: IPC_RESPONSE_TYPES.ERROR,
|
package/src/daemon/status.js
CHANGED
|
@@ -3,6 +3,7 @@ const path = require("path");
|
|
|
3
3
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
4
4
|
const { isMetaActive } = require("../bus/utils");
|
|
5
5
|
const { readReportSummary, countControllerInboxEntries } = require("../report/store");
|
|
6
|
+
const { readRecentLoopSummary } = require("../agent/loopObservability");
|
|
6
7
|
|
|
7
8
|
function readBus(projectRoot) {
|
|
8
9
|
const busPath = getUfooPaths(projectRoot).agentsFile;
|
|
@@ -180,6 +181,7 @@ function buildStatus(projectRoot, options = {}) {
|
|
|
180
181
|
controller: {
|
|
181
182
|
pending_total: controllerPendingTotal,
|
|
182
183
|
},
|
|
184
|
+
loop: readRecentLoopSummary(projectRoot),
|
|
183
185
|
cron: {
|
|
184
186
|
count: cronTasks.length,
|
|
185
187
|
tasks: cronTasks,
|
|
@@ -5,6 +5,7 @@ const os = require("os");
|
|
|
5
5
|
const path = require("path");
|
|
6
6
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
7
7
|
const { loadAgentsData } = require("../ufoo/agentsStore");
|
|
8
|
+
const { redactSecrets, redactString } = require("../providerapi/redactor");
|
|
8
9
|
|
|
9
10
|
const HISTORY_DEBUG = process.env.UFOO_HISTORY_DEBUG === "1";
|
|
10
11
|
const debugLog = (...args) => { if (HISTORY_DEBUG) console.error("[history]", ...args); };
|
|
@@ -446,10 +447,10 @@ function appendBusEntry(projectRoot, { seq, timestamp, publisher, target, messag
|
|
|
446
447
|
fromId: publisher,
|
|
447
448
|
to: nicknameMap.get(target) || target,
|
|
448
449
|
toId: target,
|
|
449
|
-
message,
|
|
450
|
+
message: redactString(message),
|
|
450
451
|
};
|
|
451
452
|
|
|
452
|
-
fs.appendFileSync(timelineFile, JSON.stringify(entry) + "\n", "utf8");
|
|
453
|
+
fs.appendFileSync(timelineFile, JSON.stringify(redactSecrets(entry)) + "\n", "utf8");
|
|
453
454
|
|
|
454
455
|
if (seq) {
|
|
455
456
|
const lock = acquireWatermarkLock(projectRoot);
|
|
@@ -504,10 +505,14 @@ function buildTimeline(projectRoot, { force = false } = {}) {
|
|
|
504
505
|
const lock = acquireWatermarkLock(projectRoot);
|
|
505
506
|
try {
|
|
506
507
|
if (force) {
|
|
507
|
-
const content = newEntries.map((e) => JSON.stringify(e)).join("\n") + (newEntries.length > 0 ? "\n" : "");
|
|
508
|
+
const content = newEntries.map((e) => JSON.stringify(redactSecrets(e))).join("\n") + (newEntries.length > 0 ? "\n" : "");
|
|
508
509
|
fs.writeFileSync(timelineFile, content, "utf8");
|
|
509
510
|
} else {
|
|
510
|
-
fs.appendFileSync(
|
|
511
|
+
fs.appendFileSync(
|
|
512
|
+
timelineFile,
|
|
513
|
+
newEntries.map((e) => JSON.stringify(redactSecrets(e))).join("\n") + "\n",
|
|
514
|
+
"utf8"
|
|
515
|
+
);
|
|
511
516
|
}
|
|
512
517
|
|
|
513
518
|
const prevCount = force ? 0 : (watermark.entryCount || 0);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const { ensureDir, appendJSONL, getTimestamp } = require("../bus/utils");
|
|
2
|
+
const { canonicalProjectRoot } = require("../projects/projectId");
|
|
3
|
+
const { getUfooPaths } = require("../ufoo/paths");
|
|
4
|
+
|
|
5
|
+
class MemoryManager {
|
|
6
|
+
constructor(projectRoot) {
|
|
7
|
+
// Phase 0 scaffolding only: this seam must stay dormant until loop/runtime
|
|
8
|
+
// wiring passes an explicit projectRoot into the memory tool path.
|
|
9
|
+
this.projectRoot = canonicalProjectRoot(projectRoot);
|
|
10
|
+
const paths = getUfooPaths(this.projectRoot);
|
|
11
|
+
this.memoryDir = paths.memoryDir;
|
|
12
|
+
this.memoryFile = paths.memoryFile;
|
|
13
|
+
ensureDir(this.memoryDir);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
addEntry(entry) {
|
|
17
|
+
appendJSONL(this.memoryFile, {
|
|
18
|
+
timestamp: getTimestamp(),
|
|
19
|
+
...entry,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = MemoryManager;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const REDACTED = "[REDACTED]";
|
|
4
|
+
const SENSITIVE_KEY_PATTERN = /(^|_|-)(authorization|accesstoken|access_token|refreshtoken|refresh_token|apikey|api_key|tokenhash|token_hash)$/i;
|
|
5
|
+
const BEARER_PATTERN = /\bBearer\s+[A-Za-z0-9._~+/=-]+\b/gi;
|
|
6
|
+
|
|
7
|
+
function isPlainObject(value) {
|
|
8
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isSensitiveKey(key = "") {
|
|
12
|
+
const normalized = String(key || "").replace(/[\s-]+/g, "_");
|
|
13
|
+
if (!normalized) return false;
|
|
14
|
+
if (SENSITIVE_KEY_PATTERN.test(normalized)) return true;
|
|
15
|
+
return /^token$/i.test(normalized);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function redactString(value) {
|
|
19
|
+
return String(value || "").replace(BEARER_PATTERN, "Bearer [REDACTED]");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function redactSecrets(value, options = {}) {
|
|
23
|
+
const seen = options._seen || new WeakMap();
|
|
24
|
+
|
|
25
|
+
if (typeof value === "string") {
|
|
26
|
+
return redactString(value);
|
|
27
|
+
}
|
|
28
|
+
if (!value || typeof value !== "object") {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
if (seen.has(value)) {
|
|
32
|
+
return seen.get(value);
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
const out = [];
|
|
36
|
+
seen.set(value, out);
|
|
37
|
+
for (const item of value) {
|
|
38
|
+
out.push(redactSecrets(item, { ...options, _seen: seen }));
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
if (!isPlainObject(value)) {
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const out = {};
|
|
47
|
+
seen.set(value, out);
|
|
48
|
+
for (const [key, entryValue] of Object.entries(value)) {
|
|
49
|
+
if (isSensitiveKey(key)) {
|
|
50
|
+
out[key] = REDACTED;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
out[key] = redactSecrets(entryValue, { ...options, _seen: seen });
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function redactJsonLine(value) {
|
|
59
|
+
return JSON.stringify(redactSecrets(value));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function redactUfooEvent(event) {
|
|
63
|
+
if (!event || typeof event !== "object") return event;
|
|
64
|
+
return redactSecrets(event);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function redactToolCallPayload(payload = {}) {
|
|
68
|
+
const input = payload && typeof payload === "object" ? payload : {};
|
|
69
|
+
return {
|
|
70
|
+
name: typeof input.name === "string" ? input.name : String(input.name || ""),
|
|
71
|
+
args: redactSecrets(input.args || input.arguments || {}),
|
|
72
|
+
tool_call_id: typeof input.tool_call_id === "string"
|
|
73
|
+
? input.tool_call_id
|
|
74
|
+
: (typeof input.toolCallId === "string" ? input.toolCallId : ""),
|
|
75
|
+
caller_tier: typeof input.caller_tier === "string" ? input.caller_tier : "",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
REDACTED,
|
|
81
|
+
redactSecrets,
|
|
82
|
+
redactJsonLine,
|
|
83
|
+
redactString,
|
|
84
|
+
isSensitiveKey,
|
|
85
|
+
redactUfooEvent,
|
|
86
|
+
redactToolCallPayload,
|
|
87
|
+
};
|