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 +28 -0
- package/dist/runtime/context-archive.js +20 -1
- package/dist/version.js +1 -1
- package/dist/workspace/checkpoint.js +45 -40
- package/package.json +1 -1
- package/vendor/harness-ui/src/context-desktop-service.mjs +19 -4
- package/vendor/harness-ui/src/kernel/talosHarness.mjs +12 -1
- package/vendor/harness-ui/src/workspace-info.mjs +7 -2
- package/vendor/manifest.json +4 -4
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) =>
|
|
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
|
+
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
|
-
|
|
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
|
-
|
|
281
|
+
return null;
|
|
267
282
|
throw error;
|
|
268
283
|
}
|
|
269
284
|
const mustCapture = dirty.has(gitPath) || stat.isSymbolicLink() || !stat.isFile();
|
|
270
|
-
if (mustCapture)
|
|
271
|
-
|
|
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
|
-
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
341
|
-
|
|
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
|
-
|
|
344
|
-
|
|
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
|
@@ -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
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
109
|
-
|
|
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 };
|
package/vendor/manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "talos.cli.vendor.v1",
|
|
3
|
-
"sourceCommit": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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"
|