u-foo 2.5.0 → 2.5.2
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/uagy.js +42 -4
- package/bin/uclaude.js +42 -1
- package/bin/ucode.js +42 -1
- package/bin/ucodex.js +42 -1
- package/package.json +1 -1
- package/src/agents/internal/internalRunner.js +57 -78
- package/src/agents/launch/notifier.js +35 -111
- package/src/agents/launch/ptyRunner.js +28 -48
- package/src/agents/prompts/defaultBootstrap.js +16 -1
- package/src/app/cli/busCoreCommands.js +19 -1
- package/src/app/cli/run.js +1 -53
- package/src/code/agent.js +41 -140
- package/src/coordination/bus/deliveryQueue.js +308 -0
- package/src/coordination/bus/index.js +3 -27
- package/src/coordination/bus/message.js +35 -15
- package/src/coordination/bus/queue.js +23 -7
- package/src/runtime/daemon/deliveryScheduler.js +205 -0
- package/src/runtime/daemon/index.js +27 -38
- package/src/runtime/daemon/reportControlBus.js +20 -43
- package/src/coordination/bus/daemon.js +0 -535
package/bin/uagy.js
CHANGED
|
@@ -42,14 +42,52 @@ const previousConversationId = readPreviousConversationId(cwd, {
|
|
|
42
42
|
tmuxPane: process.env.TMUX_PANE || "",
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
function extractUfooParamsFromArgs(args = []) {
|
|
46
|
+
const nextArgs = [];
|
|
47
|
+
let nickname = "";
|
|
48
|
+
let role = "";
|
|
49
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
50
|
+
const arg = String(args[i] || "");
|
|
51
|
+
if (arg === "--nickname") {
|
|
52
|
+
if (i + 1 < args.length) {
|
|
53
|
+
nickname = String(args[i + 1]).trim();
|
|
54
|
+
i += 1;
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (arg.startsWith("--nickname=")) {
|
|
59
|
+
nickname = arg.slice("--nickname=".length).trim();
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (arg === "--role") {
|
|
63
|
+
if (i + 1 < args.length) {
|
|
64
|
+
role = String(args[i + 1]).trim();
|
|
65
|
+
i += 1;
|
|
66
|
+
}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (arg.startsWith("--role=")) {
|
|
70
|
+
role = arg.slice("--role=".length).trim();
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
nextArgs.push(args[i]);
|
|
74
|
+
}
|
|
75
|
+
return { args: nextArgs, nickname, role };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const { args: cleanArgs, nickname, role } = extractUfooParamsFromArgs(process.argv.slice(2));
|
|
79
|
+
if (nickname) {
|
|
80
|
+
process.env.UFOO_NICKNAME = nickname;
|
|
81
|
+
}
|
|
82
|
+
if (role) {
|
|
83
|
+
process.env.UFOO_PROMPT_PROFILE = role;
|
|
84
|
+
}
|
|
85
|
+
|
|
48
86
|
const launchMode = String(process.env.UFOO_LAUNCH_MODE || "").trim().toLowerCase();
|
|
49
87
|
const skipPermissions = launchMode === "internal";
|
|
50
88
|
|
|
51
89
|
const launchArgs = buildAgyLaunchArgs({
|
|
52
|
-
userArgs:
|
|
90
|
+
userArgs: cleanArgs,
|
|
53
91
|
previousConversationId,
|
|
54
92
|
skipPermissions,
|
|
55
93
|
});
|
package/bin/uclaude.js
CHANGED
|
@@ -8,11 +8,52 @@
|
|
|
8
8
|
const AgentLauncher = require("../src/agents/launch/launcher");
|
|
9
9
|
const { resolveDefaultManualBootstrap } = require("../src/agents/prompts/defaultBootstrap");
|
|
10
10
|
|
|
11
|
+
function extractUfooParamsFromArgs(args = []) {
|
|
12
|
+
const nextArgs = [];
|
|
13
|
+
let nickname = "";
|
|
14
|
+
let role = "";
|
|
15
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
16
|
+
const arg = String(args[i] || "");
|
|
17
|
+
if (arg === "--nickname") {
|
|
18
|
+
if (i + 1 < args.length) {
|
|
19
|
+
nickname = String(args[i + 1]).trim();
|
|
20
|
+
i += 1;
|
|
21
|
+
}
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (arg.startsWith("--nickname=")) {
|
|
25
|
+
nickname = arg.slice("--nickname=".length).trim();
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (arg === "--role") {
|
|
29
|
+
if (i + 1 < args.length) {
|
|
30
|
+
role = String(args[i + 1]).trim();
|
|
31
|
+
i += 1;
|
|
32
|
+
}
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (arg.startsWith("--role=")) {
|
|
36
|
+
role = arg.slice("--role=".length).trim();
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
nextArgs.push(args[i]);
|
|
40
|
+
}
|
|
41
|
+
return { args: nextArgs, nickname, role };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { args: cleanArgs, nickname, role } = extractUfooParamsFromArgs(process.argv.slice(2));
|
|
45
|
+
if (nickname) {
|
|
46
|
+
process.env.UFOO_NICKNAME = nickname;
|
|
47
|
+
}
|
|
48
|
+
if (role) {
|
|
49
|
+
process.env.UFOO_PROMPT_PROFILE = role;
|
|
50
|
+
}
|
|
51
|
+
|
|
11
52
|
const launcher = new AgentLauncher("claude-code", "claude");
|
|
12
53
|
const resolved = resolveDefaultManualBootstrap({
|
|
13
54
|
projectRoot: process.cwd(),
|
|
14
55
|
agentType: "claude-code",
|
|
15
|
-
args:
|
|
56
|
+
args: cleanArgs,
|
|
16
57
|
env: process.env,
|
|
17
58
|
});
|
|
18
59
|
|
package/bin/ucode.js
CHANGED
|
@@ -59,8 +59,49 @@ function shouldPreserveAppendTarget({
|
|
|
59
59
|
return fs.existsSync(append);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function extractUfooParamsFromArgs(args = []) {
|
|
63
|
+
const nextArgs = [];
|
|
64
|
+
let nickname = "";
|
|
65
|
+
let role = "";
|
|
66
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
67
|
+
const arg = String(args[i] || "");
|
|
68
|
+
if (arg === "--nickname") {
|
|
69
|
+
if (i + 1 < args.length) {
|
|
70
|
+
nickname = String(args[i + 1]).trim();
|
|
71
|
+
i += 1;
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (arg.startsWith("--nickname=")) {
|
|
76
|
+
nickname = arg.slice("--nickname=".length).trim();
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (arg === "--role") {
|
|
80
|
+
if (i + 1 < args.length) {
|
|
81
|
+
role = String(args[i + 1]).trim();
|
|
82
|
+
i += 1;
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (arg.startsWith("--role=")) {
|
|
87
|
+
role = arg.slice("--role=".length).trim();
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
nextArgs.push(args[i]);
|
|
91
|
+
}
|
|
92
|
+
return { args: nextArgs, nickname, role };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const { args: cleanArgs, nickname, role } = extractUfooParamsFromArgs(process.argv.slice(2));
|
|
96
|
+
if (nickname) {
|
|
97
|
+
process.env.UFOO_NICKNAME = nickname;
|
|
98
|
+
}
|
|
99
|
+
if (role) {
|
|
100
|
+
process.env.UFOO_PROMPT_PROFILE = role;
|
|
101
|
+
}
|
|
102
|
+
|
|
62
103
|
const resolved = resolveUcodeLaunch({
|
|
63
|
-
argv:
|
|
104
|
+
argv: cleanArgs,
|
|
64
105
|
env: process.env,
|
|
65
106
|
cwd: process.cwd(),
|
|
66
107
|
});
|
package/bin/ucodex.js
CHANGED
|
@@ -8,11 +8,52 @@
|
|
|
8
8
|
const AgentLauncher = require("../src/agents/launch/launcher");
|
|
9
9
|
const { resolveDefaultManualBootstrap } = require("../src/agents/prompts/defaultBootstrap");
|
|
10
10
|
|
|
11
|
+
function extractUfooParamsFromArgs(args = []) {
|
|
12
|
+
const nextArgs = [];
|
|
13
|
+
let nickname = "";
|
|
14
|
+
let role = "";
|
|
15
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
16
|
+
const arg = String(args[i] || "");
|
|
17
|
+
if (arg === "--nickname") {
|
|
18
|
+
if (i + 1 < args.length) {
|
|
19
|
+
nickname = String(args[i + 1]).trim();
|
|
20
|
+
i += 1;
|
|
21
|
+
}
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (arg.startsWith("--nickname=")) {
|
|
25
|
+
nickname = arg.slice("--nickname=".length).trim();
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (arg === "--role") {
|
|
29
|
+
if (i + 1 < args.length) {
|
|
30
|
+
role = String(args[i + 1]).trim();
|
|
31
|
+
i += 1;
|
|
32
|
+
}
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (arg.startsWith("--role=")) {
|
|
36
|
+
role = arg.slice("--role=".length).trim();
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
nextArgs.push(args[i]);
|
|
40
|
+
}
|
|
41
|
+
return { args: nextArgs, nickname, role };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { args: cleanArgs, nickname, role } = extractUfooParamsFromArgs(process.argv.slice(2));
|
|
45
|
+
if (nickname) {
|
|
46
|
+
process.env.UFOO_NICKNAME = nickname;
|
|
47
|
+
}
|
|
48
|
+
if (role) {
|
|
49
|
+
process.env.UFOO_PROMPT_PROFILE = role;
|
|
50
|
+
}
|
|
51
|
+
|
|
11
52
|
const launcher = new AgentLauncher("codex", "codex");
|
|
12
53
|
const resolved = resolveDefaultManualBootstrap({
|
|
13
54
|
projectRoot: process.cwd(),
|
|
14
55
|
agentType: "codex",
|
|
15
|
-
args:
|
|
56
|
+
args: cleanArgs,
|
|
16
57
|
env: process.env,
|
|
17
58
|
});
|
|
18
59
|
|
package/package.json
CHANGED
|
@@ -16,6 +16,7 @@ const { redactToolCallPayload, redactSecrets } = require("../../runtime/privacy/
|
|
|
16
16
|
const { buildCachedMemoryPrefix } = require("../../coordination/memory");
|
|
17
17
|
const { normalizePublisher, shouldForwardStreamToPublisher } = require("../launch/publisherRouting");
|
|
18
18
|
const { appendAgentRegistryDiagnostic } = require("../../coordination/state/agentRegistryDiagnostics");
|
|
19
|
+
const { DeliveryQueue } = require("../../coordination/bus/deliveryQueue");
|
|
19
20
|
const {
|
|
20
21
|
buildDefaultStartupBootstrapPrompt,
|
|
21
22
|
isValueForCodexOption,
|
|
@@ -216,37 +217,15 @@ function shouldStreamReplyToPublisher(projectRoot, publisher, evt = {}) {
|
|
|
216
217
|
return shouldForwardStreamToPublisher(projectRoot, publisher);
|
|
217
218
|
}
|
|
218
219
|
|
|
219
|
-
function
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
content = fs.readFileSync(processingFile, "utf8");
|
|
227
|
-
readOk = true;
|
|
228
|
-
} catch {
|
|
229
|
-
try {
|
|
230
|
-
if (fs.existsSync(processingFile)) {
|
|
231
|
-
fs.renameSync(processingFile, queueFile);
|
|
232
|
-
}
|
|
233
|
-
} catch {
|
|
234
|
-
// ignore rollback errors
|
|
235
|
-
}
|
|
236
|
-
return [];
|
|
237
|
-
} finally {
|
|
238
|
-
if (readOk) {
|
|
239
|
-
try {
|
|
240
|
-
if (fs.existsSync(processingFile)) {
|
|
241
|
-
fs.rmSync(processingFile, { force: true });
|
|
242
|
-
}
|
|
243
|
-
} catch {
|
|
244
|
-
// ignore cleanup errors
|
|
245
|
-
}
|
|
246
|
-
}
|
|
220
|
+
function claimQueuedEvents(queueFile) {
|
|
221
|
+
const queue = new DeliveryQueue(queueFile);
|
|
222
|
+
const claims = [];
|
|
223
|
+
while (true) {
|
|
224
|
+
const claim = queue.claimNext();
|
|
225
|
+
if (!claim) break;
|
|
226
|
+
claims.push({ ...claim, queue });
|
|
247
227
|
}
|
|
248
|
-
|
|
249
|
-
return content.split(/\r?\n/).filter(Boolean);
|
|
228
|
+
return claims;
|
|
250
229
|
}
|
|
251
230
|
|
|
252
231
|
function parseAgentViewRawInput(message) {
|
|
@@ -835,67 +814,67 @@ async function runInternalRunner({ projectRoot, agentType = "codex", extraArgs =
|
|
|
835
814
|
if (!processing) {
|
|
836
815
|
processing = true;
|
|
837
816
|
try {
|
|
838
|
-
const
|
|
839
|
-
if (
|
|
840
|
-
|
|
841
|
-
for (const line of lines) {
|
|
842
|
-
try {
|
|
843
|
-
events.push(JSON.parse(line));
|
|
844
|
-
} catch {
|
|
845
|
-
// ignore malformed line
|
|
846
|
-
}
|
|
847
|
-
}
|
|
817
|
+
const claims = claimQueuedEvents(queueFile);
|
|
818
|
+
if (claims.length > 0) {
|
|
819
|
+
let handledAny = false;
|
|
848
820
|
|
|
849
|
-
const
|
|
850
|
-
|
|
821
|
+
for (const claim of claims) {
|
|
822
|
+
const evt = claim.event;
|
|
823
|
+
const runnableEvents = [];
|
|
851
824
|
const rawInput = parseAgentViewRawInput(evt && evt.data ? evt.data.message : "");
|
|
852
825
|
if (rawInput === null) {
|
|
853
826
|
runnableEvents.push(evt);
|
|
854
|
-
|
|
827
|
+
} else {
|
|
828
|
+
const session = getInteractiveSession(evt.publisher || "unknown");
|
|
829
|
+
const submissions = session.handleRaw(rawInput);
|
|
830
|
+
for (const message of submissions) {
|
|
831
|
+
runnableEvents.push({
|
|
832
|
+
...evt,
|
|
833
|
+
__agentViewRaw: true,
|
|
834
|
+
data: {
|
|
835
|
+
...(evt.data || {}),
|
|
836
|
+
message,
|
|
837
|
+
},
|
|
838
|
+
});
|
|
839
|
+
}
|
|
855
840
|
}
|
|
856
841
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
for (const message of submissions) {
|
|
860
|
-
runnableEvents.push({
|
|
861
|
-
...evt,
|
|
862
|
-
__agentViewRaw: true,
|
|
863
|
-
data: {
|
|
864
|
-
...(evt.data || {}),
|
|
865
|
-
message,
|
|
866
|
-
},
|
|
867
|
-
});
|
|
842
|
+
if (runnableEvents.length > 0) {
|
|
843
|
+
activityTracker.notifyTurnStart("thinking");
|
|
868
844
|
}
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
if (runnableEvents.length > 0) {
|
|
872
|
-
activityTracker.notifyTurnStart("thinking");
|
|
873
|
-
}
|
|
874
845
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
846
|
+
try {
|
|
847
|
+
for (const runnableEvent of runnableEvents) {
|
|
848
|
+
// eslint-disable-next-line no-await-in-loop
|
|
849
|
+
await handleEvent(
|
|
850
|
+
projectRoot,
|
|
851
|
+
parsedAgentType,
|
|
852
|
+
provider,
|
|
853
|
+
model,
|
|
854
|
+
subscriber,
|
|
855
|
+
nickname,
|
|
856
|
+
runnableEvent,
|
|
857
|
+
busSender,
|
|
858
|
+
bootstrap.extraArgs,
|
|
859
|
+
threadRuntime,
|
|
860
|
+
bootstrap.promptText,
|
|
861
|
+
activityTracker
|
|
862
|
+
);
|
|
863
|
+
if (runnableEvent.__agentViewRaw) {
|
|
864
|
+
getInteractiveSession(runnableEvent.publisher || "unknown").writeResponsePrompt();
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
claim.queue.completeClaim(claim);
|
|
868
|
+
if (runnableEvents.length > 0) handledAny = true;
|
|
869
|
+
} catch (err) {
|
|
870
|
+
claim.queue.restoreClaim(claim);
|
|
871
|
+
throw err;
|
|
893
872
|
}
|
|
894
873
|
}
|
|
895
874
|
// 处理消息后更新心跳
|
|
896
875
|
updateHeartbeat();
|
|
897
876
|
lastHeartbeat = now;
|
|
898
|
-
if (
|
|
877
|
+
if (handledAny) {
|
|
899
878
|
activityTracker.markIdle();
|
|
900
879
|
}
|
|
901
880
|
await busSender.flush();
|
|
@@ -9,8 +9,8 @@ const { shakeTerminalByTty } = require("../../coordination/bus/shake");
|
|
|
9
9
|
const { isITerm2 } = require("../../runtime/terminal/detect");
|
|
10
10
|
const iterm2 = require("../../runtime/terminal/iterm2");
|
|
11
11
|
const { createActivityStatePublisher } = require("../activity/activityStatePublisher");
|
|
12
|
-
const { INJECTION_MODES, getInjectionModeFromEvent } = require("../../coordination/bus/messageMeta");
|
|
13
12
|
const { buildPromptInjectionText } = require("../../coordination/bus/promptEnvelope");
|
|
13
|
+
const { DeliveryQueue } = require("../../coordination/bus/deliveryQueue");
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Agent 消息通知监听器
|
|
@@ -41,6 +41,7 @@ class AgentNotifier {
|
|
|
41
41
|
safeSub,
|
|
42
42
|
"pending.jsonl"
|
|
43
43
|
);
|
|
44
|
+
this.deliveryQueue = new DeliveryQueue(this.queueFile);
|
|
44
45
|
this.agentsFile = paths.agentsFile;
|
|
45
46
|
|
|
46
47
|
// 初始化 injector
|
|
@@ -187,45 +188,6 @@ class AgentNotifier {
|
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
drainPending() {
|
|
191
|
-
if (!fs.existsSync(this.queueFile)) return [];
|
|
192
|
-
const processingFile = `${this.queueFile}.processing.${process.pid}.${Date.now()}`;
|
|
193
|
-
let content = "";
|
|
194
|
-
let readOk = false;
|
|
195
|
-
try {
|
|
196
|
-
fs.renameSync(this.queueFile, processingFile);
|
|
197
|
-
content = fs.readFileSync(processingFile, "utf8");
|
|
198
|
-
readOk = true;
|
|
199
|
-
} catch {
|
|
200
|
-
try {
|
|
201
|
-
if (fs.existsSync(processingFile)) {
|
|
202
|
-
fs.renameSync(processingFile, this.queueFile);
|
|
203
|
-
}
|
|
204
|
-
} catch {
|
|
205
|
-
// ignore rollback errors
|
|
206
|
-
}
|
|
207
|
-
return [];
|
|
208
|
-
} finally {
|
|
209
|
-
if (readOk) {
|
|
210
|
-
try {
|
|
211
|
-
if (fs.existsSync(processingFile)) {
|
|
212
|
-
fs.rmSync(processingFile, { force: true });
|
|
213
|
-
}
|
|
214
|
-
} catch {
|
|
215
|
-
// ignore cleanup errors
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
if (!content.trim()) return [];
|
|
220
|
-
return content.split(/\r?\n/).filter(Boolean).map((line) => {
|
|
221
|
-
try {
|
|
222
|
-
return JSON.parse(line);
|
|
223
|
-
} catch {
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
}).filter(Boolean);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
191
|
normalizePublisher(publisher) {
|
|
230
192
|
if (!publisher) return "";
|
|
231
193
|
if (typeof publisher === "string") return publisher;
|
|
@@ -272,57 +234,41 @@ class AgentNotifier {
|
|
|
272
234
|
return 0;
|
|
273
235
|
}
|
|
274
236
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const requeue = [];
|
|
278
|
-
let delivered = 0;
|
|
279
|
-
let consumedOne = false;
|
|
280
|
-
for (const evt of events) {
|
|
281
|
-
if (!evt || evt.event !== "message" || !evt.data || typeof evt.data.message !== "string") {
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
const injectionMode = getInjectionModeFromEvent(evt, INJECTION_MODES.IMMEDIATE);
|
|
285
|
-
const activityState = this.getCurrentActivityState();
|
|
286
|
-
if (injectionMode === INJECTION_MODES.QUEUED && this.isBusyState(activityState)) {
|
|
287
|
-
requeue.push(evt);
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
if (consumedOne) {
|
|
291
|
-
requeue.push(evt);
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
const message = buildPromptInjectionText(evt, this.subscriber, this.getAgentsMap());
|
|
295
|
-
try {
|
|
296
|
-
// Inject the prompt-facing text into the terminal/tmux agent
|
|
297
|
-
// (Bus is the source of truth; inject is the delivery adapter.)
|
|
298
|
-
// eslint-disable-next-line no-await-in-loop
|
|
299
|
-
await this.injector.inject(this.subscriber, message);
|
|
300
|
-
delivered += 1;
|
|
301
|
-
consumedOne = true;
|
|
302
|
-
this.injectFailCount = 0;
|
|
303
|
-
this.updateActivityState("working");
|
|
304
|
-
// eslint-disable-next-line no-await-in-loop
|
|
305
|
-
await this.emitDelivery(evt, "ok");
|
|
306
|
-
} catch (err) {
|
|
307
|
-
consumedOne = true;
|
|
308
|
-
this.injectFailCount += 1;
|
|
309
|
-
requeue.push(evt);
|
|
310
|
-
// eslint-disable-next-line no-await-in-loop
|
|
311
|
-
await this.emitDelivery(evt, "error", err.message || "inject failed");
|
|
312
|
-
}
|
|
237
|
+
if (this.isBusyState(this.getCurrentActivityState())) {
|
|
238
|
+
return 0;
|
|
313
239
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
240
|
+
|
|
241
|
+
const claim = this.deliveryQueue.claimNext();
|
|
242
|
+
if (!claim) return 0;
|
|
243
|
+
|
|
244
|
+
const evt = claim.event;
|
|
245
|
+
if (!evt || evt.event !== "message" || !evt.data || typeof evt.data.message !== "string") {
|
|
246
|
+
this.deliveryQueue.completeClaim(claim);
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const activityState = this.getCurrentActivityState();
|
|
251
|
+
if (this.isBusyState(activityState)) {
|
|
252
|
+
this.deliveryQueue.restoreClaim(claim);
|
|
253
|
+
return 0;
|
|
321
254
|
}
|
|
322
|
-
|
|
255
|
+
|
|
256
|
+
const message = buildPromptInjectionText(evt, this.subscriber, this.getAgentsMap());
|
|
257
|
+
try {
|
|
258
|
+
// Inject the prompt-facing text into the terminal/tmux agent.
|
|
259
|
+
await this.injector.inject(this.subscriber, message);
|
|
260
|
+
this.deliveryQueue.completeClaim(claim);
|
|
261
|
+
this.injectFailCount = 0;
|
|
262
|
+
this.updateActivityState("working");
|
|
263
|
+
await this.emitDelivery(evt, "ok");
|
|
323
264
|
this.lastWorkingAt = Date.now();
|
|
265
|
+
return 1;
|
|
266
|
+
} catch (err) {
|
|
267
|
+
this.injectFailCount += 1;
|
|
268
|
+
this.deliveryQueue.restoreClaim(claim);
|
|
269
|
+
await this.emitDelivery(evt, "error", err.message || "inject failed");
|
|
270
|
+
return 0;
|
|
324
271
|
}
|
|
325
|
-
return delivered;
|
|
326
272
|
}
|
|
327
273
|
|
|
328
274
|
/**
|
|
@@ -367,32 +313,10 @@ class AgentNotifier {
|
|
|
367
313
|
if (currentCount > this.lastCount) {
|
|
368
314
|
const newCount = currentCount - this.lastCount;
|
|
369
315
|
this.notify(newCount);
|
|
370
|
-
|
|
371
|
-
// 自动触发终端输入(非阻塞)
|
|
372
|
-
this.autoTriggerInput().catch(() => {
|
|
373
|
-
// 忽略触发失败
|
|
374
|
-
});
|
|
375
316
|
}
|
|
376
317
|
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
if (this.isUfooCodeSubscriber()) {
|
|
380
|
-
if (this.lastUbusWakeCount !== currentCount) {
|
|
381
|
-
try {
|
|
382
|
-
await this.autoTriggerInput();
|
|
383
|
-
this.lastUbusWakeCount = currentCount;
|
|
384
|
-
} catch {
|
|
385
|
-
// ignore delivery errors
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
} else {
|
|
389
|
-
try {
|
|
390
|
-
await this.deliverPending();
|
|
391
|
-
} catch {
|
|
392
|
-
// ignore delivery errors
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
318
|
+
// Delivery is owned by the project daemon scheduler. The notifier keeps
|
|
319
|
+
// terminal notifications, title badges, heartbeat, and activity fallback.
|
|
396
320
|
if (currentCount <= 0) {
|
|
397
321
|
this.lastUbusWakeCount = -1;
|
|
398
322
|
}
|
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
resolveDefaultManualBootstrap,
|
|
18
18
|
} = require("../prompts/defaultBootstrap");
|
|
19
19
|
const { buildPromptInjectionText } = require("../../coordination/bus/promptEnvelope");
|
|
20
|
+
const { DeliveryQueue } = require("../../coordination/bus/deliveryQueue");
|
|
20
21
|
|
|
21
22
|
function sleep(ms) {
|
|
22
23
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -40,37 +41,15 @@ function safeSubscriber(subscriber) {
|
|
|
40
41
|
return subscriber.replace(/:/g, "_");
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
content = fs.readFileSync(processingFile, "utf8");
|
|
51
|
-
readOk = true;
|
|
52
|
-
} catch {
|
|
53
|
-
try {
|
|
54
|
-
if (fs.existsSync(processingFile)) {
|
|
55
|
-
fs.renameSync(processingFile, queueFile);
|
|
56
|
-
}
|
|
57
|
-
} catch {
|
|
58
|
-
// ignore rollback errors
|
|
59
|
-
}
|
|
60
|
-
return [];
|
|
61
|
-
} finally {
|
|
62
|
-
if (readOk) {
|
|
63
|
-
try {
|
|
64
|
-
if (fs.existsSync(processingFile)) {
|
|
65
|
-
fs.rmSync(processingFile, { force: true });
|
|
66
|
-
}
|
|
67
|
-
} catch {
|
|
68
|
-
// ignore cleanup errors
|
|
69
|
-
}
|
|
70
|
-
}
|
|
44
|
+
function claimQueuedEvents(queueFile) {
|
|
45
|
+
const queue = new DeliveryQueue(queueFile);
|
|
46
|
+
const claims = [];
|
|
47
|
+
while (true) {
|
|
48
|
+
const claim = queue.claimNext();
|
|
49
|
+
if (!claim) break;
|
|
50
|
+
claims.push({ ...claim, queue });
|
|
71
51
|
}
|
|
72
|
-
|
|
73
|
-
return content.split(/\r?\n/).filter(Boolean);
|
|
52
|
+
return claims;
|
|
74
53
|
}
|
|
75
54
|
|
|
76
55
|
function stripAnsi(text) {
|
|
@@ -1044,28 +1023,29 @@ async function runPtyRunner({ projectRoot, agentType = "codex", extraArgs = [] }
|
|
|
1044
1023
|
lastHeartbeat = now;
|
|
1045
1024
|
}
|
|
1046
1025
|
|
|
1047
|
-
const
|
|
1048
|
-
if (
|
|
1026
|
+
const claims = claimQueuedEvents(queueFile);
|
|
1027
|
+
if (claims.length > 0) {
|
|
1049
1028
|
const agents = readAgentsMap(agentsFilePath);
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1029
|
+
for (const claim of claims) {
|
|
1030
|
+
const evt = claim.event;
|
|
1052
1031
|
try {
|
|
1053
|
-
|
|
1032
|
+
const input = buildPtyInputFromEvent(evt, subscriber, agents);
|
|
1033
|
+
if (!input) {
|
|
1034
|
+
claim.queue.completeClaim(claim);
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
const { raw, text } = input;
|
|
1038
|
+
if (messageQueue.length >= maxQueue) {
|
|
1039
|
+
messageQueue.shift();
|
|
1040
|
+
}
|
|
1041
|
+
const publisher = typeof evt.publisher === "object" && evt.publisher
|
|
1042
|
+
? (evt.publisher.subscriber || evt.publisher.nickname || "unknown")
|
|
1043
|
+
: (evt.publisher || "unknown");
|
|
1044
|
+
messageQueue.push({ publisher, raw, text });
|
|
1045
|
+
claim.queue.completeClaim(claim);
|
|
1054
1046
|
} catch {
|
|
1055
|
-
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
for (const evt of events) {
|
|
1059
|
-
const input = buildPtyInputFromEvent(evt, subscriber, agents);
|
|
1060
|
-
if (!input) continue;
|
|
1061
|
-
const { raw, text } = input;
|
|
1062
|
-
if (messageQueue.length >= maxQueue) {
|
|
1063
|
-
messageQueue.shift();
|
|
1047
|
+
claim.queue.restoreClaim(claim);
|
|
1064
1048
|
}
|
|
1065
|
-
const publisher = typeof evt.publisher === "object" && evt.publisher
|
|
1066
|
-
? (evt.publisher.subscriber || evt.publisher.nickname || "unknown")
|
|
1067
|
-
: (evt.publisher || "unknown");
|
|
1068
|
-
messageQueue.push({ publisher, raw, text });
|
|
1069
1049
|
}
|
|
1070
1050
|
}
|
|
1071
1051
|
processQueue();
|