@oh-my-pi/pi-coding-agent 13.2.1 → 13.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +43 -2
  2. package/package.json +7 -7
  3. package/scripts/generate-docs-index.ts +2 -2
  4. package/src/cli/args.ts +2 -1
  5. package/src/cli/config-cli.ts +32 -20
  6. package/src/config/settings-schema.ts +96 -14
  7. package/src/config/settings.ts +10 -0
  8. package/src/discovery/claude.ts +24 -6
  9. package/src/discovery/helpers.ts +9 -2
  10. package/src/ipy/runtime.ts +1 -0
  11. package/src/mcp/config.ts +1 -1
  12. package/src/modes/components/settings-defs.ts +53 -1
  13. package/src/modes/components/status-line.ts +7 -5
  14. package/src/modes/controllers/mcp-command-controller.ts +4 -3
  15. package/src/modes/controllers/selector-controller.ts +46 -0
  16. package/src/modes/interactive-mode.ts +9 -0
  17. package/src/modes/oauth-manual-input.ts +42 -0
  18. package/src/modes/types.ts +2 -0
  19. package/src/patch/hashline.ts +19 -1
  20. package/src/patch/index.ts +7 -8
  21. package/src/prompts/system/commit-message-system.md +2 -0
  22. package/src/prompts/system/subagent-submit-reminder.md +3 -3
  23. package/src/prompts/system/subagent-system-prompt.md +4 -4
  24. package/src/prompts/system/system-prompt.md +13 -0
  25. package/src/prompts/tools/hashline.md +45 -1
  26. package/src/prompts/tools/task-summary.md +4 -4
  27. package/src/prompts/tools/task.md +1 -1
  28. package/src/sdk.ts +8 -0
  29. package/src/slash-commands/builtin-registry.ts +26 -1
  30. package/src/system-prompt.ts +4 -0
  31. package/src/task/index.ts +211 -70
  32. package/src/task/render.ts +44 -16
  33. package/src/task/types.ts +6 -1
  34. package/src/task/worktree.ts +394 -31
  35. package/src/tools/review.ts +50 -1
  36. package/src/tools/submit-result.ts +22 -23
  37. package/src/utils/commit-message-generator.ts +132 -0
  38. package/src/web/search/providers/exa.ts +41 -4
  39. package/src/web/search/providers/perplexity.ts +20 -8
@@ -31,6 +31,16 @@ import { ToolExecutionComponent } from "../components/tool-execution";
31
31
  import { TreeSelectorComponent } from "../components/tree-selector";
32
32
  import { UserMessageSelectorComponent } from "../components/user-message-selector";
33
33
 
34
+ const CALLBACK_SERVER_PROVIDERS = new Set<OAuthProvider>([
35
+ "anthropic",
36
+ "openai-codex",
37
+ "gitlab-duo",
38
+ "google-gemini-cli",
39
+ "google-antigravity",
40
+ ]);
41
+
42
+ const MANUAL_LOGIN_TIP = "Tip: You can complete pairing with /login <redirect URL>.";
43
+
34
44
  export class SelectorController {
35
45
  constructor(private ctx: InteractiveModeContext) {}
36
46
 
@@ -278,6 +288,31 @@ export class SelectorController {
278
288
  this.ctx.session.agent.temperature = temp >= 0 ? temp : undefined;
279
289
  break;
280
290
  }
291
+ case "topP": {
292
+ const topP = typeof value === "number" ? value : Number(value);
293
+ this.ctx.session.agent.topP = topP >= 0 ? topP : undefined;
294
+ break;
295
+ }
296
+ case "topK": {
297
+ const topK = typeof value === "number" ? value : Number(value);
298
+ this.ctx.session.agent.topK = topK >= 0 ? topK : undefined;
299
+ break;
300
+ }
301
+ case "minP": {
302
+ const minP = typeof value === "number" ? value : Number(value);
303
+ this.ctx.session.agent.minP = minP >= 0 ? minP : undefined;
304
+ break;
305
+ }
306
+ case "presencePenalty": {
307
+ const presencePenalty = typeof value === "number" ? value : Number(value);
308
+ this.ctx.session.agent.presencePenalty = presencePenalty >= 0 ? presencePenalty : undefined;
309
+ break;
310
+ }
311
+ case "repetitionPenalty": {
312
+ const repetitionPenalty = typeof value === "number" ? value : Number(value);
313
+ this.ctx.session.agent.repetitionPenalty = repetitionPenalty >= 0 ? repetitionPenalty : undefined;
314
+ break;
315
+ }
281
316
  case "statusLinePreset":
282
317
  case "statusLineSeparator":
283
318
  case "statusLineShowHooks":
@@ -600,6 +635,8 @@ export class SelectorController {
600
635
  done();
601
636
  if (mode === "login") {
602
637
  this.ctx.showStatus(`Logging in to ${providerId}…`);
638
+ const manualInput = this.ctx.oauthManualInput;
639
+ const useManualInput = CALLBACK_SERVER_PROVIDERS.has(providerId as OAuthProvider);
603
640
  try {
604
641
  await this.ctx.session.modelRegistry.authStorage.login(providerId as OAuthProvider, {
605
642
  onAuth: (info: { url: string; instructions?: string }) => {
@@ -612,6 +649,10 @@ export class SelectorController {
612
649
  this.ctx.chatContainer.addChild(new Spacer(1));
613
650
  this.ctx.chatContainer.addChild(new Text(theme.fg("warning", info.instructions), 1, 0));
614
651
  }
652
+ if (useManualInput) {
653
+ this.ctx.chatContainer.addChild(new Spacer(1));
654
+ this.ctx.chatContainer.addChild(new Text(theme.fg("dim", MANUAL_LOGIN_TIP), 1, 0));
655
+ }
615
656
  this.ctx.ui.requestRender();
616
657
  this.ctx.openInBrowser(info.url);
617
658
  },
@@ -641,6 +682,7 @@ export class SelectorController {
641
682
  this.ctx.chatContainer.addChild(new Text(theme.fg("dim", message), 1, 0));
642
683
  this.ctx.ui.requestRender();
643
684
  },
685
+ onManualCodeInput: useManualInput ? () => manualInput.waitForInput(providerId) : undefined,
644
686
  });
645
687
  // Refresh models to pick up new baseUrl (e.g., github-copilot)
646
688
  await this.ctx.session.modelRegistry.refresh();
@@ -658,6 +700,10 @@ export class SelectorController {
658
700
  this.ctx.ui.requestRender();
659
701
  } catch (error: unknown) {
660
702
  this.ctx.showError(`Login failed: ${error instanceof Error ? error.message : String(error)}`);
703
+ } finally {
704
+ if (useManualInput) {
705
+ manualInput.clear(`Manual OAuth input cleared for ${providerId}`);
706
+ }
661
707
  }
662
708
  } else {
663
709
  try {
@@ -51,6 +51,7 @@ import { InputController } from "./controllers/input-controller";
51
51
  import { MCPCommandController } from "./controllers/mcp-command-controller";
52
52
  import { SelectorController } from "./controllers/selector-controller";
53
53
  import { SSHCommandController } from "./controllers/ssh-command-controller";
54
+ import { OAuthManualInputManager } from "./oauth-manual-input";
54
55
  import { setMermaidRenderCallback } from "./theme/mermaid-cache";
55
56
  import type { Theme } from "./theme/theme";
56
57
  import { getEditorTheme, getMarkdownTheme, onThemeChange, theme } from "./theme/theme";
@@ -133,6 +134,7 @@ export class InteractiveMode implements InteractiveModeContext {
133
134
  lastStatusText: Text | undefined = undefined;
134
135
  fileSlashCommands: Set<string> = new Set();
135
136
  skillCommands: Map<string, string> = new Map();
137
+ oauthManualInput: OAuthManualInputManager = new OAuthManualInputManager();
136
138
 
137
139
  #pendingSlashCommands: SlashCommand[] = [];
138
140
  #cleanupUnsubscribe?: () => void;
@@ -674,6 +676,13 @@ export class InteractiveMode implements InteractiveModeContext {
674
676
  const previousTools = this.#planModePreviousTools ?? this.session.getActiveToolNames();
675
677
  await this.#exitPlanMode({ silent: true, paused: false });
676
678
  await this.handleClearCommand();
679
+ // The new session has a fresh local:// root — persist the approved plan there
680
+ // so `local://<title>.md` resolves correctly in the execution session.
681
+ const newLocalPath = resolveLocalUrlToPath(options.finalPlanFilePath, {
682
+ getArtifactsDir: () => this.sessionManager.getArtifactsDir(),
683
+ getSessionId: () => this.sessionManager.getSessionId(),
684
+ });
685
+ await Bun.write(newLocalPath, planContent);
677
686
  if (previousTools.length > 0) {
678
687
  await this.session.setActiveToolsByName(previousTools);
679
688
  }
@@ -0,0 +1,42 @@
1
+ type PendingInput = {
2
+ providerId: string;
3
+ resolve: (value: string) => void;
4
+ reject: (error: Error) => void;
5
+ };
6
+
7
+ export class OAuthManualInputManager {
8
+ #pending?: PendingInput;
9
+
10
+ waitForInput(providerId: string): Promise<string> {
11
+ if (this.#pending) {
12
+ this.clear("Manual OAuth input superseded by a new login");
13
+ }
14
+
15
+ const { promise, resolve, reject } = Promise.withResolvers<string>();
16
+ this.#pending = { providerId, resolve, reject };
17
+ return promise;
18
+ }
19
+
20
+ submit(input: string): boolean {
21
+ if (!this.#pending) return false;
22
+ const { resolve } = this.#pending;
23
+ this.#pending = undefined;
24
+ resolve(input);
25
+ return true;
26
+ }
27
+
28
+ clear(reason = "Manual OAuth input cleared"): void {
29
+ if (!this.#pending) return;
30
+ const { reject } = this.#pending;
31
+ this.#pending = undefined;
32
+ reject(new Error(reason));
33
+ }
34
+
35
+ hasPending(): boolean {
36
+ return Boolean(this.#pending);
37
+ }
38
+
39
+ get pendingProviderId(): string | undefined {
40
+ return this.#pending?.providerId;
41
+ }
42
+ }
@@ -19,6 +19,7 @@ import type { HookSelectorComponent } from "./components/hook-selector";
19
19
  import type { PythonExecutionComponent } from "./components/python-execution";
20
20
  import type { StatusLineComponent } from "./components/status-line";
21
21
  import type { ToolExecutionHandle } from "./components/tool-execution";
22
+ import type { OAuthManualInputManager } from "./oauth-manual-input";
22
23
  import type { Theme } from "./theme/theme";
23
24
 
24
25
  export type CompactionQueuedMessage = {
@@ -97,6 +98,7 @@ export interface InteractiveModeContext {
97
98
  lastStatusText: Text | undefined;
98
99
  fileSlashCommands: Set<string>;
99
100
  skillCommands: Map<string, string>;
101
+ oauthManualInput: OAuthManualInputManager;
100
102
  todoPhases: TodoPhase[];
101
103
 
102
104
  // Lifecycle
@@ -444,6 +444,7 @@ export function applyHashlineEdits(
444
444
  const originalFileLines = [...fileLines];
445
445
  let firstChangedLine: number | undefined;
446
446
  const noopEdits: Array<{ editIndex: number; loc: string; current: string }> = [];
447
+ const warnings: string[] = [];
447
448
 
448
449
  // Pre-validate: collect all hash mismatches before mutating
449
450
  const mismatches: HashMismatch[] = [];
@@ -580,7 +581,23 @@ export function applyHashlineEdits(
580
581
  trackFirstChanged(edit.pos.line);
581
582
  } else {
582
583
  const count = edit.end.line - edit.pos.line + 1;
583
- const newLines = edit.lines;
584
+ const newLines = [...edit.lines];
585
+ const trailingReplacementLine = newLines[newLines.length - 1];
586
+ const nextSurvivingLine = fileLines[edit.end.line];
587
+ if (
588
+ trailingReplacementLine !== undefined &&
589
+ trailingReplacementLine.trim().length > 0 &&
590
+ nextSurvivingLine !== undefined &&
591
+ trailingReplacementLine.trim() === nextSurvivingLine.trim() &&
592
+ // Safety: only correct when end-line content differs from the duplicate.
593
+ // If end already points to the boundary, matching next line is coincidence.
594
+ fileLines[edit.end.line - 1].trim() !== trailingReplacementLine.trim()
595
+ ) {
596
+ newLines.pop();
597
+ warnings.push(
598
+ `Auto-corrected range replace ${edit.pos.line}#${edit.pos.hash}-${edit.end.line}#${edit.end.hash}: removed trailing replacement line "${trailingReplacementLine.trim()}" that duplicated next surviving line`,
599
+ );
600
+ }
584
601
  fileLines.splice(edit.pos.line - 1, count, ...newLines);
585
602
  trackFirstChanged(edit.pos.line);
586
603
  }
@@ -639,6 +656,7 @@ export function applyHashlineEdits(
639
656
  return {
640
657
  lines: fileLines.join("\n"),
641
658
  firstChangedLine,
659
+ ...(warnings.length > 0 ? { warnings } : {}),
642
660
  ...(noopEdits.length > 0 ? { noopEdits } : {}),
643
661
  };
644
662
 
@@ -104,8 +104,8 @@ const patchEditSchema = Type.Object({
104
104
  export type ReplaceParams = Static<typeof replaceEditSchema>;
105
105
  export type PatchParams = Static<typeof patchEditSchema>;
106
106
 
107
- /** Pattern matching hashline display format: `LINE#ID:CONTENT` */
108
- const HASHLINE_PREFIX_RE = /^\s*(?:>>>|>>)?\s*\d+#[0-9a-zA-Z]{1,16}:/;
107
+ /** Pattern matching hashline display format prefixes: `LINE#ID:CONTENT` and `#ID:CONTENT` */
108
+ const HASHLINE_PREFIX_RE = /^\s*(?:>>>|>>)?\s*(?:\d+\s*#\s*|#)\s*[0-9a-zA-Z]{1,16}:/;
109
109
 
110
110
  /** Pattern matching a unified-diff added-line `+` prefix (but not `++`). Does NOT match `-` to avoid corrupting Markdown list items. */
111
111
  const DIFF_PLUS_RE = /^[+](?![+])/;
@@ -118,8 +118,9 @@ const DIFF_PLUS_RE = /^[+](?![+])/;
118
118
  * output file. This strips them heuristically before application.
119
119
  */
120
120
  export function stripNewLinePrefixes(lines: string[]): string[] {
121
- // Detect whether the *majority* of non-empty lines carry a prefix
122
- // if only one line out of many has a match it's likely real content.
121
+ // Hashline prefixes are highly specific to read output and should only be
122
+ // stripped when *every* non-empty line carries one.
123
+ // Diff '+' markers can be legitimate content less often, so keep majority mode.
123
124
  let hashPrefixCount = 0;
124
125
  let diffPlusCount = 0;
125
126
  let nonEmpty = 0;
@@ -131,9 +132,8 @@ export function stripNewLinePrefixes(lines: string[]): string[] {
131
132
  }
132
133
  if (nonEmpty === 0) return lines;
133
134
 
134
- const stripHash = hashPrefixCount > 0 && hashPrefixCount >= nonEmpty * 0.5;
135
+ const stripHash = hashPrefixCount > 0 && hashPrefixCount === nonEmpty;
135
136
  const stripPlus = !stripHash && diffPlusCount > 0 && diffPlusCount >= nonEmpty * 0.5;
136
-
137
137
  if (!stripHash && !stripPlus) return lines;
138
138
 
139
139
  return lines.map(l => {
@@ -145,8 +145,7 @@ export function stripNewLinePrefixes(lines: string[]): string[] {
145
145
 
146
146
  export function hashlineParseText(edit: string[] | string | null): string[] {
147
147
  if (edit === null) return [];
148
- if (Array.isArray(edit)) return edit;
149
- const lines = stripNewLinePrefixes(edit.split("\n"));
148
+ const lines = stripNewLinePrefixes(Array.isArray(edit) ? edit : edit.split("\n"));
150
149
  if (lines.length === 0) return [];
151
150
  if (lines[lines.length - 1].trim() === "") return lines.slice(0, -1);
152
151
  return lines;
@@ -0,0 +1,2 @@
1
+ Generate a concise git commit message from the provided diff. Use conventional commit format: `type(scope): description` where type is feat/fix/refactor/chore/test/docs and scope is optional. The description **MUST** be lowercase, imperative mood, no trailing period. Keep it under 72 characters.
2
+ You **MUST** output ONLY the commit message, nothing else.
@@ -2,10 +2,10 @@
2
2
  You stopped without calling submit_result. This is reminder {{retryCount}} of {{maxRetries}}.
3
3
 
4
4
  You **MUST** call submit_result as your only action now. Choose one:
5
- - If task is complete: you **MUST** call submit_result with your result data
6
- - If task failed or was interrupted: you **MUST** call submit_result with status="aborted" and describe what happened
5
+ - If task is complete: call submit_result with your result in the `data` field
6
+ - If task failed: call submit_result with an `error` field describing what happened
7
7
 
8
- You **MUST NOT** choose aborted if you can still complete the task through exploration (using available tools or repo context). If you abort, you **MUST** include what you tried and the exact blocker.
8
+ You **MUST NOT** give up if you can still complete the task through exploration (using available tools or repo context). If you submit an error, you **MUST** include what you tried and the exact blocker.
9
9
 
10
10
  You **MUST NOT** output text without a tool call. You **MUST** call submit_result to finish.
11
11
  </system-reminder>
@@ -29,11 +29,11 @@ Your result **MUST** match this TypeScript interface:
29
29
  {{/if}}
30
30
 
31
31
  {{SECTION_SEPERATOR "Giving Up"}}
32
- If you cannot complete the assignment, you **MUST** call `submit_result` exactly once with `status="aborted"` and an error message describing what you tried and the exact blocker.
32
+ If you cannot complete the assignment, you **MUST** call `submit_result` exactly once with an `error` message describing what you tried and the exact blocker.
33
33
 
34
- Aborting is a last resort.
35
- You **MUST NOT** abort due to uncertainty or missing information obtainable via tools or repo context.
36
- You **MUST NOT** abort due to requiring a design, you can derive that yourself, more than capable of that.
34
+ Giving up is a last resort.
35
+ You **MUST NOT** give up due to uncertainty or missing information obtainable via tools or repo context.
36
+ You **MUST NOT** give up due to requiring a design, you can derive that yourself, more than capable of that.
37
37
 
38
38
  Proceed with the best approach using the most reasonable option.
39
39
 
@@ -159,6 +159,19 @@ Semantic questions **MUST** be answered with semantic tools.
159
159
  - What is this thing? → `lsp hover`
160
160
  {{/has}}
161
161
 
162
+ {{#if eagerTasks}}
163
+ <eager-tasks>
164
+ You **SHOULD** delegate work to subagents by default. Working alone is the exception, not the rule.
165
+
166
+ Use the Task tool unless the change is:
167
+ - A single-file edit under ~30 lines
168
+ - A direct answer or explanation with no code changes
169
+ - A command the user asked you to run yourself
170
+
171
+ For everything else — multi-file changes, refactors, new features, test additions, investigations — break the work into tasks and delegate. Err on the side of delegating. You are an orchestrator first, a coder second.
172
+ </eager-tasks>
173
+ {{/if}}
174
+
162
175
  {{#has tools "ssh"}}
163
176
  ### SSH: match commands to host shell
164
177
 
@@ -40,7 +40,9 @@ Every edit has `op`, `pos`, and `lines`. Range replaces also have `end`. Both `p
40
40
  <rules>
41
41
  1. **Minimize scope:** You **MUST** use one logical mutation per operation.
42
42
  2. **Prefer insertion over neighbor rewrites:** You **SHOULD** anchor on structural boundaries (`}`, `]`, `},`), not interior lines.
43
- 3. **Range end tag:** When replacing a block (e.g., an `if` body), the `end` tag **MUST** include the block's closing brace/bracket — not just the last interior line. Verify the `end` tag covers all lines being logically removed, including trailing `}`, `]`, or `)`. An off-by-one on `end` orphans a brace and breaks syntax.
43
+ 3. **Range end tag (inclusive):** `end` is inclusive and **MUST** point to the final line being replaced.
44
+ - If `lines` includes a closing boundary token (`}`, `]`, `)`, `);`, `},`), `end` **MUST** include the original boundary line.
45
+ - You **MUST NOT** set `end` to an interior line and then re-add the boundary token in `lines`; that duplicates the next surviving line.
44
46
  </rules>
45
47
 
46
48
  <recovery>
@@ -131,6 +133,48 @@ Range — add `end`:
131
133
  ```
132
134
  </example>
133
135
 
136
+ <example name="inclusive end avoids duplicate boundary">
137
+ ```ts
138
+ {{hlinefull 70 "if (ok) {"}}
139
+ {{hlinefull 71 " run();"}}
140
+ {{hlinefull 72 "}"}}
141
+ {{hlinefull 73 "after();"}}
142
+ ```
143
+ Bad — `end` stops before `}` while `lines` already includes `}`:
144
+ ```
145
+ {
146
+ path: "…",
147
+ edits: [{
148
+ op: "replace",
149
+ pos: "{{hlineref 70 "if (ok) {"}}",
150
+ end: "{{hlineref 71 " run();"}}",
151
+ lines: [
152
+ "if (ok) {",
153
+ " runSafe();",
154
+ "}"
155
+ ]
156
+ }]
157
+ }
158
+ ```
159
+ Good — include original `}` in the replaced range when replacement keeps `}`:
160
+ ```
161
+ {
162
+ path: "…",
163
+ edits: [{
164
+ op: "replace",
165
+ pos: "{{hlineref 70 "if (ok) {"}}",
166
+ end: "{{hlineref 72 "}"}}",
167
+ lines: [
168
+ "if (ok) {",
169
+ " runSafe();",
170
+ "}"
171
+ ]
172
+ }]
173
+ }
174
+ ```
175
+ Also apply the same rule to `);`, `],`, and `},` closers: if replacement includes the closer token, `end` must include the original closer line.
176
+ </example>
177
+
134
178
  <example name="insert between siblings">
135
179
  ```ts
136
180
  {{hlinefull 44 " \"build\": \"bun run compile\","}}
@@ -20,9 +20,9 @@
20
20
  {{/unless}}
21
21
  {{/each}}
22
22
 
23
- {{#if patchApplySummary}}
24
- <patch-summary>
25
- {{patchApplySummary}}
26
- </patch-summary>
23
+ {{#if mergeSummary}}
24
+ <merge-summary>
25
+ {{mergeSummary}}
26
+ </merge-summary>
27
27
  {{/if}}
28
28
  </task-summary>
@@ -16,7 +16,7 @@ Subagents lack your conversation history. Every decision, file content, and user
16
16
  - `context`: Shared background prepended to every assignment. Session-specific info only.
17
17
  - `schema`: JTD schema for expected output. Format lives here — **MUST NOT** be duplicated in assignments.
18
18
  - `tasks`: Tasks to execute in parallel.
19
- - `isolated`: Run in isolated git worktree; returns patches. Use when tasks edit overlapping files.
19
+ - `isolated`: Run in isolated environment; returns patches. Use when tasks edit overlapping files.
20
20
  </parameters>
21
21
 
22
22
  <critical>
package/src/sdk.ts CHANGED
@@ -1120,6 +1120,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1120
1120
  });
1121
1121
 
1122
1122
  const repeatToolDescriptions = settings.get("repeatToolDescriptions");
1123
+ const eagerTasks = settings.get("task.eager");
1123
1124
  const intentField = settings.get("tools.intentTracing") || $env.PI_INTENT_TRACING === "1" ? INTENT_FIELD : undefined;
1124
1125
  const rebuildSystemPrompt = async (toolNames: string[], tools: Map<string, AgentTool>): Promise<string> => {
1125
1126
  toolContextStore.setToolNames(toolNames);
@@ -1135,6 +1136,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1135
1136
  skillsSettings: settings.getGroup("skills") as SkillsSettings,
1136
1137
  appendSystemPrompt: memoryInstructions,
1137
1138
  repeatToolDescriptions,
1139
+ eagerTasks,
1138
1140
  intentField,
1139
1141
  });
1140
1142
 
@@ -1154,6 +1156,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1154
1156
  customPrompt: options.systemPrompt,
1155
1157
  appendSystemPrompt: memoryInstructions,
1156
1158
  repeatToolDescriptions,
1159
+ eagerTasks,
1157
1160
  intentField,
1158
1161
  });
1159
1162
  }
@@ -1278,6 +1281,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1278
1281
  interruptMode: settings.get("interruptMode") ?? "immediate",
1279
1282
  thinkingBudgets: settings.getGroup("thinkingBudgets"),
1280
1283
  temperature: settings.get("temperature") >= 0 ? settings.get("temperature") : undefined,
1284
+ topP: settings.get("topP") >= 0 ? settings.get("topP") : undefined,
1285
+ topK: settings.get("topK") >= 0 ? settings.get("topK") : undefined,
1286
+ minP: settings.get("minP") >= 0 ? settings.get("minP") : undefined,
1287
+ presencePenalty: settings.get("presencePenalty") >= 0 ? settings.get("presencePenalty") : undefined,
1288
+ repetitionPenalty: settings.get("repetitionPenalty") >= 0 ? settings.get("repetitionPenalty") : undefined,
1281
1289
  kimiApiFormat: settings.get("providers.kimiApiFormat") ?? "anthropic",
1282
1290
  preferWebsockets: preferOpenAICodexWebsockets,
1283
1291
  getToolContext: tc => toolContextStore.getContext(tc),
@@ -259,7 +259,32 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
259
259
  {
260
260
  name: "login",
261
261
  description: "Login with OAuth provider",
262
- handle: (_command, runtime) => {
262
+ inlineHint: "[redirect URL]",
263
+ allowArgs: true,
264
+ handle: (command, runtime) => {
265
+ const manualInput = runtime.ctx.oauthManualInput;
266
+ const args = command.args.trim();
267
+ if (args.length > 0) {
268
+ const submitted = manualInput.submit(args);
269
+ if (submitted) {
270
+ runtime.ctx.showStatus("OAuth callback received; completing login…");
271
+ } else {
272
+ runtime.ctx.showWarning("No OAuth login is waiting for a manual callback.");
273
+ }
274
+ runtime.ctx.editor.setText("");
275
+ return;
276
+ }
277
+
278
+ if (manualInput.hasPending()) {
279
+ const provider = manualInput.pendingProviderId;
280
+ const message = provider
281
+ ? `OAuth login already in progress for ${provider}. Paste the redirect URL with /login <url>.`
282
+ : "OAuth login already in progress. Paste the redirect URL with /login <url>.";
283
+ runtime.ctx.showWarning(message);
284
+ runtime.ctx.editor.setText("");
285
+ return;
286
+ }
287
+
263
288
  void runtime.ctx.showOAuthSelector("login");
264
289
  runtime.ctx.editor.setText("");
265
290
  },
@@ -358,6 +358,8 @@ export interface BuildSystemPromptOptions {
358
358
  rules?: Array<{ name: string; description?: string; path: string; globs?: string[] }>;
359
359
  /** Intent field name injected into every tool schema. If set, explains the field in the prompt. */
360
360
  intentField?: string;
361
+ /** Encourage the agent to delegate via tasks unless changes are trivial. */
362
+ eagerTasks?: boolean;
361
363
  }
362
364
 
363
365
  /** Build the system prompt with tools, guidelines, and context */
@@ -379,6 +381,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
379
381
  preloadedSkills: providedPreloadedSkills,
380
382
  rules,
381
383
  intentField,
384
+ eagerTasks = false,
382
385
  } = options;
383
386
  const resolvedCwd = cwd ?? getProjectDir();
384
387
  const preloadedSkills = providedPreloadedSkills;
@@ -535,6 +538,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
535
538
  cwd: resolvedCwd,
536
539
  intentTracing: !!intentField,
537
540
  intentField: intentField ?? "",
541
+ eagerTasks,
538
542
  };
539
543
  return renderPromptTemplate(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
540
544
  }