@ashlr/hub 3.7.0 → 3.9.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.
Files changed (155) hide show
  1. package/CHANGELOG.md +257 -0
  2. package/dist/build-identity.json +2 -2
  3. package/dist/cli/resource-profile.js +95 -21
  4. package/dist/cli/resource-profile.js.map +1 -1
  5. package/dist/core/policy/local-only.d.ts +142 -2
  6. package/dist/core/policy/local-only.js +204 -15
  7. package/dist/core/policy/local-only.js.map +1 -1
  8. package/dist/core/resources/native-profile.d.ts +56 -0
  9. package/dist/core/resources/native-profile.js +368 -6
  10. package/dist/core/resources/native-profile.js.map +1 -1
  11. package/dist/core/run/budget.d.ts +22 -8
  12. package/dist/core/run/budget.js +80 -12
  13. package/dist/core/run/budget.js.map +1 -1
  14. package/dist/core/run/model-catalog.js +8 -1
  15. package/dist/core/run/model-catalog.js.map +1 -1
  16. package/dist/core/run/sandboxed-engine.js +3 -3
  17. package/dist/core/run/sandboxed-engine.js.map +1 -1
  18. package/dist/core/universe/builtins/preparation/manifest.json +1 -1
  19. package/dist/core/universe/builtins/preparation/preparation-bridge.mjs +69 -10
  20. package/dist/core/universe/builtins/preparation/preparation-verification-fixtures.mjs +68 -9
  21. package/dist/core/verse/adapters/claude.d.ts +49 -2
  22. package/dist/core/verse/adapters/claude.js +317 -56
  23. package/dist/core/verse/adapters/claude.js.map +1 -1
  24. package/dist/core/verse/adapters/codex.d.ts +81 -5
  25. package/dist/core/verse/adapters/codex.js +409 -47
  26. package/dist/core/verse/adapters/codex.js.map +1 -1
  27. package/dist/core/verse/adapters/grok.d.ts +13 -1
  28. package/dist/core/verse/adapters/grok.js +28 -2
  29. package/dist/core/verse/adapters/grok.js.map +1 -1
  30. package/dist/core/verse/adapters/index.d.ts +32 -0
  31. package/dist/core/verse/adapters/index.js +2 -0
  32. package/dist/core/verse/adapters/index.js.map +1 -1
  33. package/dist/core/verse/codex-rollout.d.ts +284 -0
  34. package/dist/core/verse/codex-rollout.js +786 -0
  35. package/dist/core/verse/codex-rollout.js.map +1 -0
  36. package/dist/core/verse/context-fit.d.ts +63 -0
  37. package/dist/core/verse/context-fit.js +355 -0
  38. package/dist/core/verse/context-fit.js.map +1 -0
  39. package/dist/core/verse/context-math.d.ts +215 -0
  40. package/dist/core/verse/context-math.js +365 -0
  41. package/dist/core/verse/context-math.js.map +1 -0
  42. package/dist/core/verse/local-models.d.ts +137 -1
  43. package/dist/core/verse/local-models.js +209 -5
  44. package/dist/core/verse/local-models.js.map +1 -1
  45. package/dist/core/verse/model-windows.d.ts +217 -0
  46. package/dist/core/verse/model-windows.js +616 -0
  47. package/dist/core/verse/model-windows.js.map +1 -0
  48. package/dist/core/verse/preferences.d.ts +123 -0
  49. package/dist/core/verse/preferences.js +403 -0
  50. package/dist/core/verse/preferences.js.map +1 -0
  51. package/dist/core/verse/project-memory.d.ts +112 -0
  52. package/dist/core/verse/project-memory.js +295 -0
  53. package/dist/core/verse/project-memory.js.map +1 -0
  54. package/dist/core/verse/seats.d.ts +106 -4
  55. package/dist/core/verse/seats.js +291 -89
  56. package/dist/core/verse/seats.js.map +1 -1
  57. package/dist/core/verse/session-engine.d.ts +117 -2
  58. package/dist/core/verse/session-engine.js +763 -65
  59. package/dist/core/verse/session-engine.js.map +1 -1
  60. package/dist/core/verse/session-handoff.d.ts +94 -0
  61. package/dist/core/verse/session-handoff.js +551 -0
  62. package/dist/core/verse/session-handoff.js.map +1 -0
  63. package/dist/core/verse/session-search.d.ts +63 -0
  64. package/dist/core/verse/session-search.js +213 -0
  65. package/dist/core/verse/session-search.js.map +1 -0
  66. package/dist/core/verse/session-store.d.ts +16 -2
  67. package/dist/core/verse/session-store.js +80 -6
  68. package/dist/core/verse/session-store.js.map +1 -1
  69. package/dist/core/verse/types.d.ts +333 -1
  70. package/dist/core/verse/types.js +54 -2
  71. package/dist/core/verse/types.js.map +1 -1
  72. package/dist/core/verse/verse-api.d.ts +40 -5
  73. package/dist/core/verse/verse-api.js +724 -25
  74. package/dist/core/verse/verse-api.js.map +1 -1
  75. package/dist/core/web/api.d.ts +14 -4
  76. package/dist/core/web/api.js +26 -11
  77. package/dist/core/web/api.js.map +1 -1
  78. package/dist/core/web/public/next/assets/{App-vtaHB9mp.js → App-XURmiAAp.js} +5 -5
  79. package/dist/core/web/public/next/assets/{App-vtaHB9mp.js.map → App-XURmiAAp.js.map} +1 -1
  80. package/dist/core/web/public/next/assets/{ApprovalsSection-B-bFJ0I9.js → ApprovalsSection-BriUzoZc.js} +2 -2
  81. package/dist/core/web/public/next/assets/{ApprovalsSection-B-bFJ0I9.js.map → ApprovalsSection-BriUzoZc.js.map} +1 -1
  82. package/dist/core/web/public/next/assets/AutonomySection-C4GBsQVc.js +2 -0
  83. package/dist/core/web/public/next/assets/{AutonomySection-DoqmsCjw.js.map → AutonomySection-C4GBsQVc.js.map} +1 -1
  84. package/dist/core/web/public/next/assets/ChatSection-BpOBOUvg.css +1 -0
  85. package/dist/core/web/public/next/assets/ChatSection-hlgPBRco.js +84 -0
  86. package/dist/core/web/public/next/assets/ChatSection-hlgPBRco.js.map +1 -0
  87. package/dist/core/web/public/next/assets/ConfirmDialog-BdQYI-Ng.js +2 -0
  88. package/dist/core/web/public/next/assets/ConfirmDialog-BdQYI-Ng.js.map +1 -0
  89. package/dist/core/web/public/next/assets/{Dialog-CPM9JxBc.js → Dialog-BxdIfmFH.js} +2 -2
  90. package/dist/core/web/public/next/assets/{Dialog-CPM9JxBc.js.map → Dialog-BxdIfmFH.js.map} +1 -1
  91. package/dist/core/web/public/next/assets/{DiffViewer-B84qVef9.js → DiffViewer-DmeRWuc4.js} +2 -2
  92. package/dist/core/web/public/next/assets/{DiffViewer-B84qVef9.js.map → DiffViewer-DmeRWuc4.js.map} +1 -1
  93. package/dist/core/web/public/next/assets/{McpSection-CEUpV_5X.js → McpSection-q34T7Tho.js} +2 -2
  94. package/dist/core/web/public/next/assets/{McpSection-CEUpV_5X.js.map → McpSection-q34T7Tho.js.map} +1 -1
  95. package/dist/core/web/public/next/assets/{MutationTokenDialog-DA8t97XT.js → MutationTokenDialog-CFAkwOEt.js} +2 -2
  96. package/dist/core/web/public/next/assets/{MutationTokenDialog-DA8t97XT.js.map → MutationTokenDialog-CFAkwOEt.js.map} +1 -1
  97. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-Bq7Vyud0.js → ResourcePoolConsoleApp-DHc8GFOY.js} +4 -4
  98. package/dist/core/web/public/next/assets/{ResourcePoolConsoleApp-Bq7Vyud0.js.map → ResourcePoolConsoleApp-DHc8GFOY.js.map} +1 -1
  99. package/dist/core/web/public/next/assets/{RouteErrorBoundary-Badio_uO.js → RouteErrorBoundary-ZL9F4qUn.js} +2 -2
  100. package/dist/core/web/public/next/assets/{RouteErrorBoundary-Badio_uO.js.map → RouteErrorBoundary-ZL9F4qUn.js.map} +1 -1
  101. package/dist/core/web/public/next/assets/{Meter-CLcr-4wS.css → Segmented-8cf0Fypn.css} +1 -1
  102. package/dist/core/web/public/next/assets/{Meter--h3MmZS-.js → Segmented-DbRjVye3.js} +2 -2
  103. package/dist/core/web/public/next/assets/Segmented-DbRjVye3.js.map +1 -0
  104. package/dist/core/web/public/next/assets/SettingsSection-nJGGGYqv.js +2 -0
  105. package/dist/core/web/public/next/assets/{SettingsSection-BwrpQlpP.js.map → SettingsSection-nJGGGYqv.js.map} +1 -1
  106. package/dist/core/web/public/next/assets/UniverseConsoleApp-6GJydgo4.js +2 -0
  107. package/dist/core/web/public/next/assets/{UniverseConsoleApp-_FSohAdN.js.map → UniverseConsoleApp-6GJydgo4.js.map} +1 -1
  108. package/dist/core/web/public/next/assets/{UniverseView-DqcXbed4.js → UniverseView-DLDtNYL8.js} +2 -2
  109. package/dist/core/web/public/next/assets/{UniverseView-DqcXbed4.js.map → UniverseView-DLDtNYL8.js.map} +1 -1
  110. package/dist/core/web/public/next/assets/UsageSection-Cf9d9iFH.js +2 -0
  111. package/dist/core/web/public/next/assets/UsageSection-Cf9d9iFH.js.map +1 -0
  112. package/dist/core/web/public/next/assets/VerseConsoleApp-DsdKQoTH.css +1 -0
  113. package/dist/core/web/public/next/assets/VerseConsoleApp-y0uOHixd.js +3 -0
  114. package/dist/core/web/public/next/assets/VerseConsoleApp-y0uOHixd.js.map +1 -0
  115. package/dist/core/web/public/next/assets/{charts-CTZJ-JrH.js → charts-CVooqJWN.js} +2 -2
  116. package/dist/core/web/public/next/assets/{charts-CTZJ-JrH.js.map → charts-CVooqJWN.js.map} +1 -1
  117. package/dist/core/web/public/next/assets/context-model-Bqv8DVuH.js +2 -0
  118. package/dist/core/web/public/next/assets/context-model-Bqv8DVuH.js.map +1 -0
  119. package/dist/core/web/public/next/assets/global-eaY9GyX3.js +2 -0
  120. package/dist/core/web/public/next/assets/global-eaY9GyX3.js.map +1 -0
  121. package/dist/core/web/public/next/assets/{icons-D9vlf85u.js → icons-vex8rclC.js} +2 -2
  122. package/dist/core/web/public/next/assets/{icons-D9vlf85u.js.map → icons-vex8rclC.js.map} +1 -1
  123. package/dist/core/web/public/next/assets/{index-CZamKRUg.js → index-D2Eik-zt.js} +3 -3
  124. package/dist/core/web/public/next/assets/{index-CZamKRUg.js.map → index-D2Eik-zt.js.map} +1 -1
  125. package/dist/core/web/public/next/assets/{queries-B8eBnFox.js → queries-bAYU0VNy.js} +2 -2
  126. package/dist/core/web/public/next/assets/{queries-B8eBnFox.js.map → queries-bAYU0VNy.js.map} +1 -1
  127. package/dist/core/web/public/next/assets/use-guarded-action-C7QrP7Yp.js +2 -0
  128. package/dist/core/web/public/next/assets/{use-guarded-action-bzrVVAYq.js.map → use-guarded-action-C7QrP7Yp.js.map} +1 -1
  129. package/dist/core/web/public/next/assets/verse-model-TfDk9cky.js +2 -0
  130. package/dist/core/web/public/next/assets/verse-model-TfDk9cky.js.map +1 -0
  131. package/dist/core/web/public/next/assets/{workspace-model-v0a1gl-C.js → workspace-model-CHol5RVp.js} +2 -2
  132. package/dist/core/web/public/next/assets/{workspace-model-v0a1gl-C.js.map → workspace-model-CHol5RVp.js.map} +1 -1
  133. package/dist/core/web/public/next/index.html +1 -1
  134. package/dist/release-dependency-inventory.json +1 -1
  135. package/docs/README.md +1 -0
  136. package/package.json +1 -1
  137. package/dist/core/web/public/next/assets/AutonomySection-DoqmsCjw.js +0 -2
  138. package/dist/core/web/public/next/assets/ChatSection-BfnLe5dT.css +0 -1
  139. package/dist/core/web/public/next/assets/ChatSection-vNPIJffd.js +0 -80
  140. package/dist/core/web/public/next/assets/ChatSection-vNPIJffd.js.map +0 -1
  141. package/dist/core/web/public/next/assets/ConfirmDialog-Dd2APQhB.js +0 -2
  142. package/dist/core/web/public/next/assets/ConfirmDialog-Dd2APQhB.js.map +0 -1
  143. package/dist/core/web/public/next/assets/Meter--h3MmZS-.js.map +0 -1
  144. package/dist/core/web/public/next/assets/SettingsSection-BwrpQlpP.js +0 -2
  145. package/dist/core/web/public/next/assets/UniverseConsoleApp-_FSohAdN.js +0 -2
  146. package/dist/core/web/public/next/assets/UsageSection-Bq6Daaby.js +0 -2
  147. package/dist/core/web/public/next/assets/UsageSection-Bq6Daaby.js.map +0 -1
  148. package/dist/core/web/public/next/assets/VerseConsoleApp-CMkCizvL.css +0 -1
  149. package/dist/core/web/public/next/assets/VerseConsoleApp-CsIQv9id.js +0 -3
  150. package/dist/core/web/public/next/assets/VerseConsoleApp-CsIQv9id.js.map +0 -1
  151. package/dist/core/web/public/next/assets/global-D6bTxIRn.js +0 -2
  152. package/dist/core/web/public/next/assets/global-D6bTxIRn.js.map +0 -1
  153. package/dist/core/web/public/next/assets/use-guarded-action-bzrVVAYq.js +0 -2
  154. package/dist/core/web/public/next/assets/verse-model-CvtK6vP6.js +0 -2
  155. package/dist/core/web/public/next/assets/verse-model-CvtK6vP6.js.map +0 -1
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Verse operator preferences — `<verse root>/preferences.json` (default
3
+ * `~/.ashlr/verse/preferences.json`, 0600 inside a 0700 directory).
4
+ *
5
+ * What lives here is deliberately SMALL and STANDING: choices the operator made
6
+ * once and expects every future session to honour.
7
+ *
8
+ * seats.<seatId>.contextMode default context mode for NEW sessions on a seat
9
+ * memory.enabled shared project memory, globally (default ON)
10
+ * memory.disabledProjects canonical project paths that opted out
11
+ *
12
+ * Nothing here is consulted by a RUNNING session. A session pins its mode and
13
+ * whether it was offered memory at creation (session.contextMode,
14
+ * session.memoryEnabled, launch.memory), so flipping a preference can never
15
+ * silently change what an in-flight conversation can reach or what it costs.
16
+ *
17
+ * HONESTY / SAFETY RULES
18
+ * - Absent means default. A seat set back to `standard` is REMOVED rather than
19
+ * stored, so the file only ever records a deviation from the default.
20
+ * - Load is TOTAL: a missing, unreadable, oversized or hand-mangled file yields
21
+ * the defaults (field by field — one bad seat entry does not discard the
22
+ * memory opt-outs). The server must boot on any file.
23
+ * - Update is STRICT: exactly one of the three `VersePreferencesUpdate` forms,
24
+ * no unknown keys, every value validated. A body can never change more than
25
+ * the operator clicked (types.ts `VersePreferencesUpdate`).
26
+ * - Writes are atomic (O_EXCL|O_NOFOLLOW temp, fchmod 0600, fsync, rename) and
27
+ * refuse to write through a symlinked directory.
28
+ *
29
+ * Spends nothing, reads no secrets, starts no process.
30
+ */
31
+ import { type VerseContextMode, type VersePreferences, type VersePreferencesUpdate } from './types.js';
32
+ /**
33
+ * The error every V3.9 service module (preferences, project memory, handoff,
34
+ * search, context fit) throws for a bad request.
35
+ *
36
+ * Shaped like session-engine's `VerseError` — same `code` vocabulary, same
37
+ * status mapping — because verse-api maps errors by DUCK TYPE on `.code`, so a
38
+ * throw from here becomes the contract's 400/413 with no extra wiring. It is a
39
+ * separate class, not an import of `VerseError`, on purpose: these modules are
40
+ * pure services and must not link against the session engine (and, through
41
+ * it, every adapter) just to report a validation failure.
42
+ */
43
+ export type VerseServiceErrorCode = 'VERSE_INVALID' | 'VERSE_TOO_LARGE';
44
+ export declare class VerseServiceError extends Error {
45
+ readonly code: VerseServiceErrorCode;
46
+ readonly status: 400 | 413;
47
+ constructor(code: VerseServiceErrorCode, message: string);
48
+ }
49
+ export declare const VERSE_PREFERENCES_FILE = "preferences.json";
50
+ /** Registry bounds — a blast radius for a runaway client, not a UI limit. */
51
+ export declare const VERSE_PREFERENCES_MAX_SEATS = 500;
52
+ export declare const VERSE_PREFERENCES_MAX_DISABLED_PROJECTS = 1000;
53
+ /** The Verse store root every service module defaults to (same as the session engine). */
54
+ export declare function defaultVerseRoot(): string;
55
+ export declare function defaultVersePreferences(): VersePreferences;
56
+ /**
57
+ * mkdir -p with 0700 on every directory we CREATE, then refuse to proceed if
58
+ * the final directory is a symlink or is not ours. Pre-existing ancestors are
59
+ * left alone (they may legitimately be the operator's).
60
+ */
61
+ export declare function ensurePrivateDirectory(path: string): void;
62
+ /**
63
+ * Create-exclusive temp file beside the target (O_NOFOLLOW), write, fchmod
64
+ * 0600, fsync, rename over the target. The temp name starts with `.` and ends
65
+ * with `.tmp` so directory listings can skip an orphan from a crash.
66
+ */
67
+ export declare function writePrivateFileAtomic(target: string, content: string): void;
68
+ /**
69
+ * Read a regular file without following a symlink at its name, up to
70
+ * `maxBytes`. Null when absent, not a regular file, or unreadable. `truncated`
71
+ * says whether the file was longer than the cap.
72
+ */
73
+ export declare function readPrivateFileCapped(path: string, maxBytes: number): {
74
+ text: string;
75
+ bytes: number;
76
+ mtimeMs: number;
77
+ truncated: boolean;
78
+ } | null;
79
+ /** Decode bytes, dropping a trailing partial UTF-8 sequence rather than emitting U+FFFD. */
80
+ export declare function utf8Prefix(bytes: Buffer): string;
81
+ /**
82
+ * The spelling a project path is stored and compared under: absolute, symlinks
83
+ * resolved when the directory exists (so `/tmp/x` and `/private/tmp/x` are one
84
+ * project on macOS). Throws VERSE_INVALID for anything that is not an absolute
85
+ * path — callers validate reachability with path-guard before they get here.
86
+ */
87
+ export declare function canonicalProjectPath(projectPath: string): string;
88
+ export declare function versePreferencesPath(root?: string): string;
89
+ /** The operator's preferences, or the defaults. Total: never throws. */
90
+ export declare function loadVersePreferences(root?: string): VersePreferences;
91
+ type ParsedUpdate = {
92
+ kind: 'seat';
93
+ seatId: string;
94
+ contextMode: VerseContextMode;
95
+ } | {
96
+ kind: 'memory';
97
+ memoryEnabled: boolean;
98
+ } | {
99
+ kind: 'project';
100
+ projectPath: string;
101
+ memoryEnabled: boolean;
102
+ };
103
+ /**
104
+ * Validate an update body against EXACTLY one of the three forms. Exported so
105
+ * the API can reject a malformed body before touching disk, with the same
106
+ * wording this module would use.
107
+ */
108
+ export declare function parseVersePreferencesUpdate(value: unknown): ParsedUpdate;
109
+ /**
110
+ * Apply ONE validated change and persist atomically. Returns the full
111
+ * preferences as stored. Throws VERSE_INVALID for a malformed update (nothing
112
+ * is written) and VERSE_TOO_LARGE when a registry bound would be exceeded.
113
+ */
114
+ export declare function updateVersePreferences(update: VersePreferencesUpdate, root?: string): VersePreferences;
115
+ /**
116
+ * Should a NEW session on this project be offered shared memory? Global switch
117
+ * AND not opted out. A path that cannot be canonicalised is treated as
118
+ * disabled — never "enabled because we could not check".
119
+ */
120
+ export declare function memoryEnabledFor(prefs: VersePreferences, projectPath: string): boolean;
121
+ /** The context mode a new session on this seat starts in. */
122
+ export declare function seatDefaultMode(prefs: VersePreferences, seatId: string): VerseContextMode;
123
+ export {};
@@ -0,0 +1,403 @@
1
+ /**
2
+ * Verse operator preferences — `<verse root>/preferences.json` (default
3
+ * `~/.ashlr/verse/preferences.json`, 0600 inside a 0700 directory).
4
+ *
5
+ * What lives here is deliberately SMALL and STANDING: choices the operator made
6
+ * once and expects every future session to honour.
7
+ *
8
+ * seats.<seatId>.contextMode default context mode for NEW sessions on a seat
9
+ * memory.enabled shared project memory, globally (default ON)
10
+ * memory.disabledProjects canonical project paths that opted out
11
+ *
12
+ * Nothing here is consulted by a RUNNING session. A session pins its mode and
13
+ * whether it was offered memory at creation (session.contextMode,
14
+ * session.memoryEnabled, launch.memory), so flipping a preference can never
15
+ * silently change what an in-flight conversation can reach or what it costs.
16
+ *
17
+ * HONESTY / SAFETY RULES
18
+ * - Absent means default. A seat set back to `standard` is REMOVED rather than
19
+ * stored, so the file only ever records a deviation from the default.
20
+ * - Load is TOTAL: a missing, unreadable, oversized or hand-mangled file yields
21
+ * the defaults (field by field — one bad seat entry does not discard the
22
+ * memory opt-outs). The server must boot on any file.
23
+ * - Update is STRICT: exactly one of the three `VersePreferencesUpdate` forms,
24
+ * no unknown keys, every value validated. A body can never change more than
25
+ * the operator clicked (types.ts `VersePreferencesUpdate`).
26
+ * - Writes are atomic (O_EXCL|O_NOFOLLOW temp, fchmod 0600, fsync, rename) and
27
+ * refuse to write through a symlinked directory.
28
+ *
29
+ * Spends nothing, reads no secrets, starts no process.
30
+ */
31
+ import { closeSync, constants as fsConstants, fchmodSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readSync, renameSync, rmSync, writeSync, } from 'node:fs';
32
+ import { randomBytes } from 'node:crypto';
33
+ import { homedir } from 'node:os';
34
+ import { basename, dirname, isAbsolute, join, resolve as resolvePath } from 'node:path';
35
+ import { physicalPath } from './path-guard.js';
36
+ import { VERSE_CONTEXT_MODES, } from './types.js';
37
+ export class VerseServiceError extends Error {
38
+ code;
39
+ status;
40
+ constructor(code, message) {
41
+ super(message);
42
+ this.name = 'VerseServiceError';
43
+ this.code = code;
44
+ this.status = code === 'VERSE_TOO_LARGE' ? 413 : 400;
45
+ }
46
+ }
47
+ // ---------------------------------------------------------------------------
48
+ // Constants
49
+ // ---------------------------------------------------------------------------
50
+ export const VERSE_PREFERENCES_FILE = 'preferences.json';
51
+ /** A preferences file larger than this is not ours; treat it as absent. */
52
+ const MAX_PREFERENCES_BYTES = 256 * 1024;
53
+ /** Registry bounds — a blast radius for a runaway client, not a UI limit. */
54
+ export const VERSE_PREFERENCES_MAX_SEATS = 500;
55
+ export const VERSE_PREFERENCES_MAX_DISABLED_PROJECTS = 1000;
56
+ /** Same spelling rule the seat catalog uses for ids (`local:qwen3:ctx64k`, `codex-personal`). */
57
+ const SEAT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:@/+-]{0,199}$/;
58
+ const MAX_PATH_CHARS = 4096;
59
+ /** The Verse store root every service module defaults to (same as the session engine). */
60
+ export function defaultVerseRoot() {
61
+ // Re-resolved per call so a relocated HOME (tests) is honoured.
62
+ return join(homedir(), '.ashlr', 'verse');
63
+ }
64
+ export function defaultVersePreferences() {
65
+ return { version: 1, seats: {}, memory: { enabled: true, disabledProjects: [] } };
66
+ }
67
+ // ---------------------------------------------------------------------------
68
+ // Private-file primitives (shared with project-memory.ts)
69
+ // ---------------------------------------------------------------------------
70
+ function isObject(value) {
71
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
72
+ }
73
+ /**
74
+ * mkdir -p with 0700 on every directory we CREATE, then refuse to proceed if
75
+ * the final directory is a symlink or is not ours. Pre-existing ancestors are
76
+ * left alone (they may legitimately be the operator's).
77
+ */
78
+ export function ensurePrivateDirectory(path) {
79
+ mkdirSync(path, { recursive: true, mode: 0o700 });
80
+ const stat = lstatSync(path);
81
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
82
+ throw new Error(`refusing to use ${path}: not a real directory`);
83
+ }
84
+ if (typeof process.getuid === 'function' && stat.uid !== process.getuid()) {
85
+ throw new Error(`refusing to use ${path}: owned by another user`);
86
+ }
87
+ }
88
+ /**
89
+ * Create-exclusive temp file beside the target (O_NOFOLLOW), write, fchmod
90
+ * 0600, fsync, rename over the target. The temp name starts with `.` and ends
91
+ * with `.tmp` so directory listings can skip an orphan from a crash.
92
+ */
93
+ export function writePrivateFileAtomic(target, content) {
94
+ const temp = join(dirname(target), `.${basename(target)}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`);
95
+ const noFollow = typeof fsConstants.O_NOFOLLOW === 'number' ? fsConstants.O_NOFOLLOW : 0;
96
+ const fd = openSync(temp, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | noFollow, 0o600);
97
+ let published = false;
98
+ try {
99
+ const bytes = Buffer.from(content, 'utf8');
100
+ let offset = 0;
101
+ while (offset < bytes.length) {
102
+ const written = writeSync(fd, bytes, offset, bytes.length - offset, offset);
103
+ if (written <= 0)
104
+ throw new Error('verse private write made no progress');
105
+ offset += written;
106
+ }
107
+ fchmodSync(fd, 0o600);
108
+ fsyncSync(fd);
109
+ // rename() replaces a symlink at the target NAME (the file it pointed at
110
+ // is never written), so a planted link cannot redirect this write. A
111
+ // directory there is refused up front so the error names the real problem.
112
+ let existing = null;
113
+ try {
114
+ existing = lstatSync(target);
115
+ }
116
+ catch {
117
+ existing = null;
118
+ }
119
+ if (existing && !existing.isFile() && !existing.isSymbolicLink()) {
120
+ throw new Error(`refusing to replace ${target}: not a regular file`);
121
+ }
122
+ renameSync(temp, target);
123
+ published = true;
124
+ }
125
+ finally {
126
+ closeSync(fd);
127
+ if (!published) {
128
+ try {
129
+ rmSync(temp, { force: true });
130
+ }
131
+ catch { /* best effort */ }
132
+ }
133
+ }
134
+ }
135
+ /**
136
+ * Read a regular file without following a symlink at its name, up to
137
+ * `maxBytes`. Null when absent, not a regular file, or unreadable. `truncated`
138
+ * says whether the file was longer than the cap.
139
+ */
140
+ export function readPrivateFileCapped(path, maxBytes) {
141
+ const noFollow = typeof fsConstants.O_NOFOLLOW === 'number' ? fsConstants.O_NOFOLLOW : 0;
142
+ let fd;
143
+ try {
144
+ fd = openSync(path, fsConstants.O_RDONLY | noFollow);
145
+ }
146
+ catch {
147
+ return null;
148
+ }
149
+ try {
150
+ const stat = fstatSync(fd);
151
+ if (!stat.isFile())
152
+ return null;
153
+ const want = Math.min(stat.size, maxBytes);
154
+ const buffer = Buffer.alloc(want);
155
+ let offset = 0;
156
+ while (offset < want) {
157
+ const read = readSync(fd, buffer, offset, want - offset, offset);
158
+ if (read <= 0)
159
+ break;
160
+ offset += read;
161
+ }
162
+ const slice = buffer.subarray(0, offset);
163
+ return {
164
+ text: utf8Prefix(slice),
165
+ bytes: stat.size,
166
+ mtimeMs: stat.mtimeMs,
167
+ truncated: stat.size > maxBytes,
168
+ };
169
+ }
170
+ catch {
171
+ return null;
172
+ }
173
+ finally {
174
+ closeSync(fd);
175
+ }
176
+ }
177
+ /** Decode bytes, dropping a trailing partial UTF-8 sequence rather than emitting U+FFFD. */
178
+ export function utf8Prefix(bytes) {
179
+ let end = bytes.length;
180
+ // Walk back over at most 3 continuation bytes to the lead byte of the last char.
181
+ let i = end - 1;
182
+ let continuation = 0;
183
+ while (i >= 0 && continuation < 4 && (bytes[i] & 0xc0) === 0x80) {
184
+ i -= 1;
185
+ continuation += 1;
186
+ }
187
+ if (i >= 0) {
188
+ const lead = bytes[i];
189
+ const need = lead >= 0xf0 ? 4 : lead >= 0xe0 ? 3 : lead >= 0xc0 ? 2 : 1;
190
+ if (need > 1 && continuation + 1 < need)
191
+ end = i;
192
+ }
193
+ return bytes.subarray(0, end).toString('utf8');
194
+ }
195
+ // ---------------------------------------------------------------------------
196
+ // Canonical project paths
197
+ // ---------------------------------------------------------------------------
198
+ /**
199
+ * The spelling a project path is stored and compared under: absolute, symlinks
200
+ * resolved when the directory exists (so `/tmp/x` and `/private/tmp/x` are one
201
+ * project on macOS). Throws VERSE_INVALID for anything that is not an absolute
202
+ * path — callers validate reachability with path-guard before they get here.
203
+ */
204
+ export function canonicalProjectPath(projectPath) {
205
+ if (typeof projectPath !== 'string' || projectPath.length === 0) {
206
+ throw new VerseServiceError('VERSE_INVALID', 'projectPath is required');
207
+ }
208
+ if (projectPath.length > MAX_PATH_CHARS) {
209
+ throw new VerseServiceError('VERSE_INVALID', `projectPath must be at most ${MAX_PATH_CHARS} characters`);
210
+ }
211
+ if (projectPath.includes('\0')) {
212
+ throw new VerseServiceError('VERSE_INVALID', 'projectPath must not contain NUL bytes');
213
+ }
214
+ const expanded = projectPath === '~' ? homedir() : projectPath.startsWith('~/') ? join(homedir(), projectPath.slice(2)) : projectPath;
215
+ if (!isAbsolute(expanded)) {
216
+ throw new VerseServiceError('VERSE_INVALID', 'projectPath must be an absolute path');
217
+ }
218
+ const lexical = resolvePath(expanded);
219
+ return physicalPath(lexical) ?? lexical;
220
+ }
221
+ // ---------------------------------------------------------------------------
222
+ // Load
223
+ // ---------------------------------------------------------------------------
224
+ function isContextMode(value) {
225
+ return typeof value === 'string' && VERSE_CONTEXT_MODES.includes(value);
226
+ }
227
+ /** Field-by-field salvage of whatever is on disk. Never throws. */
228
+ function sanitizePreferences(raw) {
229
+ const out = defaultVersePreferences();
230
+ if (!isObject(raw))
231
+ return out;
232
+ const seats = raw['seats'];
233
+ if (isObject(seats)) {
234
+ let kept = 0;
235
+ for (const [seatId, entry] of Object.entries(seats)) {
236
+ if (kept >= VERSE_PREFERENCES_MAX_SEATS)
237
+ break;
238
+ if (!SEAT_ID_RE.test(seatId) || !isObject(entry))
239
+ continue;
240
+ const mode = entry['contextMode'];
241
+ // Only a deviation from the default is worth keeping.
242
+ if (isContextMode(mode) && mode !== 'standard') {
243
+ out.seats[seatId] = { contextMode: mode };
244
+ kept += 1;
245
+ }
246
+ }
247
+ }
248
+ const memory = raw['memory'];
249
+ if (isObject(memory)) {
250
+ if (typeof memory['enabled'] === 'boolean')
251
+ out.memory.enabled = memory['enabled'];
252
+ const disabled = memory['disabledProjects'];
253
+ if (Array.isArray(disabled)) {
254
+ const seen = new Set();
255
+ for (const item of disabled) {
256
+ if (seen.size >= VERSE_PREFERENCES_MAX_DISABLED_PROJECTS)
257
+ break;
258
+ if (typeof item !== 'string' || item.length === 0 || item.length > MAX_PATH_CHARS)
259
+ continue;
260
+ if (item.includes('\0') || !isAbsolute(item))
261
+ continue;
262
+ seen.add(item);
263
+ }
264
+ out.memory.disabledProjects = [...seen].sort();
265
+ }
266
+ }
267
+ return out;
268
+ }
269
+ export function versePreferencesPath(root = defaultVerseRoot()) {
270
+ return join(root, VERSE_PREFERENCES_FILE);
271
+ }
272
+ /** The operator's preferences, or the defaults. Total: never throws. */
273
+ export function loadVersePreferences(root = defaultVerseRoot()) {
274
+ const file = readPrivateFileCapped(versePreferencesPath(root), MAX_PREFERENCES_BYTES);
275
+ if (!file || file.truncated)
276
+ return defaultVersePreferences();
277
+ try {
278
+ return sanitizePreferences(JSON.parse(file.text));
279
+ }
280
+ catch {
281
+ return defaultVersePreferences();
282
+ }
283
+ }
284
+ function sameKeys(value, keys) {
285
+ const own = Object.keys(value);
286
+ return own.length === keys.length && keys.every((key) => Object.prototype.hasOwnProperty.call(value, key));
287
+ }
288
+ /**
289
+ * Validate an update body against EXACTLY one of the three forms. Exported so
290
+ * the API can reject a malformed body before touching disk, with the same
291
+ * wording this module would use.
292
+ */
293
+ export function parseVersePreferencesUpdate(value) {
294
+ if (!isObject(value)) {
295
+ throw new VerseServiceError('VERSE_INVALID', 'preferences update must be a JSON object');
296
+ }
297
+ if (sameKeys(value, ['seatId', 'contextMode'])) {
298
+ const seatId = value['seatId'];
299
+ const mode = value['contextMode'];
300
+ if (typeof seatId !== 'string' || !SEAT_ID_RE.test(seatId)) {
301
+ throw new VerseServiceError('VERSE_INVALID', 'seatId must be a seat id');
302
+ }
303
+ if (!isContextMode(mode)) {
304
+ throw new VerseServiceError('VERSE_INVALID', `contextMode must be one of: ${VERSE_CONTEXT_MODES.join(', ')}`);
305
+ }
306
+ return { kind: 'seat', seatId, contextMode: mode };
307
+ }
308
+ if (sameKeys(value, ['memoryEnabled'])) {
309
+ if (typeof value['memoryEnabled'] !== 'boolean') {
310
+ throw new VerseServiceError('VERSE_INVALID', 'memoryEnabled must be a boolean');
311
+ }
312
+ return { kind: 'memory', memoryEnabled: value['memoryEnabled'] };
313
+ }
314
+ if (sameKeys(value, ['projectPath', 'memoryEnabled'])) {
315
+ const projectPath = value['projectPath'];
316
+ if (typeof projectPath !== 'string') {
317
+ throw new VerseServiceError('VERSE_INVALID', 'projectPath must be a string');
318
+ }
319
+ if (typeof value['memoryEnabled'] !== 'boolean') {
320
+ throw new VerseServiceError('VERSE_INVALID', 'memoryEnabled must be a boolean');
321
+ }
322
+ return { kind: 'project', projectPath: canonicalProjectPath(projectPath), memoryEnabled: value['memoryEnabled'] };
323
+ }
324
+ throw new VerseServiceError('VERSE_INVALID', 'preferences update must be exactly one of {seatId, contextMode}, {memoryEnabled}, or {projectPath, memoryEnabled}');
325
+ }
326
+ /**
327
+ * Apply ONE validated change and persist atomically. Returns the full
328
+ * preferences as stored. Throws VERSE_INVALID for a malformed update (nothing
329
+ * is written) and VERSE_TOO_LARGE when a registry bound would be exceeded.
330
+ */
331
+ export function updateVersePreferences(update, root = defaultVerseRoot()) {
332
+ const parsed = parseVersePreferencesUpdate(update);
333
+ const prefs = loadVersePreferences(root);
334
+ switch (parsed.kind) {
335
+ case 'seat': {
336
+ if (parsed.contextMode === 'standard') {
337
+ delete prefs.seats[parsed.seatId];
338
+ }
339
+ else {
340
+ if (!(parsed.seatId in prefs.seats) && Object.keys(prefs.seats).length >= VERSE_PREFERENCES_MAX_SEATS) {
341
+ throw new VerseServiceError('VERSE_TOO_LARGE', `at most ${VERSE_PREFERENCES_MAX_SEATS} seats can carry a preference`);
342
+ }
343
+ prefs.seats[parsed.seatId] = { contextMode: parsed.contextMode };
344
+ }
345
+ break;
346
+ }
347
+ case 'memory':
348
+ prefs.memory.enabled = parsed.memoryEnabled;
349
+ break;
350
+ case 'project': {
351
+ const set = new Set(prefs.memory.disabledProjects);
352
+ if (parsed.memoryEnabled) {
353
+ set.delete(parsed.projectPath);
354
+ }
355
+ else {
356
+ if (!set.has(parsed.projectPath) && set.size >= VERSE_PREFERENCES_MAX_DISABLED_PROJECTS) {
357
+ throw new VerseServiceError('VERSE_TOO_LARGE', `at most ${VERSE_PREFERENCES_MAX_DISABLED_PROJECTS} projects can opt out of memory`);
358
+ }
359
+ set.add(parsed.projectPath);
360
+ }
361
+ prefs.memory.disabledProjects = [...set].sort();
362
+ break;
363
+ }
364
+ }
365
+ ensurePrivateDirectory(root);
366
+ // Stable key order so the file diffs cleanly and two equal states are byte-identical.
367
+ const seats = {};
368
+ for (const seatId of Object.keys(prefs.seats).sort())
369
+ seats[seatId] = prefs.seats[seatId];
370
+ const stored = {
371
+ version: 1,
372
+ seats,
373
+ memory: { enabled: prefs.memory.enabled, disabledProjects: prefs.memory.disabledProjects },
374
+ };
375
+ writePrivateFileAtomic(versePreferencesPath(root), `${JSON.stringify(stored, null, 2)}\n`);
376
+ return stored;
377
+ }
378
+ // ---------------------------------------------------------------------------
379
+ // Helpers the API resolves creation defaults through
380
+ // ---------------------------------------------------------------------------
381
+ /**
382
+ * Should a NEW session on this project be offered shared memory? Global switch
383
+ * AND not opted out. A path that cannot be canonicalised is treated as
384
+ * disabled — never "enabled because we could not check".
385
+ */
386
+ export function memoryEnabledFor(prefs, projectPath) {
387
+ if (!prefs.memory.enabled)
388
+ return false;
389
+ let canonical;
390
+ try {
391
+ canonical = canonicalProjectPath(projectPath);
392
+ }
393
+ catch {
394
+ return false;
395
+ }
396
+ return !prefs.memory.disabledProjects.includes(canonical);
397
+ }
398
+ /** The context mode a new session on this seat starts in. */
399
+ export function seatDefaultMode(prefs, seatId) {
400
+ const mode = prefs.seats[seatId]?.contextMode;
401
+ return isContextMode(mode) ? mode : 'standard';
402
+ }
403
+ //# sourceMappingURL=preferences.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preferences.js","sourceRoot":"","sources":["../../../src/core/verse/preferences.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Shared project memory — one private directory per project that EVERY seat
3
+ * working on that project reads (and, where the CLI can be granted a writable
4
+ * root, maintains):
5
+ *
6
+ * <verse root>/memory/<basename-slug>-<sha256(realpath)[0:12]>/MEMORY.md
7
+ * (default verse root: ~/.ashlr/verse — dir 0700, files 0600)
8
+ *
9
+ * WHY VERSE OWNS IT rather than each CLI's native memory: every seat runs in an
10
+ * isolated home (`native-profiles/<seat>/native-state`), so Claude's auto
11
+ * memory, Codex's `memories` and Grok's `memory/` are per ACCOUNT, and none of
12
+ * them crosses engines. A plain Markdown file in a directory Verse controls is
13
+ * the one surface all four engines can be pointed at, costs nothing (no
14
+ * background extraction model runs — those spend automatically), and stays
15
+ * OUTSIDE the repository, so nothing an agent writes here lands in a diff.
16
+ * (docs/VERSE-CONTEXT.md, research frontier-native-memory.)
17
+ *
18
+ * HOW A SESSION SEES IT: `prepareProjectMemory` returns `{dir, block,
19
+ * writable}`, which the API snapshots into the private launch record at
20
+ * creation. The adapters send the SAME `block` on every turn (claude/local:
21
+ * `--append-system-prompt`; codex/grok per their adapters) — byte-identical, so
22
+ * the provider's prompt cache survives turn after turn. The block carries the
23
+ * MEMORY.md of the moment the chat began; agents that can reach `dir` read the
24
+ * live file for anything newer.
25
+ *
26
+ * SAFETY
27
+ * - The directory key is a hash of the canonical path, so two projects with
28
+ * the same basename never share memory and a path cannot traverse out.
29
+ * - MEMORY.md is read without following symlinks: an agent that replaced it
30
+ * with a link to ~/.ssh/id_ed25519 would otherwise have that file echoed to
31
+ * the UI and into every future system prompt.
32
+ * - The block is `scrubSecrets`'d and capped at VERSE_MEMORY_BLOCK_MAX_BYTES.
33
+ * The instructions tell agents never to store secrets; the scrub is the
34
+ * backstop, not the policy.
35
+ * - The block is ARGV-SAFE: C0/C1 control characters (NUL above all) are
36
+ * stripped. The block rides on argv (`--append-system-prompt=…`,
37
+ * `--rules=…`) and Node's spawn throws ERR_INVALID_ARG_VALUE for any argv
38
+ * element containing NUL. Agents write MEMORY.md directly (not through
39
+ * `writeProjectMemory`'s NUL check), and the block is pinned into the
40
+ * launch record — so one stray NUL would otherwise make every new chat in
41
+ * the project unable to start, permanently.
42
+ *
43
+ * Spends nothing and starts no process.
44
+ */
45
+ import { VERSE_MEMORY_BLOCK_MAX_BYTES, type VerseProjectMemory } from './types.js';
46
+ export declare const VERSE_MEMORY_DIRNAME = "memory";
47
+ export declare const VERSE_MEMORY_FILE = "MEMORY.md";
48
+ /** Hard cap on the system-prompt block, instructions included. */
49
+ export { VERSE_MEMORY_BLOCK_MAX_BYTES };
50
+ /** Marker appended where the snapshot in the block was cut. */
51
+ export declare const VERSE_MEMORY_TRUNCATION_MARKER = "[\u2026 MEMORY.md continues \u2014 read the file for the rest]";
52
+ /**
53
+ * The memory directory for a project. Pure path arithmetic (no I/O beyond the
54
+ * realpath canonicalisation); does not create anything.
55
+ *
56
+ * @param root the Verse store root (default `~/.ashlr/verse`), the same root
57
+ * the session engine and preferences use.
58
+ */
59
+ export declare function projectMemoryDir(projectPath: string, root?: string): string;
60
+ /**
61
+ * Text made safe to pass as (part of) an argv element and to show as prose:
62
+ * CRLF → LF, then every control character except `\n` and `\t` removed.
63
+ * Exported so the engine can repair a snapshot pinned before this existed
64
+ * (a launch record carrying a NUL must be healed, not rejected — rejecting it
65
+ * would brick the session it belongs to).
66
+ */
67
+ export declare function stripUnsafeControlChars(text: string): string;
68
+ /**
69
+ * Deterministic: the same (dir, content, writable) always yields the same
70
+ * bytes, so a block snapshotted into a launch record keeps the prompt prefix
71
+ * cache-stable for the whole conversation.
72
+ */
73
+ export declare function renderMemoryBlock(input: {
74
+ dir: string;
75
+ projectName: string;
76
+ content: string;
77
+ writable: boolean;
78
+ }): string;
79
+ /**
80
+ * Create the project's memory directory (0700) and render the block a new
81
+ * session will carry. Called once, at session creation; the result is pinned
82
+ * in the launch record (`VerseSeatLaunch.memory`).
83
+ *
84
+ * `writable` is the SEAT's capability, decided by the caller from the engine:
85
+ * claude/local (`--add-dir`) and codex (writable root) can write; grok, which
86
+ * reaches only its `--cwd`, cannot.
87
+ */
88
+ export declare function prepareProjectMemory(projectPath: string, opts: {
89
+ writable: boolean;
90
+ root?: string;
91
+ }): {
92
+ dir: string;
93
+ block: string;
94
+ writable: boolean;
95
+ };
96
+ /**
97
+ * The project's memory as the UI shows it. Never creates anything; a project
98
+ * with no memory yet reads as empty with `updatedAt: null`.
99
+ *
100
+ * `enabled` is passed in (the API resolves it from preferences) so this stays
101
+ * a pure read of the directory.
102
+ */
103
+ export declare function readProjectMemory(projectPath: string, enabled: boolean, root?: string): VerseProjectMemory;
104
+ /**
105
+ * Replace MEMORY.md with the operator's text (atomic, 0600). An empty string
106
+ * clears it. Rejects content over VERSE_MEMORY_MAX_BYTES (VERSE_TOO_LARGE)
107
+ * rather than truncating what the operator wrote, and NUL bytes (VERSE_INVALID).
108
+ *
109
+ * Allowed whether or not memory is enabled for the project — it is the
110
+ * operator's own file — and `enabled` in the result reports the preference.
111
+ */
112
+ export declare function writeProjectMemory(projectPath: string, content: string, root?: string): VerseProjectMemory;