Skip to content

Commit c19e897

Browse files
committed
feat(agent-core-v2): add the permission mode reminder env switch
Set PYTHINKER_CODE_PERMISSION_MODE_REMINDER to a false value to stop injecting the auto permission-mode reminder into the model context.
1 parent 6a1181a commit c19e897

5 files changed

Lines changed: 99 additions & 6 deletions

File tree

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 the `PYTHINKER_CODE_PERMISSION_MODE_REMINDER` environment variable: set it to a false value or an empty value to stop injecting the auto permission-mode reminders into the model context.

‎docs/configuration/env-vars.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ Switches that control the behavior of subsystems such as telemetry, background t
142142
| `PYTHINKER_CODE_TUI_FULL_SCREEN` | Control the fullscreen TUI with a fixed prompt dock, scrollable transcript, mouse text selection, clickable links, transcript search, and a clickable jump-to-bottom control. Fullscreen is enabled by default | `0` restores the legacy inline UI; unset or any other value keeps fullscreen enabled |
143143
| `PYTHINKER_CODE_EXPERIMENTAL_SECONDARY_MODEL` | Control the [subagent model pool](./config-files.md#subagent-model-pool) in every launch mode, including the interactive TUI. It is enabled by default; set this variable to a false value to disable it | Truthy: `1`/`true`/`yes`/`on`; falsy: `0`/`false`/`no`/`off` |
144144
| `PYTHINKER_CODE_DANGEROUS_COMMAND_GUARD` | Override [`[permission].dangerous_command_guard`](./config-files.md#permission). The guard asks before dangerous or unanalyzable `Bash` commands in interactive modes and blocks them in Auto mode | `true` or `false`; default `true` |
145+
| `PYTHINKER_CODE_PERMISSION_MODE_REMINDER` | Stop injecting the automatic permission-mode reminder (the context note that explains Auto mode) into the model context; set to a false value or an empty value to disable | Truthy keeps the reminder enabled; falsy: `0`/`false`/`no`/`off` disables it; an empty value disables it |
145146
| `PYTHINKER_CODE_EXPERIMENTAL_SUBAGENT_FORK` | Enable the experimental `fork` parameter on the `Agent` and `AgentDynamicWorkflow` tools, letting the model start a subagent with a snapshot of the calling agent's conversation history instead of an empty context; the master `PYTHINKER_CODE_EXPERIMENTAL_FLAG=1` also enables it | Truthy: `1`/`true`/`yes`/`on`; falsy: `0`/`false`/`no`/`off` |
146147
| `PYTHINKER_CODE_EXPERIMENTAL_TOWER` | Enable the experimental [`/tower`](../reference/slash-commands.md#modes--run-control) command for workspace-wide subagent coordination; the master `PYTHINKER_CODE_EXPERIMENTAL_FLAG=1` also enables it | Truthy: `1`/`true`/`yes`/`on`; falsy: `0`/`false`/`no`/`off` |
147148
| `PYTHINKER_MCP_STARTUP_TIMEOUT_MS` | Global default connection timeout (ms) for all MCP servers; takes higher priority than `[mcp] startup_timeout_ms` in `config.toml`, but a per-server `startupTimeoutMs` in `mcp.json` still wins (default `30000`) | Integer from `1` to `2147483647`; invalid values are ignored |

‎packages/agent-core-v2/src/agent/permissionMode/permissionModeService.ts‎

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { PermissionMode } from '#/agent/permissionPolicy/types';
22
import { Service } from '#/_base/di/service';
3+
import { parseBooleanEnv } from '#/_base/utils/env';
4+
import { IBootstrapService } from '#/app/bootstrap/bootstrap';
35
import { LifecycleScope } from '#/app/scopes';
46
import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
57
import { Emitter, type Event } from '#/_base/event';
@@ -20,6 +22,8 @@ import {
2022
PermissionSetMode,
2123
} from './permissionModeOps';
2224

25+
export const PERMISSION_MODE_REMINDER_ENV = 'PYTHINKER_CODE_PERMISSION_MODE_REMINDER';
26+
2327
export class AgentPermissionModeService extends Service implements IAgentPermissionModeService {
2428
declare readonly _serviceBrand: undefined;
2529

@@ -32,15 +36,19 @@ export class AgentPermissionModeService extends Service implements IAgentPermiss
3236
@IAgentLifecycleService private readonly agentLifecycle: IAgentLifecycleService,
3337
@ITelemetryService private readonly telemetry: ITelemetryService,
3438
@IAgentStateService private readonly agentState: IAgentStateService,
39+
@IBootstrapService bootstrap: IBootstrapService,
3540
) {
3641
super();
3742
this.agentState.contributeState(permissionModeKey);
3843
this.agentState.contributeState(permissionModeConfiguredKey);
39-
this._register(
40-
activateReminderWhenReady(this.agentLifecycle, this.scopeContext, (reminder) =>
41-
new PermissionModeInjection(this, reminder, this.agentState),
42-
),
43-
);
44+
const reminderEnv = bootstrap.getEnv(PERMISSION_MODE_REMINDER_ENV);
45+
if (reminderEnv?.trim() !== '' && parseBooleanEnv(reminderEnv) !== false) {
46+
this._register(
47+
activateReminderWhenReady(this.agentLifecycle, this.scopeContext, (reminder) =>
48+
new PermissionModeInjection(this, reminder, this.agentState),
49+
),
50+
);
51+
}
4452
}
4553

4654
get mode(): PermissionMode {

‎packages/agent-core-v2/test/agent/permissionMode/permissionMode.test.ts‎

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,14 @@ import type { ReminderRuntime } from '#/features/reminder/reminderAgentRuntime';
77
import type { ContextInjectionProvider } from '#/features/reminder/types';
88
import { IAgentLifecycleService } from '#/session/agentLifecycle/agentLifecycle';
99
import { lifecycleWithReminder } from '../../features/reminder/stubs';
10+
import { IBootstrapService } from '#/app/bootstrap/bootstrap';
11+
import { stubBootstrap } from '../../app/bootstrap/stubs';
1012
import { IAgentPermissionModeService } from '#/agent/permissionMode/permissionMode';
1113
import { PermissionModeInjection } from '#/agent/permissionMode/injection/permissionModeInjection';
12-
import { AgentPermissionModeService } from '#/agent/permissionMode/permissionModeService';
14+
import {
15+
AgentPermissionModeService,
16+
PERMISSION_MODE_REMINDER_ENV,
17+
} from '#/agent/permissionMode/permissionModeService';
1318
import { permissionModeKey } from '#/agent/permissionMode/permissionModeOps';
1419
import type { PermissionMode } from '#/agent/permissionPolicy/types';
1520
import { IAgentStateService } from '#/agent/state/agentState';
@@ -57,15 +62,18 @@ let log: IAppendLogStore;
5762
let dispatcher: IEventDispatcher;
5863
let svc: IAgentPermissionModeService;
5964
let reminderLive = false;
65+
let bootstrapEnv: NodeJS.ProcessEnv;
6066

6167
beforeEach(() => {
6268
registeredInjection = undefined;
6369
reminderLive = false;
70+
bootstrapEnv = {};
6471
disposables = new DisposableStore();
6572
ix = disposables.add(new TestInstantiationService());
6673
ix.stub(IFileSystemStorageService, new InMemoryStorageService());
6774
ix.set(IAppendLogStore, new SyncDescriptor(AppendLogStore));
6875
ix.stub(IAgentLifecycleService, lifecycleWithReminder(injectorStub));
76+
ix.stub(IBootstrapService, stubBootstrap('/tmp/pythinker-home', bootstrapEnv));
6977
ix.set(IAgentStateService, new AgentStateService());
7078
ix.set(IAgentPermissionModeService, new SyncDescriptor(AgentPermissionModeService));
7179
log = ix.get(IAppendLogStore);
@@ -243,4 +251,74 @@ describe('AgentPermissionModeService (wire-backed)', () => {
243251
expect(written[0]).toMatchObject({ type: 'metadata' });
244252
expect(written.slice(1)).toEqual([{ type: 'permission.set_mode', mode: 'auto' }]);
245253
});
254+
it('skips the auto-mode reminder injection when the reminder env is disabled', () => {
255+
registeredInjection = undefined;
256+
const ix2 = disposables.add(new TestInstantiationService());
257+
ix2.stub(IFileSystemStorageService, new InMemoryStorageService());
258+
ix2.set(IAppendLogStore, new SyncDescriptor(AppendLogStore));
259+
ix2.stub(IAgentLifecycleService, lifecycleWithReminder(injectorStub));
260+
ix2.stub(
261+
IBootstrapService,
262+
stubBootstrap('/tmp/pythinker-home', { [PERMISSION_MODE_REMINDER_ENV]: '0' }),
263+
);
264+
ix2.set(IAgentStateService, new AgentStateService());
265+
ix2.set(IAgentPermissionModeService, new SyncDescriptor(AgentPermissionModeService));
266+
registerTestAgentWire(ix2, testWireScope(SCOPE, 'permission-mode-no-reminder'), {
267+
log: ix2.get(IAppendLogStore),
268+
});
269+
registerTestEventDispatcher(ix2);
270+
271+
const svc2 = ix2.get(IAgentPermissionModeService);
272+
273+
expect(registeredInjection).toBeUndefined();
274+
svc2.setMode('auto');
275+
expect(svc2.mode).toBe('auto');
276+
expect(registeredInjection).toBeUndefined();
277+
});
278+
it('skips the auto-mode reminder injection when the reminder env is set to an empty value', () => {
279+
registeredInjection = undefined;
280+
const ix2 = disposables.add(new TestInstantiationService());
281+
ix2.stub(IFileSystemStorageService, new InMemoryStorageService());
282+
ix2.set(IAppendLogStore, new SyncDescriptor(AppendLogStore));
283+
ix2.stub(IAgentLifecycleService, lifecycleWithReminder(injectorStub));
284+
ix2.stub(
285+
IBootstrapService,
286+
stubBootstrap('/tmp/pythinker-home', { [PERMISSION_MODE_REMINDER_ENV]: '' }),
287+
);
288+
ix2.set(IAgentStateService, new AgentStateService());
289+
ix2.set(IAgentPermissionModeService, new SyncDescriptor(AgentPermissionModeService));
290+
registerTestAgentWire(ix2, testWireScope(SCOPE, 'permission-mode-empty-reminder'), {
291+
log: ix2.get(IAppendLogStore),
292+
});
293+
registerTestEventDispatcher(ix2);
294+
295+
const svc2 = ix2.get(IAgentPermissionModeService);
296+
297+
expect(registeredInjection).toBeUndefined();
298+
svc2.setMode('auto');
299+
expect(svc2.mode).toBe('auto');
300+
expect(registeredInjection).toBeUndefined();
301+
});
302+
303+
it('keeps the auto-mode reminder injection when the env override enables it explicitly', () => {
304+
registeredInjection = undefined;
305+
const ix2 = disposables.add(new TestInstantiationService());
306+
ix2.stub(IFileSystemStorageService, new InMemoryStorageService());
307+
ix2.set(IAppendLogStore, new SyncDescriptor(AppendLogStore));
308+
ix2.stub(IAgentLifecycleService, lifecycleWithReminder(injectorStub));
309+
ix2.stub(
310+
IBootstrapService,
311+
stubBootstrap('/tmp/pythinker-home', { [PERMISSION_MODE_REMINDER_ENV]: '1' }),
312+
);
313+
ix2.set(IAgentStateService, new AgentStateService());
314+
ix2.set(IAgentPermissionModeService, new SyncDescriptor(AgentPermissionModeService));
315+
registerTestAgentWire(ix2, testWireScope(SCOPE, 'permission-mode-reminder-on'), {
316+
log: ix2.get(IAppendLogStore),
317+
});
318+
registerTestEventDispatcher(ix2);
319+
320+
ix2.get(IAgentPermissionModeService);
321+
322+
expect((registeredInjection as { readonly name: string } | undefined)?.name).toBe('permission_mode');
323+
});
246324
});

‎packages/agent-core-v2/test/session/agentLifecycle/agentLifecycle.test.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ describe('AgentLifecycleService', () => {
265265
_serviceBrand: undefined,
266266
homeDir: '/tmp/pythinker-agentLifecycle-home',
267267
cwd: '/tmp/pythinker-agentLifecycle-home',
268+
getEnv: () => undefined,
268269
} as unknown as IBootstrapService);
269270
ix.stub(ISessionWorkspaceContext, {
270271
_serviceBrand: undefined,

0 commit comments

Comments
 (0)