@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,29 @@
1
+ /**
2
+ * Recover a fence's info string from the Markdown source. dsh's code block receives only the first
3
+ * word of the info string as its language, while `ui-*` components also read the rest (`ui-chart bar
4
+ * Weekly`), so the browser half looks the fence up in the message text it rendered from.
5
+ */
6
+ /** One fenced block found in Markdown source. */
7
+ export interface SourceFence {
8
+ /** First word of the info string, as Markdown parsers report the language. */
9
+ readonly language: string;
10
+ /** The rest of the info string, trimmed. */
11
+ readonly meta: string;
12
+ /** The fence content without its fences and without the opening line's indentation. */
13
+ readonly body: string;
14
+ }
15
+ /**
16
+ * Every fenced block of `markdown`, outer fences before the fences nested in their content. The scan is
17
+ * deliberately lenient (list and quote containers are only stripped, not modelled): it serves to look
18
+ * up a fence whose language and content the renderer already reported, never to decide what is a fence.
19
+ */
20
+ export declare function scanFences(markdown: string): SourceFence[];
21
+ /**
22
+ * The meta of the fence in `markdown` whose language starts with `language` (as dsh reports it, the
23
+ * leading `[\w-]+` run) and whose content is `code`. An exact content match wins over one that only
24
+ * matches line by line after trimming; undefined when no fence matches.
25
+ */
26
+ export declare function findFenceInfo(markdown: string, language: string, code: string): {
27
+ language: string;
28
+ meta: string;
29
+ } | undefined;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Recover a fence's info string from the Markdown source. dsh's code block receives only the first
3
+ * word of the info string as its language, while `ui-*` components also read the rest (`ui-chart bar
4
+ * Weekly`), so the browser half looks the fence up in the message text it rendered from.
5
+ */
6
+ /** Container prefix (`>` quotes) and indentation before a fence marker. */
7
+ const OPENING = /^((?:[ \t]*>)*)([ \t]*)(`{3,}|~{3,})(.*)$/u;
8
+ /**
9
+ * Every fenced block of `markdown`, outer fences before the fences nested in their content. The scan is
10
+ * deliberately lenient (list and quote containers are only stripped, not modelled): it serves to look
11
+ * up a fence whose language and content the renderer already reported, never to decide what is a fence.
12
+ */
13
+ export function scanFences(markdown) {
14
+ const lines = markdown.split(/\r?\n/u);
15
+ const found = [];
16
+ for (let index = 0; index < lines.length; index += 1) {
17
+ const opening = OPENING.exec(lines[index] ?? '');
18
+ if (!opening)
19
+ continue;
20
+ const quotes = opening[1] ?? '';
21
+ const indent = (opening[2] ?? '').length;
22
+ const marker = opening[3] ?? '';
23
+ const info = (opening[4] ?? '').trim();
24
+ if (marker.startsWith('`') && info.includes('`'))
25
+ continue;
26
+ const char = marker[0] ?? '`';
27
+ const closing = new RegExp(`^(?:[ \\t]*>)*[ \\t]*${char}{${marker.length},}[ \\t]*$`, 'u');
28
+ const body = [];
29
+ let end = index + 1;
30
+ for (; end < lines.length; end += 1) {
31
+ const line = lines[end] ?? '';
32
+ if (closing.test(line))
33
+ break;
34
+ body.push(stripPrefix(line, quotes.length > 0, indent));
35
+ }
36
+ const [word = ''] = info.split(/\s/u);
37
+ found.push({ language: word, meta: info.slice(word.length).trim(), body: body.join('\n') });
38
+ const nested = scanFences(body.join('\n'));
39
+ found.push(...nested);
40
+ index = end;
41
+ }
42
+ return found;
43
+ }
44
+ /** Remove a quote prefix and up to `indent` columns of indentation from one content line. */
45
+ function stripPrefix(line, quoted, indent) {
46
+ let text = quoted ? line.replace(/^(?:[ \t]*>[ ]?)+/u, '') : line;
47
+ let removed = 0;
48
+ while (removed < indent && (text.startsWith(' ') || text.startsWith('\t'))) {
49
+ text = text.slice(1);
50
+ removed += 1;
51
+ }
52
+ return text;
53
+ }
54
+ /**
55
+ * The meta of the fence in `markdown` whose language starts with `language` (as dsh reports it, the
56
+ * leading `[\w-]+` run) and whose content is `code`. An exact content match wins over one that only
57
+ * matches line by line after trimming; undefined when no fence matches.
58
+ */
59
+ export function findFenceInfo(markdown, language, code) {
60
+ const target = code.replace(/\n$/u, '');
61
+ const candidates = scanFences(markdown).filter((fence) => /^[\w-]+/u.exec(fence.language)?.[0] === language);
62
+ const exact = candidates.find((fence) => fence.body === target);
63
+ const loose = exact ?? candidates.find((fence) => sameTrimmedLines(fence.body, target));
64
+ return loose === undefined ? undefined : { language: loose.language, meta: loose.meta };
65
+ }
66
+ /** Whether two texts have the same nonblank lines after trimming each. */
67
+ function sameTrimmedLines(a, b) {
68
+ const lines = (text) => text.split('\n').map((line) => line.trim()).filter(Boolean).join('\n');
69
+ return lines(a) === lines(b);
70
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Vulcan Answers for DeepSeek Harness: VulcanCode's interactive answer mode. While a session is
3
+ * interactive the model receives the `ui-*` component guide as runtime context, and the browser half
4
+ * renders the settled components in the chat. Each session keeps its own mode (see `session-state.ts`).
5
+ */
6
+ import type { Context, Volatile } from '@deepseek-ai/cordis';
7
+ import Schema from '@deepseek-ai/schemastery';
8
+ import { type AnswerMode } from './session-state.js';
9
+ export { parseAnswerComponent, SUPPORTED_ANSWER_LANGUAGES, type AnswerComponent } from './answer-model.js';
10
+ export { MODE_COMMAND, MODE_RECORD, PROJECTION_KEY, type AnswerMode, type AnswerSessionView } from './session-state.js';
11
+ export declare const name = "vulcan-answers";
12
+ export declare const inject: string[];
13
+ /** Every field is volatile: dsh's settings forms expose only volatile fields. */
14
+ export interface Config {
15
+ /** Answer mode a new session starts with; existing sessions keep their own. */
16
+ defaultMode: Volatile<AnswerMode>;
17
+ }
18
+ export declare const Config: Schema<Config>;
19
+ export declare function apply(ctx: Context, config: Config): Promise<void>;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Vulcan Answers for DeepSeek Harness: VulcanCode's interactive answer mode. While a session is
3
+ * interactive the model receives the `ui-*` component guide as runtime context, and the browser half
4
+ * renders the settled components in the chat. Each session keeps its own mode (see `session-state.ts`).
5
+ */
6
+ import Schema from '@deepseek-ai/schemastery';
7
+ import { answerContextText, loadAnswerModeTexts } from './prompt.js';
8
+ import { installSessionState, seedSession } from './session-state.js';
9
+ export { parseAnswerComponent, SUPPORTED_ANSWER_LANGUAGES } from './answer-model.js';
10
+ export { MODE_COMMAND, MODE_RECORD, PROJECTION_KEY } from './session-state.js';
11
+ export const name = 'vulcan-answers';
12
+ export const inject = ['sessionProjections', 'systemPrompt'];
13
+ /** Runtime context name; one per process. */
14
+ const CONTEXT_NAME = 'vulcan-answers:answer-mode';
15
+ /** After dsh's own runtime contexts (100-210), so the guide follows the facts it builds on. */
16
+ const CONTEXT_ORDER = 300;
17
+ export const Config = Schema.object({
18
+ defaultMode: Schema.union(['interactive', 'standard'])
19
+ .default('interactive')
20
+ .description('Answer mode new sessions start with. interactive: answers use ui-* components. standard: plain Markdown. Existing sessions keep their own mode.')
21
+ .volatile(),
22
+ });
23
+ /** The session an assembly is for; dsh-agent adds `agent` to the assemble context. */
24
+ function assembledSession(context) {
25
+ return context.agent?.session;
26
+ }
27
+ export async function apply(ctx, config) {
28
+ const texts = loadAnswerModeTexts();
29
+ const defaultMode = () => config.defaultMode.get();
30
+ const read = await installSessionState(ctx, defaultMode);
31
+ ctx.systemPrompt.context({
32
+ name: CONTEXT_NAME,
33
+ order: CONTEXT_ORDER,
34
+ interpolate: false,
35
+ // The mode is the user's choice for the session, so a persona that drops optional context keeps it.
36
+ required: true,
37
+ text: (context) => {
38
+ const session = assembledSession(context);
39
+ if (session === undefined)
40
+ return '';
41
+ const view = read(session);
42
+ const fallback = defaultMode();
43
+ try {
44
+ seedSession(session, view, fallback);
45
+ }
46
+ catch (error) {
47
+ ctx.logger.warn('could not record the default answer mode: %s', error);
48
+ }
49
+ return answerContextText(view, fallback, texts);
50
+ },
51
+ });
52
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Bounds of the `ui-*` answer components. The prompt states them to the model and the parser enforces
3
+ * them, so both read these constants. The values follow VulcanCode's
4
+ * `crates/vulcan-session-core/src/v1/answer_components.rs`.
5
+ */
6
+ /** Most data rows one component (or one tabs/columns fence) may hold. */
7
+ export declare const ANSWER_COMPONENT_MAX_ROWS = 128;
8
+ /** Most series one `ui-chart` may draw. */
9
+ export declare const ANSWER_CHART_MAX_SERIES = 8;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Bounds of the `ui-*` answer components. The prompt states them to the model and the parser enforces
3
+ * them, so both read these constants. The values follow VulcanCode's
4
+ * `crates/vulcan-session-core/src/v1/answer_components.rs`.
5
+ */
6
+ /** Most data rows one component (or one tabs/columns fence) may hold. */
7
+ export const ANSWER_COMPONENT_MAX_ROWS = 128;
8
+ /** Most series one `ui-chart` may draw. */
9
+ export const ANSWER_CHART_MAX_SERIES = 8;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The model-facing answer mode instructions. Both texts are English package assets; the interactive one
3
+ * names the component limits as `{{max_rows}}` and `{{max_series}}`, which are substituted here from
4
+ * the parser's own constants, so the context is registered with interpolation off.
5
+ */
6
+ import { type AnswerMode, type AnswerSessionView } from './session-state.js';
7
+ /** The two instruction texts a session can receive. */
8
+ export interface AnswerModeTexts {
9
+ /** The component guide, sent while a session is interactive. */
10
+ readonly interactive: string;
11
+ /** The stop note, sent once a session that was interactive turns standard. */
12
+ readonly standard: string;
13
+ }
14
+ /** Replace every `{{name}}` with its value; a placeholder without a value is a packaging error. */
15
+ export declare function substituteLimits(template: string, values: Readonly<Record<string, string>>): string;
16
+ /** Read and fill the packaged instruction texts. */
17
+ export declare function loadAnswerModeTexts(): AnswerModeTexts;
18
+ /**
19
+ * The context one session receives: the guide while interactive, the stop note once an interactive
20
+ * session turned standard, and nothing for subagents or sessions that were never interactive.
21
+ */
22
+ export declare function answerContextText(view: AnswerSessionView, defaultMode: AnswerMode, texts: AnswerModeTexts): string;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The model-facing answer mode instructions. Both texts are English package assets; the interactive one
3
+ * names the component limits as `{{max_rows}}` and `{{max_series}}`, which are substituted here from
4
+ * the parser's own constants, so the context is registered with interpolation off.
5
+ */
6
+ import { readFileSync } from 'node:fs';
7
+ import { ANSWER_CHART_MAX_SERIES, ANSWER_COMPONENT_MAX_ROWS } from './limits.js';
8
+ import { effectiveMode } from './session-state.js';
9
+ /** Replace every `{{name}}` with its value; a placeholder without a value is a packaging error. */
10
+ export function substituteLimits(template, values) {
11
+ return template.replace(/\{\{(\w+)\}\}/gu, (_whole, key) => {
12
+ const value = values[key];
13
+ if (value === undefined)
14
+ throw new Error(`answer mode prompt: no value for {{${key}}}`);
15
+ return value;
16
+ });
17
+ }
18
+ /** Read and fill the packaged instruction texts. */
19
+ export function loadAnswerModeTexts() {
20
+ const read = (file) => readFileSync(new URL(`../assets/prompts/${file}`, import.meta.url), 'utf8').trim();
21
+ return Object.freeze({
22
+ interactive: substituteLimits(read('answer-mode-interactive.md'), {
23
+ max_rows: String(ANSWER_COMPONENT_MAX_ROWS),
24
+ max_series: String(ANSWER_CHART_MAX_SERIES),
25
+ }),
26
+ standard: read('answer-mode-standard.md'),
27
+ });
28
+ }
29
+ /**
30
+ * The context one session receives: the guide while interactive, the stop note once an interactive
31
+ * session turned standard, and nothing for subagents or sessions that were never interactive.
32
+ */
33
+ export function answerContextText(view, defaultMode, texts) {
34
+ if (view.subagent)
35
+ return '';
36
+ if (effectiveMode(view, defaultMode) === 'interactive')
37
+ return texts.interactive;
38
+ return view.everInteractive ? texts.standard : '';
39
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The summary of the reader's `ui-decision` answers that the view puts into the composer; the
3
+ * interactive guide describes this shape to the model.
4
+ */
5
+ /** One answered decision. */
6
+ export interface DecisionReply {
7
+ readonly question: string;
8
+ readonly picks: readonly string[];
9
+ /** Option labels the answer recommended. */
10
+ readonly recommended: readonly string[];
11
+ readonly note: string;
12
+ }
13
+ /** Interface-language words the summary uses. */
14
+ export interface ReplyLabels {
15
+ readonly decisions: string;
16
+ readonly recommended: string;
17
+ readonly noChoice: string;
18
+ }
19
+ /** Summarize decisions as Markdown: `question → picks`, a recommended pick labelled, notes quoted. */
20
+ export declare function formatDecisionReply(entries: readonly DecisionReply[], labels: ReplyLabels): string;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The summary of the reader's `ui-decision` answers that the view puts into the composer; the
3
+ * interactive guide describes this shape to the model.
4
+ */
5
+ /** Summarize decisions as Markdown: `question → picks`, a recommended pick labelled, notes quoted. */
6
+ export function formatDecisionReply(entries, labels) {
7
+ if (entries.length === 0)
8
+ return '';
9
+ const quote = (note) => note.trim() ? note.trim().split(/\r?\n/u).map((line) => ` > ${line}`.trimEnd()) : [];
10
+ const lines = entries.flatMap((entry) => [
11
+ `- ${entry.question} → ${entry.picks.length > 0
12
+ ? entry.picks.map((pick) => entry.recommended.includes(pick) ? `${pick} (${labels.recommended})` : pick).join(', ')
13
+ : labels.noChoice}`,
14
+ ...quote(entry.note),
15
+ ]);
16
+ return [`**${labels.decisions}**`, ...lines].join('\n');
17
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Per-session answer mode. The mode lives in the session's own log as plugin records, so it follows
3
+ * the session through restarts, forks and resumes; a session projection folds it and serves it to the
4
+ * composer pill, and one slash command is the only user write path. The plugin's configured default
5
+ * only seeds a session that has no record yet, at its first prompt assembly; subagent sessions are
6
+ * always standard and never get a record.
7
+ */
8
+ import type { Context } from '@deepseek-ai/cordis';
9
+ import { type Session, type SessionEvent, type SessionHeader } from '@deepseek-ai/dsh-session';
10
+ /** How the model answers in one session. */
11
+ export type AnswerMode = 'standard' | 'interactive';
12
+ /** Package name, the command definition id. */
13
+ export declare const PACKAGE_NAME = "@openvulcan/dsh-vulcan-answers";
14
+ /** Record type of one mode switch. */
15
+ export declare const MODE_RECORD = "plugin:vulcan-answers/mode";
16
+ /** Projection key the client reads; also restated in `client/shared.ts`. */
17
+ export declare const PROJECTION_KEY = "vulcanAnswers";
18
+ /** Slash command that switches the mode; also restated in `client/shared.ts`. */
19
+ export declare const MODE_COMMAND = "vulcan-answer-mode";
20
+ /** One mode switch, as logged. */
21
+ export interface ModeRecord {
22
+ readonly mode: AnswerMode;
23
+ }
24
+ /** What the session projection holds and serves. */
25
+ export interface AnswerSessionView {
26
+ /** The recorded mode; null until the session is seeded or switched. */
27
+ readonly mode: AnswerMode | null;
28
+ /** Whether any record ever set interactive, so a later standard mode needs the stop note. */
29
+ readonly everInteractive: boolean;
30
+ /** A subagent session, which always answers in standard mode. */
31
+ readonly subagent: boolean;
32
+ }
33
+ declare module '@deepseek-ai/dsh-session/types' {
34
+ interface PluginRecordMap {
35
+ /** The session's answer mode; the latest record wins. */
36
+ 'plugin:vulcan-answers/mode': ModeRecord;
37
+ }
38
+ }
39
+ declare module '@deepseek-ai/dsh-session-projection/types' {
40
+ interface SessionProjectionMap {
41
+ /** Vulcan Answers' per-session mode for the composer pill. */
42
+ vulcanAnswers: AnswerSessionView;
43
+ }
44
+ }
45
+ /** Whether a value is an answer mode. */
46
+ export declare function isAnswerMode(value: unknown): value is AnswerMode;
47
+ /** Whether a session header describes a subagent child. */
48
+ export declare function isSubagentHeader(header: Pick<SessionHeader, 'origin' | 'delegationDepth'>): boolean;
49
+ /** The state a session starts from. */
50
+ export declare function initialView(header: Pick<SessionHeader, 'origin' | 'delegationDepth'>): AnswerSessionView;
51
+ /** The mode a session answers in now: subagents are standard, an unseeded session follows the default. */
52
+ export declare function effectiveMode(view: AnswerSessionView, defaultMode: AnswerMode): AnswerMode;
53
+ /**
54
+ * Fold one event into the state. Records are validated because a restored record carries whatever an
55
+ * earlier build wrote; one that does not parse leaves the state unchanged.
56
+ */
57
+ export declare function applySessionRecord(state: AnswerSessionView, event: SessionEvent): AnswerSessionView;
58
+ /** Reader of a session's mode state. */
59
+ export type ReadAnswerState = (session: Session) => AnswerSessionView;
60
+ /** Register the projection and the command; resolves to the state reader. */
61
+ export declare function installSessionState(ctx: Context, defaultMode: () => AnswerMode): Promise<ReadAnswerState>;
62
+ /**
63
+ * Give an unseeded top-level session the configured default as its own record, so a later change of
64
+ * the default does not move it. Called while its prompt is assembled.
65
+ */
66
+ export declare function seedSession(session: Session, view: AnswerSessionView, defaultMode: AnswerMode): void;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Per-session answer mode. The mode lives in the session's own log as plugin records, so it follows
3
+ * the session through restarts, forks and resumes; a session projection folds it and serves it to the
4
+ * composer pill, and one slash command is the only user write path. The plugin's configured default
5
+ * only seeds a session that has no record yet, at its first prompt assembly; subagent sessions are
6
+ * always standard and never get a record.
7
+ */
8
+ import { CommandDefinitionId } from '@deepseek-ai/dsh-commands/brand';
9
+ import { appendPluginRecord, pluginRecordOf } from '@deepseek-ai/dsh-session';
10
+ /** Package name, the command definition id. */
11
+ export const PACKAGE_NAME = '@openvulcan/dsh-vulcan-answers';
12
+ /** Record type of one mode switch. */
13
+ export const MODE_RECORD = 'plugin:vulcan-answers/mode';
14
+ /** Projection key the client reads; also restated in `client/shared.ts`. */
15
+ export const PROJECTION_KEY = 'vulcanAnswers';
16
+ /** Slash command that switches the mode; also restated in `client/shared.ts`. */
17
+ export const MODE_COMMAND = 'vulcan-answer-mode';
18
+ /** Whether a value is an answer mode. */
19
+ export function isAnswerMode(value) {
20
+ return value === 'standard' || value === 'interactive';
21
+ }
22
+ /** Whether a session header describes a subagent child. */
23
+ export function isSubagentHeader(header) {
24
+ return header.origin === 'subagent' || (header.delegationDepth ?? 0) > 0;
25
+ }
26
+ /** The state a session starts from. */
27
+ export function initialView(header) {
28
+ return Object.freeze({ mode: null, everInteractive: false, subagent: isSubagentHeader(header) });
29
+ }
30
+ /** The mode a session answers in now: subagents are standard, an unseeded session follows the default. */
31
+ export function effectiveMode(view, defaultMode) {
32
+ return view.subagent ? 'standard' : view.mode ?? defaultMode;
33
+ }
34
+ /**
35
+ * Validator of the state and the wire view. dsh only calls `parse`; a hand-written one keeps this
36
+ * package off dsh's zod version, whose schema type the definition names.
37
+ */
38
+ const viewSchema = {
39
+ parse(value) {
40
+ const view = value;
41
+ if (typeof view === 'object' && view !== null && (view.mode === null || isAnswerMode(view.mode))
42
+ && typeof view.everInteractive === 'boolean' && typeof view.subagent === 'boolean') {
43
+ return Object.freeze({ mode: view.mode, everInteractive: view.everInteractive, subagent: view.subagent });
44
+ }
45
+ throw new TypeError('vulcanAnswers projection: expected { mode: "standard" | "interactive" | null, everInteractive: boolean, subagent: boolean }');
46
+ },
47
+ };
48
+ /**
49
+ * Fold one event into the state. Records are validated because a restored record carries whatever an
50
+ * earlier build wrote; one that does not parse leaves the state unchanged.
51
+ */
52
+ export function applySessionRecord(state, event) {
53
+ const record = pluginRecordOf(event);
54
+ if (record === undefined || record.type !== MODE_RECORD)
55
+ return state;
56
+ const data = record.data;
57
+ if (typeof data !== 'object' || data === null)
58
+ return state;
59
+ const mode = data['mode'];
60
+ if (!isAnswerMode(mode))
61
+ return state;
62
+ const everInteractive = state.everInteractive || mode === 'interactive';
63
+ return mode === state.mode && everInteractive === state.everInteractive ? state : Object.freeze({ ...state, mode, everInteractive });
64
+ }
65
+ /**
66
+ * Whether an awaited registration is a dsh 0.2.2 handle. dsh 0.2.1 registers synchronously and
67
+ * awaiting its disposer yields a function; both are inside `engines.dsh`.
68
+ */
69
+ function isHandle(value) {
70
+ return typeof value === 'object' && value !== null && typeof value.stateOf === 'function';
71
+ }
72
+ /** Register the projection and the command; resolves to the state reader. */
73
+ export async function installSessionState(ctx, defaultMode) {
74
+ const projections = ctx.sessionProjections;
75
+ const registered = await projections.register({
76
+ key: PROJECTION_KEY,
77
+ stateVersion: 1,
78
+ stateSchema: viewSchema,
79
+ init: header => initialView(header),
80
+ apply: applySessionRecord,
81
+ wire: { viewSchema, view: state => state },
82
+ });
83
+ const read = isHandle(registered)
84
+ ? session => registered.stateOf(session)
85
+ // dsh 0.2.1 serves states by key only.
86
+ : session => projections.stateOf(session, PROJECTION_KEY) ?? initialView(session.header);
87
+ ctx.inject(['commands'], (child) => {
88
+ child.commands.register({
89
+ definitionId: CommandDefinitionId(PACKAGE_NAME),
90
+ name: MODE_COMMAND,
91
+ description: 'Switch this session\'s answer mode between interactive ui-* components and standard Markdown, from the next request',
92
+ input: { hint: 'interactive | standard' },
93
+ handler: ({ agent, rawInput }) => {
94
+ const word = rawInput.trim().toLowerCase();
95
+ const view = read(agent.session);
96
+ if (word === '')
97
+ return { kind: 'success', text: `answer mode ${effectiveMode(view, defaultMode())}` };
98
+ if (!isAnswerMode(word))
99
+ return { kind: 'error', text: `expected "interactive" or "standard", got "${word}"` };
100
+ if (view.subagent)
101
+ return { kind: 'error', text: 'subagent sessions always answer in standard mode' };
102
+ if (view.mode !== word)
103
+ appendPluginRecord(agent.session, MODE_RECORD, { mode: word });
104
+ return { kind: 'success', text: `answer mode ${word} from the next request` };
105
+ },
106
+ });
107
+ });
108
+ return read;
109
+ }
110
+ /**
111
+ * Give an unseeded top-level session the configured default as its own record, so a later change of
112
+ * the default does not move it. Called while its prompt is assembled.
113
+ */
114
+ export function seedSession(session, view, defaultMode) {
115
+ if (view.subagent || view.mode !== null)
116
+ return;
117
+ appendPluginRecord(session, MODE_RECORD, { mode: defaultMode });
118
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@openvulcan/dsh-vulcan-answers",
3
+ "version": "0.4.0",
4
+ "description": "Interactive answers for DeepSeek Harness: the model answers with VulcanCode ui-* components, rendered in the chat.",
5
+ "license": "MIT",
6
+ "icon": "./assets/answers-icon.png",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/OpenVulcan/dsh-vulcancode.git",
10
+ "directory": "packages/dsh-vulcan-answers"
11
+ },
12
+ "type": "module",
13
+ "main": "lib/index.js",
14
+ "types": "lib/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./lib/index.d.ts",
18
+ "default": "./lib/index.js"
19
+ },
20
+ "./client": "./lib/client.js",
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": [
24
+ "lib",
25
+ "!lib/**/*.test.*",
26
+ "assets"
27
+ ],
28
+ "engines": {
29
+ "dsh": ">=0.2.1-rc.2 <0.3.0"
30
+ },
31
+ "dsh": {
32
+ "client": {
33
+ "platform": "web",
34
+ "inject": [
35
+ "@deepseek-ai/dsh-api-remotes",
36
+ "@deepseek-ai/dsh-api-session-controller",
37
+ "@deepseek-ai/dsh-client-locale",
38
+ "@deepseek-ai/dsh-client-ui-chat",
39
+ "@deepseek-ai/dsh-client-ui-conversation",
40
+ "@deepseek-ai/dsh-client-ui-plugin-manager",
41
+ "@deepseek-ai/dsh-client-ui-session",
42
+ "@deepseek-ai/dsh-client-ui-settings",
43
+ "@deepseek-ai/dsh-client-ui-settings-general"
44
+ ]
45
+ }
46
+ },
47
+ "peerDependencies": {
48
+ "@deepseek-ai/cordis": "^4.0.5",
49
+ "@deepseek-ai/dsh-commands": "^0.2.1-rc.2",
50
+ "@deepseek-ai/dsh-session": "^0.2.1-rc.2",
51
+ "@deepseek-ai/dsh-session-projection": "^0.2.1-rc.2",
52
+ "@deepseek-ai/dsh-settings": "^0.2.1-rc.2",
53
+ "@deepseek-ai/dsh-system-prompt": "^0.2.1-rc.2",
54
+ "@deepseek-ai/schemastery": "^3.18.5"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ }
59
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenVulcan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128.00000000 128.00000000" role="img" aria-label="LuaSkills"><title>LuaSkills — S1 contour R1 review candidate</title><desc>Review only. S1 body paths cleaned from v1.3. Dot path, position, color, transform and viewBox preserved. No changes to approved R2 wordmark. Not a new symbol design.</desc><rect id="container" x="0" y="0" width="128" height="128" rx="25.6" fill="#171C19" /><g transform="translate(19.35319915 15.36000000) scale(0.97280000)"><path id="symbol-body" d="M0.55 15.15827411L16.06973315 15.15827411C16.37348976 15.15827411 16.61973315 15.4045175 16.61973315 15.70827411L16.61973315 71.42867C16.61973315 78.8210647 22.08745984 83.32897 29.25 83.32897L75.24 83.32897C76.32047899 83.32897 77.38716 82.19985495 77.38716 81.1C77.38716 79.41897344 75.35246626 78.40112849 73.94 78.024C64.91344556 75.61390996 54.25899415 73.39323287 45.62 70.24C37.73317733 67.36130973 33.79 63.35695518 33.79 54.65C33.79 43.42129198 41.73989153 37.40335277 52.48 37.40335277L88.01407376 37.40335277C88.31783038 37.40335277 88.56407376 37.64959615 88.56407376 37.95335277L88.56407376 50.75C88.56407376 51.05375661 88.31783038 51.3 88.01407376 51.3L53 51.3C51.93161066 51.3 50.1 53.38543719 50.1 54.75C50.1 56.91 52.96532212 57.68052101 54.66 58.133C62.73280917 60.28844005 71.10844405 62.24534877 79.09 64.56C89.55856733 67.59588453 91.79029781 72.79443208 91.79029781 83.5C91.79029781 91.86377052 86.82564748 100 77.68 100L28.55 100C12.18383487 100 0 89.51226091 0 72.7L0 15.70827411C0 15.4045175 0.24624339 15.15827411 0.55 15.15827411Z" fill="#D0D0CA" fill-rule="evenodd" /><path id="symbol-dot" d="M91.70974964838106 9.341838685937416C91.70974964838106 14.501193727559992 87.68762759278803 18.683677371874833 82.72608859579702 18.683677371874833C77.76454959880601 18.683677371874833 73.74242754321298 14.501193727559992 73.74242754321298 9.341838685937416C73.74242754321298 4.182483644314841 77.76454959880601 0.0 82.72608859579702 0.0C87.68762759278803 0.0 91.70974964838106 4.182483644314841 91.70974964838106 9.341838685937416Z" fill="#7358D6" fill-rule="evenodd" /></g></svg>