@oh-my-pi/pi-coding-agent 16.1.18 → 16.1.20

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 (57) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/cli.js +15452 -15142
  3. package/dist/types/cli/gallery-cli.d.ts +6 -0
  4. package/dist/types/commands/gallery.d.ts +1 -1
  5. package/dist/types/config/service-tier.d.ts +34 -0
  6. package/dist/types/config/settings-schema.d.ts +36 -33
  7. package/dist/types/edit/hashline/filesystem.d.ts +1 -18
  8. package/dist/types/extensibility/plugins/legacy-pi-bundled-keys.d.ts +10 -0
  9. package/dist/types/extensibility/plugins/legacy-pi-bundled-registry.d.ts +4 -1
  10. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +12 -0
  11. package/dist/types/internal-urls/skill-protocol.d.ts +2 -2
  12. package/dist/types/internal-urls/types.d.ts +3 -0
  13. package/dist/types/modes/components/custom-editor.d.ts +0 -2
  14. package/dist/types/modes/controllers/input-controller.d.ts +0 -1
  15. package/dist/types/session/agent-session.d.ts +2 -0
  16. package/dist/types/task/executor.d.ts +9 -1
  17. package/dist/types/task/parallel.d.ts +17 -1
  18. package/dist/types/tools/index.d.ts +3 -1
  19. package/dist/types/tools/path-utils.d.ts +3 -0
  20. package/package.json +13 -13
  21. package/scripts/build-binary.ts +20 -0
  22. package/scripts/generate-legacy-pi-bundled-registry.ts +404 -0
  23. package/src/cli/gallery-cli.ts +31 -2
  24. package/src/cli/gallery-fixtures/agentic.ts +13 -4
  25. package/src/commands/gallery.ts +11 -3
  26. package/src/config/service-tier.ts +87 -0
  27. package/src/config/settings-schema.ts +48 -23
  28. package/src/edit/hashline/filesystem.ts +14 -0
  29. package/src/eval/agent-bridge.ts +2 -0
  30. package/src/extensibility/plugins/legacy-pi-bundled-keys.ts +987 -0
  31. package/src/extensibility/plugins/legacy-pi-bundled-registry.ts +3330 -18
  32. package/src/extensibility/plugins/legacy-pi-compat.ts +29 -14
  33. package/src/internal-urls/local-protocol.ts +116 -9
  34. package/src/internal-urls/skill-protocol.ts +3 -3
  35. package/src/internal-urls/types.ts +3 -0
  36. package/src/main.ts +1 -0
  37. package/src/mcp/transports/stdio.ts +5 -0
  38. package/src/modes/components/custom-editor.test.ts +7 -5
  39. package/src/modes/components/custom-editor.ts +6 -16
  40. package/src/modes/controllers/input-controller.ts +143 -137
  41. package/src/sdk.ts +1 -0
  42. package/src/session/agent-session.ts +72 -15
  43. package/src/session/messages.ts +70 -47
  44. package/src/session/session-history-format.ts +21 -4
  45. package/src/task/executor.ts +79 -2
  46. package/src/task/index.ts +11 -6
  47. package/src/task/parallel.ts +59 -7
  48. package/src/tools/ast-edit.ts +1 -0
  49. package/src/tools/ast-grep.ts +1 -0
  50. package/src/tools/find.ts +1 -0
  51. package/src/tools/index.ts +3 -1
  52. package/src/tools/irc.ts +16 -2
  53. package/src/tools/path-utils.ts +4 -0
  54. package/src/tools/read.ts +43 -17
  55. package/src/tools/search.ts +4 -0
  56. package/src/utils/shell-snapshot-fn-env.sh +60 -0
  57. package/src/utils/shell-snapshot.ts +77 -20
@@ -0,0 +1,60 @@
1
+ # Helpers inlined into `generateSnapshotScript` (shell-snapshot.ts).
2
+ #
3
+ # Activation idioms like `mise activate` install a shell function whose body
4
+ # expands a sidecar env var (e.g. `mise() { command "$__MISE_EXE" "$@"; }`).
5
+ # The function survives `declare -f`/`typeset -f` capture, but the helper var
6
+ # is set on the rc-sourced shell and lost when only PATH gets re-exported.
7
+ # The replay shell then calls `command "" …` and dies with
8
+ # `command: command not found:` (issue #3470).
9
+ #
10
+ # `__omp_emit_referenced_exports` reads captured function bodies from stdin,
11
+ # extracts every `$VAR` / `${VAR…}` reference, and emits a single
12
+ # `export VAR='value'` line to stdout for each referenced var that
13
+ # (a) is currently set in this shell and (b) is not a shell-internal name.
14
+ # The script bodies are scanned, not interpreted — over-inclusion (refs inside
15
+ # comments / heredocs) is harmless because we only emit names that are set.
16
+ #
17
+ # Pure POSIX so the same helper works under bash and zsh.
18
+
19
+ # Wrap $1 in single quotes, escaping every embedded `'` as `'\''`.
20
+ __omp_sq_quote() {
21
+ __omp_qbuf=$1
22
+ __omp_qout=
23
+ __omp_sq=\'
24
+ while case "$__omp_qbuf" in *$__omp_sq*) true ;; *) false ;; esac; do
25
+ __omp_qout=$__omp_qout${__omp_qbuf%%$__omp_sq*}"'\\''"
26
+ __omp_qbuf=${__omp_qbuf#*$__omp_sq}
27
+ done
28
+ __omp_qout=$__omp_qout$__omp_qbuf
29
+ printf "'%s'" "$__omp_qout"
30
+ }
31
+
32
+ # Emit `export NAME='value'` for $1 unless the name is a shell-internal we
33
+ # must never overwrite, a likely secret (token / key / password / credential
34
+ # patterns — kept conservative, since `__MISE_EXE` and `FOO_DIR` style helper
35
+ # vars never carry secrets), or the var is unset. POSIX `case` patterns are
36
+ # byte-exact so we list common uppercase variants; lowercase secret vars are
37
+ # rare and out of scope.
38
+ __omp_emit_export_for() {
39
+ case "$1" in
40
+ _|PATH|HOME|USER|LOGNAME|PWD|OLDPWD|SHELL|SHLVL|TERM|TERMINFO|TERMCAP|IFS|TMPDIR|TMOUT|LANG|RANDOM|LINENO|SECONDS|FUNCNAME|HISTFILE|HISTSIZE|HISTFILESIZE|HISTCMD|PS1|PS2|PS3|PS4|UID|EUID|GROUPS|HOSTNAME|HOSTTYPE|OSTYPE|MACHTYPE|PIPESTATUS|BASH|ZSH|argv|PROMPT|RPROMPT|RPS1|RPS2|status|pipestatus|COLUMNS|LINES|COLORTERM|FUNCNEST) return ;;
41
+ LC_*|BASH_*|ZSH_*) return ;;
42
+ # Common secret-name patterns — never materialise these into the
43
+ # snapshot file even though it's now created 0600 (defence in depth
44
+ # against the file ending up in a backup, tarball, or NFS share).
45
+ *TOKEN*|*SECRET*|*PASSWORD*|*PASSWD*|*API_KEY*|*PRIVATE_KEY*|*ACCESS_KEY*|*CREDENTIAL*|*SESSION_KEY*) return ;;
46
+ esac
47
+ eval "[ \"\${$1+x}\" = x ]" 2>/dev/null || return
48
+ eval "__omp_xv=\"\${$1}\"" 2>/dev/null || return
49
+ printf 'export %s=%s\n' "$1" "$(__omp_sq_quote "$__omp_xv")"
50
+ }
51
+
52
+ # Read function bodies on stdin, emit dedup'd export lines on stdout.
53
+ __omp_emit_referenced_exports() {
54
+ grep -oE '\$\{?[A-Za-z_][A-Za-z0-9_]*' \
55
+ | sed -E 's/^\$\{?//' \
56
+ | sort -u \
57
+ | while IFS= read -r __omp_name; do
58
+ __omp_emit_export_for "$__omp_name"
59
+ done
60
+ }
@@ -9,6 +9,7 @@ import * as fs from "node:fs";
9
9
  import * as os from "node:os";
10
10
  import * as path from "node:path";
11
11
  import { logger, postmortem } from "@oh-my-pi/pi-utils";
12
+ import fnEnvHelper from "./shell-snapshot-fn-env.sh" with { type: "text" };
12
13
 
13
14
  const cachedSnapshotPaths = new Map<string, string>();
14
15
  const SNAPSHOT_TIMEOUT_MS = 2_000;
@@ -83,9 +84,13 @@ function sanitizeSnapshotEnv(env: Record<string, string | undefined>): Record<st
83
84
 
84
85
  /**
85
86
  * Get the user's shell config file path.
87
+ *
88
+ * Honours `env.HOME` when present so a caller can target a sandboxed/test
89
+ * home, falling back to `os.homedir()` (which Bun resolves once at startup
90
+ * and caches — `process.env.HOME` overrides don't affect it).
86
91
  */
87
- function getShellConfigFile(shell: string): string {
88
- const home = os.homedir();
92
+ function getShellConfigFile(shell: string, env: Record<string, string | undefined>): string {
93
+ const home = env.HOME || os.homedir();
89
94
  if (shell.includes("zsh")) return path.join(home, ".zshrc");
90
95
  if (shell.includes("bash")) return path.join(home, ".bashrc");
91
96
  return path.join(home, ".profile");
@@ -105,26 +110,22 @@ function generateSnapshotScript(shell: string, snapshotPath: string, rcFile: str
105
110
  // Escape the snapshot path for shell
106
111
  const escapedPath = snapshotPath.replace(/'/g, "'\\''");
107
112
 
108
- // Function extraction differs between bash and zsh
109
- const functionScript = isZsh
110
- ? `
111
- echo "# Functions" >> "$SNAPSHOT_FILE"
112
- # Force autoload all functions first
113
+ // Function extraction differs between bash and zsh. Each form prints function
114
+ // bodies on stdout so we can both persist them AND scan their bodies for
115
+ // referenced env vars (issue #3470).
116
+ const functionExtractor = isZsh
117
+ ? `# Force autoload all functions first
113
118
  typeset -f > /dev/null 2>&1
114
119
  # Get user function names - filter system/private ones
115
120
  typeset +f 2>/dev/null | grep -vE '^(_|__)' | grep -vE '${commonToolsRegex}' | while read func; do
116
- typeset -f "$func" >> "$SNAPSHOT_FILE" 2>/dev/null
117
- done
118
- `
119
- : `
120
- echo "# Functions" >> "$SNAPSHOT_FILE"
121
- # Force autoload all functions first
121
+ typeset -f "$func" 2>/dev/null
122
+ done`
123
+ : `# Force autoload all functions first
122
124
  declare -f > /dev/null 2>&1
123
125
  # Get user function names - filter system/private ones
124
126
  declare -F 2>/dev/null | cut -d' ' -f3 | grep -vE '^(_|__)' | grep -vE '${commonToolsRegex}' | while read func; do
125
- declare -f "$func" >> "$SNAPSHOT_FILE" 2>/dev/null
126
- done
127
- `;
127
+ declare -f "$func" 2>/dev/null
128
+ done`;
128
129
 
129
130
  // Shell options extraction
130
131
  const optionsScript = isZsh
@@ -142,16 +143,45 @@ echo "shopt -s expand_aliases" >> "$SNAPSHOT_FILE"
142
143
  return `
143
144
  SNAPSHOT_FILE='${escapedPath}'
144
145
 
146
+ # Snapshot may inline env-var values referenced by captured functions (#3470).
147
+ # Defence in depth: (a) JS caller pre-creates the file at 0600 so the shell's
148
+ # \`>|\`/\`>>\` redirections truncate/append without changing the inode mode,
149
+ # (b) we \`umask 077\` before AND after sourcing the rc so any other file the
150
+ # script creates is private even when the rc resets umask to 022, (c) JS
151
+ # caller chmods file + dir after the script exits.
152
+ umask 077
153
+
145
154
  # Source user's rc file if it exists
146
155
  ${hasRcFile ? `source "${rcFile}" < /dev/null 2>/dev/null` : "# No user config file to source"}
147
156
 
157
+ # Re-tighten umask in case the rc reset it (common \`.bashrc\`/\`.zshrc\` set
158
+ # \`umask 022\` for the interactive shell).
159
+ umask 077
160
+
148
161
  # Create/clear the snapshot file
149
162
  echo "# Shell snapshot - generated by omp agent" >| "$SNAPSHOT_FILE"
150
163
 
151
164
  # Unalias everything first to avoid conflicts when sourced
152
165
  echo "unalias -a 2>/dev/null || true" >> "$SNAPSHOT_FILE"
153
166
 
154
- ${functionScript}
167
+ # Capture function definitions into a variable so we can both persist them
168
+ # and scan their bodies for env-var references (issue #3470).
169
+ __omp_funcs=$(
170
+ ${functionExtractor}
171
+ )
172
+ echo "# Functions" >> "$SNAPSHOT_FILE"
173
+ printf '%s\\n' "$__omp_funcs" >> "$SNAPSHOT_FILE"
174
+
175
+ # Re-export uppercase identifiers referenced by snapshotted functions whose
176
+ # value is set in the rc-sourced shell. Without this, activation idioms like
177
+ # mise's \`mise()\` -> \`command "$__MISE_EXE" "$@"\` blow up because the helper
178
+ # var is lost (issue #3470). Helper definitions are POSIX-shell so the same
179
+ # block works for both bash and zsh.
180
+ echo "# Captured function environment" >> "$SNAPSHOT_FILE"
181
+ ${fnEnvHelper}
182
+ printf '%s\\n' "$__omp_funcs" | __omp_emit_referenced_exports >> "$SNAPSHOT_FILE"
183
+ unset -f __omp_sq_quote __omp_emit_export_for __omp_emit_referenced_exports 2>/dev/null
184
+ unset __omp_funcs __omp_qbuf __omp_qout __omp_sq __omp_xv __omp_name 2>/dev/null
155
185
 
156
186
  ${optionsScript}
157
187
 
@@ -198,16 +228,35 @@ export async function getOrCreateSnapshot(
198
228
  return null;
199
229
  }
200
230
 
201
- const rcFile = getShellConfigFile(shell);
231
+ const rcFile = getShellConfigFile(shell, env);
202
232
 
203
- // Create snapshot directory
233
+ // Create snapshot directory with owner-only perms — the script may inline
234
+ // env vars referenced by captured functions (#3470) and `os.tmpdir()` is
235
+ // shared on Linux. `mode: 0o700` applies to a fresh mkdir; an existing dir
236
+ // keeps its mode, so chmod it defensively. Ignore EPERM (dir owned by
237
+ // another user on a shared box).
204
238
  const snapshotDir = path.join(os.tmpdir(), "omp-shell-snapshots");
205
- fs.mkdirSync(snapshotDir, { recursive: true });
239
+ fs.mkdirSync(snapshotDir, { recursive: true, mode: 0o700 });
240
+ try {
241
+ fs.chmodSync(snapshotDir, 0o700);
242
+ } catch {
243
+ // best-effort
244
+ }
206
245
 
207
246
  // Generate unique snapshot path
208
247
  const shellName = shell.includes("zsh") ? "zsh" : shell.includes("bash") ? "bash" : "sh";
209
248
  const snapshotPath = path.join(snapshotDir, `snapshot-${shellName}-${crypto.randomUUID()}.sh`);
210
249
 
250
+ // Pre-create the snapshot file at 0600 so the shell's `>|` (truncate) and
251
+ // `>>` (append) redirections inside `generateSnapshotScript` operate on an
252
+ // existing inode and preserve the private mode, regardless of the umask
253
+ // state inside the spawned shell. Without this, a `.bashrc` that sets
254
+ // `umask 022` (the typical interactive default) before the script's first
255
+ // redirection would create the file world-readable; the JS-side post-spawn
256
+ // chmod would tighten it, but only after the shell finished writing every
257
+ // captured env value to disk.
258
+ fs.writeFileSync(snapshotPath, "", { mode: 0o600 });
259
+
211
260
  // Generate and execute snapshot script
212
261
  const script = generateSnapshotScript(shell, snapshotPath, rcFile);
213
262
 
@@ -230,6 +279,14 @@ export async function getOrCreateSnapshot(
230
279
 
231
280
  await child.exited;
232
281
  if (child.exitCode === 0 && fs.existsSync(snapshotPath)) {
282
+ // Defence-in-depth: the script's `umask 077` already locks the file at
283
+ // first write, but chmod again in case the umask didn't take (exotic
284
+ // shells) or a postmortem-restored file ended up looser.
285
+ try {
286
+ fs.chmodSync(snapshotPath, 0o600);
287
+ } catch {
288
+ // best-effort
289
+ }
233
290
  scrubSnapshotInPlace(snapshotPath);
234
291
  cachedSnapshotPaths.set(cacheKey, snapshotPath);
235
292
  return snapshotPath;