sprag-cli 3.62.0 → 3.64.0

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/bin/cli.js CHANGED
@@ -57,7 +57,7 @@ import { sessionCostAcross } from '../src/claude-price.js';
57
57
  import { chipForIssues } from '../src/advice.js';
58
58
  import { debug } from '../src/debug.js';
59
59
  import { createArgs } from '../src/cli-args.js';
60
- import { updateStatus, maybeSpawnUpdateCheck, readUpdateState, updateCheckDisabled } from '../src/update-check.js';
60
+ import { readUpdateChip } from '../src/update-notice.js';
61
61
  import { selectAgent } from '../src/agent.js';
62
62
 
63
63
  const selection = (() => {
@@ -77,28 +77,6 @@ const PKG_VERSION = (() => {
77
77
 
78
78
  const { getArg, hasFlag, numArg } = createArgs(args);
79
79
 
80
- /**
81
- * Version/update state for the statusline chip. Reads a cache file and, when
82
- * that cache has aged past the check interval, kicks a detached child to
83
- * refresh it for a later render. Never awaits the network, never throws into
84
- * the render: a registry outage must not cost the statusline its other chips.
85
- */
86
- function readUpdateChip() {
87
- try {
88
- maybeSpawnUpdateCheck(PKG_VERSION);
89
- const status = updateStatus(PKG_VERSION);
90
- // When the registry was last asked, for the verbose "✓6h" on an up-to-date
91
- // version. updateStatus() does not carry it, and a second read of the
92
- // 200-byte state file is cheaper than a second status computation. With
93
- // checks switched off the file is a leftover, so it says nothing.
94
- const checkedAt = updateCheckDisabled() ? null : (Number(readUpdateState().checkedAt) || null);
95
- return { ...status, checkedAt };
96
- } catch (e) {
97
- debug('update-check:chip', e);
98
- return null;
99
- }
100
- }
101
-
102
80
  /**
103
81
  * Whether the table report may use ANSI codes. The formatter looked at NO_COLOR
104
82
  * alone, so `--no-color` did nothing there and a piped report (`sprag | less`,
@@ -605,7 +583,7 @@ async function main() {
605
583
  }
606
584
  const { withBaseStatusline } = await import('../src/statusline-base.js');
607
585
  console.log(withBaseStatusline(formatNoSession(
608
- { caps, model, effort, windowLabel, version: PKG_VERSION, update: readUpdateChip() },
586
+ { caps, model, effort, windowLabel, version: PKG_VERSION, update: readUpdateChip(PKG_VERSION) },
609
587
  { color: colorOk, mode: labelMode },
610
588
  )));
611
589
  return;
@@ -892,7 +870,7 @@ async function main() {
892
870
  cost,
893
871
  options: { days, windowHours, windowLabel, version: PKG_VERSION },
894
872
  // Cached-only; the background refresh it may trigger lands on a later render.
895
- update: format === 'statusline' ? readUpdateChip() : null,
873
+ update: format === 'statusline' ? readUpdateChip(PKG_VERSION) : null,
896
874
  lastActivity,
897
875
  monthSpend: monthSpendInfo,
898
876
  spikeReport,
package/docs/CODEX.ko.md CHANGED
@@ -49,6 +49,7 @@ macOS zsh 환경에서는 설치가 tmux 설치 여부를 먼저 확인합니다
49
49
  | seed | 호환되는 래칫 프리셋을 제안하고 응답을 별도 저장합니다 |
50
50
  | 문서 변환 | PDF·Office·Figma 문서를 Markdown으로 변환하고 캐시 덮어쓰기를 막습니다 |
51
51
  | 한국어 검사 | 파일 쓰기 후 문체 위반을 검사하며 block·warn·off를 지원합니다 |
52
+ | 답변 언어 검사 | Stop 훅이 현재 한국어 질문과 최종 답변을 대조합니다. 영어 답변 요청, Stop 재개, `korean reply off`는 제외합니다 |
52
53
  | 상태 표시 | 별도 패널, tmux 패널, 일회성 상태줄을 제공합니다 |
53
54
  | 브리핑·이력 | 최신 컨텍스트와 사용량 경고를 알리고 세션별로 저장합니다 |
54
55
  | 위임 원장 | Sprag가 위임한 하위 실행의 토큰에 호출 경로의 단가를 적용해 예상 차액을 계산합니다. 직접 호출은 OpenAI 정가를, LiteLLM 공급자는 그 게이트웨이의 단가를 씁니다 |
@@ -133,6 +134,12 @@ Codex 0.159.2에서는 `spawn_agent`가 `PreToolUse`를 거치지 않습니다(
133
134
  SessionStart는 캐시된 후보를 읽고 필요할 때 별도 프로세스에서 갱신합니다. 명시적인 `--refresh`는
134
135
  대기 주기를 건너뜁니다.
135
136
 
137
+ 한국어 지침이 켜져 있는 동안 Sprag는 지침 전문을 전역 Codex `AGENTS.md`(`AGENTS.override.md`가
138
+ 비어 있지 않으면 그 파일)의 `<!-- sprag:codex:korean-style:begin -->`과 `end` 표지 사이에 둡니다.
139
+ Codex는 이 파일을 세션마다 전부 읽지만 아래 훅 예산에는 지침이 들어가지 않기 때문입니다. 설치,
140
+ `korean on|off`, 세션 시작 때마다 이 블록을 맞추고, `sprag uninstall --agent codex`가 지웁니다. 훅은
141
+ 블록을 방금 쓴 세션에서만 지침을 자기 컨텍스트에 함께 넣습니다.
142
+
136
143
  Codex 0.159.2는 훅 하나가 넘긴 컨텍스트를 약 2,450토큰까지만 보존하고, 그보다 길면 가운데를
137
144
  잘라 냅니다. 그래서 SessionStart는 추정치 2,000토큰 안에 들어가는 항목만 잘리지 않은 상태로
138
145
  넣습니다. 우선순위는 ratchet 룰, 한국어 지침, 응집성 지침, 문서 변환 안내, 제안 순입니다. 들어가지
@@ -158,7 +165,6 @@ Claude를 설치하지 않은 Codex 첫 사용자도 기록을 쌓기 전에 프
158
165
  sprag seed --agent codex
159
166
  sprag seed accept all --global --agent codex
160
167
  sprag delegate shared status --agent codex
161
- sprag delegate on --agent codex
162
168
  # 자동으로 정해진 난도별 모델을 바꾸고 싶을 때만:
163
169
  sprag delegate shared map T2 --from PARENT_MODEL --model SMALL_MODEL --effort high --agent codex
164
170
  sprag delegate shared map T1 --from PARENT_MODEL --model MEDIUM_MODEL --effort medium --agent codex
@@ -169,10 +175,10 @@ sprag delegate shared map T1 --from PARENT_MODEL --model MEDIUM_MODEL --effort m
169
175
 
170
176
  `map` 줄은 선택 사항입니다. 매핑하지 않은 난도는 Claude Code가 `haiku`와 `sonnet`을 실제 모델로
171
177
  풀듯이 자동으로 정해집니다. 부모 모델의 공급자에서 가격이 매겨져 있고(게이트웨이 `/model/info` 가격,
172
- OpenAI 직접 호출이면 공개 가격), 부모보다 싸며, 이 사용자의 Codex 세션에서 이미 실행된 모델 가운데
173
- 가장 싼 모델이 T2, 그다음 모델이 T1이 됩니다. 그런 모델이 하나뿐이면 T1은 메인 모델이 처리하고, 하나도
174
- 없으면 두 난도 모두 메인 모델이 처리합니다. 자동 결정은 `delegate on` 상태에서만 동작하고, route-scan이
175
- 기록한 실행 모델을 쓰며, 아무것도 저장하지 않으므로 가격이나 실행 이력이 바뀌면 그대로 따라갑니다.
178
+ OpenAI 직접 호출이면 공개 가격), 부모보다 싸며, 이 사용자의 Codex 세션에서 이미 실행됐거나(OpenAI 기본 공급자라면 Codex가 이 계정에 보여 주는
179
+ `models_cache.json` 모델 목록에 있는) 모델 가운데 가장 싼 모델이 T2, 그다음 모델이 T1이 됩니다. 그런 모델이 하나뿐이면 T1은 메인 모델이 처리하고, 하나도
180
+ 없으면 두 난도 모두 메인 모델이 처리합니다. 위임은 기본으로 켜져 있고(`sprag delegate off --agent codex`로 끕니다), 자동 결정은 위임이 켜진 동안
181
+ 동작합니다. 아무것도 저장하지 않으므로 가격이나 실행 이력, 모델 목록이 바뀌면 그대로 따라갑니다.
176
182
  `delegate shared status`와 설치 출력에 정해진 모델이 표시되고, 정해지지 않으면 그 이유가 표시됩니다.
177
183
  끄려면 `sprag delegate shared auto off --agent codex`를 실행하십시오.
178
184
 
@@ -235,6 +241,8 @@ sprag harness prune --global --older-than 6 --dry-run --agent codex
235
241
  ```sh
236
242
  sprag doc2md install-converter --agent codex
237
243
  sprag doc2md report.pdf --agent codex
244
+ sprag doc2md deck.pptx --agent codex --session SESSION_ID
245
+ sprag doc2md savings --agent codex --format json
238
246
  sprag korean on --agent codex
239
247
  sprag korean lint warn --agent codex
240
248
  sprag cohesion on --agent codex
@@ -245,6 +253,25 @@ sprag cohesion on --agent codex
245
253
  지원하는 라이브러리로 복사본을 편집한 뒤 다시 변환하십시오. 텍스트 추출만으로 차트·이미지·배치를
246
254
  검증했다고 말할 수 없습니다. 실패와 잘린 결과는 그대로 보고해야 합니다.
247
255
 
256
+ `Doc2md saved`는 PPTX·DOCX·XLSX의 원본 XML과 준비된 Markdown을 비교한 추정치입니다.
257
+ 각각 UTF-8 바이트 수를 4로 나눠 토큰 수를 추정하고, 그 차이에 해당 Codex 모델의 입력 단가를
258
+ 적용합니다. 네이티브 첨부 비용이나 실제 청구액을 측정한 값은 아닙니다. 변환본이 더 크면
259
+ 음수를 그대로 표시합니다. Claude 단가를 가져오지 않으며 모델, 프로바이더와 단가 출처를 기록합니다.
260
+
261
+ 같은 문서를 다시 사용해도 절감액을 더하거나 단가를 바꾸지 않습니다. 원본이 바뀌면 해당 문서의
262
+ 기존 기록을 교체합니다. PDF·Figma·구형 XLS, 잘린 변환본, 단가나 모델 정보가 없는 기록은
263
+ 미산정으로 남깁니다. 과거 기록에 현재 기본 모델의 단가를 임의로 적용하지 않습니다.
264
+ 수동 변환은 `--session` 또는 `CODEX_THREAD_ID`로 세션을 식별하며, 정보가 없으면 변환만 수행합니다.
265
+
266
+ 패널은 `Doc2md saved`와 `Routing saved`를 설정 항목 앞에 함께 표시합니다. 위임 안내만
267
+ 생성되고 자식이 실행되지 않으면 절감액은 `n/a`이며, 세션별 pending·bound·completed·unclaimed
268
+ 건수로 진행 상태를 구분합니다. 한 요청의 T1/T2 선택지는 안내 한 건으로 계산합니다.
269
+
270
+ `sprag delegate doctor --agent codex --session SESSION_ID`는 모델 호출 없이 로컬 버전,
271
+ 기능 플래그, 모델 카탈로그와 연결 기록을 확인합니다. v2 카탈로그와 기능 플래그가 다르면
272
+ 진단 단서로만 알리고 설정은 바꾸지 않습니다. 실제 세션에 `spawn_agent`와 `wait_agent`가
273
+ 있는지는 별도로 확인해야 하며, 도구가 없으면 메인 모델이 처리합니다.
274
+
248
275
  훅은 지원되는 파일 읽기와 단순한 `cat`·`head`·`tail`, 인식 가능한 쓰기 대상을 처리합니다.
249
276
  임의의 추출 스크립트나 복합 셸 명령을 모두 가로채지는 않습니다. `doc2md off`는 Codex의 자동
250
277
  변환과 문서 쓰기 보호를 끕니다. 문체 설정은 공유하지만 기능을 켤 때는 Codex 훅을 먼저 등록합니다.
@@ -281,16 +308,48 @@ sprag capabilities --agent codex
281
308
 
282
309
  ## 제거와 업그레이드
283
310
 
311
+ 패널은 새 버전이 있으면 사용량 앞에 알림을 표시하고, 백그라운드에서 버전 캐시를 갱신합니다.
312
+ 일회성 상태줄은 캐시만 읽습니다. 대화형 SessionStart는 변경 내용을 알리고 설치 동의를 구합니다.
313
+ `sprag update-check --dismiss --agent codex`로 해당 버전의 알림을 끌 수 있습니다.
314
+ `CTS_NO_UPDATE_CHECK=1` 또는 `NO_UPDATE_NOTIFIER`는 확인 자체를 끕니다.
315
+ compaction, 하위 세션 시작, `codex exec`에는 업그레이드 질문을 넣지 않습니다.
316
+
284
317
  ```sh
285
318
  sprag uninstall --agent codex
286
319
  npm i -g sprag-cli --ignore-scripts
287
320
  sprag install --agent codex
288
321
  ```
289
322
 
290
- 제거는 Codex 훅과 전역 하네스 블록만 대상으로 합니다. 프로젝트 하네스, 래칫 규칙, 백업과
323
+ 제거는 Codex 훅, 전역 하네스 블록, 관리 중인 zsh 함수를 대상으로 합니다. 새 셸을 열어야
324
+ 이미 로드된 함수도 사라집니다. 프로젝트 하네스, 래칫 규칙, 백업과
291
325
  공유 상태는 남습니다. 일반 npm 설치는 Claude postinstall을 실행하므로 Codex만 사용한다면
292
326
  업그레이드에도 `--ignore-scripts`를 유지하십시오.
293
327
 
328
+ ## 구현 분업
329
+
330
+ 설계, 원인 진단, 스펙 확정과 최종 검증은 메인 모델이 맡습니다. 여러 파일을 고치는 구현은
331
+ 설계를 확정한 뒤 T1 모델에 위임합니다. Claude는 기존처럼 `model: sonnet`을 쓰고,
332
+ Codex는 현재 부모 모델과 프로바이더의 명시적 T1 매핑을 우선합니다. 매핑이 없으면
333
+ 자동 해석한 T1을 씁니다. 이는 역할 구분이며 두 모델의 성능이 같다는 뜻은 아닙니다.
334
+
335
+ ```sh
336
+ sprag delegate implementation --agent codex --from PARENT_MODEL --provider PROVIDER --session PARENT_SESSION_ID
337
+ ```
338
+
339
+ 기본 설정 대신 실제 부모 세션의 정보를 넘깁니다. 자식에게 설계, 수정할 파일, 제약,
340
+ 테스트 명령, 완료 기준과 모델 선택 이유를 전달하고, 반환된 route 줄을 메시지 끝에 붙입니다.
341
+ `fork_turns "none"`으로 생성하며 출력 상한은 8,000토큰입니다. 메인은 `wait_agent`로
342
+ 완료를 기다린 뒤 diff와 실제 테스트 결과를 검증합니다. 상한은 지침이며 강제 제한은 아닙니다.
343
+
344
+ T1이 없거나 위임이 꺼져 있으면 메인이 처리합니다. haiku나 T2로 대체하지 않습니다.
345
+ 이미 읽은 파일의 약 50줄 이하 수정도 메인에서 처리합니다. 위임 도구가 없으면 위 명령을
346
+ 실행하지 않고 제약을 알립니다. 별도 Codex CLI로 우회하지 않습니다. 원장에는 부모, 모델,
347
+ 프로바이더가 맞는 자식 실행만 집계하며, 실행되지 않은 안내는 절감으로 계산하지 않습니다.
348
+
349
+ 공유 정책의 Codex 실패율도 규칙 목록, 패널, 브리핑에 표시합니다. 정책, 등급, 범위,
350
+ 프로바이더와 부모·자식 모델별로 집계하며 Claude 통계에는 합치지 않습니다. 매핑을 검토할
351
+ 동안 `sprag delegate shared off --agent codex`로 공유 위임을 끌 수 있습니다.
352
+
294
353
  공식 인터페이스는 [Codex 훅](https://developers.openai.com/codex/hooks/)과
295
354
  [AGENTS.md 안내](https://developers.openai.com/codex/guides/agents-md/),
296
355
  [설정 참조](https://developers.openai.com/codex/config-reference/)에서 확인할 수 있습니다.
package/docs/CODEX.md CHANGED
@@ -58,6 +58,14 @@ sprag korean lint warn --agent codex
58
58
  sprag doc2md install-converter --agent codex
59
59
  ```
60
60
 
61
+ While Korean guidance is on, Sprag keeps it in the global Codex `AGENTS.md`
62
+ (or `AGENTS.override.md` when that is non-empty), between
63
+ `<!-- sprag:codex:korean-style:begin -->` and `...:end -->` markers, because
64
+ Codex loads that file in full every session and the hook budget below is too
65
+ small for the guide. `install`, `korean on|off`, and each session start keep the
66
+ block in step; `sprag uninstall --agent codex` removes it. The hook adds the
67
+ guide to its own context only in the session that just wrote the block.
68
+
61
69
  `korean on`, `cohesion on`, `brief on`, `doc2md on`, and `delegate on`
62
70
  register Codex hooks before saving the enabled preference. If hook registration
63
71
  fails, the preference stays unchanged. Review new or changed definitions in
@@ -72,6 +80,7 @@ shared hooks needed by other features.
72
80
  | Ratchet rules | Separate Codex files; global and project rules injected at SessionStart, including resume and compaction |
73
81
  | Korean guidance / English cohesion | SessionStart injection using shared Sprag preferences |
74
82
  | Korean write-time lint | PostToolUse feedback for `apply_patch` and recognized shell write targets; block, warn, and off modes |
83
+ | Reply language | Stop checks the current Korean prompt and final answer; `korean reply off` disables it, and explicit English requests and Stop continuations are excluded |
75
84
  | Documents | Shared converter, prompt paths, supported file reads, and recognized document/cache write guards; `sprag doc2md <file> --agent codex` also works manually |
76
85
  | Token report | Table, JSON, or CSV from local Codex rollout logs, with time and project filters |
77
86
  | Statusline | `--statusline` or `--format statusline` renders the compact panel once, with wrapping and label/color preferences |
@@ -86,6 +95,7 @@ shared hooks needed by other features.
86
95
  | Routing saved | Sprag-attributed child tokens priced by the path the call took (OpenAI list prices for direct calls, the gateway's `/model/info` prices for a LiteLLM provider); signed, so a pricier child is a loss; an estimate, not billed savings |
87
96
  | Diagnostics | `doctor` checks user config, hook registration, harness, logs, panel activity, and preferences |
88
97
  | Capability matrix | `capabilities` lists supported, partial, native, and unsupported areas without invoking a model |
98
+ | Updates | Live panels refresh the shared version cache in the background; one-shot statuslines read it offline; interactive SessionStart offers a consent-based Codex-only upgrade |
89
99
 
90
100
  The document converter is shared with Claude Code and requires its optional
91
101
  converter dependencies. Disable automatic Codex conversion and write guards with
@@ -112,11 +122,34 @@ history is not imported. Counts are sorted by frequency, then extension, with no
112
122
  Claude pricing involved. Codex reads a neutral Markdown view without the shared
113
123
  cache's Claude-based savings banner; the shared cache remains unchanged.
114
124
 
125
+ `Doc2md saved` is a separate Codex estimate for `.pptx`, `.docx`, and `.xlsx`:
126
+ Office body XML bytes divided by four, minus the prepared Markdown bytes divided
127
+ by four, multiplied by the recorded model's input-token price. This compares
128
+ XML extraction with Markdown, not native attachment billing or measured tokens.
129
+ Negative differences remain negative. The price comes from the active Codex
130
+ provider, never Claude's price table, and its source is recorded with the result.
131
+ Repeated use of the same source does not add another saving or reprice a saved
132
+ estimate. Changed sources replace their previous entry, just like document counts.
133
+ Clipped conversions, PDF/Figma/legacy XLS, missing prices, and old records without
134
+ model identity remain unpriced. Nothing is backfilled from default config.
135
+
136
+ ```sh
137
+ sprag doc2md deck.pptx --agent codex --session SESSION_ID
138
+ sprag doc2md savings --agent codex --format json
139
+ sprag saved --agent codex
140
+ ```
141
+
142
+ Hooks use the current payload/rollout model and provider. Manual conversion can
143
+ use `--session` or the CLI's `CODEX_THREAD_ID`; without session identity, the
144
+ document is converted but the monetary estimate stays unavailable. No network
145
+ request is needed for conversion accounting; it uses a cached provider price.
146
+
115
147
  ## Diagnostics And Continuity
116
148
 
117
149
  ```sh
118
150
  sprag doctor --agent codex
119
151
  sprag doctor --agent codex --format json
152
+ sprag delegate doctor --agent codex --session SESSION_ID
120
153
  sprag capabilities --agent codex
121
154
  sprag brief --agent codex
122
155
  sprag brief on --agent codex
@@ -134,6 +167,15 @@ proof of trust. Its config report excludes provider credentials and is not an
134
167
  effective-config resolver: active profiles, project config, managed policy,
135
168
  and invocation overrides can differ. Check Codex `/hooks` for approval.
136
169
 
170
+ `delegate doctor` reads local Codex version, feature flags, bundled model catalog
171
+ and Sprag offer/binding records without calling a model. A v2 catalog/feature
172
+ mismatch is reported only as a compatibility clue, not a diagnosis. Enabled
173
+ settings and registered hooks cannot prove the running model has `spawn_agent`
174
+ and `wait_agent`. Confirm those tools in the active session. The panel reports
175
+ pending, bound, completed and unclaimed offers separately, scoped to its session;
176
+ T1/T2 alternatives from one prompt count as one offer. Unclaimed offers are not
177
+ charged or credited as savings. Missing tools leave work on the main agent.
178
+
137
179
  Briefing is enabled by default after installing and trusting the prompt hook.
138
180
  It reads at most the latest 256 KiB of the current transcript and warns when
139
181
  recorded input reaches 80% or 95% of the recorded context window, or a reported
@@ -151,7 +193,7 @@ warning records. A handoff file is never overwritten and identifies quota
151
193
  values as recorded snapshots, not live responses. Fill in its remaining-work
152
194
  sections before using it to continue in another session.
153
195
 
154
- Delegation is independently opt-in via `delegate on`. As of Codex 0.159.2,
196
+ Delegation is on by default; `sprag delegate off --agent codex` turns it off. As of Codex 0.159.2,
155
197
  `spawn_agent` never reaches `PreToolUse` (measured directly: a recording hook
156
198
  saw only `Bash` calls while a spawn happened), so the rewrite below cannot see
157
199
  or steer it. The live path instead runs at prompt time: `UserPromptSubmit`
@@ -223,7 +265,6 @@ A Codex-first user needs no Claude installation or transcript history:
223
265
  sprag seed --agent codex
224
266
  sprag seed accept all --global --agent codex # or --project, after choosing scope
225
267
  sprag delegate shared status --agent codex
226
- sprag delegate on --agent codex
227
268
  # optional, to override the automatic tier models:
228
269
  sprag delegate shared map T2 --from PARENT_MODEL --model SMALL_MODEL --effort high --agent codex
229
270
  sprag delegate shared map T1 --from PARENT_MODEL --model MEDIUM_MODEL --effort medium --agent codex
@@ -232,11 +273,13 @@ sprag delegate shared map T1 --from PARENT_MODEL --model MEDIUM_MODEL --effort m
232
273
  The map lines are optional. A tier without a mapping resolves automatically,
233
274
  the way Claude Code resolves `haiku` and `sonnet`: the cheapest model that is
234
275
  priced on the parent's provider (gateway `/model/info` prices, or OpenAI list
235
- prices for direct calls), costs less than the parent, and has already run in
236
- your Codex sessions becomes T2, and the next one up becomes T1. With a single
237
- such model T1 stays on the main agent; with none, both do. Resolution needs
238
- `delegate on`, uses the route scan's observed models, and follows price or
239
- history changes without storing anything. `delegate shared status` and the
276
+ prices for direct calls), costs less than the parent, and is available becomes
277
+ T2, and the next one up becomes T1. A model is available when it has already
278
+ run in your Codex sessions, or, on the built-in OpenAI provider only, when
279
+ Codex lists it for your account in its `models_cache.json`. With a single
280
+ such model T1 stays on the main agent; with none, both do. Resolution runs
281
+ while delegation is on (the default) and follows price, history, and catalog
282
+ changes without storing anything. `delegate shared status` and the
240
283
  install output show the resolved models and, when nothing resolves, why.
241
284
  `sprag delegate shared auto off --agent codex` turns it off.
242
285
 
@@ -295,6 +338,35 @@ booked as a loss and the entry is marked `aborted`.
295
338
 
296
339
  ## Harness Commands
297
340
 
341
+ ### Implementation Split
342
+
343
+ The main agent retains design, diagnosis, specification and final verification.
344
+ After design is complete, multi-file implementation can use the resolved T1:
345
+
346
+ ```sh
347
+ sprag delegate implementation --agent codex --from PARENT_MODEL --provider PROVIDER --session PARENT_SESSION_ID
348
+ ```
349
+
350
+ Use the active parent's identity, not the defaults in config. The installed
351
+ harness treats this policy as the user's delegation request. The resolver uses
352
+ an explicit T1 mapping first, otherwise automatic T1 resolution; Claude's
353
+ corresponding policy still uses `model: sonnet`. These are roles, not a claim of
354
+ equal model capability. It never falls back to T2. Disabled delegation or an
355
+ unresolved T1 leaves the work on the main agent. Small edits of roughly 50 lines
356
+ in already-read files also stay on the main agent.
357
+
358
+ The child receives a self-contained specification, owned files, constraints,
359
+ tests, completion criteria, the model-selection reason and an 8,000-output-token
360
+ cap. Use `fork_turns "none"` and end the child message with the returned route
361
+ line. Wait with `wait_agent` (at least 120 seconds per wait), then inspect its
362
+ diff and actual test results. Without native subagent tools, skip the resolver
363
+ and report the limitation; do not launch a second Codex CLI as a substitute.
364
+ The cap is guidance, not runtime enforcement. Accounting requires a child of
365
+ the recorded parent on the selected model/provider; an unused hint earns no
366
+ savings. Implementation requests are not automatically classified as cheap work.
367
+
368
+ ### Ratchet Management
369
+
298
370
  ```sh
299
371
  sprag harness init --agent codex # current project
300
372
  sprag harness check --agent codex # project, or global fallback
@@ -352,7 +424,8 @@ after an hour. `--refresh` bypasses that schedule.
352
424
 
353
425
  Codex 0.159.2 keeps about 2,450 tokens of one hook's context and cuts the
354
426
  middle of anything longer. SessionStart therefore fits its parts whole within a
355
- 2,000-token estimate, in priority order: ratchet rules, Korean guidance,
427
+ 2,000-token estimate, in priority order: ratchet rules, Korean guidance
428
+ (only while it is not yet in the global `AGENTS.md`; see below),
356
429
  cohesion guidance, the document note, then offers. Rules and guidance that do
357
430
  not fit are written to a per-project file under the Sprag data directory, and
358
431
  the context opens with an instruction to read it. `codex exec` runs, recognized
@@ -364,8 +437,8 @@ compatible Codex ratchet presets. Ratchet and skip decisions remain agent-specif
364
437
  an approved common policy suppresses duplicate offers within its scope in either
365
438
  agent. Both seed acceptance and
366
439
  route approval require the user's choice of global or project scope. A project
367
- route is saved for the project where it was observed. Enable delegation with
368
- `sprag delegate on --agent codex` and trust the hooks before expecting rules to run.
440
+ route is saved for the project where it was observed. Delegation is on by default
441
+ (`sprag delegate off --agent codex` turns it off); trust the hooks before expecting rules to run.
369
442
 
370
443
  ## Reports
371
444
 
@@ -453,6 +526,12 @@ each other:
453
526
  rate and signed saving. A rule is flagged for review when the 95% lower bound
454
527
  of its failure rate exceeds 20% over at least five runs, as on the Claude side.
455
528
 
529
+ Shared policies also report their Codex outcomes in the rule list, panel and
530
+ prompt briefing. Outcomes are grouped by policy, tier, scope, provider and
531
+ parent/child model pair; they are never imported into Claude's rule statistics.
532
+ Use `sprag delegate shared off --agent codex` to pause shared routing while
533
+ reviewing the mapping. Removing a common policy affects both agents.
534
+
456
535
  Providers are kept separate. Negative values are losses: the child ran on a
457
536
  pricier model than the parent, and the loss reduces the totals. Missing rates or usage produce unpriced entries, never invented zeroes. A priced
458
537
  entry keeps its rate snapshot across later outages. Pending route records are
@@ -460,7 +539,8 @@ matched for seven days; saved ledger entries remain afterward. Run `--refresh`
460
539
  after delegation to update the ledger without waiting for the next route scan.
461
540
 
462
541
  Claude's ledger is never imported. Document conversion shows distinct document
463
- counts rather than a Codex dollar saving or billing measurement.
542
+ counts and the explicitly labelled Office XML-versus-Markdown estimate above,
543
+ not a billing measurement.
464
544
 
465
545
  ### Live Companion Panel
466
546
 
@@ -564,7 +644,9 @@ The inline compact panel reuses Claude's icon/narrow/text preferences and
564
644
  segmented gauges. Chips wrap as units instead of losing their trailing values;
565
645
  the pane grows with the content while keeping at least ten rows for Codex.
566
646
  If the terminal is too small, a remaining-row count marks hidden content.
567
- Cache reuse is shown in tokens, not an inferred dollar saving. Document counts
647
+ Cache reuse is shown in tokens, not an inferred dollar saving. `Doc2md saved` and
648
+ `Routing saved` stay together before preferences, even when unavailable; missing
649
+ prices or execution evidence are labelled instead of being reported as $0. Document counts
568
650
  and format breakdowns come from Codex's own document records and are labelled
569
651
  `Codex total`; Claude-based dollar estimates are not attributed to Codex.
570
652
  Unavailable cost and limit placeholders are hidden in compact mode. Cache timer
@@ -605,6 +687,7 @@ are skipped, and incomplete/copied/reset logs can only give best-effort totals.
605
687
  Reports show **cost and exact cache TTL as unavailable** (`null` in JSON). They do not
606
688
  apply Anthropic prices, infer subscription charges, or claim Claude's cache TTL.
607
689
  Document counts are not a measurement of Codex billing or avoided tokens.
690
+ The separate Doc2md estimate is a bytes-based comparison, not tokenizer output.
608
691
 
609
692
  ## Limits And Removal
610
693
 
@@ -624,7 +707,9 @@ Rate-limit gauges use Codex's recorded values only. Run
624
707
  sprag uninstall --agent codex
625
708
  ```
626
709
 
627
- Removal takes out Sprag's Codex hooks and global harness block. It keeps project
710
+ Removal takes out Sprag's Codex hooks, global harness block and managed zsh
711
+ wrapper. Open a fresh shell afterward; an already-loaded shell function remains
712
+ in the old shell. It keeps project
628
713
  harnesses, ratchet rules, backups, other hooks, and shared Sprag state.
629
714
  `--purge` is intentionally unsupported for Codex. Remove a project block with
630
715
  `sprag harness uninit --agent codex --project` in that project.
@@ -633,6 +718,13 @@ Normal npm upgrades still run the package's Claude postinstall. Codex-only users
633
718
  should keep using `npm i -g sprag-cli --ignore-scripts` followed by
634
719
  `sprag install --agent codex` when upgrading.
635
720
 
721
+ The panel places an available update before usage metrics. Dismissing a version
722
+ with `sprag update-check --dismiss --agent codex` hides its chip and session
723
+ offer until a newer version appears. `CTS_NO_UPDATE_CHECK=1` or
724
+ `NO_UPDATE_NOTIFIER` disables checks. Compaction, subagent starts and `codex exec`
725
+ do not ask to upgrade. SessionStart checks at most every three hours; the live
726
+ panel uses the shared 24-hour interval and never waits for a network response.
727
+
636
728
  ## Official References
637
729
 
638
730
  - [Codex hooks, event schemas, and trust](https://developers.openai.com/codex/hooks/)
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "sprag-delegation-meter",
3
+ "version": "0.1.0",
4
+ "description": "Measures what each delegated subagent actually spent, from the API's own usage, and keeps one record per run",
5
+ "author": { "name": "DeepPulse", "url": "https://www.youtube.com/@DeepPulseKR" }
6
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "modules": ["./register.ts"]
3
+ }
@@ -0,0 +1,197 @@
1
+ import type { EngineInterface, Register, TurnUsage } from 'claude-code'
2
+
3
+ /**
4
+ * sprag-delegation-meter: what each delegated subagent actually spent.
5
+ *
6
+ * sprag's delegation ledger prices a run after the fact, from the transcript
7
+ * it left. This mod reads the same numbers while the run happens, from the
8
+ * API's own usage: agent.spawn says which model the subagent got and what its
9
+ * parent runs on, every turn.step of the subagent's loop reports one
10
+ * request's tokens, and turn.complete closes a turn of that loop.
11
+ *
12
+ * Each run is kept in $.store under a key of its own, `run:<session>:<agent>`,
13
+ * so two sessions never overwrite each other's runs. The mod only observes: no
14
+ * spawn, request or turn is changed, and sprag's transcript-based ledger keeps
15
+ * running beside it.
16
+ */
17
+
18
+ type Usage = { input: number; output: number; cacheRead: number; cacheWrite: number }
19
+
20
+ type Run = {
21
+ sessionId: string
22
+ agentId: string
23
+ description: string
24
+ subagentType: string
25
+ /** The Agent tool's `model` parameter as given; null when the call named none. */
26
+ requestedModel: string | null
27
+ /** What the spawn resolved; null when this module never saw the spawn. */
28
+ model: string | null
29
+ parentModel: string | null
30
+ /** The model the API last reported answering, by its own id. */
31
+ answeredBy: string | null
32
+ startedAt: number
33
+ endedAt: number | null
34
+ durationMs: number
35
+ steps: number
36
+ turns: number
37
+ usage: Usage
38
+ lastReason: string | null
39
+ }
40
+
41
+ // Families as sprag's modelFamily() names them: versions are noise in a log line.
42
+ const FAMILIES = ['fable', 'mythos', 'opus', 'sonnet', 'haiku']
43
+
44
+ const family = (model: string) => FAMILIES.find(f => model.toLowerCase().includes(f)) ?? model
45
+
46
+ const zero = (): Usage => ({ input: 0, output: 0, cacheRead: 0, cacheWrite: 0 })
47
+
48
+ const fromApi = (u: TurnUsage): Usage => ({
49
+ input: u.input_tokens,
50
+ output: u.output_tokens,
51
+ cacheRead: u.cache_read_input_tokens,
52
+ cacheWrite: u.cache_creation_input_tokens,
53
+ })
54
+
55
+ const add = (into: Usage, more: Usage) => {
56
+ into.input += more.input
57
+ into.output += more.output
58
+ into.cacheRead += more.cacheRead
59
+ into.cacheWrite += more.cacheWrite
60
+ }
61
+
62
+ const tokens = (n: number) =>
63
+ n >= 1_000_000 ? `${(n / 1_000_000).toFixed(1)}M` : n >= 1_000 ? `${(n / 1_000).toFixed(1)}k` : String(n)
64
+
65
+ const OUTCOME: Record<string, string> = { answer: '완료', aborted: '중단', refusal: '거절', error: '실패' }
66
+
67
+ // Runs in flight by agent id, and per run the requests of the turn still open.
68
+ // A run leaves the map once its turn is saved, so the map holds only what is
69
+ // running and does not grow with the session. $.store has the run from then
70
+ // on: a subagent resumed later (SendMessage starts another turn of the same
71
+ // loop) or met again after a module reload continues from its saved totals.
72
+ const runs = new Map<string, Run>()
73
+ const openTurn = new Map<string, { usage: Usage; steps: number }>()
74
+
75
+ const storeKey = (sessionId: string, agentId: string) => `run:${sessionId}:${agentId}`
76
+
77
+ const isRun = (value: unknown, agentId: string): value is Run =>
78
+ typeof value === 'object' && value !== null
79
+ && (value as Run).agentId === agentId
80
+ && typeof (value as Run).usage === 'object' && (value as Run).usage !== null
81
+
82
+ /**
83
+ * The run for an agent id: the one in flight, else the saved one, else a new
84
+ * one. A subagent's first request can arrive before the spawn hook resumes
85
+ * after `await next(e)`, so a step may be what creates the run.
86
+ *
87
+ * The session id is read per run and never cached in the module: the module
88
+ * outlives /clear, /resume and /branch, each of which moves the conversation
89
+ * to another session id without loading the module again.
90
+ */
91
+ async function runFor($: EngineInterface, agentId: string): Promise<Run> {
92
+ const held = runs.get(agentId)
93
+ if (held) return held
94
+ const sessionId = await $.session.id()
95
+ let saved: unknown
96
+ try {
97
+ saved = await $.store.get(storeKey(sessionId, agentId))
98
+ } catch {
99
+ // An unreadable store costs this run only its earlier turns.
100
+ }
101
+ // A copy, since the totals are added to in place.
102
+ const run: Run = isRun(saved, agentId) ? JSON.parse(JSON.stringify(saved)) : {
103
+ sessionId,
104
+ agentId,
105
+ description: '',
106
+ subagentType: '',
107
+ requestedModel: null,
108
+ model: null,
109
+ parentModel: null,
110
+ answeredBy: null,
111
+ startedAt: await $.clock.now(),
112
+ endedAt: null,
113
+ durationMs: 0,
114
+ steps: 0,
115
+ turns: 0,
116
+ usage: zero(),
117
+ lastReason: null,
118
+ }
119
+ runs.set(agentId, run)
120
+ return run
121
+ }
122
+
123
+ export const register: Register = on => {
124
+ // A meter must never stop what it measures, so every hook here fails open:
125
+ // a hook that breaks before its next() is skipped and the event goes on, and
126
+ // one that breaks after it keeps the result next() settled to. That is the
127
+ // engine's default already; each .catch states it, and answers the same way
128
+ // when the hook is reached from beneath its own $ call (a re-entry).
129
+ on('agent.spawn', async ($, e, next) => {
130
+ const spawned = await next(e)
131
+ if (spawned.deny !== undefined || !spawned.agentId) return spawned
132
+ const run = await runFor($, spawned.agentId)
133
+ run.description = e.description
134
+ run.subagentType = e.subagentType
135
+ run.requestedModel = e.model ?? null
136
+ run.model = spawned.model
137
+ run.parentModel = e.parentModel || null
138
+ // A workflow can start dozens of agents; one line each would bury the transcript.
139
+ if (!e.workflow) {
140
+ const parent = run.parentModel ? ` (메인 ${family(run.parentModel)})` : ''
141
+ $.ui.log(`🔀 [sprag] "${e.description}" → ${family(spawned.model)} 위임${parent}`)
142
+ }
143
+ return spawned
144
+ }).catch(($, e, next) => next(e))
145
+
146
+ on('turn.step', async function* ($, e, next) {
147
+ const result = yield* next(e)
148
+ if (e.agentId && result.usage) {
149
+ const run = await runFor($, e.agentId)
150
+ const turn = openTurn.get(e.agentId) ?? { usage: zero(), steps: 0 }
151
+ add(turn.usage, fromApi(result.usage))
152
+ turn.steps += 1
153
+ openTurn.set(e.agentId, turn)
154
+ run.answeredBy = result.usage.model
155
+ }
156
+ return result
157
+ }).catch(async function* ($, e, next) {
158
+ return yield* next(e)
159
+ })
160
+
161
+ on('turn.complete', async ($, e, next) => {
162
+ if (!e.agentId) return next(e)
163
+ const run = await runFor($, e.agentId)
164
+ const turn = openTurn.get(e.agentId)
165
+ openTurn.delete(e.agentId)
166
+ // The steps' own counts are the measurement. The turn's sum stands in only
167
+ // when no step was seen, e.g. the module reloaded during the turn.
168
+ if (turn && turn.steps > 0) {
169
+ add(run.usage, turn.usage)
170
+ run.steps += turn.steps
171
+ } else if (e.usage) {
172
+ add(run.usage, fromApi(e.usage))
173
+ run.answeredBy = e.usage.model
174
+ }
175
+ run.turns += 1
176
+ run.durationMs += e.durationMs
177
+ run.endedAt = await $.clock.now()
178
+ run.lastReason = e.reason
179
+
180
+ try {
181
+ await $.store.set(storeKey(run.sessionId, run.agentId), run)
182
+ runs.delete(e.agentId)
183
+ } catch {
184
+ // A store that refuses the write leaves only this run unrecorded. It
185
+ // stays in the map, so its next turn still adds to these totals.
186
+ }
187
+
188
+ const model = family(run.model ?? run.answeredBy ?? 'unknown')
189
+ const u = run.usage
190
+ $.ui.log(
191
+ `🔀 [sprag] ${model} 위임 ${OUTCOME[e.reason] ?? e.reason} · ${(run.durationMs / 1000).toFixed(1)}초`
192
+ + ` · 요청 ${run.steps}회 · 입력 ${tokens(u.input)} · 캐시 읽기 ${tokens(u.cacheRead)}`
193
+ + ` · 캐시 쓰기 ${tokens(u.cacheWrite)} · 출력 ${tokens(u.output)}`,
194
+ )
195
+ return next(e)
196
+ }).catch(($, e, next) => next(e))
197
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "sprag-doc2md",
3
+ "version": "0.1.0",
4
+ "description": "Reads the Markdown that sprag doc2md converted instead of the binary document, with no denied Read in between",
5
+ "author": { "name": "DeepPulse", "url": "https://www.youtube.com/@DeepPulseKR" }
6
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "modules": ["./register.ts"]
3
+ }