@oyasmi/pipiclaw 0.6.4 → 0.6.5
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/README.md +7 -1
- package/dist/agent/commands.js +3 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/runtime/bootstrap.js +16 -2
- package/dist/runtime/delivery.js +45 -2
- package/dist/runtime/dingtalk.d.ts +11 -1
- package/dist/runtime/dingtalk.js +40 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -208,7 +208,9 @@ $env:PIPICLAW_SHELL = "C:\Program Files\Git\bin\bash.exe"
|
|
|
208
208
|
"robotCode": "",
|
|
209
209
|
"cardTemplateId": "",
|
|
210
210
|
"cardTemplateKey": "content",
|
|
211
|
-
"allowFrom": []
|
|
211
|
+
"allowFrom": [],
|
|
212
|
+
"busyMessageDefault": "steer",
|
|
213
|
+
"progressDisplay": "full"
|
|
212
214
|
}
|
|
213
215
|
```
|
|
214
216
|
|
|
@@ -227,6 +229,10 @@ $env:PIPICLAW_SHELL = "C:\Program Files\Git\bin\bash.exe"
|
|
|
227
229
|
建议配置;留空时表示暂不启用 AI Card
|
|
228
230
|
- `allowFrom`
|
|
229
231
|
设为 `[]` 或删除时表示允许所有人
|
|
232
|
+
- `busyMessageDefault`
|
|
233
|
+
设为 `"steer"`(默认)或 `"followUp"` / `"followup"`。控制 Agent 忙碌时普通消息的默认处理方式;答疑机器人场景建议设为 `"followUp"`。
|
|
234
|
+
- `progressDisplay`
|
|
235
|
+
设为 `"full"`(默认)或 `"rolling"`。控制 AI Card 进度展示方式;`"rolling"` 模式下执行中只显示最近 3 条进展,完成后收起为一行摘要。
|
|
230
236
|
|
|
231
237
|
推荐把 AI Card 一起配上,这样在钉钉里能直接看到过程更新。只有在排查接入链路时,才建议临时把 `cardTemplateId` 留空。
|
|
232
238
|
|
package/dist/agent/commands.js
CHANGED
|
@@ -19,7 +19,9 @@ These are handled directly by the DingTalk transport/runtime layer.
|
|
|
19
19
|
Queue another request to run after the current task completes
|
|
20
20
|
Example: \`/followup After that, draft a short executive summary\`
|
|
21
21
|
|
|
22
|
-
While a task is running,
|
|
22
|
+
While a task is running, plain messages use the configured busy-message default. The default is \`steer\`; set \`busyMessageDefault\` in channel.json to \`followUp\` or \`followup\` to queue plain messages after the current task.
|
|
23
|
+
|
|
24
|
+
Set \`progressDisplay\` in channel.json to \`rolling\` for compact AI Card progress: recent entries while running, then a short summary after completion.
|
|
23
25
|
|
|
24
26
|
## Session Commands
|
|
25
27
|
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export { findExactModelReferenceMatch, findModelReferenceMatch, formatModelList,
|
|
|
15
15
|
export { APP_HOME_DIR, APP_NAME, AUTH_CONFIG_PATH, CHANNEL_CONFIG_PATH, MODELS_CONFIG_PATH, SECURITY_CONFIG_PATH, SETTINGS_CONFIG_PATH, SUB_AGENTS_DIR, SUB_AGENTS_DIR_NAME, TOOLS_CONFIG_PATH, WORKSPACE_DIR, } from "./paths.js";
|
|
16
16
|
export { ensureChannelDir, getChannelDir, getChannelDirName, } from "./runtime/channel-paths.js";
|
|
17
17
|
export { createDingTalkContext } from "./runtime/delivery.js";
|
|
18
|
-
export { type BusyMessageMode, DingTalkBot, type DingTalkConfig, type DingTalkContext, type DingTalkEvent, type DingTalkHandler, } from "./runtime/dingtalk.js";
|
|
18
|
+
export { type BusyMessageDefaultConfig, type BusyMessageMode, DingTalkBot, type DingTalkConfig, type DingTalkContext, type DingTalkEvent, type DingTalkHandler, isBusyMessageDefaultConfig, isProgressDisplayConfig, normalizeBusyMessageDefault, normalizeProgressDisplay, type ProgressDisplayMode, } from "./runtime/dingtalk.js";
|
|
19
19
|
export { createEventsWatcher, type EventAction, EventsWatcher, type ImmediateEvent, type OneShotEvent, type PeriodicEvent, type ScheduledEvent, } from "./runtime/events.js";
|
|
20
20
|
export { ChannelStore, type LoggedMessage, type LoggedSubAgentRun } from "./runtime/store.js";
|
|
21
21
|
export { createExecutor, type ExecOptions, type ExecResult, type Executor, parseSandboxArg, type SandboxConfig, validateSandbox, } from "./sandbox.js";
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export { findExactModelReferenceMatch, findModelReferenceMatch, formatModelList,
|
|
|
15
15
|
export { APP_HOME_DIR, APP_NAME, AUTH_CONFIG_PATH, CHANNEL_CONFIG_PATH, MODELS_CONFIG_PATH, SECURITY_CONFIG_PATH, SETTINGS_CONFIG_PATH, SUB_AGENTS_DIR, SUB_AGENTS_DIR_NAME, TOOLS_CONFIG_PATH, WORKSPACE_DIR, } from "./paths.js";
|
|
16
16
|
export { ensureChannelDir, getChannelDir, getChannelDirName, } from "./runtime/channel-paths.js";
|
|
17
17
|
export { createDingTalkContext } from "./runtime/delivery.js";
|
|
18
|
-
export { DingTalkBot, } from "./runtime/dingtalk.js";
|
|
18
|
+
export { DingTalkBot, isBusyMessageDefaultConfig, isProgressDisplayConfig, normalizeBusyMessageDefault, normalizeProgressDisplay, } from "./runtime/dingtalk.js";
|
|
19
19
|
export { createEventsWatcher, EventsWatcher, } from "./runtime/events.js";
|
|
20
20
|
export { ChannelStore } from "./runtime/store.js";
|
|
21
21
|
export { createExecutor, parseSandboxArg, validateSandbox, } from "./sandbox.js";
|
|
@@ -14,7 +14,7 @@ import { formatConfigDiagnostic } from "../shared/config-diagnostics.js";
|
|
|
14
14
|
import { loadToolsConfigWithDiagnostics } from "../tools/config.js";
|
|
15
15
|
import { ensureChannelDir } from "./channel-paths.js";
|
|
16
16
|
import { createDingTalkContext } from "./delivery.js";
|
|
17
|
-
import { DingTalkBot, } from "./dingtalk.js";
|
|
17
|
+
import { DingTalkBot, isBusyMessageDefaultConfig, isProgressDisplayConfig, normalizeBusyMessageDefault, normalizeProgressDisplay, } from "./dingtalk.js";
|
|
18
18
|
import { createEventsWatcher } from "./events.js";
|
|
19
19
|
import { ChannelStore } from "./store.js";
|
|
20
20
|
const DEFAULT_SOUL = `# SOUL.md
|
|
@@ -102,6 +102,8 @@ const CHANNEL_CONFIG_TEMPLATE = {
|
|
|
102
102
|
cardTemplateId: "your-card-template-id",
|
|
103
103
|
cardTemplateKey: "content",
|
|
104
104
|
allowFrom: ["your-staff-id"],
|
|
105
|
+
busyMessageDefault: "steer",
|
|
106
|
+
progressDisplay: "full",
|
|
105
107
|
};
|
|
106
108
|
const MODELS_CONFIG_TEMPLATE = { providers: {} };
|
|
107
109
|
const TOOLS_CONFIG_TEMPLATE = {
|
|
@@ -236,6 +238,14 @@ function listChannelConfigIssues(config) {
|
|
|
236
238
|
if (Array.isArray(config.allowFrom) && config.allowFrom.some((value) => isPlaceholderString(value))) {
|
|
237
239
|
issues.push("Replace placeholder values in `allowFrom`, or set it to an empty array to allow all users.");
|
|
238
240
|
}
|
|
241
|
+
const busyMessageDefault = config.busyMessageDefault;
|
|
242
|
+
if (busyMessageDefault !== undefined && !isBusyMessageDefaultConfig(busyMessageDefault)) {
|
|
243
|
+
issues.push('Invalid `busyMessageDefault`: expected "steer", "followUp", or "followup".');
|
|
244
|
+
}
|
|
245
|
+
const progressDisplay = config.progressDisplay;
|
|
246
|
+
if (progressDisplay !== undefined && !isProgressDisplayConfig(progressDisplay)) {
|
|
247
|
+
issues.push('Invalid `progressDisplay`: expected "full" or "rolling".');
|
|
248
|
+
}
|
|
239
249
|
return issues;
|
|
240
250
|
}
|
|
241
251
|
export function printBootstrapSummary(result, io = console, paths = DEFAULT_BOOTSTRAP_PATHS) {
|
|
@@ -270,6 +280,8 @@ export function loadConfig(paths = DEFAULT_BOOTSTRAP_PATHS, io = console) {
|
|
|
270
280
|
}
|
|
271
281
|
parsed.cardTemplateKey = parsed.cardTemplateKey || "content";
|
|
272
282
|
parsed.robotCode = parsed.robotCode?.trim() ? parsed.robotCode : parsed.clientId;
|
|
283
|
+
parsed.busyMessageDefault = normalizeBusyMessageDefault(parsed.busyMessageDefault);
|
|
284
|
+
parsed.progressDisplay = normalizeProgressDisplay(parsed.progressDisplay);
|
|
273
285
|
if (Array.isArray(parsed.allowFrom)) {
|
|
274
286
|
parsed.allowFrom = parsed.allowFrom.filter((value) => value.trim().length > 0);
|
|
275
287
|
}
|
|
@@ -400,7 +412,9 @@ export function createRuntimeContext(options) {
|
|
|
400
412
|
await state.runner.queueSteer(trimmedQueueText, event.userName);
|
|
401
413
|
}
|
|
402
414
|
const confirmation = mode === "followUp"
|
|
403
|
-
?
|
|
415
|
+
? event.text.trim().startsWith("/")
|
|
416
|
+
? "Queued as follow-up. I’ll handle it after the current task completes."
|
|
417
|
+
: "Queued as follow-up. I’ll handle it after the current task completes. Use `/steer <message>` to apply it after the current tool step finishes."
|
|
404
418
|
: event.text.trim().startsWith("/")
|
|
405
419
|
? "Queued as steer. I’ll apply it after the current tool step finishes."
|
|
406
420
|
: "Queued as steer. I’ll apply this after the current tool step finishes. Use `/followup <message>` to queue it after completion.";
|
package/dist/runtime/delivery.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as log from "../log.js";
|
|
2
2
|
const MIN_UPDATE_INTERVAL_MS = 800;
|
|
3
|
+
const ROLLING_WINDOW_SIZE = 3;
|
|
3
4
|
const NO_CONTENT = "";
|
|
4
5
|
class ChannelDeliveryController {
|
|
5
6
|
constructor(event, bot, store) {
|
|
@@ -17,7 +18,9 @@ class ChannelDeliveryController {
|
|
|
17
18
|
this.finalResponseDelivered = false;
|
|
18
19
|
this.cardWarmupScheduled = false;
|
|
19
20
|
this.cardWarmupTriggered = false;
|
|
21
|
+
this.progressStartedAt = 0;
|
|
20
22
|
this.progressWindowStartedAt = 0;
|
|
23
|
+
this.toolCallCount = 0;
|
|
21
24
|
this.lastDeliveredAt = 0;
|
|
22
25
|
this.timer = null;
|
|
23
26
|
this.cardWarmupTimer = null;
|
|
@@ -97,11 +100,20 @@ class ChannelDeliveryController {
|
|
|
97
100
|
if (this.closed || this.finalResponseDelivered || !text.trim())
|
|
98
101
|
return;
|
|
99
102
|
this.clearCardWarmup();
|
|
103
|
+
if (this.progressStartedAt === 0) {
|
|
104
|
+
this.progressStartedAt = Date.now();
|
|
105
|
+
}
|
|
106
|
+
if (text.startsWith("Running:")) {
|
|
107
|
+
this.toolCallCount++;
|
|
108
|
+
}
|
|
100
109
|
if (this.progressSegments.length > 0) {
|
|
101
110
|
this.progressSegments.push("\n\n");
|
|
102
111
|
}
|
|
103
112
|
this.progressSegments.push(text);
|
|
104
113
|
this.progressTextDirty = true;
|
|
114
|
+
if (this.bot.progressDisplay === "rolling") {
|
|
115
|
+
this.trimToRecentEntries(ROLLING_WINDOW_SIZE);
|
|
116
|
+
}
|
|
105
117
|
if (this.progressWindowStartedAt === 0) {
|
|
106
118
|
this.progressWindowStartedAt = Date.now();
|
|
107
119
|
}
|
|
@@ -213,12 +225,13 @@ class ChannelDeliveryController {
|
|
|
213
225
|
}
|
|
214
226
|
else if (mode === "finalize-existing") {
|
|
215
227
|
if (content || this.cardWarmupTriggered) {
|
|
216
|
-
|
|
228
|
+
const finalProgressText = this.bot.progressDisplay === "rolling" ? this.buildSummaryText("Done") : progressText;
|
|
229
|
+
touchedRemote = await this.bot.replaceCard(this.event.channelId, content || this.bot.progressDisplay === "rolling" ? finalProgressText : NO_CONTENT, true);
|
|
217
230
|
if (!touchedRemote) {
|
|
218
231
|
this.bot.discardCard(this.event.channelId);
|
|
219
232
|
}
|
|
220
233
|
else {
|
|
221
|
-
this.sentProgressChars =
|
|
234
|
+
this.sentProgressChars = finalProgressText.length;
|
|
222
235
|
this.replayRequired = false;
|
|
223
236
|
}
|
|
224
237
|
}
|
|
@@ -306,6 +319,36 @@ class ChannelDeliveryController {
|
|
|
306
319
|
this.progressTextDirty = false;
|
|
307
320
|
return this.cachedProgressText;
|
|
308
321
|
}
|
|
322
|
+
trimToRecentEntries(maxEntries) {
|
|
323
|
+
let entryCount = 0;
|
|
324
|
+
for (const segment of this.progressSegments) {
|
|
325
|
+
if (segment !== "\n\n") {
|
|
326
|
+
entryCount++;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (entryCount <= maxEntries) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const entriesToRemove = entryCount - maxEntries;
|
|
333
|
+
let removedEntries = 0;
|
|
334
|
+
while (removedEntries < entriesToRemove && this.progressSegments.length > 0) {
|
|
335
|
+
const segment = this.progressSegments.shift();
|
|
336
|
+
if (segment !== "\n\n") {
|
|
337
|
+
removedEntries++;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
while (this.progressSegments[0] === "\n\n") {
|
|
341
|
+
this.progressSegments.shift();
|
|
342
|
+
}
|
|
343
|
+
this.progressTextDirty = true;
|
|
344
|
+
this.replayRequired = true;
|
|
345
|
+
this.sentProgressChars = 0;
|
|
346
|
+
}
|
|
347
|
+
buildSummaryText(status) {
|
|
348
|
+
const elapsedSeconds = this.progressStartedAt > 0 ? Math.round((Date.now() - this.progressStartedAt) / 1000) : 0;
|
|
349
|
+
const toolLabel = this.toolCallCount === 1 ? "1 tool call" : `${this.toolCallCount} tool calls`;
|
|
350
|
+
return `${status} · ${toolLabel} · ${elapsedSeconds}s`;
|
|
351
|
+
}
|
|
309
352
|
}
|
|
310
353
|
export function createDingTalkContext(event, bot, store) {
|
|
311
354
|
return new ChannelDeliveryController(event, bot, store).buildContext();
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
export type BusyMessageMode = "steer" | "followUp";
|
|
2
|
+
export type BusyMessageDefaultConfig = BusyMessageMode | "followup";
|
|
3
|
+
export type ProgressDisplayMode = "full" | "rolling";
|
|
4
|
+
export declare function isBusyMessageDefaultConfig(value: unknown): value is BusyMessageDefaultConfig;
|
|
5
|
+
export declare function isProgressDisplayConfig(value: unknown): value is ProgressDisplayMode;
|
|
6
|
+
export declare function normalizeBusyMessageDefault(value: unknown): BusyMessageMode;
|
|
7
|
+
export declare function normalizeProgressDisplay(value: unknown): ProgressDisplayMode;
|
|
1
8
|
export interface DingTalkConfig {
|
|
2
9
|
clientId: string;
|
|
3
10
|
clientSecret: string;
|
|
@@ -6,6 +13,8 @@ export interface DingTalkConfig {
|
|
|
6
13
|
cardTemplateKey?: string;
|
|
7
14
|
allowFrom?: string[];
|
|
8
15
|
stateDir?: string;
|
|
16
|
+
busyMessageDefault?: BusyMessageDefaultConfig;
|
|
17
|
+
progressDisplay?: ProgressDisplayMode;
|
|
9
18
|
}
|
|
10
19
|
export interface DingTalkEvent {
|
|
11
20
|
type: "dm" | "group";
|
|
@@ -38,7 +47,6 @@ export interface DingTalkContext {
|
|
|
38
47
|
flush: () => Promise<void>;
|
|
39
48
|
close: () => Promise<void>;
|
|
40
49
|
}
|
|
41
|
-
export type BusyMessageMode = "steer" | "followUp";
|
|
42
50
|
export interface DingTalkHandler {
|
|
43
51
|
isRunning(channelId: string): boolean;
|
|
44
52
|
handleEvent(event: DingTalkEvent, bot: DingTalkBot, isEvent?: boolean): Promise<void>;
|
|
@@ -66,6 +74,8 @@ export declare class DingTalkBot {
|
|
|
66
74
|
private processedIds;
|
|
67
75
|
private processedIdsOrder;
|
|
68
76
|
constructor(handler: DingTalkHandler, config: DingTalkConfig);
|
|
77
|
+
get busyMessageDefault(): BusyMessageMode;
|
|
78
|
+
get progressDisplay(): ProgressDisplayMode;
|
|
69
79
|
/**
|
|
70
80
|
* Mark an ID as processed. Returns true if this is a new ID, false if already seen.
|
|
71
81
|
* Maintains a FIFO buffer of at most 200 entries.
|
package/dist/runtime/dingtalk.js
CHANGED
|
@@ -15,6 +15,33 @@ import { parseBuiltInCommand, renderBuiltInHelp } from "../agent/commands.js";
|
|
|
15
15
|
import * as log from "../log.js";
|
|
16
16
|
import { isRecord } from "../shared/type-guards.js";
|
|
17
17
|
import { getChannelDir } from "./channel-paths.js";
|
|
18
|
+
export function isBusyMessageDefaultConfig(value) {
|
|
19
|
+
return value === "steer" || value === "followUp" || value === "followup";
|
|
20
|
+
}
|
|
21
|
+
export function isProgressDisplayConfig(value) {
|
|
22
|
+
return value === "full" || value === "rolling";
|
|
23
|
+
}
|
|
24
|
+
export function normalizeBusyMessageDefault(value) {
|
|
25
|
+
if (value === undefined) {
|
|
26
|
+
return "steer";
|
|
27
|
+
}
|
|
28
|
+
if (value === "steer") {
|
|
29
|
+
return "steer";
|
|
30
|
+
}
|
|
31
|
+
if (value === "followUp" || value === "followup") {
|
|
32
|
+
return "followUp";
|
|
33
|
+
}
|
|
34
|
+
throw new Error('Invalid `busyMessageDefault`: expected "steer", "followUp", or "followup".');
|
|
35
|
+
}
|
|
36
|
+
export function normalizeProgressDisplay(value) {
|
|
37
|
+
if (value === undefined) {
|
|
38
|
+
return "full";
|
|
39
|
+
}
|
|
40
|
+
if (isProgressDisplayConfig(value)) {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
throw new Error('Invalid `progressDisplay`: expected "full" or "rolling".');
|
|
44
|
+
}
|
|
18
45
|
class ChannelQueue {
|
|
19
46
|
constructor() {
|
|
20
47
|
this.queue = [];
|
|
@@ -90,7 +117,17 @@ export class DingTalkBot {
|
|
|
90
117
|
this.processedIds = new Set();
|
|
91
118
|
this.processedIdsOrder = [];
|
|
92
119
|
this.handler = handler;
|
|
93
|
-
this.config =
|
|
120
|
+
this.config = {
|
|
121
|
+
...config,
|
|
122
|
+
busyMessageDefault: normalizeBusyMessageDefault(config.busyMessageDefault),
|
|
123
|
+
progressDisplay: normalizeProgressDisplay(config.progressDisplay),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
get busyMessageDefault() {
|
|
127
|
+
return normalizeBusyMessageDefault(this.config.busyMessageDefault);
|
|
128
|
+
}
|
|
129
|
+
get progressDisplay() {
|
|
130
|
+
return normalizeProgressDisplay(this.config.progressDisplay);
|
|
94
131
|
}
|
|
95
132
|
/**
|
|
96
133
|
* Mark an ID as processed. Returns true if this is a new ID, false if already seen.
|
|
@@ -829,14 +866,14 @@ export class DingTalkBot {
|
|
|
829
866
|
return;
|
|
830
867
|
}
|
|
831
868
|
if (builtInCommand) {
|
|
832
|
-
await this.sendPlain(channelId,
|
|
869
|
+
await this.sendPlain(channelId, `A task is already running. Use \`/stop\`, \`/steer <message>\`, or \`/followup <message>\`. Plain messages default to ${this.busyMessageDefault}.`);
|
|
833
870
|
return;
|
|
834
871
|
}
|
|
835
872
|
if (isSlashCommand) {
|
|
836
873
|
await this.sendPlain(channelId, "A task is already running. Only `/stop`, `/steer <message>`, and `/followup <message>` are available while streaming.");
|
|
837
874
|
return;
|
|
838
875
|
}
|
|
839
|
-
await this.handler.handleBusyMessage(event, this,
|
|
876
|
+
await this.handler.handleBusyMessage(event, this, this.busyMessageDefault, content);
|
|
840
877
|
return;
|
|
841
878
|
}
|
|
842
879
|
// Enqueue for processing
|
package/package.json
CHANGED