@visual-z/xeno 0.1.2 → 0.1.4
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/README.md +1 -1
- package/package.json +1 -1
- package/src/core/progress.ts +34 -0
- package/src/runtime/oauth.ts +1 -1
- package/src/runtime/tools.ts +1 -1
- package/src/store/workspace.ts +6 -0
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ is ever summarized by a model, and nothing is ever rewritten in place.
|
|
|
17
17
|
messages[] frozen system prefix + handoff + this node's events
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
Version **0.1.
|
|
20
|
+
Version **0.1.4**. The on-disk session format will still change; `0.x` is that
|
|
21
21
|
statement. Published as `@visual-z/xeno`; the license is `UNLICENSED` (readable and installable, no reuse granted).
|
|
22
22
|
|
|
23
23
|
## Install
|
package/package.json
CHANGED
package/src/core/progress.ts
CHANGED
|
@@ -27,6 +27,12 @@ function hasUnseenLines(requested: readonly LineRange[], observed: readonly Line
|
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* A file version's fingerprint in the observation set. Keyed by digest alone:
|
|
32
|
+
* the same bytes under another path are not new work either.
|
|
33
|
+
*/
|
|
34
|
+
const versionKey = (_path: string, sha256: string): string => `version:${sha256}`;
|
|
35
|
+
|
|
30
36
|
export function deriveProgress(
|
|
31
37
|
previous: readonly ProgressEvidence[], events: readonly LogEvent[], node: number,
|
|
32
38
|
eligibleChecks: ReadonlySet<string>,
|
|
@@ -45,6 +51,8 @@ export function deriveProgress(
|
|
|
45
51
|
seen.push(item);
|
|
46
52
|
reads.set(item.sha256, seen);
|
|
47
53
|
if (item.observation_sha256 !== undefined) observations.add(item.observation_sha256);
|
|
54
|
+
// A version read before a cut is as known as one read in this segment.
|
|
55
|
+
observations.add(versionKey("", item.sha256));
|
|
48
56
|
} else if (item.kind === "observation") observations.add(item.sha256);
|
|
49
57
|
else checks.add(checkKey(item.command, item.gate, item.workspace_sha256));
|
|
50
58
|
}
|
|
@@ -61,6 +69,7 @@ export function deriveProgress(
|
|
|
61
69
|
const source = { node, event_id: event.id };
|
|
62
70
|
const effect = event.effect;
|
|
63
71
|
const observation = event.observation_sha256 ?? event.content_ref?.sha256 ?? event.content_sha256;
|
|
72
|
+
if (effect?.kind === "read" && effect.sha256 !== "") observations.add(versionKey(effect.path, effect.sha256));
|
|
64
73
|
if (effect?.kind === "read" && effect.sha256 !== "") {
|
|
65
74
|
const seen = reads.get(effect.sha256) ?? [];
|
|
66
75
|
const novel = !seen.some((r) => r.ranges === undefined) &&
|
|
@@ -79,6 +88,15 @@ export function deriveProgress(
|
|
|
79
88
|
if (observation !== undefined) observations.add(observation);
|
|
80
89
|
}
|
|
81
90
|
} else if (effect?.kind === "command" && effect.exit === 0) {
|
|
91
|
+
// D9 revision (2026-09-29, user-approved): output never seen before is a
|
|
92
|
+
// new observation, exactly as for read/grep. A model exploring with
|
|
93
|
+
// `sed -n` or `grep` in the shell is gathering evidence too; the digest
|
|
94
|
+
// excludes wall time, so an unchanged `git status` repeats and stalls.
|
|
95
|
+
if (observation !== undefined && !observations.has(observation)) {
|
|
96
|
+
observations.add(observation);
|
|
97
|
+
evidence.push({ kind: "observation", sha256: observation, source });
|
|
98
|
+
progressed = true;
|
|
99
|
+
}
|
|
82
100
|
const key = checkKey(effect.cmd, effect.gate, effect.workspace_sha256);
|
|
83
101
|
if (!checks.has(key)) {
|
|
84
102
|
checks.add(key);
|
|
@@ -89,6 +107,22 @@ export function deriveProgress(
|
|
|
89
107
|
// rerun of old evidence into a fresh completion by giving it a new name.
|
|
90
108
|
if (eligibleChecks.has(event.id)) progressed = true;
|
|
91
109
|
}
|
|
110
|
+
} else if (effect?.kind === "edit") {
|
|
111
|
+
// D9 revision (2026-09-29, user-approved): a file version no one has
|
|
112
|
+
// seen before is new evidence -- it is how implementation work shows up.
|
|
113
|
+
// Versions already seen anywhere (read, or either side of an edit) do
|
|
114
|
+
// not count, so an A->B->A rewrite loop still stalls.
|
|
115
|
+
// Both sides become durable evidence: the ledger is all a later
|
|
116
|
+
// derivation sees, and a version seen only as "before" must still be
|
|
117
|
+
// recognised when the file is edited back to it.
|
|
118
|
+
for (const [sha, isAfter] of [[effect.sha256_before, false], [effect.sha256_after, true]] as const) {
|
|
119
|
+
if (sha === null) continue;
|
|
120
|
+
const key = versionKey(effect.path, sha);
|
|
121
|
+
if (observations.has(key)) continue;
|
|
122
|
+
observations.add(key);
|
|
123
|
+
evidence.push({ kind: "observation", sha256: key, source });
|
|
124
|
+
if (isAfter) progressed = true;
|
|
125
|
+
}
|
|
92
126
|
} else if ((name === "grep" || name === "find" || name === "ls" || name === "query") &&
|
|
93
127
|
observation !== undefined && (event.content_ref?.bytes ?? event.content.length) > 0 &&
|
|
94
128
|
!observations.has(observation)) {
|
package/src/runtime/oauth.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { createServer, type Server } from "node:http";
|
|
|
17
17
|
import { pollDeviceCode } from "./oauth-device.ts";
|
|
18
18
|
|
|
19
19
|
/** Matches package.json. Sent as User-Agent so the server can see who we are (D18). */
|
|
20
|
-
export const XENO_VERSION = "0.1.
|
|
20
|
+
export const XENO_VERSION = "0.1.4";
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Minutes subtracted from server expiry so a request issued at the edge
|
package/src/runtime/tools.ts
CHANGED
|
@@ -135,7 +135,7 @@ export type ApprovalChoice = "once" | "command" | "tool" | "deny";
|
|
|
135
135
|
export const DEFAULT_TOOL_CONTEXT: Omit<ToolContext, "root"> = {
|
|
136
136
|
// Runner prefixes are resolved in command position so `python3 -m pytest`
|
|
137
137
|
// works without letting `echo pytest` forge a gate (D11 / M.2).
|
|
138
|
-
gates: { pytest: "pytest", tsc: "typecheck", cargo: "cargo", jest: "test", vitest: "test" },
|
|
138
|
+
gates: { pytest: "pytest", tsc: "typecheck", cargo: "cargo", jest: "test", vitest: "test", go: "go" },
|
|
139
139
|
// Five minutes covers typical compiles and installs; longer work asks for
|
|
140
140
|
// its own `timeout` up to the hour ceiling (omp uses the same 300 / 3600).
|
|
141
141
|
// Approved change from 120s (2026-09-28): 120s hard-killed real builds.
|
package/src/store/workspace.ts
CHANGED
|
@@ -120,6 +120,12 @@ export async function scanWorkspace(
|
|
|
120
120
|
if (err) return err;
|
|
121
121
|
continue;
|
|
122
122
|
}
|
|
123
|
+
// A symlink is not workspace content: it can point anywhere, and search
|
|
124
|
+
// and the file index already refuse to follow one. It is left out of the
|
|
125
|
+
// file set, so snapshots never read through it and a restore never
|
|
126
|
+
// deletes or replaces it (/back does not restore symlinks). Anything
|
|
127
|
+
// else that is not a regular file still fails the whole scan.
|
|
128
|
+
if (entry.isSymbolicLink()) continue;
|
|
123
129
|
if (!entry.isFile()) return `cannot snapshot unsupported file type: ${rel}`;
|
|
124
130
|
|
|
125
131
|
const info = await stat(abs);
|