@monoes/monomindcli 1.14.6 → 1.15.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.
- package/.claude/agents/reengineer-squad/boss.md +113 -0
- package/.claude/agents/reengineer-squad/critic-architect.md +132 -0
- package/.claude/agents/reengineer-squad/git-manager.md +145 -0
- package/.claude/agents/reengineer-squad/idea-generator.md +95 -0
- package/.claude/agents/reengineer-squad/implementer.md +112 -0
- package/.claude/agents/reengineer-squad/integration-planner.md +112 -0
- package/.claude/agents/reengineer-squad/source-analyst.md +103 -0
- package/.claude/agents/reengineer-squad/target-analyst.md +118 -0
- package/.claude/agents/reengineer-squad/tester.md +105 -0
- package/.claude/commands/mastermind/master.md +35 -14
- package/.claude/helpers/handlers/capture-handler.cjs +155 -18
- package/.claude/helpers/monolean-activate.cjs +20 -0
- package/.claude/helpers/monolean-config.cjs +76 -0
- package/.claude/helpers/monolean-instructions.cjs +109 -0
- package/.claude/helpers/monolean-propagate.cjs +9 -0
- package/.claude/helpers/monolean-tracker.cjs +18 -0
- package/.claude/helpers/skill-registry.json +2 -2
- package/.claude/skills/agent-browser-testing/SKILL.md +301 -18
- package/.claude/skills/mastermind/runorg.md +69 -23
- package/.claude/skills/monodesign/SKILL.md +32 -1
- package/.claude/skills/monodesign/adapt.md +53 -0
- package/.claude/skills/monodesign/agents/monodesign-asset-producer.md +100 -0
- package/.claude/skills/monodesign/animate.md +65 -0
- package/.claude/skills/monodesign/audit.md +89 -0
- package/.claude/skills/monodesign/bolder.md +50 -0
- package/.claude/skills/monodesign/clarify.md +64 -0
- package/.claude/skills/monodesign/colorize.md +68 -0
- package/.claude/skills/monodesign/craft.md +51 -0
- package/.claude/skills/monodesign/critique.md +66 -0
- package/.claude/skills/monodesign/delight.md +47 -0
- package/.claude/skills/monodesign/distill.md +56 -0
- package/.claude/skills/monodesign/document.md +80 -0
- package/.claude/skills/monodesign/extract.md +74 -0
- package/.claude/skills/monodesign/harden.md +65 -0
- package/.claude/skills/monodesign/live.md +59 -0
- package/.claude/skills/monodesign/onboard.md +50 -0
- package/.claude/skills/monodesign/optimize.md +64 -0
- package/.claude/skills/monodesign/overdrive.md +56 -0
- package/.claude/skills/monodesign/polish.md +68 -0
- package/.claude/skills/monodesign/quieter.md +57 -0
- package/.claude/skills/monodesign/reference/antipatterns-catalog.md +248 -76
- package/.claude/skills/monodesign/reference/codex.md +107 -0
- package/.claude/skills/monodesign/reference/craft.md +3 -0
- package/.claude/skills/monodesign/reference/hooks.md +99 -0
- package/.claude/skills/monodesign/reference/image-prompts.md +12 -0
- package/.claude/skills/monodesign/shape.md +71 -0
- package/.claude/skills/monodesign/teach.md +69 -0
- package/.claude/skills/monodesign/typeset.md +59 -0
- package/.claude/skills/monolean/SKILL.md +118 -0
- package/.claude/skills/monolean-audit/SKILL.md +41 -0
- package/.claude/skills/monolean-debt/SKILL.md +46 -0
- package/.claude/skills/monolean-help/SKILL.md +60 -0
- package/.claude/skills/monolean-review/SKILL.md +57 -0
- package/bin/cli.js +3 -1
- package/dist/dashboard/server.js +137 -0
- package/dist/src/__tests__/browse-adapters.test.d.ts +2 -0
- package/dist/src/__tests__/browse-adapters.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-adapters.test.js +51 -0
- package/dist/src/__tests__/browse-adapters.test.js.map +1 -0
- package/dist/src/__tests__/browse-analyzer.test.d.ts +2 -0
- package/dist/src/__tests__/browse-analyzer.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-analyzer.test.js +68 -0
- package/dist/src/__tests__/browse-analyzer.test.js.map +1 -0
- package/dist/src/__tests__/browse-builtin-handlers.test.d.ts +2 -0
- package/dist/src/__tests__/browse-builtin-handlers.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-builtin-handlers.test.js +139 -0
- package/dist/src/__tests__/browse-builtin-handlers.test.js.map +1 -0
- package/dist/src/__tests__/browse-cdp.test.d.ts +2 -0
- package/dist/src/__tests__/browse-cdp.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-cdp.test.js +169 -0
- package/dist/src/__tests__/browse-cdp.test.js.map +1 -0
- package/dist/src/__tests__/browse-dashboard.test.d.ts +2 -0
- package/dist/src/__tests__/browse-dashboard.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-dashboard.test.js +179 -0
- package/dist/src/__tests__/browse-dashboard.test.js.map +1 -0
- package/dist/src/__tests__/browse-engine.test.d.ts +2 -0
- package/dist/src/__tests__/browse-engine.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-engine.test.js +122 -0
- package/dist/src/__tests__/browse-engine.test.js.map +1 -0
- package/dist/src/__tests__/browse-expression.test.d.ts +2 -0
- package/dist/src/__tests__/browse-expression.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-expression.test.js +54 -0
- package/dist/src/__tests__/browse-expression.test.js.map +1 -0
- package/dist/src/__tests__/browse-store.test.d.ts +2 -0
- package/dist/src/__tests__/browse-store.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-store.test.js +99 -0
- package/dist/src/__tests__/browse-store.test.js.map +1 -0
- package/dist/src/__tests__/browse-workflow-types.test.d.ts +2 -0
- package/dist/src/__tests__/browse-workflow-types.test.d.ts.map +1 -0
- package/dist/src/__tests__/browse-workflow-types.test.js +33 -0
- package/dist/src/__tests__/browse-workflow-types.test.js.map +1 -0
- package/dist/src/browser/action-builder/analyzer.d.ts +11 -0
- package/dist/src/browser/action-builder/analyzer.d.ts.map +1 -0
- package/dist/src/browser/action-builder/analyzer.js +71 -0
- package/dist/src/browser/action-builder/analyzer.js.map +1 -0
- package/dist/src/browser/action-builder/types.d.ts +47 -0
- package/dist/src/browser/action-builder/types.d.ts.map +1 -0
- package/dist/src/browser/action-builder/types.js +2 -0
- package/dist/src/browser/action-builder/types.js.map +1 -0
- package/dist/src/browser/adapters/gemini.d.ts +3 -0
- package/dist/src/browser/adapters/gemini.d.ts.map +1 -0
- package/dist/src/browser/adapters/gemini.js +16 -0
- package/dist/src/browser/adapters/gemini.js.map +1 -0
- package/dist/src/browser/adapters/google.d.ts +3 -0
- package/dist/src/browser/adapters/google.d.ts.map +1 -0
- package/dist/src/browser/adapters/google.js +17 -0
- package/dist/src/browser/adapters/google.js.map +1 -0
- package/dist/src/browser/adapters/index.d.ts +19 -0
- package/dist/src/browser/adapters/index.d.ts.map +1 -0
- package/dist/src/browser/adapters/index.js +23 -0
- package/dist/src/browser/adapters/index.js.map +1 -0
- package/dist/src/browser/adapters/instagram.d.ts +3 -0
- package/dist/src/browser/adapters/instagram.d.ts.map +1 -0
- package/dist/src/browser/adapters/instagram.js +17 -0
- package/dist/src/browser/adapters/instagram.js.map +1 -0
- package/dist/src/browser/adapters/linkedin.d.ts +3 -0
- package/dist/src/browser/adapters/linkedin.d.ts.map +1 -0
- package/dist/src/browser/adapters/linkedin.js +19 -0
- package/dist/src/browser/adapters/linkedin.js.map +1 -0
- package/dist/src/browser/adapters/microsoft.d.ts +3 -0
- package/dist/src/browser/adapters/microsoft.d.ts.map +1 -0
- package/dist/src/browser/adapters/microsoft.js +16 -0
- package/dist/src/browser/adapters/microsoft.js.map +1 -0
- package/dist/src/browser/adapters/x.d.ts +3 -0
- package/dist/src/browser/adapters/x.d.ts.map +1 -0
- package/dist/src/browser/adapters/x.js +19 -0
- package/dist/src/browser/adapters/x.js.map +1 -0
- package/dist/src/browser/dashboard/api-types.d.ts +50 -0
- package/dist/src/browser/dashboard/api-types.d.ts.map +1 -0
- package/dist/src/browser/dashboard/api-types.js +14 -0
- package/dist/src/browser/dashboard/api-types.js.map +1 -0
- package/dist/src/browser/dashboard/server.d.ts +9 -0
- package/dist/src/browser/dashboard/server.d.ts.map +1 -0
- package/dist/src/browser/dashboard/server.js +62 -0
- package/dist/src/browser/dashboard/server.js.map +1 -0
- package/dist/src/browser/dashboard/ui.html +1811 -0
- package/dist/src/browser/workflow/builtin-handlers.d.ts +3 -0
- package/dist/src/browser/workflow/builtin-handlers.d.ts.map +1 -0
- package/dist/src/browser/workflow/builtin-handlers.js +343 -0
- package/dist/src/browser/workflow/builtin-handlers.js.map +1 -0
- package/dist/src/browser/workflow/engine.d.ts +15 -0
- package/dist/src/browser/workflow/engine.d.ts.map +1 -0
- package/dist/src/browser/workflow/engine.js +127 -0
- package/dist/src/browser/workflow/engine.js.map +1 -0
- package/dist/src/browser/workflow/expression.d.ts +4 -0
- package/dist/src/browser/workflow/expression.d.ts.map +1 -0
- package/dist/src/browser/workflow/expression.js +64 -0
- package/dist/src/browser/workflow/expression.js.map +1 -0
- package/dist/src/browser/workflow/store.d.ts +24 -0
- package/dist/src/browser/workflow/store.d.ts.map +1 -0
- package/dist/src/browser/workflow/store.js +145 -0
- package/dist/src/browser/workflow/store.js.map +1 -0
- package/dist/src/browser/workflow/types.d.ts +48 -0
- package/dist/src/browser/workflow/types.d.ts.map +1 -0
- package/dist/src/browser/workflow/types.js +2 -0
- package/dist/src/browser/workflow/types.js.map +1 -0
- package/dist/src/commands/browse-action.d.ts +4 -0
- package/dist/src/commands/browse-action.d.ts.map +1 -0
- package/dist/src/commands/browse-action.js +151 -0
- package/dist/src/commands/browse-action.js.map +1 -0
- package/dist/src/commands/browse-platform.d.ts +4 -0
- package/dist/src/commands/browse-platform.d.ts.map +1 -0
- package/dist/src/commands/browse-platform.js +117 -0
- package/dist/src/commands/browse-platform.js.map +1 -0
- package/dist/src/commands/browse-workflow.d.ts +4 -0
- package/dist/src/commands/browse-workflow.d.ts.map +1 -0
- package/dist/src/commands/browse-workflow.js +153 -0
- package/dist/src/commands/browse-workflow.js.map +1 -0
- package/dist/src/commands/browse.d.ts +10 -6
- package/dist/src/commands/browse.d.ts.map +1 -1
- package/dist/src/commands/browse.js +11 -2154
- package/dist/src/commands/browse.js.map +1 -1
- package/dist/src/commands/design-detect.d.ts +21 -0
- package/dist/src/commands/design-detect.d.ts.map +1 -0
- package/dist/src/commands/design-detect.js +127 -0
- package/dist/src/commands/design-detect.js.map +1 -0
- package/dist/src/commands/design-palette.d.ts +22 -0
- package/dist/src/commands/design-palette.d.ts.map +1 -0
- package/dist/src/commands/design-palette.js +539 -0
- package/dist/src/commands/design-palette.js.map +1 -0
- package/dist/src/commands/hooks-core-commands.d.ts +10 -0
- package/dist/src/commands/hooks-core-commands.d.ts.map +1 -0
- package/dist/src/commands/hooks-core-commands.js +377 -0
- package/dist/src/commands/hooks-core-commands.js.map +1 -0
- package/dist/src/commands/hooks-coverage-commands.d.ts +12 -0
- package/dist/src/commands/hooks-coverage-commands.d.ts.map +1 -0
- package/dist/src/commands/hooks-coverage-commands.js +1217 -0
- package/dist/src/commands/hooks-coverage-commands.js.map +1 -0
- package/dist/src/commands/hooks-coverage-utils.d.ts +42 -0
- package/dist/src/commands/hooks-coverage-utils.d.ts.map +1 -0
- package/dist/src/commands/hooks-coverage-utils.js +220 -0
- package/dist/src/commands/hooks-coverage-utils.js.map +1 -0
- package/dist/src/commands/hooks-extended-commands.d.ts +14 -0
- package/dist/src/commands/hooks-extended-commands.d.ts.map +1 -0
- package/dist/src/commands/hooks-extended-commands.js +579 -0
- package/dist/src/commands/hooks-extended-commands.js.map +1 -0
- package/dist/src/commands/hooks-formatting.d.ts +13 -0
- package/dist/src/commands/hooks-formatting.d.ts.map +1 -0
- package/dist/src/commands/hooks-formatting.js +42 -0
- package/dist/src/commands/hooks-formatting.js.map +1 -0
- package/dist/src/commands/hooks-routing-commands.d.ts +15 -0
- package/dist/src/commands/hooks-routing-commands.d.ts.map +1 -0
- package/dist/src/commands/hooks-routing-commands.js +723 -0
- package/dist/src/commands/hooks-routing-commands.js.map +1 -0
- package/dist/src/commands/hooks-workers.d.ts +9 -0
- package/dist/src/commands/hooks-workers.d.ts.map +1 -0
- package/dist/src/commands/hooks-workers.js +782 -0
- package/dist/src/commands/hooks-workers.js.map +1 -0
- package/dist/src/commands/hooks.d.ts +8 -0
- package/dist/src/commands/hooks.d.ts.map +1 -1
- package/dist/src/commands/hooks.js +179 -4103
- package/dist/src/commands/hooks.js.map +1 -1
- package/dist/src/commands/index.d.ts +1 -0
- package/dist/src/commands/index.d.ts.map +1 -1
- package/dist/src/commands/index.js +6 -0
- package/dist/src/commands/index.js.map +1 -1
- package/dist/src/commands/org.d.ts.map +1 -1
- package/dist/src/commands/org.js +14 -15
- package/dist/src/commands/org.js.map +1 -1
- package/dist/src/commands/tokens.d.ts.map +1 -1
- package/dist/src/commands/tokens.js +77 -1
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +18 -8
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/settings-generator.d.ts.map +1 -1
- package/dist/src/init/settings-generator.js +39 -5
- package/dist/src/init/settings-generator.js.map +1 -1
- package/dist/src/init/statusline-generator.d.ts.map +1 -1
- package/dist/src/init/statusline-generator.js +25 -5
- package/dist/src/init/statusline-generator.js.map +1 -1
- package/dist/src/mcp-tools/browser-tools.d.ts +3 -5
- package/dist/src/mcp-tools/browser-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/browser-tools.js +619 -326
- package/dist/src/mcp-tools/browser-tools.js.map +1 -1
- package/dist/src/mcp-tools/hooks-embedding.d.ts +161 -0
- package/dist/src/mcp-tools/hooks-embedding.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-embedding.js +506 -0
- package/dist/src/mcp-tools/hooks-embedding.js.map +1 -0
- package/dist/src/mcp-tools/hooks-intelligence.d.ts +26 -0
- package/dist/src/mcp-tools/hooks-intelligence.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-intelligence.js +1328 -0
- package/dist/src/mcp-tools/hooks-intelligence.js.map +1 -0
- package/dist/src/mcp-tools/hooks-routing.d.ts +27 -0
- package/dist/src/mcp-tools/hooks-routing.d.ts.map +1 -0
- package/dist/src/mcp-tools/hooks-routing.js +1591 -0
- package/dist/src/mcp-tools/hooks-routing.js.map +1 -0
- package/dist/src/mcp-tools/hooks-tools.d.ts +3 -38
- package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.js +5 -3393
- package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/monograph-tools.js +24 -14
- package/dist/src/mcp-tools/monograph-tools.js.map +1 -1
- package/dist/src/mcp-tools/workflow-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/workflow-tools.js +54 -1
- package/dist/src/mcp-tools/workflow-tools.js.map +1 -1
- package/dist/src/memory/embedding-operations.d.ts +58 -0
- package/dist/src/memory/embedding-operations.d.ts.map +1 -0
- package/dist/src/memory/embedding-operations.js +299 -0
- package/dist/src/memory/embedding-operations.js.map +1 -0
- package/dist/src/memory/ewc-consolidation.d.ts.map +1 -1
- package/dist/src/memory/ewc-consolidation.js +37 -3
- package/dist/src/memory/ewc-consolidation.js.map +1 -1
- package/dist/src/memory/hnsw-operations.d.ts +130 -0
- package/dist/src/memory/hnsw-operations.d.ts.map +1 -0
- package/dist/src/memory/hnsw-operations.js +400 -0
- package/dist/src/memory/hnsw-operations.js.map +1 -0
- package/dist/src/memory/intelligence.d.ts.map +1 -1
- package/dist/src/memory/intelligence.js +42 -23
- package/dist/src/memory/intelligence.js.map +1 -1
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +52 -8
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/memory/memory-crud.d.ts +67 -0
- package/dist/src/memory/memory-crud.d.ts.map +1 -0
- package/dist/src/memory/memory-crud.js +415 -0
- package/dist/src/memory/memory-crud.js.map +1 -0
- package/dist/src/memory/memory-initializer.d.ts +9 -322
- package/dist/src/memory/memory-initializer.d.ts.map +1 -1
- package/dist/src/memory/memory-initializer.js +17 -1794
- package/dist/src/memory/memory-initializer.js.map +1 -1
- package/dist/src/memory/memory-migrations.d.ts +30 -0
- package/dist/src/memory/memory-migrations.d.ts.map +1 -0
- package/dist/src/memory/memory-migrations.js +134 -0
- package/dist/src/memory/memory-migrations.js.map +1 -0
- package/dist/src/memory/memory-read.d.ts +78 -0
- package/dist/src/memory/memory-read.d.ts.map +1 -0
- package/dist/src/memory/memory-read.js +331 -0
- package/dist/src/memory/memory-read.js.map +1 -0
- package/dist/src/memory/memory-schema.d.ts +13 -0
- package/dist/src/memory/memory-schema.d.ts.map +1 -0
- package/dist/src/memory/memory-schema.js +167 -0
- package/dist/src/memory/memory-schema.js.map +1 -0
- package/dist/src/memory/sona-optimizer.d.ts.map +1 -1
- package/dist/src/memory/sona-optimizer.js +37 -4
- package/dist/src/memory/sona-optimizer.js.map +1 -1
- package/dist/src/monovector/route-outcomes.d.ts.map +1 -1
- package/dist/src/monovector/route-outcomes.js +16 -6
- package/dist/src/monovector/route-outcomes.js.map +1 -1
- package/dist/src/pricing/model-pricing.d.ts +41 -0
- package/dist/src/pricing/model-pricing.d.ts.map +1 -0
- package/dist/src/pricing/model-pricing.js +61 -0
- package/dist/src/pricing/model-pricing.js.map +1 -0
- package/dist/src/ui/.monomind/capture/active-run.json +1 -0
- package/dist/src/ui/.monomind/orgs/system-trial-qa/runs/real-events-1782290897.convs.jsonl +3 -0
- package/dist/src/ui/.monomind/orgs/system-trial-qa/runs/real-events-1782290897.jsonl +11 -0
- package/dist/src/ui/.monomind/orgs/system-trial-qa/runs/rigid-qa-restart-1782288201.jsonl +540 -0
- package/dist/src/ui/.monomind/orgs/system-trial-qa-threads.jsonl +3 -0
- package/dist/src/ui/.monomind/orgs/test-event-fix/runs/rigid-qa-restart-1782288201.jsonl +2 -0
- package/dist/src/ui/MODULARIZATION_PLAN.md +79 -0
- package/dist/src/ui/collector.mjs +23 -13
- package/dist/src/ui/dashboard.html +1652 -13
- package/dist/src/ui/data/known-projects.json +1 -0
- package/dist/src/ui/data/mastermind-events.jsonl +553 -0
- package/dist/src/ui/data/sessions/_index.json +1 -0
- package/dist/src/ui/data/sessions/final-sess-001.jsonl +542 -0
- package/dist/src/ui/data/unknown-events.jsonl +1 -0
- package/dist/src/ui/orgs.html +154 -10
- package/dist/src/ui/server.mjs +1131 -168
- package/dist/src/ui/sse-manager.mjs +119 -0
- package/dist/src/update/checker.js +1 -1
- package/dist/src/update/checker.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/workflow/builtin-handlers.js +321 -0
- package/dist/workflow/engine.js +253 -0
- package/dist/workflow/expression.js +98 -0
- package/dist/workflow/types.js +2 -0
- package/package.json +8 -5
|
@@ -286,7 +286,9 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
|
|
|
286
286
|
.oi-goal { font-size: 11px; color: var(--text-lo); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
287
287
|
.oi-chips { display: flex; gap: 5px; margin-top: 5px; flex-wrap: wrap; }
|
|
288
288
|
.oi-chip { font-size: 10px; padding: 1px 6px; border-radius: 8px; background: var(--surface-hi); color: var(--text-lo); }
|
|
289
|
-
.oi-chip.live
|
|
289
|
+
.oi-chip.live { background: oklch(65% 0.15 150 / 0.12); color: var(--green); }
|
|
290
|
+
.oi-chip.quiet { background: oklch(70% 0.12 90 / 0.12); color: oklch(78% 0.15 90); }
|
|
291
|
+
.oi-chip.stale { background: oklch(60% 0.15 25 / 0.12); color: oklch(70% 0.15 25); }
|
|
290
292
|
|
|
291
293
|
/* detail pane */
|
|
292
294
|
#org-detail-pane { flex: 1; display: flex; flex-direction: column; overflow: hidden; min-width: 0; position: relative; }
|
|
@@ -297,8 +299,10 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
|
|
|
297
299
|
#org-detail-head { padding: 12px 18px 11px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
|
|
298
300
|
.odh-name { font-size: 16px; font-weight: 600; color: var(--text-hi); }
|
|
299
301
|
.odh-badge { font-size: 11px; padding: 2px 8px; border-radius: 8px; }
|
|
300
|
-
.odh-badge.idle
|
|
301
|
-
.odh-badge.live
|
|
302
|
+
.odh-badge.idle { background: var(--surface-hi); color: var(--text-lo); }
|
|
303
|
+
.odh-badge.live { background: oklch(65% 0.15 150 / 0.12); color: var(--green); }
|
|
304
|
+
.odh-badge.quiet { background: oklch(70% 0.12 90 / 0.12); color: oklch(78% 0.15 90); }
|
|
305
|
+
.odh-badge.stale { background: oklch(60% 0.15 25 / 0.12); color: oklch(70% 0.15 25); }
|
|
302
306
|
.odh-pill { font-size: 11px; padding: 2px 8px; border-radius: 8px; background: var(--surface); color: var(--text-lo); border: 1px solid var(--border); }
|
|
303
307
|
.odh-right { margin-left: auto; display: flex; gap: 6px; }
|
|
304
308
|
|
|
@@ -336,6 +340,15 @@ html, body { height: 100%; background: var(--bg); color: var(--text-hi); font-fa
|
|
|
336
340
|
#odt-chat-feed::-webkit-scrollbar { width:4px; }
|
|
337
341
|
#odt-chat-feed::-webkit-scrollbar-thumb { background:var(--border); border-radius:2px; }
|
|
338
342
|
#odt-chat-empty { font-size:11px; color:var(--text-lo); text-align:center; padding:32px 0; line-height:2; }
|
|
343
|
+
.odt-mode-btn {
|
|
344
|
+
font-family: inherit; font-size: 7px; letter-spacing: 1.5px;
|
|
345
|
+
padding: 2px 7px; border-radius: 3px; cursor: pointer;
|
|
346
|
+
border: 1px solid oklch(62% 0.2 186 / 0.25);
|
|
347
|
+
color: oklch(45% 0.07 186); background: transparent;
|
|
348
|
+
transition: all 0.12s;
|
|
349
|
+
}
|
|
350
|
+
.odt-mode-btn:hover { color: oklch(65% 0.12 186); border-color: oklch(62% 0.2 186 / 0.5); }
|
|
351
|
+
.odt-mode-btn.active { color: oklch(72% 0.18 186); border-color: oklch(62% 0.2 186 / 0.7); background: oklch(62% 0.2 186 / 0.12); }
|
|
339
352
|
.cv-excerpt-banner { flex-shrink:0; padding:7px 16px 8px; background:oklch(13% 0.01 295); border-bottom:1px solid var(--border); display:none; }
|
|
340
353
|
.cv-excerpt-banner.visible { display:block; }
|
|
341
354
|
.cv-excerpt-label { font-size:8px; letter-spacing:2px; color:var(--text-xs); text-transform:uppercase; margin-bottom:4px; }
|
|
@@ -1339,6 +1352,150 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1339
1352
|
#mem-modal-ta:focus { border-color: var(--accent); }
|
|
1340
1353
|
.mem-modal-btns { display: flex; justify-content: flex-end; gap: 8px; }
|
|
1341
1354
|
|
|
1355
|
+
/* ── playbook view tabs ────────────────────────────────────── */
|
|
1356
|
+
.wf-pane { display: none; }
|
|
1357
|
+
.wf-pane.active { display: block; }
|
|
1358
|
+
|
|
1359
|
+
/* ── playbook builder ──────────────────────────────────────── */
|
|
1360
|
+
#wf-tab-builder { display: none; height: calc(100vh - 180px); min-height: 400px; }
|
|
1361
|
+
#wf-tab-builder.active { display: flex; flex-direction: column; }
|
|
1362
|
+
#pb-toolbar { display: flex; gap: 8px; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--border); flex-shrink: 0; }
|
|
1363
|
+
#pb-toolbar .btn { font-size: 11px; padding: 4px 10px; }
|
|
1364
|
+
#pb-name-input { background: var(--surface-hi); border: 1px solid var(--border); border-radius: 5px; color: var(--text-hi); font-size: 12px; padding: 4px 8px; flex: 1; max-width: 240px; outline: none; }
|
|
1365
|
+
#pb-name-input:focus { border-color: var(--accent); }
|
|
1366
|
+
#pb-run-status { font-size: 11px; color: var(--text-lo); margin-left: 4px; }
|
|
1367
|
+
#pb-editor { display: flex; flex: 1; gap: 0; overflow: hidden; }
|
|
1368
|
+
#pb-palette { width: 180px; flex-shrink: 0; border-right: 1px solid var(--border); overflow-y: auto; padding: 8px 0; }
|
|
1369
|
+
.pb-cat-hdr { font-size: 10px; font-weight: 700; color: var(--text-xs); letter-spacing: 0.06em; text-transform: uppercase; padding: 6px 12px 3px; cursor: pointer; display: flex; align-items: center; gap: 6px; }
|
|
1370
|
+
.pb-cat-hdr:hover { color: var(--text-lo); }
|
|
1371
|
+
.pb-cat-arrow { font-size: 8px; transition: transform 0.15s; }
|
|
1372
|
+
.pb-cat-body { overflow: hidden; }
|
|
1373
|
+
.pb-cat-body.collapsed { display: none; }
|
|
1374
|
+
.pb-node-btn { width: 100%; text-align: left; background: none; border: none; font-size: 11px; color: var(--text-lo); padding: 4px 12px 4px 20px; cursor: pointer; display: flex; align-items: center; gap: 6px; }
|
|
1375
|
+
.pb-node-btn:hover { background: var(--surface-hi); color: var(--text-hi); }
|
|
1376
|
+
.pb-node-btn .pb-node-icon { font-size: 13px; width: 16px; text-align: center; flex-shrink: 0; }
|
|
1377
|
+
#pb-canvas-wrap { flex: 1; position: relative; overflow: hidden; background: var(--bg); }
|
|
1378
|
+
#pb-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
|
|
1379
|
+
#pb-svg { position: absolute; top: 0; left: 0; pointer-events: none; }
|
|
1380
|
+
#pb-empty { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--text-xs); font-size: 12px; gap: 8px; pointer-events: none; }
|
|
1381
|
+
.pb-node-card { position: absolute; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; width: 160px; min-height: 56px; cursor: grab; user-select: none; transition: box-shadow 0.12s, border-color 0.12s; }
|
|
1382
|
+
.pb-node-card:active { cursor: grabbing; }
|
|
1383
|
+
.pb-node-card.selected { border-color: var(--accent); box-shadow: 0 0 0 2px oklch(72% 0.18 75 / 0.25); }
|
|
1384
|
+
.pb-node-card.ns-running { border-color: var(--accent); }
|
|
1385
|
+
.pb-node-card.ns-completed { border-color: var(--green); }
|
|
1386
|
+
.pb-node-card.ns-failed { border-color: var(--red); }
|
|
1387
|
+
.pb-node-hdr { display: flex; align-items: center; gap: 6px; padding: 8px 10px 6px; border-bottom: 1px solid var(--border); }
|
|
1388
|
+
.pb-node-type-icon { font-size: 14px; }
|
|
1389
|
+
.pb-node-title { font-size: 11px; font-weight: 600; color: var(--text-hi); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1390
|
+
.pb-node-del { font-size: 12px; color: var(--text-xs); cursor: pointer; padding: 0 2px; line-height: 1; background: none; border: none; }
|
|
1391
|
+
.pb-node-del:hover { color: var(--red); }
|
|
1392
|
+
.pb-node-body { padding: 6px 10px 8px; font-size: 10px; color: var(--text-lo); line-height: 1.5; }
|
|
1393
|
+
.pb-node-status { display: flex; align-items: center; gap: 4px; margin-bottom: 2px; }
|
|
1394
|
+
.pb-ns-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--border); flex-shrink: 0; }
|
|
1395
|
+
.pb-ns-dot.running { background: var(--accent); animation: pb-pulse 1s infinite; }
|
|
1396
|
+
.pb-ns-dot.completed{ background: var(--green); }
|
|
1397
|
+
.pb-ns-dot.failed { background: var(--red); }
|
|
1398
|
+
.pb-node-conn-btn { position: absolute; right: -10px; top: 50%; transform: translateY(-50%); width: 18px; height: 18px; border-radius: 50%; background: var(--accent-dim); border: 2px solid var(--accent); cursor: pointer; z-index: 10; display: flex; align-items: center; justify-content: center; font-size: 8px; color: var(--accent); }
|
|
1399
|
+
.pb-node-conn-btn:hover { background: var(--accent); color: #fff; }
|
|
1400
|
+
#pb-config { width: 220px; flex-shrink: 0; border-left: 1px solid var(--border); overflow-y: auto; padding: 12px; }
|
|
1401
|
+
#pb-config-title { font-size: 11px; font-weight: 700; color: var(--text-hi); margin-bottom: 10px; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
|
|
1402
|
+
.pb-cfg-row { margin-bottom: 10px; }
|
|
1403
|
+
.pb-cfg-lbl { font-size: 10px; color: var(--text-lo); margin-bottom: 3px; font-weight: 600; }
|
|
1404
|
+
.pb-cfg-inp { width: 100%; background: var(--bg); border: 1px solid var(--border); border-radius: 4px; color: var(--text-hi); font-size: 11px; padding: 4px 7px; outline: none; resize: vertical; font-family: var(--mono); }
|
|
1405
|
+
.pb-cfg-inp:focus { border-color: var(--accent); }
|
|
1406
|
+
select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
|
|
1407
|
+
@keyframes pb-pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
|
|
1408
|
+
|
|
1409
|
+
/* ── systems grid ──────────────────────────────────────────── */
|
|
1410
|
+
#wf-systems-grid { display: block; }
|
|
1411
|
+
/* ── monoagent connections tile grid ─────────────────────────── */
|
|
1412
|
+
.ma-cat-hdr { display:flex; align-items:center; gap:10px; margin-bottom:10px; }
|
|
1413
|
+
.ma-cat-label { font-family:var(--mono); font-size:10px; font-weight:700; color:var(--text-lo); text-transform:uppercase; letter-spacing:2px; white-space:nowrap; }
|
|
1414
|
+
.ma-cat-line { flex:1; height:1px; background:var(--border); }
|
|
1415
|
+
.ma-cat-count { font-family:var(--mono); font-size:9.5px; color:var(--text-xs); }
|
|
1416
|
+
.ma-tiles { display:grid; grid-template-columns:repeat(auto-fill,minmax(96px,1fr)); gap:8px; margin-bottom:24px; }
|
|
1417
|
+
.ma-tile { background:var(--surface); border:1px solid var(--border); border-radius:10px; padding:16px 8px 12px; cursor:pointer; display:flex; flex-direction:column; align-items:center; gap:6px; transition:border-color 0.15s,box-shadow 0.15s; user-select:none; }
|
|
1418
|
+
.ma-tile:hover { border-color:var(--border-hi); }
|
|
1419
|
+
.ma-tile.connected { border-color:oklch(65% 0.15 150 / 0.5); box-shadow:0 0 10px oklch(65% 0.15 150 / 0.12); }
|
|
1420
|
+
.ma-tile-icon { font-size:22px; line-height:1; }
|
|
1421
|
+
.ma-tile-name { font-family:var(--mono); font-size:10px; font-weight:600; color:var(--text-mid); text-align:center; line-height:1.3; }
|
|
1422
|
+
.ma-tile-dot { width:6px; height:6px; border-radius:50%; background:var(--border); }
|
|
1423
|
+
.ma-tile.connected .ma-tile-dot { background:var(--green); box-shadow:0 0 5px oklch(65% 0.15 150 / 0.7); }
|
|
1424
|
+
.ma-tile.expired .ma-tile-dot { background:#fbbf24; box-shadow:0 0 5px rgba(251,191,36,0.5); }
|
|
1425
|
+
.ma-tile-acct { font-family:var(--mono); font-size:9px; color:var(--text-xs); max-width:84px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
1426
|
+
/* modal */
|
|
1427
|
+
.ma-modal-bg { position:fixed; inset:0; background:rgba(0,0,0,0.6); z-index:1200; display:flex; align-items:center; justify-content:center; padding:24px; }
|
|
1428
|
+
.ma-modal { width:100%; max-width:440px; background:var(--surface); border:1px solid var(--border-hi); border-radius:14px; box-shadow:0 8px 40px rgba(0,0,0,0.5); overflow:hidden; }
|
|
1429
|
+
.ma-modal-hdr { display:flex; align-items:center; justify-content:space-between; padding:16px 20px 12px; border-bottom:1px solid var(--border); }
|
|
1430
|
+
.ma-modal-hdr-left { display:flex; align-items:center; gap:10px; }
|
|
1431
|
+
.ma-modal-title { font-family:var(--mono); font-size:13px; font-weight:700; color:var(--text-hi); }
|
|
1432
|
+
.ma-modal-status { font-family:var(--mono); font-size:10px; text-transform:uppercase; letter-spacing:1px; }
|
|
1433
|
+
.ma-modal-body { padding:16px 20px 20px; display:flex; flex-direction:column; gap:14px; }
|
|
1434
|
+
.ma-modal-info { background:var(--surface-hi); border:1px solid var(--border); border-radius:8px; padding:10px 14px; display:flex; flex-direction:column; gap:8px; }
|
|
1435
|
+
.ma-modal-row { display:flex; justify-content:space-between; align-items:center; }
|
|
1436
|
+
.ma-modal-lbl { font-family:var(--mono); font-size:9.5px; color:var(--text-xs); text-transform:uppercase; letter-spacing:1px; }
|
|
1437
|
+
.ma-modal-val { font-family:var(--mono); font-size:11px; color:var(--text-mid); }
|
|
1438
|
+
.ma-modal-val.ok { color:var(--green); }
|
|
1439
|
+
.ma-modal-val.warn { color:#fbbf24; }
|
|
1440
|
+
.ma-field-label { font-family:var(--mono); font-size:9.5px; color:var(--text-xs); text-transform:uppercase; letter-spacing:1.5px; margin-bottom:4px; }
|
|
1441
|
+
.ma-field-input { width:100%; padding:7px 10px; border:1px solid var(--border-hi); border-radius:6px; background:var(--surface-hi); color:var(--text-hi); font-family:var(--mono); font-size:11px; box-sizing:border-box; outline:none; }
|
|
1442
|
+
.ma-field-input:focus { border-color:var(--accent); }
|
|
1443
|
+
.ma-steps { background:var(--surface-hi); border:1px solid var(--border); border-radius:8px; padding:8px 10px; max-height:130px; overflow-y:auto; display:flex; flex-direction:column; gap:5px; }
|
|
1444
|
+
.ma-step { display:flex; align-items:center; gap:8px; }
|
|
1445
|
+
.ma-step-dot { width:5px; height:5px; border-radius:50%; background:var(--text-xs); flex-shrink:0; }
|
|
1446
|
+
.ma-step-dot.ok { background:var(--green); }
|
|
1447
|
+
.ma-step-dot.err { background:oklch(60% 0.18 25); }
|
|
1448
|
+
.ma-step-txt { font-family:var(--mono); font-size:10px; color:var(--text-lo); }
|
|
1449
|
+
.ma-step-txt.ok { color:var(--green); }
|
|
1450
|
+
.ma-step-txt.err { color:oklch(60% 0.18 25); }
|
|
1451
|
+
.ma-btns { display:flex; gap:8px; flex-wrap:wrap; }
|
|
1452
|
+
.ma-btn { padding:6px 14px; border-radius:6px; border:none; font-family:var(--mono); font-size:11px; font-weight:600; cursor:pointer; display:flex; align-items:center; gap:5px; transition:opacity 0.15s; }
|
|
1453
|
+
.ma-btn:disabled { opacity:0.45; cursor:default; }
|
|
1454
|
+
.ma-btn-primary { background:var(--accent); color:#fff; }
|
|
1455
|
+
.ma-btn-primary:hover:not(:disabled) { opacity:0.85; }
|
|
1456
|
+
.ma-btn-secondary { background:var(--surface-hi); color:var(--text-mid); border:1px solid var(--border-hi); }
|
|
1457
|
+
.ma-btn-secondary:hover:not(:disabled) { border-color:var(--accent); color:var(--accent); }
|
|
1458
|
+
.ma-btn-danger { background:oklch(17% 0.015 25); color:oklch(60% 0.18 25); border:1px solid oklch(60% 0.18 25 / 0.3); }
|
|
1459
|
+
.ma-btn-danger:hover:not(:disabled) { opacity:0.85; }
|
|
1460
|
+
.ma-btn-ghost { background:none; color:var(--text-lo); border:1px solid var(--border); }
|
|
1461
|
+
.ma-btn-ghost:hover:not(:disabled) { color:var(--text-mid); }
|
|
1462
|
+
.ma-msg { font-family:var(--mono); font-size:11px; padding:7px 12px; border-radius:6px; }
|
|
1463
|
+
.ma-msg.ok { background:oklch(65% 0.15 150 / 0.08); border:1px solid oklch(65% 0.15 150 / 0.25); color:var(--green); }
|
|
1464
|
+
.ma-msg.err { background:oklch(60% 0.18 25 / 0.08); border:1px solid oklch(60% 0.18 25 / 0.25); color:oklch(60% 0.18 25); }
|
|
1465
|
+
.ma-method-tabs { display:flex; gap:6px; flex-wrap:wrap; }
|
|
1466
|
+
.ma-method-tab { font-family:var(--mono); font-size:10px; padding:3px 10px; border-radius:4px; background:var(--surface-hi); border:1px solid var(--border); color:var(--text-lo); cursor:pointer; }
|
|
1467
|
+
.ma-method-tab.active { background:var(--accent-dim); border-color:var(--accent); color:var(--accent); }
|
|
1468
|
+
.ma-browser-hint { font-size:11px; color:var(--text-lo); line-height:1.5; }
|
|
1469
|
+
|
|
1470
|
+
/* ── playbook runs view ────────────────────────────────────── */
|
|
1471
|
+
.wf-run-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; margin-bottom: 10px; }
|
|
1472
|
+
.wf-run-header { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
|
|
1473
|
+
.wf-run-name { font-size: 13px; font-weight: 600; color: var(--text-hi); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1474
|
+
.wf-run-id { font-size: 10px; color: var(--text-xs); font-family: var(--mono); }
|
|
1475
|
+
.wf-status { font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 10px; letter-spacing: 0.04em; flex-shrink: 0; }
|
|
1476
|
+
.wf-status.s-running { background: oklch(72% 0.18 75 / 0.15); color: var(--accent); }
|
|
1477
|
+
.wf-status.s-completed{ background: oklch(65% 0.15 150 / 0.15); color: var(--green); }
|
|
1478
|
+
.wf-status.s-failed { background: oklch(60% 0.18 25 / 0.15); color: var(--red); }
|
|
1479
|
+
.wf-status.s-stopped { background: oklch(42% 0.006 75 / 0.15); color: var(--text-lo); }
|
|
1480
|
+
.wf-run-meta { display: flex; gap: 16px; font-size: 11px; color: var(--text-lo); }
|
|
1481
|
+
.wf-run-meta span { display: flex; align-items: center; gap: 4px; }
|
|
1482
|
+
.wf-run-error { font-size: 11px; color: var(--red); margin-top: 6px; font-family: var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1483
|
+
.wf-progress-bar { height: 3px; background: var(--border); border-radius: 2px; margin-top: 8px; overflow: hidden; }
|
|
1484
|
+
.wf-progress-bar-fill { height: 100%; background: var(--accent); border-radius: 2px; transition: width 0.3s; }
|
|
1485
|
+
#wf-runs-list .loading-txt, #wf-defs-list .loading-txt { padding: 40px 0; text-align: center; color: var(--text-lo); }
|
|
1486
|
+
.wf-def-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; margin-bottom: 8px; display: flex; align-items: flex-start; gap: 14px; cursor: default; transition: border-color 0.12s; }
|
|
1487
|
+
.wf-def-card:hover { border-color: var(--accent); }
|
|
1488
|
+
.wf-def-icon { font-size: 22px; flex-shrink: 0; margin-top: 1px; }
|
|
1489
|
+
.wf-def-body { flex: 1; min-width: 0; }
|
|
1490
|
+
.wf-def-name { font-size: 13px; font-weight: 600; color: var(--text-hi); margin-bottom: 2px; }
|
|
1491
|
+
.wf-def-desc { font-size: 12px; color: var(--text-lo); margin-bottom: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1492
|
+
.wf-def-meta { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
1493
|
+
.wf-def-tag { font-size: 10px; padding: 2px 7px; border-radius: 8px; background: var(--surface-hi); color: var(--text-lo); font-family: var(--mono); }
|
|
1494
|
+
.wf-def-run-btn { font-size: 11px; padding: 4px 10px; background: var(--accent-dim); color: var(--accent); border: 1px solid oklch(72% 0.18 75 / 0.3); border-radius: 5px; cursor: pointer; white-space: nowrap; flex-shrink: 0; margin-top: 2px; }
|
|
1495
|
+
.wf-def-run-btn:hover { background: oklch(72% 0.18 75 / 0.2); }
|
|
1496
|
+
#wf-lib-empty { padding: 40px 0; text-align: center; color: var(--text-lo); font-size: 13px; }
|
|
1497
|
+
#wf-empty { padding: 40px 0; text-align: center; color: var(--text-lo); font-size: 13px; }
|
|
1498
|
+
|
|
1342
1499
|
/* Mastermind overlay */
|
|
1343
1500
|
#mm-overlay { display: none; position: fixed; top: 0; right: 0; bottom: 0; width: min(680px, 100vw); background: var(--surface); border-left: 1px solid oklch(65% 0.16 295 / 0.3); z-index: 500; flex-direction: column; box-shadow: -8px 0 32px oklch(0% 0 0 / 0.4); transition: transform 0.3s cubic-bezier(0.16,1,0.3,1); transform: translateX(100%); }
|
|
1344
1501
|
#mm-overlay.open { display: flex; transform: translateX(0); }
|
|
@@ -1409,6 +1566,10 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1409
1566
|
<div class="nav-item" data-view="monograph">
|
|
1410
1567
|
<span class="ico">⬡</span><span class="lbl">Monograph</span>
|
|
1411
1568
|
</div>
|
|
1569
|
+
<div class="nav-item" data-view="playbooks">
|
|
1570
|
+
<span class="ico">⚡</span><span class="lbl">Playbooks</span>
|
|
1571
|
+
<span class="bdg" id="bdg-playbooks">—</span>
|
|
1572
|
+
</div>
|
|
1412
1573
|
</div>
|
|
1413
1574
|
</div>
|
|
1414
1575
|
<div class="nav-no-proj" id="nav-no-proj-hint">Select a project above</div>
|
|
@@ -1838,6 +1999,7 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
1838
1999
|
<button class="btn" onclick="v2ExportOrg()" title="Export org as JSON file" style="color:var(--text-lo);border-color:var(--border)">Export</button>
|
|
1839
2000
|
<button class="btn" onclick="v2ImportOrgTrigger()" title="Import org from JSON file" style="color:var(--text-lo);border-color:var(--border)">Import</button>
|
|
1840
2001
|
<button class="btn" id="org-copy-btn" onclick="v2ShowCopyOrgDialog()" style="color:var(--accent);border-color:var(--accent)">Copy to…</button>
|
|
2002
|
+
<button class="btn" id="org-mark-complete-btn" onclick="v2MarkOrgComplete()" style="display:none;color:oklch(70% 0.15 25);border-color:oklch(60% 0.15 25 / 0.4)" title="Write run:complete to clear STALE state">Mark Complete</button>
|
|
1841
2003
|
<button class="btn" id="org-stop-btn" onclick="v2StopOrg()" style="display:none;color:var(--red);border-color:oklch(60% 0.18 25 / 0.4)">Stop</button>
|
|
1842
2004
|
<button class="btn" id="org-delete-btn" onclick="v2DeleteOrg()" title="Permanently delete this org and all its data" style="color:var(--red);border-color:oklch(60% 0.18 25 / 0.4)">Delete</button>
|
|
1843
2005
|
</div>
|
|
@@ -2080,6 +2242,83 @@ textarea.sess-note-input:focus { border-color:var(--accent); }
|
|
|
2080
2242
|
</div>
|
|
2081
2243
|
</div>
|
|
2082
2244
|
|
|
2245
|
+
<!-- PLAYBOOKS -->
|
|
2246
|
+
<div class="view" id="view-playbooks">
|
|
2247
|
+
<div class="vscroll">
|
|
2248
|
+
<div class="pg-title">Playbooks</div>
|
|
2249
|
+
<div class="pg-sub">Browser automation — connected systems & run history</div>
|
|
2250
|
+
<!-- Tab bar -->
|
|
2251
|
+
<div class="mg-tab-bar" style="margin-top:14px">
|
|
2252
|
+
<button class="odt-btn active" data-wftab="systems" onclick="wfSwitchTab('systems')">Systems</button>
|
|
2253
|
+
<button class="odt-btn" data-wftab="library" onclick="wfSwitchTab('library')">Library</button>
|
|
2254
|
+
<button class="odt-btn" data-wftab="builder" onclick="wfSwitchTab('builder')">Builder</button>
|
|
2255
|
+
<button class="odt-btn" data-wftab="runs" onclick="wfSwitchTab('runs')">Runs</button>
|
|
2256
|
+
</div>
|
|
2257
|
+
|
|
2258
|
+
<!-- TAB: SYSTEMS -->
|
|
2259
|
+
<div class="wf-pane active" id="wf-tab-systems">
|
|
2260
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;margin-top:4px">
|
|
2261
|
+
<button class="btn" onclick="loadPlatformSessions()">↺ Refresh</button>
|
|
2262
|
+
</div>
|
|
2263
|
+
<div id="wf-systems-grid"></div>
|
|
2264
|
+
</div>
|
|
2265
|
+
|
|
2266
|
+
<!-- TAB: LIBRARY -->
|
|
2267
|
+
<div class="wf-pane" id="wf-tab-library">
|
|
2268
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:16px;margin-top:4px">
|
|
2269
|
+
<button class="btn" onclick="loadWorkflowDefs()">↺ Refresh</button>
|
|
2270
|
+
<span style="font-size:11px;color:var(--text-lo)">Saved in .monomind/playbooks/</span>
|
|
2271
|
+
</div>
|
|
2272
|
+
<div id="wf-defs-list"><div class="loading-txt">Loading…</div></div>
|
|
2273
|
+
</div>
|
|
2274
|
+
|
|
2275
|
+
<!-- TAB: RUNS -->
|
|
2276
|
+
<div class="wf-pane" id="wf-tab-runs">
|
|
2277
|
+
<div style="display:flex;gap:8px;margin-bottom:16px;margin-top:4px;align-items:center">
|
|
2278
|
+
<button class="btn" onclick="loadWorkflowRuns()">↺ Refresh</button>
|
|
2279
|
+
<select id="wf-status-filter" onchange="renderWorkflowRuns()" style="background:var(--surface);color:var(--text-hi);border:1px solid var(--border);border-radius:4px;padding:4px 8px;font-size:12px">
|
|
2280
|
+
<option value="">All statuses</option>
|
|
2281
|
+
<option value="running">Running</option>
|
|
2282
|
+
<option value="completed">Completed</option>
|
|
2283
|
+
<option value="failed">Failed</option>
|
|
2284
|
+
<option value="stopped">Stopped</option>
|
|
2285
|
+
</select>
|
|
2286
|
+
<span id="wf-count" style="color:var(--text-lo);font-size:12px"></span>
|
|
2287
|
+
</div>
|
|
2288
|
+
<div id="wf-runs-list"><div class="loading-txt">Loading…</div></div>
|
|
2289
|
+
</div>
|
|
2290
|
+
|
|
2291
|
+
<!-- TAB: BUILDER -->
|
|
2292
|
+
<div id="wf-tab-builder">
|
|
2293
|
+
<div id="pb-toolbar">
|
|
2294
|
+
<button class="btn" onclick="pbNew()" title="New playbook">+ New</button>
|
|
2295
|
+
<input id="pb-name-input" type="text" placeholder="Playbook name…" oninput="pbState.playbook.name=this.value">
|
|
2296
|
+
<button class="btn" onclick="pbLoad()" title="Load a playbook from Library">⬆ Load</button>
|
|
2297
|
+
<button class="btn" onclick="pbSave()" title="Save to .monomind/playbooks/">💾 Save</button>
|
|
2298
|
+
<button class="btn" onclick="pbRun()" title="Run via monobrowse dashboard" style="color:var(--accent);border-color:var(--accent)">▶ Run</button>
|
|
2299
|
+
<button class="btn" onclick="pbImportJSON()" title="Import from JSON">⬇ Import</button>
|
|
2300
|
+
<button class="btn" onclick="pbExportJSON()" title="Export to JSON">⬆ Export</button>
|
|
2301
|
+
<span id="pb-run-status"></span>
|
|
2302
|
+
</div>
|
|
2303
|
+
<div id="pb-editor">
|
|
2304
|
+
<div id="pb-palette">
|
|
2305
|
+
<!-- populated by pbRenderPalette() -->
|
|
2306
|
+
</div>
|
|
2307
|
+
<div id="pb-canvas-wrap">
|
|
2308
|
+
<div id="pb-canvas">
|
|
2309
|
+
<svg id="pb-svg" width="100%" height="100%"><defs><marker id="pb-arrow" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--text-xs)"/></marker></defs></svg>
|
|
2310
|
+
<div id="pb-empty"><div style="font-size:32px">⚡</div><div>Drag a node from the left panel onto the canvas</div><div style="font-size:11px">or click a node type to add it</div></div>
|
|
2311
|
+
</div>
|
|
2312
|
+
</div>
|
|
2313
|
+
<div id="pb-config">
|
|
2314
|
+
<div id="pb-config-title">Select a node</div>
|
|
2315
|
+
<div id="pb-config-body"></div>
|
|
2316
|
+
</div>
|
|
2317
|
+
</div>
|
|
2318
|
+
</div>
|
|
2319
|
+
</div>
|
|
2320
|
+
</div>
|
|
2321
|
+
|
|
2083
2322
|
<!-- GLOBAL FEED -->
|
|
2084
2323
|
<div class="view" id="view-global">
|
|
2085
2324
|
<div class="vscroll">
|
|
@@ -2271,7 +2510,7 @@ function switchView(v, { updateHash = true } = {}) {
|
|
|
2271
2510
|
el.classList.toggle('active', el.dataset.view === v));
|
|
2272
2511
|
document.querySelectorAll('.view').forEach(el =>
|
|
2273
2512
|
el.classList.toggle('active', el.id === 'view-' + v));
|
|
2274
|
-
const titles = { now:'Now', projects:'Projects', sessions:'Sessions', loops:'Loops', tokens:'Tokens', memory:'Memory', orgs:'Orgs', monograph:'Monograph', global:'Global Feed', 'global-loops':'Global Loops', 'global-tokens':'Global Tokens', chat:'Global Agent Chat' };
|
|
2513
|
+
const titles = { now:'Now', projects:'Projects', sessions:'Sessions', loops:'Loops', tokens:'Tokens', memory:'Memory', orgs:'Orgs', monograph:'Monograph', playbooks:'Playbooks', global:'Global Feed', 'global-loops':'Global Loops', 'global-tokens':'Global Tokens', chat:'Global Agent Chat' };
|
|
2275
2514
|
document.getElementById('view-title').textContent = titles[v] || v;
|
|
2276
2515
|
const PROJECT = DIR ? shortPath(DIR) : 'monomind';
|
|
2277
2516
|
const VIEW_LABELS = { now: 'Now', sessions: 'Sessions', projects: 'Projects', loops: 'Loops', tokens: 'Tokens', memory: 'Memory', orgs: 'Orgs', monograph: 'Monograph', global: 'Global Feed', 'global-loops': 'Global Loops', 'global-tokens': 'Global Tokens' };
|
|
@@ -2334,6 +2573,7 @@ function renderView(v) {
|
|
|
2334
2573
|
if (v === 'memory') renderMemory();
|
|
2335
2574
|
if (v === 'orgs') renderOrgs();
|
|
2336
2575
|
if (v === 'monograph') loadMonograph();
|
|
2576
|
+
if (v === 'playbooks') { loadPlatformSessions(); loadWorkflowDefs(); loadWorkflowRuns(); }
|
|
2337
2577
|
if (v === 'global') renderGlobalFeed();
|
|
2338
2578
|
if (v === 'global-loops') renderGlobalLoops();
|
|
2339
2579
|
if (v === 'global-tokens') renderGlobalTokens();
|
|
@@ -4299,6 +4539,12 @@ function appendChatViewEvent(ev, animate) {
|
|
|
4299
4539
|
if (currentView === 'loops') renderLoops();
|
|
4300
4540
|
} else if (ev.type === 'file:write') {
|
|
4301
4541
|
el = mkCVFileCard(ev.name || ev.path || '?', ev.path || '', ev.size || 0, ts);
|
|
4542
|
+
} else if (ev.type === 'agent:edit' || ev.type === 'agent:write') {
|
|
4543
|
+
el = mkCVTool('EDIT', ev.payload || ev.file || ev.path || '', '', 'oklch(65% 0.20 270)', ts);
|
|
4544
|
+
} else if (ev.type === 'agent:bash') {
|
|
4545
|
+
el = mkCVTool('BASH', ev.payload || ev.command || '', ev.output || '', 'oklch(72% 0.18 40)', ts);
|
|
4546
|
+
} else if (ev.type === 'agent:browse') {
|
|
4547
|
+
el = mkCVTool('BROWSE', ev.payload || ev.url || '', '', 'oklch(65% 0.15 190)', ts);
|
|
4302
4548
|
} else if (ev.type === 'org:agent:offline') {
|
|
4303
4549
|
el = mkCVSys('◌ ' + esc(ev.title || ev.role || '?') + ' offline' + (ev.org ? ' [' + esc(ev.org) + ']' : ''), ts);
|
|
4304
4550
|
} else if (ev.type === 'org:error') {
|
|
@@ -4317,6 +4563,9 @@ function _cvExcerptText(ev) {
|
|
|
4317
4563
|
if (t === 'intercom' || t === 'org:comms') return (ev.from || ev.role || '?') + ' → ' + (ev.to || '?') + ': ' + (ev.msg || ev.message || '').slice(0, 80);
|
|
4318
4564
|
if (t === 'agent:message') return (ev.agent || ev.name || ev.role || '?') + ': ' + (ev.msg || ev.message || ev.content || '').slice(0, 80);
|
|
4319
4565
|
if (t === 'agent:result') return (ev.agent || ev.from || '?') + ': ' + (ev.result || ev.msg || ev.message || ev.summary || '').slice(0, 80);
|
|
4566
|
+
if (t === 'agent:edit' || t === 'agent:write') return '✏ ' + (ev.payload || ev.file || ev.path || '').replace(/.*\//, '');
|
|
4567
|
+
if (t === 'agent:bash') { const c = ev.payload || ev.command || ''; return '$ ' + c.slice(0, 80); }
|
|
4568
|
+
if (t === 'agent:browse') return '🌐 ' + (ev.payload || ev.url || '').slice(0, 80);
|
|
4320
4569
|
if (t === 'agent:spawn') return 'spawned ' + (ev.to || ev.agent || ev.role || '?') + (ev.task || ev.briefing ? ': ' + (ev.task || ev.briefing || '').slice(0, 50) : '');
|
|
4321
4570
|
if (t === 'org:agent:online') return (ev.title || ev.role || '?') + ' online';
|
|
4322
4571
|
if (t === 'org:agent:offline') return (ev.title || ev.role || '?') + ' offline';
|
|
@@ -4424,6 +4673,23 @@ function mkCVFileCard(name, filePath, size, ts) {
|
|
|
4424
4673
|
return d;
|
|
4425
4674
|
}
|
|
4426
4675
|
|
|
4676
|
+
function mkCVTool(tag, cmd, output, color, ts) {
|
|
4677
|
+
const d = document.createElement('div');
|
|
4678
|
+
d.className = 'cv-msg cv-tool';
|
|
4679
|
+
const shortCmd = cmd.length > 120 ? '…' + cmd.slice(-100) : cmd;
|
|
4680
|
+
const uid = 'tool-' + Math.random().toString(36).slice(2,8);
|
|
4681
|
+
const hasOutput = output && output.trim().length > 0;
|
|
4682
|
+
const shortOut = output && output.length > 400 ? output.slice(0, 400) + '…' : output;
|
|
4683
|
+
d.innerHTML = `<div class="cv-bub" style="border-left:2px solid ${color};align-items:flex-start;flex-direction:column;gap:3px">`+
|
|
4684
|
+
`<div style="display:flex;align-items:center;gap:6px;width:100%">`+
|
|
4685
|
+
`<span class="cv-etype" style="color:${color};flex-shrink:0">${tag}</span>`+
|
|
4686
|
+
`<span class="cv-text" style="font-family:var(--mono);font-size:10px;word-break:break-all">${esc(shortCmd)}</span>`+
|
|
4687
|
+
`<span class="cv-ts" style="margin-left:auto;flex-shrink:0">${ts}</span></div>`+
|
|
4688
|
+
(hasOutput ? `<pre id="${uid}" style="margin:2px 0 0 0;padding:6px 8px;background:oklch(6% 0.01 295);border-radius:4px;font-size:9.5px;line-height:1.5;color:oklch(70% 0.05 295);overflow:auto;max-height:200px;width:100%;box-sizing:border-box;white-space:pre-wrap;word-break:break-word">${esc(shortOut)}</pre>` : '')+
|
|
4689
|
+
`</div>`;
|
|
4690
|
+
return d;
|
|
4691
|
+
}
|
|
4692
|
+
|
|
4427
4693
|
function mkCVSys(html, ts) {
|
|
4428
4694
|
const d = document.createElement('div');
|
|
4429
4695
|
d.className = 'cv-msg cv-sys';
|
|
@@ -5585,7 +5851,7 @@ function v2RenderOrgList() {
|
|
|
5585
5851
|
<div class="oi-name">${esc(o.name)}</div>
|
|
5586
5852
|
${goalSnip ? `<div class="oi-goal">${esc(goalSnip)}</div>` : ''}
|
|
5587
5853
|
<div class="oi-chips">
|
|
5588
|
-
${o.running ?
|
|
5854
|
+
${o.running ? `<span class="oi-chip ${o.lastEventAt ? (Date.now()-o.lastEventAt<1800000?'live':'quiet') : 'quiet'}">${o.lastEventAt ? (Date.now()-o.lastEventAt<1800000?'🟢 LIVE':'🟡 QUIET') : '🟡 QUIET'}</span>` : ''}
|
|
5589
5855
|
<span class="oi-chip">${rolesN} roles</span>
|
|
5590
5856
|
<span class="oi-chip">${esc(o.topology || 'hierarchical')}</span>
|
|
5591
5857
|
</div>
|
|
@@ -5659,15 +5925,30 @@ async function v2SelectOrg(name) {
|
|
|
5659
5925
|
function v2UpdateOrgHeader(listOrg, data) {
|
|
5660
5926
|
document.getElementById('odh-name').textContent = listOrg.name || _v2SelOrg || '—';
|
|
5661
5927
|
const running = data ? (data.running ?? listOrg.running) : listOrg.running;
|
|
5928
|
+
const _lastEvAt = data ? (data.lastEventAt ?? listOrg.lastEventAt) : listOrg.lastEventAt;
|
|
5662
5929
|
const badge = document.getElementById('odh-badge');
|
|
5663
|
-
|
|
5664
|
-
badge.className = 'odh-badge '
|
|
5930
|
+
const _mcBtn = document.getElementById('org-mark-complete-btn');
|
|
5931
|
+
if (!running) { badge.textContent = 'IDLE'; badge.className = 'odh-badge idle'; if (_mcBtn) _mcBtn.style.display = 'none'; }
|
|
5932
|
+
else if (!_lastEvAt) { badge.textContent = '🟡 QUIET'; badge.className = 'odh-badge quiet'; if (_mcBtn) _mcBtn.style.display = 'none'; }
|
|
5933
|
+
else { const _a = Date.now() - _lastEvAt; badge.textContent = _a < 1800000 ? '🟢 LIVE' : _a < 7200000 ? '🟡 QUIET' : '🔴 STALE'; badge.className = 'odh-badge ' + (_a < 1800000 ? 'live' : _a < 7200000 ? 'quiet' : 'stale'); if (_mcBtn) _mcBtn.style.display = _a >= 7200000 ? '' : 'none'; }
|
|
5665
5934
|
const roles = data ? (Array.isArray(data.roles) ? data.roles.length : (data.roles || 0)) : (listOrg.roles || 0);
|
|
5666
5935
|
document.getElementById('odh-roles').textContent = roles + ' role' + (roles !== 1 ? 's' : '');
|
|
5667
5936
|
document.getElementById('odh-topo').textContent = (data?.topology || listOrg.topology || '—');
|
|
5668
5937
|
document.getElementById('org-stop-btn').style.display = running ? '' : 'none';
|
|
5669
5938
|
}
|
|
5670
5939
|
|
|
5940
|
+
window.v2MarkOrgComplete = async function() {
|
|
5941
|
+
if (!_v2SelOrg) return;
|
|
5942
|
+
const btn = document.getElementById('org-mark-complete-btn');
|
|
5943
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Marking…'; }
|
|
5944
|
+
try {
|
|
5945
|
+
const r = await fetch('/api/orgs/' + encodeURIComponent(_v2SelOrg) + '/mark-complete', { method: 'POST' });
|
|
5946
|
+
const j = await r.json();
|
|
5947
|
+
if (!r.ok) { if (btn) { btn.textContent = 'Failed: ' + (j.error || r.status); setTimeout(() => { if (btn) btn.textContent = 'Mark Complete'; }, 3000); } }
|
|
5948
|
+
} catch (e) { if (btn) { btn.textContent = 'Error'; setTimeout(() => { if (btn) btn.textContent = 'Mark Complete'; }, 3000); } console.error('[mark-complete]', e); }
|
|
5949
|
+
finally { if (btn) { btn.disabled = false; btn.textContent = 'Mark Complete'; } }
|
|
5950
|
+
};
|
|
5951
|
+
|
|
5671
5952
|
window.v2SwitchOrgTab = function(tab) {
|
|
5672
5953
|
// Clear live interval when switching away from live tab
|
|
5673
5954
|
if (tab !== 'live' && _orgLiveInterval) {
|
|
@@ -6294,7 +6575,7 @@ function v2RenderOrgHealth() {
|
|
|
6294
6575
|
if (!pane) return;
|
|
6295
6576
|
pane.innerHTML = `
|
|
6296
6577
|
<div class="health-v2-grid">
|
|
6297
|
-
<div class="hv2-cell"><div class="hv2-lbl">Status</div><div class="hv2-val ${(_v2OrgData?.running??listOrg.running)?'green':''}">${(_v2OrgData?.running??listOrg.running)?'LIVE':'
|
|
6578
|
+
<div class="hv2-cell"><div class="hv2-lbl">Status</div><div class="hv2-val ${(()=>{const r=_v2OrgData?.running??listOrg.running;if(!r)return '';const a=_v2OrgData?.lastEventAt??listOrg.lastEventAt;return a&&(Date.now()-a)<1800000?'green':'amber';})()} ">${(()=>{const r=_v2OrgData?.running??listOrg.running;if(!r)return 'IDLE';const a=_v2OrgData?.lastEventAt??listOrg.lastEventAt;if(!a)return '🟡 QUIET';const d=Date.now()-a;return d<1800000?'🟢 LIVE':d<7200000?'🟡 QUIET':'🔴 STALE';})()}</div></div>
|
|
6298
6579
|
<div class="hv2-cell"><div class="hv2-lbl">Roles</div><div class="hv2-val">${roles}</div></div>
|
|
6299
6580
|
<div class="hv2-cell"><div class="hv2-lbl">Topology</div><div class="hv2-val" style="font-size:14px">${esc((_v2OrgData?.topology||'—').toUpperCase())}</div></div>
|
|
6300
6581
|
${health?.agents_active!=null?`<div class="hv2-cell"><div class="hv2-lbl">Agents</div><div class="hv2-val ${health.agents_active>0?'green':''}">${health.agents_active}</div></div>`:''}
|
|
@@ -6902,6 +7183,9 @@ let _odtChatCurrentId = null;
|
|
|
6902
7183
|
let _odtChatCurrentAgent = 'all';
|
|
6903
7184
|
let _odtChatSseSource = null;
|
|
6904
7185
|
let _odtChatSeenKeys = new Set();
|
|
7186
|
+
let _odtOrgTailSse = null; // per-org lifecycle tail SSE (receives bash-written run events)
|
|
7187
|
+
let _odtOrgTailOffset = 0; // line offset for reconnect (supplied as ?since=N)
|
|
7188
|
+
let _odtOrgTailOrg = null; // org the tail is currently connected to
|
|
6905
7189
|
let _odtChatForOrg = '';
|
|
6906
7190
|
// Loop grouping: [S] sessions with same prompt + <20 min gap → one entry
|
|
6907
7191
|
let _odtSessionGroups = {}; // groupId → {sessions, events, status, ts, _isGroup}
|
|
@@ -6954,18 +7238,30 @@ async function v2RenderOrgChat() {
|
|
|
6954
7238
|
<div id="odt-chat-agent-bar" style="display:none">
|
|
6955
7239
|
<span id="odt-chat-agent-lbl">AGENT</span>
|
|
6956
7240
|
</div>
|
|
7241
|
+
<div id="odt-chat-mode-row" style="display:flex;gap:4px;padding:4px 8px;border-bottom:1px solid oklch(62% 0.2 186 / 0.12);flex-shrink:0">
|
|
7242
|
+
<button id="odt-mode-summary" class="odt-mode-btn" onclick="setOdtChatMode('summary')" title="Summary: spawn/complete/edit/comms">SUMMARY</button>
|
|
7243
|
+
<button id="odt-mode-detailed" class="odt-mode-btn" onclick="setOdtChatMode('detailed')" title="Detailed: adds bash/browse/reads">DETAILED</button>
|
|
7244
|
+
<button id="odt-mode-raw" class="odt-mode-btn" onclick="setOdtChatMode('raw')" title="Raw: all events">RAW</button>
|
|
7245
|
+
</div>
|
|
6957
7246
|
<div id="odt-chat-excerpt" class="cv-excerpt-banner"></div>
|
|
6958
7247
|
<div id="odt-chat-feed">
|
|
6959
7248
|
<div id="odt-chat-empty">Select a session to see agent communications.<br><span style="font-size:10px;opacity:0.5">Sessions appear after the first /mastermind:runorg</span></div>
|
|
6960
7249
|
</div>`;
|
|
6961
7250
|
}
|
|
6962
7251
|
|
|
7252
|
+
// Restore saved chat mode (runs on every tab open so buttons stay in sync)
|
|
7253
|
+
initOdtChatMode();
|
|
7254
|
+
|
|
6963
7255
|
// Reset state only when org changes (not on every tab-switch)
|
|
6964
7256
|
if (_odtChatForOrg !== _v2SelOrg) {
|
|
6965
7257
|
_odtChatForOrg = _v2SelOrg;
|
|
6966
7258
|
_odtChatCurrentId = null;
|
|
6967
7259
|
_odtChatCurrentAgent = 'all';
|
|
6968
7260
|
_odtChatSeenKeys = new Set();
|
|
7261
|
+
// Reset per-org lifecycle tail so it reconnects to the new org on next chat render
|
|
7262
|
+
if (_odtOrgTailSse) { _odtOrgTailSse.close(); _odtOrgTailSse = null; }
|
|
7263
|
+
_odtOrgTailOffset = 0;
|
|
7264
|
+
_odtOrgTailOrg = null;
|
|
6969
7265
|
// Clear sessions from the previous org so _odtLoadChatSessions doesn't snapshot them
|
|
6970
7266
|
// into _prevSessions and risk contaminating the new org's event buffers. SSE-injected
|
|
6971
7267
|
// sessions for the NEW org arriving during the upcoming fetch are still captured via
|
|
@@ -7048,6 +7344,9 @@ async function v2RenderOrgChat() {
|
|
|
7048
7344
|
}
|
|
7049
7345
|
}
|
|
7050
7346
|
_odtConnectChatSSE();
|
|
7347
|
+
// Wire per-org streaming tail to pick up bash-written lifecycle events (run:start, run:cycle:complete, run:complete)
|
|
7348
|
+
// that bypass mastermind-stream. The dedup set in _odtHandleLiveEvent filters any duplicates.
|
|
7349
|
+
if (_v2SelOrg) _odtConnectOrgTail(_v2SelOrg);
|
|
7051
7350
|
}
|
|
7052
7351
|
|
|
7053
7352
|
async function _odtLoadChatSessions() {
|
|
@@ -7308,12 +7607,23 @@ function _odtPopulateChatSel() {
|
|
|
7308
7607
|
// Update label
|
|
7309
7608
|
const _lblEl = document.getElementById('odt-chat-sess-lbl');
|
|
7310
7609
|
if (_lblEl) _lblEl.textContent = (hasRuns && hasSess) ? 'HISTORY' : hasRuns ? 'RUN' : 'SESSION';
|
|
7311
|
-
// Auto-select running entry
|
|
7610
|
+
// Auto-select: prefer running entry; if already on a non-running entry and a running one
|
|
7611
|
+
// becomes available (e.g. mastermind sessions loaded after initial run-file fetch), upgrade.
|
|
7612
|
+
const runningRunGrp = runGroups.find(g => g.status === 'running');
|
|
7613
|
+
const runningSessGrp = sessGroups.find(g => g.status === 'running');
|
|
7614
|
+
const bestRunning = runningRunGrp || runningSessGrp;
|
|
7615
|
+
const currentGrp = _odtChatCurrentId
|
|
7616
|
+
? (runGroups.find(g => g.id === _odtChatCurrentId) || sessGroups.find(g => g.id === _odtChatCurrentId))
|
|
7617
|
+
: null;
|
|
7618
|
+
const currentIsRunning = currentGrp?.status === 'running';
|
|
7312
7619
|
if (!_odtChatCurrentId) {
|
|
7313
|
-
|
|
7314
|
-
const
|
|
7315
|
-
const first =
|
|
7620
|
+
// Pick: running entry first, then most-recent by timestamp across both run and session groups
|
|
7621
|
+
const allEntries = [...runGroups, ...sessGroups].sort((a, b) => (b.ts || 0) - (a.ts || 0));
|
|
7622
|
+
const first = bestRunning || allEntries[0];
|
|
7316
7623
|
if (first) { sel.value = first.id; odtChatSelectSession(first.id); }
|
|
7624
|
+
} else if (!currentIsRunning && bestRunning && bestRunning.id !== _odtChatCurrentId) {
|
|
7625
|
+
// Upgrade from stale/complete auto-selection to the running entry now that it's loaded
|
|
7626
|
+
sel.value = bestRunning.id; odtChatSelectSession(bestRunning.id);
|
|
7317
7627
|
}
|
|
7318
7628
|
}
|
|
7319
7629
|
|
|
@@ -7658,8 +7968,36 @@ window.odtChatSelectAgent = function(name) {
|
|
|
7658
7968
|
feed.scrollTop = feed.scrollHeight;
|
|
7659
7969
|
};
|
|
7660
7970
|
|
|
7971
|
+
// Chat tab render mode (Summary/Detailed/Raw) — persists to localStorage
|
|
7972
|
+
let _odtChatMode = localStorage.getItem('odt-chat-mode') || 'detailed';
|
|
7973
|
+
|
|
7974
|
+
function setOdtChatMode(mode) {
|
|
7975
|
+
_odtChatMode = mode;
|
|
7976
|
+
localStorage.setItem('odt-chat-mode', mode);
|
|
7977
|
+
document.querySelectorAll('.odt-mode-btn').forEach(b => b.classList.remove('active'));
|
|
7978
|
+
const btn = document.getElementById('odt-mode-' + mode);
|
|
7979
|
+
if (btn) btn.classList.add('active');
|
|
7980
|
+
}
|
|
7981
|
+
|
|
7982
|
+
function initOdtChatMode() {
|
|
7983
|
+
const saved = localStorage.getItem('odt-chat-mode') || 'detailed';
|
|
7984
|
+
setOdtChatMode(saved);
|
|
7985
|
+
}
|
|
7986
|
+
|
|
7661
7987
|
function _odtAppendEvent(ev, animate) {
|
|
7662
7988
|
if (!_odtChatAgentMatches(ev)) return;
|
|
7989
|
+
// Filter by render mode
|
|
7990
|
+
const _evType = ev.type || '';
|
|
7991
|
+
const _isSummaryType = _evType === 'agent:spawn' || _evType === 'agent:complete' ||
|
|
7992
|
+
_evType === 'agent:edit' || _evType === 'org:comms' || _evType === 'org:error' ||
|
|
7993
|
+
_evType === 'run:start' || _evType === 'run:complete' || _evType === 'org:start' ||
|
|
7994
|
+
_evType === 'org:checkpoint' || _evType === 'run:cycle:complete' || _evType === 'org:complete' || _evType === 'org:stop';
|
|
7995
|
+
const _isDetailedType = _isSummaryType || _evType === 'agent:bash' ||
|
|
7996
|
+
_evType === 'agent:browse' || _evType === 'agent:read:batch' || _evType === 'agent:usage' ||
|
|
7997
|
+
_evType === 'agent:result' || _evType === 'file:write' || _evType === 'org:agent:online';
|
|
7998
|
+
if (_odtChatMode === 'summary' && !_isSummaryType) return;
|
|
7999
|
+
if (_odtChatMode === 'detailed' && !_isDetailedType) return;
|
|
8000
|
+
// raw: show everything
|
|
7663
8001
|
const feed = document.getElementById('odt-chat-feed');
|
|
7664
8002
|
if (!feed) return;
|
|
7665
8003
|
// Capture scroll position BEFORE appending: only auto-scroll for live events when
|
|
@@ -7724,6 +8062,12 @@ function _odtAppendEvent(ev, animate) {
|
|
|
7724
8062
|
el = mkCVSys('⚠ Loop HIL: ' + esc(ev.command || ev.loopId || ''), ts);
|
|
7725
8063
|
} else if (ev.type === 'loop:hil:resolved') {
|
|
7726
8064
|
el = mkCVSys('✓ Loop HIL resolved: ' + esc(ev.loopId || ''), ts);
|
|
8065
|
+
} else if (ev.type === 'agent:edit' || ev.type === 'agent:write') {
|
|
8066
|
+
el = mkCVTool('EDIT', ev.payload || ev.file || ev.path || '', '', 'oklch(65% 0.20 270)', ts);
|
|
8067
|
+
} else if (ev.type === 'agent:bash') {
|
|
8068
|
+
el = mkCVTool('BASH', ev.payload || ev.command || '', ev.output || '', 'oklch(72% 0.18 40)', ts);
|
|
8069
|
+
} else if (ev.type === 'agent:browse') {
|
|
8070
|
+
el = mkCVTool('BROWSE', ev.payload || ev.url || '', '', 'oklch(65% 0.15 190)', ts);
|
|
7727
8071
|
} else if (ev.type === 'org:error') {
|
|
7728
8072
|
el = mkCVSys('⚠ Error: ' + esc(ev.msg || ev.error || ev.message || ''), ts);
|
|
7729
8073
|
el.classList.add('cv-err');
|
|
@@ -7759,6 +8103,28 @@ function _odtConnectChatSSE() {
|
|
|
7759
8103
|
};
|
|
7760
8104
|
}
|
|
7761
8105
|
|
|
8106
|
+
function _odtConnectOrgTail(orgName) {
|
|
8107
|
+
if (!orgName || (_odtOrgTailSse !== null && _odtOrgTailOrg === orgName)) return;
|
|
8108
|
+
if (_odtOrgTailSse) { _odtOrgTailSse.close(); _odtOrgTailSse = null; }
|
|
8109
|
+
_odtOrgTailOrg = orgName;
|
|
8110
|
+
const _tailUrl = '/api/orgs/' + encodeURIComponent(orgName) + '/runs/current/stream?since=' + _odtOrgTailOffset;
|
|
8111
|
+
_odtOrgTailSse = new EventSource(_tailUrl);
|
|
8112
|
+
_odtOrgTailSse.onmessage = e => {
|
|
8113
|
+
try {
|
|
8114
|
+
const ev = JSON.parse(e.data);
|
|
8115
|
+
if (ev && ev.type === 'stream:replay-done') { _odtOrgTailOffset = ev.count || _odtOrgTailOffset; return; }
|
|
8116
|
+
_odtOrgTailOffset++;
|
|
8117
|
+
_odtHandleLiveEvent(ev);
|
|
8118
|
+
} catch (_) {}
|
|
8119
|
+
};
|
|
8120
|
+
_odtOrgTailSse.onerror = () => {
|
|
8121
|
+
const _src = _odtOrgTailSse;
|
|
8122
|
+
_odtOrgTailSse = null;
|
|
8123
|
+
if (_src) _src.close();
|
|
8124
|
+
setTimeout(() => { if (_v2SelOrg === orgName && _odtOrgTailOrg === orgName) _odtConnectOrgTail(orgName); }, 5000);
|
|
8125
|
+
};
|
|
8126
|
+
}
|
|
8127
|
+
|
|
7762
8128
|
function _odtHandleLiveEvent(ev) {
|
|
7763
8129
|
if (ev?.project && DIR && ev.project !== DIR) return;
|
|
7764
8130
|
// Deduplicate SSE reconnect replays (server replays last 50 events on every reconnect)
|
|
@@ -10107,6 +10473,1278 @@ let _mgWikiFilter = 'all';
|
|
|
10107
10473
|
let _mgWikiMode = 'all';
|
|
10108
10474
|
let _mgWikiSearchTimer = null;
|
|
10109
10475
|
|
|
10476
|
+
/* ── Playbook View ─────────────────────────────────────────── */
|
|
10477
|
+
// monoagent connections state
|
|
10478
|
+
let _maPlatforms = []; // full registry from monoes
|
|
10479
|
+
let _maConns = []; // active API/OAuth connections
|
|
10480
|
+
let _maSessions = []; // browser sessions
|
|
10481
|
+
let _maSelected = null; // currently open modal platform def
|
|
10482
|
+
let _maLoginPoll = null; // active login poll interval (cleared on modal close)
|
|
10483
|
+
|
|
10484
|
+
function wfSwitchTab(tab) {
|
|
10485
|
+
document.querySelectorAll('.wf-pane, #wf-tab-builder').forEach(p => p.classList.remove('active'));
|
|
10486
|
+
document.querySelectorAll('[data-wftab]').forEach(b => b.classList.remove('active'));
|
|
10487
|
+
document.getElementById('wf-tab-' + tab).classList.add('active');
|
|
10488
|
+
document.querySelector('[data-wftab="' + tab + '"]').classList.add('active');
|
|
10489
|
+
if (tab === 'systems') loadPlatformSessions();
|
|
10490
|
+
if (tab === 'library') loadWorkflowDefs();
|
|
10491
|
+
if (tab === 'runs') loadWorkflowRuns();
|
|
10492
|
+
if (tab === 'builder') pbInit();
|
|
10493
|
+
}
|
|
10494
|
+
|
|
10495
|
+
let _wfDefs = [];
|
|
10496
|
+
async function loadWorkflowDefs() {
|
|
10497
|
+
const el = document.getElementById('wf-defs-list');
|
|
10498
|
+
if (!el) return;
|
|
10499
|
+
el.innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
10500
|
+
try {
|
|
10501
|
+
_wfDefs = await apiFetch('/api/workflow-defs');
|
|
10502
|
+
} catch { _wfDefs = []; }
|
|
10503
|
+
if (!_wfDefs.length) {
|
|
10504
|
+
el.innerHTML = `<div id="wf-lib-empty">No playbooks saved yet.<br><span style="font-size:12px;color:var(--text-xs)">Create one: <code style="font-family:var(--mono);color:var(--accent)">npx monomind browse playbook create my-playbook</code></span></div>`;
|
|
10505
|
+
return;
|
|
10506
|
+
}
|
|
10507
|
+
el.innerHTML = _wfDefs.map(d => {
|
|
10508
|
+
const ago = relativeTime(d.modifiedAt);
|
|
10509
|
+
const paramTags = (d.params || []).map(p => `<span class="wf-def-tag">${esc(p)}</span>`).join('');
|
|
10510
|
+
return `<div class="wf-def-card">
|
|
10511
|
+
<div class="wf-def-icon">⚡</div>
|
|
10512
|
+
<div class="wf-def-body">
|
|
10513
|
+
<div class="wf-def-name">${esc(d.name || d.id || d.file)}</div>
|
|
10514
|
+
${d.description ? `<div class="wf-def-desc">${esc(d.description)}</div>` : ''}
|
|
10515
|
+
<div class="wf-def-meta">
|
|
10516
|
+
<span class="wf-def-tag">${esc(d.nodeCount)} node${d.nodeCount !== 1 ? 's' : ''}</span>
|
|
10517
|
+
${paramTags}
|
|
10518
|
+
<span class="wf-def-tag" style="color:var(--text-xs)">${esc(ago)}</span>
|
|
10519
|
+
<span class="wf-def-tag" style="font-size:9px;color:var(--text-xs)">${esc(d.file)}</span>
|
|
10520
|
+
</div>
|
|
10521
|
+
</div>
|
|
10522
|
+
<div style="display:flex;flex-direction:column;gap:4px;flex-shrink:0">
|
|
10523
|
+
<button class="wf-def-run-btn" title="Copy run command" onclick="navigator.clipboard?.writeText('npx monomind browse playbook run .monomind/playbooks/${esc(d.file)}').then(()=>this.textContent='Copied!').catch(()=>{});setTimeout(()=>this.textContent='▶ Run',1500)">▶ Run</button>
|
|
10524
|
+
<button class="wf-def-run-btn" style="font-size:10px" title="Open in Builder" onclick="pbLoadDef(${JSON.stringify(d.id)});wfSwitchTab('builder')">✎ Edit</button>
|
|
10525
|
+
</div>
|
|
10526
|
+
</div>`;
|
|
10527
|
+
}).join('');
|
|
10528
|
+
}
|
|
10529
|
+
|
|
10530
|
+
async function loadPlatformSessions() {
|
|
10531
|
+
const grid = document.getElementById('wf-systems-grid');
|
|
10532
|
+
if (!grid) return;
|
|
10533
|
+
grid.innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
10534
|
+
try {
|
|
10535
|
+
const [plats, conns] = await Promise.all([
|
|
10536
|
+
fetch('/api/monoagent/platforms').then(r => r.json()).catch(() => []),
|
|
10537
|
+
fetch('/api/monoagent/connections').then(r => r.json()).catch(() => ({ connections: [], sessions: [] })),
|
|
10538
|
+
]);
|
|
10539
|
+
_maPlatforms = Array.isArray(plats) ? plats : [];
|
|
10540
|
+
_maConns = Array.isArray(conns.connections) ? conns.connections : [];
|
|
10541
|
+
_maSessions = Array.isArray(conns.sessions) ? conns.sessions : [];
|
|
10542
|
+
renderSystems();
|
|
10543
|
+
} catch (e) {
|
|
10544
|
+
grid.innerHTML = '<div class="loading-txt">Failed to load systems.</div>';
|
|
10545
|
+
}
|
|
10546
|
+
}
|
|
10547
|
+
|
|
10548
|
+
function relativeTime(ts) {
|
|
10549
|
+
if (!ts) return '—';
|
|
10550
|
+
const s = Math.floor((Date.now() - ts) / 1000);
|
|
10551
|
+
if (s < 60) return 'just now';
|
|
10552
|
+
if (s < 3600) return Math.floor(s / 60) + 'm ago';
|
|
10553
|
+
if (s < 86400) return Math.floor(s / 3600) + 'h ago';
|
|
10554
|
+
return Math.floor(s / 86400) + 'd ago';
|
|
10555
|
+
}
|
|
10556
|
+
|
|
10557
|
+
const MA_CATEGORY_ORDER = ['social', 'service', 'communication', 'database'];
|
|
10558
|
+
const MA_CATEGORY_LABELS = { social: 'Social', service: 'Services & APIs', communication: 'Communication', database: 'Databases' };
|
|
10559
|
+
const BROWSER_PLATFORMS = new Set(['instagram', 'linkedin', 'x', 'tiktok', 'gemini']);
|
|
10560
|
+
|
|
10561
|
+
function maResolveConn(pid) {
|
|
10562
|
+
pid = pid.toLowerCase();
|
|
10563
|
+
if (BROWSER_PLATFORMS.has(pid)) {
|
|
10564
|
+
const now = Date.now();
|
|
10565
|
+
const s = _maSessions.find(x => (x.Platform || '').toLowerCase() === pid);
|
|
10566
|
+
if (!s) return null;
|
|
10567
|
+
const expiry = new Date(s.Expiry).getTime();
|
|
10568
|
+
return { _type: 'session', id: String(s.ID), account: s.Username || '—', method: 'browser', status: expiry > now ? 'active' : 'expired' };
|
|
10569
|
+
}
|
|
10570
|
+
const c = _maConns.find(x => (x.platform || '').toLowerCase() === pid);
|
|
10571
|
+
if (!c) return null;
|
|
10572
|
+
return { _type: 'connection', id: c.id, account: c.account_id || c.label || '—', method: c.method || '—', status: c.status || 'active', lastTested: c.last_tested };
|
|
10573
|
+
}
|
|
10574
|
+
|
|
10575
|
+
function renderSystems() {
|
|
10576
|
+
const grid = document.getElementById('wf-systems-grid');
|
|
10577
|
+
if (!grid) return;
|
|
10578
|
+
|
|
10579
|
+
if (!_maPlatforms.length) {
|
|
10580
|
+
grid.innerHTML = `<div style="padding:24px;text-align:center;color:var(--text-xs);font-size:12px">
|
|
10581
|
+
<div style="font-size:28px;margin-bottom:8px">🔌</div>
|
|
10582
|
+
<div style="font-weight:600;color:var(--text-lo);margin-bottom:4px">monoagent not found</div>
|
|
10583
|
+
<div style="font-size:11px">Install monoes: <code style="color:var(--accent)">go install github.com/monoes/mono-agent/cmd/monoes@latest</code></div>
|
|
10584
|
+
</div>`;
|
|
10585
|
+
return;
|
|
10586
|
+
}
|
|
10587
|
+
|
|
10588
|
+
const groups = {};
|
|
10589
|
+
for (const p of _maPlatforms) {
|
|
10590
|
+
const cat = (p.Category || p.category || 'service').toLowerCase();
|
|
10591
|
+
if (!groups[cat]) groups[cat] = [];
|
|
10592
|
+
groups[cat].push(p);
|
|
10593
|
+
}
|
|
10594
|
+
|
|
10595
|
+
const cats = [...MA_CATEGORY_ORDER, ...Object.keys(groups).filter(c => !MA_CATEGORY_ORDER.includes(c))].filter(c => groups[c]);
|
|
10596
|
+
const totalConn = _maPlatforms.filter(p => maResolveConn(p.ID || p.id)).length;
|
|
10597
|
+
|
|
10598
|
+
let html = `<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
|
|
10599
|
+
<span style="font-size:11px;color:var(--text-xs)">${totalConn} / ${_maPlatforms.length} connected</span>
|
|
10600
|
+
<button class="ma-btn ma-btn-ghost" style="font-size:10px;padding:3px 10px" onclick="loadPlatformSessions()">↺ Refresh</button>
|
|
10601
|
+
</div>`;
|
|
10602
|
+
|
|
10603
|
+
for (const cat of cats) {
|
|
10604
|
+
const list = groups[cat];
|
|
10605
|
+
const catConn = list.filter(p => maResolveConn(p.ID || p.id)).length;
|
|
10606
|
+
html += `<div class="ma-cat-hdr">
|
|
10607
|
+
<span class="ma-cat-label">${MA_CATEGORY_LABELS[cat] || cat}</span>
|
|
10608
|
+
<div class="ma-cat-line"></div>
|
|
10609
|
+
<span class="ma-cat-count">${catConn}/${list.length}</span>
|
|
10610
|
+
</div><div class="ma-tiles">`;
|
|
10611
|
+
for (const p of list) {
|
|
10612
|
+
const pid = p.ID || p.id;
|
|
10613
|
+
const conn = maResolveConn(pid);
|
|
10614
|
+
const connected = conn && conn.status === 'active';
|
|
10615
|
+
const expired = conn && conn.status !== 'active';
|
|
10616
|
+
const cls = connected ? 'connected' : expired ? 'expired' : '';
|
|
10617
|
+
html += `<div class="ma-tile ${cls}" data-pid="${esc(pid)}" onclick="maOpenModal(this.dataset.pid)">
|
|
10618
|
+
<span class="ma-tile-icon">${esc(p.IconEmoji || p.iconEmoji || '🔌')}</span>
|
|
10619
|
+
<span class="ma-tile-name">${esc(p.Name || p.name || pid)}</span>
|
|
10620
|
+
<div style="display:flex;align-items:center;gap:4px">
|
|
10621
|
+
<span class="ma-tile-dot"></span>
|
|
10622
|
+
${conn ? `<span class="ma-tile-acct">${esc(conn.account)}</span>` : ''}
|
|
10623
|
+
</div>
|
|
10624
|
+
</div>`;
|
|
10625
|
+
}
|
|
10626
|
+
html += `</div>`;
|
|
10627
|
+
}
|
|
10628
|
+
|
|
10629
|
+
grid.innerHTML = html;
|
|
10630
|
+
}
|
|
10631
|
+
|
|
10632
|
+
function maOpenModal(pid) {
|
|
10633
|
+
const p = _maPlatforms.find(x => (x.ID || x.id) === pid);
|
|
10634
|
+
if (!p) return;
|
|
10635
|
+
_maSelected = p;
|
|
10636
|
+
maRenderModal(p, maResolveConn(pid), []);
|
|
10637
|
+
}
|
|
10638
|
+
|
|
10639
|
+
function maRenderModal(p, conn, steps, selMethod, fields, state) {
|
|
10640
|
+
const pid = p.ID || p.id;
|
|
10641
|
+
const name = p.Name || p.name || pid;
|
|
10642
|
+
const emoji = p.IconEmoji || p.iconEmoji || '🔌';
|
|
10643
|
+
const methods = (p.Methods || p.methods || []);
|
|
10644
|
+
const method = selMethod || methods[0] || '';
|
|
10645
|
+
const isBrowser = method === 'browser';
|
|
10646
|
+
const connected = conn && conn.status === 'active';
|
|
10647
|
+
const expired = conn && conn.status !== 'active' && conn;
|
|
10648
|
+
state = state || {};
|
|
10649
|
+
|
|
10650
|
+
const existing = document.getElementById('ma-modal-overlay');
|
|
10651
|
+
if (existing) existing.remove();
|
|
10652
|
+
|
|
10653
|
+
const overlay = document.createElement('div');
|
|
10654
|
+
overlay.id = 'ma-modal-overlay';
|
|
10655
|
+
overlay.className = 'ma-modal-bg';
|
|
10656
|
+
overlay.addEventListener('click', e => { if (e.target === overlay) maCloseModal(); });
|
|
10657
|
+
|
|
10658
|
+
let bodyHtml = '';
|
|
10659
|
+
|
|
10660
|
+
if (conn) {
|
|
10661
|
+
// Connected state
|
|
10662
|
+
const statusColor = connected ? 'var(--green)' : '#fbbf24';
|
|
10663
|
+
bodyHtml = `
|
|
10664
|
+
<div class="ma-modal-info">
|
|
10665
|
+
${[['Account', conn.account], ['Method', conn.method], ['Status', conn.status]].map(([l,v]) =>
|
|
10666
|
+
`<div class="ma-modal-row"><span class="ma-modal-lbl">${l}</span><span class="ma-modal-val ${v==='active'?'ok':v==='expired'?'warn':''}">${esc(v)}</span></div>`
|
|
10667
|
+
).join('')}
|
|
10668
|
+
${conn.lastTested ? `<div class="ma-modal-row"><span class="ma-modal-lbl">Last tested</span><span class="ma-modal-val">${esc(relativeTime(new Date(conn.lastTested).getTime()))}</span></div>` : ''}
|
|
10669
|
+
</div>
|
|
10670
|
+
${state.testMsg ? `<div class="ma-msg ${state.testOk ? 'ok' : 'err'}">${esc(state.testMsg)}</div>` : ''}
|
|
10671
|
+
${state.err ? `<div class="ma-msg err">${esc(state.err)}</div>` : ''}
|
|
10672
|
+
${steps.length ? `<div class="ma-steps">${steps.map(s=>`<div class="ma-step"><span class="ma-step-dot ${s.kind}"></span><span class="ma-step-txt ${s.kind}">${esc(s.msg)}</span></div>`).join('')}</div>` : ''}
|
|
10673
|
+
<div class="ma-btns">
|
|
10674
|
+
<button class="ma-btn ma-btn-secondary" id="ma-test-btn" onclick="maTest('${pid}','${conn.id}')">Test</button>
|
|
10675
|
+
${expired ? `<button class="ma-btn ma-btn-primary" onclick="maStartLogin('${pid}')">Log in again</button>` : ''}
|
|
10676
|
+
${state.confirmDisc ? `
|
|
10677
|
+
<button class="ma-btn ma-btn-danger" onclick="maDisconnect('${pid}','${conn.id}','${conn._type}')">Yes, disconnect</button>
|
|
10678
|
+
<button class="ma-btn ma-btn-ghost" data-pid="${esc(pid)}" onclick="maOpenModal(this.dataset.pid)">Cancel</button>
|
|
10679
|
+
` : `
|
|
10680
|
+
<button class="ma-btn ma-btn-danger" onclick="maConfirmDisconnect('${pid}')">Disconnect</button>
|
|
10681
|
+
<button class="ma-btn ma-btn-ghost" onclick="maCloseModal()">Close</button>
|
|
10682
|
+
`}
|
|
10683
|
+
</div>`;
|
|
10684
|
+
} else {
|
|
10685
|
+
// Not connected — show method tabs + connect form
|
|
10686
|
+
const methodTabsHtml = methods.length > 1 ? `<div class="ma-method-tabs">${methods.map(m =>
|
|
10687
|
+
`<button class="ma-method-tab ${m===method?'active':''}" data-pid="${esc(p.ID||p.id)}" data-method="${esc(m)}" onclick="maRenderModal(_maPlatforms.find(x=>(x.ID||x.id)===this.dataset.pid),null,[],this.dataset.method,{},{})">${esc(m)}</button>`
|
|
10688
|
+
).join('')}</div>` : '';
|
|
10689
|
+
|
|
10690
|
+
let formHtml = '';
|
|
10691
|
+
if (isBrowser) {
|
|
10692
|
+
formHtml = `<div class="ma-browser-hint">A browser window will open. Log in to ${esc(name)} and the session will be saved automatically.</div>
|
|
10693
|
+
${steps.length ? `<div class="ma-steps">${steps.map(s=>`<div class="ma-step"><span class="ma-step-dot ${s.kind}"></span><span class="ma-step-txt ${s.kind}">${esc(s.msg)}</span></div>`).join('')}</div>` : ''}
|
|
10694
|
+
${state.running ? `<div style="display:flex;align-items:center;gap:8px;font-size:11px;color:var(--text-xs)"><span class="spinner" style="width:10px;height:10px;border-width:2px"></span>Waiting for login…</div>` : ''}
|
|
10695
|
+
${state.err ? `<div class="ma-msg err">${esc(state.err)}</div>` : ''}`;
|
|
10696
|
+
} else {
|
|
10697
|
+
const fieldDefs = (p.Fields || p.fields || {})[method] || [];
|
|
10698
|
+
formHtml = fieldDefs.map(f => {
|
|
10699
|
+
const key = f.Key || f.key;
|
|
10700
|
+
const label = f.Label || f.label || key;
|
|
10701
|
+
const secret = f.Secret || f.secret;
|
|
10702
|
+
const req = f.Required || f.required;
|
|
10703
|
+
const val = (fields || {})[key] || '';
|
|
10704
|
+
return `<div>
|
|
10705
|
+
<div class="ma-field-label">${esc(label)}${req ? ' <span style="color:oklch(60% 0.18 25)">*</span>' : ''}</div>
|
|
10706
|
+
<input class="ma-field-input" type="${secret ? 'password' : 'text'}" data-key="${esc(key)}" value="${esc(val)}" autocomplete="${secret ? 'new-password' : 'off'}" placeholder="${esc(label)}">
|
|
10707
|
+
${f.HelpURL || f.helpURL ? `<a href="#" style="font-size:9.5px;color:var(--accent);text-decoration:none" onclick="event.preventDefault()">How to get this ↗</a>` : ''}
|
|
10708
|
+
</div>`;
|
|
10709
|
+
}).join('') || `<div class="ma-browser-hint">No configuration required.</div>`;
|
|
10710
|
+
formHtml += `${state.err ? `<div class="ma-msg err">${esc(state.err)}</div>` : ''}
|
|
10711
|
+
${state.ok ? `<div class="ma-msg ok">Connected!</div>` : ''}`;
|
|
10712
|
+
}
|
|
10713
|
+
|
|
10714
|
+
const connectBtn = isBrowser
|
|
10715
|
+
? `<button class="ma-btn ma-btn-primary" id="ma-conn-btn" onclick="maStartLogin('${pid}')">Connect ${esc(name)}</button>`
|
|
10716
|
+
: `<button class="ma-btn ma-btn-primary" id="ma-conn-btn" data-pid="${esc(pid)}" data-method="${esc(method)}" onclick="maConnectApiKey(this.dataset.pid,this.dataset.method)">Connect</button>`;
|
|
10717
|
+
|
|
10718
|
+
bodyHtml = `
|
|
10719
|
+
${methodTabsHtml}
|
|
10720
|
+
${formHtml}
|
|
10721
|
+
<div class="ma-btns">
|
|
10722
|
+
${state.ok ? '' : connectBtn}
|
|
10723
|
+
<button class="ma-btn ma-btn-ghost" onclick="loadPlatformSessions();maCloseModal()">Refresh</button>
|
|
10724
|
+
<button class="ma-btn ma-btn-ghost" onclick="maCloseModal()">Close</button>
|
|
10725
|
+
</div>`;
|
|
10726
|
+
}
|
|
10727
|
+
|
|
10728
|
+
overlay.innerHTML = `<div class="ma-modal">
|
|
10729
|
+
<div class="ma-modal-hdr">
|
|
10730
|
+
<div class="ma-modal-hdr-left">
|
|
10731
|
+
<span style="font-size:26px">${esc(emoji)}</span>
|
|
10732
|
+
<div>
|
|
10733
|
+
<div class="ma-modal-title">${esc(name)}</div>
|
|
10734
|
+
<div style="display:flex;align-items:center;gap:5px;margin-top:2px">
|
|
10735
|
+
<span style="width:6px;height:6px;border-radius:50%;background:${connected?'var(--green)':expired?'#fbbf24':'var(--border)'}"></span>
|
|
10736
|
+
<span class="ma-modal-status" style="color:${connected?'var(--green)':expired?'#fbbf24':'var(--text-xs)'}">${connected?'Connected':expired?'Session expired':'Not connected'}</span>
|
|
10737
|
+
</div>
|
|
10738
|
+
</div>
|
|
10739
|
+
</div>
|
|
10740
|
+
<button class="ma-btn ma-btn-ghost" style="padding:4px 8px" onclick="maCloseModal()">✕</button>
|
|
10741
|
+
</div>
|
|
10742
|
+
<div class="ma-modal-body">${bodyHtml}</div>
|
|
10743
|
+
</div>`;
|
|
10744
|
+
|
|
10745
|
+
document.body.appendChild(overlay);
|
|
10746
|
+
}
|
|
10747
|
+
|
|
10748
|
+
function maCloseModal() {
|
|
10749
|
+
if (_maLoginPoll) { clearInterval(_maLoginPoll); _maLoginPoll = null; }
|
|
10750
|
+
const el = document.getElementById('ma-modal-overlay');
|
|
10751
|
+
if (el) el.remove();
|
|
10752
|
+
_maSelected = null;
|
|
10753
|
+
}
|
|
10754
|
+
|
|
10755
|
+
function maConfirmDisconnect(pid) {
|
|
10756
|
+
const p = _maPlatforms.find(x => (x.ID || x.id) === pid);
|
|
10757
|
+
const conn = maResolveConn(pid);
|
|
10758
|
+
if (p && conn) maRenderModal(p, conn, [], null, {}, { confirmDisc: true });
|
|
10759
|
+
}
|
|
10760
|
+
|
|
10761
|
+
async function maStartLogin(pid) {
|
|
10762
|
+
const p = _maPlatforms.find(x => (x.ID || x.id) === pid);
|
|
10763
|
+
if (!p) return;
|
|
10764
|
+
const steps = [{ msg: 'Opening browser…', kind: '' }];
|
|
10765
|
+
maRenderModal(p, null, steps, 'browser', {}, { running: true });
|
|
10766
|
+
try {
|
|
10767
|
+
const r = await fetch('/api/monoagent/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: pid }) });
|
|
10768
|
+
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
10769
|
+
steps.push({ msg: 'Browser open — log in then come back', kind: 'ok' });
|
|
10770
|
+
maRenderModal(p, null, steps, 'browser', {}, { running: false });
|
|
10771
|
+
showToast('Browser opened', 'Log in to ' + (p.Name || pid) + ' and hit Refresh', 'ok');
|
|
10772
|
+
// Poll for completion
|
|
10773
|
+
let polls = 0;
|
|
10774
|
+
if (_maLoginPoll) clearInterval(_maLoginPoll);
|
|
10775
|
+
_maLoginPoll = setInterval(async () => {
|
|
10776
|
+
polls++;
|
|
10777
|
+
const d = await fetch('/api/monoagent/connections').then(r => r.json()).catch(() => null);
|
|
10778
|
+
if (d) { _maConns = d.connections || []; _maSessions = d.sessions || []; }
|
|
10779
|
+
const conn = maResolveConn(pid);
|
|
10780
|
+
if (conn || polls >= 18) {
|
|
10781
|
+
clearInterval(_maLoginPoll); _maLoginPoll = null;
|
|
10782
|
+
if (conn) { renderSystems(); maOpenModal(pid); showToast('Connected', (p.Name||pid) + ' session saved', 'ok'); }
|
|
10783
|
+
else { maRenderModal(p, null, [], 'browser', {}, { err: 'Login timed out. Close the browser tab and try again.' }); }
|
|
10784
|
+
}
|
|
10785
|
+
}, 5000);
|
|
10786
|
+
} catch (e) {
|
|
10787
|
+
maRenderModal(p, null, [], 'browser', {}, { err: e.message });
|
|
10788
|
+
}
|
|
10789
|
+
}
|
|
10790
|
+
|
|
10791
|
+
async function maConnectApiKey(pid, method) {
|
|
10792
|
+
const p = _maPlatforms.find(x => (x.ID || x.id) === pid);
|
|
10793
|
+
if (!p) return;
|
|
10794
|
+
const modal = document.getElementById('ma-modal-overlay');
|
|
10795
|
+
const inputs = modal ? modal.querySelectorAll('.ma-field-input') : [];
|
|
10796
|
+
const fields = {};
|
|
10797
|
+
inputs.forEach(inp => { fields[inp.dataset.key] = inp.value.trim(); });
|
|
10798
|
+
const btn = document.getElementById('ma-conn-btn');
|
|
10799
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Connecting…'; }
|
|
10800
|
+
try {
|
|
10801
|
+
const r = await fetch('/api/monoagent/connect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: pid, method, fields }) });
|
|
10802
|
+
const d = await r.json();
|
|
10803
|
+
if (d.ok) {
|
|
10804
|
+
await loadPlatformSessions();
|
|
10805
|
+
maRenderModal(p, maResolveConn(pid), [], method, fields, { ok: true });
|
|
10806
|
+
showToast('Connected', (p.Name || pid) + ' connected', 'ok');
|
|
10807
|
+
} else {
|
|
10808
|
+
maRenderModal(p, null, [], method, fields, { err: d.stderr || d.error || 'Connection failed' });
|
|
10809
|
+
}
|
|
10810
|
+
} catch (e) {
|
|
10811
|
+
maRenderModal(p, null, [], method, fields, { err: e.message });
|
|
10812
|
+
}
|
|
10813
|
+
}
|
|
10814
|
+
|
|
10815
|
+
async function maTest(pid, connId) {
|
|
10816
|
+
const p = _maPlatforms.find(x => (x.ID || x.id) === pid);
|
|
10817
|
+
const conn = maResolveConn(pid);
|
|
10818
|
+
if (!p || !conn) return;
|
|
10819
|
+
const btn = document.getElementById('ma-test-btn');
|
|
10820
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Testing…'; }
|
|
10821
|
+
try {
|
|
10822
|
+
const d = await fetch('/api/monoagent/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: connId }) }).then(r => r.json());
|
|
10823
|
+
maRenderModal(p, conn, [], null, {}, { testOk: d.ok, testMsg: d.ok ? '✓ Connection OK' : ('✗ ' + (d.error || 'Test failed')) });
|
|
10824
|
+
} catch (e) {
|
|
10825
|
+
maRenderModal(p, conn, [], null, {}, { testMsg: '✗ ' + e.message, testOk: false });
|
|
10826
|
+
}
|
|
10827
|
+
}
|
|
10828
|
+
|
|
10829
|
+
async function maDisconnect(pid, connId, type) {
|
|
10830
|
+
const p = _maPlatforms.find(x => (x.ID || x.id) === pid);
|
|
10831
|
+
if (!p) return;
|
|
10832
|
+
try {
|
|
10833
|
+
await fetch('/api/monoagent/disconnect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: connId, type }) });
|
|
10834
|
+
await loadPlatformSessions();
|
|
10835
|
+
maCloseModal();
|
|
10836
|
+
showToast('Disconnected', (p.Name || pid) + ' removed', 'ok');
|
|
10837
|
+
} catch (e) {
|
|
10838
|
+
showToast('Error', e.message, 'err');
|
|
10839
|
+
}
|
|
10840
|
+
}
|
|
10841
|
+
|
|
10842
|
+
let _wfRuns = [];
|
|
10843
|
+
async function loadWorkflowRuns() {
|
|
10844
|
+
document.getElementById('wf-runs-list').innerHTML = '<div class="loading-txt">Loading…</div>';
|
|
10845
|
+
try {
|
|
10846
|
+
const runs = await apiFetch('/api/workflow-runs');
|
|
10847
|
+
_wfRuns = Array.isArray(runs) ? runs : [];
|
|
10848
|
+
} catch (e) {
|
|
10849
|
+
_wfRuns = [];
|
|
10850
|
+
}
|
|
10851
|
+
const bdg = document.getElementById('bdg-playbooks');
|
|
10852
|
+
if (bdg) bdg.textContent = _wfRuns.length || '—';
|
|
10853
|
+
document.getElementById('wf-count').textContent = _wfRuns.length + ' run' + (_wfRuns.length !== 1 ? 's' : '');
|
|
10854
|
+
renderWorkflowRuns();
|
|
10855
|
+
}
|
|
10856
|
+
|
|
10857
|
+
function renderWorkflowRuns() {
|
|
10858
|
+
const filter = document.getElementById('wf-status-filter')?.value || '';
|
|
10859
|
+
const runs = filter ? _wfRuns.filter(r => r.status === filter) : _wfRuns;
|
|
10860
|
+
const el = document.getElementById('wf-runs-list');
|
|
10861
|
+
if (!runs.length) {
|
|
10862
|
+
el.innerHTML = '<div id="wf-empty">No playbook runs found. Run a playbook with:<br><code style="font-family:var(--mono);font-size:12px;color:var(--accent)">npx monomind browse playbook run <file></code></div>';
|
|
10863
|
+
return;
|
|
10864
|
+
}
|
|
10865
|
+
el.innerHTML = runs.map(r => {
|
|
10866
|
+
const st = r.status || 'unknown';
|
|
10867
|
+
const elapsed = r.completedAt
|
|
10868
|
+
? ((r.completedAt - r.startedAt) / 1000).toFixed(1) + 's'
|
|
10869
|
+
: r.startedAt ? ((Date.now() - r.startedAt) / 1000).toFixed(0) + 's' : '—';
|
|
10870
|
+
const pct = r.itemsTotal > 0 ? Math.round((r.itemsProcessed || 0) / r.itemsTotal * 100) : (st === 'completed' ? 100 : 0);
|
|
10871
|
+
const items = r.itemsTotal > 0 ? `${r.itemsProcessed || 0}/${r.itemsTotal}` : (r.itemsProcessed ? String(r.itemsProcessed) : '—');
|
|
10872
|
+
const started = r.startedAt ? new Date(r.startedAt).toLocaleString() : '—';
|
|
10873
|
+
return `<div class="wf-run-card">
|
|
10874
|
+
<div class="wf-run-header">
|
|
10875
|
+
<span class="wf-run-name" title="${esc(r.playbookName || r.playbookId || r.workflowName || r.workflowId || 'Playbook')}">${esc(r.playbookName || r.playbookId || r.workflowName || r.workflowId || 'Playbook')}</span>
|
|
10876
|
+
<span class="wf-status s-${esc(st)}">${esc(st.toUpperCase())}</span>
|
|
10877
|
+
</div>
|
|
10878
|
+
<div class="wf-run-meta">
|
|
10879
|
+
<span>⏱ ${esc(elapsed)}</span>
|
|
10880
|
+
<span>📦 ${esc(items)} items</span>
|
|
10881
|
+
<span>🕐 ${esc(started)}</span>
|
|
10882
|
+
<span class="wf-run-id">${esc((r.id || '').slice(0,12))}</span>
|
|
10883
|
+
</div>
|
|
10884
|
+
${r.error ? `<div class="wf-run-error">✗ ${esc(r.error)}</div>` : ''}
|
|
10885
|
+
${r.itemsTotal > 0 ? `<div class="wf-progress-bar"><div class="wf-progress-bar-fill" style="width:${pct}%"></div></div>` : ''}
|
|
10886
|
+
</div>`;
|
|
10887
|
+
}).join('');
|
|
10888
|
+
}
|
|
10889
|
+
|
|
10890
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
10891
|
+
PLAYBOOK BUILDER
|
|
10892
|
+
═══════════════════════════════════════════════════════════════ */
|
|
10893
|
+
const PB_CAT_COLOR = {
|
|
10894
|
+
'Triggers':'#7c3aed','Control':'#0891b2','Data':'#d97706','Actions':'#059669',
|
|
10895
|
+
'HTTP':'#ea580c','Comm':'#9333ea','Google':'#1d4ed8','Services':'#0f766e',
|
|
10896
|
+
};
|
|
10897
|
+
|
|
10898
|
+
const PB_NODE_DEFS = [
|
|
10899
|
+
// ── Triggers ──────────────────────────────────────────────────────────────
|
|
10900
|
+
{ cat:'Triggers', type:'trigger.manual', label:'Manual Trigger', icon:'▶' },
|
|
10901
|
+
{ cat:'Triggers', type:'trigger.schedule', label:'Schedule', icon:'⏰' },
|
|
10902
|
+
{ cat:'Triggers', type:'trigger.webhook', label:'Webhook', icon:'🔗' },
|
|
10903
|
+
// ── Control ───────────────────────────────────────────────────────────────
|
|
10904
|
+
{ cat:'Control', type:'core.if', label:'If / Branch', icon:'⑂' },
|
|
10905
|
+
{ cat:'Control', type:'core.switch', label:'Switch', icon:'⑃' },
|
|
10906
|
+
{ cat:'Control', type:'core.filter', label:'Filter', icon:'⚗' },
|
|
10907
|
+
{ cat:'Control', type:'core.merge', label:'Merge', icon:'⊕' },
|
|
10908
|
+
{ cat:'Control', type:'core.wait', label:'Wait', icon:'⏱' },
|
|
10909
|
+
{ cat:'Control', type:'core.stop_error', label:'Stop & Error', icon:'⛔' },
|
|
10910
|
+
// ── Data ──────────────────────────────────────────────────────────────────
|
|
10911
|
+
{ cat:'Data', type:'core.set', label:'Set Fields', icon:'✎' },
|
|
10912
|
+
{ cat:'Data', type:'core.aggregate', label:'Aggregate', icon:'∑' },
|
|
10913
|
+
{ cat:'Data', type:'core.sort', label:'Sort', icon:'⇅' },
|
|
10914
|
+
{ cat:'Data', type:'core.limit', label:'Limit', icon:'✂' },
|
|
10915
|
+
{ cat:'Data', type:'core.remove_duplicates', label:'Dedup', icon:'⊘' },
|
|
10916
|
+
{ cat:'Data', type:'core.split_in_batches', label:'Split Batches', icon:'⋮' },
|
|
10917
|
+
{ cat:'Data', type:'core.compare_datasets', label:'Compare', icon:'⟺' },
|
|
10918
|
+
{ cat:'Data', type:'core.code', label:'Code (JS)', icon:'〈/〉' },
|
|
10919
|
+
{ cat:'Data', type:'data.datetime', label:'Date & Time', icon:'📅' },
|
|
10920
|
+
{ cat:'Data', type:'data.html', label:'HTML', icon:'📝' },
|
|
10921
|
+
{ cat:'Data', type:'data.markdown', label:'Markdown', icon:'📄' },
|
|
10922
|
+
{ cat:'Data', type:'data.xml', label:'XML', icon:'📋' },
|
|
10923
|
+
// ── Actions (monoplaybook builtins) ───────────────────────────────────────
|
|
10924
|
+
{ cat:'Actions', type:'action.http', label:'HTTP Request', icon:'🌐' },
|
|
10925
|
+
{ cat:'Actions', type:'action.log', label:'Log', icon:'📜' },
|
|
10926
|
+
{ cat:'Actions', type:'action.save_file', label:'Save File', icon:'💾' },
|
|
10927
|
+
{ cat:'Actions', type:'action.gemini_image', label:'Gemini Image', icon:'🖼' },
|
|
10928
|
+
// ── HTTP / Network ────────────────────────────────────────────────────────
|
|
10929
|
+
{ cat:'HTTP', type:'http.request', label:'HTTP Request', icon:'⊹' },
|
|
10930
|
+
// ── Communications ────────────────────────────────────────────────────────
|
|
10931
|
+
{ cat:'Comm', type:'comm.email_send', label:'Send Email', icon:'✉' },
|
|
10932
|
+
{ cat:'Comm', type:'comm.slack', label:'Slack', icon:'💬' },
|
|
10933
|
+
{ cat:'Comm', type:'comm.discord', label:'Discord', icon:'🎮' },
|
|
10934
|
+
{ cat:'Comm', type:'comm.telegram', label:'Telegram', icon:'✈' },
|
|
10935
|
+
{ cat:'Comm', type:'comm.twilio', label:'Twilio SMS', icon:'📱' },
|
|
10936
|
+
// ── Google (service.* — real monoplaybook handlers) ──────────────────────
|
|
10937
|
+
{ cat:'Google', type:'service.gmail', defaultConfig:{operation:'send_message'}, label:'Gmail: Send', icon:'✉' },
|
|
10938
|
+
{ cat:'Google', type:'service.gmail', defaultConfig:{operation:'list_messages'}, label:'Gmail: List', icon:'📬' },
|
|
10939
|
+
{ cat:'Google', type:'service.google_drive', defaultConfig:{operation:'list_files'}, label:'Drive: List', icon:'📁' },
|
|
10940
|
+
{ cat:'Google', type:'service.google_drive', defaultConfig:{operation:'download_file'}, label:'Drive: Download',icon:'⬇' },
|
|
10941
|
+
{ cat:'Google', type:'service.google_drive', defaultConfig:{operation:'upload_file'}, label:'Drive: Upload', icon:'⬆' },
|
|
10942
|
+
{ cat:'Google', type:'service.google_sheets',defaultConfig:{operation:'read_rows'}, label:'Sheets: Read', icon:'📊' },
|
|
10943
|
+
{ cat:'Google', type:'service.google_sheets',defaultConfig:{operation:'append_rows'}, label:'Sheets: Append', icon:'➕' },
|
|
10944
|
+
// ── Services (service.* — real monoplaybook handlers) ────────────────────
|
|
10945
|
+
{ cat:'Services', type:'service.github', defaultConfig:{operation:'list_issues'}, label:'GitHub: Issues', icon:'⊙' },
|
|
10946
|
+
{ cat:'Services', type:'service.github', defaultConfig:{operation:'create_issue'}, label:'GitHub: Create', icon:'⊙' },
|
|
10947
|
+
{ cat:'Services', type:'service.notion', defaultConfig:{operation:'query_database'}, label:'Notion: Query', icon:'📄' },
|
|
10948
|
+
{ cat:'Services', type:'service.notion', defaultConfig:{operation:'create_page'}, label:'Notion: Create', icon:'📄' },
|
|
10949
|
+
{ cat:'Services', type:'service.linear', defaultConfig:{operation:'list_issues'}, label:'Linear: Issues', icon:'◉' },
|
|
10950
|
+
{ cat:'Services', type:'service.linear', defaultConfig:{operation:'create_issue'}, label:'Linear: Create', icon:'◉' },
|
|
10951
|
+
{ cat:'Services', type:'service.airtable', defaultConfig:{operation:'list_records'}, label:'Airtable: List', icon:'⊞' },
|
|
10952
|
+
{ cat:'Services', type:'service.airtable', defaultConfig:{operation:'create_record'}, label:'Airtable: Create', icon:'⊞' },
|
|
10953
|
+
{ cat:'Services', type:'service.stripe', defaultConfig:{operation:'list_customers'}, label:'Stripe: Customers', icon:'💳' },
|
|
10954
|
+
{ cat:'Services', type:'service.stripe', defaultConfig:{operation:'list_charges'}, label:'Stripe: Charges', icon:'💳' },
|
|
10955
|
+
];
|
|
10956
|
+
|
|
10957
|
+
const PB_NODE_FIELDS = {
|
|
10958
|
+
// ── Triggers ──────────────────────────────────────────────────────────────
|
|
10959
|
+
'trigger.manual': [],
|
|
10960
|
+
'trigger.schedule': [
|
|
10961
|
+
{ key:'cron', label:'Cron Expression', type:'text', placeholder:'*/5 * * * *', help:'Standard 5-field cron: min hour day month weekday' },
|
|
10962
|
+
{ key:'timezone', label:'Timezone', type:'text', placeholder:'UTC' },
|
|
10963
|
+
],
|
|
10964
|
+
'trigger.webhook': [
|
|
10965
|
+
{ key:'path', label:'Webhook Path', type:'text', placeholder:'/webhooks/my-playbook' },
|
|
10966
|
+
{ key:'method', label:'HTTP Method', type:'select', options:['POST','GET','PUT','PATCH'] },
|
|
10967
|
+
{ key:'secret', label:'HMAC Secret', type:'password', placeholder:'optional — verified via X-Signature header' },
|
|
10968
|
+
],
|
|
10969
|
+
// ── Control ───────────────────────────────────────────────────────────────
|
|
10970
|
+
'core.if': [{ key:'condition', label:'Condition', type:'textarea', placeholder:'{{ eq $json.active true }}', help:'Go template — items go to "true" or "false" handle' }],
|
|
10971
|
+
'core.filter': [
|
|
10972
|
+
{ key:'condition', label:'Condition', type:'textarea', placeholder:'{{ eq $json.status "todo" }}' },
|
|
10973
|
+
{ key:'mode', label:'Mode', type:'select', options:['keep','remove'] },
|
|
10974
|
+
],
|
|
10975
|
+
'core.switch': [
|
|
10976
|
+
{ key:'expression', label:'Expression', type:'text', placeholder:'{{$json.status}}' },
|
|
10977
|
+
{ key:'cases', label:'Cases (JSON)', type:'textarea', placeholder:'[{"value":"active"},{"value":"closed"}]' },
|
|
10978
|
+
{ key:'default_handle', label:'Default Handle',type:'text', placeholder:'default' },
|
|
10979
|
+
],
|
|
10980
|
+
'core.merge': [{ key:'mode', label:'Mode', type:'select', options:['append','first'] }],
|
|
10981
|
+
'core.wait': [{ key:'duration', label:'Duration', type:'text', placeholder:'5s', help:'e.g. 5s, 2m, 1h' }],
|
|
10982
|
+
'core.stop_error': [{ key:'message', label:'Error Message', type:'text', placeholder:'Stopped: condition not met' }],
|
|
10983
|
+
// ── Data ──────────────────────────────────────────────────────────────────
|
|
10984
|
+
'core.set': [
|
|
10985
|
+
{ key:'assignments', label:'Assignments (JSON)', type:'textarea', placeholder:'[{"name":"output","value":"{{$json.input}}"}]' },
|
|
10986
|
+
{ key:'include_input', label:'Include Input', type:'select', options:['true','false'] },
|
|
10987
|
+
],
|
|
10988
|
+
'core.aggregate': [
|
|
10989
|
+
{ key:'group_by', label:'Group By Field', type:'text', placeholder:'' },
|
|
10990
|
+
{ key:'operations', label:'Operations', type:'textarea', placeholder:'[{"field":"amount","operation":"sum","output_field":"total"}]' },
|
|
10991
|
+
],
|
|
10992
|
+
'core.sort': [
|
|
10993
|
+
{ key:'field', label:'Sort By', type:'text', placeholder:'name' },
|
|
10994
|
+
{ key:'order', label:'Order', type:'select', options:['asc','desc'] },
|
|
10995
|
+
{ key:'type', label:'Type', type:'select', options:['string','number','date'] },
|
|
10996
|
+
],
|
|
10997
|
+
'core.limit': [{ key:'max_items', label:'Max Items', type:'number', placeholder:'10' }],
|
|
10998
|
+
'core.remove_duplicates': [
|
|
10999
|
+
{ key:'field', label:'Field Key', type:'text', placeholder:'id' },
|
|
11000
|
+
{ key:'keep', label:'Keep', type:'select', options:['first','last'] },
|
|
11001
|
+
],
|
|
11002
|
+
'core.split_in_batches': [{ key:'batch_size', label:'Batch Size', type:'number', placeholder:'10' }],
|
|
11003
|
+
'core.compare_datasets': [
|
|
11004
|
+
{ key:'key_field', label:'Key Field', type:'text', placeholder:'id' },
|
|
11005
|
+
{ key:'split_at', label:'Split At Index', type:'number', placeholder:'' },
|
|
11006
|
+
],
|
|
11007
|
+
'core.code': [{ key:'code', label:'JavaScript', type:'textarea', placeholder:'// return array of items\nreturn items.map(item => ({ ...item.json }))' }],
|
|
11008
|
+
'data.datetime': [
|
|
11009
|
+
{ key:'operation', label:'Operation', type:'select', options:['format','parse','add','subtract','diff','now'] },
|
|
11010
|
+
{ key:'field', label:'Source Field', type:'text', placeholder:'date' },
|
|
11011
|
+
{ key:'input_format', label:'Input Format', type:'text', placeholder:'' },
|
|
11012
|
+
{ key:'output_format', label:'Output Format', type:'text', placeholder:'2006-01-02T15:04:05Z07:00' },
|
|
11013
|
+
{ key:'duration', label:'Duration', type:'text', placeholder:'24h' },
|
|
11014
|
+
{ key:'output_field', label:'Output Field', type:'text', placeholder:'formatted_date' },
|
|
11015
|
+
],
|
|
11016
|
+
'data.html': [
|
|
11017
|
+
{ key:'operation', label:'Operation', type:'select', options:['extract','extract_all','text','generate'] },
|
|
11018
|
+
{ key:'field', label:'Source Field', type:'text', placeholder:'' },
|
|
11019
|
+
{ key:'selector', label:'CSS Selector', type:'text', placeholder:'h1.title' },
|
|
11020
|
+
{ key:'attribute', label:'Attribute', type:'text', placeholder:'href' },
|
|
11021
|
+
{ key:'template', label:'HTML Template',type:'textarea',placeholder:'<p>{{.Title}}</p>' },
|
|
11022
|
+
],
|
|
11023
|
+
'data.markdown': [
|
|
11024
|
+
{ key:'field', label:'Source Field', type:'text', placeholder:'' },
|
|
11025
|
+
{ key:'output_field', label:'Output Field', type:'text', placeholder:'html' },
|
|
11026
|
+
],
|
|
11027
|
+
'data.xml': [
|
|
11028
|
+
{ key:'operation', label:'Operation', type:'select', options:['parse','generate'] },
|
|
11029
|
+
{ key:'field', label:'Source Field', type:'text', placeholder:'' },
|
|
11030
|
+
{ key:'root_element', label:'Root Element', type:'text', placeholder:'root' },
|
|
11031
|
+
],
|
|
11032
|
+
// ── Actions (monoplaybook builtins) ───────────────────────────────────────
|
|
11033
|
+
'action.http': [
|
|
11034
|
+
{ key:'url', label:'URL', type:'text', placeholder:'https://api.example.com/data' },
|
|
11035
|
+
{ key:'method', label:'Method', type:'select', options:['GET','POST','PUT','PATCH','DELETE'] },
|
|
11036
|
+
{ key:'body', label:'Body (JSON)', type:'textarea', placeholder:'{}' },
|
|
11037
|
+
{ key:'responseField', label:'Save As Field', type:'text', placeholder:'response' },
|
|
11038
|
+
{ key:'timeoutMs', label:'Timeout (ms)', type:'number', placeholder:'30000' },
|
|
11039
|
+
],
|
|
11040
|
+
'action.log': [
|
|
11041
|
+
{ key:'label', label:'Label', type:'text', placeholder:'debug', help:'Prefix shown in console output' },
|
|
11042
|
+
],
|
|
11043
|
+
'action.save_file': [
|
|
11044
|
+
{ key:'path', label:'Output Path', type:'text', placeholder:'./output/result.json' },
|
|
11045
|
+
{ key:'field', label:'Field', type:'text', placeholder:'leave blank for full item' },
|
|
11046
|
+
{ key:'encoding', label:'Encoding', type:'select', options:['utf8','base64','binary'] },
|
|
11047
|
+
],
|
|
11048
|
+
'action.gemini_image': [
|
|
11049
|
+
{ key:'prompt', label:'Prompt', type:'textarea', placeholder:'A futuristic city skyline at dusk' },
|
|
11050
|
+
{ key:'outputPath', label:'Output Path', type:'text', placeholder:'./output/image.png' },
|
|
11051
|
+
{ key:'aspectRatio', label:'Aspect Ratio',type:'select', options:['1:1','16:9','9:16','4:3'] },
|
|
11052
|
+
{ key:'apiKey', label:'API Key', type:'password', placeholder:'uses GEMINI_API_KEY env if blank' },
|
|
11053
|
+
{ key:'model', label:'Model', type:'text', placeholder:'imagen-3.0-generate-001' },
|
|
11054
|
+
],
|
|
11055
|
+
// ── HTTP / Network ────────────────────────────────────────────────────────
|
|
11056
|
+
'http.request': [
|
|
11057
|
+
{ key:'method', label:'Method', type:'select', options:['GET','POST','PUT','PATCH','DELETE','HEAD'] },
|
|
11058
|
+
{ key:'url', label:'URL', type:'text', placeholder:'https://api.example.com/endpoint' },
|
|
11059
|
+
{ key:'body_type', label:'Body Type', type:'select', options:['none','json','form','raw'] },
|
|
11060
|
+
{ key:'body', label:'Body (JSON)', type:'textarea', placeholder:'' },
|
|
11061
|
+
{ key:'auth_type', label:'Auth', type:'select', options:['none','bearer','basic','api_key'] },
|
|
11062
|
+
{ key:'auth_api_key_value', label:'Token / Key', type:'password', placeholder:'' },
|
|
11063
|
+
{ key:'response_format', label:'Response', type:'select', options:['json','text','binary'] },
|
|
11064
|
+
],
|
|
11065
|
+
'http.ftp': [
|
|
11066
|
+
{ key:'host', label:'Host', type:'text', placeholder:'ftp.example.com' },
|
|
11067
|
+
{ key:'port', label:'Port', type:'number', placeholder:'21' },
|
|
11068
|
+
{ key:'username', label:'Username', type:'text', placeholder:'' },
|
|
11069
|
+
{ key:'password', label:'Password', type:'password', placeholder:'' },
|
|
11070
|
+
{ key:'remote_path', label:'Remote Path',type:'text', placeholder:'/' },
|
|
11071
|
+
{ key:'operation', label:'Operation', type:'select', options:['list','download','upload','delete'] },
|
|
11072
|
+
],
|
|
11073
|
+
'http.ssh': [
|
|
11074
|
+
{ key:'host', label:'Host', type:'text', placeholder:'server.example.com' },
|
|
11075
|
+
{ key:'port', label:'Port', type:'number', placeholder:'22' },
|
|
11076
|
+
{ key:'username', label:'Username', type:'text', placeholder:'' },
|
|
11077
|
+
{ key:'password', label:'Password', type:'password', placeholder:'' },
|
|
11078
|
+
{ key:'command', label:'Command', type:'textarea', placeholder:'ls -la /var/log' },
|
|
11079
|
+
],
|
|
11080
|
+
'system.execute_command': [
|
|
11081
|
+
{ key:'command', label:'Command', type:'text', placeholder:'echo' },
|
|
11082
|
+
{ key:'args', label:'Args (JSON)', type:'text', placeholder:'["hello world"]' },
|
|
11083
|
+
{ key:'working_dir', label:'Working Dir', type:'text', placeholder:'' },
|
|
11084
|
+
{ key:'timeout_seconds', label:'Timeout (s)', type:'number', placeholder:'30' },
|
|
11085
|
+
],
|
|
11086
|
+
'system.rss_read': [
|
|
11087
|
+
{ key:'url', label:'Feed URL', type:'text', placeholder:'https://feeds.example.com/rss' },
|
|
11088
|
+
{ key:'limit', label:'Max Items', type:'number', placeholder:'20' },
|
|
11089
|
+
],
|
|
11090
|
+
// ── Communications ────────────────────────────────────────────────────────
|
|
11091
|
+
'comm.email_send': [
|
|
11092
|
+
{ key:'smtp_host', label:'SMTP Host', type:'text', placeholder:'smtp.gmail.com' },
|
|
11093
|
+
{ key:'smtp_port', label:'SMTP Port', type:'number', placeholder:'587' },
|
|
11094
|
+
{ key:'smtp_user', label:'Username', type:'text', placeholder:'user@example.com' },
|
|
11095
|
+
{ key:'smtp_password', label:'Password', type:'password', placeholder:'' },
|
|
11096
|
+
{ key:'from', label:'From', type:'text', placeholder:'sender@example.com' },
|
|
11097
|
+
{ key:'to', label:'To', type:'text', placeholder:'recipient@example.com' },
|
|
11098
|
+
{ key:'subject', label:'Subject', type:'text', placeholder:'Hello' },
|
|
11099
|
+
{ key:'body', label:'Body', type:'textarea', placeholder:'Hi {{$json.name}},' },
|
|
11100
|
+
{ key:'body_type', label:'Body Type', type:'select', options:['text','html'] },
|
|
11101
|
+
],
|
|
11102
|
+
'comm.slack': [
|
|
11103
|
+
{ key:'webhook_url', label:'Webhook URL', type:'password', placeholder:'https://hooks.slack.com/services/…' },
|
|
11104
|
+
{ key:'channel', label:'Channel', type:'text', placeholder:'#general' },
|
|
11105
|
+
{ key:'text', label:'Message', type:'textarea', placeholder:'Hello from monoplaybook!' },
|
|
11106
|
+
{ key:'username', label:'Bot Name', type:'text', placeholder:'Monoplaybook' },
|
|
11107
|
+
],
|
|
11108
|
+
'comm.discord': [
|
|
11109
|
+
{ key:'webhook_url', label:'Webhook URL', type:'password', placeholder:'https://discord.com/api/webhooks/…' },
|
|
11110
|
+
{ key:'content', label:'Message', type:'textarea', placeholder:'Hello from monoplaybook!' },
|
|
11111
|
+
{ key:'username', label:'Bot Name', type:'text', placeholder:'Monoplaybook' },
|
|
11112
|
+
],
|
|
11113
|
+
'comm.telegram': [
|
|
11114
|
+
{ key:'token', label:'Bot Token', type:'password', placeholder:'123456:ABC-DEF…' },
|
|
11115
|
+
{ key:'chat_id', label:'Chat ID', type:'text', placeholder:'-100123456' },
|
|
11116
|
+
{ key:'text', label:'Message', type:'textarea', placeholder:'Hello {{$json.name}}!' },
|
|
11117
|
+
{ key:'parse_mode', label:'Format', type:'select', options:['text','Markdown','HTML'] },
|
|
11118
|
+
],
|
|
11119
|
+
'comm.twilio': [
|
|
11120
|
+
{ key:'account_sid', label:'Account SID', type:'text', placeholder:'ACxxxx' },
|
|
11121
|
+
{ key:'auth_token', label:'Auth Token', type:'password', placeholder:'' },
|
|
11122
|
+
{ key:'from', label:'From Number', type:'text', placeholder:'+15551234567' },
|
|
11123
|
+
{ key:'to', label:'To Number', type:'text', placeholder:'{{$json.phone}}' },
|
|
11124
|
+
{ key:'body', label:'Message', type:'textarea', placeholder:'Your code is {{$json.code}}' },
|
|
11125
|
+
],
|
|
11126
|
+
// ── Google ────────────────────────────────────────────────────────────────
|
|
11127
|
+
// ── Google (service.* handlers — consolidated with operation selector) ─────
|
|
11128
|
+
'service.gmail': [
|
|
11129
|
+
{ key:'operation', label:'Operation', type:'select', options:['send_message','list_messages','get_message','list_labels','trash_message'] },
|
|
11130
|
+
{ key:'access_token', label:'OAuth Token', type:'password', placeholder:'ya29.…' },
|
|
11131
|
+
{ key:'to', label:'To (send)', type:'text', placeholder:'recipient@example.com' },
|
|
11132
|
+
{ key:'subject', label:'Subject (send)',type:'text', placeholder:'Hello from monoplaybook' },
|
|
11133
|
+
{ key:'body', label:'Body (send)', type:'textarea', placeholder:'Hi there,' },
|
|
11134
|
+
{ key:'body_type', label:'Body Type', type:'select', options:['text','html'] },
|
|
11135
|
+
{ key:'query', label:'Search (list)',type:'text', placeholder:'from:alice is:unread' },
|
|
11136
|
+
{ key:'max_results', label:'Max Results', type:'number', placeholder:'10' },
|
|
11137
|
+
{ key:'message_id', label:'Message ID (get/trash)',type:'text', placeholder:'' },
|
|
11138
|
+
],
|
|
11139
|
+
'service.google_drive': [
|
|
11140
|
+
{ key:'operation', label:'Operation', type:'select', options:['list_files','download_file','upload_file','get_file','create_folder','delete_file','share_file'] },
|
|
11141
|
+
{ key:'access_token', label:'OAuth Token', type:'password', placeholder:'ya29.…' },
|
|
11142
|
+
{ key:'query', label:'Search (list)',type:'text', placeholder:'name contains "report"' },
|
|
11143
|
+
{ key:'file_id', label:'File ID (get/download/delete/share)',type:'text', placeholder:'1BxiMV…' },
|
|
11144
|
+
{ key:'file_path', label:'Local Path (upload)',type:'text', placeholder:'/tmp/file.pdf' },
|
|
11145
|
+
{ key:'file_name', label:'Name (upload/folder)',type:'text', placeholder:'report.pdf' },
|
|
11146
|
+
{ key:'parent_folder_id', label:'Parent Folder ID', type:'text', placeholder:'' },
|
|
11147
|
+
{ key:'mime_type', label:'MIME Type (upload)', type:'text', placeholder:'application/pdf' },
|
|
11148
|
+
],
|
|
11149
|
+
'service.google_sheets': [
|
|
11150
|
+
{ key:'operation', label:'Operation', type:'select', options:['read_rows','append_rows','update_rows','clear_range'] },
|
|
11151
|
+
{ key:'access_token', label:'OAuth Token', type:'password', placeholder:'ya29.…' },
|
|
11152
|
+
{ key:'spreadsheet_id', label:'Spreadsheet ID', type:'text', placeholder:'1BxiMV…' },
|
|
11153
|
+
{ key:'sheet', label:'Sheet Name', type:'text', placeholder:'Sheet1' },
|
|
11154
|
+
{ key:'range', label:'Range', type:'text', placeholder:'A1:Z' },
|
|
11155
|
+
{ key:'use_header_row', label:'Header Row (read)',type:'select', options:['true','false'] },
|
|
11156
|
+
],
|
|
11157
|
+
// ── Services (service.* handlers) ─────────────────────────────────────────
|
|
11158
|
+
'service.github': [
|
|
11159
|
+
{ key:'operation', label:'Operation', type:'select', options:['list_issues','create_issue','get_issue','update_issue','list_repos','list_prs','create_pr','list_releases','create_release'] },
|
|
11160
|
+
{ key:'token', label:'Access Token', type:'password', placeholder:'ghp_…' },
|
|
11161
|
+
{ key:'owner', label:'Owner', type:'text', placeholder:'octocat' },
|
|
11162
|
+
{ key:'repo', label:'Repo', type:'text', placeholder:'hello-world' },
|
|
11163
|
+
{ key:'state', label:'State (list)', type:'select', options:['open','closed','all'] },
|
|
11164
|
+
{ key:'title', label:'Title (create)',type:'text', placeholder:'Bug: something is broken' },
|
|
11165
|
+
{ key:'body', label:'Body (create)', type:'textarea',placeholder:'Steps to reproduce…' },
|
|
11166
|
+
],
|
|
11167
|
+
'service.notion': [
|
|
11168
|
+
{ key:'operation', label:'Operation', type:'select', options:['query_database','create_page','get_page','update_page','get_database','create_database','append_blocks'] },
|
|
11169
|
+
{ key:'token', label:'Integration Token', type:'password', placeholder:'secret_…' },
|
|
11170
|
+
{ key:'database_id', label:'Database ID', type:'text', placeholder:'xxxxxxxx-xxxx-…' },
|
|
11171
|
+
{ key:'parent_id', label:'Parent Page/DB ID', type:'text', placeholder:'xxxxxxxx-xxxx-…' },
|
|
11172
|
+
{ key:'page_id', label:'Page ID (get/update)', type:'text', placeholder:'xxxxxxxx-xxxx-…' },
|
|
11173
|
+
{ key:'title', label:'Title (create)', type:'text', placeholder:'New Page' },
|
|
11174
|
+
{ key:'properties', label:'Properties (JSON)', type:'textarea', placeholder:'{}' },
|
|
11175
|
+
{ key:'filter', label:'Filter (JSON, query)', type:'textarea', placeholder:'{"property":"Status","select":{"equals":"Done"}}' },
|
|
11176
|
+
],
|
|
11177
|
+
'service.linear': [
|
|
11178
|
+
{ key:'operation', label:'Operation', type:'select', options:['list_issues','create_issue','get_issue','update_issue','list_teams','list_projects'] },
|
|
11179
|
+
{ key:'token', label:'API Key', type:'password', placeholder:'lin_api_…' },
|
|
11180
|
+
{ key:'team_id', label:'Team ID', type:'text', placeholder:'' },
|
|
11181
|
+
{ key:'issue_id', label:'Issue ID (get/update)',type:'text', placeholder:'' },
|
|
11182
|
+
{ key:'title', label:'Title (create)',type:'text', placeholder:'Fix: something broke' },
|
|
11183
|
+
{ key:'description', label:'Description', type:'textarea', placeholder:'Details…' },
|
|
11184
|
+
{ key:'priority', label:'Priority (create)',type:'number', placeholder:'1 = urgent, 4 = low' },
|
|
11185
|
+
],
|
|
11186
|
+
'service.airtable': [
|
|
11187
|
+
{ key:'operation', label:'Operation', type:'select', options:['list_records','create_record','get_record','update_record','delete_record'] },
|
|
11188
|
+
{ key:'token', label:'Access Token', type:'password', placeholder:'patXXX…' },
|
|
11189
|
+
{ key:'base_id', label:'Base ID', type:'text', placeholder:'appXXX…' },
|
|
11190
|
+
{ key:'table', label:'Table Name', type:'text', placeholder:'Tasks' },
|
|
11191
|
+
{ key:'record_id', label:'Record ID (get/update/delete)',type:'text', placeholder:'recXXX…' },
|
|
11192
|
+
{ key:'filter_formula', label:'Filter Formula (list)',type:'text', placeholder:'AND({Status}="Done")' },
|
|
11193
|
+
{ key:'max_records', label:'Max Records (list)',type:'number', placeholder:'100' },
|
|
11194
|
+
{ key:'fields', label:'Fields JSON (create/update)',type:'textarea', placeholder:'{"Name":"{{$json.title}}"}' },
|
|
11195
|
+
],
|
|
11196
|
+
'service.stripe': [
|
|
11197
|
+
{ key:'operation', label:'Operation', type:'select', options:['list_customers','create_customer','get_customer','list_charges','create_charge','list_subscriptions','create_subscription','cancel_subscription','list_products','create_payment_intent'] },
|
|
11198
|
+
{ key:'api_key', label:'Secret Key', type:'password', placeholder:'sk_live_…' },
|
|
11199
|
+
{ key:'customer_id', label:'Customer ID', type:'text', placeholder:'cus_…' },
|
|
11200
|
+
{ key:'limit', label:'Limit', type:'number', placeholder:'20' },
|
|
11201
|
+
{ key:'amount', label:'Amount (cents, charge)',type:'number', placeholder:'2000' },
|
|
11202
|
+
{ key:'currency', label:'Currency', type:'text', placeholder:'usd' },
|
|
11203
|
+
{ key:'email', label:'Email (create customer)',type:'text', placeholder:'customer@example.com' },
|
|
11204
|
+
],
|
|
11205
|
+
// ── Database ──────────────────────────────────────────────────────────────
|
|
11206
|
+
'db.postgres': [
|
|
11207
|
+
{ key:'host', label:'Host', type:'text', placeholder:'localhost' },
|
|
11208
|
+
{ key:'port', label:'Port', type:'number', placeholder:'5432' },
|
|
11209
|
+
{ key:'database', label:'Database', type:'text', placeholder:'mydb' },
|
|
11210
|
+
{ key:'username', label:'Username', type:'text', placeholder:'postgres' },
|
|
11211
|
+
{ key:'password', label:'Password', type:'password', placeholder:'' },
|
|
11212
|
+
{ key:'query', label:'SQL', type:'textarea', placeholder:'SELECT * FROM users LIMIT 10' },
|
|
11213
|
+
],
|
|
11214
|
+
'db.mysql': [
|
|
11215
|
+
{ key:'host', label:'Host', type:'text', placeholder:'localhost' },
|
|
11216
|
+
{ key:'port', label:'Port', type:'number', placeholder:'3306' },
|
|
11217
|
+
{ key:'database', label:'Database', type:'text', placeholder:'mydb' },
|
|
11218
|
+
{ key:'username', label:'Username', type:'text', placeholder:'root' },
|
|
11219
|
+
{ key:'password', label:'Password', type:'password', placeholder:'' },
|
|
11220
|
+
{ key:'query', label:'SQL', type:'textarea', placeholder:'SELECT * FROM users LIMIT 10' },
|
|
11221
|
+
],
|
|
11222
|
+
'db.mongodb': [
|
|
11223
|
+
{ key:'uri', label:'Connection URI', type:'password', placeholder:'mongodb://localhost:27017/mydb' },
|
|
11224
|
+
{ key:'collection', label:'Collection', type:'text', placeholder:'users' },
|
|
11225
|
+
{ key:'operation', label:'Operation', type:'select', options:['find','findOne','insertOne','updateOne','deleteOne'] },
|
|
11226
|
+
{ key:'filter', label:'Filter (JSON)', type:'textarea', placeholder:'{"active":true}' },
|
|
11227
|
+
{ key:'update', label:'Update (JSON)', type:'textarea', placeholder:'{"$set":{"status":"active"}}' },
|
|
11228
|
+
{ key:'limit', label:'Limit', type:'number', placeholder:'100' },
|
|
11229
|
+
],
|
|
11230
|
+
'db.redis': [
|
|
11231
|
+
{ key:'url', label:'Redis URL', type:'password', placeholder:'redis://localhost:6379' },
|
|
11232
|
+
{ key:'operation', label:'Operation', type:'select', options:['get','set','del','keys','hget','hset','lpush','lrange'] },
|
|
11233
|
+
{ key:'key', label:'Key', type:'text', placeholder:'mykey' },
|
|
11234
|
+
{ key:'value', label:'Value', type:'text', placeholder:'{{$json.value}}' },
|
|
11235
|
+
{ key:'ttl', label:'TTL (s)', type:'number', placeholder:'3600' },
|
|
11236
|
+
],
|
|
11237
|
+
// ── AI ────────────────────────────────────────────────────────────────────
|
|
11238
|
+
'ai.chat': [
|
|
11239
|
+
{ key:'provider_id', label:'Provider', type:'text', placeholder:'anthropic' },
|
|
11240
|
+
{ key:'model', label:'Model', type:'text', placeholder:'claude-3-5-sonnet-20241022' },
|
|
11241
|
+
{ key:'system_prompt', label:'System Prompt', type:'textarea', placeholder:'You are a helpful assistant.' },
|
|
11242
|
+
{ key:'prompt', label:'Prompt', type:'textarea', placeholder:'{{$json.text}}' },
|
|
11243
|
+
{ key:'temperature', label:'Temperature', type:'text', placeholder:'0.7' },
|
|
11244
|
+
{ key:'max_tokens', label:'Max Tokens', type:'number', placeholder:'1024' },
|
|
11245
|
+
{ key:'output_key', label:'Output Key', type:'text', placeholder:'ai_response' },
|
|
11246
|
+
],
|
|
11247
|
+
'ai.extract': [
|
|
11248
|
+
{ key:'provider_id', label:'Provider', type:'text', placeholder:'anthropic' },
|
|
11249
|
+
{ key:'model', label:'Model', type:'text', placeholder:'claude-3-5-haiku-20241022' },
|
|
11250
|
+
{ key:'prompt', label:'Extract Prompt', type:'textarea', placeholder:'Extract the name and email from: {{$json.text}}' },
|
|
11251
|
+
{ key:'output_schema', label:'Output Schema', type:'textarea', placeholder:'{"name":"string","email":"string"}' },
|
|
11252
|
+
{ key:'output_key', label:'Output Key', type:'text', placeholder:'extracted' },
|
|
11253
|
+
],
|
|
11254
|
+
'ai.classify': [
|
|
11255
|
+
{ key:'provider_id', label:'Provider', type:'text', placeholder:'anthropic' },
|
|
11256
|
+
{ key:'model', label:'Model', type:'text', placeholder:'claude-3-5-haiku-20241022' },
|
|
11257
|
+
{ key:'categories', label:'Categories', type:'text', placeholder:'positive,negative,neutral' },
|
|
11258
|
+
{ key:'prompt_template', label:'Prompt', type:'textarea', placeholder:'Classify this text: {{$json.text}}' },
|
|
11259
|
+
],
|
|
11260
|
+
'ai.transform': [
|
|
11261
|
+
{ key:'provider_id', label:'Provider', type:'text', placeholder:'anthropic' },
|
|
11262
|
+
{ key:'model', label:'Model', type:'text', placeholder:'claude-3-5-sonnet-20241022' },
|
|
11263
|
+
{ key:'instruction', label:'Instruction', type:'textarea', placeholder:'Summarize this in 2 sentences' },
|
|
11264
|
+
{ key:'input_field', label:'Input Field', type:'text', placeholder:'content' },
|
|
11265
|
+
{ key:'output_key', label:'Output Key', type:'text', placeholder:'transformed' },
|
|
11266
|
+
],
|
|
11267
|
+
'ai.agent': [
|
|
11268
|
+
{ key:'provider_id', label:'Provider', type:'text', placeholder:'anthropic' },
|
|
11269
|
+
{ key:'model', label:'Model', type:'text', placeholder:'claude-3-5-sonnet-20241022' },
|
|
11270
|
+
{ key:'goal', label:'Goal', type:'textarea', placeholder:'Research and summarize the topic: {{$json.query}}' },
|
|
11271
|
+
{ key:'max_steps', label:'Max Steps', type:'number', placeholder:'5' },
|
|
11272
|
+
],
|
|
11273
|
+
};
|
|
11274
|
+
|
|
11275
|
+
let pbState = {
|
|
11276
|
+
playbook: { id: '', name: 'Untitled', nodes: [], connections: [] },
|
|
11277
|
+
positions: {}, // { nodeId: { x, y } }
|
|
11278
|
+
runStatus: {}, // { nodeId: 'running'|'completed'|'failed' }
|
|
11279
|
+
selectedId: null,
|
|
11280
|
+
connecting: null, // nodeId being connected from
|
|
11281
|
+
_pbInited: false,
|
|
11282
|
+
_dragNode: null,
|
|
11283
|
+
_dragOff: { x: 0, y: 0 },
|
|
11284
|
+
};
|
|
11285
|
+
|
|
11286
|
+
let _pbWs = null;
|
|
11287
|
+
function pbHandleEvent(ev) {
|
|
11288
|
+
if (!ev) return;
|
|
11289
|
+
if (ev.nodeId) {
|
|
11290
|
+
if (ev.eventType === 'step_started') { pbState.runStatus[ev.nodeId] = 'running'; pbUpdateNodeStatus(ev.nodeId); }
|
|
11291
|
+
if (ev.eventType === 'step_completed') { pbState.runStatus[ev.nodeId] = 'completed'; pbUpdateNodeStatus(ev.nodeId); }
|
|
11292
|
+
if (ev.eventType === 'step_failed') { pbState.runStatus[ev.nodeId] = 'failed'; pbUpdateNodeStatus(ev.nodeId); }
|
|
11293
|
+
}
|
|
11294
|
+
if (ev.eventType === 'run_started') { pbState.runStatus = {}; pbRenderCanvas(); }
|
|
11295
|
+
if (ev.eventType === 'run_completed' || ev.eventType === 'run_failed' || ev.eventType === 'run_stopped') {
|
|
11296
|
+
const lbl = document.getElementById('pb-run-status');
|
|
11297
|
+
if (lbl) lbl.textContent = '● ' + ev.eventType.replace('run_', '').toUpperCase();
|
|
11298
|
+
setTimeout(() => loadWorkflowRuns(), 1000);
|
|
11299
|
+
}
|
|
11300
|
+
}
|
|
11301
|
+
|
|
11302
|
+
function pbInit() {
|
|
11303
|
+
if (pbState._pbInited) { pbRenderCanvas(); return; }
|
|
11304
|
+
pbState._pbInited = true;
|
|
11305
|
+
pbRenderPalette();
|
|
11306
|
+
pbRenderCanvas();
|
|
11307
|
+
pbConnectEvents();
|
|
11308
|
+
}
|
|
11309
|
+
|
|
11310
|
+
function pbConnectEvents() {
|
|
11311
|
+
// Connect to the monobrowse dashboard WS/SSE on port 4243 for live step events
|
|
11312
|
+
if (_pbWs) { try { _pbWs.close(); } catch {} _pbWs = null; }
|
|
11313
|
+
try {
|
|
11314
|
+
const ws = new WebSocket('ws://localhost:4243');
|
|
11315
|
+
ws.onmessage = (e) => { try { const d = JSON.parse(e.data); if (d.type === 'history') return; pbHandleEvent(d); } catch {} };
|
|
11316
|
+
ws.onerror = () => pbConnectSse();
|
|
11317
|
+
ws.onclose = () => { _pbWs = null; };
|
|
11318
|
+
_pbWs = ws;
|
|
11319
|
+
} catch { pbConnectSse(); }
|
|
11320
|
+
}
|
|
11321
|
+
|
|
11322
|
+
function pbConnectSse() {
|
|
11323
|
+
try {
|
|
11324
|
+
const es = new EventSource('http://localhost:4243/events');
|
|
11325
|
+
es.onmessage = (e) => { try { pbHandleEvent(JSON.parse(e.data)); } catch {} };
|
|
11326
|
+
es.onerror = () => es.close();
|
|
11327
|
+
} catch {}
|
|
11328
|
+
}
|
|
11329
|
+
|
|
11330
|
+
function pbRenderPalette() {
|
|
11331
|
+
const el = document.getElementById('pb-palette');
|
|
11332
|
+
if (!el) return;
|
|
11333
|
+
const cats = [...new Set(PB_NODE_DEFS.map(n => n.cat))];
|
|
11334
|
+
el.innerHTML = cats.map(cat => {
|
|
11335
|
+
const nodes = PB_NODE_DEFS.filter(n => n.cat === cat);
|
|
11336
|
+
const catColor = PB_CAT_COLOR[cat] || '#6b7280';
|
|
11337
|
+
return `<div>
|
|
11338
|
+
<div class="pb-cat-hdr" onclick="pbToggleCat(this)">
|
|
11339
|
+
<span class="pb-cat-arrow">▸</span><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:${catColor};margin-right:5px;vertical-align:middle;flex-shrink:0"></span>${cat}
|
|
11340
|
+
</div>
|
|
11341
|
+
<div class="pb-cat-body ${cat === cats[0] ? '' : 'collapsed'}">
|
|
11342
|
+
${nodes.map((n,i) => `<button class="pb-node-btn" onclick="pbAddNode('${esc(n.type)}',PB_NODE_DEFS.filter(d=>d.cat==='${cat}')[${i}])" title="${esc(n.type)}${n.defaultConfig ? ' (op: '+n.defaultConfig.operation+')' : ''}">
|
|
11343
|
+
<span class="pb-node-icon">${n.icon}</span>${esc(n.label)}
|
|
11344
|
+
</button>`).join('')}
|
|
11345
|
+
</div>
|
|
11346
|
+
</div>`;
|
|
11347
|
+
}).join('');
|
|
11348
|
+
// Expand first category by default
|
|
11349
|
+
el.querySelector('.pb-cat-hdr')?.classList.add('expanded');
|
|
11350
|
+
const firstArrow = el.querySelector('.pb-cat-arrow');
|
|
11351
|
+
if (firstArrow) firstArrow.textContent = '▾';
|
|
11352
|
+
}
|
|
11353
|
+
|
|
11354
|
+
function pbToggleCat(hdr) {
|
|
11355
|
+
const body = hdr.nextElementSibling;
|
|
11356
|
+
const arrow = hdr.querySelector('.pb-cat-arrow');
|
|
11357
|
+
body.classList.toggle('collapsed');
|
|
11358
|
+
if (arrow) arrow.textContent = body.classList.contains('collapsed') ? '▸' : '▾';
|
|
11359
|
+
}
|
|
11360
|
+
|
|
11361
|
+
function pbNew() {
|
|
11362
|
+
pbState.playbook = { id: 'pb-' + Date.now(), name: 'Untitled', nodes: [], connections: [] };
|
|
11363
|
+
pbState.positions = {};
|
|
11364
|
+
pbState.runStatus = {};
|
|
11365
|
+
pbState.selectedId = null;
|
|
11366
|
+
document.getElementById('pb-name-input').value = 'Untitled';
|
|
11367
|
+
document.getElementById('pb-run-status').textContent = '';
|
|
11368
|
+
pbRenderCanvas();
|
|
11369
|
+
pbShowConfig(null);
|
|
11370
|
+
}
|
|
11371
|
+
|
|
11372
|
+
function pbAddNode(type, defOverride) {
|
|
11373
|
+
// defOverride lets palette pass the specific def when multiple defs share a type (e.g. service.gmail)
|
|
11374
|
+
const def = defOverride || PB_NODE_DEFS.find(n => n.type === type) || { label: type, icon: '⊹' };
|
|
11375
|
+
const id = 'n-' + Date.now() + '-' + Math.random().toString(36).slice(2, 6);
|
|
11376
|
+
const pb = pbState.playbook;
|
|
11377
|
+
// Offset new nodes so they don't stack
|
|
11378
|
+
const offset = pb.nodes.length * 20;
|
|
11379
|
+
pbState.positions[id] = { x: 60 + offset, y: 60 + offset };
|
|
11380
|
+
pb.nodes.push({ id, type, name: def.label, config: Object.assign({}, def.defaultConfig || {}) });
|
|
11381
|
+
const empty = document.getElementById('pb-empty');
|
|
11382
|
+
if (empty) empty.style.display = 'none';
|
|
11383
|
+
pbRenderCanvas();
|
|
11384
|
+
pbSelectNode(id);
|
|
11385
|
+
}
|
|
11386
|
+
|
|
11387
|
+
function pbRenderCanvas() {
|
|
11388
|
+
const canvas = document.getElementById('pb-canvas');
|
|
11389
|
+
const svg = document.getElementById('pb-svg');
|
|
11390
|
+
if (!canvas || !svg) return;
|
|
11391
|
+
|
|
11392
|
+
// Remove old node cards
|
|
11393
|
+
canvas.querySelectorAll('.pb-node-card').forEach(el => el.remove());
|
|
11394
|
+
|
|
11395
|
+
const pb = pbState.playbook;
|
|
11396
|
+
if (!pb.nodes.length) {
|
|
11397
|
+
const empty = document.getElementById('pb-empty');
|
|
11398
|
+
if (empty) empty.style.display = '';
|
|
11399
|
+
svg.innerHTML = '<defs><marker id="pb-arrow" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--text-xs)"/></marker></defs>';
|
|
11400
|
+
return;
|
|
11401
|
+
}
|
|
11402
|
+
const empty = document.getElementById('pb-empty');
|
|
11403
|
+
if (empty) empty.style.display = 'none';
|
|
11404
|
+
|
|
11405
|
+
// Render nodes
|
|
11406
|
+
for (const node of pb.nodes) {
|
|
11407
|
+
const pos = pbState.positions[node.id] || { x: 40, y: 40 };
|
|
11408
|
+
const def = PB_NODE_DEFS.find(n => n.type === node.type) || { icon: '⊹' };
|
|
11409
|
+
const st = pbState.runStatus[node.id] || '';
|
|
11410
|
+
const card = document.createElement('div');
|
|
11411
|
+
card.className = 'pb-node-card' + (node.id === pbState.selectedId ? ' selected' : '') + (st ? ' ns-' + st : '');
|
|
11412
|
+
card.dataset.id = node.id;
|
|
11413
|
+
card.style.left = pos.x + 'px';
|
|
11414
|
+
card.style.top = pos.y + 'px';
|
|
11415
|
+
card.innerHTML = `
|
|
11416
|
+
<div class="pb-node-hdr">
|
|
11417
|
+
<span class="pb-node-type-icon">${def.icon}</span>
|
|
11418
|
+
<span class="pb-node-title" title="${esc(node.type)}">${esc(node.name || node.type)}</span>
|
|
11419
|
+
<button class="pb-node-del" onclick="pbDeleteNode('${esc(node.id)}')" title="Remove">✕</button>
|
|
11420
|
+
</div>
|
|
11421
|
+
<div class="pb-node-body">
|
|
11422
|
+
${st ? `<div class="pb-node-status"><div class="pb-ns-dot ${st}"></div><span style="color:${st==='running'?'var(--accent)':st==='completed'?'var(--green)':'var(--red)'}">${st}</span></div>` : ''}
|
|
11423
|
+
<div style="font-size:10px;color:var(--text-xs)">${esc(node.type)}</div>
|
|
11424
|
+
</div>
|
|
11425
|
+
<button class="pb-node-conn-btn" title="Connect to another node" onclick="pbStartConnect('${esc(node.id)}',event)">→</button>
|
|
11426
|
+
`;
|
|
11427
|
+
card.addEventListener('mousedown', (e) => pbDragStart(node.id, e));
|
|
11428
|
+
card.addEventListener('click', (e) => { if (!e.defaultPrevented) pbSelectNode(node.id); });
|
|
11429
|
+
canvas.appendChild(card);
|
|
11430
|
+
}
|
|
11431
|
+
|
|
11432
|
+
// Render SVG connections
|
|
11433
|
+
const w = canvas.offsetWidth || 800;
|
|
11434
|
+
const h = canvas.offsetHeight || 600;
|
|
11435
|
+
svg.setAttribute('width', w);
|
|
11436
|
+
svg.setAttribute('height', h);
|
|
11437
|
+
let paths = '<defs><marker id="pb-arrow" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--text-xs)"/></marker></defs>';
|
|
11438
|
+
for (const conn of pb.connections) {
|
|
11439
|
+
const fromPos = pbState.positions[conn.from];
|
|
11440
|
+
const toPos = pbState.positions[conn.to];
|
|
11441
|
+
if (!fromPos || !toPos) continue;
|
|
11442
|
+
const x1 = fromPos.x + 160; const y1 = fromPos.y + 36;
|
|
11443
|
+
const x2 = toPos.x; const y2 = toPos.y + 36;
|
|
11444
|
+
const cx1 = x1 + 60; const cx2 = x2 - 60;
|
|
11445
|
+
const label = conn.handle ? `<text x="${(x1+x2)/2}" y="${(y1+y2)/2 - 5}" font-size="9" fill="var(--text-xs)" text-anchor="middle">${esc(conn.handle)}</text>` : '';
|
|
11446
|
+
paths += `<path d="M${x1},${y1} C${cx1},${y1} ${cx2},${y2} ${x2},${y2}" stroke="var(--border)" stroke-width="2" fill="none" marker-end="url(#pb-arrow)"/>${label}`;
|
|
11447
|
+
}
|
|
11448
|
+
svg.innerHTML = paths;
|
|
11449
|
+
}
|
|
11450
|
+
|
|
11451
|
+
function pbDragStart(nodeId, e) {
|
|
11452
|
+
if (e.target.classList.contains('pb-node-del') || e.target.classList.contains('pb-node-conn-btn')) return;
|
|
11453
|
+
e.preventDefault();
|
|
11454
|
+
const pos = pbState.positions[nodeId] || { x: 0, y: 0 };
|
|
11455
|
+
pbState._dragNode = nodeId;
|
|
11456
|
+
pbState._dragOff = { x: e.clientX - pos.x, y: e.clientY - pos.y };
|
|
11457
|
+
function onMove(ev) {
|
|
11458
|
+
const wrap = document.getElementById('pb-canvas-wrap');
|
|
11459
|
+
const rect = wrap ? wrap.getBoundingClientRect() : { left: 0, top: 0 };
|
|
11460
|
+
pbState.positions[nodeId] = { x: Math.max(0, ev.clientX - pbState._dragOff.x), y: Math.max(0, ev.clientY - pbState._dragOff.y) };
|
|
11461
|
+
const card = document.querySelector('.pb-node-card[data-id="' + nodeId + '"]');
|
|
11462
|
+
if (card) { card.style.left = pbState.positions[nodeId].x + 'px'; card.style.top = pbState.positions[nodeId].y + 'px'; }
|
|
11463
|
+
pbRenderSvg();
|
|
11464
|
+
}
|
|
11465
|
+
function onUp() {
|
|
11466
|
+
pbState._dragNode = null;
|
|
11467
|
+
document.removeEventListener('mousemove', onMove);
|
|
11468
|
+
document.removeEventListener('mouseup', onUp);
|
|
11469
|
+
}
|
|
11470
|
+
document.addEventListener('mousemove', onMove);
|
|
11471
|
+
document.addEventListener('mouseup', onUp);
|
|
11472
|
+
}
|
|
11473
|
+
|
|
11474
|
+
function pbRenderSvg() {
|
|
11475
|
+
const svg = document.getElementById('pb-svg');
|
|
11476
|
+
const canvas = document.getElementById('pb-canvas');
|
|
11477
|
+
if (!svg || !canvas) return;
|
|
11478
|
+
const w = canvas.offsetWidth || 800;
|
|
11479
|
+
const h = canvas.offsetHeight || 600;
|
|
11480
|
+
svg.setAttribute('width', w);
|
|
11481
|
+
svg.setAttribute('height', h);
|
|
11482
|
+
let paths = '<defs><marker id="pb-arrow" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><polygon points="0 0,8 3,0 6" fill="var(--text-xs)"/></marker></defs>';
|
|
11483
|
+
for (const conn of pbState.playbook.connections) {
|
|
11484
|
+
const fromPos = pbState.positions[conn.from];
|
|
11485
|
+
const toPos = pbState.positions[conn.to];
|
|
11486
|
+
if (!fromPos || !toPos) continue;
|
|
11487
|
+
const x1 = fromPos.x + 160; const y1 = fromPos.y + 36;
|
|
11488
|
+
const x2 = toPos.x; const y2 = toPos.y + 36;
|
|
11489
|
+
const cx1 = x1 + 60; const cx2 = x2 - 60;
|
|
11490
|
+
const label = conn.handle ? `<text x="${(x1+x2)/2}" y="${(y1+y2)/2 - 5}" font-size="9" fill="var(--text-xs)" text-anchor="middle">${esc(conn.handle)}</text>` : '';
|
|
11491
|
+
paths += `<path d="M${x1},${y1} C${cx1},${y1} ${cx2},${y2} ${x2},${y2}" stroke="var(--border)" stroke-width="2" fill="none" marker-end="url(#pb-arrow)"/>${label}`;
|
|
11492
|
+
}
|
|
11493
|
+
svg.innerHTML = paths;
|
|
11494
|
+
}
|
|
11495
|
+
|
|
11496
|
+
function pbStartConnect(fromId, e) {
|
|
11497
|
+
e.stopPropagation();
|
|
11498
|
+
e.preventDefault();
|
|
11499
|
+
if (pbState.connecting) {
|
|
11500
|
+
// Second click: connect from→to
|
|
11501
|
+
const toId = pbState.connecting === fromId ? null : fromId;
|
|
11502
|
+
if (toId) pbAddConnection(pbState._connectFrom, toId);
|
|
11503
|
+
pbState.connecting = null;
|
|
11504
|
+
pbState._connectFrom = null;
|
|
11505
|
+
document.querySelectorAll('.pb-node-card').forEach(c => c.style.outline = '');
|
|
11506
|
+
} else {
|
|
11507
|
+
pbState.connecting = fromId;
|
|
11508
|
+
pbState._connectFrom = fromId;
|
|
11509
|
+
// Highlight the from node
|
|
11510
|
+
const card = document.querySelector('.pb-node-card[data-id="' + fromId + '"]');
|
|
11511
|
+
if (card) card.style.outline = '2px solid var(--accent)';
|
|
11512
|
+
// Next click on any node's conn-btn will complete
|
|
11513
|
+
showToast('Connect mode', 'Click → on target node to connect', 'info');
|
|
11514
|
+
}
|
|
11515
|
+
}
|
|
11516
|
+
|
|
11517
|
+
function pbAddConnection(fromId, toId) {
|
|
11518
|
+
const handle = prompt('Handle (optional, e.g. true/false for if-node, or case value for switch):') || undefined;
|
|
11519
|
+
pbState.playbook.connections.push({ from: fromId, to: toId, handle: handle || undefined });
|
|
11520
|
+
pbRenderCanvas();
|
|
11521
|
+
}
|
|
11522
|
+
|
|
11523
|
+
function pbSelectNode(nodeId) {
|
|
11524
|
+
pbState.selectedId = nodeId;
|
|
11525
|
+
document.querySelectorAll('.pb-node-card').forEach(c => c.classList.toggle('selected', c.dataset.id === nodeId));
|
|
11526
|
+
pbShowConfig(nodeId);
|
|
11527
|
+
}
|
|
11528
|
+
|
|
11529
|
+
function pbShowConfig(nodeId) {
|
|
11530
|
+
const title = document.getElementById('pb-config-title');
|
|
11531
|
+
const body = document.getElementById('pb-config-body');
|
|
11532
|
+
if (!nodeId) {
|
|
11533
|
+
if (title) title.textContent = 'Select a node';
|
|
11534
|
+
if (body) body.innerHTML = '<div style="font-size:11px;color:var(--text-xs);margin-top:8px">Click a node on the canvas to configure it.</div>';
|
|
11535
|
+
return;
|
|
11536
|
+
}
|
|
11537
|
+
const node = pbState.playbook.nodes.find(n => n.id === nodeId);
|
|
11538
|
+
if (!node) return;
|
|
11539
|
+
const fields = PB_NODE_FIELDS[node.type] || [];
|
|
11540
|
+
if (title) title.textContent = node.name || node.type;
|
|
11541
|
+
if (!body) return;
|
|
11542
|
+
body.innerHTML = `
|
|
11543
|
+
<div class="pb-cfg-row">
|
|
11544
|
+
<div class="pb-cfg-lbl">Node Name</div>
|
|
11545
|
+
<input class="pb-cfg-inp" type="text" value="${esc(node.name || '')}" oninput="pbUpdateNodeName('${nodeId}',this.value)">
|
|
11546
|
+
</div>
|
|
11547
|
+
${fields.map(f => {
|
|
11548
|
+
const val = node.config[f.key] !== undefined ? node.config[f.key] : '';
|
|
11549
|
+
if (f.type === 'select') {
|
|
11550
|
+
return `<div class="pb-cfg-row"><div class="pb-cfg-lbl">${esc(f.label)}</div>
|
|
11551
|
+
<select class="pb-cfg-inp" onchange="pbUpdateConfig('${nodeId}','${f.key}',this.value)">
|
|
11552
|
+
${(f.options || []).map(o => `<option value="${esc(o)}" ${val === o ? 'selected' : ''}>${esc(o)}</option>`).join('')}
|
|
11553
|
+
</select></div>`;
|
|
11554
|
+
}
|
|
11555
|
+
if (f.type === 'textarea') {
|
|
11556
|
+
return `<div class="pb-cfg-row"><div class="pb-cfg-lbl">${esc(f.label)}</div>
|
|
11557
|
+
<textarea class="pb-cfg-inp" rows="4" placeholder="${esc(f.placeholder||'')}" oninput="pbUpdateConfig('${nodeId}','${f.key}',this.value)">${esc(String(val))}</textarea>${f.help ? `<div style="font-size:10px;color:var(--text-xs);margin-top:2px">${esc(f.help)}</div>` : ''}</div>`;
|
|
11558
|
+
}
|
|
11559
|
+
if (f.type === 'password') {
|
|
11560
|
+
return `<div class="pb-cfg-row"><div class="pb-cfg-lbl">${esc(f.label)}</div>
|
|
11561
|
+
<input class="pb-cfg-inp" type="password" value="${esc(String(val))}" placeholder="${esc(f.placeholder||'')}" oninput="pbUpdateConfig('${nodeId}','${f.key}',this.value)">${f.help ? `<div style="font-size:10px;color:var(--text-xs);margin-top:2px">${esc(f.help)}</div>` : ''}</div>`;
|
|
11562
|
+
}
|
|
11563
|
+
return `<div class="pb-cfg-row"><div class="pb-cfg-lbl">${esc(f.label)}</div>
|
|
11564
|
+
<input class="pb-cfg-inp" type="${f.type === 'number' ? 'number' : 'text'}" value="${esc(String(val))}" placeholder="${esc(f.placeholder||'')}" oninput="pbUpdateConfig('${nodeId}','${f.key}',this.value)">${f.help ? `<div style="font-size:10px;color:var(--text-xs);margin-top:2px">${esc(f.help)}</div>` : ''}</div>`;
|
|
11565
|
+
}).join('')}
|
|
11566
|
+
<div style="margin-top:14px;padding-top:10px;border-top:1px solid var(--border)">
|
|
11567
|
+
<div class="pb-cfg-lbl">Connections from this node</div>
|
|
11568
|
+
${pbState.playbook.connections.filter(c=>c.from===nodeId).map((c,i) => `
|
|
11569
|
+
<div style="display:flex;align-items:center;gap:6px;margin-top:4px;font-size:11px;color:var(--text-lo)">
|
|
11570
|
+
<span>→ ${esc(c.to.slice(0,10))}…${c.handle ? ' <em>['+esc(c.handle)+']</em>' : ''}</span>
|
|
11571
|
+
<button class="btn" style="font-size:10px;padding:1px 6px" onclick="pbRemoveConn(${i})">✕</button>
|
|
11572
|
+
</div>`).join('') || '<div style="font-size:11px;color:var(--text-xs);margin-top:4px">None yet. Use → button on canvas.</div>'}
|
|
11573
|
+
</div>
|
|
11574
|
+
`;
|
|
11575
|
+
}
|
|
11576
|
+
|
|
11577
|
+
function pbUpdateNodeName(id, name) {
|
|
11578
|
+
const node = pbState.playbook.nodes.find(n => n.id === id);
|
|
11579
|
+
if (node) { node.name = name; pbRenderCanvas(); }
|
|
11580
|
+
}
|
|
11581
|
+
|
|
11582
|
+
function pbUpdateConfig(id, key, value) {
|
|
11583
|
+
const node = pbState.playbook.nodes.find(n => n.id === id);
|
|
11584
|
+
if (node) node.config[key] = value;
|
|
11585
|
+
}
|
|
11586
|
+
|
|
11587
|
+
function pbDeleteNode(id) {
|
|
11588
|
+
pbState.playbook.nodes = pbState.playbook.nodes.filter(n => n.id !== id);
|
|
11589
|
+
pbState.playbook.connections = pbState.playbook.connections.filter(c => c.from !== id && c.to !== id);
|
|
11590
|
+
delete pbState.positions[id];
|
|
11591
|
+
delete pbState.runStatus[id];
|
|
11592
|
+
if (pbState.selectedId === id) { pbState.selectedId = null; pbShowConfig(null); }
|
|
11593
|
+
pbRenderCanvas();
|
|
11594
|
+
}
|
|
11595
|
+
|
|
11596
|
+
function pbRemoveConn(idx) {
|
|
11597
|
+
pbState.playbook.connections.splice(idx, 1);
|
|
11598
|
+
pbShowConfig(pbState.selectedId);
|
|
11599
|
+
pbRenderCanvas();
|
|
11600
|
+
}
|
|
11601
|
+
|
|
11602
|
+
function pbUpdateNodeStatus(nodeId) {
|
|
11603
|
+
const st = pbState.runStatus[nodeId] || '';
|
|
11604
|
+
const card = document.querySelector('.pb-node-card[data-id="' + nodeId + '"]');
|
|
11605
|
+
if (!card) return;
|
|
11606
|
+
card.className = 'pb-node-card' + (nodeId === pbState.selectedId ? ' selected' : '') + (st ? ' ns-' + st : '');
|
|
11607
|
+
const body = card.querySelector('.pb-node-body');
|
|
11608
|
+
if (body) body.innerHTML = `
|
|
11609
|
+
${st ? `<div class="pb-node-status"><div class="pb-ns-dot ${st}"></div><span style="color:${st==='running'?'var(--accent)':st==='completed'?'var(--green)':'var(--red)'}">${st}</span></div>` : ''}
|
|
11610
|
+
<div style="font-size:10px;color:var(--text-xs)">${esc(pbState.playbook.nodes.find(n=>n.id===nodeId)?.type||'')}</div>
|
|
11611
|
+
`;
|
|
11612
|
+
}
|
|
11613
|
+
|
|
11614
|
+
async function pbSave() {
|
|
11615
|
+
const pb = pbState.playbook;
|
|
11616
|
+
if (!pb.name || pb.name === 'Untitled') {
|
|
11617
|
+
pb.name = document.getElementById('pb-name-input').value || 'Untitled';
|
|
11618
|
+
}
|
|
11619
|
+
if (!pb.id) pb.id = 'pb-' + Date.now();
|
|
11620
|
+
// Save to .monomind/playbooks/<id>.json via API
|
|
11621
|
+
try {
|
|
11622
|
+
const resp = await fetch('/api/playbooks', {
|
|
11623
|
+
method: 'POST',
|
|
11624
|
+
headers: { 'Content-Type': 'application/json' },
|
|
11625
|
+
body: JSON.stringify(pb),
|
|
11626
|
+
});
|
|
11627
|
+
if (resp.ok) {
|
|
11628
|
+
showToast('Saved', 'Playbook saved to .monomind/playbooks/', 'ok');
|
|
11629
|
+
loadWorkflowDefs();
|
|
11630
|
+
} else {
|
|
11631
|
+
const txt = await resp.text();
|
|
11632
|
+
showToast('Save failed', txt, 'err');
|
|
11633
|
+
}
|
|
11634
|
+
} catch (e) {
|
|
11635
|
+
showToast('Save error', e.message, 'err');
|
|
11636
|
+
}
|
|
11637
|
+
}
|
|
11638
|
+
|
|
11639
|
+
async function pbLoad() {
|
|
11640
|
+
try {
|
|
11641
|
+
_wfDefs = await apiFetch('/api/workflow-defs');
|
|
11642
|
+
} catch { _wfDefs = []; }
|
|
11643
|
+
if (!_wfDefs.length) { showToast('No playbooks', 'No saved playbooks found', 'warn'); return; }
|
|
11644
|
+
const names = _wfDefs.map((d, i) => i + ': ' + (d.name || d.id)).join('\n');
|
|
11645
|
+
const choice = prompt('Select a playbook to load:\n' + names);
|
|
11646
|
+
const idx = parseInt(choice, 10);
|
|
11647
|
+
if (isNaN(idx) || !_wfDefs[idx]) return;
|
|
11648
|
+
pbLoadDef(_wfDefs[idx].id);
|
|
11649
|
+
}
|
|
11650
|
+
|
|
11651
|
+
async function pbLoadDef(playbookId) {
|
|
11652
|
+
try {
|
|
11653
|
+
// First try to read the saved JSON file directly via workflow-defs + file read
|
|
11654
|
+
// Try monobrowse API for full definition (includes node config)
|
|
11655
|
+
let pb = null;
|
|
11656
|
+
try {
|
|
11657
|
+
pb = await apiFetch('http://localhost:4243/api/playbooks/' + encodeURIComponent(playbookId));
|
|
11658
|
+
} catch {
|
|
11659
|
+
// Fall back: read the .json file via the defs list and reconstruct
|
|
11660
|
+
const defs = await apiFetch('/api/workflow-defs').catch(() => []);
|
|
11661
|
+
const def = defs.find(d => d.id === playbookId);
|
|
11662
|
+
if (!def) { showToast('Not found', 'Playbook not found: ' + playbookId, 'err'); return; }
|
|
11663
|
+
pb = { id: def.id, name: def.name, description: def.description || '', nodes: [], connections: [] };
|
|
11664
|
+
}
|
|
11665
|
+
pbState.playbook = pb;
|
|
11666
|
+
pbState.positions = {};
|
|
11667
|
+
pbState.runStatus = {};
|
|
11668
|
+
pbState.selectedId = null;
|
|
11669
|
+
pb.nodes.forEach((n, i) => {
|
|
11670
|
+
pbState.positions[n.id] = { x: 60 + (i % 4) * 200, y: 60 + Math.floor(i / 4) * 120 };
|
|
11671
|
+
});
|
|
11672
|
+
document.getElementById('pb-name-input').value = pb.name || '';
|
|
11673
|
+
document.getElementById('pb-run-status').textContent = '';
|
|
11674
|
+
pbRenderCanvas();
|
|
11675
|
+
pbShowConfig(null);
|
|
11676
|
+
showToast('Loaded', pb.name || playbookId, 'ok');
|
|
11677
|
+
} catch (e) {
|
|
11678
|
+
showToast('Load error', e.message, 'err');
|
|
11679
|
+
}
|
|
11680
|
+
}
|
|
11681
|
+
|
|
11682
|
+
function pbImportJSON() {
|
|
11683
|
+
const input = document.createElement('input');
|
|
11684
|
+
input.type = 'file';
|
|
11685
|
+
input.accept = '.json';
|
|
11686
|
+
input.onchange = () => {
|
|
11687
|
+
const file = input.files[0];
|
|
11688
|
+
if (!file) return;
|
|
11689
|
+
const reader = new FileReader();
|
|
11690
|
+
reader.onload = (ev) => {
|
|
11691
|
+
try {
|
|
11692
|
+
const pb = JSON.parse(ev.target.result);
|
|
11693
|
+
if (!pb.id || !pb.nodes) { showToast('Invalid JSON', 'Missing id or nodes', 'err'); return; }
|
|
11694
|
+
pbState.playbook = pb;
|
|
11695
|
+
pbState.positions = {};
|
|
11696
|
+
pbState.runStatus = {};
|
|
11697
|
+
pbState.selectedId = null;
|
|
11698
|
+
pb.nodes.forEach((n, i) => { pbState.positions[n.id] = { x: 60 + (i % 4) * 200, y: 60 + Math.floor(i / 4) * 120 }; });
|
|
11699
|
+
document.getElementById('pb-name-input').value = pb.name || '';
|
|
11700
|
+
pbRenderCanvas();
|
|
11701
|
+
showToast('Imported', pb.name || pb.id, 'ok');
|
|
11702
|
+
} catch { showToast('Parse error', 'Invalid JSON file', 'err'); }
|
|
11703
|
+
};
|
|
11704
|
+
reader.readAsText(file);
|
|
11705
|
+
};
|
|
11706
|
+
input.click();
|
|
11707
|
+
}
|
|
11708
|
+
|
|
11709
|
+
function pbExportJSON() {
|
|
11710
|
+
const pb = pbState.playbook;
|
|
11711
|
+
if (!pb.nodes.length) { showToast('Nothing to export', 'Add nodes first', 'warn'); return; }
|
|
11712
|
+
const json = JSON.stringify(pb, null, 2);
|
|
11713
|
+
const blob = new Blob([json], { type: 'application/json' });
|
|
11714
|
+
const url = URL.createObjectURL(blob);
|
|
11715
|
+
const a = document.createElement('a');
|
|
11716
|
+
a.href = url;
|
|
11717
|
+
a.download = (pb.id || 'playbook') + '.json';
|
|
11718
|
+
a.click();
|
|
11719
|
+
URL.revokeObjectURL(url);
|
|
11720
|
+
}
|
|
11721
|
+
|
|
11722
|
+
async function pbRun() {
|
|
11723
|
+
const pb = pbState.playbook;
|
|
11724
|
+
if (!pb.nodes.length) { showToast('Empty playbook', 'Add nodes first', 'warn'); return; }
|
|
11725
|
+
if (!pb.id) pb.id = 'pb-' + Date.now();
|
|
11726
|
+
await pbSave();
|
|
11727
|
+
pbState.runStatus = {};
|
|
11728
|
+
document.getElementById('pb-run-status').textContent = '● STARTING…';
|
|
11729
|
+
try {
|
|
11730
|
+
const resp = await fetch('http://localhost:4243/api/playbooks/' + encodeURIComponent(pb.id) + '/run', {
|
|
11731
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}',
|
|
11732
|
+
});
|
|
11733
|
+
if (resp.ok) {
|
|
11734
|
+
showToast('Running', pb.name || pb.id, 'ok');
|
|
11735
|
+
} else {
|
|
11736
|
+
const txt = await resp.text();
|
|
11737
|
+
showToast('Run failed', txt, 'err');
|
|
11738
|
+
document.getElementById('pb-run-status').textContent = '✗ FAILED';
|
|
11739
|
+
}
|
|
11740
|
+
} catch {
|
|
11741
|
+
showToast('Offline', 'Start monobrowse: npx monomind browse dashboard', 'warn');
|
|
11742
|
+
document.getElementById('pb-run-status').textContent = '✗ OFFLINE';
|
|
11743
|
+
}
|
|
11744
|
+
}
|
|
11745
|
+
|
|
11746
|
+
/* ═══════════════════════════════════════════════════════════════ */
|
|
11747
|
+
|
|
10110
11748
|
async function loadMonograph() {
|
|
10111
11749
|
if (_mgLoaded) return;
|
|
10112
11750
|
const loadDir = DIR; // snapshot DIR at call time
|
|
@@ -12191,9 +13829,10 @@ function mmRenderOrgs(body) {
|
|
|
12191
13829
|
if (!orgs.length) { body.innerHTML = '<div class="empty">No orgs found. Run /mastermind:createorg to create one.</div>'; return; }
|
|
12192
13830
|
body.innerHTML = orgs.map(o => {
|
|
12193
13831
|
const running = o.running;
|
|
13832
|
+
const _liveLabel = running ? (o.lastEventAt ? (Date.now()-o.lastEventAt<1800000 ? '🟢 LIVE' : Date.now()-o.lastEventAt<7200000 ? '🟡 QUIET' : '🔴 STALE') : '🟡 QUIET') : '';
|
|
12194
13833
|
return `<div class="mm-skill-item" data-org="${esc(o.name)}" onclick="closeMastermind();v2SelectOrg(this.dataset.org);switchView('orgs')">
|
|
12195
13834
|
<span class="mm-skill-name">${esc(o.name)}</span>
|
|
12196
|
-
<span class="mm-skill-desc">${esc((o.goal || '').slice(0, 60))} ${
|
|
13835
|
+
<span class="mm-skill-desc">${esc((o.goal || '').slice(0, 60))} ${_liveLabel}</span>
|
|
12197
13836
|
</div>`;
|
|
12198
13837
|
}).join('');
|
|
12199
13838
|
}
|