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/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: {
|
|
@@ -484,6 +472,10 @@ const DEFAULT_CODEX = {
|
|
|
484
472
|
|
|
485
473
|
function buildArgs(backend, prompt, opts) {
|
|
486
474
|
const args = [...(backend.args || [])];
|
|
475
|
+
const extraArgs = Array.isArray(opts.extraArgs) ? opts.extraArgs.filter(Boolean) : [];
|
|
476
|
+
if (extraArgs.length > 0) {
|
|
477
|
+
args.push(...extraArgs);
|
|
478
|
+
}
|
|
487
479
|
if (opts.model && backend.modelArg) {
|
|
488
480
|
args.push(backend.modelArg, opts.model);
|
|
489
481
|
}
|
|
@@ -588,6 +580,7 @@ async function runCliAgent(params) {
|
|
|
588
580
|
sessionId,
|
|
589
581
|
systemPrompt: params.systemPrompt,
|
|
590
582
|
disableSession: params.disableSession,
|
|
583
|
+
extraArgs: params.extraArgs,
|
|
591
584
|
});
|
|
592
585
|
if (backend === DEFAULT_CODEX && params.sandbox) {
|
|
593
586
|
applySandboxOverride(args, params.sandbox);
|
|
@@ -617,7 +610,7 @@ async function runCliAgent(params) {
|
|
|
617
610
|
cwd: params.cwd,
|
|
618
611
|
env,
|
|
619
612
|
input: stdin,
|
|
620
|
-
timeoutMs: params.timeoutMs ||
|
|
613
|
+
timeoutMs: params.timeoutMs || DEFAULT_CLI_TIMEOUT_MS,
|
|
621
614
|
onStdout: codexParser ? (chunk) => codexParser.onChunk(chunk) : null,
|
|
622
615
|
signal: params.signal,
|
|
623
616
|
});
|
|
@@ -642,6 +635,7 @@ async function runCliAgent(params) {
|
|
|
642
635
|
sessionId,
|
|
643
636
|
systemPrompt: params.systemPrompt,
|
|
644
637
|
disableSession: params.disableSession,
|
|
638
|
+
extraArgs: params.extraArgs,
|
|
645
639
|
},
|
|
646
640
|
);
|
|
647
641
|
retryArgs = retry.args;
|
|
@@ -679,7 +673,7 @@ async function runCliAgent(params) {
|
|
|
679
673
|
cwd: params.cwd,
|
|
680
674
|
env,
|
|
681
675
|
input: retryStdin,
|
|
682
|
-
timeoutMs: params.timeoutMs ||
|
|
676
|
+
timeoutMs: params.timeoutMs || DEFAULT_CLI_TIMEOUT_MS,
|
|
683
677
|
onStdout: retryParser ? (chunk) => retryParser.onChunk(chunk) : null,
|
|
684
678
|
signal: params.signal,
|
|
685
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
|
+
};
|