arisa 2.3.51 → 2.3.52
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/core/index.ts +8 -28
- package/src/core/processor.ts +2 -5
- package/src/core/history.ts +0 -193
package/package.json
CHANGED
package/src/core/index.ts
CHANGED
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
import { transcribeAudio, describeImage, generateSpeech, isMediaConfigured, isSpeechConfigured } from "./media";
|
|
28
28
|
import { detectFiles } from "./file-detector";
|
|
29
29
|
|
|
30
|
-
import { addExchange, getForeignContext, clearHistory, getLastBackend } from "./history";
|
|
31
30
|
import { getOnboarding, checkDeps } from "./onboarding";
|
|
32
31
|
import { initScheduler, addTask, cancelAllChatTasks } from "./scheduler";
|
|
33
32
|
import { detectScheduleIntent } from "./intent";
|
|
@@ -60,13 +59,6 @@ function getBackend(chatId: string): "claude" | "codex" {
|
|
|
60
59
|
const current = backendState.get(chatId);
|
|
61
60
|
if (current) return preferInstalled(current);
|
|
62
61
|
|
|
63
|
-
const fromHistory = getLastBackend(chatId);
|
|
64
|
-
if (fromHistory) {
|
|
65
|
-
const resolved = preferInstalled(fromHistory);
|
|
66
|
-
backendState.set(chatId, resolved);
|
|
67
|
-
return resolved;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
62
|
return preferInstalled(defaultBackend());
|
|
71
63
|
}
|
|
72
64
|
|
|
@@ -164,7 +156,6 @@ ${messageText}`;
|
|
|
164
156
|
if (msg.command === "/reset") {
|
|
165
157
|
const { writeFileSync } = await import("fs");
|
|
166
158
|
writeFileSync(config.resetFlagPath, "reset");
|
|
167
|
-
clearHistory(msg.chatId);
|
|
168
159
|
const { resetRouterState } = await import("./router");
|
|
169
160
|
resetRouterState();
|
|
170
161
|
const response: CoreResponse = { text: "Conversation reset! Next message will start a fresh conversation." };
|
|
@@ -361,27 +352,22 @@ ${messageText}`;
|
|
|
361
352
|
const backend = getBackend(msg.chatId);
|
|
362
353
|
const canFallback = backend === "codex" ? deps.claude : deps.codex;
|
|
363
354
|
let agentResponse: string;
|
|
364
|
-
let historyResponse: string | null = null;
|
|
365
355
|
let usedBackend: "claude" | "codex" = backend;
|
|
366
356
|
|
|
367
|
-
|
|
368
|
-
const foreignCtx = getForeignContext(msg.chatId, backend);
|
|
369
|
-
const enrichedMessage = foreignCtx ? foreignCtx + messageText : messageText;
|
|
370
|
-
|
|
371
|
-
log.info(`Routing | backend: ${backend} | foreignCtx: ${!!foreignCtx} | enrichedChars: ${enrichedMessage.length}`);
|
|
357
|
+
log.info(`Routing | backend: ${backend} | messageChars: ${messageText.length}`);
|
|
372
358
|
|
|
373
359
|
if (backend === "codex") {
|
|
374
360
|
try {
|
|
375
|
-
agentResponse = await processWithCodex(
|
|
361
|
+
agentResponse = await processWithCodex(messageText);
|
|
376
362
|
if (agentResponse.startsWith("Error processing with Codex") && canFallback) {
|
|
377
363
|
log.warn("Codex failed, falling back to Claude");
|
|
378
|
-
agentResponse = await processWithClaude(
|
|
364
|
+
agentResponse = await processWithClaude(messageText, msg.chatId);
|
|
379
365
|
usedBackend = "claude";
|
|
380
366
|
}
|
|
381
367
|
} catch (error) {
|
|
382
368
|
if (canFallback) {
|
|
383
369
|
log.warn(`Codex threw, falling back to Claude: ${error}`);
|
|
384
|
-
agentResponse = await processWithClaude(
|
|
370
|
+
agentResponse = await processWithClaude(messageText, msg.chatId);
|
|
385
371
|
usedBackend = "claude";
|
|
386
372
|
} else {
|
|
387
373
|
agentResponse = "Error processing with Codex. Please try again.";
|
|
@@ -389,23 +375,20 @@ ${messageText}`;
|
|
|
389
375
|
}
|
|
390
376
|
} else {
|
|
391
377
|
try {
|
|
392
|
-
agentResponse = await processWithClaude(
|
|
378
|
+
agentResponse = await processWithClaude(messageText, msg.chatId);
|
|
393
379
|
if (agentResponse.startsWith("Error:") && canFallback) {
|
|
394
380
|
log.warn("Claude failed, falling back to Codex");
|
|
395
|
-
agentResponse = await processWithCodex(
|
|
381
|
+
agentResponse = await processWithCodex(messageText);
|
|
396
382
|
usedBackend = "codex";
|
|
397
383
|
}
|
|
398
384
|
if (isClaudeRateLimitResponse(agentResponse) && canFallback) {
|
|
399
385
|
log.warn("Claude credits exhausted, falling back to Codex");
|
|
400
|
-
const codexResponse = await processWithCodex(
|
|
386
|
+
const codexResponse = await processWithCodex(messageText);
|
|
401
387
|
if (isCodexAuthRequiredResponse(codexResponse)) {
|
|
402
388
|
agentResponse = `${agentResponse}\n---CHUNK---\n${codexResponse}`;
|
|
403
389
|
} else {
|
|
404
390
|
agentResponse = `Claude is out of credits right now, so I switched this reply to Codex.\n---CHUNK---\n${codexResponse}`;
|
|
405
|
-
historyResponse = codexResponse;
|
|
406
391
|
usedBackend = "codex";
|
|
407
|
-
// Persist the switch so subsequent messages don't keep re-injecting
|
|
408
|
-
// cross-backend context while Claude has no credits.
|
|
409
392
|
backendState.set(msg.chatId, "codex");
|
|
410
393
|
}
|
|
411
394
|
}
|
|
@@ -413,7 +396,7 @@ ${messageText}`;
|
|
|
413
396
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
414
397
|
if (canFallback) {
|
|
415
398
|
log.warn(`Claude threw, falling back to Codex: ${errMsg}`);
|
|
416
|
-
agentResponse = await processWithCodex(
|
|
399
|
+
agentResponse = await processWithCodex(messageText);
|
|
417
400
|
usedBackend = "codex";
|
|
418
401
|
} else {
|
|
419
402
|
agentResponse = `Claude error: ${errMsg.slice(0, 200)}`;
|
|
@@ -421,9 +404,6 @@ ${messageText}`;
|
|
|
421
404
|
}
|
|
422
405
|
}
|
|
423
406
|
|
|
424
|
-
// Log exchange for shared history
|
|
425
|
-
addExchange(msg.chatId, messageText, historyResponse ?? agentResponse, usedBackend);
|
|
426
|
-
|
|
427
407
|
log.info(`Response | backend: ${usedBackend} | responseChars: ${agentResponse.length}`);
|
|
428
408
|
log.debug(`Response raw >>>>\n${agentResponse}\n<<<<`);
|
|
429
409
|
|
package/src/core/processor.ts
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { selectModel } from "./router";
|
|
15
|
-
import { getRecentHistory } from "./history";
|
|
16
15
|
import { shouldContinue } from "./context";
|
|
17
16
|
import { config } from "../shared/config";
|
|
18
17
|
import { createLogger } from "../shared/logger";
|
|
@@ -127,12 +126,10 @@ async function processNext() {
|
|
|
127
126
|
|
|
128
127
|
async function runClaude(message: string, chatId: string): Promise<string> {
|
|
129
128
|
const model = selectModel(message);
|
|
130
|
-
const historyContext = getRecentHistory(chatId);
|
|
131
129
|
const start = Date.now();
|
|
132
|
-
const prompt = withSoul(
|
|
130
|
+
const prompt = withSoul(message);
|
|
133
131
|
|
|
134
|
-
|
|
135
|
-
log.info(`Model: ${model.model} (${model.reason}) | History: ${historyCount} exchanges`);
|
|
132
|
+
log.info(`Model: ${model.model} (${model.reason})`);
|
|
136
133
|
|
|
137
134
|
const args = ["--dangerously-skip-permissions", "--output-format", "text"];
|
|
138
135
|
|
package/src/core/history.ts
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module core/history
|
|
3
|
-
* @role Shared conversation history across backends (Claude/Codex).
|
|
4
|
-
* @responsibilities
|
|
5
|
-
* - Log each user↔backend exchange with backend tag
|
|
6
|
-
* - Provide "foreign" context: exchanges from the OTHER backend
|
|
7
|
-
* that the current backend hasn't seen
|
|
8
|
-
* - Persist to disk, load on startup
|
|
9
|
-
* @dependencies shared/config
|
|
10
|
-
* @effects Reads/writes runtime history.jsonl
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { existsSync, readFileSync, appendFileSync, writeFileSync, mkdirSync } from "fs";
|
|
14
|
-
import { join, dirname } from "path";
|
|
15
|
-
import { config } from "../shared/config";
|
|
16
|
-
import { createLogger } from "../shared/logger";
|
|
17
|
-
|
|
18
|
-
const log = createLogger("core");
|
|
19
|
-
|
|
20
|
-
const HISTORY_PATH = join(config.arisaDir, "history.jsonl");
|
|
21
|
-
const MAX_ENTRIES_PER_CHAT = 50;
|
|
22
|
-
const FOREIGN_CONTEXT_MAX_AGE_MS = 30 * 60 * 1000;
|
|
23
|
-
const CODEX_SWITCH_NOTICE = "Claude is out of credits right now, so I switched this reply to Codex.";
|
|
24
|
-
|
|
25
|
-
interface Exchange {
|
|
26
|
-
ts: number;
|
|
27
|
-
chatId: string;
|
|
28
|
-
user: string;
|
|
29
|
-
response: string;
|
|
30
|
-
backend: "claude" | "codex";
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let history: Exchange[] = [];
|
|
34
|
-
|
|
35
|
-
// Load persisted history on import
|
|
36
|
-
try {
|
|
37
|
-
if (existsSync(HISTORY_PATH)) {
|
|
38
|
-
const lines = readFileSync(HISTORY_PATH, "utf8").split("\n").filter(Boolean);
|
|
39
|
-
history = lines.map((l) => {
|
|
40
|
-
const entry = JSON.parse(l) as Exchange;
|
|
41
|
-
return { ...entry, response: normalizeResponse(entry.response) };
|
|
42
|
-
});
|
|
43
|
-
log.info(`Loaded ${history.length} history entries`);
|
|
44
|
-
}
|
|
45
|
-
} catch (e) {
|
|
46
|
-
log.warn(`Failed to load history: ${e}`);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function addExchange(
|
|
50
|
-
chatId: string,
|
|
51
|
-
user: string,
|
|
52
|
-
response: string,
|
|
53
|
-
backend: "claude" | "codex",
|
|
54
|
-
) {
|
|
55
|
-
const normalizedResponse = normalizeResponse(response);
|
|
56
|
-
const entry: Exchange = { ts: Date.now(), chatId, user, response: normalizedResponse, backend };
|
|
57
|
-
history.push(entry);
|
|
58
|
-
|
|
59
|
-
// Prune old entries per chat
|
|
60
|
-
const chatEntries = history.filter((e) => e.chatId === chatId);
|
|
61
|
-
if (chatEntries.length > MAX_ENTRIES_PER_CHAT) {
|
|
62
|
-
const toRemove = chatEntries.length - MAX_ENTRIES_PER_CHAT;
|
|
63
|
-
let removed = 0;
|
|
64
|
-
history = history.filter((e) => {
|
|
65
|
-
if (e.chatId === chatId && removed < toRemove) {
|
|
66
|
-
removed++;
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
return true;
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Persist
|
|
74
|
-
try {
|
|
75
|
-
const dir = dirname(HISTORY_PATH);
|
|
76
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
77
|
-
// Rewrite full file after prune to keep it clean
|
|
78
|
-
writeFileSync(HISTORY_PATH, history.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
79
|
-
} catch (e) {
|
|
80
|
-
log.warn(`Failed to persist history: ${e}`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Returns context string with exchanges from the OTHER backend
|
|
86
|
-
* that happened since the current backend was last used.
|
|
87
|
-
* Returns empty string if there's nothing to inject.
|
|
88
|
-
*/
|
|
89
|
-
export function getForeignContext(
|
|
90
|
-
chatId: string,
|
|
91
|
-
currentBackend: "claude" | "codex",
|
|
92
|
-
limit = 10,
|
|
93
|
-
): string {
|
|
94
|
-
const chatHistory = history.filter((e) => e.chatId === chatId);
|
|
95
|
-
if (chatHistory.length === 0) return "";
|
|
96
|
-
|
|
97
|
-
// Find last exchange handled by current backend
|
|
98
|
-
let lastOwnIdx = -1;
|
|
99
|
-
for (let i = chatHistory.length - 1; i >= 0; i--) {
|
|
100
|
-
if (chatHistory[i].backend === currentBackend) {
|
|
101
|
-
lastOwnIdx = i;
|
|
102
|
-
break;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const cutoff = Date.now() - FOREIGN_CONTEXT_MAX_AGE_MS;
|
|
107
|
-
|
|
108
|
-
// Get foreign exchanges since then
|
|
109
|
-
const foreign = chatHistory
|
|
110
|
-
.slice(lastOwnIdx + 1)
|
|
111
|
-
.filter((e) => e.backend !== currentBackend && e.ts >= cutoff);
|
|
112
|
-
|
|
113
|
-
if (foreign.length === 0) return "";
|
|
114
|
-
|
|
115
|
-
const otherName = currentBackend === "claude" ? "Codex" : "Claude";
|
|
116
|
-
const lines = foreign
|
|
117
|
-
.slice(-limit)
|
|
118
|
-
.map((e) => `User: ${e.user}\n${otherName}: ${normalizeResponse(e.response)}`)
|
|
119
|
-
.join("\n\n");
|
|
120
|
-
|
|
121
|
-
return `[Contexto previo con ${otherName}]\n${lines}\n[Fin del contexto previo]\n\n`;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Returns recent conversation history for this chat, formatted as User/Assistant pairs.
|
|
126
|
-
* Trims oldest entries first if total exceeds maxChars.
|
|
127
|
-
* Returns "" for new conversations.
|
|
128
|
-
*/
|
|
129
|
-
export function getRecentHistory(
|
|
130
|
-
chatId: string,
|
|
131
|
-
limit = 10,
|
|
132
|
-
maxChars = 8000,
|
|
133
|
-
): string {
|
|
134
|
-
const chatHistory = history.filter((e) => e.chatId === chatId);
|
|
135
|
-
if (chatHistory.length === 0) return "";
|
|
136
|
-
|
|
137
|
-
const recent = chatHistory.slice(-limit);
|
|
138
|
-
|
|
139
|
-
// Format exchanges
|
|
140
|
-
const formatted = recent.map(
|
|
141
|
-
(e) => `User: ${e.user}\nAssistant: ${normalizeResponse(e.response)}`,
|
|
142
|
-
);
|
|
143
|
-
|
|
144
|
-
// Trim oldest entries if total exceeds maxChars
|
|
145
|
-
let total = formatted.join("\n\n").length;
|
|
146
|
-
while (formatted.length > 1 && total > maxChars) {
|
|
147
|
-
formatted.shift();
|
|
148
|
-
total = formatted.join("\n\n").length;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (formatted.length === 0) return "";
|
|
152
|
-
|
|
153
|
-
return `[Conversation history]\n${formatted.join("\n\n")}\n[End of conversation history]\n\n`;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Removes all history entries for this chat from memory and disk.
|
|
158
|
-
*/
|
|
159
|
-
export function clearHistory(chatId: string): void {
|
|
160
|
-
const before = history.length;
|
|
161
|
-
history = history.filter((e) => e.chatId !== chatId);
|
|
162
|
-
const removed = before - history.length;
|
|
163
|
-
|
|
164
|
-
if (removed > 0) {
|
|
165
|
-
log.info(`Cleared ${removed} history entries for chat ${chatId}`);
|
|
166
|
-
try {
|
|
167
|
-
writeFileSync(HISTORY_PATH, history.map((e) => JSON.stringify(e)).join("\n") + "\n");
|
|
168
|
-
} catch (e) {
|
|
169
|
-
log.warn(`Failed to persist history after clear: ${e}`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function getLastBackend(chatId: string): "claude" | "codex" | null {
|
|
175
|
-
for (let i = history.length - 1; i >= 0; i--) {
|
|
176
|
-
if (history[i].chatId === chatId) {
|
|
177
|
-
return history[i].backend;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function normalizeResponse(response: string): string {
|
|
184
|
-
const cleaned = response
|
|
185
|
-
.replace(/\n---CHUNK---\n/g, "\n")
|
|
186
|
-
.replace(new RegExp(`^${escapeRegExp(CODEX_SWITCH_NOTICE)}\\s*`, "m"), "")
|
|
187
|
-
.trim();
|
|
188
|
-
return cleaned;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function escapeRegExp(s: string): string {
|
|
192
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
193
|
-
}
|