cuelo 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +7 -7
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.js +1 -1
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/agent/[id]/account/route.js +1 -1
- package/.next/server/app/api/agent/[id]/events/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/running/route.js +1 -1
- package/.next/server/app/api/attachments/[id]/transcription/route.js +1 -1
- package/.next/server/app/api/gpt6/handles/route.js +1 -1
- package/.next/server/app/api/gpt6/reply/route.js +1 -1
- package/.next/server/app/api/gpt6/token/route.js +1 -1
- package/.next/server/app/api/gpt6/web6-session/route.js +1 -1
- package/.next/server/app/api/live/call/[callId]/route.js +1 -1
- package/.next/server/app/api/live/events/route.js +1 -1
- package/.next/server/app/api/live/offer/route.js +1 -1
- package/.next/server/app/api/live/voices/route.js +1 -1
- package/.next/server/app/api/lounge/events/route.js +1 -1
- package/.next/server/app/api/lounge/route.js +1 -1
- package/.next/server/app/api/project-trust/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/auto-name/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/context/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/state/route.js +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/tool-image/route.js +1 -1
- package/.next/server/app/api/update-maintenance/route.js +59 -21
- package/.next/server/app/api/updates/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +4 -4
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/office/page_client-reference-manifest.js +1 -1
- package/.next/server/app/office-preview/page_client-reference-manifest.js +1 -1
- package/.next/server/app/office-preview.html +1 -1
- package/.next/server/app/office-preview.rsc +2 -2
- package/.next/server/app/office-preview.segments/_full.segment.rsc +2 -2
- package/.next/server/app/office-preview.segments/_head.segment.rsc +1 -1
- package/.next/server/app/office-preview.segments/_index.segment.rsc +2 -2
- package/.next/server/app/office-preview.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/office-preview.segments/office-preview/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/office-preview.segments/office-preview.segment.rsc +1 -1
- package/.next/server/app/office.html +1 -1
- package/.next/server/app/office.rsc +4 -4
- package/.next/server/app/office.segments/_full.segment.rsc +4 -4
- package/.next/server/app/office.segments/_head.segment.rsc +1 -1
- package/.next/server/app/office.segments/_index.segment.rsc +2 -2
- package/.next/server/app/office.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/office.segments/office/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/office.segments/office.segment.rsc +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover.html +1 -1
- package/.next/server/app/recover.rsc +2 -2
- package/.next/server/app/recover.segments/_full.segment.rsc +2 -2
- package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
- package/.next/server/app/recover.segments/_index.segment.rsc +2 -2
- package/.next/server/app/recover.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
- package/.next/server/app-paths-manifest.json +7 -7
- package/.next/server/chunks/1008.js +15 -0
- package/.next/server/chunks/5489.js +4 -4
- package/.next/server/chunks/5582.js +1 -1
- package/.next/server/chunks/6429.js +6 -6
- package/.next/server/chunks/7121.js +4 -1
- package/.next/server/chunks/7362.js +1 -1
- package/.next/server/instrumentation.js +1 -1
- package/.next/server/instrumentation.js.nft.json +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/6110-004be57c825af4d9.js +1 -0
- package/.next/static/chunks/{9575-08609cfc6c1cf3e1.js → 9575-9eb94dc1e8bcd130.js} +4 -4
- package/.next/static/chunks/app/{layout-76b01721b96d2c1a.js → layout-890e772919e033dc.js} +1 -1
- package/.next/trace +108 -108
- package/.next/trace-build +1 -1
- package/README.md +7 -3
- package/Tools/CUELO_Setup/files/native-runtime-patch.js +1 -1
- package/Tools/OMP_Global_Config/agent/extensions/command-guard/task-guard.ts +35 -14
- package/Tools/OMP_Global_Config/agent/extensions/lib/maker-routing.ts +31 -14
- package/Tools/OMP_Global_Config/agent/extensions/lib/prepared-task.ts +4 -3
- package/Tools/OMP_Global_Config/agent/rules/harness-policy.json +1 -1
- package/Tools/OMP_Global_Config/agent/rules/subagent.md +7 -2
- package/Tools/OMP_Global_Config/agent/rules/task-guard.md +1 -0
- package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +145 -159
- package/package.json +1 -1
- package/.next/static/chunks/6110-691c0f77641fc07e.js +0 -1
- /package/.next/static/{6tGLEaiCe3Wa8PkWKUhB4 → b4yL-hhdDIhd8tZ_DFHIS}/_buildManifest.js +0 -0
- /package/.next/static/{6tGLEaiCe3Wa8PkWKUhB4 → b4yL-hhdDIhd8tZ_DFHIS}/_ssgManifest.js +0 -0
package/.next/trace-build
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
[{"name":"run-webpack","duration":
|
|
1
|
+
[{"name":"run-webpack","duration":118902486,"timestamp":750983,"id":14,"parentId":1,"tags":{},"startTime":1791187467741,"traceId":"f2a993b40c58c07b"},{"name":"run-typescript","duration":21250862,"timestamp":120014802,"id":11060,"parentId":1,"tags":{},"startTime":1791187587007,"traceId":"f2a993b40c58c07b"},{"name":"static-check","duration":4116556,"timestamp":141361989,"id":11063,"parentId":1,"tags":{},"startTime":1791187608354,"traceId":"f2a993b40c58c07b"},{"name":"static-generation","duration":12489945,"timestamp":145590809,"id":11233,"parentId":1,"tags":{},"startTime":1791187612583,"traceId":"f2a993b40c58c07b"},{"name":"collect-build-traces","duration":22042462,"timestamp":145552634,"id":11230,"parentId":1,"tags":{},"startTime":1791187612545,"traceId":"f2a993b40c58c07b"},{"name":"telemetry-flush","duration":83,"timestamp":167599687,"id":11242,"parentId":1,"tags":{},"startTime":1791187634592,"traceId":"f2a993b40c58c07b"},{"name":"next-build","duration":166956596,"timestamp":643197,"id":1,"tags":{"buildMode":"default","version":"16.2.12","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1791187467633,"traceId":"f2a993b40c58c07b"}]
|
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ CUELO의 목적은 작업 중 확인한 실패와 막힘을 기억·작업 규
|
|
|
45
45
|
- 입력창에 이미지·문서 외에 음성(받아쓰기), 코드·설정 파일(본문 첨부), 그 밖의 파일(저장 후 경로 첨부)을 붙여넣기·드롭·선택으로 넣습니다. 저장형 첨부는 기본 파일당 100MB이며 설정에서 한도를 바꿀 수 있습니다. 큰 음성은 처리 계획과 계정 사용량 안내를 확인한 뒤 압축·분할 전사를 시작합니다. 음성 받아쓰기는 Google Antigravity 계정이 필요합니다. 대화·분기·초안에서 사용 중인 파일은 보존하고, 미사용이 확인된 첨부만 7일 유예 후 자동 정리합니다. [첨부와 자동 정리 안내](./docs/attachments.md)를 확인하세요.
|
|
46
46
|
- 에이전트가 생성하거나 확인한 이미지가 그 턴의 대화 흐름에 썸네일로 보이고, 누르면 크게 열립니다.
|
|
47
47
|
- Ctrl+K 팔레트로 대화 내용을 검색해 해당 메시지로 이동합니다. 압축으로 사라진 과거 메시지는 제외하고, 현재 볼 수 있는 메시지를 세션당 최대 3건 보여 줍니다. 긴 대화 안에서는 Ctrl+F로 찾고, 오른쪽 질문 레일로 내가 한 질문 사이를 오갑니다.
|
|
48
|
-
- 서버나 PC가
|
|
48
|
+
- 서버나 PC가 갑자기 꺼지면, 복구 대상인 진행 중 대화를 서버 재시작 뒤 자동으로 이어갑니다. 사용자 답변·승인을 기다리던 대화와 이전 버전의 기록은 자동으로 재개하지 않으며, 이어지지 않은 대화에는 중단 알림을 표시합니다. 컨텍스트 압축 중에는 경과 시간을 표시합니다.
|
|
49
49
|
- 모델 선택기 맨 위에 자주 고른 모델이 나오고, 팔레트에서 넓은 대화 폭을 켤 수 있습니다.
|
|
50
50
|
- 입력창 아래 줄은 Main 프리셋 → 그 제공자의 모델 → 추론 강도 순서입니다. "자동, 최대 high"처럼 상한을 고를 수 있고, Auto가 실제로 적용한 값은 「현재 추론 강도」로 따로 표시합니다. 실행 중에 지시를 덧붙이면 Auto가 다시 판단해 강도를 올릴 수 있고, 같은 답변 안에서는 내리지 않습니다. 새 대화는 선택한 상한 적용을 마친 뒤 첫 메시지를 보내며, 적용에 실패하면 재전송 때 다시 시도합니다.
|
|
51
51
|
- 도구 옆 시간에는 모델 응답 생성 시간을 포함하지 않습니다. 실행 전에 취소된 도구나 완료 시각을 확인할 수 없는 옛 기록에는 시간을 추정해 붙이지 않습니다.
|
|
@@ -69,12 +69,14 @@ CUELO의 목적은 작업 중 확인한 실패와 막힘을 기억·작업 규
|
|
|
69
69
|
- 개인 캐릭터 모델(선택): agent 데이터 디렉터리(기본 `~/.omp/agent`) 아래 `office-avatars/`에 `rin.vrm`·`mio.vrm`·`nova.vrm`·`yuki.vrm`·`isana.vrm`·`shion.vrm`·`hikari.vrm`을 두면 그 캐릭터를 VRM 모델로 그립니다. 파일 이름은 이 일곱 개만 쓰며, 서버는 이 고정 이름의 파일만 기존 접속 보호(호스트 확인·비밀번호 잠금)를 거친 `/api/office-avatars/<이름>`으로 내주고 다른 경로는 받지 않습니다. 모델은 저장소·npm 패키지·`public/`에 들어 있지 않습니다. 파일이 없는 캐릭터와 캐릭터를 특정하지 못한 참여자는 코드로 그린 캐릭터 그대로이고, 파일이 있는데 받거나 읽지 못하면 오류를 숨기지 않고 자리 카드와 「다시 시도」로 넘어갑니다. 파일을 넣거나 바꾼 뒤에는 오피스를 다시 열면 됩니다.
|
|
70
70
|
- 한 캐릭터는 작업이 몇 개든 사무실에 한 명만 섭니다. Main과 같은 캐릭터의 Maker, 같은 계정의 Maker 여럿은 한 몸에 모이고, 머리 위 말풍선에 작업마다 한 줄(이름·상태)이 보여 실패·대기 작업도 가려지지 않습니다. 그 캐릭터를 누르면 작업이 하나일 때는 바로 열리고, 여럿일 때는 말풍선의 첫 줄로 초점이 가서 줄마다 골라 엽니다. 몸의 자리와 동작은 일하는 작업 → 기다리는 작업 → 사람을 부르거나 실패한 작업 → 쉬거나 끝난 작업 순으로 고른 대표 작업을 따릅니다. 하단 참여자 줄과 자리 카드는 계속 작업마다 따로 보입니다. Maker의 캐릭터는 오피스를 열면 각 Maker 기록의 마지막 답에 남은 계정으로 바로 정해지고, 계정 근거가 없는 참여자는 다른 캐릭터에 합치지 않고 회색 「이름 없는 참여자」로 따로 섭니다.
|
|
71
71
|
- 자식 에이전트의 발화가 해당 턴 아래 대화 흐름에 끼어들어 누가 무슨 말을 했는지 이어서 읽힙니다.
|
|
72
|
-
- 공개 하네스는 시작을 기다리거나 재작업 중인 에이전트에게 같은 파일을 중복 배정하지 않도록 확인합니다. 완료 결과와 Main이 검수한 결과의 출처를 구분하고, 재작업 이력과 최종 수용을 따로 남깁니다. 적용 경계는 [하네스 안내](./docs/harness.md#발주와-검수-계약)를 따릅니다.
|
|
72
|
+
- 공개 하네스는 시작을 기다리거나 재작업 중인 에이전트에게 같은 파일을 중복 배정하지 않도록 확인합니다. 잘못된 작업 유형은 발주 준비 단계에서 알려주며, 에이전트 역할을 생략하면 메이커를 사용합니다. 완료 결과와 Main이 검수한 결과의 출처를 구분하고, 재작업 이력과 최종 수용을 따로 남깁니다. 적용 경계는 [하네스 안내](./docs/harness.md#발주와-검수-계약)를 따릅니다.
|
|
73
|
+
- 여러 작업을 한꺼번에 맡길 때 공통 준비 확인의 반복을 줄였습니다. 작업별 담당 경로와 충돌 확인은 유지하며, 다음 발주에서는 바뀐 조건을 다시 확인합니다.
|
|
74
|
+
- 완료된 위임 작업은 검수 판정과 기록을 함께 마감하고, 최종 답변 전에 판정 기록 누락을 확인합니다. 근거가 부족한 결과를 자동 합격으로 처리하지 않습니다.
|
|
73
75
|
- 메이커의 기본 추론 강도와 허용 범위는 별개입니다. 작업별 허용 범위 안에서도 선택한 모델이 실제로 지원하는 강도만 사용하며, 상한을 높여도 기본 강도를 자동으로 최대로 바꾸지 않습니다.
|
|
74
76
|
- 필요한 MCP 서버만 요청별로 연결하는 선택 모드를 지원합니다. 설치·인증·권한 변경은 먼저 승인을 받고 에이전트가 지원합니다. 설정과 적용 범위는 [요청별 MCP 선택 연결](./docs/harness.md#요청별-mcp-선택-연결)을 확인하세요.
|
|
75
77
|
- 사이드 챗(`/btw`): 본 대화를 끊지 않고 옆 패널에서 따로 묻습니다. 답변은 본 대화와 같은 캐릭터 얼굴·스티커·마크다운으로 보이고, 세션마다 초안이 따로 보관됩니다. 사이드 챗 문답은 본 대화에 남지 않으며, 답변의 「메인에 보내기」를 누를 때만 문답과 추가 지시가 본 대화로 전달됩니다(본 대화가 실행 중이면 steer, 아니면 새 요청).
|
|
76
78
|
- ChatGPT 상담(SHION): 로컬 shim이 전용 ChatGPT 탭에서 받은 답을 현재 omp 세션에 투영하고, 상담 기록과 실패 기록이 대화창에 표시됩니다.
|
|
77
|
-
- 실시간 음성: 브라우저 마이크로 Codex live 음성 세션을 엽니다. “끊어”,
|
|
79
|
+
- 실시간 음성: 브라우저 마이크로 Codex live 음성 세션을 엽니다. “끊어”, “알았어, 끊어봐”, “아니 통화 종료하라고”처럼 단독 종료 요청을 말하면 최종 전사를 받은 뒤 마이크와 통화를 종료합니다. 선택 기능인 캐릭터 음성은 **설정 → 통화 음성**에서 Cartesia API 키를 입력하고 음성 준비에 동의하면 현재 Main 캐릭터에 맞춰 적용됩니다. 통화 답변은 실제로 말한 캐릭터의 얼굴과 이름으로 표시되고, 캐릭터 음성 대신 기본 음성으로 말했으면 「기본 음성」이라고 함께 보입니다. 입력창에는 통화 시작·종료 버튼만 남으며, 좁은 화면에서는 모델 선택 등 다른 조작부와 겹치지 않게 줄바꿈합니다. 별도 음성 ID 입력은 필요하지 않으며, 키는 서버에만 보관합니다. 지원 플랜·크레딧 사용·전송 범위는 [캐릭터 통화 설정](./docs/live-voice.md)을 확인하세요.
|
|
78
80
|
|
|
79
81
|
**단톡방**
|
|
80
82
|
|
|
@@ -160,6 +162,8 @@ bun bin/cuelo.js --reset-password # 비밀번호 재설정
|
|
|
160
162
|
|
|
161
163
|
새 CUELO 릴리스가 나오면 사이드바 아래에 알림이 뜹니다. CUELO는 스스로 설치하지 않습니다. 소스에서 실행 중이면 서비스를 종료한 뒤 소스를 갱신하고 다시 설치·시작하세요. 실행 중인 개발 서버의 `.next/`에 빌드를 겹쳐 쓰지 마세요.
|
|
162
164
|
|
|
165
|
+
업데이트 대기 화면에서 실패가 확인되더라도 서버가 다시 요청을 받을 수 있고 원래 대화가 확인되면 그 대화로 자동 복귀합니다. 복귀한 화면은 진행 중인 답변이나 이미 끝난 실패 안내를 따라잡으며, 복귀 자체가 명령을 다시 보내지는 않습니다.
|
|
166
|
+
|
|
163
167
|
기존 프로필의 하네스·역할 설정은 앱 업데이트만으로 자동 덮어쓰지 않습니다. `0.6.1`의 설정 전환은 [기존 하네스 갱신](./docs/installation.md#061로-기존-하네스-갱신)을 확인하고, 반영 후 CUELO와 `omp`를 다시 시작하세요.
|
|
164
168
|
|
|
165
169
|
```bash
|
|
@@ -9,7 +9,7 @@ const MEMORY_RE = /(const phase1Model = await resolveMemoryModel\(\{[^}]*?fallba
|
|
|
9
9
|
const MEMORY_DONE = /const phase1Model = await resolveMemoryModel\(\{[^}]*?fallbackRole: "smol"/;
|
|
10
10
|
const EXPECTED = {
|
|
11
11
|
name: 'cuelo',
|
|
12
|
-
version: '0.9.
|
|
12
|
+
version: '0.9.5',
|
|
13
13
|
distribution: 'cuelo',
|
|
14
14
|
coreVersion: '18.6.1',
|
|
15
15
|
};
|
|
@@ -84,6 +84,8 @@ type SpawnItem = {
|
|
|
84
84
|
agent: string;
|
|
85
85
|
task: string;
|
|
86
86
|
name?: string;
|
|
87
|
+
/** 호출이 agent를 생략했다. 위임 정의가 maker 하나뿐이라 maker로 해석하고 반환 input에도 기록한다. */
|
|
88
|
+
agentOmitted?: true;
|
|
87
89
|
};
|
|
88
90
|
|
|
89
91
|
export type TaskCallInput = Record<string, unknown>;
|
|
@@ -155,7 +157,7 @@ function readField(block: string, name: string): string | undefined {
|
|
|
155
157
|
return value || undefined;
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
const WORK_CLASSES = ["feature", "maintenance", "diagnostic"] as const;
|
|
160
|
+
export const WORK_CLASSES = ["feature", "maintenance", "diagnostic"] as const;
|
|
159
161
|
const PURPOSES = ["primary", "rework"] as const;
|
|
160
162
|
|
|
161
163
|
/**
|
|
@@ -280,12 +282,13 @@ function normalizeSpawnItems(input: TaskCallInput): SpawnItem[] | string {
|
|
|
280
282
|
return "[SpawnGuard] tasks[] 항목 형식이 올바르지 않습니다.";
|
|
281
283
|
}
|
|
282
284
|
const item = rawItem as Record<string, unknown>;
|
|
283
|
-
if (typeof item.
|
|
284
|
-
return "[SpawnGuard] 모든 task 항목은 agent
|
|
285
|
+
if (typeof item.task !== "string" || (item.agent !== undefined && typeof item.agent !== "string")) {
|
|
286
|
+
return "[SpawnGuard] 모든 task 항목은 task 문자열을 가져야 하고 agent는 생략하거나 문자열이어야 합니다.";
|
|
285
287
|
}
|
|
286
288
|
items.push({
|
|
287
|
-
agent: item.agent.trim().toLowerCase(),
|
|
289
|
+
agent: item.agent === undefined ? "maker" : item.agent.trim().toLowerCase(),
|
|
288
290
|
task: item.task,
|
|
291
|
+
...(item.agent === undefined ? { agentOmitted: true as const } : {}),
|
|
289
292
|
...(typeof item.name === "string" && item.name.trim()
|
|
290
293
|
? { name: item.name.trim() }
|
|
291
294
|
: {}),
|
|
@@ -294,13 +297,14 @@ function normalizeSpawnItems(input: TaskCallInput): SpawnItem[] | string {
|
|
|
294
297
|
return items;
|
|
295
298
|
}
|
|
296
299
|
|
|
297
|
-
if (typeof input.
|
|
298
|
-
return "[SpawnGuard] task 호출은 agent
|
|
300
|
+
if (typeof input.task !== "string" || (input.agent !== undefined && typeof input.agent !== "string")) {
|
|
301
|
+
return "[SpawnGuard] task 호출은 task 문자열을 가져야 하고 agent는 생략하거나 문자열이어야 합니다.";
|
|
299
302
|
}
|
|
300
303
|
return [
|
|
301
304
|
{
|
|
302
|
-
agent: input.agent.trim().toLowerCase(),
|
|
305
|
+
agent: input.agent === undefined ? "maker" : input.agent.trim().toLowerCase(),
|
|
303
306
|
task: input.task,
|
|
307
|
+
...(input.agent === undefined ? { agentOmitted: true as const } : {}),
|
|
304
308
|
...(typeof input.name === "string" && input.name.trim()
|
|
305
309
|
? { name: input.name.trim() }
|
|
306
310
|
: {}),
|
|
@@ -412,6 +416,7 @@ export function reserveTaskCall(
|
|
|
412
416
|
const makers: MakerSpawn[] = [];
|
|
413
417
|
// 파생이 일어난 항목만 정규 블록으로 교체한다. 나머지 항목은 원본 문자열을 그대로 쓴다.
|
|
414
418
|
const rewritten = new Map<number, string>();
|
|
419
|
+
const agentFilled = new Set<number>();
|
|
415
420
|
|
|
416
421
|
for (const [index, item] of normalized.entries()) {
|
|
417
422
|
const raw = parseGuardMetadata(item.task);
|
|
@@ -446,6 +451,7 @@ export function reserveTaskCall(
|
|
|
446
451
|
raw.blocksPrimary === undefined ||
|
|
447
452
|
raw.primaryDeliverable === undefined;
|
|
448
453
|
if (derived) rewritten.set(index, canonicalizeTask(item.task, raw, metadata, item.agent));
|
|
454
|
+
if (item.agentOmitted) agentFilled.add(index);
|
|
449
455
|
}
|
|
450
456
|
|
|
451
457
|
const overBudget = budgetReason(state.usage, delta);
|
|
@@ -459,21 +465,36 @@ export function reserveTaskCall(
|
|
|
459
465
|
makers,
|
|
460
466
|
});
|
|
461
467
|
for (const entry of refundable) state.refundable.set(entry.name, entry.delta);
|
|
462
|
-
if (rewritten.size === 0) return { ok: true };
|
|
463
|
-
return { ok: true, input: replaceTaskBodies(input, rewritten) };
|
|
468
|
+
if (rewritten.size === 0 && agentFilled.size === 0) return { ok: true };
|
|
469
|
+
return { ok: true, input: replaceTaskBodies(input, rewritten, agentFilled) };
|
|
464
470
|
}
|
|
465
471
|
|
|
466
472
|
/**
|
|
467
|
-
* 원본 입력을 제자리에서 바꾸지 않고 교체
|
|
468
|
-
* context·model·isolated 같은 나머지 키는 그대로 보존한다.
|
|
473
|
+
* 원본 입력을 제자리에서 바꾸지 않고 교체 본문과 생략된 agent의 maker 기록만 담은 얕은 복사본을 만든다.
|
|
474
|
+
* tasks[]의 다른 항목과 context·model·isolated 같은 나머지 키는 그대로 보존한다.
|
|
469
475
|
*/
|
|
470
|
-
function replaceTaskBodies(
|
|
471
|
-
|
|
476
|
+
function replaceTaskBodies(
|
|
477
|
+
input: TaskCallInput,
|
|
478
|
+
rewritten: Map<number, string>,
|
|
479
|
+
agentFilled: Set<number>,
|
|
480
|
+
): TaskCallInput {
|
|
481
|
+
if (!Array.isArray(input.tasks)) {
|
|
482
|
+
return {
|
|
483
|
+
...input,
|
|
484
|
+
...(rewritten.has(0) ? { task: rewritten.get(0) } : {}),
|
|
485
|
+
...(agentFilled.has(0) ? { agent: "maker" } : {}),
|
|
486
|
+
};
|
|
487
|
+
}
|
|
472
488
|
return {
|
|
473
489
|
...input,
|
|
474
490
|
tasks: input.tasks.map((item, index) => {
|
|
475
491
|
const task = rewritten.get(index);
|
|
476
|
-
|
|
492
|
+
if (task === undefined && !agentFilled.has(index)) return item;
|
|
493
|
+
return {
|
|
494
|
+
...(item as Record<string, unknown>),
|
|
495
|
+
...(task === undefined ? {} : { task }),
|
|
496
|
+
...(agentFilled.has(index) ? { agent: "maker" } : {}),
|
|
497
|
+
};
|
|
477
498
|
}),
|
|
478
499
|
};
|
|
479
500
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import type { ExtensionAPI, ExtensionContext, ToolDefinition } from "@oh-my-pi/pi-coding-agent";
|
|
3
|
-
import { isPathOwned, parseOwnedPaths } from "../command-guard/task-guard";
|
|
3
|
+
import { isPathOwned, parseOwnedPaths, WORK_CLASSES } from "../command-guard/task-guard";
|
|
4
4
|
import { resolvePreparedTaskInput, storePreparedTaskBatch } from "./prepared-task";
|
|
5
5
|
import { assignmentsByName, summarizeHistory, type AttemptIdentity, type DispatchOwnership, type DispatchRecord, type RoutingHistory, type RoutingLedger } from "./routing-ledger";
|
|
6
6
|
|
|
@@ -257,7 +257,10 @@ function guardFields(task: string): ((name: string) => string) | null {
|
|
|
257
257
|
function dispatchContract(task: string, lock: DispatchContractLock | undefined): DispatchContract | null {
|
|
258
258
|
const field = guardFields(task);
|
|
259
259
|
if (!field) return null;
|
|
260
|
-
const
|
|
260
|
+
const explicitWorkClass = field("WORK_CLASS").toLowerCase();
|
|
261
|
+
// 준비 경계도 task hook과 같은 enum 정본을 쓴다. 명시된 값이 enum 밖이면 계약을 만들지 않는다.
|
|
262
|
+
if (explicitWorkClass && !(WORK_CLASSES as readonly string[]).includes(explicitWorkClass)) return null;
|
|
263
|
+
const workClass = explicitWorkClass || lock?.workClass;
|
|
261
264
|
const primaryDeliverable = field("PRIMARY_DELIVERABLE") || lock?.primaryDeliverable;
|
|
262
265
|
const ownedPaths = parseOwnedPaths(field("OWNED_PATHS"));
|
|
263
266
|
if (!workClass || !primaryDeliverable || ownedPaths.length === 0) return null;
|
|
@@ -270,7 +273,7 @@ function dispatchContract(task: string, lock: DispatchContractLock | undefined):
|
|
|
270
273
|
}
|
|
271
274
|
|
|
272
275
|
const GUARD_TEMPLATE =
|
|
273
|
-
|
|
276
|
+
`양식: 브리프 첫머리에 \`TASK_GUARD:\` 줄, 그 다음 줄부터 빈 줄 없이 \`WORK_CLASS: ${WORK_CLASSES.join("|")}\`(요청의 첫 child만 필수)·\`PRIMARY_DELIVERABLE: <완료물 한 줄>\`(첫 child만 필수)·\`OWNED_PATHS: <cwd 상대경로, 콤마 구분>\`, 그 뒤 \`TASK_TITLE\`·\`TODO_TASKS\`. 정본 rule://task-guard.`;
|
|
274
277
|
|
|
275
278
|
/** 계약을 완성하지 못한 이유를 필드 단위로 짚는다. 브리프 본문은 싣지 않는다. */
|
|
276
279
|
export function contractGapReason(task: string, lock: DispatchContractLock | undefined, name: string): string {
|
|
@@ -278,6 +281,10 @@ export function contractGapReason(task: string, lock: DispatchContractLock | und
|
|
|
278
281
|
const field = guardFields(task);
|
|
279
282
|
if (!field) return `task ${label}: \`TASK_GUARD:\` 블록이 없습니다. ${GUARD_TEMPLATE}`;
|
|
280
283
|
const missing: string[] = [];
|
|
284
|
+
const explicitWorkClass = field("WORK_CLASS").toLowerCase();
|
|
285
|
+
if (explicitWorkClass && !(WORK_CLASSES as readonly string[]).includes(explicitWorkClass)) {
|
|
286
|
+
return `task ${label}: WORK_CLASS는 ${WORK_CLASSES.join("|")} 중 하나여야 합니다(받은 값 '${explicitWorkClass.slice(0, 40)}'). ${GUARD_TEMPLATE}`;
|
|
287
|
+
}
|
|
281
288
|
if (!field("WORK_CLASS") && !lock?.workClass) missing.push("WORK_CLASS(아직 lock이 없어 이 child에 필요)");
|
|
282
289
|
if (!field("PRIMARY_DELIVERABLE") && !lock?.primaryDeliverable) missing.push("PRIMARY_DELIVERABLE(아직 lock이 없어 이 child에 필요)");
|
|
283
290
|
if (parseOwnedPaths(field("OWNED_PATHS")).length === 0) missing.push("OWNED_PATHS(모든 child 필수)");
|
|
@@ -693,7 +700,7 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
|
|
|
693
700
|
}
|
|
694
701
|
};
|
|
695
702
|
|
|
696
|
-
const INSTRUCTION = "Main은 profile·recommendations.uiUxBoundary·placement·history를 보고 후보와 concrete effort(model selector suffix, 그 후보의 efforts 안)를 지정합니다. 등급 NORMAL/HARD와 모델 이름을 구분합니다. 한도 미관측은 소진이 아닙니다. 기존 NORMAL의 명시적 Opus 선택은 ROUTING_REASON으로 유지하고, 같은 Opus owner와 완료된 비-UI 작업은 재사용합니다. 계정 쿨다운·리셋은 유지합니다. 추천 변경·Jev 불가·기존 owner 대신 새 발주는 ROUTING_REASON 한 줄을 남깁니다. 발주는 context
|
|
703
|
+
const INSTRUCTION = "Main은 profile·recommendations.uiUxBoundary·placement·history를 보고 후보와 concrete effort(model selector suffix, 그 후보의 efforts 안)를 지정합니다. 등급 NORMAL/HARD와 모델 이름을 구분합니다. 한도 미관측은 소진이 아닙니다. 기존 NORMAL의 명시적 Opus 선택은 ROUTING_REASON으로 유지하고, 같은 Opus owner와 완료된 비-UI 작업은 재사용합니다. 계정 쿨다운·리셋은 유지합니다. 추천 변경·Jev 불가·기존 owner 대신 새 발주는 ROUTING_REASON 한 줄을 남깁니다. 발주는 원문을 재사용하는 참조로 보냅니다: task({context:'PREPARED_CONTEXT', tasks:[{name:'<준비한 name>', task:'PREPARED_TASK: <preparedId>', model:'<후보 selector>:<concrete effort>'}]}). name은 생략하면 준비한 이름으로 복원되고 agent는 생략합니다(생략은 maker, 다른 agent는 거절). model은 추천에서 자동으로 채워지지 않으므로 Main이 후보와 그 efforts 안의 concrete effort로 반드시 명시합니다.";
|
|
697
704
|
/** 배치 공통 정보(candidates·quota·instruction)는 한 번만, task별 route는 배열로 돌려준다. */
|
|
698
705
|
async function prepareBatch(context: string, tasks: RouteTask[], ctx: ExtensionContext, signal?: AbortSignal, callId = "") {
|
|
699
706
|
const current = policy();
|
|
@@ -942,8 +949,8 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
|
|
|
942
949
|
}
|
|
943
950
|
const tasks = (Array.isArray(canonicalInput.tasks) ? canonicalInput.tasks : [canonicalInput]) as Record<string, unknown>[];
|
|
944
951
|
const atGeneration = generation;
|
|
945
|
-
//
|
|
946
|
-
// online provider 시도만
|
|
952
|
+
// 현재 후보는 배치 전체의 준비 참조를 모은 뒤 한 번 해석한다.
|
|
953
|
+
// 준비에서 시작한 online provider 시도만 공유하며, 다음 발주는 새로 해석한다.
|
|
947
954
|
const refreshAttempts: RefreshAttempts = new Map();
|
|
948
955
|
let candidateLock = contractLock;
|
|
949
956
|
const locksBefore: (DispatchContractLock | undefined)[] = [];
|
|
@@ -956,6 +963,7 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
|
|
|
956
963
|
return contract;
|
|
957
964
|
});
|
|
958
965
|
const drafts: [number, DispatchDraft][] = [];
|
|
966
|
+
const ready: { itemIndex: number; item: Record<string, unknown>; contract: DispatchContract; key: string; prepared: Prepared }[] = [];
|
|
959
967
|
for (const [itemIndex, item] of tasks.entries()) {
|
|
960
968
|
const requested = typeof item.model === "string" ? item.model : "";
|
|
961
969
|
const task = typeof item.task === "string" ? item.task : "";
|
|
@@ -980,16 +988,25 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
|
|
|
980
988
|
if (latestPublications.get(key)?.token !== prepared.publicationToken) {
|
|
981
989
|
return { block: true, reason: "같은 task 계약에 더 최신 maker_route 준비가 있습니다. 최신 판단이 끝난 뒤 그 결과로 발주하세요." };
|
|
982
990
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
991
|
+
ready.push({ itemIndex, item, contract, key, prepared });
|
|
992
|
+
}
|
|
993
|
+
const current = policy();
|
|
994
|
+
const candidates = ready.length > 0 ? await candidateList(ctx, current, refreshAttempts) : [];
|
|
995
|
+
if (atGeneration !== generation) {
|
|
996
|
+
return { block: true, reason: "session이 변경되어 이전 라우팅 판단은 발주에 사용할 수 없습니다." };
|
|
997
|
+
}
|
|
998
|
+
const revision = revisionOf(current, candidates);
|
|
999
|
+
const allOwners = ready.length > 0 ? deps.owners(ctx) : [];
|
|
1000
|
+
// 마지막 await 이후 모든 항목을 같은 snapshot으로 검사하고 예약한다.
|
|
1001
|
+
// 앞 항목을 검사한 뒤 뒤 항목의 후보 조회를 기다리는 stale publication 틈도 없앤다.
|
|
1002
|
+
for (const { itemIndex, item, contract, key, prepared } of ready) {
|
|
1003
|
+
const name = String(item.name ?? "").trim();
|
|
1004
|
+
const task = typeof item.task === "string" ? item.task : "";
|
|
1005
|
+
const requested = typeof item.model === "string" ? item.model : "";
|
|
988
1006
|
if (latestPublications.get(key)?.token !== prepared.publicationToken) {
|
|
989
1007
|
return { block: true, reason: "candidate 확인 중 같은 task 계약에 더 최신 maker_route 준비가 생겼습니다. 최신 판단으로 발주하세요." };
|
|
990
1008
|
}
|
|
991
|
-
if (prepared.revision !==
|
|
992
|
-
const allOwners = deps.owners(ctx);
|
|
1009
|
+
if (prepared.revision !== revision) return { block: true, reason: "후보 또는 판단 기준이 바뀌었습니다. maker_route로 변경된 조건만 다시 판단하세요." };
|
|
993
1010
|
const owners = relevantOwners(contract, allOwners);
|
|
994
1011
|
if (ownerRevision(prepared.owners) !== ownerRevision(owners)) return { block: true, reason: "현재 소유권 충돌 조건이 바뀌었습니다. maker_route로 owner placement만 다시 판단하세요." };
|
|
995
1012
|
// 같은 모델이 여러 profile에 있을 수 있다(HARD_*). 추천 profile, 이어서 추천 등급의
|
|
@@ -1147,7 +1164,7 @@ export function registerMakerRouting(pi: ExtensionAPI, deps: RoutingDeps) {
|
|
|
1147
1164
|
const z = pi.zod;
|
|
1148
1165
|
const strings = () => z.array(z.string());
|
|
1149
1166
|
const nullable = () => strings().nullable();
|
|
1150
|
-
const parameters = z.object({ context: z.string(), tasks: z.array(z.object({ name: z.string(), task: z.string().describe(
|
|
1167
|
+
const parameters = z.object({ context: z.string(), tasks: z.array(z.object({ name: z.string(), task: z.string().describe(`실제로 발주할 브리프 전문(요약·제목 아님). 첫 줄 \`TASK_GUARD:\` 다음 줄부터 빈 줄 없이 WORK_CLASS: ${WORK_CLASSES.join("|")}(이 셋만 허용, 요청의 첫 child 필수)·PRIMARY_DELIVERABLE(요청의 첫 child)·OWNED_PATHS, 그 뒤 TASK_TITLE·TODO_TASKS, 이어서 # Target·# Change·# Acceptance. 실제 task 호출은 agent를 생략하면 maker이고 다른 agent는 거절된다. 양식 정본 rule://task-guard.`), assessment: z.object({ goal: z.string(), acceptance: strings(), facts: strings(), hypotheses: nullable(), unknowns: nullable(), paths: strings(), callBoundaries: nullable(), settledImplementation: nullable(), reusedPatterns: nullable(), remainingJudgments: nullable(), invariants: strings(), checks: strings(), failureEvidence: nullable() }) })) });
|
|
1151
1168
|
// SDK 18.2.6 TSchema의 unknown generic 불변성만 연결한다. 실제 Zod schema 검증은 그대로다.
|
|
1152
1169
|
const toolParameters = parameters as unknown as ToolDefinition["parameters"];
|
|
1153
1170
|
pi.registerTool({
|
|
@@ -189,13 +189,14 @@ export function resolvePreparedTaskInput(
|
|
|
189
189
|
const restoredItems = items.map((item, index) => {
|
|
190
190
|
const source = item as Record<string, unknown>;
|
|
191
191
|
const record = resolved[index]!;
|
|
192
|
-
|
|
192
|
+
// 참조에 이미 session·batch·ref로 묶인 name이 있으므로 생략만 그 name으로 복원한다. 명시한 값은 일치해야 한다.
|
|
193
|
+
if (source.name !== undefined && source.name !== record.name) {
|
|
193
194
|
throw new Error(
|
|
194
195
|
`prepared task name이 일치하지 않습니다: prepared='${clipped(record.name, 40)}' 발주='${clipped(typeof source.name === "string" ? source.name : String(source.name), 40)}'. ` +
|
|
195
|
-
"참조 발주는 준비한 이름을 그대로 써야 합니다.",
|
|
196
|
+
"참조 발주는 name을 생략하거나 준비한 이름을 그대로 써야 합니다.",
|
|
196
197
|
);
|
|
197
198
|
}
|
|
198
|
-
return { ...source, task: record.task };
|
|
199
|
+
return { ...source, name: record.name, task: record.task };
|
|
199
200
|
});
|
|
200
201
|
if (Array.isArray(input.tasks)) {
|
|
201
202
|
return { ...input, context, tasks: restoredItems };
|
|
@@ -811,7 +811,7 @@
|
|
|
811
811
|
"decidedAt": "spawn-time-per-task-item",
|
|
812
812
|
"preparedReference": {
|
|
813
813
|
"produce": "maker_route-receives-the-context-and-every-full-task-with-its-assessment-once-and-returns-one-session-local-preparedId-per-task",
|
|
814
|
-
"consume": "the-real-task-call-may-send-context-PREPARED_CONTEXT-and-each-task-string-PREPARED_TASK-space-preparedId-with-
|
|
814
|
+
"consume": "the-real-task-call-may-send-context-PREPARED_CONTEXT-and-each-task-string-PREPARED_TASK-space-preparedId-with-model-with-concrete-effort-chosen-by-Main-and-name-and-agent-omitted-where-an-omitted-name-is-restored-from-the-prepared-record-and-an-omitted-agent-is-read-as-maker-while-an-explicit-mismatched-name-or-other-agent-is-rejected",
|
|
815
815
|
"restore": "the-task-hook-restores-the-original-context-guard-task-title-and-TODO-text-before-the-existing-checks-run",
|
|
816
816
|
"linkage": "the-binding-stays-the-session-local-task-name-and-the-task-guard-semantic-fields-the-preparedId-only-carries-the-original-text",
|
|
817
817
|
"errors": [
|
|
@@ -81,8 +81,8 @@ description: SubAgent 위임 판단, 병렬 실행, 검수 계약과 Git·통신
|
|
|
81
81
|
`OWNED_PATHS`·`FINDING_ID`)로 연결하고 정본 기준·후보·owner revision을 확인한다 — 산문·`context`
|
|
82
82
|
표현 차이는 허용되고 이름이나 의미 필드가 바뀌면 다시 준비한다. 다시 판정하거나 자율 선택하지 않는다.
|
|
83
83
|
실제 발주는 `maker_route`가 돌려준 session-local `preparedId`로 원문을 재사용할 수 있다 —
|
|
84
|
-
`context='PREPARED_CONTEXT'`와 각 task 문자열 `'PREPARED_TASK: <preparedId>'`에
|
|
85
|
-
`name`·`agent
|
|
84
|
+
`context='PREPARED_CONTEXT'`와 각 task 문자열 `'PREPARED_TASK: <preparedId>'`에 `model:<concrete-effort>`는 Main이 반드시 명시하고
|
|
85
|
+
`name`·`agent`는 생략할 수 있다(생략한 name은 준비한 이름으로 복원되고 명시하면 일치해야 하며, 생략한 agent는 maker이고 명시한 다른 agent는 거절) — hook이 원문 context·guard·task·title·TODO를
|
|
86
86
|
기존 guard 앞에서 복원한다. full/ref 혼합, 문법 변형, 다른 session·batch·name, 해제된 참조는
|
|
87
87
|
오류이고 full brief 경로는 그대로 쓸 수 있다. 연결 키는 여전히 session-local task 이름과
|
|
88
88
|
`TASK_GUARD` 의미 필드이고 `preparedId`는 원문 운반일 뿐이다. 사실·계약·policy·후보가 그대로면
|
|
@@ -471,6 +471,11 @@ Task Guard lock·budget·소유권·`FINDING_ID`, exit status, 파일·권한·
|
|
|
471
471
|
수용 조건을 확인한 뒤, `rework`는 근거 있는 재작업 판정에 사용하며 두 판정 모두 검수한 revision·
|
|
472
472
|
evidence locator·이유를 남긴다. `held`는 같은 identity와 보류 이유를 남기고 후속 입력 뒤에도
|
|
473
473
|
명시적으로 다시 판정할 수 있다. 저장 실패는 미기록으로 보고하며 발주 자체를 차단하지 않는다.
|
|
474
|
+
최종 판정을 내린 Main은 **같은 단계에서 `routing_verdict`를 호출하고 저장 성공을 확인한다.**
|
|
475
|
+
산출물 사용·다음 실험 실행·배포·TODO 완료는 판정 기록을 대신하지 않는다. 최종 답변 전에는
|
|
476
|
+
이번 요청의 실제 spawn triple별 완료 attempt와 판정 기록을 대조해 누락을 정산한다.
|
|
477
|
+
이미 검수한 근거는 재사용하고, 판정할 근거가 부족하면 구체적인 이유로 `held`를 남긴다.
|
|
478
|
+
표시를 채우려고 자동 `accepted` 처리하거나 과거 원 revision을 만들어 넣지 않는다.
|
|
474
479
|
`revision`은 Main이 실제로 검수한 revision이다. 원장은 그 attempt의 terminal report revision
|
|
475
480
|
(`sourceRevision`, 미관측이면 `null`)과의 관계를 `same`·`integrated`·`mismatch`·`unknown`으로 함께
|
|
476
481
|
남긴다. 통합 뒤 새 revision을 직접 검수했으면 `integratedFrom`에 그 attempt의 원 revision을 적어 연결을
|
|
@@ -27,6 +27,7 @@ TODO_TASKS: ["<현재 Main TODO의 정확한 문자열>", "…"]
|
|
|
27
27
|
- `TODO_TASKS`는 **현재 Main TODO 항목의 exact 문자열** JSON 배열이다. 비어 있지 않은 고유 문자열이어야 하고 trim·유사도 정규화 없이 그대로 비교되므로 Main이 실제로 가진 문자열을 그대로 쓴다. 두 메타 중 하나라도 있으면 런타임이 TODO 진행 관측을 시작한다.
|
|
28
28
|
- `TASK_GUARD` **안에서** Main이 직접 판단해야 하는 값만 적는다. **무엇을 만드는가**(`WORK_CLASS`·`PRIMARY_DELIVERABLE`)와 **어디까지 고치는가**(`OWNED_PATHS`)가 그것이고, 나머지는 런타임이 승인된 값에서 조립한다. 한 요청의 첫 child가 앞의 둘로 lock을 세우면 그 다음 child부터는 `OWNED_PATHS`(와 rework면 `FINDING_ID`)만 적어도 된다. 생략한 `WORK_CLASS`·`PRIMARY_DELIVERABLE`은 lock 값으로 채워지고, `PURPOSE`는 `FINDING_ID` 유무에서(`FINDING_ID` 있으면 `rework`, 그 밖엔 `primary`) 파생되며, `BLOCKS_PRIMARY`는 `yes`로 채워진다. 조립이 일어난 항목은 런타임이 그 브리프의 `TASK_GUARD` 블록을 실제 적용된 값으로 다시 써서 실행하므로 child와 기록이 같은 계약을 본다.
|
|
29
29
|
- 비어 있는 것과 잘못 적은 것은 다르다. 생략은 조립되지만 enum 밖의 값(`WORK_CLASS: feat` 같은)은 거부된다. 허용 child는 `maker`뿐이다. lock과 다른 `WORK_CLASS`·`PRIMARY_DELIVERABLE`, `BLOCKS_PRIMARY:no`, `OWNED_PATHS` 누락, `FINDING_ID` 없는 rework도 거부된다.
|
|
30
|
+
- `agent`는 생략하면 `maker`로 읽히고 반환 input에도 `maker`로 기록된다. 명시한 다른 값(`task`·`scout` 등)은 거부된다. `maker_route` 준비도 같은 `WORK_CLASS` enum으로 명시 오값을 거절해 준비 성공처럼 보이지 않는다.
|
|
30
31
|
- `feature`의 `PRIMARY_DELIVERABLE`은 테스트 개수나 내부 정리가 아니라 사용자가 실제로 쓸 수 있게 되는 동작·화면으로 적는다.
|
|
31
32
|
- child는 현재 `PRIMARY_DELIVERABLE`을 직접 진전시키거나 완료를 막는 일에만 쓴다. 아니면 `BLOCKS_PRIMARY:no`로 기록하고 backlog로 넘기며 spawn하지 않는다.
|
|
32
33
|
- 첫 child의 `WORK_CLASS`와 `PRIMARY_DELIVERABLE`은 그 사용자 요청 동안 고정한다. 관측·오탐·증거 복구를 이유로 다른 완료물로 바꾸지 않는다. 사용자가 실행 중인 턴에 방향을 바꾸면 runtime이 그 redirect를 보고 lock만 해제하므로 새 완료물로 다시 고정해 발주한다. 누적 budget은 그대로 유지된다.
|