chore: merge current upstream dev into welcome gutter successor - #15
Open
twoimo wants to merge 1082 commits into
Open
chore: merge current upstream dev into welcome gutter successor#15twoimo wants to merge 1082 commits into
twoimo wants to merge 1082 commits into
Conversation
Regenerate the public schema description so it reflects the compatibility-only fallback behavior already declared by the source settings schema. Constraint: Generated schema verification is required on the exact latest dev baseline Rejected: Hand-maintain divergent schema text | would fail the source-of-truth gate Confidence: high Scope-risk: narrow Reversibility: clean Directive: Regenerate schemas whenever settings descriptions change Tested: v0.11.6/dev schema gate, URL guard tests, and 19-PR integration Not-tested: Hosted CI on the new PR head
* fix(session): migrate legacy local root before resume * fix(session): handle absent managed artifact root * test(session): allow managed resume setup time
…d display (Yeachan-Heo#2794) Fable 5 adaptive thinking was billed but never displayed. Model discovery parsed claude-fable-5 as an unknown family and cached mode 'budget', sending enabled+budget_tokens that Fable answers with signature-only thinking; and supportsAdaptiveThinkingDisplay only matched Opus 4.7+, so the display: summarized opt-in never applied to Fable on either the Anthropic Messages or Bedrock Converse transport. Fixes Yeachan-Heo#2791
…an-Heo#2808) * fix(coding-agent): reject prose image paths * fix(coding-agent): reject multiline singular pasted-image paths --------- Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…ollback and replaying the transcript (Yeachan-Heo#2796) iTerm2 delivers SIGWINCH-driven resize events on tab activation and window focus changes without an actual grid size change. The resize handler forced a redraw on every event; on hosts still using the fullRender path (legacy multiplexer opt-in, non-process terminals) that clears scrollback (2J/H/3J) and replays the entire transcript, parking the native viewport at the transcript top — users returning to the tab found the thread scrolled to the very beginning. On viewport-repaint hosts it forced a pointless full-viewport repaint. requestResizeRender() now forces only when the terminal dimensions actually changed since the last committed frame; same-size events fall through to a plain diff render (a no-op when nothing changed). Genuine resizes, viewport-repaint hosts, and the explicit forced-redraw paths (Ctrl+Z resume, external editor) are unaffected. Regression test proves the unguarded path emitted 2J/H/3J plus a full 60-line transcript replay on a same-size resize event.
…#2815) Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…Yeachan-Heo#2813) * fix(ai): degrade a non-base64 Anthropic image payload to text A resident image externalized to a content-addressed blob is referenced by a `blob:sha256:` sentinel. When the blob goes missing, session materialization bakes a human-readable placeholder into the image content block's `data` (`{type:"image", data:"[Session resident imageData blob missing: …]", mimeType}`). `convertContentBlocks` forwarded that `data` straight into the Anthropic wire block `source.data`, so the request carried a non-base64 image payload and the API rejected the whole request: 400 invalid_request_error: messages.N…image.source.base64: invalid base64 data Because the poisoned image lives in the persisted tool_result, every subsequent turn re-sends it and fails identically — the session is bricked, even for a plain text message. Guard the wire format: an image whose `data` is not standard base64 is degraded to a text block (its placeholder is preserved so the model still sees the context) instead of being sent as an image. Valid base64 images are unchanged. Adds regression tests: a missing-blob placeholder image degrades to text (no `source`, no invalid base64), and a valid base64 image is preserved as an image block. * fix(ai): strict base64 validation before emitting an Anthropic image Address review on the previous head: the alphabet-only check accepted malformed payloads (`"a"`, `"abcde"`, misplaced/overlong padding, `length % 4 === 1`), which would still be emitted as `source.data` and rejected by the API with the same 400 `invalid base64 data`. Validate standard (RFC 4648) base64 structurally — correct quartet grouping and trailing `=`/`==` padding — accepting both canonical padded and unpadded forms, and rejecting whitespace, data URLs, URL-safe (`-`/`_`) alphabets, misplaced or overlong padding, `length % 4 === 1`, prose, and empty input. The pattern has no nested quantifier, so oversized inputs are rejected in linear time. Valid images keep their block order and MIME; only non-base64 payloads degrade to text. Expands tests to cover valid padded/unpadded/oversized payloads and degradation of whitespace, data URLs, URL-safe forms, malformed padding/length, prose, empty, and oversized inputs, plus block-order/MIME preservation — all via the production `convertAnthropicMessages` converter. * style(ai): format base64 degradation regression --------- Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
* fix(session): verify Windows ACLs before startup repair * chore(telegram): refresh native authority attestations * fix(telegram): advance daemon generation for ACL authority * fix(chat): advance daemon generations for ACL authority * fix(natives): satisfy native authority checks * chore(telegram): refresh final native digest --------- Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…Heo#2810) * docs(models): declare vision input for custom OpenAI-compatible models Custom models that only set `id` default to input: [text], so GJC strips images with "[image omitted: model does not support vision]" even when the upstream host (e.g. Aliyun Token Plan qwen3.8-max-preview) supports vision. Document the required input: [text, image] declaration and lock the default behavior with focused ModelRegistry tests. * chore(docs): regenerate docs index for models vision note docs/models.md changed; check:public-sync requires docs-index.generated.ts to match. Fixes Public site sync CI on Yeachan-Heo#2810.
Map immutable Anthropic cache-eval evidence to its focused affected-path test.
…Yeachan-Heo#2821) formatBytes clamped the terminal GB unit below the next-unit boundary that only KB/MB need, so every value >= 1 TiB rendered as "1023.9GB" (e.g. 2 TiB -> "1023.9GB" instead of "2048.0GB"). formatDuration's seconds branch used toFixed(1), which rounds any ms in [59950, 59999] up to "60.0s" instead of staying below the minute unit. Skip the clamp on the terminal GB unit and route the seconds branch through the existing roundBelow helper (already used by formatNumber for this exact invariant). Add regression tests covering both boundaries and the preserved sub-boundary behavior.
…#2831) * fix(notifications): tailor uncertain lifecycle guidance - Pass the originating lifecycle verb into Telegram outcome formatting. - Keep duplicate-start warnings on create while describing close and resume uncertainty accurately. - Add command-specific regression coverage and an Unreleased changelog entry. Tests: - bun test packages/coding-agent/test/notifications-lifecycle-commands.test.ts packages/coding-agent/test/notifications-lifecycle-command-routing.test.ts packages/coding-agent/test/notifications-lifecycle-orchestrator.test.ts - bunx biome check targeted files - git diff upstream/dev...HEAD --check * docs(changelog): reuse unreleased fixed section
* feat: add ephemeral retained multi-turn /btw-r side chat Keep one-shot /btw unchanged and add /btw-r as a same-panel temporary thread that reuses the BTW controller/panel path without writing the main session transcript. Plain editor text becomes follow-ups while open, slash commands still dispatch normally, tools stay off, and Esc closes the retained panel. * fix: keep failed /btw-r turns in retained context and satisfy biome Failed retained turns now append the user question plus an error assistant message into contextMessages so follow-ups like "try again" still see the failed question. Also fix biome optional-chain/format issues that broke CI path validation. * fix: keep slash skills on follow-up path while /btw-r is open Mirror Enter routing in handleFollowUp so slash-origin text (including /skill:*) is not captured into the retained side thread. Adds a regression test for the follow-up keybinding path. * fix: satisfy typecheck for retained slash-skill routing test Cast the harness context through unknown when stubbing skillCommands and promptCustomMessage so package check:types accepts the regression test. * fix: capture non-slash /btw-r text before inline skill parse While a retained /btw-r thread is open, plain follow-ups that merely mention an embedded /skill: token must stay in the side chat. Move the non-slash retained capture ahead of #invokeSkillCommand and add an Enter path regression test. * fix: isolate btw-r retained content flow * refactor: make btw multi-turn by default * fix: enforce btw privacy lifecycle boundaries * fix: close btw privacy review gaps * chore: retrigger successor PR checks --------- Co-authored-by: project820 <239489026+project820@users.noreply.github.com>
…eachan-Heo#2760) DeepSeek V4 (accessed through OpenRouter) rejects `strict: true` on function tool definitions with a 400 error even when the parameter schema is valid JSON Schema draft 2020-12. Scope the fix to the canonical compatibility boundary: in detectOpenAICompat, suppress supportsStrictMode when the model family is DeepSeek AND the provider is OpenRouter. This keeps strict mode enabled for: - non-DeepSeek models via OpenRouter (Claude, GPT, etc.) - DeepSeek direct API (api.deepseek.com) - DeepSeek via other providers (NVIDIA NIM, etc.) Only the OpenRouter + DeepSeek combination is affected.
…-2835-btw-test-contracts fix: route notification BTW through side-turn contract
…an-Heo#2755) The compiled stats server previously extracted its embedded dashboard bundle into a predictable cross-process filesystem location. Parse validated archive entries into an in-memory Blob map instead, serve assets directly from that map, and build the embedded archive without a temporary archive file. Constraint: Exact gzip bytes remain mtime-dependent, which is a pre-existing generator property; verification targets archive content and canonical reset cleanliness Rejected: Harden the shared extraction directory | a cross-process filesystem boundary would remain in the compiled request path Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep compiled dashboard requests backed by validated in-memory assets and do not reintroduce shared extraction paths Tested: focused compiled-client-assets tests (4 pass); all stats tests (37 pass); stats Biome and both TypeScript configs; public sync; generator generate/reset with exact placeholder restoration; git diff --cached --check Not-tested: End-to-end HTTP smoke through a compiled dashboard binary
Fixes Yeachan-Heo#2812. Exact head `377ff98351eff85777b7a78cbf5a0df19af40578` passed 25/25 hosted checks and independent CLEAR safety review. — *[repo owner's gaebal-gajae (clawdbot) 🦞]*
…legacy-resume-oom # Conflicts: # packages/coding-agent/test/notifications-telegram-btw-e2e.test.ts # packages/coding-agent/test/resume-confirm-continue.test.ts
…-2834-legacy-resume-oom fix: reduce managed legacy-resume memory amplification
…suites (Yeachan-Heo#3082) Six telegram test suites merged on dev (Yeachan-Heo#2984 / Yeachan-Heo#2958 / Yeachan-Heo#2959) without regenerating test/manifests/telegram-baseline-v1.json, so the exact-match gate in check:sdk-closure fails on pristine dev for everyone. Regenerated via bun scripts/generate-telegram-baseline-manifest.ts (55 commands).
Co-authored-by: jasonyooyj <47925973+jasonyooyj@users.noreply.github.com>
* fix(tui): expose provider retry recovery state (Yeachan-Heo#3071) * fix(tui): repair provider retry recovery state * fix(tui): harden retry rendering edge cases * fix(tui): reject cloned retry replay events --------- Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…et (Yeachan-Heo#3068) * fix(coding-agent): isolate tombstone reconciliation failures per-target reconcileManagedTombstones() iterated every tombstone in a scope with no per-target error boundary: one target throwing (e.g. identity_mismatch on a stale artifact directory) propagated out of the loop entirely, failing prepareManagedSessionScopeForWrite for the whole scope and blocking resume/delete of every other, unrelated session in that scope. This function is documented as best-effort GC of already-retired sessions ("without restoring retired candidates"), so a single unresolvable tombstone should not be able to block access to live sessions. Wrap each target's reconciliation in a try/catch that leaves the tombstone for a future attempt instead of aborting the whole scope. Reproduced on Windows 11 / GJC 0.11.8: a session whose delete got stuck on an identity_mismatch during artifact detach left every other session in the same managed scope unable to resume or delete. * docs(changelog): note tombstone scope isolation fix Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(session): cover tombstone isolation session-identity gate Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs(changelog): drop unfilled issue-number placeholder Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix(tui): collapse and redact bash invocations (Yeachan-Heo#3072) * fix(tui): harden bash invocation rendering * fix(tui): redact interactive bash headers * fix(tui): close shell redaction bypasses * fix(tui): redact escaped shell invocations * fix(tui): preserve nested shell substitution spans --------- Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…erations epoch, marker-free heartbeat, root GC, 429 cooldown, lazy topics (gen 27) (Yeachan-Heo#3048) * feat(notifications): servingEpoch serve-across-generations compatibility + fail-closed legacy gen-absent row (WS2/D4) * fix(notifications): epoch-gated reload requires signalable provenance; preserve dead legacy reclaim; epoch-era test fixtures * fix(notifications): cooldown attach requires signalable ready provenance (TOCTOU) * test(notifications): route malformed servingEpoch fixtures through an explicit unknown boundary * fix(notifications): renew daemon ownership through fenced sidecar * test(notifications): cover steady sidecar heartbeat freshness * fix(notifications): route daemon liveness through sidecar freshness * test(notifications): post-TTL sidecar acceptance and steady-renewal zero-growth coverage * fix(notifications): corrupt or unreadable heartbeat sidecar degrades to state-floor freshness * fix(notifications): harden daemon heartbeat ownership * test(notifications): stale-rename-after-steal window stays inert and successor self-heals * fix(notifications): release managed telegram roots on teardown * fix(notifications): honor Telegram flood-control cooldowns * test(notifications): cover Telegram flood cooldown * test(sdk): cover setup root isolation and corrupt markers * test(notifications): transiently unreadable root is kept and does not block live-root scanning * fix(telegram): handle cooldown suppression and root cleanup * test(telegram): cover root cleanup lifecycle retries * chore(telegram): bump guarded daemon generation * test(telegram): cover generation convergence harnesses * fix(telegram): fail closed during reload cooldown * test(telegram): bridge lifecycle pollers in D6 * test: align telegram daemon control fixtures * style: apply biome formatting to telegram structural-fix surfaces * test(notifications): tolerate platform-dependent clock-read counts in the cooldown successor test * fix(telegram-daemon): resolve six Yeachan-Heo#3048 review blockers (1) replace banned ReturnType<> with exported OwnerFreshnessSnapshot; (2) joinExclusive uses Promise.withResolvers instead of raw new Promise; (3) cooldown-suppressed flush grants requeue untouched and settle ambiguous (retry after cooldown), and mid-item suppression flips an otherwise-accepted grant to ambiguous via a suppressed-call counter; (4) readiness gates on serving-epoch compatibility with a safe-integer generation check, attaching same-epoch cross-generation owners; (5) a failed initial heartbeat-sidecar proof after ready publication rolls the state forward to retired and exact-unlinks the rebound ownership lock; (6) per-registration tokens fence root unregister/rollback/prune end-to-end so a stale same-session cleanup never deletes a live replacement registration, with legacy token-less registrations keeping root-match behavior; also fix an optional-call short-circuit that skipped registration when no onRegistered callback was provided. Add adversarial regression tests for each blocker, refresh the telegram-daemon generation guard manifest and the telegram baseline test manifest, and record the fix in the changelog. * fix(telegram-daemon): close adversarial review blockers * fix(telegram): fence removal and classify per-call outcomes * fix(telegram): fence last-root daemon stop --------- Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…Yeachan-Heo#3109) * feat(agents): give read-only role agents irc and read-only git access `architect`, `planner`, and `critic` pin an explicit `tools:` allow-list, so they only receive the tools they name. That excluded `irc`, and their `bashAllowedPrefixes` allowed only `gjc ralplan --write` / `gjc state`, so every git command was blocked. - add `irc` to the three agents' tool lists (`executor` and generic subagents declare no `tools:` frontmatter and already inherit `irc` from the builtin set, so they are intentionally untouched) - add read-only git prefixes: status, log, show, diff, blame, rev-parse, ls-files. These agents use the workflow bash profile, where `validateMatchedGjcCommand` passes any non-`gjc` command once a prefix matches, so mutating git (commit/push/reset/checkout/branch -D/config) and arbitrary shell stay blocked and the read-only contract holds. - sync AGENTS.md and the shared restricted-bash prompt fragment, which both documented the old `gjc`-only contract - add a behavioral regression test driving the real bundled agent definitions through `checkBashAllowedPrefixes`, rather than only pinning the prefix array `web_search` was already present in all three lists; it is now asserted. * fix(task): keep irc active for subagents instead of hiding it behind discovery `irc` is `loadMode: "discoverable"`, and `tools.discoveryMode` defaults to `"all"`, so the initial-tool filter in `createAgentSession` dropped it from every subagent that does not pin an explicit `tools:` list — notably `executor`. The tool was constructed and reachable, but only after the model spent a `search_tool_bm25` round-trip to find it, which is the wrong tradeoff for a coordination channel the agent needs proactively. - add `CreateAgentSessionOptions.alwaysActiveToolNames`: discoverable built-ins that stay in the initial active set even under `discoveryMode: "all"` - pass `["irc"]` from the subagent executor when the parent runtime reports IRC is actually available (`ircAvailable`), so behavior still follows the existing `irc.enabled` + peer-roster gating - cover it with a session-level regression test asserting irc is hidden by default for a subagent-style session, active when forced, and that forcing one tool does not drag in the rest of the discoverable set Read-only role agents are unaffected: they pass explicit tool names, so irc already survived the filter via the explicitly-requested path. --------- Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…h race (Yeachan-Heo#3111) * test: remediate 11 confirmed-flaky tests and 1 superseded member; fix broker identity publish race Phase 2 of the flaky-CI stabilization program (audit-scoped, approval-gated): - sdk/broker/identity.ts: atomic identity publication via same-directory temp file + hard link with EEXIST reread (real product race reproduced under 4x parallel contention: "Invalid broker identity key") - sdk-broker.test.ts: new concurrent identity-publication regression test - 11 flaky-test rewrites fixing adjudicated root causes (deterministic awaits over fixed sleeps, fixture isolation, lease/heartbeat seams, bounded polling, mkdtemp AuthStorage isolation, scoped timeouts) - session-directory.test.ts: remove one superseded append-only-sidecar member (superseded by retained-cleanup contract, c49c259) Evidence chain: 62-suspect audit (2-week window, 3 reconciliation identities, blinded second validation, third-adjudicator protocol), 24 verification receipts, mutation-probe matrix, 220/220 stress + 80/80 contention iterations. No workflow/branch-protection changes; dev PR CI critical path untouched. * style: apply biome formatting to Phase 2 remediation test edits Formatter-only (line wrapping); no logic change. Fixes the check:@gajae-code/coding-agent CI failure on this branch. --------- Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…sh (Yeachan-Heo#3115) Follow-up to Yeachan-Heo#3109, which gave `architect`/`planner`/`critic` read-only git but had to document a workaround: `git diff HEAD~1` was rejected and had to be quoted as `git diff 'HEAD~1'` (or rewritten as `HEAD^` / an explicit SHA). The restricted-bash parser treated `~` as an unsafe expansion character anywhere in a command. Bash only performs tilde expansion when the tilde opens a word, so `HEAD~1` is a literal argument and the rejection was a false positive against ordinary git revision syntax. `~` is now checked positionally instead of unconditionally: word-initial tildes (`~`, `~/path`, `~user`) are still rejected because bash really does expand them, while a tilde inside a word is allowed. Every other expansion character (`$`, `*`, `?`, `[`, `]`, `{`, `}`), command substitution, control operators, and backslash escapes are unchanged. Verified against real bash: `HEAD~1`, `HEAD~2`, `HEAD~1..HEAD`, `HEAD~5`, and `main...HEAD` all print literally, while `~`, `~/secrets`, and `~root` expand to home directories and therefore stay blocked. Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…an-Heo#3117) (Yeachan-Heo#3121) * fix(security): reject bash tilde expansion in assignment words (Yeachan-Heo#3117) Follow-up repair to Yeachan-Heo#3115, which made the restricted role-agent bash parser treat `~` positionally but only rejected it at the start of a whitespace- delimited word. Bash also performs tilde expansion inside an assignment word: directly after the first `=` and after each `:` in the assigned value, so `A=~`, `foo=~root/bar`, `A=x:~`, and `a=x:~:y:~` all expanded while the parser accepted them. `parseShellWords` now tracks per-token assignment state and rejects an unquoted `~` at every position bash can expand it: word start, right after the first unquoted `=` of an assignment word, and right after an unquoted `:` in that word's value. An assignment word requires the raw characters before the first unquoted `=` to match `[A-Za-z_][A-Za-z0-9_]*` with no quoting, so non-assignment tokens (`--opt=~`, `a-b=~`, `1abc=~`, `=~`) and non-expansion positions (`a=x~y`, `a=x:y~z`, `a=b=~`) stay allowed, as do literal mid-word git revisions such as `HEAD~1`. Token state resets at whitespace boundaries. Verified against real bash: `A=~`, `A=x:~` expand to home paths while `a=b=~`, `a=x~y`, and `HEAD~1` stay literal. Parameter expansion, command substitution, control operators, newlines, backslash escapes, and the tilde denial reason string are unchanged. Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev> * fix(security): recognize compound `+=` assignment words in restricted bash (Yeachan-Heo#3117) Adversarial review of Yeachan-Heo#3121 found a fail-open bypass: bash(1) defines an assignment word as both `name=value` and `name+=value`, and both tilde-expand in the value, but `ASSIGNMENT_NAME_PATTERN` was tested against the region before the first unquoted `=`, which holds `A+` for `A+=`. `A+` failed the name pattern, so `tildeExpandable` was never armed and `A+=~`, `A+=x:~`, and `A+=~root/bar` were allowed through despite real bash expanding all three. The assignment-name pattern now accepts an optional single trailing `+`, so a compound assignment word arms tilde expansion on exactly the same positions as a plain one, including the `:` continuation in the value. Nothing else changes: `a++=~`, `+=~`, and `a+b=~` still fail name validation and stay allowed, as do `a+=x~y` and `a+=b=~` where bash does not expand. Verified against bash 5.1.16 with HOME=/HOMEDIR: `A+=~` and `A+=x:~` expand, while `a+=x~y`, `a+=b=~`, `a++=~`, `+=~`, and `a+b=~` stay literal. The denial reason string is unchanged. Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev> --------- Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…eo#3122) * ci: add non-required rehearsal dispatch mode to release CI The stabilization plan requires proving the release graph is green without waiting for a real tag (tags are non-deterministic and must not gate a soak). Every ci.yml job is already switched on `startsWith(github.ref, 'refs/tags/v')`, so a workflow_dispatch rehearsal mode reuses the EXACT production job definitions in place — no reusable-workflow extraction, no drift risk, and job IDs / check context names are unchanged. - `workflow_dispatch` input `rehearsal`: tag-build-verify | main-nontag - tag-graph jobs (native, binaries) additionally run under dispatch:tag-build-verify; non-tag jobs under dispatch:main-nontag - publish stays tag-only AND is explicitly excluded from dispatch (`github.event_name != 'workflow_dispatch'`), so NPM_TOKEN — referenced only inside publish, with no `secrets: inherit` anywhere — is unreachable from any rehearsal - concurrency group gains a dispatch-mode suffix so a rehearsal can never cancel a real release run Behavior for push-main, pull_request, and push-tag is unchanged: the added clauses only constrain workflow_dispatch. Release-policy and publish-order guard tests updated accordingly (35 tests green). * test(ci): expect test:@gajae-code/stats in the native-change push plan packages/stats has a `test` script and a real test/ directory, so the planner correctly emits test:@gajae-code/stats for full push plans. The hardcoded expectation in the native-workspace planning test was stale and fails on clean dev — this PR's workflow edit merely routes the selftest selector into the plan, which surfaced it. Verified: fails identically at origin/dev without this change. --------- Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…view state (Yeachan-Heo#3127) Two defects made the deep-interview workflow unusable in practice. **Guard blocked `/dev/null` redirects.** `extractBashTargets` captured every `>`/`>>` target, so `cmd 2>/dev/null` was treated as a repository write and blocked during any planning phase — including the brownfield exploration deep-interview itself mandates before Round 1. Only `/dev/null` is exempted. `/dev/stdout`, `/dev/stderr`, and `/dev/fd/<n>` stay blocked: they are descriptor aliases, and `exec 1<>src/product.ts; printf x >/dev/stdout` reaches a real file through a rebound descriptor. Suppressing a sink target must not turn a mutating command into an empty-target "safe" verdict, so this also closes the bypasses that suppression would otherwise open: - `exec` redirections fail closed (descriptor rebinding is not statically resolvable) - `>|`, `>&path`, and `<>` write forms are recognized - path-qualified writers (`/bin/dd`, `/usr/bin/tee`) are matched - every `dd of=` operand is inspected, not just the first (GNU dd honors the last) - a redirect capture that dequotes to nothing is `unknown`, not safe **Hook-seeded state failed native validation.** The draft CLI ran `validateDeepInterviewV1Envelope` whenever `verifyWorkflowEnvelopeReceiptValue` returned `native-valid`, but that verdict covers receipt shape and checksum only — it does not imply a native v1 body. The hook's minimal `ModeState` has no `schema_version` and no `state`, so every typed operation failed with `DI_STATE_SCHEMA_INVALID` and interviews ran fully manually with `rounds: []`. Gate on `isNativeDeepInterviewV1` instead, matching the already-correct `transformGuardedWorkflowEnvelopeAtomic`, and seed a native v1 envelope from the hook. The seed omits `initial_idea`: pre-seeding `""` made a later `initialize-context` carrying a real idea fail with `DI_SETUP_CONFLICT`. Round shells now canonicalize the agent-supplied `deepInterview.dimension` label, which is free text on post-topology asks but must be a canonical id in persisted state. .gjc/** writes remain blocked in every case. Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
* Keep ACP eval execution inside the established permission boundary Eval-triggered JavaScript and Python tool dispatch must pass through the same prepared session tool path as model-issued calls, so ACP policy remains authoritative while recursion and lifecycle ownership stay centralized. This preserves the accepted implementation direction from the closed contribution chain without unrelated repairs. Constraint: Fresh replacement is based exactly on upstream/dev e3c7f6a. Constraint: Publication remains gated on terminal-success exact-dev CI plus explicit owner authorization. Rejected: Add a second eval-specific authorization layer | it would duplicate policy and risk disagreement with the canonical session boundary. Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep nested eval tool dispatch routed through getToolForExecution; raw registry lookup would bypass ACP session policy. Tested: Exact six-file ACP/SDK/Python integration set (98 tests, 488 assertions); coding-agent Biome and TypeScript checks; operation inventory generation and --check; native addon build; production binary build; root/source and compiled-binary CLI smokes; isolated compiled-binary and source-linked install smokes; git diff, stable patch-id, and merge-tree checks. Not-tested: Hosted-only Linux x64 tarball install substep requiring the CI-provided pi_natives.linux-x64 addon. Related: Yeachan-Heo#2737, Yeachan-Heo#2980, Yeachan-Heo#2992 * docs(changelog): move ACP eval permission entry into Unreleased The Yeachan-Heo#2737 sweep note required the changelog entry to land in the current Unreleased/Fixed section. At head 0ae2315 it was appended to the already released [0.11.8] block instead, retroactively editing a shipped release section. Moved verbatim into [Unreleased] > Fixed; no other change. Co-authored-by: Oreochococukie <Oreochococukie@users.noreply.github.com> --------- Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev> Co-authored-by: Oreochococukie <Oreochococukie@users.noreply.github.com>
The workflow-level `permissions: contents: write` grant was redundant: `publish` is the only job that needs write and already declares its own job-level `contents: write` override. The workflow-level grant handed a write-scoped GITHUB_TOKEN to every build/verify job, including the new rehearsal dispatches, which only check out and build. No job outside `publish` references GITHUB_TOKEN, github.token, `gh`, or `git push`, so narrowing the default is behavior-neutral for real push/PR/tag runs while removing unnecessary write scope from rehearsals. Surfaced by an independent audit of the stabilization program. Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…ancellation/timeout regression (successor to Yeachan-Heo#3112) (Yeachan-Heo#3131) * fix(vendor/insane-search): P0 — 429 rate-limit no longer kills fetch + bias_check violations Problem 1: RATE_LIMITED was in TERMINAL_NONSUCCESS, causing a single 429 on the probe or any grid candidate to immediately terminate the entire fetch pipeline — no grid diversity, no browser fallback, no backoff. Fix: - Remove RATE_LIMITED from TERMINAL_NONSUCCESS (validators.py) - Add _rate_limit_backoff() with linear escalation (2s→10s cap) and Retry-After header support (fetch_chain.py) - Grid now skips rate-limited candidates and continues to the next TLS/referer combo instead of breaking - Browser fallback is no longer skipped on 429 Problem 2: bias_check CI gate reported violations for WAF vendor domains (funcaptcha.com) and Jina Reader (r.jina.ai) used as infrastructure. Fix: - Add funcaptcha.com, api.funcaptcha.com, r.jina.ai, jina.ai to URL_ALLOWLIST in bias_check.py with explanatory comments Verified: - bias_check → clean - TERMINAL_NONSUCCESS == {auth_required, not_found} (429 excluded) * fix(vendor/insane-search): repair two NameErrors in SessionPool.warmup + record local patches engine/transport.py SessionPool.warmup() referenced two names that do not exist in its scope: 1. allow_private — computed inline into classify_url() but never bound, then passed to _fetch_following() on the next line. 2. DEFAULT_MAX_REDIRECTS — lives in engine.safety; the sibling call site at get_or_create() already qualifies it as safety.DEFAULT_MAX_REDIRECTS. Every warmup() call that passed the safety gate raised NameError, so the per-(host, impersonate) root warmup — the step that lets a WAF sensor set a resolved session cookie before the deep request — never actually ran. engine/tests/test_u4.py failed on the vendored tree because of this. Also records all three local fixes in MANIFEST.json localPatches, per the vendoring convention, so a future re-vendor from upstream does not silently drop them. Verified: - engine/tests: 8/8 files pass (was 7/8) - engine/bias_check.py: clean - bun run ci:check:full: exit 0 * fix(vendor/insane-search): pass resp to _rate_limit_backoff on all 429 paths + probe backoff + tests Review findings addressed (PR Yeachan-Heo#3104): 1. _rate_limit_backoff(resp) now receives the actual response on every 429 path (grid AND probe), so server-provided Retry-After is read and honoured (numeric, capped at 30s). Previously resp was discarded. 2. Probe-phase 429 now triggers backoff before entering the grid, preventing the first grid candidate from hitting the same rate window. 3. Deterministic regression tests (engine/tests/test_rate_limit_backoff.py): - Retry-After header read + cap at 30s - Probe-phase 429 backoff - Grid-phase 429 passes resp - Repeated 429s bounded by attempt budget, terminate correctly - 429 does not defeat Playwright browser fallback - Linear escalation (base*1..base*5 cap) All sleeps mocked; no real delays. 4. transport.py: SECURITY NOTE clarifies the SSRF guard is resolver-level (pre-connect) only — NOT transport-bound DNS validation. Does not claim equivalence to DNS rebinding or redirect-to-private authority protection. 5. MANIFEST.json localPatches entry updated to reflect the Retry-After fix and test coverage. * fix(vendor/insane-search): clamp/validate rate-limit backoff base + cancellation/timeout regression Addresses REQUEST_CHANGES on Yeachan-Heo#3112 (bounded/cancellable backoff contract). P0: _rl_base = float(INSANE_RATE_LIMIT_BACKOFF_S) was neither validated nor capped. Verified failure modes: a non-numeric value raised ValueError inside _fetch_core; NaN produced an undefined time.sleep; inf hung time.sleep forever (defeating any timeout); a negative value raised ValueError in time.sleep; a huge value bypassed the advertised 30s bound (the cap only applied to the Retry-After header, not the base-derived delay). Fix: - Add fetch_chain._clamp_rate_limit_base(): a total parser that rejects non-numeric/NaN/infinite/negative values (falls back to the 2s default) and clamps a valid value to the 30s ceiling (_RATE_LIMIT_MAX_DELAY). - Hard-cap the final per-attempt delay to _RATE_LIMIT_MAX_DELAY with a finite/non-negative guard before time.sleep, so the bound holds end-to-end even for a huge-but-finite base, and sleeps stay short enough that an abort/KeyboardInterrupt between attempts is honoured promptly (cancellable). Tests (test_rate_limit_backoff.py): - The header claimed abort/timeout coverage but contained none. Fixed the header and added the missing deterministic regressions: * cancellation: KeyboardInterrupt raised during a backoff sleep propagates immediately and cuts the chain short (backoff does not swallow the abort); * timeout: no single sleep exceeds 30s or becomes non-finite even under a huge base AND a huge Retry-After, so a per-attempt deadline can rely on it; * base-validation: helper unit test + end-to-end huge/negative/NaN/inf/ non-numeric env cases. - All new tests fail/error against the unfixed code and pass with the fix (14 tests total, all deterministic, mocked sleep). Provenance: kept the MANIFEST.json local-patch entry and documented the hardening accurately. Boundary: the disabled production TypeScript bridge is intentionally untouched — DNS authority is not transport-bound, so that boundary stays closed. Gates: test_rate_limit_backoff.py (14), test_hardening.py (4), bias_check.py (clean), scripts/verify-insane-vendor.ts (passed). --------- Co-authored-by: dmae97 <dmae97@users.noreply.github.com> Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
…4 migration before publishing successor identity (Yeachan-Heo#3080) Gate the successor `'/tmp/gjc-local/019f97df-38e7-7000-a304-bd5a7ea8e374'` root before the successor session identity is published, across `/new` (both lease branches), `fork()`, handoff, `/resume` switchSession, and branch/tree-jump. Adversarial exact-head review approved at review #4778681080 for head a1878d7. Verification at merge time: - Per-site rotation -> gate -> publication ordering confirmed at all six sites. - Exhaustive adjudication of the full #beginSessionTransition set: clear-context, navigate-tree, and compact perform no identity rotation and correctly need no gate. - Order-inversion mutation testing proved the added invariant test is non-vacuous (fails with markerExists=false when the gate is moved after identity publication on the covered path). - Focused suites 13/13 and 66/66 pass, deterministic across 3 runs; coding-agent typecheck clean. - Exact-head CI green on a1878d7 (15 success, 5 conditional skips, 0 failures). - Current dev da7cc11 CI terminal success (24 success, 5 skipped, 0 failures). - merge-tree against current dev: no conflicts; dev's two intervening commits touch disjoint files. Non-blocking follow-ups noted in review: the pre-existing cleanup_pending sync/async marker asymmetry in internal-urls (untouched by this PR), and order-inversion regression coverage for the five gated sites the new test does not exercise.
* feat(models): repoint opus presets to claude-opus-5 Anthropic shipped claude-opus-5 (2026-07-24) with the same published envelope as claude-opus-4-8: 1M context, 128k output, effort range low..max, 5/25 pricing. Repoint the opus-related built-in presets to it. - claude-opus: default/planner/critic/architect -> anthropic/claude-opus-5 with existing effort suffixes; executor stays anthropic/claude-sonnet-5 - opus-codex: default -> anthropic/claude-opus-5:xhigh; the durable anthropic/claude-sonnet-5 planner override is unchanged - fable-opus-codex: planner/critic -> anthropic/claude-opus-5; the fable default and codex roles are unchanged - Regenerate packages/ai/src/models.json so anthropic/claude-opus-5 resolves; zero providers or models removed - Extend the generator's Claude Opus vision normalization to opus-5, which upstream shipped without image input on three variants, and replace the duplicated substring guard with a shared exact-generation parser plus a tripwire test that fails when the catalog bundles an Opus generation newer than any reviewed one - Realign preset expectation tests and update docs; measured opus-4-8 figures in docs/multi-vendor-profiles.md stay attributed to opus-4-8 rather than silently transferring to an unmeasured model * fix(models): remove unrelated opus preset catalog drift --------- Co-authored-by: Yeachan-Heo <yeachan-heo@gajae.dev>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
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
devinto the preserved welcome/composer gutter successor without squashing feature history.