- Add unbounded CLI sessions agent-task with plan and code review logs - Add edge console with tests - Add node run manager for session lifecycle management - Add router and store tests - Update transport session handling - Add runtime proto definitions - Update configurations and packages
221 lines
8.9 KiB
Text
221 lines
8.9 KiB
Text
<!-- task=unbounded_cli_sessions plan=1 tag=REVIEW_REFACTOR -->
|
|
|
|
# Follow-up Plan - Unbounded CLI Sessions Review Fixes
|
|
|
|
## 이 파일을 읽는 구현 에이전트에게
|
|
|
|
이 계획은 `plan_0.log` / `code_review_0.log` 리뷰에서 발견된 Required 이슈만 해결하기 위한 follow-up입니다. 각 체크리스트를 완료하고, 중간/최종 검증을 실행한 뒤 `agent-task/unbounded_cli_sessions/CODE_REVIEW.md`의 모든 섹션에 실제 구현 메모와 명령 출력을 채우세요.
|
|
|
|
구현 에이전트는 `CODE_REVIEW.md`의 "이 파일을 읽는 리뷰 에이전트에게" 섹션에 적힌 아카이브 지시를 실행하면 안 됩니다. `CODE_REVIEW.md` -> `code_review_*.log`, `PLAN.md` -> `plan_*.log`, `complete.log` 작성은 code-review skill 전용 작업입니다.
|
|
|
|
## 배경
|
|
|
|
첫 구현은 unbounded CLI logical session의 큰 구조를 도입했지만, foreground adapter error 반환 계약과 기존 SQLite DB migration 안전성에서 Required 이슈가 남았습니다. 이 follow-up은 새 구조를 유지하면서 호출자 error 관측과 기존 workspace DB 호환성을 회복합니다.
|
|
|
|
## 의존 관계 및 구현 순서
|
|
|
|
1. `REVIEW_REFACTOR-1`에서 node foreground error 반환 계약을 복구한다.
|
|
2. `REVIEW_REFACTOR-2`에서 store migration을 idempotent하게 추가한다.
|
|
3. 두 항목의 중간 검증 후 최종 `go test ./...`를 실행한다.
|
|
|
|
### [REVIEW_REFACTOR-1] Restore Foreground OnRunRequest Error Return
|
|
|
|
#### 문제
|
|
|
|
`OnRunRequest`의 foreground 경로가 adapter failure를 호출자에게 반환하지 않습니다.
|
|
|
|
현재 코드:
|
|
|
|
```go
|
|
// apps/node/internal/node/node.go:120
|
|
run := func() {
|
|
defer cancel()
|
|
defer n.runs.deregister(spec.RunID)
|
|
defer close(h.done)
|
|
|
|
execErr := adapter.Execute(execCtx, spec, sink)
|
|
n.completeRun(spec, execErr)
|
|
}
|
|
|
|
if spec.Background {
|
|
go run()
|
|
return nil
|
|
}
|
|
run()
|
|
return nil
|
|
```
|
|
|
|
이전 계약과 `plan_0.log`의 pseudocode는 synchronous run에서 `adapter.Execute` error를 반환해야 합니다. 지금 상태에서는 store에는 failed/cancelled가 기록되더라도 transport handler caller는 실패를 알 수 없습니다.
|
|
|
|
#### 해결 방법
|
|
|
|
`run` closure가 `error`를 반환하도록 바꾸고, foreground 경로에서는 그 error를 그대로 반환한다. background 경로는 기존처럼 goroutine dispatch 후 `nil`을 반환한다.
|
|
|
|
계획 코드:
|
|
|
|
```go
|
|
// apps/node/internal/node/node.go:120
|
|
run := func() error {
|
|
defer cancel()
|
|
defer n.runs.deregister(spec.RunID)
|
|
defer close(h.done)
|
|
|
|
execErr := adapter.Execute(execCtx, spec, sink)
|
|
n.completeRun(spec, execErr)
|
|
return execErr
|
|
}
|
|
|
|
if spec.Background {
|
|
go func() { _ = run() }()
|
|
return nil
|
|
}
|
|
return run()
|
|
```
|
|
|
|
`runtime.ErrRunCancelled`도 adapter error이므로 foreground caller에 반환한다. store status는 기존 `completeRun` 로직대로 `cancelled`를 유지한다.
|
|
|
|
#### 수정 파일 및 체크리스트
|
|
|
|
- [ ] `apps/node/internal/node/node.go`: `run` closure return type을 `error`로 변경.
|
|
- [ ] `apps/node/internal/node/node.go`: foreground path를 `return run()`으로 변경.
|
|
- [ ] `apps/node/internal/node/node.go`: background path는 `go func() { _ = run() }()`로 error를 소비하되 store update는 유지.
|
|
- [ ] `apps/node/internal/node/node_test.go`: adapter failure를 반환하는 test double 추가.
|
|
- [ ] `apps/node/internal/node/node_test.go`: foreground adapter failure가 `OnRunRequest` return error와 store `failed` status에 모두 반영되는 regression test 추가.
|
|
- [ ] `apps/node/internal/node/node_test.go`: foreground `runtime.ErrRunCancelled` 반환 시 `OnRunRequest`가 해당 sentinel error를 반환하고 store status가 `cancelled`가 되는지 필요하면 추가 test로 고정.
|
|
|
|
#### 테스트 작성
|
|
|
|
작성한다.
|
|
|
|
- `apps/node/internal/node/node_test.go`
|
|
- `TestOnRunRequest_ForegroundAdapterErrorReturned`: fake adapter가 `errors.New("adapter boom")`을 반환하게 하고 `OnRunRequest`가 non-nil error를 반환하는지, `st.GetRun(...).Status == "failed"`인지, `Error`에 `"adapter boom"`이 들어가는지 검증한다.
|
|
- `TestOnRunRequest_ForegroundCancelReturnedAndStored`: blocking adapter 또는 sentinel adapter를 사용해 foreground path에서 `runtime.ErrRunCancelled`가 반환되고 store status가 `cancelled`인지 검증한다. 구현이 복잡하면 첫 번째 test를 필수로 두고 cancelled path는 existing background cancel test로 충분한지 CODE_REVIEW에 근거를 남긴다.
|
|
|
|
#### 중간 검증
|
|
|
|
```bash
|
|
go test ./apps/node/internal/node
|
|
```
|
|
|
|
예상 결과: node package tests가 통과하고 새 regression test가 실패 계약을 방지한다.
|
|
|
|
### [REVIEW_REFACTOR-2] Add Idempotent Store Migration For Existing Runs Table
|
|
|
|
#### 문제
|
|
|
|
store schema는 새 table 생성에는 `session_id`, `background` 컬럼을 포함하지만, 기존 DB에 이미 `runs` table이 있으면 `CREATE TABLE IF NOT EXISTS`는 아무 것도 바꾸지 않습니다.
|
|
|
|
현재 코드:
|
|
|
|
```go
|
|
// apps/node/internal/store/store.go:47
|
|
func New(dsn string, logger *zap.Logger) (*Store, error) {
|
|
db, err := sql.Open("sqlite", dsn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("store: open %s: %w", dsn, err)
|
|
}
|
|
db.SetMaxOpenConns(1) // SQLite is single-writer
|
|
if _, err := db.Exec(schema); err != nil {
|
|
return nil, fmt.Errorf("store: migrate: %w", err)
|
|
}
|
|
logger.Info("store ready", zap.String("dsn", dsn))
|
|
return &Store{db: db, logger: logger}, nil
|
|
}
|
|
```
|
|
|
|
그런데 `InsertRun`과 `GetRun`은 항상 새 컬럼을 사용합니다.
|
|
|
|
```go
|
|
// apps/node/internal/store/store.go:77
|
|
`INSERT INTO runs (run_id, adapter, model, session_id, background, status, created_at) VALUES (?,?,?,?,?,?,?)`
|
|
|
|
// apps/node/internal/store/store.go:95
|
|
`SELECT run_id, adapter, model, session_id, background, status, created_at, completed_at, error FROM runs WHERE run_id=?`
|
|
```
|
|
|
|
기존 workspace DB에서는 insert/select가 `no such column: session_id` 계열 오류로 깨집니다.
|
|
|
|
#### 해결 방법
|
|
|
|
`schema` 실행 후 idempotent migration을 실행한다. SQLite는 `ADD COLUMN IF NOT EXISTS` 지원이 환경에 따라 애매할 수 있으므로, `PRAGMA table_info(runs)`로 컬럼 존재 여부를 확인한 뒤 없는 컬럼만 `ALTER TABLE` 한다.
|
|
|
|
계획 코드:
|
|
|
|
```go
|
|
// apps/node/internal/store/store.go:54
|
|
if _, err := db.Exec(schema); err != nil {
|
|
return nil, fmt.Errorf("store: migrate: %w", err)
|
|
}
|
|
if err := migrateRunsTable(db); err != nil {
|
|
_ = db.Close()
|
|
return nil, fmt.Errorf("store: migrate runs: %w", err)
|
|
}
|
|
```
|
|
|
|
```go
|
|
// apps/node/internal/store/store.go:57
|
|
func migrateRunsTable(db *sql.DB) error {
|
|
cols, err := tableColumns(db, "runs")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !cols["session_id"] {
|
|
if _, err := db.Exec(`ALTER TABLE runs ADD COLUMN session_id TEXT NOT NULL DEFAULT 'default'`); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if !cols["background"] {
|
|
if _, err := db.Exec(`ALTER TABLE runs ADD COLUMN background INTEGER NOT NULL DEFAULT 0`); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
```
|
|
|
|
`tableColumns`는 `PRAGMA table_info(runs)`의 `name` column을 읽어 `map[string]bool`로 반환한다.
|
|
|
|
#### 수정 파일 및 체크리스트
|
|
|
|
- [ ] `apps/node/internal/store/store.go`: `migrateRunsTable` 추가.
|
|
- [ ] `apps/node/internal/store/store.go`: `tableColumns` helper 추가.
|
|
- [ ] `apps/node/internal/store/store.go`: migration 실패 시 DB close 후 error 반환.
|
|
- [ ] `apps/node/internal/store/store_test.go`: old schema fixture 생성 helper 추가.
|
|
- [ ] `apps/node/internal/store/store_test.go`: old schema DB를 `Store.New`로 열고 `InsertRun`/`GetRun`이 새 columns와 함께 동작하는 regression test 추가.
|
|
|
|
#### 테스트 작성
|
|
|
|
작성한다.
|
|
|
|
- `apps/node/internal/store/store_test.go`
|
|
- `TestStore_MigratesExistingRunsTable`: temp sqlite file에 구 schema(`run_id`, `adapter`, `model`, `status`, `created_at`, `completed_at`, `error`)만 가진 `runs` table을 먼저 만든다. 이후 `store.New(dsn, ...)`를 호출하고 `InsertRun` with `SessionID: "session-a", Background: true`가 성공하는지, `GetRun`이 해당 값을 읽는지 검증한다.
|
|
- `TestStore_MigrationIsIdempotent`: 같은 DB에 `store.New`를 두 번 호출해 두 번째 호출도 실패하지 않는지 검증한다.
|
|
|
|
#### 중간 검증
|
|
|
|
```bash
|
|
go test ./apps/node/internal/store
|
|
```
|
|
|
|
예상 결과: store package tests가 통과하고 구 schema DB migration regression test가 추가된다.
|
|
|
|
## 수정 파일 요약
|
|
|
|
| 파일 | 항목 |
|
|
|------|------|
|
|
| `apps/node/internal/node/node.go` | REVIEW_REFACTOR-1 |
|
|
| `apps/node/internal/node/node_test.go` | REVIEW_REFACTOR-1 |
|
|
| `apps/node/internal/store/store.go` | REVIEW_REFACTOR-2 |
|
|
| `apps/node/internal/store/store_test.go` | REVIEW_REFACTOR-2 |
|
|
| `agent-task/unbounded_cli_sessions/CODE_REVIEW.md` | REVIEW_REFACTOR-1, REVIEW_REFACTOR-2 |
|
|
|
|
## 최종 검증
|
|
|
|
```bash
|
|
gofmt -w apps/node/internal/node/node.go apps/node/internal/node/node_test.go apps/node/internal/store/store.go apps/node/internal/store/store_test.go
|
|
go test ./apps/node/internal/node
|
|
go test ./apps/node/internal/store
|
|
go test ./...
|
|
```
|
|
|
|
예상 결과: 모든 명령이 성공한다.
|