@visual-z/xeno 0.7.1 → 0.8.0
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 +47 -0
- package/README.md +39 -12
- package/docs/foundations.md +1 -1
- package/docs/install.md +18 -5
- package/package.json +3 -1
- package/src/apps/cli.ts +4 -3
- package/src/apps/commands.ts +9 -1
- package/src/apps/goal-flow.ts +70 -0
- package/src/apps/render.ts +0 -2
- package/src/apps/session-hub.ts +6 -5
- package/src/apps/session-lifecycle.ts +64 -0
- package/src/apps/shell.ts +90 -35
- package/src/apps/startup.ts +33 -32
- package/src/apps/ui/app.ts +47 -10
- package/src/apps/ui/control-model.ts +18 -1
- package/src/apps/ui/surface.ts +2 -0
- package/src/apps/update-check.ts +34 -4
- package/src/core/acceptance.ts +41 -9
- package/src/core/blind-checks.ts +72 -0
- package/src/core/branch-receipt.ts +3 -1
- package/src/core/checkpoints.ts +21 -0
- package/src/core/churn.ts +2 -2
- package/src/core/completion-review.ts +18 -11
- package/src/core/continuation.ts +11 -10
- package/src/core/control-view.ts +5 -8
- package/src/core/deadline.ts +2 -1
- package/src/core/derive.ts +0 -7
- package/src/core/environment.ts +2 -2
- package/src/core/event-validation.ts +19 -6
- package/src/core/events.ts +41 -32
- package/src/core/evidence-ladder.ts +62 -0
- package/src/core/evidence-links.ts +0 -10
- package/src/core/evidence.ts +1 -2
- package/src/core/export.ts +6 -11
- package/src/core/goal.ts +200 -0
- package/src/core/history.ts +1 -11
- package/src/core/json-event.ts +0 -6
- package/src/core/outcome.ts +0 -1
- package/src/core/program-status.ts +11 -2
- package/src/core/progress.ts +14 -8
- package/src/core/project.ts +6 -16
- package/src/core/read-bounds.ts +11 -2
- package/src/core/read-observation.ts +24 -0
- package/src/core/request-budget.ts +13 -3
- package/src/core/request-state.ts +178 -19
- package/src/core/response-recovery.ts +2 -1
- package/src/core/session-entry.ts +8 -3
- package/src/core/session-exit.ts +19 -3
- package/src/core/session-format.ts +169 -0
- package/src/core/settings.ts +7 -27
- package/src/core/stall.ts +30 -7
- package/src/core/stop-report.ts +4 -3
- package/src/core/transcript.ts +22 -2
- package/src/core/update-check.ts +40 -0
- package/src/runtime/blind-check.ts +36 -0
- package/src/runtime/counter-check.ts +74 -0
- package/src/runtime/exec-sessions.ts +11 -0
- package/src/runtime/foundation.ts +46 -8
- package/src/runtime/loop.ts +239 -430
- package/src/runtime/oauth.ts +1 -3
- package/src/runtime/provider-request.ts +2 -1
- package/src/runtime/runtime-target.ts +2 -2
- package/src/runtime/self-update.ts +74 -0
- package/src/runtime/session-control.ts +4 -4
- package/src/runtime/tools.ts +44 -42
- package/src/runtime/version.ts +12 -0
- package/src/runtime/workspace-lifecycle.ts +31 -0
- package/src/store/blind-checks.ts +32 -0
- package/src/store/checkpoint-history.ts +1 -12
- package/src/store/file-index.ts +88 -7
- package/src/store/log.ts +25 -27
- package/src/store/recovery.ts +1 -3
- package/src/store/replay.ts +2 -2
- package/src/store/restore.ts +10 -202
- package/src/store/search.ts +7 -5
- package/src/store/session-archive.ts +0 -1
- package/src/store/sessions.ts +31 -33
- package/src/store/workspace-clone.ts +42 -0
- package/src/store/workspace.ts +10 -157
- package/src/tui/ui/screen.ts +5 -4
- package/src/tui/ui/status.ts +6 -2
- package/src/core/benchmark.ts +0 -195
- package/src/core/evaluation-coverage.ts +0 -74
- package/src/core/experiment.ts +0 -173
- package/src/core/harness-comparison.ts +0 -90
- package/src/core/harness-diagnostics.ts +0 -206
- package/src/core/native-eval.ts +0 -25
- package/src/core/policy-evaluation.ts +0 -55
- package/src/core/query-eval-contract.ts +0 -14
- package/src/core/recall-analysis.ts +0 -90
- package/src/core/recall-trial.ts +0 -153
- package/src/core/recovery-corpus.ts +0 -17
- package/src/core/recovery-evaluation.ts +0 -60
- package/src/core/tool-mode-protocol.ts +0 -25
- package/src/core/trial-lifecycle.ts +0 -14
- package/src/core/window-trial.ts +0 -65
- package/src/index.ts +0 -10
- package/src/runtime/benchmark.ts +0 -187
- package/src/runtime/harness-comparison.ts +0 -376
- package/src/runtime/harness-eval.ts +0 -462
- package/src/runtime/native-eval-fixtures.ts +0 -33
- package/src/runtime/native-eval.ts +0 -146
- package/src/runtime/query-eval-verifier.ts +0 -28
- package/src/runtime/recall-trial.ts +0 -226
- package/src/runtime/recovery-coverage.ts +0 -71
- package/src/runtime/recovery-evidence.ts +0 -76
- package/src/runtime/recovery-fixtures.ts +0 -116
- package/src/runtime/tool-mode-eval.ts +0 -146
- package/src/runtime/trial-control.ts +0 -45
- package/src/runtime/window-trial.ts +0 -141
- package/src/store/git-branch.ts +0 -35
- package/src/tui/ui/testing.ts +0 -5
|
@@ -12,15 +12,27 @@ import { LspManager, parseLspConfig } from "./lsp-client.ts";
|
|
|
12
12
|
import { validateFormatters } from "./formatter.ts";
|
|
13
13
|
import { createFormatterTool } from "./formatter-tool.ts";
|
|
14
14
|
import { permissionResourceAction } from "./permission-resources.ts";
|
|
15
|
-
import type { ToolContext, ToolDefinition
|
|
15
|
+
import type { InstructionCheck, ToolContext, ToolDefinition } from "./tools.ts";
|
|
16
16
|
import { McpManager } from "./mcp-client.ts";
|
|
17
17
|
import { parseMcpServers } from "../core/mcp-config.ts";
|
|
18
18
|
import type { ExtensionRegistry } from "./extensions.ts";
|
|
19
19
|
import { join } from "node:path";
|
|
20
20
|
import { getBlob } from "../store/blobs.ts";
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Calls that only observe run and carry newly discovered rules (S.6 item 3:
|
|
24
|
+
* OpenCode, Gemini CLI and Claude Code do the same). A change is still held
|
|
25
|
+
* back until the rules have been read: the reason for blocking first was never
|
|
26
|
+
* to notify after a modification (foundation-editing research).
|
|
27
|
+
*/
|
|
28
|
+
const OBSERVING_TOOLS = new Set(["read", "query", "lsp"]);
|
|
29
|
+
/** Assumed: more batches than any interleaving of live sessions sharing this runtime. */
|
|
30
|
+
const RECENT_BATCHES = 16;
|
|
31
|
+
|
|
22
32
|
export class FoundationRuntime {
|
|
23
33
|
readonly questionSlot: QuestionSlot;
|
|
34
|
+
/** Per batch (assistant event id): instruction key -> the call that carried it. */
|
|
35
|
+
private readonly batches = new Map<string, Map<string, string>>();
|
|
24
36
|
readonly mcp: McpManager;
|
|
25
37
|
readonly tools: ToolDefinition[];
|
|
26
38
|
readonly permissions: PermissionRule[];
|
|
@@ -47,14 +59,32 @@ export class FoundationRuntime {
|
|
|
47
59
|
const discovered = await this.mcp.discover();
|
|
48
60
|
for (const integration of discovered.integrations) registry.api("xeno-mcp").registerIntegration(integration);
|
|
49
61
|
}
|
|
50
|
-
|
|
62
|
+
/** Calls are prepared one at a time in their batch, so "first to discover carries the rules" is deterministic. */
|
|
63
|
+
private claims(batch: string): Map<string, string> {
|
|
64
|
+
let claimed = this.batches.get(batch);
|
|
65
|
+
if (claimed === undefined) {
|
|
66
|
+
claimed = new Map();
|
|
67
|
+
this.batches.set(batch, claimed);
|
|
68
|
+
for (const old of this.batches.keys()) if (this.batches.size > RECENT_BATCHES) this.batches.delete(old);
|
|
69
|
+
}
|
|
70
|
+
return claimed;
|
|
71
|
+
}
|
|
72
|
+
async instructions(ctx: ToolContext, name: string, input: unknown, noContextFiles: boolean): Promise<InstructionCheck | null> {
|
|
51
73
|
if (noContextFiles || !["read", "write", "edit", "apply_patch", "query", "format", "lsp"].includes(name) || !input || typeof input !== "object") return null;
|
|
52
74
|
const args = input as Record<string, unknown>;
|
|
53
75
|
const paths = name === "apply_patch" ? patchPaths(parsePatch(String(args["patchText"]))) : typeof args["path"] === "string" ? [args["path"]] : Array.isArray(args["paths"]) ? args["paths"].filter((path): path is string => typeof path === "string") : [];
|
|
54
76
|
if (!paths.length) return null;
|
|
77
|
+
const blocked = (content: string): InstructionCheck => ({ kind: "block", result: { ok: false, content } });
|
|
55
78
|
const known = new Set<string>();
|
|
56
|
-
|
|
57
|
-
|
|
79
|
+
const history = await ctx.historyEvents?.() ?? [];
|
|
80
|
+
// Rules count as read only once the model has seen them: results of this
|
|
81
|
+
// same batch may already be committed, but they reach the model together
|
|
82
|
+
// with this call's result. Within the batch, claims decide instead.
|
|
83
|
+
const batch = history.findLast(event => event.t === "assistant");
|
|
84
|
+
const sameBatch = new Set(batch?.t === "assistant" ? batch.blocks.flatMap(block => block.type === "tool_use" ? [block.call_id] : []) : []);
|
|
85
|
+
for (const event of history) {
|
|
86
|
+
if (event.t !== "tool_result" || sameBatch.has(event.call_id)) continue;
|
|
87
|
+
for (const key of event.instructions?.keys ?? []) known.add(key);
|
|
58
88
|
let data: unknown = event.data;
|
|
59
89
|
if (event.data_ref && ctx.outputStore) {
|
|
60
90
|
const saved = await getBlob(ctx.outputStore, event.data_ref.sha256);
|
|
@@ -67,12 +97,20 @@ export class FoundationRuntime {
|
|
|
67
97
|
}
|
|
68
98
|
const found = await discoverTargetInstructions({ workspace: ctx.root, paths, known,
|
|
69
99
|
canRead: async path => await permissionResourceAction(ctx.root, path, ctx.rules ?? [], ctx.sessionRules ?? []) === "allow" });
|
|
70
|
-
if (found.notices.some(notice => notice.kind === "skipped" && notice.reason === "instruction read permission is unavailable")) return
|
|
100
|
+
if (found.notices.some(notice => notice.kind === "skipped" && notice.reason === "instruction read permission is unavailable")) return blocked("Scoped instructions require read permission before this operation.");
|
|
71
101
|
const files = found.files.filter(file => !ctx.frozenSystem?.().includes(file.content));
|
|
72
102
|
if (!files.length) return null;
|
|
73
|
-
for (const file of files) if (await permissionResourceAction(ctx.root, file.path, ctx.rules ?? [], ctx.sessionRules ?? []) !== "allow") return
|
|
74
|
-
|
|
75
|
-
|
|
103
|
+
for (const file of files) if (await permissionResourceAction(ctx.root, file.path, ctx.rules ?? [], ctx.sessionRules ?? []) !== "allow") return blocked("Scoped instructions require read permission before this operation.");
|
|
104
|
+
// Once per batch: a later call in the same assistant message does not receive the same rules again.
|
|
105
|
+
const claimed = this.claims(batch?.id ?? "");
|
|
106
|
+
const fresh = files.filter(file => !claimed.has(file.key));
|
|
107
|
+
const carriers = [...new Set(files.flatMap(file => claimed.get(file.key) ?? []))];
|
|
108
|
+
for (const file of fresh) claimed.set(file.key, ctx.callId ?? name);
|
|
109
|
+
if (OBSERVING_TOOLS.has(name)) return fresh.length ? { kind: "attach", instructions: { keys: fresh.map(file => file.key), text: renderTargetInstructions(fresh) } } : null;
|
|
110
|
+
const earlier = carriers.length ? `Scoped instructions for this target were attached to the result of ${carriers.join(", ")} in this batch. ` : "";
|
|
111
|
+
if (!fresh.length) return blocked(`${earlier}No original operation was executed. Follow them, then retry.`);
|
|
112
|
+
return { kind: "block", result: { ok: false, content: `Scoped instructions were discovered. ${earlier}Read and follow them before deciding whether to retry the original operation. No original operation was executed.\n\n` + renderTargetInstructions(fresh),
|
|
113
|
+
data: { kind: "scoped_instructions", keys: fresh.map(file => file.key), files: fresh.map(file => ({ path: file.path, scope: file.scope, key: file.key, content: file.content, truncated: file.truncated })) } } };
|
|
76
114
|
}
|
|
77
115
|
async dispose(): Promise<void> { await Promise.all([this.lsp.dispose(), this.mcp.dispose()]); }
|
|
78
116
|
}
|