u-foo 2.3.8 → 2.3.9
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 +81 -10
- package/src/agent/publisherRouting.js +54 -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,10 @@ 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
|
+
shouldForwardStreamToPublisher,
|
|
13
|
+
} = require("./publisherRouting");
|
|
10
14
|
|
|
11
15
|
function sleep(ms) {
|
|
12
16
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -64,11 +68,14 @@ function drainQueue(queueFile) {
|
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
function stripAnsi(text) {
|
|
67
|
-
return text
|
|
71
|
+
return text
|
|
72
|
+
.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "")
|
|
73
|
+
.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
|
|
68
74
|
}
|
|
69
75
|
|
|
70
76
|
function parseInputMessage(message) {
|
|
71
77
|
if (!message) return { raw: false, text: "" };
|
|
78
|
+
if (parseStreamEnvelope(message)) return null;
|
|
72
79
|
try {
|
|
73
80
|
const parsed = JSON.parse(message);
|
|
74
81
|
if (parsed && typeof parsed === "object") {
|
|
@@ -185,6 +192,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
185
192
|
let suppressEcho = false;
|
|
186
193
|
let echoMarker = "";
|
|
187
194
|
let suppressTimer = null;
|
|
195
|
+
let managedReplyBuffer = "";
|
|
188
196
|
let ptyProcess = null;
|
|
189
197
|
let restartCount = 0;
|
|
190
198
|
let lastSpawnTime = 0;
|
|
@@ -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,55 @@ 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 appendManagedReply(chunk) {
|
|
258
|
+
const text = String(chunk || "");
|
|
259
|
+
if (!text) return;
|
|
260
|
+
managedReplyBuffer += text;
|
|
261
|
+
if (managedReplyBuffer.length > 40000) {
|
|
262
|
+
managedReplyBuffer = managedReplyBuffer.slice(-40000);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function takeManagedReply() {
|
|
267
|
+
const reply = sanitizeChunk(managedReplyBuffer)
|
|
268
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
269
|
+
.trim();
|
|
270
|
+
managedReplyBuffer = "";
|
|
271
|
+
return reply;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function completePublisherResponse(reason, fallbackNote = "") {
|
|
275
|
+
if (!currentPublisher) return;
|
|
276
|
+
if (flushTimer) {
|
|
277
|
+
clearTimeout(flushTimer);
|
|
278
|
+
flushTimer = null;
|
|
279
|
+
}
|
|
280
|
+
if (outputBuffer) {
|
|
281
|
+
const remaining = outputBuffer;
|
|
282
|
+
outputBuffer = "";
|
|
283
|
+
deliverChunk(remaining);
|
|
284
|
+
}
|
|
285
|
+
if (canStreamToPublisher(currentPublisher)) {
|
|
286
|
+
if (fallbackNote) enqueueSend(currentPublisher, fallbackNote);
|
|
287
|
+
enqueueSend(currentPublisher, JSON.stringify({ stream: true, done: true, reason }));
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const reply = takeManagedReply();
|
|
291
|
+
if (reply) {
|
|
292
|
+
enqueueSend(currentPublisher, reply);
|
|
293
|
+
} else if (fallbackNote) {
|
|
294
|
+
enqueueSend(currentPublisher, fallbackNote);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
239
298
|
// TTY view subscribers (same protocol as launcher inject.sock)
|
|
240
299
|
const outputSubscribers = new Set();
|
|
241
300
|
let term = null;
|
|
@@ -550,6 +609,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
550
609
|
if (!currentPublisher || pendingOutput.length === 0) return;
|
|
551
610
|
const chunks = pendingOutput;
|
|
552
611
|
pendingOutput = [];
|
|
612
|
+
if (!canStreamToPublisher(currentPublisher)) return;
|
|
553
613
|
for (const chunk of chunks) {
|
|
554
614
|
enqueueSend(currentPublisher, chunk);
|
|
555
615
|
}
|
|
@@ -561,7 +621,11 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
561
621
|
if (!cleaned) return;
|
|
562
622
|
const payload = JSON.stringify({ stream: true, delta: cleaned });
|
|
563
623
|
if (currentPublisher) {
|
|
564
|
-
|
|
624
|
+
if (canStreamToPublisher(currentPublisher)) {
|
|
625
|
+
enqueueSend(currentPublisher, payload);
|
|
626
|
+
} else {
|
|
627
|
+
appendManagedReply(cleaned);
|
|
628
|
+
}
|
|
565
629
|
} else {
|
|
566
630
|
pendingOutput.push(payload);
|
|
567
631
|
if (pendingOutput.length > 50) pendingOutput.shift();
|
|
@@ -631,10 +695,11 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
631
695
|
watchdogTimer = null;
|
|
632
696
|
}
|
|
633
697
|
if (currentPublisher) {
|
|
634
|
-
|
|
698
|
+
completePublisherResponse("idle");
|
|
635
699
|
}
|
|
636
700
|
busy = false;
|
|
637
701
|
currentPublisher = "";
|
|
702
|
+
managedReplyBuffer = "";
|
|
638
703
|
processQueue();
|
|
639
704
|
}
|
|
640
705
|
});
|
|
@@ -681,12 +746,13 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
681
746
|
deliverChunk(before);
|
|
682
747
|
}
|
|
683
748
|
if (currentPublisher) {
|
|
684
|
-
|
|
749
|
+
completePublisherResponse("marker");
|
|
685
750
|
}
|
|
686
751
|
currentMarker = "";
|
|
687
752
|
busy = false;
|
|
688
753
|
activityDetector.markIdle();
|
|
689
754
|
currentPublisher = "";
|
|
755
|
+
managedReplyBuffer = "";
|
|
690
756
|
if (watchdogTimer) {
|
|
691
757
|
clearTimeout(watchdogTimer);
|
|
692
758
|
watchdogTimer = null;
|
|
@@ -722,11 +788,12 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
722
788
|
idleTimer = setTimeout(() => {
|
|
723
789
|
idleTimer = null;
|
|
724
790
|
if (currentPublisher) {
|
|
725
|
-
|
|
791
|
+
completePublisherResponse("idle");
|
|
726
792
|
}
|
|
727
793
|
busy = false;
|
|
728
794
|
activityDetector.markIdle();
|
|
729
795
|
currentPublisher = "";
|
|
796
|
+
managedReplyBuffer = "";
|
|
730
797
|
processQueue();
|
|
731
798
|
}, idleMs);
|
|
732
799
|
}
|
|
@@ -758,7 +825,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
758
825
|
watchdogTimer = null;
|
|
759
826
|
}
|
|
760
827
|
const note = `[internal-pty] process exited code=${exitCode} signal=${signal || ""}`.trim();
|
|
761
|
-
if (currentPublisher)
|
|
828
|
+
if (currentPublisher) completePublisherResponse("exit", note);
|
|
762
829
|
logNote(note);
|
|
763
830
|
|
|
764
831
|
// Reset busy state
|
|
@@ -766,6 +833,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
766
833
|
activityDetector.markIdle();
|
|
767
834
|
currentPublisher = "";
|
|
768
835
|
currentMarker = "";
|
|
836
|
+
managedReplyBuffer = "";
|
|
769
837
|
|
|
770
838
|
// If stop() was called, let the runner exit
|
|
771
839
|
if (!running) return;
|
|
@@ -901,6 +969,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
901
969
|
activityDetector.markWorking();
|
|
902
970
|
currentPublisher = next.publisher;
|
|
903
971
|
currentMarker = next.marker || "";
|
|
972
|
+
managedReplyBuffer = "";
|
|
904
973
|
if (suppressTimer) {
|
|
905
974
|
clearTimeout(suppressTimer);
|
|
906
975
|
suppressTimer = null;
|
|
@@ -962,9 +1031,8 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
962
1031
|
watchdogTimer = null;
|
|
963
1032
|
if (!busy) return;
|
|
964
1033
|
const timeoutNote = `[internal-pty] marker timeout; restarting PTY`;
|
|
965
|
-
if (currentPublisher) enqueueSend(currentPublisher, timeoutNote);
|
|
966
1034
|
if (currentPublisher) {
|
|
967
|
-
|
|
1035
|
+
completePublisherResponse("timeout", timeoutNote);
|
|
968
1036
|
}
|
|
969
1037
|
logNote(timeoutNote);
|
|
970
1038
|
restartPty("marker timeout");
|
|
@@ -972,6 +1040,7 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
972
1040
|
busy = false;
|
|
973
1041
|
activityDetector.markIdle();
|
|
974
1042
|
currentPublisher = "";
|
|
1043
|
+
managedReplyBuffer = "";
|
|
975
1044
|
processQueue();
|
|
976
1045
|
}, watchdogMs);
|
|
977
1046
|
}
|
|
@@ -1013,7 +1082,9 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
1013
1082
|
}
|
|
1014
1083
|
for (const evt of events) {
|
|
1015
1084
|
if (!evt || !evt.data || typeof evt.data.message !== "string") continue;
|
|
1016
|
-
const
|
|
1085
|
+
const input = parseInputMessage(evt.data.message);
|
|
1086
|
+
if (!input) continue;
|
|
1087
|
+
const { raw, text } = input;
|
|
1017
1088
|
if (messageQueue.length >= maxQueue) {
|
|
1018
1089
|
messageQueue.shift();
|
|
1019
1090
|
}
|
|
@@ -1030,4 +1101,4 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
1030
1101
|
}
|
|
1031
1102
|
}
|
|
1032
1103
|
|
|
1033
|
-
module.exports = { runPtyRunner };
|
|
1104
|
+
module.exports = { parseInputMessage, runPtyRunner };
|
|
@@ -0,0 +1,54 @@
|
|
|
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 parseStreamEnvelope(message) {
|
|
37
|
+
if (typeof message !== "string" || !message.trim()) return null;
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(message);
|
|
40
|
+
if (parsed && typeof parsed === "object" && parsed.stream === true) {
|
|
41
|
+
return parsed;
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// Not JSON.
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
isManagedAgentPublisher,
|
|
51
|
+
normalizePublisher,
|
|
52
|
+
parseStreamEnvelope,
|
|
53
|
+
shouldForwardStreamToPublisher,
|
|
54
|
+
};
|