@plainconceptsplatform/loop-task 2.6.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 (202) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +534 -0
  3. package/dist/app/App.js +132 -0
  4. package/dist/app/BoardLayout.js +5 -0
  5. package/dist/app/index.js +48 -0
  6. package/dist/app/providers/index.js +1 -0
  7. package/dist/app/router/index.js +16 -0
  8. package/dist/app/types.js +1 -0
  9. package/dist/cli/import-validator.js +152 -0
  10. package/dist/cli/import-writer.js +81 -0
  11. package/dist/cli.js +573 -0
  12. package/dist/client/cli-format.js +7 -0
  13. package/dist/client/commands.js +162 -0
  14. package/dist/client/ipc.js +96 -0
  15. package/dist/client/project-commands.js +164 -0
  16. package/dist/core/command/command-runner.js +300 -0
  17. package/dist/core/command/process-tree.js +73 -0
  18. package/dist/core/command/resolve-cwd.js +8 -0
  19. package/dist/core/command/stdout-capture-transform.js +73 -0
  20. package/dist/core/context/context-parser.js +65 -0
  21. package/dist/core/context/log-context.js +3 -0
  22. package/dist/core/context/template.js +20 -0
  23. package/dist/core/context/validate-context.js +19 -0
  24. package/dist/core/foreground/index.js +81 -0
  25. package/dist/core/logging/bounded-log-reader.js +368 -0
  26. package/dist/core/logging/log-follower.js +26 -0
  27. package/dist/core/logging/log-parser.js +29 -0
  28. package/dist/core/logging/log-rotator.js +34 -0
  29. package/dist/core/logging/rotating-log-stream.js +137 -0
  30. package/dist/core/loop/chain-executor.js +171 -0
  31. package/dist/core/loop/delay-utils.js +65 -0
  32. package/dist/core/loop/loop-controller.js +240 -0
  33. package/dist/core/loop/loop-runner.js +194 -0
  34. package/dist/core/loop/run-executor.js +144 -0
  35. package/dist/core/loop/types.js +1 -0
  36. package/dist/core/scheduling/index.js +16 -0
  37. package/dist/daemon/daemon-log.js +11 -0
  38. package/dist/daemon/diagnostics.js +85 -0
  39. package/dist/daemon/http/helpers.js +81 -0
  40. package/dist/daemon/http/openapi.js +119 -0
  41. package/dist/daemon/http/route-loops.js +311 -0
  42. package/dist/daemon/http/route-misc.js +60 -0
  43. package/dist/daemon/http/route-projects.js +55 -0
  44. package/dist/daemon/http/route-tasks.js +141 -0
  45. package/dist/daemon/http/routes.js +19 -0
  46. package/dist/daemon/http/server.js +82 -0
  47. package/dist/daemon/http/sse.js +36 -0
  48. package/dist/daemon/index.js +189 -0
  49. package/dist/daemon/ipc/send.js +5 -0
  50. package/dist/daemon/managers/loop-entry.js +27 -0
  51. package/dist/daemon/managers/loop-manager.js +346 -0
  52. package/dist/daemon/managers/loop-options.js +17 -0
  53. package/dist/daemon/managers/loop-serialization.js +53 -0
  54. package/dist/daemon/managers/project-manager.js +170 -0
  55. package/dist/daemon/managers/task-manager.js +94 -0
  56. package/dist/daemon/mcp/index.js +2 -0
  57. package/dist/daemon/mcp/openapi-sync.js +50 -0
  58. package/dist/daemon/mcp/server.js +167 -0
  59. package/dist/daemon/mcp/tools.js +382 -0
  60. package/dist/daemon/recipe/deferred-reload.js +32 -0
  61. package/dist/daemon/recipe/diagram-reader.js +26 -0
  62. package/dist/daemon/recipe/file-writer.js +52 -0
  63. package/dist/daemon/recipe/id-remapper.js +46 -0
  64. package/dist/daemon/recipe/runtime-state.js +23 -0
  65. package/dist/daemon/recipe/scanner.js +165 -0
  66. package/dist/daemon/recipe/task-store.js +33 -0
  67. package/dist/daemon/recipe/validator.js +35 -0
  68. package/dist/daemon/server/handlers/diagnostics-handlers.js +7 -0
  69. package/dist/daemon/server/handlers/index.js +101 -0
  70. package/dist/daemon/server/handlers/log-handlers.js +129 -0
  71. package/dist/daemon/server/handlers/loop-handlers.js +64 -0
  72. package/dist/daemon/server/handlers/project-handlers.js +42 -0
  73. package/dist/daemon/server/handlers/settings-handlers.js +8 -0
  74. package/dist/daemon/server/handlers/task-handlers.js +20 -0
  75. package/dist/daemon/server/handlers/telemetry-handlers.js +5 -0
  76. package/dist/daemon/server/index.js +117 -0
  77. package/dist/daemon/settings-manager.js +73 -0
  78. package/dist/daemon/spawner/index.js +204 -0
  79. package/dist/daemon/state/index.js +209 -0
  80. package/dist/daemon/telemetry/agent-integrations/agent-integration.js +1 -0
  81. package/dist/daemon/telemetry/agent-integrations/claude-code-integration.js +55 -0
  82. package/dist/daemon/telemetry/agent-integrations/detect-agent-integration.js +20 -0
  83. package/dist/daemon/telemetry/agent-integrations/index.js +3 -0
  84. package/dist/daemon/telemetry/agent-integrations/opencode-integration.js +72 -0
  85. package/dist/daemon/telemetry/index.js +6 -0
  86. package/dist/daemon/telemetry/noop-telemetry-adapter.js +53 -0
  87. package/dist/daemon/telemetry/open-telemetry-adapter.js +464 -0
  88. package/dist/daemon/telemetry/telemetry-manager.js +147 -0
  89. package/dist/daemon/telemetry/telemetry-redaction.js +67 -0
  90. package/dist/daemon/telemetry/telemetry-types.js +45 -0
  91. package/dist/daemon/telemetry/telemetry.js +1 -0
  92. package/dist/daemon/watcher/index.js +250 -0
  93. package/dist/duration.js +24 -0
  94. package/dist/entities/loops/filters.js +109 -0
  95. package/dist/entities/loops/index.js +1 -0
  96. package/dist/entities/projects/filters.js +69 -0
  97. package/dist/entities/projects/index.js +1 -0
  98. package/dist/entities/tasks/filters.js +41 -0
  99. package/dist/entities/tasks/index.js +1 -0
  100. package/dist/entry.js +16 -0
  101. package/dist/esm-loader.js +39 -0
  102. package/dist/features/chain-editor/ChainEditor.js +116 -0
  103. package/dist/features/chain-editor/mermaidToAscii.js +375 -0
  104. package/dist/features/chain-editor/renderChainDiagram.js +106 -0
  105. package/dist/features/code-editor/CodeEditorModal.js +84 -0
  106. package/dist/features/code-editor/CodeEditorPreview.js +14 -0
  107. package/dist/features/code-editor/useEditorKeyboard.js +150 -0
  108. package/dist/features/code-editor/useModalDimensions.js +9 -0
  109. package/dist/features/commands/commands.js +132 -0
  110. package/dist/features/commands/useCommandHandlers.js +280 -0
  111. package/dist/features/commands/useContextualActions.js +78 -0
  112. package/dist/features/commands/useGlobalShortcuts.js +154 -0
  113. package/dist/features/forms/FormRouter.js +46 -0
  114. package/dist/features/overlays/ContextHelpModal.js +8 -0
  115. package/dist/features/overlays/DiagramModal.js +45 -0
  116. package/dist/features/overlays/ExportModal.js +51 -0
  117. package/dist/features/overlays/HelpGuideModal.js +23 -0
  118. package/dist/features/overlays/HelpModal.js +59 -0
  119. package/dist/features/overlays/OverlayStack.js +10 -0
  120. package/dist/features/overlays/ProjectsModal.js +42 -0
  121. package/dist/features/overlays/TaskPickerModal.js +62 -0
  122. package/dist/features/overlays/WelcomeScreen.js +74 -0
  123. package/dist/features/overlays/useOverlayStack.js +55 -0
  124. package/dist/features/state/useAppState.js +151 -0
  125. package/dist/logger.js +25 -0
  126. package/dist/loop-config.js +152 -0
  127. package/dist/shared/clipboard.js +111 -0
  128. package/dist/shared/config/constants.js +104 -0
  129. package/dist/shared/config/paths.js +67 -0
  130. package/dist/shared/container/index.js +24 -0
  131. package/dist/shared/fs-utils.js +44 -0
  132. package/dist/shared/hooks/useBreakpoint.js +11 -0
  133. package/dist/shared/hooks/useDaemonSettings.js +41 -0
  134. package/dist/shared/hooks/useInject.js +6 -0
  135. package/dist/shared/hooks/useLogStream.js +37 -0
  136. package/dist/shared/hooks/useLoopFormValidation.js +131 -0
  137. package/dist/shared/hooks/useLoopPolling.js +30 -0
  138. package/dist/shared/hooks/useMouseScroll.js +56 -0
  139. package/dist/shared/hooks/useUndoRedo.js +77 -0
  140. package/dist/shared/i18n/en.json +709 -0
  141. package/dist/shared/i18n/index.js +11 -0
  142. package/dist/shared/providers/InversifyProvider.js +6 -0
  143. package/dist/shared/services/export-service.js +43 -0
  144. package/dist/shared/services/log-service.js +28 -0
  145. package/dist/shared/services/loop-service.js +78 -0
  146. package/dist/shared/services/project-service.js +39 -0
  147. package/dist/shared/services/settings-service.js +102 -0
  148. package/dist/shared/services/task-service.js +41 -0
  149. package/dist/shared/services/types.js +8 -0
  150. package/dist/shared/sleep.js +17 -0
  151. package/dist/shared/tail.js +4 -0
  152. package/dist/shared/ui/Button.js +18 -0
  153. package/dist/shared/ui/DebugPanel.js +9 -0
  154. package/dist/shared/ui/FocusableButton.js +18 -0
  155. package/dist/shared/ui/FocusableInput.js +72 -0
  156. package/dist/shared/ui/FocusableList.js +48 -0
  157. package/dist/shared/ui/Modal.js +13 -0
  158. package/dist/shared/ui/SelectModal.js +78 -0
  159. package/dist/shared/ui/Toast.js +37 -0
  160. package/dist/shared/ui/format.js +117 -0
  161. package/dist/shared/ui/hooks/useHoverState.js +11 -0
  162. package/dist/shared/ui/state.js +9 -0
  163. package/dist/shared/ui/theme.js +96 -0
  164. package/dist/shared/utils/log-lines.js +13 -0
  165. package/dist/shared/utils/paste.js +37 -0
  166. package/dist/shared/utils/syntax.js +128 -0
  167. package/dist/shared/utils/validation.js +56 -0
  168. package/dist/types.js +1 -0
  169. package/dist/visual-evidence/capture.js +51 -0
  170. package/dist/visual-evidence/cli.js +85 -0
  171. package/dist/visual-evidence/evidence-required.js +71 -0
  172. package/dist/visual-evidence/index.js +10 -0
  173. package/dist/visual-evidence/launch.js +61 -0
  174. package/dist/visual-evidence/manifest.js +32 -0
  175. package/dist/visual-evidence/openspec-resolver.js +45 -0
  176. package/dist/visual-evidence/publish.js +126 -0
  177. package/dist/visual-evidence/run.js +121 -0
  178. package/dist/visual-evidence/scenario-registry.js +132 -0
  179. package/dist/widgets/command-input/CommandDropdown.js +69 -0
  180. package/dist/widgets/command-input/CommandInput.js +133 -0
  181. package/dist/widgets/command-input/HintBar.js +9 -0
  182. package/dist/widgets/command-input/InputModes.js +72 -0
  183. package/dist/widgets/commands-browser/CommandsBrowserModal.js +85 -0
  184. package/dist/widgets/header/Header.js +54 -0
  185. package/dist/widgets/header/TabBar.js +20 -0
  186. package/dist/widgets/left-panel/LeftPanel.js +34 -0
  187. package/dist/widgets/left-panel/Navigator.js +107 -0
  188. package/dist/widgets/left-panel/ProjectsPage.js +76 -0
  189. package/dist/widgets/left-panel/ProjectsPageParts.js +34 -0
  190. package/dist/widgets/left-panel/TaskBrowser.js +132 -0
  191. package/dist/widgets/log-modal/LogModal.js +211 -0
  192. package/dist/widgets/loop-form/CreateForm.js +82 -0
  193. package/dist/widgets/loop-form/TextField.js +9 -0
  194. package/dist/widgets/loop-form/WizardForm.js +199 -0
  195. package/dist/widgets/loop-form/useCreateSteps.js +135 -0
  196. package/dist/widgets/loop-form/useHandleComplete.js +94 -0
  197. package/dist/widgets/project-form/ProjectForm.js +83 -0
  198. package/dist/widgets/right-panel/Inspector.js +41 -0
  199. package/dist/widgets/right-panel/RightPanel.js +38 -0
  200. package/dist/widgets/right-panel/RunHistory.js +187 -0
  201. package/dist/widgets/task-form/TaskForm.js +272 -0
  202. package/package.json +119 -0
@@ -0,0 +1,131 @@
1
+ import fs from "node:fs";
2
+ import { parseDuration } from "../../duration.js";
3
+ import { parseMaxRuns } from "../../loop-config.js";
4
+ import { t } from "../i18n/index.js";
5
+ export const createFields = [
6
+ "interval",
7
+ "taskMode",
8
+ "command",
9
+ "cwd",
10
+ "taskId",
11
+ "description",
12
+ "runNow",
13
+ "maxRuns",
14
+ "project",
15
+ ];
16
+ /**
17
+ * Validates a single loop form field and returns an error string, or
18
+ * `null` if the value is valid.
19
+ *
20
+ * Mirrors the validation logic used in the board's CreateForm submit()
21
+ * and the TUI's validation utility, but in a single shared hook that
22
+ * both surfaces can consume.
23
+ *
24
+ * @param key - The field to validate.
25
+ * @param values - All form values (needed for cross-field rules like
26
+ * "command is required only when taskMode is inline").
27
+ */
28
+ function validateField(key, values) {
29
+ const value = values[key] ?? "";
30
+ switch (key) {
31
+ // Always required; delegates to parseDuration() which throws on
32
+ // empty, invalid syntax, or non-positive values.
33
+ case "interval": {
34
+ if (!value.trim()) {
35
+ return t("errors.durationEmpty");
36
+ }
37
+ try {
38
+ parseDuration(value);
39
+ return null;
40
+ }
41
+ catch (err) {
42
+ return err instanceof Error ? err.message : String(err);
43
+ }
44
+ }
45
+ // Required only when the user chose inline mode.
46
+ case "command": {
47
+ if (values.taskMode === "existing")
48
+ return null;
49
+ if (!value.trim()) {
50
+ return t("errors.commandEmpty");
51
+ }
52
+ return null;
53
+ }
54
+ // Optional; if provided the directory must exist on disk.
55
+ case "cwd": {
56
+ if (!value.trim())
57
+ return null;
58
+ if (!fs.existsSync(value)) {
59
+ return t("board.cwdMissing", { cwd: value });
60
+ }
61
+ return null;
62
+ }
63
+ // Always required.
64
+ case "description": {
65
+ if (!value.trim()) {
66
+ return t("errors.descriptionEmpty");
67
+ }
68
+ return null;
69
+ }
70
+ // Optional; if provided it must be a positive integer.
71
+ case "maxRuns": {
72
+ if (!value.trim())
73
+ return null;
74
+ try {
75
+ parseMaxRuns(value);
76
+ return null;
77
+ }
78
+ catch (err) {
79
+ return err instanceof Error ? err.message : String(err);
80
+ }
81
+ }
82
+ case "taskMode":
83
+ case "runNow":
84
+ case "project":
85
+ case "taskId":
86
+ return null;
87
+ }
88
+ }
89
+ /**
90
+ * Validates every relevant field and returns a map of field-key → error
91
+ * message. Only fields that fail validation appear in the map (omitted
92
+ * fields are valid).
93
+ */
94
+ function validateAll(values) {
95
+ const errors = {};
96
+ const fieldsToValidate = [
97
+ "interval",
98
+ "command",
99
+ "cwd",
100
+ "description",
101
+ "maxRuns",
102
+ ];
103
+ for (const field of fieldsToValidate) {
104
+ const error = validateField(field, values);
105
+ if (error !== null) {
106
+ errors[field] = error;
107
+ }
108
+ }
109
+ return errors;
110
+ }
111
+ /**
112
+ * Shared form-validation hook for loop creation / edit forms.
113
+ *
114
+ * Returns stable references to `validateField` and `validateAll` so it
115
+ * can be called from any React component without triggering re-renders.
116
+ *
117
+ * @example
118
+ * ```ts
119
+ * const { validateField, validateAll } = useLoopFormValidation();
120
+ *
121
+ * // On-blur per-field check:
122
+ * const err = validateField("interval", values);
123
+ *
124
+ * // On-submit full check:
125
+ * const errors = validateAll(values);
126
+ * if (Object.keys(errors).length === 0) { submit(); }
127
+ * ```
128
+ */
129
+ export function useLoopFormValidation() {
130
+ return { validateField, validateAll };
131
+ }
@@ -0,0 +1,30 @@
1
+ import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { useInject } from "./useInject.js";
3
+ import { TYPES } from "../services/types.js";
4
+ import { POLL_MS } from "../config/constants.js";
5
+ export function useLoopPolling() {
6
+ const loopService = useInject(TYPES.LoopService);
7
+ const [loops, setLoops] = useState([]);
8
+ const [daemonStatus, setDaemonStatus] = useState("starting");
9
+ const lastSerialized = useRef("");
10
+ const refresh = useCallback(async () => {
11
+ try {
12
+ const next = await loopService.list();
13
+ const serialized = JSON.stringify(next);
14
+ if (serialized !== lastSerialized.current) {
15
+ lastSerialized.current = serialized;
16
+ setLoops(next);
17
+ }
18
+ setDaemonStatus("connected");
19
+ }
20
+ catch {
21
+ setDaemonStatus("error");
22
+ }
23
+ }, [loopService]);
24
+ useEffect(() => {
25
+ void refresh();
26
+ const timer = setInterval(() => void refresh(), POLL_MS);
27
+ return () => clearInterval(timer);
28
+ }, [refresh]);
29
+ return { loops, daemonStatus, refresh };
30
+ }
@@ -0,0 +1,56 @@
1
+ import { useInput } from "ink";
2
+ import { useRef } from "react";
3
+ /**
4
+ * Parse SGR (1006) mouse scroll sequences from an input string that Ink
5
+ * has already passed through (ESC byte stripped).
6
+ *
7
+ * Ink strips the leading \x1b, so the input arrives as:
8
+ * [<button;col;rowM (press) or [<button;col;rowm (release)
9
+ *
10
+ * Scroll wheel: button 64 = scroll up, button 65 = scroll down.
11
+ *
12
+ * A single useInput call may contain multiple concatenated mouse events
13
+ * (fast scrolling). Returns the net scroll direction:
14
+ * positive = scrolled down, negative = up, zero = no scroll.
15
+ */
16
+ export function parseMouseScroll(input) {
17
+ let delta = 0;
18
+ // Match stripped SGR sequences: [<button;col;rowM or m
19
+ // (Ink strips the ESC byte, so we match without \x1b)
20
+ const sgrRe = /\[<(\d+);\d+;\d+[Mm]/g;
21
+ let match;
22
+ while ((match = sgrRe.exec(input)) !== null) {
23
+ const button = Number(match[1]);
24
+ if (button === 64)
25
+ delta -= 1; // scroll up
26
+ else if (button === 65)
27
+ delta += 1; // scroll down
28
+ }
29
+ return delta;
30
+ }
31
+ /**
32
+ * Hook that listens for mouse scroll events via Ink's useInput and fires
33
+ * callbacks.
34
+ *
35
+ * Must be used inside an Ink app that has enabled SGR mouse tracking
36
+ * (DECSET 1000 + 1006) at startup. The hook does not enable/disable
37
+ * tracking itself.
38
+ */
39
+ export function useMouseScroll(options) {
40
+ const { onScrollUp, onScrollDown, isActive = true } = options;
41
+ const callbacksRef = useRef({ onScrollUp, onScrollDown });
42
+ callbacksRef.current = { onScrollUp, onScrollDown };
43
+ useInput((input) => {
44
+ const delta = parseMouseScroll(input);
45
+ if (delta < 0) {
46
+ for (let i = 0; i < Math.abs(delta); i++) {
47
+ callbacksRef.current.onScrollUp();
48
+ }
49
+ }
50
+ else if (delta > 0) {
51
+ for (let i = 0; i < delta; i++) {
52
+ callbacksRef.current.onScrollDown();
53
+ }
54
+ }
55
+ }, { isActive });
56
+ }
@@ -0,0 +1,77 @@
1
+ import { useState, useRef, useCallback } from "react";
2
+ /**
3
+ * Pure undo/redo stack logic exported for direct unit testing
4
+ * without needing React DOM or renderHook.
5
+ */
6
+ export class UndoRedoStack {
7
+ constructor(value, limit = 50) {
8
+ this.value = value;
9
+ this.limit = limit;
10
+ this.history = [];
11
+ this.future = [];
12
+ }
13
+ get canUndo() {
14
+ return this.history.length > 0;
15
+ }
16
+ get canRedo() {
17
+ return this.future.length > 0;
18
+ }
19
+ setValue(next) {
20
+ if (next === this.value)
21
+ return false;
22
+ this.history.push(this.value);
23
+ if (this.history.length > this.limit) {
24
+ this.history.shift();
25
+ }
26
+ this.future = [];
27
+ this.value = next;
28
+ return true;
29
+ }
30
+ undo() {
31
+ if (this.history.length === 0)
32
+ return false;
33
+ this.future.push(this.value);
34
+ this.value = this.history.pop();
35
+ return true;
36
+ }
37
+ redo() {
38
+ if (this.future.length === 0)
39
+ return false;
40
+ this.history.push(this.value);
41
+ this.value = this.future.pop();
42
+ return true;
43
+ }
44
+ }
45
+ export function useUndoRedo(initial, limit = 50) {
46
+ const [value, setValueState] = useState(initial);
47
+ const stackRef = useRef(new UndoRedoStack(initial, limit));
48
+ const setValue = useCallback((next) => {
49
+ setValueState((prev) => {
50
+ if (stackRef.current.setValue(next))
51
+ return next;
52
+ return prev;
53
+ });
54
+ }, [limit]);
55
+ const undo = useCallback(() => {
56
+ setValueState((prev) => {
57
+ if (stackRef.current.undo())
58
+ return stackRef.current.value;
59
+ return prev;
60
+ });
61
+ }, []);
62
+ const redo = useCallback(() => {
63
+ setValueState((prev) => {
64
+ if (stackRef.current.redo())
65
+ return stackRef.current.value;
66
+ return prev;
67
+ });
68
+ }, []);
69
+ return {
70
+ value,
71
+ setValue,
72
+ undo,
73
+ redo,
74
+ canUndo: stackRef.current.canUndo,
75
+ canRedo: stackRef.current.canRedo,
76
+ };
77
+ }