mulmoclaude 0.4.0 → 0.5.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 (228) hide show
  1. package/bin/mulmoclaude.js +1 -1
  2. package/client/assets/{html2canvas-Cx501zZr-Cv5snK9D.js → html2canvas-Cx501zZr-DiKaqnKs.js} +1 -1
  3. package/client/assets/{index-DtcyExH9.js → index-C94GcmNa.js} +182 -196
  4. package/client/assets/index-CY-WpQUm.css +2 -0
  5. package/client/assets/{index.es-D4YyL_Dg-DnizuhIY.js → index.es-D4YyL_Dg-5ipqh8Pe.js} +1 -1
  6. package/client/assets/material-symbols-outlined-NzYEeyps.woff2 +0 -0
  7. package/client/index.html +2 -2
  8. package/package.json +8 -6
  9. package/server/agent/backend/claude-code.ts +170 -0
  10. package/server/agent/backend/index.ts +14 -0
  11. package/server/agent/backend/types.ts +65 -0
  12. package/server/agent/index.ts +22 -156
  13. package/server/agent/mcp-server.ts +88 -10
  14. package/server/agent/mcp-tools/index.ts +2 -1
  15. package/server/agent/mcp-tools/notify.ts +76 -0
  16. package/server/agent/mcp-tools/x.ts +10 -1
  17. package/server/agent/plugin-names.ts +10 -4
  18. package/server/agent/prompt.ts +1 -1
  19. package/server/api/routes/agent.ts +78 -1
  20. package/server/api/routes/chart.ts +13 -0
  21. package/server/api/routes/chat-index.ts +2 -1
  22. package/server/api/routes/config.ts +34 -7
  23. package/server/api/routes/files.ts +62 -12
  24. package/server/api/routes/html.ts +13 -0
  25. package/server/api/routes/image.ts +76 -21
  26. package/server/api/routes/news.ts +146 -0
  27. package/server/api/routes/notifications.ts +58 -2
  28. package/server/api/routes/plugins.ts +62 -90
  29. package/server/api/routes/presentHtml.ts +9 -0
  30. package/server/api/routes/roles.ts +10 -0
  31. package/server/api/routes/scheduler.ts +12 -5
  32. package/server/api/routes/schedulerTasks.ts +55 -20
  33. package/server/api/routes/sessions.ts +13 -2
  34. package/server/api/routes/skills.ts +21 -0
  35. package/server/api/routes/sources.ts +5 -4
  36. package/server/api/routes/todos.ts +30 -0
  37. package/server/api/routes/todosColumnsHandlers.ts +13 -27
  38. package/server/api/routes/wiki/frontmatter.ts +86 -0
  39. package/server/api/routes/wiki.ts +313 -67
  40. package/server/events/notifications.ts +26 -2
  41. package/server/events/relay-client.ts +9 -0
  42. package/server/events/resolveRelayBridgeOptions.ts +125 -0
  43. package/server/index.ts +33 -4
  44. package/server/system/env.ts +10 -0
  45. package/server/system/macosNotify.ts +152 -0
  46. package/server/utils/errors.ts +11 -2
  47. package/server/utils/fetch.ts +54 -0
  48. package/server/utils/files/atomic.ts +7 -6
  49. package/server/utils/files/image-store.ts +14 -19
  50. package/server/utils/files/markdown-image-fill.ts +131 -0
  51. package/server/utils/files/markdown-store.ts +20 -4
  52. package/server/utils/files/naming.ts +20 -10
  53. package/server/utils/files/spreadsheet-store.ts +11 -6
  54. package/server/utils/gemini.ts +29 -3
  55. package/server/utils/id.ts +40 -8
  56. package/server/utils/logPreview.ts +24 -0
  57. package/server/utils/promptMeta.ts +32 -0
  58. package/server/utils/slug.ts +65 -4
  59. package/server/workspace/chat-index/index.ts +1 -1
  60. package/server/workspace/chat-index/summarizer.ts +1 -1
  61. package/server/workspace/helps/gemini.md +57 -0
  62. package/server/workspace/helps/index.md +2 -1
  63. package/server/workspace/helps/sources.md +42 -0
  64. package/server/workspace/helps/wiki.md +40 -5
  65. package/server/workspace/journal/archivist-cli.ts +121 -0
  66. package/server/workspace/journal/{archivist.ts → archivist-schemas.ts} +12 -120
  67. package/server/workspace/journal/dailyPass.ts +78 -38
  68. package/server/workspace/journal/index.ts +52 -1
  69. package/server/workspace/journal/memoryExtractor.ts +1 -1
  70. package/server/workspace/journal/optimizationPass.ts +2 -3
  71. package/server/workspace/journal/paths.ts +8 -24
  72. package/server/workspace/journal/state.ts +12 -2
  73. package/server/workspace/news/reader.ts +248 -0
  74. package/server/workspace/paths.ts +1 -0
  75. package/server/workspace/skills/scheduler.ts +2 -1
  76. package/server/workspace/skills/user-tasks.ts +3 -2
  77. package/server/workspace/sources/classifier.ts +1 -1
  78. package/server/workspace/sources/pipeline/fetch.ts +23 -0
  79. package/server/workspace/sources/pipeline/index.ts +57 -0
  80. package/server/workspace/sources/pipeline/notify.ts +10 -2
  81. package/server/workspace/sources/pipeline/summarize.ts +1 -1
  82. package/src/App.vue +322 -270
  83. package/src/components/CanvasViewToggle.vue +2 -3
  84. package/src/components/ChatInput.vue +82 -109
  85. package/src/components/FileContentHeader.vue +3 -3
  86. package/src/components/FileContentRenderer.vue +17 -4
  87. package/src/components/FileTree.vue +28 -1
  88. package/src/components/FileTreePane.vue +25 -23
  89. package/src/components/FilesView.vue +1 -1
  90. package/src/components/FilterChip.vue +22 -0
  91. package/src/components/LockStatusPopup.vue +4 -1
  92. package/src/components/NewsView.vue +252 -0
  93. package/src/components/NotificationBell.vue +22 -5
  94. package/src/components/PageChatComposer.vue +101 -0
  95. package/src/components/PluginLauncher.vue +18 -7
  96. package/src/components/RoleSelector.vue +3 -2
  97. package/src/components/SessionHeaderControls.vue +63 -0
  98. package/src/components/SessionHistoryExpandButton.vue +30 -0
  99. package/src/components/SessionHistoryPanel.vue +47 -80
  100. package/src/components/SessionHistoryToggleButton.vue +40 -0
  101. package/src/components/SessionRoleIcon.vue +72 -0
  102. package/src/components/SessionSidebar.vue +96 -0
  103. package/src/components/SessionTabBar.vue +42 -47
  104. package/src/components/SettingsMcpTab.vue +318 -22
  105. package/src/components/SettingsModal.vue +185 -56
  106. package/src/components/SettingsReferenceDirsTab.vue +54 -38
  107. package/src/components/SettingsWorkspaceDirsTab.vue +55 -42
  108. package/src/components/SidebarHeader.vue +34 -21
  109. package/src/components/SourcesManager.vue +900 -0
  110. package/src/components/SourcesView.vue +45 -0
  111. package/src/components/StackView.vue +81 -48
  112. package/src/components/SuggestionsPanel.vue +22 -36
  113. package/src/components/SystemFileBanner.vue +106 -0
  114. package/src/components/ThinkingIndicator.vue +41 -0
  115. package/src/components/TodoExplorer.vue +47 -8
  116. package/src/components/todo/TodoKanbanView.vue +6 -1
  117. package/src/components/todo/TodoListView.vue +9 -1
  118. package/src/components/todo/TodoTableView.vue +31 -3
  119. package/src/composables/favicon/conditions.ts +76 -0
  120. package/src/composables/favicon/resolveColor.ts +93 -0
  121. package/src/composables/favicon/types.ts +61 -0
  122. package/src/composables/useAppApi.ts +19 -5
  123. package/src/composables/useChatScroll.ts +5 -5
  124. package/src/composables/useCurrentRole.ts +32 -0
  125. package/src/composables/useDynamicFavicon.ts +58 -77
  126. package/src/composables/useEventListeners.ts +3 -7
  127. package/src/composables/useFaviconState.ts +91 -21
  128. package/src/composables/useFileSelection.ts +2 -1
  129. package/src/composables/useHealth.ts +76 -7
  130. package/src/composables/useLayoutMode.ts +2 -7
  131. package/src/composables/useNewsItems.ts +38 -0
  132. package/src/composables/useNewsReadState.ts +75 -0
  133. package/src/composables/useNotifications.ts +76 -13
  134. package/src/composables/usePendingCalls.ts +11 -1
  135. package/src/composables/useRoles.ts +6 -10
  136. package/src/composables/useRunElapsed.ts +80 -0
  137. package/src/composables/useSessionDerived.ts +21 -5
  138. package/src/composables/useSessionHistory.ts +1 -1
  139. package/src/composables/useSidePanelVisible.ts +25 -0
  140. package/src/composables/useViewLayout.ts +2 -9
  141. package/src/config/apiRoutes.ts +19 -6
  142. package/src/config/historyFilters.ts +30 -0
  143. package/src/config/mcpCatalog.ts +285 -0
  144. package/src/config/mcpTypes.ts +26 -0
  145. package/src/config/roles.ts +19 -51
  146. package/src/config/systemFileDescriptors.ts +170 -0
  147. package/src/config/toolNames.ts +6 -1
  148. package/src/config/workspacePaths.ts +1 -0
  149. package/src/index.css +14 -0
  150. package/src/lang/de.ts +189 -19
  151. package/src/lang/en.ts +187 -19
  152. package/src/lang/es.ts +188 -19
  153. package/src/lang/fr.ts +187 -19
  154. package/src/lang/ja.ts +187 -16
  155. package/src/lang/ko.ts +187 -18
  156. package/src/lang/pt-BR.ts +187 -19
  157. package/src/lang/zh.ts +186 -18
  158. package/src/main.ts +1 -0
  159. package/src/plugins/canvas/View.vue +4 -2
  160. package/src/plugins/canvas/index.ts +3 -2
  161. package/src/plugins/chart/index.ts +3 -2
  162. package/src/plugins/editImage/index.ts +3 -2
  163. package/src/plugins/generateImage/index.ts +3 -2
  164. package/src/plugins/manageRoles/View.vue +8 -3
  165. package/src/plugins/manageRoles/index.ts +3 -2
  166. package/src/plugins/manageSkills/View.vue +13 -12
  167. package/src/plugins/manageSkills/index.ts +3 -2
  168. package/src/plugins/manageSource/View.vue +3 -708
  169. package/src/plugins/manageSource/index.ts +3 -2
  170. package/src/plugins/markdown/View.vue +148 -47
  171. package/src/plugins/markdown/definition.ts +6 -4
  172. package/src/plugins/markdown/index.ts +3 -2
  173. package/src/plugins/presentForm/Preview.vue +99 -0
  174. package/src/plugins/presentForm/View.vue +675 -0
  175. package/src/plugins/presentForm/definition.ts +127 -0
  176. package/src/plugins/presentForm/index.ts +18 -0
  177. package/src/plugins/presentForm/plugin.ts +94 -0
  178. package/src/plugins/presentForm/types.ts +109 -0
  179. package/src/plugins/presentHtml/index.ts +3 -2
  180. package/src/plugins/presentMulmoScript/index.ts +3 -2
  181. package/src/plugins/scheduler/AutomationsPreview.vue +37 -0
  182. package/src/plugins/scheduler/AutomationsView.vue +23 -0
  183. package/src/plugins/scheduler/CalendarView.vue +23 -0
  184. package/src/plugins/scheduler/LegacySchedulerView.vue +32 -0
  185. package/src/plugins/scheduler/TasksTab.vue +66 -4
  186. package/src/plugins/scheduler/View.vue +52 -18
  187. package/src/plugins/scheduler/automationsDefinition.ts +58 -0
  188. package/src/plugins/scheduler/calendarDefinition.ts +46 -0
  189. package/src/plugins/scheduler/index.ts +68 -14
  190. package/src/plugins/scheduler/legacyShape.ts +34 -0
  191. package/src/plugins/spreadsheet/Preview.vue +2 -3
  192. package/src/plugins/spreadsheet/View.vue +23 -46
  193. package/src/plugins/spreadsheet/engine/responseDecoder.ts +2 -1
  194. package/src/plugins/spreadsheet/index.ts +3 -2
  195. package/src/plugins/textResponse/View.vue +17 -40
  196. package/src/plugins/textResponse/utils.ts +25 -0
  197. package/src/plugins/todo/composables/useTodos.ts +3 -1
  198. package/src/plugins/todo/index.ts +3 -2
  199. package/src/plugins/wiki/View.vue +426 -100
  200. package/src/plugins/wiki/index.ts +5 -2
  201. package/src/plugins/wiki/route.ts +14 -5
  202. package/src/router/guards.ts +3 -2
  203. package/src/router/index.ts +34 -22
  204. package/src/router/pageRoutes.ts +23 -0
  205. package/src/tools/index.ts +12 -5
  206. package/src/tools/legacyPluginNames.ts +13 -0
  207. package/src/types/notification.ts +31 -6
  208. package/src/utils/agent/eventDispatch.ts +3 -6
  209. package/src/utils/agent/formatElapsed.ts +37 -0
  210. package/src/utils/agent/request.ts +4 -2
  211. package/src/utils/canvas/sidePanelVisible.ts +19 -0
  212. package/src/utils/dom/scrollIntoViewByTestId.ts +38 -0
  213. package/src/utils/errors.ts +9 -2
  214. package/src/utils/files/filename.ts +24 -0
  215. package/src/utils/filesPreview/schedulerPreview.ts +9 -3
  216. package/src/utils/id.ts +18 -0
  217. package/src/utils/markdown/taskList.ts +175 -0
  218. package/src/utils/mcp/interpolateSpec.ts +97 -0
  219. package/src/utils/notification/dispatch.ts +51 -15
  220. package/src/utils/path/workspaceLinkRouter.ts +18 -0
  221. package/src/utils/session/mergeSessions.ts +5 -0
  222. package/src/utils/sources/filter.ts +69 -0
  223. package/client/assets/index-CubzmCVK.css +0 -2
  224. package/server/workspace/journal/linkRewrite.ts +0 -4
  225. package/src/components/ToolResultsPanel.vue +0 -91
  226. package/src/plugins/scheduler/definition.ts +0 -57
  227. package/src/utils/role/plugins.ts +0 -12
  228. package/src/utils/session/seedRoleDefault.ts +0 -35
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Form Tool Definition (Schema)
3
+ */
4
+
5
+ import type { ToolDefinition } from "gui-chat-protocol";
6
+
7
+ export const TOOL_NAME = "presentForm";
8
+
9
+ export const TOOL_DEFINITION: ToolDefinition = {
10
+ type: "function",
11
+ name: TOOL_NAME,
12
+ description:
13
+ "Create a structured form to collect information from the user. Supports various field types including text input, textarea, multiple choice (radio), dropdown menus, checkboxes, date/time pickers, and number inputs. Each field can have validation rules and help text.",
14
+ parameters: {
15
+ type: "object",
16
+ properties: {
17
+ title: {
18
+ type: "string",
19
+ description: "Optional title for the form (e.g., 'User Registration')",
20
+ },
21
+ description: {
22
+ type: "string",
23
+ description: "Optional description explaining the purpose of the form",
24
+ },
25
+ fields: {
26
+ type: "array",
27
+ description: "Array of form fields with various types and configurations",
28
+ items: {
29
+ type: "object",
30
+ properties: {
31
+ id: {
32
+ type: "string",
33
+ description:
34
+ "Unique identifier for the field (e.g., 'email', 'birthdate'). This will be the key in the JSON response. Use descriptive camelCase or snake_case names.",
35
+ },
36
+ type: {
37
+ type: "string",
38
+ enum: ["text", "textarea", "radio", "dropdown", "checkbox", "date", "time", "number"],
39
+ description:
40
+ "Field type: 'text' for short text, 'textarea' for long text, 'radio' for 2-6 choices, 'dropdown' for many choices, 'checkbox' for multiple selections, 'date' for date picker, 'time' for time picker, 'number' for numeric input",
41
+ },
42
+ label: {
43
+ type: "string",
44
+ description: "Field label shown to the user",
45
+ },
46
+ description: {
47
+ type: "string",
48
+ description: "Optional help text explaining the field",
49
+ },
50
+ required: {
51
+ type: "boolean",
52
+ description: "Whether the field is required (default: false)",
53
+ },
54
+ placeholder: {
55
+ type: "string",
56
+ description: "Placeholder text for text/textarea fields",
57
+ },
58
+ validation: {
59
+ type: "string",
60
+ description: "For text fields: 'email', 'url', 'phone', or a regex pattern",
61
+ },
62
+ minLength: {
63
+ type: "number",
64
+ description: "Minimum character length for textarea fields",
65
+ },
66
+ maxLength: {
67
+ type: "number",
68
+ description: "Maximum character length for textarea fields",
69
+ },
70
+ rows: {
71
+ type: "number",
72
+ description: "Number of visible rows for textarea (default: 4)",
73
+ },
74
+ choices: {
75
+ type: "array",
76
+ items: { type: "string" },
77
+ description: "Array of choices for radio/dropdown/checkbox fields. Radio should have 2-6 choices, dropdown for 7+ choices.",
78
+ },
79
+ searchable: {
80
+ type: "boolean",
81
+ description: "Make dropdown searchable (for large lists)",
82
+ },
83
+ minSelections: {
84
+ type: "number",
85
+ description: "Minimum number of selections for checkbox fields",
86
+ },
87
+ maxSelections: {
88
+ type: "number",
89
+ description: "Maximum number of selections for checkbox fields",
90
+ },
91
+ minDate: {
92
+ type: "string",
93
+ description: "Minimum date (ISO format: YYYY-MM-DD)",
94
+ },
95
+ maxDate: {
96
+ type: "string",
97
+ description: "Maximum date (ISO format: YYYY-MM-DD)",
98
+ },
99
+ format: {
100
+ type: "string",
101
+ description: "Format for time fields: '12hr' or '24hr'",
102
+ },
103
+ min: {
104
+ type: "number",
105
+ description: "Minimum value for number fields",
106
+ },
107
+ max: {
108
+ type: "number",
109
+ description: "Maximum value for number fields",
110
+ },
111
+ step: {
112
+ type: "number",
113
+ description: "Step increment for number fields",
114
+ },
115
+ defaultValue: {
116
+ description:
117
+ "Optional default/pre-filled value for the field. Type varies by field: string for text/textarea/radio/dropdown/date/time, number for number fields, array of strings for checkbox fields. For radio/dropdown, must be one of the choices. For checkbox, must be a subset of choices.",
118
+ },
119
+ },
120
+ required: ["id", "type", "label"],
121
+ },
122
+ minItems: 1,
123
+ },
124
+ },
125
+ required: ["fields"],
126
+ },
127
+ };
@@ -0,0 +1,18 @@
1
+ import type { ToolPlugin } from "gui-chat-protocol/vue";
2
+ import type { FormData, FormArgs } from "./types";
3
+ import { TOOL_DEFINITION } from "./definition";
4
+ import { executeForm } from "./plugin";
5
+ import View from "./View.vue";
6
+ import Preview from "./Preview.vue";
7
+
8
+ const presentFormPlugin: ToolPlugin<never, FormData, FormArgs> = {
9
+ toolDefinition: TOOL_DEFINITION,
10
+ execute: executeForm,
11
+ generatingMessage: "Preparing form...",
12
+ isEnabled: () => true,
13
+ viewComponent: View,
14
+ previewComponent: Preview,
15
+ };
16
+
17
+ export default presentFormPlugin;
18
+ export { TOOL_NAME } from "./definition";
@@ -0,0 +1,94 @@
1
+ // Local fork of @mulmochat-plugin/form (originally
2
+ // ~/ss/llm/GUIChatPlugins/MulmoChatPluginForm). Forked so the form
3
+ // submission payload (see View.vue:handleSubmit) renders as readable
4
+ // markdown bullets in the chat history instead of a JSON-wrapped
5
+ // object.
6
+
7
+ import type { ToolContext, ToolResult } from "gui-chat-protocol";
8
+ import type { FormData, FormArgs, FormField } from "./types";
9
+
10
+ export { TOOL_NAME, TOOL_DEFINITION } from "./definition";
11
+
12
+ function validateChoiceField(field: FormField): void {
13
+ if (field.type === "radio") {
14
+ if (!Array.isArray(field.choices) || field.choices.length < 2) {
15
+ throw new Error(`Field '${field.id}': radio fields must have at least 2 choices`);
16
+ }
17
+ return;
18
+ }
19
+ if (field.type === "dropdown" || field.type === "checkbox") {
20
+ if (!Array.isArray(field.choices) || field.choices.length < 1) {
21
+ throw new Error(`Field '${field.id}': ${field.type} fields must have at least 1 choice`);
22
+ }
23
+ }
24
+ }
25
+
26
+ function validateCheckboxRange(field: FormField & { type: "checkbox" }): void {
27
+ const { minSelections, maxSelections, choices, id } = field;
28
+ if (minSelections !== undefined && maxSelections !== undefined && minSelections > maxSelections) {
29
+ throw new Error(`Field '${id}': minSelections cannot be greater than maxSelections`);
30
+ }
31
+ if (maxSelections !== undefined && maxSelections > choices.length) {
32
+ throw new Error(`Field '${id}': maxSelections cannot exceed number of choices`);
33
+ }
34
+ // Without this lower-bound check, a form with minSelections > choices.length
35
+ // would render but be impossible to submit — the user can never satisfy the
36
+ // minimum because there aren't enough options to pick.
37
+ if (minSelections !== undefined && minSelections > choices.length) {
38
+ throw new Error(`Field '${id}': minSelections cannot exceed number of choices`);
39
+ }
40
+ }
41
+
42
+ function validateRangeField(field: FormField): void {
43
+ if (
44
+ (field.type === "text" || field.type === "textarea") &&
45
+ field.minLength !== undefined &&
46
+ field.maxLength !== undefined &&
47
+ field.minLength > field.maxLength
48
+ ) {
49
+ throw new Error(`Field '${field.id}': minLength cannot be greater than maxLength`);
50
+ }
51
+ if (field.type === "number" && field.min !== undefined && field.max !== undefined && field.min > field.max) {
52
+ throw new Error(`Field '${field.id}': min cannot be greater than max`);
53
+ }
54
+ if (field.type === "date" && field.minDate && field.maxDate && field.minDate > field.maxDate) {
55
+ throw new Error(`Field '${field.id}': minDate cannot be after maxDate`);
56
+ }
57
+ if (field.type === "checkbox") validateCheckboxRange(field);
58
+ }
59
+
60
+ function validateField(field: FormField, index: number, seenIds: Set<string>): void {
61
+ if (!field.id || typeof field.id !== "string") throw new Error(`Field ${index + 1} must have a valid 'id' property`);
62
+ if (!field.type || typeof field.type !== "string") throw new Error(`Field ${index + 1} must have a valid 'type' property`);
63
+ if (!field.label || typeof field.label !== "string") throw new Error(`Field ${index + 1} must have a valid 'label' property`);
64
+ if (seenIds.has(field.id)) throw new Error(`Duplicate field ID: '${field.id}'`);
65
+ seenIds.add(field.id);
66
+ validateChoiceField(field);
67
+ validateRangeField(field);
68
+ }
69
+
70
+ export const executeForm = async (_context: ToolContext, args: FormArgs): Promise<ToolResult<never, FormData>> => {
71
+ try {
72
+ const { title, description, fields } = args;
73
+ if (!fields || !Array.isArray(fields) || fields.length === 0) {
74
+ throw new Error("At least one field is required");
75
+ }
76
+ const seen = new Set<string>();
77
+ fields.forEach((field, i) => validateField(field, i, seen));
78
+
79
+ const formData: FormData = { title, description, fields };
80
+ const fieldCount = `${fields.length} field${fields.length > 1 ? "s" : ""}`;
81
+ const titleSuffix = title ? `: ${title}` : "";
82
+ return {
83
+ message: `Form created with ${fieldCount}${titleSuffix}`,
84
+ jsonData: formData,
85
+ instructions:
86
+ "The form has been presented to the user. Wait for the user to fill out and submit it. They will reply with a markdown bullet list of `- {label}: {value}` lines.",
87
+ };
88
+ } catch (error) {
89
+ return {
90
+ message: `Form error: ${error instanceof Error ? error.message : "Unknown error"}`,
91
+ instructions: "Acknowledge that there was an error creating the form and suggest trying again with corrected field definitions.",
92
+ };
93
+ }
94
+ };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Form Plugin Types
3
+ *
4
+ * Form-specific type definitions only.
5
+ * Common types should be imported directly from gui-chat-protocol.
6
+ */
7
+
8
+ // ============================================================================
9
+ // Form-specific Types
10
+ // ============================================================================
11
+
12
+ /** Field type discriminator */
13
+ export type FieldType = "text" | "textarea" | "radio" | "dropdown" | "checkbox" | "date" | "time" | "number";
14
+
15
+ /** Base field interface */
16
+ export interface BaseField {
17
+ id: string;
18
+ type: FieldType;
19
+ label: string;
20
+ description?: string;
21
+ required?: boolean;
22
+ maxLength?: number;
23
+ }
24
+
25
+ /** Text field */
26
+ export interface TextField extends BaseField {
27
+ type: "text";
28
+ placeholder?: string;
29
+ validation?: "email" | "url" | "phone" | string;
30
+ defaultValue?: string;
31
+ minLength?: number;
32
+ maxLength?: number;
33
+ }
34
+
35
+ /** Textarea field */
36
+ export interface TextareaField extends BaseField {
37
+ type: "textarea";
38
+ placeholder?: string;
39
+ minLength?: number;
40
+ maxLength?: number;
41
+ rows?: number;
42
+ defaultValue?: string;
43
+ }
44
+
45
+ /** Radio field */
46
+ export interface RadioField extends BaseField {
47
+ type: "radio";
48
+ choices: string[];
49
+ defaultValue?: string;
50
+ }
51
+
52
+ /** Dropdown field */
53
+ export interface DropdownField extends BaseField {
54
+ type: "dropdown";
55
+ choices: string[];
56
+ searchable?: boolean;
57
+ defaultValue?: string;
58
+ }
59
+
60
+ /** Checkbox field */
61
+ export interface CheckboxField extends BaseField {
62
+ type: "checkbox";
63
+ choices: string[];
64
+ minSelections?: number;
65
+ maxSelections?: number;
66
+ defaultValue?: string[];
67
+ }
68
+
69
+ /** Date field */
70
+ export interface DateField extends BaseField {
71
+ type: "date";
72
+ minDate?: string;
73
+ maxDate?: string;
74
+ format?: string;
75
+ defaultValue?: string;
76
+ }
77
+
78
+ /** Time field */
79
+ export interface TimeField extends BaseField {
80
+ type: "time";
81
+ format?: "12hr" | "24hr";
82
+ defaultValue?: string;
83
+ }
84
+
85
+ /** Number field */
86
+ export interface NumberField extends BaseField {
87
+ type: "number";
88
+ min?: number;
89
+ max?: number;
90
+ step?: number;
91
+ defaultValue?: number;
92
+ }
93
+
94
+ /** Union type for all fields */
95
+ export type FormField = TextField | TextareaField | RadioField | DropdownField | CheckboxField | DateField | TimeField | NumberField;
96
+
97
+ /** Form data stored in result.jsonData */
98
+ export interface FormData {
99
+ title?: string;
100
+ description?: string;
101
+ fields: FormField[];
102
+ }
103
+
104
+ /** Arguments passed to the form tool */
105
+ export interface FormArgs {
106
+ title?: string;
107
+ description?: string;
108
+ fields: FormField[];
109
+ }
@@ -5,6 +5,7 @@ import View from "./View.vue";
5
5
  import Preview from "./Preview.vue";
6
6
  import { apiPost } from "../../utils/api";
7
7
  import { API_ROUTES } from "../../config/apiRoutes";
8
+ import { makeUuid } from "../../utils/id";
8
9
 
9
10
  export interface PresentHtmlData {
10
11
  html: string;
@@ -20,14 +21,14 @@ const presentHtmlPlugin: ToolPlugin<PresentHtmlData> = {
20
21
  if (!result.ok) {
21
22
  return {
22
23
  toolName: TOOL_NAME,
23
- uuid: crypto.randomUUID(),
24
+ uuid: makeUuid(),
24
25
  message: result.error,
25
26
  };
26
27
  }
27
28
  return {
28
29
  ...result.data,
29
30
  toolName: TOOL_NAME,
30
- uuid: crypto.randomUUID(),
31
+ uuid: makeUuid(),
31
32
  };
32
33
  },
33
34
 
@@ -6,6 +6,7 @@ import View from "./View.vue";
6
6
  import Preview from "./Preview.vue";
7
7
  import { apiPost } from "../../utils/api";
8
8
  import { API_ROUTES } from "../../config/apiRoutes";
9
+ import { makeUuid } from "../../utils/id";
9
10
 
10
11
  export interface MulmoScriptData {
11
12
  script: MulmoScript;
@@ -20,14 +21,14 @@ const presentMulmoScriptPlugin: ToolPlugin<MulmoScriptData> = {
20
21
  if (!result.ok) {
21
22
  return {
22
23
  toolName: TOOL_NAME,
23
- uuid: crypto.randomUUID(),
24
+ uuid: makeUuid(),
24
25
  message: result.error,
25
26
  };
26
27
  }
27
28
  return {
28
29
  ...result.data,
29
30
  toolName: TOOL_NAME,
30
- uuid: crypto.randomUUID(),
31
+ uuid: makeUuid(),
31
32
  };
32
33
  },
33
34
 
@@ -0,0 +1,37 @@
1
+ <template>
2
+ <div class="p-2 text-sm">
3
+ <div class="flex items-center gap-1 font-medium text-gray-700 mb-1">
4
+ <span aria-hidden="true">{{ t("pluginScheduler.previewIcon") }}</span>
5
+ <span>{{ t("pluginScheduler.previewAutomations", { count: items.length }) }}</span>
6
+ </div>
7
+ <div v-for="item in preview" :key="item.id" class="text-xs truncate text-gray-600">
8
+ {{ item.title }}
9
+ </div>
10
+ <div v-if="more > 0" class="text-xs text-gray-400">{{ t("pluginScheduler.previewMore", { count: more }) }}</div>
11
+ </div>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ import { computed } from "vue";
16
+ import { useI18n } from "vue-i18n";
17
+ import type { ToolResultComplete } from "gui-chat-protocol/vue";
18
+ import type { SchedulerData } from "./index";
19
+
20
+ // Sidebar preview for `manageAutomations` tool results. Deliberately
21
+ // does NOT auto-refresh from `/api/scheduler` (which returns calendar
22
+ // items, not tasks — see #828 follow-up). The post-action snapshot
23
+ // in `props.result.data.items` is the right thing to show; tasks
24
+ // have their own GET endpoint (`/api/scheduler/tasks`) but the
25
+ // preview is a frozen time-slice of one tool call, so re-fetching
26
+ // would either drift to wrong data (calendar) or duplicate state
27
+ // the AutomationsView already owns.
28
+
29
+ const { t } = useI18n();
30
+
31
+ const props = defineProps<{ result: ToolResultComplete<SchedulerData> }>();
32
+
33
+ const items = computed(() => props.result.data?.items ?? []);
34
+ const PREVIEW_LIMIT = 3;
35
+ const preview = computed(() => items.value.slice(0, PREVIEW_LIMIT));
36
+ const more = computed(() => Math.max(0, items.value.length - PREVIEW_LIMIT));
37
+ </script>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <!-- Automations mount point used in two places (#758, #824):
3
+ 1. Standalone /automations page — no `selectedResult`, the
4
+ underlying TasksTab fetches via /api/scheduler/tasks.
5
+ 2. `manageAutomations` chat tool result — `selectedResult`
6
+ is forwarded so View.vue can pick up task-shaped data.
7
+ Both modes lock the tab to "tasks" so the legacy tab bar
8
+ inside SchedulerView stays hidden. -->
9
+ <SchedulerView :force-tab="SCHEDULER_TAB.tasks" :selected-result="selectedResult" @update-result="(result) => emit('updateResult', result)" />
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import type { ToolResultComplete } from "gui-chat-protocol/vue";
14
+ import SchedulerView from "./View.vue";
15
+ import { SCHEDULER_TAB } from "./viewModes";
16
+ import type { SchedulerData } from "./index";
17
+
18
+ defineProps<{
19
+ selectedResult?: ToolResultComplete<SchedulerData>;
20
+ }>();
21
+
22
+ const emit = defineEmits<{ updateResult: [result: ToolResultComplete] }>();
23
+ </script>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <!-- Calendar mount point used in two places (#758, #824):
3
+ 1. Standalone /calendar page — no `selectedResult`, View.vue
4
+ fetches items itself.
5
+ 2. `manageCalendar` chat tool result — `selectedResult` is
6
+ forwarded so View.vue seeds items from the tool payload.
7
+ Both modes lock the tab to "calendar" so the legacy tab bar
8
+ inside SchedulerView stays hidden. -->
9
+ <SchedulerView :force-tab="SCHEDULER_TAB.calendar" :selected-result="selectedResult" @update-result="(result) => emit('updateResult', result)" />
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import type { ToolResultComplete } from "gui-chat-protocol/vue";
14
+ import SchedulerView from "./View.vue";
15
+ import { SCHEDULER_TAB } from "./viewModes";
16
+ import type { SchedulerData } from "./index";
17
+
18
+ defineProps<{
19
+ selectedResult?: ToolResultComplete<SchedulerData>;
20
+ }>();
21
+
22
+ const emit = defineEmits<{ updateResult: [result: ToolResultComplete] }>();
23
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <!-- View-only fallback for chat sessions saved before the
3
+ manageScheduler split (#824). The legacy tool returned two
4
+ distinct payload shapes — calendar items vs task records —
5
+ so we route on shape and mount the matching post-split view.
6
+ Never produces fresh tool calls (the plugin is not exposed
7
+ to the LLM); only renders persisted history. -->
8
+ <AutomationsView v-if="renderAutomations" :selected-result="selectedResult" @update-result="(result) => emit('updateResult', result)" />
9
+ <CalendarView v-else :selected-result="selectedResult" @update-result="(result) => emit('updateResult', result)" />
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { computed } from "vue";
14
+ import type { ToolResultComplete } from "gui-chat-protocol/vue";
15
+ import AutomationsView from "./AutomationsView.vue";
16
+ import CalendarView from "./CalendarView.vue";
17
+ import { isLegacyAutomationsShape } from "./legacyShape";
18
+ import type { SchedulerData } from "./index";
19
+
20
+ const props = defineProps<{
21
+ selectedResult?: ToolResultComplete<SchedulerData>;
22
+ }>();
23
+
24
+ const emit = defineEmits<{ updateResult: [result: ToolResultComplete] }>();
25
+
26
+ // Shape-based dispatch. Errors on the side of CalendarView when
27
+ // the payload shape is unknown — calendar was the more common
28
+ // pre-split action and the view degrades gracefully on missing
29
+ // fields, while the automations view assumes the task shape and
30
+ // would render an empty Tasks tab.
31
+ const renderAutomations = computed(() => isLegacyAutomationsShape(props.selectedResult?.data));
32
+ </script>
@@ -104,10 +104,35 @@
104
104
  <span v-if="task.state?.nextScheduledAt">{{ t("pluginSchedulerTasks.nextRun", { time: formatShortTime(task.state.nextScheduledAt) }) }}</span>
105
105
  </div>
106
106
 
107
- <!-- Description -->
108
- <div v-if="task.description" class="mt-1 text-xs text-gray-400 truncate">
107
+ <!-- Description (full, not truncated — users need to know what
108
+ each task actually does). System tasks have only this
109
+ line; user / skill tasks have an expandable details
110
+ block below with the prompt + role. -->
111
+ <div v-if="task.description" class="mt-1 text-xs text-gray-500 whitespace-pre-line">
109
112
  {{ task.description }}
110
113
  </div>
114
+
115
+ <!-- Prompt + role (user / skill tasks only). Collapsed by
116
+ default to keep the list compact; click to inspect. -->
117
+ <details v-if="task.prompt" class="mt-2" :data-testid="`scheduler-task-details-${task.id}`">
118
+ <summary class="text-xs text-gray-500 cursor-pointer select-none hover:text-gray-700">
119
+ {{ t("pluginSchedulerTasks.detailsToggle") }}
120
+ </summary>
121
+ <div class="mt-1.5 space-y-1.5 ml-4">
122
+ <div v-if="task.roleId" class="text-xs text-gray-600">
123
+ <span class="font-medium">{{ t("pluginSchedulerTasks.roleLabel") }}:</span>
124
+ <code class="ml-1 px-1 py-0.5 rounded bg-gray-100 text-gray-700">{{ task.roleId }}</code>
125
+ </div>
126
+ <div class="text-xs text-gray-600">
127
+ <div class="font-medium mb-0.5">{{ t("pluginSchedulerTasks.promptLabel") }}:</div>
128
+ <pre
129
+ class="px-2 py-1.5 rounded bg-gray-50 border border-gray-200 text-gray-700 whitespace-pre-wrap break-words font-mono text-[11px] leading-relaxed"
130
+ :data-testid="`scheduler-task-prompt-${task.id}`"
131
+ >{{ task.prompt }}</pre
132
+ >
133
+ </div>
134
+ </div>
135
+ </details>
111
136
  </div>
112
137
  </div>
113
138
  </div>
@@ -115,12 +140,14 @@
115
140
  </template>
116
141
 
117
142
  <script setup lang="ts">
118
- import { ref, onMounted } from "vue";
143
+ import { ref, onMounted, nextTick, watch } from "vue";
119
144
  import { useI18n } from "vue-i18n";
145
+ import { useRoute } from "vue-router";
120
146
  import { apiGet, apiPost, apiPut, apiDelete } from "../../utils/api";
121
147
  import { API_ROUTES } from "../../config/apiRoutes";
122
148
  import { formatShortTime } from "../../utils/format/date";
123
149
  import { formatSchedule as formatTaskSchedule, type TaskSchedule as FormatterTaskSchedule } from "./formatSchedule";
150
+ import { scrollIntoViewByTestId } from "../../utils/dom/scrollIntoViewByTestId";
124
151
 
125
152
  const { t } = useI18n();
126
153
 
@@ -144,6 +171,12 @@ interface SchedulerTask {
144
171
  origin: string;
145
172
  enabled?: boolean;
146
173
  state?: TaskState;
174
+ // user / skill tasks carry the agent prompt + role on the wire;
175
+ // system tasks omit them (their "what does this do" is the
176
+ // description text plus the source code behind the id).
177
+ prompt?: string;
178
+ roleId?: string;
179
+ missedRunPolicy?: string;
147
180
  }
148
181
 
149
182
  // Hints showing common task cadences. Stored as structured schedules
@@ -230,5 +263,34 @@ async function deleteTask(taskId: string): Promise<void> {
230
263
  await fetchTasks();
231
264
  }
232
265
 
233
- onMounted(fetchTasks);
266
+ // When the user lands on /automations/:taskId (e.g. from a
267
+ // notification), fetch the list, then scroll + flash the matching
268
+ // row once it's rendered. Unknown IDs are a no-op — the list still
269
+ // renders normally.
270
+ const route = useRoute();
271
+
272
+ async function focusUrlTask(taskId: string): Promise<void> {
273
+ await nextTick();
274
+ scrollIntoViewByTestId(`scheduler-task-${taskId}`);
275
+ }
276
+
277
+ onMounted(async () => {
278
+ await fetchTasks();
279
+ const urlTaskId = route.params.taskId;
280
+ if (typeof urlTaskId === "string" && urlTaskId) {
281
+ await focusUrlTask(urlTaskId);
282
+ }
283
+ });
284
+
285
+ // Also react when the user is already on the page and the URL
286
+ // changes (e.g. clicking a second notification without leaving the
287
+ // Automations view).
288
+ watch(
289
+ () => route.params.taskId,
290
+ (taskId) => {
291
+ if (typeof taskId === "string" && taskId) {
292
+ void focusUrlTask(taskId);
293
+ }
294
+ },
295
+ );
234
296
  </script>