@geminixiang/mikan 0.2.0
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/CHANGELOG.md +324 -0
- package/LICENSE +22 -0
- package/README.md +297 -0
- package/dist/adapter.d.ts +134 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +2 -0
- package/dist/adapter.js.map +1 -0
- package/dist/adapters/discord/bot.d.ts +63 -0
- package/dist/adapters/discord/bot.d.ts.map +1 -0
- package/dist/adapters/discord/bot.js +577 -0
- package/dist/adapters/discord/bot.js.map +1 -0
- package/dist/adapters/discord/context.d.ts +9 -0
- package/dist/adapters/discord/context.d.ts.map +1 -0
- package/dist/adapters/discord/context.js +245 -0
- package/dist/adapters/discord/context.js.map +1 -0
- package/dist/adapters/discord/index.d.ts +3 -0
- package/dist/adapters/discord/index.d.ts.map +1 -0
- package/dist/adapters/discord/index.js +3 -0
- package/dist/adapters/discord/index.js.map +1 -0
- package/dist/adapters/shared.d.ts +91 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +191 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/slack/bot.d.ts +139 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -0
- package/dist/adapters/slack/bot.js +1272 -0
- package/dist/adapters/slack/bot.js.map +1 -0
- package/dist/adapters/slack/branch-manager.d.ts +28 -0
- package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
- package/dist/adapters/slack/branch-manager.js +117 -0
- package/dist/adapters/slack/branch-manager.js.map +1 -0
- package/dist/adapters/slack/context.d.ts +12 -0
- package/dist/adapters/slack/context.d.ts.map +1 -0
- package/dist/adapters/slack/context.js +327 -0
- package/dist/adapters/slack/context.js.map +1 -0
- package/dist/adapters/slack/index.d.ts +3 -0
- package/dist/adapters/slack/index.d.ts.map +1 -0
- package/dist/adapters/slack/index.js +3 -0
- package/dist/adapters/slack/index.js.map +1 -0
- package/dist/adapters/slack/session.d.ts +38 -0
- package/dist/adapters/slack/session.d.ts.map +1 -0
- package/dist/adapters/slack/session.js +66 -0
- package/dist/adapters/slack/session.js.map +1 -0
- package/dist/adapters/slack/tools/attach.d.ts +12 -0
- package/dist/adapters/slack/tools/attach.d.ts.map +1 -0
- package/dist/adapters/slack/tools/attach.js +40 -0
- package/dist/adapters/slack/tools/attach.js.map +1 -0
- package/dist/adapters/telegram/bot.d.ts +51 -0
- package/dist/adapters/telegram/bot.d.ts.map +1 -0
- package/dist/adapters/telegram/bot.js +430 -0
- package/dist/adapters/telegram/bot.js.map +1 -0
- package/dist/adapters/telegram/context.d.ts +9 -0
- package/dist/adapters/telegram/context.d.ts.map +1 -0
- package/dist/adapters/telegram/context.js +190 -0
- package/dist/adapters/telegram/context.js.map +1 -0
- package/dist/adapters/telegram/html.d.ts +3 -0
- package/dist/adapters/telegram/html.d.ts.map +1 -0
- package/dist/adapters/telegram/html.js +98 -0
- package/dist/adapters/telegram/html.js.map +1 -0
- package/dist/adapters/telegram/index.d.ts +3 -0
- package/dist/adapters/telegram/index.d.ts.map +1 -0
- package/dist/adapters/telegram/index.js +3 -0
- package/dist/adapters/telegram/index.js.map +1 -0
- package/dist/agent.d.ts +36 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +1147 -0
- package/dist/agent.js.map +1 -0
- package/dist/commands/auto-reply.d.ts +5 -0
- package/dist/commands/auto-reply.d.ts.map +1 -0
- package/dist/commands/auto-reply.js +79 -0
- package/dist/commands/auto-reply.js.map +1 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +18 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/login.d.ts +5 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +91 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/model.d.ts +14 -0
- package/dist/commands/model.d.ts.map +1 -0
- package/dist/commands/model.js +110 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/new.d.ts +5 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +24 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/parse.d.ts +7 -0
- package/dist/commands/parse.d.ts.map +1 -0
- package/dist/commands/parse.js +17 -0
- package/dist/commands/parse.js.map +1 -0
- package/dist/commands/registry.d.ts +4 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +9 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/sandbox.d.ts +10 -0
- package/dist/commands/sandbox.d.ts.map +1 -0
- package/dist/commands/sandbox.js +83 -0
- package/dist/commands/sandbox.js.map +1 -0
- package/dist/commands/session-view.d.ts +5 -0
- package/dist/commands/session-view.d.ts.map +1 -0
- package/dist/commands/session-view.js +62 -0
- package/dist/commands/session-view.js.map +1 -0
- package/dist/commands/types.d.ts +41 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/utils.d.ts +8 -0
- package/dist/commands/utils.d.ts.map +1 -0
- package/dist/commands/utils.js +14 -0
- package/dist/commands/utils.js.map +1 -0
- package/dist/config.d.ts +59 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +370 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +17 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +24 -0
- package/dist/context.js.map +1 -0
- package/dist/conversation-history.d.ts +16 -0
- package/dist/conversation-history.d.ts.map +1 -0
- package/dist/conversation-history.js +144 -0
- package/dist/conversation-history.js.map +1 -0
- package/dist/download.d.ts +2 -0
- package/dist/download.d.ts.map +1 -0
- package/dist/download.js +89 -0
- package/dist/download.js.map +1 -0
- package/dist/env.d.ts +3 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +12 -0
- package/dist/env.js.map +1 -0
- package/dist/events.d.ts +85 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +483 -0
- package/dist/events.js.map +1 -0
- package/dist/execution-resolver.d.ts +25 -0
- package/dist/execution-resolver.d.ts.map +1 -0
- package/dist/execution-resolver.js +167 -0
- package/dist/execution-resolver.js.map +1 -0
- package/dist/file-guards.d.ts +9 -0
- package/dist/file-guards.d.ts.map +1 -0
- package/dist/file-guards.js +56 -0
- package/dist/file-guards.js.map +1 -0
- package/dist/fs-atomic.d.ts +10 -0
- package/dist/fs-atomic.d.ts.map +1 -0
- package/dist/fs-atomic.js +45 -0
- package/dist/fs-atomic.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/instrument.d.ts +2 -0
- package/dist/instrument.d.ts.map +1 -0
- package/dist/instrument.js +10 -0
- package/dist/instrument.js.map +1 -0
- package/dist/log.d.ts +36 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +206 -0
- package/dist/log.js.map +1 -0
- package/dist/login/index.d.ts +42 -0
- package/dist/login/index.d.ts.map +1 -0
- package/dist/login/index.js +239 -0
- package/dist/login/index.js.map +1 -0
- package/dist/login/portal.d.ts +19 -0
- package/dist/login/portal.d.ts.map +1 -0
- package/dist/login/portal.js +1544 -0
- package/dist/login/portal.js.map +1 -0
- package/dist/login/session.d.ts +26 -0
- package/dist/login/session.d.ts.map +1 -0
- package/dist/login/session.js +56 -0
- package/dist/login/session.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +366 -0
- package/dist/main.js.map +1 -0
- package/dist/provisioner.d.ts +83 -0
- package/dist/provisioner.d.ts.map +1 -0
- package/dist/provisioner.js +500 -0
- package/dist/provisioner.js.map +1 -0
- package/dist/runtime/conversation-orchestrator.d.ts +40 -0
- package/dist/runtime/conversation-orchestrator.d.ts.map +1 -0
- package/dist/runtime/conversation-orchestrator.js +183 -0
- package/dist/runtime/conversation-orchestrator.js.map +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/session-runtime.d.ts +26 -0
- package/dist/runtime/session-runtime.d.ts.map +1 -0
- package/dist/runtime/session-runtime.js +221 -0
- package/dist/runtime/session-runtime.js.map +1 -0
- package/dist/sandbox/cloudflare.d.ts +15 -0
- package/dist/sandbox/cloudflare.d.ts.map +1 -0
- package/dist/sandbox/cloudflare.js +138 -0
- package/dist/sandbox/cloudflare.js.map +1 -0
- package/dist/sandbox/container.d.ts +16 -0
- package/dist/sandbox/container.d.ts.map +1 -0
- package/dist/sandbox/container.js +138 -0
- package/dist/sandbox/container.js.map +1 -0
- package/dist/sandbox/errors.d.ts +6 -0
- package/dist/sandbox/errors.d.ts.map +1 -0
- package/dist/sandbox/errors.js +11 -0
- package/dist/sandbox/errors.js.map +1 -0
- package/dist/sandbox/firecracker.d.ts +17 -0
- package/dist/sandbox/firecracker.d.ts.map +1 -0
- package/dist/sandbox/firecracker.js +212 -0
- package/dist/sandbox/firecracker.js.map +1 -0
- package/dist/sandbox/host.d.ts +11 -0
- package/dist/sandbox/host.d.ts.map +1 -0
- package/dist/sandbox/host.js +89 -0
- package/dist/sandbox/host.js.map +1 -0
- package/dist/sandbox/image.d.ts +5 -0
- package/dist/sandbox/image.d.ts.map +1 -0
- package/dist/sandbox/image.js +30 -0
- package/dist/sandbox/image.js.map +1 -0
- package/dist/sandbox/index.d.ts +22 -0
- package/dist/sandbox/index.d.ts.map +1 -0
- package/dist/sandbox/index.js +54 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/path-context.d.ts +4 -0
- package/dist/sandbox/path-context.d.ts.map +1 -0
- package/dist/sandbox/path-context.js +20 -0
- package/dist/sandbox/path-context.js.map +1 -0
- package/dist/sandbox/types.d.ts +67 -0
- package/dist/sandbox/types.d.ts.map +1 -0
- package/dist/sandbox/types.js +2 -0
- package/dist/sandbox/types.js.map +1 -0
- package/dist/sandbox/utils.d.ts +4 -0
- package/dist/sandbox/utils.d.ts.map +1 -0
- package/dist/sandbox/utils.js +51 -0
- package/dist/sandbox/utils.js.map +1 -0
- package/dist/sentry.d.ts +50 -0
- package/dist/sentry.d.ts.map +1 -0
- package/dist/sentry.js +257 -0
- package/dist/sentry.js.map +1 -0
- package/dist/session-view/command.d.ts +5 -0
- package/dist/session-view/command.d.ts.map +1 -0
- package/dist/session-view/command.js +7 -0
- package/dist/session-view/command.js.map +1 -0
- package/dist/session-view/portal.d.ts +16 -0
- package/dist/session-view/portal.d.ts.map +1 -0
- package/dist/session-view/portal.js +1822 -0
- package/dist/session-view/portal.js.map +1 -0
- package/dist/session-view/service.d.ts +34 -0
- package/dist/session-view/service.d.ts.map +1 -0
- package/dist/session-view/service.js +434 -0
- package/dist/session-view/service.js.map +1 -0
- package/dist/session-view/store.d.ts +18 -0
- package/dist/session-view/store.d.ts.map +1 -0
- package/dist/session-view/store.js +36 -0
- package/dist/session-view/store.js.map +1 -0
- package/dist/sessions/metadata.d.ts +15 -0
- package/dist/sessions/metadata.d.ts.map +1 -0
- package/dist/sessions/metadata.js +11 -0
- package/dist/sessions/metadata.js.map +1 -0
- package/dist/sessions/policy.d.ts +13 -0
- package/dist/sessions/policy.d.ts.map +1 -0
- package/dist/sessions/policy.js +23 -0
- package/dist/sessions/policy.js.map +1 -0
- package/dist/sessions/store.d.ts +103 -0
- package/dist/sessions/store.d.ts.map +1 -0
- package/dist/sessions/store.js +349 -0
- package/dist/sessions/store.js.map +1 -0
- package/dist/store.d.ts +58 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +152 -0
- package/dist/store.js.map +1 -0
- package/dist/tool-diagnostics.d.ts +2 -0
- package/dist/tool-diagnostics.d.ts.map +1 -0
- package/dist/tool-diagnostics.js +7 -0
- package/dist/tool-diagnostics.js.map +1 -0
- package/dist/tools/bash.d.ts +10 -0
- package/dist/tools/bash.d.ts.map +1 -0
- package/dist/tools/bash.js +80 -0
- package/dist/tools/bash.js.map +1 -0
- package/dist/tools/edit.d.ts +11 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +133 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/event.d.ts +62 -0
- package/dist/tools/event.d.ts.map +1 -0
- package/dist/tools/event.js +138 -0
- package/dist/tools/event.js.map +1 -0
- package/dist/tools/index.d.ts +14 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/read.d.ts +11 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +136 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/truncate.d.ts +57 -0
- package/dist/tools/truncate.d.ts.map +1 -0
- package/dist/tools/truncate.js +184 -0
- package/dist/tools/truncate.js.map +1 -0
- package/dist/tools/write.d.ts +10 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +33 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/trigger.d.ts +31 -0
- package/dist/trigger.d.ts.map +1 -0
- package/dist/trigger.js +98 -0
- package/dist/trigger.js.map +1 -0
- package/dist/ui-copy.d.ts +12 -0
- package/dist/ui-copy.d.ts.map +1 -0
- package/dist/ui-copy.js +36 -0
- package/dist/ui-copy.js.map +1 -0
- package/dist/vault-routing.d.ts +4 -0
- package/dist/vault-routing.d.ts.map +1 -0
- package/dist/vault-routing.js +16 -0
- package/dist/vault-routing.js.map +1 -0
- package/dist/vault.d.ts +72 -0
- package/dist/vault.d.ts.map +1 -0
- package/dist/vault.js +281 -0
- package/dist/vault.js.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared truncation utilities for tool outputs.
|
|
3
|
+
*
|
|
4
|
+
* Truncation is based on two independent limits - whichever is hit first wins:
|
|
5
|
+
* - Line limit (default: 2000 lines)
|
|
6
|
+
* - Byte limit (default: 50KB)
|
|
7
|
+
*
|
|
8
|
+
* Never returns partial lines (except bash tail truncation edge case).
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_MAX_LINES = 2000;
|
|
11
|
+
export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
|
|
12
|
+
/**
|
|
13
|
+
* Format bytes as human-readable size.
|
|
14
|
+
*/
|
|
15
|
+
export function formatSize(bytes) {
|
|
16
|
+
if (bytes < 1024) {
|
|
17
|
+
return `${bytes}B`;
|
|
18
|
+
}
|
|
19
|
+
else if (bytes < 1024 * 1024) {
|
|
20
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Truncate content from the head (keep first N lines/bytes).
|
|
28
|
+
* Suitable for file reads where you want to see the beginning.
|
|
29
|
+
*
|
|
30
|
+
* Never returns partial lines. If first line exceeds byte limit,
|
|
31
|
+
* returns empty content with firstLineExceedsLimit=true.
|
|
32
|
+
*/
|
|
33
|
+
export function truncateHead(content, options = {}) {
|
|
34
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
35
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
36
|
+
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
37
|
+
const lines = content.split("\n");
|
|
38
|
+
const totalLines = lines.length;
|
|
39
|
+
// Check if no truncation needed
|
|
40
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
41
|
+
return {
|
|
42
|
+
content,
|
|
43
|
+
truncated: false,
|
|
44
|
+
truncatedBy: null,
|
|
45
|
+
totalLines,
|
|
46
|
+
totalBytes,
|
|
47
|
+
outputLines: totalLines,
|
|
48
|
+
outputBytes: totalBytes,
|
|
49
|
+
lastLinePartial: false,
|
|
50
|
+
firstLineExceedsLimit: false,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Check if first line alone exceeds byte limit
|
|
54
|
+
const firstLineBytes = Buffer.byteLength(lines[0], "utf-8");
|
|
55
|
+
if (firstLineBytes > maxBytes) {
|
|
56
|
+
return {
|
|
57
|
+
content: "",
|
|
58
|
+
truncated: true,
|
|
59
|
+
truncatedBy: "bytes",
|
|
60
|
+
totalLines,
|
|
61
|
+
totalBytes,
|
|
62
|
+
outputLines: 0,
|
|
63
|
+
outputBytes: 0,
|
|
64
|
+
lastLinePartial: false,
|
|
65
|
+
firstLineExceedsLimit: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Collect complete lines that fit
|
|
69
|
+
const outputLinesArr = [];
|
|
70
|
+
let outputBytesCount = 0;
|
|
71
|
+
let truncatedBy = "lines";
|
|
72
|
+
for (let i = 0; i < lines.length && i < maxLines; i++) {
|
|
73
|
+
const line = lines[i];
|
|
74
|
+
const lineBytes = Buffer.byteLength(line, "utf-8") + (i > 0 ? 1 : 0); // +1 for newline
|
|
75
|
+
if (outputBytesCount + lineBytes > maxBytes) {
|
|
76
|
+
truncatedBy = "bytes";
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
outputLinesArr.push(line);
|
|
80
|
+
outputBytesCount += lineBytes;
|
|
81
|
+
}
|
|
82
|
+
// If we exited due to line limit
|
|
83
|
+
if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
|
|
84
|
+
truncatedBy = "lines";
|
|
85
|
+
}
|
|
86
|
+
const outputContent = outputLinesArr.join("\n");
|
|
87
|
+
const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
|
|
88
|
+
return {
|
|
89
|
+
content: outputContent,
|
|
90
|
+
truncated: true,
|
|
91
|
+
truncatedBy,
|
|
92
|
+
totalLines,
|
|
93
|
+
totalBytes,
|
|
94
|
+
outputLines: outputLinesArr.length,
|
|
95
|
+
outputBytes: finalOutputBytes,
|
|
96
|
+
lastLinePartial: false,
|
|
97
|
+
firstLineExceedsLimit: false,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Truncate content from the tail (keep last N lines/bytes).
|
|
102
|
+
* Suitable for bash output where you want to see the end (errors, final results).
|
|
103
|
+
*
|
|
104
|
+
* May return partial first line if the last line of original content exceeds byte limit.
|
|
105
|
+
*/
|
|
106
|
+
export function truncateTail(content, options = {}) {
|
|
107
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
108
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
109
|
+
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
110
|
+
const lines = content.split("\n");
|
|
111
|
+
const totalLines = lines.length;
|
|
112
|
+
// Check if no truncation needed
|
|
113
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
114
|
+
return {
|
|
115
|
+
content,
|
|
116
|
+
truncated: false,
|
|
117
|
+
truncatedBy: null,
|
|
118
|
+
totalLines,
|
|
119
|
+
totalBytes,
|
|
120
|
+
outputLines: totalLines,
|
|
121
|
+
outputBytes: totalBytes,
|
|
122
|
+
lastLinePartial: false,
|
|
123
|
+
firstLineExceedsLimit: false,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// Work backwards from the end
|
|
127
|
+
const outputLinesArr = [];
|
|
128
|
+
let outputBytesCount = 0;
|
|
129
|
+
let truncatedBy = "lines";
|
|
130
|
+
let lastLinePartial = false;
|
|
131
|
+
for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {
|
|
132
|
+
const line = lines[i];
|
|
133
|
+
const lineBytes = Buffer.byteLength(line, "utf-8") + (outputLinesArr.length > 0 ? 1 : 0); // +1 for newline
|
|
134
|
+
if (outputBytesCount + lineBytes > maxBytes) {
|
|
135
|
+
truncatedBy = "bytes";
|
|
136
|
+
// Edge case: if we haven't added ANY lines yet and this line exceeds maxBytes,
|
|
137
|
+
// take the end of the line (partial)
|
|
138
|
+
if (outputLinesArr.length === 0) {
|
|
139
|
+
const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);
|
|
140
|
+
outputLinesArr.unshift(truncatedLine);
|
|
141
|
+
outputBytesCount = Buffer.byteLength(truncatedLine, "utf-8");
|
|
142
|
+
lastLinePartial = true;
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
outputLinesArr.unshift(line);
|
|
147
|
+
outputBytesCount += lineBytes;
|
|
148
|
+
}
|
|
149
|
+
// If we exited due to line limit
|
|
150
|
+
if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {
|
|
151
|
+
truncatedBy = "lines";
|
|
152
|
+
}
|
|
153
|
+
const outputContent = outputLinesArr.join("\n");
|
|
154
|
+
const finalOutputBytes = Buffer.byteLength(outputContent, "utf-8");
|
|
155
|
+
return {
|
|
156
|
+
content: outputContent,
|
|
157
|
+
truncated: true,
|
|
158
|
+
truncatedBy,
|
|
159
|
+
totalLines,
|
|
160
|
+
totalBytes,
|
|
161
|
+
outputLines: outputLinesArr.length,
|
|
162
|
+
outputBytes: finalOutputBytes,
|
|
163
|
+
lastLinePartial,
|
|
164
|
+
firstLineExceedsLimit: false,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Truncate a string to fit within a byte limit (from the end).
|
|
169
|
+
* Handles multi-byte UTF-8 characters correctly.
|
|
170
|
+
*/
|
|
171
|
+
function truncateStringToBytesFromEnd(str, maxBytes) {
|
|
172
|
+
const buf = Buffer.from(str, "utf-8");
|
|
173
|
+
if (buf.length <= maxBytes) {
|
|
174
|
+
return str;
|
|
175
|
+
}
|
|
176
|
+
// Start from the end, skip maxBytes back
|
|
177
|
+
let start = buf.length - maxBytes;
|
|
178
|
+
// Find a valid UTF-8 boundary (start of a character)
|
|
179
|
+
while (start < buf.length && (buf[start] & 0xc0) === 0x80) {
|
|
180
|
+
start++;
|
|
181
|
+
}
|
|
182
|
+
return buf.slice(start).toString("utf-8");
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=truncate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"truncate.js","sourceRoot":"","sources":["../../src/tools/truncate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AACtC,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,OAAO;AA8BnD;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,IAAI,KAAK,GAAG,IAAI,EAAE,CAAC;QACjB,OAAO,GAAG,KAAK,GAAG,CAAC;IACrB,CAAC;SAAM,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;QAC/B,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1C,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACnD,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,OAAO,GAAsB,EAAE;IAC3E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IACvD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IAEvD,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC;IAEhC,gCAAgC;IAChC,IAAI,UAAU,IAAI,QAAQ,IAAI,UAAU,IAAI,QAAQ,EAAE,CAAC;QACrD,OAAO;YACL,OAAO;YACP,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,IAAI;YACjB,UAAU;YACV,UAAU;YACV,WAAW,EAAE,UAAU;YACvB,WAAW,EAAE,UAAU;YACvB,eAAe,EAAE,KAAK;YACtB,qBAAqB,EAAE,KAAK;SAC7B,CAAC;IACJ,CAAC;IAED,+CAA+C;IAC/C,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC5D,IAAI,cAAc,GAAG,QAAQ,EAAE,CAAC;QAC9B,OAAO;YACL,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,IAAI;YACf,WAAW,EAAE,OAAO;YACpB,UAAU;YACV,UAAU;YACV,WAAW,EAAE,CAAC;YACd,WAAW,EAAE,CAAC;YACd,eAAe,EAAE,KAAK;YACtB,qBAAqB,EAAE,IAAI;SAC5B,CAAC;IACJ,CAAC;IAED,kCAAkC;IAClC,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,IAAI,gBAAgB,GAAG,CAAC,CAAC;IACzB,IAAI,WAAW,GAAsB,OAAO,CAAC;IAE7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;QACtD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB;QAEvF,IAAI,gBAAgB,GAAG,SAAS,GAAG,QAAQ,EAAE,CAAC;YAC5C,WAAW,GAAG,OAAO,CAAC;YACtB,MAAM;QACR,CAAC;QAED,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1B,gBAAgB,IAAI,SAAS,CAAC;IAChC,CAAC;IAED,iCAAiC;IACjC,IAAI,cAAc,CAAC,MAAM,IAAI,QAAQ,IAAI,gBAAgB,IAAI,QAAQ,EAAE,CAAC;QACtE,WAAW,GAAG,OAAO,CAAC;IACxB,CAAC;IAED,MAAM,aAAa,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,gBAAgB,GAAG,MAAM,CAAC,UAAU,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IAEnE,OAAO;QACL,OAAO,EAAE,aAAa;QACtB,SAAS,EAAE,IAAI;QACf,WAAW;QACX,UAAU;QACV,UAAU;QACV,WAAW,EAAE,cAAc,CAAC,MAAM;QAClC,WAAW,EAAE,gBAAgB;QAC7B,eAAe,EAAE,KAAK;QACtB,qBAAqB,EAAE,KAAK;KAC7B,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,OAAO,GAAsB,EAAE;IAC3E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IACvD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IAEvD,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACvD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC;IAEhC,gCAAgC;IAChC,IAAI,UAAU,IAAI,QAAQ,IAAI,UAAU,IAAI,QAAQ,EAAE,CAAC;QACrD,OAAO;YACL,OAAO;YACP,SAAS,EAAE,KAAK;YAChB,WAAW,EAAE,IAAI;YACjB,UAAU;YACV,UAAU;YACV,WAAW,EAAE,UAAU;YACvB,WAAW,EAAE,UAAU;YACvB,eAAe,EAAE,KAAK;YACtB,qBAAqB,EAAE,KAAK;SAC7B,CAAC;IACJ,CAAC;IAED,8BAA8B;IAC9B,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,IAAI,gBAAgB,GAAG,CAAC,CAAC;IACzB,IAAI,WAAW,GAAsB,OAAO,CAAC;IAC7C,IAAI,eAAe,GAAG,KAAK,CAAC;IAE5B,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/E,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB;QAE3G,IAAI,gBAAgB,GAAG,SAAS,GAAG,QAAQ,EAAE,CAAC;YAC5C,WAAW,GAAG,OAAO,CAAC;YACtB,+EAA+E;YAC/E,qCAAqC;YACrC,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAChC,MAAM,aAAa,GAAG,4BAA4B,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;gBACnE,cAAc,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;gBACtC,gBAAgB,GAAG,MAAM,CAAC,UAAU,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;gBAC7D,eAAe,GAAG,IAAI,CAAC;YACzB,CAAC;YACD,MAAM;QACR,CAAC;QAED,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC7B,gBAAgB,IAAI,SAAS,CAAC;IAChC,CAAC;IAED,iCAAiC;IACjC,IAAI,cAAc,CAAC,MAAM,IAAI,QAAQ,IAAI,gBAAgB,IAAI,QAAQ,EAAE,CAAC;QACtE,WAAW,GAAG,OAAO,CAAC;IACxB,CAAC;IAED,MAAM,aAAa,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,gBAAgB,GAAG,MAAM,CAAC,UAAU,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;IAEnE,OAAO;QACL,OAAO,EAAE,aAAa;QACtB,SAAS,EAAE,IAAI;QACf,WAAW;QACX,UAAU;QACV,UAAU;QACV,WAAW,EAAE,cAAc,CAAC,MAAM;QAClC,WAAW,EAAE,gBAAgB;QAC7B,eAAe;QACf,qBAAqB,EAAE,KAAK;KAC7B,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,4BAA4B,CAAC,GAAW,EAAE,QAAgB;IACjE,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACtC,IAAI,GAAG,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC3B,OAAO,GAAG,CAAC;IACb,CAAC;IAED,yCAAyC;IACzC,IAAI,KAAK,GAAG,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC;IAElC,qDAAqD;IACrD,OAAO,KAAK,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1D,KAAK,EAAE,CAAC;IACV,CAAC;IAED,OAAO,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAC5C,CAAC","sourcesContent":["/**\n * Shared truncation utilities for tool outputs.\n *\n * Truncation is based on two independent limits - whichever is hit first wins:\n * - Line limit (default: 2000 lines)\n * - Byte limit (default: 50KB)\n *\n * Never returns partial lines (except bash tail truncation edge case).\n */\n\nexport const DEFAULT_MAX_LINES = 2000;\nexport const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB\n\nexport interface TruncationResult {\n /** The truncated content */\n content: string;\n /** Whether truncation occurred */\n truncated: boolean;\n /** Which limit was hit: \"lines\", \"bytes\", or null if not truncated */\n truncatedBy: \"lines\" | \"bytes\" | null;\n /** Total number of lines in the original content */\n totalLines: number;\n /** Total number of bytes in the original content */\n totalBytes: number;\n /** Number of complete lines in the truncated output */\n outputLines: number;\n /** Number of bytes in the truncated output */\n outputBytes: number;\n /** Whether the last line was partially truncated (only for tail truncation edge case) */\n lastLinePartial: boolean;\n /** Whether the first line exceeded the byte limit (for head truncation) */\n firstLineExceedsLimit: boolean;\n}\n\nexport interface TruncationOptions {\n /** Maximum number of lines (default: 2000) */\n maxLines?: number;\n /** Maximum number of bytes (default: 50KB) */\n maxBytes?: number;\n}\n\n/**\n * Format bytes as human-readable size.\n */\nexport function formatSize(bytes: number): string {\n if (bytes < 1024) {\n return `${bytes}B`;\n } else if (bytes < 1024 * 1024) {\n return `${(bytes / 1024).toFixed(1)}KB`;\n } else {\n return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\n }\n}\n\n/**\n * Truncate content from the head (keep first N lines/bytes).\n * Suitable for file reads where you want to see the beginning.\n *\n * Never returns partial lines. If first line exceeds byte limit,\n * returns empty content with firstLineExceedsLimit=true.\n */\nexport function truncateHead(content: string, options: TruncationOptions = {}): TruncationResult {\n const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n const totalBytes = Buffer.byteLength(content, \"utf-8\");\n const lines = content.split(\"\\n\");\n const totalLines = lines.length;\n\n // Check if no truncation needed\n if (totalLines <= maxLines && totalBytes <= maxBytes) {\n return {\n content,\n truncated: false,\n truncatedBy: null,\n totalLines,\n totalBytes,\n outputLines: totalLines,\n outputBytes: totalBytes,\n lastLinePartial: false,\n firstLineExceedsLimit: false,\n };\n }\n\n // Check if first line alone exceeds byte limit\n const firstLineBytes = Buffer.byteLength(lines[0], \"utf-8\");\n if (firstLineBytes > maxBytes) {\n return {\n content: \"\",\n truncated: true,\n truncatedBy: \"bytes\",\n totalLines,\n totalBytes,\n outputLines: 0,\n outputBytes: 0,\n lastLinePartial: false,\n firstLineExceedsLimit: true,\n };\n }\n\n // Collect complete lines that fit\n const outputLinesArr: string[] = [];\n let outputBytesCount = 0;\n let truncatedBy: \"lines\" | \"bytes\" = \"lines\";\n\n for (let i = 0; i < lines.length && i < maxLines; i++) {\n const line = lines[i];\n const lineBytes = Buffer.byteLength(line, \"utf-8\") + (i > 0 ? 1 : 0); // +1 for newline\n\n if (outputBytesCount + lineBytes > maxBytes) {\n truncatedBy = \"bytes\";\n break;\n }\n\n outputLinesArr.push(line);\n outputBytesCount += lineBytes;\n }\n\n // If we exited due to line limit\n if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {\n truncatedBy = \"lines\";\n }\n\n const outputContent = outputLinesArr.join(\"\\n\");\n const finalOutputBytes = Buffer.byteLength(outputContent, \"utf-8\");\n\n return {\n content: outputContent,\n truncated: true,\n truncatedBy,\n totalLines,\n totalBytes,\n outputLines: outputLinesArr.length,\n outputBytes: finalOutputBytes,\n lastLinePartial: false,\n firstLineExceedsLimit: false,\n };\n}\n\n/**\n * Truncate content from the tail (keep last N lines/bytes).\n * Suitable for bash output where you want to see the end (errors, final results).\n *\n * May return partial first line if the last line of original content exceeds byte limit.\n */\nexport function truncateTail(content: string, options: TruncationOptions = {}): TruncationResult {\n const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;\n const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;\n\n const totalBytes = Buffer.byteLength(content, \"utf-8\");\n const lines = content.split(\"\\n\");\n const totalLines = lines.length;\n\n // Check if no truncation needed\n if (totalLines <= maxLines && totalBytes <= maxBytes) {\n return {\n content,\n truncated: false,\n truncatedBy: null,\n totalLines,\n totalBytes,\n outputLines: totalLines,\n outputBytes: totalBytes,\n lastLinePartial: false,\n firstLineExceedsLimit: false,\n };\n }\n\n // Work backwards from the end\n const outputLinesArr: string[] = [];\n let outputBytesCount = 0;\n let truncatedBy: \"lines\" | \"bytes\" = \"lines\";\n let lastLinePartial = false;\n\n for (let i = lines.length - 1; i >= 0 && outputLinesArr.length < maxLines; i--) {\n const line = lines[i];\n const lineBytes = Buffer.byteLength(line, \"utf-8\") + (outputLinesArr.length > 0 ? 1 : 0); // +1 for newline\n\n if (outputBytesCount + lineBytes > maxBytes) {\n truncatedBy = \"bytes\";\n // Edge case: if we haven't added ANY lines yet and this line exceeds maxBytes,\n // take the end of the line (partial)\n if (outputLinesArr.length === 0) {\n const truncatedLine = truncateStringToBytesFromEnd(line, maxBytes);\n outputLinesArr.unshift(truncatedLine);\n outputBytesCount = Buffer.byteLength(truncatedLine, \"utf-8\");\n lastLinePartial = true;\n }\n break;\n }\n\n outputLinesArr.unshift(line);\n outputBytesCount += lineBytes;\n }\n\n // If we exited due to line limit\n if (outputLinesArr.length >= maxLines && outputBytesCount <= maxBytes) {\n truncatedBy = \"lines\";\n }\n\n const outputContent = outputLinesArr.join(\"\\n\");\n const finalOutputBytes = Buffer.byteLength(outputContent, \"utf-8\");\n\n return {\n content: outputContent,\n truncated: true,\n truncatedBy,\n totalLines,\n totalBytes,\n outputLines: outputLinesArr.length,\n outputBytes: finalOutputBytes,\n lastLinePartial,\n firstLineExceedsLimit: false,\n };\n}\n\n/**\n * Truncate a string to fit within a byte limit (from the end).\n * Handles multi-byte UTF-8 characters correctly.\n */\nfunction truncateStringToBytesFromEnd(str: string, maxBytes: number): string {\n const buf = Buffer.from(str, \"utf-8\");\n if (buf.length <= maxBytes) {\n return str;\n }\n\n // Start from the end, skip maxBytes back\n let start = buf.length - maxBytes;\n\n // Find a valid UTF-8 boundary (start of a character)\n while (start < buf.length && (buf[start] & 0xc0) === 0x80) {\n start++;\n }\n\n return buf.slice(start).toString(\"utf-8\");\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AgentTool } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { Executor } from "../sandbox/index.js";
|
|
3
|
+
declare const writeSchema: import("@sinclair/typebox").TObject<{
|
|
4
|
+
label: import("@sinclair/typebox").TString;
|
|
5
|
+
path: import("@sinclair/typebox").TString;
|
|
6
|
+
content: import("@sinclair/typebox").TString;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function createWriteTool(executor: Executor): AgentTool<typeof writeSchema>;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/tools/write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,QAAA,MAAM,WAAW;;;;EAIf,CAAC;AAEH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,WAAW,CAAC,CA8BjF","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox/index.js\";\n\nconst writeSchema = Type.Object({\n label: Type.String({ description: \"Brief description of what you're writing (shown to user)\" }),\n path: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n content: Type.String({ description: \"Content to write to the file\" }),\n});\n\nexport function createWriteTool(executor: Executor): AgentTool<typeof writeSchema> {\n return {\n name: \"write\",\n label: \"write\",\n description:\n \"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.\",\n parameters: writeSchema,\n execute: async (\n _toolCallId: string,\n { path, content }: { label: string; path: string; content: string },\n signal?: AbortSignal,\n ) => {\n // Create parent directories and write file using heredoc\n const dir = path.includes(\"/\") ? path.substring(0, path.lastIndexOf(\"/\")) : \".\";\n\n // Use printf to handle content with special characters, pipe to file\n // This avoids issues with heredoc and special characters\n const cmd = `mkdir -p ${shellEscape(dir)} && printf '%s' ${shellEscape(content)} > ${shellEscape(path)}`;\n\n const result = await executor.exec(cmd, { signal });\n if (result.code !== 0) {\n throw new Error(result.stderr || `Failed to write file: ${path}`);\n }\n\n return {\n content: [{ type: \"text\", text: `Successfully wrote ${content.length} bytes to ${path}` }],\n details: undefined,\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
const writeSchema = Type.Object({
|
|
3
|
+
label: Type.String({ description: "Brief description of what you're writing (shown to user)" }),
|
|
4
|
+
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
|
5
|
+
content: Type.String({ description: "Content to write to the file" }),
|
|
6
|
+
});
|
|
7
|
+
export function createWriteTool(executor) {
|
|
8
|
+
return {
|
|
9
|
+
name: "write",
|
|
10
|
+
label: "write",
|
|
11
|
+
description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.",
|
|
12
|
+
parameters: writeSchema,
|
|
13
|
+
execute: async (_toolCallId, { path, content }, signal) => {
|
|
14
|
+
// Create parent directories and write file using heredoc
|
|
15
|
+
const dir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : ".";
|
|
16
|
+
// Use printf to handle content with special characters, pipe to file
|
|
17
|
+
// This avoids issues with heredoc and special characters
|
|
18
|
+
const cmd = `mkdir -p ${shellEscape(dir)} && printf '%s' ${shellEscape(content)} > ${shellEscape(path)}`;
|
|
19
|
+
const result = await executor.exec(cmd, { signal });
|
|
20
|
+
if (result.code !== 0) {
|
|
21
|
+
throw new Error(result.stderr || `Failed to write file: ${path}`);
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${path}` }],
|
|
25
|
+
details: undefined,
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function shellEscape(s) {
|
|
31
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write.js","sourceRoot":"","sources":["../../src/tools/write.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAGzC,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC;IAC9B,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,0DAA0D,EAAE,CAAC;IAC/F,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,kDAAkD,EAAE,CAAC;IACtF,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,8BAA8B,EAAE,CAAC;CACtE,CAAC,CAAC;AAEH,MAAM,UAAU,eAAe,CAAC,QAAkB;IAChD,OAAO;QACL,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO;QACd,WAAW,EACT,iIAAiI;QACnI,UAAU,EAAE,WAAW;QACvB,OAAO,EAAE,KAAK,EACZ,WAAmB,EACnB,EAAE,IAAI,EAAE,OAAO,EAAoD,EACnE,MAAoB,EACpB,EAAE;YACF,yDAAyD;YACzD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;YAEhF,qEAAqE;YACrE,yDAAyD;YACzD,MAAM,GAAG,GAAG,YAAY,WAAW,CAAC,GAAG,CAAC,mBAAmB,WAAW,CAAC,OAAO,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;YAEzG,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YACpD,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACtB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,IAAI,yBAAyB,IAAI,EAAE,CAAC,CAAC;YACpE,CAAC;YAED,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,OAAO,CAAC,MAAM,aAAa,IAAI,EAAE,EAAE,CAAC;gBAC1F,OAAO,EAAE,SAAS;aACnB,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAAC,CAAS;IAC5B,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AACzC,CAAC","sourcesContent":["import type { AgentTool } from \"@earendil-works/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox/index.js\";\n\nconst writeSchema = Type.Object({\n label: Type.String({ description: \"Brief description of what you're writing (shown to user)\" }),\n path: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n content: Type.String({ description: \"Content to write to the file\" }),\n});\n\nexport function createWriteTool(executor: Executor): AgentTool<typeof writeSchema> {\n return {\n name: \"write\",\n label: \"write\",\n description:\n \"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.\",\n parameters: writeSchema,\n execute: async (\n _toolCallId: string,\n { path, content }: { label: string; path: string; content: string },\n signal?: AbortSignal,\n ) => {\n // Create parent directories and write file using heredoc\n const dir = path.includes(\"/\") ? path.substring(0, path.lastIndexOf(\"/\")) : \".\";\n\n // Use printf to handle content with special characters, pipe to file\n // This avoids issues with heredoc and special characters\n const cmd = `mkdir -p ${shellEscape(dir)} && printf '%s' ${shellEscape(content)} > ${shellEscape(path)}`;\n\n const result = await executor.exec(cmd, { signal });\n if (result.code !== 0) {\n throw new Error(result.stderr || `Failed to write file: ${path}`);\n }\n\n return {\n content: [{ type: \"text\", text: `Successfully wrote ${content.length} bytes to ${path}` }],\n details: undefined,\n };\n },\n };\n}\n\nfunction shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { BotEvent } from "./adapter.js";
|
|
2
|
+
export type TriggerIntent = "mention" | "direct" | "thread-continuation" | "auto-reply-candidate";
|
|
3
|
+
export type TriggerResult = {
|
|
4
|
+
trigger: true;
|
|
5
|
+
reason: string;
|
|
6
|
+
} | {
|
|
7
|
+
trigger: false;
|
|
8
|
+
reason: string;
|
|
9
|
+
};
|
|
10
|
+
export type AutoReplyJudge = (input: {
|
|
11
|
+
event: BotEvent;
|
|
12
|
+
rules: string[];
|
|
13
|
+
conversationDir: string;
|
|
14
|
+
}) => Promise<boolean>;
|
|
15
|
+
/**
|
|
16
|
+
* Trivially decide non-auto-reply intents synchronously. For "auto-reply-candidate"
|
|
17
|
+
* callers must use {@link evaluateAutoReplyPolicy}.
|
|
18
|
+
*/
|
|
19
|
+
export declare function decideTrigger(intent: Exclude<TriggerIntent, "auto-reply-candidate">): TriggerResult;
|
|
20
|
+
/**
|
|
21
|
+
* Decide whether to auto-reply. Never throws — judge errors and timeouts are
|
|
22
|
+
* folded into a `trigger: false` result with a distinct reason, so adapters
|
|
23
|
+
* can apply a single uniform "do not trigger, but still log" policy.
|
|
24
|
+
*/
|
|
25
|
+
export declare function evaluateAutoReplyPolicy(input: {
|
|
26
|
+
event: BotEvent;
|
|
27
|
+
workingDir: string | undefined;
|
|
28
|
+
judge?: AutoReplyJudge;
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
}): Promise<TriggerResult>;
|
|
31
|
+
//# sourceMappingURL=trigger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trigger.d.ts","sourceRoot":"","sources":["../src/trigger.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAO7C,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,QAAQ,GAAG,qBAAqB,GAAG,sBAAsB,CAAC;AAElG,MAAM,MAAM,aAAa,GAAG;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnG,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE;IACnC,KAAK,EAAE,QAAQ,CAAC;IAChB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;CACzB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEvB;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,OAAO,CAAC,aAAa,EAAE,sBAAsB,CAAC,GACrD,aAAa,CAEf;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE;IACnD,KAAK,EAAE,QAAQ,CAAC;IAChB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,aAAa,CAAC,CA4BzB","sourcesContent":["import { completeSimple, getModel, type Api, type Model } from \"@earendil-works/pi-ai\";\nimport type { BotEvent } from \"./adapter.js\";\nimport { loadAutoReplyJudgeModel, loadConversationAutoReplyConfig } from \"./config.js\";\nimport * as log from \"./log.js\";\nimport { join } from \"path\";\n\nconst JUDGE_TIMEOUT_MS = 10_000;\n\nexport type TriggerIntent = \"mention\" | \"direct\" | \"thread-continuation\" | \"auto-reply-candidate\";\n\nexport type TriggerResult = { trigger: true; reason: string } | { trigger: false; reason: string };\n\nexport type AutoReplyJudge = (input: {\n event: BotEvent;\n rules: string[];\n conversationDir: string;\n}) => Promise<boolean>;\n\n/**\n * Trivially decide non-auto-reply intents synchronously. For \"auto-reply-candidate\"\n * callers must use {@link evaluateAutoReplyPolicy}.\n */\nexport function decideTrigger(\n intent: Exclude<TriggerIntent, \"auto-reply-candidate\">,\n): TriggerResult {\n return { trigger: true, reason: intent };\n}\n\n/**\n * Decide whether to auto-reply. Never throws — judge errors and timeouts are\n * folded into a `trigger: false` result with a distinct reason, so adapters\n * can apply a single uniform \"do not trigger, but still log\" policy.\n */\nexport async function evaluateAutoReplyPolicy(input: {\n event: BotEvent;\n workingDir: string | undefined;\n judge?: AutoReplyJudge;\n timeoutMs?: number;\n}): Promise<TriggerResult> {\n const { event, workingDir, judge = judgeAutoReplyWithLlm, timeoutMs = JUDGE_TIMEOUT_MS } = input;\n if (!workingDir) return { trigger: false, reason: \"auto-reply-unconfigured\" };\n\n const conversationDir = join(workingDir, event.conversationId);\n\n try {\n const config = loadConversationAutoReplyConfig(conversationDir);\n if (!config.enabled) return { trigger: false, reason: \"auto-reply-disabled\" };\n if (config.rules.length === 0) {\n return { trigger: true, reason: \"auto-reply-enabled\" };\n }\n\n const shouldReply = await withTimeout(\n judge({ event, rules: config.rules, conversationDir }),\n timeoutMs,\n );\n return shouldReply\n ? { trigger: true, reason: \"auto-reply-rule-match\" }\n : { trigger: false, reason: \"auto-reply-rule-no-match\" };\n } catch (err) {\n if (err instanceof JudgeTimeoutError) {\n log.logWarning(\"Auto-reply judge timed out\", String(err));\n return { trigger: false, reason: \"auto-reply-judge-timeout\" };\n }\n log.logWarning(\"Auto-reply policy evaluation failed\", String(err));\n return { trigger: false, reason: \"auto-reply-judge-failed\" };\n }\n}\n\nclass JudgeTimeoutError extends Error {\n constructor(ms: number) {\n super(`auto-reply judge exceeded ${ms}ms`);\n this.name = \"JudgeTimeoutError\";\n }\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new JudgeTimeoutError(ms)), ms);\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (err) => {\n clearTimeout(timer);\n reject(err);\n },\n );\n });\n}\n\nasync function judgeAutoReplyWithLlm(input: {\n event: BotEvent;\n rules: string[];\n conversationDir: string;\n}): Promise<boolean> {\n const judgeConfig = loadAutoReplyJudgeModel(input.conversationDir);\n // Config stores provider/model as user-provided strings, while getModel's public\n // overload is narrowed to generated known providers.\n const model = getModel(judgeConfig.provider as never, judgeConfig.model as never) as Model<Api>;\n const answer = await completeSimple(\n model,\n {\n systemPrompt:\n \"You decide whether a bot should reply to a group/channel message. \" +\n \"Use only the rules provided by the user. Answer exactly YES or NO.\",\n messages: [\n {\n role: \"user\",\n timestamp: Date.now(),\n content: [\n \"Rules:\",\n ...input.rules.map((rule, index) => `${index + 1}. ${rule}`),\n \"\",\n \"Message:\",\n input.event.text,\n \"\",\n \"Should the bot reply? Answer exactly YES or NO.\",\n ].join(\"\\n\"),\n },\n ],\n },\n {\n temperature: 0,\n maxTokens: 4,\n reasoning: \"minimal\",\n },\n );\n const text = answer.content\n .filter((part) => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\")\n .trim()\n .toUpperCase();\n return /^YES\\b/.test(text);\n}\n"]}
|
package/dist/trigger.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { completeSimple, getModel } from "@earendil-works/pi-ai";
|
|
2
|
+
import { loadAutoReplyJudgeModel, loadConversationAutoReplyConfig } from "./config.js";
|
|
3
|
+
import * as log from "./log.js";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
const JUDGE_TIMEOUT_MS = 10_000;
|
|
6
|
+
/**
|
|
7
|
+
* Trivially decide non-auto-reply intents synchronously. For "auto-reply-candidate"
|
|
8
|
+
* callers must use {@link evaluateAutoReplyPolicy}.
|
|
9
|
+
*/
|
|
10
|
+
export function decideTrigger(intent) {
|
|
11
|
+
return { trigger: true, reason: intent };
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Decide whether to auto-reply. Never throws — judge errors and timeouts are
|
|
15
|
+
* folded into a `trigger: false` result with a distinct reason, so adapters
|
|
16
|
+
* can apply a single uniform "do not trigger, but still log" policy.
|
|
17
|
+
*/
|
|
18
|
+
export async function evaluateAutoReplyPolicy(input) {
|
|
19
|
+
const { event, workingDir, judge = judgeAutoReplyWithLlm, timeoutMs = JUDGE_TIMEOUT_MS } = input;
|
|
20
|
+
if (!workingDir)
|
|
21
|
+
return { trigger: false, reason: "auto-reply-unconfigured" };
|
|
22
|
+
const conversationDir = join(workingDir, event.conversationId);
|
|
23
|
+
try {
|
|
24
|
+
const config = loadConversationAutoReplyConfig(conversationDir);
|
|
25
|
+
if (!config.enabled)
|
|
26
|
+
return { trigger: false, reason: "auto-reply-disabled" };
|
|
27
|
+
if (config.rules.length === 0) {
|
|
28
|
+
return { trigger: true, reason: "auto-reply-enabled" };
|
|
29
|
+
}
|
|
30
|
+
const shouldReply = await withTimeout(judge({ event, rules: config.rules, conversationDir }), timeoutMs);
|
|
31
|
+
return shouldReply
|
|
32
|
+
? { trigger: true, reason: "auto-reply-rule-match" }
|
|
33
|
+
: { trigger: false, reason: "auto-reply-rule-no-match" };
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
if (err instanceof JudgeTimeoutError) {
|
|
37
|
+
log.logWarning("Auto-reply judge timed out", String(err));
|
|
38
|
+
return { trigger: false, reason: "auto-reply-judge-timeout" };
|
|
39
|
+
}
|
|
40
|
+
log.logWarning("Auto-reply policy evaluation failed", String(err));
|
|
41
|
+
return { trigger: false, reason: "auto-reply-judge-failed" };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
class JudgeTimeoutError extends Error {
|
|
45
|
+
constructor(ms) {
|
|
46
|
+
super(`auto-reply judge exceeded ${ms}ms`);
|
|
47
|
+
this.name = "JudgeTimeoutError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function withTimeout(promise, ms) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const timer = setTimeout(() => reject(new JudgeTimeoutError(ms)), ms);
|
|
53
|
+
promise.then((value) => {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
resolve(value);
|
|
56
|
+
}, (err) => {
|
|
57
|
+
clearTimeout(timer);
|
|
58
|
+
reject(err);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function judgeAutoReplyWithLlm(input) {
|
|
63
|
+
const judgeConfig = loadAutoReplyJudgeModel(input.conversationDir);
|
|
64
|
+
// Config stores provider/model as user-provided strings, while getModel's public
|
|
65
|
+
// overload is narrowed to generated known providers.
|
|
66
|
+
const model = getModel(judgeConfig.provider, judgeConfig.model);
|
|
67
|
+
const answer = await completeSimple(model, {
|
|
68
|
+
systemPrompt: "You decide whether a bot should reply to a group/channel message. " +
|
|
69
|
+
"Use only the rules provided by the user. Answer exactly YES or NO.",
|
|
70
|
+
messages: [
|
|
71
|
+
{
|
|
72
|
+
role: "user",
|
|
73
|
+
timestamp: Date.now(),
|
|
74
|
+
content: [
|
|
75
|
+
"Rules:",
|
|
76
|
+
...input.rules.map((rule, index) => `${index + 1}. ${rule}`),
|
|
77
|
+
"",
|
|
78
|
+
"Message:",
|
|
79
|
+
input.event.text,
|
|
80
|
+
"",
|
|
81
|
+
"Should the bot reply? Answer exactly YES or NO.",
|
|
82
|
+
].join("\n"),
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
}, {
|
|
86
|
+
temperature: 0,
|
|
87
|
+
maxTokens: 4,
|
|
88
|
+
reasoning: "minimal",
|
|
89
|
+
});
|
|
90
|
+
const text = answer.content
|
|
91
|
+
.filter((part) => part.type === "text")
|
|
92
|
+
.map((part) => part.text)
|
|
93
|
+
.join("")
|
|
94
|
+
.trim()
|
|
95
|
+
.toUpperCase();
|
|
96
|
+
return /^YES\b/.test(text);
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=trigger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trigger.js","sourceRoot":"","sources":["../src/trigger.ts"],"names":[],"mappings":"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","sourcesContent":["import { completeSimple, getModel, type Api, type Model } from \"@earendil-works/pi-ai\";\nimport type { BotEvent } from \"./adapter.js\";\nimport { loadAutoReplyJudgeModel, loadConversationAutoReplyConfig } from \"./config.js\";\nimport * as log from \"./log.js\";\nimport { join } from \"path\";\n\nconst JUDGE_TIMEOUT_MS = 10_000;\n\nexport type TriggerIntent = \"mention\" | \"direct\" | \"thread-continuation\" | \"auto-reply-candidate\";\n\nexport type TriggerResult = { trigger: true; reason: string } | { trigger: false; reason: string };\n\nexport type AutoReplyJudge = (input: {\n event: BotEvent;\n rules: string[];\n conversationDir: string;\n}) => Promise<boolean>;\n\n/**\n * Trivially decide non-auto-reply intents synchronously. For \"auto-reply-candidate\"\n * callers must use {@link evaluateAutoReplyPolicy}.\n */\nexport function decideTrigger(\n intent: Exclude<TriggerIntent, \"auto-reply-candidate\">,\n): TriggerResult {\n return { trigger: true, reason: intent };\n}\n\n/**\n * Decide whether to auto-reply. Never throws — judge errors and timeouts are\n * folded into a `trigger: false` result with a distinct reason, so adapters\n * can apply a single uniform \"do not trigger, but still log\" policy.\n */\nexport async function evaluateAutoReplyPolicy(input: {\n event: BotEvent;\n workingDir: string | undefined;\n judge?: AutoReplyJudge;\n timeoutMs?: number;\n}): Promise<TriggerResult> {\n const { event, workingDir, judge = judgeAutoReplyWithLlm, timeoutMs = JUDGE_TIMEOUT_MS } = input;\n if (!workingDir) return { trigger: false, reason: \"auto-reply-unconfigured\" };\n\n const conversationDir = join(workingDir, event.conversationId);\n\n try {\n const config = loadConversationAutoReplyConfig(conversationDir);\n if (!config.enabled) return { trigger: false, reason: \"auto-reply-disabled\" };\n if (config.rules.length === 0) {\n return { trigger: true, reason: \"auto-reply-enabled\" };\n }\n\n const shouldReply = await withTimeout(\n judge({ event, rules: config.rules, conversationDir }),\n timeoutMs,\n );\n return shouldReply\n ? { trigger: true, reason: \"auto-reply-rule-match\" }\n : { trigger: false, reason: \"auto-reply-rule-no-match\" };\n } catch (err) {\n if (err instanceof JudgeTimeoutError) {\n log.logWarning(\"Auto-reply judge timed out\", String(err));\n return { trigger: false, reason: \"auto-reply-judge-timeout\" };\n }\n log.logWarning(\"Auto-reply policy evaluation failed\", String(err));\n return { trigger: false, reason: \"auto-reply-judge-failed\" };\n }\n}\n\nclass JudgeTimeoutError extends Error {\n constructor(ms: number) {\n super(`auto-reply judge exceeded ${ms}ms`);\n this.name = \"JudgeTimeoutError\";\n }\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(() => reject(new JudgeTimeoutError(ms)), ms);\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (err) => {\n clearTimeout(timer);\n reject(err);\n },\n );\n });\n}\n\nasync function judgeAutoReplyWithLlm(input: {\n event: BotEvent;\n rules: string[];\n conversationDir: string;\n}): Promise<boolean> {\n const judgeConfig = loadAutoReplyJudgeModel(input.conversationDir);\n // Config stores provider/model as user-provided strings, while getModel's public\n // overload is narrowed to generated known providers.\n const model = getModel(judgeConfig.provider as never, judgeConfig.model as never) as Model<Api>;\n const answer = await completeSimple(\n model,\n {\n systemPrompt:\n \"You decide whether a bot should reply to a group/channel message. \" +\n \"Use only the rules provided by the user. Answer exactly YES or NO.\",\n messages: [\n {\n role: \"user\",\n timestamp: Date.now(),\n content: [\n \"Rules:\",\n ...input.rules.map((rule, index) => `${index + 1}. ${rule}`),\n \"\",\n \"Message:\",\n input.event.text,\n \"\",\n \"Should the bot reply? Answer exactly YES or NO.\",\n ].join(\"\\n\"),\n },\n ],\n },\n {\n temperature: 0,\n maxTokens: 4,\n reasoning: \"minimal\",\n },\n );\n const text = answer.content\n .filter((part) => part.type === \"text\")\n .map((part) => part.text)\n .join(\"\")\n .trim()\n .toUpperCase();\n return /^YES\\b/.test(text);\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Bot, PlatformInfo } from "./adapter.js";
|
|
2
|
+
export declare const PRODUCT_NAME = "mikan";
|
|
3
|
+
type PlatformSource = Bot | PlatformInfo | string;
|
|
4
|
+
export declare function formatNothingRunning(source: PlatformSource): string;
|
|
5
|
+
export declare function formatStopping(source: PlatformSource): string;
|
|
6
|
+
export declare function formatStopped(source: PlatformSource): string;
|
|
7
|
+
export declare function formatAlreadyWorking(source: PlatformSource, stopCommand: string, options?: {
|
|
8
|
+
scope?: "thread";
|
|
9
|
+
}): string;
|
|
10
|
+
export declare function formatForceStopped(source: PlatformSource, actorLabel: string): string;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=ui-copy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-copy.d.ts","sourceRoot":"","sources":["../src/ui-copy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEtD,eAAO,MAAM,YAAY,UAAU,CAAC;AAEpC,KAAK,cAAc,GAAG,GAAG,GAAG,YAAY,GAAG,MAAM,CAAC;AAoBlD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAEnE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAE7D;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAE5D;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,QAAQ,CAAA;CAAE,GAC7B,MAAM,CAMR;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAErF","sourcesContent":["import type { Bot, PlatformInfo } from \"./adapter.js\";\n\nexport const PRODUCT_NAME = \"mikan\";\n\ntype PlatformSource = Bot | PlatformInfo | string;\n\nfunction resolvePlatformName(source: PlatformSource): string {\n if (typeof source === \"string\") return source;\n if (\"getPlatformInfo\" in source) return source.getPlatformInfo().name;\n return source.name;\n}\n\nfunction supportsHtmlFormatting(platformName: string): boolean {\n return platformName === \"telegram\";\n}\n\nfunction formatItalic(platformName: string, text: string): string {\n return supportsHtmlFormatting(platformName) ? text : `_${text}_`;\n}\n\nfunction formatCode(platformName: string, text: string): string {\n return supportsHtmlFormatting(platformName) ? `<code>${text}</code>` : `\\`${text}\\``;\n}\n\nexport function formatNothingRunning(source: PlatformSource): string {\n return formatItalic(resolvePlatformName(source), \"Nothing running.\");\n}\n\nexport function formatStopping(source: PlatformSource): string {\n return formatItalic(resolvePlatformName(source), \"Stopping…\");\n}\n\nexport function formatStopped(source: PlatformSource): string {\n return formatItalic(resolvePlatformName(source), \"Stopped.\");\n}\n\nexport function formatAlreadyWorking(\n source: PlatformSource,\n stopCommand: string,\n options?: { scope?: \"thread\" },\n): string {\n const platformName = resolvePlatformName(source);\n const command = formatCode(platformName, stopCommand);\n const prefix =\n options?.scope === \"thread\" ? \"Already working in this thread.\" : \"Already working.\";\n return formatItalic(platformName, `${prefix} Send ${command} to cancel.`);\n}\n\nexport function formatForceStopped(source: PlatformSource, actorLabel: string): string {\n return formatItalic(resolvePlatformName(source), `Force stopped by ${actorLabel}.`);\n}\n"]}
|
package/dist/ui-copy.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const PRODUCT_NAME = "mikan";
|
|
2
|
+
function resolvePlatformName(source) {
|
|
3
|
+
if (typeof source === "string")
|
|
4
|
+
return source;
|
|
5
|
+
if ("getPlatformInfo" in source)
|
|
6
|
+
return source.getPlatformInfo().name;
|
|
7
|
+
return source.name;
|
|
8
|
+
}
|
|
9
|
+
function supportsHtmlFormatting(platformName) {
|
|
10
|
+
return platformName === "telegram";
|
|
11
|
+
}
|
|
12
|
+
function formatItalic(platformName, text) {
|
|
13
|
+
return supportsHtmlFormatting(platformName) ? text : `_${text}_`;
|
|
14
|
+
}
|
|
15
|
+
function formatCode(platformName, text) {
|
|
16
|
+
return supportsHtmlFormatting(platformName) ? `<code>${text}</code>` : `\`${text}\``;
|
|
17
|
+
}
|
|
18
|
+
export function formatNothingRunning(source) {
|
|
19
|
+
return formatItalic(resolvePlatformName(source), "Nothing running.");
|
|
20
|
+
}
|
|
21
|
+
export function formatStopping(source) {
|
|
22
|
+
return formatItalic(resolvePlatformName(source), "Stopping…");
|
|
23
|
+
}
|
|
24
|
+
export function formatStopped(source) {
|
|
25
|
+
return formatItalic(resolvePlatformName(source), "Stopped.");
|
|
26
|
+
}
|
|
27
|
+
export function formatAlreadyWorking(source, stopCommand, options) {
|
|
28
|
+
const platformName = resolvePlatformName(source);
|
|
29
|
+
const command = formatCode(platformName, stopCommand);
|
|
30
|
+
const prefix = options?.scope === "thread" ? "Already working in this thread." : "Already working.";
|
|
31
|
+
return formatItalic(platformName, `${prefix} Send ${command} to cancel.`);
|
|
32
|
+
}
|
|
33
|
+
export function formatForceStopped(source, actorLabel) {
|
|
34
|
+
return formatItalic(resolvePlatformName(source), `Force stopped by ${actorLabel}.`);
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=ui-copy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-copy.js","sourceRoot":"","sources":["../src/ui-copy.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC;AAIpC,SAAS,mBAAmB,CAAC,MAAsB;IACjD,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC9C,IAAI,iBAAiB,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC,eAAe,EAAE,CAAC,IAAI,CAAC;IACtE,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAED,SAAS,sBAAsB,CAAC,YAAoB;IAClD,OAAO,YAAY,KAAK,UAAU,CAAC;AACrC,CAAC;AAED,SAAS,YAAY,CAAC,YAAoB,EAAE,IAAY;IACtD,OAAO,sBAAsB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC;AACnE,CAAC;AAED,SAAS,UAAU,CAAC,YAAoB,EAAE,IAAY;IACpD,OAAO,sBAAsB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC;AACvF,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAsB;IACzD,OAAO,YAAY,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,kBAAkB,CAAC,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAsB;IACnD,OAAO,YAAY,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAsB;IAClD,OAAO,YAAY,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,MAAsB,EACtB,WAAmB,EACnB,OAA8B;IAE9B,MAAM,YAAY,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,UAAU,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;IACtD,MAAM,MAAM,GACV,OAAO,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,kBAAkB,CAAC;IACvF,OAAO,YAAY,CAAC,YAAY,EAAE,GAAG,MAAM,SAAS,OAAO,aAAa,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAsB,EAAE,UAAkB;IAC3E,OAAO,YAAY,CAAC,mBAAmB,CAAC,MAAM,CAAC,EAAE,oBAAoB,UAAU,GAAG,CAAC,CAAC;AACtF,CAAC","sourcesContent":["import type { Bot, PlatformInfo } from \"./adapter.js\";\n\nexport const PRODUCT_NAME = \"mikan\";\n\ntype PlatformSource = Bot | PlatformInfo | string;\n\nfunction resolvePlatformName(source: PlatformSource): string {\n if (typeof source === \"string\") return source;\n if (\"getPlatformInfo\" in source) return source.getPlatformInfo().name;\n return source.name;\n}\n\nfunction supportsHtmlFormatting(platformName: string): boolean {\n return platformName === \"telegram\";\n}\n\nfunction formatItalic(platformName: string, text: string): string {\n return supportsHtmlFormatting(platformName) ? text : `_${text}_`;\n}\n\nfunction formatCode(platformName: string, text: string): string {\n return supportsHtmlFormatting(platformName) ? `<code>${text}</code>` : `\\`${text}\\``;\n}\n\nexport function formatNothingRunning(source: PlatformSource): string {\n return formatItalic(resolvePlatformName(source), \"Nothing running.\");\n}\n\nexport function formatStopping(source: PlatformSource): string {\n return formatItalic(resolvePlatformName(source), \"Stopping…\");\n}\n\nexport function formatStopped(source: PlatformSource): string {\n return formatItalic(resolvePlatformName(source), \"Stopped.\");\n}\n\nexport function formatAlreadyWorking(\n source: PlatformSource,\n stopCommand: string,\n options?: { scope?: \"thread\" },\n): string {\n const platformName = resolvePlatformName(source);\n const command = formatCode(platformName, stopCommand);\n const prefix =\n options?.scope === \"thread\" ? \"Already working in this thread.\" : \"Already working.\";\n return formatItalic(platformName, `${prefix} Send ${command} to cancel.`);\n}\n\nexport function formatForceStopped(source: PlatformSource, actorLabel: string): string {\n return formatItalic(resolvePlatformName(source), `Force stopped by ${actorLabel}.`);\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SandboxConfig } from "./sandbox/index.js";
|
|
2
|
+
export declare function resolveActorVaultKey(baseConfig: SandboxConfig, userId: string, conversationId: string): string;
|
|
3
|
+
export declare function containerSharedVaultId(containerName: string): string;
|
|
4
|
+
//# sourceMappingURL=vault-routing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault-routing.d.ts","sourceRoot":"","sources":["../src/vault-routing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,aAAa,EACzB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GACrB,MAAM,CAcR;AAED,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAEpE","sourcesContent":["import { DockerContainerManager } from \"./provisioner.js\";\nimport type { SandboxConfig } from \"./sandbox/index.js\";\nexport function resolveActorVaultKey(\n baseConfig: SandboxConfig,\n userId: string,\n conversationId: string,\n): string {\n if (baseConfig.type === \"container\") {\n return containerSharedVaultId(baseConfig.container);\n }\n\n if (\n baseConfig.type === \"image\" ||\n baseConfig.type === \"cloudflare\" ||\n baseConfig.type === \"firecracker\"\n ) {\n return DockerContainerManager.sanitizeSegment(conversationId);\n }\n\n return userId;\n}\n\nexport function containerSharedVaultId(containerName: string): string {\n return `container-${containerName}`;\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DockerContainerManager } from "./provisioner.js";
|
|
2
|
+
export function resolveActorVaultKey(baseConfig, userId, conversationId) {
|
|
3
|
+
if (baseConfig.type === "container") {
|
|
4
|
+
return containerSharedVaultId(baseConfig.container);
|
|
5
|
+
}
|
|
6
|
+
if (baseConfig.type === "image" ||
|
|
7
|
+
baseConfig.type === "cloudflare" ||
|
|
8
|
+
baseConfig.type === "firecracker") {
|
|
9
|
+
return DockerContainerManager.sanitizeSegment(conversationId);
|
|
10
|
+
}
|
|
11
|
+
return userId;
|
|
12
|
+
}
|
|
13
|
+
export function containerSharedVaultId(containerName) {
|
|
14
|
+
return `container-${containerName}`;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=vault-routing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault-routing.js","sourceRoot":"","sources":["../src/vault-routing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,MAAM,UAAU,oBAAoB,CAClC,UAAyB,EACzB,MAAc,EACd,cAAsB;IAEtB,IAAI,UAAU,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QACpC,OAAO,sBAAsB,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACtD,CAAC;IAED,IACE,UAAU,CAAC,IAAI,KAAK,OAAO;QAC3B,UAAU,CAAC,IAAI,KAAK,YAAY;QAChC,UAAU,CAAC,IAAI,KAAK,aAAa,EACjC,CAAC;QACD,OAAO,sBAAsB,CAAC,eAAe,CAAC,cAAc,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,aAAqB;IAC1D,OAAO,aAAa,aAAa,EAAE,CAAC;AACtC,CAAC","sourcesContent":["import { DockerContainerManager } from \"./provisioner.js\";\nimport type { SandboxConfig } from \"./sandbox/index.js\";\nexport function resolveActorVaultKey(\n baseConfig: SandboxConfig,\n userId: string,\n conversationId: string,\n): string {\n if (baseConfig.type === \"container\") {\n return containerSharedVaultId(baseConfig.container);\n }\n\n if (\n baseConfig.type === \"image\" ||\n baseConfig.type === \"cloudflare\" ||\n baseConfig.type === \"firecracker\"\n ) {\n return DockerContainerManager.sanitizeSegment(conversationId);\n }\n\n return userId;\n}\n\nexport function containerSharedVaultId(containerName: string): string {\n return `container-${containerName}`;\n}\n"]}
|
package/dist/vault.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { SandboxConfig } from "./sandbox/index.js";
|
|
2
|
+
export declare function normalizeSharedVaultName(name: string): string | undefined;
|
|
3
|
+
export declare function sharedVaultKey(name: string): string | undefined;
|
|
4
|
+
export interface ResolvedVaultMount {
|
|
5
|
+
source: string;
|
|
6
|
+
target: string;
|
|
7
|
+
}
|
|
8
|
+
/** Resolved vault ready for use at runtime */
|
|
9
|
+
export interface ResolvedVault {
|
|
10
|
+
userId: string;
|
|
11
|
+
displayName: string;
|
|
12
|
+
/** Absolute path to vault directory */
|
|
13
|
+
dir: string;
|
|
14
|
+
/** Absolute mount specs */
|
|
15
|
+
mounts: ResolvedVaultMount[];
|
|
16
|
+
/** Parsed from env file */
|
|
17
|
+
env: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
export interface VaultManager {
|
|
20
|
+
/** Return true when a vault directory exists for this exact key. */
|
|
21
|
+
hasEntry(key: string): boolean;
|
|
22
|
+
/** Resolve vault for a user; returns undefined when no directory exists. */
|
|
23
|
+
resolve(userId: string): ResolvedVault | undefined;
|
|
24
|
+
/** Get sandbox config with credential injection for a user */
|
|
25
|
+
getSandboxConfig(userId: string, baseConfig: SandboxConfig): SandboxConfig;
|
|
26
|
+
/** List all vaults discovered under vaults/. */
|
|
27
|
+
list(): ResolvedVault[];
|
|
28
|
+
/** Check if the vaults directory exists. */
|
|
29
|
+
isEnabled(): boolean;
|
|
30
|
+
/** Merge environment variables into vaults/<key>/env and persist them to disk. */
|
|
31
|
+
upsertEnv(key: string, env: Record<string, string>): void;
|
|
32
|
+
/** Write a private file into vaults/<key>/ and ensure it is mounted into the sandbox. */
|
|
33
|
+
upsertFile(key: string, relativePath: string, content: string, targetPath?: string): void;
|
|
34
|
+
/** List named shared login profiles under vaults/shared/. */
|
|
35
|
+
listSharedVaults(): string[];
|
|
36
|
+
/** Delete a shared login profile's directory. Returns true when it existed. */
|
|
37
|
+
deleteSharedVault(name: string): boolean;
|
|
38
|
+
/** Copy a shared login profile's files into another vault directory. */
|
|
39
|
+
copySharedVaultTo(name: string, targetKey: string): {
|
|
40
|
+
filesCopied: number;
|
|
41
|
+
envKeysCopied: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Parse a KEY=VALUE env file. Supports:
|
|
46
|
+
* - Lines starting with # are comments
|
|
47
|
+
* - Empty lines are skipped
|
|
48
|
+
* - Values can be quoted with single or double quotes (quotes are stripped)
|
|
49
|
+
* - No variable expansion
|
|
50
|
+
* - The value is everything after the first `=` to end of line (no inline comments)
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseEnvFile(content: string): Record<string, string>;
|
|
53
|
+
export declare class FileVaultManager implements VaultManager {
|
|
54
|
+
private readonly vaultsDir;
|
|
55
|
+
constructor(stateDir: string);
|
|
56
|
+
isEnabled(): boolean;
|
|
57
|
+
hasEntry(key: string): boolean;
|
|
58
|
+
listSharedVaults(): string[];
|
|
59
|
+
deleteSharedVault(name: string): boolean;
|
|
60
|
+
copySharedVaultTo(name: string, targetKey: string): {
|
|
61
|
+
filesCopied: number;
|
|
62
|
+
envKeysCopied: number;
|
|
63
|
+
};
|
|
64
|
+
resolve(userId: string): ResolvedVault | undefined;
|
|
65
|
+
getSandboxConfig(userId: string, baseConfig: SandboxConfig): SandboxConfig;
|
|
66
|
+
list(): ResolvedVault[];
|
|
67
|
+
upsertEnv(key: string, env: Record<string, string>): void;
|
|
68
|
+
upsertFile(key: string, relativePath: string, content: string, targetPath?: string): void;
|
|
69
|
+
private buildResolved;
|
|
70
|
+
}
|
|
71
|
+
export declare function defaultVaultTargetPath(relativePath: string): string;
|
|
72
|
+
//# sourceMappingURL=vault.d.ts.map
|