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.
Files changed (69) hide show
  1. package/package.json +2 -4
  2. package/src/agent/claudeEventTranslator.js +267 -0
  3. package/src/agent/claudeOauthTokenReader.js +52 -0
  4. package/src/agent/claudeThreadProvider.js +343 -0
  5. package/src/agent/cliRunner.js +4 -16
  6. package/src/agent/codexEventTranslator.js +78 -0
  7. package/src/agent/codexThreadProvider.js +181 -0
  8. package/src/agent/controllerToolExecutor.js +233 -0
  9. package/src/agent/credentials/claude.js +324 -0
  10. package/src/agent/credentials/codex.js +203 -0
  11. package/src/agent/credentials/index.js +106 -0
  12. package/src/agent/defaultBootstrap.js +128 -5
  13. package/src/agent/internalRunner.js +333 -2
  14. package/src/agent/loopObservability.js +190 -0
  15. package/src/agent/loopRuntime.js +457 -0
  16. package/src/agent/ufooAgent.js +178 -120
  17. package/src/agent/upstreamTransport.js +464 -0
  18. package/src/bus/utils.js +3 -2
  19. package/src/chat/dashboardView.js +51 -1
  20. package/src/chat/index.js +3 -1
  21. package/src/config.js +53 -17
  22. package/src/controller/flags.js +160 -0
  23. package/src/controller/gateRouter.js +201 -0
  24. package/src/controller/routerFastPath.js +22 -0
  25. package/src/controller/shadowGuard.js +280 -0
  26. package/src/daemon/index.js +2 -3
  27. package/src/daemon/promptLoop.js +33 -224
  28. package/src/daemon/promptRequest.js +360 -5
  29. package/src/daemon/status.js +2 -0
  30. package/src/history/inputTimeline.js +9 -4
  31. package/src/memory/index.js +24 -0
  32. package/src/providerapi/redactor.js +87 -0
  33. package/src/providerapi/shadowDiff.js +174 -0
  34. package/src/report/store.js +4 -3
  35. package/src/tools/handlers/ackBus.js +26 -0
  36. package/src/tools/handlers/common.js +64 -0
  37. package/src/tools/handlers/dispatchMessage.js +81 -0
  38. package/src/tools/handlers/listAgents.js +14 -0
  39. package/src/tools/handlers/readBusSummary.js +34 -0
  40. package/src/tools/handlers/readOpenDecisions.js +26 -0
  41. package/src/tools/handlers/readProjectRegistry.js +20 -0
  42. package/src/tools/handlers/readPromptHistory.js +123 -0
  43. package/src/tools/handlers/tier2.js +134 -0
  44. package/src/tools/index.js +55 -0
  45. package/src/tools/registry.js +69 -0
  46. package/src/tools/schemaFixtures.js +415 -0
  47. package/src/tools/tier0/listAgents.js +14 -0
  48. package/src/tools/tier0/readBusSummary.js +14 -0
  49. package/src/tools/tier0/readOpenDecisions.js +14 -0
  50. package/src/tools/tier0/readProjectRegistry.js +14 -0
  51. package/src/tools/tier0/readPromptHistory.js +14 -0
  52. package/src/tools/tier1/ackBus.js +14 -0
  53. package/src/tools/tier1/dispatchMessage.js +14 -0
  54. package/src/tools/tier1/routeAgent.js +14 -0
  55. package/src/tools/tier2/closeAgent.js +14 -0
  56. package/src/tools/tier2/launchAgent.js +14 -0
  57. package/src/tools/tier2/manageCron.js +14 -0
  58. package/src/tools/tier2/renameAgent.js +14 -0
  59. package/src/tools/types.js +75 -0
  60. package/src/tools/unimplemented.js +13 -0
  61. package/src/ufoo/paths.js +4 -0
  62. package/bin/ufoo-assistant-agent.js +0 -5
  63. package/bin/ufoo-engine.js +0 -25
  64. package/src/assistant/agent.js +0 -261
  65. package/src/assistant/bridge.js +0 -178
  66. package/src/assistant/constants.js +0 -15
  67. package/src/assistant/engine.js +0 -252
  68. package/src/assistant/stdio.js +0 -58
  69. package/src/assistant/ufooEngineCli.js +0 -312
@@ -1,24 +1,12 @@
1
1
  const { spawn } = require("child_process");
2
2
  const { randomUUID } = require("crypto");
3
- const { DEFAULT_ASSISTANT_TIMEOUT_MS } = require("../assistant/constants");
3
+
4
+ const DEFAULT_CLI_TIMEOUT_MS = 600000;
4
5
 
5
6
  const ROUTER_JSON_SCHEMA = JSON.stringify({
6
7
  type: "object",
7
8
  properties: {
8
9
  reply: { type: "string" },
9
- assistant_call: {
10
- type: "object",
11
- properties: {
12
- kind: { type: "string", enum: ["explore", "bash", "mixed"] },
13
- task: { type: "string" },
14
- context: { type: "string" },
15
- expect: { type: "string" },
16
- provider: { type: "string" },
17
- model: { type: "string" },
18
- timeout_ms: { type: "integer" },
19
- },
20
- required: ["task"],
21
- },
22
10
  dispatch: {
23
11
  type: "array",
24
12
  items: {
@@ -622,7 +610,7 @@ async function runCliAgent(params) {
622
610
  cwd: params.cwd,
623
611
  env,
624
612
  input: stdin,
625
- timeoutMs: params.timeoutMs || DEFAULT_ASSISTANT_TIMEOUT_MS,
613
+ timeoutMs: params.timeoutMs || DEFAULT_CLI_TIMEOUT_MS,
626
614
  onStdout: codexParser ? (chunk) => codexParser.onChunk(chunk) : null,
627
615
  signal: params.signal,
628
616
  });
@@ -685,7 +673,7 @@ async function runCliAgent(params) {
685
673
  cwd: params.cwd,
686
674
  env,
687
675
  input: retryStdin,
688
- timeoutMs: params.timeoutMs || DEFAULT_ASSISTANT_TIMEOUT_MS,
676
+ timeoutMs: params.timeoutMs || DEFAULT_CLI_TIMEOUT_MS,
689
677
  onStdout: retryParser ? (chunk) => retryParser.onChunk(chunk) : null,
690
678
  signal: params.signal,
691
679
  });
@@ -0,0 +1,78 @@
1
+ function getTextFromItem(item) {
2
+ if (!item || typeof item !== "object") return "";
3
+ if (typeof item.text === "string") return item.text;
4
+ if (item.item && typeof item.item.text === "string") return item.item.text;
5
+ if (Array.isArray(item.content)) {
6
+ return item.content
7
+ .map((entry) => (entry && typeof entry.text === "string" ? entry.text : ""))
8
+ .join("");
9
+ }
10
+ return "";
11
+ }
12
+
13
+ function normalizeCodexEvent(event = {}) {
14
+ const type = String(event.type || "").trim();
15
+ if (!type) return null;
16
+
17
+ if (type === "thread.started") {
18
+ return { type: "thread_started", threadId: event.thread_id || event.threadId || "" };
19
+ }
20
+
21
+ if (type === "turn.started") {
22
+ return { type: "turn_started", turnId: event.turn_id || event.turnId || "" };
23
+ }
24
+
25
+ if (type === "turn.completed") {
26
+ return {
27
+ type: "turn_completed",
28
+ turnId: event.turn_id || event.turnId || "",
29
+ usage: event.usage || null,
30
+ };
31
+ }
32
+
33
+ if (type === "turn.failed") {
34
+ const error = event.error || {};
35
+ return {
36
+ type: "turn_failed",
37
+ turnId: event.turn_id || event.turnId || "",
38
+ error: typeof error.message === "string" ? error.message : String(error || "turn failed"),
39
+ };
40
+ }
41
+
42
+ if (type === "item.completed") {
43
+ const item = event.item || {};
44
+ const itemType = String(item.type || "").trim();
45
+ const text = getTextFromItem(item);
46
+
47
+ if (itemType === "message" || itemType === "assistant_message" || text) {
48
+ return {
49
+ type: "text_delta",
50
+ delta: text,
51
+ itemType,
52
+ };
53
+ }
54
+
55
+ if (itemType === "tool_call") {
56
+ return {
57
+ type: "tool_call",
58
+ name: item.name || "",
59
+ toolCallId: item.id || item.tool_call_id || "",
60
+ args: item.arguments || item.args || {},
61
+ };
62
+ }
63
+
64
+ if (itemType === "tool_result") {
65
+ return {
66
+ type: "tool_result",
67
+ toolCallId: item.tool_call_id || item.id || "",
68
+ output: item.output,
69
+ };
70
+ }
71
+ }
72
+
73
+ return null;
74
+ }
75
+
76
+ module.exports = {
77
+ normalizeCodexEvent,
78
+ };
@@ -0,0 +1,181 @@
1
+ const { normalizeCodexEvent } = require("./codexEventTranslator");
2
+ const { redactUfooEvent } = require("../providerapi/redactor");
3
+ const { sendUpstreamPrompt } = require("./upstreamTransport");
4
+
5
+ function resolveCodexSdk() {
6
+ try {
7
+ // Optional dependency during Phase 1a seam work.
8
+ // eslint-disable-next-line global-require, import/no-extraneous-dependencies
9
+ return require("@openai/codex-sdk");
10
+ } catch (err) {
11
+ const error = new Error("Codex SDK seam enabled but @openai/codex-sdk is not installed");
12
+ error.code = "CODEX_SDK_UNAVAILABLE";
13
+ error.cause = err;
14
+ throw error;
15
+ }
16
+ }
17
+
18
+ function defaultCodexStreamFactory({
19
+ sdk,
20
+ model,
21
+ cwd,
22
+ extraArgs = [],
23
+ threadId = "",
24
+ input,
25
+ opts = {},
26
+ }) {
27
+ if (!sdk || typeof sdk.runStreamed !== "function") {
28
+ throw new Error("Codex SDK seam requires runStreamed support");
29
+ }
30
+ const { history, ...sdkOpts } = opts;
31
+ void history;
32
+ return sdk.runStreamed({
33
+ model,
34
+ cwd,
35
+ extraArgs,
36
+ threadId,
37
+ input,
38
+ ...sdkOpts,
39
+ });
40
+ }
41
+
42
+ function createThreadId() {
43
+ return `codex-thread-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
44
+ }
45
+
46
+ async function* defaultCodexTransportStreamFactory({
47
+ model,
48
+ cwd,
49
+ threadId = "",
50
+ input,
51
+ opts = {},
52
+ }) {
53
+ const nextThreadId = String(threadId || "").trim() || createThreadId();
54
+ const result = await sendUpstreamPrompt({
55
+ projectRoot: cwd,
56
+ provider: "codex",
57
+ model,
58
+ prompt: String(input || ""),
59
+ messages: Array.isArray(opts.history) ? opts.history : [],
60
+ timeoutMs: Number.isFinite(Number(opts.timeoutMs)) ? Number(opts.timeoutMs) : 120000,
61
+ });
62
+ if (!result.ok) {
63
+ const err = new Error(result.error || "Codex upstream request failed");
64
+ err.code = "CODEX_UPSTREAM_FAILED";
65
+ throw err;
66
+ }
67
+
68
+ yield { type: "thread.started", thread_id: nextThreadId };
69
+ yield {
70
+ type: "item.completed",
71
+ item: { type: "message", text: String(result.output || "") },
72
+ };
73
+ yield {
74
+ type: "turn.completed",
75
+ turn_id: `turn-${Date.now().toString(36)}`,
76
+ usage: result.usage || null,
77
+ };
78
+ }
79
+
80
+ class CodexSdkThread {
81
+ constructor({
82
+ model = "",
83
+ cwd = "",
84
+ extraArgs = [],
85
+ streamFactory = defaultCodexStreamFactory,
86
+ sdk,
87
+ tools = [],
88
+ } = {}) {
89
+ this.id = "";
90
+ this.model = model;
91
+ this.cwd = cwd;
92
+ this.extraArgs = Array.isArray(extraArgs) ? extraArgs.slice() : [];
93
+ this.streamFactory = streamFactory;
94
+ this.sdk = sdk;
95
+ this.tools = Array.isArray(tools) ? tools.slice() : [];
96
+ this.messages = [];
97
+ }
98
+
99
+ async *runStreamed(input, opts = {}) {
100
+ const mergedOpts = { ...opts };
101
+ if (this.tools.length > 0 && !Array.isArray(mergedOpts.tools)) {
102
+ mergedOpts.tools = this.tools.slice();
103
+ }
104
+ mergedOpts.history = this.messages.slice();
105
+ const stream = this.streamFactory({
106
+ sdk: this.sdk,
107
+ model: this.model,
108
+ cwd: this.cwd,
109
+ extraArgs: this.extraArgs,
110
+ threadId: this.id,
111
+ input,
112
+ opts: mergedOpts,
113
+ });
114
+ let outputText = "";
115
+ for await (const rawEvent of stream) {
116
+ const normalized = normalizeCodexEvent(rawEvent);
117
+ if (!normalized) continue;
118
+ if (normalized.type === "thread_started" && normalized.threadId) {
119
+ this.id = normalized.threadId;
120
+ }
121
+ if (normalized.type === "text_delta" && normalized.delta) {
122
+ outputText += normalized.delta;
123
+ }
124
+ yield redactUfooEvent(normalized);
125
+ }
126
+ this.messages.push({ role: "user", content: String(input || "") });
127
+ this.messages.push({ role: "assistant", content: outputText });
128
+ }
129
+
130
+ async close() {
131
+ return undefined;
132
+ }
133
+ }
134
+
135
+ class CodexThreadProvider {
136
+ constructor({
137
+ model = "",
138
+ cwd = "",
139
+ extraArgs = [],
140
+ streamFactory = defaultCodexStreamFactory,
141
+ sdk,
142
+ tools = [],
143
+ } = {}) {
144
+ this.model = model;
145
+ this.cwd = cwd;
146
+ this.extraArgs = Array.isArray(extraArgs) ? extraArgs.slice() : [];
147
+ this.streamFactory = streamFactory;
148
+ this.sdk = sdk || resolveCodexSdk();
149
+ this.tools = Array.isArray(tools) ? tools.slice() : [];
150
+ }
151
+
152
+ startThread() {
153
+ return new CodexSdkThread({
154
+ model: this.model,
155
+ cwd: this.cwd,
156
+ extraArgs: this.extraArgs,
157
+ streamFactory: this.streamFactory,
158
+ sdk: this.sdk,
159
+ tools: this.tools,
160
+ });
161
+ }
162
+
163
+ resumeThread(threadId = "") {
164
+ const thread = this.startThread();
165
+ thread.id = String(threadId || "").trim();
166
+ return thread;
167
+ }
168
+ }
169
+
170
+ function createCodexThreadProvider(options = {}) {
171
+ return new CodexThreadProvider(options);
172
+ }
173
+
174
+ module.exports = {
175
+ CodexSdkThread,
176
+ CodexThreadProvider,
177
+ createCodexThreadProvider,
178
+ defaultCodexStreamFactory,
179
+ defaultCodexTransportStreamFactory,
180
+ resolveCodexSdk,
181
+ };
@@ -0,0 +1,233 @@
1
+ "use strict";
2
+
3
+ const EventBus = require("../bus");
4
+ const { dispatchMessageHandler } = require("../tools/handlers/dispatchMessage");
5
+ const { ackBusHandler } = require("../tools/handlers/ackBus");
6
+
7
+ function normalizeObjectArgs(value) {
8
+ if (!value) return {};
9
+ if (typeof value === "string") {
10
+ try {
11
+ const parsed = JSON.parse(value);
12
+ return parsed && typeof parsed === "object" ? parsed : {};
13
+ } catch {
14
+ return {};
15
+ }
16
+ }
17
+ return typeof value === "object" ? value : {};
18
+ }
19
+
20
+ function normalizePositiveInt(value, fallback) {
21
+ const num = Number.parseInt(value, 10);
22
+ if (Number.isFinite(num) && num > 0) return num;
23
+ return fallback;
24
+ }
25
+
26
+ function buildStructuredError(code, message, extra = {}) {
27
+ return {
28
+ code: String(code || "tool_error"),
29
+ message: String(message || "tool execution failed"),
30
+ ...extra,
31
+ };
32
+ }
33
+
34
+ function resolveDispatchTargets(ctx, target) {
35
+ if (typeof ctx.resolveDispatchTargets === "function") {
36
+ const resolved = ctx.resolveDispatchTargets(target);
37
+ if (Array.isArray(resolved) && resolved.length > 0) return resolved;
38
+ }
39
+
40
+ const eventBus = ctx.eventBus || new EventBus(ctx.projectRoot);
41
+ try {
42
+ if (typeof eventBus.ensureBus === "function") eventBus.ensureBus();
43
+ if (typeof eventBus.loadBusData === "function") eventBus.loadBusData();
44
+ const targets = eventBus.messageManager && typeof eventBus.messageManager.resolveTarget === "function"
45
+ ? eventBus.messageManager.resolveTarget(target)
46
+ : [];
47
+ if (Array.isArray(targets) && targets.length > 0) return targets;
48
+ } catch {
49
+ // fall through to conservative fallback
50
+ }
51
+
52
+ if (target.includes(":")) return [target];
53
+
54
+ throw buildStructuredError("invalid_target", `dispatch_message target not found: ${target}`);
55
+ }
56
+
57
+ async function handleDispatchMessage(ctx, args) {
58
+ const eventBus = typeof ctx.dispatchMessages === "function"
59
+ ? {
60
+ send: async (target, message, publisher, options = {}) => {
61
+ resolveDispatchTargets(ctx, target);
62
+ if (target !== "broadcast" && typeof ctx.markPending === "function") {
63
+ ctx.markPending(target);
64
+ }
65
+ await ctx.dispatchMessages(ctx.projectRoot, [{
66
+ target,
67
+ message,
68
+ injection_mode: options.injectionMode || "immediate",
69
+ source: options.source || publisher,
70
+ }]);
71
+ return { seq: 0, targets: [target] };
72
+ },
73
+ broadcast: async (message, publisher, options = {}) => {
74
+ await ctx.dispatchMessages(ctx.projectRoot, [{
75
+ target: "broadcast",
76
+ message,
77
+ injection_mode: options.injectionMode || "immediate",
78
+ source: options.source || publisher,
79
+ }]);
80
+ return { seq: 0, targets: ["broadcast"] };
81
+ },
82
+ }
83
+ : new EventBus(ctx.projectRoot);
84
+
85
+ const result = await dispatchMessageHandler({
86
+ projectRoot: ctx.projectRoot,
87
+ subscriber: ctx.subscriber,
88
+ eventBus,
89
+ }, args);
90
+
91
+ return {
92
+ dispatched: 1,
93
+ target: result.target,
94
+ injection_mode: result.mode,
95
+ source: result.source,
96
+ };
97
+ }
98
+
99
+ async function handleAckBus(ctx, args) {
100
+ const eventBus = typeof ctx.ackBus === "function"
101
+ ? {
102
+ ack: async (subscriber) => {
103
+ await ctx.ackBus(ctx.projectRoot, subscriber);
104
+ return 1;
105
+ },
106
+ }
107
+ : new EventBus(ctx.projectRoot);
108
+
109
+ const result = await ackBusHandler({
110
+ projectRoot: ctx.projectRoot,
111
+ subscriber: ctx.subscriber,
112
+ eventBus,
113
+ }, args);
114
+
115
+ return {
116
+ acknowledged: true,
117
+ subscriber: result.subscriber,
118
+ };
119
+ }
120
+
121
+ async function handleLaunchAgent(ctx, args) {
122
+ if (typeof ctx.handleOps !== "function") {
123
+ throw buildStructuredError("tool_unavailable", "launch_agent hook is unavailable");
124
+ }
125
+
126
+ const agent = String(args.agent || "").trim().toLowerCase();
127
+ if (!agent) {
128
+ throw buildStructuredError("invalid_arguments", "launch_agent requires agent");
129
+ }
130
+
131
+ const op = {
132
+ action: "launch",
133
+ agent,
134
+ count: normalizePositiveInt(args.count, 1),
135
+ };
136
+ if (args.nickname) op.nickname = String(args.nickname).trim();
137
+ if (args.prompt_profile) op.prompt_profile = String(args.prompt_profile).trim();
138
+ if (args.launch_scope) op.launch_scope = String(args.launch_scope).trim();
139
+ if (args.terminal_app) op.terminal_app = String(args.terminal_app).trim();
140
+
141
+ const opsResults = await ctx.handleOps(ctx.projectRoot, [op], ctx.processManager || null);
142
+ return {
143
+ operation: op,
144
+ ops_results: Array.isArray(opsResults) ? opsResults : [],
145
+ };
146
+ }
147
+
148
+ async function executeControllerTool(ctx, toolCall = {}) {
149
+ const observer = ctx.observer || { emit: () => {}, audit: () => {} };
150
+ const name = String(toolCall.name || "").trim();
151
+ const args = normalizeObjectArgs(toolCall.arguments);
152
+ const toolCallId = String(
153
+ toolCall.tool_call_id || toolCall.toolCallId || toolCall.id || `${Date.now().toString(36)}`
154
+ ).trim();
155
+ const turnId = String(toolCall.turn_id || toolCall.turnId || ctx.turnId || "").trim();
156
+
157
+ observer.emit("tool_call_started", {
158
+ tool_name: name,
159
+ tool_call_id: toolCallId,
160
+ turn_id: turnId,
161
+ });
162
+
163
+ try {
164
+ let result;
165
+ if (name === "dispatch_message") {
166
+ result = await handleDispatchMessage(ctx, args);
167
+ } else if (name === "ack_bus") {
168
+ result = await handleAckBus(ctx, args);
169
+ } else if (name === "launch_agent") {
170
+ result = await handleLaunchAgent(ctx, args);
171
+ } else {
172
+ throw buildStructuredError("unsupported_tool", `unsupported controller tool: ${name}`);
173
+ }
174
+
175
+ observer.emit("tool_call_finished", {
176
+ tool_name: name,
177
+ tool_call_id: toolCallId,
178
+ turn_id: turnId,
179
+ ok: true,
180
+ });
181
+ observer.audit({
182
+ source: "controller_loop",
183
+ kind: "tool_call",
184
+ tool_name: name,
185
+ tool_call_id: toolCallId,
186
+ turn_id: turnId,
187
+ ok: true,
188
+ result,
189
+ });
190
+
191
+ return {
192
+ ok: true,
193
+ name,
194
+ tool_call_id: toolCallId,
195
+ turn_id: turnId,
196
+ result,
197
+ };
198
+ } catch (err) {
199
+ const error = err && typeof err === "object" && err.code
200
+ ? err
201
+ : buildStructuredError("tool_execution_failed", err && err.message ? err.message : String(err || "tool failed"));
202
+
203
+ observer.emit("tool_call_finished", {
204
+ tool_name: name,
205
+ tool_call_id: toolCallId,
206
+ turn_id: turnId,
207
+ ok: false,
208
+ error_code: error.code,
209
+ });
210
+ observer.audit({
211
+ source: "controller_loop",
212
+ kind: "tool_call",
213
+ tool_name: name,
214
+ tool_call_id: toolCallId,
215
+ turn_id: turnId,
216
+ ok: false,
217
+ error,
218
+ });
219
+
220
+ return {
221
+ ok: false,
222
+ name,
223
+ tool_call_id: toolCallId,
224
+ turn_id: turnId,
225
+ error,
226
+ };
227
+ }
228
+ }
229
+
230
+ module.exports = {
231
+ buildStructuredError,
232
+ executeControllerTool,
233
+ };