@byte5ai/palaia 2.0.4 → 2.0.6
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/package.json +1 -1
- package/src/hooks.ts +167 -81
package/package.json
CHANGED
package/src/hooks.ts
CHANGED
|
@@ -137,6 +137,16 @@ const lastInboundMessageByChannel = new Map<string, { messageId: string; provide
|
|
|
137
137
|
/** Channels that support emoji reactions. */
|
|
138
138
|
const REACTION_SUPPORTED_PROVIDERS = new Set(["slack", "discord"]);
|
|
139
139
|
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// Logger (Issue: api.logger integration)
|
|
142
|
+
// ============================================================================
|
|
143
|
+
|
|
144
|
+
/** Module-level logger — defaults to console, replaced by api.logger in registerHooks. */
|
|
145
|
+
let logger: { info: (...args: any[]) => void; warn: (...args: any[]) => void } = {
|
|
146
|
+
info: (...args: any[]) => console.log(...args),
|
|
147
|
+
warn: (...args: any[]) => console.warn(...args),
|
|
148
|
+
};
|
|
149
|
+
|
|
140
150
|
// ============================================================================
|
|
141
151
|
// Scope Validation (Issue #90)
|
|
142
152
|
// ============================================================================
|
|
@@ -350,7 +360,7 @@ async function sendSlackReaction(
|
|
|
350
360
|
): Promise<void> {
|
|
351
361
|
const token = await resolveSlackBotToken();
|
|
352
362
|
if (!token) {
|
|
353
|
-
|
|
363
|
+
logger.warn("[palaia] Cannot send Slack reaction: no bot token found");
|
|
354
364
|
return;
|
|
355
365
|
}
|
|
356
366
|
|
|
@@ -375,11 +385,11 @@ async function sendSlackReaction(
|
|
|
375
385
|
});
|
|
376
386
|
const data = await response.json() as { ok: boolean; error?: string };
|
|
377
387
|
if (!data.ok && data.error !== "already_reacted") {
|
|
378
|
-
|
|
388
|
+
logger.warn(`[palaia] Slack reaction failed: ${data.error} (${normalizedEmoji} on ${channelId})`);
|
|
379
389
|
}
|
|
380
390
|
} catch (err) {
|
|
381
391
|
if ((err as Error).name !== "AbortError") {
|
|
382
|
-
|
|
392
|
+
logger.warn(`[palaia] Slack reaction error (${normalizedEmoji}): ${err}`);
|
|
383
393
|
}
|
|
384
394
|
} finally {
|
|
385
395
|
clearTimeout(timeout);
|
|
@@ -792,7 +802,7 @@ export function resolveCaptureModel(
|
|
|
792
802
|
if (parts.length >= 2) {
|
|
793
803
|
if (!_captureModelFallbackWarned) {
|
|
794
804
|
_captureModelFallbackWarned = true;
|
|
795
|
-
|
|
805
|
+
logger.warn(`[palaia] No captureModel configured — using primary model. Set captureModel in plugin config for cost savings.`);
|
|
796
806
|
}
|
|
797
807
|
return { provider: parts[0], model: parts.slice(1).join("/") };
|
|
798
808
|
}
|
|
@@ -825,21 +835,27 @@ function collectText(payloads: Array<{ text?: string; isError?: boolean }> | und
|
|
|
825
835
|
* then hard-cap at maxChars from the end (newest messages kept).
|
|
826
836
|
*/
|
|
827
837
|
export function trimToRecentExchanges(
|
|
828
|
-
texts: Array<{ role: string; text: string }>,
|
|
838
|
+
texts: Array<{ role: string; text: string; provenance?: string }>,
|
|
829
839
|
maxPairs = 5,
|
|
830
840
|
maxChars = 10_000,
|
|
831
|
-
): Array<{ role: string; text: string }> {
|
|
841
|
+
): Array<{ role: string; text: string; provenance?: string }> {
|
|
832
842
|
// Filter to only user + assistant messages (skip tool, toolResult, system, etc.)
|
|
833
843
|
const exchanges = texts.filter((t) => t.role === "user" || t.role === "assistant");
|
|
834
844
|
|
|
835
845
|
// Keep the last N pairs (a pair = one user + one assistant message)
|
|
846
|
+
// Only count external_user messages as real user turns.
|
|
847
|
+
// System-injected user messages (inter_session, internal_system) don't count as conversation turns.
|
|
836
848
|
// Walk backwards, count pairs
|
|
837
849
|
let pairCount = 0;
|
|
838
850
|
let lastRole = "";
|
|
839
851
|
let cutIndex = 0; // default: keep everything
|
|
840
852
|
for (let i = exchanges.length - 1; i >= 0; i--) {
|
|
841
|
-
|
|
842
|
-
|
|
853
|
+
const isRealUser = exchanges[i].role === "user" && (
|
|
854
|
+
exchanges[i].provenance === "external_user" ||
|
|
855
|
+
!exchanges[i].provenance // backward compat: no provenance = treat as real user
|
|
856
|
+
);
|
|
857
|
+
// Count a new pair when we see a real user message after having seen an assistant
|
|
858
|
+
if (isRealUser && lastRole === "assistant") {
|
|
843
859
|
pairCount++;
|
|
844
860
|
if (pairCount > maxPairs) {
|
|
845
861
|
cutIndex = i + 1; // keep from next message onwards
|
|
@@ -1087,8 +1103,8 @@ export function extractSignificance(
|
|
|
1087
1103
|
return { tags, type: primaryType, summary };
|
|
1088
1104
|
}
|
|
1089
1105
|
|
|
1090
|
-
export function extractMessageTexts(messages: unknown[]): Array<{ role: string; text: string }> {
|
|
1091
|
-
const result: Array<{ role: string; text: string }> = [];
|
|
1106
|
+
export function extractMessageTexts(messages: unknown[]): Array<{ role: string; text: string; provenance?: string }> {
|
|
1107
|
+
const result: Array<{ role: string; text: string; provenance?: string }> = [];
|
|
1092
1108
|
|
|
1093
1109
|
for (const msg of messages) {
|
|
1094
1110
|
if (!msg || typeof msg !== "object") continue;
|
|
@@ -1096,8 +1112,12 @@ export function extractMessageTexts(messages: unknown[]): Array<{ role: string;
|
|
|
1096
1112
|
const role = m.role;
|
|
1097
1113
|
if (!role || typeof role !== "string") continue;
|
|
1098
1114
|
|
|
1115
|
+
// Extract provenance kind (string or object with .kind)
|
|
1116
|
+
const rawProvenance = (m as any).provenance?.kind ?? (m as any).provenance;
|
|
1117
|
+
const provenance = typeof rawProvenance === "string" ? rawProvenance : undefined;
|
|
1118
|
+
|
|
1099
1119
|
if (typeof m.content === "string" && m.content.trim()) {
|
|
1100
|
-
result.push({ role, text: m.content.trim() });
|
|
1120
|
+
result.push({ role, text: m.content.trim(), provenance });
|
|
1101
1121
|
continue;
|
|
1102
1122
|
}
|
|
1103
1123
|
|
|
@@ -1110,7 +1130,7 @@ export function extractMessageTexts(messages: unknown[]): Array<{ role: string;
|
|
|
1110
1130
|
typeof block.text === "string" &&
|
|
1111
1131
|
block.text.trim()
|
|
1112
1132
|
) {
|
|
1113
|
-
result.push({ role, text: block.text.trim() });
|
|
1133
|
+
result.push({ role, text: block.text.trim(), provenance });
|
|
1114
1134
|
}
|
|
1115
1135
|
}
|
|
1116
1136
|
}
|
|
@@ -1121,6 +1141,12 @@ export function extractMessageTexts(messages: unknown[]): Array<{ role: string;
|
|
|
1121
1141
|
|
|
1122
1142
|
export function getLastUserMessage(messages: unknown[]): string | null {
|
|
1123
1143
|
const texts = extractMessageTexts(messages);
|
|
1144
|
+
// Prefer external_user provenance (real human input)
|
|
1145
|
+
for (let i = texts.length - 1; i >= 0; i--) {
|
|
1146
|
+
if (texts[i].role === "user" && texts[i].provenance === "external_user")
|
|
1147
|
+
return texts[i].text;
|
|
1148
|
+
}
|
|
1149
|
+
// Fallback: any user message (backward compat for OpenClaw without provenance)
|
|
1124
1150
|
for (let i = texts.length - 1; i >= 0; i--) {
|
|
1125
1151
|
if (texts[i].role === "user") return texts[i].text;
|
|
1126
1152
|
}
|
|
@@ -1128,80 +1154,122 @@ export function getLastUserMessage(messages: unknown[]): string | null {
|
|
|
1128
1154
|
}
|
|
1129
1155
|
|
|
1130
1156
|
// ============================================================================
|
|
1131
|
-
//
|
|
1157
|
+
// Channel Envelope Stripping (v2.0.6)
|
|
1132
1158
|
// ============================================================================
|
|
1133
1159
|
|
|
1134
|
-
/**
|
|
1135
|
-
|
|
1160
|
+
/**
|
|
1161
|
+
* Strip OpenClaw channel envelope from message text.
|
|
1162
|
+
* Matches the pattern: [TIMESTAMP] or [CHANNEL TIMESTAMP] prefix
|
|
1163
|
+
* that OpenClaw adds to inbound messages from all channels.
|
|
1164
|
+
* Based on OpenClaw's internal stripEnvelope() logic.
|
|
1165
|
+
*/
|
|
1166
|
+
const ENVELOPE_PREFIX_RE = /^\[([^\]]+)\]\s*/;
|
|
1167
|
+
const ENVELOPE_CHANNELS = [
|
|
1168
|
+
"WebChat", "WhatsApp", "Telegram", "Signal", "Slack",
|
|
1169
|
+
"Discord", "Google Chat", "iMessage", "Teams", "Matrix",
|
|
1170
|
+
"Zalo", "Zalo Personal", "BlueBubbles",
|
|
1171
|
+
];
|
|
1172
|
+
|
|
1173
|
+
function looksLikeEnvelopeHeader(header: string): boolean {
|
|
1174
|
+
// ISO timestamp pattern
|
|
1175
|
+
if (/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z\b/.test(header)) return true;
|
|
1176
|
+
// Space-separated timestamp
|
|
1177
|
+
if (/\d{4}-\d{2}-\d{2} \d{2}:\d{2}\b/.test(header)) return true;
|
|
1178
|
+
// Channel prefix
|
|
1179
|
+
return ENVELOPE_CHANNELS.some(ch => header.startsWith(`${ch} `));
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
export function stripChannelEnvelope(text: string): string {
|
|
1183
|
+
const match = text.match(ENVELOPE_PREFIX_RE);
|
|
1184
|
+
if (!match) return text;
|
|
1185
|
+
if (!looksLikeEnvelopeHeader(match[1] ?? "")) return text;
|
|
1186
|
+
return text.slice(match[0].length);
|
|
1187
|
+
}
|
|
1136
1188
|
|
|
1137
1189
|
/**
|
|
1138
|
-
*
|
|
1139
|
-
*
|
|
1190
|
+
* Strip "System: [timestamp] Channel message in #channel from User: " prefix.
|
|
1191
|
+
* OpenClaw wraps inbound messages with this pattern for all channel providers.
|
|
1140
1192
|
*/
|
|
1141
|
-
|
|
1142
|
-
let cleaned = text;
|
|
1143
|
-
|
|
1144
|
-
// Remove JSON code blocks (```json ... ```)
|
|
1145
|
-
cleaned = cleaned.replace(/```json[\s\S]*?```/gi, "");
|
|
1146
|
-
|
|
1147
|
-
// Remove lines starting with system markers
|
|
1148
|
-
cleaned = cleaned
|
|
1149
|
-
.split("\n")
|
|
1150
|
-
.filter((line) => {
|
|
1151
|
-
const trimmed = line.trimStart();
|
|
1152
|
-
if (trimmed.startsWith("System:")) return false;
|
|
1153
|
-
if (trimmed.startsWith("[Queued")) return false;
|
|
1154
|
-
if (trimmed.startsWith("[Inter-session")) return false;
|
|
1155
|
-
for (const prefix of DAY_PREFIXES) {
|
|
1156
|
-
if (trimmed.startsWith(prefix)) return false;
|
|
1157
|
-
}
|
|
1158
|
-
return true;
|
|
1159
|
-
})
|
|
1160
|
-
.join("\n")
|
|
1161
|
-
.trim();
|
|
1193
|
+
const SYSTEM_PREFIX_RE = /^System:\s*\[\d{4}-\d{2}-\d{2}[^\]]*\]\s*(?:Slack message|Telegram message|Discord message|WhatsApp message|Signal message|message).*?(?:from \w+:\s*)?/i;
|
|
1162
1194
|
|
|
1163
|
-
|
|
1195
|
+
export function stripSystemPrefix(text: string): string {
|
|
1196
|
+
const match = text.match(SYSTEM_PREFIX_RE);
|
|
1197
|
+
if (match) return text.slice(match[0].length);
|
|
1198
|
+
return text;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// ============================================================================
|
|
1202
|
+
// Recall Query Builder (v2.0.6: envelope-aware, provenance-based)
|
|
1203
|
+
// ============================================================================
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* Messages that are purely system content (no user text).
|
|
1207
|
+
* Used to skip edited notifications, sync events, inter-session messages, etc.
|
|
1208
|
+
*/
|
|
1209
|
+
function isSystemOnlyContent(text: string): boolean {
|
|
1210
|
+
if (!text) return true;
|
|
1211
|
+
if (text.startsWith("System:")) return true;
|
|
1212
|
+
if (text.startsWith("[Queued")) return true;
|
|
1213
|
+
if (text.startsWith("[Inter-session")) return true;
|
|
1214
|
+
if (/^Slack message (edited|deleted)/.test(text)) return true;
|
|
1215
|
+
if (/^\[auto\]/.test(text)) return true;
|
|
1216
|
+
if (text.length < 3) return true;
|
|
1217
|
+
return false;
|
|
1164
1218
|
}
|
|
1165
1219
|
|
|
1166
1220
|
/**
|
|
1167
1221
|
* Build a recall query from message history.
|
|
1168
1222
|
*
|
|
1169
|
-
*
|
|
1170
|
-
* -
|
|
1171
|
-
*
|
|
1172
|
-
* - Strips system markers, JSON blocks, and other noise.
|
|
1173
|
-
* - Hard-caps at 500 characters.
|
|
1223
|
+
* v2.0.6: Strips OpenClaw channel envelopes (System: [...] Slack message from ...:)
|
|
1224
|
+
* and inter-session prefixes before building the query. This prevents envelope
|
|
1225
|
+
* metadata from polluting semantic search and causing timeouts / false-high scores.
|
|
1174
1226
|
*
|
|
1175
|
-
*
|
|
1227
|
+
* - Filters out inter_session and internal_system provenance messages.
|
|
1228
|
+
* - Falls back to any user message for backward compat (OpenClaw without provenance).
|
|
1229
|
+
* - Strips channel envelopes and system prefixes from message text.
|
|
1230
|
+
* - Skips system-only content (edited notifications, sync events).
|
|
1231
|
+
* - Short messages (< 30 chars): prepends previous for context.
|
|
1232
|
+
* - Hard-caps at 500 characters.
|
|
1176
1233
|
*/
|
|
1177
1234
|
export function buildRecallQuery(messages: unknown[]): string {
|
|
1178
1235
|
const texts = extractMessageTexts(messages);
|
|
1179
|
-
const userMessages: string[] = [];
|
|
1180
|
-
for (let i = texts.length - 1; i >= 0 && userMessages.length < 2; i--) {
|
|
1181
|
-
if (texts[i].role === "user") {
|
|
1182
|
-
const cleaned = cleanMessageForQuery(texts[i].text);
|
|
1183
|
-
if (cleaned) userMessages.unshift(cleaned);
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
1236
|
|
|
1187
|
-
|
|
1237
|
+
// Step 1: Filter out inter_session messages (sub-agent results, sessions_send)
|
|
1238
|
+
const candidates = texts.filter(
|
|
1239
|
+
t => t.role === "user" && t.provenance !== "inter_session" && t.provenance !== "internal_system"
|
|
1240
|
+
);
|
|
1241
|
+
|
|
1242
|
+
// Fallback: if no messages without provenance, use all user messages
|
|
1243
|
+
const userMsgs = candidates.length > 0
|
|
1244
|
+
? candidates
|
|
1245
|
+
: texts.filter(t => t.role === "user");
|
|
1188
1246
|
|
|
1189
|
-
|
|
1247
|
+
if (userMsgs.length === 0) return "";
|
|
1190
1248
|
|
|
1191
|
-
//
|
|
1192
|
-
let
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1249
|
+
// Step 2: Strip envelopes from the last user message(s)
|
|
1250
|
+
let lastText = stripSystemPrefix(stripChannelEnvelope(userMsgs[userMsgs.length - 1].text.trim()));
|
|
1251
|
+
|
|
1252
|
+
// Skip system-only messages (edited notifications, sync events, etc.)
|
|
1253
|
+
// Walk backwards to find a message with actual content
|
|
1254
|
+
let idx = userMsgs.length - 1;
|
|
1255
|
+
while (idx >= 0 && (!lastText || isSystemOnlyContent(lastText))) {
|
|
1256
|
+
idx--;
|
|
1257
|
+
if (idx >= 0) {
|
|
1258
|
+
lastText = stripSystemPrefix(stripChannelEnvelope(userMsgs[idx].text.trim()));
|
|
1259
|
+
}
|
|
1197
1260
|
}
|
|
1198
1261
|
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1262
|
+
if (!lastText) return "";
|
|
1263
|
+
|
|
1264
|
+
// Step 3: Short messages → include previous for context
|
|
1265
|
+
if (lastText.length < 30 && idx > 0) {
|
|
1266
|
+
const prevText = stripSystemPrefix(stripChannelEnvelope(userMsgs[idx - 1].text.trim()));
|
|
1267
|
+
if (prevText && !isSystemOnlyContent(prevText)) {
|
|
1268
|
+
return `${prevText} ${lastText}`.slice(0, 500);
|
|
1269
|
+
}
|
|
1202
1270
|
}
|
|
1203
1271
|
|
|
1204
|
-
return
|
|
1272
|
+
return lastText.slice(0, 500);
|
|
1205
1273
|
}
|
|
1206
1274
|
|
|
1207
1275
|
// ============================================================================
|
|
@@ -1304,22 +1372,27 @@ export function resetTurnState(): void {
|
|
|
1304
1372
|
* Register lifecycle hooks on the plugin API.
|
|
1305
1373
|
*/
|
|
1306
1374
|
export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
1375
|
+
// Store api.logger for module-wide use (integrates into OpenClaw log system)
|
|
1376
|
+
if (api.logger && typeof api.logger.info === "function") {
|
|
1377
|
+
logger = api.logger;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1307
1380
|
const opts = buildRunnerOpts(config);
|
|
1308
1381
|
|
|
1309
|
-
// ── Startup checks (H-2, H-3)
|
|
1382
|
+
// ── Startup checks (H-2, H-3, captureModel validation) ────────
|
|
1310
1383
|
(async () => {
|
|
1311
1384
|
// H-2: Warn if no agent is configured
|
|
1312
1385
|
if (!process.env.PALAIA_AGENT) {
|
|
1313
1386
|
try {
|
|
1314
1387
|
const statusOut = await run(["config", "get", "agent"], { ...opts, timeoutMs: 3000 });
|
|
1315
1388
|
if (!statusOut.trim()) {
|
|
1316
|
-
|
|
1389
|
+
logger.warn(
|
|
1317
1390
|
"[palaia] No agent configured. Set PALAIA_AGENT env var or run 'palaia init --agent <name>'. " +
|
|
1318
1391
|
"Auto-captured entries will have no agent attribution."
|
|
1319
1392
|
);
|
|
1320
1393
|
}
|
|
1321
1394
|
} catch {
|
|
1322
|
-
|
|
1395
|
+
logger.warn(
|
|
1323
1396
|
"[palaia] No agent configured. Set PALAIA_AGENT env var or run 'palaia init --agent <name>'. " +
|
|
1324
1397
|
"Auto-captured entries will have no agent attribution."
|
|
1325
1398
|
);
|
|
@@ -1346,7 +1419,7 @@ export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
|
1346
1419
|
|| status.config?.embedding_provider
|
|
1347
1420
|
);
|
|
1348
1421
|
if (!hasSemanticProvider && !hasProviderConfig) {
|
|
1349
|
-
|
|
1422
|
+
logger.warn(
|
|
1350
1423
|
"[palaia] No embedding provider configured. Semantic search is inactive (BM25 keyword-only). " +
|
|
1351
1424
|
"Run 'pip install palaia[fastembed]' and 'palaia doctor --fix' for better recall quality."
|
|
1352
1425
|
);
|
|
@@ -1356,6 +1429,19 @@ export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
|
1356
1429
|
} catch {
|
|
1357
1430
|
// Non-fatal — status check failed, skip warning (avoid false positive)
|
|
1358
1431
|
}
|
|
1432
|
+
|
|
1433
|
+
// Validate captureModel auth at plugin startup via modelAuth API
|
|
1434
|
+
if (config.captureModel && api.runtime?.modelAuth) {
|
|
1435
|
+
try {
|
|
1436
|
+
const resolved = resolveCaptureModel(api.config, config.captureModel);
|
|
1437
|
+
if (resolved?.provider) {
|
|
1438
|
+
const key = await api.runtime.modelAuth.resolveApiKeyForProvider({ provider: resolved.provider, cfg: api.config });
|
|
1439
|
+
if (!key) {
|
|
1440
|
+
logger.warn(`[palaia] captureModel provider "${resolved.provider}" has no API key — auto-capture LLM extraction will fail`);
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
} catch { /* non-fatal */ }
|
|
1444
|
+
}
|
|
1359
1445
|
})();
|
|
1360
1446
|
|
|
1361
1447
|
// ── /palaia status command ─────────────────────────────────────
|
|
@@ -1445,7 +1531,7 @@ export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
|
1445
1531
|
entries = result.results;
|
|
1446
1532
|
}
|
|
1447
1533
|
} catch (queryError) {
|
|
1448
|
-
|
|
1534
|
+
logger.warn(`[palaia] Query recall failed, falling back to list: ${queryError}`);
|
|
1449
1535
|
}
|
|
1450
1536
|
}
|
|
1451
1537
|
}
|
|
@@ -1556,7 +1642,7 @@ export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
|
1556
1642
|
: undefined,
|
|
1557
1643
|
};
|
|
1558
1644
|
} catch (error) {
|
|
1559
|
-
|
|
1645
|
+
logger.warn(`[palaia] Memory injection failed: ${error}`);
|
|
1560
1646
|
}
|
|
1561
1647
|
});
|
|
1562
1648
|
}
|
|
@@ -1669,7 +1755,7 @@ export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
|
1669
1755
|
(p) => p.name.toLowerCase() === validatedProject!.toLowerCase(),
|
|
1670
1756
|
);
|
|
1671
1757
|
if (!isKnown) {
|
|
1672
|
-
|
|
1758
|
+
logger.info(`[palaia] Auto-capture: unknown project "${validatedProject}" ignored`);
|
|
1673
1759
|
validatedProject = null;
|
|
1674
1760
|
}
|
|
1675
1761
|
}
|
|
@@ -1686,7 +1772,7 @@ export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
|
1686
1772
|
effectiveScope,
|
|
1687
1773
|
);
|
|
1688
1774
|
await run(args, { ...opts, timeoutMs: 10_000 });
|
|
1689
|
-
|
|
1775
|
+
logger.info(
|
|
1690
1776
|
`[palaia] LLM auto-captured: type=${r.type}, significance=${r.significance}, tags=${tags.join(",")}, project=${validatedProject || "none"}, scope=${effectiveScope || "team"}`
|
|
1691
1777
|
);
|
|
1692
1778
|
}
|
|
@@ -1711,7 +1797,7 @@ export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
|
1711
1797
|
// captureModel is broken — try primary model as fallback
|
|
1712
1798
|
if (!_captureModelFailoverWarned) {
|
|
1713
1799
|
_captureModelFailoverWarned = true;
|
|
1714
|
-
|
|
1800
|
+
logger.warn(`[palaia] WARNING: captureModel failed (${errStr}). Using primary model as fallback. Please update captureModel in your config.`);
|
|
1715
1801
|
}
|
|
1716
1802
|
try {
|
|
1717
1803
|
// Retry without captureModel → resolveCaptureModel will use primary model
|
|
@@ -1722,13 +1808,13 @@ export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
|
1722
1808
|
llmHandled = true;
|
|
1723
1809
|
} catch (fallbackError) {
|
|
1724
1810
|
if (!_llmImportFailureLogged) {
|
|
1725
|
-
|
|
1811
|
+
logger.warn(`[palaia] LLM extraction failed (primary model fallback also failed): ${fallbackError}`);
|
|
1726
1812
|
_llmImportFailureLogged = true;
|
|
1727
1813
|
}
|
|
1728
1814
|
}
|
|
1729
1815
|
} else {
|
|
1730
1816
|
if (!_llmImportFailureLogged) {
|
|
1731
|
-
|
|
1817
|
+
logger.warn(`[palaia] LLM extraction failed, using rule-based fallback: ${llmError}`);
|
|
1732
1818
|
_llmImportFailureLogged = true;
|
|
1733
1819
|
}
|
|
1734
1820
|
}
|
|
@@ -1770,7 +1856,7 @@ export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
|
1770
1856
|
);
|
|
1771
1857
|
|
|
1772
1858
|
await run(args, { ...opts, timeoutMs: 10_000 });
|
|
1773
|
-
|
|
1859
|
+
logger.info(
|
|
1774
1860
|
`[palaia] Rule-based auto-captured: type=${captureData.type}, tags=${captureData.tags.join(",")}`
|
|
1775
1861
|
);
|
|
1776
1862
|
}
|
|
@@ -1782,7 +1868,7 @@ export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
|
1782
1868
|
} else {
|
|
1783
1869
|
}
|
|
1784
1870
|
} catch (error) {
|
|
1785
|
-
|
|
1871
|
+
logger.warn(`[palaia] Auto-capture failed: ${error}`);
|
|
1786
1872
|
}
|
|
1787
1873
|
|
|
1788
1874
|
// ── Emoji Reactions (Issue #87) ──────────────────────────
|
|
@@ -1814,7 +1900,7 @@ export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
|
1814
1900
|
}
|
|
1815
1901
|
}
|
|
1816
1902
|
} catch (reactionError) {
|
|
1817
|
-
|
|
1903
|
+
logger.warn(`[palaia] Reaction sending failed: ${reactionError}`);
|
|
1818
1904
|
} finally {
|
|
1819
1905
|
// Always clean up turn state
|
|
1820
1906
|
deleteTurnState(sessionKey);
|
|
@@ -1844,7 +1930,7 @@ export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
|
1844
1930
|
}
|
|
1845
1931
|
}
|
|
1846
1932
|
} catch (err) {
|
|
1847
|
-
|
|
1933
|
+
logger.warn(`[palaia] Recall reaction failed: ${err}`);
|
|
1848
1934
|
} finally {
|
|
1849
1935
|
deleteTurnState(sessionKey);
|
|
1850
1936
|
}
|
|
@@ -1857,10 +1943,10 @@ export function registerHooks(api: any, config: PalaiaPluginConfig): void {
|
|
|
1857
1943
|
start: async () => {
|
|
1858
1944
|
const result = await recover(opts);
|
|
1859
1945
|
if (result.replayed > 0) {
|
|
1860
|
-
|
|
1946
|
+
logger.info(`[palaia] WAL recovery: replayed ${result.replayed} entries`);
|
|
1861
1947
|
}
|
|
1862
1948
|
if (result.errors > 0) {
|
|
1863
|
-
|
|
1949
|
+
logger.warn(`[palaia] WAL recovery completed with ${result.errors} error(s)`);
|
|
1864
1950
|
}
|
|
1865
1951
|
},
|
|
1866
1952
|
});
|