Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ omo install npm:omo-jev-plugin

API 키는 `~/.omo/jev-plugin.jsonc`의 `provider.jev_compatible.apiKey` 또는 `provider.respan-ai.apiKey`에 공급자별로 설정합니다. 없으면 각각 **OmO/senpi를 실행하는 프로세스의 환경 변수** `TYPESAFE_API_KEY` 또는 `OPENROUTER_API_KEY`를 사용합니다. 공급자별 `endpoint`도 설정할 수 있습니다. 프로젝트별 설정에 키를 넣었다면 파일을 공유할 때 주의하세요. 선택한 공급자의 키가 없으면 로딩 직후 경고하고 판단 요청을 보내지 않습니다.

설치만으로 판단 API를 호출하지는 않습니다. 플러그인을 처음 로드할 때 기본 설정 파일이 자동 생성되며, 사용하려면 아래처럼 모드를 변경해야 합니다.
플러그인을 처음 로드할 때 `advise` 모드의 기본 설정 파일이 자동 생성됩니다. 판단 API 호출에는 선택한 공급자의 API 키가 필요합니다.

### 텔레메트리

Expand All @@ -26,7 +26,7 @@ API 키는 `~/.omo/jev-plugin.jsonc`의 `provider.jev_compatible.apiKey` 또는

## 설정

처음 로드하면 `~/.omo/jev-plugin.jsonc`가 `off` 모드의 기본값으로 생성됩니다. 기존 전역 설정의 최상위 `model`, `apiKey`, `openrouterApiKey`, `endpoint`는 로드할 때 `provider` 아래로 옮깁니다. 파일을 바꾸기 전에 원본을 `jev-plugin.jsonc.bak.<타임스탬프>`에 백업하며, `_migrations`에 적용 이력을 기록합니다. 기존 설정값과 주석은 가능한 한 유지합니다. 프로젝트별 설정은 상속에 필요한 항목만 적으며 자동으로 채우지 않습니다. [`설정 예시`](./jev-plugin.example.jsonc)를 참고하세요. 이 파일은 OmO의 `omo.jsonc`와 별개입니다. 설정 변경 후에는 새 세션을 시작하거나 확장을 다시 로드하세요.
처음 로드하면 `~/.omo/jev-plugin.jsonc`가 `advise` 모드의 기본값으로 생성됩니다. 기존 설정의 모드는 유지되며 `mode`를 생략했다면 암묵적 기본값은 `off`입니다. 기존 전역 설정의 최상위 `model`, `apiKey`, `openrouterApiKey`, `endpoint`는 로드할 때 `provider` 아래로 옮깁니다. 파일을 바꾸기 전에 원본을 `jev-plugin.jsonc.bak.<타임스탬프>`에 백업하며, `_migrations`에 적용 이력을 기록합니다. 기존 설정값과 주석은 가능한 한 유지합니다. 프로젝트별 설정은 상속에 필요한 항목만 적으며 자동으로 채우지 않습니다. [`설정 예시`](./jev-plugin.example.jsonc)를 참고하세요. 이 파일은 OmO의 `omo.jsonc`와 별개입니다. 설정 변경 후에는 새 세션을 시작하거나 확장을 다시 로드하세요.

가장 간단한 설정은 다음과 같습니다.

Expand Down Expand Up @@ -72,9 +72,9 @@ API 키는 `~/.omo/jev-plugin.jsonc`의 `provider.jev_compatible.apiKey` 또는

| 모드 | 동작 |
| --- | --- |
| `off` | Jev를 호출하지 않습니다. 기본값입니다. |
| `off` | Jev를 호출하지 않습니다. |
| `shadow` | 판단을 세션에 기록하지만 에이전트 동작은 바꾸지 않습니다. |
| `advise` | 판단 결과 중 스킬·도구 후보, 반복 및 완료 가능성을 에이전트에 제안합니다. |
| `advise` | 판단 결과 중 스킬·도구 후보, 반복 및 완료 가능성을 에이전트에 제안합니다. 새로 생성하는 설정 파일의 기본값입니다. |
| `act` | `advise`에 더해, 개별적으로 활성화한 도구 활성화·호출 차단·모델 및 사고 수준 선택을 적용합니다. |

`enabled: false`는 모드와 관계없이 플러그인의 판단을 끕니다. 에이전트가 명시적으로 호출한 스킬은 자동 스킬 제안보다 우선합니다. Jev가 적합한 후보를 찾지 못하거나 응답에 확신이 부족하면 후보를 제안하지 않습니다.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ When the plugin loads, it checks npm for a newer version and announces an availa

Set the provider-specific API key in `~/.omo/jev-plugin.jsonc` under `provider.jev_compatible.apiKey` or `provider.respan-ai.apiKey`. If absent, the plugin uses `TYPESAFE_API_KEY` or `OPENROUTER_API_KEY`, respectively, from the **process running OmO/senpi**. Each provider also supports an `endpoint`. Be careful when sharing project configuration containing a key. If the selected provider has no key, the plugin warns on load and makes no decision requests.

Installation alone does not call the decision API. On first load, the plugin creates a default configuration file in `off` mode. Change its mode as described below to enable it.
On first load, the plugin creates a default configuration file in `advise` mode. Decision requests require an API key for the selected provider.

### Telemetry

Expand All @@ -26,7 +26,7 @@ Detailed telemetry is enabled by default and also collects the decision mode, de

## Configuration

On first load, `~/.omo/jev-plugin.jsonc` is created with `mode: "off"`. Legacy top-level `model`, `apiKey`, `openrouterApiKey`, and `endpoint` settings are moved under `provider` when loaded. Before changing the file, the plugin backs it up as `jev-plugin.jsonc.bak.<timestamp>` and records the migration in `_migrations`, preserving existing settings and comments where possible. A project configuration contains only the fields it needs to override; it is not filled in automatically. See the [example configuration](./jev-plugin.example.jsonc). This file is separate from OmO's `omo.jsonc`. Start a new session or reload the extension after changing settings.
On first load, `~/.omo/jev-plugin.jsonc` is created with `mode: "advise"`. Existing configuration keeps its mode; if it omits `mode`, the implicit default remains `off`. Legacy top-level `model`, `apiKey`, `openrouterApiKey`, and `endpoint` settings are moved under `provider` when loaded. Before changing the file, the plugin backs it up as `jev-plugin.jsonc.bak.<timestamp>` and records the migration in `_migrations`, preserving existing settings and comments where possible. A project configuration contains only the fields it needs to override; it is not filled in automatically. See the [example configuration](./jev-plugin.example.jsonc). This file is separate from OmO's `omo.jsonc`. Start a new session or reload the extension after changing settings.

The simplest configuration is:

Expand Down Expand Up @@ -72,9 +72,9 @@ You can place a project override in `.omo/jev-plugin.jsonc`. The plugin does not

| Mode | Behavior |
| --- | --- |
| `off` | Do not call Jev. This is the default. |
| `off` | Do not call Jev. |
| `shadow` | Record decisions in the session without changing agent behavior. |
| `advise` | Suggest skills and tool candidates, repetition, and possible completion to the agent. |
| `advise` | Suggest skills and tool candidates, repetition, and possible completion to the agent. This is the default for newly created configuration files. |
| `act` | In addition to `advise`, apply individually enabled tool activation, call blocking, model routing, and thinking-level selection. |

`enabled: false` disables decisions regardless of mode. An explicitly invoked skill takes precedence over automatic skill suggestions. When Jev finds no suitable candidate or its confidence is too low, the plugin makes no recommendation.
Expand Down
2 changes: 1 addition & 1 deletion jev-plugin.example.jsonc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
// Copy to ~/.omo/jev-plugin.jsonc (or .omo/jev-plugin.jsonc in a trusted project).
// No decision API request is made until you set mode to shadow, advise, or act.
// Advice is enabled by default; decision API requests require a provider API key.
"enabled": true,
// Install newer npm releases automatically and reload the session when idle.
"autoUpdate": false,
Expand Down
2 changes: 1 addition & 1 deletion src/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Runtime integration and its configuration, Jev decision, and update boundaries.
- `turn_start` deduplicates an unchanged state fingerprint and shares the configured call budget with `tool_call` preflight.
- `shadow` records decisions only; `advise` injects hidden context; `act` alone may activate tools or change the session model/thinking level.
- `context` appends advice to the supplied messages; `tool_call` can block an already selected call but cannot replace it.
- `config.ts` creates only the global file when absent, with mode `off` and private permissions; project config is read only when the project is trusted.
- `config.ts` creates only the global file when absent, with mode `advise` and private permissions; existing files without a mode still default to `off`, and project config is read only when the project is trusted.
- `decision.ts` validates selected candidates and thresholds locally; the SDK client disables retries and logging.
- `tool_result` retains the last four summaries; actual output text is included only when configured.
- `config.ts` resolves the API key from the file first, then `TYPESAFE_API_KEY`.
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ export async function loadConfig(
await mkdir(dirname(globalPath), { recursive: true });
try {
const { telemetry: _telemetry, ...defaults } = configSchema.parse({});
await writeFile(globalPath, `${JSON.stringify(defaults, null, 2)}\n`, {
await writeFile(globalPath, `${JSON.stringify({ ...defaults, mode: "advise" }, null, 2)}\n`, {
encoding: "utf8",
flag: "wx",
mode: 0o600,
Expand Down
18 changes: 15 additions & 3 deletions test/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ async function fixture(): Promise<{ cwd: string; globalPath: string; projectPath
};
}

test("creates a private global configuration with network decisions off", async () => {
test("creates a private global configuration in advise mode", async () => {
// Given an empty configuration directory.
const { cwd, globalPath } = await fixture();
// When config is loaded.
const config = await loadConfig(cwd, false, globalPath);
// Then a persisted default leaves network-dependent decisions off.
expect(config.mode).toBe("off");
// Then the persisted default enables advice without changing private file permissions.
expect(config.mode).toBe("advise");
const { telemetry: _telemetry, ...persisted } = config;
expect(JSON.parse(await readFile(globalPath, "utf8"))).toEqual(persisted);
expect((await stat(globalPath)).mode & 0o777).toBe(0o600);
Expand Down Expand Up @@ -150,6 +150,18 @@ test("does not retry a recorded migration when a user removes a default", async
expect(await readFile(globalPath, "utf8")).toBe(existing);
expect((await readdir(cwd)).filter((entry) => entry.startsWith("global.jsonc.bak."))).toHaveLength(0);
});
test("keeps an existing mode-free configuration off without rewriting it", async () => {
// Given an existing file with the prior migration recorded and no explicit mode.
const { cwd, globalPath } = await fixture();
const existing = '{ // retain the existing choice\n "_migrations": ["jev-defaults-v1"] }\n';
await writeFile(globalPath, existing);
// When the configuration is loaded after the new-file default changes.
const config = await loadConfig(cwd, false, globalPath);
// Then the implicit old mode remains off and the file is untouched.
expect(config.mode).toBe("off");
expect(await readFile(globalPath, "utf8")).toBe(existing);
expect((await readdir(cwd)).filter((entry) => entry.startsWith("global.jsonc.bak."))).toHaveLength(0);
});
test("keeps the original API key bytes when validating a migrated config", async () => {
// Given a valid key with whitespace that validation normalizes only in memory.
const { cwd, globalPath } = await fixture();
Expand Down
Loading