u-foo 1.9.7 → 2.1.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/bin/ufoo.js +5 -3
- 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 +10 -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/internalRunner.js +348 -3
- package/src/agent/loopObservability.js +190 -0
- package/src/agent/loopRuntime.js +457 -0
- package/src/agent/ptyRunner.js +8 -7
- 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
package/src/agent/ufooAgent.js
CHANGED
|
@@ -4,14 +4,14 @@ const { runCliAgent } = require("./cliRunner");
|
|
|
4
4
|
const { normalizeCliOutput } = require("./normalizeOutput");
|
|
5
5
|
const { buildStatus } = require("../daemon/status");
|
|
6
6
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
resolveCompletionUrl,
|
|
10
|
-
resolveAnthropicMessagesUrl,
|
|
11
|
-
} = require("../code/nativeRunner");
|
|
12
|
-
const { DEFAULT_ASSISTANT_TIMEOUT_MS } = require("../assistant/constants");
|
|
7
|
+
const { normalizeGateRouterResult } = require("../controller/gateRouter");
|
|
8
|
+
const { sendUpstreamPrompt } = require("./upstreamTransport");
|
|
13
9
|
const { normalizeAgentTypeAlias } = require("../bus/utils");
|
|
14
10
|
const { listProjectRuntimes, isGlobalControllerProjectRoot } = require("../projects");
|
|
11
|
+
const {
|
|
12
|
+
CONTROLLER_MODES,
|
|
13
|
+
resolveControllerMode,
|
|
14
|
+
} = require("../controller/flags");
|
|
15
15
|
|
|
16
16
|
function loadSessionState(projectRoot) {
|
|
17
17
|
const dir = getUfooPaths(projectRoot).agentDir;
|
|
@@ -405,19 +405,23 @@ function buildGlobalProjectRouterContext(projectRoot, options = {}) {
|
|
|
405
405
|
|
|
406
406
|
function buildSystemPrompt(context, options = {}) {
|
|
407
407
|
const mode = String(options.routingMode || (context && context.mode) || "").trim().toLowerCase();
|
|
408
|
+
const loopRuntime = options.loopRuntime && options.loopRuntime.enabled ? options.loopRuntime : null;
|
|
409
|
+
const controllerMode = String(options.controllerMode || CONTROLLER_MODES.LEGACY);
|
|
408
410
|
if (mode === "global-router") {
|
|
411
|
+
const schemaLines = [
|
|
412
|
+
"{",
|
|
413
|
+
' "reply": "string",',
|
|
414
|
+
];
|
|
415
|
+
schemaLines.push(' "project_route": {"project_root":"absolute-path","project_name":"string","prompt":"string","reason":"string"},');
|
|
416
|
+
schemaLines.push(' "dispatch": [],');
|
|
417
|
+
schemaLines.push(' "ops": []');
|
|
418
|
+
schemaLines.push("}");
|
|
409
419
|
return [
|
|
410
420
|
"You are ufoo-agent, the global project router for `ufoo chat -g`.",
|
|
411
421
|
"You run inside the home-scoped controller runtime and must choose the right project before any project-local routing happens.",
|
|
412
422
|
"Return ONLY valid JSON. No extra text.",
|
|
413
423
|
"Schema:",
|
|
414
|
-
|
|
415
|
-
' "reply": "string",',
|
|
416
|
-
` "assistant_call": {"kind":"explore|bash|mixed","task":"string","context":"optional","expect":"optional","provider":"codex|claude|ufoo (optional)","model":"optional","timeout_ms":${DEFAULT_ASSISTANT_TIMEOUT_MS}},`,
|
|
417
|
-
' "project_route": {"project_root":"absolute-path","project_name":"string","prompt":"string","reason":"string"},',
|
|
418
|
-
' "dispatch": [],',
|
|
419
|
-
' "ops": []',
|
|
420
|
-
"}",
|
|
424
|
+
...schemaLines,
|
|
421
425
|
"Rules:",
|
|
422
426
|
"- Use project_route when the request should be handed to one specific registered project.",
|
|
423
427
|
"- project_route.prompt should usually preserve the user request, optionally rewritten only to clarify project context for the next router.",
|
|
@@ -427,7 +431,7 @@ function buildSystemPrompt(context, options = {}) {
|
|
|
427
431
|
"- The target project's ufoo-agent will do the second-hop routing to a concrete agent.",
|
|
428
432
|
"- If the user asks for a global comparison, registry overview, or other controller-level answer, reply directly and omit project_route.",
|
|
429
433
|
"- If no registered project is a clear match, reply with a concise clarification request or tell the user to use /open <path> first.",
|
|
430
|
-
|
|
434
|
+
`- Controller mode=${controllerMode}. Do not emit assistant_call or ops.assistant_call; the legacy helper path has been removed.`,
|
|
431
435
|
"- Prefer continuity: if a project's recent prompt history clearly matches the current request, route there.",
|
|
432
436
|
"",
|
|
433
437
|
"Context: registered projects and project activity summaries:",
|
|
@@ -438,20 +442,55 @@ function buildSystemPrompt(context, options = {}) {
|
|
|
438
442
|
const hasAgents = context.agents && context.agents.length > 0;
|
|
439
443
|
const agentGuidance = hasAgents
|
|
440
444
|
? ""
|
|
441
|
-
: "\n- IMPORTANT: No coding agents are currently online
|
|
445
|
+
: "\n- IMPORTANT: No coding agents are currently online.\n- Use ops.launch only when a persistent coding-agent session is necessary; otherwise reply with a clarification or route later.";
|
|
442
446
|
|
|
443
|
-
|
|
447
|
+
if (loopRuntime) {
|
|
448
|
+
return [
|
|
449
|
+
"You are ufoo-agent, a headless routing controller running in limited loop mode.",
|
|
450
|
+
"Return ONLY valid JSON. No extra text.",
|
|
451
|
+
"Loop schema:",
|
|
452
|
+
"{",
|
|
453
|
+
' "reply": "string",',
|
|
454
|
+
' "done": true,',
|
|
455
|
+
' "dispatch": [{"target":"broadcast|<agent-id>|<nickname>","message":"string","injection_mode":"immediate|queued (optional)","source":"optional"}],',
|
|
456
|
+
' "ops": [{"action":"launch|close|rename|role|cron","agent":"codex|claude|ucode","count":1,"agent_id":"id","nickname":"optional"}],',
|
|
457
|
+
' "tool_call": {"id":"optional","name":"dispatch_message|ack_bus|launch_agent","arguments":{}}',
|
|
458
|
+
"}",
|
|
459
|
+
"Loop rules:",
|
|
460
|
+
"- Use tool_call only when the controller must execute a control-plane action before deciding the final answer.",
|
|
461
|
+
"- When returning tool_call, set done=false and keep dispatch/ops empty for that round.",
|
|
462
|
+
"- Use dispatch_message for direct bus delivery, ack_bus for controller queue acknowledgement, and launch_agent for bounded worker launches.",
|
|
463
|
+
"- When you have enough information, omit tool_call and return the final reply/dispatch/ops with done=true.",
|
|
464
|
+
"- Do not emit assistant_call or ops.assistant_call; that legacy helper path has been removed.",
|
|
465
|
+
`- Round budget: maxRounds=${loopRuntime.maxRounds || ""}, remainingToolCalls=${loopRuntime.remainingToolCalls || 0}.`,
|
|
466
|
+
agentGuidance,
|
|
467
|
+
"",
|
|
468
|
+
"Context: online agents and recent bus events:",
|
|
469
|
+
JSON.stringify(context),
|
|
470
|
+
].join("\n");
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const baseHeader = [
|
|
444
474
|
"You are ufoo-agent, a headless routing controller.",
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
475
|
+
`Controller mode=${controllerMode}. The legacy assistant_call / helper-agent path has been removed; route via dispatch/ops or reply directly.`,
|
|
476
|
+
];
|
|
477
|
+
const schemaLines = [
|
|
448
478
|
"{",
|
|
449
479
|
' "reply": "string",',
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
480
|
+
];
|
|
481
|
+
schemaLines.push(' "dispatch": [{"target":"broadcast|<agent-id>|<nickname>","message":"string","injection_mode":"immediate|queued (optional)","source":"optional"}],');
|
|
482
|
+
schemaLines.push(' "ops": [{"action":"launch|close|rename|role|cron","agent":"codex|claude|ucode","count":1,"agent_id":"id","nickname":"optional","prompt_profile":"profile-id (for role)","operation":"start|list|stop","every":"30m","interval_ms":1800000,"at":"YYYY-MM-DD HH:mm","once_at_ms":1700000000000,"target":"agent-id|nickname|csv","targets":["agent-id"],"title":"optional short title","prompt":"message","id":"task-id|all"}],');
|
|
483
|
+
schemaLines.push(' "disambiguate": {"prompt":"string","candidates":[{"agent_id":"id","reason":"string"}]}');
|
|
484
|
+
if (controllerMode === CONTROLLER_MODES.LOOP) {
|
|
485
|
+
schemaLines.push(' "upgrade_to_loop_router": true');
|
|
486
|
+
}
|
|
487
|
+
schemaLines.push("}");
|
|
488
|
+
|
|
489
|
+
return [
|
|
490
|
+
...baseHeader,
|
|
491
|
+
"Return ONLY valid JSON. No extra text.",
|
|
492
|
+
"Schema:",
|
|
493
|
+
...schemaLines,
|
|
455
494
|
"Rules:",
|
|
456
495
|
"- target must be 'broadcast', concrete agent-id, or a known nickname",
|
|
457
496
|
"- If multiple possible agents, use disambiguate with candidates and no dispatch.",
|
|
@@ -461,16 +500,20 @@ function buildSystemPrompt(context, options = {}) {
|
|
|
461
500
|
"- To check scheduled tasks, use ops.cron with operation=list.",
|
|
462
501
|
"- To stop scheduled tasks, use ops.cron with operation=stop and id (or id=all).",
|
|
463
502
|
"- To assign a preset role to an existing agent, use ops.role with target (agent-id or nickname) and prompt_profile (profile id or alias). Available profiles: discovery-facilitator, scope-challenger, system-architect, implementation-lead, frontend-refiner, design-critic, review-critic, qa-driver, debug-investigator, release-coordinator, task-breakdown, research-scan, rapid-prototype.",
|
|
464
|
-
"-
|
|
465
|
-
"-
|
|
466
|
-
"- Prefer assistant_call over launching coding agents when the task is short-lived.",
|
|
503
|
+
"- Do not emit assistant_call or ops.assistant_call; that schema has been removed and the daemon will ignore it if emitted.",
|
|
504
|
+
"- For short-lived exploration, prefer a dispatch to an online agent or reply with a clarification.",
|
|
467
505
|
"- Primary routing signal is semantic continuity from agent_prompt_history; prefer the agent that already handled similar prompts.",
|
|
468
506
|
"- Launch a new coding agent when the request is a new topic without clear ownership in existing histories.",
|
|
469
507
|
"- dispatch.injection_mode defaults to immediate when omitted.",
|
|
470
508
|
"- Use queued only when routing a chat-dialog request that is clearly a new unrelated task for an agent whose recent prompt history shows a different ongoing thread.",
|
|
471
509
|
"- If the new request strongly continues the target agent's recent prompt history, keep injection_mode immediate even when that agent is busy.",
|
|
472
510
|
"- Manual @agent sends in ufoo chat are handled outside this router and remain immediate; do not model them here.",
|
|
473
|
-
|
|
511
|
+
...(controllerMode === CONTROLLER_MODES.LOOP
|
|
512
|
+
? [
|
|
513
|
+
"- When single-shot routing is insufficient and the controller should continue with tool-assisted exploration, set upgrade_to_loop_router=true.",
|
|
514
|
+
"- If upgrade_to_loop_router=true, keep dispatch/ops empty for this response.",
|
|
515
|
+
]
|
|
516
|
+
: []),
|
|
474
517
|
"- If no action needed, return reply with empty dispatch/ops.",
|
|
475
518
|
agentGuidance,
|
|
476
519
|
"",
|
|
@@ -508,6 +551,34 @@ function buildHistoryPrompt(history) {
|
|
|
508
551
|
return lines.join("\n");
|
|
509
552
|
}
|
|
510
553
|
|
|
554
|
+
function buildRouteAgentSystemPrompt(context, options = {}) {
|
|
555
|
+
return [
|
|
556
|
+
"You are ufoo-agent gate_router, the front-door router for pure delegation requests.",
|
|
557
|
+
"Return ONLY valid JSON. No markdown or extra text.",
|
|
558
|
+
"Schema:",
|
|
559
|
+
"{",
|
|
560
|
+
' "decision": "direct_dispatch|upgrade_to_main_router",',
|
|
561
|
+
' "target": "broadcast|<agent-id>|<nickname>|unknown",',
|
|
562
|
+
' "message": "string",',
|
|
563
|
+
' "confidence": 0.0,',
|
|
564
|
+
' "reason": "string",',
|
|
565
|
+
' "injection_mode": "immediate|queued"',
|
|
566
|
+
"}",
|
|
567
|
+
"Rules:",
|
|
568
|
+
"- Every request reaches you first. Decide whether to direct_dispatch immediately or upgrade.",
|
|
569
|
+
"- Use decision=direct_dispatch only when a single target is clear and no richer orchestration is needed.",
|
|
570
|
+
"- If the request needs repo work, richer controller context, or the best target is unclear, return decision=upgrade_to_main_router.",
|
|
571
|
+
"- Do not decide loop_router here. Main_router will decide whether a later upgrade to loop_router is necessary.",
|
|
572
|
+
"- Use only agent IDs or nicknames that appear in context.",
|
|
573
|
+
"- Preserve the user request in message unless a small clarification helps the chosen target.",
|
|
574
|
+
"- Prefer continuity from agent_prompt_history when one agent already owns the thread.",
|
|
575
|
+
"- Use queued only when the user is clearly starting a new unrelated thread for a busy agent.",
|
|
576
|
+
"",
|
|
577
|
+
"Context: online agents and recent bus events:",
|
|
578
|
+
JSON.stringify(context),
|
|
579
|
+
].join("\n");
|
|
580
|
+
}
|
|
581
|
+
|
|
511
582
|
function extractNickname(prompt) {
|
|
512
583
|
if (!prompt) return "";
|
|
513
584
|
const patterns = [
|
|
@@ -535,106 +606,49 @@ function stripMarkdownFence(text = "") {
|
|
|
535
606
|
return raw;
|
|
536
607
|
}
|
|
537
608
|
|
|
538
|
-
function
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
609
|
+
async function runNativeRouterCall({
|
|
610
|
+
projectRoot,
|
|
611
|
+
prompt,
|
|
612
|
+
systemPrompt,
|
|
613
|
+
provider: requestedProvider,
|
|
614
|
+
model: requestedModel,
|
|
615
|
+
timeoutMs = 120000,
|
|
616
|
+
}) {
|
|
617
|
+
return sendUpstreamPrompt({
|
|
618
|
+
projectRoot,
|
|
619
|
+
prompt,
|
|
620
|
+
systemPrompt,
|
|
621
|
+
provider: requestedProvider,
|
|
548
622
|
model: requestedModel,
|
|
623
|
+
timeoutMs,
|
|
549
624
|
});
|
|
550
|
-
|
|
551
|
-
const requestModel = String(runtime.model || "").trim();
|
|
552
|
-
if (!requestModel) {
|
|
553
|
-
return { ok: false, error: "ucode model is not configured" };
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const isAnthropic = runtime.transport === "anthropic-messages";
|
|
557
|
-
const url = isAnthropic
|
|
558
|
-
? resolveAnthropicMessagesUrl(runtime.baseUrl)
|
|
559
|
-
: resolveCompletionUrl(runtime.baseUrl);
|
|
560
|
-
|
|
561
|
-
if (!url) {
|
|
562
|
-
return { ok: false, error: "ucode baseUrl is not configured" };
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const headers = { "content-type": "application/json" };
|
|
566
|
-
let body;
|
|
567
|
-
|
|
568
|
-
if (isAnthropic) {
|
|
569
|
-
headers["anthropic-version"] = "2023-06-01";
|
|
570
|
-
if (runtime.apiKey) headers["x-api-key"] = runtime.apiKey;
|
|
571
|
-
body = JSON.stringify({
|
|
572
|
-
model: requestModel,
|
|
573
|
-
max_tokens: 4096,
|
|
574
|
-
system: String(systemPrompt || ""),
|
|
575
|
-
messages: [{ role: "user", content: String(prompt || "") }],
|
|
576
|
-
temperature: 0,
|
|
577
|
-
});
|
|
578
|
-
} else {
|
|
579
|
-
if (runtime.apiKey) headers.authorization = `Bearer ${runtime.apiKey}`;
|
|
580
|
-
const messages = [];
|
|
581
|
-
if (systemPrompt) messages.push({ role: "system", content: String(systemPrompt) });
|
|
582
|
-
messages.push({ role: "user", content: String(prompt || "") });
|
|
583
|
-
body = JSON.stringify({
|
|
584
|
-
model: requestModel,
|
|
585
|
-
messages,
|
|
586
|
-
temperature: 0,
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
const controller = new AbortController();
|
|
591
|
-
const timer = setTimeout(() => { try { controller.abort(); } catch {} }, timeoutMs);
|
|
592
|
-
|
|
593
|
-
try {
|
|
594
|
-
const response = await fetch(url, {
|
|
595
|
-
method: "POST",
|
|
596
|
-
headers,
|
|
597
|
-
body,
|
|
598
|
-
signal: controller.signal,
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
if (!response.ok) {
|
|
602
|
-
const errBody = await response.text().catch(() => "");
|
|
603
|
-
return { ok: false, error: `provider request failed (${response.status}): ${clipText(errBody)}` };
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
const data = await response.json();
|
|
607
|
-
|
|
608
|
-
let text = "";
|
|
609
|
-
if (isAnthropic) {
|
|
610
|
-
const content = Array.isArray(data.content) ? data.content : [];
|
|
611
|
-
text = content
|
|
612
|
-
.filter((item) => item && item.type === "text")
|
|
613
|
-
.map((item) => String(item.text || ""))
|
|
614
|
-
.join("");
|
|
615
|
-
} else {
|
|
616
|
-
const choice = data.choices && data.choices[0];
|
|
617
|
-
text = choice && choice.message && typeof choice.message.content === "string"
|
|
618
|
-
? choice.message.content
|
|
619
|
-
: "";
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
return { ok: true, output: text.trim() };
|
|
623
|
-
} catch (err) {
|
|
624
|
-
const message = err && err.message ? err.message : "native router call failed";
|
|
625
|
-
return { ok: false, error: message };
|
|
626
|
-
} finally {
|
|
627
|
-
clearTimeout(timer);
|
|
628
|
-
}
|
|
629
625
|
}
|
|
630
626
|
|
|
631
|
-
async function runUfooAgent({
|
|
627
|
+
async function runUfooAgent({
|
|
628
|
+
projectRoot,
|
|
629
|
+
prompt,
|
|
630
|
+
provider,
|
|
631
|
+
model,
|
|
632
|
+
routingMode = "",
|
|
633
|
+
routingContext = null,
|
|
634
|
+
loopRuntime = null,
|
|
635
|
+
controllerMode = null,
|
|
636
|
+
}) {
|
|
632
637
|
const state = loadSessionState(projectRoot);
|
|
633
638
|
const mode = String(routingMode || (routingContext && routingContext.mode) || "").trim().toLowerCase();
|
|
639
|
+
const resolvedControllerMode = String(
|
|
640
|
+
controllerMode
|
|
641
|
+
|| resolveControllerMode({ projectRoot })
|
|
642
|
+
|| CONTROLLER_MODES.LEGACY,
|
|
643
|
+
);
|
|
634
644
|
const bus = routingContext || (mode === "global-router"
|
|
635
645
|
? buildGlobalProjectRouterContext(projectRoot)
|
|
636
646
|
: loadBusSummary(projectRoot));
|
|
637
|
-
const systemPrompt = buildSystemPrompt(bus, {
|
|
647
|
+
const systemPrompt = buildSystemPrompt(bus, {
|
|
648
|
+
routingMode: mode,
|
|
649
|
+
loopRuntime,
|
|
650
|
+
controllerMode: resolvedControllerMode,
|
|
651
|
+
});
|
|
638
652
|
const history = loadHistory(projectRoot);
|
|
639
653
|
const historyPrompt = buildHistoryPrompt(history);
|
|
640
654
|
const fullPrompt = historyPrompt ? `${historyPrompt}User: ${prompt}` : prompt;
|
|
@@ -722,4 +736,48 @@ async function runUfooAgent({ projectRoot, prompt, provider, model, routingMode
|
|
|
722
736
|
return { ok: true, payload };
|
|
723
737
|
}
|
|
724
738
|
|
|
725
|
-
|
|
739
|
+
async function runUfooRouteAgent({
|
|
740
|
+
projectRoot,
|
|
741
|
+
prompt,
|
|
742
|
+
provider = "ucode",
|
|
743
|
+
model = "",
|
|
744
|
+
timeoutMs = 5000,
|
|
745
|
+
}) {
|
|
746
|
+
const bus = loadBusSummary(projectRoot);
|
|
747
|
+
const systemPrompt = buildRouteAgentSystemPrompt(bus);
|
|
748
|
+
const history = loadHistory(projectRoot);
|
|
749
|
+
const historyPrompt = buildHistoryPrompt(history);
|
|
750
|
+
const fullPrompt = historyPrompt ? `${historyPrompt}User: ${prompt}` : prompt;
|
|
751
|
+
|
|
752
|
+
const res = await runNativeRouterCall({
|
|
753
|
+
projectRoot,
|
|
754
|
+
prompt: fullPrompt,
|
|
755
|
+
systemPrompt,
|
|
756
|
+
provider,
|
|
757
|
+
model,
|
|
758
|
+
timeoutMs,
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
if (!res.ok) {
|
|
762
|
+
return { ok: false, error: res.error || "gate_router failed" };
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const text = stripMarkdownFence(String(res.output || "").trim());
|
|
766
|
+
let payload = null;
|
|
767
|
+
try {
|
|
768
|
+
payload = JSON.parse(text);
|
|
769
|
+
} catch {
|
|
770
|
+
return { ok: false, error: "gate_router returned non-JSON output" };
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
return {
|
|
774
|
+
ok: true,
|
|
775
|
+
route: normalizeGateRouterResult(payload, prompt),
|
|
776
|
+
meta: {
|
|
777
|
+
provider: String(res.provider || ""),
|
|
778
|
+
model: String(res.model || ""),
|
|
779
|
+
},
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
module.exports = { runUfooAgent, runUfooRouteAgent };
|