@hmj-ai/cflow 1.1.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/DESIGN.md +241 -0
- package/README.md +111 -0
- package/dist/public/assets/index-BqTfYp5s.js +15 -0
- package/dist/public/assets/index-D0BpmA_V.css +1 -0
- package/dist/public/index.html +15 -0
- package/dist/src/compiler.js +164 -0
- package/dist/src/contract.js +26 -0
- package/dist/src/db.js +208 -0
- package/dist/src/engine.js +487 -0
- package/dist/src/flow-agent.js +190 -0
- package/dist/src/hash.js +14 -0
- package/dist/src/proposal.js +595 -0
- package/dist/src/runtime-manifest.js +244 -0
- package/dist/src/runtime-process.js +175 -0
- package/dist/src/runtime.js +850 -0
- package/dist/src/server.js +652 -0
- package/dist/src/types.js +1 -0
- package/dist/src/workspace.js +26 -0
- package/package.json +68 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.react-flow{--xy-edge-stroke-default:#b1b1b7;--xy-edge-stroke-width-default:1;--xy-edge-stroke-selected-default:#555;--xy-connectionline-stroke-default:#b1b1b7;--xy-connectionline-stroke-width-default:1;--xy-attribution-background-color-default:#ffffff80;--xy-minimap-background-color-default:#fff;--xy-minimap-mask-background-color-default:#f0f0f099;--xy-minimap-mask-stroke-color-default:transparent;--xy-minimap-mask-stroke-width-default:1;--xy-minimap-node-background-color-default:#e2e2e2;--xy-minimap-node-stroke-color-default:transparent;--xy-minimap-node-stroke-width-default:2;--xy-background-color-default:transparent;--xy-background-pattern-dots-color-default:#91919a;--xy-background-pattern-lines-color-default:#eee;--xy-background-pattern-cross-color-default:#e2e2e2;background-color:var(--xy-background-color,var(--xy-background-color-default));--xy-node-color-default:inherit;--xy-node-border-default:1px solid #1a192b;--xy-node-background-color-default:#fff;--xy-node-group-background-color-default:#f0f0f040;--xy-node-boxshadow-hover-default:0 1px 4px 1px #00000014;--xy-node-boxshadow-selected-default:0 0 0 .5px #1a192b;--xy-node-border-radius-default:3px;--xy-handle-background-color-default:#1a192b;--xy-handle-border-color-default:#fff;--xy-selection-background-color-default:#0059dc14;--xy-selection-border-default:1px dotted #0059dccc;--xy-controls-button-background-color-default:#fefefe;--xy-controls-button-background-color-hover-default:#f4f4f4;--xy-controls-button-color-default:inherit;--xy-controls-button-color-hover-default:inherit;--xy-controls-button-border-color-default:#eee;--xy-controls-box-shadow-default:0 0 2px 1px #00000014;--xy-edge-label-background-color-default:#fff;--xy-edge-label-color-default:inherit;--xy-resize-background-color-default:#3367d9;direction:ltr}.react-flow.dark{--xy-edge-stroke-default:#3e3e3e;--xy-edge-stroke-width-default:1;--xy-edge-stroke-selected-default:#727272;--xy-connectionline-stroke-default:#b1b1b7;--xy-connectionline-stroke-width-default:1;--xy-attribution-background-color-default:#96969640;--xy-minimap-background-color-default:#141414;--xy-minimap-mask-background-color-default:#3c3c3c99;--xy-minimap-mask-stroke-color-default:transparent;--xy-minimap-mask-stroke-width-default:1;--xy-minimap-node-background-color-default:#2b2b2b;--xy-minimap-node-stroke-color-default:transparent;--xy-minimap-node-stroke-width-default:2;--xy-background-color-default:#141414;--xy-background-pattern-dots-color-default:#555;--xy-background-pattern-lines-color-default:#333;--xy-background-pattern-cross-color-default:#333;--xy-node-color-default:#f8f8f8;--xy-node-border-default:1px solid #3c3c3c;--xy-node-background-color-default:#1e1e1e;--xy-node-group-background-color-default:#f0f0f040;--xy-node-boxshadow-hover-default:0 1px 4px 1px #ffffff14;--xy-node-boxshadow-selected-default:0 0 0 .5px #999;--xy-handle-background-color-default:#bebebe;--xy-handle-border-color-default:#1e1e1e;--xy-selection-background-color-default:#c8c8dc14;--xy-selection-border-default:1px dotted #c8c8dccc;--xy-controls-button-background-color-default:#2b2b2b;--xy-controls-button-background-color-hover-default:#3e3e3e;--xy-controls-button-color-default:#f8f8f8;--xy-controls-button-color-hover-default:#fff;--xy-controls-button-border-color-default:#5b5b5b;--xy-controls-box-shadow-default:0 0 2px 1px #00000014;--xy-edge-label-background-color-default:#141414;--xy-edge-label-color-default:#f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props,var(--xy-background-color,var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke,var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width,var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke,var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width,var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{pointer-events:none;position:absolute;overflow:visible}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:.5s linear infinite dashdraw}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected,var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke,var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke,var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:.5s linear infinite dashdraw}svg.react-flow__connectionline{z-index:1001;position:absolute;overflow:visible}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{-webkit-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default;position:absolute}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:0 0;pointer-events:none}.react-flow__nodesselection-rect{pointer-events:all;cursor:grab;position:absolute}.react-flow__handle{pointer-events:none;background-color:var(--xy-handle-background-color,var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color,var(--xy-handle-border-color-default));border-radius:100%;width:6px;min-width:5px;height:6px;min-height:5px;position:absolute}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;bottom:0;left:50%;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{z-index:5;margin:15px;position:absolute}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px)translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px)translateY(-50%)}.react-flow__attribution{background:var(--xy-attribution-background-color,var(--xy-attribution-background-color-default));margin:0;padding:2px 3px;font-size:10px}.react-flow__attribution a{color:#999;text-decoration:none}@keyframes dashdraw{0%{stroke-dashoffset:10px}}.react-flow__edgelabel-renderer{pointer-events:none;-webkit-user-select:none;user-select:none;width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__viewport-portal{-webkit-user-select:none;user-select:none;width:100%;height:100%;position:absolute;top:0;left:0}.react-flow__minimap{background:var(--xy-minimap-background-color-props,var(--xy-minimap-background-color,var(--xy-minimap-background-color-default)))}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var(--xy-minimap-mask-background-color-props,var(--xy-minimap-mask-background-color,var(--xy-minimap-mask-background-color-default)));stroke:var(--xy-minimap-mask-stroke-color-props,var(--xy-minimap-mask-stroke-color,var(--xy-minimap-mask-stroke-color-default)));stroke-width:var(--xy-minimap-mask-stroke-width-props,var(--xy-minimap-mask-stroke-width,var(--xy-minimap-mask-stroke-width-default)))}.react-flow__minimap-node{fill:var(--xy-minimap-node-background-color-props,var(--xy-minimap-node-background-color,var(--xy-minimap-node-background-color-default)));stroke:var(--xy-minimap-node-stroke-color-props,var(--xy-minimap-node-stroke-color,var(--xy-minimap-node-stroke-color-default)));stroke-width:var(--xy-minimap-node-stroke-width-props,var(--xy-minimap-node-stroke-width,var(--xy-minimap-node-stroke-width-default)))}.react-flow__background-pattern.dots{fill:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-dots-color-default)))}.react-flow__background-pattern.lines{stroke:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-lines-color-default)))}.react-flow__background-pattern.cross{stroke:var(--xy-background-pattern-color-props,var(--xy-background-pattern-color,var(--xy-background-pattern-cross-color-default)))}.react-flow__controls{box-shadow:var(--xy-controls-box-shadow,var(--xy-controls-box-shadow-default));flex-direction:column;display:flex}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{background:var(--xy-controls-button-background-color,var(--xy-controls-button-background-color-default));border:none;border-bottom:1px solid var(--xy-controls-button-border-color-props,var(--xy-controls-button-border-color,var(--xy-controls-button-border-color-default)));width:26px;height:26px;color:var(--xy-controls-button-color-props,var(--xy-controls-button-color,var(--xy-controls-button-color-default)));cursor:pointer;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;padding:4px;display:flex}.react-flow__controls-button svg{fill:currentColor;width:100%;max-width:12px;max-height:12px}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{border-radius:var(--xy-node-border-radius,var(--xy-node-border-radius-default));width:150px;color:var(--xy-node-color,var(--xy-node-color-default));text-align:center;border:var(--xy-node-border,var(--xy-node-border-default));background-color:var(--xy-node-background-color,var(--xy-node-background-color-default));padding:10px;font-size:12px}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover,var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected,var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color,var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color,var(--xy-selection-background-color-default));border:var(--xy-selection-border,var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var(--xy-controls-button-background-color-hover-props,var(--xy-controls-button-background-color-hover,var(--xy-controls-button-background-color-hover-default)));color:var(--xy-controls-button-color-hover-props,var(--xy-controls-button-color-hover,var(--xy-controls-button-color-hover-default)))}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var(--xy-controls-button-border-color-props,var(--xy-controls-button-border-color,var(--xy-controls-button-border-color-default)))}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{background-color:var(--xy-resize-background-color,var(--xy-resize-background-color-default));border:1px solid #fff;border-radius:1px;width:5px;height:5px;translate:-50% -50%}.react-flow__resize-control.handle.left{top:50%;left:0}.react-flow__resize-control.handle.right{top:50%;left:100%}.react-flow__resize-control.handle.top{top:0;left:50%}.react-flow__resize-control.handle.bottom{top:100%;left:50%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color,var(--xy-resize-background-color-default));border-style:solid;border-width:0}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;height:100%;top:0;transform:translate(-50%)}.react-flow__resize-control.line.left{border-left-width:1px;left:0}.react-flow__resize-control.line.right{border-right-width:1px;left:100%}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{width:100%;height:1px;left:0;transform:translateY(-50%)}.react-flow__resize-control.line.top{border-top-width:1px;top:0}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-text{fill:var(--xy-edge-label-color,var(--xy-edge-label-color-default))}:root{color:#1d2126;font-synthesis:none;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;--paper:#f6f7f9;--paper-deep:#eef0f4;--surface:#fff;--white:#fff;--panel:#fbfbfc;--inset:#f2f4f7;--text:#1d2126;--muted:#5b636d;--faint:#8a919b;--border:#e5e8ec;--border-strong:#d2d7de;--accent:#35569e;--accent-deep:#2b4681;--accent-soft:#eef1f8;--green:#16794e;--green-soft:#e8f3ed;--red:#b0392d;--red-soft:#fbecea;--amber:#8f6314;--amber-soft:#fbf2df;--blue:#35569e;--orange:var(--accent);--orange-deep:var(--accent-deep);--ink:#f0f2f5;--ink-soft:#e9ecf1;--ink-line:#e5e8ec;--topbar-height:52px;--detail-width:300px;--agent-width:340px;--rail-width:50px;--drawer-height:min(38vh, 320px);--radius:9px;--shadow-pop:0 8px 24px #18202e1a;background:#f6f7f9;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Noto Sans CJK SC,Source Han Sans SC,PingFang SC,Microsoft YaHei,system-ui,sans-serif}*{box-sizing:border-box}html,body,#root{width:100%;height:100%;margin:0}body{min-width:1280px;overflow:hidden}button,input,select,textarea{font:inherit}button{color:inherit}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--accent);outline-offset:2px}button:disabled{cursor:not-allowed;opacity:.46}code,pre{font-family:SFMono-Regular,Consolas,Liberation Mono,monospace}.app-frame{grid-template-rows:var(--topbar-height) auto minmax(0, 1fr);width:100%;min-width:1280px;height:100%;display:grid;position:relative;overflow:hidden}.notice-stack{display:grid}.workbench{grid-template-columns:var(--detail-width) minmax(560px, 1fr) var(--agent-width);min-height:0;transition:grid-template-columns .18s cubic-bezier(.22,1,.36,1);display:grid;overflow:hidden}.workbench.left-collapsed{grid-template-columns:var(--rail-width) minmax(560px, 1fr) var(--agent-width)}.workbench.right-collapsed{grid-template-columns:var(--detail-width) minmax(560px, 1fr) var(--rail-width)}.workbench.left-collapsed.right-collapsed{grid-template-columns:var(--rail-width) minmax(560px, 1fr) var(--rail-width)}.workbench.is-empty{grid-template-columns:minmax(0,1fr)}.brand-mark{width:34px;height:34px;color:var(--accent);background:var(--accent-soft);border:1px solid var(--border);letter-spacing:.02em;border-radius:9px;flex:none;place-items:center;font-size:11px;font-weight:600;display:grid}.new-flow-button{height:36px;color:var(--accent);background:var(--surface);border:1px solid var(--border-strong);cursor:pointer;border-radius:8px;justify-content:center;align-items:center;gap:8px;font-size:12px;font-weight:600;display:flex}.new-flow-button:hover{background:var(--accent-soft);border-color:var(--accent)}.search-field{height:34px;color:var(--muted);background:var(--inset);border:1px solid var(--border);border-radius:7px;align-items:center;gap:7px;padding:0 9px;display:flex}.search-field input{width:100%;min-width:0;color:var(--text);background:0 0;border:0;outline:0;font-size:12px}.search-field input::placeholder{color:var(--faint)}.flow-list-item{width:100%;color:var(--muted);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:8px;grid-template-columns:10px minmax(0,1fr);gap:9px;padding:9px;display:grid;position:relative}.flow-list-item.has-action{cursor:default;grid-template-columns:minmax(0,1fr) 30px;gap:2px;padding:0}.flow-list-main{min-width:0;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;grid-template-columns:10px minmax(0,1fr);gap:9px;padding:9px 0 9px 9px;display:grid}.flow-list-delete{width:28px;height:28px;color:var(--faint);cursor:pointer;background:0 0;border:0;border-radius:7px;align-self:center;place-items:center;display:grid}.flow-list-item:hover{color:var(--text);background:var(--inset)}.flow-list-delete:hover:not(:disabled){color:var(--red);background:var(--red-soft)}.flow-list-item.is-current{color:var(--text);background:var(--accent-soft)}.flow-list-item.is-current:before{content:"";background:var(--orange);border-radius:0 2px 2px 0;width:3px;position:absolute;top:8px;bottom:8px;left:-9px}.flow-list-item>span:last-child,.flow-list-main>span:last-child{min-width:0}.flow-list-item strong,.flow-list-item small{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.flow-list-item strong{font-size:12px;font-weight:550}.flow-list-item small{color:var(--faint);margin-top:3px;font-size:10px}.route-lamp{background:var(--amber);border:1px solid var(--amber);border-radius:50%;width:8px;height:8px;margin-top:4px}.route-lamp.is-published{background:var(--green);border-color:var(--green)}.service-health{min-width:0;color:var(--faint);font-size:10px}.icon-button{width:34px;height:34px;color:var(--muted);background:var(--surface);border:1px solid var(--border);cursor:pointer;border-radius:8px;flex:none;place-items:center;padding:0;display:inline-grid}.icon-button:hover{color:var(--text);background:var(--white);border-color:var(--border-strong)}.icon-button.on-dark{color:var(--muted);background:0 0;border-color:#0000}.icon-button.on-dark:hover{color:var(--text);background:var(--inset);border-color:var(--border)}.route-state{background:var(--amber);border:2px solid var(--surface);width:11px;height:11px;box-shadow:0 0 0 1px var(--amber);border-radius:50%;flex:none}.route-state.is-running{background:var(--orange);box-shadow:0 0 0 1px var(--orange-deep)}.route-state.is-passed,.route-state.is-published{background:var(--green);box-shadow:0 0 0 1px #245c43}.route-state.is-failed{background:var(--red);box-shadow:0 0 0 1px #813326}.route-state.is-cancelled{background:var(--amber);box-shadow:0 0 0 1px var(--amber)}.button{min-height:34px;color:var(--text);background:var(--surface);border:1px solid var(--border-strong);cursor:pointer;white-space:nowrap;border-radius:7px;flex:none;justify-content:center;align-items:center;gap:7px;padding:0 11px;font-size:11px;font-weight:600;transition:background .15s,border-color .15s,transform .15s;display:inline-flex}.button:hover:not(:disabled){background:var(--white);border-color:var(--border-strong);transform:translateY(-1px)}.button.signal{color:#fff;background:var(--orange);border-color:var(--orange-deep)}.button.signal:hover:not(:disabled){background:var(--accent-deep)}.button.publish{color:var(--green);background:var(--surface);border-color:var(--border-strong)}.button.publish.signal{color:#fff;background:var(--green);border-color:var(--green)}.button.publish:hover:not(:disabled){border-color:var(--green)}.button.publish.signal:hover:not(:disabled){background:#12633f}.button.danger{color:var(--red);border-color:var(--red)}.button.full{width:100%}.button.is-active{color:var(--orange-deep);background:var(--accent-soft);border-color:var(--border-strong)}.count{background:var(--paper-deep);border-radius:9px;place-items:center;min-width:18px;height:18px;padding:0 5px;font-size:9px;display:grid}.save-state{color:var(--muted);align-items:center;gap:5px;font-size:10px;display:flex}.save-state.is-dirty{color:var(--amber)}.save-state.is-error{color:var(--red)}.operation-notice{color:#314037;background:var(--inset);border-bottom:1px solid var(--border);flex:none;grid-template-columns:10px minmax(0,1fr) 28px;align-items:center;gap:9px;min-height:44px;padding:6px 10px 6px 16px;display:grid}.operation-notice.error{color:var(--red);background:var(--red-soft);border-color:var(--border-strong)}.operation-notice.success{color:#24583f;background:var(--green-soft);border-color:var(--green)}.operation-notice strong,.operation-notice p{margin:0;display:block}.operation-notice strong{font-size:11px}.operation-notice p{margin-top:1px;font-size:10px}.operation-notice>button{color:currentColor;cursor:pointer;background:0 0;border:0;width:28px;height:28px;font-size:18px}.approval-notice{color:#63451e;background:var(--amber-soft);border-bottom:1px solid var(--amber);flex:none;grid-template-columns:10px minmax(0,1fr) auto;align-items:center;gap:10px;min-height:56px;padding:8px 12px 8px 16px;display:grid}.workspace-unavailable-notice{min-height:52px;color:var(--red);background:var(--red-soft);border-bottom:1px solid var(--border-strong);flex:none;grid-template-columns:10px minmax(0,1fr);align-items:center;gap:10px;padding:8px 16px;display:grid}.workspace-unavailable-notice .status-lamp{background:var(--red)}.workspace-unavailable-notice strong,.workspace-unavailable-notice p{margin:0}.workspace-unavailable-notice strong{font-size:11px}.workspace-unavailable-notice p{margin-top:2px;font-size:10px}.approval-notice>div:last-child{gap:7px;display:flex}.approval-notice strong,.approval-notice p{margin:0;display:block}.approval-notice strong{font-size:11px}.approval-notice p{margin-top:2px;font-size:10px}.directory-picker{background:var(--surface);border:1px solid var(--border-strong);border-radius:12px;flex-direction:column;flex:1;width:min(760px,100% - 64px);min-height:0;margin:34px auto;display:flex;overflow:hidden}.directory-picker-heading{border-bottom:1px solid var(--border);align-items:center;gap:12px;padding:18px 20px;display:flex}.directory-picker-mark{width:42px;height:42px;color:var(--accent);background:var(--accent-soft);border-radius:10px;flex:none;place-items:center;display:grid}.directory-picker-heading h2,.directory-picker-heading p{margin:0}.directory-picker-heading h2{letter-spacing:-.008em;font-size:17px}.directory-picker-heading p{color:var(--muted);margin-top:3px;font-size:11px}.directory-jump{padding:14px 18px 10px}.directory-jump-row{gap:7px;display:flex}.directory-breadcrumbs{gap:3px;padding:0 18px 10px;display:flex;overflow-x:auto}.directory-breadcrumbs button{color:var(--muted);cursor:pointer;background:0 0;border:0;border-radius:5px;flex:none;padding:3px 6px;font-size:10px}.directory-breadcrumbs button:hover{color:var(--text);background:var(--paper-deep)}.directory-list{background:var(--white);border:1px solid var(--border);border-radius:8px;flex:1;min-height:180px;margin:0 18px;overflow-y:auto}.directory-list>button{text-align:left;border:0;border-bottom:1px solid var(--border);cursor:pointer;background:0 0;grid-template-columns:20px minmax(0,1fr);align-items:center;gap:8px;width:100%;padding:9px 11px;display:grid}.directory-list>button:hover{background:var(--paper)}.directory-list>button span,.directory-list>button strong,.directory-list>button small{min-width:0;display:block}.directory-list>button strong{font-size:11px}.directory-list>button small{color:var(--muted);text-overflow:ellipsis;white-space:nowrap;margin-top:2px;font:9px SFMono-Regular,monospace;overflow:hidden}.directory-empty{min-height:140px;color:var(--muted);justify-content:center;align-items:center;gap:7px;font-size:11px;display:flex}.directory-error{color:var(--red);margin:8px 18px 0;font-size:10px}.directory-picker-footer{border-top:1px solid var(--border);justify-content:space-between;align-items:center;gap:12px;padding:14px 18px;display:flex}.directory-picker-footer>span{min-width:0;color:var(--muted);text-overflow:ellipsis;white-space:nowrap;font:10px SFMono-Regular,monospace;overflow:hidden}.signal-emblem{width:58px;height:58px;color:var(--accent);background:var(--accent-soft);border:1px solid var(--border);box-shadow:none;border-radius:14px;place-items:center;margin:0 auto 17px;display:grid}.view-kicker{color:var(--orange-deep);letter-spacing:.06em;font-size:10px;font-weight:600}.messages{gap:18px;max-width:760px;margin:0 auto;display:grid}.message{grid-template-columns:30px minmax(0,1fr);gap:11px;display:grid}.message-avatar{width:30px;height:30px;color:var(--inset);background:var(--ink);border-radius:7px;place-items:center;font-size:10px;font-weight:600;display:grid}.message.is-user .message-avatar{color:#fff;background:var(--orange)}.message strong{font-size:11px}.message p{white-space:pre-wrap;max-width:70ch;margin:4px 0 0;font-size:13px;line-height:1.55}.message small{color:var(--muted);margin-top:5px;font:10px SFMono-Regular,Consolas,monospace;display:block}.thinking{color:var(--muted);align-items:center;gap:7px;display:flex}.goal-composer{background:var(--white);border:1px solid var(--border-strong);border-radius:12px;flex:none;width:min(790px,100% - 48px);margin:0 auto 18px;box-shadow:0 9px 26px #353a311c}.goal-composer:focus-within{border-color:var(--orange);box-shadow:0 0 0 2px #35569e26}.goal-composer textarea{resize:vertical;width:100%;min-height:74px;max-height:180px;color:var(--text);background:0 0;border:0;outline:0;padding:13px 14px 6px;font-size:14px;line-height:1.5;display:block}.goal-composer textarea::placeholder{color:var(--muted)}.visually-hidden{clip:rect(0, 0, 0, 0)!important;white-space:nowrap!important;border:0!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;position:absolute!important;overflow:hidden!important}.skill-attachments{background:var(--surface);border:1px solid var(--border);border-radius:8px;margin:2px 12px 8px;padding:9px 10px 10px}.attachment-summary{justify-content:space-between;align-items:center;gap:12px;margin-bottom:8px;display:flex}.attachment-summary>div{align-items:baseline;gap:8px;min-width:0;display:flex}.attachment-summary strong{color:var(--text);font-size:11px}.attachment-summary span{color:var(--muted);font-size:10px}.attachment-clear{color:var(--muted);cursor:pointer;background:0 0;border:0;border-radius:4px;padding:2px 4px;font-size:10px}.attachment-clear:hover{color:var(--red)}.attachment-list{grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin:0;padding:0;list-style:none;display:grid}.skill-attachment{min-width:0;height:30px;color:var(--text);background:var(--white);border:1px solid var(--border);border-radius:6px;align-items:center;gap:6px;padding:0 5px 0 8px;font-size:10px;display:flex}.skill-attachment>svg{color:var(--orange-deep);flex:none}.skill-attachment>span{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.skill-attachment small{color:var(--muted);white-space:nowrap;flex:none}.skill-attachment button{width:24px;height:24px;color:var(--muted);cursor:pointer;background:0 0;border:0;border-radius:5px;flex:none;place-items:center;padding:0;display:grid}.skill-attachment button:hover{color:var(--red);background:var(--red-soft)}.attachment-overflow{color:var(--muted);margin:7px 0 0;font-size:10px}.attachment-button{border:1px solid var(--border);background:var(--surface);color:var(--text);cursor:pointer;border-radius:6px;align-items:center;gap:6px;height:30px;padding:0 8px;font-size:10px;display:inline-flex}.attachment-button:hover{border-color:var(--orange);color:var(--orange)}.attachment-button:disabled{cursor:not-allowed;opacity:.55}.composer-footer{align-items:center;gap:8px;min-height:44px;padding:4px 7px 7px 10px;display:flex}.composer-footer>span{color:var(--muted);flex:1;font-size:10px}.runtime-select{background:var(--surface);border:1px solid var(--border);border-radius:6px;align-items:center;gap:5px;height:30px;padding:0 7px;display:flex}.runtime-select select{background:0 0;border:0;outline:0;max-width:150px;font-size:11px}.send-button{color:#fff;background:var(--orange);border:1px solid var(--orange-deep);cursor:pointer;border-radius:8px;place-items:center;width:34px;height:34px;display:grid}.canvas-view{flex-direction:column;flex:1;min-width:0;min-height:0;display:flex;overflow:hidden}.canvas-workspace{flex-direction:row;flex:1;min-width:0;min-height:0;display:flex;overflow:hidden}.canvas-toolbar{background:var(--surface);border-bottom:1px solid var(--border);flex:none;align-items:center;gap:7px;min-height:48px;padding:7px 10px;display:flex;overflow-x:auto}.toolbar-divider{background:var(--border);flex:none;width:1px;height:22px}.library-tray{background:var(--paper-deep);border-right:1px solid var(--border);flex-direction:column;flex:0 0 268px;width:268px;min-width:268px;min-height:0;display:flex;overflow:hidden}.library-tray.is-collapsed{flex-basis:50px;width:50px;min-width:50px}.library-header{flex:none;align-items:center;gap:8px;min-height:46px;padding:8px 8px 6px 12px;display:flex}.library-header>div{flex:1;min-width:0}.library-header strong,.library-header span{display:block}.library-header strong{font-size:12px;font-weight:600}.library-header span{color:var(--muted);margin-top:2px;font-size:10px}.library-toggle{flex:none}.library-search{height:32px;color:var(--muted);background:var(--surface);border:1px solid var(--border);border-radius:7px;flex:none;align-items:center;gap:6px;margin:0 10px 8px;padding:0 8px;display:flex}.library-search input{width:100%;min-width:0;color:var(--text);background:0 0;border:0;outline:0;font-size:12px}.library-search input::placeholder{color:var(--muted)}.library-scroll{flex:1;min-height:0;padding:0 8px 14px;overflow-y:auto}.library-group+.library-group{margin-top:10px}.library-group-header{width:100%;min-height:28px;color:var(--text);background:0 0;border:0;align-items:center;gap:6px;padding:0 4px;font-size:11px;font-weight:600;display:flex}.library-group-header strong{color:var(--muted);margin-left:auto;font-size:10px;font-weight:600}.library-group-body{gap:8px;padding:6px 0 4px;display:grid}.library-card{background:var(--surface);border:1px solid var(--border);border-radius:8px;overflow:hidden}.library-card-row{display:block}.library-card-main{text-align:left;cursor:pointer;background:0 0;border:0;grid-template-columns:34px minmax(0,1fr) 16px;align-items:center;gap:8px;min-width:0;padding:8px 6px 8px 8px;display:grid}.library-card-main strong,.library-card-main small{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.library-card-main strong{font-size:11px}.library-card-main small{color:var(--muted);margin-top:3px;font-size:9px}.library-card-main:hover{color:var(--text);background:var(--white)}.library-empty{color:var(--muted);margin:4px 6px 8px;font-size:11px;line-height:1.5}.library-rail-button{width:100%;height:100%;color:var(--muted);cursor:pointer;writing-mode:vertical-rl;letter-spacing:.08em;background:0 0;border:0;align-items:center;gap:12px;padding:14px 0;font-size:10px;font-weight:600;display:flex}.library-rail-button:hover{color:var(--text);background:#fbfaf5b3}.capability-mark{color:#fff;background:var(--blue);border-radius:6px;place-items:center;width:32px;height:32px;font-size:9px;font-weight:600;display:grid}.capability-mark.candidate{color:#6e4719;background:var(--amber-soft)}.flow-canvas-shell{background:var(--paper);flex:1;min-height:0}.react-flow__viewport{cursor:grab}.react-flow__viewport:active{cursor:grabbing}.react-flow__node{cursor:grab}.react-flow__node.dragging{cursor:grabbing}.react-flow__edge-path{stroke:#68746b;stroke-width:2.5px}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge:focus .react-flow__edge-path{stroke:var(--orange);stroke-width:3.5px}.react-flow__edge-text{fill:var(--text);font-size:10px}.react-flow__edge-textbg{fill:var(--surface)}.react-flow__controls{border:1px solid var(--border-strong);border-radius:8px;overflow:hidden;box-shadow:0 3px 8px #2e342d1a}.react-flow__controls-button{color:var(--text);background:var(--surface);border-bottom-color:var(--border)}.react-flow__controls-button:hover{background:var(--white)}.react-flow__minimap{border:1px solid var(--border-strong);box-shadow:none;border-radius:8px}.flow-card{width:190px;color:var(--text);background:var(--white);border:1px solid var(--border-strong);border-radius:11px;overflow:hidden;box-shadow:0 5px 14px #2e342d1c}.flow-card:hover,.flow-card.is-selected{border-color:var(--orange);box-shadow:0 0 0 2px #ce6f322e,0 7px 18px #2e342d26}.flow-card.is-running,.flow-card.is-pending,.flow-card.is-waiting-approval{border-color:#ce6f3273}.flow-card.is-completed,.flow-card.is-published{border-color:#2f7d5559}.flow-card.is-failed,.flow-card.is-blocked{border-color:#b5474759}.flow-card.is-inactive,.flow-card.is-draft{border-color:var(--border-strong)}.flow-card-head{color:var(--muted);justify-content:space-between;align-items:center;padding:8px 10px 6px;display:flex}.node-kind{color:var(--blue);text-transform:uppercase;font-size:9px;font-weight:600}.flow-card-branch .node-kind{color:var(--orange-deep)}.flow-card-join .node-kind{color:var(--green)}.flow-card-approval .node-kind{color:var(--amber)}.flow-card-output .node-kind{color:var(--ink)}.node-index{color:var(--faint);font:9px SFMono-Regular,Consolas,monospace}.flow-card>strong{-webkit-line-clamp:2;-webkit-box-orient:vertical;padding:0 10px;font-size:14px;line-height:1.35;display:-webkit-box;overflow:hidden}.flow-card>p{height:34px;color:var(--muted);margin:4px 0 8px;padding:0 10px;font-size:10px;line-height:1.45;overflow:hidden}.flow-card-bindings{min-height:28px;color:var(--muted);gap:3px;padding:0 10px 7px;font-size:9px;line-height:1.35;display:grid}.flow-card-bindings span{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.flow-card-bindings .is-bound{color:var(--green)}.flow-card-bindings .is-unbound{color:var(--faint)}.flow-card-config{color:var(--orange-deep);background:var(--accent-soft);border:1px solid var(--border-strong);text-overflow:ellipsis;white-space:nowrap;border-radius:5px;margin:0 10px 7px;padding:3px 5px;font-size:8px;overflow:hidden}.flow-card-foot{color:var(--muted);background:var(--paper);border-top:1px solid var(--border);align-items:center;gap:6px;padding:7px 10px;font-size:9px;display:flex}.flow-handle{background:var(--surface)!important;border:3px solid var(--orange)!important;width:11px!important;height:11px!important}.flow-handle:hover{transform:scale(1.25)}.entry-card{width:105px;height:40px;color:var(--accent);background:var(--surface);border:1px solid var(--accent);box-shadow:none;border-radius:20px;align-items:center;gap:8px;padding:0 12px;font-size:10px;font-weight:600;display:flex;position:relative}.entry-signal{background:var(--accent);border-radius:50%;width:7px;height:7px}.canvas-readout{color:var(--muted);border:1px solid var(--border);background:#fbfaf5f0;border-radius:7px;align-items:center;gap:8px;font-size:9px;display:flex;padding:6px 9px!important}.canvas-readout strong{color:var(--text);font:11px SFMono-Regular,Consolas,monospace}.agent-chat{flex-direction:column;gap:12px;min-height:100%;padding:14px;display:flex}.agent-context{background:var(--paper);border:1px solid var(--border);border-radius:8px;gap:10px;padding:11px;display:grid}.agent-context-heading{align-items:center;gap:8px;min-width:0;display:flex}.agent-context-heading>div{flex:1;gap:2px;min-width:0;display:grid}.agent-context-heading strong{font-size:12px}.agent-context-heading span:not(.agent-icon):not(.agent-live-mark){color:var(--muted);text-overflow:ellipsis;white-space:nowrap;font-size:9px;overflow:hidden}.agent-icon{width:26px;height:26px;color:var(--orange-deep);background:var(--inset);border-radius:7px;flex:none;place-items:center;display:grid}.agent-live-mark{background:var(--green);border:1px solid var(--green);border-radius:50%;flex:none;width:7px;height:7px}.agent-context-facts{flex-wrap:wrap;gap:5px;display:flex}.agent-context-facts span{color:var(--muted);background:var(--surface);border:1px solid var(--border);border-radius:5px;padding:3px 6px;font:9px SFMono-Regular,Consolas,monospace}.agent-fault-context{color:var(--red);background:var(--red-soft);border:1px solid var(--border-strong);border-radius:8px;align-items:flex-start;gap:8px;padding:10px 11px;display:flex}.agent-fault-context>svg{flex:none;margin-top:1px}.agent-fault-context strong,.agent-fault-context p{margin:0;display:block}.agent-fault-context strong{font-size:10px}.agent-fault-context p{overflow-wrap:anywhere;margin-top:3px;font:9px/1.45 SFMono-Regular,Consolas,monospace}.agent-messages{flex:1;align-content:start;gap:14px;display:grid}.agent-empty{justify-items:start;gap:5px;padding:8px 2px 4px;display:grid}.agent-empty-mark{width:28px;height:28px;color:var(--orange-deep);background:var(--inset);border-radius:7px;place-items:center;margin-bottom:2px;display:grid}.agent-empty>strong{font-size:12px}.agent-empty>p{color:var(--muted);margin:0;font-size:10px;line-height:1.5}.agent-starters{flex-wrap:wrap;gap:6px;margin-top:5px;display:flex}.agent-starters button{color:var(--orange-deep);background:var(--accent-soft);border:1px solid var(--border-strong);cursor:pointer;border-radius:6px;padding:5px 7px;font-size:9px;font-weight:600}.agent-starters button:hover{background:var(--accent-soft)}.agent-message{grid-template-columns:24px minmax(0,1fr);align-items:start;gap:8px;display:grid}.agent-message-avatar{width:24px;height:24px;color:var(--inset);background:var(--ink);border-radius:6px;place-items:center;font-size:9px;font-weight:600;display:grid}.agent-message.is-user .agent-message-avatar{color:#fff;background:var(--orange)}.agent-message-body{min-width:0}.agent-message-body>strong{font-size:10px}.agent-message-body>p{color:var(--text);white-space:pre-wrap;margin:4px 0 0;font-size:11px;line-height:1.55}.agent-message-body>small{color:var(--muted);margin-top:5px;font:8px SFMono-Regular,Consolas,monospace;display:block}.agent-actions{gap:5px;margin-top:8px;display:grid}.agent-action{color:var(--orange-deep);text-align:left;background:var(--accent-soft);border:1px solid var(--border-strong);cursor:pointer;border-radius:6px;align-items:flex-start;gap:7px;padding:8px;display:flex}.agent-action:hover{background:var(--accent-soft)}.agent-action>svg{flex:none;margin-top:1px}.agent-action>span{gap:2px;min-width:0;display:grid}.agent-action strong,.agent-action small{display:block}.agent-action strong{font-size:9px}.agent-action small{color:var(--muted);font-size:9px;line-height:1.4}.agent-composer{background:var(--white);border:1px solid var(--border-strong);border-radius:9px;flex:none}.agent-composer:focus-within{border-color:var(--orange);box-shadow:0 0 0 2px #35569e26}.agent-composer textarea{resize:vertical;width:100%;min-height:62px;max-height:140px;color:var(--text);background:0 0;border:0;outline:0;padding:10px 11px 4px;font-size:11px;line-height:1.5;display:block}.agent-composer textarea::placeholder{color:var(--muted)}.agent-composer-footer{align-items:center;gap:7px;min-height:38px;padding:3px 6px 6px 9px;display:flex}.agent-composer-footer>span{color:var(--muted);flex:1;font-size:9px}.inspector-section{gap:12px;padding:18px;display:grid}.inspector-section+.inspector-section{border-top:1px solid var(--border)}.inspector-section h3{margin:0;font-size:12px;font-weight:600}.section-title-row{justify-content:space-between;align-items:center;gap:10px;display:flex}.section-title-row>div{align-items:center;gap:7px;display:flex}.section-icon{width:26px;height:26px;color:var(--orange-deep);background:var(--inset);border-radius:6px;place-items:center;display:grid}.tag{color:var(--muted);background:var(--paper-deep);border-radius:10px;padding:3px 7px;font:9px SFMono-Regular,Consolas,monospace}.tag.is-running,.tag.is-waiting-approval{color:var(--amber);background:var(--amber-soft)}.tag.is-completed,.tag.is-published{color:#285f46;background:var(--green-soft)}.tag.is-failed,.tag.is-blocked,.tag.is-cancelled{color:var(--red);background:var(--red-soft)}.tag.is-inactive,.tag.is-draft{color:var(--muted)}.field{gap:5px;display:grid}.field>span{color:var(--muted);font-size:10px;font-weight:600}.field input,.field select,.field textarea{width:100%;min-width:0;height:34px;color:var(--text);background:var(--white);border:1px solid var(--border-strong);border-radius:7px;padding:0 9px;font-size:11px}.field textarea{resize:vertical;min-height:82px;padding:8px 9px;line-height:1.45}.field small{color:var(--muted);font-size:9px;line-height:1.5}.workspace-path-row{gap:6px;display:flex}.workspace-path-row input{font-family:SFMono-Regular,Consolas,monospace}.field small.workspace-ok{color:var(--green)}.field small.workspace-error{color:var(--red)}.agent-disabled-note{color:var(--red);background:var(--red-soft);border-radius:7px;margin:0 0 8px;padding:8px 9px;font-size:10px;line-height:1.45}.field-auto-context{gap:6px}.auto-context-note{color:var(--muted);background:var(--paper-deep);border:1px solid var(--border);border-radius:7px;gap:3px;padding:10px 11px;font-size:11px;line-height:1.45;display:grid}.auto-context-note strong{color:var(--text);font-size:11px;font-weight:600}.field input:focus,.field select:focus,.field textarea:focus{border-color:var(--orange);outline:0;box-shadow:0 0 0 2px #ce6f3221}.effects-field{gap:8px;padding-top:2px}.effects-field>span{margin-bottom:1px;display:block}.effect-warning{color:var(--amber);line-height:1.45}.checkbox-row{min-height:34px;color:var(--text);background:var(--white);border:1px solid var(--border);cursor:pointer;border-radius:7px;align-items:center;gap:9px;padding:7px 10px;font-size:11px;line-height:1.4;transition:border-color .16s,background-color .16s;display:flex}.checkbox-row:hover{border-color:var(--border-strong);background:var(--surface)}.checkbox-row:focus-within{border-color:var(--orange);box-shadow:0 0 0 2px #ce6f3221}.field .checkbox-row input[type=checkbox]{width:16px;min-width:16px;height:16px;accent-color:var(--orange);flex:0 0 16px;margin:0;padding:0}.branch-conditions{background:var(--paper-deep);border:1px solid var(--border);border-radius:9px;gap:10px;padding:12px;display:grid}.branch-conditions-heading,.branch-condition-row{align-items:flex-start;gap:8px;display:flex}.branch-conditions-heading{justify-content:space-between;align-items:center}.branch-conditions-heading strong,.branch-conditions-heading span{display:block}.branch-conditions-heading strong{font-size:11px}.branch-conditions-heading span{color:var(--muted);margin-top:3px;font-size:9px}.branch-condition-row{border-top:1px solid var(--border);padding-top:10px}.branch-condition-index{width:24px;height:24px;color:var(--orange-deep);background:var(--orange-soft);border-radius:6px;flex:none;place-items:center;font:10px SFMono-Regular,Consolas,monospace;display:grid}.branch-condition-fields{flex:1;gap:8px;min-width:0;display:grid}.branch-condition-fields .field textarea{min-height:64px}.section-help{color:var(--muted);margin:0;font-size:10px;line-height:1.5}.property-list{border-top:1px solid var(--border)}.property-list>div{border-bottom:1px solid var(--border);justify-content:space-between;align-items:center;padding:8px 0;font-size:10px;display:flex}.property-list span{color:var(--muted)}.property-list strong{font:10px SFMono-Regular,Consolas,monospace}.compile-status{background:var(--paper);border:1px solid var(--border);border-radius:8px;align-items:flex-start;gap:9px;padding:10px;display:flex}.compile-status.ready{background:var(--green-soft);border-color:var(--green)}.compile-status.ready .status-lamp{background:var(--green)}.compile-status strong,.compile-status p{margin:0;display:block}.compile-status strong{font-size:10px}.compile-status p{color:var(--muted);margin-top:3px;font-size:9px;line-height:1.45}.activity-list{gap:2px;display:grid}.activity-list article,.activity-list-item{width:100%;color:inherit;text-align:left;border:0;border-bottom:1px solid var(--border);background:0 0;grid-template-columns:10px minmax(0,1fr);align-items:start;gap:8px;padding:9px 0;display:grid}.activity-list-item{cursor:pointer;appearance:none}.activity-list-item:hover,.activity-list-item.is-selected{background:var(--paper-deep)}.activity-list-item>div{min-width:0}.activity-list strong{font-size:10px}.activity-list p{overflow-wrap:anywhere;color:var(--muted);margin:2px 0 0;font:9px SFMono-Regular,Consolas,monospace}.activity-list small{color:var(--faint);margin-top:4px;font:8px SFMono-Regular,Consolas,monospace;display:block}.run-result{background:var(--paper);border:1px solid var(--border);border-radius:8px;align-items:flex-start;gap:8px;padding:10px 11px;display:flex}.run-result strong{font-size:11px;display:block}.run-result p{color:var(--muted);margin:2px 0 0;font-size:9px}.run-output{background:var(--paper);border:1px solid var(--border);border-radius:8px;gap:6px;padding:10px 11px;display:grid}.run-output>span{color:var(--muted);font-size:9px;font-weight:600}.run-output pre{color:var(--text);white-space:pre-wrap;word-break:break-word;margin:0;font:9px/1.5 SFMono-Regular,Consolas,monospace;overflow:auto}.empty-panel{color:var(--muted);text-align:center;background:var(--paper);border:1px solid var(--border);border-radius:8px;margin:18px;padding:20px 14px;font-size:10px;line-height:1.6}.empty-panel.compact{margin:0}.inline-alert{color:var(--red);background:var(--red-soft);border-bottom:1px solid var(--border-strong);flex:none;gap:10px;padding:11px 28px;font-size:11px;display:flex}.inline-alert strong{flex:none}.code-section{background:#101814;border:1px solid #26332b;border-radius:9px;margin-bottom:14px;overflow:hidden}.code-section>header{min-height:42px;color:var(--border);background:#1b2720;border-bottom:1px solid #344239;justify-content:space-between;align-items:center;gap:16px;padding:8px 12px;display:flex}.code-section>header>div{align-items:center;gap:7px;display:flex}.code-section h3{margin:0;font-size:11px}.code-section header code{max-width:70%;color:var(--faint);text-overflow:ellipsis;white-space:nowrap;font-size:9px;overflow:hidden}.code-section pre{max-height:460px;color:var(--border);tab-size:2;margin:0;padding:14px;font-size:10px;line-height:1.6;overflow:auto}.settings-view{flex-direction:column;flex:1;min-width:0;min-height:0;display:flex}.settings-layer{inset:var(--topbar-height) 0 0 0;z-index:8;background:var(--paper);box-shadow:var(--shadow-pop);display:flex;position:absolute}.settings-header{background:var(--surface);border-bottom:1px solid var(--border);flex:none;align-items:center;gap:12px;min-height:88px;padding:16px 24px;display:flex}.settings-header .button{margin-left:auto}.settings-emblem{width:40px;height:40px;color:var(--accent);background:var(--accent-soft);border-radius:9px;place-items:center;display:grid}.settings-header h2{margin:0;font-size:18px}.settings-header p{color:var(--muted);margin:3px 0 0;font-size:11px}.settings-scroll{flex:1;min-height:0;overflow-y:auto}.settings-section-main{width:min(820px,100% - 48px);margin:0 auto;padding:25px 0}.settings-section-main+.settings-section-main{border-top:1px solid var(--border-strong)}.settings-section-title{margin-bottom:16px}.settings-section-heading{justify-content:space-between;align-items:flex-start;gap:18px;margin-bottom:16px;display:flex}.settings-section-heading .settings-section-title{margin-bottom:0}.settings-section-heading .button{flex:none}.runtime-discovery-warning{color:var(--red);background:var(--red-soft);border:1px solid color-mix(in srgb, var(--red) 28%, var(--border));overflow-wrap:anywhere;border-radius:7px;align-items:flex-start;gap:8px;margin:0 0 12px;padding:9px 10px;font-size:10px;line-height:1.5;display:flex}.runtime-discovery-warning svg{flex:none;margin-top:1px}.settings-section-title h3{margin:0;font-size:14px}.settings-section-title p{color:var(--muted);margin:4px 0 0;font-size:10px}.settings-section-main form{gap:13px;display:grid}.settings-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;display:grid}.runtime-table{background:var(--surface);border-top:1px solid var(--border-strong)}.runtime-table article{border-bottom:1px solid var(--border);grid-template-columns:10px minmax(0,1fr) 88px 104px;align-items:center;gap:10px;padding:12px 10px;display:grid}.runtime-table strong{font-size:11px}.runtime-table p{color:var(--muted);margin:2px 0;font-size:10px}.runtime-table code,.runtime-table small{color:var(--faint);font-size:9px}.runtime-health{color:var(--muted);font-size:10px}.status-lamp{background:var(--amber);border-radius:50%;flex:none;width:8px;height:8px;display:inline-block;box-shadow:inset 0 1px 1px #ffffffa6,0 1px 3px #1e28203d}.status-lamp.is-completed,.status-lamp.is-available{background:var(--green)}.status-lamp.is-running,.status-lamp.is-checking{background:var(--orange)}.status-lamp.is-failed,.status-lamp.is-unavailable,.status-lamp.is-cancelled{background:var(--red)}.status-lamp.is-disabled{background:var(--faint)}.spin{animation:.8s linear infinite spin}@keyframes spin{to{transform:rotate(360deg)}}.boot-screen{width:100%;height:100%;color:var(--muted);background:var(--paper);place-content:center;justify-items:center;gap:12px;display:grid}.boot-screen p{margin:0;font-size:11px}.boot-screen.error strong{color:var(--red)}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}.tech-disclosure{background:var(--surface);border:1px solid var(--border);border-radius:8px;margin:12px 0;padding:8px 10px 10px}.tech-disclosure>summary{cursor:pointer;color:var(--text);font-size:12px;font-weight:600}.tech-disclosure>summary+*{margin-top:10px}.inspector-callout{background:var(--paper-deep);border-radius:8px;gap:3px;margin-bottom:12px;padding:10px 11px;display:grid}.inspector-callout strong,.inspector-callout span{display:block}.inspector-callout strong{font-size:12px}.inspector-callout span{color:var(--muted);font-size:11px;line-height:1.5}.directory-options{justify-content:space-between;align-items:center;gap:12px;padding:10px 20px 0;display:flex}.quiet-toggle{color:var(--muted);cursor:pointer;align-items:center;gap:7px;font-size:11px;display:inline-flex}.quiet-toggle input{width:13px;height:13px;accent-color:var(--accent);margin:0}.link-button{color:var(--accent);cursor:pointer;background:0 0;border:0;padding:0;font-size:11px;font-weight:600}.link-button:hover{text-decoration:underline}.settings-form-actions{justify-content:flex-end;display:flex}.top-bar{background:var(--surface);border-bottom:1px solid var(--border-strong);align-items:center;gap:10px;min-width:0;padding:0 10px 0 12px;display:flex}.top-bar-meta{color:var(--muted);flex:none;align-items:center;gap:6px;font-size:11px;display:flex}.top-bar-dot{color:var(--faint)}.toolbar-spacer{flex:1;min-width:12px}.flow-switcher{flex:0 auto;min-width:0;position:relative}.flow-switcher-trigger{min-width:0;max-width:380px;height:34px;color:var(--text);background:var(--surface);border:1px solid var(--border-strong);cursor:pointer;border-radius:8px;align-items:center;gap:8px;padding:0 9px;font-size:13px;font-weight:600;display:flex}.flow-switcher-trigger:hover{background:var(--inset)}.flow-switcher-name{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.flow-switcher-menu{z-index:7;background:var(--surface);border:1px solid var(--border-strong);width:380px;max-height:min(60vh,440px);box-shadow:var(--shadow-pop);border-radius:9px;flex-direction:column;padding:8px;display:flex;position:absolute;top:calc(100% + 6px);left:0}.flow-switcher-list{flex:1;min-height:0;margin:8px 0 0;padding:0;list-style:none;overflow-y:auto}.flow-switcher-list .flow-list-item{grid-template-columns:minmax(0,1fr) auto 30px;align-items:center}.flow-switcher-empty{color:var(--muted);margin:14px 8px;font-size:11px}.flow-switcher-new{color:var(--accent);background:var(--surface);border:1px solid var(--border);cursor:pointer;border-radius:8px;flex:none;justify-content:center;align-items:center;gap:7px;margin-top:8px;padding:9px;font-size:12px;font-weight:600;display:flex}.flow-switcher-new:hover{background:var(--accent-soft);border-color:var(--accent)}.detail-panel,.agent-panel{background:var(--surface);flex-direction:column;min-width:0;min-height:0;display:flex;overflow:hidden}.detail-panel{border-right:1px solid var(--border)}.agent-panel{border-left:1px solid var(--border)}.center-column{background:var(--paper);flex-direction:column;min-width:0;min-height:0;display:flex;overflow:hidden}.panel-rail{width:100%;height:100%;color:var(--muted);cursor:pointer;letter-spacing:.08em;writing-mode:vertical-rl;background:0 0;border:0;justify-content:center;align-items:center;padding:15px 0;font-size:10px;font-weight:600;display:flex}.panel-rail:hover{color:var(--text);background:var(--inset)}.detail-layout{flex-direction:column;flex:1;min-height:0;display:flex}.detail-heading{border-bottom:1px solid var(--border);flex:none;padding:14px 16px 11px}.detail-heading h2{letter-spacing:-.008em;margin:0;font-size:15px;font-weight:600}.detail-heading p{color:var(--muted);margin:3px 0 0;font-size:11px}.detail-scroll{flex:1;min-height:0;overflow-y:auto}.agent-panel .agent-chat{height:100%;min-height:0;overflow:hidden}.agent-panel .agent-context,.agent-panel .agent-fault-context,.agent-panel .agent-composer{flex:none}.agent-panel .agent-messages{flex:1;min-height:0;overflow-y:auto}.agent-attachments{background:var(--inset);border:1px solid var(--border);border-radius:8px;gap:6px;padding:8px;display:grid}.agent-regenerate-note{color:var(--muted);margin:0;font-size:10px}.goal-view{flex-direction:column;flex:1;min-height:0;display:flex}.goal-scroll{flex:1;min-height:0;padding:38px max(26px,50% - 380px) 24px;overflow-y:auto}.goal-intro{text-align:center;max-width:650px;margin:6vh auto 34px}.bottom-drawer{height:var(--drawer-height);background:var(--surface);border-top:1px solid var(--border-strong);flex-direction:column;flex:none;min-height:0;display:flex}.drawer-tabs{border-bottom:1px solid var(--border);flex:none;align-items:center;gap:2px;height:36px;padding:0 8px 0 10px;display:flex}.drawer-tabs button[role=tab]{height:100%;color:var(--muted);cursor:pointer;background:0 0;border:0;align-items:center;gap:6px;padding:0 11px;font-size:11px;font-weight:600;display:flex;position:relative}.drawer-tabs button[role=tab][aria-selected=true]{color:var(--text)}.drawer-tabs button[role=tab][aria-selected=true]:after{content:"";background:var(--accent);height:2px;position:absolute;bottom:-1px;left:6px;right:6px}.drawer-close{margin-left:auto}.drawer-body{flex:1;min-height:0;overflow-y:auto}.run-log{grid-template-columns:minmax(0,1fr) 232px;min-height:0;display:grid}.run-log-main{min-width:0;padding:12px 14px 18px}.run-log-runs{border-left:1px solid var(--border);min-width:0;padding:12px 14px 18px}.run-log-runs h3{color:var(--muted);letter-spacing:.08em;text-transform:uppercase;margin:0 0 8px;font-size:10px;font-weight:600}.run-log-summary{background:var(--inset);border-radius:8px;align-items:flex-start;gap:9px;margin-bottom:12px;padding:10px 11px;display:flex}.run-log-summary strong{font-size:12px;font-weight:600;display:block}.run-log-summary p{color:var(--muted);margin:2px 0 0;font-size:11px}.run-log-output{max-width:320px;color:var(--muted);margin:0 0 0 auto;font-size:10px;overflow:auto}.run-log-group+.run-log-group{margin-top:10px}.run-log-group{border:1px solid var(--border);border-radius:9px;overflow:hidden}.run-log-head{width:100%;color:inherit;text-align:left;background:var(--inset);border:0;border-bottom:1px solid var(--border);cursor:pointer;align-items:center;gap:10px;padding:9px 11px;display:flex}.run-log-head.is-static{cursor:default}.run-log-head:not(.is-static):hover{background:var(--accent-soft)}.run-log-order{color:var(--faint);font:10px SFMono-Regular,Consolas,monospace}.run-log-title{flex:1;min-width:0}.run-log-title strong{text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:600;display:block;overflow:hidden}.run-log-title small{color:var(--muted);margin-top:2px;font-size:10px;display:block}.run-log-state{color:var(--muted);flex:none;align-items:center;gap:5px;font-size:10px;display:flex}.run-log-time{color:var(--faint);flex:none;font-size:10px}.run-log-group .activity-list{padding:4px 6px 6px}.run-log-group .tech-disclosure{margin:0 8px 8px}.check-panel{flex-direction:column;min-height:0;display:flex}.check-bar{border-bottom:1px solid var(--border);flex:none;align-items:center;gap:9px;padding:10px 14px;display:flex}.check-runtime{color:var(--muted);align-items:center;gap:7px;font-size:11px;display:flex}.check-runtime select{min-width:160px;height:30px;color:var(--text);background:var(--surface);border:1px solid var(--border-strong);border-radius:7px;padding:0 8px}.check-counts{color:var(--muted);font-size:11px}.check-body{flex:1;min-height:0;padding:12px 14px 18px}.check-steps{gap:4px;margin:0;padding:0;list-style:none;display:grid}.check-steps li{background:var(--inset);border-radius:7px;align-items:center;gap:9px;padding:7px 10px;font-size:12px;display:flex}.check-step-kind{min-width:34px;color:var(--accent);font-size:9px;font-weight:600}.button.is-icon{width:34px;padding:0}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="color-scheme" content="light" />
|
|
7
|
+
<link rel="icon" href="data:," />
|
|
8
|
+
<title>CF Platform</title>
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-BqTfYp5s.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-D0BpmA_V.css">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="root"></div>
|
|
14
|
+
</body>
|
|
15
|
+
</html>
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { sha256 } from './hash.js';
|
|
2
|
+
import { assertContractSchema } from './contract.js';
|
|
3
|
+
function assert(condition, message) {
|
|
4
|
+
if (!condition)
|
|
5
|
+
throw new Error(message);
|
|
6
|
+
}
|
|
7
|
+
function checkGraph(nodes, edges, entries) {
|
|
8
|
+
const adj = new Map();
|
|
9
|
+
for (const i of nodes)
|
|
10
|
+
adj.set(i, []);
|
|
11
|
+
for (const e of edges)
|
|
12
|
+
if (typeof e.from === 'number')
|
|
13
|
+
adj.get(e.from).push(e.to);
|
|
14
|
+
const seen = new Set(), visiting = new Set();
|
|
15
|
+
const visit = (n) => {
|
|
16
|
+
if (visiting.has(n))
|
|
17
|
+
throw new Error('NON_TERMINATING_PATH: cycle');
|
|
18
|
+
if (seen.has(n))
|
|
19
|
+
return;
|
|
20
|
+
visiting.add(n);
|
|
21
|
+
for (const x of adj.get(n) ?? [])
|
|
22
|
+
visit(x);
|
|
23
|
+
visiting.delete(n);
|
|
24
|
+
seen.add(n);
|
|
25
|
+
};
|
|
26
|
+
entries.forEach(visit);
|
|
27
|
+
assert(seen.size === nodes.length, 'UNREACHABLE_NODE: all nodes must be reachable');
|
|
28
|
+
}
|
|
29
|
+
export function compileCF(draft) {
|
|
30
|
+
if (draft.effects) {
|
|
31
|
+
const validTypes = new Set(['file-read', 'file-write', 'command']);
|
|
32
|
+
for (const effect of draft.effects) {
|
|
33
|
+
assert(validTypes.has(effect.type), 'CF_EFFECT_TYPE_INVALID');
|
|
34
|
+
assert(effect.scope === 'workspace', 'CF_EFFECT_SCOPE_INVALID');
|
|
35
|
+
assert(typeof effect.description === 'string' && effect.description.trim().length > 0, 'CF_EFFECT_DESCRIPTION_REQUIRED');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const inputContract = draft.inputContract ?? { type: 'object' };
|
|
39
|
+
const outputContract = draft.outputContract ?? { type: 'object' };
|
|
40
|
+
assertContractSchema(inputContract, 'CF_INPUT');
|
|
41
|
+
assertContractSchema(outputContract, 'CF_OUTPUT');
|
|
42
|
+
assert(draft.does.trim().length > 0, 'CF_DOES_REQUIRED');
|
|
43
|
+
const task = [
|
|
44
|
+
draft.does.trim(),
|
|
45
|
+
draft.input?.trim() ? `Input guidance: ${draft.input.trim()}` : undefined,
|
|
46
|
+
draft.output?.trim() ? `Output guidance: ${draft.output.trim()}` : undefined,
|
|
47
|
+
draft.process?.trim() ? `Process constraints: ${draft.process.trim()}` : undefined,
|
|
48
|
+
]
|
|
49
|
+
.filter(Boolean)
|
|
50
|
+
.join('\n');
|
|
51
|
+
const program = {
|
|
52
|
+
version: '0.2',
|
|
53
|
+
cfId: draft.cfId,
|
|
54
|
+
sourceRevision: draft.revision,
|
|
55
|
+
task,
|
|
56
|
+
};
|
|
57
|
+
const normalizedDraft = { ...draft, inputContract, outputContract };
|
|
58
|
+
return {
|
|
59
|
+
cfId: draft.cfId,
|
|
60
|
+
version: `${draft.revision}.0.0`,
|
|
61
|
+
draft: normalizedDraft,
|
|
62
|
+
program,
|
|
63
|
+
programHash: sha256({ inputContract, outputContract, program }),
|
|
64
|
+
createdAt: new Date().toISOString(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function compileFlow(draft, versions) {
|
|
68
|
+
assert(draft.workspaceRoot?.trim().length > 0, 'FLOW_WORKSPACE_REQUIRED');
|
|
69
|
+
assert(draft.nodes.length > 0, 'FLOW_EMPTY');
|
|
70
|
+
assert((draft.limits?.maxConcurrency ?? 2) > 0, 'FLOW_INVALID_CONCURRENCY');
|
|
71
|
+
assert((draft.limits?.maxNodeDispatches ?? 32) > 0, 'FLOW_INVALID_DISPATCH_LIMIT');
|
|
72
|
+
const ids = new Set();
|
|
73
|
+
draft.nodes.forEach((n) => {
|
|
74
|
+
assert(n.id.trim().length > 0, 'FLOW_NODE_ID_REQUIRED');
|
|
75
|
+
assert(!ids.has(n.id), 'FLOW_DUPLICATE_NODE');
|
|
76
|
+
ids.add(n.id);
|
|
77
|
+
if (n.kind === 'cf-call') {
|
|
78
|
+
assert(versions.has(`${n.cfRef.cfId}@${n.cfRef.version}`), `UNKNOWN_CF:${n.cfRef.cfId}@${n.cfRef.version}`);
|
|
79
|
+
assertContractSchema(n.inputContract, `FLOW_INPUT_${n.id}`);
|
|
80
|
+
assertContractSchema(n.outputContract, `FLOW_OUTPUT_${n.id}`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const index = new Map(draft.nodes.map((n, i) => [n.id, i]));
|
|
84
|
+
const entries = draft.edges
|
|
85
|
+
.filter((e) => e.from === '$entry')
|
|
86
|
+
.map((e) => index.get(e.to))
|
|
87
|
+
.filter((n) => n !== undefined);
|
|
88
|
+
assert(entries.length > 0, 'FLOW_NO_ENTRY');
|
|
89
|
+
const edgeIds = new Set();
|
|
90
|
+
const edges = draft.edges.map((e) => {
|
|
91
|
+
assert(e.id.trim().length > 0, 'FLOW_EDGE_ID_REQUIRED');
|
|
92
|
+
assert(!edgeIds.has(e.id), 'FLOW_DUPLICATE_EDGE');
|
|
93
|
+
edgeIds.add(e.id);
|
|
94
|
+
const to = index.get(e.to);
|
|
95
|
+
assert(to !== undefined, `BAD_EDGE_TO:${e.to}`);
|
|
96
|
+
const from = e.from === '$entry' ? '$entry' : index.get(e.from);
|
|
97
|
+
assert(from !== undefined, `BAD_EDGE_FROM:${e.from}`);
|
|
98
|
+
return { ...e, from, to };
|
|
99
|
+
});
|
|
100
|
+
checkGraph([...index.values()], edges, entries);
|
|
101
|
+
assert(draft.nodes.some((n) => n.kind === 'output'), 'FLOW_NO_OUTPUT');
|
|
102
|
+
for (const node of draft.nodes) {
|
|
103
|
+
const outgoing = edges.filter((edge) => edge.from === index.get(node.id));
|
|
104
|
+
if (node.kind === 'output')
|
|
105
|
+
assert(outgoing.length === 0, 'OUTPUT_MUST_BE_TERMINAL');
|
|
106
|
+
else
|
|
107
|
+
assert(outgoing.length > 0, `NON_TERMINAL_NODE:${node.id}`);
|
|
108
|
+
if (node.kind === 'branch') {
|
|
109
|
+
assert(node.cases.length > 0, `BRANCH_CASES_REQUIRED:${node.id}`);
|
|
110
|
+
assert(new Set(node.cases).size === node.cases.length &&
|
|
111
|
+
node.cases.every((caseId) => caseId.trim().length > 0), `BRANCH_CASE_IDS_INVALID:${node.id}`);
|
|
112
|
+
if (node.caseConditions)
|
|
113
|
+
for (const caseId of node.cases)
|
|
114
|
+
assert(node.caseConditions[caseId]?.trim().length, `BRANCH_CONDITION_REQUIRED:${node.id}:${caseId}`);
|
|
115
|
+
const cases = outgoing.map((edge) => edge.when?.outcome === 'branch-case' ? edge.when.caseId : undefined);
|
|
116
|
+
assert(cases.every(Boolean) &&
|
|
117
|
+
new Set(cases).size === node.cases.length &&
|
|
118
|
+
node.cases.every((caseId) => cases.includes(caseId)), `BRANCH_CASES_INCOMPLETE:${node.id}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const e of edges) {
|
|
122
|
+
if (e.when?.outcome === 'branch-case') {
|
|
123
|
+
const source = typeof e.from === 'number' ? draft.nodes[e.from] : undefined;
|
|
124
|
+
assert(source?.kind === 'branch', 'BRANCH_EDGE_SOURCE_REQUIRED');
|
|
125
|
+
assert(source.cases.includes(e.when.caseId ?? ''), 'UNKNOWN_BRANCH_CASE');
|
|
126
|
+
}
|
|
127
|
+
if (e.when?.outcome === 'approved' || e.when?.outcome === 'rejected') {
|
|
128
|
+
const source = typeof e.from === 'number' ? draft.nodes[e.from] : undefined;
|
|
129
|
+
assert(source?.kind === 'approval', 'APPROVAL_EDGE_SOURCE_REQUIRED');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (draft.resources) {
|
|
133
|
+
const requirementIds = new Set();
|
|
134
|
+
for (const requirement of draft.resources) {
|
|
135
|
+
assert(requirement.id.trim().length > 0, 'RESOURCE_REQUIREMENT_ID_REQUIRED');
|
|
136
|
+
assert(!requirementIds.has(requirement.id), 'DUPLICATE_RESOURCE_REQUIREMENT');
|
|
137
|
+
requirementIds.add(requirement.id);
|
|
138
|
+
assert(requirement.type.trim().length > 0, `RESOURCE_TYPE_REQUIRED:${requirement.id}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const nodes = draft.nodes.map((n, i) => ({
|
|
142
|
+
...n,
|
|
143
|
+
index: i,
|
|
144
|
+
...(n.kind === 'cf-call'
|
|
145
|
+
? { programHash: versions.get(`${n.cfRef.cfId}@${n.cfRef.version}`).programHash }
|
|
146
|
+
: {}),
|
|
147
|
+
}));
|
|
148
|
+
const base = {
|
|
149
|
+
version: '0.6',
|
|
150
|
+
flowId: draft.flowId,
|
|
151
|
+
flowVersion: `${draft.revision}.0.0`,
|
|
152
|
+
objective: draft.objective,
|
|
153
|
+
workspaceRoot: draft.workspaceRoot,
|
|
154
|
+
entries,
|
|
155
|
+
nodes,
|
|
156
|
+
edges,
|
|
157
|
+
...(draft.resources ? { resources: draft.resources } : {}),
|
|
158
|
+
limits: {
|
|
159
|
+
maxConcurrency: draft.limits?.maxConcurrency ?? 2,
|
|
160
|
+
maxNodeDispatches: draft.limits?.maxNodeDispatches ?? 32,
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
return { ...base, planHash: sha256(base) };
|
|
164
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import AjvModule from 'ajv';
|
|
2
|
+
const Ajv = AjvModule.default ?? AjvModule;
|
|
3
|
+
const ajv = new Ajv({ allErrors: true, strict: false, removeAdditional: false });
|
|
4
|
+
export function assertContractSchema(contract, label) {
|
|
5
|
+
if (contract === undefined || contract === null)
|
|
6
|
+
return;
|
|
7
|
+
try {
|
|
8
|
+
ajv.compile(contract);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
throw new Error(`${label}_CONTRACT_INVALID: ${error instanceof Error ? error.message : String(error)}`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function assertContract(contract, value, label) {
|
|
15
|
+
if (contract === undefined || contract === null)
|
|
16
|
+
return;
|
|
17
|
+
let validate;
|
|
18
|
+
try {
|
|
19
|
+
validate = ajv.compile(contract);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw new Error(`${label}_CONTRACT_INVALID: ${error instanceof Error ? error.message : String(error)}`);
|
|
23
|
+
}
|
|
24
|
+
if (!validate(value))
|
|
25
|
+
throw new Error(`${label}_CONTRACT_FAILED: ${ajv.errorsText(validate.errors)}`);
|
|
26
|
+
}
|
package/dist/src/db.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { mkdirSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
export class Store {
|
|
5
|
+
db;
|
|
6
|
+
constructor(file = process.env.CF_DB ?? './data/cf.sqlite') {
|
|
7
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
8
|
+
this.db = new Database(file);
|
|
9
|
+
this.db.pragma('journal_mode = WAL');
|
|
10
|
+
this.db.pragma('busy_timeout = 5000');
|
|
11
|
+
this.db.exec(`CREATE TABLE IF NOT EXISTS cf_drafts (id TEXT PRIMARY KEY, value TEXT NOT NULL); CREATE TABLE IF NOT EXISTS cf_versions (id TEXT PRIMARY KEY, value TEXT NOT NULL); CREATE TABLE IF NOT EXISTS flow_drafts (id TEXT PRIMARY KEY, value TEXT NOT NULL); CREATE TABLE IF NOT EXISTS flow_versions (id TEXT PRIMARY KEY, value TEXT NOT NULL); CREATE TABLE IF NOT EXISTS flow_compilations (id TEXT PRIMARY KEY, flow_id TEXT NOT NULL, flow_revision INTEGER NOT NULL, mode TEXT NOT NULL, value TEXT NOT NULL, created_at TEXT NOT NULL); CREATE TABLE IF NOT EXISTS runs (id TEXT PRIMARY KEY, flow_version_id TEXT NOT NULL, status TEXT NOT NULL, value TEXT NOT NULL, input TEXT NOT NULL DEFAULT '{}', resource_profile_id TEXT, resources TEXT NOT NULL DEFAULT '[]', created_at TEXT NOT NULL, updated_at TEXT NOT NULL); CREATE TABLE IF NOT EXISTS ledger_events (run_id TEXT NOT NULL, seq INTEGER NOT NULL, type TEXT NOT NULL, node INTEGER, data TEXT, at TEXT NOT NULL, PRIMARY KEY(run_id, seq)); CREATE TABLE IF NOT EXISTS jobs (id TEXT PRIMARY KEY, run_id TEXT NOT NULL UNIQUE, status TEXT NOT NULL, lease_until INTEGER, attempts INTEGER NOT NULL DEFAULT 0); CREATE TABLE IF NOT EXISTS approvals (run_id TEXT NOT NULL, node INTEGER NOT NULL, decision TEXT, decided_at TEXT, PRIMARY KEY(run_id,node)); CREATE TABLE IF NOT EXISTS resource_profiles (id TEXT PRIMARY KEY, value TEXT NOT NULL); CREATE TABLE IF NOT EXISTS runtime_profiles (id TEXT PRIMARY KEY, runtime_id TEXT NOT NULL, profile_version INTEGER NOT NULL, value TEXT NOT NULL, created_at TEXT NOT NULL); CREATE TABLE IF NOT EXISTS runtime_current (runtime_id TEXT PRIMARY KEY, profile_id TEXT NOT NULL); CREATE TABLE IF NOT EXISTS workspace_settings (id TEXT PRIMARY KEY, value TEXT NOT NULL);`);
|
|
12
|
+
}
|
|
13
|
+
saveFlowCompilation(snapshot) {
|
|
14
|
+
this.db
|
|
15
|
+
.prepare('INSERT OR REPLACE INTO flow_compilations(id,flow_id,flow_revision,mode,value,created_at) VALUES(?,?,?,?,?,?)')
|
|
16
|
+
.run(snapshot.id, snapshot.flowId, snapshot.flowRevision, snapshot.mode, JSON.stringify(snapshot), snapshot.createdAt);
|
|
17
|
+
}
|
|
18
|
+
flowCompilations() {
|
|
19
|
+
return this.db
|
|
20
|
+
.prepare('SELECT value FROM flow_compilations ORDER BY created_at DESC, id DESC')
|
|
21
|
+
.all().map((row) => JSON.parse(row.value));
|
|
22
|
+
}
|
|
23
|
+
save(table, id, value) {
|
|
24
|
+
this.db
|
|
25
|
+
.prepare(`INSERT OR REPLACE INTO ${table}(id,value) VALUES(?,?)`)
|
|
26
|
+
.run(id, JSON.stringify(value));
|
|
27
|
+
}
|
|
28
|
+
get(table, id) {
|
|
29
|
+
const row = this.db.prepare(`SELECT value FROM ${table} WHERE id=?`).get(id);
|
|
30
|
+
return row ? JSON.parse(row.value) : undefined;
|
|
31
|
+
}
|
|
32
|
+
list(table) {
|
|
33
|
+
return this.db.prepare(`SELECT value FROM ${table} ORDER BY id`).all().map((r) => JSON.parse(r.value));
|
|
34
|
+
}
|
|
35
|
+
createRun(id, flowVersionId, input = {}, resources = [], resourceProfileId) {
|
|
36
|
+
const now = new Date().toISOString();
|
|
37
|
+
this.db
|
|
38
|
+
.prepare('INSERT INTO runs(id,flow_version_id,status,value,input,resource_profile_id,resources,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?)')
|
|
39
|
+
.run(id, flowVersionId, 'queued', '{}', JSON.stringify(input), resourceProfileId ?? null, JSON.stringify(resources), now, now);
|
|
40
|
+
this.db.prepare('INSERT INTO jobs(id,run_id,status) VALUES(?,?,?)').run(id, id, 'queued');
|
|
41
|
+
}
|
|
42
|
+
getRun(id) {
|
|
43
|
+
const row = this.db.prepare('SELECT * FROM runs WHERE id=?').get(id);
|
|
44
|
+
return row
|
|
45
|
+
? {
|
|
46
|
+
...row,
|
|
47
|
+
value: JSON.parse(row.value),
|
|
48
|
+
input: JSON.parse(row.input ?? '{}'),
|
|
49
|
+
resources: JSON.parse(row.resources ?? '[]'),
|
|
50
|
+
}
|
|
51
|
+
: undefined;
|
|
52
|
+
}
|
|
53
|
+
runs() {
|
|
54
|
+
return this.db.prepare('SELECT * FROM runs ORDER BY created_at DESC').all().map((row) => ({
|
|
55
|
+
...row,
|
|
56
|
+
value: JSON.parse(row.value),
|
|
57
|
+
input: JSON.parse(row.input ?? '{}'),
|
|
58
|
+
resources: JSON.parse(row.resources ?? '[]'),
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
setRun(id, status, value = {}) {
|
|
62
|
+
this.db
|
|
63
|
+
.prepare('UPDATE runs SET status=?,value=?,updated_at=? WHERE id=?')
|
|
64
|
+
.run(status, JSON.stringify(value), new Date().toISOString(), id);
|
|
65
|
+
}
|
|
66
|
+
append(runId, type, node, data) {
|
|
67
|
+
const tx = this.db.transaction(() => {
|
|
68
|
+
const row = this.db
|
|
69
|
+
.prepare('SELECT COALESCE(MAX(seq),0)+1 seq FROM ledger_events WHERE run_id=?')
|
|
70
|
+
.get(runId);
|
|
71
|
+
const event = { seq: row.seq, runId, type, node, data, at: new Date().toISOString() };
|
|
72
|
+
this.db
|
|
73
|
+
.prepare('INSERT INTO ledger_events(run_id,seq,type,node,data,at) VALUES(?,?,?,?,?,?)')
|
|
74
|
+
.run(runId, event.seq, type, node, data === undefined ? null : JSON.stringify(data), event.at);
|
|
75
|
+
return event;
|
|
76
|
+
});
|
|
77
|
+
return tx();
|
|
78
|
+
}
|
|
79
|
+
events(runId) {
|
|
80
|
+
return this.db.prepare('SELECT * FROM ledger_events WHERE run_id=? ORDER BY seq').all(runId).map((r) => ({
|
|
81
|
+
seq: r.seq,
|
|
82
|
+
runId,
|
|
83
|
+
type: r.type,
|
|
84
|
+
node: r.node ?? undefined,
|
|
85
|
+
data: r.data ? JSON.parse(r.data) : undefined,
|
|
86
|
+
at: r.at,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
claimJob() {
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
const tx = this.db.transaction(() => {
|
|
92
|
+
const row = this.db
|
|
93
|
+
.prepare("SELECT id,run_id FROM jobs WHERE status='queued' OR (status='running' AND lease_until<?) ORDER BY id LIMIT 1")
|
|
94
|
+
.get(now);
|
|
95
|
+
if (!row)
|
|
96
|
+
return undefined;
|
|
97
|
+
this.db
|
|
98
|
+
.prepare("UPDATE jobs SET status='running',lease_until=?,attempts=attempts+1 WHERE id=?")
|
|
99
|
+
.run(now + 30000, row.id);
|
|
100
|
+
return { id: row.id, runId: row.run_id };
|
|
101
|
+
});
|
|
102
|
+
return tx();
|
|
103
|
+
}
|
|
104
|
+
finishJob(id) {
|
|
105
|
+
this.db.prepare("UPDATE jobs SET status='done',lease_until=NULL WHERE id=?").run(id);
|
|
106
|
+
}
|
|
107
|
+
approval(runId, node) {
|
|
108
|
+
return (this.db
|
|
109
|
+
.prepare('SELECT decision FROM approvals WHERE run_id=? AND node=?')
|
|
110
|
+
.get(runId, node)?.decision ?? undefined);
|
|
111
|
+
}
|
|
112
|
+
decideApproval(runId, node, decision) {
|
|
113
|
+
this.db
|
|
114
|
+
.prepare('INSERT OR REPLACE INTO approvals(run_id,node,decision,decided_at) VALUES(?,?,?,?)')
|
|
115
|
+
.run(runId, node, decision, new Date().toISOString());
|
|
116
|
+
this.append(runId, `approval.${decision}`, node);
|
|
117
|
+
}
|
|
118
|
+
saveResourceProfile(id, value) {
|
|
119
|
+
this.db
|
|
120
|
+
.prepare('INSERT OR REPLACE INTO resource_profiles(id,value) VALUES(?,?)')
|
|
121
|
+
.run(id, JSON.stringify(value));
|
|
122
|
+
}
|
|
123
|
+
getResourceProfile(id) {
|
|
124
|
+
const row = this.db.prepare('SELECT value FROM resource_profiles WHERE id=?').get(id);
|
|
125
|
+
return row ? JSON.parse(row.value) : undefined;
|
|
126
|
+
}
|
|
127
|
+
resourceProfiles() {
|
|
128
|
+
return this.db.prepare('SELECT value FROM resource_profiles ORDER BY id').all().map((row) => JSON.parse(row.value));
|
|
129
|
+
}
|
|
130
|
+
deleteResourceProfile(id) {
|
|
131
|
+
return this.db.prepare('DELETE FROM resource_profiles WHERE id=?').run(id);
|
|
132
|
+
}
|
|
133
|
+
deleteFlowVersion(id) {
|
|
134
|
+
return this.db.prepare('DELETE FROM flow_versions WHERE id=?').run(id);
|
|
135
|
+
}
|
|
136
|
+
deleteFlowVersions(flowId) {
|
|
137
|
+
const rows = this.db.prepare('SELECT id,value FROM flow_versions').all();
|
|
138
|
+
const ids = rows
|
|
139
|
+
.filter((row) => JSON.parse(row.value).flowId === flowId)
|
|
140
|
+
.map((row) => row.id);
|
|
141
|
+
if (!ids.length)
|
|
142
|
+
return { changes: 0 };
|
|
143
|
+
const tx = this.db.transaction(() => {
|
|
144
|
+
const statement = this.db.prepare('DELETE FROM flow_versions WHERE id=?');
|
|
145
|
+
for (const id of ids)
|
|
146
|
+
statement.run(id);
|
|
147
|
+
return ids.length;
|
|
148
|
+
});
|
|
149
|
+
return { changes: tx() };
|
|
150
|
+
}
|
|
151
|
+
saveRuntimeProfile(profile) {
|
|
152
|
+
const profileId = `${profile.id}@${profile.profileVersion}`;
|
|
153
|
+
const tx = this.db.transaction(() => {
|
|
154
|
+
this.db
|
|
155
|
+
.prepare('INSERT INTO runtime_profiles(id,runtime_id,profile_version,value,created_at) VALUES(?,?,?,?,?)')
|
|
156
|
+
.run(profileId, profile.id, profile.profileVersion, JSON.stringify(profile), profile.createdAt);
|
|
157
|
+
this.db
|
|
158
|
+
.prepare('INSERT OR REPLACE INTO runtime_current(runtime_id,profile_id) VALUES(?,?)')
|
|
159
|
+
.run(profile.id, profileId);
|
|
160
|
+
});
|
|
161
|
+
tx();
|
|
162
|
+
}
|
|
163
|
+
currentRuntimeProfiles() {
|
|
164
|
+
return this.db
|
|
165
|
+
.prepare('SELECT p.value FROM runtime_current c JOIN runtime_profiles p ON p.id=c.profile_id ORDER BY c.runtime_id')
|
|
166
|
+
.all().map((row) => JSON.parse(row.value));
|
|
167
|
+
}
|
|
168
|
+
runtimeProfile(id, version) {
|
|
169
|
+
const row = version
|
|
170
|
+
? this.db
|
|
171
|
+
.prepare('SELECT value FROM runtime_profiles WHERE runtime_id=? AND profile_version=?')
|
|
172
|
+
.get(id, version)
|
|
173
|
+
: this.db
|
|
174
|
+
.prepare('SELECT p.value FROM runtime_current c JOIN runtime_profiles p ON p.id=c.profile_id WHERE c.runtime_id=?')
|
|
175
|
+
.get(id);
|
|
176
|
+
return row ? JSON.parse(row.value) : undefined;
|
|
177
|
+
}
|
|
178
|
+
runtimeProfileHistory(id) {
|
|
179
|
+
return this.db
|
|
180
|
+
.prepare('SELECT value FROM runtime_profiles WHERE runtime_id=? ORDER BY profile_version DESC')
|
|
181
|
+
.all(id).map((row) => JSON.parse(row.value));
|
|
182
|
+
}
|
|
183
|
+
allRuntimeProfiles() {
|
|
184
|
+
return this.db
|
|
185
|
+
.prepare('SELECT value FROM runtime_profiles ORDER BY runtime_id,profile_version')
|
|
186
|
+
.all().map((row) => JSON.parse(row.value));
|
|
187
|
+
}
|
|
188
|
+
nextRuntimeProfileVersion(id) {
|
|
189
|
+
const row = this.db
|
|
190
|
+
.prepare('SELECT COALESCE(MAX(profile_version),0)+1 version FROM runtime_profiles WHERE runtime_id=?')
|
|
191
|
+
.get(id);
|
|
192
|
+
return row.version;
|
|
193
|
+
}
|
|
194
|
+
settings() {
|
|
195
|
+
const row = this.db
|
|
196
|
+
.prepare("SELECT value FROM workspace_settings WHERE id='workspace'")
|
|
197
|
+
.get();
|
|
198
|
+
return row ? JSON.parse(row.value) : undefined;
|
|
199
|
+
}
|
|
200
|
+
saveSettings(settings) {
|
|
201
|
+
this.db
|
|
202
|
+
.prepare("INSERT OR REPLACE INTO workspace_settings(id,value) VALUES('workspace',?)")
|
|
203
|
+
.run(JSON.stringify(settings));
|
|
204
|
+
}
|
|
205
|
+
close() {
|
|
206
|
+
this.db.close();
|
|
207
|
+
}
|
|
208
|
+
}
|