@buzzni/saycode-cli 0.9.9 → 0.9.11

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/README.md CHANGED
@@ -211,10 +211,20 @@ saycode를 따로 설치하지 않은 작업PC는 `npx @buzzni/saycode-cli searc
211
211
  마무리합니다(specs/codexcli-approval-session-binding R12). 값이 없거나 모르는 값이면 아무것도 싣지 않고,
212
212
  자식은 지금처럼 사람의 답을 기다립니다.
213
213
 
214
+ `agent prompt`의 결과는 `resultVersion:2`입니다. `ok:true`는 서버 저장 행을 다시 조회·복호화하여 본문과 설정까지 확인했다는 뜻이며 `storage.seq`가 확인한 위치입니다. `delivered:true`는 그 서버 메시지 ID의 **런타임 소비 증거**가 있어야 나옵니다. 구버전의 `delivered`는 같은 뜻으로 읽으면 안 됩니다. 큐 수락(receipt)만으로는 소비나 답변 성공이 아닙니다.
215
+
216
+ `--wait` 없이도 저장 뒤 runtime의 수락·거부 receipt를 최대 5초 기다려 거부를 바로 알려 주며, receipt가 없으면 `outcome:unconfirmed`로 돌아옵니다. `--wait`는 저장 이후 같은 입력 ID의 증인으로 기다립니다(최대 600초, 저장·검사 포함 620초). `response.attribution=bound`일 때만 `response.answer`가 이번 질문의 단독 답변이며, 공동 배치·steer·질문/자유문 권한 답변이 끼면 `consumed-with`와 입력 목록/사유를 돌려줍니다. receipt timeout은 `outcome:unconfirmed`이고 실패로 확정하지 않습니다. 소비는 확인됐지만 마감까지 작업이 끝나지 않으면 같은 증거를 담은 `wait_timeout`(exit 3)이고, 소비된 턴이 `--until` 조건 없이 끝나면(예: `consumed-with`) 마감을 기다리지 않고 `outcome:unconfirmed`로 바로 돌려줍니다. `next.command` 또는 `happy agent read <id> --since <storage.seq>`로 늦은 receipt·소비를 정산하세요.
217
+
218
+ 재실행에 대비하려면 **송신 전** `--local-id <id>`와 대상·본문·설정을 보관합니다(1–128 ASCII 영숫자 및 `._:-`). 같은 ID/동일 intent로 저장 응답 유실을 한 번만 재시도하며, 다른 본문이나 설정이면 `localid_conflict`입니다. CLI가 죽은 뒤에는 보관한 같은 ID로 다시 실행해 원래 저장 행을 확인합니다. 그사이 대상 runtime이 재시작되면 확인 조건이 달라져 `localid_conflict`가 되지만, 본문이 같으면(`storage.existing.sameBody:true`) `storage.existing`과 `next.command`(`happy agent read <id> --since <seq>`)로 원래 요청의 소비 여부를 확인할 수 있습니다. 본문이 다르면 그 행은 다른 질문이므로 `next.command`를 주지 않습니다. ID를 보관하지 않은 송신은 본문·시각으로 찾지 않으므로 결과를 알 수 없습니다. 복호화/조회 실패는 `storage-verification-unknown`입니다. 미확인 요청을 새 ID로 자동 재전송하지 않습니다.
219
+
220
+ 입력 증거는 지원 Claude/Codex 세션의 명시적 opt-in(`HAPPY_INPUT_EVIDENCE=1`, 기본 OFF)과 살아 있는 동일 runtime/remote epoch 확인이 필요합니다. 그 확인이 안 되는 세션(ON marker 없는 .358, 미지원·OFF 세션)은 `CLI_GUARDED_PROMPT_ROLLOUT`(기본 off) 동안 이전처럼 보내되 결과는 `resultVersion:2`, `delivered:false`, `delivery.state:"unsupported"`로 소비를 주장하지 않습니다. 이 플래그를 켜면 같은 세션은 `prompt_unsupported`로 보내기 전에 닫힙니다. Desktop 자동 opt-in은 T2r pin 전까지 꺼져 있습니다. A6 생성 안내는 T4rt와 함께 수락합니다. 허브 자동 알림은 `HUB_BRIDGE_OWNERSHIP_ROLLOUT`(기본 off) 동안 이전과 같고, 켜면 main이 개인은 같은 계정, 회사는 confirmed owner만 수신하도록 확인하며 확인 실패 시 배지에서 복구를 안내합니다. 사람의 직접 입력은 유지됩니다.
221
+
214
222
  `steer` 성공의 `accepted:true`는 세션 RPC가 요청을 받았다는 뜻이며 실제 transcript 전달 완료를
215
223
  뜻하지 않습니다(`delivered:false`). 끝난 턴의 후속 작업에는 `prompt`를 사용합니다. 에이전트 세션은
216
224
  자기 자신에게 입력할 수 없고, `steer`와 `stop`은 depth와 관계없이 원장에 기록된 자기 후손에게만 보낼 수
217
- 있습니다. 후손이 아닌 세션에 `prompt`하려면 소유가 검증돼야 하며, `not_owned`를 받으면 `read`로만 보고
225
+ 있습니다. 예외로 사람이 시작한 Claude·Codex 허브(broker 경로, depth 0)는 소유가 검증된 형제 세션(`ls`의
226
+ `ownership.relation:"own-sibling"`)에도 보낼 수 있으며, 이때 `stop`은 프로세스를 끄지 않고 지금 응답만 끊습니다
227
+ (`mode:"turn-abort"`, 터미널에서 쓰는 Claude는 stop을 `controlled-by-user`, steer를 `local-mode`로 거부). 회사 계정 PC에서는 owner relay가 확인한 형제(`ownership.proof:"owner-relay"`)에게 같은 규칙으로 relay를 통해서만 보냅니다(steer=`steer-own-session`, stop=`abort-own-turn`, 기본 꺼짐). 응답 중인지는 상대 Desktop이 보내기 직전에 확인하며, 명령이 읽은 runtime이 바뀌었으면 보내지 않습니다(`stop_rejected`, `runtime-changed`). Claude 대상이 터미널 사용 중인지 확인할 수 없으면 보내지 않습니다(`local-mode-unknown`). relay가 확인하지 못한 결과는 `owner-contact-unavailable`이며 다시 보내거나 credential 경로로 바꾸지 않습니다. 형제 결과에는 대상 `title`과 `ownership.relation`이 붙습니다. 형제에게 보내기 전에 대상 제목과 전체 ID를 확인합니다. 후손이 아닌 세션에 `prompt`하려면 소유가 검증돼야 하며, `not_owned`를 받으면 `read`로만 보고
218
228
  다시 시도하지 않습니다(ADR-078). `stop`은 보관·삭제를 하지 않습니다. spawn 한도(`whoami`의 `maxSpawn`)는 세션이
219
229
  도는 머신의 코어·메모리에서 산정하므로 머신마다 다르고, 최근 6시간 안의 spawn만 세는 롤링 윈도우라
220
230
  소진돼도 시간이 지나면 회복됩니다(`budgetResetsAt`, ADR-063). 데몬이 확인하지 못한 시도도 같은
@@ -510,3 +520,72 @@ node ../../scripts/verify-cli-tarball.mjs out/cli/buzzni-saycode-cli-*.tgz
510
520
 
511
521
  번들에 포함된 서드파티 오픈소스는 각자의 라이선스를 유지하며, 목록과 전문은 패키지에
512
522
  동봉된 `THIRD-PARTY-NOTICES.md`에 있습니다 (빌드 시 실제 번들 내용에서 자동 생성).
523
+
524
+ T2 응답은 소비한 native turn과 runtime에 결속합니다. `prompt --wait`의 질문·구조화 출력도 그 턴만 판정하며 `structuredOutput`과 `structured_output_missing`을 v2에 유지합니다. 명시적 runtime 거부는 `prompt_rejected`, exit 3이며 localId·storage·거부 증거를 보존합니다. 백그라운드 후속 관측은 `response.followUps`에 따로 남기고 최초 답변을 덮지 않습니다.
525
+
526
+ 송신 당시 검증된 runtime/epoch는 암호화 요청에 보존하므로 host 재시작 뒤에도 `read --since`로 과거 소비 사실을 조회할 수 있습니다. 이 기록은 새 runtime에 송신 권한을 주지 않습니다. 검증 기록이 없는 구 요청은 현재 runtime이 같고 ON이어도 unknown으로 남습니다. 기록을 소급 생성하거나 과거 소비를 확인됐다고 표시하지 않습니다.
527
+
528
+ Codex 명령/패치 승인은 tool 시작 전에 대기할 수 있지만 .359의 REST pending 상태에는 approval↔turn 결속이 없습니다. `prompt --wait --until waiting-input`은 이 승인을 현재 턴으로 추측하지 않습니다. 해당 대기가 확인되지 않은 채 timeout되면 `outcome:unconfirmed`, `waitingInput:{state:"unsupported",reason:"pending-approval-turn-binding-unavailable"}`를 반환합니다. 이는 pending 승인이 요청 B에 속한다는 뜻이 아닙니다. Claude 질문·Codex 질문은 실제 tool-call ID/턴 결속이 있을 때 판정하며, 채널 routing requestId로 대체하지 않습니다. 미확인 결과를 새 메시지로 자동 재시도하지 마세요.
529
+
530
+ ### Owner relay 개발 경로 (T1c-b2 D2, 기본 off)
531
+
532
+ 호환 host가 제공하는 typed broker IPC 또는 `SAYCODE_OWNER_CONTACT_SOCKET`이 있을 때 새 own lookup/read/prompt를 main으로 보냅니다.
533
+ 기존 후손 권한은 별도 경로로 유지하며 포트가 없으면 기존 동작을 유지합니다. broker agent의 형제 확장은 depth 0만 가능하고,
534
+ 직접 경로와 chat은 host/main의 별도 권한 검사를 받습니다. 소켓 주소는 source/owner 권한 증거가 아닙니다.
535
+
536
+ `agent read <id>`의 새 own 경로는 cursor·snapshot·truncated를 반환합니다. `agent prompt <id> <text> --local-id <id>`는
537
+ read의 live guard 관측을 사용하고 main이 저장 직전에 다시 검증합니다. guard가 없으면 저장하지 않고 기존 경로와 같이 `prompt_unsupported`(`reason:guard-unavailable`, `accepted:false`)입니다.
538
+ `storage-verification-unknown`은 저장이 시도됐을 수 있을 때만 씁니다. relay read 실패나 저장 전 거부는 `owner-contact-unavailable`이며 재조정할 저장이 없고, 직접 주지 않은 localId는 결과에 넣지 않습니다.
539
+ `localid_conflict`·`storage-verification-unknown`이면 같은 localId로 원래 저장을 확인하며 새 ID 자동 재송신을 하지 않습니다.
540
+ 저장 receipt는 runtime 소비 증거가 아닙니다.
541
+
542
+ agent 환경의 `chat -s <id>`는 host socket이 있으면 credential을 읽기 전에 typed adapter로 진입합니다.
543
+ `/detach`·`/exit`로 나갈 수 있고, 첨부·voice는 아직 unavailable입니다. resume/permission/abort는 아래 D3 절을 따릅니다.
544
+ broker 환경에서도 chat은 host의 chat 전용 socket을 사용합니다. depth≥1 broker의 chat lookup 거부는
545
+ `owner-contact-unavailable`로 표시하며 credential 경로로 재시도하지 않습니다. 명시적 localId 재시도에서는 원래 guard를 유지합니다.
546
+ H1/H2 보완 소비는 연결했으며 운영 활성화·공식 artifact 및 실제 host/CLI 소켓 왕복 수락은 별도입니다.
547
+
548
+
549
+ D2 relay의 prompt 거부는 기존 CLI와 같이 `prompt_rejected`, 소비 후 대기 만료는 `wait_timeout`(exit 3)입니다.
550
+ 관측 공백이 있는 응답은 소비 사실을 보존하되 단독 답변·완료를 확정하지 않습니다. 단순 페이지 연속 조회는 공백으로 표시하지 않습니다.
551
+ main은 같은 localId의 저장 행을 먼저 확인합니다. 같은 본문·guard이면 기존 receipt를 돌려주고, 다르면 `localid_conflict`입니다.
552
+ CLI가 read로 기존 행을 확인했을 때만 `storage.existing`을 반환하며, 본문이 같을 때만 `next`로 원래 저장의 read를 안내합니다.
553
+ 조회 불가·행 미발견이면 existing을 추정하지 않습니다. ACK 유실은 우선 저장 조회로 복구하고, 저장이 없다고 확인한 같은 intent는 최대 1회 재시도합니다.
554
+ 대상이 비활성이 되어 live guard가 없어도 같은 `--local-id` 재실행은 저장 행의 원래 guard로 기존 receipt를 확인합니다. 새 ID 송신은 여전히 live guard가 필요하고, 저장 여부를 증명할 수 없으면 `storage-verification-unknown`입니다.
555
+ 저장 뒤 관측(read)이 끊기면 저장 불명확으로 바꾸지 않고 확인된 receipt·소비·`next`를 유지한 채 `observation.state:failed`로 귀속·완료만 낮춥니다. relay read도 기존 read처럼 `structuredOutput`을 실패 없이 보여 줍니다.
556
+ relay의 `read --since <seq>`도 기존 read와 같은 `prompt` 정산 결과를 돌려줍니다. 관측 공백은 귀속을 낮추지만 명시적 거부(`prompt_rejected`)는 공백이 있어도 유지됩니다.
557
+ relay의 `read --last/--around`도 이력 끝까지 확인합니다. 60초 안에 끝을 확인하지 못하면 `read-window-incomplete`로 실패하므로 오래된 구간을 최신으로 쓰지 마세요.
558
+ 구 host의 일반 broker IPC 연결만으로 owner contact를 시도하지 않습니다. socket env가 없는 broker 자식은 broker가 `SAYCODE_OWNER_CONTACT_BROKER_IPC=1`로 지원을 표시했을 때만 broker IPC로 owner contact를 보냅니다. 직접 workspace 원장 후손의 chat은 기존 경로를 먼저 선택합니다.
559
+ owner relay의 형제·부모·chat 입력에는 후손 전용 `delegatedAttendance`를 전달하지 않습니다.
560
+
561
+ ### Owner relay resume·permission·abort (T1c-b2 D3, 기본 off)
562
+
563
+ relay가 자기 외 own으로 확인한 대상만 main의 `resume-own-session`·`reply-own-permission`·`abort-own-turn`으로 보냅니다.
564
+ main은 source/target 소유·세대를 다시 조회하고 RPC 직전에 같은 소유자인지 재확인합니다. self·타인·미확인·scope 밖은 RPC 없이 거부합니다.
565
+ - resume: 관측한 machine/runtime이 그대로인 정지 세션만 기존 machine `resume-happy-session`으로 재개하고, 새 runtime과 같은 소유자를 관측한 뒤에만 accepted입니다.
566
+ socket chat은 그 새 runtime의 live guard를 확인하기 전 입력을 보내지 않습니다(`guard-unavailable`, 미송신).
567
+ - permission: 실제 pending request의 permissionId와 현재 runtime에 결속하며 기존 `permission` RPC 인자(`id/approved/decision/updatedInput/mode`)를 그대로 씁니다.
568
+ 가짜·만료·종결 request는 RPC 없이 거부합니다. socket chat에서는 공지된 request에 `/allow`·`/allow-always`·`/deny [id]`로 답합니다.
569
+ - abort: 세션 `abort` RPC만 씁니다. 머신 `stop-session`으로 바꾸거나 승격하지 않습니다(`agent stop`의 후손 stop과 별개).
570
+ relay abort는 socket chat의 명시 `/stop`만 씁니다. Claude local 대상에도 보내며 remote 전환 효과를 표시합니다. 일반 chat의 공유 abort 포트(중단·즉시 보내기)는 main이 명시 중단과 즉시 보내기를 구분할 수 없어 relay를 쓰지 않으며, own 대상은 기존처럼 `not_owned`입니다(local 여부와 무관).
571
+ - 제어 RPC는 키 조회·암호화 뒤 실제 전송 직전에 요청 수명과 source/target 권위·대상 상태를 다시 확인하고, 실패하면 보내지 않고 `unavailable`입니다.
572
+ - ACK 유실은 `unknown`이며 자동 재송신하지 않습니다. 같은 permission 재응답도 보내지 않으며, 미확정 응답은 request 종결을 관측할 때까지 보존하고 보존 한도가 차면 새 응답을 `answer-capacity`로 거부합니다. resume 복구는 같은 owner authority를 다시 관측할 때만 성공이며 main의 `owner-unconfirmed`는 복구하지 않습니다.
573
+ - 일반 chat의 permission 응답도 relay를 쓸 때는 공지 시점에 같은 request가 대기 중인 runtime에 묶입니다. 묶지 못하면 보내지 않으며 같은 질문을 반복하지 않습니다: 요청이 끝났으면 질문을 멈추고, 바뀌었으면 현재 요청으로 다시 묻고, 입력이 끝났으면 chat을 정상 종료하며, 그 밖에는 한 번 안내한 뒤 다음 세션 갱신 때 다시 묻습니다.
574
+ - permission 공지는 runtime과 요청 내용(tool·arguments·생성 시각)에 묶입니다. 같은 runtime에서 같은 ID의 내용이 바뀌면 이전 공지를 철회하고 다시 공지하며, 응답 직전에 현재 요청이 공지한 내용과 다르면 보내지 않습니다.
575
+ - socket chat과 일반 chat 모두, main이 실행 전에 거부한 응답(`control-in-flight` 등)은 보내지 않은 것으로 보고 다시 답할 수 있습니다(socket chat은 공지 복구, 일반 chat은 같은 질문 재질문). 전달 불명 응답만 다시 보내지 않습니다.
576
+ - socket chat의 permission 공지는 공지 당시 runtime에 묶이고 그 runtime으로만 답합니다. runtime이 바뀌면 공지를 철회하고 새 요청을 다시 공지합니다. 전달 확인(`delivered:true`)일 때만 `Answered.`를 표시합니다.
577
+ owner port가 없거나 relay가 own을 증명하지 못하면 기존 credential 경로와 verb gate를 그대로 씁니다(새 IPC 0회). relay 결과로 credential RPC를 허가하지 않습니다.
578
+
579
+ ### Owner relay owned child (T1c-b2 D4, 기본 off)
580
+
581
+ owner contact가 있고 자기 세션의 lookup 행이 `self` + `confirmed-owner`일 때만 `agent spawn`·`spawn --handoff`가
582
+ main의 `spawn-owned-child`로 자식을 만듭니다. 포트가 없거나 개인 계정·미확인 source, `--output-schema`, claude/codex 밖의
583
+ agent, 부모 scope와 다른 directory(`--worktree`·worktree 안의 root)는 이 판정 단계에서 기존 daemon spawn을 그대로 씁니다. broker 하위(depth≥1) 자식은 host가 자기 세션 하나만의 lookup을 허용할 때(형제·부모 ID가 섞인 lookup은 계속 host가 거부) 같은 규칙으로 owned 손자를 만들고, 그 lookup을 거부하는 구 broker host에서는 기존 daemon spawn입니다. Desktop의 owner relay 전환이 꺼져 있으면 host contact가 있어도 self lookup을 하지 않습니다. owned 경로를 시작한 뒤에는 어떤 실패도 무서명 spawn으로 바꾸지 않습니다.
584
+ 예산 예약(기존 6시간 차감, 환급 없음)과 `owned-spawns/<operationId>.json` 기록·재읽기가 먼저 성공해야 main을 부릅니다.
585
+ ACK 유실·pending은 같은 operation의 `owned-child-status`만 2~10초 간격 최대 120회 조회합니다. 620초 deadline은 명령 시작부터 세며 각 contact 호출도 남은 시간 안에 끊습니다.
586
+ 서명된 started receipt(session·binding·provider runtime)를 같은 예약에 대조한 뒤에만 lineage를 기록하고 결과에 `ownership`을 붙입니다.
587
+ 끝까지 확인되지 않으면 `spawn_pending`(state·stage·observedSessionId·`next`)입니다. `spawn_failed`는 보고된 실패이거나, main이 생성 단계 전 거부임을 표시한(`reason`이 `before-creation:`으로 시작) 경우뿐입니다.
588
+ `agent spawn --resume-op <operationId>`(`next.command`)는 CLI가 끝난 뒤에도 같은 operation의 status만 다시 조회해 정산합니다.
589
+ 기록(`owned-spawns/<operationId>.json`)이 이 세션·scope·예산 key의 것이고 원장에 그 예약이 있을 때만 진행하며, 새 spawn·새 예약·환급은 없습니다.
590
+ started면 원래 spawnId로 lineage를 기록해 `resumedOperation`과 함께 반환하고, 아직이거나 status를 조회할 수 없으면 다시 `spawn_pending`입니다(보고된 실패만 `spawn_failed`). 기록이 없거나 다른 source·scope면 `not_owned`,
591
+ owner contact가 없으면 `owner-contact-unavailable`, 재개 명령이 초기화(인증·연결)조차 못 하면 같은 operation의 `spawn_pending`(`next` 유지)입니다. 다른 spawn 옵션과 함께 쓸 수 없습니다.