@ouro.bot/cli 0.1.0-alpha.153 → 0.1.0-alpha.155
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/changelog.json +16 -0
- package/dist/heart/tool-activity-callbacks.js +36 -0
- package/dist/heart/tool-description.js +129 -0
- package/dist/senses/bluebubbles.js +73 -58
- package/dist/senses/cli.js +45 -23
- package/dist/senses/commands.js +37 -0
- package/dist/senses/pipeline.js +36 -0
- package/dist/senses/teams.js +46 -35
- package/package.json +1 -1
- package/dist/senses/debug-activity.js +0 -154
package/changelog.json
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.155",
|
|
6
|
+
"changes": [
|
|
7
|
+
"polish(debug): dynamic toggle, richer descriptions, human-readable results"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.154",
|
|
12
|
+
"changes": [
|
|
13
|
+
"feat: clean tool status messages \u2014 human-readable by default, /debug toggle",
|
|
14
|
+
"humanReadableToolDescription derives from tool name+args, not hardcoded map",
|
|
15
|
+
"Shared tool activity callbacks (DRY) \u2014 senses only provide render function",
|
|
16
|
+
"Slash command handling moved to pipeline \u2014 all senses get /debug for free",
|
|
17
|
+
"BlueBubbles: one clean iMessage per tool, not raw shared work: processing"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
4
20
|
{
|
|
5
21
|
"version": "0.1.0-alpha.153",
|
|
6
22
|
"changes": [
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createToolActivityCallbacks = createToolActivityCallbacks;
|
|
4
|
+
const tool_description_1 = require("./tool-description");
|
|
5
|
+
const runtime_1 = require("../nerves/runtime");
|
|
6
|
+
function createToolActivityCallbacks(options) {
|
|
7
|
+
(0, runtime_1.emitNervesEvent)({
|
|
8
|
+
component: "engine",
|
|
9
|
+
event: "engine.tool_activity_callbacks_created",
|
|
10
|
+
message: "tool activity callbacks initialized",
|
|
11
|
+
meta: {},
|
|
12
|
+
});
|
|
13
|
+
// Track the last description so we can reference it in END messages
|
|
14
|
+
let lastDescription = null;
|
|
15
|
+
return {
|
|
16
|
+
onToolStart(name, args) {
|
|
17
|
+
const description = (0, tool_description_1.humanReadableToolDescription)(name, args);
|
|
18
|
+
if (description === null)
|
|
19
|
+
return; // hidden tool (settle, rest, descend)
|
|
20
|
+
lastDescription = description;
|
|
21
|
+
options.onDescription(description);
|
|
22
|
+
},
|
|
23
|
+
onToolEnd(name, summary, success) {
|
|
24
|
+
const desc = lastDescription ?? name;
|
|
25
|
+
// Strip trailing "..." from description for the result line
|
|
26
|
+
const cleanDesc = desc.endsWith("...") ? desc.slice(0, -3) : desc;
|
|
27
|
+
if (!success) {
|
|
28
|
+
options.onFailure(`\u2717 ${cleanDesc} — ${summary}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (options.isDebug()) {
|
|
32
|
+
options.onResult(`\u2713 ${cleanDesc}`);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.humanReadableToolDescription = humanReadableToolDescription;
|
|
4
|
+
const runtime_1 = require("../nerves/runtime");
|
|
5
|
+
function basename(filePath) {
|
|
6
|
+
const idx = filePath.lastIndexOf("/");
|
|
7
|
+
return idx >= 0 ? filePath.slice(idx + 1) : filePath;
|
|
8
|
+
}
|
|
9
|
+
function truncate(text, max) {
|
|
10
|
+
if (text.length <= max)
|
|
11
|
+
return text;
|
|
12
|
+
return text.slice(0, max - 1) + "\u2026";
|
|
13
|
+
}
|
|
14
|
+
const TOOL_DESCRIPTIONS = {
|
|
15
|
+
// File operations
|
|
16
|
+
shell: (args) => {
|
|
17
|
+
const cmd = args.command;
|
|
18
|
+
if (!cmd)
|
|
19
|
+
return "running a command...";
|
|
20
|
+
return `running ${truncate(cmd, 50)}...`;
|
|
21
|
+
},
|
|
22
|
+
read_file: (args) => {
|
|
23
|
+
const fp = args.path || args.file_path;
|
|
24
|
+
if (!fp)
|
|
25
|
+
return "reading a file...";
|
|
26
|
+
return `reading ${basename(fp)}...`;
|
|
27
|
+
},
|
|
28
|
+
write_file: (args) => {
|
|
29
|
+
const fp = args.path || args.file_path;
|
|
30
|
+
if (!fp)
|
|
31
|
+
return "writing a file...";
|
|
32
|
+
return `writing ${basename(fp)}...`;
|
|
33
|
+
},
|
|
34
|
+
edit_file: (args) => {
|
|
35
|
+
const fp = args.path || args.file_path;
|
|
36
|
+
if (!fp)
|
|
37
|
+
return "editing a file...";
|
|
38
|
+
return `editing ${basename(fp)}...`;
|
|
39
|
+
},
|
|
40
|
+
glob: (args) => {
|
|
41
|
+
const p = args.pattern;
|
|
42
|
+
if (!p)
|
|
43
|
+
return "searching for files...";
|
|
44
|
+
return `searching for ${truncate(p, 40)}...`;
|
|
45
|
+
},
|
|
46
|
+
grep: (args) => {
|
|
47
|
+
const p = args.pattern;
|
|
48
|
+
if (!p)
|
|
49
|
+
return "searching code...";
|
|
50
|
+
return `searching code for '${truncate(p, 40)}'...`;
|
|
51
|
+
},
|
|
52
|
+
// Memory and knowledge
|
|
53
|
+
recall: (args) => {
|
|
54
|
+
const q = args.query;
|
|
55
|
+
if (!q)
|
|
56
|
+
return "searching memory...";
|
|
57
|
+
return `searching memory for '${truncate(q, 40)}'...`;
|
|
58
|
+
},
|
|
59
|
+
diary_write: (args) => {
|
|
60
|
+
const about = args.about;
|
|
61
|
+
return about ? `noting something about ${truncate(about, 30)}...` : "noting something down...";
|
|
62
|
+
},
|
|
63
|
+
save_friend_note: () => "making a note about you...",
|
|
64
|
+
get_friend_note: () => "checking my notes...",
|
|
65
|
+
load_skill: (args) => {
|
|
66
|
+
const name = args.name || args.skill;
|
|
67
|
+
return name ? `loading ${name} skill...` : "loading a skill...";
|
|
68
|
+
},
|
|
69
|
+
// Session and context
|
|
70
|
+
query_session: (args) => {
|
|
71
|
+
const mode = args.mode;
|
|
72
|
+
if (mode === "search")
|
|
73
|
+
return `searching session for '${truncate(args.query || "", 30)}'...`;
|
|
74
|
+
if (mode === "status")
|
|
75
|
+
return "checking inner dialog status...";
|
|
76
|
+
return "checking session history...";
|
|
77
|
+
},
|
|
78
|
+
web_search: (args) => {
|
|
79
|
+
const q = args.query;
|
|
80
|
+
return q ? `searching the web for '${truncate(q, 35)}'...` : "searching the web...";
|
|
81
|
+
},
|
|
82
|
+
coding_spawn: (args) => {
|
|
83
|
+
const runner = args.runner;
|
|
84
|
+
return runner ? `starting ${runner} coding session...` : "starting coding session...";
|
|
85
|
+
},
|
|
86
|
+
coding_status: () => "checking coding sessions...",
|
|
87
|
+
coding_tail: () => "reading coding output...",
|
|
88
|
+
coding_kill: () => "stopping coding session...",
|
|
89
|
+
bridge_manage: () => "managing conversation bridge...",
|
|
90
|
+
// Communication
|
|
91
|
+
send_message: (args) => {
|
|
92
|
+
const to = args.to;
|
|
93
|
+
return to ? `sending a message to ${to}...` : "sending a message...";
|
|
94
|
+
},
|
|
95
|
+
surface: () => "sharing a thought...",
|
|
96
|
+
// Metacognitive (agent's inner life)
|
|
97
|
+
ponder: (args) => {
|
|
98
|
+
const thought = args.thought;
|
|
99
|
+
return thought ? `thinking about ${truncate(thought, 40)}...` : "thinking deeper...";
|
|
100
|
+
},
|
|
101
|
+
observe: () => "listening...",
|
|
102
|
+
claude: () => "reasoning...",
|
|
103
|
+
set_reasoning_effort: (args) => {
|
|
104
|
+
const level = args.level;
|
|
105
|
+
return level ? `setting thinking depth to ${level}...` : "adjusting thinking depth...";
|
|
106
|
+
},
|
|
107
|
+
// Hidden — these ARE the response or end-of-turn
|
|
108
|
+
settle: () => null,
|
|
109
|
+
rest: () => null,
|
|
110
|
+
descend: () => null,
|
|
111
|
+
};
|
|
112
|
+
function humanReadableToolDescription(name, args) {
|
|
113
|
+
(0, runtime_1.emitNervesEvent)({
|
|
114
|
+
component: "engine",
|
|
115
|
+
event: "engine.tool_description",
|
|
116
|
+
message: "generated human-readable tool description",
|
|
117
|
+
meta: { tool: name },
|
|
118
|
+
});
|
|
119
|
+
const builder = TOOL_DESCRIPTIONS[name];
|
|
120
|
+
if (builder)
|
|
121
|
+
return builder(args);
|
|
122
|
+
// MCP tools: mcp__server__toolname — extract last segment
|
|
123
|
+
if (name.startsWith("mcp__")) {
|
|
124
|
+
const parts = name.split("__");
|
|
125
|
+
const toolName = parts[parts.length - 1];
|
|
126
|
+
return `using ${toolName}...`;
|
|
127
|
+
}
|
|
128
|
+
return `using ${name}...`;
|
|
129
|
+
}
|
|
@@ -57,7 +57,7 @@ const channel_1 = require("../mind/friends/channel");
|
|
|
57
57
|
const pending_1 = require("../mind/pending");
|
|
58
58
|
const prompt_1 = require("../mind/prompt");
|
|
59
59
|
const mcp_manager_1 = require("../repertoire/mcp-manager");
|
|
60
|
-
|
|
60
|
+
// getPhrases removed — no longer needed after debug-activity cleanup
|
|
61
61
|
const runtime_1 = require("../nerves/runtime");
|
|
62
62
|
const bluebubbles_model_1 = require("./bluebubbles-model");
|
|
63
63
|
const bluebubbles_client_1 = require("./bluebubbles-client");
|
|
@@ -65,7 +65,8 @@ const bluebubbles_inbound_log_1 = require("./bluebubbles-inbound-log");
|
|
|
65
65
|
const bluebubbles_mutation_log_1 = require("./bluebubbles-mutation-log");
|
|
66
66
|
const bluebubbles_runtime_state_1 = require("./bluebubbles-runtime-state");
|
|
67
67
|
const bluebubbles_session_cleanup_1 = require("./bluebubbles-session-cleanup");
|
|
68
|
-
const
|
|
68
|
+
const tool_activity_callbacks_1 = require("../heart/tool-activity-callbacks");
|
|
69
|
+
const commands_1 = require("./commands");
|
|
69
70
|
const trust_gate_1 = require("./trust-gate");
|
|
70
71
|
const pipeline_1 = require("./pipeline");
|
|
71
72
|
const bbFailoverStates = new Map();
|
|
@@ -362,63 +363,58 @@ function emitBlueBubblesMarkReadWarning(chat, error) {
|
|
|
362
363
|
}
|
|
363
364
|
function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat) {
|
|
364
365
|
let textBuffer = "";
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
startTypingOnModelStart: !isGroupChat,
|
|
370
|
-
startTypingOnFirstTextChunk: isGroupChat,
|
|
371
|
-
suppressInitialModelStatus: true,
|
|
372
|
-
suppressFollowupPhraseStatus: true,
|
|
373
|
-
transport: {
|
|
374
|
-
sendStatus: async (text) => {
|
|
375
|
-
const sent = await client.sendText({
|
|
376
|
-
chat,
|
|
377
|
-
text,
|
|
378
|
-
replyToMessageGuid: replyTarget.getReplyToMessageGuid(),
|
|
379
|
-
});
|
|
380
|
-
return sent.messageGuid;
|
|
381
|
-
},
|
|
382
|
-
editStatus: async (_messageGuid, text) => {
|
|
383
|
-
await client.sendText({
|
|
384
|
-
chat,
|
|
385
|
-
text,
|
|
386
|
-
replyToMessageGuid: replyTarget.getReplyToMessageGuid(),
|
|
387
|
-
});
|
|
388
|
-
},
|
|
389
|
-
setTyping: async (active) => {
|
|
390
|
-
if (!active) {
|
|
391
|
-
await client.setTyping(chat, false);
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
const [markReadResult, typingResult] = await Promise.allSettled([
|
|
395
|
-
client.markChatRead(chat),
|
|
396
|
-
client.setTyping(chat, true),
|
|
397
|
-
]);
|
|
398
|
-
if (markReadResult.status === "rejected") {
|
|
399
|
-
emitBlueBubblesMarkReadWarning(chat, markReadResult.reason);
|
|
400
|
-
}
|
|
401
|
-
if (typingResult.status === "rejected") {
|
|
402
|
-
throw typingResult.reason;
|
|
403
|
-
}
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
onTransportError: (operation, error) => {
|
|
366
|
+
let typingActive = false;
|
|
367
|
+
let queue = Promise.resolve();
|
|
368
|
+
function enqueue(operation, task) {
|
|
369
|
+
queue = queue.then(task).catch((error) => {
|
|
407
370
|
(0, runtime_1.emitNervesEvent)({
|
|
408
371
|
level: "warn",
|
|
409
372
|
component: "senses",
|
|
410
373
|
event: "senses.bluebubbles_activity_error",
|
|
411
374
|
message: "bluebubbles activity transport failed",
|
|
412
|
-
meta: {
|
|
413
|
-
operation,
|
|
414
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
415
|
-
},
|
|
375
|
+
meta: { operation, reason: error instanceof Error ? error.message : String(error) },
|
|
416
376
|
});
|
|
417
|
-
}
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
function startTypingNow() {
|
|
380
|
+
/* v8 ignore next -- defensive guard: callers already check typingActive @preserve */
|
|
381
|
+
if (typingActive)
|
|
382
|
+
return;
|
|
383
|
+
typingActive = true;
|
|
384
|
+
enqueue("typing_start", async () => {
|
|
385
|
+
const [markReadResult, typingResult] = await Promise.allSettled([
|
|
386
|
+
client.markChatRead(chat),
|
|
387
|
+
client.setTyping(chat, true),
|
|
388
|
+
]);
|
|
389
|
+
if (markReadResult.status === "rejected") {
|
|
390
|
+
emitBlueBubblesMarkReadWarning(chat, markReadResult.reason);
|
|
391
|
+
}
|
|
392
|
+
if (typingResult.status === "rejected") {
|
|
393
|
+
throw typingResult.reason;
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
function sendStatus(text) {
|
|
398
|
+
enqueue("send_status", async () => {
|
|
399
|
+
await client.sendText({
|
|
400
|
+
chat,
|
|
401
|
+
text,
|
|
402
|
+
replyToMessageGuid: replyTarget.getReplyToMessageGuid(),
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
const toolCallbacks = (0, tool_activity_callbacks_1.createToolActivityCallbacks)({
|
|
407
|
+
onDescription: (text) => sendStatus(text),
|
|
408
|
+
/* v8 ignore next -- onResult only called in debug mode; tested via tool-activity-callbacks.test.ts @preserve */
|
|
409
|
+
onResult: (text) => sendStatus(text),
|
|
410
|
+
/* v8 ignore next -- onFailure only called on tool failure; tested via tool-activity-callbacks.test.ts @preserve */
|
|
411
|
+
onFailure: (text) => sendStatus(text),
|
|
412
|
+
isDebug: commands_1.getDebugMode,
|
|
418
413
|
});
|
|
419
414
|
return {
|
|
420
415
|
onModelStart() {
|
|
421
|
-
|
|
416
|
+
if (!isGroupChat)
|
|
417
|
+
startTypingNow();
|
|
422
418
|
(0, runtime_1.emitNervesEvent)({
|
|
423
419
|
component: "senses",
|
|
424
420
|
event: "senses.bluebubbles_turn_start",
|
|
@@ -435,12 +431,16 @@ function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat) {
|
|
|
435
431
|
});
|
|
436
432
|
},
|
|
437
433
|
onTextChunk(text) {
|
|
438
|
-
|
|
434
|
+
if (isGroupChat && !typingActive)
|
|
435
|
+
startTypingNow();
|
|
439
436
|
textBuffer += text;
|
|
440
437
|
},
|
|
441
438
|
onReasoningChunk(_text) { },
|
|
442
439
|
onToolStart(name, _args) {
|
|
443
|
-
activity
|
|
440
|
+
// Tool activity is a reply commitment — start typing if not already
|
|
441
|
+
if (!typingActive)
|
|
442
|
+
startTypingNow();
|
|
443
|
+
toolCallbacks.onToolStart(name, _args);
|
|
444
444
|
(0, runtime_1.emitNervesEvent)({
|
|
445
445
|
component: "senses",
|
|
446
446
|
event: "senses.bluebubbles_tool_start",
|
|
@@ -449,7 +449,7 @@ function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat) {
|
|
|
449
449
|
});
|
|
450
450
|
},
|
|
451
451
|
onToolEnd(name, summary, success) {
|
|
452
|
-
|
|
452
|
+
toolCallbacks.onToolEnd(name, summary, success);
|
|
453
453
|
(0, runtime_1.emitNervesEvent)({
|
|
454
454
|
component: "senses",
|
|
455
455
|
event: "senses.bluebubbles_tool_end",
|
|
@@ -458,7 +458,7 @@ function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat) {
|
|
|
458
458
|
});
|
|
459
459
|
},
|
|
460
460
|
onError(error, severity) {
|
|
461
|
-
|
|
461
|
+
sendStatus(`\u2717 ${error.message}`);
|
|
462
462
|
(0, runtime_1.emitNervesEvent)({
|
|
463
463
|
level: severity === "terminal" ? "error" : "warn",
|
|
464
464
|
component: "senses",
|
|
@@ -471,14 +471,23 @@ function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat) {
|
|
|
471
471
|
textBuffer = "";
|
|
472
472
|
},
|
|
473
473
|
async flush() {
|
|
474
|
-
await
|
|
474
|
+
await queue;
|
|
475
475
|
const trimmed = textBuffer.trim();
|
|
476
476
|
if (!trimmed) {
|
|
477
|
-
|
|
477
|
+
if (typingActive) {
|
|
478
|
+
typingActive = false;
|
|
479
|
+
enqueue("typing_stop", async () => { await client.setTyping(chat, false); });
|
|
480
|
+
await queue;
|
|
481
|
+
}
|
|
478
482
|
return;
|
|
479
483
|
}
|
|
480
484
|
textBuffer = "";
|
|
481
|
-
|
|
485
|
+
/* v8 ignore next 4 -- branch: typing may already be stopped before flush @preserve */
|
|
486
|
+
if (typingActive) {
|
|
487
|
+
typingActive = false;
|
|
488
|
+
enqueue("typing_stop", async () => { await client.setTyping(chat, false); });
|
|
489
|
+
await queue;
|
|
490
|
+
}
|
|
482
491
|
await client.sendText({
|
|
483
492
|
chat,
|
|
484
493
|
text: trimmed,
|
|
@@ -486,7 +495,13 @@ function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat) {
|
|
|
486
495
|
});
|
|
487
496
|
},
|
|
488
497
|
async finish() {
|
|
489
|
-
|
|
498
|
+
if (!typingActive) {
|
|
499
|
+
await queue;
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
typingActive = false;
|
|
503
|
+
enqueue("typing_stop", async () => { await client.setTyping(chat, false); });
|
|
504
|
+
await queue;
|
|
490
505
|
},
|
|
491
506
|
};
|
|
492
507
|
}
|
package/dist/senses/cli.js
CHANGED
|
@@ -660,28 +660,30 @@ async function runCliSession(options) {
|
|
|
660
660
|
continue;
|
|
661
661
|
}
|
|
662
662
|
}
|
|
663
|
-
// Check for slash commands
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
if (dispatchResult.
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
663
|
+
// Check for slash commands (legacy path only — pipeline handles commands for runTurn path)
|
|
664
|
+
if (!options.runTurn) {
|
|
665
|
+
const parsed = (0, commands_1.parseSlashCommand)(input);
|
|
666
|
+
if (parsed) {
|
|
667
|
+
const dispatchResult = registry.dispatch(parsed.command, { channel: "cli" });
|
|
668
|
+
if (dispatchResult.handled && dispatchResult.result) {
|
|
669
|
+
if (dispatchResult.result.action === "exit") {
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
else if (dispatchResult.result.action === "new") {
|
|
673
|
+
messages.length = 0;
|
|
674
|
+
messages.push({ role: "system", content: await (0, prompt_1.buildSystem)("cli") });
|
|
675
|
+
await options.onNewSession?.();
|
|
676
|
+
// eslint-disable-next-line no-console -- terminal UX: session cleared
|
|
677
|
+
console.log("session cleared");
|
|
678
|
+
process.stdout.write(CLI_PROMPT);
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
else if (dispatchResult.result.action === "response") {
|
|
682
|
+
// eslint-disable-next-line no-console -- terminal UX: command dispatch result
|
|
683
|
+
console.log(dispatchResult.result.message || "");
|
|
684
|
+
process.stdout.write(CLI_PROMPT);
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
685
687
|
}
|
|
686
688
|
}
|
|
687
689
|
}
|
|
@@ -696,7 +698,27 @@ async function runCliSession(options) {
|
|
|
696
698
|
if (options.runTurn) {
|
|
697
699
|
// Pipeline-based turn: the runTurn callback handles user message assembly,
|
|
698
700
|
// pending drain, trust gate, runAgent, postTurn, and token accumulation.
|
|
701
|
+
// Commands (e.g. /debug, /exit, /new) are intercepted in the pipeline.
|
|
699
702
|
result = await options.runTurn(messages, input, cliCallbacks, currentAbort.signal, effectiveToolContext);
|
|
703
|
+
// Handle pipeline-intercepted commands with loop-control side effects
|
|
704
|
+
if (result?.turnOutcome === "command") {
|
|
705
|
+
if (result.commandAction === "exit") {
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
else if (result.commandAction === "new") {
|
|
709
|
+
messages.length = 0;
|
|
710
|
+
messages.push({ role: "system", content: await (0, prompt_1.buildSystem)("cli") });
|
|
711
|
+
await options.onNewSession?.();
|
|
712
|
+
// eslint-disable-next-line no-console -- terminal UX: session cleared
|
|
713
|
+
console.log("session cleared");
|
|
714
|
+
process.stdout.write(CLI_PROMPT);
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
// For "response" commands: the pipeline already emitted the response via onTextChunk
|
|
718
|
+
cliCallbacks.flushMarkdown();
|
|
719
|
+
process.stdout.write(CLI_PROMPT);
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
700
722
|
}
|
|
701
723
|
else {
|
|
702
724
|
// Legacy path: inline runAgent (used by adoption specialist and tests)
|
|
@@ -898,7 +920,7 @@ async function main(agentName, options) {
|
|
|
898
920
|
process.stdout.write(`${result.gateResult.autoReply}\n`);
|
|
899
921
|
}
|
|
900
922
|
}
|
|
901
|
-
return { usage: result.usage };
|
|
923
|
+
return { usage: result.usage, turnOutcome: result.turnOutcome, commandAction: result.commandAction };
|
|
902
924
|
},
|
|
903
925
|
onNewSession: () => {
|
|
904
926
|
(0, context_1.deleteSession)(sessPath);
|
package/dist/senses/commands.js
CHANGED
|
@@ -3,7 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createCommandRegistry = createCommandRegistry;
|
|
4
4
|
exports.getToolChoiceRequired = getToolChoiceRequired;
|
|
5
5
|
exports.resetToolChoiceRequired = resetToolChoiceRequired;
|
|
6
|
+
exports.getDebugMode = getDebugMode;
|
|
7
|
+
exports.resetDebugMode = resetDebugMode;
|
|
6
8
|
exports.registerDefaultCommands = registerDefaultCommands;
|
|
9
|
+
exports.getSharedCommandRegistry = getSharedCommandRegistry;
|
|
10
|
+
exports.resetSharedCommandRegistry = resetSharedCommandRegistry;
|
|
7
11
|
exports.parseSlashCommand = parseSlashCommand;
|
|
8
12
|
const identity_1 = require("../heart/identity");
|
|
9
13
|
const runtime_1 = require("../nerves/runtime");
|
|
@@ -35,6 +39,14 @@ function getToolChoiceRequired() {
|
|
|
35
39
|
function resetToolChoiceRequired() {
|
|
36
40
|
_toolChoiceRequired = false;
|
|
37
41
|
}
|
|
42
|
+
// Module-level toggle for debug mode
|
|
43
|
+
let _debugMode = false;
|
|
44
|
+
function getDebugMode() {
|
|
45
|
+
return _debugMode;
|
|
46
|
+
}
|
|
47
|
+
function resetDebugMode() {
|
|
48
|
+
_debugMode = false;
|
|
49
|
+
}
|
|
38
50
|
function registerDefaultCommands(registry) {
|
|
39
51
|
(0, runtime_1.emitNervesEvent)({
|
|
40
52
|
event: "repertoire.load_start",
|
|
@@ -73,6 +85,20 @@ function registerDefaultCommands(registry) {
|
|
|
73
85
|
return { action: "response", message: `tool-required mode: ${_toolChoiceRequired ? "ON" : "OFF"}` };
|
|
74
86
|
},
|
|
75
87
|
});
|
|
88
|
+
registry.register({
|
|
89
|
+
name: "debug",
|
|
90
|
+
description: "toggle debug mode — see more detail about what I'm doing",
|
|
91
|
+
channels: ["cli", "teams", "bluebubbles", "mcp"],
|
|
92
|
+
handler: () => {
|
|
93
|
+
_debugMode = !_debugMode;
|
|
94
|
+
return {
|
|
95
|
+
action: "response",
|
|
96
|
+
message: _debugMode
|
|
97
|
+
? "debug mode on — you'll see more detail about what I'm doing"
|
|
98
|
+
: "debug mode off — back to clean output",
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
});
|
|
76
102
|
(0, runtime_1.emitNervesEvent)({
|
|
77
103
|
event: "repertoire.load_end",
|
|
78
104
|
component: "repertoire",
|
|
@@ -80,6 +106,17 @@ function registerDefaultCommands(registry) {
|
|
|
80
106
|
meta: {},
|
|
81
107
|
});
|
|
82
108
|
}
|
|
109
|
+
let _sharedRegistry = null;
|
|
110
|
+
function getSharedCommandRegistry() {
|
|
111
|
+
if (!_sharedRegistry) {
|
|
112
|
+
_sharedRegistry = createCommandRegistry();
|
|
113
|
+
registerDefaultCommands(_sharedRegistry);
|
|
114
|
+
}
|
|
115
|
+
return _sharedRegistry;
|
|
116
|
+
}
|
|
117
|
+
function resetSharedCommandRegistry() {
|
|
118
|
+
_sharedRegistry = null;
|
|
119
|
+
}
|
|
83
120
|
function parseSlashCommand(input) {
|
|
84
121
|
const trimmed = input.trim();
|
|
85
122
|
if (!trimmed.startsWith("/"))
|
package/dist/senses/pipeline.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.handleInboundTurn = handleInboundTurn;
|
|
9
9
|
const runtime_1 = require("../nerves/runtime");
|
|
10
|
+
const commands_1 = require("./commands");
|
|
10
11
|
const continuity_1 = require("./continuity");
|
|
11
12
|
const manager_1 = require("../heart/bridges/manager");
|
|
12
13
|
const identity_1 = require("../heart/identity");
|
|
@@ -167,6 +168,41 @@ async function handleInboundTurn(input) {
|
|
|
167
168
|
// Switch failed OR succeeded — either way, fall through to normal processing.
|
|
168
169
|
}
|
|
169
170
|
}
|
|
171
|
+
// Step 0b: Slash command interception (before friend resolution / agent turn)
|
|
172
|
+
{
|
|
173
|
+
const firstUserMsg = input.messages.find((m) => m.role === "user");
|
|
174
|
+
const userText = firstUserMsg
|
|
175
|
+
? (typeof firstUserMsg.content === "string"
|
|
176
|
+
? firstUserMsg.content
|
|
177
|
+
: Array.isArray(firstUserMsg.content)
|
|
178
|
+
? firstUserMsg.content.find((p) => p.type === "text")?.text ?? ""
|
|
179
|
+
: /* v8 ignore next -- defensive: content is always string or array @preserve */ "")
|
|
180
|
+
: "";
|
|
181
|
+
const parsed = (0, commands_1.parseSlashCommand)(userText);
|
|
182
|
+
if (parsed) {
|
|
183
|
+
const registry = (0, commands_1.getSharedCommandRegistry)();
|
|
184
|
+
const dispatchResult = registry.dispatch(parsed.command, { channel: input.channel });
|
|
185
|
+
if (dispatchResult.handled && dispatchResult.result) {
|
|
186
|
+
(0, runtime_1.emitNervesEvent)({
|
|
187
|
+
component: "senses",
|
|
188
|
+
event: "senses.pipeline_command",
|
|
189
|
+
message: `slash command intercepted: /${parsed.command}`,
|
|
190
|
+
meta: { command: parsed.command, channel: input.channel },
|
|
191
|
+
});
|
|
192
|
+
if (dispatchResult.result.message) {
|
|
193
|
+
input.callbacks.onTextChunk(dispatchResult.result.message);
|
|
194
|
+
}
|
|
195
|
+
// Return a minimal result — no agent turn, no session write
|
|
196
|
+
const resolvedContext = await input.friendResolver.resolve();
|
|
197
|
+
return {
|
|
198
|
+
resolvedContext,
|
|
199
|
+
gateResult: { allowed: true },
|
|
200
|
+
turnOutcome: "command",
|
|
201
|
+
commandAction: dispatchResult.result.action,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
170
206
|
// Step 1: Resolve friend
|
|
171
207
|
const resolvedContext = await input.friendResolver.resolve();
|
|
172
208
|
(0, runtime_1.emitNervesEvent)({
|
package/dist/senses/teams.js
CHANGED
|
@@ -70,6 +70,8 @@ const turn_coordinator_1 = require("../heart/turn-coordinator");
|
|
|
70
70
|
const identity_1 = require("../heart/identity");
|
|
71
71
|
const mcp_manager_1 = require("../repertoire/mcp-manager");
|
|
72
72
|
const progress_story_1 = require("../heart/progress-story");
|
|
73
|
+
const tool_activity_callbacks_1 = require("../heart/tool-activity-callbacks");
|
|
74
|
+
const commands_2 = require("./commands");
|
|
73
75
|
const http = __importStar(require("http"));
|
|
74
76
|
const path = __importStar(require("path"));
|
|
75
77
|
const trust_gate_1 = require("./trust-gate");
|
|
@@ -425,35 +427,37 @@ function createTeamsCallbacks(stream, controller, sendMessage, options) {
|
|
|
425
427
|
onClearText: () => {
|
|
426
428
|
textBuffer = "";
|
|
427
429
|
},
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
430
|
+
...(() => {
|
|
431
|
+
const toolCbs = (0, tool_activity_callbacks_1.createToolActivityCallbacks)({
|
|
432
|
+
onDescription: (text) => safeUpdate(text),
|
|
433
|
+
/* v8 ignore next -- onResult only called in debug mode; tested via tool-activity-callbacks.test.ts @preserve */
|
|
434
|
+
onResult: (text) => safeUpdate(text),
|
|
435
|
+
/* v8 ignore next -- onFailure tested via onToolEnd failure test @preserve */
|
|
436
|
+
onFailure: (text) => safeUpdate(text),
|
|
437
|
+
isDebug: commands_2.getDebugMode,
|
|
438
|
+
});
|
|
439
|
+
return {
|
|
440
|
+
onToolStart: (name, args) => {
|
|
441
|
+
stopPhraseRotation();
|
|
442
|
+
// Force-flush any accumulated text, bypassing MIN_INITIAL_CHARS threshold
|
|
443
|
+
firstContentEmitted = true;
|
|
444
|
+
flushTextBuffer();
|
|
445
|
+
// Emit a placeholder to satisfy the 15s Copilot timeout for initial
|
|
446
|
+
// stream.emit(). Without this, long tool chains (e.g. ADO batch ops)
|
|
447
|
+
// never emit before the timeout and the user sees "this response was
|
|
448
|
+
// stopped". The placeholder is replaced by actual content on next emit.
|
|
449
|
+
// https://learn.microsoft.com/en-us/answers/questions/2288017/m365-custom-engine-agents-timeout-message-after-15
|
|
450
|
+
if (!streamHasContent)
|
|
451
|
+
safeEmit("\u23f3");
|
|
452
|
+
toolCbs.onToolStart(name, args);
|
|
453
|
+
hadToolRun = true;
|
|
454
|
+
},
|
|
455
|
+
onToolEnd: (name, summary, success) => {
|
|
456
|
+
stopPhraseRotation();
|
|
457
|
+
toolCbs.onToolEnd(name, summary, success);
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
})(),
|
|
457
461
|
onKick: () => {
|
|
458
462
|
stopPhraseRotation();
|
|
459
463
|
const msg = (0, format_1.formatKick)();
|
|
@@ -568,6 +572,7 @@ function createTeamsCommandRegistry() {
|
|
|
568
572
|
(0, commands_1.registerDefaultCommands)(registry);
|
|
569
573
|
return registry;
|
|
570
574
|
}
|
|
575
|
+
/* v8 ignore start -- superseding follow-up slash command handler; tested via startTeamsApp integration tests @preserve */
|
|
571
576
|
function handleTeamsSlashCommand(text, registry, friendId, conversationId, stream, emitResponse = true) {
|
|
572
577
|
const parsed = (0, commands_1.parseSlashCommand)(text);
|
|
573
578
|
if (!parsed)
|
|
@@ -591,6 +596,7 @@ function handleTeamsSlashCommand(text, registry, friendId, conversationId, strea
|
|
|
591
596
|
}
|
|
592
597
|
return null;
|
|
593
598
|
}
|
|
599
|
+
/* v8 ignore stop */
|
|
594
600
|
// Handle an incoming Teams message
|
|
595
601
|
async function handleTeamsMessage(text, stream, conversationId, teamsContext, sendMessage, reactionOverrides) {
|
|
596
602
|
const turnKey = teamsTurnKey(conversationId);
|
|
@@ -619,11 +625,6 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
|
|
|
619
625
|
// Pre-resolve friend for session path + slash commands (pipeline will re-use the cached result)
|
|
620
626
|
const resolvedContext = await resolver.resolve();
|
|
621
627
|
const friendId = resolvedContext.friend.id;
|
|
622
|
-
const registry = createTeamsCommandRegistry();
|
|
623
|
-
// Check for slash commands (before pipeline -- these are transport-level concerns)
|
|
624
|
-
if (handleTeamsSlashCommand(text, registry, friendId, conversationId, stream)) {
|
|
625
|
-
return;
|
|
626
|
-
}
|
|
627
628
|
// ── Teams adapter concerns: controller, callbacks, session path ──────────
|
|
628
629
|
const controller = new AbortController();
|
|
629
630
|
const channelConfig = (0, config_2.getTeamsChannelConfig)();
|
|
@@ -729,6 +730,16 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
|
|
|
729
730
|
runAgentOptions: agentOptions,
|
|
730
731
|
failoverState: teamsFailoverState,
|
|
731
732
|
});
|
|
733
|
+
// ── Handle pipeline-intercepted commands ────────────────────────
|
|
734
|
+
if (result.turnOutcome === "command") {
|
|
735
|
+
if (result.commandAction === "new") {
|
|
736
|
+
(0, context_1.deleteSession)(sessPath);
|
|
737
|
+
stream.emit("session cleared");
|
|
738
|
+
}
|
|
739
|
+
// For "response" commands: pipeline already emitted the response via onTextChunk
|
|
740
|
+
await callbacks.flush();
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
732
743
|
/* v8 ignore start -- failover display: tested via pipeline integration tests @preserve */
|
|
733
744
|
if (result.failoverMessage) {
|
|
734
745
|
stream.emit(result.failoverMessage);
|
|
@@ -776,7 +787,7 @@ async function handleTeamsMessage(text, stream, conversationId, teamsContext, se
|
|
|
776
787
|
currentText = replayTail;
|
|
777
788
|
continue;
|
|
778
789
|
}
|
|
779
|
-
if (handleTeamsSlashCommand(supersedingFollowUp.text,
|
|
790
|
+
if (handleTeamsSlashCommand(supersedingFollowUp.text, createTeamsCommandRegistry(), friendId, conversationId, stream, false)) {
|
|
780
791
|
return;
|
|
781
792
|
}
|
|
782
793
|
currentText = supersedingFollowUp.text;
|
package/package.json
CHANGED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createDebugActivityController = createDebugActivityController;
|
|
4
|
-
const format_1 = require("../mind/format");
|
|
5
|
-
const phrases_1 = require("../mind/phrases");
|
|
6
|
-
const runtime_1 = require("../nerves/runtime");
|
|
7
|
-
const progress_story_1 = require("../heart/progress-story");
|
|
8
|
-
function createDebugActivityController(options) {
|
|
9
|
-
let queue = Promise.resolve();
|
|
10
|
-
let statusMessageGuid;
|
|
11
|
-
let typingActive = false;
|
|
12
|
-
let hadToolRun = false;
|
|
13
|
-
let followupShown = false;
|
|
14
|
-
let lastPhrase = "";
|
|
15
|
-
function reportTransportError(operation, error) {
|
|
16
|
-
(0, runtime_1.emitNervesEvent)({
|
|
17
|
-
level: "warn",
|
|
18
|
-
component: "senses",
|
|
19
|
-
event: "senses.debug_activity_transport_error",
|
|
20
|
-
message: "debug activity transport failed",
|
|
21
|
-
meta: {
|
|
22
|
-
operation,
|
|
23
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
options.onTransportError?.(operation, error);
|
|
27
|
-
}
|
|
28
|
-
function enqueue(operation, task) {
|
|
29
|
-
queue = queue
|
|
30
|
-
.then(task)
|
|
31
|
-
.catch((error) => {
|
|
32
|
-
reportTransportError(operation, error);
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
function nextPhrase(pool) {
|
|
36
|
-
const phrase = (0, phrases_1.pickPhrase)(pool, lastPhrase);
|
|
37
|
-
lastPhrase = phrase;
|
|
38
|
-
return phrase;
|
|
39
|
-
}
|
|
40
|
-
function startTypingNow() {
|
|
41
|
-
if (typingActive) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
typingActive = true;
|
|
45
|
-
enqueue("typing_start", async () => {
|
|
46
|
-
await options.transport.setTyping(true);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
function setStatus(text) {
|
|
50
|
-
(0, runtime_1.emitNervesEvent)({
|
|
51
|
-
component: "senses",
|
|
52
|
-
event: "senses.debug_activity_update",
|
|
53
|
-
message: "debug activity status updated",
|
|
54
|
-
meta: {
|
|
55
|
-
hasStatusGuid: Boolean(statusMessageGuid),
|
|
56
|
-
textLength: text.length,
|
|
57
|
-
},
|
|
58
|
-
});
|
|
59
|
-
const shouldStartTyping = !typingActive;
|
|
60
|
-
if (shouldStartTyping) {
|
|
61
|
-
typingActive = true;
|
|
62
|
-
}
|
|
63
|
-
enqueue("status_update", async () => {
|
|
64
|
-
if (statusMessageGuid) {
|
|
65
|
-
await options.transport.editStatus(statusMessageGuid, text);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
statusMessageGuid = await options.transport.sendStatus(text);
|
|
69
|
-
}
|
|
70
|
-
if (shouldStartTyping) {
|
|
71
|
-
await options.transport.setTyping(true);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
return {
|
|
76
|
-
onModelStart() {
|
|
77
|
-
const pool = hadToolRun ? options.followupPhrases : options.thinkingPhrases;
|
|
78
|
-
if (options.startTypingOnModelStart) {
|
|
79
|
-
startTypingNow();
|
|
80
|
-
}
|
|
81
|
-
if (options.suppressInitialModelStatus && !statusMessageGuid && !hadToolRun) {
|
|
82
|
-
nextPhrase(pool);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
setStatus((0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
|
|
86
|
-
scope: "shared-work",
|
|
87
|
-
phase: "processing",
|
|
88
|
-
objective: `${nextPhrase(pool)}...`,
|
|
89
|
-
})));
|
|
90
|
-
},
|
|
91
|
-
onToolStart(name, args) {
|
|
92
|
-
hadToolRun = true;
|
|
93
|
-
followupShown = false;
|
|
94
|
-
const argSummary = Object.values(args).join(", ");
|
|
95
|
-
const detail = argSummary ? ` (${argSummary})` : "";
|
|
96
|
-
setStatus((0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
|
|
97
|
-
scope: "shared-work",
|
|
98
|
-
phase: "processing",
|
|
99
|
-
objective: `running ${name}${detail}...`,
|
|
100
|
-
})));
|
|
101
|
-
},
|
|
102
|
-
onToolEnd(name, summary, success) {
|
|
103
|
-
hadToolRun = true;
|
|
104
|
-
followupShown = false;
|
|
105
|
-
setStatus((0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
|
|
106
|
-
scope: "shared-work",
|
|
107
|
-
phase: "processing",
|
|
108
|
-
objective: (0, format_1.formatToolResult)(name, summary, success),
|
|
109
|
-
})));
|
|
110
|
-
},
|
|
111
|
-
onTextChunk(text) {
|
|
112
|
-
if (!text) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (!hadToolRun && options.startTypingOnFirstTextChunk) {
|
|
116
|
-
startTypingNow();
|
|
117
|
-
}
|
|
118
|
-
if (!hadToolRun || followupShown) {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
followupShown = true;
|
|
122
|
-
if (options.suppressFollowupPhraseStatus) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
setStatus((0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
|
|
126
|
-
scope: "shared-work",
|
|
127
|
-
phase: "processing",
|
|
128
|
-
objective: `${nextPhrase(options.followupPhrases)}...`,
|
|
129
|
-
})));
|
|
130
|
-
},
|
|
131
|
-
onError(error) {
|
|
132
|
-
setStatus((0, progress_story_1.renderProgressStory)((0, progress_story_1.buildProgressStory)({
|
|
133
|
-
scope: "shared-work",
|
|
134
|
-
phase: "errored",
|
|
135
|
-
outcomeText: (0, format_1.formatError)(error),
|
|
136
|
-
})));
|
|
137
|
-
this.finish();
|
|
138
|
-
},
|
|
139
|
-
async drain() {
|
|
140
|
-
await queue;
|
|
141
|
-
},
|
|
142
|
-
async finish() {
|
|
143
|
-
if (!typingActive) {
|
|
144
|
-
await queue;
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
typingActive = false;
|
|
148
|
-
enqueue("typing_stop", async () => {
|
|
149
|
-
await options.transport.setTyping(false);
|
|
150
|
-
});
|
|
151
|
-
await queue;
|
|
152
|
-
},
|
|
153
|
-
};
|
|
154
|
-
}
|