@vascend/talos 0.1.3 → 0.1.5
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/LICENSE +22 -0
- package/README.md +49 -13
- package/dist/core/agent.js +58 -38
- package/dist/core/session-naming.d.ts +3 -0
- package/dist/core/session-naming.js +43 -0
- package/dist/core/session-usage.js +4 -1
- package/dist/core/session.d.ts +1 -0
- package/dist/core/session.js +9 -3
- package/dist/core/storage-health.d.ts +24 -0
- package/dist/core/storage-health.js +72 -0
- package/dist/core/types.d.ts +17 -0
- package/dist/tui/agent-sessions-screen.d.ts +25 -0
- package/dist/tui/agent-sessions-screen.js +132 -0
- package/dist/tui/agent-sessions.d.ts +39 -0
- package/dist/tui/agent-sessions.js +175 -0
- package/dist/tui/bottom-input.d.ts +11 -0
- package/dist/tui/bottom-input.js +106 -10
- package/dist/tui/commands.js +2 -1
- package/dist/tui/i18n-catalog.js +39 -1
- package/dist/tui/logo.js +3 -0
- package/dist/tui/model-picker.d.ts +2 -0
- package/dist/tui/model-picker.js +49 -0
- package/dist/tui/native-scrollback.d.ts +2 -1
- package/dist/tui/native-scrollback.js +26 -6
- package/dist/tui/prompt-queue-store.d.ts +1 -0
- package/dist/tui/prompt-queue-store.js +4 -0
- package/dist/tui/queued-questions.js +2 -4
- package/dist/tui/quick-help.d.ts +1 -1
- package/dist/tui/quick-help.js +3 -0
- package/dist/tui/repl.d.ts +3 -0
- package/dist/tui/repl.js +110 -24
- package/dist/tui/review-store.d.ts +7 -1
- package/dist/tui/review-store.js +36 -4
- package/dist/tui/scrollback-archive.d.ts +16 -0
- package/dist/tui/scrollback-archive.js +90 -0
- package/dist/tui/selector.d.ts +4 -0
- package/dist/tui/selector.js +9 -2
- package/dist/tui/session-naming-notice.d.ts +14 -0
- package/dist/tui/session-naming-notice.js +57 -0
- package/dist/tui/session-picker.d.ts +2 -0
- package/dist/tui/session-picker.js +32 -0
- package/dist/tui/storage-warning.d.ts +2 -0
- package/dist/tui/storage-warning.js +35 -0
- package/dist/vascend/context-feedback-loop.js +2 -0
- package/dist/vascend/context-snapshot.d.ts +1 -0
- package/dist/vascend/context-snapshot.js +4 -3
- package/dist/vascend/context-window-compiler.d.ts +1 -0
- package/dist/vascend/context-window-compiler.js +1 -0
- package/dist/vascend/granular-kingdom.js +32 -4
- package/dist/vascend/kingdom.js +4 -0
- package/dist/vascend/memory-store.d.ts +1 -0
- package/dist/vascend/memory-store.js +12 -4
- package/dist/vascend/room-memory.d.ts +3 -1
- package/dist/vascend/room-memory.js +6 -4
- package/package.json +2 -2
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// A short, non-scrolling transition from the first prompt to its saved title.
|
|
2
|
+
export class SessionNamingNotice {
|
|
3
|
+
refresh;
|
|
4
|
+
title = "";
|
|
5
|
+
startedAt = 0;
|
|
6
|
+
pending = false;
|
|
7
|
+
timer;
|
|
8
|
+
constructor(refresh) {
|
|
9
|
+
this.refresh = refresh;
|
|
10
|
+
}
|
|
11
|
+
show(title) {
|
|
12
|
+
this.clear();
|
|
13
|
+
this.title = title;
|
|
14
|
+
this.startedAt = Date.now();
|
|
15
|
+
let showingTitle = false;
|
|
16
|
+
this.timer = setInterval(() => {
|
|
17
|
+
if (!this.pending && Date.now() - this.startedAt >= 5900)
|
|
18
|
+
this.clear();
|
|
19
|
+
else if (this.pending || Date.now() - this.startedAt < 900 || !showingTitle) {
|
|
20
|
+
showingTitle = Date.now() - this.startedAt >= 900;
|
|
21
|
+
this.refresh();
|
|
22
|
+
}
|
|
23
|
+
}, 100);
|
|
24
|
+
this.timer.unref?.();
|
|
25
|
+
this.refresh();
|
|
26
|
+
}
|
|
27
|
+
start() {
|
|
28
|
+
this.show("Renaming");
|
|
29
|
+
this.pending = true;
|
|
30
|
+
}
|
|
31
|
+
finish(title) {
|
|
32
|
+
this.show(title);
|
|
33
|
+
this.startedAt = Date.now() - 900;
|
|
34
|
+
this.refresh();
|
|
35
|
+
}
|
|
36
|
+
lines(now = Date.now()) {
|
|
37
|
+
if (!this.title)
|
|
38
|
+
return [];
|
|
39
|
+
const elapsed = Math.max(0, now - this.startedAt);
|
|
40
|
+
if (!this.pending && elapsed >= 5900)
|
|
41
|
+
return [];
|
|
42
|
+
if (!this.pending && elapsed >= 900)
|
|
43
|
+
return [this.title];
|
|
44
|
+
const frame = Math.floor(elapsed / 100) % 9;
|
|
45
|
+
return Array.from({ length: 3 }, (_, row) => Array.from({ length: 3 }, (_, col) => row * 3 + col === frame ? "\u25cf" : "\u00b7").join(" ")
|
|
46
|
+
+ (row === 1 ? " Renaming" : ""));
|
|
47
|
+
}
|
|
48
|
+
line(now = Date.now()) { return this.lines(now).join("\n"); }
|
|
49
|
+
clear() {
|
|
50
|
+
if (this.timer)
|
|
51
|
+
clearInterval(this.timer);
|
|
52
|
+
this.timer = undefined;
|
|
53
|
+
this.title = "";
|
|
54
|
+
this.pending = false;
|
|
55
|
+
this.refresh();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { fitLine, oneLine, padLine, terminalWidth } from "./terminal-text.js";
|
|
2
|
+
// A dense session list leaves the conversation title most of the screen width.
|
|
3
|
+
export function renderSessionPicker(opts) {
|
|
4
|
+
const width = Math.max(1, opts.columns - 2);
|
|
5
|
+
const t = opts.theme;
|
|
6
|
+
const row = (text) => " " + fitLine(text, width);
|
|
7
|
+
const page = Math.max(1, opts.pageSize);
|
|
8
|
+
const index = Math.max(0, Math.min(opts.index, opts.options.length - 1));
|
|
9
|
+
const start = Math.max(0, Math.min(index - Math.floor(page / 2), opts.options.length - page));
|
|
10
|
+
const visible = opts.options.slice(start, start + page);
|
|
11
|
+
const filter = width >= 40 ? "[Cwd] Sort:[Updated]" : "";
|
|
12
|
+
const searchWidth = Math.max(1, width - (filter ? filter.length + 2 : 0));
|
|
13
|
+
const search = padLine(oneLine(opts.query || "Type to search"), searchWidth);
|
|
14
|
+
const lines = [row(t.accent(opts.title)), "", row(t.muted(search) + " " + t.silver(filter)), ""];
|
|
15
|
+
const ageWidth = Math.min(10, Math.max(3, Math.floor(width / 5)));
|
|
16
|
+
for (const [offset, option] of visible.entries()) {
|
|
17
|
+
const active = start + offset === index;
|
|
18
|
+
const text = (active ? "› " : " ") + padLine(option.age || "", ageWidth)
|
|
19
|
+
+ " " + oneLine(option.label);
|
|
20
|
+
lines.push(row(active ? t.selected(padLine(text, width)) : t.silver(text)));
|
|
21
|
+
}
|
|
22
|
+
if (!visible.length)
|
|
23
|
+
lines.push(row(t.muted("No matching sessions")));
|
|
24
|
+
while (lines.length < page + 4)
|
|
25
|
+
lines.push("");
|
|
26
|
+
const count = `${opts.options.length ? index + 1 : 0} / ${opts.options.length}`
|
|
27
|
+
+ (opts.totalCount !== undefined && opts.totalCount !== opts.options.length ? ` (${opts.totalCount} total)` : "");
|
|
28
|
+
lines.push(row(t.muted("─".repeat(Math.max(0, width - terminalWidth(count) - 1)) + " " + count)));
|
|
29
|
+
lines.push(row(t.silver("enter resume esc back tab details")));
|
|
30
|
+
lines.push(row(t.muted("↑/↓ browse PgUp/PgDn page ctrl+u clear search")));
|
|
31
|
+
return lines;
|
|
32
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { tr } from "./i18n.js";
|
|
2
|
+
import { wrapText } from "./terminal-text.js";
|
|
3
|
+
function reason(code) {
|
|
4
|
+
switch (code) {
|
|
5
|
+
case "ENOSPC": return tr("disco pieno", "disk full");
|
|
6
|
+
case "EDQUOT": return tr("quota disco esaurita", "disk quota exceeded");
|
|
7
|
+
case "EACCES":
|
|
8
|
+
case "EPERM": return tr("permesso negato", "permission denied");
|
|
9
|
+
case "EROFS": return tr("disco in sola lettura", "read-only filesystem");
|
|
10
|
+
case "EIO": return tr("errore di lettura/scrittura", "I/O error");
|
|
11
|
+
case "ENOENT":
|
|
12
|
+
case "ENOTDIR":
|
|
13
|
+
case "EISDIR": return tr("percorso non disponibile", "path unavailable");
|
|
14
|
+
case "EMFILE":
|
|
15
|
+
case "ENFILE": return tr("troppi file aperti", "too many open files");
|
|
16
|
+
case "EBUSY": return tr("file occupato", "file busy");
|
|
17
|
+
default: return tr("errore di salvataggio", "storage error");
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function storageWarning(issues, columns, paint = (text) => text) {
|
|
21
|
+
// A still-failing store takes precedence over the short recovery notice.
|
|
22
|
+
const issue = issues.find(item => !item.recovered && item.kind === "transcript")
|
|
23
|
+
?? issues.find(item => !item.recovered) ?? issues[0];
|
|
24
|
+
if (!issue)
|
|
25
|
+
return [];
|
|
26
|
+
const target = issue.kind === "transcript" ? tr("conversazione", "conversation")
|
|
27
|
+
: issue.kind === "queue" ? tr("coda", "queue") : tr("consumi token", "token usage");
|
|
28
|
+
const detail = reason(issue.code) + (issue.code === "UNKNOWN" ? "" : ` - ${issue.code}`);
|
|
29
|
+
const text = issue.recovered
|
|
30
|
+
? tr("Salvataggio {target} ripreso · alcuni dati precedenti potrebbero mancare", "Saving {target} resumed · some earlier data may be missing", { target })
|
|
31
|
+
: columns < 65
|
|
32
|
+
? tr("{target}: dati non salvati ({code})", "{target}: data not saved ({code})", { target, code: issue.code === "UNKNOWN" ? detail : issue.code })
|
|
33
|
+
: tr("Salvataggio {target} fallito ({reason}) · i dati recenti potrebbero non essere salvati", "Saving {target} failed ({reason}) · recent data may not be saved", { target, reason: detail });
|
|
34
|
+
return wrapText("! " + text, Math.max(1, columns - 3), true).map(row => paint(" " + row));
|
|
35
|
+
}
|
|
@@ -111,6 +111,8 @@ function snapshotFor(input) {
|
|
|
111
111
|
return input.snapshot ?? loadContextStoreSnapshot({
|
|
112
112
|
sessionDir: input.sessionDir,
|
|
113
113
|
scopeId: input.scopeId,
|
|
114
|
+
sharedProjectMemory: input.sharedProjectMemory,
|
|
115
|
+
...(input.planScopeId ? { planScopeId: input.planScopeId } : {}),
|
|
114
116
|
...(input.projectKey ? { projectKey: input.projectKey } : {}),
|
|
115
117
|
});
|
|
116
118
|
}
|
|
@@ -76,11 +76,12 @@ function legacyStoreSignature(sessionDir) {
|
|
|
76
76
|
}
|
|
77
77
|
export function loadContextStoreSnapshot(input) {
|
|
78
78
|
const indexed = input.indexed ?? process.env["TALOS_CONTEXT_INDEX"] !== "0";
|
|
79
|
-
const
|
|
80
|
-
const
|
|
79
|
+
const shared = input.sharedProjectMemory === true;
|
|
80
|
+
const key = JSON.stringify([input.sessionDir, input.scopeId, input.projectKey, input.planScopeId, indexed, shared]);
|
|
81
|
+
const projectScopes = !shared ? [] : indexed
|
|
81
82
|
? (input.projectKey ? projectMemoryScopes(input.sessionDir, input.projectKey) : []).filter((scope) => scope !== input.scopeId).sort()
|
|
82
83
|
: undefined;
|
|
83
|
-
const scopes = indexed ? [input.scopeId, ...(projectScopes ?? []), ...(input.planScopeId ? [input.planScopeId] : [])] : undefined;
|
|
84
|
+
const scopes = indexed || !shared ? [input.scopeId, ...(projectScopes ?? []), ...(input.planScopeId ? [input.planScopeId] : [])] : undefined;
|
|
84
85
|
const signature = storeSignature(input.sessionDir, scopes);
|
|
85
86
|
const previous = cache.get(key);
|
|
86
87
|
if (signature && previous?.signature === signature) {
|
|
@@ -12,6 +12,7 @@ export interface CompileInput {
|
|
|
12
12
|
readonly sessionDir: string;
|
|
13
13
|
readonly scopeId: string;
|
|
14
14
|
readonly projectKey?: string;
|
|
15
|
+
readonly sharedProjectMemory?: boolean;
|
|
15
16
|
readonly userText: string;
|
|
16
17
|
readonly retrievalQuery?: string;
|
|
17
18
|
readonly totalTokens?: number;
|
|
@@ -48,6 +48,7 @@ export function compileContext(input) {
|
|
|
48
48
|
const snapshot = input.snapshot ?? loadContextStoreSnapshot({
|
|
49
49
|
sessionDir: input.sessionDir,
|
|
50
50
|
scopeId: input.scopeId,
|
|
51
|
+
sharedProjectMemory: input.sharedProjectMemory,
|
|
51
52
|
...(input.planScopeId ? { planScopeId: input.planScopeId } : {}),
|
|
52
53
|
...(input.projectKey ? { projectKey: input.projectKey } : {}),
|
|
53
54
|
});
|
|
@@ -39,6 +39,8 @@ function nestedTaskFields(slug) {
|
|
|
39
39
|
function nestedActivity(slug, event) {
|
|
40
40
|
if (event.type === "kingdom")
|
|
41
41
|
return event;
|
|
42
|
+
if (event.type === "usage")
|
|
43
|
+
return { ...event, record: { ...event.record, actor: event.record.actor ?? slug } };
|
|
42
44
|
if (event.type === "turn_start")
|
|
43
45
|
return {
|
|
44
46
|
type: "kingdom", phase: "vassal", slug, state: "active", label: "Pianifica il task", ...nestedTaskFields(slug),
|
|
@@ -63,9 +65,13 @@ function nestedActivity(slug, event) {
|
|
|
63
65
|
};
|
|
64
66
|
}
|
|
65
67
|
if (event.type === "tool_call")
|
|
66
|
-
return {
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
return { type: "kingdom", phase: "tool", slug,
|
|
69
|
+
callId: event.call.id, tool: event.call.name, args: JSON.stringify(event.call.input).slice(0, 2400),
|
|
70
|
+
state: "start", at: Date.now() };
|
|
71
|
+
if (event.type === "tool_result")
|
|
72
|
+
return { type: "kingdom", phase: "tool", slug,
|
|
73
|
+
callId: event.result.callId, tool: "", output: event.result.output.slice(0, 12_000),
|
|
74
|
+
state: event.result.isError ? "error" : "ok", at: Date.now() };
|
|
69
75
|
if (event.type === "done")
|
|
70
76
|
return {
|
|
71
77
|
type: "kingdom", phase: "vassal", slug, state: "done", ...nestedTaskFields(slug),
|
|
@@ -173,6 +179,13 @@ export function makeAgentUnitRunner(opts) {
|
|
|
173
179
|
...(route[1] ? { taskRef: route[1] } : {}),
|
|
174
180
|
});
|
|
175
181
|
}
|
|
182
|
+
const textBuffers = new Map();
|
|
183
|
+
const flushText = (slug) => {
|
|
184
|
+
const text = textBuffers.get(slug)?.trim();
|
|
185
|
+
textBuffers.delete(slug);
|
|
186
|
+
if (text)
|
|
187
|
+
ctx.emit({ type: "kingdom", phase: "say", slug, text: text.slice(0, 12_000), at: Date.now() });
|
|
188
|
+
};
|
|
176
189
|
const report = await launchGranularKingdom({
|
|
177
190
|
...opts,
|
|
178
191
|
harness: { ...harness, config: runConfig, tools: workerTools },
|
|
@@ -182,7 +195,18 @@ export function makeAgentUnitRunner(opts) {
|
|
|
182
195
|
maxDepth,
|
|
183
196
|
markCastle: undefined,
|
|
184
197
|
onEvent: (id, event) => {
|
|
185
|
-
const
|
|
198
|
+
const slug = `${self}/${id}`;
|
|
199
|
+
if (event.type === "text") {
|
|
200
|
+
const text = (textBuffers.get(slug) ?? "") + event.delta;
|
|
201
|
+
textBuffers.set(slug, text);
|
|
202
|
+
if (text.length >= 480)
|
|
203
|
+
flushText(slug);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (event.type === "tool_call" || event.type === "turn_end" || event.type === "done" || event.type === "error"
|
|
207
|
+
|| (event.type === "kingdom" && event.phase === "session" && event.state === "despawned"))
|
|
208
|
+
flushText(slug);
|
|
209
|
+
const mapped = nestedActivity(slug, event);
|
|
186
210
|
if (mapped)
|
|
187
211
|
ctx.emit(mapped);
|
|
188
212
|
},
|
|
@@ -228,11 +252,15 @@ export function makeAgentUnitRunner(opts) {
|
|
|
228
252
|
+ (ctx.prerequisites?.length ? "\nPREREQUISITI_COMPLETATI (risultati da verificare)\n"
|
|
229
253
|
+ ctx.prerequisites.map((result) => `[${result.id}] ${result.summary}`).join("\n").slice(0, 6000) + "\n" : "") + unit.goal;
|
|
230
254
|
try {
|
|
255
|
+
sink({ type: "kingdom", phase: "session", kingdomId, sessionId: session.id,
|
|
256
|
+
slug: self, state: "spawned", goal: unit.goal, at: Date.now() });
|
|
231
257
|
await runTurn(deps, input, sink, ctx.signal);
|
|
232
258
|
}
|
|
233
259
|
finally {
|
|
234
260
|
deps.fileCoordination?.hub.clearActor(deps.fileCoordination.actor.id);
|
|
235
261
|
kingdomLive.release(kingdomId, self);
|
|
262
|
+
sink({ type: "kingdom", phase: "session", kingdomId, sessionId: session.id,
|
|
263
|
+
slug: self, state: "despawned", at: Date.now() });
|
|
236
264
|
}
|
|
237
265
|
if (ctx.signal.aborted)
|
|
238
266
|
throw new Error("interrotta");
|
package/dist/vascend/kingdom.js
CHANGED
|
@@ -774,6 +774,8 @@ async function runVassal(opts, provider, modelId, displayModel, castle, peers, i
|
|
|
774
774
|
};
|
|
775
775
|
try {
|
|
776
776
|
const role = castle.role?.trim() || "collaboratore";
|
|
777
|
+
sink({ type: "kingdom", phase: "session", kingdomId, sessionId: session.id,
|
|
778
|
+
slug: castle.slug, state: "spawned", goal: castle.goal, at: Date.now() });
|
|
777
779
|
const parent = castle.parent?.trim() || "orchestratore";
|
|
778
780
|
const instructions = (castle.area?.length ? `\nAREA_ASSEGNATA: ${castle.area.join(", ")}. Coordina modifiche fuori area prima di eseguirle.\n` : "") + (castle.instructions?.trim()
|
|
779
781
|
? `\nISTRUZIONI_AGGIUNTIVE\n${castle.instructions.trim()}\n\n`
|
|
@@ -829,6 +831,8 @@ async function runVassal(opts, provider, modelId, displayModel, castle, peers, i
|
|
|
829
831
|
// Il collaboratore non c'e' piu': la UI deve saperlo subito, altrimenti
|
|
830
832
|
// continuerebbe a offrire un campo di scrittura che non consegna nulla.
|
|
831
833
|
kingdomLive.release(kingdomId, castle.slug);
|
|
834
|
+
sink({ type: "kingdom", phase: "session", kingdomId, sessionId: session.id,
|
|
835
|
+
slug: castle.slug, state: "despawned", at: Date.now() });
|
|
832
836
|
}
|
|
833
837
|
}
|
|
834
838
|
function mandateCompletion(sessionDir, planStoreId, mandate) {
|
|
@@ -106,6 +106,8 @@ export class MemoryStore {
|
|
|
106
106
|
}
|
|
107
107
|
const n = pool.length || 1;
|
|
108
108
|
const unfiltered = !opts.domain && !opts.type && pool.length === this.events.length;
|
|
109
|
+
if (unfiltered)
|
|
110
|
+
this.ensureSearchIndex();
|
|
109
111
|
const df = unfiltered ? this.globalDf : new Map();
|
|
110
112
|
const docTokens = unfiltered ? this.docTokens : pool.map((e) => {
|
|
111
113
|
const toks = tokensForEvent(e);
|
|
@@ -218,11 +220,17 @@ export class MemoryStore {
|
|
|
218
220
|
const numeric = /^E(\d+)$/i.exec(event.id)?.[1];
|
|
219
221
|
if (numeric)
|
|
220
222
|
this.maxNumericId = Math.max(this.maxNumericId, Number(numeric));
|
|
221
|
-
const tokens = tokensForEvent(event);
|
|
222
223
|
this.events.push(event);
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
}
|
|
225
|
+
// Reading/forwarding events does not need a second full-text index. Build
|
|
226
|
+
// this local index only for search(), and extend it after concurrent appends.
|
|
227
|
+
ensureSearchIndex() {
|
|
228
|
+
for (let i = this.docTokens.length; i < this.events.length; i++) {
|
|
229
|
+
const tokens = tokensForEvent(this.events[i]);
|
|
230
|
+
this.docTokens.push(tokens);
|
|
231
|
+
for (const token of new Set(tokens))
|
|
232
|
+
this.globalDf.set(token, (this.globalDf.get(token) ?? 0) + 1);
|
|
233
|
+
}
|
|
226
234
|
}
|
|
227
235
|
}
|
|
228
236
|
function tokensForEvent(event) {
|
|
@@ -22,12 +22,14 @@ export declare const DEFAULT_RECENT_KEEP = 2;
|
|
|
22
22
|
export declare class RoomMemory {
|
|
23
23
|
private readonly file;
|
|
24
24
|
private readonly fanout;
|
|
25
|
-
private readonly
|
|
25
|
+
private readonly openMemory;
|
|
26
26
|
private records;
|
|
27
27
|
private synonyms;
|
|
28
28
|
private tokenCache;
|
|
29
29
|
private graphCache;
|
|
30
30
|
private readonly recentKeep;
|
|
31
|
+
private memoryStore?;
|
|
32
|
+
private get memory();
|
|
31
33
|
private constructor();
|
|
32
34
|
withSynonyms(groups: readonly (readonly string[])[]): this;
|
|
33
35
|
private tokensFor;
|
|
@@ -18,7 +18,7 @@ export const DEFAULT_RECENT_KEEP = 2;
|
|
|
18
18
|
export class RoomMemory {
|
|
19
19
|
file;
|
|
20
20
|
fanout;
|
|
21
|
-
|
|
21
|
+
openMemory;
|
|
22
22
|
records = [];
|
|
23
23
|
// Indice sinonimi (default; configurabile via withSynonyms) per espandere le
|
|
24
24
|
// query verso forme equivalenti IT/EN.
|
|
@@ -33,10 +33,12 @@ export class RoomMemory {
|
|
|
33
33
|
// cambiano mai corpo ne' archi dopo la scrittura).
|
|
34
34
|
graphCache = null;
|
|
35
35
|
recentKeep;
|
|
36
|
-
|
|
36
|
+
memoryStore;
|
|
37
|
+
get memory() { return this.memoryStore ??= this.openMemory(); }
|
|
38
|
+
constructor(file, fanout, openMemory) {
|
|
37
39
|
this.file = file;
|
|
38
40
|
this.fanout = fanout;
|
|
39
|
-
this.
|
|
41
|
+
this.openMemory = openMemory;
|
|
40
42
|
this.recentKeep = Math.max(0, Math.min(this.fanout - 1, DEFAULT_RECENT_KEEP));
|
|
41
43
|
}
|
|
42
44
|
// Sostituisce la tabella sinonimi (es. per dominio specifico). Ritorna this
|
|
@@ -60,7 +62,7 @@ export class RoomMemory {
|
|
|
60
62
|
}
|
|
61
63
|
static open(sessionDir, sessionId, fanout = DEFAULT_FANOUT) {
|
|
62
64
|
const file = join(sessionDir, `${sessionId}.rooms.jsonl`);
|
|
63
|
-
const mem = new RoomMemory(file, Math.max(2, fanout), MemoryStore.open(sessionDir, sessionId));
|
|
65
|
+
const mem = new RoomMemory(file, Math.max(2, fanout), () => MemoryStore.open(sessionDir, sessionId));
|
|
64
66
|
if (existsSync(file)) {
|
|
65
67
|
const raw = readFileSync(file, "utf8");
|
|
66
68
|
for (const line of raw.split("\n")) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vascend/talos",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Vascend Talos - extensible coding agent for your terminal",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@xterm/headless": "^6.0.0",
|
|
49
49
|
"typescript": "^5.7.0"
|
|
50
50
|
},
|
|
51
|
-
"license": "
|
|
51
|
+
"license": "MIT",
|
|
52
52
|
"private": false,
|
|
53
53
|
"publishConfig": {
|
|
54
54
|
"access": "public"
|