feat: update control-plane-client roadmap, add edge Dockerfile & config, update docker-compose & dev scripts
This commit is contained in:
parent
7ccefd7f9f
commit
68f6e08da8
7 changed files with 127 additions and 113 deletions
|
|
@ -16,17 +16,20 @@ Client는 NomadCode 전용 화면이 아니라 IOP가 독립적으로 운영되
|
|||
|
||||
## 구현 잠금
|
||||
|
||||
- 상태: 잠금
|
||||
- 결정 필요: 아래 체크리스트
|
||||
- [ ] Client의 첫 운영 화면 범위를 Edge/Node 상태 조회 중심으로 둘지, 명령/설정 변경까지 포함할지 결정한다.
|
||||
- [ ] IOP client가 범용 운영 UI로 남기 위해 제외할 NomadCode 전용 UX 경계를 결정한다.
|
||||
- 상태: 해제
|
||||
- 결정 필요: 없음
|
||||
- 결정 결과:
|
||||
- Client의 첫 운영 화면은 Edge/Node 운영 lifecycle을 얇게 파악하는 데 필요한 설정 조회, Node registry, 연결 상태, 로그/이벤트 중심으로 둔다.
|
||||
- 설정 변경, 명령 실행, 권한이 필요한 조작 UX는 첫 화면에 무겁게 넣지 않고 조회/상태/근거가 안정화된 뒤 후속으로 덧붙인다.
|
||||
- IOP client는 범용 운영 UI로 유지하며 NomadCode 전용 project/workspace/webview/diff/PR/branch/commit UX를 포함하지 않는다.
|
||||
|
||||
## 범위
|
||||
|
||||
- Control Plane Go server의 Edge connection 관리
|
||||
- Client-Control Plane wire surface와 Control Plane-Edge Wire Baseline 위의 운영 표면 확장
|
||||
- Flutter client의 Runtime/Automation/Serving 운영 화면 기초
|
||||
- Edge 상태 조회, 설정 변경, 명령 전달, 이벤트 수신
|
||||
- Edge/Node 설정 조회, Node registry, 연결 상태, 로그/이벤트 수신을 중심으로 한 얇은 lifecycle 운영 화면
|
||||
- 설정 변경과 명령 전달은 lifecycle 조회 표면 위에 붙일 후속 확장 후보로 정리
|
||||
- Edge agent 생성과 bootstrap command 발급 상태 조회/관리
|
||||
- remote terminal bridge session 생성/조회/권한 확인 표면
|
||||
- IOP 책임 범위 안의 model serving, automation, session, routing, policy, history, audit 운영 UX
|
||||
|
|
@ -36,14 +39,16 @@ Client는 NomadCode 전용 화면이 아니라 IOP가 독립적으로 운영되
|
|||
### Epic: [edge-ops-boundary] Edge Operations Boundary
|
||||
|
||||
- [ ] [edge-mediated] Control Plane은 Node를 직접 연결하거나 직접 스케줄링하지 않는다.
|
||||
- [ ] [edge-status] Control Plane은 선행 Wire Baseline으로 연결된 하나 이상의 Edge 상태를 조회할 수 있다. 검증: Edge 연결 상태 조회 경로가 동작하는지 확인한다.
|
||||
- [ ] [edge-command-events] Edge 명령 전달과 이벤트 수신 흐름이 Control Plane-Edge Wire Baseline 위의 IOP Wire Protocol 기준으로 문서화된다.
|
||||
- [ ] [client-via-cp] Client는 Control Plane을 통해 Edge 운영 상태를 조회한다.
|
||||
- [ ] [edge-status] Control Plane은 선행 Wire Baseline으로 연결된 하나 이상의 Edge 연결 상태를 조회할 수 있다. 검증: Edge 연결 상태 조회 경로가 동작하는지 확인한다.
|
||||
- [ ] [node-registry] Control Plane은 Edge가 보고하는 Node registry와 Node 연결 상태를 운영 표면에 전달할 수 있다. 검증: Node 목록과 연결 상태가 Control Plane을 통해 조회된다.
|
||||
- [ ] [edge-node-config-view] Control Plane/Client는 Edge/Node의 현재 운영 설정 요약을 조회 중심으로 보여준다. 검증: 설정 원본이나 effective config 요약이 조작 없이 표시된다.
|
||||
- [ ] [edge-event-logs] Edge lifecycle event와 기본 로그/상태 이벤트 수신 흐름이 Control Plane-Edge Wire Baseline 위의 IOP Wire Protocol 기준으로 정리된다.
|
||||
- [ ] [client-via-cp] Client는 Control Plane을 통해 Edge/Node 운영 lifecycle 상태를 조회한다.
|
||||
|
||||
### Epic: [client-wire-surface] Client Wire Surface
|
||||
|
||||
- [x] [client-boundary] Flutter client는 IOP 자체 운영 UI/UX이며 NomadCode에 맞춘 전용 shell로 만들지 않는다.
|
||||
- [ ] [client-status] Client는 Edge/Node/agent 상태를 사용자에게 보여줄 수 있다. 검증: Client 상태 화면 또는 상태 표시 경로를 확인한다.
|
||||
- [ ] [client-status] Client는 Edge/Node 설정 요약, registry, 연결 상태, 로그/이벤트를 lifecycle 중심으로 얇게 보여줄 수 있다. 검증: Client 상태 화면 또는 상태 표시 경로를 확인한다.
|
||||
- [x] [server-bridge] 브라우저 직접 TCP가 어려운 구간은 Control Plane server-side bridge를 둔다.
|
||||
- [x] [client-wire-hello] Client-Control Plane proto-socket WebSocket hello handshake scaffold가 구현되고 테스트되어 있다. 검증: Control Plane과 client 테스트에서 hello handshake가 통과한다.
|
||||
- [x] [http-boundary] `net/http`는 health/readiness/bootstrap 같은 보조 endpoint 용도로 유지한다.
|
||||
|
|
@ -64,6 +69,7 @@ Client는 NomadCode 전용 화면이 아니라 IOP가 독립적으로 운영되
|
|||
- Control Plane을 Kubernetes식 중앙 스케줄러로 만드는 작업
|
||||
- Edge 내부 DB를 Control Plane DB로 대체
|
||||
- 모든 런타임 상태를 Control Plane 단일 원본으로 만드는 작업
|
||||
- 첫 운영 화면에서 설정 변경, 명령 실행, 권한 조작 UX를 무겁게 구현하는 작업
|
||||
- NomadCode Agent Shell 자체 구현
|
||||
- NomadCode 전용 Project Workspace, diff/PR/branch/commit UX
|
||||
|
||||
|
|
@ -73,6 +79,7 @@ Client는 NomadCode 전용 화면이 아니라 IOP가 독립적으로 운영되
|
|||
- 표준선(선택): Control Plane은 Node를 직접 스케줄링하지 않고 Edge를 통해 관찰/제어한다.
|
||||
- 표준선(선택): Control Plane-Edge 통신 레일은 `Control Plane-Edge Wire Baseline` 선행 Milestone에서 먼저 구축한다.
|
||||
- 표준선(선택): Client-Control Plane의 첫 wire surface는 `/client` WebSocket hello handshake를 기준으로 둔다.
|
||||
- 표준선(선택): 첫 운영 화면은 설정 조회, Node registry, 연결 상태, 로그/이벤트처럼 한 lifecycle을 이해하는 데 필요한 얇은 조회 표면을 우선한다.
|
||||
- 선행 작업: Control Plane-Edge Wire Baseline, Flutter-first Client 마이그레이션, Edge Command와 Control Plane 운영 경계, Agent Bootstrap과 OTO 등록, 정책/이력/감사 일부 기준
|
||||
- 후속 작업: Multi-Edge 운영
|
||||
- 확인 필요: Client 첫 화면 범위, NomadCode 전용 UX 제외 기준
|
||||
- 확인 필요: 없음
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
# Milestone: Embeddable IOP Console Workbench
|
||||
|
||||
## 위치
|
||||
|
||||
- Roadmap: `agent-roadmap/ROADMAP.md`
|
||||
- Phase: `agent-roadmap/phase/control-plane-portal-ops/PHASE.md`
|
||||
|
||||
## 목표
|
||||
|
||||
IOP standalone 앱은 application shell로 유지하되, 실제 IOP 관리 기능과 UI 위젯 전체는 Flutter package 형태로 분리한다. 이 package는 IOP 앱에서는 좌측 icon rail + center content 구조로 보이고, NomadCode 같은 host 앱에서는 같은 native Flutter 위젯이 별도 제품 shell 안에 그대로 들어갈 수 있어야 한다.
|
||||
|
||||
## 상태
|
||||
|
||||
[진행중]
|
||||
|
||||
## 구현 잠금
|
||||
|
||||
- 상태: 해제
|
||||
- 결정 필요: 없음
|
||||
|
||||
## 범위
|
||||
|
||||
- `iop/apps/client`를 standalone application shell로 유지하는 구조
|
||||
- IOP 관리 화면, route/controller, rail item, agent capability를 embeddable Flutter package로 분리하는 구조
|
||||
- IOP standalone에서는 좌측 icon rail만 두고 중앙 content를 전환하는 workbench layout
|
||||
- host 앱이 IOP package를 선택적으로 import해 native Flutter widget으로 삽입할 수 있는 public API
|
||||
- 공통 agent shell package와 IOP agent capability pack의 연결 경계
|
||||
- IOP package가 NomadCode 제품 shell, WebView 조작 UX, workflow/task UX를 알지 못하게 하는 의존성 경계
|
||||
|
||||
## 기능
|
||||
|
||||
### Epic: [package-boundary] IOP console package boundary
|
||||
|
||||
IOP 운영 경험을 application shell에서 분리해, standalone 앱과 외부 host가 같은 관리 UI를 재사용할 수 있는 package 경계를 만든다.
|
||||
|
||||
- [x] [console-package] IOP 관리 UI를 `MaterialApp`/global bootstrap 없이 import 가능한 `packages/flutter/iop_console` Flutter package로 분리하고, `IopConsoleShell`, `IopConsoleSection`, `IopAgentPanel`을 public export로 제공한다. 검증: package public export가 app shell 없이 widget/section 단위로 소비 가능하다.
|
||||
- [x] [standalone-shell] `iop/apps/client`는 Firebase/SystemChrome/app bootstrap과 standalone theme를 담당하고, 실제 workbench shell은 IOP console package를 감싸서 실행한다. 검증: standalone 앱에서 IOP console이 좌측 rail + center content 구조로 표시되고 agent panel 진입 widget test가 있다.
|
||||
- [x] [host-agnostic] IOP console package는 NomadCode, Project/Workspace, WebView 조작, product workflow 개념을 import하거나 전제하지 않는다. 검증: package dependency graph에 NomadCode 전용 package가 없다.
|
||||
- [ ] [overview-boundary] Control Plane wire status overview는 현재 `apps/client/lib/main.dart`의 host-injected content로 남아 있다. 재사용 가능한 package widget으로 옮길지, standalone host content로 유지할지 경계를 확정한다.
|
||||
|
||||
### Epic: [workbench-layout] IOP left-rail workbench
|
||||
|
||||
IOP standalone 화면은 좌측 icon rail을 기준으로 중앙 content를 전환하는 운영 workbench로 정리한다.
|
||||
|
||||
- [x] [left-rail] IOP standalone layout은 좌측 icon rail과 center content host를 기본 구조로 둔다.
|
||||
- [x] [content-switch] Overview, Edges, Nodes, Runtime, Maintenance, Agent, Settings 영역은 rail item 선택에 따라 중앙 content를 전환한다.
|
||||
- [ ] [execution-logs] Execution/Logs 영역은 별도 rail item, Runtime 하위 화면, Maintenance 하위 화면 중 어디에 둘지 정리하고 표시 경로를 추가한다.
|
||||
- [ ] [stable-widgets] rail item, tooltip, selected state, content host는 host layout 크기에 따라 안정적으로 배치된다. 검증: desktop/mobile 또는 narrow/wide viewport에서 rail과 content가 겹치지 않는다.
|
||||
|
||||
### Epic: [agent-capability] IOP agent capability pack
|
||||
|
||||
공통 agent shell은 외부 package로 두고, IOP는 자신의 운영 capability만 제공한다.
|
||||
|
||||
- [x] [agent-shell-link] IOP console package는 공통 `agent_shell` 계열 package를 통해 채팅형 agent surface를 열 수 있다.
|
||||
- [ ] [iop-capabilities] IOP agent capability는 Edge/Node 상태, runtime/adapter/target, execution, logs, maintenance command 같은 IOP 책임 범위만 포함한다.
|
||||
- [ ] [capability-api] IOP capability pack은 host가 agent shell registry에 꽂을 수 있는 public API를 제공한다. 검증: IOP standalone과 외부 host가 같은 IOP capability pack을 등록할 수 있다.
|
||||
|
||||
### Epic: [embedding-contract] External host embedding contract
|
||||
|
||||
NomadCode 같은 외부 host가 IOP UI를 optional feature로 넣거나 빼도 빌드 경계가 깨지지 않도록 한다.
|
||||
|
||||
- [x] [public-widget] IOP package는 `IopConsoleShell`, `IopConsoleSection`, `IopAgentPanel` public entry를 제공한다.
|
||||
- [ ] [config-injection] host는 Control Plane endpoint, auth/token reference, theme adapter, navigation callback을 주입하고 IOP package는 host 전역 상태를 직접 소유하지 않는다.
|
||||
- [ ] [optional-consumer] IOP package는 optional consumer인 NomadCode 없이도 단독으로 테스트되고 빌드된다. 검증: IOP repository 안에서 package test/analyze 경로가 독립적으로 통과한다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: `packages/flutter/iop_console` package boundary, left rail shell, standalone app mount, agent shell link는 구현되었으나 모든 기능 Task와 Task 안에 명시된 검증은 아직 충족되지 않았다.
|
||||
- 리뷰 필요:
|
||||
- [ ] 사용자가 완료 결과를 확인했다
|
||||
- [ ] archive 이동을 승인했다
|
||||
- 리뷰 코멘트: 없음
|
||||
|
||||
## 범위 제외
|
||||
|
||||
- NomadCode의 우측 rail, titlebar, workflow/project/webview UX 구현
|
||||
- NomadCode default build와 IOP 포함 build의 조립 전략 구현
|
||||
- IOP 내부 모델 라우팅, RAG, MCP/tool policy, output validation 세부 구현
|
||||
- 런타임 동적 plugin framework 도입
|
||||
- IOP package가 NomadCode 전용 화면을 직접 import하거나 소유하는 구조
|
||||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `apps/client/`, `packages/flutter/iop_console/`, `proto/iop/`, `apps/control-plane/`
|
||||
- 표준선(선택): IOP는 범용 추론/자동화 운영 엔진이며 NomadCode 전용 Agent Shell이 아니다.
|
||||
- 표준선(선택): shared UI는 application shell이 아니라 package/widget/controller/capability 단위로 재사용한다.
|
||||
- 표준선(선택): IOP standalone은 좌측 icon rail + center content 구조를 기본 workbench로 둔다.
|
||||
- 구현 근거: `49872ae feat(client): iop_console 패키지로 클라이언트 UI를 분리한다`에서 `packages/flutter/iop_console` package, `IopConsoleShell`, `IopAgentPanel`, `apps/client` mount와 widget test가 추가되었다.
|
||||
- 현재 구현 상태: package 이름은 `iop_console`으로 확정되었고 최초 rail item은 Overview, Edges, Nodes, Runtime, Maintenance, Agent, Settings다. Execution/Logs, controller/config injection, 독립 package test/analyze는 남은 작업이다.
|
||||
- 선행 작업: Control Plane과 Client, Flutter-first Client 마이그레이션, Control Plane-Edge Wire Baseline
|
||||
- 후속 작업: NomadCode Workbench Shell and Optional IOP Composition, Multi-Edge 운영
|
||||
- 확인 필요: Execution/Logs의 화면 배치, Control Plane endpoint/auth/theme/navigation injection public API, package 독립 검증 경로를 확정한다.
|
||||
29
apps/edge/Dockerfile
Normal file
29
apps/edge/Dockerfile
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
WORKDIR /workspace/iop
|
||||
RUN apk add --no-cache git
|
||||
|
||||
# docker-compose builds this Dockerfile with /config/workspace as context so
|
||||
# the local ../proto-socket/go replace in go.mod is available.
|
||||
COPY iop/go.mod iop/go.sum ./
|
||||
COPY proto-socket/go /workspace/proto-socket/go
|
||||
RUN go mod download
|
||||
|
||||
COPY iop/ ./
|
||||
RUN CGO_ENABLED=0 GOFLAGS=-trimpath go build -o /out/edge ./apps/edge/cmd/edge
|
||||
|
||||
FROM alpine:3.22
|
||||
|
||||
RUN addgroup -S iop && adduser -S iop -G iop
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /out/edge /usr/local/bin/edge
|
||||
COPY --from=builder /workspace/iop/configs/edge-compose.yaml /app/configs/edge-compose.yaml
|
||||
|
||||
EXPOSE 9090
|
||||
|
||||
USER iop
|
||||
ENTRYPOINT ["edge"]
|
||||
CMD ["serve", "--config", "/app/configs/edge-compose.yaml"]
|
||||
61
configs/edge-compose.yaml
Normal file
61
configs/edge-compose.yaml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
edge:
|
||||
id: "edge-compose"
|
||||
name: "Compose Edge"
|
||||
|
||||
server:
|
||||
listen: "0.0.0.0:9090"
|
||||
advertise_host: ""
|
||||
|
||||
bootstrap:
|
||||
artifact_base_url: ""
|
||||
listen: "0.0.0.0:18080"
|
||||
artifact_dir: "/tmp/iop-artifacts"
|
||||
|
||||
tls:
|
||||
enabled: false
|
||||
|
||||
logging:
|
||||
level: "info"
|
||||
pretty: true
|
||||
path: "/tmp/iop-edge/edge.log"
|
||||
|
||||
metrics:
|
||||
port: 0
|
||||
|
||||
control_plane:
|
||||
enabled: true
|
||||
wire_addr: "control-plane:19081"
|
||||
reconnect_interval_sec: 5
|
||||
|
||||
a2a:
|
||||
enabled: false
|
||||
|
||||
openai:
|
||||
enabled: false
|
||||
|
||||
console:
|
||||
adapter: "cli"
|
||||
target: "codex"
|
||||
session_id: "default"
|
||||
background: false
|
||||
timeout_sec: 300
|
||||
|
||||
nodes:
|
||||
- id: "external-node"
|
||||
alias: "external-node"
|
||||
token: "changeme"
|
||||
adapters:
|
||||
cli:
|
||||
enabled: true
|
||||
profiles:
|
||||
codex:
|
||||
command: "codex"
|
||||
args: []
|
||||
persistent: false
|
||||
terminal: false
|
||||
response_idle_timeout_ms: 1000
|
||||
startup_idle_timeout_ms: 1000
|
||||
output_format: "plain"
|
||||
mode: "exec"
|
||||
runtime:
|
||||
concurrency: 1
|
||||
|
|
@ -9,7 +9,7 @@ services:
|
|||
POSTGRES_PASSWORD: ${IOP_POSTGRES_PASSWORD:-iop_dev_password}
|
||||
POSTGRES_DB: ${IOP_POSTGRES_DB:-iop-control-plane-dev}
|
||||
ports:
|
||||
- "${IOP_POSTGRES_PORT:-5432}:5432"
|
||||
- "${IOP_POSTGRES_BIND:-127.0.0.1}:${IOP_POSTGRES_PORT:-5432}:5432"
|
||||
volumes:
|
||||
- control-plane-postgres:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
|
|
@ -23,7 +23,7 @@ services:
|
|||
restart: unless-stopped
|
||||
command: ["redis-server", "--appendonly", "yes"]
|
||||
ports:
|
||||
- "${IOP_REDIS_PORT:-6379}:6379"
|
||||
- "${IOP_REDIS_BIND:-127.0.0.1}:${IOP_REDIS_PORT:-6379}:6379"
|
||||
volumes:
|
||||
- control-plane-redis:/data
|
||||
healthcheck:
|
||||
|
|
@ -42,9 +42,9 @@ services:
|
|||
IOP_REDIS_URL: "redis://redis:6379/${IOP_REDIS_DB:-2}"
|
||||
IOP_REDIS_KEY_PREFIX: "${IOP_REDIS_KEY_PREFIX:-iop:control-plane:dev:}"
|
||||
ports:
|
||||
- "${IOP_CONTROL_PLANE_HTTP_PORT:-9080}:9080"
|
||||
- "${IOP_CONTROL_PLANE_WIRE_PORT:-19080}:19080"
|
||||
- "${IOP_CONTROL_PLANE_EDGE_WIRE_PORT:-19081}:19081"
|
||||
- "${IOP_CONTROL_PLANE_HTTP_BIND:-127.0.0.1}:${IOP_CONTROL_PLANE_HTTP_PORT:-9080}:9080"
|
||||
- "${IOP_CONTROL_PLANE_WIRE_BIND:-0.0.0.0}:${IOP_CONTROL_PLANE_WIRE_PORT:-19080}:19080"
|
||||
- "${IOP_CONTROL_PLANE_EDGE_WIRE_BIND:-127.0.0.1}:${IOP_CONTROL_PLANE_EDGE_WIRE_PORT:-19081}:19081"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
|
@ -56,16 +56,27 @@ services:
|
|||
timeout: 3s
|
||||
retries: 5
|
||||
|
||||
edge:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: iop/apps/edge/Dockerfile
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${IOP_EDGE_NODE_BIND:-0.0.0.0}:${IOP_EDGE_NODE_PORT:-19090}:9090"
|
||||
depends_on:
|
||||
control-plane:
|
||||
condition: service_healthy
|
||||
|
||||
web:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: iop/apps/client/Dockerfile
|
||||
args:
|
||||
IOP_CONTROL_PLANE_HTTP_URL: "http://localhost:${IOP_CONTROL_PLANE_HTTP_PORT:-9080}"
|
||||
IOP_CONTROL_PLANE_WIRE_URL: "ws://localhost:${IOP_CONTROL_PLANE_WIRE_PORT:-19080}/client"
|
||||
IOP_CONTROL_PLANE_HTTP_URL: "${IOP_CONTROL_PLANE_HTTP_URL:-http://localhost:9080}"
|
||||
IOP_CONTROL_PLANE_WIRE_URL: "${IOP_CONTROL_PLANE_WIRE_URL:-ws://localhost:19080/client}"
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${IOP_WEB_PORT:-3000}:3000"
|
||||
- "${IOP_WEB_BIND:-0.0.0.0}:${IOP_WEB_PORT:-13000}:3000"
|
||||
depends_on:
|
||||
control-plane:
|
||||
condition: service_healthy
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -6,7 +6,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|||
CLIENT_DIR="$REPO_ROOT/apps/client"
|
||||
|
||||
WEB_HOST="${IOP_WEB_HOST:-0.0.0.0}"
|
||||
WEB_PORT="${IOP_WEB_PORT:-3000}"
|
||||
WEB_PORT="${IOP_WEB_PORT:-13000}"
|
||||
|
||||
IOP_CONTROL_PLANE_HTTP_URL="${IOP_CONTROL_PLANE_HTTP_URL:-http://localhost:9080}"
|
||||
IOP_CONTROL_PLANE_WIRE_URL="${IOP_CONTROL_PLANE_WIRE_URL:-ws://localhost:19080/client}"
|
||||
|
|
|
|||
Loading…
Reference in a new issue