feat: m-node-multi-target-serving-foundation task completion and edge/node updates
- Complete edge owned options and commands capabilities tasks - Update edge ollama passthrough and server tests - Update opsconsole console, events, and status - Update node command service and service tests - Update node ollama adapter and node tests - Update node README and domain rules
This commit is contained in:
parent
738bd1939c
commit
fa910ef06c
24 changed files with 1096 additions and 204 deletions
|
|
@ -69,7 +69,7 @@ Edge에 연결되어 실제 adapter execution을 수행하는 IOP 노드 에이
|
|||
- `cline-json`, `opencode-json`, `codex-json`, `claude-json` 같은 provider별 stdout parser는 `adapters/cli` emitter로 등록하고 runtime 공통 이벤트로만 외부에 노출한다.
|
||||
- CLI logical session은 `(target, session_id)`로 식별한다. Antigravity conversation id, Codex external id, opencode session/server 상태를 전역 target 단위로 공유하지 않는다.
|
||||
- `response_idle_timeout_ms`, `startup_idle_timeout_ms`, `completion_marker`, `resume_args`, `mode` 같은 CLI profile 설정은 edge config/proto payload를 통해 주입하고 node 코드에 target별 상수를 늘리지 않는다.
|
||||
- Ollama adapter는 내부 target을 model 이름으로 사용하고, `context_size`는 `options.num_ctx` default로만 주입한다. 요청 input에 명시된 `options.num_ctx`가 있으면 이를 덮어쓰지 않는다.
|
||||
- Ollama adapter는 내부 target을 model 이름으로 사용하고, `context_size`는 `options.num_ctx`의 강제 소유값으로 주입한다. 요청 input에 명시된 `options.num_ctx`가 있어도 Edge-owned `context_size`가 항상 우선한다. `context_size`가 0이면 request 값을 그대로 사용한다.
|
||||
- `RuntimeEvent`는 start/delta/reasoning_delta/complete/error/cancelled 타입을 유지하고, adapter별 streaming 표현을 node 외부로 새 이벤트 체계로 노출하지 않는다.
|
||||
- node 내부 변경은 가능한 대상 패키지 테스트를 먼저 추가하거나 갱신한다.
|
||||
- `apps/node/cmd/node/**`, `apps/node/internal/bootstrap/**`, `apps/node/internal/transport/**`, `apps/node/internal/node/**`, `apps/node/internal/router/**`, `apps/node/internal/adapters/**`, `apps/node/internal/store/**`의 실행 요청/응답/stream/cancel/status/session 경로를 바꾼 뒤에는 `testing` domain rule의 작업 후 검증 기준을 따른다.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
<!-- task=m-node-multi-target-serving-foundation/05+03,04_edge_owned_options plan=0 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> Fill implementation-owned sections and real verification output before reporting ready for review.
|
||||
> Do not ask the user directly; record user-only blockers in `사용자 리뷰 요청`.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-10
|
||||
task=m-node-multi-target-serving-foundation/05+03,04_edge_owned_options, plan=0, tag=API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md`
|
||||
- Task ids:
|
||||
- `edge-owned-options`: context window와 runner sizing 옵션은 Edge config가 최종 소유하며, 외부 agent request의 `options.num_ctx` 같은 값이 Edge-owned 값을 변경하지 못한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.
|
||||
|
||||
request option이 route/profile owned option을 덮지 못하는지 테스트와 구현을 같이 확인한다.
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [API-1] Edge-Owned Runtime Options | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] Edge route/profile config의 context/runner sizing 옵션이 request option보다 우선한다.
|
||||
- [x] OpenAI chat/responses에서 request `options.num_ctx` 등 runner sizing override가 내부 profile 값을 바꾸지 못한다.
|
||||
- [x] provider별 option merge 정책을 테스트로 고정한다.
|
||||
- [x] 최종 검증 명령을 실행한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
- [x] `코드리뷰 결과`에 판정을 append한다.
|
||||
- [x] active `CODE_REVIEW-local-G07.md`를 `code_review_local_G07_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-local-G07.md`를 `plan_local_G07_M.log`로 아카이브한다.
|
||||
- [x] PASS이면 `complete.log` 작성 후 archive 이동한다.
|
||||
- [ ] WARN/FAIL이면 후속 상태를 작성한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
- `apps/edge/internal/openai/chat_handler.go`, `responses_handler.go`: 계획에는 수정 대상으로 포함되어 있으나 실제 변경 없음. chat handler는 이미 request options를 그대로 service로 전달하고 있어 정책에 맞음. responses handler는 options 필드가 없어 `num_ctx` 경유 불가. 두 파일 모두 추가 변경이 필요 없음을 확인.
|
||||
- `packages/go/config/config.go`: 수정 없음. `OllamaInstanceConf.ContextSize` 및 `OllamaConf.ContextSize` 필드가 이미 Edge-owned canonical source 역할을 충분히 수행함.
|
||||
- `agent-ops/rules/project/domain/node/rules.md`: 계획에 없었으나 코드와 규칙 일관성을 위해 Ollama context_size 정책 설명 수정.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- 정책 집행 위치: Edge handler 레벨이 아닌 node adapter 레벨(`ollamaOptionsFromInput`)에서 적용. Edge는 route당 context_size를 갖지 않으므로 Edge에서 강제할 방법이 없음. Adapter는 생성 시 config의 `contextSize`를 이미 보유함.
|
||||
- 강제 정책: `if _, hasNumCtx := options["num_ctx"]; !hasNumCtx` 조건 제거 → `options["num_ctx"] = contextSize`로 무조건 덮어쓰기. `contextSize == 0`이면 할당 자체를 skip하므로 미설정 adapter는 기존 동작 유지.
|
||||
- 테스트 전략: `ollama_test.go`에서 adapter-level 강제 검증(실제 HTTP mock), `server_test.go`에서 edge passthrough 검증(fakeRunService)으로 계층 분리.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- request `options.num_ctx`가 profile context size보다 우선하지 않는다.
|
||||
- chat/responses 양쪽 경로가 같은 정책을 따른다.
|
||||
- allow-pass-through option과 forced-owned option이 코드에서 구분된다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### API-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ go test -count=1 ./apps/edge/internal/openai/... ./apps/node/internal/adapters/ollama/...
|
||||
ok iop/apps/edge/internal/openai 1.508s
|
||||
ok iop/apps/node/internal/adapters/ollama 0.007s
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ go test -count=1 ./apps/edge/... ./apps/node/...
|
||||
ok iop/apps/edge/cmd/edge 0.057s
|
||||
ok iop/apps/edge/internal/bootstrap 0.027s
|
||||
ok iop/apps/edge/internal/controlplane 4.461s
|
||||
ok iop/apps/edge/internal/edgecmd 0.014s
|
||||
ok iop/apps/edge/internal/events 0.004s
|
||||
ok iop/apps/edge/internal/input 0.009s
|
||||
ok iop/apps/edge/internal/input/a2a 0.008s
|
||||
ok iop/apps/edge/internal/node 0.006s
|
||||
ok iop/apps/edge/internal/openai 1.511s
|
||||
ok iop/apps/edge/internal/opsconsole 0.009s
|
||||
FAIL iop/apps/edge/internal/service (pre-existing: TestListNodeSnapshotsWithConfig)
|
||||
FAIL iop/apps/edge/internal/transport (pre-existing: TestBuildConfigPayload_OllamaVllmOneof, TestEdgeServerIntegration)
|
||||
ok iop/apps/node/cmd/node 0.012s
|
||||
ok iop/apps/node/internal/adapters 0.009s
|
||||
ok iop/apps/node/internal/adapters/cli 42.085s
|
||||
ok iop/apps/node/internal/adapters/cli/status 39.765s
|
||||
ok iop/apps/node/internal/adapters/ollama 0.009s
|
||||
ok iop/apps/node/internal/adapters/vllm 0.007s
|
||||
ok iop/apps/node/internal/bootstrap 0.261s
|
||||
ok iop/apps/node/internal/node 0.014s
|
||||
ok iop/apps/node/internal/router 0.005s
|
||||
ok iop/apps/node/internal/store 0.044s
|
||||
ok iop/apps/node/internal/terminal 0.435s
|
||||
ok iop/apps/node/internal/transport 5.135s
|
||||
|
||||
※ service/transport 실패는 이 작업 이전부터 존재하는 사전 실패 (git stash 확인).
|
||||
이 작업 대상 패키지(openai, ollama) 전체 PASS.
|
||||
```
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
- 종합 판정: PASS
|
||||
- 차원별 평가:
|
||||
- correctness: Pass
|
||||
- completeness: Pass
|
||||
- test coverage: Pass
|
||||
- API contract: Pass
|
||||
- code quality: Pass
|
||||
- plan deviation: Pass
|
||||
- verification trust: Pass
|
||||
- 발견된 문제: 없음
|
||||
- 검증 메모:
|
||||
- 리뷰 중 `go test -count=1 ./apps/edge/internal/openai/... ./apps/node/internal/adapters/ollama/...`를 재실행했고 통과했다.
|
||||
- 리뷰 중 `go test -count=1 ./apps/edge/... ./apps/node/...`를 재실행했으며, 보고된 기존 실패와 같은 `apps/edge/internal/service`의 `TestListNodeSnapshotsWithConfig`, `apps/edge/internal/transport`의 `TestBuildConfigPayload_OllamaVllmOneof`, `TestEdgeServerIntegration` 실패를 확인했다. 실패 지점은 이번 diff의 `options.num_ctx` 강제 정책과 직접 연결되지 않는 기존 single-instance adapter fixture 계약이다.
|
||||
- 리뷰 중 stale README 설명을 `apps/node/README.md`에서 비동작 문서 정리로 수정했다.
|
||||
- 다음 단계: PASS이므로 `complete.log` 작성 후 task directory를 archive로 이동한다.
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
# Complete - m-node-multi-target-serving-foundation/05+03,04_edge_owned_options
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-06-11
|
||||
|
||||
## 요약
|
||||
|
||||
Edge-owned Ollama `context_size`가 request `options.num_ctx`보다 우선하도록 adapter 정책과 테스트를 정리했고, 1회차 리뷰에서 PASS로 종료했다.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_local_G07_0.log` | `code_review_local_G07_0.log` | PASS | `num_ctx` override 방지 구현과 대상 패키지 검증 통과를 확인했다. |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `apps/node/internal/adapters/ollama/ollama.go`에서 `context_size > 0`인 경우 request `options.num_ctx`를 항상 Edge-owned 값으로 덮어쓰게 했다.
|
||||
- `apps/node/internal/adapters/ollama/ollama_test.go`와 `apps/edge/internal/openai/server_test.go`에 request override가 profile/context 정책을 바꾸지 못하는 회귀 테스트를 추가했다.
|
||||
- `agent-ops/rules/project/domain/node/rules.md`와 `apps/node/README.md`의 Ollama context ownership 설명을 현재 정책에 맞게 정리했다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `go test -count=1 ./apps/edge/internal/openai/... ./apps/node/internal/adapters/ollama/...` - PASS; `iop/apps/edge/internal/openai`와 `iop/apps/node/internal/adapters/ollama` 모두 통과했다.
|
||||
- `go test -count=1 ./apps/edge/... ./apps/node/...` - FAIL; `apps/edge/internal/service`의 `TestListNodeSnapshotsWithConfig`, `apps/edge/internal/transport`의 `TestBuildConfigPayload_OllamaVllmOneof`, `TestEdgeServerIntegration` 실패가 재현됐다. 실패 지점은 이번 diff의 `options.num_ctx` 강제 정책과 직접 연결되지 않는 기존 single-instance adapter fixture 계약이다.
|
||||
|
||||
## Roadmap Completion
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md`
|
||||
- Completed task ids:
|
||||
- `edge-owned-options`: PASS; evidence=`plan_local_G07_0.log`, `code_review_local_G07_0.log`; verification=`go test -count=1 ./apps/edge/internal/openai/... ./apps/node/internal/adapters/ollama/...`
|
||||
- Not completed task ids: 없음
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
<!-- task=m-node-multi-target-serving-foundation/06+02,04_commands_caps plan=0 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> Fill implementation-owned sections and verification output, then report ready for review.
|
||||
> Do not ask the user directly; record user-only blockers in `사용자 리뷰 요청`.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-10
|
||||
task=m-node-multi-target-serving-foundation/06+02,04_commands_caps, plan=0, tag=API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md`
|
||||
- Task ids:
|
||||
- `commands-caps`: `CAPABILITIES`, `SESSION_LIST`, `USAGE_STATUS`, provider passthrough command가 여러 adapter/profile/model target에서 모호하지 않은 결과를 반환한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.
|
||||
|
||||
command/capability 결과가 adapter instance와 target namespace를 잃지 않는지 확인한다.
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [API-1] Command Identity Contract | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] capabilities 응답이 adapter instance/profile/model target identity를 모호하지 않게 포함한다.
|
||||
- [x] session/usage/provider command가 target namespace를 정확히 보존한다.
|
||||
- [x] Edge service command DTO와 Node command handling 테스트를 갱신한다.
|
||||
- [x] 최종 검증 명령을 실행한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
- [x] `코드리뷰 결과`에 판정을 append한다.
|
||||
- [x] 판정과 차원별 평가, Required 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-local-G07.md`를 `code_review_local_G07_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-local-G07.md`를 `plan_local_G07_M.log`로 아카이브한다.
|
||||
- [x] `.gitignore`가 생성된 task `.md`/`.log` artifacts를 ignore하지 않는지 확인한다.
|
||||
- [ ] PASS이면 `complete.log` 작성 후 archive 이동한다.
|
||||
- [x] WARN/FAIL이면 후속 상태를 작성한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
- `proto/iop/runtime.proto`: 변경 없음. 기존 proto가 `adapter`, `target`, `session_id`를 이미 충분히 보유하여 신규 필드 불필요.
|
||||
- `apps/node/internal/runtime/types.go`: 변경 없음. `Capabilities.InstanceKey`, `CommandRequest/Response.Adapter/Target/SessionID`가 이미 존재.
|
||||
- `apps/node/internal/node/node.go`: 변경 없음. `handleCapabilitiesCommand`, `toDomainCommandRequest`, `toProtoCommandResponse`가 이미 정확히 identity를 보존.
|
||||
- `apps/node/internal/adapters/cli/cli.go`: 변경 없음. `handleSessionList`가 per-session `mode/target/session_id` 구조체를 이미 반환, `handleUsageStatus`가 adapter/target/session을 응답에 이미 보존.
|
||||
- `apps/edge/internal/service/node_command.go`: `UsageStatusView`에 `Adapter` 필드 추가 + `UsageStatus()` 반환 시 `commandReq.GetAdapter()` 채움. (`NodeCommandView`는 이미 Adapter 있었으나 `UsageStatusView`만 누락).
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- **코드 변경 최소화**: G07/G08 이전 작업에서 capabilities/session/usage identity 처리가 이미 구현됨을 확인. 핵심 gap은 `UsageStatusView.Adapter` 누락뿐.
|
||||
- **`UsageStatusView.Adapter` 추가**: `NodeCommandView`와 일관성을 맞추기 위해 추가. multi-adapter 환경에서 caller가 어느 어댑터의 usage 상태인지 명확히 알 수 있음.
|
||||
- **`TestListNodeSnapshotsWithConfig` 버그 수정**: `Ollama` (legacy single-instance) 필드 대신 `OllamaInstances` 사용. `BuildConfigPayload` 주석("callers that bypass LoadEdge must populate OllamaInstances manually")과 일치.
|
||||
- **신규 테스트 추가**:
|
||||
- `TestOnCommandRequest_MultiAdapterCapabilities` (node_test.go): 두 instance key 어댑터 등록 후 각각의 CAPABILITIES가 올바른 `instance_key`를 반환하는지 검증.
|
||||
- `TestUsageStatusPreservesAdapterIdentity` (service_test.go): pipe 기반 mock node로 `UsageStatusView.Adapter/Target/SessionID` identity 보존 검증.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- capabilities/session/usage가 동일 target 이름 충돌에서 모호하지 않다.
|
||||
- Edge command DTO와 proto/runtime DTO가 같은 identity를 전달한다.
|
||||
- long command 처리와 concurrency scheduling 범위가 이 plan을 넘어 확장되지 않았다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### API-1 중간 검증
|
||||
|
||||
```
|
||||
ok iop/apps/node/cmd/node 0.015s
|
||||
ok iop/apps/node/internal/adapters 0.013s
|
||||
ok iop/apps/node/internal/adapters/cli 42.120s
|
||||
ok iop/apps/node/internal/adapters/cli/status 39.766s
|
||||
ok iop/apps/node/internal/adapters/ollama 0.010s
|
||||
ok iop/apps/node/internal/adapters/vllm 0.013s
|
||||
ok iop/apps/node/internal/bootstrap 0.267s
|
||||
ok iop/apps/node/internal/node 0.027s
|
||||
ok iop/apps/node/internal/router 0.012s
|
||||
ok iop/apps/node/internal/store 0.052s
|
||||
ok iop/apps/node/internal/terminal 0.441s
|
||||
ok iop/apps/node/internal/transport 5.149s
|
||||
ok iop/apps/edge/internal/service 0.005s
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```
|
||||
ok iop/apps/node/cmd/node 0.015s
|
||||
ok iop/apps/node/internal/adapters 0.013s
|
||||
ok iop/apps/node/internal/adapters/cli 42.120s
|
||||
ok iop/apps/node/internal/adapters/cli/status 39.766s
|
||||
ok iop/apps/node/internal/adapters/ollama 0.010s
|
||||
ok iop/apps/node/internal/adapters/vllm 0.013s
|
||||
ok iop/apps/node/internal/bootstrap 0.267s
|
||||
ok iop/apps/node/internal/node 0.027s
|
||||
ok iop/apps/node/internal/router 0.012s
|
||||
ok iop/apps/node/internal/store 0.052s
|
||||
ok iop/apps/node/internal/terminal 0.441s
|
||||
ok iop/apps/node/internal/transport 5.149s
|
||||
ok iop/apps/edge/internal/service 0.005s
|
||||
```
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
- 종합 판정: FAIL
|
||||
- 차원별 평가:
|
||||
- Correctness: Fail
|
||||
- Completeness: Fail
|
||||
- Test coverage: Fail
|
||||
- API contract: Fail
|
||||
- Code quality: Pass
|
||||
- Plan deviation: Fail
|
||||
- Verification trust: Pass
|
||||
- 발견된 문제:
|
||||
- Required: `apps/edge/internal/openai/ollama_passthrough.go:21`에서 `/api/*` Ollama passthrough가 `OllamaAPIRequest.Target`을 채우지 않는다. 결과적으로 `apps/edge/internal/service/node_command.go:153`의 `NodeCommandRequest.Target`도 빈 문자열이 되어, configured `openai.target` 또는 route target이 있는 multi-target 환경에서 provider passthrough command가 어느 model target에 대한 명령인지 보존하지 못한다. `handleOllamaAPI`가 `resolveRouteDispatch` 또는 적어도 `resolveTarget` 기준으로 target을 채우고, `TestOllamaAPIPassthrough`에 target 보존 assertion을 추가한다.
|
||||
- Required: `apps/edge/internal/opsconsole/status.go:44`가 새로 추가된 `UsageStatusView.Adapter`를 버리고 `FormatUsageStatus`는 `target/session`만 출력한다. `/status`는 `SendStatus`의 sent line 이후 실제 status result header에서 adapter identity를 잃으므로 같은 target/session을 여러 adapter instance가 공유하는 경우 결과가 모호하다. `FormatUsageStatus`와 호출부를 adapter 포함 형태로 바꾸고, opsconsole formatter 테스트에 `adapter=...` 출력 assertion을 추가한다.
|
||||
- 다음 단계:
|
||||
- FAIL 후속: `PLAN-local-G08.md` / `CODE_REVIEW-local-G08.md`에서 passthrough target 보존과 usage status 출력 identity를 보완한다.
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
<!-- task=m-node-multi-target-serving-foundation/06+02,04_commands_caps plan=1 tag=REVIEW_API -->
|
||||
|
||||
# Code Review Reference - REVIEW_API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves.
|
||||
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record the needed decision in `사용자 리뷰 요청` and stop for code-review.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-11
|
||||
task=m-node-multi-target-serving-foundation/06+02,04_commands_caps, plan=1, tag=REVIEW_API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md`
|
||||
- Task ids:
|
||||
- `commands-caps`: `CAPABILITIES`, `SESSION_LIST`, `USAGE_STATUS`, provider passthrough command가 여러 adapter/profile/model target에서 모호하지 않은 결과를 반환한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-local-G08.md` → `code_review_local_G08_N.log`, `PLAN-local-G08.md` → `plan_local_G08_M.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-node-multi-target-serving-foundation/06+02,04_commands_caps/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
|
||||
4. PASS이고 task group이 `m-node-multi-target-serving-foundation`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정이나 `update-roadmap` 직접 호출은 하지 않는다.
|
||||
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [REVIEW_API-1] Ollama Passthrough Target Identity | [x] |
|
||||
| [REVIEW_API-2] Usage Status Console Identity | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] `/api/*` Ollama passthrough가 resolved target identity를 `OllamaAPIRequest.Target`과 `NodeCommandRequest.Target`까지 보존한다.
|
||||
- [x] usage status console result header가 adapter/target/session identity를 함께 표시하고 테스트가 이를 검증한다.
|
||||
- [x] 관련 edge/openai/service/opsconsole 테스트를 갱신한다.
|
||||
- [x] 최종 검증 명령을 실행한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-local-G08.md`를 `code_review_local_G08_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-local-G08.md`를 `plan_local_G08_M.log`로 아카이브한다.
|
||||
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
|
||||
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS이면 active task 디렉터리 `agent-task/m-node-multi-target-serving-foundation/06+02,04_commands_caps/`를 `agent-task/archive/YYYY/MM/m-node-multi-target-serving-foundation/06+02,04_commands_caps/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-node-multi-target-serving-foundation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||
- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
|
||||
- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
없음. 계획에서 지정한 수정 파일과 테스트 방향을 그대로 따랐다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `FormatUsageStatus` 서명에 `adapterName` 파라미터를 `nodeAlias` 바로 다음에 삽입했다. header 출력 순서는 `adapter=` → `target=` → `session=`으로 identity 계층(어느 adapter → 어느 model → 어느 session) 순서를 따른다.
|
||||
- `resolveTarget("")`을 passthrough에 사용했다. `/api/*` 경로에는 request body에서 모델을 파싱할 수 없으므로 빈 문자열을 전달하면 configured target이 있을 때만 채워지고, 없으면 빈 값을 유지해 기존 동작을 깨지 않는다.
|
||||
- `events_test.go` 기존 테스트에서 `TestFormatUsageStatus_PrintsParsedLimits`에 header assertion(`adapter=cli@local target=codex session=default` 포함 확인)을 추가했다. 나머지 테스트는 adapter 파라미터만 추가하고 기존 assertions는 유지했다.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `/api/*` passthrough가 configured OpenAI target을 `OllamaAPIRequest.Target`과 service-built `NodeCommandRequest.Target`까지 보존한다.
|
||||
- usage status console header가 adapter/target/session을 모두 표시한다.
|
||||
- 새 테스트가 실패 재현과 수정 검증을 모두 포함한다.
|
||||
- 범위 밖 Ollama context_size 정책 변경이나 concurrency scheduling 변경이 섞이지 않았다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
|
||||
|
||||
### REVIEW_API-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/service
|
||||
ok iop/apps/edge/internal/openai 1.508s
|
||||
ok iop/apps/edge/internal/service 0.006s
|
||||
```
|
||||
|
||||
### REVIEW_API-2 중간 검증
|
||||
|
||||
```bash
|
||||
$ go test -count=1 ./apps/edge/internal/opsconsole
|
||||
ok iop/apps/edge/internal/opsconsole 0.006s
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/service ./apps/edge/internal/opsconsole
|
||||
ok iop/apps/edge/internal/openai 1.507s
|
||||
ok iop/apps/edge/internal/service 0.005s
|
||||
ok iop/apps/edge/internal/opsconsole 0.006s
|
||||
|
||||
$ go test -count=1 ./apps/node/... ./apps/edge/internal/service/...
|
||||
ok iop/apps/node/cmd/node 0.011s
|
||||
ok iop/apps/node/internal/adapters 0.010s
|
||||
ok iop/apps/node/internal/adapters/cli 42.209s
|
||||
? iop/apps/node/internal/adapters/cli/internal/testutil [no test files]
|
||||
ok iop/apps/node/internal/adapters/cli/status 39.777s
|
||||
? iop/apps/node/internal/adapters/mock [no test files]
|
||||
ok iop/apps/node/internal/adapters/ollama 0.009s
|
||||
ok iop/apps/node/internal/adapters/vllm 0.009s
|
||||
ok iop/apps/node/internal/bootstrap 0.266s
|
||||
ok iop/apps/node/internal/node 0.020s
|
||||
ok iop/apps/node/internal/router 0.009s
|
||||
? iop/apps/node/internal/runtime [no test files]
|
||||
ok iop/apps/node/internal/store 0.040s
|
||||
ok iop/apps/node/internal/terminal 0.450s
|
||||
ok iop/apps/node/internal/transport 5.152s
|
||||
ok iop/apps/edge/internal/service 0.006s
|
||||
|
||||
$ ./scripts/e2e-smoke.sh
|
||||
[e2e] NOTE: auxiliary smoke only; completion requires scripts/dev/edge.sh + scripts/dev/node.sh user-flow verification.
|
||||
[e2e] shellcheck not found, skipping
|
||||
[e2e] prompt templates: first=bye-short second=yes-short background=hello-basic base=9
|
||||
[e2e] preparing honest mock smoke test (using scripted cli adapter)...
|
||||
[e2e] starting smoke test (profile: mock, port: 30195, persistent: 1, has_status: 0)
|
||||
[e2e] waiting for node registration (timeout: 60s)
|
||||
[e2e] > /nodes
|
||||
[e2e] > /capabilities
|
||||
[e2e] > /transport
|
||||
[e2e] > (run tests omitted for brevity)
|
||||
[e2e] Auxiliary smoke test PASSED.
|
||||
[e2e] Completion still requires scripts/dev/edge.sh + scripts/dev/node.sh user-flow verification.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Ownership
|
||||
|
||||
| 섹션 | 소유자 | 설명 |
|
||||
|------|--------|------|
|
||||
| 헤더 주석, 개요(date/task/plan/tag), 리뷰 에이전트 지시 | 스텁 생성 시 고정 | 구현 에이전트가 수정하거나 실행하지 않음 |
|
||||
| Roadmap Targets | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 수정하지 않음; PASS 시 code-review가 `complete.log`의 `Roadmap Completion`으로 복사 |
|
||||
| 구현 항목별 완료 여부 (항목명) | 스텁 생성 시 고정 | `[ ]` → `[x]` 체크만 구현 에이전트가 수행 |
|
||||
| 구현 체크리스트 (항목 텍스트/순서) | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 `[ ]` → `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트가 채움 | placeholder 텍스트를 실제 내용으로 교체 |
|
||||
| 사용자 리뷰 요청 | 구현 에이전트가 채움 | 진행에 사용자 입력이 필요하지 않으면 `상태: 없음` 유지 |
|
||||
| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 |
|
||||
| 검증 결과 (섹션 제목 + 명령) | 스텁 생성 시 고정 | 실행 출력만 구현 에이전트가 채움 |
|
||||
| 코드리뷰 결과 | 리뷰 에이전트가 append | 스텁에 포함하지 않음 |
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
- 종합 판정: FAIL
|
||||
- 차원별 평가:
|
||||
- Correctness: Fail
|
||||
- Completeness: Fail
|
||||
- Test coverage: Fail
|
||||
- API contract: Fail
|
||||
- Code quality: Pass
|
||||
- Plan deviation: Warn
|
||||
- Verification trust: Pass
|
||||
- 발견된 문제:
|
||||
- Required: `apps/edge/internal/opsconsole/status.go:89`의 `FormatNodeCommandView`가 `NodeCommandView.Adapter`를 출력하지 않아 `/capabilities`, `/sessions`, `/transport` 결과 header가 여전히 `target/session`만 표시한다. 실제 `./scripts/e2e-smoke.sh` 출력도 `[node0-capabilities] target=fake-cli session=default`, `[node0-sessions] target=fake-cli session=session2` 형태라, usage status 외 command 결과는 같은 target/session을 여러 adapter instance가 공유할 때 모호하다. `FormatNodeCommandView` header에 `adapter=%s`를 포함하고 `console_test.go`의 capabilities/session/transport assertions를 갱신한다.
|
||||
- 다음 단계:
|
||||
- FAIL 후속: `PLAN-local-G08.md` / `CODE_REVIEW-local-G08.md`에서 non-usage node command formatter의 adapter identity 표시와 테스트를 보완한다.
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
<!-- task=m-node-multi-target-serving-foundation/06+02,04_commands_caps plan=2 tag=REVIEW_REVIEW_API -->
|
||||
|
||||
# Code Review Reference - REVIEW_REVIEW_API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves.
|
||||
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record the needed decision in `사용자 리뷰 요청` and stop for code-review.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-11
|
||||
task=m-node-multi-target-serving-foundation/06+02,04_commands_caps, plan=2, tag=REVIEW_REVIEW_API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md`
|
||||
- Task ids:
|
||||
- `commands-caps`: `CAPABILITIES`, `SESSION_LIST`, `USAGE_STATUS`, provider passthrough command가 여러 adapter/profile/model target에서 모호하지 않은 결과를 반환한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-local-G08.md` → `code_review_local_G08_N.log`, `PLAN-local-G08.md` → `plan_local_G08_M.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-node-multi-target-serving-foundation/06+02,04_commands_caps/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
|
||||
4. PASS이고 task group이 `m-node-multi-target-serving-foundation`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정이나 `update-roadmap` 직접 호출은 하지 않는다.
|
||||
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
| ---------------------------------------------------------| -----------|
|
||||
| [REVIEW_REVIEW_API-1] Non-Usage Command Header Identity | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] `FormatNodeCommandView`가 capabilities/session/transport 결과 header에 adapter/target/session identity를 함께 표시한다.
|
||||
- [x] capabilities, structured sessions, transport formatter 테스트가 adapter header를 검증한다.
|
||||
- [x] 최종 검증 명령을 실행한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-local-G08.md`를 `code_review_local_G08_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-local-G08.md`를 `plan_local_G08_M.log`로 아카이브한다.
|
||||
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
|
||||
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [x] PASS이면 active task 디렉터리 `agent-task/m-node-multi-target-serving-foundation/06+02,04_commands_caps/`를 `agent-task/archive/YYYY/MM/m-node-multi-target-serving-foundation/06+02,04_commands_caps/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-node-multi-target-serving-foundation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
|
||||
- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
없음. 계획에서 지정한 수정 파일과 테스트 방향을 그대로 따랐다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `FormatNodeCommandView` header 출력 순서를 `adapter=` → `target=` → `session=`으로 통일했다. `FormatUsageStatus`와 동일한 순서로 모든 command 결과 header의 identity 표시 방식을 일관되게 맞췄다.
|
||||
- `TestFormatNodeCommandView_RendersTransportStatusKeys`에 header assertion을 추가했다. 계획에 명시된 3개 테스트(capabilities, structured sessions, transport) 모두 adapter header를 검증한다.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `FormatNodeCommandView` header가 adapter/target/session을 모두 표시한다.
|
||||
- capabilities, sessions, transport formatter tests가 adapter header를 검증한다.
|
||||
- `USAGE_STATUS`, provider passthrough, Ollama context 정책 변경이 새로 섞이지 않았다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
필수 규칙:
|
||||
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
|
||||
|
||||
### REVIEW_REVIEW_API-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ go test -count=1 ./apps/edge/internal/opsconsole
|
||||
ok iop/apps/edge/internal/opsconsole 0.005s
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ go test -count=1 ./apps/edge/internal/opsconsole
|
||||
ok iop/apps/edge/internal/opsconsole 0.005s
|
||||
|
||||
$ ./scripts/e2e-smoke.sh
|
||||
[e2e] NOTE: auxiliary smoke only; completion requires scripts/dev/edge.sh + scripts/dev/node.sh user-flow verification.
|
||||
[e2e] > /capabilities
|
||||
[e2e] > /transport
|
||||
[e2e] > /sessions
|
||||
edge> [node0-capabilities] adapter=cli target=fake-cli session=default
|
||||
edge> [node0-transport] adapter=cli target=fake-cli session=default
|
||||
edge> [node0-sessions] adapter=cli target=fake-cli session=session2
|
||||
sessions: 2
|
||||
[e2e] Auxiliary smoke test PASSED.
|
||||
[e2e] Completion still requires scripts/dev/edge.sh + scripts/dev/node.sh user-flow verification.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## Ownership
|
||||
|
||||
| 섹션 | 소유자 | 설명 |
|
||||
| ---------------------------------------------------------| ---------------------------------------------| -----------------------------------------------------------------------------------------------------|
|
||||
| 헤더 주석, 개요(date/task/plan/tag), 리뷰 에이전트 지시 | 스텁 생성 시 고정 | 구현 에이전트가 수정하거나 실행하지 않음 |
|
||||
| Roadmap Targets | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 수정하지 않음; PASS 시 code-review가 `complete.log`의 `Roadmap Completion`으로 복사 |
|
||||
| 구현 항목별 완료 여부 (항목명) | 스텁 생성 시 고정 | `[ ]` → `[x]` 체크만 구현 에이전트가 수행 |
|
||||
| 구현 체크리스트 (항목 텍스트/순서) | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 `[ ]` → `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 |
|
||||
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
|
||||
| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트가 채움 | placeholder 텍스트를 실제 내용으로 교체 |
|
||||
| 사용자 리뷰 요청 | 구현 에이전트가 채움 | 진행에 사용자 입력이 필요하지 않으면 `상태: 없음` 유지 |
|
||||
| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 |
|
||||
| 검증 결과 (섹션 제목 + 명령) | 스텁 생성 시 고정 | 실행 출력만 구현 에이전트가 채움 |
|
||||
| 코드리뷰 결과 | 리뷰 에이전트가 append | 스텁에 포함하지 않음 |
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
- 종합 판정: PASS
|
||||
- 차원별 평가:
|
||||
- Correctness: Pass
|
||||
- Completeness: Pass
|
||||
- Test coverage: Pass
|
||||
- API contract: Pass
|
||||
- Code quality: Pass
|
||||
- Plan deviation: Pass
|
||||
- Verification trust: Pass
|
||||
- 발견된 문제: 없음
|
||||
- 다음 단계:
|
||||
- PASS: `complete.log` 작성 후 task directory를 archive로 이동한다.
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# Complete - m-node-multi-target-serving-foundation/06+02,04_commands_caps
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-06-11
|
||||
|
||||
## 요약
|
||||
|
||||
`commands-caps` command identity 보완 작업을 3회 리뷰 루프 끝에 PASS로 종료했다.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_local_G07_0.log` | `code_review_local_G07_0.log` | FAIL | Ollama passthrough target 누락과 usage status adapter 표시 누락 발견 |
|
||||
| `plan_local_G08_1.log` | `code_review_local_G08_1.log` | FAIL | non-usage command formatter가 adapter header를 표시하지 않는 문제 발견 |
|
||||
| `plan_local_G08_2.log` | `code_review_local_G08_2.log` | PASS | capabilities, sessions, transport, usage status, provider passthrough command identity 보존 확인 |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `/api/*` Ollama passthrough가 configured OpenAI target을 `OllamaAPIRequest.Target`과 service-built `NodeCommandRequest.Target`까지 보존하도록 했다.
|
||||
- `UsageStatusView`와 ops console `/status` 출력이 adapter/target/session identity를 함께 표시하도록 했다.
|
||||
- `FormatNodeCommandView`가 `/capabilities`, `/sessions`, `/transport` 결과 header에 adapter/target/session identity를 모두 표시하도록 했다.
|
||||
- 관련 openai/service/opsconsole/node 테스트를 추가 또는 갱신했다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `go test -count=1 ./apps/edge/internal/opsconsole` - PASS; `ok iop/apps/edge/internal/opsconsole`.
|
||||
- `./scripts/e2e-smoke.sh` - PASS; `/capabilities`, `/transport`, `/sessions` 출력에서 `adapter=cli target=fake-cli session=...` header 확인.
|
||||
- 이전 루프 검증: `go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/service ./apps/edge/internal/opsconsole` PASS, `go test -count=1 ./apps/node/... ./apps/edge/internal/service/...` PASS.
|
||||
|
||||
## Roadmap Completion
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md`
|
||||
- Completed task ids:
|
||||
- `commands-caps`: PASS; evidence=`plan_local_G08_2.log`, `code_review_local_G08_2.log`; verification=`go test -count=1 ./apps/edge/internal/opsconsole`, `./scripts/e2e-smoke.sh`
|
||||
- Not completed task ids: 없음
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<!-- task=m-node-multi-target-serving-foundation/06+02,04_commands_caps plan=1 tag=REVIEW_API -->
|
||||
|
||||
# Plan - REVIEW_API
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
이 plan은 직전 코드리뷰 `code_review_local_G07_0.log`의 Required 지적만 해결한다. 구현 중 사용자에게 직접 묻지 말고, 사용자 전용 blocker가 있으면 `CODE_REVIEW-local-G08.md`의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 기록한 뒤 멈춘다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md`
|
||||
- Task ids:
|
||||
- `commands-caps`: `CAPABILITIES`, `SESSION_LIST`, `USAGE_STATUS`, provider passthrough command가 여러 adapter/profile/model target에서 모호하지 않은 결과를 반환한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 배경
|
||||
|
||||
직전 구현은 service DTO 일부에 identity를 추가했지만, 두 표면에서 여전히 command result identity가 손실된다.
|
||||
|
||||
- OpenAI-compatible `/api/*` Ollama passthrough가 target을 채우지 않아 provider command가 어느 model target에 대한 것인지 보존하지 못한다.
|
||||
- ops console `/status` 결과 header가 adapter를 표시하지 않아 usage status 결과가 같은 target/session 조합에서 모호하다.
|
||||
|
||||
## 범위 결정 근거
|
||||
|
||||
- proto/runtime 구조 변경은 하지 않는다. 기존 `OllamaAPIRequest.Target`, `NodeCommandRequest.Target`, `UsageStatusView.Adapter`를 끝까지 사용하는 보완이다.
|
||||
- Ollama `context_size` 우선순위 변경과 node domain rule 변경은 이 follow-up 범위가 아니다.
|
||||
- concurrency scheduling, long command 비동기화, provider API body 재작성은 별도 task 범위다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `/api/*` Ollama passthrough가 resolved target identity를 `OllamaAPIRequest.Target`과 `NodeCommandRequest.Target`까지 보존한다.
|
||||
- [ ] usage status console result header가 adapter/target/session identity를 함께 표시하고 테스트가 이를 검증한다.
|
||||
- [ ] 관련 edge/openai/service/opsconsole 테스트를 갱신한다.
|
||||
- [ ] 최종 검증 명령을 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [REVIEW_API-1] Ollama Passthrough Target Identity
|
||||
|
||||
- 문제: `apps/edge/internal/openai/ollama_passthrough.go:21`이 `OllamaAPIRequest.Target`을 채우지 않는다. 그 결과 `apps/edge/internal/service/node_command.go:153`의 provider passthrough `NodeCommandRequest.Target`도 빈 값이 된다.
|
||||
- 해결 방법: `/api/*` passthrough에서도 OpenAI server의 configured target을 resolved target으로 전달한다. configured target이 비어 있는 legacy passthrough에서는 빈 target을 유지해 기존 동작을 깨지 않는다. route catalog 기반 target을 적용할 수 있는 입력 정보가 없으면 이 plan에서는 configured `openai.target` 보존까지만 다룬다.
|
||||
- 수정 파일:
|
||||
- `apps/edge/internal/openai/ollama_passthrough.go`
|
||||
- `apps/edge/internal/openai/server_test.go`
|
||||
- 필요 시 `apps/edge/internal/service/service_test.go`
|
||||
- 테스트 작성:
|
||||
- `TestOllamaAPIPassthrough` 또는 신규 테스트에서 `EdgeOpenAIConf{Adapter: "ollama", Target: "gemma4:26b"}`일 때 fake service가 받은 `OllamaAPIRequest.Target == "gemma4:26b"`인지 검증한다.
|
||||
- service level 테스트가 부족하면 `OllamaAPI`가 `NodeCommandRequest.Target`을 보존하는 assertion을 추가한다.
|
||||
- 중간 검증:
|
||||
- `go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/service`
|
||||
|
||||
### [REVIEW_API-2] Usage Status Console Identity
|
||||
|
||||
- 문제: `apps/edge/internal/opsconsole/status.go:44`가 `UsageStatusView.Adapter`를 버리고, `FormatUsageStatus` header는 `target/session`만 표시한다.
|
||||
- 해결 방법: `FormatUsageStatus` 호출부와 signature를 adapter 포함 형태로 바꾸고 header를 예를 들어 `[node-status] adapter=cli@dgx target=claude session=default`처럼 출력한다.
|
||||
- 수정 파일:
|
||||
- `apps/edge/internal/opsconsole/status.go`
|
||||
- `apps/edge/internal/opsconsole/events_test.go`
|
||||
- 필요 시 `apps/edge/internal/opsconsole/console_test.go`
|
||||
- 테스트 작성:
|
||||
- 기존 `FormatUsageStatus` 테스트 중 최소 1개 이상에서 header에 `adapter=... target=... session=...`이 포함되는지 검증한다.
|
||||
- `SendStatus`가 `result.Adapter`를 renderer에 전달하는 경로를 단위 테스트로 확인할 수 있으면 추가한다.
|
||||
- 중간 검증:
|
||||
- `go test -count=1 ./apps/edge/internal/opsconsole`
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/openai ./apps/edge/internal/service ./apps/edge/internal/opsconsole
|
||||
go test -count=1 ./apps/node/... ./apps/edge/internal/service/...
|
||||
```
|
||||
|
||||
edge service/console/HTTP command 표면에 닿으므로 로컬 smoke가 가능하면 `./scripts/e2e-smoke.sh`로 command 응답을 확인한다. 실행하지 못하면 `CODE_REVIEW-local-G08.md`의 검증 결과에 정확한 미실행 사유를 남긴다.
|
||||
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
<!-- task=m-node-multi-target-serving-foundation/06+02,04_commands_caps plan=2 tag=REVIEW_REVIEW_API -->
|
||||
|
||||
# Plan - REVIEW_REVIEW_API
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
이 plan은 `code_review_local_G08_1.log`의 Required 지적만 해결한다. 구현 중 사용자에게 직접 묻지 말고, 사용자 전용 blocker가 있으면 `CODE_REVIEW-local-G08.md`의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 기록한 뒤 멈춘다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md`
|
||||
- Task ids:
|
||||
- `commands-caps`: `CAPABILITIES`, `SESSION_LIST`, `USAGE_STATUS`, provider passthrough command가 여러 adapter/profile/model target에서 모호하지 않은 결과를 반환한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 배경
|
||||
|
||||
G08 구현은 `/api/*` passthrough target 보존과 `/status` header의 adapter 표시를 해결했다. 하지만 non-usage node command renderer는 여전히 `NodeCommandView.Adapter`를 버린다.
|
||||
|
||||
실제 `./scripts/e2e-smoke.sh` 출력에서도 다음처럼 adapter가 header에 없다.
|
||||
|
||||
```text
|
||||
[node0-capabilities] target=fake-cli session=default
|
||||
[node0-sessions] target=fake-cli session=session2
|
||||
```
|
||||
|
||||
## 범위 결정 근거
|
||||
|
||||
- 서비스 DTO와 proto/runtime 경계는 이미 adapter/target/session을 보존한다. 이번 작업은 ops console formatter의 표시 누락만 보완한다.
|
||||
- `USAGE_STATUS`는 G08에서 이미 보완됐으므로 수정하지 않는다.
|
||||
- provider passthrough, Ollama context 정책, command concurrency는 범위 밖이다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `FormatNodeCommandView`가 capabilities/session/transport 결과 header에 adapter/target/session identity를 함께 표시한다.
|
||||
- [ ] capabilities, structured sessions, transport formatter 테스트가 adapter header를 검증한다.
|
||||
- [ ] 최종 검증 명령을 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [REVIEW_REVIEW_API-1] Non-Usage Command Header Identity
|
||||
|
||||
- 문제: `apps/edge/internal/opsconsole/status.go:89`의 `FormatNodeCommandView`가 `view.Adapter`를 출력하지 않는다. `/capabilities`, `/sessions`, `/transport` 결과가 같은 target/session을 여러 adapter instance가 공유하는 환경에서 모호하다.
|
||||
- 해결 방법: `FormatNodeCommandView` header를 예를 들어 `[node0-capabilities] adapter=cli@local target=codex session=default` 형태로 바꾼다. 빈 adapter는 그대로 빈 문자열로 출력해 DTO 값을 숨기지 않는다.
|
||||
- 수정 파일:
|
||||
- `apps/edge/internal/opsconsole/status.go`
|
||||
- `apps/edge/internal/opsconsole/console_test.go`
|
||||
- 테스트 작성:
|
||||
- `TestFormatNodeCommandView_RendersSortedResult`에 `Adapter: "cli@local"`을 넣고 header에 `adapter=cli@local target=codex session=default`가 있는지 검증한다.
|
||||
- `TestFormatNodeCommandView_RendersStructuredSessions`에 adapter assertion을 추가한다.
|
||||
- `TestFormatNodeCommandView_RendersTransportStatusKeys`도 header adapter를 검증한다.
|
||||
- 중간 검증:
|
||||
- `go test -count=1 ./apps/edge/internal/opsconsole`
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/edge/internal/opsconsole
|
||||
./scripts/e2e-smoke.sh
|
||||
```
|
||||
|
||||
`./scripts/e2e-smoke.sh` 출력에서 `/capabilities`, `/sessions`, `/transport` 결과 header가 adapter/target/session을 모두 표시하는지 확인하고, 실제 stdout/stderr를 `CODE_REVIEW-local-G08.md`에 붙여 넣는다.
|
||||
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
<!-- task=m-node-multi-target-serving-foundation/05+03,04_edge_owned_options plan=0 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> Fill implementation-owned sections and real verification output before reporting ready for review.
|
||||
> Do not ask the user directly; record user-only blockers in `사용자 리뷰 요청`.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-10
|
||||
task=m-node-multi-target-serving-foundation/05+03,04_edge_owned_options, plan=0, tag=API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md`
|
||||
- Task ids:
|
||||
- `edge-owned-options`: context window와 runner sizing 옵션은 Edge config가 최종 소유하며, 외부 agent request의 `options.num_ctx` 같은 값이 Edge-owned 값을 변경하지 못한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.
|
||||
|
||||
request option이 route/profile owned option을 덮지 못하는지 테스트와 구현을 같이 확인한다.
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [API-1] Edge-Owned Runtime Options | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] Edge route/profile config의 context/runner sizing 옵션이 request option보다 우선한다.
|
||||
- [ ] OpenAI chat/responses에서 request `options.num_ctx` 등 runner sizing override가 내부 profile 값을 바꾸지 못한다.
|
||||
- [ ] provider별 option merge 정책을 테스트로 고정한다.
|
||||
- [ ] 최종 검증 명령을 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
- [ ] `코드리뷰 결과`에 판정을 append한다.
|
||||
- [ ] active `CODE_REVIEW-local-G07.md`를 `code_review_local_G07_N.log`로 아카이브한다.
|
||||
- [ ] active `PLAN-local-G07.md`를 `plan_local_G07_M.log`로 아카이브한다.
|
||||
- [ ] PASS이면 `complete.log` 작성 후 archive 이동한다.
|
||||
- [ ] WARN/FAIL이면 후속 상태를 작성한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- request `options.num_ctx`가 profile context size보다 우선하지 않는다.
|
||||
- chat/responses 양쪽 경로가 같은 정책을 따른다.
|
||||
- allow-pass-through option과 forced-owned option이 코드에서 구분된다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### API-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ go test -count=1 ./apps/edge/internal/openai/... ./apps/node/internal/adapters/ollama/...
|
||||
(output)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ go test -count=1 ./apps/edge/... ./apps/node/...
|
||||
(output)
|
||||
```
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
<!-- task=m-node-multi-target-serving-foundation/06+02,04_commands_caps plan=0 tag=API -->
|
||||
|
||||
# Code Review Reference - API
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> Fill implementation-owned sections and verification output, then report ready for review.
|
||||
> Do not ask the user directly; record user-only blockers in `사용자 리뷰 요청`.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-10
|
||||
task=m-node-multi-target-serving-foundation/06+02,04_commands_caps, plan=0, tag=API
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/inference-provider-extension/milestones/node-multi-target-serving-foundation.md`
|
||||
- Task ids:
|
||||
- `commands-caps`: `CAPABILITIES`, `SESSION_LIST`, `USAGE_STATUS`, provider passthrough command가 여러 adapter/profile/model target에서 모호하지 않은 결과를 반환한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.
|
||||
|
||||
command/capability 결과가 adapter instance와 target namespace를 잃지 않는지 확인한다.
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [API-1] Command Identity Contract | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] capabilities 응답이 adapter instance/profile/model target identity를 모호하지 않게 포함한다.
|
||||
- [ ] session/usage/provider command가 target namespace를 정확히 보존한다.
|
||||
- [ ] Edge service command DTO와 Node command handling 테스트를 갱신한다.
|
||||
- [ ] 최종 검증 명령을 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
- [ ] `코드리뷰 결과`에 판정을 append한다.
|
||||
- [ ] active `CODE_REVIEW-local-G07.md`를 `code_review_local_G07_N.log`로 아카이브한다.
|
||||
- [ ] active `PLAN-local-G07.md`를 `plan_local_G07_M.log`로 아카이브한다.
|
||||
- [ ] PASS이면 `complete.log` 작성 후 archive 이동한다.
|
||||
- [ ] WARN/FAIL이면 후속 상태를 작성한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- capabilities/session/usage가 동일 target 이름 충돌에서 모호하지 않다.
|
||||
- Edge command DTO와 proto/runtime DTO가 같은 identity를 전달한다.
|
||||
- long command 처리와 concurrency scheduling 범위가 이 plan을 넘어 확장되지 않았다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### API-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ go test -count=1 ./apps/node/... ./apps/edge/internal/service/...
|
||||
(output)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ go test -count=1 ./apps/node/... ./apps/edge/internal/service/...
|
||||
(output)
|
||||
```
|
||||
|
|
@ -21,6 +21,7 @@ func (s *Server) handleOllamaAPI(w http.ResponseWriter, r *http.Request) {
|
|||
resp, err := s.service.OllamaAPI(r.Context(), edgeservice.OllamaAPIRequest{
|
||||
NodeRef: s.cfg.NodeRef,
|
||||
Adapter: s.resolveAdapter(),
|
||||
Target: s.resolveTarget(""),
|
||||
Method: r.Method,
|
||||
Path: r.URL.RequestURI(),
|
||||
Body: string(body),
|
||||
|
|
|
|||
|
|
@ -426,6 +426,28 @@ func TestOllamaAPIPassthrough(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOllamaAPIPassthroughPreservesConfiguredTarget(t *testing.T) {
|
||||
fake := &fakeRunService{
|
||||
ollamaResp: edgeservice.OllamaAPIView{
|
||||
StatusCode: http.StatusOK,
|
||||
ContentType: "application/json",
|
||||
Body: `{"models":[]}`,
|
||||
},
|
||||
}
|
||||
srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "gemma4:26b", TimeoutSec: 15}, fake, nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/tags", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
srv.handleOllamaAPI(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
if fake.ollamaReq.Target != "gemma4:26b" {
|
||||
t.Fatalf("passthrough target: got %q, want gemma4:26b", fake.ollamaReq.Target)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponsesDispatchesNonStreamingRequest(t *testing.T) {
|
||||
fake := &fakeRunService{events: make(chan *iop.RunEvent, 3)}
|
||||
fake.events <- &iop.RunEvent{Type: "delta", Delta: "hello"}
|
||||
|
|
@ -986,6 +1008,34 @@ func TestResponsesRouteCatalogTakesPriorityOverMetadataTarget(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestChatCompletionsEdgePassesNumCtxForAdapterEnforcement(t *testing.T) {
|
||||
// Edge passes request options.num_ctx to the service unchanged.
|
||||
// The Ollama adapter enforces Edge-owned context_size over this value.
|
||||
fake := &fakeRunService{events: make(chan *iop.RunEvent, 2)}
|
||||
fake.events <- &iop.RunEvent{Type: "delta", Delta: "ok"}
|
||||
fake.events <- &iop.RunEvent{Type: "complete"}
|
||||
|
||||
srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "llama"}, fake, nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", strings.NewReader(`{
|
||||
"model":"from-request",
|
||||
"messages":[{"role":"user","content":"hi"}],
|
||||
"options":{"num_ctx":8192}
|
||||
}`))
|
||||
w := httptest.NewRecorder()
|
||||
srv.handleChatCompletions(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
options, ok := fake.req.Input["options"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("options not passed to service: %+v", fake.req.Input)
|
||||
}
|
||||
if options["num_ctx"].(float64) != 8192 {
|
||||
t.Fatalf("edge should pass num_ctx unchanged for adapter enforcement: got %v", options["num_ctx"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectRunResultFailsWhenEventStreamCloses(t *testing.T) {
|
||||
events := make(chan *iop.RunEvent)
|
||||
close(events)
|
||||
|
|
|
|||
|
|
@ -171,6 +171,7 @@ func TestSendRun_SubmitRunRequest_MetadataSource(t *testing.T) {
|
|||
func TestFormatNodeCommandView_RendersSortedResult(t *testing.T) {
|
||||
view := edgeservice.NodeCommandView{
|
||||
NodeLabel: "node0",
|
||||
Adapter: "cli@local",
|
||||
Target: "codex",
|
||||
SessionID: "default",
|
||||
Result: map[string]string{
|
||||
|
|
@ -181,7 +182,7 @@ func TestFormatNodeCommandView_RendersSortedResult(t *testing.T) {
|
|||
var out bytes.Buffer
|
||||
FormatNodeCommandView(&out, "capabilities", view)
|
||||
got := out.String()
|
||||
if !strings.Contains(got, "[node0-capabilities] target=codex session=default") {
|
||||
if !strings.Contains(got, "[node0-capabilities] adapter=cli@local target=codex session=default") {
|
||||
t.Errorf("missing header: %q", got)
|
||||
}
|
||||
// keys must appear in sorted order: targets before version
|
||||
|
|
@ -204,6 +205,7 @@ func TestFormatNodeCommandView_EmptyResult(t *testing.T) {
|
|||
func TestFormatNodeCommandView_RendersStructuredSessions(t *testing.T) {
|
||||
view := edgeservice.NodeCommandView{
|
||||
NodeLabel: "node0",
|
||||
Adapter: "cli@local",
|
||||
Target: "claude",
|
||||
SessionID: "default",
|
||||
Result: map[string]string{
|
||||
|
|
@ -220,7 +222,7 @@ func TestFormatNodeCommandView_RendersStructuredSessions(t *testing.T) {
|
|||
var out bytes.Buffer
|
||||
FormatNodeCommandView(&out, "sessions", view)
|
||||
got := out.String()
|
||||
if !strings.Contains(got, "[node0-sessions] target=claude session=default") {
|
||||
if !strings.Contains(got, "[node0-sessions] adapter=cli@local target=claude session=default") {
|
||||
t.Errorf("missing header: %q", got)
|
||||
}
|
||||
if !strings.Contains(got, "sessions: 2") {
|
||||
|
|
@ -237,6 +239,7 @@ func TestFormatNodeCommandView_RendersStructuredSessions(t *testing.T) {
|
|||
func TestFormatNodeCommandView_RendersTransportStatusKeys(t *testing.T) {
|
||||
view := edgeservice.NodeCommandView{
|
||||
NodeLabel: "node0",
|
||||
Adapter: "cli@local",
|
||||
Target: "claude",
|
||||
SessionID: "default",
|
||||
Result: map[string]string{
|
||||
|
|
@ -251,6 +254,9 @@ func TestFormatNodeCommandView_RendersTransportStatusKeys(t *testing.T) {
|
|||
var out bytes.Buffer
|
||||
FormatNodeCommandView(&out, "transport", view)
|
||||
got := out.String()
|
||||
if !strings.Contains(got, "[node0-transport] adapter=cli@local target=claude session=default") {
|
||||
t.Errorf("missing header: %q", got)
|
||||
}
|
||||
for _, want := range []string{"connected = true", "state = connected", "node_id = test-node", "session_id = default"} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("expected %q in transport output, got:\n%s", want, got)
|
||||
|
|
|
|||
|
|
@ -270,13 +270,16 @@ func TestStatusWaitTimeout_UsesNormalizedRequestTimeout(t *testing.T) {
|
|||
|
||||
func TestFormatUsageStatus_PrintsParsedLimits(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
FormatUsageStatus(&out, "local-node", "codex", "default", &iop.AgentUsageStatus{
|
||||
FormatUsageStatus(&out, "local-node", "cli@local", "codex", "default", &iop.AgentUsageStatus{
|
||||
DailyLimit: "98%",
|
||||
DailyResetTime: "18:38",
|
||||
WeeklyLimit: "22%",
|
||||
WeeklyResetTime: "10:20 on 8 May",
|
||||
})
|
||||
got := out.String()
|
||||
if !strings.Contains(got, "adapter=cli@local target=codex session=default") {
|
||||
t.Errorf("expected header with adapter identity, got:\n%s", got)
|
||||
}
|
||||
if !strings.Contains(got, "Daily limit: 98% remaining (resets 18:38)") {
|
||||
t.Errorf("expected Daily limit: 98%% remaining (resets 18:38), got:\n%s", got)
|
||||
}
|
||||
|
|
@ -287,7 +290,7 @@ func TestFormatUsageStatus_PrintsParsedLimits(t *testing.T) {
|
|||
|
||||
func TestFormatUsageStatus_PrintsClaudeLabels(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
FormatUsageStatus(&out, "local-node", "claude", "default", &iop.AgentUsageStatus{
|
||||
FormatUsageStatus(&out, "local-node", "cli@local", "claude", "default", &iop.AgentUsageStatus{
|
||||
DailyLimit: "98%",
|
||||
DailyResetTime: "10am (Asia/Seoul)",
|
||||
WeeklyLimit: "50%",
|
||||
|
|
@ -308,7 +311,7 @@ func TestFormatUsageStatus_PrintsClaudeLabels(t *testing.T) {
|
|||
|
||||
func TestFormatUsageStatus_PrintsAntigravityQuotaMetadata(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
FormatUsageStatus(&out, "local-node", "antigravity", "default", &iop.AgentUsageStatus{
|
||||
FormatUsageStatus(&out, "local-node", "cli@local", "antigravity", "default", &iop.AgentUsageStatus{
|
||||
DailyLimit: "100%",
|
||||
DailyResetTime: "24h",
|
||||
Metadata: map[string]string{
|
||||
|
|
@ -331,7 +334,7 @@ func TestFormatUsageStatus_PrintsAntigravityQuotaMetadata(t *testing.T) {
|
|||
|
||||
func TestFormatUsageStatus_PrintsModelUsage(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
FormatUsageStatus(&out, "local-node", "antigravity", "default", &iop.AgentUsageStatus{
|
||||
FormatUsageStatus(&out, "local-node", "cli@local", "antigravity", "default", &iop.AgentUsageStatus{
|
||||
DailyLimit: "100%",
|
||||
Metadata: map[string]string{
|
||||
"daily_label": "Daily quota",
|
||||
|
|
@ -359,7 +362,7 @@ func TestFormatUsageStatus_PrintsModelUsage(t *testing.T) {
|
|||
|
||||
func TestFormatUsageStatus_PrintsClaudeStatusMetadata(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
FormatUsageStatus(&out, "local-node", "claude-tui", "default", &iop.AgentUsageStatus{
|
||||
FormatUsageStatus(&out, "local-node", "cli@1", "claude-tui", "default", &iop.AgentUsageStatus{
|
||||
Metadata: map[string]string{
|
||||
"status_kind": "claude",
|
||||
"claude_status_version": "2.1.144",
|
||||
|
|
@ -416,7 +419,7 @@ func TestFormatUsageStatus_PrintsFallbackMetadata(t *testing.T) {
|
|||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
FormatUsageStatus(&out, "local-node", "codex", "default", tc.status)
|
||||
FormatUsageStatus(&out, "local-node", "cli@local", "codex", "default", tc.status)
|
||||
got := out.String()
|
||||
if !strings.Contains(got, tc.wantContain) {
|
||||
t.Errorf("expected %q in output, got:\n%s", tc.wantContain, got)
|
||||
|
|
@ -427,7 +430,7 @@ func TestFormatUsageStatus_PrintsFallbackMetadata(t *testing.T) {
|
|||
|
||||
func TestFormatUsageStatus_Error(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
FormatUsageStatus(&out, "local-node", "codex", "default", &iop.AgentUsageStatus{
|
||||
FormatUsageStatus(&out, "local-node", "cli@local", "codex", "default", &iop.AgentUsageStatus{
|
||||
RawOutput: "unparsed error text",
|
||||
})
|
||||
got := out.String()
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func SendStatus(ctx context.Context, edgeSvc *edgeservice.Service, out io.Writer
|
|||
return err
|
||||
}
|
||||
|
||||
FormatUsageStatus(out, result.NodeLabel, result.Target, result.SessionID, result.UsageStatus)
|
||||
FormatUsageStatus(out, result.NodeLabel, result.Adapter, result.Target, result.SessionID, result.UsageStatus)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ func sendNodeCommandView(ctx context.Context, out io.Writer, target *TargetState
|
|||
// results with structured session.N.* keys, a human-readable grouped layout
|
||||
// is used. All other commands fall back to stable sorted key output.
|
||||
func FormatNodeCommandView(out io.Writer, label string, view edgeservice.NodeCommandView) {
|
||||
fmt.Fprintf(out, "[%s-%s] target=%s session=%s\n", view.NodeLabel, label, view.Target, view.SessionID)
|
||||
fmt.Fprintf(out, "[%s-%s] adapter=%s target=%s session=%s\n", view.NodeLabel, label, view.Adapter, view.Target, view.SessionID)
|
||||
if len(view.Result) == 0 {
|
||||
fmt.Fprintf(out, "no %s payload returned\n", label)
|
||||
return
|
||||
|
|
@ -125,8 +125,8 @@ func renderStructuredSessions(out io.Writer, result map[string]string) {
|
|||
}
|
||||
}
|
||||
|
||||
func FormatUsageStatus(out io.Writer, nodeAlias, targetName, sessionID string, status *iop.AgentUsageStatus) {
|
||||
fmt.Fprintf(out, "[%s-status] target=%s session=%s\n", nodeAlias, targetName, sessionID)
|
||||
func FormatUsageStatus(out io.Writer, nodeAlias, adapterName, targetName, sessionID string, status *iop.AgentUsageStatus) {
|
||||
fmt.Fprintf(out, "[%s-status] adapter=%s target=%s session=%s\n", nodeAlias, adapterName, targetName, sessionID)
|
||||
|
||||
if status == nil {
|
||||
fmt.Fprintln(out, "no usage status provided")
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ type UsageStatusRequest struct {
|
|||
type UsageStatusView struct {
|
||||
NodeID string
|
||||
NodeLabel string
|
||||
Adapter string
|
||||
Target string
|
||||
SessionID string
|
||||
UsageStatus *iop.AgentUsageStatus
|
||||
|
|
@ -54,6 +55,7 @@ func (s *Service) UsageStatus(_ context.Context, req UsageStatusRequest) (UsageS
|
|||
return UsageStatusResult{
|
||||
NodeID: entry.NodeID,
|
||||
NodeLabel: nodeLabel(entry),
|
||||
Adapter: commandReq.GetAdapter(),
|
||||
Target: commandReq.GetTarget(),
|
||||
SessionID: commandReq.GetSessionId(),
|
||||
UsageStatus: resp.GetUsageStatus(),
|
||||
|
|
|
|||
|
|
@ -312,10 +312,8 @@ func TestListNodeSnapshotsWithConfig(t *testing.T) {
|
|||
WorkspaceRoot: "/workspace",
|
||||
},
|
||||
Adapters: config.AdaptersConf{
|
||||
Ollama: config.OllamaConf{
|
||||
Enabled: true,
|
||||
BaseURL: "http://localhost:11434",
|
||||
ContextSize: 2048,
|
||||
OllamaInstances: []config.OllamaInstanceConf{
|
||||
{Name: "ollama", Enabled: true, BaseURL: "http://localhost:11434", ContextSize: 2048},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -716,6 +714,67 @@ func TestServiceExecuteCommandAgentCommandCapabilitiesSuccess(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUsageStatusPreservesAdapterIdentity(t *testing.T) {
|
||||
edgeConn, nodeConn := net.Pipe()
|
||||
defer edgeConn.Close()
|
||||
defer nodeConn.Close()
|
||||
|
||||
parserMap := toki.ParserMap{
|
||||
toki.TypeNameOf(&iop.NodeCommandRequest{}): func(b []byte) (proto.Message, error) {
|
||||
m := &iop.NodeCommandRequest{}
|
||||
return m, proto.Unmarshal(b, m)
|
||||
},
|
||||
toki.TypeNameOf(&iop.NodeCommandResponse{}): func(b []byte) (proto.Message, error) {
|
||||
m := &iop.NodeCommandResponse{}
|
||||
return m, proto.Unmarshal(b, m)
|
||||
},
|
||||
}
|
||||
|
||||
edgeClient := toki.NewTcpClient(edgeConn, 0, 0, parserMap)
|
||||
nodeClient := toki.NewTcpClient(nodeConn, 0, 0, parserMap)
|
||||
|
||||
toki.AddRequestListenerTyped(&nodeClient.Communicator, func(req *iop.NodeCommandRequest) (*iop.NodeCommandResponse, error) {
|
||||
return &iop.NodeCommandResponse{
|
||||
RequestId: req.RequestId,
|
||||
Type: req.Type,
|
||||
Adapter: req.Adapter,
|
||||
Target: req.Target,
|
||||
SessionId: req.SessionId,
|
||||
UsageStatus: &iop.AgentUsageStatus{RawOutput: "ok"},
|
||||
}, nil
|
||||
})
|
||||
|
||||
reg := edgenode.NewRegistry()
|
||||
reg.Register(&edgenode.NodeEntry{
|
||||
NodeID: "node-uid",
|
||||
Alias: "svc-node",
|
||||
Client: edgeClient,
|
||||
})
|
||||
svc := edgeservice.New(reg, nil)
|
||||
|
||||
result, err := svc.UsageStatus(context.Background(), edgeservice.UsageStatusRequest{
|
||||
NodeRef: "node-uid",
|
||||
Adapter: "cli@1",
|
||||
Target: "claude",
|
||||
SessionID: "sess-u",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("UsageStatus: %v", err)
|
||||
}
|
||||
if result.Adapter != "cli@1" {
|
||||
t.Errorf("Adapter: got %q want %q", result.Adapter, "cli@1")
|
||||
}
|
||||
if result.Target != "claude" {
|
||||
t.Errorf("Target: got %q want %q", result.Target, "claude")
|
||||
}
|
||||
if result.SessionID != "sess-u" {
|
||||
t.Errorf("SessionID: got %q want %q", result.SessionID, "sess-u")
|
||||
}
|
||||
if result.UsageStatus == nil || result.UsageStatus.RawOutput != "ok" {
|
||||
t.Errorf("UsageStatus: got %+v", result.UsageStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceExecuteCommandAgentCommandCapabilitiesNodeError(t *testing.T) {
|
||||
edgeConn, nodeConn := net.Pipe()
|
||||
defer edgeConn.Close()
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ node는 adapter execution(`RunRequest`) 외에도 edge가 보내는 `NodeCommand
|
|||
| `vllm` | vLLM OpenAI-compatible API | `/v1/models` 조회 skeleton |
|
||||
| `cli` | claude/antigravity/codex/opencode/cline CLI 실행 | 구현 완료 |
|
||||
|
||||
Ollama adapter는 edge에서 받은 내부 target을 Ollama model 이름으로 사용한다. Edge OpenAI-compatible HTTP API에서 `openai.target`을 지정하면 외부 `model` 값과 무관하게 해당 Ollama model로 라우팅하고, `openai.target`을 비우면 요청의 `model` 값을 그대로 내부 target으로 전달한다. `adapters.ollama.context_size`가 설정되어 있으면 Ollama `/api/chat` 요청의 `options.num_ctx`로 전달한다.
|
||||
Ollama adapter는 edge에서 받은 내부 target을 Ollama model 이름으로 사용한다. Edge OpenAI-compatible HTTP API에서 `openai.target`을 지정하면 외부 `model` 값과 무관하게 해당 Ollama model로 라우팅하고, `openai.target`을 비우면 요청의 `model` 값을 그대로 내부 target으로 전달한다. `adapters.ollama.context_size`가 설정되어 있으면 Ollama `/api/chat` 요청의 `options.num_ctx`를 강제하며, 요청 input의 `options.num_ctx`보다 우선한다.
|
||||
|
||||
`claude`, `antigravity`, `codex`, `opencode`, `cline`처럼 기본이 interactive TUI인 CLI는 기본적으로 non-interactive 모드로 설정한다. 예: `claude -p --dangerously-skip-permissions`, `agy --dangerously-skip-permissions --print <prompt>`, `codex exec --dangerously-bypass-approvals-and-sandbox`, `cline -y --json --config /config/.cline/profiles/ollama-dgx <prompt>`.
|
||||
|
||||
|
|
|
|||
|
|
@ -312,9 +312,7 @@ func ollamaOptionsFromInput(input map[string]any, contextSize int) map[string]an
|
|||
}
|
||||
}
|
||||
if contextSize > 0 {
|
||||
if _, hasNumCtx := options["num_ctx"]; !hasNumCtx {
|
||||
options["num_ctx"] = contextSize
|
||||
}
|
||||
options["num_ctx"] = contextSize // Edge-owned: always overrides request
|
||||
}
|
||||
if len(options) == 0 {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -117,12 +117,12 @@ func TestOllamaExecutePassesOptionsAndTopLevelFields(t *testing.T) {
|
|||
}
|
||||
switch v := req.Options["num_ctx"].(type) {
|
||||
case float64:
|
||||
if int(v) != 4096 {
|
||||
t.Fatalf("request num_ctx should win over config default: got %d, want 4096", int(v))
|
||||
if int(v) != 262144 {
|
||||
t.Fatalf("profile context_size should override request num_ctx: got %d, want 262144", int(v))
|
||||
}
|
||||
case int:
|
||||
if v != 4096 {
|
||||
t.Fatalf("request num_ctx should win over config default: got %d, want 4096", v)
|
||||
if v != 262144 {
|
||||
t.Fatalf("profile context_size should override request num_ctx: got %d, want 262144", v)
|
||||
}
|
||||
default:
|
||||
t.Fatalf("unexpected num_ctx type %T in %+v", v, req.Options)
|
||||
|
|
@ -323,6 +323,41 @@ func TestOllamaMultiEndpoint(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOllamaContextSizeForcedOverRequestNumCtx(t *testing.T) {
|
||||
var gotNumCtx int
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var req ollamaChatRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
t.Fatalf("decode request: %v", err)
|
||||
}
|
||||
if v, ok := req.Options["num_ctx"].(float64); ok {
|
||||
gotNumCtx = int(v)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/x-ndjson")
|
||||
_, _ = w.Write([]byte(`{"message":{"role":"assistant","content":"ok"},"done":false}` + "\n"))
|
||||
_, _ = w.Write([]byte(`{"done":true}` + "\n"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// profile context_size=262144 must override request options.num_ctx=8192
|
||||
adapter := New(config.OllamaConf{BaseURL: server.URL, ContextSize: 262144}, zap.NewNop())
|
||||
sink := &fakeSink{}
|
||||
err := adapter.Execute(context.Background(), noderuntime.ExecutionSpec{
|
||||
RunID: "run-forced",
|
||||
Target: "llama-test",
|
||||
Input: map[string]any{
|
||||
"prompt": "hi",
|
||||
"options": map[string]any{"num_ctx": float64(8192), "temperature": 0.5},
|
||||
},
|
||||
}, sink)
|
||||
if err != nil {
|
||||
t.Fatalf("Execute failed: %v", err)
|
||||
}
|
||||
if gotNumCtx != 262144 {
|
||||
t.Fatalf("Edge-owned context_size must override request num_ctx: got %d, want 262144", gotNumCtx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOllamaExecuteStreamsThinkingDeltasSeparately(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/api/chat" {
|
||||
|
|
|
|||
|
|
@ -845,6 +845,51 @@ func TestOnCommandRequest_AdapterDispatch_AmbiguousAdapter(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOnCommandRequest_MultiAdapterCapabilities(t *testing.T) {
|
||||
ika1 := &instanceKeyAdapter{instanceKey: "ollama@local"}
|
||||
ika2 := &instanceKeyAdapter{instanceKey: "ollama@dgx"}
|
||||
router := &fixedRouter{
|
||||
adapters: map[string]runtime.Adapter{
|
||||
"ollama@local": ika1,
|
||||
"ollama@dgx": ika2,
|
||||
},
|
||||
}
|
||||
n, _ := makeNode(t, router)
|
||||
|
||||
cases := []struct {
|
||||
adapter string
|
||||
wantInstKey string
|
||||
}{
|
||||
{"ollama@local", "ollama@local"},
|
||||
{"ollama@dgx", "ollama@dgx"},
|
||||
}
|
||||
instKeys := make([]string, 0, len(cases))
|
||||
for _, tc := range cases {
|
||||
resp, err := n.OnCommandRequest(context.Background(), &transport.Session{}, &iop.NodeCommandRequest{
|
||||
RequestId: "req-multi-" + tc.adapter,
|
||||
Type: iop.NodeCommandType_NODE_COMMAND_TYPE_CAPABILITIES,
|
||||
Adapter: tc.adapter,
|
||||
Target: "llama3",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("CAPABILITIES %s: %v", tc.adapter, err)
|
||||
}
|
||||
if resp.GetError() != "" {
|
||||
t.Fatalf("CAPABILITIES %s: unexpected error %q", tc.adapter, resp.GetError())
|
||||
}
|
||||
if got := resp.GetResult()["instance_key"]; got != tc.wantInstKey {
|
||||
t.Errorf("CAPABILITIES %s: instance_key got %q want %q", tc.adapter, got, tc.wantInstKey)
|
||||
}
|
||||
if resp.GetAdapter() != tc.adapter {
|
||||
t.Errorf("CAPABILITIES %s: response adapter got %q want %q", tc.adapter, resp.GetAdapter(), tc.adapter)
|
||||
}
|
||||
instKeys = append(instKeys, resp.GetResult()["instance_key"])
|
||||
}
|
||||
if instKeys[0] == instKeys[1] {
|
||||
t.Errorf("expected distinct instance_key for distinct adapters, both got %q", instKeys[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnCommandRequest_Capabilities_ExactInstanceKey(t *testing.T) {
|
||||
ika := &instanceKeyAdapter{instanceKey: "ollama@local"}
|
||||
router := &fixedRouter{
|
||||
|
|
|
|||
Loading…
Reference in a new issue