oh-my-opencode 5.0.0-beta.5 → 5.0.0-beta.7
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/README.md +12 -1
- package/dist/cli/index.js +341 -214
- package/dist/cli-node/index.js +341 -214
- package/dist/index.js +1217 -1112
- 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 +97 -5
- 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 +90 -4
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +90 -4
- 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/directive.md +6 -0
- 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/ultrawork/skills/ultrawork/SKILL.md +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +6 -0
- 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/components/ulw-loop/skills/ulw-loop/SKILL.md +3 -2
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/define-goal.md +106 -0
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +1 -0
- 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/plugin/skills/ultrawork/SKILL.md +6 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +3 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/references/define-goal.md +106 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +1 -0
- 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
|
@@ -18837,15 +18837,16 @@ var init_codegraph = __esm(() => {
|
|
|
18837
18837
|
});
|
|
18838
18838
|
|
|
18839
18839
|
// packages/omo-config-core/src/schema/memory.ts
|
|
18840
|
-
var OmoMemoryReflectionTriggerSchema, OmoMemoryReflectionSchema, OmoMemorySyncSchema, OmoMemorySearchSchema, OmoMemoryReflectionTriggerLayerSchema, OmoMemoryReflectionLayerSchema, OmoMemorySyncLayerSchema, OmoMemorySearchLayerSchema, OmoMemoryAgentOverridesSchema, OmoMemorySettingsSchema, OmoMemorySettingsLayerSchema;
|
|
18840
|
+
var OmoMemoryReflectionTriggerSchema, OmoMemoryReflectionSchema, OmoMemorySyncSchema, OmoMemorySearchSchema, OmoMemoryNudgeSchema, OmoMemoryFactsSchema, OmoMemoryDreamSchema, OmoMemoryPeopleSchema, OmoMemorySoulSchema, OmoMemoryReflectionTriggerLayerSchema, OmoMemoryReflectionLayerSchema, OmoMemorySyncLayerSchema, OmoMemorySearchLayerSchema, OmoMemoryNudgeLayerSchema, OmoMemoryFactsLayerSchema, OmoMemoryDreamLayerSchema, OmoMemoryPeopleLayerSchema, OmoMemorySoulLayerSchema, OmoMemoryAgentOverridesSchema, OmoMemorySettingsSchema, OmoMemorySettingsLayerSchema;
|
|
18841
18841
|
var init_memory = __esm(() => {
|
|
18842
18842
|
init_zod();
|
|
18843
18843
|
OmoMemoryReflectionTriggerSchema = object({
|
|
18844
|
-
step_count: number2().int().nonnegative().default(
|
|
18844
|
+
step_count: number2().int().nonnegative().default(25),
|
|
18845
18845
|
on_compaction: boolean2().default(true)
|
|
18846
18846
|
}).strict();
|
|
18847
18847
|
OmoMemoryReflectionSchema = object({
|
|
18848
|
-
|
|
18848
|
+
enabled: boolean2().default(true),
|
|
18849
|
+
trigger: OmoMemoryReflectionTriggerSchema.default({ step_count: 25, on_compaction: true }),
|
|
18849
18850
|
merge: _enum2(["auto", "integration"]).default("auto"),
|
|
18850
18851
|
category: string2().min(1).default("quick"),
|
|
18851
18852
|
timeout_minutes: number2().int().positive().default(15),
|
|
@@ -18858,11 +18859,36 @@ var init_memory = __esm(() => {
|
|
|
18858
18859
|
OmoMemorySearchSchema = object({
|
|
18859
18860
|
enabled: boolean2().default(true)
|
|
18860
18861
|
}).strict();
|
|
18862
|
+
OmoMemoryNudgeSchema = object({
|
|
18863
|
+
enabled: boolean2().default(true),
|
|
18864
|
+
every_user_turns: number2().int().min(1).default(10)
|
|
18865
|
+
}).strict();
|
|
18866
|
+
OmoMemoryFactsSchema = object({
|
|
18867
|
+
enabled: boolean2().default(true),
|
|
18868
|
+
debounce_settles: number2().int().min(1).default(4)
|
|
18869
|
+
}).strict();
|
|
18870
|
+
OmoMemoryDreamSchema = object({
|
|
18871
|
+
enabled: boolean2().default(true),
|
|
18872
|
+
idle_minutes: number2().int().min(0).default(30),
|
|
18873
|
+
min_hours_between: number2().int().min(1).default(24),
|
|
18874
|
+
shutdown_launch: boolean2().default(true),
|
|
18875
|
+
auto_select_max: number2().int().min(1).max(10).default(5),
|
|
18876
|
+
auto_select_max_chars: number2().int().min(1e4).default(150000)
|
|
18877
|
+
}).strict();
|
|
18878
|
+
OmoMemoryPeopleSchema = object({
|
|
18879
|
+
enabled: boolean2().default(true),
|
|
18880
|
+
max_entries: number2().int().min(1).max(100).default(40),
|
|
18881
|
+
max_entry_chars: number2().int().min(50).max(500).default(200)
|
|
18882
|
+
}).strict();
|
|
18883
|
+
OmoMemorySoulSchema = object({
|
|
18884
|
+
edit_notice: boolean2().default(true)
|
|
18885
|
+
}).strict();
|
|
18861
18886
|
OmoMemoryReflectionTriggerLayerSchema = object({
|
|
18862
18887
|
step_count: number2().int().nonnegative().optional(),
|
|
18863
18888
|
on_compaction: boolean2().optional()
|
|
18864
18889
|
}).strict();
|
|
18865
18890
|
OmoMemoryReflectionLayerSchema = object({
|
|
18891
|
+
enabled: boolean2().optional(),
|
|
18866
18892
|
trigger: OmoMemoryReflectionTriggerLayerSchema.optional(),
|
|
18867
18893
|
merge: _enum2(["auto", "integration"]).optional(),
|
|
18868
18894
|
category: string2().min(1).optional(),
|
|
@@ -18876,10 +18902,39 @@ var init_memory = __esm(() => {
|
|
|
18876
18902
|
OmoMemorySearchLayerSchema = object({
|
|
18877
18903
|
enabled: boolean2().optional()
|
|
18878
18904
|
}).strict();
|
|
18905
|
+
OmoMemoryNudgeLayerSchema = object({
|
|
18906
|
+
enabled: boolean2().optional(),
|
|
18907
|
+
every_user_turns: number2().int().min(1).optional()
|
|
18908
|
+
}).strict();
|
|
18909
|
+
OmoMemoryFactsLayerSchema = object({
|
|
18910
|
+
enabled: boolean2().optional(),
|
|
18911
|
+
debounce_settles: number2().int().min(1).optional()
|
|
18912
|
+
}).strict();
|
|
18913
|
+
OmoMemoryDreamLayerSchema = object({
|
|
18914
|
+
enabled: boolean2().optional(),
|
|
18915
|
+
idle_minutes: number2().int().min(0).optional(),
|
|
18916
|
+
min_hours_between: number2().int().min(1).optional(),
|
|
18917
|
+
shutdown_launch: boolean2().optional(),
|
|
18918
|
+
auto_select_max: number2().int().min(1).max(10).optional(),
|
|
18919
|
+
auto_select_max_chars: number2().int().min(1e4).optional()
|
|
18920
|
+
}).strict();
|
|
18921
|
+
OmoMemoryPeopleLayerSchema = object({
|
|
18922
|
+
enabled: boolean2().optional(),
|
|
18923
|
+
max_entries: number2().int().min(1).max(100).optional(),
|
|
18924
|
+
max_entry_chars: number2().int().min(50).max(500).optional()
|
|
18925
|
+
}).strict();
|
|
18926
|
+
OmoMemorySoulLayerSchema = object({
|
|
18927
|
+
edit_notice: boolean2().optional()
|
|
18928
|
+
}).strict();
|
|
18879
18929
|
OmoMemoryAgentOverridesSchema = object({
|
|
18880
18930
|
enabled: boolean2().optional(),
|
|
18881
18931
|
agent: string2().min(1).optional(),
|
|
18882
18932
|
reflection: OmoMemoryReflectionLayerSchema.optional(),
|
|
18933
|
+
nudge: OmoMemoryNudgeLayerSchema.optional(),
|
|
18934
|
+
facts: OmoMemoryFactsLayerSchema.optional(),
|
|
18935
|
+
dream: OmoMemoryDreamLayerSchema.optional(),
|
|
18936
|
+
people: OmoMemoryPeopleLayerSchema.optional(),
|
|
18937
|
+
soul: OmoMemorySoulLayerSchema.optional(),
|
|
18883
18938
|
sync: OmoMemorySyncLayerSchema.optional(),
|
|
18884
18939
|
search: OmoMemorySearchLayerSchema.optional(),
|
|
18885
18940
|
compile_warn_tokens: number2().int().positive().optional()
|
|
@@ -18889,12 +18944,25 @@ var init_memory = __esm(() => {
|
|
|
18889
18944
|
agent: string2().min(1).default("auto"),
|
|
18890
18945
|
tool_exposure: _enum2(["direct", "search"]).default("direct"),
|
|
18891
18946
|
reflection: OmoMemoryReflectionSchema.default({
|
|
18892
|
-
|
|
18947
|
+
enabled: true,
|
|
18948
|
+
trigger: { step_count: 25, on_compaction: true },
|
|
18893
18949
|
merge: "auto",
|
|
18894
18950
|
category: "quick",
|
|
18895
18951
|
timeout_minutes: 15,
|
|
18896
18952
|
sandbox: "auto"
|
|
18897
18953
|
}),
|
|
18954
|
+
nudge: OmoMemoryNudgeSchema.default({ enabled: true, every_user_turns: 10 }),
|
|
18955
|
+
facts: OmoMemoryFactsSchema.default({ enabled: true, debounce_settles: 4 }),
|
|
18956
|
+
dream: OmoMemoryDreamSchema.default({
|
|
18957
|
+
enabled: true,
|
|
18958
|
+
idle_minutes: 30,
|
|
18959
|
+
min_hours_between: 24,
|
|
18960
|
+
shutdown_launch: true,
|
|
18961
|
+
auto_select_max: 5,
|
|
18962
|
+
auto_select_max_chars: 150000
|
|
18963
|
+
}),
|
|
18964
|
+
people: OmoMemoryPeopleSchema.default({ enabled: true, max_entries: 40, max_entry_chars: 200 }),
|
|
18965
|
+
soul: OmoMemorySoulSchema.default({ edit_notice: true }),
|
|
18898
18966
|
sync: OmoMemorySyncSchema.default({ enabled: true }),
|
|
18899
18967
|
search: OmoMemorySearchSchema.default({ enabled: true }),
|
|
18900
18968
|
compile_warn_tokens: number2().int().positive().default(30000),
|
|
@@ -18905,6 +18973,11 @@ var init_memory = __esm(() => {
|
|
|
18905
18973
|
agent: string2().min(1).optional(),
|
|
18906
18974
|
tool_exposure: _enum2(["direct", "search"]).optional(),
|
|
18907
18975
|
reflection: OmoMemoryReflectionLayerSchema.optional(),
|
|
18976
|
+
nudge: OmoMemoryNudgeLayerSchema.optional(),
|
|
18977
|
+
facts: OmoMemoryFactsLayerSchema.optional(),
|
|
18978
|
+
dream: OmoMemoryDreamLayerSchema.optional(),
|
|
18979
|
+
people: OmoMemoryPeopleLayerSchema.optional(),
|
|
18980
|
+
soul: OmoMemorySoulLayerSchema.optional(),
|
|
18908
18981
|
sync: OmoMemorySyncLayerSchema.optional(),
|
|
18909
18982
|
search: OmoMemorySearchLayerSchema.optional(),
|
|
18910
18983
|
compile_warn_tokens: number2().int().positive().optional(),
|
|
@@ -19060,6 +19133,19 @@ var init_team = __esm(() => {
|
|
|
19060
19133
|
OmoTeamsConfigLayerSchema = record(string2(), OmoTeamSpecLayerSchema);
|
|
19061
19134
|
});
|
|
19062
19135
|
|
|
19136
|
+
// packages/omo-config-core/src/schema/telemetry.ts
|
|
19137
|
+
var OmoTelemetrySettingsShape, OmoTelemetrySettingsLayerSchema, OmoTelemetrySettingsSchema;
|
|
19138
|
+
var init_telemetry = __esm(() => {
|
|
19139
|
+
init_zod();
|
|
19140
|
+
OmoTelemetrySettingsShape = {
|
|
19141
|
+
enabled: boolean2()
|
|
19142
|
+
};
|
|
19143
|
+
OmoTelemetrySettingsLayerSchema = object(OmoTelemetrySettingsShape).partial().strict();
|
|
19144
|
+
OmoTelemetrySettingsSchema = OmoTelemetrySettingsLayerSchema.extend({
|
|
19145
|
+
enabled: boolean2().default(true)
|
|
19146
|
+
}).strict();
|
|
19147
|
+
});
|
|
19148
|
+
|
|
19063
19149
|
// packages/omo-config-core/src/schema/config.ts
|
|
19064
19150
|
var OmoOpenCodeHarnessConfigSchema, OmoTypedHarnessConfigSchema, OmoConfigProfileSchema, OmoConfigSchema, OmoConfigLayerSchema;
|
|
19065
19151
|
var init_config = __esm(() => {
|
|
@@ -19071,6 +19157,7 @@ var init_config = __esm(() => {
|
|
|
19071
19157
|
init_model_catalog();
|
|
19072
19158
|
init_task();
|
|
19073
19159
|
init_team();
|
|
19160
|
+
init_telemetry();
|
|
19074
19161
|
OmoOpenCodeHarnessConfigSchema = record(string2(), unknown());
|
|
19075
19162
|
OmoTypedHarnessConfigSchema = object({
|
|
19076
19163
|
categories: OmoCategoriesConfigSchema.optional(),
|
|
@@ -19079,7 +19166,8 @@ var init_config = __esm(() => {
|
|
|
19079
19166
|
task: OmoTaskSettingsLayerSchema.optional(),
|
|
19080
19167
|
teams: OmoTeamsConfigLayerSchema.optional(),
|
|
19081
19168
|
models: OmoModelCatalogLayerSchema.optional(),
|
|
19082
|
-
memory: OmoMemorySettingsLayerSchema.optional()
|
|
19169
|
+
memory: OmoMemorySettingsLayerSchema.optional(),
|
|
19170
|
+
telemetry: OmoTelemetrySettingsLayerSchema.optional()
|
|
19083
19171
|
}).strict();
|
|
19084
19172
|
OmoConfigProfileSchema = object({
|
|
19085
19173
|
categories: OmoCategoriesConfigSchema.optional(),
|
|
@@ -19089,6 +19177,7 @@ var init_config = __esm(() => {
|
|
|
19089
19177
|
teams: OmoTeamsConfigLayerSchema.optional(),
|
|
19090
19178
|
models: OmoModelCatalogLayerSchema.optional(),
|
|
19091
19179
|
memory: OmoMemorySettingsLayerSchema.optional(),
|
|
19180
|
+
telemetry: OmoTelemetrySettingsLayerSchema.optional(),
|
|
19092
19181
|
"[opencode]": OmoOpenCodeHarnessConfigSchema.optional(),
|
|
19093
19182
|
"[senpi]": OmoTypedHarnessConfigSchema.optional(),
|
|
19094
19183
|
"[codex]": OmoTypedHarnessConfigSchema.optional()
|
|
@@ -19102,6 +19191,7 @@ var init_config = __esm(() => {
|
|
|
19102
19191
|
teams: OmoTeamsConfigSchema.optional(),
|
|
19103
19192
|
models: OmoModelCatalogSchema.optional(),
|
|
19104
19193
|
memory: OmoMemorySettingsSchema.optional(),
|
|
19194
|
+
telemetry: OmoTelemetrySettingsSchema.optional(),
|
|
19105
19195
|
"[opencode]": OmoOpenCodeHarnessConfigSchema.optional(),
|
|
19106
19196
|
"[senpi]": OmoTypedHarnessConfigSchema.optional(),
|
|
19107
19197
|
"[codex]": OmoTypedHarnessConfigSchema.optional(),
|
|
@@ -19118,6 +19208,7 @@ var init_config = __esm(() => {
|
|
|
19118
19208
|
teams: OmoTeamsConfigLayerSchema.optional(),
|
|
19119
19209
|
models: OmoModelCatalogLayerSchema.optional(),
|
|
19120
19210
|
memory: OmoMemorySettingsLayerSchema.optional(),
|
|
19211
|
+
telemetry: OmoTelemetrySettingsLayerSchema.optional(),
|
|
19121
19212
|
"[opencode]": OmoOpenCodeHarnessConfigSchema.optional(),
|
|
19122
19213
|
"[senpi]": OmoTypedHarnessConfigSchema.optional(),
|
|
19123
19214
|
"[codex]": OmoTypedHarnessConfigSchema.optional(),
|
|
@@ -19140,6 +19231,7 @@ var init_schema = __esm(() => {
|
|
|
19140
19231
|
init_model_ref();
|
|
19141
19232
|
init_task();
|
|
19142
19233
|
init_team();
|
|
19234
|
+
init_telemetry();
|
|
19143
19235
|
});
|
|
19144
19236
|
|
|
19145
19237
|
// 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.7",
|
|
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.
|
|
183
|
+
"@code-yeongyu/senpi": "2026.8.12-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.7",
|
|
225
|
+
"oh-my-opencode-darwin-x64": "5.0.0-beta.7",
|
|
226
|
+
"oh-my-opencode-darwin-x64-baseline": "5.0.0-beta.7",
|
|
227
|
+
"oh-my-opencode-linux-arm64": "5.0.0-beta.7",
|
|
228
|
+
"oh-my-opencode-linux-arm64-musl": "5.0.0-beta.7",
|
|
229
|
+
"oh-my-opencode-linux-x64": "5.0.0-beta.7",
|
|
230
|
+
"oh-my-opencode-linux-x64-baseline": "5.0.0-beta.7",
|
|
231
|
+
"oh-my-opencode-linux-x64-musl": "5.0.0-beta.7",
|
|
232
|
+
"oh-my-opencode-linux-x64-musl-baseline": "5.0.0-beta.7",
|
|
233
|
+
"oh-my-opencode-windows-arm64": "5.0.0-beta.7",
|
|
234
|
+
"oh-my-opencode-windows-x64": "5.0.0-beta.7",
|
|
235
|
+
"oh-my-opencode-windows-x64-baseline": "5.0.0-beta.7"
|
|
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
|
);
|