oh-my-opencode 5.0.0-beta.4 → 5.0.0-beta.6
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/dist/cli/index.js +496 -377
- package/dist/cli-node/index.js +496 -377
- package/dist/index.js +1136 -1104
- package/dist/skills/coding-agent-sessions/SKILL.md +3 -2
- package/dist/skills/coding-agent-sessions/references/all-platforms.md +1 -1
- package/dist/skills/coding-agent-sessions/references/senpi.md +4 -4
- package/dist/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +1 -1
- package/dist/tui.js +20 -1
- package/package.json +15 -14
- package/packages/lsp-core/src/lsp/client-wrapper.test.ts +60 -7
- package/packages/lsp-core/src/lsp/client-wrapper.ts +69 -16
- package/packages/lsp-core/src/lsp/workspace-edit-adversarial.test.ts +20 -1
- package/packages/lsp-core/src/tools/diagnostics.ts +3 -3
- package/packages/lsp-core/src/tools/navigation.ts +4 -2
- package/packages/lsp-core/src/tools/rename.ts +4 -2
- package/packages/lsp-core/src/tools/symbols.ts +1 -1
- package/packages/lsp-daemon/dist/cli.js +83 -29
- package/packages/lsp-daemon/dist/client.js +76 -22
- package/packages/lsp-daemon/dist/index.js +81 -27
- package/packages/lsp-tools-mcp/dist/cli.js +76 -22
- package/packages/lsp-tools-mcp/dist/mcp.js +76 -22
- package/packages/lsp-tools-mcp/dist/tools.js +76 -22
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +14 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +14 -1
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +83 -29
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/dist/cli.js +24 -12
- package/packages/omo-codex/plugin/components/telemetry/dist/posthog.js +24 -12
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +3 -2
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +1 -1
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/senpi.md +4 -4
- package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +1 -1
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +39 -16
- package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +3 -2
- package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +1 -1
- package/packages/shared-skills/skills/coding-agent-sessions/references/senpi.md +4 -4
- package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/pi_family.py +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: coding-agent-sessions
|
|
3
|
-
description: "MUST USE when asked to find, read, list, search, inspect, fetch, export, or reconstruct coding-agent sessions across Codex, Claude Code/Desktop, OpenCode, Senpi/pi, oh-my-pi (omp), gajae-code (gjc), OpenClaw, Factory Droid, Amp, Gemini/Kimi/Qwen CLIs, Codebuff, Roo/Kilo/Cline, Kodu, Cursor CLI, Aider, Aside browser-agent sessions, or unknown local agent logs. Covers transcripts, session IDs, rollout JSONL, state SQLite, Claude projects/pre-compact histories, OpenCode messages/parts, child/subagent linkage, cwd/model/time/token filters, archives, and cost clues. Expands fuzzy recall into parallel query lanes and first probes known stores so absent platforms are skipped cheaply. Triggers: coding agent sessions, Codex/Claude/OpenCode/Senpi/pi/oh-my-pi/omp/gajae-code/gjc/OpenClaw/Droid/Amp/Kodu/Cursor/Aider/Aside sessions, transcript search, session history, session ID, read transcript, token usage, subagent sessions, what did I do yesterday, did we already do this."
|
|
3
|
+
description: "MUST USE when asked to find, read, list, search, inspect, fetch, export, or reconstruct coding-agent sessions across Codex, Claude Code/Desktop, OpenCode, OMO/Senpi/pi, oh-my-pi (omp), gajae-code (gjc), OpenClaw, Factory Droid, Amp, Gemini/Kimi/Qwen CLIs, Codebuff, Roo/Kilo/Cline, Kodu, Cursor CLI, Aider, Aside browser-agent sessions, or unknown local agent logs. Covers transcripts, session IDs, rollout JSONL, state SQLite, Claude projects/pre-compact histories, OpenCode messages/parts, child/subagent linkage, cwd/model/time/token filters, archives, and cost clues. Expands fuzzy recall into parallel query lanes and first probes known stores so absent platforms are skipped cheaply. Triggers: coding agent sessions, Codex/Claude/OpenCode/OMO/Senpi/pi/oh-my-pi/omp/gajae-code/gjc/OpenClaw/Droid/Amp/Kodu/Cursor/Aider/Aside sessions, transcript search, session history, session ID, read transcript, token usage, subagent sessions, what did I do yesterday, did we already do this."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Coding Agent Sessions
|
|
@@ -15,7 +15,7 @@ Find local coding-agent sessions across agent products before answering from mem
|
|
|
15
15
|
|---|---|
|
|
16
16
|
| Codex / OpenAI Codex CLI | `references/codex.md` |
|
|
17
17
|
| Claude Code / Claude Desktop histories | `references/claude.md` |
|
|
18
|
-
| Senpi / pi coding-agent logs | `references/senpi.md` |
|
|
18
|
+
| OMO / Senpi / pi coding-agent logs | `references/senpi.md` |
|
|
19
19
|
| oh-my-pi (`omp`, `~/.omp`) and gajae-code (`gjc`, `~/.gjc`) logs | `references/senpi.md` |
|
|
20
20
|
| OpenCode / oh-my-openagent (formerly oh-my-opencode) storage | `references/opencode.md` |
|
|
21
21
|
| OpenClaw, Droid, Amp, Gemini, Kimi, Qwen, Codebuff, Roo/Kilo/Cline, Kodu, Cursor CLI, Aider, Kiro, Goose, Hermes, Crush, Zed, Aside | `references/all-platforms.md` |
|
|
@@ -120,6 +120,7 @@ Use `references/codex.md` for Codex storage details.
|
|
|
120
120
|
|---|---|
|
|
121
121
|
| Missing Codex sessions | Set `CODEX_HOME` or pass `--root /path/to/.codex`. |
|
|
122
122
|
| Missing oh-my-pi / gajae-code sessions | Those stores live in `~/.omp/agent/sessions` and `~/.gjc/agent/sessions`. For a custom `PI_CONFIG_DIR` / `PI_CODING_AGENT_DIR`, pass that agent dir with `--root`. |
|
|
123
|
+
| Missing OMO / Senpi sessions | The `senpi` platform searches `~/.omo/agent/sessions`, `~/.senpi/agent/sessions`, and `~/.pi/agent/sessions` (plus matching profile roots). Pass a nonstandard agent directory with `--root`. |
|
|
123
124
|
| Missing OpenCode sessions | Pass the data dir that contains `messages/` and `parts/`, often `~/.opencode` or `~/.local/share/opencode`. |
|
|
124
125
|
| Missing Claude sessions | Search `~/.claude/projects`, `~/.claude/transcripts`, and `~/.claude/pre-compact-session-histories`; use `--root` for nonstandard config dirs. |
|
|
125
126
|
| Missing Aside sessions | The Aside browser agent stores per-user data under `~/.aside/u/<n>/` (`sessions/<date>_<id>/messages.jsonl` transcripts + a `state.db` index; `agents/*/sessions/` is just a hardlink mirror). Pass `--root` for a nonstandard `.aside` dir or an exported user dir. |
|
|
@@ -10,7 +10,7 @@ Registered platform keys: `codex`, `claude`, `senpi`, `oh-my-pi`, `gajae-code`,
|
|
|
10
10
|
|---|---|---|
|
|
11
11
|
| Codex | `$CODEX_HOME`, `~/.codex` | `%CODEX_HOME%`, `%USERPROFILE%\.codex` |
|
|
12
12
|
| Claude | `~/.claude` | `%USERPROFILE%\.claude`, `%APPDATA%\Claude` |
|
|
13
|
-
| Senpi / pi | `~/.senpi/agent`, `~/.pi/agent` | `%USERPROFILE%\.senpi\agent`, `%USERPROFILE%\.pi\agent` |
|
|
13
|
+
| OMO / Senpi / pi | `~/.omo/agent`, `~/.senpi/agent`, `~/.pi/agent` | `%USERPROFILE%\.omo\agent`, `%USERPROFILE%\.senpi\agent`, `%USERPROFILE%\.pi\agent` |
|
|
14
14
|
| oh-my-pi (`omp`) | `~/.omp/agent`, `~/.omp/profiles/*/agent`, `$XDG_DATA_HOME/omp` | `%USERPROFILE%\.omp\agent` |
|
|
15
15
|
| gajae-code (`gjc`) | `~/.gjc/agent`, `~/.gjc/profiles/*/agent`, `$XDG_DATA_HOME/gjc` | `%USERPROFILE%\.gjc\agent` |
|
|
16
16
|
| OpenCode | `$OPENCODE_HOME`, `~/.opencode`, `~/.local/share/opencode` | `%OPENCODE_HOME%`, `%APPDATA%\opencode`, `%USERPROFILE%\.opencode` |
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# Senpi / pi Family Coding-Agent Sessions
|
|
1
|
+
# OMO / Senpi / pi Family Coding-Agent Sessions
|
|
2
2
|
|
|
3
|
-
The pi family (Senpi, oh-my-pi, gajae-code) shares one session format, so one scanner serves all three under separate platform keys.
|
|
3
|
+
The pi family (OMO/Senpi, oh-my-pi, gajae-code) shares one session format, so one scanner serves all three under separate platform keys.
|
|
4
4
|
|
|
5
5
|
| Platform key | Aliases | Config root | Sessions |
|
|
6
6
|
|---|---|---|---|
|
|
7
|
-
| `senpi` | - | `~/.senpi`, `~/.pi` | `<root>/agent/sessions/<encoded-cwd>/<timestamp>_<uuid>.jsonl` |
|
|
7
|
+
| `senpi` | - | `~/.omo`, `~/.senpi`, `~/.pi` | `<root>/agent/sessions/<encoded-cwd>/<timestamp>_<uuid>.jsonl` |
|
|
8
8
|
| `oh-my-pi` | `omp`, `ohmypi` | `~/.omp` | `~/.omp/agent/sessions/<encoded-cwd>/<timestamp>_<uuid>.jsonl` |
|
|
9
9
|
| `gajae-code` | `gjc`, `gajae` | `~/.gjc` | `~/.gjc/agent/sessions/<encoded-cwd>/<timestamp>_<uuid>.jsonl` |
|
|
10
10
|
|
|
@@ -14,7 +14,7 @@ Extra roots scanned for every pi-family platform:
|
|
|
14
14
|
- XDG stores (macOS/Linux, default profile): `$XDG_DATA_HOME/<app>/sessions/**` and `$XDG_DATA_HOME/<app>/profiles/<profile>/sessions/**`, where `<app>` is `senpi`, `omp`, or `gjc`. XDG flattens the `agent/` path segment.
|
|
15
15
|
- A custom `PI_CONFIG_DIR` / `PI_CODING_AGENT_DIR` / `GJC_CONFIG_DIR` store: pass that agent directory with `--root`.
|
|
16
16
|
|
|
17
|
-
`~/.senpi/agent/settings.json`, `models.json`, and `auth.json` provide environment context; oh-my-pi and gajae-code keep the same files plus `config.yml` and `models.yml` next to their sessions directory.
|
|
17
|
+
`~/.omo/agent` and `~/.senpi/agent` are the current and legacy OMO/Senpi stores. Their `settings.json`, `models.json`, and `auth.json` files provide environment context; oh-my-pi and gajae-code keep the same files plus `config.yml` and `models.yml` next to their sessions directory.
|
|
18
18
|
|
|
19
19
|
Common event types:
|
|
20
20
|
|
|
@@ -5,7 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
from .transcript import env_path, existing, jsonl_parallel, recent, stem_id
|
|
6
6
|
from .types import Session
|
|
7
7
|
|
|
8
|
-
SENPI_CONFIG_DIRS = (".senpi", ".pi")
|
|
8
|
+
SENPI_CONFIG_DIRS = (".omo", ".senpi", ".pi")
|
|
9
9
|
OH_MY_PI_CONFIG_DIRS = (".omp",)
|
|
10
10
|
GAJAE_CODE_CONFIG_DIRS = (".gjc",)
|
|
11
11
|
|
package/dist/tui.js
CHANGED
|
@@ -19060,6 +19060,19 @@ var init_team = __esm(() => {
|
|
|
19060
19060
|
OmoTeamsConfigLayerSchema = record(string2(), OmoTeamSpecLayerSchema);
|
|
19061
19061
|
});
|
|
19062
19062
|
|
|
19063
|
+
// packages/omo-config-core/src/schema/telemetry.ts
|
|
19064
|
+
var OmoTelemetrySettingsShape, OmoTelemetrySettingsLayerSchema, OmoTelemetrySettingsSchema;
|
|
19065
|
+
var init_telemetry = __esm(() => {
|
|
19066
|
+
init_zod();
|
|
19067
|
+
OmoTelemetrySettingsShape = {
|
|
19068
|
+
enabled: boolean2()
|
|
19069
|
+
};
|
|
19070
|
+
OmoTelemetrySettingsLayerSchema = object(OmoTelemetrySettingsShape).partial().strict();
|
|
19071
|
+
OmoTelemetrySettingsSchema = OmoTelemetrySettingsLayerSchema.extend({
|
|
19072
|
+
enabled: boolean2().default(true)
|
|
19073
|
+
}).strict();
|
|
19074
|
+
});
|
|
19075
|
+
|
|
19063
19076
|
// packages/omo-config-core/src/schema/config.ts
|
|
19064
19077
|
var OmoOpenCodeHarnessConfigSchema, OmoTypedHarnessConfigSchema, OmoConfigProfileSchema, OmoConfigSchema, OmoConfigLayerSchema;
|
|
19065
19078
|
var init_config = __esm(() => {
|
|
@@ -19071,6 +19084,7 @@ var init_config = __esm(() => {
|
|
|
19071
19084
|
init_model_catalog();
|
|
19072
19085
|
init_task();
|
|
19073
19086
|
init_team();
|
|
19087
|
+
init_telemetry();
|
|
19074
19088
|
OmoOpenCodeHarnessConfigSchema = record(string2(), unknown());
|
|
19075
19089
|
OmoTypedHarnessConfigSchema = object({
|
|
19076
19090
|
categories: OmoCategoriesConfigSchema.optional(),
|
|
@@ -19079,7 +19093,8 @@ var init_config = __esm(() => {
|
|
|
19079
19093
|
task: OmoTaskSettingsLayerSchema.optional(),
|
|
19080
19094
|
teams: OmoTeamsConfigLayerSchema.optional(),
|
|
19081
19095
|
models: OmoModelCatalogLayerSchema.optional(),
|
|
19082
|
-
memory: OmoMemorySettingsLayerSchema.optional()
|
|
19096
|
+
memory: OmoMemorySettingsLayerSchema.optional(),
|
|
19097
|
+
telemetry: OmoTelemetrySettingsLayerSchema.optional()
|
|
19083
19098
|
}).strict();
|
|
19084
19099
|
OmoConfigProfileSchema = object({
|
|
19085
19100
|
categories: OmoCategoriesConfigSchema.optional(),
|
|
@@ -19089,6 +19104,7 @@ var init_config = __esm(() => {
|
|
|
19089
19104
|
teams: OmoTeamsConfigLayerSchema.optional(),
|
|
19090
19105
|
models: OmoModelCatalogLayerSchema.optional(),
|
|
19091
19106
|
memory: OmoMemorySettingsLayerSchema.optional(),
|
|
19107
|
+
telemetry: OmoTelemetrySettingsLayerSchema.optional(),
|
|
19092
19108
|
"[opencode]": OmoOpenCodeHarnessConfigSchema.optional(),
|
|
19093
19109
|
"[senpi]": OmoTypedHarnessConfigSchema.optional(),
|
|
19094
19110
|
"[codex]": OmoTypedHarnessConfigSchema.optional()
|
|
@@ -19102,6 +19118,7 @@ var init_config = __esm(() => {
|
|
|
19102
19118
|
teams: OmoTeamsConfigSchema.optional(),
|
|
19103
19119
|
models: OmoModelCatalogSchema.optional(),
|
|
19104
19120
|
memory: OmoMemorySettingsSchema.optional(),
|
|
19121
|
+
telemetry: OmoTelemetrySettingsSchema.optional(),
|
|
19105
19122
|
"[opencode]": OmoOpenCodeHarnessConfigSchema.optional(),
|
|
19106
19123
|
"[senpi]": OmoTypedHarnessConfigSchema.optional(),
|
|
19107
19124
|
"[codex]": OmoTypedHarnessConfigSchema.optional(),
|
|
@@ -19118,6 +19135,7 @@ var init_config = __esm(() => {
|
|
|
19118
19135
|
teams: OmoTeamsConfigLayerSchema.optional(),
|
|
19119
19136
|
models: OmoModelCatalogLayerSchema.optional(),
|
|
19120
19137
|
memory: OmoMemorySettingsLayerSchema.optional(),
|
|
19138
|
+
telemetry: OmoTelemetrySettingsLayerSchema.optional(),
|
|
19121
19139
|
"[opencode]": OmoOpenCodeHarnessConfigSchema.optional(),
|
|
19122
19140
|
"[senpi]": OmoTypedHarnessConfigSchema.optional(),
|
|
19123
19141
|
"[codex]": OmoTypedHarnessConfigSchema.optional(),
|
|
@@ -19140,6 +19158,7 @@ var init_schema = __esm(() => {
|
|
|
19140
19158
|
init_model_ref();
|
|
19141
19159
|
init_task();
|
|
19142
19160
|
init_team();
|
|
19161
|
+
init_telemetry();
|
|
19143
19162
|
});
|
|
19144
19163
|
|
|
19145
19164
|
// packages/omo-config-core/src/loader/merge.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-opencode",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.6",
|
|
4
4
|
"description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
"zod": "^4.4.3"
|
|
181
181
|
},
|
|
182
182
|
"devDependencies": {
|
|
183
|
-
"@code-yeongyu/senpi": "2026.8.11",
|
|
183
|
+
"@code-yeongyu/senpi": "2026.8.11-4",
|
|
184
184
|
"@oh-my-opencode/agents-md-core": "workspace:*",
|
|
185
185
|
"@oh-my-opencode/ast-grep-mcp": "workspace:*",
|
|
186
186
|
"@oh-my-opencode/boulder-state": "workspace:*",
|
|
@@ -196,6 +196,7 @@
|
|
|
196
196
|
"@oh-my-opencode/model-core": "workspace:*",
|
|
197
197
|
"@oh-my-opencode/omo-codex": "workspace:*",
|
|
198
198
|
"@oh-my-opencode/omo-config-core": "workspace:*",
|
|
199
|
+
"@oh-my-opencode/omo-opencode": "workspace:*",
|
|
199
200
|
"@oh-my-opencode/omo-senpi": "workspace:*",
|
|
200
201
|
"@oh-my-opencode/openclaw-core": "workspace:*",
|
|
201
202
|
"@oh-my-opencode/pi-goal": "workspace:*",
|
|
@@ -220,18 +221,18 @@
|
|
|
220
221
|
"typescript": "^7.0.2"
|
|
221
222
|
},
|
|
222
223
|
"optionalDependencies": {
|
|
223
|
-
"oh-my-opencode-darwin-arm64": "5.0.0-beta.
|
|
224
|
-
"oh-my-opencode-darwin-x64": "5.0.0-beta.
|
|
225
|
-
"oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.
|
|
226
|
-
"oh-my-opencode-linux-arm64": "5.0.0-beta.
|
|
227
|
-
"oh-my-opencode-linux-arm64-musl": "5.0.0-beta.
|
|
228
|
-
"oh-my-opencode-linux-x64": "5.0.0-beta.
|
|
229
|
-
"oh-my-opencode-linux-x64-baseline": "5.0.0-beta.
|
|
230
|
-
"oh-my-opencode-linux-x64-musl": "5.0.0-beta.
|
|
231
|
-
"oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.
|
|
232
|
-
"oh-my-opencode-windows-arm64": "5.0.0-beta.
|
|
233
|
-
"oh-my-opencode-windows-x64": "5.0.0-beta.
|
|
234
|
-
"oh-my-opencode-windows-x64-baseline": "5.0.0-beta.
|
|
224
|
+
"oh-my-opencode-darwin-arm64": "5.0.0-beta.6",
|
|
225
|
+
"oh-my-opencode-darwin-x64": "5.0.0-beta.6",
|
|
226
|
+
"oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.6",
|
|
227
|
+
"oh-my-opencode-linux-arm64": "5.0.0-beta.6",
|
|
228
|
+
"oh-my-opencode-linux-arm64-musl": "5.0.0-beta.6",
|
|
229
|
+
"oh-my-opencode-linux-x64": "5.0.0-beta.6",
|
|
230
|
+
"oh-my-opencode-linux-x64-baseline": "5.0.0-beta.6",
|
|
231
|
+
"oh-my-opencode-linux-x64-musl": "5.0.0-beta.6",
|
|
232
|
+
"oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.6",
|
|
233
|
+
"oh-my-opencode-windows-arm64": "5.0.0-beta.6",
|
|
234
|
+
"oh-my-opencode-windows-x64": "5.0.0-beta.6",
|
|
235
|
+
"oh-my-opencode-windows-x64-baseline": "5.0.0-beta.6"
|
|
235
236
|
},
|
|
236
237
|
"overrides": {
|
|
237
238
|
"@earendil-works/pi-agent-core": "0.80.3",
|
|
@@ -4,8 +4,9 @@ import { join } from "node:path";
|
|
|
4
4
|
import { afterEach, describe, expect, it } from "bun:test";
|
|
5
5
|
|
|
6
6
|
import { createStandaloneMcpRequestContext, runWithRequestContext } from "../request-context.js";
|
|
7
|
-
import { findWorkspaceRoot,
|
|
7
|
+
import { findWorkspaceRoot, resolveReadablePathInsideContext, withLspClient } from "./client-wrapper.js";
|
|
8
8
|
import { LspInvalidPathError } from "./errors.js";
|
|
9
|
+
import { LspManager } from "./manager.js";
|
|
9
10
|
|
|
10
11
|
const tempDirectories: string[] = [];
|
|
11
12
|
|
|
@@ -48,16 +49,68 @@ describe("LSP client path confinement", () => {
|
|
|
48
49
|
).toThrow(LspInvalidPathError);
|
|
49
50
|
});
|
|
50
51
|
|
|
51
|
-
it("#given a symlink inside cwd that points outside #when resolving #then
|
|
52
|
+
it("#given a symlink inside cwd that points outside #when resolving read path #then keeps lexical path and cwd root", () => {
|
|
52
53
|
const root = tempRoot("lsp-client-wrapper-symlink-root-");
|
|
53
54
|
const outside = tempRoot("lsp-client-wrapper-symlink-outside-");
|
|
55
|
+
mkdirSync(join(root, ".git"), { recursive: true });
|
|
56
|
+
mkdirSync(join(outside, ".git"), { recursive: true });
|
|
54
57
|
writeFileSync(join(outside, "file.ts"), "export const outside = true;\n");
|
|
55
58
|
symlinkSync(outside, join(root, "linked"));
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
const result = runWithRequestContext(createStandaloneMcpRequestContext({ cwd: root }), () => ({
|
|
61
|
+
filePath: resolveReadablePathInsideContext("linked/file.ts"),
|
|
62
|
+
workspaceRoot: findWorkspaceRoot("linked/file.ts"),
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
expect(result.filePath).toBe(join(realpathSync(root), "linked", "file.ts"));
|
|
66
|
+
expect(result.workspaceRoot).toBe(realpathSync(root));
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("#given an absolute macOS alias path through an outside symlink #when resolving read path #then preserves the lexical suffix", () => {
|
|
70
|
+
const root = tempRoot("lsp-client-wrapper-alias-root-");
|
|
71
|
+
const outside = tempRoot("lsp-client-wrapper-alias-outside-");
|
|
72
|
+
mkdirSync(join(root, ".git"), { recursive: true });
|
|
73
|
+
writeFileSync(join(outside, "file.ts"), "export const outside = true;\n");
|
|
74
|
+
symlinkSync(outside, join(root, "linked"));
|
|
75
|
+
|
|
76
|
+
const result = runWithRequestContext(createStandaloneMcpRequestContext({ cwd: root }), () =>
|
|
77
|
+
resolveReadablePathInsideContext(join(root, "linked", "file.ts")),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(result).toBe(join(realpathSync(root), "linked", "file.ts"));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("#given missing parent directories without markers #when resolving workspace #then uses an existing directory", () => {
|
|
84
|
+
const root = tempRoot("lsp-client-wrapper-missing-root-");
|
|
85
|
+
const workspace = runWithRequestContext(createStandaloneMcpRequestContext({ cwd: root }), () =>
|
|
86
|
+
findWorkspaceRoot("missing/deep/file.ts"),
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
expect(workspace).toBe(realpathSync(root));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("#given a symlink inside cwd that points outside #when starting rename #then rejects before client acquisition", async () => {
|
|
93
|
+
const root = tempRoot("lsp-client-wrapper-rename-root-");
|
|
94
|
+
const outside = tempRoot("lsp-client-wrapper-rename-outside-");
|
|
95
|
+
writeFileSync(join(outside, "file.ts"), "export const outside = true;\n");
|
|
96
|
+
symlinkSync(outside, join(root, "linked"));
|
|
97
|
+
let clientFactoryCalls = 0;
|
|
98
|
+
const manager = new LspManager({
|
|
99
|
+
clientFactory: () => {
|
|
100
|
+
clientFactoryCalls += 1;
|
|
101
|
+
throw new Error("client acquisition must not start");
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await expect(
|
|
107
|
+
runWithRequestContext(createStandaloneMcpRequestContext({ cwd: root }), () =>
|
|
108
|
+
withLspClient("linked/file.ts", async () => undefined, "rename", { manager }),
|
|
109
|
+
),
|
|
110
|
+
).rejects.toThrow(LspInvalidPathError);
|
|
111
|
+
expect(clientFactoryCalls).toBe(0);
|
|
112
|
+
} finally {
|
|
113
|
+
await manager.stopAll();
|
|
114
|
+
}
|
|
62
115
|
});
|
|
63
116
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, statSync } from "node:fs";
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
1
|
+
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import {
|
|
4
4
|
canonicalizeExistingOrNearestAncestor,
|
|
5
5
|
contextCwd,
|
|
@@ -31,30 +31,48 @@ export function isDirectoryPath(filePath: string): boolean {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export function findWorkspaceRoot(filePath: string): string {
|
|
34
|
-
const
|
|
34
|
+
const cwd = contextCwd();
|
|
35
|
+
const abs = resolveReadablePathInsideContext(filePath);
|
|
35
36
|
let dir = abs;
|
|
36
37
|
|
|
37
38
|
if (!isDirectoryPath(dir)) {
|
|
38
39
|
dir = dirname(dir);
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
while (dir
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
const fallbackRoot = nearestExistingDirectoryInsideContext(dir, cwd) ?? cwd;
|
|
43
|
+
while (isPathInside(cwd, dir)) {
|
|
44
|
+
const canonicalDir = existingDirectoryInsideContext(dir, cwd);
|
|
45
|
+
if (canonicalDir !== undefined) {
|
|
46
|
+
for (const marker of WORKSPACE_MARKERS) {
|
|
47
|
+
if (existsSync(join(dir, marker))) {
|
|
48
|
+
return canonicalDir;
|
|
49
|
+
}
|
|
46
50
|
}
|
|
47
51
|
}
|
|
48
|
-
|
|
52
|
+
if (dir === cwd) break;
|
|
49
53
|
dir = dirname(dir);
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
return
|
|
56
|
+
return fallbackRoot;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
|
-
export function
|
|
59
|
+
export function resolveReadablePathInsideContext(filePath: string): string {
|
|
56
60
|
const cwd = contextCwd();
|
|
57
61
|
const abs = resolve(cwd, filePath);
|
|
62
|
+
if (isPathInside(cwd, abs)) return abs;
|
|
63
|
+
|
|
64
|
+
const rebased = rebaseThroughCanonicalAncestor(abs, cwd);
|
|
65
|
+
if (rebased !== undefined) return rebased;
|
|
66
|
+
|
|
67
|
+
const canonical = canonicalizeExistingOrNearestAncestor(abs);
|
|
68
|
+
if (isPathInside(cwd, canonical)) return canonical;
|
|
69
|
+
|
|
70
|
+
throw new LspInvalidPathError(`LSP file path must be inside request cwd: ${filePath}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function resolvePathInsideContext(filePath: string): string {
|
|
74
|
+
const cwd = contextCwd();
|
|
75
|
+
const abs = resolveReadablePathInsideContext(filePath);
|
|
58
76
|
const canonical = canonicalizeExistingOrNearestAncestor(abs);
|
|
59
77
|
if (!isPathInside(cwd, canonical)) {
|
|
60
78
|
throw new LspInvalidPathError(`LSP file path must be inside request cwd: ${filePath}`);
|
|
@@ -62,6 +80,39 @@ export function resolvePathInsideContext(filePath: string): string {
|
|
|
62
80
|
return canonical;
|
|
63
81
|
}
|
|
64
82
|
|
|
83
|
+
function rebaseThroughCanonicalAncestor(path: string, cwd: string): string | undefined {
|
|
84
|
+
let current = path;
|
|
85
|
+
const suffix: string[] = [];
|
|
86
|
+
while (true) {
|
|
87
|
+
if (existsSync(current)) {
|
|
88
|
+
const canonical = realpathSync(current);
|
|
89
|
+
if (isPathInside(cwd, canonical)) return suffix.length === 0 ? canonical : join(canonical, ...suffix);
|
|
90
|
+
}
|
|
91
|
+
const parent = dirname(current);
|
|
92
|
+
if (parent === current) return undefined;
|
|
93
|
+
suffix.unshift(basename(current));
|
|
94
|
+
current = parent;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function existingDirectoryInsideContext(directory: string, cwd: string): string | undefined {
|
|
99
|
+
if (!existsSync(directory)) return undefined;
|
|
100
|
+
const canonical = realpathSync(directory);
|
|
101
|
+
if (!statSync(canonical).isDirectory()) return undefined;
|
|
102
|
+
return isPathInside(cwd, canonical) ? canonical : undefined;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function nearestExistingDirectoryInsideContext(directory: string, cwd: string): string | undefined {
|
|
106
|
+
let current = directory;
|
|
107
|
+
while (isPathInside(cwd, current)) {
|
|
108
|
+
const canonical = existingDirectoryInsideContext(current, cwd);
|
|
109
|
+
if (canonical !== undefined) return canonical;
|
|
110
|
+
if (current === cwd) return undefined;
|
|
111
|
+
current = dirname(current);
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
65
116
|
export function formatServerLookupError(result: Exclude<ServerLookupResult, { status: "found" }>): string {
|
|
66
117
|
if (result.status === "not_installed") {
|
|
67
118
|
return formatNotInstalled(result);
|
|
@@ -143,7 +194,7 @@ export interface WithLspClientOptions {
|
|
|
143
194
|
manager?: LspManager;
|
|
144
195
|
}
|
|
145
196
|
|
|
146
|
-
const
|
|
197
|
+
const READ_ONLY_TOOLS = new Set([
|
|
147
198
|
"diagnostics",
|
|
148
199
|
"definition",
|
|
149
200
|
"references",
|
|
@@ -154,11 +205,13 @@ const READ_ONLY_RETRY_TOOLS = new Set([
|
|
|
154
205
|
|
|
155
206
|
export async function withLspClient<T>(
|
|
156
207
|
filePath: string,
|
|
157
|
-
fn: (client: LspClient, workspaceRoot: string) => Promise<T>,
|
|
208
|
+
fn: (client: LspClient, workspaceRoot: string, resolvedFilePath: string) => Promise<T>,
|
|
158
209
|
toolName: string,
|
|
159
210
|
options: WithLspClientOptions = {},
|
|
160
211
|
): Promise<T> {
|
|
161
|
-
const absPath =
|
|
212
|
+
const absPath = READ_ONLY_TOOLS.has(toolName)
|
|
213
|
+
? resolveReadablePathInsideContext(filePath)
|
|
214
|
+
: resolvePathInsideContext(filePath);
|
|
162
215
|
|
|
163
216
|
if (isDirectoryPath(absPath)) {
|
|
164
217
|
throw new LspInvalidPathError(
|
|
@@ -181,9 +234,9 @@ export async function withLspClient<T>(
|
|
|
181
234
|
const client = await manager.getClient(root, server, options.signal);
|
|
182
235
|
|
|
183
236
|
try {
|
|
184
|
-
return await fn(client, root);
|
|
237
|
+
return await fn(client, root, absPath);
|
|
185
238
|
} catch (err) {
|
|
186
|
-
if (allowRetry &&
|
|
239
|
+
if (allowRetry && READ_ONLY_TOOLS.has(toolName) && isLspDeadConnectionError(err)) {
|
|
187
240
|
manager.invalidateClient(root, server.id, client);
|
|
188
241
|
return acquireAndCall(false);
|
|
189
242
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { mkdtempSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
@@ -87,6 +87,25 @@ describe("workspace edit adversarial inputs", () => {
|
|
|
87
87
|
expect(readFileSync(outsideFile, "utf-8")).toBe("outside bytes\n");
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
+
it("#given an in-workspace symlink to an outside file #when applying an edit #then rejects without modifying target", () => {
|
|
91
|
+
const workspace = makeTempDirectory("lsp-workspace-");
|
|
92
|
+
const outside = makeTempDirectory("lsp-outside-");
|
|
93
|
+
const outsideFile = join(outside, "outside.ts");
|
|
94
|
+
const canonicalWorkspace = realpathSync(workspace);
|
|
95
|
+
const linkedDirectory = join(canonicalWorkspace, "linked");
|
|
96
|
+
writeFileSync(outsideFile, "outside bytes\n", "utf-8");
|
|
97
|
+
symlinkSync(outside, linkedDirectory);
|
|
98
|
+
|
|
99
|
+
const result = applyWorkspaceEdit(
|
|
100
|
+
{ changes: { [pathToFileURL(join(linkedDirectory, "outside.ts")).href]: [insertion()] } },
|
|
101
|
+
{ workspaceRoot: canonicalWorkspace },
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(result.success).toBe(false);
|
|
105
|
+
expect(result.errors.join("\n")).toContain("outside workspace");
|
|
106
|
+
expect(readFileSync(outsideFile, "utf-8")).toBe("outside bytes\n");
|
|
107
|
+
});
|
|
108
|
+
|
|
90
109
|
it("#given annotations or mixed edit representations #when planned #then unsupported claims are rejected", () => {
|
|
91
110
|
const workspace = makeTempDirectory("lsp-workspace-");
|
|
92
111
|
const unsupported = [
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isDirectoryPath,
|
|
1
|
+
import { isDirectoryPath, resolveReadablePathInsideContext, withLspClient } from "../lsp/client-wrapper.js";
|
|
2
2
|
import { DEFAULT_MAX_DIAGNOSTICS } from "../lsp/constants.js";
|
|
3
3
|
import { aggregateDiagnosticsForDirectory } from "../lsp/directory-diagnostics.js";
|
|
4
4
|
import { filterDiagnosticsBySeverity, formatDiagnostic } from "../lsp/formatters.js";
|
|
@@ -23,7 +23,7 @@ export async function executeLspDiagnostics(
|
|
|
23
23
|
const severity = severityFilter(params);
|
|
24
24
|
|
|
25
25
|
try {
|
|
26
|
-
const absPath =
|
|
26
|
+
const absPath = resolveReadablePathInsideContext(filePath);
|
|
27
27
|
if (isDirectoryPath(absPath)) {
|
|
28
28
|
const extension = inferExtensionFromDirectory(absPath);
|
|
29
29
|
if (!extension) {
|
|
@@ -62,7 +62,7 @@ export async function executeLspDiagnostics(
|
|
|
62
62
|
|
|
63
63
|
const result = await withLspClient(
|
|
64
64
|
filePath,
|
|
65
|
-
async (client) => client.diagnostics(
|
|
65
|
+
async (client, _workspaceRoot, resolvedFilePath) => client.diagnostics(resolvedFilePath, signal),
|
|
66
66
|
"diagnostics",
|
|
67
67
|
clientOptions(signal),
|
|
68
68
|
);
|
|
@@ -17,7 +17,8 @@ export async function executeLspGotoDefinition(
|
|
|
17
17
|
try {
|
|
18
18
|
const result = await withLspClient(
|
|
19
19
|
filePath,
|
|
20
|
-
async (client
|
|
20
|
+
async (client, _workspaceRoot, resolvedFilePath) =>
|
|
21
|
+
client.definition(resolvedFilePath, line, character, signal),
|
|
21
22
|
"definition",
|
|
22
23
|
clientOptions(signal),
|
|
23
24
|
);
|
|
@@ -49,7 +50,8 @@ export async function executeLspFindReferences(
|
|
|
49
50
|
try {
|
|
50
51
|
const result = await withLspClient(
|
|
51
52
|
filePath,
|
|
52
|
-
async (client
|
|
53
|
+
async (client, _workspaceRoot, resolvedFilePath) =>
|
|
54
|
+
client.references(resolvedFilePath, line, character, includeDeclaration, signal),
|
|
53
55
|
"references",
|
|
54
56
|
clientOptions(signal),
|
|
55
57
|
);
|
|
@@ -16,7 +16,8 @@ export async function executeLspPrepareRename(
|
|
|
16
16
|
try {
|
|
17
17
|
const result = await withLspClient(
|
|
18
18
|
filePath,
|
|
19
|
-
async (client
|
|
19
|
+
async (client, _workspaceRoot, resolvedFilePath) =>
|
|
20
|
+
client.prepareRename(resolvedFilePath, line, character, signal),
|
|
20
21
|
"prepareRename",
|
|
21
22
|
clientOptions(signal),
|
|
22
23
|
);
|
|
@@ -46,7 +47,8 @@ export async function executeLspRename(
|
|
|
46
47
|
try {
|
|
47
48
|
const result = await withLspClient(
|
|
48
49
|
filePath,
|
|
49
|
-
async (client
|
|
50
|
+
async (client, _workspaceRoot, resolvedFilePath) =>
|
|
51
|
+
client.rename(resolvedFilePath, line, character, newName, signal),
|
|
50
52
|
"rename",
|
|
51
53
|
clientOptions(signal),
|
|
52
54
|
);
|
|
@@ -47,7 +47,7 @@ export async function executeLspSymbols(
|
|
|
47
47
|
|
|
48
48
|
const symbols = await withLspClient(
|
|
49
49
|
filePath,
|
|
50
|
-
async (client) => client.documentSymbols(
|
|
50
|
+
async (client, _workspaceRoot, resolvedFilePath) => client.documentSymbols(resolvedFilePath, signal),
|
|
51
51
|
"documentSymbols",
|
|
52
52
|
clientOptions(signal),
|
|
53
53
|
);
|