@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/runtime/events.js
CHANGED
|
@@ -3,6 +3,7 @@ import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync, watch, writeF
|
|
|
3
3
|
import { readFile } from "fs/promises";
|
|
4
4
|
import { join } from "path";
|
|
5
5
|
import * as log from "../log.js";
|
|
6
|
+
import { guardCommand } from "../security/command-guard.js";
|
|
6
7
|
// ============================================================================
|
|
7
8
|
// EventsWatcher
|
|
8
9
|
// ============================================================================
|
|
@@ -11,9 +12,11 @@ const MAX_RETRIES = 3;
|
|
|
11
12
|
const RETRY_BASE_MS = 100;
|
|
12
13
|
const MAX_TIMEOUT_MS = 2_147_483_647;
|
|
13
14
|
export class EventsWatcher {
|
|
14
|
-
constructor(eventsDir, bot) {
|
|
15
|
+
constructor(eventsDir, bot, executor, commandGuardConfig) {
|
|
15
16
|
this.eventsDir = eventsDir;
|
|
16
17
|
this.bot = bot;
|
|
18
|
+
this.executor = executor;
|
|
19
|
+
this.commandGuardConfig = commandGuardConfig;
|
|
17
20
|
this.timers = new Map();
|
|
18
21
|
this.crons = new Map();
|
|
19
22
|
this.debounceTimers = new Map();
|
|
@@ -145,19 +148,39 @@ export class EventsWatcher {
|
|
|
145
148
|
break;
|
|
146
149
|
}
|
|
147
150
|
}
|
|
151
|
+
parsePreAction(data, filename) {
|
|
152
|
+
if (!data.preAction)
|
|
153
|
+
return undefined;
|
|
154
|
+
if (typeof data.preAction !== "object" || data.preAction === null) {
|
|
155
|
+
throw new Error(`Invalid 'preAction' field in ${filename}, expected an object`);
|
|
156
|
+
}
|
|
157
|
+
const action = data.preAction;
|
|
158
|
+
if (action.type !== "bash") {
|
|
159
|
+
throw new Error(`Unsupported preAction type '${String(action.type)}' in ${filename}, only 'bash' is supported`);
|
|
160
|
+
}
|
|
161
|
+
if (typeof action.command !== "string" || action.command.trim().length === 0) {
|
|
162
|
+
throw new Error(`Missing or empty 'preAction.command' in ${filename}`);
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
type: "bash",
|
|
166
|
+
command: action.command,
|
|
167
|
+
...(typeof action.timeout === "number" ? { timeout: action.timeout } : {}),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
148
170
|
parseEvent(content, filename) {
|
|
149
171
|
const data = JSON.parse(content);
|
|
150
172
|
if (!data.type || !data.channelId || !data.text) {
|
|
151
173
|
throw new Error(`Missing required fields (type, channelId, text) in ${filename}`);
|
|
152
174
|
}
|
|
175
|
+
const preAction = this.parsePreAction(data, filename);
|
|
153
176
|
switch (data.type) {
|
|
154
177
|
case "immediate":
|
|
155
|
-
return { type: "immediate", channelId: data.channelId, text: data.text };
|
|
178
|
+
return { type: "immediate", channelId: data.channelId, text: data.text, preAction };
|
|
156
179
|
case "one-shot":
|
|
157
180
|
if (!data.at) {
|
|
158
181
|
throw new Error(`Missing 'at' field for one-shot event in ${filename}`);
|
|
159
182
|
}
|
|
160
|
-
return { type: "one-shot", channelId: data.channelId, text: data.text, at: data.at };
|
|
183
|
+
return { type: "one-shot", channelId: data.channelId, text: data.text, at: data.at, preAction };
|
|
161
184
|
case "periodic":
|
|
162
185
|
if (!data.schedule) {
|
|
163
186
|
throw new Error(`Missing 'schedule' field for periodic event in ${filename}`);
|
|
@@ -171,12 +194,13 @@ export class EventsWatcher {
|
|
|
171
194
|
text: data.text,
|
|
172
195
|
schedule: data.schedule,
|
|
173
196
|
timezone: data.timezone,
|
|
197
|
+
preAction,
|
|
174
198
|
};
|
|
175
199
|
default:
|
|
176
200
|
throw new Error(`Unknown event type '${data.type}' in ${filename}`);
|
|
177
201
|
}
|
|
178
202
|
}
|
|
179
|
-
handleImmediate(filename, event) {
|
|
203
|
+
async handleImmediate(filename, event) {
|
|
180
204
|
const filePath = join(this.eventsDir, filename);
|
|
181
205
|
try {
|
|
182
206
|
const stat = statSync(filePath);
|
|
@@ -190,7 +214,7 @@ export class EventsWatcher {
|
|
|
190
214
|
return;
|
|
191
215
|
}
|
|
192
216
|
log.logInfo(`Executing immediate event: ${filename}`);
|
|
193
|
-
this.execute(filename, event);
|
|
217
|
+
await this.execute(filename, event);
|
|
194
218
|
}
|
|
195
219
|
handleOneShot(filename, event) {
|
|
196
220
|
const atTime = new Date(event.at).getTime();
|
|
@@ -212,18 +236,28 @@ export class EventsWatcher {
|
|
|
212
236
|
return;
|
|
213
237
|
}
|
|
214
238
|
log.logInfo(`Scheduling one-shot event: ${filename} in ${Math.round(delay / 1000)}s`);
|
|
215
|
-
const timer = setTimeout(() => {
|
|
239
|
+
const timer = setTimeout(async () => {
|
|
216
240
|
this.timers.delete(filename);
|
|
217
|
-
|
|
218
|
-
|
|
241
|
+
try {
|
|
242
|
+
log.logInfo(`Executing one-shot event: ${filename}`);
|
|
243
|
+
await this.execute(filename, event);
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
log.logWarning(`One-shot event execution failed: ${filename}`, String(err));
|
|
247
|
+
}
|
|
219
248
|
}, delay);
|
|
220
249
|
this.timers.set(filename, timer);
|
|
221
250
|
}
|
|
222
251
|
handlePeriodic(filename, event) {
|
|
223
252
|
try {
|
|
224
|
-
const cron = new Cron(event.schedule, { timezone: event.timezone }, () => {
|
|
225
|
-
|
|
226
|
-
|
|
253
|
+
const cron = new Cron(event.schedule, { timezone: event.timezone }, async () => {
|
|
254
|
+
try {
|
|
255
|
+
log.logInfo(`Executing periodic event: ${filename}`);
|
|
256
|
+
await this.execute(filename, event, false);
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
log.logWarning(`Periodic event execution failed: ${filename}`, String(err));
|
|
260
|
+
}
|
|
227
261
|
});
|
|
228
262
|
this.crons.set(filename, cron);
|
|
229
263
|
const next = cron.nextRun();
|
|
@@ -234,7 +268,17 @@ export class EventsWatcher {
|
|
|
234
268
|
this.markInvalid(filename, `Invalid cron schedule: ${event.schedule}\n${String(err)}`);
|
|
235
269
|
}
|
|
236
270
|
}
|
|
237
|
-
execute(filename, event, deleteAfter = true) {
|
|
271
|
+
async execute(filename, event, deleteAfter = true) {
|
|
272
|
+
if (event.preAction) {
|
|
273
|
+
try {
|
|
274
|
+
await this.runPreAction(event.preAction, filename);
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
278
|
+
log.logInfo(`Pre-action gate blocked event: ${filename} (${reason})`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
238
282
|
let scheduleInfo;
|
|
239
283
|
switch (event.type) {
|
|
240
284
|
case "immediate":
|
|
@@ -270,6 +314,21 @@ export class EventsWatcher {
|
|
|
270
314
|
}
|
|
271
315
|
}
|
|
272
316
|
}
|
|
317
|
+
async runPreAction(action, filename) {
|
|
318
|
+
if (this.commandGuardConfig?.enabled) {
|
|
319
|
+
const guardResult = guardCommand(action.command, this.commandGuardConfig);
|
|
320
|
+
if (!guardResult.allowed) {
|
|
321
|
+
log.logWarning(`Pre-action command blocked by guard for ${filename}: ${guardResult.reason}`);
|
|
322
|
+
throw new Error(`guard: ${guardResult.reason}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const timeoutMs = action.timeout ?? 10_000;
|
|
326
|
+
const timeoutSeconds = Math.max(1, Math.ceil(timeoutMs / 1000));
|
|
327
|
+
const result = await this.executor.exec(action.command, { timeout: timeoutSeconds });
|
|
328
|
+
if (result.code !== 0) {
|
|
329
|
+
throw new Error(`exit ${result.code}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
273
332
|
deleteFile(filename) {
|
|
274
333
|
const filePath = join(this.eventsDir, filename);
|
|
275
334
|
try {
|
|
@@ -313,7 +372,7 @@ export class EventsWatcher {
|
|
|
313
372
|
/**
|
|
314
373
|
* Create and start an events watcher.
|
|
315
374
|
*/
|
|
316
|
-
export function createEventsWatcher(workspaceDir, bot) {
|
|
375
|
+
export function createEventsWatcher(workspaceDir, bot, executor, commandGuardConfig) {
|
|
317
376
|
const eventsDir = join(workspaceDir, "events");
|
|
318
|
-
return new EventsWatcher(eventsDir, bot);
|
|
377
|
+
return new EventsWatcher(eventsDir, bot, executor, commandGuardConfig);
|
|
319
378
|
}
|
package/dist/settings.js
CHANGED
|
@@ -194,13 +194,13 @@ export class PipiclawSettingsManager {
|
|
|
194
194
|
}
|
|
195
195
|
// Compaction details
|
|
196
196
|
getCompactionReserveTokens() {
|
|
197
|
-
return
|
|
197
|
+
return this.getCompactionSettings().reserveTokens;
|
|
198
198
|
}
|
|
199
199
|
getCompactionKeepRecentTokens() {
|
|
200
|
-
return
|
|
200
|
+
return this.getCompactionSettings().keepRecentTokens;
|
|
201
201
|
}
|
|
202
202
|
getBranchSummarySettings() {
|
|
203
|
-
return { reserveTokens:
|
|
203
|
+
return { reserveTokens: this.getCompactionSettings().reserveTokens };
|
|
204
204
|
}
|
|
205
205
|
getBranchSummarySkipPrompt() {
|
|
206
206
|
return false;
|
package/dist/subagents/tool.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type AgentEvent, type AgentMessage, type AgentTool } from "@mariozechner/pi-agent-core";
|
|
2
2
|
import type { Api, Model } from "@mariozechner/pi-ai";
|
|
3
|
+
import type { MemoryCandidateStore } from "../memory/candidates.js";
|
|
3
4
|
import type { Executor } from "../sandbox.js";
|
|
4
5
|
import type { SecurityConfig } from "../security/types.js";
|
|
5
6
|
import type { PipiclawMemoryRecallSettings } from "../settings.js";
|
|
@@ -44,6 +45,7 @@ export interface SubAgentToolOptions {
|
|
|
44
45
|
channelDir: string;
|
|
45
46
|
getSubAgentDiscovery?: () => SubAgentDiscoveryResult;
|
|
46
47
|
getMemoryRecallSettings?: () => PipiclawMemoryRecallSettings;
|
|
48
|
+
memoryCandidateStore?: MemoryCandidateStore;
|
|
47
49
|
securityConfig?: SecurityConfig;
|
|
48
50
|
webConfig?: PipiclawWebToolsConfig;
|
|
49
51
|
runtimeContext: {
|
package/dist/subagents/tool.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
2
2
|
import { convertToLlm } from "@mariozechner/pi-coding-agent";
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
|
-
import { createMemoryCandidateCache } from "../memory/candidates.js";
|
|
5
4
|
import { readChannelSession } from "../memory/files.js";
|
|
6
5
|
import { recallRelevantMemory } from "../memory/recall.js";
|
|
7
6
|
import { formatModelReference } from "../models/utils.js";
|
|
@@ -187,7 +186,7 @@ function stripRuntimeContextWrapper(renderedText) {
|
|
|
187
186
|
.replace(/\s*<\/runtime_context>$/i, "")
|
|
188
187
|
.trim();
|
|
189
188
|
}
|
|
190
|
-
async function buildContextualBlocks(task, config, options, currentModel
|
|
189
|
+
async function buildContextualBlocks(task, config, options, currentModel) {
|
|
191
190
|
if (config.contextMode !== "contextual") {
|
|
192
191
|
return [];
|
|
193
192
|
}
|
|
@@ -226,7 +225,7 @@ async function buildContextualBlocks(task, config, options, currentModel, candid
|
|
|
226
225
|
model: currentModel,
|
|
227
226
|
resolveApiKey: options.resolveApiKey,
|
|
228
227
|
allowedSources: ["workspace-memory", "channel-memory", "channel-history"],
|
|
229
|
-
|
|
228
|
+
candidateStore: options.memoryCandidateStore,
|
|
230
229
|
});
|
|
231
230
|
const recalledText = stripRuntimeContextWrapper(recalled.renderedText);
|
|
232
231
|
if (recalledText) {
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
2
2
|
import type { Api, Model } from "@mariozechner/pi-ai";
|
|
3
|
+
import type { MemoryCandidateStore } from "../memory/candidates.js";
|
|
3
4
|
import type { Executor, SandboxConfig } from "../sandbox.js";
|
|
4
5
|
import type { SecurityConfig, SecurityRuntimeContext } from "../security/types.js";
|
|
5
6
|
import type { PipiclawMemoryRecallSettings } from "../settings.js";
|
|
@@ -17,6 +18,7 @@ export interface CreatePipiclawToolsOptions {
|
|
|
17
18
|
sandboxConfig: SandboxConfig;
|
|
18
19
|
getSubAgentDiscovery: () => SubAgentDiscoveryResult;
|
|
19
20
|
getMemoryRecallSettings: () => PipiclawMemoryRecallSettings;
|
|
21
|
+
memoryCandidateStore: MemoryCandidateStore;
|
|
20
22
|
securityConfig?: SecurityConfig;
|
|
21
23
|
toolsConfig?: PipiclawToolsConfig;
|
|
22
24
|
}
|
package/dist/tools/index.js
CHANGED
|
@@ -65,6 +65,7 @@ export function createPipiclawTools(options) {
|
|
|
65
65
|
channelDir: options.channelDir,
|
|
66
66
|
getSubAgentDiscovery: options.getSubAgentDiscovery,
|
|
67
67
|
getMemoryRecallSettings: options.getMemoryRecallSettings,
|
|
68
|
+
memoryCandidateStore: options.memoryCandidateStore,
|
|
68
69
|
securityConfig,
|
|
69
70
|
webConfig: toolsConfig.tools.web,
|
|
70
71
|
runtimeContext: {
|
package/dist/tools/read.js
CHANGED
|
@@ -28,6 +28,12 @@ const readSchema = Type.Object({
|
|
|
28
28
|
offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
|
|
29
29
|
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
|
|
30
30
|
});
|
|
31
|
+
function countTextLines(content) {
|
|
32
|
+
if (content.length === 0) {
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
35
|
+
return content.endsWith("\n") ? content.split("\n").length - 1 : content.split("\n").length;
|
|
36
|
+
}
|
|
31
37
|
function formatPathBlockMessage(resolvedPath, category, reason) {
|
|
32
38
|
const lines = [`Path blocked${category ? ` [${category}]` : ""}`];
|
|
33
39
|
if (reason) {
|
|
@@ -84,16 +90,17 @@ export function createReadTool(executor, options = {}) {
|
|
|
84
90
|
};
|
|
85
91
|
}
|
|
86
92
|
// Get total line count first
|
|
87
|
-
const countResult = await executor.exec(`
|
|
93
|
+
const countResult = await executor.exec(`awk 'END { print NR }' ${shellEscapePath(path)}`, { signal });
|
|
88
94
|
if (countResult.code !== 0) {
|
|
89
95
|
throw new Error(countResult.stderr || `Failed to read file: ${path}`);
|
|
90
96
|
}
|
|
91
|
-
const totalFileLines = Number.parseInt(countResult.stdout.trim(), 10)
|
|
97
|
+
const totalFileLines = Number.parseInt(countResult.stdout.trim(), 10);
|
|
92
98
|
// Apply offset if specified (1-indexed)
|
|
93
99
|
const startLine = offset ? Math.max(1, offset) : 1;
|
|
94
100
|
const startLineDisplay = startLine;
|
|
95
101
|
// Check if offset is out of bounds
|
|
96
|
-
if (startLine >
|
|
102
|
+
if ((totalFileLines === 0 && offset !== undefined && startLine > 1) ||
|
|
103
|
+
(totalFileLines > 0 && startLine > totalFileLines)) {
|
|
97
104
|
throw new Error(`Offset ${offset} is beyond end of file (${totalFileLines} lines total)`);
|
|
98
105
|
}
|
|
99
106
|
// Read content with offset
|
|
@@ -113,7 +120,7 @@ export function createReadTool(executor, options = {}) {
|
|
|
113
120
|
// Apply user limit if specified
|
|
114
121
|
if (limit !== undefined) {
|
|
115
122
|
const lines = selectedContent.split("\n");
|
|
116
|
-
const endLine = Math.min(limit,
|
|
123
|
+
const endLine = Math.min(limit, countTextLines(selectedContent));
|
|
117
124
|
selectedContent = lines.slice(0, endLine).join("\n");
|
|
118
125
|
userLimitedLines = endLine;
|
|
119
126
|
}
|
package/package.json
CHANGED