talos-code 0.2.1 → 0.2.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,34 @@ Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/). Versions
5
5
  `src/version.ts`, and in the `talos-cli-vX.Y.Z` tag. The package is published on npm as
6
6
  [`talos-code`](https://www.npmjs.com/package/talos-code); the command is `talos`.
7
7
 
8
+ ## talos-cli-v0.2.2 — 2026-09-27
9
+
10
+ `npm i -g talos-code@0.2.2`, or `talos update` to see it.
11
+
12
+ ### Changed
13
+ - Faster turns again. The `/undo` snapshot looks at the files of a folder together instead of one by one: a turn in a
14
+ folder of 6,000 files went from 1.8 s to 1.25 s (1.4 s to 1.28 s in a git repository), on par with Pi.
15
+ - Long sessions no longer slow down at every tool result: the context archive is written once per tool exchange instead
16
+ of after every result, and is no longer read back whole at every write. At 1,350 messages, a turn with six tools
17
+ spends 24 ms on it instead of 160 ms.
18
+
19
+ ### Fixed
20
+ - A search asked in the same answer AFTER a file write could miss what that write had just changed (0.2.1: the searches
21
+ of one answer started together, before the write). Only the searches before any other tool start together now.
22
+ - Stopping a turn also stops a search that is still walking the project when ripgrep is not available.
23
+
24
+ ### Known limits
25
+ - Tested on Windows 11 x64 only; Node.js 24 (24.18 or later in the 24 line).
26
+ - Shell commands run one at a time, each in its sandbox: the safety check of each command depends on what the
27
+ previous ones did.
28
+
29
+ ### Verification
30
+ - Hermetic bench (`scripts/bench-harness.ts`, a git repository of 6,000 files, local fake provider): a turn with no
31
+ tool 1.13 s (Pi 1.23, OpenCode 2.69, Codex 0.50); six reads 1.10 s (Pi 1.23, Codex 1.98, OpenCode 3.34); four
32
+ searches 1.32 s (Pi 1.59, Codex 1.71, OpenCode 3.61).
33
+ - CLI unit suite: 1879 tests, 1875 pass, 4 skip, 0 fail. Acceptance on the built CLI in a synthetic terminal: 263/263.
34
+ The package proof (`scripts/prove-standalone.ts`): 16/16 on `talos-code-0.2.2.tgz`.
35
+
8
36
  ## talos-cli-v0.2.1 — 2026-09-26
9
37
 
10
38
  `npm i -g talos-code@0.2.1`, or `talos update` to see it.
@@ -61,9 +61,28 @@ export function withArchivableCapture(hooks, { stalePatienceMs = STALE_PATIENCE_
61
61
  if (!hooks || typeof hooks.capture !== 'function')
62
62
  return hooks;
63
63
  const capture = hooks.capture, prepare = hooks.prepare;
64
+ /*
65
+ * P18 phase 1 (owner 2026-09-26). The kernel captures at 'start', 'before-request', 'response', after EVERY tool result
66
+ * and at 'finished'; with the rule above most of those carry the prefix already archived (a tool exchange is archived
67
+ * once, when its last result is in), and each still cost the service a full reread of the archive and a sha256 of
68
+ * every message (context-desktop-service.mjs syncOriginals): ~25 ms a capture at 1,351 messages, measured with
69
+ * scripts/bench-tools.ts. A prefix with the same length, first and last message as the last one archived is not sent
70
+ * again. Any other change (longer, shorter, a message replaced) still goes to the service, which decides.
71
+ */
72
+ let archived = null;
73
+ const sameAsArchived = (rows) => archived !== null && rows.length === archived.length && rows[0] === archived.first && rows[rows.length - 1] === archived.last;
64
74
  return {
65
75
  ...hooks,
66
- capture: (input) => retryStale(() => capture({ ...input, messages: Array.isArray(input?.messages) ? archivableHistory(input.messages) : input?.messages }), stalePatienceMs),
76
+ capture: async (input) => {
77
+ if (!Array.isArray(input?.messages))
78
+ return retryStale(() => capture(input), stalePatienceMs);
79
+ const rows = archivableHistory(input.messages);
80
+ if (sameAsArchived(rows))
81
+ return undefined;
82
+ const result = await retryStale(() => capture({ ...input, messages: rows }), stalePatienceMs);
83
+ archived = { length: rows.length, first: rows[0], last: rows[rows.length - 1] };
84
+ return result;
85
+ },
67
86
  ...(typeof prepare === 'function' ? { prepare: (input) => retryStale(() => prepare(input), stalePatienceMs) } : {}),
68
87
  };
69
88
  }
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const CLI_VERSION = '0.2.1';
1
+ export const CLI_VERSION = '0.2.2';
2
2
  export const TALOS_BASE_COMMIT = '0c432153a288f64237e98403869d02d20d8fabc7';
@@ -5,7 +5,17 @@ import path from 'node:path';
5
5
  import { spawn } from 'node:child_process';
6
6
  import { resolveWorkspaceIdentity } from "../security/workspace-identity.js";
7
7
  export const CHECKPOINT_LIMITS = { maxEntries: 50_000, maxStoredBytes: 256 * 1024 * 1024, maxStoredFileBytes: 64 * 1024 * 1024 };
8
- const RACY_MARGIN_MS = 2_000, BLOB_SYNC_BATCH = 16;
8
+ const RACY_MARGIN_MS = 2_000, BLOB_SYNC_BATCH = 16, CAPTURE_CONCURRENCY = 32;
9
+ /* Runs `fn` over `items` with at most `limit` in flight and returns the results in the items' order. */
10
+ async function inOrder(items, limit, fn) {
11
+ const out = new Array(items.length);
12
+ let next = 0;
13
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, async () => { while (next < items.length) {
14
+ const i = next++;
15
+ out[i] = await fn(items[i]);
16
+ } }));
17
+ return out;
18
+ }
9
19
  function coded(code, message = code, details = {}) { return Object.assign(new Error(message === code ? code : `${code}: ${message}`), { code, details }); }
10
20
  function sha256(data) { return createHash('sha256').update(data).digest('hex'); }
11
21
  function slash(value) { return value.split(path.sep).join('/'); }
@@ -238,6 +248,9 @@ async function captureGit(workspace, blobRoot, budget) {
238
248
  }
239
249
  const entries = {};
240
250
  const seen = new Set();
251
+ /* The index is validated first, in order (unmerged, duplicate, gitlink: the first bad row decides), then the files are
252
+ looked at CAPTURE_CONCURRENCY at a time and their entries stored in index order. */
253
+ const jobs = [];
241
254
  for (const row of trackedRows) {
242
255
  const tab = row.indexOf('\t');
243
256
  if (tab < 0)
@@ -256,60 +269,50 @@ async function captureGit(workspace, blobRoot, budget) {
256
269
  if (!relGit)
257
270
  continue;
258
271
  const relative = safeRelative(relGit);
259
- const absolute = absoluteFromRelative(workspace.canonicalRoot, relative);
272
+ jobs.push({ relative, absolute: absoluteFromRelative(workspace.canonicalRoot, relative), mode, oid, gitPath });
273
+ }
274
+ const tracked = await inOrder(jobs, CAPTURE_CONCURRENCY, async ({ relative, absolute, mode, oid, gitPath }) => {
260
275
  let stat;
261
276
  try {
262
277
  stat = await lstat(absolute);
263
278
  }
264
279
  catch (error) {
265
280
  if (error?.code === 'ENOENT')
266
- continue;
281
+ return null;
267
282
  throw error;
268
283
  }
269
284
  const mustCapture = dirty.has(gitPath) || stat.isSymbolicLink() || !stat.isFile();
270
- if (mustCapture) {
271
- const actual = await captureActualFile(workspace, absolute, relative, blobRoot, budget, mode === '100755');
272
- if (actual)
273
- entries[relative] = actual;
274
- continue;
275
- }
285
+ if (mustCapture)
286
+ return captureActualFile(workspace, absolute, relative, blobRoot, budget, mode === '100755');
276
287
  consumeEntry(budget);
277
288
  await assertFileContained(workspace, absolute, budget);
278
- entries[relative] = { kind: 'file', source: 'git', ref: oid, size: stat.size, mode: stat.mode & 0o777, executable: mode === '100755', gitPath };
279
- }
280
- for (const gitPath of nulList(untrackedRaw)) {
281
- const relGit = prefix ? (gitPath.startsWith(`${prefix}/`) ? gitPath.slice(prefix.length + 1) : null) : gitPath;
282
- if (!relGit)
283
- continue;
284
- const relative = safeRelative(relGit);
285
- if (entries[relative])
286
- continue;
287
- const actual = await captureActualFile(workspace, absoluteFromRelative(workspace.canonicalRoot, relative), relative, blobRoot, budget);
288
- if (actual)
289
- entries[relative] = actual;
290
- }
289
+ return { kind: 'file', source: 'git', ref: oid, size: stat.size, mode: stat.mode & 0o777, executable: mode === '100755', gitPath };
290
+ });
291
+ jobs.forEach((job, i) => { const entry = tracked[i]; if (entry)
292
+ entries[job.relative] = entry; });
293
+ const untracked = [...new Set(nulList(untrackedRaw).map(gitPath => prefix ? (gitPath.startsWith(`${prefix}/`) ? gitPath.slice(prefix.length + 1) : null) : gitPath).filter((relGit) => Boolean(relGit)).map(safeRelative))].filter(relative => !entries[relative]);
294
+ const captured = await inOrder(untracked, CAPTURE_CONCURRENCY, relative => captureActualFile(workspace, absoluteFromRelative(workspace.canonicalRoot, relative), relative, blobRoot, budget));
295
+ untracked.forEach((relative, i) => { const entry = captured[i]; if (entry)
296
+ entries[relative] = entry; });
291
297
  return { capturedAt: new Date().toISOString(), workspace: { canonicalRoot: workspace.canonicalRoot, identityHash: workspace.identityHash, gitRoot }, entries, git: { root: gitRoot, head, indexHash, worktreeSemanticsHash }, coverage: { tracked: true, untrackedNonIgnored: true, ignoredUntracked: false, gitMetadataRestored: false }, exclusions: ['Untracked files ignored by Git are outside rollback coverage.', 'Empty-directory existence is outside rollback coverage.', 'Git refs, index and object database are observed for conflict evidence but are not restored.'] };
292
298
  }
293
299
  async function captureNonGit(workspace, blobRoot, budget) {
294
300
  const entries = {};
301
+ /* Folders one at a time and in order; the files of a folder CAPTURE_CONCURRENCY at a time. */
295
302
  async function preflight(dirname, relativeDir) {
296
303
  const rows = await readdir(dirname, { withFileTypes: true });
297
304
  rows.sort((a, b) => a.name.localeCompare(b.name));
298
- for (const row of rows) {
305
+ const files = rows.filter(row => !row.isDirectory());
306
+ await inOrder(files, CAPTURE_CONCURRENCY, async (row) => {
299
307
  const relative = safeRelative(relativeDir ? `${relativeDir}/${row.name}` : row.name);
300
308
  const absolute = path.join(dirname, row.name);
301
- if (row.isDirectory()) {
302
- await assertExistingPathContained(workspace, absolute);
303
- await preflight(absolute, relative);
304
- continue;
305
- }
306
309
  let stat;
307
310
  try {
308
311
  stat = await lstat(absolute);
309
312
  }
310
313
  catch (error) {
311
314
  if (error?.code === 'ENOENT')
312
- continue;
315
+ return;
313
316
  throw error;
314
317
  }
315
318
  consumeEntry(budget);
@@ -323,12 +326,17 @@ async function captureNonGit(workspace, blobRoot, budget) {
323
326
  }
324
327
  if (!inside(workspace.canonicalRoot, canonical))
325
328
  throw coded('CHECKPOINT_SYMLINK_ESCAPE', `Checkpoint symlink escapes workspace: ${relative}`);
326
- continue;
329
+ return;
327
330
  }
328
331
  if (!stat.isFile())
329
332
  throw coded('CHECKPOINT_SPECIAL_FILE_UNSUPPORTED', `Unsupported workspace entry: ${relative}`);
330
333
  await assertFileContained(workspace, absolute, budget);
331
334
  consumeFileBudget(budget, stat.size);
335
+ });
336
+ for (const row of rows.filter(row => row.isDirectory())) {
337
+ const absolute = path.join(dirname, row.name);
338
+ await assertExistingPathContained(workspace, absolute);
339
+ await preflight(absolute, safeRelative(relativeDir ? `${relativeDir}/${row.name}` : row.name));
332
340
  }
333
341
  }
334
342
  await preflight(workspace.canonicalRoot, '');
@@ -337,17 +345,14 @@ async function captureNonGit(workspace, blobRoot, budget) {
337
345
  async function walk(dirname, relativeDir) {
338
346
  const rows = await readdir(dirname, { withFileTypes: true });
339
347
  rows.sort((a, b) => a.name.localeCompare(b.name));
340
- for (const row of rows) {
341
- const relative = safeRelative(relativeDir ? `${relativeDir}/${row.name}` : row.name);
348
+ const files = rows.filter(row => !row.isDirectory()).map(row => safeRelative(relativeDir ? `${relativeDir}/${row.name}` : row.name));
349
+ const captured = await inOrder(files, CAPTURE_CONCURRENCY, relative => captureActualFile(workspace, path.join(dirname, path.basename(relative)), relative, blobRoot, budget));
350
+ files.forEach((relative, i) => { const entry = captured[i]; if (entry)
351
+ entries[relative] = entry; });
352
+ for (const row of rows.filter(row => row.isDirectory())) {
342
353
  const absolute = path.join(dirname, row.name);
343
- if (row.isDirectory()) {
344
- await assertExistingPathContained(workspace, absolute);
345
- await walk(absolute, relative);
346
- continue;
347
- }
348
- const actual = await captureActualFile(workspace, absolute, relative, blobRoot, budget);
349
- if (actual)
350
- entries[relative] = actual;
354
+ await assertExistingPathContained(workspace, absolute);
355
+ await walk(absolute, safeRelative(relativeDir ? `${relativeDir}/${row.name}` : row.name));
351
356
  }
352
357
  }
353
358
  await walk(workspace.canonicalRoot, '');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "talos-code",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "TALOS in the terminal: a coding agent with per-tool permissions, file checkpoints and resumable sessions, on the model you choose.",
5
5
  "keywords": [
6
6
  "coding-agent",
@@ -20,6 +20,8 @@ export function createDesktopContextService({ engine, store, loadLegacy, resolve
20
20
  /* 24/09 — F4: le sessioni per cui questo processo ha già cercato job orfani (basta una volta: in
21
21
  questo processo un job attivo è sempre nella mappa `running` del motore). */
22
22
  const recuperate = new Set();
23
+ /* P18: per sessione, le impronte dei record gia' verificati e la revisione dell'archivio che le ha prodotte. */
24
+ const impronteVerificate = new Map();
23
25
  let closed = false;
24
26
  function serial(sessionId, task) {
25
27
  const previous = queues.get(sessionId) ?? Promise.resolve();
@@ -194,15 +196,28 @@ export function createDesktopContextService({ engine, store, loadLegacy, resolve
194
196
  }
195
197
  fail('CTX_ROUTE_NOT_FOUND', 'Operazione del contesto non trovata.');
196
198
  },
199
+ /*
200
+ * P18 (26/09/2026, patch approvata dall'owner via la lane CLI): l'archivio non si RILEGGE a ogni capture. Le
201
+ * impronte dei record gia' verificati restano in memoria, legate alla revisione dell'archivio che le ha prodotte;
202
+ * se la revisione e' ancora quella (nessun altro ha scritto: una compattazione la cambia), si usano quelle invece
203
+ * di `allRecords` (misurato: ~25 ms a capture a 1.351 messaggi, cresce con la storia). Il confronto con OGNI
204
+ * messaggio resta: la divergenza si scopre come prima.
205
+ */
197
206
  syncOriginals({ sessionId, messages }) {
198
207
  return serial(sessionId, async () => {
199
208
  const snapshot = await ensure(sessionId);
200
209
  if (!Array.isArray(messages)) fail('CTX_INVALID_INPUT', 'Cronologia non valida.');
201
- const saved = await allRecords(sessionId);
202
- if (messages.length < saved.length || saved.some((record, index) => record.sha256 !== digest(messages[index]))) fail('CTX_HISTORY_DIVERGED', 'La cronologia attiva differisce dall’archivio. Gli originali sono conservati; occorre recuperare la versione completa.');
203
- const records = messages.slice(saved.length).map((message, offset) => ({ id: `message-${saved.length + offset + 1}`, message, createdAt: clock(), origin: 'desktop-kernel' }));
210
+ const noto = impronteVerificate.get(sessionId);
211
+ const salvate = noto && Number.isInteger(snapshot?.revision) && noto.revision === snapshot.revision ? noto.impronte : (await allRecords(sessionId)).map((record) => record.sha256);
212
+ if (messages.length < salvate.length || salvate.some((impronta, index) => impronta !== digest(messages[index]))) {
213
+ impronteVerificate.delete(sessionId);
214
+ fail('CTX_HISTORY_DIVERGED', 'La cronologia attiva differisce dall’archivio. Gli originali sono conservati; occorre recuperare la versione completa.');
215
+ }
216
+ const records = messages.slice(salvate.length).map((message, offset) => ({ id: `message-${salvate.length + offset + 1}`, message, createdAt: clock(), origin: 'desktop-kernel' }));
204
217
  if (records.length) await store.appendOriginalBatch({ sessionId, records, expectedRevision: snapshot.revision });
205
- return store.readContextSnapshot({ sessionId });
218
+ const dopo = await store.readContextSnapshot({ sessionId });
219
+ impronteVerificate.set(sessionId, { revision: dopo?.revision, impronte: [...salvate, ...records.map((record) => digest(record.message))] });
220
+ return dopo;
206
221
  });
207
222
  },
208
223
  /*
@@ -3679,6 +3679,8 @@ async function tuttiIPercorsi(disco, { filtro = null } = {}) {
3679
3679
  * camminata JS di sempre: nessuna installazione perde la ricerca.
3680
3680
  */
3681
3681
  const MAX_CERCA_INSIEME = 8
3682
+ /* Gli attrezzi che non cambiano niente: una partenza anticipata puo' scavalcarli, mai scavalcare altro. */
3683
+ const LETTURE_PURE = new Set(['cerca', 'leggi', 'elenca'])
3682
3684
  const MAX_RIGHE_RG = 100
3683
3685
  const MAX_RIGHE_PER_FILE = 5
3684
3686
  const MAX_COLONNE_RG = 200
@@ -3815,11 +3817,15 @@ export async function cercaNelProgetto(disco, { testo, nome }, { radice, segnale
3815
3817
 
3816
3818
  const filtro = await filtroDelProgetto(radice)
3817
3819
  const { percorsi, dimensioni, stato } = await tuttiIPercorsi(disco, { filtro })
3820
+ /* P18 (vincolo desktop a, 26/09): lo Stop ferma anche la camminata JS, non solo rg — fino a 8 `cerca` possono
3821
+ essere partite insieme, e nessuna deve finire in sottofondo dopo «Ferma». */
3822
+ if (segnale?.aborted) return 'stopped: the search was interrupted.'
3818
3823
  const perNome = []
3819
3824
  const perTesto = []
3820
3825
  const conto = { letti: 0, binari: 0, troppoGrandi: 0, illeggibili: 0, tettoLetture: false, tettoRicerca: false }
3821
3826
 
3822
3827
  for (const p of percorsi) {
3828
+ if (segnale?.aborted) return 'stopped: the search was interrupted.'
3823
3829
  const basso = p.toLowerCase()
3824
3830
  const combaciaNome = chiaveNome && basso.includes(chiaveNome)
3825
3831
  if (combaciaNome) { perNome.push(p); continue }
@@ -8271,8 +8277,13 @@ export async function talosLavora({
8271
8277
  * ⛔ Lo Stop ferma anche quelle in volo: il segnale arriva fino a rg, che viene ucciso.
8272
8278
  * Misura (banco ermetico, 4 `cerca` su 6.000 file): 8.648 ms prima di P18, 377 con ripgrep in fila.
8273
8279
  */
8280
+ /* ⛔⛔ Solo il PREFISSO di sole letture della risposta (`cerca`, `leggi`, `elenca`): una `cerca` che viene DOPO
8281
+ una scrittura o un comando deve vedere il loro effetto, e partita prima non lo vedrebbe (misurato: `scrivi`
8282
+ poi `cerca` nella stessa risposta dava la ricerca SENZA il file appena scritto — p18-cerca-insieme). */
8274
8283
  const ricercheAvviate = new Map()
8275
- const daCercare = chiamate.filter((c) => c.function?.name === 'cerca')
8284
+ const primaNonLettura = chiamate.findIndex((c) => !LETTURE_PURE.has(c.function?.name))
8285
+ const prefissoDiLettura = primaNonLettura === -1 ? chiamate : chiamate.slice(0, primaNonLettura)
8286
+ const daCercare = prefissoDiLettura.filter((c) => c.function?.name === 'cerca')
8276
8287
  if (daCercare.length > 1 && !segnaleStop?.aborted) {
8277
8288
  for (const c of daCercare.slice(0, MAX_CERCA_INSIEME)) {
8278
8289
  let argomentiCerca = {}
@@ -105,8 +105,13 @@ function git(argomenti, cwd) {
105
105
  export async function statoGit(percorso, { profondita = PROFONDITA_REPO_ANNIDATI, eseguiGit = git } = {}) {
106
106
  const dentro = await eseguiGit(['rev-parse', '--is-inside-work-tree'], percorso);
107
107
  if (!dentro || dentro.trim() !== 'true') return null;
108
- const ramo = (await eseguiGit(['rev-parse', '--abbrev-ref', 'HEAD'], percorso) || '').trim() || null;
109
- const stato = await eseguiGit(['status', '--porcelain'], percorso);
108
+ /* P18 (26/09/2026): ramo e stato sono indipendenti, si chiedono INSIEME (misurato all'avvio di un turno: ~55 ms
109
+ in meno, due processi git in fila diventano uno accanto all'altro). */
110
+ const [ramoGrezzo, stato] = await Promise.all([
111
+ eseguiGit(['rev-parse', '--abbrev-ref', 'HEAD'], percorso),
112
+ eseguiGit(['status', '--porcelain'], percorso),
113
+ ]);
114
+ const ramo = (ramoGrezzo || '').trim() || null;
110
115
  const nonSalvate = stato === null ? null : stato.split('\n').filter((r) => r.trim()).length;
111
116
  const annidati = await repoAnnidati(percorso, profondita);
112
117
  return { ramo, nonSalvate, repoAnnidati: annidati };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "talos.cli.vendor.v1",
3
- "sourceCommit": "9a10705c95b865e72c431f8d8ad7873bcdd430b5",
3
+ "sourceCommit": "e553a0cd2616df27b399505afc3fa0eeb8b504fa",
4
4
  "files": 163,
5
5
  "sha256": {
6
6
  "context-engine/package.json": "cd416c2ef02241365eca721ce1bed7a702cff34bb804d79e974f9f36731ced8a",
@@ -36,7 +36,7 @@
36
36
  "harness-ui/src/config.mjs": "ab482ac221f4b5ad3e1b46052d87549ebf89fd2ee30ce113ceda8132b028e1ef",
37
37
  "harness-ui/src/contesto-del-progetto.mjs": "5aec0670f0d81fc545112ae4f2a0a36a5359e76e76f39bf63d403a6cb615d030",
38
38
  "harness-ui/src/context-asset-adapter.mjs": "5a5feccad64c3b427031dcc8dfc47a2ffa5e0db4eec2959a8d3b70e3c87a55fc",
39
- "harness-ui/src/context-desktop-service.mjs": "5415123aa96f5bdeeedcedbcd55cb0b3fe944c629232a92bb98eeda372c8d397",
39
+ "harness-ui/src/context-desktop-service.mjs": "0ac2625b63512909c689d81370047a7e54892fbf0c58622936b5c9a72aff9fc0",
40
40
  "harness-ui/src/context-embedding-runtime.mjs": "de494d5d1bf08e2a9b685dbf78f8e9a16631700e0c4e5d045c91f65a6a4c6ec2",
41
41
  "harness-ui/src/context-inference-scheduler.mjs": "26dcccde934993226d4adb0b239b465a45640701d377c5851fc58209552e7d39",
42
42
  "harness-ui/src/context-native-compaction.mjs": "55069d9a80283d10be924bd1309bb271a234608660424deb48aac82f8e273125",
@@ -73,7 +73,7 @@
73
73
  "harness-ui/src/image-generator.mjs": "8762739d578f81d1b881d24338518480e59fcae5b8a3ff6a071947652bec8008",
74
74
  "harness-ui/src/istruzioni-di-progetto.mjs": "a0c2c9dec9d119799e3bc02b1aee99ffa996d2c141ba679345f7309495a29cff",
75
75
  "harness-ui/src/kernel/dist/kernelPerIlBanco.js": "ff75a3e93b6c6d6d838331016c5eb2d1e6acfea85ffc0609d2f531f07b202a54",
76
- "harness-ui/src/kernel/talosHarness.mjs": "c03a8d1e155a8f4f31793e687f7b26e5ee625ff3a3dcfc737dde6f8cb0fca32a",
76
+ "harness-ui/src/kernel/talosHarness.mjs": "0ee781ebbb507389a0ac109eebb599d45760dedf9d368d6f921e02b2a8e97d1e",
77
77
  "harness-ui/src/library-policy-store.mjs": "b0295e20d7348cc13cb23a8ad2a0b577fe275b4778b94e102ceeb4fb1bb4c70b",
78
78
  "harness-ui/src/library-store.mjs": "b7a0333da5783361536509f9108e1293b1e8c22bddfb02fd5581a4e1ed4bcd4f",
79
79
  "harness-ui/src/llama-server-supervisor.mjs": "d59cc22a0e7875b4deeb4a4eac1daa55c3f524db7f5f9bd1f5e58f2c4be261e3",
@@ -162,7 +162,7 @@
162
162
  "harness-ui/src/workspace-context.mjs": "75bb99b0e5624b2257a73c76997bb8670206758cbabe4613683503c6c785930a",
163
163
  "harness-ui/src/workspace-disk.mjs": "b90a666880079a72bca7512bb936324f2abe3ed1075aec828cbcbeba3965ec7a",
164
164
  "harness-ui/src/workspace-files.mjs": "998f04da2959d8404da801d4cf981e1e42fcab221d2b96456acb19688980c4a4",
165
- "harness-ui/src/workspace-info.mjs": "e685e98675cb432154cb20e076be310076e2f07c240de11bc4d0feab94293d05",
165
+ "harness-ui/src/workspace-info.mjs": "cd3cb40401139e06a5ddc23683822578913c180744bea233d3b5e2c29b417042",
166
166
  "harness-ui/src/workspace-launch-store.mjs": "06e14d1c740377d788609d6cd930575a1180dab32640a1692605a0225cadf9a9",
167
167
  "harness-ui/src/workspace-tree.mjs": "c5723383cdff663ea0537e384fef8593f574893e63b9645c395a076910ac1f8b",
168
168
  "harness-ui/src/workspace-watcher.mjs": "ab1e1a03ab61b98f2a7a5bd559b56e1e6dfc0b3ec201a41edc96d70512f8053f"