@oh-my-pi/pi-coding-agent 14.9.2 → 14.9.5

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 (97) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/package.json +7 -7
  3. package/scripts/format-prompts.ts +3 -3
  4. package/src/async/job-manager.ts +66 -9
  5. package/src/capability/rule.ts +20 -0
  6. package/src/config/model-registry.ts +13 -0
  7. package/src/config/model-resolver.ts +8 -2
  8. package/src/config/prompt-templates.ts +0 -5
  9. package/src/config/settings-schema.ts +39 -1
  10. package/src/edit/index.ts +8 -0
  11. package/src/edit/renderer.ts +6 -1
  12. package/src/edit/streaming.ts +53 -2
  13. package/src/eval/eval.lark +10 -31
  14. package/src/eval/index.ts +1 -0
  15. package/src/eval/js/context-manager.ts +1 -38
  16. package/src/eval/js/prelude.txt +0 -2
  17. package/src/eval/parse.ts +156 -255
  18. package/src/eval/py/executor.ts +24 -8
  19. package/src/eval/py/index.ts +1 -0
  20. package/src/eval/py/prelude.py +11 -80
  21. package/src/eval/sniff.ts +28 -0
  22. package/src/export/html/template.css +50 -0
  23. package/src/export/html/template.generated.ts +1 -1
  24. package/src/export/html/template.js +229 -17
  25. package/src/extensibility/plugins/loader.ts +31 -6
  26. package/src/extensibility/skills.ts +20 -0
  27. package/src/hashline/constants.ts +20 -0
  28. package/src/hashline/grammar.lark +16 -23
  29. package/src/hashline/hash.ts +4 -34
  30. package/src/hashline/input.ts +16 -2
  31. package/src/hashline/parser.ts +12 -1
  32. package/src/internal-urls/agent-protocol.ts +64 -52
  33. package/src/internal-urls/artifact-protocol.ts +52 -51
  34. package/src/internal-urls/docs-index.generated.ts +34 -1
  35. package/src/internal-urls/index.ts +6 -19
  36. package/src/internal-urls/local-protocol.ts +50 -7
  37. package/src/internal-urls/mcp-protocol.ts +3 -8
  38. package/src/internal-urls/memory-protocol.ts +90 -59
  39. package/src/internal-urls/pi-protocol.ts +1 -0
  40. package/src/internal-urls/router.ts +40 -23
  41. package/src/internal-urls/rule-protocol.ts +3 -20
  42. package/src/internal-urls/skill-protocol.ts +5 -27
  43. package/src/internal-urls/types.ts +18 -2
  44. package/src/main.ts +1 -1
  45. package/src/mcp/manager.ts +17 -0
  46. package/src/modes/components/session-observer-overlay.ts +2 -2
  47. package/src/modes/components/tool-execution.ts +6 -0
  48. package/src/modes/components/tree-selector.ts +4 -0
  49. package/src/modes/controllers/event-controller.ts +23 -2
  50. package/src/modes/controllers/mcp-command-controller.ts +7 -10
  51. package/src/modes/interactive-mode.ts +2 -2
  52. package/src/modes/theme/theme.ts +27 -27
  53. package/src/modes/types.ts +1 -1
  54. package/src/modes/utils/ui-helpers.ts +14 -9
  55. package/src/prompts/commands/orchestrate.md +1 -0
  56. package/src/prompts/system/custom-system-prompt.md +0 -2
  57. package/src/prompts/system/project-prompt.md +10 -0
  58. package/src/prompts/system/subagent-system-prompt.md +18 -9
  59. package/src/prompts/system/subagent-user-prompt.md +1 -10
  60. package/src/prompts/system/system-prompt.md +159 -232
  61. package/src/prompts/tools/ask.md +0 -1
  62. package/src/prompts/tools/bash.md +0 -34
  63. package/src/prompts/tools/eval.md +27 -16
  64. package/src/prompts/tools/github.md +6 -5
  65. package/src/prompts/tools/hashline.md +1 -0
  66. package/src/prompts/tools/job.md +14 -6
  67. package/src/prompts/tools/task.md +20 -3
  68. package/src/registry/agent-registry.ts +2 -1
  69. package/src/sdk.ts +87 -89
  70. package/src/session/agent-session.ts +107 -37
  71. package/src/session/artifacts.ts +7 -4
  72. package/src/session/session-manager.ts +30 -1
  73. package/src/ssh/connection-manager.ts +32 -16
  74. package/src/ssh/sshfs-mount.ts +10 -7
  75. package/src/system-prompt.ts +3 -9
  76. package/src/task/executor.ts +23 -7
  77. package/src/task/index.ts +57 -36
  78. package/src/tool-discovery/tool-index.ts +21 -8
  79. package/src/tools/ast-edit.ts +3 -2
  80. package/src/tools/ast-grep.ts +3 -2
  81. package/src/tools/bash.ts +30 -50
  82. package/src/tools/browser/tab-supervisor.ts +12 -2
  83. package/src/tools/eval.ts +59 -44
  84. package/src/tools/fetch.ts +1 -1
  85. package/src/tools/gh.ts +140 -4
  86. package/src/tools/index.ts +12 -11
  87. package/src/tools/job.ts +48 -12
  88. package/src/tools/path-utils.ts +21 -1
  89. package/src/tools/read.ts +74 -31
  90. package/src/tools/search.ts +16 -3
  91. package/src/tools/todo-write.ts +1 -1
  92. package/src/utils/file-display-mode.ts +11 -5
  93. package/src/web/scrapers/mastodon.ts +1 -1
  94. package/src/web/scrapers/repology.ts +7 -7
  95. package/src/internal-urls/jobs-protocol.ts +0 -119
  96. package/src/task/template.ts +0 -47
  97. package/src/tools/bash-normalize.ts +0 -107
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  if "__omp_prelude_loaded__" not in globals():
4
4
  __omp_prelude_loaded__ = True
5
5
  from pathlib import Path
6
- import os, json, shutil, subprocess
6
+ import os, json
7
7
  from IPython.display import display as _ipy_display, JSON
8
8
 
9
9
  _PRESENTABLE_REPRS = (
@@ -79,79 +79,6 @@ if "__omp_prelude_loaded__" not in globals():
79
79
  f.write(content)
80
80
  _emit_status("append", path=str(p), chars=len(content))
81
81
  return p
82
- class ShellResult:
83
- """Result from shell command execution."""
84
- __slots__ = ("args", "stdout", "stderr", "returncode")
85
- def __init__(self, args: str, stdout: str, stderr: str, returncode: int):
86
- self.args = args
87
- self.stdout = stdout
88
- self.stderr = stderr
89
- self.returncode = returncode
90
-
91
- @property
92
- def code(self) -> int:
93
- return self.returncode
94
-
95
- @property
96
- def exit_code(self) -> int:
97
- return self.returncode
98
-
99
- def check_returncode(self) -> None:
100
- if self.returncode != 0:
101
- raise subprocess.CalledProcessError(
102
- self.returncode, self.args, output=self.stdout, stderr=self.stderr
103
- )
104
-
105
- def __repr__(self):
106
- if self.returncode == 0:
107
- return ""
108
- return f"exit code {self.returncode}"
109
-
110
- def __bool__(self):
111
- return self.returncode == 0
112
-
113
- def _make_shell_result(proc: subprocess.CompletedProcess[str], cmd: str) -> ShellResult:
114
- """Create ShellResult and emit status."""
115
- output = proc.stdout + proc.stderr if proc.stderr else proc.stdout
116
- _emit_status("sh", cmd=cmd[:80], code=proc.returncode, output=output[:500])
117
- return ShellResult(cmd, proc.stdout, proc.stderr, proc.returncode)
118
-
119
- import signal as _signal
120
-
121
- def _run_with_interrupt(args: list[str], cwd: str | None, timeout: int | None, cmd: str) -> ShellResult:
122
- """Run subprocess with proper interrupt handling."""
123
- proc = subprocess.Popen(
124
- args,
125
- cwd=cwd,
126
- stdout=subprocess.PIPE,
127
- stderr=subprocess.PIPE,
128
- text=True,
129
- start_new_session=True,
130
- )
131
- try:
132
- stdout, stderr = proc.communicate(timeout=timeout)
133
- except KeyboardInterrupt:
134
- os.killpg(proc.pid, _signal.SIGINT)
135
- try:
136
- stdout, stderr = proc.communicate(timeout=2)
137
- except subprocess.TimeoutExpired:
138
- os.killpg(proc.pid, _signal.SIGKILL)
139
- stdout, stderr = proc.communicate()
140
- result = subprocess.CompletedProcess(args, -_signal.SIGINT, stdout, stderr)
141
- return _make_shell_result(result, cmd)
142
- except subprocess.TimeoutExpired:
143
- os.killpg(proc.pid, _signal.SIGKILL)
144
- stdout, stderr = proc.communicate()
145
- result = subprocess.CompletedProcess(args, -_signal.SIGKILL, stdout, stderr)
146
- return _make_shell_result(result, cmd)
147
- result = subprocess.CompletedProcess(args, proc.returncode, stdout, stderr)
148
- return _make_shell_result(result, cmd)
149
-
150
- def run(cmd: str, *, cwd: str | Path | None = None, timeout: int | None = None) -> ShellResult:
151
- """Run a shell command. Returns ShellResult with stdout/stderr and returncode/exit_code fields."""
152
- shell_path = shutil.which("bash") or shutil.which("sh") or "/bin/sh"
153
- args = [shell_path, "-c", cmd]
154
- return _run_with_interrupt(args, str(cwd) if cwd else None, timeout, cmd)
155
82
 
156
83
  def sort(text: str, *, reverse: bool = False, unique: bool = False) -> str:
157
84
  """Sort lines of text."""
@@ -268,12 +195,16 @@ if "__omp_prelude_loaded__" not in globals():
268
195
  output('explore_0', offset=10, limit=20) # Lines 10-29
269
196
  output('explore_0', 'reviewer_1') # Read multiple outputs
270
197
  """
271
- session_file = os.environ.get("PI_SESSION_FILE")
272
- if not session_file:
273
- _emit_status("output", error="No session file available")
274
- raise RuntimeError("No session - output artifacts unavailable")
275
-
276
- artifacts_dir = session_file.rsplit(".", 1)[0] # Strip .jsonl extension
198
+ # Prefer PI_ARTIFACTS_DIR so subagents resolve through the parent's
199
+ # shared artifacts dir; fall back to deriving from PI_SESSION_FILE
200
+ # for legacy callers / top-level sessions where the two coincide.
201
+ artifacts_dir = os.environ.get("PI_ARTIFACTS_DIR")
202
+ if not artifacts_dir:
203
+ session_file = os.environ.get("PI_SESSION_FILE")
204
+ if not session_file:
205
+ _emit_status("output", error="No session file available")
206
+ raise RuntimeError("No session - output artifacts unavailable")
207
+ artifacts_dir = session_file.rsplit(".", 1)[0] # Strip .jsonl extension
277
208
  if not Path(artifacts_dir).exists():
278
209
  _emit_status("output", error="Artifacts directory not found", path=artifacts_dir)
279
210
  raise RuntimeError(f"No artifacts directory found: {artifacts_dir}")
@@ -0,0 +1,28 @@
1
+ import type { EvalLanguage } from "./types";
2
+
3
+ /**
4
+ * Best-effort language sniff for cells with no explicit `language`.
5
+ *
6
+ * Order:
7
+ * 1. Shebang on first line (`#!/usr/bin/env python`, `#!/usr/bin/env node`, etc.)
8
+ * 2. Strong syntactic markers unique to one language. Bias false negatives over
9
+ * false positives — anything ambiguous returns `undefined` and the caller
10
+ * falls back to the default-backend rules.
11
+ */
12
+ export function sniffEvalLanguage(code: string): EvalLanguage | undefined {
13
+ const stripped = code.replace(/^\s+/, "");
14
+ if (stripped.startsWith("#!")) {
15
+ const firstLine = stripped.split("\n", 1)[0]!.toLowerCase();
16
+ if (/(\bpython\d?\b|\bipython\b)/.test(firstLine)) return "python";
17
+ if (/(\bnode\b|\bbun\b|\bdeno\b|\bjavascript\b|\bjs\b)/.test(firstLine)) return "js";
18
+ }
19
+ const jsMarkers =
20
+ /(^|\n)\s*(const|let|var|async\s+function|function\s*\*?\s*[\w$]*\s*\(|import\s+[^\n]+\sfrom\s|export\s+(default|const|let|function|class|async)|require\s*\(|console\.\w+\s*\(|=>|;\s*$)/m;
21
+ const pyMarkers =
22
+ /(^|\n)\s*(def\s+\w+\s*\(|from\s+[\w.]+\s+import|import\s+\w+(\s+as\s+\w+)?\s*$|class\s+\w+\s*[(:]|print\s*\(|elif\s+[^\n]*:|with\s+[^\n]+:\s*$|@[\w.]+\s*$)/m;
23
+ const hasJs = jsMarkers.test(code);
24
+ const hasPy = pyMarkers.test(code);
25
+ if (hasJs && !hasPy) return "js";
26
+ if (hasPy && !hasJs) return "python";
27
+ return undefined;
28
+ }
@@ -179,6 +179,10 @@
179
179
  color: var(--accent);
180
180
  }
181
181
 
182
+ .tree-role-developer {
183
+ color: var(--dim);
184
+ }
185
+
182
186
  .tree-role-assistant {
183
187
  color: var(--success);
184
188
  }
@@ -316,6 +320,14 @@
316
320
  position: relative;
317
321
  }
318
322
 
323
+ .user-message.developer-message {
324
+ opacity: 0.7;
325
+ }
326
+
327
+ .user-message.developer-message .markdown-content {
328
+ color: var(--dim);
329
+ }
330
+
319
331
  .assistant-message {
320
332
  padding: 0;
321
333
  position: relative;
@@ -650,6 +662,7 @@
650
662
  padding: var(--line-height);
651
663
  border-radius: 4px;
652
664
  margin-bottom: var(--line-height);
665
+ cursor: pointer;
653
666
  }
654
667
 
655
668
  .tools-header {
@@ -658,6 +671,35 @@
658
671
  margin-bottom: var(--line-height);
659
672
  }
660
673
 
674
+ .tools-list.collapsed .tools-header {
675
+ margin-bottom: 0;
676
+ }
677
+
678
+ .tools-list.collapsed .tools-content {
679
+ display: none;
680
+ }
681
+
682
+ .tools-list:not(.collapsed) .tools-collapsed {
683
+ display: none;
684
+ }
685
+
686
+ .tools-collapsed {
687
+ margin-top: 6px;
688
+ display: flex;
689
+ flex-wrap: wrap;
690
+ gap: 4px;
691
+ }
692
+
693
+ .tool-name-chip {
694
+ display: inline-block;
695
+ padding: 1px 6px;
696
+ border-radius: 3px;
697
+ background: var(--container-bg);
698
+ color: var(--text);
699
+ font-size: 11px;
700
+ font-weight: 500;
701
+ }
702
+
661
703
  .tool-item {
662
704
  font-size: 11px;
663
705
  }
@@ -726,6 +768,14 @@
726
768
  color: var(--warning);
727
769
  }
728
770
 
771
+ .tool-intent {
772
+ color: var(--muted);
773
+ font-style: italic;
774
+ font-size: 11px;
775
+ margin-bottom: 4px;
776
+ opacity: 0.85;
777
+ }
778
+
729
779
  .tool-args {
730
780
  margin-top: 4px;
731
781
  color: var(--toolOutput);