@zosmaai/pi-llm-wiki 0.11.2 → 0.11.4
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/CHANGELOG.md +11 -0
- package/README.de.md +2 -1
- package/README.es.md +2 -1
- package/README.fr.md +2 -1
- package/README.hi.md +2 -1
- package/README.ja.md +2 -1
- package/README.ko.md +2 -1
- package/README.md +89 -10
- package/README.pt.md +2 -1
- package/README.ru.md +2 -1
- package/README.zh.md +2 -1
- package/commands/wiki-digest.md +28 -0
- package/commands/wiki-discover.md +30 -0
- package/commands/wiki-ingest.md +36 -0
- package/commands/wiki-init.md +30 -0
- package/commands/wiki-lint.md +25 -0
- package/commands/wiki-query.md +37 -0
- package/commands/wiki-record.md +36 -0
- package/commands/wiki-req.md +55 -0
- package/commands/wiki-retro.md +34 -0
- package/commands/wiki-run.md +31 -0
- package/commands/wiki-skills.md +26 -0
- package/commands/wiki-status.md +16 -0
- package/dist/extensions/llm-wiki/lib/host.js +97 -0
- package/dist/extensions/llm-wiki/lib/observation.js +9 -0
- package/dist/extensions/llm-wiki/lib/runtime.js +13 -1
- package/dist/extensions/llm-wiki/lib/task-config.js +74 -49
- package/dist/extensions/llm-wiki/lib/tools.js +10 -7
- package/dist/extensions/llm-wiki/lib/utils.js +59 -16
- package/dist/mcp/index.js +55 -3
- package/dist/mcp/operations.js +39 -3
- package/docs/api.md +9 -1
- package/docs/configuration.md +49 -8
- package/extensions/llm-wiki/index.ts +44 -6
- package/extensions/llm-wiki/lib/host.ts +125 -0
- package/extensions/llm-wiki/lib/observation.ts +14 -1
- package/extensions/llm-wiki/lib/runtime.ts +13 -1
- package/extensions/llm-wiki/lib/task-config.ts +115 -55
- package/extensions/llm-wiki/lib/tools.ts +10 -7
- package/extensions/llm-wiki/lib/utils.ts +55 -14
- package/mcp/index.ts +65 -2
- package/mcp/operations.ts +47 -4
- package/package.json +12 -1
package/dist/mcp/operations.js
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* used by Pi tools. No operation parses YAML, scans files, scores
|
|
6
6
|
* registry entries, or builds page strings itself.
|
|
7
7
|
*/
|
|
8
|
+
import { bootstrapVault } from "../extensions/llm-wiki/lib/bootstrap.js";
|
|
8
9
|
import { rebuildMetadata } from "../extensions/llm-wiki/lib/metadata.js";
|
|
9
|
-
import {
|
|
10
|
+
import { searchWikiLayered } from "../extensions/llm-wiki/lib/recall.js";
|
|
10
11
|
import { saveInsight } from "../extensions/llm-wiki/lib/retro.js";
|
|
11
12
|
import { captureFile, captureText, captureUrl } from "../extensions/llm-wiki/lib/source-packet.js";
|
|
12
13
|
import { VaultWriteError, inspectVaultFormat, inspectWritableVault, } from "../extensions/llm-wiki/lib/vault-format.js";
|
|
@@ -19,9 +20,44 @@ function projectionOutcome(projection) {
|
|
|
19
20
|
diagnostics: projection.diagnostics.map(({ code, message }) => ({ code, message })),
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Shared bootstrap operation: create (or update) the vault at `paths`.
|
|
25
|
+
*
|
|
26
|
+
* This is the one operation that must work when no vault exists — every other
|
|
27
|
+
* one fails closed naming it. `bootstrapVault` is pure Node (`node:fs`,
|
|
28
|
+
* `node:path` and sibling lib modules), so it needs no model and no
|
|
29
|
+
* credentials, which is what makes it fit the MCP surface.
|
|
30
|
+
*
|
|
31
|
+
* A failed projection rebuild is reported as diagnostics alongside `ok: true`:
|
|
32
|
+
* the vault has been written to disk by then, and `wiki_lint` is the repair
|
|
33
|
+
* path, so failing the call outright would misreport what happened.
|
|
34
|
+
*/
|
|
35
|
+
export async function bootstrapOperation(paths, input) {
|
|
36
|
+
const result = bootstrapVault(paths, { topic: input.topic, mode: input.mode ?? "personal" });
|
|
37
|
+
if (!result.ok) {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
diagnostics: result.diagnostics.map(({ code, message }) => ({ code, message })),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const projection = projectionOutcome(result.projection);
|
|
44
|
+
return {
|
|
45
|
+
ok: true,
|
|
46
|
+
created: result.created,
|
|
47
|
+
diagnostics: projection.ok ? [] : projection.diagnostics,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Shared recall operation: layered search plus vault diagnostics.
|
|
52
|
+
*
|
|
53
|
+
* Layering is the shared contract, not an extension-only feature: MCP clients
|
|
54
|
+
* get the same personal + project merge the Pi `wiki_recall` tool does.
|
|
55
|
+
* `searchWikiLayered` appends personal-vault hits, deduplicates by page ID and
|
|
56
|
+
* tags personal results with `vaultLabel`. It is a no-op when no personal vault
|
|
57
|
+
* exists, or when the resolved vault IS the personal vault.
|
|
58
|
+
*/
|
|
23
59
|
export async function recallOperation(paths, query, maxResults = 5) {
|
|
24
|
-
const results =
|
|
60
|
+
const results = searchWikiLayered(paths, query, maxResults);
|
|
25
61
|
const vaultState = inspectVaultFormat(paths);
|
|
26
62
|
return {
|
|
27
63
|
results,
|
package/docs/api.md
CHANGED
|
@@ -223,6 +223,10 @@ Deterministic health check of the wiki. Scans for orphan pages (no inbound links
|
|
|
223
223
|
(linked but not created), and contradiction markers. Optionally auto-creates stub pages for
|
|
224
224
|
knowledge gaps cited in two or more pages.
|
|
225
225
|
|
|
226
|
+
Runs **asynchronously**: the tool acknowledges immediately and scans off-thread. On completion a
|
|
227
|
+
UI toast fires instantly (when a UI is available) and the full health report is delivered with
|
|
228
|
+
the next user message.
|
|
229
|
+
|
|
226
230
|
**Parameters**
|
|
227
231
|
|
|
228
232
|
| Name | Type | Required | Description |
|
|
@@ -277,9 +281,13 @@ Health is `"⚠️ Warning"` when orphan count exceeds 5, `"🔴 Empty"` when th
|
|
|
277
281
|
|
|
278
282
|
## wiki_rebuild_meta
|
|
279
283
|
|
|
280
|
-
Force a full
|
|
284
|
+
Force a full rebuild of all generated metadata: `registry.json`, `backlinks.json`,
|
|
281
285
|
`index.md`, `log.md`. Use when metadata appears out of sync with actual wiki files.
|
|
282
286
|
|
|
287
|
+
Runs **asynchronously**: the tool acknowledges immediately and rebuilds off-thread. On completion
|
|
288
|
+
a UI toast fires instantly (when a UI is available) and the result is delivered with the next
|
|
289
|
+
user message.
|
|
290
|
+
|
|
283
291
|
If `meta/events.jsonl` is missing or unreadable, rebuild reports a warning and preserves existing log projections while continuing to rebuild registry, backlinks, and indexes. A present zero-byte event file is an intentional empty history.
|
|
284
292
|
|
|
285
293
|
**Parameters**
|
package/docs/configuration.md
CHANGED
|
@@ -31,17 +31,34 @@ The personal vault lives at `~/.llm-wiki/` (or `$WIKI_HOME`) and is always avail
|
|
|
31
31
|
| ----------------------------- | ----------- | ----------------------------------------------- |
|
|
32
32
|
| `WIKI_HOME` | `~/.llm-wiki` | Override the personal wiki vault location |
|
|
33
33
|
| `WIKI_MARKITDOWN_TIMEOUT_MS` | 180000 | Timeout (ms) for MarkItDown PDF/text extraction |
|
|
34
|
+
| `LLM_WIKI_HOST` | auto | Force the host layout: `pi` or `omp` |
|
|
34
35
|
|
|
35
|
-
##
|
|
36
|
+
## Agent Settings
|
|
36
37
|
|
|
37
|
-
Runtime settings for the wiki's background tasks live
|
|
38
|
+
Runtime settings for the wiki's background tasks live under the `llm-wiki`
|
|
39
|
+
namespace of the host's settings file. Both host layouts are read and merged,
|
|
40
|
+
lowest precedence first:
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
1. `<agentDir>/settings.json`, then `config.yml` / `config.yaml`
|
|
43
|
+
— `~/.pi/agent` under pi, `~/.omp/agent` under oh-my-pi
|
|
44
|
+
2. `<cwd>/.pi/{settings.json,config.yml,config.yaml}`
|
|
45
|
+
3. `<cwd>/.omp/{settings.json,config.yml,config.yaml}`
|
|
46
|
+
|
|
47
|
+
The **host-native** project directory is applied last, so it wins: `.omp` under
|
|
48
|
+
oh-my-pi, `.pi` under pi. Reading the other host's directory means a vault
|
|
49
|
+
configured under pi keeps working after `omp` takes over the repository.
|
|
50
|
+
|
|
51
|
+
`/wiki-model` and `/wiki-trajectories` write JSON only, into whichever project
|
|
52
|
+
config directory already exists (host-native first, created if neither is
|
|
53
|
+
present). A hand-authored `config.yml` is read but never rewritten.
|
|
54
|
+
|
|
55
|
+
| Setting | Default | Description |
|
|
56
|
+
| ---------------------- | ---------- | ------------------------------------------------------------ |
|
|
57
|
+
| `taskModel` | — | Model for background tasks (`{ provider: "openai", id: "gpt-4o" }`) |
|
|
58
|
+
| `synthesisLanguage` | — | BCP 47 language tag for ingest synthesis (e.g. `"ru"`, `"fr"`). When unset, synthesis defaults to English. |
|
|
59
|
+
| `trajectories` | false | Enable agent-trajectory working-memory |
|
|
60
|
+
| `notices` | true | Show wiki activity notices in chat |
|
|
61
|
+
| `ambientPersonalVault` | host-dependent | Let the personal vault act as the ambient vault in projects that have no wiki. `true` under pi, `false` under oh-my-pi — see below. |
|
|
45
62
|
|
|
46
63
|
Example:
|
|
47
64
|
|
|
@@ -66,6 +83,30 @@ The vault root is resolved in this priority order:
|
|
|
66
83
|
|
|
67
84
|
This means when you're in a project with its own `.llm-wiki/`, that project wiki is active. When you're outside any project wiki, your personal `~/.llm-wiki/` takes over automatically.
|
|
68
85
|
|
|
86
|
+
### Ambient surfaces in projects without a wiki
|
|
87
|
+
|
|
88
|
+
Three surfaces fire without being asked: the session notice, the periodic
|
|
89
|
+
observe/retro reminder, and the `before_agent_start` recall injection (plus its
|
|
90
|
+
`<wiki_status>` system-prompt footer).
|
|
91
|
+
|
|
92
|
+
Because vault resolution falls back to the personal vault, those surfaces would
|
|
93
|
+
otherwise speak up in *every* directory as soon as `~/.llm-wiki/` exists —
|
|
94
|
+
injecting reminders and unrelated cross-project recall hits into repositories
|
|
95
|
+
where no wiki was ever initialized. Under oh-my-pi the plugin is installed once
|
|
96
|
+
and loads in every project, so that fallback is **off** by default there; under
|
|
97
|
+
pi the historical behaviour is kept.
|
|
98
|
+
|
|
99
|
+
`ambientPersonalVault` overrides the host default in either direction:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{ "llm-wiki": { "ambientPersonalVault": true } }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The gate only affects unprompted injections. Tools and slash commands are
|
|
106
|
+
always registered, so `/wiki-init` and `wiki_bootstrap` work in any directory —
|
|
107
|
+
and once a project has its own `.llm-wiki/`, every ambient surface turns back on
|
|
108
|
+
for it.
|
|
109
|
+
|
|
69
110
|
## Page Frontmatter
|
|
70
111
|
|
|
71
112
|
```yaml
|
|
@@ -20,7 +20,12 @@ import {
|
|
|
20
20
|
} from "./lib/recall.js";
|
|
21
21
|
import { registerWikiRetro } from "./lib/retro.js";
|
|
22
22
|
import { registerBackgroundRuntime } from "./lib/runtime.js";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
loadTaskConfig,
|
|
25
|
+
noticesEnabled,
|
|
26
|
+
personalVaultIsAmbient,
|
|
27
|
+
trajectoriesEnabled,
|
|
28
|
+
} from "./lib/task-config.js";
|
|
24
29
|
import {
|
|
25
30
|
registerWikiBootstrap,
|
|
26
31
|
registerWikiCaptureSource,
|
|
@@ -40,7 +45,11 @@ import {
|
|
|
40
45
|
registerWikiDistillSkills,
|
|
41
46
|
registerWikiRecallSkill,
|
|
42
47
|
} from "./lib/trajectory.js";
|
|
43
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
migrateDoubledPersonalVault,
|
|
50
|
+
resolveProjectVaultRoot,
|
|
51
|
+
resolveVaultPaths,
|
|
52
|
+
} from "./lib/utils.js";
|
|
44
53
|
import { inspectWritableVault } from "./lib/vault-format.js";
|
|
45
54
|
import { applySessionStartStatus } from "./lib/visible-status.js";
|
|
46
55
|
|
|
@@ -68,6 +77,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
68
77
|
// work. Created first so tools (e.g. wiki_ingest) can dispatch to it.
|
|
69
78
|
const runtime = registerBackgroundRuntime(pi);
|
|
70
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Does a wiki apply to the directory this session is working in?
|
|
82
|
+
*
|
|
83
|
+
* Gates every AMBIENT surface — the ones that speak without being asked:
|
|
84
|
+
* session bootstrap/notice, the periodic observe/retro reminder, and
|
|
85
|
+
* `before_agent_start` recall injection. Tools and commands are registered
|
|
86
|
+
* regardless, so `/wiki-init` remains the way in.
|
|
87
|
+
*
|
|
88
|
+
* `resolveVaultRoot` falls back to the personal vault when a project has
|
|
89
|
+
* none, which is why the ambient surfaces used to fire in EVERY directory
|
|
90
|
+
* once a personal vault existed — reminders and unrelated cross-project
|
|
91
|
+
* recall hits leaking into repositories that never initialized a wiki.
|
|
92
|
+
* Under omp that fallback is off by default (`llm-wiki.ambientPersonalVault`);
|
|
93
|
+
* under pi it stays on, preserving the historical behavior.
|
|
94
|
+
*
|
|
95
|
+
* Resolved per call, not once at load: `cwd` changes within a session.
|
|
96
|
+
*/
|
|
97
|
+
const wikiAppliesTo = (cwd: string): boolean =>
|
|
98
|
+
resolveProjectVaultRoot(cwd) !== null || personalVaultIsAmbient(runtime.config);
|
|
99
|
+
|
|
71
100
|
registerWikiBootstrap(pi);
|
|
72
101
|
registerWikiCaptureSource(pi, runtime);
|
|
73
102
|
registerWikiIngest(pi, runtime);
|
|
@@ -106,9 +135,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
106
135
|
registerWikiObserve(pi, runtime, reminderState);
|
|
107
136
|
// Visible observe/retro reminder by default (issue #77); silenced when the
|
|
108
137
|
// user sets `llm-wiki.notices: false`. Resolver reads the live config so the
|
|
109
|
-
// setting takes effect without a restart.
|
|
138
|
+
// setting takes effect without a restart. `display: false` still injects the
|
|
139
|
+
// reminder into model context, so the "no wiki here" case needs its own gate.
|
|
110
140
|
registerObservationReminder(pi, reminderState, {
|
|
111
141
|
display: () => noticesEnabled(runtime.config),
|
|
142
|
+
enabled: () => wikiAppliesTo(process.cwd()),
|
|
112
143
|
});
|
|
113
144
|
|
|
114
145
|
installGuardrails(pi, runtime);
|
|
@@ -137,6 +168,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
137
168
|
console.warn(`[llm-wiki] doubled-dotdir migration skipped: ${(err as Error).message}`);
|
|
138
169
|
}
|
|
139
170
|
|
|
171
|
+
// Ambient gate. `ensureConfig` first so an explicit
|
|
172
|
+
// `llm-wiki.ambientPersonalVault` is honored on the very first session —
|
|
173
|
+
// `runtime.config` is otherwise empty until the first `turn_start`.
|
|
174
|
+
runtime.ensureConfig(process.cwd());
|
|
175
|
+
if (!wikiAppliesTo(process.cwd())) return;
|
|
176
|
+
|
|
140
177
|
const paths = resolveVaultPaths(process.cwd());
|
|
141
178
|
if (!existsSync(join(paths.dotWiki, "config.json"))) {
|
|
142
179
|
// Silently create the wiki vault — no UI prompts. Topic/mode will be
|
|
@@ -168,9 +205,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
168
205
|
|
|
169
206
|
// Surface the "wiki active" badge and the active background task model
|
|
170
207
|
// (issue #69), both gated by `llm-wiki.notices` (issue #77, regression
|
|
171
|
-
// fixed in #83, helper extracted in #84). `ensureConfig`
|
|
172
|
-
//
|
|
173
|
-
runtime.ensureConfig(process.cwd());
|
|
208
|
+
// fixed in #83, helper extracted in #84). The `ensureConfig` above the
|
|
209
|
+
// ambient gate already loaded the project settings this reads.
|
|
174
210
|
applySessionStartStatus({
|
|
175
211
|
ui: ctx.ui,
|
|
176
212
|
runtime,
|
|
@@ -196,6 +232,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
196
232
|
// from the user's first prompt and update config via wiki_bootstrap.
|
|
197
233
|
// 2. Search both personal + project vaults for relevant pages.
|
|
198
234
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
235
|
+
if (!wikiAppliesTo(process.cwd())) return;
|
|
236
|
+
|
|
199
237
|
const paths = resolveVaultPaths(process.cwd());
|
|
200
238
|
if (!existsSync(join(paths.dotWiki, "config.json"))) {
|
|
201
239
|
return;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getAgentDir } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Host adapter for the two coding agents that can load this extension:
|
|
7
|
+
*
|
|
8
|
+
* - **pi** — `@mariozechner/pi-coding-agent`, config dir `.pi`
|
|
9
|
+
* - **omp** — oh-my-pi (`@oh-my-pi/pi-coding-agent`), config dir `.omp`
|
|
10
|
+
*
|
|
11
|
+
* omp rewrites `@mariozechner/pi-*` (and bare `typebox`) imports onto its own
|
|
12
|
+
* bundled packages at load time (its `legacy-pi-compat.ts`), so the *module
|
|
13
|
+
* graph* needs no changes. What does differ is the on-disk config layout:
|
|
14
|
+
*
|
|
15
|
+
* | | pi | omp |
|
|
16
|
+
* |---|---|---|
|
|
17
|
+
* | user dir | `~/.pi/agent` | `~/.omp/agent` |
|
|
18
|
+
* | project dir | `<cwd>/.pi` | `<cwd>/.omp` |
|
|
19
|
+
* | settings file | `settings.json` | `settings.json`, then `config.yml` |
|
|
20
|
+
*
|
|
21
|
+
* omp explicitly does **not** read `.pi` (its config source order is
|
|
22
|
+
* `.omp` → `.claude` → `.codex` → `.gemini`), so a wiki configured under pi
|
|
23
|
+
* would silently lose its settings after switching hosts. This module keeps
|
|
24
|
+
* both layouts readable and picks a sensible file to write to.
|
|
25
|
+
*
|
|
26
|
+
* Everything here is additive: on pi with only a `.pi/` directory the effective
|
|
27
|
+
* behaviour is identical to the pre-compat code path, which keeps upstream
|
|
28
|
+
* merges clean.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
export type HostKind = "pi" | "omp";
|
|
32
|
+
|
|
33
|
+
/** Project config directory name per host. */
|
|
34
|
+
const CONFIG_DIR: Record<HostKind, string> = { pi: ".pi", omp: ".omp" };
|
|
35
|
+
|
|
36
|
+
/** Settings file names inside a config directory, lowest → highest precedence. */
|
|
37
|
+
const SETTINGS_FILES = ["settings.json", "config.yml", "config.yaml"] as const;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Detect which agent is hosting this extension.
|
|
41
|
+
*
|
|
42
|
+
* Ordered by reliability:
|
|
43
|
+
* 1. `LLM_WIKI_HOST` — explicit escape hatch (tests, exotic embeddings).
|
|
44
|
+
* 2. The agent directory path: pi resolves `~/.pi/agent`, omp `~/.omp/agent`.
|
|
45
|
+
* A `PI_CODING_AGENT_DIR` override that keeps the marker segment still
|
|
46
|
+
* classifies correctly; anything else falls through.
|
|
47
|
+
* 3. `OMP_PROFILE`, which omp sets on itself whenever a profile is active.
|
|
48
|
+
* 4. Default `pi` — the historical behaviour.
|
|
49
|
+
*/
|
|
50
|
+
export function detectHost(): HostKind {
|
|
51
|
+
const forced = process.env.LLM_WIKI_HOST?.trim().toLowerCase();
|
|
52
|
+
if (forced === "omp" || forced === "pi") return forced;
|
|
53
|
+
|
|
54
|
+
let agentDir = "";
|
|
55
|
+
try {
|
|
56
|
+
agentDir = getAgentDir();
|
|
57
|
+
} catch {
|
|
58
|
+
agentDir = "";
|
|
59
|
+
}
|
|
60
|
+
if (agentDir) {
|
|
61
|
+
const segments = agentDir.split(/[\\/]/);
|
|
62
|
+
if (segments.includes(".omp")) return "omp";
|
|
63
|
+
if (segments.includes(".pi")) return "pi";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (process.env.OMP_PROFILE !== undefined) return "omp";
|
|
67
|
+
return "pi";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Every project settings file that may hold `llm-wiki` configuration, ordered
|
|
72
|
+
* from lowest to highest precedence so callers can merge left-to-right.
|
|
73
|
+
*
|
|
74
|
+
* The host's *native* directory is last (wins). The foreign directory is still
|
|
75
|
+
* read so a vault configured under pi keeps working after omp takes over the
|
|
76
|
+
* repository, and vice versa. Within a directory `config.yml` follows
|
|
77
|
+
* `settings.json`, matching omp's own project-settings precedence.
|
|
78
|
+
*/
|
|
79
|
+
export function listProjectSettingsFiles(cwd: string, host: HostKind = detectHost()): string[] {
|
|
80
|
+
const foreign: HostKind = host === "omp" ? "pi" : "omp";
|
|
81
|
+
const files: string[] = [];
|
|
82
|
+
for (const kind of [foreign, host]) {
|
|
83
|
+
const dir = join(cwd, CONFIG_DIR[kind]);
|
|
84
|
+
for (const name of SETTINGS_FILES) files.push(join(dir, name));
|
|
85
|
+
}
|
|
86
|
+
return files;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* User-level settings files, lowest → highest precedence.
|
|
91
|
+
*
|
|
92
|
+
* `getAgentDir()` already resolves per host (`~/.pi/agent` vs `~/.omp/agent`),
|
|
93
|
+
* so only the file names differ: omp migrates `settings.json` into `config.yml`
|
|
94
|
+
* on first start, and a migrated install has *only* the YAML file.
|
|
95
|
+
*/
|
|
96
|
+
export function listGlobalSettingsFiles(): string[] {
|
|
97
|
+
let agentDir = "";
|
|
98
|
+
try {
|
|
99
|
+
agentDir = getAgentDir();
|
|
100
|
+
} catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
if (!agentDir) return [];
|
|
104
|
+
return SETTINGS_FILES.map((name) => join(agentDir, name));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The project settings file this extension writes to.
|
|
109
|
+
*
|
|
110
|
+
* Always JSON (`settings.json`) — both hosts read it, and rewriting a user's
|
|
111
|
+
* hand-authored `config.yml` would destroy comments and formatting.
|
|
112
|
+
*
|
|
113
|
+
* Directory choice: an already-existing project config directory wins (so a
|
|
114
|
+
* repo that only has `.pi/` keeps a single settings file), otherwise the
|
|
115
|
+
* detected host's native directory is created.
|
|
116
|
+
*/
|
|
117
|
+
export function resolveProjectSettingsPath(cwd: string, host: HostKind = detectHost()): string {
|
|
118
|
+
const native = join(cwd, CONFIG_DIR[host]);
|
|
119
|
+
if (existsSync(native)) return join(native, "settings.json");
|
|
120
|
+
|
|
121
|
+
const foreign = join(cwd, CONFIG_DIR[host === "omp" ? "pi" : "omp"]);
|
|
122
|
+
if (existsSync(foreign)) return join(foreign, "settings.json");
|
|
123
|
+
|
|
124
|
+
return join(native, "settings.json");
|
|
125
|
+
}
|
|
@@ -322,11 +322,20 @@ export function buildReminderText(): string {
|
|
|
322
322
|
* `options.display` (issue #77) controls whether the reminder is shown to the
|
|
323
323
|
* user (`true`, the default) or injected silently into model context only
|
|
324
324
|
* (`false`). Pass a resolver so the live `notices` config is read at send time.
|
|
325
|
+
*
|
|
326
|
+
* `options.enabled` gates the reminder entirely — note that `display: false`
|
|
327
|
+
* still injects it into model context, so it is NOT a way to switch the
|
|
328
|
+
* reminder off. Callers pass a resolver that answers "does a wiki apply to the
|
|
329
|
+
* current working directory", evaluated per turn because the session can move.
|
|
325
330
|
*/
|
|
326
331
|
export function registerObservationReminder(
|
|
327
332
|
pi: ExtensionAPI,
|
|
328
333
|
reminderState: ReminderState,
|
|
329
|
-
options?: {
|
|
334
|
+
options?: {
|
|
335
|
+
turnsBetweenReminders?: number;
|
|
336
|
+
display?: boolean | (() => boolean);
|
|
337
|
+
enabled?: () => boolean;
|
|
338
|
+
},
|
|
330
339
|
): void {
|
|
331
340
|
const REMINDER_INTERVAL = options?.turnsBetweenReminders ?? 5;
|
|
332
341
|
const resolveDisplay = (): boolean => {
|
|
@@ -355,6 +364,10 @@ export function registerObservationReminder(
|
|
|
355
364
|
// errors cause multiple retries, each firing agent_end).
|
|
356
365
|
if ("willRetry" in event && (event as { willRetry?: boolean }).willRetry) return;
|
|
357
366
|
|
|
367
|
+
// No wiki applies here: never nag, and never accumulate a pending reminder
|
|
368
|
+
// that would fire the moment the session moves into a wiki-bearing project.
|
|
369
|
+
if (options?.enabled && !options.enabled()) return;
|
|
370
|
+
|
|
358
371
|
turnsSinceLastReminder++;
|
|
359
372
|
if (turnsSinceLastReminder < REMINDER_INTERVAL) return;
|
|
360
373
|
if (reminderState.observeDoneThisSession) return;
|
|
@@ -213,8 +213,20 @@ export class Runtime {
|
|
|
213
213
|
*/
|
|
214
214
|
launchReported(ctx: LaunchCtx, label: string, work: () => Promise<string | null>): Promise<void> {
|
|
215
215
|
return this.launchTask(ctx, label, async () => {
|
|
216
|
+
// Capture synchronously — after `await work()` the extension ctx may be
|
|
217
|
+
// a stale proxy (newSession/fork/switchSession/reload) and accessing
|
|
218
|
+
// ctx.hasUI or ctx.ui on it throws (see launchTask).
|
|
219
|
+
const hasUI = ctx.hasUI;
|
|
220
|
+
const ui = ctx.ui;
|
|
216
221
|
const summary = await work();
|
|
217
|
-
if (summary)
|
|
222
|
+
if (summary) {
|
|
223
|
+
// Instant completion feedback: the nextTurn report below is queued for
|
|
224
|
+
// the next user prompt, so without a toast a background task looks
|
|
225
|
+
// stuck. Mirrors the failure notification in launchTask and the
|
|
226
|
+
// success toast already used by wiki_ingest.
|
|
227
|
+
if (hasUI && ui) ui.notify(summary.split("\n")[0].replace(/\*\*/g, ""), "info");
|
|
228
|
+
this.report(summary);
|
|
229
|
+
}
|
|
218
230
|
});
|
|
219
231
|
}
|
|
220
232
|
|