@oh-my-pi/pi-coding-agent 16.0.1 → 16.0.3
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 +70 -0
- package/README.md +0 -1
- package/dist/cli.js +316 -371
- package/dist/types/advisor/advise-tool.d.ts +30 -1
- package/dist/types/commands/install.d.ts +1 -1
- package/dist/types/config/model-resolver.d.ts +22 -0
- package/dist/types/config/settings-schema.d.ts +0 -10
- package/dist/types/eval/js/shared/runtime.d.ts +1 -0
- package/dist/types/eval/js/worker-core.d.ts +1 -0
- package/dist/types/exec/non-interactive-env.d.ts +2 -0
- package/dist/types/extensibility/extensions/loader.d.ts +2 -2
- package/dist/types/goals/runtime.d.ts +0 -1
- package/dist/types/mcp/tool-bridge.d.ts +3 -0
- package/dist/types/modes/components/custom-editor.d.ts +14 -4
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +3 -2
- package/dist/types/modes/theme/mermaid-cache.d.ts +18 -1
- package/dist/types/modes/types.d.ts +1 -1
- package/dist/types/registry/agent-lifecycle.d.ts +16 -1
- package/dist/types/sdk.d.ts +8 -0
- package/dist/types/session/agent-session.d.ts +20 -8
- package/dist/types/session/messages.d.ts +3 -0
- package/dist/types/session/session-dump-format.d.ts +8 -2
- package/dist/types/session/session-entries.d.ts +4 -0
- package/dist/types/session/session-history-format.d.ts +2 -0
- package/dist/types/session/session-manager.d.ts +22 -0
- package/dist/types/stt/downloader.d.ts +5 -5
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/persisted-revive.d.ts +36 -0
- package/dist/types/tiny/models.d.ts +8 -0
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/index.d.ts +0 -1
- package/dist/types/utils/markit.d.ts +8 -0
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +156 -12
- package/src/advisor/advise-tool.ts +48 -6
- package/src/advisor/runtime.ts +10 -3
- package/src/auto-thinking/classifier.ts +12 -3
- package/src/cli/args.ts +1 -0
- package/src/cli.ts +2 -2
- package/src/commands/install.ts +3 -3
- package/src/config/model-resolver.ts +63 -12
- package/src/config/settings-schema.ts +0 -11
- package/src/discovery/github.ts +89 -1
- package/src/eval/agent-bridge.ts +2 -0
- package/src/eval/js/context-manager.ts +2 -1
- package/src/eval/js/shared/runtime.ts +189 -15
- package/src/eval/js/worker-core.ts +19 -0
- package/src/exec/bash-executor.ts +2 -2
- package/src/exec/non-interactive-env.ts +71 -0
- package/src/export/html/index.ts +1 -1
- package/src/export/html/tool-views.generated.js +34 -35
- package/src/extensibility/extensions/loader.ts +21 -9
- package/src/extensibility/extensions/runner.ts +17 -1
- package/src/extensibility/plugins/loader.ts +154 -21
- package/src/extensibility/plugins/manager.ts +40 -33
- package/src/goals/runtime.ts +1 -23
- package/src/internal-urls/docs-index.generated.ts +9 -11
- package/src/main.ts +20 -0
- package/src/mcp/render.ts +11 -1
- package/src/mcp/tool-bridge.ts +3 -0
- package/src/modes/components/custom-editor.test.ts +63 -18
- package/src/modes/components/custom-editor.ts +63 -15
- package/src/modes/controllers/command-controller.ts +2 -2
- package/src/modes/controllers/input-controller.ts +15 -9
- package/src/modes/controllers/selector-controller.ts +13 -8
- package/src/modes/controllers/tan-command-controller.ts +1 -0
- package/src/modes/interactive-mode.ts +4 -2
- package/src/modes/setup-wizard/wizard-overlay.ts +26 -4
- package/src/modes/theme/mermaid-cache.ts +74 -11
- package/src/modes/theme/theme.ts +14 -1
- package/src/modes/types.ts +1 -1
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/registry/agent-lifecycle.ts +60 -8
- package/src/sdk.ts +20 -26
- package/src/session/agent-session.ts +381 -110
- package/src/session/artifacts.ts +19 -1
- package/src/session/messages.ts +1 -1
- package/src/session/session-dump-format.ts +167 -23
- package/src/session/session-entries.ts +4 -0
- package/src/session/session-history-format.ts +37 -3
- package/src/session/session-manager.ts +94 -4
- package/src/slash-commands/builtin-registry.ts +4 -7
- package/src/stt/asr-client.ts +6 -0
- package/src/stt/downloader.ts +13 -6
- package/src/stt/stt-controller.ts +52 -11
- package/src/system-prompt.ts +7 -1
- package/src/task/executor.ts +118 -6
- package/src/task/index.ts +2 -2
- package/src/task/persisted-revive.ts +128 -0
- package/src/tiny/models.ts +10 -0
- package/src/tiny/worker.ts +4 -3
- package/src/tools/builtin-names.ts +0 -1
- package/src/tools/index.ts +0 -4
- package/src/tools/output-meta.ts +17 -3
- package/src/utils/lang-from-path.ts +5 -0
- package/src/utils/markit.ts +24 -1
- package/src/utils/title-generator.ts +4 -4
- package/dist/types/tools/render-mermaid.d.ts +0 -38
- package/src/prompts/tools/render-mermaid.md +0 -9
- package/src/tools/render-mermaid.ts +0 -69
|
@@ -10,6 +10,7 @@ import type { KeyId } from "@oh-my-pi/pi-tui";
|
|
|
10
10
|
import { hasFsCode, isEacces, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
11
11
|
import { z } from "zod/v4";
|
|
12
12
|
import { type ExtensionModule, extensionModuleCapability } from "../../capability/extension-module";
|
|
13
|
+
import { type Hook, hookCapability } from "../../capability/hook";
|
|
13
14
|
import { loadCapability } from "../../discovery";
|
|
14
15
|
import { getExtensionNameFromPath } from "../../discovery/helpers";
|
|
15
16
|
import type { ExecOptions } from "../../exec/exec";
|
|
@@ -479,8 +480,8 @@ async function discoverExtensionsInDir(dir: string): Promise<string[]> {
|
|
|
479
480
|
/**
|
|
480
481
|
* Discover absolute paths of extensions to load, without importing or
|
|
481
482
|
* binding factories. Hot path on session startup — the scan walks native
|
|
482
|
-
* `.omp`/`.pi` extension capabilities,
|
|
483
|
-
* configured paths.
|
|
483
|
+
* `.omp`/`.pi` extension capabilities, JS/TS hook factories, the
|
|
484
|
+
* installed-plugin tree, and any configured paths.
|
|
484
485
|
*
|
|
485
486
|
* Subagents reuse the parent's collected paths via the SDK's
|
|
486
487
|
* `preloadedExtensionPaths` option, then call {@link loadExtensions} themselves
|
|
@@ -492,11 +493,12 @@ async function discoverExtensionsInDir(dir: string): Promise<string[]> {
|
|
|
492
493
|
export async function discoverExtensionPaths(
|
|
493
494
|
configuredPaths: string[],
|
|
494
495
|
cwd: string,
|
|
495
|
-
disabledExtensionIds
|
|
496
|
+
disabledExtensionIds?: string[],
|
|
496
497
|
): Promise<string[]> {
|
|
497
498
|
const allPaths: string[] = [];
|
|
498
499
|
const seen = new Set<string>();
|
|
499
|
-
const disabled = new Set(disabledExtensionIds);
|
|
500
|
+
const disabled = new Set(disabledExtensionIds ?? []);
|
|
501
|
+
const loadOptions = disabledExtensionIds ? { cwd, disabledExtensions: disabledExtensionIds } : { cwd };
|
|
500
502
|
|
|
501
503
|
const isDisabledName = (name: string): boolean => disabled.has(`extension-module:${name}`);
|
|
502
504
|
|
|
@@ -516,17 +518,27 @@ export async function discoverExtensionPaths(
|
|
|
516
518
|
};
|
|
517
519
|
|
|
518
520
|
// 1. Discover extension modules via capability API (native .omp/.pi only)
|
|
519
|
-
const discovered = await loadCapability<ExtensionModule>(extensionModuleCapability.id,
|
|
521
|
+
const discovered = await loadCapability<ExtensionModule>(extensionModuleCapability.id, loadOptions);
|
|
520
522
|
for (const ext of discovered.items) {
|
|
521
523
|
if (ext._source.provider !== "native") continue;
|
|
522
|
-
if (isDisabledName(ext.name)) continue;
|
|
523
524
|
addPath(ext.path);
|
|
524
525
|
}
|
|
525
526
|
|
|
526
|
-
// 2. Discover
|
|
527
|
+
// 2. Discover JS/TS hook factories from hookCapability and bind them through
|
|
528
|
+
// the extension runner, which owns the current runtime event bus. Hook
|
|
529
|
+
// capability loading already applies hook-specific disabled ids; do not also
|
|
530
|
+
// filter them through extension-module names.
|
|
531
|
+
const hooks = await loadCapability<Hook>(hookCapability.id, loadOptions);
|
|
532
|
+
for (const hookPath of hooks.items
|
|
533
|
+
.map(hook => hook.path)
|
|
534
|
+
.filter(hookPath => isExtensionFile(path.basename(hookPath)))) {
|
|
535
|
+
addPath(hookPath);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// 3. Discover extension entry points from installed plugins
|
|
527
539
|
addPaths(await getAllPluginExtensionPaths(cwd));
|
|
528
540
|
|
|
529
|
-
//
|
|
541
|
+
// 4. Explicitly configured paths
|
|
530
542
|
for (const configuredPath of configuredPaths) {
|
|
531
543
|
const resolved = resolvePath(configuredPath, cwd);
|
|
532
544
|
|
|
@@ -566,7 +578,7 @@ export async function discoverAndLoadExtensions(
|
|
|
566
578
|
configuredPaths: string[],
|
|
567
579
|
cwd: string,
|
|
568
580
|
eventBus?: EventBus,
|
|
569
|
-
disabledExtensionIds
|
|
581
|
+
disabledExtensionIds?: string[],
|
|
570
582
|
): Promise<LoadExtensionsResult> {
|
|
571
583
|
const paths = await discoverExtensionPaths(configuredPaths, cwd, disabledExtensionIds);
|
|
572
584
|
return loadExtensions(paths, cwd, eventBus);
|
|
@@ -543,7 +543,9 @@ export class ExtensionRunner {
|
|
|
543
543
|
event.type === "session_before_tree"
|
|
544
544
|
);
|
|
545
545
|
}
|
|
546
|
-
|
|
546
|
+
#isSessionShutdownEvent(event: RunnerEmitEvent): event is Extract<RunnerEmitEvent, { type: "session_shutdown" }> {
|
|
547
|
+
return event.type === "session_shutdown";
|
|
548
|
+
}
|
|
547
549
|
async #runHandlerWithTimeout<TEvent extends { type: string }, TResult>(
|
|
548
550
|
handler: (event: TEvent, ctx: ExtensionContext) => Promise<TResult | undefined> | TResult | undefined,
|
|
549
551
|
event: TEvent,
|
|
@@ -588,6 +590,20 @@ export class ExtensionRunner {
|
|
|
588
590
|
const ctx = this.createContext();
|
|
589
591
|
let result: SessionBeforeEventResult | SessionCompactingResult | undefined;
|
|
590
592
|
|
|
593
|
+
if (this.#isSessionShutdownEvent(event)) {
|
|
594
|
+
const timeoutMs = handlerTimeoutForEvent(event.type);
|
|
595
|
+
const promises: Promise<unknown>[] = [];
|
|
596
|
+
for (const ext of this.extensions) {
|
|
597
|
+
const handlers = ext.handlers.get(event.type);
|
|
598
|
+
if (!handlers || handlers.length === 0) continue;
|
|
599
|
+
for (const handler of handlers) {
|
|
600
|
+
promises.push(this.#runHandlerWithTimeout(handler, event, ctx, ext, timeoutMs));
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
await Promise.all(promises);
|
|
604
|
+
return result as RunnerEmitResult<TEvent>;
|
|
605
|
+
}
|
|
606
|
+
|
|
591
607
|
for (const ext of this.extensions) {
|
|
592
608
|
const handlers = ext.handlers.get(event.type);
|
|
593
609
|
if (!handlers || handlers.length === 0) continue;
|
|
@@ -143,29 +143,161 @@ export async function getEnabledPlugins(cwd: string, opts: { home?: string } = {
|
|
|
143
143
|
// Path Resolution
|
|
144
144
|
// =============================================================================
|
|
145
145
|
|
|
146
|
-
const
|
|
146
|
+
const MANIFEST_ENTRY_MODULE_EXTENSIONS = [".ts", ".js", ".mjs", ".cjs"];
|
|
147
|
+
const MANIFEST_ENTRY_INDEX_NAMES = MANIFEST_ENTRY_MODULE_EXTENSIONS.map(ext => `index${ext}`);
|
|
148
|
+
|
|
149
|
+
/** `.d.ts` / `.d.mts` / `.d.cts` TypeScript declaration files — never loadable as modules. */
|
|
150
|
+
const DECLARATION_FILE_RE = /\.d\.[mc]?ts$/;
|
|
151
|
+
|
|
152
|
+
/** A loadable module file: a .ts/.js/.mjs/.cjs that is not a declaration file. */
|
|
153
|
+
function isModuleFile(name: string): boolean {
|
|
154
|
+
return MANIFEST_ENTRY_MODULE_EXTENSIONS.includes(path.extname(name)) && !DECLARATION_FILE_RE.test(name);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** First `index.{ts,js,mjs,cjs}` inside `dir`, or null when none exists. */
|
|
158
|
+
function findDirectoryIndex(dir: string): string | null {
|
|
159
|
+
for (const name of MANIFEST_ENTRY_INDEX_NAMES) {
|
|
160
|
+
const candidate = path.join(dir, name);
|
|
161
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface DeclaredManifestEntries {
|
|
167
|
+
/** True when the directory's package.json declares a non-empty `omp`/`pi` `extensions` array. */
|
|
168
|
+
declared: boolean;
|
|
169
|
+
/** Resolved, existing module files for the declared entries (may be empty when declared files are missing). */
|
|
170
|
+
files: string[];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Read the extension entries declared by `dir`'s own package.json `omp`/`pi`
|
|
175
|
+
* manifest. `declared` distinguishes "a manifest explicitly lists extensions"
|
|
176
|
+
* (authoritative — callers must not fall back to index/scan, so a missing
|
|
177
|
+
* declared file surfaces as a missing entry instead of silently loading a stale
|
|
178
|
+
* index) from "no manifest / no extensions field" (callers fall back to
|
|
179
|
+
* convention). Mirrors the manifest branch of the configured-directory (`-e`)
|
|
180
|
+
* scanner: a declared entry that is a file resolves to itself; one that is a
|
|
181
|
+
* directory resolves to its direct index.{ts,js,mjs,cjs}.
|
|
182
|
+
*/
|
|
183
|
+
function readDeclaredManifestEntries(dir: string): DeclaredManifestEntries {
|
|
184
|
+
let raw: string;
|
|
185
|
+
try {
|
|
186
|
+
raw = fs.readFileSync(path.join(dir, "package.json"), "utf8");
|
|
187
|
+
} catch {
|
|
188
|
+
return { declared: false, files: [] };
|
|
189
|
+
}
|
|
190
|
+
let pkg: { omp?: { extensions?: unknown }; pi?: { extensions?: unknown } };
|
|
191
|
+
try {
|
|
192
|
+
pkg = JSON.parse(raw) as { omp?: { extensions?: unknown }; pi?: { extensions?: unknown } };
|
|
193
|
+
} catch {
|
|
194
|
+
return { declared: false, files: [] };
|
|
195
|
+
}
|
|
196
|
+
const declared = (pkg.omp ?? pkg.pi)?.extensions;
|
|
197
|
+
if (!Array.isArray(declared) || declared.length === 0) {
|
|
198
|
+
return { declared: false, files: [] };
|
|
199
|
+
}
|
|
200
|
+
const files: string[] = [];
|
|
201
|
+
for (const entry of declared) {
|
|
202
|
+
if (typeof entry !== "string") continue;
|
|
203
|
+
const candidate = path.resolve(dir, entry);
|
|
204
|
+
let candidateStats: fs.Stats;
|
|
205
|
+
try {
|
|
206
|
+
candidateStats = fs.statSync(candidate);
|
|
207
|
+
} catch {
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (candidateStats.isDirectory()) {
|
|
211
|
+
const index = findDirectoryIndex(candidate);
|
|
212
|
+
if (index) files.push(index);
|
|
213
|
+
} else {
|
|
214
|
+
files.push(candidate);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return { declared: true, files };
|
|
218
|
+
}
|
|
147
219
|
|
|
148
220
|
/**
|
|
149
|
-
* Resolve a
|
|
150
|
-
*
|
|
151
|
-
* the
|
|
152
|
-
*
|
|
221
|
+
* Resolve a directory to its loadable extension module files, mirroring the
|
|
222
|
+
* configured-directory (`-e`) scanner in extensions/loader.ts:
|
|
223
|
+
* 1. the directory's own package.json `omp`/`pi` `extensions` entries —
|
|
224
|
+
* authoritative: a manifest that lists extensions suppresses the index/scan
|
|
225
|
+
* fallback, so a missing declared file is reported rather than silently
|
|
226
|
+
* replaced by a decoy index
|
|
227
|
+
* 2. a direct index.{ts,js,mjs,cjs}
|
|
228
|
+
* 3. one level of children: each direct *.{ts,js,mjs,cjs} file plus each
|
|
229
|
+
* sub-directory resolved by the same precedence (manifest, then index)
|
|
153
230
|
*/
|
|
154
|
-
function
|
|
231
|
+
function resolveDirectoryEntries(dir: string): string[] {
|
|
232
|
+
const manifest = readDeclaredManifestEntries(dir);
|
|
233
|
+
if (manifest.declared) return manifest.files;
|
|
234
|
+
|
|
235
|
+
const directIndex = findDirectoryIndex(dir);
|
|
236
|
+
if (directIndex) return [directIndex];
|
|
237
|
+
|
|
238
|
+
let children: string[];
|
|
239
|
+
try {
|
|
240
|
+
children = fs.readdirSync(dir);
|
|
241
|
+
} catch {
|
|
242
|
+
return [];
|
|
243
|
+
}
|
|
244
|
+
const resolved: string[] = [];
|
|
245
|
+
for (const child of children.sort()) {
|
|
246
|
+
const childPath = path.join(dir, child);
|
|
247
|
+
let childStats: fs.Stats;
|
|
248
|
+
try {
|
|
249
|
+
// statSync follows symlinks, matching the configured-dir loader.
|
|
250
|
+
childStats = fs.statSync(childPath);
|
|
251
|
+
} catch {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (childStats.isDirectory()) {
|
|
255
|
+
const childManifest = readDeclaredManifestEntries(childPath);
|
|
256
|
+
if (childManifest.declared) {
|
|
257
|
+
resolved.push(...childManifest.files);
|
|
258
|
+
} else {
|
|
259
|
+
const index = findDirectoryIndex(childPath);
|
|
260
|
+
if (index) resolved.push(index);
|
|
261
|
+
}
|
|
262
|
+
} else if (isModuleFile(child)) {
|
|
263
|
+
resolved.push(childPath);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return resolved;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Resolve a plugin manifest entry to the loadable module files it names:
|
|
271
|
+
* - a file entry → that file
|
|
272
|
+
* - a directory:
|
|
273
|
+
* - when `expandDirectory` (the `extensions` key), resolved by
|
|
274
|
+
* {@link resolveDirectoryEntries} — its own package.json `omp`/`pi`
|
|
275
|
+
* `extensions`, then a direct index, then a one-level scan of
|
|
276
|
+
* sub-extensions — matching the pi `extensions/<name>/index.ts` convention
|
|
277
|
+
* and OMP's configured-directory (`-e`) extension loader
|
|
278
|
+
* - otherwise (tools/hooks/commands) only a direct index.{ts,js,mjs,cjs}.
|
|
279
|
+
* The sub-extension scan and the `omp`/`pi` `extensions` manifest are
|
|
280
|
+
* extensions-specific and must not hijack a non-extension directory entry
|
|
281
|
+
* (e.g. a `tools: "."` entry must still resolve `./index.ts`).
|
|
282
|
+
*
|
|
283
|
+
* Returns an empty array when nothing loadable exists at `joined`, letting
|
|
284
|
+
* callers flag a missing entry instead of silently dropping it.
|
|
285
|
+
*/
|
|
286
|
+
function resolveManifestEntryFiles(joined: string, expandDirectory: boolean): string[] {
|
|
155
287
|
let stats: fs.Stats;
|
|
156
288
|
try {
|
|
157
289
|
stats = fs.statSync(joined);
|
|
158
290
|
} catch {
|
|
159
|
-
return
|
|
291
|
+
return [];
|
|
160
292
|
}
|
|
161
|
-
if (stats.isDirectory()) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return null;
|
|
293
|
+
if (!stats.isDirectory()) {
|
|
294
|
+
return [joined];
|
|
295
|
+
}
|
|
296
|
+
if (expandDirectory) {
|
|
297
|
+
return resolveDirectoryEntries(joined);
|
|
167
298
|
}
|
|
168
|
-
|
|
299
|
+
const index = findDirectoryIndex(joined);
|
|
300
|
+
return index ? [index] : [];
|
|
169
301
|
}
|
|
170
302
|
|
|
171
303
|
/**
|
|
@@ -196,16 +328,17 @@ export function resolvePluginManifestEntries(
|
|
|
196
328
|
const declared: Array<{ entry: string; resolvedPath: string | null }> = [];
|
|
197
329
|
const manifest = plugin.manifest;
|
|
198
330
|
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
331
|
+
const expandDirectory = key === "extensions";
|
|
332
|
+
const resolveEntry = (entry: string): Array<{ entry: string; resolvedPath: string | null }> => {
|
|
333
|
+
const files = resolveManifestEntryFiles(path.join(plugin.path, entry), expandDirectory);
|
|
334
|
+
return files.length > 0 ? files.map(resolvedPath => ({ entry, resolvedPath })) : [{ entry, resolvedPath: null }];
|
|
335
|
+
};
|
|
203
336
|
|
|
204
337
|
const base = manifest[key];
|
|
205
338
|
if (base) {
|
|
206
339
|
const entries = Array.isArray(base) ? base : [base];
|
|
207
340
|
for (const entry of entries) {
|
|
208
|
-
declared.push(resolveEntry(entry));
|
|
341
|
+
declared.push(...resolveEntry(entry));
|
|
209
342
|
}
|
|
210
343
|
}
|
|
211
344
|
|
|
@@ -215,7 +348,7 @@ export function resolvePluginManifestEntries(
|
|
|
215
348
|
if (!enabledSet.has(featName)) continue;
|
|
216
349
|
if (feat[key]) {
|
|
217
350
|
for (const entry of feat[key]) {
|
|
218
|
-
declared.push(resolveEntry(entry));
|
|
351
|
+
declared.push(...resolveEntry(entry));
|
|
219
352
|
}
|
|
220
353
|
}
|
|
221
354
|
}
|
|
@@ -225,7 +358,7 @@ export function resolvePluginManifestEntries(
|
|
|
225
358
|
if (!feat.default) continue;
|
|
226
359
|
if (feat[key]) {
|
|
227
360
|
for (const entry of feat[key]) {
|
|
228
|
-
declared.push(resolveEntry(entry));
|
|
361
|
+
declared.push(...resolveEntry(entry));
|
|
229
362
|
}
|
|
230
363
|
}
|
|
231
364
|
}
|
|
@@ -205,6 +205,17 @@ export class PluginManager {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
#collectInstalledNames(deps: Record<string, string>, config: PluginRuntimeConfig): Set<string> {
|
|
209
|
+
const installedNames = new Set<string>();
|
|
210
|
+
for (const name of Object.keys(deps)) {
|
|
211
|
+
installedNames.add(name);
|
|
212
|
+
}
|
|
213
|
+
for (const name of Object.keys(config.plugins)) {
|
|
214
|
+
installedNames.add(name);
|
|
215
|
+
}
|
|
216
|
+
return installedNames;
|
|
217
|
+
}
|
|
218
|
+
|
|
208
219
|
async #snapshotInstalledPackage(actualName: string | undefined): Promise<PluginPackageSnapshot | null> {
|
|
209
220
|
if (!actualName) {
|
|
210
221
|
return null;
|
|
@@ -490,21 +501,22 @@ export class PluginManager {
|
|
|
490
501
|
*/
|
|
491
502
|
async list(): Promise<InstalledPlugin[]> {
|
|
492
503
|
const pkgJsonPath = getPluginsPackageJson();
|
|
493
|
-
let
|
|
504
|
+
let deps: Record<string, string> = {};
|
|
494
505
|
try {
|
|
495
|
-
pkg = await Bun.file(pkgJsonPath).json();
|
|
506
|
+
const pkg: { dependencies?: Record<string, string> } = await Bun.file(pkgJsonPath).json();
|
|
507
|
+
deps = pkg.dependencies ?? {};
|
|
496
508
|
} catch (err) {
|
|
497
|
-
if (isEnoent(err))
|
|
498
|
-
throw err;
|
|
509
|
+
if (!isEnoent(err)) throw err;
|
|
499
510
|
}
|
|
500
511
|
|
|
501
|
-
const deps = pkg.dependencies || {};
|
|
502
512
|
const projectOverrides = await this.#loadProjectOverrides();
|
|
503
513
|
const config = await this.#ensureConfigLoaded();
|
|
504
514
|
const plugins: InstalledPlugin[] = [];
|
|
515
|
+
const installedNames = this.#collectInstalledNames(deps, config);
|
|
505
516
|
|
|
506
|
-
for (const
|
|
507
|
-
const
|
|
517
|
+
for (const name of installedNames) {
|
|
518
|
+
const pluginPath = path.join(getPluginsNodeModules(), name);
|
|
519
|
+
const pluginPkgPath = path.join(pluginPath, "package.json");
|
|
508
520
|
let pluginPkg: { version: string; omp?: PluginManifest; pi?: PluginManifest };
|
|
509
521
|
try {
|
|
510
522
|
pluginPkg = await Bun.file(pluginPkgPath).json();
|
|
@@ -521,14 +533,13 @@ export class PluginManager {
|
|
|
521
533
|
enabled: true,
|
|
522
534
|
};
|
|
523
535
|
|
|
524
|
-
// Apply project overrides
|
|
525
536
|
const isDisabledInProject = projectOverrides.disabled?.includes(name) ?? false;
|
|
526
537
|
const projectFeatures = projectOverrides.features?.[name];
|
|
527
538
|
|
|
528
539
|
plugins.push({
|
|
529
540
|
name,
|
|
530
541
|
version: pluginPkg.version,
|
|
531
|
-
path:
|
|
542
|
+
path: pluginPath,
|
|
532
543
|
manifest,
|
|
533
544
|
enabledFeatures: projectFeatures ?? runtimeState.enabledFeatures,
|
|
534
545
|
enabled: runtimeState.enabled && !isDisabledInProject,
|
|
@@ -744,15 +755,14 @@ export class PluginManager {
|
|
|
744
755
|
message: hasNodeModules ? "Found" : "Missing (run npm install in plugins dir)",
|
|
745
756
|
});
|
|
746
757
|
|
|
747
|
-
if (!hasPkgJson) {
|
|
748
|
-
return checks;
|
|
749
|
-
}
|
|
750
758
|
const deps = pkg.dependencies || {};
|
|
751
759
|
const config = await this.#ensureConfigLoaded();
|
|
760
|
+
const installedNames = this.#collectInstalledNames(deps, config);
|
|
752
761
|
|
|
753
|
-
for (const
|
|
762
|
+
for (const name of installedNames) {
|
|
754
763
|
const pluginPath = path.join(nodeModulesPath, name);
|
|
755
764
|
const pluginPkgPath = path.join(pluginPath, "package.json");
|
|
765
|
+
const fromDependencies = name in deps;
|
|
756
766
|
|
|
757
767
|
let pluginPkg: { version: string; description?: string; omp?: PluginManifest; pi?: PluginManifest };
|
|
758
768
|
try {
|
|
@@ -760,13 +770,23 @@ export class PluginManager {
|
|
|
760
770
|
} catch (err) {
|
|
761
771
|
if (isEnoent(err)) {
|
|
762
772
|
if (!fs.existsSync(pluginPath)) {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
773
|
+
if (fromDependencies) {
|
|
774
|
+
const fixed = options.fix ? await this.#fixMissingPlugin() : false;
|
|
775
|
+
checks.push({
|
|
776
|
+
name: `plugin:${name}`,
|
|
777
|
+
status: "error",
|
|
778
|
+
message: "Missing from node_modules",
|
|
779
|
+
fixed,
|
|
780
|
+
});
|
|
781
|
+
} else {
|
|
782
|
+
const fixed = options.fix ? await this.#removeOrphanedConfig(name) : false;
|
|
783
|
+
checks.push({
|
|
784
|
+
name: `orphan:${name}`,
|
|
785
|
+
status: "warning",
|
|
786
|
+
message: "Plugin in config but not installed",
|
|
787
|
+
fixed,
|
|
788
|
+
});
|
|
789
|
+
}
|
|
770
790
|
} else {
|
|
771
791
|
checks.push({
|
|
772
792
|
name: `plugin:${name}`,
|
|
@@ -844,19 +864,6 @@ export class PluginManager {
|
|
|
844
864
|
}
|
|
845
865
|
}
|
|
846
866
|
|
|
847
|
-
// Check for orphaned runtime config entries
|
|
848
|
-
for (const name of Object.keys(config.plugins)) {
|
|
849
|
-
if (!(name in deps)) {
|
|
850
|
-
const fixed = options.fix ? await this.#removeOrphanedConfig(name) : false;
|
|
851
|
-
checks.push({
|
|
852
|
-
name: `orphan:${name}`,
|
|
853
|
-
status: "warning",
|
|
854
|
-
message: "Plugin in config but not installed",
|
|
855
|
-
fixed,
|
|
856
|
-
});
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
|
|
860
867
|
return checks;
|
|
861
868
|
}
|
|
862
869
|
|
package/src/goals/runtime.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { prompt, Snowflake } from "@oh-my-pi/pi-utils";
|
|
1
|
+
import { escapeXmlText, prompt, Snowflake } from "@oh-my-pi/pi-utils";
|
|
2
2
|
import goalBudgetLimitPrompt from "../prompts/goals/goal-budget-limit.md" with { type: "text" };
|
|
3
3
|
import goalContinuationPrompt from "../prompts/goals/goal-continuation.md" with { type: "text" };
|
|
4
4
|
import goalModeActivePrompt from "../prompts/goals/goal-mode-active.md" with { type: "text" };
|
|
@@ -58,28 +58,6 @@ export function remainingTokens(goal: Goal | null | undefined): number | null {
|
|
|
58
58
|
return Math.max(0, goal.tokenBudget - goal.tokensUsed);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
export function escapeXmlText(input: string): string {
|
|
62
|
-
let firstEscapable = -1;
|
|
63
|
-
for (let index = 0; index < input.length; index++) {
|
|
64
|
-
const char = input.charCodeAt(index);
|
|
65
|
-
if (char === 38 || char === 60 || char === 62) {
|
|
66
|
-
firstEscapable = index;
|
|
67
|
-
break;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (firstEscapable === -1) return input;
|
|
71
|
-
|
|
72
|
-
let output = input.slice(0, firstEscapable);
|
|
73
|
-
for (let index = firstEscapable; index < input.length; index++) {
|
|
74
|
-
const char = input[index];
|
|
75
|
-
if (char === "&") output += "&";
|
|
76
|
-
else if (char === "<") output += "<";
|
|
77
|
-
else if (char === ">") output += ">";
|
|
78
|
-
else output += char;
|
|
79
|
-
}
|
|
80
|
-
return output;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
61
|
export function renderTrustedObjective(objective: string): string {
|
|
84
62
|
return `<objective>\n${escapeXmlText(objective)}\n</objective>`;
|
|
85
63
|
}
|