chore: sync Telegram successor with current dev - #26
Merged
twoimo merged 15 commits intoJul 26, 2026
Merged
Conversation
…ueue availability seams (Yeachan-Heo#3159) Yeachan-Heo#3073 widened two availability predicates in InputController#isActionAvailable: - app.message.queue now also requires a non-empty composer draft, reading this.ctx.editor.getText(). - app.message.dequeue now reads this.ctx.session.getQueuedMessageEntries() and this.ctx.compactionQueuedMessages instead of session.queuedMessageCount. The g002/g003 red-team contexts are hand-built partial InteractiveModeContext stubs that predate those reads, so the predicates threw. ActionRegistry catches availability throws, reports via showError and caches false, which surfaced as: - g002:380 app.message.queue availability false (editor stub absent) - g003:171 palette order gaining a leading "error" entry, because building the palette evaluates availability for every action and the dequeue throw fired showError before the hide/focus/execute sequence. Both are stub gaps, not product regressions: the same code and the same tests pass at the PR head ce4f799, and the failure only appears once Yeachan-Heo#3073 landed alongside these files. Verified the product seams behave correctly by asserting draft gating (empty/whitespace/non-empty) and dequeue truth from real session plus compaction entries, with zero swallowed availability errors. Fix is test-only, 3 lines, filling the missing context surfaces. Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…xDelayMs (Yeachan-Heo#3156) * fix(coding-agent): cap legacy auto-compaction Retry-After at retry.maxDelayMs The auto-compaction retry loop recovered Retry-After by regex over provider error prose (`#parseRetryAfterMsFromError`) and then slept on it uncapped: const baseDelayMs = retrySettings.baseDelayMs * 2 ** attempt; const delayMs = retryAfterMs !== undefined ? Math.max(baseDelayMs, retryAfterMs) : baseDelayMs; A 30s heuristic switches to the next compaction candidate when the delay is too long, but it explicitly falls through on the last candidate ("No more candidates - we have to wait"). There the session slept for the full server-suggested duration — a 3h hint produced a 3h sleep. This contradicts the documented legacy rule in non-compaction-retry-policy.md: "retry.maxDelayMs caps every legacy session retry delay, including provider retry-after hints. Managed fallback intentionally does not cap typed Retry-After values because it retries within its separate per-entry budget." Compaction is legacy (prose-parsed, not typed transport facts) and has no per-entry budget, so it must be capped. The non-compaction legacy path already does exactly this at agent-session.ts (`Math.min(retryAfterMs, maxDelayMs)`); compaction was the only legacy consumer missing the bound. Adds `compactionRetryDelay()` next to `cappedExponentialWithFullJitter` and `effectiveFallbackDelay`, following this file's existing convention of pure, directly unit-tested delay math. It also collapses a missing/NaN/infinite hint to "no usable hint" — `#parseRetryAfterMsFromError` can return Infinity via `Number("1e999") * 1000`. Managed fallback behaviour is deliberately untouched: `effectiveFallbackDelay` stays uncapped and its "intentionally uncapped" contract test still passes. The two tests now sit adjacent and document the two-tier policy as a pair. Candidate-switching is unaffected in the default configuration: a hint above the cap still exceeds the 30s threshold either way, so only the terminal last-candidate sleep changes (3h -> retry.maxDelayMs, default 5min). Gates: routing-adversarial (9 pass), fallback/retry/compaction suites (279 pass across 34 files), tsc --noEmit clean. The 2 failures in agent-session-retry-fallback.test.ts are pre-existing on origin/dev (identical 14 pass / 2 fail with these changes stashed). * test(coding-agent): strengthen compaction retry-after cap coverage Adds two contract probes on top of the existing cap regressions: - Cross-path parity: both legacy surfaces (auto-compaction and the non-compaction session retry) recover Retry-After from provider prose, so the documented rule — "retry.maxDelayMs caps every legacy session retry delay, including provider retry-after hints" — must bind them identically. The probe pins compaction to the legacy non-compaction bound (Math.min(retryAfterMs, maxDelayMs)) so a future change to one cannot silently drift from the other. - Exhaustive invariant grid: 700 combinations of (baseDelayMs, maxDelayMs, attempt, retryAfterMs) — including NaN, +Infinity, negative, and undefined hints, and the maxDelayMs <= 0 "no cap" convention — assert the delay is always finite, non-negative, and within the cap. Non-vacuity verified by mutation: reverting the cap to the pre-fix `return hinted` fails 3 of the compaction probes (previously 1). expect() calls in this file go 64 -> 2002. No production change; managed fallback stays intentionally uncapped and its contract test is untouched. --------- Co-authored-by: dmae97 <dmae97@users.noreply.github.com>
…gnal (Yeachan-Heo#3076) Workflow-state readers (readJsonFile/readJsonValue) and the handoff paths wrote raw `WARNING: ... ignoring corrupt state` bytes to process.stderr. During an interactive session those bytes land in the TUI alternate-screen stream and paint over the live composer (Yeachan-Heo#3002). Route every such warning through the TUI-safe centralized file logger (console transport off by default) via the `@gajae-code/utils/logger` subpath, which keeps this module native-free for the gjc-state-gates shards (the package barrel pulls procmgr/ptree -> @gajae-code/natives). Unlike the file-logger-only approach, corrupt read/status/handoff results still surface the warning on the structured StateCommandResult.stderr channel, so `gjc state` CLI/automation can still tell corrupt state from absent state. The readers take an optional onWarning sink: in-process/TUI callers pass none (logger only), while handleRead/handleStatus collect it into the command result. Regressions: corrupt read/status now assert (a) no raw process.stderr write, (b) the warning present on the command result, (c) logger.warn carried it; plus an in-process readWorkflowStateJson path that must stay off process.stderr. Builds on Yeachan-Heo#3042 by @innocarpe (reviewed MERGE_READY, closed only for the read/status command-result gap this change closes); fixes Yeachan-Heo#3002.
… e2e (Yeachan-Heo#3160) Post-Yeachan-Heo#3076 shard-6 red was EBADF on closeSync in runCli finally after the fail-closed lifecycle idempotency path, not a product contract failure. Ignore already-closed capture FDs so the CLI exit/JSON assertions remain visible under CI load. Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…icit keyhints (Yeachan-Heo#3161) Co-authored-by: twoimo <twoimo@twoimoui-MacBookPro.local>
…re rounds (Yeachan-Heo#3162) * fix(deep-interview): unblock apply-round-result so interviews can score rounds `gjc deep-interview apply-round-result` failed with DI_INTERNAL_ERROR on every call, so a deep interview could never score a single round. Three independent defects stacked on the same path: 1. The Round-0 topology gate is persisted by the recorder as an `answered` round shell (the locked intent contract binds to its answer hash), but it can never be scored: `apply-round-result` validates `--round` as positive. `applyDeepInterviewRoundResultV1` still counted it in the "every strictly-earlier round must be scored" precondition, permanently deadlocking every later round. 2. `decodeDeepInterviewRoundResultJson` materializes every optional key, so an omitted `targeting`/`ontology`/`bookkeeping` arrives as an explicit `undefined`. `canonicalJsonValue` rejected `undefined` outright, so computing the replay digest threw a raw TypeError that surfaced as the opaque DI_INTERNAL_ERROR. 3. `scoreToUnits` tested the raw float product. `0.69 * 10_000` is `6900.000000000001` and effective ambiguity round-trips through `units / 10_000`, so ordinary scores were rejected as non-integral 1e-4 units. Fixes, in order: exclude non-scorable Round-0 gate shells from the ordering precondition; drop `undefined` object properties in canonical JSON exactly like `JSON.stringify` (array elements and the top-level value stay strict, and absent vs present-but-undefined now digest identically); decide 1e-4 units from the shortest round-trip decimal instead of an epsilon, which accepts every genuine four-decimal score while still rejecting off-grid precision such as `0.00005` and `0.05000000000000001`. Tests: an end-to-end regression that records a Round-0 topology gate through the recorder and then scores Round 1 through the repair CLI, plus unit coverage for canonical-JSON undefined handling, digest equivalence, and the 1e-4 unit boundaries. * test(deep-interview): pin the invariants the scoring fix relies on Independent architect review flagged two coverage gaps and three nits; all are addressed here. - Pin the premise behind the Round-0 exclusion, which is sound only because a round-0 record can never carry scoring. A first attempt asserted this with a fixture that was rejected for four unrelated reasons and stayed green when the rule was deleted; it is now a positive control plus a negative case differing by exactly one field (`scores`), so deleting the validator's round-0 clause turns it red. Also assert `apply-round-result --round 0` returns DI_INVALID_ROUND. - Pin the collision boundary that makes dropping `undefined` safe: null (and 0/""/false) must survive canonical JSON. Loosening the skip predicate to `!value[key]` would otherwise change the meaning of every persisted round_result_digest with nothing failing. - Replace the hand-picked scoreToUnits spot checks with an exhaustive sweep over all 10,001 grid values, plus -0, 0.9999, and 0.30000000000000004. - Cover CLI idempotency for a result JSON that omits optional keys - the exact shape that used to throw - asserting the replay settles as a noop (status 0, written:false) rather than a second write. - Normalize `question_hash` with `?? null` in deepInterviewAnswerIdentityEqual, the one compared field left raw. For any record that has a question_hash this is the identity; it only makes an omitted and an explicitly-null hash compare equal, matching all seven sibling fields. No reachable behavior change. - State the Round-0 exclusion as `round.round !== 0` (identity) instead of `>= 1` (a range that also silently covered unreachable negatives), and drop the now-unreachable `Number.isSafeInteger` throw in scoreToUnits: the [0,1] guard plus the four-digit cap already bound units to an integer in [0, 10_000]. Each of the four assertions was mutation-tested by reverting the corresponding production change and confirming the suite fails.
…Heo#3171) The stream-watchdog idle-timeout helpers (getStreamIdleTimeoutMs, getOpenAIStreamIdleTimeoutMs) only read the PI_-prefixed env names, so the documented GJC_OPENAI_STREAM_IDLE_TIMEOUT_MS (docs/environment-variables.md) was a silent no-op. Resolve it GJC-first with the PI_ names as legacy fallbacks, matching the migration in Yeachan-Heo#2827/Yeachan-Heo#2943/Yeachan-Heo#3041. Semantics preserved (?? nullish chain; =0 still disables the watchdog). Extends stream-timeout-defaults.test.ts with GJC-first, override, disable, and alias-fallback coverage for both getters.
…eo#3165) (Yeachan-Heo#3170) * fix(coding-agent): enforce ralplan consensus iteration cap (Yeachan-Heo#3165) Native `gjc ralplan --write` now refuses planner/revision openers past a finite consensus-iteration budget (default 5, override via gjc.ralplan.maxIterations). Overflow exits 3 with PLANNING-STUCK so headless runs cannot silently loop on perpetual ITERATE, while final escalation remains allowed without auto-implementation. * fix(coding-agent): fail closed ralplan cap on untrusted index (Yeachan-Heo#3165) Floor consensus opener count by on-disk stage-*-{planner,revision}.md so a wiped, truncated, deleted, or malformed index.jsonl cannot under-count and allow unbounded revision openers after prior passes. --------- Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
twoimo
merged commit Jul 26, 2026
7b1e4d4
into
successor/telegram-tool-activity-current-v2
5 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Internal synchronization PR. Merge current upstream
devat41d6850cinto the preserved Telegram tool-activity successor before applying the remaining changelog fix and running exact-head verification.