Skip to content

Commit c8efdac

Browse files
committed
fix: persist a picked thinking effort up to the model's default effort
A concrete effort picked in the TUI or the VS Code webview is stored as the global default only when it does not rank above the model's effective default; a more expensive pick stays session-only.
1 parent 237c7a2 commit c8efdac

34 files changed

Lines changed: 794 additions & 105 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": patch
3+
---
4+
5+
Fix messages sent from one web client not appearing on other clients connected to the same session.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": patch
3+
---
4+
5+
Remove the `--allow-remote-terminals` flag from `pythinker web`; PTY terminal routes now stay available on loopback binds only.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": patch
3+
---
4+
5+
Persist a picked thinking effort as the default only up to the model's own default effort; a more expensive pick stays session-only.

.changeset/infinite-retry-mode.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pymodel/pythinker-code": minor
3+
---
4+
5+
Add `PYTHINKER_CODE_INFINITE_RETRY=1` to retry every failed model request indefinitely with backoff instead of failing the turn, for long unattended runs.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"pythinker": patch
3+
---
4+
5+
Persist a picked thinking effort as the default only up to the model's own default effort; a more expensive pick stays session-only.

apps/pythinker-code/src/cli/sub/web/run.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,6 @@ export function buildWebCommand(cmd: Command): Command {
134134
'On a non-loopback bind, keep POST /api/v1/shutdown enabled (default: route is disabled → 404).',
135135
false,
136136
)
137-
.option(
138-
'--allow-remote-terminals',
139-
'On a non-loopback bind, keep the PTY /api/v1/terminals/* routes enabled (default: disabled → 404). Remote shell is high risk.',
140-
false,
141-
)
142137
.option(
143138
'--dangerous-bypass-auth',
144139
'Disable bearer-token auth on every REST and WebSocket route, and advertise it via /api/v1/meta so the web UI connects without a token. Only use on a trusted network or behind your own authenticating proxy.',
@@ -300,7 +295,6 @@ async function runServerInProcess(
300295
debugEndpoints: options.debugEndpoints,
301296
insecureNoTls: options.insecureNoTls,
302297
allowRemoteShutdown: options.allowRemoteShutdown,
303-
allowRemoteTerminals: options.allowRemoteTerminals,
304298
allowedHosts: options.allowedHosts,
305299
disableAuth: options.dangerousBypassAuth,
306300
webTitle: options.webTitle,

apps/pythinker-code/src/cli/sub/web/shared.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@ export interface ParsedServerOptions {
4040
insecureNoTls: boolean;
4141
/** Allow `POST /api/v1/shutdown` on a non-loopback bind. */
4242
allowRemoteShutdown: boolean;
43-
/** Allow PTY `/api/v1/terminals/*` routes on a non-loopback bind. */
44-
allowRemoteTerminals: boolean;
4543
/** Disable bearer-token auth on every route (`--dangerous-bypass-auth`). */
4644
dangerousBypassAuth: boolean;
4745
/** Extra `Host` header values to allow through the DNS-rebinding check. */
@@ -59,8 +57,6 @@ export interface ServerCliOptions {
5957
insecureNoTls?: boolean;
6058
/** Allow remote shutdown on a non-loopback bind (`--allow-remote-shutdown`). */
6159
allowRemoteShutdown?: boolean;
62-
/** Allow remote terminals on a non-loopback bind (`--allow-remote-terminals`). */
63-
allowRemoteTerminals?: boolean;
6460
/** Disable bearer-token auth on every route (`--dangerous-bypass-auth`). */
6561
dangerousBypassAuth?: boolean;
6662
/** Extra `Host` header values to allow (`--allowed-host`). */
@@ -77,7 +73,6 @@ export function parseServerOptions(opts: ServerCliOptions): ParsedServerOptions
7773
debugEndpoints: opts.debugEndpoints === true,
7874
insecureNoTls: opts.insecureNoTls !== false,
7975
allowRemoteShutdown: opts.allowRemoteShutdown === true,
80-
allowRemoteTerminals: opts.allowRemoteTerminals === true,
8176
dangerousBypassAuth: opts.dangerousBypassAuth === true,
8277
allowedHosts: parseAllowedHostArgs(opts.allowedHost),
8378
webTitle: opts.webTitle,

apps/pythinker-code/src/tui/commands/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ async function persistModelSelection(
597597
const model = host.state.appState.availableModels[alias];
598598
const full = thinkingEffortToConfig(
599599
effort,
600-
model === undefined ? undefined : effectiveModelForHost(host, model).supportEfforts,
600+
model === undefined ? undefined : effectiveModelForHost(host, model),
601601
);
602602
// Re-confirming the effort shown when the picker opened is not an explicit
603603
// choice — persist the model but leave the stored effort preference alone.

apps/pythinker-code/src/tui/commands/provider.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -295,24 +295,31 @@ async function handleCatalogProviderAdd(host: SlashCommandHost): Promise<void> {
295295
host.mountEditorReplacement(selector);
296296
}
297297

298-
async function setDefaultModel(
298+
export async function setDefaultModel(
299299
host: SlashCommandHost,
300300
alias: string,
301301
effort: ThinkingEffort,
302302
): Promise<void> {
303303
// Resolve efforts the same way the /model path does (effectiveModelForHost
304304
// applies overrides and the protocol-profile inference): catalog entries for
305305
// e.g. Anthropic models declare no support_efforts on the alias, and without
306-
// the inference a top-tier pick would slip through as a persisted effort.
306+
// the inference an above-default pick would slip through as a persisted effort.
307307
const model = host.state.appState.availableModels[alias];
308+
const thinking = thinkingEffortToConfig(
309+
effort,
310+
model === undefined ? undefined : effectiveModelForHost(host, model),
311+
);
308312
await host.harness.setConfig({
309313
defaultModel: alias,
310-
thinking: thinkingEffortToConfig(
311-
effort,
312-
model === undefined ? undefined : effectiveModelForHost(host, model).supportEfforts,
313-
),
314+
thinking,
314315
});
315316
await host.authFlow.refreshConfigAfterLogin();
317+
// refreshConfigAfterLogin reactivates from the persisted config, so a pick
318+
// the gate keeps session-only never reaches the runtime — apply it after
319+
// the refresh, or the persisted value would clobber it.
320+
if (thinking.effort === undefined && effort !== 'off' && effort !== 'on') {
321+
await host.authFlow.activateModelAfterLogin(alias, effort);
322+
}
316323
host.track('model_switch', { model: alias });
317324
host.showStatus(`Default model set to ${alias} with thinking ${effort}.`);
318325
}

apps/pythinker-code/src/tui/controllers/editor-keyboard.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,7 @@ export class EditorKeyboardController {
595595
const harness = this.host.harness;
596596
if (harness === undefined || alias !== this.host.state.appState.model) return;
597597
try {
598-
await harness.setConfig({ thinking: thinkingEffortToConfig(effort, model.supportEfforts) });
598+
await harness.setConfig({ thinking: thinkingEffortToConfig(effort, model) });
599599
} catch (error) {
600600
this.host.showError(
601601
`Thinking effort set to ${effort}, but failed to save default: ${formatErrorMessage(error)}`,

0 commit comments

Comments
 (0)