@hybridaione/hybridclaw 0.2.3 → 0.2.7
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/.github/workflows/ci.yml +70 -0
- package/.husky/pre-commit +1 -0
- package/CHANGELOG.md +95 -0
- package/CONTRIBUTING.md +33 -0
- package/README.md +57 -19
- package/SECURITY.md +17 -0
- package/biome.json +35 -0
- package/config.example.json +72 -7
- package/container/package-lock.json +2 -2
- package/container/package.json +1 -1
- package/container/src/approval-policy.ts +1405 -0
- package/container/src/browser-tools.ts +672 -136
- package/container/src/extensions.ts +36 -12
- package/container/src/hybridai-client.ts +65 -16
- package/container/src/index.ts +463 -110
- package/container/src/ipc.ts +5 -3
- package/container/src/token-usage.ts +107 -18
- package/container/src/tools.ts +600 -225
- package/container/src/types.ts +49 -2
- package/container/src/web-fetch.ts +89 -32
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +15 -4
- package/dist/agent.js.map +1 -1
- package/dist/audit-cli.d.ts.map +1 -1
- package/dist/audit-cli.js +4 -2
- package/dist/audit-cli.js.map +1 -1
- package/dist/audit-events.d.ts.map +1 -1
- package/dist/audit-events.js +53 -3
- package/dist/audit-events.js.map +1 -1
- package/dist/audit-trail.d.ts.map +1 -1
- package/dist/audit-trail.js +17 -8
- package/dist/audit-trail.js.map +1 -1
- package/dist/channels/discord/attachments.d.ts.map +1 -1
- package/dist/channels/discord/attachments.js +14 -7
- package/dist/channels/discord/attachments.js.map +1 -1
- package/dist/channels/discord/debounce.d.ts +9 -0
- package/dist/channels/discord/debounce.d.ts.map +1 -0
- package/dist/channels/discord/debounce.js +20 -0
- package/dist/channels/discord/debounce.js.map +1 -0
- package/dist/channels/discord/delivery.d.ts +4 -1
- package/dist/channels/discord/delivery.d.ts.map +1 -1
- package/dist/channels/discord/delivery.js +24 -4
- package/dist/channels/discord/delivery.js.map +1 -1
- package/dist/channels/discord/human-delay.d.ts +16 -0
- package/dist/channels/discord/human-delay.d.ts.map +1 -0
- package/dist/channels/discord/human-delay.js +29 -0
- package/dist/channels/discord/human-delay.js.map +1 -0
- package/dist/channels/discord/inbound.d.ts +31 -0
- package/dist/channels/discord/inbound.d.ts.map +1 -1
- package/dist/channels/discord/inbound.js +156 -6
- package/dist/channels/discord/inbound.js.map +1 -1
- package/dist/channels/discord/mentions.d.ts.map +1 -1
- package/dist/channels/discord/mentions.js +16 -4
- package/dist/channels/discord/mentions.js.map +1 -1
- package/dist/channels/discord/presence.d.ts +33 -0
- package/dist/channels/discord/presence.d.ts.map +1 -0
- package/dist/channels/discord/presence.js +111 -0
- package/dist/channels/discord/presence.js.map +1 -0
- package/dist/channels/discord/rate-limiter.d.ts +14 -0
- package/dist/channels/discord/rate-limiter.d.ts.map +1 -0
- package/dist/channels/discord/rate-limiter.js +49 -0
- package/dist/channels/discord/rate-limiter.js.map +1 -0
- package/dist/channels/discord/reactions.d.ts +38 -0
- package/dist/channels/discord/reactions.d.ts.map +1 -0
- package/dist/channels/discord/reactions.js +151 -0
- package/dist/channels/discord/reactions.js.map +1 -0
- package/dist/channels/discord/runtime.d.ts +7 -4
- package/dist/channels/discord/runtime.d.ts.map +1 -1
- package/dist/channels/discord/runtime.js +755 -150
- package/dist/channels/discord/runtime.js.map +1 -1
- package/dist/channels/discord/stream.d.ts +4 -1
- package/dist/channels/discord/stream.d.ts.map +1 -1
- package/dist/channels/discord/stream.js +19 -12
- package/dist/channels/discord/stream.js.map +1 -1
- package/dist/channels/discord/tool-actions.d.ts.map +1 -1
- package/dist/channels/discord/tool-actions.js +24 -12
- package/dist/channels/discord/tool-actions.js.map +1 -1
- package/dist/channels/discord/typing.d.ts +15 -0
- package/dist/channels/discord/typing.d.ts.map +1 -0
- package/dist/channels/discord/typing.js +106 -0
- package/dist/channels/discord/typing.js.map +1 -0
- package/dist/chunk.d.ts.map +1 -1
- package/dist/chunk.js +4 -2
- package/dist/chunk.js.map +1 -1
- package/dist/cli.js +77 -23
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +24 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +113 -18
- package/dist/config.js.map +1 -1
- package/dist/container-runner.d.ts.map +1 -1
- package/dist/container-runner.js +59 -26
- package/dist/container-runner.js.map +1 -1
- package/dist/container-setup.d.ts.map +1 -1
- package/dist/container-setup.js +10 -9
- package/dist/container-setup.js.map +1 -1
- package/dist/conversation.d.ts +2 -2
- package/dist/conversation.d.ts.map +1 -1
- package/dist/conversation.js +1 -1
- package/dist/conversation.js.map +1 -1
- package/dist/db.d.ts +118 -2
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +1568 -50
- package/dist/db.js.map +1 -1
- package/dist/delegation-manager.d.ts.map +1 -1
- package/dist/delegation-manager.js +3 -2
- package/dist/delegation-manager.js.map +1 -1
- package/dist/gateway-client.d.ts +2 -2
- package/dist/gateway-client.d.ts.map +1 -1
- package/dist/gateway-client.js +10 -4
- package/dist/gateway-client.js.map +1 -1
- package/dist/gateway-service.d.ts +3 -3
- package/dist/gateway-service.d.ts.map +1 -1
- package/dist/gateway-service.js +624 -87
- package/dist/gateway-service.js.map +1 -1
- package/dist/gateway-types.d.ts +24 -0
- package/dist/gateway-types.d.ts.map +1 -1
- package/dist/gateway-types.js.map +1 -1
- package/dist/gateway.js +303 -26
- package/dist/gateway.js.map +1 -1
- package/dist/health.d.ts.map +1 -1
- package/dist/health.js +20 -10
- package/dist/health.js.map +1 -1
- package/dist/heartbeat.d.ts +4 -0
- package/dist/heartbeat.d.ts.map +1 -1
- package/dist/heartbeat.js +62 -20
- package/dist/heartbeat.js.map +1 -1
- package/dist/hybridai-bots.d.ts.map +1 -1
- package/dist/hybridai-bots.js +4 -2
- package/dist/hybridai-bots.js.map +1 -1
- package/dist/hybridai-models.d.ts +8 -0
- package/dist/hybridai-models.d.ts.map +1 -0
- package/dist/hybridai-models.js +94 -0
- package/dist/hybridai-models.js.map +1 -0
- package/dist/instruction-approval-audit.d.ts.map +1 -1
- package/dist/instruction-approval-audit.js.map +1 -1
- package/dist/instruction-integrity.d.ts.map +1 -1
- package/dist/instruction-integrity.js +8 -2
- package/dist/instruction-integrity.js.map +1 -1
- package/dist/ipc.d.ts.map +1 -1
- package/dist/ipc.js +6 -1
- package/dist/ipc.js.map +1 -1
- package/dist/logger.js.map +1 -1
- package/dist/memory-consolidation.d.ts +17 -0
- package/dist/memory-consolidation.d.ts.map +1 -0
- package/dist/memory-consolidation.js +25 -0
- package/dist/memory-consolidation.js.map +1 -0
- package/dist/memory-service.d.ts +200 -0
- package/dist/memory-service.d.ts.map +1 -0
- package/dist/memory-service.js +294 -0
- package/dist/memory-service.js.map +1 -0
- package/dist/mount-security.d.ts.map +1 -1
- package/dist/mount-security.js +31 -7
- package/dist/mount-security.js.map +1 -1
- package/dist/observability-ingest.d.ts.map +1 -1
- package/dist/observability-ingest.js +32 -11
- package/dist/observability-ingest.js.map +1 -1
- package/dist/onboarding.d.ts.map +1 -1
- package/dist/onboarding.js +32 -9
- package/dist/onboarding.js.map +1 -1
- package/dist/proactive-policy.d.ts.map +1 -1
- package/dist/proactive-policy.js +2 -1
- package/dist/proactive-policy.js.map +1 -1
- package/dist/prompt-hooks.d.ts.map +1 -1
- package/dist/prompt-hooks.js +11 -7
- package/dist/prompt-hooks.js.map +1 -1
- package/dist/runtime-config.d.ts +104 -1
- package/dist/runtime-config.d.ts.map +1 -1
- package/dist/runtime-config.js +503 -24
- package/dist/runtime-config.js.map +1 -1
- package/dist/scheduled-task-runner.d.ts +1 -0
- package/dist/scheduled-task-runner.d.ts.map +1 -1
- package/dist/scheduled-task-runner.js +43 -10
- package/dist/scheduled-task-runner.js.map +1 -1
- package/dist/scheduler.d.ts +43 -4
- package/dist/scheduler.d.ts.map +1 -1
- package/dist/scheduler.js +530 -56
- package/dist/scheduler.js.map +1 -1
- package/dist/session-export.d.ts +26 -0
- package/dist/session-export.d.ts.map +1 -0
- package/dist/session-export.js +149 -0
- package/dist/session-export.js.map +1 -0
- package/dist/session-maintenance.d.ts.map +1 -1
- package/dist/session-maintenance.js +75 -13
- package/dist/session-maintenance.js.map +1 -1
- package/dist/session-transcripts.d.ts.map +1 -1
- package/dist/session-transcripts.js.map +1 -1
- package/dist/side-effects.d.ts.map +1 -1
- package/dist/side-effects.js +14 -2
- package/dist/side-effects.js.map +1 -1
- package/dist/skills-guard.d.ts.map +1 -1
- package/dist/skills-guard.js +893 -130
- package/dist/skills-guard.js.map +1 -1
- package/dist/skills.d.ts +5 -0
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +29 -15
- package/dist/skills.js.map +1 -1
- package/dist/token-efficiency.d.ts.map +1 -1
- package/dist/token-efficiency.js.map +1 -1
- package/dist/tui.js +92 -11
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +150 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +24 -1
- package/dist/types.js.map +1 -1
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +42 -14
- package/dist/update.js.map +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +49 -9
- package/dist/workspace.js.map +1 -1
- package/docs/chat.html +9 -3
- package/docs/index.html +72 -13
- package/package.json +8 -2
- package/src/agent.ts +31 -4
- package/src/audit-cli.ts +44 -16
- package/src/audit-events.ts +69 -5
- package/src/audit-trail.ts +41 -15
- package/src/channels/discord/attachments.ts +81 -27
- package/src/channels/discord/debounce.ts +25 -0
- package/src/channels/discord/delivery.ts +65 -14
- package/src/channels/discord/human-delay.ts +48 -0
- package/src/channels/discord/inbound.ts +190 -8
- package/src/channels/discord/mentions.ts +42 -18
- package/src/channels/discord/presence.ts +148 -0
- package/src/channels/discord/rate-limiter.ts +58 -0
- package/src/channels/discord/reactions.ts +211 -0
- package/src/channels/discord/runtime.ts +1161 -205
- package/src/channels/discord/stream.ts +85 -31
- package/src/channels/discord/tool-actions.ts +78 -37
- package/src/channels/discord/typing.ts +140 -0
- package/src/chunk.ts +12 -4
- package/src/cli.ts +174 -57
- package/src/config.ts +208 -36
- package/src/container-runner.ts +134 -42
- package/src/container-setup.ts +57 -22
- package/src/conversation.ts +9 -7
- package/src/db.ts +2217 -84
- package/src/delegation-manager.ts +6 -2
- package/src/gateway-client.ts +41 -17
- package/src/gateway-service.ts +996 -220
- package/src/gateway-types.ts +33 -0
- package/src/gateway.ts +487 -52
- package/src/health.ts +66 -26
- package/src/heartbeat.ts +99 -22
- package/src/hybridai-bots.ts +14 -5
- package/src/hybridai-models.ts +158 -0
- package/src/instruction-approval-audit.ts +4 -1
- package/src/instruction-integrity.ts +30 -9
- package/src/ipc.ts +23 -5
- package/src/logger.ts +4 -1
- package/src/memory-consolidation.ts +41 -0
- package/src/memory-service.ts +606 -0
- package/src/mount-security.ts +58 -13
- package/src/observability-ingest.ts +134 -35
- package/src/onboarding.ts +126 -35
- package/src/proactive-policy.ts +3 -1
- package/src/prompt-hooks.ts +42 -17
- package/src/runtime-config.ts +1112 -122
- package/src/scheduled-task-runner.ts +77 -11
- package/src/scheduler.ts +683 -60
- package/src/session-export.ts +196 -0
- package/src/session-maintenance.ts +125 -22
- package/src/session-transcripts.ts +12 -3
- package/src/side-effects.ts +28 -5
- package/src/skills-guard.ts +1067 -219
- package/src/skills.ts +163 -65
- package/src/token-efficiency.ts +31 -9
- package/src/tui.ts +166 -25
- package/src/types.ts +199 -2
- package/src/update.ts +79 -23
- package/src/workspace.ts +63 -11
- package/tests/approval-policy.test.ts +335 -0
- package/tests/discord.basic.test.ts +213 -4
- package/tests/discord.human-presence.test.ts +85 -0
- package/tests/gateway-service.media-routing.test.ts +8 -2
- package/tests/hybridai-client.test.ts +112 -0
- package/tests/hybridai-models.test.ts +46 -0
- package/tests/memory-service.test.ts +1114 -0
- package/tests/token-efficiency.basic.test.ts +8 -2
- package/tests/token-usage.cache.test.ts +128 -0
- package/vitest.e2e.config.ts +3 -1
- package/vitest.integration.config.ts +3 -1
- package/vitest.live.config.ts +3 -1
- package/vitest.unit.config.ts +9 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { agentWorkspaceDir, ensureAgentDirs } from './ipc.js';
|
|
5
|
+
import { logger } from './logger.js';
|
|
6
|
+
import type { StoredMessage } from './types.js';
|
|
7
|
+
|
|
8
|
+
const SESSION_EXPORTS_DIR_NAME = '.session-exports';
|
|
9
|
+
|
|
10
|
+
interface ExportedMessageRow {
|
|
11
|
+
type: 'message';
|
|
12
|
+
section: 'full' | 'compacted' | 'retained';
|
|
13
|
+
sessionId: string;
|
|
14
|
+
channelId: string | null;
|
|
15
|
+
id: number;
|
|
16
|
+
role: string;
|
|
17
|
+
userId: string;
|
|
18
|
+
username: string | null;
|
|
19
|
+
content: string;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function safeFilePart(raw: string): string {
|
|
24
|
+
const normalized = raw.trim().replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
25
|
+
return normalized || 'session';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function compactText(text: string, maxChars = 4_000): string {
|
|
29
|
+
const compact = text.replace(/\s+/g, ' ').trim();
|
|
30
|
+
if (compact.length <= maxChars) return compact;
|
|
31
|
+
return `${compact.slice(0, maxChars)}...`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function exportBaseDir(agentId: string, sessionId: string): string {
|
|
35
|
+
ensureAgentDirs(agentId);
|
|
36
|
+
return path.join(
|
|
37
|
+
agentWorkspaceDir(agentId),
|
|
38
|
+
SESSION_EXPORTS_DIR_NAME,
|
|
39
|
+
safeFilePart(sessionId),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function exportFilePath(baseDir: string, reason: string): string {
|
|
44
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
45
|
+
const filename = `${stamp}-${safeFilePart(reason)}.jsonl`;
|
|
46
|
+
return path.join(baseDir, filename);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function writeJsonlFile(filePath: string, rows: unknown[]): boolean {
|
|
50
|
+
try {
|
|
51
|
+
const lines = rows.map((row) => JSON.stringify(row)).join('\n');
|
|
52
|
+
fs.writeFileSync(filePath, `${lines}\n`, 'utf8');
|
|
53
|
+
return true;
|
|
54
|
+
} catch (err) {
|
|
55
|
+
logger.warn({ filePath, err }, 'Failed to write session export JSONL');
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function exportSessionSnapshotJsonl(params: {
|
|
61
|
+
agentId: string;
|
|
62
|
+
sessionId: string;
|
|
63
|
+
channelId?: string | null;
|
|
64
|
+
summary?: string | null;
|
|
65
|
+
messages: StoredMessage[];
|
|
66
|
+
reason: string;
|
|
67
|
+
}): { path: string; lineCount: number } | null {
|
|
68
|
+
const agentId = params.agentId.trim();
|
|
69
|
+
const sessionId = params.sessionId.trim();
|
|
70
|
+
if (!agentId || !sessionId) return null;
|
|
71
|
+
try {
|
|
72
|
+
const baseDir = exportBaseDir(agentId, sessionId);
|
|
73
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
74
|
+
const filePath = exportFilePath(baseDir, params.reason);
|
|
75
|
+
const now = new Date().toISOString();
|
|
76
|
+
const rows: unknown[] = [
|
|
77
|
+
{
|
|
78
|
+
type: 'meta',
|
|
79
|
+
reason: params.reason,
|
|
80
|
+
exportedAt: now,
|
|
81
|
+
agentId,
|
|
82
|
+
sessionId,
|
|
83
|
+
channelId: params.channelId || null,
|
|
84
|
+
messageCount: params.messages.length,
|
|
85
|
+
hasSummary: Boolean((params.summary || '').trim()),
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
const summary = (params.summary || '').trim();
|
|
89
|
+
if (summary) {
|
|
90
|
+
rows.push({
|
|
91
|
+
type: 'summary',
|
|
92
|
+
sessionId,
|
|
93
|
+
content: compactText(summary, 12_000),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
rows.push(
|
|
97
|
+
...params.messages.map<ExportedMessageRow>((message) => ({
|
|
98
|
+
type: 'message',
|
|
99
|
+
section: 'full',
|
|
100
|
+
sessionId,
|
|
101
|
+
channelId: params.channelId || null,
|
|
102
|
+
id: message.id,
|
|
103
|
+
role: message.role,
|
|
104
|
+
userId: message.user_id,
|
|
105
|
+
username: message.username,
|
|
106
|
+
content: message.content,
|
|
107
|
+
createdAt: message.created_at,
|
|
108
|
+
})),
|
|
109
|
+
);
|
|
110
|
+
if (!writeJsonlFile(filePath, rows)) return null;
|
|
111
|
+
return { path: filePath, lineCount: rows.length };
|
|
112
|
+
} catch (err) {
|
|
113
|
+
logger.warn(
|
|
114
|
+
{ agentId, sessionId, err },
|
|
115
|
+
'Failed to export session snapshot',
|
|
116
|
+
);
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function exportCompactedSessionJsonl(params: {
|
|
122
|
+
agentId: string;
|
|
123
|
+
sessionId: string;
|
|
124
|
+
channelId: string;
|
|
125
|
+
summary: string;
|
|
126
|
+
compactedMessages: StoredMessage[];
|
|
127
|
+
retainedMessages: StoredMessage[];
|
|
128
|
+
deletedCount: number;
|
|
129
|
+
cutoffId: number;
|
|
130
|
+
}): { path: string; lineCount: number } | null {
|
|
131
|
+
const agentId = params.agentId.trim();
|
|
132
|
+
const sessionId = params.sessionId.trim();
|
|
133
|
+
if (!agentId || !sessionId) return null;
|
|
134
|
+
try {
|
|
135
|
+
const baseDir = exportBaseDir(agentId, sessionId);
|
|
136
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
137
|
+
const filePath = exportFilePath(baseDir, 'compaction');
|
|
138
|
+
const exportedAt = new Date().toISOString();
|
|
139
|
+
const rows: unknown[] = [
|
|
140
|
+
{
|
|
141
|
+
type: 'meta',
|
|
142
|
+
reason: 'compaction',
|
|
143
|
+
exportedAt,
|
|
144
|
+
agentId,
|
|
145
|
+
sessionId,
|
|
146
|
+
channelId: params.channelId,
|
|
147
|
+
cutoffId: params.cutoffId,
|
|
148
|
+
deletedCount: params.deletedCount,
|
|
149
|
+
compactedMessageCount: params.compactedMessages.length,
|
|
150
|
+
retainedMessageCount: params.retainedMessages.length,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
type: 'summary',
|
|
154
|
+
sessionId,
|
|
155
|
+
content: compactText(params.summary, 12_000),
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
rows.push(
|
|
160
|
+
...params.compactedMessages.map<ExportedMessageRow>((message) => ({
|
|
161
|
+
type: 'message',
|
|
162
|
+
section: 'compacted',
|
|
163
|
+
sessionId,
|
|
164
|
+
channelId: params.channelId,
|
|
165
|
+
id: message.id,
|
|
166
|
+
role: message.role,
|
|
167
|
+
userId: message.user_id,
|
|
168
|
+
username: message.username,
|
|
169
|
+
content: message.content,
|
|
170
|
+
createdAt: message.created_at,
|
|
171
|
+
})),
|
|
172
|
+
);
|
|
173
|
+
rows.push(
|
|
174
|
+
...params.retainedMessages.map<ExportedMessageRow>((message) => ({
|
|
175
|
+
type: 'message',
|
|
176
|
+
section: 'retained',
|
|
177
|
+
sessionId,
|
|
178
|
+
channelId: params.channelId,
|
|
179
|
+
id: message.id,
|
|
180
|
+
role: message.role,
|
|
181
|
+
userId: message.user_id,
|
|
182
|
+
username: message.username,
|
|
183
|
+
content: message.content,
|
|
184
|
+
createdAt: message.created_at,
|
|
185
|
+
})),
|
|
186
|
+
);
|
|
187
|
+
if (!writeJsonlFile(filePath, rows)) return null;
|
|
188
|
+
return { path: filePath, lineCount: rows.length };
|
|
189
|
+
} catch (err) {
|
|
190
|
+
logger.warn(
|
|
191
|
+
{ agentId, sessionId, err },
|
|
192
|
+
'Failed to export compacted session JSONL',
|
|
193
|
+
);
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -1,28 +1,41 @@
|
|
|
1
|
+
import { runAgent } from './agent.js';
|
|
1
2
|
import {
|
|
2
3
|
PRE_COMPACTION_MEMORY_FLUSH_ENABLED,
|
|
3
4
|
PRE_COMPACTION_MEMORY_FLUSH_MAX_CHARS,
|
|
4
5
|
PRE_COMPACTION_MEMORY_FLUSH_MAX_MESSAGES,
|
|
6
|
+
SESSION_COMPACTION_BUDGET_RATIO,
|
|
5
7
|
SESSION_COMPACTION_ENABLED,
|
|
6
8
|
SESSION_COMPACTION_KEEP_RECENT,
|
|
7
9
|
SESSION_COMPACTION_SUMMARY_MAX_CHARS,
|
|
8
10
|
SESSION_COMPACTION_THRESHOLD,
|
|
11
|
+
SESSION_COMPACTION_TOKEN_BUDGET,
|
|
9
12
|
} from './config.js';
|
|
10
|
-
import {
|
|
11
|
-
deleteMessagesBeforeId,
|
|
12
|
-
getCompactionCandidateMessages,
|
|
13
|
-
getSessionById,
|
|
14
|
-
markSessionMemoryFlush,
|
|
15
|
-
updateSessionSummary,
|
|
16
|
-
} from './db.js';
|
|
17
13
|
import { logger } from './logger.js';
|
|
14
|
+
import { memoryService } from './memory-service.js';
|
|
15
|
+
import { buildSystemPromptFromHooks } from './prompt-hooks.js';
|
|
16
|
+
import { exportCompactedSessionJsonl } from './session-export.js';
|
|
18
17
|
import { loadSkills } from './skills.js';
|
|
18
|
+
import {
|
|
19
|
+
estimateTokenCountFromMessages,
|
|
20
|
+
estimateTokenCountFromText,
|
|
21
|
+
} from './token-efficiency.js';
|
|
19
22
|
import type { ChatMessage, StoredMessage } from './types.js';
|
|
20
|
-
import { runAgent } from './agent.js';
|
|
21
|
-
import { buildSystemPromptFromHooks } from './prompt-hooks.js';
|
|
22
23
|
|
|
23
24
|
const COMPACTION_SOURCE_MAX_MESSAGES = 240;
|
|
24
25
|
const COMPACTION_SOURCE_MAX_CHARS = 80_000;
|
|
25
26
|
|
|
27
|
+
function normalizeStoredMessageRole(role: string): ChatMessage['role'] {
|
|
28
|
+
if (
|
|
29
|
+
role === 'system' ||
|
|
30
|
+
role === 'user' ||
|
|
31
|
+
role === 'assistant' ||
|
|
32
|
+
role === 'tool'
|
|
33
|
+
) {
|
|
34
|
+
return role;
|
|
35
|
+
}
|
|
36
|
+
return 'user';
|
|
37
|
+
}
|
|
38
|
+
|
|
26
39
|
function formatDateStampInLocalTimezone(now: Date): string {
|
|
27
40
|
const parts = new Intl.DateTimeFormat('en-US', {
|
|
28
41
|
year: 'numeric',
|
|
@@ -41,7 +54,10 @@ function formatDateStampInLocalTimezone(now: Date): string {
|
|
|
41
54
|
function normalizeSummary(summary: string): string {
|
|
42
55
|
let text = summary.trim();
|
|
43
56
|
if (text.startsWith('```')) {
|
|
44
|
-
text = text
|
|
57
|
+
text = text
|
|
58
|
+
.replace(/^```[a-z0-9_-]*\s*/i, '')
|
|
59
|
+
.replace(/```$/i, '')
|
|
60
|
+
.trim();
|
|
45
61
|
}
|
|
46
62
|
if (text.length > SESSION_COMPACTION_SUMMARY_MAX_CHARS) {
|
|
47
63
|
text = `${text.slice(0, SESSION_COMPACTION_SUMMARY_MAX_CHARS)}\n\n...[truncated]`;
|
|
@@ -61,7 +77,10 @@ function formatMessagesForPrompt(
|
|
|
61
77
|
for (const msg of selected) {
|
|
62
78
|
const role = (msg.role || 'unknown').toUpperCase();
|
|
63
79
|
const compact = msg.content.replace(/\r/g, '').trim();
|
|
64
|
-
const bounded =
|
|
80
|
+
const bounded =
|
|
81
|
+
compact.length > 1_200
|
|
82
|
+
? `${compact.slice(0, 1_200)}\n...[truncated]`
|
|
83
|
+
: compact;
|
|
65
84
|
const entry = `[${role}] ${bounded}`;
|
|
66
85
|
const bytes = entry.length + 2;
|
|
67
86
|
if (usedChars + bytes > maxChars) break;
|
|
@@ -72,7 +91,11 @@ function formatMessagesForPrompt(
|
|
|
72
91
|
return lines.join('\n\n');
|
|
73
92
|
}
|
|
74
93
|
|
|
75
|
-
function buildSystemPrompt(
|
|
94
|
+
function buildSystemPrompt(
|
|
95
|
+
agentId: string,
|
|
96
|
+
sessionSummary?: string | null,
|
|
97
|
+
extra?: string,
|
|
98
|
+
): string {
|
|
76
99
|
return buildSystemPromptFromHooks({
|
|
77
100
|
agentId,
|
|
78
101
|
sessionSummary,
|
|
@@ -143,12 +166,18 @@ async function runPreCompactionMemoryFlush(params: {
|
|
|
143
166
|
['memory'],
|
|
144
167
|
);
|
|
145
168
|
if (output.status === 'error') {
|
|
146
|
-
logger.warn(
|
|
169
|
+
logger.warn(
|
|
170
|
+
{ sessionId: params.sessionId, error: output.error },
|
|
171
|
+
'Pre-compaction memory flush failed',
|
|
172
|
+
);
|
|
147
173
|
return;
|
|
148
174
|
}
|
|
149
|
-
markSessionMemoryFlush(params.sessionId);
|
|
175
|
+
memoryService.markSessionMemoryFlush(params.sessionId);
|
|
150
176
|
} catch (err) {
|
|
151
|
-
logger.warn(
|
|
177
|
+
logger.warn(
|
|
178
|
+
{ sessionId: params.sessionId, err },
|
|
179
|
+
'Pre-compaction memory flush crashed',
|
|
180
|
+
);
|
|
152
181
|
}
|
|
153
182
|
}
|
|
154
183
|
|
|
@@ -204,7 +233,10 @@ async function generateCompactionSummary(params: {
|
|
|
204
233
|
);
|
|
205
234
|
|
|
206
235
|
if (output.status === 'error' || !output.result) {
|
|
207
|
-
logger.warn(
|
|
236
|
+
logger.warn(
|
|
237
|
+
{ sessionId: params.sessionId, error: output.error },
|
|
238
|
+
'Session compaction summary failed',
|
|
239
|
+
);
|
|
208
240
|
return null;
|
|
209
241
|
}
|
|
210
242
|
|
|
@@ -223,15 +255,60 @@ export async function maybeCompactSession(params: {
|
|
|
223
255
|
}): Promise<void> {
|
|
224
256
|
if (!SESSION_COMPACTION_ENABLED) return;
|
|
225
257
|
|
|
226
|
-
const session = getSessionById(params.sessionId);
|
|
258
|
+
const session = memoryService.getSessionById(params.sessionId);
|
|
227
259
|
if (!session) return;
|
|
228
260
|
|
|
229
261
|
const threshold = Math.max(SESSION_COMPACTION_THRESHOLD, 20);
|
|
230
|
-
const
|
|
262
|
+
const tokenBudget = Math.max(1_000, SESSION_COMPACTION_TOKEN_BUDGET);
|
|
263
|
+
const budgetRatio = Math.max(
|
|
264
|
+
0.05,
|
|
265
|
+
Math.min(1, SESSION_COMPACTION_BUDGET_RATIO),
|
|
266
|
+
);
|
|
267
|
+
const budget = Math.max(1, Math.floor(tokenBudget * budgetRatio));
|
|
268
|
+
const allMessages = memoryService.getRecentMessages(params.sessionId);
|
|
269
|
+
const keepRecent = Math.max(
|
|
270
|
+
1,
|
|
271
|
+
Math.min(
|
|
272
|
+
SESSION_COMPACTION_KEEP_RECENT,
|
|
273
|
+
Math.max(1, threshold - 1),
|
|
274
|
+
Math.max(1, allMessages.length - 1),
|
|
275
|
+
),
|
|
276
|
+
);
|
|
277
|
+
const msgTokens = estimateTokenCountFromMessages(
|
|
278
|
+
allMessages.map((message) => ({
|
|
279
|
+
role: normalizeStoredMessageRole(message.role),
|
|
280
|
+
content: message.content,
|
|
281
|
+
})),
|
|
282
|
+
);
|
|
283
|
+
const summaryTokens = estimateTokenCountFromText(session.session_summary);
|
|
284
|
+
const totalTokens = msgTokens + summaryTokens;
|
|
285
|
+
const shouldCompactForTokens = totalTokens >= budget;
|
|
286
|
+
const shouldCompactForMessageCount = session.message_count >= threshold;
|
|
287
|
+
|
|
288
|
+
logger.debug(
|
|
289
|
+
{
|
|
290
|
+
sessionId: params.sessionId,
|
|
291
|
+
messageCount: session.message_count,
|
|
292
|
+
loadedMessages: allMessages.length,
|
|
293
|
+
msgTokens,
|
|
294
|
+
summaryTokens,
|
|
295
|
+
totalTokens,
|
|
296
|
+
tokenBudget,
|
|
297
|
+
budgetRatio,
|
|
298
|
+
triggerBudget: budget,
|
|
299
|
+
triggerThreshold: threshold,
|
|
300
|
+
shouldCompactForTokens,
|
|
301
|
+
shouldCompactForMessageCount,
|
|
302
|
+
},
|
|
303
|
+
'Session compaction budget check',
|
|
304
|
+
);
|
|
231
305
|
|
|
232
|
-
if (
|
|
306
|
+
if (!shouldCompactForTokens && !shouldCompactForMessageCount) return;
|
|
233
307
|
|
|
234
|
-
const candidate = getCompactionCandidateMessages(
|
|
308
|
+
const candidate = memoryService.getCompactionCandidateMessages(
|
|
309
|
+
params.sessionId,
|
|
310
|
+
keepRecent,
|
|
311
|
+
);
|
|
235
312
|
if (!candidate || candidate.olderMessages.length === 0) return;
|
|
236
313
|
|
|
237
314
|
await runPreCompactionMemoryFlush({
|
|
@@ -247,10 +324,27 @@ export async function maybeCompactSession(params: {
|
|
|
247
324
|
});
|
|
248
325
|
if (!summary) return;
|
|
249
326
|
|
|
250
|
-
const deleted = deleteMessagesBeforeId(
|
|
327
|
+
const deleted = memoryService.deleteMessagesBeforeId(
|
|
328
|
+
params.sessionId,
|
|
329
|
+
candidate.cutoffId,
|
|
330
|
+
);
|
|
251
331
|
if (deleted <= 0) return;
|
|
252
332
|
|
|
253
|
-
updateSessionSummary(params.sessionId, summary);
|
|
333
|
+
memoryService.updateSessionSummary(params.sessionId, summary);
|
|
334
|
+
const retainedMessages = memoryService.getRecentMessages(
|
|
335
|
+
params.sessionId,
|
|
336
|
+
keepRecent,
|
|
337
|
+
);
|
|
338
|
+
const exported = exportCompactedSessionJsonl({
|
|
339
|
+
agentId: params.agentId,
|
|
340
|
+
sessionId: params.sessionId,
|
|
341
|
+
channelId: params.channelId,
|
|
342
|
+
summary,
|
|
343
|
+
compactedMessages: candidate.olderMessages,
|
|
344
|
+
retainedMessages,
|
|
345
|
+
deletedCount: deleted,
|
|
346
|
+
cutoffId: candidate.cutoffId,
|
|
347
|
+
});
|
|
254
348
|
logger.info(
|
|
255
349
|
{
|
|
256
350
|
sessionId: params.sessionId,
|
|
@@ -258,6 +352,15 @@ export async function maybeCompactSession(params: {
|
|
|
258
352
|
cutoffId: candidate.cutoffId,
|
|
259
353
|
threshold,
|
|
260
354
|
keepRecent,
|
|
355
|
+
msgTokens,
|
|
356
|
+
summaryTokens,
|
|
357
|
+
totalTokens,
|
|
358
|
+
tokenBudget,
|
|
359
|
+
budgetRatio,
|
|
360
|
+
triggerBudget: budget,
|
|
361
|
+
shouldCompactForTokens,
|
|
362
|
+
shouldCompactForMessageCount,
|
|
363
|
+
exportPath: exported?.path || null,
|
|
261
364
|
},
|
|
262
365
|
'Session compacted',
|
|
263
366
|
);
|
|
@@ -21,14 +21,20 @@ function safeSessionFilename(sessionId: string): string {
|
|
|
21
21
|
return normalized || 'session';
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export function appendSessionTranscript(
|
|
24
|
+
export function appendSessionTranscript(
|
|
25
|
+
agentId: string,
|
|
26
|
+
entry: TranscriptEntry,
|
|
27
|
+
): void {
|
|
25
28
|
try {
|
|
26
29
|
ensureAgentDirs(agentId);
|
|
27
30
|
const workspace = agentWorkspaceDir(agentId);
|
|
28
31
|
const transcriptDir = path.join(workspace, TRANSCRIPTS_DIR_NAME);
|
|
29
32
|
fs.mkdirSync(transcriptDir, { recursive: true });
|
|
30
33
|
|
|
31
|
-
const filePath = path.join(
|
|
34
|
+
const filePath = path.join(
|
|
35
|
+
transcriptDir,
|
|
36
|
+
`${safeSessionFilename(entry.sessionId)}.jsonl`,
|
|
37
|
+
);
|
|
32
38
|
const row = {
|
|
33
39
|
sessionId: entry.sessionId,
|
|
34
40
|
channelId: entry.channelId,
|
|
@@ -40,6 +46,9 @@ export function appendSessionTranscript(agentId: string, entry: TranscriptEntry)
|
|
|
40
46
|
};
|
|
41
47
|
fs.appendFileSync(filePath, JSON.stringify(row) + '\n', 'utf-8');
|
|
42
48
|
} catch (err) {
|
|
43
|
-
logger.debug(
|
|
49
|
+
logger.debug(
|
|
50
|
+
{ agentId, sessionId: entry.sessionId, err },
|
|
51
|
+
'Failed to append session transcript',
|
|
52
|
+
);
|
|
44
53
|
}
|
|
45
54
|
}
|
package/src/side-effects.ts
CHANGED
|
@@ -15,7 +15,8 @@ export function processSideEffects(
|
|
|
15
15
|
): void {
|
|
16
16
|
const schedules = output.sideEffects?.schedules;
|
|
17
17
|
const delegations = output.sideEffects?.delegations || [];
|
|
18
|
-
if ((!schedules || schedules.length === 0) && delegations.length === 0)
|
|
18
|
+
if ((!schedules || schedules.length === 0) && delegations.length === 0)
|
|
19
|
+
return;
|
|
19
20
|
|
|
20
21
|
let changed = false;
|
|
21
22
|
|
|
@@ -31,11 +32,24 @@ export function processSideEffects(
|
|
|
31
32
|
effect.runAt,
|
|
32
33
|
effect.everyMs,
|
|
33
34
|
);
|
|
34
|
-
logger.info(
|
|
35
|
+
logger.info(
|
|
36
|
+
{
|
|
37
|
+
taskId,
|
|
38
|
+
sessionId,
|
|
39
|
+
channelId,
|
|
40
|
+
cronExpr: effect.cronExpr,
|
|
41
|
+
runAt: effect.runAt,
|
|
42
|
+
everyMs: effect.everyMs,
|
|
43
|
+
},
|
|
44
|
+
'Side-effect: created task',
|
|
45
|
+
);
|
|
35
46
|
changed = true;
|
|
36
47
|
} else if (effect.action === 'remove') {
|
|
37
48
|
deleteTask(effect.taskId);
|
|
38
|
-
logger.info(
|
|
49
|
+
logger.info(
|
|
50
|
+
{ taskId: effect.taskId, sessionId },
|
|
51
|
+
'Side-effect: removed task',
|
|
52
|
+
);
|
|
39
53
|
changed = true;
|
|
40
54
|
}
|
|
41
55
|
} catch (err) {
|
|
@@ -54,7 +68,13 @@ export function processSideEffects(
|
|
|
54
68
|
{
|
|
55
69
|
sessionId,
|
|
56
70
|
channelId,
|
|
57
|
-
mode:
|
|
71
|
+
mode:
|
|
72
|
+
effect.mode ||
|
|
73
|
+
(effect.chain?.length
|
|
74
|
+
? 'chain'
|
|
75
|
+
: effect.tasks?.length
|
|
76
|
+
? 'parallel'
|
|
77
|
+
: 'single'),
|
|
58
78
|
prompt: effect.prompt,
|
|
59
79
|
label: effect.label,
|
|
60
80
|
tasks: effect.tasks?.length,
|
|
@@ -64,7 +84,10 @@ export function processSideEffects(
|
|
|
64
84
|
);
|
|
65
85
|
}
|
|
66
86
|
} catch (err) {
|
|
67
|
-
logger.error(
|
|
87
|
+
logger.error(
|
|
88
|
+
{ effect, err },
|
|
89
|
+
'Failed to process delegation side-effect',
|
|
90
|
+
);
|
|
68
91
|
}
|
|
69
92
|
}
|
|
70
93
|
}
|