u-foo 2.3.8 → 2.3.10
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 +1 -1
- package/src/agent/internalRunner.js +33 -16
- package/src/agent/ptyRunner.js +51 -10
- package/src/agent/publisherRouting.js +59 -0
package/package.json
CHANGED
|
@@ -20,6 +20,7 @@ const { buildUpstreamAuthFromCredential } = require("./credentials");
|
|
|
20
20
|
const { listToolsForCallerTier, CALLER_TIERS } = require("../tools");
|
|
21
21
|
const { redactToolCallPayload, redactSecrets } = require("../providerapi/redactor");
|
|
22
22
|
const { buildCachedMemoryPrefix } = require("../memory");
|
|
23
|
+
const { shouldForwardStreamToPublisher } = require("./publisherRouting");
|
|
23
24
|
|
|
24
25
|
function sleep(ms) {
|
|
25
26
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -151,10 +152,12 @@ async function handleEvent(
|
|
|
151
152
|
const publisher = evt.publisher || "unknown";
|
|
152
153
|
const sandbox = "workspace-write";
|
|
153
154
|
const streamState = { emitted: false, lastChar: "" };
|
|
155
|
+
const streamToPublisher = shouldForwardStreamToPublisher(projectRoot, publisher);
|
|
154
156
|
|
|
155
157
|
const emitStreamDelta = (delta) => {
|
|
156
158
|
const text = String(delta || "");
|
|
157
159
|
if (!text) return;
|
|
160
|
+
if (!streamToPublisher) return;
|
|
158
161
|
streamState.emitted = true;
|
|
159
162
|
streamState.lastChar = text.slice(-1);
|
|
160
163
|
busSender.enqueue(publisher, JSON.stringify({ stream: true, delta: text }));
|
|
@@ -168,6 +171,7 @@ async function handleEvent(
|
|
|
168
171
|
prompt,
|
|
169
172
|
busSender,
|
|
170
173
|
emitStreamDelta,
|
|
174
|
+
streamToPublisher,
|
|
171
175
|
threadRuntime,
|
|
172
176
|
});
|
|
173
177
|
if (!threadedResult || !threadedResult.fallbackToLegacy) {
|
|
@@ -248,22 +252,33 @@ async function handleThreadedEvent({
|
|
|
248
252
|
prompt,
|
|
249
253
|
busSender,
|
|
250
254
|
emitStreamDelta,
|
|
255
|
+
streamToPublisher = true,
|
|
251
256
|
threadRuntime,
|
|
252
257
|
}) {
|
|
253
258
|
try {
|
|
259
|
+
const plainReplyParts = [];
|
|
254
260
|
for await (const event of threadRuntime.thread.runStreamed(prompt, {})) {
|
|
255
261
|
if (!event || typeof event !== "object") continue;
|
|
256
262
|
if (event.type === "text_delta" && event.delta) {
|
|
257
|
-
|
|
263
|
+
if (streamToPublisher) {
|
|
264
|
+
emitStreamDelta(event.delta);
|
|
265
|
+
} else {
|
|
266
|
+
plainReplyParts.push(String(event.delta));
|
|
267
|
+
}
|
|
258
268
|
} else if (event.type === "turn_failed") {
|
|
259
269
|
throw new Error(event.error || `thread turn failed for ${agentType}`);
|
|
260
270
|
}
|
|
261
271
|
}
|
|
262
272
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
273
|
+
if (streamToPublisher) {
|
|
274
|
+
busSender.enqueue(
|
|
275
|
+
publisher,
|
|
276
|
+
JSON.stringify({ stream: true, done: true, reason: "complete" })
|
|
277
|
+
);
|
|
278
|
+
} else {
|
|
279
|
+
const reply = plainReplyParts.join("").trim();
|
|
280
|
+
if (reply) busSender.enqueue(publisher, reply);
|
|
281
|
+
}
|
|
267
282
|
await busSender.flush();
|
|
268
283
|
} catch (err) {
|
|
269
284
|
if (shouldFallbackToLegacyThreadProvider(err, provider)) {
|
|
@@ -272,17 +287,19 @@ async function handleThreadedEvent({
|
|
|
272
287
|
if (threadRuntime && typeof threadRuntime.rebuildThread === "function") {
|
|
273
288
|
await threadRuntime.rebuildThread();
|
|
274
289
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
290
|
+
const errorText = `[internal:${agentType}] error: ${err && err.message ? err.message : "unknown error"}`;
|
|
291
|
+
if (streamToPublisher) {
|
|
292
|
+
busSender.enqueue(
|
|
293
|
+
publisher,
|
|
294
|
+
JSON.stringify({ stream: true, delta: errorText })
|
|
295
|
+
);
|
|
296
|
+
busSender.enqueue(
|
|
297
|
+
publisher,
|
|
298
|
+
JSON.stringify({ stream: true, done: true, reason: "error" })
|
|
299
|
+
);
|
|
300
|
+
} else {
|
|
301
|
+
busSender.enqueue(publisher, errorText);
|
|
302
|
+
}
|
|
286
303
|
await busSender.flush();
|
|
287
304
|
return { fallbackToLegacy: false };
|
|
288
305
|
}
|
package/src/agent/ptyRunner.js
CHANGED
|
@@ -7,6 +7,11 @@ const { PTY_SOCKET_MESSAGE_TYPES, PTY_SOCKET_SUBSCRIBE_MODES } = require("../sha
|
|
|
7
7
|
const { getUfooPaths } = require("../ufoo/paths");
|
|
8
8
|
const { ActivityDetector } = require("./activityDetector");
|
|
9
9
|
const { createActivityStatePublisher } = require("./activityStatePublisher");
|
|
10
|
+
const {
|
|
11
|
+
parseStreamEnvelope,
|
|
12
|
+
shouldAutoReplyFromPtyToPublisher,
|
|
13
|
+
shouldForwardStreamToPublisher,
|
|
14
|
+
} = require("./publisherRouting");
|
|
10
15
|
|
|
11
16
|
function sleep(ms) {
|
|
12
17
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -64,11 +69,14 @@ function drainQueue(queueFile) {
|
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
function stripAnsi(text) {
|
|
67
|
-
return text
|
|
72
|
+
return text
|
|
73
|
+
.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
|
|
74
|
+
.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
function parseInputMessage(message) {
|
|
71
78
|
if (!message) return { raw: false, text: "" };
|
|
79
|
+
if (parseStreamEnvelope(message)) return null;
|
|
72
80
|
try {
|
|
73
81
|
const parsed = JSON.parse(message);
|
|
74
82
|
if (parsed && typeof parsed === "object") {
|
|
@@ -201,6 +209,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
201
209
|
const watchdogMs = 120000;
|
|
202
210
|
const maxQueue = 200;
|
|
203
211
|
let sendQueue = Promise.resolve();
|
|
212
|
+
const streamPublisherCache = new Map();
|
|
204
213
|
const DROP_LINE_PATTERNS = [
|
|
205
214
|
/__UFOO_DONE_/,
|
|
206
215
|
/请在完成后输出以下标记/,
|
|
@@ -208,6 +217,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
208
217
|
/esc to interrupt/i,
|
|
209
218
|
/for shortcuts/i,
|
|
210
219
|
/Preparing to run session start commands/i,
|
|
220
|
+
/^[•\s]*(working|thinking|loading|reading|editing|running|checking)[•\s]*$/i,
|
|
211
221
|
];
|
|
212
222
|
|
|
213
223
|
function shouldDropLine(line) {
|
|
@@ -236,6 +246,33 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
236
246
|
});
|
|
237
247
|
}
|
|
238
248
|
|
|
249
|
+
function canStreamToPublisher(target) {
|
|
250
|
+
if (!target) return false;
|
|
251
|
+
if (streamPublisherCache.has(target)) return streamPublisherCache.get(target);
|
|
252
|
+
const result = shouldForwardStreamToPublisher(projectRoot, target);
|
|
253
|
+
streamPublisherCache.set(target, result);
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function completePublisherResponse(reason, fallbackNote = "") {
|
|
258
|
+
if (!currentPublisher) return;
|
|
259
|
+
if (flushTimer) {
|
|
260
|
+
clearTimeout(flushTimer);
|
|
261
|
+
flushTimer = null;
|
|
262
|
+
}
|
|
263
|
+
if (!shouldAutoReplyFromPtyToPublisher(projectRoot, currentPublisher)) {
|
|
264
|
+
outputBuffer = "";
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (outputBuffer) {
|
|
268
|
+
const remaining = outputBuffer;
|
|
269
|
+
outputBuffer = "";
|
|
270
|
+
deliverChunk(remaining);
|
|
271
|
+
}
|
|
272
|
+
if (fallbackNote) enqueueSend(currentPublisher, fallbackNote);
|
|
273
|
+
enqueueSend(currentPublisher, JSON.stringify({ stream: true, done: true, reason }));
|
|
274
|
+
}
|
|
275
|
+
|
|
239
276
|
// TTY view subscribers (same protocol as launcher inject.sock)
|
|
240
277
|
const outputSubscribers = new Set();
|
|
241
278
|
let term = null;
|
|
@@ -550,6 +587,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
550
587
|
if (!currentPublisher || pendingOutput.length === 0) return;
|
|
551
588
|
const chunks = pendingOutput;
|
|
552
589
|
pendingOutput = [];
|
|
590
|
+
if (!canStreamToPublisher(currentPublisher)) return;
|
|
553
591
|
for (const chunk of chunks) {
|
|
554
592
|
enqueueSend(currentPublisher, chunk);
|
|
555
593
|
}
|
|
@@ -561,7 +599,9 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
561
599
|
if (!cleaned) return;
|
|
562
600
|
const payload = JSON.stringify({ stream: true, delta: cleaned });
|
|
563
601
|
if (currentPublisher) {
|
|
564
|
-
|
|
602
|
+
if (canStreamToPublisher(currentPublisher)) {
|
|
603
|
+
enqueueSend(currentPublisher, payload);
|
|
604
|
+
}
|
|
565
605
|
} else {
|
|
566
606
|
pendingOutput.push(payload);
|
|
567
607
|
if (pendingOutput.length > 50) pendingOutput.shift();
|
|
@@ -631,7 +671,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
631
671
|
watchdogTimer = null;
|
|
632
672
|
}
|
|
633
673
|
if (currentPublisher) {
|
|
634
|
-
|
|
674
|
+
completePublisherResponse("idle");
|
|
635
675
|
}
|
|
636
676
|
busy = false;
|
|
637
677
|
currentPublisher = "";
|
|
@@ -681,7 +721,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
681
721
|
deliverChunk(before);
|
|
682
722
|
}
|
|
683
723
|
if (currentPublisher) {
|
|
684
|
-
|
|
724
|
+
completePublisherResponse("marker");
|
|
685
725
|
}
|
|
686
726
|
currentMarker = "";
|
|
687
727
|
busy = false;
|
|
@@ -722,7 +762,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
722
762
|
idleTimer = setTimeout(() => {
|
|
723
763
|
idleTimer = null;
|
|
724
764
|
if (currentPublisher) {
|
|
725
|
-
|
|
765
|
+
completePublisherResponse("idle");
|
|
726
766
|
}
|
|
727
767
|
busy = false;
|
|
728
768
|
activityDetector.markIdle();
|
|
@@ -758,7 +798,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
758
798
|
watchdogTimer = null;
|
|
759
799
|
}
|
|
760
800
|
const note = `[internal-pty] process exited code=${exitCode} signal=${signal || ""}`.trim();
|
|
761
|
-
if (currentPublisher)
|
|
801
|
+
if (currentPublisher) completePublisherResponse("exit", note);
|
|
762
802
|
logNote(note);
|
|
763
803
|
|
|
764
804
|
// Reset busy state
|
|
@@ -962,9 +1002,8 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
962
1002
|
watchdogTimer = null;
|
|
963
1003
|
if (!busy) return;
|
|
964
1004
|
const timeoutNote = `[internal-pty] marker timeout; restarting PTY`;
|
|
965
|
-
if (currentPublisher) enqueueSend(currentPublisher, timeoutNote);
|
|
966
1005
|
if (currentPublisher) {
|
|
967
|
-
|
|
1006
|
+
completePublisherResponse("timeout", timeoutNote);
|
|
968
1007
|
}
|
|
969
1008
|
logNote(timeoutNote);
|
|
970
1009
|
restartPty("marker timeout");
|
|
@@ -1013,7 +1052,9 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
1013
1052
|
}
|
|
1014
1053
|
for (const evt of events) {
|
|
1015
1054
|
if (!evt || !evt.data || typeof evt.data.message !== "string") continue;
|
|
1016
|
-
const
|
|
1055
|
+
const input = parseInputMessage(evt.data.message);
|
|
1056
|
+
if (!input) continue;
|
|
1057
|
+
const { raw, text } = input;
|
|
1017
1058
|
if (messageQueue.length >= maxQueue) {
|
|
1018
1059
|
messageQueue.shift();
|
|
1019
1060
|
}
|
|
@@ -1030,4 +1071,4 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
1030
1071
|
}
|
|
1031
1072
|
}
|
|
1032
1073
|
|
|
1033
|
-
module.exports = { runPtyRunner };
|
|
1074
|
+
module.exports = { parseInputMessage, runPtyRunner };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const { getUfooPaths } = require("../ufoo/paths");
|
|
3
|
+
|
|
4
|
+
function normalizePublisher(publisher) {
|
|
5
|
+
if (typeof publisher === "string") return publisher.trim();
|
|
6
|
+
if (publisher && typeof publisher === "object") {
|
|
7
|
+
return String(publisher.subscriber || publisher.id || publisher.nickname || "").trim();
|
|
8
|
+
}
|
|
9
|
+
return String(publisher || "").trim();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function readAgents(projectRoot) {
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(fs.readFileSync(getUfooPaths(projectRoot).agentsFile, "utf8"));
|
|
15
|
+
return parsed && typeof parsed === "object" && parsed.agents && typeof parsed.agents === "object"
|
|
16
|
+
? parsed.agents
|
|
17
|
+
: {};
|
|
18
|
+
} catch {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isManagedAgentPublisher(projectRoot, publisher) {
|
|
24
|
+
const id = normalizePublisher(publisher);
|
|
25
|
+
if (!id) return false;
|
|
26
|
+
const agents = readAgents(projectRoot);
|
|
27
|
+
return Boolean(agents[id]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function shouldForwardStreamToPublisher(projectRoot, publisher) {
|
|
31
|
+
const id = normalizePublisher(publisher);
|
|
32
|
+
if (!id) return false;
|
|
33
|
+
return !isManagedAgentPublisher(projectRoot, id);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function shouldAutoReplyFromPtyToPublisher(projectRoot, publisher) {
|
|
37
|
+
return shouldForwardStreamToPublisher(projectRoot, publisher);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseStreamEnvelope(message) {
|
|
41
|
+
if (typeof message !== "string" || !message.trim()) return null;
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(message);
|
|
44
|
+
if (parsed && typeof parsed === "object" && parsed.stream === true) {
|
|
45
|
+
return parsed;
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
// Not JSON.
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
isManagedAgentPublisher,
|
|
55
|
+
normalizePublisher,
|
|
56
|
+
parseStreamEnvelope,
|
|
57
|
+
shouldAutoReplyFromPtyToPublisher,
|
|
58
|
+
shouldForwardStreamToPublisher,
|
|
59
|
+
};
|