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
package/src/agent/cliRunner.js
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
const { spawn } = require("child_process");
|
|
2
2
|
const { randomUUID } = require("crypto");
|
|
3
|
-
|
|
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 ||
|
|
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 ||
|
|
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
|
+
};
|