project-state-manager 1.0.1

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 (37) hide show
  1. package/README.md +51 -0
  2. package/dist/shared/schemas/process.schema.d.ts +969 -0
  3. package/dist/shared/schemas/process.schema.js +119 -0
  4. package/dist/src/core/error-formatter.d.ts +31 -0
  5. package/dist/src/core/error-formatter.js +97 -0
  6. package/dist/src/core/signal-calculator.d.ts +12 -0
  7. package/dist/src/core/signal-calculator.js +19 -0
  8. package/dist/src/core/yaml-engine.d.ts +15 -0
  9. package/dist/src/core/yaml-engine.js +162 -0
  10. package/dist/src/gui-worker.d.ts +1 -0
  11. package/dist/src/gui-worker.js +221 -0
  12. package/dist/src/index.d.ts +2 -0
  13. package/dist/src/index.js +281 -0
  14. package/dist/src/infra/ipc-hub.d.ts +18 -0
  15. package/dist/src/infra/ipc-hub.js +71 -0
  16. package/dist/src/infra/proxy-client.d.ts +32 -0
  17. package/dist/src/infra/proxy-client.js +134 -0
  18. package/dist/src/tools/check-readiness.d.ts +17 -0
  19. package/dist/src/tools/check-readiness.js +48 -0
  20. package/dist/src/tools/define-process.d.ts +14 -0
  21. package/dist/src/tools/define-process.js +39 -0
  22. package/dist/src/tools/discover-process.d.ts +7 -0
  23. package/dist/src/tools/discover-process.js +41 -0
  24. package/dist/src/tools/get-health-status.d.ts +11 -0
  25. package/dist/src/tools/get-health-status.js +62 -0
  26. package/dist/src/tools/list-existing-processes.d.ts +7 -0
  27. package/dist/src/tools/list-existing-processes.js +25 -0
  28. package/dist/src/tools/scan-node.d.ts +14 -0
  29. package/dist/src/tools/scan-node.js +47 -0
  30. package/dist/src/tools/submit-node-data.d.ts +18 -0
  31. package/dist/src/tools/submit-node-data.js +86 -0
  32. package/gui/README.md +73 -0
  33. package/gui/dist/assets/index-BpHg-2LI.css +1 -0
  34. package/gui/dist/assets/index-Cq6Ut9u2.js +9 -0
  35. package/gui/dist/index.html +14 -0
  36. package/gui/dist/vite.svg +1 -0
  37. package/package.json +55 -0
@@ -0,0 +1,41 @@
1
+ import { DiscoverProcessArgsSchema } from '../../shared/schemas/process.schema.js';
2
+ export async function discoverProcess(yamlEngine, args) {
3
+ const { context } = DiscoverProcessArgsSchema.parse(args);
4
+ const processes = await yamlEngine.listProcesses();
5
+ const matches = [];
6
+ for (const id of processes) {
7
+ const p = await yamlEngine.loadProcess(id);
8
+ if (p) {
9
+ const searchTerm = context.toLowerCase();
10
+ const inName = p.name.toLowerCase().includes(searchTerm);
11
+ const inId = id.toLowerCase().includes(searchTerm);
12
+ const inEntry = p.entryPoint.toLowerCase().includes(searchTerm);
13
+ // Extended search in node content
14
+ const inInput = p.nodes.input.content.toLowerCase().includes(searchTerm);
15
+ const inFlow = p.nodes.flow.content.toLowerCase().includes(searchTerm);
16
+ const inOutput = p.nodes.output.content.toLowerCase().includes(searchTerm);
17
+ if (inName || inId || inEntry || inInput || inFlow || inOutput) {
18
+ matches.push(p);
19
+ }
20
+ }
21
+ }
22
+ if (matches.length === 0) {
23
+ return {
24
+ content: [{ type: 'text', text: `No process found matching context: "${context}"` }]
25
+ };
26
+ }
27
+ const results = matches.map(p => {
28
+ const manualFlags = [];
29
+ if (p.nodes.input.manuallyModified)
30
+ manualFlags.push('Input');
31
+ if (p.nodes.flow.manuallyModified)
32
+ manualFlags.push('Flow');
33
+ if (p.nodes.output.manuallyModified)
34
+ manualFlags.push('Output');
35
+ const manualSuffix = manualFlags.length > 0 ? ` [MODIFIÉ MANUELLEMENT: ${manualFlags.join(', ')}]` : '';
36
+ return `- ${p.name} (ID: ${p.id}) [Entry: ${p.entryPoint}]${manualSuffix}`;
37
+ });
38
+ return {
39
+ content: [{ type: 'text', text: `Found relevant processes for "${context}":\n${results.join('\n')}` }]
40
+ };
41
+ }
@@ -0,0 +1,11 @@
1
+ import { IpcHub } from '../infra/ipc-hub.js';
2
+ /**
3
+ * MCP Tool to get health status of Master and Worker processes.
4
+ */
5
+ export declare function getHealthStatus(ipcHub: IpcHub): Promise<{
6
+ isError: boolean;
7
+ content: {
8
+ type: string;
9
+ text: string;
10
+ }[];
11
+ }>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * MCP Tool to get health status of Master and Worker processes.
3
+ */
4
+ export async function getHealthStatus(ipcHub) {
5
+ const masterMemory = process.memoryUsage();
6
+ const health = {
7
+ master: {
8
+ status: 'ok',
9
+ uptime: process.uptime(),
10
+ memory: {
11
+ rss: masterMemory.rss,
12
+ heapUsed: masterMemory.heapUsed,
13
+ },
14
+ },
15
+ };
16
+ try {
17
+ const workerHealth = await ipcHub.request({ type: 'GET_HEALTH' }, 1000);
18
+ health.worker = {
19
+ status: workerHealth.data.status,
20
+ connected: true,
21
+ uptime: workerHealth.data.uptime,
22
+ memory: workerHealth.data.memory,
23
+ };
24
+ }
25
+ catch (err) {
26
+ health.worker = {
27
+ status: 'error',
28
+ connected: false,
29
+ error: err instanceof Error ? err.message : String(err),
30
+ };
31
+ }
32
+ return {
33
+ isError: false,
34
+ content: [{
35
+ type: 'text',
36
+ text: formatHealthMarkdown(health)
37
+ }],
38
+ };
39
+ }
40
+ function formatHealthMarkdown(health) {
41
+ let md = `## Infrastructure Health Status\n\n`;
42
+ md += `### Master Process\n`;
43
+ md += `- **Status**: \`${health.master.status}\`\n`;
44
+ md += `- **Uptime**: ${Math.floor(health.master.uptime)}s\n`;
45
+ md += `- **Memory**: RSS: ${(health.master.memory.rss / 1024 / 1024).toFixed(2)}MB, Heap: ${(health.master.memory.heapUsed / 1024 / 1024).toFixed(2)}MB\n\n`;
46
+ md += `### Worker Process (GUI)\n`;
47
+ if (health.worker) {
48
+ md += `- **Status**: \`${health.worker.status}\`\n`;
49
+ md += `- **IPC**: ${health.worker.connected ? '✅ Connected' : '❌ Disconnected'}\n`;
50
+ if (health.worker.connected) {
51
+ md += `- **Uptime**: ${Math.floor(health.worker.uptime)}s\n`;
52
+ md += `- **Memory**: RSS: ${(health.worker.memory.rss / 1024 / 1024).toFixed(2)}MB, Heap: ${(health.worker.memory.heapUsed / 1024 / 1024).toFixed(2)}MB\n`;
53
+ }
54
+ else {
55
+ md += `- **Error**: ${health.worker.error}\n`;
56
+ }
57
+ }
58
+ else {
59
+ md += `*Worker status unavailable*\n`;
60
+ }
61
+ return md;
62
+ }
@@ -0,0 +1,7 @@
1
+ import { YamlEngine } from '../core/yaml-engine.js';
2
+ export declare function listExistingProcesses(yamlEngine: YamlEngine): Promise<{
3
+ content: {
4
+ type: string;
5
+ text: string;
6
+ }[];
7
+ }>;
@@ -0,0 +1,25 @@
1
+ export async function listExistingProcesses(yamlEngine) {
2
+ const processes = await yamlEngine.listProcesses();
3
+ if (processes.length === 0) {
4
+ return {
5
+ content: [{ type: 'text', text: 'No processes found.' }]
6
+ };
7
+ }
8
+ const results = await Promise.all(processes.map(async (id) => {
9
+ const p = await yamlEngine.loadProcess(id);
10
+ if (!p)
11
+ return `- (ID: ${id}) [Error loading]`;
12
+ const manualFlags = [];
13
+ if (p.nodes.input.manuallyModified)
14
+ manualFlags.push('Input');
15
+ if (p.nodes.flow.manuallyModified)
16
+ manualFlags.push('Flow');
17
+ if (p.nodes.output.manuallyModified)
18
+ manualFlags.push('Output');
19
+ const manualSuffix = manualFlags.length > 0 ? ` [MODIFIÉ MANUELLEMENT: ${manualFlags.join(', ')}]` : '';
20
+ return `- ${p.name} (ID: ${id}) [Entry: ${p.entryPoint}]${manualSuffix}`;
21
+ }));
22
+ return {
23
+ content: [{ type: 'text', text: `Existing processes:\n${results.join('\n')}` }]
24
+ };
25
+ }
@@ -0,0 +1,14 @@
1
+ import { YamlEngine } from '../core/yaml-engine.js';
2
+ export declare function scanNode(yamlEngine: YamlEngine, args: any): Promise<{
3
+ content: {
4
+ type: string;
5
+ text: string;
6
+ }[];
7
+ isError: boolean;
8
+ } | {
9
+ content: {
10
+ type: string;
11
+ text: string;
12
+ }[];
13
+ isError?: undefined;
14
+ }>;
@@ -0,0 +1,47 @@
1
+ import { ScanNodeArgsSchema } from '../../shared/schemas/process.schema.js';
2
+ const PROMPTS = {
3
+ entry: `Analyse les points d'entrée de la feature. Identifie :
4
+ 1. Les sources de données (APIs, fichiers, input utilisateur).
5
+ 2. La structure des données entrantes.
6
+ 3. Les schémas de validation appliqués (ex: Zod, Joi).
7
+ 4. Les contraintes de sécurité (auth, rate limiting).`,
8
+ flow: `Analyse la logique de traitement. Identifie :
9
+ 1. Le cheminement des données entre les modules.
10
+ 2. Les transformations appliquées.
11
+ 3. Les services ou helpers sollicités.
12
+ 4. Les effets de bord (écriture DB, appels externes, logs).`,
13
+ exit: `Analyse les résultats et retours. Identifie :
14
+ 1. Le format de la réponse (JSON, Status Codes).
15
+ 2. La destination des données (Client, Cache, DB).
16
+ 3. Les mécanismes de gestion d'erreurs et les codes d'erreur spécifiques.
17
+ 4. Les garanties de succès (retries, transactions).`
18
+ };
19
+ export async function scanNode(yamlEngine, args) {
20
+ const validatedArgs = ScanNodeArgsSchema.parse(args);
21
+ const { process_name, node_type } = validatedArgs;
22
+ const process = await yamlEngine.loadProcess(process_name);
23
+ if (!process) {
24
+ return {
25
+ content: [{ type: 'text', text: `Processus "${process_name}" non trouvé.` }],
26
+ isError: true
27
+ };
28
+ }
29
+ // Map node type to check for manual modification
30
+ let targetKey;
31
+ if (node_type === 'entry')
32
+ targetKey = 'input';
33
+ else if (node_type === 'flow')
34
+ targetKey = 'flow';
35
+ else
36
+ targetKey = 'output';
37
+ let prompt = PROMPTS[node_type];
38
+ if (process.nodes[targetKey].manuallyModified) {
39
+ prompt = `⚠️ ATTENTION : Ce nœud a été modifié manuellement par l'utilisateur.
40
+ Prends en compte les corrections humaines dans ton analyse et évite d'écraser les informations importantes.
41
+
42
+ ${prompt}`;
43
+ }
44
+ return {
45
+ content: [{ type: 'text', text: prompt }]
46
+ };
47
+ }
@@ -0,0 +1,18 @@
1
+ import { YamlEngine } from '../core/yaml-engine.js';
2
+ /**
3
+ * Submits and validates findings for a specific process node.
4
+ *
5
+ * This tool validates the findings against the appropriate Zod schema
6
+ * for the node type, then persists the data via YamlEngine.
7
+ *
8
+ * @param yamlEngine - The YamlEngine instance for persistence
9
+ * @param args - The tool arguments (process_name, node_type, findings)
10
+ * @returns MCP tool response with success/error status
11
+ */
12
+ export declare function submitNodeData(yamlEngine: YamlEngine, args: unknown): Promise<{
13
+ isError: boolean;
14
+ content: {
15
+ type: string;
16
+ text: string;
17
+ }[];
18
+ }>;
@@ -0,0 +1,86 @@
1
+ import { SubmitNodeDataArgsSchema, NodeFindingsSchema } from '../../shared/schemas/process.schema.js';
2
+ import { ZodError } from 'zod';
3
+ import { ErrorFormatter } from '../core/error-formatter.js';
4
+ /**
5
+ * Submits and validates findings for a specific process node.
6
+ *
7
+ * This tool validates the findings against the appropriate Zod schema
8
+ * for the node type, then persists the data via YamlEngine.
9
+ *
10
+ * @param yamlEngine - The YamlEngine instance for persistence
11
+ * @param args - The tool arguments (process_name, node_type, findings)
12
+ * @returns MCP tool response with success/error status
13
+ */
14
+ export async function submitNodeData(yamlEngine, args) {
15
+ try {
16
+ const validatedArgs = SubmitNodeDataArgsSchema.parse(args);
17
+ const { process_name, node_type, findings } = validatedArgs;
18
+ const process = await yamlEngine.loadProcess(process_name);
19
+ if (!process) {
20
+ return {
21
+ isError: true,
22
+ content: [{ type: 'text', text: `Processus "${process_name}" non trouvé.` }],
23
+ };
24
+ }
25
+ // Prepare findings for validation by adding the 'type' field to match the discriminated union
26
+ const findingsWithType = { ...findings, type: node_type };
27
+ try {
28
+ const validatedFindings = NodeFindingsSchema.parse(findingsWithType);
29
+ // Update the specific node
30
+ const timestamp = new Date().toISOString();
31
+ let targetNode;
32
+ let warning = "";
33
+ if (node_type === 'entry')
34
+ targetNode = process.nodes.input;
35
+ else if (node_type === 'flow')
36
+ targetNode = process.nodes.flow;
37
+ else
38
+ targetNode = process.nodes.output;
39
+ if (targetNode.manuallyModified) {
40
+ warning = "\n⚠️ NOTE : Tu viens d'écraser une modification manuelle effectuée par l'utilisateur sur ce nœud.";
41
+ }
42
+ const updatedNode = {
43
+ ...targetNode,
44
+ status: 'done',
45
+ lastUpdated: timestamp,
46
+ data: validatedFindings,
47
+ manuallyModified: false // Reset flag on agent submission
48
+ };
49
+ if (node_type === 'entry')
50
+ process.nodes.input = updatedNode;
51
+ else if (node_type === 'flow')
52
+ process.nodes.flow = updatedNode;
53
+ else
54
+ process.nodes.output = updatedNode;
55
+ await yamlEngine.saveProcess(process);
56
+ const updatedProcess = await yamlEngine.loadProcess(process_name);
57
+ const statusMsg = updatedProcess?.is_executable ? "TRUE ✅" : "false ❌";
58
+ return {
59
+ isError: false,
60
+ content: [{
61
+ type: 'text',
62
+ text: `Données soumises et validées pour le nœud "${node_type}" du processus "${process_name}".${warning}\n\n[GUARDRAIL] État actuel de is_executable : ${statusMsg}`
63
+ }],
64
+ };
65
+ }
66
+ catch (err) {
67
+ if (err instanceof ZodError) {
68
+ return {
69
+ isError: true,
70
+ content: [{
71
+ type: 'text',
72
+ text: `Échec de la validation des données pour le nœud "${node_type}":\n${ErrorFormatter.format(err, node_type)}`
73
+ }],
74
+ };
75
+ }
76
+ throw err;
77
+ }
78
+ }
79
+ catch (err) {
80
+ const message = err instanceof Error ? err.message : String(err);
81
+ return {
82
+ isError: true,
83
+ content: [{ type: 'text', text: `Erreur lors de la soumission : ${message}` }],
84
+ };
85
+ }
86
+ }
package/gui/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # React + TypeScript + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
+
10
+ ## React Compiler
11
+
12
+ The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
+
14
+ ## Expanding the ESLint configuration
15
+
16
+ If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
17
+
18
+ ```js
19
+ export default defineConfig([
20
+ globalIgnores(['dist']),
21
+ {
22
+ files: ['**/*.{ts,tsx}'],
23
+ extends: [
24
+ // Other configs...
25
+
26
+ // Remove tseslint.configs.recommended and replace with this
27
+ tseslint.configs.recommendedTypeChecked,
28
+ // Alternatively, use this for stricter rules
29
+ tseslint.configs.strictTypeChecked,
30
+ // Optionally, add this for stylistic rules
31
+ tseslint.configs.stylisticTypeChecked,
32
+
33
+ // Other configs...
34
+ ],
35
+ languageOptions: {
36
+ parserOptions: {
37
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
38
+ tsconfigRootDir: import.meta.dirname,
39
+ },
40
+ // other options...
41
+ },
42
+ },
43
+ ])
44
+ ```
45
+
46
+ You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
47
+
48
+ ```js
49
+ // eslint.config.js
50
+ import reactX from 'eslint-plugin-react-x'
51
+ import reactDom from 'eslint-plugin-react-dom'
52
+
53
+ export default defineConfig([
54
+ globalIgnores(['dist']),
55
+ {
56
+ files: ['**/*.{ts,tsx}'],
57
+ extends: [
58
+ // Other configs...
59
+ // Enable lint rules for React
60
+ reactX.configs['recommended-typescript'],
61
+ // Enable lint rules for React DOM
62
+ reactDom.configs.recommended,
63
+ ],
64
+ languageOptions: {
65
+ parserOptions: {
66
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
67
+ tsconfigRootDir: import.meta.dirname,
68
+ },
69
+ // other options...
70
+ },
71
+ },
72
+ ])
73
+ ```
@@ -0,0 +1 @@
1
+ :root{font-family:system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}a{font-weight:500;color:#646cff;text-decoration:inherit}a:hover{color:#535bf2}body{margin:0;display:flex;place-items:center;min-width:320px;min-height:100vh}h1{font-size:3.2em;line-height:1.1}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;background-color:#1a1a1a;cursor:pointer;transition:border-color .25s}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}@media(prefers-color-scheme:light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}}:root{--bg-color: #0f172a;--sidebar-bg: #1e293b;--card-bg: #334155;--text-primary: #f8fafc;--text-secondary: #94a3b8;--accent: #3b82f6;--success: #22c55e;--error: #ef4444;--warning: #f59e0b;--muted: #64748b;--border: #334155}.dashboard-layout{display:flex;flex-direction:column;height:100vh;background-color:var(--bg-color);color:var(--text-primary);font-family:Inter,system-ui,-apple-system,sans-serif}.main-header{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1.5rem;background-color:var(--sidebar-bg);border-bottom:1px solid var(--border)}.main-header h1{font-size:1.25rem;margin:0;color:var(--accent)}.connection-status{display:flex;align-items:center;gap:.5rem;font-size:.875rem;padding:.25rem .75rem;border-radius:9999px}.connection-status.connected{background-color:#22c55e1a;color:var(--success)}.connection-status.disconnected{background-color:#ef44441a;color:var(--error)}.main-content{display:flex;flex:1;overflow:hidden}.sidebar{width:260px;background-color:var(--sidebar-bg);border-right:1px solid var(--border);display:flex;flex-direction:column}.sidebar-header{display:flex;align-items:center;gap:.75rem;padding:1.25rem;border-bottom:1px solid var(--border)}.sidebar-header h2{font-size:1rem;margin:0}.process-list{flex:1;overflow-y:auto;padding:.5rem}.process-item{display:flex;align-items:center;gap:.75rem;padding:.75rem 1rem;border-radius:.5rem;cursor:pointer;transition:all .2s;margin-bottom:.25rem}.process-item:hover{background-color:#ffffff0d}.process-item.active{background-color:#3b82f61a;color:var(--accent)}.process-name{flex:1;font-size:.875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.node-card.selected{border-color:var(--accent);box-shadow:0 0 10px #3b82f64d;transform:translateY(-4px)}.node-editor-overlay{position:fixed;inset:0;background-color:#000000bf;display:flex;justify-content:center;align-items:center;z-index:1000}.node-editor-container{width:800px;max-width:90vw;height:600px;max-height:80vh;background-color:var(--sidebar-bg);border-radius:1rem;border:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden;box-shadow:0 20px 25px -5px #00000080}.editor-header{padding:1.5rem;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}.editor-title h2{font-size:1.25rem;margin:0}.editor-title .process-name{font-size:.875rem;color:var(--text-secondary)}.close-btn{background:none;border:none;color:var(--text-secondary);cursor:pointer;padding:.5rem;display:flex;align-items:center;justify-content:center;border-radius:.375rem;transition:background-color .2s}.close-btn:hover{background-color:#ffffff1a;color:var(--text-primary)}.editor-content{flex:1;display:flex;flex-direction:column;padding:1.5rem;gap:1rem}.yaml-textarea{flex:1;background-color:var(--bg-color);color:var(--text-primary);border:1px solid var(--border);border-radius:.5rem;padding:1rem;font-family:JetBrains Mono,Fira Code,"ui-monospace",monospace;font-size:.875rem;line-height:1.6;resize:none;outline:none}.yaml-textarea:focus{border-color:var(--accent)}.error-banner{background-color:#ef44441a;border:1px solid var(--error);color:#fca5a5;padding:.75rem 1rem;border-radius:.5rem;display:flex;align-items:center;gap:.75rem;font-size:.875rem}.editor-footer{padding:1.5rem;border-top:1px solid var(--border);display:flex;justify-content:flex-end;gap:1rem}.cancel-btn{background:none;border:1px solid var(--border);color:var(--text-primary)}.cancel-btn:hover{background-color:#ffffff0d}.save-btn{background-color:var(--accent);color:#fff;display:flex;align-items:center;gap:.5rem}.save-btn:hover{background-color:#2563eb}.save-btn:disabled{opacity:.5;cursor:not-allowed}.process-view{flex:1;display:flex;flex-direction:column;padding:2rem;overflow-y:auto}.view-header{margin-bottom:2rem;display:flex;justify-content:space-between;align-items:flex-start}.execution-signal{display:flex;align-items:center;gap:.75rem;padding:.5rem 1rem;border-radius:.5rem;background-color:#1e293b80;border:1px solid var(--border)}.execution-signal.executable{border-color:var(--success)}.execution-signal.executable .signal-dot{width:10px;height:10px;background-color:var(--success);border-radius:50%;box-shadow:0 0 8px var(--success)}.execution-signal.not-executable .signal-dot{width:10px;height:10px;background-color:var(--muted);border-radius:50%}.signal-label{font-size:.875rem;font-weight:500}.signal-label.executable{color:var(--success)}.nodes-container{display:flex;align-items:flex-start;gap:1rem}.connector{flex:1;height:2px;background-color:var(--border);margin-top:3rem;min-width:2rem}.node-card{width:300px;background-color:var(--sidebar-bg);border-radius:.75rem;border:1px solid var(--border);padding:1.25rem;display:flex;flex-direction:column;gap:1rem;transition:transform .2s,border-color .2s}.node-card:hover{transform:translateY(-4px)}.node-card.status-done{border-left:4px solid var(--success)}.node-card.status-error{border-left:4px solid var(--error)}.node-card.status-progress{border-left:4px solid var(--warning)}.node-card.status-todo{border-left:4px solid var(--muted)}.node-header{display:flex;justify-content:space-between;align-items:center}.node-title{display:flex;align-items:center;gap:.75rem}.node-title h3{font-size:1rem;margin:0}.node-content{background-color:#0f172a80;border-radius:.5rem;padding:1rem;min-height:80px}.content-text{font-size:.875rem;line-height:1.5;color:var(--text-secondary);margin:0}.node-footer{font-size:.75rem;color:var(--muted)}.empty-view{flex:1;display:flex;justify-content:center;align-items:center;color:var(--text-secondary)}.status-icon.success{color:var(--success)}.status-icon.error{color:var(--error)}.status-icon.warning{color:var(--warning)}.status-icon.muted{color:var(--muted)}