Skip to content

fix: cap compaction and completion max_tokens to provider limits - #12

Merged
elkaix merged 6 commits into
mainfrom
fix/compaction-max-tokens-cap
Aug 4, 2026
Merged

fix: cap compaction and completion max_tokens to provider limits#12
elkaix merged 6 commits into
mainfrom
fix/compaction-max-tokens-cap

Conversation

@elkaix

@elkaix elkaix commented Aug 4, 2026

Copy link
Copy Markdown
Member

Related Issue

No issue filed — problem explained directly.

Problem

Context compaction fails with a provider 400 whenever the model alias has no maxOutputSize configured:

Compaction cancelled
Error: [compaction.failed] APIStatusError: 400 Error from provider (Console Go):
Upstream request failed: [invalid_request_error] Invalid max_tokens value, the valid
range of max_tokens is [1, 393216]

The compaction request resolved its completion budget without maxOutputSize, so the cap fell back to the model's full context window and was sent as max_tokens. Providers that enforce input + max_tokens <= context window (Moonshot/Console gateways, DeepSeek, Groq, and also Anthropic/OpenAI/Google under the same conditions) reject the request outright. The class of bug is provider-agnostic; compaction just hits it first because its prompt is the largest.

What changed

Port of the two upstream fixes for this (kimi-code 794db5553 and 76c643bcb), adapted to pythinker:

  • Compaction now resolves its budget with maxOutputSize ?? min(contextWindow, 128k) instead of falling through to the context window (packages/agent-core/src/agent/compaction/full.ts).
  • withMaxCompletionTokens gained optional context: OpenAI-legacy and Pythinker chat-completions providers clamp the cap to the remaining context window (maxContextTokens - usedContextTokens), and OpenAI-legacy additionally to a 128k ceiling (packages/kosong).
  • Normal turns pass the live context token count through KosongLLM so per-request caps shrink as the context fills.

Also includes a second commit: restart Homebrew-managed installs after /update (helper + activation step, doctor/preflight updates, docs). Both changes carry their own changeset; happy to split into a separate PR if preferred.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

Summary by CodeRabbit

  • New Features
    • Automatic updates can download and verify in the background, then install on the next launch.
    • Added clearer update status, failure details, pending versions, and installer logs to diagnostics.
  • Bug Fixes
    • Improved context compaction to prevent excessive completion sizes and provider overflows.
    • Workflow progress now advances smoothly, displays a Finalizing state, and aligns correctly on narrow screens.
  • Documentation
    • Updated upgrade, configuration, environment-variable, and data-location guidance for the revised update behavior.

elkaix added 3 commits August 4, 2026 01:29
Per-agent progress previously jumped through fixed stage values and
capped at 90 for the entire finalizing stream. Each model delta now
creeps toward the stage ceiling with a minimum step, the aggregate
line shows Finalizing once every delegated agent is terminal but the
result has not arrived, and narrow-width member rows pad the state
token so the task column no longer shifts between phases.
Compaction requests sent max_tokens equal to the model's full context
window whenever maxOutputSize was not configured, which strict
OpenAI-compatible providers reject with 400 invalid_request_error
(e.g. "Invalid max_tokens value, the valid range of max_tokens is
[1, 393216]"). Cap compaction output at 128k by default and size
chat-completions caps to the remaining context window.
…e preflight

Add Homebrew install detection with an update helper and activation
step so brew-managed CLIs restart onto the new version after /update,
and update the doctor, preflight, and docs accordingly.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8e0455bb-2cb8-4fd3-8201-2a4e9f76dff2

📥 Commits

Reviewing files that changed from the base of the PR and between 74f9c09 and 47254a0.

📒 Files selected for processing (2)
  • apps/pythinker-code/src/cli/sub/doctor.ts
  • apps/pythinker-code/src/tui/commands/info.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/pythinker-code/src/tui/commands/info.ts
  • apps/pythinker-code/src/cli/sub/doctor.ts

📝 Walkthrough

Walkthrough

This pull request adds restart-based Homebrew updates, durable update state, context-aware completion-token caps, and event-driven Dynamic Workflow progress. It also updates diagnostics, TUI messages, tests, documentation, and release changesets.

Changes

Update lifecycle

Layer / File(s) Summary
Update state and durable storage
apps/pythinker-code/src/cli/update/{types,install-state,install-lock}.*, apps/pythinker-code/src/utils/*
Update state now tracks prepared Homebrew updates, operations, jobs, and failures. Lock and JSON writes use atomic and durable persistence.
Homebrew preparation and activation
apps/pythinker-code/src/cli/update/{homebrew,update-helper,preflight,activation}.*, apps/pythinker-code/src/main.ts
Homebrew updates are prepared by detached helpers, verified against immutable metadata, activated at startup, and followed by a CLI relaunch.
Diagnostics and user-facing update flows
apps/pythinker-code/src/cli/sub/doctor.ts, apps/pythinker-code/src/tui/..., docs/configuration/*, docs/reference/*, .changeset/*
Doctor output, TUI notices, configuration references, and upgrade documentation describe automatic modes, prepared versions, restart installation, failures, and logs.

Completion budget

Layer / File(s) Summary
Context-aware completion contracts
packages/agent-core/src/agent/*, packages/agent-core/src/utils/completion-budget.ts, packages/kosong/src/provider.ts, packages/agent-core/test/utils/*
Agent requests pass current context usage to completion-budget calculation. The budget calculation reduces output capacity to the remaining context window.
Compaction and provider caps
packages/agent-core/src/agent/compaction/full.ts, packages/kosong/src/providers/*, packages/agent-core/test/agent/*, packages/kosong/test/*
Compaction uses a default 128K output cap. Providers clamp requested output to minimum and transport-specific limits. Regression tests cover constrained and unknown context sizes.

Dynamic Workflow progress

Layer / File(s) Summary
Event-driven progress rendering
apps/pythinker-code/src/tui/components/messages/dynamic-workflow-mission-control.ts, apps/pythinker-code/src/tui/constant/rendering.ts
Progress now creeps toward event-specific ceilings. Aggregate rendering shows Finalizing after terminal members complete but before the result arrives. Narrow rows use aligned columns.
Workflow validation
apps/pythinker-code/test/tui/components/messages/*, .changeset/dynamic-workflow-progress-stall.md
Tests cover progress ceilings, Finalizing transitions, out-of-band members, percentage display, and narrow-width alignment.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Preflight
  participant UpdateHelper
  participant Homebrew
  participant InstallState
  CLI->>Preflight: request Homebrew update
  Preflight->>InstallState: record preparation job
  Preflight->>UpdateHelper: launch detached preparation
  UpdateHelper->>Homebrew: prepare and verify artifact
  Homebrew-->>UpdateHelper: return prepared metadata
  UpdateHelper->>InstallState: persist pending update
  CLI->>InstallState: load pending update at startup
  CLI->>Homebrew: activate verified update
  Homebrew-->>CLI: return activated executable
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title uses the fix prefix, imperative mood, stays within 72 characters, and describes the main completion-token cap change.
Description check ✅ Passed The description includes the required sections, explains the problem and changes, and confirms tests, changesets, documentation, and contribution checks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Aug 4, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@pythoughts/pythinker-code@47254a0
npx https://pkg.pr.new/@pythoughts/pythinker-code@47254a0

commit: 47254a0

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (7)
apps/pythinker-code/src/cli/update/preflight.ts (1)

571-583: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Narrow the operation parameter to the helper's accepted values.

operation is typed as string. Only 'prepare-homebrew' is ever passed, and the helper validates the value with PrepareHomebrewArgsSchema. A literal union would make a typo a compile error instead of a runtime helper rejection.

♻️ Proposed change
 function updateHelperCommand(
-  operation: string,
+  operation: 'prepare-homebrew',
   jobId: string,
   version: string,
   requestedBy: UpdateRequestOrigin,
 ): SpawnCommand {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/pythinker-code/src/cli/update/preflight.ts` around lines 571 - 583,
Update the operation parameter of updateHelperCommand to a literal type
containing the helper’s accepted operation value, currently 'prepare-homebrew',
so invalid operations fail at compile time while preserving the existing command
construction.
apps/pythinker-code/test/cli/update/preflight.test.ts (1)

488-497: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the exact helper argument array, not a subset.

Both tests use expect.arrayContaining. That matcher ignores order and position. runUpdateHelper destructures the arguments positionally with const [, jobId, requestedVersion, requestedBy] = parsed.data, so order is part of the contract. A reordered or shifted argument list would still pass these assertions.

Assert the full array so the positional contract is covered.

♻️ Proposed change for the preflight test
     expect(mocks.spawn).toHaveBeenCalledWith(
       process.execPath,
-      expect.arrayContaining([
-        '__update_helper',
-        'prepare-homebrew',
-        '0.5.0',
-        'automatic',
-      ]),
+      [
+        process.argv[1],
+        '__update_helper',
+        'prepare-homebrew',
+        expect.any(String),
+        '0.5.0',
+        'automatic',
+      ],
       expect.objectContaining({ detached: true, stdio: 'ignore' }),
     );
♻️ Proposed change for the manual-update test
     expect(mocks.spawn).toHaveBeenCalledWith(
       process.execPath,
-      expect.arrayContaining(['prepare-homebrew', '0.5.0', 'manual']),
+      [
+        process.argv[1],
+        '__update_helper',
+        'prepare-homebrew',
+        expect.any(String),
+        '0.5.0',
+        'manual',
+      ],
       expect.objectContaining({ detached: true, stdio: 'ignore' }),
     );

Also applies to: 1547-1551

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/pythinker-code/test/cli/update/preflight.test.ts` around lines 488 -
497, Update both spawn assertions in the preflight and manual-update tests to
use an exact array matcher for the helper arguments instead of
expect.arrayContaining. Preserve and verify the expected positional order used
by runUpdateHelper’s parsed.data destructuring, while keeping the existing spawn
options assertion unchanged.
apps/pythinker-code/src/cli/update/install-state.ts (1)

30-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace both z.string().uuid() calls with z.uuid(). Zod 4 deprecates the method form.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/pythinker-code/src/cli/update/install-state.ts` at line 30, Update both
UUID schema definitions in the install-state validation to use Zod 4’s
standalone z.uuid() form instead of the deprecated z.string().uuid() method,
while preserving their existing optionality and validation behavior.
apps/pythinker-code/src/cli/update/activation.ts (1)

108-134: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use pending.source instead of the hardcoded literal.

Line 119 writes source: 'homebrew'. pending.source already carries that value and is the literal 'homebrew' in UpdatePreparedHomebrew, so behavior is identical today. Reading it from pending keeps the record correct if a second prepared source is added later.

♻️ Proposed change
       active: {
         version: pending.version,
-        source: 'homebrew',
+        source: pending.source,
         operation: 'activate',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/pythinker-code/src/cli/update/activation.ts` around lines 108 - 134,
Update the activatingState construction in the activation flow to set
active.source from pending.source instead of the hardcoded 'homebrew' literal,
while leaving the rest of the state transition unchanged.
apps/pythinker-code/test/cli/update/update-helper.test.ts (1)

18-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use one module specifier style for the mocked module.

Line 18 mocks '../../../src/cli/update/homebrew' while line 20 imports the actual module through '#/cli/update/homebrew'. Both resolve to the same file today, so the mock applies. Using the alias in both places removes the dependence on that resolution equivalence.

♻️ Proposed change
-vi.mock('../../../src/cli/update/homebrew', async () => {
+vi.mock('`#/cli/update/homebrew`', async () => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/pythinker-code/test/cli/update/update-helper.test.ts` around lines 18 -
21, Update the vi.mock declaration in the update-helper test to use the same
'`#/cli/update/homebrew`' module specifier already passed to vi.importActual,
keeping both references consistent and preserving the existing mock behavior.

Source: Coding guidelines

apps/pythinker-code/src/cli/update/homebrew.ts (1)

90-127: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a timeout to brew invocations.

runHomebrewCommand waits for close with no time bound. brew update, brew fetch, and brew upgrade can stall on network or lock contention. The detached helper holds active state for the job while it waits, so a stalled child blocks later prepare attempts until the state is cleared elsewhere.

Add an optional timeout and kill the child when it expires.

♻️ Proposed timeout support
 export interface HomebrewCommandOptions {
   readonly capture?: boolean;
   readonly inheritOutput?: boolean;
   readonly env?: NodeJS.ProcessEnv;
   readonly logFile?: string;
+  readonly timeoutMs?: number;
 }
       const child = spawn('brew', [...args], {
         cwd: homedir(),
         env: { ...process.env, ...options.env },
         stdio: ['ignore', 'pipe', 'pipe'],
+        timeout: options.timeoutMs,
+        killSignal: 'SIGTERM',
       });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/pythinker-code/src/cli/update/homebrew.ts` around lines 90 - 127, Update
runHomebrewCommand to accept an optional timeout, start a timer for each spawned
brew child, and terminate the child when the timeout expires so the promise
settles and active job state is released. Clear the timer when the child exits,
while preserving existing stdout, stderr, and normal completion handling.
apps/pythinker-code/src/cli/update/update-helper.ts (1)

15-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use z.uuid() for the UUID schema. The top-level Zod 4 format matches the RFC 4122 v4 IDs generated by randomUUID().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/pythinker-code/src/cli/update/update-helper.ts` around lines 15 - 20,
Update PrepareHomebrewArgsSchema to replace the UUID string validation with the
top-level z.uuid() schema, while preserving the existing tuple order and other
argument validations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/pythinker-code/src/cli/update/activation.ts`:
- Around line 137-151: Update the PreparedHomebrewUpdateInvalidError branch in
the activation flow to preserve the existing cumulative prepare-failure attempt
count instead of resetting attempts to 1. Reuse the prior lastFailure state when
it represents the same pending version, increment its prepare attempts, and
persist the incremented value in the invalidation result so repeated invalid
artifacts can reach AUTO_INSTALL_FAILURE_PROMPT_THRESHOLD.
- Around line 100-106: Update the failure branch in the activation flow around
activationAttempts so reaching ACTIVATION_FAILURE_LIMIT persists the terminal
pending-update state as pending: null before returning the failed status.
Preserve pending.version, the failure message, and any existing lastFailure
value needed by preflight fallback handling.

In `@apps/pythinker-code/src/cli/update/install-lock.ts`:
- Around line 127-147: Update tryAcquireUpdateInstallLock’s publication path
around link(stagedPath, filePath) to handle data directories that do not support
hard links without rethrowing an avoidable filesystem error. Preserve atomic
no-overwrite lock publication and do not replace it with direct open(filePath,
'wx'); either validate/document the hard-link requirement or implement an
alternative portable protocol, while retaining cleanup of stagedPath.

In `@apps/pythinker-code/src/cli/update/preflight.ts`:
- Around line 611-623: In the preparation state created around startedState,
preserve the existing verified pending update instead of resetting pending to
null when a new job begins. Keep the success path’s existing behavior of
replacing pending with the newly prepared update, and ensure retained pending
state remains compatible with active preparation without emitting a misleading
notice.
- Around line 640-651: Update failureAttemptsFor to match both version and
operation, then pass the relevant operation at every caller. Ensure each failure
writer supplies its own operation when calculating attempts and recording
lastFailure, so prepare, install, and activate failures maintain independent
counters.

In
`@apps/pythinker-code/src/tui/components/dialogs/update-preference-selector.ts`:
- Line 7: Update the description in the preference selector to use
source-neutral wording that applies to all installation sources; remove the
Homebrew-specific and next-launch installation claim while preserving the
automatic-update meaning.

In `@apps/pythinker-code/test/cli/update/update-helper.test.ts`:
- Around line 125-244: Update the detached-process test beginning “finishes
preparation in a detached process after its parent exits” to use
it.skipIf(process.platform === 'win32') and retain the existing 12_000 timeout
argument, so the test is skipped on Windows while preserving its current
behavior elsewhere.

In `@docs/guides/getting-started.md`:
- Line 64: Update the “Upgrade” description to match the command reference:
state that running `pythinker upgrade` checks immediately and offers to install
the update now for global npm, pnpm, yarn, bun, and macOS/Linux native
installations, while Homebrew and Windows native installations print the update
command instead. Correct the subject so the CLI, rather than the reader,
presents the option.

---

Nitpick comments:
In `@apps/pythinker-code/src/cli/update/activation.ts`:
- Around line 108-134: Update the activatingState construction in the activation
flow to set active.source from pending.source instead of the hardcoded
'homebrew' literal, while leaving the rest of the state transition unchanged.

In `@apps/pythinker-code/src/cli/update/homebrew.ts`:
- Around line 90-127: Update runHomebrewCommand to accept an optional timeout,
start a timer for each spawned brew child, and terminate the child when the
timeout expires so the promise settles and active job state is released. Clear
the timer when the child exits, while preserving existing stdout, stderr, and
normal completion handling.

In `@apps/pythinker-code/src/cli/update/install-state.ts`:
- Line 30: Update both UUID schema definitions in the install-state validation
to use Zod 4’s standalone z.uuid() form instead of the deprecated
z.string().uuid() method, while preserving their existing optionality and
validation behavior.

In `@apps/pythinker-code/src/cli/update/preflight.ts`:
- Around line 571-583: Update the operation parameter of updateHelperCommand to
a literal type containing the helper’s accepted operation value, currently
'prepare-homebrew', so invalid operations fail at compile time while preserving
the existing command construction.

In `@apps/pythinker-code/src/cli/update/update-helper.ts`:
- Around line 15-20: Update PrepareHomebrewArgsSchema to replace the UUID string
validation with the top-level z.uuid() schema, while preserving the existing
tuple order and other argument validations.

In `@apps/pythinker-code/test/cli/update/preflight.test.ts`:
- Around line 488-497: Update both spawn assertions in the preflight and
manual-update tests to use an exact array matcher for the helper arguments
instead of expect.arrayContaining. Preserve and verify the expected positional
order used by runUpdateHelper’s parsed.data destructuring, while keeping the
existing spawn options assertion unchanged.

In `@apps/pythinker-code/test/cli/update/update-helper.test.ts`:
- Around line 18-21: Update the vi.mock declaration in the update-helper test to
use the same '`#/cli/update/homebrew`' module specifier already passed to
vi.importActual, keeping both references consistent and preserving the existing
mock behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bcc013b1-dc28-4b52-973e-d5e39686292e

📥 Commits

Reviewing files that changed from the base of the PR and between 3c9cffe and 2bb9e9d.

📒 Files selected for processing (43)
  • .changeset/compaction-max-tokens-cap.md
  • .changeset/dynamic-workflow-progress-stall.md
  • .changeset/homebrew-restart-updates.md
  • apps/pythinker-code/src/cli/sub/doctor.ts
  • apps/pythinker-code/src/cli/update/activation.ts
  • apps/pythinker-code/src/cli/update/homebrew.ts
  • apps/pythinker-code/src/cli/update/install-lock.ts
  • apps/pythinker-code/src/cli/update/install-state.ts
  • apps/pythinker-code/src/cli/update/preflight.ts
  • apps/pythinker-code/src/cli/update/types.ts
  • apps/pythinker-code/src/cli/update/update-helper.ts
  • apps/pythinker-code/src/constant/app.ts
  • apps/pythinker-code/src/main.ts
  • apps/pythinker-code/src/tui/commands/info.ts
  • apps/pythinker-code/src/tui/components/dialogs/update-preference-selector.ts
  • apps/pythinker-code/src/tui/components/messages/dynamic-workflow-mission-control.ts
  • apps/pythinker-code/src/tui/constant/rendering.ts
  • apps/pythinker-code/src/utils/paths.ts
  • apps/pythinker-code/src/utils/persistence.ts
  • apps/pythinker-code/test/cli/doctor.test.ts
  • apps/pythinker-code/test/cli/main.test.ts
  • apps/pythinker-code/test/cli/update/activation.test.ts
  • apps/pythinker-code/test/cli/update/cache.test.ts
  • apps/pythinker-code/test/cli/update/preflight.test.ts
  • apps/pythinker-code/test/cli/update/update-helper.test.ts
  • apps/pythinker-code/test/tui/components/dialogs/choice-picker.test.ts
  • apps/pythinker-code/test/tui/components/messages/dynamic-workflow-mission-control.test.ts
  • docs/configuration/config-files.md
  • docs/configuration/data-locations.md
  • docs/configuration/env-vars.md
  • docs/guides/getting-started.md
  • docs/reference/pythinker-command.md
  • packages/agent-core/src/agent/compaction/full.ts
  • packages/agent-core/src/agent/index.ts
  • packages/agent-core/src/agent/turn/kosong-llm.ts
  • packages/agent-core/src/utils/completion-budget.ts
  • packages/agent-core/test/agent/compaction/full.test.ts
  • packages/agent-core/test/agent/config-state.test.ts
  • packages/kosong/src/provider.ts
  • packages/kosong/src/providers/openai-legacy.ts
  • packages/kosong/src/providers/pythinker.ts
  • packages/kosong/test/openai-legacy.test.ts
  • packages/kosong/test/pythinker.test.ts

Comment thread apps/pythinker-code/src/cli/update/activation.ts
Comment thread apps/pythinker-code/src/cli/update/activation.ts
Comment thread apps/pythinker-code/src/cli/update/install-lock.ts
Comment thread apps/pythinker-code/src/cli/update/preflight.ts
Comment thread apps/pythinker-code/src/cli/update/preflight.ts Outdated
Comment thread apps/pythinker-code/src/tui/components/dialogs/update-preference-selector.ts Outdated
Comment thread apps/pythinker-code/test/cli/update/update-helper.test.ts Outdated
Comment thread docs/guides/getting-started.md Outdated
- Centralize the remaining-context-window clamp in computeCompletionBudgetCap
  so every provider benefits, and pass usedContextTokens from compaction —
  the near-full request the cap exists for. Remove the per-provider
  MaxCompletionTokensOptions duplication from kosong.
- Clear the pending update record once the activation failure limit is
  reached so startup and /update stop reporting a dead update forever.
- Keep lastFailure across a successful prepare so invalid-artifact
  activations accumulate toward the failure threshold.
- Retain a verified pending update while a newer preparation runs.
- Scope failure-attempt increments by operation; record install failures
  with operation: 'install'.
- Return whether background prepare/install actually started and map a
  refused start to 'in-progress' instead of claiming 'started'.
- Make writeJsonFile fsync opt-in ({ durable: true }, install.json only).
- Dedupe formatErrorMessage into cli/update/format-error.ts; normalize
  appendLog chunks instead of duplicated branches; z.uuid(); source-neutral
  update-preference wording; align pythinker upgrade docs; skip the
  POSIX-only detached-helper test on Windows; exact helper spawn asserts.
@elkaix

elkaix commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

Review findings addressed in 1ea5543. Nitpicks: fixed — 'prepare-homebrew' literal type, exact helper spawn-argument asserts, z.uuid() (both files), active.source from pending.source, alias-style vi.mock specifier. Skipped — brew invocation timeout: advisory; a stalled brew holds only the per-job active record, which the existing freshness check (hasFreshActiveInstall) already expires, and adding kill-timer plumbing to the helper isn't warranted without a report.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
apps/pythinker-code/src/utils/persistence.ts (1)

76-89: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Do not fail a completed write when the directory close rejects.

Line 76 completes the atomic rename. After that point the write has succeeded. directory.sync() already tolerates failure, but directory.close() on line 86 can still reject (for example EIO). That rejection reaches the outer catch, unlink(tmpPath) finds nothing, and writeJsonFile rejects for a write that is already durable on disk. Callers such as writeUpdateInstallState then record a spurious update failure.

Swallow the close rejection to keep the post-rename path non-fatal.

♻️ Proposed fix
       const directory = await open(dirname(filePath), 'r').catch(() => null);
       if (directory !== null) {
         try {
           await directory.sync().catch(() => {});
         } finally {
-          await directory.close();
+          await directory.close().catch(() => {});
         }
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/pythinker-code/src/utils/persistence.ts` around lines 76 - 89, Update
the directory cleanup in the durable branch of writeJsonFile so
directory.close() rejection is swallowed, just like directory.sync(). Keep the
completed rename non-fatal after await rename(tmpPath, filePath), while
preserving the existing close execution in the finally block.
apps/pythinker-code/test/cli/update/preflight.test.ts (1)

1580-1615: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the retained pending update during a new preparation.

startBackgroundHomebrewPreparation keeps an existing verified pending when it starts a newer preparation. It relies on the ...freshState spread in startedState. No test asserts that behavior. A future change that sets pending: null in the started state would remove an installable update without failing any test.

Add a case that starts preparation for 0.6.0 with a verified pending at 0.5.0 and asserts the persisted started state still carries that pending.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/pythinker-code/test/cli/update/preflight.test.ts` around lines 1580 -
1615, Add a test for startBackgroundHomebrewPreparation that begins preparing
version 0.6.0 while install state contains a verified pending update for 0.5.0,
then assert the persisted started state retains that pending update. Reuse the
existing pending-state and mock helpers, and verify writeUpdateInstallState
receives the retained pending value.
apps/pythinker-code/src/cli/update/preflight.ts (1)

954-969: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Report the ready update even when the promotion write fails.

The prepared update stays installable on restart. requestedBy is ownership metadata only. If writeUpdateInstallState fails, this path returns check-failed and hides an update that is already verified and ready. The user then sees an error instead of the restart instruction.

Treat the promotion write as best effort and keep returning the ready result.

♻️ Proposed fix
     const pending = installState.pending;
     if (pending.requestedBy === 'automatic') {
-      try {
-        await writeUpdateInstallState({
-          ...installState,
-          pending: { ...pending, requestedBy: 'manual' },
-        });
-      } catch (error) {
-        return { status: 'check-failed', message: formatErrorMessage(error) };
-      }
+      // Ownership metadata only: the prepared update stays installable even
+      // when this write fails, so do not report a check failure.
+      await writeUpdateInstallState({
+        ...installState,
+        pending: { ...pending, requestedBy: 'manual' },
+      }).catch(() => {});
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/pythinker-code/src/cli/update/preflight.ts` around lines 954 - 969,
Update the automatic-promotion block in the preflight flow so failures from
writeUpdateInstallState do not return check-failed. Treat the requestedBy
promotion as best effort, preserve the error handling only as appropriate for
non-blocking reporting, and always continue to return the existing in-progress
ready-to-install result for the verified pending update.
apps/pythinker-code/test/cli/update/update-helper.test.ts (1)

125-146: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Test source and version mismatches separately.

This fixture changes both active.source and active.version. The test passes if ownsPrepareJob validates only one field. Add one case with the matching source and a wrong version. Add one case with the matching version and a wrong source. This preserves the helper ownership contract before it changes install state.

As per path instructions, “Tests must be able to fail.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/pythinker-code/test/cli/update/update-helper.test.ts` around lines 125 -
146, Split the test around runUpdateHelper into two independent cases: one with
the expected source and mismatched version, and another with the expected
version and mismatched source. Keep each case asserting that
prepareHomebrewUpdate is not called and the active state remains unchanged, so
ownsPrepareJob must validate both fields.

Source: Path instructions

packages/kosong/src/providers/openai-legacy.ts (1)

655-657: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add provider-level Vitest coverage for the changed bounds.

The supplied tests cover the central budget helper but not the concrete provider behavior.

  • packages/kosong/src/providers/openai-legacy.ts#L655-L657: test both token-parameter branches and the 128K and minimum-one boundaries.
  • packages/kosong/src/providers/pythinker.ts#L554-L555: test negative, zero, and positive completion-token values.

As per path instructions, new behavior in published library code should have Vitest coverage; extend existing suites.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/kosong/src/providers/openai-legacy.ts` around lines 655 - 657,
Extend the existing Vitest provider suites to cover
OpenAILegacyChatProvider.withMaxCompletionTokens, testing both token-parameter
branches at the 128K ceiling and minimum-one boundary; also cover Pythinker’s
completion-token handling for negative, zero, and positive values. Apply changes
in packages/kosong/src/providers/openai-legacy.ts lines 655-657 and
packages/kosong/src/providers/pythinker.ts lines 554-555, with the
implementation sites requiring no direct changes.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/agent-core/test/agent/compaction/full.test.ts`:
- Around line 1911-1913: Update the assertions around
compactionMaxCompletionTokens in the full compaction test to derive tokensBefore
with estimateTokensForMessages and assert the exact expected value Math.max(1,
maxContextTokens - tokensBefore). Replace the compile-time as number cast with a
runtime type guard that verifies the provider parameter before using it.

In `@packages/kosong/src/providers/openai-legacy.ts`:
- Around line 655-657: Preserve the exported ChatProvider contract by restoring
a deprecated compatibility overload for withMaxCompletionTokens that accepts
MaxCompletionTokensOptions, while retaining the numeric overload and existing
behavior. Ensure the options form still applies context-aware clamping using
usedContextTokens and maxContextTokens before delegating through
withGenerationKwargs, including for JavaScript callers.
- Around line 655-657: Preserve backward compatibility for
withMaxCompletionTokens in OpenAILegacyChatProvider at
packages/kosong/src/providers/openai-legacy.ts:655-657 and PythinkerChatProvider
at packages/kosong/src/providers/pythinker.ts:554-555 by restoring a deprecated
overload accepting MaxCompletionTokensOptions and retaining its runtime
context-aware clamping; alternatively, explicitly version and document the
breaking API change, including updating the package version from 0.4.6.

---

Nitpick comments:
In `@apps/pythinker-code/src/cli/update/preflight.ts`:
- Around line 954-969: Update the automatic-promotion block in the preflight
flow so failures from writeUpdateInstallState do not return check-failed. Treat
the requestedBy promotion as best effort, preserve the error handling only as
appropriate for non-blocking reporting, and always continue to return the
existing in-progress ready-to-install result for the verified pending update.

In `@apps/pythinker-code/src/utils/persistence.ts`:
- Around line 76-89: Update the directory cleanup in the durable branch of
writeJsonFile so directory.close() rejection is swallowed, just like
directory.sync(). Keep the completed rename non-fatal after await
rename(tmpPath, filePath), while preserving the existing close execution in the
finally block.

In `@apps/pythinker-code/test/cli/update/preflight.test.ts`:
- Around line 1580-1615: Add a test for startBackgroundHomebrewPreparation that
begins preparing version 0.6.0 while install state contains a verified pending
update for 0.5.0, then assert the persisted started state retains that pending
update. Reuse the existing pending-state and mock helpers, and verify
writeUpdateInstallState receives the retained pending value.

In `@apps/pythinker-code/test/cli/update/update-helper.test.ts`:
- Around line 125-146: Split the test around runUpdateHelper into two
independent cases: one with the expected source and mismatched version, and
another with the expected version and mismatched source. Keep each case
asserting that prepareHomebrewUpdate is not called and the active state remains
unchanged, so ownsPrepareJob must validate both fields.

In `@packages/kosong/src/providers/openai-legacy.ts`:
- Around line 655-657: Extend the existing Vitest provider suites to cover
OpenAILegacyChatProvider.withMaxCompletionTokens, testing both token-parameter
branches at the 128K ceiling and minimum-one boundary; also cover Pythinker’s
completion-token handling for negative, zero, and positive values. Apply changes
in packages/kosong/src/providers/openai-legacy.ts lines 655-657 and
packages/kosong/src/providers/pythinker.ts lines 554-555, with the
implementation sites requiring no direct changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d87ae561-5dcb-4081-8288-53d1b83297e7

📥 Commits

Reviewing files that changed from the base of the PR and between 2bb9e9d and 1ea5543.

📒 Files selected for processing (21)
  • apps/pythinker-code/src/cli/update/activation.ts
  • apps/pythinker-code/src/cli/update/format-error.ts
  • apps/pythinker-code/src/cli/update/homebrew.ts
  • apps/pythinker-code/src/cli/update/install-state.ts
  • apps/pythinker-code/src/cli/update/preflight.ts
  • apps/pythinker-code/src/cli/update/update-helper.ts
  • apps/pythinker-code/src/tui/components/dialogs/update-preference-selector.ts
  • apps/pythinker-code/src/utils/persistence.ts
  • apps/pythinker-code/test/cli/update/activation.test.ts
  • apps/pythinker-code/test/cli/update/preflight.test.ts
  • apps/pythinker-code/test/cli/update/update-helper.test.ts
  • apps/pythinker-code/test/tui/components/dialogs/choice-picker.test.ts
  • docs/guides/getting-started.md
  • packages/agent-core/src/agent/compaction/full.ts
  • packages/agent-core/src/utils/completion-budget.ts
  • packages/agent-core/test/agent/compaction/full.test.ts
  • packages/agent-core/test/utils/completion-budget.test.ts
  • packages/kosong/src/provider.ts
  • packages/kosong/src/providers/openai-legacy.ts
  • packages/kosong/src/providers/pythinker.ts
  • packages/kosong/test/openai-legacy.test.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/kosong/test/openai-legacy.test.ts
  • apps/pythinker-code/src/cli/update/install-state.ts
  • docs/guides/getting-started.md
  • apps/pythinker-code/src/tui/components/dialogs/update-preference-selector.ts
  • apps/pythinker-code/test/cli/update/activation.test.ts
  • packages/kosong/src/provider.ts
  • apps/pythinker-code/test/tui/components/dialogs/choice-picker.test.ts
  • apps/pythinker-code/src/cli/update/update-helper.ts
  • packages/agent-core/src/agent/compaction/full.ts
  • apps/pythinker-code/src/cli/update/activation.ts

Comment thread packages/agent-core/test/agent/compaction/full.test.ts Outdated
Comment thread packages/kosong/src/providers/openai-legacy.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/agent-core/test/agent/compaction/full.test.ts (1)

1912-1913: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the runtime type guard out of the test body.

vitest(no-conditional-in-test) flags this branch. Extract the guard into a helper outside the test so the test remains branch-free. Keep runtime validation; do not replace it with a type assertion.

This addresses the reported oxlint warning for conditionals in tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/agent-core/test/agent/compaction/full.test.ts` around lines 1912 -
1913, Extract the typeof cap runtime validation from the test body into a helper
defined outside the test, preserving the existing TypeError message and
validation behavior. Update the test to call the helper so its body contains no
conditional branch or type assertion.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/agent-core/test/agent/compaction/full.test.ts`:
- Around line 1912-1913: Extract the typeof cap runtime validation from the test
body into a helper defined outside the test, preserving the existing TypeError
message and validation behavior. Update the test to call the helper so its body
contains no conditional branch or type assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d79d866c-7f95-4b3f-bf99-f078f8cb8066

📥 Commits

Reviewing files that changed from the base of the PR and between 1ea5543 and 74f9c09.

📒 Files selected for processing (1)
  • packages/agent-core/test/agent/compaction/full.test.ts

@elkaix
elkaix merged commit 02f7f8d into main Aug 4, 2026
11 checks passed
@elkaix
elkaix deleted the fix/compaction-max-tokens-cap branch August 4, 2026 09:09
elkaix pushed a commit that referenced this pull request Aug 4, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @pythoughts/pythinker-code@0.7.0

### Minor Changes

- [#12](#12)
[`02f7f8d`](02f7f8d)
- Prepare verified Homebrew updates in the background and install them
automatically on the next interactive launch.

### Patch Changes

- [#12](#12)
[`02f7f8d`](02f7f8d)
- Fix context compaction failing with provider "Invalid max_tokens"
errors by capping requested completion tokens to the remaining context
window and a safe output ceiling instead of the full context window
size.

- [#12](#12)
[`02f7f8d`](02f7f8d)
- Fix Dynamic Workflow progress sticking at 90% during long streaming,
show a Finalizing state once all delegated agents finish, and fix member
row alignment at narrow widths.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant