@oh-my-pi/pi-coding-agent 14.5.8 → 14.5.10
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 +56 -0
- package/package.json +7 -15
- package/scripts/build-binary.ts +1 -1
- package/src/cli/update-cli.ts +25 -1
- package/src/config/model-registry.ts +21 -19
- package/src/config/settings-schema.ts +14 -19
- package/src/discovery/claude-plugins.ts +28 -3
- package/src/edit/modes/atom.lark +7 -5
- package/src/edit/modes/atom.ts +510 -73
- package/src/edit/modes/hashline.ts +172 -91
- package/src/extensibility/extensions/runner.ts +34 -1
- package/src/extensibility/extensions/types.ts +8 -0
- package/src/lsp/client.ts +27 -35
- package/src/lsp/index.ts +2 -4
- package/src/lsp/render.ts +0 -3
- package/src/lsp/types.ts +1 -4
- package/src/lsp/utils.ts +18 -14
- package/src/memories/index.ts +5 -0
- package/src/modes/components/settings-defs.ts +1 -1
- package/src/modes/controllers/command-controller.ts +17 -0
- package/src/modes/controllers/input-controller.ts +7 -1
- package/src/modes/controllers/selector-controller.ts +2 -2
- package/src/modes/interactive-mode.ts +57 -26
- package/src/modes/theme/theme.ts +10 -1
- package/src/modes/types.ts +5 -3
- package/src/modes/utils/context-usage.ts +294 -0
- package/src/modes/utils/ui-helpers.ts +19 -6
- package/src/prompts/system/auto-continue.md +1 -0
- package/src/prompts/tools/atom.md +99 -44
- package/src/prompts/tools/exit-plan-mode.md +5 -39
- package/src/prompts/tools/github.md +3 -3
- package/src/prompts/tools/lsp.md +2 -3
- package/src/prompts/tools/{run-command.md → recipe.md} +1 -1
- package/src/prompts/tools/task.md +34 -147
- package/src/prompts/tools/todo-write.md +22 -64
- package/src/sdk.ts +13 -2
- package/src/session/agent-session.ts +175 -79
- package/src/session/compaction/compaction.ts +35 -22
- package/src/session/session-dump-format.ts +1 -0
- package/src/session/session-manager.ts +19 -2
- package/src/slash-commands/builtin-registry.ts +12 -5
- package/src/tools/bash.ts +9 -4
- package/src/tools/debug.ts +57 -70
- package/src/tools/gh.ts +267 -119
- package/src/tools/index.ts +7 -7
- package/src/tools/{run-command → recipe}/index.ts +19 -19
- package/src/tools/recipe/render.ts +19 -0
- package/src/tools/{run-command → recipe}/runner.ts +28 -7
- package/src/tools/{run-command → recipe}/runners/pkg.ts +23 -53
- package/src/tools/renderers.ts +2 -2
- package/src/utils/git.ts +61 -2
- package/src/web/search/providers/searxng.ts +71 -13
- package/src/tools/run-command/render.ts +0 -18
- /package/src/tools/{run-command → recipe}/runners/cargo.ts +0 -0
- /package/src/tools/{run-command → recipe}/runners/index.ts +0 -0
- /package/src/tools/{run-command → recipe}/runners/just.ts +0 -0
- /package/src/tools/{run-command → recipe}/runners/make.ts +0 -0
- /package/src/tools/{run-command → recipe}/runners/task.ts +0 -0
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
getOpenAIResponsesHistoryPayload,
|
|
27
27
|
normalizeResponsesToolCallId,
|
|
28
28
|
} from "@oh-my-pi/pi-ai/utils";
|
|
29
|
+
import { countTokens } from "@oh-my-pi/pi-natives";
|
|
29
30
|
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
|
30
31
|
import compactionShortSummaryPrompt from "../../prompts/compaction/compaction-short-summary.md" with { type: "text" };
|
|
31
32
|
import compactionSummaryPrompt from "../../prompts/compaction/compaction-summary.md" with { type: "text" };
|
|
@@ -218,7 +219,7 @@ export function shouldCompact(contextTokens: number, contextWindow: number, sett
|
|
|
218
219
|
return contextTokens > thresholdTokens;
|
|
219
220
|
}
|
|
220
221
|
|
|
221
|
-
function resolveThresholdTokens(contextWindow: number, settings: CompactionSettings): number {
|
|
222
|
+
export function resolveThresholdTokens(contextWindow: number, settings: CompactionSettings): number {
|
|
222
223
|
// Fixed token limit takes priority over percentage
|
|
223
224
|
const thresholdTokens = settings.thresholdTokens;
|
|
224
225
|
if (typeof thresholdTokens === "number" && Number.isFinite(thresholdTokens) && thresholdTokens > 0) {
|
|
@@ -240,67 +241,79 @@ function resolveThresholdTokens(contextWindow: number, settings: CompactionSetti
|
|
|
240
241
|
// ============================================================================
|
|
241
242
|
|
|
242
243
|
/**
|
|
243
|
-
*
|
|
244
|
-
*
|
|
244
|
+
* Image content has no tokenizer representation; charge a fixed estimate
|
|
245
|
+
* matching what providers typically bill for inline images.
|
|
246
|
+
*/
|
|
247
|
+
const IMAGE_TOKEN_ESTIMATE = 1200;
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Estimate token count for a message using cl100k_base via the native
|
|
251
|
+
* tokenizer. This is not Claude's first-party tokenizer (Anthropic doesn't
|
|
252
|
+
* publish one) but is within ~5–10% across English/code text.
|
|
245
253
|
*/
|
|
246
254
|
export function estimateTokens(message: AgentMessage): number {
|
|
247
|
-
|
|
255
|
+
const fragments: string[] = [];
|
|
256
|
+
let extra = 0;
|
|
248
257
|
|
|
249
258
|
switch (message.role) {
|
|
250
259
|
case "user": {
|
|
251
260
|
const content = (message as { content: string | Array<{ type: string; text?: string }> }).content;
|
|
252
261
|
if (typeof content === "string") {
|
|
253
|
-
|
|
262
|
+
fragments.push(content);
|
|
254
263
|
} else if (Array.isArray(content)) {
|
|
255
264
|
for (const block of content) {
|
|
256
265
|
if (block.type === "text" && block.text) {
|
|
257
|
-
|
|
266
|
+
fragments.push(block.text);
|
|
258
267
|
}
|
|
259
268
|
}
|
|
260
269
|
}
|
|
261
|
-
|
|
270
|
+
break;
|
|
262
271
|
}
|
|
263
272
|
case "assistant": {
|
|
264
273
|
const assistant = message as AssistantMessage;
|
|
265
274
|
for (const block of assistant.content) {
|
|
266
275
|
if (block.type === "text") {
|
|
267
|
-
|
|
276
|
+
fragments.push(block.text);
|
|
268
277
|
} else if (block.type === "thinking") {
|
|
269
|
-
|
|
278
|
+
fragments.push(block.thinking);
|
|
270
279
|
} else if (block.type === "toolCall") {
|
|
271
|
-
|
|
280
|
+
fragments.push(block.name);
|
|
281
|
+
fragments.push(JSON.stringify(block.arguments));
|
|
272
282
|
}
|
|
273
283
|
}
|
|
274
|
-
|
|
284
|
+
break;
|
|
275
285
|
}
|
|
276
286
|
case "hookMessage":
|
|
277
287
|
case "toolResult": {
|
|
278
288
|
if (typeof message.content === "string") {
|
|
279
|
-
|
|
289
|
+
fragments.push(message.content);
|
|
280
290
|
} else {
|
|
281
291
|
for (const block of message.content) {
|
|
282
292
|
if (block.type === "text" && block.text) {
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
chars += 4800; // Estimate images as 4000 chars, or 1200 tokens
|
|
293
|
+
fragments.push(block.text);
|
|
294
|
+
} else if (block.type === "image") {
|
|
295
|
+
extra += IMAGE_TOKEN_ESTIMATE;
|
|
287
296
|
}
|
|
288
297
|
}
|
|
289
298
|
}
|
|
290
|
-
|
|
299
|
+
break;
|
|
291
300
|
}
|
|
292
301
|
case "bashExecution": {
|
|
293
|
-
|
|
294
|
-
|
|
302
|
+
fragments.push(message.command);
|
|
303
|
+
fragments.push(message.output);
|
|
304
|
+
break;
|
|
295
305
|
}
|
|
296
306
|
case "branchSummary":
|
|
297
307
|
case "compactionSummary": {
|
|
298
|
-
|
|
299
|
-
|
|
308
|
+
fragments.push(message.summary);
|
|
309
|
+
break;
|
|
300
310
|
}
|
|
311
|
+
default:
|
|
312
|
+
return 0;
|
|
301
313
|
}
|
|
302
314
|
|
|
303
|
-
return
|
|
315
|
+
if (fragments.length === 0) return extra;
|
|
316
|
+
return extra + countTokens(fragments);
|
|
304
317
|
}
|
|
305
318
|
|
|
306
319
|
function estimateEntriesTokens(entries: SessionEntry[], startIndex: number, endIndex: number): number {
|
|
@@ -114,6 +114,7 @@ export function formatSessionDumpText(options: FormatSessionDumpTextOptions): st
|
|
|
114
114
|
if (c.type === "text") {
|
|
115
115
|
lines.push(c.text);
|
|
116
116
|
} else if (c.type === "thinking") {
|
|
117
|
+
if (c.thinking.trim().length === 0) continue;
|
|
117
118
|
lines.push("<thinking>");
|
|
118
119
|
lines.push(c.thinking);
|
|
119
120
|
lines.push("</thinking>\n");
|
|
@@ -566,6 +566,14 @@ export function buildSessionContext(
|
|
|
566
566
|
let hasPersistedMCPToolSelection = false;
|
|
567
567
|
let mode = "none";
|
|
568
568
|
let modeData: Record<string, unknown> | undefined;
|
|
569
|
+
// Track whether an explicit `model_change` with role="default" has been
|
|
570
|
+
// seen on this path. Once a user (or the agent itself) records an
|
|
571
|
+
// explicit default, later assistant-message inference must NOT overwrite
|
|
572
|
+
// it: temporary fallbacks (retry fallback, context promotion) and
|
|
573
|
+
// server-side model downgrades both produce assistant messages tagged
|
|
574
|
+
// with the wrong model id, which previously clobbered the user's pick on
|
|
575
|
+
// resume (issue #849).
|
|
576
|
+
let hasExplicitDefaultModel = false;
|
|
569
577
|
|
|
570
578
|
for (const entry of path) {
|
|
571
579
|
if (entry.type === "thinking_level_change") {
|
|
@@ -575,12 +583,21 @@ export function buildSessionContext(
|
|
|
575
583
|
if (entry.model) {
|
|
576
584
|
const role = entry.role ?? "default";
|
|
577
585
|
models[role] = entry.model;
|
|
586
|
+
if (role === "default") {
|
|
587
|
+
hasExplicitDefaultModel = true;
|
|
588
|
+
}
|
|
578
589
|
}
|
|
579
590
|
} else if (entry.type === "service_tier_change") {
|
|
580
591
|
serviceTier = entry.serviceTier ?? undefined;
|
|
581
592
|
} else if (entry.type === "message" && entry.message.role === "assistant") {
|
|
582
|
-
//
|
|
583
|
-
|
|
593
|
+
// Legacy fallback: infer default model from assistant messages only
|
|
594
|
+
// when no explicit `model_change` (role=default) entry has been
|
|
595
|
+
// recorded yet. Newer sessions always record an explicit default
|
|
596
|
+
// model_change at the start of the conversation, so this branch is
|
|
597
|
+
// only used to keep pre-model_change sessions working.
|
|
598
|
+
if (!hasExplicitDefaultModel) {
|
|
599
|
+
models.default = `${entry.message.provider}/${entry.message.model}`;
|
|
600
|
+
}
|
|
584
601
|
} else if (entry.type === "compaction") {
|
|
585
602
|
compaction = entry;
|
|
586
603
|
} else if (entry.type === "ttsr_injection") {
|
|
@@ -123,11 +123,10 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
name: "loop",
|
|
126
|
-
description:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
await runtime.ctx.handleLoopCommand(command.args || undefined);
|
|
126
|
+
description:
|
|
127
|
+
"Toggle loop mode. While enabled, the next prompt you send re-submits after every yield. Esc cancels the current iteration; /loop again to disable.",
|
|
128
|
+
handle: async (_command, runtime) => {
|
|
129
|
+
await runtime.ctx.handleLoopCommand();
|
|
131
130
|
runtime.ctx.editor.setText("");
|
|
132
131
|
},
|
|
133
132
|
},
|
|
@@ -356,6 +355,14 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
356
355
|
runtime.ctx.editor.setText("");
|
|
357
356
|
},
|
|
358
357
|
},
|
|
358
|
+
{
|
|
359
|
+
name: "context",
|
|
360
|
+
description: "Show estimated context usage breakdown",
|
|
361
|
+
handle: (_command, runtime) => {
|
|
362
|
+
runtime.ctx.handleContextCommand();
|
|
363
|
+
runtime.ctx.editor.setText("");
|
|
364
|
+
},
|
|
365
|
+
},
|
|
359
366
|
{
|
|
360
367
|
name: "extensions",
|
|
361
368
|
aliases: ["status"],
|
package/src/tools/bash.ts
CHANGED
|
@@ -508,12 +508,17 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
|
|
|
508
508
|
const headLines = head;
|
|
509
509
|
const tailLines = tail;
|
|
510
510
|
|
|
511
|
-
// Check
|
|
511
|
+
// Check both the original command and the cwd-normalized command so
|
|
512
|
+
// leading `cd ... &&` wrappers do not hide either shell-navigation rules
|
|
513
|
+
// or the dedicated-tool command that follows the directory change.
|
|
512
514
|
if (this.session.settings.get("bashInterceptor.enabled")) {
|
|
513
515
|
const rules = this.session.settings.getBashInterceptorRules();
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
516
|
+
const commandsToCheck = rawCommand === command ? [command] : [rawCommand, command];
|
|
517
|
+
for (const commandToCheck of commandsToCheck) {
|
|
518
|
+
const interception = checkBashInterception(commandToCheck, ctx?.toolNames ?? [], rules);
|
|
519
|
+
if (interception.block) {
|
|
520
|
+
throw new ToolError(interception.message ?? "Command blocked");
|
|
521
|
+
}
|
|
517
522
|
}
|
|
518
523
|
}
|
|
519
524
|
|
package/src/tools/debug.ts
CHANGED
|
@@ -52,85 +52,72 @@ import { toolResult } from "./tool-result";
|
|
|
52
52
|
import { clampTimeout } from "./tool-timeouts";
|
|
53
53
|
|
|
54
54
|
const debugSchema = Type.Object({
|
|
55
|
-
action: StringEnum(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
),
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
),
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
function: Type.Optional(Type.String({ description: "function name", examples: ["main", "handle_request"] })),
|
|
97
|
-
name: Type.Optional(Type.String({ description: "variable or data name", examples: ["counter", "buffer"] })),
|
|
98
|
-
condition: Type.Optional(Type.String({ description: "breakpoint condition", examples: ["i == 10", "x > 0"] })),
|
|
99
|
-
hit_condition: Type.Optional(Type.String({ description: "hit condition" })),
|
|
100
|
-
expression: Type.Optional(Type.String({ description: "expression to evaluate", examples: ["x + 1", "obj.field"] })),
|
|
55
|
+
action: StringEnum([
|
|
56
|
+
"launch",
|
|
57
|
+
"attach",
|
|
58
|
+
"set_breakpoint",
|
|
59
|
+
"remove_breakpoint",
|
|
60
|
+
"set_instruction_breakpoint",
|
|
61
|
+
"remove_instruction_breakpoint",
|
|
62
|
+
"data_breakpoint_info",
|
|
63
|
+
"set_data_breakpoint",
|
|
64
|
+
"remove_data_breakpoint",
|
|
65
|
+
"continue",
|
|
66
|
+
"step_over",
|
|
67
|
+
"step_in",
|
|
68
|
+
"step_out",
|
|
69
|
+
"pause",
|
|
70
|
+
"evaluate",
|
|
71
|
+
"stack_trace",
|
|
72
|
+
"threads",
|
|
73
|
+
"scopes",
|
|
74
|
+
"variables",
|
|
75
|
+
"disassemble",
|
|
76
|
+
"read_memory",
|
|
77
|
+
"write_memory",
|
|
78
|
+
"modules",
|
|
79
|
+
"loaded_sources",
|
|
80
|
+
"custom_request",
|
|
81
|
+
"output",
|
|
82
|
+
"terminate",
|
|
83
|
+
"sessions",
|
|
84
|
+
]),
|
|
85
|
+
program: Type.Optional(Type.String({ description: "program path" })),
|
|
86
|
+
args: Type.Optional(Type.Array(Type.String(), { description: "program arguments" })),
|
|
87
|
+
adapter: Type.Optional(Type.String({ description: "debugger adapter (gdb, lldb-dap, debugpy, dlv)" })),
|
|
88
|
+
cwd: Type.Optional(Type.String()),
|
|
89
|
+
file: Type.Optional(Type.String({ description: "source file" })),
|
|
90
|
+
line: Type.Optional(Type.Number({ description: "source line" })),
|
|
91
|
+
function: Type.Optional(Type.String({ description: "function name" })),
|
|
92
|
+
name: Type.Optional(Type.String({ description: "variable or data name" })),
|
|
93
|
+
condition: Type.Optional(Type.String({ description: "breakpoint condition" })),
|
|
94
|
+
hit_condition: Type.Optional(Type.String()),
|
|
95
|
+
expression: Type.Optional(Type.String({ description: "expression to evaluate" })),
|
|
101
96
|
context: Type.Optional(
|
|
102
|
-
Type.String({ description: "evaluate context
|
|
97
|
+
Type.String({ description: "evaluate context: watch | repl | hover | variables | clipboard" }),
|
|
103
98
|
),
|
|
104
|
-
frame_id: Type.Optional(Type.Number(
|
|
99
|
+
frame_id: Type.Optional(Type.Number()),
|
|
105
100
|
scope_id: Type.Optional(Type.Number({ description: "scope variables reference" })),
|
|
106
101
|
variable_ref: Type.Optional(Type.Number({ description: "variable reference" })),
|
|
107
|
-
pid: Type.Optional(Type.Number({ description: "process id for attach"
|
|
108
|
-
port: Type.Optional(Type.Number({ description: "remote attach port"
|
|
109
|
-
host: Type.Optional(Type.String({ description: "remote attach host"
|
|
102
|
+
pid: Type.Optional(Type.Number({ description: "process id for attach" })),
|
|
103
|
+
port: Type.Optional(Type.Number({ description: "remote attach port" })),
|
|
104
|
+
host: Type.Optional(Type.String({ description: "remote attach host" })),
|
|
110
105
|
levels: Type.Optional(Type.Number({ description: "max stack frames" })),
|
|
111
|
-
memory_reference: Type.Optional(
|
|
112
|
-
|
|
113
|
-
),
|
|
114
|
-
|
|
115
|
-
instruction_count: Type.Optional(Type.Number({ description: "instructions to disassemble" })),
|
|
116
|
-
instruction_offset: Type.Optional(Type.Number({ description: "instruction offset" })),
|
|
106
|
+
memory_reference: Type.Optional(Type.String({ description: "memory reference or address" })),
|
|
107
|
+
instruction_reference: Type.Optional(Type.String()),
|
|
108
|
+
instruction_count: Type.Optional(Type.Number()),
|
|
109
|
+
instruction_offset: Type.Optional(Type.Number()),
|
|
117
110
|
count: Type.Optional(Type.Number({ description: "bytes to read" })),
|
|
118
111
|
data: Type.Optional(Type.String({ description: "base64 memory payload" })),
|
|
119
112
|
data_id: Type.Optional(Type.String({ description: "data breakpoint id" })),
|
|
120
|
-
access_type: Type.Optional(
|
|
121
|
-
StringEnum(["read", "write", "readWrite"], { description: "data breakpoint access type" }),
|
|
122
|
-
),
|
|
113
|
+
access_type: Type.Optional(StringEnum(["read", "write", "readWrite"])),
|
|
123
114
|
command: Type.Optional(Type.String({ description: "custom dap request command" })),
|
|
124
|
-
arguments: Type.Optional(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
),
|
|
129
|
-
|
|
130
|
-
resolve_symbols: Type.Optional(Type.Boolean({ description: "resolve symbols during disassembly" })),
|
|
131
|
-
allow_partial: Type.Optional(Type.Boolean({ description: "allow partial writes" })),
|
|
132
|
-
start_module: Type.Optional(Type.Number({ description: "modules start index" })),
|
|
133
|
-
module_count: Type.Optional(Type.Number({ description: "max modules to fetch" })),
|
|
115
|
+
arguments: Type.Optional(Type.Record(Type.String(), Type.Any(), { description: "custom request arguments" })),
|
|
116
|
+
offset: Type.Optional(Type.Number()),
|
|
117
|
+
resolve_symbols: Type.Optional(Type.Boolean()),
|
|
118
|
+
allow_partial: Type.Optional(Type.Boolean()),
|
|
119
|
+
start_module: Type.Optional(Type.Number()),
|
|
120
|
+
module_count: Type.Optional(Type.Number()),
|
|
134
121
|
timeout: Type.Optional(Type.Number({ description: "per-request timeout seconds" })),
|
|
135
122
|
});
|
|
136
123
|
|