@openvulcan/dsh-vulcancode 0.0.0-stage → 0.4.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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -2
  3. package/assets/openvulcan-icon.svg +33 -0
  4. package/cordis.patch.yml +17 -0
  5. package/node_modules/@openvulcan/dsh-decision-guard/LICENSE +21 -0
  6. package/node_modules/@openvulcan/dsh-decision-guard/README.md +14 -0
  7. package/node_modules/@openvulcan/dsh-decision-guard/assets/decision-guard-icon.png +0 -0
  8. package/node_modules/@openvulcan/dsh-decision-guard/lib/calls.d.ts +40 -0
  9. package/node_modules/@openvulcan/dsh-decision-guard/lib/calls.js +150 -0
  10. package/node_modules/@openvulcan/dsh-decision-guard/lib/client.js +2193 -0
  11. package/node_modules/@openvulcan/dsh-decision-guard/lib/decide.d.ts +90 -0
  12. package/node_modules/@openvulcan/dsh-decision-guard/lib/decide.js +108 -0
  13. package/node_modules/@openvulcan/dsh-decision-guard/lib/index.d.ts +129 -0
  14. package/node_modules/@openvulcan/dsh-decision-guard/lib/index.js +572 -0
  15. package/node_modules/@openvulcan/dsh-decision-guard/lib/intent.d.ts +22 -0
  16. package/node_modules/@openvulcan/dsh-decision-guard/lib/intent.js +77 -0
  17. package/node_modules/@openvulcan/dsh-decision-guard/lib/panel.d.ts +158 -0
  18. package/node_modules/@openvulcan/dsh-decision-guard/lib/panel.js +340 -0
  19. package/node_modules/@openvulcan/dsh-decision-guard/lib/records.d.ts +133 -0
  20. package/node_modules/@openvulcan/dsh-decision-guard/lib/records.js +186 -0
  21. package/node_modules/@openvulcan/dsh-decision-guard/lib/review.d.ts +55 -0
  22. package/node_modules/@openvulcan/dsh-decision-guard/lib/review.js +202 -0
  23. package/node_modules/@openvulcan/dsh-decision-guard/lib/session-state.d.ts +62 -0
  24. package/node_modules/@openvulcan/dsh-decision-guard/lib/session-state.js +155 -0
  25. package/node_modules/@openvulcan/dsh-decision-guard/lib/version.d.ts +2 -0
  26. package/node_modules/@openvulcan/dsh-decision-guard/lib/version.js +4 -0
  27. package/node_modules/@openvulcan/dsh-decision-guard/package.json +68 -0
  28. package/node_modules/@openvulcan/dsh-vulcan-account/LICENSE +21 -0
  29. package/node_modules/@openvulcan/dsh-vulcan-account/assets/account-icon.png +0 -0
  30. package/node_modules/@openvulcan/dsh-vulcan-account/lib/client.js +565 -0
  31. package/node_modules/@openvulcan/dsh-vulcan-account/lib/index.d.ts +92 -0
  32. package/node_modules/@openvulcan/dsh-vulcan-account/lib/index.js +194 -0
  33. package/node_modules/@openvulcan/dsh-vulcan-account/package.json +57 -0
  34. package/node_modules/@openvulcan/dsh-vulcan-answers/LICENSE +21 -0
  35. package/node_modules/@openvulcan/dsh-vulcan-answers/assets/answers-icon.png +0 -0
  36. package/node_modules/@openvulcan/dsh-vulcan-answers/assets/prompts/answer-mode-interactive.md +79 -0
  37. package/node_modules/@openvulcan/dsh-vulcan-answers/assets/prompts/answer-mode-standard.md +1 -0
  38. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/answer-model.d.ts +258 -0
  39. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/answer-model.js +698 -0
  40. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/client.js +671 -0
  41. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/fences.d.ts +29 -0
  42. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/fences.js +70 -0
  43. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/index.d.ts +19 -0
  44. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/index.js +52 -0
  45. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/limits.d.ts +9 -0
  46. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/limits.js +9 -0
  47. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/prompt.d.ts +22 -0
  48. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/prompt.js +39 -0
  49. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/reply-format.d.ts +20 -0
  50. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/reply-format.js +17 -0
  51. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/session-state.d.ts +66 -0
  52. package/node_modules/@openvulcan/dsh-vulcan-answers/lib/session-state.js +118 -0
  53. package/node_modules/@openvulcan/dsh-vulcan-answers/package.json +59 -0
  54. package/node_modules/@openvulcan/dsh-vulcan-luaskills/LICENSE +21 -0
  55. package/node_modules/@openvulcan/dsh-vulcan-luaskills/assets/luaskills-icon.svg +1 -0
  56. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/client.js +940 -0
  57. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/constants.d.ts +46 -0
  58. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/constants.js +51 -0
  59. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/engine.d.ts +55 -0
  60. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/engine.js +149 -0
  61. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/hub.d.ts +49 -0
  62. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/hub.js +116 -0
  63. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/index.d.ts +26 -0
  64. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/index.js +276 -0
  65. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/protocol.d.ts +65 -0
  66. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/protocol.js +2 -0
  67. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/runtime.d.ts +111 -0
  68. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/runtime.js +281 -0
  69. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/sdk-install.d.ts +26 -0
  70. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/sdk-install.js +87 -0
  71. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/settings.d.ts +79 -0
  72. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/settings.js +165 -0
  73. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/skills.d.ts +34 -0
  74. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/skills.js +61 -0
  75. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/tools.d.ts +59 -0
  76. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/tools.js +107 -0
  77. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/worker.d.ts +10 -0
  78. package/node_modules/@openvulcan/dsh-vulcan-luaskills/lib/worker.js +140 -0
  79. package/node_modules/@openvulcan/dsh-vulcan-luaskills/package.json +57 -0
  80. package/node_modules/@openvulcan/dsh-vulcan-memory/LICENSE +21 -0
  81. package/node_modules/@openvulcan/dsh-vulcan-memory/assets/markdown-memory-guide.md +21 -0
  82. package/node_modules/@openvulcan/dsh-vulcan-memory/assets/memory-curator/SKILL.md +31 -0
  83. package/node_modules/@openvulcan/dsh-vulcan-memory/assets/memory-icon.png +0 -0
  84. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/client.js +1090 -0
  85. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/index.d.ts +30 -0
  86. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/index.js +240 -0
  87. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/inject.d.ts +30 -0
  88. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/inject.js +46 -0
  89. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/constants.d.ts +23 -0
  90. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/constants.js +25 -0
  91. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/document.d.ts +108 -0
  92. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/document.js +373 -0
  93. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/errors.d.ts +14 -0
  94. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/errors.js +31 -0
  95. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/index.d.ts +196 -0
  96. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/index.js +204 -0
  97. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/operations.d.ts +92 -0
  98. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/operations.js +472 -0
  99. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/package.d.ts +126 -0
  100. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/package.js +553 -0
  101. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/registry.d.ts +56 -0
  102. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/registry.js +182 -0
  103. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/scope.d.ts +13 -0
  104. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/scope.js +24 -0
  105. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/search.d.ts +65 -0
  106. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/search.js +227 -0
  107. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/snapshot.d.ts +34 -0
  108. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/snapshot.js +171 -0
  109. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/store.d.ts +72 -0
  110. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/store.js +250 -0
  111. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/timestamp.d.ts +6 -0
  112. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/timestamp.js +12 -0
  113. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/tokens.d.ts +8 -0
  114. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/markdown/tokens.js +38 -0
  115. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/page.d.ts +132 -0
  116. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/page.js +235 -0
  117. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/project.d.ts +14 -0
  118. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/project.js +27 -0
  119. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/settings.d.ts +29 -0
  120. package/node_modules/@openvulcan/dsh-vulcan-memory/lib/settings.js +62 -0
  121. package/node_modules/@openvulcan/dsh-vulcan-memory/package.json +63 -0
  122. package/node_modules/@openvulcan/dsh-vulcan-style/LICENSE +21 -0
  123. package/node_modules/@openvulcan/dsh-vulcan-style/assets/style-icon.png +0 -0
  124. package/node_modules/@openvulcan/dsh-vulcan-style/lib/client.js +1061 -0
  125. package/node_modules/@openvulcan/dsh-vulcan-style/lib/index.d.ts +34 -0
  126. package/node_modules/@openvulcan/dsh-vulcan-style/lib/index.js +42 -0
  127. package/node_modules/@openvulcan/dsh-vulcan-style/package.json +51 -0
  128. package/node_modules/@openvulcan/openvulcan-account/LICENSE +21 -0
  129. package/node_modules/@openvulcan/openvulcan-account/lib/account.d.ts +140 -0
  130. package/node_modules/@openvulcan/openvulcan-account/lib/account.js +347 -0
  131. package/node_modules/@openvulcan/openvulcan-account/lib/index.d.ts +1 -0
  132. package/node_modules/@openvulcan/openvulcan-account/lib/index.js +1 -0
  133. package/node_modules/@openvulcan/openvulcan-account/package.json +29 -0
  134. package/node_modules/@openvulcan/vulcan-decision/LICENSE +21 -0
  135. package/node_modules/@openvulcan/vulcan-decision/README.md +23 -0
  136. package/node_modules/@openvulcan/vulcan-decision/bin/vulcan-decision.js +4 -0
  137. package/node_modules/@openvulcan/vulcan-decision/lib/cli.d.ts +5 -0
  138. package/node_modules/@openvulcan/vulcan-decision/lib/cli.js +267 -0
  139. package/node_modules/@openvulcan/vulcan-decision/lib/cloud/edge.d.ts +211 -0
  140. package/node_modules/@openvulcan/vulcan-decision/lib/cloud/edge.js +594 -0
  141. package/node_modules/@openvulcan/vulcan-decision/lib/download.d.ts +36 -0
  142. package/node_modules/@openvulcan/vulcan-decision/lib/download.js +99 -0
  143. package/node_modules/@openvulcan/vulcan-decision/lib/engine.d.ts +37 -0
  144. package/node_modules/@openvulcan/vulcan-decision/lib/engine.js +39 -0
  145. package/node_modules/@openvulcan/vulcan-decision/lib/hardware.d.ts +105 -0
  146. package/node_modules/@openvulcan/vulcan-decision/lib/hardware.js +207 -0
  147. package/node_modules/@openvulcan/vulcan-decision/lib/home.d.ts +26 -0
  148. package/node_modules/@openvulcan/vulcan-decision/lib/home.js +54 -0
  149. package/node_modules/@openvulcan/vulcan-decision/lib/index.d.ts +17 -0
  150. package/node_modules/@openvulcan/vulcan-decision/lib/index.js +17 -0
  151. package/node_modules/@openvulcan/vulcan-decision/lib/local/engine.d.ts +54 -0
  152. package/node_modules/@openvulcan/vulcan-decision/lib/local/engine.js +157 -0
  153. package/node_modules/@openvulcan/vulcan-decision/lib/local/models.d.ts +126 -0
  154. package/node_modules/@openvulcan/vulcan-decision/lib/local/models.js +261 -0
  155. package/node_modules/@openvulcan/vulcan-decision/lib/local/packs.d.ts +102 -0
  156. package/node_modules/@openvulcan/vulcan-decision/lib/local/packs.js +195 -0
  157. package/node_modules/@openvulcan/vulcan-decision/lib/local/worker.d.ts +61 -0
  158. package/node_modules/@openvulcan/vulcan-decision/lib/local/worker.js +123 -0
  159. package/node_modules/@openvulcan/vulcan-decision/lib/protocol.d.ts +74 -0
  160. package/node_modules/@openvulcan/vulcan-decision/lib/protocol.js +31 -0
  161. package/node_modules/@openvulcan/vulcan-decision/lib/render.d.ts +15 -0
  162. package/node_modules/@openvulcan/vulcan-decision/lib/render.js +33 -0
  163. package/node_modules/@openvulcan/vulcan-decision/lib/version.d.ts +2 -0
  164. package/node_modules/@openvulcan/vulcan-decision/lib/version.js +4 -0
  165. package/node_modules/@openvulcan/vulcan-decision/package.json +40 -0
  166. package/node_modules/@openvulcan/vulcan-decision/python/vulcan_decision_worker.py +363 -0
  167. package/node_modules/@openvulcan/vulcan-decision/runtime-packs/onnx-cpu.json +627 -0
  168. package/node_modules/@openvulcan/vulcan-decision/runtime-packs/onnx-cuda.json +423 -0
  169. package/node_modules/@openvulcan/vulcan-decision/runtime-packs/torch-cpu.json +1047 -0
  170. package/node_modules/@openvulcan/vulcan-decision/runtime-packs/torch-cu128.json +647 -0
  171. package/package.json +53 -4
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Long-term Markdown memory in dsh, ported from VulcanCode. Three tools let the model read and
3
+ * change memory; a snapshot of it enters each conversation once (and again after a compaction);
4
+ * the memory-curator skill guides tidying. Files live under `<dshHome>/vulcan-memory`, in the same
5
+ * formats as VulcanCode.
6
+ *
7
+ * The memory mode is read from `settings.json`: `markdown` (the default) enables everything here;
8
+ * `off` and the not-yet-implemented `vmm` leave the tools refusing and inject nothing.
9
+ *
10
+ * The memory page (`client/`) reads and changes the same files through the routes below
11
+ * {@link ROUTE_PREFIX}.
12
+ */
13
+ import type { Context } from '@deepseek-ai/cordis';
14
+ export type { MemoryMode, MemorySettings, InjectionBudget } from './settings.js';
15
+ export declare const name = "vulcan-memory";
16
+ export declare const inject: string[];
17
+ /** Mirrored by `ROUTE_BASE` in `client/shared.ts`. */
18
+ export declare const ROUTE_PREFIX = "/api/vulcan-memory/";
19
+ export declare function apply(ctx: Context): Promise<void>;
20
+ /** Fetch-shaped handler of one page route. */
21
+ type Handler = (request: Request, url: URL) => Promise<unknown>;
22
+ /** Group route rows by path; dsh dispatches only the methods registered for a path. */
23
+ export declare function groupRoutes(routes: readonly (readonly ['GET' | 'POST', string, Handler])[]): Map<string, Map<'GET' | 'POST', Handler>>;
24
+ /** Answer one page request; failures carry their kind and message, never a stack. */
25
+ export declare function handle(handler: Handler, request: Request): Promise<Response>;
26
+ /** Split a SKILL.md into its front-matter description and body. */
27
+ export declare function parseSkill(text: string): {
28
+ description: string;
29
+ body: string;
30
+ };
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Long-term Markdown memory in dsh, ported from VulcanCode. Three tools let the model read and
3
+ * change memory; a snapshot of it enters each conversation once (and again after a compaction);
4
+ * the memory-curator skill guides tidying. Files live under `<dshHome>/vulcan-memory`, in the same
5
+ * formats as VulcanCode.
6
+ *
7
+ * The memory mode is read from `settings.json`: `markdown` (the default) enables everything here;
8
+ * `off` and the not-yet-implemented `vmm` leave the tools refusing and inject nothing.
9
+ *
10
+ * The memory page (`client/`) reads and changes the same files through the routes below
11
+ * {@link ROUTE_PREFIX}.
12
+ */
13
+ import { readFileSync } from 'node:fs';
14
+ import path from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { resolveDshHome } from '@deepseek-ai/dsh-home-paths';
17
+ import { createUserMessage } from '@deepseek-ai/dsh-llm';
18
+ import { INITIAL_STATE, PROJECTION_KEY, SOURCE_KIND, applyEvent, injectionStateSchema, isSubagent, renderMemoryBlock } from './inject.js';
19
+ import { MemoryError } from './markdown/errors.js';
20
+ import * as markdown from './markdown/index.js';
21
+ import * as page from './page.js';
22
+ import { projectContext } from './project.js';
23
+ import { loadSettings } from './settings.js';
24
+ export const name = 'vulcan-memory';
25
+ export const inject = ['tools', 'skills', 'agents', 'sessionProjections'];
26
+ /** Mirrored by `ROUTE_BASE` in `client/shared.ts`. */
27
+ export const ROUTE_PREFIX = '/api/vulcan-memory/';
28
+ const ASSETS = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'assets');
29
+ const SKILL_NAME = 'memory-curator';
30
+ /** The standard rank of packaged skills; the type ties the literal to dsh's constant. */
31
+ const SKILL_RANK = 600;
32
+ const PROVIDER = 'vulcan-memory';
33
+ const OUTPUT = {
34
+ schema: { type: 'object', properties: { content: { type: 'string' } }, required: ['content'], additionalProperties: false },
35
+ render: (_args, value) => [{ type: 'text', text: value.content }],
36
+ };
37
+ export async function apply(ctx) {
38
+ const memoryDirectory = path.join(resolveDshHome(), 'vulcan-memory');
39
+ const store = new markdown.MarkdownMemoryStore(memoryDirectory);
40
+ const guide = readFileSync(path.join(ASSETS, 'markdown-memory-guide.md'), 'utf8');
41
+ const skill = parseSkill(readFileSync(path.join(ASSETS, SKILL_NAME, 'SKILL.md'), 'utf8'));
42
+ /** Memory of an agent's project, or `undefined` when memory is not active for it. */
43
+ const access = (agent) => markdown.resolveAccess(store, loadSettings(memoryDirectory), projectContext(agent.session.header.cwd));
44
+ const tool = (spec, run) => ({
45
+ name: spec.name,
46
+ description: spec.description,
47
+ parameters: spec.parameters,
48
+ output: OUTPUT,
49
+ async execute(args, { agent }) {
50
+ if (agent === undefined)
51
+ throw new Error('memory tools need a session');
52
+ const resolved = access(agent);
53
+ if (resolved === undefined)
54
+ throw new Error(markdown.NOT_ACTIVE_MESSAGE);
55
+ return { content: run(resolved, args, agent) };
56
+ },
57
+ });
58
+ ctx.effect(() => ctx.tools.register(tool(markdown.TOOL_SPECS.outline, (resolved, args) => markdown.outline(resolved, args))));
59
+ ctx.effect(() => ctx.tools.register(tool(markdown.TOOL_SPECS.search, (resolved, args) => markdown.search(resolved, args))));
60
+ ctx.effect(() => ctx.tools.register(tool(markdown.TOOL_SPECS.write, (resolved, args, agent) => markdown.write(resolved, { session: agent.id, source: 'ai' }, args))));
61
+ // VulcanCode hides memory_write in Plan mode and forbids it for subagents; dsh keeps the tool
62
+ // catalog stable, so the same rule is a refusal before the call runs.
63
+ ctx.on('tools/pre-execute', async (exec, next) => {
64
+ if (exec.name !== markdown.TOOL_SPECS.write.name || exec.agent === undefined)
65
+ return next();
66
+ if (isSubagent(exec.agent.session.header)) {
67
+ return { kind: 'deny', reason: 'Subagents cannot change long-term memory; report the fact to the parent conversation instead.' };
68
+ }
69
+ if (ctx.get('planMode')?.get(exec.agent).active === true) {
70
+ return { kind: 'deny', reason: 'Plan mode is read-only: long-term memory cannot be changed until plan mode ends.' };
71
+ }
72
+ return next();
73
+ });
74
+ const projections = ctx.sessionProjections;
75
+ const registered = await projections.register({
76
+ key: PROJECTION_KEY,
77
+ stateVersion: 1,
78
+ stateSchema: injectionStateSchema,
79
+ init: () => INITIAL_STATE,
80
+ apply: applyEvent,
81
+ });
82
+ /** Whether this session's model currently sees a memory snapshot. */
83
+ const injectionState = isHandle(registered)
84
+ ? session => registered.stateOf(session)
85
+ // dsh 0.2.1 serves states by key only.
86
+ : session => projections.stateOf(session, PROJECTION_KEY) ?? INITIAL_STATE;
87
+ ctx.on('agent/pre-step', async ({ agent, signal }, next) => {
88
+ const decision = await next();
89
+ if (decision.kind === 'reject' || signal.aborted)
90
+ return decision;
91
+ if (isSubagent(agent.session.header))
92
+ return decision;
93
+ const state = injectionState(agent.session);
94
+ if (state.injectedSeq !== null)
95
+ return decision;
96
+ let text;
97
+ try {
98
+ const resolved = access(agent);
99
+ if (resolved === undefined)
100
+ return decision;
101
+ text = renderMemoryBlock(guide, markdown.snapshot(resolved), state.compacted);
102
+ }
103
+ catch (error) {
104
+ ctx.logger.warn(`cannot read long-term memory: ${error instanceof Error ? error.message : String(error)}`);
105
+ return decision;
106
+ }
107
+ return {
108
+ ...decision,
109
+ messages: [createUserMessage({
110
+ content: [{ type: 'text', text }],
111
+ source: { kind: SOURCE_KIND, form: 'snapshot', sections: [{ name: SOURCE_KIND, text }] },
112
+ }), ...decision.messages],
113
+ };
114
+ }, { prepend: true });
115
+ ctx.skills.registerProvider(() => {
116
+ const provider = {
117
+ name: PROVIDER,
118
+ list: () => Promise.resolve(loadSettings(memoryDirectory).mode !== 'markdown' ? [] : [{
119
+ name: SKILL_NAME,
120
+ description: skill.description,
121
+ invocation: { modelInvocable: true, userInvocable: true },
122
+ provider: PROVIDER,
123
+ source: 'vulcan-memory',
124
+ rank: SKILL_RANK,
125
+ locator: SKILL_NAME,
126
+ }]),
127
+ async get(candidate) {
128
+ const { rank: _rank, locator: _locator, ...summary } = candidate;
129
+ return { ...summary, content: skill.body };
130
+ },
131
+ };
132
+ return provider;
133
+ });
134
+ ctx.inject(['connection', 'workspaceRegistry'], (child) => {
135
+ const hosts = () => child.workspaceRegistry.list().map(page.hostProjectOf);
136
+ const routes = [
137
+ ['GET', 'overview', async (_request, url) => {
138
+ const all = hosts();
139
+ return page.loadOverview(store, all, page.resolveHost(all, param(url, 'projectId')), param(url, 'memoryProjectId'));
140
+ }],
141
+ ['POST', 'write', async (request) => {
142
+ const input = page.fields(await readJson(request), ['projectId', 'memoryProjectId', 'operations']);
143
+ const all = hosts();
144
+ return page.writeAsUser(store, all, page.resolveHost(all, page.optionalText(input, 'projectId')), page.optionalText(input, 'memoryProjectId'), input['operations']);
145
+ }],
146
+ ['GET', 'file', async (_request, url) => page.readFile(store, page.scopeParam(url.searchParams.get('scope')), param(url, 'memoryProjectId'))],
147
+ ['POST', 'file', async (request) => {
148
+ const input = page.fields(await readJson(request), ['scope', 'memoryProjectId', 'expected', 'text']);
149
+ return page.replaceFile(store, page.scopeParam(input['scope']), page.optionalText(input, 'memoryProjectId'), page.text(input, 'expected'), page.text(input, 'text'));
150
+ }],
151
+ ['POST', 'project', async (request) => {
152
+ const action = page.parseProjectAction(await readJson(request));
153
+ return page.applyProjectAction(store, page.resolveHost(hosts(), page.actionHostId(action)), action);
154
+ }],
155
+ ['GET', 'settings', async () => page.settingsView(loadSettings(memoryDirectory))],
156
+ ['POST', 'settings', async (request) => {
157
+ const settings = page.parseSettingsSave(await readJson(request));
158
+ page.saveSettings(memoryDirectory, settings);
159
+ return page.settingsView(settings);
160
+ }],
161
+ ['POST', 'export', async (request) => page.exportRequest(store, await readJson(request))],
162
+ ['POST', 'import/preview', async (request) => page.previewRequest(store, await readJson(request))],
163
+ ['POST', 'import', async (request) => page.importRequest(store, await readJson(request))],
164
+ ];
165
+ // dsh accepts one registration per exact path, so a path's methods share one route.
166
+ for (const [route, byMethod] of groupRoutes(routes)) {
167
+ child.effect(() => child.connection.fetch.register({
168
+ path: `${ROUTE_PREFIX}${route}`,
169
+ methods: [...byMethod.keys()],
170
+ requestBody: 'buffered',
171
+ fetch: request => handle(byMethod.get(request.method), request),
172
+ }));
173
+ }
174
+ });
175
+ }
176
+ /**
177
+ * Whether an awaited registration is a dsh 0.2.2 handle. dsh 0.2.1 registers synchronously and
178
+ * awaiting its disposer yields a function; both are inside `engines.dsh`.
179
+ */
180
+ function isHandle(value) {
181
+ return typeof value === 'object' && value !== null && typeof value.stateOf === 'function';
182
+ }
183
+ /** Group route rows by path; dsh dispatches only the methods registered for a path. */
184
+ export function groupRoutes(routes) {
185
+ const grouped = new Map();
186
+ for (const [method, route, handler] of routes) {
187
+ const byMethod = grouped.get(route) ?? new Map();
188
+ if (byMethod.has(method))
189
+ throw new Error(`vulcan-memory: ${method} ${route} is declared twice`);
190
+ byMethod.set(method, handler);
191
+ grouped.set(route, byMethod);
192
+ }
193
+ return grouped;
194
+ }
195
+ function param(url, name) {
196
+ const value = url.searchParams.get(name);
197
+ return value === null || value === '' ? undefined : value;
198
+ }
199
+ async function readJson(request) {
200
+ try {
201
+ return await request.json();
202
+ }
203
+ catch {
204
+ throw MemoryError.invalid('The request body must be JSON.');
205
+ }
206
+ }
207
+ const STATUS = {
208
+ invalid: 400,
209
+ unsupported_format: 400,
210
+ project_unavailable: 400,
211
+ not_found: 404,
212
+ revision_mismatch: 409,
213
+ concurrent_modification: 409,
214
+ rollback_incomplete: 500,
215
+ io: 500,
216
+ json: 500,
217
+ };
218
+ /** Answer one page request; failures carry their kind and message, never a stack. */
219
+ export async function handle(handler, request) {
220
+ try {
221
+ const value = await handler(request, new URL(request.url));
222
+ return Response.json({ ok: true, value: value ?? null }, { headers: { 'cache-control': 'no-store' } });
223
+ }
224
+ catch (error) {
225
+ const code = error instanceof MemoryError ? error.kind : 'failed';
226
+ const status = error instanceof MemoryError ? STATUS[error.kind] : 500;
227
+ const message = error instanceof Error ? error.message : String(error);
228
+ return Response.json({ ok: false, error: { code, message } }, { status, headers: { 'cache-control': 'no-store' } });
229
+ }
230
+ }
231
+ /** Split a SKILL.md into its front-matter description and body. */
232
+ export function parseSkill(text) {
233
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
234
+ if (match === null)
235
+ throw new Error('memory-curator SKILL.md has no front matter');
236
+ const description = /^description:\s*(.+)$/m.exec(match[1] ?? '')?.[1]?.trim();
237
+ if (description === undefined || description === '')
238
+ throw new Error('memory-curator SKILL.md has no description');
239
+ return { description, body: text.slice(match[0].length).trim() };
240
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The memory snapshot a conversation sees. VulcanCode locks it into the system-prompt baseline;
3
+ * dsh's equivalent is one durable, source-attributed user message: appended before the first step
4
+ * of a session, and again only after a compaction shadowed it. Writes made in between are not
5
+ * shown (the model reads current text with `memory_search`), so the prompt cache stays intact.
6
+ */
7
+ import { z } from 'zod';
8
+ import type { ContextFormed } from '@deepseek-ai/dsh-llm';
9
+ import type { SessionEvent, SessionHeader } from '@deepseek-ai/dsh-session';
10
+ export declare const SOURCE_KIND = "vulcan-memory";
11
+ export declare const PROJECTION_KEY = "vulcanMemory";
12
+ declare module '@deepseek-ai/dsh-llm' {
13
+ interface MessageSourceMap {
14
+ 'vulcan-memory': {
15
+ kind: 'vulcan-memory';
16
+ } & ContextFormed;
17
+ }
18
+ }
19
+ export declare const injectionStateSchema: z.ZodObject<{
20
+ injectedSeq: z.ZodNullable<z.ZodNumber>;
21
+ compacted: z.ZodBoolean;
22
+ }, z.core.$strip>;
23
+ export type InjectionState = z.infer<typeof injectionStateSchema>;
24
+ export declare const INITIAL_STATE: InjectionState;
25
+ /** Fold one session event into the injection state. */
26
+ export declare function applyEvent(state: InjectionState, event: SessionEvent): InjectionState;
27
+ /** Subagents never see or write long-term memory, as in VulcanCode. */
28
+ export declare function isSubagent(header: SessionHeader): boolean;
29
+ /** The snapshot message text: VulcanCode's `long_term_memory` block, guide first. */
30
+ export declare function renderMemoryBlock(guide: string, snapshot: string, compacted: boolean): string;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The memory snapshot a conversation sees. VulcanCode locks it into the system-prompt baseline;
3
+ * dsh's equivalent is one durable, source-attributed user message: appended before the first step
4
+ * of a session, and again only after a compaction shadowed it. Writes made in between are not
5
+ * shown (the model reads current text with `memory_search`), so the prompt cache stays intact.
6
+ */
7
+ import { z } from 'zod';
8
+ export const SOURCE_KIND = 'vulcan-memory';
9
+ export const PROJECTION_KEY = 'vulcanMemory';
10
+ export const injectionStateSchema = z.object({
11
+ /** Seq of the snapshot message the model currently sees, or null when none is visible. */
12
+ injectedSeq: z.number().nullable(),
13
+ /** Whether a compaction shadowed an earlier snapshot. */
14
+ compacted: z.boolean(),
15
+ });
16
+ export const INITIAL_STATE = { injectedSeq: null, compacted: false };
17
+ /** Fold one session event into the injection state. */
18
+ export function applyEvent(state, event) {
19
+ if (event.type === 'user/message') {
20
+ return event.data.source.kind === SOURCE_KIND ? { ...state, injectedSeq: event.seq } : state;
21
+ }
22
+ if (event.type === 'compaction/summary' || event.type === 'compaction/prune') {
23
+ const injected = state.injectedSeq;
24
+ if (injected !== null && event.data.shadowedSeqs.some(seq => seq === injected))
25
+ return { injectedSeq: null, compacted: true };
26
+ }
27
+ return state;
28
+ }
29
+ /** Subagents never see or write long-term memory, as in VulcanCode. */
30
+ export function isSubagent(header) {
31
+ return header.origin === 'subagent' || (header.delegationDepth ?? 0) > 0;
32
+ }
33
+ const DESCRIPTION = 'Long-term memory captured when this conversation started or was last compacted. Changes written since then are not shown here; use memory_search for the current text.';
34
+ /** The snapshot message text: VulcanCode's `long_term_memory` block, guide first. */
35
+ export function renderMemoryBlock(guide, snapshot, compacted) {
36
+ const memory = snapshot.trim() === '' ? 'Memory is empty so far.' : snapshot;
37
+ return [
38
+ '<long_term_memory>',
39
+ DESCRIPTION,
40
+ `Phase: ${compacted ? 'after_context_compaction' : 'first_session'}`,
41
+ 'Source: Markdown memory',
42
+ '',
43
+ `${guide.trim()}\n\n${memory}`,
44
+ '</long_term_memory>',
45
+ ].join('\n');
46
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Limits of Markdown memory, the same as VulcanCode's `vulcan-markdown-memory` crate
3
+ * (`crates/vulcan-markdown-memory/src/lib.rs`, `settings.rs`). Byte limits count UTF-8 bytes.
4
+ */
5
+ export declare const MAX_NODE_LEVEL = 4;
6
+ export declare const MAX_TITLE_CHARS = 120;
7
+ export declare const MAX_NODE_BODY_BYTES: number;
8
+ export declare const MAX_FILE_BYTES: number;
9
+ export declare const MAX_OPERATIONS_PER_WRITE = 16;
10
+ export declare const MAX_SEARCH_PATTERNS = 12;
11
+ export declare const DEFAULT_SEARCH_LIMIT = 6;
12
+ export declare const MAX_SEARCH_LIMIT = 20;
13
+ export declare const NODE_PAGE_BYTES: number;
14
+ export declare const BUDGET_WARNING_PERCENT = 80;
15
+ export declare const MAX_PART_TOKENS = 16000;
16
+ export declare const REGISTRY_FORMAT_VERSION = 1;
17
+ export declare const SETTINGS_FORMAT_VERSION = 1;
18
+ export declare const MAX_PACKAGE_PROJECTS = 256;
19
+ /** The manifest, user and space entries plus every project, each at most a whole file. */
20
+ export declare const MAX_PACKAGE_BYTES: number;
21
+ export declare const EXPORT_FORMAT_VERSION = 1;
22
+ /** UTF-8 byte length, which every Rust `str::len` limit counts. */
23
+ export declare function byteLength(text: string): number;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Limits of Markdown memory, the same as VulcanCode's `vulcan-markdown-memory` crate
3
+ * (`crates/vulcan-markdown-memory/src/lib.rs`, `settings.rs`). Byte limits count UTF-8 bytes.
4
+ */
5
+ export const MAX_NODE_LEVEL = 4;
6
+ export const MAX_TITLE_CHARS = 120;
7
+ export const MAX_NODE_BODY_BYTES = 16 * 1024;
8
+ export const MAX_FILE_BYTES = 512 * 1024;
9
+ export const MAX_OPERATIONS_PER_WRITE = 16;
10
+ export const MAX_SEARCH_PATTERNS = 12;
11
+ export const DEFAULT_SEARCH_LIMIT = 6;
12
+ export const MAX_SEARCH_LIMIT = 20;
13
+ export const NODE_PAGE_BYTES = 6 * 1024;
14
+ export const BUDGET_WARNING_PERCENT = 80;
15
+ export const MAX_PART_TOKENS = 16_000;
16
+ export const REGISTRY_FORMAT_VERSION = 1;
17
+ export const SETTINGS_FORMAT_VERSION = 1;
18
+ export const MAX_PACKAGE_PROJECTS = 256;
19
+ /** The manifest, user and space entries plus every project, each at most a whole file. */
20
+ export const MAX_PACKAGE_BYTES = (MAX_PACKAGE_PROJECTS + 3) * MAX_FILE_BYTES;
21
+ export const EXPORT_FORMAT_VERSION = 1;
22
+ /** UTF-8 byte length, which every Rust `str::len` limit counts. */
23
+ export function byteLength(text) {
24
+ return Buffer.byteLength(text, 'utf8');
25
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * One memory file parsed into heading nodes (`document.rs`). Parsing never fails and rendering
3
+ * reproduces the source bytes exactly; only nodes that were written regenerate their heading or
4
+ * metadata line. Headings are found with a CommonMark parser, so `#` lines inside fences, quotes
5
+ * and lists stay body text.
6
+ */
7
+ import type { MemoryScope } from './scope.js';
8
+ export type NodeSource = 'ai' | 'user';
9
+ /** Parsed `<!-- mem ... -->` metadata line. */
10
+ export interface NodeMeta {
11
+ readonly id: string;
12
+ readonly section?: string;
13
+ readonly created: string;
14
+ readonly updated: string;
15
+ readonly session?: string;
16
+ readonly source: NodeSource;
17
+ }
18
+ /** Whether a value can be stored in a metadata line and read back. */
19
+ export declare function isMetaValue(value: string): boolean;
20
+ /** Parse one metadata line; malformed lines are ordinary body text. */
21
+ export declare function parseMetaLine(line: string): NodeMeta | undefined;
22
+ /** Render a metadata comment without a line terminator. */
23
+ export declare function renderMeta(meta: NodeMeta): string;
24
+ /** One heading node with its own body and child nodes. */
25
+ export declare class MemoryNode {
26
+ level: number;
27
+ title: string;
28
+ meta: NodeMeta | undefined;
29
+ /** Own body bytes up to the next node heading. */
30
+ private body;
31
+ /** Original heading bytes including the terminator; `undefined` regenerates it. */
32
+ private headingRaw;
33
+ /** Original metadata line including the terminator; `undefined` regenerates it. */
34
+ private metaRaw;
35
+ children: MemoryNode[];
36
+ constructor(level: number, title: string, meta: NodeMeta | undefined,
37
+ /** Own body bytes up to the next node heading. */
38
+ body: string, raw?: {
39
+ heading: string;
40
+ meta: string | undefined;
41
+ });
42
+ /** Copy without children for another document: lines regenerate at `level`, the body uses `eol`. */
43
+ detached(level: number, eol: string): MemoryNode;
44
+ rawBody(): string;
45
+ /** The own body with normalized line endings and no surrounding blank lines. */
46
+ bodyText(): string;
47
+ setBody(body: string): void;
48
+ setTitle(title: string): void;
49
+ setMeta(meta: NodeMeta): void;
50
+ /** Shift the subtree to a new root level; returns the deepest level reached when it does not fit. */
51
+ relevel(level: number): number | undefined;
52
+ private subtreeDepth;
53
+ private shiftLevels;
54
+ /** Revision token of the node's own title and body. */
55
+ revision(): string;
56
+ descendantCount(): number;
57
+ /** Append this subtree's text, reusing original bytes wherever they are still valid. */
58
+ render(eol: string, output: string[]): void;
59
+ }
60
+ /** A flattened, read-only view of one node used by search, outline and writes. */
61
+ export interface NodeView {
62
+ /** Metadata id, or an `h-` id derived from scope, title path and occurrence. */
63
+ readonly id: string;
64
+ readonly scope: MemoryScope;
65
+ readonly level: number;
66
+ /** Titles from the top-level node down to this one. */
67
+ readonly path: readonly string[];
68
+ /** Child indexes from the document root. */
69
+ readonly locator: readonly number[];
70
+ /** Section key of the top-level H1 this node belongs to. */
71
+ readonly section: string | undefined;
72
+ readonly revision: string;
73
+ readonly meta: NodeMeta | undefined;
74
+ /** Normalized own body. */
75
+ readonly body: string;
76
+ /** Titles of the direct children. */
77
+ readonly children: readonly string[];
78
+ readonly descendants: number;
79
+ }
80
+ /** The last title of a view's path. */
81
+ export declare function viewTitle(view: NodeView): string;
82
+ /** One parsed memory file. */
83
+ export declare class MemoryDocument {
84
+ /** Line terminator for generated lines, taken from the source. */
85
+ readonly eol: '\n' | '\r\n';
86
+ /** Bytes before the first node heading. */
87
+ private readonly preamble;
88
+ readonly roots: MemoryNode[];
89
+ private constructor();
90
+ static empty(): MemoryDocument;
91
+ static parse(text: string): MemoryDocument;
92
+ render(): string;
93
+ /** Views of every node in document order. */
94
+ views(scope: MemoryScope): NodeView[];
95
+ node(locator: readonly number[]): MemoryNode | undefined;
96
+ /** Remove and return the subtree at a locator. */
97
+ remove(locator: readonly number[]): MemoryNode | undefined;
98
+ /** Append a subtree as the last child of a parent, or as the last top-level node. */
99
+ append(parent: readonly number[] | undefined, node: MemoryNode): boolean;
100
+ }
101
+ /** Convert written text into stored body bytes: the file's terminators and one trailing blank line. */
102
+ export declare function storedBody(text: string, eol: string): string;
103
+ /** Levels of node-forming headings in text written as a body. */
104
+ export declare function nodeHeadingLevels(text: string): number[];
105
+ /** Whether text renders as exactly one ATX heading with the given title. */
106
+ export declare function titleRoundTrips(level: number, title: string): boolean;
107
+ /** Rust `str::lines`: split on `\n`, drop one trailing empty line, strip a `\r` ending each line. */
108
+ export declare function lines(text: string): string[];