@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.
- package/CHANGELOG.md +43 -2
- package/package.json +7 -7
- package/scripts/generate-docs-index.ts +2 -2
- package/src/cli/args.ts +2 -1
- package/src/cli/config-cli.ts +32 -20
- package/src/config/settings-schema.ts +96 -14
- package/src/config/settings.ts +10 -0
- package/src/discovery/claude.ts +24 -6
- package/src/discovery/helpers.ts +9 -2
- package/src/ipy/runtime.ts +1 -0
- package/src/mcp/config.ts +1 -1
- package/src/modes/components/settings-defs.ts +53 -1
- package/src/modes/components/status-line.ts +7 -5
- package/src/modes/controllers/mcp-command-controller.ts +4 -3
- package/src/modes/controllers/selector-controller.ts +46 -0
- package/src/modes/interactive-mode.ts +9 -0
- package/src/modes/oauth-manual-input.ts +42 -0
- package/src/modes/types.ts +2 -0
- package/src/patch/hashline.ts +19 -1
- package/src/patch/index.ts +7 -8
- package/src/prompts/system/commit-message-system.md +2 -0
- package/src/prompts/system/subagent-submit-reminder.md +3 -3
- package/src/prompts/system/subagent-system-prompt.md +4 -4
- package/src/prompts/system/system-prompt.md +13 -0
- package/src/prompts/tools/hashline.md +45 -1
- package/src/prompts/tools/task-summary.md +4 -4
- package/src/prompts/tools/task.md +1 -1
- package/src/sdk.ts +8 -0
- package/src/slash-commands/builtin-registry.ts +26 -1
- package/src/system-prompt.ts +4 -0
- package/src/task/index.ts +211 -70
- package/src/task/render.ts +44 -16
- package/src/task/types.ts +6 -1
- package/src/task/worktree.ts +394 -31
- package/src/tools/review.ts +50 -1
- package/src/tools/submit-result.ts +22 -23
- package/src/utils/commit-message-generator.ts +132 -0
- package/src/web/search/providers/exa.ts +41 -4
- 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
|
+
}
|
package/src/modes/types.ts
CHANGED
|
@@ -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
|
package/src/patch/hashline.ts
CHANGED
|
@@ -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
|
|
package/src/patch/index.ts
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
122
|
-
//
|
|
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
|
|
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
|
-
|
|
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:
|
|
6
|
-
- If task failed
|
|
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**
|
|
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
|
|
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
|
-
|
|
35
|
-
You **MUST NOT**
|
|
36
|
-
You **MUST NOT**
|
|
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
|
|
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\","}}
|
|
@@ -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
|
|
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
|
-
|
|
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
|
},
|
package/src/system-prompt.ts
CHANGED
|
@@ -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
|
}
|