@oyasmi/pipiclaw 0.6.5 → 0.6.6-beta.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/dist/agent/channel-runner.d.ts +2 -0
- package/dist/agent/channel-runner.js +41 -6
- package/dist/agent/session-events.js +2 -0
- package/dist/runtime/bootstrap.js +20 -11
- package/dist/runtime/delivery.js +22 -4
- package/dist/runtime/dingtalk.d.ts +2 -1
- package/dist/runtime/dingtalk.js +25 -13
- package/package.json +1 -1
|
@@ -24,6 +24,8 @@ export declare class ChannelRunner implements AgentRunner {
|
|
|
24
24
|
private activeModel;
|
|
25
25
|
private currentSkills;
|
|
26
26
|
private firstTurnMemoryBootstrapPending;
|
|
27
|
+
private acceptingBusyMessages;
|
|
28
|
+
private agentLoopStarted;
|
|
27
29
|
private runState;
|
|
28
30
|
constructor(sandboxConfig: SandboxConfig, channelId: string, channelDir: string);
|
|
29
31
|
run(ctx: DingTalkContext, store: ChannelStore): Promise<{
|
|
@@ -53,6 +53,8 @@ function asSdkSettingsManager(manager) {
|
|
|
53
53
|
export class ChannelRunner {
|
|
54
54
|
constructor(sandboxConfig, channelId, channelDir) {
|
|
55
55
|
this.firstTurnMemoryBootstrapPending = true;
|
|
56
|
+
this.acceptingBusyMessages = false;
|
|
57
|
+
this.agentLoopStarted = false;
|
|
56
58
|
// --- Per run ---
|
|
57
59
|
this.runState = createEmptyRunState();
|
|
58
60
|
this.sandboxConfig = sandboxConfig;
|
|
@@ -169,8 +171,11 @@ export class ChannelRunner {
|
|
|
169
171
|
// === Public API ===
|
|
170
172
|
async run(ctx, store) {
|
|
171
173
|
this.resetRunState(ctx, store);
|
|
174
|
+
this.acceptingBusyMessages = true;
|
|
175
|
+
this.agentLoopStarted = false;
|
|
172
176
|
const runQueue = createRunQueue(ctx);
|
|
173
177
|
this.runState.queue = runQueue.queue;
|
|
178
|
+
let promptSubmitted = false;
|
|
174
179
|
try {
|
|
175
180
|
await this.ensureSessionReady();
|
|
176
181
|
this.memoryLifecycle.noteUserTurnStarted();
|
|
@@ -225,6 +230,7 @@ export class ChannelRunner {
|
|
|
225
230
|
}
|
|
226
231
|
await this.sessionResourceGate.runPrompt(async () => {
|
|
227
232
|
await this.session.prompt(promptText);
|
|
233
|
+
promptSubmitted = true;
|
|
228
234
|
});
|
|
229
235
|
}
|
|
230
236
|
catch (err) {
|
|
@@ -233,6 +239,15 @@ export class ChannelRunner {
|
|
|
233
239
|
log.logWarning(`[${this.channelId}] Runner failed`, this.runState.errorMessage);
|
|
234
240
|
}
|
|
235
241
|
finally {
|
|
242
|
+
this.acceptingBusyMessages = false;
|
|
243
|
+
this.agentLoopStarted = false;
|
|
244
|
+
if (!promptSubmitted) {
|
|
245
|
+
const discarded = this.session.clearQueue();
|
|
246
|
+
const discardedCount = discarded.steering.length + discarded.followUp.length;
|
|
247
|
+
if (discardedCount > 0) {
|
|
248
|
+
log.logWarning(`[${this.channelId}] Discarded ${discardedCount} queued busy message(s) after run setup failed`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
236
251
|
await runQueue.drain();
|
|
237
252
|
const finalOutcome = this.runState.finalOutcome;
|
|
238
253
|
const finalOutcomeText = getFinalOutcomeText(finalOutcome);
|
|
@@ -423,20 +438,37 @@ export class ChannelRunner {
|
|
|
423
438
|
return `[${timestamp}] [${userName || "unknown"}]: ${text}`;
|
|
424
439
|
}
|
|
425
440
|
async queueBusyMessage(delivery, text, userName) {
|
|
426
|
-
if (!this.
|
|
441
|
+
if (!this.acceptingBusyMessages) {
|
|
442
|
+
throw new Error("No task is currently running.");
|
|
443
|
+
}
|
|
444
|
+
if (this.agentLoopStarted && !this.session.isStreaming) {
|
|
427
445
|
throw new Error("No task is currently running.");
|
|
428
446
|
}
|
|
447
|
+
await this.ensureSessionReady();
|
|
429
448
|
const clippedText = clipUserInput(text, MAX_USER_MESSAGE_CHARS);
|
|
430
449
|
if (clippedText !== text.trim()) {
|
|
431
450
|
log.logWarning(`[${this.channelId}] Queued message exceeded ${MAX_USER_MESSAGE_CHARS} chars and was clipped`);
|
|
432
451
|
}
|
|
433
452
|
const queuedMessage = this.formatUserMessage(clippedText, userName);
|
|
434
453
|
await this.maybeRunPreventiveCompactionForIncomingText(queuedMessage);
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
454
|
+
if (!this.acceptingBusyMessages) {
|
|
455
|
+
throw new Error("No task is currently running.");
|
|
456
|
+
}
|
|
457
|
+
if (this.agentLoopStarted && !this.session.isStreaming) {
|
|
458
|
+
throw new Error("No task is currently running.");
|
|
459
|
+
}
|
|
460
|
+
const queueMessage = async () => {
|
|
461
|
+
if (this.agentLoopStarted && !this.session.isStreaming) {
|
|
462
|
+
throw new Error("No task is currently running.");
|
|
463
|
+
}
|
|
464
|
+
if (delivery === "followUp") {
|
|
465
|
+
await this.session.followUp(queuedMessage);
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
await this.session.steer(queuedMessage);
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
await this.sessionResourceGate.runPrompt(queueMessage);
|
|
440
472
|
}
|
|
441
473
|
resetRunState(ctx, store) {
|
|
442
474
|
this.runState = createEmptyRunState();
|
|
@@ -564,6 +596,9 @@ export class ChannelRunner {
|
|
|
564
596
|
// === Session event subscription ===
|
|
565
597
|
subscribeToSessionEvents() {
|
|
566
598
|
this.session.subscribe(async (event) => {
|
|
599
|
+
if (isRecord(event) && event.type === "message_start") {
|
|
600
|
+
this.agentLoopStarted = true;
|
|
601
|
+
}
|
|
567
602
|
if (isRecord(event) && "reason" in event && event.reason === "new") {
|
|
568
603
|
this.firstTurnMemoryBootstrapPending = true;
|
|
569
604
|
}
|
|
@@ -127,6 +127,7 @@ export async function handleSessionEvent(event, context) {
|
|
|
127
127
|
const commandResultText = extractCustomCommandResultText(event.message);
|
|
128
128
|
if (commandResultText) {
|
|
129
129
|
runState.finalOutcome = { kind: "final", text: commandResultText };
|
|
130
|
+
runState.finalResponseDelivered = false;
|
|
130
131
|
log.logResponse(logCtx, commandResultText);
|
|
131
132
|
queue.enqueue(async () => {
|
|
132
133
|
const delivered = await ctx.respondPlain(commandResultText);
|
|
@@ -195,6 +196,7 @@ export async function handleSessionEvent(event, context) {
|
|
|
195
196
|
return;
|
|
196
197
|
}
|
|
197
198
|
runState.finalOutcome = { kind: "final", text: finalText };
|
|
199
|
+
runState.finalResponseDelivered = false;
|
|
198
200
|
memoryLifecycle.noteCompletedAssistantTurn();
|
|
199
201
|
log.logResponse(logCtx, finalText);
|
|
200
202
|
queue.enqueue(async () => {
|
|
@@ -341,6 +341,9 @@ function flushInactiveChannelMemory(channelStates) {
|
|
|
341
341
|
}
|
|
342
342
|
return flushes;
|
|
343
343
|
}
|
|
344
|
+
function isNoRunningTaskQueueError(err) {
|
|
345
|
+
return err instanceof Error && err.message === "No task is currently running.";
|
|
346
|
+
}
|
|
344
347
|
export function createRuntimeContext(options) {
|
|
345
348
|
const startServices = options.startServices ?? true;
|
|
346
349
|
const registerSignalHandlers = options.registerSignalHandlers ?? true;
|
|
@@ -390,20 +393,10 @@ export function createRuntimeContext(options) {
|
|
|
390
393
|
},
|
|
391
394
|
async handleBusyMessage(event, bot, mode, queueText) {
|
|
392
395
|
if (shuttingDown) {
|
|
393
|
-
return;
|
|
396
|
+
return true;
|
|
394
397
|
}
|
|
395
398
|
const state = getState(event.channelId);
|
|
396
399
|
const trimmedQueueText = queueText.trim();
|
|
397
|
-
await archiveIncomingMessage(event.channelId, {
|
|
398
|
-
date: new Date().toISOString(),
|
|
399
|
-
ts: event.ts,
|
|
400
|
-
user: event.user,
|
|
401
|
-
userName: event.userName,
|
|
402
|
-
text: event.text,
|
|
403
|
-
isBot: false,
|
|
404
|
-
deliveryMode: mode,
|
|
405
|
-
skipContextSync: true,
|
|
406
|
-
}, `${mode} message`);
|
|
407
400
|
try {
|
|
408
401
|
if (mode === "followUp") {
|
|
409
402
|
await state.runner.queueFollowUp(trimmedQueueText, event.userName);
|
|
@@ -411,6 +404,16 @@ export function createRuntimeContext(options) {
|
|
|
411
404
|
else {
|
|
412
405
|
await state.runner.queueSteer(trimmedQueueText, event.userName);
|
|
413
406
|
}
|
|
407
|
+
await archiveIncomingMessage(event.channelId, {
|
|
408
|
+
date: new Date().toISOString(),
|
|
409
|
+
ts: event.ts,
|
|
410
|
+
user: event.user,
|
|
411
|
+
userName: event.userName,
|
|
412
|
+
text: event.text,
|
|
413
|
+
isBot: false,
|
|
414
|
+
deliveryMode: mode,
|
|
415
|
+
skipContextSync: true,
|
|
416
|
+
}, `${mode} message`);
|
|
414
417
|
const confirmation = mode === "followUp"
|
|
415
418
|
? event.text.trim().startsWith("/")
|
|
416
419
|
? "Queued as follow-up. I’ll handle it after the current task completes."
|
|
@@ -420,11 +423,17 @@ export function createRuntimeContext(options) {
|
|
|
420
423
|
: "Queued as steer. I’ll apply this after the current tool step finishes. Use `/followup <message>` to queue it after completion.";
|
|
421
424
|
await bot.sendPlain(event.channelId, confirmation);
|
|
422
425
|
log.logInfo(`[${event.channelId}] Queued ${mode}: ${trimmedQueueText.substring(0, 80)}`);
|
|
426
|
+
return true;
|
|
423
427
|
}
|
|
424
428
|
catch (err) {
|
|
425
429
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
430
|
+
if (isNoRunningTaskQueueError(err)) {
|
|
431
|
+
log.logInfo(`[${event.channelId}] Busy ${mode} window closed; requeueing as a normal message`);
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
426
434
|
log.logWarning(`[${event.channelId}] Failed to queue ${mode}`, errMsg);
|
|
427
435
|
await bot.sendPlain(event.channelId, `Could not queue this message: ${errMsg}`);
|
|
436
|
+
return true;
|
|
428
437
|
}
|
|
429
438
|
},
|
|
430
439
|
async handleEvent(event, bot, _isEvent) {
|
package/dist/runtime/delivery.js
CHANGED
|
@@ -96,10 +96,28 @@ class ChannelDeliveryController {
|
|
|
96
96
|
log.logWarning(`[${this.event.channelId}] Failed to archive bot response`, err instanceof Error ? err.message : String(err));
|
|
97
97
|
});
|
|
98
98
|
}
|
|
99
|
+
resetProgressAfterFinal() {
|
|
100
|
+
if (!this.finalResponseDelivered) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
this.progressSegments = [];
|
|
104
|
+
this.cachedProgressText = "";
|
|
105
|
+
this.progressTextDirty = false;
|
|
106
|
+
this.mode = "progress";
|
|
107
|
+
this.progressStartedAt = 0;
|
|
108
|
+
this.progressWindowStartedAt = 0;
|
|
109
|
+
this.toolCallCount = 0;
|
|
110
|
+
this.sentProgressChars = 0;
|
|
111
|
+
this.replayRequired = false;
|
|
112
|
+
this.finalReplacementText = "";
|
|
113
|
+
this.cardWarmupTriggered = false;
|
|
114
|
+
this.finalResponseDelivered = false;
|
|
115
|
+
}
|
|
99
116
|
async appendProgress(text, shouldLog) {
|
|
100
|
-
if (this.closed ||
|
|
117
|
+
if (this.closed || !text.trim())
|
|
101
118
|
return;
|
|
102
119
|
this.clearCardWarmup();
|
|
120
|
+
this.resetProgressAfterFinal();
|
|
103
121
|
if (this.progressStartedAt === 0) {
|
|
104
122
|
this.progressStartedAt = Date.now();
|
|
105
123
|
}
|
|
@@ -124,8 +142,8 @@ class ChannelDeliveryController {
|
|
|
124
142
|
this.bumpRevision(false);
|
|
125
143
|
}
|
|
126
144
|
async sendFinal(text, shouldLog) {
|
|
127
|
-
if (this.closed
|
|
128
|
-
return
|
|
145
|
+
if (this.closed)
|
|
146
|
+
return false;
|
|
129
147
|
this.clearCardWarmup();
|
|
130
148
|
if (shouldLog) {
|
|
131
149
|
this.archiveBotResponse(text);
|
|
@@ -140,7 +158,7 @@ class ChannelDeliveryController {
|
|
|
140
158
|
return true;
|
|
141
159
|
}
|
|
142
160
|
async replaceWithFinal(text) {
|
|
143
|
-
if (this.closed
|
|
161
|
+
if (this.closed)
|
|
144
162
|
return;
|
|
145
163
|
this.clearCardWarmup();
|
|
146
164
|
this.finalReplacementText = text;
|
|
@@ -51,7 +51,7 @@ export interface DingTalkHandler {
|
|
|
51
51
|
isRunning(channelId: string): boolean;
|
|
52
52
|
handleEvent(event: DingTalkEvent, bot: DingTalkBot, isEvent?: boolean): Promise<void>;
|
|
53
53
|
handleStop(channelId: string, bot: DingTalkBot): Promise<void>;
|
|
54
|
-
handleBusyMessage(event: DingTalkEvent, bot: DingTalkBot, mode: BusyMessageMode, queueText: string): Promise<
|
|
54
|
+
handleBusyMessage(event: DingTalkEvent, bot: DingTalkBot, mode: BusyMessageMode, queueText: string): Promise<boolean>;
|
|
55
55
|
}
|
|
56
56
|
export declare class DingTalkBot {
|
|
57
57
|
private handler;
|
|
@@ -105,6 +105,7 @@ export declare class DingTalkBot {
|
|
|
105
105
|
* Returns true if enqueued, false if queue is full (max 5).
|
|
106
106
|
*/
|
|
107
107
|
enqueueEvent(event: DingTalkEvent): boolean;
|
|
108
|
+
private enqueueStreamMessage;
|
|
108
109
|
/**
|
|
109
110
|
* Get or create an AI Card for a channel.
|
|
110
111
|
*/
|
package/dist/runtime/dingtalk.js
CHANGED
|
@@ -481,6 +481,18 @@ export class DingTalkBot {
|
|
|
481
481
|
});
|
|
482
482
|
return true;
|
|
483
483
|
}
|
|
484
|
+
enqueueStreamMessage(event) {
|
|
485
|
+
this.getQueue(event.channelId).enqueue(async () => {
|
|
486
|
+
this.activeMessageProcessing = true;
|
|
487
|
+
try {
|
|
488
|
+
await this.handler.handleEvent(event, this);
|
|
489
|
+
}
|
|
490
|
+
finally {
|
|
491
|
+
this.activeMessageProcessing = false;
|
|
492
|
+
this.lastSocketAvailableTime = Date.now();
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
484
496
|
// ==========================================================================
|
|
485
497
|
// AI Card operations
|
|
486
498
|
// ==========================================================================
|
|
@@ -858,11 +870,17 @@ export class DingTalkBot {
|
|
|
858
870
|
return;
|
|
859
871
|
}
|
|
860
872
|
if (builtInCommand?.name === "steer") {
|
|
861
|
-
await this.handler.handleBusyMessage(event, this, "steer", builtInCommand.args);
|
|
873
|
+
const handled = await this.handler.handleBusyMessage(event, this, "steer", builtInCommand.args);
|
|
874
|
+
if (!handled) {
|
|
875
|
+
this.enqueueStreamMessage(event);
|
|
876
|
+
}
|
|
862
877
|
return;
|
|
863
878
|
}
|
|
864
879
|
if (builtInCommand?.name === "followup") {
|
|
865
|
-
await this.handler.handleBusyMessage(event, this, "followUp", builtInCommand.args);
|
|
880
|
+
const handled = await this.handler.handleBusyMessage(event, this, "followUp", builtInCommand.args);
|
|
881
|
+
if (!handled) {
|
|
882
|
+
this.enqueueStreamMessage(event);
|
|
883
|
+
}
|
|
866
884
|
return;
|
|
867
885
|
}
|
|
868
886
|
if (builtInCommand) {
|
|
@@ -873,20 +891,14 @@ export class DingTalkBot {
|
|
|
873
891
|
await this.sendPlain(channelId, "A task is already running. Only `/stop`, `/steer <message>`, and `/followup <message>` are available while streaming.");
|
|
874
892
|
return;
|
|
875
893
|
}
|
|
876
|
-
await this.handler.handleBusyMessage(event, this, this.busyMessageDefault, content);
|
|
894
|
+
const handled = await this.handler.handleBusyMessage(event, this, this.busyMessageDefault, content);
|
|
895
|
+
if (!handled) {
|
|
896
|
+
this.enqueueStreamMessage(event);
|
|
897
|
+
}
|
|
877
898
|
return;
|
|
878
899
|
}
|
|
879
900
|
// Enqueue for processing
|
|
880
|
-
this.
|
|
881
|
-
this.activeMessageProcessing = true;
|
|
882
|
-
try {
|
|
883
|
-
await this.handler.handleEvent(event, this);
|
|
884
|
-
}
|
|
885
|
-
finally {
|
|
886
|
-
this.activeMessageProcessing = false;
|
|
887
|
-
this.lastSocketAvailableTime = Date.now();
|
|
888
|
-
}
|
|
889
|
-
});
|
|
901
|
+
this.enqueueStreamMessage(event);
|
|
890
902
|
}
|
|
891
903
|
getQueue(channelId) {
|
|
892
904
|
let queue = this.queues.get(channelId);
|
package/package.json
CHANGED