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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arisa",
3
- "version": "2.3.51",
3
+ "version": "2.3.52",
4
4
  "description": "Arisa - dynamic agent runtime with daemon/core architecture that evolves through user interaction",
5
5
  "keywords": [
6
6
  "tinyclaw",
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
- // Inject cross-backend context if switching
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(enrichedMessage);
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(enrichedMessage, msg.chatId);
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(enrichedMessage, msg.chatId);
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(enrichedMessage, msg.chatId);
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(enrichedMessage);
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(enrichedMessage);
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(enrichedMessage);
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
 
@@ -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(historyContext + message);
130
+ const prompt = withSoul(message);
133
131
 
134
- const historyCount = historyContext ? historyContext.split("\nUser: ").length - 1 : 0;
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
 
@@ -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
- }