@oh-my-pi/pi-coding-agent 14.6.6 → 14.7.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 (66) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/examples/hooks/handoff.ts +1 -1
  3. package/examples/hooks/qna.ts +1 -1
  4. package/examples/sdk/03-custom-prompt.ts +7 -4
  5. package/examples/sdk/README.md +1 -1
  6. package/package.json +12 -12
  7. package/src/autoresearch/index.ts +48 -44
  8. package/src/cli/grep-cli.ts +1 -1
  9. package/src/cli/read-cli.ts +58 -0
  10. package/src/cli.ts +1 -0
  11. package/src/commands/read.ts +40 -0
  12. package/src/commit/agentic/agent.ts +1 -1
  13. package/src/commit/analysis/conventional.ts +1 -1
  14. package/src/commit/analysis/summary.ts +1 -1
  15. package/src/commit/changelog/generate.ts +1 -1
  16. package/src/commit/map-reduce/map-phase.ts +1 -1
  17. package/src/commit/map-reduce/reduce-phase.ts +1 -1
  18. package/src/config/settings-schema.ts +49 -0
  19. package/src/config/settings.ts +71 -1
  20. package/src/dap/client.ts +1 -0
  21. package/src/discovery/builtin.ts +34 -9
  22. package/src/edit/line-hash.ts +34 -4
  23. package/src/edit/modes/hashline.ts +352 -8
  24. package/src/edit/streaming.ts +4 -1
  25. package/src/export/html/index.ts +1 -1
  26. package/src/extensibility/extensions/runner.ts +3 -3
  27. package/src/extensibility/extensions/types.ts +4 -4
  28. package/src/internal-urls/docs-index.generated.ts +1 -1
  29. package/src/main.ts +13 -18
  30. package/src/memories/index.ts +1 -1
  31. package/src/modes/components/agent-dashboard.ts +1 -1
  32. package/src/modes/components/read-tool-group.ts +4 -9
  33. package/src/modes/components/tool-execution.ts +4 -0
  34. package/src/modes/controllers/event-controller.ts +2 -0
  35. package/src/modes/interactive-mode.ts +19 -12
  36. package/src/modes/rpc/rpc-types.ts +1 -1
  37. package/src/modes/utils/context-usage.ts +12 -5
  38. package/src/modes/utils/ui-helpers.ts +1 -0
  39. package/src/prompts/system/plan-mode-active.md +7 -3
  40. package/src/prompts/system/plan-mode-approved.md +5 -0
  41. package/src/prompts/system/project-prompt.md +36 -0
  42. package/src/prompts/system/system-prompt.md +0 -29
  43. package/src/prompts/tools/github.md +1 -0
  44. package/src/prompts/tools/read.md +15 -14
  45. package/src/sdk.ts +29 -28
  46. package/src/session/agent-session.ts +20 -12
  47. package/src/session/compaction/branch-summarization.ts +1 -1
  48. package/src/session/compaction/compaction.ts +3 -3
  49. package/src/session/session-dump-format.ts +10 -5
  50. package/src/session/streaming-output.ts +1 -1
  51. package/src/slash-commands/builtin-registry.ts +2 -2
  52. package/src/system-prompt.ts +35 -3
  53. package/src/task/executor.ts +4 -3
  54. package/src/task/isolation-backend.ts +22 -0
  55. package/src/tools/fetch.ts +4 -4
  56. package/src/tools/gh.ts +187 -0
  57. package/src/tools/inspect-image.ts +1 -1
  58. package/src/tools/output-meta.ts +1 -1
  59. package/src/tools/path-utils.ts +11 -0
  60. package/src/tools/read.ts +393 -204
  61. package/src/tools/search.ts +1 -1
  62. package/src/tools/sqlite-reader.ts +1 -1
  63. package/src/utils/commit-message-generator.ts +1 -1
  64. package/src/utils/title-generator.ts +1 -1
  65. package/src/web/search/providers/anthropic.ts +1 -1
  66. package/src/workspace-tree.ts +396 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [14.7.1] - 2026-05-06
6
+
7
+ ### Added
8
+
9
+ - Added `pr_create` operation to the GitHub tool to create pull requests with title/body (or `fill`), base/head branch, draft, reviewer, assignee, and label options and return a summarized result including the new PR URL
10
+ - Added `read.summarize.prose` setting to keep Markdown and plain-text reads out of the structural summarizer by default.
11
+
12
+ ### Changed
13
+
14
+ - Changed the `PI_GREP_WORKERS` environment variable help text to state that it sets filesystem walker workers, defaults to 4, and uses `0` for automatic worker selection
15
+ - Changed hashline replacement and pure-insert auto-absorb to also drop a single duplicated structural-closing line (`}`, `);`, `]`, etc.) on either boundary when keeping it would unbalance brackets. The pure-insert variant fires regardless of `edit.hashlineAutoDropPureInsertDuplicates`, while the existing 2+ line generic absorb stays gated on that setting.
16
+
17
+ ## [14.7.0] - 2026-05-04
18
+ ### Breaking Changes
19
+
20
+ - Changed session system-prompt APIs to use ordered string block arrays by requiring `buildSystemPrompt`, `CreateAgentSessionOptions.systemPrompt`, `Session.rebuildSystemPrompt`, and extension `before_agent_start`/`getSystemPrompt` hooks to accept and return `systemPrompt: string[]` instead of a plain system-prompt string or separate `projectPrompt` field
21
+ - Changed `buildSystemPrompt` and session `rebuildSystemPrompt` APIs to return `{ systemPrompt, projectPrompt }`, requiring callers expecting a plain system prompt string to update to the new shape
22
+ - Removed the top-level `sel` parameter from the `read` tool schema, requiring callers to migrate to `path`-embedded selectors (for example `path:50-100`, `path:raw`, or `https://...:L1-L40`)
23
+
24
+ ### Added
25
+
26
+ - Added a separate `projectPrompt` artifact containing per-session project context (workstation, context files, AGENTS.md rules, workspace tree, and append prompt) so dynamic context is decoupled from the static system prompt
27
+ - Added `Project prompt` token accounting to context-usage breakdowns and charts
28
+ - Added `tools.elideFileMutationInputs` setting to optionally elide large `write`, `edit`, and `apply_patch` payloads in history after successful mutations
29
+ - Added hashline-style return data for elided `write` calls so tools can include the resulting file content without leaking full input text
30
+ - Added `buildDirectoryTree` and `DirectoryTree` exports to generate configurable directory trees with options for depth, entry limits, hidden-file handling, and truncation caps
31
+ - Added `buildWorkspaceTree` and `WorkspaceTree` exports so callers can precompute and pass a workspace context to prompt generation
32
+ - Added `workspaceTree` support to `buildSystemPrompt` options to reuse a prebuilt directory snapshot
33
+ - Added `read.summarize.enabled`, `read.summarize.minBodyLines`, and `read.summarize.minCommentLines` settings to control whether `read` returns structural summaries and how many multiline body/comment lines are collapsed
34
+ - Added `edit.hashlineAutoDropPureInsertDuplicates` setting to opt into dropping 2+ pure-insert hashline payload lines that duplicate adjacent file context; default is `false`.
35
+
36
+ ### Changed
37
+
38
+ - Updated session dump and HTML export output to serialize ordered system-prompt blocks (including project context) and removed the dedicated project-prompt dump section
39
+ - Renamed context-usage system-prompt accounting from a separate `projectPrompt` bucket to `systemContext` to match the new multi-block prompt structure
40
+ - Changed prompt delivery to inject non-empty `projectPrompt` as a leading `developer` message before conversation messages instead of merging it into the base system prompt
41
+ - Added `projectPrompt` to session dumps to expose the injected per-session project context separately
42
+ - Changed write success output and preview rendering to display hashline-formatted written content from captured file text when mutation inputs are elided
43
+ - Changed `read` directory rendering to return a two-level recency-sorted directory tree (including nested folders) instead of a flat alphabetical entry list, while still applying configurable truncation
44
+ - Changed generated system prompts to include a working-directory tree block after directory context, showing recent files/directories (depth ≤ 3) and truncation notices when entries are elided
45
+ - Changed `read` summary rendering to merge opening- and closing-brace boundaries around elided sections into a single `..` line (including closers like `};` or `})`), reducing those segments to one concise anchored summary line
46
+ - Changed default `read` output for parseable code files without an explicit selector to return a structural summary instead of full verbatim lines, while still supporting full output for `:raw` and explicit ranges
47
+ - Changed truncation/pagination hints in read, archive, and SQLite outputs to use colon syntax (`Use :<offset>`) when continuing reads
48
+ - Changed the read tool UI preview title to include summary elision counts when a summary is returned
49
+ - Changed hashline pure-insert duplicate auto-drop to be opt-in through `edit.hashlineAutoDropPureInsertDuplicates` instead of always enabled.
50
+
51
+ ### Fixed
52
+
53
+ - Fixed selector parsing for colon-containing paths by only splitting `:<sel>` when the suffix matches a valid line-range or `raw` pattern, preventing paths like `db.sqlite:users:42` from being misread as selectors
54
+
5
55
  ## [14.6.6] - 2026-05-04
6
56
 
7
57
  ### Added
@@ -94,7 +94,7 @@ export default function (pi: HookAPI) {
94
94
 
95
95
  const response = await complete(
96
96
  ctx.model!,
97
- { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
97
+ { systemPrompt: [SYSTEM_PROMPT], messages: [userMessage] },
98
98
  { apiKey, signal: loader.signal },
99
99
  );
100
100
 
@@ -85,7 +85,7 @@ export default function (pi: HookAPI) {
85
85
 
86
86
  const response = await complete(
87
87
  ctx.model!,
88
- { systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
88
+ { systemPrompt: [SYSTEM_PROMPT], messages: [userMessage] },
89
89
  { apiKey, signal: loader.signal },
90
90
  );
91
91
 
@@ -7,8 +7,10 @@ import { createAgentSession, SessionManager } from "@oh-my-pi/pi-coding-agent";
7
7
 
8
8
  // Option 1: Replace prompt entirely
9
9
  const { session: session1 } = await createAgentSession({
10
- systemPrompt: `You are a helpful assistant that speaks like a pirate.
10
+ systemPrompt: [
11
+ `You are a helpful assistant that speaks like a pirate.
11
12
  Always end responses with "Arrr!"`,
13
+ ],
12
14
  sessionManager: SessionManager.inMemory(),
13
15
  });
14
16
 
@@ -24,11 +26,12 @@ console.log("\n");
24
26
 
25
27
  // Option 2: Modify default prompt (receives default, returns modified)
26
28
  const { session: session2 } = await createAgentSession({
27
- systemPrompt: defaultPrompt => `${defaultPrompt}
28
-
29
- ## Additional Instructions
29
+ systemPrompt: defaultPrompt => [
30
+ ...defaultPrompt,
31
+ `## Additional Instructions
30
32
  - Always be concise
31
33
  - Use bullet points when listing things`,
34
+ ],
32
35
  sessionManager: SessionManager.inMemory(),
33
36
  });
34
37
 
@@ -87,7 +87,7 @@ const { session } = await createAgentSession({
87
87
  model,
88
88
  authStorage: customAuth,
89
89
  modelRegistry: customRegistry,
90
- systemPrompt: "You are helpful.",
90
+ systemPrompt: ["You are helpful."],
91
91
  toolNames: ["read", "bash"],
92
92
  customTools: [{ tool: myTool }],
93
93
  hooks: [{ factory: myHook }],
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "14.6.6",
4
+ "version": "14.7.1",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -44,16 +44,17 @@
44
44
  "generate-template": "bun scripts/generate-template.ts"
45
45
  },
46
46
  "dependencies": {
47
- "@agentclientprotocol/sdk": "0.20.0",
47
+ "@agentclientprotocol/sdk": "0.21.0",
48
48
  "@mozilla/readability": "^0.6.0",
49
- "@oh-my-pi/omp-stats": "14.6.6",
50
- "@oh-my-pi/pi-agent-core": "14.6.6",
51
- "@oh-my-pi/pi-ai": "14.6.6",
52
- "@oh-my-pi/pi-natives": "14.6.6",
53
- "@oh-my-pi/pi-tui": "14.6.6",
54
- "@oh-my-pi/pi-utils": "14.6.6",
49
+ "@oh-my-pi/omp-stats": "14.7.1",
50
+ "@oh-my-pi/pi-agent-core": "14.7.1",
51
+ "@oh-my-pi/pi-ai": "14.7.1",
52
+ "@oh-my-pi/pi-natives": "14.7.1",
53
+ "@oh-my-pi/pi-tui": "14.7.1",
54
+ "@oh-my-pi/pi-utils": "14.7.1",
55
55
  "@puppeteer/browsers": "^2.13.0",
56
56
  "@sinclair/typebox": "^0.34.49",
57
+ "@types/turndown": "5.0.6",
57
58
  "@xterm/headless": "^6.0.0",
58
59
  "ajv": "^8.20.0",
59
60
  "chalk": "^5.6.2",
@@ -61,16 +62,15 @@
61
62
  "fflate": "0.8.2",
62
63
  "handlebars": "^4.7.9",
63
64
  "linkedom": "^0.18.12",
64
- "lru-cache": "11.3.5",
65
+ "lru-cache": "11.3.6",
65
66
  "markit-ai": "0.5.3",
66
67
  "puppeteer-core": "^24.42.0",
67
68
  "turndown": "7.2.4",
68
69
  "turndown-plugin-gfm": "1.0.2",
69
- "zod": "4.3.6"
70
+ "zod": "4.4.3"
70
71
  },
71
72
  "devDependencies": {
72
- "@types/bun": "^1.3",
73
- "@types/turndown": "5.0.6"
73
+ "@types/bun": "^1.3.13"
74
74
  },
75
75
  "engines": {
76
76
  "bun": ">=1.3.7"
@@ -356,54 +356,58 @@ export const createAutoresearchExtension: ExtensionFactory = api => {
356
356
  ? null
357
357
  : "Heads up: you are not on a dedicated `autoresearch/*` branch. `log_experiment discard` will only revert run-modified files, not reset to baseline — so harness files written before `init_experiment` may not survive a discard. Clean the worktree and re-run `/autoresearch` if you want full revert safety.";
358
358
  return {
359
- systemPrompt: prompt.render(setupPromptTemplate, {
360
- base_system_prompt: event.systemPrompt,
359
+ systemPrompt: [
360
+ prompt.render(setupPromptTemplate, {
361
+ base_system_prompt: event.systemPrompt.join("\n\n"),
362
+ has_goal: goal.trim().length > 0,
363
+ goal,
364
+ working_dir: ctx.cwd,
365
+ has_branch: Boolean(currentBranch),
366
+ branch: currentBranch ?? "",
367
+ has_baseline_warning: baselineWarning !== null,
368
+ baseline_warning: baselineWarning ?? "",
369
+ }),
370
+ ],
371
+ };
372
+ }
373
+ return {
374
+ systemPrompt: [
375
+ prompt.render(promptTemplate, {
376
+ base_system_prompt: event.systemPrompt.join("\n\n"),
361
377
  has_goal: goal.trim().length > 0,
362
378
  goal,
363
379
  working_dir: ctx.cwd,
364
- has_branch: Boolean(currentBranch),
365
- branch: currentBranch ?? "",
366
- has_baseline_warning: baselineWarning !== null,
367
- baseline_warning: baselineWarning ?? "",
380
+ default_metric_name: state.metricName,
381
+ metric_name: state.metricName,
382
+ has_branch: Boolean(state.branch),
383
+ branch: state.branch,
384
+ has_baseline_commit: Boolean(state.baselineCommit),
385
+ baseline_commit: state.baselineCommit ? state.baselineCommit.slice(0, 12) : "",
386
+ has_notes: state.notes.trim().length > 0,
387
+ notes: state.notes,
388
+ current_segment: state.currentSegment + 1,
389
+ current_segment_run_count: currentSegmentResults.length,
390
+ has_baseline_metric: baselineMetric !== null,
391
+ baseline_metric_display: formatNum(baselineMetric, state.metricUnit),
392
+ baseline_run_number: baselineRunNumber,
393
+ has_best_result: bestResult !== null && bestMetric !== null,
394
+ best_metric_display: bestMetric !== null ? formatNum(bestMetric, state.metricUnit) : "-",
395
+ best_run_number: bestResult ? (bestResult.runNumber ?? state.results.indexOf(bestResult) + 1) : null,
396
+ has_recent_results: recentResults.length > 0,
397
+ recent_results: recentResults,
398
+ has_unjustified_runs: unjustifiedRuns.length > 0,
399
+ unjustified_runs: unjustifiedRuns,
400
+ has_pending_run: Boolean(pendingRun),
401
+ pending_run_number: pendingRun?.runNumber,
402
+ pending_run_command: pendingRun?.command,
403
+ pending_run_passed: pendingRun?.passed ?? false,
404
+ has_pending_run_metric: pendingRun?.parsedPrimary !== null && pendingRun?.parsedPrimary !== undefined,
405
+ pending_run_metric_display:
406
+ pendingRun?.parsedPrimary !== null && pendingRun?.parsedPrimary !== undefined
407
+ ? formatNum(pendingRun.parsedPrimary, state.metricUnit)
408
+ : null,
368
409
  }),
369
- };
370
- }
371
- return {
372
- systemPrompt: prompt.render(promptTemplate, {
373
- base_system_prompt: event.systemPrompt,
374
- has_goal: goal.trim().length > 0,
375
- goal,
376
- working_dir: ctx.cwd,
377
- default_metric_name: state.metricName,
378
- metric_name: state.metricName,
379
- has_branch: Boolean(state.branch),
380
- branch: state.branch,
381
- has_baseline_commit: Boolean(state.baselineCommit),
382
- baseline_commit: state.baselineCommit ? state.baselineCommit.slice(0, 12) : "",
383
- has_notes: state.notes.trim().length > 0,
384
- notes: state.notes,
385
- current_segment: state.currentSegment + 1,
386
- current_segment_run_count: currentSegmentResults.length,
387
- has_baseline_metric: baselineMetric !== null,
388
- baseline_metric_display: formatNum(baselineMetric, state.metricUnit),
389
- baseline_run_number: baselineRunNumber,
390
- has_best_result: bestResult !== null && bestMetric !== null,
391
- best_metric_display: bestMetric !== null ? formatNum(bestMetric, state.metricUnit) : "-",
392
- best_run_number: bestResult ? (bestResult.runNumber ?? state.results.indexOf(bestResult) + 1) : null,
393
- has_recent_results: recentResults.length > 0,
394
- recent_results: recentResults,
395
- has_unjustified_runs: unjustifiedRuns.length > 0,
396
- unjustified_runs: unjustifiedRuns,
397
- has_pending_run: Boolean(pendingRun),
398
- pending_run_number: pendingRun?.runNumber,
399
- pending_run_command: pendingRun?.command,
400
- pending_run_passed: pendingRun?.passed ?? false,
401
- has_pending_run_metric: pendingRun?.parsedPrimary !== null && pendingRun?.parsedPrimary !== undefined,
402
- pending_run_metric_display:
403
- pendingRun?.parsedPrimary !== null && pendingRun?.parsedPrimary !== undefined
404
- ? formatNum(pendingRun.parsedPrimary, state.metricUnit)
405
- : null,
406
- }),
410
+ ],
407
411
  };
408
412
  });
409
413
 
@@ -150,7 +150,7 @@ ${chalk.bold("Options:")}
150
150
  --no-gitignore Include files excluded by .gitignore
151
151
 
152
152
  ${chalk.bold("Environment:")}
153
- PI_GREP_WORKERS=0 Disable worker pool (use single-threaded mode)
153
+ PI_GREP_WORKERS=N Set filesystem walker workers (default 4, 0 = auto)
154
154
 
155
155
  ${chalk.bold("Examples:")}
156
156
  ${APP_NAME} grep "import" src/
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Read CLI command handler.
3
+ *
4
+ * Handles `omp read` — invokes the `read` agent tool against a path/URL and
5
+ * prints the resulting content blocks exactly as the model would receive them
6
+ * (including truncation/limit notices appended by the meta-notice wrapper).
7
+ */
8
+ import { getProjectDir } from "@oh-my-pi/pi-utils";
9
+ import chalk from "chalk";
10
+ import { Settings } from "../config/settings";
11
+ import type { ToolSession } from "../tools";
12
+ import { wrapToolWithMetaNotice } from "../tools/output-meta";
13
+ import { ReadTool } from "../tools/read";
14
+ import { renderError } from "../tools/tool-errors";
15
+
16
+ export interface ReadCommandArgs {
17
+ path: string;
18
+ timeout?: number;
19
+ }
20
+
21
+ export async function runReadCommand(cmd: ReadCommandArgs): Promise<void> {
22
+ if (!cmd.path) {
23
+ process.stderr.write(chalk.red("error: path is required\n"));
24
+ process.exit(1);
25
+ }
26
+
27
+ const cwd = getProjectDir();
28
+ const settings = await Settings.init({ cwd });
29
+
30
+ const session: ToolSession = {
31
+ cwd,
32
+ hasUI: false,
33
+ settings,
34
+ getSessionFile: () => null,
35
+ getSessionSpawns: () => "*",
36
+ };
37
+
38
+ const tool = wrapToolWithMetaNotice(new ReadTool(session));
39
+
40
+ try {
41
+ const result = await tool.execute("omp-read", { path: cmd.path, timeout: cmd.timeout });
42
+
43
+ for (const block of result.content) {
44
+ if (block.type === "text") {
45
+ process.stdout.write(block.text);
46
+ if (!block.text.endsWith("\n")) process.stdout.write("\n");
47
+ } else if (block.type === "image") {
48
+ const decodedBytes = Buffer.from(block.data, "base64").byteLength;
49
+ process.stdout.write(
50
+ chalk.dim(`[image content: ${block.mimeType}, ${decodedBytes} bytes base64-decoded]\n`),
51
+ );
52
+ }
53
+ }
54
+ } catch (err) {
55
+ process.stderr.write(`${chalk.red(renderError(err))}\n`);
56
+ process.exit(1);
57
+ }
58
+ }
package/src/cli.ts CHANGED
@@ -53,6 +53,7 @@ const commands: CommandEntry[] = [
53
53
  { name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
54
54
  { name: "setup", load: () => import("./commands/setup").then(m => m.default) },
55
55
  { name: "shell", load: () => import("./commands/shell").then(m => m.default) },
56
+ { name: "read", load: () => import("./commands/read").then(m => m.default) },
56
57
  { name: "ssh", load: () => import("./commands/ssh").then(m => m.default) },
57
58
  { name: "stats", load: () => import("./commands/stats").then(m => m.default) },
58
59
  { name: "update", load: () => import("./commands/update").then(m => m.default) },
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Show what the read tool will return for a given path.
3
+ */
4
+ import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
5
+ import { type ReadCommandArgs, runReadCommand } from "../cli/read-cli";
6
+ import { initTheme } from "../modes/theme/theme";
7
+
8
+ export default class Read extends Command {
9
+ static description = "Show what the read tool will return for a path or URL";
10
+
11
+ static args = {
12
+ path: Args.string({
13
+ description: "Path or URL to read (append :sel for line ranges or raw mode, e.g. src/foo.ts:50-100)",
14
+ required: true,
15
+ }),
16
+ };
17
+
18
+ static flags = {
19
+ timeout: Flags.integer({ description: "Request timeout in seconds (URLs only)" }),
20
+ };
21
+
22
+ static examples = [
23
+ "omp read src/foo.ts",
24
+ "omp read src/foo.ts:50-100",
25
+ "omp read src/foo.ts:raw",
26
+ "omp read https://example.com",
27
+ "omp read path/to/archive.zip:dir/file.ts",
28
+ "omp read path/to/db.sqlite:users:42",
29
+ ];
30
+
31
+ async run(): Promise<void> {
32
+ const { args, flags } = await this.parse(Read);
33
+ const cmd: ReadCommandArgs = {
34
+ path: args.path ?? "",
35
+ timeout: flags.timeout,
36
+ };
37
+ await initTheme();
38
+ await runReadCommand(cmd);
39
+ }
40
+ }
@@ -60,7 +60,7 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
60
60
  settings: input.settings,
61
61
  model: input.model,
62
62
  thinkingLevel: input.thinkingLevel,
63
- systemPrompt,
63
+ systemPrompt: [systemPrompt],
64
64
  customTools: tools,
65
65
  enableLsp: false,
66
66
  enableMCP: false,
@@ -89,7 +89,7 @@ export async function generateConventionalAnalysis({
89
89
  const response = await completeSimple(
90
90
  model,
91
91
  {
92
- systemPrompt: prompt.render(analysisSystemPrompt),
92
+ systemPrompt: [prompt.render(analysisSystemPrompt)],
93
93
  messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
94
94
  tools: [ConventionalAnalysisTool],
95
95
  },
@@ -53,7 +53,7 @@ export async function generateSummary({
53
53
  const response = await completeSimple(
54
54
  model,
55
55
  {
56
- systemPrompt,
56
+ systemPrompt: [systemPrompt],
57
57
  messages: [{ role: "user", content: userPrompt, timestamp: Date.now() }],
58
58
  tools: [SummaryTool],
59
59
  },
@@ -58,7 +58,7 @@ export async function generateChangelogEntries({
58
58
  const response = await completeSimple(
59
59
  model,
60
60
  {
61
- systemPrompt: prompt.render(changelogSystemPrompt),
61
+ systemPrompt: [prompt.render(changelogSystemPrompt)],
62
62
  messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
63
63
  tools: [changelogTool],
64
64
  },
@@ -62,7 +62,7 @@ export async function runMapPhase({
62
62
  context_header: contextHeader,
63
63
  });
64
64
  const request = {
65
- systemPrompt,
65
+ systemPrompt: [systemPrompt],
66
66
  messages: [{ role: "user", content: userContent, timestamp: Date.now() }] as Message[],
67
67
  };
68
68
 
@@ -76,7 +76,7 @@ export async function runReducePhase({
76
76
  const response = await completeSimple(
77
77
  model,
78
78
  {
79
- systemPrompt: prompt.render(reduceSystemPrompt),
79
+ systemPrompt: [prompt.render(reduceSystemPrompt)],
80
80
  messages: [{ role: "user", content: userContent, timestamp: Date.now() }],
81
81
  tools: [ReduceTool],
82
82
  },
@@ -1419,6 +1419,15 @@ export const SETTINGS_SCHEMA = {
1419
1419
  },
1420
1420
  },
1421
1421
 
1422
+ "edit.hashlineAutoDropPureInsertDuplicates": {
1423
+ type: "boolean",
1424
+ default: false,
1425
+ ui: {
1426
+ tab: "editing",
1427
+ label: "Hashline Duplicate Insert Drop",
1428
+ description: "Drop 2+ pure-insert payload lines that duplicate adjacent file context",
1429
+ },
1430
+ },
1422
1431
  "edit.blockAutoGenerated": {
1423
1432
  type: "boolean",
1424
1433
  default: true,
@@ -1466,6 +1475,46 @@ export const SETTINGS_SCHEMA = {
1466
1475
  },
1467
1476
  },
1468
1477
 
1478
+ "read.summarize.enabled": {
1479
+ type: "boolean",
1480
+ default: true,
1481
+ ui: {
1482
+ tab: "editing",
1483
+ label: "Read Summaries",
1484
+ description: "Return structural code summaries when read is called without an explicit selector",
1485
+ },
1486
+ },
1487
+
1488
+ "read.summarize.prose": {
1489
+ type: "boolean",
1490
+ default: false,
1491
+ ui: {
1492
+ tab: "editing",
1493
+ label: "Prose Summaries",
1494
+ description: "Return structural summaries for Markdown and plain text reads",
1495
+ },
1496
+ },
1497
+
1498
+ "read.summarize.minBodyLines": {
1499
+ type: "number",
1500
+ default: 4,
1501
+ ui: {
1502
+ tab: "editing",
1503
+ label: "Read Summary Body Lines",
1504
+ description: "Minimum multiline body or literal length before read summaries collapse it",
1505
+ },
1506
+ },
1507
+
1508
+ "read.summarize.minCommentLines": {
1509
+ type: "number",
1510
+ default: 6,
1511
+ ui: {
1512
+ tab: "editing",
1513
+ label: "Read Summary Comment Lines",
1514
+ description: "Minimum multiline block comment length before read summaries collapse it",
1515
+ },
1516
+ },
1517
+
1469
1518
  "read.toolResultPreview": {
1470
1519
  type: "boolean",
1471
1520
  default: false,
@@ -12,6 +12,7 @@
12
12
  */
13
13
 
14
14
  import * as fs from "node:fs";
15
+ import * as os from "node:os";
15
16
  import * as path from "node:path";
16
17
  import {
17
18
  getAgentDbPath,
@@ -98,6 +99,74 @@ function setByPath(obj: RawSettings, segments: string[], value: unknown): void {
98
99
  current[segments[segments.length - 1]] = value;
99
100
  }
100
101
 
102
+ const PATH_SCOPED_ARRAY_SETTINGS = new Set<SettingPath>(["enabledModels", "disabledProviders"]);
103
+
104
+ type PathScopedStringArrayEntry = {
105
+ path?: unknown;
106
+ paths?: unknown;
107
+ pathPrefix?: unknown;
108
+ pathPrefixes?: unknown;
109
+ values?: unknown;
110
+ items?: unknown;
111
+ models?: unknown;
112
+ providers?: unknown;
113
+ };
114
+
115
+ function normalizePathPrefix(prefix: string): string {
116
+ const expanded =
117
+ prefix === "~" ? os.homedir() : prefix.startsWith("~/") ? path.join(os.homedir(), prefix.slice(2)) : prefix;
118
+ return path.resolve(expanded);
119
+ }
120
+
121
+ function pathMatchesPrefix(cwd: string, prefix: string): boolean {
122
+ const relative = path.relative(normalizePathPrefix(prefix), path.resolve(cwd));
123
+ return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative));
124
+ }
125
+
126
+ function stringArrayFromUnknown(value: unknown): string[] {
127
+ if (typeof value === "string") return [value];
128
+ if (Array.isArray(value)) return value.filter((item): item is string => typeof item === "string");
129
+ return [];
130
+ }
131
+
132
+ function resolvePathScopedStringArray(settingPath: SettingPath, value: unknown, cwd: string): string[] | undefined {
133
+ if (!PATH_SCOPED_ARRAY_SETTINGS.has(settingPath) || !Array.isArray(value)) return undefined;
134
+
135
+ const resolved: string[] = [];
136
+ for (const entry of value) {
137
+ if (typeof entry === "string") {
138
+ resolved.push(entry);
139
+ continue;
140
+ }
141
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
142
+
143
+ const scoped = entry as PathScopedStringArrayEntry;
144
+ const prefixes = [
145
+ ...stringArrayFromUnknown(scoped.path),
146
+ ...stringArrayFromUnknown(scoped.paths),
147
+ ...stringArrayFromUnknown(scoped.pathPrefix),
148
+ ...stringArrayFromUnknown(scoped.pathPrefixes),
149
+ ];
150
+ if (prefixes.length === 0 || !prefixes.some(prefix => pathMatchesPrefix(cwd, prefix))) continue;
151
+
152
+ const values =
153
+ settingPath === "enabledModels"
154
+ ? [
155
+ ...stringArrayFromUnknown(scoped.values),
156
+ ...stringArrayFromUnknown(scoped.items),
157
+ ...stringArrayFromUnknown(scoped.models),
158
+ ]
159
+ : [
160
+ ...stringArrayFromUnknown(scoped.values),
161
+ ...stringArrayFromUnknown(scoped.items),
162
+ ...stringArrayFromUnknown(scoped.providers),
163
+ ];
164
+ resolved.push(...values);
165
+ }
166
+
167
+ return resolved;
168
+ }
169
+
101
170
  // ═══════════════════════════════════════════════════════════════════════════
102
171
  // Settings Class
103
172
  // ═══════════════════════════════════════════════════════════════════════════
@@ -201,7 +270,8 @@ export class Settings {
201
270
  const segments = path.split(".");
202
271
  const value = getByPath(this.#merged, segments);
203
272
  if (value !== undefined) {
204
- return value as SettingValue<P>;
273
+ const pathScopedValue = resolvePathScopedStringArray(path, value, this.#cwd);
274
+ return (pathScopedValue ?? value) as SettingValue<P>;
205
275
  }
206
276
  return getDefault(path);
207
277
  }
package/src/dap/client.ts CHANGED
@@ -584,6 +584,7 @@ function socketToSink(socket: Bun.Socket<undefined>): DapWriteSink {
584
584
  },
585
585
  flush() {
586
586
  socket.flush();
587
+ return undefined;
587
588
  },
588
589
  };
589
590
  }