therookie 0.4.24 → 0.4.25
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/package.json
CHANGED
package/src/lib/codex-config.js
CHANGED
|
@@ -5,11 +5,20 @@ import { toPosixPath } from './hook-command.js'
|
|
|
5
5
|
export const ROOKIE_BEGIN = '# >>> rookie codex hook (managed) — 수동 편집 금지 >>>'
|
|
6
6
|
export const ROOKIE_END = '# <<< rookie codex hook (managed) <<<'
|
|
7
7
|
|
|
8
|
-
function block(
|
|
9
|
-
// Codex SessionStart command handler. additionalContext 출력 형식은 Claude 와 동일.
|
|
8
|
+
function block(paths, platform = process.platform) {
|
|
9
|
+
// Codex SessionStart / UserPromptSubmit command handler. additionalContext 출력 형식은 Claude 와 동일.
|
|
10
10
|
// Windows 는 backslash 경로를 forward-slash 로 변환(Git Bash 가 실행) — macOS/Linux 무변경.
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
//
|
|
12
|
+
// spec 2026-09-02 (2단계 로드 강제): UserPromptSubmit 이 없으면 SessionStart 가 만든
|
|
13
|
+
// 인격 대기열을 아무도 소비하지 않는다 — 인격은 안 들어가고 파일만 쌓인다(2026-09-02 실사고).
|
|
14
|
+
// 둘은 같이 등록해야 짝이 맞는다. paths 는 문자열(SessionStart 만) 또는 {sessionStart, promptSubmit}.
|
|
15
|
+
const sessionStart = toPosixPath(
|
|
16
|
+
typeof paths === 'string' ? paths : paths.sessionStart,
|
|
17
|
+
platform
|
|
18
|
+
)
|
|
19
|
+
const promptSubmitRaw = typeof paths === 'string' ? null : paths.promptSubmit
|
|
20
|
+
const promptSubmit = promptSubmitRaw ? toPosixPath(promptSubmitRaw, platform) : null
|
|
21
|
+
const lines = [
|
|
13
22
|
ROOKIE_BEGIN,
|
|
14
23
|
'[[hooks.SessionStart]]',
|
|
15
24
|
'matcher = "startup|resume"',
|
|
@@ -18,15 +27,28 @@ function block(hookScriptPath, platform = process.platform) {
|
|
|
18
27
|
'type = "command"',
|
|
19
28
|
// B-3: codex_cli 표면을 인자로 전달 → session-start.sh 가 x-rookie-surface 로 비콘.
|
|
20
29
|
// (Codex Desktop 도 같은 config.toml 을 읽어 codex_cli 로 집계 — desktop 세분화는 후속.)
|
|
21
|
-
`command = 'bash "${
|
|
30
|
+
`command = 'bash "${sessionStart}" codex_cli'`,
|
|
22
31
|
'timeout = 15',
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
]
|
|
33
|
+
if (promptSubmit) {
|
|
34
|
+
lines.push(
|
|
35
|
+
'',
|
|
36
|
+
'[[hooks.UserPromptSubmit]]',
|
|
37
|
+
'',
|
|
38
|
+
'[[hooks.UserPromptSubmit.hooks]]',
|
|
39
|
+
'type = "command"',
|
|
40
|
+
`command = 'bash "${promptSubmit}"'`,
|
|
41
|
+
// 파일만 읽고 끝나는 경로라 짧게 — Claude 쪽도 3초다.
|
|
42
|
+
'timeout = 5'
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
lines.push(ROOKIE_END)
|
|
46
|
+
return lines.join('\n')
|
|
25
47
|
}
|
|
26
48
|
|
|
27
49
|
// 기존 managed 블록이 있으면 교체, 없으면 끝에 append. 마커 밖은 보존.
|
|
28
|
-
export function upsertCodexHookBlock(existingToml,
|
|
29
|
-
const fresh = block(
|
|
50
|
+
export function upsertCodexHookBlock(existingToml, hookPaths, platform = process.platform) {
|
|
51
|
+
const fresh = block(hookPaths, platform)
|
|
30
52
|
const begin = existingToml.indexOf(ROOKIE_BEGIN)
|
|
31
53
|
if (begin === -1) {
|
|
32
54
|
const base = existingToml.replace(/\s*$/, '')
|
|
@@ -170,7 +170,11 @@ export function applyCodexSetup({ home = homedir() } = {}) {
|
|
|
170
170
|
const existing = existsSync(configPath) ? readFileSync(configPath, 'utf8') : ''
|
|
171
171
|
let next
|
|
172
172
|
try {
|
|
173
|
-
next = upsertCodexHookBlock(existing,
|
|
173
|
+
next = upsertCodexHookBlock(existing, {
|
|
174
|
+
sessionStart: hookScript,
|
|
175
|
+
// 인격 대기열 소비자 — 짝이 없으면 파일만 쌓인다(spec 2026-09-02).
|
|
176
|
+
promptSubmit: join(homedir(), '.rookie', 'bin', 'user-prompt-skill-gate.sh'),
|
|
177
|
+
})
|
|
174
178
|
} catch (e) {
|
|
175
179
|
// marker 손상(begin 만 있고 end 없음) → 파일을 건드리지 않고 경고 신호만 반환(사용자 config 보호).
|
|
176
180
|
if (e?.code === 'MALFORMED_CODEX_MARKER') return { configPath, hookScript, error: 'malformed_marker' }
|
|
@@ -61,3 +61,34 @@ pending_inject_write() {
|
|
|
61
61
|
' 2>/dev/null || true
|
|
62
62
|
return 0
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
# pending_inject_sweep — 소비되지 않고 남은 대기열 청소.
|
|
66
|
+
# 짝이 되는 UserPromptSubmit 훅이 없는 환경(구 Codex 등록본 등)에서는 대기열이
|
|
67
|
+
# 만들어지기만 하고 아무도 읽지 않는다. 그대로 두면 파일이 쌓이고, 그 자체가
|
|
68
|
+
# 조용한 실패가 된다(2026-09-02 실사고 — Codex 는 SessionStart 만 등록돼 있었다).
|
|
69
|
+
# 24시간 넘게 남은 것만 지운다 — 진행 중인 세션의 대기열은 건드리지 않는다.
|
|
70
|
+
pending_inject_sweep() {
|
|
71
|
+
local dir
|
|
72
|
+
dir="$(pending_inject_dir)"
|
|
73
|
+
[ -d "$dir" ] || return 0
|
|
74
|
+
ROOKIE_PI_DIR="$dir" ROOKIE_DROP_LOG="${ROOKIE_DROP_LOG:-$HOME/.rookie/dropped-mutations.log}" node -e '
|
|
75
|
+
try {
|
|
76
|
+
const fs=require("fs"), path=require("path");
|
|
77
|
+
const dir=process.env.ROOKIE_PI_DIR, MAX=24*3600*1000;
|
|
78
|
+
let swept=0;
|
|
79
|
+
for(const f of fs.readdirSync(dir)){
|
|
80
|
+
if(!f.endsWith(".json") && !f.endsWith(".lock")) continue;
|
|
81
|
+
const p=path.join(dir,f);
|
|
82
|
+
try{
|
|
83
|
+
const st=fs.lstatSync(p);
|
|
84
|
+
if(!st.isFile()){ fs.unlinkSync(p); continue; }
|
|
85
|
+
if(Date.now()-st.mtimeMs>MAX){ fs.unlinkSync(p); swept+=1; }
|
|
86
|
+
}catch{}
|
|
87
|
+
}
|
|
88
|
+
if(swept>0){
|
|
89
|
+
try{ fs.appendFileSync(process.env.ROOKIE_DROP_LOG, new Date().toISOString()+" pending_inject swept_unconsumed="+swept+"\n"); }catch{}
|
|
90
|
+
}
|
|
91
|
+
} catch {}
|
|
92
|
+
' 2>/dev/null || true
|
|
93
|
+
return 0
|
|
94
|
+
}
|
|
@@ -261,6 +261,8 @@ if [ "$STATUS_CODE" = "200" ]; then
|
|
|
261
261
|
# spec 2026-09-02 (2단계 로드 강제) — 예산에서 생략된 섹션 본문을 대기열에 둔다.
|
|
262
262
|
# UserPromptSubmit 이 첫 발화에 직접 주입한다. resume 은 컨텍스트가 살아 있어 제외.
|
|
263
263
|
if command -v pending_inject_write >/dev/null 2>&1; then
|
|
264
|
+
# 소비자 없는 환경에서 쌓인 옛 대기열부터 치운다(24시간 초과분만).
|
|
265
|
+
command -v pending_inject_sweep >/dev/null 2>&1 && pending_inject_sweep || true
|
|
264
266
|
pending_inject_write "$SESSION_ID" "$RESP_FILE" "$SOURCE" || true
|
|
265
267
|
fi
|
|
266
268
|
|
package/version.json
CHANGED