@oyasmi/pipiclaw 0.5.1 → 0.5.3
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 +308 -209
- package/dist/agent/channel-runner.d.ts +47 -0
- package/dist/agent/channel-runner.js +441 -0
- package/dist/agent/index.d.ts +3 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/progress-formatter.d.ts +4 -0
- package/dist/agent/progress-formatter.js +52 -0
- package/dist/agent/run-queue.d.ts +7 -0
- package/dist/agent/run-queue.js +26 -0
- package/dist/agent/runner-factory.d.ts +3 -0
- package/dist/agent/runner-factory.js +10 -0
- package/dist/agent/session-events.d.ts +14 -0
- package/dist/agent/session-events.js +215 -0
- package/dist/agent/session-resource-gate.d.ts +10 -0
- package/dist/agent/session-resource-gate.js +44 -0
- package/dist/agent/type-guards.d.ts +22 -0
- package/dist/agent/type-guards.js +106 -0
- package/dist/agent/types.d.ts +160 -0
- package/dist/agent/types.js +22 -0
- package/dist/agent.d.ts +2 -16
- package/dist/agent.js +1 -782
- package/dist/command-extension.d.ts +0 -1
- package/dist/command-extension.js +0 -1
- package/dist/commands.d.ts +0 -1
- package/dist/commands.js +0 -1
- package/dist/config-loader.d.ts +0 -1
- package/dist/config-loader.js +1 -2
- package/dist/context.d.ts +58 -15
- package/dist/context.js +50 -8
- package/dist/index.d.ts +12 -13
- package/dist/index.js +12 -13
- package/dist/log.d.ts +0 -1
- package/dist/log.js +0 -1
- package/dist/main.d.ts +0 -1
- package/dist/main.js +5 -405
- package/dist/memory/bootstrap.d.ts +6 -0
- package/dist/memory/bootstrap.js +46 -0
- package/dist/{memory-candidates.d.ts → memory/candidates.d.ts} +1 -1
- package/dist/{memory-candidates.js → memory/candidates.js} +33 -21
- package/dist/memory/chinese-words.d.ts +1 -0
- package/dist/memory/chinese-words.js +273 -0
- package/dist/{memory-consolidation.d.ts → memory/consolidation.d.ts} +0 -1
- package/dist/{memory-consolidation.js → memory/consolidation.js} +26 -35
- package/dist/{memory-files.d.ts → memory/files.d.ts} +0 -6
- package/dist/{memory-files.js → memory/files.js} +11 -36
- package/dist/{memory-lifecycle.d.ts → memory/lifecycle.d.ts} +23 -6
- package/dist/memory/lifecycle.js +246 -0
- package/dist/{memory-recall.d.ts → memory/recall.d.ts} +2 -2
- package/dist/memory/recall.js +501 -0
- package/dist/{session-memory.d.ts → memory/session.d.ts} +1 -1
- package/dist/{session-memory.js → memory/session.js} +31 -62
- package/dist/model-utils.d.ts +0 -1
- package/dist/model-utils.js +0 -1
- package/dist/paths.d.ts +0 -1
- package/dist/paths.js +0 -1
- package/dist/prompt-builder.d.ts +0 -1
- package/dist/prompt-builder.js +0 -1
- package/dist/runtime/bootstrap.d.ts +47 -0
- package/dist/runtime/bootstrap.js +450 -0
- package/dist/{delivery.d.ts → runtime/delivery.d.ts} +0 -1
- package/dist/{delivery.js → runtime/delivery.js} +1 -2
- package/dist/{dingtalk.d.ts → runtime/dingtalk.d.ts} +10 -1
- package/dist/{dingtalk.js → runtime/dingtalk.js} +87 -28
- package/dist/{events.d.ts → runtime/events.d.ts} +0 -1
- package/dist/{events.js → runtime/events.js} +1 -2
- package/dist/{store.d.ts → runtime/store.d.ts} +5 -1
- package/dist/{store.js → runtime/store.js} +60 -20
- package/dist/sandbox.d.ts +0 -1
- package/dist/sandbox.js +1 -2
- package/dist/{llm-json.d.ts → shared/llm-json.d.ts} +0 -1
- package/dist/{llm-json.js → shared/llm-json.js} +0 -1
- package/dist/shared/markdown-sections.d.ts +6 -0
- package/dist/{markdown-sections.js → shared/markdown-sections.js} +10 -4
- package/dist/{shell-escape.d.ts → shared/shell-escape.d.ts} +0 -1
- package/dist/{shell-escape.js → shared/shell-escape.js} +0 -1
- package/dist/shared/text-utils.d.ts +9 -0
- package/dist/shared/text-utils.js +36 -0
- package/dist/shared/type-guards.d.ts +5 -0
- package/dist/shared/type-guards.js +12 -0
- package/dist/shared/types.d.ts +14 -0
- package/dist/shared/types.js +1 -0
- package/dist/sidecar-worker.d.ts +0 -1
- package/dist/sidecar-worker.js +1 -8
- package/dist/{sub-agents.d.ts → subagents/discovery.d.ts} +0 -1
- package/dist/{sub-agents.js → subagents/discovery.js} +2 -3
- package/dist/{tools/subagent.d.ts → subagents/tool.d.ts} +2 -16
- package/dist/{tools/subagent.js → subagents/tool.js} +16 -38
- package/dist/tools/attach.d.ts +0 -1
- package/dist/tools/attach.js +0 -1
- package/dist/tools/bash.d.ts +0 -1
- package/dist/tools/bash.js +0 -1
- package/dist/tools/edit.d.ts +0 -1
- package/dist/tools/edit.js +1 -2
- package/dist/tools/index.d.ts +1 -2
- package/dist/tools/index.js +1 -2
- package/dist/tools/read.d.ts +0 -1
- package/dist/tools/read.js +1 -2
- package/dist/tools/truncate.d.ts +0 -1
- package/dist/tools/truncate.js +0 -1
- package/dist/tools/write-content.d.ts +0 -1
- package/dist/tools/write-content.js +1 -2
- package/dist/tools/write.d.ts +0 -1
- package/dist/tools/write.js +0 -1
- package/package.json +9 -3
- package/CHANGELOG.md +0 -47
- package/dist/agent.d.ts.map +0 -1
- package/dist/agent.js.map +0 -1
- package/dist/command-extension.d.ts.map +0 -1
- package/dist/command-extension.js.map +0 -1
- package/dist/commands.d.ts.map +0 -1
- package/dist/commands.js.map +0 -1
- package/dist/config-loader.d.ts.map +0 -1
- package/dist/config-loader.js.map +0 -1
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js.map +0 -1
- package/dist/delivery.d.ts.map +0 -1
- package/dist/delivery.js.map +0 -1
- package/dist/dingtalk.d.ts.map +0 -1
- package/dist/dingtalk.js.map +0 -1
- package/dist/events.d.ts.map +0 -1
- package/dist/events.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/llm-json.d.ts.map +0 -1
- package/dist/llm-json.js.map +0 -1
- package/dist/log.d.ts.map +0 -1
- package/dist/log.js.map +0 -1
- package/dist/main.d.ts.map +0 -1
- package/dist/main.js.map +0 -1
- package/dist/markdown-sections.d.ts +0 -6
- package/dist/markdown-sections.d.ts.map +0 -1
- package/dist/markdown-sections.js.map +0 -1
- package/dist/memory-candidates.d.ts.map +0 -1
- package/dist/memory-candidates.js.map +0 -1
- package/dist/memory-consolidation.d.ts.map +0 -1
- package/dist/memory-consolidation.js.map +0 -1
- package/dist/memory-files.d.ts.map +0 -1
- package/dist/memory-files.js.map +0 -1
- package/dist/memory-lifecycle.d.ts.map +0 -1
- package/dist/memory-lifecycle.js +0 -150
- package/dist/memory-lifecycle.js.map +0 -1
- package/dist/memory-recall.d.ts.map +0 -1
- package/dist/memory-recall.js +0 -218
- package/dist/memory-recall.js.map +0 -1
- package/dist/model-utils.d.ts.map +0 -1
- package/dist/model-utils.js.map +0 -1
- package/dist/paths.d.ts.map +0 -1
- package/dist/paths.js.map +0 -1
- package/dist/prompt-builder.d.ts.map +0 -1
- package/dist/prompt-builder.js.map +0 -1
- package/dist/sandbox.d.ts.map +0 -1
- package/dist/sandbox.js.map +0 -1
- package/dist/session-memory-files.d.ts +0 -2
- package/dist/session-memory-files.d.ts.map +0 -1
- package/dist/session-memory-files.js +0 -2
- package/dist/session-memory-files.js.map +0 -1
- package/dist/session-memory.d.ts.map +0 -1
- package/dist/session-memory.js.map +0 -1
- package/dist/shell-escape.d.ts.map +0 -1
- package/dist/shell-escape.js.map +0 -1
- package/dist/sidecar-worker.d.ts.map +0 -1
- package/dist/sidecar-worker.js.map +0 -1
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js.map +0 -1
- package/dist/sub-agents.d.ts.map +0 -1
- package/dist/sub-agents.js.map +0 -1
- package/dist/tools/attach.d.ts.map +0 -1
- package/dist/tools/attach.js.map +0 -1
- package/dist/tools/bash.d.ts.map +0 -1
- package/dist/tools/bash.js.map +0 -1
- package/dist/tools/edit.d.ts.map +0 -1
- package/dist/tools/edit.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/read.d.ts.map +0 -1
- package/dist/tools/read.js.map +0 -1
- package/dist/tools/subagent.d.ts.map +0 -1
- package/dist/tools/subagent.js.map +0 -1
- package/dist/tools/truncate.d.ts.map +0 -1
- package/dist/tools/truncate.js.map +0 -1
- package/dist/tools/write-content.d.ts.map +0 -1
- package/dist/tools/write-content.js.map +0 -1
- package/dist/tools/write.d.ts.map +0 -1
- package/dist/tools/write.js.map +0 -1
- package/docs/improve-memory/design.md +0 -537
- package/docs/improve-memory/interfaces-and-tests.md +0 -473
- package/docs/improve-memory/spec.md +0 -357
- package/docs/memory-rfc.md +0 -297
- package/docs/proj-review.md +0 -188
- package/docs/subagent/pi-subagent-analyse.txt +0 -190
- package/docs/subagent/pi-subagent-design.txt +0 -266
- package/docs/subagent/pi-subagent-phase1-plan.txt +0 -529
- package/docs/test-supplementation-plan.md +0 -553
|
@@ -11,8 +11,9 @@ import axios from "axios";
|
|
|
11
11
|
import { DWClient, TOPIC_ROBOT } from "dingtalk-stream";
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
13
13
|
import { dirname, join } from "path";
|
|
14
|
-
import { parseBuiltInCommand, renderBuiltInHelp } from "
|
|
15
|
-
import * as log from "
|
|
14
|
+
import { parseBuiltInCommand, renderBuiltInHelp } from "../commands.js";
|
|
15
|
+
import * as log from "../log.js";
|
|
16
|
+
import { isRecord } from "../shared/type-guards.js";
|
|
16
17
|
class ChannelQueue {
|
|
17
18
|
constructor() {
|
|
18
19
|
this.queue = [];
|
|
@@ -72,6 +73,7 @@ export class DingTalkBot {
|
|
|
72
73
|
this.lastSocketAvailableTime = Date.now();
|
|
73
74
|
this.activeMessageProcessing = false;
|
|
74
75
|
this.keepAliveTimer = null;
|
|
76
|
+
this.reconnectTimer = null;
|
|
75
77
|
this.isReconnecting = false;
|
|
76
78
|
this.isStopped = false;
|
|
77
79
|
this.reconnectAttempts = 0;
|
|
@@ -95,6 +97,67 @@ export class DingTalkBot {
|
|
|
95
97
|
}
|
|
96
98
|
return true;
|
|
97
99
|
}
|
|
100
|
+
getSocket() {
|
|
101
|
+
if (!this.client) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const socket = Reflect.get(this.client, "socket");
|
|
105
|
+
return this.isSocketLike(socket) ? socket : null;
|
|
106
|
+
}
|
|
107
|
+
isSocketLike(value) {
|
|
108
|
+
if (!isRecord(value)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return typeof value.on === "function";
|
|
112
|
+
}
|
|
113
|
+
setTrackedTimeout(callback, delayMs) {
|
|
114
|
+
const timer = setTimeout(() => {
|
|
115
|
+
callback();
|
|
116
|
+
}, delayMs);
|
|
117
|
+
timer.unref?.();
|
|
118
|
+
return timer;
|
|
119
|
+
}
|
|
120
|
+
setTrackedInterval(callback, intervalMs) {
|
|
121
|
+
const timer = setInterval(callback, intervalMs);
|
|
122
|
+
timer.unref?.();
|
|
123
|
+
return timer;
|
|
124
|
+
}
|
|
125
|
+
clearKeepAliveTimer() {
|
|
126
|
+
if (this.keepAliveTimer) {
|
|
127
|
+
clearInterval(this.keepAliveTimer);
|
|
128
|
+
this.keepAliveTimer = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
clearReconnectTimer() {
|
|
132
|
+
if (this.reconnectTimer) {
|
|
133
|
+
clearTimeout(this.reconnectTimer);
|
|
134
|
+
this.reconnectTimer = null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
clearAllTimers() {
|
|
138
|
+
this.clearKeepAliveTimer();
|
|
139
|
+
this.clearReconnectTimer();
|
|
140
|
+
}
|
|
141
|
+
async waitForDelay(delayMs) {
|
|
142
|
+
await new Promise((resolve) => {
|
|
143
|
+
this.reconnectTimer = this.setTrackedTimeout(() => {
|
|
144
|
+
this.reconnectTimer = null;
|
|
145
|
+
resolve();
|
|
146
|
+
}, delayMs);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
scheduleReconnect(delayMs, immediate) {
|
|
150
|
+
if (this.isStopped) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this.clearReconnectTimer();
|
|
154
|
+
this.reconnectTimer = this.setTrackedTimeout(() => {
|
|
155
|
+
this.reconnectTimer = null;
|
|
156
|
+
this.doReconnect(immediate).catch((err) => {
|
|
157
|
+
log.logWarning("DingTalk: reconnect failed", err instanceof Error ? err.message : String(err));
|
|
158
|
+
});
|
|
159
|
+
}, delayMs);
|
|
160
|
+
}
|
|
98
161
|
// ==========================================================================
|
|
99
162
|
// Public API
|
|
100
163
|
// ==========================================================================
|
|
@@ -110,10 +173,10 @@ export class DingTalkBot {
|
|
|
110
173
|
if (process.env.DINGTALK_FORCE_PROXY !== "true") {
|
|
111
174
|
axios.defaults.proxy = false;
|
|
112
175
|
}
|
|
176
|
+
this.clearAllTimers();
|
|
113
177
|
this.client = new DWClient({
|
|
114
178
|
clientId: this.config.clientId,
|
|
115
179
|
clientSecret: this.config.clientSecret,
|
|
116
|
-
autoReconnect: false,
|
|
117
180
|
keepAlive: false,
|
|
118
181
|
});
|
|
119
182
|
this.client.registerCallbackListener(TOPIC_ROBOT, (msg) => {
|
|
@@ -133,7 +196,8 @@ export class DingTalkBot {
|
|
|
133
196
|
return { status: "SUCCESS", message: "OK" };
|
|
134
197
|
}
|
|
135
198
|
try {
|
|
136
|
-
const
|
|
199
|
+
const parsedData = typeof msg.data === "string" ? JSON.parse(msg.data) : msg.data;
|
|
200
|
+
const data = isRecord(parsedData) ? parsedData : {};
|
|
137
201
|
// 3. Business logic deduplication
|
|
138
202
|
const msgId = data.msgId;
|
|
139
203
|
if (msgId && !this.markProcessed(msgId)) {
|
|
@@ -157,21 +221,24 @@ export class DingTalkBot {
|
|
|
157
221
|
if (!immediate && this.reconnectAttempts > 0) {
|
|
158
222
|
const delay = Math.min(1000 * 2 ** this.reconnectAttempts + Math.random() * 1000, 30000);
|
|
159
223
|
log.logInfo(`DingTalk: waiting ${Math.round(delay / 1000)}s before reconnecting...`);
|
|
160
|
-
await
|
|
224
|
+
await this.waitForDelay(delay);
|
|
225
|
+
if (this.isStopped || !this.client) {
|
|
226
|
+
this.isReconnecting = false;
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
161
229
|
}
|
|
162
230
|
try {
|
|
163
|
-
const socket = this.
|
|
231
|
+
const socket = this.getSocket();
|
|
164
232
|
if (socket?.readyState === 1 || socket?.readyState === 3) {
|
|
165
|
-
await this.client.disconnect();
|
|
233
|
+
await Promise.resolve(this.client.disconnect());
|
|
166
234
|
}
|
|
167
235
|
await this.client.connect();
|
|
168
236
|
this.lastSocketAvailableTime = Date.now();
|
|
169
237
|
this.reconnectAttempts = 0; // Success, reset backoff
|
|
170
238
|
log.logInfo("DingTalk: connected to stream.");
|
|
171
239
|
// Setup keep alive
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this.keepAliveTimer = setInterval(() => {
|
|
240
|
+
this.clearKeepAliveTimer();
|
|
241
|
+
this.keepAliveTimer = this.setTrackedInterval(() => {
|
|
175
242
|
if (this.isStopped)
|
|
176
243
|
return;
|
|
177
244
|
const elapsed = Date.now() - this.lastSocketAvailableTime;
|
|
@@ -179,9 +246,9 @@ export class DingTalkBot {
|
|
|
179
246
|
log.logWarning("DingTalk: connection timeout detected (>90s). Keeping active where possible...");
|
|
180
247
|
}
|
|
181
248
|
try {
|
|
182
|
-
const s = this.
|
|
249
|
+
const s = this.getSocket();
|
|
183
250
|
if (s?.readyState === 1) {
|
|
184
|
-
s.ping();
|
|
251
|
+
s.ping?.();
|
|
185
252
|
}
|
|
186
253
|
}
|
|
187
254
|
catch (_err) {
|
|
@@ -189,7 +256,7 @@ export class DingTalkBot {
|
|
|
189
256
|
}
|
|
190
257
|
}, 30 * 1000);
|
|
191
258
|
// Setup native socket events
|
|
192
|
-
const s = this.
|
|
259
|
+
const s = this.getSocket();
|
|
193
260
|
s?.on("pong", () => {
|
|
194
261
|
this.lastSocketAvailableTime = Date.now();
|
|
195
262
|
});
|
|
@@ -197,11 +264,7 @@ export class DingTalkBot {
|
|
|
197
264
|
log.logWarning(`DingTalk: WebSocket closed: code=${code}, reason=${reason}`);
|
|
198
265
|
if (this.isStopped)
|
|
199
266
|
return;
|
|
200
|
-
|
|
201
|
-
this.doReconnect(true).catch((err) => {
|
|
202
|
-
log.logWarning("DingTalk: reconnect failed", err instanceof Error ? err.message : String(err));
|
|
203
|
-
});
|
|
204
|
-
}, 1000);
|
|
267
|
+
this.scheduleReconnect(1000, true);
|
|
205
268
|
});
|
|
206
269
|
s?.on("message", (raw) => {
|
|
207
270
|
try {
|
|
@@ -228,20 +291,19 @@ export class DingTalkBot {
|
|
|
228
291
|
}
|
|
229
292
|
// Auto-retry on failure with exponential backoff
|
|
230
293
|
if (connectionFailed && !this.isStopped) {
|
|
231
|
-
this.
|
|
294
|
+
this.scheduleReconnect(0, false);
|
|
232
295
|
}
|
|
233
296
|
}
|
|
234
297
|
async stop() {
|
|
235
298
|
log.logInfo("DingTalk: stopping bot");
|
|
236
299
|
this.isStopped = true;
|
|
237
|
-
|
|
238
|
-
clearInterval(this.keepAliveTimer);
|
|
300
|
+
this.clearAllTimers();
|
|
239
301
|
for (const queue of this.queues.values()) {
|
|
240
302
|
queue.stop();
|
|
241
303
|
}
|
|
242
304
|
if (this.client) {
|
|
243
305
|
try {
|
|
244
|
-
await Promise.resolve(this.client.disconnect
|
|
306
|
+
await Promise.resolve(this.client.disconnect());
|
|
245
307
|
}
|
|
246
308
|
catch (err) {
|
|
247
309
|
log.logWarning("DingTalk: failed to disconnect cleanly", err instanceof Error ? err.message : String(err));
|
|
@@ -553,11 +615,9 @@ export class DingTalkBot {
|
|
|
553
615
|
if (textContent)
|
|
554
616
|
return textContent;
|
|
555
617
|
// 2. richText 类型消息:从 content.richText 列表提取文本片段
|
|
556
|
-
|
|
557
|
-
const contentObj = raw.content;
|
|
558
|
-
if (contentObj?.richText) {
|
|
618
|
+
if (data.content?.richText) {
|
|
559
619
|
const parts = [];
|
|
560
|
-
for (const item of
|
|
620
|
+
for (const item of data.content.richText) {
|
|
561
621
|
if (item.text)
|
|
562
622
|
parts.push(item.text);
|
|
563
623
|
}
|
|
@@ -577,7 +637,7 @@ export class DingTalkBot {
|
|
|
577
637
|
const conversationId = data.conversationId || "";
|
|
578
638
|
const conversationType = data.conversationType || "1";
|
|
579
639
|
if (!content) {
|
|
580
|
-
const msgtype = data.msgtype
|
|
640
|
+
const msgtype = typeof data.msgtype === "string" ? data.msgtype : "unknown";
|
|
581
641
|
log.logWarning(`DingTalk: empty message (type=${msgtype})`);
|
|
582
642
|
return;
|
|
583
643
|
}
|
|
@@ -704,4 +764,3 @@ export class DingTalkBot {
|
|
|
704
764
|
return join(this.config.stateDir, channelId, ".channel-meta.json");
|
|
705
765
|
}
|
|
706
766
|
}
|
|
707
|
-
//# sourceMappingURL=dingtalk.js.map
|
|
@@ -2,7 +2,7 @@ import { Cron } from "croner";
|
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync, watch } from "fs";
|
|
3
3
|
import { readFile } from "fs/promises";
|
|
4
4
|
import { join } from "path";
|
|
5
|
-
import * as log from "
|
|
5
|
+
import * as log from "../log.js";
|
|
6
6
|
// ============================================================================
|
|
7
7
|
// EventsWatcher
|
|
8
8
|
// ============================================================================
|
|
@@ -292,4 +292,3 @@ export function createEventsWatcher(workspaceDir, bot) {
|
|
|
292
292
|
const eventsDir = join(workspaceDir, "events");
|
|
293
293
|
return new EventsWatcher(eventsDir, bot);
|
|
294
294
|
}
|
|
295
|
-
//# sourceMappingURL=events.js.map
|
|
@@ -45,6 +45,8 @@ export interface LoggedSubAgentRun {
|
|
|
45
45
|
export declare class ChannelStore {
|
|
46
46
|
private workingDir;
|
|
47
47
|
private recentlyLogged;
|
|
48
|
+
private cleanupTimer;
|
|
49
|
+
private writeChains;
|
|
48
50
|
constructor(config: ChannelStoreConfig);
|
|
49
51
|
/**
|
|
50
52
|
* Get or create the directory for a channel/DM
|
|
@@ -71,5 +73,7 @@ export declare class ChannelStore {
|
|
|
71
73
|
* Returns null if no log exists
|
|
72
74
|
*/
|
|
73
75
|
getLastTimestamp(channelId: string): string | null;
|
|
76
|
+
private enqueueWrite;
|
|
77
|
+
private startCleanupTimer;
|
|
78
|
+
private cleanupExpiredEntries;
|
|
74
79
|
}
|
|
75
|
-
//# sourceMappingURL=store.d.ts.map
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { closeSync, existsSync, mkdirSync, openSync, readSync, renameSync, statSync } from "fs";
|
|
2
2
|
import { appendFile, writeFile } from "fs/promises";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
|
+
const MAX_LOG_SIZE_BYTES = 1_000_000;
|
|
5
|
+
const DEDUPE_TTL_MS = 60_000;
|
|
6
|
+
const DEDUPE_CLEANUP_INTERVAL_MS = 30_000;
|
|
4
7
|
export class ChannelStore {
|
|
5
8
|
constructor(config) {
|
|
6
|
-
// Track recently logged message timestamps to prevent duplicates
|
|
7
9
|
this.recentlyLogged = new Map();
|
|
10
|
+
this.cleanupTimer = null;
|
|
11
|
+
this.writeChains = new Map();
|
|
8
12
|
this.workingDir = config.workingDir;
|
|
9
13
|
// Ensure working directory exists
|
|
10
14
|
if (!existsSync(this.workingDir)) {
|
|
@@ -27,46 +31,50 @@ export class ChannelStore {
|
|
|
27
31
|
* Returns false if message was already logged (duplicate)
|
|
28
32
|
*/
|
|
29
33
|
async logMessage(channelId, message) {
|
|
30
|
-
// Check for duplicate (same channel + timestamp)
|
|
31
34
|
const dedupeKey = `${channelId}:${message.ts}`;
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
const previousLogTime = this.recentlyLogged.get(dedupeKey);
|
|
37
|
+
if (previousLogTime !== undefined) {
|
|
38
|
+
if (now - previousLogTime < DEDUPE_TTL_MS) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
this.recentlyLogged.delete(dedupeKey);
|
|
34
42
|
}
|
|
35
|
-
|
|
36
|
-
this.
|
|
37
|
-
setTimeout(() => this.recentlyLogged.delete(dedupeKey), 60000);
|
|
43
|
+
this.recentlyLogged.set(dedupeKey, now);
|
|
44
|
+
this.startCleanupTimer();
|
|
38
45
|
const logPath = join(this.getChannelDir(channelId), "log.jsonl");
|
|
39
|
-
// Rotate if file exceeds size limit
|
|
40
|
-
this.rotateIfNeeded(logPath);
|
|
41
|
-
// Ensure message has a date field
|
|
42
46
|
if (!message.date) {
|
|
43
47
|
message.date = new Date().toISOString();
|
|
44
48
|
}
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
await this.enqueueWrite(logPath, async () => {
|
|
50
|
+
await this.rotateIfNeeded(logPath);
|
|
51
|
+
const line = `${JSON.stringify(message)}\n`;
|
|
52
|
+
await appendFile(logPath, line, "utf-8");
|
|
53
|
+
});
|
|
47
54
|
return true;
|
|
48
55
|
}
|
|
49
56
|
async logSubAgentRun(channelId, run) {
|
|
50
57
|
const logPath = join(this.getChannelDir(channelId), "subagent-runs.jsonl");
|
|
51
|
-
this.
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
await this.enqueueWrite(logPath, async () => {
|
|
59
|
+
await this.rotateIfNeeded(logPath);
|
|
60
|
+
const line = `${JSON.stringify(run)}\n`;
|
|
61
|
+
await appendFile(logPath, line, "utf-8");
|
|
62
|
+
});
|
|
54
63
|
}
|
|
55
64
|
/**
|
|
56
65
|
* Rotate log file if it exceeds 1MB.
|
|
57
66
|
* Keeps one backup (log.jsonl.1) and resets the sync offset.
|
|
58
67
|
*/
|
|
59
|
-
rotateIfNeeded(logPath) {
|
|
68
|
+
async rotateIfNeeded(logPath) {
|
|
60
69
|
try {
|
|
61
70
|
if (!existsSync(logPath))
|
|
62
71
|
return;
|
|
63
72
|
const stats = statSync(logPath);
|
|
64
|
-
if (stats.size >
|
|
73
|
+
if (stats.size > MAX_LOG_SIZE_BYTES) {
|
|
65
74
|
renameSync(logPath, `${logPath}.1`);
|
|
66
|
-
// Reset sync offset since log.jsonl was replaced
|
|
67
75
|
const syncOffsetPath = join(dirname(logPath), ".sync-offset");
|
|
68
76
|
try {
|
|
69
|
-
writeFile(syncOffsetPath, "0", "utf-8")
|
|
77
|
+
await writeFile(syncOffsetPath, "0", "utf-8");
|
|
70
78
|
}
|
|
71
79
|
catch {
|
|
72
80
|
/* ignore */
|
|
@@ -152,5 +160,37 @@ export class ChannelStore {
|
|
|
152
160
|
return null;
|
|
153
161
|
}
|
|
154
162
|
}
|
|
163
|
+
enqueueWrite(logPath, work) {
|
|
164
|
+
const previous = this.writeChains.get(logPath) ?? Promise.resolve();
|
|
165
|
+
const result = previous.catch(() => undefined).then(() => work());
|
|
166
|
+
const completion = result.then(() => undefined, () => undefined);
|
|
167
|
+
this.writeChains.set(logPath, completion);
|
|
168
|
+
completion.finally(() => {
|
|
169
|
+
if (this.writeChains.get(logPath) === completion) {
|
|
170
|
+
this.writeChains.delete(logPath);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
startCleanupTimer() {
|
|
176
|
+
if (this.cleanupTimer) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.cleanupTimer = setInterval(() => {
|
|
180
|
+
this.cleanupExpiredEntries();
|
|
181
|
+
}, DEDUPE_CLEANUP_INTERVAL_MS);
|
|
182
|
+
this.cleanupTimer.unref?.();
|
|
183
|
+
}
|
|
184
|
+
cleanupExpiredEntries(now = Date.now()) {
|
|
185
|
+
const cutoff = now - DEDUPE_TTL_MS;
|
|
186
|
+
for (const [key, loggedAt] of this.recentlyLogged) {
|
|
187
|
+
if (loggedAt <= cutoff) {
|
|
188
|
+
this.recentlyLogged.delete(key);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (this.recentlyLogged.size === 0 && this.cleanupTimer) {
|
|
192
|
+
clearInterval(this.cleanupTimer);
|
|
193
|
+
this.cleanupTimer = null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
155
196
|
}
|
|
156
|
-
//# sourceMappingURL=store.js.map
|
package/dist/sandbox.d.ts
CHANGED
package/dist/sandbox.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
-
import { shellEscape } from "./shell-escape.js";
|
|
2
|
+
import { shellEscape } from "./shared/shell-escape.js";
|
|
3
3
|
export function parseSandboxArg(value) {
|
|
4
4
|
if (value === "host") {
|
|
5
5
|
return { type: "host" };
|
|
@@ -219,4 +219,3 @@ function killProcessTree(pid) {
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
}
|
|
222
|
-
//# sourceMappingURL=sandbox.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
function splitSectionsByHeading(content, headingPrefix) {
|
|
2
2
|
const normalized = content.replace(/\r/g, "").trim();
|
|
3
3
|
if (!normalized) {
|
|
4
4
|
return [];
|
|
@@ -7,6 +7,7 @@ export function splitLevelOneSections(content) {
|
|
|
7
7
|
const sections = [];
|
|
8
8
|
let currentHeading = "";
|
|
9
9
|
let currentLines = [];
|
|
10
|
+
const prefixLength = headingPrefix.length;
|
|
10
11
|
const flush = () => {
|
|
11
12
|
if (!currentHeading) {
|
|
12
13
|
return;
|
|
@@ -18,9 +19,9 @@ export function splitLevelOneSections(content) {
|
|
|
18
19
|
sections.push({ heading: currentHeading, content: sectionContent });
|
|
19
20
|
};
|
|
20
21
|
for (const line of lines) {
|
|
21
|
-
if (line.startsWith(
|
|
22
|
+
if (line.startsWith(headingPrefix)) {
|
|
22
23
|
flush();
|
|
23
|
-
currentHeading = line.slice(
|
|
24
|
+
currentHeading = line.slice(prefixLength).trim();
|
|
24
25
|
currentLines = [];
|
|
25
26
|
continue;
|
|
26
27
|
}
|
|
@@ -31,4 +32,9 @@ export function splitLevelOneSections(content) {
|
|
|
31
32
|
flush();
|
|
32
33
|
return sections;
|
|
33
34
|
}
|
|
34
|
-
|
|
35
|
+
export function splitH1Sections(content) {
|
|
36
|
+
return splitSectionsByHeading(content, "# ");
|
|
37
|
+
}
|
|
38
|
+
export function splitH2Sections(content) {
|
|
39
|
+
return splitSectionsByHeading(content, "## ");
|
|
40
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
|
2
|
+
export declare function clipText(text: string, maxChars: number, opts?: {
|
|
3
|
+
headRatio?: number;
|
|
4
|
+
omitHint?: string;
|
|
5
|
+
}): string;
|
|
6
|
+
export declare function truncate(text: string, maxLen: number): string;
|
|
7
|
+
export declare const HAN_REGEX: RegExp;
|
|
8
|
+
export declare function extractLabelFromArgs(args: unknown): string | null;
|
|
9
|
+
export declare function extractAssistantText(message: AssistantMessage): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function clipText(text, maxChars, opts = {}) {
|
|
2
|
+
const normalized = text.replace(/\s+\n/g, "\n").replace(/\r/g, "").trim();
|
|
3
|
+
if (normalized.length <= maxChars) {
|
|
4
|
+
return normalized;
|
|
5
|
+
}
|
|
6
|
+
const headRatio = Math.max(0, Math.min(1, opts.headRatio ?? 0.45));
|
|
7
|
+
const omitHint = opts.omitHint ?? "[... omitted middle section ...]";
|
|
8
|
+
if (headRatio >= 1) {
|
|
9
|
+
const headChars = Math.max(0, maxChars - omitHint.length);
|
|
10
|
+
return `${normalized.slice(0, headChars).trimEnd()}${omitHint}`;
|
|
11
|
+
}
|
|
12
|
+
const headChars = Math.floor(maxChars * headRatio);
|
|
13
|
+
const tailChars = maxChars - headChars;
|
|
14
|
+
return `${normalized.slice(0, headChars)}\n\n${omitHint}\n\n${normalized.slice(-tailChars)}`;
|
|
15
|
+
}
|
|
16
|
+
export function truncate(text, maxLen) {
|
|
17
|
+
if (text.length <= maxLen) {
|
|
18
|
+
return text;
|
|
19
|
+
}
|
|
20
|
+
return `${text.substring(0, maxLen - 3)}...`;
|
|
21
|
+
}
|
|
22
|
+
export const HAN_REGEX = /\p{Script=Han}/u;
|
|
23
|
+
export function extractLabelFromArgs(args) {
|
|
24
|
+
if (!args || typeof args !== "object" || !("label" in args)) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const label = args.label;
|
|
28
|
+
return typeof label === "string" && label.trim() ? label.trim() : null;
|
|
29
|
+
}
|
|
30
|
+
export function extractAssistantText(message) {
|
|
31
|
+
return message.content
|
|
32
|
+
.filter((part) => part.type === "text")
|
|
33
|
+
.map((part) => part.text)
|
|
34
|
+
.join("\n")
|
|
35
|
+
.trim();
|
|
36
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
|
2
|
+
import type { Message } from "@mariozechner/pi-ai";
|
|
3
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
4
|
+
export declare function isStandardAgentMessage(message: AgentMessage): message is Message;
|
|
5
|
+
export declare function buildStandardMessages(messages: AgentMessage[]): Message[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function isRecord(value) {
|
|
2
|
+
return typeof value === "object" && value !== null;
|
|
3
|
+
}
|
|
4
|
+
export function isStandardAgentMessage(message) {
|
|
5
|
+
return (typeof message === "object" &&
|
|
6
|
+
message !== null &&
|
|
7
|
+
"role" in message &&
|
|
8
|
+
(message.role === "user" || message.role === "assistant" || message.role === "toolResult"));
|
|
9
|
+
}
|
|
10
|
+
export function buildStandardMessages(messages) {
|
|
11
|
+
return messages.filter(isStandardAgentMessage);
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/sidecar-worker.d.ts
CHANGED
package/dist/sidecar-worker.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
2
2
|
import { convertToLlm } from "@mariozechner/pi-coding-agent";
|
|
3
|
+
import { extractAssistantText } from "./shared/text-utils.js";
|
|
3
4
|
export class SidecarTimeoutError extends Error {
|
|
4
5
|
constructor(taskName, timeoutMs) {
|
|
5
6
|
super(`Sidecar task "${taskName}" timed out after ${timeoutMs}ms`);
|
|
@@ -17,13 +18,6 @@ export class SidecarParseError extends Error {
|
|
|
17
18
|
this.cause = cause;
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
|
-
function extractAssistantText(message) {
|
|
21
|
-
return message.content
|
|
22
|
-
.filter((part) => part.type === "text")
|
|
23
|
-
.map((part) => part.text)
|
|
24
|
-
.join("\n")
|
|
25
|
-
.trim();
|
|
26
|
-
}
|
|
27
21
|
export async function runSidecarTask(task) {
|
|
28
22
|
const apiKey = await task.resolveApiKey(task.model);
|
|
29
23
|
const worker = new Agent({
|
|
@@ -102,4 +96,3 @@ export async function runSidecarTask(task) {
|
|
|
102
96
|
removeAbortListener();
|
|
103
97
|
}
|
|
104
98
|
}
|
|
105
|
-
//# sourceMappingURL=sidecar-worker.js.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { parseFrontmatter } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
import { findExactModelReferenceMatch, formatModelReference } from "
|
|
5
|
-
import { SUB_AGENTS_DIR_NAME } from "
|
|
4
|
+
import { findExactModelReferenceMatch, formatModelReference } from "../model-utils.js";
|
|
5
|
+
import { SUB_AGENTS_DIR_NAME } from "../paths.js";
|
|
6
6
|
const ALLOWED_SUB_AGENT_TOOLS = ["read", "bash", "edit", "write"];
|
|
7
7
|
const DEFAULT_SUB_AGENT_TOOLS = ["read", "bash"];
|
|
8
8
|
const DEFAULT_MAX_TURNS = 24;
|
|
@@ -366,4 +366,3 @@ export function formatSubAgentList(agents, maxItems = 12) {
|
|
|
366
366
|
}
|
|
367
367
|
return `${listed.join("\n")}\n- ... and ${agents.length - maxItems} more`;
|
|
368
368
|
}
|
|
369
|
-
//# sourceMappingURL=sub-agents.js.map
|
|
@@ -2,7 +2,8 @@ import { type AgentEvent, type AgentMessage, type AgentTool } from "@mariozechne
|
|
|
2
2
|
import type { Api, Model } from "@mariozechner/pi-ai";
|
|
3
3
|
import type { PipiclawMemoryRecallSettings } from "../context.js";
|
|
4
4
|
import type { Executor } from "../sandbox.js";
|
|
5
|
-
import
|
|
5
|
+
import type { UsageTotals } from "../shared/types.js";
|
|
6
|
+
import { type ResolvedSubAgentConfig, type SubAgentDiscoveryResult } from "./discovery.js";
|
|
6
7
|
declare const subagentSchema: import("@sinclair/typebox").TObject<{
|
|
7
8
|
label: import("@sinclair/typebox").TString;
|
|
8
9
|
agent: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
@@ -19,20 +20,6 @@ declare const subagentSchema: import("@sinclair/typebox").TObject<{
|
|
|
19
20
|
memory: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
20
21
|
paths: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>>;
|
|
21
22
|
}>;
|
|
22
|
-
interface UsageTotals {
|
|
23
|
-
input: number;
|
|
24
|
-
output: number;
|
|
25
|
-
cacheRead: number;
|
|
26
|
-
cacheWrite: number;
|
|
27
|
-
total: number;
|
|
28
|
-
cost: {
|
|
29
|
-
input: number;
|
|
30
|
-
output: number;
|
|
31
|
-
cacheRead: number;
|
|
32
|
-
cacheWrite: number;
|
|
33
|
-
total: number;
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
23
|
export interface SubAgentToolDetails {
|
|
37
24
|
kind: "subagent";
|
|
38
25
|
agent: string;
|
|
@@ -77,4 +64,3 @@ interface SubAgentWorker {
|
|
|
77
64
|
}
|
|
78
65
|
export declare function createSubAgentTool(options: SubAgentToolOptions): AgentTool<typeof subagentSchema, SubAgentToolDetails>;
|
|
79
66
|
export {};
|
|
80
|
-
//# sourceMappingURL=subagent.d.ts.map
|