@oyasmi/pipiclaw 0.6.0 → 0.6.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 +3 -0
- package/dist/agent/channel-runner.js +75 -11
- package/dist/agent/context-budget.d.ts +9 -0
- package/dist/agent/context-budget.js +31 -0
- package/dist/agent/session-events.js +11 -4
- package/dist/agent/type-guards.js +4 -2
- package/dist/agent/types.d.ts +10 -3
- package/dist/agent/types.js +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/memory/candidates.d.ts +8 -5
- package/dist/memory/candidates.js +92 -42
- package/dist/memory/consolidation.js +2 -2
- package/dist/memory/files.js +2 -1
- package/dist/memory/lifecycle.d.ts +4 -2
- package/dist/memory/lifecycle.js +22 -11
- package/dist/memory/recall.d.ts +2 -2
- package/dist/memory/recall.js +2 -3
- package/dist/runtime/bootstrap.d.ts +2 -2
- package/dist/runtime/bootstrap.js +5 -4
- package/dist/runtime/delivery.js +47 -9
- package/dist/runtime/dingtalk.d.ts +9 -1
- package/dist/runtime/dingtalk.js +55 -20
- package/dist/runtime/events.d.ts +16 -2
- package/dist/runtime/events.js +73 -14
- package/dist/settings.js +3 -3
- package/dist/subagents/tool.d.ts +2 -0
- package/dist/subagents/tool.js +2 -3
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/read.js +11 -4
- package/package.json +1 -1
package/dist/memory/lifecycle.js
CHANGED
|
@@ -5,7 +5,7 @@ const IDLE_CONSOLIDATION_DELAY_MS = 60_000;
|
|
|
5
5
|
export class MemoryLifecycle {
|
|
6
6
|
constructor(options) {
|
|
7
7
|
this.options = options;
|
|
8
|
-
this.
|
|
8
|
+
this.durableMemoryQueue = Promise.resolve();
|
|
9
9
|
this.sessionRefreshQueue = Promise.resolve();
|
|
10
10
|
this.turnsSinceSessionUpdate = 0;
|
|
11
11
|
this.toolCallsSinceSessionUpdate = 0;
|
|
@@ -75,15 +75,16 @@ export class MemoryLifecycle {
|
|
|
75
75
|
}
|
|
76
76
|
async flushForShutdown() {
|
|
77
77
|
this.clearIdleConsolidationTimer();
|
|
78
|
-
|
|
78
|
+
await this.runDurableMemoryJobSerial(async () => {
|
|
79
79
|
if (!this.hasPendingAssistantSnapshot()) {
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
const messageSnapshot = [...this.options.getMessages()];
|
|
83
|
+
const sessionEntrySnapshot = [...this.options.getSessionEntries()];
|
|
84
|
+
const revisionSnapshot = this.durableRevision;
|
|
85
|
+
const settings = this.options.getSessionMemorySettings();
|
|
86
|
+
await this.runPreflightConsolidationNow("shutdown", messageSnapshot, sessionEntrySnapshot, revisionSnapshot, settings);
|
|
87
|
+
});
|
|
87
88
|
}
|
|
88
89
|
clearIdleConsolidationTimer() {
|
|
89
90
|
if (!this.idleConsolidationTimer) {
|
|
@@ -152,8 +153,13 @@ export class MemoryLifecycle {
|
|
|
152
153
|
this.thresholdRefreshQueued = false;
|
|
153
154
|
});
|
|
154
155
|
}
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
runDurableMemoryJobSerial(job) {
|
|
157
|
+
const resultPromise = this.durableMemoryQueue.then(job, job);
|
|
158
|
+
this.durableMemoryQueue = resultPromise.then(() => undefined, () => undefined);
|
|
159
|
+
return resultPromise;
|
|
160
|
+
}
|
|
161
|
+
enqueueDurableMemoryJob(job, failureMessage) {
|
|
162
|
+
void this.runDurableMemoryJobSerial(job).catch((error) => {
|
|
157
163
|
const message = error instanceof Error ? error.message : String(error);
|
|
158
164
|
log.logWarning(failureMessage, message);
|
|
159
165
|
});
|
|
@@ -186,7 +192,7 @@ export class MemoryLifecycle {
|
|
|
186
192
|
const messageSnapshot = [...this.options.getMessages()];
|
|
187
193
|
const sessionEntrySnapshot = [...this.options.getSessionEntries()];
|
|
188
194
|
const revisionSnapshot = this.durableRevision;
|
|
189
|
-
this.
|
|
195
|
+
this.enqueueDurableMemoryJob(async () => {
|
|
190
196
|
try {
|
|
191
197
|
log.logInfo(`[${this.options.channelId}] Memory consolidation starting (idle)`);
|
|
192
198
|
const result = await runInlineConsolidation(this.buildRunOptions(messageSnapshot, sessionEntrySnapshot));
|
|
@@ -210,6 +216,11 @@ export class MemoryLifecycle {
|
|
|
210
216
|
const sessionEntrySnapshot = sessionEntries ? [...sessionEntries] : [...this.options.getSessionEntries()];
|
|
211
217
|
const revisionSnapshot = this.durableRevision;
|
|
212
218
|
const settings = this.options.getSessionMemorySettings();
|
|
219
|
+
await this.runDurableMemoryJobSerial(async () => {
|
|
220
|
+
await this.runPreflightConsolidationNow(reason, messageSnapshot, sessionEntrySnapshot, revisionSnapshot, settings);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
async runPreflightConsolidationNow(reason, messageSnapshot, sessionEntrySnapshot, revisionSnapshot = this.durableRevision, settings = this.options.getSessionMemorySettings()) {
|
|
213
224
|
if (this.shouldForceRefreshFor(reason, settings)) {
|
|
214
225
|
await this.runSessionRefreshSerial({
|
|
215
226
|
reason,
|
|
@@ -246,7 +257,7 @@ export class MemoryLifecycle {
|
|
|
246
257
|
this.enqueueBackgroundMaintenance();
|
|
247
258
|
}
|
|
248
259
|
enqueueBackgroundMaintenance() {
|
|
249
|
-
this.
|
|
260
|
+
this.enqueueDurableMemoryJob(async () => {
|
|
250
261
|
const result = await runBackgroundMaintenance(this.buildRunOptions([], []));
|
|
251
262
|
this.logBackgroundResult(result);
|
|
252
263
|
}, `[${this.options.channelId}] Background memory maintenance failed`);
|
package/dist/memory/recall.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Api, Model } from "@mariozechner/pi-ai";
|
|
2
|
-
import { type MemoryCandidate, type
|
|
2
|
+
import { type MemoryCandidate, type MemoryCandidateStore } from "./candidates.js";
|
|
3
3
|
export interface RecallRequest {
|
|
4
4
|
query: string;
|
|
5
5
|
workspaceDir: string;
|
|
@@ -12,7 +12,7 @@ export interface RecallRequest {
|
|
|
12
12
|
autoRerank?: boolean;
|
|
13
13
|
model: Model<Api>;
|
|
14
14
|
resolveApiKey: (model: Model<Api>) => Promise<string>;
|
|
15
|
-
|
|
15
|
+
candidateStore?: MemoryCandidateStore;
|
|
16
16
|
}
|
|
17
17
|
export interface RecalledMemory {
|
|
18
18
|
source: MemoryCandidate["source"];
|
package/dist/memory/recall.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseJsonObject } from "../shared/llm-json.js";
|
|
2
2
|
import { HAN_REGEX } from "../shared/text-utils.js";
|
|
3
|
-
import { buildMemoryCandidates } from "./candidates.js";
|
|
3
|
+
import { buildMemoryCandidates, createMemoryCandidateStore, } from "./candidates.js";
|
|
4
4
|
import { COMMON_CHINESE_WORDS } from "./chinese-words.js";
|
|
5
5
|
import { runSidecarTask } from "./sidecar-worker.js";
|
|
6
6
|
const RERANK_SYSTEM_PROMPT = `You are selecting which memory snippets are most relevant to the current user turn.
|
|
@@ -466,8 +466,7 @@ export async function recallRelevantMemory(request) {
|
|
|
466
466
|
const candidates = await buildMemoryCandidates({
|
|
467
467
|
workspaceDir: request.workspaceDir,
|
|
468
468
|
channelDir: request.channelDir,
|
|
469
|
-
|
|
470
|
-
});
|
|
469
|
+
}, request.candidateStore ?? createMemoryCandidateStore());
|
|
471
470
|
const filteredCandidates = request.allowedSources?.length
|
|
472
471
|
? candidates.filter((candidate) => request.allowedSources?.includes(candidate.source))
|
|
473
472
|
: candidates;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type SandboxConfig } from "../sandbox.js";
|
|
1
|
+
import { type Executor, type SandboxConfig } from "../sandbox.js";
|
|
2
2
|
import { DingTalkBot, type DingTalkConfig, type DingTalkHandler } from "./dingtalk.js";
|
|
3
3
|
import { ChannelStore } from "./store.js";
|
|
4
4
|
export interface BootstrapPaths {
|
|
@@ -55,7 +55,7 @@ interface RuntimeContextOptions {
|
|
|
55
55
|
sandbox: SandboxConfig;
|
|
56
56
|
dingtalkConfig: DingTalkConfig;
|
|
57
57
|
createBot?: (handler: DingTalkHandler, config: DingTalkConfig) => DingTalkBot;
|
|
58
|
-
createEventsWatcher?: (workspaceDir: string, bot: DingTalkBot) => {
|
|
58
|
+
createEventsWatcher?: (workspaceDir: string, bot: DingTalkBot, executor: Executor) => {
|
|
59
59
|
start(): void;
|
|
60
60
|
stop(): void;
|
|
61
61
|
};
|
|
@@ -6,7 +6,7 @@ import { resetRunner } from "../agent/runner-factory.js";
|
|
|
6
6
|
import * as log from "../log.js";
|
|
7
7
|
import { ensureChannelMemoryFilesSync } from "../memory/files.js";
|
|
8
8
|
import { APP_HOME_DIR, APP_NAME, AUTH_CONFIG_PATH, CHANNEL_CONFIG_PATH, MODELS_CONFIG_PATH, SECURITY_CONFIG_PATH, SETTINGS_CONFIG_PATH, TOOLS_CONFIG_PATH, WORKSPACE_DIR, } from "../paths.js";
|
|
9
|
-
import { parseSandboxArg, validateSandbox } from "../sandbox.js";
|
|
9
|
+
import { createExecutor, parseSandboxArg, validateSandbox } from "../sandbox.js";
|
|
10
10
|
import { loadSecurityConfigWithDiagnostics } from "../security/config.js";
|
|
11
11
|
import { PipiclawSettingsManager } from "../settings.js";
|
|
12
12
|
import { formatConfigDiagnostic } from "../shared/config-diagnostics.js";
|
|
@@ -137,7 +137,7 @@ const SECURITY_CONFIG_TEMPLATE = {
|
|
|
137
137
|
},
|
|
138
138
|
};
|
|
139
139
|
const SHUTDOWN_WAIT_MS = 15000;
|
|
140
|
-
const SHUTDOWN_FLUSH_WAIT_MS =
|
|
140
|
+
const SHUTDOWN_FLUSH_WAIT_MS = 45000;
|
|
141
141
|
const SHUTDOWN_ABORT_WAIT_MS = 5000;
|
|
142
142
|
export const DEFAULT_BOOTSTRAP_PATHS = {
|
|
143
143
|
appName: APP_NAME,
|
|
@@ -450,9 +450,10 @@ export function createRuntimeContext(options) {
|
|
|
450
450
|
const bot = options.createBot
|
|
451
451
|
? options.createBot(handler, options.dingtalkConfig)
|
|
452
452
|
: new DingTalkBot(handler, options.dingtalkConfig);
|
|
453
|
+
const executor = createExecutor(options.sandbox);
|
|
453
454
|
const eventsWatcher = options.createEventsWatcher
|
|
454
|
-
? options.createEventsWatcher(options.paths.workspaceDir, bot)
|
|
455
|
-
: createEventsWatcher(options.paths.workspaceDir, bot);
|
|
455
|
+
? options.createEventsWatcher(options.paths.workspaceDir, bot, executor)
|
|
456
|
+
: createEventsWatcher(options.paths.workspaceDir, bot, executor, loadSecurityConfigWithDiagnostics(options.paths.appHomeDir).config.commandGuard);
|
|
456
457
|
const shutdownWithReason = async (reason = "manual") => {
|
|
457
458
|
if (shutdownPromise) {
|
|
458
459
|
return shutdownPromise;
|
package/dist/runtime/delivery.js
CHANGED
|
@@ -6,7 +6,9 @@ class ChannelDeliveryController {
|
|
|
6
6
|
this.event = event;
|
|
7
7
|
this.bot = bot;
|
|
8
8
|
this.store = store;
|
|
9
|
-
this.
|
|
9
|
+
this.progressSegments = [];
|
|
10
|
+
this.cachedProgressText = "";
|
|
11
|
+
this.progressTextDirty = false;
|
|
10
12
|
this.mode = "progress";
|
|
11
13
|
this.desiredRevision = 0;
|
|
12
14
|
this.appliedRevision = 0;
|
|
@@ -20,6 +22,9 @@ class ChannelDeliveryController {
|
|
|
20
22
|
this.timer = null;
|
|
21
23
|
this.cardWarmupTimer = null;
|
|
22
24
|
this.flushWaiters = [];
|
|
25
|
+
this.sentProgressChars = 0;
|
|
26
|
+
this.replayRequired = false;
|
|
27
|
+
this.finalReplacementText = "";
|
|
23
28
|
}
|
|
24
29
|
buildContext() {
|
|
25
30
|
return {
|
|
@@ -86,7 +91,11 @@ class ChannelDeliveryController {
|
|
|
86
91
|
if (this.closed || this.finalResponseDelivered || !text.trim())
|
|
87
92
|
return;
|
|
88
93
|
this.clearCardWarmup();
|
|
89
|
-
this.
|
|
94
|
+
if (this.progressSegments.length > 0) {
|
|
95
|
+
this.progressSegments.push("\n\n");
|
|
96
|
+
}
|
|
97
|
+
this.progressSegments.push(text);
|
|
98
|
+
this.progressTextDirty = true;
|
|
90
99
|
if (this.progressWindowStartedAt === 0) {
|
|
91
100
|
this.progressWindowStartedAt = Date.now();
|
|
92
101
|
}
|
|
@@ -116,7 +125,7 @@ class ChannelDeliveryController {
|
|
|
116
125
|
if (this.closed || this.finalResponseDelivered)
|
|
117
126
|
return;
|
|
118
127
|
this.clearCardWarmup();
|
|
119
|
-
this.
|
|
128
|
+
this.finalReplacementText = text;
|
|
120
129
|
this.mode = "finalize-with-fallback";
|
|
121
130
|
this.bumpRevision(true);
|
|
122
131
|
}
|
|
@@ -159,6 +168,7 @@ class ChannelDeliveryController {
|
|
|
159
168
|
try {
|
|
160
169
|
while (this.appliedRevision < this.desiredRevision) {
|
|
161
170
|
const mode = this.mode;
|
|
171
|
+
const progressText = this.getProgressText();
|
|
162
172
|
const throttleBaseAt = this.lastDeliveredAt > 0 ? this.lastDeliveredAt : this.progressWindowStartedAt;
|
|
163
173
|
if (mode === "progress" && throttleBaseAt > 0) {
|
|
164
174
|
const remaining = MIN_UPDATE_INTERVAL_MS - (Date.now() - throttleBaseAt);
|
|
@@ -171,31 +181,48 @@ class ChannelDeliveryController {
|
|
|
171
181
|
}
|
|
172
182
|
}
|
|
173
183
|
const revision = this.desiredRevision;
|
|
174
|
-
const content =
|
|
184
|
+
const content = progressText.trim();
|
|
185
|
+
const replacementText = this.finalReplacementText;
|
|
175
186
|
let touchedRemote = false;
|
|
176
187
|
try {
|
|
177
188
|
if (mode === "progress") {
|
|
178
189
|
if (content) {
|
|
179
|
-
|
|
190
|
+
const nextSentChars = progressText.length;
|
|
191
|
+
if (this.replayRequired) {
|
|
192
|
+
touchedRemote = await this.bot.replaceCard(this.event.channelId, progressText);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const delta = progressText.slice(this.sentProgressChars);
|
|
196
|
+
touchedRemote = delta ? await this.bot.appendToCard(this.event.channelId, delta) : true;
|
|
197
|
+
}
|
|
180
198
|
if (!touchedRemote) {
|
|
181
199
|
this.bot.discardCard(this.event.channelId);
|
|
200
|
+
this.replayRequired = true;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
this.sentProgressChars = nextSentChars;
|
|
204
|
+
this.replayRequired = false;
|
|
182
205
|
}
|
|
183
206
|
}
|
|
184
207
|
}
|
|
185
208
|
else if (mode === "finalize-existing") {
|
|
186
209
|
if (content || this.cardWarmupTriggered) {
|
|
187
|
-
touchedRemote = await this.bot.
|
|
210
|
+
touchedRemote = await this.bot.replaceCard(this.event.channelId, content ? progressText : NO_CONTENT, true);
|
|
188
211
|
if (!touchedRemote) {
|
|
189
212
|
this.bot.discardCard(this.event.channelId);
|
|
190
213
|
}
|
|
214
|
+
else {
|
|
215
|
+
this.sentProgressChars = progressText.length;
|
|
216
|
+
this.replayRequired = false;
|
|
217
|
+
}
|
|
191
218
|
}
|
|
192
219
|
else {
|
|
193
220
|
this.bot.discardCard(this.event.channelId);
|
|
194
221
|
}
|
|
195
222
|
}
|
|
196
223
|
else if (mode === "finalize-with-fallback") {
|
|
197
|
-
if (
|
|
198
|
-
touchedRemote = await this.bot.finalizeCard(this.event.channelId,
|
|
224
|
+
if (replacementText.trim()) {
|
|
225
|
+
touchedRemote = await this.bot.finalizeCard(this.event.channelId, replacementText);
|
|
199
226
|
if (!touchedRemote) {
|
|
200
227
|
this.bot.discardCard(this.event.channelId);
|
|
201
228
|
}
|
|
@@ -206,7 +233,7 @@ class ChannelDeliveryController {
|
|
|
206
233
|
}
|
|
207
234
|
else if (mode === "silent") {
|
|
208
235
|
if (this.cardWarmupTriggered) {
|
|
209
|
-
touchedRemote = await this.bot.
|
|
236
|
+
touchedRemote = await this.bot.replaceCard(this.event.channelId, NO_CONTENT, true);
|
|
210
237
|
}
|
|
211
238
|
if (!touchedRemote) {
|
|
212
239
|
this.bot.discardCard(this.event.channelId);
|
|
@@ -216,6 +243,9 @@ class ChannelDeliveryController {
|
|
|
216
243
|
catch (err) {
|
|
217
244
|
log.logWarning(`[${this.event.channelId}] Delivery sync failed`, err instanceof Error ? err.message : String(err));
|
|
218
245
|
this.bot.discardCard(this.event.channelId);
|
|
246
|
+
if (mode === "progress") {
|
|
247
|
+
this.replayRequired = true;
|
|
248
|
+
}
|
|
219
249
|
}
|
|
220
250
|
if (touchedRemote) {
|
|
221
251
|
this.lastDeliveredAt = Date.now();
|
|
@@ -262,6 +292,14 @@ class ChannelDeliveryController {
|
|
|
262
292
|
this.clearCardWarmup();
|
|
263
293
|
await this.flush();
|
|
264
294
|
}
|
|
295
|
+
getProgressText() {
|
|
296
|
+
if (!this.progressTextDirty) {
|
|
297
|
+
return this.cachedProgressText;
|
|
298
|
+
}
|
|
299
|
+
this.cachedProgressText = this.progressSegments.join("");
|
|
300
|
+
this.progressTextDirty = false;
|
|
301
|
+
return this.cachedProgressText;
|
|
302
|
+
}
|
|
265
303
|
}
|
|
266
304
|
export function createDingTalkContext(event, bot, store) {
|
|
267
305
|
return new ChannelDeliveryController(event, bot, store).buildContext();
|
|
@@ -94,7 +94,15 @@ export declare class DingTalkBot {
|
|
|
94
94
|
*/
|
|
95
95
|
ensureCard(channelId: string): Promise<void>;
|
|
96
96
|
/**
|
|
97
|
-
*
|
|
97
|
+
* Replace the active card content with a full snapshot.
|
|
98
|
+
*/
|
|
99
|
+
replaceCard(channelId: string, content: string, finalize?: boolean, failed?: boolean): Promise<boolean>;
|
|
100
|
+
/**
|
|
101
|
+
* Append a delta to the active card transcript.
|
|
102
|
+
*/
|
|
103
|
+
appendToCard(channelId: string, content: string, finalize?: boolean, failed?: boolean): Promise<boolean>;
|
|
104
|
+
/**
|
|
105
|
+
* Stream content to the active AI Card for a channel using full replacement semantics.
|
|
98
106
|
*/
|
|
99
107
|
streamToCard(channelId: string, content: string, finalize?: boolean): Promise<boolean>;
|
|
100
108
|
/**
|
package/dist/runtime/dingtalk.js
CHANGED
|
@@ -362,48 +362,80 @@ export class DingTalkBot {
|
|
|
362
362
|
await this.createCard(channelId);
|
|
363
363
|
}
|
|
364
364
|
/**
|
|
365
|
-
*
|
|
365
|
+
* Replace the active card content with a full snapshot.
|
|
366
366
|
*/
|
|
367
|
-
async
|
|
367
|
+
async replaceCard(channelId, content, finalize = false, failed = false) {
|
|
368
368
|
let card = this.activeCards.get(channelId);
|
|
369
|
-
if ((!card || card.finished) &&
|
|
369
|
+
if ((!card || card.finished) && this.config.cardTemplateId && (content.trim() || !finalize || failed)) {
|
|
370
370
|
await this.ensureCard(channelId);
|
|
371
371
|
card = this.activeCards.get(channelId);
|
|
372
372
|
}
|
|
373
373
|
if (!card || card.finished) {
|
|
374
|
-
if (finalize) {
|
|
374
|
+
if (finalize && content.trim()) {
|
|
375
375
|
return this.sendPlain(channelId, content);
|
|
376
376
|
}
|
|
377
377
|
return false;
|
|
378
378
|
}
|
|
379
|
-
const streamed = await this.streamCard(card, content,
|
|
380
|
-
|
|
379
|
+
const streamed = await this.streamCard(card, content, {
|
|
380
|
+
append: false,
|
|
381
|
+
finalize,
|
|
382
|
+
failed,
|
|
383
|
+
});
|
|
384
|
+
if (!streamed || finalize || failed) {
|
|
381
385
|
this.activeCards.delete(channelId);
|
|
382
386
|
}
|
|
383
387
|
return streamed;
|
|
384
388
|
}
|
|
385
389
|
/**
|
|
386
|
-
*
|
|
387
|
-
* Returns true if a card was finalized, false if no active card existed.
|
|
390
|
+
* Append a delta to the active card transcript.
|
|
388
391
|
*/
|
|
389
|
-
async
|
|
392
|
+
async appendToCard(channelId, content, finalize = false, failed = false) {
|
|
393
|
+
if (!content && !finalize && !failed) {
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
390
396
|
let card = this.activeCards.get(channelId);
|
|
391
|
-
if ((!card || card.finished) && this.config.cardTemplateId && content.trim()) {
|
|
397
|
+
if ((!card || card.finished) && !finalize && !failed && this.config.cardTemplateId && content.trim()) {
|
|
392
398
|
await this.ensureCard(channelId);
|
|
393
399
|
card = this.activeCards.get(channelId);
|
|
394
400
|
}
|
|
395
401
|
if (!card || card.finished) {
|
|
402
|
+
if (finalize && content.trim()) {
|
|
403
|
+
return this.sendPlain(channelId, content);
|
|
404
|
+
}
|
|
396
405
|
return false;
|
|
397
406
|
}
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
407
|
+
const streamed = await this.streamCard(card, content, {
|
|
408
|
+
append: true,
|
|
409
|
+
finalize,
|
|
410
|
+
failed,
|
|
411
|
+
});
|
|
412
|
+
if (!streamed || finalize || failed) {
|
|
413
|
+
this.activeCards.delete(channelId);
|
|
414
|
+
}
|
|
415
|
+
return streamed;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Stream content to the active AI Card for a channel using full replacement semantics.
|
|
419
|
+
*/
|
|
420
|
+
async streamToCard(channelId, content, finalize = false) {
|
|
421
|
+
return this.replaceCard(channelId, content, finalize, false);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Finalize the active card for a channel without falling back to a plain message.
|
|
425
|
+
* Returns true if a card was finalized, false if no active card existed.
|
|
426
|
+
*/
|
|
427
|
+
async finalizeExistingCard(channelId, content) {
|
|
428
|
+
const finalized = await this.replaceCard(channelId, content, true, false);
|
|
429
|
+
if (!finalized) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
return true;
|
|
401
433
|
}
|
|
402
434
|
/**
|
|
403
435
|
* Finalize and remove the active card for a channel.
|
|
404
436
|
*/
|
|
405
437
|
async finalizeCard(channelId, content) {
|
|
406
|
-
const finalized = await this.
|
|
438
|
+
const finalized = await this.replaceCard(channelId, content, true, false);
|
|
407
439
|
if (!finalized) {
|
|
408
440
|
return this.sendPlain(channelId, content);
|
|
409
441
|
}
|
|
@@ -529,7 +561,7 @@ export class DingTalkBot {
|
|
|
529
561
|
this.activeCards.set(channelId, card);
|
|
530
562
|
return card;
|
|
531
563
|
}
|
|
532
|
-
async streamCard(card, content,
|
|
564
|
+
async streamCard(card, content, options) {
|
|
533
565
|
// Refresh token if needed
|
|
534
566
|
const ageSecs = Date.now() / 1000 - card.createdAt;
|
|
535
567
|
if (ageSecs > TOKEN_REFRESH_SECS) {
|
|
@@ -543,9 +575,12 @@ export class DingTalkBot {
|
|
|
543
575
|
guid: `${Date.now()}_${Math.random().toString(36).substring(2, 8)}`,
|
|
544
576
|
key: card.templateKey,
|
|
545
577
|
content,
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
578
|
+
append: options.append,
|
|
579
|
+
finished: options.finalize,
|
|
580
|
+
failed: options.failed,
|
|
581
|
+
isFull: !options.append,
|
|
582
|
+
isFinalize: options.finalize,
|
|
583
|
+
isError: options.failed,
|
|
549
584
|
};
|
|
550
585
|
const start = Date.now();
|
|
551
586
|
try {
|
|
@@ -560,8 +595,8 @@ export class DingTalkBot {
|
|
|
560
595
|
log.logWarning(`DingTalk Card: streaming request took ${duration}ms (slow)`);
|
|
561
596
|
}
|
|
562
597
|
card.lastUpdated = Date.now() / 1000;
|
|
563
|
-
card.content = content;
|
|
564
|
-
if (finalize) {
|
|
598
|
+
card.content = options.append ? `${card.content}${content}` : content;
|
|
599
|
+
if (options.finalize || options.failed) {
|
|
565
600
|
card.finished = true;
|
|
566
601
|
}
|
|
567
602
|
return true;
|
package/dist/runtime/events.d.ts
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
|
+
import type { Executor } from "../sandbox.js";
|
|
2
|
+
import type { SecurityConfig } from "../security/types.js";
|
|
1
3
|
import type { DingTalkBot } from "./dingtalk.js";
|
|
4
|
+
export interface EventAction {
|
|
5
|
+
type: "bash";
|
|
6
|
+
command: string;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
}
|
|
2
9
|
export interface ImmediateEvent {
|
|
3
10
|
type: "immediate";
|
|
4
11
|
channelId: string;
|
|
5
12
|
text: string;
|
|
13
|
+
preAction?: EventAction;
|
|
6
14
|
}
|
|
7
15
|
export interface OneShotEvent {
|
|
8
16
|
type: "one-shot";
|
|
9
17
|
channelId: string;
|
|
10
18
|
text: string;
|
|
11
19
|
at: string;
|
|
20
|
+
preAction?: EventAction;
|
|
12
21
|
}
|
|
13
22
|
export interface PeriodicEvent {
|
|
14
23
|
type: "periodic";
|
|
@@ -16,18 +25,21 @@ export interface PeriodicEvent {
|
|
|
16
25
|
text: string;
|
|
17
26
|
schedule: string;
|
|
18
27
|
timezone: string;
|
|
28
|
+
preAction?: EventAction;
|
|
19
29
|
}
|
|
20
30
|
export type ScheduledEvent = ImmediateEvent | OneShotEvent | PeriodicEvent;
|
|
21
31
|
export declare class EventsWatcher {
|
|
22
32
|
private eventsDir;
|
|
23
33
|
private bot;
|
|
34
|
+
private executor;
|
|
35
|
+
private commandGuardConfig?;
|
|
24
36
|
private timers;
|
|
25
37
|
private crons;
|
|
26
38
|
private debounceTimers;
|
|
27
39
|
private startTime;
|
|
28
40
|
private watcher;
|
|
29
41
|
private knownFiles;
|
|
30
|
-
constructor(eventsDir: string, bot: DingTalkBot);
|
|
42
|
+
constructor(eventsDir: string, bot: DingTalkBot, executor: Executor, commandGuardConfig?: SecurityConfig["commandGuard"] | undefined);
|
|
31
43
|
start(): void;
|
|
32
44
|
stop(): void;
|
|
33
45
|
private debounce;
|
|
@@ -36,11 +48,13 @@ export declare class EventsWatcher {
|
|
|
36
48
|
private handleDelete;
|
|
37
49
|
private cancelScheduled;
|
|
38
50
|
private handleFile;
|
|
51
|
+
private parsePreAction;
|
|
39
52
|
private parseEvent;
|
|
40
53
|
private handleImmediate;
|
|
41
54
|
private handleOneShot;
|
|
42
55
|
private handlePeriodic;
|
|
43
56
|
private execute;
|
|
57
|
+
private runPreAction;
|
|
44
58
|
private deleteFile;
|
|
45
59
|
private getInvalidMarkerPath;
|
|
46
60
|
private markInvalid;
|
|
@@ -50,4 +64,4 @@ export declare class EventsWatcher {
|
|
|
50
64
|
/**
|
|
51
65
|
* Create and start an events watcher.
|
|
52
66
|
*/
|
|
53
|
-
export declare function createEventsWatcher(workspaceDir: string, bot: DingTalkBot): EventsWatcher;
|
|
67
|
+
export declare function createEventsWatcher(workspaceDir: string, bot: DingTalkBot, executor: Executor, commandGuardConfig?: SecurityConfig["commandGuard"]): EventsWatcher;
|