@raidiant/notifai 10.1.0 → 10.1.1
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/CHANGELOG.md +17 -0
- package/README.md +3 -0
- package/dist/cli-bin.d.ts +21 -1
- package/dist/cli-bin.js +141 -10
- package/dist/cli-bin.js.map +1 -1
- package/dist/cli-update-contract.d.ts +7 -0
- package/dist/cli-update-contract.js +10 -0
- package/dist/cli-update-contract.js.map +1 -0
- package/dist/client.d.ts +6 -1
- package/dist/client.js +1 -0
- package/dist/client.js.map +1 -1
- package/dist/codex-wake.d.ts +2 -0
- package/dist/codex-wake.js +2 -1
- package/dist/codex-wake.js.map +1 -1
- package/dist/commands-auth.d.ts +15 -1
- package/dist/commands-auth.js +29 -11
- package/dist/commands-auth.js.map +1 -1
- package/dist/commands-core.d.ts +8 -1
- package/dist/commands-core.js +11 -4
- package/dist/commands-core.js.map +1 -1
- package/dist/commands-devices.d.ts +10 -1
- package/dist/commands-devices.js +17 -13
- package/dist/commands-devices.js.map +1 -1
- package/dist/commands-doctor.js +93 -49
- package/dist/commands-doctor.js.map +1 -1
- package/dist/commands-hooks.js +47 -10
- package/dist/commands-hooks.js.map +1 -1
- package/dist/commands-init.d.ts +16 -0
- package/dist/commands-init.js +245 -87
- package/dist/commands-init.js.map +1 -1
- package/dist/commands-send.d.ts +1 -1
- package/dist/commands-send.js +121 -35
- package/dist/commands-send.js.map +1 -1
- package/dist/commands-update.d.ts +10 -0
- package/dist/commands-update.js +141 -0
- package/dist/commands-update.js.map +1 -0
- package/dist/commands.d.ts +2 -1
- package/dist/commands.js +2 -1
- package/dist/commands.js.map +1 -1
- package/dist/hook-adapter.d.ts +2 -0
- package/dist/hook-adapter.js +12 -1
- package/dist/hook-adapter.js.map +1 -1
- package/dist/hooks.d.ts +43 -2
- package/dist/hooks.js +163 -21
- package/dist/hooks.js.map +1 -1
- package/dist/main-run.d.ts +1 -0
- package/dist/main-run.js +42 -0
- package/dist/main-run.js.map +1 -0
- package/dist/main.js +10 -31
- package/dist/main.js.map +1 -1
- package/dist/node-floor.d.ts +18 -0
- package/dist/node-floor.js +35 -0
- package/dist/node-floor.js.map +1 -0
- package/dist/program.d.ts +2 -1
- package/dist/program.js +22 -5
- package/dist/program.js.map +1 -1
- package/dist/question-settlement-process.d.ts +15 -0
- package/dist/question-settlement-process.js +35 -0
- package/dist/question-settlement-process.js.map +1 -0
- package/dist/readiness.d.ts +25 -1
- package/dist/readiness.js +32 -2
- package/dist/readiness.js.map +1 -1
- package/dist/setup-destinations.d.ts +23 -0
- package/dist/setup-destinations.js +44 -0
- package/dist/setup-destinations.js.map +1 -0
- package/package.json +2 -2
- package/src/cli-bin.test.ts +145 -7
- package/src/cli-bin.ts +176 -10
- package/src/cli-update-contract.ts +9 -0
- package/src/cli-update.test.ts +125 -0
- package/src/client.ts +7 -0
- package/src/codex-wake.ts +5 -1
- package/src/commands-auth.ts +44 -11
- package/src/commands-core.ts +18 -4
- package/src/commands-devices.ts +23 -16
- package/src/commands-doctor.ts +116 -55
- package/src/commands-hooks.ts +66 -16
- package/src/commands-init.ts +265 -88
- package/src/commands-send.ts +145 -39
- package/src/commands-update.ts +180 -0
- package/src/commands.test.ts +518 -49
- package/src/commands.ts +2 -0
- package/src/hook-adapter.ts +14 -0
- package/src/hooks.test.ts +275 -0
- package/src/hooks.ts +270 -18
- package/src/main-run.ts +45 -0
- package/src/main.ts +9 -33
- package/src/node-floor.test.ts +27 -0
- package/src/node-floor.ts +37 -0
- package/src/program.test.ts +35 -0
- package/src/program.ts +28 -4
- package/src/question-settlement-process.ts +47 -0
- package/src/readiness.ts +35 -2
- package/src/setup-destinations.test.ts +41 -0
- package/src/setup-destinations.ts +50 -0
- package/src/skill.test.ts +11 -0
- package/src/commands-devices.test.ts +0 -15
package/src/commands.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export {
|
|
2
2
|
EXIT,
|
|
3
3
|
log,
|
|
4
|
+
updateCliCommand,
|
|
4
5
|
type CommandDeps,
|
|
5
6
|
type CommandIo,
|
|
6
7
|
type CommandSpinner,
|
|
@@ -63,6 +64,7 @@ export {
|
|
|
63
64
|
type SetupScope,
|
|
64
65
|
} from './commands-init.js'
|
|
65
66
|
export { assessReadiness, doctorCommand } from './commands-doctor.js'
|
|
67
|
+
export { cliUpdateCommand, type CliUpdateFlags } from './commands-update.js'
|
|
66
68
|
export {
|
|
67
69
|
projectDisableCommand,
|
|
68
70
|
projectEnableCommand,
|
package/src/hook-adapter.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
lstatSync,
|
|
5
5
|
mkdirSync,
|
|
6
6
|
readFileSync,
|
|
7
|
+
realpathSync,
|
|
7
8
|
statSync,
|
|
8
9
|
} from 'node:fs'
|
|
9
10
|
import os from 'node:os'
|
|
@@ -42,6 +43,19 @@ export interface HookAdapterInspection {
|
|
|
42
43
|
problems: string[]
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
/** Whether a parsed adapter runs the same installed CLI artifact as PATH. */
|
|
47
|
+
export function hookAdapterTargetsArtifact(
|
|
48
|
+
target: HookAdapterTarget | null,
|
|
49
|
+
artifactPath: string,
|
|
50
|
+
): boolean {
|
|
51
|
+
if (target === null || isNpxAdapterTarget(target)) return false
|
|
52
|
+
try {
|
|
53
|
+
return realpathSync(target.scriptPath) === realpathSync(artifactPath)
|
|
54
|
+
} catch {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
45
59
|
export function hookHostPlatform(
|
|
46
60
|
platform: NodeJS.Platform | HookHostPlatform = process.platform,
|
|
47
61
|
): HookHostPlatform {
|
package/src/hooks.test.ts
CHANGED
|
@@ -48,6 +48,7 @@ import {
|
|
|
48
48
|
drainRetirements,
|
|
49
49
|
drainOrphanRetirements,
|
|
50
50
|
handleSessionEnd,
|
|
51
|
+
inspectQuestionState,
|
|
51
52
|
runEscalationWaiter,
|
|
52
53
|
MAX_CONTINUATION_COUNT,
|
|
53
54
|
MAX_HELD_DELIVERIES,
|
|
@@ -355,6 +356,7 @@ function harness(replies: ReplyView[] = []): Harness {
|
|
|
355
356
|
sleep: async (milliseconds: number) => {
|
|
356
357
|
clock += milliseconds
|
|
357
358
|
},
|
|
359
|
+
spawnQuestionSettlement: () => {},
|
|
358
360
|
},
|
|
359
361
|
}
|
|
360
362
|
}
|
|
@@ -2219,6 +2221,7 @@ describe('several questions in flight', () => {
|
|
|
2219
2221
|
pending: [
|
|
2220
2222
|
{
|
|
2221
2223
|
question: 'Ship it?',
|
|
2224
|
+
question_id: 'q_answer_handoff',
|
|
2222
2225
|
asked_at: NOW,
|
|
2223
2226
|
request_id: 'req_existing',
|
|
2224
2227
|
collapse_key: 'question-existing',
|
|
@@ -2231,6 +2234,10 @@ describe('several questions in flight', () => {
|
|
|
2231
2234
|
await hookRunCommand(h.deps, 'stop', stdin({ session_id: 'answer-handoff' }))
|
|
2232
2235
|
const staged = readSessionState('answer-handoff', h.env)
|
|
2233
2236
|
expect(staged.accepted?.answers[0]?.reply.text).toBe('Ship it')
|
|
2237
|
+
expect(inspectQuestionState('q_answer_handoff', h.env)).toMatchObject({
|
|
2238
|
+
found: true,
|
|
2239
|
+
question: { state: 'answered', submitted: true, request_id: 'req_existing' },
|
|
2240
|
+
})
|
|
2234
2241
|
expect(JSON.parse(h.io.outLines.at(-1)!).decision).toBe('block')
|
|
2235
2242
|
|
|
2236
2243
|
h.io.outLines = []
|
|
@@ -3054,6 +3061,40 @@ describe('user-prompt-submit hook', () => {
|
|
|
3054
3061
|
expect(readSessionState('s10', h.env).last_prompt_at).toBe(NOW)
|
|
3055
3062
|
})
|
|
3056
3063
|
|
|
3064
|
+
it('launches one settlement owner when a new prompt overtakes the asking turn Stop', async () => {
|
|
3065
|
+
const h = harness([])
|
|
3066
|
+
writeSessionState('pre-stop-prompt', h.env, { last_prompt_at: AWAY })
|
|
3067
|
+
registerQuestion(
|
|
3068
|
+
'pre-stop-prompt',
|
|
3069
|
+
h.env,
|
|
3070
|
+
{ question: 'Ship the release?', body: 'Ship the release?' },
|
|
3071
|
+
NOW,
|
|
3072
|
+
)
|
|
3073
|
+
let launches = 0
|
|
3074
|
+
const deps = {
|
|
3075
|
+
...h.deps,
|
|
3076
|
+
spawnQuestionSettlement: () => {
|
|
3077
|
+
launches += 1
|
|
3078
|
+
},
|
|
3079
|
+
} as CommandDeps
|
|
3080
|
+
|
|
3081
|
+
await hookRunCommand(
|
|
3082
|
+
deps,
|
|
3083
|
+
'user-prompt-submit',
|
|
3084
|
+
stdin({
|
|
3085
|
+
session_id: 'pre-stop-prompt',
|
|
3086
|
+
cwd: h.deps.cwd,
|
|
3087
|
+
prompt: 'Keep working on the unrelated release notes.',
|
|
3088
|
+
}),
|
|
3089
|
+
'codex',
|
|
3090
|
+
)
|
|
3091
|
+
|
|
3092
|
+
expect(launches).toBe(1)
|
|
3093
|
+
const pending = readSessionState('pre-stop-prompt', h.env).pending?.[0]
|
|
3094
|
+
expect(pending?.question).toBe('Ship the release?')
|
|
3095
|
+
expect(pending?.request_id).toBeUndefined()
|
|
3096
|
+
})
|
|
3097
|
+
|
|
3057
3098
|
it('records Stop separately so a prompt hook cannot impersonate turn-end routing', async () => {
|
|
3058
3099
|
const h = harness()
|
|
3059
3100
|
await hookRunCommand(h.deps, 'user-prompt-submit', stdin({ session_id: 'events' }))
|
|
@@ -3249,6 +3290,40 @@ describe('user-prompt-submit hook', () => {
|
|
|
3249
3290
|
expect(readSessionState('prompt-match', h.env).pending).toBeUndefined()
|
|
3250
3291
|
})
|
|
3251
3292
|
|
|
3293
|
+
it('withdraws a matching unpushed registration without launching settlement', async () => {
|
|
3294
|
+
const h = harness([])
|
|
3295
|
+
registerQuestion('prompt-match-unpushed', h.env, {
|
|
3296
|
+
question: 'Ship it?',
|
|
3297
|
+
questions: [
|
|
3298
|
+
{
|
|
3299
|
+
id: 'q1',
|
|
3300
|
+
text: 'Ship it?',
|
|
3301
|
+
choices: [
|
|
3302
|
+
{ id: 'ship', label: 'Ship it' },
|
|
3303
|
+
{ id: 'wait', label: 'Wait' },
|
|
3304
|
+
],
|
|
3305
|
+
},
|
|
3306
|
+
],
|
|
3307
|
+
})
|
|
3308
|
+
let launches = 0
|
|
3309
|
+
|
|
3310
|
+
await hookRunCommand(
|
|
3311
|
+
{
|
|
3312
|
+
...h.deps,
|
|
3313
|
+
spawnQuestionSettlement: () => {
|
|
3314
|
+
launches += 1
|
|
3315
|
+
},
|
|
3316
|
+
},
|
|
3317
|
+
'user-prompt-submit',
|
|
3318
|
+
stdin({ session_id: 'prompt-match-unpushed', prompt: 'Ship it' }),
|
|
3319
|
+
'codex',
|
|
3320
|
+
)
|
|
3321
|
+
|
|
3322
|
+
expect(launches).toBe(0)
|
|
3323
|
+
expect(h.recorder.submitted).toEqual([])
|
|
3324
|
+
expect(readSessionState('prompt-match-unpushed', h.env).pending).toBeUndefined()
|
|
3325
|
+
})
|
|
3326
|
+
|
|
3252
3327
|
it('leaves an unrelated prompt’s live question open for the reply window', async () => {
|
|
3253
3328
|
const h = harness([])
|
|
3254
3329
|
writeSessionState('prompt-unrelated', h.env, {
|
|
@@ -3446,6 +3521,26 @@ describe('session-end hook', () => {
|
|
|
3446
3521
|
expect(records.at(-1)?.data).toMatchObject({ outcome: 'cleaned', queued_retirements: 0 })
|
|
3447
3522
|
})
|
|
3448
3523
|
|
|
3524
|
+
it('keeps only content-free terminal question state after SessionEnd', async () => {
|
|
3525
|
+
const h = harness()
|
|
3526
|
+
writeSessionState('ended-question', h.env, {
|
|
3527
|
+
last_prompt_at: NOW,
|
|
3528
|
+
pending: [{ question: 'Ship it?', question_id: 'q_ended_local' }],
|
|
3529
|
+
})
|
|
3530
|
+
|
|
3531
|
+
expect(
|
|
3532
|
+
await hookRunCommand(h.deps, 'session-end', stdin({ session_id: 'ended-question' })),
|
|
3533
|
+
).toBe(EXIT.ok)
|
|
3534
|
+
|
|
3535
|
+
expect(readSessionState('ended-question', h.env)).toEqual({
|
|
3536
|
+
question_history: [{ question_id: 'q_ended_local', state: 'withdrawn' }],
|
|
3537
|
+
})
|
|
3538
|
+
expect(inspectQuestionState('q_ended_local', h.env)).toMatchObject({
|
|
3539
|
+
found: true,
|
|
3540
|
+
question: { state: 'withdrawn', submitted: false, request_id: null },
|
|
3541
|
+
})
|
|
3542
|
+
})
|
|
3543
|
+
|
|
3449
3544
|
it('round-trips future session-state fields when SessionEnd preserves an obligation', async () => {
|
|
3450
3545
|
const h = harness()
|
|
3451
3546
|
const sessionId = 'session-end-future-state'
|
|
@@ -4462,6 +4557,63 @@ describe('Claude Code Stop wake route', () => {
|
|
|
4462
4557
|
}
|
|
4463
4558
|
}
|
|
4464
4559
|
|
|
4560
|
+
it('settles a pre-Stop registration through the detached Claude owner', async () => {
|
|
4561
|
+
const h = harness([reply({ text: 'Ship it' })])
|
|
4562
|
+
h.recorder.acknowledgementRequiredFor = () => false
|
|
4563
|
+
writeGlobalConfig(h, 'ask_grace_seconds = 0\n')
|
|
4564
|
+
registerQuestion('claude-route', h.env, { question: 'Ship it?' }, NOW)
|
|
4565
|
+
const wake = claudeWake()
|
|
4566
|
+
let settlementInput: { session_id?: string; cwd?: string } | undefined
|
|
4567
|
+
const deps: CommandDeps = {
|
|
4568
|
+
...h.deps,
|
|
4569
|
+
claudeWake: wake,
|
|
4570
|
+
claudeSourcePid: 12345,
|
|
4571
|
+
spawnQuestionSettlement: (launch) => {
|
|
4572
|
+
settlementInput = launch.envelope
|
|
4573
|
+
},
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4576
|
+
await hookRunCommand(
|
|
4577
|
+
deps,
|
|
4578
|
+
'user-prompt-submit',
|
|
4579
|
+
stdin({
|
|
4580
|
+
session_id: 'claude-route',
|
|
4581
|
+
cwd: h.deps.cwd,
|
|
4582
|
+
prompt: 'Keep working on the unrelated release notes.',
|
|
4583
|
+
}),
|
|
4584
|
+
'claude-code',
|
|
4585
|
+
)
|
|
4586
|
+
await hookRunCommand(
|
|
4587
|
+
deps,
|
|
4588
|
+
'question-settlement',
|
|
4589
|
+
stdin(settlementInput),
|
|
4590
|
+
'claude-code',
|
|
4591
|
+
)
|
|
4592
|
+
|
|
4593
|
+
expect(h.recorder.submitted.filter((entry) => isQuestionSubmit(entry))).toHaveLength(1)
|
|
4594
|
+
expect(wake.sent).toHaveLength(1)
|
|
4595
|
+
expect(wake.resumed).toEqual([])
|
|
4596
|
+
expect(h.io.outLines).toEqual([])
|
|
4597
|
+
|
|
4598
|
+
await hookRunCommand(
|
|
4599
|
+
deps,
|
|
4600
|
+
'stop',
|
|
4601
|
+
stdin({ session_id: 'claude-route', cwd: h.deps.cwd }),
|
|
4602
|
+
'claude-code',
|
|
4603
|
+
)
|
|
4604
|
+
await hookRunCommand(
|
|
4605
|
+
deps,
|
|
4606
|
+
'stop',
|
|
4607
|
+
stdin({ session_id: 'claude-route', cwd: h.deps.cwd }),
|
|
4608
|
+
'claude-code',
|
|
4609
|
+
)
|
|
4610
|
+
|
|
4611
|
+
expect(h.recorder.submitted.filter((entry) => isQuestionSubmit(entry))).toHaveLength(1)
|
|
4612
|
+
expect(wake.sent).toHaveLength(1)
|
|
4613
|
+
expect(readSessionState('claude-route', h.env).pending).toBeUndefined()
|
|
4614
|
+
expect(readSessionState('claude-route', h.env).accepted).toBeUndefined()
|
|
4615
|
+
})
|
|
4616
|
+
|
|
4465
4617
|
it('routes an accepted answer through the Claude inbox instead of hook stdout', async () => {
|
|
4466
4618
|
const h = harness([reply({ text: 'BETA' })])
|
|
4467
4619
|
writeGlobalConfig(h, 'ask_grace_seconds = 0\n')
|
|
@@ -4877,6 +5029,129 @@ describe('Codex Stop wake route', () => {
|
|
|
4877
5029
|
})
|
|
4878
5030
|
}
|
|
4879
5031
|
|
|
5032
|
+
it('settles once when UserPromptSubmit overtakes the asking turn Stop', async () => {
|
|
5033
|
+
const h = harness([reply({ text: 'Ship it' })])
|
|
5034
|
+
h.recorder.acknowledgementRequiredFor = () => false
|
|
5035
|
+
writeGlobalConfig(h, 'ask_grace_seconds = 0\n')
|
|
5036
|
+
writeSessionState(CODEX_THREAD, h.env, { last_prompt_at: AWAY })
|
|
5037
|
+
registerQuestion(CODEX_THREAD, h.env, { question: 'Ship it?' }, NOW)
|
|
5038
|
+
const wake = codexWake({ sourceAlive: true, probe: { state: 'live' } })
|
|
5039
|
+
let launched:
|
|
5040
|
+
| { envelope: { session_id?: string; cwd?: string }; harness: string }
|
|
5041
|
+
| undefined
|
|
5042
|
+
const deps: CommandDeps = {
|
|
5043
|
+
...h.deps,
|
|
5044
|
+
codexWake: wake,
|
|
5045
|
+
codexSourcePid: 12345,
|
|
5046
|
+
spawnQuestionSettlement: (launch) => {
|
|
5047
|
+
launched = launch
|
|
5048
|
+
},
|
|
5049
|
+
}
|
|
5050
|
+
|
|
5051
|
+
await hookRunCommand(
|
|
5052
|
+
deps,
|
|
5053
|
+
'user-prompt-submit',
|
|
5054
|
+
stdin({
|
|
5055
|
+
session_id: CODEX_THREAD,
|
|
5056
|
+
cwd: h.deps.cwd,
|
|
5057
|
+
prompt: 'Keep working on the unrelated release notes.',
|
|
5058
|
+
}),
|
|
5059
|
+
'codex',
|
|
5060
|
+
)
|
|
5061
|
+
|
|
5062
|
+
expect(launched).toEqual({
|
|
5063
|
+
envelope: { session_id: CODEX_THREAD, cwd: h.deps.cwd },
|
|
5064
|
+
harness: 'codex',
|
|
5065
|
+
})
|
|
5066
|
+
expect(h.deps.now?.()).toBe(NOW)
|
|
5067
|
+
expect(h.recorder.submitted).toEqual([])
|
|
5068
|
+
expect(readSessionState(CODEX_THREAD, h.env).last_stop_at).toBeUndefined()
|
|
5069
|
+
|
|
5070
|
+
await hookRunCommand(
|
|
5071
|
+
deps,
|
|
5072
|
+
'question-settlement',
|
|
5073
|
+
stdin(launched?.envelope),
|
|
5074
|
+
'codex',
|
|
5075
|
+
)
|
|
5076
|
+
|
|
5077
|
+
expect(h.recorder.submitted.filter((entry) => isQuestionSubmit(entry))).toHaveLength(1)
|
|
5078
|
+
expect(h.io.outLines).toEqual([])
|
|
5079
|
+
expect(wake.resumed).toEqual([])
|
|
5080
|
+
expect(readSessionState(CODEX_THREAD, h.env).accepted).toBeDefined()
|
|
5081
|
+
expect(readSessionState(CODEX_THREAD, h.env).last_stop_at).toBeUndefined()
|
|
5082
|
+
|
|
5083
|
+
await hookRunCommand(
|
|
5084
|
+
deps,
|
|
5085
|
+
'stop',
|
|
5086
|
+
stdin({ session_id: CODEX_THREAD, cwd: h.deps.cwd }),
|
|
5087
|
+
'codex',
|
|
5088
|
+
)
|
|
5089
|
+
expect(h.io.outLines.map((line) => JSON.parse(line))).toEqual([
|
|
5090
|
+
expect.objectContaining({ decision: 'block' }),
|
|
5091
|
+
])
|
|
5092
|
+
|
|
5093
|
+
await hookRunCommand(
|
|
5094
|
+
deps,
|
|
5095
|
+
'stop',
|
|
5096
|
+
stdin({
|
|
5097
|
+
session_id: CODEX_THREAD,
|
|
5098
|
+
cwd: h.deps.cwd,
|
|
5099
|
+
stop_hook_active: true,
|
|
5100
|
+
}),
|
|
5101
|
+
'codex',
|
|
5102
|
+
)
|
|
5103
|
+
await hookRunCommand(
|
|
5104
|
+
deps,
|
|
5105
|
+
'stop',
|
|
5106
|
+
stdin({ session_id: CODEX_THREAD, cwd: h.deps.cwd }),
|
|
5107
|
+
'codex',
|
|
5108
|
+
)
|
|
5109
|
+
|
|
5110
|
+
expect(h.recorder.submitted.filter((entry) => isQuestionSubmit(entry))).toHaveLength(1)
|
|
5111
|
+
expect(h.io.outLines).toHaveLength(1)
|
|
5112
|
+
expect(readSessionState(CODEX_THREAD, h.env).pending).toBeUndefined()
|
|
5113
|
+
expect(readSessionState(CODEX_THREAD, h.env).accepted).toBeUndefined()
|
|
5114
|
+
})
|
|
5115
|
+
|
|
5116
|
+
it('keeps the detached recovery owner through the complete reply window', async () => {
|
|
5117
|
+
const h = harness([])
|
|
5118
|
+
writeGlobalConfig(h, 'ask_grace_seconds = 0\n')
|
|
5119
|
+
writeSessionState(CODEX_THREAD, h.env, { last_prompt_at: AWAY })
|
|
5120
|
+
registerQuestion(CODEX_THREAD, h.env, { question: 'Ship it?' }, NOW)
|
|
5121
|
+
let settlementInput: { session_id?: string; cwd?: string } | undefined
|
|
5122
|
+
const deps: CommandDeps = {
|
|
5123
|
+
...h.deps,
|
|
5124
|
+
codexWake: codexWake({ sourceAlive: true, probe: { state: 'live' } }),
|
|
5125
|
+
codexSourcePid: 12345,
|
|
5126
|
+
spawnQuestionSettlement: (launch) => {
|
|
5127
|
+
settlementInput = launch.envelope
|
|
5128
|
+
},
|
|
5129
|
+
}
|
|
5130
|
+
|
|
5131
|
+
await hookRunCommand(
|
|
5132
|
+
deps,
|
|
5133
|
+
'user-prompt-submit',
|
|
5134
|
+
stdin({
|
|
5135
|
+
session_id: CODEX_THREAD,
|
|
5136
|
+
cwd: h.deps.cwd,
|
|
5137
|
+
prompt: 'Keep working on the unrelated release notes.',
|
|
5138
|
+
}),
|
|
5139
|
+
'codex',
|
|
5140
|
+
)
|
|
5141
|
+
expect(h.deps.now?.()).toBe(NOW)
|
|
5142
|
+
|
|
5143
|
+
await hookRunCommand(
|
|
5144
|
+
deps,
|
|
5145
|
+
'question-settlement',
|
|
5146
|
+
stdin(settlementInput),
|
|
5147
|
+
'codex',
|
|
5148
|
+
)
|
|
5149
|
+
|
|
5150
|
+
expect(h.deps.now?.()).toBe(NOW + 480_000)
|
|
5151
|
+
expect(h.recorder.closed).toEqual([h.recorder.receipts[0]])
|
|
5152
|
+
expect(readSessionState(CODEX_THREAD, h.env).pending).toBeUndefined()
|
|
5153
|
+
})
|
|
5154
|
+
|
|
4880
5155
|
it('continues the held turn with decision:block when the answer arrives during the hold', async () => {
|
|
4881
5156
|
const h = harness([reply({ text: 'BETA' })])
|
|
4882
5157
|
writeGlobalConfig(h, 'ask_grace_seconds = 0\n')
|