@vascend/talos 0.1.4 → 0.1.6

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 (251) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +37 -12
  3. package/dist/core/agent.d.ts +16 -3
  4. package/dist/core/agent.js +170 -70
  5. package/dist/core/background.d.ts +10 -3
  6. package/dist/core/background.js +55 -9
  7. package/dist/core/bootstrap.js +1 -0
  8. package/dist/core/compaction.js +1 -1
  9. package/dist/core/config-storage.d.ts +5 -0
  10. package/dist/core/config-storage.js +83 -0
  11. package/dist/core/config.d.ts +11 -2
  12. package/dist/core/config.js +24 -8
  13. package/dist/core/context-ledger.js +2 -1
  14. package/dist/core/context-policy.d.ts +1 -1
  15. package/dist/core/context-policy.js +10 -7
  16. package/dist/core/credentials.js +29 -30
  17. package/dist/core/effort.d.ts +1 -1
  18. package/dist/core/effort.js +5 -1
  19. package/dist/core/execution-guidance.d.ts +1 -1
  20. package/dist/core/execution-guidance.js +2 -0
  21. package/dist/core/file-coordination.d.ts +3 -0
  22. package/dist/core/file-coordination.js +28 -0
  23. package/dist/core/file-lease-store.d.ts +3 -0
  24. package/dist/core/file-lease-store.js +28 -12
  25. package/dist/core/fusion.d.ts +24 -0
  26. package/dist/core/fusion.js +80 -0
  27. package/dist/core/model-limits.d.ts +1 -0
  28. package/dist/core/model-limits.js +5 -1
  29. package/dist/core/permission-queue.js +15 -15
  30. package/dist/core/request-plan.js +4 -3
  31. package/dist/core/session-naming.d.ts +3 -0
  32. package/dist/core/session-naming.js +47 -0
  33. package/dist/core/session-usage.js +4 -1
  34. package/dist/core/session.d.ts +5 -2
  35. package/dist/core/session.js +22 -7
  36. package/dist/core/settings.d.ts +2 -0
  37. package/dist/core/settings.js +5 -16
  38. package/dist/core/step-policy.d.ts +18 -0
  39. package/dist/core/step-policy.js +6 -0
  40. package/dist/core/storage-health.d.ts +24 -0
  41. package/dist/core/storage-health.js +72 -0
  42. package/dist/core/tool-artifacts.d.ts +2 -0
  43. package/dist/core/tool-artifacts.js +21 -3
  44. package/dist/core/tool-result-index.js +29 -33
  45. package/dist/core/tool-result-store.js +6 -5
  46. package/dist/core/types.d.ts +26 -2
  47. package/dist/core/user-inbox.d.ts +9 -0
  48. package/dist/core/user-inbox.js +22 -3
  49. package/dist/core/user-input.js +1 -1
  50. package/dist/fusion/autopilot-fast.d.ts +59 -0
  51. package/dist/fusion/autopilot-fast.js +213 -0
  52. package/dist/fusion/autopilot.d.ts +75 -0
  53. package/dist/fusion/autopilot.js +308 -0
  54. package/dist/fusion/focus.d.ts +30 -0
  55. package/dist/fusion/focus.js +122 -0
  56. package/dist/fusion/gate.d.ts +30 -0
  57. package/dist/fusion/gate.js +95 -0
  58. package/dist/fusion/jev.d.ts +71 -0
  59. package/dist/fusion/jev.js +164 -0
  60. package/dist/fusion/receipts.d.ts +25 -0
  61. package/dist/fusion/receipts.js +34 -0
  62. package/dist/fusion/route.d.ts +25 -0
  63. package/dist/fusion/route.js +84 -0
  64. package/dist/fusion/run.d.ts +4 -0
  65. package/dist/fusion/run.js +186 -0
  66. package/dist/horizon/engine.d.ts +20 -1
  67. package/dist/horizon/engine.js +57 -0
  68. package/dist/horizon/index.d.ts +2 -2
  69. package/dist/horizon/index.js +1 -1
  70. package/dist/horizon/types.d.ts +29 -0
  71. package/dist/index.js +12 -0
  72. package/dist/providers/anthropic-oauth.d.ts +8 -3
  73. package/dist/providers/anthropic-oauth.js +13 -10
  74. package/dist/providers/anthropic.d.ts +9 -2
  75. package/dist/providers/anthropic.js +64 -21
  76. package/dist/providers/cli-models.d.ts +30 -0
  77. package/dist/providers/cli-models.js +194 -0
  78. package/dist/providers/model-effort.js +1 -1
  79. package/dist/providers/openai-oauth.d.ts +8 -3
  80. package/dist/providers/openai-oauth.js +9 -3
  81. package/dist/providers/openrouter-manual-fallback.d.ts +3 -1
  82. package/dist/providers/openrouter-manual-fallback.js +20 -20
  83. package/dist/providers/registry.js +3 -2
  84. package/dist/providers/retry.d.ts +3 -0
  85. package/dist/providers/retry.js +44 -1
  86. package/dist/remote/cifratura.d.ts +12 -0
  87. package/dist/remote/cifratura.js +66 -0
  88. package/dist/remote/index.d.ts +15 -0
  89. package/dist/remote/index.js +65 -0
  90. package/dist/remote/relay-client.d.ts +58 -0
  91. package/dist/remote/relay-client.js +315 -0
  92. package/dist/remote/sessione-remota.d.ts +60 -0
  93. package/dist/remote/sessione-remota.js +266 -0
  94. package/dist/sdk.d.ts +7 -2
  95. package/dist/sdk.js +3 -1
  96. package/dist/tools/builtin.js +41 -20
  97. package/dist/tools/capabilities.d.ts +3 -1
  98. package/dist/tools/capabilities.js +5 -2
  99. package/dist/tools/computer-use.js +2 -1
  100. package/dist/tools/pagination.js +1 -1
  101. package/dist/tools/program.js +34 -21
  102. package/dist/tools/team-tools.js +11 -6
  103. package/dist/tui/account-entry.js +14 -9
  104. package/dist/tui/agent-label.d.ts +2 -0
  105. package/dist/tui/agent-label.js +5 -0
  106. package/dist/tui/agent-sessions-screen.d.ts +35 -0
  107. package/dist/tui/agent-sessions-screen.js +178 -0
  108. package/dist/tui/agent-sessions.d.ts +53 -0
  109. package/dist/tui/agent-sessions.js +236 -0
  110. package/dist/tui/bottom-input.d.ts +57 -2
  111. package/dist/tui/bottom-input.js +449 -54
  112. package/dist/tui/branding.js +8 -4
  113. package/dist/tui/capability-lists.d.ts +10 -0
  114. package/dist/tui/capability-lists.js +41 -0
  115. package/dist/tui/command-output-registry.js +4 -0
  116. package/dist/tui/command-transcript.d.ts +1 -1
  117. package/dist/tui/command-transcript.js +17 -21
  118. package/dist/tui/commands.js +6 -2
  119. package/dist/tui/compaction-status.d.ts +7 -0
  120. package/dist/tui/compaction-status.js +39 -0
  121. package/dist/tui/config-recovery.d.ts +12 -0
  122. package/dist/tui/config-recovery.js +54 -0
  123. package/dist/tui/context-meter.d.ts +3 -0
  124. package/dist/tui/context-meter.js +35 -8
  125. package/dist/tui/diff.d.ts +9 -0
  126. package/dist/tui/diff.js +43 -19
  127. package/dist/tui/fusion-menu.d.ts +5 -0
  128. package/dist/tui/fusion-menu.js +89 -0
  129. package/dist/tui/i18n-catalog.js +351 -2
  130. package/dist/tui/live-command.d.ts +1 -0
  131. package/dist/tui/live-command.js +9 -0
  132. package/dist/tui/live-output.d.ts +1 -0
  133. package/dist/tui/live-output.js +11 -0
  134. package/dist/tui/logo.js +3 -0
  135. package/dist/tui/model-details.js +13 -12
  136. package/dist/tui/model-picker.d.ts +2 -0
  137. package/dist/tui/model-picker.js +59 -0
  138. package/dist/tui/model-selection.js +19 -10
  139. package/dist/tui/native-scrollback.d.ts +2 -1
  140. package/dist/tui/native-scrollback.js +31 -6
  141. package/dist/tui/prompt-queue-store.d.ts +1 -0
  142. package/dist/tui/prompt-queue-store.js +4 -0
  143. package/dist/tui/prompt-queue.d.ts +3 -0
  144. package/dist/tui/prompt-queue.js +19 -1
  145. package/dist/tui/provider-label.d.ts +2 -0
  146. package/dist/tui/provider-label.js +9 -0
  147. package/dist/tui/provider-menu.js +25 -24
  148. package/dist/tui/queue-screen.js +2 -1
  149. package/dist/tui/queued-questions.js +2 -4
  150. package/dist/tui/quick-help.d.ts +1 -1
  151. package/dist/tui/quick-help.js +3 -0
  152. package/dist/tui/remote-command.d.ts +7 -0
  153. package/dist/tui/remote-command.js +79 -0
  154. package/dist/tui/repl.d.ts +11 -2
  155. package/dist/tui/repl.js +457 -327
  156. package/dist/tui/review-store.d.ts +8 -1
  157. package/dist/tui/review-store.js +64 -11
  158. package/dist/tui/review-team.js +4 -3
  159. package/dist/tui/runtime-error.d.ts +2 -0
  160. package/dist/tui/runtime-error.js +26 -0
  161. package/dist/tui/scrollback-archive.d.ts +16 -0
  162. package/dist/tui/scrollback-archive.js +90 -0
  163. package/dist/tui/selector-mouse.d.ts +1 -0
  164. package/dist/tui/selector-mouse.js +1 -1
  165. package/dist/tui/selector.d.ts +11 -0
  166. package/dist/tui/selector.js +28 -4
  167. package/dist/tui/session-controls.d.ts +26 -0
  168. package/dist/tui/session-controls.js +65 -0
  169. package/dist/tui/session-naming-notice.d.ts +14 -0
  170. package/dist/tui/session-naming-notice.js +59 -0
  171. package/dist/tui/session-picker.d.ts +2 -0
  172. package/dist/tui/session-picker.js +32 -0
  173. package/dist/tui/session-replay.d.ts +1 -0
  174. package/dist/tui/session-replay.js +101 -32
  175. package/dist/tui/session-transcript-layout.d.ts +9 -0
  176. package/dist/tui/session-transcript-layout.js +41 -0
  177. package/dist/tui/startup.js +63 -33
  178. package/dist/tui/status-line.d.ts +1 -1
  179. package/dist/tui/status-line.js +20 -12
  180. package/dist/tui/storage-warning.d.ts +2 -0
  181. package/dist/tui/storage-warning.js +35 -0
  182. package/dist/tui/talos-art-data.d.ts +1 -0
  183. package/dist/tui/talos-art-data.js +2 -0
  184. package/dist/tui/talos-art.d.ts +1 -0
  185. package/dist/tui/talos-art.js +9 -0
  186. package/dist/tui/task-labels.d.ts +1 -0
  187. package/dist/tui/task-labels.js +8 -0
  188. package/dist/tui/terminal-text.d.ts +2 -1
  189. package/dist/tui/terminal-text.js +28 -2
  190. package/dist/tui/theme.js +2 -2
  191. package/dist/tui/tool-presentation.d.ts +3 -0
  192. package/dist/tui/tool-presentation.js +98 -0
  193. package/dist/tui/tool-style.js +4 -0
  194. package/dist/tui/transcript-store.d.ts +25 -0
  195. package/dist/tui/transcript-store.js +151 -0
  196. package/dist/tui/usage-footer.d.ts +1 -1
  197. package/dist/tui/usage-footer.js +3 -1
  198. package/dist/tui/vascend-id.d.ts +49 -0
  199. package/dist/tui/vascend-id.js +321 -0
  200. package/dist/tui/web-login-screen.js +4 -3
  201. package/dist/tui/web-login.d.ts +7 -15
  202. package/dist/tui/web-login.js +38 -78
  203. package/dist/tui/worker-transcripts.d.ts +31 -0
  204. package/dist/tui/worker-transcripts.js +91 -0
  205. package/dist/vascend/candidate-collector.d.ts +6 -5
  206. package/dist/vascend/candidate-collector.js +44 -43
  207. package/dist/vascend/context-feedback-loop.js +2 -0
  208. package/dist/vascend/context-lint.js +32 -2
  209. package/dist/vascend/context-market-selection.js +12 -5
  210. package/dist/vascend/context-pack-render.js +10 -4
  211. package/dist/vascend/context-packet-mirrors.d.ts +8 -0
  212. package/dist/vascend/context-packet-mirrors.js +24 -0
  213. package/dist/vascend/context-plan-events.d.ts +4 -0
  214. package/dist/vascend/context-plan-events.js +44 -0
  215. package/dist/vascend/context-snapshot.d.ts +1 -0
  216. package/dist/vascend/context-snapshot.js +8 -5
  217. package/dist/vascend/context-store-reader.d.ts +20 -0
  218. package/dist/vascend/context-store-reader.js +46 -0
  219. package/dist/vascend/context-window-compiler.d.ts +1 -0
  220. package/dist/vascend/context-window-compiler.js +1 -0
  221. package/dist/vascend/file-lock.d.ts +1 -0
  222. package/dist/vascend/file-lock.js +26 -0
  223. package/dist/vascend/granular-kingdom.d.ts +1 -0
  224. package/dist/vascend/granular-kingdom.js +52 -17
  225. package/dist/vascend/kingdom-bus.d.ts +1 -0
  226. package/dist/vascend/kingdom-bus.js +3 -0
  227. package/dist/vascend/kingdom-channel.d.ts +2 -0
  228. package/dist/vascend/kingdom-channel.js +3 -0
  229. package/dist/vascend/kingdom-live.d.ts +32 -4
  230. package/dist/vascend/kingdom-live.js +149 -13
  231. package/dist/vascend/kingdom-run.d.ts +5 -1
  232. package/dist/vascend/kingdom-run.js +6 -2
  233. package/dist/vascend/kingdom.d.ts +3 -0
  234. package/dist/vascend/kingdom.js +22 -11
  235. package/dist/vascend/memory-packet.d.ts +2 -1
  236. package/dist/vascend/memory-packet.js +31 -44
  237. package/dist/vascend/memory-store.d.ts +1 -0
  238. package/dist/vascend/memory-store.js +12 -4
  239. package/dist/vascend/plan-store.d.ts +3 -1
  240. package/dist/vascend/plan-store.js +6 -4
  241. package/dist/vascend/room-memory.d.ts +13 -2
  242. package/dist/vascend/room-memory.js +48 -6
  243. package/dist/vascend/runtime.d.ts +3 -11
  244. package/dist/vascend/runtime.js +11 -123
  245. package/dist/vascend/session-search.js +2 -0
  246. package/dist/vascend/team-session.js +3 -2
  247. package/dist/vascend/vision.d.ts +3 -1
  248. package/dist/vascend/vision.js +6 -4
  249. package/dist/vascend/worker-session.d.ts +20 -0
  250. package/dist/vascend/worker-session.js +83 -0
  251. package/package.json +2 -2
@@ -1,17 +1,18 @@
1
1
  import { sanitizeText } from "../core/text-sanitize.js";
2
- import { listMemoryScopes, MemoryStore } from "./memory-store.js";
3
- import { VascendPlanStore } from "./plan-store.js";
4
- import { RoomMemory } from "./room-memory.js";
5
- import { VascendVisionStore, visionCompleteness } from "./vision.js";
2
+ import { listMemoryScopes } from "./memory-store.js";
3
+ import { ContextStoreReader } from "./context-store-reader.js";
4
+ import { packetEventsWithoutMirrors } from "./context-packet-mirrors.js";
5
+ import { visionCompleteness } from "./vision.js";
6
6
  import { listContextMemoryScopes } from "./context-fabric.js";
7
- export function buildMemoryPacket(input) {
7
+ export function buildMemoryPacket(input, reader = new ContextStoreReader(input.sessionDir)) {
8
8
  const maxChars = input.maxChars && input.maxChars > 0 ? input.maxChars : 12_000;
9
9
  const recentLimit = input.recentLimit && input.recentLimit > 0 ? input.recentLimit : 10;
10
10
  const projectScopeLimit = input.projectScopeLimit && input.projectScopeLimit > 0 ? input.projectScopeLimit : 6;
11
- const rooms = safe(() => RoomMemory.open(input.sessionDir, input.scopeId).frontier(), []);
12
- const castles = safe(() => VascendPlanStore.open(input.sessionDir, input.planScopeId ?? input.scopeId).castles(), []);
13
- const visions = safe(() => VascendVisionStore.open(input.sessionDir, input.scopeId).all(), []);
14
- const events = safe(() => recentEvents(MemoryStore.open(input.sessionDir, input.scopeId).all(), recentLimit).map((event) => ({ scopeId: input.scopeId, value: event })), []);
11
+ const rooms = selectFrontierRooms(reader.rooms(input.scopeId));
12
+ const localPlan = reader.plan(input.scopeId);
13
+ const castles = (!input.planScopeId || input.planScopeId === input.scopeId
14
+ ? localPlan : reader.plan(input.planScopeId)) ?? [];
15
+ const visions = reader.visions(input.scopeId);
15
16
  const allowedScopes = !input.projectScopes && input.projectKey
16
17
  ? new Set(listContextMemoryScopes(input.sessionDir, input.projectKey))
17
18
  : null;
@@ -19,10 +20,18 @@ export function buildMemoryPacket(input) {
19
20
  excludeScope: input.scopeId,
20
21
  limit: allowedScopes ? 10_000 : projectScopeLimit,
21
22
  }).filter((scope) => !allowedScopes || allowedScopes.has(scope)).slice(0, projectScopeLimit);
22
- const projectRooms = loadProjectFrontiers(input.sessionDir, projectScopes, 8);
23
- const projectCastles = loadProjectCastles(input.sessionDir, projectScopes, 8);
24
- const projectVisions = loadProjectVisions(input.sessionDir, projectScopes, 5);
25
- const projectEvents = loadProjectEvents(input.sessionDir, projectScopes, recentLimit);
23
+ const projectRooms = projectScopes.flatMap(scopeId => reader.rooms(scopeId).slice(-2).map(value => ({ scopeId, value }))).slice(0, 8);
24
+ const projectCastles = projectScopes.flatMap(scopeId => (reader.plan(scopeId) ?? []).slice(0, 2).map(value => ({ scopeId, value }))).slice(0, 8);
25
+ const projectVisions = projectScopes.flatMap(scopeId => reader.visions(scopeId).slice(0, 2).map(value => ({ scopeId, value }))).slice(0, 5);
26
+ const localEvents = reader.events(input.scopeId);
27
+ const scopedEvents = projectScopes.flatMap(scopeId => reader.events(scopeId).map(value => ({ scopeId, value })));
28
+ const allEvents = [...localEvents.map(value => ({ scopeId: input.scopeId, value })), ...scopedEvents];
29
+ const displayedRooms = [...rooms.map(value => ({ scopeId: input.scopeId, value })), ...projectRooms];
30
+ const filtered = packetEventsWithoutMirrors(allEvents, displayedRooms);
31
+ const events = recentEvents(filtered.filter(e => e.scopeId === input.scopeId).map(e => e.value), recentLimit)
32
+ .map(value => ({ scopeId: input.scopeId, value }));
33
+ const projectEvents = filtered.filter(e => e.scopeId !== input.scopeId && !isSuperseded(e.value))
34
+ .sort((a, b) => b.value.createdAt - a.value.createdAt || b.value.id.localeCompare(a.value.id)).slice(0, recentLimit);
26
35
  const hasProject = Boolean(projectRooms.length || projectCastles.length || projectVisions.length || projectEvents.length);
27
36
  if (!rooms.length && !castles.length && !visions.length && !events.length && !hasProject)
28
37
  return "";
@@ -52,9 +61,7 @@ export function buildMemoryPacket(input) {
52
61
  ];
53
62
  return clampMemoryPacket(lines.filter(Boolean).join("\n"), maxChars);
54
63
  }
55
- function frontierRows(rooms) {
56
- if (!rooms.length)
57
- return ["@frontiera[vuota]: nessuna attivita indicizzata"];
64
+ function selectFrontierRooms(rooms) {
58
65
  // La frontiera e' ordinata vecchio->recente. Conserva un indice per livello
59
66
  // ma dedica la maggior parte degli slot allo stato piu' recente: slice(0,10)
60
67
  // nascondeva proprio le ultime stanze nei run lunghi.
@@ -63,8 +70,12 @@ function frontierRows(rooms) {
63
70
  if (!byLevel.has(room.level))
64
71
  byLevel.set(room.level, room);
65
72
  const picked = [...byLevel.values(), ...rooms.slice(-8)];
66
- const unique = [...new Map(picked.map((room) => [room.room, room])).values()].slice(-10);
67
- return unique.map((r) => `@frontiera[${r.room}]: level=${r.level}, title="${q(r.title)}", termini=${r.terms.slice(0, 6).join(",")}`);
73
+ return [...new Map(picked.map((room) => [room.room, room])).values()].slice(-10);
74
+ }
75
+ function frontierRows(rooms) {
76
+ if (!rooms.length)
77
+ return ["@frontiera[vuota]: nessuna attivita indicizzata"];
78
+ return rooms.map((r) => `@frontiera[${r.room}]: level=${r.level}, title="${q(r.title)}", termini=${r.terms.slice(0, 6).join(",")}${r.attachments?.length ? `, allegati=${r.attachments.length}, recupero=recall rooms=["${r.room}"] attachments=true` : ''}`);
68
79
  }
69
80
  function planRows(castles) {
70
81
  if (!castles.length)
@@ -75,7 +86,7 @@ function planRows(castles) {
75
86
  .sort((a, b) => Number(a.status === "completed") - Number(b.status === "completed") || a.bit - b.bit)
76
87
  .slice(0, 8)
77
88
  .map((t) => `@task[${castle.slug}/${t.id}]: status=${t.status}, title="${q(t.title)}"`);
78
- return [`@plan[${castle.slug}]: title="${q(castle.title)}", target=${castle.maskTarget}, attivita=${castle.tasks.length}`, ...tasks];
89
+ return [`@plan[${castle.slug}]: title="${q(castle.title)}", target=${castle.maskTarget}, attivita=${castle.tasks.length}${castle.after?.trim() ? `, after="${q(castle.after)}"` : ""}`, ...tasks];
79
90
  }).slice(0, 32);
80
91
  }
81
92
  function visionRows(visions) {
@@ -93,7 +104,7 @@ function projectRows(rooms, castles, visions) {
93
104
  "[WORKING_SET_PROGETTO]",
94
105
  ...rooms.map((r) => `@project_frontiera[${r.scopeId}/${r.value.room}]: level=${r.value.level}, title="${q(r.value.title)}"`),
95
106
  ...castles.flatMap((c) => [
96
- `@project_plan[${c.scopeId}/${c.value.slug}]: title="${q(c.value.title)}", target=${c.value.maskTarget}, attivita=${c.value.tasks.length}`,
107
+ `@project_plan[${c.scopeId}/${c.value.slug}]: title="${q(c.value.title)}", target=${c.value.maskTarget}, attivita=${c.value.tasks.length}${c.value.after?.trim() ? `, after="${q(c.value.after)}"` : ""}`,
97
108
  ...c.value.tasks.slice(0, 4).map((t) => `@project_task[${c.scopeId}/${c.value.slug}/${t.id}]: status=${t.status}, title="${q(t.title)}"`),
98
109
  ]),
99
110
  ...visions.map((v) => `@project_vision[${v.scopeId}/${v.value.taskId}]: goal="${q(v.value.goal)}"`),
@@ -116,30 +127,6 @@ function isSuperseded(event) {
116
127
  const status = typeof event.payload?.["status"] === "string" ? event.payload["status"] : "active";
117
128
  return status === "superseded" || status === "archived" || typeof event.payload?.["supersededBy"] === "string";
118
129
  }
119
- function loadProjectFrontiers(sessionDir, scopes, limit) {
120
- return scopes.flatMap((scopeId) => safe(() => RoomMemory.open(sessionDir, scopeId).frontier().slice(-2).map((value) => ({ scopeId, value })), [])).slice(0, limit);
121
- }
122
- function loadProjectCastles(sessionDir, scopes, limit) {
123
- return scopes.flatMap((scopeId) => safe(() => VascendPlanStore.open(sessionDir, scopeId).castles().slice(0, 2).map((value) => ({ scopeId, value })), [])).slice(0, limit);
124
- }
125
- function loadProjectVisions(sessionDir, scopes, limit) {
126
- return scopes.flatMap((scopeId) => safe(() => VascendVisionStore.open(sessionDir, scopeId).all().slice(0, 2).map((value) => ({ scopeId, value })), [])).slice(0, limit);
127
- }
128
- function loadProjectEvents(sessionDir, scopes, limit) {
129
- const events = scopes.flatMap((scopeId) => safe(() => MemoryStore.open(sessionDir, scopeId).all().map((value) => ({ scopeId, value })), []));
130
- return events
131
- .filter((e) => !isSuperseded(e.value))
132
- .sort((a, b) => b.value.createdAt - a.value.createdAt || b.value.id.localeCompare(a.value.id))
133
- .slice(0, limit);
134
- }
135
- function safe(fn, fallback) {
136
- try {
137
- return fn();
138
- }
139
- catch {
140
- return fallback;
141
- }
142
- }
143
130
  function q(value, max = 120) {
144
131
  const oneLine = sanitizeText(value).replace(/\s+/g, " ").trim().replace(/"/g, "'");
145
132
  return oneLine.length > max ? oneLine.slice(0, max - 1).trimEnd() + "..." : oneLine;
@@ -59,4 +59,5 @@ export declare class MemoryStore {
59
59
  private reloadTailFromDisk;
60
60
  private resetIndexes;
61
61
  private addEvent;
62
+ private ensureSearchIndex;
62
63
  }
@@ -106,6 +106,8 @@ export class MemoryStore {
106
106
  }
107
107
  const n = pool.length || 1;
108
108
  const unfiltered = !opts.domain && !opts.type && pool.length === this.events.length;
109
+ if (unfiltered)
110
+ this.ensureSearchIndex();
109
111
  const df = unfiltered ? this.globalDf : new Map();
110
112
  const docTokens = unfiltered ? this.docTokens : pool.map((e) => {
111
113
  const toks = tokensForEvent(e);
@@ -218,11 +220,17 @@ export class MemoryStore {
218
220
  const numeric = /^E(\d+)$/i.exec(event.id)?.[1];
219
221
  if (numeric)
220
222
  this.maxNumericId = Math.max(this.maxNumericId, Number(numeric));
221
- const tokens = tokensForEvent(event);
222
223
  this.events.push(event);
223
- this.docTokens.push(tokens);
224
- for (const token of new Set(tokens))
225
- this.globalDf.set(token, (this.globalDf.get(token) ?? 0) + 1);
224
+ }
225
+ // Reading/forwarding events does not need a second full-text index. Build
226
+ // this local index only for search(), and extend it after concurrent appends.
227
+ ensureSearchIndex() {
228
+ for (let i = this.docTokens.length; i < this.events.length; i++) {
229
+ const tokens = tokensForEvent(this.events[i]);
230
+ this.docTokens.push(tokens);
231
+ for (const token of new Set(tokens))
232
+ this.globalDf.set(token, (this.globalDf.get(token) ?? 0) + 1);
233
+ }
226
234
  }
227
235
  }
228
236
  function tokensForEvent(event) {
@@ -37,8 +37,10 @@ export interface KingdomPlan {
37
37
  }
38
38
  export declare class VascendPlanStore {
39
39
  private readonly file;
40
- private readonly memory;
40
+ private readonly openMemory;
41
41
  private kingdom;
42
+ private memoryStore?;
43
+ private get memory();
42
44
  private constructor();
43
45
  static open(sessionDir: string, sessionId: string, options?: {
44
46
  strict?: boolean;
@@ -63,14 +63,16 @@ function buildCastle(slug, title, taskTitles, after) {
63
63
  }
64
64
  export class VascendPlanStore {
65
65
  file;
66
- memory;
66
+ openMemory;
67
67
  kingdom = { castles: [] };
68
- constructor(file, memory) {
68
+ memoryStore;
69
+ get memory() { return this.memoryStore ??= this.openMemory(); }
70
+ constructor(file, openMemory) {
69
71
  this.file = file;
70
- this.memory = memory;
72
+ this.openMemory = openMemory;
71
73
  }
72
74
  static open(sessionDir, sessionId, options = {}) {
73
- const store = new VascendPlanStore(join(sessionDir, `${sessionId}.castle.json`), MemoryStore.open(sessionDir, sessionId));
75
+ const store = new VascendPlanStore(join(sessionDir, `${sessionId}.castle.json`), () => MemoryStore.open(sessionDir, sessionId));
74
76
  if (existsSync(store.file)) {
75
77
  try {
76
78
  const raw = JSON.parse(readFileSync(store.file, "utf8"));
@@ -1,6 +1,13 @@
1
- import type { Message } from "../core/types.js";
1
+ import type { Message, ImagePart } from "../core/types.js";
2
2
  import { RoomGraph, type RoomEdge, type RoomNeighbor } from "./room-graph.js";
3
+ export interface RoomAttachment {
4
+ readonly id: string;
5
+ readonly mime: string;
6
+ readonly source?: string;
7
+ readonly bytes: number;
8
+ }
3
9
  export interface RoomRecord {
10
+ readonly attachments?: readonly RoomAttachment[];
4
11
  readonly room: string;
5
12
  readonly level: number;
6
13
  readonly title: string;
@@ -22,17 +29,21 @@ export declare const DEFAULT_RECENT_KEEP = 2;
22
29
  export declare class RoomMemory {
23
30
  private readonly file;
24
31
  private readonly fanout;
25
- private readonly memory;
32
+ private readonly openMemory;
26
33
  private records;
27
34
  private synonyms;
28
35
  private tokenCache;
29
36
  private graphCache;
30
37
  private readonly recentKeep;
38
+ private memoryStore?;
39
+ private get memory();
31
40
  private constructor();
32
41
  withSynonyms(groups: readonly (readonly string[])[]): this;
33
42
  private tokensFor;
34
43
  private indexedDocs;
35
44
  static open(sessionDir: string, sessionId: string, fanout?: number): RoomMemory;
45
+ private persistMedia;
46
+ attachmentMedia(rooms: readonly string[]): readonly ImagePart[];
36
47
  count(): number;
37
48
  all(): readonly RoomRecord[];
38
49
  frontier(): readonly RoomRecord[];
@@ -1,10 +1,15 @@
1
1
  import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
+ import { createHash } from "node:crypto";
3
4
  import { sanitizeText } from "../core/text-sanitize.js";
4
5
  import { MemoryStore } from "./memory-store.js";
5
6
  import { STOP, tokenize, buildSynonyms, expandQuery, bm25Score, } from "./lexical-search.js";
6
7
  import { withLockSync } from "./file-lock.js";
7
8
  import { RoomGraph, clusterByAffinity, clusterLabel, roomEdges, topTerms, } from "./room-graph.js";
9
+ const pendingMedia = new WeakMap();
10
+ const validImage = (image) => /^image\/(png|jpeg|webp|gif)$/.test(image.mime)
11
+ && image.data.length > 0 && image.data.length <= 28_000_000 && /^[A-Za-z0-9+/=\s]+$/.test(image.data);
12
+ const attachmentId = (image) => createHash('sha256').update(image.mime).update(image.data).digest('hex');
8
13
  // Peso di un vicino a 2 hop rispetto al seme che lo ha portato. Allineato al
9
14
  // peso del link tipizzato in context-retriever.ts: stessa semantica, stesso
10
15
  // numero, cosi' recall e compilazione del contesto non si contraddicono.
@@ -18,7 +23,7 @@ export const DEFAULT_RECENT_KEEP = 2;
18
23
  export class RoomMemory {
19
24
  file;
20
25
  fanout;
21
- memory;
26
+ openMemory;
22
27
  records = [];
23
28
  // Indice sinonimi (default; configurabile via withSynonyms) per espandere le
24
29
  // query verso forme equivalenti IT/EN.
@@ -33,10 +38,12 @@ export class RoomMemory {
33
38
  // cambiano mai corpo ne' archi dopo la scrittura).
34
39
  graphCache = null;
35
40
  recentKeep;
36
- constructor(file, fanout, memory) {
41
+ memoryStore;
42
+ get memory() { return this.memoryStore ??= this.openMemory(); }
43
+ constructor(file, fanout, openMemory) {
37
44
  this.file = file;
38
45
  this.fanout = fanout;
39
- this.memory = memory;
46
+ this.openMemory = openMemory;
40
47
  this.recentKeep = Math.max(0, Math.min(this.fanout - 1, DEFAULT_RECENT_KEEP));
41
48
  }
42
49
  // Sostituisce la tabella sinonimi (es. per dominio specifico). Ritorna this
@@ -60,7 +67,7 @@ export class RoomMemory {
60
67
  }
61
68
  static open(sessionDir, sessionId, fanout = DEFAULT_FANOUT) {
62
69
  const file = join(sessionDir, `${sessionId}.rooms.jsonl`);
63
- const mem = new RoomMemory(file, Math.max(2, fanout), MemoryStore.open(sessionDir, sessionId));
70
+ const mem = new RoomMemory(file, Math.max(2, fanout), () => MemoryStore.open(sessionDir, sessionId));
64
71
  if (existsSync(file)) {
65
72
  const raw = readFileSync(file, "utf8");
66
73
  for (const line of raw.split("\n")) {
@@ -76,6 +83,31 @@ export class RoomMemory {
76
83
  }
77
84
  return mem;
78
85
  }
86
+ persistMedia(record) {
87
+ const media = pendingMedia.get(record) || [];
88
+ if (!media.length)
89
+ return;
90
+ const dir = this.file + '.attachments';
91
+ mkdirSync(dir, { recursive: true });
92
+ for (const image of media)
93
+ writeFileSync(join(dir, attachmentId(image) + '.json'), JSON.stringify(image), { encoding: 'utf8', mode: 0o600 });
94
+ pendingMedia.delete(record);
95
+ }
96
+ attachmentMedia(rooms) {
97
+ const allowed = this.expandRooms(rooms).flatMap(record => record.attachments || []);
98
+ const out = [];
99
+ for (const ref of allowed.slice(0, 8)) {
100
+ if (!/^[a-f0-9]{64}$/.test(ref.id))
101
+ continue;
102
+ try {
103
+ const image = JSON.parse(readFileSync(join(this.file + '.attachments', ref.id + '.json'), 'utf8'));
104
+ if (validImage(image) && attachmentId(image) === ref.id)
105
+ out.push(image);
106
+ }
107
+ catch { /* Allegato assente: non inventare dati visivi. */ }
108
+ }
109
+ return out;
110
+ }
79
111
  count() {
80
112
  return this.records.length;
81
113
  }
@@ -100,6 +132,7 @@ export class RoomMemory {
100
132
  // Aggiunge una stanza (livello 0): fast path append, protetto da lock
101
133
  // file-system cosi' due processi concorrenti non interlacciano le righe.
102
134
  append(record) {
135
+ this.persistMedia(record);
103
136
  this.records.push(record);
104
137
  mkdirSync(dirname(this.file), { recursive: true });
105
138
  withLockSync(this.file, () => {
@@ -149,6 +182,7 @@ export class RoomMemory {
149
182
  this.reloadFromDisk();
150
183
  const id = this.nextRoomId(ns);
151
184
  const rec = build(id);
185
+ this.persistMedia(rec);
152
186
  this.records.push(rec);
153
187
  appendFileSync(this.file, JSON.stringify(rec) + "\n", "utf8");
154
188
  return rec;
@@ -327,7 +361,7 @@ export class RoomMemory {
327
361
  const rows = front
328
362
  .map((r, i) => {
329
363
  const marker = i === last ? " <- TURNO PIU' RECENTE" : "";
330
- return `${i + 1}. @${levelLabel(r.level)}[${r.room}]: "${escapeValue(r.title)}" · termini=${r.terms.slice(0, 8).join(",")}${marker}`;
364
+ return `${i + 1}. @${levelLabel(r.level)}[${r.room}]: "${escapeValue(r.title)}" · termini=${r.terms.slice(0, 8).join(",")}${r.attachments?.length ? ` · allegati=${r.attachments.length}; recall rooms=["${r.room}"] attachments=true` : ''}${marker}`;
331
365
  })
332
366
  .join("\n");
333
367
  return `ATTIVITA_ARCHIVIATE (ordine cronologico: prima le piu' vecchie, ultima la piu' recente)
@@ -369,6 +403,8 @@ function roomEvent(type, record, children = []) {
369
403
  }
370
404
  // Record Danilov compatto di una STANZA (livello 0) dai messaggi del turno.
371
405
  export function formatRoomRecord(room, messages, createdAt) {
406
+ const media = messages.filter(message => message.role === 'user').flatMap(message => message.content.filter((part) => part.type === 'image')).filter(validImage).slice(0, 8);
407
+ const attachments = media.map(image => ({ id: attachmentId(image), mime: image.mime, bytes: Math.floor(image.data.length * 0.75), ...(image.source ? { source: image.source } : {}) }));
372
408
  const title = roomTitle(messages);
373
409
  const tools = collectToolCalls(messages);
374
410
  const files = collectFiles(messages);
@@ -380,6 +416,7 @@ export function formatRoomRecord(room, messages, createdAt) {
380
416
  ]).slice(0, 12);
381
417
  const def = [
382
418
  `@goal[${room}]: "${escapeValue(title)}"`,
419
+ ...attachments.map(att => `@attachment[${att.id}]: ${att.mime}, source=${escapeValue(att.source || 'immagine')}; recall rooms=["${room}"] attachments=true`),
383
420
  ...tools.slice(0, 12).map((t, i) => `@tool[${room}.${i + 1}]: ${t.name}(${escapeValue(t.arg)}) -> ${escapeValue(t.result)}`),
384
421
  ...files.slice(0, 12).map((f, i) => `@file[${room}.${i + 1}]: ${escapeValue(f)}`),
385
422
  ].join("\n");
@@ -399,7 +436,10 @@ RELAZIONI
399
436
  ${rel}
400
437
 
401
438
  OUTPUT: ${escapeValue(output) || "(nessun output testuale)"}`);
402
- return { room, level: 0, title, terms, body, children: [], parent: null, createdAt, edges };
439
+ const record = { room, level: 0, title, terms, body, children: [], parent: null, createdAt, edges, ...(attachments.length ? { attachments } : {}) };
440
+ if (media.length)
441
+ pendingMedia.set(record, media);
442
+ return record;
403
443
  }
404
444
  // Record di ROLLUP (castello/regno/...): indice sui figli che ha chiuso.
405
445
  export function formatRollupRecord(id, level, children, createdAt) {
@@ -437,6 +477,7 @@ OUTPUT: riepilogo delle attivita; apri i dettagli col tool recall.`);
437
477
  terms,
438
478
  body,
439
479
  children: children.map((c) => c.room),
480
+ attachments: children.flatMap(c => c.attachments || []),
440
481
  parent: null,
441
482
  // Il padre eredita il timestamp del figlio piu' recente: con i gruppi per
442
483
  // affinita' l'istante del rollup non direbbe nulla, mentre cosi' la
@@ -486,6 +527,7 @@ function normalize(raw) {
486
527
  parent: raw.parent ?? null,
487
528
  createdAt: typeof raw.createdAt === "number" ? raw.createdAt : 0,
488
529
  ...(Array.isArray(raw.edges) ? { edges: raw.edges.filter(isRoomEdge) } : {}),
530
+ ...(Array.isArray(raw.attachments) ? { attachments: raw.attachments.filter(att => att && /^[a-f0-9]{64}$/.test(att.id)) } : {}),
489
531
  };
490
532
  }
491
533
  // Aggiorna solo le etichette generate dei vecchi riepiloghi. Gli ID,
@@ -1,5 +1,4 @@
1
- import type { Message } from "../core/types.js";
2
- import { type VisionLike } from "./long-term-context.js";
1
+ import type { VisionLike } from "./long-term-context.js";
3
2
  export type VascendMode = "off" | "sticky";
4
3
  export type VascendPromptMode = "one-shot" | "sticky";
5
4
  export type VascendTaskStatus = "pending" | "running" | "completed" | "failed";
@@ -48,10 +47,6 @@ export type VascendCommandResult = {
48
47
  readonly message: string;
49
48
  readonly input: string;
50
49
  };
51
- export interface VascendCheckpoint {
52
- readonly path: string;
53
- readonly text: string;
54
- }
55
50
  export declare const VASCEND_SYSTEM_BLOCK = "# Lista delle attivita\nComunica in italiano naturale, con spiegazioni chiare e Markdown leggibile.\nDescrivi il lavoro con una to-do list: attivita, gruppi, dipendenze e stati\nda fare, in corso, completata o fallita. I riepiloghi della memoria sono solo\ncontesto interno: all'utente mostra le attivita e i risultati pertinenti.\nPer lavori con piu passi usa vascend_plan: un piano contiene gruppi di attivita,\nogni gruppo contiene attivita con stato, dipendenze e prove di verifica.\nAggiorna gli stati con vascend_mark; completa solo dopo le verifiche.\nUsa vascend_note per decisioni e blocchi e recall per recuperare il passato.\nPer lavori indipendenti valuta collaboratori tramite gli strumenti del team.\nPer domande o richieste semplici non creare un piano inutile.";
56
51
  export interface VascendVerdict {
57
52
  readonly ok: boolean;
@@ -66,19 +61,16 @@ export declare function createVascendState(): VascendState;
66
61
  export declare function createVascendGoal(state: VascendState, goal: string): VascendState;
67
62
  export declare function setVascendMode(state: VascendState, mode: VascendMode): VascendState;
68
63
  export declare function setVascendVision(state: VascendState, vision: VisionLike | null): VascendState;
69
- export declare function wrapVascendPrompt(state: VascendState, userText: string, mode: VascendPromptMode, checkpoint?: string): string;
70
- export declare function formatVascendCompact(state: VascendState, messages?: readonly Message[]): string;
64
+ export declare function wrapVascendPrompt(_state: VascendState, userText: string, _mode: VascendPromptMode): string;
71
65
  export declare class VascendRuntime {
72
- private readonly cwd;
73
66
  private stateValue;
74
67
  private customPlan;
75
- constructor(cwd: string, initialMode?: VascendMode);
68
+ constructor(_cwd: string, initialMode?: VascendMode);
76
69
  state(): VascendState;
77
70
  hasCustomPlan(): boolean;
78
71
  loadPlan(sessionDir: string, sessionId: string): boolean;
79
72
  resetPlan(sessionDir: string, sessionId: string): void;
80
73
  handleCommand(args: string): VascendCommandResult;
81
74
  prepareUserInput(userText: string): PreparedVascendInput;
82
- compact(messages: readonly Message[]): VascendCheckpoint;
83
75
  private wrap;
84
76
  }
@@ -1,8 +1,5 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import { join } from "node:path";
3
1
  import { sanitizeText } from "../core/text-sanitize.js";
4
2
  import { VascendPlanStore } from "./plan-store.js";
5
- import { injectLongTermContext } from "./long-term-context.js";
6
3
  export const VASCEND_SYSTEM_BLOCK = `# Lista delle attivita
7
4
  Comunica in italiano naturale, con spiegazioni chiare e Markdown leggibile.
8
5
  Descrivi il lavoro con una to-do list: attivita, gruppi, dipendenze e stati
@@ -14,6 +11,7 @@ Aggiorna gli stati con vascend_mark; completa solo dopo le verifiche.
14
11
  Usa vascend_note per decisioni e blocchi e recall per recuperare il passato.
15
12
  Per lavori indipendenti valuta collaboratori tramite gli strumenti del team.
16
13
  Per domande o richieste semplici non creare un piano inutile.`;
14
+ const MAX_GOAL_CHARS = 240;
17
15
  export function validateKingdom(state) {
18
16
  const castles = state.kingdom.castles;
19
17
  const multi = castles.length > 1;
@@ -55,7 +53,9 @@ export function createVascendState() {
55
53
  return { mode: "off", goal: null, kingdom: { castles: [] } };
56
54
  }
57
55
  export function createVascendGoal(state, goal) {
58
- const title = oneLine(goal) || "obiettivo";
56
+ // Il goal e' una chiave breve per UI, routing e memoria. Resta nello stato
57
+ // strutturato e non viene copiato dentro al messaggio utente.
58
+ const title = oneLine(goal).slice(0, MAX_GOAL_CHARS) || "obiettivo";
59
59
  // NIENTE castello automatico: il castello (con le sue stanze) si crea solo su
60
60
  // richiesta (comando /castle) o quando l'agente pianifica con vascend_plan.
61
61
  // Cosi' il riquadro non compare quando non serve (es. un saluto).
@@ -69,90 +69,19 @@ export function setVascendMode(state, mode) {
69
69
  export function setVascendVision(state, vision) {
70
70
  return { ...state, vision };
71
71
  }
72
- export function wrapVascendPrompt(state, userText, mode, checkpoint) {
73
- if (!state.goal)
74
- return userText;
75
- const checkpointBlock = checkpoint
76
- ? `\nCHECKPOINT_PRECEDENTE\n${checkpoint.trim()}\n`
77
- : "";
78
- const immediatePrompt = `[VASCEND_GOAL]
79
- GOAL: ${state.goal}
80
- MODE: ${mode}
81
- ${checkpointBlock}
82
- Piano di lavoro:
83
- - Comunica in modo naturale e leggibile, senza imporre notazioni speciali.
84
- - Per lavori con piu passi crea o riprendi il piano con vascend_plan e vascend_status.
85
- - Organizza il piano in gruppi e attivita; aggiorna gli stati con vascend_mark.
86
- - Completa le attivita solo dopo verifiche concrete; registra decisioni e blocchi.
87
- - Per lavori indipendenti valuta collaboratori; per domande semplici non serve un piano.
88
-
89
- INPUT_UTENTE
90
- ${userText}`;
91
- // Antepone il CONTESTO A LUNGO TERMINE (Vision finale) come piano SEPARATO
92
- // dal goal immediato. Il goal corrente viene passato come "immediato" cosi'
93
- // che il blocco espliciti la distinzione momentaneo/visione. Se non c'e'
94
- // Vision con contenuto, injectLongTermContext ritorna il prompt invariato.
95
- const currentCastle = state.kingdom.castles[0];
96
- const currentRoom = currentCastle?.tasks.find((t) => t.status === "running");
97
- return injectLongTermContext(immediatePrompt, {
98
- vision: state.vision ?? null,
99
- immediate: {
100
- ...(state.goal ? { goal: state.goal } : {}),
101
- ...(currentCastle ? { castle: currentCastle.title } : {}),
102
- ...(currentRoom ? { room: currentRoom.title } : {}),
103
- },
104
- });
105
- }
106
- export function formatVascendCompact(state, messages = []) {
107
- const castle = state.kingdom.castles[0];
108
- const multi = state.kingdom.castles.length > 1;
109
- const tasks = state.kingdom.castles.flatMap(group => group.tasks.map(task => ({ ...task,
110
- id: multi ? `${group.slug}/${task.id}` : task.id,
111
- })));
112
- const groups = state.kingdom.castles.map(group => `@gruppo[${group.slug}]: titolo="${escapeValue(group.title)}", dipendenza=${group.after ?? "none"}`).join("\n");
113
- const taskRows = tasks.length
114
- ? tasks.map((task) => compactTaskRow(task)).join("\n")
115
- : "@aperto[goal]: nessun_gruppo_attivo, blocco=none";
116
- const recent = messagePreviews(messages)
117
- .map((text, index) => `@stato[recent_${index + 1}]: ${text}`)
118
- .join("\n");
119
- const relations = tasks.length
120
- ? tasks.map((task, index) => `@R${index + 1}: @stato[goal] -> @${task.status === "completed" ? "fatto" : "aperto"}[${task.id}], task`).join("\n")
121
- : "@R1: @stato[goal] -> @aperto[goal], crea_gruppo";
122
- // The synthetic summary is usually older than the last four messages.
123
- // Keep it in the cold-start checkpoint, not just 140-character previews.
124
- const summary = messages.flatMap(message => message.content
125
- .filter(part => part.type === "text" && part.text.startsWith("[Riepilogo del contesto precedente]"))
126
- .map(part => part.type === "text" ? part.text : "")).at(-1);
127
- return `INDICE
128
- 1 = goal
129
- 2 = gruppo
130
- 3 = task
131
- 4 = file
132
- 5 = contesto
133
-
134
- DEFINIZIONI
135
- @stato[goal]: valore="${escapeValue(state.goal ?? "nessun_goal")}", mode=${state.mode}
136
- @stato[gruppo]: slug=${castle?.slug ?? "none"}, mask=${castle?.maskTarget ?? "0x0000"}
137
- ${groups}
138
- ${taskRows}
139
- @file[checkpoint]: path=.vascend-compact.md, ruolo=checkpoint, stato=aggiornato${recent ? `\n${recent}` : ""}
140
-
141
- RELAZIONI
142
- ${relations}
143
- @R99: @file[checkpoint] -> @stato[goal], ripresa
144
-
145
- OUTPUT: stato compatto della sessione, pronto a riprendere a freddo.${summary ? `\n\nCONTESTO DI RIPRESA\n${summary}` : ""}`;
72
+ export function wrapVascendPrompt(_state, userText, _mode) {
73
+ // La richiesta dell'utente deve comparire una sola volta nel payload. Goal,
74
+ // piano, Vision e ricevute vengono gia' composti dal Context Fabric e dagli
75
+ // store JSON nel canale di contesto del turno.
76
+ return userText;
146
77
  }
147
78
  export class VascendRuntime {
148
- cwd;
149
79
  stateValue;
150
80
  customPlan = false;
151
81
  // initialMode permette di rendere il metodo Vascend NATIVO (sticky) di
152
82
  // default nella TUI, senza dover digitare /vascend on. Default "off" per
153
83
  // mantenere puro il runtime fuori dalla TUI (test, print, SDK).
154
- constructor(cwd, initialMode = "off") {
155
- this.cwd = cwd;
84
+ constructor(_cwd, initialMode = "off") {
156
85
  this.stateValue = setVascendMode(createVascendState(), initialMode);
157
86
  }
158
87
  state() {
@@ -233,54 +162,13 @@ export class VascendRuntime {
233
162
  this.stateValue = createVascendGoal(this.stateValue, userText);
234
163
  return { active: true, input: this.wrap(userText, "sticky") };
235
164
  }
236
- compact(messages) {
237
- const text = formatVascendCompact(this.stateValue, messages);
238
- const path = checkpointPath(this.cwd);
239
- writeFileSync(path, text + "\n", "utf8");
240
- return { path, text };
241
- }
242
165
  wrap(userText, mode) {
243
- return wrapVascendPrompt(this.stateValue, userText, mode, readCheckpoint(this.cwd));
166
+ return wrapVascendPrompt(this.stateValue, userText, mode);
244
167
  }
245
168
  }
246
- function compactTaskRow(task) {
247
- const kind = task.status === "completed" ? "fatto" : "aperto";
248
- const block = task.status === "failed" ? ", blocco=failed" : "";
249
- const evidence = task.evidence?.length
250
- ? `, prove="${escapeValue(task.evidence.slice(-3).join(" | "))}"`
251
- : "";
252
- return `@${kind}[${task.id}]: titolo="${escapeValue(task.title)}", bit=${task.bit}, mask=${task.mask}, esito=${task.status}${block}${evidence}`;
253
- }
254
- function messagePreviews(messages) {
255
- return messages.slice(-4).flatMap((message) => {
256
- const text = message.content
257
- .map((part) => {
258
- if (part.type === "text")
259
- return part.text;
260
- if (part.type === "tool_call")
261
- return `${part.name} ${JSON.stringify(part.input)}`;
262
- if (part.type === "image")
263
- return `[immagine ${part.mime}]`;
264
- return part.output;
265
- })
266
- .join(" ");
267
- const compact = oneLine(text).slice(0, 140);
268
- return compact ? [`role=${message.role}, text="${escapeValue(compact)}"`] : [];
269
- });
270
- }
271
- function checkpointPath(cwd) {
272
- return join(cwd, ".vascend-compact.md");
273
- }
274
- function readCheckpoint(cwd) {
275
- const path = checkpointPath(cwd);
276
- return existsSync(path) ? readFileSync(path, "utf8") : undefined;
277
- }
278
169
  function oneLine(value) {
279
170
  return sanitizeText(value).replace(/\s+/g, " ").trim();
280
171
  }
281
- function escapeValue(value) {
282
- return oneLine(value).replace(/"/g, "'");
283
- }
284
172
  function hexMask(value) {
285
173
  return `0x${value.toString(16).padStart(4, "0")}`;
286
174
  }
@@ -80,6 +80,8 @@ function lineText(line) {
80
80
  const BOILERPLATE = /^\s*(?:\[COLLABORATORE|\[VASSALLO|\[VASCEND_GOAL\]|INDICE\s+\d\s*=|<!--\s*vascend:|@regno|@stato\[)/i;
81
81
  const OBJECTIVE_MARKER = /(?:OBIETTIVO_ATTIVITA|OBIETTIVO_CASTELLO|OBIETTIVO|GOAL)\s*[:=]\s*(.{4,})/i;
82
82
  export function pickObjective(userTexts) {
83
+ // Compatibilita di sola lettura con sessioni storiche che contenevano il
84
+ // vecchio wrapper/checkpoint. Non carica file e non aggiunge dati al prompt.
83
85
  const cut = (text) => text.split(/\s(?:MODE|CHECKPOINT_PRECEDENTE|CHECKPOINT|INDICE|OUTPUT)\s*[:=]/)[0].trim().slice(0, 200);
84
86
  const plain = userTexts.find((text) => !BOILERPLATE.test(text));
85
87
  if (plain)
@@ -119,8 +119,9 @@ function checkpoint(team) {
119
119
  export async function waitForTeamChange(team, signal, interrupted) {
120
120
  const signature = (current) => JSON.stringify(current.members.map((member) => [member.slug, member.status]));
121
121
  const before = signature(team);
122
- const deadline = Date.now() + 30_000;
123
- while (!signal.aborted && !interrupted() && Date.now() < deadline) {
122
+ // Poll local state only. A wall-clock deadline used to wake the provider
123
+ // repeatedly even when no worker or message had changed.
124
+ while (!signal.aborted && !interrupted()) {
124
125
  const current = teamById(team.sessionId, team.id);
125
126
  if (!current || current.done || signature(current) !== before)
126
127
  return;
@@ -60,8 +60,10 @@ export declare function visionFromJSON(json: string, opts?: {
60
60
  }): Vision | null;
61
61
  export declare class VascendVisionStore {
62
62
  private readonly file;
63
- private readonly memory;
63
+ private readonly openMemory;
64
64
  private visions;
65
+ private memoryStore?;
66
+ private get memory();
65
67
  private constructor();
66
68
  static open(sessionDir: string, sessionId: string): VascendVisionStore;
67
69
  has(taskId: string): boolean;